@echojs-ecosystem/ui 0.1.0
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/README.md +102 -0
- package/dist/button.d.ts +20 -0
- package/dist/button.js +559 -0
- package/dist/button.js.map +1 -0
- package/dist/button.types-B0SrEYRV.d.ts +27 -0
- package/dist/checkbox.d.ts +42 -0
- package/dist/checkbox.js +388 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/cn-rGlUI2mx.d.ts +16 -0
- package/dist/core.d.ts +16 -0
- package/dist/core.js +294 -0
- package/dist/core.js.map +1 -0
- package/dist/field.d.ts +72 -0
- package/dist/field.js +479 -0
- package/dist/field.js.map +1 -0
- package/dist/field.types-BSyhu0Cf.d.ts +24 -0
- package/dist/icon-button.d.ts +15 -0
- package/dist/icon-button.js +606 -0
- package/dist/icon-button.js.map +1 -0
- package/dist/index-BhC5sVej.d.ts +35 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +1604 -0
- package/dist/index.js.map +1 -0
- package/dist/input-mask.d.ts +5 -0
- package/dist/input-mask.js +568 -0
- package/dist/input-mask.js.map +1 -0
- package/dist/input-otp.d.ts +16 -0
- package/dist/input-otp.js +475 -0
- package/dist/input-otp.js.map +1 -0
- package/dist/input-tags.d.ts +16 -0
- package/dist/input-tags.js +549 -0
- package/dist/input-tags.js.map +1 -0
- package/dist/input.d.ts +14 -0
- package/dist/input.js +466 -0
- package/dist/input.js.map +1 -0
- package/dist/label.d.ts +17 -0
- package/dist/label.js +345 -0
- package/dist/label.js.map +1 -0
- package/dist/primitives.d.ts +23 -0
- package/dist/primitives.js +301 -0
- package/dist/primitives.js.map +1 -0
- package/dist/provider.d.ts +19 -0
- package/dist/provider.js +124 -0
- package/dist/provider.js.map +1 -0
- package/dist/slots-D2y1YgGz.d.ts +58 -0
- package/dist/textarea.d.ts +27 -0
- package/dist/textarea.js +361 -0
- package/dist/textarea.js.map +1 -0
- package/dist/theme-context-CyY95LK0.d.ts +39 -0
- package/dist/theme.d.ts +20 -0
- package/dist/theme.js +155 -0
- package/dist/theme.js.map +1 -0
- package/dist/types-CSHcGa_F.d.ts +13 -0
- package/dist/utils.d.ts +36 -0
- package/dist/utils.js +101 -0
- package/dist/utils.js.map +1 -0
- package/dist/variants-Bj38CFcH.d.ts +51 -0
- package/package.json +134 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# @echojs-ecosystem/ui
|
|
4
|
+
|
|
5
|
+
**Accessible UI primitives for HyperDOM — theme, variants, and headless mode.**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@echojs-ecosystem/ui)
|
|
8
|
+
[](https://echojs.dev/docs/packages/ui)
|
|
9
|
+
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Component library for [`@echojs-ecosystem/hyperdom`](https://www.npmjs.com/package/@echojs-ecosystem/hyperdom). No React, no VDOM — real DOM nodes with **ARIA-first** semantics, **Tailwind** styling, and **tailwind-variants**.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Subpath exports** — `@echojs-ecosystem/ui/button` for tree-shaking
|
|
19
|
+
- **`UIProvider`** — global theme, default variants, overrides
|
|
20
|
+
- **Headless mode** — behavior & a11y without visual classes
|
|
21
|
+
- **Components** — Button, Input, Textarea, Field, Checkbox, Label, …
|
|
22
|
+
- **Storybook** — component catalog in `packages/ui`
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @echojs-ecosystem/ui @echojs-ecosystem/hyperdom
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { UIProvider } from "@echojs-ecosystem/ui/provider";
|
|
34
|
+
import { createTheme } from "@echojs-ecosystem/ui/theme";
|
|
35
|
+
import { Button } from "@echojs-ecosystem/ui/button";
|
|
36
|
+
import { h, render } from "@echojs-ecosystem/hyperdom";
|
|
37
|
+
|
|
38
|
+
const theme = createTheme({
|
|
39
|
+
prefix: "echo",
|
|
40
|
+
components: {
|
|
41
|
+
button: { defaultVariants: { variant: "primary", size: "md" } },
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
render(
|
|
46
|
+
UIProvider({
|
|
47
|
+
theme,
|
|
48
|
+
children: () => Button({ children: "Save", variant: "primary" }),
|
|
49
|
+
}),
|
|
50
|
+
document.getElementById("app")!,
|
|
51
|
+
);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Subpath imports
|
|
55
|
+
|
|
56
|
+
| Subpath | Components |
|
|
57
|
+
|---------|------------|
|
|
58
|
+
| `@echojs-ecosystem/ui/button` | Button |
|
|
59
|
+
| `@echojs-ecosystem/ui/input` | Input |
|
|
60
|
+
| `@echojs-ecosystem/ui/textarea` | Textarea |
|
|
61
|
+
| `@echojs-ecosystem/ui/field` | Field, `mergeFieldControlProps` |
|
|
62
|
+
| `@echojs-ecosystem/ui/checkbox` | Checkbox |
|
|
63
|
+
| `@echojs-ecosystem/ui/provider` | UIProvider |
|
|
64
|
+
| `@echojs-ecosystem/ui/theme` | `createTheme`, variants |
|
|
65
|
+
| `@echojs-ecosystem/ui/utils` | `cn`, `mergeProps`, … |
|
|
66
|
+
|
|
67
|
+
## Field + Input pattern
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { Field, mergeFieldControlProps } from "@echojs-ecosystem/ui/field";
|
|
71
|
+
import { Input } from "@echojs-ecosystem/ui/input";
|
|
72
|
+
|
|
73
|
+
Field({
|
|
74
|
+
label: "Email",
|
|
75
|
+
error: emailError.value(),
|
|
76
|
+
children: (ctx) =>
|
|
77
|
+
Input(
|
|
78
|
+
mergeFieldControlProps(ctx.inputProps, {
|
|
79
|
+
value: email.value(),
|
|
80
|
+
onInput: (e) => email.set(e.currentTarget.value),
|
|
81
|
+
}),
|
|
82
|
+
),
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Headless mode
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
UIProvider({ headless: true, children: () => App() });
|
|
90
|
+
Button({ headless: true, children: "Save" }); // a11y only, no theme classes
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Storybook
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
bun run storybook # from packages/ui
|
|
97
|
+
bun run storybook:build # static catalog
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Documentation
|
|
101
|
+
|
|
102
|
+
[echojs.dev/docs/packages/ui](https://echojs.dev/docs/packages/ui)
|
package/dist/button.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Child } from '@echojs-ecosystem/hyperdom';
|
|
2
|
+
import { B as ButtonProps, a as ButtonSize } from './button.types-B0SrEYRV.js';
|
|
3
|
+
export { b as ButtonRadius, c as ButtonVariant } from './button.types-B0SrEYRV.js';
|
|
4
|
+
import { T as TVConfig, V as VariantProps } from './variants-Bj38CFcH.js';
|
|
5
|
+
import './types-CSHcGa_F.js';
|
|
6
|
+
import './cn-rGlUI2mx.js';
|
|
7
|
+
|
|
8
|
+
declare const Button: (props: ButtonProps) => Child;
|
|
9
|
+
|
|
10
|
+
/** Default spinner for {@link Button} pending state (`data-slot="spinner"`). */
|
|
11
|
+
declare const defaultButtonSpinner: (size?: ButtonSize) => Child;
|
|
12
|
+
|
|
13
|
+
/** HeroUI v3–inspired button styles (Tailwind + CSS variables). */
|
|
14
|
+
declare const buttonStyles: ((options?: Record<string, unknown>) => string) & {
|
|
15
|
+
_config: TVConfig;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type ButtonVariantProps = VariantProps<typeof buttonStyles>;
|
|
19
|
+
|
|
20
|
+
export { Button, ButtonProps, ButtonSize, type ButtonVariantProps, buttonStyles, defaultButtonSpinner };
|
package/dist/button.js
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
import { h } from '@echojs-ecosystem/hyperdom';
|
|
2
|
+
|
|
3
|
+
// src/components/button/button.ts
|
|
4
|
+
|
|
5
|
+
// src/theme/default-theme.ts
|
|
6
|
+
var defaultTheme = {
|
|
7
|
+
prefix: "echo",
|
|
8
|
+
headless: false,
|
|
9
|
+
components: {
|
|
10
|
+
button: {
|
|
11
|
+
defaultVariants: {
|
|
12
|
+
variant: "primary",
|
|
13
|
+
size: "md",
|
|
14
|
+
radius: "full"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
input: {
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
variant: "outline",
|
|
20
|
+
size: "md"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
inputMask: {
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
variant: "outline",
|
|
26
|
+
size: "md"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
inputOtp: {
|
|
30
|
+
defaultVariants: {
|
|
31
|
+
variant: "outline",
|
|
32
|
+
size: "md"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
inputTags: {
|
|
36
|
+
defaultVariants: {
|
|
37
|
+
variant: "outline",
|
|
38
|
+
size: "md"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
textarea: {
|
|
42
|
+
defaultVariants: {
|
|
43
|
+
variant: "outline",
|
|
44
|
+
size: "md",
|
|
45
|
+
resize: "vertical"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
checkbox: {
|
|
49
|
+
defaultVariants: {
|
|
50
|
+
size: "md"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
iconButton: {
|
|
54
|
+
defaultVariants: {
|
|
55
|
+
variant: "ghost",
|
|
56
|
+
size: "md"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/theme/create-theme.ts
|
|
63
|
+
var mergeComponentConfig = (base, next) => {
|
|
64
|
+
if (!base && !next) return void 0;
|
|
65
|
+
if (!base) return next ? { ...next } : void 0;
|
|
66
|
+
if (!next) return { ...base };
|
|
67
|
+
return {
|
|
68
|
+
baseClass: next.baseClass ?? base.baseClass,
|
|
69
|
+
className: next.className ?? base.className,
|
|
70
|
+
defaultProps: { ...base.defaultProps, ...next.defaultProps },
|
|
71
|
+
defaultVariants: { ...base.defaultVariants, ...next.defaultVariants },
|
|
72
|
+
variants: { ...base.variants, ...next.variants }
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
var mergeComponents = (base, next) => {
|
|
76
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(next ?? {})]);
|
|
77
|
+
const merged = {};
|
|
78
|
+
for (const key of keys) {
|
|
79
|
+
const slice = mergeComponentConfig(base?.[key], next?.[key]);
|
|
80
|
+
if (slice) merged[key] = slice;
|
|
81
|
+
}
|
|
82
|
+
return merged;
|
|
83
|
+
};
|
|
84
|
+
var createTheme = (input, base = defaultTheme) => ({
|
|
85
|
+
prefix: input.prefix ?? base.prefix,
|
|
86
|
+
headless: input.headless ?? base.headless,
|
|
87
|
+
components: mergeComponents(base.components, input.components)
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// src/theme/theme-context.ts
|
|
91
|
+
var stack = [];
|
|
92
|
+
var getUIContext = () => stack[stack.length - 1];
|
|
93
|
+
var getUIContextOrDefault = () => getUIContext() ?? createUIContextValue({});
|
|
94
|
+
var createUIContextValue = (input, parent = getUIContext()) => {
|
|
95
|
+
const parentTheme = parent?.theme ?? defaultTheme;
|
|
96
|
+
const theme = createTheme(input.theme ?? {}, parentTheme);
|
|
97
|
+
const headless = input.headless ?? theme.headless ?? parent?.headless ?? false;
|
|
98
|
+
return {
|
|
99
|
+
theme: { ...theme, headless },
|
|
100
|
+
headless
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// src/theme/variants.ts
|
|
105
|
+
var tv = (config) => {
|
|
106
|
+
const resolve = (options) => {
|
|
107
|
+
const parts = [];
|
|
108
|
+
if (config.base) parts.push(config.base);
|
|
109
|
+
const resolved = { ...config.defaultVariants ?? {}, ...options ?? {} };
|
|
110
|
+
const variants = config.variants ?? {};
|
|
111
|
+
for (const [variantName, map] of Object.entries(variants)) {
|
|
112
|
+
const value = resolved[variantName];
|
|
113
|
+
if (value === void 0) continue;
|
|
114
|
+
const key = typeof value === "boolean" ? value ? "true" : "false" : String(value);
|
|
115
|
+
const cls = map[key];
|
|
116
|
+
if (cls) parts.push(cls);
|
|
117
|
+
}
|
|
118
|
+
for (const compound of config.compoundVariants ?? []) {
|
|
119
|
+
const { class: compoundClass, ...conditions } = compound;
|
|
120
|
+
const matches = Object.entries(conditions).every(([name, value]) => resolved[name] === value);
|
|
121
|
+
if (matches && compoundClass) parts.push(compoundClass);
|
|
122
|
+
}
|
|
123
|
+
return parts.filter(Boolean).join(" ");
|
|
124
|
+
};
|
|
125
|
+
if (config.slots) {
|
|
126
|
+
const slotFns = {};
|
|
127
|
+
for (const [slot, slotBase] of Object.entries(config.slots)) {
|
|
128
|
+
slotFns[slot] = (slotOptions) => {
|
|
129
|
+
const composed = [slotBase, resolve(slotOptions)].filter(Boolean).join(" ");
|
|
130
|
+
return composed.trim();
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
slotFns._config = config;
|
|
134
|
+
return slotFns;
|
|
135
|
+
}
|
|
136
|
+
const fn = (options) => resolve(options);
|
|
137
|
+
fn._config = config;
|
|
138
|
+
return fn;
|
|
139
|
+
};
|
|
140
|
+
var resolveVariantClasses = (variantFn, options, headless) => {
|
|
141
|
+
if (headless || !variantFn) return void 0;
|
|
142
|
+
return variantFn(options);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// src/core/variant-keys.ts
|
|
146
|
+
var getVariantKeysFromFn = (variantsFn) => {
|
|
147
|
+
const variants = variantsFn?._config?.variants;
|
|
148
|
+
if (!variants) return [];
|
|
149
|
+
return Object.keys(variants);
|
|
150
|
+
};
|
|
151
|
+
var resolveVariantOptions = (variantsFn, themeDefaults, props) => {
|
|
152
|
+
const keys = [
|
|
153
|
+
.../* @__PURE__ */ new Set([
|
|
154
|
+
...getVariantKeysFromFn(variantsFn),
|
|
155
|
+
...Object.keys(variantsFn?._config?.defaultVariants ?? {}),
|
|
156
|
+
...Object.keys(themeDefaults ?? {})
|
|
157
|
+
])
|
|
158
|
+
];
|
|
159
|
+
const picked = {};
|
|
160
|
+
for (const key of keys) {
|
|
161
|
+
if (props[key] !== void 0) picked[key] = props[key];
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
...variantsFn?._config?.defaultVariants,
|
|
165
|
+
...themeDefaults,
|
|
166
|
+
...picked
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// src/utils/cn.ts
|
|
171
|
+
var defaultClassNameMerger = (...values) => {
|
|
172
|
+
const out = [];
|
|
173
|
+
const walk = (value) => {
|
|
174
|
+
if (value == null || value === false) return;
|
|
175
|
+
if (typeof value === "string") {
|
|
176
|
+
if (value) out.push(value);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (Array.isArray(value)) {
|
|
180
|
+
for (const item of value) walk(item);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
for (const [key, enabled] of Object.entries(value)) {
|
|
184
|
+
if (enabled) out.push(key);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
for (const value of values) walk(value);
|
|
188
|
+
return out.join(" ");
|
|
189
|
+
};
|
|
190
|
+
var classNameMerger = defaultClassNameMerger;
|
|
191
|
+
var cn = (...values) => classNameMerger(...values);
|
|
192
|
+
|
|
193
|
+
// src/utils/compose-event-handlers.ts
|
|
194
|
+
var composeEventHandlers = (userHandler, internalHandler, options = {}) => {
|
|
195
|
+
if (!userHandler && !internalHandler) return void 0;
|
|
196
|
+
if (!userHandler) return internalHandler;
|
|
197
|
+
if (!internalHandler) return userHandler;
|
|
198
|
+
const { checkDefaultPrevented = false } = options;
|
|
199
|
+
return (event) => {
|
|
200
|
+
userHandler(event);
|
|
201
|
+
if (checkDefaultPrevented && event?.defaultPrevented) return;
|
|
202
|
+
internalHandler(event);
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// src/utils/merge-props.ts
|
|
207
|
+
var CLASS_KEYS = /* @__PURE__ */ new Set(["class", "className"]);
|
|
208
|
+
var isEventHandlerKey = (key) => key.startsWith("on") || key.startsWith("on:");
|
|
209
|
+
var mergeClassValues = (...values) => {
|
|
210
|
+
const merged = cn(...values);
|
|
211
|
+
return merged || void 0;
|
|
212
|
+
};
|
|
213
|
+
var mergePair = (target, source) => {
|
|
214
|
+
if (!source) return;
|
|
215
|
+
for (const [key, value] of Object.entries(source)) {
|
|
216
|
+
if (value === void 0) continue;
|
|
217
|
+
if (CLASS_KEYS.has(key)) {
|
|
218
|
+
const mergedClass = mergeClassValues(target.className, value);
|
|
219
|
+
if (mergedClass !== void 0) {
|
|
220
|
+
target.className = mergedClass;
|
|
221
|
+
target.class = mergedClass;
|
|
222
|
+
}
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (isEventHandlerKey(key)) {
|
|
226
|
+
const existing = target[key];
|
|
227
|
+
target[key] = composeEventHandlers(
|
|
228
|
+
value,
|
|
229
|
+
existing,
|
|
230
|
+
{ checkDefaultPrevented: true }
|
|
231
|
+
);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
target[key] = value;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
var mergeProps = (defaultProps, providerProps, componentProps) => {
|
|
238
|
+
const merged = {};
|
|
239
|
+
mergePair(merged, defaultProps);
|
|
240
|
+
mergePair(merged, providerProps);
|
|
241
|
+
mergePair(merged, componentProps);
|
|
242
|
+
return merged;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// src/core/component.ts
|
|
246
|
+
var themeKeyFromName = (name) => name.charAt(0).toLowerCase() + name.slice(1);
|
|
247
|
+
var createUIComponent = (config) => {
|
|
248
|
+
const themeKey = themeKeyFromName(config.name);
|
|
249
|
+
return (props) => {
|
|
250
|
+
const ctx = getUIContextOrDefault();
|
|
251
|
+
const themeSlice = ctx.theme.components?.[themeKey];
|
|
252
|
+
const headless = props.headless ?? ctx.headless ?? ctx.theme.headless ?? false;
|
|
253
|
+
const providerProps = {
|
|
254
|
+
...themeSlice?.defaultProps,
|
|
255
|
+
...themeSlice?.defaultVariants
|
|
256
|
+
};
|
|
257
|
+
if (themeSlice?.className) {
|
|
258
|
+
providerProps.className = themeSlice.className;
|
|
259
|
+
}
|
|
260
|
+
const merged = mergeProps(
|
|
261
|
+
config.defaultProps,
|
|
262
|
+
providerProps,
|
|
263
|
+
props
|
|
264
|
+
);
|
|
265
|
+
const variantOptions = resolveVariantOptions(
|
|
266
|
+
config.variants,
|
|
267
|
+
themeSlice?.defaultVariants,
|
|
268
|
+
merged
|
|
269
|
+
);
|
|
270
|
+
const variantClass = resolveVariantClasses(config.variants, variantOptions, headless);
|
|
271
|
+
const visualClass = headless ? void 0 : cn(themeSlice?.baseClass, themeSlice?.className, variantClass, merged.className, merged.class);
|
|
272
|
+
const finalProps = {
|
|
273
|
+
...merged,
|
|
274
|
+
className: visualClass,
|
|
275
|
+
class: visualClass
|
|
276
|
+
};
|
|
277
|
+
if (config.render) {
|
|
278
|
+
return config.render({
|
|
279
|
+
props: finalProps,
|
|
280
|
+
headless,
|
|
281
|
+
className: visualClass,
|
|
282
|
+
ctx
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
const { children, as, ...rest } = finalProps;
|
|
286
|
+
const tag = as ?? config.defaultTag;
|
|
287
|
+
return h(tag, rest, children);
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// src/utils/data-attributes.ts
|
|
292
|
+
var dataDisabled = (disabled) => disabled ? { "data-disabled": "" } : {};
|
|
293
|
+
var dataPending = (pending) => pending ? { "data-pending": "" } : {};
|
|
294
|
+
var spinnerSizeClass = {
|
|
295
|
+
xs: "size-3.5 border",
|
|
296
|
+
sm: "size-4 border",
|
|
297
|
+
md: "size-4 border-2",
|
|
298
|
+
lg: "size-5 border-2",
|
|
299
|
+
xl: "size-5 border-2"
|
|
300
|
+
};
|
|
301
|
+
var defaultButtonSpinner = (size = "md") => h(
|
|
302
|
+
"span",
|
|
303
|
+
{
|
|
304
|
+
"data-slot": "spinner",
|
|
305
|
+
"data-btn-icon": "",
|
|
306
|
+
className: [
|
|
307
|
+
"inline-block shrink-0 animate-spin rounded-full border-current border-t-transparent",
|
|
308
|
+
spinnerSizeClass[size]
|
|
309
|
+
].join(" "),
|
|
310
|
+
"aria-hidden": "true"
|
|
311
|
+
}
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
// src/components/button/button.styles.ts
|
|
315
|
+
var buttonStyles = tv({
|
|
316
|
+
base: [
|
|
317
|
+
"relative isolate inline-flex w-fit origin-center items-center justify-center gap-2",
|
|
318
|
+
"font-medium whitespace-nowrap select-none outline-none",
|
|
319
|
+
"rounded-3xl",
|
|
320
|
+
"transition-[transform,background-color,box-shadow,color] duration-200 ease-out",
|
|
321
|
+
"motion-reduce:transition-none",
|
|
322
|
+
"cursor-pointer",
|
|
323
|
+
"bg-[var(--button-bg)] text-[var(--button-fg)]",
|
|
324
|
+
"hover:bg-[var(--button-bg-hover)]",
|
|
325
|
+
"active:scale-[0.97] active:bg-[var(--button-bg-pressed)]",
|
|
326
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-zinc-400",
|
|
327
|
+
"disabled:opacity-50 disabled:pointer-events-none",
|
|
328
|
+
"data-[disabled]:opacity-50 data-[disabled]:pointer-events-none",
|
|
329
|
+
"data-[pending]:pointer-events-none data-[pending]:cursor-wait",
|
|
330
|
+
"[&_svg:not([data-slot=spinner]_svg)]:pointer-events-none [&_svg:not([data-slot=spinner]_svg)]:shrink-0",
|
|
331
|
+
"[&_[data-btn-icon]]:inline-flex [&_[data-btn-icon]]:shrink-0"
|
|
332
|
+
].join(" "),
|
|
333
|
+
variants: {
|
|
334
|
+
variant: {
|
|
335
|
+
primary: [
|
|
336
|
+
"[--button-bg:var(--color-accent)]",
|
|
337
|
+
"[--button-bg-hover:var(--color-accent-hover)]",
|
|
338
|
+
"[--button-bg-pressed:var(--color-accent-hover)]",
|
|
339
|
+
"[--button-fg:var(--color-accent-foreground)]",
|
|
340
|
+
"focus-visible:ring-zinc-900"
|
|
341
|
+
].join(" "),
|
|
342
|
+
secondary: [
|
|
343
|
+
"[--button-bg:var(--color-default)]",
|
|
344
|
+
"[--button-bg-hover:var(--color-default-hover)]",
|
|
345
|
+
"[--button-bg-pressed:var(--color-default-hover)]",
|
|
346
|
+
"[--button-fg:var(--color-default-foreground)]"
|
|
347
|
+
].join(" "),
|
|
348
|
+
tertiary: [
|
|
349
|
+
"[--button-bg:var(--color-default)]",
|
|
350
|
+
"[--button-bg-hover:var(--color-default-hover)]",
|
|
351
|
+
"[--button-bg-pressed:var(--color-default-hover)]",
|
|
352
|
+
"[--button-fg:var(--color-zinc-700)]"
|
|
353
|
+
].join(" "),
|
|
354
|
+
outline: [
|
|
355
|
+
"border border-[var(--color-border)]",
|
|
356
|
+
"[--button-bg:transparent]",
|
|
357
|
+
"[--button-bg-hover:color-mix(in_srgb,var(--color-default)_60%,transparent)]",
|
|
358
|
+
"[--button-bg-pressed:var(--color-default)]",
|
|
359
|
+
"[--button-fg:var(--color-default-foreground)]"
|
|
360
|
+
].join(" "),
|
|
361
|
+
ghost: [
|
|
362
|
+
"[--button-bg:transparent]",
|
|
363
|
+
"[--button-bg-hover:var(--color-default)]",
|
|
364
|
+
"[--button-bg-pressed:var(--color-default)]",
|
|
365
|
+
"[--button-fg:var(--color-default-foreground)]"
|
|
366
|
+
].join(" "),
|
|
367
|
+
danger: [
|
|
368
|
+
"[--button-bg:var(--color-danger)]",
|
|
369
|
+
"[--button-bg-hover:var(--color-danger-hover)]",
|
|
370
|
+
"[--button-bg-pressed:var(--color-danger-hover)]",
|
|
371
|
+
"[--button-fg:var(--color-danger-foreground)]",
|
|
372
|
+
"focus-visible:ring-red-600"
|
|
373
|
+
].join(" "),
|
|
374
|
+
dangerSoft: [
|
|
375
|
+
"[--button-bg:var(--color-danger-soft)]",
|
|
376
|
+
"[--button-bg-hover:var(--color-danger-soft-hover)]",
|
|
377
|
+
"[--button-bg-pressed:var(--color-danger-soft-hover)]",
|
|
378
|
+
"[--button-fg:var(--color-danger-soft-foreground)]",
|
|
379
|
+
"focus-visible:ring-red-400"
|
|
380
|
+
].join(" "),
|
|
381
|
+
/** @deprecated Use `danger`. */
|
|
382
|
+
destructive: [
|
|
383
|
+
"[--button-bg:var(--color-danger)]",
|
|
384
|
+
"[--button-bg-hover:var(--color-danger-hover)]",
|
|
385
|
+
"[--button-bg-pressed:var(--color-danger-hover)]",
|
|
386
|
+
"[--button-fg:var(--color-danger-foreground)]",
|
|
387
|
+
"focus-visible:ring-red-600"
|
|
388
|
+
].join(" "),
|
|
389
|
+
link: [
|
|
390
|
+
"h-auto min-h-0 w-auto gap-1 rounded-none px-0",
|
|
391
|
+
"[--button-bg:transparent] [--button-bg-hover:transparent] [--button-bg-pressed:transparent]",
|
|
392
|
+
"[--button-fg:var(--color-zinc-900)]",
|
|
393
|
+
"underline-offset-4 hover:underline active:scale-100"
|
|
394
|
+
].join(" ")
|
|
395
|
+
},
|
|
396
|
+
size: {
|
|
397
|
+
xs: "h-8 gap-1.5 px-2.5 text-xs md:h-7 [&_[data-btn-icon]]:size-3.5 active:scale-[0.98]",
|
|
398
|
+
sm: "h-9 gap-2 px-3 text-sm md:h-8 [&_[data-btn-icon]]:size-4 active:scale-[0.98]",
|
|
399
|
+
md: "h-10 gap-2 px-4 text-sm md:h-9 [&_[data-btn-icon]]:size-4",
|
|
400
|
+
lg: "h-11 gap-2 px-4 text-base md:h-10 [&_[data-btn-icon]]:size-5 active:scale-[0.96]",
|
|
401
|
+
xl: "h-12 gap-2.5 px-5 text-base [&_[data-btn-icon]]:size-5 active:scale-[0.96]"
|
|
402
|
+
},
|
|
403
|
+
radius: {
|
|
404
|
+
none: "!rounded-none",
|
|
405
|
+
sm: "!rounded-sm",
|
|
406
|
+
md: "!rounded-md",
|
|
407
|
+
lg: "!rounded-lg",
|
|
408
|
+
full: "!rounded-full"
|
|
409
|
+
},
|
|
410
|
+
fullWidth: {
|
|
411
|
+
true: "w-full",
|
|
412
|
+
false: ""
|
|
413
|
+
},
|
|
414
|
+
iconOnly: {
|
|
415
|
+
true: "aspect-square p-0 [&_[data-btn-icon]]:mx-0",
|
|
416
|
+
false: ""
|
|
417
|
+
},
|
|
418
|
+
pending: {
|
|
419
|
+
true: "",
|
|
420
|
+
false: ""
|
|
421
|
+
},
|
|
422
|
+
/** @deprecated Alias for `pending` (variant key resolution). */
|
|
423
|
+
loading: {
|
|
424
|
+
true: "",
|
|
425
|
+
false: ""
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
compoundVariants: [
|
|
429
|
+
{ iconOnly: true, size: "xs", class: "size-8 md:size-7" },
|
|
430
|
+
{ iconOnly: true, size: "sm", class: "size-9 md:size-8" },
|
|
431
|
+
{ iconOnly: true, size: "md", class: "size-10 md:size-9" },
|
|
432
|
+
{ iconOnly: true, size: "lg", class: "size-11 md:size-10" },
|
|
433
|
+
{ iconOnly: true, size: "xl", class: "size-12" },
|
|
434
|
+
{ variant: "link", iconOnly: true, class: "size-auto aspect-auto p-0" }
|
|
435
|
+
],
|
|
436
|
+
defaultVariants: {
|
|
437
|
+
variant: "primary",
|
|
438
|
+
size: "md",
|
|
439
|
+
radius: "full",
|
|
440
|
+
fullWidth: false,
|
|
441
|
+
iconOnly: false,
|
|
442
|
+
pending: false
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// src/components/button/button.ts
|
|
447
|
+
var INTERNAL_KEYS = /* @__PURE__ */ new Set([
|
|
448
|
+
"variant",
|
|
449
|
+
"size",
|
|
450
|
+
"radius",
|
|
451
|
+
"fullWidth",
|
|
452
|
+
"iconOnly",
|
|
453
|
+
"pending",
|
|
454
|
+
"loading",
|
|
455
|
+
"disabled",
|
|
456
|
+
"leftIcon",
|
|
457
|
+
"rightIcon",
|
|
458
|
+
"spinner",
|
|
459
|
+
"headless",
|
|
460
|
+
"children",
|
|
461
|
+
"onClick"
|
|
462
|
+
]);
|
|
463
|
+
var pickDomProps = (props) => {
|
|
464
|
+
const out = {};
|
|
465
|
+
for (const [key, value] of Object.entries(props)) {
|
|
466
|
+
if (!INTERNAL_KEYS.has(key)) out[key] = value;
|
|
467
|
+
}
|
|
468
|
+
return out;
|
|
469
|
+
};
|
|
470
|
+
var wrapIcon = (node) => h(
|
|
471
|
+
"span",
|
|
472
|
+
{ "data-btn-icon": "", className: "inline-flex shrink-0", "aria-hidden": "true" },
|
|
473
|
+
node
|
|
474
|
+
);
|
|
475
|
+
var buildContent = (options) => {
|
|
476
|
+
const { pending, size, leftIcon, rightIcon, spinner, children } = options;
|
|
477
|
+
const parts = [];
|
|
478
|
+
if (pending) {
|
|
479
|
+
parts.push(spinner ?? defaultButtonSpinner(size));
|
|
480
|
+
} else if (leftIcon) {
|
|
481
|
+
parts.push(wrapIcon(leftIcon));
|
|
482
|
+
}
|
|
483
|
+
if (children !== void 0 && children !== null && children !== false) {
|
|
484
|
+
parts.push(children);
|
|
485
|
+
}
|
|
486
|
+
if (!pending && rightIcon) {
|
|
487
|
+
parts.push(wrapIcon(rightIcon));
|
|
488
|
+
}
|
|
489
|
+
if (parts.length === 1) return parts[0];
|
|
490
|
+
return parts;
|
|
491
|
+
};
|
|
492
|
+
var Button = createUIComponent({
|
|
493
|
+
name: "Button",
|
|
494
|
+
defaultTag: "button",
|
|
495
|
+
defaultProps: {
|
|
496
|
+
variant: "primary",
|
|
497
|
+
size: "md",
|
|
498
|
+
radius: "full",
|
|
499
|
+
type: "button",
|
|
500
|
+
fullWidth: false,
|
|
501
|
+
iconOnly: false,
|
|
502
|
+
pending: false,
|
|
503
|
+
loading: false,
|
|
504
|
+
disabled: false
|
|
505
|
+
},
|
|
506
|
+
variants: buttonStyles,
|
|
507
|
+
render: ({ props, className, headless }) => {
|
|
508
|
+
const {
|
|
509
|
+
pending: pendingProp,
|
|
510
|
+
loading: loadingProp = false,
|
|
511
|
+
disabled: disabledProp = false,
|
|
512
|
+
type,
|
|
513
|
+
leftIcon,
|
|
514
|
+
rightIcon,
|
|
515
|
+
spinner,
|
|
516
|
+
children,
|
|
517
|
+
size = "md",
|
|
518
|
+
onClick,
|
|
519
|
+
...rest
|
|
520
|
+
} = props;
|
|
521
|
+
const pending = Boolean(pendingProp || loadingProp);
|
|
522
|
+
const inactive = Boolean(disabledProp || pending);
|
|
523
|
+
const content = buildContent({
|
|
524
|
+
pending,
|
|
525
|
+
size,
|
|
526
|
+
leftIcon,
|
|
527
|
+
rightIcon,
|
|
528
|
+
spinner,
|
|
529
|
+
children
|
|
530
|
+
});
|
|
531
|
+
const domProps = pickDomProps(rest);
|
|
532
|
+
const visualClass = headless ? void 0 : className;
|
|
533
|
+
const handleClick = composeEventHandlers(
|
|
534
|
+
onClick,
|
|
535
|
+
inactive ? (event) => event.preventDefault() : void 0,
|
|
536
|
+
{ checkDefaultPrevented: true }
|
|
537
|
+
);
|
|
538
|
+
return h(
|
|
539
|
+
"button",
|
|
540
|
+
{
|
|
541
|
+
...domProps,
|
|
542
|
+
type: type ?? "button",
|
|
543
|
+
disabled: inactive,
|
|
544
|
+
className: visualClass,
|
|
545
|
+
class: visualClass,
|
|
546
|
+
...dataDisabled(inactive),
|
|
547
|
+
...dataPending(pending),
|
|
548
|
+
"aria-disabled": inactive ? "true" : void 0,
|
|
549
|
+
"aria-busy": pending ? "true" : void 0,
|
|
550
|
+
onClick: inactive ? handleClick : onClick
|
|
551
|
+
},
|
|
552
|
+
content
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
export { Button, buttonStyles, defaultButtonSpinner };
|
|
558
|
+
//# sourceMappingURL=button.js.map
|
|
559
|
+
//# sourceMappingURL=button.js.map
|