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.
- package/dist/index.css +1 -1
- package/dist/index.js +572 -579
- package/dist/src/alert-dialog/alert-dialog.d.ts +2 -1
- package/package.json +5 -5
- 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
|
@@ -4,8 +4,9 @@ interface AlertDialogProps extends ComponentProps<typeof KAlertDialog> {
|
|
|
4
4
|
trigger: JSX.Element;
|
|
5
5
|
title: string;
|
|
6
6
|
description?: string;
|
|
7
|
-
action
|
|
7
|
+
action?: JSX.Element;
|
|
8
8
|
cancel?: JSX.Element;
|
|
9
|
+
onConfirm?: () => void;
|
|
9
10
|
}
|
|
10
11
|
export declare const AlertDialog: (props: AlertDialogProps) => JSX.Element;
|
|
11
12
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solid-element-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "sanguogege",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"dist",
|
|
28
|
+
"src",
|
|
28
29
|
"README.md"
|
|
29
30
|
],
|
|
30
31
|
"main": "./dist/index.js",
|
|
@@ -33,12 +34,11 @@
|
|
|
33
34
|
"exports": {
|
|
34
35
|
".": {
|
|
35
36
|
"types": "./dist/index.d.ts",
|
|
36
|
-
"solid": "./src/index.tsx",
|
|
37
37
|
"import": "./dist/index.js",
|
|
38
|
-
"
|
|
38
|
+
"solid": "./src/index.tsx",
|
|
39
|
+
"default": "./dist/index.js"
|
|
39
40
|
},
|
|
40
|
-
"./
|
|
41
|
-
"./dist/index.css": "./dist/index.css"
|
|
41
|
+
"./index.css": "./dist/index.css"
|
|
42
42
|
},
|
|
43
43
|
"source": "./src/index.tsx",
|
|
44
44
|
"scripts": {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Accordion as KAccordion,
|
|
3
|
+
type AccordionRootProps,
|
|
4
|
+
} from "@kobalte/core/accordion";
|
|
5
|
+
import { For, type JSX, splitProps } from "solid-js";
|
|
6
|
+
import { ChevronDown } from "lucide-solid";
|
|
7
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
8
|
+
|
|
9
|
+
// 1. 定义样式
|
|
10
|
+
const accordionStyles = tv(
|
|
11
|
+
{
|
|
12
|
+
slots: {
|
|
13
|
+
root: "w-full divide-y divide-base border border-base rounded-lg overflow-hidden",
|
|
14
|
+
item: "group",
|
|
15
|
+
header: "flex",
|
|
16
|
+
trigger: [
|
|
17
|
+
"flex flex-1 items-center justify-between cursor-pointer py-4 px-4 text-md font-medium transition-all ",
|
|
18
|
+
"bg-header hover:bg-header/80",
|
|
19
|
+
],
|
|
20
|
+
content: [
|
|
21
|
+
"overflow-hidden text-md transition-all bg-transparent text-main",
|
|
22
|
+
"data-[expanded]:animate-accordion-down data-[closed]:animate-accordion-up",
|
|
23
|
+
],
|
|
24
|
+
contentInner: "pb-4 pt-2 px-4",
|
|
25
|
+
icon: "h-4 w-4 transition-transform duration-200 group-data-[expanded]:rotate-180",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
twMerge: true,
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const { root, item, header, trigger, content, contentInner, icon } =
|
|
34
|
+
accordionStyles();
|
|
35
|
+
|
|
36
|
+
// 2. 类型定义
|
|
37
|
+
export interface AccordionItem {
|
|
38
|
+
value: string;
|
|
39
|
+
title: JSX.Element;
|
|
40
|
+
content: JSX.Element;
|
|
41
|
+
disabled?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface AccordionProps
|
|
45
|
+
extends AccordionRootProps, VariantProps<typeof accordionStyles> {
|
|
46
|
+
items: AccordionItem[];
|
|
47
|
+
class?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const Accordion = (props: AccordionProps) => {
|
|
51
|
+
const [local, others] = splitProps(props, ["items", "class"]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<KAccordion class={root({ class: local.class })} {...others}>
|
|
55
|
+
<For each={local.items}>
|
|
56
|
+
{(itemData) => (
|
|
57
|
+
<KAccordion.Item
|
|
58
|
+
value={itemData.value}
|
|
59
|
+
disabled={itemData.disabled}
|
|
60
|
+
class={item()}
|
|
61
|
+
>
|
|
62
|
+
<KAccordion.Header class={header()}>
|
|
63
|
+
<KAccordion.Trigger class={trigger()}>
|
|
64
|
+
{itemData.title}
|
|
65
|
+
<ChevronDown
|
|
66
|
+
class={icon()}
|
|
67
|
+
aria-hidden="true"
|
|
68
|
+
/>
|
|
69
|
+
</KAccordion.Trigger>
|
|
70
|
+
</KAccordion.Header>
|
|
71
|
+
|
|
72
|
+
<KAccordion.Content class={content()}>
|
|
73
|
+
<div class={contentInner()}>{itemData.content}</div>
|
|
74
|
+
</KAccordion.Content>
|
|
75
|
+
</KAccordion.Item>
|
|
76
|
+
)}
|
|
77
|
+
</For>
|
|
78
|
+
</KAccordion>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Alert as KAlert } from "@kobalte/core/alert";
|
|
2
|
+
import { splitProps, type JSX, type ComponentProps } from "solid-js";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
import { Info, CircleAlert, CircleCheck, CircleX } from "lucide-solid";
|
|
5
|
+
|
|
6
|
+
const alertStyles = tv(
|
|
7
|
+
{
|
|
8
|
+
slots: {
|
|
9
|
+
root: "relative w-full rounded-lg border p-4 flex gap-3 antialiased text-main",
|
|
10
|
+
content: "flex flex-col gap-1 text-left",
|
|
11
|
+
title: "font-semibold leading-none tracking-tight",
|
|
12
|
+
children: "text-md leading-relaxed opacity-90",
|
|
13
|
+
icon: "shrink-0",
|
|
14
|
+
},
|
|
15
|
+
variants: {
|
|
16
|
+
variant: {
|
|
17
|
+
info: {
|
|
18
|
+
root: "bg-primary/20 border-primary/80",
|
|
19
|
+
icon: "text-primary",
|
|
20
|
+
},
|
|
21
|
+
success: {
|
|
22
|
+
root: "bg-success/20 border-success/80 ",
|
|
23
|
+
icon: "text-success",
|
|
24
|
+
},
|
|
25
|
+
warning: {
|
|
26
|
+
root: "bg-warning/20 border-warning/80 ",
|
|
27
|
+
icon: "text-warning",
|
|
28
|
+
},
|
|
29
|
+
danger: {
|
|
30
|
+
root: "bg-danger/20 border-danger/80 ",
|
|
31
|
+
icon: "text-danger",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
variant: "info",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
twMerge: true,
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
type AlertVariants = VariantProps<typeof alertStyles>;
|
|
45
|
+
|
|
46
|
+
export interface AlertProps
|
|
47
|
+
extends ComponentProps<typeof KAlert>, AlertVariants {
|
|
48
|
+
title?: string;
|
|
49
|
+
icon?: boolean | JSX.Element;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const iconMap = {
|
|
53
|
+
info: Info,
|
|
54
|
+
success: CircleCheck,
|
|
55
|
+
warning: CircleAlert,
|
|
56
|
+
danger: CircleX,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const Alert = (props: AlertProps) => {
|
|
60
|
+
const [local, variantProps, others] = splitProps(
|
|
61
|
+
props,
|
|
62
|
+
["title", "icon", "children", "class"],
|
|
63
|
+
["variant"],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const { root, content, title, children, icon } = alertStyles(variantProps);
|
|
67
|
+
|
|
68
|
+
const RenderedIcon = () => {
|
|
69
|
+
if (local.icon === false) return null;
|
|
70
|
+
if (typeof local.icon === "object") return local.icon;
|
|
71
|
+
|
|
72
|
+
const Icon = iconMap[variantProps.variant || "info"];
|
|
73
|
+
return <Icon size={18} class={icon()} />;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// 5. 渲染组件
|
|
77
|
+
return (
|
|
78
|
+
<KAlert class={`${root()} ${local.class || ""}`.trim()} {...others}>
|
|
79
|
+
<RenderedIcon />
|
|
80
|
+
<div class={content()}>
|
|
81
|
+
{local.title && <h5 class={title()}>{local.title}</h5>}
|
|
82
|
+
<div class={children()}>{local.children}</div>
|
|
83
|
+
</div>
|
|
84
|
+
</KAlert>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { AlertDialog as KAlertDialog } from "@kobalte/core/alert-dialog";
|
|
2
|
+
import { splitProps, type JSX, type ComponentProps } from "solid-js";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
import { X } from "lucide-solid";
|
|
5
|
+
import { Button } from "../button/button";
|
|
6
|
+
|
|
7
|
+
// TODO 修改点击确定时的行为,目前是关闭对话框
|
|
8
|
+
|
|
9
|
+
const alertDialogStyles = tv(
|
|
10
|
+
{
|
|
11
|
+
slots: {
|
|
12
|
+
overlay: [
|
|
13
|
+
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm",
|
|
14
|
+
" data-[expanded]:animate-in data-[closed]:animate-out ",
|
|
15
|
+
],
|
|
16
|
+
content: [
|
|
17
|
+
"fixed left-1/2 top-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2 rounded-xl bg-app p-4 shadow-xl ",
|
|
18
|
+
"data-[expanded]:animate-in data-[closed]:animate-out",
|
|
19
|
+
],
|
|
20
|
+
header: "flex align-center justify-between",
|
|
21
|
+
title: "text-lg font-semibold text-main ",
|
|
22
|
+
description: "text-sm py-2 text-main",
|
|
23
|
+
footer: "mt-6 flex flex-row justify-end gap-3",
|
|
24
|
+
closeButton:
|
|
25
|
+
"rounded-sm opacity-70 text-main transition-opacity hover:opacity-100 focus:outline-none",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
twMerge: true,
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const { overlay, content, header, title, description, footer, closeButton } =
|
|
34
|
+
alertDialogStyles();
|
|
35
|
+
|
|
36
|
+
interface AlertDialogProps extends ComponentProps<typeof KAlertDialog> {
|
|
37
|
+
trigger: JSX.Element;
|
|
38
|
+
title: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
action?: JSX.Element;
|
|
41
|
+
cancel?: JSX.Element;
|
|
42
|
+
onConfirm?: () => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const AlertDialog = (props: AlertDialogProps) => {
|
|
46
|
+
const [local, others] = splitProps(props, [
|
|
47
|
+
"trigger",
|
|
48
|
+
"title",
|
|
49
|
+
"description",
|
|
50
|
+
"action",
|
|
51
|
+
"cancel",
|
|
52
|
+
"onConfirm",
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
const handleConfirm = () => {
|
|
56
|
+
local.onConfirm?.();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<KAlertDialog {...others}>
|
|
61
|
+
<KAlertDialog.Trigger>{local.trigger}</KAlertDialog.Trigger>
|
|
62
|
+
<KAlertDialog.Portal>
|
|
63
|
+
<KAlertDialog.Overlay class={overlay()} />
|
|
64
|
+
<KAlertDialog.Content class={content()}>
|
|
65
|
+
<div class={header()}>
|
|
66
|
+
<KAlertDialog.Title class={title()}>
|
|
67
|
+
{local.title}
|
|
68
|
+
</KAlertDialog.Title>
|
|
69
|
+
<KAlertDialog.CloseButton class={closeButton()}>
|
|
70
|
+
<X size={18} />
|
|
71
|
+
</KAlertDialog.CloseButton>
|
|
72
|
+
</div>
|
|
73
|
+
<div>
|
|
74
|
+
{local.description && (
|
|
75
|
+
<KAlertDialog.Description class={description()}>
|
|
76
|
+
{local.description}
|
|
77
|
+
</KAlertDialog.Description>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div class={footer()}>
|
|
82
|
+
<KAlertDialog.CloseButton>
|
|
83
|
+
{local.cancel || (
|
|
84
|
+
<Button variant="outline">取消</Button>
|
|
85
|
+
)}
|
|
86
|
+
</KAlertDialog.CloseButton>
|
|
87
|
+
|
|
88
|
+
<KAlertDialog.CloseButton onClick={handleConfirm}>
|
|
89
|
+
{local.action || (
|
|
90
|
+
<Button color="primary">确认</Button>
|
|
91
|
+
)}
|
|
92
|
+
</KAlertDialog.CloseButton>
|
|
93
|
+
</div>
|
|
94
|
+
</KAlertDialog.Content>
|
|
95
|
+
</KAlertDialog.Portal>
|
|
96
|
+
</KAlertDialog>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Badge as KBadge } from "@kobalte/core/badge";
|
|
2
|
+
import { splitProps, type ComponentProps } from "solid-js";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
|
|
5
|
+
const badgeStyles = tv(
|
|
6
|
+
{
|
|
7
|
+
base: "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none",
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default:
|
|
11
|
+
"border-transparent bg-zinc-900 text-zinc-50 hover:bg-zinc-900/80 dark:bg-zinc-50 dark:text-zinc-900",
|
|
12
|
+
secondary:
|
|
13
|
+
"border-transparent bg-zinc-100 text-zinc-900 hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50",
|
|
14
|
+
outline:
|
|
15
|
+
"text-zinc-950 border-zinc-200 dark:text-zinc-50 dark:border-zinc-800",
|
|
16
|
+
success:
|
|
17
|
+
"border-transparent bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-400",
|
|
18
|
+
danger: "border-transparent bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
variant: "default",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
twMerge: true,
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
type BadgeVariants = VariantProps<typeof badgeStyles>;
|
|
31
|
+
|
|
32
|
+
export interface BadgeProps
|
|
33
|
+
extends ComponentProps<typeof KBadge>,
|
|
34
|
+
BadgeVariants {}
|
|
35
|
+
|
|
36
|
+
export const Badge = (props: BadgeProps) => {
|
|
37
|
+
const [local, variantProps, others] = splitProps(
|
|
38
|
+
props,
|
|
39
|
+
["class"],
|
|
40
|
+
["variant"]
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<KBadge
|
|
45
|
+
class={badgeStyles({
|
|
46
|
+
variant: variantProps.variant,
|
|
47
|
+
class: local.class,
|
|
48
|
+
})}
|
|
49
|
+
{...others}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Breadcrumbs as KBreadcrumbs } from "@kobalte/core/breadcrumbs";
|
|
2
|
+
import { For, type JSX, splitProps, type ComponentProps } from "solid-js";
|
|
3
|
+
import { tv } from "tailwind-variants";
|
|
4
|
+
import { ChevronRight } from "lucide-solid";
|
|
5
|
+
|
|
6
|
+
// TODO 1. 定义样式
|
|
7
|
+
// 2. icon 支持自定义
|
|
8
|
+
|
|
9
|
+
const breadcrumbStyles = tv(
|
|
10
|
+
{
|
|
11
|
+
slots: {
|
|
12
|
+
root: "flex w-full justify-start items-center gap-2",
|
|
13
|
+
link: "text-md transition-colors text-main data-[current]:text-main/50 data-[disabled]:pointer-events-none no-underline",
|
|
14
|
+
separator: "flex h-4 w-4 items-center justify-center text-main/80",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
twMerge: true,
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const { root, link, separator } = breadcrumbStyles();
|
|
23
|
+
|
|
24
|
+
export interface BreadcrumbItem {
|
|
25
|
+
title: JSX.Element;
|
|
26
|
+
href?: string;
|
|
27
|
+
current?: boolean;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface BreadcrumbsProps extends ComponentProps<typeof KBreadcrumbs> {
|
|
32
|
+
items: BreadcrumbItem[];
|
|
33
|
+
separatorIcon?: JSX.Element;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const Breadcrumbs = (props: BreadcrumbsProps) => {
|
|
37
|
+
const [local, others] = splitProps(props, [
|
|
38
|
+
"items",
|
|
39
|
+
"separatorIcon",
|
|
40
|
+
"class",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<KBreadcrumbs class={root()} {...others}>
|
|
45
|
+
<For each={local.items}>
|
|
46
|
+
{(breadcrumb, index) => (
|
|
47
|
+
<>
|
|
48
|
+
<KBreadcrumbs.Link
|
|
49
|
+
href={breadcrumb.href}
|
|
50
|
+
current={breadcrumb.current}
|
|
51
|
+
disabled={breadcrumb.disabled}
|
|
52
|
+
class={link({ class: local.class })}
|
|
53
|
+
>
|
|
54
|
+
{breadcrumb.title}
|
|
55
|
+
</KBreadcrumbs.Link>
|
|
56
|
+
|
|
57
|
+
{index() < local.items.length - 1 && (
|
|
58
|
+
<span aria-hidden="true" class={separator()}>
|
|
59
|
+
{local.separatorIcon || (
|
|
60
|
+
<ChevronRight size={16} />
|
|
61
|
+
)}
|
|
62
|
+
</span>
|
|
63
|
+
)}
|
|
64
|
+
</>
|
|
65
|
+
)}
|
|
66
|
+
</For>
|
|
67
|
+
</KBreadcrumbs>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { Button as KButton } from "@kobalte/core/button";
|
|
2
|
+
import { splitProps, type JSX, type ComponentProps, Show } from "solid-js";
|
|
3
|
+
import { tv, type VariantProps } from "tailwind-variants";
|
|
4
|
+
import { LoaderCircle } from "lucide-solid";
|
|
5
|
+
|
|
6
|
+
// --- 样式定义保持不变 ---
|
|
7
|
+
const buttonStyles = tv(
|
|
8
|
+
{
|
|
9
|
+
slots: {
|
|
10
|
+
base: "inline-flex items-center cursor-pointer justify-center rounded-sm text-sm font-medium transition-all duration-200 active:scale-[0.98] focus:outline-none disabled:opacity-50 disabled:pointer-events-none",
|
|
11
|
+
icon: "animate-spin -ml-1 mr-2 h-4 w-4 text-current",
|
|
12
|
+
},
|
|
13
|
+
variants: {
|
|
14
|
+
size: {
|
|
15
|
+
sm: { base: "h-6 px-2 text-xs" },
|
|
16
|
+
md: { base: "h-8 px-4 text-sm" },
|
|
17
|
+
lg: { base: "h-10 px-6 text-base" },
|
|
18
|
+
},
|
|
19
|
+
loading: {
|
|
20
|
+
true: {
|
|
21
|
+
base: "pointer-events-none opacity-70",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
variant: {
|
|
25
|
+
default: { base: "bg-main text-reversal" },
|
|
26
|
+
outline: {
|
|
27
|
+
base: "border border-base hover:border-primary hover:text-primary",
|
|
28
|
+
},
|
|
29
|
+
dashed: {
|
|
30
|
+
base: "border border-dashed border-base hover:border-primary hover:text-primary",
|
|
31
|
+
},
|
|
32
|
+
filled: { base: "bg-main/5 hover:bg-main/10" },
|
|
33
|
+
text: { base: "hover:bg-main/10" },
|
|
34
|
+
},
|
|
35
|
+
color: {
|
|
36
|
+
primary: { base: "" },
|
|
37
|
+
success: { base: "" },
|
|
38
|
+
warning: { base: "" },
|
|
39
|
+
danger: { base: "" },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
compoundVariants: [
|
|
43
|
+
{
|
|
44
|
+
variant: "default",
|
|
45
|
+
color: "primary",
|
|
46
|
+
class: {
|
|
47
|
+
base: "bg-primary text-white border-primary hover:bg-primary/70 hover:border-primary/70",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
variant: "default",
|
|
52
|
+
color: "success",
|
|
53
|
+
class: {
|
|
54
|
+
base: "bg-success text-white border-success hover:bg-success/70 hover:border-success/70",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
variant: "default",
|
|
59
|
+
color: "warning",
|
|
60
|
+
class: {
|
|
61
|
+
base: "bg-warning text-white border-warning hover:bg-warning/70 hover:border-warning/70",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
variant: "default",
|
|
66
|
+
color: "danger",
|
|
67
|
+
class: {
|
|
68
|
+
base: "bg-danger text-white border-danger hover:bg-danger/70 hover:border-danger/70",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
// --- outline 和 dashed 类型 ---
|
|
72
|
+
{
|
|
73
|
+
variant: ["outline", "dashed"],
|
|
74
|
+
color: "primary",
|
|
75
|
+
class: {
|
|
76
|
+
base: "text-primary border-primary hover:border-primary/70 hover:text-primary/70",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
variant: ["outline", "dashed"],
|
|
81
|
+
color: "success",
|
|
82
|
+
class: {
|
|
83
|
+
base: "text-success border-success hover:border-success/70 hover:text-success/70",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
variant: ["outline", "dashed"],
|
|
88
|
+
color: "warning",
|
|
89
|
+
class: {
|
|
90
|
+
base: "text-warning border-warning hover:border-warning/70 hover:text-warning/70",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
variant: ["outline", "dashed"],
|
|
95
|
+
color: "danger",
|
|
96
|
+
class: {
|
|
97
|
+
base: "text-danger border-danger hover:border-danger/70 hover:text-danger/70",
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
// --- filled 类型 ---
|
|
102
|
+
{
|
|
103
|
+
variant: "filled",
|
|
104
|
+
color: "primary",
|
|
105
|
+
class: {
|
|
106
|
+
base: "text-primary bg-primary/5 hover:bg-primary/10",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
variant: "filled",
|
|
111
|
+
color: "success",
|
|
112
|
+
class: {
|
|
113
|
+
base: "text-success bg-success/5 hover:bg-success/10",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
variant: "filled",
|
|
118
|
+
color: "warning",
|
|
119
|
+
class: {
|
|
120
|
+
base: "text-warning bg-warning/5 hover:bg-warning/10",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
variant: "filled",
|
|
125
|
+
color: "danger",
|
|
126
|
+
class: {
|
|
127
|
+
base: "text-danger bg-danger/5 hover:bg-danger/10",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
// --- text 类型 ---
|
|
131
|
+
{
|
|
132
|
+
variant: "text",
|
|
133
|
+
color: "primary",
|
|
134
|
+
class: {
|
|
135
|
+
base: "text-primary hover:bg-primary/10",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
variant: "text",
|
|
140
|
+
color: "success",
|
|
141
|
+
class: {
|
|
142
|
+
base: "text-success hover:bg-success/10",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
variant: "text",
|
|
147
|
+
color: "warning",
|
|
148
|
+
class: {
|
|
149
|
+
base: "text-warning hover:bg-warning/10",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
variant: "text",
|
|
154
|
+
color: "danger",
|
|
155
|
+
class: {
|
|
156
|
+
base: "text-danger hover:bg-danger/10",
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
defaultVariants: {
|
|
161
|
+
size: "md",
|
|
162
|
+
variant: "default",
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{ twMerge: true },
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
type ButtonVariants = VariantProps<typeof buttonStyles>;
|
|
169
|
+
|
|
170
|
+
export interface ButtonProps
|
|
171
|
+
extends ComponentProps<typeof KButton>, ButtonVariants {
|
|
172
|
+
loading?: boolean;
|
|
173
|
+
leftIcon?: JSX.Element;
|
|
174
|
+
rightIcon?: JSX.Element;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const Button = (props: ButtonProps) => {
|
|
178
|
+
const [local, variantKeys, others] = splitProps(
|
|
179
|
+
props,
|
|
180
|
+
["class", "children", "loading", "leftIcon", "rightIcon", "disabled"],
|
|
181
|
+
["variant", "size", "color", "loading"],
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// 2. 调用 styles 获取 slots
|
|
185
|
+
// 注意:将 local.class 传入 base 槽位
|
|
186
|
+
const styles = () =>
|
|
187
|
+
buttonStyles({
|
|
188
|
+
...variantKeys,
|
|
189
|
+
class: local.class,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<KButton
|
|
194
|
+
class={styles().base()}
|
|
195
|
+
disabled={local.disabled || local.loading}
|
|
196
|
+
{...others}
|
|
197
|
+
>
|
|
198
|
+
{/* Loading 状态显示 */}
|
|
199
|
+
<Show when={local.loading}>
|
|
200
|
+
<LoaderCircle class={styles().icon()} />
|
|
201
|
+
</Show>
|
|
202
|
+
|
|
203
|
+
{/* 非 Loading 状态下的 Left Icon */}
|
|
204
|
+
<Show when={!local.loading && local.leftIcon}>
|
|
205
|
+
<span class="mr-2 inline-flex">{local.leftIcon}</span>
|
|
206
|
+
</Show>
|
|
207
|
+
|
|
208
|
+
{local.children}
|
|
209
|
+
|
|
210
|
+
{/* Right Icon */}
|
|
211
|
+
<Show when={local.rightIcon}>
|
|
212
|
+
<span class="ml-2 inline-flex">{local.rightIcon}</span>
|
|
213
|
+
</Show>
|
|
214
|
+
</KButton>
|
|
215
|
+
);
|
|
216
|
+
};
|