@astryxdesign/core 0.6.2-canary.bfee8a4 → 0.6.2-canary.c8d5849
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/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +2 -2
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +12 -2
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +6 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +1 -1
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +4 -4
- package/dist/DateInput/NativeDateField.d.ts.map +1 -1
- package/dist/DateInput/NativeDateField.js +16 -16
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +2 -2
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +4 -4
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +8 -8
- package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
- package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
- package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +1 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +7 -7
- package/dist/Kbd/Kbd.d.ts +5 -3
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +36 -42
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +6 -2
- package/dist/Markdown/Markdown.d.ts +8 -2
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
- package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
- package/dist/Markdown/Markdown.fr23.bench.js +150 -0
- package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
- package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
- package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
- package/dist/Markdown/Markdown.js +262 -109
- package/dist/Markdown/ast.d.ts +162 -0
- package/dist/Markdown/ast.d.ts.map +1 -0
- package/dist/Markdown/ast.js +74 -0
- package/dist/Markdown/index.d.ts +5 -3
- package/dist/Markdown/index.d.ts.map +1 -1
- package/dist/Markdown/index.js +3 -2
- package/dist/Markdown/parser/index.d.ts +10 -0
- package/dist/Markdown/parser/index.d.ts.map +1 -0
- package/dist/Markdown/parser/index.js +10 -0
- package/dist/Markdown/parser.d.ts +126 -58
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +716 -167
- package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
- package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
- package/dist/Markdown/plugins/frontmatter.js +132 -0
- package/dist/Markdown/plugins/index.d.ts +16 -0
- package/dist/Markdown/plugins/index.d.ts.map +1 -0
- package/dist/Markdown/plugins/index.js +13 -0
- package/dist/Markdown/plugins/protocol.d.ts +191 -0
- package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
- package/dist/Markdown/plugins/protocol.js +957 -0
- package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
- package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
- package/dist/Markdown/plugins/semanticFence.js +213 -0
- package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
- package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
- package/dist/Markdown/plugins/sourceDecoration.js +393 -0
- package/dist/Markdown/plugins/textTransform.d.ts +30 -0
- package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
- package/dist/Markdown/plugins/textTransform.js +374 -0
- package/dist/Markdown/remark.d.ts +196 -0
- package/dist/Markdown/remark.d.ts.map +1 -0
- package/dist/Markdown/remark.js +1062 -0
- package/dist/Markdown/url.d.ts +4 -0
- package/dist/Markdown/url.d.ts.map +1 -0
- package/dist/Markdown/url.js +25 -0
- package/dist/Markdown/utils.d.ts +4 -2
- package/dist/Markdown/utils.d.ts.map +1 -1
- package/dist/Markdown/utils.js +2 -1
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +4 -4
- package/dist/Outline/index.d.ts +1 -0
- package/dist/Outline/index.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
- package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
- package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
- package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/useOutlineFromMarkdown.js +8 -3
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +36 -31
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +13 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +1 -1
- package/dist/SideNav/SideNav.d.ts +2 -1
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +9 -3
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +14 -8
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +23 -7
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +11 -0
- package/dist/TabList/Tab.d.ts +1 -1
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +20 -7
- package/dist/Table/Table.d.ts +1 -0
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +29 -13
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +1 -1
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +2 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +4 -4
- package/dist/ToggleButton/ToggleButton.d.ts +2 -1
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +7 -1
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +16 -7
- package/dist/astryx.css +14 -6
- package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +9 -0
- package/package.json +18 -3
- package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
- package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
- package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
- package/src/Button/__tests__/Button.a11y.states.ts +9 -0
- package/src/Chat/ChatComposerInput.doc.mjs +3 -3
- package/src/Chat/ChatComposerInput.tsx +10 -2
- package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
- package/src/CheckboxInput/CheckboxInput.tsx +21 -1
- package/src/ClickableCard/ClickableCard.test.tsx +102 -5
- package/src/Collapsible/Collapsible.doc.mjs +11 -0
- package/src/Collapsible/Collapsible.test.tsx +21 -0
- package/src/Collapsible/Collapsible.tsx +5 -0
- package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
- package/src/ComplexSelector/ComplexSelector.tsx +6 -1
- package/src/DateInput/DateInput.tsx +6 -1
- package/src/DateInput/NativeDateField.tsx +24 -8
- package/src/DateInput/TouchDateField.tsx +6 -2
- package/src/DateRangeInput/DateRangeInput.tsx +6 -1
- package/src/DateTimeInput/DateTimeInput.tsx +6 -1
- package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
- package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
- package/src/Field/PanelSearchInput.tsx +5 -2
- package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
- package/src/FileInput/FileInput.tsx +11 -2
- package/src/Kbd/Kbd.doc.mjs +3 -3
- package/src/Kbd/Kbd.test.tsx +57 -1
- package/src/Kbd/Kbd.tsx +57 -37
- package/src/Layout/LayoutFooter.doc.mjs +17 -0
- package/src/Link/Link.doc.mjs +11 -0
- package/src/Link/Link.test.tsx +24 -0
- package/src/Link/Link.tsx +5 -0
- package/src/Markdown/Markdown.doc.mjs +272 -0
- package/src/Markdown/Markdown.fr23.bench.ts +174 -0
- package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
- package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
- package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
- package/src/Markdown/Markdown.public.test.ts +126 -3
- package/src/Markdown/Markdown.spec.md +197 -44
- package/src/Markdown/Markdown.test.tsx +11 -0
- package/src/Markdown/Markdown.tsx +414 -135
- package/src/Markdown/ast.test.ts +307 -0
- package/src/Markdown/ast.ts +337 -0
- package/src/Markdown/incremental.test.ts +12 -0
- package/src/Markdown/index.ts +37 -1
- package/src/Markdown/modules/remark.spec.md +269 -0
- package/src/Markdown/parser/index.ts +64 -0
- package/src/Markdown/parser.public.test.ts +103 -0
- package/src/Markdown/parser.ts +1180 -235
- package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
- package/src/Markdown/plugins/frontmatter.ts +198 -0
- package/src/Markdown/plugins/index.ts +53 -0
- package/src/Markdown/plugins/protocol.test.tsx +1022 -0
- package/src/Markdown/plugins/protocol.ts +1618 -0
- package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
- package/src/Markdown/plugins/semanticFence.ts +424 -0
- package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
- package/src/Markdown/plugins/sourceDecoration.ts +539 -0
- package/src/Markdown/plugins/textTransform.test.ts +467 -0
- package/src/Markdown/plugins/textTransform.ts +554 -0
- package/src/Markdown/remark.test.tsx +1877 -0
- package/src/Markdown/remark.ts +1574 -0
- package/src/Markdown/url.ts +30 -0
- package/src/Markdown/utils.ts +13 -1
- package/src/MultiSelector/MultiSelector.test.tsx +1 -35
- package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
- package/src/NumberInput/NumberInput.tsx +6 -1
- package/src/Outline/Outline.doc.mjs +7 -5
- package/src/Outline/Outline.spec.md +1 -1
- package/src/Outline/index.ts +1 -0
- package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
- package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
- package/src/Outline/useOutlineFromMarkdown.ts +15 -3
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
- package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
- package/src/ProgressBar/ProgressBar.test.tsx +29 -11
- package/src/ProgressBar/ProgressBar.tsx +59 -72
- package/src/RadioList/RadioList.doc.mjs +11 -0
- package/src/RadioList/RadioList.test.tsx +32 -0
- package/src/RadioList/RadioListItem.tsx +25 -1
- package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
- package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
- package/src/Selector/Selector.spec.md +12 -9
- package/src/Selector/Selector.tsx +6 -1
- package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
- package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
- package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
- package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
- package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
- package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
- package/src/SideNav/SideNav.doc.mjs +1 -1
- package/src/SideNav/SideNav.test.tsx +10 -0
- package/src/SideNav/SideNav.tsx +14 -2
- package/src/Slider/Slider.doc.mjs +11 -0
- package/src/Slider/Slider.test.tsx +128 -0
- package/src/Slider/Slider.tsx +25 -7
- package/src/Spinner/Spinner.test.tsx +4 -0
- package/src/Spinner/Spinner.tsx +31 -18
- package/src/Switch/Switch.doc.mjs +11 -0
- package/src/Switch/Switch.test.tsx +16 -0
- package/src/Switch/Switch.tsx +28 -0
- package/src/TabList/Tab.tsx +35 -7
- package/src/TabList/TabList.doc.mjs +11 -0
- package/src/TabList/TabList.test.tsx +66 -0
- package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
- package/src/Table/Table.doc.mjs +1 -1
- package/src/Table/Table.spec.md +54 -40
- package/src/Table/Table.test.tsx +83 -10
- package/src/Table/Table.tsx +30 -22
- package/src/Table/__tests__/TableScroll.a11y.chromium.spec.ts +137 -0
- package/src/TextArea/TextArea.tsx +6 -1
- package/src/TextInput/TextInput.test.tsx +20 -1
- package/src/TextInput/TextInput.tsx +6 -1
- package/src/TimeInput/TimeInput.tsx +6 -1
- package/src/ToggleButton/ToggleButton.test.tsx +133 -0
- package/src/ToggleButton/ToggleButton.tsx +9 -2
- package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
- package/src/Tokenizer/Tokenizer.spec.md +142 -75
- package/src/TreeList/TreeList.spec.md +123 -75
- package/src/Typeahead/BaseTypeahead.spec.md +4 -3
- package/src/Typeahead/BaseTypeahead.tsx +21 -7
- package/src/Typeahead/Typeahead.test.tsx +53 -0
- package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
- package/src/__tests__/pressState.ts +93 -0
- package/src/inputFontFloor.test.ts +106 -0
- package/src/utils/interactionOverlay.stylex.ts +20 -0
|
@@ -82,6 +82,17 @@ export const docs = {
|
|
|
82
82
|
{name: 'CollapsibleGroup'},
|
|
83
83
|
],
|
|
84
84
|
usage: {
|
|
85
|
+
accessibility: [
|
|
86
|
+
{
|
|
87
|
+
name: 'Trigger label',
|
|
88
|
+
category: 'Color contrast',
|
|
89
|
+
criterion: '1.4.3 Contrast (Minimum)',
|
|
90
|
+
requirement: '4.5:1',
|
|
91
|
+
states: ['Rest', 'Pointer down'],
|
|
92
|
+
description:
|
|
93
|
+
'The trigger text must have at least 4.5:1 contrast with the surface behind it. For Pointer down, measure against the pressed overlay the trigger row paints while it is pressed.',
|
|
94
|
+
},
|
|
95
|
+
],
|
|
85
96
|
description: 'Collapsible hides and reveals content behind a trigger button. Use it in settings panels, FAQ pages, or detail views to keep the page scannable while letting users drill into sections they care about. Wrap multiple collapsibles in CollapsibleGroup for accordion behavior. For custom collapsible components, use the `useCollapsible` hook directly (`astryx hook useCollapsible`).',
|
|
86
97
|
bestPractices: [
|
|
87
98
|
{ guidance: true, description: 'Use hasDividers on CollapsibleGroup for FAQ-style lists: built-in row hairlines with themed border tokens, no hand-rolled borders.' },
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import {describe, it, expect, vi} from 'vitest';
|
|
13
13
|
import {render, screen, within} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
|
+
import {hasPressedArm} from '../__tests__/pressState';
|
|
15
16
|
import {Collapsible} from './Collapsible';
|
|
16
17
|
import {CollapsibleGroup} from './CollapsibleGroup';
|
|
17
18
|
|
|
@@ -615,3 +616,23 @@ describe('Collapsible', () => {
|
|
|
615
616
|
});
|
|
616
617
|
});
|
|
617
618
|
});
|
|
619
|
+
|
|
620
|
+
describe('pressed state', () => {
|
|
621
|
+
it('paints the pressed overlay on the trigger row while it is pressed', () => {
|
|
622
|
+
render(
|
|
623
|
+
<Collapsible trigger="Details">
|
|
624
|
+
<p>Body</p>
|
|
625
|
+
</Collapsible>,
|
|
626
|
+
);
|
|
627
|
+
expect(hasPressedArm(screen.getByRole('button'))).toBe(true);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('does not press a disabled trigger', () => {
|
|
631
|
+
render(
|
|
632
|
+
<Collapsible trigger="Details" isDisabled>
|
|
633
|
+
<p>Body</p>
|
|
634
|
+
</Collapsible>,
|
|
635
|
+
);
|
|
636
|
+
expect(hasPressedArm(screen.getByRole('button'))).toBe(false);
|
|
637
|
+
});
|
|
638
|
+
});
|
|
@@ -48,6 +48,7 @@ import {mergeProps} from '../utils';
|
|
|
48
48
|
import type {BaseProps} from '../BaseProps';
|
|
49
49
|
import {themeProps} from '../utils/themeProps';
|
|
50
50
|
import {focusOutlineProps} from '../utils/focusOutline.stylex';
|
|
51
|
+
import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
|
|
51
52
|
|
|
52
53
|
const styles = stylex.create({
|
|
53
54
|
root: {
|
|
@@ -411,6 +412,10 @@ export function Collapsible({
|
|
|
411
412
|
focusOutlineProps.focusVisible(
|
|
412
413
|
styles.trigger,
|
|
413
414
|
density != null && triggerDensity[density],
|
|
415
|
+
// The system's pressed overlay on the disclosure row. The trigger
|
|
416
|
+
// has no hover surface of its own, so this is the one background
|
|
417
|
+
// it paints, and only while it is pressed.
|
|
418
|
+
!isDisabled && interactionOverlayStyles.pressedBackgroundColor,
|
|
414
419
|
isDisabled && styles.triggerDisabled,
|
|
415
420
|
),
|
|
416
421
|
)}>
|
|
@@ -73,9 +73,14 @@ const styles = stylex.create({
|
|
|
73
73
|
backgroundColor: 'transparent',
|
|
74
74
|
color: colorVars['--color-text-primary'],
|
|
75
75
|
fontFamily: typographyVars['--font-family-body'],
|
|
76
|
+
// The 16px floor is iOS-only: iOS Safari zooms the page when a focused
|
|
77
|
+
// control sits under 16px, and only iOS WebKit implements
|
|
78
|
+
// -webkit-touch-callout to key the coarse-pointer floor to it.
|
|
76
79
|
fontSize: {
|
|
77
80
|
default: typeScaleVars['--text-body-size'],
|
|
78
|
-
'@media (pointer: coarse)':
|
|
81
|
+
'@media (pointer: coarse)': {
|
|
82
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
83
|
+
},
|
|
79
84
|
},
|
|
80
85
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
81
86
|
padding: 0,
|
|
@@ -65,9 +65,14 @@ const styles = stylex.create({
|
|
|
65
65
|
paddingBlock: spacingVars['--spacing-2'],
|
|
66
66
|
paddingInline: spacingVars['--spacing-3'],
|
|
67
67
|
fontFamily: typographyVars['--font-family-body'],
|
|
68
|
+
// The 16px floor is iOS-only: iOS Safari zooms the page when a focused
|
|
69
|
+
// control sits under 16px, and only iOS WebKit implements
|
|
70
|
+
// -webkit-touch-callout to key the coarse-pointer floor to it.
|
|
68
71
|
fontSize: {
|
|
69
72
|
default: typeScaleVars['--text-label-size'],
|
|
70
|
-
'@media (pointer: coarse)':
|
|
73
|
+
'@media (pointer: coarse)': {
|
|
74
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-label-size']})`,
|
|
75
|
+
},
|
|
71
76
|
},
|
|
72
77
|
lineHeight: typeScaleVars['--text-label-leading'],
|
|
73
78
|
color: colorVars['--color-text-primary'],
|
|
@@ -99,9 +99,14 @@ const styles = stylex.create({
|
|
|
99
99
|
borderStyle: 'none',
|
|
100
100
|
padding: 0,
|
|
101
101
|
fontFamily: typographyVars['--font-family-body'],
|
|
102
|
+
// The 16px floor is iOS-only: iOS Safari zooms the page when a focused
|
|
103
|
+
// control sits under 16px, and only iOS WebKit implements
|
|
104
|
+
// -webkit-touch-callout to key the coarse-pointer floor to it.
|
|
102
105
|
fontSize: {
|
|
103
106
|
default: typeScaleVars['--text-body-size'],
|
|
104
|
-
'@media (pointer: coarse)':
|
|
107
|
+
'@media (pointer: coarse)': {
|
|
108
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
109
|
+
},
|
|
105
110
|
},
|
|
106
111
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
107
112
|
color: colorVars['--color-text-primary'],
|
|
@@ -100,10 +100,14 @@ const styles = stylex.create({
|
|
|
100
100
|
borderStyle: 'none',
|
|
101
101
|
padding: 0,
|
|
102
102
|
fontFamily: typographyVars['--font-family-body'],
|
|
103
|
-
// Below 16px iOS zooms the page when the field takes focus.
|
|
103
|
+
// Below 16px iOS zooms the page when the field takes focus. Only iOS
|
|
104
|
+
// WebKit implements -webkit-touch-callout, so the coarse-pointer floor is
|
|
105
|
+
// keyed to iOS alone.
|
|
104
106
|
fontSize: {
|
|
105
107
|
default: typeScaleVars['--text-body-size'],
|
|
106
|
-
'@media (pointer: coarse)':
|
|
108
|
+
'@media (pointer: coarse)': {
|
|
109
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
110
|
+
},
|
|
107
111
|
},
|
|
108
112
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
109
113
|
color: colorVars['--color-text-primary'],
|
|
@@ -112,11 +116,20 @@ const styles = stylex.create({
|
|
|
112
116
|
// A date control's intrinsic height comes from its inner edit fields, not
|
|
113
117
|
// from `line-height`, so it renders ~2px taller than a text input and its
|
|
114
118
|
// value sits off the shared baseline inside the same flex row. One line
|
|
115
|
-
// box is exactly what the text field occupies.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
// box is exactly what the text field occupies. The calc fallback mirrors
|
|
120
|
+
// the iOS-only floor above; browsers with `1lh` track it for free.
|
|
121
|
+
height: {
|
|
122
|
+
default: stylex.firstThatWorks(
|
|
123
|
+
'1lh',
|
|
124
|
+
`calc(${typeScaleVars['--text-body-size']} * ${typeScaleVars['--text-body-leading']})`,
|
|
125
|
+
),
|
|
126
|
+
'@media (pointer: coarse)': {
|
|
127
|
+
'@supports (-webkit-touch-callout: none)': stylex.firstThatWorks(
|
|
128
|
+
'1lh',
|
|
129
|
+
`calc(max(1rem, ${typeScaleVars['--text-body-size']}) * ${typeScaleVars['--text-body-leading']})`,
|
|
130
|
+
),
|
|
131
|
+
},
|
|
132
|
+
},
|
|
120
133
|
// iOS gives date controls their own button-like chrome, with inner
|
|
121
134
|
// spacing and a centred value that no reset of ours can reach.
|
|
122
135
|
WebkitAppearance: 'none',
|
|
@@ -187,9 +200,12 @@ const styles = stylex.create({
|
|
|
187
200
|
// covers — one line of that leading fills its height exactly, which puts
|
|
188
201
|
// the glyphs on the input's own baseline.
|
|
189
202
|
display: 'block',
|
|
203
|
+
// ...with the input's own iOS floor, so the two baselines stay shared.
|
|
190
204
|
fontSize: {
|
|
191
205
|
default: typeScaleVars['--text-body-size'],
|
|
192
|
-
'@media (pointer: coarse)':
|
|
206
|
+
'@media (pointer: coarse)': {
|
|
207
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
208
|
+
},
|
|
193
209
|
},
|
|
194
210
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
195
211
|
// A tap has to reach the control underneath — that is what raises the
|
|
@@ -194,10 +194,14 @@ const styles = stylex.create({
|
|
|
194
194
|
padding: 0,
|
|
195
195
|
fontFamily: typographyVars['--font-family-body'],
|
|
196
196
|
// Below 16px iOS zooms the page on focus. The field is focusable even
|
|
197
|
-
// though it is not typable, so it needs the same floor DateInput has
|
|
197
|
+
// though it is not typable, so it needs the same floor DateInput has —
|
|
198
|
+
// keyed to iOS alone, since only iOS WebKit implements
|
|
199
|
+
// -webkit-touch-callout.
|
|
198
200
|
fontSize: {
|
|
199
201
|
default: typeScaleVars['--text-body-size'],
|
|
200
|
-
'@media (pointer: coarse)':
|
|
202
|
+
'@media (pointer: coarse)': {
|
|
203
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
204
|
+
},
|
|
201
205
|
},
|
|
202
206
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
203
207
|
color: colorVars['--color-text-primary'],
|
|
@@ -92,9 +92,14 @@ const styles = stylex.create({
|
|
|
92
92
|
borderStyle: 'none',
|
|
93
93
|
padding: 0,
|
|
94
94
|
fontFamily: typographyVars['--font-family-body'],
|
|
95
|
+
// The 16px floor is iOS-only: iOS Safari zooms the page when a focused
|
|
96
|
+
// control sits under 16px, and only iOS WebKit implements
|
|
97
|
+
// -webkit-touch-callout to key the coarse-pointer floor to it.
|
|
95
98
|
fontSize: {
|
|
96
99
|
default: typeScaleVars['--text-body-size'],
|
|
97
|
-
'@media (pointer: coarse)':
|
|
100
|
+
'@media (pointer: coarse)': {
|
|
101
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
102
|
+
},
|
|
98
103
|
},
|
|
99
104
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
100
105
|
color: colorVars['--color-text-primary'],
|
|
@@ -173,9 +173,14 @@ const styles = stylex.create({
|
|
|
173
173
|
borderStyle: 'none',
|
|
174
174
|
padding: 0,
|
|
175
175
|
fontFamily: typographyVars['--font-family-body'],
|
|
176
|
+
// The 16px floor is iOS-only: iOS Safari zooms the page when a focused
|
|
177
|
+
// control sits under 16px, and only iOS WebKit implements
|
|
178
|
+
// -webkit-touch-callout to key the coarse-pointer floor to it.
|
|
176
179
|
fontSize: {
|
|
177
180
|
default: typeScaleVars['--text-body-size'],
|
|
178
|
-
'@media (pointer: coarse)':
|
|
181
|
+
'@media (pointer: coarse)': {
|
|
182
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
183
|
+
},
|
|
179
184
|
},
|
|
180
185
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
181
186
|
color: colorVars['--color-text-primary'],
|
|
@@ -182,9 +182,14 @@ const styles = stylex.create({
|
|
|
182
182
|
borderStyle: 'none',
|
|
183
183
|
padding: 0,
|
|
184
184
|
fontFamily: typographyVars['--font-family-body'],
|
|
185
|
+
// The 16px floor is iOS-only: iOS Safari zooms the page when a focused
|
|
186
|
+
// control sits under 16px, and only iOS WebKit implements
|
|
187
|
+
// -webkit-touch-callout to key the coarse-pointer floor to it.
|
|
185
188
|
fontSize: {
|
|
186
189
|
default: typeScaleVars['--text-body-size'],
|
|
187
|
-
'@media (pointer: coarse)':
|
|
190
|
+
'@media (pointer: coarse)': {
|
|
191
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
192
|
+
},
|
|
188
193
|
},
|
|
189
194
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
190
195
|
color: colorVars['--color-text-primary'],
|
|
@@ -42,21 +42,34 @@ export const nativePickerSegmentStyles = stylex.create({
|
|
|
42
42
|
borderStyle: 'none',
|
|
43
43
|
padding: 0,
|
|
44
44
|
fontFamily: typographyVars['--font-family-body'],
|
|
45
|
-
// Below 16px iOS zooms the page when the field takes focus.
|
|
45
|
+
// Below 16px iOS zooms the page when the field takes focus. Only iOS
|
|
46
|
+
// WebKit implements -webkit-touch-callout, so the coarse-pointer floor is
|
|
47
|
+
// keyed to iOS alone.
|
|
46
48
|
fontSize: {
|
|
47
49
|
default: typeScaleVars['--text-body-size'],
|
|
48
|
-
'@media (pointer: coarse)':
|
|
50
|
+
'@media (pointer: coarse)': {
|
|
51
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
52
|
+
},
|
|
49
53
|
},
|
|
50
54
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
51
55
|
color: colorVars['--color-text-primary'],
|
|
52
56
|
backgroundColor: 'transparent',
|
|
53
57
|
outline: 'none',
|
|
54
58
|
// Native date/time controls size from their internal edit fields. Pin the
|
|
55
|
-
// box to one text line so both segments stay aligned.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
// box to one text line so both segments stay aligned. The calc fallback
|
|
60
|
+
// mirrors the iOS-only floor above; browsers with `1lh` track it for free.
|
|
61
|
+
height: {
|
|
62
|
+
default: stylex.firstThatWorks(
|
|
63
|
+
'1lh',
|
|
64
|
+
`calc(${typeScaleVars['--text-body-size']} * ${typeScaleVars['--text-body-leading']})`,
|
|
65
|
+
),
|
|
66
|
+
'@media (pointer: coarse)': {
|
|
67
|
+
'@supports (-webkit-touch-callout: none)': stylex.firstThatWorks(
|
|
68
|
+
'1lh',
|
|
69
|
+
`calc(max(1rem, ${typeScaleVars['--text-body-size']}) * ${typeScaleVars['--text-body-leading']})`,
|
|
70
|
+
),
|
|
71
|
+
},
|
|
72
|
+
},
|
|
60
73
|
WebkitAppearance: 'none',
|
|
61
74
|
appearance: 'none',
|
|
62
75
|
// DateTimeInput renders its own calendar/clock affordances.
|
|
@@ -101,9 +114,12 @@ export const nativePickerSegmentStyles = stylex.create({
|
|
|
101
114
|
insetInlineEnd: 0,
|
|
102
115
|
insetBlock: 0,
|
|
103
116
|
display: 'block',
|
|
117
|
+
// The same iOS-only floor as the input it covers, shared baselines.
|
|
104
118
|
fontSize: {
|
|
105
119
|
default: typeScaleVars['--text-body-size'],
|
|
106
|
-
'@media (pointer: coarse)':
|
|
120
|
+
'@media (pointer: coarse)': {
|
|
121
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
122
|
+
},
|
|
107
123
|
},
|
|
108
124
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
109
125
|
pointerEvents: 'none',
|
|
@@ -119,10 +119,13 @@ const styles = stylex.create({
|
|
|
119
119
|
color: colorVars['--color-text-primary'],
|
|
120
120
|
fontFamily: typographyVars['--font-family-body'],
|
|
121
121
|
// Matches the option rows below it, so the query reads as the first line of
|
|
122
|
-
// the list. The
|
|
122
|
+
// the list. The floor keeps iOS from zooming on focus — keyed to iOS
|
|
123
|
+
// alone, since only iOS WebKit implements -webkit-touch-callout.
|
|
123
124
|
fontSize: {
|
|
124
125
|
default: typeScaleVars['--text-label-size'],
|
|
125
|
-
'@media (pointer: coarse)':
|
|
126
|
+
'@media (pointer: coarse)': {
|
|
127
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-label-size']})`,
|
|
128
|
+
},
|
|
126
129
|
},
|
|
127
130
|
lineHeight: typeScaleVars['--text-label-leading'],
|
|
128
131
|
// The field draws the focus ring (see `field`), so the bare input must not
|
|
@@ -256,7 +256,7 @@ export const CORE_STATUS_MESSAGE_BINDING_STATES = [
|
|
|
256
256
|
minValue: 0,
|
|
257
257
|
maxValue: 100,
|
|
258
258
|
},
|
|
259
|
-
focusSelector: '[
|
|
259
|
+
focusSelector: '[tabindex="0"]',
|
|
260
260
|
storyId: 'a11y-status-message-pattern--progress-mark-focused-update',
|
|
261
261
|
},
|
|
262
262
|
] as const satisfies ReadonlyArray<CoreStatusMessageBindingDefinition>;
|
|
@@ -231,9 +231,14 @@ const styles = stylex.create({
|
|
|
231
231
|
},
|
|
232
232
|
placeholderText: {
|
|
233
233
|
fontFamily: typographyVars['--font-family-body'],
|
|
234
|
+
// The 16px floor is iOS-only: iOS Safari zooms the page when a focused
|
|
235
|
+
// control sits under 16px, and only iOS WebKit implements
|
|
236
|
+
// -webkit-touch-callout to key the coarse-pointer floor to it.
|
|
234
237
|
fontSize: {
|
|
235
238
|
default: typeScaleVars['--text-body-size'],
|
|
236
|
-
'@media (pointer: coarse)':
|
|
239
|
+
'@media (pointer: coarse)': {
|
|
240
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
241
|
+
},
|
|
237
242
|
},
|
|
238
243
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
239
244
|
color: colorVars['--color-text-secondary'],
|
|
@@ -242,9 +247,13 @@ const styles = stylex.create({
|
|
|
242
247
|
},
|
|
243
248
|
fileNameText: {
|
|
244
249
|
fontFamily: typographyVars['--font-family-body'],
|
|
250
|
+
// ...and the filename beside it keeps the same floor, so both lines the
|
|
251
|
+
// control shows stay the same size.
|
|
245
252
|
fontSize: {
|
|
246
253
|
default: typeScaleVars['--text-body-size'],
|
|
247
|
-
'@media (pointer: coarse)':
|
|
254
|
+
'@media (pointer: coarse)': {
|
|
255
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
256
|
+
},
|
|
248
257
|
},
|
|
249
258
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
250
259
|
color: colorVars['--color-text-primary'],
|
package/src/Kbd/Kbd.doc.mjs
CHANGED
|
@@ -27,7 +27,7 @@ export const docs = {
|
|
|
27
27
|
name: 'keys',
|
|
28
28
|
type: 'string',
|
|
29
29
|
description:
|
|
30
|
-
'Keyboard shortcut string. Use "+" to separate keys. Special keys: mod (Cmd on Mac), ctrl, alt, shift, enter, backspace, escape, tab, up, down, left, right.',
|
|
30
|
+
'Keyboard shortcut string. Use "+" to separate keys. Special keys: mod (Cmd on Mac), ctrl, alt, shift, enter, backspace, escape, tab, up, down, left, right, plus. Aliases: esc for escape and return for enter.',
|
|
31
31
|
required: true,
|
|
32
32
|
},
|
|
33
33
|
{
|
|
@@ -72,7 +72,7 @@ export const docsZh = {
|
|
|
72
72
|
name: 'keys',
|
|
73
73
|
type: 'string',
|
|
74
74
|
description:
|
|
75
|
-
'键盘快捷键字符串。使用 "+" 分隔各按键。特殊按键:mod(Mac 上为 Cmd)、ctrl、alt、shift、enter、backspace、escape、tab、up、down、left、right。',
|
|
75
|
+
'键盘快捷键字符串。使用 "+" 分隔各按键。特殊按键:mod(Mac 上为 Cmd)、ctrl、alt、shift、enter、backspace、escape、tab、up、down、left、right、plus。别名:esc 等同于 escape,return 等同于 enter。',
|
|
76
76
|
required: true,
|
|
77
77
|
},
|
|
78
78
|
{
|
|
@@ -122,7 +122,7 @@ export const docsDense = {
|
|
|
122
122
|
],
|
|
123
123
|
},
|
|
124
124
|
propDescriptions: {
|
|
125
|
-
keys: 'Shortcut string. "+" separates keys. Special: mod (Cmd on Mac), ctrl, alt, shift, enter, backspace, escape, tab, up, down, left, right.',
|
|
125
|
+
keys: 'Shortcut string. "+" separates keys. Special: mod (Cmd on Mac), ctrl, alt, shift, enter, backspace, escape, tab, up, down, left, right, plus. Aliases: esc for escape, return for enter.',
|
|
126
126
|
xstyle: 'StyleX styles for layout customization. Must be stylex.create() value.',
|
|
127
127
|
className: 'CSS class for root element. Prefer xstyle; className for non-StyleX integration.',
|
|
128
128
|
style: 'Inline styles for root element. Prefer xstyle; inline styles bypass StyleX optimization.',
|
package/src/Kbd/Kbd.test.tsx
CHANGED
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import {render, screen} from '@testing-library/react';
|
|
13
|
-
import {describe, it, expect, afterEach} from 'vitest';
|
|
13
|
+
import {describe, it, expect, afterEach, vi} from 'vitest';
|
|
14
14
|
import {Kbd} from './Kbd';
|
|
15
15
|
|
|
16
16
|
describe('Kbd', () => {
|
|
17
17
|
const originalPlatform = navigator.platform;
|
|
18
18
|
|
|
19
19
|
afterEach(() => {
|
|
20
|
+
vi.restoreAllMocks();
|
|
20
21
|
// Restore platform after any test that spoofs it \u2014 ensures no
|
|
21
22
|
// test pollution even if an assertion fails mid-test.
|
|
22
23
|
Object.defineProperty(navigator, 'platform', {
|
|
@@ -72,6 +73,61 @@ describe('Kbd', () => {
|
|
|
72
73
|
expect(screen.getByText('Esc')).toBeInTheDocument();
|
|
73
74
|
});
|
|
74
75
|
|
|
76
|
+
it.each([
|
|
77
|
+
{alias: 'esc', display: 'Esc', label: 'Escape'},
|
|
78
|
+
{alias: 'return', display: '\u21B5', label: 'Enter'},
|
|
79
|
+
])('normalizes the $alias key alias', ({alias, display, label}) => {
|
|
80
|
+
render(<Kbd keys={alias} />);
|
|
81
|
+
expect(screen.getByText(display)).toBeInTheDocument();
|
|
82
|
+
expect(screen.getByRole('img')).toHaveAttribute('aria-label', label);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it.each([
|
|
86
|
+
{alias: 'esc', canonical: 'escape'},
|
|
87
|
+
{alias: 'return', canonical: 'enter'},
|
|
88
|
+
])('$alias renders identically to $canonical', ({alias, canonical}) => {
|
|
89
|
+
const {container, rerender} = render(<Kbd keys={canonical} />);
|
|
90
|
+
const text = container.textContent;
|
|
91
|
+
const label = screen.getByRole('img').getAttribute('aria-label');
|
|
92
|
+
rerender(<Kbd keys={alias} />);
|
|
93
|
+
expect(container.textContent).toBe(text);
|
|
94
|
+
expect(screen.getByRole('img').getAttribute('aria-label')).toBe(label);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it.each([
|
|
98
|
+
{keys: 'ctrl + ESC', display: 'Esc', label: 'Control + Escape'},
|
|
99
|
+
{keys: 'shift + RETURN', display: '\u21B5', label: 'Shift + Enter'},
|
|
100
|
+
])('normalizes aliases inside the $keys combo', ({keys, display, label}) => {
|
|
101
|
+
render(<Kbd keys={keys} />);
|
|
102
|
+
expect(screen.getByText(display)).toBeInTheDocument();
|
|
103
|
+
expect(screen.getByRole('img')).toHaveAttribute('aria-label', label);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('keeps child keys unique after alias normalization', () => {
|
|
107
|
+
const consoleError = vi
|
|
108
|
+
.spyOn(console, 'error')
|
|
109
|
+
.mockImplementation(() => {});
|
|
110
|
+
const {container} = render(
|
|
111
|
+
<Kbd keys="escape+esc+ESC+enter+return+return" />,
|
|
112
|
+
);
|
|
113
|
+
expect(consoleError).not.toHaveBeenCalled();
|
|
114
|
+
expect(
|
|
115
|
+
Array.from(container.querySelectorAll('kbd'), key => key.textContent),
|
|
116
|
+
).toEqual(['Esc', 'Esc', 'Esc', '↵', '↵', '↵']);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it.each(['constructor', '__proto__'])(
|
|
120
|
+
'renders the unknown %s key without consulting object prototypes',
|
|
121
|
+
key => {
|
|
122
|
+
render(<Kbd keys={key} />);
|
|
123
|
+
expect(screen.getByText(key.toUpperCase())).toBeInTheDocument();
|
|
124
|
+
expect(screen.getByRole('img')).toHaveAttribute(
|
|
125
|
+
'aria-label',
|
|
126
|
+
key.toUpperCase(),
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
75
131
|
it('exposes a spoken accessible name and hides the glyphs (obs-1)', () => {
|
|
76
132
|
render(<Kbd keys="mod+shift+k" />);
|
|
77
133
|
// The wrapper carries a screen-reader name built from spoken key labels
|
package/src/Kbd/Kbd.tsx
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file Kbd.tsx
|
|
7
|
-
* @input Uses React, StyleX, theme tokens
|
|
8
|
-
* @output Exports Kbd component and KbdProps
|
|
7
|
+
* @input Uses React, StyleX, theme tokens, shortcut key names and aliases
|
|
8
|
+
* @output Exports Kbd component and KbdProps with canonical shortcut labels
|
|
9
9
|
* @position Core implementation; renders styled keyboard shortcut indicators
|
|
10
10
|
*
|
|
11
11
|
* SYNC: When modified, update:
|
|
@@ -61,20 +61,25 @@ const styles = stylex.create({
|
|
|
61
61
|
* Note: `mod` is not in this map — it resolves dynamically via platform
|
|
62
62
|
* detection inside the component.
|
|
63
63
|
*/
|
|
64
|
-
const KEY_DISPLAY
|
|
65
|
-
ctrl
|
|
66
|
-
alt
|
|
67
|
-
shift
|
|
68
|
-
enter
|
|
69
|
-
backspace
|
|
70
|
-
escape
|
|
71
|
-
tab
|
|
72
|
-
up
|
|
73
|
-
down
|
|
74
|
-
left
|
|
75
|
-
right
|
|
76
|
-
plus
|
|
77
|
-
|
|
64
|
+
const KEY_DISPLAY = new Map<string, string>([
|
|
65
|
+
['ctrl', '\u2303'], // ⌃
|
|
66
|
+
['alt', '\u2325'], // ⌥
|
|
67
|
+
['shift', '\u21E7'], // ⇧
|
|
68
|
+
['enter', '\u21B5'], // ↵
|
|
69
|
+
['backspace', '\u232B'], // ⌫
|
|
70
|
+
['escape', 'Esc'],
|
|
71
|
+
['tab', '\u21E5'], // ⇥
|
|
72
|
+
['up', '\u2191'],
|
|
73
|
+
['down', '\u2193'],
|
|
74
|
+
['left', '\u2190'],
|
|
75
|
+
['right', '\u2192'],
|
|
76
|
+
['plus', '+'],
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const KBD_KEY_ALIASES = new Map<string, string>([
|
|
80
|
+
['esc', 'escape'],
|
|
81
|
+
['return', 'enter'],
|
|
82
|
+
]);
|
|
78
83
|
|
|
79
84
|
/**
|
|
80
85
|
* Resolves a key name to its display string. Handles the platform-aware
|
|
@@ -85,7 +90,7 @@ function getKeyDisplay(key: string, isMac: boolean): string {
|
|
|
85
90
|
if (key === 'mod') {
|
|
86
91
|
return isMac ? '\u2318' : 'Ctrl';
|
|
87
92
|
}
|
|
88
|
-
return KEY_DISPLAY
|
|
93
|
+
return KEY_DISPLAY.get(key) ?? key.toUpperCase();
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
/**
|
|
@@ -93,26 +98,26 @@ function getKeyDisplay(key: string, isMac: boolean): string {
|
|
|
93
98
|
* (⌘, ⇧, ↵, …) that assistive tech cannot announce meaningfully, so the
|
|
94
99
|
* accessible name for the shortcut is built from these words instead.
|
|
95
100
|
*/
|
|
96
|
-
const KEY_LABEL
|
|
97
|
-
ctrl
|
|
98
|
-
alt
|
|
99
|
-
shift
|
|
100
|
-
enter
|
|
101
|
-
backspace
|
|
102
|
-
escape
|
|
103
|
-
tab
|
|
104
|
-
up
|
|
105
|
-
down
|
|
106
|
-
left
|
|
107
|
-
right
|
|
108
|
-
plus
|
|
109
|
-
|
|
101
|
+
const KEY_LABEL = new Map<string, string>([
|
|
102
|
+
['ctrl', 'Control'],
|
|
103
|
+
['alt', 'Alt'],
|
|
104
|
+
['shift', 'Shift'],
|
|
105
|
+
['enter', 'Enter'],
|
|
106
|
+
['backspace', 'Backspace'],
|
|
107
|
+
['escape', 'Escape'],
|
|
108
|
+
['tab', 'Tab'],
|
|
109
|
+
['up', 'Up arrow'],
|
|
110
|
+
['down', 'Down arrow'],
|
|
111
|
+
['left', 'Left arrow'],
|
|
112
|
+
['right', 'Right arrow'],
|
|
113
|
+
['plus', 'Plus'],
|
|
114
|
+
]);
|
|
110
115
|
|
|
111
116
|
function getKeyLabel(key: string, isMac: boolean): string {
|
|
112
117
|
if (key === 'mod') {
|
|
113
118
|
return isMac ? 'Command' : 'Control';
|
|
114
119
|
}
|
|
115
|
-
return KEY_LABEL
|
|
120
|
+
return KEY_LABEL.get(key) ?? key.toUpperCase();
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
function subscribeToPlatformChanges(): () => void {
|
|
@@ -127,7 +132,9 @@ export interface KbdProps extends BaseProps<HTMLSpanElement> {
|
|
|
127
132
|
ref?: React.Ref<HTMLSpanElement>;
|
|
128
133
|
/**
|
|
129
134
|
* Keyboard shortcut string. Use "+" to separate keys.
|
|
130
|
-
* Special keys: mod (Cmd on Mac), ctrl, alt, shift, enter, backspace, escape
|
|
135
|
+
* Special keys: mod (Cmd on Mac), ctrl, alt, shift, enter, backspace, escape,
|
|
136
|
+
* tab, up, down, left, and right.
|
|
137
|
+
* Aliases: "esc" for "escape" and "return" for "enter".
|
|
131
138
|
* Use "plus" to render a literal "+" key (e.g. "shift+plus").
|
|
132
139
|
*
|
|
133
140
|
* @example
|
|
@@ -163,11 +170,24 @@ export function Kbd({keys, ref, xstyle, className, style, ...rest}: KbdProps) {
|
|
|
163
170
|
getServerPlatformSnapshot,
|
|
164
171
|
);
|
|
165
172
|
|
|
166
|
-
const
|
|
173
|
+
const keyOccurrences = new Map<string, number>();
|
|
174
|
+
const parts = keys
|
|
175
|
+
.split('+')
|
|
176
|
+
.map(key => key.trim().toLowerCase())
|
|
177
|
+
.map(sourceKey => {
|
|
178
|
+
const occurrence = keyOccurrences.get(sourceKey) ?? 0;
|
|
179
|
+
keyOccurrences.set(sourceKey, occurrence + 1);
|
|
180
|
+
return {
|
|
181
|
+
key: KBD_KEY_ALIASES.get(sourceKey) ?? sourceKey,
|
|
182
|
+
reactKey: `${sourceKey}:${occurrence}`,
|
|
183
|
+
};
|
|
184
|
+
});
|
|
167
185
|
|
|
168
186
|
// Screen-reader name: the joined spoken labels (e.g. "Command + K"), since
|
|
169
187
|
// the visual glyphs below are announced meaninglessly by assistive tech.
|
|
170
|
-
const accessibleName = parts
|
|
188
|
+
const accessibleName = parts
|
|
189
|
+
.map(({key}) => getKeyLabel(key, isMac))
|
|
190
|
+
.join(' + ');
|
|
171
191
|
|
|
172
192
|
return (
|
|
173
193
|
<span
|
|
@@ -181,8 +201,8 @@ export function Kbd({keys, ref, xstyle, className, style, ...rest}: KbdProps) {
|
|
|
181
201
|
className,
|
|
182
202
|
style,
|
|
183
203
|
)}>
|
|
184
|
-
{parts.map(key => (
|
|
185
|
-
<kbd key={
|
|
204
|
+
{parts.map(({key, reactKey}) => (
|
|
205
|
+
<kbd key={reactKey} aria-hidden="true" {...stylex.props(styles.kbd)}>
|
|
186
206
|
{getKeyDisplay(key, isMac)}
|
|
187
207
|
</kbd>
|
|
188
208
|
))}
|
|
@@ -8,6 +8,23 @@ export const docs = {
|
|
|
8
8
|
displayName: 'Layout Footer',
|
|
9
9
|
isHiddenFromOverview: true,
|
|
10
10
|
description: 'Bottom bar for action bars, pagination, and status bars.',
|
|
11
|
+
playground: {
|
|
12
|
+
defaults: {
|
|
13
|
+
children: 'Footer content: status bar or actions',
|
|
14
|
+
hasDivider: true,
|
|
15
|
+
},
|
|
16
|
+
wrapper: {
|
|
17
|
+
component: 'Layout',
|
|
18
|
+
slotProp: 'footer',
|
|
19
|
+
props: {
|
|
20
|
+
content: {
|
|
21
|
+
__element: 'LayoutContent',
|
|
22
|
+
props: {},
|
|
23
|
+
children: 'Main content area',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
11
28
|
props: [
|
|
12
29
|
{
|
|
13
30
|
name: 'children',
|