@nubitio/admin 0.5.29 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -21,15 +21,90 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
+ let react_router_dom = require("react-router-dom");
25
+ let react_jsx_runtime = require("react/jsx-runtime");
24
26
  let react = require("react");
25
27
  react = __toESM(react, 1);
26
- let react_router_dom = require("react-router-dom");
27
28
  let _nubitio_ui = require("@nubitio/ui");
28
- let react_jsx_runtime = require("react/jsx-runtime");
29
29
  let _tanstack_react_query = require("@tanstack/react-query");
30
30
  let _nubitio_core = require("@nubitio/core");
31
31
  let _nubitio_crud = require("@nubitio/crud");
32
32
  let _nubitio_hydra = require("@nubitio/hydra");
33
+ //#region packages/admin/FeatureHubLayout.tsx
34
+ function normalizeIcon(icon) {
35
+ if (!icon) return void 0;
36
+ if (icon.startsWith("ph ")) return icon;
37
+ if (icon.startsWith("ph-")) return `ph ${icon}`;
38
+ return `ph ph-${icon}`;
39
+ }
40
+ function normalizeBasePath(path) {
41
+ return path.endsWith("/") && path.length > 1 ? path.slice(0, -1) : path;
42
+ }
43
+ function FeatureHubLayout({ title, subtitle, tabs, basePath, defaultPath, banner, tabsAriaLabel = "Secciones", density = "default", className = "" }) {
44
+ const location = (0, react_router_dom.useLocation)();
45
+ const normalizedBase = normalizeBasePath(basePath);
46
+ const visibleTabs = tabs.filter((tab) => tab.visible !== false);
47
+ const compact = density === "compact";
48
+ const densityClass = compact ? " nb-feature-hub--compact" : "";
49
+ const showBanner = banner && banner.visible !== false && (!compact || banner.showInCompact === true);
50
+ if (location.pathname === normalizedBase || location.pathname === `${normalizedBase}/`) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Navigate, {
51
+ to: defaultPath,
52
+ replace: true
53
+ });
54
+ const tabsNav = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
55
+ className: "nb-feature-hub__tabs",
56
+ "aria-label": tabsAriaLabel,
57
+ children: visibleTabs.map((tab) => {
58
+ const iconClass = normalizeIcon(tab.icon);
59
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_router_dom.NavLink, {
60
+ to: tab.path,
61
+ className: ({ isActive }) => `nb-feature-hub__tab${isActive ? " nb-feature-hub__tab--active" : ""}`,
62
+ children: [iconClass && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
63
+ className: iconClass,
64
+ "aria-hidden": "true"
65
+ }), tab.label]
66
+ }, tab.key);
67
+ })
68
+ });
69
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
70
+ className: `nb-feature-hub${densityClass}${className ? ` ${className}` : ""}`,
71
+ children: [
72
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
73
+ className: "nb-feature-hub__header",
74
+ children: compact ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
75
+ className: "nb-feature-hub__tabs-card",
76
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
77
+ className: "nb-feature-hub__title",
78
+ children: title
79
+ }), tabsNav]
80
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
81
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
82
+ className: "nb-feature-hub__title",
83
+ children: title
84
+ }),
85
+ subtitle && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
86
+ className: "nb-feature-hub__subtitle",
87
+ children: subtitle
88
+ }),
89
+ tabsNav
90
+ ] })
91
+ }),
92
+ showBanner && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
93
+ className: `nb-feature-hub__banner nb-feature-hub__banner--${banner.tone ?? "info"}`,
94
+ role: "status",
95
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
96
+ className: normalizeIcon(banner.icon ?? "ph-info") ?? "ph ph-info",
97
+ "aria-hidden": "true"
98
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: banner.message })]
99
+ }),
100
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
101
+ className: "nb-feature-hub__content",
102
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Outlet, {})
103
+ })
104
+ ]
105
+ });
106
+ }
107
+ //#endregion
33
108
  //#region packages/admin/AdminHeader.tsx
34
109
  function ActionPopover({ action }) {
35
110
  const { open, toggle, setOpen, containerRef } = (0, _nubitio_ui.useFloatingPanel)();
@@ -972,6 +1047,158 @@ function ToastHost({ toasts, onDismiss }) {
972
1047
  });
973
1048
  }
974
1049
  //#endregion
1050
+ //#region packages/admin/devtools/NubitDevToolsPanel.tsx
1051
+ const PROVIDER_TREE = [
1052
+ "MercureProvider",
1053
+ "SchemaProvider",
1054
+ "HydraResourceSchemaProvider",
1055
+ "HydraResourceStoreProvider",
1056
+ "SmartCrudRolesProvider",
1057
+ "CoreProvider"
1058
+ ];
1059
+ function NubitDevToolsPanel() {
1060
+ const { enabled, resources, clearResources } = (0, _nubitio_crud.useDevTools)();
1061
+ const [open, setOpen] = (0, react.useState)(false);
1062
+ const [expandedUrl, setExpandedUrl] = (0, react.useState)(null);
1063
+ if (!enabled || !(0, _nubitio_crud.isDevEnvironment)()) return null;
1064
+ const active = expandedUrl ? resources.find((r) => r.apiUrl === expandedUrl) : resources[0];
1065
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1066
+ className: `nb-devtools ${open ? "nb-devtools--open" : ""}`,
1067
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1068
+ type: "button",
1069
+ className: "nb-devtools__toggle",
1070
+ onClick: () => setOpen((v) => !v),
1071
+ "aria-expanded": open,
1072
+ title: "Nubit DevTools",
1073
+ children: "Nubit"
1074
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1075
+ className: "nb-devtools__panel",
1076
+ role: "complementary",
1077
+ "aria-label": "Nubit DevTools",
1078
+ children: [
1079
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
1080
+ className: "nb-devtools__header",
1081
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Nubit DevTools" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1082
+ type: "button",
1083
+ className: "nb-devtools__clear",
1084
+ onClick: clearResources,
1085
+ children: "Clear"
1086
+ })]
1087
+ }),
1088
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1089
+ className: "nb-devtools__section",
1090
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: "Provider tree" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1091
+ className: "nb-devtools__providers",
1092
+ children: PROVIDER_TREE.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: name }, name))
1093
+ })]
1094
+ }),
1095
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1096
+ className: "nb-devtools__section",
1097
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h4", { children: [
1098
+ "Active resources (",
1099
+ resources.length,
1100
+ ")"
1101
+ ] }), resources.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1102
+ className: "nb-devtools__empty",
1103
+ children: "Open a SchemaCrudPage to inspect field resolution."
1104
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1105
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
1106
+ className: "nb-devtools__select",
1107
+ value: active?.apiUrl ?? "",
1108
+ onChange: (e) => setExpandedUrl(e.target.value),
1109
+ children: resources.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1110
+ value: r.apiUrl,
1111
+ children: r.apiUrl
1112
+ }, r.apiUrl))
1113
+ }),
1114
+ active && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
1115
+ className: "nb-devtools__meta",
1116
+ children: [
1117
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "Source" }),
1118
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: active.source }),
1119
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "Operations" }),
1120
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: active.supportedOperations.join(", ") || "—" }),
1121
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "Permissions" }),
1122
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: active.permissionsSource ?? "—" })
1123
+ ]
1124
+ }),
1125
+ active?.formDetail && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
1126
+ className: "nb-devtools__subsection",
1127
+ children: "Form detail (lines)"
1128
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
1129
+ className: "nb-devtools__meta",
1130
+ children: [
1131
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "Line fields" }),
1132
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: active.formDetail.fieldSource }),
1133
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "Property" }),
1134
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: active.formDetail.propertyName ?? "—" }),
1135
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "Reload URL" }),
1136
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
1137
+ className: "nb-devtools__mapping",
1138
+ children: active.formDetail.reloadUrl ?? "—"
1139
+ }),
1140
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "Line class" }),
1141
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: active.formDetail.lineClass ?? "—" }),
1142
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: "x-embedded-lines" }),
1143
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: active.formDetail.embeddedLinesCount })
1144
+ ]
1145
+ })] }),
1146
+ active && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
1147
+ className: "nb-devtools__subsection",
1148
+ children: "Grid fields"
1149
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1150
+ className: "nb-devtools__table",
1151
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1152
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Field" }),
1153
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Type" }),
1154
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Mapping" })
1155
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: active.fields.filter((f) => !f.isIdentity).map((f) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1156
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: f.name }),
1157
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: f.type }),
1158
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1159
+ className: "nb-devtools__mapping",
1160
+ children: f.mappingReason ?? "—"
1161
+ })
1162
+ ] }, f.name)) })]
1163
+ })] }),
1164
+ active?.formDetail && active.formDetail.lineFieldCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h4", {
1165
+ className: "nb-devtools__subsection",
1166
+ children: [
1167
+ "Line fields (",
1168
+ active.formDetail.lineFieldCount,
1169
+ ")"
1170
+ ]
1171
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1172
+ className: "nb-devtools__table",
1173
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1174
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Field" }),
1175
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Type" }),
1176
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Mapping" })
1177
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: active.formDetail.lineFields.filter((f) => !f.isIdentity).map((f) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1178
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: f.name }),
1179
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: f.type }),
1180
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1181
+ className: "nb-devtools__mapping",
1182
+ children: f.mappingReason ?? "—"
1183
+ })
1184
+ ] }, f.name)) })]
1185
+ })] })
1186
+ ] })]
1187
+ }),
1188
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("footer", {
1189
+ className: "nb-devtools__footer",
1190
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1191
+ href: "https://github.com/nubitio/nubit-react/blob/main/docs/architecture/README.md",
1192
+ target: "_blank",
1193
+ rel: "noreferrer",
1194
+ children: "Architecture docs"
1195
+ })
1196
+ })
1197
+ ]
1198
+ })]
1199
+ });
1200
+ }
1201
+ //#endregion
975
1202
  //#region packages/admin/app/filterMenuByRoles.ts
976
1203
  function hasAnyRole(required, roles) {
977
1204
  if (required === void 0) return true;
@@ -1092,24 +1319,32 @@ function NubitAuthenticatedApp({ config }) {
1092
1319
  element: route.element
1093
1320
  }, route.path))] })
1094
1321
  });
1095
- const authenticated = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_core.CoreProvider, {
1096
- http: {
1097
- baseUrl: apiBaseUrl,
1098
- refreshPath: "auth/refresh",
1099
- loginPath: "auth/login"
1100
- },
1101
- runtime,
1102
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_core.CoreConfigProvider, {
1103
- apiBaseUrl,
1104
- locale: config.locale ?? "en",
1105
- timezone: config.timezone ?? "UTC",
1106
- currency: config.currency ?? "USD",
1107
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_nubitio_crud.SmartCrudRolesProvider, {
1108
- roles: session.roles,
1109
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.BrowserRouter, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Wrapper, { children: config.hydra === false ? shell : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_core.MercureProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_hydra.SchemaProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_hydra.HydraResourceSchemaProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_hydra.HydraResourceStoreProvider, { children: shell }) }) }) }) }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToastHost, {
1110
- toasts,
1111
- onDismiss: dismiss
1112
- })]
1322
+ const devToolsEnabled = config.devTools ?? (0, _nubitio_crud.isDevEnvironment)();
1323
+ const authenticated = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_crud.DevToolsProvider, {
1324
+ enabled: devToolsEnabled,
1325
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_core.CoreProvider, {
1326
+ http: {
1327
+ baseUrl: apiBaseUrl,
1328
+ refreshPath: "auth/refresh",
1329
+ loginPath: "auth/login"
1330
+ },
1331
+ runtime,
1332
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_core.CoreConfigProvider, {
1333
+ apiBaseUrl,
1334
+ locale: config.locale ?? "en",
1335
+ timezone: config.timezone ?? "UTC",
1336
+ currency: config.currency ?? "USD",
1337
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_nubitio_crud.SmartCrudRolesProvider, {
1338
+ roles: session.roles,
1339
+ children: [
1340
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.BrowserRouter, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Wrapper, { children: config.hydra === false ? shell : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_core.MercureProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_hydra.SchemaProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_hydra.HydraResourceSchemaProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_hydra.HydraResourceStoreProvider, { children: shell }) }) }) }) }) }),
1341
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToastHost, {
1342
+ toasts,
1343
+ onDismiss: dismiss
1344
+ }),
1345
+ devToolsEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NubitDevToolsPanel, {})
1346
+ ]
1347
+ })
1113
1348
  })
1114
1349
  })
1115
1350
  });
@@ -1144,7 +1379,9 @@ exports.AdminHeader = AdminHeader;
1144
1379
  exports.AdminShell = AdminShell;
1145
1380
  exports.AdminSidebarMenu = AdminSidebarMenu;
1146
1381
  exports.FeatureGate = FeatureGate;
1382
+ exports.FeatureHubLayout = FeatureHubLayout;
1147
1383
  exports.LoginPage = LoginPage;
1384
+ exports.NubitDevToolsPanel = NubitDevToolsPanel;
1148
1385
  exports.PlanPanel = PlanPanel;
1149
1386
  exports.QuotaUsageBanner = QuotaUsageBanner;
1150
1387
  exports.RegisterPage = RegisterPage;
package/dist/index.d.cts CHANGED
@@ -3,6 +3,48 @@ import { FeatureGateProps as FeatureGateProps$1 } from "@nubitio/ui";
3
3
  import { CoreRuntime } from "@nubitio/core";
4
4
  import { QueryClient } from "@tanstack/react-query";
5
5
 
6
+ //#region packages/admin/FeatureHubLayout.d.ts
7
+ interface FeatureHubTab {
8
+ key: string;
9
+ label: string;
10
+ path: string;
11
+ icon?: string;
12
+ visible?: boolean;
13
+ }
14
+ interface FeatureHubBanner {
15
+ message: ReactNode;
16
+ tone?: 'info' | 'warning';
17
+ icon?: string;
18
+ visible?: boolean;
19
+ /** When false, compact density hides the banner to preserve grid space. Default: false */
20
+ showInCompact?: boolean;
21
+ }
22
+ type FeatureHubDensity = 'default' | 'compact';
23
+ interface FeatureHubLayoutProps {
24
+ title: string;
25
+ subtitle?: ReactNode;
26
+ tabs: FeatureHubTab[];
27
+ /** Redirect here when pathname equals basePath exactly. */
28
+ basePath: string;
29
+ defaultPath: string;
30
+ banner?: FeatureHubBanner;
31
+ tabsAriaLabel?: string;
32
+ /** Compact merges title + tabs on one row and hides the subtitle. */
33
+ density?: FeatureHubDensity;
34
+ className?: string;
35
+ }
36
+ declare function FeatureHubLayout({
37
+ title,
38
+ subtitle,
39
+ tabs,
40
+ basePath,
41
+ defaultPath,
42
+ banner,
43
+ tabsAriaLabel,
44
+ density,
45
+ className
46
+ }: FeatureHubLayoutProps): import("react").JSX.Element;
47
+ //#endregion
6
48
  //#region packages/admin/AdminHeader.d.ts
7
49
  interface AdminHeaderAction {
8
50
  id: string;
@@ -428,6 +470,11 @@ type CreateNubitAppConfig = {
428
470
  }>;
429
471
  queryClient?: QueryClient; /** Register Mercure + Hydra schema/store providers. Default true. */
430
472
  hydra?: boolean;
473
+ /**
474
+ * Show the Nubit DevTools panel (field mapping, provider tree).
475
+ * Default true on localhost; set false to disable.
476
+ */
477
+ devTools?: boolean;
431
478
  };
432
479
  type NubitApp = {
433
480
  App: React.ComponentType;
@@ -436,6 +483,9 @@ type NubitApp = {
436
483
  //#region packages/admin/app/createNubitApp.d.ts
437
484
  declare function createNubitApp(config: CreateNubitAppConfig): NubitApp;
438
485
  //#endregion
486
+ //#region packages/admin/devtools/NubitDevToolsPanel.d.ts
487
+ declare function NubitDevToolsPanel(): React$1.JSX.Element | null;
488
+ //#endregion
439
489
  //#region packages/admin/app/filterMenuByRoles.d.ts
440
490
  declare function hasAnyRole(required: string | string[] | undefined, roles: string[]): boolean;
441
491
  /**
@@ -444,4 +494,4 @@ declare function hasAnyRole(required: string | string[] | undefined, roles: stri
444
494
  */
445
495
  declare function filterMenuByRoles(items: NubitAppMenuItem[], roles: string[]): AdminMenuItem[];
446
496
  //#endregion
447
- export { AdminHeader, type AdminHeaderAction, type AdminHeaderProps, type AdminMenuItem, type AdminMenuSubItem, AdminShell, type AdminShellProps, AdminSidebarMenu, type AdminSidebarMenuProps, type AdminSidebarMenuSelectEvent, type AppProfile, type CreateNubitAppConfig, FeatureGate, type FeatureGateProps, LoginPage, type LoginPageProps, type NotificationType, type NubitApp, type NubitAppMenuContext, type NubitAppMenuItem, type NubitAppMenuSubItem, type NubitAppRoute, type NubitAppUserMenuContext, type ParsedQuotaError, type PlanDefinition, PlanPanel, type PlanPanelLabels, type PlanPanelProps, QuotaUsageBanner, type QuotaUsageBannerProps, type RegisterField, type RegisterFieldType, RegisterPage, type RegisterPageProps, type RegisterSelectOption, type RuntimeConfig, type RuntimeConfigState, type SessionContextValue, type SessionFeatureEntitlement, type SessionProfile, SessionProvider, type SessionProviderConfig, type SessionState, type SessionTenant, StaticSessionProvider, ToastHost, type ToastHostProps, type ToastItem, type UseQuotaUsageOptions, type UseRuntimeConfigOptions, createNubitApp, filterMenuByRoles, hasAnyRole, parseQuotaError, quotaErrorToastMessage, quotaUsageAboveGrid, useAppRuntime, useFeature, useFeatureConfig, useQuotaUsage, useRuntimeConfig, useScreenSize, useScreenSizeClass, useSession };
497
+ export { AdminHeader, type AdminHeaderAction, type AdminHeaderProps, type AdminMenuItem, type AdminMenuSubItem, AdminShell, type AdminShellProps, AdminSidebarMenu, type AdminSidebarMenuProps, type AdminSidebarMenuSelectEvent, type AppProfile, type CreateNubitAppConfig, FeatureGate, type FeatureGateProps, type FeatureHubBanner, type FeatureHubDensity, FeatureHubLayout, type FeatureHubLayoutProps, type FeatureHubTab, LoginPage, type LoginPageProps, type NotificationType, type NubitApp, type NubitAppMenuContext, type NubitAppMenuItem, type NubitAppMenuSubItem, type NubitAppRoute, type NubitAppUserMenuContext, NubitDevToolsPanel, type ParsedQuotaError, type PlanDefinition, PlanPanel, type PlanPanelLabels, type PlanPanelProps, QuotaUsageBanner, type QuotaUsageBannerProps, type RegisterField, type RegisterFieldType, RegisterPage, type RegisterPageProps, type RegisterSelectOption, type RuntimeConfig, type RuntimeConfigState, type SessionContextValue, type SessionFeatureEntitlement, type SessionProfile, SessionProvider, type SessionProviderConfig, type SessionState, type SessionTenant, StaticSessionProvider, ToastHost, type ToastHostProps, type ToastItem, type UseQuotaUsageOptions, type UseRuntimeConfigOptions, createNubitApp, filterMenuByRoles, hasAnyRole, parseQuotaError, quotaErrorToastMessage, quotaUsageAboveGrid, useAppRuntime, useFeature, useFeatureConfig, useQuotaUsage, useRuntimeConfig, useScreenSize, useScreenSizeClass, useSession };
package/dist/index.d.mts CHANGED
@@ -3,6 +3,48 @@ import { FeatureGateProps as FeatureGateProps$1 } from "@nubitio/ui";
3
3
  import { QueryClient } from "@tanstack/react-query";
4
4
  import { CoreRuntime } from "@nubitio/core";
5
5
 
6
+ //#region packages/admin/FeatureHubLayout.d.ts
7
+ interface FeatureHubTab {
8
+ key: string;
9
+ label: string;
10
+ path: string;
11
+ icon?: string;
12
+ visible?: boolean;
13
+ }
14
+ interface FeatureHubBanner {
15
+ message: ReactNode;
16
+ tone?: 'info' | 'warning';
17
+ icon?: string;
18
+ visible?: boolean;
19
+ /** When false, compact density hides the banner to preserve grid space. Default: false */
20
+ showInCompact?: boolean;
21
+ }
22
+ type FeatureHubDensity = 'default' | 'compact';
23
+ interface FeatureHubLayoutProps {
24
+ title: string;
25
+ subtitle?: ReactNode;
26
+ tabs: FeatureHubTab[];
27
+ /** Redirect here when pathname equals basePath exactly. */
28
+ basePath: string;
29
+ defaultPath: string;
30
+ banner?: FeatureHubBanner;
31
+ tabsAriaLabel?: string;
32
+ /** Compact merges title + tabs on one row and hides the subtitle. */
33
+ density?: FeatureHubDensity;
34
+ className?: string;
35
+ }
36
+ declare function FeatureHubLayout({
37
+ title,
38
+ subtitle,
39
+ tabs,
40
+ basePath,
41
+ defaultPath,
42
+ banner,
43
+ tabsAriaLabel,
44
+ density,
45
+ className
46
+ }: FeatureHubLayoutProps): import("react").JSX.Element;
47
+ //#endregion
6
48
  //#region packages/admin/AdminHeader.d.ts
7
49
  interface AdminHeaderAction {
8
50
  id: string;
@@ -428,6 +470,11 @@ type CreateNubitAppConfig = {
428
470
  }>;
429
471
  queryClient?: QueryClient; /** Register Mercure + Hydra schema/store providers. Default true. */
430
472
  hydra?: boolean;
473
+ /**
474
+ * Show the Nubit DevTools panel (field mapping, provider tree).
475
+ * Default true on localhost; set false to disable.
476
+ */
477
+ devTools?: boolean;
431
478
  };
432
479
  type NubitApp = {
433
480
  App: React.ComponentType;
@@ -436,6 +483,9 @@ type NubitApp = {
436
483
  //#region packages/admin/app/createNubitApp.d.ts
437
484
  declare function createNubitApp(config: CreateNubitAppConfig): NubitApp;
438
485
  //#endregion
486
+ //#region packages/admin/devtools/NubitDevToolsPanel.d.ts
487
+ declare function NubitDevToolsPanel(): React$1.JSX.Element | null;
488
+ //#endregion
439
489
  //#region packages/admin/app/filterMenuByRoles.d.ts
440
490
  declare function hasAnyRole(required: string | string[] | undefined, roles: string[]): boolean;
441
491
  /**
@@ -444,4 +494,4 @@ declare function hasAnyRole(required: string | string[] | undefined, roles: stri
444
494
  */
445
495
  declare function filterMenuByRoles(items: NubitAppMenuItem[], roles: string[]): AdminMenuItem[];
446
496
  //#endregion
447
- export { AdminHeader, type AdminHeaderAction, type AdminHeaderProps, type AdminMenuItem, type AdminMenuSubItem, AdminShell, type AdminShellProps, AdminSidebarMenu, type AdminSidebarMenuProps, type AdminSidebarMenuSelectEvent, type AppProfile, type CreateNubitAppConfig, FeatureGate, type FeatureGateProps, LoginPage, type LoginPageProps, type NotificationType, type NubitApp, type NubitAppMenuContext, type NubitAppMenuItem, type NubitAppMenuSubItem, type NubitAppRoute, type NubitAppUserMenuContext, type ParsedQuotaError, type PlanDefinition, PlanPanel, type PlanPanelLabels, type PlanPanelProps, QuotaUsageBanner, type QuotaUsageBannerProps, type RegisterField, type RegisterFieldType, RegisterPage, type RegisterPageProps, type RegisterSelectOption, type RuntimeConfig, type RuntimeConfigState, type SessionContextValue, type SessionFeatureEntitlement, type SessionProfile, SessionProvider, type SessionProviderConfig, type SessionState, type SessionTenant, StaticSessionProvider, ToastHost, type ToastHostProps, type ToastItem, type UseQuotaUsageOptions, type UseRuntimeConfigOptions, createNubitApp, filterMenuByRoles, hasAnyRole, parseQuotaError, quotaErrorToastMessage, quotaUsageAboveGrid, useAppRuntime, useFeature, useFeatureConfig, useQuotaUsage, useRuntimeConfig, useScreenSize, useScreenSizeClass, useSession };
497
+ export { AdminHeader, type AdminHeaderAction, type AdminHeaderProps, type AdminMenuItem, type AdminMenuSubItem, AdminShell, type AdminShellProps, AdminSidebarMenu, type AdminSidebarMenuProps, type AdminSidebarMenuSelectEvent, type AppProfile, type CreateNubitAppConfig, FeatureGate, type FeatureGateProps, type FeatureHubBanner, type FeatureHubDensity, FeatureHubLayout, type FeatureHubLayoutProps, type FeatureHubTab, LoginPage, type LoginPageProps, type NotificationType, type NubitApp, type NubitAppMenuContext, type NubitAppMenuItem, type NubitAppMenuSubItem, type NubitAppRoute, type NubitAppUserMenuContext, NubitDevToolsPanel, type ParsedQuotaError, type PlanDefinition, PlanPanel, type PlanPanelLabels, type PlanPanelProps, QuotaUsageBanner, type QuotaUsageBannerProps, type RegisterField, type RegisterFieldType, RegisterPage, type RegisterPageProps, type RegisterSelectOption, type RuntimeConfig, type RuntimeConfigState, type SessionContextValue, type SessionFeatureEntitlement, type SessionProfile, SessionProvider, type SessionProviderConfig, type SessionState, type SessionTenant, StaticSessionProvider, ToastHost, type ToastHostProps, type ToastItem, type UseQuotaUsageOptions, type UseRuntimeConfigOptions, createNubitApp, filterMenuByRoles, hasAnyRole, parseQuotaError, quotaErrorToastMessage, quotaUsageAboveGrid, useAppRuntime, useFeature, useFeatureConfig, useQuotaUsage, useRuntimeConfig, useScreenSize, useScreenSizeClass, useSession };
package/dist/index.mjs CHANGED
@@ -1,11 +1,86 @@
1
+ import { BrowserRouter, Link, NavLink, Navigate, Outlet, Route, Routes, useLocation, useNavigate } from "react-router-dom";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1
3
  import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
2
- import { BrowserRouter, Link, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom";
3
4
  import { Badge, Button, Card, FeatureGate as FeatureGate$1, IconButton, TextField, ThemeProvider, ThemeSwitcher, useFloatingPanel } from "@nubitio/ui";
4
- import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query";
6
6
  import { CoreConfigProvider, CoreProvider, MercureProvider, useCoreHttpClient } from "@nubitio/core";
7
- import { SmartCrudRolesProvider } from "@nubitio/crud";
7
+ import { DevToolsProvider, SmartCrudRolesProvider, isDevEnvironment, useDevTools } from "@nubitio/crud";
8
8
  import { HydraResourceSchemaProvider, HydraResourceStoreProvider, SchemaProvider } from "@nubitio/hydra";
9
+ //#region packages/admin/FeatureHubLayout.tsx
10
+ function normalizeIcon(icon) {
11
+ if (!icon) return void 0;
12
+ if (icon.startsWith("ph ")) return icon;
13
+ if (icon.startsWith("ph-")) return `ph ${icon}`;
14
+ return `ph ph-${icon}`;
15
+ }
16
+ function normalizeBasePath(path) {
17
+ return path.endsWith("/") && path.length > 1 ? path.slice(0, -1) : path;
18
+ }
19
+ function FeatureHubLayout({ title, subtitle, tabs, basePath, defaultPath, banner, tabsAriaLabel = "Secciones", density = "default", className = "" }) {
20
+ const location = useLocation();
21
+ const normalizedBase = normalizeBasePath(basePath);
22
+ const visibleTabs = tabs.filter((tab) => tab.visible !== false);
23
+ const compact = density === "compact";
24
+ const densityClass = compact ? " nb-feature-hub--compact" : "";
25
+ const showBanner = banner && banner.visible !== false && (!compact || banner.showInCompact === true);
26
+ if (location.pathname === normalizedBase || location.pathname === `${normalizedBase}/`) return /* @__PURE__ */ jsx(Navigate, {
27
+ to: defaultPath,
28
+ replace: true
29
+ });
30
+ const tabsNav = /* @__PURE__ */ jsx("nav", {
31
+ className: "nb-feature-hub__tabs",
32
+ "aria-label": tabsAriaLabel,
33
+ children: visibleTabs.map((tab) => {
34
+ const iconClass = normalizeIcon(tab.icon);
35
+ return /* @__PURE__ */ jsxs(NavLink, {
36
+ to: tab.path,
37
+ className: ({ isActive }) => `nb-feature-hub__tab${isActive ? " nb-feature-hub__tab--active" : ""}`,
38
+ children: [iconClass && /* @__PURE__ */ jsx("i", {
39
+ className: iconClass,
40
+ "aria-hidden": "true"
41
+ }), tab.label]
42
+ }, tab.key);
43
+ })
44
+ });
45
+ return /* @__PURE__ */ jsxs("div", {
46
+ className: `nb-feature-hub${densityClass}${className ? ` ${className}` : ""}`,
47
+ children: [
48
+ /* @__PURE__ */ jsx("header", {
49
+ className: "nb-feature-hub__header",
50
+ children: compact ? /* @__PURE__ */ jsxs("div", {
51
+ className: "nb-feature-hub__tabs-card",
52
+ children: [/* @__PURE__ */ jsx("h2", {
53
+ className: "nb-feature-hub__title",
54
+ children: title
55
+ }), tabsNav]
56
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
57
+ /* @__PURE__ */ jsx("h2", {
58
+ className: "nb-feature-hub__title",
59
+ children: title
60
+ }),
61
+ subtitle && /* @__PURE__ */ jsx("p", {
62
+ className: "nb-feature-hub__subtitle",
63
+ children: subtitle
64
+ }),
65
+ tabsNav
66
+ ] })
67
+ }),
68
+ showBanner && /* @__PURE__ */ jsxs("div", {
69
+ className: `nb-feature-hub__banner nb-feature-hub__banner--${banner.tone ?? "info"}`,
70
+ role: "status",
71
+ children: [/* @__PURE__ */ jsx("i", {
72
+ className: normalizeIcon(banner.icon ?? "ph-info") ?? "ph ph-info",
73
+ "aria-hidden": "true"
74
+ }), /* @__PURE__ */ jsx("span", { children: banner.message })]
75
+ }),
76
+ /* @__PURE__ */ jsx("div", {
77
+ className: "nb-feature-hub__content",
78
+ children: /* @__PURE__ */ jsx(Outlet, {})
79
+ })
80
+ ]
81
+ });
82
+ }
83
+ //#endregion
9
84
  //#region packages/admin/AdminHeader.tsx
10
85
  function ActionPopover({ action }) {
11
86
  const { open, toggle, setOpen, containerRef } = useFloatingPanel();
@@ -948,6 +1023,158 @@ function ToastHost({ toasts, onDismiss }) {
948
1023
  });
949
1024
  }
950
1025
  //#endregion
1026
+ //#region packages/admin/devtools/NubitDevToolsPanel.tsx
1027
+ const PROVIDER_TREE = [
1028
+ "MercureProvider",
1029
+ "SchemaProvider",
1030
+ "HydraResourceSchemaProvider",
1031
+ "HydraResourceStoreProvider",
1032
+ "SmartCrudRolesProvider",
1033
+ "CoreProvider"
1034
+ ];
1035
+ function NubitDevToolsPanel() {
1036
+ const { enabled, resources, clearResources } = useDevTools();
1037
+ const [open, setOpen] = useState(false);
1038
+ const [expandedUrl, setExpandedUrl] = useState(null);
1039
+ if (!enabled || !isDevEnvironment()) return null;
1040
+ const active = expandedUrl ? resources.find((r) => r.apiUrl === expandedUrl) : resources[0];
1041
+ return /* @__PURE__ */ jsxs("div", {
1042
+ className: `nb-devtools ${open ? "nb-devtools--open" : ""}`,
1043
+ children: [/* @__PURE__ */ jsx("button", {
1044
+ type: "button",
1045
+ className: "nb-devtools__toggle",
1046
+ onClick: () => setOpen((v) => !v),
1047
+ "aria-expanded": open,
1048
+ title: "Nubit DevTools",
1049
+ children: "Nubit"
1050
+ }), open && /* @__PURE__ */ jsxs("div", {
1051
+ className: "nb-devtools__panel",
1052
+ role: "complementary",
1053
+ "aria-label": "Nubit DevTools",
1054
+ children: [
1055
+ /* @__PURE__ */ jsxs("header", {
1056
+ className: "nb-devtools__header",
1057
+ children: [/* @__PURE__ */ jsx("strong", { children: "Nubit DevTools" }), /* @__PURE__ */ jsx("button", {
1058
+ type: "button",
1059
+ className: "nb-devtools__clear",
1060
+ onClick: clearResources,
1061
+ children: "Clear"
1062
+ })]
1063
+ }),
1064
+ /* @__PURE__ */ jsxs("section", {
1065
+ className: "nb-devtools__section",
1066
+ children: [/* @__PURE__ */ jsx("h4", { children: "Provider tree" }), /* @__PURE__ */ jsx("ul", {
1067
+ className: "nb-devtools__providers",
1068
+ children: PROVIDER_TREE.map((name) => /* @__PURE__ */ jsx("li", { children: name }, name))
1069
+ })]
1070
+ }),
1071
+ /* @__PURE__ */ jsxs("section", {
1072
+ className: "nb-devtools__section",
1073
+ children: [/* @__PURE__ */ jsxs("h4", { children: [
1074
+ "Active resources (",
1075
+ resources.length,
1076
+ ")"
1077
+ ] }), resources.length === 0 ? /* @__PURE__ */ jsx("p", {
1078
+ className: "nb-devtools__empty",
1079
+ children: "Open a SchemaCrudPage to inspect field resolution."
1080
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1081
+ /* @__PURE__ */ jsx("select", {
1082
+ className: "nb-devtools__select",
1083
+ value: active?.apiUrl ?? "",
1084
+ onChange: (e) => setExpandedUrl(e.target.value),
1085
+ children: resources.map((r) => /* @__PURE__ */ jsx("option", {
1086
+ value: r.apiUrl,
1087
+ children: r.apiUrl
1088
+ }, r.apiUrl))
1089
+ }),
1090
+ active && /* @__PURE__ */ jsxs("dl", {
1091
+ className: "nb-devtools__meta",
1092
+ children: [
1093
+ /* @__PURE__ */ jsx("dt", { children: "Source" }),
1094
+ /* @__PURE__ */ jsx("dd", { children: active.source }),
1095
+ /* @__PURE__ */ jsx("dt", { children: "Operations" }),
1096
+ /* @__PURE__ */ jsx("dd", { children: active.supportedOperations.join(", ") || "—" }),
1097
+ /* @__PURE__ */ jsx("dt", { children: "Permissions" }),
1098
+ /* @__PURE__ */ jsx("dd", { children: active.permissionsSource ?? "—" })
1099
+ ]
1100
+ }),
1101
+ active?.formDetail && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("h4", {
1102
+ className: "nb-devtools__subsection",
1103
+ children: "Form detail (lines)"
1104
+ }), /* @__PURE__ */ jsxs("dl", {
1105
+ className: "nb-devtools__meta",
1106
+ children: [
1107
+ /* @__PURE__ */ jsx("dt", { children: "Line fields" }),
1108
+ /* @__PURE__ */ jsx("dd", { children: active.formDetail.fieldSource }),
1109
+ /* @__PURE__ */ jsx("dt", { children: "Property" }),
1110
+ /* @__PURE__ */ jsx("dd", { children: active.formDetail.propertyName ?? "—" }),
1111
+ /* @__PURE__ */ jsx("dt", { children: "Reload URL" }),
1112
+ /* @__PURE__ */ jsx("dd", {
1113
+ className: "nb-devtools__mapping",
1114
+ children: active.formDetail.reloadUrl ?? "—"
1115
+ }),
1116
+ /* @__PURE__ */ jsx("dt", { children: "Line class" }),
1117
+ /* @__PURE__ */ jsx("dd", { children: active.formDetail.lineClass ?? "—" }),
1118
+ /* @__PURE__ */ jsx("dt", { children: "x-embedded-lines" }),
1119
+ /* @__PURE__ */ jsx("dd", { children: active.formDetail.embeddedLinesCount })
1120
+ ]
1121
+ })] }),
1122
+ active && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("h4", {
1123
+ className: "nb-devtools__subsection",
1124
+ children: "Grid fields"
1125
+ }), /* @__PURE__ */ jsxs("table", {
1126
+ className: "nb-devtools__table",
1127
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1128
+ /* @__PURE__ */ jsx("th", { children: "Field" }),
1129
+ /* @__PURE__ */ jsx("th", { children: "Type" }),
1130
+ /* @__PURE__ */ jsx("th", { children: "Mapping" })
1131
+ ] }) }), /* @__PURE__ */ jsx("tbody", { children: active.fields.filter((f) => !f.isIdentity).map((f) => /* @__PURE__ */ jsxs("tr", { children: [
1132
+ /* @__PURE__ */ jsx("td", { children: f.name }),
1133
+ /* @__PURE__ */ jsx("td", { children: f.type }),
1134
+ /* @__PURE__ */ jsx("td", {
1135
+ className: "nb-devtools__mapping",
1136
+ children: f.mappingReason ?? "—"
1137
+ })
1138
+ ] }, f.name)) })]
1139
+ })] }),
1140
+ active?.formDetail && active.formDetail.lineFieldCount > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("h4", {
1141
+ className: "nb-devtools__subsection",
1142
+ children: [
1143
+ "Line fields (",
1144
+ active.formDetail.lineFieldCount,
1145
+ ")"
1146
+ ]
1147
+ }), /* @__PURE__ */ jsxs("table", {
1148
+ className: "nb-devtools__table",
1149
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1150
+ /* @__PURE__ */ jsx("th", { children: "Field" }),
1151
+ /* @__PURE__ */ jsx("th", { children: "Type" }),
1152
+ /* @__PURE__ */ jsx("th", { children: "Mapping" })
1153
+ ] }) }), /* @__PURE__ */ jsx("tbody", { children: active.formDetail.lineFields.filter((f) => !f.isIdentity).map((f) => /* @__PURE__ */ jsxs("tr", { children: [
1154
+ /* @__PURE__ */ jsx("td", { children: f.name }),
1155
+ /* @__PURE__ */ jsx("td", { children: f.type }),
1156
+ /* @__PURE__ */ jsx("td", {
1157
+ className: "nb-devtools__mapping",
1158
+ children: f.mappingReason ?? "—"
1159
+ })
1160
+ ] }, f.name)) })]
1161
+ })] })
1162
+ ] })]
1163
+ }),
1164
+ /* @__PURE__ */ jsx("footer", {
1165
+ className: "nb-devtools__footer",
1166
+ children: /* @__PURE__ */ jsx("a", {
1167
+ href: "https://github.com/nubitio/nubit-react/blob/main/docs/architecture/README.md",
1168
+ target: "_blank",
1169
+ rel: "noreferrer",
1170
+ children: "Architecture docs"
1171
+ })
1172
+ })
1173
+ ]
1174
+ })]
1175
+ });
1176
+ }
1177
+ //#endregion
951
1178
  //#region packages/admin/app/filterMenuByRoles.ts
952
1179
  function hasAnyRole(required, roles) {
953
1180
  if (required === void 0) return true;
@@ -1068,24 +1295,32 @@ function NubitAuthenticatedApp({ config }) {
1068
1295
  element: route.element
1069
1296
  }, route.path))] })
1070
1297
  });
1071
- const authenticated = /* @__PURE__ */ jsx(CoreProvider, {
1072
- http: {
1073
- baseUrl: apiBaseUrl,
1074
- refreshPath: "auth/refresh",
1075
- loginPath: "auth/login"
1076
- },
1077
- runtime,
1078
- children: /* @__PURE__ */ jsx(CoreConfigProvider, {
1079
- apiBaseUrl,
1080
- locale: config.locale ?? "en",
1081
- timezone: config.timezone ?? "UTC",
1082
- currency: config.currency ?? "USD",
1083
- children: /* @__PURE__ */ jsxs(SmartCrudRolesProvider, {
1084
- roles: session.roles,
1085
- children: [/* @__PURE__ */ jsx(BrowserRouter, { children: /* @__PURE__ */ jsx(Wrapper, { children: config.hydra === false ? shell : /* @__PURE__ */ jsx(MercureProvider, { children: /* @__PURE__ */ jsx(SchemaProvider, { children: /* @__PURE__ */ jsx(HydraResourceSchemaProvider, { children: /* @__PURE__ */ jsx(HydraResourceStoreProvider, { children: shell }) }) }) }) }) }), /* @__PURE__ */ jsx(ToastHost, {
1086
- toasts,
1087
- onDismiss: dismiss
1088
- })]
1298
+ const devToolsEnabled = config.devTools ?? isDevEnvironment();
1299
+ const authenticated = /* @__PURE__ */ jsx(DevToolsProvider, {
1300
+ enabled: devToolsEnabled,
1301
+ children: /* @__PURE__ */ jsx(CoreProvider, {
1302
+ http: {
1303
+ baseUrl: apiBaseUrl,
1304
+ refreshPath: "auth/refresh",
1305
+ loginPath: "auth/login"
1306
+ },
1307
+ runtime,
1308
+ children: /* @__PURE__ */ jsx(CoreConfigProvider, {
1309
+ apiBaseUrl,
1310
+ locale: config.locale ?? "en",
1311
+ timezone: config.timezone ?? "UTC",
1312
+ currency: config.currency ?? "USD",
1313
+ children: /* @__PURE__ */ jsxs(SmartCrudRolesProvider, {
1314
+ roles: session.roles,
1315
+ children: [
1316
+ /* @__PURE__ */ jsx(BrowserRouter, { children: /* @__PURE__ */ jsx(Wrapper, { children: config.hydra === false ? shell : /* @__PURE__ */ jsx(MercureProvider, { children: /* @__PURE__ */ jsx(SchemaProvider, { children: /* @__PURE__ */ jsx(HydraResourceSchemaProvider, { children: /* @__PURE__ */ jsx(HydraResourceStoreProvider, { children: shell }) }) }) }) }) }),
1317
+ /* @__PURE__ */ jsx(ToastHost, {
1318
+ toasts,
1319
+ onDismiss: dismiss
1320
+ }),
1321
+ devToolsEnabled && /* @__PURE__ */ jsx(NubitDevToolsPanel, {})
1322
+ ]
1323
+ })
1089
1324
  })
1090
1325
  })
1091
1326
  });
@@ -1116,4 +1351,4 @@ function createNubitApp(config) {
1116
1351
  return { App };
1117
1352
  }
1118
1353
  //#endregion
1119
- export { AdminHeader, AdminShell, AdminSidebarMenu, FeatureGate, LoginPage, PlanPanel, QuotaUsageBanner, RegisterPage, SessionProvider, StaticSessionProvider, ToastHost, createNubitApp, filterMenuByRoles, hasAnyRole, parseQuotaError, quotaErrorToastMessage, quotaUsageAboveGrid, useAppRuntime, useFeature, useFeatureConfig, useQuotaUsage, useRuntimeConfig, useScreenSize, useScreenSizeClass, useSession };
1354
+ export { AdminHeader, AdminShell, AdminSidebarMenu, FeatureGate, FeatureHubLayout, LoginPage, NubitDevToolsPanel, PlanPanel, QuotaUsageBanner, RegisterPage, SessionProvider, StaticSessionProvider, ToastHost, createNubitApp, filterMenuByRoles, hasAnyRole, parseQuotaError, quotaErrorToastMessage, quotaUsageAboveGrid, useAppRuntime, useFeature, useFeatureConfig, useQuotaUsage, useRuntimeConfig, useScreenSize, useScreenSizeClass, useSession };
package/dist/style.css CHANGED
@@ -1,3 +1,290 @@
1
+ /* stylelint-disable */
2
+ :root {
3
+ --font-family-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
4
+ --font-family-mono: 'JetBrains Mono', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', monospace;
5
+ --font-family-display: 'Syne', var(--font-family-sans);
6
+ --font-size-xxs: 11px;
7
+ --font-size-xs: 12px;
8
+ --font-size-sm: 13px;
9
+ --font-size-md: 14px;
10
+ --font-size-lg: 16px;
11
+ --font-size-xl: 20px;
12
+ --font-size-2xl: 24px;
13
+ --font-size-3xl: 28px;
14
+ --font-size-4xl: 32px;
15
+ --font-size-5xl: 40px;
16
+ --font-weight-regular: 400;
17
+ --font-weight-medium: 500;
18
+ --font-weight-semibold: 600;
19
+ --font-weight-bold: 700;
20
+ --line-height-tight: 1.25;
21
+ --line-height-normal: 1.5;
22
+ --line-height-relaxed: 1.65;
23
+ --space-1: 4px;
24
+ --space-2: 8px;
25
+ --space-3: 12px;
26
+ --space-4: 16px;
27
+ --space-5: 20px;
28
+ --space-6: 24px;
29
+ --space-7: 32px;
30
+ --space-8: 40px;
31
+ --space-9: 48px;
32
+ --space-10: 64px;
33
+ --radius-xs: 3px;
34
+ --radius-sm: 4px;
35
+ --radius-md: 6px;
36
+ --radius-lg: 8px;
37
+ --radius-xl: 14px;
38
+ --toolbar-height: 36px;
39
+ --row-height: 34px;
40
+ --control-height: 28px;
41
+ --control-height-form: 34px;
42
+ --control-icon-size: 20px;
43
+ --footer-height: 48px;
44
+ --side-panel-min-width: 48px;
45
+ --content-padding: 16px;
46
+ --page-padding: 24px;
47
+ --calendar-width: 292px;
48
+ --toolbar-margin-bottom: var(--content-padding);
49
+ --footer-left-padding: calc((var(--side-panel-min-width) - 16px) / 2);
50
+ --grid-header-height: 34px;
51
+ --grid-filter-height: 28px;
52
+ --grid-cell-padding-x: 10px;
53
+ --grid-cell-padding-y: 6px;
54
+ --grid-row-line-height: 1.35;
55
+ --overlay-bg: rgb(0 0 0 / 42%);
56
+ --transition-fast: 100ms ease;
57
+ --transition-base: 150ms ease;
58
+ --transition-slow: 250ms cubic-bezier(0.4, 0, 0.2, 1);
59
+ --z-dropdown: 100;
60
+ --z-sticky: 200;
61
+ --z-overlay: 300;
62
+ --z-modal: 400;
63
+ --z-popover: 500;
64
+ --z-toast: 600;
65
+ --z-shell-panel: 1500;
66
+ --z-shell-scrim: 1499;
67
+ --z-shell-header: 1501;
68
+ --z-shell-popover: 1700;
69
+ --z-shell-dropdown: 1600;
70
+ --z-dialog: 2000;
71
+ --z-picker-panel: 3000;
72
+ --list-padding-left: 8px;
73
+ --list-padding-vertical: 4px;
74
+ --popup-toolbar-item-spacing: 8px;
75
+ --drop-down-gnrc-button-height: 24px;
76
+ --theme-padding: 8px;
77
+ --button-height-theme-difference: calc((var(--theme-padding) - 10px) / 3);
78
+ --button-height: calc(26px + var(--button-height-theme-difference));
79
+ --nb-admin-header-toolbar-vertical-padding: 10px;
80
+ --toolbar-vertical-padding: 16px;
81
+ }
82
+
83
+ html[data-density=compact] {
84
+ --font-size-xs: 11px;
85
+ --font-size-sm: 12px;
86
+ --font-size-md: 13px;
87
+ --space-1: 2px;
88
+ --space-2: 5px;
89
+ --space-3: 8px;
90
+ --space-4: 12px;
91
+ --space-5: 16px;
92
+ --space-6: 20px;
93
+ --toolbar-height: 30px;
94
+ --row-height: 28px;
95
+ --control-height: 24px;
96
+ --control-height-form: 28px;
97
+ --control-icon-size: 16px;
98
+ --content-padding: 12px;
99
+ --page-padding: 16px;
100
+ --grid-header-height: 28px;
101
+ --grid-filter-height: 24px;
102
+ --grid-cell-padding-x: 8px;
103
+ --grid-cell-padding-y: 4px;
104
+ --grid-row-line-height: 1.3;
105
+ }
106
+
107
+ html[data-density=comfortable] {
108
+ --font-size-xs: 13px;
109
+ --font-size-sm: 14px;
110
+ --font-size-md: 15px;
111
+ --space-1: 6px;
112
+ --space-2: 10px;
113
+ --space-3: 16px;
114
+ --space-4: 20px;
115
+ --space-5: 28px;
116
+ --space-6: 36px;
117
+ --toolbar-height: 44px;
118
+ --row-height: 40px;
119
+ --control-height: 34px;
120
+ --control-height-form: 40px;
121
+ --control-icon-size: 22px;
122
+ --content-padding: 20px;
123
+ --page-padding: 28px;
124
+ --grid-header-height: 40px;
125
+ --grid-filter-height: 34px;
126
+ --grid-cell-padding-x: 14px;
127
+ --grid-cell-padding-y: 8px;
128
+ --grid-row-line-height: 1.4;
129
+ }
130
+
131
+ .nb-feature-hub {
132
+ display: flex;
133
+ flex-direction: column;
134
+ height: 100%;
135
+ min-height: 0;
136
+ background: var(--surface-canvas, var(--surface-0));
137
+ }
138
+
139
+ .nb-feature-hub__header {
140
+ display: flex;
141
+ flex-direction: column;
142
+ gap: 0.35rem;
143
+ padding: 1.5rem 1.5rem 0;
144
+ }
145
+
146
+ .nb-feature-hub__title {
147
+ margin: 0;
148
+ font-size: 1.5rem;
149
+ font-weight: 700;
150
+ letter-spacing: -0.02em;
151
+ color: var(--text-primary);
152
+ }
153
+
154
+ .nb-feature-hub__subtitle {
155
+ margin: 0;
156
+ font-size: 0.875rem;
157
+ color: var(--text-secondary);
158
+ max-width: 36rem;
159
+ }
160
+
161
+ .nb-feature-hub__tabs {
162
+ display: flex;
163
+ flex-wrap: wrap;
164
+ gap: 0.25rem;
165
+ margin: 1.25rem 1.5rem 0;
166
+ padding: 0.25rem;
167
+ background: var(--surface-1);
168
+ border: 1px solid var(--border-subtle, var(--border-color));
169
+ border-radius: 10px;
170
+ box-shadow: var(--shadow-sm);
171
+ }
172
+
173
+ .nb-feature-hub__tab {
174
+ display: inline-flex;
175
+ align-items: center;
176
+ gap: 0.45rem;
177
+ padding: 0.55rem 0.9rem;
178
+ font-size: 0.8125rem;
179
+ font-weight: 600;
180
+ color: var(--text-secondary);
181
+ text-decoration: none;
182
+ border: 1px solid transparent;
183
+ border-radius: 7px;
184
+ transition: color 0.15s, background 0.15s, border-color 0.15s;
185
+ }
186
+ .nb-feature-hub__tab .ph {
187
+ font-size: 1rem;
188
+ opacity: 0.75;
189
+ }
190
+ .nb-feature-hub__tab:hover {
191
+ color: var(--text-primary);
192
+ background: var(--surface-2);
193
+ }
194
+ .nb-feature-hub__tab--active {
195
+ color: var(--accent-color);
196
+ background: var(--accent-subtle, color-mix(in srgb, var(--accent-color) 10%, #fff));
197
+ border-color: color-mix(in srgb, var(--accent-color) 22%, transparent);
198
+ box-shadow: var(--shadow-sm);
199
+ }
200
+ .nb-feature-hub__tab--active .ph {
201
+ opacity: 1;
202
+ }
203
+
204
+ .nb-feature-hub--compact .nb-feature-hub__header {
205
+ padding: 0.65rem 1rem 0;
206
+ }
207
+ .nb-feature-hub--compact .nb-feature-hub__tabs-card {
208
+ align-items: center;
209
+ background: var(--surface-1);
210
+ border: 1px solid var(--border-subtle, var(--border-color));
211
+ border-radius: 10px;
212
+ box-shadow: var(--shadow-sm);
213
+ display: flex;
214
+ flex: 1 1 auto;
215
+ flex-wrap: wrap;
216
+ gap: 0.5rem 1rem;
217
+ justify-content: space-between;
218
+ min-width: 0;
219
+ padding: 0.35rem 0.45rem 0.35rem 0.85rem;
220
+ width: 100%;
221
+ }
222
+ .nb-feature-hub--compact .nb-feature-hub__title {
223
+ font-size: 1.125rem;
224
+ line-height: 1.2;
225
+ margin: 0;
226
+ }
227
+ .nb-feature-hub--compact .nb-feature-hub__tabs {
228
+ flex: 1 1 auto;
229
+ justify-content: flex-end;
230
+ margin: 0;
231
+ padding: 0.15rem;
232
+ border: none;
233
+ border-radius: 8px;
234
+ background: transparent;
235
+ box-shadow: none;
236
+ min-width: min(100%, 520px);
237
+ }
238
+ .nb-feature-hub--compact .nb-feature-hub__tab {
239
+ padding: 0.38rem 0.7rem;
240
+ font-size: 0.75rem;
241
+ }
242
+ .nb-feature-hub--compact .nb-feature-hub__tab .ph {
243
+ font-size: 0.9rem;
244
+ }
245
+ .nb-feature-hub--compact .nb-feature-hub__banner {
246
+ margin: 0.5rem 1rem 0;
247
+ padding: 0.45rem 0.65rem;
248
+ font-size: 0.75rem;
249
+ }
250
+
251
+ .nb-feature-hub__banner {
252
+ display: flex;
253
+ align-items: flex-start;
254
+ gap: 0.6rem;
255
+ margin: 0.85rem 1.5rem 0;
256
+ padding: 0.7rem 0.9rem;
257
+ font-size: 0.8125rem;
258
+ line-height: 1.45;
259
+ border-radius: 8px;
260
+ }
261
+ .nb-feature-hub__banner .ph {
262
+ flex-shrink: 0;
263
+ margin-top: 0.1rem;
264
+ font-size: 1rem;
265
+ }
266
+ .nb-feature-hub__banner--info {
267
+ color: var(--status-info-text, #0369a1);
268
+ background: var(--status-info-bg, #e0f2fe);
269
+ border: 1px solid var(--status-info-border, #7dd3fc);
270
+ }
271
+ .nb-feature-hub__banner--warning {
272
+ color: var(--status-warning-text, #b45309);
273
+ background: var(--status-warning-bg, #fef3c7);
274
+ border: 1px solid var(--status-warning-border, #fcd34d);
275
+ }
276
+
277
+ .nb-feature-hub__content {
278
+ flex: 1;
279
+ min-height: 0;
280
+ overflow: auto;
281
+ display: flex;
282
+ flex-direction: column;
283
+ }
284
+
285
+ .nb-feature-hub--compact .nb-feature-hub__content {
286
+ padding-top: 0.35rem;
287
+ }
1
288
  .nb-admin-shell {
2
289
  display: flex;
3
290
  flex-direction: column;
@@ -560,3 +847,135 @@
560
847
  line-height: 1;
561
848
  padding: 0;
562
849
  }
850
+ .nb-devtools {
851
+ position: fixed;
852
+ bottom: 12px;
853
+ right: 12px;
854
+ z-index: 9999;
855
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
856
+ font-size: 12px;
857
+ }
858
+
859
+ .nb-devtools__toggle {
860
+ background: #1e293b;
861
+ color: #f8fafc;
862
+ border: 1px solid #334155;
863
+ border-radius: 6px;
864
+ padding: 6px 10px;
865
+ cursor: pointer;
866
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
867
+ }
868
+
869
+ .nb-devtools__panel {
870
+ position: absolute;
871
+ bottom: 40px;
872
+ right: 0;
873
+ width: min(420px, 100vw - 24px);
874
+ max-height: 70vh;
875
+ overflow: auto;
876
+ background: #0f172a;
877
+ color: #e2e8f0;
878
+ border: 1px solid #334155;
879
+ border-radius: 8px;
880
+ padding: 12px;
881
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
882
+ }
883
+
884
+ .nb-devtools__header {
885
+ display: flex;
886
+ justify-content: space-between;
887
+ align-items: center;
888
+ margin-bottom: 8px;
889
+ }
890
+
891
+ .nb-devtools__clear {
892
+ background: transparent;
893
+ color: #94a3b8;
894
+ border: none;
895
+ cursor: pointer;
896
+ font-size: 11px;
897
+ }
898
+
899
+ .nb-devtools__section {
900
+ margin-bottom: 12px;
901
+ }
902
+ .nb-devtools__section h4 {
903
+ margin: 0 0 6px;
904
+ font-size: 11px;
905
+ text-transform: uppercase;
906
+ letter-spacing: 0.04em;
907
+ color: #94a3b8;
908
+ }
909
+
910
+ .nb-devtools__subsection {
911
+ margin: 10px 0 6px;
912
+ font-size: 11px;
913
+ text-transform: uppercase;
914
+ letter-spacing: 0.04em;
915
+ color: #94a3b8;
916
+ }
917
+
918
+ .nb-devtools__providers {
919
+ margin: 0;
920
+ padding-left: 16px;
921
+ color: #cbd5e1;
922
+ }
923
+
924
+ .nb-devtools__empty {
925
+ margin: 0;
926
+ color: #94a3b8;
927
+ }
928
+
929
+ .nb-devtools__select {
930
+ width: 100%;
931
+ margin-bottom: 8px;
932
+ background: #1e293b;
933
+ color: #f8fafc;
934
+ border: 1px solid #334155;
935
+ border-radius: 4px;
936
+ padding: 4px 6px;
937
+ }
938
+
939
+ .nb-devtools__meta {
940
+ display: grid;
941
+ grid-template-columns: auto 1fr;
942
+ gap: 2px 8px;
943
+ margin: 0 0 8px;
944
+ }
945
+ .nb-devtools__meta dt {
946
+ color: #94a3b8;
947
+ }
948
+ .nb-devtools__meta dd {
949
+ margin: 0;
950
+ }
951
+
952
+ .nb-devtools__table {
953
+ width: 100%;
954
+ border-collapse: collapse;
955
+ }
956
+ .nb-devtools__table th,
957
+ .nb-devtools__table td {
958
+ text-align: left;
959
+ padding: 3px 4px;
960
+ border-bottom: 1px solid #1e293b;
961
+ vertical-align: top;
962
+ }
963
+ .nb-devtools__table th {
964
+ color: #94a3b8;
965
+ font-weight: 600;
966
+ }
967
+
968
+ .nb-devtools__mapping {
969
+ color: #7dd3fc;
970
+ word-break: break-word;
971
+ }
972
+
973
+ .nb-devtools__footer {
974
+ margin-top: 8px;
975
+ padding-top: 8px;
976
+ border-top: 1px solid #1e293b;
977
+ }
978
+ .nb-devtools__footer a {
979
+ color: #38bdf8;
980
+ text-decoration: none;
981
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nubitio/admin",
3
- "version": "0.5.29",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "Admin shell layout components: responsive sidebar, header, and screen size utilities for Nubit apps.",
6
6
  "license": "MIT",
@@ -53,9 +53,9 @@
53
53
  "react": "^19.0.0",
54
54
  "react-dom": "^19.0.0",
55
55
  "react-router-dom": "^6.0.0",
56
- "@nubitio/core": "^0.5.29",
57
- "@nubitio/crud": "^0.5.29",
58
- "@nubitio/hydra": "^0.5.29",
59
- "@nubitio/ui": "^0.5.29"
56
+ "@nubitio/crud": "^0.6.0",
57
+ "@nubitio/hydra": "^0.6.0",
58
+ "@nubitio/core": "^0.6.0",
59
+ "@nubitio/ui": "^0.6.0"
60
60
  }
61
61
  }