Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added paper view #89

Merged
merged 24 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d4a266a
feat: add css for global container
Slartibartfass2 Nov 18, 2024
072348c
feat: add basic paper view layout
Slartibartfass2 Nov 18, 2024
dbb2f5f
feat: add basic paper card layout
Slartibartfass2 Nov 18, 2024
5f56fc7
feat: add paper details card and paper research context card
Slartibartfass2 Nov 18, 2024
969fb5e
feat: add button component
Slartibartfass2 Nov 21, 2024
6f80bbe
feat: make paper view page a component
Slartibartfass2 Nov 21, 2024
b0455ea
feat: add paper navigation buttons
Slartibartfass2 Nov 21, 2024
fed233d
feat: add bookmark button
Slartibartfass2 Nov 21, 2024
8f58a47
feat: add paper view component to project-dependent paper view
Slartibartfass2 Nov 21, 2024
0d3a179
feat: add tooltip component
Slartibartfass2 Nov 21, 2024
91c5cd9
feat: add reusable tooltip
Slartibartfass2 Nov 22, 2024
959018b
feat: add decision buttons
Slartibartfass2 Nov 21, 2024
f89a1d5
feat: use own tooltip component for bookmark and paper navigation button
Slartibartfass2 Nov 22, 2024
9172acc
docs: add component documentation for exisiting components
Slartibartfass2 Nov 22, 2024
e23b191
feat: improved accessibility
Slartibartfass2 Nov 23, 2024
f77361e
test: add tests for button components
Slartibartfass2 Nov 25, 2024
ad34fd4
feat: use default values for navigation bar components
Slartibartfass2 Nov 28, 2024
5300e0f
test: add tests for remaining components
Slartibartfass2 Nov 28, 2024
e45ee13
chore: fix import and types after rebasing
Slartibartfass2 Dec 2, 2024
5fc55a9
feat: remove project layout file
Slartibartfass2 Dec 3, 2024
dcc000c
fix: adjust types after rebase
Slartibartfass2 Dec 5, 2024
20713d4
refactor: Move bookmark state handling to html code
Slartibartfass2 Dec 9, 2024
dbf00cb
chore: rename tab name
Slartibartfass2 Dec 9, 2024
11fe5f0
refactor: move underlined tabs list to separate component
Slartibartfass2 Dec 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 81 additions & 0 deletions src/lib/components/composites/PaperBookmarkButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<script lang="ts">
import { buttonVariants } from "$lib/components/primitives/button/index.js";
import Bookmark from "lucide-svelte/icons/bookmark";
import BookmarkPlus from "lucide-svelte/icons/bookmark-plus";
import BookmarkMinus from "lucide-svelte/icons/bookmark-minus";
import { cn } from "$lib/utils";
import Tooltip from "./Tooltip.svelte";

interface Props {
paperId: number;
isBookmarkedDefault: boolean;
}

const { paperId, isBookmarkedDefault }: Props = $props();

let isBookmarked = $state(isBookmarkedDefault);
let isHovered = $state(false);
const tooltipText = $derived(isBookmarked ? "Remove from Reading List" : "Add to Reading List");

const onMouseEnter = () => (isHovered = true);
const onMouseLeave = () => (isHovered = false);

function onClick() {
if (isBookmarked) {
removePaperFromReadingList();
} else {
addPaperToReadingList();
}
}

function addPaperToReadingList() {
// TODO: Will be implemented in #99
isBookmarked = true;
console.log(`Added paper with id ${paperId} to reading list`);
}
function removePaperFromReadingList() {
// TODO: Will be implemented in #100
isBookmarked = false;
console.log(`Removed paper with id ${paperId} from reading list`);
}
</script>

<!--
@component
Button to add a paper to or remove a paper from the reading list.

This component will handle the API calls itself.
According to bookmark state of the paper (isBookmarked), the button will change its appearance.

Usage:
```svelte
<PaperBookmarkButton paperId={42} isBookmarked={false} />
```
-->
<Tooltip
class={cn(
buttonVariants(),
"border border-container-border-grey bg-transparent hover:bg-transparent text-primary p-1.5 w-fit h-fit",
)}
onmouseenter={onMouseEnter}
onmouseleave={onMouseLeave}
onclick={onClick}
aria-label={tooltipText}
>
{#snippet trigger()}
{#if isHovered}
{#if isBookmarked}
<BookmarkMinus />
{:else}
<BookmarkPlus />
{/if}
{:else if isBookmarked}
<Bookmark fill="bg-primary" />
{:else}
<Bookmark />
{/if}
{/snippet}
{#snippet content()}
<p>{tooltipText}</p>
{/snippet}
</Tooltip>
57 changes: 57 additions & 0 deletions src/lib/components/composites/Tooltip.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<script lang="ts">
import { buttonVariants } from "$lib/components/primitives/button/index.js";
import * as Tooltip from "$lib/components/primitives/tooltip/index.js";
import { cn } from "$lib/utils";
import type { TooltipTriggerProps, WithElementRef } from "bits-ui";
import type { Snippet } from "svelte";

type Props = WithElementRef<TooltipTriggerProps> & {
trigger: Snippet;
content: Snippet;
buttonVariant?:
| "default"
| "link"
| "destructive"
| "outline"
| "secondary"
| "ghost"
| undefined;
};

const { trigger, content, buttonVariant, class: className, ...restProps }: Props = $props();
</script>

<!--
@component
Reusable tooltip component that wraps a trigger and content component.
The tooltip can't be used as link. To still redirect on click, use the `onclick` prop.

Usage:
```svelte
<Tooltip
class="text-primary shadow-sm"
buttonVariant="default"
onclick={() => goto(href)}
>
{#snippet trigger()}
I'm a trigger
{/snippet}
{#snippet content()}
This shows a tooltip
{/snippet}
</Tooltip>
```
-->
<Tooltip.Provider>
luca-schlecker marked this conversation as resolved.
Show resolved Hide resolved
<Tooltip.Root>
<Tooltip.Trigger
class={cn(buttonVariants({ variant: buttonVariant }), className)}
{...restProps}
>
{@render trigger()}
</Tooltip.Trigger>
<Tooltip.Content>
{@render content()}
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@
interface Props {
user: User;
backRef?: string | undefined;
tabs: Tab[];
defaultTabValue: (typeof tabs)[number]["value"];
tabs?: Tab[] | undefined;
defaultTabValue?: (typeof tabs)[number]["value"] | undefined;
children?: Snippet | undefined;
}

const {
user,
backRef = undefined,
tabs,
defaultTabValue,
tabs = [],
defaultTabValue = "",
children = undefined,
}: Props = $props();
</script>
Expand All @@ -29,7 +29,9 @@
<nav class="grid grid-flow-col gap-3 items-center px-4 py-3">
<UserMenu {user} />
{#if backRef !== undefined}
<a href={backRef}><ArrowLeft class="w-6 h-6" /></a>
<a href={backRef} aria-label={`Back to ${backRef}`}>
<ArrowLeft class="w-6 h-6" />
</a>
{/if}
<!-- Children can be e.g. a title element -->
{@render children?.()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
const { user, backRef, paper }: Props = $props();
</script>

<NavigationBar {user} {backRef} tabs={[]} defaultTabValue="">
<NavigationBar {user} {backRef}>
<PaperInfo {paper} />
</NavigationBar>
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
user: User;
backRef?: string | undefined;
title: string;
tabs: Tab[];
defaultTabValue: (typeof tabs)[number]["value"];
tabs?: Tab[] | undefined;
defaultTabValue?: (typeof tabs)[number]["value"] | undefined;
}

const { user, backRef = undefined, title, tabs, defaultTabValue }: Props = $props();
const { user, backRef = undefined, title, tabs = [], defaultTabValue = "" }: Props = $props();
</script>

<NavigationBar {user} {backRef} {tabs} {defaultTabValue}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<script lang="ts">
import ArrowLeft from "lucide-svelte/icons/arrow-left";
import ArrowRight from "lucide-svelte/icons/arrow-right";
import Tooltip from "../../Tooltip.svelte";
import { goto } from "$app/navigation";

interface Props {
direction: "left" | "right";
href: string;
onClick?: () => void;
}

const { direction, href, onClick }: Props = $props();
const tooltipText = direction === "left" ? "Previous Paper" : "Next Paper";
</script>

<!--
@component
Button that navigates to the next or previous paper.

This component is used in the PaperView component to navigate between papers.
When the button is clicked, the `onClick` function is called and the user is navigated to the `href` location.

Usage:
```svelte
<PaperNavigationButton
direction="left"
href="/papers/1"
onClick={() => console.log("button was clicked")}
/>
```
-->
<Tooltip
class="bg-slate-200 hover:bg-slate-400 text-primary flex-grow max-w-xs shadow-lg min-w-32"
buttonVariant="link"
onclick={() => {
if (onClick) onClick();
goto(href);
}}
aria-label={tooltipText}
data-testid="navigation-button"
>
{#snippet trigger()}
{#if direction === "left"}
<ArrowLeft />
{:else}
<ArrowRight />
{/if}
{/snippet}
{#snippet content()}
<p>{tooltipText}</p>
{/snippet}
</Tooltip>
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<script lang="ts">
import PaperNavigationBar from "$lib/components/composites/navigation-bar/PaperNavigationBar.svelte";
import PaperDetailsCard from "$lib/components/composites/paper-components/paper-view/cards/PaperDetailsCard.svelte";
import PaperResearchContextCard from "$lib/components/composites/paper-components/paper-view/cards/PaperResearchContextCard.svelte";
import type { Paper, User } from "$lib/model/backend";
import PaperBookmarkButton from "../../PaperBookmarkButton.svelte";
import AcceptButton from "./decision-buttons/AcceptButton.svelte";
import DeclineButton from "./decision-buttons/DeclineButton.svelte";
import MaybeButton from "./decision-buttons/MaybeButton.svelte";
import PaperNavigationButton from "./PaperNavigationButton.svelte";

interface Props {
user: User;
paper: Paper;
isPaperBookmarked?: boolean;
showButtonBar: boolean;
backRef: string;
userConfig: {
isReviewMode: boolean;
showMaybeButton: boolean;
};
}

const {
user,
paper,
isPaperBookmarked = false,
showButtonBar,
backRef,
userConfig,
}: Props = $props();
</script>

<!--
@component
Whole page component to display information about a paper.
In the bottom, there are buttons to accept, decline or mark the paper as undecided.
Additonally, there are buttons to navigate to the previous or next paper.

- when `showButtonBar` is false, then no buttons are shown at the bottom of the page
- when `userConfig.isReviewMode` is false, then no decision buttons are shown
- when `userConfig.showMaybeButton` is false, then the maybe button is not shown

Usage:
```svelte
<PaperView
user={user}
paper={paper}
showButtonBar={true}
backRef="/"
userConfig={{
isReviewMode: true,
showMaybeButton: true,
}}
/>
```
-->
<div class="flex flex-row justify-between h-fit w-full gap-4">
<PaperNavigationBar {user} {backRef} {paper} />
<PaperBookmarkButton paperId={paper.id} isBookmarkedDefault={isPaperBookmarked} />
</div>
<main class="flex flex-col h-full w-full px-2 py-4 gap-5">
<div class="flex flex-row w-full h-full gap-5">
<PaperDetailsCard />
<PaperResearchContextCard />
</div>
{#if showButtonBar}
<div class="flex flex-row w-full h-fit justify-between gap-4" data-testid="button-bar">
<!-- TODO: Implementation of navigation buttons will be done in #46 and #47 -->
<PaperNavigationButton direction="left" href="" />
luca-schlecker marked this conversation as resolved.
Show resolved Hide resolved
{#if userConfig.isReviewMode}
<!-- flex grow is very high so that it grows first, before the navigation buttons do -->
<!-- max-width is max-width of buttons + gap, which is the reason why they have fixed values -->
<div class="flex flex-grow-1000 max-w-[62rem] gap-[1rem] justify-center">
<DeclineButton paperId={paper.id} />
{#if userConfig.showMaybeButton}
<MaybeButton paperId={paper.id} />
{/if}
<AcceptButton paperId={paper.id} />
</div>
luca-schlecker marked this conversation as resolved.
Show resolved Hide resolved
{/if}
<PaperNavigationButton direction="right" href="" />
</div>
{/if}
</main>
luca-schlecker marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script lang="ts">
import type { Tab } from "$lib/components/composites/tabs/tab-props";
import UnderlineTabsList from "$lib/components/composites/tabs/UnderlineTabsList.svelte";
import * as Card from "$lib/components/primitives/card/index.js";
import * as Tabs from "$lib/components/primitives/tabs/index.js";
import type { Snippet } from "svelte";

interface Props {
tabs: Tab[];
children: Snippet;
}
const { tabs, children }: Props = $props();
</script>

<!-- Use PaperCardContent elements as children with values according to the tabs props -->
<!--
@component
Paper Card used in the Paper View component.

For each tab in the tabs prop, a tab is created with the label and value of the tab.
The children of the PaperCard component are rendered in the content of the tab.

Usage:
```svelte
<PaperCard tabs={[{ value: "details", label: "Details" }, { value: "research-context", label: "Research Context" }]}>
<PaperCardContent value="details">
<p>Details content</p>
</PaperCardContent>
<PaperCardContent value="research-context">
<p>Research context content</p>
</PaperCardContent>
</PaperCard>
```
-->
<Card.Root class="shadow-lg border-container-border-grey w-full h-full">
<section>
<Tabs.Root value={tabs.length == 0 ? "" : tabs[0].value}>
<UnderlineTabsList {tabs} />
<Card.Content class="p-5">
luca-schlecker marked this conversation as resolved.
Show resolved Hide resolved
{@render children()}
</Card.Content>
</Tabs.Root>
</section>
</Card.Root>
Loading
Loading