solid-element-ui 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/index.css +1 -1
  2. package/dist/index.js +572 -579
  3. package/dist/src/alert-dialog/alert-dialog.d.ts +2 -1
  4. package/package.json +5 -5
  5. package/src/accordion/accordion.tsx +80 -0
  6. package/src/alert/alert.tsx +86 -0
  7. package/src/alert-dialog/alert-dialog.tsx +98 -0
  8. package/src/badge/badge.tsx +52 -0
  9. package/src/breadcrumbs/breadcrumbs.tsx +69 -0
  10. package/src/button/button.tsx +216 -0
  11. package/src/checkbox/checkbox.tsx +64 -0
  12. package/src/collapsible/collapsible.tsx +46 -0
  13. package/src/color-area/color-area.tsx +46 -0
  14. package/src/color-channel-field/color-channel-field.tsx +46 -0
  15. package/src/color-field/color-field.tsx +64 -0
  16. package/src/color-slider/color-slider.tsx +60 -0
  17. package/src/color-swatch/color-swatch.tsx +33 -0
  18. package/src/color-wheel/color-wheel.tsx +50 -0
  19. package/src/combobox/combobox.tsx +97 -0
  20. package/src/context-menu/context-menu.tsx +102 -0
  21. package/src/dialog/dialog.tsx +102 -0
  22. package/src/dropdown-menu/dropdown-menu.tsx +111 -0
  23. package/src/file-field/file-field.tsx +114 -0
  24. package/src/hover-card/hover-card.tsx +62 -0
  25. package/src/image/image.tsx +59 -0
  26. package/src/index.tsx +91 -0
  27. package/src/link/link.tsx +64 -0
  28. package/src/menubar/menubar.tsx +81 -0
  29. package/src/meter/meter.tsx +89 -0
  30. package/src/navigation-menu/navigation-menu.tsx +90 -0
  31. package/src/number-field/number-field.tsx +80 -0
  32. package/src/pagination/pagination.tsx +68 -0
  33. package/src/popover/popover.tsx +59 -0
  34. package/src/progress/progress.tsx +83 -0
  35. package/src/radio-group/radio-group.tsx +94 -0
  36. package/src/rating-group/rating-group.tsx +101 -0
  37. package/src/search/search.tsx +99 -0
  38. package/src/segmented-control/segmented-control.tsx +92 -0
  39. package/src/select/select.tsx +164 -0
  40. package/src/separator/separator.tsx +62 -0
  41. package/src/skeleton/skeleton.tsx +73 -0
  42. package/src/slider/slider.tsx +91 -0
  43. package/src/style/index.css +150 -0
  44. package/src/switch/switch.tsx +104 -0
  45. package/src/tabs/tabs.tsx +73 -0
  46. package/src/text-field/text-field.tsx +97 -0
  47. package/src/time-field/time-field.tsx +103 -0
  48. package/src/toast/toast.tsx +128 -0
  49. package/src/toggle-button/toggle-button.tsx +68 -0
  50. package/src/toggle-group/toggle-group.tsx +86 -0
  51. package/src/tooltip/tooltip.tsx +78 -0
@@ -0,0 +1,64 @@
1
+ import { Checkbox as KCheckbox } from "@kobalte/core/checkbox";
2
+ import { splitProps, type ComponentProps, type JSX } from "solid-js";
3
+ import { tv } from "tailwind-variants";
4
+ import { Check } from "lucide-solid";
5
+
6
+ // TODO 添加checkbox 的几种形状尺寸,看情况吧
7
+
8
+
9
+
10
+ const checkboxStyles = tv(
11
+ {
12
+ slots: {
13
+ root: "group flex items-center gap-2 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50",
14
+ control: [
15
+ "flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border border-zinc-300 transition-all",
16
+ "group-focus-visible:outline-none group-focus-visible:ring-2 group-focus-visible:ring-zinc-950",
17
+ "data-[checked]:bg-zinc-900 data-[checked]:border-zinc-900 data-[checked]:text-zinc-50",
18
+ "data-[disabled]:bg-zinc-400 data-[disabled]:border-zinc-400",
19
+ "dark:border-zinc-700 dark:data-[checked]:bg-zinc-50 dark:data-[checked]:text-zinc-900 dark:group-focus-visible:ring-zinc-300",
20
+ ],
21
+ label: "text-sm font-medium leading-none select-none data-[disabled]:text-zinc-400",
22
+ indicator: "h-3.5 w-3.5",
23
+ description: "text-sm text-zinc-500",
24
+ errorMessage: "text-sm text-red-500",
25
+ },
26
+ },
27
+ {
28
+ twMerge: true,
29
+ },
30
+ );
31
+
32
+ const { root, control, label, indicator, description, errorMessage } = checkboxStyles();
33
+ export interface CheckboxProps extends ComponentProps<typeof KCheckbox> {
34
+ label?: JSX.Element;
35
+ }
36
+
37
+ export const Checkbox = (props: CheckboxProps) => {
38
+ const [local, others] = splitProps(props, [
39
+ "label",
40
+ "class",
41
+ "description",
42
+ "errorMessage",
43
+ ]);
44
+
45
+ return (
46
+ <KCheckbox class={root({ class: local.class })} {...others}>
47
+ <KCheckbox.Input />
48
+ <KCheckbox.Control class={control()}>
49
+ <KCheckbox.Indicator class={indicator()}>
50
+ <Check class={indicator()} stroke-width={3} />
51
+ </KCheckbox.Indicator>
52
+ </KCheckbox.Control>
53
+ {local.label && (
54
+ <KCheckbox.Label class={label()}>{local.label}</KCheckbox.Label>
55
+ )}
56
+ <KCheckbox.Description class={description()}>
57
+ {local.description}
58
+ </KCheckbox.Description>
59
+ <KCheckbox.ErrorMessage class={errorMessage()}>
60
+ {local.errorMessage}
61
+ </KCheckbox.ErrorMessage>
62
+ </KCheckbox>
63
+ );
64
+ };
@@ -0,0 +1,46 @@
1
+ import { Collapsible as KCollapsible } from "@kobalte/core/collapsible";
2
+ import { splitProps, type JSX, type ComponentProps } from "solid-js";
3
+ import { tv } from "tailwind-variants";
4
+ import { ChevronDown } from "lucide-solid";
5
+
6
+ const collapsibleStyles = tv(
7
+ {
8
+ slots: {
9
+ root: "w-full space-y-2",
10
+ trigger:
11
+ "flex w-full items-center justify-between border border-base/30 bg-header px-4 py-2 text-sm font-medium hover:bg-header/80 transition-all group mb-0",
12
+ content:
13
+ "overflow-hidden text-sm transition-all data-[expanded]:animate-collapsible-down data-[closed]:animate-collapsible-up",
14
+ contentInner:
15
+ "px-4 py-3 text-main border border-base/20 !border-t-0",
16
+ icon: "h-4 w-4 text-zinc-500 transition-transform duration-200 group-data-[expanded]:rotate-180",
17
+ },
18
+ },
19
+ {
20
+ twMerge: true,
21
+ },
22
+ );
23
+
24
+ const { root, trigger, content, contentInner, icon } = collapsibleStyles();
25
+
26
+ interface CollapsibleProps extends ComponentProps<typeof KCollapsible> {
27
+ title: JSX.Element;
28
+ children: JSX.Element;
29
+ }
30
+
31
+ export const Collapsible = (props: CollapsibleProps) => {
32
+ const [local, others] = splitProps(props, ["title", "children", "class"]);
33
+
34
+ return (
35
+ <KCollapsible class={root({ class: local.class })} {...others}>
36
+ <KCollapsible.Trigger class={trigger()}>
37
+ <span>{local.title}</span>
38
+ <ChevronDown class={icon()} />
39
+ </KCollapsible.Trigger>
40
+
41
+ <KCollapsible.Content class={content()}>
42
+ <div class={contentInner()}>{local.children}</div>
43
+ </KCollapsible.Content>
44
+ </KCollapsible>
45
+ );
46
+ };
@@ -0,0 +1,46 @@
1
+ import { ColorArea as KColorArea } from "@kobalte/core/color-area";
2
+ import { splitProps, type ComponentProps } from "solid-js";
3
+ import { tv } from "tailwind-variants";
4
+
5
+ const colorAreaStyles = tv(
6
+ {
7
+ slots: {
8
+ root: "relative h-48 w-full shrink-0 rounded-lg border border-zinc-200 dark:border-zinc-800 touch-none",
9
+ background: "h-full w-full rounded-[inherit]",
10
+ thumb: [
11
+ "z-10 h-5 w-5 rounded-full border-2 border-white bg-transparent shadow-md transition-[transform]",
12
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-950 focus-visible:ring-offset-2",
13
+ "hover:scale-110 active:scale-90 cursor-grab active:cursor-grabbing",
14
+ ],
15
+ },
16
+ },
17
+ {
18
+ twMerge: true,
19
+ },
20
+ );
21
+
22
+ const { root, background, thumb } = colorAreaStyles();
23
+
24
+ export interface ColorAreaProps extends ComponentProps<typeof KColorArea> {}
25
+
26
+ export const ColorArea = (props: ColorAreaProps) => {
27
+ const [local, others] = splitProps(props, ["class"]);
28
+
29
+ return (
30
+ <KColorArea class={root({ class: local.class })} {...others}>
31
+ <KColorArea.Background
32
+ class={background()}
33
+ // 默认背景,通常实际使用时会根据 Hue 滑块动态改变这里的红色部分
34
+ style={{
35
+ background:
36
+ "linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, transparent), red",
37
+ }}
38
+ />
39
+ <KColorArea.Thumb class={thumb()}>
40
+ {/* 修复点:ColorArea 需要分别定义 X 和 Y 的隐藏输入框 */}
41
+ <KColorArea.HiddenInputX />
42
+ <KColorArea.HiddenInputY />
43
+ </KColorArea.Thumb>
44
+ </KColorArea>
45
+ );
46
+ };
@@ -0,0 +1,46 @@
1
+ import { ColorChannelField as KColorChannelField } from "@kobalte/core/color-channel-field";
2
+ import { splitProps, type ComponentProps } from "solid-js";
3
+ import { tv } from "tailwind-variants";
4
+
5
+ const fieldStyles = tv(
6
+ {
7
+ slots: {
8
+ root: "flex flex-col gap-1.5 w-full",
9
+ label: "text-sm font-medium text-zinc-900 dark:text-zinc-100 select-none",
10
+ input: [
11
+ "h-9 w-full rounded-md border border-zinc-200 bg-white px-3 py-1 text-sm shadow-sm transition-colors",
12
+ "placeholder:text-zinc-500 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-zinc-950",
13
+ "disabled:cursor-not-allowed disabled:opacity-50",
14
+ "dark:border-zinc-800 dark:bg-zinc-950 dark:focus-visible:ring-zinc-300",
15
+ ],
16
+ },
17
+ },
18
+ {
19
+ twMerge: true,
20
+ },
21
+ );
22
+
23
+ const { root, label, input } = fieldStyles();
24
+
25
+ export interface ColorChannelFieldProps
26
+ extends ComponentProps<typeof KColorChannelField> {
27
+ label?: string;
28
+ }
29
+
30
+
31
+ // TODO channel 问题
32
+
33
+ export const ColorChannelField = (props: ColorChannelFieldProps) => {
34
+ const [local, others] = splitProps(props, ["label", "class"]);
35
+
36
+ return (
37
+ <KColorChannelField class={root({ class: local.class })} {...others}>
38
+ {local.label && (
39
+ <KColorChannelField.Label class={label()}>
40
+ {local.label}
41
+ </KColorChannelField.Label>
42
+ )}
43
+ <KColorChannelField.Input class={input()} />
44
+ </KColorChannelField>
45
+ );
46
+ };
@@ -0,0 +1,64 @@
1
+ import { ColorField as KColorField } from "@kobalte/core/color-field";
2
+ import { splitProps, type ComponentProps } from "solid-js";
3
+ import { tv } from "tailwind-variants";
4
+
5
+ const colorFieldStyles = tv(
6
+ {
7
+ slots: {
8
+ root: "flex flex-col gap-1.5 w-full",
9
+ label: "text-sm font-medium text-zinc-900 dark:text-zinc-100 select-none disabled:opacity-50",
10
+ input: [
11
+ "h-9 w-full rounded-md border border-zinc-200 bg-white px-3 py-1 text-sm shadow-sm transition-all",
12
+ "placeholder:text-zinc-500 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-zinc-950",
13
+ "data-[invalid]:border-red-500 data-[invalid]:focus-visible:ring-red-500",
14
+ "disabled:cursor-not-allowed disabled:opacity-50",
15
+ "dark:border-zinc-800 dark:bg-zinc-950 dark:focus-visible:ring-zinc-300",
16
+ ],
17
+ description: "text-[0.8rem] text-zinc-500 dark:text-zinc-400",
18
+ errorMessage: "text-[0.8rem] font-medium text-red-500",
19
+ },
20
+ },
21
+ {
22
+ twMerge: true,
23
+ },
24
+ );
25
+
26
+ const { root, label, input, description, errorMessage } = colorFieldStyles();
27
+
28
+ export interface ColorFieldProps extends ComponentProps<typeof KColorField> {
29
+ label?: string;
30
+ desc?: string;
31
+ error?: string;
32
+ }
33
+
34
+ export const ColorField = (props: ColorFieldProps) => {
35
+ const [local, others] = splitProps(props, [
36
+ "label",
37
+ "desc",
38
+ "error",
39
+ "class",
40
+ ]);
41
+
42
+ return (
43
+ <KColorField
44
+ class={root({ class: local.class })}
45
+ validationState={local.error ? "invalid" : "valid"}
46
+ {...others}
47
+ >
48
+ {local.label && (
49
+ <KColorField.Label class={label()}>
50
+ {local.label}
51
+ </KColorField.Label>
52
+ )}
53
+ <KColorField.Input class={input()} placeholder="#FFFFFF" />
54
+ {local.desc && !local.error && (
55
+ <KColorField.Description class={description()}>
56
+ {local.desc}
57
+ </KColorField.Description>
58
+ )}
59
+ <KColorField.ErrorMessage class={errorMessage()}>
60
+ {local.error}
61
+ </KColorField.ErrorMessage>
62
+ </KColorField>
63
+ );
64
+ };
@@ -0,0 +1,60 @@
1
+ import { ColorSlider as KColorSlider } from "@kobalte/core/color-slider";
2
+ import { splitProps, type ComponentProps } from "solid-js";
3
+ import { tv } from "tailwind-variants";
4
+
5
+ // TODO defaultValue,还有channel 的问题
6
+
7
+ const sliderStyles = tv(
8
+ {
9
+ slots: {
10
+ root: "relative flex flex-col items-center select-none touch-none w-full gap-2",
11
+ label: "text-sm font-medium text-zinc-900 dark:text-zinc-100 self-start",
12
+ track: "relative h-3 w-full rounded-full border border-black/5 dark:border-white/10",
13
+ thumb: [
14
+ "z-10 h-5 w-5 rounded-full border-2 border-white bg-transparent shadow-md transition-[transform]",
15
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-950 focus-visible:ring-offset-2",
16
+ "hover:scale-110 active:scale-90 cursor-grab active:cursor-grabbing",
17
+ ],
18
+ valueLabel: "text-xs text-zinc-500 dark:text-zinc-400 tabular-nums",
19
+ },
20
+ },
21
+ {
22
+ twMerge: true,
23
+ },
24
+ );
25
+
26
+ const { root, label, track, thumb, valueLabel } = sliderStyles();
27
+
28
+ export interface ColorSliderProps extends ComponentProps<typeof KColorSlider> {
29
+ label?: string;
30
+ showValue?: boolean;
31
+ }
32
+
33
+ export const ColorSlider = (props: ColorSliderProps) => {
34
+ const [local, others] = splitProps(props, ["label", "showValue", "class"]);
35
+
36
+ return (
37
+ <KColorSlider class={root({ class: local.class })} {...others}>
38
+ <div class="flex w-full justify-between items-center">
39
+ {local.label && (
40
+ <KColorSlider.Label class={label()}>
41
+ {local.label}
42
+ </KColorSlider.Label>
43
+ )}
44
+ {local.showValue && (
45
+ <KColorSlider.ValueLabel class={valueLabel()} />
46
+ )}
47
+ </div>
48
+ <KColorSlider.Track
49
+ class={track()}
50
+ style={{
51
+ background: "var(--kb-color-slider-track-background)",
52
+ }}
53
+ >
54
+ <KColorSlider.Thumb class={thumb()}>
55
+ <KColorSlider.Input />
56
+ </KColorSlider.Thumb>
57
+ </KColorSlider.Track>
58
+ </KColorSlider>
59
+ );
60
+ };
@@ -0,0 +1,33 @@
1
+ import { ColorSwatch as KColorSwatch } from "@kobalte/core/color-swatch";
2
+ import { splitProps, type ComponentProps } from "solid-js";
3
+ import { tv } from "tailwind-variants";
4
+
5
+ const colorSwatchStyles = tv(
6
+ {
7
+ base: [
8
+ "h-8 w-8 rounded-md border border-black/10 shadow-sm transition-transform",
9
+ "hover:scale-105 select-none dark:border-white/20",
10
+ ],
11
+ },
12
+ {
13
+ twMerge: true,
14
+ },
15
+ );
16
+
17
+
18
+ // TODO 源代码问题
19
+
20
+ // FIXME 源代码问题
21
+
22
+ export interface ColorSwatchProps extends ComponentProps<typeof KColorSwatch> {}
23
+
24
+ export const ColorSwatch = (props: ColorSwatchProps) => {
25
+ const [local, others] = splitProps(props, ["class", "style"]);
26
+
27
+ return (
28
+ <KColorSwatch
29
+ class={colorSwatchStyles({ class: local.class })}
30
+ {...others}
31
+ />
32
+ );
33
+ };
@@ -0,0 +1,50 @@
1
+ import { ColorWheel as KColorWheel } from "@kobalte/core/color-wheel";
2
+ import { splitProps, type ComponentProps } from "solid-js";
3
+ import { tv } from "tailwind-variants";
4
+
5
+ // FIXME 颜色选项都有源代码问题,注意查看原始kobalte的问题。
6
+
7
+ const colorWheelStyles = tv(
8
+ {
9
+ slots: {
10
+ root: "relative flex flex-col items-center justify-center select-none touch-none",
11
+ track: "relative rounded-full border border-black/5 dark:border-white/10",
12
+ thumb: [
13
+ "z-10 h-5 w-5 rounded-full border-2 border-white bg-transparent shadow-md transition-[transform]",
14
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-950 focus-visible:ring-offset-2",
15
+ "hover:scale-110 active:scale-90 cursor-grab active:cursor-grabbing",
16
+ ],
17
+ },
18
+ },
19
+ {
20
+ twMerge: true,
21
+ },
22
+ );
23
+
24
+ const { root, track, thumb } = colorWheelStyles();
25
+
26
+ export interface ColorWheelProps extends ComponentProps<typeof KColorWheel> {
27
+ size?: number;
28
+ }
29
+
30
+ export const ColorWheel = (props: ColorWheelProps) => {
31
+ const [local, others] = splitProps(props, ["size", "class"]);
32
+
33
+ return (
34
+ <KColorWheel class={root({ class: local.class })} {...others}>
35
+
36
+ <KColorWheel.Track
37
+ class={track()}
38
+ style={{
39
+ width: `${local.size || 160}px`,
40
+ height: `${local.size || 160}px`,
41
+ background: "var(--kb-color-wheel-track-background)",
42
+ }}
43
+ >
44
+ <KColorWheel.Thumb class={thumb()}>
45
+ <KColorWheel.Input />
46
+ </KColorWheel.Thumb>
47
+ </KColorWheel.Track>
48
+ </KColorWheel>
49
+ );
50
+ };
@@ -0,0 +1,97 @@
1
+ import { Combobox as KCombobox } from "@kobalte/core/combobox";
2
+ import { splitProps, type ComponentProps } from "solid-js";
3
+ import { tv } from "tailwind-variants";
4
+ import { Check, ChevronDown } from "lucide-solid";
5
+
6
+ // FIXME 缺少Description,ErrorMessage,验证
7
+
8
+ const comboboxStyles = tv(
9
+ {
10
+ slots: {
11
+ root: "flex flex-col gap-1.5 w-full",
12
+ label: "text-sm font-medium text-zinc-900 dark:text-zinc-100 select-none",
13
+ control:
14
+ "relative flex items-center rounded-md border border-zinc-200 bg-white shadow-sm transition-colors focus-within:ring-1 focus-within:ring-zinc-950 dark:border-zinc-800 dark:bg-zinc-950 dark:focus-within:ring-zinc-300",
15
+ input: "h-9 w-full bg-transparent px-3 py-1 text-sm outline-none placeholder:text-zinc-500 disabled:cursor-not-allowed",
16
+ trigger: "flex h-9 w-9 items-center justify-center text-zinc-500",
17
+ content: [
18
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border border-zinc-200 bg-white text-zinc-950 shadow-md animate-in fade-in-0 zoom-in-95 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50",
19
+ "data-[expanded]:animate-in data-[closed]:animate-out",
20
+ ],
21
+ listbox: "p-1",
22
+ item: "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[highlighted]:bg-zinc-100 data-[highlighted]:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:data-[highlighted]:bg-zinc-800 dark:data-[highlighted]:text-zinc-50",
23
+ itemIndicator:
24
+ "absolute left-2 flex h-3.5 w-3.5 items-center justify-center",
25
+ icon: "h-4 w-4 transition-transform duration-200 origin-center data-[expanded]:rotate-180",
26
+ },
27
+ },
28
+ {
29
+ twMerge: true,
30
+ },
31
+ );
32
+
33
+ const {
34
+ root,
35
+ label,
36
+ control,
37
+ input,
38
+ trigger,
39
+ content,
40
+ listbox,
41
+ item,
42
+ itemIndicator,
43
+ icon,
44
+ } = comboboxStyles();
45
+
46
+ export type ComboboxProps<T> = ComponentProps<typeof KCombobox<T>> & {
47
+ label?: string;
48
+ placeholder?: string;
49
+ class?: string;
50
+ };
51
+
52
+ export const Combobox = <T extends string | object>(
53
+ props: ComboboxProps<T>,
54
+ ) => {
55
+ const [local, others] = splitProps(props as ComboboxProps<T>, [
56
+ "label",
57
+ "placeholder",
58
+ "class",
59
+ ]);
60
+
61
+ return (
62
+ <KCombobox<T> class={root({ class: local.class })} {...others}>
63
+ {local.label && (
64
+ <KCombobox.Label class={label()}>{local.label}</KCombobox.Label>
65
+ )}
66
+
67
+ <KCombobox.Control class={control()}>
68
+ <KCombobox.Input
69
+ class={input()}
70
+ placeholder={local.placeholder}
71
+ />
72
+ <KCombobox.Trigger class={trigger()}>
73
+ <KCombobox.Icon class={icon()}>
74
+ <ChevronDown class="h-4 w-4" />
75
+ </KCombobox.Icon>
76
+ </KCombobox.Trigger>
77
+ </KCombobox.Control>
78
+
79
+ <KCombobox.Portal>
80
+ <KCombobox.Content class={content()}>
81
+ <KCombobox.Listbox class={listbox()} />
82
+ </KCombobox.Content>
83
+ </KCombobox.Portal>
84
+ </KCombobox>
85
+ );
86
+ };
87
+
88
+ export const ComboboxItem = (props: { item: any }) => {
89
+ return (
90
+ <KCombobox.Item item={props.item} class={item()}>
91
+ <KCombobox.ItemIndicator class={itemIndicator()}>
92
+ <Check size={14} />
93
+ </KCombobox.ItemIndicator>
94
+ <KCombobox.ItemLabel>{props.item.rawValue}</KCombobox.ItemLabel>
95
+ </KCombobox.Item>
96
+ );
97
+ };
@@ -0,0 +1,102 @@
1
+ import { ContextMenu as KContextMenu } from "@kobalte/core/context-menu";
2
+ import { splitProps, For, Show,type JSX } from "solid-js";
3
+ import { tv } from "tailwind-variants";
4
+ import { ChevronRight } from "lucide-solid";
5
+
6
+ // TODO 样式修改
7
+
8
+ const menuStyles = tv(
9
+ {
10
+ slots: {
11
+ content: [
12
+ "z-50 min-w-[10rem] overflow-hidden rounded-md border border-zinc-200 bg-white p-1 text-zinc-950 shadow-md dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50",
13
+ "data-[expanded]:animate-in data-[closed]:animate-out",
14
+ ],
15
+ item: "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-zinc-100 data-[highlighted]:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:data-[highlighted]:bg-zinc-800 dark:data-[highlighted]:text-zinc-50",
16
+ separator: "-mx-1 my-1 h-px bg-zinc-100 dark:bg-zinc-800",
17
+ subIcon: "ml-auto h-4 w-4",
18
+ },
19
+ },
20
+ {
21
+ twMerge: true,
22
+ },
23
+ );
24
+
25
+ const { content, item, separator, subIcon } = menuStyles();
26
+
27
+ // 定义配置项类型
28
+ export type ContextMenuItemConfig = {
29
+ label: string;
30
+ onClick?: () => void;
31
+ disabled?: boolean;
32
+ separator?: boolean;
33
+ children?: ContextMenuItemConfig[]; // 支持嵌套子菜单
34
+ };
35
+
36
+ interface UnifiedContextMenuProps {
37
+ items: ContextMenuItemConfig[];
38
+ children: JSX.Element; // 触发区域
39
+ class?: string;
40
+ }
41
+
42
+ // 递归渲染函数
43
+ const RenderMenuItems = (props: { items: ContextMenuItemConfig[] }) => {
44
+ return (
45
+ <For each={props.items}>
46
+ {(itemConfig) => (
47
+ <Show
48
+ when={!itemConfig.separator}
49
+ fallback={<KContextMenu.Separator class={separator()} />}
50
+ >
51
+ <Show
52
+ when={
53
+ itemConfig.children &&
54
+ itemConfig.children.length > 0
55
+ }
56
+ fallback={
57
+ <KContextMenu.Item
58
+ class={item()}
59
+ disabled={itemConfig.disabled}
60
+ onSelect={() => itemConfig.onClick?.()}
61
+ >
62
+ {itemConfig.label}
63
+ </KContextMenu.Item>
64
+ }
65
+ >
66
+ {/* 渲染子菜单 */}
67
+ <KContextMenu.Sub>
68
+ <KContextMenu.SubTrigger class={item()}>
69
+ {itemConfig.label}
70
+ <ChevronRight class={subIcon()} />
71
+ </KContextMenu.SubTrigger>
72
+ <KContextMenu.Portal>
73
+ <KContextMenu.SubContent class={content()}>
74
+ <RenderMenuItems
75
+ items={itemConfig.children!}
76
+ />
77
+ </KContextMenu.SubContent>
78
+ </KContextMenu.Portal>
79
+ </KContextMenu.Sub>
80
+ </Show>
81
+ </Show>
82
+ )}
83
+ </For>
84
+ );
85
+ };
86
+
87
+ export const ContextMenu = (props: UnifiedContextMenuProps) => {
88
+ const [local] = splitProps(props, ["items", "children", "class"]);
89
+
90
+ return (
91
+ <KContextMenu>
92
+ <KContextMenu.Trigger class={local.class}>
93
+ {local.children}
94
+ </KContextMenu.Trigger>
95
+ <KContextMenu.Portal>
96
+ <KContextMenu.Content class={content()}>
97
+ <RenderMenuItems items={local.items} />
98
+ </KContextMenu.Content>
99
+ </KContextMenu.Portal>
100
+ </KContextMenu>
101
+ );
102
+ };