@dreamtree-org/twreact-ui 1.3.2 → 1.3.3

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 (37) hide show
  1. package/DESIGN.md +2 -2
  2. package/README.md +11 -5
  3. package/ai-skills/dreamtree-ui.md +3 -1
  4. package/bin/cli.mjs +72 -4
  5. package/dist/ai-schema.json +1 -1
  6. package/dist/charts/index.js +75 -47
  7. package/dist/components/charts/CartesianChart.d.ts.map +1 -1
  8. package/dist/components/charts/DonutChart.d.ts.map +1 -1
  9. package/dist/components/charts/Sparkline.d.ts.map +1 -1
  10. package/dist/components/charts/chartTheme.d.ts +13 -3
  11. package/dist/components/charts/chartTheme.d.ts.map +1 -1
  12. package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
  13. package/dist/components/core/Table/MobileFilters.d.ts.map +1 -1
  14. package/dist/components/core/Table/Table.d.ts.map +1 -1
  15. package/dist/components/utility/Stepper.d.ts.map +1 -1
  16. package/dist/esm/components/blocks/StatCard.js +1 -1
  17. package/dist/esm/components/charts/CartesianChart.js +7 -2
  18. package/dist/esm/components/charts/DonutChart.js +4 -1
  19. package/dist/esm/components/charts/Sparkline.js +4 -1
  20. package/dist/esm/components/charts/chartTheme.js +65 -48
  21. package/dist/esm/components/core/Select.js +2 -2
  22. package/dist/esm/components/core/Table/MobileCard.js +5 -2
  23. package/dist/esm/components/core/Table/MobileFilters.js +10 -3
  24. package/dist/esm/components/core/Table/Table.js +10 -7
  25. package/dist/esm/components/utility/Stepper.js +6 -4
  26. package/dist/index.css +1 -1
  27. package/dist/index.esm.css +1 -1
  28. package/dist/index.esm.js +33 -18
  29. package/dist/index.js +33 -18
  30. package/dist/tokens/brands/practicepro.json +1 -0
  31. package/dist/tokens/brands/studio.json +1 -0
  32. package/mcp/catalog.snapshot.json +13 -13
  33. package/package.json +2 -1
  34. package/tokens/brand.schema.json +612 -0
  35. package/tokens/brands/practicepro.json +1 -0
  36. package/tokens/brands/studio.json +1 -0
  37. package/tokens/lib.cjs +131 -7
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { slicedToArray as _slicedToArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
- import { useState, useEffect } from 'react';
3
+ import { useSyncExternalStore, useId } from 'react';
4
4
  import { prefersReducedMotion } from '../../utils/position.js';
5
5
 
6
6
  var CHART_PALETTES = ["primary", "secondary", "success", "warning", "error", "info", "gray"];
@@ -23,9 +23,11 @@ function readVar(name) {
23
23
  }
24
24
  // SVG presentation attributes do not resolve var() in every engine, so a token is
25
25
  // turned into a concrete rgb() string when the browser can tell us its value. The
26
- // var() form is the SSR / jsdom fallback.
26
+ // var() form is the SSR / jsdom fallback, and what the first (hydrating) client
27
+ // render uses so it matches the server's HTML (`live` = false).
27
28
  function resolveVar(name) {
28
- var value = readVar(name);
29
+ var live = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
30
+ var value = live ? readVar(name) : "";
29
31
  return value ? "rgb(".concat(value.split(/[\s,]+/).join(" "), ")") : "rgb(var(".concat(name, "))");
30
32
  }
31
33
  /**
@@ -34,76 +36,91 @@ function resolveVar(name) {
34
36
  * by the consumer (e.g. a colour stored with the data).
35
37
  */
36
38
  function resolveColor(color) {
39
+ var live = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
37
40
  var name = tokenVar(color);
38
- return name ? resolveVar(name) : color;
41
+ return name ? resolveVar(name, live) : color;
39
42
  }
40
43
  function seriesColors(categories, colors) {
44
+ var live = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
41
45
  var palette = colors && colors.length ? colors : DEFAULT_CHART_COLORS;
42
46
  return categories.map(function (_, i) {
43
- return resolveColor(palette[i % palette.length]);
47
+ return resolveColor(palette[i % palette.length], live);
44
48
  });
45
49
  }
46
- function readChrome() {
50
+ function readChrome(live) {
47
51
  return Object.fromEntries(Object.entries(CHROME_VARS).map(function (_ref) {
48
52
  var _ref2 = _slicedToArray(_ref, 2),
49
53
  k = _ref2[0],
50
54
  v = _ref2[1];
51
- return [k, resolveVar(v)];
55
+ return [k, resolveVar(v, live)];
52
56
  }));
53
57
  }
58
+ // The theme is an external store: the attributes on <html> that flip it. The
59
+ // server snapshot is null, so a hydrating render keeps the var() fallback the
60
+ // server rendered and React re-renders with live colours right after (#130).
61
+ function subscribeTheme(onChange) {
62
+ if (typeof document === "undefined" || typeof MutationObserver !== "function") return function () {};
63
+ var observer = new MutationObserver(onChange);
64
+ observer.observe(document.documentElement, {
65
+ attributes: true,
66
+ attributeFilter: ["class", "data-theme", "style"]
67
+ });
68
+ return function () {
69
+ return observer.disconnect();
70
+ };
71
+ }
72
+ function themeSnapshot() {
73
+ if (typeof document === "undefined") return null;
74
+ var el = document.documentElement;
75
+ return "".concat(el.className, "|").concat(el.getAttribute("data-theme") || "", "|").concat(el.getAttribute("style") || "");
76
+ }
77
+ var serverThemeSnapshot = function serverThemeSnapshot() {
78
+ return null;
79
+ };
54
80
  /**
55
81
  * Chart chrome + series colours, re-read whenever the theme flips (the `dark`
56
82
  * class or `data-theme` on <html>) so axes and series follow dark mode without
57
83
  * a remount.
58
84
  */
59
85
  function useChartTheme(categories, colors) {
60
- var _useState = useState(0),
61
- _useState2 = _slicedToArray(_useState, 2);
62
- _useState2[0];
63
- var setVersion = _useState2[1];
64
- useEffect(function () {
65
- if (typeof MutationObserver !== "function") return undefined;
66
- var observer = new MutationObserver(function () {
67
- return setVersion(function (v) {
68
- return v + 1;
69
- });
70
- });
71
- observer.observe(document.documentElement, {
72
- attributes: true,
73
- attributeFilter: ["class", "data-theme", "style"]
74
- });
75
- return function () {
76
- return observer.disconnect();
77
- };
78
- }, []);
86
+ // The snapshot only signals a change; the values come from the live document.
87
+ var live = useSyncExternalStore(subscribeTheme, themeSnapshot, serverThemeSnapshot) !== null;
79
88
  return {
80
- chrome: readChrome(),
81
- series: seriesColors(categories, colors)
89
+ chrome: readChrome(live),
90
+ series: seriesColors(categories, colors, live)
91
+ };
92
+ }
93
+ function subscribeReducedMotion(onChange) {
94
+ var _query$addEventListen;
95
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return function () {};
96
+ var query = window.matchMedia("(prefers-reduced-motion: reduce)");
97
+ (_query$addEventListen = query.addEventListener) === null || _query$addEventListen === void 0 || _query$addEventListen.call(query, "change", onChange);
98
+ return function () {
99
+ var _query$removeEventLis;
100
+ return (_query$removeEventLis = query.removeEventListener) === null || _query$removeEventLis === void 0 ? void 0 : _query$removeEventLis.call(query, "change", onChange);
82
101
  };
83
102
  }
84
- /** `true` while the user asks the OS for reduced motion; follows changes live. */
103
+ var serverReducedMotion = function serverReducedMotion() {
104
+ return false;
105
+ };
106
+ /**
107
+ * `true` while the user asks the OS for reduced motion; follows changes live.
108
+ * `false` on the server and while hydrating, so the first render matches.
109
+ */
85
110
  function useReducedMotion() {
86
- var _useState3 = useState(prefersReducedMotion),
87
- _useState4 = _slicedToArray(_useState3, 2),
88
- reduced = _useState4[0],
89
- setReduced = _useState4[1];
90
- useEffect(function () {
91
- var _query$addEventListen;
92
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return undefined;
93
- var query = window.matchMedia("(prefers-reduced-motion: reduce)");
94
- var update = function update() {
95
- return setReduced(query.matches);
96
- };
97
- (_query$addEventListen = query.addEventListener) === null || _query$addEventListen === void 0 || _query$addEventListen.call(query, "change", update);
98
- return function () {
99
- var _query$removeEventLis;
100
- return (_query$removeEventLis = query.removeEventListener) === null || _query$removeEventLis === void 0 ? void 0 : _query$removeEventLis.call(query, "change", update);
101
- };
102
- }, []);
103
- return reduced;
111
+ return useSyncExternalStore(subscribeReducedMotion, prefersReducedMotion, serverReducedMotion);
104
112
  }
105
113
  var defaultValueFormatter = function defaultValueFormatter(value) {
106
114
  return typeof value === "number" ? value.toLocaleString() : value == null ? "" : String(value);
107
115
  };
116
+ /**
117
+ * A per-instance id for the SVG defs recharts draws (clip paths). recharts 2
118
+ * falls back to a module-level counter, which differs between the server and the
119
+ * client; an id from React.useId() is the same on both. Only [A-Za-z0-9_-] so it
120
+ * is safe inside `url(#…)`.
121
+ */
122
+ function useChartId() {
123
+ return "dt-chart-".concat(useId().replace(/[^A-Za-z0-9_-]/g, ""));
124
+ }
108
125
 
109
- export { CHART_PALETTES, DEFAULT_CHART_COLORS, defaultValueFormatter, resolveColor, seriesColors, tokenVar, useChartTheme, useReducedMotion };
126
+ export { CHART_PALETTES, DEFAULT_CHART_COLORS, defaultValueFormatter, resolveColor, seriesColors, tokenVar, useChartId, useChartTheme, useReducedMotion };
@@ -127,7 +127,7 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
127
127
  onMenuItemRender = _ref.onMenuItemRender,
128
128
  className = _ref.className,
129
129
  _ref$allowClear = _ref.allowClear,
130
- allowClear = _ref$allowClear === void 0 ? true : _ref$allowClear,
130
+ allowClear = _ref$allowClear === void 0 ? false : _ref$allowClear,
131
131
  _ref$creatable = _ref.creatable,
132
132
  creatable = _ref$creatable === void 0 ? false : _ref$creatable,
133
133
  onCreateOption = _ref.onCreateOption,
@@ -564,7 +564,7 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
564
564
  }, opt.value);
565
565
  })
566
566
  }), jsxs("div", {
567
- className: "text-xs text-primary-500",
567
+ className: "text-xs text-primary-700",
568
568
  children: ["+", rest]
569
569
  })]
570
570
  });
@@ -128,6 +128,7 @@ function MobileCard(_ref) {
128
128
  }), menuActions.length > 0 && jsxs("div", {
129
129
  className: "relative",
130
130
  children: [jsx("button", {
131
+ type: "button",
131
132
  ref: function ref(el) {
132
133
  return registerActionButton === null || registerActionButton === void 0 ? void 0 : registerActionButton(actionCellKey, el);
133
134
  },
@@ -137,8 +138,10 @@ function MobileCard(_ref) {
137
138
  "aria-expanded": activeActionRowKey === actionCellKey,
138
139
  className: "p-1.5 rounded hover:bg-gray-200",
139
140
  title: "Actions",
141
+ "aria-label": "Actions",
140
142
  children: jsx(MoreVertical, {
141
- className: "h-4 w-4"
143
+ className: "h-4 w-4",
144
+ "aria-hidden": "true"
142
145
  })
143
146
  }), activeActionRowKey === actionCellKey && jsx(ActionMenu, {
144
147
  actions: menuActions,
@@ -166,7 +169,7 @@ function MobileCard(_ref) {
166
169
  return jsxs("div", {
167
170
  className: cn("flex justify-between items-center flex-row py-2 border-b last:border-b-0", safeExtraCellClass),
168
171
  children: [jsx("div", {
169
- className: "text-xs font-medium text-primary-500 uppercase tracking-wide mb-1 sm:mb-0 sm:w-1/3 sm:pr-2",
172
+ className: "text-xs font-medium text-primary-700 uppercase tracking-wide mb-1 sm:mb-0 sm:w-1/3 sm:pr-2",
170
173
  children: column.label
171
174
  }), jsx("div", {
172
175
  className: "text-sm text-primary-900 sm:w-2/3 sm:pl-2 break-words",
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
- import 'react';
3
+ import React from 'react';
4
4
  import { Search } from 'lucide-react';
5
5
 
6
6
  /**
@@ -17,6 +17,7 @@ function MobileFilters(_ref) {
17
17
  visibleColumns = _ref.visibleColumns,
18
18
  filters = _ref.filters,
19
19
  handleFilter = _ref.handleFilter;
20
+ var idPrefix = React.useId();
20
21
  return jsxs("div", {
21
22
  className: "mb-4 p-3 bg-gray-50 rounded-lg",
22
23
  children: [jsxs("div", {
@@ -25,12 +26,14 @@ function MobileFilters(_ref) {
25
26
  className: "text-sm font-medium",
26
27
  children: "Filters"
27
28
  }), jsx("button", {
29
+ type: "button",
28
30
  onClick: function onClick() {
29
31
  return setShowMobileFilters(function (s) {
30
32
  return !s;
31
33
  });
32
34
  },
33
- className: "text-xs text-primary-500",
35
+ "aria-expanded": !!showMobileFilters,
36
+ className: "text-xs font-medium text-primary-700",
34
37
  children: showMobileFilters ? "Hide" : "Show"
35
38
  })]
36
39
  }), showMobileFilters && jsxs("div", {
@@ -40,6 +43,7 @@ function MobileFilters(_ref) {
40
43
  children: [jsx("input", {
41
44
  type: "text",
42
45
  placeholder: "Search...",
46
+ "aria-label": "Search",
43
47
  className: "w-full rounded-md border border-gray-300 px-4 py-2 text-sm",
44
48
  value: searchInput,
45
49
  onChange: function onChange(e) {
@@ -47,14 +51,17 @@ function MobileFilters(_ref) {
47
51
  },
48
52
  onKeyDown: onGlobalKeyDown
49
53
  }), jsx(Search, {
50
- className: "absolute right-3 top-2.5 h-4 w-4 text-primary-400"
54
+ className: "absolute right-3 top-2.5 h-4 w-4 text-primary-400",
55
+ "aria-hidden": "true"
51
56
  })]
52
57
  }), filterable && visibleColumns.map(function (column) {
53
58
  return jsxs("div", {
54
59
  children: [jsx("label", {
60
+ htmlFor: "".concat(idPrefix, "-filter-").concat(column.key),
55
61
  className: "block text-xs font-medium mb-1",
56
62
  children: column.label
57
63
  }), jsx("input", {
64
+ id: "".concat(idPrefix, "-filter-").concat(column.key),
58
65
  type: "text",
59
66
  placeholder: "Filter ".concat(column.label),
60
67
  className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm",
@@ -1179,10 +1179,13 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
1179
1179
  children: [showReloadButton && jsx(Fragment, {
1180
1180
  children: renderReloadButton ? typeof renderReloadButton === "function" ? renderReloadButton() : renderReloadButton : jsxs("button", {
1181
1181
  type: "button",
1182
+ "aria-label": "Reload",
1183
+ title: "Reload",
1182
1184
  className: "px-4 py-2 bg-primary-100 flex items-center gap-2 rounded-lg md:rounded text-primary-700 hover:bg-primary-200 transition-colors",
1183
1185
  onClick: onReload,
1184
1186
  children: [jsx(RefreshCcw, {
1185
- className: "size-4"
1187
+ className: "size-4",
1188
+ "aria-hidden": "true"
1186
1189
  }), jsx("span", {
1187
1190
  className: "hidden sm:inline",
1188
1191
  children: "Reload"
@@ -1323,7 +1326,7 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
1323
1326
  children: [hasDetailsColumn && jsx("th", {
1324
1327
  className: "w-8 px-4 py-3"
1325
1328
  }), showSerial && jsx("th", {
1326
- className: "w-8 px-4 py-3 text-left text-xs font-medium text-primary-500 uppercase tracking-wider",
1329
+ className: "w-8 px-4 py-3 text-left text-xs font-medium text-primary-700 uppercase tracking-wider",
1327
1330
  children: "#"
1328
1331
  }), selectable && jsx("th", {
1329
1332
  className: "w-12 px-6 py-3",
@@ -1336,7 +1339,7 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
1336
1339
  }), visibleColumns.map(function (column) {
1337
1340
  var _column$rules;
1338
1341
  return jsx("th", {
1339
- className: cn("px-6 py-3 text-left text-xs font-medium text-primary-500 uppercase tracking-wider", {
1342
+ className: cn("px-6 py-3 text-left text-xs font-medium text-primary-700 uppercase tracking-wider", {
1340
1343
  "cursor-pointer hover:bg-gray-100": sortable && column.sortable !== false
1341
1344
  }),
1342
1345
  onClick: function onClick() {
@@ -1427,7 +1430,7 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
1427
1430
  children: [jsx("div", {
1428
1431
  className: "h-6 w-6 animate-spin rounded-full border-2 border-primary-600 border-t-transparent"
1429
1432
  }), jsx("span", {
1430
- className: "text-sm text-primary-500",
1433
+ className: "text-sm text-muted",
1431
1434
  children: "Loading..."
1432
1435
  })]
1433
1436
  })
@@ -1445,7 +1448,7 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
1445
1448
  className: "h-8 w-8"
1446
1449
  })
1447
1450
  }), jsx("span", {
1448
- className: "text-sm text-primary-500",
1451
+ className: "text-sm text-muted",
1449
1452
  children: "No results found."
1450
1453
  })]
1451
1454
  })
@@ -1490,7 +1493,7 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
1490
1493
  children: [jsx("div", {
1491
1494
  className: "h-6 w-6 animate-spin rounded-full border-2 border-primary-600 border-t-transparent"
1492
1495
  }), jsx("span", {
1493
- className: "text-sm text-primary-500",
1496
+ className: "text-sm text-muted",
1494
1497
  children: "Loading..."
1495
1498
  })]
1496
1499
  })
@@ -1504,7 +1507,7 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
1504
1507
  className: "h-8 w-8"
1505
1508
  })
1506
1509
  }), jsx("span", {
1507
- className: "text-sm text-primary-500",
1510
+ className: "text-sm text-muted",
1508
1511
  children: "No results found."
1509
1512
  })]
1510
1513
  })
@@ -34,9 +34,9 @@ var Stepper = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
34
34
  text: 'text-primary-600'
35
35
  },
36
36
  upcoming: {
37
- circle: 'bg-gray-300 text-primary-500',
37
+ circle: 'bg-gray-300 text-gray-700',
38
38
  line: 'bg-gray-300',
39
- text: 'text-primary-500'
39
+ text: 'text-muted'
40
40
  }
41
41
  };
42
42
  return jsx("nav", _objectSpread2(_objectSpread2({
@@ -70,8 +70,10 @@ var Stepper = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
70
70
  },
71
71
  disabled: !onStepClick || stepIndex > currentStep,
72
72
  "aria-current": status === 'current' ? 'step' : undefined,
73
+ "aria-label": "Step ".concat(stepIndex + 1).concat(step.title ? ": ".concat(step.title) : '').concat(status === 'completed' ? ' (completed)' : ''),
73
74
  children: status === 'completed' ? jsx(Check, {
74
- className: "h-4 w-4"
75
+ className: "h-4 w-4",
76
+ "aria-hidden": "true"
75
77
  }) : stepIndex + 1
76
78
  }), !isLast && jsx("div", {
77
79
  className: cn('bg-gray-300', {
@@ -88,7 +90,7 @@ var Stepper = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
88
90
  className: cn('text-sm font-medium', colors.text),
89
91
  children: step.title
90
92
  }), step.description && jsx("span", {
91
- className: "text-xs text-primary-500",
93
+ className: "text-xs text-muted",
92
94
  children: step.description
93
95
  })]
94
96
  })]