@astryxdesign/core 0.4.3 → 0.4.4-canary.0f4d45f
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/Field/InputClearButton.d.ts +2 -1
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +6 -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/StatusDot/StatusDot.d.ts +43 -3
- package/dist/StatusDot/StatusDot.d.ts.map +1 -1
- package/dist/StatusDot/StatusDot.js +42 -5
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +5 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
- 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/af-ZA.json +958 -0
- package/locales/ar-SA.json +998 -0
- package/locales/ca-ES.json +974 -0
- package/locales/cs-CZ.json +990 -0
- package/locales/da-DK.json +966 -0
- package/locales/de-DE.json +966 -0
- package/locales/el-GR.json +990 -0
- package/locales/en.json +48 -0
- package/locales/es-ES.json +978 -0
- package/locales/fi-FI.json +990 -0
- package/locales/fr-FR.json +928 -0
- package/locales/he-IL.json +990 -0
- package/locales/hu-HU.json +990 -0
- package/locales/it-IT.json +978 -0
- package/locales/ja-JP.json +998 -0
- package/locales/ko-KR.json +990 -0
- package/locales/nl-NL.json +954 -0
- package/locales/no-NO.json +978 -0
- package/locales/pl-PL.json +982 -0
- package/locales/pseudo.json +36 -0
- package/locales/pt-BR.json +982 -0
- package/locales/pt-PT.json +982 -0
- package/locales/ro-RO.json +982 -0
- package/locales/ru-RU.json +990 -0
- package/locales/sr-SP.json +990 -0
- package/locales/sv-SE.json +986 -0
- package/locales/tr-TR.json +990 -0
- package/locales/uk-UA.json +990 -0
- package/locales/vi-VN.json +986 -0
- package/locales/zh-CN.json +998 -0
- package/locales/zh-TW.json +998 -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/Field/Field.doc.mjs +1 -0
- package/src/Field/InputClearButton.test.tsx +25 -0
- package/src/Field/InputClearButton.tsx +4 -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/StatusDot/StatusDot.doc.mjs +13 -0
- package/src/StatusDot/StatusDot.test.tsx +115 -8
- package/src/StatusDot/StatusDot.tsx +74 -9
- package/src/Switch/Switch.test.tsx +16 -0
- package/src/Switch/Switch.tsx +7 -0
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +20 -2
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4-canary.0f4d45f",
|
|
4
4
|
"displayName": "Astryx Core",
|
|
5
5
|
"description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -27,8 +27,6 @@
|
|
|
27
27
|
"type": "module",
|
|
28
28
|
"main": "./dist/index.js",
|
|
29
29
|
"types": "./dist/index.d.ts",
|
|
30
|
-
"unpkg": "./dist/astryx.umd.js",
|
|
31
|
-
"jsdelivr": "./dist/astryx.umd.js",
|
|
32
30
|
"sideEffects": [
|
|
33
31
|
"**/*.stylex.ts",
|
|
34
32
|
"**/*.stylex.js",
|
|
@@ -49,7 +47,6 @@
|
|
|
49
47
|
"types": "./src/astryx.css.d.ts",
|
|
50
48
|
"default": "./dist/astryx.css"
|
|
51
49
|
},
|
|
52
|
-
"./astryx.umd.js": "./dist/astryx.umd.js",
|
|
53
50
|
"./tailwind-theme.css": {
|
|
54
51
|
"types": "./src/tailwind-theme.css.d.ts",
|
|
55
52
|
"default": "./src/tailwind-theme.css"
|
|
@@ -122,6 +119,11 @@
|
|
|
122
119
|
"types": "./dist/Blockquote/index.d.ts",
|
|
123
120
|
"default": "./dist/Blockquote/index.js"
|
|
124
121
|
},
|
|
122
|
+
"./BottomSheet": {
|
|
123
|
+
"source": "./src/BottomSheet/index.ts",
|
|
124
|
+
"types": "./dist/BottomSheet/index.d.ts",
|
|
125
|
+
"default": "./dist/BottomSheet/index.js"
|
|
126
|
+
},
|
|
125
127
|
"./Breadcrumbs": {
|
|
126
128
|
"source": "./src/Breadcrumbs/index.ts",
|
|
127
129
|
"types": "./dist/Breadcrumbs/index.d.ts",
|
|
@@ -654,6 +656,7 @@
|
|
|
654
656
|
"react-dom": ">=19.0.0"
|
|
655
657
|
},
|
|
656
658
|
"devDependencies": {
|
|
659
|
+
"@astryxdesign/cli": "0.4.4-canary.0f4d45f",
|
|
657
660
|
"@babel/cli": "^8.0.4",
|
|
658
661
|
"@babel/core": "^7.29.7",
|
|
659
662
|
"@babel/preset-react": "^8.0.1",
|
|
@@ -662,19 +665,17 @@
|
|
|
662
665
|
"@testing-library/dom": "^10.0.0",
|
|
663
666
|
"@testing-library/jest-dom": "^6.6.0",
|
|
664
667
|
"@testing-library/react": "^16.3.2",
|
|
665
|
-
"rimraf": "^6.0.1"
|
|
666
|
-
"@astryxdesign/cli": "0.4.3"
|
|
668
|
+
"rimraf": "^6.0.1"
|
|
667
669
|
},
|
|
668
670
|
"dependencies": {
|
|
669
671
|
"intl-messageformat": "^11.2.9"
|
|
670
672
|
},
|
|
671
673
|
"scripts": {
|
|
672
674
|
"postinstall": "node scripts/postinstall.mjs",
|
|
673
|
-
"build": "rimraf dist && pnpm build:i18n && pnpm build:esm && tsc --project tsconfig.build.json && pnpm build:css &&
|
|
675
|
+
"build": "rimraf dist && pnpm build:i18n && pnpm build:esm && tsc --project tsconfig.build.json && pnpm build:css && node ../../scripts/check-no-dev-jsx.mjs && node ../../scripts/check-fully-specified.mjs",
|
|
674
676
|
"build:i18n": "node scripts/build-pseudo-locale.mjs",
|
|
675
677
|
"build:esm": "babel src --out-dir dist --extensions .ts,.tsx --out-file-extension .js --ignore \"**/*.test.ts,**/*.test.tsx,**/__tests__/**,**/*.perf.test.*,**/*.doc.mjs,**/*.d.ts,**/*.css,**/*.css.d.ts\" --config-file ./babel.config.json",
|
|
676
678
|
"build:css": "node ../../scripts/build-css.mjs",
|
|
677
|
-
"build:umd": "node ../../scripts/build-umd.mjs",
|
|
678
679
|
"dev": "babel src --watch --out-dir dist --extensions .ts,.tsx --out-file-extension .js --ignore \"**/*.test.*,**/*.doc.mjs,**/*.d.ts,**/*.css*\" --config-file ./babel.config.json",
|
|
679
680
|
"test": "vitest run --root ../..",
|
|
680
681
|
"test:watch": "vitest --root ../..",
|
|
@@ -70,7 +70,7 @@ export const docs = {
|
|
|
70
70
|
name: 'endContent',
|
|
71
71
|
type: 'ReactNode',
|
|
72
72
|
description:
|
|
73
|
-
'Action content rendered in the header area, end-aligned.
|
|
73
|
+
'Action content rendered in the header area, end-aligned. Wraps to its own row below the text when the header is too narrow to hold both.',
|
|
74
74
|
slotElements: [
|
|
75
75
|
{__element: 'Icon', props: {icon: 'chevronDown', size: 'sm'}},
|
|
76
76
|
{__element: 'Badge', props: {label: '3'}},
|
|
@@ -164,7 +164,7 @@ export const docsZh = {
|
|
|
164
164
|
{name: 'icon', type: 'ReactNode', description: '覆盖默认的状态图标。'},
|
|
165
165
|
{name: 'isDismissable', type: 'boolean', description: '横幅是否可被用户关闭。', default: 'false'},
|
|
166
166
|
{name: 'onDismiss', type: '() => void', description: '点击关闭按钮时调用;无论是否提供此回调,横幅都会自动隐藏。'},
|
|
167
|
-
{name: 'endContent', type: 'ReactNode', description: '
|
|
167
|
+
{name: 'endContent', type: 'ReactNode', description: '渲染在头部区域末端对齐的操作内容,通常是按钮或链接。头部过窄时会整体换行到文本下方,自成一行。'},
|
|
168
168
|
{name: 'container', type: "'card' | 'section'", description: '视觉变体:card 带圆角;section 无圆角全宽,适用于页面级场景。', default: "'card'"},
|
|
169
169
|
{name: 'elevation', type: "'none' | 'low' | 'med' | 'high'", description: '静止阴影深度。用于悬浮于内容之上的浮动横幅;none 为默认内联横幅。', default: "'none'"},
|
|
170
170
|
{name: 'children', type: 'ReactNode', description: '渲染在彩色头部下方卡片背景区域的内容。'},
|
|
@@ -238,7 +238,7 @@ export const docsDense = {
|
|
|
238
238
|
icon: 'override default status icon',
|
|
239
239
|
isDismissable: 'user can dismiss banner',
|
|
240
240
|
onDismiss: 'dismiss callback; banner self-hides regardless',
|
|
241
|
-
endContent: 'end-aligned action in header, typically button/link',
|
|
241
|
+
endContent: 'end-aligned action in header, typically button/link; wraps to its own row when the header is too narrow',
|
|
242
242
|
container: 'card=border-radius; section=full-width no radius for page-level',
|
|
243
243
|
elevation: 'resting shadow depth: none|low|med|high; raise for a floating banner',
|
|
244
244
|
children: 'content in card-bg area below colored header',
|
|
@@ -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
|
+
};
|