@umodeapporg/ui 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/umode-ui.css +148 -0
- package/dist/components/Shell/AppHeader.d.ts +19 -0
- package/dist/components/Shell/AppHeader.js +64 -0
- package/dist/components/Shell/AppHeader.js.map +1 -0
- package/dist/components/Shell/AppShell.d.ts +20 -0
- package/dist/components/Shell/AppShell.js +100 -0
- package/dist/components/Shell/AppShell.js.map +1 -0
- package/dist/components/Shell/AppSidebar.d.ts +15 -0
- package/dist/components/Shell/AppSidebar.js +63 -0
- package/dist/components/Shell/AppSidebar.js.map +1 -0
- package/dist/components/Shell/LanguageToggle.d.ts +8 -0
- package/dist/components/Shell/LanguageToggle.js +59 -0
- package/dist/components/Shell/LanguageToggle.js.map +1 -0
- package/dist/components/Shell/SidebarMenu.d.ts +24 -0
- package/dist/components/Shell/SidebarMenu.js +204 -0
- package/dist/components/Shell/SidebarMenu.js.map +1 -0
- package/dist/components/Shell/ThemeToggle.d.ts +11 -0
- package/dist/components/Shell/ThemeToggle.js +55 -0
- package/dist/components/Shell/ThemeToggle.js.map +1 -0
- package/dist/components/Shell/headerControlStyles.d.ts +10 -0
- package/dist/components/Shell/headerControlStyles.js +11 -0
- package/dist/components/Shell/headerControlStyles.js.map +1 -0
- package/dist/components/Shell/useSidebarState.d.ts +14 -0
- package/dist/components/Shell/useSidebarState.js +106 -0
- package/dist/components/Shell/useSidebarState.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +44 -36
- package/dist/index.js.map +1 -1
- package/dist/locales/en/ui.json.d.ts +21 -0
- package/dist/locales/en/ui.json.js +9 -0
- package/dist/locales/en/ui.json.js.map +1 -1
- package/dist/locales/index.d.ts +42 -0
- package/dist/locales/pt/ui.json.d.ts +21 -0
- package/dist/locales/pt/ui.json.js +9 -0
- package/dist/locales/pt/ui.json.js.map +1 -1
- package/package.json +1 -1
- package/src/assets/styles/base.css +14 -2
package/dist/assets/umode-ui.css
CHANGED
|
@@ -72,6 +72,154 @@
|
|
|
72
72
|
.controlled-select-container input:hover {
|
|
73
73
|
outline: none !important;
|
|
74
74
|
}
|
|
75
|
+
/* Sidebar expand/collapse motion.
|
|
76
|
+
Kept as plain CSS so the transitions are explicit and not affected by
|
|
77
|
+
Tailwind's transition-property utilities (which bundle their own duration). */
|
|
78
|
+
|
|
79
|
+
.sidebar-aside {
|
|
80
|
+
width: 16rem;
|
|
81
|
+
transition: width 180ms ease-out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.sidebar-aside.is-rail {
|
|
85
|
+
width: 4.5rem;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Floating overlay: while expanded by hover (not pinned) the panel sits above the
|
|
89
|
+
content instead of pushing it. This is the ONLY sidebar state where glass is honest,
|
|
90
|
+
because it is the only one with page content behind it; docked, the sidebar pushes the
|
|
91
|
+
layout and there is nothing to see through. */
|
|
92
|
+
.sidebar-aside.is-floating,
|
|
93
|
+
.sidebar-aside.is-mobile {
|
|
94
|
+
background-color: rgb(var(--glass-bg) / var(--glass-alpha));
|
|
95
|
+
backdrop-filter: blur(20px) saturate(var(--glass-saturate-strong));
|
|
96
|
+
-webkit-backdrop-filter: blur(20px) saturate(var(--glass-saturate-strong));
|
|
97
|
+
box-shadow:
|
|
98
|
+
var(--shadow-lg),
|
|
99
|
+
inset 0 1px 0 0 rgb(var(--glass-highlight) / var(--glass-highlight-alpha));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@media (max-width: 639px) {
|
|
103
|
+
.sidebar-aside.is-mobile {
|
|
104
|
+
backdrop-filter: blur(12px) saturate(var(--glass-saturate-strong));
|
|
105
|
+
-webkit-backdrop-filter: blur(12px) saturate(var(--glass-saturate-strong));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
|
|
110
|
+
.sidebar-aside.is-floating,
|
|
111
|
+
.sidebar-aside.is-mobile {
|
|
112
|
+
background-color: rgb(var(--color-surface-overlay));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@media (prefers-reduced-transparency: reduce) {
|
|
117
|
+
.sidebar-aside.is-floating,
|
|
118
|
+
.sidebar-aside.is-mobile {
|
|
119
|
+
background-color: rgb(var(--color-surface-overlay));
|
|
120
|
+
backdrop-filter: none;
|
|
121
|
+
-webkit-backdrop-filter: none;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.sidebar-aside.is-mobile {
|
|
126
|
+
width: 16rem;
|
|
127
|
+
transition: transform 180ms ease-out;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.sidebar-aside.is-mobile.is-hidden {
|
|
131
|
+
transform: translateX(-100%);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.sidebar-content {
|
|
135
|
+
padding-left: 16rem;
|
|
136
|
+
transition: padding-left 180ms ease-out;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.sidebar-content.is-rail {
|
|
140
|
+
padding-left: 4.5rem;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.sidebar-content.is-mobile {
|
|
144
|
+
padding-left: 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.sidebar-rail-slot {
|
|
148
|
+
width: 4.5rem;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.sidebar-label {
|
|
152
|
+
white-space: nowrap;
|
|
153
|
+
transition:
|
|
154
|
+
opacity 150ms ease-out 60ms,
|
|
155
|
+
transform 150ms ease-out 60ms;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.sidebar-label.is-collapsed {
|
|
159
|
+
opacity: 0;
|
|
160
|
+
transform: translateX(-4px);
|
|
161
|
+
pointer-events: none;
|
|
162
|
+
transition:
|
|
163
|
+
opacity 120ms ease-out,
|
|
164
|
+
transform 120ms ease-out;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.sidebar-chevron {
|
|
168
|
+
transition:
|
|
169
|
+
opacity 150ms ease-out 60ms,
|
|
170
|
+
transform 180ms ease-out;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.sidebar-chevron.is-collapsed {
|
|
174
|
+
opacity: 0;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.sidebar-chevron.is-open {
|
|
178
|
+
transform: rotate(90deg);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.sidebar-submenu {
|
|
182
|
+
overflow: hidden;
|
|
183
|
+
transition: max-height 200ms ease-out;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* Delicate Notion-style scrollbar: thin, semi-transparent, no visible track,
|
|
187
|
+
revealed on hover of the panel. The gutter is reserved so content never
|
|
188
|
+
shifts when the thumb appears. */
|
|
189
|
+
.sidebar-scroll {
|
|
190
|
+
scrollbar-gutter: stable;
|
|
191
|
+
scrollbar-width: thin;
|
|
192
|
+
scrollbar-color: transparent transparent;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.sidebar-scroll:hover,
|
|
196
|
+
.sidebar-scroll:focus-within {
|
|
197
|
+
scrollbar-color: rgb(var(--color-border-strong) / 0.55) transparent;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.sidebar-scroll::-webkit-scrollbar {
|
|
201
|
+
width: 6px;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.sidebar-scroll::-webkit-scrollbar-track {
|
|
205
|
+
background: transparent;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.sidebar-scroll::-webkit-scrollbar-thumb {
|
|
209
|
+
background-color: transparent;
|
|
210
|
+
border-radius: 9999px;
|
|
211
|
+
-webkit-transition: background-color 200ms ease-out;
|
|
212
|
+
transition: background-color 200ms ease-out;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.sidebar-scroll:hover::-webkit-scrollbar-thumb,
|
|
216
|
+
.sidebar-scroll:focus-within::-webkit-scrollbar-thumb {
|
|
217
|
+
background-color: rgb(var(--color-border-strong) / 0.55);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.sidebar-scroll::-webkit-scrollbar-thumb:hover {
|
|
221
|
+
background-color: rgb(var(--color-border-strong) / 0.85);
|
|
222
|
+
}
|
|
75
223
|
/* Custom Toast Styles - Material Design style */
|
|
76
224
|
|
|
77
225
|
.Toastify__toast {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
interface IAppHeaderProps {
|
|
3
|
+
toggleSidebar: () => void;
|
|
4
|
+
isSidebarOpen: boolean;
|
|
5
|
+
toggleLabelKey: string;
|
|
6
|
+
homePath: string;
|
|
7
|
+
/** Replaces the uMode logo; the header stays the same. */
|
|
8
|
+
logo?: ReactNode;
|
|
9
|
+
/** App tools rendered inside the pill shell (workspace, notifications...). */
|
|
10
|
+
toolsSlot?: ReactNode;
|
|
11
|
+
/** Theme/language knobs (usually the lib's ThemeToggle + LanguageToggle wired by the host). */
|
|
12
|
+
controlsSlot?: ReactNode;
|
|
13
|
+
/** The account surface (usually the lib's UserMenu wired by the host). */
|
|
14
|
+
userMenuSlot?: ReactNode;
|
|
15
|
+
onHoverEnter: () => void;
|
|
16
|
+
onHoverLeave: () => void;
|
|
17
|
+
}
|
|
18
|
+
declare const AppHeader: React.FC<IAppHeaderProps>;
|
|
19
|
+
export default AppHeader;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
import { useUiNavigation } from "../Navigation/UiNavigationContext.js";
|
|
5
|
+
import ControlledTooltip from "../UI/ControlledTooltip.js";
|
|
6
|
+
import { MaterialSymbol } from "../UI/MaterialSymbol/index.js";
|
|
7
|
+
import UmodeLogo from "../UI/UmodeLogo.js";
|
|
8
|
+
import { HEADER_CONTROL_SHELL_CLASS } from "./headerControlStyles.js";
|
|
9
|
+
const AppHeader = ({
|
|
10
|
+
toggleSidebar,
|
|
11
|
+
isSidebarOpen,
|
|
12
|
+
toggleLabelKey,
|
|
13
|
+
homePath,
|
|
14
|
+
logo,
|
|
15
|
+
toolsSlot,
|
|
16
|
+
controlsSlot,
|
|
17
|
+
userMenuSlot,
|
|
18
|
+
onHoverEnter,
|
|
19
|
+
onHoverLeave
|
|
20
|
+
}) => {
|
|
21
|
+
const { t } = useTranslation("ui");
|
|
22
|
+
const { Link: NavigationLink } = useUiNavigation();
|
|
23
|
+
return /* @__PURE__ */ jsxs("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", children: [
|
|
24
|
+
/* @__PURE__ */ jsxs("div", { className: "flex cursor-pointer items-center gap-x-2", children: [
|
|
25
|
+
/* @__PURE__ */ jsx("div", { className: "sidebar-rail-slot flex shrink-0 items-center justify-center", children: /* @__PURE__ */ jsx(
|
|
26
|
+
MaterialSymbol,
|
|
27
|
+
{
|
|
28
|
+
icon: isSidebarOpen ? "menu_open" : "menu",
|
|
29
|
+
size: 22,
|
|
30
|
+
weight: 400,
|
|
31
|
+
onClick: toggleSidebar,
|
|
32
|
+
onMouseEnter: onHoverEnter,
|
|
33
|
+
onMouseLeave: onHoverLeave,
|
|
34
|
+
"aria-label": t(toggleLabelKey),
|
|
35
|
+
className: classNames(
|
|
36
|
+
"rounded-lg p-1 transition-colors duration-base",
|
|
37
|
+
isSidebarOpen ? "text-brand-emphasis hover:bg-brand-soft" : "text-foreground-muted hover:bg-muted-hover hover:text-foreground"
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
) }),
|
|
41
|
+
/* @__PURE__ */ jsx(ControlledTooltip, { content: t("sidebar.home"), children: /* @__PURE__ */ jsx(
|
|
42
|
+
NavigationLink,
|
|
43
|
+
{
|
|
44
|
+
to: homePath,
|
|
45
|
+
"aria-label": t("sidebar.home"),
|
|
46
|
+
className: "interactive hidden shrink-0 rounded-lg px-1 text-foreground hover:opacity-70 tablet:block",
|
|
47
|
+
children: logo ?? /* @__PURE__ */ jsx(UmodeLogo, { className: "h-5 w-auto" })
|
|
48
|
+
}
|
|
49
|
+
) })
|
|
50
|
+
] }),
|
|
51
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
52
|
+
toolsSlot && /* @__PURE__ */ jsx("div", { className: HEADER_CONTROL_SHELL_CLASS, children: toolsSlot }),
|
|
53
|
+
controlsSlot,
|
|
54
|
+
userMenuSlot && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
55
|
+
/* @__PURE__ */ jsx("div", { className: "mx-1 h-6 w-px shrink-0 bg-border", "aria-hidden": true }),
|
|
56
|
+
userMenuSlot
|
|
57
|
+
] })
|
|
58
|
+
] })
|
|
59
|
+
] });
|
|
60
|
+
};
|
|
61
|
+
export {
|
|
62
|
+
AppHeader as default
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=AppHeader.js.map
|
|
@@ -0,0 +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;"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { ISidebarMenuItem } from './SidebarMenu';
|
|
3
|
+
interface IAppShellProps {
|
|
4
|
+
items: Array<ISidebarMenuItem>;
|
|
5
|
+
homePath: string;
|
|
6
|
+
logo?: ReactNode;
|
|
7
|
+
toolsSlot?: ReactNode;
|
|
8
|
+
controlsSlot?: ReactNode;
|
|
9
|
+
userMenuSlot?: ReactNode;
|
|
10
|
+
/** Sidebar footer sign-out row; hidden when absent. */
|
|
11
|
+
onLogout?: () => void;
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The uMode application chrome: fixed glass header, rail/dock/hover sidebar (pin persisted
|
|
16
|
+
* through uiPreferences, Ctrl+\ shortcut) and the scroll container every page renders into.
|
|
17
|
+
* Everything app-shaped (menu items, tools, account surface, logout) arrives as props/slots.
|
|
18
|
+
*/
|
|
19
|
+
declare const AppShell: React.FC<IAppShellProps>;
|
|
20
|
+
export default AppShell;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useEffect } from "react";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import { useUiNavigation } from "../Navigation/UiNavigationContext.js";
|
|
5
|
+
import AppHeader from "./AppHeader.js";
|
|
6
|
+
import AppSidebar from "./AppSidebar.js";
|
|
7
|
+
import { useSidebarState } from "./useSidebarState.js";
|
|
8
|
+
/* empty css */
|
|
9
|
+
const AppShell = ({
|
|
10
|
+
items,
|
|
11
|
+
homePath,
|
|
12
|
+
logo,
|
|
13
|
+
toolsSlot,
|
|
14
|
+
controlsSlot,
|
|
15
|
+
userMenuSlot,
|
|
16
|
+
onLogout,
|
|
17
|
+
children
|
|
18
|
+
}) => {
|
|
19
|
+
const { location } = useUiNavigation();
|
|
20
|
+
const scrollRef = useRef(null);
|
|
21
|
+
const {
|
|
22
|
+
isMobile,
|
|
23
|
+
isVisible,
|
|
24
|
+
isDocked,
|
|
25
|
+
isOverlayOnMobile,
|
|
26
|
+
showRail,
|
|
27
|
+
toggleLabelKey,
|
|
28
|
+
togglePrimary,
|
|
29
|
+
onHoverEnter,
|
|
30
|
+
onHoverLeave,
|
|
31
|
+
closeMobileDrawer
|
|
32
|
+
} = useSidebarState();
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
scrollRef.current?.scrollTo({ top: 0, behavior: "smooth" });
|
|
35
|
+
}, [location.pathname]);
|
|
36
|
+
return /* @__PURE__ */ jsxs("div", { className: "h-screen w-full bg-surface", children: [
|
|
37
|
+
/* @__PURE__ */ jsx(
|
|
38
|
+
AppHeader,
|
|
39
|
+
{
|
|
40
|
+
toggleSidebar: togglePrimary,
|
|
41
|
+
isSidebarOpen: isDocked,
|
|
42
|
+
toggleLabelKey,
|
|
43
|
+
homePath,
|
|
44
|
+
logo,
|
|
45
|
+
toolsSlot,
|
|
46
|
+
controlsSlot,
|
|
47
|
+
userMenuSlot,
|
|
48
|
+
onHoverEnter,
|
|
49
|
+
onHoverLeave
|
|
50
|
+
}
|
|
51
|
+
),
|
|
52
|
+
/* @__PURE__ */ jsxs(
|
|
53
|
+
"main",
|
|
54
|
+
{
|
|
55
|
+
className: classNames("sidebar-content flex h-screen w-full", {
|
|
56
|
+
"is-rail": !isMobile && !isDocked,
|
|
57
|
+
"is-mobile": isMobile
|
|
58
|
+
}),
|
|
59
|
+
children: [
|
|
60
|
+
isOverlayOnMobile && /* @__PURE__ */ jsx(
|
|
61
|
+
"div",
|
|
62
|
+
{
|
|
63
|
+
"aria-hidden": true,
|
|
64
|
+
className: "fixed inset-0 z-40 bg-scrim motion-reduce:transition-none",
|
|
65
|
+
onClick: closeMobileDrawer
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
/* @__PURE__ */ jsx(
|
|
69
|
+
AppSidebar,
|
|
70
|
+
{
|
|
71
|
+
items,
|
|
72
|
+
isMobile,
|
|
73
|
+
isVisible,
|
|
74
|
+
collapsed: showRail,
|
|
75
|
+
isFloating: !isMobile && !showRail && !isDocked,
|
|
76
|
+
onHoverEnter,
|
|
77
|
+
onHoverLeave,
|
|
78
|
+
onNavigate: isMobile ? closeMobileDrawer : void 0,
|
|
79
|
+
onLogout
|
|
80
|
+
}
|
|
81
|
+
),
|
|
82
|
+
/* @__PURE__ */ jsx(
|
|
83
|
+
"div",
|
|
84
|
+
{
|
|
85
|
+
id: "scrollableDiv",
|
|
86
|
+
ref: scrollRef,
|
|
87
|
+
className: "z-30 h-screen w-full px-4 pt-24 tablet:px-6",
|
|
88
|
+
style: { overflow: "auto" },
|
|
89
|
+
children
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
] });
|
|
96
|
+
};
|
|
97
|
+
export {
|
|
98
|
+
AppShell as default
|
|
99
|
+
};
|
|
100
|
+
//# sourceMappingURL=AppShell.js.map
|
|
@@ -0,0 +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;"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ISidebarMenuItem } from './SidebarMenu';
|
|
2
|
+
interface IAppSidebarProps {
|
|
3
|
+
items: Array<ISidebarMenuItem>;
|
|
4
|
+
isMobile: boolean;
|
|
5
|
+
isVisible: boolean;
|
|
6
|
+
collapsed: boolean;
|
|
7
|
+
isFloating: boolean;
|
|
8
|
+
onHoverEnter: () => void;
|
|
9
|
+
onHoverLeave: () => void;
|
|
10
|
+
onNavigate?: () => void;
|
|
11
|
+
/** Hides the footer sign-out row when absent. */
|
|
12
|
+
onLogout?: () => void;
|
|
13
|
+
}
|
|
14
|
+
declare const AppSidebar: React.FC<IAppSidebarProps>;
|
|
15
|
+
export default AppSidebar;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
import ControlledButton from "../Form/ControlledButton.js";
|
|
5
|
+
import { MaterialSymbol } from "../UI/MaterialSymbol/index.js";
|
|
6
|
+
import SidebarMenu from "./SidebarMenu.js";
|
|
7
|
+
const AppSidebar = ({
|
|
8
|
+
items,
|
|
9
|
+
isMobile,
|
|
10
|
+
isVisible,
|
|
11
|
+
collapsed,
|
|
12
|
+
isFloating,
|
|
13
|
+
onHoverEnter,
|
|
14
|
+
onHoverLeave,
|
|
15
|
+
onNavigate,
|
|
16
|
+
onLogout
|
|
17
|
+
}) => {
|
|
18
|
+
const { t } = useTranslation("ui");
|
|
19
|
+
return /* @__PURE__ */ jsx(
|
|
20
|
+
"aside",
|
|
21
|
+
{
|
|
22
|
+
onMouseEnter: onHoverEnter,
|
|
23
|
+
onMouseLeave: onHoverLeave,
|
|
24
|
+
className: classNames(
|
|
25
|
+
"sidebar-aside fixed left-0 top-0 z-40 h-full overflow-hidden border-r border-border bg-muted",
|
|
26
|
+
{
|
|
27
|
+
"is-rail": !isMobile && collapsed,
|
|
28
|
+
"is-mobile": isMobile,
|
|
29
|
+
"is-floating": isFloating,
|
|
30
|
+
"is-hidden pointer-events-none": isMobile && !isVisible
|
|
31
|
+
}
|
|
32
|
+
),
|
|
33
|
+
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: [
|
|
34
|
+
/* @__PURE__ */ jsx(
|
|
35
|
+
SidebarMenu,
|
|
36
|
+
{
|
|
37
|
+
items,
|
|
38
|
+
collapsed,
|
|
39
|
+
activeClass: "text-brand-emphasis bg-brand-soft",
|
|
40
|
+
activeIconClass: "text-brand-emphasis",
|
|
41
|
+
onNavigate
|
|
42
|
+
}
|
|
43
|
+
),
|
|
44
|
+
onLogout && /* @__PURE__ */ jsx("div", { className: "text-foreground-muted", children: /* @__PURE__ */ jsxs(
|
|
45
|
+
ControlledButton,
|
|
46
|
+
{
|
|
47
|
+
defaultHtmlButton: true,
|
|
48
|
+
onClick: onLogout,
|
|
49
|
+
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",
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ jsx(MaterialSymbol, { icon: "logout", size: 20, weight: 400, className: "shrink-0" }),
|
|
52
|
+
/* @__PURE__ */ jsx("span", { className: classNames("sidebar-label text-sm", { "is-collapsed": collapsed }), children: t("sidebar.logout") })
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
) })
|
|
56
|
+
] })
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
export {
|
|
61
|
+
AppSidebar as default
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=AppSidebar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AppSidebar.js","sources":["../../../src/components/Shell/AppSidebar.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\n\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\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 <SidebarMenu\n items={items}\n collapsed={collapsed}\n activeClass=\"text-brand-emphasis bg-brand-soft\"\n activeIconClass=\"text-brand-emphasis\"\n onNavigate={onNavigate}\n />\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":";;;;;;AAqBA,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;AAAA,UAAC;AAAA,UAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA,aAAY;AAAA,YACZ,iBAAgB;AAAA,YAChB;AAAA,UAAA;AAAA,QAAA;AAAA,QAGD,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;"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type TUiLanguage = 'pt' | 'en';
|
|
2
|
+
interface ILanguageToggleProps {
|
|
3
|
+
language: TUiLanguage;
|
|
4
|
+
onChange: (language: TUiLanguage) => void;
|
|
5
|
+
}
|
|
6
|
+
/** PT/EN knob; the host owns the i18n instance and persistence. */
|
|
7
|
+
declare const LanguageToggle: React.FC<ILanguageToggleProps>;
|
|
8
|
+
export default LanguageToggle;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
import ControlledButton from "../Form/ControlledButton.js";
|
|
5
|
+
import ControlledTooltip from "../UI/ControlledTooltip.js";
|
|
6
|
+
import { MaterialSymbol } from "../UI/MaterialSymbol/index.js";
|
|
7
|
+
import { HEADER_CONTROL_SIZE_REM, HEADER_CONTROL_SHELL_CLASS } from "./headerControlStyles.js";
|
|
8
|
+
const OPTION_WIDTH_REM = 2.25;
|
|
9
|
+
const LanguageToggle = ({ language, onChange }) => {
|
|
10
|
+
const { t } = useTranslation("ui");
|
|
11
|
+
const isPT = language === "pt";
|
|
12
|
+
const optionClass = (isActive) => classNames(
|
|
13
|
+
"relative z-10 flex shrink-0 items-center justify-center rounded-full text-[0.6875rem] font-semibold leading-none tracking-wide transition-colors duration-base",
|
|
14
|
+
isActive ? "text-brand-emphasis" : "text-foreground-faint hover:text-foreground-muted"
|
|
15
|
+
);
|
|
16
|
+
return /* @__PURE__ */ jsx(ControlledTooltip, { content: t("language.toggle"), children: /* @__PURE__ */ jsxs("div", { className: classNames(HEADER_CONTROL_SHELL_CLASS, "gap-x-1.5 pl-2.5"), children: [
|
|
17
|
+
/* @__PURE__ */ jsx(MaterialSymbol, { icon: "language", size: 15, className: "shrink-0 text-foreground-faint" }),
|
|
18
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
|
|
19
|
+
/* @__PURE__ */ jsx(
|
|
20
|
+
"div",
|
|
21
|
+
{
|
|
22
|
+
"aria-hidden": true,
|
|
23
|
+
className: "pointer-events-none absolute left-0 top-0 rounded-full bg-surface shadow-elevation1 ring-1 ring-border transition-transform duration-base ease-enter",
|
|
24
|
+
style: {
|
|
25
|
+
width: `${OPTION_WIDTH_REM}rem`,
|
|
26
|
+
height: `${HEADER_CONTROL_SIZE_REM}rem`,
|
|
27
|
+
transform: `translateX(${isPT ? 0 : OPTION_WIDTH_REM}rem)`
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
),
|
|
31
|
+
/* @__PURE__ */ jsx(
|
|
32
|
+
ControlledButton,
|
|
33
|
+
{
|
|
34
|
+
defaultHtmlButton: true,
|
|
35
|
+
"aria-pressed": isPT,
|
|
36
|
+
onClick: () => onChange("pt"),
|
|
37
|
+
className: optionClass(isPT),
|
|
38
|
+
style: { width: `${OPTION_WIDTH_REM}rem`, height: `${HEADER_CONTROL_SIZE_REM}rem` },
|
|
39
|
+
children: t("language.pt_short")
|
|
40
|
+
}
|
|
41
|
+
),
|
|
42
|
+
/* @__PURE__ */ jsx(
|
|
43
|
+
ControlledButton,
|
|
44
|
+
{
|
|
45
|
+
defaultHtmlButton: true,
|
|
46
|
+
"aria-pressed": !isPT,
|
|
47
|
+
onClick: () => onChange("en"),
|
|
48
|
+
className: optionClass(!isPT),
|
|
49
|
+
style: { width: `${OPTION_WIDTH_REM}rem`, height: `${HEADER_CONTROL_SIZE_REM}rem` },
|
|
50
|
+
children: t("language.en_short")
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
] })
|
|
54
|
+
] }) });
|
|
55
|
+
};
|
|
56
|
+
export {
|
|
57
|
+
LanguageToggle as default
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=LanguageToggle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LanguageToggle.js","sources":["../../../src/components/Shell/LanguageToggle.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\n\nimport { HEADER_CONTROL_SHELL_CLASS, HEADER_CONTROL_SIZE_REM } from './headerControlStyles';\n\nconst OPTION_WIDTH_REM = 2.25;\n\nexport type TUiLanguage = 'pt' | 'en';\n\ninterface ILanguageToggleProps {\n language: TUiLanguage;\n onChange: (language: TUiLanguage) => void;\n}\n\n/** PT/EN knob; the host owns the i18n instance and persistence. */\nconst LanguageToggle: React.FC<ILanguageToggleProps> = ({ language, onChange }) => {\n const { t } = useTranslation('ui');\n\n const isPT = language === 'pt';\n\n const optionClass = (isActive: boolean) =>\n classNames(\n 'relative z-10 flex shrink-0 items-center justify-center rounded-full text-[0.6875rem] font-semibold leading-none tracking-wide transition-colors duration-base',\n isActive ? 'text-brand-emphasis' : 'text-foreground-faint hover:text-foreground-muted',\n );\n\n return (\n <ControlledTooltip content={t('language.toggle')}>\n <div className={classNames(HEADER_CONTROL_SHELL_CLASS, 'gap-x-1.5 pl-2.5')}>\n <MaterialSymbol icon=\"language\" size={15} className=\"shrink-0 text-foreground-faint\" />\n\n <div className=\"relative flex items-center\">\n <div\n aria-hidden\n className=\"pointer-events-none absolute left-0 top-0 rounded-full bg-surface shadow-elevation1 ring-1 ring-border transition-transform duration-base ease-enter\"\n style={{\n width: `${OPTION_WIDTH_REM}rem`,\n height: `${HEADER_CONTROL_SIZE_REM}rem`,\n transform: `translateX(${isPT ? 0 : OPTION_WIDTH_REM}rem)`,\n }}\n />\n\n <ControlledButton\n defaultHtmlButton\n aria-pressed={isPT}\n onClick={() => onChange('pt')}\n className={optionClass(isPT)}\n style={{ width: `${OPTION_WIDTH_REM}rem`, height: `${HEADER_CONTROL_SIZE_REM}rem` }}\n >\n {t('language.pt_short')}\n </ControlledButton>\n\n <ControlledButton\n defaultHtmlButton\n aria-pressed={!isPT}\n onClick={() => onChange('en')}\n className={optionClass(!isPT)}\n style={{ width: `${OPTION_WIDTH_REM}rem`, height: `${HEADER_CONTROL_SIZE_REM}rem` }}\n >\n {t('language.en_short')}\n </ControlledButton>\n </div>\n </div>\n </ControlledTooltip>\n );\n};\n\nexport default LanguageToggle;\n"],"names":[],"mappings":";;;;;;;AASA,MAAM,mBAAmB;AAUzB,MAAM,iBAAiD,CAAC,EAAE,UAAU,eAAe;AACjF,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,QAAM,OAAO,aAAa;AAE1B,QAAM,cAAc,CAAC,aACnB;AAAA,IACE;AAAA,IACA,WAAW,wBAAwB;AAAA,EAAA;AAGvC,SACE,oBAAC,mBAAA,EAAkB,SAAS,EAAE,iBAAiB,GAC7C,UAAA,qBAAC,OAAA,EAAI,WAAW,WAAW,4BAA4B,kBAAkB,GACvE,UAAA;AAAA,IAAA,oBAAC,kBAAe,MAAK,YAAW,MAAM,IAAI,WAAU,kCAAiC;AAAA,IAErF,qBAAC,OAAA,EAAI,WAAU,8BACb,UAAA;AAAA,MAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,eAAW;AAAA,UACX,WAAU;AAAA,UACV,OAAO;AAAA,YACL,OAAO,GAAG,gBAAgB;AAAA,YAC1B,QAAQ,GAAG,uBAAuB;AAAA,YAClC,WAAW,cAAc,OAAO,IAAI,gBAAgB;AAAA,UAAA;AAAA,QACtD;AAAA,MAAA;AAAA,MAGF;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,mBAAiB;AAAA,UACjB,gBAAc;AAAA,UACd,SAAS,MAAM,SAAS,IAAI;AAAA,UAC5B,WAAW,YAAY,IAAI;AAAA,UAC3B,OAAO,EAAE,OAAO,GAAG,gBAAgB,OAAO,QAAQ,GAAG,uBAAuB,MAAA;AAAA,UAE3E,YAAE,mBAAmB;AAAA,QAAA;AAAA,MAAA;AAAA,MAGxB;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,mBAAiB;AAAA,UACjB,gBAAc,CAAC;AAAA,UACf,SAAS,MAAM,SAAS,IAAI;AAAA,UAC5B,WAAW,YAAY,CAAC,IAAI;AAAA,UAC5B,OAAO,EAAE,OAAO,GAAG,gBAAgB,OAAO,QAAQ,GAAG,uBAAuB,MAAA;AAAA,UAE3E,YAAE,mBAAmB;AAAA,QAAA;AAAA,MAAA;AAAA,IACxB,EAAA,CACF;AAAA,EAAA,EAAA,CACF,EAAA,CACF;AAEJ;"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { MaterialSymbolWeight, SymbolCodepoints } from '../UI/MaterialSymbol';
|
|
2
|
+
export type TUiSidebarSection = 'management' | 'registration';
|
|
3
|
+
export interface ISidebarMenuItem {
|
|
4
|
+
id?: string;
|
|
5
|
+
weight?: MaterialSymbolWeight;
|
|
6
|
+
icon: SymbolCodepoints;
|
|
7
|
+
text: string;
|
|
8
|
+
to: string;
|
|
9
|
+
externalUrl?: string;
|
|
10
|
+
end?: boolean;
|
|
11
|
+
section?: TUiSidebarSection;
|
|
12
|
+
children?: Array<ISidebarMenuItem>;
|
|
13
|
+
}
|
|
14
|
+
export declare const resolveSidebarItemId: (item: ISidebarMenuItem) => string;
|
|
15
|
+
interface ISidebarMenuProps {
|
|
16
|
+
items: Array<ISidebarMenuItem>;
|
|
17
|
+
activeClass: string;
|
|
18
|
+
collapsed: boolean;
|
|
19
|
+
activeIconClass?: string;
|
|
20
|
+
onNavigate?: () => void;
|
|
21
|
+
level?: number;
|
|
22
|
+
}
|
|
23
|
+
declare const SidebarMenu: React.FC<ISidebarMenuProps>;
|
|
24
|
+
export default SidebarMenu;
|