@astryxdesign/core 0.5.2-canary.c9c8564 → 0.5.2-canary.e03511b
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/BottomSheet/BottomSheet.d.ts +3 -1
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +4 -3
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +2 -2
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetEdgeTint.js +7 -10
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +1 -1
- package/dist/Button/Button.d.ts +2 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +11 -4
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +2 -2
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +34 -17
- package/dist/ContextMenu/ContextMenu.d.ts +14 -3
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +149 -26
- package/dist/ContextMenu/index.d.ts +1 -0
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts +24 -5
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +340 -26
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +6 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +43 -14
- package/dist/DropdownMenu/MenuBottomSheet.d.ts +20 -0
- package/dist/DropdownMenu/MenuBottomSheet.d.ts.map +1 -0
- package/dist/DropdownMenu/MenuBottomSheet.js +36 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts +10 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.js +111 -0
- package/dist/DropdownMenu/index.d.ts +2 -1
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/menuWidth.d.ts +14 -0
- package/dist/DropdownMenu/menuWidth.d.ts.map +1 -0
- package/dist/DropdownMenu/menuWidth.js +35 -0
- package/dist/DropdownMenu/useMenuOverflow.d.ts +10 -0
- package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -0
- package/dist/DropdownMenu/useMenuOverflow.js +60 -0
- package/dist/MoreMenu/MoreMenu.d.ts +8 -2
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/MoreMenu/MoreMenu.js +2 -0
- package/dist/MultiSelector/MultiSelector.d.ts +11 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +97 -68
- package/dist/MultiSelector/index.d.ts +1 -1
- package/dist/MultiSelector/index.d.ts.map +1 -1
- package/dist/Popover/Popover.d.ts +10 -6
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +141 -16
- package/dist/Popover/usePopover.d.ts +4 -0
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +52 -5
- package/dist/Selector/Selector.d.ts +10 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +106 -80
- package/dist/Selector/SelectorBottomSheet.d.ts +21 -0
- package/dist/Selector/SelectorBottomSheet.d.ts.map +1 -0
- package/dist/Selector/SelectorBottomSheet.js +83 -0
- package/dist/Selector/index.d.ts +1 -1
- package/dist/Selector/index.d.ts.map +1 -1
- package/dist/Selector/selectorPresentation.stylex.d.ts +16 -0
- package/dist/Selector/selectorPresentation.stylex.d.ts.map +1 -0
- package/dist/Selector/selectorPresentation.stylex.js +21 -0
- package/dist/Selector/useSelectorPresentation.d.ts +32 -0
- package/dist/Selector/useSelectorPresentation.d.ts.map +1 -0
- package/dist/Selector/useSelectorPresentation.js +90 -0
- package/dist/Toast/useToastGesture.d.ts.map +1 -1
- package/dist/Toast/useToastGesture.js +46 -9
- package/dist/astryx.css +19 -1
- package/dist/hooks/useAdaptivePresentation.d.ts +5 -0
- package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -0
- package/dist/hooks/useAdaptivePresentation.js +19 -0
- package/dist/hooks/useFocusReturnVisibility.d.ts +7 -0
- package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -0
- package/dist/hooks/useFocusReturnVisibility.js +35 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +12 -0
- package/locales/en.json +20 -0
- package/locales/pseudo.json +15 -0
- package/package.json +2 -2
- package/src/BottomSheet/BottomSheet.doc.mjs +37 -0
- package/src/BottomSheet/BottomSheet.spec.md +177 -0
- package/src/BottomSheet/BottomSheet.test.tsx +29 -0
- package/src/BottomSheet/BottomSheet.tsx +7 -4
- package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +4 -6
- package/src/BottomSheet/BottomSheetEdgeTint.tsx +7 -10
- package/src/BottomSheet/BottomSheetSwitcher.tsx +1 -2
- package/src/Button/Button.doc.mjs +56 -0
- package/src/Button/Button.test.tsx +10 -0
- package/src/Button/Button.tsx +13 -5
- package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
- package/src/Carousel/Carousel.test.tsx +83 -0
- package/src/Carousel/Carousel.tsx +8 -2
- package/src/Chat/ChatToolCalls.test.tsx +44 -0
- package/src/Chat/ChatToolCalls.tsx +36 -15
- package/src/CheckboxList/CheckboxList.doc.mjs +172 -31
- package/src/CheckboxList/CheckboxList.spec.md +214 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +72 -0
- package/src/CommandPalette/CommandPalette.spec.md +223 -0
- package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
- package/src/ComplexSelector/ComplexSelector.spec.md +189 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +61 -1
- package/src/ContextMenu/ContextMenu.spec.md +206 -0
- package/src/ContextMenu/ContextMenu.test.tsx +131 -1
- package/src/ContextMenu/ContextMenu.tsx +199 -32
- package/src/ContextMenu/index.ts +1 -0
- package/src/Divider/Divider.doc.mjs +24 -0
- package/src/Divider/Divider.spec.md +150 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +305 -40
- package/src/DropdownMenu/DropdownMenu.spec.md +245 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +518 -7
- package/src/DropdownMenu/DropdownMenu.tsx +459 -28
- package/src/DropdownMenu/DropdownMenuContext.tsx +1 -1
- package/src/DropdownMenu/DropdownMenuItem.tsx +2 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +4 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +124 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +72 -17
- package/src/DropdownMenu/MenuBottomSheet.tsx +46 -0
- package/src/DropdownMenu/MenuBottomSheetActionList.tsx +141 -0
- package/src/DropdownMenu/index.ts +2 -0
- package/src/DropdownMenu/menuWidth.ts +51 -0
- package/src/DropdownMenu/useMenuOverflow.ts +77 -0
- package/src/FieldStatus/FieldStatus.doc.mjs +21 -0
- package/src/FieldStatus/FieldStatus.spec.md +212 -0
- package/src/Grid/Grid.doc.mjs +19 -0
- package/src/Grid/Grid.spec.md +161 -0
- package/src/Icon/Icon.doc.mjs +196 -48
- package/src/Icon/Icon.spec.md +191 -0
- package/src/IconButton/IconButton.doc.mjs +38 -0
- package/src/Kbd/Kbd.doc.mjs +18 -0
- package/src/Kbd/Kbd.spec.md +141 -0
- package/src/Layout/Layout.doc.mjs +32 -0
- package/src/Layout/Layout.spec.md +199 -0
- package/src/Lightbox/Lightbox.doc.mjs +149 -33
- package/src/Lightbox/Lightbox.spec.md +197 -0
- package/src/Markdown/Markdown.doc.mjs +60 -0
- package/src/Markdown/Markdown.spec.md +200 -0
- package/src/MobileNav/MobileNav.doc.mjs +140 -32
- package/src/MobileNav/MobileNav.spec.md +208 -0
- package/src/MoreMenu/MoreMenu.doc.mjs +59 -0
- package/src/MoreMenu/MoreMenu.spec.md +221 -0
- package/src/MoreMenu/MoreMenu.test.tsx +47 -0
- package/src/MoreMenu/MoreMenu.tsx +13 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +128 -0
- package/src/MultiSelector/MultiSelector.spec.md +266 -0
- package/src/MultiSelector/MultiSelector.test.tsx +127 -0
- package/src/MultiSelector/MultiSelector.tsx +121 -73
- package/src/MultiSelector/index.ts +1 -0
- package/src/NavIcon/NavIcon.doc.mjs +17 -0
- package/src/NavIcon/NavIcon.spec.md +133 -0
- package/src/NavMenu/NavHeadingMenu.spec.md +205 -0
- package/src/NavMenu/NavMenu.doc.mjs +37 -0
- package/src/Outline/Outline.doc.mjs +35 -0
- package/src/Outline/Outline.spec.md +161 -0
- package/src/OverflowList/OverflowList.doc.mjs +24 -0
- package/src/OverflowList/OverflowList.spec.md +156 -0
- package/src/Pagination/Pagination.doc.mjs +74 -0
- package/src/Pagination/Pagination.spec.md +225 -0
- package/src/Popover/Popover.doc.mjs +11 -11
- package/src/Popover/Popover.test.tsx +397 -2
- package/src/Popover/Popover.tsx +228 -21
- package/src/Popover/usePopover.tsx +92 -15
- package/src/ProgressBar/ProgressBar.doc.mjs +198 -38
- package/src/ProgressBar/ProgressBar.spec.md +260 -0
- package/src/Resizable/Resizable.doc.mjs +59 -16
- package/src/Resizable/Resizable.spec.md +194 -0
- package/src/Section/Section.doc.mjs +17 -0
- package/src/Section/Section.spec.md +138 -0
- package/src/SegmentedControl/SegmentedControl.doc.mjs +83 -0
- package/src/SegmentedControl/SegmentedControl.spec.md +151 -0
- package/src/Selector/Selector.doc.mjs +12 -0
- package/src/Selector/Selector.spec.md +202 -0
- package/src/Selector/Selector.test.tsx +135 -0
- package/src/Selector/Selector.tsx +141 -82
- package/src/Selector/SelectorBottomSheet.tsx +97 -0
- package/src/Selector/index.ts +1 -0
- package/src/Selector/selectorPresentation.stylex.ts +20 -0
- package/src/Selector/useSelectorPresentation.ts +147 -0
- package/src/Skeleton/Skeleton.doc.mjs +12 -0
- package/src/Skeleton/Skeleton.spec.md +126 -0
- package/src/Slider/Slider.doc.mjs +184 -30
- package/src/Slider/Slider.spec.md +223 -0
- package/src/Stack/Stack.doc.mjs +22 -0
- package/src/Stack/Stack.spec.md +154 -0
- package/src/StatusDot/StatusDot.doc.mjs +17 -0
- package/src/StatusDot/StatusDot.spec.md +137 -0
- package/src/Switch/Switch.doc.mjs +40 -0
- package/src/Switch/Switch.spec.md +174 -0
- package/src/Table/Table.doc.mjs +102 -24
- package/src/Table/Table.spec.md +283 -0
- package/src/Text/Text.doc.mjs +190 -43
- package/src/Text/Text.spec.md +205 -0
- package/src/TextArea/TextArea.doc.mjs +255 -45
- package/src/TextArea/TextArea.spec.md +204 -0
- package/src/Toast/ToastViewport.test.tsx +118 -4
- package/src/Toast/useToastGesture.ts +61 -8
- package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
- package/src/Toolbar/Toolbar.doc.mjs +19 -0
- package/src/Toolbar/Toolbar.spec.md +173 -0
- package/src/Tooltip/Tooltip.doc.mjs +17 -0
- package/src/Tooltip/Tooltip.spec.md +141 -0
- package/src/TreeList/TreeList.doc.mjs +58 -0
- package/src/TreeList/TreeList.spec.md +220 -0
- package/src/Typeahead/Typeahead.doc.mjs +83 -1
- package/src/Typeahead/Typeahead.spec.md +250 -0
- package/src/hooks/useAdaptivePresentation.ts +35 -0
- package/src/hooks/useFocusReturnVisibility.ts +45 -0
- package/src/hooks/useFocusTrap.test.tsx +40 -0
- package/src/hooks/useFocusTrap.ts +13 -0
- package/src/theme/themingTargets.test.ts +91 -25
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').ComponentAnatomyElement[]} */
|
|
4
|
+
const anatomy = [
|
|
5
|
+
{
|
|
6
|
+
name: 'Sheet panel',
|
|
7
|
+
required: true,
|
|
8
|
+
description:
|
|
9
|
+
'Painted surface that rises from the bottom edge and contains the sheet.',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: 'Content area',
|
|
13
|
+
required: true,
|
|
14
|
+
description:
|
|
15
|
+
'Scrollable area that presents the caller-provided sheet content.',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Handle',
|
|
19
|
+
required: true,
|
|
20
|
+
description:
|
|
21
|
+
'Decorative grab affordance and drag region at the top of the panel.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'Scrim',
|
|
25
|
+
required: false,
|
|
26
|
+
description:
|
|
27
|
+
'Backdrop that dims and blocks the page in a scrim-backed presentation.',
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
|
|
2
31
|
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
|
|
3
32
|
|
|
4
33
|
export const docs = {
|
|
@@ -71,6 +100,12 @@ export const docs = {
|
|
|
71
100
|
description:
|
|
72
101
|
'For a standalone sheet, called when it requests an open-state change. Automatic calls follow purpose: info dismisses on Escape, scrim click, or swipe; form dismisses on Escape only; required never dismisses implicitly. Omit inside BottomSheetSwitcher.',
|
|
73
102
|
},
|
|
103
|
+
{
|
|
104
|
+
name: 'finalFocusRef',
|
|
105
|
+
type: 'RefObject<HTMLElement | null>',
|
|
106
|
+
description:
|
|
107
|
+
'Optional explicit focus-return target for a standalone sheet. Use when the opener can remount or the active element is not a reliable trigger, such as an adaptive presentation switch. Omit inside BottomSheetSwitcher.',
|
|
108
|
+
},
|
|
74
109
|
{
|
|
75
110
|
name: 'purpose',
|
|
76
111
|
type: "'required' | 'form' | 'info'",
|
|
@@ -120,6 +155,7 @@ export const docs = {
|
|
|
120
155
|
},
|
|
121
156
|
],
|
|
122
157
|
usage: {
|
|
158
|
+
anatomy,
|
|
123
159
|
description:
|
|
124
160
|
'A mobile touch surface for filters, actions, forms, and detail views that should rise from the bottom of the viewport; use BottomSheetSwitcher for multi-step flows.',
|
|
125
161
|
bestPractices: [
|
|
@@ -271,6 +307,7 @@ export const docsDense = {
|
|
|
271
307
|
description:
|
|
272
308
|
'mobile touch sheet rising from the bottom edge (native <dialog>): grab handle, opt-in transform-based drag-to-resize snap points (snapPoints: viewport fraction, percent or px length), scrolling area resizes to the snapped visible height on release (a peek stop, a quarter of the sheet or less, keeps the full height and slides instead), Dialog-aligned dismissal purpose (info/form/required), purpose-gated swipe-to-dismiss, fully-expanded Tall visual-viewport mobile-keyboard handling, named height scale, modal (default) or non-modal (hasScrim={false}) presentation',
|
|
273
309
|
usage: {
|
|
310
|
+
anatomy,
|
|
274
311
|
description:
|
|
275
312
|
'Mobile touch surface for filters, actions, forms, and detail views that should rise from the bottom of the viewport; use BottomSheetSwitcher for multi-step flows.',
|
|
276
313
|
bestPractices: [
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 1
|
|
3
|
+
template_version: 3
|
|
4
|
+
kind: component
|
|
5
|
+
id: component:BottomSheet
|
|
6
|
+
authority: draft
|
|
7
|
+
archive_reason: null
|
|
8
|
+
superseded_by: null
|
|
9
|
+
approved_by: null
|
|
10
|
+
approved_at: null
|
|
11
|
+
owners: [cixzhang]
|
|
12
|
+
review_triggers: [theming]
|
|
13
|
+
verified_by:
|
|
14
|
+
[
|
|
15
|
+
packages/core/src/BottomSheet/BottomSheet.test.tsx,
|
|
16
|
+
packages/core/src/BottomSheet/BottomSheetPanel.test.tsx,
|
|
17
|
+
scripts/check-knowledge.mjs,
|
|
18
|
+
]
|
|
19
|
+
families: [family:overlay-dismissal]
|
|
20
|
+
design_specs: []
|
|
21
|
+
architecture:
|
|
22
|
+
[architecture:component-theming-surface, architecture:layer-runtime]
|
|
23
|
+
contributing: []
|
|
24
|
+
system_specs: []
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# BottomSheet component contract
|
|
28
|
+
|
|
29
|
+
## Intent
|
|
30
|
+
|
|
31
|
+
BottomSheet presents caller-provided content in a panel that rises from the
|
|
32
|
+
bottom edge. This draft records current consumer anatomy and theming reachability
|
|
33
|
+
without changing runtime behavior, styling, targets, or public API.
|
|
34
|
+
|
|
35
|
+
## Compatibility and migration
|
|
36
|
+
|
|
37
|
+
- Released default preserved: `yes`
|
|
38
|
+
- Compatibility class: additive documentation only; runtime, DOM, styling,
|
|
39
|
+
targets, and public API remain unchanged
|
|
40
|
+
- Controlled/uncontrolled behavior: unchanged
|
|
41
|
+
- Migration decision: none
|
|
42
|
+
|
|
43
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
44
|
+
|
|
45
|
+
## Ownership boundary
|
|
46
|
+
|
|
47
|
+
**Owns**
|
|
48
|
+
|
|
49
|
+
- The visual Sheet panel and its current `bottom-sheet` target.
|
|
50
|
+
- The scrolling Content area and decorative Handle inside the panel.
|
|
51
|
+
- Standalone sheet presentation, including its optional native-dialog Scrim.
|
|
52
|
+
|
|
53
|
+
**Does not own / non-goals**
|
|
54
|
+
|
|
55
|
+
- Caller-provided content rendered inside the Content area.
|
|
56
|
+
- A separate target for Content area, Handle, or Scrim; none exists on current
|
|
57
|
+
`main`.
|
|
58
|
+
- A shared switcher dialog or scrim — owned by BottomSheetSwitcher when the sheet
|
|
59
|
+
participates in that host.
|
|
60
|
+
- Shared layer hosting or dismissal policy.
|
|
61
|
+
|
|
62
|
+
## Public concepts
|
|
63
|
+
|
|
64
|
+
No new public concept is introduced. Consumer props and usage remain documented
|
|
65
|
+
in `BottomSheet.doc.mjs`.
|
|
66
|
+
|
|
67
|
+
## Behavioral and layout contract
|
|
68
|
+
|
|
69
|
+
| ID | Candidate invariant | Basis | Draft review state |
|
|
70
|
+
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------- | -------------------------------------------------- |
|
|
71
|
+
| FR1 | The current presented sheet contains one Sheet panel, one scrolling Content area, and one decorative Handle; a Scrim is present only in scrim-backed presentation. | Current source, docs, and tests | Verified current behavior; no new behavior decided |
|
|
72
|
+
| FR2 | The Sheet panel carries `bottom-sheet`; Content area, Handle, and Scrim carry no BottomSheet public target. | Current source, docs, and tests | Verified current behavior; no target change |
|
|
73
|
+
|
|
74
|
+
### Allowed variation
|
|
75
|
+
|
|
76
|
+
- Caller-provided children may vary without becoming BottomSheet-owned anatomy.
|
|
77
|
+
- Height, snap points, motion phase, and standalone versus switcher hosting may
|
|
78
|
+
vary without changing the four-part anatomy recorded here.
|
|
79
|
+
|
|
80
|
+
### Representative states
|
|
81
|
+
|
|
82
|
+
- A standalone modal sheet renders Sheet panel, Content area, Handle, and Scrim.
|
|
83
|
+
- A standalone non-modal sheet renders Sheet panel, Content area, and Handle
|
|
84
|
+
without a Scrim.
|
|
85
|
+
- A switcher-managed sheet renders the same panel anatomy while
|
|
86
|
+
BottomSheetSwitcher owns the shared dialog and optional Scrim.
|
|
87
|
+
|
|
88
|
+
### Transformation and precedence order
|
|
89
|
+
|
|
90
|
+
- No new height, gesture, motion, or styling precedence rule is introduced.
|
|
91
|
+
|
|
92
|
+
### Performance and resources
|
|
93
|
+
|
|
94
|
+
- No new performance or resource rule is introduced.
|
|
95
|
+
|
|
96
|
+
## Accessibility contract
|
|
97
|
+
|
|
98
|
+
This draft does not change or extend BottomSheet's existing dialog naming,
|
|
99
|
+
focus, Handle, keyboard, or dismissal behavior.
|
|
100
|
+
|
|
101
|
+
## Design relationships
|
|
102
|
+
|
|
103
|
+
| Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
|
|
104
|
+
| ---------------- | ------------------------------------------------------------------------------------- | ------------------------------ | -------------- | ------------------ |
|
|
105
|
+
| Sheet panel | Presents the painted bottom-edge surface and owns panel geometry and motion. | Current source and public docs | Prominent | FR1, FR2 |
|
|
106
|
+
| Content area | Provides the scrolling area for caller-provided sheet content. | Current source and tests | Prominent | FR1, FR2 |
|
|
107
|
+
| Handle | Presents the decorative grab affordance and owns the panel's drag interaction region. | Current source and tests | Supporting | FR1, FR2 |
|
|
108
|
+
| Scrim | Dims and blocks the page in a scrim-backed host. | Current source and public docs | Supporting | FR1, FR2 |
|
|
109
|
+
|
|
110
|
+
Content area, Handle, and Scrim are stable visible parts, but no current public
|
|
111
|
+
target reaches them. Their `none` dispositions record factual reachability, not
|
|
112
|
+
a decision that they must remain unthemeable.
|
|
113
|
+
|
|
114
|
+
### Theming anatomy
|
|
115
|
+
|
|
116
|
+
<!-- anatomy-theming:v1 -->
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"Sheet panel": {"target": "bottom-sheet"},
|
|
121
|
+
"Content area": {
|
|
122
|
+
"none": {
|
|
123
|
+
"reason": "reachability-gap: No current public target reaches the scrolling content area."
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"Handle": {
|
|
127
|
+
"none": {
|
|
128
|
+
"reason": "reachability-gap: No current public target reaches the handle bar or pill."
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"Scrim": {
|
|
132
|
+
"none": {
|
|
133
|
+
"reason": "reachability-gap: No BottomSheet public target reaches the native dialog backdrop or switcher-owned scrim."
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Family and system relationships
|
|
140
|
+
|
|
141
|
+
- `architecture:component-theming-surface` owns anatomy qualification, target
|
|
142
|
+
mapping, and factual `none` dispositions.
|
|
143
|
+
- `architecture:layer-runtime` owns the current native-dialog host distinction
|
|
144
|
+
and records that sheets retain local scrim and swipe behavior.
|
|
145
|
+
- `family:overlay-dismissal` owns shared Escape and platform-close ordering and
|
|
146
|
+
records BottomSheet's current local-only adoption gap. This anatomy backfill
|
|
147
|
+
does not migrate that runtime behavior.
|
|
148
|
+
|
|
149
|
+
## Verification map
|
|
150
|
+
|
|
151
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
152
|
+
| ------------------- | ------------------------------------------------------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------- | --------------------------- |
|
|
153
|
+
| FR1 | `BottomSheet.test.tsx` render, content, Handle, and scrim behavior suites | Modal, non-modal, and switcher presentations | Removing a documented part fails existing content, structure, or dismissal assertions. | `audit:BottomSheet/anatomy` |
|
|
154
|
+
| FR2 | Panel target assertion, source inspection, and theming target inventories | Sheet panel, Content area, Handle, and Scrim | Removing the panel target or documenting an unshipped child target fails evidence or inventory. | `audit:BottomSheet/theming` |
|
|
155
|
+
| Layout evidence | `BottomSheetPanel.test.tsx` | Floating Handle and scrolling Content area | Reordering or merging the stable parts fails existing panel structure and style assertions. | `audit:BottomSheet/anatomy` |
|
|
156
|
+
| Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and current target inventory | Missing, extra, prefixed, stale, or multiply assigned mappings fail repository validation. | `audit:BottomSheet/theming` |
|
|
157
|
+
|
|
158
|
+
Existing tests directly assert the Sheet panel target, Content area placement,
|
|
159
|
+
Handle structure, and scrim behavior. Source and public target metadata confirm
|
|
160
|
+
that the other three parts have no public target.
|
|
161
|
+
|
|
162
|
+
## Decision log
|
|
163
|
+
|
|
164
|
+
None. This draft records current facts and introduces no component-local design,
|
|
165
|
+
API, theming, or layer-system decision.
|
|
166
|
+
|
|
167
|
+
## Open questions
|
|
168
|
+
|
|
169
|
+
- **OQ1 — Should Content area, Handle, or Scrim gain a stable public theming
|
|
170
|
+
target?** (`human-api`) Their current lack of reachability is an audit gap, not
|
|
171
|
+
settled intent.
|
|
172
|
+
|
|
173
|
+
## Content boundary
|
|
174
|
+
|
|
175
|
+
This file does not duplicate consumer prop tables, examples, gesture algorithms,
|
|
176
|
+
implementation steps, or shared layer and theming rules. It links to their
|
|
177
|
+
owners.
|
|
@@ -2319,6 +2319,35 @@ describe('BottomSheet', () => {
|
|
|
2319
2319
|
|
|
2320
2320
|
expect(document.activeElement).toBe(opener);
|
|
2321
2321
|
});
|
|
2322
|
+
|
|
2323
|
+
it('prefers an explicit final focus target', () => {
|
|
2324
|
+
const finalFocusRef = createRef<HTMLButtonElement>();
|
|
2325
|
+
function ExplicitFocusHarness() {
|
|
2326
|
+
const [isOpen, setIsOpen] = useState(true);
|
|
2327
|
+
return (
|
|
2328
|
+
<>
|
|
2329
|
+
<button ref={finalFocusRef} type="button">
|
|
2330
|
+
Adaptive trigger
|
|
2331
|
+
</button>
|
|
2332
|
+
<BottomSheet
|
|
2333
|
+
isOpen={isOpen}
|
|
2334
|
+
onOpenChange={setIsOpen}
|
|
2335
|
+
finalFocusRef={finalFocusRef}
|
|
2336
|
+
label="Filters">
|
|
2337
|
+
<button type="button" onClick={() => setIsOpen(false)}>
|
|
2338
|
+
Done
|
|
2339
|
+
</button>
|
|
2340
|
+
</BottomSheet>
|
|
2341
|
+
</>
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
render(<ExplicitFocusHarness />);
|
|
2346
|
+
fireEvent.click(screen.getByRole('button', {name: 'Done'}));
|
|
2347
|
+
finishSheetExit();
|
|
2348
|
+
|
|
2349
|
+
expect(document.activeElement).toBe(finalFocusRef.current);
|
|
2350
|
+
});
|
|
2322
2351
|
});
|
|
2323
2352
|
|
|
2324
2353
|
describe('initial focus', () => {
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
useLayoutEffect,
|
|
33
33
|
useRef,
|
|
34
34
|
useState,
|
|
35
|
+
type RefObject,
|
|
35
36
|
type ReactNode,
|
|
36
37
|
} from 'react';
|
|
37
38
|
import * as stylex from '@stylexjs/stylex';
|
|
@@ -162,6 +163,8 @@ interface StandaloneBottomSheetProps extends BottomSheetSharedProps {
|
|
|
162
163
|
isOpen: boolean;
|
|
163
164
|
onOpenChange: (isOpen: boolean) => void;
|
|
164
165
|
hasScrim?: boolean;
|
|
166
|
+
/** Element that receives focus after the sheet closes. */
|
|
167
|
+
finalFocusRef?: RefObject<HTMLElement | null>;
|
|
165
168
|
sheetId?: never;
|
|
166
169
|
}
|
|
167
170
|
|
|
@@ -221,6 +224,7 @@ function StandaloneBottomSheet({
|
|
|
221
224
|
height = 'capped',
|
|
222
225
|
snapPoints,
|
|
223
226
|
hasScrim = true,
|
|
227
|
+
finalFocusRef,
|
|
224
228
|
purpose = 'info',
|
|
225
229
|
xstyle,
|
|
226
230
|
...props
|
|
@@ -298,10 +302,10 @@ function StandaloneBottomSheet({
|
|
|
298
302
|
dialog.close();
|
|
299
303
|
}
|
|
300
304
|
setIsPresented(false);
|
|
301
|
-
triggerRef.current?.focus();
|
|
305
|
+
(finalFocusRef?.current ?? triggerRef.current)?.focus();
|
|
302
306
|
triggerRef.current = null;
|
|
303
307
|
},
|
|
304
|
-
[isOpen],
|
|
308
|
+
[finalFocusRef, isOpen],
|
|
305
309
|
);
|
|
306
310
|
|
|
307
311
|
useScrollLock(shouldPresent && hasScrim);
|
|
@@ -385,8 +389,7 @@ function StandaloneBottomSheet({
|
|
|
385
389
|
{children}
|
|
386
390
|
</BottomSheetPanel>
|
|
387
391
|
</div>
|
|
388
|
-
|
|
389
|
-
{hasScrim ? null : <BottomSheetEdgeTint />}
|
|
392
|
+
<BottomSheetEdgeTint />
|
|
390
393
|
</dialog>
|
|
391
394
|
);
|
|
392
395
|
}
|
|
@@ -101,15 +101,13 @@ describe('BottomSheetEdgeTint', () => {
|
|
|
101
101
|
expect(tints()).toHaveLength(1);
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
// so a second sampling target there would only fight it.
|
|
106
|
-
it('leaves a modal sheet to its ::backdrop', () => {
|
|
104
|
+
it('gives a modal sheet the same surface-coloured edge tint', () => {
|
|
107
105
|
render(
|
|
108
106
|
<BottomSheet isOpen onOpenChange={() => {}} label="Place details">
|
|
109
107
|
Content
|
|
110
108
|
</BottomSheet>,
|
|
111
109
|
);
|
|
112
|
-
expect(tints()).toHaveLength(
|
|
110
|
+
expect(tints()).toHaveLength(1);
|
|
113
111
|
});
|
|
114
112
|
|
|
115
113
|
it('renders the tint inside the dialog, so it unmounts with the sheet', () => {
|
|
@@ -148,7 +146,7 @@ describe('BottomSheetEdgeTint', () => {
|
|
|
148
146
|
expect(tints()).toHaveLength(1);
|
|
149
147
|
});
|
|
150
148
|
|
|
151
|
-
it('
|
|
149
|
+
it('gives a modal switcher flow exactly one tint', () => {
|
|
152
150
|
render(
|
|
153
151
|
<BottomSheetSwitcher activeSheet="comment" onActiveSheetChange={() => {}}>
|
|
154
152
|
<BottomSheet sheetId="comment" label="Add a comment">
|
|
@@ -156,7 +154,7 @@ describe('BottomSheetEdgeTint', () => {
|
|
|
156
154
|
</BottomSheet>
|
|
157
155
|
</BottomSheetSwitcher>,
|
|
158
156
|
);
|
|
159
|
-
expect(tints()).toHaveLength(
|
|
157
|
+
expect(tints()).toHaveLength(1);
|
|
160
158
|
});
|
|
161
159
|
|
|
162
160
|
it('keeps the tint out of the accessibility tree and out of hit testing', async () => {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @file BottomSheetEdgeTint.tsx
|
|
7
7
|
* @input Uses StyleX and core color tokens
|
|
8
8
|
* @output Exports BottomSheetEdgeTint, an internal decorative element
|
|
9
|
-
* @position Private helper rendered inside
|
|
9
|
+
* @position Private helper rendered inside every BottomSheet dialog
|
|
10
10
|
*
|
|
11
11
|
* iOS 26 Safari dropped `<meta name="theme-color">` and instead derives the
|
|
12
12
|
* colour it paints behind its translucent toolbars by sampling the page: it
|
|
@@ -14,13 +14,10 @@
|
|
|
14
14
|
* `fixed`/`sticky` ancestor, and extends that element's declared
|
|
15
15
|
* `background-color` into the browser chrome.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* fixed
|
|
20
|
-
*
|
|
21
|
-
* candidate by *keeping the colour it already had* — the host page's. The page
|
|
22
|
-
* then shows through behind the address bar while the sheet covers the screen
|
|
23
|
-
* above it.
|
|
17
|
+
* WebKit may otherwise extend either the dialog backdrop or the host page into
|
|
18
|
+
* the browser chrome. Which one wins varies with dialog mode and the page's
|
|
19
|
+
* own fixed or sticky edge content, so relying on the backdrop makes otherwise
|
|
20
|
+
* identical BottomSheets produce different toolbar colours.
|
|
24
21
|
*
|
|
25
22
|
* This element gives the heuristic something unambiguous to sample: fixed,
|
|
26
23
|
* full width, flush with the bottom edge, taller than WebKit's 10px minimum
|
|
@@ -68,8 +65,8 @@ const styles = stylex.create({
|
|
|
68
65
|
});
|
|
69
66
|
|
|
70
67
|
/**
|
|
71
|
-
* Colours the iOS Safari toolbar strip below a
|
|
72
|
-
*
|
|
68
|
+
* Colours the iOS Safari toolbar strip below a sheet. Renders nothing visible;
|
|
69
|
+
* see the file header for why it exists.
|
|
73
70
|
*/
|
|
74
71
|
export function BottomSheetEdgeTint() {
|
|
75
72
|
return (
|
|
@@ -632,8 +632,7 @@ export function BottomSheetSwitcher({
|
|
|
632
632
|
? {role: 'alertdialog'}
|
|
633
633
|
: undefined)}>
|
|
634
634
|
{children}
|
|
635
|
-
|
|
636
|
-
{hasScrim ? null : <BottomSheetEdgeTint />}
|
|
635
|
+
<BottomSheetEdgeTint />
|
|
637
636
|
</dialog>
|
|
638
637
|
</BottomSheetSwitcherContext>
|
|
639
638
|
);
|
|
@@ -23,6 +23,62 @@ export const docs = {
|
|
|
23
23
|
{guidance: false, description: 'Use the destructive variant without a confirmation step for irreversible actions like deleting data.'},
|
|
24
24
|
{guidance: false, description: 'Use a button for navigation. If it only takes the user to another page, use a link instead. Buttons are for actions like saving, deleting, or submitting.'},
|
|
25
25
|
],
|
|
26
|
+
accessibility: [
|
|
27
|
+
{
|
|
28
|
+
name: 'Text label',
|
|
29
|
+
category: 'Color contrast',
|
|
30
|
+
criterion: '1.4.3 Contrast (Minimum)',
|
|
31
|
+
requirement: '4.5:1',
|
|
32
|
+
states: ['Rest', 'Hover', 'Pointer down'],
|
|
33
|
+
description:
|
|
34
|
+
'Button text must have at least 4.5:1 contrast with the button background in every state. For Hover and Pointer down, measure the final background after the overlay is applied.',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'Essential icon or spinner arc',
|
|
38
|
+
category: 'Color contrast',
|
|
39
|
+
criterion: '1.4.11 Non-text Contrast',
|
|
40
|
+
requirement: '3:1',
|
|
41
|
+
states: ['Icon only', 'Loading'],
|
|
42
|
+
description:
|
|
43
|
+
'An icon used instead of text must have at least 3:1 contrast with the button background. The moving spinner arc must also meet 3:1. An icon beside a visible label does not need its own check.',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'Badge text',
|
|
47
|
+
category: 'Color contrast',
|
|
48
|
+
criterion: '1.4.3 Contrast (Minimum)',
|
|
49
|
+
requirement: '4.5:1',
|
|
50
|
+
states: ['Rest', 'Hover', 'Pointer down'],
|
|
51
|
+
description:
|
|
52
|
+
'Badge text inside a button must have at least 4.5:1 contrast with the Badge background. Check all 14 built-in Badge colors in Rest, Hover, and Pointer down on page and surface backgrounds. This covers 336 pairs per mode. Check custom end content separately.',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'Visible control boundary',
|
|
56
|
+
category: 'Color contrast',
|
|
57
|
+
criterion: '1.4.11 Non-text Contrast',
|
|
58
|
+
requirement: '3:1 if needed',
|
|
59
|
+
states: ['Rest'],
|
|
60
|
+
description:
|
|
61
|
+
'The button edge needs 3:1 contrast only when users need it to see the control. A text-only button can rely on its label.',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'Keyboard focus indicator',
|
|
65
|
+
category: 'Color contrast',
|
|
66
|
+
criterion: '1.4.11 Non-text Contrast',
|
|
67
|
+
requirement: '3:1',
|
|
68
|
+
states: ['Focus visible'],
|
|
69
|
+
description:
|
|
70
|
+
'The focus outline needs at least 3:1 contrast with the area around the button. Check every style. Destructive buttons use a red outline.',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'Disabled appearance',
|
|
74
|
+
category: 'Color contrast',
|
|
75
|
+
criterion: '1.4.3 and 1.4.11 exceptions',
|
|
76
|
+
requirement: 'Not required',
|
|
77
|
+
states: ['Disabled'],
|
|
78
|
+
description:
|
|
79
|
+
'Disabled controls do not need to meet these contrast ratios.',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
26
82
|
anatomy: [
|
|
27
83
|
{name: 'Icon', required: false, description: 'A leading icon that reinforces the label, like a trash icon on a Delete button.'},
|
|
28
84
|
{name: 'Label', required: true, description: 'The visible text describing the action. Also used as the accessible name.'},
|
|
@@ -70,6 +70,16 @@ describe('Button', () => {
|
|
|
70
70
|
const button = screen.getByRole('button');
|
|
71
71
|
// Button should be disabled when loading
|
|
72
72
|
expect(button).toBeDisabled();
|
|
73
|
+
expect(button.className).toContain('styles.inactive');
|
|
74
|
+
expect(button.className).not.toContain('styles.disabled');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('keeps the dimmed treatment for explicitly disabled buttons', () => {
|
|
78
|
+
render(<Button label="Submit" isDisabled />);
|
|
79
|
+
const button = screen.getByRole('button');
|
|
80
|
+
expect(button).toBeDisabled();
|
|
81
|
+
expect(button.className).toContain('styles.inactive');
|
|
82
|
+
expect(button.className).toContain('styles.disabled');
|
|
73
83
|
});
|
|
74
84
|
|
|
75
85
|
it('sets aria-busy synchronously while clickAction is pending', async () => {
|
package/src/Button/Button.tsx
CHANGED
|
@@ -99,18 +99,20 @@ const styles = stylex.create({
|
|
|
99
99
|
pressable: {
|
|
100
100
|
transform: {
|
|
101
101
|
default: 'scale(1)',
|
|
102
|
-
':active': 'scale(0.98)',
|
|
102
|
+
':active:where(:not(:disabled,[aria-disabled="true"]))': 'scale(0.98)',
|
|
103
103
|
},
|
|
104
104
|
},
|
|
105
|
-
|
|
105
|
+
inactive: {
|
|
106
106
|
cursor: 'default',
|
|
107
|
-
opacity: 0.5,
|
|
108
107
|
backgroundImage: 'none',
|
|
109
108
|
transform: {
|
|
110
109
|
default: 'none',
|
|
111
110
|
':active': 'none',
|
|
112
111
|
},
|
|
113
112
|
},
|
|
113
|
+
disabled: {
|
|
114
|
+
opacity: 0.5,
|
|
115
|
+
},
|
|
114
116
|
ariaDisabled: {
|
|
115
117
|
// The variants' hover treatment already steps aside for
|
|
116
118
|
// `[aria-disabled]`; `:active` still matches a press on an aria-disabled
|
|
@@ -270,7 +272,8 @@ export interface ButtonProps extends BaseProps<HTMLButtonElement> {
|
|
|
270
272
|
*/
|
|
271
273
|
isDisabled?: boolean;
|
|
272
274
|
/**
|
|
273
|
-
* Whether the button is in a loading state.
|
|
275
|
+
* Whether the button is in a loading state. Loading prevents interaction
|
|
276
|
+
* without dimming the spinner; explicit disabled states remain dimmed.
|
|
274
277
|
* @default false
|
|
275
278
|
*/
|
|
276
279
|
isLoading?: boolean;
|
|
@@ -573,6 +576,10 @@ export function Button({
|
|
|
573
576
|
// not disabled, so clicks keep landing and can interrupt the in-flight action.
|
|
574
577
|
const buttonDisabled =
|
|
575
578
|
isDisabled || groupDisabled || (isLoadingState && !isInterruptible);
|
|
579
|
+
// A loading button remains non-interactive, but its spinner communicates an
|
|
580
|
+
// active state and must retain contrast. Only explicitly disabled controls
|
|
581
|
+
// receive the visually dimmed treatment.
|
|
582
|
+
const visuallyDisabled = isDisabled || groupDisabled;
|
|
576
583
|
// isIconOnly prop is the source of truth for icon-only rendering.
|
|
577
584
|
// When false (default), label is always rendered as visible text.
|
|
578
585
|
|
|
@@ -639,7 +646,8 @@ export function Button({
|
|
|
639
646
|
sizeStyles[size],
|
|
640
647
|
isIconOnly && styles.iconOnly,
|
|
641
648
|
interactionOverlayStyles.backgroundImage,
|
|
642
|
-
buttonDisabled && styles.
|
|
649
|
+
buttonDisabled && styles.inactive,
|
|
650
|
+
visuallyDisabled && styles.disabled,
|
|
643
651
|
useAriaDisabled && styles.ariaDisabled,
|
|
644
652
|
renderAsLink && styles.link,
|
|
645
653
|
!buttonGroup && styles.pressable,
|
|
@@ -39,6 +39,53 @@ export const docs = {
|
|
|
39
39
|
usage: {
|
|
40
40
|
description:
|
|
41
41
|
'ButtonGroup joins related actions into a single connected control. Use it when multiple buttons represent related choices or operations that belong together visually, like copy/cut/paste, or undo/redo.',
|
|
42
|
+
accessibility: [
|
|
43
|
+
{
|
|
44
|
+
name: 'Text label',
|
|
45
|
+
category: 'Color contrast',
|
|
46
|
+
criterion: '1.4.3 Contrast (Minimum)',
|
|
47
|
+
requirement: '4.5:1',
|
|
48
|
+
states: ['Rest', 'Hover', 'Pointer down'],
|
|
49
|
+
description:
|
|
50
|
+
'Text in each button must have at least 4.5:1 contrast with its background in every state. For Hover and Pointer down, measure the final background after the overlay is applied.',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'Essential icon or spinner arc',
|
|
54
|
+
category: 'Color contrast',
|
|
55
|
+
criterion: '1.4.11 Non-text Contrast',
|
|
56
|
+
requirement: '3:1',
|
|
57
|
+
states: ['Icon only', 'Loading'],
|
|
58
|
+
description:
|
|
59
|
+
'An icon used instead of text must have at least 3:1 contrast with the button background. The moving spinner arc must also meet 3:1. An icon beside a visible label does not need its own check.',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'Visible control boundary',
|
|
63
|
+
category: 'Color contrast',
|
|
64
|
+
criterion: '1.4.11 Non-text Contrast',
|
|
65
|
+
requirement: '3:1 if needed',
|
|
66
|
+
states: ['Rest'],
|
|
67
|
+
description:
|
|
68
|
+
'Some groups need a divider or edge to show each button. That divider or edge must have at least 3:1 contrast.',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'Keyboard focus indicator',
|
|
72
|
+
category: 'Color contrast',
|
|
73
|
+
criterion: '1.4.11 Non-text Contrast',
|
|
74
|
+
requirement: '3:1',
|
|
75
|
+
states: ['Focus visible'],
|
|
76
|
+
description:
|
|
77
|
+
'The focus outline must have at least 3:1 contrast with the area around it.',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'Disabled appearance',
|
|
81
|
+
category: 'Color contrast',
|
|
82
|
+
criterion: '1.4.3 and 1.4.11 exceptions',
|
|
83
|
+
requirement: 'Not required',
|
|
84
|
+
states: ['Disabled'],
|
|
85
|
+
description:
|
|
86
|
+
'Disabled controls do not need to meet these contrast ratios.',
|
|
87
|
+
},
|
|
88
|
+
],
|
|
42
89
|
bestPractices: [
|
|
43
90
|
{guidance: true, description: 'Group buttons that perform related actions on the same object, like copy, cut, paste on selected text.'},
|
|
44
91
|
{guidance: true, description: 'Use the same variant for all buttons in a group so they look like a single connected unit.'},
|