Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"@sveltejs/vite-plugin-svelte": "6.1.3",
"@tailwindcss/vite": "4.1.12",
"@types/node": "24.3.0",
"bits-ui": "2.9.4",
"bits-ui": "2.11.3",
"clsx": "2.1.1",
"eslint": "9.33.0",
"eslint-config-prettier": "10.1.8",
Expand Down
44 changes: 27 additions & 17 deletions pnpm-lock.yaml

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

20 changes: 15 additions & 5 deletions src/components/layout/sidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,34 @@
import { Separator } from "@/components/ui/separator";
import ShowCategories from "@/components/layout/showCategories.svelte";
import ShowSidebarLinks from "@/components/layout/showSidebarLinks.svelte";
import ScrollArea from "@/components/ui/scroll-area/scroll-area.svelte";
import ScrollAreaScrollbar from "@/components/ui/scroll-area/scroll-area-scrollbar.svelte";
</script>

<section>
<aside
class={cn(
"md:fixed md:left-1 md:h-[calc(100vh-4.5rem)]",
"overflow-x-hidden",
"w-54 pr-2 pl-2",
"w-54",
"hidden flex-col space-y-3 md:flex",
"bg-neutral-100 dark:bg-neutral-950",
)}
>
<nav class="flex flex-col space-y-0.5">
<nav class="flex flex-col space-y-0.5 px-2">
<ShowSidebarLinks />
</nav>
<Separator orientation="horizontal" />
<nav class="relative flex h-auto flex-col space-y-0.5 overflow-y-auto">
<ShowCategories />
<div class="px-2">
<Separator orientation="horizontal" />
</div>
<nav class="relative flex h-auto flex-col space-y-0.5 overflow-hidden">
<ScrollArea
maskClassName="before:from-neutral-100 after:from-neutral-100 dark:before:from-neutral-950 dark:after:from-neutral-950"
class="flex size-full flex-col gap-y-0.5 overflow-hidden px-2"
>
<ShowCategories />
<ScrollAreaScrollbar orientation="vertical" />
</ScrollArea>
</nav>
</aside>
<main class={cn("px-2 md:mr-4 md:ml-56 md:px-0", "overflow-hidden")}>
Expand Down
9 changes: 7 additions & 2 deletions src/components/layout/sidebarMobileMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import ShowCategories from "@/components/layout/showCategories.svelte";
import ShowSidebarLinks from "@/components/layout/showSidebarLinks.svelte";
import ScrollArea from "@/components/ui/scroll-area/scroll-area.svelte";

interface Props {
className?: string;
Expand All @@ -33,10 +34,14 @@
<h2 class="text-xl font-medium tracking-tight">svgl</h2>
</Sheet.Title>
</Sheet.Header>
<nav class="flex flex-col space-y-0.5 overflow-y-auto px-3 pb-3">
<ScrollArea
class="flex size-full flex-col"
viewportClassName="pb-3 px-3 space-y-0.5"
maskClassName="before:from-white after:from-white dark:before:from-neutral-900 dark:after:from-neutral-900"
>
<ShowSidebarLinks />
<Separator orientation="horizontal" class="my-3" />
<ShowCategories />
</nav>
</ScrollArea>
</Sheet.Content>
</Sheet.Root>
12 changes: 8 additions & 4 deletions src/components/pageCard.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script lang="ts">
import type { Snippet } from "svelte";
import { cn } from "@/utils/cn";
import ScrollArea from "./ui/scroll-area/scroll-area.svelte";
import { ScrollAreaScrollbar } from "./ui/scroll-area";

interface PageCardProps {
children: Snippet;
Expand All @@ -21,14 +23,16 @@
containerClass,
)}
>
<div
<ScrollArea
maskHeight={50}
maskClassName="before:from-transparent after:from-white dark:before:from-[#0f0f0f] dark:after:from-[#0f0f0f]"
class={cn(
"max-h-[calc(100vh-4.5rem)] min-h-[calc(100vh-4.5rem)]",
"overflow-hidden overflow-y-auto",
"flex size-full max-h-[calc(100vh-4.5rem)] min-h-[calc(100vh-4.5rem)] flex-col",
contentCardClass,
)}
>
<ScrollAreaScrollbar orientation="vertical" class="relative z-99999" />
{@render children?.()}
</div>
</ScrollArea>
</div>
</div>
13 changes: 13 additions & 0 deletions src/components/ui/scroll-area/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Scrollbar from "./scroll-area-scrollbar.svelte";
import Root from "./scroll-area.svelte";
import Mask from "./scroll-area-mask.svelte";

export {
Root,
Scrollbar,
Mask,
//
Root as ScrollArea,
Scrollbar as ScrollAreaScrollbar,
Mask as ScrollAreaMask,
};
50 changes: 50 additions & 0 deletions src/components/ui/scroll-area/scroll-area-mask.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script lang="ts">
import { cn } from "@/utils/cn";
type Mask = {
top: boolean;
bottom: boolean;
left: boolean;
right: boolean;
};

let {
showMask,
maskHeight,
class: className = "",
}: {
showMask: Mask;
maskHeight: number;
class?: string;
} = $props();
</script>

<div
aria-hidden="true"
style={`--top-fade-height: ${showMask.top ? `${maskHeight}px` : "0px"}; --bottom-fade-height: ${showMask.bottom ? `${maskHeight}px` : "0px"};`}
class={cn(
"pointer-events-none absolute inset-0 z-10",
"before:absolute before:inset-x-0 before:top-0 before:transition-[height,opacity] before:duration-300 before:content-['']",
"after:absolute after:inset-x-0 after:bottom-0 after:transition-[height,opacity] after:duration-300 after:content-['']",
"before:h-(--top-fade-height) after:h-(--bottom-fade-height)",
showMask.top ? "before:opacity-100" : "before:opacity-0",
showMask.bottom ? "after:opacity-100" : "after:opacity-0",
"before:from-background before:bg-gradient-to-b before:to-transparent",
"after:from-background after:bg-gradient-to-t after:to-transparent",
className,
)}
></div>
<div
aria-hidden="true"
style={`--left-fade-width: ${showMask.left ? `${maskHeight}px` : "0px"}; --right-fade-width: ${showMask.right ? `${maskHeight}px` : "0px"};`}
class={cn(
"pointer-events-none absolute inset-0 z-10",
"before:absolute before:inset-y-0 before:left-0 before:transition-[width,opacity] before:duration-300 before:content-['']",
"after:absolute after:inset-y-0 after:right-0 after:transition-[width,opacity] after:duration-300 after:content-['']",
"before:w-(--left-fade-width) after:w-(--right-fade-width)",
showMask.left ? "before:opacity-100" : "before:opacity-0",
showMask.right ? "after:opacity-100" : "after:opacity-0",
"before:from-background before:bg-gradient-to-r before:to-transparent",
"after:from-background after:bg-gradient-to-l after:to-transparent",
className,
)}
></div>
44 changes: 44 additions & 0 deletions src/components/ui/scroll-area/scroll-area-scrollbar.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script lang="ts">
import { ScrollArea as ScrollAreaPrimitive } from "bits-ui";
import { cn, type WithoutChild } from "@/utils/cn";
import { useHasPrimaryTouch } from "@/hooks/use-has-primary-touch";

let {
ref = $bindable(null),
class: className,
orientation = "vertical",
children,
thumbClassName,
...restProps
}: WithoutChild<ScrollAreaPrimitive.ScrollbarProps> & {
thumbClassName?: string;
} = $props();

const hasPrimaryTouch = useHasPrimaryTouch();
</script>

{#if !$hasPrimaryTouch}
<ScrollAreaPrimitive.Scrollbar
bind:ref
data-slot="scroll-area-scrollbar"
{orientation}
class={cn(
"flex touch-none p-px transition-[colors] duration-150 select-none hover:bg-neutral-200 data-[state=hidden]:animate-out data-[state=hidden]:fade-out-0 data-[state=visible]:animate-in data-[state=visible]:fade-in-0 dark:hover:bg-neutral-900",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent px-1 pr-1.25",
className,
)}
{...restProps}
>
{@render children?.()}
<ScrollAreaPrimitive.Thumb
data-slot="scroll-area-thumb"
class={cn(
"relative my-0.5 flex-1 rounded-full bg-neutral-300 transition-colors ease-out hover:bg-neutral-500/50 active:bg-neutral-500/75 dark:bg-neutral-800 dark:hover:bg-neutral-700 dark:active:bg-neutral-600",
thumbClassName,
)}
/>
</ScrollAreaPrimitive.Scrollbar>
{/if}
Loading