@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
|
@@ -392,4 +392,40 @@ describe('CheckboxInput', () => {
|
|
|
392
392
|
expect(screen.getByRole('checkbox')).toBeDisabled();
|
|
393
393
|
});
|
|
394
394
|
});
|
|
395
|
+
describe('form participation', () => {
|
|
396
|
+
it('submits under htmlName when checked', () => {
|
|
397
|
+
const {container} = render(
|
|
398
|
+
<form>
|
|
399
|
+
<CheckboxInput label="Terms" htmlName="terms" value={true} onChange={() => {}} />
|
|
400
|
+
</form>,
|
|
401
|
+
);
|
|
402
|
+
const data = new FormData(container.querySelector('form')!);
|
|
403
|
+
expect(data.get('terms')).toBe('on');
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('is excluded from form data when disabled, even with a disabledMessage', () => {
|
|
407
|
+
const {container} = render(
|
|
408
|
+
<form>
|
|
409
|
+
<CheckboxInput
|
|
410
|
+
label="Terms"
|
|
411
|
+
htmlName="terms"
|
|
412
|
+
value={true}
|
|
413
|
+
onChange={() => {}}
|
|
414
|
+
isDisabled
|
|
415
|
+
disabledMessage="Locked"
|
|
416
|
+
/>
|
|
417
|
+
</form>,
|
|
418
|
+
);
|
|
419
|
+
expect([...new FormData(container.querySelector('form')!).keys()]).toEqual([]);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('submits nothing when unchecked', () => {
|
|
423
|
+
const {container} = render(
|
|
424
|
+
<form>
|
|
425
|
+
<CheckboxInput label="Terms" htmlName="terms" value={false} onChange={() => {}} />
|
|
426
|
+
</form>,
|
|
427
|
+
);
|
|
428
|
+
expect([...new FormData(container.querySelector('form')!).keys()]).toEqual([]);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
395
431
|
});
|
|
@@ -272,6 +272,12 @@ export interface CheckboxInputProps extends Omit<BaseProps, 'onChange'> {
|
|
|
272
272
|
* @default false
|
|
273
273
|
*/
|
|
274
274
|
isDisabled?: boolean;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The HTML name attribute for the underlying checkbox input.
|
|
278
|
+
* Useful for form submissions.
|
|
279
|
+
*/
|
|
280
|
+
htmlName?: string;
|
|
275
281
|
/**
|
|
276
282
|
* Explains why the checkbox is disabled. When set together with
|
|
277
283
|
* `isDisabled`, the checkbox shows a tooltip with this text on hover and
|
|
@@ -374,6 +380,7 @@ export function CheckboxInput({
|
|
|
374
380
|
isLoading = false,
|
|
375
381
|
value,
|
|
376
382
|
isDisabled = false,
|
|
383
|
+
htmlName,
|
|
377
384
|
disabledMessage,
|
|
378
385
|
isReadOnly = false,
|
|
379
386
|
isOptional = false,
|
|
@@ -484,6 +491,10 @@ export function CheckboxInput({
|
|
|
484
491
|
)}
|
|
485
492
|
id={id}
|
|
486
493
|
type="checkbox"
|
|
494
|
+
// Withhold the name while disabled: with a disabledMessage the
|
|
495
|
+
// input stays focusable (not natively disabled), and a disabled
|
|
496
|
+
// control must not submit.
|
|
497
|
+
name={isDisabled ? undefined : htmlName}
|
|
487
498
|
checked={isChecked}
|
|
488
499
|
// With a disabledMessage the checkbox keeps focusability via
|
|
489
500
|
// aria-disabled so the reason is focus-discoverable; toggling is
|
|
@@ -81,7 +81,7 @@ export const docs = {
|
|
|
81
81
|
name: 'disabledMessage',
|
|
82
82
|
type: 'string',
|
|
83
83
|
description:
|
|
84
|
-
'Explains why the group is disabled. Applies to the whole-group disabled state (isDisabled), not per item. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the checkboxes focusable via aria-disabled (toggling stays blocked). Use this instead of wrapping a disabled CheckboxList in Tooltip
|
|
84
|
+
'Explains why the group is disabled. Applies to the whole-group disabled state (isDisabled), not per item. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the checkboxes focusable via aria-disabled (toggling stays blocked). Use this instead of wrapping a disabled CheckboxList in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.',
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
name: 'status',
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Code.test.tsx
|
|
5
|
+
* @input Uses vitest, @testing-library/react, Code component
|
|
6
|
+
* @output Unit tests for Code component behavior
|
|
7
|
+
* @position Testing; validates Code.tsx implementation
|
|
8
|
+
*
|
|
9
|
+
* SYNC: When Code.tsx changes, update tests to match new behavior
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {describe, it, expect, vi} from 'vitest';
|
|
13
|
+
import {render, screen} from '@testing-library/react';
|
|
14
|
+
import {Code} from './Code';
|
|
15
|
+
|
|
16
|
+
describe('Code', () => {
|
|
17
|
+
it('renders children inside a <code> element', () => {
|
|
18
|
+
render(<Code>const x = 1</Code>);
|
|
19
|
+
const el = screen.getByText('const x = 1');
|
|
20
|
+
expect(el.tagName).toBe('CODE');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('forwards ref to the root element', () => {
|
|
24
|
+
const ref = vi.fn();
|
|
25
|
+
render(<Code ref={ref}>code</Code>);
|
|
26
|
+
expect(ref).toHaveBeenCalledWith(expect.any(HTMLElement));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('defaults color to primary', () => {
|
|
30
|
+
render(<Code>code</Code>);
|
|
31
|
+
expect(screen.getByText('code')).toHaveClass('astryx-code', 'primary');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('applies the secondary color', () => {
|
|
35
|
+
render(<Code color="secondary">code</Code>);
|
|
36
|
+
expect(screen.getByText('code')).toHaveClass('astryx-code', 'secondary');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('applies the inherit color', () => {
|
|
40
|
+
render(<Code color="inherit">code</Code>);
|
|
41
|
+
expect(screen.getByText('code')).toHaveClass('astryx-code', 'inherit');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('adds a size class when size="inherit" (font-size + line-height inherit)', () => {
|
|
45
|
+
const {rerender} = render(<Code>code</Code>);
|
|
46
|
+
const defaultClass = screen.getByText('code').getAttribute('class');
|
|
47
|
+
|
|
48
|
+
rerender(<Code size="inherit">code</Code>);
|
|
49
|
+
const inheritClass = screen.getByText('code').getAttribute('class');
|
|
50
|
+
|
|
51
|
+
// size="inherit" adds an extra StyleX class beyond the default rendering.
|
|
52
|
+
expect(inheritClass).not.toEqual(defaultClass);
|
|
53
|
+
});
|
|
54
|
+
});
|
package/src/Code/Code.tsx
CHANGED
|
@@ -42,9 +42,46 @@ const styles = stylex.create({
|
|
|
42
42
|
},
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
const colorStyles = stylex.create({
|
|
46
|
+
primary: {
|
|
47
|
+
color: colorVars['--color-text-primary'],
|
|
48
|
+
},
|
|
49
|
+
secondary: {
|
|
50
|
+
color: colorVars['--color-text-secondary'],
|
|
51
|
+
},
|
|
52
|
+
inherit: {
|
|
53
|
+
color: 'inherit',
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const sizeStyles = stylex.create({
|
|
58
|
+
// Adopt the surrounding text's font-size and line-height, for inline code
|
|
59
|
+
// that should match differently-sized text around it.
|
|
60
|
+
inherit: {
|
|
61
|
+
fontSize: 'inherit',
|
|
62
|
+
lineHeight: 'inherit',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/** Text color for {@link Code}, mirroring the primary/secondary/inherit subset of Text. */
|
|
67
|
+
export type CodeColor = 'primary' | 'secondary' | 'inherit';
|
|
68
|
+
|
|
69
|
+
/** Font size for {@link Code}. `'inherit'` adopts the surrounding text size. */
|
|
70
|
+
export type CodeSize = 'inherit';
|
|
71
|
+
|
|
45
72
|
export interface CodeProps extends BaseProps<HTMLElement> {
|
|
46
73
|
/** Ref forwarded to the root element */
|
|
47
74
|
ref?: React.Ref<HTMLElement>;
|
|
75
|
+
/**
|
|
76
|
+
* Text color. Mirrors the Text color subset.
|
|
77
|
+
* @default 'primary'
|
|
78
|
+
*/
|
|
79
|
+
color?: CodeColor;
|
|
80
|
+
/**
|
|
81
|
+
* Font size. Set to `'inherit'` to adopt the surrounding text's font-size
|
|
82
|
+
* and line-height (useful for inline code inside larger/smaller text).
|
|
83
|
+
*/
|
|
84
|
+
size?: CodeSize;
|
|
48
85
|
/** Code content */
|
|
49
86
|
children: ReactNode;
|
|
50
87
|
}
|
|
@@ -64,6 +101,8 @@ export interface CodeProps extends BaseProps<HTMLElement> {
|
|
|
64
101
|
*/
|
|
65
102
|
export function Code({
|
|
66
103
|
children,
|
|
104
|
+
color = 'primary',
|
|
105
|
+
size,
|
|
67
106
|
xstyle,
|
|
68
107
|
className,
|
|
69
108
|
style,
|
|
@@ -73,13 +112,18 @@ export function Code({
|
|
|
73
112
|
return (
|
|
74
113
|
<code
|
|
75
114
|
ref={ref}
|
|
115
|
+
{...props}
|
|
76
116
|
{...mergeProps(
|
|
77
|
-
themeProps('code'),
|
|
78
|
-
stylex.props(
|
|
117
|
+
themeProps('code', {color}),
|
|
118
|
+
stylex.props(
|
|
119
|
+
styles.base,
|
|
120
|
+
colorStyles[color],
|
|
121
|
+
size && sizeStyles[size],
|
|
122
|
+
xstyle,
|
|
123
|
+
),
|
|
79
124
|
className,
|
|
80
125
|
style,
|
|
81
|
-
)}
|
|
82
|
-
data-testid={props['data-testid']}>
|
|
126
|
+
)}>
|
|
83
127
|
{children}
|
|
84
128
|
</code>
|
|
85
129
|
);
|
package/src/Code/index.ts
CHANGED
package/src/CodeBlock/index.ts
CHANGED
|
@@ -15,7 +15,7 @@ export {CodeBlock} from './CodeBlock';
|
|
|
15
15
|
export type {CodeBlockProps} from './CodeBlock';
|
|
16
16
|
|
|
17
17
|
export {Code} from '../Code';
|
|
18
|
-
export type {CodeProps} from '../Code';
|
|
18
|
+
export type {CodeProps, CodeColor, CodeSize} from '../Code';
|
|
19
19
|
|
|
20
20
|
export {
|
|
21
21
|
tokenize,
|
|
@@ -20,7 +20,9 @@ import {
|
|
|
20
20
|
typeScaleVars,
|
|
21
21
|
typographyVars,
|
|
22
22
|
} from '../theme/tokens.stylex';
|
|
23
|
+
import {mergeProps} from '../utils';
|
|
23
24
|
import type {BaseProps} from '../BaseProps';
|
|
25
|
+
import {themeProps} from '../utils/themeProps';
|
|
24
26
|
|
|
25
27
|
const styles = stylex.create({
|
|
26
28
|
empty: {
|
|
@@ -64,9 +66,21 @@ export interface CommandPaletteEmptyProps extends BaseProps<HTMLDivElement> {
|
|
|
64
66
|
export function CommandPaletteEmpty({
|
|
65
67
|
ref,
|
|
66
68
|
children,
|
|
69
|
+
xstyle,
|
|
70
|
+
className,
|
|
71
|
+
style,
|
|
72
|
+
...props
|
|
67
73
|
}: CommandPaletteEmptyProps) {
|
|
68
74
|
return (
|
|
69
|
-
<div
|
|
75
|
+
<div
|
|
76
|
+
ref={ref}
|
|
77
|
+
{...mergeProps(
|
|
78
|
+
themeProps('command-palette-empty'),
|
|
79
|
+
stylex.props(styles.empty, xstyle),
|
|
80
|
+
className,
|
|
81
|
+
style,
|
|
82
|
+
)}
|
|
83
|
+
{...props}>
|
|
70
84
|
{children}
|
|
71
85
|
</div>
|
|
72
86
|
);
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
} from '../theme/tokens.stylex';
|
|
56
56
|
import {mergeProps} from '../utils';
|
|
57
57
|
import type {BaseProps} from '../BaseProps';
|
|
58
|
+
import type {StyleXStyles} from '../theme/types';
|
|
58
59
|
import {themeProps} from '../utils/themeProps';
|
|
59
60
|
import type {
|
|
60
61
|
DropdownMenuOption,
|
|
@@ -114,6 +115,13 @@ export type ContextMenuOption = DropdownMenuOption;
|
|
|
114
115
|
interface ContextMenuBaseProps extends BaseProps {
|
|
115
116
|
/** Ref forwarded to the trigger wrapper element. */
|
|
116
117
|
ref?: React.Ref<HTMLDivElement>;
|
|
118
|
+
/**
|
|
119
|
+
* Styles applied to the trigger wrapper element (the right-click target).
|
|
120
|
+
* By default the trigger is a plain block that hugs its content — pass a
|
|
121
|
+
* fill style (e.g. `width/height: 100%`) when the whole parent area should
|
|
122
|
+
* be right-clickable (as the Table does for full-cell context menus).
|
|
123
|
+
*/
|
|
124
|
+
triggerXstyle?: StyleXStyles | StyleXStyles[];
|
|
117
125
|
/** The trigger area — right-click on this to open the menu. */
|
|
118
126
|
children: ReactNode;
|
|
119
127
|
/** Custom menu width. @default '160px' */
|
|
@@ -184,6 +192,7 @@ export function ContextMenu({
|
|
|
184
192
|
className,
|
|
185
193
|
style,
|
|
186
194
|
xstyle,
|
|
195
|
+
triggerXstyle,
|
|
187
196
|
'data-testid': testId,
|
|
188
197
|
...props
|
|
189
198
|
}: ContextMenuProps) {
|
|
@@ -382,7 +391,14 @@ export function ContextMenu({
|
|
|
382
391
|
onContextMenu={handleContextMenu}
|
|
383
392
|
{...longPressHandlers}
|
|
384
393
|
data-testid={testId}
|
|
385
|
-
{...stylex.props(
|
|
394
|
+
{...stylex.props(
|
|
395
|
+
styles.trigger,
|
|
396
|
+
...(triggerXstyle
|
|
397
|
+
? Array.isArray(triggerXstyle)
|
|
398
|
+
? triggerXstyle
|
|
399
|
+
: [triggerXstyle]
|
|
400
|
+
: []),
|
|
401
|
+
)}>
|
|
386
402
|
{children}
|
|
387
403
|
</div>
|
|
388
404
|
|
|
@@ -56,7 +56,7 @@ export const docs = {
|
|
|
56
56
|
name: 'disabledMessage',
|
|
57
57
|
type: 'string',
|
|
58
58
|
description:
|
|
59
|
-
'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 DateInput in Tooltip
|
|
59
|
+
'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 DateInput in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.',
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
name: 'value',
|
|
@@ -432,6 +432,11 @@ export const docsDense = {
|
|
|
432
432
|
description:
|
|
433
433
|
'Show a loading state with changeAction when the date triggers a server-side save.',
|
|
434
434
|
},
|
|
435
|
+
{
|
|
436
|
+
guidance: true,
|
|
437
|
+
description:
|
|
438
|
+
'Use inside InputGroup for a short static prefix or suffix, such as a due-date hint.',
|
|
439
|
+
},
|
|
435
440
|
{
|
|
436
441
|
guidance: false,
|
|
437
442
|
description:
|
|
@@ -52,7 +52,7 @@ export const docs = {
|
|
|
52
52
|
name: 'disabledMessage',
|
|
53
53
|
type: 'string',
|
|
54
54
|
description:
|
|
55
|
-
'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 DateRangeInput in Tooltip
|
|
55
|
+
'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 DateRangeInput in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.',
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
name: 'value',
|
|
@@ -146,8 +146,8 @@ const styles = stylex.create({
|
|
|
146
146
|
},
|
|
147
147
|
},
|
|
148
148
|
fontFamily: typographyVars['--font-family-body'],
|
|
149
|
-
fontSize: typeScaleVars['--text-
|
|
150
|
-
lineHeight: typeScaleVars['--text-
|
|
149
|
+
fontSize: typeScaleVars['--text-label-size'],
|
|
150
|
+
lineHeight: typeScaleVars['--text-label-leading'],
|
|
151
151
|
color: colorVars['--color-text-primary'],
|
|
152
152
|
cursor: 'pointer',
|
|
153
153
|
textAlign: 'start',
|
|
@@ -58,7 +58,7 @@ export const docs = {
|
|
|
58
58
|
name: 'disabledMessage',
|
|
59
59
|
type: 'string',
|
|
60
60
|
description:
|
|
61
|
-
'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 DateTimeInput in Tooltip
|
|
61
|
+
'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 DateTimeInput in Tooltip. Disabled controls swallow the hover events an external Tooltip needs.',
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
name: 'value',
|
|
@@ -72,7 +72,7 @@ import {
|
|
|
72
72
|
plainDateFormat,
|
|
73
73
|
DATE_FORMAT_LONG,
|
|
74
74
|
} from '../utils/plainDate';
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
import type {BaseProps} from '../BaseProps';
|
|
77
77
|
import type {SizeValue} from '../utils/types';
|
|
78
78
|
import {useSize} from '../SizeContext/SizeContext';
|
|
@@ -344,11 +344,6 @@ export interface DateTimeInputProps extends Omit<
|
|
|
344
344
|
* @default 1
|
|
345
345
|
*/
|
|
346
346
|
numberOfMonths?: 1 | 2;
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Style overrides applied to the outer row container.
|
|
350
|
-
*/
|
|
351
|
-
xstyle?: StyleXStyles;
|
|
352
347
|
}
|
|
353
348
|
|
|
354
349
|
function splitDateTime(dt: ISODateTimeString | undefined): {
|
package/src/Divider/Divider.tsx
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import type {ReactNode} from 'react';
|
|
19
19
|
import type {BaseProps} from '../BaseProps';
|
|
20
20
|
import * as stylex from '@stylexjs/stylex';
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
import {
|
|
23
23
|
colorVars,
|
|
24
24
|
spacingVars,
|
|
@@ -80,28 +80,6 @@ export interface DividerProps extends BaseProps<HTMLDivElement> {
|
|
|
80
80
|
* @default false
|
|
81
81
|
*/
|
|
82
82
|
isFullBleed?: boolean;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* StyleX styles created via `stylex.create()`. Merged with the component's
|
|
86
|
-
* base styles inside a single `stylex.props()` call for optimal deduplication.
|
|
87
|
-
*
|
|
88
|
-
* @example
|
|
89
|
-
* ```
|
|
90
|
-
* const overrides = stylex.create({ root: { marginBottom: 8 } });
|
|
91
|
-
* <Component xstyle={overrides.root} />
|
|
92
|
-
* ```
|
|
93
|
-
*/
|
|
94
|
-
xstyle?: StyleXStyles;
|
|
95
|
-
/**
|
|
96
|
-
* CSS class name(s) appended to the root element.
|
|
97
|
-
* If you're using StyleX, prefer `xstyle` for optimal style deduplication.
|
|
98
|
-
*/
|
|
99
|
-
className?: string;
|
|
100
|
-
/**
|
|
101
|
-
* Inline styles to apply to the root element. Spread after StyleX
|
|
102
|
-
* inline styles, so these values take priority.
|
|
103
|
-
*/
|
|
104
|
-
style?: React.CSSProperties;
|
|
105
83
|
}
|
|
106
84
|
|
|
107
85
|
const baseStyles = stylex.create({
|
|
@@ -73,7 +73,9 @@ describe('DropdownMenu', () => {
|
|
|
73
73
|
// The popup exposes its own role="menu"; it must not be nested inside a
|
|
74
74
|
// modal dialog, which would announce an unnamed dialog around the menu
|
|
75
75
|
// while focus stays on the trigger.
|
|
76
|
-
expect(
|
|
76
|
+
expect(
|
|
77
|
+
screen.queryByRole('dialog', {hidden: true}),
|
|
78
|
+
).not.toBeInTheDocument();
|
|
77
79
|
expect(
|
|
78
80
|
document.querySelector('[aria-modal="true"]'),
|
|
79
81
|
).not.toBeInTheDocument();
|
|
@@ -192,11 +194,11 @@ describe('DropdownMenu light-dismiss race', () => {
|
|
|
192
194
|
<DropdownMenu
|
|
193
195
|
button={{label: 'Actions'}}
|
|
194
196
|
items={[{label: 'Edit'}]}
|
|
195
|
-
data-testid="
|
|
197
|
+
data-testid="astryx-dropdown-menu"
|
|
196
198
|
/>,
|
|
197
199
|
);
|
|
198
200
|
|
|
199
|
-
const trigger = screen.getByTestId('
|
|
201
|
+
const trigger = screen.getByTestId('astryx-dropdown-menu');
|
|
200
202
|
fireEvent.click(trigger); // open
|
|
201
203
|
fireEvent.click(trigger); // close (stamps guard)
|
|
202
204
|
fireEvent.click(trigger); // would re-open without guard
|
package/src/Field/Field.doc.mjs
CHANGED
|
@@ -27,7 +27,7 @@ export const docs = {
|
|
|
27
27
|
},
|
|
28
28
|
theming: {
|
|
29
29
|
targets: [
|
|
30
|
-
{className: 'astryx-field'},
|
|
30
|
+
{className: 'astryx-field', visualProps: ['layout']},
|
|
31
31
|
{className: 'astryx-field-label'},
|
|
32
32
|
{className: 'astryx-field-status', visualProps: ['type', 'variant']},
|
|
33
33
|
],
|
|
@@ -92,7 +92,7 @@ export const docs = {
|
|
|
92
92
|
name: 'disabledMessage',
|
|
93
93
|
type: 'string',
|
|
94
94
|
description:
|
|
95
|
-
'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the trigger focusable via aria-disabled (opening the file picker stays blocked). Use this instead of wrapping a disabled FileInput in Tooltip
|
|
95
|
+
'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the trigger focusable via aria-disabled (opening the file picker stays blocked). Use this instead of wrapping a disabled FileInput in Tooltip; disabled controls swallow the hover events an external Tooltip needs.',
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
98
|
name: 'isLoading',
|
|
@@ -4,7 +4,6 @@ exports[`FormLayout > matches snapshot for horizontal direction 1`] = `
|
|
|
4
4
|
<div
|
|
5
5
|
class="astryx-form-layout horizontal FormLayout__styles.base xdt5ytf x18g69wz FormLayout__styles.horizontal xrvj5dj x1mt1orb xu6a5m6"
|
|
6
6
|
data-direction="horizontal"
|
|
7
|
-
data-style-src="@astryxdesign/core:src/FormLayout/FormLayout.tsx:40; @astryxdesign/core:src/FormLayout/FormLayout.tsx:45"
|
|
8
7
|
data-testid="layout"
|
|
9
8
|
>
|
|
10
9
|
<input
|
|
@@ -20,7 +19,6 @@ exports[`FormLayout > matches snapshot for horizontal-labels direction 1`] = `
|
|
|
20
19
|
<div
|
|
21
20
|
class="astryx-form-layout horizontal-labels FormLayout__styles.base xdt5ytf FormLayout__styles.horizontalLabels xrvj5dj x1pmbctz xlaq8a2 x7a106z xedohl4 x1rpgqan x1a1jff"
|
|
22
21
|
data-direction="horizontal-labels"
|
|
23
|
-
data-style-src="@astryxdesign/core:src/FormLayout/FormLayout.tsx:40; @astryxdesign/core:src/FormLayout/FormLayout.tsx:50"
|
|
24
22
|
data-testid="layout"
|
|
25
23
|
>
|
|
26
24
|
<label>
|
|
@@ -36,7 +34,6 @@ exports[`FormLayout > matches snapshot for vertical direction 1`] = `
|
|
|
36
34
|
<div
|
|
37
35
|
class="astryx-form-layout vertical FormLayout__styles.base x78zum5 xdt5ytf x18g69wz"
|
|
38
36
|
data-direction="vertical"
|
|
39
|
-
data-style-src="@astryxdesign/core:src/FormLayout/FormLayout.tsx:40"
|
|
40
37
|
data-testid="layout"
|
|
41
38
|
>
|
|
42
39
|
<input
|
package/src/Grid/Grid.doc.mjs
CHANGED
|
@@ -17,7 +17,7 @@ export const docs = {
|
|
|
17
17
|
{ guidance: true, description: 'Use `repeat: \'fill\'` (the default) for consistent item widths. Use `\'fit\'` when items should stretch to fill leftover space.' },
|
|
18
18
|
{ guidance: false, description: 'Write manual CSS grid; Grid handles spacing and responsive behavior for you.' },
|
|
19
19
|
{ guidance: false, description: 'Use `HStack` with wrapping for grids; use Grid instead.' },
|
|
20
|
-
{ guidance: true, description: 'Track templates use CSS-variable indirection (not raw inline styles), so `xstyle` overrides of `gridTemplateColumns`
|
|
20
|
+
{ guidance: true, description: 'Track templates use CSS-variable indirection (not raw inline styles), so `xstyle` overrides of `gridTemplateColumns` (including inside `@media` queries) take effect.' },
|
|
21
21
|
],
|
|
22
22
|
},
|
|
23
23
|
theming: {
|
|
@@ -112,7 +112,7 @@ export const docsZh = {
|
|
|
112
112
|
{ guidance: true, description: 'Use `repeat: \'fill\'` (the default) for consistent item widths. Use `\'fit\'` when items should stretch to fill leftover space.' },
|
|
113
113
|
{ guidance: false, description: 'Write manual CSS grid; Grid handles spacing and responsive behavior for you.' },
|
|
114
114
|
{ guidance: false, description: 'Use `HStack` with wrapping for grids; use Grid instead.' },
|
|
115
|
-
{ guidance: true, description: 'Track templates use CSS-variable indirection (not raw inline styles), so `xstyle` overrides of `gridTemplateColumns`
|
|
115
|
+
{ guidance: true, description: 'Track templates use CSS-variable indirection (not raw inline styles), so `xstyle` overrides of `gridTemplateColumns` (including inside `@media` queries) take effect.' },
|
|
116
116
|
],
|
|
117
117
|
},
|
|
118
118
|
};
|
|
@@ -128,7 +128,7 @@ export const docsDense = {
|
|
|
128
128
|
{ guidance: true, description: 'Use repeat: \'fill\' (the default) for consistent item widths. Use \'fit\' when items should stretch to fill leftover space.' },
|
|
129
129
|
{ guidance: false, description: 'Write manual CSS grid; Grid handles spacing and responsive behavior for you.' },
|
|
130
130
|
{ guidance: false, description: 'Use HStack with wrapping for grids; use Grid instead.' },
|
|
131
|
-
{ guidance: true, description: 'track templates use CSS-var indirection, not inline styles
|
|
131
|
+
{ guidance: true, description: 'track templates use CSS-var indirection, not inline styles, so xstyle/@media overrides of gridTemplateColumns work.' },
|
|
132
132
|
],
|
|
133
133
|
},
|
|
134
134
|
};
|
|
@@ -116,4 +116,76 @@ describe('Edge Compensation', () => {
|
|
|
116
116
|
expect(dismissButton).toHaveAttribute(EDGE_COMP_ATTR);
|
|
117
117
|
});
|
|
118
118
|
});
|
|
119
|
+
|
|
120
|
+
// The container's compensation selector is a direct-child combinator:
|
|
121
|
+
// :has(> [data-astryx-edge-comp]:last-child)
|
|
122
|
+
// so an edge-comp marker only counts when it sits on a *direct* child of the
|
|
123
|
+
// slot. Attaching a tooltip must not bury the marker one level deeper — the
|
|
124
|
+
// Button uses the tooltip hook (no wrapper element) so the marker stays on the
|
|
125
|
+
// button itself (#2578).
|
|
126
|
+
describe('Tooltip attachment preserves marker discoverability', () => {
|
|
127
|
+
// Resolve the toolbar slot (the flex wrapper that carries the negative
|
|
128
|
+
// margin) — it is the direct child of the [role="toolbar"] row.
|
|
129
|
+
const findSlot = (
|
|
130
|
+
button: HTMLElement,
|
|
131
|
+
toolbar: HTMLElement,
|
|
132
|
+
): HTMLElement => {
|
|
133
|
+
let node = button;
|
|
134
|
+
while (node.parentElement && node.parentElement !== toolbar) {
|
|
135
|
+
node = node.parentElement;
|
|
136
|
+
}
|
|
137
|
+
return node;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
it('keeps the marker on a direct child of the slot without a tooltip', () => {
|
|
141
|
+
const {container} = render(
|
|
142
|
+
<Toolbar
|
|
143
|
+
label="Actions"
|
|
144
|
+
endContent={
|
|
145
|
+
<Button
|
|
146
|
+
label="Close"
|
|
147
|
+
variant="ghost"
|
|
148
|
+
isIconOnly
|
|
149
|
+
icon={<span>x</span>}
|
|
150
|
+
/>
|
|
151
|
+
}
|
|
152
|
+
/>,
|
|
153
|
+
);
|
|
154
|
+
const button = screen.getByRole('button', {name: 'Close'});
|
|
155
|
+
const toolbar = container.querySelector(
|
|
156
|
+
'[role="toolbar"]',
|
|
157
|
+
) as HTMLElement;
|
|
158
|
+
const slot = findSlot(button, toolbar);
|
|
159
|
+
const directMarked = slot.querySelector(`:scope > [${EDGE_COMP_ATTR}]`);
|
|
160
|
+
expect(directMarked).not.toBeNull();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('keeps the marker on a direct child of the slot when tooltip is set', () => {
|
|
164
|
+
const {container} = render(
|
|
165
|
+
<Toolbar
|
|
166
|
+
label="Actions"
|
|
167
|
+
endContent={
|
|
168
|
+
<Button
|
|
169
|
+
label="Close"
|
|
170
|
+
variant="ghost"
|
|
171
|
+
isIconOnly
|
|
172
|
+
icon={<span>x</span>}
|
|
173
|
+
tooltip="Close panel"
|
|
174
|
+
/>
|
|
175
|
+
}
|
|
176
|
+
/>,
|
|
177
|
+
);
|
|
178
|
+
const button = screen.getByRole('button', {name: 'Close'});
|
|
179
|
+
const toolbar = container.querySelector(
|
|
180
|
+
'[role="toolbar"]',
|
|
181
|
+
) as HTMLElement;
|
|
182
|
+
const slot = findSlot(button, toolbar);
|
|
183
|
+
// The tooltip is attached via the hook (no wrapper element), so the
|
|
184
|
+
// marker stays on the button — a direct child of the container's slot —
|
|
185
|
+
// and the container's direct-child `:has(> [marker]:last-child)` selector
|
|
186
|
+
// still matches.
|
|
187
|
+
const directMarked = slot.querySelector(`:scope > [${EDGE_COMP_ATTR}]`);
|
|
188
|
+
expect(directMarked).not.toBeNull();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
119
191
|
});
|
|
@@ -95,6 +95,29 @@ describe('Markdown', () => {
|
|
|
95
95
|
expect(link.getAttribute('rel')).toBe('noopener noreferrer');
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
+
it('renders a footer reference-style link as an anchor', () => {
|
|
99
|
+
// The XDS parser previously had no reference-definition support, so this
|
|
100
|
+
// rendered as literal `[the docs][docs]` text with the definition leaking
|
|
101
|
+
// as a paragraph. It now resolves to a real anchor.
|
|
102
|
+
render(
|
|
103
|
+
<Markdown>
|
|
104
|
+
{'See [the docs][docs] here.\n\n[docs]: https://example.com/docs\n'}
|
|
105
|
+
</Markdown>,
|
|
106
|
+
);
|
|
107
|
+
const link = screen.getByText('the docs');
|
|
108
|
+
expect(link.tagName).toBe('A');
|
|
109
|
+
expect(link.getAttribute('href')).toBe('https://example.com/docs');
|
|
110
|
+
// The definition line must not leak into the rendered output.
|
|
111
|
+
expect(screen.queryByText(/\[docs\]:/)).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('renders a shortcut reference-style link as an anchor', () => {
|
|
115
|
+
render(<Markdown>{'See [the docs].\n\n[the docs]: /docs'}</Markdown>);
|
|
116
|
+
const link = screen.getByText('the docs');
|
|
117
|
+
expect(link.tagName).toBe('A');
|
|
118
|
+
expect(link.getAttribute('href')).toBe('/docs');
|
|
119
|
+
});
|
|
120
|
+
|
|
98
121
|
it('does not add target="_blank" to relative links', () => {
|
|
99
122
|
render(<Markdown>{'[internal](/page)'}</Markdown>);
|
|
100
123
|
const link = screen.getByText('internal');
|