@umodeapporg/ui 0.17.1 → 0.17.2
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/assets/umode-ui.css +11 -1
- package/dist/components/Shell/AppHeader.d.ts +7 -1
- package/dist/components/Shell/AppHeader.js +5 -0
- package/dist/components/Shell/AppHeader.js.map +1 -1
- package/dist/components/Shell/AppShell.js +45 -1
- package/dist/components/Shell/AppShell.js.map +1 -1
- package/dist/components/Shell/AppSidebar.d.ts +3 -0
- package/dist/components/Shell/AppSidebar.js +4 -0
- package/dist/components/Shell/AppSidebar.js.map +1 -1
- package/dist/components/UI/NoDataFound/index.js +12 -12
- package/dist/components/UI/NoDataFound/index.js.map +1 -1
- package/package.json +1 -1
- package/tailwind.preset.js +18 -3
package/dist/assets/umode-ui.css
CHANGED
|
@@ -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
|
-
|
|
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":";;;;;;;;
|
|
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;"}
|
|
@@ -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,
|
|
@@ -18,6 +24,10 @@ const AppShell = ({
|
|
|
18
24
|
}) => {
|
|
19
25
|
const { location } = useUiNavigation();
|
|
20
26
|
const scrollRef = useRef(null);
|
|
27
|
+
const sidebarRef = useRef(null);
|
|
28
|
+
const toggleRef = useRef(null);
|
|
29
|
+
const wasMobileOpenRef = useRef(false);
|
|
30
|
+
const sidebarId = useId();
|
|
21
31
|
const {
|
|
22
32
|
isMobile,
|
|
23
33
|
isVisible,
|
|
@@ -30,9 +40,39 @@ const AppShell = ({
|
|
|
30
40
|
onHoverLeave,
|
|
31
41
|
closeMobileDrawer
|
|
32
42
|
} = useSidebarState();
|
|
43
|
+
const isSidebarHidden = isMobile && !isVisible;
|
|
44
|
+
const wasSidebarHiddenRef = useRef(isSidebarHidden);
|
|
33
45
|
useEffect(() => {
|
|
34
46
|
scrollRef.current?.scrollTo({ top: 0, behavior: "smooth" });
|
|
35
47
|
}, [location.pathname]);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const sidebar = sidebarRef.current;
|
|
50
|
+
const wasOpen = wasMobileOpenRef.current;
|
|
51
|
+
const wasHidden = wasSidebarHiddenRef.current;
|
|
52
|
+
wasMobileOpenRef.current = isOverlayOnMobile;
|
|
53
|
+
wasSidebarHiddenRef.current = isSidebarHidden;
|
|
54
|
+
if (sidebar && isSidebarHidden && !wasHidden) {
|
|
55
|
+
const active = document.activeElement;
|
|
56
|
+
const isFocusLost = sidebar.contains(active) || wasOpen && (!active || active === document.body);
|
|
57
|
+
if (isFocusLost) toggleRef.current?.focus();
|
|
58
|
+
}
|
|
59
|
+
setInert(sidebar, isSidebarHidden);
|
|
60
|
+
setInert(scrollRef.current, isOverlayOnMobile);
|
|
61
|
+
if (sidebar && isOverlayOnMobile && !wasOpen) getTabbableElements(sidebar)[0]?.focus();
|
|
62
|
+
}, [isSidebarHidden, isOverlayOnMobile]);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!isOverlayOnMobile) return void 0;
|
|
65
|
+
const handleKeyDown = (event) => {
|
|
66
|
+
if (event.key !== "Escape" || event.defaultPrevented) return;
|
|
67
|
+
const active = document.activeElement;
|
|
68
|
+
const ownsFocus = !active || active === document.body || active === toggleRef.current || sidebarRef.current?.contains(active);
|
|
69
|
+
if (!ownsFocus) return;
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
closeMobileDrawer();
|
|
72
|
+
};
|
|
73
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
74
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
75
|
+
}, [isOverlayOnMobile, closeMobileDrawer]);
|
|
36
76
|
return /* @__PURE__ */ jsxs("div", { className: "h-screen w-full bg-surface", children: [
|
|
37
77
|
/* @__PURE__ */ jsx(
|
|
38
78
|
AppHeader,
|
|
@@ -40,6 +80,8 @@ const AppShell = ({
|
|
|
40
80
|
toggleSidebar: togglePrimary,
|
|
41
81
|
isSidebarOpen: isDocked,
|
|
42
82
|
toggleLabelKey,
|
|
83
|
+
toggleRef,
|
|
84
|
+
mobileSidebar: isMobile ? { id: sidebarId, open: isOverlayOnMobile } : void 0,
|
|
43
85
|
homePath,
|
|
44
86
|
logo,
|
|
45
87
|
toolsSlot,
|
|
@@ -68,6 +110,8 @@ const AppShell = ({
|
|
|
68
110
|
/* @__PURE__ */ jsx(
|
|
69
111
|
AppSidebar,
|
|
70
112
|
{
|
|
113
|
+
id: sidebarId,
|
|
114
|
+
sidebarRef,
|
|
71
115
|
items,
|
|
72
116
|
isMobile,
|
|
73
117
|
isVisible,
|
|
@@ -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":"
|
|
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 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 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 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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"}
|
|
@@ -24,6 +24,8 @@ const SidebarMenuWithFavorites = ({ items, collapsed, onNavigate }) => {
|
|
|
24
24
|
);
|
|
25
25
|
};
|
|
26
26
|
const AppSidebar = ({
|
|
27
|
+
id,
|
|
28
|
+
sidebarRef,
|
|
27
29
|
items,
|
|
28
30
|
isMobile,
|
|
29
31
|
isVisible,
|
|
@@ -38,6 +40,8 @@ const AppSidebar = ({
|
|
|
38
40
|
return /* @__PURE__ */ jsx(
|
|
39
41
|
"aside",
|
|
40
42
|
{
|
|
43
|
+
id,
|
|
44
|
+
ref: sidebarRef,
|
|
41
45
|
onMouseEnter: onHoverEnter,
|
|
42
46
|
onMouseLeave: onHoverLeave,
|
|
43
47
|
className: classNames(
|
|
@@ -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":";;;;;;;;
|
|
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 /** 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 id,\n sidebarRef,\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 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 {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":";;;;;;;;AA+BA,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;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,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,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;"}
|
|
@@ -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
package/tailwind.preset.js
CHANGED
|
@@ -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
|
-
|
|
178
|
-
|
|
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',
|