@godxjp/ui 13.13.0 → 13.14.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.
@@ -34,9 +34,13 @@ function Toaster({ ...props }) {
34
34
  loading: /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": "true" })
35
35
  },
36
36
  style: {
37
- "--normal-bg": "var(--popover)",
38
- "--normal-text": "var(--popover-foreground)",
39
- "--normal-border": "var(--border)",
37
+ // Color tokens are raw HSL triplets (consumed as hsl(var(--token)));
38
+ // sonner uses these vars verbatim as CSS colors, so wrap with hsl()
39
+ // here — unwrapped they are invalid values and the toast renders
40
+ // transparent.
41
+ "--normal-bg": "hsl(var(--popover))",
42
+ "--normal-text": "hsl(var(--popover-foreground))",
43
+ "--normal-border": "hsl(var(--border))",
40
44
  "--border-radius": "var(--radius)"
41
45
  },
42
46
  position: "bottom-right",
@@ -2,7 +2,7 @@ import type { PageContainerProp, PageInsetProp } from "../../props/components/la
2
2
  export type { PageContainerProp, PageContainerProp as PageContainerProps, } from "../../props/components/layout.prop";
3
3
  export type { BreadcrumbItemProp, BreadcrumbItemProp as BreadcrumbItem, } from "../../props/vocabulary/navigation.prop";
4
4
  export declare function PageContainerInset({ className, children, ...props }: PageInsetProp): import("react/jsx-runtime").JSX.Element;
5
- declare function PageContainerRoot({ title, subtitle, extra, footer, breadcrumb, linkComponent: LinkComponent, density, variant, stickyFooter, fill, children, className, }: PageContainerProp): import("react/jsx-runtime").JSX.Element;
5
+ declare function PageContainerRoot({ title, subtitle, extra, footer, breadcrumb, linkComponent: LinkComponent, density, variant, stickyFooter, footerReveal, fill, children, className, }: PageContainerProp): import("react/jsx-runtime").JSX.Element;
6
6
  export declare const PageContainer: typeof PageContainerRoot & {
7
7
  Inset: typeof PageContainerInset;
8
8
  };
@@ -1,7 +1,33 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
2
3
  import { ChevronRight } from "lucide-react";
3
4
  import { cn } from "../../lib/utils";
4
5
  import { densityClass, pageContainerVariantClass } from "../../lib/variants";
6
+ function scrollParent(el) {
7
+ let node = el?.parentElement ?? null;
8
+ while (node) {
9
+ const overflowY = getComputedStyle(node).overflowY;
10
+ if (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") return node;
11
+ node = node.parentElement;
12
+ }
13
+ return null;
14
+ }
15
+ function useFooterReveal(enabled) {
16
+ const headerRef = useRef(null);
17
+ const [revealed, setRevealed] = useState(false);
18
+ useEffect(() => {
19
+ if (!enabled) return;
20
+ const el = headerRef.current;
21
+ if (!el || typeof IntersectionObserver === "undefined") return;
22
+ const observer = new IntersectionObserver(
23
+ ([entry]) => setRevealed(!entry.isIntersecting),
24
+ { root: scrollParent(el), threshold: 0 }
25
+ );
26
+ observer.observe(el);
27
+ return () => observer.disconnect();
28
+ }, [enabled]);
29
+ return { headerRef, revealed: enabled && revealed };
30
+ }
5
31
  function PageContainerInset({ className, children, ...props }) {
6
32
  return /* @__PURE__ */ jsx("div", { className: cn("ui-page-container-inset", className), ...props, children });
7
33
  }
@@ -15,23 +41,28 @@ function PageContainerRoot({
15
41
  density = "default",
16
42
  variant = "default",
17
43
  stickyFooter = false,
44
+ footerReveal = "always",
18
45
  fill = false,
19
46
  children,
20
47
  className
21
48
  }) {
49
+ const reveal = stickyFooter && footer != null && footerReveal === "onScroll";
50
+ const { headerRef, revealed } = useFooterReveal(reveal);
22
51
  return /* @__PURE__ */ jsxs(
23
52
  "div",
24
53
  {
54
+ "data-revealed": revealed ? "true" : void 0,
25
55
  className: cn(
26
56
  "ui-page-container",
27
57
  densityClass[density],
28
58
  pageContainerVariantClass[variant],
29
59
  stickyFooter && "ui-page-container--sticky-footer",
60
+ reveal && "ui-page-container--reveal-footer",
30
61
  fill && "ui-page-container--fill",
31
62
  className
32
63
  ),
33
64
  children: [
34
- /* @__PURE__ */ jsxs("header", { className: "ui-page-header", children: [
65
+ /* @__PURE__ */ jsxs("header", { ref: headerRef, className: "ui-page-header", children: [
35
66
  breadcrumb && breadcrumb.length > 0 && /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className: "ui-breadcrumb", children: /* @__PURE__ */ jsx("ol", { className: "ui-breadcrumb-list", children: breadcrumb.map((item, i) => {
36
67
  const isLast = i === breadcrumb.length - 1;
37
68
  return /* @__PURE__ */ jsxs("li", { className: "ui-inline-xs", children: [
@@ -14,6 +14,16 @@ export type PageContainerProp = {
14
14
  variant?: PageContainerVariantProp;
15
15
  /** Pin footer to viewport bottom on scroll — pairs well with `variant="narrow"`. */
16
16
  stickyFooter?: boolean;
17
+ /**
18
+ * When the footer is sticky, control WHEN it shows. `"always"` (default)
19
+ * keeps it pinned the whole time. `"onScroll"` hides it until the header
20
+ * (title + `extra` actions) scrolls out of view, then slides it up — the
21
+ * standard edit/create "save bar" so the primary actions stay reachable as
22
+ * the form scrolls, without cluttering the top. The footer stays mounted
23
+ * (no layout reflow → no jitter); observed against the nearest scroll
24
+ * container.
25
+ */
26
+ footerReveal?: "always" | "onScroll";
17
27
  /**
18
28
  * Grow the body to fill the remaining shell height. Default `false` (top-packed,
19
29
  * content-height — short pages leave no stretched void, gh#103). Enable for a
@@ -308,6 +308,31 @@
308
308
  background-color: hsl(var(--background));
309
309
  }
310
310
 
311
+ /* footerReveal="onScroll": the sticky footer stays mounted (reserving its
312
+ * flow space, so toggling never reflows the body → no scroll jitter) but is
313
+ * slid below the fold until the header leaves the viewport, then slides up.
314
+ * data-revealed is toggled by an IntersectionObserver on the header. */
315
+ .ui-page-container--reveal-footer .ui-page-footer {
316
+ transform: translateY(100%);
317
+ opacity: 0;
318
+ pointer-events: none;
319
+ transition:
320
+ transform 200ms ease-out,
321
+ opacity 200ms ease-out;
322
+ }
323
+
324
+ .ui-page-container--reveal-footer[data-revealed="true"] .ui-page-footer {
325
+ transform: none;
326
+ opacity: 1;
327
+ pointer-events: auto;
328
+ }
329
+
330
+ @media (prefers-reduced-motion: reduce) {
331
+ .ui-page-container--reveal-footer .ui-page-footer {
332
+ transition: none;
333
+ }
334
+ }
335
+
311
336
  .ui-page-header {
312
337
  display: flex;
313
338
  flex-direction: column;
@@ -62,6 +62,14 @@
62
62
  --search-input-end-padding: calc(
63
63
  var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap)
64
64
  );
65
+
66
+ --choice-description-font-size: var(--font-size-xs);
67
+ --color-picker-hex-font-size: var(--font-size-xs);
68
+ --command-group-heading-font-size: var(--font-size-xs);
69
+ --search-input-label-font-size: var(--font-size-xs);
70
+ --tag-input-chip-font-size: var(--font-size-xs);
71
+ --toggle-sm-font-size: var(--font-size-xs);
72
+ --button-sm-font-size: var(--font-size-xs);
65
73
  }
66
74
 
67
75
  /* Rule #24 — on touch devices (coarse pointer) interactive controls keep a ≥44px tap target
@@ -72,11 +80,4 @@
72
80
  --control-height-compact: 2.75rem;
73
81
  --control-height-default: 2.75rem;
74
82
  }
75
- --choice-description-font-size: var(--font-size-xs);
76
- --color-picker-hex-font-size: var(--font-size-xs);
77
- --command-group-heading-font-size: var(--font-size-xs);
78
- --search-input-label-font-size: var(--font-size-xs);
79
- --tag-input-chip-font-size: var(--font-size-xs);
80
- --toggle-sm-font-size: var(--font-size-xs);
81
- --button-sm-font-size: var(--font-size-xs);
82
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "13.13.0",
3
+ "version": "13.14.0",
4
4
  "type": "module",
5
5
  "packageManager": "pnpm@10.29.1",
6
6
  "sideEffects": false,