@iris-ui-kit/react 0.1.1 → 0.2.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.
@@ -0,0 +1,107 @@
1
+ 'use client'
2
+ import * as React from 'react';
3
+ import { createSortable } from '@iris-ui-kit/core';
4
+ import { jsx } from 'react/jsx-runtime';
5
+
6
+ // src/behaviors/Sortable.tsx
7
+ var SORTABLE_ITEM_ATTR = "data-iris-sortable-item";
8
+ function IrisSortable({
9
+ items,
10
+ onReorder,
11
+ getKey = (_item, index) => String(index),
12
+ disabled = false,
13
+ orientation = "vertical",
14
+ style,
15
+ className,
16
+ children,
17
+ ...rest
18
+ }) {
19
+ const containerRef = React.useRef(null);
20
+ const ctrlRef = React.useRef(null);
21
+ if (ctrlRef.current === null) ctrlRef.current = createSortable();
22
+ const ctrl = ctrlRef.current;
23
+ const [state, setState] = React.useState({ activeId: null, overId: null });
24
+ React.useEffect(() => ctrl.subscribe(setState), [ctrl]);
25
+ const dragging = state.activeId;
26
+ const indexByKey = (key) => Array.from(containerRef.current?.querySelectorAll(`[${SORTABLE_ITEM_ATTR}]`) ?? []).findIndex(
27
+ (el) => el.getAttribute(SORTABLE_ITEM_ATTR) === key
28
+ );
29
+ const onPointerDown = (e) => {
30
+ if (disabled) return;
31
+ const target = e.target.closest(`[${SORTABLE_ITEM_ATTR}]`);
32
+ if (!target) return;
33
+ const key = target.getAttribute(SORTABLE_ITEM_ATTR) ?? "";
34
+ if (!key) return;
35
+ ctrl.press(key, e.clientX, e.clientY);
36
+ };
37
+ const onPointerMove = (e) => {
38
+ if (!ctrl.getState().activeId) {
39
+ if (!ctrl.tryStart(e.clientX, e.clientY)) return;
40
+ }
41
+ const els = containerRef.current?.querySelectorAll(`[${SORTABLE_ITEM_ATTR}]`);
42
+ const rects = Array.from(els ?? []).map((el) => ({
43
+ id: el.getAttribute(SORTABLE_ITEM_ATTR) ?? "",
44
+ ...el.getBoundingClientRect()
45
+ }));
46
+ ctrl.moveOver({ x: e.clientX, y: e.clientY }, rects);
47
+ };
48
+ const onPointerUp = () => {
49
+ const result = ctrl.end();
50
+ if (result.activeId && result.overId && result.activeId !== result.overId) {
51
+ const from = indexByKey(result.activeId);
52
+ const to = indexByKey(result.overId);
53
+ if (from >= 0 && to >= 0 && from !== to) {
54
+ const next = [...items];
55
+ const [moved] = next.splice(from, 1);
56
+ next.splice(to, 0, moved);
57
+ onReorder(next);
58
+ }
59
+ }
60
+ };
61
+ return /* @__PURE__ */ jsx(
62
+ "div",
63
+ {
64
+ ...rest,
65
+ ref: containerRef,
66
+ "data-iris-sortable": "",
67
+ "data-state": dragging ? "dragging" : "idle",
68
+ className,
69
+ style: {
70
+ display: "flex",
71
+ flexDirection: orientation === "horizontal" ? "row" : "column",
72
+ gap: "var(--iris-gap-sm, 4px)",
73
+ opacity: disabled ? 0.6 : 1,
74
+ userSelect: dragging ? "none" : void 0,
75
+ ...style
76
+ },
77
+ onPointerDown,
78
+ onPointerMove,
79
+ onPointerUp,
80
+ onPointerLeave: onPointerUp,
81
+ children: React.Children.map(children, (child, index) => {
82
+ if (!React.isValidElement(child)) return child;
83
+ const key = getKey(items[index], index);
84
+ const isDragging = key === dragging;
85
+ return /* @__PURE__ */ jsx(
86
+ "div",
87
+ {
88
+ "data-iris-sortable-item": key,
89
+ "data-iris-sortable-dragging": isDragging ? "" : void 0,
90
+ style: {
91
+ transition: isDragging ? "none" : "transform 150ms ease",
92
+ opacity: isDragging ? 0.4 : 1,
93
+ position: "relative",
94
+ zIndex: isDragging ? 100 : void 0
95
+ },
96
+ children: child
97
+ },
98
+ key
99
+ );
100
+ })
101
+ }
102
+ );
103
+ }
104
+
105
+ export { IrisSortable, SORTABLE_ITEM_ATTR };
106
+ //# sourceMappingURL=chunk-K2F7SC2T.js.map
107
+ //# sourceMappingURL=chunk-K2F7SC2T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/behaviors/Sortable.tsx"],"names":[],"mappings":"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","file":"chunk-K2F7SC2T.js","sourcesContent":["/**\n * Behavior wrapper: makes a list of items sortable via drag-and-drop.\n * Uses `createSortable` from `@iris-ui-kit/core` for the drag-and-drop controller.\n *\n * @example\n * const [items, setItems] = React.useState(['A', 'B', 'C'])\n * <IrisSortable items={items} onReorder={setItems}>\n * {items.map((label) => <div key={label}>{label}</div>)}\n * </IrisSortable>\n */\nimport * as React from 'react'\nimport { createSortable, type SortableState } from '@iris-ui-kit/core'\n\nexport interface IrisSortableProps<T = string> {\n items: readonly T[]\n onReorder: (next: T[]) => void\n getKey?: (item: T, index: number) => string\n children?: React.ReactNode\n className?: string\n style?: React.CSSProperties\n disabled?: boolean\n orientation?: 'vertical' | 'horizontal'\n}\n\nexport const SORTABLE_ITEM_ATTR = 'data-iris-sortable-item'\n\nexport function IrisSortable<T>({\n items,\n onReorder,\n getKey = (_item, index) => String(index),\n disabled = false,\n orientation = 'vertical',\n style,\n className,\n children,\n ...rest\n}: IrisSortableProps<T> &\n Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>): React.ReactElement {\n const containerRef = React.useRef<HTMLDivElement | null>(null)\n const ctrlRef = React.useRef<ReturnType<typeof createSortable> | null>(null)\n if (ctrlRef.current === null) ctrlRef.current = createSortable()\n const ctrl = ctrlRef.current\n\n const [state, setState] = React.useState<SortableState>({ activeId: null, overId: null })\n React.useEffect(() => ctrl.subscribe(setState), [ctrl])\n\n const dragging = state.activeId\n\n const indexByKey = (key: string): number =>\n Array.from(containerRef.current?.querySelectorAll(`[${SORTABLE_ITEM_ATTR}]`) ?? []).findIndex(\n (el) => el.getAttribute(SORTABLE_ITEM_ATTR) === key,\n )\n\n const onPointerDown = (e: React.PointerEvent<HTMLElement>): void => {\n if (disabled) return\n const target = (e.target as HTMLElement).closest(`[${SORTABLE_ITEM_ATTR}]`)\n if (!target) return\n const key = target.getAttribute(SORTABLE_ITEM_ATTR) ?? ''\n if (!key) return\n ctrl.press(key, e.clientX, e.clientY)\n }\n\n const onPointerMove = (e: React.PointerEvent<HTMLElement>): void => {\n if (!ctrl.getState().activeId) {\n // Pending press — promote if past threshold\n if (!ctrl.tryStart(e.clientX, e.clientY)) return\n }\n const els = containerRef.current?.querySelectorAll<HTMLElement>(`[${SORTABLE_ITEM_ATTR}]`)\n const rects = Array.from(els ?? []).map((el) => ({\n id: el.getAttribute(SORTABLE_ITEM_ATTR) ?? '',\n ...el.getBoundingClientRect(),\n }))\n ctrl.moveOver({ x: e.clientX, y: e.clientY }, rects)\n }\n\n const onPointerUp = (): void => {\n const result = ctrl.end()\n if (result.activeId && result.overId && result.activeId !== result.overId) {\n const from = indexByKey(result.activeId)\n const to = indexByKey(result.overId)\n if (from >= 0 && to >= 0 && from !== to) {\n const next = [...items]\n const [moved] = next.splice(from, 1)\n next.splice(to, 0, moved!)\n onReorder(next)\n }\n }\n }\n\n return (\n <div\n {...rest}\n ref={containerRef}\n data-iris-sortable=\"\"\n data-state={dragging ? 'dragging' : 'idle'}\n className={className}\n style={{\n display: 'flex',\n flexDirection: orientation === 'horizontal' ? 'row' : 'column',\n gap: 'var(--iris-gap-sm, 4px)',\n opacity: disabled ? 0.6 : 1,\n userSelect: dragging ? 'none' : undefined,\n ...style,\n }}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerLeave={onPointerUp}\n >\n {React.Children.map(children, (child, index) => {\n if (!React.isValidElement(child)) return child\n const key = getKey(items[index]!, index)\n const isDragging = key === dragging\n return (\n <div\n key={key}\n data-iris-sortable-item={key}\n data-iris-sortable-dragging={isDragging ? '' : undefined}\n style={{\n transition: isDragging ? 'none' : 'transform 150ms ease',\n opacity: isDragging ? 0.4 : 1,\n position: 'relative',\n zIndex: isDragging ? 100 : undefined,\n }}\n >\n {child}\n </div>\n )\n })}\n </div>\n )\n}\n"]}
@@ -1,13 +1,14 @@
1
1
  'use client'
2
2
  import { useThemeOptional } from './chunk-XCK4RIXB.js';
3
- import { IrisSidebarLayout, IrisHeaderLayout } from './chunk-J5FB2UB2.js';
3
+ import { IrisHeaderLayout, IrisSidebarLayout } from './chunk-J5FB2UB2.js';
4
+ import { IrisSortable } from './chunk-K2F7SC2T.js';
4
5
  import { useI18n } from './chunk-OMOTUHYI.js';
5
6
  import { useFloating, useDismiss } from './chunk-6BMFMCMQ.js';
6
7
  import { useStore } from './chunk-RZNYIFV2.js';
7
8
  import * as React6 from 'react';
8
9
  import { useMemo } from 'react';
9
10
  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
+ export { createAdminPreferences, createTabsNav, filterNavByAccess, findNavNode, findNavPath, firstLeaf, flattenNav, isBranch, isClosable, localStorageAdminPreferencesStorage, nodeAllowsRoles, visibleNav } from '@iris-ui-kit/core';
11
12
  import { resolveThemedIcon, defaultIconRegistry } from '@iris-ui-kit/icons';
12
13
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
13
14
  import { createPortal } from 'react-dom';
@@ -59,11 +60,13 @@ function IrisNavMenu({
59
60
  expandedKeys,
60
61
  defaultExpandedKeys,
61
62
  collapsed = false,
63
+ orientation = "vertical",
62
64
  ariaLabel,
63
65
  onSelect,
64
66
  onExpandedKeysChange
65
67
  }) {
66
68
  const { t } = useI18n();
69
+ const horizontal = orientation === "horizontal";
67
70
  const trailOf = React6.useCallback(
68
71
  (key) => key ? branchTrail(items, key) : [],
69
72
  [items]
@@ -99,7 +102,7 @@ function IrisNavMenu({
99
102
  display: "flex",
100
103
  alignItems: "center",
101
104
  gap: 10,
102
- width: "100%",
105
+ width: horizontal && opts.depth === 0 ? "auto" : "100%",
103
106
  boxSizing: "border-box",
104
107
  border: "none",
105
108
  borderRadius: "var(--iris-radius-md, 6px)",
@@ -111,7 +114,8 @@ function IrisNavMenu({
111
114
  textAlign: "start",
112
115
  cursor: opts.disabled ? "not-allowed" : "pointer",
113
116
  opacity: opts.disabled ? 0.5 : 1,
114
- padding: collapsed ? 10 : `8px 10px 8px ${12 + opts.depth * 16}px`,
117
+ padding: collapsed ? 10 : "8px 10px",
118
+ paddingInlineStart: collapsed ? 10 : 12 + opts.depth * 16,
115
119
  justifyContent: collapsed ? "center" : "flex-start"
116
120
  });
117
121
  const iconNode = (node, size = 18) => node.icon ? /* @__PURE__ */ jsx(IrisIcon, { name: node.icon, size }) : null;
@@ -218,12 +222,48 @@ function IrisNavMenu({
218
222
  }
219
223
  );
220
224
  if (!branch || !open) {
221
- return /* @__PURE__ */ jsx("div", { "data-iris-nav-group": branch ? "" : void 0, children: row }, node.key);
225
+ return /* @__PURE__ */ jsx(
226
+ "div",
227
+ {
228
+ "data-iris-nav-group": branch ? "" : void 0,
229
+ style: { position: horizontal && depth === 0 ? "relative" : void 0 },
230
+ children: row
231
+ },
232
+ node.key
233
+ );
222
234
  }
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);
235
+ return /* @__PURE__ */ jsxs(
236
+ "div",
237
+ {
238
+ "data-iris-nav-group": "",
239
+ "data-open": "true",
240
+ style: { position: horizontal && depth === 0 ? "relative" : void 0 },
241
+ children: [
242
+ row,
243
+ /* @__PURE__ */ jsx(
244
+ "div",
245
+ {
246
+ "data-iris-nav-children": "",
247
+ role: "group",
248
+ style: horizontal && depth === 0 ? {
249
+ position: "absolute",
250
+ insetBlockStart: "calc(100% + 4px)",
251
+ insetInlineStart: 0,
252
+ zIndex: 60,
253
+ minWidth: 220,
254
+ padding: 6,
255
+ border: "1px solid var(--iris-border)",
256
+ borderRadius: "var(--iris-radius-md, 6px)",
257
+ background: "var(--iris-surface)",
258
+ boxShadow: "var(--iris-shadow-md)"
259
+ } : void 0,
260
+ children: (node.children ?? []).map((child) => renderItem(child, depth + 1))
261
+ }
262
+ )
263
+ ]
264
+ },
265
+ node.key
266
+ );
227
267
  };
228
268
  const onKeyDown = (e) => {
229
269
  const root = e.currentTarget;
@@ -271,9 +311,15 @@ function IrisNavMenu({
271
311
  {
272
312
  "data-iris-nav-menu": "",
273
313
  "data-collapsed": collapsed ? "true" : void 0,
314
+ "data-orientation": orientation,
274
315
  "aria-label": ariaLabel ?? t("admin.nav"),
275
316
  onKeyDown,
276
- style: { display: "flex", flexDirection: "column", gap: 2 },
317
+ style: {
318
+ display: "flex",
319
+ flexDirection: horizontal ? "row" : "column",
320
+ alignItems: horizontal ? "center" : void 0,
321
+ gap: 2
322
+ },
277
323
  children: collapsed ? tree.map((node) => renderCollapsed(node)) : tree.map((node) => renderItem(node, 0))
278
324
  }
279
325
  );
@@ -404,6 +450,7 @@ function useTabsNav(nav) {
404
450
  closeRight: nav.closeRight,
405
451
  refresh: nav.refresh,
406
452
  setPinned: nav.setPinned,
453
+ move: nav.move,
407
454
  cacheKey: nav.cacheKey
408
455
  };
409
456
  }
@@ -775,7 +822,9 @@ function IrisAdminTabs({
775
822
  nav,
776
823
  onChange,
777
824
  onClose,
778
- onRefresh
825
+ onRefresh,
826
+ reorderable = true,
827
+ onReorder
779
828
  }) {
780
829
  const t = useTabsNav(nav);
781
830
  const { t: translate } = useI18n();
@@ -801,6 +850,10 @@ function IrisAdminTabs({
801
850
  nav.refresh(key);
802
851
  onRefresh?.(key);
803
852
  };
853
+ const reorder = (tabs) => {
854
+ tabs.forEach((tab, index) => nav.move(tab.key, index));
855
+ onReorder?.(tabs);
856
+ };
804
857
  const onKeyDown = (e) => {
805
858
  const tabs = t.tabs;
806
859
  const idx = tabs.findIndex((x) => x.key === t.activeKey);
@@ -932,8 +985,19 @@ function IrisAdminTabs({
932
985
  role: "tablist",
933
986
  "aria-label": translate("admin.openPages"),
934
987
  onKeyDown,
935
- style: { display: "flex", alignItems: "center", gap: 6, overflowX: "auto", flex: 1 },
936
- children: t.tabs.map((tab) => chip(tab))
988
+ style: { overflowX: "auto", flex: 1 },
989
+ children: /* @__PURE__ */ jsx(
990
+ IrisSortable,
991
+ {
992
+ items: t.tabs,
993
+ getKey: (tab) => tab.key,
994
+ onReorder: reorder,
995
+ disabled: !reorderable,
996
+ orientation: "horizontal",
997
+ style: { alignItems: "center", gap: 6, width: "max-content" },
998
+ children: t.tabs.map((tab) => chip(tab))
999
+ }
1000
+ )
937
1001
  }
938
1002
  ),
939
1003
  /* @__PURE__ */ jsxs(IrisDropdown, { children: [
@@ -960,6 +1024,8 @@ function IrisAdminTabs({
960
1024
  /* @__PURE__ */ jsx(IrisDropdownItem, { onSelect: () => refresh(activeKey), children: translate("admin.refresh") }),
961
1025
  /* @__PURE__ */ jsx(IrisDropdownItem, { onSelect: () => close(activeKey), children: translate("admin.close") }),
962
1026
  /* @__PURE__ */ jsx(IrisDropdownSeparator, {}),
1027
+ /* @__PURE__ */ jsx(IrisDropdownItem, { onSelect: () => nav.closeLeft(activeKey), children: translate("admin.closeLeft") }),
1028
+ /* @__PURE__ */ jsx(IrisDropdownItem, { onSelect: () => nav.closeRight(activeKey), children: translate("admin.closeRight") }),
963
1029
  /* @__PURE__ */ jsx(IrisDropdownItem, { onSelect: () => nav.closeOthers(activeKey), children: translate("admin.closeOthers") }),
964
1030
  /* @__PURE__ */ jsx(IrisDropdownItem, { onSelect: () => nav.closeAll(), children: translate("admin.closeAll") })
965
1031
  ] }) : /* @__PURE__ */ jsx(IrisDropdownItem, { disabled: true, children: "No active tab" }) })
@@ -1025,7 +1091,13 @@ function IrisAdminLayout({
1025
1091
  mode = "sidebar",
1026
1092
  appTitle = "Iris Admin",
1027
1093
  tabs,
1094
+ showTabs = true,
1028
1095
  showBreadcrumb = true,
1096
+ stickyHeader = true,
1097
+ stickyTabs = true,
1098
+ menuAlign = "start",
1099
+ contentWidth = "fluid",
1100
+ contentHeight = "viewport",
1029
1101
  sidebarWidth = 240,
1030
1102
  collapsedWidth = 64,
1031
1103
  logo,
@@ -1096,6 +1168,37 @@ function IrisAdminLayout({
1096
1168
  }
1097
1169
  );
1098
1170
  const renderLogo = (state) => typeof logo === "function" ? logo(state) : logo ?? defaultLogo(state);
1171
+ const tabsBar = tabs && showTabs ? /* @__PURE__ */ jsx(
1172
+ "div",
1173
+ {
1174
+ "data-iris-admin-tabs-region": "",
1175
+ "data-sticky": stickyTabs ? "true" : void 0,
1176
+ style: {
1177
+ position: stickyTabs && !stickyHeader ? "sticky" : "relative",
1178
+ top: 0,
1179
+ zIndex: 49
1180
+ },
1181
+ children: /* @__PURE__ */ jsx(IrisAdminTabs, { nav: tabs })
1182
+ }
1183
+ ) : null;
1184
+ const contentRegion = /* @__PURE__ */ jsxs(
1185
+ "div",
1186
+ {
1187
+ "data-iris-admin-content": "",
1188
+ "data-width": contentWidth,
1189
+ style: {
1190
+ boxSizing: "border-box",
1191
+ width: "100%",
1192
+ maxWidth: contentWidth === "centered" ? "72rem" : void 0,
1193
+ marginInline: contentWidth === "centered" ? "auto" : void 0,
1194
+ padding: 16
1195
+ },
1196
+ children: [
1197
+ mode === "horizontal" && showBreadcrumb ? /* @__PURE__ */ jsx(IrisAdminBreadcrumb, { trail, onSelect: handleSelect }) : null,
1198
+ renderContent()
1199
+ ]
1200
+ }
1201
+ );
1099
1202
  const collapseToggle = /* @__PURE__ */ jsx(
1100
1203
  "button",
1101
1204
  {
@@ -1141,6 +1244,75 @@ function IrisAdminLayout({
1141
1244
  ]
1142
1245
  }
1143
1246
  );
1247
+ if (mode === "horizontal") {
1248
+ const justifyContent = menuAlign === "center" ? "center" : menuAlign === "end" ? "flex-end" : "flex-start";
1249
+ return /* @__PURE__ */ jsxs(
1250
+ "div",
1251
+ {
1252
+ "data-iris-admin-layout": "",
1253
+ "data-mode": "horizontal",
1254
+ style: {
1255
+ height: contentHeight === "viewport" ? "100vh" : "auto",
1256
+ minHeight: "100vh"
1257
+ },
1258
+ children: [
1259
+ tabs ? /* @__PURE__ */ jsx(TabsSync, { nav: tabs, activeKey: currentActive, onActivate: syncFromTab }) : null,
1260
+ /* @__PURE__ */ jsx(
1261
+ IrisHeaderLayout,
1262
+ {
1263
+ sticky: stickyHeader,
1264
+ footer,
1265
+ header: /* @__PURE__ */ jsxs("div", { "data-iris-admin-header": "", children: [
1266
+ /* @__PURE__ */ jsxs(
1267
+ "div",
1268
+ {
1269
+ "data-iris-admin-headerbar": "",
1270
+ style: {
1271
+ display: "flex",
1272
+ alignItems: "center",
1273
+ gap: 12,
1274
+ minHeight: 52,
1275
+ padding: "0 16px",
1276
+ background: "var(--iris-background)"
1277
+ },
1278
+ children: [
1279
+ renderLogo({ collapsed: false }),
1280
+ /* @__PURE__ */ jsx(
1281
+ "div",
1282
+ {
1283
+ "data-iris-admin-topnav": "",
1284
+ style: { display: "flex", flex: 1, minWidth: 0, justifyContent },
1285
+ children: /* @__PURE__ */ jsx(
1286
+ IrisNavMenu,
1287
+ {
1288
+ items: menus,
1289
+ activeKey: currentActive,
1290
+ orientation: "horizontal",
1291
+ onSelect: handleSelect
1292
+ }
1293
+ )
1294
+ }
1295
+ ),
1296
+ toolbar ? /* @__PURE__ */ jsx(
1297
+ "div",
1298
+ {
1299
+ "data-iris-admin-toolbar": "",
1300
+ style: { display: "flex", alignItems: "center", gap: 8 },
1301
+ children: toolbar
1302
+ }
1303
+ ) : null
1304
+ ]
1305
+ }
1306
+ ),
1307
+ tabsBar
1308
+ ] }),
1309
+ children: contentRegion
1310
+ }
1311
+ )
1312
+ ]
1313
+ }
1314
+ );
1315
+ }
1144
1316
  return /* @__PURE__ */ jsxs(
1145
1317
  IrisSidebarLayout,
1146
1318
  {
@@ -1181,20 +1353,34 @@ function IrisAdminLayout({
1181
1353
  /* @__PURE__ */ jsx(
1182
1354
  IrisHeaderLayout,
1183
1355
  {
1184
- sticky: true,
1356
+ sticky: stickyHeader,
1185
1357
  header: /* @__PURE__ */ jsxs("div", { "data-iris-admin-header": "", children: [
1186
1358
  headerBar,
1187
- tabs ? /* @__PURE__ */ jsx(IrisAdminTabs, { nav: tabs }) : null
1359
+ tabsBar
1188
1360
  ] }),
1189
1361
  footer,
1190
- children: /* @__PURE__ */ jsx("div", { "data-iris-admin-content": "", style: { padding: 16 }, children: renderContent() })
1362
+ children: contentRegion
1191
1363
  }
1192
1364
  )
1193
1365
  ]
1194
1366
  }
1195
1367
  );
1196
1368
  }
1369
+ function useAdminPreferences(preferences, hydrate = true) {
1370
+ const state = useStore(preferences.store);
1371
+ React6.useEffect(() => {
1372
+ if (hydrate) void preferences.hydrate();
1373
+ }, [hydrate, preferences]);
1374
+ return {
1375
+ state,
1376
+ set: preferences.set,
1377
+ patch: preferences.patch,
1378
+ reset: preferences.reset,
1379
+ hydrate: preferences.hydrate,
1380
+ flush: preferences.flush
1381
+ };
1382
+ }
1197
1383
 
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
1384
+ export { DropdownContext, IrisAdminBreadcrumb, IrisAdminLayout, IrisAdminTabs, IrisBreadcrumb, IrisBreadcrumbItem, IrisDropdown, IrisDropdownItem, IrisDropdownMenu, IrisDropdownSeparator, IrisDropdownTrigger, IrisIcon, IrisNavMenu, IrisSlot, useAdminPreferences, useAdminShell, useDropdownContext, useMachine, useTabsNav };
1385
+ //# sourceMappingURL=chunk-XVXURJZQ.js.map
1386
+ //# sourceMappingURL=chunk-XVXURJZQ.js.map