feat(webui): add shadcn-svelte ui component library with utilities
- commit
- d1786342b8d3f0ba0323e5cad0579f60deb918e9
- author
- bnema · · 2025-12-21 13:53 UTC
- parent
- ac16f2b
- signature
- unsigned
34 files · +710 -0
A webui/components.json +16 -0
| @@ -0,0 +1,16 @@ | |||
| 1 | + | { | |
| 2 | + | "$schema": "https://shadcn-svelte.com/schema.json", | |
| 3 | + | "tailwind": { | |
| 4 | + | "css": "src/styles/app.css", | |
| 5 | + | "baseColor": "neutral" | |
| 6 | + | }, | |
| 7 | + | "aliases": { | |
| 8 | + | "components": "$lib/components", | |
| 9 | + | "utils": "$lib/utils", | |
| 10 | + | "ui": "$lib/components/ui", | |
| 11 | + | "hooks": "$lib/hooks", | |
| 12 | + | "lib": "$lib" | |
| 13 | + | }, | |
| 14 | + | "typescript": true, | |
| 15 | + | "registry": "https://shadcn-svelte.com/registry" | |
| 16 | + | } | |
| @@ -0,0 +1,16 @@ | |||||
| 1 | + | { | |||
| 2 | + | "$schema": "https://shadcn-svelte.com/schema.json", | |||
| 3 | + | "tailwind": { | |||
| 4 | + | "css": "src/styles/app.css", | |||
| 5 | + | "baseColor": "neutral" | |||
| 6 | + | }, | |||
| 7 | + | "aliases": { | |||
| 8 | + | "components": "$lib/components", | |||
| 9 | + | "utils": "$lib/utils", | |||
| 10 | + | "ui": "$lib/components/ui", | |||
| 11 | + | "hooks": "$lib/hooks", | |||
| 12 | + | "lib": "$lib" | |||
| 13 | + | }, | |||
| 14 | + | "typescript": true, | |||
| 15 | + | "registry": "https://shadcn-svelte.com/registry" | |||
| 16 | + | } | |||
A webui/src/lib/components/ui/button/button.svelte +82 -0
| @@ -0,0 +1,82 @@ | |||
| 1 | + | <script lang="ts" module> | |
| 2 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |
| 3 | + | import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements"; | |
| 4 | + | import { type VariantProps, tv } from "tailwind-variants"; | |
| 5 | + | ||
| 6 | + | export const buttonVariants = tv({ | |
| 7 | + | base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", | |
| 8 | + | variants: { | |
| 9 | + | variant: { | |
| 10 | + | default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow-xs", | |
| 11 | + | destructive: | |
| 12 | + | "bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white shadow-xs", | |
| 13 | + | outline: | |
| 14 | + | "bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border border-border shadow-xs", | |
| 15 | + | secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-xs", | |
| 16 | + | ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", | |
| 17 | + | link: "text-primary underline-offset-4 hover:underline", | |
| 18 | + | }, | |
| 19 | + | size: { | |
| 20 | + | default: "h-9 px-4 py-2 has-[>svg]:px-3", | |
| 21 | + | sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5", | |
| 22 | + | lg: "h-10 px-6 has-[>svg]:px-4", | |
| 23 | + | icon: "size-9", | |
| 24 | + | "icon-sm": "size-8", | |
| 25 | + | "icon-lg": "size-10", | |
| 26 | + | }, | |
| 27 | + | }, | |
| 28 | + | defaultVariants: { | |
| 29 | + | variant: "default", | |
| 30 | + | size: "default", | |
| 31 | + | }, | |
| 32 | + | }); | |
| 33 | + | ||
| 34 | + | export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"]; | |
| 35 | + | export type ButtonSize = VariantProps<typeof buttonVariants>["size"]; | |
| 36 | + | ||
| 37 | + | export type ButtonProps = WithElementRef<HTMLButtonAttributes> & | |
| 38 | + | WithElementRef<HTMLAnchorAttributes> & { | |
| 39 | + | variant?: ButtonVariant; | |
| 40 | + | size?: ButtonSize; | |
| 41 | + | }; | |
| 42 | + | </script> | |
| 43 | + | ||
| 44 | + | <script lang="ts"> | |
| 45 | + | let { | |
| 46 | + | class: className, | |
| 47 | + | variant = "default", | |
| 48 | + | size = "default", | |
| 49 | + | ref = $bindable(null), | |
| 50 | + | href = undefined, | |
| 51 | + | type = "button", | |
| 52 | + | disabled, | |
| 53 | + | children, | |
| 54 | + | ...restProps | |
| 55 | + | }: ButtonProps = $props(); | |
| 56 | + | </script> | |
| 57 | + | ||
| 58 | + | {#if href} | |
| 59 | + | <a | |
| 60 | + | bind:this={ref} | |
| 61 | + | data-slot="button" | |
| 62 | + | class={cn(buttonVariants({ variant, size }), className)} | |
| 63 | + | href={disabled ? undefined : href} | |
| 64 | + | aria-disabled={disabled} | |
| 65 | + | role={disabled ? "link" : undefined} | |
| 66 | + | tabindex={disabled ? -1 : undefined} | |
| 67 | + | {...restProps} | |
| 68 | + | > | |
| 69 | + | {@render children?.()} | |
| 70 | + | </a> | |
| 71 | + | {:else} | |
| 72 | + | <button | |
| 73 | + | bind:this={ref} | |
| 74 | + | data-slot="button" | |
| 75 | + | class={cn(buttonVariants({ variant, size }), className)} | |
| 76 | + | {type} | |
| 77 | + | {disabled} | |
| 78 | + | {...restProps} | |
| 79 | + | > | |
| 80 | + | {@render children?.()} | |
| 81 | + | </button> | |
| 82 | + | {/if} | |
| @@ -0,0 +1,82 @@ | |||||
| 1 | + | <script lang="ts" module> | |||
| 2 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |||
| 3 | + | import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements"; | |||
| 4 | + | import { type VariantProps, tv } from "tailwind-variants"; | |||
| 5 | + | ||||
| 6 | + | export const buttonVariants = tv({ | |||
| 7 | + | base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", | |||
| 8 | + | variants: { | |||
| 9 | + | variant: { | |||
| 10 | + | default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow-xs", | |||
| 11 | + | destructive: | |||
| 12 | + | "bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white shadow-xs", | |||
| 13 | + | outline: | |||
| 14 | + | "bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border border-border shadow-xs", | |||
| 15 | + | secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-xs", | |||
| 16 | + | ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", | |||
| 17 | + | link: "text-primary underline-offset-4 hover:underline", | |||
| 18 | + | }, | |||
| 19 | + | size: { | |||
| 20 | + | default: "h-9 px-4 py-2 has-[>svg]:px-3", | |||
| 21 | + | sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5", | |||
| 22 | + | lg: "h-10 px-6 has-[>svg]:px-4", | |||
| 23 | + | icon: "size-9", | |||
| 24 | + | "icon-sm": "size-8", | |||
| 25 | + | "icon-lg": "size-10", | |||
| 26 | + | }, | |||
| 27 | + | }, | |||
| 28 | + | defaultVariants: { | |||
| 29 | + | variant: "default", | |||
| 30 | + | size: "default", | |||
| 31 | + | }, | |||
| 32 | + | }); | |||
| 33 | + | ||||
| 34 | + | export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"]; | |||
| 35 | + | export type ButtonSize = VariantProps<typeof buttonVariants>["size"]; | |||
| 36 | + | ||||
| 37 | + | export type ButtonProps = WithElementRef<HTMLButtonAttributes> & | |||
| 38 | + | WithElementRef<HTMLAnchorAttributes> & { | |||
| 39 | + | variant?: ButtonVariant; | |||
| 40 | + | size?: ButtonSize; | |||
| 41 | + | }; | |||
| 42 | + | </script> | |||
| 43 | + | ||||
| 44 | + | <script lang="ts"> | |||
| 45 | + | let { | |||
| 46 | + | class: className, | |||
| 47 | + | variant = "default", | |||
| 48 | + | size = "default", | |||
| 49 | + | ref = $bindable(null), | |||
| 50 | + | href = undefined, | |||
| 51 | + | type = "button", | |||
| 52 | + | disabled, | |||
| 53 | + | children, | |||
| 54 | + | ...restProps | |||
| 55 | + | }: ButtonProps = $props(); | |||
| 56 | + | </script> | |||
| 57 | + | ||||
| 58 | + | {#if href} | |||
| 59 | + | <a | |||
| 60 | + | bind:this={ref} | |||
| 61 | + | data-slot="button" | |||
| 62 | + | class={cn(buttonVariants({ variant, size }), className)} | |||
| 63 | + | href={disabled ? undefined : href} | |||
| 64 | + | aria-disabled={disabled} | |||
| 65 | + | role={disabled ? "link" : undefined} | |||
| 66 | + | tabindex={disabled ? -1 : undefined} | |||
| 67 | + | {...restProps} | |||
| 68 | + | > | |||
| 69 | + | {@render children?.()} | |||
| 70 | + | </a> | |||
| 71 | + | {:else} | |||
| 72 | + | <button | |||
| 73 | + | bind:this={ref} | |||
| 74 | + | data-slot="button" | |||
| 75 | + | class={cn(buttonVariants({ variant, size }), className)} | |||
| 76 | + | {type} | |||
| 77 | + | {disabled} | |||
| 78 | + | {...restProps} | |||
| 79 | + | > | |||
| 80 | + | {@render children?.()} | |||
| 81 | + | </button> | |||
| 82 | + | {/if} | |||
A webui/src/lib/components/ui/button/index.ts +17 -0
| @@ -0,0 +1,17 @@ | |||
| 1 | + | import Root, { | |
| 2 | + | type ButtonProps, | |
| 3 | + | type ButtonSize, | |
| 4 | + | type ButtonVariant, | |
| 5 | + | buttonVariants, | |
| 6 | + | } from "./button.svelte"; | |
| 7 | + | ||
| 8 | + | export { | |
| 9 | + | Root, | |
| 10 | + | type ButtonProps as Props, | |
| 11 | + | // | |
| 12 | + | Root as Button, | |
| 13 | + | buttonVariants, | |
| 14 | + | type ButtonProps, | |
| 15 | + | type ButtonSize, | |
| 16 | + | type ButtonVariant, | |
| 17 | + | }; | |
| @@ -0,0 +1,17 @@ | |||||
| 1 | + | import Root, { | |||
| 2 | + | type ButtonProps, | |||
| 3 | + | type ButtonSize, | |||
| 4 | + | type ButtonVariant, | |||
| 5 | + | buttonVariants, | |||
| 6 | + | } from "./button.svelte"; | |||
| 7 | + | ||||
| 8 | + | export { | |||
| 9 | + | Root, | |||
| 10 | + | type ButtonProps as Props, | |||
| 11 | + | // | |||
| 12 | + | Root as Button, | |||
| 13 | + | buttonVariants, | |||
| 14 | + | type ButtonProps, | |||
| 15 | + | type ButtonSize, | |||
| 16 | + | type ButtonVariant, | |||
| 17 | + | }; | |||
A webui/src/lib/components/ui/card/card-action.svelte +20 -0
| @@ -0,0 +1,20 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { cn, type WithElementRef } from "$lib/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="card-action" | |
| 16 | + | class={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)} | |
| 17 | + | {...restProps} | |
| 18 | + | > | |
| 19 | + | {@render children?.()} | |
| 20 | + | </div> | |
| @@ -0,0 +1,20 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { cn, type WithElementRef } from "$lib/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="card-action" | |||
| 16 | + | class={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)} | |||
| 17 | + | {...restProps} | |||
| 18 | + | > | |||
| 19 | + | {@render children?.()} | |||
| 20 | + | </div> | |||
A webui/src/lib/components/ui/card/card-content.svelte +15 -0
| @@ -0,0 +1,15 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import type { HTMLAttributes } from "svelte/elements"; | |
| 3 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |
| 4 | + | ||
| 5 | + | let { | |
| 6 | + | ref = $bindable(null), | |
| 7 | + | class: className, | |
| 8 | + | children, | |
| 9 | + | ...restProps | |
| 10 | + | }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props(); | |
| 11 | + | </script> | |
| 12 | + | ||
| 13 | + | <div bind:this={ref} data-slot="card-content" class={cn("px-6", className)} {...restProps}> | |
| 14 | + | {@render children?.()} | |
| 15 | + | </div> | |
| @@ -0,0 +1,15 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import type { HTMLAttributes } from "svelte/elements"; | |||
| 3 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |||
| 4 | + | ||||
| 5 | + | let { | |||
| 6 | + | ref = $bindable(null), | |||
| 7 | + | class: className, | |||
| 8 | + | children, | |||
| 9 | + | ...restProps | |||
| 10 | + | }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props(); | |||
| 11 | + | </script> | |||
| 12 | + | ||||
| 13 | + | <div bind:this={ref} data-slot="card-content" class={cn("px-6", className)} {...restProps}> | |||
| 14 | + | {@render children?.()} | |||
| 15 | + | </div> | |||
A webui/src/lib/components/ui/card/card-description.svelte +20 -0
| @@ -0,0 +1,20 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import type { HTMLAttributes } from "svelte/elements"; | |
| 3 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |
| 4 | + | ||
| 5 | + | let { | |
| 6 | + | ref = $bindable(null), | |
| 7 | + | class: className, | |
| 8 | + | children, | |
| 9 | + | ...restProps | |
| 10 | + | }: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props(); | |
| 11 | + | </script> | |
| 12 | + | ||
| 13 | + | <p | |
| 14 | + | bind:this={ref} | |
| 15 | + | data-slot="card-description" | |
| 16 | + | class={cn("text-muted-foreground text-sm", className)} | |
| 17 | + | {...restProps} | |
| 18 | + | > | |
| 19 | + | {@render children?.()} | |
| 20 | + | </p> | |
| @@ -0,0 +1,20 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import type { HTMLAttributes } from "svelte/elements"; | |||
| 3 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |||
| 4 | + | ||||
| 5 | + | let { | |||
| 6 | + | ref = $bindable(null), | |||
| 7 | + | class: className, | |||
| 8 | + | children, | |||
| 9 | + | ...restProps | |||
| 10 | + | }: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props(); | |||
| 11 | + | </script> | |||
| 12 | + | ||||
| 13 | + | <p | |||
| 14 | + | bind:this={ref} | |||
| 15 | + | data-slot="card-description" | |||
| 16 | + | class={cn("text-muted-foreground text-sm", className)} | |||
| 17 | + | {...restProps} | |||
| 18 | + | > | |||
| 19 | + | {@render children?.()} | |||
| 20 | + | </p> | |||
A webui/src/lib/components/ui/card/card-footer.svelte +20 -0
| @@ -0,0 +1,20 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { cn, type WithElementRef } from "$lib/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="card-footer" | |
| 16 | + | class={cn("flex items-center px-6 [.border-t]:pt-6", className)} | |
| 17 | + | {...restProps} | |
| 18 | + | > | |
| 19 | + | {@render children?.()} | |
| 20 | + | </div> | |
| @@ -0,0 +1,20 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { cn, type WithElementRef } from "$lib/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="card-footer" | |||
| 16 | + | class={cn("flex items-center px-6 [.border-t]:pt-6", className)} | |||
| 17 | + | {...restProps} | |||
| 18 | + | > | |||
| 19 | + | {@render children?.()} | |||
| 20 | + | </div> | |||
A webui/src/lib/components/ui/card/card-header.svelte +23 -0
| @@ -0,0 +1,23 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { cn, type WithElementRef } from "$lib/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="card-header" | |
| 16 | + | class={cn( | |
| 17 | + | "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", | |
| 18 | + | className | |
| 19 | + | )} | |
| 20 | + | {...restProps} | |
| 21 | + | > | |
| 22 | + | {@render children?.()} | |
| 23 | + | </div> | |
| @@ -0,0 +1,23 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { cn, type WithElementRef } from "$lib/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="card-header" | |||
| 16 | + | class={cn( | |||
| 17 | + | "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", | |||
| 18 | + | className | |||
| 19 | + | )} | |||
| 20 | + | {...restProps} | |||
| 21 | + | > | |||
| 22 | + | {@render children?.()} | |||
| 23 | + | </div> | |||
A webui/src/lib/components/ui/card/card-title.svelte +20 -0
| @@ -0,0 +1,20 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import type { HTMLAttributes } from "svelte/elements"; | |
| 3 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |
| 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="card-title" | |
| 16 | + | class={cn("leading-none font-semibold", className)} | |
| 17 | + | {...restProps} | |
| 18 | + | > | |
| 19 | + | {@render children?.()} | |
| 20 | + | </div> | |
| @@ -0,0 +1,20 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import type { HTMLAttributes } from "svelte/elements"; | |||
| 3 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |||
| 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="card-title" | |||
| 16 | + | class={cn("leading-none font-semibold", className)} | |||
| 17 | + | {...restProps} | |||
| 18 | + | > | |||
| 19 | + | {@render children?.()} | |||
| 20 | + | </div> | |||
A webui/src/lib/components/ui/card/card.svelte +23 -0
| @@ -0,0 +1,23 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import type { HTMLAttributes } from "svelte/elements"; | |
| 3 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |
| 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="card" | |
| 16 | + | class={cn( | |
| 17 | + | "bg-card text-card-foreground flex flex-col gap-6 border border-border py-6 shadow-sm", | |
| 18 | + | className | |
| 19 | + | )} | |
| 20 | + | {...restProps} | |
| 21 | + | > | |
| 22 | + | {@render children?.()} | |
| 23 | + | </div> | |
| @@ -0,0 +1,23 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import type { HTMLAttributes } from "svelte/elements"; | |||
| 3 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |||
| 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="card" | |||
| 16 | + | class={cn( | |||
| 17 | + | "bg-card text-card-foreground flex flex-col gap-6 border border-border py-6 shadow-sm", | |||
| 18 | + | className | |||
| 19 | + | )} | |||
| 20 | + | {...restProps} | |||
| 21 | + | > | |||
| 22 | + | {@render children?.()} | |||
| 23 | + | </div> | |||
A webui/src/lib/components/ui/card/index.ts +25 -0
| @@ -0,0 +1,25 @@ | |||
| 1 | + | import Root from "./card.svelte"; | |
| 2 | + | import Content from "./card-content.svelte"; | |
| 3 | + | import Description from "./card-description.svelte"; | |
| 4 | + | import Footer from "./card-footer.svelte"; | |
| 5 | + | import Header from "./card-header.svelte"; | |
| 6 | + | import Title from "./card-title.svelte"; | |
| 7 | + | import Action from "./card-action.svelte"; | |
| 8 | + | ||
| 9 | + | export { | |
| 10 | + | Root, | |
| 11 | + | Content, | |
| 12 | + | Description, | |
| 13 | + | Footer, | |
| 14 | + | Header, | |
| 15 | + | Title, | |
| 16 | + | Action, | |
| 17 | + | // | |
| 18 | + | Root as Card, | |
| 19 | + | Content as CardContent, | |
| 20 | + | Description as CardDescription, | |
| 21 | + | Footer as CardFooter, | |
| 22 | + | Header as CardHeader, | |
| 23 | + | Title as CardTitle, | |
| 24 | + | Action as CardAction, | |
| 25 | + | }; | |
| @@ -0,0 +1,25 @@ | |||||
| 1 | + | import Root from "./card.svelte"; | |||
| 2 | + | import Content from "./card-content.svelte"; | |||
| 3 | + | import Description from "./card-description.svelte"; | |||
| 4 | + | import Footer from "./card-footer.svelte"; | |||
| 5 | + | import Header from "./card-header.svelte"; | |||
| 6 | + | import Title from "./card-title.svelte"; | |||
| 7 | + | import Action from "./card-action.svelte"; | |||
| 8 | + | ||||
| 9 | + | export { | |||
| 10 | + | Root, | |||
| 11 | + | Content, | |||
| 12 | + | Description, | |||
| 13 | + | Footer, | |||
| 14 | + | Header, | |||
| 15 | + | Title, | |||
| 16 | + | Action, | |||
| 17 | + | // | |||
| 18 | + | Root as Card, | |||
| 19 | + | Content as CardContent, | |||
| 20 | + | Description as CardDescription, | |||
| 21 | + | Footer as CardFooter, | |||
| 22 | + | Header as CardHeader, | |||
| 23 | + | Title as CardTitle, | |||
| 24 | + | Action as CardAction, | |||
| 25 | + | }; | |||
A webui/src/lib/components/ui/input/index.ts +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | import Root from "./input.svelte"; | |
| 2 | + | ||
| 3 | + | export { | |
| 4 | + | Root, | |
| 5 | + | // | |
| 6 | + | Root as Input, | |
| 7 | + | }; | |
| @@ -0,0 +1,7 @@ | |||||
| 1 | + | import Root from "./input.svelte"; | |||
| 2 | + | ||||
| 3 | + | export { | |||
| 4 | + | Root, | |||
| 5 | + | // | |||
| 6 | + | Root as Input, | |||
| 7 | + | }; | |||
A webui/src/lib/components/ui/input/input.svelte +52 -0
| @@ -0,0 +1,52 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements"; | |
| 3 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |
| 4 | + | ||
| 5 | + | type InputType = Exclude<HTMLInputTypeAttribute, "file">; | |
| 6 | + | ||
| 7 | + | type Props = WithElementRef< | |
| 8 | + | Omit<HTMLInputAttributes, "type"> & | |
| 9 | + | ({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined }) | |
| 10 | + | >; | |
| 11 | + | ||
| 12 | + | let { | |
| 13 | + | ref = $bindable(null), | |
| 14 | + | value = $bindable(), | |
| 15 | + | type, | |
| 16 | + | files = $bindable(), | |
| 17 | + | class: className, | |
| 18 | + | "data-slot": dataSlot = "input", | |
| 19 | + | ...restProps | |
| 20 | + | }: Props = $props(); | |
| 21 | + | </script> | |
| 22 | + | ||
| 23 | + | {#if type === "file"} | |
| 24 | + | <input | |
| 25 | + | bind:this={ref} | |
| 26 | + | data-slot={dataSlot} | |
| 27 | + | class={cn( | |
| 28 | + | "selection:bg-primary dark:bg-input/30 selection:text-primary-foreground border-border ring-offset-background placeholder:text-muted-foreground flex h-9 w-full min-w-0 border bg-transparent px-3 pt-1.5 text-sm font-medium shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50", | |
| 29 | + | "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", | |
| 30 | + | "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", | |
| 31 | + | className | |
| 32 | + | )} | |
| 33 | + | type="file" | |
| 34 | + | bind:files | |
| 35 | + | bind:value | |
| 36 | + | {...restProps} | |
| 37 | + | /> | |
| 38 | + | {:else} | |
| 39 | + | <input | |
| 40 | + | bind:this={ref} | |
| 41 | + | data-slot={dataSlot} | |
| 42 | + | class={cn( | |
| 43 | + | "border-border bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground flex h-9 w-full min-w-0 border px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", | |
| 44 | + | "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", | |
| 45 | + | "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", | |
| 46 | + | className | |
| 47 | + | )} | |
| 48 | + | {type} | |
| 49 | + | bind:value | |
| 50 | + | {...restProps} | |
| 51 | + | /> | |
| 52 | + | {/if} | |
| @@ -0,0 +1,52 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements"; | |||
| 3 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |||
| 4 | + | ||||
| 5 | + | type InputType = Exclude<HTMLInputTypeAttribute, "file">; | |||
| 6 | + | ||||
| 7 | + | type Props = WithElementRef< | |||
| 8 | + | Omit<HTMLInputAttributes, "type"> & | |||
| 9 | + | ({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined }) | |||
| 10 | + | >; | |||
| 11 | + | ||||
| 12 | + | let { | |||
| 13 | + | ref = $bindable(null), | |||
| 14 | + | value = $bindable(), | |||
| 15 | + | type, | |||
| 16 | + | files = $bindable(), | |||
| 17 | + | class: className, | |||
| 18 | + | "data-slot": dataSlot = "input", | |||
| 19 | + | ...restProps | |||
| 20 | + | }: Props = $props(); | |||
| 21 | + | </script> | |||
| 22 | + | ||||
| 23 | + | {#if type === "file"} | |||
| 24 | + | <input | |||
| 25 | + | bind:this={ref} | |||
| 26 | + | data-slot={dataSlot} | |||
| 27 | + | class={cn( | |||
| 28 | + | "selection:bg-primary dark:bg-input/30 selection:text-primary-foreground border-border ring-offset-background placeholder:text-muted-foreground flex h-9 w-full min-w-0 border bg-transparent px-3 pt-1.5 text-sm font-medium shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50", | |||
| 29 | + | "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", | |||
| 30 | + | "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", | |||
| 31 | + | className | |||
| 32 | + | )} | |||
| 33 | + | type="file" | |||
| 34 | + | bind:files | |||
| 35 | + | bind:value | |||
| 36 | + | {...restProps} | |||
| 37 | + | /> | |||
| 38 | + | {:else} | |||
| 39 | + | <input | |||
| 40 | + | bind:this={ref} | |||
| 41 | + | data-slot={dataSlot} | |||
| 42 | + | class={cn( | |||
| 43 | + | "border-border bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground flex h-9 w-full min-w-0 border px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", | |||
| 44 | + | "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", | |||
| 45 | + | "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", | |||
| 46 | + | className | |||
| 47 | + | )} | |||
| 48 | + | {type} | |||
| 49 | + | bind:value | |||
| 50 | + | {...restProps} | |||
| 51 | + | /> | |||
| 52 | + | {/if} | |||
A webui/src/lib/components/ui/label/index.ts +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | import Root from "./label.svelte"; | |
| 2 | + | ||
| 3 | + | export { | |
| 4 | + | Root, | |
| 5 | + | // | |
| 6 | + | Root as Label, | |
| 7 | + | }; | |
| @@ -0,0 +1,7 @@ | |||||
| 1 | + | import Root from "./label.svelte"; | |||
| 2 | + | ||||
| 3 | + | export { | |||
| 4 | + | Root, | |||
| 5 | + | // | |||
| 6 | + | Root as Label, | |||
| 7 | + | }; | |||
A webui/src/lib/components/ui/label/label.svelte +20 -0
| @@ -0,0 +1,20 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { Label as LabelPrimitive } from "bits-ui"; | |
| 3 | + | import { cn } from "$lib/utils.js"; | |
| 4 | + | ||
| 5 | + | let { | |
| 6 | + | ref = $bindable(null), | |
| 7 | + | class: className, | |
| 8 | + | ...restProps | |
| 9 | + | }: LabelPrimitive.RootProps = $props(); | |
| 10 | + | </script> | |
| 11 | + | ||
| 12 | + | <LabelPrimitive.Root | |
| 13 | + | bind:ref | |
| 14 | + | data-slot="label" | |
| 15 | + | class={cn( | |
| 16 | + | "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", | |
| 17 | + | className | |
| 18 | + | )} | |
| 19 | + | {...restProps} | |
| 20 | + | /> | |
| @@ -0,0 +1,20 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { Label as LabelPrimitive } from "bits-ui"; | |||
| 3 | + | import { cn } from "$lib/utils.js"; | |||
| 4 | + | ||||
| 5 | + | let { | |||
| 6 | + | ref = $bindable(null), | |||
| 7 | + | class: className, | |||
| 8 | + | ...restProps | |||
| 9 | + | }: LabelPrimitive.RootProps = $props(); | |||
| 10 | + | </script> | |||
| 11 | + | ||||
| 12 | + | <LabelPrimitive.Root | |||
| 13 | + | bind:ref | |||
| 14 | + | data-slot="label" | |||
| 15 | + | class={cn( | |||
| 16 | + | "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", | |||
| 17 | + | className | |||
| 18 | + | )} | |||
| 19 | + | {...restProps} | |||
| 20 | + | /> | |||
A webui/src/lib/components/ui/skeleton/index.ts +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | import Root from "./skeleton.svelte"; | |
| 2 | + | ||
| 3 | + | export { | |
| 4 | + | Root, | |
| 5 | + | // | |
| 6 | + | Root as Skeleton, | |
| 7 | + | }; | |
| @@ -0,0 +1,7 @@ | |||||
| 1 | + | import Root from "./skeleton.svelte"; | |||
| 2 | + | ||||
| 3 | + | export { | |||
| 4 | + | Root, | |||
| 5 | + | // | |||
| 6 | + | Root as Skeleton, | |||
| 7 | + | }; | |||
A webui/src/lib/components/ui/skeleton/skeleton.svelte +17 -0
| @@ -0,0 +1,17 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils.js"; | |
| 3 | + | import type { HTMLAttributes } from "svelte/elements"; | |
| 4 | + | ||
| 5 | + | let { | |
| 6 | + | ref = $bindable(null), | |
| 7 | + | class: className, | |
| 8 | + | ...restProps | |
| 9 | + | }: WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> = $props(); | |
| 10 | + | </script> | |
| 11 | + | ||
| 12 | + | <div | |
| 13 | + | bind:this={ref} | |
| 14 | + | data-slot="skeleton" | |
| 15 | + | class={cn("bg-accent animate-pulse", className)} | |
| 16 | + | {...restProps} | |
| 17 | + | ></div> | |
| @@ -0,0 +1,17 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils.js"; | |||
| 3 | + | import type { HTMLAttributes } from "svelte/elements"; | |||
| 4 | + | ||||
| 5 | + | let { | |||
| 6 | + | ref = $bindable(null), | |||
| 7 | + | class: className, | |||
| 8 | + | ...restProps | |||
| 9 | + | }: WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> = $props(); | |||
| 10 | + | </script> | |||
| 11 | + | ||||
| 12 | + | <div | |||
| 13 | + | bind:this={ref} | |||
| 14 | + | data-slot="skeleton" | |||
| 15 | + | class={cn("bg-accent animate-pulse", className)} | |||
| 16 | + | {...restProps} | |||
| 17 | + | ></div> | |||
A webui/src/lib/components/ui/spinner/index.ts +1 -0
| @@ -0,0 +1,1 @@ | |||
| 1 | + | export { default as Spinner } from "./spinner.svelte"; | |
| @@ -0,0 +1,1 @@ | |||||
| 1 | + | export { default as Spinner } from "./spinner.svelte"; | |||
A webui/src/lib/components/ui/spinner/spinner.svelte +14 -0
| @@ -0,0 +1,14 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { cn } from "$lib/utils.js"; | |
| 3 | + | import Loader2Icon from "@lucide/svelte/icons/loader-2"; | |
| 4 | + | import type { ComponentProps } from "svelte"; | |
| 5 | + | ||
| 6 | + | let { class: className, ...restProps }: ComponentProps<typeof Loader2Icon> = $props(); | |
| 7 | + | </script> | |
| 8 | + | ||
| 9 | + | <Loader2Icon | |
| 10 | + | role="status" | |
| 11 | + | aria-label="Loading" | |
| 12 | + | class={cn("size-4 animate-spin", className)} | |
| 13 | + | {...restProps} | |
| 14 | + | /> | |
| @@ -0,0 +1,14 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { cn } from "$lib/utils.js"; | |||
| 3 | + | import Loader2Icon from "@lucide/svelte/icons/loader-2"; | |||
| 4 | + | import type { ComponentProps } from "svelte"; | |||
| 5 | + | ||||
| 6 | + | let { class: className, ...restProps }: ComponentProps<typeof Loader2Icon> = $props(); | |||
| 7 | + | </script> | |||
| 8 | + | ||||
| 9 | + | <Loader2Icon | |||
| 10 | + | role="status" | |||
| 11 | + | aria-label="Loading" | |||
| 12 | + | class={cn("size-4 animate-spin", className)} | |||
| 13 | + | {...restProps} | |||
| 14 | + | /> | |||
A webui/src/lib/components/ui/table/index.ts +28 -0
| @@ -0,0 +1,28 @@ | |||
| 1 | + | import Root from "./table.svelte"; | |
| 2 | + | import Body from "./table-body.svelte"; | |
| 3 | + | import Caption from "./table-caption.svelte"; | |
| 4 | + | import Cell from "./table-cell.svelte"; | |
| 5 | + | import Footer from "./table-footer.svelte"; | |
| 6 | + | import Head from "./table-head.svelte"; | |
| 7 | + | import Header from "./table-header.svelte"; | |
| 8 | + | import Row from "./table-row.svelte"; | |
| 9 | + | ||
| 10 | + | export { | |
| 11 | + | Root, | |
| 12 | + | Body, | |
| 13 | + | Caption, | |
| 14 | + | Cell, | |
| 15 | + | Footer, | |
| 16 | + | Head, | |
| 17 | + | Header, | |
| 18 | + | Row, | |
| 19 | + | // | |
| 20 | + | Root as Table, | |
| 21 | + | Body as TableBody, | |
| 22 | + | Caption as TableCaption, | |
| 23 | + | Cell as TableCell, | |
| 24 | + | Footer as TableFooter, | |
| 25 | + | Head as TableHead, | |
| 26 | + | Header as TableHeader, | |
| 27 | + | Row as TableRow, | |
| 28 | + | }; | |
| @@ -0,0 +1,28 @@ | |||||
| 1 | + | import Root from "./table.svelte"; | |||
| 2 | + | import Body from "./table-body.svelte"; | |||
| 3 | + | import Caption from "./table-caption.svelte"; | |||
| 4 | + | import Cell from "./table-cell.svelte"; | |||
| 5 | + | import Footer from "./table-footer.svelte"; | |||
| 6 | + | import Head from "./table-head.svelte"; | |||
| 7 | + | import Header from "./table-header.svelte"; | |||
| 8 | + | import Row from "./table-row.svelte"; | |||
| 9 | + | ||||
| 10 | + | export { | |||
| 11 | + | Root, | |||
| 12 | + | Body, | |||
| 13 | + | Caption, | |||
| 14 | + | Cell, | |||
| 15 | + | Footer, | |||
| 16 | + | Head, | |||
| 17 | + | Header, | |||
| 18 | + | Row, | |||
| 19 | + | // | |||
| 20 | + | Root as Table, | |||
| 21 | + | Body as TableBody, | |||
| 22 | + | Caption as TableCaption, | |||
| 23 | + | Cell as TableCell, | |||
| 24 | + | Footer as TableFooter, | |||
| 25 | + | Head as TableHead, | |||
| 26 | + | Header as TableHeader, | |||
| 27 | + | Row as TableRow, | |||
| 28 | + | }; | |||
A webui/src/lib/components/ui/table/table-body.svelte +20 -0
| @@ -0,0 +1,20 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { cn, type WithElementRef } from "$lib/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<HTMLTableSectionElement>> = $props(); | |
| 11 | + | </script> | |
| 12 | + | ||
| 13 | + | <tbody | |
| 14 | + | bind:this={ref} | |
| 15 | + | data-slot="table-body" | |
| 16 | + | class={cn("[&_tr:last-child]:border-0", className)} | |
| 17 | + | {...restProps} | |
| 18 | + | > | |
| 19 | + | {@render children?.()} | |
| 20 | + | </tbody> | |
| @@ -0,0 +1,20 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { cn, type WithElementRef } from "$lib/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<HTMLTableSectionElement>> = $props(); | |||
| 11 | + | </script> | |||
| 12 | + | ||||
| 13 | + | <tbody | |||
| 14 | + | bind:this={ref} | |||
| 15 | + | data-slot="table-body" | |||
| 16 | + | class={cn("[&_tr:last-child]:border-0", className)} | |||
| 17 | + | {...restProps} | |||
| 18 | + | > | |||
| 19 | + | {@render children?.()} | |||
| 20 | + | </tbody> | |||
A webui/src/lib/components/ui/table/table-caption.svelte +20 -0
| @@ -0,0 +1,20 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { cn, type WithElementRef } from "$lib/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 | + | <caption | |
| 14 | + | bind:this={ref} | |
| 15 | + | data-slot="table-caption" | |
| 16 | + | class={cn("text-muted-foreground mt-4 text-sm", className)} | |
| 17 | + | {...restProps} | |
| 18 | + | > | |
| 19 | + | {@render children?.()} | |
| 20 | + | </caption> | |
| @@ -0,0 +1,20 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { cn, type WithElementRef } from "$lib/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 | + | <caption | |||
| 14 | + | bind:this={ref} | |||
| 15 | + | data-slot="table-caption" | |||
| 16 | + | class={cn("text-muted-foreground mt-4 text-sm", className)} | |||
| 17 | + | {...restProps} | |||
| 18 | + | > | |||
| 19 | + | {@render children?.()} | |||
| 20 | + | </caption> | |||
A webui/src/lib/components/ui/table/table-cell.svelte +23 -0
| @@ -0,0 +1,23 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |
| 3 | + | import type { HTMLTdAttributes } from "svelte/elements"; | |
| 4 | + | ||
| 5 | + | let { | |
| 6 | + | ref = $bindable(null), | |
| 7 | + | class: className, | |
| 8 | + | children, | |
| 9 | + | ...restProps | |
| 10 | + | }: WithElementRef<HTMLTdAttributes> = $props(); | |
| 11 | + | </script> | |
| 12 | + | ||
| 13 | + | <td | |
| 14 | + | bind:this={ref} | |
| 15 | + | data-slot="table-cell" | |
| 16 | + | class={cn( | |
| 17 | + | "bg-clip-padding p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pe-0", | |
| 18 | + | className | |
| 19 | + | )} | |
| 20 | + | {...restProps} | |
| 21 | + | > | |
| 22 | + | {@render children?.()} | |
| 23 | + | </td> | |
| @@ -0,0 +1,23 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |||
| 3 | + | import type { HTMLTdAttributes } from "svelte/elements"; | |||
| 4 | + | ||||
| 5 | + | let { | |||
| 6 | + | ref = $bindable(null), | |||
| 7 | + | class: className, | |||
| 8 | + | children, | |||
| 9 | + | ...restProps | |||
| 10 | + | }: WithElementRef<HTMLTdAttributes> = $props(); | |||
| 11 | + | </script> | |||
| 12 | + | ||||
| 13 | + | <td | |||
| 14 | + | bind:this={ref} | |||
| 15 | + | data-slot="table-cell" | |||
| 16 | + | class={cn( | |||
| 17 | + | "bg-clip-padding p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pe-0", | |||
| 18 | + | className | |||
| 19 | + | )} | |||
| 20 | + | {...restProps} | |||
| 21 | + | > | |||
| 22 | + | {@render children?.()} | |||
| 23 | + | </td> | |||
A webui/src/lib/components/ui/table/table-footer.svelte +20 -0
| @@ -0,0 +1,20 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { cn, type WithElementRef } from "$lib/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<HTMLTableSectionElement>> = $props(); | |
| 11 | + | </script> | |
| 12 | + | ||
| 13 | + | <tfoot | |
| 14 | + | bind:this={ref} | |
| 15 | + | data-slot="table-footer" | |
| 16 | + | class={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)} | |
| 17 | + | {...restProps} | |
| 18 | + | > | |
| 19 | + | {@render children?.()} | |
| 20 | + | </tfoot> | |
| @@ -0,0 +1,20 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { cn, type WithElementRef } from "$lib/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<HTMLTableSectionElement>> = $props(); | |||
| 11 | + | </script> | |||
| 12 | + | ||||
| 13 | + | <tfoot | |||
| 14 | + | bind:this={ref} | |||
| 15 | + | data-slot="table-footer" | |||
| 16 | + | class={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)} | |||
| 17 | + | {...restProps} | |||
| 18 | + | > | |||
| 19 | + | {@render children?.()} | |||
| 20 | + | </tfoot> | |||
A webui/src/lib/components/ui/table/table-head.svelte +23 -0
| @@ -0,0 +1,23 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |
| 3 | + | import type { HTMLThAttributes } from "svelte/elements"; | |
| 4 | + | ||
| 5 | + | let { | |
| 6 | + | ref = $bindable(null), | |
| 7 | + | class: className, | |
| 8 | + | children, | |
| 9 | + | ...restProps | |
| 10 | + | }: WithElementRef<HTMLThAttributes> = $props(); | |
| 11 | + | </script> | |
| 12 | + | ||
| 13 | + | <th | |
| 14 | + | bind:this={ref} | |
| 15 | + | data-slot="table-head" | |
| 16 | + | class={cn( | |
| 17 | + | "text-foreground h-10 bg-clip-padding px-2 text-start align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pe-0", | |
| 18 | + | className | |
| 19 | + | )} | |
| 20 | + | {...restProps} | |
| 21 | + | > | |
| 22 | + | {@render children?.()} | |
| 23 | + | </th> | |
| @@ -0,0 +1,23 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |||
| 3 | + | import type { HTMLThAttributes } from "svelte/elements"; | |||
| 4 | + | ||||
| 5 | + | let { | |||
| 6 | + | ref = $bindable(null), | |||
| 7 | + | class: className, | |||
| 8 | + | children, | |||
| 9 | + | ...restProps | |||
| 10 | + | }: WithElementRef<HTMLThAttributes> = $props(); | |||
| 11 | + | </script> | |||
| 12 | + | ||||
| 13 | + | <th | |||
| 14 | + | bind:this={ref} | |||
| 15 | + | data-slot="table-head" | |||
| 16 | + | class={cn( | |||
| 17 | + | "text-foreground h-10 bg-clip-padding px-2 text-start align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pe-0", | |||
| 18 | + | className | |||
| 19 | + | )} | |||
| 20 | + | {...restProps} | |||
| 21 | + | > | |||
| 22 | + | {@render children?.()} | |||
| 23 | + | </th> | |||
A webui/src/lib/components/ui/table/table-header.svelte +20 -0
| @@ -0,0 +1,20 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { cn, type WithElementRef } from "$lib/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<HTMLTableSectionElement>> = $props(); | |
| 11 | + | </script> | |
| 12 | + | ||
| 13 | + | <thead | |
| 14 | + | bind:this={ref} | |
| 15 | + | data-slot="table-header" | |
| 16 | + | class={cn("[&_tr]:border-b", className)} | |
| 17 | + | {...restProps} | |
| 18 | + | > | |
| 19 | + | {@render children?.()} | |
| 20 | + | </thead> | |
| @@ -0,0 +1,20 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { cn, type WithElementRef } from "$lib/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<HTMLTableSectionElement>> = $props(); | |||
| 11 | + | </script> | |||
| 12 | + | ||||
| 13 | + | <thead | |||
| 14 | + | bind:this={ref} | |||
| 15 | + | data-slot="table-header" | |||
| 16 | + | class={cn("[&_tr]:border-b", className)} | |||
| 17 | + | {...restProps} | |||
| 18 | + | > | |||
| 19 | + | {@render children?.()} | |||
| 20 | + | </thead> | |||
A webui/src/lib/components/ui/table/table-row.svelte +23 -0
| @@ -0,0 +1,23 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { cn, type WithElementRef } from "$lib/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<HTMLTableRowElement>> = $props(); | |
| 11 | + | </script> | |
| 12 | + | ||
| 13 | + | <tr | |
| 14 | + | bind:this={ref} | |
| 15 | + | data-slot="table-row" | |
| 16 | + | class={cn( | |
| 17 | + | "hover:[&,&>svelte-css-wrapper]:[&>th,td]:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", | |
| 18 | + | className | |
| 19 | + | )} | |
| 20 | + | {...restProps} | |
| 21 | + | > | |
| 22 | + | {@render children?.()} | |
| 23 | + | </tr> | |
| @@ -0,0 +1,23 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { cn, type WithElementRef } from "$lib/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<HTMLTableRowElement>> = $props(); | |||
| 11 | + | </script> | |||
| 12 | + | ||||
| 13 | + | <tr | |||
| 14 | + | bind:this={ref} | |||
| 15 | + | data-slot="table-row" | |||
| 16 | + | class={cn( | |||
| 17 | + | "hover:[&,&>svelte-css-wrapper]:[&>th,td]:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", | |||
| 18 | + | className | |||
| 19 | + | )} | |||
| 20 | + | {...restProps} | |||
| 21 | + | > | |||
| 22 | + | {@render children?.()} | |||
| 23 | + | </tr> | |||
A webui/src/lib/components/ui/table/table.svelte +22 -0
| @@ -0,0 +1,22 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import type { HTMLTableAttributes } from "svelte/elements"; | |
| 3 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |
| 4 | + | ||
| 5 | + | let { | |
| 6 | + | ref = $bindable(null), | |
| 7 | + | class: className, | |
| 8 | + | children, | |
| 9 | + | ...restProps | |
| 10 | + | }: WithElementRef<HTMLTableAttributes> = $props(); | |
| 11 | + | </script> | |
| 12 | + | ||
| 13 | + | <div data-slot="table-container" class="relative w-full overflow-x-auto"> | |
| 14 | + | <table | |
| 15 | + | bind:this={ref} | |
| 16 | + | data-slot="table" | |
| 17 | + | class={cn("w-full caption-bottom text-sm", className)} | |
| 18 | + | {...restProps} | |
| 19 | + | > | |
| 20 | + | {@render children?.()} | |
| 21 | + | </table> | |
| 22 | + | </div> | |
| @@ -0,0 +1,22 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import type { HTMLTableAttributes } from "svelte/elements"; | |||
| 3 | + | import { cn, type WithElementRef } from "$lib/utils.js"; | |||
| 4 | + | ||||
| 5 | + | let { | |||
| 6 | + | ref = $bindable(null), | |||
| 7 | + | class: className, | |||
| 8 | + | children, | |||
| 9 | + | ...restProps | |||
| 10 | + | }: WithElementRef<HTMLTableAttributes> = $props(); | |||
| 11 | + | </script> | |||
| 12 | + | ||||
| 13 | + | <div data-slot="table-container" class="relative w-full overflow-x-auto"> | |||
| 14 | + | <table | |||
| 15 | + | bind:this={ref} | |||
| 16 | + | data-slot="table" | |||
| 17 | + | class={cn("w-full caption-bottom text-sm", className)} | |||
| 18 | + | {...restProps} | |||
| 19 | + | > | |||
| 20 | + | {@render children?.()} | |||
| 21 | + | </table> | |||
| 22 | + | </div> | |||
A webui/src/lib/components/ui/tabs/index.ts +16 -0
| @@ -0,0 +1,16 @@ | |||
| 1 | + | import Root from "./tabs.svelte"; | |
| 2 | + | import Content from "./tabs-content.svelte"; | |
| 3 | + | import List from "./tabs-list.svelte"; | |
| 4 | + | import Trigger from "./tabs-trigger.svelte"; | |
| 5 | + | ||
| 6 | + | export { | |
| 7 | + | Root, | |
| 8 | + | Content, | |
| 9 | + | List, | |
| 10 | + | Trigger, | |
| 11 | + | // | |
| 12 | + | Root as Tabs, | |
| 13 | + | Content as TabsContent, | |
| 14 | + | List as TabsList, | |
| 15 | + | Trigger as TabsTrigger, | |
| 16 | + | }; | |
| @@ -0,0 +1,16 @@ | |||||
| 1 | + | import Root from "./tabs.svelte"; | |||
| 2 | + | import Content from "./tabs-content.svelte"; | |||
| 3 | + | import List from "./tabs-list.svelte"; | |||
| 4 | + | import Trigger from "./tabs-trigger.svelte"; | |||
| 5 | + | ||||
| 6 | + | export { | |||
| 7 | + | Root, | |||
| 8 | + | Content, | |||
| 9 | + | List, | |||
| 10 | + | Trigger, | |||
| 11 | + | // | |||
| 12 | + | Root as Tabs, | |||
| 13 | + | Content as TabsContent, | |||
| 14 | + | List as TabsList, | |||
| 15 | + | Trigger as TabsTrigger, | |||
| 16 | + | }; | |||
A webui/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
| @@ -0,0 +1,17 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { Tabs as TabsPrimitive } from "bits-ui"; | |
| 3 | + | import { cn } from "$lib/utils.js"; | |
| 4 | + | ||
| 5 | + | let { | |
| 6 | + | ref = $bindable(null), | |
| 7 | + | class: className, | |
| 8 | + | ...restProps | |
| 9 | + | }: TabsPrimitive.ContentProps = $props(); | |
| 10 | + | </script> | |
| 11 | + | ||
| 12 | + | <TabsPrimitive.Content | |
| 13 | + | bind:ref | |
| 14 | + | data-slot="tabs-content" | |
| 15 | + | class={cn("flex-1 outline-none", className)} | |
| 16 | + | {...restProps} | |
| 17 | + | /> | |
| @@ -0,0 +1,17 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { Tabs as TabsPrimitive } from "bits-ui"; | |||
| 3 | + | import { cn } from "$lib/utils.js"; | |||
| 4 | + | ||||
| 5 | + | let { | |||
| 6 | + | ref = $bindable(null), | |||
| 7 | + | class: className, | |||
| 8 | + | ...restProps | |||
| 9 | + | }: TabsPrimitive.ContentProps = $props(); | |||
| 10 | + | </script> | |||
| 11 | + | ||||
| 12 | + | <TabsPrimitive.Content | |||
| 13 | + | bind:ref | |||
| 14 | + | data-slot="tabs-content" | |||
| 15 | + | class={cn("flex-1 outline-none", className)} | |||
| 16 | + | {...restProps} | |||
| 17 | + | /> | |||
A webui/src/lib/components/ui/tabs/tabs-list.svelte +20 -0
| @@ -0,0 +1,20 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { Tabs as TabsPrimitive } from "bits-ui"; | |
| 3 | + | import { cn } from "$lib/utils.js"; | |
| 4 | + | ||
| 5 | + | let { | |
| 6 | + | ref = $bindable(null), | |
| 7 | + | class: className, | |
| 8 | + | ...restProps | |
| 9 | + | }: TabsPrimitive.ListProps = $props(); | |
| 10 | + | </script> | |
| 11 | + | ||
| 12 | + | <TabsPrimitive.List | |
| 13 | + | bind:ref | |
| 14 | + | data-slot="tabs-list" | |
| 15 | + | class={cn( | |
| 16 | + | "bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center border border-border p-[3px]", | |
| 17 | + | className | |
| 18 | + | )} | |
| 19 | + | {...restProps} | |
| 20 | + | /> | |
| @@ -0,0 +1,20 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { Tabs as TabsPrimitive } from "bits-ui"; | |||
| 3 | + | import { cn } from "$lib/utils.js"; | |||
| 4 | + | ||||
| 5 | + | let { | |||
| 6 | + | ref = $bindable(null), | |||
| 7 | + | class: className, | |||
| 8 | + | ...restProps | |||
| 9 | + | }: TabsPrimitive.ListProps = $props(); | |||
| 10 | + | </script> | |||
| 11 | + | ||||
| 12 | + | <TabsPrimitive.List | |||
| 13 | + | bind:ref | |||
| 14 | + | data-slot="tabs-list" | |||
| 15 | + | class={cn( | |||
| 16 | + | "bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center border border-border p-[3px]", | |||
| 17 | + | className | |||
| 18 | + | )} | |||
| 19 | + | {...restProps} | |||
| 20 | + | /> | |||
A webui/src/lib/components/ui/tabs/tabs-trigger.svelte +20 -0
| @@ -0,0 +1,20 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { Tabs as TabsPrimitive } from "bits-ui"; | |
| 3 | + | import { cn } from "$lib/utils.js"; | |
| 4 | + | ||
| 5 | + | let { | |
| 6 | + | ref = $bindable(null), | |
| 7 | + | class: className, | |
| 8 | + | ...restProps | |
| 9 | + | }: TabsPrimitive.TriggerProps = $props(); | |
| 10 | + | </script> | |
| 11 | + | ||
| 12 | + | <TabsPrimitive.Trigger | |
| 13 | + | bind:ref | |
| 14 | + | data-slot="tabs-trigger" | |
| 15 | + | class={cn( | |
| 16 | + | "data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", | |
| 17 | + | className | |
| 18 | + | )} | |
| 19 | + | {...restProps} | |
| 20 | + | /> | |
| @@ -0,0 +1,20 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { Tabs as TabsPrimitive } from "bits-ui"; | |||
| 3 | + | import { cn } from "$lib/utils.js"; | |||
| 4 | + | ||||
| 5 | + | let { | |||
| 6 | + | ref = $bindable(null), | |||
| 7 | + | class: className, | |||
| 8 | + | ...restProps | |||
| 9 | + | }: TabsPrimitive.TriggerProps = $props(); | |||
| 10 | + | </script> | |||
| 11 | + | ||||
| 12 | + | <TabsPrimitive.Trigger | |||
| 13 | + | bind:ref | |||
| 14 | + | data-slot="tabs-trigger" | |||
| 15 | + | class={cn( | |||
| 16 | + | "data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", | |||
| 17 | + | className | |||
| 18 | + | )} | |||
| 19 | + | {...restProps} | |||
| 20 | + | /> | |||
A webui/src/lib/components/ui/tabs/tabs.svelte +19 -0
| @@ -0,0 +1,19 @@ | |||
| 1 | + | <script lang="ts"> | |
| 2 | + | import { Tabs as TabsPrimitive } from "bits-ui"; | |
| 3 | + | import { cn } from "$lib/utils.js"; | |
| 4 | + | ||
| 5 | + | let { | |
| 6 | + | ref = $bindable(null), | |
| 7 | + | value = $bindable(""), | |
| 8 | + | class: className, | |
| 9 | + | ...restProps | |
| 10 | + | }: TabsPrimitive.RootProps = $props(); | |
| 11 | + | </script> | |
| 12 | + | ||
| 13 | + | <TabsPrimitive.Root | |
| 14 | + | bind:ref | |
| 15 | + | bind:value | |
| 16 | + | data-slot="tabs" | |
| 17 | + | class={cn("flex flex-col gap-2", className)} | |
| 18 | + | {...restProps} | |
| 19 | + | /> | |
| @@ -0,0 +1,19 @@ | |||||
| 1 | + | <script lang="ts"> | |||
| 2 | + | import { Tabs as TabsPrimitive } from "bits-ui"; | |||
| 3 | + | import { cn } from "$lib/utils.js"; | |||
| 4 | + | ||||
| 5 | + | let { | |||
| 6 | + | ref = $bindable(null), | |||
| 7 | + | value = $bindable(""), | |||
| 8 | + | class: className, | |||
| 9 | + | ...restProps | |||
| 10 | + | }: TabsPrimitive.RootProps = $props(); | |||
| 11 | + | </script> | |||
| 12 | + | ||||
| 13 | + | <TabsPrimitive.Root | |||
| 14 | + | bind:ref | |||
| 15 | + | bind:value | |||
| 16 | + | data-slot="tabs" | |||
| 17 | + | class={cn("flex flex-col gap-2", className)} | |||
| 18 | + | {...restProps} | |||
| 19 | + | /> | |||
A webui/src/lib/utils.ts +13 -0
| @@ -0,0 +1,13 @@ | |||
| 1 | + | import { clsx, type ClassValue } from "clsx"; | |
| 2 | + | import { twMerge } from "tailwind-merge"; | |
| 3 | + | ||
| 4 | + | export function cn(...inputs: ClassValue[]) { | |
| 5 | + | return twMerge(clsx(inputs)); | |
| 6 | + | } | |
| 7 | + | ||
| 8 | + | // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| 9 | + | export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T; | |
| 10 | + | // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| 11 | + | export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T; | |
| 12 | + | export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>; | |
| 13 | + | export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null }; | |
| @@ -0,0 +1,13 @@ | |||||
| 1 | + | import { clsx, type ClassValue } from "clsx"; | |||
| 2 | + | import { twMerge } from "tailwind-merge"; | |||
| 3 | + | ||||
| 4 | + | export function cn(...inputs: ClassValue[]) { | |||
| 5 | + | return twMerge(clsx(inputs)); | |||
| 6 | + | } | |||
| 7 | + | ||||
| 8 | + | // eslint-disable-next-line @typescript-eslint/no-explicit-any | |||
| 9 | + | export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T; | |||
| 10 | + | // eslint-disable-next-line @typescript-eslint/no-explicit-any | |||
| 11 | + | export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T; | |||
| 12 | + | export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>; | |||
| 13 | + | export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null }; | |||