@motor-hero/ui-kit 0.45.0 → 0.46.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.
@@ -16,7 +16,15 @@ export interface AppShellProps {
16
16
  headerActions?: ReactNode;
17
17
  collapsible?: boolean;
18
18
  defaultCollapsed?: boolean;
19
+ /**
20
+ * Bottom of the desktop sidebar, above the collapse control — a product
21
+ * credit, a version, a support link. Receives the collapsed state so it
22
+ * can shrink with the sidebar.
23
+ */
24
+ sidebarFooter?: ReactNode | ((collapsed: boolean) => ReactNode);
25
+ /** Above the header, full width of the content column — `UpdateBanner` goes here. */
26
+ banner?: ReactNode;
19
27
  children: ReactNode;
20
28
  }
21
- export declare function AppShell({ brand, brandCollapsed, navItems, activePath, isActive, isAdmin, user, userMenuItems, onLogout, renderLink, headerActions, collapsible, defaultCollapsed, children, }: AppShellProps): import("react").JSX.Element;
29
+ export declare function AppShell({ brand, brandCollapsed, navItems, activePath, isActive, isAdmin, user, userMenuItems, onLogout, renderLink, headerActions, collapsible, defaultCollapsed, sidebarFooter, banner, children, }: AppShellProps): import("react").JSX.Element;
22
30
  //# sourceMappingURL=app-shell.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-shell.d.ts","sourceRoot":"","sources":["../../src/components/app-shell.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAA;AAEhD,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,eAAe,CAAA;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEzC,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAEvE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,CAAA;IAChB,cAAc,CAAC,EAAE,SAAS,CAAA;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAA;IACzD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,aAAa,CAAC,EAAE,YAAY,EAAE,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,aAAa,CAAC,EAAE,SAAS,CAAA;IACzB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,cAAc,EACd,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,OAAe,EACf,IAAI,EACJ,aAAa,EACb,QAAQ,EACR,UAA8B,EAC9B,aAAa,EACb,WAAkB,EAClB,gBAAwB,EACxB,QAAQ,GACT,EAAE,aAAa,+BAsFf"}
1
+ {"version":3,"file":"app-shell.d.ts","sourceRoot":"","sources":["../../src/components/app-shell.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAA;AAEhD,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,eAAe,CAAA;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAEzC,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAEvE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,CAAA;IAChB,cAAc,CAAC,EAAE,SAAS,CAAA;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAA;IACzD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,aAAa,CAAC,EAAE,YAAY,EAAE,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,aAAa,CAAC,EAAE,SAAS,CAAA;IACzB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,SAAS,EAAE,OAAO,KAAK,SAAS,CAAC,CAAA;IAC/D,qFAAqF;IACrF,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,cAAc,EACd,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,OAAe,EACf,IAAI,EACJ,aAAa,EACb,QAAQ,EACR,UAA8B,EAC9B,aAAa,EACb,WAAkB,EAClB,gBAAwB,EACxB,aAAa,EACb,MAAM,EACN,QAAQ,GACT,EAAE,aAAa,+BA4Ff"}
@@ -0,0 +1,16 @@
1
+ import { type UseVersionCheckOptions } from "../hooks/use-version-check";
2
+ export interface UpdateBannerProps extends UseVersionCheckOptions {
3
+ title?: string;
4
+ description?: string;
5
+ actionLabel?: string;
6
+ className?: string;
7
+ }
8
+ /**
9
+ * A bar that appears when a newer build is deployed and stays until the
10
+ * person reloads — not a toast: a notice that can be missed or dismissed
11
+ * leaves the old bundle running against a new API. Themed with the product
12
+ * tokens so it matches light and dark alike. Put it in `AppShell`'s
13
+ * `banner` slot (or at the top of any page).
14
+ */
15
+ export declare function UpdateBanner({ title, description, actionLabel, className, ...options }: UpdateBannerProps): import("react").JSX.Element | null;
16
+ //# sourceMappingURL=update-banner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-banner.d.ts","sourceRoot":"","sources":["../../src/components/update-banner.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,sBAAsB,EAAmB,MAAM,4BAA4B,CAAA;AAEzF,MAAM,WAAW,iBAAkB,SAAQ,sBAAsB;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAgC,EAChC,WAA+D,EAC/D,WAAyB,EACzB,SAAS,EACT,GAAG,OAAO,EACX,EAAE,iBAAiB,sCA4BnB"}
@@ -0,0 +1,24 @@
1
+ export interface UseVersionCheckOptions {
2
+ /** The build id baked into the running bundle (see `versionFile` in `@motor-hero/ui-kit/vite`). */
3
+ currentBuild: string | undefined;
4
+ /** Where the deployed build id is published. */
5
+ url?: string;
6
+ intervalMs?: number;
7
+ }
8
+ export interface VersionCheckState {
9
+ /** The deployed build differs from the running one. */
10
+ updateAvailable: boolean;
11
+ /** Grows on every poll that still sees a newer build. */
12
+ staleChecks: number;
13
+ latestBuild: string | null;
14
+ reload: () => void;
15
+ }
16
+ /**
17
+ * Polls the deployed `version.json` and compares its `buildTime` with the
18
+ * build id baked into this bundle. Nothing is inferred from a first fetch —
19
+ * a page loaded during a deploy, or a poll that fails while the server
20
+ * restarts, cannot make the running code believe it is current. Polling
21
+ * pauses while the tab is hidden and runs at once when it comes back.
22
+ */
23
+ export declare function useVersionCheck({ currentBuild, url, intervalMs, }: UseVersionCheckOptions): VersionCheckState;
24
+ //# sourceMappingURL=use-version-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-version-check.d.ts","sourceRoot":"","sources":["../../src/hooks/use-version-check.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,sBAAsB;IACrC,mGAAmG;IACnG,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,eAAe,EAAE,OAAO,CAAA;IACxB,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAC9B,YAAY,EACZ,GAAqB,EACrB,UAAmB,GACpB,EAAE,sBAAsB,GAAG,iBAAiB,CAyC5C"}
package/dist/index.cjs CHANGED
@@ -65,6 +65,7 @@ __export(index_exports, {
65
65
  TableSkeleton: () => TableSkeleton,
66
66
  ThemeProvider: () => ThemeProvider,
67
67
  Toaster: () => Toaster,
68
+ UpdateBanner: () => UpdateBanner,
68
69
  UserMenu: () => UserMenu,
69
70
  applyCompanyTheme: () => applyCompanyTheme,
70
71
  cn: () => cn,
@@ -76,7 +77,8 @@ __export(index_exports, {
76
77
  useCustomToast: () => useCustomToast,
77
78
  useDisclosure: () => useDisclosure,
78
79
  useIsDesktop: () => useIsDesktop,
79
- useTheme: () => useTheme
80
+ useTheme: () => useTheme,
81
+ useVersionCheck: () => useVersionCheck
80
82
  });
81
83
  module.exports = __toCommonJS(index_exports);
82
84
 
@@ -341,6 +343,8 @@ function AppShell({
341
343
  headerActions,
342
344
  collapsible = true,
343
345
  defaultCollapsed = false,
346
+ sidebarFooter,
347
+ banner,
344
348
  children
345
349
  }) {
346
350
  const [collapsed, setCollapsed] = (0, import_react3.useState)(defaultCollapsed);
@@ -368,6 +372,7 @@ function AppShell({
368
372
  children: [
369
373
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex h-14 shrink-0 items-center border-b px-4", children: collapsed ? brandCollapsed ?? brand : brand }),
370
374
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex-1 overflow-y-auto px-2 py-4", children: nav(collapsed) }),
375
+ sidebarFooter && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "border-t px-3 py-2", "data-slot": "sidebar-footer", children: typeof sidebarFooter === "function" ? sidebarFooter(collapsed) : sidebarFooter }),
371
376
  collapsible && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "border-t p-2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
372
377
  "button",
373
378
  {
@@ -409,6 +414,7 @@ function AppShell({
409
414
  }
410
415
  ),
411
416
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-1 flex-col overflow-hidden", children: [
417
+ banner,
412
418
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "flex h-14 shrink-0 items-center gap-2 border-b bg-background px-4", children: [
413
419
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
414
420
  "button",
@@ -2564,8 +2570,96 @@ function Toaster(props) {
2564
2570
  );
2565
2571
  }
2566
2572
 
2567
- // src/hooks/use-company-subdomain.ts
2573
+ // src/components/update-banner.tsx
2574
+ var import_lucide_react13 = require("lucide-react");
2575
+
2576
+ // src/hooks/use-version-check.ts
2568
2577
  var import_react8 = require("react");
2578
+ function useVersionCheck({
2579
+ currentBuild,
2580
+ url = "/version.json",
2581
+ intervalMs = 6e4
2582
+ }) {
2583
+ const [latestBuild, setLatestBuild] = (0, import_react8.useState)(null);
2584
+ const [staleChecks, setStaleChecks] = (0, import_react8.useState)(0);
2585
+ (0, import_react8.useEffect)(() => {
2586
+ if (!currentBuild) return;
2587
+ let cancelled = false;
2588
+ const check = async () => {
2589
+ if (document.visibilityState === "hidden") return;
2590
+ try {
2591
+ const res = await fetch(url, { cache: "no-store" });
2592
+ const contentType = res.headers.get("content-type") ?? "";
2593
+ if (!res.ok || !contentType.includes("application/json")) return;
2594
+ const data = await res.json();
2595
+ if (cancelled || typeof data.buildTime !== "string") return;
2596
+ if (data.buildTime !== currentBuild) {
2597
+ setLatestBuild(data.buildTime);
2598
+ setStaleChecks((n) => n + 1);
2599
+ }
2600
+ } catch (error) {
2601
+ console.error("Version check failed", error);
2602
+ }
2603
+ };
2604
+ check();
2605
+ const interval = setInterval(check, intervalMs);
2606
+ const onVisible = () => {
2607
+ if (document.visibilityState === "visible") check();
2608
+ };
2609
+ document.addEventListener("visibilitychange", onVisible);
2610
+ return () => {
2611
+ cancelled = true;
2612
+ clearInterval(interval);
2613
+ document.removeEventListener("visibilitychange", onVisible);
2614
+ };
2615
+ }, [currentBuild, url, intervalMs]);
2616
+ const reload = (0, import_react8.useCallback)(() => window.location.reload(), []);
2617
+ return { updateAvailable: latestBuild !== null, staleChecks, latestBuild, reload };
2618
+ }
2619
+
2620
+ // src/components/update-banner.tsx
2621
+ var import_jsx_runtime34 = require("react/jsx-runtime");
2622
+ function UpdateBanner({
2623
+ title = "Nova vers\xE3o dispon\xEDvel",
2624
+ description = "Atualize para continuar na vers\xE3o mais recente.",
2625
+ actionLabel = "Atualizar",
2626
+ className,
2627
+ ...options
2628
+ }) {
2629
+ const { updateAvailable, reload } = useVersionCheck(options);
2630
+ if (!updateAvailable) return null;
2631
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
2632
+ "div",
2633
+ {
2634
+ role: "status",
2635
+ "aria-live": "polite",
2636
+ "data-slot": "update-banner",
2637
+ className: cn(
2638
+ "flex flex-wrap items-center justify-between gap-3 border-b border-primary/30 bg-primary px-4 py-2 text-sm text-primary-foreground",
2639
+ className
2640
+ ),
2641
+ children: [
2642
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "flex min-w-0 items-center gap-2", children: [
2643
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react13.RefreshCw, { className: "h-4 w-4 shrink-0 animate-spin [animation-duration:3s]", "aria-hidden": true }),
2644
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "font-medium", children: title }),
2645
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "hidden truncate opacity-80 sm:inline", children: description })
2646
+ ] }),
2647
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2648
+ "button",
2649
+ {
2650
+ type: "button",
2651
+ onClick: reload,
2652
+ className: "shrink-0 rounded-md bg-background px-3 py-1 text-xs font-medium text-foreground transition-colors hover:bg-background/90",
2653
+ children: actionLabel
2654
+ }
2655
+ )
2656
+ ]
2657
+ }
2658
+ );
2659
+ }
2660
+
2661
+ // src/hooks/use-company-subdomain.ts
2662
+ var import_react9 = require("react");
2569
2663
  var ALWAYS_RESERVED = ["www", "localhost"];
2570
2664
  function getSubdomain(hostname, reservedSubdomains = []) {
2571
2665
  const labels = hostname.split(".");
@@ -2598,7 +2692,7 @@ function useCompanySubdomain(opts = {}) {
2598
2692
  overrideStorageKey = "company-subdomain",
2599
2693
  reservedSubdomains = []
2600
2694
  } = opts;
2601
- const [subdomain] = (0, import_react8.useState)(() => {
2695
+ const [subdomain] = (0, import_react9.useState)(() => {
2602
2696
  if (enableOverride) {
2603
2697
  const override = resolveOverride(overrideStorageKey);
2604
2698
  if (override) return override;
@@ -2610,20 +2704,20 @@ function useCompanySubdomain(opts = {}) {
2610
2704
  }
2611
2705
 
2612
2706
  // src/hooks/use-disclosure.ts
2613
- var import_react9 = require("react");
2707
+ var import_react10 = require("react");
2614
2708
  function useDisclosure(initial = false) {
2615
- const [open, setOpen] = (0, import_react9.useState)(initial);
2616
- const onOpen = (0, import_react9.useCallback)(() => setOpen(true), []);
2617
- const onClose = (0, import_react9.useCallback)(() => setOpen(false), []);
2618
- const onToggle = (0, import_react9.useCallback)(() => setOpen((v) => !v), []);
2709
+ const [open, setOpen] = (0, import_react10.useState)(initial);
2710
+ const onOpen = (0, import_react10.useCallback)(() => setOpen(true), []);
2711
+ const onClose = (0, import_react10.useCallback)(() => setOpen(false), []);
2712
+ const onToggle = (0, import_react10.useCallback)(() => setOpen((v) => !v), []);
2619
2713
  return { open, onOpen, onClose, onToggle, setOpen };
2620
2714
  }
2621
2715
 
2622
2716
  // src/hooks/use-toast.ts
2623
2717
  var import_sonner2 = require("sonner");
2624
- var import_react10 = require("react");
2718
+ var import_react11 = require("react");
2625
2719
  function useCustomToast() {
2626
- const showToast = (0, import_react10.useCallback)(
2720
+ const showToast = (0, import_react11.useCallback)(
2627
2721
  (title, description, status = "success") => {
2628
2722
  switch (status) {
2629
2723
  case "success":
@@ -2690,6 +2784,7 @@ function extractApiError(err, fallbackMessage = "Ocorreu um erro inesperado.") {
2690
2784
  TableSkeleton,
2691
2785
  ThemeProvider,
2692
2786
  Toaster,
2787
+ UpdateBanner,
2693
2788
  UserMenu,
2694
2789
  applyCompanyTheme,
2695
2790
  cn,
@@ -2701,6 +2796,7 @@ function extractApiError(err, fallbackMessage = "Ocorreu um erro inesperado.") {
2701
2796
  useCustomToast,
2702
2797
  useDisclosure,
2703
2798
  useIsDesktop,
2704
- useTheme
2799
+ useTheme,
2800
+ useVersionCheck
2705
2801
  });
2706
2802
  //# sourceMappingURL=index.cjs.map