@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.
Files changed (98) hide show
  1. package/README.md +13 -0
  2. package/dist/components/Accordion/index.d.ts +6 -0
  3. package/dist/components/Accordion/index.d.ts.map +1 -1
  4. package/dist/components/Accordion/index.js +65 -9
  5. package/dist/components/Autocomplete/index.d.ts +12 -2
  6. package/dist/components/Autocomplete/index.d.ts.map +1 -1
  7. package/dist/components/Autocomplete/index.js +148 -24
  8. package/dist/components/Backdrop/index.d.ts +2 -1
  9. package/dist/components/Backdrop/index.d.ts.map +1 -1
  10. package/dist/components/Backdrop/index.js +6 -3
  11. package/dist/components/Checkbox/index.d.ts +1 -0
  12. package/dist/components/Checkbox/index.d.ts.map +1 -1
  13. package/dist/components/Checkbox/index.js +6 -2
  14. package/dist/components/DashboardLayout/index.d.ts +13 -0
  15. package/dist/components/DashboardLayout/index.d.ts.map +1 -1
  16. package/dist/components/DashboardLayout/index.js +50 -7
  17. package/dist/components/DataTable/index.d.ts +43 -0
  18. package/dist/components/DataTable/index.d.ts.map +1 -1
  19. package/dist/components/DataTable/index.js +126 -21
  20. package/dist/components/Dialog/index.d.ts +9 -3
  21. package/dist/components/Dialog/index.d.ts.map +1 -1
  22. package/dist/components/Dialog/index.js +46 -30
  23. package/dist/components/Drawer/index.d.ts +11 -3
  24. package/dist/components/Drawer/index.d.ts.map +1 -1
  25. package/dist/components/Drawer/index.js +66 -11
  26. package/dist/components/DropdownMenu/index.d.ts +5 -3
  27. package/dist/components/DropdownMenu/index.d.ts.map +1 -1
  28. package/dist/components/DropdownMenu/index.js +56 -13
  29. package/dist/components/FocusTrap/index.d.ts.map +1 -1
  30. package/dist/components/FocusTrap/index.js +34 -32
  31. package/dist/components/Input/index.d.ts +2 -0
  32. package/dist/components/Input/index.d.ts.map +1 -1
  33. package/dist/components/Input/index.js +18 -4
  34. package/dist/components/Menu/index.d.ts +6 -2
  35. package/dist/components/Menu/index.d.ts.map +1 -1
  36. package/dist/components/Menu/index.js +50 -15
  37. package/dist/components/Modal/index.d.ts +3 -1
  38. package/dist/components/Modal/index.d.ts.map +1 -1
  39. package/dist/components/Modal/index.js +27 -9
  40. package/dist/components/NestedNavbar/index.d.ts +33 -0
  41. package/dist/components/NestedNavbar/index.d.ts.map +1 -0
  42. package/dist/components/NestedNavbar/index.js +435 -0
  43. package/dist/components/NestedSidebar/index.d.ts +48 -0
  44. package/dist/components/NestedSidebar/index.d.ts.map +1 -0
  45. package/dist/components/NestedSidebar/index.js +368 -0
  46. package/dist/components/Popover/index.d.ts +11 -3
  47. package/dist/components/Popover/index.d.ts.map +1 -1
  48. package/dist/components/Popover/index.js +45 -9
  49. package/dist/components/Radio/index.d.ts +26 -1
  50. package/dist/components/Radio/index.d.ts.map +1 -1
  51. package/dist/components/Radio/index.js +61 -2
  52. package/dist/components/Select/index.d.ts +5 -0
  53. package/dist/components/Select/index.d.ts.map +1 -1
  54. package/dist/components/Select/index.js +22 -5
  55. package/dist/components/Sheet/index.d.ts +9 -3
  56. package/dist/components/Sheet/index.d.ts.map +1 -1
  57. package/dist/components/Sheet/index.js +48 -23
  58. package/dist/components/Sidebar/index.d.ts +20 -1
  59. package/dist/components/Sidebar/index.d.ts.map +1 -1
  60. package/dist/components/Sidebar/index.js +285 -8
  61. package/dist/components/SpeedDial/index.d.ts +10 -0
  62. package/dist/components/SpeedDial/index.d.ts.map +1 -1
  63. package/dist/components/SpeedDial/index.js +61 -11
  64. package/dist/components/Switch/index.d.ts +2 -0
  65. package/dist/components/Switch/index.d.ts.map +1 -1
  66. package/dist/components/Switch/index.js +6 -2
  67. package/dist/components/Tabs/index.d.ts +3 -0
  68. package/dist/components/Tabs/index.d.ts.map +1 -1
  69. package/dist/components/Tabs/index.js +47 -8
  70. package/dist/components/TextField/index.d.ts +2 -0
  71. package/dist/components/TextField/index.d.ts.map +1 -1
  72. package/dist/components/TextField/index.js +12 -4
  73. package/dist/components/Textarea/index.d.ts +5 -0
  74. package/dist/components/Textarea/index.d.ts.map +1 -1
  75. package/dist/components/Textarea/index.js +21 -4
  76. package/dist/components/Transition/index.d.ts +14 -0
  77. package/dist/components/Transition/index.d.ts.map +1 -0
  78. package/dist/components/Transition/index.js +49 -0
  79. package/dist/components/TransitionGroup/index.d.ts +16 -0
  80. package/dist/components/TransitionGroup/index.d.ts.map +1 -0
  81. package/dist/components/TransitionGroup/index.js +95 -0
  82. package/dist/components/data.d.ts +81 -16
  83. package/dist/components/data.d.ts.map +1 -1
  84. package/dist/components/data.js +163 -31
  85. package/dist/components/enterprise.d.ts +85 -26
  86. package/dist/components/enterprise.d.ts.map +1 -1
  87. package/dist/components/enterprise.js +211 -36
  88. package/dist/components/index.d.ts +21 -13
  89. package/dist/components/index.d.ts.map +1 -1
  90. package/dist/components/index.js +7 -2
  91. package/dist/portal.d.ts.map +1 -1
  92. package/dist/portal.js +3 -0
  93. package/dist/theme/index.d.ts +5 -6
  94. package/dist/theme/index.d.ts.map +1 -1
  95. package/dist/theme/index.js +30 -0
  96. package/dist/tokens/index.d.ts.map +1 -1
  97. package/dist/tokens/index.js +11 -0
  98. package/package.json +6 -1
package/README.md CHANGED
@@ -72,6 +72,19 @@ All components support customization through CSS variables and theme config.
72
72
  - [Theming Guide](https://hyperpack.dev/docs/hyperui/theming)
73
73
  - [Component Gallery](https://hyperpack.dev/docs)
74
74
 
75
+ ## Release Readiness
76
+
77
+ HyperUI owns its benchmark baselines and release gate. Before publishing
78
+ HyperUI-facing changes, run:
79
+
80
+ ```bash
81
+ npm run release:gate:hyperui
82
+ ```
83
+
84
+ The gate builds HyperUI, runs API/accessibility/keyboard/theme tests, enforces
85
+ Node and Chromium benchmark budgets, checks the HyperDocs bundle size, and
86
+ captures HyperDocs visual smoke screenshots.
87
+
75
88
  ## License
76
89
 
77
90
  MIT
@@ -1,3 +1,4 @@
1
+ import { type Signal } from "@hyperpackai/hyperion";
1
2
  import { type VNode } from "../../theme/index.js";
2
3
  export interface AccordionItem {
3
4
  id: string;
@@ -9,7 +10,12 @@ export interface AccordionItem {
9
10
  export interface AccordionProps {
10
11
  items: AccordionItem[];
11
12
  multiple?: boolean;
13
+ openItems?: string[] | Signal<string[]>;
14
+ value?: string[] | Signal<string[]>;
12
15
  defaultOpen?: string[];
16
+ defaultValue?: string[];
17
+ onChange?: (openItems: string[]) => void;
18
+ onValueChange?: (openItems: string[]) => void;
13
19
  variant?: "default" | "ghost";
14
20
  class?: string;
15
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Accordion/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAsBpE,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,KAAK,CAqCtD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Accordion/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAsBpE,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACxC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACzC,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC9C,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,KAAK,CAmFtD"}
@@ -21,26 +21,82 @@ const ACCORDION_CSS = `
21
21
  `;
22
22
  export function Accordion(props) {
23
23
  injectCSS("hu-accordion", ACCORDION_CSS);
24
- const { items, multiple = false, defaultOpen = [], variant = "default" } = props;
25
- const open = signal(defaultOpen);
24
+ const { items, multiple = false, variant = "default", onChange, onValueChange } = props;
25
+ const uncontrolledOpen = signal(props.defaultValue ?? props.defaultOpen ?? []);
26
+ const controlledSignal = getStringArraySignalProp(props.value) ?? getStringArraySignalProp(props.openItems);
27
+ const controlledValue = Array.isArray(props.value) ? props.value : Array.isArray(props.openItems) ? props.openItems : undefined;
28
+ const controlled = controlledSignal != null || controlledValue !== undefined;
29
+ const getOpenItems = () => controlledSignal?.value ?? controlledValue ?? uncontrolledOpen.value;
30
+ const setOpenItems = (next) => {
31
+ if (!controlled)
32
+ uncontrolledOpen.value = next;
33
+ if (controlledSignal)
34
+ controlledSignal.value = next;
35
+ onChange?.(next);
36
+ onValueChange?.(next);
37
+ };
26
38
  const toggle = (id) => {
27
- const current = open.value;
39
+ const current = getOpenItems();
40
+ let next;
28
41
  if (multiple) {
29
- open.value = current.includes(id) ? current.filter((i) => i !== id) : [...current, id];
42
+ next = current.includes(id) ? current.filter((i) => i !== id) : [...current, id];
30
43
  }
31
44
  else {
32
- open.value = current.includes(id) ? [] : [id];
45
+ next = current.includes(id) ? [] : [id];
33
46
  }
47
+ setOpenItems(next);
48
+ };
49
+ const focusTrigger = (id, event) => {
50
+ const root = event.currentTarget;
51
+ const next = root.querySelector(`#accordion-trigger-${cssEscape(id)}`);
52
+ next?.focus();
53
+ };
54
+ const handleKeyDown = (event) => {
55
+ const enabled = items.filter((item) => !item.disabled);
56
+ if (enabled.length === 0)
57
+ return;
58
+ const currentButton = event.target;
59
+ const currentId = currentButton.getAttribute("data-accordion-id");
60
+ const currentIndex = Math.max(0, enabled.findIndex((item) => item.id === currentId));
61
+ let nextIndex = currentIndex;
62
+ if (event.key === "ArrowDown")
63
+ nextIndex = (currentIndex + 1) % enabled.length;
64
+ else if (event.key === "ArrowUp")
65
+ nextIndex = (currentIndex - 1 + enabled.length) % enabled.length;
66
+ else if (event.key === "Home")
67
+ nextIndex = 0;
68
+ else if (event.key === "End")
69
+ nextIndex = enabled.length - 1;
70
+ else
71
+ return;
72
+ event.preventDefault();
73
+ focusTrigger(enabled[nextIndex].id, event);
34
74
  };
35
- const chevronSVG = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 6l4 4 4-4"/></svg>`;
36
- return h("div", { class: cn("hu-accordion", variant !== "default" && `hu-accordion--${variant}`, props.class) }, ...items.map((item) => {
37
- const isOpen = () => open.value.includes(item.id);
75
+ const chevronIcon = h("svg", {
76
+ viewBox: "0 0 16 16",
77
+ fill: "none",
78
+ stroke: "currentColor",
79
+ "stroke-width": "1.5",
80
+ "aria-hidden": "true"
81
+ }, h("path", { d: "M4 6l4 4 4-4" }));
82
+ return h("div", { class: cn("hu-accordion", variant !== "default" && `hu-accordion--${variant}`, props.class), onKeyDown: handleKeyDown }, ...items.map((item) => {
83
+ const isOpen = () => getOpenItems().includes(item.id);
38
84
  return h("div", { key: item.id, class: "hu-accordion-item" }, h("button", {
85
+ id: `accordion-trigger-${item.id}`,
39
86
  class: "hu-accordion-trigger",
87
+ "data-accordion-id": item.id,
40
88
  "aria-expanded": () => isOpen() ? "true" : "false",
41
89
  "aria-controls": `accordion-${item.id}`,
42
90
  disabled: item.disabled,
43
91
  onClick: () => toggle(item.id)
44
- }, h("span", { style: "display:flex;align-items:center;gap:8px;" }, item.icon && h("span", {}, item.icon), item.title), h("span", { class: "hu-accordion-icon", innerHTML: chevronSVG, "aria-hidden": "true" })), () => isOpen() ? h("div", { id: `accordion-${item.id}`, class: "hu-accordion-content" }, item.content) : null);
92
+ }, h("span", { style: "display:flex;align-items:center;gap:8px;" }, item.icon && h("span", {}, item.icon), item.title), h("span", { class: "hu-accordion-icon", "aria-hidden": "true" }, chevronIcon)), () => isOpen() ? h("div", { id: `accordion-${item.id}`, class: "hu-accordion-content", role: "region", "aria-labelledby": `accordion-trigger-${item.id}` }, item.content) : null);
45
93
  }));
46
94
  }
95
+ function getStringArraySignalProp(value) {
96
+ return typeof value === "object" && value !== null && "peek" in value ? value : undefined;
97
+ }
98
+ function cssEscape(value) {
99
+ return typeof CSS !== "undefined" && typeof CSS.escape === "function"
100
+ ? CSS.escape(value)
101
+ : value.replace(/["\\#.;?+*~':!^$[\]()=>|/@]/g, "\\$&");
102
+ }
@@ -1,3 +1,4 @@
1
+ import { type Signal } from "@hyperpackai/hyperion";
1
2
  import { type VNode } from "../../theme/index.js";
2
3
  export interface AutocompleteOption {
3
4
  label: string;
@@ -7,14 +8,23 @@ export interface AutocompleteOption {
7
8
  }
8
9
  export interface AutocompleteProps {
9
10
  options: AutocompleteOption[];
10
- value?: string;
11
+ value?: string | Signal<string>;
12
+ defaultValue?: string;
11
13
  placeholder?: string;
12
14
  label?: string;
13
15
  multiple?: boolean;
14
16
  clearable?: boolean;
15
17
  disabled?: boolean;
18
+ required?: boolean;
19
+ id?: string;
20
+ name?: string;
21
+ helper?: string;
22
+ error?: string;
23
+ "aria-label"?: string;
24
+ "aria-describedby"?: string;
16
25
  noOptionsText?: string;
17
- onChange?: (value: string | null) => void;
26
+ onChange?: (value: string | null, option?: AutocompleteOption | null) => void;
27
+ onValueChange?: (value: string | null, option?: AutocompleteOption | null) => void;
18
28
  class?: string;
19
29
  }
20
30
  export declare function Autocomplete(props: AutocompleteProps): VNode;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Autocomplete/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAqDpE,MAAM,WAAW,kBAAkB;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE;AAExG,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,CA+D5D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Autocomplete/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAyDpE,MAAM,WAAW,kBAAkB;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE;AAExG,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,CAAC;IAC9E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,CAAC;IACnF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,CA2L5D"}
@@ -49,46 +49,170 @@ const AUTOCOMPLETE_CSS = `
49
49
  .hu-autocomplete__check { margin-inline-start: auto; color: var(--hu-primary); font-size: 12px; }
50
50
  .hu-autocomplete__no-options { padding: var(--hu-space-5); font-size: var(--hu-font-size-sm); color: var(--hu-text-2); text-align: center; }
51
51
  .hu-autocomplete__group-label { padding: var(--hu-space-2) var(--hu-space-3); font-size: var(--hu-font-size-xs); font-weight: var(--hu-font-weight-semibold); color: var(--hu-text-3); text-transform: uppercase; letter-spacing: .06em; }
52
+ .hu-autocomplete__helper { font-size: var(--hu-font-size-xs); color: var(--hu-text-2); line-height: 1.45; }
53
+ .hu-autocomplete__error { font-size: var(--hu-font-size-xs); color: var(--hu-error); line-height: 1.45; }
54
+ .hu-autocomplete--error .hu-autocomplete__input { border-color: var(--hu-error); }
55
+ .hu-autocomplete--error .hu-autocomplete__input:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--hu-error) 18%, transparent), var(--hu-shadow-sm); }
52
56
  `;
57
+ let autocompleteId = 0;
53
58
  export function Autocomplete(props) {
54
59
  injectCSS("hu-autocomplete", AUTOCOMPLETE_CSS);
55
- const { options, placeholder, label, clearable, disabled, noOptionsText = "No options", onChange } = props;
56
- const inputVal = signal(props.value ? (options.find(o => o.value === props.value)?.label ?? "") : "");
60
+ const { options, placeholder, label, clearable, disabled, required, helper, error, noOptionsText = "No options", onChange, onValueChange } = props;
61
+ const generatedId = props.id ?? `hu-autocomplete-${++autocompleteId}`;
62
+ const listboxId = `${generatedId}-listbox`;
63
+ const helperId = helper && !error ? `${generatedId}-helper` : undefined;
64
+ const errorId = error ? `${generatedId}-error` : undefined;
65
+ const describedBy = [props["aria-describedby"], errorId, helperId].filter(Boolean).join(" ") || undefined;
66
+ const valueSignal = getSignalProp(props.value);
67
+ const isControlled = typeof props.value === "string" || !!valueSignal;
68
+ const internalValue = signal(typeof props.value === "string" ? props.value : props.defaultValue ?? "");
69
+ const currentValue = () => valueSignal ? valueSignal.value : typeof props.value === "string" ? props.value : internalValue.value;
70
+ const optionForValue = (value) => options.find(o => o.value === value);
71
+ const inputVal = signal(optionForValue(currentValue())?.label ?? "");
57
72
  const isOpen = signal(false);
58
- const selected = signal(props.value ?? "");
73
+ const activeIndex = signal(-1);
59
74
  const filtered = () => options.filter(o => !inputVal.value || o.label.toLowerCase().includes(inputVal.value.toLowerCase()));
75
+ const selectedValue = () => currentValue();
76
+ const activeOption = () => activeIndex.value >= 0 ? filtered()[activeIndex.value] : undefined;
77
+ const optionId = (opt) => `${generatedId}-option-${toIdPart(opt.value)}`;
78
+ const activeDescendant = () => {
79
+ const opt = activeOption();
80
+ return opt ? optionId(opt) : undefined;
81
+ };
82
+ const firstEnabledIndex = () => filtered().findIndex(opt => !opt.disabled);
83
+ const selectedIndex = () => {
84
+ const index = filtered().findIndex(opt => opt.value === selectedValue() && !opt.disabled);
85
+ return index >= 0 ? index : firstEnabledIndex();
86
+ };
87
+ const setOpen = (next) => {
88
+ isOpen.value = next;
89
+ if (next && activeIndex.value < 0)
90
+ activeIndex.value = selectedIndex();
91
+ if (!next)
92
+ activeIndex.value = -1;
93
+ };
94
+ const setValue = (value, option) => {
95
+ if (valueSignal)
96
+ valueSignal.value = value;
97
+ else if (!isControlled)
98
+ internalValue.value = value;
99
+ onChange?.(value || null, option);
100
+ onValueChange?.(value || null, option);
101
+ };
102
+ const selectOption = (opt) => {
103
+ if (!opt || opt.disabled)
104
+ return;
105
+ inputVal.value = opt.label;
106
+ setValue(opt.value, opt);
107
+ setOpen(false);
108
+ };
109
+ const clearSelection = () => {
110
+ inputVal.value = "";
111
+ setValue("", null);
112
+ setOpen(false);
113
+ };
114
+ const moveActive = (delta) => {
115
+ const items = filtered();
116
+ if (!items.length)
117
+ return;
118
+ let index = activeIndex.value;
119
+ for (let i = 0; i < items.length; i++) {
120
+ index = (index + delta + items.length) % items.length;
121
+ if (!items[index]?.disabled) {
122
+ activeIndex.value = index;
123
+ return;
124
+ }
125
+ }
126
+ };
127
+ const handleKeyDown = (e) => {
128
+ if (disabled)
129
+ return;
130
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
131
+ e.preventDefault();
132
+ if (!isOpen.value)
133
+ setOpen(true);
134
+ moveActive(e.key === "ArrowDown" ? 1 : -1);
135
+ return;
136
+ }
137
+ if (e.key === "Home" || e.key === "End") {
138
+ if (!isOpen.value)
139
+ return;
140
+ e.preventDefault();
141
+ const items = filtered();
142
+ const start = e.key === "Home" ? 0 : items.length - 1;
143
+ const step = e.key === "Home" ? 1 : -1;
144
+ for (let index = start; index >= 0 && index < items.length; index += step) {
145
+ if (!items[index]?.disabled) {
146
+ activeIndex.value = index;
147
+ break;
148
+ }
149
+ }
150
+ return;
151
+ }
152
+ if (e.key === "Enter" && isOpen.value) {
153
+ e.preventDefault();
154
+ selectOption(activeOption());
155
+ return;
156
+ }
157
+ if (e.key === "Escape") {
158
+ e.preventDefault();
159
+ setOpen(false);
160
+ }
161
+ };
60
162
  return h("div", {
61
- class: cn("hu-autocomplete", isOpen.value && "hu-autocomplete--open", props.class)
62
- }, label && h("label", { class: "hu-autocomplete__label" }, label), h("div", { class: "hu-autocomplete__input-wrap" }, h("input", {
163
+ class: cn("hu-autocomplete", isOpen.value && "hu-autocomplete--open", error && "hu-autocomplete--error", props.class)
164
+ }, label && h("label", { class: "hu-autocomplete__label", for: generatedId }, label), h("div", { class: "hu-autocomplete__input-wrap" }, h("input", {
165
+ id: generatedId,
166
+ name: props.name,
63
167
  class: "hu-autocomplete__input",
64
- value: inputVal.value, placeholder, disabled,
65
- onFocus: () => { isOpen.value = true; },
66
- onInput: (e) => { inputVal.value = e.target.value; isOpen.value = true; },
67
- onBlur: () => setTimeout(() => { isOpen.value = false; }, 150)
68
- }), clearable && selected.value && h("button", {
168
+ value: inputVal.value,
169
+ placeholder,
170
+ disabled,
171
+ required,
172
+ role: "combobox",
173
+ autocomplete: "off",
174
+ "aria-autocomplete": "list",
175
+ "aria-controls": listboxId,
176
+ "aria-expanded": isOpen.value ? "true" : "false",
177
+ "aria-activedescendant": activeDescendant(),
178
+ "aria-invalid": error ? "true" : undefined,
179
+ "aria-describedby": describedBy,
180
+ "aria-label": props["aria-label"],
181
+ onFocus: () => { setOpen(true); },
182
+ onInput: (e) => {
183
+ inputVal.value = e.target.value;
184
+ activeIndex.value = firstEnabledIndex();
185
+ setOpen(true);
186
+ },
187
+ onKeyDown: handleKeyDown,
188
+ onBlur: () => setTimeout(() => { setOpen(false); }, 150)
189
+ }), clearable && selectedValue() && h("button", {
69
190
  class: "hu-autocomplete__clear",
70
191
  type: "button",
192
+ "aria-label": "Clear selection",
71
193
  onMouseDown: (e) => {
72
194
  e.preventDefault();
73
- selected.value = "";
74
- inputVal.value = "";
75
- onChange?.(null);
195
+ clearSelection();
76
196
  }
77
- }, "×"), h("span", { class: "hu-autocomplete__arrow" }, "▾")), isOpen.value && h("div", { class: "hu-autocomplete__listbox-wrap" }, h("ul", { class: "hu-autocomplete__listbox", role: "listbox" }, filtered().length === 0
197
+ }, "×"), h("span", { class: "hu-autocomplete__arrow" }, "▾")), isOpen.value && h("div", { class: "hu-autocomplete__listbox-wrap" }, h("ul", { id: listboxId, class: "hu-autocomplete__listbox", role: "listbox" }, filtered().length === 0
78
198
  ? h("li", { class: "hu-autocomplete__no-options" }, noOptionsText)
79
- : filtered().map(opt => h("li", {
80
- class: cn("hu-autocomplete__option", selected.value === opt.value && "hu-autocomplete__option--selected", opt.disabled && "hu-autocomplete__option--disabled"),
199
+ : filtered().map((opt, index) => h("li", {
200
+ id: optionId(opt),
201
+ class: cn("hu-autocomplete__option", selectedValue() === opt.value && "hu-autocomplete__option--selected", activeIndex.value === index && "hu-autocomplete__option--focused", opt.disabled && "hu-autocomplete__option--disabled"),
81
202
  role: "option",
82
- "aria-selected": selected.value === opt.value,
203
+ "aria-selected": selectedValue() === opt.value ? "true" : "false",
83
204
  "aria-disabled": opt.disabled ? "true" : undefined,
205
+ onMouseEnter: () => { if (!opt.disabled)
206
+ activeIndex.value = index; },
84
207
  onMouseDown: (e) => {
85
208
  e.preventDefault();
86
- if (opt.disabled)
87
- return;
88
- selected.value = opt.value;
89
- inputVal.value = opt.label;
90
- isOpen.value = false;
91
- onChange?.(opt.value);
209
+ selectOption(opt);
92
210
  }
93
- }, opt.label, selected.value === opt.value && h("span", { class: "hu-autocomplete__check", "aria-hidden": "true" }, "✓"))))));
211
+ }, opt.label, selectedValue() === opt.value && h("span", { class: "hu-autocomplete__check", "aria-hidden": "true" }, "✓"))))), error && h("span", { id: errorId, class: "hu-autocomplete__error", role: "alert" }, error), !error && helper && h("span", { id: helperId, class: "hu-autocomplete__helper" }, helper));
212
+ }
213
+ function getSignalProp(value) {
214
+ return typeof value === "object" && value != null && "peek" in value ? value : undefined;
215
+ }
216
+ function toIdPart(value) {
217
+ return value.replace(/[^a-zA-Z0-9_-]/g, "-");
94
218
  }
@@ -1,6 +1,7 @@
1
+ import { type Signal } from "@hyperpackai/hyperion";
1
2
  import { type VNode } from "../../theme/index.js";
2
3
  export interface BackdropProps {
3
- open: boolean;
4
+ open: boolean | Signal<boolean>;
4
5
  invisible?: boolean;
5
6
  onClick?: (e: MouseEvent) => void;
6
7
  class?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Backdrop/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAiBpE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,GAAG,IAAI,CAQ3D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Backdrop/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAiBpE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,GAAG,IAAI,CAY3D"}
@@ -15,11 +15,14 @@ const CSS = `
15
15
  `;
16
16
  export function Backdrop(props) {
17
17
  injectCSS("hu-backdrop", CSS);
18
- if (!props.open)
19
- return null;
20
- return renderInPortal(h("div", {
18
+ const isSignalOpen = typeof props.open === "object" && props.open != null && "peek" in props.open;
19
+ const isOpen = () => isSignalOpen ? props.open.value : props.open;
20
+ const renderBackdrop = () => renderInPortal(h("div", {
21
21
  class: cn("hu-backdrop", props.invisible && "hu-backdrop--invisible", props.class),
22
22
  onClick: props.onClick,
23
23
  "aria-hidden": "true"
24
24
  }, props.children), "backdrop");
25
+ if (!isSignalOpen)
26
+ return isOpen() ? renderBackdrop() : null;
27
+ return (() => isOpen() ? renderBackdrop() : null);
25
28
  }
@@ -12,6 +12,7 @@ export interface CheckboxProps {
12
12
  description?: string;
13
13
  class?: string;
14
14
  onChange?: (e: Event) => void;
15
+ onCheckedChange?: (checked: boolean, e: Event) => void;
15
16
  }
16
17
  export declare function Checkbox(props: CheckboxProps): VNode;
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAuCpE,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;CAC/B;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,CAWpD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAuCpE,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,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,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,CAepD"}
@@ -37,6 +37,10 @@ const CHECKBOX_CSS = `
37
37
  `;
38
38
  export function Checkbox(props) {
39
39
  injectCSS("hu-checkbox", CHECKBOX_CSS);
40
- const { label, description, disabled, id, ...rest } = props;
41
- return h("label", { class: cn("hu-checkbox-wrap", disabled && "hu-checkbox-wrap--disabled", props.class) }, h("input", { ...rest, type: "checkbox", id, disabled, class: "hu-checkbox" }), (label || description) && h("span", { class: "hu-checkbox-text" }, label && h("span", { class: "hu-checkbox-label" }, label), description && h("span", { class: "hu-checkbox-desc" }, description)));
40
+ const { label, description, disabled, id, onChange, onCheckedChange, ...rest } = props;
41
+ const handleChange = (event) => {
42
+ onChange?.(event);
43
+ onCheckedChange?.(event.target.checked, event);
44
+ };
45
+ return h("label", { class: cn("hu-checkbox-wrap", disabled && "hu-checkbox-wrap--disabled", props.class) }, h("input", { ...rest, type: "checkbox", id, disabled, onChange: handleChange, class: "hu-checkbox" }), (label || description) && h("span", { class: "hu-checkbox-text" }, label && h("span", { class: "hu-checkbox-label" }, label), description && h("span", { class: "hu-checkbox-desc" }, description)));
42
46
  }
@@ -1,15 +1,28 @@
1
1
  import { type VNode } from "../../theme/index.js";
2
2
  export interface DashboardLayoutProps {
3
+ id?: string;
3
4
  header?: unknown;
4
5
  sidebar?: unknown;
5
6
  aside?: unknown;
7
+ footer?: unknown;
6
8
  sidebarWidth?: string | number;
7
9
  collapsedSidebarWidth?: string | number;
8
10
  asideWidth?: string | number;
9
11
  collapsed?: boolean;
10
12
  padding?: string | number;
13
+ skipLinkLabel?: string | null;
14
+ skipLinkTarget?: string;
15
+ headerLabel?: string;
16
+ sidebarLabel?: string;
17
+ asideLabel?: string;
18
+ footerLabel?: string;
19
+ mainLabel?: string;
20
+ testId?: string;
21
+ style?: string;
11
22
  class?: string;
12
23
  children?: unknown;
13
24
  }
14
25
  export declare function DashboardLayout(props: DashboardLayoutProps): VNode;
26
+ export type AppShellProps = DashboardLayoutProps;
27
+ export declare function AppShell(props: AppShellProps): VNode;
15
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/DashboardLayout/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAsDpE,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,KAAK,CAsBlE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/DashboardLayout/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAqEpE,MAAM,WAAW,oBAAoB;IACnC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,KAAK,CAgDlE;AAED,MAAM,MAAM,aAAa,GAAG,oBAAoB,CAAC;AAEjD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,KAAK,CAEpD"}
@@ -4,11 +4,20 @@ const DASHBOARD_LAYOUT_CSS = `
4
4
  min-height: 100dvh; display: grid; color: var(--hu-text); background: var(--hu-bg-2);
5
5
  grid-template-areas:
6
6
  "sidebar header"
7
- "sidebar main";
7
+ "sidebar main"
8
+ "sidebar footer";
8
9
  grid-template-columns: var(--hu-dashboard-sidebar, 280px) minmax(0, 1fr);
9
- grid-template-rows: auto minmax(0, 1fr);
10
+ grid-template-rows: auto minmax(0, 1fr) auto;
10
11
  }
11
12
  .hu-dashboard-layout--collapsed { grid-template-columns: var(--hu-dashboard-sidebar-collapsed, 72px) minmax(0, 1fr); }
13
+ .hu-dashboard-layout__skip {
14
+ position: fixed; inset-inline-start: var(--hu-space-3); inset-block-start: var(--hu-space-3);
15
+ z-index: calc(var(--hu-z-toast) + 1); transform: translateY(-150%);
16
+ padding: var(--hu-space-2) var(--hu-space-3); border-radius: var(--hu-radius);
17
+ background: var(--hu-primary); color: var(--hu-primary-text); text-decoration: none;
18
+ font-weight: var(--hu-font-weight-semibold);
19
+ }
20
+ .hu-dashboard-layout__skip:focus { transform: translateY(0); outline: 2px solid var(--hu-primary-text); outline-offset: 2px; }
12
21
  .hu-dashboard-layout__header {
13
22
  grid-area: header; min-width: 0; position: sticky; inset-block-start: 0;
14
23
  z-index: var(--hu-z-sticky); background: var(--hu-bg);
@@ -26,10 +35,15 @@ const DASHBOARD_LAYOUT_CSS = `
26
35
  border-inline-start: 1px solid var(--hu-border); background: var(--hu-bg);
27
36
  min-width: 0; overflow: auto;
28
37
  }
38
+ .hu-dashboard-layout__footer {
39
+ grid-area: footer; min-width: 0; background: var(--hu-bg);
40
+ border-block-start: 1px solid var(--hu-border);
41
+ }
29
42
  .hu-dashboard-layout--with-aside {
30
43
  grid-template-areas:
31
44
  "sidebar header header"
32
- "sidebar main aside";
45
+ "sidebar main aside"
46
+ "sidebar footer aside";
33
47
  grid-template-columns: var(--hu-dashboard-sidebar, 280px) minmax(0, 1fr) var(--hu-dashboard-aside, 320px);
34
48
  }
35
49
  @media (max-width: 768px) {
@@ -37,9 +51,10 @@ const DASHBOARD_LAYOUT_CSS = `
37
51
  .hu-dashboard-layout--with-aside {
38
52
  grid-template-areas:
39
53
  "header"
40
- "main";
54
+ "main"
55
+ "footer";
41
56
  grid-template-columns: minmax(0, 1fr);
42
- grid-template-rows: auto minmax(0, 1fr);
57
+ grid-template-rows: auto minmax(0, 1fr) auto;
43
58
  }
44
59
  .hu-dashboard-layout__sidebar,
45
60
  .hu-dashboard-layout__aside { display: none; }
@@ -60,8 +75,36 @@ export function DashboardLayout(props) {
60
75
  styles.push(`--hu-dashboard-aside:${toCssUnit(props.asideWidth)}`);
61
76
  if (props.padding !== undefined)
62
77
  styles.push(`--hu-dashboard-padding:${toCssUnit(props.padding)}`);
78
+ if (props.style)
79
+ styles.push(props.style);
80
+ const mainId = props.skipLinkTarget?.replace(/^#/, "") ?? (props.id ? `${props.id}-main` : "hu-dashboard-main");
63
81
  return h("div", {
82
+ id: props.id,
64
83
  class: cn("hu-dashboard-layout", props.collapsed && "hu-dashboard-layout--collapsed", props.aside != null && "hu-dashboard-layout--with-aside", props.class),
65
- style: styles.join(";") || undefined
66
- }, props.header && h("header", { class: "hu-dashboard-layout__header" }, props.header), props.sidebar && h("aside", { class: "hu-dashboard-layout__sidebar", "aria-label": "Primary navigation" }, props.sidebar), h("main", { class: "hu-dashboard-layout__main" }, props.children), props.aside && h("aside", { class: "hu-dashboard-layout__aside", "aria-label": "Secondary panel" }, props.aside));
84
+ style: styles.join(";") || undefined,
85
+ "data-testid": props.testId
86
+ }, props.skipLinkLabel !== null && h("a", {
87
+ class: "hu-dashboard-layout__skip",
88
+ href: `#${mainId}`
89
+ }, props.skipLinkLabel ?? "Skip to main content"), props.header && h("header", {
90
+ class: "hu-dashboard-layout__header",
91
+ "aria-label": props.headerLabel
92
+ }, props.header), props.sidebar && h("aside", {
93
+ class: "hu-dashboard-layout__sidebar",
94
+ "aria-label": props.sidebarLabel ?? "Primary navigation"
95
+ }, props.sidebar), h("main", {
96
+ id: mainId,
97
+ class: "hu-dashboard-layout__main",
98
+ tabindex: "-1",
99
+ "aria-label": props.mainLabel
100
+ }, props.children), props.aside && h("aside", {
101
+ class: "hu-dashboard-layout__aside",
102
+ "aria-label": props.asideLabel ?? "Secondary panel"
103
+ }, props.aside), props.footer && h("footer", {
104
+ class: "hu-dashboard-layout__footer",
105
+ "aria-label": props.footerLabel
106
+ }, props.footer));
107
+ }
108
+ export function AppShell(props) {
109
+ return DashboardLayout(props);
67
110
  }
@@ -2,26 +2,69 @@ import { type VNode } from "../../theme/index.js";
2
2
  export interface TableColumn<T> {
3
3
  key: string;
4
4
  header: string;
5
+ id?: string;
5
6
  width?: string;
6
7
  sortable?: boolean;
7
8
  render?: (row: T, i: number) => unknown;
8
9
  align?: "left" | "center" | "right";
10
+ headerLabel?: string;
11
+ cellLabel?: (row: T, i: number) => string;
9
12
  }
13
+ export interface DataTableSortEvent {
14
+ key: string;
15
+ direction: "asc" | "desc";
16
+ }
17
+ export interface DataTableRowEvent<T> {
18
+ row: T;
19
+ key: string;
20
+ index: number;
21
+ }
22
+ export interface DataTableSelectionEvent<T> {
23
+ row: T;
24
+ key: string;
25
+ selected: boolean;
26
+ }
27
+ export interface DataTableSelectAllEvent<T> {
28
+ selected: boolean;
29
+ keys: string[];
30
+ rows: T[];
31
+ }
32
+ export type DataTableDensity = "compact" | "default" | "comfortable";
10
33
  export interface DataTableProps<T extends Record<string, unknown>> {
34
+ id?: string;
11
35
  columns: TableColumn<T>[];
12
36
  rows: T[];
13
37
  rowKey?: (row: T) => string;
38
+ caption?: string;
39
+ "aria-label"?: string;
40
+ "aria-labelledby"?: string;
41
+ density?: DataTableDensity;
42
+ stickyHeader?: boolean;
43
+ maxHeight?: string | number;
14
44
  sortColumn?: string;
15
45
  sortDir?: "asc" | "desc";
16
46
  onSort?: (key: string) => void;
47
+ onSortChange?: (event: DataTableSortEvent) => void;
17
48
  onRowClick?: (row: T) => void;
49
+ onRowAction?: (event: DataTableRowEvent<T>) => void;
50
+ isRowDisabled?: (row: T, index: number) => boolean;
51
+ getRowLabel?: (row: T, index: number) => string;
18
52
  emptyMessage?: string;
53
+ emptyState?: unknown;
54
+ loading?: boolean;
55
+ loadingState?: unknown;
56
+ error?: unknown;
57
+ errorState?: unknown;
19
58
  footer?: unknown;
20
59
  selectable?: boolean;
21
60
  selectedRows?: Set<string>;
22
61
  onSelectRow?: (key: string, selected: boolean) => void;
62
+ onSelectionChange?: (event: DataTableSelectionEvent<T>) => void;
23
63
  onSelectAll?: (selected: boolean) => void;
64
+ onSelectAllChange?: (event: DataTableSelectAllEvent<T>) => void;
24
65
  class?: string;
66
+ style?: string;
67
+ testId?: string;
25
68
  }
26
69
  export declare function DataTable<T extends Record<string, unknown>>(props: DataTableProps<T>): VNode;
27
70
  //# sourceMappingURL=index.d.ts.map