diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2026-02-12 20:57:17 +0100 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2026-02-12 20:57:17 +0100 |
| commit | b333b06772c89d96aacb5490d6a219fba7c09cc6 (patch) | |
| tree | 211df60083a5946baa2ed61d33d8121b7e251b06 /llama.cpp/tools/server/webui/src/lib/components/ui/sidebar | |
| download | llmnpc-b333b06772c89d96aacb5490d6a219fba7c09cc6.tar.gz | |
Engage!
Diffstat (limited to 'llama.cpp/tools/server/webui/src/lib/components/ui/sidebar')
26 files changed, 969 insertions, 0 deletions
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/constants.ts b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/constants.ts new file mode 100644 index 0000000..c7e827b --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/constants.ts | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | export const SIDEBAR_COOKIE_NAME = 'sidebar:state'; | ||
| 2 | export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; | ||
| 3 | export const SIDEBAR_WIDTH = '18rem'; | ||
| 4 | export const SIDEBAR_WIDTH_MOBILE = '18rem'; | ||
| 5 | export const SIDEBAR_WIDTH_ICON = '3rem'; | ||
| 6 | export const SIDEBAR_KEYBOARD_SHORTCUT = 'b'; | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/context.svelte.ts b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/context.svelte.ts new file mode 100644 index 0000000..6fa2aa3 --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/context.svelte.ts | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | import { IsMobile } from '$lib/hooks/is-mobile.svelte.js'; | ||
| 2 | import { getContext, setContext } from 'svelte'; | ||
| 3 | import { SIDEBAR_KEYBOARD_SHORTCUT } from './constants.js'; | ||
| 4 | |||
| 5 | type Getter<T> = () => T; | ||
| 6 | |||
| 7 | export type SidebarStateProps = { | ||
| 8 | /** | ||
| 9 | * A getter function that returns the current open state of the sidebar. | ||
| 10 | * We use a getter function here to support `bind:open` on the `Sidebar.Provider` | ||
| 11 | * component. | ||
| 12 | */ | ||
| 13 | open: Getter<boolean>; | ||
| 14 | |||
| 15 | /** | ||
| 16 | * A function that sets the open state of the sidebar. To support `bind:open`, we need | ||
| 17 | * a source of truth for changing the open state to ensure it will be synced throughout | ||
| 18 | * the sub-components and any `bind:` references. | ||
| 19 | */ | ||
| 20 | setOpen: (open: boolean) => void; | ||
| 21 | }; | ||
| 22 | |||
| 23 | class SidebarState { | ||
| 24 | readonly props: SidebarStateProps; | ||
| 25 | open = $derived.by(() => this.props.open()); | ||
| 26 | openMobile = $state(false); | ||
| 27 | setOpen: SidebarStateProps['setOpen']; | ||
| 28 | #isMobile: IsMobile; | ||
| 29 | state = $derived.by(() => (this.open ? 'expanded' : 'collapsed')); | ||
| 30 | |||
| 31 | constructor(props: SidebarStateProps) { | ||
| 32 | this.setOpen = props.setOpen; | ||
| 33 | this.#isMobile = new IsMobile(); | ||
| 34 | this.props = props; | ||
| 35 | } | ||
| 36 | |||
| 37 | // Convenience getter for checking if the sidebar is mobile | ||
| 38 | // without this, we would need to use `sidebar.isMobile.current` everywhere | ||
| 39 | get isMobile() { | ||
| 40 | return this.#isMobile.current; | ||
| 41 | } | ||
| 42 | |||
| 43 | // Event handler to apply to the `<svelte:window>` | ||
| 44 | handleShortcutKeydown = (e: KeyboardEvent) => { | ||
| 45 | if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) { | ||
| 46 | e.preventDefault(); | ||
| 47 | this.toggle(); | ||
| 48 | } | ||
| 49 | }; | ||
| 50 | |||
| 51 | setOpenMobile = (value: boolean) => { | ||
| 52 | this.openMobile = value; | ||
| 53 | }; | ||
| 54 | |||
| 55 | toggle = () => { | ||
| 56 | return this.#isMobile.current ? (this.openMobile = !this.openMobile) : this.setOpen(!this.open); | ||
| 57 | }; | ||
| 58 | } | ||
| 59 | |||
| 60 | const SYMBOL_KEY = 'scn-sidebar'; | ||
| 61 | |||
| 62 | /** | ||
| 63 | * Instantiates a new `SidebarState` instance and sets it in the context. | ||
| 64 | * | ||
| 65 | * @param props The constructor props for the `SidebarState` class. | ||
| 66 | * @returns The `SidebarState` instance. | ||
| 67 | */ | ||
| 68 | export function setSidebar(props: SidebarStateProps): SidebarState { | ||
| 69 | return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props)); | ||
| 70 | } | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Retrieves the `SidebarState` instance from the context. This is a class instance, | ||
| 74 | * so you cannot destructure it. | ||
| 75 | * @returns The `SidebarState` instance. | ||
| 76 | */ | ||
| 77 | export function useSidebar(): SidebarState { | ||
| 78 | return getContext(Symbol.for(SYMBOL_KEY)); | ||
| 79 | } | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/index.ts b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/index.ts new file mode 100644 index 0000000..280e640 --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/index.ts | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | import { useSidebar } from './context.svelte.js'; | ||
| 2 | import Content from './sidebar-content.svelte'; | ||
| 3 | import Footer from './sidebar-footer.svelte'; | ||
| 4 | import GroupAction from './sidebar-group-action.svelte'; | ||
| 5 | import GroupContent from './sidebar-group-content.svelte'; | ||
| 6 | import GroupLabel from './sidebar-group-label.svelte'; | ||
| 7 | import Group from './sidebar-group.svelte'; | ||
| 8 | import Header from './sidebar-header.svelte'; | ||
| 9 | import Input from './sidebar-input.svelte'; | ||
| 10 | import Inset from './sidebar-inset.svelte'; | ||
| 11 | import MenuAction from './sidebar-menu-action.svelte'; | ||
| 12 | import MenuBadge from './sidebar-menu-badge.svelte'; | ||
| 13 | import MenuButton from './sidebar-menu-button.svelte'; | ||
| 14 | import MenuItem from './sidebar-menu-item.svelte'; | ||
| 15 | import MenuSkeleton from './sidebar-menu-skeleton.svelte'; | ||
| 16 | import MenuSubButton from './sidebar-menu-sub-button.svelte'; | ||
| 17 | import MenuSubItem from './sidebar-menu-sub-item.svelte'; | ||
| 18 | import MenuSub from './sidebar-menu-sub.svelte'; | ||
| 19 | import Menu from './sidebar-menu.svelte'; | ||
| 20 | import Provider from './sidebar-provider.svelte'; | ||
| 21 | import Rail from './sidebar-rail.svelte'; | ||
| 22 | import Separator from './sidebar-separator.svelte'; | ||
| 23 | import Trigger from './sidebar-trigger.svelte'; | ||
| 24 | import Root from './sidebar.svelte'; | ||
| 25 | |||
| 26 | export { | ||
| 27 | Content, | ||
| 28 | Footer, | ||
| 29 | Group, | ||
| 30 | GroupAction, | ||
| 31 | GroupContent, | ||
| 32 | GroupLabel, | ||
| 33 | Header, | ||
| 34 | Input, | ||
| 35 | Inset, | ||
| 36 | Menu, | ||
| 37 | MenuAction, | ||
| 38 | MenuBadge, | ||
| 39 | MenuButton, | ||
| 40 | MenuItem, | ||
| 41 | MenuSkeleton, | ||
| 42 | MenuSub, | ||
| 43 | MenuSubButton, | ||
| 44 | MenuSubItem, | ||
| 45 | Provider, | ||
| 46 | Rail, | ||
| 47 | Root, | ||
| 48 | Separator, | ||
| 49 | // | ||
| 50 | Root as Sidebar, | ||
| 51 | Content as SidebarContent, | ||
| 52 | Footer as SidebarFooter, | ||
| 53 | Group as SidebarGroup, | ||
| 54 | GroupAction as SidebarGroupAction, | ||
| 55 | GroupContent as SidebarGroupContent, | ||
| 56 | GroupLabel as SidebarGroupLabel, | ||
| 57 | Header as SidebarHeader, | ||
| 58 | Input as SidebarInput, | ||
| 59 | Inset as SidebarInset, | ||
| 60 | Menu as SidebarMenu, | ||
| 61 | MenuAction as SidebarMenuAction, | ||
| 62 | MenuBadge as SidebarMenuBadge, | ||
| 63 | MenuButton as SidebarMenuButton, | ||
| 64 | MenuItem as SidebarMenuItem, | ||
| 65 | MenuSkeleton as SidebarMenuSkeleton, | ||
| 66 | MenuSub as SidebarMenuSub, | ||
| 67 | MenuSubButton as SidebarMenuSubButton, | ||
| 68 | MenuSubItem as SidebarMenuSubItem, | ||
| 69 | Provider as SidebarProvider, | ||
| 70 | Rail as SidebarRail, | ||
| 71 | Separator as SidebarSeparator, | ||
| 72 | Trigger as SidebarTrigger, | ||
| 73 | Trigger, | ||
| 74 | useSidebar | ||
| 75 | }; | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-content.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-content.svelte new file mode 100644 index 0000000..0e5f75e --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-content.svelte | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 3 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 4 | |||
| 5 | let { | ||
| 6 | ref = $bindable(null), | ||
| 7 | class: className, | ||
| 8 | children, | ||
| 9 | ...restProps | ||
| 10 | }: WithElementRef<HTMLAttributes<HTMLElement>> = $props(); | ||
| 11 | </script> | ||
| 12 | |||
| 13 | <div | ||
| 14 | bind:this={ref} | ||
| 15 | data-slot="sidebar-content" | ||
| 16 | data-sidebar="content" | ||
| 17 | class={cn( | ||
| 18 | 'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden', | ||
| 19 | className | ||
| 20 | )} | ||
| 21 | {...restProps} | ||
| 22 | > | ||
| 23 | {@render children?.()} | ||
| 24 | </div> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-footer.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-footer.svelte new file mode 100644 index 0000000..67be0a4 --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-footer.svelte | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 3 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 4 | |||
| 5 | let { | ||
| 6 | ref = $bindable(null), | ||
| 7 | class: className, | ||
| 8 | children, | ||
| 9 | ...restProps | ||
| 10 | }: WithElementRef<HTMLAttributes<HTMLElement>> = $props(); | ||
| 11 | </script> | ||
| 12 | |||
| 13 | <div | ||
| 14 | bind:this={ref} | ||
| 15 | data-slot="sidebar-footer" | ||
| 16 | data-sidebar="footer" | ||
| 17 | class={cn('flex flex-col gap-2 p-2', className)} | ||
| 18 | {...restProps} | ||
| 19 | > | ||
| 20 | {@render children?.()} | ||
| 21 | </div> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-group-action.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-group-action.svelte new file mode 100644 index 0000000..027a711 --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-group-action.svelte | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 3 | import type { Snippet } from 'svelte'; | ||
| 4 | import type { HTMLButtonAttributes } from 'svelte/elements'; | ||
| 5 | |||
| 6 | let { | ||
| 7 | ref = $bindable(null), | ||
| 8 | class: className, | ||
| 9 | children, | ||
| 10 | child, | ||
| 11 | ...restProps | ||
| 12 | }: WithElementRef<HTMLButtonAttributes> & { | ||
| 13 | child?: Snippet<[{ props: Record<string, unknown> }]>; | ||
| 14 | } = $props(); | ||
| 15 | |||
| 16 | const mergedProps = $derived({ | ||
| 17 | class: cn( | ||
| 18 | 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground outline-hidden absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0', | ||
| 19 | // Increases the hit area of the button on mobile. | ||
| 20 | 'after:absolute after:-inset-2 md:after:hidden', | ||
| 21 | 'group-data-[collapsible=icon]:hidden', | ||
| 22 | className | ||
| 23 | ), | ||
| 24 | 'data-slot': 'sidebar-group-action', | ||
| 25 | 'data-sidebar': 'group-action', | ||
| 26 | ...restProps | ||
| 27 | }); | ||
| 28 | </script> | ||
| 29 | |||
| 30 | {#if child} | ||
| 31 | {@render child({ props: mergedProps })} | ||
| 32 | {:else} | ||
| 33 | <button bind:this={ref} {...mergedProps}> | ||
| 34 | {@render children?.()} | ||
| 35 | </button> | ||
| 36 | {/if} | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-group-content.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-group-content.svelte new file mode 100644 index 0000000..9e018fb --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-group-content.svelte | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 3 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 4 | |||
| 5 | let { | ||
| 6 | ref = $bindable(null), | ||
| 7 | class: className, | ||
| 8 | children, | ||
| 9 | ...restProps | ||
| 10 | }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props(); | ||
| 11 | </script> | ||
| 12 | |||
| 13 | <div | ||
| 14 | bind:this={ref} | ||
| 15 | data-slot="sidebar-group-content" | ||
| 16 | data-sidebar="group-content" | ||
| 17 | class={cn('w-full text-sm', className)} | ||
| 18 | {...restProps} | ||
| 19 | > | ||
| 20 | {@render children?.()} | ||
| 21 | </div> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-group-label.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-group-label.svelte new file mode 100644 index 0000000..79f47d7 --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-group-label.svelte | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 3 | import type { Snippet } from 'svelte'; | ||
| 4 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 5 | |||
| 6 | let { | ||
| 7 | ref = $bindable(null), | ||
| 8 | children, | ||
| 9 | child, | ||
| 10 | class: className, | ||
| 11 | ...restProps | ||
| 12 | }: WithElementRef<HTMLAttributes<HTMLElement>> & { | ||
| 13 | child?: Snippet<[{ props: Record<string, unknown> }]>; | ||
| 14 | } = $props(); | ||
| 15 | |||
| 16 | const mergedProps = $derived({ | ||
| 17 | class: cn( | ||
| 18 | 'text-sidebar-foreground/70 ring-sidebar-ring outline-hidden flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0', | ||
| 19 | 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0', | ||
| 20 | className | ||
| 21 | ), | ||
| 22 | 'data-slot': 'sidebar-group-label', | ||
| 23 | 'data-sidebar': 'group-label', | ||
| 24 | ...restProps | ||
| 25 | }); | ||
| 26 | </script> | ||
| 27 | |||
| 28 | {#if child} | ||
| 29 | {@render child({ props: mergedProps })} | ||
| 30 | {:else} | ||
| 31 | <div bind:this={ref} {...mergedProps}> | ||
| 32 | {@render children?.()} | ||
| 33 | </div> | ||
| 34 | {/if} | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-group.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-group.svelte new file mode 100644 index 0000000..eed5ace --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-group.svelte | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 3 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 4 | |||
| 5 | let { | ||
| 6 | ref = $bindable(null), | ||
| 7 | class: className, | ||
| 8 | children, | ||
| 9 | ...restProps | ||
| 10 | }: WithElementRef<HTMLAttributes<HTMLElement>> = $props(); | ||
| 11 | </script> | ||
| 12 | |||
| 13 | <div | ||
| 14 | bind:this={ref} | ||
| 15 | data-slot="sidebar-group" | ||
| 16 | data-sidebar="group" | ||
| 17 | class={cn('relative flex w-full min-w-0 flex-col p-2', className)} | ||
| 18 | {...restProps} | ||
| 19 | > | ||
| 20 | {@render children?.()} | ||
| 21 | </div> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-header.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-header.svelte new file mode 100644 index 0000000..0651550 --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-header.svelte | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 3 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 4 | |||
| 5 | let { | ||
| 6 | ref = $bindable(null), | ||
| 7 | class: className, | ||
| 8 | children, | ||
| 9 | ...restProps | ||
| 10 | }: WithElementRef<HTMLAttributes<HTMLElement>> = $props(); | ||
| 11 | </script> | ||
| 12 | |||
| 13 | <div | ||
| 14 | bind:this={ref} | ||
| 15 | data-slot="sidebar-header" | ||
| 16 | data-sidebar="header" | ||
| 17 | class={cn('flex flex-col gap-2 p-2', className)} | ||
| 18 | {...restProps} | ||
| 19 | > | ||
| 20 | {@render children?.()} | ||
| 21 | </div> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-input.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-input.svelte new file mode 100644 index 0000000..fa57473 --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-input.svelte | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import type { ComponentProps } from 'svelte'; | ||
| 3 | import { Input } from '$lib/components/ui/input/index.js'; | ||
| 4 | import { cn } from '$lib/components/ui/utils.js'; | ||
| 5 | |||
| 6 | let { | ||
| 7 | ref = $bindable(null), | ||
| 8 | value = $bindable(''), | ||
| 9 | class: className, | ||
| 10 | ...restProps | ||
| 11 | }: ComponentProps<typeof Input> = $props(); | ||
| 12 | </script> | ||
| 13 | |||
| 14 | <Input | ||
| 15 | bind:ref | ||
| 16 | bind:value | ||
| 17 | data-slot="sidebar-input" | ||
| 18 | data-sidebar="input" | ||
| 19 | class={cn('h-8 w-full bg-background shadow-none', className)} | ||
| 20 | {...restProps} | ||
| 21 | /> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-inset.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-inset.svelte new file mode 100644 index 0000000..f55d2f4 --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-inset.svelte | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 3 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 4 | |||
| 5 | let { | ||
| 6 | ref = $bindable(null), | ||
| 7 | class: className, | ||
| 8 | children, | ||
| 9 | ...restProps | ||
| 10 | }: WithElementRef<HTMLAttributes<HTMLElement>> = $props(); | ||
| 11 | </script> | ||
| 12 | |||
| 13 | <main | ||
| 14 | bind:this={ref} | ||
| 15 | data-slot="sidebar-inset" | ||
| 16 | class={cn( | ||
| 17 | 'relative flex w-full flex-1 flex-col', | ||
| 18 | 'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2', | ||
| 19 | className | ||
| 20 | )} | ||
| 21 | {...restProps} | ||
| 22 | > | ||
| 23 | {@render children?.()} | ||
| 24 | </main> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-action.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-action.svelte new file mode 100644 index 0000000..ded1ffd --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-action.svelte | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 3 | import type { Snippet } from 'svelte'; | ||
| 4 | import type { HTMLButtonAttributes } from 'svelte/elements'; | ||
| 5 | |||
| 6 | let { | ||
| 7 | ref = $bindable(null), | ||
| 8 | class: className, | ||
| 9 | showOnHover = false, | ||
| 10 | children, | ||
| 11 | child, | ||
| 12 | ...restProps | ||
| 13 | }: WithElementRef<HTMLButtonAttributes> & { | ||
| 14 | child?: Snippet<[{ props: Record<string, unknown> }]>; | ||
| 15 | showOnHover?: boolean; | ||
| 16 | } = $props(); | ||
| 17 | |||
| 18 | const mergedProps = $derived({ | ||
| 19 | class: cn( | ||
| 20 | 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground outline-hidden absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0', | ||
| 21 | // Increases the hit area of the button on mobile. | ||
| 22 | 'after:absolute after:-inset-2 md:after:hidden', | ||
| 23 | 'peer-data-[size=sm]/menu-button:top-1', | ||
| 24 | 'peer-data-[size=default]/menu-button:top-1.5', | ||
| 25 | 'peer-data-[size=lg]/menu-button:top-2.5', | ||
| 26 | 'group-data-[collapsible=icon]:hidden', | ||
| 27 | showOnHover && | ||
| 28 | 'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0', | ||
| 29 | className | ||
| 30 | ), | ||
| 31 | 'data-slot': 'sidebar-menu-action', | ||
| 32 | 'data-sidebar': 'menu-action', | ||
| 33 | ...restProps | ||
| 34 | }); | ||
| 35 | </script> | ||
| 36 | |||
| 37 | {#if child} | ||
| 38 | {@render child({ props: mergedProps })} | ||
| 39 | {:else} | ||
| 40 | <button bind:this={ref} {...mergedProps}> | ||
| 41 | {@render children?.()} | ||
| 42 | </button> | ||
| 43 | {/if} | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte new file mode 100644 index 0000000..f4525a1 --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 3 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 4 | |||
| 5 | let { | ||
| 6 | ref = $bindable(null), | ||
| 7 | class: className, | ||
| 8 | children, | ||
| 9 | ...restProps | ||
| 10 | }: WithElementRef<HTMLAttributes<HTMLElement>> = $props(); | ||
| 11 | </script> | ||
| 12 | |||
| 13 | <div | ||
| 14 | bind:this={ref} | ||
| 15 | data-slot="sidebar-menu-badge" | ||
| 16 | data-sidebar="menu-badge" | ||
| 17 | class={cn( | ||
| 18 | 'pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none', | ||
| 19 | 'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground', | ||
| 20 | 'peer-data-[size=sm]/menu-button:top-1', | ||
| 21 | 'peer-data-[size=default]/menu-button:top-1.5', | ||
| 22 | 'peer-data-[size=lg]/menu-button:top-2.5', | ||
| 23 | 'group-data-[collapsible=icon]:hidden', | ||
| 24 | className | ||
| 25 | )} | ||
| 26 | {...restProps} | ||
| 27 | > | ||
| 28 | {@render children?.()} | ||
| 29 | </div> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-button.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-button.svelte new file mode 100644 index 0000000..2ce0305 --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-button.svelte | |||
| @@ -0,0 +1,106 @@ | |||
| 1 | <script lang="ts" module> | ||
| 2 | import { tv, type VariantProps } from 'tailwind-variants'; | ||
| 3 | |||
| 4 | export const sidebarMenuButtonVariants = tv({ | ||
| 5 | base: 'peer/menu-button outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground group-has-data-[sidebar=menu-action]/menu-item:pr-8 data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm transition-[width,height,padding] focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:font-medium [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', | ||
| 6 | variants: { | ||
| 7 | variant: { | ||
| 8 | default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground', | ||
| 9 | outline: | ||
| 10 | 'bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_var(--sidebar-border)] hover:shadow-[0_0_0_1px_var(--sidebar-accent)]' | ||
| 11 | }, | ||
| 12 | size: { | ||
| 13 | default: 'h-8 text-sm', | ||
| 14 | sm: 'h-7 text-xs', | ||
| 15 | lg: 'group-data-[collapsible=icon]:p-0! h-12 text-sm' | ||
| 16 | } | ||
| 17 | }, | ||
| 18 | defaultVariants: { | ||
| 19 | variant: 'default', | ||
| 20 | size: 'default' | ||
| 21 | } | ||
| 22 | }); | ||
| 23 | |||
| 24 | export type SidebarMenuButtonVariant = VariantProps<typeof sidebarMenuButtonVariants>['variant']; | ||
| 25 | export type SidebarMenuButtonSize = VariantProps<typeof sidebarMenuButtonVariants>['size']; | ||
| 26 | </script> | ||
| 27 | |||
| 28 | <script lang="ts"> | ||
| 29 | import * as Tooltip from '$lib/components/ui/tooltip/index.js'; | ||
| 30 | import { | ||
| 31 | cn, | ||
| 32 | type WithElementRef, | ||
| 33 | type WithoutChildrenOrChild | ||
| 34 | } from '$lib/components/ui/utils.js'; | ||
| 35 | import { mergeProps } from 'bits-ui'; | ||
| 36 | import type { ComponentProps, Snippet } from 'svelte'; | ||
| 37 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 38 | import { useSidebar } from './context.svelte.js'; | ||
| 39 | |||
| 40 | let { | ||
| 41 | ref = $bindable(null), | ||
| 42 | class: className, | ||
| 43 | children, | ||
| 44 | child, | ||
| 45 | variant = 'default', | ||
| 46 | size = 'default', | ||
| 47 | isActive = false, | ||
| 48 | tooltipContent, | ||
| 49 | tooltipContentProps, | ||
| 50 | ...restProps | ||
| 51 | }: WithElementRef<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> & { | ||
| 52 | isActive?: boolean; | ||
| 53 | variant?: SidebarMenuButtonVariant; | ||
| 54 | size?: SidebarMenuButtonSize; | ||
| 55 | tooltipContent?: Snippet | string; | ||
| 56 | tooltipContentProps?: WithoutChildrenOrChild<ComponentProps<typeof Tooltip.Content>>; | ||
| 57 | child?: Snippet<[{ props: Record<string, unknown> }]>; | ||
| 58 | } = $props(); | ||
| 59 | |||
| 60 | const sidebar = useSidebar(); | ||
| 61 | |||
| 62 | const buttonProps = $derived({ | ||
| 63 | class: cn(sidebarMenuButtonVariants({ variant, size }), className), | ||
| 64 | 'data-slot': 'sidebar-menu-button', | ||
| 65 | 'data-sidebar': 'menu-button', | ||
| 66 | 'data-size': size, | ||
| 67 | 'data-active': isActive, | ||
| 68 | ...restProps | ||
| 69 | }); | ||
| 70 | </script> | ||
| 71 | |||
| 72 | {#snippet Button({ props }: { props?: Record<string, unknown> })} | ||
| 73 | {@const mergedProps = mergeProps(buttonProps, props)} | ||
| 74 | {#if child} | ||
| 75 | {@render child({ props: mergedProps })} | ||
| 76 | {:else} | ||
| 77 | <button bind:this={ref} {...mergedProps}> | ||
| 78 | {@render children?.()} | ||
| 79 | </button> | ||
| 80 | {/if} | ||
| 81 | {/snippet} | ||
| 82 | |||
| 83 | {#if !tooltipContent} | ||
| 84 | {@render Button({})} | ||
| 85 | {:else} | ||
| 86 | <Tooltip.Root> | ||
| 87 | <Tooltip.Trigger> | ||
| 88 | {#snippet child({ props })} | ||
| 89 | {@render Button({ props })} | ||
| 90 | {/snippet} | ||
| 91 | </Tooltip.Trigger> | ||
| 92 | |||
| 93 | <Tooltip.Content | ||
| 94 | side="right" | ||
| 95 | align="center" | ||
| 96 | hidden={sidebar.state !== 'collapsed' || sidebar.isMobile} | ||
| 97 | {...tooltipContentProps} | ||
| 98 | > | ||
| 99 | {#if typeof tooltipContent === 'string'} | ||
| 100 | {tooltipContent} | ||
| 101 | {:else if tooltipContent} | ||
| 102 | {@render tooltipContent()} | ||
| 103 | {/if} | ||
| 104 | </Tooltip.Content> | ||
| 105 | </Tooltip.Root> | ||
| 106 | {/if} | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-item.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-item.svelte new file mode 100644 index 0000000..5adbedd --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-item.svelte | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 3 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 4 | |||
| 5 | let { | ||
| 6 | ref = $bindable(null), | ||
| 7 | class: className, | ||
| 8 | children, | ||
| 9 | ...restProps | ||
| 10 | }: WithElementRef<HTMLAttributes<HTMLLIElement>, HTMLLIElement> = $props(); | ||
| 11 | </script> | ||
| 12 | |||
| 13 | <li | ||
| 14 | bind:this={ref} | ||
| 15 | data-slot="sidebar-menu-item" | ||
| 16 | data-sidebar="menu-item" | ||
| 17 | class={cn('group/menu-item relative', className)} | ||
| 18 | {...restProps} | ||
| 19 | > | ||
| 20 | {@render children?.()} | ||
| 21 | </li> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte new file mode 100644 index 0000000..2b2acd6 --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 3 | import { Skeleton } from '$lib/components/ui/skeleton/index.js'; | ||
| 4 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 5 | |||
| 6 | let { | ||
| 7 | ref = $bindable(null), | ||
| 8 | class: className, | ||
| 9 | showIcon = false, | ||
| 10 | children, | ||
| 11 | ...restProps | ||
| 12 | }: WithElementRef<HTMLAttributes<HTMLElement>> & { | ||
| 13 | showIcon?: boolean; | ||
| 14 | } = $props(); | ||
| 15 | |||
| 16 | // Random width between 50% and 90% | ||
| 17 | const width = `${Math.floor(Math.random() * 40) + 50}%`; | ||
| 18 | </script> | ||
| 19 | |||
| 20 | <div | ||
| 21 | bind:this={ref} | ||
| 22 | data-slot="sidebar-menu-skeleton" | ||
| 23 | data-sidebar="menu-skeleton" | ||
| 24 | class={cn('flex h-8 items-center gap-2 rounded-md px-2', className)} | ||
| 25 | {...restProps} | ||
| 26 | > | ||
| 27 | {#if showIcon} | ||
| 28 | <Skeleton class="size-4 rounded-md" data-sidebar="menu-skeleton-icon" /> | ||
| 29 | {/if} | ||
| 30 | <Skeleton | ||
| 31 | class="h-4 max-w-(--skeleton-width) flex-1" | ||
| 32 | data-sidebar="menu-skeleton-text" | ||
| 33 | style="--skeleton-width: {width};" | ||
| 34 | /> | ||
| 35 | {@render children?.()} | ||
| 36 | </div> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte new file mode 100644 index 0000000..dabfe0f --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 3 | import type { Snippet } from 'svelte'; | ||
| 4 | import type { HTMLAnchorAttributes } from 'svelte/elements'; | ||
| 5 | |||
| 6 | let { | ||
| 7 | ref = $bindable(null), | ||
| 8 | children, | ||
| 9 | child, | ||
| 10 | class: className, | ||
| 11 | size = 'md', | ||
| 12 | isActive = false, | ||
| 13 | ...restProps | ||
| 14 | }: WithElementRef<HTMLAnchorAttributes> & { | ||
| 15 | child?: Snippet<[{ props: Record<string, unknown> }]>; | ||
| 16 | size?: 'sm' | 'md'; | ||
| 17 | isActive?: boolean; | ||
| 18 | } = $props(); | ||
| 19 | |||
| 20 | const mergedProps = $derived({ | ||
| 21 | class: cn( | ||
| 22 | 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground outline-hidden flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', | ||
| 23 | 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground', | ||
| 24 | size === 'sm' && 'text-xs', | ||
| 25 | size === 'md' && 'text-sm', | ||
| 26 | 'group-data-[collapsible=icon]:hidden', | ||
| 27 | className | ||
| 28 | ), | ||
| 29 | 'data-slot': 'sidebar-menu-sub-button', | ||
| 30 | 'data-sidebar': 'menu-sub-button', | ||
| 31 | 'data-size': size, | ||
| 32 | 'data-active': isActive, | ||
| 33 | ...restProps | ||
| 34 | }); | ||
| 35 | </script> | ||
| 36 | |||
| 37 | {#if child} | ||
| 38 | {@render child({ props: mergedProps })} | ||
| 39 | {:else} | ||
| 40 | <a bind:this={ref} {...mergedProps}> | ||
| 41 | {@render children?.()} | ||
| 42 | </a> | ||
| 43 | {/if} | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte new file mode 100644 index 0000000..cca870e --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 3 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 4 | |||
| 5 | let { | ||
| 6 | ref = $bindable(null), | ||
| 7 | children, | ||
| 8 | class: className, | ||
| 9 | ...restProps | ||
| 10 | }: WithElementRef<HTMLAttributes<HTMLLIElement>> = $props(); | ||
| 11 | </script> | ||
| 12 | |||
| 13 | <li | ||
| 14 | bind:this={ref} | ||
| 15 | data-slot="sidebar-menu-sub-item" | ||
| 16 | data-sidebar="menu-sub-item" | ||
| 17 | class={cn('group/menu-sub-item relative', className)} | ||
| 18 | {...restProps} | ||
| 19 | > | ||
| 20 | {@render children?.()} | ||
| 21 | </li> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte new file mode 100644 index 0000000..5458ced --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 3 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 4 | |||
| 5 | let { | ||
| 6 | ref = $bindable(null), | ||
| 7 | class: className, | ||
| 8 | children, | ||
| 9 | ...restProps | ||
| 10 | }: WithElementRef<HTMLAttributes<HTMLUListElement>> = $props(); | ||
| 11 | </script> | ||
| 12 | |||
| 13 | <ul | ||
| 14 | bind:this={ref} | ||
| 15 | data-slot="sidebar-menu-sub" | ||
| 16 | data-sidebar="menu-sub" | ||
| 17 | class={cn( | ||
| 18 | 'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5', | ||
| 19 | 'group-data-[collapsible=icon]:hidden', | ||
| 20 | className | ||
| 21 | )} | ||
| 22 | {...restProps} | ||
| 23 | > | ||
| 24 | {@render children?.()} | ||
| 25 | </ul> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu.svelte new file mode 100644 index 0000000..fee96ed --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-menu.svelte | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 3 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 4 | |||
| 5 | let { | ||
| 6 | ref = $bindable(null), | ||
| 7 | class: className, | ||
| 8 | children, | ||
| 9 | ...restProps | ||
| 10 | }: WithElementRef<HTMLAttributes<HTMLUListElement>, HTMLUListElement> = $props(); | ||
| 11 | </script> | ||
| 12 | |||
| 13 | <ul | ||
| 14 | bind:this={ref} | ||
| 15 | data-slot="sidebar-menu" | ||
| 16 | data-sidebar="menu" | ||
| 17 | class={cn('flex w-full min-w-0 flex-col gap-1', className)} | ||
| 18 | {...restProps} | ||
| 19 | > | ||
| 20 | {@render children?.()} | ||
| 21 | </ul> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-provider.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-provider.svelte new file mode 100644 index 0000000..364235a --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-provider.svelte | |||
| @@ -0,0 +1,50 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 3 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 4 | import { | ||
| 5 | SIDEBAR_COOKIE_MAX_AGE, | ||
| 6 | SIDEBAR_COOKIE_NAME, | ||
| 7 | SIDEBAR_WIDTH, | ||
| 8 | SIDEBAR_WIDTH_ICON | ||
| 9 | } from './constants.js'; | ||
| 10 | import { setSidebar } from './context.svelte.js'; | ||
| 11 | |||
| 12 | let { | ||
| 13 | ref = $bindable(null), | ||
| 14 | open = $bindable(true), | ||
| 15 | onOpenChange = () => {}, | ||
| 16 | class: className, | ||
| 17 | style, | ||
| 18 | children, | ||
| 19 | ...restProps | ||
| 20 | }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { | ||
| 21 | open?: boolean; | ||
| 22 | onOpenChange?: (open: boolean) => void; | ||
| 23 | } = $props(); | ||
| 24 | |||
| 25 | const sidebar = setSidebar({ | ||
| 26 | open: () => open, | ||
| 27 | setOpen: (value: boolean) => { | ||
| 28 | open = value; | ||
| 29 | onOpenChange(value); | ||
| 30 | |||
| 31 | // This sets the cookie to keep the sidebar state. | ||
| 32 | document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; | ||
| 33 | } | ||
| 34 | }); | ||
| 35 | </script> | ||
| 36 | |||
| 37 | <svelte:window onkeydown={sidebar.handleShortcutKeydown} /> | ||
| 38 | |||
| 39 | <div | ||
| 40 | data-slot="sidebar-wrapper" | ||
| 41 | style="--sidebar-width: {SIDEBAR_WIDTH}; --sidebar-width-icon: {SIDEBAR_WIDTH_ICON}; {style}" | ||
| 42 | class={cn( | ||
| 43 | 'group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar', | ||
| 44 | className | ||
| 45 | )} | ||
| 46 | bind:this={ref} | ||
| 47 | {...restProps} | ||
| 48 | > | ||
| 49 | {@render children?.()} | ||
| 50 | </div> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-rail.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-rail.svelte new file mode 100644 index 0000000..cde9307 --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-rail.svelte | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 3 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 4 | import { useSidebar } from './context.svelte.js'; | ||
| 5 | |||
| 6 | let { | ||
| 7 | ref = $bindable(null), | ||
| 8 | class: className, | ||
| 9 | children, | ||
| 10 | ...restProps | ||
| 11 | }: WithElementRef<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> = $props(); | ||
| 12 | |||
| 13 | const sidebar = useSidebar(); | ||
| 14 | </script> | ||
| 15 | |||
| 16 | <button | ||
| 17 | bind:this={ref} | ||
| 18 | data-sidebar="rail" | ||
| 19 | data-slot="sidebar-rail" | ||
| 20 | aria-label="Toggle Sidebar" | ||
| 21 | tabIndex={-1} | ||
| 22 | onclick={sidebar.toggle} | ||
| 23 | title="Toggle Sidebar" | ||
| 24 | class={cn( | ||
| 25 | 'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-[calc(1/2*100%-1px)] after:w-[2px] hover:after:bg-sidebar-border sm:flex', | ||
| 26 | 'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize', | ||
| 27 | '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize', | ||
| 28 | 'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar', | ||
| 29 | '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2', | ||
| 30 | '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2', | ||
| 31 | className | ||
| 32 | )} | ||
| 33 | {...restProps} | ||
| 34 | > | ||
| 35 | {@render children?.()} | ||
| 36 | </button> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-separator.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-separator.svelte new file mode 100644 index 0000000..8fc2065 --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-separator.svelte | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { Separator } from '$lib/components/ui/separator/index.js'; | ||
| 3 | import { cn } from '$lib/components/ui/utils.js'; | ||
| 4 | import type { ComponentProps } from 'svelte'; | ||
| 5 | |||
| 6 | let { | ||
| 7 | ref = $bindable(null), | ||
| 8 | class: className, | ||
| 9 | ...restProps | ||
| 10 | }: ComponentProps<typeof Separator> = $props(); | ||
| 11 | </script> | ||
| 12 | |||
| 13 | <Separator | ||
| 14 | bind:ref | ||
| 15 | data-slot="sidebar-separator" | ||
| 16 | data-sidebar="separator" | ||
| 17 | class={cn('bg-sidebar-border', className)} | ||
| 18 | {...restProps} | ||
| 19 | /> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-trigger.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-trigger.svelte new file mode 100644 index 0000000..29d3a9c --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar-trigger.svelte | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import { Button } from '$lib/components/ui/button/index.js'; | ||
| 3 | import { cn } from '$lib/components/ui/utils.js'; | ||
| 4 | import PanelLeftIcon from '@lucide/svelte/icons/panel-left'; | ||
| 5 | import type { ComponentProps } from 'svelte'; | ||
| 6 | import { useSidebar } from './context.svelte.js'; | ||
| 7 | |||
| 8 | let { | ||
| 9 | ref = $bindable(null), | ||
| 10 | class: className, | ||
| 11 | onclick, | ||
| 12 | ...restProps | ||
| 13 | }: ComponentProps<typeof Button> & { | ||
| 14 | onclick?: (e: MouseEvent) => void; | ||
| 15 | } = $props(); | ||
| 16 | |||
| 17 | const sidebar = useSidebar(); | ||
| 18 | </script> | ||
| 19 | |||
| 20 | <Button | ||
| 21 | data-sidebar="trigger" | ||
| 22 | data-slot="sidebar-trigger" | ||
| 23 | variant="ghost" | ||
| 24 | size="icon" | ||
| 25 | class={cn('size-7', className)} | ||
| 26 | type="button" | ||
| 27 | onclick={(e) => { | ||
| 28 | onclick?.(e); | ||
| 29 | sidebar.toggle(); | ||
| 30 | }} | ||
| 31 | {...restProps} | ||
| 32 | > | ||
| 33 | <PanelLeftIcon /> | ||
| 34 | <span class="sr-only">Toggle Sidebar</span> | ||
| 35 | </Button> | ||
diff --git a/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar.svelte b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar.svelte new file mode 100644 index 0000000..e2c4a75 --- /dev/null +++ b/llama.cpp/tools/server/webui/src/lib/components/ui/sidebar/sidebar.svelte | |||
| @@ -0,0 +1,101 @@ | |||
| 1 | <script lang="ts"> | ||
| 2 | import * as Sheet from '$lib/components/ui/sheet/index.js'; | ||
| 3 | import { cn, type WithElementRef } from '$lib/components/ui/utils.js'; | ||
| 4 | import type { HTMLAttributes } from 'svelte/elements'; | ||
| 5 | import { SIDEBAR_WIDTH_MOBILE } from './constants.js'; | ||
| 6 | import { useSidebar } from './context.svelte.js'; | ||
| 7 | |||
| 8 | let { | ||
| 9 | ref = $bindable(null), | ||
| 10 | side = 'left', | ||
| 11 | variant = 'sidebar', | ||
| 12 | collapsible = 'offcanvas', | ||
| 13 | class: className, | ||
| 14 | children, | ||
| 15 | ...restProps | ||
| 16 | }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { | ||
| 17 | side?: 'left' | 'right'; | ||
| 18 | variant?: 'sidebar' | 'floating' | 'inset'; | ||
| 19 | collapsible?: 'offcanvas' | 'icon' | 'none'; | ||
| 20 | } = $props(); | ||
| 21 | |||
| 22 | const sidebar = useSidebar(); | ||
| 23 | </script> | ||
| 24 | |||
| 25 | {#if collapsible === 'none'} | ||
| 26 | <div | ||
| 27 | class={cn( | ||
| 28 | 'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground', | ||
| 29 | className | ||
| 30 | )} | ||
| 31 | bind:this={ref} | ||
| 32 | {...restProps} | ||
| 33 | > | ||
| 34 | {@render children?.()} | ||
| 35 | </div> | ||
| 36 | {:else if sidebar.isMobile} | ||
| 37 | <Sheet.Root bind:open={() => sidebar.openMobile, (v) => sidebar.setOpenMobile(v)} {...restProps}> | ||
| 38 | <Sheet.Content | ||
| 39 | data-sidebar="sidebar" | ||
| 40 | data-slot="sidebar" | ||
| 41 | data-mobile="true" | ||
| 42 | class="z-99999 w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground sm:z-99 [&>button]:hidden" | ||
| 43 | style="--sidebar-width: {SIDEBAR_WIDTH_MOBILE};" | ||
| 44 | {side} | ||
| 45 | > | ||
| 46 | <Sheet.Header class="sr-only"> | ||
| 47 | <Sheet.Title>Sidebar</Sheet.Title> | ||
| 48 | <Sheet.Description>Displays the mobile sidebar.</Sheet.Description> | ||
| 49 | </Sheet.Header> | ||
| 50 | <div class="flex h-full w-full flex-col"> | ||
| 51 | {@render children?.()} | ||
| 52 | </div> | ||
| 53 | </Sheet.Content> | ||
| 54 | </Sheet.Root> | ||
| 55 | {:else} | ||
| 56 | <div | ||
| 57 | bind:this={ref} | ||
| 58 | class="group peer hidden text-sidebar-foreground md:block" | ||
| 59 | data-state={sidebar.state} | ||
| 60 | data-collapsible={sidebar.state === 'collapsed' ? collapsible : ''} | ||
| 61 | data-variant={variant} | ||
| 62 | data-side={side} | ||
| 63 | data-slot="sidebar" | ||
| 64 | > | ||
| 65 | <!-- This is what handles the sidebar gap on desktop --> | ||
| 66 | <div | ||
| 67 | data-slot="sidebar-gap" | ||
| 68 | class={cn( | ||
| 69 | 'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear', | ||
| 70 | 'group-data-[collapsible=offcanvas]:w-0', | ||
| 71 | 'group-data-[side=right]:rotate-180', | ||
| 72 | variant === 'floating' || variant === 'inset' | ||
| 73 | ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]' | ||
| 74 | : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)' | ||
| 75 | )} | ||
| 76 | ></div> | ||
| 77 | <div | ||
| 78 | data-slot="sidebar-container" | ||
| 79 | class={cn( | ||
| 80 | 'fixed inset-y-0 z-999 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:z-0 md:flex', | ||
| 81 | side === 'left' | ||
| 82 | ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]' | ||
| 83 | : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]', | ||
| 84 | // Adjust the padding for floating and inset variants. | ||
| 85 | variant === 'floating' || variant === 'inset' | ||
| 86 | ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]' | ||
| 87 | : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)', | ||
| 88 | className | ||
| 89 | )} | ||
| 90 | {...restProps} | ||
| 91 | > | ||
| 92 | <div | ||
| 93 | data-sidebar="sidebar" | ||
| 94 | data-slot="sidebar-inner" | ||
| 95 | class="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow-sm" | ||
| 96 | > | ||
| 97 | {@render children?.()} | ||
| 98 | </div> | ||
| 99 | </div> | ||
| 100 | </div> | ||
| 101 | {/if} | ||
