diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-18 08:26:30 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-18 08:26:30 +0800 |
| commit | 06a7b1668b44f111eca5d08bc8c2c9d41d8e617e (patch) | |
| tree | 0c7220a22b866d3e298f756b42f565f4f674152c /frontend/src/lib/components/ui/select | |
| parent | 95215e84e045602db2370586f6089a7f9f91b33b (diff) | |
| download | strategy-lab-06a7b1668b44f111eca5d08bc8c2c9d41d8e617e.tar.gz | |
chore(frontend): shadcn-svelte / Tailwind / layerchart 工具链初始化
[变更性质]
本轮为维护性依赖与配置初始化,不改变业务页面(页面迁移见后续提交)。
[维护内容]
按 Leader 返工指令,前端 UI 组件库全量迁移 shadcn-svelte,图表渲染
从 echarts 换为 layerchart,需要一次性引入配套依赖与构建配置。
[实现方案]
package.json/pnpm-lock.yaml:tailwindcss@4、@tailwindcss/vite、
tw-animate-css、shadcn-svelte、bits-ui、tailwind-variants、clsx、
tailwind-merge、@lucide/svelte、layerchart、@fontsource-variable/inter
与 @types/node;echarts 保留至页面迁移完成后移除。pnpm-workspace.yaml
声明 allowBuilds(esbuild、@tailwindcss/oxide)。components.json 采用
mira preset,别名 $lib。tsconfig 新增 $lib paths;vite.config.ts 加入
tailwindcss 插件、$lib alias(vitest 需要)与 test 环境配置。
src/lib/components/ 为 shadcn 生成的 UI 组件源码(button/input/label/
card/badge/alert/select/table/tabs/separator/chart),src/lib/utils.ts
为 cn 工具。app.css 引入 tailwind 与 shadcn CSS 变量并调整为 teal
主题(primary #0f766e、radius 0.375rem、双主题 chart-1..5),保留
legacy 选择器(banner/beat/table.data/metric 等),mobileCss.test.ts
依赖其语义断言。
[影响范围]
构建链与主题变量;后续页面提交逐步消费这些组件,npm run check/test/
build 已全绿验证。
Diffstat (limited to 'frontend/src/lib/components/ui/select')
13 files changed, 297 insertions, 0 deletions
diff --git a/frontend/src/lib/components/ui/select/index.ts b/frontend/src/lib/components/ui/select/index.ts new file mode 100644 index 0000000..4960d45 --- /dev/null +++ b/frontend/src/lib/components/ui/select/index.ts @@ -0,0 +1,40 @@ +import Content from "./select-content.svelte"; +import GroupHeading from "./select-group-heading.svelte"; +import Group from "./select-group.svelte"; +import Item from "./select-item.svelte"; +import Label from "./select-label.svelte"; +import Portal from "./select-portal.svelte"; +import ScrollDownButton from "./select-scroll-down-button.svelte"; +import ScrollUpButton from "./select-scroll-up-button.svelte"; +import Separator from "./select-separator.svelte"; +import Trigger from "./select-trigger.svelte"; +import Value from "./select-value.svelte"; +import Root from "./select.svelte"; + +export { + Root, + Group, + Label, + Item, + Content, + Trigger, + Separator, + ScrollDownButton, + ScrollUpButton, + GroupHeading, + Portal, + Value, + // + Root as Select, + Group as SelectGroup, + Label as SelectLabel, + Item as SelectItem, + Content as SelectContent, + Trigger as SelectTrigger, + Separator as SelectSeparator, + ScrollDownButton as SelectScrollDownButton, + ScrollUpButton as SelectScrollUpButton, + GroupHeading as SelectGroupHeading, + Portal as SelectPortal, + Value as SelectValue, +}; diff --git a/frontend/src/lib/components/ui/select/select-content.svelte b/frontend/src/lib/components/ui/select/select-content.svelte new file mode 100644 index 0000000..3ec8408 --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-content.svelte @@ -0,0 +1,45 @@ +<script lang="ts"> + import { Select as SelectPrimitive } from "bits-ui"; + import { cn, type WithoutChild } from "$lib/utils.js"; + import type { WithoutChildrenOrChild } from "$lib/utils.js"; + import SelectPortal from "./select-portal.svelte"; + import SelectScrollDownButton from "./select-scroll-down-button.svelte"; + import SelectScrollUpButton from "./select-scroll-up-button.svelte"; + import type { ComponentProps } from "svelte"; + + let { + ref = $bindable(null), + class: className, + sideOffset = 4, + portalProps, + children, + preventScroll = true, + ...restProps + }: WithoutChild<SelectPrimitive.ContentProps> & { + portalProps?: WithoutChildrenOrChild<ComponentProps<typeof SelectPortal>>; + } = $props(); +</script> + +<SelectPortal {...portalProps}> + <SelectPrimitive.Content + bind:ref + {sideOffset} + {preventScroll} + data-slot="select-content" + class={cn( + "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-32 rounded-lg shadow-md ring-1 duration-100 relative z-50 max-h-(--bits-select-content-available-height) origin-(--bits-select-content-transform-origin) overflow-x-hidden overflow-y-auto", + className + )} + {...restProps} + > + <SelectScrollUpButton /> + <SelectPrimitive.Viewport + class={cn( + "h-(--bits-select-anchor-height) w-full min-w-(--bits-select-anchor-width) scroll-my-1" + )} + > + {@render children?.()} + </SelectPrimitive.Viewport> + <SelectScrollDownButton /> + </SelectPrimitive.Content> +</SelectPortal> diff --git a/frontend/src/lib/components/ui/select/select-group-heading.svelte b/frontend/src/lib/components/ui/select/select-group-heading.svelte new file mode 100644 index 0000000..1fab5f0 --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-group-heading.svelte @@ -0,0 +1,21 @@ +<script lang="ts"> + import { Select as SelectPrimitive } from "bits-ui"; + import { cn } from "$lib/utils.js"; + import type { ComponentProps } from "svelte"; + + let { + ref = $bindable(null), + class: className, + children, + ...restProps + }: ComponentProps<typeof SelectPrimitive.GroupHeading> = $props(); +</script> + +<SelectPrimitive.GroupHeading + bind:ref + data-slot="select-group-heading" + class={cn("text-muted-foreground px-2 py-1.5 text-xs", className)} + {...restProps} +> + {@render children?.()} +</SelectPrimitive.GroupHeading> diff --git a/frontend/src/lib/components/ui/select/select-group.svelte b/frontend/src/lib/components/ui/select/select-group.svelte new file mode 100644 index 0000000..f666cb2 --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-group.svelte @@ -0,0 +1,17 @@ +<script lang="ts"> + import { Select as SelectPrimitive } from "bits-ui"; + import { cn } from "$lib/utils.js"; + + let { + ref = $bindable(null), + class: className, + ...restProps + }: SelectPrimitive.GroupProps = $props(); +</script> + +<SelectPrimitive.Group + bind:ref + data-slot="select-group" + class={cn("scroll-my-1 p-1", className)} + {...restProps} +/> diff --git a/frontend/src/lib/components/ui/select/select-item.svelte b/frontend/src/lib/components/ui/select/select-item.svelte new file mode 100644 index 0000000..446dfdc --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-item.svelte @@ -0,0 +1,42 @@ +<script lang="ts"> + import { Select as SelectPrimitive } from "bits-ui"; + import CheckIcon from '@lucide/svelte/icons/check'; + import { cn, type WithoutChild } from "$lib/utils.js"; + + let { + ref = $bindable(null), + class: className, + value, + label, + children: childrenProp, + ...restProps + }: WithoutChild<SelectPrimitive.ItemProps> = $props(); +</script> + +<SelectPrimitive.Item + bind:ref + {value} + {label} + data-slot="select-item" + class={cn( + "focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground min-h-7 gap-2 rounded-md px-2 py-1 text-xs/relaxed [&_svg:not([class*='size-'])]:size-3.5 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", + "data-highlighted:bg-accent data-highlighted:text-accent-foreground", + className + )} + {...restProps} +> + {#snippet children({ selected, highlighted })} + <span class="pointer-events-none absolute right-2 flex items-center justify-center"> + {#if selected} + <CheckIcon class="pointer-events-none" /> + {/if} + </span> + <span class="flex flex-1 gap-2 shrink-0 whitespace-nowrap"> + {#if childrenProp} + {@render childrenProp({ selected, highlighted })} + {:else} + {label || value} + {/if} + </span> + {/snippet} +</SelectPrimitive.Item> diff --git a/frontend/src/lib/components/ui/select/select-label.svelte b/frontend/src/lib/components/ui/select/select-label.svelte new file mode 100644 index 0000000..4696025 --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-label.svelte @@ -0,0 +1,20 @@ +<script lang="ts"> + import { cn, type WithElementRef } from "$lib/utils.js"; + import type { HTMLAttributes } from "svelte/elements"; + + let { + ref = $bindable(null), + class: className, + children, + ...restProps + }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {} = $props(); +</script> + +<div + bind:this={ref} + data-slot="select-label" + class={cn("text-muted-foreground px-2 py-1.5 text-xs", className)} + {...restProps} +> + {@render children?.()} +</div> diff --git a/frontend/src/lib/components/ui/select/select-portal.svelte b/frontend/src/lib/components/ui/select/select-portal.svelte new file mode 100644 index 0000000..424bcdd --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-portal.svelte @@ -0,0 +1,7 @@ +<script lang="ts"> + import { Select as SelectPrimitive } from "bits-ui"; + + let { ...restProps }: SelectPrimitive.PortalProps = $props(); +</script> + +<SelectPrimitive.Portal {...restProps} /> diff --git a/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte b/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte new file mode 100644 index 0000000..7bc111e --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte @@ -0,0 +1,20 @@ +<script lang="ts"> + import { Select as SelectPrimitive } from "bits-ui"; + import ChevronDownIcon from '@lucide/svelte/icons/chevron-down'; + import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; + + let { + ref = $bindable(null), + class: className, + ...restProps + }: WithoutChildrenOrChild<SelectPrimitive.ScrollDownButtonProps> = $props(); +</script> + +<SelectPrimitive.ScrollDownButton + bind:ref + data-slot="select-scroll-down-button" + class={cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-3.5", className)} + {...restProps} +> + <ChevronDownIcon /> +</SelectPrimitive.ScrollDownButton> diff --git a/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte b/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte new file mode 100644 index 0000000..6c066ff --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte @@ -0,0 +1,20 @@ +<script lang="ts"> + import { Select as SelectPrimitive } from "bits-ui"; + import ChevronUpIcon from '@lucide/svelte/icons/chevron-up'; + import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; + + let { + ref = $bindable(null), + class: className, + ...restProps + }: WithoutChildrenOrChild<SelectPrimitive.ScrollUpButtonProps> = $props(); +</script> + +<SelectPrimitive.ScrollUpButton + bind:ref + data-slot="select-scroll-up-button" + class={cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-3.5", className)} + {...restProps} +> + <ChevronUpIcon /> +</SelectPrimitive.ScrollUpButton> diff --git a/frontend/src/lib/components/ui/select/select-separator.svelte b/frontend/src/lib/components/ui/select/select-separator.svelte new file mode 100644 index 0000000..aac5ca3 --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-separator.svelte @@ -0,0 +1,18 @@ +<script lang="ts"> + import { Separator } from "$lib/components/ui/separator/index.js"; + import { cn } from "$lib/utils.js"; + import type { Separator as SeparatorPrimitive } from "bits-ui"; + + let { + ref = $bindable(null), + class: className, + ...restProps + }: SeparatorPrimitive.RootProps = $props(); +</script> + +<Separator + bind:ref + data-slot="select-separator" + class={cn("bg-border/50 -mx-1 my-1 h-px pointer-events-none", className)} + {...restProps} +/> diff --git a/frontend/src/lib/components/ui/select/select-trigger.svelte b/frontend/src/lib/components/ui/select/select-trigger.svelte new file mode 100644 index 0000000..3acff3b --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-trigger.svelte @@ -0,0 +1,29 @@ +<script lang="ts"> + import { Select as SelectPrimitive } from "bits-ui"; + import ChevronDownIcon from '@lucide/svelte/icons/chevron-down'; + import { cn, type WithoutChild } from "$lib/utils.js"; + + let { + ref = $bindable(null), + class: className, + children, + size = "default", + ...restProps + }: WithoutChild<SelectPrimitive.TriggerProps> & { + size?: "sm" | "default"; + } = $props(); +</script> + +<SelectPrimitive.Trigger + bind:ref + data-slot="select-trigger" + data-size={size} + class={cn( + "border-input data-placeholder:text-muted-foreground bg-input/20 dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-md border px-2 py-1.5 text-xs/relaxed transition-colors focus-visible:ring-2 aria-invalid:ring-2 data-[size=default]:h-7 data-[size=sm]:h-6 *:data-[slot=select-value]:flex *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-3.5 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0", + className + )} + {...restProps} +> + {@render children?.()} + <ChevronDownIcon class="text-muted-foreground size-3.5 pointer-events-none" /> +</SelectPrimitive.Trigger> diff --git a/frontend/src/lib/components/ui/select/select-value.svelte b/frontend/src/lib/components/ui/select/select-value.svelte new file mode 100644 index 0000000..ff34b0e --- /dev/null +++ b/frontend/src/lib/components/ui/select/select-value.svelte @@ -0,0 +1,7 @@ +<script lang="ts"> + import { Select as SelectPrimitive } from "bits-ui"; + + let { ref = $bindable(null), ...restProps }: SelectPrimitive.ValueProps = $props(); +</script> + +<SelectPrimitive.Value bind:ref data-slot="select-value" {...restProps} /> diff --git a/frontend/src/lib/components/ui/select/select.svelte b/frontend/src/lib/components/ui/select/select.svelte new file mode 100644 index 0000000..05eb663 --- /dev/null +++ b/frontend/src/lib/components/ui/select/select.svelte @@ -0,0 +1,11 @@ +<script lang="ts"> + import { Select as SelectPrimitive } from "bits-ui"; + + let { + open = $bindable(false), + value = $bindable(), + ...restProps + }: SelectPrimitive.RootProps = $props(); +</script> + +<SelectPrimitive.Root bind:open bind:value={value as never} {...restProps} /> |
