@hyperpackai/hyperui 0.2.0 → 0.3.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 +13 -0
- package/dist/components/Accordion/index.d.ts +6 -0
- package/dist/components/Accordion/index.d.ts.map +1 -1
- package/dist/components/Accordion/index.js +65 -9
- package/dist/components/Autocomplete/index.d.ts +12 -2
- package/dist/components/Autocomplete/index.d.ts.map +1 -1
- package/dist/components/Autocomplete/index.js +148 -24
- package/dist/components/Backdrop/index.d.ts +2 -1
- package/dist/components/Backdrop/index.d.ts.map +1 -1
- package/dist/components/Backdrop/index.js +6 -3
- package/dist/components/Checkbox/index.d.ts +1 -0
- package/dist/components/Checkbox/index.d.ts.map +1 -1
- package/dist/components/Checkbox/index.js +6 -2
- package/dist/components/DashboardLayout/index.d.ts +13 -0
- package/dist/components/DashboardLayout/index.d.ts.map +1 -1
- package/dist/components/DashboardLayout/index.js +50 -7
- package/dist/components/DataTable/index.d.ts +43 -0
- package/dist/components/DataTable/index.d.ts.map +1 -1
- package/dist/components/DataTable/index.js +126 -21
- package/dist/components/Dialog/index.d.ts +9 -3
- package/dist/components/Dialog/index.d.ts.map +1 -1
- package/dist/components/Dialog/index.js +46 -30
- package/dist/components/Drawer/index.d.ts +11 -3
- package/dist/components/Drawer/index.d.ts.map +1 -1
- package/dist/components/Drawer/index.js +66 -11
- package/dist/components/DropdownMenu/index.d.ts +5 -3
- package/dist/components/DropdownMenu/index.d.ts.map +1 -1
- package/dist/components/DropdownMenu/index.js +56 -13
- package/dist/components/FocusTrap/index.d.ts.map +1 -1
- package/dist/components/FocusTrap/index.js +34 -32
- package/dist/components/Input/index.d.ts +2 -0
- package/dist/components/Input/index.d.ts.map +1 -1
- package/dist/components/Input/index.js +18 -4
- package/dist/components/Menu/index.d.ts +6 -2
- package/dist/components/Menu/index.d.ts.map +1 -1
- package/dist/components/Menu/index.js +50 -15
- package/dist/components/Modal/index.d.ts +3 -1
- package/dist/components/Modal/index.d.ts.map +1 -1
- package/dist/components/Modal/index.js +27 -9
- package/dist/components/NestedNavbar/index.d.ts +33 -0
- package/dist/components/NestedNavbar/index.d.ts.map +1 -0
- package/dist/components/NestedNavbar/index.js +435 -0
- package/dist/components/NestedSidebar/index.d.ts +48 -0
- package/dist/components/NestedSidebar/index.d.ts.map +1 -0
- package/dist/components/NestedSidebar/index.js +368 -0
- package/dist/components/Popover/index.d.ts +11 -3
- package/dist/components/Popover/index.d.ts.map +1 -1
- package/dist/components/Popover/index.js +45 -9
- package/dist/components/Radio/index.d.ts +26 -1
- package/dist/components/Radio/index.d.ts.map +1 -1
- package/dist/components/Radio/index.js +61 -2
- package/dist/components/Select/index.d.ts +5 -0
- package/dist/components/Select/index.d.ts.map +1 -1
- package/dist/components/Select/index.js +22 -5
- package/dist/components/Sheet/index.d.ts +9 -3
- package/dist/components/Sheet/index.d.ts.map +1 -1
- package/dist/components/Sheet/index.js +48 -23
- package/dist/components/Sidebar/index.d.ts +20 -1
- package/dist/components/Sidebar/index.d.ts.map +1 -1
- package/dist/components/Sidebar/index.js +285 -8
- package/dist/components/SpeedDial/index.d.ts +10 -0
- package/dist/components/SpeedDial/index.d.ts.map +1 -1
- package/dist/components/SpeedDial/index.js +61 -11
- package/dist/components/Switch/index.d.ts +2 -0
- package/dist/components/Switch/index.d.ts.map +1 -1
- package/dist/components/Switch/index.js +6 -2
- package/dist/components/Tabs/index.d.ts +3 -0
- package/dist/components/Tabs/index.d.ts.map +1 -1
- package/dist/components/Tabs/index.js +47 -8
- package/dist/components/TextField/index.d.ts +2 -0
- package/dist/components/TextField/index.d.ts.map +1 -1
- package/dist/components/TextField/index.js +12 -4
- package/dist/components/Textarea/index.d.ts +5 -0
- package/dist/components/Textarea/index.d.ts.map +1 -1
- package/dist/components/Textarea/index.js +21 -4
- package/dist/components/Transition/index.d.ts +14 -0
- package/dist/components/Transition/index.d.ts.map +1 -0
- package/dist/components/Transition/index.js +49 -0
- package/dist/components/TransitionGroup/index.d.ts +16 -0
- package/dist/components/TransitionGroup/index.d.ts.map +1 -0
- package/dist/components/TransitionGroup/index.js +95 -0
- package/dist/components/data.d.ts +81 -16
- package/dist/components/data.d.ts.map +1 -1
- package/dist/components/data.js +163 -31
- package/dist/components/enterprise.d.ts +85 -26
- package/dist/components/enterprise.d.ts.map +1 -1
- package/dist/components/enterprise.js +211 -36
- package/dist/components/index.d.ts +21 -13
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +7 -2
- package/dist/portal.d.ts.map +1 -1
- package/dist/portal.js +3 -0
- package/dist/theme/index.d.ts +5 -6
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +30 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +11 -0
- package/package.json +6 -1
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { signal } from "@hyperpackai/hyperion";
|
|
2
2
|
import { injectCSS, cn, h } from "../../theme/index.js";
|
|
3
3
|
const CSS = `
|
|
4
|
-
.hu-speed-dial {
|
|
4
|
+
.hu-speed-dial { z-index: 50; display: flex; flex-direction: column-reverse; align-items: center; gap: var(--hu-space-2); }
|
|
5
|
+
.hu-speed-dial--fixed { position: fixed; }
|
|
6
|
+
.hu-speed-dial--absolute { position: absolute; }
|
|
7
|
+
.hu-speed-dial--static { position: static; }
|
|
5
8
|
.hu-speed-dial--bottom-right { bottom: var(--hu-space-6); right: var(--hu-space-6); }
|
|
6
9
|
.hu-speed-dial--bottom-left { bottom: var(--hu-space-6); left: var(--hu-space-6); }
|
|
7
10
|
.hu-speed-dial--top-right { top: var(--hu-space-6); right: var(--hu-space-6); flex-direction: column; }
|
|
8
11
|
.hu-speed-dial--top-left { top: var(--hu-space-6); left: var(--hu-space-6); flex-direction: column; }
|
|
12
|
+
.hu-speed-dial--static.hu-speed-dial--bottom-right,
|
|
13
|
+
.hu-speed-dial--static.hu-speed-dial--bottom-left,
|
|
14
|
+
.hu-speed-dial--static.hu-speed-dial--top-right,
|
|
15
|
+
.hu-speed-dial--static.hu-speed-dial--top-left {
|
|
16
|
+
inset: auto;
|
|
17
|
+
}
|
|
9
18
|
|
|
10
19
|
.hu-speed-dial__fab {
|
|
11
20
|
width: 56px; height: 56px; border-radius: 50%; border: none; cursor: pointer;
|
|
@@ -19,6 +28,8 @@ const CSS = `
|
|
|
19
28
|
.hu-speed-dial__fab--open { transform: rotate(45deg); }
|
|
20
29
|
|
|
21
30
|
.hu-speed-dial__actions { display: flex; flex-direction: column-reverse; gap: var(--hu-space-2); align-items: center; }
|
|
31
|
+
.hu-speed-dial__actions[hidden],
|
|
32
|
+
.hu-speed-dial__backdrop[hidden] { display: none; }
|
|
22
33
|
.hu-speed-dial--top-right .hu-speed-dial__actions,
|
|
23
34
|
.hu-speed-dial--top-left .hu-speed-dial__actions { flex-direction: column; }
|
|
24
35
|
|
|
@@ -50,22 +61,61 @@ const CSS = `
|
|
|
50
61
|
`;
|
|
51
62
|
export function SpeedDial(props) {
|
|
52
63
|
injectCSS("hu-speed-dial", CSS);
|
|
53
|
-
const { actions, direction = "bottom-right", backdrop, ariaLabel } = props;
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
64
|
+
const { actions, direction = "bottom-right", position = "fixed", backdrop, closeOnBackdrop = true, closeOnEscape = true, ariaLabel } = props;
|
|
65
|
+
const uncontrolledOpen = signal(props.defaultOpen ?? false);
|
|
66
|
+
const isSignalOpen = typeof props.open === "object" && props.open != null && "peek" in props.open;
|
|
67
|
+
const isControlled = typeof props.open === "boolean" || isSignalOpen;
|
|
68
|
+
const isOpen = () => {
|
|
69
|
+
if (typeof props.open === "boolean")
|
|
70
|
+
return props.open;
|
|
71
|
+
if (isSignalOpen)
|
|
72
|
+
return props.open.value;
|
|
73
|
+
return uncontrolledOpen.value;
|
|
74
|
+
};
|
|
75
|
+
const setOpen = (next, reason = "programmatic") => {
|
|
76
|
+
if (!isControlled)
|
|
77
|
+
uncontrolledOpen.value = next;
|
|
78
|
+
if (isSignalOpen)
|
|
79
|
+
props.open.value = next;
|
|
80
|
+
props.onOpenChange?.(next, reason);
|
|
81
|
+
if (!next)
|
|
82
|
+
props.onClose?.(reason);
|
|
83
|
+
};
|
|
84
|
+
const toggle = () => { setOpen(!isOpen(), "toggle"); };
|
|
85
|
+
const close = (reason = "programmatic") => { setOpen(false, reason); };
|
|
86
|
+
return h("div", {
|
|
87
|
+
class: cn("hu-speed-dial", `hu-speed-dial--${position}`, `hu-speed-dial--${direction}`),
|
|
88
|
+
"aria-label": ariaLabel,
|
|
89
|
+
onKeyDown: closeOnEscape ? (event) => {
|
|
90
|
+
if (event.key === "Escape" && isOpen())
|
|
91
|
+
close("escape");
|
|
92
|
+
} : undefined
|
|
93
|
+
}, backdrop && h("div", {
|
|
94
|
+
class: "hu-speed-dial__backdrop",
|
|
95
|
+
hidden: () => !isOpen(),
|
|
96
|
+
onClick: closeOnBackdrop ? () => close("backdrop") : undefined
|
|
97
|
+
}), h("div", {
|
|
98
|
+
class: "hu-speed-dial__actions",
|
|
99
|
+
hidden: () => !isOpen(),
|
|
100
|
+
"aria-hidden": () => String(!isOpen())
|
|
101
|
+
}, ...actions.map((action, i) => h("div", {
|
|
58
102
|
class: "hu-speed-dial__action-wrap",
|
|
59
103
|
style: `animation-delay:${i * 40}ms`
|
|
60
104
|
}, h("button", {
|
|
61
105
|
class: "hu-speed-dial__action",
|
|
62
|
-
onClick: () => {
|
|
106
|
+
onClick: () => {
|
|
107
|
+
if (action.disabled)
|
|
108
|
+
return;
|
|
109
|
+
action.onClick();
|
|
110
|
+
close("action");
|
|
111
|
+
},
|
|
63
112
|
disabled: action.disabled,
|
|
64
113
|
"aria-label": action.label
|
|
65
114
|
}, action.icon), h("span", { class: "hu-speed-dial__action-tooltip" }, action.label)))), h("button", {
|
|
66
|
-
|
|
115
|
+
type: "button",
|
|
116
|
+
class: () => cn("hu-speed-dial__fab", isOpen() && "hu-speed-dial__fab--open"),
|
|
67
117
|
onClick: toggle,
|
|
68
|
-
"aria-haspopup": "
|
|
69
|
-
"aria-expanded": isOpen
|
|
70
|
-
}, isOpen
|
|
118
|
+
"aria-haspopup": "menu",
|
|
119
|
+
"aria-expanded": () => String(isOpen())
|
|
120
|
+
}, () => isOpen() && props.openIcon ? props.openIcon : (props.icon ?? "+")));
|
|
71
121
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type VNode } from "../../theme/index.js";
|
|
2
2
|
export interface SwitchProps {
|
|
3
3
|
checked?: boolean;
|
|
4
|
+
defaultChecked?: boolean;
|
|
4
5
|
disabled?: boolean;
|
|
5
6
|
size?: "sm" | "md" | "lg";
|
|
6
7
|
label?: string;
|
|
@@ -8,6 +9,7 @@ export interface SwitchProps {
|
|
|
8
9
|
name?: string;
|
|
9
10
|
class?: string;
|
|
10
11
|
onChange?: (e: Event) => void;
|
|
12
|
+
onCheckedChange?: (checked: boolean, e: Event) => void;
|
|
11
13
|
}
|
|
12
14
|
export declare function Switch(props: SwitchProps): VNode;
|
|
13
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Switch/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAmCpE,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Switch/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAmCpE,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;CACxD;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,CAehD"}
|
|
@@ -33,6 +33,10 @@ const SWITCH_CSS = `
|
|
|
33
33
|
`;
|
|
34
34
|
export function Switch(props) {
|
|
35
35
|
injectCSS("hu-switch", SWITCH_CSS);
|
|
36
|
-
const { label, disabled, size = "md", id, ...rest } = props;
|
|
37
|
-
|
|
36
|
+
const { label, disabled, size = "md", id, onChange, onCheckedChange, ...rest } = props;
|
|
37
|
+
const handleChange = (event) => {
|
|
38
|
+
onChange?.(event);
|
|
39
|
+
onCheckedChange?.(event.target.checked, event);
|
|
40
|
+
};
|
|
41
|
+
return h("label", { class: cn("hu-switch-wrap", disabled && "hu-switch-wrap--disabled", size !== "md" && `hu-switch--${size}`, props.class) }, h("input", { ...rest, type: "checkbox", id, disabled, onChange: handleChange, class: "hu-switch-input", role: "switch" }), h("span", { class: "hu-switch-track" }, h("span", { class: "hu-switch-thumb" })), label && h("span", { class: "hu-switch-label" }, label));
|
|
38
42
|
}
|
|
@@ -10,10 +10,13 @@ export interface TabItem {
|
|
|
10
10
|
}
|
|
11
11
|
export interface TabsProps {
|
|
12
12
|
items: TabItem[];
|
|
13
|
+
value?: string | Signal<string>;
|
|
14
|
+
defaultValue?: string;
|
|
13
15
|
defaultTab?: string;
|
|
14
16
|
activeTab?: string | Signal<string>;
|
|
15
17
|
variant?: "underline" | "pill";
|
|
16
18
|
onChange?: (id: string) => void;
|
|
19
|
+
onValueChange?: (id: string) => void;
|
|
17
20
|
class?: string;
|
|
18
21
|
}
|
|
19
22
|
export declare function Tabs(props: TabsProps): VNode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Tabs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAoC5D,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Tabs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAoC5D,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,CAsE5C"}
|
|
@@ -35,22 +35,53 @@ const TABS_CSS = `
|
|
|
35
35
|
`;
|
|
36
36
|
export function Tabs(props) {
|
|
37
37
|
injectCSS("hu-tabs", TABS_CSS);
|
|
38
|
-
const { items, variant = "underline", onChange } = props;
|
|
39
|
-
const uncontrolledActive = signal(props.defaultTab ?? items[0]?.id ?? "");
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
const { items, variant = "underline", onChange, onValueChange } = props;
|
|
39
|
+
const uncontrolledActive = signal(props.defaultValue ?? props.defaultTab ?? items[0]?.id ?? "");
|
|
40
|
+
const controlledSignal = getSignalProp(props.value) ?? getSignalProp(props.activeTab);
|
|
41
|
+
const controlledValue = typeof props.value === "string" ? props.value : typeof props.activeTab === "string" ? props.activeTab : undefined;
|
|
42
|
+
const controlled = controlledSignal != null || controlledValue !== undefined;
|
|
43
|
+
const enabledItems = () => items.filter((item) => !item.disabled);
|
|
44
|
+
const getActive = () => controlledSignal?.value ?? controlledValue ?? uncontrolledActive.value;
|
|
44
45
|
const selectTab = (id) => {
|
|
45
|
-
if (!controlled
|
|
46
|
+
if (!controlled)
|
|
46
47
|
uncontrolledActive.value = id;
|
|
48
|
+
if (controlledSignal)
|
|
49
|
+
controlledSignal.value = id;
|
|
47
50
|
onChange?.(id);
|
|
51
|
+
onValueChange?.(id);
|
|
48
52
|
};
|
|
49
|
-
|
|
53
|
+
const focusTab = (id, event) => {
|
|
54
|
+
const root = event.currentTarget;
|
|
55
|
+
const next = root.querySelector(`#tab-${cssEscape(id)}`);
|
|
56
|
+
next?.focus();
|
|
57
|
+
selectTab(id);
|
|
58
|
+
};
|
|
59
|
+
const handleKeyDown = (event) => {
|
|
60
|
+
const enabled = enabledItems();
|
|
61
|
+
if (enabled.length === 0)
|
|
62
|
+
return;
|
|
63
|
+
const current = getActive();
|
|
64
|
+
const currentIndex = Math.max(0, enabled.findIndex((item) => item.id === current));
|
|
65
|
+
let nextIndex = currentIndex;
|
|
66
|
+
if (event.key === "ArrowRight" || event.key === "ArrowDown")
|
|
67
|
+
nextIndex = (currentIndex + 1) % enabled.length;
|
|
68
|
+
else if (event.key === "ArrowLeft" || event.key === "ArrowUp")
|
|
69
|
+
nextIndex = (currentIndex - 1 + enabled.length) % enabled.length;
|
|
70
|
+
else if (event.key === "Home")
|
|
71
|
+
nextIndex = 0;
|
|
72
|
+
else if (event.key === "End")
|
|
73
|
+
nextIndex = enabled.length - 1;
|
|
74
|
+
else
|
|
75
|
+
return;
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
focusTab(enabled[nextIndex].id, event);
|
|
78
|
+
};
|
|
79
|
+
return h("div", { class: cn("hu-tabs", props.class) }, h("div", { class: cn("hu-tabs-list", variant === "pill" && "hu-tabs-list--pill"), role: "tablist", onKeyDown: handleKeyDown }, ...items.map((item) => h("button", {
|
|
50
80
|
key: item.id, id: `tab-${item.id}`, role: "tab",
|
|
51
81
|
class: "hu-tabs-trigger",
|
|
52
82
|
"aria-selected": () => getActive() === item.id ? "true" : "false",
|
|
53
83
|
"aria-controls": `tabpanel-${item.id}`,
|
|
84
|
+
tabindex: () => getActive() === item.id ? "0" : "-1",
|
|
54
85
|
disabled: item.disabled,
|
|
55
86
|
onClick: () => selectTab(item.id)
|
|
56
87
|
}, item.icon && h("span", { "aria-hidden": "true" }, item.icon), item.label, item.badge !== undefined && h("span", { class: "hu-tabs-badge" }, item.badge)))), ...items.map((item) => h("div", {
|
|
@@ -60,3 +91,11 @@ export function Tabs(props) {
|
|
|
60
91
|
"aria-labelledby": `tab-${item.id}`
|
|
61
92
|
}, () => getActive() === item.id ? item.content : null)));
|
|
62
93
|
}
|
|
94
|
+
function getSignalProp(value) {
|
|
95
|
+
return typeof value === "object" && value !== null && "peek" in value ? value : undefined;
|
|
96
|
+
}
|
|
97
|
+
function cssEscape(value) {
|
|
98
|
+
return typeof CSS !== "undefined" && typeof CSS.escape === "function"
|
|
99
|
+
? CSS.escape(value)
|
|
100
|
+
: value.replace(/["\\#.;?+*~':!^$[\]()=>|/@]/g, "\\$&");
|
|
101
|
+
}
|
|
@@ -26,6 +26,8 @@ export interface TextFieldProps {
|
|
|
26
26
|
name?: string;
|
|
27
27
|
autoFocus?: boolean;
|
|
28
28
|
autoComplete?: string;
|
|
29
|
+
"aria-label"?: string;
|
|
30
|
+
"aria-describedby"?: string;
|
|
29
31
|
onChange?: (value: string, e: Event) => void;
|
|
30
32
|
onBlur?: (e: FocusEvent) => void;
|
|
31
33
|
onFocus?: (e: FocusEvent) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/TextField/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AA0CpE,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAClE,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC7C,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/TextField/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AA0CpE,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAC;AAClE,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC7C,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,KAAK,CAiEtD"}
|
|
@@ -38,25 +38,33 @@ const TEXTFIELD_CSS = `
|
|
|
38
38
|
.hu-textfield--sm .hu-textfield__input { min-height: 34px; padding: 6px 10px; font-size: var(--hu-font-size-xs); border-radius: var(--hu-radius); }
|
|
39
39
|
.hu-textfield--lg .hu-textfield__input { min-height: 46px; padding: 11px 14px; font-size: var(--hu-font-size-base); }
|
|
40
40
|
`;
|
|
41
|
+
let textFieldId = 0;
|
|
41
42
|
export function TextField(props) {
|
|
42
43
|
injectCSS("hu-textfield", TEXTFIELD_CSS);
|
|
43
|
-
const { label, placeholder, value, type = "text", variant = "outlined", size = "md", helperText, error, success, required, disabled, readOnly, multiline, rows = 3, startIcon, endIcon, maxLength,
|
|
44
|
+
const { label, placeholder, value, type = "text", variant = "outlined", size = "md", helperText, error, success, required, disabled, readOnly, multiline, rows = 3, startIcon, endIcon, maxLength, name, autoFocus, autoComplete, onChange, onBlur, onFocus } = props;
|
|
45
|
+
const id = props.id ?? (label || helperText || error ? `hu-textfield-${++textFieldId}` : undefined);
|
|
44
46
|
const errorMsg = typeof error === "string" ? error : undefined;
|
|
45
47
|
const hasError = !!error;
|
|
46
48
|
const hasStartIcon = startIcon != null;
|
|
47
49
|
const hasEndIcon = endIcon != null;
|
|
50
|
+
const helperId = helperText && !errorMsg ? `${id}-helper` : undefined;
|
|
51
|
+
const errorId = errorMsg ? `${id}-error` : undefined;
|
|
52
|
+
const describedBy = [props["aria-describedby"], errorId, helperId].filter(Boolean).join(" ") || undefined;
|
|
48
53
|
const inputProps = {
|
|
49
54
|
class: cn("hu-textfield__input", hasStartIcon && "hu-textfield__input--has-start", hasEndIcon && "hu-textfield__input--has-end"),
|
|
50
55
|
id, name, placeholder, disabled, required, "readonly": readOnly,
|
|
51
56
|
autofocus: autoFocus, autocomplete: autoComplete,
|
|
52
57
|
maxlength: maxLength,
|
|
58
|
+
"aria-invalid": hasError ? "true" : undefined,
|
|
59
|
+
"aria-describedby": describedBy,
|
|
60
|
+
"aria-label": props["aria-label"],
|
|
53
61
|
onInput: onChange ? (e) => onChange(e.target.value, e) : undefined,
|
|
54
62
|
onBlur, onFocus
|
|
55
63
|
};
|
|
56
64
|
if (value !== undefined)
|
|
57
65
|
inputProps["value"] = value;
|
|
58
|
-
if (props.defaultValue !== undefined)
|
|
59
|
-
inputProps["
|
|
66
|
+
if (value === undefined && props.defaultValue !== undefined)
|
|
67
|
+
inputProps["defaultValue"] = props.defaultValue;
|
|
60
68
|
const inputEl = multiline
|
|
61
69
|
? h("textarea", { ...inputProps, rows })
|
|
62
70
|
: h("input", { ...inputProps, type });
|
|
@@ -65,5 +73,5 @@ export function TextField(props) {
|
|
|
65
73
|
}, label && h("label", {
|
|
66
74
|
class: cn("hu-textfield__label", required && "hu-textfield__label--required"),
|
|
67
75
|
for: id
|
|
68
|
-
}, label), h("div", { class: "hu-textfield__input-wrap" }, startIcon && h("span", { class: "hu-textfield__start-icon" }, startIcon), inputEl, endIcon && h("span", { class: "hu-textfield__end-icon" }, endIcon)), (
|
|
76
|
+
}, label), h("div", { class: "hu-textfield__input-wrap" }, startIcon && h("span", { class: "hu-textfield__start-icon" }, startIcon), inputEl, endIcon && h("span", { class: "hu-textfield__end-icon" }, endIcon)), errorMsg && h("span", { id: errorId, class: "hu-textfield__helper", role: "alert" }, errorMsg), !errorMsg && helperText && h("span", { id: helperId, class: "hu-textfield__helper" }, helperText), maxLength && h("span", { class: "hu-textfield__char-count" }, `${(value ?? "").length}/${maxLength}`));
|
|
69
77
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type VNode } from "../../theme/index.js";
|
|
2
2
|
export interface TextareaProps {
|
|
3
3
|
value?: string;
|
|
4
|
+
defaultValue?: string;
|
|
4
5
|
placeholder?: string;
|
|
5
6
|
disabled?: boolean;
|
|
6
7
|
readOnly?: boolean;
|
|
@@ -14,9 +15,13 @@ export interface TextareaProps {
|
|
|
14
15
|
name?: string;
|
|
15
16
|
maxLength?: number;
|
|
16
17
|
class?: string;
|
|
18
|
+
"aria-label"?: string;
|
|
19
|
+
"aria-describedby"?: string;
|
|
17
20
|
onInput?: (e: InputEvent) => void;
|
|
18
21
|
onChange?: (e: Event) => void;
|
|
22
|
+
onValueChange?: (value: string, e: Event) => void;
|
|
19
23
|
onBlur?: (e: FocusEvent) => void;
|
|
24
|
+
onFocus?: (e: FocusEvent) => void;
|
|
20
25
|
}
|
|
21
26
|
export declare function Textarea(props: TextareaProps): VNode;
|
|
22
27
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Textarea/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Textarea/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAsBpE,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC9B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAClD,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;CACnC;AAID,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,CAyCpD"}
|
|
@@ -16,12 +16,29 @@ const TEXTAREA_CSS = `
|
|
|
16
16
|
.hu-textarea--error { border-color: var(--hu-error) !important; }
|
|
17
17
|
.hu-textarea--error:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--hu-error) 18%, transparent); }
|
|
18
18
|
.hu-textarea--no-resize { resize: none; }
|
|
19
|
+
.hu-textarea__count { font-size: var(--hu-font-size-xs); color: var(--hu-text-2); text-align: right; }
|
|
19
20
|
`;
|
|
21
|
+
let textareaId = 0;
|
|
20
22
|
export function Textarea(props) {
|
|
21
23
|
injectCSS("hu-textarea", TEXTAREA_CSS);
|
|
22
|
-
const { error, helper, label, resize = true,
|
|
24
|
+
const { error, helper, label, resize = true, value, defaultValue, onInput, onChange, onValueChange, class: className, ...rest } = props;
|
|
25
|
+
const id = props.id ?? (label || helper || error ? `hu-textarea-${++textareaId}` : undefined);
|
|
26
|
+
const helperId = helper && !error ? `${id}-helper` : undefined;
|
|
27
|
+
const errorId = error ? `${id}-error` : undefined;
|
|
28
|
+
const describedBy = [props["aria-describedby"], errorId, helperId].filter(Boolean).join(" ") || undefined;
|
|
29
|
+
const handleInput = (event) => {
|
|
30
|
+
onInput?.(event);
|
|
31
|
+
onValueChange?.(event.target.value, event);
|
|
32
|
+
};
|
|
23
33
|
return h("div", { class: "hu-input-wrap" }, label && Label({ for: id, required: props.required, children: label }), h("textarea", {
|
|
24
|
-
...rest,
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
...rest,
|
|
35
|
+
id,
|
|
36
|
+
value,
|
|
37
|
+
defaultValue: value === undefined ? defaultValue : undefined,
|
|
38
|
+
onInput: handleInput,
|
|
39
|
+
onChange,
|
|
40
|
+
"aria-invalid": error ? "true" : undefined,
|
|
41
|
+
"aria-describedby": describedBy,
|
|
42
|
+
class: cn("hu-textarea", error && "hu-textarea--error", !resize && "hu-textarea--no-resize", className)
|
|
43
|
+
}), error && h("span", { id: errorId, class: "hu-input-error-msg", role: "alert" }, error), !error && helper && h("span", { id: helperId, class: "hu-input-helper" }, helper), props.maxLength && h("span", { class: "hu-textarea__count" }, `${(value ?? defaultValue ?? "").length}/${props.maxLength}`));
|
|
27
44
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Signal, type TransitionOptions } from "@hyperpackai/hyperion";
|
|
2
|
+
import { type VNode } from "../../theme/index.js";
|
|
3
|
+
export interface TransitionProps extends TransitionOptions {
|
|
4
|
+
show: boolean | Signal<boolean>;
|
|
5
|
+
class?: string;
|
|
6
|
+
children?: unknown;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Mounts/unmounts `children` based on `show`, applying enter/leave CSS
|
|
10
|
+
* classes (Vue-style: `*-from` / `*-active` / `*-to`) around the transition.
|
|
11
|
+
* Override the class names via props to plug in custom animations.
|
|
12
|
+
*/
|
|
13
|
+
export declare function Transition(props: TransitionProps): VNode;
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Transition/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,MAAM,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC3G,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAUpE,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,KAAK,CAgDxD"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { signal, effect, useTransition } from "@hyperpackai/hyperion";
|
|
2
|
+
import { injectCSS, cn, h } from "../../theme/index.js";
|
|
3
|
+
const CSS = `
|
|
4
|
+
.hu-transition-enter-active, .hu-transition-leave-active {
|
|
5
|
+
transition: opacity var(--hu-duration) var(--hu-ease), transform var(--hu-duration) var(--hu-ease);
|
|
6
|
+
}
|
|
7
|
+
.hu-transition-enter-from, .hu-transition-leave-to { opacity: 0; transform: scale(0.96); }
|
|
8
|
+
.hu-transition-enter-to, .hu-transition-leave-from { opacity: 1; transform: scale(1); }
|
|
9
|
+
`;
|
|
10
|
+
/**
|
|
11
|
+
* Mounts/unmounts `children` based on `show`, applying enter/leave CSS
|
|
12
|
+
* classes (Vue-style: `*-from` / `*-active` / `*-to`) around the transition.
|
|
13
|
+
* Override the class names via props to plug in custom animations.
|
|
14
|
+
*/
|
|
15
|
+
export function Transition(props) {
|
|
16
|
+
injectCSS("hu-transition", CSS);
|
|
17
|
+
const { enterClass = "hu-transition-enter-from", enterActiveClass = "hu-transition-enter-active", enterToClass = "hu-transition-enter-to", leaveClass = "hu-transition-leave-from", leaveActiveClass = "hu-transition-leave-active", leaveToClass = "hu-transition-leave-to" } = props;
|
|
18
|
+
const { state, shouldRender } = useTransition(props.show, props);
|
|
19
|
+
// Drives the dynamic part of the `class` attribute. Kept as its own signal
|
|
20
|
+
// (rather than mutating `classList` from a side-effect) so it composes
|
|
21
|
+
// cleanly with `props.class` through the reactive `class` getter below —
|
|
22
|
+
// applyProp re-sets the whole `class` attribute on every change, which
|
|
23
|
+
// would otherwise wipe out classes toggled imperatively.
|
|
24
|
+
const transitionClass = signal("");
|
|
25
|
+
effect(() => {
|
|
26
|
+
switch (state.value) {
|
|
27
|
+
case "entering":
|
|
28
|
+
transitionClass.value = cn(enterClass, enterActiveClass);
|
|
29
|
+
requestAnimationFrame(() => {
|
|
30
|
+
transitionClass.value = cn(enterActiveClass, enterToClass);
|
|
31
|
+
});
|
|
32
|
+
break;
|
|
33
|
+
case "leaving":
|
|
34
|
+
transitionClass.value = cn(leaveClass, leaveActiveClass);
|
|
35
|
+
requestAnimationFrame(() => {
|
|
36
|
+
transitionClass.value = cn(leaveActiveClass, leaveToClass);
|
|
37
|
+
});
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
transitionClass.value = "";
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return (() => shouldRender.value
|
|
45
|
+
? h("div", {
|
|
46
|
+
class: () => cn(props.class, transitionClass.value)
|
|
47
|
+
}, props.children)
|
|
48
|
+
: null);
|
|
49
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Signal } from "@hyperpackai/hyperion";
|
|
2
|
+
import { type VNode } from "../../theme/index.js";
|
|
3
|
+
export interface TransitionGroupProps<T> {
|
|
4
|
+
each: T[] | Signal<T[]>;
|
|
5
|
+
key: (item: T, index: number) => string | number;
|
|
6
|
+
children: (item: T, index: number) => unknown;
|
|
7
|
+
duration?: number;
|
|
8
|
+
tag?: string;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Animates list items in/out with enter/leave CSS classes and applies a
|
|
13
|
+
* best-effort FLIP transform to items that moved on reorder.
|
|
14
|
+
*/
|
|
15
|
+
export declare function TransitionGroup<T>(props: TransitionGroupProps<T>): VNode;
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/TransitionGroup/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAsBpE,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACrC,IAAI,EAAE,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;IACxB,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAC;IACjD,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAAG,KAAK,CA4FxE"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { signal, effect, For } from "@hyperpackai/hyperion";
|
|
2
|
+
import { injectCSS, cn, h } from "../../theme/index.js";
|
|
3
|
+
const CSS = `
|
|
4
|
+
.hu-transition-group-item {
|
|
5
|
+
transition: opacity var(--hu-duration) var(--hu-ease), transform var(--hu-duration) var(--hu-ease);
|
|
6
|
+
}
|
|
7
|
+
.hu-transition-group-item-enter-from,
|
|
8
|
+
.hu-transition-group-item-leave-to {
|
|
9
|
+
opacity: 0;
|
|
10
|
+
transform: scale(0.96);
|
|
11
|
+
}
|
|
12
|
+
`;
|
|
13
|
+
/**
|
|
14
|
+
* Animates list items in/out with enter/leave CSS classes and applies a
|
|
15
|
+
* best-effort FLIP transform to items that moved on reorder.
|
|
16
|
+
*/
|
|
17
|
+
export function TransitionGroup(props) {
|
|
18
|
+
injectCSS("hu-transition-group", CSS);
|
|
19
|
+
const duration = props.duration ?? 150;
|
|
20
|
+
const entries = signal([]);
|
|
21
|
+
const elements = new Map();
|
|
22
|
+
// JSX delivers a single function child as `[fn]` (see `For`'s same handling).
|
|
23
|
+
const childrenFn = (Array.isArray(props.children) ? props.children[0] : props.children);
|
|
24
|
+
effect(() => {
|
|
25
|
+
const list = Array.isArray(props.each) ? props.each : props.each.value;
|
|
26
|
+
const current = entries.peek();
|
|
27
|
+
const nextKeys = list.map((item, i) => props.key(item, i));
|
|
28
|
+
const nextKeySet = new Set(nextKeys);
|
|
29
|
+
// FLIP: capture positions before the DOM updates.
|
|
30
|
+
const rectsBefore = new Map();
|
|
31
|
+
for (const [key, el] of elements) {
|
|
32
|
+
if (el)
|
|
33
|
+
rectsBefore.set(key, el.getBoundingClientRect());
|
|
34
|
+
}
|
|
35
|
+
const updated = [];
|
|
36
|
+
// Items removed from `each`: animate out, then drop after `duration`.
|
|
37
|
+
for (const entry of current) {
|
|
38
|
+
if (!nextKeySet.has(entry.key)) {
|
|
39
|
+
if (entry.status.peek() !== "leaving") {
|
|
40
|
+
entry.status.value = "leaving";
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
entries.value = entries.peek().filter((e) => e.key !== entry.key);
|
|
43
|
+
elements.delete(entry.key);
|
|
44
|
+
}, duration);
|
|
45
|
+
}
|
|
46
|
+
updated.push(entry);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Items present in the new list: reuse existing entries or create new ones.
|
|
50
|
+
list.forEach((item, index) => {
|
|
51
|
+
const key = nextKeys[index];
|
|
52
|
+
const existing = current.find((e) => e.key === key);
|
|
53
|
+
if (existing) {
|
|
54
|
+
existing.item = item;
|
|
55
|
+
existing.index = index;
|
|
56
|
+
updated.push(existing);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const status = signal("entering");
|
|
60
|
+
updated.push({ key, item, index, status });
|
|
61
|
+
requestAnimationFrame(() => { status.value = "entered"; });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
entries.value = updated;
|
|
65
|
+
// FLIP: animate items whose position changed due to reordering.
|
|
66
|
+
requestAnimationFrame(() => {
|
|
67
|
+
for (const [key, el] of elements) {
|
|
68
|
+
if (!el)
|
|
69
|
+
continue;
|
|
70
|
+
const before = rectsBefore.get(key);
|
|
71
|
+
if (!before)
|
|
72
|
+
continue;
|
|
73
|
+
const after = el.getBoundingClientRect();
|
|
74
|
+
const dx = before.left - after.left;
|
|
75
|
+
const dy = before.top - after.top;
|
|
76
|
+
if (dx || dy) {
|
|
77
|
+
el.style.transition = "none";
|
|
78
|
+
el.style.transform = `translate(${dx}px, ${dy}px)`;
|
|
79
|
+
requestAnimationFrame(() => {
|
|
80
|
+
el.style.transition = `transform ${duration}ms var(--hu-ease, ease)`;
|
|
81
|
+
el.style.transform = "";
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
return h(props.tag ?? "div", { class: cn("hu-transition-group", props.class) }, For({
|
|
88
|
+
each: entries,
|
|
89
|
+
key: (entry) => entry.key,
|
|
90
|
+
children: (entry) => h("div", {
|
|
91
|
+
ref: (el) => { elements.set(entry.key, el); },
|
|
92
|
+
class: () => cn("hu-transition-group-item", entry.status.value === "entering" && "hu-transition-group-item-enter-from", entry.status.value === "leaving" && "hu-transition-group-item-leave-to")
|
|
93
|
+
}, childrenFn(entry.item, entry.index))
|
|
94
|
+
}));
|
|
95
|
+
}
|