From 4014cf024bc0ea3a99d07f12f843b9a2625bda63 Mon Sep 17 00:00:00 2001 From: TZGyn Date: Sat, 16 Nov 2024 14:09:07 +0800 Subject: [PATCH] update sidebar to use shadcn svelte next --- frontend/bun.lockb | Bin 227353 -> 227353 bytes frontend/package.json | 2 +- .../src/lib/components/app-sidebar.svelte | 97 +++++++++ frontend/src/lib/components/nav-main.svelte | 36 +++ frontend/src/lib/components/nav-user.svelte | 163 ++++++++++++++ .../src/lib/components/team-switcher.svelte | 69 ++++++ .../lib/components/ui/collapsible/index.ts | 15 ++ .../lib/components/ui/sidebar/constants.ts | 6 + .../components/ui/sidebar/context.svelte.ts | 81 +++++++ .../src/lib/components/ui/sidebar/index.ts | 75 +++++++ .../ui/sidebar/sidebar-content.svelte | 24 ++ .../ui/sidebar/sidebar-footer.svelte | 21 ++ .../ui/sidebar/sidebar-group-action.svelte | 36 +++ .../ui/sidebar/sidebar-group-content.svelte | 21 ++ .../ui/sidebar/sidebar-group-label.svelte | 34 +++ .../ui/sidebar/sidebar-group.svelte | 21 ++ .../ui/sidebar/sidebar-header.svelte | 21 ++ .../ui/sidebar/sidebar-input.svelte | 23 ++ .../ui/sidebar/sidebar-inset.svelte | 24 ++ .../ui/sidebar/sidebar-menu-action.svelte | 43 ++++ .../ui/sidebar/sidebar-menu-badge.svelte | 29 +++ .../ui/sidebar/sidebar-menu-button.svelte | 97 +++++++++ .../ui/sidebar/sidebar-menu-item.svelte | 21 ++ .../ui/sidebar/sidebar-menu-skeleton.svelte | 36 +++ .../ui/sidebar/sidebar-menu-sub-button.svelte | 43 ++++ .../ui/sidebar/sidebar-menu-sub-item.svelte | 14 ++ .../ui/sidebar/sidebar-menu-sub.svelte | 25 +++ .../components/ui/sidebar/sidebar-menu.svelte | 21 ++ .../ui/sidebar/sidebar-provider.svelte | 59 +++++ .../components/ui/sidebar/sidebar-rail.svelte | 36 +++ .../ui/sidebar/sidebar-separator.svelte | 18 ++ .../ui/sidebar/sidebar-trigger.svelte | 34 +++ .../lib/components/ui/sidebar/sidebar.svelte | 98 +++++++++ frontend/src/lib/hooks/is-mobile.svelte.ts | 27 +++ .../src/routes/(app)/dashboard/+layout.svelte | 206 ++++-------------- .../(app)/dashboard/billing/+page.server.ts | 4 + 36 files changed, 1417 insertions(+), 163 deletions(-) create mode 100644 frontend/src/lib/components/app-sidebar.svelte create mode 100644 frontend/src/lib/components/nav-main.svelte create mode 100644 frontend/src/lib/components/nav-user.svelte create mode 100644 frontend/src/lib/components/team-switcher.svelte create mode 100644 frontend/src/lib/components/ui/collapsible/index.ts create mode 100644 frontend/src/lib/components/ui/sidebar/constants.ts create mode 100644 frontend/src/lib/components/ui/sidebar/context.svelte.ts create mode 100644 frontend/src/lib/components/ui/sidebar/index.ts create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-content.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-footer.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-group-action.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-group-content.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-group-label.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-group.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-header.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-input.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-inset.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-menu-action.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-menu-button.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-menu-item.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-menu.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-provider.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-rail.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-separator.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar-trigger.svelte create mode 100644 frontend/src/lib/components/ui/sidebar/sidebar.svelte create mode 100644 frontend/src/lib/hooks/is-mobile.svelte.ts diff --git a/frontend/bun.lockb b/frontend/bun.lockb index 2672e657a991dc841d482eae0365702287828a4e..a0fb77041dacb3f232a55f06d5b0389defaec12f 100755 GIT binary patch delta 32 ocmbRFfp_Ky-i8*&ElfFy90qzOre=Bu4DBU}OxsHmnU{(I0K{YpzyJUM delta 32 ocmbRFfp_Ky-i8*&ElfFy9B~GECZ=Y32JIz@OxsHmnU{(I0Li5b9smFU diff --git a/frontend/package.json b/frontend/package.json index 3917680..40def35 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -52,7 +52,7 @@ "drizzle-orm": "^0.32.1", "formsnap": "^2.0.0-next.1", "he": "^1.2.0", - "lucide-svelte": "^0.456.0", + "lucide-svelte": "0.456.0", "mode-watcher": "^0.4.1", "nanoid": "^5.0.3", "node-html-parser": "^6.1.12", diff --git a/frontend/src/lib/components/app-sidebar.svelte b/frontend/src/lib/components/app-sidebar.svelte new file mode 100644 index 0000000..1dd3703 --- /dev/null +++ b/frontend/src/lib/components/app-sidebar.svelte @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + diff --git a/frontend/src/lib/components/nav-main.svelte b/frontend/src/lib/components/nav-main.svelte new file mode 100644 index 0000000..2a5ed8a --- /dev/null +++ b/frontend/src/lib/components/nav-main.svelte @@ -0,0 +1,36 @@ + + + + Dashboard + + {#each items as item (item.title)} + + + {#snippet child({ props })} + + {#if item.icon} + + {/if} + {item.title} + + {/snippet} + + + {/each} + + diff --git a/frontend/src/lib/components/nav-user.svelte b/frontend/src/lib/components/nav-user.svelte new file mode 100644 index 0000000..591b360 --- /dev/null +++ b/frontend/src/lib/components/nav-user.svelte @@ -0,0 +1,163 @@ + + + + + + + {#snippet child({ props })} + + + + + + + +
+ {user.name} + {user.email} +
+ +
+ {/snippet} +
+ + +
+ + + + + + +
+ {user.name} + {user.email} +
+
+
+ + {#if !user.isPro} + + + + + Upgrade to Pro + + + + + {/if} + + + + + Account + + + + + + Billing + + + + + (dialogOpen = true)} + class="text-destructive data-[highlighted]:bg-destructive data-[highlighted]:text-white"> + + Log out + +
+
+
+
+ + + + +
+
+ +
+
+
+ Log Out? + + You are about to log out of this account. + +
+
+ + +
+
+
+
diff --git a/frontend/src/lib/components/team-switcher.svelte b/frontend/src/lib/components/team-switcher.svelte new file mode 100644 index 0000000..d8a4272 --- /dev/null +++ b/frontend/src/lib/components/team-switcher.svelte @@ -0,0 +1,69 @@ + + + + + + + {#snippet child({ props })} + +
+ +
+
+ + {activeTeam.name} + + {activeTeam.plan} +
+ +
+ {/snippet} +
+ + Teams + {#each teams as team, index (team.name)} + (activeTeam = team)} class="gap-2 p-2"> +
+ +
+ {team.name} + ⌘{index + 1} +
+ {/each} + + +
+ +
+
Add team
+
+
+
+
+
diff --git a/frontend/src/lib/components/ui/collapsible/index.ts b/frontend/src/lib/components/ui/collapsible/index.ts new file mode 100644 index 0000000..83c0198 --- /dev/null +++ b/frontend/src/lib/components/ui/collapsible/index.ts @@ -0,0 +1,15 @@ +import { Collapsible as CollapsiblePrimitive } from "bits-ui"; + +const Root = CollapsiblePrimitive.Root; +const Trigger = CollapsiblePrimitive.Trigger; +const Content = CollapsiblePrimitive.Content; + +export { + Root, + Content, + Trigger, + // + Root as Collapsible, + Content as CollapsibleContent, + Trigger as CollapsibleTrigger, +}; diff --git a/frontend/src/lib/components/ui/sidebar/constants.ts b/frontend/src/lib/components/ui/sidebar/constants.ts new file mode 100644 index 0000000..4de4435 --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/constants.ts @@ -0,0 +1,6 @@ +export const SIDEBAR_COOKIE_NAME = "sidebar:state"; +export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; +export const SIDEBAR_WIDTH = "16rem"; +export const SIDEBAR_WIDTH_MOBILE = "18rem"; +export const SIDEBAR_WIDTH_ICON = "3rem"; +export const SIDEBAR_KEYBOARD_SHORTCUT = "b"; diff --git a/frontend/src/lib/components/ui/sidebar/context.svelte.ts b/frontend/src/lib/components/ui/sidebar/context.svelte.ts new file mode 100644 index 0000000..15248ad --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/context.svelte.ts @@ -0,0 +1,81 @@ +import { IsMobile } from "$lib/hooks/is-mobile.svelte.js"; +import { getContext, setContext } from "svelte"; +import { SIDEBAR_KEYBOARD_SHORTCUT } from "./constants.js"; + +type Getter = () => T; + +export type SidebarStateProps = { + /** + * A getter function that returns the current open state of the sidebar. + * We use a getter function here to support `bind:open` on the `Sidebar.Provider` + * component. + */ + open: Getter; + + /** + * A function that sets the open state of the sidebar. To support `bind:open`, we need + * a source of truth for changing the open state to ensure it will be synced throughout + * the sub-components and any `bind:` references. + */ + setOpen: (open: boolean) => void; +}; + +class SidebarState { + readonly props: SidebarStateProps; + open = $derived.by(() => this.props.open()); + openMobile = $state(false); + setOpen: SidebarStateProps["setOpen"]; + #isMobile: IsMobile; + state = $derived.by(() => (this.open ? "expanded" : "collapsed")); + + constructor(props: SidebarStateProps) { + this.setOpen = props.setOpen; + this.#isMobile = new IsMobile(); + this.props = props; + } + + // Convenience getter for checking if the sidebar is mobile + // without this, we would need to use `sidebar.isMobile.current` everywhere + get isMobile() { + return this.#isMobile.current; + } + + // Event handler to apply to the `` + handleShortcutKeydown = (e: KeyboardEvent) => { + if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + this.toggle(); + } + }; + + setOpenMobile = (value: boolean) => { + this.openMobile = value; + }; + + toggle = () => { + return this.#isMobile.current + ? (this.openMobile = !this.openMobile) + : this.setOpen(!this.open); + }; +} + +const SYMBOL_KEY = "scn-sidebar"; + +/** + * Instantiates a new `SidebarState` instance and sets it in the context. + * + * @param props The constructor props for the `SidebarState` class. + * @returns The `SidebarState` instance. + */ +export function setSidebar(props: SidebarStateProps): SidebarState { + return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props)); +} + +/** + * Retrieves the `SidebarState` instance from the context. This is a class instance, + * so you cannot destructure it. + * @returns The `SidebarState` instance. + */ +export function useSidebar(): SidebarState { + return getContext(Symbol.for(SYMBOL_KEY)); +} diff --git a/frontend/src/lib/components/ui/sidebar/index.ts b/frontend/src/lib/components/ui/sidebar/index.ts new file mode 100644 index 0000000..318a341 --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/index.ts @@ -0,0 +1,75 @@ +import { useSidebar } from "./context.svelte.js"; +import Content from "./sidebar-content.svelte"; +import Footer from "./sidebar-footer.svelte"; +import GroupAction from "./sidebar-group-action.svelte"; +import GroupContent from "./sidebar-group-content.svelte"; +import GroupLabel from "./sidebar-group-label.svelte"; +import Group from "./sidebar-group.svelte"; +import Header from "./sidebar-header.svelte"; +import Input from "./sidebar-input.svelte"; +import Inset from "./sidebar-inset.svelte"; +import MenuAction from "./sidebar-menu-action.svelte"; +import MenuBadge from "./sidebar-menu-badge.svelte"; +import MenuButton from "./sidebar-menu-button.svelte"; +import MenuItem from "./sidebar-menu-item.svelte"; +import MenuSkeleton from "./sidebar-menu-skeleton.svelte"; +import MenuSubButton from "./sidebar-menu-sub-button.svelte"; +import MenuSubItem from "./sidebar-menu-sub-item.svelte"; +import MenuSub from "./sidebar-menu-sub.svelte"; +import Menu from "./sidebar-menu.svelte"; +import Provider from "./sidebar-provider.svelte"; +import Rail from "./sidebar-rail.svelte"; +import Separator from "./sidebar-separator.svelte"; +import Trigger from "./sidebar-trigger.svelte"; +import Root from "./sidebar.svelte"; + +export { + Content, + Footer, + Group, + GroupAction, + GroupContent, + GroupLabel, + Header, + Input, + Inset, + Menu, + MenuAction, + MenuBadge, + MenuButton, + MenuItem, + MenuSkeleton, + MenuSub, + MenuSubButton, + MenuSubItem, + Provider, + Rail, + Root, + Separator, + // + Root as Sidebar, + Content as SidebarContent, + Footer as SidebarFooter, + Group as SidebarGroup, + GroupAction as SidebarGroupAction, + GroupContent as SidebarGroupContent, + GroupLabel as SidebarGroupLabel, + Header as SidebarHeader, + Input as SidebarInput, + Inset as SidebarInset, + Menu as SidebarMenu, + MenuAction as SidebarMenuAction, + MenuBadge as SidebarMenuBadge, + MenuButton as SidebarMenuButton, + MenuItem as SidebarMenuItem, + MenuSkeleton as SidebarMenuSkeleton, + MenuSub as SidebarMenuSub, + MenuSubButton as SidebarMenuSubButton, + MenuSubItem as SidebarMenuSubItem, + Provider as SidebarProvider, + Rail as SidebarRail, + Separator as SidebarSeparator, + Trigger as SidebarTrigger, + Trigger, + useSidebar, +}; diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-content.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-content.svelte new file mode 100644 index 0000000..c6db009 --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-content.svelte @@ -0,0 +1,24 @@ + + +
+ {@render children?.()} +
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-footer.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-footer.svelte new file mode 100644 index 0000000..c62cb41 --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-footer.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-group-action.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-group-action.svelte new file mode 100644 index 0000000..6961e4c --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-group-action.svelte @@ -0,0 +1,36 @@ + + +{#if child} + {@render child({ props: propObj })} +{:else} + +{/if} diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-group-content.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-group-content.svelte new file mode 100644 index 0000000..f5e623b --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-group-content.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-group-label.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-group-label.svelte new file mode 100644 index 0000000..77c180f --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-group-label.svelte @@ -0,0 +1,34 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} +
+ {@render children?.()} +
+{/if} diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-group.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-group.svelte new file mode 100644 index 0000000..aa8bf31 --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-group.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-header.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-header.svelte new file mode 100644 index 0000000..5656c0b --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-header.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children?.()} +
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-input.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-input.svelte new file mode 100644 index 0000000..1d57f43 --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-input.svelte @@ -0,0 +1,23 @@ + + + diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-inset.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-inset.svelte new file mode 100644 index 0000000..b9cd447 --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-inset.svelte @@ -0,0 +1,24 @@ + + +
+ {@render children?.()} +
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-menu-action.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-menu-action.svelte new file mode 100644 index 0000000..5a94f39 --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-menu-action.svelte @@ -0,0 +1,43 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} + +{/if} diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte new file mode 100644 index 0000000..0fd4c51 --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte @@ -0,0 +1,29 @@ + + +
+ {@render children?.()} +
diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-menu-button.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-menu-button.svelte new file mode 100644 index 0000000..1513721 --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-menu-button.svelte @@ -0,0 +1,97 @@ + + + + +{#snippet Button({ props }: { props?: Record })} + {@const mergedProps = mergeProps(buttonProps, props)} + {#if child} + {@render child({ props: mergedProps })} + {:else} + + {/if} +{/snippet} + +{#if !tooltipContent} + {@render Button({})} +{:else} + + + {#snippet child({ props })} + {@render Button({ props })} + {/snippet} + + +{/if} diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-menu-item.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-menu-item.svelte new file mode 100644 index 0000000..ee82144 --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-menu-item.svelte @@ -0,0 +1,21 @@ + + +
  • + {@render children?.()} +
  • diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte new file mode 100644 index 0000000..5c2effe --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte @@ -0,0 +1,36 @@ + + +
    + {#if showIcon} + + {/if} + + {@render children?.()} +
    diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte new file mode 100644 index 0000000..26b2a90 --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte @@ -0,0 +1,43 @@ + + +{#if child} + {@render child({ props: mergedProps })} +{:else} + + {@render children?.()} + +{/if} diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte new file mode 100644 index 0000000..6e7346d --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte @@ -0,0 +1,14 @@ + + +
  • + {@render children?.()} +
  • diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte new file mode 100644 index 0000000..2a23990 --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte @@ -0,0 +1,25 @@ + + +
      + {@render children?.()} +
    diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-menu.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-menu.svelte new file mode 100644 index 0000000..72534db --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-menu.svelte @@ -0,0 +1,21 @@ + + +
      + {@render children?.()} +
    diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-provider.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-provider.svelte new file mode 100644 index 0000000..4583bbe --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-provider.svelte @@ -0,0 +1,59 @@ + + + + + +
    + {@render children?.()} +
    +
    diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-rail.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-rail.svelte new file mode 100644 index 0000000..ee16fce --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-rail.svelte @@ -0,0 +1,36 @@ + + + diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-separator.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-separator.svelte new file mode 100644 index 0000000..0cc3a25 --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-separator.svelte @@ -0,0 +1,18 @@ + + + diff --git a/frontend/src/lib/components/ui/sidebar/sidebar-trigger.svelte b/frontend/src/lib/components/ui/sidebar/sidebar-trigger.svelte new file mode 100644 index 0000000..98a4a7f --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar-trigger.svelte @@ -0,0 +1,34 @@ + + + diff --git a/frontend/src/lib/components/ui/sidebar/sidebar.svelte b/frontend/src/lib/components/ui/sidebar/sidebar.svelte new file mode 100644 index 0000000..f53363d --- /dev/null +++ b/frontend/src/lib/components/ui/sidebar/sidebar.svelte @@ -0,0 +1,98 @@ + + +{#if collapsible === "none"} +
    + {@render children?.()} +
    +{:else if sidebar.isMobile} + + +
    + {@render children?.()} +
    +
    +
    +{:else} + +{/if} diff --git a/frontend/src/lib/hooks/is-mobile.svelte.ts b/frontend/src/lib/hooks/is-mobile.svelte.ts new file mode 100644 index 0000000..87bea4b --- /dev/null +++ b/frontend/src/lib/hooks/is-mobile.svelte.ts @@ -0,0 +1,27 @@ +import { untrack } from "svelte"; + +const MOBILE_BREAKPOINT = 768; + +export class IsMobile { + #current = $state(false); + + constructor() { + $effect(() => { + return untrack(() => { + const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); + const onChange = () => { + this.#current = window.innerWidth < MOBILE_BREAKPOINT; + }; + mql.addEventListener("change", onChange); + onChange(); + return () => { + mql.removeEventListener("change", onChange); + }; + }); + }); + } + + get current() { + return this.#current; + } +} diff --git a/frontend/src/routes/(app)/dashboard/+layout.svelte b/frontend/src/routes/(app)/dashboard/+layout.svelte index 042234a..dd12220 100644 --- a/frontend/src/routes/(app)/dashboard/+layout.svelte +++ b/frontend/src/routes/(app)/dashboard/+layout.svelte @@ -15,6 +15,9 @@ Settings, UserIcon, } from 'lucide-svelte' + import AppSidebar from '$lib/components/app-sidebar.svelte' + import { Separator } from '$lib/components/ui/separator/index.js' + import * as Sidebar from '$lib/components/ui/sidebar/index.js' import { goto } from '$app/navigation' import { Loader2, User } from 'lucide-svelte' @@ -35,174 +38,53 @@ } let { data, children } = $props() - - let value = $state( - ($page.data.project?.id as string) || 'none', - ) - - const triggerContent = $derived( - data.projects.find((f) => f.id === value)?.name ?? 'None', - ) - - const routes = [ - { - href: '/dashboard', - name: 'Home', - match: (path: string) => path === '/dashboard', - icon: Home, - }, - { - href: '/dashboard/links', - name: 'Links', - match: (path: string) => path.startsWith('/dashboard/links'), - icon: Link, - }, - { - href: '/dashboard/projects', - name: 'Projects', - match: (path: string) => path.startsWith('/dashboard/projects'), - icon: Blocks, - }, - { - href: '/dashboard/billing', - name: 'Billing', - match: (path: string) => path.startsWith('/dashboard/billing'), - icon: CreditCardIcon, - }, - { - href: '/dashboard/settings/account', - name: 'Settings', - match: (path: string) => path.startsWith('/dashboard/settings'), - icon: Settings, - }, - ] as const -
    -
    -
    - - - - - - - - - - {data.user.email} - - { - goto('/dashboard/settings') - }}> - Settings - - - (dialogOpen = true)} - class="text-destructive data-[highlighted]:bg-destructive"> - Log Out - - - - - - -
    + + + +
    + + + + + {#if $page.data.breadcrumbs} + {#each $page.data.breadcrumbs as breadcrumb, index} + {#if index == $page.data.breadcrumbs.length - 1} + + + {breadcrumb.name} + + + {:else} + + + +
    -
    -
    - - {#each routes as route} - - {/each} -
    + class="flex max-h-[calc(100vh-64px)] flex-grow overflow-hidden"> +
    + {@render children()}
    -
    - -
    -
    - -
    -
    - {@render children()} -
    -
    -
    + + diff --git a/frontend/src/routes/(app)/dashboard/billing/+page.server.ts b/frontend/src/routes/(app)/dashboard/billing/+page.server.ts index f722d82..147210a 100644 --- a/frontend/src/routes/(app)/dashboard/billing/+page.server.ts +++ b/frontend/src/routes/(app)/dashboard/billing/+page.server.ts @@ -6,7 +6,11 @@ import { zod } from 'sveltekit-superforms/adapters' import { cancelSubscriptionSchema } from './schema' export const load = (async () => { + const breadcrumbs = [ + { name: 'Billing', path: '/dashboard/billing' }, + ] return { + breadcrumbs, cancel_subscription_form: await superValidate( zod(cancelSubscriptionSchema), ),