@bizapps/design-system-testing 1.11.0 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -1,10 +1,10 @@
1
1
  "use client";
2
- import { SearchIcon, BellIcon, CircleHelpIcon, ChevronDownIcon, ChevronLeftIcon, MoreHorizontalIcon, ChevronRightIcon, MinusIcon, CheckIcon, LoaderCircle, ChevronUpIcon, X, ChevronLeft, ChevronRight, ExternalLinkIcon, ChevronsUpDownIcon, XIcon, UploadIcon, Loader2Icon, LoaderIcon, PipetteIcon, CalendarIcon, MenuIcon, Pencil, Clock, Funnel, ArrowUp, ArrowDown, ArrowUpDown, Ellipsis, Rows2, Eye, Search, ChevronsLeft, Menu, ChevronDown, Star, BrushCleaning, House, Plane, Wallet, LayoutDashboardIcon, CircleDashed, InfoIcon, TriangleAlertIcon, OctagonXIcon, CircleCheckIcon } from 'lucide-react';
2
+ import { SearchIcon, BellIcon, CircleHelpIcon, ChevronDownIcon, ChevronLeftIcon, MoreHorizontalIcon, ChevronRightIcon, MinusIcon, CheckIcon, LoaderCircle, ChevronUpIcon, X, ChevronLeft, ChevronRight, ExternalLinkIcon, ChevronsUpDownIcon, XIcon, UploadIcon, Loader2Icon, LoaderIcon, PipetteIcon, CalendarIcon, MenuIcon, Pencil, Clock, Funnel, ArrowUp, ArrowDown, ArrowUpDown, Ellipsis, Rows2, Eye, Search, ChevronsLeft, Menu, ChevronDown, Star, BrushCleaning, House, Plane, Wallet, LayoutDashboardIcon, CircleDashed, LayoutGrid, PanelRightClose, InfoIcon, TriangleAlertIcon, OctagonXIcon, CircleCheckIcon, ArrowRight, CircleAlert, ArrowUpRight, Lock } from 'lucide-react';
3
3
  import { cva } from 'class-variance-authority';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
7
- import { createContext, memo, useRef, useState, useMemo, useCallback, useEffect, forwardRef, Fragment, useId, useContext, useLayoutEffect, isValidElement, useImperativeHandle, cloneElement } from 'react';
7
+ import { createContext, memo, useRef, useState, useMemo, useCallback, useEffect, forwardRef, Fragment, useId, useContext, useLayoutEffect, isValidElement, useReducer, useImperativeHandle, cloneElement, useSyncExternalStore, use } from 'react';
8
8
  import { Avatar as Avatar$1, Checkbox as Checkbox$1, Slot, Select as Select$1, Label as Label$1, Popover as Popover$1, Toggle as Toggle$1, ToggleGroup, Separator as Separator$1, Progress as Progress$1, Slider as Slider$1, Switch as Switch$1, Tabs as Tabs$1, Tooltip as Tooltip$1, Dialog as Dialog$1, AlertDialog as AlertDialog$1, Accordion as Accordion$1, DropdownMenu as DropdownMenu$1, RadioGroup as RadioGroup$1, NavigationMenu as NavigationMenu$1 } from 'radix-ui';
9
9
  import { isSameDay, isToday, format, addDays } from 'date-fns';
10
10
  import useEmblaCarousel from 'embla-carousel-react';
@@ -28,6 +28,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
28
28
  import confetti from 'canvas-confetti';
29
29
  import { flexRender, useReactTable, getFilteredRowModel, getSortedRowModel, getCoreRowModel } from '@tanstack/react-table';
30
30
  export { createColumnHelper } from '@tanstack/react-table';
31
+ import { createPortal } from 'react-dom';
31
32
  import SignaturePadLib from 'signature_pad';
32
33
 
33
34
  // src/components/ui/Alert/alert.tsx
@@ -5404,10 +5405,11 @@ function SheetContent({
5404
5405
  children,
5405
5406
  side = "right",
5406
5407
  showCloseButton = true,
5408
+ overlayClassName,
5407
5409
  ...props
5408
5410
  }) {
5409
5411
  return /* @__PURE__ */ jsxs(SheetPortal, { children: [
5410
- /* @__PURE__ */ jsx(SheetOverlay, {}),
5412
+ /* @__PURE__ */ jsx(SheetOverlay, { className: overlayClassName }),
5411
5413
  /* @__PURE__ */ jsxs(
5412
5414
  Dialog$1.Content,
5413
5415
  {
@@ -9181,6 +9183,7 @@ var DEFAULT_LABELS = {
9181
9183
  collapse: "Collapse menu",
9182
9184
  expand: "Expand menu",
9183
9185
  openMenu: "Open menu",
9186
+ closeMenu: "Close menu",
9184
9187
  currentPageSuffix: " (contains the current page)",
9185
9188
  searchPlaceholder: () => "Filter links\u2026",
9186
9189
  searchLabel: (moduleLabel) => `Filter links of ${moduleLabel}`,
@@ -9195,6 +9198,8 @@ var DEFAULT_LABELS = {
9195
9198
  var EASE = "cubic-bezier(.2,.8,.2,1)";
9196
9199
  var MOTION_MS = 240;
9197
9200
  var FADE_MS = MOTION_MS / 2;
9201
+ var MORPH_MS = 360;
9202
+ var MORPH_EASE = "cubic-bezier(.65,0,.35,1)";
9198
9203
  var BOUNCE_KEYFRAMES = [
9199
9204
  { transform: "translateY(0) scale(1)" },
9200
9205
  { transform: "translateY(-3px) scale(1.06)", offset: 0.35 },
@@ -9203,8 +9208,132 @@ var BOUNCE_KEYFRAMES = [
9203
9208
  ];
9204
9209
  var BOUNCE_MS = 420;
9205
9210
  var SUBTLE_SCROLLBAR = "[scrollbar-color:var(--floating-sidebar-scrollbar-thumb)_var(--floating-sidebar-scrollbar-track)]! [&::-webkit-scrollbar]:w-2.5! [&::-webkit-scrollbar-track]:bg-floating-sidebar-scrollbar-track! [&::-webkit-scrollbar-thumb]:rounded-full! [&::-webkit-scrollbar-thumb]:border-2! [&::-webkit-scrollbar-thumb]:border-solid! [&::-webkit-scrollbar-thumb]:border-floating-sidebar-scrollbar-track! [&::-webkit-scrollbar-thumb]:bg-floating-sidebar-scrollbar-thumb! [&::-webkit-scrollbar-thumb:hover]:bg-floating-sidebar-scrollbar-thumb-hover!";
9206
- var DEFAULT_WIDTH = 276;
9211
+ var MORPH_SETTLE_MS = 120;
9212
+ function morphBox(element) {
9213
+ const rect = element.getBoundingClientRect();
9214
+ const computed = getComputedStyle(element);
9215
+ const drawer = element.closest('[data-slot="sheet-content"]');
9216
+ const drawerRect = drawer?.getBoundingClientRect();
9217
+ const offsetX = drawer && drawerRect ? drawerRect.left - drawer.offsetLeft : 0;
9218
+ const offsetY = drawer && drawerRect ? drawerRect.top - drawer.offsetTop : 0;
9219
+ const borderWidth = parseFloat(computed.borderTopWidth) || 0;
9220
+ return {
9221
+ left: rect.left - offsetX,
9222
+ top: rect.top - offsetY,
9223
+ width: rect.width,
9224
+ height: rect.height,
9225
+ // `rounded-full` resolves to an enormous radius that would not interpolate visibly;
9226
+ // half the height draws the same pill.
9227
+ borderRadius: `${Math.min(parseFloat(computed.borderTopLeftRadius) || 0, rect.height / 2)}px`,
9228
+ borderWidth: `${borderWidth}px`,
9229
+ borderColor: borderWidth > 0 ? computed.borderTopColor : "transparent",
9230
+ backgroundColor: computed.backgroundColor,
9231
+ boxShadow: computed.boxShadow
9232
+ };
9233
+ }
9234
+ var morphStyle = (b) => ({
9235
+ left: `${b.left}px`,
9236
+ top: `${b.top}px`,
9237
+ width: `${b.width}px`,
9238
+ height: `${b.height}px`,
9239
+ borderRadius: b.borderRadius,
9240
+ borderWidth: b.borderWidth,
9241
+ borderColor: b.borderColor,
9242
+ backgroundColor: b.backgroundColor,
9243
+ boxShadow: b.boxShadow
9244
+ });
9245
+ var sameBox = (a, b) => Math.abs(a.left - b.left) < 0.5 && Math.abs(a.top - b.top) < 0.5 && Math.abs(a.width - b.width) < 0.5 && Math.abs(a.height - b.height) < 0.5;
9246
+ function MobileMorph({ morph, side, ghostRef, getTarget, onDone }) {
9247
+ const chevronsRef = useRef(null);
9248
+ const menuRef = useRef(null);
9249
+ const onDoneRef = useRef(onDone);
9250
+ useLayoutEffect(() => {
9251
+ onDoneRef.current = onDone;
9252
+ });
9253
+ useLayoutEffect(() => {
9254
+ const ghost = ghostRef.current;
9255
+ if (!ghost) return;
9256
+ let frame = 0;
9257
+ let tries = 0;
9258
+ const animations = [];
9259
+ const options = {
9260
+ duration: MORPH_MS,
9261
+ easing: MORPH_EASE,
9262
+ fill: "forwards"
9263
+ };
9264
+ const start = () => {
9265
+ const target = getTarget();
9266
+ if (!target) {
9267
+ if (tries++ < 10) frame = requestAnimationFrame(start);
9268
+ else onDoneRef.current();
9269
+ return;
9270
+ }
9271
+ const to = morphBox(target);
9272
+ const chevrons = morph.to === "open" ? 1 : 0;
9273
+ animations.push(
9274
+ ghost.animate([morphStyle(morph.from), morphStyle(to)], options),
9275
+ chevronsRef.current.animate(
9276
+ [{ opacity: morph.chevrons }, { opacity: chevrons }],
9277
+ options
9278
+ ),
9279
+ menuRef.current.animate(
9280
+ [{ opacity: 1 - morph.chevrons }, { opacity: 1 - chevrons }],
9281
+ options
9282
+ )
9283
+ );
9284
+ animations[0].onfinish = () => {
9285
+ const settled = getTarget();
9286
+ const end = settled && morphBox(settled);
9287
+ if (!end || sameBox(end, to)) return onDoneRef.current();
9288
+ const settle = ghost.animate([morphStyle(to), morphStyle(end)], {
9289
+ ...options,
9290
+ duration: MORPH_SETTLE_MS
9291
+ });
9292
+ animations.push(settle);
9293
+ settle.onfinish = () => onDoneRef.current();
9294
+ };
9295
+ };
9296
+ start();
9297
+ return () => {
9298
+ cancelAnimationFrame(frame);
9299
+ animations.forEach((animation2) => animation2.cancel());
9300
+ };
9301
+ }, [morph, getTarget, ghostRef]);
9302
+ return createPortal(
9303
+ /* @__PURE__ */ jsxs(
9304
+ "div",
9305
+ {
9306
+ ref: ghostRef,
9307
+ "aria-hidden": "true",
9308
+ "data-slot": "floating-sidebar-morph",
9309
+ "data-state": morph.to,
9310
+ className: "text-primary pointer-events-none fixed z-[60] flex items-center justify-center overflow-hidden border-solid",
9311
+ style: morphStyle(morph.from),
9312
+ children: [
9313
+ /* @__PURE__ */ jsx(
9314
+ "span",
9315
+ {
9316
+ ref: chevronsRef,
9317
+ "data-slot": "floating-sidebar-morph-chevrons",
9318
+ className: "absolute flex",
9319
+ style: {
9320
+ opacity: morph.chevrons,
9321
+ // Same direction as the close button's chevrons in the drawer.
9322
+ transform: side === "right" ? "rotate(180deg)" : void 0
9323
+ },
9324
+ children: /* @__PURE__ */ jsx(ChevronsLeft, { className: "size-5", strokeWidth: 1.5 })
9325
+ }
9326
+ ),
9327
+ /* @__PURE__ */ jsx("span", { ref: menuRef, className: "absolute flex", style: { opacity: 1 - morph.chevrons }, children: /* @__PURE__ */ jsx(Menu, { className: "size-5", strokeWidth: 1.5 }) })
9328
+ ]
9329
+ }
9330
+ ),
9331
+ document.body
9332
+ );
9333
+ }
9334
+ var DEFAULT_WIDTH = 305;
9207
9335
  var DEFAULT_COLLAPSED_WIDTH = 48;
9336
+ var TILE_ICON_SIZE = 22;
9208
9337
  var MIN_COLLAPSED_WIDTH = 40;
9209
9338
  var t = (prop, ms = 300, delay = 0) => `${prop} ${ms}ms ${EASE} ${delay}ms`;
9210
9339
  function transition2(reduced, ...parts) {
@@ -9236,31 +9365,29 @@ function isEditableTarget(target) {
9236
9365
  if (target.closest('[data-slot="floating-sidebar-search"]')) return false;
9237
9366
  return target.closest('[contenteditable]:not([contenteditable="false"])') !== null || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT";
9238
9367
  }
9368
+ var ICON_SIZES = {
9369
+ sm: "[&>svg]:size-[18px]",
9370
+ md: "[&>svg]:size-5",
9371
+ lg: "[&>svg]:size-[22px]"
9372
+ };
9239
9373
  function SidebarIcon({
9240
9374
  icon,
9241
9375
  resolveIcon,
9242
- small = false
9376
+ size = "md"
9243
9377
  }) {
9244
9378
  const resolved = typeof icon === "string" ? resolveIcon?.(icon) : icon;
9379
+ const sizeClass2 = cn(
9380
+ ICON_SIZES[size],
9381
+ "[&>svg]:transition-[width,height] [&>svg]:duration-240 [&>svg]:ease-[cubic-bezier(.2,.8,.2,1)] motion-reduce:[&>svg]:transition-none"
9382
+ );
9245
9383
  if (resolved === void 0 || resolved === null) {
9246
- return /* @__PURE__ */ jsx("span", { "data-slot": "floating-sidebar-fallback-icon", className: "flex", children: /* @__PURE__ */ jsx(
9247
- CircleDashed,
9248
- {
9249
- "aria-hidden": "true",
9250
- className: small ? "size-[18px]" : "size-5",
9251
- strokeWidth: 1.5
9252
- }
9253
- ) });
9384
+ return /* @__PURE__ */ jsx("span", { "data-slot": "floating-sidebar-fallback-icon", className: cn("flex", sizeClass2), children: /* @__PURE__ */ jsx(CircleDashed, { "aria-hidden": "true", strokeWidth: 1.5 }) });
9254
9385
  }
9255
9386
  return /* @__PURE__ */ jsx(
9256
9387
  "span",
9257
9388
  {
9258
9389
  "data-slot": "floating-sidebar-icon",
9259
- className: cn(
9260
- "flex",
9261
- "[&>svg]:stroke-[1.5]",
9262
- small ? "[&>svg]:size-[18px]" : "[&>svg]:size-5"
9263
- ),
9390
+ className: cn("flex [&>svg]:stroke-[1.5]", sizeClass2),
9264
9391
  children: resolved
9265
9392
  }
9266
9393
  );
@@ -9277,7 +9404,7 @@ function FloatingSidebar({
9277
9404
  collapsedWidth = DEFAULT_COLLAPSED_WIDTH,
9278
9405
  type = "single",
9279
9406
  openModuleIds: openModulesProp,
9280
- defaultOpenModuleIds = [],
9407
+ defaultOpenModuleIds,
9281
9408
  onOpenModulesChange,
9282
9409
  hideEmptyModules = true,
9283
9410
  activeItemId: activeItemProp,
@@ -9306,9 +9433,16 @@ function FloatingSidebar({
9306
9433
  const isMobile = useIsMobile();
9307
9434
  const baseId = useId();
9308
9435
  const [open, setOpen] = useControllableState2(openProp, defaultOpen, onOpenChange);
9436
+ const moduleIdByItemId = useMemo(
9437
+ () => new Map(modules.flatMap((module) => module.items.map((item) => [item.id, module.id]))),
9438
+ [modules]
9439
+ );
9440
+ const moduleIdOf = (itemId) => itemId ? moduleIdByItemId.get(itemId) : void 0;
9441
+ const initialActiveModuleId = moduleIdOf(activeItemProp ?? defaultActiveItemId);
9309
9442
  const [rawOpenModuleIds, setOpenModuleIds] = useControllableState2(
9310
9443
  openModulesProp,
9311
- defaultOpenModuleIds,
9444
+ // Without an explicit default, the module of the current page starts open.
9445
+ defaultOpenModuleIds ?? (initialActiveModuleId ? [initialActiveModuleId] : []),
9312
9446
  onOpenModulesChange
9313
9447
  );
9314
9448
  const railWidth = Math.max(MIN_COLLAPSED_WIDTH, collapsedWidth);
@@ -9326,6 +9460,51 @@ function FloatingSidebar({
9326
9460
  defaultMobileOpen,
9327
9461
  onMobileOpenChange
9328
9462
  );
9463
+ const morphEnabled = isMobile && mobileOpenProp === void 0 && !reduced;
9464
+ const triggerRef = useRef(null);
9465
+ const toggleRef = useRef(null);
9466
+ const morphGhostRef = useRef(null);
9467
+ const deferredFocus = useRef(null);
9468
+ const [morph, setMorph] = useState(null);
9469
+ if (morph && !morphEnabled) setMorph(null);
9470
+ const prevMobileOpen = useRef(mobileOpen);
9471
+ useLayoutEffect(() => {
9472
+ if (prevMobileOpen.current === mobileOpen) return;
9473
+ prevMobileOpen.current = mobileOpen;
9474
+ const ghost = morphGhostRef.current;
9475
+ const source = ghost ?? (mobileOpen ? triggerRef.current : toggleRef.current);
9476
+ deferredFocus.current = null;
9477
+ if (!morphEnabled || !source || typeof source.animate !== "function") return;
9478
+ deferredFocus.current = mobileOpen ? "toggle" : "trigger";
9479
+ const chevrons = ghost?.querySelector(
9480
+ '[data-slot="floating-sidebar-morph-chevrons"]'
9481
+ );
9482
+ setMorph((current) => ({
9483
+ to: mobileOpen ? "open" : "closed",
9484
+ from: morphBox(source),
9485
+ chevrons: chevrons ? parseFloat(getComputedStyle(chevrons).opacity) : mobileOpen ? 0 : 1,
9486
+ key: (current?.key ?? 0) + 1
9487
+ }));
9488
+ }, [mobileOpen, morphEnabled]);
9489
+ const getMorphTarget = useCallback(
9490
+ () => morph?.to === "open" ? toggleRef.current : triggerRef.current,
9491
+ [morph?.to]
9492
+ );
9493
+ const endMorph = useCallback(() => setMorph(null), []);
9494
+ useLayoutEffect(() => {
9495
+ if (morph) return;
9496
+ const pending = deferredFocus.current;
9497
+ deferredFocus.current = null;
9498
+ const active = document.activeElement;
9499
+ if (pending === "toggle") {
9500
+ const toggle2 = toggleRef.current;
9501
+ if (toggle2 && active === toggle2.closest('[data-slot="sheet-content"]')) toggle2.focus();
9502
+ } else if (pending === "trigger" && (!active || active === document.body)) {
9503
+ triggerRef.current?.focus();
9504
+ }
9505
+ }, [morph]);
9506
+ const morphing = morphEnabled && morph !== null;
9507
+ const finePointer = useMediaQuery("(hover: hover) and (pointer: fine)");
9329
9508
  const [queries, setQueries] = useState({});
9330
9509
  const [flyoutId, setFlyoutId] = useState(null);
9331
9510
  const [hoverId, setHoverId] = useState(null);
@@ -9365,8 +9544,31 @@ function FloatingSidebar({
9365
9544
  document.addEventListener("keydown", onKeyDown);
9366
9545
  return () => document.removeEventListener("keydown", onKeyDown);
9367
9546
  }, [shortcut, toggle, isMobile, mobileOpen, setMobileOpen]);
9547
+ const openModule = (moduleId) => {
9548
+ const opened = rawOpenModuleIds.includes(moduleId);
9549
+ if (type === "multiple") {
9550
+ if (!opened) setOpenModuleIds([...rawOpenModuleIds, moduleId]);
9551
+ } else if (!opened || rawOpenModuleIds.length > 1) {
9552
+ setOpenModuleIds([moduleId]);
9553
+ }
9554
+ };
9555
+ const activeModuleId = moduleIdOf(activeItemId);
9556
+ const syncedActive = useRef(`${activeItemId}|${activeModuleId}`);
9557
+ useEffect(() => {
9558
+ const key = `${activeItemId}|${activeModuleId}`;
9559
+ if (syncedActive.current === key) return;
9560
+ syncedActive.current = key;
9561
+ if (activeModuleId) openModule(activeModuleId);
9562
+ });
9368
9563
  const selectItem = (item, module, navigates) => {
9369
- if (navigates) setActiveItemId(item.id);
9564
+ if (navigates) {
9565
+ setActiveItemId(item.id);
9566
+ openModule(module.id);
9567
+ if (activeItemProp === void 0) {
9568
+ syncedActive.current = `${item.id}|${moduleIdOf(item.id)}`;
9569
+ }
9570
+ if (queries[module.id]) setQueries((current) => ({ ...current, [module.id]: "" }));
9571
+ }
9370
9572
  setFlyoutId(null);
9371
9573
  if (isMobile) setMobileOpen(false);
9372
9574
  onItemSelect?.(item, module);
@@ -9383,7 +9585,7 @@ function FloatingSidebar({
9383
9585
  renderLink,
9384
9586
  onToggleFavorite: toggleFavorite
9385
9587
  };
9386
- const toggleLabel = collapsed ? labels.expand : labels.collapse;
9588
+ const toggleLabel = isMobile ? labels.closeMenu : collapsed ? labels.expand : labels.collapse;
9387
9589
  const ability = useOptionalAbility();
9388
9590
  const usesPermissions = modules.some(
9389
9591
  (module) => module.permission || module.items.some((item) => item.permission)
@@ -9470,20 +9672,25 @@ function FloatingSidebar({
9470
9672
  children: labels.title
9471
9673
  }
9472
9674
  ),
9473
- !isMobile && /* @__PURE__ */ jsx(
9675
+ /* @__PURE__ */ jsx(
9474
9676
  "button",
9475
9677
  {
9678
+ ref: toggleRef,
9476
9679
  type: "button",
9477
9680
  "data-slot": "floating-sidebar-toggle",
9478
9681
  "aria-label": toggleLabel,
9479
- "aria-expanded": !collapsed,
9480
- title: shortcut ? `${toggleLabel} (Ctrl/\u2318 + B)` : toggleLabel,
9481
- onClick: toggle,
9682
+ "aria-expanded": isMobile ? void 0 : !collapsed,
9683
+ title: shortcut && (!isMobile || finePointer) ? `${toggleLabel} (Ctrl/\u2318 + B)` : toggleLabel,
9684
+ onClick: isMobile ? () => setMobileOpen(false) : toggle,
9482
9685
  className: cn(
9483
9686
  "text-primary focus-visible:ring-primary bg-sidebar border-floating-sidebar-card-border hover:bg-floating-sidebar-toggle-hover flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-full border shadow-[0_2px_6px_color-mix(in_srgb,var(--color-primary)_6%,transparent)] outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
9484
9687
  side === "right" ? "mr-auto" : "ml-auto"
9485
9688
  ),
9486
9689
  style: {
9690
+ // Hidden, and out of reach of a stray tap, while the mobile morph stands in
9691
+ // for it.
9692
+ opacity: morphing ? 0 : void 0,
9693
+ pointerEvents: morphing ? "none" : void 0,
9487
9694
  // The auto margin keeps the toggle on the edge while the width animates;
9488
9695
  // only the outer margin changes, to center it on the collapsed rail.
9489
9696
  // (`auto` cannot be interpolated, so animating it made the toggle jump.)
@@ -9569,36 +9776,65 @@ function FloatingSidebar({
9569
9776
  }
9570
9777
  );
9571
9778
  if (isMobile) {
9572
- return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsxs(Sheet, { open: mobileOpen, onOpenChange: setMobileOpen, children: [
9573
- mobileOpenProp === void 0 && /* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
9574
- "button",
9575
- {
9576
- type: "button",
9577
- "data-slot": "floating-sidebar-mobile-trigger",
9578
- "aria-label": labels.openMenu,
9579
- className: "text-primary focus-visible:ring-primary bg-sidebar hover:bg-sidebar-accent flex size-10 cursor-pointer items-center justify-center rounded-[10px] shadow-[0_8px_24px_color-mix(in_srgb,var(--color-primary)_8%,transparent)] outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
9580
- children: /* @__PURE__ */ jsx(Menu, { "aria-hidden": "true", className: "size-5", strokeWidth: 1.5 })
9581
- }
9582
- ) }),
9583
- /* @__PURE__ */ jsxs(
9584
- SheetContent,
9779
+ return /* @__PURE__ */ jsxs(TooltipProvider, { delayDuration: 300, children: [
9780
+ /* @__PURE__ */ jsxs(Sheet, { open: mobileOpen, onOpenChange: setMobileOpen, children: [
9781
+ mobileOpenProp === void 0 && /* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
9782
+ "button",
9783
+ {
9784
+ ref: triggerRef,
9785
+ type: "button",
9786
+ "data-slot": "floating-sidebar-mobile-trigger",
9787
+ "aria-label": labels.openMenu,
9788
+ style: {
9789
+ opacity: morphing ? 0 : void 0,
9790
+ pointerEvents: morphing ? "none" : void 0
9791
+ },
9792
+ className: "text-primary focus-visible:ring-primary bg-sidebar hover:bg-sidebar-accent flex size-10 cursor-pointer items-center justify-center rounded-[10px] shadow-[0_8px_24px_color-mix(in_srgb,var(--color-primary)_8%,transparent)] outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
9793
+ children: /* @__PURE__ */ jsx(Menu, { "aria-hidden": "true", className: "size-5", strokeWidth: 1.5 })
9794
+ }
9795
+ ) }),
9796
+ /* @__PURE__ */ jsxs(
9797
+ SheetContent,
9798
+ {
9799
+ side,
9800
+ "aria-describedby": void 0,
9801
+ showCloseButton: false,
9802
+ overlayClassName: "bg-primary/20",
9803
+ className: "bg-transparent p-3 pt-2 shadow-none data-[side=left]:border-r-0 data-[side=right]:border-l-0",
9804
+ style: { width: width + 24, maxWidth: "100%" },
9805
+ onOpenAutoFocus: (event) => {
9806
+ if (deferredFocus.current !== "toggle") return;
9807
+ event.preventDefault();
9808
+ event.currentTarget?.focus();
9809
+ },
9810
+ onCloseAutoFocus: (event) => {
9811
+ if (deferredFocus.current === "trigger") event.preventDefault();
9812
+ },
9813
+ onEscapeKeyDown: (event) => {
9814
+ const active = document.activeElement;
9815
+ if (active instanceof HTMLInputElement && active.value && active.closest('[data-slot="floating-sidebar-search"]')) {
9816
+ event.preventDefault();
9817
+ }
9818
+ },
9819
+ children: [
9820
+ /* @__PURE__ */ jsx(SheetTitle, { className: "sr-only", children: labels.title }),
9821
+ nav
9822
+ ]
9823
+ }
9824
+ )
9825
+ ] }),
9826
+ morphing && /* @__PURE__ */ jsx(
9827
+ MobileMorph,
9585
9828
  {
9829
+ morph,
9586
9830
  side,
9587
- "aria-describedby": void 0,
9588
- className: "p-4 pt-12",
9589
- onEscapeKeyDown: (event) => {
9590
- const active = document.activeElement;
9591
- if (active instanceof HTMLInputElement && active.value && active.closest('[data-slot="floating-sidebar-search"]')) {
9592
- event.preventDefault();
9593
- }
9594
- },
9595
- children: [
9596
- /* @__PURE__ */ jsx(SheetTitle, { className: "sr-only", children: labels.title }),
9597
- nav
9598
- ]
9599
- }
9831
+ ghostRef: morphGhostRef,
9832
+ getTarget: getMorphTarget,
9833
+ onDone: endMorph
9834
+ },
9835
+ morph.key
9600
9836
  )
9601
- ] }) });
9837
+ ] });
9602
9838
  }
9603
9839
  return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 300, children: nav });
9604
9840
  }
@@ -9732,7 +9968,8 @@ function ModuleCard({
9732
9968
  const fillAfterCollapse = tileSelected && tileActive && settling && !reduced;
9733
9969
  const fillTransition = fillAfterCollapse ? `0ms linear ${MOTION_MS}ms` : `${FADE_MS}ms linear 0ms`;
9734
9970
  const iconFlipDelay = fillAfterCollapse ? MOTION_MS : FADE_MS / 2;
9735
- const tilePadding = (tileSize - 2 - 20) / 2;
9971
+ const tilePadding = (tileSize - 2 - TILE_ICON_SIZE) / 2;
9972
+ const iconHighlighted = collapsed ? tileSelected : open;
9736
9973
  const cardRef = useRef(null);
9737
9974
  const wasCollapsed = useRef(collapsed);
9738
9975
  useEffect(() => {
@@ -9772,7 +10009,7 @@ function ModuleCard({
9772
10009
  "border",
9773
10010
  collapsed ? "rounded-[14px]" : "rounded-2xl",
9774
10011
  tileSelected ? "border-primary bg-primary" : "bg-sidebar border-floating-sidebar-card-border",
9775
- open ? "shadow-[0_8px_24px_color-mix(in_srgb,var(--color-primary)_8%,transparent)]" : "shadow-[0_2px_8px_color-mix(in_srgb,var(--color-primary)_4%,transparent)]"
10012
+ open ? "shadow-[0_4px_14px_color-mix(in_srgb,var(--color-primary)_3%,transparent)]" : "shadow-[0_1px_4px_color-mix(in_srgb,var(--color-primary)_3%,transparent)]"
9776
10013
  ),
9777
10014
  style: {
9778
10015
  flex: open ? "0 1 auto" : "0 0 auto",
@@ -9840,8 +10077,24 @@ function ModuleCard({
9840
10077
  "span",
9841
10078
  {
9842
10079
  "data-slot": "floating-sidebar-module-icon",
9843
- className: "relative flex shrink-0 items-center justify-center",
9844
- children: /* @__PURE__ */ jsx(SidebarIcon, { icon: module.icon, resolveIcon })
10080
+ className: cn(
10081
+ "relative flex shrink-0 items-center justify-center",
10082
+ !iconHighlighted && "text-muted-foreground"
10083
+ ),
10084
+ style: {
10085
+ transition: transition2(
10086
+ reduced,
10087
+ `color 0ms linear ${iconFlipDelay}ms`
10088
+ )
10089
+ },
10090
+ children: /* @__PURE__ */ jsx(
10091
+ SidebarIcon,
10092
+ {
10093
+ icon: module.icon,
10094
+ resolveIcon,
10095
+ size: collapsed ? "lg" : "md"
10096
+ }
10097
+ )
9845
10098
  }
9846
10099
  ),
9847
10100
  /* @__PURE__ */ jsx(
@@ -10029,7 +10282,9 @@ function ModuleCard({
10029
10282
  "data-slot": "floating-sidebar-flyout-list",
10030
10283
  "data-scroll": "muted",
10031
10284
  className: cn(
10032
- "max-h-[420px] min-h-0 overflow-y-auto",
10285
+ // Clip x: items nudge 2px right on hover, and a y-only scroller
10286
+ // would then scroll sideways.
10287
+ "max-h-[420px] min-h-0 overflow-x-hidden overflow-y-auto",
10033
10288
  SUBTLE_SCROLLBAR
10034
10289
  ),
10035
10290
  children: [
@@ -10094,7 +10349,7 @@ function ItemRow({ item, list, visible, staggerIndex, onSelect }) {
10094
10349
  "span",
10095
10350
  {
10096
10351
  className: cn("flex shrink-0", active ? "text-primary" : "text-muted-foreground"),
10097
- children: /* @__PURE__ */ jsx(SidebarIcon, { icon: item.icon, resolveIcon, small: true })
10352
+ children: /* @__PURE__ */ jsx(SidebarIcon, { icon: item.icon, resolveIcon, size: "sm" })
10098
10353
  }
10099
10354
  ),
10100
10355
  /* @__PURE__ */ jsx("span", { className: "min-w-0 [overflow-wrap:anywhere]", children: item.label })
@@ -10203,6 +10458,1111 @@ function ItemRow({ item, list, visible, staggerIndex, onSelect }) {
10203
10458
  }
10204
10459
  );
10205
10460
  }
10461
+ var AppHubContext = createContext(null);
10462
+ function useAppHubContext() {
10463
+ const context = use(AppHubContext);
10464
+ if (!context) throw new Error("AppHub parts must be rendered inside <AppHub>.");
10465
+ return context;
10466
+ }
10467
+
10468
+ // src/components/compounds/AppHub/app-hub.constants.ts
10469
+ var DEFAULT_APP_HUB_ID = "app-hub";
10470
+ var DEFAULT_STORAGE_KEY = "tcce-app-hub:open";
10471
+ var OPEN_CHANGE_EVENT = "tcce-app-hub:open-change";
10472
+ var ITEM_SELECTOR = "[data-app-hub-item]";
10473
+ var ENTRY_SELECTOR = "[data-app-hub-entry]";
10474
+ var FOCUS_RING = "outline-hidden focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-3 focus-visible:outline-main-blue-primary";
10475
+ var TOOLTIP_CLASS = "rounded-lg px-2.75 py-1.75 text-[11px] leading-none font-semibold shadow-black-button motion-reduce:animate-none";
10476
+ var DEFAULT_APP_HUB_LABELS = {
10477
+ navigation: "Applications",
10478
+ allApps: "All applications",
10479
+ sheetTitle: "Applications",
10480
+ hideDock: "Hide dock",
10481
+ showDock: "show applications dock",
10482
+ tab: "Apps",
10483
+ fab: "Apps",
10484
+ pending: (count) => `${count} pending`,
10485
+ leaves: (appName) => `leaves ${appName}`,
10486
+ current: "Current",
10487
+ noAccess: "No access \xB7 ask your manager",
10488
+ searchLabel: "Search applications",
10489
+ searchPlaceholder: "Search apps",
10490
+ clearSearch: "Clear search",
10491
+ resultCount: (count) => count === 1 ? "1 application found" : `${count} applications found`,
10492
+ loading: "Loading applications",
10493
+ empty: "No applications found",
10494
+ noMatch: (query) => `No apps match \u201C${query}\u201D`,
10495
+ noMatchDescription: "Check the spelling or clear the search.",
10496
+ error: "Couldn\u2019t load applications",
10497
+ errorDescription: "The apps in the dock still work.",
10498
+ retry: "Try again",
10499
+ directory: "View all applications",
10500
+ close: "Close",
10501
+ dockHidden: "Applications dock hidden. Use the Apps tab on the right edge to show it again.",
10502
+ dockShown: "Applications dock shown"
10503
+ };
10504
+
10505
+ // src/components/compounds/AppHub/app-hub.utils.ts
10506
+ function mergeLabels(defaults, overrides = {}) {
10507
+ const defined = Object.entries(overrides).filter(([, value]) => value !== void 0);
10508
+ return { ...defaults, ...Object.fromEntries(defined) };
10509
+ }
10510
+ function areLabelsEqual(a, b) {
10511
+ const keys = Object.keys(a);
10512
+ return keys.every((key) => Object.is(a[key], b[key]));
10513
+ }
10514
+ function normalizePending(pending) {
10515
+ return typeof pending === "number" && Number.isFinite(pending) && pending >= 1 ? Math.floor(pending) : 0;
10516
+ }
10517
+ function formatPendingCount(count) {
10518
+ return count > 9 ? "9+" : String(count);
10519
+ }
10520
+ function sumPending(apps) {
10521
+ return apps.reduce((total, app) => total + (app.locked ? 0 : normalizePending(app.pending)), 0);
10522
+ }
10523
+ function getAppAccessibleName(app, labels, appName) {
10524
+ const pending = normalizePending(app.pending);
10525
+ return [
10526
+ app.label,
10527
+ pending > 0 && labels.pending(pending),
10528
+ app.external && labels.leaves(appName)
10529
+ ].filter(Boolean).join(", ");
10530
+ }
10531
+ function getEntryElement(container) {
10532
+ return container?.querySelector(ENTRY_SELECTOR) ?? container?.querySelector(ITEM_SELECTOR) ?? null;
10533
+ }
10534
+ function groupApps(apps) {
10535
+ const sections = /* @__PURE__ */ new Map([[void 0, []]]);
10536
+ for (const app of apps) {
10537
+ const key = app.group || void 0;
10538
+ sections.set(key, [...sections.get(key) ?? [], app]);
10539
+ }
10540
+ return [...sections].filter(([, items]) => items.length > 0).map(([heading, items]) => ({ heading, apps: items }));
10541
+ }
10542
+ function filterApps(apps, query) {
10543
+ const needle = query.trim().toLowerCase();
10544
+ if (!needle) return apps;
10545
+ return apps.filter(
10546
+ (app) => `${app.label} ${app.description ?? ""}`.toLowerCase().includes(needle)
10547
+ );
10548
+ }
10549
+ var appHubIconVariants = cva("relative flex shrink-0 items-center justify-center", {
10550
+ variants: {
10551
+ tone: {
10552
+ green: "bg-green-100 text-green-800",
10553
+ yellow: "bg-yellow-200 text-yellow-800",
10554
+ navy: "bg-main-blue-primary/10 text-main-blue-primary",
10555
+ orange: "bg-orange-100 text-orange-800",
10556
+ purple: "bg-purple-100 text-purple-600",
10557
+ grey: "bg-light-200 text-main-grey"
10558
+ },
10559
+ size: {
10560
+ tile: "size-12 rounded-2xl [&_svg]:size-5",
10561
+ row: "size-9 rounded-xl [&_svg]:size-4.5",
10562
+ sheet: "size-10 rounded-xl [&_svg]:size-5"
10563
+ },
10564
+ // Declared last so `cn()` keeps the current fill over the tone.
10565
+ current: { true: "bg-main-blue-primary text-main-white", false: "" }
10566
+ },
10567
+ defaultVariants: { tone: "grey", size: "tile", current: false }
10568
+ });
10569
+ function AppHubLink({
10570
+ app,
10571
+ afterNavigate,
10572
+ onClick,
10573
+ ...props
10574
+ }) {
10575
+ const { renderLink, onNavigate } = useAppHubContext();
10576
+ const linkProps = {
10577
+ ...props,
10578
+ href: app.href,
10579
+ onClick: (event) => {
10580
+ onClick?.(event);
10581
+ const modified = event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
10582
+ if (modified) return;
10583
+ onNavigate?.(app, event);
10584
+ afterNavigate?.();
10585
+ }
10586
+ };
10587
+ return renderLink ? renderLink(linkProps, app) : /* @__PURE__ */ jsx("a", { ...linkProps });
10588
+ }
10589
+ function AppHubCounter({ count, className }) {
10590
+ if (count <= 0) return null;
10591
+ return /* @__PURE__ */ jsx(
10592
+ "span",
10593
+ {
10594
+ "aria-hidden": "true",
10595
+ "data-slot": "app-hub-counter",
10596
+ className: cn(
10597
+ "bg-main-orange-primary text-background ring-background absolute -top-1 -right-1 flex h-4.5 min-w-4.5 items-center justify-center rounded-full px-1 text-[11px] font-bold ring-2",
10598
+ className
10599
+ ),
10600
+ children: formatPendingCount(count)
10601
+ }
10602
+ );
10603
+ }
10604
+ function AppHubTile({ app, current = false }) {
10605
+ const { labels, appName } = useAppHubContext();
10606
+ const name = getAppAccessibleName(app, labels, appName);
10607
+ const tone = app.tone ?? "grey";
10608
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
10609
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, "aria-describedby": void 0, children: /* @__PURE__ */ jsxs(
10610
+ AppHubLink,
10611
+ {
10612
+ app,
10613
+ "aria-label": name,
10614
+ "aria-current": current ? "page" : void 0,
10615
+ "data-slot": "app-hub-tile",
10616
+ "data-variant": tone,
10617
+ "data-current": current ? "" : void 0,
10618
+ "data-app-hub-item": "",
10619
+ "data-app-hub-entry": current ? "" : void 0,
10620
+ className: cn(
10621
+ appHubIconVariants({ tone, size: "tile", current }),
10622
+ "hover:shadow-black-button transition-[translate,box-shadow] hover:-translate-y-0.5 active:translate-y-px active:shadow-none motion-reduce:transition-none",
10623
+ "data-current:before:bg-main-orange-primary data-current:before:absolute data-current:before:-left-2.5 data-current:before:h-5 data-current:before:w-0.75 data-current:before:rounded-full",
10624
+ FOCUS_RING
10625
+ ),
10626
+ children: [
10627
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "contents", children: app.icon }),
10628
+ /* @__PURE__ */ jsx(AppHubCounter, { count: normalizePending(app.pending) })
10629
+ ]
10630
+ }
10631
+ ) }),
10632
+ /* @__PURE__ */ jsx(
10633
+ TooltipContent,
10634
+ {
10635
+ "aria-hidden": "true",
10636
+ side: "left",
10637
+ variant: "primary",
10638
+ size: "sm",
10639
+ className: TOOLTIP_CLASS,
10640
+ children: app.label
10641
+ }
10642
+ )
10643
+ ] });
10644
+ }
10645
+ var NAV_KEYS = /* @__PURE__ */ new Set(["ArrowDown", "ArrowUp", "Home", "End"]);
10646
+ function useArrowNavigation(containerRef) {
10647
+ return useCallback(
10648
+ (event) => {
10649
+ const container = containerRef.current;
10650
+ const target = event.target;
10651
+ if (event.defaultPrevented || !container || !NAV_KEYS.has(event.key)) return;
10652
+ if (!(target instanceof Element) || !container.contains(target)) return;
10653
+ const current = target.closest(ITEM_SELECTOR);
10654
+ if (!current) return;
10655
+ const items = Array.from(container.querySelectorAll(ITEM_SELECTOR)).filter(
10656
+ (item) => !item.closest("[inert]")
10657
+ );
10658
+ const index = items.indexOf(current);
10659
+ if (index === -1) return;
10660
+ event.preventDefault();
10661
+ const last = items.length - 1;
10662
+ const next = event.key === "Home" ? 0 : event.key === "End" ? last : event.key === "ArrowDown" ? (index + 1) % items.length : (index - 1 + items.length) % items.length;
10663
+ items[next]?.focus();
10664
+ },
10665
+ [containerRef]
10666
+ );
10667
+ }
10668
+ var SKELETON_ROWS = [
10669
+ ["w-[58%]", "w-[82%]"],
10670
+ ["w-[44%]", "w-[70%]"],
10671
+ ["w-[64%]", "w-[76%]"]
10672
+ ];
10673
+ function AppHubRow({
10674
+ app,
10675
+ current,
10676
+ variant,
10677
+ onActivate
10678
+ }) {
10679
+ const { labels, appName } = useAppHubContext();
10680
+ const descId = useId();
10681
+ const locked = app.locked === true;
10682
+ const pending = normalizePending(app.pending);
10683
+ const className = cn(
10684
+ "hover:bg-light-200 flex w-full items-center gap-3 rounded-xl p-2 text-left",
10685
+ variant === "panel" ? "min-h-13" : "min-h-14",
10686
+ current && "bg-light-200",
10687
+ FOCUS_RING
10688
+ );
10689
+ const body = /* @__PURE__ */ jsxs(Fragment$1, { children: [
10690
+ /* @__PURE__ */ jsx(
10691
+ "span",
10692
+ {
10693
+ "aria-hidden": "true",
10694
+ className: appHubIconVariants({
10695
+ tone: locked ? "grey" : app.tone ?? "grey",
10696
+ size: variant === "panel" ? "row" : "sheet"
10697
+ }),
10698
+ children: app.icon
10699
+ }
10700
+ ),
10701
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
10702
+ /* @__PURE__ */ jsxs(
10703
+ "span",
10704
+ {
10705
+ className: cn(
10706
+ "text-light-1000 flex items-center gap-1.5 font-medium",
10707
+ variant === "panel" ? "text-sm" : "text-[15px]"
10708
+ ),
10709
+ children: [
10710
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: app.label }),
10711
+ app.external && /* @__PURE__ */ jsx(
10712
+ ArrowUpRight,
10713
+ {
10714
+ "aria-hidden": "true",
10715
+ className: "text-main-grey size-3.25 shrink-0"
10716
+ }
10717
+ )
10718
+ ]
10719
+ }
10720
+ ),
10721
+ locked ? /* @__PURE__ */ jsxs("span", { className: "text-main-grey flex items-center gap-1 text-xs", children: [
10722
+ /* @__PURE__ */ jsx(Lock, { "aria-hidden": "true", className: "size-3" }),
10723
+ labels.noAccess
10724
+ ] }) : app.description && /* @__PURE__ */ jsx(
10725
+ "span",
10726
+ {
10727
+ id: descId,
10728
+ className: cn(
10729
+ "text-main-grey truncate",
10730
+ variant === "panel" ? "text-xs" : "text-[13px]"
10731
+ ),
10732
+ children: app.description
10733
+ }
10734
+ )
10735
+ ] }),
10736
+ current && /* @__PURE__ */ jsx(
10737
+ "span",
10738
+ {
10739
+ "aria-hidden": "true",
10740
+ className: "bg-main-blue-primary text-main-white shrink-0 rounded-full px-2 py-0.5 text-[11px] leading-tight font-semibold",
10741
+ children: labels.current
10742
+ }
10743
+ ),
10744
+ pending > 0 && !locked && /* @__PURE__ */ jsx(
10745
+ "span",
10746
+ {
10747
+ "aria-hidden": "true",
10748
+ "data-slot": "app-hub-row-counter",
10749
+ className: cn(
10750
+ "bg-main-orange-primary text-background flex shrink-0 items-center justify-center rounded-full font-bold",
10751
+ variant === "panel" ? "h-5 min-w-5 px-1.5 text-[11px]" : "h-5.5 min-w-5.5 px-1.75 text-xs"
10752
+ ),
10753
+ children: formatPendingCount(pending)
10754
+ }
10755
+ )
10756
+ ] });
10757
+ if (locked) {
10758
+ return /* @__PURE__ */ jsx(
10759
+ "button",
10760
+ {
10761
+ type: "button",
10762
+ "aria-disabled": "true",
10763
+ "data-slot": "app-hub-row",
10764
+ "data-app-hub-item": "",
10765
+ className: cn(className, "text-main-grey cursor-not-allowed"),
10766
+ children: body
10767
+ }
10768
+ );
10769
+ }
10770
+ return /* @__PURE__ */ jsx(
10771
+ AppHubLink,
10772
+ {
10773
+ app,
10774
+ afterNavigate: onActivate,
10775
+ "aria-label": getAppAccessibleName(app, labels, appName),
10776
+ "aria-current": current ? "page" : void 0,
10777
+ "aria-describedby": app.description ? descId : void 0,
10778
+ "data-slot": "app-hub-row",
10779
+ "data-app-hub-item": "",
10780
+ className,
10781
+ children: body
10782
+ }
10783
+ );
10784
+ }
10785
+ function AppHubCatalog({
10786
+ variant,
10787
+ apps,
10788
+ currentAppId,
10789
+ status,
10790
+ onRetry,
10791
+ directoryHref,
10792
+ searchThreshold,
10793
+ onActivate
10794
+ }) {
10795
+ const { labels, renderLink } = useAppHubContext();
10796
+ const rootRef = useRef(null);
10797
+ const onKeyDown = useArrowNavigation(rootRef);
10798
+ const searchId = useId();
10799
+ const searchRef = useRef(null);
10800
+ const [query, setQuery] = useState("");
10801
+ const directoryProps = {
10802
+ href: directoryHref ?? "",
10803
+ "data-slot": "app-hub-directory",
10804
+ "data-app-hub-item": "",
10805
+ onClick: () => onActivate?.(),
10806
+ className: cn(
10807
+ "text-main-blue-primary flex items-center gap-2 font-semibold",
10808
+ variant === "panel" ? "border-light-200 hover:bg-light-200 justify-between border-t px-4 py-3 text-[13px]" : "border-light-300 mx-4 mb-4 min-h-13 justify-center rounded-full border text-sm",
10809
+ FOCUS_RING
10810
+ ),
10811
+ children: /* @__PURE__ */ jsxs(Fragment$1, { children: [
10812
+ labels.directory,
10813
+ /* @__PURE__ */ jsx(ArrowRight, { "aria-hidden": "true", className: "size-4 shrink-0" })
10814
+ ] })
10815
+ };
10816
+ const ready = status === "ready";
10817
+ const searchVisible = variant === "panel" && ready && apps.length >= searchThreshold;
10818
+ if (!searchVisible && query) setQuery("");
10819
+ const activeQuery = searchVisible ? query : "";
10820
+ const filtered = filterApps(apps, activeQuery);
10821
+ const message = status === "loading" ? labels.loading : activeQuery && labels.resultCount(filtered.length);
10822
+ const [live, setLive] = useState("");
10823
+ useEffect(() => setLive(message), [message]);
10824
+ return /* @__PURE__ */ jsxs(
10825
+ "div",
10826
+ {
10827
+ ref: rootRef,
10828
+ "data-slot": "app-hub-catalog",
10829
+ "data-variant": variant,
10830
+ tabIndex: -1,
10831
+ onKeyDown,
10832
+ className: "flex min-h-0 flex-1 flex-col outline-none",
10833
+ children: [
10834
+ searchVisible && /* @__PURE__ */ jsxs("div", { "data-slot": "app-hub-search", className: "relative shrink-0 px-3 pt-3", children: [
10835
+ /* @__PURE__ */ jsx("label", { htmlFor: searchId, className: "sr-only", children: labels.searchLabel }),
10836
+ /* @__PURE__ */ jsx(
10837
+ Search,
10838
+ {
10839
+ "aria-hidden": "true",
10840
+ className: "text-main-grey pointer-events-none absolute top-[calc(50%+6px)] left-6 size-4 -translate-y-1/2"
10841
+ }
10842
+ ),
10843
+ /* @__PURE__ */ jsx(
10844
+ Input,
10845
+ {
10846
+ ref: searchRef,
10847
+ id: searchId,
10848
+ type: "search",
10849
+ value: query,
10850
+ placeholder: labels.searchPlaceholder,
10851
+ onChange: (event) => setQuery(event.target.value),
10852
+ className: "border-main-grey/70 bg-background h-10 rounded-lg px-9 text-sm [&::-webkit-search-cancel-button]:appearance-none"
10853
+ }
10854
+ ),
10855
+ query && /* @__PURE__ */ jsx(
10856
+ "button",
10857
+ {
10858
+ type: "button",
10859
+ "aria-label": labels.clearSearch,
10860
+ onClick: () => {
10861
+ setQuery("");
10862
+ searchRef.current?.focus();
10863
+ },
10864
+ className: cn(
10865
+ "text-main-grey hover:bg-light-200 absolute top-[calc(50%+6px)] right-4 flex size-8 -translate-y-1/2 items-center justify-center rounded-lg [&_svg]:size-3.5",
10866
+ FOCUS_RING
10867
+ ),
10868
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
10869
+ }
10870
+ )
10871
+ ] }),
10872
+ /* @__PURE__ */ jsxs(
10873
+ "div",
10874
+ {
10875
+ "data-slot": "app-hub-catalog-scroll",
10876
+ className: cn(
10877
+ "flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto p-2",
10878
+ variant === "panel" && "max-h-74"
10879
+ ),
10880
+ children: [
10881
+ /* @__PURE__ */ jsx("div", { role: "status", className: "sr-only", children: live }),
10882
+ status === "loading" && /* @__PURE__ */ jsx(
10883
+ "div",
10884
+ {
10885
+ "data-slot": "app-hub-loading",
10886
+ "aria-hidden": "true",
10887
+ className: "flex flex-col gap-1",
10888
+ children: SKELETON_ROWS.map(([first, second]) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-2", children: [
10889
+ /* @__PURE__ */ jsx("span", { className: "bg-light-200 size-9 shrink-0 rounded-xl" }),
10890
+ /* @__PURE__ */ jsxs("span", { className: "flex flex-1 flex-col gap-1.5", children: [
10891
+ /* @__PURE__ */ jsx(
10892
+ "span",
10893
+ {
10894
+ className: cn("bg-light-200 h-2.5 rounded-[5px]", first)
10895
+ }
10896
+ ),
10897
+ /* @__PURE__ */ jsx("span", { className: cn("bg-light-200 h-2 rounded-sm", second) })
10898
+ ] })
10899
+ ] }, first))
10900
+ }
10901
+ ),
10902
+ status === "error" && /* @__PURE__ */ jsxs(
10903
+ "div",
10904
+ {
10905
+ role: "alert",
10906
+ "data-slot": "app-hub-error",
10907
+ className: "flex flex-col items-start gap-2.5 px-2 pt-3 pb-2",
10908
+ children: [
10909
+ /* @__PURE__ */ jsx(
10910
+ "span",
10911
+ {
10912
+ "aria-hidden": "true",
10913
+ className: "flex size-9 items-center justify-center rounded-xl bg-red-100 text-red-800",
10914
+ children: /* @__PURE__ */ jsx(CircleAlert, { className: "size-4.5" })
10915
+ }
10916
+ ),
10917
+ /* @__PURE__ */ jsxs("span", { className: "flex flex-col gap-1", children: [
10918
+ /* @__PURE__ */ jsx("span", { className: "text-light-1000 text-sm font-semibold", children: labels.error }),
10919
+ /* @__PURE__ */ jsx("span", { className: "text-main-grey text-[13px] leading-snug", children: labels.errorDescription })
10920
+ ] }),
10921
+ onRetry && /* @__PURE__ */ jsx(
10922
+ Button,
10923
+ {
10924
+ size: "sm",
10925
+ variant: "outline",
10926
+ "data-app-hub-item": "",
10927
+ onClick: () => {
10928
+ rootRef.current?.focus();
10929
+ onRetry();
10930
+ },
10931
+ children: labels.retry
10932
+ }
10933
+ )
10934
+ ]
10935
+ }
10936
+ ),
10937
+ ready && filtered.length === 0 && /* @__PURE__ */ jsxs(
10938
+ "div",
10939
+ {
10940
+ "data-slot": "app-hub-empty",
10941
+ className: "flex flex-col items-start gap-1.5 px-2 pt-4 pb-2",
10942
+ children: [
10943
+ /* @__PURE__ */ jsx("span", { className: "text-light-1000 text-sm font-semibold", children: activeQuery ? labels.noMatch(activeQuery) : labels.empty }),
10944
+ activeQuery && /* @__PURE__ */ jsxs(Fragment$1, { children: [
10945
+ /* @__PURE__ */ jsx("span", { className: "text-main-grey text-[13px] leading-snug", children: labels.noMatchDescription }),
10946
+ /* @__PURE__ */ jsx(
10947
+ "button",
10948
+ {
10949
+ type: "button",
10950
+ "data-app-hub-item": "",
10951
+ onClick: () => {
10952
+ setQuery("");
10953
+ searchRef.current?.focus();
10954
+ },
10955
+ className: cn(
10956
+ "text-main-blue-primary mt-1 rounded-sm py-1.5 text-[13px] font-semibold underline underline-offset-3",
10957
+ FOCUS_RING
10958
+ ),
10959
+ children: labels.clearSearch
10960
+ }
10961
+ )
10962
+ ] })
10963
+ ]
10964
+ }
10965
+ ),
10966
+ ready && groupApps(filtered).map((section) => /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-1", children: [
10967
+ section.heading && /* @__PURE__ */ jsx("h3", { className: "text-main-grey px-2 text-[11px] font-semibold uppercase", children: section.heading }),
10968
+ /* @__PURE__ */ jsx("ul", { role: "list", className: "flex flex-col gap-1", children: section.apps.map((app) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
10969
+ AppHubRow,
10970
+ {
10971
+ app,
10972
+ current: app.id === currentAppId,
10973
+ variant,
10974
+ onActivate
10975
+ }
10976
+ ) }, app.id)) })
10977
+ ] }, section.heading ?? ""))
10978
+ ]
10979
+ }
10980
+ ),
10981
+ directoryHref && /* @__PURE__ */ jsx(Fragment$1, { children: renderLink ? renderLink(directoryProps, void 0) : /* @__PURE__ */ jsx("a", { ...directoryProps }) })
10982
+ ]
10983
+ }
10984
+ );
10985
+ }
10986
+ function AppHubPanel({
10987
+ id,
10988
+ open,
10989
+ onOpenChange,
10990
+ onCloseAutoFocus,
10991
+ ...catalog
10992
+ }) {
10993
+ const { labels } = useAppHubContext();
10994
+ const titleId = useId();
10995
+ const contentRef = useRef(null);
10996
+ const [tooltipOpen, setTooltipOpen] = useState(false);
10997
+ if (open && tooltipOpen) setTooltipOpen(false);
10998
+ const quietRef = useRef(false);
10999
+ useEffect(() => {
11000
+ if (open) return () => void (quietRef.current = true);
11001
+ }, [open]);
11002
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange, children: [
11003
+ /* @__PURE__ */ jsxs(
11004
+ Tooltip,
11005
+ {
11006
+ open: tooltipOpen,
11007
+ onOpenChange: (next) => setTooltipOpen(next && !open && !quietRef.current),
11008
+ children: [
11009
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, "aria-describedby": void 0, children: /* @__PURE__ */ jsx(
11010
+ PopoverTrigger,
11011
+ {
11012
+ "aria-label": labels.allApps,
11013
+ "aria-controls": id,
11014
+ "data-slot": "app-hub-panel-trigger",
11015
+ onPointerEnter: () => void (quietRef.current = false),
11016
+ onBlur: () => void (quietRef.current = false),
11017
+ "data-app-hub-item": "",
11018
+ className: cn(
11019
+ appHubIconVariants({ tone: "grey", size: "tile" }),
11020
+ // Both triggers write `data-state` through `asChild` and the tooltip's value
11021
+ // wins, so the open tint keys off `aria-expanded` instead.
11022
+ "aria-expanded:bg-main-blue-primary/10 aria-expanded:text-main-blue-primary",
11023
+ FOCUS_RING
11024
+ ),
11025
+ children: /* @__PURE__ */ jsx(LayoutGrid, { "aria-hidden": "true" })
11026
+ }
11027
+ ) }),
11028
+ /* @__PURE__ */ jsx(
11029
+ TooltipContent,
11030
+ {
11031
+ "aria-hidden": "true",
11032
+ side: "left",
11033
+ variant: "primary",
11034
+ size: "sm",
11035
+ className: TOOLTIP_CLASS,
11036
+ children: labels.allApps
11037
+ }
11038
+ )
11039
+ ]
11040
+ }
11041
+ ),
11042
+ /* @__PURE__ */ jsxs(
11043
+ PopoverContent,
11044
+ {
11045
+ ref: contentRef,
11046
+ id,
11047
+ side: "left",
11048
+ align: "center",
11049
+ sideOffset: 22,
11050
+ collisionPadding: 8,
11051
+ "aria-labelledby": titleId,
11052
+ "data-slot": "app-hub-panel",
11053
+ onCloseAutoFocus,
11054
+ onOpenAutoFocus: (event) => {
11055
+ event.preventDefault();
11056
+ const content = contentRef.current;
11057
+ (content?.querySelector('input[type="search"]') ?? content?.querySelector(ITEM_SELECTOR) ?? content)?.focus();
11058
+ },
11059
+ className: "border-light-300 shadow-black-card max-h-(--radix-popover-content-available-height) w-75 gap-0 rounded-2xl border p-0 motion-reduce:data-[state=closed]:animate-none motion-reduce:data-[state=open]:animate-none",
11060
+ children: [
11061
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 pt-3 pb-1", children: [
11062
+ /* @__PURE__ */ jsx(
11063
+ "h2",
11064
+ {
11065
+ id: titleId,
11066
+ className: "font-heading text-main-blue-primary text-base leading-tight font-bold",
11067
+ children: labels.allApps
11068
+ }
11069
+ ),
11070
+ /* @__PURE__ */ jsx(
11071
+ "button",
11072
+ {
11073
+ type: "button",
11074
+ "aria-label": labels.close,
11075
+ onClick: () => onOpenChange(false),
11076
+ className: cn(
11077
+ "text-main-grey hover:bg-light-200 flex size-8 items-center justify-center rounded-xl [&_svg]:size-4",
11078
+ FOCUS_RING
11079
+ ),
11080
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
11081
+ }
11082
+ )
11083
+ ] }),
11084
+ /* @__PURE__ */ jsx(
11085
+ AppHubCatalog,
11086
+ {
11087
+ variant: "panel",
11088
+ onActivate: () => onOpenChange(false),
11089
+ ...catalog
11090
+ }
11091
+ )
11092
+ ]
11093
+ }
11094
+ )
11095
+ ] });
11096
+ }
11097
+ function AppHubRail({
11098
+ id,
11099
+ apps,
11100
+ currentAppId,
11101
+ open,
11102
+ onHide,
11103
+ railRef,
11104
+ panel
11105
+ }) {
11106
+ const { labels } = useAppHubContext();
11107
+ const onKeyDown = useArrowNavigation(railRef);
11108
+ return /* @__PURE__ */ jsxs(
11109
+ "div",
11110
+ {
11111
+ ref: railRef,
11112
+ id,
11113
+ "data-slot": "app-hub-rail",
11114
+ "data-state": open ? "open" : "closed",
11115
+ inert: !open,
11116
+ onKeyDown,
11117
+ className: cn(
11118
+ "pointer-events-auto flex flex-col items-center gap-2 px-2.5 py-3",
11119
+ "border-light-200 bg-background/95 shadow-black-card rounded-l-4xl border border-r-0",
11120
+ "transition-transform duration-300 data-[state=closed]:translate-x-[150%] motion-reduce:transition-none"
11121
+ ),
11122
+ children: [
11123
+ /* @__PURE__ */ jsx("ul", { role: "list", className: "flex flex-col items-center gap-2", children: apps.map((app) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(AppHubTile, { app, current: app.id === currentAppId }) }, app.id)) }),
11124
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "bg-light-300 my-0.5 h-px w-6.5" }),
11125
+ panel,
11126
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "bg-light-300 my-0.5 h-px w-6.5" }),
11127
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
11128
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, "aria-describedby": void 0, children: /* @__PURE__ */ jsx(
11129
+ "button",
11130
+ {
11131
+ type: "button",
11132
+ "aria-label": labels.hideDock,
11133
+ "aria-expanded": "true",
11134
+ "aria-controls": id,
11135
+ "data-slot": "app-hub-hide",
11136
+ "data-app-hub-item": "",
11137
+ onClick: onHide,
11138
+ className: cn(
11139
+ "text-main-grey hover:bg-light-200 hover:text-main-blue-primary flex size-8 items-center justify-center rounded-xl [&_svg]:size-4",
11140
+ FOCUS_RING
11141
+ ),
11142
+ children: /* @__PURE__ */ jsx(PanelRightClose, { "aria-hidden": "true" })
11143
+ }
11144
+ ) }),
11145
+ /* @__PURE__ */ jsx(
11146
+ TooltipContent,
11147
+ {
11148
+ "aria-hidden": "true",
11149
+ side: "left",
11150
+ variant: "primary",
11151
+ size: "sm",
11152
+ className: TOOLTIP_CLASS,
11153
+ children: labels.hideDock
11154
+ }
11155
+ )
11156
+ ] })
11157
+ ]
11158
+ }
11159
+ );
11160
+ }
11161
+ function AppHubTab({ railId, open, pending, onShow, tabRef }) {
11162
+ const { labels } = useAppHubContext();
11163
+ const name = [`${labels.tab}, ${labels.showDock}`, pending > 0 && labels.pending(pending)].filter(Boolean).join(", ");
11164
+ return /* @__PURE__ */ jsx(
11165
+ "div",
11166
+ {
11167
+ "data-slot": "app-hub-tab-wrapper",
11168
+ "data-state": open ? "open" : "closed",
11169
+ inert: open,
11170
+ className: "pointer-events-auto transition-transform duration-300 data-[state=open]:translate-x-[150%] motion-reduce:transition-none",
11171
+ children: /* @__PURE__ */ jsxs(
11172
+ "button",
11173
+ {
11174
+ ref: tabRef,
11175
+ type: "button",
11176
+ "aria-label": name,
11177
+ "aria-expanded": "false",
11178
+ "aria-controls": railId,
11179
+ "data-slot": "app-hub-tab",
11180
+ "data-app-hub-entry": "",
11181
+ onClick: onShow,
11182
+ className: cn(
11183
+ "border-light-300 bg-background/95 text-main-blue-primary flex w-10 flex-col items-center gap-2 rounded-l-2xl border border-r-0 py-3.5",
11184
+ FOCUS_RING
11185
+ ),
11186
+ children: [
11187
+ /* @__PURE__ */ jsx(LayoutGrid, { "aria-hidden": "true", className: "size-4.5" }),
11188
+ /* @__PURE__ */ jsx(
11189
+ "span",
11190
+ {
11191
+ "aria-hidden": "true",
11192
+ className: "text-main-grey text-[11px] leading-none font-semibold tracking-[0.12em] uppercase [writing-mode:vertical-rl]",
11193
+ children: labels.tab
11194
+ }
11195
+ ),
11196
+ pending > 0 && /* @__PURE__ */ jsx(
11197
+ "span",
11198
+ {
11199
+ "aria-hidden": "true",
11200
+ "data-slot": "app-hub-counter",
11201
+ className: "bg-main-orange-primary text-background flex h-4.5 min-w-4.5 items-center justify-center rounded-full px-1.25 text-[11px] leading-none font-bold",
11202
+ children: formatPendingCount(pending)
11203
+ }
11204
+ )
11205
+ ]
11206
+ }
11207
+ )
11208
+ }
11209
+ );
11210
+ }
11211
+ function AppHubSheet({ pending, ...catalog }) {
11212
+ const { labels } = useAppHubContext();
11213
+ const [open, setOpen] = useState(false);
11214
+ return /* @__PURE__ */ jsxs(Sheet, { open, onOpenChange: setOpen, children: [
11215
+ /* @__PURE__ */ jsxs(
11216
+ SheetTrigger,
11217
+ {
11218
+ "data-slot": "app-hub-fab",
11219
+ "data-app-hub-entry": "",
11220
+ className: cn(
11221
+ // Bottom-right, in the thumb zone and out of the reading area (the rail centers instead).
11222
+ "bg-main-blue-primary text-main-white shadow-black-button pointer-events-auto mr-4 mb-4 flex h-12 items-center gap-2 self-end rounded-full pr-4.5 pl-4 text-sm font-semibold",
11223
+ FOCUS_RING
11224
+ ),
11225
+ children: [
11226
+ /* @__PURE__ */ jsx(LayoutGrid, { "aria-hidden": "true", className: "size-4.5" }),
11227
+ labels.fab,
11228
+ pending > 0 && /* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
11229
+ ", ",
11230
+ labels.pending(pending)
11231
+ ] }),
11232
+ pending > 0 && /* @__PURE__ */ jsx(
11233
+ "span",
11234
+ {
11235
+ "aria-hidden": "true",
11236
+ "data-slot": "app-hub-counter",
11237
+ className: "bg-main-orange-primary text-background flex h-5 min-w-5 items-center justify-center rounded-full px-1.5 text-[11px] leading-none font-bold",
11238
+ children: formatPendingCount(pending)
11239
+ }
11240
+ )
11241
+ ]
11242
+ }
11243
+ ),
11244
+ /* @__PURE__ */ jsxs(
11245
+ SheetContent,
11246
+ {
11247
+ side: "bottom",
11248
+ "aria-modal": "true",
11249
+ "aria-describedby": void 0,
11250
+ showCloseButton: false,
11251
+ onOpenAutoFocus: (event) => {
11252
+ event.preventDefault();
11253
+ event.currentTarget.querySelector('[data-slot="app-hub-sheet-close"]')?.focus();
11254
+ },
11255
+ className: "max-h-[85dvh] rounded-t-3xl motion-reduce:transition-none motion-reduce:data-closed:animate-none motion-reduce:data-open:animate-none",
11256
+ children: [
11257
+ /* @__PURE__ */ jsx(
11258
+ "div",
11259
+ {
11260
+ "aria-hidden": "true",
11261
+ className: "bg-light-300 mx-auto mt-2 h-1 w-10 rounded-full"
11262
+ }
11263
+ ),
11264
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 pt-2", children: [
11265
+ /* @__PURE__ */ jsx(SheetTitle, { className: "font-heading text-main-blue-primary text-xl leading-tight font-bold", children: labels.sheetTitle }),
11266
+ /* @__PURE__ */ jsx(
11267
+ SheetClose,
11268
+ {
11269
+ "data-slot": "app-hub-sheet-close",
11270
+ "aria-label": labels.close,
11271
+ className: cn(
11272
+ "text-main-grey hover:bg-light-200 flex size-11 items-center justify-center rounded-xl [&_svg]:size-5",
11273
+ FOCUS_RING
11274
+ ),
11275
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
11276
+ }
11277
+ )
11278
+ ] }),
11279
+ /* @__PURE__ */ jsx(AppHubCatalog, { ...catalog, variant: "sheet", onActivate: () => setOpen(false) })
11280
+ ]
11281
+ }
11282
+ )
11283
+ ] });
11284
+ }
11285
+ var noopSubscribe = () => () => {
11286
+ };
11287
+ var nullSnapshot = () => null;
11288
+ function useAppHubOpen({
11289
+ open: controlledOpen,
11290
+ defaultOpen,
11291
+ onOpenChange,
11292
+ storageKey: storageKey2,
11293
+ layoutDefault
11294
+ }) {
11295
+ const controlled = controlledOpen !== void 0;
11296
+ const persistedKey = !controlled ? storageKey2 : null;
11297
+ const fallback = defaultOpen ?? layoutDefault;
11298
+ const [memoryOpen, setMemoryOpen] = useState(void 0);
11299
+ const [sessionOverride, setSessionOverride] = useState(void 0);
11300
+ const store = useMemo(() => {
11301
+ if (persistedKey === null) return { subscribe: noopSubscribe, getSnapshot: nullSnapshot };
11302
+ const subscribe2 = (notify) => {
11303
+ const onStorage = (event) => {
11304
+ if (event.key === null || event.key === persistedKey) notify();
11305
+ };
11306
+ const onChange = (event) => {
11307
+ if (event.detail?.key === persistedKey) notify();
11308
+ };
11309
+ window.addEventListener("storage", onStorage);
11310
+ window.addEventListener(OPEN_CHANGE_EVENT, onChange);
11311
+ return () => {
11312
+ window.removeEventListener("storage", onStorage);
11313
+ window.removeEventListener(OPEN_CHANGE_EVENT, onChange);
11314
+ };
11315
+ };
11316
+ const getSnapshot2 = () => {
11317
+ try {
11318
+ const value = window.localStorage.getItem(persistedKey);
11319
+ return value === "true" ? true : value === "false" ? false : null;
11320
+ } catch {
11321
+ return null;
11322
+ }
11323
+ };
11324
+ return { subscribe: subscribe2, getSnapshot: getSnapshot2 };
11325
+ }, [persistedKey]);
11326
+ const stored = useSyncExternalStore(store.subscribe, store.getSnapshot, nullSnapshot);
11327
+ const setOpen = useCallback(
11328
+ (next) => {
11329
+ onOpenChange?.(next);
11330
+ if (controlled) return;
11331
+ if (storageKey2 === null) {
11332
+ setMemoryOpen(next);
11333
+ return;
11334
+ }
11335
+ try {
11336
+ window.localStorage.setItem(storageKey2, String(next));
11337
+ setSessionOverride(void 0);
11338
+ } catch {
11339
+ setSessionOverride(next);
11340
+ }
11341
+ window.dispatchEvent(
11342
+ new CustomEvent(OPEN_CHANGE_EVENT, { detail: { key: storageKey2 } })
11343
+ );
11344
+ },
11345
+ [controlled, onOpenChange, storageKey2]
11346
+ );
11347
+ let open;
11348
+ if (controlled) open = controlledOpen;
11349
+ else if (storageKey2 === null) open = memoryOpen ?? fallback;
11350
+ else open = sessionOverride ?? stored ?? fallback;
11351
+ return { open, setOpen };
11352
+ }
11353
+ var DESKTOP_QUERY = "(min-width: 1024px)";
11354
+ var TABLET_QUERY = "(min-width: 768px)";
11355
+ function hasMatchMedia() {
11356
+ return typeof window !== "undefined" && typeof window.matchMedia === "function";
11357
+ }
11358
+ function subscribe(notify) {
11359
+ if (!hasMatchMedia()) return () => {
11360
+ };
11361
+ const lists = [DESKTOP_QUERY, TABLET_QUERY].map((query) => window.matchMedia(query));
11362
+ lists.forEach((list) => list.addEventListener("change", notify));
11363
+ return () => lists.forEach((list) => list.removeEventListener("change", notify));
11364
+ }
11365
+ function getSnapshot() {
11366
+ if (!hasMatchMedia()) return "desktop";
11367
+ if (window.matchMedia(DESKTOP_QUERY).matches) return "desktop";
11368
+ return window.matchMedia(TABLET_QUERY).matches ? "tablet" : "mobile";
11369
+ }
11370
+ var getServerSnapshot = () => "desktop";
11371
+ function useAppHubViewport() {
11372
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
11373
+ }
11374
+ function AppHub({
11375
+ apps,
11376
+ appName,
11377
+ currentAppId,
11378
+ catalogStatus = "ready",
11379
+ onRetry,
11380
+ directoryHref,
11381
+ layout = "auto",
11382
+ searchThreshold = 7,
11383
+ position = "fixed",
11384
+ open: openProp,
11385
+ defaultOpen,
11386
+ onOpenChange,
11387
+ storageKey: storageKey2 = DEFAULT_STORAGE_KEY,
11388
+ onNavigate,
11389
+ renderLink,
11390
+ labels: labelOverrides,
11391
+ id = DEFAULT_APP_HUB_ID,
11392
+ className
11393
+ }) {
11394
+ const merged = mergeLabels(DEFAULT_APP_HUB_LABELS, labelOverrides);
11395
+ const [stableLabels, setStableLabels] = useState(merged);
11396
+ const labels = areLabelsEqual(stableLabels, merged) ? stableLabels : merged;
11397
+ if (labels !== stableLabels) setStableLabels(labels);
11398
+ const viewport = useAppHubViewport();
11399
+ const family = layout === "auto" ? viewport === "mobile" ? "sheet" : "dock" : layout;
11400
+ const { open, setOpen } = useAppHubOpen({
11401
+ open: openProp,
11402
+ defaultOpen,
11403
+ onOpenChange,
11404
+ storageKey: storageKey2,
11405
+ // Tablet starts collapsed only when the layout follows the viewport.
11406
+ layoutDefault: !(layout === "auto" && viewport === "tablet")
11407
+ });
11408
+ const [panelOpen, setPanelOpen] = useState(false);
11409
+ if (!open && panelOpen) setPanelOpen(false);
11410
+ const [announcement, setAnnouncement] = useState("");
11411
+ const [, requestRender] = useReducer((count) => count + 1, 0);
11412
+ const previousOpenRef = useRef(open);
11413
+ const railRef = useRef(null);
11414
+ const tabRef = useRef(null);
11415
+ const focusIntentRef = useRef(null);
11416
+ const railId = `${id}-rail`;
11417
+ useLayoutEffect(() => {
11418
+ const intent = focusIntentRef.current;
11419
+ focusIntentRef.current = null;
11420
+ if (previousOpenRef.current === open) return;
11421
+ previousOpenRef.current = open;
11422
+ setAnnouncement(intent === open ? open ? labels.dockShown : labels.dockHidden : "");
11423
+ const entry = open ? getEntryElement(railRef.current) : tabRef.current;
11424
+ if (intent === open) {
11425
+ entry?.focus();
11426
+ return;
11427
+ }
11428
+ const active = document.activeElement;
11429
+ const stranded = open ? [tabRef.current] : [railRef.current, document.getElementById(`${id}-panel`)];
11430
+ if (active && stranded.some((element) => element?.contains(active))) entry?.focus();
11431
+ });
11432
+ function toggle(next) {
11433
+ focusIntentRef.current = next;
11434
+ setOpen(next);
11435
+ requestRender();
11436
+ }
11437
+ const context = useMemo(
11438
+ () => ({ labels, appName, renderLink, onNavigate }),
11439
+ [labels, appName, renderLink, onNavigate]
11440
+ );
11441
+ return /* @__PURE__ */ jsx(AppHubContext, { value: context, children: /* @__PURE__ */ jsxs(
11442
+ "nav",
11443
+ {
11444
+ id,
11445
+ "aria-label": labels.navigation,
11446
+ "data-slot": "app-hub",
11447
+ "data-layout": family,
11448
+ "data-state": open ? "open" : "closed",
11449
+ "data-position": position,
11450
+ className: cn(
11451
+ // `overflow-x-clip` keeps the slid-out form from creating scrollable overflow
11452
+ // (a scrollIntoView would otherwise shift the host); `pl-8` leaves room for the shadow.
11453
+ "pointer-events-none inset-y-0 right-0 z-40 grid items-center justify-items-end overflow-x-clip pl-8 *:col-start-1 *:row-start-1",
11454
+ position === "fixed" ? "fixed" : "absolute",
11455
+ className
11456
+ ),
11457
+ children: [
11458
+ family === "sheet" ? /* @__PURE__ */ jsx(
11459
+ AppHubSheet,
11460
+ {
11461
+ apps,
11462
+ currentAppId,
11463
+ status: catalogStatus,
11464
+ onRetry,
11465
+ directoryHref,
11466
+ searchThreshold,
11467
+ pending: sumPending(apps)
11468
+ }
11469
+ ) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
11470
+ /* @__PURE__ */ jsx(
11471
+ AppHubRail,
11472
+ {
11473
+ id: railId,
11474
+ apps: apps.filter((app) => app.inRail && !app.locked),
11475
+ currentAppId,
11476
+ open,
11477
+ onHide: () => toggle(false),
11478
+ railRef,
11479
+ panel: /* @__PURE__ */ jsx(
11480
+ AppHubPanel,
11481
+ {
11482
+ id: `${id}-panel`,
11483
+ open: panelOpen,
11484
+ onOpenChange: setPanelOpen,
11485
+ onCloseAutoFocus: (event) => {
11486
+ if (open) return;
11487
+ event.preventDefault();
11488
+ const active = document.activeElement;
11489
+ if (!active || active === document.body)
11490
+ tabRef.current?.focus();
11491
+ },
11492
+ apps,
11493
+ currentAppId,
11494
+ status: catalogStatus,
11495
+ onRetry,
11496
+ directoryHref,
11497
+ searchThreshold
11498
+ }
11499
+ )
11500
+ }
11501
+ ),
11502
+ /* @__PURE__ */ jsx(
11503
+ AppHubTab,
11504
+ {
11505
+ railId,
11506
+ open,
11507
+ pending: sumPending(apps),
11508
+ onShow: () => toggle(true),
11509
+ tabRef
11510
+ }
11511
+ )
11512
+ ] }),
11513
+ /* @__PURE__ */ jsx(
11514
+ "div",
11515
+ {
11516
+ role: "status",
11517
+ "aria-live": "polite",
11518
+ "aria-atomic": "true",
11519
+ "data-slot": "app-hub-live",
11520
+ className: "sr-only",
11521
+ children: announcement
11522
+ }
11523
+ )
11524
+ ]
11525
+ }
11526
+ ) });
11527
+ }
11528
+ function getLiveEntry(root) {
11529
+ const live = [...root?.querySelectorAll("[data-slot]") ?? []].find(
11530
+ (region) => !region.closest("[inert]") && getEntryElement(region) !== null
11531
+ );
11532
+ return getEntryElement(live ?? root);
11533
+ }
11534
+ function AppHubSkipLink({
11535
+ targetId = DEFAULT_APP_HUB_ID,
11536
+ children = "Skip to applications",
11537
+ className,
11538
+ onClick,
11539
+ ...props
11540
+ }) {
11541
+ function handleClick(event) {
11542
+ onClick?.(event);
11543
+ if (event.defaultPrevented) return;
11544
+ const entry = getLiveEntry(document.getElementById(targetId));
11545
+ if (!entry) return;
11546
+ event.preventDefault();
11547
+ entry.focus();
11548
+ }
11549
+ return /* @__PURE__ */ jsx(
11550
+ "a",
11551
+ {
11552
+ ...props,
11553
+ href: `#${targetId}`,
11554
+ "data-slot": "app-hub-skip-link",
11555
+ onClick: handleClick,
11556
+ className: cn(
11557
+ "bg-main-blue-primary text-main-white sr-only rounded-md px-4 py-2 text-sm font-semibold",
11558
+ "focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-50",
11559
+ "focus-visible:outline-main-orange-primary outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2",
11560
+ className
11561
+ ),
11562
+ children
11563
+ }
11564
+ );
11565
+ }
10206
11566
  var DrawerContext = createContext({ isOpen: false, modal: true });
10207
11567
  function Drawer({ children, open, onOpenChange, modal = true, ...props }) {
10208
11568
  const [internalOpen, setInternalOpen] = useState(props.defaultOpen ?? false);
@@ -10631,6 +11991,6 @@ function TCCEProvider({
10631
11991
  return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: tooltipDelayDuration, children: /* @__PURE__ */ jsx(AbilityProvider, { permissions, children: /* @__PURE__ */ jsx(Toaster, { position: toasterPosition, children }) }) });
10632
11992
  }
10633
11993
 
10634
- export { AbilityProvider, Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogMedia, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppShell, Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, Badge2 as Badge, BottomNav, BottomNavTabs, BottomNavTabsContent, BottomNavTabsList, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, CONFETTI_COLORS, Can, CanNot, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, Checkbox, ColorPicker, ColorPickerAlpha, ColorPickerEyeDropper, ColorPickerFormat, ColorPickerHue, ColorPickerOutput, ColorPickerPresets, ColorPickerSelection, Combobox, DataTable, DataTableCardView, DataTableColumnFilter, DataTableFooter, DataTableHeaderCell, DataTableRowActions, DataTableSearch, DataTableSkeleton, DataTableToolbar, DatePicker, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Dropzone, DropzoneContent, DropzoneEmptyState, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, EventCalendar, Field, FieldDescription, FieldError, FieldGroup, FieldLabel, FilterOption, FloatingSidebar, Input, Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle, KNOWN_PERMISSIONS, Label, Link, Logo, MiniCalendar, MiniCalendarDay, MiniCalendarDays, MiniCalendarNavigation, Navbar, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PhoneField, PinInput, Popover, PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, SegmentedControl, SegmentedControlItem, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, SignaturePad, Skeleton, Slider, Spinner, SpinnerDots, SpinnerLoader, Status, StatusIndicator, StatusLabel, Switch, TCCEProvider, TEXT_SIZES, TEXT_WEIGHTS, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TablePagination, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Text, Textarea, Toaster, Toggle, ToggleGroup2 as ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, alertVariants, animation, avatarVariants, badgeVariants2 as badgeVariants, buttonVariants, columnFiltersToArrayQueryParams, columnFiltersToJsonString, columnFiltersToQueryParams, columnFiltersToRecord, createBackendParams, createBackendParamsWithFilters, duration, ease, fadeIn, fireConfetti, getActiveFilterCount, getActiveFilterValues, getColumnValueCounts, getUniqueColumnValues, hasActiveFilters, linkVariants, logos, matchPermission, mergeFilters, multiSelectFilter, navigationMenuTriggerStyle, parseJsonToFilters, parseQueryParamsToFilters, progressIndicatorVariants, progressVariants, resetConfetti, scaleIn, searchInArray, skeletonVariants, slideRight, slideUp, sortRecordToSortingState, sortingStateToJsonString, sortingStateToRecord, spinnerDotsVariants, spinnerLoaderVariants, spinnerVariants, spring, stagger, toggleVariants, tooltipContentVariants, transition, useAbility, useAbilityStatus, useAppForm, useCan, useCarousel, useColorPicker, useColumnFilter, useSidebar, useTableContext, useTableData, useTableFiltering, useTableGlobalFilter, useTableSizing, useTableSorting, useTableVisibility, useToast, validatePhone };
11994
+ export { AbilityProvider, Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogMedia, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppHub, AppHubSkipLink, AppShell, Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, Badge2 as Badge, BottomNav, BottomNavTabs, BottomNavTabsContent, BottomNavTabsList, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, CONFETTI_COLORS, Can, CanNot, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, Checkbox, ColorPicker, ColorPickerAlpha, ColorPickerEyeDropper, ColorPickerFormat, ColorPickerHue, ColorPickerOutput, ColorPickerPresets, ColorPickerSelection, Combobox, DataTable, DataTableCardView, DataTableColumnFilter, DataTableFooter, DataTableHeaderCell, DataTableRowActions, DataTableSearch, DataTableSkeleton, DataTableToolbar, DatePicker, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Drawer, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Dropzone, DropzoneContent, DropzoneEmptyState, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, EventCalendar, Field, FieldDescription, FieldError, FieldGroup, FieldLabel, FilterOption, FloatingSidebar, Input, Item, ItemActions, ItemContent, ItemDescription, ItemFooter, ItemGroup, ItemHeader, ItemMedia, ItemSeparator, ItemTitle, KNOWN_PERMISSIONS, Label, Link, Logo, MiniCalendar, MiniCalendarDay, MiniCalendarDays, MiniCalendarNavigation, Navbar, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, PhoneField, PinInput, Popover, PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, SegmentedControl, SegmentedControlItem, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInput, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSkeleton, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarSeparator, SidebarTrigger, SignaturePad, Skeleton, Slider, Spinner, SpinnerDots, SpinnerLoader, Status, StatusIndicator, StatusLabel, Switch, TCCEProvider, TEXT_SIZES, TEXT_WEIGHTS, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TablePagination, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Text, Textarea, Toaster, Toggle, ToggleGroup2 as ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, alertVariants, animation, avatarVariants, badgeVariants2 as badgeVariants, buttonVariants, columnFiltersToArrayQueryParams, columnFiltersToJsonString, columnFiltersToQueryParams, columnFiltersToRecord, createBackendParams, createBackendParamsWithFilters, duration, ease, fadeIn, fireConfetti, getActiveFilterCount, getActiveFilterValues, getColumnValueCounts, getUniqueColumnValues, hasActiveFilters, linkVariants, logos, matchPermission, mergeFilters, multiSelectFilter, navigationMenuTriggerStyle, parseJsonToFilters, parseQueryParamsToFilters, progressIndicatorVariants, progressVariants, resetConfetti, scaleIn, searchInArray, skeletonVariants, slideRight, slideUp, sortRecordToSortingState, sortingStateToJsonString, sortingStateToRecord, spinnerDotsVariants, spinnerLoaderVariants, spinnerVariants, spring, stagger, toggleVariants, tooltipContentVariants, transition, useAbility, useAbilityStatus, useAppForm, useCan, useCarousel, useColorPicker, useColumnFilter, useSidebar, useTableContext, useTableData, useTableFiltering, useTableGlobalFilter, useTableSizing, useTableSorting, useTableVisibility, useToast, validatePhone };
10635
11995
  //# sourceMappingURL=main.js.map
10636
11996
  //# sourceMappingURL=main.js.map