@astryxdesign/core 0.1.4-canary.37c19d3 → 0.1.4-canary.39ffd21

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 (226) hide show
  1. package/dist/Banner/Banner.d.ts.map +1 -1
  2. package/dist/Banner/Banner.js +1 -8
  3. package/dist/Calendar/Calendar.d.ts.map +1 -1
  4. package/dist/Calendar/Calendar.js +11 -31
  5. package/dist/Calendar/styles.d.ts +2 -2
  6. package/dist/Calendar/styles.d.ts.map +1 -1
  7. package/dist/Calendar/styles.js +2 -2
  8. package/dist/Carousel/Carousel.d.ts.map +1 -1
  9. package/dist/Carousel/Carousel.js +0 -1
  10. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  11. package/dist/Chat/ChatToolCalls.js +1 -8
  12. package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
  13. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  14. package/dist/CheckboxInput/CheckboxInput.js +1 -3
  15. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  16. package/dist/ClickableCard/ClickableCard.js +3 -26
  17. package/dist/CodeBlock/CodeBlock.d.ts +1 -10
  18. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  19. package/dist/CodeBlock/CodeBlock.js +5 -23
  20. package/dist/Collapsible/Collapsible.d.ts +3 -9
  21. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  22. package/dist/Collapsible/Collapsible.js +13 -101
  23. package/dist/Collapsible/CollapsibleGroup.d.ts +7 -39
  24. package/dist/Collapsible/CollapsibleGroup.d.ts.map +1 -1
  25. package/dist/Collapsible/CollapsibleGroup.js +9 -70
  26. package/dist/Collapsible/CollapsibleGroupContext.d.ts +0 -31
  27. package/dist/Collapsible/CollapsibleGroupContext.d.ts.map +1 -1
  28. package/dist/Collapsible/CollapsibleGroupContext.js +3 -33
  29. package/dist/Collapsible/index.d.ts +0 -1
  30. package/dist/Collapsible/index.d.ts.map +1 -1
  31. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  32. package/dist/ContextMenu/ContextMenu.js +0 -2
  33. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  34. package/dist/DateTimeInput/DateTimeInput.js +0 -2
  35. package/dist/Grid/Grid.d.ts +3 -4
  36. package/dist/Grid/Grid.d.ts.map +1 -1
  37. package/dist/Grid/Grid.js +9 -26
  38. package/dist/Icon/Icon.d.ts.map +1 -1
  39. package/dist/Icon/Icon.js +1 -5
  40. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  41. package/dist/Lightbox/Lightbox.js +2 -26
  42. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  43. package/dist/NumberInput/NumberInput.js +16 -32
  44. package/dist/Pagination/Pagination.d.ts.map +1 -1
  45. package/dist/Pagination/Pagination.js +36 -87
  46. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  47. package/dist/PowerSearch/PowerSearch.js +21 -39
  48. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  49. package/dist/ProgressBar/ProgressBar.js +1 -2
  50. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  51. package/dist/Resizable/ResizeHandle.js +2 -10
  52. package/dist/SideNav/SideNav.d.ts.map +1 -1
  53. package/dist/SideNav/SideNav.js +2 -6
  54. package/dist/TabList/TabMenu.d.ts.map +1 -1
  55. package/dist/TabList/TabMenu.js +7 -34
  56. package/dist/Table/BaseTable.d.ts.map +1 -1
  57. package/dist/Table/BaseTable.js +2 -12
  58. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  59. package/dist/Table/plugins/columnResize/useTableColumnResize.js +2 -5
  60. package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
  61. package/dist/Table/plugins/pagination/paginateData.js +1 -5
  62. package/dist/Table/plugins/selection/useTableSelectionState.d.ts.map +1 -1
  63. package/dist/Table/plugins/selection/useTableSelectionState.js +1 -11
  64. package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
  65. package/dist/Table/plugins/sortable/useTableSortableState.js +4 -8
  66. package/dist/Thumbnail/Thumbnail.d.ts +2 -3
  67. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  68. package/dist/Thumbnail/Thumbnail.js +0 -1
  69. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  70. package/dist/TimeInput/TimeInput.js +1 -6
  71. package/dist/Toast/Toast.d.ts.map +1 -1
  72. package/dist/Toast/Toast.js +3 -11
  73. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  74. package/dist/Toast/ToastViewport.js +0 -12
  75. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  76. package/dist/Typeahead/BaseTypeahead.js +2 -5
  77. package/dist/astryx.css +0 -7
  78. package/dist/astryx.umd.js +48 -48
  79. package/dist/astryx.umd.js.map +4 -4
  80. package/dist/hooks/focusableSelector.d.ts +1 -1
  81. package/dist/hooks/focusableSelector.d.ts.map +1 -1
  82. package/dist/hooks/focusableSelector.js +1 -1
  83. package/dist/hooks/useFocusTrap.d.ts +0 -3
  84. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  85. package/dist/hooks/useFocusTrap.js +1 -38
  86. package/dist/theme/defineTheme.d.ts +1 -1
  87. package/dist/theme/defineTheme.d.ts.map +1 -1
  88. package/dist/theme/expandColorScale.d.ts.map +1 -1
  89. package/dist/theme/expandColorScale.js +3 -10
  90. package/dist/theme/hct.d.ts +15 -0
  91. package/dist/theme/hct.d.ts.map +1 -1
  92. package/dist/theme/hct.js +9 -9
  93. package/dist/theme/tokens.d.ts +2 -10
  94. package/dist/theme/tokens.d.ts.map +1 -1
  95. package/dist/theme/tokens.js +4 -220
  96. package/dist/utils/index.d.ts +0 -2
  97. package/dist/utils/index.d.ts.map +1 -1
  98. package/dist/utils/index.js +1 -2
  99. package/dist/utils/timeParser.d.ts.map +1 -1
  100. package/dist/utils/timeParser.js +5 -8
  101. package/package.json +1 -1
  102. package/src/Avatar/Avatar.doc.mjs +0 -11
  103. package/src/Banner/Banner.doc.mjs +0 -8
  104. package/src/Banner/Banner.test.tsx +0 -38
  105. package/src/Banner/Banner.tsx +1 -8
  106. package/src/Calendar/Calendar.doc.mjs +1 -1
  107. package/src/Calendar/Calendar.test.tsx +4 -112
  108. package/src/Calendar/Calendar.tsx +5 -26
  109. package/src/Calendar/styles.ts +2 -12
  110. package/src/Carousel/Carousel.doc.mjs +12 -12
  111. package/src/Carousel/Carousel.test.tsx +0 -13
  112. package/src/Carousel/Carousel.tsx +0 -1
  113. package/src/Chat/Chat.doc.mjs +1 -1
  114. package/src/Chat/ChatToolCalls.test.tsx +0 -55
  115. package/src/Chat/ChatToolCalls.tsx +12 -10
  116. package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -3
  117. package/src/CheckboxInput/CheckboxInput.test.tsx +4 -71
  118. package/src/CheckboxInput/CheckboxInput.tsx +0 -2
  119. package/src/Citation/Citation.doc.mjs +0 -5
  120. package/src/ClickableCard/ClickableCard.tsx +2 -45
  121. package/src/CodeBlock/CodeBlock.doc.mjs +2 -7
  122. package/src/CodeBlock/CodeBlock.test.tsx +3 -87
  123. package/src/CodeBlock/CodeBlock.tsx +3 -43
  124. package/src/Collapsible/Collapsible.doc.mjs +4 -8
  125. package/src/Collapsible/Collapsible.tsx +10 -86
  126. package/src/Collapsible/CollapsibleGroup.doc.mjs +3 -27
  127. package/src/Collapsible/CollapsibleGroup.test.tsx +0 -288
  128. package/src/Collapsible/CollapsibleGroup.tsx +9 -107
  129. package/src/Collapsible/CollapsibleGroupContext.tsx +2 -42
  130. package/src/Collapsible/index.ts +0 -4
  131. package/src/CommandPalette/CommandPaletteEmpty.doc.mjs +0 -5
  132. package/src/CommandPalette/CommandPaletteGroup.doc.mjs +0 -10
  133. package/src/CommandPalette/CommandPaletteInput.doc.mjs +0 -6
  134. package/src/CommandPalette/CommandPaletteItem.doc.mjs +0 -6
  135. package/src/CommandPalette/CommandPaletteList.doc.mjs +0 -9
  136. package/src/ContextMenu/ContextMenu.doc.mjs +0 -15
  137. package/src/ContextMenu/ContextMenu.test.tsx +0 -15
  138. package/src/ContextMenu/ContextMenu.tsx +0 -2
  139. package/src/ContextMenu/ContextMenuItem.doc.mjs +0 -6
  140. package/src/DateTimeInput/DateTimeInput.test.tsx +0 -70
  141. package/src/DateTimeInput/DateTimeInput.tsx +0 -2
  142. package/src/Dialog/DialogHeader.doc.mjs +0 -45
  143. package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -1
  144. package/src/Field/FieldLabel.test.tsx +2 -2
  145. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +3 -0
  146. package/src/Grid/Grid.test.tsx +18 -43
  147. package/src/Grid/Grid.tsx +9 -26
  148. package/src/Icon/Icon.test.tsx +29 -41
  149. package/src/Icon/Icon.tsx +2 -6
  150. package/src/Layout/LayoutContent.doc.mjs +0 -12
  151. package/src/Layout/LayoutPanel.doc.mjs +0 -40
  152. package/src/Lightbox/Lightbox.doc.mjs +0 -5
  153. package/src/Lightbox/Lightbox.test.tsx +2 -123
  154. package/src/Lightbox/Lightbox.tsx +1 -29
  155. package/src/NavMenu/NavMenu.doc.mjs +0 -15
  156. package/src/NumberInput/NumberInput.test.tsx +3 -48
  157. package/src/NumberInput/NumberInput.tsx +15 -32
  158. package/src/Overlay/Overlay.doc.mjs +0 -6
  159. package/src/Pagination/Pagination.test.tsx +0 -143
  160. package/src/Pagination/Pagination.tsx +23 -75
  161. package/src/Popover/Popover.test.tsx +0 -65
  162. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  163. package/src/PowerSearch/PowerSearch.test.tsx +1 -109
  164. package/src/PowerSearch/PowerSearch.tsx +16 -37
  165. package/src/ProgressBar/ProgressBar.test.tsx +0 -10
  166. package/src/ProgressBar/ProgressBar.tsx +1 -2
  167. package/src/Resizable/ResizeHandle.tsx +2 -10
  168. package/src/SegmentedControl/SegmentedControl.doc.mjs +1 -1
  169. package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
  170. package/src/SideNav/SideNav.doc.mjs +1 -7
  171. package/src/SideNav/SideNav.test.tsx +0 -20
  172. package/src/SideNav/SideNav.tsx +2 -6
  173. package/src/TabList/TabList.test.tsx +1 -185
  174. package/src/TabList/TabMenu.tsx +9 -45
  175. package/src/Table/BaseTable.tsx +1 -11
  176. package/src/Table/Table.test.tsx +0 -51
  177. package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +0 -13
  178. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +1 -3
  179. package/src/Table/plugins/pagination/paginateData.ts +1 -5
  180. package/src/Table/plugins/pagination/useTablePagination.test.tsx +0 -13
  181. package/src/Table/plugins/selection/useTableSelectionState.test.tsx +0 -23
  182. package/src/Table/plugins/selection/useTableSelectionState.tsx +6 -11
  183. package/src/Table/plugins/sortable/useTableSortableState.test.tsx +1 -35
  184. package/src/Table/plugins/sortable/useTableSortableState.tsx +4 -8
  185. package/src/TextArea/TextArea.test.tsx +3 -3
  186. package/src/TextInput/TextInput.test.tsx +3 -3
  187. package/src/Thumbnail/Thumbnail.test.tsx +2 -36
  188. package/src/Thumbnail/Thumbnail.tsx +2 -4
  189. package/src/TimeInput/TimeInput.test.tsx +0 -37
  190. package/src/TimeInput/TimeInput.tsx +1 -11
  191. package/src/Timestamp/Timestamp.doc.mjs +1 -1
  192. package/src/Toast/Toast.tsx +3 -11
  193. package/src/Toast/ToastViewport.test.tsx +0 -64
  194. package/src/Toast/ToastViewport.tsx +0 -12
  195. package/src/ToggleButton/ToggleButton.doc.mjs +0 -1
  196. package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
  197. package/src/TopNav/TopNav.doc.mjs +1 -1
  198. package/src/TreeList/TreeList.doc.mjs +2 -2
  199. package/src/Typeahead/BaseTypeahead.doc.mjs +0 -19
  200. package/src/Typeahead/BaseTypeahead.tsx +2 -5
  201. package/src/Typeahead/Typeahead.doc.mjs +1 -1
  202. package/src/Typeahead/Typeahead.test.tsx +1 -45
  203. package/src/hooks/focusableSelector.ts +1 -1
  204. package/src/hooks/useFocusTrap.doc.mjs +2 -4
  205. package/src/hooks/useFocusTrap.test.tsx +1 -149
  206. package/src/hooks/useFocusTrap.ts +1 -50
  207. package/src/theme/SyntaxTheme.doc.mjs +4 -4
  208. package/src/theme/defineTheme.ts +13 -4
  209. package/src/theme/derivedVarRegistry.test.ts +2 -2
  210. package/src/theme/expandColorScale.test.ts +0 -29
  211. package/src/theme/expandColorScale.ts +6 -11
  212. package/src/theme/hct.ts +17 -9
  213. package/src/theme/tokens.test.ts +9 -182
  214. package/src/theme/tokens.ts +4 -239
  215. package/src/theme/useTheme.test.tsx +0 -20
  216. package/src/utils/index.ts +0 -3
  217. package/src/utils/timeParser.test.ts +0 -13
  218. package/src/utils/timeParser.ts +5 -8
  219. package/dist/utils/color.d.ts +0 -42
  220. package/dist/utils/color.d.ts.map +0 -1
  221. package/dist/utils/color.js +0 -150
  222. package/src/Resizable/ResizeHandle.test.tsx +0 -180
  223. package/src/__tests__/TestIcon.tsx +0 -26
  224. package/src/docPropReferences.test.ts +0 -178
  225. package/src/utils/color.test.ts +0 -133
  226. package/src/utils/color.ts +0 -148
@@ -1,150 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /**
4
- * @file color.ts
5
- * @input CSS color strings (hex, rgb()/rgba(), a small set of named colors)
6
- * @output Shared color parsing/formatting primitives used across the design system
7
- * @position Package utility; backs theme token resolution, HCT color math, and chart rendering
8
- *
9
- * A single home for the color parsers that were previously duplicated across
10
- * the theme layer, charts, and lab. Consumers get one well-tested definition of
11
- * "parse this color" rather than several subtly different regexes.
12
- */
13
-
14
- /** A color decomposed into 0-255 RGB channels and a 0-1 alpha. */
15
-
16
- /** The named colors the design system relies on in token expressions. */
17
- const NAMED_COLORS = {
18
- transparent: {
19
- r: 0,
20
- g: 0,
21
- b: 0,
22
- a: 0
23
- },
24
- black: {
25
- r: 0,
26
- g: 0,
27
- b: 0,
28
- a: 1
29
- },
30
- white: {
31
- r: 255,
32
- g: 255,
33
- b: 255,
34
- a: 1
35
- }
36
- };
37
- const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
38
-
39
- /** Expand a shorthand hex body (`rgb`/`rgba`) to its full form (`rrggbb`/`rrggbbaa`). */
40
- function expandShorthand(body) {
41
- return body.split('').map(c => c + c).join('');
42
- }
43
-
44
- /**
45
- * Parse a hex color into {@link RGBA}. Accepts `#rgb`, `#rgba`, `#rrggbb`, and
46
- * `#rrggbbaa`, with or without the leading `#`. Returns null for anything else.
47
- */
48
- export function parseHex(hex) {
49
- if (typeof hex !== 'string') {
50
- return null;
51
- }
52
- const body = hex.trim().replace(/^#/, '');
53
- const normalized = body.length === 3 || body.length === 4 ? expandShorthand(body) : body;
54
- if (normalized.length !== 6 && normalized.length !== 8 || !/^[0-9a-fA-F]+$/.test(normalized)) {
55
- return null;
56
- }
57
- return {
58
- r: parseInt(normalized.slice(0, 2), 16),
59
- g: parseInt(normalized.slice(2, 4), 16),
60
- b: parseInt(normalized.slice(4, 6), 16),
61
- a: normalized.length === 8 ? parseInt(normalized.slice(6, 8), 16) / 255 : 1
62
- };
63
- }
64
-
65
- /**
66
- * Parse an `rgb()`/`rgba()` color into {@link RGBA}. Accepts comma- or
67
- * space-separated channels, an optional `/ alpha`, and percentage channels.
68
- */
69
- export function parseRgb(value) {
70
- const open = value.indexOf('(');
71
- if (open === -1 || !value.trim().endsWith(')')) {
72
- return null;
73
- }
74
- const body = value.slice(open + 1, value.lastIndexOf(')')).replace(/\//g, ' ');
75
- const parts = body.split(/[\s,]+/).map(p => p.trim()).filter(Boolean);
76
- if (parts.length < 3) {
77
- return null;
78
- }
79
- const channel = p => {
80
- const n = p.endsWith('%') ? parseFloat(p) / 100 * 255 : parseFloat(p);
81
- return clamp(n, 0, 255);
82
- };
83
- const r = channel(parts[0]);
84
- const g = channel(parts[1]);
85
- const b = channel(parts[2]);
86
- if ([r, g, b].some(Number.isNaN)) {
87
- return null;
88
- }
89
- let a = 1;
90
- if (parts.length >= 4) {
91
- const raw = parts[3];
92
- a = raw.endsWith('%') ? parseFloat(raw) / 100 : parseFloat(raw);
93
- if (Number.isNaN(a)) {
94
- return null;
95
- }
96
- a = clamp(a, 0, 1);
97
- }
98
- return {
99
- r,
100
- g,
101
- b,
102
- a
103
- };
104
- }
105
-
106
- /**
107
- * Parse a concrete CSS color string into {@link RGBA}. Supports hex,
108
- * `rgb()`/`rgba()`, and the named colors used in token expressions. Returns
109
- * null for anything it can't evaluate (e.g. `var()`, `oklch()`, unknown names)
110
- * so callers can preserve the original expression rather than guessing.
111
- */
112
- export function parseColor(value) {
113
- const trimmed = value.trim();
114
- const named = NAMED_COLORS[trimmed.toLowerCase()];
115
- if (named) {
116
- return {
117
- ...named
118
- };
119
- }
120
- if (trimmed.startsWith('#')) {
121
- return parseHex(trimmed);
122
- }
123
- if (/^rgba?\(/i.test(trimmed)) {
124
- return parseRgb(trimmed);
125
- }
126
- return null;
127
- }
128
-
129
- /** Format RGB channels (0-255) as an uppercase `#RRGGBB` string. */
130
- export function formatHex(r, g, b) {
131
- const channel = c => clamp(Math.round(c), 0, 255).toString(16).padStart(2, '0').toUpperCase();
132
- return `#${channel(r)}${channel(g)}${channel(b)}`;
133
- }
134
-
135
- /**
136
- * Serialize {@link RGBA} to a compact, JS-usable CSS color: `#RRGGBB` when
137
- * fully opaque, otherwise `rgba(r, g, b, a)`.
138
- */
139
- export function formatColor({
140
- r,
141
- g,
142
- b,
143
- a
144
- }) {
145
- if (a >= 1) {
146
- return formatHex(r, g, b);
147
- }
148
- const round = n => clamp(Math.round(n), 0, 255);
149
- return `rgba(${round(r)}, ${round(g)}, ${round(b)}, ${parseFloat(a.toFixed(4))})`;
150
- }
@@ -1,180 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /**
4
- * @file ResizeHandle.test.tsx
5
- * @input Uses vitest, @testing-library/react, useResizable, ResizeHandle
6
- * @output Unit tests for ResizeHandle keyboard operability and ARIA state
7
- * @position Testing; validates ResizeHandle.tsx implementation
8
- *
9
- * SYNC: When ResizeHandle.tsx changes, update tests to match new behavior
10
- *
11
- * These tests guard the WAI-ARIA window-splitter keyboard contract: the
12
- * keydown handler must live on the focusable `role="separator"` element.
13
- * Keyboard resizing is pure state math in useResizable (no layout
14
- * measurement), so the observable effect asserted here is aria-valuenow,
15
- * which is bound to the region's `_size`.
16
- */
17
-
18
- import {describe, it, expect, vi} from 'vitest';
19
- import {render, screen, fireEvent, act} from '@testing-library/react';
20
- import {ResizeHandle} from './ResizeHandle';
21
- import type {ResizeHandleProps} from './ResizeHandle';
22
- import {useResizable} from './useResizable';
23
- import type {UseResizableSingleConfig} from './useResizable';
24
-
25
- const KEYBOARD_STEP = 10;
26
- const KEYBOARD_LARGE_STEP = 50;
27
-
28
- function Harness({
29
- config,
30
- handleProps,
31
- }: {
32
- config?: UseResizableSingleConfig;
33
- handleProps?: Partial<ResizeHandleProps>;
34
- }) {
35
- const region = useResizable(
36
- config ?? {defaultSize: 200, minSizePx: 100, maxSizePx: 400},
37
- );
38
- return (
39
- <ResizeHandle resizable={region.props} label="Resize" {...handleProps} />
40
- );
41
- }
42
-
43
- function getSeparator(): HTMLElement {
44
- return screen.getByRole('separator');
45
- }
46
-
47
- describe('ResizeHandle', () => {
48
- // --- ARIA wiring ---
49
-
50
- it('exposes the region size and bounds via ARIA', () => {
51
- render(<Harness />);
52
- const separator = getSeparator();
53
- expect(separator).toHaveAttribute('aria-valuenow', '200');
54
- expect(separator).toHaveAttribute('aria-valuemin', '100');
55
- expect(separator).toHaveAttribute('aria-valuemax', '400');
56
- // Horizontal layout splits along the vertical axis.
57
- expect(separator).toHaveAttribute('aria-orientation', 'vertical');
58
- expect(separator).toHaveAttribute('aria-label', 'Resize');
59
- });
60
-
61
- it('makes the separator focusable', () => {
62
- render(<Harness />);
63
- const separator = getSeparator();
64
- expect(separator).toHaveAttribute('tabindex', '0');
65
- act(() => separator.focus());
66
- expect(separator).toHaveFocus();
67
- });
68
-
69
- // --- Keyboard resizing (the fix: handler lives on the focused separator) ---
70
-
71
- it('grows the panel by a step on ArrowRight', () => {
72
- render(<Harness />);
73
- const separator = getSeparator();
74
- act(() => separator.focus());
75
- fireEvent.keyDown(separator, {key: 'ArrowRight'});
76
- expect(separator).toHaveAttribute(
77
- 'aria-valuenow',
78
- String(200 + KEYBOARD_STEP),
79
- );
80
- });
81
-
82
- it('shrinks the panel by a step on ArrowLeft', () => {
83
- render(<Harness />);
84
- const separator = getSeparator();
85
- act(() => separator.focus());
86
- fireEvent.keyDown(separator, {key: 'ArrowLeft'});
87
- expect(separator).toHaveAttribute(
88
- 'aria-valuenow',
89
- String(200 - KEYBOARD_STEP),
90
- );
91
- });
92
-
93
- it('uses the large step when Shift is held', () => {
94
- render(<Harness />);
95
- const separator = getSeparator();
96
- act(() => separator.focus());
97
- fireEvent.keyDown(separator, {key: 'ArrowRight', shiftKey: true});
98
- expect(separator).toHaveAttribute(
99
- 'aria-valuenow',
100
- String(200 + KEYBOARD_LARGE_STEP),
101
- );
102
- });
103
-
104
- it('jumps to the minimum on Home', () => {
105
- render(<Harness />);
106
- const separator = getSeparator();
107
- act(() => separator.focus());
108
- fireEvent.keyDown(separator, {key: 'Home'});
109
- expect(separator).toHaveAttribute('aria-valuenow', '100');
110
- });
111
-
112
- it('jumps to the maximum on End', () => {
113
- render(<Harness />);
114
- const separator = getSeparator();
115
- act(() => separator.focus());
116
- fireEvent.keyDown(separator, {key: 'End'});
117
- expect(separator).toHaveAttribute('aria-valuenow', '400');
118
- });
119
-
120
- it('resizes along the block axis for a vertical handle', () => {
121
- render(
122
- <Harness
123
- config={{defaultSize: 200, minSizePx: 100, maxSizePx: 400}}
124
- handleProps={{direction: 'vertical'}}
125
- />,
126
- );
127
- const separator = getSeparator();
128
- expect(separator).toHaveAttribute('aria-orientation', 'horizontal');
129
- act(() => separator.focus());
130
- fireEvent.keyDown(separator, {key: 'ArrowDown'});
131
- expect(separator).toHaveAttribute(
132
- 'aria-valuenow',
133
- String(200 + KEYBOARD_STEP),
134
- );
135
- fireEvent.keyDown(separator, {key: 'ArrowUp'});
136
- expect(separator).toHaveAttribute('aria-valuenow', '200');
137
- });
138
-
139
- it('collapses on Enter when the region is collapsible', () => {
140
- render(
141
- <Harness
142
- config={{
143
- defaultSize: 200,
144
- minSizePx: 100,
145
- maxSizePx: 400,
146
- collapsible: true,
147
- }}
148
- />,
149
- );
150
- const separator = getSeparator();
151
- act(() => separator.focus());
152
- fireEvent.keyDown(separator, {key: 'Enter'});
153
- expect(separator).toHaveAttribute('aria-valuenow', '0');
154
- });
155
-
156
- // --- Disabled guard ---
157
-
158
- it('ignores keyboard input when disabled', () => {
159
- render(<Harness handleProps={{isDisabled: true}} />);
160
- const separator = getSeparator();
161
- expect(separator).toHaveAttribute('tabindex', '-1');
162
- fireEvent.keyDown(separator, {key: 'ArrowRight'});
163
- expect(separator).toHaveAttribute('aria-valuenow', '200');
164
- });
165
-
166
- // --- Prop composition (ordering choice: handler sits after {...props}) ---
167
-
168
- it('runs a consumer onKeyDown alongside keyboard resizing', () => {
169
- const onKeyDown = vi.fn();
170
- render(<Harness handleProps={{onKeyDown}} />);
171
- const separator = getSeparator();
172
- act(() => separator.focus());
173
- fireEvent.keyDown(separator, {key: 'ArrowRight'});
174
- expect(onKeyDown).toHaveBeenCalledTimes(1);
175
- expect(separator).toHaveAttribute(
176
- 'aria-valuenow',
177
- String(200 + KEYBOARD_STEP),
178
- );
179
- });
180
- });
@@ -1,26 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /**
4
- * @file TestIcon.tsx
5
- * @input Standard SVG props
6
- * @output A minimal SVG React component for tests
7
- * @position Test fixture; a stand-in external SVG icon for Icon component-mode tests
8
- *
9
- * A trivial SVG component used by tests that need to pass a custom SVG icon to
10
- * `Icon` (component mode) or component `*Icon` props. It forwards all standard
11
- * SVG props and its ref so tests exercising prop/ref forwarding behave the same
12
- * as a real third-party icon component.
13
- */
14
-
15
- import React, {type SVGProps} from 'react';
16
-
17
- export function TestIcon({
18
- ref,
19
- ...props
20
- }: SVGProps<SVGSVGElement> & {ref?: React.Ref<SVGSVGElement>}) {
21
- return (
22
- <svg ref={ref} viewBox="0 0 24 24" {...props}>
23
- <path d="M4 4h16v16H4z" />
24
- </svg>
25
- );
26
- }
@@ -1,178 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /* eslint-disable @typescript-eslint/no-require-imports */
4
- /**
5
- * @file Guards doc prose against docs/source prop mismatches (#3360).
6
- *
7
- * Scans every {Name}.doc.mjs for prose phrases like "the foo prop on Bar"
8
- * and verifies the referenced prop is documented on the referenced
9
- * component. Doc prose is LLM training signal: a reference to a prop that
10
- * doesn't exist steers codegen toward hallucinated APIs (#3360 shipped a
11
- * bullet pointing at a nonexistent CodeBlock syntaxTheme prop).
12
- *
13
- * A reference to a missing prop is allowed only when the same string
14
- * explicitly negates it ("no such prop exists" / "does not exist"), the
15
- * corrective-bullet pattern used to counter known hallucinations.
16
- */
17
-
18
- import {describe, it, expect} from 'vitest';
19
- import {readdirSync} from 'node:fs';
20
- import {join, relative} from 'node:path';
21
-
22
- const SRC_DIR = __dirname;
23
-
24
- type DocModule = {
25
- docs?: {
26
- name?: string;
27
- props?: {name: string}[];
28
- components?: {name?: string; props?: {name: string}[]}[];
29
- };
30
- };
31
-
32
- /**
33
- * Props intentionally omitted from doc props[]: PropDoc in docs-types.ts
34
- * says to skip internal/styling props, so prose may reference them even
35
- * though no doc lists them.
36
- */
37
- const UNIVERSAL_PROPS = new Set([
38
- 'xstyle',
39
- 'className',
40
- 'style',
41
- 'ref',
42
- 'data-testid',
43
- ]);
44
-
45
- /**
46
- * Matches prose like "the foo prop on Bar", "foo prop of Bar", and
47
- * conjunction targets: "foo prop on Bar or Baz". Group 1 is the prop
48
- * (camelCase, lowercase first letter), group 2 the component name list.
49
- */
50
- const PROP_REF_PATTERN =
51
- /\b([a-z][A-Za-z0-9]*) prop (?:on|of) (?:the )?([A-Z][A-Za-z0-9]*(?:(?:,(?: and| or)?| and| or) [A-Z][A-Za-z0-9]*)*)/g;
52
-
53
- /** A same-string negation marks the reference as a deliberate counter-signal. */
54
- const NEGATION_PATTERN = /no such prop|does not exist|doesn't exist/i;
55
-
56
- function findDocFiles(dir: string): string[] {
57
- const files: string[] = [];
58
- for (const entry of readdirSync(dir, {withFileTypes: true})) {
59
- const full = join(dir, entry.name);
60
- if (entry.isDirectory()) {
61
- files.push(...findDocFiles(full));
62
- } else if (entry.name.endsWith('.doc.mjs')) {
63
- files.push(full);
64
- }
65
- }
66
- return files;
67
- }
68
-
69
- /** Collect every string value reachable from a doc module export. */
70
- function collectStrings(value: unknown, out: string[]): void {
71
- if (typeof value === 'string') {
72
- out.push(value);
73
- } else if (Array.isArray(value)) {
74
- for (const item of value) {
75
- collectStrings(item, out);
76
- }
77
- } else if (value && typeof value === 'object') {
78
- for (const item of Object.values(value)) {
79
- collectStrings(item, out);
80
- }
81
- }
82
- }
83
-
84
- const docFiles = findDocFiles(SRC_DIR);
85
-
86
- // Component name → documented prop names, from every doc file's props[]
87
- // (single and sub-component docs) and inline components[] entries.
88
- const documentedProps = new Map<string, Set<string>>();
89
- for (const file of docFiles) {
90
- const mod = require(file) as DocModule;
91
- const docs = mod.docs;
92
- if (!docs) {
93
- continue;
94
- }
95
- const entries = [
96
- {name: docs.name, props: docs.props},
97
- ...(docs.components || []),
98
- ];
99
- for (const {name, props} of entries) {
100
- if (!name || !Array.isArray(props)) {
101
- continue;
102
- }
103
- const set = documentedProps.get(name) || new Set<string>();
104
- for (const prop of props) {
105
- set.add(prop.name);
106
- }
107
- documentedProps.set(name, set);
108
- }
109
- }
110
-
111
- interface Violation {
112
- file: string;
113
- component: string;
114
- prop: string;
115
- excerpt: string;
116
- }
117
-
118
- function findViolations(): Violation[] {
119
- const violations: Violation[] = [];
120
- for (const file of docFiles) {
121
- const strings: string[] = [];
122
- collectStrings(require(file), strings);
123
- for (const text of strings) {
124
- let match;
125
- while ((match = PROP_REF_PATTERN.exec(text)) !== null) {
126
- const [, prop, componentList] = match;
127
- if (UNIVERSAL_PROPS.has(prop) || NEGATION_PATTERN.test(text)) {
128
- continue;
129
- }
130
- for (const component of componentList.split(
131
- /,(?: and| or)?| and | or /,
132
- )) {
133
- const name = component.trim();
134
- if (!name) {
135
- continue;
136
- }
137
- const known = documentedProps.get(name);
138
- if (known && !known.has(prop)) {
139
- violations.push({
140
- file: relative(SRC_DIR, file),
141
- component: name,
142
- prop,
143
- excerpt: text.slice(
144
- Math.max(0, match.index - 40),
145
- match.index + match[0].length + 20,
146
- ),
147
- });
148
- }
149
- }
150
- }
151
- }
152
- }
153
- return violations;
154
- }
155
-
156
- describe('doc prose prop references', () => {
157
- it('sanity: discovers doc files and documented components', () => {
158
- expect(docFiles.length).toBeGreaterThan(0);
159
- expect(documentedProps.size).toBeGreaterThan(0);
160
- });
161
-
162
- it('every "X prop on Y" reference names a documented prop of Y', () => {
163
- const violations = findViolations();
164
- expect(
165
- violations,
166
- violations
167
- .map(
168
- v =>
169
- `${v.file} references prop "${v.prop}" on ${v.component}, ` +
170
- `which does not document it (…${v.excerpt}…). Either the prop ` +
171
- `doesn't exist (fix the prose; negate with "no such prop ` +
172
- `exists" if countering a known hallucination) or it exists in ` +
173
- `source but is missing from the component's doc props[] (add it).`,
174
- )
175
- .join('\n'),
176
- ).toEqual([]);
177
- });
178
- });
@@ -1,133 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- import {describe, expect, it} from 'vitest';
4
- import {parseHex, parseRgb, parseColor, formatHex, formatColor} from './color';
5
-
6
- describe('parseHex', () => {
7
- it('parses #rrggbb', () => {
8
- expect(parseHex('#0064E0')).toEqual({r: 0, g: 100, b: 224, a: 1});
9
- });
10
-
11
- it('parses without a leading #', () => {
12
- expect(parseHex('0064E0')).toEqual({r: 0, g: 100, b: 224, a: 1});
13
- });
14
-
15
- it('expands #rgb shorthand', () => {
16
- expect(parseHex('#abc')).toEqual({r: 0xaa, g: 0xbb, b: 0xcc, a: 1});
17
- });
18
-
19
- it('parses #rrggbbaa alpha', () => {
20
- expect(parseHex('#00000080')).toEqual({r: 0, g: 0, b: 0, a: 128 / 255});
21
- });
22
-
23
- it('expands #rgba shorthand alpha', () => {
24
- expect(parseHex('#f008')).toEqual({
25
- r: 0xff,
26
- g: 0,
27
- b: 0,
28
- a: 0x88 / 255,
29
- });
30
- });
31
-
32
- it('returns null for non-hex input', () => {
33
- expect(parseHex('rgb(0,0,0)')).toBeNull();
34
- expect(parseHex('#12')).toBeNull();
35
- expect(parseHex('#zzzzzz')).toBeNull();
36
- // @ts-expect-error runtime guard for non-string input
37
- expect(parseHex(null)).toBeNull();
38
- });
39
- });
40
-
41
- describe('parseRgb', () => {
42
- it('parses comma-separated rgb()', () => {
43
- expect(parseRgb('rgb(0, 100, 224)')).toEqual({
44
- r: 0,
45
- g: 100,
46
- b: 224,
47
- a: 1,
48
- });
49
- });
50
-
51
- it('parses rgba() with alpha', () => {
52
- expect(parseRgb('rgba(0, 0, 0, 0.5)')).toEqual({r: 0, g: 0, b: 0, a: 0.5});
53
- });
54
-
55
- it('parses space-separated with slash alpha', () => {
56
- expect(parseRgb('rgb(0 100 224 / 0.25)')).toEqual({
57
- r: 0,
58
- g: 100,
59
- b: 224,
60
- a: 0.25,
61
- });
62
- });
63
-
64
- it('parses percentage channels', () => {
65
- expect(parseRgb('rgb(100%, 0%, 0%)')).toEqual({
66
- r: 255,
67
- g: 0,
68
- b: 0,
69
- a: 1,
70
- });
71
- });
72
-
73
- it('clamps alpha and channels into range', () => {
74
- expect(parseRgb('rgba(300, -20, 10, 2)')).toEqual({
75
- r: 255,
76
- g: 0,
77
- b: 10,
78
- a: 1,
79
- });
80
- });
81
-
82
- it('returns null when it cannot parse', () => {
83
- expect(parseRgb('rgb(0, 0)')).toBeNull();
84
- expect(parseRgb('#000000')).toBeNull();
85
- });
86
- });
87
-
88
- describe('parseColor', () => {
89
- it('routes hex and rgb strings to the right parser', () => {
90
- expect(parseColor('#FFFFFF')).toEqual({r: 255, g: 255, b: 255, a: 1});
91
- expect(parseColor('rgb(1, 2, 3)')).toEqual({r: 1, g: 2, b: 3, a: 1});
92
- });
93
-
94
- it('resolves the named colors used in token expressions', () => {
95
- expect(parseColor('transparent')).toEqual({r: 0, g: 0, b: 0, a: 0});
96
- expect(parseColor('BLACK')).toEqual({r: 0, g: 0, b: 0, a: 1});
97
- expect(parseColor('white')).toEqual({r: 255, g: 255, b: 255, a: 1});
98
- });
99
-
100
- it('returns null for unsupported colors so callers can preserve them', () => {
101
- expect(parseColor('var(--color-accent)')).toBeNull();
102
- expect(parseColor('oklch(0.5 0.1 200)')).toBeNull();
103
- expect(parseColor('rebeccapurple')).toBeNull();
104
- });
105
- });
106
-
107
- describe('formatHex', () => {
108
- it('formats channels as uppercase #RRGGBB', () => {
109
- expect(formatHex(0, 100, 224)).toBe('#0064E0');
110
- });
111
-
112
- it('rounds and clamps out-of-range channels', () => {
113
- expect(formatHex(-5, 300, 127.6)).toBe('#00FF80');
114
- });
115
- });
116
-
117
- describe('formatColor', () => {
118
- it('formats opaque colors as hex', () => {
119
- expect(formatColor({r: 0, g: 100, b: 224, a: 1})).toBe('#0064E0');
120
- });
121
-
122
- it('formats translucent colors as rgba()', () => {
123
- expect(formatColor({r: 0, g: 100, b: 224, a: 0.2})).toBe(
124
- 'rgba(0, 100, 224, 0.2)',
125
- );
126
- });
127
-
128
- it('round-trips through parseColor', () => {
129
- const parsed = parseColor('rgba(10, 20, 30, 0.5)');
130
- expect(parsed).not.toBeNull();
131
- expect(formatColor(parsed!)).toBe('rgba(10, 20, 30, 0.5)');
132
- });
133
- });