@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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AA8BpE,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC/D,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1B,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IACvD,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,KAAK,CAkE5F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/index.ts"],"names":[],"mappings":"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"}
@@ -5,7 +5,13 @@ const TABLE_CSS = `
5
5
  border: 1px solid color-mix(in srgb, var(--hu-border) 82%, transparent);
6
6
  background: var(--hu-bg); box-shadow: var(--hu-shadow-sm);
7
7
  }
8
+ .hu-table-wrap--sticky { max-height: var(--hu-table-max-height, 560px); }
8
9
  .hu-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--hu-font-size-sm); min-width: 560px; }
10
+ .hu-table-caption {
11
+ caption-side: top; text-align: left; padding: var(--hu-space-3) var(--hu-space-4);
12
+ font-size: var(--hu-font-size-sm); font-weight: var(--hu-font-weight-semibold); color: var(--hu-text);
13
+ background: var(--hu-bg); border-bottom: 1px solid var(--hu-border);
14
+ }
9
15
  .hu-table th {
10
16
  padding: 10px 16px; text-align: left; font-size: 11px; font-weight: var(--hu-font-weight-semibold);
11
17
  color: var(--hu-text-3); text-transform: uppercase; letter-spacing: .05em;
@@ -13,47 +19,146 @@ const TABLE_CSS = `
13
19
  border-bottom: 1px solid var(--hu-border);
14
20
  white-space: nowrap;
15
21
  }
22
+ .hu-table--sticky-header thead th { position: sticky; top: 0; z-index: 1; }
16
23
  .hu-table th.hu-table-sortable { cursor: pointer; user-select: none; }
17
24
  .hu-table th.hu-table-sortable:hover { background: var(--hu-bg-3); color: var(--hu-text); }
25
+ .hu-table th.hu-table-sortable:focus-visible, .hu-table tbody tr[tabindex]:focus-visible { outline: 2px solid var(--hu-primary); outline-offset: -2px; }
18
26
  .hu-table-sort-icon { display: inline-flex; align-items: center; margin-left: 4px; opacity: .4; vertical-align: middle; }
19
27
  .hu-table-sort-icon--asc, .hu-table-sort-icon--desc { opacity: 1; color: var(--hu-primary); }
20
28
  .hu-table td { padding: 12px 16px; border-bottom: 1px solid var(--hu-border); color: var(--hu-text); vertical-align: middle; background: var(--hu-bg); }
21
29
  .hu-table tr:last-child td { border-bottom: none; }
22
30
  .hu-table tbody tr { transition: background var(--hu-duration) var(--hu-ease); }
23
31
  .hu-table tbody tr:hover td { background: color-mix(in srgb, var(--hu-primary-bg) 28%, var(--hu-bg-2)); }
32
+ .hu-table tbody tr[aria-selected="true"] td { background: color-mix(in srgb, var(--hu-primary-bg) 45%, var(--hu-bg)); }
33
+ .hu-table tbody tr[aria-disabled="true"] { opacity: .55; cursor: not-allowed; }
34
+ .hu-table tbody tr[aria-disabled="true"]:hover td { background: var(--hu-bg); }
24
35
  .hu-table-empty { text-align: center; padding: var(--hu-space-12) var(--hu-space-6); color: var(--hu-text-3); }
25
36
  .hu-table-footer { display: flex; align-items: center; justify-content: space-between; gap: var(--hu-space-3); flex-wrap: wrap; padding: var(--hu-space-3) var(--hu-space-4); border-top: 1px solid var(--hu-border); background: var(--hu-bg-2); font-size: var(--hu-font-size-xs); color: var(--hu-text-3); }
26
37
  .hu-table-checkbox-col { width: 40px; padding-left: 16px !important; }
27
38
  .hu-table input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--hu-primary); }
39
+ .hu-table--compact th { padding: 7px 12px; }
40
+ .hu-table--compact td { padding: 8px 12px; }
41
+ .hu-table--comfortable th { padding: 13px 18px; }
42
+ .hu-table--comfortable td { padding: 16px 18px; }
28
43
  `;
44
+ function toCssUnit(value) {
45
+ return typeof value === "number" ? `${value}px` : value;
46
+ }
29
47
  export function DataTable(props) {
30
48
  injectCSS("hu-table", TABLE_CSS);
31
- const { columns, rows, rowKey, sortColumn, sortDir, onSort, onRowClick, emptyMessage = "No data available.", footer, selectable, selectedRows, onSelectRow, onSelectAll } = props;
32
- const sortSVG = (dir) => dir === "asc"
33
- ? `<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor"><path d="M6 3l4 5H2l4-5z"/></svg>`
49
+ const { columns, rows, rowKey, sortColumn, sortDir, onSort, onRowClick, emptyMessage = "No data available.", emptyState, loading, loadingState, error, errorState, footer, selectable, selectedRows, onSelectRow, onSelectAll, density = "default" } = props;
50
+ const rowId = (row, index) => rowKey?.(row) ?? String(row["id"] ?? index);
51
+ const rowKeys = rows.map(rowId);
52
+ const stateColSpan = columns.length + (selectable ? 1 : 0);
53
+ const sortDirectionFor = (key) => sortColumn === key && sortDir === "asc" ? "desc" : "asc";
54
+ const handleSort = (key) => {
55
+ const direction = sortDirectionFor(key);
56
+ onSort?.(key);
57
+ props.onSortChange?.({ key, direction });
58
+ };
59
+ const handleSortKeyDown = (event, key) => {
60
+ if (event.key !== "Enter" && event.key !== " ")
61
+ return;
62
+ event.preventDefault();
63
+ handleSort(key);
64
+ };
65
+ const handleRowAction = (row, key, index) => {
66
+ if (props.isRowDisabled?.(row, index))
67
+ return;
68
+ onRowClick?.(row);
69
+ props.onRowAction?.({ row, key, index });
70
+ };
71
+ const handleRowKeyDown = (event, row, key, index) => {
72
+ if (event.key !== "Enter" && event.key !== " ")
73
+ return;
74
+ event.preventDefault();
75
+ handleRowAction(row, key, index);
76
+ };
77
+ const handleSelectAll = (selected) => {
78
+ onSelectAll?.(selected);
79
+ props.onSelectAllChange?.({ selected, keys: rowKeys, rows });
80
+ };
81
+ const handleSelectRow = (row, key, index, selected) => {
82
+ if (props.isRowDisabled?.(row, index))
83
+ return;
84
+ onSelectRow?.(key, selected);
85
+ props.onSelectionChange?.({ row, key, selected });
86
+ };
87
+ const sortIcon = (dir) => h("svg", {
88
+ width: "12",
89
+ height: "12",
90
+ viewBox: "0 0 12 12",
91
+ fill: "currentColor",
92
+ "aria-hidden": "true"
93
+ }, dir === "asc"
94
+ ? h("path", { d: "M6 3l4 5H2l4-5z" })
34
95
  : dir === "desc"
35
- ? `<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor"><path d="M6 9L2 4h8L6 9z"/></svg>`
36
- : `<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor" opacity=".4"><path d="M6 3l3 3H3l3-3zM6 9L3 6h6L6 9z"/></svg>`;
37
- const allSelected = rows.length > 0 && rows.every((r) => selectedRows?.has(rowKey?.(r) ?? String(r["id"] ?? "")));
38
- return h("div", { class: cn("hu-table-wrap", props.class) }, h("table", { class: "hu-table", role: "table" }, h("thead", {}, h("tr", {}, selectable && h("th", { class: "hu-table-checkbox-col" }, h("input", { type: "checkbox", checked: allSelected, onChange: (e) => onSelectAll?.(e.target.checked) })), ...columns.map((col) => h("th", {
96
+ ? h("path", { d: "M6 9L2 4h8L6 9z" })
97
+ : h("path", { d: "M6 3l3 3H3l3-3zM6 9L3 6h6L6 9z", opacity: ".4" }));
98
+ const allSelected = rows.length > 0 && rowKeys.every((key) => selectedRows?.has(key));
99
+ const wrapperStyle = [
100
+ props.maxHeight !== undefined && `--hu-table-max-height:${toCssUnit(props.maxHeight)}`,
101
+ props.style
102
+ ].filter(Boolean).join(";") || undefined;
103
+ return h("div", {
104
+ id: props.id,
105
+ class: cn("hu-table-wrap", props.stickyHeader && "hu-table-wrap--sticky", props.class),
106
+ style: wrapperStyle,
107
+ "data-testid": props.testId
108
+ }, h("table", {
109
+ class: cn("hu-table", density !== "default" && `hu-table--${density}`, props.stickyHeader && "hu-table--sticky-header"),
110
+ role: "table",
111
+ "aria-label": props["aria-label"],
112
+ "aria-labelledby": props["aria-labelledby"]
113
+ }, props.caption && h("caption", { class: "hu-table-caption" }, props.caption), h("thead", {}, h("tr", {}, selectable && h("th", { class: "hu-table-checkbox-col" }, h("input", {
114
+ type: "checkbox",
115
+ checked: allSelected,
116
+ "aria-label": "Select all rows",
117
+ "aria-checked": allSelected ? "true" : "false",
118
+ onChange: (e) => handleSelectAll(e.target.checked)
119
+ })), ...columns.map((col) => h("th", {
120
+ id: col.id ?? `hu-table-col-${col.key}`,
39
121
  key: col.key,
40
122
  class: col.sortable ? "hu-table-sortable" : undefined,
41
123
  style: [col.width && `width:${col.width}`, col.align && `text-align:${col.align}`].filter(Boolean).join(";"),
42
- onClick: col.sortable ? () => onSort?.(col.key) : undefined
124
+ tabindex: col.sortable ? "0" : undefined,
125
+ "aria-sort": sortColumn === col.key ? (sortDir === "desc" ? "descending" : "ascending") : undefined,
126
+ "aria-label": col.headerLabel,
127
+ onClick: col.sortable ? () => handleSort(col.key) : undefined,
128
+ onKeyDown: col.sortable ? (event) => handleSortKeyDown(event, col.key) : undefined
43
129
  }, col.header, col.sortable && h("span", {
44
130
  class: cn("hu-table-sort-icon", sortColumn === col.key && `hu-table-sort-icon--${sortDir}`),
45
- innerHTML: sortSVG(sortColumn === col.key ? sortDir ?? "" : ""),
46
131
  "aria-hidden": "true"
47
- }))))), h("tbody", {}, rows.length === 0
48
- ? h("tr", {}, h("td", { colspan: columns.length + (selectable ? 1 : 0), class: "hu-table-empty" }, emptyMessage))
49
- : rows.map((row, i) => {
50
- const key = rowKey?.(row) ?? String(row["id"] ?? i);
51
- return h("tr", {
52
- key, style: onRowClick ? "cursor:pointer" : undefined,
53
- onClick: onRowClick ? () => onRowClick(row) : undefined
54
- }, selectable && h("td", { class: "hu-table-checkbox-col" }, h("input", {
55
- type: "checkbox", checked: selectedRows?.has(key) ?? false,
56
- onChange: (e) => { e.stopPropagation(); onSelectRow?.(key, e.target.checked); }
57
- })), ...columns.map((col) => h("td", { key: col.key, style: col.align ? `text-align:${col.align}` : undefined }, col.render ? col.render(row, i) : row[col.key])));
58
- }))), footer && h("div", { class: "hu-table-footer" }, footer));
132
+ }, sortIcon(sortColumn === col.key ? sortDir ?? "" : "")))))), h("tbody", {}, loading
133
+ ? h("tr", {}, h("td", { colspan: stateColSpan, class: "hu-table-empty" }, loadingState ?? "Loading..."))
134
+ : error || errorState
135
+ ? h("tr", {}, h("td", { colspan: stateColSpan, class: "hu-table-empty", role: "alert" }, errorState ?? error))
136
+ : rows.length === 0
137
+ ? h("tr", {}, h("td", { colspan: stateColSpan, class: "hu-table-empty" }, emptyState ?? emptyMessage))
138
+ : rows.map((row, i) => {
139
+ const key = rowId(row, i);
140
+ const disabled = props.isRowDisabled?.(row, i) ?? false;
141
+ const selected = selectedRows?.has(key) ?? false;
142
+ const actionable = (onRowClick || props.onRowAction) && !disabled;
143
+ return h("tr", {
144
+ key,
145
+ style: actionable ? "cursor:pointer" : undefined,
146
+ tabindex: actionable ? "0" : undefined,
147
+ "aria-label": props.getRowLabel?.(row, i),
148
+ "aria-selected": selectable ? (selected ? "true" : "false") : undefined,
149
+ "aria-disabled": disabled ? "true" : undefined,
150
+ onClick: actionable ? () => handleRowAction(row, key, i) : undefined,
151
+ onKeyDown: actionable ? (event) => handleRowKeyDown(event, row, key, i) : undefined
152
+ }, selectable && h("td", { class: "hu-table-checkbox-col" }, h("input", {
153
+ type: "checkbox", checked: selected,
154
+ disabled,
155
+ "aria-label": `Select row ${i + 1}`,
156
+ onChange: (e) => { e.stopPropagation(); handleSelectRow(row, key, i, e.target.checked); }
157
+ })), ...columns.map((col) => h("td", {
158
+ key: col.key,
159
+ headers: col.id ?? `hu-table-col-${col.key}`,
160
+ style: col.align ? `text-align:${col.align}` : undefined,
161
+ "aria-label": col.cellLabel?.(row, i)
162
+ }, col.render ? col.render(row, i) : row[col.key])));
163
+ }))), footer && h("div", { class: "hu-table-footer" }, footer));
59
164
  }
@@ -1,15 +1,21 @@
1
1
  import { type Signal } from "@hyperpackai/hyperion";
2
2
  import { type VNode } from "../../theme/index.js";
3
+ export type DialogCloseReason = "backdrop" | "escape" | "close-button" | "programmatic";
4
+ export type DialogCloseButtonRenderer = (close: (reason?: DialogCloseReason) => void) => unknown;
3
5
  export interface DialogProps {
4
- open: boolean | Signal<boolean>;
5
- onClose?: (reason?: "backdrop" | "escape" | "close-button") => void;
6
- onOpenChange?: (open: boolean, reason?: "backdrop" | "escape" | "close-button") => void;
6
+ open?: boolean | Signal<boolean>;
7
+ defaultOpen?: boolean;
8
+ onClose?: (reason?: DialogCloseReason) => void;
9
+ onOpenChange?: (open: boolean, reason?: DialogCloseReason) => void;
7
10
  title?: string;
8
11
  description?: string;
12
+ id?: string;
9
13
  size?: "sm" | "md" | "lg" | "xl" | "full";
10
14
  closeOnBackdrop?: boolean;
11
15
  closeOnEscape?: boolean;
12
16
  showClose?: boolean;
17
+ closeIcon?: unknown;
18
+ closeButton?: unknown | DialogCloseButtonRenderer;
13
19
  footer?: unknown;
14
20
  footerAlign?: "start" | "end" | "between";
15
21
  class?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Dialog/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAsDpE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,cAAc,KAAK,IAAI,CAAC;IACpE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,cAAc,KAAK,IAAI,CAAC;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAC1C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,SAAS,CAAC;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,CAiEhD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Dialog/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAsDpE,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,QAAQ,GAAG,cAAc,GAAG,cAAc,CAAC;AACxF,MAAM,MAAM,yBAAyB,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,iBAAiB,KAAK,IAAI,KAAK,OAAO,CAAC;AAEjG,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC/C,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAC1C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,GAAG,yBAAyB,CAAC;IAClD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,SAAS,CAAC;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAID,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,CA4EhD"}
@@ -1,3 +1,4 @@
1
+ import { signal } from "@hyperpackai/hyperion";
1
2
  import { injectCSS, cn, h } from "../../theme/index.js";
2
3
  import { renderInPortal } from "../../portal.js";
3
4
  import { FocusTrap } from "../FocusTrap/index.js";
@@ -50,47 +51,62 @@ const DIALOG_CSS = `
50
51
  .hu-dialog-footer--start { justify-content: flex-start; }
51
52
  .hu-dialog-footer--between { justify-content: space-between; }
52
53
  `;
54
+ let dialogId = 0;
53
55
  export function Dialog(props) {
54
56
  injectCSS("hu-dialog", DIALOG_CSS);
55
- const { title, description, size = "md", closeOnBackdrop = true, closeOnEscape = true, showClose = true, footer, footerAlign = "end", onClose, onOpenChange, children } = props;
56
- const isSignalOpen = typeof props.open === "object" && "peek" in props.open;
57
- const isOpen = () => isSignalOpen
58
- ? props.open.value
59
- : props.open;
60
- const handleKeyDown = (e) => {
61
- if (closeOnEscape && e.key === "Escape") {
62
- onOpenChange?.(false, "escape");
63
- onClose?.("escape");
64
- }
57
+ const { title, description, size = "md", closeOnBackdrop = true, closeOnEscape = true, showClose = true, footer, footerAlign = "end", closeIcon, closeButton, onClose, onOpenChange, children } = props;
58
+ const generatedId = props.id ?? `hu-dialog-${++dialogId}`;
59
+ const titleId = title ? `${generatedId}-title` : undefined;
60
+ const descriptionId = description ? `${generatedId}-description` : undefined;
61
+ const signalOpen = getSignalProp(props.open);
62
+ const internalOpen = signal(props.defaultOpen ?? false);
63
+ const isControlled = typeof props.open === "boolean" || !!signalOpen;
64
+ const isOpen = () => signalOpen ? signalOpen.value : typeof props.open === "boolean" ? props.open : internalOpen.value;
65
+ const close = (reason = "programmatic") => {
66
+ if (signalOpen)
67
+ signalOpen.value = false;
68
+ else if (!isControlled)
69
+ internalOpen.value = false;
70
+ onOpenChange?.(false, reason);
71
+ onClose?.(reason);
72
+ };
73
+ const defaultCloseIcon = h("svg", {
74
+ viewBox: "0 0 16 16",
75
+ fill: "none",
76
+ stroke: "currentColor",
77
+ "stroke-width": "2",
78
+ "aria-hidden": "true"
79
+ }, h("path", { d: "M2 2l12 12M14 2L2 14" }));
80
+ const renderedCloseButton = () => {
81
+ if (typeof closeButton === "function")
82
+ return closeButton(close);
83
+ if (closeButton)
84
+ return closeButton;
85
+ return h("button", {
86
+ class: "hu-dialog-close",
87
+ type: "button",
88
+ onClick: () => close("close-button"),
89
+ "aria-label": "Close dialog"
90
+ }, closeIcon ?? defaultCloseIcon);
65
91
  };
66
- const closeSVG = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 2l12 12M14 2L2 14"/></svg>`;
67
92
  const renderDialog = () => h("div", {
68
93
  class: "hu-dialog-backdrop", role: "presentation", tabIndex: -1,
69
94
  onClick: closeOnBackdrop ? (e) => {
70
95
  if (e.target.classList.contains("hu-dialog-backdrop")) {
71
- onOpenChange?.(false, "backdrop");
72
- onClose?.("backdrop");
96
+ close("backdrop");
73
97
  }
74
- } : undefined,
75
- onKeyDown: handleKeyDown
76
- }, FocusTrap({ active: true, restoreFocus: true, autoFocus: true, onEscape: closeOnEscape ? () => {
77
- onOpenChange?.(false, "escape");
78
- onClose?.("escape");
79
- } : undefined, children: h("div", {
98
+ } : undefined
99
+ }, FocusTrap({ active: true, restoreFocus: true, autoFocus: true, onEscape: closeOnEscape ? () => close("escape") : undefined, children: h("div", {
80
100
  class: cn("hu-dialog", `hu-dialog--${size}`, props.class),
81
101
  role: "dialog", "aria-modal": "true",
82
- "aria-labelledby": title ? "hu-dialog-title" : undefined,
83
- "aria-describedby": description ? "hu-dialog-description" : undefined
84
- }, (title || showClose) && h("div", { class: "hu-dialog-header" }, title && h("div", {}, h("h2", { id: "hu-dialog-title", class: "hu-dialog-title" }, title), description && h("p", { id: "hu-dialog-description", class: "hu-dialog-desc" }, description)), showClose && h("button", {
85
- class: "hu-dialog-close",
86
- onClick: () => {
87
- onOpenChange?.(false, "close-button");
88
- onClose?.("close-button");
89
- },
90
- "aria-label": "Close dialog", innerHTML: closeSVG
91
- })), h("div", { class: "hu-dialog-body" }, children), footer && h("div", { class: cn("hu-dialog-footer", `hu-dialog-footer--${footerAlign}`) }, footer))
102
+ "aria-labelledby": titleId,
103
+ "aria-describedby": descriptionId
104
+ }, (title || showClose) && h("div", { class: "hu-dialog-header" }, title && h("div", {}, h("h2", { id: titleId, class: "hu-dialog-title" }, title), description && h("p", { id: descriptionId, class: "hu-dialog-desc" }, description)), showClose && renderedCloseButton()), h("div", { class: "hu-dialog-body" }, children), footer && h("div", { class: cn("hu-dialog-footer", `hu-dialog-footer--${footerAlign}`) }, footer))
92
105
  }));
93
- if (!isSignalOpen)
106
+ if (typeof props.open === "boolean")
94
107
  return isOpen() ? renderInPortal(renderDialog(), "dialog") : h("span", { "aria-hidden": "true" });
95
108
  return (() => isOpen() ? renderInPortal(renderDialog(), "dialog") : h("span", { "aria-hidden": "true" }));
96
109
  }
110
+ function getSignalProp(value) {
111
+ return typeof value === "object" && value != null && "peek" in value ? value : undefined;
112
+ }
@@ -1,15 +1,23 @@
1
+ import { type Signal } from "@hyperpackai/hyperion";
1
2
  import { type VNode } from "../../theme/index.js";
2
3
  export type DrawerAnchor = "left" | "right" | "top" | "bottom";
3
4
  export type DrawerVariant = "temporary" | "permanent" | "persistent";
4
5
  export type DrawerSize = "sm" | "md" | "lg";
6
+ export type DrawerCloseReason = "backdrop" | "escape" | "close-button";
7
+ export type DrawerCloseButtonRenderer = (close: (reason?: DrawerCloseReason) => void) => unknown;
5
8
  export interface DrawerProps {
6
- open: boolean;
9
+ open?: boolean | Signal<boolean>;
10
+ defaultOpen?: boolean;
7
11
  anchor?: DrawerAnchor;
8
12
  variant?: DrawerVariant;
9
13
  size?: DrawerSize;
10
14
  title?: string;
11
- onClose?: (reason?: "backdrop" | "close-button") => void;
12
- onOpenChange?: (open: boolean, reason?: "backdrop" | "close-button") => void;
15
+ closeIcon?: unknown;
16
+ closeButton?: unknown | DrawerCloseButtonRenderer;
17
+ closeOnBackdrop?: boolean;
18
+ closeOnEscape?: boolean;
19
+ onClose?: (reason?: DrawerCloseReason) => void;
20
+ onOpenChange?: (open: boolean, reason?: DrawerCloseReason) => void;
13
21
  class?: string;
14
22
  children?: unknown;
15
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Drawer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AA0DpE,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;AAC/D,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,WAAW,GAAG,YAAY,CAAC;AACrE,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE5C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,cAAc,KAAK,IAAI,CAAC;IACzD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,cAAc,KAAK,IAAI,CAAC;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,GAAG,IAAI,CA8BvD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Drawer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AA2DpE,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;AAC/D,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,WAAW,GAAG,YAAY,CAAC;AACrE,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAC5C,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,QAAQ,GAAG,cAAc,CAAC;AACvE,MAAM,MAAM,yBAAyB,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,iBAAiB,KAAK,IAAI,KAAK,OAAO,CAAC;AAEjG,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,GAAG,yBAAyB,CAAC;IAClD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC/C,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,GAAG,IAAI,CA0EvD"}
@@ -1,5 +1,7 @@
1
+ import { signal } from "@hyperpackai/hyperion";
1
2
  import { injectCSS, cn, h } from "../../theme/index.js";
2
3
  import { renderInPortal } from "../../portal.js";
4
+ import { FocusTrap } from "../FocusTrap/index.js";
3
5
  const CSS = `
4
6
  .hu-drawer-root { }
5
7
 
@@ -56,22 +58,75 @@ const CSS = `
56
58
  `;
57
59
  export function Drawer(props) {
58
60
  injectCSS("hu-drawer", CSS);
59
- const { open, anchor = "left", variant = "temporary", size = "md", title, onClose, onOpenChange } = props;
60
- const close = (reason) => {
61
+ const { anchor = "left", variant = "temporary", size = "md", title, closeOnBackdrop = true, closeOnEscape = true, onClose, onOpenChange } = props;
62
+ const uncontrolledOpen = signal(props.defaultOpen ?? false);
63
+ const isSignalOpen = typeof props.open === "object" && props.open != null && "peek" in props.open;
64
+ const isControlled = typeof props.open === "boolean" || isSignalOpen;
65
+ const isOpen = () => {
66
+ if (typeof props.open === "boolean")
67
+ return props.open;
68
+ if (isSignalOpen)
69
+ return props.open.value;
70
+ return uncontrolledOpen.value;
71
+ };
72
+ const close = (reason = "close-button") => {
73
+ if (!isControlled)
74
+ uncontrolledOpen.value = false;
75
+ if (isSignalOpen)
76
+ props.open.value = false;
61
77
  onOpenChange?.(false, reason);
62
78
  onClose?.(reason);
63
79
  };
64
- if (!open && variant === "temporary")
80
+ const open = isOpen();
81
+ if (!open && variant === "temporary" && isControlled && typeof props.open === "boolean")
65
82
  return null;
66
- if (!open && variant !== "permanent")
83
+ if (!open && variant !== "permanent" && isControlled && typeof props.open === "boolean")
67
84
  return null;
68
- const closeSVG = `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 2l12 12M14 2L2 14"/></svg>`;
69
- const panel = h("div", {
85
+ const closeIcon = props.closeIcon ?? h("svg", {
86
+ viewBox: "0 0 16 16",
87
+ width: "16",
88
+ height: "16",
89
+ fill: "none",
90
+ stroke: "currentColor",
91
+ "stroke-width": "2",
92
+ "aria-hidden": "true"
93
+ }, h("path", { d: "M2 2l12 12M14 2L2 14" }));
94
+ const showDefaultClose = props.closeButton === undefined && Boolean(onClose ?? onOpenChange ?? isSignalOpen ?? !isControlled);
95
+ const showCloseButton = props.closeButton !== null && (props.closeButton !== undefined || showDefaultClose);
96
+ const panel = () => h("div", {
70
97
  class: cn("hu-drawer", `hu-drawer--${anchor}`, `hu-drawer--${variant}`, size !== "md" && `hu-drawer--${size}`, props.class),
71
98
  role: "dialog",
72
- "aria-modal": variant === "temporary" ? "true" : undefined
73
- }, (title || onClose) && h("div", { class: "hu-drawer__header" }, title && h("span", { class: "hu-drawer__title" }, title), onClose && h("button", { class: "hu-drawer__close", onClick: () => close("close-button"), "aria-label": "Close drawer", innerHTML: closeSVG })), h("div", { class: "hu-drawer__content" }, props.children));
74
- if (variant !== "temporary")
75
- return panel;
76
- return renderInPortal(h("div", { class: "hu-drawer-root" }, h("div", { class: "hu-drawer-backdrop", onClick: () => close("backdrop") }), panel), "drawer");
99
+ "aria-modal": variant === "temporary" ? "true" : undefined,
100
+ }, (title || showCloseButton) && h("div", { class: "hu-drawer__header" }, title && h("span", { class: "hu-drawer__title" }, title), showCloseButton && renderDrawerCloseButton(props.closeButton, close, closeIcon)), h("div", { class: "hu-drawer__content" }, props.children));
101
+ const renderClosed = () => null;
102
+ const renderDrawer = () => {
103
+ if (!isOpen() && variant === "temporary")
104
+ return renderClosed();
105
+ if (!isOpen() && variant !== "permanent")
106
+ return renderClosed();
107
+ if (variant !== "temporary")
108
+ return panel();
109
+ return renderInPortal(h("div", { class: "hu-drawer-root" }, h("div", { class: "hu-drawer-backdrop", onClick: closeOnBackdrop ? () => close("backdrop") : undefined }), FocusTrap({
110
+ active: true,
111
+ restoreFocus: true,
112
+ autoFocus: true,
113
+ onEscape: closeOnEscape ? () => close("escape") : undefined,
114
+ children: panel()
115
+ })), "drawer");
116
+ };
117
+ if (isControlled && typeof props.open === "boolean")
118
+ return renderDrawer();
119
+ return renderDrawer;
120
+ }
121
+ function renderDrawerCloseButton(closeButton, close, closeIcon) {
122
+ if (typeof closeButton === "function")
123
+ return closeButton(close);
124
+ if (closeButton != null)
125
+ return closeButton;
126
+ return h("button", {
127
+ type: "button",
128
+ class: "hu-drawer__close",
129
+ onClick: () => close("close-button"),
130
+ "aria-label": "Close drawer"
131
+ }, closeIcon);
77
132
  }
@@ -11,14 +11,16 @@ export interface MenuItem {
11
11
  onClick?: () => void;
12
12
  children?: MenuItem[];
13
13
  }
14
+ export type DropdownMenuCloseReason = "escape" | "item-select" | "programmatic" | "trigger";
14
15
  export interface DropdownMenuProps {
15
- open: boolean | Signal<boolean>;
16
+ open?: boolean | Signal<boolean>;
17
+ defaultOpen?: boolean;
16
18
  items: MenuItem[];
17
19
  align?: "left" | "right";
18
20
  side?: "top" | "bottom";
19
21
  trigger?: unknown;
20
- onClose?: () => void;
21
- onOpenChange?: (open: boolean) => void;
22
+ onClose?: (reason?: DropdownMenuCloseReason) => void;
23
+ onOpenChange?: (open: boolean, reason?: DropdownMenuCloseReason) => void;
22
24
  class?: string;
23
25
  }
24
26
  export declare function DropdownMenu(props: DropdownMenuProps): VNode;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/DropdownMenu/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAmCpE,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC;IACtC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,CA4C5D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/DropdownMenu/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAoB,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAmCpE,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC;IACtC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,uBAAuB,GAAG,QAAQ,GAAG,aAAa,GAAG,cAAc,GAAG,SAAS,CAAC;AAE5F,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACrD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,CAsF5D"}
@@ -1,3 +1,4 @@
1
+ import { signal } from "@hyperpackai/hyperion";
1
2
  import { injectCSS, cn, h } from "../../theme/index.js";
2
3
  const DROPDOWN_CSS = `
3
4
  .hu-dropdown { position: relative; display: inline-block; }
@@ -34,23 +35,59 @@ const DROPDOWN_CSS = `
34
35
  export function DropdownMenu(props) {
35
36
  injectCSS("hu-dropdown", DROPDOWN_CSS);
36
37
  const { items, align = "left", side = "bottom", trigger, onClose, onOpenChange } = props;
37
- const isSignalOpen = typeof props.open === "object" && "peek" in props.open;
38
- const isOpen = () => isSignalOpen
39
- ? props.open.value
40
- : props.open;
41
- const close = () => {
42
- onOpenChange?.(false);
43
- onClose?.();
38
+ const internalOpen = signal(props.defaultOpen ?? false);
39
+ const signalOpen = getSignalProp(props.open);
40
+ const isControlled = typeof props.open === "boolean" || !!signalOpen;
41
+ const isOpen = () => signalOpen ? signalOpen.value : typeof props.open === "boolean" ? props.open : internalOpen.value;
42
+ const setOpen = (next, reason = "programmatic") => {
43
+ if (signalOpen)
44
+ signalOpen.value = next;
45
+ else if (!isControlled)
46
+ internalOpen.value = next;
47
+ onOpenChange?.(next, reason);
48
+ if (!next)
49
+ onClose?.(reason);
50
+ };
51
+ const close = (reason = "programmatic") => {
52
+ setOpen(false, reason);
53
+ };
54
+ const toggle = () => setOpen(!isOpen(), "trigger");
55
+ const handleMenuKeyDown = (e) => {
56
+ if (e.key === "Escape") {
57
+ e.preventDefault();
58
+ close("escape");
59
+ return;
60
+ }
61
+ if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(e.key))
62
+ return;
63
+ const menu = e.currentTarget;
64
+ const items = Array.from(menu.querySelectorAll('[role="menuitem"]:not(:disabled)'));
65
+ if (!items.length)
66
+ return;
67
+ e.preventDefault();
68
+ const activeIndex = items.indexOf(document.activeElement);
69
+ const lastIndex = items.length - 1;
70
+ if (e.key === "Home") {
71
+ items[0]?.focus();
72
+ return;
73
+ }
74
+ if (e.key === "End") {
75
+ items[lastIndex]?.focus();
76
+ return;
77
+ }
78
+ const delta = e.key === "ArrowDown" ? 1 : -1;
79
+ const nextIndex = activeIndex === -1
80
+ ? (delta === 1 ? 0 : lastIndex)
81
+ : (activeIndex + delta + items.length) % items.length;
82
+ items[nextIndex]?.focus();
44
83
  };
45
84
  const menuClass = `hu-dropdown-menu--${side}-${align}`;
46
85
  const renderMenu = () => isOpen()
47
86
  ? h("ul", {
48
87
  class: cn("hu-dropdown-menu", menuClass),
49
88
  role: "menu",
50
- onKeyDown: (e) => {
51
- if (e.key === "Escape")
52
- close();
53
- }
89
+ tabindex: "-1",
90
+ onKeyDown: handleMenuKeyDown
54
91
  }, ...items.map((item, i) => {
55
92
  if (item.type === "separator")
56
93
  return h("li", { key: `sep-${i}`, class: "hu-dropdown-separator", role: "separator" });
@@ -59,9 +96,15 @@ export function DropdownMenu(props) {
59
96
  return h("li", { key: item.id ?? item.label, role: "none" }, h("button", {
60
97
  class: cn("hu-dropdown-item", item.destructive && "hu-dropdown-item--destructive", item.disabled && "hu-dropdown-item--disabled"),
61
98
  role: "menuitem", disabled: item.disabled,
62
- onClick: () => { item.onClick?.(); close(); }
99
+ tabindex: "-1",
100
+ onClick: () => { item.onClick?.(); close("item-select"); }
63
101
  }, item.icon && h("span", { class: "hu-dropdown-item-icon", "aria-hidden": "true" }, item.icon), item.label, item.shortcut && h("span", { class: "hu-dropdown-item-shortcut" }, item.shortcut)));
64
102
  }))
65
103
  : null;
66
- return h("div", { class: cn("hu-dropdown", props.class) }, trigger, typeof props.open === "boolean" ? renderMenu() : renderMenu);
104
+ return h("div", { class: cn("hu-dropdown", props.class) }, trigger && (isControlled
105
+ ? trigger
106
+ : h("div", { onClick: toggle, style: "display:contents;" }, trigger)), typeof props.open === "boolean" ? renderMenu() : renderMenu);
107
+ }
108
+ function getSignalProp(value) {
109
+ return typeof value === "object" && value != null && "peek" in value ? value : undefined;
67
110
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/FocusTrap/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAK,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAWrD,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,KAAK,CAgEtD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/FocusTrap/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAK,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAWrD,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,KAAK,CAiEtD"}