@godxjp/ui 2.2.0 → 5.0.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.
Files changed (47) hide show
  1. package/BRAND.md +39 -29
  2. package/CHANGELOG.md +566 -10
  3. package/README.md +143 -168
  4. package/config/eslint.js +54 -0
  5. package/config/prettier.cjs +20 -0
  6. package/config/tsconfig.base.json +22 -0
  7. package/config/vitest.base.ts +26 -0
  8. package/dist/MiniMonth-YAmPGEpC.d.ts +143 -0
  9. package/dist/Table.types-BbsxoIYE.d.ts +352 -0
  10. package/dist/color-DO0qqUAb.d.ts +38 -0
  11. package/dist/components/composites.d.ts +963 -0
  12. package/dist/components/composites.js +7340 -0
  13. package/dist/components/composites.js.map +1 -0
  14. package/dist/components/primitives.d.ts +2633 -163
  15. package/dist/components/primitives.js +7266 -165
  16. package/dist/components/primitives.js.map +1 -1
  17. package/dist/components/shell.d.ts +82 -12
  18. package/dist/components/shell.js +168 -162
  19. package/dist/components/shell.js.map +1 -1
  20. package/dist/hooks.d.ts +83 -8
  21. package/dist/hooks.js +497 -83
  22. package/dist/hooks.js.map +1 -1
  23. package/dist/i18n.d.ts +55 -3
  24. package/dist/i18n.js +456 -5
  25. package/dist/i18n.js.map +1 -1
  26. package/dist/index.d.ts +24 -5
  27. package/dist/index.js +12524 -267
  28. package/dist/index.js.map +1 -1
  29. package/dist/padding-DY0JV5Ja.d.ts +16 -0
  30. package/dist/preferences.d.ts +132 -0
  31. package/dist/preferences.js +262 -0
  32. package/dist/preferences.js.map +1 -0
  33. package/dist/props.d.ts +86 -0
  34. package/dist/props.js +16 -0
  35. package/dist/props.js.map +1 -0
  36. package/dist/size-CQwNvOWd.d.ts +19 -0
  37. package/dist/{data.d.ts → types-LTj-2bl-.d.ts} +7 -12
  38. package/dist/useTableViews-D5NIAJ7h.d.ts +154 -0
  39. package/package.json +92 -35
  40. package/src/tokens/tailwind.css +158 -0
  41. package/dist/components/screens.d.ts +0 -51
  42. package/dist/components/screens.js +0 -806
  43. package/dist/components/screens.js.map +0 -1
  44. package/dist/data.js +0 -93
  45. package/dist/data.js.map +0 -1
  46. package/src/tokens/tokens-ext.css +0 -401
  47. package/src/tokens/tokens.css +0 -765
@@ -1,19 +1,34 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, ComponentType } from 'react';
3
- import { ForgeProduct, ForgeProject } from '../data.js';
3
+ import { F as ForgeProduct, a as ForgeProject } from '../types-LTj-2bl-.js';
4
+ export { P as ProjectKind, b as ProjectStatus } from '../types-LTj-2bl-.js';
5
+ import { P as PaddingProp } from '../padding-DY0JV5Ja.js';
4
6
 
5
7
  interface AppShellProps {
8
+ /** Sidebar content — usually `<Sidebar … />` from this package. */
6
9
  sidebar: ReactNode;
7
- topbar: ReactNode;
10
+ /** Main content — page or `<Outlet />`. */
8
11
  children: ReactNode;
9
12
  /**
10
- * Controls the visual collapsed state. The hook + Tweaks panel
11
- * mirror this onto `<html data-collapsed>` via useTweaks; pass the
12
- * same value here so the grid columns shrink in lockstep.
13
+ * Topbar slot. Provide EITHER `topbar` for a single composed header
14
+ * (e.g. `<Topbar />` from this package) OR `topbarLeft` +
15
+ * `topbarRight` to lay out two slot groups within the canonical
16
+ * header rail. Mixing `topbar` with the split slots is allowed —
17
+ * the single `topbar` wins.
13
18
  */
19
+ topbar?: ReactNode;
20
+ topbarLeft?: ReactNode;
21
+ topbarRight?: ReactNode;
22
+ /** Optional logo / brand mark — rendered at the top-left of the topbar. */
23
+ logo?: ReactNode;
24
+ /** Optional breadcrumb rail — sits just above main content. */
25
+ breadcrumb?: ReactNode;
26
+ /** Optional footer band under main content. */
27
+ footer?: ReactNode;
28
+ /** Sidebar collapsed state — mirror onto `<html data-collapsed>` via useTweaks. */
14
29
  sidebarCollapsed?: boolean;
15
30
  }
16
- declare function AppShell({ sidebar, topbar, children, sidebarCollapsed }: AppShellProps): react_jsx_runtime.JSX.Element;
31
+ declare function AppShell({ sidebar, topbar, topbarLeft, topbarRight, logo, breadcrumb, footer, children, sidebarCollapsed, }: AppShellProps): react_jsx_runtime.JSX.Element;
17
32
 
18
33
  interface SidebarItem {
19
34
  id: string;
@@ -37,14 +52,23 @@ interface SidebarProps {
37
52
  /** Click handler — called with the item's `id`. */
38
53
  onSelect: (id: string) => void;
39
54
  sections: SidebarSection[];
40
- /** Product = the org-shaped tenant shown in the top product chip. */
41
- product: ForgeProduct;
55
+ /**
56
+ * Product = the org-shaped tenant shown in the top product chip.
57
+ * Required unless `brand` is provided (custom top slot wins).
58
+ */
59
+ product?: ForgeProduct;
42
60
  /** Click to open the product switcher dropdown (owned by parent). */
43
61
  onProductClick?: () => void;
62
+ /**
63
+ * Custom top-of-sidebar slot — when present, replaces the product
64
+ * chip entirely. Use for service-specific brands (e.g. me-service
65
+ * renders the signed-in user's avatar + name + email here).
66
+ */
67
+ brand?: ReactNode;
44
68
  collapsed?: boolean;
45
69
  footer?: ReactNode;
46
70
  }
47
- declare function Sidebar({ activeId, onSelect, sections, product, onProductClick, collapsed, footer, }: SidebarProps): react_jsx_runtime.JSX.Element;
71
+ declare function Sidebar({ activeId, onSelect, sections, product, onProductClick, brand, collapsed, footer, }: SidebarProps): react_jsx_runtime.JSX.Element;
48
72
 
49
73
  interface TopbarProps {
50
74
  product: ForgeProduct;
@@ -57,14 +81,30 @@ interface TopbarProps {
57
81
  onToggleCollapsed?: () => void;
58
82
  /** Optional breadcrumb / actions slot on the right. */
59
83
  rightSlot?: ReactNode;
84
+ /**
85
+ * Notification bell. When `unread` is true a 6×6 attention-colored
86
+ * dot renders in the top-right corner. Click handler invokes
87
+ * `onNotificationsOpen`. Hidden when both are unset.
88
+ */
89
+ unread?: boolean;
90
+ onNotificationsOpen?: () => void;
91
+ /**
92
+ * Right-edge user chip. Rendered as the trailing element (after
93
+ * notifications, before tweaks) to match the design-handoff topbar
94
+ * shape (shell.jsx:366). Pass any node — typically a `<Avatar>` or
95
+ * a `<DropdownMenu>` wrapping one.
96
+ */
97
+ user?: ReactNode;
60
98
  }
61
- declare function Topbar({ product, project, onProductOpen, onProjectOpen, onSearchOpen, onTweaksOpen, collapsed, onToggleCollapsed, rightSlot, }: TopbarProps): react_jsx_runtime.JSX.Element;
99
+ declare function Topbar({ product, project, onProductOpen, onProjectOpen, onSearchOpen, onTweaksOpen, collapsed, onToggleCollapsed, rightSlot, unread, onNotificationsOpen, user, }: TopbarProps): react_jsx_runtime.JSX.Element;
62
100
 
63
101
  interface TweaksPanelProps {
64
102
  open: boolean;
65
103
  onOpenChange: (open: boolean) => void;
104
+ /** Product catalogue for the tenant selector. Pass `[]` to hide. */
105
+ products?: ForgeProduct[];
66
106
  }
67
- declare function TweaksPanel({ open, onOpenChange }: TweaksPanelProps): react_jsx_runtime.JSX.Element;
107
+ declare function TweaksPanel({ open, onOpenChange, products }: TweaksPanelProps): react_jsx_runtime.JSX.Element;
68
108
 
69
109
  interface ProductSwitcherProps {
70
110
  trigger: ReactNode;
@@ -109,4 +149,34 @@ interface CommandPaletteProps {
109
149
  }
110
150
  declare function CommandPalette({ open, onOpenChange, commands }: CommandPaletteProps): react_jsx_runtime.JSX.Element;
111
151
 
112
- export { AppShell, type AppShellProps, type CommandItem, CommandPalette, type CommandPaletteProps, ProductSwitcher, type ProductSwitcherProps, ProjectSwitcher, type ProjectSwitcherProps, type RecentProject, Sidebar, type SidebarItem, type SidebarProps, type SidebarSection, Topbar, type TopbarProps, TweaksPanel, type TweaksPanelProps };
152
+ type PageContentPadding = PaddingProp;
153
+ interface PageContentProps {
154
+ /** Page title — usually rendered as <h1>. */
155
+ title?: ReactNode;
156
+ /** Short page description under the title. */
157
+ subtitle?: ReactNode;
158
+ /**
159
+ * Right-aligned slot in the header — typically primary actions
160
+ * (Save, Cancel, New, etc.) or settings buttons.
161
+ */
162
+ extra?: ReactNode;
163
+ /** Optional breadcrumb rendered above the title. */
164
+ breadcrumb?: ReactNode;
165
+ /** Optional tabs row rendered under the title block, above content. */
166
+ tabs?: ReactNode;
167
+ /** Main content. */
168
+ children?: ReactNode;
169
+ /** Page-level footer (sticky-feeling band under content). */
170
+ footer?: ReactNode;
171
+ /** Padding step — default = system-wide standard. */
172
+ padding?: PageContentPadding;
173
+ /**
174
+ * Header behaviour. `default` shows the title block; `none` skips it
175
+ * entirely (when the page itself renders its own custom header).
176
+ */
177
+ header?: "default" | "none";
178
+ className?: string;
179
+ }
180
+ declare function PageContent({ title, subtitle, extra, breadcrumb, tabs, children, footer, padding, header, className, }: PageContentProps): react_jsx_runtime.JSX.Element;
181
+
182
+ export { AppShell, type AppShellProps, type CommandItem, CommandPalette, type CommandPaletteProps, ForgeProduct, ForgeProject, PageContent, type PageContentPadding, type PageContentProps, ProductSwitcher, type ProductSwitcherProps, ProjectSwitcher, type ProjectSwitcherProps, type RecentProject, Sidebar, type SidebarItem, type SidebarProps, type SidebarSection, Topbar, type TopbarProps, TweaksPanel, type TweaksPanelProps };
@@ -1,5 +1,5 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
- import { ChevronDown, PanelLeftOpen, PanelLeftClose, Search, SlidersHorizontal, X, Check, Clock } from 'lucide-react';
2
+ import { ChevronDown, PanelLeftOpen, PanelLeftClose, Search, Bell, SlidersHorizontal, X, Check, Clock } from 'lucide-react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
@@ -11,11 +11,32 @@ import * as Popover from '@radix-ui/react-popover';
11
11
  import * as VisuallyHidden from '@radix-ui/react-visually-hidden';
12
12
  import { Command } from 'cmdk';
13
13
 
14
- function AppShell({ sidebar, topbar, children, sidebarCollapsed = false }) {
14
+ // src/components/shell/AppShell.tsx
15
+ function AppShell({
16
+ sidebar,
17
+ topbar,
18
+ topbarLeft,
19
+ topbarRight,
20
+ logo,
21
+ breadcrumb,
22
+ footer,
23
+ children,
24
+ sidebarCollapsed = false
25
+ }) {
26
+ const resolvedTopbar = topbar !== void 0 ? topbar : /* @__PURE__ */ jsxs("div", { className: "app-topbar-rail", children: [
27
+ logo !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-logo", children: logo }),
28
+ topbarLeft !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-left", children: topbarLeft }),
29
+ /* @__PURE__ */ jsx("div", { className: "app-topbar-spacer" }),
30
+ topbarRight !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-topbar-right", children: topbarRight })
31
+ ] });
15
32
  return /* @__PURE__ */ jsxs("div", { className: "app-root", "data-collapsed": sidebarCollapsed, children: [
16
33
  /* @__PURE__ */ jsx("aside", { className: "app-sidebar", children: sidebar }),
17
- /* @__PURE__ */ jsx("header", { className: "app-topbar", children: topbar }),
18
- /* @__PURE__ */ jsx("main", { className: "app-main", children })
34
+ /* @__PURE__ */ jsx("header", { className: "app-topbar", children: resolvedTopbar }),
35
+ /* @__PURE__ */ jsxs("main", { className: "app-main", children: [
36
+ breadcrumb !== void 0 && /* @__PURE__ */ jsx("div", { className: "app-breadcrumb", children: breadcrumb }),
37
+ children
38
+ ] }),
39
+ footer !== void 0 && /* @__PURE__ */ jsx("footer", { className: "app-footer", children: footer })
19
40
  ] });
20
41
  }
21
42
  function cn(...inputs) {
@@ -27,12 +48,13 @@ function Sidebar({
27
48
  sections,
28
49
  product,
29
50
  onProductClick,
51
+ brand,
30
52
  collapsed = false,
31
53
  footer
32
54
  }) {
33
55
  const { t } = useTranslation();
34
- return /* @__PURE__ */ jsxs(Fragment, { children: [
35
- /* @__PURE__ */ jsxs(
56
+ return /* @__PURE__ */ jsxs("div", { className: "sb-root", "data-collapsed": collapsed ? "true" : void 0, style: { display: "contents" }, children: [
57
+ brand !== void 0 ? /* @__PURE__ */ jsx("div", { className: "sb-brand", children: brand }) : product ? /* @__PURE__ */ jsxs(
36
58
  "button",
37
59
  {
38
60
  type: "button",
@@ -62,7 +84,7 @@ function Sidebar({
62
84
  !collapsed && /* @__PURE__ */ jsx("span", { className: "sb-product-tenant shrink-0", children: /* @__PURE__ */ jsx(ChevronDown, { size: 14 }) })
63
85
  ]
64
86
  }
65
- ),
87
+ ) : null,
66
88
  /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: sections.map((section, i) => /* @__PURE__ */ jsxs("div", { className: "sb-section", children: [
67
89
  section.label && !collapsed && /* @__PURE__ */ jsx("div", { className: "sb-section-label", children: section.label }),
68
90
  /* @__PURE__ */ jsx("div", { className: "sb-nav", role: "navigation", children: section.items.map((item) => {
@@ -74,13 +96,15 @@ function Sidebar({
74
96
  type: "button",
75
97
  className: cn("sb-nav-item"),
76
98
  "data-active": isActive,
99
+ "aria-label": collapsed ? item.label : void 0,
77
100
  "aria-current": isActive ? "page" : void 0,
78
101
  "aria-disabled": item.disabled,
102
+ title: collapsed ? item.label : void 0,
79
103
  onClick: () => !item.disabled && onSelect(item.id),
80
104
  children: [
81
105
  /* @__PURE__ */ jsx("span", { className: "sb-icon", children: /* @__PURE__ */ jsx(Icon, { size: 16 }) }),
82
- /* @__PURE__ */ jsx("span", { className: "sb-label", children: item.label }),
83
- item.badge !== void 0 && item.badge !== "" && /* @__PURE__ */ jsx("span", { className: "sb-badge", children: item.badge })
106
+ !collapsed && /* @__PURE__ */ jsx("span", { className: "sb-label", children: item.label }),
107
+ !collapsed && item.badge !== void 0 && item.badge !== "" && /* @__PURE__ */ jsx("span", { className: "sb-badge", children: item.badge })
84
108
  ]
85
109
  },
86
110
  item.id
@@ -99,7 +123,10 @@ function Topbar({
99
123
  onTweaksOpen,
100
124
  collapsed = false,
101
125
  onToggleCollapsed,
102
- rightSlot
126
+ rightSlot,
127
+ unread = false,
128
+ onNotificationsOpen,
129
+ user
103
130
  }) {
104
131
  const { t } = useTranslation();
105
132
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -141,7 +168,7 @@ function Topbar({
141
168
  "button",
142
169
  {
143
170
  type: "button",
144
- className: cn("tb-chip", !project && "empty"),
171
+ className: cn("tb-chip", !project && "tb-chip-empty"),
145
172
  "aria-label": project ? project.name : t("shell.pickProject"),
146
173
  onClick: onProjectOpen,
147
174
  children: [
@@ -168,6 +195,20 @@ function Topbar({
168
195
  }
169
196
  ),
170
197
  rightSlot,
198
+ onNotificationsOpen && /* @__PURE__ */ jsxs(
199
+ "button",
200
+ {
201
+ type: "button",
202
+ className: "tb-icon-btn tb-bell",
203
+ "aria-label": t("topbar.notifications", { defaultValue: "Notifications" }),
204
+ onClick: onNotificationsOpen,
205
+ children: [
206
+ /* @__PURE__ */ jsx(Bell, { size: 16 }),
207
+ unread && /* @__PURE__ */ jsx("span", { className: "tb-bell-dot", "aria-hidden": true })
208
+ ]
209
+ }
210
+ ),
211
+ user,
171
212
  onTweaksOpen && /* @__PURE__ */ jsx(
172
213
  "button",
173
214
  {
@@ -180,90 +221,12 @@ function Topbar({
180
221
  )
181
222
  ] });
182
223
  }
183
-
184
- // src/data/products.ts
185
- var PRODUCTS = [
186
- {
187
- id: "restaurant",
188
- name: "godx-restaurant",
189
- tenant: "restaurant",
190
- role: "\u30EC\u30B9\u30C8\u30E9\u30F3\u7BA1\u7406",
191
- desc: "\u5E97\u8217\u5411\u3051\u7D71\u5408\u7BA1\u7406\u30D7\u30E9\u30C3\u30C8\u30D5\u30A9\u30FC\u30E0",
192
- color: "oklch(58% 0.18 25)",
193
- owner: "Satoshi F",
194
- devs: 6,
195
- projects: [
196
- { id: "api", name: "restaurant-api", stack: "NestJS \xB7 PostgreSQL", kind: "service", devs: 3, status: "active", branch: "main", lastCommit: "12\u5206\u524D", openIssues: 8, prs: 2, sandbox: true },
197
- { id: "admin", name: "restaurant-admin", stack: "Next.js 14 \xB7 React", kind: "web", devs: 2, status: "active", branch: "main", lastCommit: "1\u6642\u9593\u524D", openIssues: 5, prs: 1, sandbox: true },
198
- { id: "pos", name: "restaurant-pos", stack: "Tauri \xB7 Vue 3", kind: "desktop", devs: 1, status: "active", branch: "feature/print", lastCommit: "30\u5206\u524D", openIssues: 3, prs: 1, sandbox: true },
199
- { id: "kds", name: "restaurant-kds", stack: "React \xB7 Electron", kind: "workstation", devs: 1, status: "review", branch: "main", lastCommit: "\u6628\u65E5", openIssues: 2, prs: 1, sandbox: true },
200
- { id: "kintai", name: "restaurant-kintai", stack: "Vue 3 \xB7 Laravel", kind: "service", devs: 2, status: "active", branch: "main", lastCommit: "5\u5206\u524D", openIssues: 4, prs: 0, sandbox: true },
201
- { id: "mobile", name: "restaurant-mobile", stack: "React Native", kind: "mobile", devs: 1, status: "planning", branch: "develop", lastCommit: "3\u65E5\u524D", openIssues: 1, prs: 0, sandbox: false }
202
- ]
203
- },
204
- {
205
- id: "godx",
206
- name: "godx-admin",
207
- tenant: "godx",
208
- role: "Platform admin",
209
- desc: "GoDX Forge developer workspace",
210
- color: "oklch(60% 0.137 163)",
211
- owner: "Satoshi F",
212
- devs: 4,
213
- projects: [
214
- { id: "frontend", name: "godx-admin-frontend", stack: "React \xB7 Vite", kind: "web", devs: 2, status: "active", branch: "master", lastCommit: "2\u6642\u9593\u524D", openIssues: 6, prs: 2, sandbox: true },
215
- { id: "api", name: "godx-admin-api", stack: "Go \xB7 Gin", kind: "service", devs: 1, status: "active", branch: "master", lastCommit: "4\u6642\u9593\u524D", openIssues: 3, prs: 1, sandbox: true },
216
- { id: "ui", name: "@godxjp/ui", stack: "TypeScript \xB7 React", kind: "library", devs: 2, status: "active", branch: "master", lastCommit: "1\u6642\u9593\u524D", openIssues: 2, prs: 0, sandbox: false }
217
- ]
218
- },
219
- {
220
- id: "kintai",
221
- name: "dxs-kintai",
222
- tenant: "kintai",
223
- role: "HR / Attendance",
224
- desc: "\u52E4\u6020\u7BA1\u7406\u30D7\u30E9\u30C3\u30C8\u30D5\u30A9\u30FC\u30E0",
225
- color: "oklch(56% 0.15 240)",
226
- owner: "Naoki N",
227
- devs: 3,
228
- projects: [
229
- { id: "frontend", name: "kintai-web", stack: "Vue 3 \xB7 Vite", kind: "web", devs: 2, status: "active", branch: "main", lastCommit: "20\u5206\u524D", openIssues: 7, prs: 1, sandbox: true },
230
- { id: "backend", name: "kintai-api", stack: "Laravel 11", kind: "service", devs: 1, status: "active", branch: "main", lastCommit: "1\u65E5\u524D", openIssues: 4, prs: 0, sandbox: true }
231
- ]
232
- },
233
- {
234
- id: "tempo",
235
- name: "dxs-tempo",
236
- tenant: "tempo",
237
- role: "Shop / Inventory",
238
- desc: "\u5E97\u8217\u30FB\u5728\u5EAB\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9",
239
- color: "oklch(48% 0.16 285)",
240
- owner: "Naoki N",
241
- devs: 2,
242
- projects: [
243
- { id: "api", name: "tempo-api", stack: "Go \xB7 Echo", kind: "service", devs: 2, status: "active", branch: "main", lastCommit: "5\u6642\u9593\u524D", openIssues: 9, prs: 1, sandbox: true },
244
- { id: "ops", name: "tempo-ops", stack: "Terraform", kind: "infra", devs: 1, status: "planning", branch: "main", lastCommit: "1\u9031\u9593\u524D", openIssues: 1, prs: 0, sandbox: false }
245
- ]
246
- },
247
- {
248
- id: "betoya",
249
- name: "betoya",
250
- tenant: "betoya",
251
- role: "Vietnamese restaurant",
252
- desc: "\u30D9\u30C8\u5C4B Tenant",
253
- color: "oklch(58% 0.159 150)",
254
- owner: "Anh K",
255
- devs: 1,
256
- projects: [
257
- { id: "site", name: "betoya-site", stack: "Astro", kind: "web", devs: 1, status: "active", branch: "main", lastCommit: "\u6628\u65E5", openIssues: 2, prs: 0, sandbox: false }
258
- ]
259
- }
260
- ];
261
- var SUPPORTED_LOCALES = ["ja", "en", "vi"];
262
- var FORGE_LOCALE_STORAGE_KEY = "forge.locale";
224
+ var SUPPORTED_LOCALES = ["ja", "en", "vi", "fil"];
225
+ var GODX_LOCALE_STORAGE_KEY = "godx.locale";
263
226
  var i18n_default = i18next;
264
227
 
265
228
  // src/hooks/useTweaks.ts
266
- var STORAGE_KEY = "forge.tweaks";
229
+ var STORAGE_KEY = "godx.tweaks";
267
230
  var DEFAULTS = {
268
231
  density: "default",
269
232
  theme: "light",
@@ -302,10 +265,11 @@ function useTweaks() {
302
265
  html.lang = tweaks.locale;
303
266
  }, [tweaks.theme, tweaks.density, tweaks.tenant, tweaks.locale]);
304
267
  useEffect(() => {
268
+ if (!i18n_default.isInitialized) return;
305
269
  if (i18n_default.language?.slice(0, 2) !== tweaks.locale) {
306
270
  void i18n_default.changeLanguage(tweaks.locale);
307
271
  try {
308
- window.localStorage.setItem(FORGE_LOCALE_STORAGE_KEY, tweaks.locale);
272
+ window.localStorage.setItem(GODX_LOCALE_STORAGE_KEY, tweaks.locale);
309
273
  } catch {
310
274
  }
311
275
  }
@@ -315,76 +279,82 @@ function useTweaks() {
315
279
  }, []);
316
280
  return { tweaks, setTweak, setTweaks };
317
281
  }
318
- PRODUCTS.map((p) => ({ value: p.tenant, label: p.name }));
319
- function TweaksPanel({ open, onOpenChange }) {
282
+ function TweaksPanel({ open, onOpenChange, products = [] }) {
320
283
  const { t } = useTranslation();
321
284
  const { tweaks, setTweak } = useTweaks();
322
285
  return /* @__PURE__ */ jsx(Dialog.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [
323
286
  /* @__PURE__ */ jsx(Dialog.Overlay, { className: "fixed inset-0 z-40 bg-black/30 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" }),
324
- /* @__PURE__ */ jsxs(Dialog.Content, { className: "fixed right-0 top-0 z-50 h-full w-80 bg-popover text-popover-foreground border-l border-border shadow-2xl data-[state=open]:animate-in data-[state=open]:slide-in-from-right", children: [
325
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-4 h-12", children: [
326
- /* @__PURE__ */ jsx(Dialog.Title, { className: "font-medium text-sm", children: t("tweaks.title") }),
327
- /* @__PURE__ */ jsx(Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsx("button", { className: "tb-icon-btn", "aria-label": "Close", children: /* @__PURE__ */ jsx(X, { size: 14 }) }) })
328
- ] }),
329
- /* @__PURE__ */ jsxs("div", { className: "overflow-y-auto h-[calc(100%-3rem)] p-4 flex flex-col gap-6", children: [
330
- /* @__PURE__ */ jsxs(Section, { label: t("tweaks.display"), children: [
331
- /* @__PURE__ */ jsx(
332
- Radio,
333
- {
334
- label: t("tweaks.density"),
335
- value: tweaks.density,
336
- onChange: (v) => setTweak("density", v),
337
- options: [
338
- { value: "compact", label: t("tweaks.densityCompact") },
339
- { value: "default", label: t("tweaks.densityDefault") },
340
- { value: "comfortable", label: t("tweaks.densityComfortable") }
341
- ]
342
- }
343
- ),
344
- /* @__PURE__ */ jsx(
345
- Radio,
346
- {
347
- label: t("tweaks.theme"),
348
- value: tweaks.theme,
349
- onChange: (v) => setTweak("theme", v),
350
- options: [
351
- { value: "light", label: t("tweaks.themeLight") },
352
- { value: "dark", label: t("tweaks.themeDark") }
353
- ]
354
- }
355
- ),
356
- /* @__PURE__ */ jsx(
357
- Toggle,
358
- {
359
- label: t("shell.sidebarCollapse"),
360
- value: tweaks.sidebarCollapsed,
361
- onChange: (v) => setTweak("sidebarCollapsed", v)
362
- }
363
- )
364
- ] }),
365
- /* @__PURE__ */ jsx(Section, { label: t("tweaks.product"), children: /* @__PURE__ */ jsx(
366
- Select,
367
- {
368
- label: t("tweaks.product"),
369
- value: tweaks.tenant,
370
- onChange: (v) => setTweak("tenant", v),
371
- options: PRODUCTS.map((p) => ({ value: p.tenant, label: p.name }))
372
- }
373
- ) }),
374
- /* @__PURE__ */ jsx(Section, { label: t("tweaks.locale"), children: /* @__PURE__ */ jsx(
375
- Radio,
376
- {
377
- label: t("tweaks.language"),
378
- value: tweaks.locale,
379
- onChange: (v) => setTweak("locale", v),
380
- options: SUPPORTED_LOCALES.map((code) => ({
381
- value: code,
382
- label: { ja: "\u65E5\u672C\u8A9E", en: "English", vi: "Ti\u1EBFng Vi\u1EC7t" }[code]
383
- }))
384
- }
385
- ) })
386
- ] })
387
- ] })
287
+ /* @__PURE__ */ jsxs(
288
+ Dialog.Content,
289
+ {
290
+ className: "fixed right-0 top-0 z-50 h-full w-80 bg-popover text-popover-foreground border-l border-border shadow-2xl data-[state=open]:animate-in data-[state=open]:slide-in-from-right",
291
+ "aria-describedby": void 0,
292
+ children: [
293
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-4 h-12", children: [
294
+ /* @__PURE__ */ jsx(Dialog.Title, { className: "font-medium text-sm", children: t("tweaks.title") }),
295
+ /* @__PURE__ */ jsx(Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsx("button", { className: "tb-icon-btn", "aria-label": "Close", children: /* @__PURE__ */ jsx(X, { size: 14 }) }) })
296
+ ] }),
297
+ /* @__PURE__ */ jsxs("div", { className: "overflow-y-auto h-[calc(100%-3rem)] p-4 flex flex-col gap-6", children: [
298
+ /* @__PURE__ */ jsxs(Section, { label: t("tweaks.display"), children: [
299
+ /* @__PURE__ */ jsx(
300
+ Radio,
301
+ {
302
+ label: t("tweaks.density"),
303
+ value: tweaks.density,
304
+ onChange: (v) => setTweak("density", v),
305
+ options: [
306
+ { value: "compact", label: t("tweaks.densityCompact") },
307
+ { value: "default", label: t("tweaks.densityDefault") },
308
+ { value: "comfortable", label: t("tweaks.densityComfortable") }
309
+ ]
310
+ }
311
+ ),
312
+ /* @__PURE__ */ jsx(
313
+ Radio,
314
+ {
315
+ label: t("tweaks.theme"),
316
+ value: tweaks.theme,
317
+ onChange: (v) => setTweak("theme", v),
318
+ options: [
319
+ { value: "light", label: t("tweaks.themeLight") },
320
+ { value: "dark", label: t("tweaks.themeDark") }
321
+ ]
322
+ }
323
+ ),
324
+ /* @__PURE__ */ jsx(
325
+ Toggle,
326
+ {
327
+ label: t("shell.sidebarCollapse"),
328
+ value: tweaks.sidebarCollapsed,
329
+ onChange: (v) => setTweak("sidebarCollapsed", v)
330
+ }
331
+ )
332
+ ] }),
333
+ products.length > 0 && /* @__PURE__ */ jsx(Section, { label: t("tweaks.product"), children: /* @__PURE__ */ jsx(
334
+ Select,
335
+ {
336
+ label: t("tweaks.product"),
337
+ value: tweaks.tenant,
338
+ onChange: (v) => setTweak("tenant", v),
339
+ options: products.map((p) => ({ value: p.tenant, label: p.name }))
340
+ }
341
+ ) }),
342
+ /* @__PURE__ */ jsx(Section, { label: t("tweaks.locale"), children: /* @__PURE__ */ jsx(
343
+ Radio,
344
+ {
345
+ label: t("tweaks.language"),
346
+ value: tweaks.locale,
347
+ onChange: (v) => setTweak("locale", v),
348
+ options: SUPPORTED_LOCALES.map((code) => ({
349
+ value: code,
350
+ label: { ja: "\u65E5\u672C\u8A9E", en: "English", vi: "Ti\u1EBFng Vi\u1EC7t", fil: "Filipino" }[code] ?? code
351
+ }))
352
+ }
353
+ ) })
354
+ ] })
355
+ ]
356
+ }
357
+ )
388
358
  ] }) });
389
359
  }
390
360
  function Section({ label, children }) {
@@ -463,7 +433,7 @@ function Select({
463
433
  function ProductSwitcher({
464
434
  trigger,
465
435
  activeId,
466
- products = PRODUCTS,
436
+ products = [],
467
437
  onSelect,
468
438
  open,
469
439
  onOpenChange
@@ -551,7 +521,7 @@ function ProjectSwitcher({
551
521
  activeProductId,
552
522
  activeProjectId,
553
523
  recent = [],
554
- products = PRODUCTS,
524
+ products = [],
555
525
  onSelect,
556
526
  open,
557
527
  onOpenChange
@@ -762,7 +732,43 @@ function CommandPalette({ open, onOpenChange, commands }) {
762
732
  )
763
733
  ] }) });
764
734
  }
735
+ function PageContent({
736
+ title,
737
+ subtitle,
738
+ extra,
739
+ breadcrumb,
740
+ tabs,
741
+ children,
742
+ footer,
743
+ padding = "default",
744
+ header = "default",
745
+ className
746
+ }) {
747
+ const showHeader = header !== "none" && (breadcrumb || title || subtitle || extra);
748
+ return /* @__PURE__ */ jsxs(
749
+ "section",
750
+ {
751
+ className: cn("page-content", className),
752
+ "data-padding": padding,
753
+ children: [
754
+ showHeader && /* @__PURE__ */ jsxs("header", { className: "page-content-header", children: [
755
+ breadcrumb && /* @__PURE__ */ jsx("div", { className: "page-content-breadcrumb", children: breadcrumb }),
756
+ /* @__PURE__ */ jsxs("div", { className: "page-content-titlebar", children: [
757
+ /* @__PURE__ */ jsxs("div", { className: "page-content-titlegroup", children: [
758
+ title && /* @__PURE__ */ jsx("h1", { className: "page-content-title", children: title }),
759
+ subtitle && /* @__PURE__ */ jsx("p", { className: "page-content-subtitle", children: subtitle })
760
+ ] }),
761
+ extra && /* @__PURE__ */ jsx("div", { className: "page-content-extra", children: extra })
762
+ ] }),
763
+ tabs && /* @__PURE__ */ jsx("div", { className: "page-content-tabs", children: tabs })
764
+ ] }),
765
+ children !== void 0 && /* @__PURE__ */ jsx("div", { className: "page-content-body", children }),
766
+ footer && /* @__PURE__ */ jsx("footer", { className: "page-content-footer", children: footer })
767
+ ]
768
+ }
769
+ );
770
+ }
765
771
 
766
- export { AppShell, CommandPalette, ProductSwitcher, ProjectSwitcher, Sidebar, Topbar, TweaksPanel };
772
+ export { AppShell, CommandPalette, PageContent, ProductSwitcher, ProjectSwitcher, Sidebar, Topbar, TweaksPanel };
767
773
  //# sourceMappingURL=shell.js.map
768
774
  //# sourceMappingURL=shell.js.map