@umodeapporg/ui 0.17.1 → 0.18.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.
@@ -122,13 +122,23 @@
122
122
  }
123
123
  }
124
124
 
125
+ /* Closed, the drawer is also `visibility: hidden` (out of the Tab order and the
126
+ accessibility tree even where `inert` is unsupported), flipped only once the slide-out
127
+ has finished; opening flips it back at once so the slide-in is visible. */
125
128
  .sidebar-aside.is-mobile {
126
129
  width: 16rem;
127
- transition: transform 180ms ease-out;
130
+ visibility: visible;
131
+ transition:
132
+ transform 180ms ease-out,
133
+ visibility 0s linear 0s;
128
134
  }
129
135
 
130
136
  .sidebar-aside.is-mobile.is-hidden {
131
137
  transform: translateX(-100%);
138
+ visibility: hidden;
139
+ transition:
140
+ transform 180ms ease-out,
141
+ visibility 0s linear 180ms;
132
142
  }
133
143
 
134
144
  .sidebar-content {
@@ -1,8 +1,14 @@
1
- import { ReactNode } from 'react';
1
+ import { ReactNode, Ref } from 'react';
2
2
  interface IAppHeaderProps {
3
3
  toggleSidebar: () => void;
4
4
  isSidebarOpen: boolean;
5
5
  toggleLabelKey: string;
6
+ toggleRef?: Ref<HTMLElement>;
7
+ /** Mobile drawer only: drives `aria-expanded` / `aria-controls` on the toggle. */
8
+ mobileSidebar?: {
9
+ id: string;
10
+ open: boolean;
11
+ };
6
12
  homePath: string;
7
13
  /** Replaces the uMode logo; the header stays the same. */
8
14
  logo?: ReactNode;
@@ -10,6 +10,8 @@ const AppHeader = ({
10
10
  toggleSidebar,
11
11
  isSidebarOpen,
12
12
  toggleLabelKey,
13
+ toggleRef,
14
+ mobileSidebar,
13
15
  homePath,
14
16
  logo,
15
17
  toolsSlot,
@@ -28,7 +30,10 @@ const AppHeader = ({
28
30
  icon: isSidebarOpen ? "menu_open" : "menu",
29
31
  size: 22,
30
32
  weight: 400,
33
+ ref: toggleRef,
31
34
  onClick: toggleSidebar,
35
+ "aria-controls": mobileSidebar?.id,
36
+ "aria-expanded": mobileSidebar?.open,
32
37
  onMouseEnter: onHoverEnter,
33
38
  onMouseLeave: onHoverLeave,
34
39
  "aria-label": t(toggleLabelKey),
@@ -1 +1 @@
1
- {"version":3,"file":"AppHeader.js","sources":["../../../src/components/Shell/AppHeader.tsx"],"sourcesContent":["import { ReactNode } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport { useUiNavigation } from '@/components/Navigation/UiNavigationContext';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport UmodeLogo from '@/components/UI/UmodeLogo';\n\nimport { HEADER_CONTROL_SHELL_CLASS } from './headerControlStyles';\n\ninterface IAppHeaderProps {\n toggleSidebar: () => void;\n isSidebarOpen: boolean;\n toggleLabelKey: string;\n homePath: string;\n /** Replaces the uMode logo; the header stays the same. */\n logo?: ReactNode;\n /** App tools rendered inside the pill shell (workspace, notifications...). */\n toolsSlot?: ReactNode;\n /** Theme/language knobs (usually the lib's ThemeToggle + LanguageToggle wired by the host). */\n controlsSlot?: ReactNode;\n /** The account surface (usually the lib's UserMenu wired by the host). */\n userMenuSlot?: ReactNode;\n onHoverEnter: () => void;\n onHoverLeave: () => void;\n}\n\nconst AppHeader: React.FC<IAppHeaderProps> = ({\n toggleSidebar,\n isSidebarOpen,\n toggleLabelKey,\n homePath,\n logo,\n toolsSlot,\n controlsSlot,\n userMenuSlot,\n onHoverEnter,\n onHoverLeave,\n}) => {\n const { t } = useTranslation('ui');\n const { Link: NavigationLink } = useUiNavigation();\n\n return (\n <header className=\"fixed top-0 z-50 flex h-16 w-full select-none items-center justify-between border-x-0 border-t-0 py-2 pr-4 glass-subtle\">\n <div className=\"flex cursor-pointer items-center gap-x-2\">\n <div className=\"sidebar-rail-slot flex shrink-0 items-center justify-center\">\n <MaterialSymbol\n icon={isSidebarOpen ? 'menu_open' : 'menu'}\n size={22}\n weight={400}\n onClick={toggleSidebar}\n onMouseEnter={onHoverEnter}\n onMouseLeave={onHoverLeave}\n aria-label={t(toggleLabelKey)}\n className={classNames(\n 'rounded-lg p-1 transition-colors duration-base',\n isSidebarOpen\n ? 'text-brand-emphasis hover:bg-brand-soft'\n : 'text-foreground-muted hover:bg-muted-hover hover:text-foreground',\n )}\n />\n </div>\n\n <ControlledTooltip content={t('sidebar.home')}>\n <NavigationLink\n to={homePath}\n aria-label={t('sidebar.home')}\n className=\"interactive hidden shrink-0 rounded-lg px-1 text-foreground hover:opacity-70 tablet:block\"\n >\n {logo ?? <UmodeLogo className=\"h-5 w-auto\" />}\n </NavigationLink>\n </ControlledTooltip>\n </div>\n\n <div className=\"flex items-center gap-x-2\">\n {toolsSlot && <div className={HEADER_CONTROL_SHELL_CLASS}>{toolsSlot}</div>}\n {controlsSlot}\n\n {userMenuSlot && (\n <>\n <div className=\"mx-1 h-6 w-px shrink-0 bg-border\" aria-hidden />\n {userMenuSlot}\n </>\n )}\n </div>\n </header>\n );\n};\n\nexport default AppHeader;\n"],"names":[],"mappings":";;;;;;;;AA4BA,MAAM,YAAuC,CAAC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,EAAE,MAAM,eAAA,IAAmB,gBAAA;AAEjC,SACE,qBAAC,UAAA,EAAO,WAAU,2HAChB,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,4CACb,UAAA;AAAA,MAAA,oBAAC,OAAA,EAAI,WAAU,+DACb,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAM,gBAAgB,cAAc;AAAA,UACpC,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,cAAc;AAAA,UACd,cAAc;AAAA,UACd,cAAY,EAAE,cAAc;AAAA,UAC5B,WAAW;AAAA,YACT;AAAA,YACA,gBACI,4CACA;AAAA,UAAA;AAAA,QACN;AAAA,MAAA,GAEJ;AAAA,MAEA,oBAAC,mBAAA,EAAkB,SAAS,EAAE,cAAc,GAC1C,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI;AAAA,UACJ,cAAY,EAAE,cAAc;AAAA,UAC5B,WAAU;AAAA,UAET,UAAA,QAAQ,oBAAC,WAAA,EAAU,WAAU,aAAA,CAAa;AAAA,QAAA;AAAA,MAAA,EAC7C,CACF;AAAA,IAAA,GACF;AAAA,IAEA,qBAAC,OAAA,EAAI,WAAU,6BACZ,UAAA;AAAA,MAAA,aAAa,oBAAC,OAAA,EAAI,WAAW,4BAA6B,UAAA,WAAU;AAAA,MACpE;AAAA,MAEA,gBACC,qBAAA,UAAA,EACE,UAAA;AAAA,QAAA,oBAAC,OAAA,EAAI,WAAU,oCAAmC,eAAW,MAAC;AAAA,QAC7D;AAAA,MAAA,EAAA,CACH;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,GACF;AAEJ;"}
1
+ {"version":3,"file":"AppHeader.js","sources":["../../../src/components/Shell/AppHeader.tsx"],"sourcesContent":["import { ReactNode, Ref } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport { useUiNavigation } from '@/components/Navigation/UiNavigationContext';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport UmodeLogo from '@/components/UI/UmodeLogo';\n\nimport { HEADER_CONTROL_SHELL_CLASS } from './headerControlStyles';\n\ninterface IAppHeaderProps {\n toggleSidebar: () => void;\n isSidebarOpen: boolean;\n toggleLabelKey: string;\n toggleRef?: Ref<HTMLElement>;\n /** Mobile drawer only: drives `aria-expanded` / `aria-controls` on the toggle. */\n mobileSidebar?: { id: string; open: boolean };\n homePath: string;\n /** Replaces the uMode logo; the header stays the same. */\n logo?: ReactNode;\n /** App tools rendered inside the pill shell (workspace, notifications...). */\n toolsSlot?: ReactNode;\n /** Theme/language knobs (usually the lib's ThemeToggle + LanguageToggle wired by the host). */\n controlsSlot?: ReactNode;\n /** The account surface (usually the lib's UserMenu wired by the host). */\n userMenuSlot?: ReactNode;\n onHoverEnter: () => void;\n onHoverLeave: () => void;\n}\n\nconst AppHeader: React.FC<IAppHeaderProps> = ({\n toggleSidebar,\n isSidebarOpen,\n toggleLabelKey,\n toggleRef,\n mobileSidebar,\n homePath,\n logo,\n toolsSlot,\n controlsSlot,\n userMenuSlot,\n onHoverEnter,\n onHoverLeave,\n}) => {\n const { t } = useTranslation('ui');\n const { Link: NavigationLink } = useUiNavigation();\n\n return (\n <header className=\"fixed top-0 z-50 flex h-16 w-full select-none items-center justify-between border-x-0 border-t-0 py-2 pr-4 glass-subtle\">\n <div className=\"flex cursor-pointer items-center gap-x-2\">\n <div className=\"sidebar-rail-slot flex shrink-0 items-center justify-center\">\n <MaterialSymbol\n icon={isSidebarOpen ? 'menu_open' : 'menu'}\n size={22}\n weight={400}\n ref={toggleRef}\n onClick={toggleSidebar}\n aria-controls={mobileSidebar?.id}\n aria-expanded={mobileSidebar?.open}\n onMouseEnter={onHoverEnter}\n onMouseLeave={onHoverLeave}\n aria-label={t(toggleLabelKey)}\n className={classNames(\n 'rounded-lg p-1 transition-colors duration-base',\n isSidebarOpen\n ? 'text-brand-emphasis hover:bg-brand-soft'\n : 'text-foreground-muted hover:bg-muted-hover hover:text-foreground',\n )}\n />\n </div>\n\n <ControlledTooltip content={t('sidebar.home')}>\n <NavigationLink\n to={homePath}\n aria-label={t('sidebar.home')}\n className=\"interactive hidden shrink-0 rounded-lg px-1 text-foreground hover:opacity-70 tablet:block\"\n >\n {logo ?? <UmodeLogo className=\"h-5 w-auto\" />}\n </NavigationLink>\n </ControlledTooltip>\n </div>\n\n <div className=\"flex items-center gap-x-2\">\n {toolsSlot && <div className={HEADER_CONTROL_SHELL_CLASS}>{toolsSlot}</div>}\n {controlsSlot}\n\n {userMenuSlot && (\n <>\n <div className=\"mx-1 h-6 w-px shrink-0 bg-border\" aria-hidden />\n {userMenuSlot}\n </>\n )}\n </div>\n </header>\n );\n};\n\nexport default AppHeader;\n"],"names":[],"mappings":";;;;;;;;AA+BA,MAAM,YAAuC,CAAC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,EAAE,MAAM,eAAA,IAAmB,gBAAA;AAEjC,SACE,qBAAC,UAAA,EAAO,WAAU,2HAChB,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,4CACb,UAAA;AAAA,MAAA,oBAAC,OAAA,EAAI,WAAU,+DACb,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAM,gBAAgB,cAAc;AAAA,UACpC,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,KAAK;AAAA,UACL,SAAS;AAAA,UACT,iBAAe,eAAe;AAAA,UAC9B,iBAAe,eAAe;AAAA,UAC9B,cAAc;AAAA,UACd,cAAc;AAAA,UACd,cAAY,EAAE,cAAc;AAAA,UAC5B,WAAW;AAAA,YACT;AAAA,YACA,gBACI,4CACA;AAAA,UAAA;AAAA,QACN;AAAA,MAAA,GAEJ;AAAA,MAEA,oBAAC,mBAAA,EAAkB,SAAS,EAAE,cAAc,GAC1C,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI;AAAA,UACJ,cAAY,EAAE,cAAc;AAAA,UAC5B,WAAU;AAAA,UAET,UAAA,QAAQ,oBAAC,WAAA,EAAU,WAAU,aAAA,CAAa;AAAA,QAAA;AAAA,MAAA,EAC7C,CACF;AAAA,IAAA,GACF;AAAA,IAEA,qBAAC,OAAA,EAAI,WAAU,6BACZ,UAAA;AAAA,MAAA,aAAa,oBAAC,OAAA,EAAI,WAAW,4BAA6B,UAAA,WAAU;AAAA,MACpE;AAAA,MAEA,gBACC,qBAAA,UAAA,EACE,UAAA;AAAA,QAAA,oBAAC,OAAA,EAAI,WAAU,oCAAmC,eAAW,MAAC;AAAA,QAC7D;AAAA,MAAA,EAAA,CACH;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,GACF;AAEJ;"}
@@ -7,6 +7,8 @@ interface IAppShellProps {
7
7
  toolsSlot?: ReactNode;
8
8
  controlsSlot?: ReactNode;
9
9
  userMenuSlot?: ReactNode;
10
+ /** Sidebar rows pinned to the foot, above the sign-out row (profile, settings, help). */
11
+ footerItems?: Array<ISidebarMenuItem>;
10
12
  /** Sidebar footer sign-out row; hidden when absent. */
11
13
  onLogout?: () => void;
12
14
  children: ReactNode;
@@ -1,11 +1,17 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useRef, useEffect } from "react";
2
+ import { useRef, useId, useEffect } from "react";
3
3
  import classNames from "classnames";
4
+ import { getTabbableElements } from "../Form/ControlledDialogPrimitives.js";
4
5
  import { useUiNavigation } from "../Navigation/UiNavigationContext.js";
5
6
  import AppHeader from "./AppHeader.js";
6
7
  import AppSidebar from "./AppSidebar.js";
7
8
  import { useSidebarState } from "./useSidebarState.js";
8
9
  /* empty css */
10
+ const setInert = (element, inert) => {
11
+ if (!element) return;
12
+ if (inert) element.setAttribute("inert", "");
13
+ else element.removeAttribute("inert");
14
+ };
9
15
  const AppShell = ({
10
16
  items,
11
17
  homePath,
@@ -13,11 +19,16 @@ const AppShell = ({
13
19
  toolsSlot,
14
20
  controlsSlot,
15
21
  userMenuSlot,
22
+ footerItems,
16
23
  onLogout,
17
24
  children
18
25
  }) => {
19
26
  const { location } = useUiNavigation();
20
27
  const scrollRef = useRef(null);
28
+ const sidebarRef = useRef(null);
29
+ const toggleRef = useRef(null);
30
+ const wasMobileOpenRef = useRef(false);
31
+ const sidebarId = useId();
21
32
  const {
22
33
  isMobile,
23
34
  isVisible,
@@ -30,9 +41,39 @@ const AppShell = ({
30
41
  onHoverLeave,
31
42
  closeMobileDrawer
32
43
  } = useSidebarState();
44
+ const isSidebarHidden = isMobile && !isVisible;
45
+ const wasSidebarHiddenRef = useRef(isSidebarHidden);
33
46
  useEffect(() => {
34
47
  scrollRef.current?.scrollTo({ top: 0, behavior: "smooth" });
35
48
  }, [location.pathname]);
49
+ useEffect(() => {
50
+ const sidebar = sidebarRef.current;
51
+ const wasOpen = wasMobileOpenRef.current;
52
+ const wasHidden = wasSidebarHiddenRef.current;
53
+ wasMobileOpenRef.current = isOverlayOnMobile;
54
+ wasSidebarHiddenRef.current = isSidebarHidden;
55
+ if (sidebar && isSidebarHidden && !wasHidden) {
56
+ const active = document.activeElement;
57
+ const isFocusLost = sidebar.contains(active) || wasOpen && (!active || active === document.body);
58
+ if (isFocusLost) toggleRef.current?.focus();
59
+ }
60
+ setInert(sidebar, isSidebarHidden);
61
+ setInert(scrollRef.current, isOverlayOnMobile);
62
+ if (sidebar && isOverlayOnMobile && !wasOpen) getTabbableElements(sidebar)[0]?.focus();
63
+ }, [isSidebarHidden, isOverlayOnMobile]);
64
+ useEffect(() => {
65
+ if (!isOverlayOnMobile) return void 0;
66
+ const handleKeyDown = (event) => {
67
+ if (event.key !== "Escape" || event.defaultPrevented) return;
68
+ const active = document.activeElement;
69
+ const ownsFocus = !active || active === document.body || active === toggleRef.current || sidebarRef.current?.contains(active);
70
+ if (!ownsFocus) return;
71
+ event.preventDefault();
72
+ closeMobileDrawer();
73
+ };
74
+ document.addEventListener("keydown", handleKeyDown);
75
+ return () => document.removeEventListener("keydown", handleKeyDown);
76
+ }, [isOverlayOnMobile, closeMobileDrawer]);
36
77
  return /* @__PURE__ */ jsxs("div", { className: "h-screen w-full bg-surface", children: [
37
78
  /* @__PURE__ */ jsx(
38
79
  AppHeader,
@@ -40,6 +81,8 @@ const AppShell = ({
40
81
  toggleSidebar: togglePrimary,
41
82
  isSidebarOpen: isDocked,
42
83
  toggleLabelKey,
84
+ toggleRef,
85
+ mobileSidebar: isMobile ? { id: sidebarId, open: isOverlayOnMobile } : void 0,
43
86
  homePath,
44
87
  logo,
45
88
  toolsSlot,
@@ -68,6 +111,8 @@ const AppShell = ({
68
111
  /* @__PURE__ */ jsx(
69
112
  AppSidebar,
70
113
  {
114
+ id: sidebarId,
115
+ sidebarRef,
71
116
  items,
72
117
  isMobile,
73
118
  isVisible,
@@ -76,6 +121,7 @@ const AppShell = ({
76
121
  onHoverEnter,
77
122
  onHoverLeave,
78
123
  onNavigate: isMobile ? closeMobileDrawer : void 0,
124
+ footerItems,
79
125
  onLogout
80
126
  }
81
127
  ),
@@ -1 +1 @@
1
- {"version":3,"file":"AppShell.js","sources":["../../../src/components/Shell/AppShell.tsx"],"sourcesContent":["import { ReactNode, useEffect, useRef } from 'react';\nimport classNames from 'classnames';\n\nimport { useUiNavigation } from '@/components/Navigation/UiNavigationContext';\n\nimport AppHeader from './AppHeader';\nimport AppSidebar from './AppSidebar';\nimport { ISidebarMenuItem } from './SidebarMenu';\nimport { useSidebarState } from './useSidebarState';\n\nimport '../../styles/sidebar.css';\n\ninterface IAppShellProps {\n items: Array<ISidebarMenuItem>;\n homePath: string;\n logo?: ReactNode;\n toolsSlot?: ReactNode;\n controlsSlot?: ReactNode;\n userMenuSlot?: ReactNode;\n /** Sidebar footer sign-out row; hidden when absent. */\n onLogout?: () => void;\n children: ReactNode;\n}\n\n/**\n * The uMode application chrome: fixed glass header, rail/dock/hover sidebar (pin persisted\n * through uiPreferences, Ctrl+\\ shortcut) and the scroll container every page renders into.\n * Everything app-shaped (menu items, tools, account surface, logout) arrives as props/slots.\n */\nconst AppShell: React.FC<IAppShellProps> = ({\n items,\n homePath,\n logo,\n toolsSlot,\n controlsSlot,\n userMenuSlot,\n onLogout,\n children,\n}) => {\n const { location } = useUiNavigation();\n const scrollRef = useRef<HTMLDivElement>(null);\n\n const {\n isMobile,\n isVisible,\n isDocked,\n isOverlayOnMobile,\n showRail,\n toggleLabelKey,\n togglePrimary,\n onHoverEnter,\n onHoverLeave,\n closeMobileDrawer,\n } = useSidebarState();\n\n useEffect(() => {\n scrollRef.current?.scrollTo({ top: 0, behavior: 'smooth' });\n }, [location.pathname]);\n\n return (\n <div className=\"h-screen w-full bg-surface\">\n <AppHeader\n toggleSidebar={togglePrimary}\n isSidebarOpen={isDocked}\n toggleLabelKey={toggleLabelKey}\n homePath={homePath}\n logo={logo}\n toolsSlot={toolsSlot}\n controlsSlot={controlsSlot}\n userMenuSlot={userMenuSlot}\n onHoverEnter={onHoverEnter}\n onHoverLeave={onHoverLeave}\n />\n\n <main\n className={classNames('sidebar-content flex h-screen w-full', {\n 'is-rail': !isMobile && !isDocked,\n 'is-mobile': isMobile,\n })}\n >\n {isOverlayOnMobile && (\n <div\n aria-hidden\n className=\"fixed inset-0 z-40 bg-scrim motion-reduce:transition-none\"\n onClick={closeMobileDrawer}\n />\n )}\n\n <AppSidebar\n items={items}\n isMobile={isMobile}\n isVisible={isVisible}\n collapsed={showRail}\n isFloating={!isMobile && !showRail && !isDocked}\n onHoverEnter={onHoverEnter}\n onHoverLeave={onHoverLeave}\n onNavigate={isMobile ? closeMobileDrawer : undefined}\n onLogout={onLogout}\n />\n\n <div\n id=\"scrollableDiv\"\n ref={scrollRef}\n className=\"z-30 h-screen w-full px-4 pt-24 tablet:px-6\"\n style={{ overflow: 'auto' }}\n >\n {children}\n </div>\n </main>\n </div>\n );\n};\n\nexport default AppShell;\n"],"names":[],"mappings":";;;;;;;;AA6BA,MAAM,WAAqC,CAAC;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,SAAA,IAAa,gBAAA;AACrB,QAAM,YAAY,OAAuB,IAAI;AAE7C,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,IACE,gBAAA;AAEJ,YAAU,MAAM;AACd,cAAU,SAAS,SAAS,EAAE,KAAK,GAAG,UAAU,UAAU;AAAA,EAC5D,GAAG,CAAC,SAAS,QAAQ,CAAC;AAEtB,SACE,qBAAC,OAAA,EAAI,WAAU,8BACb,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAe;AAAA,QACf,eAAe;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IAAA;AAAA,IAGF;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW,WAAW,wCAAwC;AAAA,UAC5D,WAAW,CAAC,YAAY,CAAC;AAAA,UACzB,aAAa;AAAA,QAAA,CACd;AAAA,QAEA,UAAA;AAAA,UAAA,qBACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,eAAW;AAAA,cACX,WAAU;AAAA,cACV,SAAS;AAAA,YAAA;AAAA,UAAA;AAAA,UAIb;AAAA,YAAC;AAAA,YAAA;AAAA,cACC;AAAA,cACA;AAAA,cACA;AAAA,cACA,WAAW;AAAA,cACX,YAAY,CAAC,YAAY,CAAC,YAAY,CAAC;AAAA,cACvC;AAAA,cACA;AAAA,cACA,YAAY,WAAW,oBAAoB;AAAA,cAC3C;AAAA,YAAA;AAAA,UAAA;AAAA,UAGF;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,IAAG;AAAA,cACH,KAAK;AAAA,cACL,WAAU;AAAA,cACV,OAAO,EAAE,UAAU,OAAA;AAAA,cAElB;AAAA,YAAA;AAAA,UAAA;AAAA,QACH;AAAA,MAAA;AAAA,IAAA;AAAA,EACF,GACF;AAEJ;"}
1
+ {"version":3,"file":"AppShell.js","sources":["../../../src/components/Shell/AppShell.tsx"],"sourcesContent":["import { ReactNode, useEffect, useId, useRef } from 'react';\nimport classNames from 'classnames';\n\nimport { getTabbableElements } from '@/components/Form/ControlledDialogPrimitives';\nimport { useUiNavigation } from '@/components/Navigation/UiNavigationContext';\n\nimport AppHeader from './AppHeader';\nimport AppSidebar from './AppSidebar';\nimport { ISidebarMenuItem } from './SidebarMenu';\nimport { useSidebarState } from './useSidebarState';\n\nimport '../../styles/sidebar.css';\n\ninterface IAppShellProps {\n items: Array<ISidebarMenuItem>;\n homePath: string;\n logo?: ReactNode;\n toolsSlot?: ReactNode;\n controlsSlot?: ReactNode;\n userMenuSlot?: ReactNode;\n /** Sidebar rows pinned to the foot, above the sign-out row (profile, settings, help). */\n footerItems?: Array<ISidebarMenuItem>;\n /** Sidebar footer sign-out row; hidden when absent. */\n onLogout?: () => void;\n children: ReactNode;\n}\n\nconst setInert = (element: HTMLElement | null, inert: boolean) => {\n if (!element) return;\n if (inert) element.setAttribute('inert', '');\n else element.removeAttribute('inert');\n};\n\n/**\n * The uMode application chrome: fixed glass header, rail/dock/hover sidebar (pin persisted\n * through uiPreferences, Ctrl+\\ shortcut) and the scroll container every page renders into.\n * Everything app-shaped (menu items, tools, account surface, logout) arrives as props/slots.\n */\nconst AppShell: React.FC<IAppShellProps> = ({\n items,\n homePath,\n logo,\n toolsSlot,\n controlsSlot,\n userMenuSlot,\n footerItems,\n onLogout,\n children,\n}) => {\n const { location } = useUiNavigation();\n const scrollRef = useRef<HTMLDivElement>(null);\n const sidebarRef = useRef<HTMLElement>(null);\n const toggleRef = useRef<HTMLElement>(null);\n const wasMobileOpenRef = useRef(false);\n const sidebarId = useId();\n\n const {\n isMobile,\n isVisible,\n isDocked,\n isOverlayOnMobile,\n showRail,\n toggleLabelKey,\n togglePrimary,\n onHoverEnter,\n onHoverLeave,\n closeMobileDrawer,\n } = useSidebarState();\n\n const isSidebarHidden = isMobile && !isVisible;\n const wasSidebarHiddenRef = useRef(isSidebarHidden);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({ top: 0, behavior: 'smooth' });\n }, [location.pathname]);\n\n /* The closed mobile drawer only slides off-screen, so it can animate out: `inert` takes it\n out of the Tab order and the accessibility tree, and the open drawer makes the page behind\n its scrim inert instead. Set through the DOM because React 18 does not type the attribute.\n Focus leaves the sidebar BEFORE it goes inert, and only when it is actually being hidden\n (drawer closing, or a desktop resize down to mobile): a drawer that becomes the visible\n desktop sidebar keeps the focus where it is. */\n useEffect(() => {\n const sidebar = sidebarRef.current;\n const wasOpen = wasMobileOpenRef.current;\n const wasHidden = wasSidebarHiddenRef.current;\n wasMobileOpenRef.current = isOverlayOnMobile;\n wasSidebarHiddenRef.current = isSidebarHidden;\n\n if (sidebar && isSidebarHidden && !wasHidden) {\n const active = document.activeElement;\n const isFocusLost = sidebar.contains(active) || (wasOpen && (!active || active === document.body));\n if (isFocusLost) toggleRef.current?.focus();\n }\n\n setInert(sidebar, isSidebarHidden);\n setInert(scrollRef.current, isOverlayOnMobile);\n\n if (sidebar && isOverlayOnMobile && !wasOpen) getTabbableElements(sidebar)[0]?.focus();\n }, [isSidebarHidden, isOverlayOnMobile]);\n\n useEffect(() => {\n if (!isOverlayOnMobile) return undefined;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape' || event.defaultPrevented) return;\n const active = document.activeElement;\n const ownsFocus =\n !active || active === document.body || active === toggleRef.current || sidebarRef.current?.contains(active);\n if (!ownsFocus) return;\n event.preventDefault();\n closeMobileDrawer();\n };\n\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [isOverlayOnMobile, closeMobileDrawer]);\n\n return (\n <div className=\"h-screen w-full bg-surface\">\n <AppHeader\n toggleSidebar={togglePrimary}\n isSidebarOpen={isDocked}\n toggleLabelKey={toggleLabelKey}\n toggleRef={toggleRef}\n mobileSidebar={isMobile ? { id: sidebarId, open: isOverlayOnMobile } : undefined}\n homePath={homePath}\n logo={logo}\n toolsSlot={toolsSlot}\n controlsSlot={controlsSlot}\n userMenuSlot={userMenuSlot}\n onHoverEnter={onHoverEnter}\n onHoverLeave={onHoverLeave}\n />\n\n <main\n className={classNames('sidebar-content flex h-screen w-full', {\n 'is-rail': !isMobile && !isDocked,\n 'is-mobile': isMobile,\n })}\n >\n {isOverlayOnMobile && (\n <div\n aria-hidden\n className=\"fixed inset-0 z-40 bg-scrim motion-reduce:transition-none\"\n onClick={closeMobileDrawer}\n />\n )}\n\n <AppSidebar\n id={sidebarId}\n sidebarRef={sidebarRef}\n items={items}\n isMobile={isMobile}\n isVisible={isVisible}\n collapsed={showRail}\n isFloating={!isMobile && !showRail && !isDocked}\n onHoverEnter={onHoverEnter}\n onHoverLeave={onHoverLeave}\n onNavigate={isMobile ? closeMobileDrawer : undefined}\n footerItems={footerItems}\n onLogout={onLogout}\n />\n\n <div\n id=\"scrollableDiv\"\n ref={scrollRef}\n className=\"z-30 h-screen w-full px-4 pt-24 tablet:px-6\"\n style={{ overflow: 'auto' }}\n >\n {children}\n </div>\n </main>\n </div>\n );\n};\n\nexport default AppShell;\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,8 @@
1
+ import { Ref } from 'react';
1
2
  import { ISidebarMenuItem } from './SidebarMenu';
2
3
  interface IAppSidebarProps {
4
+ id?: string;
5
+ sidebarRef?: Ref<HTMLElement>;
3
6
  items: Array<ISidebarMenuItem>;
4
7
  isMobile: boolean;
5
8
  isVisible: boolean;
@@ -8,6 +11,8 @@ interface IAppSidebarProps {
8
11
  onHoverEnter: () => void;
9
12
  onHoverLeave: () => void;
10
13
  onNavigate?: () => void;
14
+ /** Rows pinned to the foot of the sidebar, above the sign-out row. */
15
+ footerItems?: Array<ISidebarMenuItem>;
11
16
  /** Hides the footer sign-out row when absent. */
12
17
  onLogout?: () => void;
13
18
  }
@@ -6,6 +6,8 @@ import ControlledButton from "../Form/ControlledButton.js";
6
6
  import { MaterialSymbol } from "../UI/MaterialSymbol/index.js";
7
7
  import { SidebarFavoritesProvider, useSidebarFavorites, buildFavoriteMenuItems } from "./sidebarFavorites.js";
8
8
  import SidebarMenu from "./SidebarMenu.js";
9
+ const ACTIVE_CLASS = "text-brand-emphasis bg-brand-soft";
10
+ const ACTIVE_ICON_CLASS = "text-brand-emphasis";
9
11
  const SidebarMenuWithFavorites = ({ items, collapsed, onNavigate }) => {
10
12
  const { favoriteKeys } = useSidebarFavorites();
11
13
  const menuItems = useMemo(
@@ -17,13 +19,15 @@ const SidebarMenuWithFavorites = ({ items, collapsed, onNavigate }) => {
17
19
  {
18
20
  items: menuItems,
19
21
  collapsed,
20
- activeClass: "text-brand-emphasis bg-brand-soft",
21
- activeIconClass: "text-brand-emphasis",
22
+ activeClass: ACTIVE_CLASS,
23
+ activeIconClass: ACTIVE_ICON_CLASS,
22
24
  onNavigate
23
25
  }
24
26
  );
25
27
  };
26
28
  const AppSidebar = ({
29
+ id,
30
+ sidebarRef,
27
31
  items,
28
32
  isMobile,
29
33
  isVisible,
@@ -32,12 +36,16 @@ const AppSidebar = ({
32
36
  onHoverEnter,
33
37
  onHoverLeave,
34
38
  onNavigate,
39
+ footerItems,
35
40
  onLogout
36
41
  }) => {
37
42
  const { t } = useTranslation("ui");
43
+ const hasFooterItems = Boolean(footerItems?.length);
38
44
  return /* @__PURE__ */ jsx(
39
45
  "aside",
40
46
  {
47
+ id,
48
+ ref: sidebarRef,
41
49
  onMouseEnter: onHoverEnter,
42
50
  onMouseLeave: onHoverLeave,
43
51
  className: classNames(
@@ -51,18 +59,30 @@ const AppSidebar = ({
51
59
  ),
52
60
  children: /* @__PURE__ */ jsxs("nav", { className: "sidebar-scroll flex h-full flex-col justify-between gap-y-5 overflow-y-auto overflow-x-hidden px-3 py-5 pt-20", children: [
53
61
  /* @__PURE__ */ jsx(SidebarFavoritesProvider, { children: /* @__PURE__ */ jsx(SidebarMenuWithFavorites, { items, collapsed, onNavigate }) }),
54
- onLogout && /* @__PURE__ */ jsx("div", { className: "text-foreground-muted", children: /* @__PURE__ */ jsxs(
55
- ControlledButton,
56
- {
57
- defaultHtmlButton: true,
58
- onClick: onLogout,
59
- className: "flex h-9 w-full items-center gap-x-3 rounded-lg px-3 text-left text-foreground-muted transition-colors duration-base hover:bg-muted-hover hover:text-foreground",
60
- children: [
61
- /* @__PURE__ */ jsx(MaterialSymbol, { icon: "logout", size: 20, weight: 400, className: "shrink-0" }),
62
- /* @__PURE__ */ jsx("span", { className: classNames("sidebar-label text-sm", { "is-collapsed": collapsed }), children: t("sidebar.logout") })
63
- ]
64
- }
65
- ) })
62
+ (hasFooterItems || onLogout) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-2 text-foreground-muted", children: [
63
+ hasFooterItems && /* @__PURE__ */ jsx(
64
+ SidebarMenu,
65
+ {
66
+ items: footerItems ?? [],
67
+ collapsed,
68
+ activeClass: ACTIVE_CLASS,
69
+ activeIconClass: ACTIVE_ICON_CLASS,
70
+ onNavigate
71
+ }
72
+ ),
73
+ onLogout && /* @__PURE__ */ jsxs(
74
+ ControlledButton,
75
+ {
76
+ defaultHtmlButton: true,
77
+ onClick: onLogout,
78
+ className: "flex h-9 w-full items-center gap-x-3 rounded-lg px-3 text-left text-foreground-muted transition-colors duration-base hover:bg-muted-hover hover:text-foreground",
79
+ children: [
80
+ /* @__PURE__ */ jsx(MaterialSymbol, { icon: "logout", size: 20, weight: 400, className: "shrink-0" }),
81
+ /* @__PURE__ */ jsx("span", { className: classNames("sidebar-label text-sm", { "is-collapsed": collapsed }), children: t("sidebar.logout") })
82
+ ]
83
+ }
84
+ )
85
+ ] })
66
86
  ] })
67
87
  }
68
88
  );
@@ -1 +1 @@
1
- {"version":3,"file":"AppSidebar.js","sources":["../../../src/components/Shell/AppSidebar.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\n\nimport { buildFavoriteMenuItems, SidebarFavoritesProvider, useSidebarFavorites } from './sidebarFavorites';\nimport SidebarMenu, { ISidebarMenuItem } from './SidebarMenu';\n\ninterface IAppSidebarProps {\n items: Array<ISidebarMenuItem>;\n isMobile: boolean;\n isVisible: boolean;\n collapsed: boolean;\n isFloating: boolean;\n onHoverEnter: () => void;\n onHoverLeave: () => void;\n onNavigate?: () => void;\n /** Hides the footer sign-out row when absent. */\n onLogout?: () => void;\n}\n\ninterface ISidebarMenuWithFavoritesProps {\n items: Array<ISidebarMenuItem>;\n collapsed: boolean;\n onNavigate?: () => void;\n}\n\nconst SidebarMenuWithFavorites: React.FC<ISidebarMenuWithFavoritesProps> = ({ items, collapsed, onNavigate }) => {\n const { favoriteKeys } = useSidebarFavorites();\n\n const menuItems = useMemo(\n () => [...buildFavoriteMenuItems(items, favoriteKeys), ...(items ?? [])],\n [favoriteKeys, items],\n );\n\n return (\n <SidebarMenu\n items={menuItems}\n collapsed={collapsed}\n activeClass=\"text-brand-emphasis bg-brand-soft\"\n activeIconClass=\"text-brand-emphasis\"\n onNavigate={onNavigate}\n />\n );\n};\n\nconst AppSidebar: React.FC<IAppSidebarProps> = ({\n items,\n isMobile,\n isVisible,\n collapsed,\n isFloating,\n onHoverEnter,\n onHoverLeave,\n onNavigate,\n onLogout,\n}) => {\n const { t } = useTranslation('ui');\n\n return (\n <aside\n onMouseEnter={onHoverEnter}\n onMouseLeave={onHoverLeave}\n className={classNames(\n 'sidebar-aside fixed left-0 top-0 z-40 h-full overflow-hidden border-r border-border bg-muted',\n {\n 'is-rail': !isMobile && collapsed,\n 'is-mobile': isMobile,\n 'is-floating': isFloating,\n 'is-hidden pointer-events-none': isMobile && !isVisible,\n },\n )}\n >\n <nav className=\"sidebar-scroll flex h-full flex-col justify-between gap-y-5 overflow-y-auto overflow-x-hidden px-3 py-5 pt-20\">\n <SidebarFavoritesProvider>\n <SidebarMenuWithFavorites items={items} collapsed={collapsed} onNavigate={onNavigate} />\n </SidebarFavoritesProvider>\n\n {onLogout && (\n <div className=\"text-foreground-muted\">\n <ControlledButton\n defaultHtmlButton\n onClick={onLogout}\n className=\"flex h-9 w-full items-center gap-x-3 rounded-lg px-3 text-left text-foreground-muted transition-colors duration-base hover:bg-muted-hover hover:text-foreground\"\n >\n <MaterialSymbol icon=\"logout\" size={20} weight={400} className=\"shrink-0\" />\n <span className={classNames('sidebar-label text-sm', { 'is-collapsed': collapsed })}>\n {t('sidebar.logout')}\n </span>\n </ControlledButton>\n </div>\n )}\n </nav>\n </aside>\n );\n};\n\nexport default AppSidebar;\n"],"names":[],"mappings":";;;;;;;;AA6BA,MAAM,2BAAqE,CAAC,EAAE,OAAO,WAAW,iBAAiB;AAC/G,QAAM,EAAE,aAAA,IAAiB,oBAAA;AAEzB,QAAM,YAAY;AAAA,IAChB,MAAM,CAAC,GAAG,uBAAuB,OAAO,YAAY,GAAG,GAAI,SAAS,EAAG;AAAA,IACvE,CAAC,cAAc,KAAK;AAAA,EAAA;AAGtB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,OAAO;AAAA,MACP;AAAA,MACA,aAAY;AAAA,MACZ,iBAAgB;AAAA,MAChB;AAAA,IAAA;AAAA,EAAA;AAGN;AAEA,MAAM,aAAyC,CAAC;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,cAAc;AAAA,MACd,cAAc;AAAA,MACd,WAAW;AAAA,QACT;AAAA,QACA;AAAA,UACE,WAAW,CAAC,YAAY;AAAA,UACxB,aAAa;AAAA,UACb,eAAe;AAAA,UACf,iCAAiC,YAAY,CAAC;AAAA,QAAA;AAAA,MAChD;AAAA,MAGF,UAAA,qBAAC,OAAA,EAAI,WAAU,iHACb,UAAA;AAAA,QAAA,oBAAC,4BACC,UAAA,oBAAC,0BAAA,EAAyB,OAAc,WAAsB,YAAwB,GACxF;AAAA,QAEC,YACC,oBAAC,OAAA,EAAI,WAAU,yBACb,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,mBAAiB;AAAA,YACjB,SAAS;AAAA,YACT,WAAU;AAAA,YAEV,UAAA;AAAA,cAAA,oBAAC,gBAAA,EAAe,MAAK,UAAS,MAAM,IAAI,QAAQ,KAAK,WAAU,WAAA,CAAW;AAAA,cAC1E,oBAAC,QAAA,EAAK,WAAW,WAAW,yBAAyB,EAAE,gBAAgB,UAAA,CAAW,GAC/E,UAAA,EAAE,gBAAgB,EAAA,CACrB;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA,EACF,CACF;AAAA,MAAA,EAAA,CAEJ;AAAA,IAAA;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"AppSidebar.js","sources":["../../../src/components/Shell/AppSidebar.tsx"],"sourcesContent":["import { Ref, useMemo } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\n\nimport { buildFavoriteMenuItems, SidebarFavoritesProvider, useSidebarFavorites } from './sidebarFavorites';\nimport SidebarMenu, { ISidebarMenuItem } from './SidebarMenu';\n\ninterface IAppSidebarProps {\n id?: string;\n sidebarRef?: Ref<HTMLElement>;\n items: Array<ISidebarMenuItem>;\n isMobile: boolean;\n isVisible: boolean;\n collapsed: boolean;\n isFloating: boolean;\n onHoverEnter: () => void;\n onHoverLeave: () => void;\n onNavigate?: () => void;\n /** Rows pinned to the foot of the sidebar, above the sign-out row. */\n footerItems?: Array<ISidebarMenuItem>;\n /** Hides the footer sign-out row when absent. */\n onLogout?: () => void;\n}\n\ninterface ISidebarMenuWithFavoritesProps {\n items: Array<ISidebarMenuItem>;\n collapsed: boolean;\n onNavigate?: () => void;\n}\n\nconst ACTIVE_CLASS = 'text-brand-emphasis bg-brand-soft';\nconst ACTIVE_ICON_CLASS = 'text-brand-emphasis';\n\nconst SidebarMenuWithFavorites: React.FC<ISidebarMenuWithFavoritesProps> = ({ items, collapsed, onNavigate }) => {\n const { favoriteKeys } = useSidebarFavorites();\n\n const menuItems = useMemo(\n () => [...buildFavoriteMenuItems(items, favoriteKeys), ...(items ?? [])],\n [favoriteKeys, items],\n );\n\n return (\n <SidebarMenu\n items={menuItems}\n collapsed={collapsed}\n activeClass={ACTIVE_CLASS}\n activeIconClass={ACTIVE_ICON_CLASS}\n onNavigate={onNavigate}\n />\n );\n};\n\nconst AppSidebar: React.FC<IAppSidebarProps> = ({\n id,\n sidebarRef,\n items,\n isMobile,\n isVisible,\n collapsed,\n isFloating,\n onHoverEnter,\n onHoverLeave,\n onNavigate,\n footerItems,\n onLogout,\n}) => {\n const { t } = useTranslation('ui');\n const hasFooterItems = Boolean(footerItems?.length);\n\n return (\n <aside\n id={id}\n ref={sidebarRef}\n onMouseEnter={onHoverEnter}\n onMouseLeave={onHoverLeave}\n className={classNames(\n 'sidebar-aside fixed left-0 top-0 z-40 h-full overflow-hidden border-r border-border bg-muted',\n {\n 'is-rail': !isMobile && collapsed,\n 'is-mobile': isMobile,\n 'is-floating': isFloating,\n 'is-hidden pointer-events-none': isMobile && !isVisible,\n },\n )}\n >\n <nav className=\"sidebar-scroll flex h-full flex-col justify-between gap-y-5 overflow-y-auto overflow-x-hidden px-3 py-5 pt-20\">\n <SidebarFavoritesProvider>\n <SidebarMenuWithFavorites items={items} collapsed={collapsed} onNavigate={onNavigate} />\n </SidebarFavoritesProvider>\n\n {(hasFooterItems || onLogout) && (\n <div className=\"flex flex-col gap-y-2 text-foreground-muted\">\n {/* Outside the favorites provider on purpose: a row that is always in sight takes no star. */}\n {hasFooterItems && (\n <SidebarMenu\n items={footerItems ?? []}\n collapsed={collapsed}\n activeClass={ACTIVE_CLASS}\n activeIconClass={ACTIVE_ICON_CLASS}\n onNavigate={onNavigate}\n />\n )}\n\n {onLogout && (\n <ControlledButton\n defaultHtmlButton\n onClick={onLogout}\n className=\"flex h-9 w-full items-center gap-x-3 rounded-lg px-3 text-left text-foreground-muted transition-colors duration-base hover:bg-muted-hover hover:text-foreground\"\n >\n <MaterialSymbol icon=\"logout\" size={20} weight={400} className=\"shrink-0\" />\n <span className={classNames('sidebar-label text-sm', { 'is-collapsed': collapsed })}>\n {t('sidebar.logout')}\n </span>\n </ControlledButton>\n )}\n </div>\n )}\n </nav>\n </aside>\n );\n};\n\nexport default AppSidebar;\n"],"names":[],"mappings":";;;;;;;;AAiCA,MAAM,eAAe;AACrB,MAAM,oBAAoB;AAE1B,MAAM,2BAAqE,CAAC,EAAE,OAAO,WAAW,iBAAiB;AAC/G,QAAM,EAAE,aAAA,IAAiB,oBAAA;AAEzB,QAAM,YAAY;AAAA,IAChB,MAAM,CAAC,GAAG,uBAAuB,OAAO,YAAY,GAAG,GAAI,SAAS,EAAG;AAAA,IACvE,CAAC,cAAc,KAAK;AAAA,EAAA;AAGtB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,OAAO;AAAA,MACP;AAAA,MACA,aAAa;AAAA,MACb,iBAAiB;AAAA,MACjB;AAAA,IAAA;AAAA,EAAA;AAGN;AAEA,MAAM,aAAyC,CAAC;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,iBAAiB,QAAQ,aAAa,MAAM;AAElD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,KAAK;AAAA,MACL,cAAc;AAAA,MACd,cAAc;AAAA,MACd,WAAW;AAAA,QACT;AAAA,QACA;AAAA,UACE,WAAW,CAAC,YAAY;AAAA,UACxB,aAAa;AAAA,UACb,eAAe;AAAA,UACf,iCAAiC,YAAY,CAAC;AAAA,QAAA;AAAA,MAChD;AAAA,MAGF,UAAA,qBAAC,OAAA,EAAI,WAAU,iHACb,UAAA;AAAA,QAAA,oBAAC,4BACC,UAAA,oBAAC,0BAAA,EAAyB,OAAc,WAAsB,YAAwB,GACxF;AAAA,SAEE,kBAAkB,aAClB,qBAAC,OAAA,EAAI,WAAU,+CAEZ,UAAA;AAAA,UAAA,kBACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,OAAO,eAAe,CAAA;AAAA,cACtB;AAAA,cACA,aAAa;AAAA,cACb,iBAAiB;AAAA,cACjB;AAAA,YAAA;AAAA,UAAA;AAAA,UAIH,YACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,mBAAiB;AAAA,cACjB,SAAS;AAAA,cACT,WAAU;AAAA,cAEV,UAAA;AAAA,gBAAA,oBAAC,gBAAA,EAAe,MAAK,UAAS,MAAM,IAAI,QAAQ,KAAK,WAAU,WAAA,CAAW;AAAA,gBAC1E,oBAAC,QAAA,EAAK,WAAW,WAAW,yBAAyB,EAAE,gBAAgB,UAAA,CAAW,GAC/E,UAAA,EAAE,gBAAgB,EAAA,CACrB;AAAA,cAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QACF,EAAA,CAEJ;AAAA,MAAA,EAAA,CAEJ;AAAA,IAAA;AAAA,EAAA;AAGN;"}
@@ -12,7 +12,7 @@ const NoDataFoundVisual = ({ icon, toneClasses, sizeClasses }) => /* @__PURE__ *
12
12
  {
13
13
  "aria-hidden": true,
14
14
  className: classNames(
15
- "relative flex shrink-0 animate-state-reveal items-center justify-center",
15
+ "relative flex shrink-0 animate-state-reveal items-center justify-center motion-reduce:animate-state-fade",
16
16
  sizeClasses.visual
17
17
  ),
18
18
  children: [
@@ -20,7 +20,7 @@ const NoDataFoundVisual = ({ icon, toneClasses, sizeClasses }) => /* @__PURE__ *
20
20
  "span",
21
21
  {
22
22
  className: classNames(
23
- "absolute inset-0 m-auto animate-state-breathe rounded-full blur-2xl",
23
+ "absolute inset-0 m-auto animate-state-breathe rounded-full blur-2xl motion-reduce:animate-none motion-reduce:opacity-60",
24
24
  toneClasses.aura,
25
25
  sizeClasses.aura
26
26
  )
@@ -30,7 +30,7 @@ const NoDataFoundVisual = ({ icon, toneClasses, sizeClasses }) => /* @__PURE__ *
30
30
  "span",
31
31
  {
32
32
  className: classNames(
33
- "absolute inset-0 m-auto animate-state-halo rounded-full border",
33
+ "absolute inset-0 m-auto animate-state-halo rounded-full border motion-reduce:hidden",
34
34
  toneClasses.halo,
35
35
  sizeClasses.halo
36
36
  )
@@ -40,7 +40,7 @@ const NoDataFoundVisual = ({ icon, toneClasses, sizeClasses }) => /* @__PURE__ *
40
40
  "span",
41
41
  {
42
42
  className: classNames(
43
- "absolute inset-0 m-auto animate-state-halo rounded-full border [animation-delay:1.5s]",
43
+ "absolute inset-0 m-auto animate-state-halo rounded-full border [animation-delay:1.5s] motion-reduce:hidden",
44
44
  toneClasses.halo,
45
45
  sizeClasses.halo
46
46
  )
@@ -50,7 +50,7 @@ const NoDataFoundVisual = ({ icon, toneClasses, sizeClasses }) => /* @__PURE__ *
50
50
  "span",
51
51
  {
52
52
  className: classNames(
53
- "relative flex animate-state-float items-center justify-center border bg-gradient-to-br shadow-elevation3 glass-tint",
53
+ "relative flex animate-state-float items-center justify-center border bg-gradient-to-br shadow-elevation3 glass-tint motion-reduce:animate-none",
54
54
  toneClasses.tile,
55
55
  sizeClasses.tile
56
56
  ),
@@ -61,7 +61,7 @@ const NoDataFoundVisual = ({ icon, toneClasses, sizeClasses }) => /* @__PURE__ *
61
61
  "span",
62
62
  {
63
63
  className: classNames(
64
- "absolute right-[6%] top-[18%] size-1.5 animate-state-drift rounded-full",
64
+ "absolute right-[6%] top-[18%] size-1.5 animate-state-drift rounded-full motion-reduce:animate-none",
65
65
  toneClasses.accent
66
66
  )
67
67
  }
@@ -70,7 +70,7 @@ const NoDataFoundVisual = ({ icon, toneClasses, sizeClasses }) => /* @__PURE__ *
70
70
  "span",
71
71
  {
72
72
  className: classNames(
73
- "absolute bottom-[16%] left-[9%] size-1 animate-state-drift rounded-full [animation-delay:2.5s]",
73
+ "absolute bottom-[16%] left-[9%] size-1 animate-state-drift rounded-full [animation-delay:2.5s] motion-reduce:animate-none",
74
74
  toneClasses.accent
75
75
  )
76
76
  }
@@ -79,7 +79,7 @@ const NoDataFoundVisual = ({ icon, toneClasses, sizeClasses }) => /* @__PURE__ *
79
79
  "span",
80
80
  {
81
81
  className: classNames(
82
- "absolute left-[24%] top-[4%] size-1 animate-state-drift rounded-full [animation-delay:5s]",
82
+ "absolute left-[24%] top-[4%] size-1 animate-state-drift rounded-full [animation-delay:5s] motion-reduce:animate-none",
83
83
  toneClasses.accent
84
84
  )
85
85
  }
@@ -174,7 +174,7 @@ const NoDataFound = ({
174
174
  "p",
175
175
  {
176
176
  className: classNames(
177
- "relative animate-state-rise font-semibold tracking-[-0.01em] text-foreground [animation-delay:80ms]",
177
+ "relative animate-state-rise font-semibold tracking-[-0.01em] text-foreground [animation-delay:80ms] motion-reduce:animate-state-fade",
178
178
  sizeClasses.titleClass
179
179
  ),
180
180
  children: resolvedTitle
@@ -184,18 +184,18 @@ const NoDataFound = ({
184
184
  "p",
185
185
  {
186
186
  className: classNames(
187
- "relative animate-state-rise text-balance leading-relaxed text-foreground-subtle [animation-delay:140ms]",
187
+ "relative animate-state-rise text-balance leading-relaxed text-foreground-subtle [animation-delay:140ms] motion-reduce:animate-state-fade",
188
188
  sizeClasses.descriptionClass
189
189
  ),
190
190
  children: description
191
191
  }
192
192
  ) : null,
193
- children ? /* @__PURE__ */ jsx("div", { className: "relative w-full animate-state-rise [animation-delay:180ms]", children }) : null,
193
+ children ? /* @__PURE__ */ jsx("div", { className: "relative w-full animate-state-rise [animation-delay:180ms] motion-reduce:animate-state-fade", children }) : null,
194
194
  primaryAction || supportAction ? /* @__PURE__ */ jsxs(
195
195
  "div",
196
196
  {
197
197
  className: classNames(
198
- "relative flex animate-state-rise flex-wrap items-center justify-center gap-2 [animation-delay:220ms]",
198
+ "relative flex animate-state-rise flex-wrap items-center justify-center gap-2 [animation-delay:220ms] motion-reduce:animate-state-fade",
199
199
  sizeClasses.actions
200
200
  ),
201
201
  children: [
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../src/components/UI/NoDataFound/index.tsx"],"sourcesContent":["import { isValidElement, memo } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '../../Form/ControlledButton';\nimport { MaterialSymbol, SymbolCodepoints } from '../MaterialSymbol';\n\nimport {\n NO_DATA_FOUND_SIZES,\n NO_DATA_FOUND_TONES,\n NO_DATA_FOUND_VARIANTS,\n TNoDataFoundSize,\n TNoDataFoundTone,\n TNoDataFoundVariant,\n} from './noDataFoundPresets';\n\nexport type { TNoDataFoundSize, TNoDataFoundTone, TNoDataFoundVariant } from './noDataFoundPresets';\n\nexport interface INoDataFoundAction {\n label: string;\n onClick: () => void;\n icon?: SymbolCodepoints;\n disabled?: boolean;\n}\n\ntype TNoDataFoundActionProp = INoDataFoundAction | React.ReactNode;\n\nexport interface INoDataFoundProps {\n variant?: TNoDataFoundVariant;\n size?: TNoDataFoundSize;\n tone?: TNoDataFoundTone;\n title?: string;\n /** Alias of `title`, kept because most call sites already speak this name. */\n message?: string;\n description?: string;\n icon?: SymbolCodepoints;\n illustration?: React.ReactNode;\n action?: TNoDataFoundActionProp;\n secondaryAction?: TNoDataFoundActionProp;\n /** Sugar for `size=\"inline\"`: a single row for dropdowns, list slots and dense panels. */\n compact?: boolean;\n children?: React.ReactNode;\n className?: string;\n /** Turn off the live region when the host already announces the state itself. */\n announce?: boolean;\n}\n\ntype TToneClasses = (typeof NO_DATA_FOUND_TONES)[TNoDataFoundTone];\ntype TSizeClasses = (typeof NO_DATA_FOUND_SIZES)[Exclude<TNoDataFoundSize, 'inline'>];\n\nconst isActionConfig = (action: TNoDataFoundActionProp): action is INoDataFoundAction =>\n typeof action === 'object' && action !== null && !isValidElement(action) && 'label' in action;\n\ninterface INoDataFoundVisualProps {\n icon: SymbolCodepoints;\n toneClasses: TToneClasses;\n sizeClasses: TSizeClasses;\n}\n\nconst NoDataFoundVisual: React.FC<INoDataFoundVisualProps> = ({ icon, toneClasses, sizeClasses }) => (\n <span\n aria-hidden\n className={classNames(\n 'relative flex shrink-0 animate-state-reveal items-center justify-center',\n sizeClasses.visual,\n )}\n >\n <span\n className={classNames(\n 'absolute inset-0 m-auto animate-state-breathe rounded-full blur-2xl',\n toneClasses.aura,\n sizeClasses.aura,\n )}\n />\n <span\n className={classNames(\n 'absolute inset-0 m-auto animate-state-halo rounded-full border',\n toneClasses.halo,\n sizeClasses.halo,\n )}\n />\n <span\n className={classNames(\n 'absolute inset-0 m-auto animate-state-halo rounded-full border [animation-delay:1.5s]',\n toneClasses.halo,\n sizeClasses.halo,\n )}\n />\n\n <span\n className={classNames(\n 'relative flex animate-state-float items-center justify-center border bg-gradient-to-br shadow-elevation3 glass-tint',\n toneClasses.tile,\n sizeClasses.tile,\n )}\n >\n <MaterialSymbol icon={icon} weight={300} className={toneClasses.icon} />\n </span>\n\n <span\n className={classNames(\n 'absolute right-[6%] top-[18%] size-1.5 animate-state-drift rounded-full',\n toneClasses.accent,\n )}\n />\n <span\n className={classNames(\n 'absolute bottom-[16%] left-[9%] size-1 animate-state-drift rounded-full [animation-delay:2.5s]',\n toneClasses.accent,\n )}\n />\n <span\n className={classNames(\n 'absolute left-[24%] top-[4%] size-1 animate-state-drift rounded-full [animation-delay:5s]',\n toneClasses.accent,\n )}\n />\n </span>\n);\n\nconst NoDataFoundActionButton: React.FC<{ action: INoDataFoundAction; isPrimary: boolean }> = ({\n action,\n isPrimary,\n}) => (\n <ControlledButton\n size=\"sm\"\n variant={isPrimary ? 'filled' : 'text'}\n tone={isPrimary ? 'brand' : 'neutral'}\n disabled={action.disabled}\n onClick={action.onClick}\n className={classNames(\n 'flex items-center gap-x-1.5 px-3.5 text-xs transition-interactive duration-base',\n isPrimary\n ? 'shadow-md shadow-brand/20 hover:-translate-y-px hover:shadow-lg hover:shadow-brand/25'\n : 'text-foreground-muted hover:bg-muted-hover',\n )}\n >\n {action.icon ? <MaterialSymbol icon={action.icon} size={16} aria-hidden /> : null}\n <span>{action.label}</span>\n </ControlledButton>\n);\n\nconst renderAction = (action: TNoDataFoundActionProp, isPrimary: boolean): React.ReactNode => {\n if (!action) return null;\n if (isActionConfig(action)) return <NoDataFoundActionButton action={action} isPrimary={isPrimary} />;\n\n return action;\n};\n\nconst NoDataFound: React.FC<INoDataFoundProps> = ({\n variant = 'empty',\n size = 'md',\n tone,\n title,\n message,\n description,\n icon,\n illustration,\n action,\n secondaryAction,\n compact = false,\n children,\n className,\n announce = true,\n}) => {\n const { t } = useTranslation('ui');\n\n const preset = NO_DATA_FOUND_VARIANTS[variant] ?? NO_DATA_FOUND_VARIANTS.empty;\n const toneClasses = NO_DATA_FOUND_TONES[tone ?? preset.tone] ?? NO_DATA_FOUND_TONES.neutral;\n const resolvedIcon = icon ?? preset.icon;\n const resolvedTitle = title ?? message ?? t(preset.titleKey);\n const liveRegionProps = announce ? { role: 'status', 'aria-live': 'polite' as const } : {};\n\n const primaryAction = renderAction(action, true);\n const supportAction = renderAction(secondaryAction, false);\n\n if (compact || size === 'inline') {\n return (\n <div {...liveRegionProps} className={twMerge('flex w-full items-center gap-x-1.5 text-left', className)}>\n <MaterialSymbol icon={resolvedIcon} size={16} className={classNames('shrink-0', toneClasses.icon)} />\n <span className=\"min-w-0 text-xs text-foreground-subtle\">{resolvedTitle}</span>\n\n {description ? (\n <>\n <span aria-hidden className=\"size-1 shrink-0 rounded-full bg-border-emphasis\" />\n <span className=\"min-w-0 truncate text-xs text-foreground-faint\">{description}</span>\n </>\n ) : null}\n\n {primaryAction ? <span className=\"ml-auto shrink-0\">{primaryAction}</span> : null}\n </div>\n );\n }\n\n const sizeClasses = NO_DATA_FOUND_SIZES[size] ?? NO_DATA_FOUND_SIZES.md;\n\n return (\n <div\n {...liveRegionProps}\n className={twMerge(\n classNames(\n 'relative flex w-full flex-col items-center justify-center overflow-hidden text-center @container',\n sizeClasses.root,\n ),\n className,\n )}\n >\n {sizeClasses.hasTexture ? (\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-0 [mask-image:radial-gradient(60%_55%_at_50%_35%,black,transparent)]\"\n >\n <span className=\"absolute inset-0 bg-[radial-gradient(circle,rgba(148,163,184,0.3)_1px,transparent_1px)] [background-size:1.25rem_1.25rem]\" />\n </span>\n ) : null}\n\n {illustration ?? <NoDataFoundVisual icon={resolvedIcon} toneClasses={toneClasses} sizeClasses={sizeClasses} />}\n\n <p\n className={classNames(\n 'relative animate-state-rise font-semibold tracking-[-0.01em] text-foreground [animation-delay:80ms]',\n sizeClasses.titleClass,\n )}\n >\n {resolvedTitle}\n </p>\n\n {description ? (\n <p\n className={classNames(\n 'relative animate-state-rise text-balance leading-relaxed text-foreground-subtle [animation-delay:140ms]',\n sizeClasses.descriptionClass,\n )}\n >\n {description}\n </p>\n ) : null}\n\n {children ? <div className=\"relative w-full animate-state-rise [animation-delay:180ms]\">{children}</div> : null}\n\n {primaryAction || supportAction ? (\n <div\n className={classNames(\n 'relative flex animate-state-rise flex-wrap items-center justify-center gap-2 [animation-delay:220ms]',\n sizeClasses.actions,\n )}\n >\n {primaryAction}\n {supportAction}\n </div>\n ) : null}\n </div>\n );\n};\n\nexport default memo(NoDataFound);\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/components/UI/NoDataFound/index.tsx"],"sourcesContent":["import { isValidElement, memo } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '../../Form/ControlledButton';\nimport { MaterialSymbol, SymbolCodepoints } from '../MaterialSymbol';\n\nimport {\n NO_DATA_FOUND_SIZES,\n NO_DATA_FOUND_TONES,\n NO_DATA_FOUND_VARIANTS,\n TNoDataFoundSize,\n TNoDataFoundTone,\n TNoDataFoundVariant,\n} from './noDataFoundPresets';\n\nexport type { TNoDataFoundSize, TNoDataFoundTone, TNoDataFoundVariant } from './noDataFoundPresets';\n\nexport interface INoDataFoundAction {\n label: string;\n onClick: () => void;\n icon?: SymbolCodepoints;\n disabled?: boolean;\n}\n\ntype TNoDataFoundActionProp = INoDataFoundAction | React.ReactNode;\n\nexport interface INoDataFoundProps {\n variant?: TNoDataFoundVariant;\n size?: TNoDataFoundSize;\n tone?: TNoDataFoundTone;\n title?: string;\n /** Alias of `title`, kept because most call sites already speak this name. */\n message?: string;\n description?: string;\n icon?: SymbolCodepoints;\n illustration?: React.ReactNode;\n action?: TNoDataFoundActionProp;\n secondaryAction?: TNoDataFoundActionProp;\n /** Sugar for `size=\"inline\"`: a single row for dropdowns, list slots and dense panels. */\n compact?: boolean;\n children?: React.ReactNode;\n className?: string;\n /** Turn off the live region when the host already announces the state itself. */\n announce?: boolean;\n}\n\ntype TToneClasses = (typeof NO_DATA_FOUND_TONES)[TNoDataFoundTone];\ntype TSizeClasses = (typeof NO_DATA_FOUND_SIZES)[Exclude<TNoDataFoundSize, 'inline'>];\n\nconst isActionConfig = (action: TNoDataFoundActionProp): action is INoDataFoundAction =>\n typeof action === 'object' && action !== null && !isValidElement(action) && 'label' in action;\n\ninterface INoDataFoundVisualProps {\n icon: SymbolCodepoints;\n toneClasses: TToneClasses;\n sizeClasses: TSizeClasses;\n}\n\nconst NoDataFoundVisual: React.FC<INoDataFoundVisualProps> = ({ icon, toneClasses, sizeClasses }) => (\n <span\n aria-hidden\n className={classNames(\n 'relative flex shrink-0 animate-state-reveal items-center justify-center motion-reduce:animate-state-fade',\n sizeClasses.visual,\n )}\n >\n <span\n className={classNames(\n 'absolute inset-0 m-auto animate-state-breathe rounded-full blur-2xl motion-reduce:animate-none motion-reduce:opacity-60',\n toneClasses.aura,\n sizeClasses.aura,\n )}\n />\n <span\n className={classNames(\n 'absolute inset-0 m-auto animate-state-halo rounded-full border motion-reduce:hidden',\n toneClasses.halo,\n sizeClasses.halo,\n )}\n />\n <span\n className={classNames(\n 'absolute inset-0 m-auto animate-state-halo rounded-full border [animation-delay:1.5s] motion-reduce:hidden',\n toneClasses.halo,\n sizeClasses.halo,\n )}\n />\n\n <span\n className={classNames(\n 'relative flex animate-state-float items-center justify-center border bg-gradient-to-br shadow-elevation3 glass-tint motion-reduce:animate-none',\n toneClasses.tile,\n sizeClasses.tile,\n )}\n >\n <MaterialSymbol icon={icon} weight={300} className={toneClasses.icon} />\n </span>\n\n <span\n className={classNames(\n 'absolute right-[6%] top-[18%] size-1.5 animate-state-drift rounded-full motion-reduce:animate-none',\n toneClasses.accent,\n )}\n />\n <span\n className={classNames(\n 'absolute bottom-[16%] left-[9%] size-1 animate-state-drift rounded-full [animation-delay:2.5s] motion-reduce:animate-none',\n toneClasses.accent,\n )}\n />\n <span\n className={classNames(\n 'absolute left-[24%] top-[4%] size-1 animate-state-drift rounded-full [animation-delay:5s] motion-reduce:animate-none',\n toneClasses.accent,\n )}\n />\n </span>\n);\n\nconst NoDataFoundActionButton: React.FC<{ action: INoDataFoundAction; isPrimary: boolean }> = ({\n action,\n isPrimary,\n}) => (\n <ControlledButton\n size=\"sm\"\n variant={isPrimary ? 'filled' : 'text'}\n tone={isPrimary ? 'brand' : 'neutral'}\n disabled={action.disabled}\n onClick={action.onClick}\n className={classNames(\n 'flex items-center gap-x-1.5 px-3.5 text-xs transition-interactive duration-base',\n isPrimary\n ? 'shadow-md shadow-brand/20 hover:-translate-y-px hover:shadow-lg hover:shadow-brand/25'\n : 'text-foreground-muted hover:bg-muted-hover',\n )}\n >\n {action.icon ? <MaterialSymbol icon={action.icon} size={16} aria-hidden /> : null}\n <span>{action.label}</span>\n </ControlledButton>\n);\n\nconst renderAction = (action: TNoDataFoundActionProp, isPrimary: boolean): React.ReactNode => {\n if (!action) return null;\n if (isActionConfig(action)) return <NoDataFoundActionButton action={action} isPrimary={isPrimary} />;\n\n return action;\n};\n\nconst NoDataFound: React.FC<INoDataFoundProps> = ({\n variant = 'empty',\n size = 'md',\n tone,\n title,\n message,\n description,\n icon,\n illustration,\n action,\n secondaryAction,\n compact = false,\n children,\n className,\n announce = true,\n}) => {\n const { t } = useTranslation('ui');\n\n const preset = NO_DATA_FOUND_VARIANTS[variant] ?? NO_DATA_FOUND_VARIANTS.empty;\n const toneClasses = NO_DATA_FOUND_TONES[tone ?? preset.tone] ?? NO_DATA_FOUND_TONES.neutral;\n const resolvedIcon = icon ?? preset.icon;\n const resolvedTitle = title ?? message ?? t(preset.titleKey);\n const liveRegionProps = announce ? { role: 'status', 'aria-live': 'polite' as const } : {};\n\n const primaryAction = renderAction(action, true);\n const supportAction = renderAction(secondaryAction, false);\n\n if (compact || size === 'inline') {\n return (\n <div {...liveRegionProps} className={twMerge('flex w-full items-center gap-x-1.5 text-left', className)}>\n <MaterialSymbol icon={resolvedIcon} size={16} className={classNames('shrink-0', toneClasses.icon)} />\n <span className=\"min-w-0 text-xs text-foreground-subtle\">{resolvedTitle}</span>\n\n {description ? (\n <>\n <span aria-hidden className=\"size-1 shrink-0 rounded-full bg-border-emphasis\" />\n <span className=\"min-w-0 truncate text-xs text-foreground-faint\">{description}</span>\n </>\n ) : null}\n\n {primaryAction ? <span className=\"ml-auto shrink-0\">{primaryAction}</span> : null}\n </div>\n );\n }\n\n const sizeClasses = NO_DATA_FOUND_SIZES[size] ?? NO_DATA_FOUND_SIZES.md;\n\n return (\n <div\n {...liveRegionProps}\n className={twMerge(\n classNames(\n 'relative flex w-full flex-col items-center justify-center overflow-hidden text-center @container',\n sizeClasses.root,\n ),\n className,\n )}\n >\n {sizeClasses.hasTexture ? (\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-0 [mask-image:radial-gradient(60%_55%_at_50%_35%,black,transparent)]\"\n >\n <span className=\"absolute inset-0 bg-[radial-gradient(circle,rgba(148,163,184,0.3)_1px,transparent_1px)] [background-size:1.25rem_1.25rem]\" />\n </span>\n ) : null}\n\n {illustration ?? <NoDataFoundVisual icon={resolvedIcon} toneClasses={toneClasses} sizeClasses={sizeClasses} />}\n\n <p\n className={classNames(\n 'relative animate-state-rise font-semibold tracking-[-0.01em] text-foreground [animation-delay:80ms] motion-reduce:animate-state-fade',\n sizeClasses.titleClass,\n )}\n >\n {resolvedTitle}\n </p>\n\n {description ? (\n <p\n className={classNames(\n 'relative animate-state-rise text-balance leading-relaxed text-foreground-subtle [animation-delay:140ms] motion-reduce:animate-state-fade',\n sizeClasses.descriptionClass,\n )}\n >\n {description}\n </p>\n ) : null}\n\n {children ? (\n <div className=\"relative w-full animate-state-rise [animation-delay:180ms] motion-reduce:animate-state-fade\">\n {children}\n </div>\n ) : null}\n\n {primaryAction || supportAction ? (\n <div\n className={classNames(\n 'relative flex animate-state-rise flex-wrap items-center justify-center gap-2 [animation-delay:220ms] motion-reduce:animate-state-fade',\n sizeClasses.actions,\n )}\n >\n {primaryAction}\n {supportAction}\n </div>\n ) : null}\n </div>\n );\n};\n\nexport default memo(NoDataFound);\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umodeapporg/ui",
3
- "version": "0.17.1",
3
+ "version": "0.18.0",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -173,9 +173,18 @@ export default {
173
173
  },
174
174
  /* Defaults for the bare `ring` / `ring-offset` utilities. Without these Tailwind
175
175
  falls back to its own blue, which is how a stray blue focus ring survives an
176
- otherwise complete migration. */
177
- ringColor: { DEFAULT: token('ring') },
178
- ringOffsetColor: { DEFAULT: token('ring-offset') },
176
+ otherwise complete migration.
177
+ Both DEFAULTs are also read RAW into the `ring-width` defaults that every `ring-*`
178
+ element carries, where `<alpha-value>` is never substituted: token() there left a
179
+ literal `<alpha-value>` in `--tw-ring-offset-color`, which invalidates the whole
180
+ ring `box-shadow` (no focus ring at all), and an unparseable `--tw-ring-color` that
181
+ Tailwind replaced with its blue. So they are a plain colour and a colour function,
182
+ and the bare ring is the full-strength token, like the global focus outline. */
183
+ ringColor: {
184
+ DEFAULT: ({ opacityValue }) => `rgb(var(--color-ring) / ${opacityValue ?? 1})`,
185
+ },
186
+ ringOpacity: { DEFAULT: '1' },
187
+ ringOffsetColor: { DEFAULT: 'rgb(var(--color-ring-offset))' },
179
188
  borderColor: { DEFAULT: token('border') },
180
189
  divideColor: { DEFAULT: token('border') },
181
190
  outlineColor: { DEFAULT: token('ring') },
@@ -297,6 +306,11 @@ export default {
297
306
  '60%': { opacity: '1', transform: 'scale(1.15)' },
298
307
  '100%': { opacity: '1', transform: 'scale(1)' },
299
308
  },
309
+ /* The reduced-motion entry of the empty states: the same reveal, opacity only. */
310
+ 'state-fade': {
311
+ '0%': { opacity: '0' },
312
+ '100%': { opacity: '1' },
313
+ },
300
314
  'state-rise': {
301
315
  '0%': { opacity: '0', transform: 'translate3d(0, 0.5rem, 0)' },
302
316
  '100%': { opacity: '1', transform: 'translate3d(0, 0, 0)' },
@@ -332,6 +346,7 @@ export default {
332
346
  'ai-sweep': 'ai-sweep 1.8s ease-in-out infinite',
333
347
  'ai-step': 'ai-step 1.5s ease-in-out infinite',
334
348
  'pop-in': 'pop-in 0.3s ease-out forwards',
349
+ 'state-fade': 'state-fade 0.4s cubic-bezier(0.16, 1, 0.3, 1) both',
335
350
  'state-rise': 'state-rise 0.5s cubic-bezier(0.16, 1, 0.3, 1) both',
336
351
  'state-reveal': 'state-reveal 0.6s cubic-bezier(0.16, 1, 0.3, 1) both',
337
352
  'state-float': 'state-float 5s ease-in-out infinite',