@astryxdesign/core 0.1.3 → 0.1.4-canary.02643db
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.
- package/CHANGELOG.md +51 -0
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +29 -8
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +49 -20
- package/dist/Calendar/dayCellUtils.d.ts +67 -5
- package/dist/Calendar/dayCellUtils.d.ts.map +1 -1
- package/dist/Calendar/dayCellUtils.js +139 -18
- package/dist/CheckboxInput/CheckboxInput.d.ts +6 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +7 -1
- package/dist/Code/Code.d.ts +15 -1
- package/dist/Code/Code.d.ts.map +1 -1
- package/dist/Code/Code.js +32 -2
- package/dist/Code/index.d.ts +1 -1
- package/dist/Code/index.d.ts.map +1 -1
- package/dist/CodeBlock/index.d.ts +1 -1
- package/dist/CodeBlock/index.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteEmpty.d.ts +1 -1
- package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteEmpty.js +24 -4
- package/dist/ContextMenu/ContextMenu.d.ts +9 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +6 -3
- package/dist/DateRangeInput/DateRangeInput.js +2 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts +0 -5
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/Divider/Divider.d.ts +0 -22
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Markdown/parser.d.ts +7 -0
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +300 -6
- package/dist/MultiSelector/MultiSelector.d.ts +7 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +10 -1
- package/dist/RadioList/RadioList.d.ts +7 -1
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +3 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +6 -1
- package/dist/Selector/Selector.d.ts +6 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +9 -0
- package/dist/Slider/Slider.d.ts +6 -0
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +10 -1
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +1 -1
- package/dist/Switch/Switch.d.ts +6 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +7 -1
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +8 -1
- package/dist/Table/TableCell.d.ts.map +1 -1
- package/dist/Table/TableCell.js +73 -4
- package/dist/Table/index.d.ts +2 -0
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +1 -0
- package/dist/Table/plugins/rowExpansion/index.d.ts +3 -0
- package/dist/Table/plugins/rowExpansion/index.d.ts.map +1 -0
- package/dist/Table/plugins/rowExpansion/index.js +3 -0
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts +90 -0
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -0
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +398 -0
- package/dist/Table/tableContextMenu.d.ts +5 -1
- package/dist/Table/tableContextMenu.d.ts.map +1 -1
- package/dist/Table/tableContextMenu.js +10 -3
- package/dist/Text/Text.d.ts +1 -1
- package/dist/Text/Text.d.ts.map +1 -1
- package/dist/Text/Text.js +4 -3
- package/dist/Text/text.stylex.d.ts +35 -0
- package/dist/Text/text.stylex.d.ts.map +1 -1
- package/dist/Text/text.stylex.js +52 -1
- package/dist/Tokenizer/Tokenizer.d.ts +6 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +10 -1
- package/dist/astryx.css +16 -0
- package/dist/astryx.umd.js +51 -49
- package/dist/astryx.umd.js.map +4 -4
- package/dist/docs-types.d.ts +12 -0
- package/dist/docs-types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/AspectRatio/AspectRatio.doc.mjs +1 -1
- package/src/Avatar/Avatar.doc.mjs +2 -2
- package/src/Button/Button.tsx +39 -6
- package/src/ButtonGroup/ButtonGroup.doc.mjs +1 -1
- package/src/Calendar/Calendar.test.tsx +106 -0
- package/src/Calendar/Calendar.tsx +59 -22
- package/src/Calendar/dayCellUtils.test.ts +306 -1
- package/src/Calendar/dayCellUtils.ts +206 -13
- package/src/Card/Card.doc.mjs +1 -1
- package/src/Chat/useChatNewMessages.test.tsx +4 -4
- package/src/CheckboxInput/CheckboxInput.doc.mjs +9 -1
- package/src/CheckboxInput/CheckboxInput.test.tsx +36 -0
- package/src/CheckboxInput/CheckboxInput.tsx +11 -0
- package/src/CheckboxList/CheckboxList.doc.mjs +1 -1
- package/src/Code/Code.test.tsx +54 -0
- package/src/Code/Code.tsx +48 -4
- package/src/Code/index.ts +1 -1
- package/src/CodeBlock/index.ts +1 -1
- package/src/CommandPalette/CommandPaletteEmpty.tsx +15 -1
- package/src/ContextMenu/ContextMenu.tsx +17 -1
- package/src/DateInput/DateInput.doc.mjs +6 -1
- package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
- package/src/DateRangeInput/DateRangeInput.tsx +2 -2
- package/src/DateTimeInput/DateTimeInput.doc.mjs +1 -1
- package/src/DateTimeInput/DateTimeInput.tsx +1 -6
- package/src/Divider/Divider.tsx +1 -23
- package/src/DropdownMenu/DropdownMenu.test.tsx +5 -3
- package/src/EmptyState/EmptyState.doc.mjs +1 -1
- package/src/Field/Field.doc.mjs +1 -1
- package/src/FileInput/FileInput.doc.mjs +1 -1
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +0 -3
- package/src/Grid/Grid.doc.mjs +3 -3
- package/src/Layout/__tests__/edgeCompensation.test.tsx +72 -0
- package/src/Markdown/Markdown.test.tsx +23 -0
- package/src/Markdown/incremental.test.ts +36 -0
- package/src/Markdown/parser.test.ts +210 -0
- package/src/Markdown/parser.ts +301 -6
- package/src/MobileNav/MobileNav.doc.mjs +20 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +14 -1
- package/src/MultiSelector/MultiSelector.test.tsx +33 -0
- package/src/MultiSelector/MultiSelector.tsx +20 -0
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/RadioList/RadioList.doc.mjs +7 -1
- package/src/RadioList/RadioList.test.tsx +46 -0
- package/src/RadioList/RadioList.tsx +10 -1
- package/src/RadioList/RadioListItem.tsx +4 -0
- package/src/Section/Section.test.tsx +4 -10
- package/src/SegmentedControl/SegmentedControl.doc.mjs +1 -1
- package/src/Selector/Selector.doc.mjs +12 -1
- package/src/Selector/Selector.test.tsx +30 -0
- package/src/Selector/Selector.tsx +18 -0
- package/src/Slider/Slider.doc.mjs +14 -2
- package/src/Slider/Slider.test.tsx +30 -0
- package/src/Slider/Slider.tsx +20 -0
- package/src/Spinner/Spinner.tsx +1 -0
- package/src/Switch/Switch.doc.mjs +14 -2
- package/src/Switch/Switch.test.tsx +38 -0
- package/src/Switch/Switch.tsx +12 -0
- package/src/TabList/TabList.doc.mjs +3 -3
- package/src/Table/BaseTable.tsx +9 -1
- package/src/Table/Table.test.tsx +23 -0
- package/src/Table/TableCell.tsx +87 -8
- package/src/Table/index.ts +5 -0
- package/src/Table/plugins/rowExpansion/index.ts +7 -0
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +155 -0
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +590 -0
- package/src/Table/tableContextMenu.test.tsx +27 -3
- package/src/Table/tableContextMenu.tsx +20 -2
- package/src/Table/useTablePagination.doc.mjs +4 -1
- package/src/Table/useTableRowExpansion.doc.mjs +80 -0
- package/src/Table/useTableSortable.doc.mjs +4 -1
- package/src/Text/Text.doc.mjs +2 -2
- package/src/Text/Text.test.tsx +11 -0
- package/src/Text/Text.tsx +4 -2
- package/src/Text/text.stylex.ts +41 -0
- package/src/TextArea/TextArea.doc.mjs +1 -1
- package/src/TextInput/TextInput.doc.mjs +1 -1
- package/src/TimeInput/TimeInput.doc.mjs +11 -6
- package/src/Tokenizer/Tokenizer.doc.mjs +14 -2
- package/src/Tokenizer/Tokenizer.test.tsx +33 -0
- package/src/Tokenizer/Tokenizer.tsx +19 -0
- package/src/Toolbar/Toolbar.test.tsx +1 -1
- package/src/Typeahead/BaseTypeahead.doc.mjs +10 -0
- package/src/Typeahead/Typeahead.doc.mjs +6 -1
- package/src/docs-types.ts +12 -0
- package/src/hooks/useKeyboardHint.doc.mjs +6 -6
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('../docs-types').ComponentDoc} */
|
|
4
|
+
|
|
5
|
+
export const docs = {
|
|
6
|
+
name: 'useTableRowExpansion',
|
|
7
|
+
subComponentOf: 'Table',
|
|
8
|
+
displayName: 'useTableRowExpansion',
|
|
9
|
+
description:
|
|
10
|
+
'Hook that returns a TablePlugin implementing expandable rows with inherited columns. Child rows use the same columns as their parents, indented by depth. Clicking the chevron (or right-click context menu) toggles expansion. Pair with useTableRowExpansionState, which flattens the tree and derives this config (expand/collapse handlers + expand-all state) from a single expandedKeys set.',
|
|
11
|
+
props: [
|
|
12
|
+
{
|
|
13
|
+
name: 'expandedKeys',
|
|
14
|
+
type: 'Set<string>',
|
|
15
|
+
description: 'Set of currently-expanded row keys.',
|
|
16
|
+
required: true,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'onToggle',
|
|
20
|
+
type: '(key: string) => void',
|
|
21
|
+
description: 'Called when a row expansion is toggled.',
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'getRowKey',
|
|
26
|
+
type: '(item: T) => string',
|
|
27
|
+
description: 'Derive a stable unique key from a row item.',
|
|
28
|
+
required: true,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'getChildren',
|
|
32
|
+
type: '(item: T) => T[]',
|
|
33
|
+
description: 'Return the children of a row (determines expandability).',
|
|
34
|
+
required: true,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'getDepth',
|
|
38
|
+
type: '(item: T) => number',
|
|
39
|
+
description: 'Return the depth of a row in the hierarchy (0 = top-level). Used for indentation.',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'getIsItemExpandable',
|
|
43
|
+
type: '(item: T) => boolean',
|
|
44
|
+
description: 'Control which rows are expandable. Defaults to checking getChildren length.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'hasRowClickExpansion',
|
|
48
|
+
type: 'boolean',
|
|
49
|
+
description: 'When true, clicking anywhere on the row toggles expansion.',
|
|
50
|
+
default: 'false',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'isAllExpanded',
|
|
54
|
+
type: "boolean | 'indeterminate'",
|
|
55
|
+
description: 'State of the expand-all toggle in the header. Enables the header toggle button.',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'onToggleExpandAll',
|
|
59
|
+
type: '(expand: boolean) => void',
|
|
60
|
+
description: 'Callback when the expand-all header toggle is clicked.',
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** @type {import('../docs-types').TranslationDoc} */
|
|
66
|
+
export const docsDense = {
|
|
67
|
+
description:
|
|
68
|
+
'Returns a TablePlugin for expandable rows w/ inherited columns. Child rows reuse parent columns, indented by depth. Chevron click (or right-click menu) toggles expansion. Pair w/ useTableRowExpansionState, which flattens the tree + derives this config from one expandedKeys set.',
|
|
69
|
+
propDescriptions: {
|
|
70
|
+
expandedKeys: 'Set of currently-expanded row keys.',
|
|
71
|
+
onToggle: 'Called when a row expansion is toggled.',
|
|
72
|
+
getRowKey: 'Derive a stable unique key from a row item.',
|
|
73
|
+
getChildren: 'Return children of a row; determines expandability.',
|
|
74
|
+
getDepth: 'Return depth of a row (0 = top-level). Used for indentation.',
|
|
75
|
+
getIsItemExpandable: 'Control which rows are expandable. Defaults to checking getChildren length.',
|
|
76
|
+
hasRowClickExpansion: 'If true, clicking anywhere on the row toggles expansion. Default false.',
|
|
77
|
+
isAllExpanded: 'State of the expand-all header toggle. Enables the header toggle button.',
|
|
78
|
+
onToggleExpandAll: 'Callback when the expand-all header toggle is clicked.',
|
|
79
|
+
},
|
|
80
|
+
};
|
|
@@ -6,7 +6,10 @@ export const docs = {
|
|
|
6
6
|
name: 'useTableSortable',
|
|
7
7
|
subComponentOf: 'Table',
|
|
8
8
|
displayName: 'useTableSortable',
|
|
9
|
-
description: 'Headless multi-sort plugin for Table.
|
|
9
|
+
description: 'Headless multi-sort plugin for Table. Call with a config object: `useTableSortable({ sort, onSortChange })`. Returns a TablePlugin to pass to `<Table plugins={{ sort: sortPlugin }} />`.',
|
|
10
|
+
usage: {
|
|
11
|
+
description: 'Call useTableSortable with a config object containing sort state and callback. Pass the returned plugin to Table via the plugins prop.',
|
|
12
|
+
},
|
|
10
13
|
props: [
|
|
11
14
|
{
|
|
12
15
|
name: 'sort',
|
package/src/Text/Text.doc.mjs
CHANGED
|
@@ -16,7 +16,7 @@ export const docs = {
|
|
|
16
16
|
theming: {
|
|
17
17
|
targets: [
|
|
18
18
|
{className: 'astryx-heading', visualProps: ['level', 'color']},
|
|
19
|
-
{className: 'astryx-text', visualProps: ['type', 'color']},
|
|
19
|
+
{className: 'astryx-text', visualProps: ['type', 'size', 'color']},
|
|
20
20
|
],
|
|
21
21
|
},
|
|
22
22
|
description: 'Semantic body text component that renders text with type-based styling from the theme, with optional truncation, decoration, and layout props.',
|
|
@@ -174,4 +174,4 @@ export const docsDense = {
|
|
|
174
174
|
{ guidance: false, description: 'Text for headings: use Heading with level (1\u20136).' },
|
|
175
175
|
],
|
|
176
176
|
},
|
|
177
|
-
};
|
|
177
|
+
};
|
package/src/Text/Text.test.tsx
CHANGED
|
@@ -114,6 +114,17 @@ describe('Text', () => {
|
|
|
114
114
|
expect(screen.getByText('Bold text')).toBeInTheDocument();
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
+
it('reflects explicit size overrides for styling and theming', () => {
|
|
118
|
+
render(
|
|
119
|
+
<Text type="code" size="2xs">
|
|
120
|
+
Tiny code
|
|
121
|
+
</Text>,
|
|
122
|
+
);
|
|
123
|
+
const element = screen.getByText('Tiny code');
|
|
124
|
+
expect(element).toHaveAttribute('data-size', '2xs');
|
|
125
|
+
expect(element.className).toContain('size-2xs');
|
|
126
|
+
});
|
|
127
|
+
|
|
117
128
|
it('accepts display prop', () => {
|
|
118
129
|
render(
|
|
119
130
|
<Text type="body" display="block">
|
package/src/Text/Text.tsx
CHANGED
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
import {
|
|
33
33
|
colorStyles,
|
|
34
34
|
defaultWeightByTypeStyles,
|
|
35
|
+
sizeStyles,
|
|
35
36
|
sizeByTypeStyles,
|
|
36
37
|
weightStyles,
|
|
37
38
|
displayStyles,
|
|
@@ -201,7 +202,7 @@ function resolveStyleType(type: TextType): BuiltinTextType {
|
|
|
201
202
|
*/
|
|
202
203
|
export function Text({
|
|
203
204
|
type = 'body',
|
|
204
|
-
size
|
|
205
|
+
size,
|
|
205
206
|
color,
|
|
206
207
|
weight,
|
|
207
208
|
display = 'inline',
|
|
@@ -254,10 +255,11 @@ export function Text({
|
|
|
254
255
|
<Component
|
|
255
256
|
ref={mergeRefs(ref, truncation.ref, textRef)}
|
|
256
257
|
{...mergeProps(
|
|
257
|
-
themeProps('text', {type, color: resolvedColor}),
|
|
258
|
+
themeProps('text', {type, size, color: resolvedColor}),
|
|
258
259
|
stylex.props(
|
|
259
260
|
colorStyles[resolvedColor],
|
|
260
261
|
sizeByTypeStyles[styleType],
|
|
262
|
+
size && sizeStyles[size],
|
|
261
263
|
defaultWeightByTypeStyles[styleType],
|
|
262
264
|
weight && weightStyles[weight],
|
|
263
265
|
// Display: use truncation styles when maxLines > 0
|
package/src/Text/text.stylex.ts
CHANGED
|
@@ -14,6 +14,7 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
14
14
|
import {
|
|
15
15
|
colorVars,
|
|
16
16
|
fontWeightVars,
|
|
17
|
+
textSizeVars,
|
|
17
18
|
typeScaleVars,
|
|
18
19
|
typographyVars,
|
|
19
20
|
} from '../theme/tokens.stylex';
|
|
@@ -145,6 +146,46 @@ export const sizeByTypeStyles = stylex.create({
|
|
|
145
146
|
},
|
|
146
147
|
});
|
|
147
148
|
|
|
149
|
+
// =============================================================================
|
|
150
|
+
// Explicit Size Override
|
|
151
|
+
// =============================================================================
|
|
152
|
+
|
|
153
|
+
export const sizeStyles = stylex.create({
|
|
154
|
+
'4xs': {
|
|
155
|
+
fontSize: textSizeVars['--font-size-4xs'],
|
|
156
|
+
},
|
|
157
|
+
'3xs': {
|
|
158
|
+
fontSize: textSizeVars['--font-size-3xs'],
|
|
159
|
+
},
|
|
160
|
+
'2xs': {
|
|
161
|
+
fontSize: textSizeVars['--font-size-2xs'],
|
|
162
|
+
},
|
|
163
|
+
xsm: {
|
|
164
|
+
fontSize: textSizeVars['--font-size-xs'],
|
|
165
|
+
},
|
|
166
|
+
sm: {
|
|
167
|
+
fontSize: textSizeVars['--font-size-sm'],
|
|
168
|
+
},
|
|
169
|
+
base: {
|
|
170
|
+
fontSize: textSizeVars['--font-size-base'],
|
|
171
|
+
},
|
|
172
|
+
lg: {
|
|
173
|
+
fontSize: textSizeVars['--font-size-lg'],
|
|
174
|
+
},
|
|
175
|
+
xl: {
|
|
176
|
+
fontSize: textSizeVars['--font-size-xl'],
|
|
177
|
+
},
|
|
178
|
+
'2xl': {
|
|
179
|
+
fontSize: textSizeVars['--font-size-2xl'],
|
|
180
|
+
},
|
|
181
|
+
'3xl': {
|
|
182
|
+
fontSize: textSizeVars['--font-size-3xl'],
|
|
183
|
+
},
|
|
184
|
+
'4xl': {
|
|
185
|
+
fontSize: textSizeVars['--font-size-4xl'],
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
148
189
|
// =============================================================================
|
|
149
190
|
// Baseline Size/Leading by Heading Level (from type-scale tokens)
|
|
150
191
|
//
|
|
@@ -74,7 +74,7 @@ export const docs = {
|
|
|
74
74
|
name: 'disabledMessage',
|
|
75
75
|
type: 'string',
|
|
76
76
|
description:
|
|
77
|
-
'Explains why the textarea is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the textarea focusable via aria-disabled (the field becomes read-only). Use this instead of wrapping a disabled TextArea in Tooltip
|
|
77
|
+
'Explains why the textarea is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the textarea focusable via aria-disabled (the field becomes read-only). Use this instead of wrapping a disabled TextArea in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.',
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
name: 'isLoading',
|
|
@@ -82,7 +82,7 @@ export const docs = {
|
|
|
82
82
|
name: 'disabledMessage',
|
|
83
83
|
type: 'string',
|
|
84
84
|
description:
|
|
85
|
-
'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the input focusable via aria-disabled (the field becomes read-only). Use this instead of wrapping a disabled TextInput in Tooltip
|
|
85
|
+
'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the input focusable via aria-disabled (the field becomes read-only). Use this instead of wrapping a disabled TextInput in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.',
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
name: 'isLoading',
|
|
@@ -26,7 +26,7 @@ export const docs = {
|
|
|
26
26
|
{
|
|
27
27
|
guidance: true,
|
|
28
28
|
description:
|
|
29
|
-
'Choose the hour format (12h or 24h) that matches your audience\'s locale
|
|
29
|
+
'Choose the hour format (12h or 24h) that matches your audience\'s locale: 12-hour with AM/PM for US-centric UIs, 24-hour for international or technical contexts.',
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
guidance: true,
|
|
@@ -41,7 +41,7 @@ export const docs = {
|
|
|
41
41
|
{
|
|
42
42
|
guidance: true,
|
|
43
43
|
description:
|
|
44
|
-
'Use the status prop to surface validation errors inline
|
|
44
|
+
'Use the status prop to surface validation errors inline: show a message like "Time must be during business hours" so users know exactly what to fix.',
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
guidance: true,
|
|
@@ -146,7 +146,7 @@ export const docs = {
|
|
|
146
146
|
name: 'disabledMessage',
|
|
147
147
|
type: 'string',
|
|
148
148
|
description:
|
|
149
|
-
'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the field focusable via aria-disabled (activation stays blocked). Use this instead of wrapping a disabled TimeInput in Tooltip
|
|
149
|
+
'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the field focusable via aria-disabled (activation stays blocked). Use this instead of wrapping a disabled TimeInput in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.',
|
|
150
150
|
},
|
|
151
151
|
{
|
|
152
152
|
name: 'value',
|
|
@@ -396,7 +396,7 @@ export const docsZh = {
|
|
|
396
396
|
{
|
|
397
397
|
guidance: true,
|
|
398
398
|
description:
|
|
399
|
-
'Choose the hour format (12h or 24h) that matches your audience\'s locale
|
|
399
|
+
'Choose the hour format (12h or 24h) that matches your audience\'s locale: 12-hour with AM/PM for US-centric UIs, 24-hour for international or technical contexts.',
|
|
400
400
|
},
|
|
401
401
|
{
|
|
402
402
|
guidance: true,
|
|
@@ -411,7 +411,7 @@ export const docsZh = {
|
|
|
411
411
|
{
|
|
412
412
|
guidance: true,
|
|
413
413
|
description:
|
|
414
|
-
'Use the status prop to surface validation errors inline
|
|
414
|
+
'Use the status prop to surface validation errors inline: show a message like "Time must be during business hours" so users know exactly what to fix.',
|
|
415
415
|
},
|
|
416
416
|
{
|
|
417
417
|
guidance: true,
|
|
@@ -480,7 +480,7 @@ export const docsDense = {
|
|
|
480
480
|
{
|
|
481
481
|
guidance: true,
|
|
482
482
|
description:
|
|
483
|
-
'Choose hour format (12h/24h) to match locale
|
|
483
|
+
'Choose hour format (12h/24h) to match locale: 12h for US, 24h for international.',
|
|
484
484
|
},
|
|
485
485
|
{
|
|
486
486
|
guidance: true,
|
|
@@ -497,6 +497,11 @@ export const docsDense = {
|
|
|
497
497
|
description: 'Use status prop for inline validation errors.',
|
|
498
498
|
},
|
|
499
499
|
{guidance: true, description: 'Enable hasClear for optional fields.'},
|
|
500
|
+
{
|
|
501
|
+
guidance: true,
|
|
502
|
+
description:
|
|
503
|
+
'Place inside InputGroup for a single-line prefix or suffix addon, like a label or timezone marker.',
|
|
504
|
+
},
|
|
500
505
|
{
|
|
501
506
|
guidance: false,
|
|
502
507
|
description:
|
|
@@ -70,11 +70,17 @@ export const docs = {
|
|
|
70
70
|
description: 'Disables the input and all token interactions.',
|
|
71
71
|
default: 'false',
|
|
72
72
|
},
|
|
73
|
+
{
|
|
74
|
+
name: 'htmlName',
|
|
75
|
+
type: 'string',
|
|
76
|
+
description:
|
|
77
|
+
'The HTML name attribute for form submissions. Renders one hidden input per selected item id.',
|
|
78
|
+
},
|
|
73
79
|
{
|
|
74
80
|
name: 'disabledMessage',
|
|
75
81
|
type: 'string',
|
|
76
82
|
description:
|
|
77
|
-
'Explains why the tokenizer is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the input focusable via aria-disabled (input stays blocked). Use this instead of wrapping a disabled Tokenizer in Tooltip
|
|
83
|
+
'Explains why the tokenizer is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the input focusable via aria-disabled (input stays blocked). Use this instead of wrapping a disabled Tokenizer in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.',
|
|
78
84
|
},
|
|
79
85
|
{
|
|
80
86
|
name: 'status',
|
|
@@ -277,11 +283,16 @@ export const docsZh = {
|
|
|
277
283
|
description: '\u7981\u7528\u8f93\u5165\u6846\u548c\u6240\u6709\u6807\u8bb0\u4ea4\u4e92\u3002',
|
|
278
284
|
default: 'false',
|
|
279
285
|
},
|
|
286
|
+
{
|
|
287
|
+
name: 'htmlName',
|
|
288
|
+
type: 'string',
|
|
289
|
+
description: '用于表单提交的 HTML name 属性。为每个已选项目的 id 渲染一个隐藏输入。',
|
|
290
|
+
},
|
|
280
291
|
{
|
|
281
292
|
name: 'disabledMessage',
|
|
282
293
|
type: 'string',
|
|
283
294
|
description:
|
|
284
|
-
'Explains why the tokenizer is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the input focusable via aria-disabled (input stays blocked). Use this instead of wrapping a disabled Tokenizer in Tooltip
|
|
295
|
+
'Explains why the tokenizer is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the input focusable via aria-disabled (input stays blocked). Use this instead of wrapping a disabled Tokenizer in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.',
|
|
285
296
|
},
|
|
286
297
|
{
|
|
287
298
|
name: 'status',
|
|
@@ -435,6 +446,7 @@ export const docsDense = {
|
|
|
435
446
|
renderToken: 'Custom token render. Default renders Token w/ label+onRemove.',
|
|
436
447
|
renderItem: 'Custom dropdown item render. Default renders TypeaheadItem.',
|
|
437
448
|
isDisabled: 'Disables input+all token interactions.',
|
|
449
|
+
htmlName: 'HTML name attr; one hidden input per selected item id.',
|
|
438
450
|
status: 'Validation status w/ type+message for error/warning/success.',
|
|
439
451
|
isLabelHidden: 'Visually hides label; keeps a11y.',
|
|
440
452
|
description: 'Helper text below label.',
|
|
@@ -867,4 +867,37 @@ describe('Tokenizer', () => {
|
|
|
867
867
|
expect(screen.getByRole('combobox')).toBeDisabled();
|
|
868
868
|
});
|
|
869
869
|
});
|
|
870
|
+
describe('form participation', () => {
|
|
871
|
+
it('submits one entry per token id under htmlName', () => {
|
|
872
|
+
const {container} = render(
|
|
873
|
+
<form>
|
|
874
|
+
<Tokenizer
|
|
875
|
+
label="Users"
|
|
876
|
+
htmlName="users"
|
|
877
|
+
searchSource={userSource}
|
|
878
|
+
value={[users[0], users[1]]}
|
|
879
|
+
onChange={() => {}}
|
|
880
|
+
/>
|
|
881
|
+
</form>,
|
|
882
|
+
);
|
|
883
|
+
const data = new FormData(container.querySelector('form')!);
|
|
884
|
+
expect(data.getAll('users')).toEqual([users[0].id, users[1].id]);
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
it('is excluded from form data when disabled', () => {
|
|
888
|
+
const {container} = render(
|
|
889
|
+
<form>
|
|
890
|
+
<Tokenizer
|
|
891
|
+
label="Users"
|
|
892
|
+
htmlName="users"
|
|
893
|
+
searchSource={userSource}
|
|
894
|
+
value={[users[0]]}
|
|
895
|
+
onChange={() => {}}
|
|
896
|
+
isDisabled
|
|
897
|
+
/>
|
|
898
|
+
</form>,
|
|
899
|
+
);
|
|
900
|
+
expect([...new FormData(container.querySelector('form')!).keys()]).toEqual([]);
|
|
901
|
+
});
|
|
902
|
+
});
|
|
870
903
|
});
|
|
@@ -128,6 +128,12 @@ export interface TokenizerProps<T extends SearchableItem> extends Omit<
|
|
|
128
128
|
searchSource: SearchSource<T>;
|
|
129
129
|
/** Currently selected items. */
|
|
130
130
|
value: T[];
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The HTML name attribute for form submissions. When set, hidden inputs
|
|
134
|
+
* carry one entry per selected item's id under this name.
|
|
135
|
+
*/
|
|
136
|
+
htmlName?: string;
|
|
131
137
|
/** Callback when selection changes. Includes change metadata. */
|
|
132
138
|
onChange: (items: T[], change: TokenizerChange<T>) => void;
|
|
133
139
|
/** Render function for dropdown items. Default: TypeaheadItem. */
|
|
@@ -377,6 +383,7 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
377
383
|
maxMenuItems,
|
|
378
384
|
emptySearchResultsText,
|
|
379
385
|
isDisabled = false,
|
|
386
|
+
htmlName,
|
|
380
387
|
disabledMessage,
|
|
381
388
|
hasClear = false,
|
|
382
389
|
endContent,
|
|
@@ -760,6 +767,18 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
760
767
|
: undefined
|
|
761
768
|
}
|
|
762
769
|
/>
|
|
770
|
+
{htmlName != null &&
|
|
771
|
+
value.map(item => (
|
|
772
|
+
<input
|
|
773
|
+
key={item.id}
|
|
774
|
+
type="hidden"
|
|
775
|
+
name={htmlName}
|
|
776
|
+
value={item.id}
|
|
777
|
+
// Disabled native controls are excluded from form submission;
|
|
778
|
+
// mirror that for the hidden carriers.
|
|
779
|
+
disabled={isDisabled}
|
|
780
|
+
/>
|
|
781
|
+
))}
|
|
763
782
|
{(endContent || (hasClear && value.length > 0 && !isDisabled)) && (
|
|
764
783
|
<div {...stylex.props(styles.endSection, endSectionSizeStyles[size])}>
|
|
765
784
|
{endContent}
|
|
@@ -168,7 +168,7 @@ describe('Toolbar', () => {
|
|
|
168
168
|
|
|
169
169
|
it('passes variant to Section', () => {
|
|
170
170
|
const {container} = render(<Toolbar label="Actions" variant="muted" />);
|
|
171
|
-
// Section renders with
|
|
171
|
+
// Section renders with astryx-section class containing the variant
|
|
172
172
|
const sectionInner = container.querySelector('.astryx-section');
|
|
173
173
|
expect(sectionInner).toBeInTheDocument();
|
|
174
174
|
expect(sectionInner?.className).toContain('muted');
|
|
@@ -8,6 +8,16 @@ export const docs = {
|
|
|
8
8
|
displayName: 'Base Typeahead',
|
|
9
9
|
isHiddenFromOverview: true,
|
|
10
10
|
description: 'Unstyled combobox engine providing input, search, keyboard navigation, and dropdown. No wrapper div, no border styling, no token rendering. Used by Typeahead and Tokenizer for custom compositions.',
|
|
11
|
+
usage: {
|
|
12
|
+
description: 'Unstyled combobox engine providing input, search, keyboard navigation, and dropdown. No wrapper div, no border styling, no token rendering. Used by Typeahead and Tokenizer for custom compositions.',
|
|
13
|
+
bestPractices: [
|
|
14
|
+
{ guidance: true, description: 'Use Typeahead or Tokenizer for standard fields — they wrap BaseTypeahead with the wrapper div, border styling, and token rendering it intentionally omits.' },
|
|
15
|
+
{ guidance: true, description: 'Provide your own wrapper div with border and layout when composing directly — BaseTypeahead renders no visual chrome of its own.' },
|
|
16
|
+
{ guidance: true, description: 'Pass anchorRef pointing to your wrapper so the dropdown positions against your custom input chrome, not just the bare input element.' },
|
|
17
|
+
{ guidance: false, description: 'Expect a wrapper div, border, or token rendering — BaseTypeahead is an engine only; all visual chrome is the caller\'s responsibility.' },
|
|
18
|
+
{ guidance: false, description: 'Use BaseTypeahead when Typeahead or Tokenizer would suffice — the extra wrapper and styling work is only justified for truly custom compositions.' },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
11
21
|
props: [
|
|
12
22
|
{
|
|
13
23
|
name: 'searchSource',
|
|
@@ -75,7 +75,7 @@ export const docs = {
|
|
|
75
75
|
name: 'disabledMessage',
|
|
76
76
|
type: 'string',
|
|
77
77
|
description:
|
|
78
|
-
'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the field focusable via aria-disabled (activation stays blocked). Use this instead of wrapping a disabled Typeahead in Tooltip
|
|
78
|
+
'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the field focusable via aria-disabled (activation stays blocked). Use this instead of wrapping a disabled Typeahead in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.',
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
81
|
name: 'maxMenuItems',
|
|
@@ -287,6 +287,11 @@ export const docsDense = {
|
|
|
287
287
|
description:
|
|
288
288
|
'Add a search delay for remote data sources to avoid excessive network requests.',
|
|
289
289
|
},
|
|
290
|
+
{
|
|
291
|
+
guidance: true,
|
|
292
|
+
description:
|
|
293
|
+
'Use inside InputGroup when the typeahead needs a single-line prefix or suffix addon.',
|
|
294
|
+
},
|
|
290
295
|
{
|
|
291
296
|
guidance: false,
|
|
292
297
|
description:
|
package/src/docs-types.ts
CHANGED
|
@@ -217,6 +217,11 @@ export interface ComponentEntry {
|
|
|
217
217
|
examples?: ExampleDoc[];
|
|
218
218
|
/** When true, this sub-component is excluded from the overview page. */
|
|
219
219
|
isHiddenFromOverview?: boolean;
|
|
220
|
+
/** Playground configuration for this specific component. Falls back to
|
|
221
|
+
* the directory doc's `playground` when omitted — declare one here when
|
|
222
|
+
* siblings must not share it (e.g. an overlay drawer whose toggle
|
|
223
|
+
* sub-component should not inherit `overlay: true`). */
|
|
224
|
+
playground?: PlaygroundConfig;
|
|
220
225
|
}
|
|
221
226
|
|
|
222
227
|
/**
|
|
@@ -390,6 +395,13 @@ export interface PlaygroundConfig {
|
|
|
390
395
|
/** Initial prop values for the playground preview.
|
|
391
396
|
* Keys are prop names. Values are primitives or ElementDescriptors. */
|
|
392
397
|
defaults?: Record<string, unknown>;
|
|
398
|
+
/** The component opens as a full-viewport overlay (e.g. via
|
|
399
|
+
* `dialog.showModal()`) and renders nothing inline while closed. The
|
|
400
|
+
* interactive preview shows an open-trigger placeholder instead of an
|
|
401
|
+
* empty stage while `isOpen` is false, and lets the real overlay render
|
|
402
|
+
* when opened. Include `isOpen: false` in `defaults` so the preview can
|
|
403
|
+
* bridge `onOpenChange` back into playground state. */
|
|
404
|
+
overlay?: boolean;
|
|
393
405
|
/** Required parent wrapper for sub-components that depend on a parent
|
|
394
406
|
* context provider (e.g. `Tab` calls `useTabListContext()` and throws
|
|
395
407
|
* standalone). The preview wraps the component in this parent before
|
|
@@ -38,7 +38,7 @@ export const docs = {
|
|
|
38
38
|
{
|
|
39
39
|
name: 'hintElement',
|
|
40
40
|
type: 'ReactNode',
|
|
41
|
-
description: 'The popover hint element to render inside the composite container (as the last child). Portals to the top layer via popover="manual", renders arrow keys with Kbd, and manages its own visibility
|
|
41
|
+
description: 'The popover hint element to render inside the composite container (as the last child). Portals to the top layer via popover="manual", renders arrow keys with Kbd, and manages its own visibility; render it unconditionally.',
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
name: 'onFocus',
|
|
@@ -58,12 +58,12 @@ export const docs = {
|
|
|
58
58
|
],
|
|
59
59
|
usage: {
|
|
60
60
|
description:
|
|
61
|
-
'Shows an ephemeral "← → to navigate" hint anchored to the focused item the first time a roving-tabindex composite (Toolbar, TabList, SegmentedControl, etc.) receives keyboard focus. It teaches sighted keyboard users that arrow keys move within the group. The hint renders arrow keys with Kbd in the top layer (popover="manual") and is CSS-anchor-positioned to the focused element, so overflow containers never clip it. It auto-dismisses on the first arrow press, on timeout, or on blur, and does not re-show for that instance. Toolbar, TabList, and SegmentedControl wire this in automatically
|
|
61
|
+
'Shows an ephemeral "← → to navigate" hint anchored to the focused item the first time a roving-tabindex composite (Toolbar, TabList, SegmentedControl, etc.) receives keyboard focus. It teaches sighted keyboard users that arrow keys move within the group. The hint renders arrow keys with Kbd in the top layer (popover="manual") and is CSS-anchor-positioned to the focused element, so overflow containers never clip it. It auto-dismisses on the first arrow press, on timeout, or on blur, and does not re-show for that instance. Toolbar, TabList, and SegmentedControl wire this in automatically; reach for the hook directly only when building a custom roving-tabindex widget.',
|
|
62
62
|
bestPractices: [
|
|
63
|
-
{ guidance: true, description: 'Compose the returned onFocus/onKeyDown with your existing focus handlers rather than replacing them
|
|
63
|
+
{ guidance: true, description: 'Compose the returned onFocus/onKeyDown with your existing focus handlers rather than replacing them: call onKeyDown first (it only dismisses, never prevents), then your navigation handler.' },
|
|
64
64
|
{ guidance: true, description: 'Render hintElement as the last child of the composite container; it is position:fixed in the top layer and aria-hidden, so it never affects layout or the accessibility tree.' },
|
|
65
65
|
{ guidance: true, description: 'Match orientation to the arrow keys your widget actually responds to so the hint shows the correct icons.' },
|
|
66
|
-
{ guidance: false, description: 'Use for single controls or widgets without roving-tabindex navigation
|
|
66
|
+
{ guidance: false, description: 'Use for single controls or widgets without roving-tabindex navigation; the hint only makes sense where arrows move focus within a group.' },
|
|
67
67
|
],
|
|
68
68
|
},
|
|
69
69
|
relatedComponents: ['Toolbar', 'TabList', 'SegmentedControl'],
|
|
@@ -83,7 +83,7 @@ export const docsDense = {
|
|
|
83
83
|
'options.isEnabled': 'whether the hint is enabled; false suppresses it (e.g. disabled/read-only widget).',
|
|
84
84
|
},
|
|
85
85
|
returnDescriptions: {
|
|
86
|
-
hintElement: 'popover hint to render as last child of the container. Kbd-rendered arrows, top-layer, self-managing
|
|
86
|
+
hintElement: 'popover hint to render as last child of the container. Kbd-rendered arrows, top-layer, self-managing; render unconditionally.',
|
|
87
87
|
onFocus: 'container onFocus: shows hint on first :focus-visible entry from outside.',
|
|
88
88
|
onBlur: 'container onBlur: hides on leaving the composite; re-anchors on internal moves.',
|
|
89
89
|
onKeyDown: 'container onKeyDown: dismisses on first arrow press. Never prevents default.',
|
|
@@ -92,7 +92,7 @@ export const docsDense = {
|
|
|
92
92
|
description:
|
|
93
93
|
'Ephemeral "← → to navigate" hint on first keyboard focus of a roving-tabindex composite. Top-layer anchored popover, aria-hidden. Auto-dismisses on first arrow press, timeout, or blur; no re-show. Toolbar/TabList/SegmentedControl wire it in automatically.',
|
|
94
94
|
bestPractices: [
|
|
95
|
-
{ guidance: true, description: 'Compose returned onFocus/onKeyDown with existing handlers (call onKeyDown first
|
|
95
|
+
{ guidance: true, description: 'Compose returned onFocus/onKeyDown with existing handlers (call onKeyDown first; it only dismisses).' },
|
|
96
96
|
{ guidance: true, description: 'Render hintElement as last child; top-layer + aria-hidden, no layout/a11y impact.' },
|
|
97
97
|
{ guidance: true, description: 'Match orientation to the arrows the widget responds to.' },
|
|
98
98
|
{ guidance: false, description: 'Use for single controls / non-roving widgets.' },
|