@godxjp/ui 33.24.0 → 33.25.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 (88) hide show
  1. package/README.md +14 -3
  2. package/agent/START-HERE.md +5 -5
  3. package/agent/components/CatalogDetail.json +7 -3
  4. package/agent/components/DatePicker.json +1 -1
  5. package/agent/components/DropdownMenu.json +1 -0
  6. package/agent/components/EditorialCard.json +2 -1
  7. package/agent/components/Image.json +6 -1
  8. package/agent/components/Pagination.json +5 -0
  9. package/agent/components/Popover.json +1 -0
  10. package/agent/components/Select.json +2 -1
  11. package/agent/components/Toaster.json +1 -1
  12. package/agent/components.json +26 -8
  13. package/agent/index.json +5 -5
  14. package/agent/llms.txt +6 -6
  15. package/agent/patterns/notice-bar.json +16 -0
  16. package/agent/patterns-index.json +15 -0
  17. package/agent/patterns.json +16 -0
  18. package/agent/rules.json +2 -2
  19. package/agent/tokens.json +37 -7
  20. package/dist/components/charts/chart-cartesian.js +5 -1
  21. package/dist/components/charts/chart-summary.d.ts +1 -1
  22. package/dist/components/charts/chart-summary.js +17 -6
  23. package/dist/components/charts/compact-bar-trend.js +4 -3
  24. package/dist/components/charts/pie-chart.js +2 -1
  25. package/dist/components/data-display/image.d.ts +1 -0
  26. package/dist/components/data-display/image.js +9 -1
  27. package/dist/components/data-display/popover.js +11 -0
  28. package/dist/components/data-entry/calendar.js +1 -1
  29. package/dist/components/data-entry/date-picker.js +22 -5
  30. package/dist/components/general/button.js +2 -2
  31. package/dist/components/layout/affix.js +1 -1
  32. package/dist/components/layout/editorial-catalog.d.ts +1 -1
  33. package/dist/components/layout/editorial-catalog.js +53 -15
  34. package/dist/components/navigation/dropdown-menu.d.ts +13 -3
  35. package/dist/components/navigation/dropdown-menu.js +36 -6
  36. package/dist/components/navigation/pagination.d.ts +1 -1
  37. package/dist/components/navigation/pagination.js +13 -1
  38. package/dist/contracts/measurement.json +1 -1
  39. package/dist/i18n/messages/en.json +1 -0
  40. package/dist/i18n/messages/ja.json +1 -0
  41. package/dist/i18n/messages/vi.json +1 -0
  42. package/dist/props/components/data-display.prop.d.ts +2 -0
  43. package/dist/props/components/layout.prop.d.ts +7 -2
  44. package/dist/props/components/navigation.prop.d.ts +3 -0
  45. package/dist/props/registry.d.ts +15 -3
  46. package/dist/props/registry.js +18 -1
  47. package/dist/styles/application.css +24 -0
  48. package/dist/styles/base.css +11 -0
  49. package/dist/styles/control.css +32 -19
  50. package/dist/styles/core.css +1 -0
  51. package/dist/styles/data-display-layout.css +7 -0
  52. package/dist/styles/dialog-layout.css +9 -0
  53. package/dist/styles/editorial-layout.css +2076 -0
  54. package/dist/styles/editorial.css +3 -0
  55. package/dist/styles/index.css +1 -0
  56. package/dist/styles/layers.json +39 -8
  57. package/dist/styles/layout.css +0 -1987
  58. package/dist/styles/precompiled/alert-layout.css +2 -0
  59. package/dist/styles/precompiled/badge-layout.css +2 -0
  60. package/dist/styles/precompiled/base.css +2 -0
  61. package/dist/styles/precompiled/card-layout.css +2 -0
  62. package/dist/styles/precompiled/chart-layout.css +2 -0
  63. package/dist/styles/precompiled/control.css +2 -0
  64. package/dist/styles/precompiled/data-display-layout.css +2 -0
  65. package/dist/styles/precompiled/data-entry-layout.css +2 -0
  66. package/dist/styles/precompiled/dialog-layout.css +2 -0
  67. package/dist/styles/precompiled/editorial-account-layout.css +2 -0
  68. package/dist/styles/precompiled/editorial-layout.css +2 -0
  69. package/dist/styles/precompiled/float-button-layout.css +2 -0
  70. package/dist/styles/precompiled/form-layout.css +2 -0
  71. package/dist/styles/precompiled/icon-layout.css +2 -0
  72. package/dist/styles/precompiled/layout.css +2 -0
  73. package/dist/styles/precompiled/logo-layout.css +2 -0
  74. package/dist/styles/precompiled/motion.css +2 -0
  75. package/dist/styles/precompiled/navigation-layout.css +2 -0
  76. package/dist/styles/precompiled/shell-layout.css +2 -0
  77. package/dist/styles/precompiled/table-layout.css +2 -0
  78. package/dist/styles/precompiled/text-layout.css +2 -0
  79. package/dist/styles/precompiled/toggle.css +2 -0
  80. package/dist/styles/precompiled/vendor-day-picker.css +2 -0
  81. package/dist/styles/precompiled/vendor-sonner.css +2 -0
  82. package/dist/styles.css +1 -1
  83. package/dist/tokens/components/control.css +3 -0
  84. package/dist/tokens/components/editorial.css +9 -6
  85. package/docs/CONSUMER-RULES.md +35 -0
  86. package/package.json +4 -2
  87. package/scripts/prune-css.mjs +56 -8
  88. package/scripts/ui-audit.mjs +36 -0
package/agent/tokens.json CHANGED
@@ -4469,6 +4469,18 @@
4469
4469
  "tier": "component",
4470
4470
  "value": "24rem"
4471
4471
  },
4472
+ {
4473
+ "description": "The listbox hugs its longest row up to this, then wraps rows; the same cap as the search panel so both Select modes size alike. The inset keeps it off the viewport edges.",
4474
+ "name": "--select-content-max-width",
4475
+ "tier": "component",
4476
+ "value": "32rem"
4477
+ },
4478
+ {
4479
+ "description": "Control primitive tokens: heights, horizontal padding, adjacent control sizes.",
4480
+ "name": "--select-content-viewport-inset",
4481
+ "tier": "component",
4482
+ "value": "var(--space-6)"
4483
+ },
4472
4484
  {
4473
4485
  "description": "Seat for a NUMERIC `popupMatchSelectWidth` on the plain listbox. `auto` is the inert default.",
4474
4486
  "name": "--select-content-inline-size",
@@ -4722,7 +4734,7 @@
4722
4734
  "value": "var(--font-size-sm, var(--font-size-base))"
4723
4735
  },
4724
4736
  {
4725
- "description": "gh#888 — φ tier freezes at :root; default = calc(var(--radius) / var(--radius-ratio))",
4737
+ "description": "Scoped at the reader: interactive days fall back to --button-radius; read-only cells to calc(var(--radius) / var(--radius-ratio)).",
4726
4738
  "name": "--calendar-day-radius",
4727
4739
  "tier": "component",
4728
4740
  "value": "initial"
@@ -9771,7 +9783,7 @@
9771
9783
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
9772
9784
  "name": "--editorial-search-shadow",
9773
9785
  "tier": "component",
9774
- "value": "0 4px 16px rgb(var(--shadow-color, 0 0 0) / 0.06)"
9786
+ "value": "initial"
9775
9787
  },
9776
9788
  {
9777
9789
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
@@ -9891,7 +9903,7 @@
9891
9903
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
9892
9904
  "name": "--editorial-search-popup-shadow",
9893
9905
  "tier": "component",
9894
- "value": "0 12px 32px rgb(var(--shadow-color, 0 0 0) / 0.12)"
9906
+ "value": "initial"
9895
9907
  },
9896
9908
  {
9897
9909
  "description": "Default = hsl(var(--border)).",
@@ -10335,7 +10347,7 @@
10335
10347
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
10336
10348
  "name": "--editorial-showcase-caption-shadow",
10337
10349
  "tier": "component",
10338
- "value": "0 4px 16px rgb(var(--shadow-color, 0 0 0) / 0.06)"
10350
+ "value": "initial"
10339
10351
  },
10340
10352
  {
10341
10353
  "description": "scale-exempt: measured editorial geometry; retain the existing size between icon steps",
@@ -10563,7 +10575,7 @@
10563
10575
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
10564
10576
  "name": "--editorial-catalog-card-hover-shadow",
10565
10577
  "tier": "component",
10566
- "value": "0 10px 28px rgb(var(--shadow-color, 0 0 0) / 0.1)"
10578
+ "value": "initial"
10567
10579
  },
10568
10580
  {
10569
10581
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
@@ -10989,7 +11001,7 @@
10989
11001
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
10990
11002
  "name": "--editorial-spotlight-hover-shadow",
10991
11003
  "tier": "component",
10992
- "value": "0 4px 16px rgb(var(--shadow-color, 0 0 0) / 0.06)"
11004
+ "value": "initial"
10993
11005
  },
10994
11006
  {
10995
11007
  "description": "── EditorialCard variant=\"journal\" ──────────────────────────────────────────────────────",
@@ -11151,7 +11163,7 @@
11151
11163
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
11152
11164
  "name": "--editorial-journal-hover-shadow",
11153
11165
  "tier": "component",
11154
- "value": "0 12px 28px rgb(var(--shadow-color, 0 0 0) / 0.08)"
11166
+ "value": "initial"
11155
11167
  },
11156
11168
  {
11157
11169
  "description": "── EditorialMasthead ────────────────────────────────────────────────────────────────────",
@@ -11681,6 +11693,24 @@
11681
11693
  "tier": "component",
11682
11694
  "value": "var(--space-10)"
11683
11695
  },
11696
+ {
11697
+ "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
11698
+ "name": "--editorial-catalog-detail-loading-background",
11699
+ "tier": "component",
11700
+ "value": "initial"
11701
+ },
11702
+ {
11703
+ "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
11704
+ "name": "--editorial-catalog-detail-loading-duration",
11705
+ "tier": "component",
11706
+ "value": "var(--duration-loop)"
11707
+ },
11708
+ {
11709
+ "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
11710
+ "name": "--editorial-catalog-detail-loading-alpha",
11711
+ "tier": "component",
11712
+ "value": "0.5"
11713
+ },
11684
11714
  {
11685
11715
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
11686
11716
  "name": "--editorial-catalog-detail-art-radius",
@@ -54,6 +54,7 @@ function CartesianChart({
54
54
  assertRechartsPeer();
55
55
  const { t, locale } = useTranslation();
56
56
  const fmt = useChartNumberFormat(numberFormat);
57
+ const counts = useChartNumberFormat();
57
58
  const list = React.useMemo(() => listFormat(locale, { style: "narrow", type: "unit" }), [locale]);
58
59
  const points = series.flatMap(
59
60
  (s, seriesIndex) => data.flatMap(
@@ -127,7 +128,10 @@ function CartesianChart({
127
128
  categoryKey,
128
129
  fmt,
129
130
  list,
130
- (c) => t("chart.summaryCartesian", { series: fmt.format(c.series), points: fmt.format(c.points) })
131
+ (c) => t("chart.summaryCartesian", {
132
+ series: counts.format(c.series),
133
+ points: counts.format(c.points)
134
+ })
131
135
  );
132
136
  const resolvedHeight = chartHeight(size, height);
133
137
  const tickFormatter = (value) => fmt.format(value);
@@ -33,7 +33,7 @@ export declare function buildTrendSummary(data: ChartDatum[], categoryKey: strin
33
33
  * Negative values plot at the baseline (a compact trend has no negative axis) but
34
34
  * still read their true figure in the text alternative.
35
35
  */
36
- export declare function trendRatios(data: ChartDatum[], valueKey: string): number[];
36
+ export declare function trendRatios(data: ChartDatum[], valueKey: string): (number | null)[];
37
37
  /**
38
38
  * Resolve `emphasizedIndex` against a row count. Negative counts from the end (`-1` = the latest
39
39
  * datum).
@@ -1,11 +1,16 @@
1
+ import { translateCurrent as t } from "../../i18n/translate.js";
1
2
  function numeric(value) {
2
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
3
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
4
+ }
5
+ function formatValue(value, fmt) {
6
+ const number = numeric(value);
7
+ return number === null ? t("chart.noData") : fmt.format(number);
3
8
  }
4
9
  function buildCartesianSummary(data, series, categoryKey, fmt, list, summaryLine) {
5
10
  const rows = data.map((datum) => {
6
11
  const category = String(datum[categoryKey] ?? "");
7
12
  const parts = series.map(
8
- (s) => `${s.label ?? s.dataKey}: ${fmt.format(numeric(datum[s.dataKey]))}`
13
+ (s) => `${s.label ?? s.dataKey}: ${formatValue(datum[s.dataKey], fmt)}`
9
14
  );
10
15
  return `${category} \u2014 ${list.format(parts)}`;
11
16
  });
@@ -13,7 +18,7 @@ function buildCartesianSummary(data, series, categoryKey, fmt, list, summaryLine
13
18
  }
14
19
  function buildSeriesRows(data, categoryKey, valueKey, fmt, annotate) {
15
20
  return data.map((datum, index) => {
16
- const row = `${String(datum[categoryKey] ?? "")}: ${fmt.format(numeric(datum[valueKey]))}`;
21
+ const row = `${String(datum[categoryKey] ?? "")}: ${formatValue(datum[valueKey], fmt)}`;
17
22
  const note = annotate?.(index);
18
23
  return note ? `${row} (${note})` : row;
19
24
  });
@@ -31,9 +36,15 @@ function buildTrendSummary(data, categoryKey, valueKey, fmt, summaryLine, annota
31
36
  };
32
37
  }
33
38
  function trendRatios(data, valueKey) {
34
- const values = data.map((datum) => Math.max(0, numeric(datum[valueKey])));
35
- const max = values.reduce((acc, value) => value > acc ? value : acc, 0);
36
- return values.map((value) => max > 0 ? value / max : 0);
39
+ const values = data.map((datum) => {
40
+ const value = numeric(datum[valueKey]);
41
+ return value === null ? null : Math.max(0, value);
42
+ });
43
+ const max = values.reduce(
44
+ (acc, value) => value !== null && value > acc ? value : acc,
45
+ 0
46
+ );
47
+ return values.map((value) => value === null ? null : max > 0 ? value / max : 0);
37
48
  }
38
49
  function resolveEmphasizedIndex(index, length) {
39
50
  if (index === void 0 || !Number.isInteger(index) || length === 0) return -1;
@@ -25,6 +25,7 @@ function CompactBarTrend({
25
25
  }) {
26
26
  const { t } = useTranslation();
27
27
  const fmt = useChartNumberFormat(numberFormat);
28
+ const countsFormat = useChartNumberFormat();
28
29
  const hasData = data.length > 0;
29
30
  const emphasized = resolveEmphasizedIndex(emphasizedIndex, data.length);
30
31
  const ratios = React.useMemo(() => trendRatios(data, valueKey), [data, valueKey]);
@@ -33,7 +34,7 @@ function CompactBarTrend({
33
34
  categoryKey,
34
35
  valueKey,
35
36
  fmt,
36
- (counts) => t("chart.summaryTrend", { points: fmt.format(counts.points) }),
37
+ (counts) => t("chart.summaryTrend", { points: countsFormat.format(counts.points) }),
37
38
  (index) => index === emphasized ? t("chart.trendCurrent") : void 0
38
39
  );
39
40
  return /* @__PURE__ */ jsx(
@@ -51,14 +52,14 @@ function CompactBarTrend({
51
52
  className: cn("ui-chart-trend", className),
52
53
  id,
53
54
  children: hasData ? /* @__PURE__ */ jsxs("div", { className: "ui-chart-trend-body", children: [
54
- /* @__PURE__ */ jsx("div", { className: "ui-chart-trend-plot", "aria-hidden": "true", children: data.map((datum, index) => /* @__PURE__ */ jsx("div", { className: "ui-chart-trend-column", children: /* @__PURE__ */ jsx(
55
+ /* @__PURE__ */ jsx("div", { className: "ui-chart-trend-plot", "aria-hidden": "true", children: data.map((datum, index) => /* @__PURE__ */ jsx("div", { className: "ui-chart-trend-column", children: ratios[index] !== null ? /* @__PURE__ */ jsx(
55
56
  "div",
56
57
  {
57
58
  className: "ui-chart-trend-bar",
58
59
  "data-emphasized": index === emphasized ? "true" : void 0,
59
60
  style: { "--chart-trend-bar-value": ratios[index] }
60
61
  }
61
- ) }, `${String(datum[categoryKey])}-${index}`)) }),
62
+ ) : null }, `${String(datum[categoryKey])}-${index}`)) }),
62
63
  showCategoryLabels ? /* @__PURE__ */ jsx("div", { className: "ui-chart-trend-ticks", "aria-hidden": "true", children: data.map((datum, index) => /* @__PURE__ */ jsx(
63
64
  "span",
64
65
  {
@@ -37,6 +37,7 @@ function PieChart({
37
37
  assertRechartsPeer();
38
38
  const { t } = useTranslation();
39
39
  const fmt = useChartNumberFormat(numberFormat);
40
+ const counts = useChartNumberFormat();
40
41
  const points = data.flatMap(
41
42
  (datum, index) => typeof datum[dataKey] === "number" && Number.isFinite(datum[dataKey]) && Number(datum[dataKey]) > 0 ? [{ index, series: 0 }] : []
42
43
  );
@@ -71,7 +72,7 @@ function PieChart({
71
72
  dataKey,
72
73
  nameKey,
73
74
  fmt,
74
- (c) => t("chart.summaryPie", { slices: fmt.format(c.slices) })
75
+ (c) => t("chart.summaryPie", { slices: counts.format(c.slices) })
75
76
  );
76
77
  const resolvedHeight = chartHeight(size, height);
77
78
  return /* @__PURE__ */ jsx(
@@ -14,6 +14,7 @@ export declare const Image: React.ForwardRefExoticComponent<Omit<React.ImgHTMLAt
14
14
  alt: string;
15
15
  fallback?: string;
16
16
  placeholder?: React.ReactNode | boolean;
17
+ onStatusChange?: (status: "loading" | "loaded" | "error") => void;
17
18
  preview?: boolean | ImagePreviewConfigProp;
18
19
  width?: number | string;
19
20
  height?: number | string;
@@ -420,6 +420,7 @@ const ImageRoot = React.forwardRef(function Image({
420
420
  alt,
421
421
  fallback,
422
422
  placeholder,
423
+ onStatusChange,
423
424
  preview = true,
424
425
  width,
425
426
  height,
@@ -444,6 +445,11 @@ const ImageRoot = React.forwardRef(function Image({
444
445
  status: "loading"
445
446
  });
446
447
  const status = load.src === src ? load.status : "loading";
448
+ const statusChangeRef = React.useRef(onStatusChange);
449
+ statusChangeRef.current = onStatusChange;
450
+ React.useEffect(() => {
451
+ statusChangeRef.current?.(status);
452
+ }, [src, status]);
447
453
  const setStatus = React.useCallback(
448
454
  (next) => setLoad(
449
455
  (prev) => prev.src === src && prev.status === "error" ? prev : { src, status: next }
@@ -471,7 +477,9 @@ const ImageRoot = React.forwardRef(function Image({
471
477
  }, [inGroup, group, id, previewSrc, alt]);
472
478
  const [visible, setVisible] = useMergedState(config.visible, false, config.onVisibleChange);
473
479
  const shownSrc = isError && fallback ? fallback : src;
474
- const showPlaceholder = placeholder != null && placeholder !== false && status === "loading";
480
+ const [mounted, setMounted] = React.useState(false);
481
+ React.useEffect(() => setMounted(true), []);
482
+ const showPlaceholder = mounted && placeholder != null && placeholder !== false && status === "loading";
475
483
  const intrinsic = fit === "intrinsic";
476
484
  const sized = intrinsic || size != null || width != null || height != null;
477
485
  const frameFit = fit ?? (size != null || height != null ? "cover" : void 0);
@@ -243,6 +243,16 @@ function ClickContent({
243
243
  }) {
244
244
  const root = usePopoverRoot("PopoverContent");
245
245
  const contentRef = React.useRef(null);
246
+ const [viewportInlineSize, setViewportInlineSize] = React.useState();
247
+ React.useLayoutEffect(() => {
248
+ if (!root.open) return;
249
+ const doc = root.triggerRef.current?.ownerDocument ?? document;
250
+ const update = () => setViewportInlineSize(doc.documentElement.clientWidth);
251
+ update();
252
+ const observer = new ResizeObserver(update);
253
+ observer.observe(doc.documentElement);
254
+ return () => observer.disconnect();
255
+ }, [root.open, root.triggerRef]);
246
256
  const openAutoFocusRef = React.useRef(onOpenAutoFocus);
247
257
  openAutoFocusRef.current = onOpenAutoFocus;
248
258
  useOverlayCloseFocus(root.open, onCloseAutoFocus, contentRef);
@@ -350,6 +360,7 @@ function ClickContent({
350
360
  style: {
351
361
  ...racStyle,
352
362
  "--radix-popover-trigger-width": "var(--trigger-width)",
363
+ ...viewportInlineSize == null ? null : { "--popover-viewport-inline-size": `${viewportInlineSize}px` },
353
364
  "--radix-popover-content-transform-origin": "var(--trigger-anchor-point)",
354
365
  ...availableHeight == null ? null : {
355
366
  "--radix-popover-content-available-height": typeof availableHeight === "number" ? `${availableHeight}px` : availableHeight
@@ -161,7 +161,7 @@ function Calendar({
161
161
  day: cn("ui-calendar-day", classNames?.day),
162
162
  day_button: cn(
163
163
  buttonVariants({ variant: "ghost" }),
164
- "ui-calendar-day-button",
164
+ "ui-calendar-day-button rounded-[var(--calendar-day-radius,var(--button-radius))]",
165
165
  classNames?.day_button
166
166
  ),
167
167
  range_start: cn("day-range-start", classNames?.range_start),
@@ -131,7 +131,7 @@ function DatePicker(props) {
131
131
  defaultValue
132
132
  );
133
133
  const value = isControlled ? valueProp : internalValue;
134
- const working = hasPending ? pending : value;
134
+ const working = open && hasPending ? pending : value;
135
135
  const rangeValue = range ? working : void 0;
136
136
  const selectedDate = range ? rangeValue?.from : Array.isArray(working) ? working[0] : working;
137
137
  const emit = (next) => {
@@ -156,6 +156,16 @@ function DatePicker(props) {
156
156
  setText(displayAll(committedSingle));
157
157
  }
158
158
  }, [range, value, committedFrom, committedTo, format, locale, showTime]);
159
+ React.useEffect(() => {
160
+ if (!open) {
161
+ setHasPending(false);
162
+ setPending(void 0);
163
+ if (range) {
164
+ setFromText(display(committedFrom));
165
+ setToText(display(committedTo));
166
+ }
167
+ }
168
+ }, [open]);
159
169
  const valueAnchor = range ? committedRange?.from : Array.isArray(committedSingle) ? committedSingle[0] : committedSingle;
160
170
  const [internalView, setInternalView] = React.useState(
161
171
  () => defaultPickerValue ?? valueAnchor
@@ -182,7 +192,7 @@ function DatePicker(props) {
182
192
  const validRange = (next) => Boolean(
183
193
  next && (next.from ? allowed(next.from) : allowEmpty[0]) && (next.to ? allowed(next.to) : allowEmpty[1])
184
194
  );
185
- const choose = (input) => {
195
+ const choose = (input, typed = false) => {
186
196
  if (range) {
187
197
  let next = input;
188
198
  if (anyDisabled && next)
@@ -196,7 +206,14 @@ function DatePicker(props) {
196
206
  if (needConfirm) {
197
207
  setPending(next);
198
208
  setHasPending(true);
199
- } else emit(next);
209
+ } else if (open && !typed && next && !(next.from && next.to)) {
210
+ setPending(next);
211
+ setHasPending(true);
212
+ } else {
213
+ setPending(void 0);
214
+ setHasPending(false);
215
+ emit(next);
216
+ }
200
217
  return;
201
218
  }
202
219
  let date = input;
@@ -253,7 +270,7 @@ function DatePicker(props) {
253
270
  if (!trimmed && !allowEmpty[edge === "from" ? 0 : 1]) return;
254
271
  const base = working;
255
272
  const next = { from: base?.from, to: base?.to, [edge]: parsed && periodDate(parsed) };
256
- choose(next.from || next.to ? next : void 0);
273
+ choose(next.from || next.to ? next : void 0, true);
257
274
  };
258
275
  const keyHandlers = (onEnter) => ({
259
276
  onKeyDown: (event) => {
@@ -458,7 +475,7 @@ function DatePicker(props) {
458
475
  {
459
476
  mode: "multiple",
460
477
  selected: Array.isArray(working) ? working : [],
461
- onSelect: choose,
478
+ onSelect: (days) => choose(days),
462
479
  disabled: (day) => !allowed(day),
463
480
  ...calendarShared
464
481
  }
@@ -54,11 +54,11 @@ const buttonVariants = cva("ui-button", {
54
54
  md: "ui-button--default-size",
55
55
  // utilities after components, so only a utility can out-rank a child's own `size-*`.
56
56
  // They read the token, so the value is still themeable.
57
- xs: "ui-button--xs [&_svg:not([class*='size-'])]:size-[var(--button-xs-icon-size)]",
57
+ xs: "ui-button--xs [&_svg:not([class*='size-'])]:size-[var(--button-icon-size,var(--button-xs-icon-size))]",
58
58
  sm: "ui-button--sm",
59
59
  lg: "ui-button--lg",
60
60
  icon: "ui-button--icon",
61
- "icon-xs": "ui-button--icon-xs [&_svg]:size-[var(--button-xs-icon-size)] [&_svg]:shrink-0",
61
+ "icon-xs": "ui-button--icon-xs [&_svg]:size-[var(--button-icon-size,var(--button-xs-icon-size))] [&_svg]:shrink-0",
62
62
  "icon-sm": "ui-button--icon-sm",
63
63
  "icon-lg": "ui-button--icon-lg",
64
64
  // The glove step (gh#1304): ≥56×56, `--control-height-touch`.
@@ -106,7 +106,7 @@ const Affix = React.forwardRef(function Affix2({
106
106
  };
107
107
  }, [measure, targetElement]);
108
108
  React.useEffect(() => {
109
- if (!affixed || !targetElement || typeof window === "undefined") return void 0;
109
+ if (!affixed || typeof window === "undefined") return void 0;
110
110
  let frame = null;
111
111
  const onScroll = () => {
112
112
  if (frame !== null) return;
@@ -7,7 +7,7 @@ export declare function EditorialFilterPanel({ variant, children, action, classN
7
7
  /** One field of an `EditorialFilterPanel`: a visible label over one control. */
8
8
  export declare function EditorialFilterField({ label, htmlFor, children, className, }: EditorialFilterFieldProp): React.JSX.Element;
9
9
  /** The head of a catalog item's page: framed picture beside kicker, h1, tags, summary, facts. */
10
- export declare function CatalogDetail({ variant, media, favorite, mediaCaption, overline, title, meta, summary, actions, facts, className, }: CatalogDetailProp): React.JSX.Element;
10
+ export declare function CatalogDetail({ variant, media, image, favorite, mediaCaption, overline, title, meta, summary, actions, facts, className, }: CatalogDetailProp): React.JSX.Element;
11
11
  /** One outlined tag in a `CatalogDetail` meta row. */
12
12
  export declare function CatalogDetailTag({ children, className }: CatalogDetailTagProp): React.JSX.Element;
13
13
  /** One band of an item page under its head: hairline, h2, muted paragraph, then the rest. */
@@ -1,5 +1,8 @@
1
+ "use client";
1
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
2
4
  import { Bookmark } from "lucide-react";
5
+ import { Image } from "../data-display/image.js";
3
6
  import { cn } from "../../lib/utils.js";
4
7
  function EditorialFilterPanel({
5
8
  variant = "panel",
@@ -37,6 +40,7 @@ function EditorialFilterField({
37
40
  function CatalogDetail({
38
41
  variant = "card",
39
42
  media,
43
+ image,
40
44
  favorite,
41
45
  mediaCaption,
42
46
  overline,
@@ -47,6 +51,18 @@ function CatalogDetail({
47
51
  facts,
48
52
  className
49
53
  }) {
54
+ const [load, setLoad] = React.useState(null);
55
+ const src = image?.src;
56
+ const status = image ? load && load.src === src ? load.status : "loading" : void 0;
57
+ const onStatusChange = React.useCallback(
58
+ (next) => {
59
+ if (src !== void 0)
60
+ setLoad(
61
+ (previous) => previous?.src === src && previous.status === next ? previous : { src, status: next }
62
+ );
63
+ },
64
+ [src]
65
+ );
50
66
  return /* @__PURE__ */ jsxs(
51
67
  "div",
52
68
  {
@@ -55,21 +71,43 @@ function CatalogDetail({
55
71
  className: cn("ui-catalog-detail", className),
56
72
  children: [
57
73
  /* @__PURE__ */ jsxs("div", { className: "ui-catalog-detail-visual", children: [
58
- /* @__PURE__ */ jsxs("div", { className: "ui-catalog-detail-art", children: [
59
- favorite && /* @__PURE__ */ jsx(
60
- "button",
61
- {
62
- type: "button",
63
- "aria-pressed": favorite.pressed,
64
- "aria-label": favorite.label,
65
- disabled: favorite.disabled,
66
- className: "ui-catalog-card-favorite ui-focus-ring",
67
- onClick: () => favorite.onPressedChange(!favorite.pressed),
68
- children: /* @__PURE__ */ jsx(Bookmark, { "aria-hidden": true, className: "ui-catalog-card-favorite-icon" })
69
- }
70
- ),
71
- media
72
- ] }),
74
+ /* @__PURE__ */ jsxs(
75
+ "div",
76
+ {
77
+ className: "ui-catalog-detail-art",
78
+ "data-status": status,
79
+ "aria-busy": status === "loading" ? true : void 0,
80
+ children: [
81
+ favorite && /* @__PURE__ */ jsx(
82
+ "button",
83
+ {
84
+ type: "button",
85
+ "aria-pressed": favorite.pressed,
86
+ "aria-label": favorite.label,
87
+ disabled: favorite.disabled,
88
+ className: "ui-catalog-card-favorite ui-focus-ring",
89
+ onClick: () => favorite.onPressedChange(!favorite.pressed),
90
+ children: /* @__PURE__ */ jsx(Bookmark, { "aria-hidden": true, className: "ui-catalog-card-favorite-icon" })
91
+ }
92
+ ),
93
+ image ? /* @__PURE__ */ jsx(
94
+ Image,
95
+ {
96
+ src: image.src,
97
+ alt: image.alt,
98
+ width: image.width,
99
+ height: image.height,
100
+ fit: "contain",
101
+ fallback: image.fallback,
102
+ fetchPriority: image.priority ? "high" : void 0,
103
+ preview: false,
104
+ onStatusChange
105
+ },
106
+ image.src
107
+ ) : media
108
+ ]
109
+ }
110
+ ),
73
111
  mediaCaption != null && /* @__PURE__ */ jsx("p", { className: "ui-catalog-detail-caption", children: mediaCaption })
74
112
  ] }),
75
113
  /* @__PURE__ */ jsxs("div", { className: "ui-catalog-detail-main", children: [
@@ -169,6 +169,14 @@ interface DropdownMenuItemPropsOwn {
169
169
  description?: React.ReactNode;
170
170
  /** Mượn thẻ của con thay vì dựng `<div>` riêng — chỗ để nhét một `<Link>` vào một mục menu. */
171
171
  asChild?: boolean;
172
+ /**
173
+ * The item is a link to this URL. With `asChild` it is read from the borrowed child's own `href`
174
+ * when there is one; pass it here when the child is a router link that takes something else
175
+ * (TanStack/React Router `<Link to>`), so react-aria renders its LINK item — an `<a>` — instead of
176
+ * an action `<div>` (and stops warning "Expected <div>, got <a>"). Without a react-aria
177
+ * `RouterProvider` the click stays the child's own: navigation is whatever the child does.
178
+ */
179
+ href?: string;
172
180
  /**
173
181
  * `id` và mọi `data-*` ĐI XUỐNG phần tử gốc của item (gh#631).
174
182
  *
@@ -183,7 +191,7 @@ interface DropdownMenuItemPropsOwn {
183
191
  id?: string;
184
192
  }
185
193
  type DropdownMenuItemProps = React.PropsWithChildren<DropdownMenuItemPropsOwn> & Record<`data-${string}`, unknown>;
186
- export declare function DropdownMenuItem({ children, className, inset, variant, disabled, onSelect, textValue, description, asChild, ...forwarded }: DropdownMenuItemProps): React.JSX.Element;
194
+ export declare function DropdownMenuItem({ children, className, inset, variant, disabled, onSelect, textValue, description, asChild, href, ...forwarded }: DropdownMenuItemProps): React.JSX.Element;
187
195
  interface DropdownMenuLabelPropsOwn {
188
196
  className?: string;
189
197
  inset?: boolean;
@@ -197,10 +205,12 @@ type DropdownMenuLabelProps = React.PropsWithChildren<DropdownMenuLabelPropsOwn>
197
205
  * `Header` không đi qua `useRenderProps`, nên `props.children` trong `render` CHÍNH là children của
198
206
  * consumer — không có `Provider` chen vào như ở `MenuItem`. `Slot` vì thế nhận thẳng gói props.
199
207
  *
200
- * NHƯNG PHẢI RENDER RA `<div>`, KHÔNG PHẢI `<header>`. `<header>` mang vai trò ngầm `banner`, và
208
+ * `<header>` TRẦN thì không được: nó mang vai trò ngầm `banner`, và
201
209
  * `banner` không nằm trong danh sách con hợp lệ của `role="menu"` (axe: aria-required-children,
202
210
  * ARIA 1.2 — menu chỉ nhận menuitem / menuitemcheckbox / menuitemradio / group / separator). Radix
203
- * dựng `<div>`, vai trò `generic`, nên nó không bao giờ vấp lỗi này.
211
+ * dựng `<div>`, vai trò `generic`, nên nó không bao giờ vấp lỗi này. Còn `<div>` thì RAC cảnh báo
212
+ * "Expected <header>, got <div>" (gh#1452). Nên: `<header role="presentation">` — đúng thẻ RAC chờ,
213
+ * không có landmark.
204
214
  *
205
215
  * `Header` vẫn được dùng chứ không thay bằng `<div>` trần: bộ dựng collection của RAC chỉ nhận các
206
216
  * nút nó biết, một phần tử lạ đặt thẳng trong `Menu` sẽ bị nó bỏ. Nên giữ nút, đổi thẻ.