@delacour/react-native-ui 0.1.0-alpha.3
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/README.md +113 -0
- package/package.json +125 -0
- package/src/components/accordion/AGENTS.md +192 -0
- package/src/components/accordion/accordion-content.tsx +125 -0
- package/src/components/accordion/accordion-description.tsx +17 -0
- package/src/components/accordion/accordion-indicator.tsx +104 -0
- package/src/components/accordion/accordion-item.tsx +78 -0
- package/src/components/accordion/accordion-title.tsx +18 -0
- package/src/components/accordion/accordion-trigger.tsx +148 -0
- package/src/components/accordion/accordion.context.tsx +153 -0
- package/src/components/accordion/accordion.tsx +253 -0
- package/src/components/accordion/accordion.types.ts +11 -0
- package/src/components/accordion/accordion.variants.test.ts +435 -0
- package/src/components/accordion/accordion.variants.ts +358 -0
- package/src/components/accordion/index.ts +45 -0
- package/src/components/badge/AGENTS.md +83 -0
- package/src/components/badge/badge-close-button.tsx +48 -0
- package/src/components/badge/badge-end-content.tsx +15 -0
- package/src/components/badge/badge-label.tsx +24 -0
- package/src/components/badge/badge-start-content.tsx +16 -0
- package/src/components/badge/badge.context.tsx +64 -0
- package/src/components/badge/badge.tsx +192 -0
- package/src/components/badge/badge.types.ts +10 -0
- package/src/components/badge/badge.variants.test.ts +269 -0
- package/src/components/badge/badge.variants.ts +238 -0
- package/src/components/badge/index.ts +17 -0
- package/src/components/bottom-sheet/AGENTS.md +232 -0
- package/src/components/bottom-sheet/bottom-sheet-background.tsx +35 -0
- package/src/components/bottom-sheet/bottom-sheet-close.tsx +60 -0
- package/src/components/bottom-sheet/bottom-sheet-container.tsx +245 -0
- package/src/components/bottom-sheet/bottom-sheet-content.tsx +66 -0
- package/src/components/bottom-sheet/bottom-sheet-description.tsx +29 -0
- package/src/components/bottom-sheet/bottom-sheet-footer.tsx +148 -0
- package/src/components/bottom-sheet/bottom-sheet-handle.tsx +47 -0
- package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +180 -0
- package/src/components/bottom-sheet/bottom-sheet-portal.tsx +72 -0
- package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +95 -0
- package/src/components/bottom-sheet/bottom-sheet-title.tsx +24 -0
- package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +73 -0
- package/src/components/bottom-sheet/bottom-sheet.context.tsx +135 -0
- package/src/components/bottom-sheet/bottom-sheet.tsx +125 -0
- package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +276 -0
- package/src/components/bottom-sheet/bottom-sheet.variants.ts +199 -0
- package/src/components/bottom-sheet/index.ts +40 -0
- package/src/components/bottom-sheet/use-bottom-sheet-input.ts +125 -0
- package/src/components/button/AGENTS.md +217 -0
- package/src/components/button/button-end-content.tsx +15 -0
- package/src/components/button/button-group-separator.tsx +37 -0
- package/src/components/button/button-group-text.tsx +101 -0
- package/src/components/button/button-group.tsx +158 -0
- package/src/components/button/button-label.tsx +24 -0
- package/src/components/button/button-start-content.tsx +15 -0
- package/src/components/button/button.context.tsx +187 -0
- package/src/components/button/button.tsx +273 -0
- package/src/components/button/button.types.ts +10 -0
- package/src/components/button/button.variants.test.ts +811 -0
- package/src/components/button/button.variants.ts +480 -0
- package/src/components/button/index.ts +50 -0
- package/src/components/chart/AGENTS.md +210 -0
- package/src/components/chart/chart-area.tsx +93 -0
- package/src/components/chart/chart-bar.tsx +103 -0
- package/src/components/chart/chart-bars.tsx +75 -0
- package/src/components/chart/chart-candlestick.tsx +60 -0
- package/src/components/chart/chart-grid.tsx +33 -0
- package/src/components/chart/chart-legend.tsx +41 -0
- package/src/components/chart/chart-line.tsx +50 -0
- package/src/components/chart/chart-scatter.tsx +35 -0
- package/src/components/chart/chart-tooltip-dot.tsx +70 -0
- package/src/components/chart/chart-tooltip-x.tsx +121 -0
- package/src/components/chart/chart-tooltip-y.tsx +55 -0
- package/src/components/chart/chart-tooltip.tsx +133 -0
- package/src/components/chart/chart-x-axis.tsx +27 -0
- package/src/components/chart/chart-y-axis.tsx +21 -0
- package/src/components/chart/chart.context.tsx +127 -0
- package/src/components/chart/chart.tsx +448 -0
- package/src/components/chart/chart.types.ts +108 -0
- package/src/components/chart/chart.variants.test.ts +893 -0
- package/src/components/chart/chart.variants.ts +726 -0
- package/src/components/chart/index.ts +103 -0
- package/src/components/chart/pie-chart-center.tsx +40 -0
- package/src/components/chart/pie-chart-label.tsx +56 -0
- package/src/components/chart/pie-chart-slice.tsx +45 -0
- package/src/components/chart/pie-chart-tooltip.tsx +85 -0
- package/src/components/chart/pie-chart.context.tsx +77 -0
- package/src/components/chart/pie-chart.tsx +247 -0
- package/src/components/chart/use-chart-font.ts +25 -0
- package/src/components/chart/use-chart-palette.ts +38 -0
- package/src/components/checkbox/AGENTS.md +185 -0
- package/src/components/checkbox/checkbox-box.tsx +152 -0
- package/src/components/checkbox/checkbox-group.tsx +107 -0
- package/src/components/checkbox/checkbox-label.tsx +34 -0
- package/src/components/checkbox/checkbox.context.tsx +136 -0
- package/src/components/checkbox/checkbox.tsx +258 -0
- package/src/components/checkbox/checkbox.types.ts +14 -0
- package/src/components/checkbox/checkbox.variants.test.ts +634 -0
- package/src/components/checkbox/checkbox.variants.ts +484 -0
- package/src/components/checkbox/index.ts +42 -0
- package/src/components/field/AGENTS.md +100 -0
- package/src/components/field/field-content.tsx +24 -0
- package/src/components/field/field-description.tsx +29 -0
- package/src/components/field/field-error.tsx +61 -0
- package/src/components/field/field-group.tsx +22 -0
- package/src/components/field/field-label.tsx +33 -0
- package/src/components/field/field-legend.tsx +34 -0
- package/src/components/field/field-separator.tsx +81 -0
- package/src/components/field/field-set.tsx +22 -0
- package/src/components/field/field.context.tsx +78 -0
- package/src/components/field/field.tsx +144 -0
- package/src/components/field/field.types.ts +15 -0
- package/src/components/field/field.variants.test.ts +216 -0
- package/src/components/field/field.variants.ts +136 -0
- package/src/components/field/index.ts +21 -0
- package/src/components/icon/AGENTS.md +104 -0
- package/src/components/icon/icon.context.tsx +29 -0
- package/src/components/icon/icon.tsx +92 -0
- package/src/components/icon/icon.variants.test.ts +111 -0
- package/src/components/icon/icon.variants.ts +79 -0
- package/src/components/icon/index.ts +11 -0
- package/src/components/input/AGENTS.md +126 -0
- package/src/components/input/index.ts +27 -0
- package/src/components/input/input-group-decorator.tsx +84 -0
- package/src/components/input/input-group-prefix.tsx +16 -0
- package/src/components/input/input-group-suffix.tsx +15 -0
- package/src/components/input/input-group.tsx +140 -0
- package/src/components/input/input.context.tsx +87 -0
- package/src/components/input/input.tsx +184 -0
- package/src/components/input/input.types.ts +10 -0
- package/src/components/input/input.variants.test.ts +369 -0
- package/src/components/input/input.variants.ts +329 -0
- package/src/components/list-group/AGENTS.md +67 -0
- package/src/components/list-group/index.ts +20 -0
- package/src/components/list-group/list-group-item-content.tsx +10 -0
- package/src/components/list-group/list-group-item-description.tsx +12 -0
- package/src/components/list-group/list-group-item-prefix.tsx +30 -0
- package/src/components/list-group/list-group-item-suffix.tsx +55 -0
- package/src/components/list-group/list-group-item-title.tsx +17 -0
- package/src/components/list-group/list-group-item.tsx +87 -0
- package/src/components/list-group/list-group.context.tsx +65 -0
- package/src/components/list-group/list-group.tsx +116 -0
- package/src/components/list-group/list-group.types.ts +14 -0
- package/src/components/list-group/list-group.variants.test.ts +223 -0
- package/src/components/list-group/list-group.variants.ts +107 -0
- package/src/components/pressable/AGENTS.md +64 -0
- package/src/components/pressable/index.ts +9 -0
- package/src/components/pressable/pressable.tsx +256 -0
- package/src/components/pressable/pressable.variants.test.ts +99 -0
- package/src/components/pressable/pressable.variants.ts +63 -0
- package/src/components/provider/AGENTS.md +90 -0
- package/src/components/provider/index.ts +1 -0
- package/src/components/provider/provider.tsx +105 -0
- package/src/components/radio/AGENTS.md +247 -0
- package/src/components/radio/index.ts +35 -0
- package/src/components/radio/radio-group.tsx +126 -0
- package/src/components/radio/radio-indicator.tsx +91 -0
- package/src/components/radio/radio-label.tsx +39 -0
- package/src/components/radio/radio.context.tsx +132 -0
- package/src/components/radio/radio.tsx +215 -0
- package/src/components/radio/radio.variants.test.ts +581 -0
- package/src/components/radio/radio.variants.ts +272 -0
- package/src/components/screen/AGENTS.md +287 -0
- package/src/components/screen/index.ts +85 -0
- package/src/components/screen/screen-chat-list.tsx +485 -0
- package/src/components/screen/screen-content.tsx +69 -0
- package/src/components/screen/screen-debug.ts +43 -0
- package/src/components/screen/screen-error.tsx +61 -0
- package/src/components/screen/screen-flat-list.tsx +88 -0
- package/src/components/screen/screen-footer-background.tsx +60 -0
- package/src/components/screen/screen-footer.tsx +170 -0
- package/src/components/screen/screen-header.tsx +36 -0
- package/src/components/screen/screen-legend-list.tsx +105 -0
- package/src/components/screen/screen-list-component.tsx +23 -0
- package/src/components/screen/screen-loading.tsx +50 -0
- package/src/components/screen/screen-navbar-back-button.tsx +79 -0
- package/src/components/screen/screen-navbar-background.tsx +45 -0
- package/src/components/screen/screen-navbar-subtitle.tsx +26 -0
- package/src/components/screen/screen-navbar-title.tsx +31 -0
- package/src/components/screen/screen-navbar.tsx +154 -0
- package/src/components/screen/screen-root.tsx +38 -0
- package/src/components/screen/screen-scroll-area.tsx +116 -0
- package/src/components/screen/screen-scroll-shadow.tsx +186 -0
- package/src/components/screen/screen-section-list.tsx +81 -0
- package/src/components/screen/screen-view.tsx +59 -0
- package/src/components/screen/screen.context.tsx +167 -0
- package/src/components/screen/screen.tsx +114 -0
- package/src/components/screen/screen.types.ts +66 -0
- package/src/components/screen/screen.variants.test.ts +602 -0
- package/src/components/screen/screen.variants.ts +475 -0
- package/src/components/screen/use-screen-scroll-insets.ts +218 -0
- package/src/components/separator/AGENTS.md +30 -0
- package/src/components/separator/index.ts +8 -0
- package/src/components/separator/separator.tsx +85 -0
- package/src/components/slider/AGENTS.md +274 -0
- package/src/components/slider/index.ts +48 -0
- package/src/components/slider/slider-fill.tsx +68 -0
- package/src/components/slider/slider-output.tsx +55 -0
- package/src/components/slider/slider-thumb.tsx +161 -0
- package/src/components/slider/slider-track.tsx +233 -0
- package/src/components/slider/slider.context.tsx +118 -0
- package/src/components/slider/slider.tsx +321 -0
- package/src/components/slider/slider.types.ts +26 -0
- package/src/components/slider/slider.variants.test.ts +804 -0
- package/src/components/slider/slider.variants.ts +607 -0
- package/src/components/spinner/AGENTS.md +73 -0
- package/src/components/spinner/index.ts +17 -0
- package/src/components/spinner/spinner-arc.tsx +90 -0
- package/src/components/spinner/spinner-content.tsx +63 -0
- package/src/components/spinner/spinner.context.tsx +52 -0
- package/src/components/spinner/spinner.tsx +128 -0
- package/src/components/spinner/spinner.variants.test.ts +273 -0
- package/src/components/spinner/spinner.variants.ts +187 -0
- package/src/components/switch/AGENTS.md +213 -0
- package/src/components/switch/index.ts +42 -0
- package/src/components/switch/switch-content.tsx +111 -0
- package/src/components/switch/switch-end-content.tsx +18 -0
- package/src/components/switch/switch-start-content.tsx +20 -0
- package/src/components/switch/switch-thumb.tsx +102 -0
- package/src/components/switch/switch.context.tsx +79 -0
- package/src/components/switch/switch.tsx +423 -0
- package/src/components/switch/switch.types.ts +14 -0
- package/src/components/switch/switch.variants.test.ts +570 -0
- package/src/components/switch/switch.variants.ts +511 -0
- package/src/components/tabs/AGENTS.md +286 -0
- package/src/components/tabs/index.ts +69 -0
- package/src/components/tabs/tabs-content.tsx +65 -0
- package/src/components/tabs/tabs-indicator.tsx +97 -0
- package/src/components/tabs/tabs-label.tsx +62 -0
- package/src/components/tabs/tabs-list.tsx +139 -0
- package/src/components/tabs/tabs-pager.tsx +59 -0
- package/src/components/tabs/tabs-scroll-view.tsx +171 -0
- package/src/components/tabs/tabs-separator.tsx +73 -0
- package/src/components/tabs/tabs-trigger.tsx +210 -0
- package/src/components/tabs/tabs.context.tsx +294 -0
- package/src/components/tabs/tabs.tsx +435 -0
- package/src/components/tabs/tabs.types.ts +13 -0
- package/src/components/tabs/tabs.variants.test.ts +1020 -0
- package/src/components/tabs/tabs.variants.ts +672 -0
- package/src/components/text/AGENTS.md +95 -0
- package/src/components/text/index.ts +25 -0
- package/src/components/text/text.context.tsx +60 -0
- package/src/components/text/text.tsx +251 -0
- package/src/components/text/text.variants.test.ts +422 -0
- package/src/components/text/text.variants.ts +282 -0
- package/src/display-name.test.ts +145 -0
- package/src/docs.test.ts +98 -0
- package/src/expo/navigation-theme.tsx +58 -0
- package/src/hooks/use-controllable-state.ts +45 -0
- package/src/hooks/use-keyboard-state-sync.tsx +147 -0
- package/src/hooks/use-navigation-theme.ts +78 -0
- package/src/hooks/use-theme-color.ts +44 -0
- package/src/icons/central.ts +1 -0
- package/src/lib/cn.test.ts +136 -0
- package/src/lib/cn.ts +29 -0
- package/src/lib/color.test.ts +80 -0
- package/src/lib/color.ts +79 -0
- package/src/lib/compose-refs.test.ts +64 -0
- package/src/lib/compose-refs.ts +37 -0
- package/src/lib/keyboard-animation.test.ts +33 -0
- package/src/lib/keyboard-animation.ts +28 -0
- package/src/lib/merge-props.test.ts +82 -0
- package/src/lib/merge-props.ts +47 -0
- package/src/lib/navigation-theme.test.ts +49 -0
- package/src/lib/navigation-theme.ts +53 -0
- package/src/lib/slot.tsx +45 -0
- package/src/lib/tv.ts +20 -0
- package/src/styles/base.css +2 -0
- package/src/styles/geometry.test.ts +117 -0
- package/src/styles/index.css +3 -0
- package/src/styles/theme-tokens.test.ts +252 -0
- package/src/styles/theme.css +447 -0
- package/src/styles/tokens.css +132 -0
- package/src/styles/tokens.test.ts +181 -0
- package/src/styles/tokens.ts +86 -0
- package/src/uniwind-env.d.ts +1 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { Children, isValidElement, type ReactElement, type ReactNode, useCallback, useMemo, useRef } from "react";
|
|
2
|
+
import { View, type ViewProps } from "react-native";
|
|
3
|
+
import { useControllableState } from "../../hooks/use-controllable-state";
|
|
4
|
+
import { Separator } from "../separator";
|
|
5
|
+
import { type AccordionContextValue, AccordionProvider } from "./accordion.context";
|
|
6
|
+
import {
|
|
7
|
+
ACCORDION_DEFAULT_SIZE,
|
|
8
|
+
ACCORDION_DEFAULT_VARIANT,
|
|
9
|
+
type AccordionSelection,
|
|
10
|
+
type AccordionSelectionMode,
|
|
11
|
+
type AccordionSize,
|
|
12
|
+
type AccordionVariant,
|
|
13
|
+
accordionVariants,
|
|
14
|
+
toExpandedList,
|
|
15
|
+
toggleExpandedValue,
|
|
16
|
+
} from "./accordion.variants";
|
|
17
|
+
import { AccordionContent } from "./accordion-content";
|
|
18
|
+
import { AccordionDescription } from "./accordion-description";
|
|
19
|
+
import { AccordionIndicator } from "./accordion-indicator";
|
|
20
|
+
import { AccordionItem } from "./accordion-item";
|
|
21
|
+
import { AccordionTitle } from "./accordion-title";
|
|
22
|
+
import { AccordionTrigger } from "./accordion-trigger";
|
|
23
|
+
|
|
24
|
+
export type AccordionBaseProps = Omit<ViewProps, "children"> & {
|
|
25
|
+
variant?: AccordionVariant;
|
|
26
|
+
size?: AccordionSize;
|
|
27
|
+
/** Draw a divider between adjacent items. On by default. */
|
|
28
|
+
isDivided?: boolean;
|
|
29
|
+
/** Blocks every trigger and fades every row. An item's own prop still overrides it. */
|
|
30
|
+
isDisabled?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Whether the last open item may be closed. On by default.
|
|
33
|
+
*
|
|
34
|
+
* It bounds the *set*, never a single item: in `multiple` mode an item still
|
|
35
|
+
* closes while another is open, and only the last one is refused.
|
|
36
|
+
*/
|
|
37
|
+
isCollapsible?: boolean;
|
|
38
|
+
className?: string;
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type AccordionProps = AccordionBaseProps & AccordionSelection;
|
|
43
|
+
|
|
44
|
+
type AccordionBodyProps = AccordionBaseProps & {
|
|
45
|
+
selectionMode?: AccordionSelectionMode;
|
|
46
|
+
expanded?: readonly string[];
|
|
47
|
+
defaultExpanded: readonly string[];
|
|
48
|
+
onExpandedChange?: (expanded: readonly string[]) => void;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Everything the accordion does, over one internal list of open values.
|
|
53
|
+
*
|
|
54
|
+
* Split from the root so the root can be nothing but a narrow on `selectionMode`.
|
|
55
|
+
* `useControllableState` cannot be called conditionally, so the two shapes a
|
|
56
|
+
* caller can hold — a string or an array — are converted to this one list at the
|
|
57
|
+
* boundary and back again on the way out. Two hooks would be two pieces of state
|
|
58
|
+
* for one answer.
|
|
59
|
+
*/
|
|
60
|
+
function AccordionBody({
|
|
61
|
+
selectionMode = "single",
|
|
62
|
+
variant = ACCORDION_DEFAULT_VARIANT,
|
|
63
|
+
size = ACCORDION_DEFAULT_SIZE,
|
|
64
|
+
isDivided = true,
|
|
65
|
+
isDisabled = false,
|
|
66
|
+
isCollapsible = true,
|
|
67
|
+
expanded: expandedProp,
|
|
68
|
+
defaultExpanded,
|
|
69
|
+
onExpandedChange,
|
|
70
|
+
className,
|
|
71
|
+
children,
|
|
72
|
+
...props
|
|
73
|
+
}: AccordionBodyProps): ReactElement {
|
|
74
|
+
// A ref-backed trampoline, so the accordion's own context does not change
|
|
75
|
+
// identity whenever the caller passes a fresh arrow — which would re-render
|
|
76
|
+
// every item on every render of the screen above it. `Switch` registers its
|
|
77
|
+
// toggle with a `Field` the same way, for the same reason.
|
|
78
|
+
const changeRef = useRef(onExpandedChange);
|
|
79
|
+
changeRef.current = onExpandedChange;
|
|
80
|
+
const handleChange = useCallback((next: readonly string[]) => changeRef.current?.(next), []);
|
|
81
|
+
|
|
82
|
+
const [expanded, setExpanded] = useControllableState<readonly string[]>({
|
|
83
|
+
defaultValue: defaultExpanded,
|
|
84
|
+
onChange: handleChange,
|
|
85
|
+
value: expandedProp,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Read by `toggle` rather than closed over, so one stable callback serves the
|
|
89
|
+
// accordion for its whole life. A `toggle` that changed on every expansion
|
|
90
|
+
// would be a new context value on every expansion, and therefore a re-render
|
|
91
|
+
// of every item rather than of the one that moved.
|
|
92
|
+
const expandedRef = useRef(expanded);
|
|
93
|
+
expandedRef.current = expanded;
|
|
94
|
+
|
|
95
|
+
const toggle = useCallback(
|
|
96
|
+
(value: string) => {
|
|
97
|
+
const next = toggleExpandedValue({ expanded: expandedRef.current, isCollapsible, selectionMode, value });
|
|
98
|
+
// Refused, by identity. A tap the rules reject must not re-render and
|
|
99
|
+
// must not report an `onValueChange` for a change that did not happen.
|
|
100
|
+
if (next === expandedRef.current) return;
|
|
101
|
+
setExpanded(next);
|
|
102
|
+
},
|
|
103
|
+
[isCollapsible, selectionMode, setExpanded]
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const context = useMemo<AccordionContextValue>(
|
|
107
|
+
() => ({ expanded, isDisabled, size, toggle, variant }),
|
|
108
|
+
[expanded, isDisabled, size, toggle, variant]
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const slots = accordionVariants({ size, variant });
|
|
112
|
+
const dividerClassName = slots.divider();
|
|
113
|
+
|
|
114
|
+
const content = useMemo(
|
|
115
|
+
() => (isDivided ? withDividers(children, dividerClassName) : children),
|
|
116
|
+
[children, dividerClassName, isDivided]
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<AccordionProvider value={context}>
|
|
121
|
+
<View className={slots.root({ className })} {...props}>
|
|
122
|
+
{content}
|
|
123
|
+
</View>
|
|
124
|
+
</AccordionProvider>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
AccordionBody.displayName = "DelacourUI.Accordion.Body";
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Inserts a divider between adjacent items.
|
|
131
|
+
*
|
|
132
|
+
* A pair is skipped when either side already is a `Separator`, so a hand-placed
|
|
133
|
+
* divider is never doubled. `Children.toArray` drops the nulls and booleans a
|
|
134
|
+
* conditional child leaves behind, so an item rendered only some of the time does
|
|
135
|
+
* not strand a divider where nothing follows it — which counting indices against
|
|
136
|
+
* the raw children cannot do.
|
|
137
|
+
*/
|
|
138
|
+
function withDividers(children: ReactNode, dividerClassName: string): ReactNode {
|
|
139
|
+
const items = Children.toArray(children);
|
|
140
|
+
const output: ReactNode[] = [];
|
|
141
|
+
|
|
142
|
+
for (const [index, child] of items.entries()) {
|
|
143
|
+
if (index > 0 && !isSeparator(items[index - 1]) && !isSeparator(child)) {
|
|
144
|
+
output.push(<Separator className={dividerClassName} key={`divider-${index}`} />);
|
|
145
|
+
}
|
|
146
|
+
output.push(child);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return output;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isSeparator(node: ReactNode): boolean {
|
|
153
|
+
return isValidElement(node) && node.type === Separator;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function AccordionRoot(props: AccordionProps): ReactElement {
|
|
157
|
+
// The one place the union is narrowed, and it happens before anything is
|
|
158
|
+
// destructured — which is what keeps the two shapes correlated. Destructure
|
|
159
|
+
// first and `onValueChange` widens to a union of two functions that cannot be
|
|
160
|
+
// called, which is what forces a cast or a `@ts-expect-error` on every other
|
|
161
|
+
// implementation of this component.
|
|
162
|
+
if (props.selectionMode === "multiple") {
|
|
163
|
+
const { value, defaultValue, onValueChange, ...rest } = props;
|
|
164
|
+
return (
|
|
165
|
+
<AccordionBody
|
|
166
|
+
{...rest}
|
|
167
|
+
defaultExpanded={toExpandedList(defaultValue)}
|
|
168
|
+
expanded={value === undefined ? undefined : toExpandedList(value)}
|
|
169
|
+
onExpandedChange={(next) => onValueChange?.([...next])}
|
|
170
|
+
/>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const { value, defaultValue, onValueChange, ...rest } = props;
|
|
175
|
+
return (
|
|
176
|
+
<AccordionBody
|
|
177
|
+
{...rest}
|
|
178
|
+
defaultExpanded={toExpandedList(defaultValue)}
|
|
179
|
+
expanded={value === undefined ? undefined : toExpandedList(value)}
|
|
180
|
+
onExpandedChange={(next) => onValueChange?.(next[0] ?? null)}
|
|
181
|
+
/>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* A set of rows that each disclose a panel.
|
|
187
|
+
*
|
|
188
|
+
* `selectionMode` decides how many open at once: `single` (the default) keeps one
|
|
189
|
+
* open and reports a `string | null`, `multiple` keeps any number and reports a
|
|
190
|
+
* `string[]`. The two are a real discriminated union, so an array `defaultValue`
|
|
191
|
+
* on a single accordion is a compile error rather than a runtime surprise.
|
|
192
|
+
*
|
|
193
|
+
* **A panel's height is measured and animated**, so the animation lives on the
|
|
194
|
+
* panel's own node and whatever sits below simply follows it — no sibling on the
|
|
195
|
+
* screen has to opt into anything. Its
|
|
196
|
+
* height, its fade and the indicator's rotation all run off one shared value per
|
|
197
|
+
* item, so they cannot drift out of step by a frame, and a tap mid-travel reverses
|
|
198
|
+
* the spring rather than restarting it.
|
|
199
|
+
*
|
|
200
|
+
* **A panel mounts on first expand and stays mounted**, so a form inside one keeps
|
|
201
|
+
* what was typed, a list keeps where it was scrolled, and a video keeps playing
|
|
202
|
+
* position across every later collapse.
|
|
203
|
+
*
|
|
204
|
+
* Dividers are inserted between adjacent items rather than written out at every
|
|
205
|
+
* call site, inset to line up with the triggers' own padding. A `Separator` placed
|
|
206
|
+
* by hand is respected — no divider is added on either side of it — so a caller can
|
|
207
|
+
* control one gap without turning the feature off. `isDivided={false}` turns it off.
|
|
208
|
+
*
|
|
209
|
+
* `isCollapsible={false}` keeps one item open at all times. It bounds the set and
|
|
210
|
+
* never a single item, so in `multiple` mode a row still closes while another is
|
|
211
|
+
* open and only the last one is refused.
|
|
212
|
+
*
|
|
213
|
+
* **State works either way from one hook**: pass `value` to control it, or nothing
|
|
214
|
+
* and let it hold its own.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* <Accordion>
|
|
218
|
+
* <Accordion.Item value="shipping">
|
|
219
|
+
* <Accordion.Trigger>
|
|
220
|
+
* <Accordion.Title>Shipping</Accordion.Title>
|
|
221
|
+
* <Accordion.Description>2–5 business days</Accordion.Description>
|
|
222
|
+
* </Accordion.Trigger>
|
|
223
|
+
* <Accordion.Content>
|
|
224
|
+
* <Text.Paragraph>Tracked, and signed for over $200.</Text.Paragraph>
|
|
225
|
+
* </Accordion.Content>
|
|
226
|
+
* </Accordion.Item>
|
|
227
|
+
* </Accordion>
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* <Accordion onValueChange={setOpen} selectionMode="multiple" value={open}>
|
|
231
|
+
* {sections.map((section) => (
|
|
232
|
+
* <Accordion.Item key={section.id} value={section.id}>
|
|
233
|
+
* <Accordion.Trigger>{section.title}</Accordion.Trigger>
|
|
234
|
+
* <Accordion.Content>{section.body}</Accordion.Content>
|
|
235
|
+
* </Accordion.Item>
|
|
236
|
+
* ))}
|
|
237
|
+
* </Accordion>
|
|
238
|
+
*/
|
|
239
|
+
export const Accordion = Object.assign(AccordionRoot, {
|
|
240
|
+
/** One section: a trigger, and the panel it discloses. */
|
|
241
|
+
Item: AccordionItem,
|
|
242
|
+
/** The row that opens the item. A `Pressable`, so it inherits the whole vocabulary. */
|
|
243
|
+
Trigger: AccordionTrigger,
|
|
244
|
+
/** The trigger's primary line. Bare string children become one automatically. */
|
|
245
|
+
Title: AccordionTitle,
|
|
246
|
+
/** The trigger's secondary line, stacked under the title. */
|
|
247
|
+
Description: AccordionDescription,
|
|
248
|
+
/** The glyph that turns as the panel opens. Composed in when a trigger holds none. */
|
|
249
|
+
Indicator: AccordionIndicator,
|
|
250
|
+
/** The measured, clipped panel. Mounts on first expand and stays mounted. */
|
|
251
|
+
Content: AccordionContent,
|
|
252
|
+
displayName: "DelacourUI.Accordion",
|
|
253
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TextProps } from "react-native";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The shape of a trigger's two text lines, `Title` and `Description`.
|
|
5
|
+
*
|
|
6
|
+
* Shared by both parts, and by the root's trigger, which wraps bare string
|
|
7
|
+
* children in a `Accordion.Title` — three modules, so it lives in a leaf rather
|
|
8
|
+
* than in one of them arbitrarily. `ListGroupTextProps` is the same type for the
|
|
9
|
+
* same reason.
|
|
10
|
+
*/
|
|
11
|
+
export type AccordionTextProps = TextProps & { className?: string };
|