@astryxdesign/core 0.1.4-canary.39ffd21 → 0.1.4-canary.3d64ef2
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/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +8 -1
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +31 -11
- package/dist/Calendar/styles.d.ts +2 -2
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +2 -2
- package/dist/Card/Card.d.ts +4 -2
- package/dist/Card/Card.d.ts.map +1 -1
- package/dist/Card/Card.js +15 -7
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +1 -0
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +8 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +3 -1
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +26 -3
- package/dist/CodeBlock/CodeBlock.d.ts +10 -1
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +23 -5
- package/dist/Collapsible/Collapsible.d.ts +9 -3
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +101 -13
- package/dist/Collapsible/CollapsibleGroup.d.ts +39 -7
- package/dist/Collapsible/CollapsibleGroup.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroup.js +70 -9
- package/dist/Collapsible/CollapsibleGroupContext.d.ts +31 -0
- package/dist/Collapsible/CollapsibleGroupContext.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroupContext.js +33 -3
- package/dist/Collapsible/index.d.ts +1 -0
- package/dist/Collapsible/index.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +2 -0
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +2 -0
- package/dist/Grid/Grid.d.ts +4 -3
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +26 -9
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +5 -1
- package/dist/InputGroup/InputGroup.js +2 -2
- package/dist/Item/Item.js +1 -1
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +26 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +32 -16
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +87 -36
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +39 -21
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +2 -1
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +10 -2
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +6 -2
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +34 -7
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +14 -3
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +5 -2
- package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/paginateData.js +5 -1
- package/dist/Table/plugins/selection/useTableSelectionState.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelectionState.js +11 -1
- package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortableState.js +8 -4
- package/dist/Thumbnail/Thumbnail.d.ts +3 -2
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +1 -0
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +6 -1
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +11 -3
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +12 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +5 -2
- package/dist/astryx.css +7 -0
- package/dist/astryx.umd.js +48 -48
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/focusableSelector.d.ts +1 -1
- package/dist/hooks/focusableSelector.d.ts.map +1 -1
- package/dist/hooks/focusableSelector.js +1 -1
- package/dist/hooks/useFocusTrap.d.ts +3 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +38 -1
- package/dist/theme/defineTheme.d.ts +1 -1
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +10 -3
- package/dist/theme/hct.d.ts +0 -15
- package/dist/theme/hct.d.ts.map +1 -1
- package/dist/theme/hct.js +9 -9
- package/dist/theme/tokens.d.ts +10 -2
- package/dist/theme/tokens.d.ts.map +1 -1
- package/dist/theme/tokens.js +220 -4
- package/dist/utils/color.d.ts +42 -0
- package/dist/utils/color.d.ts.map +1 -0
- package/dist/utils/color.js +150 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +8 -5
- package/package.json +1 -1
- package/src/Avatar/Avatar.doc.mjs +11 -0
- package/src/Banner/Banner.doc.mjs +8 -0
- package/src/Banner/Banner.test.tsx +38 -0
- package/src/Banner/Banner.tsx +8 -1
- package/src/Calendar/Calendar.doc.mjs +1 -1
- package/src/Calendar/Calendar.test.tsx +112 -4
- package/src/Calendar/Calendar.tsx +26 -5
- package/src/Calendar/styles.ts +12 -2
- package/src/Card/Card.tsx +16 -6
- package/src/Carousel/Carousel.doc.mjs +12 -12
- package/src/Carousel/Carousel.test.tsx +13 -0
- package/src/Carousel/Carousel.tsx +1 -0
- package/src/Chat/Chat.doc.mjs +1 -1
- package/src/Chat/ChatToolCalls.test.tsx +55 -0
- package/src/Chat/ChatToolCalls.tsx +10 -12
- package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -3
- package/src/CheckboxInput/CheckboxInput.test.tsx +71 -4
- package/src/CheckboxInput/CheckboxInput.tsx +2 -0
- package/src/Citation/Citation.doc.mjs +5 -0
- package/src/ClickableCard/ClickableCard.tsx +45 -2
- package/src/CodeBlock/CodeBlock.doc.mjs +7 -2
- package/src/CodeBlock/CodeBlock.test.tsx +87 -3
- package/src/CodeBlock/CodeBlock.tsx +43 -3
- package/src/Collapsible/Collapsible.doc.mjs +8 -4
- package/src/Collapsible/Collapsible.tsx +86 -10
- package/src/Collapsible/CollapsibleGroup.doc.mjs +27 -3
- package/src/Collapsible/CollapsibleGroup.test.tsx +288 -0
- package/src/Collapsible/CollapsibleGroup.tsx +107 -9
- package/src/Collapsible/CollapsibleGroupContext.tsx +42 -2
- package/src/Collapsible/index.ts +4 -0
- package/src/CommandPalette/CommandPaletteEmpty.doc.mjs +5 -0
- package/src/CommandPalette/CommandPaletteGroup.doc.mjs +10 -0
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +6 -0
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +6 -0
- package/src/CommandPalette/CommandPaletteList.doc.mjs +9 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +15 -0
- package/src/ContextMenu/ContextMenu.test.tsx +15 -0
- package/src/ContextMenu/ContextMenu.tsx +2 -0
- package/src/ContextMenu/ContextMenuItem.doc.mjs +6 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +70 -0
- package/src/DateTimeInput/DateTimeInput.tsx +2 -0
- package/src/Dialog/DialogHeader.doc.mjs +45 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -1
- package/src/Field/FieldLabel.test.tsx +2 -2
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +0 -3
- package/src/Grid/Grid.test.tsx +43 -18
- package/src/Grid/Grid.tsx +26 -9
- package/src/Icon/Icon.test.tsx +41 -29
- package/src/Icon/Icon.tsx +6 -2
- package/src/InputGroup/InputGroup.tsx +2 -2
- package/src/Item/Item.tsx +1 -1
- package/src/Layout/LayoutContent.doc.mjs +12 -0
- package/src/Layout/LayoutPanel.doc.mjs +40 -0
- package/src/Lightbox/Lightbox.doc.mjs +5 -0
- package/src/Lightbox/Lightbox.test.tsx +123 -2
- package/src/Lightbox/Lightbox.tsx +29 -1
- package/src/NavMenu/NavMenu.doc.mjs +15 -0
- package/src/NumberInput/NumberInput.test.tsx +48 -3
- package/src/NumberInput/NumberInput.tsx +32 -15
- package/src/Overlay/Overlay.doc.mjs +6 -0
- package/src/Pagination/Pagination.test.tsx +143 -0
- package/src/Pagination/Pagination.tsx +75 -23
- package/src/Popover/Popover.test.tsx +65 -0
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearch.test.tsx +109 -1
- package/src/PowerSearch/PowerSearch.tsx +37 -16
- package/src/ProgressBar/ProgressBar.test.tsx +10 -0
- package/src/ProgressBar/ProgressBar.tsx +2 -1
- package/src/Resizable/ResizeHandle.test.tsx +180 -0
- package/src/Resizable/ResizeHandle.tsx +10 -2
- package/src/SegmentedControl/SegmentedControl.doc.mjs +1 -1
- package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
- package/src/SideNav/SideNav.doc.mjs +7 -1
- package/src/SideNav/SideNav.test.tsx +20 -0
- package/src/SideNav/SideNav.tsx +6 -2
- package/src/TabList/TabList.test.tsx +185 -1
- package/src/TabList/TabMenu.tsx +45 -9
- package/src/Table/BaseTable.tsx +13 -2
- package/src/Table/Table.test.tsx +51 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +13 -0
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +3 -1
- package/src/Table/plugins/pagination/paginateData.ts +5 -1
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +13 -0
- package/src/Table/plugins/selection/useTableSelectionState.test.tsx +23 -0
- package/src/Table/plugins/selection/useTableSelectionState.tsx +11 -6
- package/src/Table/plugins/sortable/useTableSortableState.test.tsx +35 -1
- package/src/Table/plugins/sortable/useTableSortableState.tsx +8 -4
- package/src/TextArea/TextArea.test.tsx +3 -3
- package/src/TextInput/TextInput.test.tsx +3 -3
- package/src/Thumbnail/Thumbnail.test.tsx +36 -2
- package/src/Thumbnail/Thumbnail.tsx +4 -2
- package/src/TimeInput/TimeInput.test.tsx +37 -0
- package/src/TimeInput/TimeInput.tsx +11 -1
- package/src/Timestamp/Timestamp.doc.mjs +1 -1
- package/src/Toast/Toast.tsx +11 -3
- package/src/Toast/ToastViewport.test.tsx +64 -0
- package/src/Toast/ToastViewport.tsx +12 -0
- package/src/ToggleButton/ToggleButton.doc.mjs +1 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
- package/src/TopNav/TopNav.doc.mjs +1 -1
- package/src/TreeList/TreeList.doc.mjs +2 -2
- package/src/Typeahead/BaseTypeahead.doc.mjs +19 -0
- package/src/Typeahead/BaseTypeahead.tsx +5 -2
- package/src/Typeahead/Typeahead.doc.mjs +1 -1
- package/src/Typeahead/Typeahead.test.tsx +45 -1
- package/src/__tests__/TestIcon.tsx +26 -0
- package/src/docPropReferences.test.ts +178 -0
- package/src/hooks/focusableSelector.ts +1 -1
- package/src/hooks/useFocusTrap.doc.mjs +4 -2
- package/src/hooks/useFocusTrap.test.tsx +149 -1
- package/src/hooks/useFocusTrap.ts +50 -1
- package/src/theme/SyntaxTheme.doc.mjs +4 -4
- package/src/theme/defineTheme.ts +4 -13
- package/src/theme/derivedVarRegistry.test.ts +2 -2
- package/src/theme/expandColorScale.test.ts +29 -0
- package/src/theme/expandColorScale.ts +11 -6
- package/src/theme/hct.ts +9 -17
- package/src/theme/tokens.test.ts +182 -9
- package/src/theme/tokens.ts +239 -4
- package/src/theme/useTheme.test.tsx +20 -0
- package/src/utils/color.test.ts +133 -0
- package/src/utils/color.ts +148 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/timeParser.test.ts +13 -0
- package/src/utils/timeParser.ts +8 -5
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
'use client';
|
|
4
4
|
/**
|
|
5
5
|
* @file CodeBlock.tsx
|
|
6
|
-
* @input Uses React, StyleX, theme tokens, CSS Custom Highlight API
|
|
6
|
+
* @input Uses React, StyleX, theme tokens, CSS Custom Highlight API, SyntaxTheme provider
|
|
7
7
|
* @output Exports CodeBlock component and CodeBlockProps
|
|
8
8
|
* @position Core implementation; read-only syntax-highlighted code display
|
|
9
9
|
*/
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import {
|
|
12
12
|
useInsertionEffect,
|
|
13
13
|
useEffect,
|
|
14
|
+
useId,
|
|
14
15
|
useRef,
|
|
15
16
|
useState,
|
|
16
17
|
useCallback,
|
|
@@ -32,6 +33,7 @@ import {
|
|
|
32
33
|
easeVars,
|
|
33
34
|
} from '../theme/tokens.stylex';
|
|
34
35
|
import {mergeProps} from '../utils';
|
|
36
|
+
import {useAnnounce} from '../hooks/useAnnounce';
|
|
35
37
|
import {Icon} from '../Icon';
|
|
36
38
|
import {
|
|
37
39
|
tokenize,
|
|
@@ -43,6 +45,7 @@ import type {SyntaxToken, TokenLine} from './tokenizer';
|
|
|
43
45
|
import {ensureHighlightStyles} from './highlightStyles';
|
|
44
46
|
import {applyHighlightRangesChunked} from './highlightRanges';
|
|
45
47
|
import {themeProps} from '../utils/themeProps';
|
|
48
|
+
import {SyntaxTheme, type SyntaxThemeDefinition} from '../theme/syntax';
|
|
46
49
|
|
|
47
50
|
// ---------------------------------------------------------------------------
|
|
48
51
|
// Styles
|
|
@@ -173,6 +176,17 @@ const styles = stylex.create({
|
|
|
173
176
|
headerCollapsible: {
|
|
174
177
|
cursor: 'pointer',
|
|
175
178
|
userSelect: 'none',
|
|
179
|
+
// Restore a keyboard-only focus ring with the standard token/offset so this
|
|
180
|
+
// disclosure control matches the rest of the system (Collapsible, TabMenu);
|
|
181
|
+
// otherwise it falls back to the inconsistent UA default outline.
|
|
182
|
+
outline: {
|
|
183
|
+
default: null,
|
|
184
|
+
':focus-visible': `2px solid ${colorVars['--color-accent']}`,
|
|
185
|
+
},
|
|
186
|
+
outlineOffset: {
|
|
187
|
+
default: '0',
|
|
188
|
+
':focus-visible': '2px',
|
|
189
|
+
},
|
|
176
190
|
},
|
|
177
191
|
gutter: {
|
|
178
192
|
flexShrink: 0,
|
|
@@ -382,6 +396,14 @@ export interface CodeBlockProps extends BaseProps<HTMLPreElement> {
|
|
|
382
396
|
language: string,
|
|
383
397
|
) => {type: string; start: number; end: number}[];
|
|
384
398
|
highlightMode?: 'auto' | 'ranges' | 'spans';
|
|
399
|
+
/**
|
|
400
|
+
* Per-instance syntax theme override. Shorthand for wrapping this block in
|
|
401
|
+
* `<SyntaxTheme theme={...}>` — accepts a preset from
|
|
402
|
+
* `@astryxdesign/core/theme/syntax` or a theme created with
|
|
403
|
+
* `defineSyntaxTheme()`. Without it, the block uses the theme-level syntax
|
|
404
|
+
* colors from the nearest SyntaxTheme ancestor or `defineTheme({ syntax })`.
|
|
405
|
+
*/
|
|
406
|
+
syntaxTheme?: SyntaxThemeDefinition;
|
|
385
407
|
}
|
|
386
408
|
|
|
387
409
|
// ---------------------------------------------------------------------------
|
|
@@ -633,6 +655,7 @@ export function CodeBlock({
|
|
|
633
655
|
container = 'card',
|
|
634
656
|
tokenizer: customTokenizer,
|
|
635
657
|
highlightMode = 'auto',
|
|
658
|
+
syntaxTheme,
|
|
636
659
|
xstyle,
|
|
637
660
|
className,
|
|
638
661
|
style,
|
|
@@ -640,6 +663,7 @@ export function CodeBlock({
|
|
|
640
663
|
...props
|
|
641
664
|
}: CodeBlockProps) {
|
|
642
665
|
const [copied, setCopied] = useState(false);
|
|
666
|
+
const announce = useAnnounce();
|
|
643
667
|
|
|
644
668
|
const useSpans =
|
|
645
669
|
highlightMode === 'spans' ||
|
|
@@ -665,12 +689,15 @@ export function CodeBlock({
|
|
|
665
689
|
try {
|
|
666
690
|
await navigator.clipboard.writeText(code);
|
|
667
691
|
setCopied(true);
|
|
692
|
+
// Swapping the button's aria-label alone isn't reliably announced by
|
|
693
|
+
// screen readers, so confirm the copy via a polite live region.
|
|
694
|
+
announce('Copied');
|
|
668
695
|
onCopy?.();
|
|
669
696
|
setTimeout(() => setCopied(false), 2000);
|
|
670
697
|
} catch {
|
|
671
698
|
// Clipboard failures leave the copied state unchanged.
|
|
672
699
|
}
|
|
673
|
-
}, [code, onCopy]);
|
|
700
|
+
}, [code, onCopy, announce]);
|
|
674
701
|
|
|
675
702
|
const sizeStyle = size === 'sm' ? styles.sizeSm : styles.sizeMd;
|
|
676
703
|
const gutterSizeStyle = size === 'sm' ? styles.gutterSm : styles.gutterMd;
|
|
@@ -680,6 +707,11 @@ export function CodeBlock({
|
|
|
680
707
|
|
|
681
708
|
const canCollapse = isCollapsible && lines.length >= collapsibleThreshold;
|
|
682
709
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
710
|
+
// Links the collapsible header to the code region it shows/hides so assistive
|
|
711
|
+
// tech can move from the button to its controlled content (disclosure
|
|
712
|
+
// pattern). The region stays mounted when collapsed (CSS grid animation), so
|
|
713
|
+
// this is always a resolvable reference — aria-controls can be unconditional.
|
|
714
|
+
const regionId = useId();
|
|
683
715
|
|
|
684
716
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
685
717
|
const scrollStyle: CSSProperties | undefined = maxHeight
|
|
@@ -717,6 +749,7 @@ export function CodeBlock({
|
|
|
717
749
|
role={canCollapse ? 'button' : undefined}
|
|
718
750
|
tabIndex={canCollapse ? 0 : undefined}
|
|
719
751
|
aria-expanded={canCollapse ? !isCollapsed : undefined}
|
|
752
|
+
aria-controls={canCollapse ? regionId : undefined}
|
|
720
753
|
onClick={canCollapse ? () => setIsCollapsed(prev => !prev) : undefined}
|
|
721
754
|
onKeyDown={
|
|
722
755
|
canCollapse
|
|
@@ -802,7 +835,7 @@ export function CodeBlock({
|
|
|
802
835
|
</div>
|
|
803
836
|
);
|
|
804
837
|
|
|
805
|
-
|
|
838
|
+
const block = (
|
|
806
839
|
<pre
|
|
807
840
|
ref={ref}
|
|
808
841
|
{...mergeProps(
|
|
@@ -820,6 +853,7 @@ export function CodeBlock({
|
|
|
820
853
|
{headerEl}
|
|
821
854
|
{canCollapse ? (
|
|
822
855
|
<div
|
|
856
|
+
id={regionId}
|
|
823
857
|
{...stylex.props(
|
|
824
858
|
styles.collapseGrid,
|
|
825
859
|
isCollapsed && styles.collapseGridCollapsed,
|
|
@@ -832,6 +866,12 @@ export function CodeBlock({
|
|
|
832
866
|
{!showHeader && copyButtonEl}
|
|
833
867
|
</pre>
|
|
834
868
|
);
|
|
869
|
+
|
|
870
|
+
return syntaxTheme ? (
|
|
871
|
+
<SyntaxTheme theme={syntaxTheme}>{block}</SyntaxTheme>
|
|
872
|
+
) : (
|
|
873
|
+
block
|
|
874
|
+
);
|
|
835
875
|
}
|
|
836
876
|
|
|
837
877
|
CodeBlock.displayName = 'CodeBlock';
|
|
@@ -16,7 +16,8 @@ export const docs = {
|
|
|
16
16
|
},
|
|
17
17
|
theming: {
|
|
18
18
|
targets: [
|
|
19
|
-
{className: 'astryx-collapsible'},
|
|
19
|
+
{className: 'astryx-collapsible', visualProps: ['dividers', 'density']},
|
|
20
|
+
{className: 'astryx-collapsible-group', visualProps: ['dividers', 'density']},
|
|
20
21
|
],
|
|
21
22
|
},
|
|
22
23
|
description: 'A primitive that makes any content collapsible: a trigger button toggles visibility of the content area, managing its own state or deferring to a parent CollapsibleGroup.',
|
|
@@ -69,7 +70,8 @@ export const docs = {
|
|
|
69
70
|
usage: {
|
|
70
71
|
description: 'Collapsible hides and reveals content behind a trigger button. Use it in settings panels, FAQ pages, or detail views to keep the page scannable while letting users drill into sections they care about. Wrap multiple collapsibles in CollapsibleGroup for accordion behavior. For custom collapsible components, use the `useCollapsible` hook directly (`astryx hook useCollapsible`).',
|
|
71
72
|
bestPractices: [
|
|
72
|
-
{ guidance: true, description: '
|
|
73
|
+
{ guidance: true, description: 'Use dividers="between" on CollapsibleGroup for FAQ-style lists: built-in row hairlines with themed border tokens, no hand-rolled borders.' },
|
|
74
|
+
{ guidance: true, description: 'Wrap each Collapsible in an Card for visual separation in accordion layouts, or use CollapsibleGroup dividers for flat lists; don\'t combine both.' },
|
|
73
75
|
{ guidance: true, description: 'Use CollapsibleGroup with type="single" for settings or FAQ pages where only one section should be open at a time.' },
|
|
74
76
|
{ guidance: true, description: 'Use type="multiple" when users need to compare content across sections, like feature lists or pricing tiers.' },
|
|
75
77
|
{ guidance: true, description: 'Start sections open (defaultIsOpen) when the content is likely needed on first view; don\'t make users click to see essential info.' },
|
|
@@ -90,7 +92,8 @@ export const docsZh = {
|
|
|
90
92
|
usage: {
|
|
91
93
|
description: 'Collapsible hides and reveals content behind a trigger button. Use it in settings panels, FAQ pages, or detail views to keep the page scannable while letting users drill into sections they care about. Wrap multiple collapsibles in CollapsibleGroup for accordion behavior.',
|
|
92
94
|
bestPractices: [
|
|
93
|
-
{ guidance: true, description: '
|
|
95
|
+
{ guidance: true, description: 'Use dividers="between" on CollapsibleGroup for FAQ-style lists: built-in row hairlines with themed border tokens, no hand-rolled borders.' },
|
|
96
|
+
{ guidance: true, description: 'Wrap each Collapsible in an Card for visual separation in accordion layouts, or use CollapsibleGroup dividers for flat lists; don\'t combine both.' },
|
|
94
97
|
{ guidance: true, description: 'Use CollapsibleGroup with type="single" for settings or FAQ pages where only one section should be open at a time.' },
|
|
95
98
|
{ guidance: true, description: 'Use type="multiple" when users need to compare content across sections, like feature lists or pricing tiers.' },
|
|
96
99
|
{ guidance: true, description: 'Start sections open (defaultIsOpen) when the content is likely needed on first view; don\'t make users click to see essential info.' },
|
|
@@ -107,7 +110,8 @@ export const docsDense = {
|
|
|
107
110
|
usage: {
|
|
108
111
|
description: 'Collapsible hides and reveals content behind a trigger button. Use in settings, FAQs, or detail views. Wrap in CollapsibleGroup for accordion behavior.',
|
|
109
112
|
bestPractices: [
|
|
110
|
-
{ guidance: true, description: '
|
|
113
|
+
{ guidance: true, description: 'Use dividers="between" on CollapsibleGroup for FAQ-style lists: built-in row hairlines, no hand-rolled borders.' },
|
|
114
|
+
{ guidance: true, description: 'Wrap each Collapsible in an Card for visual separation, or use CollapsibleGroup dividers for flat lists; not both.' },
|
|
111
115
|
{ guidance: true, description: 'Use CollapsibleGroup with type="single" for settings or FAQ pages where only one section should be open at a time.' },
|
|
112
116
|
{ guidance: true, description: 'Use type="multiple" when users need to compare across sections.' },
|
|
113
117
|
{ guidance: true, description: 'Start sections open (defaultIsOpen) when content is needed on first view.' },
|
|
@@ -4,15 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file Collapsible.tsx
|
|
7
|
-
* @input Uses React, StyleX, useCollapsible hook, getIcon, theme tokens
|
|
7
|
+
* @input Uses React, StyleX, useCollapsible hook, CollapsibleGroupPresentationContext, getIcon, theme tokens
|
|
8
8
|
* @output Exports Collapsible component and CollapsibleProps
|
|
9
9
|
* @position Collapsible content primitive — trigger toggles visibility of children
|
|
10
10
|
*
|
|
11
11
|
* Collapsible is a standalone primitive that makes any content collapsible.
|
|
12
12
|
* It renders a trigger area (always visible) and a content area that toggles.
|
|
13
|
-
* Handles state management, accessibility (aria-expanded
|
|
13
|
+
* Handles state management, accessibility (aria-expanded + aria-controls linking
|
|
14
|
+
* the trigger to its content region), and chevron indicator.
|
|
14
15
|
*
|
|
15
16
|
* Works standalone or coordinated by CollapsibleGroup via the `value` prop.
|
|
17
|
+
* When the surrounding CollapsibleGroup enables `dividers`, each Collapsible
|
|
18
|
+
* draws its own row chrome (borderBlockStart suppressed on :first-child, plus
|
|
19
|
+
* density padding) from CollapsibleGroupPresentationContext — StyleX has no
|
|
20
|
+
* child selectors, so the group cannot draw it from outside. The presentation
|
|
21
|
+
* context is reset around children so nested collapsibles stay chrome-free.
|
|
16
22
|
*
|
|
17
23
|
* SYNC: When modified, update these files to stay in sync:
|
|
18
24
|
* - /packages/core/src/Collapsible/index.ts (exports)
|
|
@@ -21,9 +27,10 @@
|
|
|
21
27
|
* - /packages/cli/templates/blocks/components/Collapsible/ (showcase blocks)
|
|
22
28
|
*/
|
|
23
29
|
|
|
24
|
-
import type
|
|
30
|
+
import {use, useId, type ReactNode} from 'react';
|
|
25
31
|
import * as stylex from '@stylexjs/stylex';
|
|
26
32
|
import {
|
|
33
|
+
borderVars,
|
|
27
34
|
colorVars,
|
|
28
35
|
typographyVars,
|
|
29
36
|
fontWeightVars,
|
|
@@ -33,6 +40,7 @@ import {
|
|
|
33
40
|
easeVars,
|
|
34
41
|
} from '../theme/tokens.stylex';
|
|
35
42
|
import {useCollapsible} from './useCollapsible';
|
|
43
|
+
import {CollapsibleGroupPresentationContext} from './CollapsibleGroupContext';
|
|
36
44
|
import {getIcon} from '../Icon/globalIconRegistry';
|
|
37
45
|
import {mergeProps} from '../utils';
|
|
38
46
|
import type {BaseProps} from '../BaseProps';
|
|
@@ -57,6 +65,16 @@ const styles = stylex.create({
|
|
|
57
65
|
color: colorVars['--color-text-primary'],
|
|
58
66
|
textAlign: 'start',
|
|
59
67
|
paddingBlock: 0,
|
|
68
|
+
// `all: unset` above wipes the UA focus outline; restore a keyboard-only
|
|
69
|
+
// focus ring using the standard token/offset (WCAG 2.4.7).
|
|
70
|
+
outline: {
|
|
71
|
+
default: null,
|
|
72
|
+
':focus-visible': `2px solid ${colorVars['--color-accent']}`,
|
|
73
|
+
},
|
|
74
|
+
outlineOffset: {
|
|
75
|
+
default: '0',
|
|
76
|
+
':focus-visible': '2px',
|
|
77
|
+
},
|
|
60
78
|
},
|
|
61
79
|
// Capsize: trim leading from text triggers
|
|
62
80
|
triggerLabel: {
|
|
@@ -87,8 +105,42 @@ const styles = stylex.create({
|
|
|
87
105
|
content: {
|
|
88
106
|
paddingBlockStart: spacingVars['--spacing-1'],
|
|
89
107
|
},
|
|
108
|
+
// Group divider chrome — a hairline above every item except the first.
|
|
109
|
+
// The group's wrapper (or 'all' mode) owns the outer edges.
|
|
110
|
+
divided: {
|
|
111
|
+
borderBlockStartWidth: {
|
|
112
|
+
default: borderVars['--border-width'],
|
|
113
|
+
':first-child': '0',
|
|
114
|
+
},
|
|
115
|
+
borderBlockStartStyle: 'solid',
|
|
116
|
+
borderBlockStartColor: colorVars['--color-border'],
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Density padding for divided/padded accordion rows. paddingBlock mapping
|
|
121
|
+
// follows Table's density scale (spacing-1/2/3); content only pads its end
|
|
122
|
+
// so text doesn't sit on the divider below (block-start stays spacing-1).
|
|
123
|
+
const densityStyles = stylex.create({
|
|
124
|
+
triggerCompact: {paddingBlock: spacingVars['--spacing-1']},
|
|
125
|
+
triggerBalanced: {paddingBlock: spacingVars['--spacing-2']},
|
|
126
|
+
triggerSpacious: {paddingBlock: spacingVars['--spacing-3']},
|
|
127
|
+
contentCompact: {paddingBlockEnd: spacingVars['--spacing-1']},
|
|
128
|
+
contentBalanced: {paddingBlockEnd: spacingVars['--spacing-2']},
|
|
129
|
+
contentSpacious: {paddingBlockEnd: spacingVars['--spacing-3']},
|
|
90
130
|
});
|
|
91
131
|
|
|
132
|
+
const triggerDensity = {
|
|
133
|
+
compact: densityStyles.triggerCompact,
|
|
134
|
+
balanced: densityStyles.triggerBalanced,
|
|
135
|
+
spacious: densityStyles.triggerSpacious,
|
|
136
|
+
} as const;
|
|
137
|
+
|
|
138
|
+
const contentDensity = {
|
|
139
|
+
compact: densityStyles.contentCompact,
|
|
140
|
+
balanced: densityStyles.contentBalanced,
|
|
141
|
+
spacious: densityStyles.contentSpacious,
|
|
142
|
+
} as const;
|
|
143
|
+
|
|
92
144
|
export interface CollapsibleProps extends BaseProps {
|
|
93
145
|
/** Ref forwarded to the root element */
|
|
94
146
|
ref?: React.Ref<HTMLDivElement>;
|
|
@@ -192,14 +244,25 @@ export function Collapsible({
|
|
|
192
244
|
value,
|
|
193
245
|
});
|
|
194
246
|
|
|
247
|
+
const presentation = use(CollapsibleGroupPresentationContext);
|
|
248
|
+
const isDivided = presentation != null && presentation.dividers !== 'none';
|
|
249
|
+
const density = presentation?.density ?? null;
|
|
250
|
+
|
|
195
251
|
const chevronIcon = getIcon('chevronDown');
|
|
196
252
|
|
|
253
|
+
// Links the trigger to the region it shows/hides so assistive tech can move
|
|
254
|
+
// from the button to its controlled content (disclosure pattern).
|
|
255
|
+
const contentId = useId();
|
|
256
|
+
|
|
197
257
|
return (
|
|
198
258
|
<div
|
|
199
259
|
ref={ref}
|
|
200
260
|
{...mergeProps(
|
|
201
|
-
themeProps('collapsible'
|
|
202
|
-
|
|
261
|
+
themeProps('collapsible', {
|
|
262
|
+
dividers: isDivided ? presentation.dividers : undefined,
|
|
263
|
+
density: density ?? undefined,
|
|
264
|
+
}),
|
|
265
|
+
stylex.props(styles.root, isDivided && styles.divided, xstyle),
|
|
203
266
|
className,
|
|
204
267
|
style,
|
|
205
268
|
)}
|
|
@@ -208,7 +271,11 @@ export function Collapsible({
|
|
|
208
271
|
type="button"
|
|
209
272
|
onClick={toggle}
|
|
210
273
|
aria-expanded={isOpen}
|
|
211
|
-
{
|
|
274
|
+
aria-controls={contentId}
|
|
275
|
+
{...stylex.props(
|
|
276
|
+
styles.trigger,
|
|
277
|
+
density != null && triggerDensity[density],
|
|
278
|
+
)}>
|
|
212
279
|
<span {...stylex.props(styles.triggerLabel)}>{trigger}</span>
|
|
213
280
|
<span
|
|
214
281
|
{...stylex.props(
|
|
@@ -219,10 +286,19 @@ export function Collapsible({
|
|
|
219
286
|
</span>
|
|
220
287
|
</button>
|
|
221
288
|
<div
|
|
222
|
-
{
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
289
|
+
id={contentId}
|
|
290
|
+
{...stylex.props(
|
|
291
|
+
styles.content,
|
|
292
|
+
density != null && contentDensity[density],
|
|
293
|
+
!isOpen && styles.contentHidden,
|
|
294
|
+
)}>
|
|
295
|
+
{presentation != null ? (
|
|
296
|
+
<CollapsibleGroupPresentationContext value={null}>
|
|
297
|
+
{children}
|
|
298
|
+
</CollapsibleGroupPresentationContext>
|
|
299
|
+
) : (
|
|
300
|
+
children
|
|
301
|
+
)}
|
|
226
302
|
</div>
|
|
227
303
|
</div>
|
|
228
304
|
);
|
|
@@ -7,7 +7,7 @@ export const docs = {
|
|
|
7
7
|
subComponentOf: 'Collapsible',
|
|
8
8
|
displayName: 'Collapsible Group',
|
|
9
9
|
isHiddenFromOverview: true,
|
|
10
|
-
description: 'Coordinates multiple Collapsible instances so only one (single mode) or any number (multiple mode) can be open at a time. Renders no wrapper DOM element.',
|
|
10
|
+
description: 'Coordinates multiple Collapsible instances so only one (single mode) or any number (multiple mode) can be open at a time. Renders no wrapper DOM element unless dividers are enabled.',
|
|
11
11
|
props: [
|
|
12
12
|
{
|
|
13
13
|
name: 'type',
|
|
@@ -30,6 +30,17 @@ export const docs = {
|
|
|
30
30
|
type: '(value: string | string[]) => void',
|
|
31
31
|
description: 'Callback invoked when the set of open items changes.',
|
|
32
32
|
},
|
|
33
|
+
{
|
|
34
|
+
name: 'dividers',
|
|
35
|
+
type: "'between' | 'all' | 'none'",
|
|
36
|
+
description: "Divider style rendered around the group's items. 'between' draws hairlines between adjacent items; 'all' adds the group's top and bottom edges. When enabled, the group renders a wrapper div and items default to 'balanced' density. Pair with bare Collapsible children; Card-wrapped items provide their own separation.",
|
|
37
|
+
default: "'none'",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'density',
|
|
41
|
+
type: "'compact' | 'balanced' | 'spacious'",
|
|
42
|
+
description: "Row density controlling trigger and content block padding on the group's items. Defaults to 'balanced' when dividers are shown; otherwise items keep their default unpadded look.",
|
|
43
|
+
},
|
|
33
44
|
{
|
|
34
45
|
name: 'children',
|
|
35
46
|
type: 'ReactNode',
|
|
@@ -52,7 +63,7 @@ export const docsZh = {
|
|
|
52
63
|
name: 'CollapsibleGroup',
|
|
53
64
|
isHiddenFromOverview: true,
|
|
54
65
|
displayName: 'Collapsible Group',
|
|
55
|
-
description: '协调多个 Collapsible 实例,使同一时间只有一个(single 模式)或任意数量(multiple
|
|
66
|
+
description: '协调多个 Collapsible 实例,使同一时间只有一个(single 模式)或任意数量(multiple 模式)可以展开。除非启用 dividers,否则不渲染包裹 DOM 元素。',
|
|
56
67
|
props: [
|
|
57
68
|
{
|
|
58
69
|
name: 'type',
|
|
@@ -75,6 +86,17 @@ export const docsZh = {
|
|
|
75
86
|
type: '(value: string | string[]) => void',
|
|
76
87
|
description: '展开项目集合变更时调用的回调。',
|
|
77
88
|
},
|
|
89
|
+
{
|
|
90
|
+
name: 'dividers',
|
|
91
|
+
type: "'between' | 'all' | 'none'",
|
|
92
|
+
description: "渲染在组项目周围的分隔线样式。'between' 仅在相邻项目之间绘制细线;'all' 额外绘制组的顶部和底部边缘。启用后组会渲染一个包裹 div,且项目默认使用 'balanced' 密度。适合搭配裸 Collapsible 子项使用;用 Card 包裹的项目自带视觉分隔。",
|
|
93
|
+
default: "'none'",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'density',
|
|
97
|
+
type: "'compact' | 'balanced' | 'spacious'",
|
|
98
|
+
description: "控制组内项目触发器和内容块内边距的行密度。显示分隔线时默认为 'balanced';否则项目保持默认的无内边距外观。",
|
|
99
|
+
},
|
|
78
100
|
{
|
|
79
101
|
name: 'children',
|
|
80
102
|
type: 'ReactNode',
|
|
@@ -88,12 +110,14 @@ export const docsDense = {
|
|
|
88
110
|
name: 'CollapsibleGroup',
|
|
89
111
|
isHiddenFromOverview: true,
|
|
90
112
|
displayName: 'Collapsible Group',
|
|
91
|
-
description: 'coordinates multiple Collapsible instances; single or multiple open. no wrapper DOM.',
|
|
113
|
+
description: 'coordinates multiple Collapsible instances; single or multiple open. no wrapper DOM unless dividers enabled.',
|
|
92
114
|
propDescriptions: {
|
|
93
115
|
type: 'one or many items open simultaneously',
|
|
94
116
|
defaultValue: 'default open item(s) (uncontrolled); string for single, array for multiple',
|
|
95
117
|
value: 'controlled open item(s)',
|
|
96
118
|
onChange: 'callback on open items change',
|
|
119
|
+
dividers: "row hairlines: 'between' items only, 'all' adds outer edges, 'none' (default); enables wrapper div + 'balanced' density; use bare Collapsible children",
|
|
120
|
+
density: "row padding 'compact' | 'balanced' | 'spacious'; defaults 'balanced' with dividers, else unpadded",
|
|
97
121
|
children: 'Collapsible instances to coordinate',
|
|
98
122
|
},
|
|
99
123
|
};
|