@godxjp/ui 23.1.0 → 23.2.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 (56) hide show
  1. package/dist/components/data-display/card.d.ts +45 -2
  2. package/dist/components/data-display/card.js +4 -2
  3. package/dist/components/data-display/feature-list.d.ts +26 -0
  4. package/dist/components/data-display/feature-list.js +29 -0
  5. package/dist/components/data-display/index.d.ts +6 -0
  6. package/dist/components/data-display/index.js +6 -0
  7. package/dist/components/data-display/swatch.d.ts +24 -0
  8. package/dist/components/data-display/swatch.js +19 -0
  9. package/dist/components/data-display/thumbnail.d.ts +37 -0
  10. package/dist/components/data-display/thumbnail.js +20 -0
  11. package/dist/components/feedback/index.d.ts +2 -2
  12. package/dist/components/feedback/index.js +19 -1
  13. package/dist/components/feedback/skeleton.d.ts +33 -3
  14. package/dist/components/feedback/skeleton.js +154 -3
  15. package/dist/components/layout/flex.d.ts +1 -1
  16. package/dist/components/layout/flex.js +17 -5
  17. package/dist/components/layout/sidebar.js +9 -2
  18. package/dist/components/navigation/tabs-scroll.d.ts +27 -0
  19. package/dist/components/navigation/tabs-scroll.js +36 -1
  20. package/dist/components/navigation/tabs.d.ts +2 -2
  21. package/dist/components/navigation/tabs.js +44 -12
  22. package/dist/i18n/messages/en.json +5 -0
  23. package/dist/i18n/messages/ja.json +5 -0
  24. package/dist/i18n/messages/vi.json +5 -0
  25. package/dist/props/components/data-display.prop.d.ts +93 -0
  26. package/dist/props/components/feedback.prop.d.ts +85 -1
  27. package/dist/props/components/layout.prop.d.ts +72 -1
  28. package/dist/props/components/navigation.prop.d.ts +72 -1
  29. package/dist/props/registry.d.ts +118 -0
  30. package/dist/props/registry.js +134 -0
  31. package/dist/styles/alert-layout.css +139 -0
  32. package/dist/styles/card-layout.css +48 -0
  33. package/dist/styles/data-display-layout.css +88 -0
  34. package/dist/styles/navigation-layout.css +76 -3
  35. package/dist/styles/shell-layout.css +18 -0
  36. package/dist/tokens/components/card.css +2 -0
  37. package/dist/tokens/components/data-display.css +23 -0
  38. package/dist/tokens/components/feedback.css +18 -0
  39. package/dist/tokens/components/navigation.css +2 -0
  40. package/dist/tokens/foundation.css +18 -16
  41. package/docs/CONSUMER-RULES.md +1 -1
  42. package/docs/DESIGN-AUTHORITY.md +2 -1
  43. package/docs/FRAME-COVERAGE-REPORT.md +29 -3
  44. package/docs/assets/shot-landscape.svg +12 -0
  45. package/docs/assets/shot-portrait.svg +10 -0
  46. package/docs/data-display/card/index.tsx +40 -0
  47. package/docs/data-display/feature-list.tsx +159 -0
  48. package/docs/data-display/swatch.tsx +107 -0
  49. package/docs/data-display/thumbnail.tsx +128 -0
  50. package/docs/feedback/skeleton.tsx +57 -0
  51. package/docs/layout/flex.tsx +29 -0
  52. package/docs/layout/sidebar.tsx +66 -10
  53. package/docs/navigation/tabs.tsx +59 -0
  54. package/package.json +4 -7
  55. package/scripts/consumer-rule.md +16 -0
  56. package/scripts/ui-audit.mjs +3 -4
@@ -1,9 +1,11 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { mergeAriaIds } from "../../lib/field-a11y.js";
4
4
  import { cn } from "../../lib/utils.js";
5
5
  import { flexGapClass, padStyle, padStepToken } from "../../lib/variants.js";
6
6
  const toCssLength = (value) => typeof value === "number" ? `${value}px` : value;
7
+ const rawBreakpoint = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
8
+ const rawBreakpointRule = (axis, px) => `@media (width ${axis === "below" ? "<" : ">="} ${px}px){.ui-flex[data-hide-${axis}-raw="${px}"]{display:none}}`;
7
9
  function Flex({
8
10
  as: Element = "div",
9
11
  direction = "row",
@@ -21,6 +23,8 @@ function Flex({
21
23
  wrap = false,
22
24
  hideBelow,
23
25
  hideFrom,
26
+ hideBelowRaw,
27
+ hideFromRaw,
24
28
  fill = false,
25
29
  width,
26
30
  className,
@@ -42,6 +46,8 @@ function Flex({
42
46
  "aria-describedby": mergeAriaIds(props["aria-describedby"], ariaErrorMessage)
43
47
  };
44
48
  }
49
+ const belowRaw = rawBreakpoint(hideBelowRaw);
50
+ const fromRaw = rawBreakpoint(hideFromRaw);
45
51
  const responsive = typeof direction === "object";
46
52
  const axes = responsive ? direction : { base: direction };
47
53
  const cssDirection = (axis) => axis === "col" ? "column" : axis;
@@ -51,7 +57,7 @@ function Flex({
51
57
  cssDirection(axis)
52
58
  ])
53
59
  ) : {};
54
- return /* @__PURE__ */ jsx(
60
+ return /* @__PURE__ */ jsxs(
55
61
  Element,
56
62
  {
57
63
  "data-direction": responsive ? "responsive" : direction,
@@ -63,8 +69,10 @@ function Flex({
63
69
  "data-align": align,
64
70
  "data-justify": justify,
65
71
  "data-wrap": wrap ? "true" : void 0,
66
- "data-hide-below": hideBelow,
67
- "data-hide-from": hideFrom,
72
+ "data-hide-below": belowRaw === void 0 ? hideBelow : void 0,
73
+ "data-hide-from": fromRaw === void 0 ? hideFrom : void 0,
74
+ "data-hide-below-raw": belowRaw,
75
+ "data-hide-from-raw": fromRaw,
68
76
  "data-fill": fill ? "" : void 0,
69
77
  "data-width-raw": width === void 0 ? void 0 : "",
70
78
  "data-gap-raw": gapRaw,
@@ -79,7 +87,11 @@ function Flex({
79
87
  ...padStyle(pad, padRaw)
80
88
  },
81
89
  ...domProps,
82
- children
90
+ children: [
91
+ belowRaw === void 0 ? null : /* @__PURE__ */ jsx("style", { href: `ui-flex-hide-below-raw-${belowRaw}`, precedence: "ui-flex-raw-breakpoint", children: rawBreakpointRule("below", belowRaw) }),
92
+ fromRaw === void 0 ? null : /* @__PURE__ */ jsx("style", { href: `ui-flex-hide-from-raw-${fromRaw}`, precedence: "ui-flex-raw-breakpoint", children: rawBreakpointRule("from", fromRaw) }),
93
+ children
94
+ ]
83
95
  }
84
96
  );
85
97
  }
@@ -27,6 +27,11 @@ function SidebarSection({
27
27
  function SidebarIcon({ icon: Icon }) {
28
28
  return /* @__PURE__ */ jsx("span", { className: "sb-icon", children: Icon ? /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true" }) : null });
29
29
  }
30
+ function SidebarTrailingIcon({
31
+ icon: Icon
32
+ }) {
33
+ return /* @__PURE__ */ jsx("span", { className: "sb-trailing-icon", children: /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true" }) });
34
+ }
30
35
  function SidebarRowContent({
31
36
  item,
32
37
  sub = false,
@@ -48,7 +53,8 @@ function SidebarRowContent({
48
53
  children: item.badge
49
54
  }
50
55
  )
51
- ) : null
56
+ ) : null,
57
+ item.trailingIcon ? /* @__PURE__ */ jsx(SidebarTrailingIcon, { icon: item.trailingIcon }) : null
52
58
  ] });
53
59
  }
54
60
  function SidebarItem({
@@ -305,6 +311,7 @@ function Sidebar({
305
311
  const surface = useNavSurface();
306
312
  collapsed = surface === "drawer" ? false : collapsed;
307
313
  const brandNode = typeof brand === "function" ? brand(collapsed) : brand;
314
+ const footerNode = typeof footer === "function" ? footer(collapsed) : footer;
308
315
  return /* @__PURE__ */ jsxs("div", { className: "sb-root", "data-collapsed": collapsed ? "true" : void 0, children: [
309
316
  brand !== void 0 ? /* @__PURE__ */ jsx(SidebarHeader, { children: brandNode }) : product ? (() => {
310
317
  const interactive = onProductClick != null;
@@ -385,7 +392,7 @@ function Sidebar({
385
392
  ))
386
393
  }
387
394
  ),
388
- footer ? /* @__PURE__ */ jsx("div", { className: "sb-footer", children: footer }) : null
395
+ footerNode ? /* @__PURE__ */ jsx("div", { className: "sb-footer", children: footerNode }) : null
389
396
  ] });
390
397
  }
391
398
  export {
@@ -47,6 +47,33 @@ export declare function useKeepActiveTabVisible(listRef: React.RefObject<HTMLEle
47
47
  * it are the ones that hand it real geometry.
48
48
  */
49
49
  export declare function resolveHiddenTabValues(list: HTMLElement, values: readonly string[]): string[];
50
+ /**
51
+ * The LOGICAL scroll offset of a strip: how far it has moved from its own start edge, growing
52
+ * towards `end` on whichever axis the strip is on and in whichever direction it is written.
53
+ *
54
+ * `scrollLeft` cannot be read raw. Per CSSOM-View an RTL scroller reports `0` at its START edge
55
+ * (the right one) and goes NEGATIVE towards the end, so "scrollLeft went up" means opposite things
56
+ * in the two directions — exactly the bug `start`/`end` exist to prevent, and it would have been
57
+ * invisible in an LTR test suite.
58
+ */
59
+ export declare function readTabsScrollOffset(list: HTMLElement, vertical: boolean): number;
60
+ /**
61
+ * Ant Design `onTabScroll`'s one piece of logic: which edge a movement of the scroll offset went
62
+ * towards. `null` when the offset did not move, so a scroll event that reports the same position
63
+ * (momentum settling, a programmatic re-pin that was already in place) reports nothing.
64
+ */
65
+ export declare function resolveTabsScrollDirection(previous: number, next: number): "start" | "end" | null;
66
+ /**
67
+ * Reports every real movement of the strip's own scrollport to `onScroll` — Ant Design
68
+ * `onTabScroll`. The handler is held in a ref so a consumer's inline arrow function does not
69
+ * re-arm the listener on every render (same contract as `useTabsOverflowValues` above).
70
+ *
71
+ * It reports a `scrollIntoView` re-pin too, and deliberately: antd's does the same, and "the strip
72
+ * moved" is the fact a consumer is subscribing to — not "the user moved it".
73
+ */
74
+ export declare function useTabsScrollReporter(listRef: React.RefObject<HTMLElement | null>, vertical: boolean, onScroll: ((info: {
75
+ direction: "start" | "end";
76
+ }) => void) | undefined): void;
50
77
  /**
51
78
  * Keeps `onChange` fed with the values currently out of the scrollport, recomputing on the two
52
79
  * things that can move them: the strip resizing (a viewport change, a font swap, a tab added or
@@ -78,6 +78,38 @@ function resolveHiddenTabValues(list, values) {
78
78
  });
79
79
  return hidden;
80
80
  }
81
+ function readTabsScrollOffset(list, vertical) {
82
+ if (vertical) return list.scrollTop;
83
+ const rtl = list.ownerDocument?.defaultView?.getComputedStyle(list).direction === "rtl" || list.closest("[dir]")?.getAttribute("dir") === "rtl";
84
+ return rtl ? -list.scrollLeft : list.scrollLeft;
85
+ }
86
+ function resolveTabsScrollDirection(previous, next) {
87
+ if (next === previous) return null;
88
+ return next > previous ? "end" : "start";
89
+ }
90
+ function useTabsScrollReporter(listRef, vertical, onScroll) {
91
+ const onScrollRef = React.useRef(onScroll);
92
+ React.useEffect(() => {
93
+ onScrollRef.current = onScroll;
94
+ });
95
+ const armed = onScroll !== void 0;
96
+ React.useEffect(() => {
97
+ if (!armed) return void 0;
98
+ const list = listRef.current;
99
+ if (!list) return void 0;
100
+ let last = readTabsScrollOffset(list, vertical);
101
+ const report = () => {
102
+ const next = readTabsScrollOffset(list, vertical);
103
+ const direction = resolveTabsScrollDirection(last, next);
104
+ last = next;
105
+ if (direction) onScrollRef.current?.({ direction });
106
+ };
107
+ list.addEventListener("scroll", report, { passive: true });
108
+ return () => {
109
+ list.removeEventListener("scroll", report);
110
+ };
111
+ }, [armed, listRef, vertical]);
112
+ }
81
113
  function useTabsOverflowValues(listRef, values, onChange) {
82
114
  const onChangeRef = React.useRef(onChange);
83
115
  React.useEffect(() => {
@@ -119,9 +151,12 @@ function useTabsOverflowValues(listRef, values, onChange) {
119
151
  export {
120
152
  isTabFullyVisible,
121
153
  prefersReducedMotion,
154
+ readTabsScrollOffset,
122
155
  resolveHiddenTabValues,
123
156
  resolveTabScrollTarget,
157
+ resolveTabsScrollDirection,
124
158
  syncActiveTabIntoView,
125
159
  useKeepActiveTabVisible,
126
- useTabsOverflowValues
160
+ useTabsOverflowValues,
161
+ useTabsScrollReporter
127
162
  };
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import type { TabItemProp, TabsProp } from "../../props/components/navigation.prop.js";
3
- export type { TabItemProp, TabsProp, TabsVariantProp, TabsPlacementProp, TabsExtraProp, TabsOverflowProp, } from "../../props/components/navigation.prop.js";
3
+ export type { TabItemProp, TabsProp, TabsVariantProp, TabsPlacementProp, TabsExtraProp, TabsOverflowProp, TabsAnimatedProp, TabsIndicatorProp, TabsIndicatorSizeProp, TabsScrollDirectionProp, TabsOnScrollProp, } from "../../props/components/navigation.prop.js";
4
4
  export type TabsOrientation = "vertical" | "horizontal";
5
5
  export type TabsItem = TabItemProp;
6
6
  export type TabsProps = Omit<React.ComponentPropsWithoutRef<"div">, "defaultValue" | "dir"> & TabsProp & {
@@ -8,7 +8,7 @@ export type TabsProps = Omit<React.ComponentPropsWithoutRef<"div">, "defaultValu
8
8
  dir?: "ltr" | "rtl";
9
9
  activationMode?: "manual" | "automatic";
10
10
  };
11
- export declare function Tabs({ className, orientation, items, value, defaultValue, onValueChange, activationMode, dir, variant, tabPlacement, size, centered, extra, destroyOnHidden, onEdit, addIcon, hideAdd, closeIcon, onTabClick, overflow, listClassName, contentClassName, children, ...props }: TabsProps): React.JSX.Element;
11
+ export declare function Tabs({ className, orientation, items, value, defaultValue, onValueChange, activationMode, dir, variant, tabPlacement, size, centered, extra, destroyOnHidden, onEdit, addIcon, hideAdd, closeIcon, onTabClick, overflow, animated, indicator, moreIcon, onTabScroll, listClassName, contentClassName, children, ...props }: TabsProps): React.JSX.Element;
12
12
  export declare const TabsList: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
13
13
  variant?: "default" | "line";
14
14
  /**
@@ -17,10 +17,22 @@ import {
17
17
  DropdownMenuItem,
18
18
  DropdownMenuTrigger
19
19
  } from "./dropdown-menu.js";
20
- import { useKeepActiveTabVisible, useTabsOverflowValues } from "./tabs-scroll.js";
20
+ import {
21
+ useKeepActiveTabVisible,
22
+ useTabsOverflowValues,
23
+ useTabsScrollReporter
24
+ } from "./tabs-scroll.js";
25
+ function triggerSizeClassName(size) {
26
+ return cn(
27
+ size === "sm" && "min-h-[var(--tabs-trigger-height-sm)] px-[var(--tabs-trigger-padding-x-sm)] text-[length:var(--tabs-trigger-font-size-sm)]",
28
+ size === "md" && "min-h-[var(--tabs-trigger-height-md)] px-[var(--tabs-trigger-padding-x-md)] text-[length:var(--tabs-trigger-font-size-md)]",
29
+ size === "lg" && "min-h-[var(--tabs-trigger-height-lg)] px-[var(--tabs-trigger-padding-x-lg)] text-[length:var(--tabs-trigger-font-size-lg)]"
30
+ );
31
+ }
21
32
  const TabsFrameContext = React.createContext({
22
33
  orientation: "horizontal",
23
- selectionSuppressed: false
34
+ selectionSuppressed: false,
35
+ size: "md"
24
36
  });
25
37
  function resolveFallbackTabValue(items, requested) {
26
38
  if (!items || items.length === 0) return requested;
@@ -46,6 +58,12 @@ function resolveTabsExtra(extra) {
46
58
  if (isSlotMap) return extra;
47
59
  return { end: extra };
48
60
  }
61
+ function resolveTabsAnimated(animated) {
62
+ if (animated === false) return { inkBar: false, tabPane: false };
63
+ if (animated === true) return { inkBar: true, tabPane: true };
64
+ if (animated === void 0) return { inkBar: true, tabPane: false };
65
+ return { inkBar: animated.inkBar ?? true, tabPane: animated.tabPane ?? false };
66
+ }
49
67
  function resolveTabsAxis(tabPlacement, orientation) {
50
68
  if (tabPlacement) {
51
69
  return {
@@ -86,12 +104,19 @@ function Tabs({
86
104
  closeIcon,
87
105
  onTabClick,
88
106
  overflow = "scroll",
107
+ animated,
108
+ indicator,
109
+ moreIcon,
110
+ onTabScroll,
89
111
  listClassName,
90
112
  contentClassName,
91
113
  children,
92
114
  ...props
93
115
  }) {
94
116
  const { t } = useTranslation();
117
+ const motion = resolveTabsAnimated(animated);
118
+ const indicatorSize = indicator?.size ?? "full";
119
+ const indicatorAlign = indicator?.align ?? "center";
95
120
  const resolvedDefault = resolveFallbackTabValue(items, defaultValue);
96
121
  const narrow = useMaxWidthBreakpoint(
97
122
  TABS_PLACEMENT_BREAKPOINT_TOKEN,
@@ -103,8 +128,8 @@ function Tabs({
103
128
  );
104
129
  const selectionSuppressed = value === void 0 && items != null && items.length > 0 && resolvedDefault === void 0;
105
130
  const frame = React.useMemo(
106
- () => ({ orientation: resolvedOrientation, selectionSuppressed }),
107
- [resolvedOrientation, selectionSuppressed]
131
+ () => ({ orientation: resolvedOrientation, selectionSuppressed, size }),
132
+ [resolvedOrientation, selectionSuppressed, size]
108
133
  );
109
134
  const editable = variant === "editable-card";
110
135
  const collapsible = items != null && overflow === "menu";
@@ -120,6 +145,7 @@ function Tabs({
120
145
  );
121
146
  }, []);
122
147
  useTabsOverflowValues(listRef, itemValues, handleHiddenChange);
148
+ useTabsScrollReporter(listRef, resolvedOrientation === "vertical", onTabScroll);
123
149
  const [mirroredValue, setMirroredValue] = React.useState(
124
150
  value ?? resolvedDefault
125
151
  );
@@ -139,11 +165,7 @@ function Tabs({
139
165
  const showAdd = editable && !hideAdd && Boolean(onEdit);
140
166
  const needsBar = Boolean(extraStart || extraEnd || showAdd || collapsible);
141
167
  const card = variant === "card" || variant === "editable-card";
142
- const sizeTriggerClassName = cn(
143
- size === "sm" && "min-h-[var(--tabs-trigger-height-sm)] px-[var(--tabs-trigger-padding-x-sm)] text-[length:var(--tabs-trigger-font-size-sm)]",
144
- size === "md" && "min-h-[var(--tabs-trigger-height-md)] px-[var(--tabs-trigger-padding-x-md)] text-[length:var(--tabs-trigger-font-size-md)]",
145
- size === "lg" && "min-h-[var(--tabs-trigger-height-lg)] px-[var(--tabs-trigger-padding-x-lg)] text-[length:var(--tabs-trigger-font-size-lg)]"
146
- );
168
+ const sizeTriggerClassName = triggerSizeClassName(size);
147
169
  const list = items ? /* @__PURE__ */ jsx(
148
170
  TabsList,
149
171
  {
@@ -283,6 +305,10 @@ function Tabs({
283
305
  "data-placement": placement,
284
306
  "data-size": size,
285
307
  "data-centered": centered ? "true" : void 0,
308
+ "data-animated-ink-bar": motion.inkBar ? "true" : "false",
309
+ "data-animated-tab-pane": motion.tabPane ? "true" : "false",
310
+ "data-indicator-size": indicatorSize,
311
+ "data-indicator-align": indicatorAlign,
286
312
  orientation: resolvedOrientation,
287
313
  keyboardActivation: activationMode,
288
314
  selectedKey: value ?? (collapsible ? mirroredValue : void 0),
@@ -307,7 +333,11 @@ function Tabs({
307
333
  "data-variant": variant,
308
334
  "data-placement": placement,
309
335
  "data-size": size,
310
- "data-centered": centered ? "true" : void 0
336
+ "data-centered": centered ? "true" : void 0,
337
+ "data-animated-ink-bar": motion.inkBar ? "true" : "false",
338
+ "data-animated-tab-pane": motion.tabPane ? "true" : "false",
339
+ "data-indicator-size": indicatorSize,
340
+ "data-indicator-align": indicatorAlign
311
341
  }),
312
342
  children: items ? /* @__PURE__ */ jsxs(Fragment, { children: [
313
343
  needsBar ? /* @__PURE__ */ jsxs("div", { "data-slot": "tabs-bar", className: "ui-tabs-bar", children: [
@@ -330,7 +360,7 @@ function Tabs({
330
360
  {
331
361
  className: "ui-tabs-overflow",
332
362
  "aria-label": t("navigation.tabs.moreTabs"),
333
- children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "ui-tabs-overflow-icon", "aria-hidden": "true" })
363
+ children: moreIcon ?? /* @__PURE__ */ jsx(MoreHorizontal, { className: "ui-tabs-overflow-icon", "aria-hidden": "true" })
334
364
  }
335
365
  ),
336
366
  /* @__PURE__ */ jsx(DropdownMenuContent, { placement: "bottomEnd", children: overflowItems.map((item) => /* @__PURE__ */ jsx(
@@ -407,7 +437,7 @@ const TabsList = React.forwardRef(
407
437
  TabsList.displayName = "TabsList";
408
438
  const TabsTrigger = React.forwardRef(
409
439
  ({ className, value, disabled, children, onKeyDown, onClick, ...props }, ref) => {
410
- const { orientation, selectionSuppressed } = React.useContext(TabsFrameContext);
440
+ const { orientation, selectionSuppressed, size } = React.useContext(TabsFrameContext);
411
441
  return /* @__PURE__ */ jsx(
412
442
  AriaTab,
413
443
  {
@@ -434,6 +464,8 @@ const TabsTrigger = React.forwardRef(
434
464
  // Selected and focused stay visually distinct (WCAG 2.4.7): selected is a 1px hairline in the
435
465
  // border, focused is the 2px ring plus its halo outside it.
436
466
  "text-muted-foreground ring-offset-background hover:text-foreground ui-focus-ring data-[state=active]:bg-background data-[state=active]:text-foreground group-data-[variant=default]/tabs-list:data-[state=active]:border-primary/25 relative inline-flex flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-3 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:flex-none group-data-[orientation=vertical]/tabs:justify-start group-data-[variant=line]/tabs-list:border-e-0 group-data-[variant=line]/tabs-list:border-b-0 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none",
467
+ // The tier the root was given. Without this the compound form ignored `size` outright.
468
+ triggerSizeClassName(size),
437
469
  className
438
470
  ),
439
471
  render: (domProps, renderProps) => withDomProps("button", "tabs-trigger", { type: "button", ...props }, domProps, {
@@ -283,6 +283,11 @@
283
283
  "dotsLabel": "Choose slide",
284
284
  "goToSlide": "Go to slide {index}"
285
285
  },
286
+ "featureList": {
287
+ "included": "Included",
288
+ "excluded": "Not included",
289
+ "limited": "Limited"
290
+ },
286
291
  "listRow": {
287
292
  "unread": "Unread",
288
293
  "read": "Read"
@@ -276,6 +276,11 @@
276
276
  "dotsLabel": "スライドを選択",
277
277
  "goToSlide": "{index} 番目のスライドへ"
278
278
  },
279
+ "featureList": {
280
+ "included": "含む",
281
+ "excluded": "含まない",
282
+ "limited": "制限あり"
283
+ },
279
284
  "listRow": {
280
285
  "unread": "未読",
281
286
  "read": "既読"
@@ -277,6 +277,11 @@
277
277
  "dotsLabel": "Chọn slide",
278
278
  "goToSlide": "Đến slide {index}"
279
279
  },
280
+ "featureList": {
281
+ "included": "Có",
282
+ "excluded": "Không có",
283
+ "limited": "Giới hạn"
284
+ },
280
285
  "listRow": {
281
286
  "unread": "Chưa đọc",
282
287
  "read": "Đã đọc"
@@ -47,6 +47,99 @@ export type LegendProp = Omit<React.HTMLAttributes<HTMLUListElement>, "children"
47
47
  items: LegendItemProp[];
48
48
  className?: ClassNameProp;
49
49
  };
50
+ /**
51
+ * @see Swatch — a READ-ONLY sample of ONE colour a person chose.
52
+ *
53
+ * ## Why it is not a Legend, a ColorPicker or a Badge
54
+ *
55
+ * `Legend` is a KEY: a closed set of semantic TONES, each with required words beside it, because
56
+ * there the colour stands FOR something. Here the colour IS the content — a brand's
57
+ * `primary_color`, a calendar category, a label a user tinted — so there is no tone it maps to and
58
+ * no second thing for a word to name. `ColorPicker` is the INPUT for the same value, and rendering
59
+ * a disabled input to display one reads as a control that broke. `Badge` is a chip: tinted fill,
60
+ * border, clickable affordance — not a sample of the exact colour.
61
+ *
62
+ * Its mark is the same square Legend draws, one type step larger, because it stands beside a name
63
+ * rather than inside an 11px key.
64
+ */
65
+ export type SwatchProp = Omit<React.HTMLAttributes<HTMLSpanElement>, "children" | "color"> & {
66
+ /**
67
+ * The colour to show, as a CSS colour VALUE — `#7C3AED`, `rgb(…)`, `oklch(…)`. It is DATA the
68
+ * same way `Badge`'s `color` is: a value a person picked in a settings screen, so it arrives as
69
+ * a prop and is never written into a stylesheet. Passing a semantic token's own colour here is
70
+ * the one thing to avoid — a tone that MEANS something belongs on a component that names the
71
+ * meaning (`Badge tone`, `Legend`), not on a sample.
72
+ */
73
+ color: string;
74
+ /**
75
+ * The accessible NAME of the sample, and the reason this component can exist without a visible
76
+ * label. Say what the colour is FOR and what it is: `aria-label={`${t.primaryColor}: ${hex}`}`.
77
+ *
78
+ * With it, the swatch is a `role="img"` that announces that sentence. Without it the swatch is
79
+ * `aria-hidden` — correct, and the only correct option, when a visible line beside it already
80
+ * states the colour. Either way colour is never the sole carrier of the meaning (WCAG 1.4.1);
81
+ * what is NOT offered is a third path where it is.
82
+ */
83
+ "aria-label"?: string;
84
+ };
85
+ /**
86
+ * Whether a `FeatureList` line is in, out, or in with a limit.
87
+ *
88
+ * Three members and no fourth: the axis is INCLUSION, and a list that also carried "coming soon",
89
+ * "beta" or "deprecated" would be encoding a roadmap on the same glyph column as a fact about
90
+ * today. Those are a `Badge` beside the label, which is content the screen already owns.
91
+ */
92
+ export type FeatureStateProp = "included" | "excluded" | "limited";
93
+ /**
94
+ * One line of a `FeatureList`: what state it is in, what it is called, and — optionally — a
95
+ * sentence about it that WRAPS.
96
+ *
97
+ * There is deliberately no field for a quantity ("10,000 req/mo"). Composing it into `label` as
98
+ * `<>API calls <Text tone="muted" tabular>10,000 req/mo</Text></>` is already legal and already
99
+ * audit-clean, so a prop for it would fail question 1 of docs/WHAT-BELONGS-HERE.md — the consumer
100
+ * has a move. What the consumer did NOT have a move for is the glyph column and its alignment,
101
+ * which is what this component owns.
102
+ */
103
+ export type FeatureItemProp = {
104
+ /** In, out, or in with a limit. Drives the glyph, its mark colour and the `sr-only` prefix. */
105
+ state: FeatureStateProp;
106
+ /** What the line is about. Wraps. */
107
+ label: LabelProp;
108
+ /** An optional muted sentence under the label. Wraps; long unbroken tokens break. */
109
+ description?: DescriptionProp;
110
+ };
111
+ /**
112
+ * @see FeatureList — a list of statements, each with a leading state glyph: what a plan includes,
113
+ * what a tier supports, which requirements a submission met.
114
+ */
115
+ export type FeatureListProp = Omit<React.HTMLAttributes<HTMLUListElement>, "children"> & {
116
+ items: FeatureItemProp[];
117
+ className?: ClassNameProp;
118
+ };
119
+ /**
120
+ * Fixed BLOCK size of a `Thumbnail`; the inline size stays intrinsic.
121
+ *
122
+ * A subset of the shared `SizeProp` ladder — `xs` is off the bottom because a 48px frame with a
123
+ * 1px hairline is a favicon, not a thumbnail, and nothing in the reported cases wanted one.
124
+ */
125
+ export type ThumbnailSizeProp = Extract<SizeProp, "sm" | "md" | "lg">;
126
+ /**
127
+ * @see Thumbnail — a framed image at a FIXED HEIGHT and its own intrinsic width, for a wrapping
128
+ * row of pictures whose aspect ratios differ.
129
+ */
130
+ export type ThumbnailProp = Omit<React.ImgHTMLAttributes<HTMLImageElement>, "alt"> & {
131
+ /** Image URL. */
132
+ src: string;
133
+ /**
134
+ * Required, with no way to omit it. Pass `""` for a picture that carries no information the
135
+ * page does not already say — the empty string is a DECISION the author has to make, where a
136
+ * missing attribute is an omission nobody notices (WCAG 1.1.1).
137
+ */
138
+ alt: string;
139
+ /** Frame height. Default `md`. The width follows the picture's own ratio. */
140
+ size?: ThumbnailSizeProp;
141
+ className?: ClassNameProp;
142
+ };
50
143
  /** @see EmptyState */
51
144
  /**
52
145
  * Semantic intent of the EmptyState icon medallion — a subset of the shared `ToneProp` vocabulary
@@ -1,7 +1,7 @@
1
1
  /** Feedback component prop types — @see docs/COMPONENTS.md#feedback */
2
2
  import type * as React from "react";
3
3
  import type { QueryErrorCategory } from "../../lib/query-error.js";
4
- import type { AlertVariantProp, CancelLabelProp, ChildrenProp, ClassNameProp, ConfirmLabelProp, ConfirmVariantProp, DescriptionProp, HandlerProp, IconProp, OpenProp, OnOpenChangeProp, PendingProp, ToneProp, TitleProp } from "../vocabulary/index.js";
4
+ import type { AlertVariantProp, AvatarShapeProp, CancelLabelProp, ChildrenProp, ClassNameProp, ConfirmLabelProp, ConfirmVariantProp, DescriptionProp, HandlerProp, IconProp, OpenProp, OnOpenChangeProp, PendingProp, ShapeProp, SizeProp, ToneProp, TitleProp } from "../vocabulary/index.js";
5
5
  /** @see AlertDialog */
6
6
  export type AlertDialogProp = {
7
7
  open: OpenProp;
@@ -83,3 +83,87 @@ export type SkeletonRowsProp = {
83
83
  rows?: number;
84
84
  columns?: number;
85
85
  };
86
+ /**
87
+ * A skeleton line's MEASURE. `number` is read as pixels, matching antd's
88
+ * `SkeletonParagraphProps["width"]` (components/skeleton/Paragraph.tsx); a string is any CSS length
89
+ * or percentage. It reaches the DOM as the `--skeleton-line-width` custom property rather than a
90
+ * raw `width`, so the line still resolves its own block size and radius from the token tier.
91
+ */
92
+ export type SkeletonWidth = number | string;
93
+ /**
94
+ * @see Skeleton — the placeholder BLOCK. Both fields are antd's, ported onto the block this
95
+ * library already shipped rather than onto a second component beside it.
96
+ */
97
+ export type SkeletonProp = React.HTMLAttributes<HTMLDivElement> & {
98
+ /**
99
+ * Swap the resting pulse for the travelling SHEEN (antd's `active`). antd's non-active skeleton
100
+ * is fully static; this library's block has always pulsed, so the default is left alone and
101
+ * `active` selects the louder of the two motions instead of turning motion on.
102
+ */
103
+ active?: boolean;
104
+ /**
105
+ * `false` renders `children` in place of the placeholder. An OMITTED `loading` still renders the
106
+ * placeholder — antd's `loading || !("loading" in props)`.
107
+ */
108
+ loading?: boolean;
109
+ };
110
+ /** @see SkeletonAvatar */
111
+ export type SkeletonAvatarProp = {
112
+ /** Box, from the `--control-height` tier — the same tier the real `Avatar` sizes from. */
113
+ size?: SizeProp;
114
+ shape?: AvatarShapeProp;
115
+ active?: boolean;
116
+ className?: ClassNameProp;
117
+ };
118
+ /** @see SkeletonButton */
119
+ export type SkeletonButtonProp = {
120
+ size?: SizeProp;
121
+ /** Corner, in `Button`'s own vocabulary: `pill` is antd's `shape="round"`. */
122
+ shape?: ShapeProp;
123
+ /** Fill the inline axis, for a button that spans its column (antd's `block`). */
124
+ block?: boolean;
125
+ active?: boolean;
126
+ className?: ClassNameProp;
127
+ };
128
+ /** @see SkeletonInput */
129
+ export type SkeletonInputProp = {
130
+ size?: SizeProp;
131
+ block?: boolean;
132
+ active?: boolean;
133
+ className?: ClassNameProp;
134
+ };
135
+ /** @see SkeletonNode — a square standing in for a media/custom slot; `children` centres in it. */
136
+ export type SkeletonNodeProp = {
137
+ active?: boolean;
138
+ children?: ChildrenProp;
139
+ className?: ClassNameProp;
140
+ };
141
+ /** @see SkeletonImage */
142
+ export type SkeletonImageProp = {
143
+ active?: boolean;
144
+ className?: ClassNameProp;
145
+ };
146
+ /**
147
+ * @see SkeletonArticle — antd's own `<Skeleton>` shape (avatar + title + paragraph). It is a
148
+ * SIBLING of `Skeleton`, not a replacement: `Skeleton` here is the block antd spells
149
+ * `Skeleton.Node`, and re-pointing it at the article would change what every existing call site
150
+ * renders.
151
+ */
152
+ export type SkeletonArticleProp = {
153
+ avatar?: boolean | Pick<SkeletonAvatarProp, "size" | "shape">;
154
+ /** The heading line. NOT a string title — `false` drops the line, `{ width }` re-measures it. */
155
+ title?: boolean | {
156
+ width?: SkeletonWidth;
157
+ };
158
+ /** `width` as an array measures each row; as a single value it measures the LAST row. */
159
+ paragraph?: boolean | {
160
+ rows?: number;
161
+ width?: SkeletonWidth | SkeletonWidth[];
162
+ };
163
+ /** Pill corners on every line (antd's `round`, whose capsule radius is `--radius-pill` here). */
164
+ round?: boolean;
165
+ active?: boolean;
166
+ loading?: boolean;
167
+ children?: ChildrenProp;
168
+ className?: ClassNameProp;
169
+ };