solid-element-ui 0.2.4 → 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.
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/src/accordion/accordion.tsx +80 -0
- package/src/alert/alert.tsx +86 -0
- package/src/alert-dialog/alert-dialog.tsx +98 -0
- package/src/badge/badge.tsx +52 -0
- package/src/breadcrumbs/breadcrumbs.tsx +69 -0
- package/src/button/button.tsx +216 -0
- package/src/checkbox/checkbox.tsx +64 -0
- package/src/collapsible/collapsible.tsx +46 -0
- package/src/color-area/color-area.tsx +46 -0
- package/src/color-channel-field/color-channel-field.tsx +46 -0
- package/src/color-field/color-field.tsx +64 -0
- package/src/color-slider/color-slider.tsx +60 -0
- package/src/color-swatch/color-swatch.tsx +33 -0
- package/src/color-wheel/color-wheel.tsx +50 -0
- package/src/combobox/combobox.tsx +97 -0
- package/src/context-menu/context-menu.tsx +102 -0
- package/src/dialog/dialog.tsx +102 -0
- package/src/dropdown-menu/dropdown-menu.tsx +111 -0
- package/src/file-field/file-field.tsx +114 -0
- package/src/hover-card/hover-card.tsx +62 -0
- package/src/image/image.tsx +59 -0
- package/src/index.tsx +91 -0
- package/src/link/link.tsx +64 -0
- package/src/menubar/menubar.tsx +81 -0
- package/src/meter/meter.tsx +89 -0
- package/src/navigation-menu/navigation-menu.tsx +90 -0
- package/src/number-field/number-field.tsx +80 -0
- package/src/pagination/pagination.tsx +68 -0
- package/src/popover/popover.tsx +59 -0
- package/src/progress/progress.tsx +83 -0
- package/src/radio-group/radio-group.tsx +94 -0
- package/src/rating-group/rating-group.tsx +101 -0
- package/src/search/search.tsx +99 -0
- package/src/segmented-control/segmented-control.tsx +92 -0
- package/src/select/select.tsx +164 -0
- package/src/separator/separator.tsx +62 -0
- package/src/skeleton/skeleton.tsx +73 -0
- package/src/slider/slider.tsx +91 -0
- package/src/style/index.css +150 -0
- package/src/switch/switch.tsx +104 -0
- package/src/tabs/tabs.tsx +73 -0
- package/src/text-field/text-field.tsx +97 -0
- package/src/time-field/time-field.tsx +103 -0
- package/src/toast/toast.tsx +128 -0
- package/src/toggle-button/toggle-button.tsx +68 -0
- package/src/toggle-group/toggle-group.tsx +86 -0
- package/src/tooltip/tooltip.tsx +78 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Dialog as KDialog } from "@kobalte/core/dialog";
|
|
2
|
+
import { splitProps, type JSX, Show } from "solid-js";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
import { X } from "lucide-solid";
|
|
5
|
+
|
|
6
|
+
//TODO 修改footer,可自定义或是自带,方法传入等等
|
|
7
|
+
// FIXME title 和close icon 平行。
|
|
8
|
+
|
|
9
|
+
const dialogStyles = tv(
|
|
10
|
+
{
|
|
11
|
+
slots: {
|
|
12
|
+
overlay: [
|
|
13
|
+
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm animate-in fade-in-0 duration-200",
|
|
14
|
+
"data-[expanded]:animate-in data-[closed]:animate-out",
|
|
15
|
+
],
|
|
16
|
+
content: [
|
|
17
|
+
[
|
|
18
|
+
"fixed left-[50%] top-[50%] z-50 w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-zinc-200 bg-white p-4 shadow-lg rounded-xl",
|
|
19
|
+
"animate-in fade-in-0 zoom-in-95 duration-200 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50",
|
|
20
|
+
"data-[expanded]:animate-in data-[closed]:animate-out",
|
|
21
|
+
],
|
|
22
|
+
],
|
|
23
|
+
title: "text-lg font-semibold leading-none text-zinc-950 dark:text-zinc-50",
|
|
24
|
+
description: "text-sm text-zinc-500 dark:text-zinc-400 mt-2",
|
|
25
|
+
closeButton:
|
|
26
|
+
"rounded-sm opacity-70 transition-opacity hover:opacity-100 dark:text-zinc-400"
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
twMerge: true,
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const { overlay, content, title, description, closeButton } =
|
|
35
|
+
dialogStyles();
|
|
36
|
+
|
|
37
|
+
interface DialogProps {
|
|
38
|
+
// 状态控制
|
|
39
|
+
open?: boolean;
|
|
40
|
+
onOpenChange?: (open: boolean) => void;
|
|
41
|
+
|
|
42
|
+
// 触发器
|
|
43
|
+
trigger?: JSX.Element;
|
|
44
|
+
|
|
45
|
+
// 内容配置
|
|
46
|
+
title: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
children: JSX.Element; // 弹窗主体
|
|
49
|
+
|
|
50
|
+
// 底部按钮配置
|
|
51
|
+
footer?: JSX.Element;
|
|
52
|
+
|
|
53
|
+
// 样式
|
|
54
|
+
class?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const Dialog = (props: DialogProps) => {
|
|
58
|
+
const [local] = splitProps(props, [
|
|
59
|
+
"trigger",
|
|
60
|
+
"title",
|
|
61
|
+
"description",
|
|
62
|
+
"footer",
|
|
63
|
+
"children",
|
|
64
|
+
"class",
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<KDialog open={props.open} onOpenChange={props.onOpenChange}>
|
|
69
|
+
<Show when={local.trigger}>
|
|
70
|
+
<KDialog.Trigger class="inline-block">
|
|
71
|
+
{local.trigger}
|
|
72
|
+
</KDialog.Trigger>
|
|
73
|
+
</Show>
|
|
74
|
+
|
|
75
|
+
<KDialog.Portal>
|
|
76
|
+
<KDialog.Overlay class={overlay()} />
|
|
77
|
+
<KDialog.Content
|
|
78
|
+
class={content({ class: local.class })}
|
|
79
|
+
style={{ "pointer-events": "auto" }}
|
|
80
|
+
>
|
|
81
|
+
<div class="flex justify-between items-center">
|
|
82
|
+
<KDialog.Title class={title()}>
|
|
83
|
+
{local.title}
|
|
84
|
+
</KDialog.Title>
|
|
85
|
+
<KDialog.CloseButton class={closeButton()}>
|
|
86
|
+
<X size={18} />
|
|
87
|
+
<span class="sr-only">关闭</span>
|
|
88
|
+
</KDialog.CloseButton>
|
|
89
|
+
</div>
|
|
90
|
+
<Show when={local.description}>
|
|
91
|
+
<KDialog.Description class={description()}>
|
|
92
|
+
{local.description}
|
|
93
|
+
</KDialog.Description>
|
|
94
|
+
</Show>
|
|
95
|
+
|
|
96
|
+
<div class="mt-6">{local.children}</div>
|
|
97
|
+
|
|
98
|
+
</KDialog.Content>
|
|
99
|
+
</KDialog.Portal>
|
|
100
|
+
</KDialog>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { DropdownMenu as KDropdownMenu } from "@kobalte/core/dropdown-menu";
|
|
2
|
+
import { splitProps, type JSX, For, Show } 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
|
+
trigger: "inline-block cursor-pointer",
|
|
12
|
+
content: [
|
|
13
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-zinc-200 bg-white p-1 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",
|
|
14
|
+
"data-[expanded]:animate-in data-[closed]:animate-out",
|
|
15
|
+
],
|
|
16
|
+
item: "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors 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",
|
|
17
|
+
separator: "-mx-1 my-1 h-px bg-zinc-100 dark:bg-zinc-800",
|
|
18
|
+
subIcon: "ml-auto h-4 w-4",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
twMerge: true,
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const { content, item, separator, subIcon, trigger } = menuStyles();
|
|
27
|
+
|
|
28
|
+
// 定义菜单项配置类型
|
|
29
|
+
export type DropdownItemConfig = {
|
|
30
|
+
label: string;
|
|
31
|
+
onClick?: () => void;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
separator?: boolean; // 是否作为分隔线
|
|
34
|
+
children?: DropdownItemConfig[]; // 子菜单
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
interface DropdownMenuProps {
|
|
38
|
+
trigger: JSX.Element;
|
|
39
|
+
items: DropdownItemConfig[];
|
|
40
|
+
placement?:
|
|
41
|
+
| "bottom"
|
|
42
|
+
| "bottom-start"
|
|
43
|
+
| "bottom-end"
|
|
44
|
+
| "top"
|
|
45
|
+
| "left"
|
|
46
|
+
| "right";
|
|
47
|
+
class?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 递归渲染函数:处理无限级嵌套
|
|
51
|
+
const RenderMenuItems = (props: { items: DropdownItemConfig[] }) => {
|
|
52
|
+
return (
|
|
53
|
+
<For each={props.items}>
|
|
54
|
+
{(config) => (
|
|
55
|
+
<Show
|
|
56
|
+
when={!config.separator}
|
|
57
|
+
fallback={<KDropdownMenu.Separator class={separator()} />}
|
|
58
|
+
>
|
|
59
|
+
<Show
|
|
60
|
+
when={config.children && config.children.length > 0}
|
|
61
|
+
fallback={
|
|
62
|
+
<KDropdownMenu.Item
|
|
63
|
+
class={item()}
|
|
64
|
+
disabled={config.disabled}
|
|
65
|
+
onSelect={() => config.onClick?.()}
|
|
66
|
+
>
|
|
67
|
+
{config.label}
|
|
68
|
+
</KDropdownMenu.Item>
|
|
69
|
+
}
|
|
70
|
+
>
|
|
71
|
+
{/* 子菜单渲染逻辑 */}
|
|
72
|
+
<KDropdownMenu.Sub>
|
|
73
|
+
<KDropdownMenu.SubTrigger class={item()}>
|
|
74
|
+
{config.label}
|
|
75
|
+
<ChevronRight class={subIcon()} />
|
|
76
|
+
</KDropdownMenu.SubTrigger>
|
|
77
|
+
<KDropdownMenu.Portal>
|
|
78
|
+
<KDropdownMenu.SubContent class={content()}>
|
|
79
|
+
<RenderMenuItems items={config.children!} />
|
|
80
|
+
</KDropdownMenu.SubContent>
|
|
81
|
+
</KDropdownMenu.Portal>
|
|
82
|
+
</KDropdownMenu.Sub>
|
|
83
|
+
</Show>
|
|
84
|
+
</Show>
|
|
85
|
+
)}
|
|
86
|
+
</For>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const DropdownMenu = (props: DropdownMenuProps) => {
|
|
91
|
+
const [local] = splitProps(props, [
|
|
92
|
+
"trigger",
|
|
93
|
+
"items",
|
|
94
|
+
"placement",
|
|
95
|
+
"class",
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<KDropdownMenu placement={local.placement ?? "bottom-start"}>
|
|
100
|
+
<KDropdownMenu.Trigger as="div" class={trigger()}>
|
|
101
|
+
{local.trigger}
|
|
102
|
+
</KDropdownMenu.Trigger>
|
|
103
|
+
|
|
104
|
+
<KDropdownMenu.Portal>
|
|
105
|
+
<KDropdownMenu.Content class={content({ class: local.class })}>
|
|
106
|
+
<RenderMenuItems items={local.items} />
|
|
107
|
+
</KDropdownMenu.Content>
|
|
108
|
+
</KDropdownMenu.Portal>
|
|
109
|
+
</KDropdownMenu>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { TextField as KTextField } from "@kobalte/core/text-field";
|
|
2
|
+
import { splitProps, type ComponentProps, Show, createSignal } from "solid-js";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
import { CloudUpload } from "lucide-solid";
|
|
5
|
+
|
|
6
|
+
//TODO 样式修改,移除 UploadCloud这种已废弃的icon
|
|
7
|
+
|
|
8
|
+
const fileFieldStyles = tv(
|
|
9
|
+
{
|
|
10
|
+
slots: {
|
|
11
|
+
root: "flex flex-col gap-1.5 w-full antialiased",
|
|
12
|
+
label: "text-sm font-medium text-slate-700 dark:text-slate-300 ml-1",
|
|
13
|
+
dropzone: [
|
|
14
|
+
"relative flex flex-col items-center justify-center w-full min-h-[140px]",
|
|
15
|
+
"border-2 border-dashed rounded-xl transition-all cursor-pointer",
|
|
16
|
+
"bg-slate-50/50 hover:bg-slate-100 dark:bg-slate-900/10 dark:hover:bg-slate-900/20",
|
|
17
|
+
"focus-within:ring-2 focus-within:ring-blue-500/20 focus-within:border-blue-500",
|
|
18
|
+
],
|
|
19
|
+
icon: "w-10 h-10 mb-3 text-slate-400",
|
|
20
|
+
description: "text-xs text-slate-500 dark:text-slate-400 mt-1",
|
|
21
|
+
errorMessage: "text-xs text-red-500 font-medium ml-1 mt-1",
|
|
22
|
+
},
|
|
23
|
+
variants: {
|
|
24
|
+
validationState: {
|
|
25
|
+
valid: {},
|
|
26
|
+
invalid: {
|
|
27
|
+
dropzone:
|
|
28
|
+
"border-red-400 bg-red-50/30 dark:border-red-900/20",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
isDisabled: {
|
|
32
|
+
true: {
|
|
33
|
+
dropzone: "opacity-50 cursor-not-allowed grayscale",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
twMerge: true,
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
type FileFieldVariants = VariantProps<typeof fileFieldStyles>;
|
|
44
|
+
|
|
45
|
+
export interface FileFieldProps
|
|
46
|
+
extends
|
|
47
|
+
Omit<ComponentProps<typeof KTextField>, "value" | "onChange">,
|
|
48
|
+
FileFieldVariants {
|
|
49
|
+
label?: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
accept?: string;
|
|
52
|
+
multiple?: boolean;
|
|
53
|
+
onChange?: (files: File[]) => void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const FileField = (props: FileFieldProps) => {
|
|
57
|
+
const [local, variantProps, others] = splitProps(
|
|
58
|
+
props,
|
|
59
|
+
["label", "description", "class", "accept", "multiple", "onChange"],
|
|
60
|
+
["validationState", "isDisabled"],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const [files, setFiles] = createSignal<File[]>([]);
|
|
64
|
+
const styles = () =>
|
|
65
|
+
fileFieldStyles({
|
|
66
|
+
validationState: variantProps.validationState,
|
|
67
|
+
isDisabled: variantProps.isDisabled,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const onFileChange = (e: Event) => {
|
|
71
|
+
const target = e.target as HTMLInputElement;
|
|
72
|
+
if (target.files) {
|
|
73
|
+
const fileList = Array.from(target.files);
|
|
74
|
+
setFiles(fileList);
|
|
75
|
+
local.onChange?.(fileList);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<KTextField
|
|
81
|
+
class={styles().root({ class: local.class })}
|
|
82
|
+
validationState={variantProps.validationState}
|
|
83
|
+
disabled={variantProps.isDisabled}
|
|
84
|
+
{...others}
|
|
85
|
+
>
|
|
86
|
+
<Show when={local.label}>
|
|
87
|
+
<KTextField.Label class={styles().label()}>
|
|
88
|
+
{local.label}
|
|
89
|
+
</KTextField.Label>
|
|
90
|
+
</Show>
|
|
91
|
+
|
|
92
|
+
<label class={styles().dropzone()}>
|
|
93
|
+
<KTextField.Input
|
|
94
|
+
type="file"
|
|
95
|
+
class="sr-only"
|
|
96
|
+
accept={local.accept}
|
|
97
|
+
multiple={local.multiple}
|
|
98
|
+
onChange={onFileChange}
|
|
99
|
+
/>
|
|
100
|
+
<CloudUpload class={styles().icon()} />
|
|
101
|
+
<div class="text-sm font-medium text-slate-600 dark:text-slate-400">
|
|
102
|
+
{files().length > 0
|
|
103
|
+
? `已选择 ${files().length} 个文件`
|
|
104
|
+
: "点击或拖拽上传文件"}
|
|
105
|
+
</div>
|
|
106
|
+
<Show when={local.description}>
|
|
107
|
+
<p class={styles().description()}>{local.description}</p>
|
|
108
|
+
</Show>
|
|
109
|
+
</label>
|
|
110
|
+
|
|
111
|
+
<KTextField.ErrorMessage class={styles().errorMessage()} />
|
|
112
|
+
</KTextField>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { HoverCard as KHoverCard } from "@kobalte/core/hover-card";
|
|
2
|
+
import { splitProps, type ComponentProps, type JSX } from "solid-js";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
const hoverCardStyles = tv(
|
|
6
|
+
{
|
|
7
|
+
slots: {
|
|
8
|
+
content: [
|
|
9
|
+
"z-50 w-64 rounded-xl border bg-white p-4 shadow-lg outline-none",
|
|
10
|
+
"dark:bg-slate-900 dark:border-slate-800 antialiased",
|
|
11
|
+
"data-[expanded]:animate-in data-[closed]:animate-out",
|
|
12
|
+
],
|
|
13
|
+
arrow: "fill-white stroke-slate-200 dark:fill-slate-900 dark:stroke-slate-800",
|
|
14
|
+
},
|
|
15
|
+
variants: {
|
|
16
|
+
size: {
|
|
17
|
+
sm: { content: "w-48 p-3" },
|
|
18
|
+
md: { content: "w-64 p-4" },
|
|
19
|
+
lg: { content: "w-80 p-6" },
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
size: "md",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
twMerge: true,
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
type HoverCardVariants = VariantProps<typeof hoverCardStyles>;
|
|
32
|
+
|
|
33
|
+
export interface HoverCardProps
|
|
34
|
+
extends ComponentProps<typeof KHoverCard>, HoverCardVariants {
|
|
35
|
+
trigger: JSX.Element;
|
|
36
|
+
showArrow?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const HoverCard = (props: HoverCardProps) => {
|
|
40
|
+
const [local, variantProps, others] = splitProps(
|
|
41
|
+
props,
|
|
42
|
+
["trigger", "children", "showArrow"],
|
|
43
|
+
["size"],
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const styles = hoverCardStyles({ size: variantProps.size });
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<KHoverCard openDelay={200} closeDelay={300} {...others}>
|
|
50
|
+
<KHoverCard.Trigger>{local.trigger}</KHoverCard.Trigger>
|
|
51
|
+
|
|
52
|
+
<KHoverCard.Portal>
|
|
53
|
+
<KHoverCard.Content class={styles.content()}>
|
|
54
|
+
{local.showArrow && (
|
|
55
|
+
<KHoverCard.Arrow class={styles.arrow()} />
|
|
56
|
+
)}
|
|
57
|
+
{local.children}
|
|
58
|
+
</KHoverCard.Content>
|
|
59
|
+
</KHoverCard.Portal>
|
|
60
|
+
</KHoverCard>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Image as KImage } from "@kobalte/core/image";
|
|
2
|
+
import { splitProps, type ComponentProps } from "solid-js";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
const imageStyles = tv(
|
|
6
|
+
{
|
|
7
|
+
slots: {
|
|
8
|
+
root: "relative flex items-center h-full w-full shrink-0 overflow-hidden",
|
|
9
|
+
img: "h-full w-full aspect-square object-cover",
|
|
10
|
+
fallback:
|
|
11
|
+
"flex h-full w-full items-center justify-center bg-slate-100 dark:bg-slate-800 text-slate-400",
|
|
12
|
+
},
|
|
13
|
+
variants: {
|
|
14
|
+
radius: {
|
|
15
|
+
none: { root: "rounded-none" },
|
|
16
|
+
sm: { root: "rounded-sm" },
|
|
17
|
+
md: { root: "rounded-md" },
|
|
18
|
+
lg: { root: "rounded-lg" },
|
|
19
|
+
full: { root: "rounded-full" },
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
radius: "none",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
twMerge: true,
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
type ImageVariants = VariantProps<typeof imageStyles>;
|
|
32
|
+
|
|
33
|
+
export interface ImageProps
|
|
34
|
+
extends ComponentProps<typeof KImage>,
|
|
35
|
+
ImageVariants {
|
|
36
|
+
src?: string;
|
|
37
|
+
alt?: string;
|
|
38
|
+
fallback?: string | Array<any> | any; // 支持自定义 fallback 内容
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const Image = (props: ImageProps) => {
|
|
42
|
+
const [local, variantProps, others] = splitProps(
|
|
43
|
+
props,
|
|
44
|
+
["src", "alt", "fallback", "class"],
|
|
45
|
+
["radius"]
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const styles = imageStyles({ radius: variantProps.radius });
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<KImage class={styles.root({ class: local.class })} {...others}>
|
|
52
|
+
<KImage.Img src={local.src} alt={local.alt} class={styles.img()} />
|
|
53
|
+
<KImage.Fallback class={styles.fallback()}>
|
|
54
|
+
{local.fallback ||
|
|
55
|
+
(local.alt ? local.alt.slice(0, 2).toUpperCase() : "IMG")}
|
|
56
|
+
</KImage.Fallback>
|
|
57
|
+
</KImage>
|
|
58
|
+
);
|
|
59
|
+
};
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import "./style/index.css";
|
|
2
|
+
|
|
3
|
+
export { Accordion } from "./accordion/accordion";
|
|
4
|
+
|
|
5
|
+
export { Alert } from "./alert/alert";
|
|
6
|
+
|
|
7
|
+
export { AlertDialog } from "./alert-dialog/alert-dialog";
|
|
8
|
+
|
|
9
|
+
export { Badge } from "./badge/badge";
|
|
10
|
+
|
|
11
|
+
export { Breadcrumbs } from "./breadcrumbs/breadcrumbs";
|
|
12
|
+
|
|
13
|
+
export { Button } from "./button/button";
|
|
14
|
+
|
|
15
|
+
export { Checkbox } from "./checkbox/checkbox";
|
|
16
|
+
|
|
17
|
+
export { Collapsible } from "./collapsible/collapsible";
|
|
18
|
+
|
|
19
|
+
export { ColorArea } from "./color-area/color-area";
|
|
20
|
+
|
|
21
|
+
export { ColorChannelField } from "./color-channel-field/color-channel-field";
|
|
22
|
+
|
|
23
|
+
export { ColorField } from "./color-field/color-field";
|
|
24
|
+
|
|
25
|
+
export { ColorSlider } from "./color-slider/color-slider";
|
|
26
|
+
|
|
27
|
+
export { ColorSwatch } from "./color-swatch/color-swatch";
|
|
28
|
+
|
|
29
|
+
export { ColorWheel } from "./color-wheel/color-wheel";
|
|
30
|
+
|
|
31
|
+
export { Combobox, ComboboxItem } from "./combobox/combobox";
|
|
32
|
+
|
|
33
|
+
export { ContextMenu } from "./context-menu/context-menu";
|
|
34
|
+
|
|
35
|
+
export { Dialog } from "./dialog/dialog";
|
|
36
|
+
|
|
37
|
+
export { DropdownMenu } from "./dropdown-menu/dropdown-menu";
|
|
38
|
+
|
|
39
|
+
export { FileField } from "./file-field/file-field";
|
|
40
|
+
|
|
41
|
+
export { HoverCard } from "./hover-card/hover-card";
|
|
42
|
+
|
|
43
|
+
export { Image } from "./image/image";
|
|
44
|
+
|
|
45
|
+
export { Link } from "./link/link";
|
|
46
|
+
|
|
47
|
+
export { Menubar } from "./menubar/menubar";
|
|
48
|
+
|
|
49
|
+
export { Meter } from "./meter/meter";
|
|
50
|
+
|
|
51
|
+
export { NavigationMenu } from "./navigation-menu/navigation-menu";
|
|
52
|
+
|
|
53
|
+
export { NumberField } from "./number-field/number-field";
|
|
54
|
+
|
|
55
|
+
export { Pagination } from "./pagination/pagination";
|
|
56
|
+
|
|
57
|
+
export { Popover } from "./popover/popover";
|
|
58
|
+
|
|
59
|
+
export { Progress } from "./progress/progress";
|
|
60
|
+
|
|
61
|
+
export { RadioGroup } from "./radio-group/radio-group";
|
|
62
|
+
|
|
63
|
+
// rating-group 组件
|
|
64
|
+
|
|
65
|
+
export { Search } from "./search/search";
|
|
66
|
+
|
|
67
|
+
export { SegmentedControl } from "./segmented-control/segmented-control";
|
|
68
|
+
|
|
69
|
+
export { Select } from "./select/select";
|
|
70
|
+
|
|
71
|
+
export { Separator } from "./separator/separator";
|
|
72
|
+
|
|
73
|
+
export { Skeleton } from "./skeleton/skeleton";
|
|
74
|
+
|
|
75
|
+
export { Slider } from "./slider/slider";
|
|
76
|
+
|
|
77
|
+
export { Switch } from "./switch/switch";
|
|
78
|
+
|
|
79
|
+
export { Tabs } from "./tabs/tabs";
|
|
80
|
+
|
|
81
|
+
export { TextField } from "./text-field/text-field";
|
|
82
|
+
|
|
83
|
+
// time-field
|
|
84
|
+
|
|
85
|
+
export { ToastProvider, showToast } from "./toast/toast";
|
|
86
|
+
|
|
87
|
+
export { ToggleButton } from "./toggle-button/toggle-button";
|
|
88
|
+
|
|
89
|
+
export { ToggleGroup } from "./toggle-group/toggle-group";
|
|
90
|
+
|
|
91
|
+
export { Tooltip } from "./tooltip/tooltip";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Link as KLink } from "@kobalte/core/link";
|
|
2
|
+
import { splitProps, type ComponentProps } from "solid-js";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
const linkStyles = tv(
|
|
6
|
+
{
|
|
7
|
+
base: "inline-flex items-center justify-center transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 rounded-sm disabled:pointer-events-none disabled:opacity-50",
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default:
|
|
11
|
+
"text-slate-900 underline underline-offset-4 hover:text-slate-700 dark:text-slate-100",
|
|
12
|
+
primary:
|
|
13
|
+
"text-blue-600 hover:text-blue-500 dark:text-blue-400 font-medium",
|
|
14
|
+
muted: "text-slate-500 hover:text-slate-600 dark:text-slate-400",
|
|
15
|
+
button: "bg-slate-900 text-slate-50 hover:bg-slate-900/90 px-4 py-2 text-sm dark:bg-slate-50 dark:text-slate-900",
|
|
16
|
+
},
|
|
17
|
+
underline: {
|
|
18
|
+
always: "underline",
|
|
19
|
+
hover: "no-underline hover:underline",
|
|
20
|
+
none: "no-underline",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultVariants: {
|
|
24
|
+
variant: "default",
|
|
25
|
+
underline: "hover",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
twMerge: true,
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
type LinkVariants = VariantProps<typeof linkStyles>;
|
|
34
|
+
|
|
35
|
+
export interface LinkProps extends ComponentProps<typeof KLink>, LinkVariants {
|
|
36
|
+
external?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const Link = (props: LinkProps) => {
|
|
40
|
+
const [local, variantProps, others] = splitProps(
|
|
41
|
+
props,
|
|
42
|
+
["class", "external", "children", "href"],
|
|
43
|
+
["variant", "underline"]
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const styles = () =>
|
|
47
|
+
linkStyles({
|
|
48
|
+
variant: variantProps.variant,
|
|
49
|
+
underline: variantProps.underline,
|
|
50
|
+
class: local.class,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<KLink
|
|
55
|
+
href={local.href}
|
|
56
|
+
target={local.external ? "_blank" : undefined}
|
|
57
|
+
rel={local.external ? "noopener noreferrer" : undefined}
|
|
58
|
+
class={styles()}
|
|
59
|
+
{...others}
|
|
60
|
+
>
|
|
61
|
+
{local.children}
|
|
62
|
+
</KLink>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Menubar as KMenubar } from "@kobalte/core/menubar";
|
|
2
|
+
import { splitProps, type ComponentProps } from "solid-js";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
// TODO 1. 格式
|
|
6
|
+
|
|
7
|
+
const menubarStyles = tv(
|
|
8
|
+
{
|
|
9
|
+
slots: {
|
|
10
|
+
root: "flex h-10 items-center space-x-1 rounded-md border bg-white p-1 shadow-sm dark:bg-slate-950 dark:border-slate-800",
|
|
11
|
+
trigger:
|
|
12
|
+
"flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-800 dark:data-[state=open]:bg-slate-800",
|
|
13
|
+
content: [
|
|
14
|
+
"z-50 min-w-[12rem] overflow-hidden rounded-md border bg-white p-1 shadow-md dark:bg-slate-950 dark:border-slate-800 animate-in fade-in zoom-in-95",
|
|
15
|
+
"data-[expanded]:animate-in data-[closed]:animate-out",
|
|
16
|
+
],
|
|
17
|
+
item: "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-slate-100 data-[disabled]:opacity-50 dark:focus:bg-slate-800",
|
|
18
|
+
separator: "-mx-1 my-1 h-px bg-slate-100 dark:bg-slate-800",
|
|
19
|
+
shortcut: "ml-auto text-xs tracking-widest text-slate-500",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
twMerge: true,
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const s = menubarStyles();
|
|
28
|
+
|
|
29
|
+
export const Menubar = Object.assign(
|
|
30
|
+
(props: ComponentProps<typeof KMenubar>) => {
|
|
31
|
+
const [local, others] = splitProps(props, ["class"]);
|
|
32
|
+
return <KMenubar class={s.root({ class: local.class })} {...others} />;
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
Menu: KMenubar.Menu,
|
|
36
|
+
Trigger: (props: ComponentProps<typeof KMenubar.Trigger>) => {
|
|
37
|
+
const [local, others] = splitProps(props, ["class"]);
|
|
38
|
+
return (
|
|
39
|
+
<KMenubar.Trigger
|
|
40
|
+
class={s.trigger({ class: local.class })}
|
|
41
|
+
{...others}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
Content: (props: ComponentProps<typeof KMenubar.Content>) => {
|
|
46
|
+
const [local, others] = splitProps(props, ["class"]);
|
|
47
|
+
return (
|
|
48
|
+
<KMenubar.Portal>
|
|
49
|
+
<KMenubar.Content
|
|
50
|
+
class={s.content({ class: local.class })}
|
|
51
|
+
{...others}
|
|
52
|
+
/>
|
|
53
|
+
</KMenubar.Portal>
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
Item: (props: ComponentProps<typeof KMenubar.Item>) => {
|
|
57
|
+
const [local, others] = splitProps(props, ["class"]);
|
|
58
|
+
return (
|
|
59
|
+
<KMenubar.Item
|
|
60
|
+
class={s.item({ class: local.class })}
|
|
61
|
+
{...others}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
Separator: (props: ComponentProps<typeof KMenubar.Separator>) => {
|
|
66
|
+
const [local, others] = splitProps(props, ["class"]);
|
|
67
|
+
return (
|
|
68
|
+
<KMenubar.Separator
|
|
69
|
+
class={s.separator({ class: local.class })}
|
|
70
|
+
{...others}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
},
|
|
74
|
+
Shortcut: (props: ComponentProps<"span">) => {
|
|
75
|
+
const [local, others] = splitProps(props, ["class"]);
|
|
76
|
+
return (
|
|
77
|
+
<span class={s.shortcut({ class: local.class })} {...others} />
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
);
|