@astryxdesign/core 0.4.3 → 0.4.4-canary.2022d94
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 +60 -0
- package/README.md +79 -45
- package/dist/Banner/Banner.d.ts +3 -0
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +13 -3
- package/dist/BottomSheet/BottomSheet.d.ts +66 -0
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheet.js +392 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts +60 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetPanel.js +398 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +61 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetSwitcher.js +410 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts +21 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.js +13 -0
- package/dist/BottomSheet/index.d.ts +11 -0
- package/dist/BottomSheet/index.d.ts.map +1 -0
- package/dist/BottomSheet/index.js +12 -0
- package/dist/BottomSheet/snapOffsets.d.ts +72 -0
- package/dist/BottomSheet/snapOffsets.d.ts.map +1 -0
- package/dist/BottomSheet/snapOffsets.js +130 -0
- package/dist/BottomSheet/useMobileKeyboard.d.ts +35 -0
- package/dist/BottomSheet/useMobileKeyboard.d.ts.map +1 -0
- package/dist/BottomSheet/useMobileKeyboard.js +373 -0
- package/dist/BottomSheet/useSheetGestures.d.ts +180 -0
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -0
- package/dist/BottomSheet/useSheetGestures.js +989 -0
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +3 -4
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +12 -6
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +4 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +9 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +31 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +2 -1
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +9 -3
- package/dist/MobileNav/MobileNav.d.ts +12 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +102 -30
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +21 -7
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +9 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +14 -3
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +5 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +8 -1
- package/dist/Tokenizer/Tokenizer.js +11 -5
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +4 -1
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +5 -3
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +10 -1
- package/dist/astryx.css +21 -0
- package/dist/hooks/index.d.ts +8 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +8 -1
- package/dist/hooks/useFocusTrap.d.ts +0 -11
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +1 -11
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
- package/dist/hooks/useInputStatusIcon.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/defineTheme.d.ts +22 -4
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/expandColorScale.d.ts +17 -2
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +63 -33
- package/dist/utils/ime.d.ts +5 -0
- package/dist/utils/ime.d.ts.map +1 -0
- package/dist/utils/ime.js +63 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/locales/en.json +48 -0
- package/locales/pseudo.json +36 -0
- package/package.json +9 -8
- package/src/Banner/Banner.doc.mjs +3 -3
- package/src/Banner/Banner.test.tsx +40 -0
- package/src/Banner/Banner.tsx +27 -2
- package/src/BottomSheet/BottomSheet.doc.mjs +266 -0
- package/src/BottomSheet/BottomSheet.test.tsx +2114 -0
- package/src/BottomSheet/BottomSheet.tsx +545 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +240 -0
- package/src/BottomSheet/BottomSheetPanel.tsx +547 -0
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +219 -0
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +850 -0
- package/src/BottomSheet/BottomSheetSwitcher.tsx +628 -0
- package/src/BottomSheet/BottomSheetSwitcherContext.ts +51 -0
- package/src/BottomSheet/index.ts +15 -0
- package/src/BottomSheet/snapOffsets.test.ts +187 -0
- package/src/BottomSheet/snapOffsets.ts +159 -0
- package/src/BottomSheet/useMobileKeyboard.ts +499 -0
- package/src/BottomSheet/useSheetGestures.test.ts +1059 -0
- package/src/BottomSheet/useSheetGestures.ts +1459 -0
- package/src/Chat/ChatComposerInput.tsx +3 -4
- package/src/ComplexSelector/ComplexSelector.tsx +12 -5
- package/src/ContextMenu/ContextMenu.tsx +4 -2
- package/src/DateInput/DateInput.test.tsx +21 -0
- package/src/DateInput/DateInput.tsx +9 -1
- package/src/DateTimeInput/DateTimeInput.doc.mjs +4 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +137 -0
- package/src/DateTimeInput/DateTimeInput.tsx +39 -2
- package/src/Dialog/Dialog.tsx +2 -1
- package/src/FileInput/FileInput.test.tsx +53 -9
- package/src/Lightbox/Lightbox.test.tsx +49 -28
- package/src/Lightbox/Lightbox.tsx +7 -3
- package/src/MobileNav/MobileNav.tsx +128 -37
- package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +426 -0
- package/src/MobileNav/MobileNavCloseTiming.test.ts +58 -0
- package/src/MobileNav/MobileNavCloseVisibility.test.tsx +172 -0
- package/src/MultiSelector/MultiSelector.test.tsx +195 -51
- package/src/MultiSelector/MultiSelector.tsx +22 -8
- package/src/NumberInput/NumberInput.test.tsx +18 -0
- package/src/NumberInput/NumberInput.tsx +9 -1
- package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
- package/src/Selector/Selector.test.tsx +85 -26
- package/src/Selector/Selector.tsx +13 -4
- package/src/Switch/Switch.test.tsx +16 -0
- package/src/Switch/Switch.tsx +7 -0
- package/src/TimeInput/TimeInput.test.tsx +23 -0
- package/src/TimeInput/TimeInput.tsx +8 -0
- package/src/Tokenizer/Tokenizer.test.tsx +50 -32
- package/src/Tokenizer/Tokenizer.tsx +5 -5
- package/src/Tooltip/useTooltip.tsx +4 -1
- package/src/Typeahead/BaseTypeahead.tsx +10 -3
- package/src/Typeahead/Typeahead.test.tsx +116 -7
- package/src/Typeahead/Typeahead.tsx +10 -1
- package/src/__tests__/stubMatchMedia.test.ts +79 -0
- package/src/__tests__/stubMatchMedia.ts +95 -0
- package/src/hooks/index.ts +9 -5
- package/src/hooks/useFocusTrap.ts +1 -14
- package/src/hooks/useInputStatusIcon.test.tsx +19 -0
- package/src/hooks/useInputStatusIcon.tsx +7 -0
- package/src/index.ts +1 -0
- package/src/theme/defineTheme.ts +22 -4
- package/src/theme/expandColorScale.test.ts +138 -0
- package/src/theme/expandColorScale.ts +89 -44
- package/src/utils/ime.test.ts +32 -0
- package/src/utils/ime.ts +69 -0
- package/src/utils/index.ts +2 -0
- package/dist/astryx.umd.js +0 -147
- package/dist/astryx.umd.js.map +0 -7
|
@@ -523,4 +523,44 @@ describe('Banner', () => {
|
|
|
523
523
|
expect(header.children[1].children).toHaveLength(1);
|
|
524
524
|
});
|
|
525
525
|
});
|
|
526
|
+
|
|
527
|
+
// jsdom does no flex layout, so these read the declarations that produce the
|
|
528
|
+
// wrap. The rendered result is verified in Chromium at 320/375/480/768.
|
|
529
|
+
describe('narrow-viewport wrapping', () => {
|
|
530
|
+
const renderBanner = (endContent?: React.ReactNode) => {
|
|
531
|
+
const {container} = render(
|
|
532
|
+
<Banner
|
|
533
|
+
status="warning"
|
|
534
|
+
title="A compute node is required"
|
|
535
|
+
endContent={endContent}
|
|
536
|
+
/>,
|
|
537
|
+
);
|
|
538
|
+
const header = container.firstElementChild!.firstElementChild!;
|
|
539
|
+
return {
|
|
540
|
+
header,
|
|
541
|
+
textColumn: screen.getByText('A compute node is required')
|
|
542
|
+
.parentElement!,
|
|
543
|
+
};
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
it('lets the header wrap so the end area can take its own row', () => {
|
|
547
|
+
const {header} = renderBanner(<button type="button">Retry</button>);
|
|
548
|
+
expect(getComputedStyle(header).flexWrap).toBe('wrap');
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it('gives the text column a wrap threshold when endContent is present', () => {
|
|
552
|
+
const {textColumn} = renderBanner(<button type="button">Retry</button>);
|
|
553
|
+
expect(getComputedStyle(textColumn).flexBasis).toBe('8rem');
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it('leaves the text column free to shrink when there is no endContent', () => {
|
|
557
|
+
const {textColumn} = renderBanner();
|
|
558
|
+
expect(getComputedStyle(textColumn).flexBasis).not.toBe('8rem');
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('leaves it free for an endContent that renders nothing', () => {
|
|
562
|
+
const {textColumn} = renderBanner(false);
|
|
563
|
+
expect(getComputedStyle(textColumn).flexBasis).not.toBe('8rem');
|
|
564
|
+
});
|
|
565
|
+
});
|
|
526
566
|
});
|
package/src/Banner/Banner.tsx
CHANGED
|
@@ -116,6 +116,9 @@ export interface BannerProps extends BaseProps<HTMLDivElement> {
|
|
|
116
116
|
* Action button rendered in the header area (end-aligned).
|
|
117
117
|
* Typically an Button with a secondary or ghost variant.
|
|
118
118
|
*
|
|
119
|
+
* When the header is too narrow to hold the actions and a readable text
|
|
120
|
+
* column, the whole end area wraps to its own row below the text.
|
|
121
|
+
*
|
|
119
122
|
* @example
|
|
120
123
|
* ```
|
|
121
124
|
* endContent={<Button label="Retry" variant="ghost" onClick={handleRetry} />}
|
|
@@ -205,7 +208,11 @@ const styles = stylex.create({
|
|
|
205
208
|
header: {
|
|
206
209
|
display: 'flex',
|
|
207
210
|
alignItems: 'flex-start',
|
|
208
|
-
|
|
211
|
+
flexWrap: 'wrap',
|
|
212
|
+
columnGap: spacingVars['--spacing-2'],
|
|
213
|
+
// The end area carries a -4px block margin (see endArea), so the wrapped
|
|
214
|
+
// row reads as one step of spacing rather than two.
|
|
215
|
+
rowGap: spacingVars['--spacing-3'],
|
|
209
216
|
paddingBlock: spacingVars['--spacing-3'],
|
|
210
217
|
paddingInline: spacingVars['--spacing-4'],
|
|
211
218
|
},
|
|
@@ -231,6 +238,15 @@ const styles = stylex.create({
|
|
|
231
238
|
flex: 1,
|
|
232
239
|
minWidth: 0,
|
|
233
240
|
},
|
|
241
|
+
// The wrap threshold, applied only when there is `endContent` to wrap. Flex
|
|
242
|
+
// breaks a line when the items no longer fit at their base size, so this
|
|
243
|
+
// basis is what moves the end area to its own row instead of letting it hold
|
|
244
|
+
// the header and squeeze the title down to one word per line. In rem so it
|
|
245
|
+
// tracks the user's font size. The dismiss and expand controls alone are
|
|
246
|
+
// narrow enough never to need it.
|
|
247
|
+
headerContentWithEndContent: {
|
|
248
|
+
flexBasis: '8rem',
|
|
249
|
+
},
|
|
234
250
|
title: {
|
|
235
251
|
margin: 0,
|
|
236
252
|
fontFamily: 'inherit',
|
|
@@ -260,8 +276,13 @@ const styles = stylex.create({
|
|
|
260
276
|
endArea: {
|
|
261
277
|
display: 'flex',
|
|
262
278
|
alignItems: 'center',
|
|
279
|
+
flexWrap: 'wrap',
|
|
280
|
+
justifyContent: 'flex-end',
|
|
263
281
|
gap: spacingVars['--spacing-2'],
|
|
264
282
|
flexShrink: 0,
|
|
283
|
+
// Bounded so a group of actions wider than the row wraps within itself
|
|
284
|
+
// rather than pushing the banner past the viewport (WCAG 1.4.10).
|
|
285
|
+
maxWidth: '100%',
|
|
265
286
|
marginInlineStart: 'auto',
|
|
266
287
|
marginBlock: `calc(-1 * (${spacingVars['--spacing-3']} - ${spacingVars['--spacing-2']}))`,
|
|
267
288
|
},
|
|
@@ -537,7 +558,11 @@ export function Banner({
|
|
|
537
558
|
/>
|
|
538
559
|
) : null}
|
|
539
560
|
</div>
|
|
540
|
-
<div
|
|
561
|
+
<div
|
|
562
|
+
{...stylex.props(
|
|
563
|
+
styles.headerContent,
|
|
564
|
+
isRenderable(endContent) && styles.headerContentWithEndContent,
|
|
565
|
+
)}>
|
|
541
566
|
<div {...stylex.props(styles.title)}>{title}</div>
|
|
542
567
|
{isRenderable(description) && (
|
|
543
568
|
<div {...stylex.props(styles.description)}>{description}</div>
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
|
|
3
|
+
|
|
4
|
+
export const docs = {
|
|
5
|
+
name: 'BottomSheet',
|
|
6
|
+
displayName: 'Bottom Sheet',
|
|
7
|
+
group: 'BottomSheet',
|
|
8
|
+
category: 'Overlay',
|
|
9
|
+
keywords: [
|
|
10
|
+
'bottom sheet',
|
|
11
|
+
'sheet',
|
|
12
|
+
'mobile',
|
|
13
|
+
'touch',
|
|
14
|
+
'drag',
|
|
15
|
+
'swipe',
|
|
16
|
+
'dismiss',
|
|
17
|
+
'grab handle',
|
|
18
|
+
'dialog',
|
|
19
|
+
'overlay',
|
|
20
|
+
'modal',
|
|
21
|
+
'form',
|
|
22
|
+
'mobile keyboard',
|
|
23
|
+
'visual viewport',
|
|
24
|
+
],
|
|
25
|
+
playground: {
|
|
26
|
+
overlay: true,
|
|
27
|
+
defaults: {
|
|
28
|
+
isOpen: false,
|
|
29
|
+
label: 'Filters',
|
|
30
|
+
height: 'hug',
|
|
31
|
+
children: {
|
|
32
|
+
__element: 'Section',
|
|
33
|
+
props: {padding: 4},
|
|
34
|
+
children: {
|
|
35
|
+
__element: 'VStack',
|
|
36
|
+
props: {gap: 2},
|
|
37
|
+
children: [
|
|
38
|
+
{
|
|
39
|
+
__element: 'Heading',
|
|
40
|
+
props: {level: 3},
|
|
41
|
+
children: 'Filters',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
__element: 'Text',
|
|
45
|
+
props: {type: 'body'},
|
|
46
|
+
children: 'Adjust the properties below, then open the preview.',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
theming: {
|
|
54
|
+
targets: [{className: 'astryx-bottom-sheet', visualProps: []}],
|
|
55
|
+
},
|
|
56
|
+
description:
|
|
57
|
+
"A mobile touch sheet that rises from the bottom edge, with animated entrance and exit, a grab handle, drag-to-resize snap points, and purpose-controlled dismissal. A standalone sheet owns a native <dialog>; inside BottomSheetSwitcher it renders a panel in the switcher's shared dialog. In both modes, ref and shared DOM props target the visual panel <div>.",
|
|
58
|
+
props: [
|
|
59
|
+
{
|
|
60
|
+
name: 'isOpen',
|
|
61
|
+
type: 'boolean',
|
|
62
|
+
description:
|
|
63
|
+
'Whether a standalone sheet is open. Fully controlled; pair with onOpenChange. Omit inside BottomSheetSwitcher.',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'onOpenChange',
|
|
67
|
+
type: '(isOpen: boolean) => void',
|
|
68
|
+
description:
|
|
69
|
+
'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.',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'purpose',
|
|
73
|
+
type: "'required' | 'form' | 'info'",
|
|
74
|
+
description:
|
|
75
|
+
"Controls implicit dismissal behavior, matching Dialog. info allows Escape, scrim click, and swipe-to-dismiss. form protects entered data by blocking scrim click and swipe while allowing Escape. required blocks every implicit dismissal path and uses role='alertdialog'. Explicit controls may still update the controlled state. Works for standalone and BottomSheetSwitcher-managed sheets.",
|
|
76
|
+
default: "'info'",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'sheetId',
|
|
80
|
+
type: 'string',
|
|
81
|
+
description:
|
|
82
|
+
'Unique ID for this sheet inside BottomSheetSwitcher. The switcher opens it when activeSheet matches. Omit isOpen and onOpenChange when sheetId is used.',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'label',
|
|
86
|
+
type: 'string',
|
|
87
|
+
description:
|
|
88
|
+
'Accessible label for the sheet. Required; the sheet has no built-in heading to derive a name from.',
|
|
89
|
+
required: true,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'children',
|
|
93
|
+
type: 'ReactNode',
|
|
94
|
+
description:
|
|
95
|
+
"Sheet content, rendered below the grab handle in a scrollable area. If it includes a text-entry control that can bring up the mobile keyboard, use height='tall' and keep the sheet fully expanded while editing.",
|
|
96
|
+
required: true,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'height',
|
|
100
|
+
type: "'hug' | 'capped' | 'tall' | number | string",
|
|
101
|
+
description:
|
|
102
|
+
"How tall the sheet is. Named budgets: 'hug' fits its content up to 92% of the viewport, 'capped' is a scrolling mid-height panel (~62%), and 'tall' is a pinned near-full panel (~92%) for content that streams in. Or pass a number (px) / CSS length for a custom budget. The user can drag between snap points regardless. On shorter viewports the sheet fills the available height. Only a fully expanded 'tall' sheet provides mobile-keyboard accommodation: it stays put and scrolls each focused control above the keyboard. Hug, Capped, numeric and CSS-length heights never do, and a Tall sheet stops doing it the moment the user drags it to a shorter detent — resuming when they drag it back. Outside that state the sheet neither moves nor adds keyboard scroll space, and the browser's own focus reveal is left in place; on iOS that reveal can shift the whole page.",
|
|
103
|
+
default: "'capped'",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'hasScrim',
|
|
107
|
+
type: 'boolean',
|
|
108
|
+
description:
|
|
109
|
+
"For a standalone BottomSheet, whether to render a scrim, the semi-transparent overlay that covers and blocks the background. true (default) uses showModal(): top layer, focus trap, ::backdrop scrim, body scroll lock, and tap-scrim-to-dismiss when purpose='info', with the background inert. false uses show() with no scrim, leaving the page behind interactive and scrollable. For a multi-step flow, configure hasScrim on BottomSheetSwitcher instead; it owns one shared dialog across every child.",
|
|
110
|
+
default: 'true',
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
usage: {
|
|
114
|
+
description:
|
|
115
|
+
'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.',
|
|
116
|
+
bestPractices: [
|
|
117
|
+
{
|
|
118
|
+
guidance: true,
|
|
119
|
+
description:
|
|
120
|
+
'Use for mobile-first surfaces (filters, share sheets, quick actions) where the content should rise from the bottom edge.',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
guidance: true,
|
|
124
|
+
description:
|
|
125
|
+
"Pick the starting height that fits the content: 'hug' for short bounded content, 'capped' for lists, and 'tall' for forms or streaming/resizing content.",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
guidance: true,
|
|
129
|
+
description:
|
|
130
|
+
"Use purpose='form' to protect entered data from scrim clicks and swipes while keeping Escape available; reserve purpose='required' for flows that must end through an explicit action.",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
guidance: false,
|
|
134
|
+
description:
|
|
135
|
+
"Don't make the sheet content overly long. Consider breaking it into steps and using Bottom Sheet Switcher.",
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
examples: [
|
|
140
|
+
{
|
|
141
|
+
label: 'Basic',
|
|
142
|
+
code: `const [isOpen, setIsOpen] = useState(false);
|
|
143
|
+
<BottomSheet
|
|
144
|
+
isOpen={isOpen}
|
|
145
|
+
onOpenChange={setIsOpen}
|
|
146
|
+
label="Filters">
|
|
147
|
+
<FilterControls />
|
|
148
|
+
</BottomSheet>`,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
label: 'Tall sheet (a list)',
|
|
152
|
+
code: `const [isOpen, setIsOpen] = useState(false);
|
|
153
|
+
<BottomSheet
|
|
154
|
+
isOpen={isOpen}
|
|
155
|
+
onOpenChange={setIsOpen}
|
|
156
|
+
label="Nearby places"
|
|
157
|
+
height="tall">
|
|
158
|
+
<PlaceList />
|
|
159
|
+
</BottomSheet>`,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
label: 'Hug height (fits content)',
|
|
163
|
+
code: `const [isOpen, setIsOpen] = useState(false);
|
|
164
|
+
<BottomSheet
|
|
165
|
+
isOpen={isOpen}
|
|
166
|
+
onOpenChange={setIsOpen}
|
|
167
|
+
label="Share"
|
|
168
|
+
height="hug">
|
|
169
|
+
<ShareActions />
|
|
170
|
+
</BottomSheet>`,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
label: 'Long scrolling content',
|
|
174
|
+
code: `const [isOpen, setIsOpen] = useState(false);
|
|
175
|
+
<BottomSheet
|
|
176
|
+
isOpen={isOpen}
|
|
177
|
+
onOpenChange={setIsOpen}
|
|
178
|
+
label="Release notes"
|
|
179
|
+
height="hug">
|
|
180
|
+
<ReleaseNotes />
|
|
181
|
+
</BottomSheet>`,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
label: 'Multi-step flow (one sheet at a time)',
|
|
185
|
+
code: `const [activeSheet, setActiveSheet] = useState(null);
|
|
186
|
+
<>
|
|
187
|
+
<Button label="Start" onClick={() => setActiveSheet('details')} />
|
|
188
|
+
<BottomSheetSwitcher
|
|
189
|
+
activeSheet={activeSheet}
|
|
190
|
+
onActiveSheetChange={setActiveSheet}>
|
|
191
|
+
<BottomSheet sheetId="details" label="Details">
|
|
192
|
+
<Button label="Continue" onClick={() => setActiveSheet('confirm')} />
|
|
193
|
+
</BottomSheet>
|
|
194
|
+
<BottomSheet sheetId="confirm" label="Confirm">
|
|
195
|
+
<Button label="Back" onClick={() => setActiveSheet('details')} />
|
|
196
|
+
</BottomSheet>
|
|
197
|
+
</BottomSheetSwitcher>
|
|
198
|
+
</>`,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
label: 'Mobile keyboard',
|
|
202
|
+
code: `const [isOpen, setIsOpen] = useState(false);
|
|
203
|
+
<BottomSheet
|
|
204
|
+
isOpen={isOpen}
|
|
205
|
+
onOpenChange={setIsOpen}
|
|
206
|
+
label="Add a comment"
|
|
207
|
+
height="tall">
|
|
208
|
+
<LongCommentForm />
|
|
209
|
+
</BottomSheet>`,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
label: 'No scrim',
|
|
213
|
+
code: `const [isOpen, setIsOpen] = useState(true);
|
|
214
|
+
<BottomSheet
|
|
215
|
+
isOpen={isOpen}
|
|
216
|
+
onOpenChange={setIsOpen}
|
|
217
|
+
label="Nearby places"
|
|
218
|
+
hasScrim={false}>
|
|
219
|
+
<PlaceList />
|
|
220
|
+
</BottomSheet>`,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
label: 'Protect form input',
|
|
224
|
+
code: `const [isOpen, setIsOpen] = useState(false);
|
|
225
|
+
<BottomSheet
|
|
226
|
+
isOpen={isOpen}
|
|
227
|
+
onOpenChange={setIsOpen}
|
|
228
|
+
purpose="form"
|
|
229
|
+
label="Edit profile">
|
|
230
|
+
<ProfileForm onSave={() => setIsOpen(false)} />
|
|
231
|
+
</BottomSheet>`,
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/** @type {import('@astryxdesign/cli/authoring').ComponentTranslationDoc} */
|
|
237
|
+
export const docsDense = {
|
|
238
|
+
description:
|
|
239
|
+
'mobile touch sheet rising from the bottom edge (native <dialog>): grab handle, transform-based drag-to-resize snap points, scrolling area resizes to the snapped visible height on release (the peek detent 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',
|
|
240
|
+
usage: {
|
|
241
|
+
description:
|
|
242
|
+
'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.',
|
|
243
|
+
bestPractices: [
|
|
244
|
+
{
|
|
245
|
+
guidance: true,
|
|
246
|
+
description:
|
|
247
|
+
'Use for mobile-first surfaces (filters, share sheets, quick actions) where the content should rise from the bottom edge.',
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
guidance: true,
|
|
251
|
+
description:
|
|
252
|
+
"Pick the starting height that fits the content: 'hug' for short bounded content, 'capped' for lists, and 'tall' for forms or streaming/resizing content.",
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
guidance: true,
|
|
256
|
+
description:
|
|
257
|
+
"Use purpose='form' to protect entered data from scrim clicks and swipes while keeping Escape available; reserve purpose='required' for flows that must end through an explicit action.",
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
guidance: false,
|
|
261
|
+
description:
|
|
262
|
+
"Don't make the sheet content overly long. Consider breaking it into steps and using Bottom Sheet Switcher.",
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
},
|
|
266
|
+
};
|