@iris-ui-kit/react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/LICENSE +21 -0
  2. package/dist/SidebarLayout-fWQRvK0a.d.cts +26 -0
  3. package/dist/SidebarLayout-fWQRvK0a.d.ts +26 -0
  4. package/dist/admin.cjs +1583 -0
  5. package/dist/admin.cjs.map +1 -0
  6. package/dist/admin.d.cts +183 -0
  7. package/dist/admin.d.ts +183 -0
  8. package/dist/admin.js +9 -0
  9. package/dist/admin.js.map +1 -0
  10. package/dist/async.cjs +97 -0
  11. package/dist/async.cjs.map +1 -0
  12. package/dist/async.d.cts +57 -0
  13. package/dist/async.d.ts +57 -0
  14. package/dist/async.js +5 -0
  15. package/dist/async.js.map +1 -0
  16. package/dist/behaviors.cjs +569 -0
  17. package/dist/behaviors.cjs.map +1 -0
  18. package/dist/behaviors.d.cts +188 -0
  19. package/dist/behaviors.d.ts +188 -0
  20. package/dist/behaviors.js +4 -0
  21. package/dist/behaviors.js.map +1 -0
  22. package/dist/chunk-2TQSTINK.js +541 -0
  23. package/dist/chunk-2TQSTINK.js.map +1 -0
  24. package/dist/chunk-53KFLVIZ.js +68 -0
  25. package/dist/chunk-53KFLVIZ.js.map +1 -0
  26. package/dist/chunk-6BMFMCMQ.js +140 -0
  27. package/dist/chunk-6BMFMCMQ.js.map +1 -0
  28. package/dist/chunk-AUA5GBT6.js +143 -0
  29. package/dist/chunk-AUA5GBT6.js.map +1 -0
  30. package/dist/chunk-DE6V6NOI.js +122 -0
  31. package/dist/chunk-DE6V6NOI.js.map +1 -0
  32. package/dist/chunk-HEJGN6UI.js +78 -0
  33. package/dist/chunk-HEJGN6UI.js.map +1 -0
  34. package/dist/chunk-IDD35UJ6.js +83 -0
  35. package/dist/chunk-IDD35UJ6.js.map +1 -0
  36. package/dist/chunk-J5FB2UB2.js +166 -0
  37. package/dist/chunk-J5FB2UB2.js.map +1 -0
  38. package/dist/chunk-OMOTUHYI.js +66 -0
  39. package/dist/chunk-OMOTUHYI.js.map +1 -0
  40. package/dist/chunk-RZNYIFV2.js +34 -0
  41. package/dist/chunk-RZNYIFV2.js.map +1 -0
  42. package/dist/chunk-SJO74IB3.js +54 -0
  43. package/dist/chunk-SJO74IB3.js.map +1 -0
  44. package/dist/chunk-SORLRLS4.js +53 -0
  45. package/dist/chunk-SORLRLS4.js.map +1 -0
  46. package/dist/chunk-TO7YVSAI.js +64 -0
  47. package/dist/chunk-TO7YVSAI.js.map +1 -0
  48. package/dist/chunk-TQ33XJIK.js +22 -0
  49. package/dist/chunk-TQ33XJIK.js.map +1 -0
  50. package/dist/chunk-UWUUEZCD.js +950 -0
  51. package/dist/chunk-UWUUEZCD.js.map +1 -0
  52. package/dist/chunk-VWCJJRSC.js +1200 -0
  53. package/dist/chunk-VWCJJRSC.js.map +1 -0
  54. package/dist/chunk-WIHBIDVJ.js +54 -0
  55. package/dist/chunk-WIHBIDVJ.js.map +1 -0
  56. package/dist/chunk-XCK4RIXB.js +66 -0
  57. package/dist/chunk-XCK4RIXB.js.map +1 -0
  58. package/dist/chunk-YAHXR2CJ.js +48 -0
  59. package/dist/chunk-YAHXR2CJ.js.map +1 -0
  60. package/dist/chunk-YXIO5THG.js +4 -0
  61. package/dist/chunk-YXIO5THG.js.map +1 -0
  62. package/dist/chunk-ZKBDUWUD.js +156 -0
  63. package/dist/chunk-ZKBDUWUD.js.map +1 -0
  64. package/dist/data.cjs +112 -0
  65. package/dist/data.cjs.map +1 -0
  66. package/dist/data.d.cts +53 -0
  67. package/dist/data.d.ts +53 -0
  68. package/dist/data.js +5 -0
  69. package/dist/data.js.map +1 -0
  70. package/dist/error-boundary.cjs +106 -0
  71. package/dist/error-boundary.cjs.map +1 -0
  72. package/dist/error-boundary.d.cts +41 -0
  73. package/dist/error-boundary.d.ts +41 -0
  74. package/dist/error-boundary.js +6 -0
  75. package/dist/error-boundary.js.map +1 -0
  76. package/dist/floating.cjs +163 -0
  77. package/dist/floating.cjs.map +1 -0
  78. package/dist/floating.d.cts +92 -0
  79. package/dist/floating.d.ts +92 -0
  80. package/dist/floating.js +5 -0
  81. package/dist/floating.js.map +1 -0
  82. package/dist/form.cjs +159 -0
  83. package/dist/form.cjs.map +1 -0
  84. package/dist/form.d.cts +120 -0
  85. package/dist/form.d.ts +120 -0
  86. package/dist/form.js +5 -0
  87. package/dist/form.js.map +1 -0
  88. package/dist/i18n.cjs +100 -0
  89. package/dist/i18n.cjs.map +1 -0
  90. package/dist/i18n.d.cts +50 -0
  91. package/dist/i18n.d.ts +50 -0
  92. package/dist/i18n.js +5 -0
  93. package/dist/i18n.js.map +1 -0
  94. package/dist/index.cjs +16635 -0
  95. package/dist/index.cjs.map +1 -0
  96. package/dist/index.d.cts +2964 -0
  97. package/dist/index.d.ts +2964 -0
  98. package/dist/index.js +12217 -0
  99. package/dist/index.js.map +1 -0
  100. package/dist/layouts.cjs +386 -0
  101. package/dist/layouts.cjs.map +1 -0
  102. package/dist/layouts.d.cts +92 -0
  103. package/dist/layouts.d.ts +92 -0
  104. package/dist/layouts.js +6 -0
  105. package/dist/layouts.js.map +1 -0
  106. package/dist/modal-utils.cjs +168 -0
  107. package/dist/modal-utils.cjs.map +1 -0
  108. package/dist/modal-utils.d.cts +43 -0
  109. package/dist/modal-utils.d.ts +43 -0
  110. package/dist/modal-utils.js +4 -0
  111. package/dist/modal-utils.js.map +1 -0
  112. package/dist/motion.cjs +104 -0
  113. package/dist/motion.cjs.map +1 -0
  114. package/dist/motion.d.cts +41 -0
  115. package/dist/motion.d.ts +41 -0
  116. package/dist/motion.js +4 -0
  117. package/dist/motion.js.map +1 -0
  118. package/dist/provider.cjs +191 -0
  119. package/dist/provider.cjs.map +1 -0
  120. package/dist/provider.d.cts +62 -0
  121. package/dist/provider.d.ts +62 -0
  122. package/dist/provider.js +8 -0
  123. package/dist/provider.js.map +1 -0
  124. package/dist/resource.cjs +61 -0
  125. package/dist/resource.cjs.map +1 -0
  126. package/dist/resource.d.cts +21 -0
  127. package/dist/resource.d.ts +21 -0
  128. package/dist/resource.js +5 -0
  129. package/dist/resource.js.map +1 -0
  130. package/dist/skeletons.cjs +1304 -0
  131. package/dist/skeletons.cjs.map +1 -0
  132. package/dist/skeletons.d.cts +70 -0
  133. package/dist/skeletons.d.ts +70 -0
  134. package/dist/skeletons.js +8 -0
  135. package/dist/skeletons.js.map +1 -0
  136. package/dist/skins.cjs +134 -0
  137. package/dist/skins.cjs.map +1 -0
  138. package/dist/skins.d.cts +45 -0
  139. package/dist/skins.d.ts +45 -0
  140. package/dist/skins.js +5 -0
  141. package/dist/skins.js.map +1 -0
  142. package/dist/theme.cjs +100 -0
  143. package/dist/theme.cjs.map +1 -0
  144. package/dist/theme.d.cts +71 -0
  145. package/dist/theme.d.ts +71 -0
  146. package/dist/theme.js +5 -0
  147. package/dist/theme.js.map +1 -0
  148. package/dist/undo.cjs +73 -0
  149. package/dist/undo.cjs.map +1 -0
  150. package/dist/undo.d.cts +47 -0
  151. package/dist/undo.d.ts +47 -0
  152. package/dist/undo.js +4 -0
  153. package/dist/undo.js.map +1 -0
  154. package/package.json +61 -0
@@ -0,0 +1,1200 @@
1
+ 'use client'
2
+ import { useThemeOptional } from './chunk-XCK4RIXB.js';
3
+ import { IrisSidebarLayout, IrisHeaderLayout } from './chunk-J5FB2UB2.js';
4
+ import { useI18n } from './chunk-OMOTUHYI.js';
5
+ import { useFloating, useDismiss } from './chunk-6BMFMCMQ.js';
6
+ import { useStore } from './chunk-RZNYIFV2.js';
7
+ import * as React6 from 'react';
8
+ import { useMemo } from 'react';
9
+ import { composeEventHandlers, matchTypeahead, branchTrail, createExpansion, visibleNav, findNavPath, createFloatingMachine, createAdminShell, isClosable, isBranch, firstLeaf, findNavNode } from '@iris-ui-kit/core';
10
+ export { createTabsNav, filterNavByAccess, findNavNode, findNavPath, firstLeaf, flattenNav, isBranch, isClosable, nodeAllowsRoles, visibleNav } from '@iris-ui-kit/core';
11
+ import { resolveThemedIcon, defaultIconRegistry } from '@iris-ui-kit/icons';
12
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
13
+ import { createPortal } from 'react-dom';
14
+
15
+ function IrisIcon({
16
+ name,
17
+ size = 24,
18
+ strokeWidth = 2,
19
+ fill = false,
20
+ title,
21
+ registry = defaultIconRegistry,
22
+ style,
23
+ ...rest
24
+ }) {
25
+ const theme = useThemeOptional();
26
+ const icon = resolveThemedIcon(registry, name, theme);
27
+ if (!icon) return null;
28
+ const paint = fill ? { fill: "currentColor" } : {
29
+ fill: "none",
30
+ stroke: "currentColor",
31
+ strokeWidth,
32
+ strokeLinecap: "round",
33
+ strokeLinejoin: "round"
34
+ };
35
+ return /* @__PURE__ */ jsxs(
36
+ "svg",
37
+ {
38
+ ...rest,
39
+ xmlns: "http://www.w3.org/2000/svg",
40
+ viewBox: icon.viewBox ?? "0 0 24 24",
41
+ width: size,
42
+ height: size,
43
+ ...paint,
44
+ role: title ? "img" : void 0,
45
+ "aria-label": title || void 0,
46
+ "aria-hidden": title ? void 0 : true,
47
+ "data-iris-icon": name,
48
+ style: { display: "inline-block", verticalAlign: "middle", flexShrink: 0, ...style },
49
+ children: [
50
+ title ? /* @__PURE__ */ jsx("title", { children: title }) : null,
51
+ icon.nodes.map((node, i) => React6.createElement(node.tag, { key: i, ...node.attrs }))
52
+ ]
53
+ }
54
+ );
55
+ }
56
+ function IrisNavMenu({
57
+ items,
58
+ activeKey,
59
+ expandedKeys,
60
+ defaultExpandedKeys,
61
+ collapsed = false,
62
+ ariaLabel,
63
+ onSelect,
64
+ onExpandedKeysChange
65
+ }) {
66
+ const { t } = useI18n();
67
+ const trailOf = React6.useCallback(
68
+ (key) => key ? branchTrail(items, key) : [],
69
+ [items]
70
+ );
71
+ const isControlled = expandedKeys !== void 0;
72
+ const modelRef = React6.useRef(null);
73
+ if (modelRef.current === null) {
74
+ modelRef.current = createExpansion({
75
+ mode: "multiple",
76
+ defaultExpanded: defaultExpandedKeys ?? trailOf(activeKey)
77
+ });
78
+ }
79
+ const model = modelRef.current;
80
+ const internalExpanded = useStore(model.store);
81
+ const expanded = isControlled ? expandedKeys : internalExpanded;
82
+ React6.useEffect(() => {
83
+ if (isControlled) return;
84
+ model.merge(trailOf(activeKey));
85
+ }, [activeKey, isControlled, model, trailOf]);
86
+ const setExpanded = (keys) => {
87
+ if (!isControlled) model.set(keys);
88
+ onExpandedKeysChange?.(keys);
89
+ };
90
+ const toggle = (key) => setExpanded(expanded.includes(key) ? expanded.filter((k) => k !== key) : [...expanded, key]);
91
+ const select = (node) => {
92
+ if (node.disabled) return;
93
+ onSelect?.(node.key, node);
94
+ };
95
+ const [hovered, setHovered] = React6.useState(null);
96
+ const tree = visibleNav(items);
97
+ const activePath = activeKey ? findNavPath(items, activeKey).map((n) => n.key) : [];
98
+ const itemStyle = (opts) => ({
99
+ display: "flex",
100
+ alignItems: "center",
101
+ gap: 10,
102
+ width: "100%",
103
+ boxSizing: "border-box",
104
+ border: "none",
105
+ borderRadius: "var(--iris-radius-md, 6px)",
106
+ background: opts.active ? "var(--iris-primary)" : opts.hovered && !opts.disabled ? "var(--iris-surface)" : "transparent",
107
+ color: opts.active ? "var(--iris-primary-foreground, #fff)" : opts.trail ? "var(--iris-primary)" : "var(--iris-foreground)",
108
+ font: "inherit",
109
+ fontSize: 14,
110
+ fontWeight: opts.active || opts.trail ? 600 : 400,
111
+ textAlign: "start",
112
+ cursor: opts.disabled ? "not-allowed" : "pointer",
113
+ opacity: opts.disabled ? 0.5 : 1,
114
+ padding: collapsed ? 10 : `8px 10px 8px ${12 + opts.depth * 16}px`,
115
+ justifyContent: collapsed ? "center" : "flex-start"
116
+ });
117
+ const iconNode = (node, size = 18) => node.icon ? /* @__PURE__ */ jsx(IrisIcon, { name: node.icon, size }) : null;
118
+ const badgeNode = (node) => node.badge !== void 0 && node.badge !== "" && !collapsed ? /* @__PURE__ */ jsx(
119
+ "span",
120
+ {
121
+ "data-iris-nav-badge": "",
122
+ style: {
123
+ marginInlineStart: "auto",
124
+ fontSize: 11,
125
+ lineHeight: 1,
126
+ padding: "2px 6px",
127
+ borderRadius: 999,
128
+ background: "var(--iris-danger, #e5484d)",
129
+ color: "#fff"
130
+ },
131
+ children: String(node.badge)
132
+ }
133
+ ) : null;
134
+ const hoverProps = (key) => ({
135
+ onMouseEnter: () => setHovered(key),
136
+ onMouseLeave: () => setHovered((h) => h === key ? null : h)
137
+ });
138
+ const renderCollapsed = (node) => {
139
+ const branch = isBranch(node);
140
+ const active = branch ? activePath.includes(node.key) : node.key === activeKey;
141
+ return /* @__PURE__ */ jsx(
142
+ "button",
143
+ {
144
+ type: "button",
145
+ "data-iris-nav-item": "",
146
+ "data-key": node.key,
147
+ "data-active": active ? "true" : void 0,
148
+ "data-branch": branch ? "true" : void 0,
149
+ disabled: node.disabled,
150
+ title: node.title,
151
+ "aria-label": branch ? `${node.title} (section)` : node.title,
152
+ "aria-current": active && !branch ? "page" : void 0,
153
+ style: itemStyle({
154
+ depth: 0,
155
+ active,
156
+ trail: false,
157
+ disabled: !!node.disabled,
158
+ hovered: hovered === node.key
159
+ }),
160
+ ...hoverProps(node.key),
161
+ onClick: () => select(branch ? firstLeaf(node) : node),
162
+ children: iconNode(node, 20) ?? /* @__PURE__ */ jsx("span", { style: { fontWeight: 700 }, children: node.title.charAt(0) })
163
+ },
164
+ node.key
165
+ );
166
+ };
167
+ const renderItem = (node, depth) => {
168
+ const branch = isBranch(node);
169
+ const open = expanded.includes(node.key);
170
+ const active = node.key === activeKey;
171
+ const trail = branch && activePath.includes(node.key);
172
+ const row = /* @__PURE__ */ jsxs(
173
+ "button",
174
+ {
175
+ type: "button",
176
+ "data-iris-nav-item": "",
177
+ "data-key": node.key,
178
+ "data-branch": branch ? "true" : void 0,
179
+ "data-active": active ? "true" : void 0,
180
+ "data-open": branch && open ? "true" : void 0,
181
+ "data-depth": String(depth),
182
+ disabled: node.disabled,
183
+ "aria-expanded": branch ? open ? "true" : "false" : void 0,
184
+ "aria-current": active ? "page" : void 0,
185
+ style: itemStyle({
186
+ depth,
187
+ active,
188
+ trail,
189
+ disabled: !!node.disabled,
190
+ hovered: hovered === node.key
191
+ }),
192
+ ...hoverProps(node.key),
193
+ onClick: () => branch ? toggle(node.key) : select(node),
194
+ children: [
195
+ iconNode(node),
196
+ /* @__PURE__ */ jsx(
197
+ "span",
198
+ {
199
+ style: {
200
+ flex: branch ? 1 : "0 1 auto",
201
+ overflow: "hidden",
202
+ textOverflow: "ellipsis",
203
+ whiteSpace: "nowrap"
204
+ },
205
+ children: node.title
206
+ }
207
+ ),
208
+ badgeNode(node),
209
+ branch ? /* @__PURE__ */ jsx(
210
+ IrisIcon,
211
+ {
212
+ name: open ? "chevron-down" : "chevron-right",
213
+ size: 16,
214
+ style: { marginInlineStart: node.badge ? 6 : "auto", opacity: 0.6 }
215
+ }
216
+ ) : null
217
+ ]
218
+ }
219
+ );
220
+ if (!branch || !open) {
221
+ return /* @__PURE__ */ jsx("div", { "data-iris-nav-group": branch ? "" : void 0, children: row }, node.key);
222
+ }
223
+ return /* @__PURE__ */ jsxs("div", { "data-iris-nav-group": "", "data-open": "true", children: [
224
+ row,
225
+ /* @__PURE__ */ jsx("div", { "data-iris-nav-children": "", role: "group", children: (node.children ?? []).map((child) => renderItem(child, depth + 1)) })
226
+ ] }, node.key);
227
+ };
228
+ const onKeyDown = (e) => {
229
+ const root = e.currentTarget;
230
+ const buttons = Array.from(root.querySelectorAll("[data-iris-nav-item]"));
231
+ if (buttons.length === 0) return;
232
+ const idx = buttons.indexOf(document.activeElement);
233
+ const focusAt = (i) => buttons[(i + buttons.length) % buttons.length]?.focus();
234
+ switch (e.key) {
235
+ case "ArrowDown":
236
+ e.preventDefault();
237
+ return focusAt(idx < 0 ? 0 : idx + 1);
238
+ case "ArrowUp":
239
+ e.preventDefault();
240
+ return focusAt(idx < 0 ? buttons.length - 1 : idx - 1);
241
+ case "Home":
242
+ e.preventDefault();
243
+ return void buttons[0]?.focus();
244
+ case "End":
245
+ e.preventDefault();
246
+ return void buttons[buttons.length - 1]?.focus();
247
+ }
248
+ if (collapsed || idx < 0) return;
249
+ const key = buttons[idx]?.getAttribute("data-key");
250
+ if (!key) return;
251
+ const node = findNavNode(items, key);
252
+ if (e.key === "ArrowRight" && node && isBranch(node)) {
253
+ e.preventDefault();
254
+ if (!expanded.includes(key)) toggle(key);
255
+ else focusAt(idx + 1);
256
+ } else if (e.key === "ArrowLeft") {
257
+ if (node && isBranch(node) && expanded.includes(key)) {
258
+ e.preventDefault();
259
+ toggle(key);
260
+ } else {
261
+ const parentKey = findNavPath(items, key).at(-2)?.key;
262
+ if (parentKey) {
263
+ e.preventDefault();
264
+ buttons.find((b) => b.getAttribute("data-key") === parentKey)?.focus();
265
+ }
266
+ }
267
+ }
268
+ };
269
+ return /* @__PURE__ */ jsx(
270
+ "nav",
271
+ {
272
+ "data-iris-nav-menu": "",
273
+ "data-collapsed": collapsed ? "true" : void 0,
274
+ "aria-label": ariaLabel ?? t("admin.nav"),
275
+ onKeyDown,
276
+ style: { display: "flex", flexDirection: "column", gap: 2 },
277
+ children: collapsed ? tree.map((node) => renderCollapsed(node)) : tree.map((node) => renderItem(node, 0))
278
+ }
279
+ );
280
+ }
281
+ var IrisBreadcrumb = React6.forwardRef(
282
+ function IrisBreadcrumb2({ separator = "/", children, ...rest }, ref) {
283
+ const { t } = useI18n();
284
+ const flat = React6.Children.toArray(children).filter(
285
+ (c) => React6.isValidElement(c)
286
+ );
287
+ const total = flat.length;
288
+ return /* @__PURE__ */ jsx(
289
+ "nav",
290
+ {
291
+ ...rest,
292
+ ref,
293
+ "aria-label": t("breadcrumb.label"),
294
+ "data-iris-breadcrumb": "",
295
+ children: /* @__PURE__ */ jsx(
296
+ "ol",
297
+ {
298
+ style: {
299
+ display: "inline-flex",
300
+ alignItems: "center",
301
+ gap: 6,
302
+ margin: 0,
303
+ padding: 0,
304
+ listStyle: "none",
305
+ fontSize: 14
306
+ },
307
+ children: flat.map((child, i) => {
308
+ const isLast = i === total - 1;
309
+ const itemNode = /* @__PURE__ */ jsx(
310
+ "li",
311
+ {
312
+ "data-iris-breadcrumb-item": "",
313
+ "data-iris-breadcrumb-last": isLast ? "true" : void 0,
314
+ style: { display: "inline-flex", alignItems: "center" },
315
+ children: React6.cloneElement(child, {
316
+ isCurrent: isLast
317
+ })
318
+ },
319
+ `item-${i}`
320
+ );
321
+ const sepNode = !isLast ? /* @__PURE__ */ jsx(
322
+ "li",
323
+ {
324
+ "data-iris-breadcrumb-separator": "",
325
+ "aria-hidden": "true",
326
+ style: { color: "var(--iris-muted)", display: "inline-flex", alignItems: "center" },
327
+ children: separator
328
+ },
329
+ `sep-${i}`
330
+ ) : null;
331
+ return /* @__PURE__ */ jsxs(React6.Fragment, { children: [
332
+ itemNode,
333
+ sepNode
334
+ ] }, `pair-${i}`);
335
+ })
336
+ }
337
+ )
338
+ }
339
+ );
340
+ }
341
+ );
342
+ var IrisBreadcrumbItem = React6.forwardRef(
343
+ function IrisBreadcrumbItem2({ href, isCurrent = false, style, children, ...rest }, ref) {
344
+ const baseStyle = {
345
+ color: isCurrent ? "var(--iris-muted)" : "var(--iris-primary)",
346
+ textDecoration: "none",
347
+ cursor: isCurrent ? "default" : href ? "pointer" : "default"
348
+ };
349
+ const common = {
350
+ ...rest,
351
+ "aria-current": isCurrent ? "page" : void 0,
352
+ "data-iris-breadcrumb-crumb": "",
353
+ style: { ...baseStyle, ...style }
354
+ };
355
+ if (href && !isCurrent) {
356
+ return /* @__PURE__ */ jsx("a", { ...common, ref, href, children });
357
+ }
358
+ return /* @__PURE__ */ jsx("span", { ...common, ref, children });
359
+ }
360
+ );
361
+ function IrisAdminBreadcrumb({
362
+ trail,
363
+ showIcon = true,
364
+ hideSingle = false,
365
+ separator = "/",
366
+ onSelect
367
+ }) {
368
+ if (trail.length === 0 || hideSingle && trail.length === 1) return null;
369
+ return /* @__PURE__ */ jsx(IrisBreadcrumb, { separator, children: trail.map((node, i) => {
370
+ const last = i === trail.length - 1;
371
+ return /* @__PURE__ */ jsxs(
372
+ IrisBreadcrumbItem,
373
+ {
374
+ "data-iris-admin-crumb": "",
375
+ style: {
376
+ display: "inline-flex",
377
+ alignItems: "center",
378
+ cursor: last ? "default" : "pointer"
379
+ },
380
+ onClick: last ? void 0 : () => onSelect?.(node.key, node),
381
+ children: [
382
+ showIcon && node.icon ? /* @__PURE__ */ jsx(IrisIcon, { name: node.icon, size: 14, style: { marginInlineEnd: 4 } }) : null,
383
+ /* @__PURE__ */ jsx("span", { children: node.title })
384
+ ]
385
+ },
386
+ node.key
387
+ );
388
+ }) });
389
+ }
390
+
391
+ // src/admin/useTabsNav.ts
392
+ function useTabsNav(nav) {
393
+ const state = useStore(nav.store);
394
+ return {
395
+ tabs: state.tabs,
396
+ activeKey: state.activeKey,
397
+ cacheKeys: state.tabs.map((t) => nav.cacheKey(t.key)),
398
+ open: nav.open,
399
+ activate: nav.activate,
400
+ close: nav.close,
401
+ closeOthers: nav.closeOthers,
402
+ closeAll: nav.closeAll,
403
+ closeLeft: nav.closeLeft,
404
+ closeRight: nav.closeRight,
405
+ refresh: nav.refresh,
406
+ setPinned: nav.setPinned,
407
+ cacheKey: nav.cacheKey
408
+ };
409
+ }
410
+ function useMachine(factory) {
411
+ const machine = useMemo(factory, []);
412
+ const state = useStore(machine.store);
413
+ return [state, machine.send];
414
+ }
415
+ var DropdownContext = React6.createContext(null);
416
+ function useDropdownContext(componentName) {
417
+ const ctx = React6.useContext(DropdownContext);
418
+ if (!ctx) {
419
+ throw new Error(`[iris-ui] ${componentName} must be a descendant of <IrisDropdown>`);
420
+ }
421
+ return ctx;
422
+ }
423
+ function IrisDropdown({
424
+ open: openProp,
425
+ defaultOpen = false,
426
+ onOpenChange,
427
+ placement = "bottom-start",
428
+ offset = 6,
429
+ children
430
+ }) {
431
+ const isControlled = openProp !== void 0;
432
+ const [state, send] = useMachine(() => createFloatingMachine(defaultOpen ? "open" : "closed"));
433
+ const internalOpen = state.value === "open";
434
+ React6.useEffect(() => {
435
+ if (!isControlled) return;
436
+ const target = openProp ? "open" : "closed";
437
+ if (state.value !== target) {
438
+ send({ type: openProp ? "OPEN" : "CLOSE" });
439
+ }
440
+ }, [isControlled, openProp, state.value, send]);
441
+ const open = isControlled ? Boolean(openProp) : internalOpen;
442
+ const setOpen = React6.useCallback(
443
+ (next) => {
444
+ if (!isControlled) send({ type: next ? "OPEN" : "CLOSE" });
445
+ onOpenChange?.(next);
446
+ },
447
+ [isControlled, send, onOpenChange]
448
+ );
449
+ const triggerRef = React6.useRef(null);
450
+ const contentRef = React6.useRef(null);
451
+ const contentId = React6.useId();
452
+ const value = React6.useMemo(
453
+ () => ({
454
+ open,
455
+ setOpen,
456
+ triggerRef,
457
+ contentRef,
458
+ contentId,
459
+ placement,
460
+ offset
461
+ }),
462
+ [open, setOpen, contentId, placement, offset]
463
+ );
464
+ return /* @__PURE__ */ jsx(DropdownContext.Provider, { value, children });
465
+ }
466
+ function mergeRefs(...refs) {
467
+ return (value) => {
468
+ for (const ref of refs) {
469
+ if (!ref) continue;
470
+ if (typeof ref === "function") ref(value);
471
+ else ref.current = value;
472
+ }
473
+ };
474
+ }
475
+ function isEventHandlerName(key) {
476
+ return /^on[A-Z]/.test(key);
477
+ }
478
+ function mergeSlotProps(slot, child) {
479
+ const merged = { ...slot };
480
+ for (const key of Object.keys(child)) {
481
+ const slotValue = slot[key];
482
+ const childValue = child[key];
483
+ if (key === "style") {
484
+ merged.style = {
485
+ ...slotValue ?? {},
486
+ ...childValue ?? {}
487
+ };
488
+ } else if (key === "className") {
489
+ merged.className = [slotValue, childValue].filter(Boolean).join(" ").trim() || void 0;
490
+ } else if (isEventHandlerName(key) && typeof slotValue === "function" && typeof childValue === "function") {
491
+ merged[key] = composeEventHandlers(
492
+ slotValue,
493
+ childValue
494
+ );
495
+ } else if (childValue !== void 0) {
496
+ merged[key] = childValue;
497
+ }
498
+ }
499
+ return merged;
500
+ }
501
+ var IrisSlot = React6.forwardRef(function IrisSlot2({ children, ...slotProps }, forwardedRef) {
502
+ if (!React6.isValidElement(children)) return null;
503
+ const child = children;
504
+ const childProps = child.props ?? {};
505
+ const merged = mergeSlotProps(slotProps, childProps);
506
+ const childRef = child.ref ?? null;
507
+ if (forwardedRef || childRef) {
508
+ merged.ref = mergeRefs(
509
+ forwardedRef,
510
+ childRef
511
+ );
512
+ }
513
+ return React6.cloneElement(child, merged);
514
+ });
515
+ var IrisDropdownTrigger = React6.forwardRef(
516
+ function IrisDropdownTrigger2({ asChild = false, onClick, onKeyDown, children, ...rest }, forwardedRef) {
517
+ const ctx = useDropdownContext("IrisDropdownTrigger");
518
+ const captureRef = React6.useCallback(
519
+ (el) => {
520
+ ctx.triggerRef.current = el;
521
+ if (typeof forwardedRef === "function") forwardedRef(el);
522
+ else if (forwardedRef)
523
+ forwardedRef.current = el;
524
+ },
525
+ [ctx, forwardedRef]
526
+ );
527
+ const handleClick = composeEventHandlers(
528
+ onClick,
529
+ () => ctx.setOpen(!ctx.open)
530
+ );
531
+ const handleKeyDown = composeEventHandlers(
532
+ onKeyDown,
533
+ (e) => {
534
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
535
+ e.preventDefault();
536
+ ctx.setOpen(true);
537
+ }
538
+ }
539
+ );
540
+ const triggerProps = {
541
+ "aria-haspopup": "menu",
542
+ "aria-expanded": ctx.open,
543
+ "aria-controls": ctx.contentId,
544
+ "data-state": ctx.open ? "open" : "closed",
545
+ onClick: handleClick,
546
+ onKeyDown: handleKeyDown
547
+ };
548
+ if (asChild) {
549
+ if (!React6.isValidElement(children)) {
550
+ if (process.env.NODE_ENV !== "production") {
551
+ console.warn("[iris-ui] IrisDropdownTrigger asChild requires a single React element");
552
+ }
553
+ return null;
554
+ }
555
+ return /* @__PURE__ */ jsx(IrisSlot, { ref: captureRef, ...rest, ...triggerProps, children });
556
+ }
557
+ return /* @__PURE__ */ jsx(
558
+ "button",
559
+ {
560
+ type: "button",
561
+ ...rest,
562
+ ...triggerProps,
563
+ ref: captureRef,
564
+ children
565
+ }
566
+ );
567
+ }
568
+ );
569
+ var IrisDropdownMenu = React6.forwardRef(
570
+ function IrisDropdownMenu2({ portalTarget, style, onKeyDown, children, ...rest }, forwardedRef) {
571
+ const ctx = useDropdownContext("IrisDropdownMenu");
572
+ const innerRef = React6.useRef(null);
573
+ const captureRef = React6.useCallback(
574
+ (el) => {
575
+ innerRef.current = el;
576
+ ctx.contentRef.current = el;
577
+ if (typeof forwardedRef === "function") forwardedRef(el);
578
+ else if (forwardedRef)
579
+ forwardedRef.current = el;
580
+ },
581
+ [ctx, forwardedRef]
582
+ );
583
+ const { floatingStyles } = useFloating({
584
+ anchor: ctx.triggerRef,
585
+ floating: innerRef,
586
+ open: ctx.open,
587
+ placement: ctx.placement,
588
+ offset: ctx.offset
589
+ });
590
+ useDismiss({
591
+ enabled: ctx.open,
592
+ exclude: [ctx.triggerRef, ctx.contentRef],
593
+ onDismiss: () => ctx.setOpen(false)
594
+ });
595
+ const wasOpenRef = React6.useRef(false);
596
+ React6.useEffect(() => {
597
+ if (ctx.open && !wasOpenRef.current) {
598
+ queueMicrotask(() => {
599
+ const items = innerRef.current?.querySelectorAll('[role="menuitem"]');
600
+ items?.[0]?.focus();
601
+ });
602
+ } else if (!ctx.open && wasOpenRef.current) {
603
+ ctx.triggerRef.current?.focus?.();
604
+ }
605
+ wasOpenRef.current = ctx.open;
606
+ }, [ctx.open, ctx.triggerRef]);
607
+ const typeaheadRef = React6.useRef({ buffer: "", timer: null });
608
+ const handleKeyDown = (e) => {
609
+ onKeyDown?.(e);
610
+ if (!ctx.open) return;
611
+ const items = Array.from(
612
+ innerRef.current?.querySelectorAll(
613
+ '[role="menuitem"]:not([aria-disabled="true"])'
614
+ ) ?? []
615
+ );
616
+ if (items.length === 0) return;
617
+ const current = document.activeElement;
618
+ const index = current ? items.indexOf(current) : -1;
619
+ switch (e.key) {
620
+ case "ArrowDown": {
621
+ e.preventDefault();
622
+ const next = index < 0 ? 0 : (index + 1) % items.length;
623
+ items[next]?.focus();
624
+ break;
625
+ }
626
+ case "ArrowUp": {
627
+ e.preventDefault();
628
+ const next = index <= 0 ? items.length - 1 : index - 1;
629
+ items[next]?.focus();
630
+ break;
631
+ }
632
+ case "Home":
633
+ e.preventDefault();
634
+ items[0]?.focus();
635
+ break;
636
+ case "End":
637
+ e.preventDefault();
638
+ items[items.length - 1]?.focus();
639
+ break;
640
+ case "Tab":
641
+ ctx.setOpen(false);
642
+ break;
643
+ default: {
644
+ if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {
645
+ const ta = typeaheadRef.current;
646
+ ta.buffer += e.key;
647
+ if (ta.timer) clearTimeout(ta.timer);
648
+ ta.timer = setTimeout(() => {
649
+ ta.buffer = "";
650
+ }, 500);
651
+ const match = matchTypeahead(
652
+ items.map((it) => it.textContent ?? ""),
653
+ ta.buffer,
654
+ index
655
+ );
656
+ if (match >= 0) {
657
+ e.preventDefault();
658
+ items[match]?.focus();
659
+ }
660
+ }
661
+ }
662
+ }
663
+ };
664
+ if (!ctx.open) return null;
665
+ const node = /* @__PURE__ */ jsx(
666
+ "div",
667
+ {
668
+ ...rest,
669
+ ref: captureRef,
670
+ id: ctx.contentId,
671
+ role: "menu",
672
+ tabIndex: -1,
673
+ "data-iris-dropdown-menu": "",
674
+ "data-state": "open",
675
+ onKeyDown: handleKeyDown,
676
+ style: {
677
+ ...floatingStyles,
678
+ background: "var(--iris-surface)",
679
+ color: "var(--iris-foreground)",
680
+ border: "1px solid var(--iris-border)",
681
+ borderRadius: "var(--iris-radius-md, 6px)",
682
+ padding: "var(--iris-padding-sm, 4px)",
683
+ boxShadow: "0 8px 24px -8px rgba(0, 0, 0, 0.16), 0 4px 8px -2px rgba(0, 0, 0, 0.08)",
684
+ minWidth: 160,
685
+ outline: "none",
686
+ zIndex: 1e3,
687
+ ...style
688
+ },
689
+ children
690
+ }
691
+ );
692
+ if (portalTarget === false) return node;
693
+ if (typeof document === "undefined") return null;
694
+ return createPortal(node, portalTarget ?? document.body);
695
+ }
696
+ );
697
+ var IrisDropdownItem = React6.forwardRef(
698
+ function IrisDropdownItem2({ disabled = false, keepOpen = false, onSelect, onClick, onKeyDown, style, children, ...rest }, ref) {
699
+ const ctx = useDropdownContext("IrisDropdownItem");
700
+ const [hovered, setHovered] = React6.useState(false);
701
+ const fire = (event) => {
702
+ if (disabled) return;
703
+ onSelect?.(event);
704
+ if (!keepOpen) ctx.setOpen(false);
705
+ };
706
+ const handleClick = composeEventHandlers(
707
+ onClick,
708
+ (e) => fire(e)
709
+ );
710
+ const handleKeyDown = composeEventHandlers(
711
+ onKeyDown,
712
+ (e) => {
713
+ if (e.key === "Enter" || e.key === " ") {
714
+ e.preventDefault();
715
+ fire(e);
716
+ }
717
+ }
718
+ );
719
+ return /* @__PURE__ */ jsx(
720
+ "div",
721
+ {
722
+ ...rest,
723
+ ref,
724
+ role: "menuitem",
725
+ tabIndex: disabled ? -1 : 0,
726
+ "aria-disabled": disabled ? "true" : void 0,
727
+ "data-iris-dropdown-item": "",
728
+ "data-disabled": disabled ? "" : void 0,
729
+ onClick: handleClick,
730
+ onKeyDown: handleKeyDown,
731
+ onPointerEnter: () => setHovered(true),
732
+ onPointerLeave: () => setHovered(false),
733
+ onFocus: () => setHovered(true),
734
+ onBlur: () => setHovered(false),
735
+ style: {
736
+ display: "flex",
737
+ alignItems: "center",
738
+ gap: "var(--iris-gap-sm, 6px)",
739
+ padding: "6px var(--iris-padding-md, 12px)",
740
+ borderRadius: "var(--iris-radius-sm, 4px)",
741
+ cursor: disabled ? "not-allowed" : "pointer",
742
+ opacity: disabled ? 0.5 : 1,
743
+ background: hovered && !disabled ? "var(--iris-surface-hover)" : "transparent",
744
+ color: "inherit",
745
+ outline: "none",
746
+ fontSize: 14,
747
+ transition: "background-color 80ms ease",
748
+ ...style
749
+ },
750
+ children
751
+ }
752
+ );
753
+ }
754
+ );
755
+ var IrisDropdownSeparator = React6.forwardRef(
756
+ function IrisDropdownSeparator2({ style, ...rest }, ref) {
757
+ return /* @__PURE__ */ jsx(
758
+ "div",
759
+ {
760
+ ...rest,
761
+ ref,
762
+ role: "separator",
763
+ "data-iris-dropdown-separator": "",
764
+ style: {
765
+ height: 1,
766
+ background: "var(--iris-border)",
767
+ margin: "4px 0",
768
+ ...style
769
+ }
770
+ }
771
+ );
772
+ }
773
+ );
774
+ function IrisAdminTabs({
775
+ nav,
776
+ onChange,
777
+ onClose,
778
+ onRefresh
779
+ }) {
780
+ const t = useTabsNav(nav);
781
+ const { t: translate } = useI18n();
782
+ const tablistRef = React6.useRef(null);
783
+ const pendingFocus = React6.useRef(null);
784
+ React6.useEffect(() => {
785
+ if (!pendingFocus.current) return;
786
+ const key = pendingFocus.current;
787
+ pendingFocus.current = null;
788
+ tablistRef.current?.querySelectorAll("[data-iris-tab-label]").forEach((el) => {
789
+ if (el.getAttribute("data-key") === key) el.focus();
790
+ });
791
+ });
792
+ const activate = (key) => {
793
+ nav.activate(key);
794
+ onChange?.(key);
795
+ };
796
+ const close = (key) => {
797
+ nav.close(key);
798
+ onClose?.(key);
799
+ };
800
+ const refresh = (key) => {
801
+ nav.refresh(key);
802
+ onRefresh?.(key);
803
+ };
804
+ const onKeyDown = (e) => {
805
+ const tabs = t.tabs;
806
+ const idx = tabs.findIndex((x) => x.key === t.activeKey);
807
+ if (idx < 0 || tabs.length === 0) return;
808
+ if (e.key === "Delete" || e.key === "Backspace") {
809
+ const cur = tabs[idx];
810
+ if (cur && isClosable(cur)) {
811
+ e.preventDefault();
812
+ close(cur.key);
813
+ pendingFocus.current = nav.getState().activeKey ?? null;
814
+ }
815
+ return;
816
+ }
817
+ let target = -1;
818
+ if (e.key === "ArrowRight") target = (idx + 1) % tabs.length;
819
+ else if (e.key === "ArrowLeft") target = (idx - 1 + tabs.length) % tabs.length;
820
+ else if (e.key === "Home") target = 0;
821
+ else if (e.key === "End") target = tabs.length - 1;
822
+ else return;
823
+ e.preventDefault();
824
+ const key = tabs[target]?.key;
825
+ if (!key) return;
826
+ activate(key);
827
+ pendingFocus.current = key;
828
+ };
829
+ const chip = (tab) => {
830
+ const active = tab.key === t.activeKey;
831
+ const closable = isClosable(tab);
832
+ const label = /* @__PURE__ */ jsxs(
833
+ "button",
834
+ {
835
+ type: "button",
836
+ role: "tab",
837
+ "data-iris-tab-label": "",
838
+ "data-key": tab.key,
839
+ "aria-selected": active ? "true" : "false",
840
+ "aria-current": active ? "page" : void 0,
841
+ tabIndex: active ? 0 : -1,
842
+ style: {
843
+ display: "inline-flex",
844
+ alignItems: "center",
845
+ gap: 6,
846
+ border: "none",
847
+ background: "transparent",
848
+ color: "inherit",
849
+ font: "inherit",
850
+ fontSize: 13,
851
+ cursor: "pointer",
852
+ padding: 0
853
+ },
854
+ onClick: () => activate(tab.key),
855
+ children: [
856
+ tab.icon ? /* @__PURE__ */ jsx(IrisIcon, { name: tab.icon, size: 14 }) : null,
857
+ /* @__PURE__ */ jsx("span", { style: { whiteSpace: "nowrap" }, children: tab.title })
858
+ ]
859
+ }
860
+ );
861
+ const closeBtn = closable ? /* @__PURE__ */ jsx(
862
+ "button",
863
+ {
864
+ type: "button",
865
+ tabIndex: -1,
866
+ "data-iris-tab-close": "",
867
+ "aria-label": translate("admin.closeTab", { title: tab.title }),
868
+ style: {
869
+ display: "inline-flex",
870
+ alignItems: "center",
871
+ justifyContent: "center",
872
+ width: 16,
873
+ height: 16,
874
+ border: "none",
875
+ borderRadius: 4,
876
+ background: "transparent",
877
+ color: "inherit",
878
+ cursor: "pointer",
879
+ padding: 0,
880
+ opacity: 0.6
881
+ },
882
+ onClick: (e) => {
883
+ e.stopPropagation();
884
+ close(tab.key);
885
+ },
886
+ children: /* @__PURE__ */ jsx(IrisIcon, { name: "x", size: 12 })
887
+ }
888
+ ) : tab.pinned ? /* @__PURE__ */ jsx(IrisIcon, { name: "check-circle", size: 12, style: { opacity: 0.5 } }) : null;
889
+ return /* @__PURE__ */ jsxs(
890
+ "div",
891
+ {
892
+ "data-iris-tab": "",
893
+ "data-active": active ? "true" : void 0,
894
+ style: {
895
+ display: "inline-flex",
896
+ alignItems: "center",
897
+ gap: 4,
898
+ padding: "5px 8px 5px 10px",
899
+ borderRadius: "var(--iris-radius-md, 6px)",
900
+ border: `1px solid ${active ? "var(--iris-primary)" : "var(--iris-border)"}`,
901
+ background: active ? "var(--iris-primary)" : "var(--iris-surface)",
902
+ color: active ? "var(--iris-primary-foreground, #fff)" : "var(--iris-foreground)",
903
+ flex: "0 0 auto"
904
+ },
905
+ children: [
906
+ label,
907
+ closeBtn
908
+ ]
909
+ },
910
+ tab.key
911
+ );
912
+ };
913
+ const activeKey = t.activeKey;
914
+ return /* @__PURE__ */ jsxs(
915
+ "div",
916
+ {
917
+ "data-iris-admin-tabs": "",
918
+ style: {
919
+ display: "flex",
920
+ alignItems: "center",
921
+ gap: 6,
922
+ padding: "6px 10px",
923
+ borderBottom: "1px solid var(--iris-border)",
924
+ background: "var(--iris-background)"
925
+ },
926
+ children: [
927
+ /* @__PURE__ */ jsx(
928
+ "div",
929
+ {
930
+ ref: tablistRef,
931
+ "data-iris-tabs-scroll": "",
932
+ role: "tablist",
933
+ "aria-label": translate("admin.openPages"),
934
+ onKeyDown,
935
+ style: { display: "flex", alignItems: "center", gap: 6, overflowX: "auto", flex: 1 },
936
+ children: t.tabs.map((tab) => chip(tab))
937
+ }
938
+ ),
939
+ /* @__PURE__ */ jsxs(IrisDropdown, { children: [
940
+ /* @__PURE__ */ jsx(
941
+ IrisDropdownTrigger,
942
+ {
943
+ "aria-label": translate("admin.tabActions"),
944
+ style: {
945
+ display: "inline-flex",
946
+ alignItems: "center",
947
+ justifyContent: "center",
948
+ width: 28,
949
+ height: 28,
950
+ borderRadius: "var(--iris-radius-md, 6px)",
951
+ border: "1px solid var(--iris-border)",
952
+ background: "var(--iris-surface)",
953
+ color: "var(--iris-foreground)",
954
+ cursor: "pointer"
955
+ },
956
+ children: /* @__PURE__ */ jsx(IrisIcon, { name: "more-horizontal", size: 16 })
957
+ }
958
+ ),
959
+ /* @__PURE__ */ jsx(IrisDropdownMenu, { children: activeKey ? /* @__PURE__ */ jsxs(Fragment, { children: [
960
+ /* @__PURE__ */ jsx(IrisDropdownItem, { onSelect: () => refresh(activeKey), children: translate("admin.refresh") }),
961
+ /* @__PURE__ */ jsx(IrisDropdownItem, { onSelect: () => close(activeKey), children: translate("admin.close") }),
962
+ /* @__PURE__ */ jsx(IrisDropdownSeparator, {}),
963
+ /* @__PURE__ */ jsx(IrisDropdownItem, { onSelect: () => nav.closeOthers(activeKey), children: translate("admin.closeOthers") }),
964
+ /* @__PURE__ */ jsx(IrisDropdownItem, { onSelect: () => nav.closeAll(), children: translate("admin.closeAll") })
965
+ ] }) : /* @__PURE__ */ jsx(IrisDropdownItem, { disabled: true, children: "No active tab" }) })
966
+ ] })
967
+ ]
968
+ }
969
+ );
970
+ }
971
+ function useAdminShell(config) {
972
+ const { menus, activeKey, defaultActiveKey, onActiveKeyChange, onSelect, tabs } = config;
973
+ const activeControlled = activeKey !== void 0;
974
+ const latest = React6.useRef({ onActiveKeyChange, onSelect });
975
+ latest.current = { onActiveKeyChange, onSelect };
976
+ const shellRef = React6.useRef(null);
977
+ if (shellRef.current === null) {
978
+ shellRef.current = createAdminShell({
979
+ menus,
980
+ defaultActiveKey: activeKey ?? defaultActiveKey,
981
+ tabs,
982
+ onActiveChange: (key, node) => {
983
+ latest.current.onActiveKeyChange?.(key);
984
+ if (node) latest.current.onSelect?.(key, node);
985
+ }
986
+ });
987
+ }
988
+ const shell = shellRef.current;
989
+ const state = useStore(shell.store);
990
+ React6.useEffect(() => {
991
+ shell.setMenus(menus);
992
+ }, [shell, menus]);
993
+ React6.useEffect(() => {
994
+ if (activeControlled && shell.getActiveKey() !== (activeKey ?? null)) {
995
+ shell.store.setState({ activeKey: activeKey ?? null });
996
+ }
997
+ });
998
+ const current = activeControlled ? activeKey ?? "" : state.activeKey ?? "";
999
+ return {
1000
+ activeKey: current,
1001
+ navigate: shell.navigate,
1002
+ syncFromTab: shell.syncFromTab,
1003
+ breadcrumb: current ? findNavPath(menus, current) : []
1004
+ };
1005
+ }
1006
+ function TabsSync({
1007
+ nav,
1008
+ activeKey,
1009
+ onActivate
1010
+ }) {
1011
+ const state = useStore(nav.store);
1012
+ React6.useEffect(() => {
1013
+ if (state.activeKey && state.activeKey !== activeKey) onActivate(state.activeKey);
1014
+ }, [state.activeKey, activeKey, onActivate]);
1015
+ return null;
1016
+ }
1017
+ function IrisAdminLayout({
1018
+ menus,
1019
+ activeKey,
1020
+ defaultActiveKey,
1021
+ onActiveKeyChange,
1022
+ collapsed,
1023
+ defaultCollapsed = false,
1024
+ onCollapsedChange,
1025
+ mode = "sidebar",
1026
+ appTitle = "Iris Admin",
1027
+ tabs,
1028
+ showBreadcrumb = true,
1029
+ sidebarWidth = 240,
1030
+ collapsedWidth = 64,
1031
+ logo,
1032
+ toolbar,
1033
+ footer,
1034
+ onSelect,
1035
+ children
1036
+ }) {
1037
+ const { t } = useI18n();
1038
+ const {
1039
+ activeKey: currentActive,
1040
+ navigate,
1041
+ syncFromTab,
1042
+ breadcrumb: trail
1043
+ } = useAdminShell({ menus, activeKey, defaultActiveKey, onActiveKeyChange, onSelect, tabs });
1044
+ const collapseControlled = collapsed !== void 0;
1045
+ const [internalCollapsed, setInternalCollapsed] = React6.useState(defaultCollapsed);
1046
+ const currentCollapsed = collapseControlled ? collapsed : internalCollapsed;
1047
+ const setCollapsed = React6.useCallback(
1048
+ (next) => {
1049
+ if (!collapseControlled) setInternalCollapsed(next);
1050
+ onCollapsedChange?.(next);
1051
+ },
1052
+ [collapseControlled, onCollapsedChange]
1053
+ );
1054
+ const handleSelect = (_key, node) => navigate(node);
1055
+ const renderContent = () => typeof children === "function" ? children({ activeKey: currentActive }) : children;
1056
+ if (mode === "full-content") {
1057
+ return /* @__PURE__ */ jsx("div", { "data-iris-admin-layout": "", "data-mode": "full-content", style: { height: "100%" }, children: renderContent() });
1058
+ }
1059
+ const defaultLogo = (state) => /* @__PURE__ */ jsxs(
1060
+ "div",
1061
+ {
1062
+ "data-iris-admin-logo": "",
1063
+ style: {
1064
+ display: "flex",
1065
+ alignItems: "center",
1066
+ gap: 10,
1067
+ height: 52,
1068
+ padding: "0 16px",
1069
+ color: "var(--iris-foreground)",
1070
+ fontWeight: 700,
1071
+ fontSize: 16,
1072
+ whiteSpace: "nowrap",
1073
+ overflow: "hidden",
1074
+ flexShrink: 0
1075
+ },
1076
+ children: [
1077
+ /* @__PURE__ */ jsx(
1078
+ "span",
1079
+ {
1080
+ style: {
1081
+ display: "inline-flex",
1082
+ alignItems: "center",
1083
+ justifyContent: "center",
1084
+ width: 28,
1085
+ height: 28,
1086
+ borderRadius: "var(--iris-radius-md, 6px)",
1087
+ background: "var(--iris-primary)",
1088
+ color: "var(--iris-primary-foreground, #fff)",
1089
+ flexShrink: 0
1090
+ },
1091
+ children: /* @__PURE__ */ jsx(IrisIcon, { name: "menu", size: 18 })
1092
+ }
1093
+ ),
1094
+ state.collapsed ? null : /* @__PURE__ */ jsx("span", { children: appTitle })
1095
+ ]
1096
+ }
1097
+ );
1098
+ const renderLogo = (state) => typeof logo === "function" ? logo(state) : logo ?? defaultLogo(state);
1099
+ const collapseToggle = /* @__PURE__ */ jsx(
1100
+ "button",
1101
+ {
1102
+ type: "button",
1103
+ "data-iris-admin-collapse": "",
1104
+ "aria-label": currentCollapsed ? t("admin.expandSidebar") : t("admin.collapseSidebar"),
1105
+ "aria-pressed": currentCollapsed ? "true" : "false",
1106
+ style: {
1107
+ display: "inline-flex",
1108
+ alignItems: "center",
1109
+ justifyContent: "center",
1110
+ width: 32,
1111
+ height: 32,
1112
+ border: "none",
1113
+ borderRadius: "var(--iris-radius-md, 6px)",
1114
+ background: "transparent",
1115
+ color: "var(--iris-foreground)",
1116
+ cursor: "pointer",
1117
+ flexShrink: 0
1118
+ },
1119
+ onClick: () => setCollapsed(!currentCollapsed),
1120
+ children: /* @__PURE__ */ jsx(IrisIcon, { name: currentCollapsed ? "chevron-right" : "menu", size: 18 })
1121
+ }
1122
+ );
1123
+ const headerBar = /* @__PURE__ */ jsxs(
1124
+ "div",
1125
+ {
1126
+ "data-iris-admin-headerbar": "",
1127
+ style: {
1128
+ display: "flex",
1129
+ alignItems: "center",
1130
+ gap: 12,
1131
+ height: 52,
1132
+ padding: "0 16px",
1133
+ borderBottom: "1px solid var(--iris-border)",
1134
+ background: "var(--iris-background)"
1135
+ },
1136
+ children: [
1137
+ collapseToggle,
1138
+ showBreadcrumb ? /* @__PURE__ */ jsx(IrisAdminBreadcrumb, { trail, onSelect: handleSelect }) : null,
1139
+ /* @__PURE__ */ jsx("div", { style: { flex: 1 } }),
1140
+ toolbar ? /* @__PURE__ */ jsx("div", { "data-iris-admin-toolbar": "", style: { display: "flex", alignItems: "center", gap: 8 }, children: toolbar }) : null
1141
+ ]
1142
+ }
1143
+ );
1144
+ return /* @__PURE__ */ jsxs(
1145
+ IrisSidebarLayout,
1146
+ {
1147
+ "data-iris-admin-layout": "",
1148
+ "data-mode": "sidebar",
1149
+ collapsed: currentCollapsed,
1150
+ onCollapsedChange: setCollapsed,
1151
+ width: sidebarWidth,
1152
+ collapsedWidth,
1153
+ style: { height: "100vh" },
1154
+ sidebar: (state) => /* @__PURE__ */ jsxs(
1155
+ "div",
1156
+ {
1157
+ "data-iris-admin-sidebar": "",
1158
+ style: {
1159
+ display: "flex",
1160
+ flexDirection: "column",
1161
+ height: "100%",
1162
+ borderInlineEnd: "1px solid var(--iris-border)",
1163
+ background: "var(--iris-surface)"
1164
+ },
1165
+ children: [
1166
+ renderLogo(state),
1167
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, overflowY: "auto", overflowX: "hidden", padding: 8 }, children: /* @__PURE__ */ jsx(
1168
+ IrisNavMenu,
1169
+ {
1170
+ items: menus,
1171
+ activeKey: currentActive,
1172
+ collapsed: state.collapsed,
1173
+ onSelect: handleSelect
1174
+ }
1175
+ ) })
1176
+ ]
1177
+ }
1178
+ ),
1179
+ children: [
1180
+ tabs ? /* @__PURE__ */ jsx(TabsSync, { nav: tabs, activeKey: currentActive, onActivate: syncFromTab }) : null,
1181
+ /* @__PURE__ */ jsx(
1182
+ IrisHeaderLayout,
1183
+ {
1184
+ sticky: true,
1185
+ header: /* @__PURE__ */ jsxs("div", { "data-iris-admin-header": "", children: [
1186
+ headerBar,
1187
+ tabs ? /* @__PURE__ */ jsx(IrisAdminTabs, { nav: tabs }) : null
1188
+ ] }),
1189
+ footer,
1190
+ children: /* @__PURE__ */ jsx("div", { "data-iris-admin-content": "", style: { padding: 16 }, children: renderContent() })
1191
+ }
1192
+ )
1193
+ ]
1194
+ }
1195
+ );
1196
+ }
1197
+
1198
+ export { DropdownContext, IrisAdminBreadcrumb, IrisAdminLayout, IrisAdminTabs, IrisBreadcrumb, IrisBreadcrumbItem, IrisDropdown, IrisDropdownItem, IrisDropdownMenu, IrisDropdownSeparator, IrisDropdownTrigger, IrisIcon, IrisNavMenu, IrisSlot, useAdminShell, useDropdownContext, useMachine, useTabsNav };
1199
+ //# sourceMappingURL=chunk-VWCJJRSC.js.map
1200
+ //# sourceMappingURL=chunk-VWCJJRSC.js.map