@godxjp/ui 28.6.0 → 28.8.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.
Files changed (99) hide show
  1. package/dist/components/data-display/data-table.d.ts +10 -2
  2. package/dist/components/data-display/data-table.js +12 -5
  3. package/dist/components/data-display/index.d.ts +2 -0
  4. package/dist/components/data-display/index.js +2 -0
  5. package/dist/components/data-display/marquee.d.ts +16 -0
  6. package/dist/components/data-display/marquee.js +155 -0
  7. package/dist/components/data-display/scroll-area.js +16 -2
  8. package/dist/components/data-display/service-launcher-card.d.ts +25 -0
  9. package/dist/components/data-display/table.d.ts +38 -1
  10. package/dist/components/data-display/table.js +57 -39
  11. package/dist/components/data-entry/form-field.js +29 -12
  12. package/dist/components/general/reveal.d.ts +23 -2
  13. package/dist/components/general/reveal.js +37 -7
  14. package/dist/components/general/typography.d.ts +4 -1
  15. package/dist/components/general/typography.js +14 -1
  16. package/dist/components/layout/affix.d.ts +86 -0
  17. package/dist/components/layout/affix.js +187 -0
  18. package/dist/components/layout/index.d.ts +4 -0
  19. package/dist/components/layout/index.js +4 -0
  20. package/dist/components/layout/legal-document-shell.js +4 -3
  21. package/dist/components/layout/masonry.d.ts +74 -0
  22. package/dist/components/layout/masonry.js +214 -0
  23. package/dist/components/layout/page-container.js +5 -20
  24. package/dist/components/navigation/anchor.d.ts +64 -0
  25. package/dist/components/navigation/anchor.js +284 -0
  26. package/dist/components/navigation/app-setting-picker.js +20 -3
  27. package/dist/components/navigation/index.d.ts +4 -0
  28. package/dist/components/navigation/index.js +4 -0
  29. package/dist/components/navigation/mega-menu.d.ts +21 -0
  30. package/dist/components/navigation/mega-menu.js +526 -0
  31. package/dist/components/ui/table.js +1 -0
  32. package/dist/contracts/measurement.json +1 -1
  33. package/dist/i18n/messages/en.json +522 -1
  34. package/dist/i18n/messages/ja.json +518 -1
  35. package/dist/i18n/messages/vi.json +518 -1
  36. package/dist/lib/hooks.d.ts +91 -0
  37. package/dist/lib/hooks.js +79 -0
  38. package/dist/lib/platform.d.ts +14 -0
  39. package/dist/lib/platform.js +10 -1
  40. package/dist/lib/utils.d.ts +1 -1
  41. package/dist/lib/utils.js +3 -2
  42. package/dist/props/components/data-display.prop.d.ts +109 -1
  43. package/dist/props/components/general.prop.d.ts +47 -3
  44. package/dist/props/components/layout.prop.d.ts +194 -0
  45. package/dist/props/components/navigation.prop.d.ts +263 -0
  46. package/dist/props/registry.d.ts +360 -5
  47. package/dist/props/registry.js +474 -4
  48. package/dist/props/vocabulary/index.d.ts +1 -1
  49. package/dist/props/vocabulary/interaction.prop.d.ts +39 -2
  50. package/dist/styles/control.css +5 -6
  51. package/dist/styles/data-display-layout.css +2 -1
  52. package/dist/styles/density.css +4 -0
  53. package/dist/styles/form-layout.css +22 -0
  54. package/dist/styles/layout.css +79 -1
  55. package/dist/styles/motion.css +121 -1
  56. package/dist/styles/navigation-layout.css +415 -20
  57. package/dist/styles/shell-layout.css +3 -0
  58. package/dist/styles/text-layout.css +52 -4
  59. package/dist/tokens/base.css +5 -0
  60. package/dist/tokens/components/affix.css +7 -0
  61. package/dist/tokens/components/anchor.css +17 -0
  62. package/dist/tokens/components/control.css +3 -3
  63. package/dist/tokens/components/form.css +3 -1
  64. package/dist/tokens/components/marquee.css +7 -0
  65. package/dist/tokens/components/masonry.css +6 -0
  66. package/dist/tokens/components/mega-menu.css +62 -0
  67. package/dist/tokens/components/navigation.css +2 -0
  68. package/dist/tokens/components/shell.css +3 -0
  69. package/dist/tokens/foundation.css +11 -0
  70. package/dist/tokens/semantic/layout.css +7 -0
  71. package/docs/COMPOSITION-VS-COMPONENT.md +21 -0
  72. package/docs/DESIGN-AUTHORITY.md +124 -23
  73. package/docs/FRAME-COVERAGE-REPORT.md +7 -2
  74. package/docs/data-display/marquee.tsx +254 -0
  75. package/docs/data-display/scroll-area.tsx +31 -0
  76. package/docs/data-display/service-launcher-card.tsx +275 -115
  77. package/docs/foundation/_theme-editor-scope.ts +222 -0
  78. package/docs/foundation/density.tsx +12 -2
  79. package/docs/foundation/spacing.tsx +5 -0
  80. package/docs/foundation/theme-editor.tsx +645 -0
  81. package/docs/general/activity.tsx +65 -0
  82. package/docs/general/reveal.tsx +290 -22
  83. package/docs/general/typography.tsx +91 -1
  84. package/docs/layout/affix.tsx +209 -0
  85. package/docs/layout/masonry.tsx +291 -0
  86. package/docs/navigation/anchor.tsx +285 -0
  87. package/docs/navigation/mega-menu-panel.tsx +86 -0
  88. package/docs/navigation/mega-menu.tsx +254 -0
  89. package/docs/roadmap/website-components.md +779 -0
  90. package/docs/showcase/acme-website.tsx +75 -39
  91. package/docs/showcase/futurelastic-web.tsx +91 -49
  92. package/docs/showcase/marketing-page.tsx +885 -0
  93. package/docs/showcase/table-footer-totals.tsx +12 -2
  94. package/docs/showcase/theme-customization.tsx +1259 -0
  95. package/package.json +8 -5
  96. package/scripts/brand-accent.generated.mjs +27 -0
  97. package/scripts/ui-audit.mjs +66 -0
  98. package/scripts/visual-audit-rules.mjs +46 -2
  99. package/scripts/visual-audit.mjs +29 -1
package/dist/lib/hooks.js CHANGED
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { useLayoutEffect } from "@react-aria/utils";
2
3
  import { useEffect, useState } from "react";
3
4
  function useDebouncedValue(value, delay = 250) {
4
5
  const [debounced, setDebounced] = useState(value);
@@ -104,11 +105,89 @@ function useScrollableRegionTabIndex(element) {
104
105
  };
105
106
  }, [element]);
106
107
  }
108
+ function useScrollsOnAxis(ref, enabled, axis) {
109
+ const [scrolls, setScrolls] = useState(true);
110
+ useEffect(() => {
111
+ const el = ref.current;
112
+ if (!enabled || !el) return void 0;
113
+ const overflows = (client, scroll) => client === 0 || scroll - client > 1;
114
+ const update = () => {
115
+ setScrolls(
116
+ axis !== "vertical" && overflows(el.clientWidth, el.scrollWidth) || axis !== "horizontal" && overflows(el.clientHeight, el.scrollHeight)
117
+ );
118
+ };
119
+ update();
120
+ if (typeof ResizeObserver === "undefined") return void 0;
121
+ const observer = new ResizeObserver(update);
122
+ observer.observe(el);
123
+ if (el.firstElementChild) observer.observe(el.firstElementChild);
124
+ return () => {
125
+ observer.disconnect();
126
+ };
127
+ }, [ref, enabled, axis]);
128
+ return enabled && scrolls;
129
+ }
130
+ function useScrollsHorizontally(ref, enabled) {
131
+ return useScrollsOnAxis(ref, enabled, "horizontal");
132
+ }
133
+ function scrollParent(el) {
134
+ let node = el?.parentElement ?? null;
135
+ while (node) {
136
+ const overflowY = getComputedStyle(node).overflowY;
137
+ if (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") return node;
138
+ node = node.parentElement;
139
+ }
140
+ return null;
141
+ }
142
+ function useInView(ref, {
143
+ enabled = true,
144
+ once = false,
145
+ amount = "some",
146
+ root = null,
147
+ rootMargin,
148
+ assumeInView = false
149
+ } = {}) {
150
+ const [inView, setInView] = useState(true);
151
+ useLayoutEffect(() => {
152
+ const el = ref.current;
153
+ if (!enabled || !el || typeof IntersectionObserver === "undefined") return void 0;
154
+ const requested = typeof amount === "number" ? amount : amount === "all" ? 1 : 0;
155
+ const box = el.getBoundingClientRect();
156
+ const rootBox = root?.getBoundingClientRect();
157
+ const rootWidth = rootBox?.width ?? window.innerWidth;
158
+ const rootHeight = rootBox?.height ?? window.innerHeight;
159
+ const reachable = box.width > 0 && box.height > 0 ? Math.min(box.width, rootWidth) * Math.min(box.height, rootHeight) / (box.width * box.height) : 1;
160
+ const threshold = Math.min(requested, reachable);
161
+ if (!assumeInView) setInView(false);
162
+ const observer = new IntersectionObserver(
163
+ (entries) => {
164
+ const entry = entries[entries.length - 1];
165
+ if (!entry) return;
166
+ if (entry.isIntersecting && entry.intersectionRatio >= threshold) {
167
+ setInView(true);
168
+ if (once) observer.unobserve(entry.target);
169
+ } else if (!once) {
170
+ setInView(false);
171
+ }
172
+ },
173
+ { root, threshold, ...rootMargin === void 0 ? void 0 : { rootMargin } }
174
+ );
175
+ observer.observe(el);
176
+ return () => {
177
+ observer.disconnect();
178
+ };
179
+ }, [ref, enabled, once, amount, root, rootMargin, assumeInView]);
180
+ return !enabled || inView;
181
+ }
107
182
  export {
183
+ scrollParent,
108
184
  useControlledLatch,
109
185
  useDebouncedValue,
186
+ useInView,
110
187
  useIsMobile,
111
188
  useMediaQuery,
112
189
  useScrollableRegionTabIndex,
190
+ useScrollsHorizontally,
191
+ useScrollsOnAxis,
113
192
  useTimeoutFlag
114
193
  };
@@ -8,3 +8,17 @@
8
8
  * server render therefore describes the Ctrl shortcut.
9
9
  */
10
10
  export declare function isApplePlatform(): boolean;
11
+ /**
12
+ * Has the user asked for reduced motion (WCAG 2.3.3)?
13
+ *
14
+ * Read at the moment of the gesture rather than subscribed to, because every caller asks the same
15
+ * question at the same instant: "am I allowed to tween THIS scroll / THIS transition". A falsy or
16
+ * unsupported environment — SSR, jsdom, a browser without `matchMedia`, a `matchMedia` that throws
17
+ * on an unknown feature — answers `false`, which is the ordinary animated behaviour rather than a
18
+ * crash.
19
+ *
20
+ * It lives here rather than beside its first caller because it had already been written twice
21
+ * (`src/form/form-root.tsx`, `src/components/layout/legal-document-shell.tsx`) before a third
22
+ * caller needed it.
23
+ */
24
+ export declare function prefersReducedMotion(): boolean;
@@ -4,6 +4,15 @@ function isApplePlatform() {
4
4
  const platform = data?.platform || navigator.platform || "";
5
5
  return /mac|iphone|ipad|ipod/i.test(platform);
6
6
  }
7
+ function prefersReducedMotion() {
8
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
9
+ try {
10
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
7
15
  export {
8
- isApplePlatform
16
+ isApplePlatform,
17
+ prefersReducedMotion
9
18
  };
@@ -1,3 +1,3 @@
1
1
  import { type ClassValue } from "clsx";
2
2
  export declare function cn(...inputs: ClassValue[]): string;
3
- export { isApplePlatform } from "./platform.js";
3
+ export { isApplePlatform, prefersReducedMotion } from "./platform.js";
package/dist/lib/utils.js CHANGED
@@ -3,8 +3,9 @@ import { twMerge } from "tailwind-merge";
3
3
  function cn(...inputs) {
4
4
  return twMerge(clsx(inputs));
5
5
  }
6
- import { isApplePlatform } from "./platform.js";
6
+ import { isApplePlatform, prefersReducedMotion } from "./platform.js";
7
7
  export {
8
8
  cn,
9
- isApplePlatform
9
+ isApplePlatform,
10
+ prefersReducedMotion
10
11
  };
@@ -36,7 +36,7 @@ export type ProseProp = {
36
36
  className?: ClassNameProp;
37
37
  children?: ChildrenProp;
38
38
  };
39
- import type { ActionProp, ClassNameProp, DescriptionProp, IconProp, TitleProp, ColumnDefProp, GetRowIdProp, GetRowLabelProp, OnRowClickProp, OnSelectChangeProp, OnSortChangeProp, OnTableDensityChangeProp, SelectedIdsProp, SortStateProp, TableDensityProp, TablePresetProp, BreakpointProp, DensityProp, ChildrenProp, PendingProp, ToneProp, AvatarShapeProp, HeadingLevelProp, HandlerProp, SizeProp, LabelProp, IdProp, DescriptionsLayoutProp, DescriptionsColumnProp, DescriptionsSpanProp, DescriptionsItemsProp, SortDirectionProp, OnColumnFilterChangeProp, OnRowProp, TableExpandableProp, TableRowSelectionProp, TableScrollProp, TableStickyProp, TableSummaryProp, DisabledProp, OnClickProp, ValueProp, DefaultValueProp, OnValueChangeProp } from "../vocabulary/index.js";
39
+ import type { ActionProp, ClassNameProp, DescriptionProp, IconProp, TitleProp, ColumnDefProp, GetRowIdProp, GetRowLabelProp, OnRowClickProp, OnSelectChangeProp, OnSortChangeProp, OnTableDensityChangeProp, SelectedIdsProp, SortStateProp, TableDensityProp, TablePresetProp, BreakpointProp, DensityProp, ChildrenProp, PendingProp, ToneProp, AvatarShapeProp, HeadingLevelProp, HandlerProp, SizeProp, LabelProp, IdProp, DescriptionsLayoutProp, DescriptionsColumnProp, DescriptionsSpanProp, DescriptionsItemsProp, GapProp, SortDirectionProp, OnColumnFilterChangeProp, OnRowProp, TableExpandableProp, TableRowSelectionProp, TableScrollProp, TableStickyProp, TableSummaryProp, DisabledProp, OnClickProp, ValueProp, DefaultValueProp, OnValueChangeProp } from "../vocabulary/index.js";
40
40
  import type { TreeFieldNamesProp, TreeOptionProp } from "./data-entry.prop.js";
41
41
  /**
42
42
  * One key in a `Legend`: a tone, and the words that tone stands for.
@@ -605,6 +605,20 @@ export type ScrollAreaProp = {
605
605
  * from it — anchoring must never be the only route back to new content.
606
606
  */
607
607
  onAnchoredChange?: (anchored: boolean) => void;
608
+ /**
609
+ * Accessible name for the scroll REGION — the `tabindex="0"` element a keyboard user lands on to
610
+ * scroll content taller or wider than the box.
611
+ *
612
+ * Optional on purpose. A consumer is never forced to invent a name for every scrolling box: left
613
+ * out, the region takes the localized `dataDisplay.scrollArea.region` default ("Scrollable
614
+ * region"), which is what a screen-reader user needs to hear anyway — that the arrow keys now
615
+ * scroll something. Pass a plain string when the screen can say WHICH region ("Activity log"); a
616
+ * non-string node cannot be an `aria-label`, so it falls back to the default.
617
+ *
618
+ * The region is only announced while it HAS overflow to reach, on the axes `orientation` opens:
619
+ * no overflow, no tab stop, no role and no name. (gh#821)
620
+ */
621
+ label?: LabelProp;
608
622
  };
609
623
  /**
610
624
  * @see TimelineGrid — one COLUMN of the grid (a day, a room, a machine). The label names the
@@ -1018,3 +1032,97 @@ export type CardTabItemProp = {
1018
1032
  /** Ant Design `closeIcon` — replaces the default × on this tab's remove button. */
1019
1033
  closeIcon?: React.ReactNode;
1020
1034
  };
1035
+ /**
1036
+ * @see Marquee — direction of travel, spelled LOGICALLY.
1037
+ *
1038
+ * `react-fast-marquee` says `left | right`; this says `start | end`, the edge the content travels
1039
+ * TOWARDS. `start` is the default because it is the one that follows the reading direction: the
1040
+ * next item arrives from the end edge, exactly as the next word does. Under `dir="rtl"` both
1041
+ * members mirror with the text, so an Arabic page's default marquee travels rightwards with no
1042
+ * prop change at the call site.
1043
+ *
1044
+ * The vertical members of the prior art (`up` / `down`) are NOT ported. A vertical ticker fights
1045
+ * the page scroll and has a different failure mode; porting an axis nobody asked for is how a
1046
+ * single-responsibility component stops being one.
1047
+ */
1048
+ export type MarqueeDirectionProp = "start" | "end";
1049
+ /**
1050
+ * @see Marquee — travel pace, as an ordinal over the `--marquee-interval` motion token rather than
1051
+ * the prior art's raw `speed` in px/s.
1052
+ *
1053
+ * A px/s number is neither themeable nor width-aware: 50px/s reads as a crawl on a 2560px display
1054
+ * and a sprint on a 390px phone. The ordinal resolves to a duration per SCREENFUL, so the
1055
+ * perceived pace is the same at every width, and a service retunes all of them from one token.
1056
+ */
1057
+ export type MarqueeSpeedProp = "slow" | "base" | "fast";
1058
+ /**
1059
+ * @see Marquee — a track of content that travels continuously, carrying the WCAG 2.2.2 pause
1060
+ * control that makes it conformant.
1061
+ *
1062
+ * The clone count is MEASURED (content width against viewport width, re-measured on resize) —
1063
+ * the behaviour no CSS-only marquee can have — and the pause control is why this is a component
1064
+ * rather than a snippet. See `src/components/data-display/marquee.tsx` for the full argument and
1065
+ * for what is deliberately not ported from `react-fast-marquee`.
1066
+ */
1067
+ export type MarqueeProp = Omit<React.HTMLAttributes<HTMLDivElement>, "onPlay" | "onPause" | "children"> & {
1068
+ /**
1069
+ * The row of content — logos, headlines, status chips. Rendered ONCE for real; every further
1070
+ * copy that fills the track is an `aria-hidden` + `inert` clone, so the accessibility tree and
1071
+ * the tab order see the content exactly once however wide the viewport is.
1072
+ */
1073
+ children?: ChildrenProp;
1074
+ /**
1075
+ * Whether the track is moving. The controlled member of the triad — pass it with
1076
+ * `onPlayChange`.
1077
+ *
1078
+ * WCAG 2.2.2 is satisfied by the built-in control whichever way this is driven; a controlled
1079
+ * `play` exists so one "stop all motion" switch can halt every marquee on a page at once.
1080
+ */
1081
+ play?: boolean;
1082
+ /**
1083
+ * Uncontrolled initial state. Default `true`.
1084
+ *
1085
+ * WebAIM recommends animated content be paused by default where the motion is not the point of
1086
+ * the screen (https://webaim.org/techniques/carousels/); `defaultPlay={false}` is that screen,
1087
+ * and it still starts from the built-in control.
1088
+ */
1089
+ defaultPlay?: boolean;
1090
+ /** Fires on every transition, from the built-in control or from a controlled write. */
1091
+ onPlayChange?: (play: boolean) => void;
1092
+ /** Edge the content travels towards. Default `start` — the reading direction. */
1093
+ direction?: MarqueeDirectionProp;
1094
+ /** Pace ordinal over `--marquee-interval`. Default `base`. */
1095
+ speed?: MarqueeSpeedProp;
1096
+ /**
1097
+ * Space between items AND between copies, as a token step. Defaults to the
1098
+ * `--marquee-gap-inline` token, so a service sets the resting rhythm once.
1099
+ */
1100
+ gap?: GapProp;
1101
+ /**
1102
+ * Also pause while the pointer is over the track. Default `false`.
1103
+ *
1104
+ * An ADDITION, never the mechanism: a keyboard user never hovers, so hover-to-pause alone is
1105
+ * exactly the WCAG 2.2.2 failure (technique F16) the prior art ships. Focus inside the track
1106
+ * always pauses it, whatever this says, because a link that is moving cannot be activated.
1107
+ */
1108
+ pauseOnHover?: boolean;
1109
+ /**
1110
+ * Fade both edges over `--marquee-mask-width` so items enter and leave instead of being cut.
1111
+ * Default `false`. A mask, not the prior art's opaque `gradientColor` — a gradient painted in a
1112
+ * fixed colour cannot follow a themed or dark background.
1113
+ */
1114
+ fade?: boolean;
1115
+ /**
1116
+ * Names the CONTENT, not the button: "partner logos", "お知らせ". The control's accessible name
1117
+ * is built from it ("Pause scrolling: partner logos"), so a page with several tracks can tell
1118
+ * them apart by voice as well as by eye.
1119
+ *
1120
+ * It is not the whole button name on purpose. A caller string would read the same in both
1121
+ * states, and a control that says "pause" while it resumes is worse than an unnamed one; the
1122
+ * verb stays the component's, and the catalog composes the two in each locale's own order.
1123
+ * Optional: omitted, the control takes the localized verb alone. A non-string node cannot be an
1124
+ * `aria-label`, so it is ignored.
1125
+ */
1126
+ label?: LabelProp;
1127
+ className?: ClassNameProp;
1128
+ };
@@ -1,6 +1,6 @@
1
1
  /** Foundation component prop types — @see docs/COMPONENTS.md#foundation */
2
2
  import type * as React from "react";
3
- import type { ActivityAnnounceProp, ActivityVariantProp, AsChildProp, ButtonSizeProp, ButtonVariantProp, ChildrenProp, ClassNameProp, DescriptionProp, DisabledProp, FontWeightProp, HeadingLevelProp, IconSizeProp, IdProp, LabelProp, OnClickProp, OnOpenChangeProp, OpenProp, PendingProp, RevealDelayProp, ShapeProp, SizeProp, TextAlignProp, TextSizeProp, TextToneProp, TextWhitespaceProp, TitleLevelProp, TypographyActionsConfigProp, TypographyCopyConfigProp, TypographyEditConfigProp, TypographyEllipsisConfigProp, TypographyTypeProp } from "../vocabulary/index.js";
3
+ import type { ActivityAnnounceProp, ActivityVariantProp, AsChildProp, ButtonSizeProp, ButtonVariantProp, ChildrenProp, ClassNameProp, DescriptionProp, DisabledProp, FontWeightProp, HeadingLevelProp, IconSizeProp, IdProp, InViewAmountProp, LabelProp, OnClickProp, OnOpenChangeProp, OpenProp, PendingProp, RevealDelayProp, RevealTriggerProp, ShapeProp, SizeProp, TextAlignProp, TextSizeProp, TextToneProp, TextWhitespaceProp, TitleLevelProp, TypographyActionsConfigProp, TypographyCopyConfigProp, TypographyEditConfigProp, TypographyEllipsisConfigProp, TypographyTypeProp } from "../vocabulary/index.js";
4
4
  /**
5
5
  * @see Text — typographic primitive; replaces hand-rolled `<span className="text-[13px] …">`.
6
6
  *
@@ -87,10 +87,24 @@ export type TextProp = Omit<React.HTMLAttributes<HTMLElement>, "color"> & Omit<T
87
87
  rel?: string;
88
88
  download?: React.AnchorHTMLAttributes<HTMLAnchorElement>["download"];
89
89
  };
90
- /** @see Heading — h1..h4 sized from the `--heading-h*` tokens. */
90
+ /** @see Heading — h1..h4 sized from the `--heading-h*` tokens, or from `size` when one is given. */
91
91
  export type HeadingProp = Omit<React.HTMLAttributes<HTMLHeadingElement>, "color"> & {
92
92
  /** Heading level — sets size token AND the semantic element (override the element with `as`). */
93
93
  level?: HeadingLevelProp;
94
+ /**
95
+ * Visual size, overriding the step `level` would have taken — the SAME ten-step ladder
96
+ * `Text size` reads, so a headline and a stat figure beside it are one step name apart rather
97
+ * than a lookup between two ramps.
98
+ *
99
+ * `level` still owns the document outline, and that separation is the point (gh#826). The
100
+ * enterprise heading ramp tops out at `--heading-h1` ≈ 20px by deliberate 渋み restraint, so a
101
+ * marketing hero had no way to be both a real `<h1>` and 54px: the display tokens existed,
102
+ * nothing public reached them, and every marketing page wrote its own class instead. Now it is
103
+ * `<Heading level={1} size="5xl">`, and no admin screen's `<h1>` moves.
104
+ *
105
+ * Omit it and `level` decides, exactly as before — this is an override, not a second default.
106
+ */
107
+ size?: TextSizeProp;
94
108
  as?: "h1" | "h2" | "h3" | "h4" | "div";
95
109
  tone?: TextToneProp;
96
110
  align?: TextAlignProp;
@@ -284,10 +298,20 @@ export type ButtonProp = React.ButtonHTMLAttributes<HTMLButtonElement> & {
284
298
  };
285
299
  /**
286
300
  * @see Reveal — entrance-motion primitive (staggered fade-up). Wraps content in a real element
287
- * that animates in on mount reading the DS motion tokens (`--duration-slow`, `--ease-emphasized`,
301
+ * that animates in reading the DS motion tokens (`--duration-slow`, `--ease-emphasized`,
288
302
  * `--reveal-distance`), replacing hand-rolled `@keyframes` + `.app-reveal`/`.d1..d6` classes.
289
303
  * Honours `prefers-reduced-motion` — the animation is dropped and content stays fully visible with
290
304
  * no layout shift.
305
+ *
306
+ * `on` moves the TRIGGER and nothing else (gh#829): `"mount"` is the historical behaviour, `"view"`
307
+ * waits for the viewport. There is deliberately no second `ScrollReveal` component — same
308
+ * animation, same tokens, same reduced-motion contract, one extra prop.
309
+ *
310
+ * **Visibility is never gated on the observer.** The resting state in the stylesheet is the FINAL,
311
+ * fully visible one; only a mounted component that has attached a live `IntersectionObserver` and
312
+ * measured the element as still outside the viewport writes the hidden state. A server render,
313
+ * jsdom, a browser without `IntersectionObserver` and `prefers-reduced-motion: reduce` therefore
314
+ * all show the content — a reveal that never reveals is the worst outcome available here.
291
315
  */
292
316
  export type RevealProp = React.HTMLAttributes<HTMLDivElement> & {
293
317
  /** Child content to reveal on enter. */
@@ -297,6 +321,26 @@ export type RevealProp = React.HTMLAttributes<HTMLDivElement> & {
297
321
  * `--reveal-stagger-step` of delay so sibling reveals cascade.
298
322
  */
299
323
  delay?: RevealDelayProp;
324
+ /**
325
+ * What starts the entrance. Default `"mount"` — today's behaviour, so nothing moves for existing
326
+ * consumers. `"view"` holds the entrance until the element reaches the viewport.
327
+ */
328
+ on?: RevealTriggerProp;
329
+ /**
330
+ * `on="view"` only. Reveal once and never re-hide. Default `true` — a `Reveal` is a ONE-SHOT
331
+ * entrance under either trigger, so this keeps `"view"` semantically identical to `"mount"`.
332
+ * (Motion's `useInView`, where this prop comes from, defaults it to `false`; that is the neutral
333
+ * reading for a general-purpose observer, not for an entrance primitive.) `false` re-plays the
334
+ * entrance on every re-entry.
335
+ */
336
+ once?: boolean;
337
+ /**
338
+ * `on="view"` only. How much of the element must be visible before it reveals — Motion's
339
+ * `amount`, unchanged: `"some"` (default, any pixel) | `"all"` | an explicit `0..1` ratio.
340
+ * `"all"` is clamped to the most the element's own box can show inside the viewport, so a
341
+ * full-height section still reveals.
342
+ */
343
+ amount?: InViewAmountProp;
300
344
  /**
301
345
  * Merge the reveal behaviour onto the single child element (Radix `Slot`) instead of rendering a
302
346
  * wrapper `<div>` — use when an extra box would break a grid/flex layout. Default `false`.
@@ -1657,3 +1657,197 @@ export type DraggablePanelProp = Omit<React.HTMLAttributes<HTMLElement>, "title"
1657
1657
  disabled?: DisabledProp;
1658
1658
  className?: ClassNameProp;
1659
1659
  };
1660
+ /**
1661
+ * Masonry column count — Ant Design `Masonry columns`, in this library's responsive shape.
1662
+ *
1663
+ * A plain number is that many columns at every width. The object form is the step map
1664
+ * `Flex direction` already uses — `base` plus `sm` 40rem · `md` 48rem · `lg` 64rem · `xl` 80rem —
1665
+ * and each omitted step falls back to the widest one below it, exactly as the `--flex-direction-*`
1666
+ * cascade does in CSS.
1667
+ *
1668
+ * **antd spells the steps `xs sm md lg xl xxl` and this does not.** The two ends are the
1669
+ * divergence: antd's `xs` is its mobile-first floor, which is this library's `base` (a second
1670
+ * spelling of one axis is what `check:prop-vocabulary` exists to prevent), and `xxl` has no step
1671
+ * here at all. An `xs` or `xxl` key is therefore never silently dropped — TypeScript rejects it as
1672
+ * an excess property, and a value that reaches the component anyway (a plain-JS caller, a widened
1673
+ * object) is named in a development-only warning. See docs/DESIGN-AUTHORITY.md.
1674
+ * @see Masonry
1675
+ */
1676
+ export type MasonryColumnsProp = number | Partial<Record<"base" | BreakpointProp, number>>;
1677
+ /**
1678
+ * Masonry spacing — Ant Design `Masonry gutter`, on this library's `GapProp` scale.
1679
+ *
1680
+ * One step applies to both axes; the tuple is antd's `[Gap, Gap]`, i.e. `[inline, block]`.
1681
+ * The value is a TOKEN STEP rather than antd's raw pixel number, so a masonry breathes with
1682
+ * `--scaling` (density) like every other gap in the package.
1683
+ * @see Masonry
1684
+ */
1685
+ export type MasonryGapProp = GapProp | [GapProp, GapProp];
1686
+ /**
1687
+ * One tile handed to `Masonry items` — Ant Design `MasonryItem`, field for field.
1688
+ * @see Masonry
1689
+ */
1690
+ export type MasonryItemProp<TData = unknown> = {
1691
+ /** Identity of the tile across re-orders and re-measures. Ant Design `MasonryItem.key`. */
1692
+ key: React.Key;
1693
+ /** The tile itself. Wins over `itemRender`, as in Ant Design. */
1694
+ children?: ReactNode;
1695
+ /**
1696
+ * Pin the tile to a column INDEX (0-based) instead of letting it fall into the shortest one.
1697
+ * Ant Design `MasonryItem.column`, clamped to the live column count.
1698
+ */
1699
+ column?: number;
1700
+ /**
1701
+ * Declared block size, in pixels, used INSTEAD of measuring the tile.
1702
+ *
1703
+ * antd declares and documents this field and then never reads it — its layout is measured from
1704
+ * `getBoundingClientRect()` alone, and all six of its demos carry their heights in `data`. A
1705
+ * prop that does nothing is worse than an absent one, so here the documented meaning is the
1706
+ * real one: a finite `height` sizes the tile and skips its measurement, which is what makes a
1707
+ * first paint (and an SSR render) land in the right place.
1708
+ */
1709
+ height?: number;
1710
+ /** Arbitrary payload handed back to `itemRender` and `onLayoutChange`. Ant Design `MasonryItem.data`. */
1711
+ data?: TData;
1712
+ };
1713
+ /** One row of the `onLayoutChange` payload: the item, plus the column it landed in. @see Masonry */
1714
+ export type MasonryLayoutEntryProp<TData = unknown> = MasonryItemProp<TData> & {
1715
+ /** 0-based column index the item occupies. */
1716
+ column: number;
1717
+ };
1718
+ /**
1719
+ * @see Masonry — Ant Design's `Masonry` (6.0.0): tiles of unequal height packed into columns.
1720
+ *
1721
+ * Ant's own surface, field for field: `items`, `itemRender`, `columns`, `fresh`, `onLayoutChange`.
1722
+ * The three named divergences (`gap` for `gutter`, the breakpoint step names, and the absence of
1723
+ * `classNames`/`styles`) each carry their reason at the field, and all three are recorded in
1724
+ * docs/DESIGN-AUTHORITY.md.
1725
+ */
1726
+ export type MasonryProp<TData = unknown> = {
1727
+ /** The tiles, in READING order — which is also DOM order. Ant Design `items`. */
1728
+ items?: MasonryItemProp<TData>[];
1729
+ /**
1730
+ * Renders a tile that carries no `children`. Receives the item plus its live `index` and
1731
+ * `column`, exactly as Ant Design does. Ant Design `itemRender`.
1732
+ */
1733
+ itemRender?: (item: MasonryItemProp<TData> & {
1734
+ index: number;
1735
+ column: number;
1736
+ }) => ReactNode;
1737
+ /** Column count, fixed or per step. Ant Design `columns`, default `3`. */
1738
+ columns?: MasonryColumnsProp;
1739
+ /**
1740
+ * Spacing between tiles, on the `GapProp` scale. Ant Design calls this `gutter` and defaults it
1741
+ * to `0`; the name here is `gap` because this package already owns that axis under that name
1742
+ * (`Flex`, `ResponsiveGrid`, `AuthStack`, …) and `check:prop-vocabulary` maps a field called
1743
+ * `gap` to `GapProp`. The antd default is kept: omitted means `"none"`.
1744
+ */
1745
+ gap?: MasonryGapProp;
1746
+ /**
1747
+ * NOT A PROP — Ant Design's name for `gap`, kept in the type so that arriving from antd's docs
1748
+ * is a compile error that says where to go, rather than a tile grid with no spacing and no
1749
+ * complaint. Passing it also warns in development.
1750
+ *
1751
+ * @deprecated Ant Design spells this `gutter`; in `@godxjp/ui` it is `gap`, and it takes a
1752
+ * `GapProp` token step (`"sm"`, `4`, `["md", "lg"]`) rather than antd's raw pixel number.
1753
+ */
1754
+ gutter?: never;
1755
+ /**
1756
+ * Keep watching every tile's own size, not just the container's.
1757
+ *
1758
+ * Ant Design `fresh`, and it means exactly one thing in Ant's source: each tile is wrapped in
1759
+ * its own `ResizeObserver` so a tile that changes height on its own — an image decoding, a
1760
+ * "show more" expanding, a chart settling — re-packs the columns. Off (the default), the layout
1761
+ * is re-measured only when the CONTAINER resizes or a descendant `load`/`error` event fires, so
1762
+ * a tile that grows in place leaves a gap or overlaps its neighbour until something else moves.
1763
+ */
1764
+ fresh?: boolean;
1765
+ /**
1766
+ * Fires when the column assignment changes. Ant Design `onLayoutChange`.
1767
+ *
1768
+ * Ant's own documented type is `({ key, column }[]) => void`, but its implementation hands back
1769
+ * the WHOLE item spread with `column` added — so `data` and `children` come with it. This ports
1770
+ * the implementation (a superset of the documented payload) rather than the narrower doc.
1771
+ *
1772
+ * It fires only once every tile has a resolved position and the count matches `items`, and is
1773
+ * deduped: an identical [item, column] list does not call it again.
1774
+ */
1775
+ onLayoutChange?: (layout: MasonryLayoutEntryProp<TData>[]) => void;
1776
+ id?: IdProp;
1777
+ className?: ClassNameProp;
1778
+ };
1779
+ /**
1780
+ * The scroll box an `Affix` pins against — Ant Design `Affix target`, shape for shape, and the
1781
+ * same lazy getter `FloatButton.BackTop target` already spells here.
1782
+ *
1783
+ * It is a FUNCTION and not an element because the element does not exist on the render that
1784
+ * declares it: the caller writes `target={() => scrollRef.current}` and the component calls it
1785
+ * after mount. `window` (the default) means the document viewport.
1786
+ * @see Affix
1787
+ */
1788
+ export type AffixTargetProp = () => Window | HTMLElement | null;
1789
+ /**
1790
+ * @see Affix — Ant Design's `Affix`: pin an element to its scrollport once the page scrolls past
1791
+ * it, and SAY SO, which is the whole difference from `position: sticky`.
1792
+ *
1793
+ * antd's surface, field for field — `offsetTop`, `offsetBottom`, `target`, `onChange` — with the
1794
+ * two physical offsets renamed to their logical axis (see the fields). `onChange` keeps antd's
1795
+ * name and antd's payload: it is an OBSERVATION of a derived boolean, not the setter half of a
1796
+ * controlled value, so the `value`/`defaultValue`/`onValueChange` triad does not apply to it —
1797
+ * the same call `Attachments.onChange` and `ActionsFeedback.onChange` already make here.
1798
+ */
1799
+ export type AffixProp = {
1800
+ /** What gets pinned. Must not itself be `position: absolute` — antd's note, and it is the same
1801
+ * note here, because the pin works by making this element `position: fixed`. */
1802
+ children?: ChildrenProp;
1803
+ /**
1804
+ * Distance from the scrollport's BLOCK-START edge at which the element pins, in pixels.
1805
+ * Ant Design `offsetTop`, default `0`.
1806
+ *
1807
+ * The name is the logical axis, not `top`, for the reason `check:rtl` exists and the reason
1808
+ * `inset-block-start` replaced `top` in every stylesheet here: a sticky bar in a
1809
+ * `writing-mode: vertical-rl` document pins against the axis it flows on, and "top" is only
1810
+ * the right word for one of the four. `offsetTop` is still FINDABLE — it is declared `never`
1811
+ * below, so arriving from antd's docs is a compile error that names the replacement.
1812
+ *
1813
+ * Pass neither offset and this defaults to `0` (antd's `internalOffsetTop` rule exactly:
1814
+ * the block-start offset defaults to `0` ONLY when the block-end offset is also absent).
1815
+ */
1816
+ offsetBlockStart?: number;
1817
+ /**
1818
+ * Distance from the scrollport's BLOCK-END edge at which the element pins, in pixels.
1819
+ * Ant Design `offsetBottom`. No default — passing it is what selects block-end pinning.
1820
+ *
1821
+ * Setting both is antd's own precedence: block-start wins, and block-end is only consulted
1822
+ * when the element has not pinned to the start.
1823
+ */
1824
+ offsetBlockEnd?: number;
1825
+ /**
1826
+ * NOT A PROP — Ant Design's name for `offsetBlockStart`, kept in the type so that arriving from
1827
+ * antd's docs is a compile error that says where to go, rather than a header that never pins
1828
+ * and never complains. Passing it also warns in development.
1829
+ *
1830
+ * @deprecated Ant Design spells this `offsetTop`; in `@godxjp/ui` it is `offsetBlockStart` —
1831
+ * the logical axis, per `check:rtl` and docs/DESIGN-AUTHORITY.md.
1832
+ */
1833
+ offsetTop?: never;
1834
+ /**
1835
+ * NOT A PROP — Ant Design's name for `offsetBlockEnd`. See `offsetTop`.
1836
+ *
1837
+ * @deprecated Ant Design spells this `offsetBottom`; in `@godxjp/ui` it is `offsetBlockEnd`.
1838
+ */
1839
+ offsetBottom?: never;
1840
+ /** The scroll box to pin against. Ant Design `target`, default `() => window`. */
1841
+ target?: AffixTargetProp;
1842
+ /**
1843
+ * Fires when the pinned state FLIPS, and only then — never on a scroll frame that did not
1844
+ * change it. Ant Design `onChange`, whose payload is the new state.
1845
+ *
1846
+ * antd types the argument optional (`(affixed?: boolean) => void`) because its own call site can
1847
+ * pass `undefined`; here the transition is derived from a boolean, so the argument is always
1848
+ * present and the type says so. A handler written against antd's signature still compiles.
1849
+ */
1850
+ onChange?: (affixed: boolean) => void;
1851
+ id?: IdProp;
1852
+ className?: ClassNameProp;
1853
+ };