@mrmeg/expo-ui 0.13.0-rsd.0 → 0.13.0
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 +31 -0
- package/LLM_USAGE.md +15 -0
- package/README.md +15 -1
- package/dist/components/Accordion.d.ts +18 -38
- package/dist/components/Accordion.js +121 -137
- package/dist/components/AnimatedView.d.ts +24 -13
- package/dist/components/AnimatedView.js +29 -70
- package/dist/components/Badge.d.ts +2 -2
- package/dist/components/Badge.js +47 -45
- package/dist/components/BottomSheet.d.ts +73 -30
- package/dist/components/BottomSheet.js +225 -191
- package/dist/components/Button.d.ts +56 -43
- package/dist/components/Button.js +203 -189
- package/dist/components/Card.d.ts +10 -7
- package/dist/components/Card.js +105 -90
- package/dist/components/Checkbox.d.ts +8 -20
- package/dist/components/Checkbox.js +80 -77
- package/dist/components/Collapsible.d.ts +47 -47
- package/dist/components/Collapsible.js +29 -108
- package/dist/components/Dialog.d.ts +121 -80
- package/dist/components/Dialog.js +132 -225
- package/dist/components/DismissKeyboard.js +2 -33
- package/dist/components/Drawer.d.ts +83 -37
- package/dist/components/Drawer.js +512 -252
- package/dist/components/DropdownMenu.d.ts +113 -106
- package/dist/components/DropdownMenu.js +204 -350
- package/dist/components/EmptyState.d.ts +2 -2
- package/dist/components/EmptyState.js +34 -41
- package/dist/components/InputOTP.d.ts +53 -19
- package/dist/components/InputOTP.js +102 -81
- package/dist/components/KeyboardAvoidingView.d.ts +3 -6
- package/dist/components/KeyboardAvoidingView.js +5 -18
- package/dist/components/Label.d.ts +17 -12
- package/dist/components/Label.js +51 -38
- package/dist/components/MaxWidthContainer.d.ts +16 -5
- package/dist/components/MaxWidthContainer.js +29 -28
- package/dist/components/Notification.d.ts +9 -5
- package/dist/components/Notification.js +187 -190
- package/dist/components/Popover.d.ts +66 -38
- package/dist/components/Popover.js +69 -158
- package/dist/components/Progress.d.ts +4 -5
- package/dist/components/Progress.js +77 -65
- package/dist/components/RadioGroup.d.ts +24 -30
- package/dist/components/RadioGroup.js +94 -90
- package/dist/components/Select.d.ts +74 -62
- package/dist/components/Select.js +154 -241
- package/dist/components/Separator.d.ts +23 -13
- package/dist/components/Separator.js +36 -29
- package/dist/components/Skeleton.d.ts +7 -8
- package/dist/components/Skeleton.js +61 -74
- package/dist/components/StyledText.context.d.ts +2 -6
- package/dist/components/StyledText.context.js +0 -3
- package/dist/components/StyledText.d.ts +7 -29
- package/dist/components/StyledText.js +29 -92
- package/dist/components/Switch.d.ts +6 -18
- package/dist/components/Switch.js +106 -112
- package/dist/components/Tabs.d.ts +16 -26
- package/dist/components/Tabs.js +91 -189
- package/dist/components/TextInput.d.ts +19 -6
- package/dist/components/TextInput.js +198 -226
- package/dist/components/Toggle.d.ts +41 -23
- package/dist/components/Toggle.js +98 -84
- package/dist/components/ToggleGroup.d.ts +29 -37
- package/dist/components/ToggleGroup.js +108 -113
- package/dist/components/Tooltip.d.ts +111 -41
- package/dist/components/Tooltip.js +118 -156
- package/dist/components/UIProvider.d.ts +1 -1
- package/dist/components/UIProvider.js +1 -1
- package/dist/components/keyboardFocusRegistry.d.ts +15 -1
- package/dist/components/keyboardFocusRegistry.js +39 -2
- package/dist/hooks/useTheme.d.ts +10 -34
- package/dist/hooks/useTheme.js +8 -20
- package/dist/lib/index.d.ts +0 -2
- package/dist/lib/index.js +0 -2
- package/package.json +29 -11
- package/dist/lib/portal.d.ts +0 -16
- package/dist/lib/portal.js +0 -84
- package/dist/lib/styles.d.ts +0 -32
- package/dist/lib/styles.js +0 -91
- package/dist/lib/useAnchoredPosition.d.ts +0 -57
- package/dist/lib/useAnchoredPosition.js +0 -120
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,37 @@
|
|
|
3
3
|
All notable changes to `@mrmeg/expo-ui` are documented here. This project
|
|
4
4
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
5
|
|
|
6
|
+
## [0.13.0]
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **`Drawer.Header` now supports a compact app-brand layout.** Use the new
|
|
11
|
+
`icon`, `title`, and `action` slots for a leading brand icon and title with a
|
|
12
|
+
trailing close or `Drawer.ToggleCollapse` control. Existing child-based
|
|
13
|
+
headers remain supported.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Expanded peer compatibility through Expo 57, React Native 0.86, and their
|
|
18
|
+
matching `@expo/ui` and gesture-handler releases.
|
|
19
|
+
|
|
20
|
+
## [0.12.1]
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **`BottomSheet` now dismisses the keyboard when tapping outside a focused
|
|
25
|
+
field on Android.** The native sheet hosts its content in a separate window
|
|
26
|
+
(via `@expo/ui`'s `RNHostView`), outside the app's `KeyboardProvider` — so the
|
|
27
|
+
app-wide tap-away never reached sheet content, and `useKeyboardState()` /
|
|
28
|
+
`KeyboardController.dismiss()` are blind to that window. iOS papered over this
|
|
29
|
+
(SwiftUI resigns first-responder on outside taps for free); Compose did not,
|
|
30
|
+
leaving a focused field stuck open. `BottomSheet.Content` now mounts a
|
|
31
|
+
transparent dismiss overlay while a field is focused, detecting focus via the
|
|
32
|
+
window-independent `keyboardFocusRegistry` and resigning the field through its
|
|
33
|
+
own native `blur()` ref. The registry gained `subscribeKeyboardFocus`,
|
|
34
|
+
`hasKeyboardFocusedInput`, and `dismissKeyboardFocusedInput`, and `TextInput`
|
|
35
|
+
now registers a `blur` handle on focus.
|
|
36
|
+
|
|
6
37
|
## [0.12.0]
|
|
7
38
|
|
|
8
39
|
### Added
|
package/LLM_USAGE.md
CHANGED
|
@@ -212,6 +212,7 @@ Use this table before creating a new app-local primitive.
|
|
|
212
212
|
- Use `EmptyState` for no-data or recoverable error regions.
|
|
213
213
|
- Use `Skeleton` for loading content with stable layout.
|
|
214
214
|
- Use `Progress` for real progress or indeterminate long-running work.
|
|
215
|
+
- Use `Drawer.Header` with `icon`, `title`, and `action` for a compact app-brand row; place `Drawer.ToggleCollapse` in `action` for a trailing rail control.
|
|
215
216
|
|
|
216
217
|
## Minimal Examples
|
|
217
218
|
|
|
@@ -249,6 +250,20 @@ import { Button, Switch, TextInput } from "@mrmeg/expo-ui/components";
|
|
|
249
250
|
</Button>
|
|
250
251
|
```
|
|
251
252
|
|
|
253
|
+
```tsx
|
|
254
|
+
import { Drawer, Icon } from "@mrmeg/expo-ui/components";
|
|
255
|
+
|
|
256
|
+
<Drawer.Header
|
|
257
|
+
icon={<Icon name="hexagon" color="accent" />}
|
|
258
|
+
title="Acme"
|
|
259
|
+
action={
|
|
260
|
+
<Drawer.ToggleCollapse>
|
|
261
|
+
<Icon name="sidebar" decorative />
|
|
262
|
+
</Drawer.ToggleCollapse>
|
|
263
|
+
}
|
|
264
|
+
/>
|
|
265
|
+
```
|
|
266
|
+
|
|
252
267
|
```tsx
|
|
253
268
|
import { EmptyState, Progress, SkeletonCard } from "@mrmeg/expo-ui/components";
|
|
254
269
|
import { notify } from "@mrmeg/expo-ui/state";
|
package/README.md
CHANGED
|
@@ -254,7 +254,7 @@ Most compound components support both direct named imports and dot notation on t
|
|
|
254
254
|
| `Card` | `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter` |
|
|
255
255
|
| `Collapsible` | `CollapsibleTrigger`, `CollapsibleContent` |
|
|
256
256
|
| `Dialog` | `DialogTrigger`, `DialogContent`, `DialogHeader`, `DialogFooter`, `DialogTitle`, `DialogDescription`, `DialogClose` |
|
|
257
|
-
| `Drawer` | `DrawerTrigger`, `DrawerContent`, `DrawerHeader`, `DrawerBody`, `DrawerFooter`, `DrawerClose` |
|
|
257
|
+
| `Drawer` | `DrawerTrigger`, `DrawerContent`, `DrawerHeader`, `DrawerBody`, `DrawerFooter`, `DrawerClose`, `DrawerToggleCollapse` |
|
|
258
258
|
| `DropdownMenu` | `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuGroup`, `DropdownMenuItem`, `DropdownMenuCheckboxItem`, `DropdownMenuRadioGroup`, `DropdownMenuRadioItem`, `DropdownMenuLabel`, `DropdownMenuSeparator`, `DropdownMenuShortcut`, `DropdownMenuPortal`, `DropdownMenuSub`, `DropdownMenuSubTrigger`, `DropdownMenuSubContent` |
|
|
259
259
|
| `Popover` | `PopoverTrigger`, `PopoverContent`, `PopoverHeader`, `PopoverBody`, `PopoverFooter` |
|
|
260
260
|
| `RadioGroup` | `RadioGroupItem` |
|
|
@@ -273,6 +273,20 @@ Use `Button.preset`, not `variant`. `default` is the neutral primary action, `se
|
|
|
273
273
|
|
|
274
274
|
Use `StyledText` or its aliases instead of raw `Text` whenever the text is part of app UI. Use `TextInput` for labeled fields because it already owns label, helper text, error text, clear buttons, password visibility, numeric filtering, and left/right elements.
|
|
275
275
|
|
|
276
|
+
`Drawer.Header` accepts `icon`, `title`, and `action` slots for a compact app-brand row. Pass a string title to use the package typography, and place `Drawer.ToggleCollapse` in `action` for a trailing rail control:
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
<Drawer.Header
|
|
280
|
+
icon={<Icon name="hexagon" color="accent" />}
|
|
281
|
+
title="Acme"
|
|
282
|
+
action={
|
|
283
|
+
<Drawer.ToggleCollapse>
|
|
284
|
+
<Icon name="sidebar" decorative />
|
|
285
|
+
</Drawer.ToggleCollapse>
|
|
286
|
+
}
|
|
287
|
+
/>
|
|
288
|
+
```
|
|
289
|
+
|
|
276
290
|
Mount `UIProvider` once near the root before using `Dialog`, `AlertDialog`, `BottomSheet`, `Drawer`, `DropdownMenu`, `Popover`, `SelectContent`, `Tooltip`, or package notifications. On native, `UIProvider` also wraps app content in the package keyboard-avoiding root by default, so ordinary screens and fixed footers stay above the soft keyboard without repeated app-local wrappers; pass `keyboardAvoiding={false}` to opt out, or use `KeyboardAvoidingView` directly for a subtree with custom behavior. Web skips the root keyboard wrapper unless `keyboardAvoiding` is explicitly enabled. `BottomSheet.Content` listens to React Native keyboard events when `avoidKeyboard` is enabled; it defaults to `true` and can be disabled per sheet. Trigger transient feedback with `notify`.
|
|
277
291
|
|
|
278
292
|
Use `Skeleton` components for loading content with stable dimensions, `EmptyState` for no-data/recoverable errors, `Alert` for blocking confirm/alert dialogs, and `Notification` for transient global feedback.
|
|
@@ -1,33 +1,28 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
type BaseAccordionRootProps = {
|
|
1
|
+
import { View, ViewStyle } from "react-native";
|
|
2
|
+
import * as AccordionPrimitive from "@rn-primitives/accordion";
|
|
3
|
+
type BaseAccordionRootProps = Omit<React.ComponentProps<typeof View>, "style"> & React.RefAttributes<AccordionPrimitive.RootRef> & {
|
|
4
4
|
disabled?: boolean;
|
|
5
|
-
/** Single-type only: allow closing the open item by re-pressing it. */
|
|
6
5
|
collapsible?: boolean;
|
|
7
|
-
/** Platform: WEB ONLY (kept for API compat; unused). */
|
|
8
6
|
dir?: "ltr" | "rtl";
|
|
9
|
-
/** Platform: WEB ONLY (kept for API compat; unused). */
|
|
10
7
|
orientation?: "vertical" | "horizontal";
|
|
11
|
-
style?:
|
|
12
|
-
children?: React.ReactNode;
|
|
8
|
+
style?: ViewStyle;
|
|
13
9
|
};
|
|
14
|
-
type
|
|
10
|
+
type WebSingleAccordionRootProps = BaseAccordionRootProps & {
|
|
15
11
|
type: "single";
|
|
16
12
|
defaultValue?: string;
|
|
17
13
|
value?: string;
|
|
18
14
|
onValueChange?: (value: string | undefined) => void;
|
|
19
15
|
};
|
|
20
|
-
type
|
|
16
|
+
type WebMultipleAccordionRootProps = BaseAccordionRootProps & {
|
|
21
17
|
type: "multiple";
|
|
22
18
|
defaultValue?: string[];
|
|
23
19
|
value?: string[];
|
|
24
20
|
onValueChange?: (value: string[]) => void;
|
|
25
21
|
};
|
|
26
|
-
type AccordionRootProps =
|
|
22
|
+
type AccordionRootProps = WebSingleAccordionRootProps | WebMultipleAccordionRootProps;
|
|
27
23
|
/**
|
|
28
|
-
* Accordion Root Component
|
|
29
|
-
* Container for accordion items with single or multiple open items
|
|
30
|
-
* controlled (`value`) and uncontrolled (`defaultValue`) usage.
|
|
24
|
+
* Accordion Root Component
|
|
25
|
+
* Container for accordion items with support for single or multiple open items
|
|
31
26
|
*
|
|
32
27
|
* Usage:
|
|
33
28
|
* <Accordion type="single" collapsible>
|
|
@@ -37,38 +32,23 @@ type AccordionRootProps = SingleAccordionRootProps | MultipleAccordionRootProps;
|
|
|
37
32
|
* </AccordionItem>
|
|
38
33
|
* </Accordion>
|
|
39
34
|
*/
|
|
40
|
-
declare function Accordion(props: AccordionRootProps):
|
|
41
|
-
export interface AccordionItemProps {
|
|
42
|
-
value: string;
|
|
43
|
-
disabled?: boolean;
|
|
44
|
-
children?: React.ReactNode;
|
|
45
|
-
style?: LooseStyle;
|
|
46
|
-
}
|
|
35
|
+
declare function Accordion({ children, style, ...props }: AccordionRootProps): import("react").JSX.Element;
|
|
47
36
|
/**
|
|
48
37
|
* Accordion Item Component
|
|
49
|
-
* Individual accordion item with
|
|
50
|
-
* with the Trigger (chevron) and Content via context.
|
|
38
|
+
* Individual accordion item with border styling
|
|
51
39
|
*/
|
|
52
|
-
declare function AccordionItem({ value,
|
|
53
|
-
export interface AccordionTriggerProps {
|
|
54
|
-
children?: React.ReactNode;
|
|
55
|
-
style?: LooseStyle;
|
|
56
|
-
}
|
|
40
|
+
declare function AccordionItem({ children, value, style: styleOverride, ...props }: AccordionPrimitive.ItemProps & React.RefAttributes<AccordionPrimitive.ItemRef>): import("react").JSX.Element;
|
|
57
41
|
/**
|
|
58
42
|
* Accordion Trigger Component
|
|
59
|
-
* Clickable header that expands/collapses the
|
|
60
|
-
*
|
|
43
|
+
* Clickable header that expands/collapses the content
|
|
44
|
+
* Includes animated chevron icon
|
|
61
45
|
*/
|
|
62
|
-
declare function AccordionTrigger({ children, style }:
|
|
63
|
-
export interface AccordionContentProps {
|
|
64
|
-
/** Keep content mounted (hidden) when collapsed instead of unmounting. */
|
|
65
|
-
forceMount?: boolean;
|
|
46
|
+
declare function AccordionTrigger({ children, style: styleOverride, ...props }: AccordionPrimitive.TriggerProps & {
|
|
66
47
|
children?: React.ReactNode;
|
|
67
|
-
|
|
68
|
-
}
|
|
48
|
+
} & React.RefAttributes<AccordionPrimitive.TriggerRef>): import("react").JSX.Element;
|
|
69
49
|
/**
|
|
70
50
|
* Accordion Content Component
|
|
71
|
-
* Expandable content area
|
|
51
|
+
* Expandable content area with animations
|
|
72
52
|
*/
|
|
73
|
-
declare function AccordionContent({
|
|
53
|
+
declare function AccordionContent({ children, style: styleOverride, ...props }: AccordionPrimitive.ContentProps & React.RefAttributes<AccordionPrimitive.ContentRef>): import("react").JSX.Element;
|
|
74
54
|
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
|
@@ -1,31 +1,62 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Animated, Platform, Pressable, View } from "react-native";
|
|
4
4
|
import { Icon } from "./Icon.js";
|
|
5
5
|
import { TextClassContext, TextSelectabilityContext } from "./StyledText.context";
|
|
6
6
|
import { useTheme } from "../hooks/useTheme.js";
|
|
7
7
|
import { useReducedMotion } from "../hooks/useReduceMotion.js";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
import { spacing } from "../constants/spacing.js";
|
|
9
|
+
import * as AccordionPrimitive from "@rn-primitives/accordion";
|
|
10
|
+
function normalizeSingleValue(value) {
|
|
11
|
+
return value ?? "";
|
|
12
|
+
}
|
|
13
|
+
function denormalizeSingleValue(value) {
|
|
14
|
+
return value === "" ? undefined : value;
|
|
15
|
+
}
|
|
16
|
+
function normalizeMultipleValue(value) {
|
|
17
|
+
return value ?? [];
|
|
18
|
+
}
|
|
19
|
+
function WebSingleAccordionRoot(props) {
|
|
20
|
+
const isControlled = Object.prototype.hasOwnProperty.call(props, "value");
|
|
21
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(() => normalizeSingleValue(props.defaultValue));
|
|
22
|
+
const normalizedValue = isControlled
|
|
23
|
+
? normalizeSingleValue(props.value)
|
|
24
|
+
: uncontrolledValue;
|
|
25
|
+
const handleValueChange = (nextValue) => {
|
|
26
|
+
const normalizedNextValue = normalizeSingleValue(nextValue);
|
|
27
|
+
if (!isControlled) {
|
|
28
|
+
setUncontrolledValue(normalizedNextValue);
|
|
29
|
+
}
|
|
30
|
+
props.onValueChange?.(denormalizeSingleValue(normalizedNextValue));
|
|
31
|
+
};
|
|
32
|
+
const { children, style, type: _type, defaultValue: _defaultValue, onValueChange: _onValueChange, value: _value, ...rootProps } = props;
|
|
33
|
+
return (_jsx(AccordionPrimitive.Root, { ...rootProps, type: "single", value: normalizedValue, onValueChange: handleValueChange, asChild: false, children: _jsx(View, { style: style, children: children }) }));
|
|
16
34
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
35
|
+
function WebMultipleAccordionRoot(props) {
|
|
36
|
+
const isControlled = Object.prototype.hasOwnProperty.call(props, "value");
|
|
37
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(() => normalizeMultipleValue(props.defaultValue));
|
|
38
|
+
const normalizedValue = isControlled
|
|
39
|
+
? normalizeMultipleValue(props.value)
|
|
40
|
+
: uncontrolledValue;
|
|
41
|
+
const handleValueChange = (nextValue) => {
|
|
42
|
+
const normalizedNextValue = normalizeMultipleValue(nextValue);
|
|
43
|
+
if (!isControlled) {
|
|
44
|
+
setUncontrolledValue(normalizedNextValue);
|
|
45
|
+
}
|
|
46
|
+
props.onValueChange?.(normalizedNextValue);
|
|
47
|
+
};
|
|
48
|
+
const { children, style, type: _type, defaultValue: _defaultValue, onValueChange: _onValueChange, value: _value, ...rootProps } = props;
|
|
49
|
+
return (_jsx(AccordionPrimitive.Root, { ...rootProps, type: "multiple", value: normalizedValue, onValueChange: handleValueChange, asChild: false, children: _jsx(View, { style: style, children: children }) }));
|
|
50
|
+
}
|
|
51
|
+
function WebAccordionRoot(props) {
|
|
52
|
+
if (props.type === "multiple") {
|
|
53
|
+
return _jsx(WebMultipleAccordionRoot, { ...props });
|
|
22
54
|
}
|
|
23
|
-
return
|
|
55
|
+
return _jsx(WebSingleAccordionRoot, { ...props });
|
|
24
56
|
}
|
|
25
57
|
/**
|
|
26
|
-
* Accordion Root Component
|
|
27
|
-
* Container for accordion items with single or multiple open items
|
|
28
|
-
* controlled (`value`) and uncontrolled (`defaultValue`) usage.
|
|
58
|
+
* Accordion Root Component
|
|
59
|
+
* Container for accordion items with support for single or multiple open items
|
|
29
60
|
*
|
|
30
61
|
* Usage:
|
|
31
62
|
* <Accordion type="single" collapsible>
|
|
@@ -35,139 +66,92 @@ function useAccordionItemContext() {
|
|
|
35
66
|
* </AccordionItem>
|
|
36
67
|
* </Accordion>
|
|
37
68
|
*/
|
|
38
|
-
function Accordion(props) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const [uncontrolled, setUncontrolled] = useState(() => {
|
|
44
|
-
if (props.type === "multiple")
|
|
45
|
-
return props.defaultValue ?? [];
|
|
46
|
-
return props.defaultValue ? [props.defaultValue] : [];
|
|
47
|
-
});
|
|
48
|
-
const expandedValues = useMemo(() => {
|
|
49
|
-
if (!isControlled)
|
|
50
|
-
return uncontrolled;
|
|
51
|
-
if (props.type === "multiple")
|
|
52
|
-
return props.value ?? [];
|
|
53
|
-
return props.value ? [props.value] : [];
|
|
54
|
-
}, [isControlled, uncontrolled, props]);
|
|
55
|
-
const isExpanded = useCallback((value) => expandedValues.includes(value), [expandedValues]);
|
|
56
|
-
const toggle = useCallback((value) => {
|
|
57
|
-
if (disabled)
|
|
58
|
-
return;
|
|
59
|
-
if (props.type === "multiple") {
|
|
60
|
-
const next = expandedValues.includes(value)
|
|
61
|
-
? expandedValues.filter((v) => v !== value)
|
|
62
|
-
: [...expandedValues, value];
|
|
63
|
-
if (!isControlled)
|
|
64
|
-
setUncontrolled(next);
|
|
65
|
-
props.onValueChange?.(next);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
// Single: open the pressed item; collapsible allows re-press to close.
|
|
69
|
-
const isOpen = expandedValues.includes(value);
|
|
70
|
-
const collapsible = props.collapsible ?? false;
|
|
71
|
-
const nextValue = isOpen && collapsible ? undefined : value;
|
|
72
|
-
if (!isControlled)
|
|
73
|
-
setUncontrolled(nextValue ? [nextValue] : []);
|
|
74
|
-
props.onValueChange?.(nextValue);
|
|
75
|
-
}, [disabled, expandedValues, isControlled, props]);
|
|
76
|
-
const contextValue = useMemo(() => ({ isExpanded, toggle, disabled }), [isExpanded, toggle, disabled]);
|
|
77
|
-
return (_jsx(AccordionContext.Provider, { value: contextValue, children: _jsx(html.div, { style: sanitizeWebStyle(style), children: children }) }));
|
|
69
|
+
function Accordion({ children, style, ...props }) {
|
|
70
|
+
if (Platform.OS === "web") {
|
|
71
|
+
return (_jsx(WebAccordionRoot, { ...props, style: style, children: children }));
|
|
72
|
+
}
|
|
73
|
+
return (_jsx(AccordionPrimitive.Root, { ...props, asChild: true, children: _jsx(View, { style: style, children: children }) }));
|
|
78
74
|
}
|
|
79
|
-
const itemStyles = css.create({
|
|
80
|
-
base: (borderBottomColor) => ({
|
|
81
|
-
borderBottomWidth: 1,
|
|
82
|
-
borderBottomStyle: "solid",
|
|
83
|
-
borderBottomColor,
|
|
84
|
-
overflow: "hidden",
|
|
85
|
-
}),
|
|
86
|
-
});
|
|
87
75
|
/**
|
|
88
76
|
* Accordion Item Component
|
|
89
|
-
* Individual accordion item with
|
|
90
|
-
* with the Trigger (chevron) and Content via context.
|
|
77
|
+
* Individual accordion item with border styling
|
|
91
78
|
*/
|
|
92
|
-
function AccordionItem({ value,
|
|
79
|
+
function AccordionItem({ children, value, style: styleOverride, ...props }) {
|
|
93
80
|
const { theme } = useTheme();
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
81
|
+
return (_jsx(AccordionPrimitive.Item, { value: value, asChild: true, ...props, children: _jsx(View, { style: [
|
|
82
|
+
{
|
|
83
|
+
borderBottomWidth: 1,
|
|
84
|
+
borderBottomColor: theme.colors.border,
|
|
85
|
+
overflow: "hidden",
|
|
86
|
+
},
|
|
87
|
+
// Spread array styles from primitives to prevent nested arrays on web
|
|
88
|
+
...(styleOverride && typeof styleOverride !== "function"
|
|
89
|
+
? (Array.isArray(styleOverride) ? styleOverride : [styleOverride])
|
|
90
|
+
: []),
|
|
103
91
|
], children: children }) }));
|
|
104
92
|
}
|
|
105
|
-
const
|
|
106
|
-
// gap (16) = spacing.md, borderRadius (6) = spacing.radiusMd, paddingVertical (16) = spacing.md.
|
|
107
|
-
base: {
|
|
108
|
-
display: "flex",
|
|
109
|
-
flexDirection: "row",
|
|
110
|
-
alignItems: "center",
|
|
111
|
-
justifyContent: "space-between",
|
|
112
|
-
gap: 16,
|
|
113
|
-
borderRadius: 6,
|
|
114
|
-
paddingTop: 16,
|
|
115
|
-
paddingBottom: 16,
|
|
116
|
-
margin: 0,
|
|
117
|
-
borderWidth: 0,
|
|
118
|
-
backgroundColor: "transparent",
|
|
119
|
-
appearance: "none",
|
|
120
|
-
cursor: "pointer",
|
|
121
|
-
userSelect: "none",
|
|
122
|
-
textAlign: "inherit",
|
|
123
|
-
width: "100%",
|
|
124
|
-
},
|
|
125
|
-
disabled: { cursor: "not-allowed" },
|
|
126
|
-
// Chevron rotates 0deg (closed) → 180deg (open). Transition gated on motion.
|
|
127
|
-
chevron: (rotate, animated) => ({
|
|
128
|
-
display: "flex",
|
|
129
|
-
transform: rotate,
|
|
130
|
-
transitionProperty: animated ? "transform" : "none",
|
|
131
|
-
transitionDuration: animated ? "0.2s" : "0s",
|
|
132
|
-
transitionTimingFunction: "ease",
|
|
133
|
-
}),
|
|
134
|
-
});
|
|
93
|
+
const Trigger = Platform.OS === "web" ? View : Pressable;
|
|
135
94
|
/**
|
|
136
95
|
* Accordion Trigger Component
|
|
137
|
-
* Clickable header that expands/collapses the
|
|
138
|
-
*
|
|
96
|
+
* Clickable header that expands/collapses the content
|
|
97
|
+
* Includes animated chevron icon
|
|
139
98
|
*/
|
|
140
|
-
function AccordionTrigger({ children, style }) {
|
|
99
|
+
function AccordionTrigger({ children, style: styleOverride, ...props }) {
|
|
141
100
|
const { theme } = useTheme();
|
|
142
101
|
const reduceMotion = useReducedMotion();
|
|
143
|
-
const {
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
102
|
+
const { isExpanded } = AccordionPrimitive.useItemContext();
|
|
103
|
+
const rotation = useRef(new Animated.Value(isExpanded ? 1 : 0)).current;
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
const target = isExpanded ? 1 : 0;
|
|
106
|
+
Animated.timing(rotation, {
|
|
107
|
+
toValue: target,
|
|
108
|
+
duration: reduceMotion ? 0 : isExpanded ? 200 : 150,
|
|
109
|
+
useNativeDriver: true,
|
|
110
|
+
}).start();
|
|
111
|
+
}, [isExpanded, reduceMotion, rotation]);
|
|
112
|
+
const chevronStyle = {
|
|
113
|
+
transform: [
|
|
114
|
+
{
|
|
115
|
+
rotate: rotation.interpolate({
|
|
116
|
+
inputRange: [0, 1],
|
|
117
|
+
outputRange: ["0deg", "180deg"],
|
|
118
|
+
}),
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
return (_jsx(TextClassContext.Provider, { value: "", children: _jsx(TextSelectabilityContext.Provider, { value: false, children: _jsx(AccordionPrimitive.Header, { children: _jsx(AccordionPrimitive.Trigger, { ...props, asChild: true, children: _jsxs(Trigger, { style: [
|
|
123
|
+
{
|
|
124
|
+
flexDirection: "row",
|
|
125
|
+
alignItems: "center",
|
|
126
|
+
justifyContent: "space-between",
|
|
127
|
+
gap: spacing.md,
|
|
128
|
+
borderRadius: spacing.radiusMd,
|
|
129
|
+
paddingVertical: spacing.md,
|
|
130
|
+
...(Platform.OS === "web" && {
|
|
131
|
+
cursor: "pointer",
|
|
132
|
+
userSelect: "none",
|
|
133
|
+
}),
|
|
134
|
+
},
|
|
135
|
+
// Spread array styles from primitives to prevent nested arrays on web
|
|
136
|
+
...(styleOverride && typeof styleOverride !== "function"
|
|
137
|
+
? (Array.isArray(styleOverride) ? styleOverride : [styleOverride])
|
|
138
|
+
: []),
|
|
139
|
+
], children: [_jsx(_Fragment, { children: children }), _jsx(Animated.View, { style: chevronStyle, children: _jsx(Icon, { name: "chevron-down", size: 16, color: theme.colors.textDim, decorative: true }) })] }) }) }) }) }));
|
|
150
140
|
}
|
|
151
|
-
const contentStyles = css.create({
|
|
152
|
-
// paddingBottom (8) = spacing.sm.
|
|
153
|
-
base: {
|
|
154
|
-
paddingBottom: 8,
|
|
155
|
-
overflow: "hidden",
|
|
156
|
-
},
|
|
157
|
-
hidden: { display: "none" },
|
|
158
|
-
});
|
|
159
141
|
/**
|
|
160
142
|
* Accordion Content Component
|
|
161
|
-
* Expandable content area
|
|
143
|
+
* Expandable content area with animations
|
|
162
144
|
*/
|
|
163
|
-
function AccordionContent({
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
145
|
+
function AccordionContent({ children, style: styleOverride, ...props }) {
|
|
146
|
+
return (_jsx(TextClassContext.Provider, { value: "", children: _jsx(AccordionPrimitive.Content, { ...props, children: _jsx(View, { style: [
|
|
147
|
+
{
|
|
148
|
+
paddingBottom: spacing.sm,
|
|
149
|
+
overflow: "hidden",
|
|
150
|
+
},
|
|
151
|
+
// Spread array styles from primitives to prevent nested arrays on web
|
|
152
|
+
...(styleOverride && typeof styleOverride !== "function"
|
|
153
|
+
? (Array.isArray(styleOverride) ? styleOverride : [styleOverride])
|
|
154
|
+
: []),
|
|
155
|
+
], children: children }) }) }));
|
|
172
156
|
}
|
|
173
157
|
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { ViewProps } from "react-native";
|
|
3
3
|
/**
|
|
4
4
|
* Animation type options
|
|
5
5
|
*/
|
|
6
6
|
export type AnimationType = "fade" | "fadeSlideUp" | "fadeSlideDown" | "scale";
|
|
7
|
-
|
|
7
|
+
interface AnimatedViewProps extends ViewProps {
|
|
8
8
|
children: React.ReactNode;
|
|
9
9
|
/**
|
|
10
10
|
* Type of animation to use
|
|
@@ -22,23 +22,34 @@ export interface AnimatedViewProps {
|
|
|
22
22
|
* @default 0
|
|
23
23
|
*/
|
|
24
24
|
delay?: number;
|
|
25
|
-
/** Custom style override */
|
|
26
|
-
style?: LooseStyle;
|
|
27
|
-
/** Test identifier (forwarded as data-testid on web) */
|
|
28
|
-
testID?: string;
|
|
29
25
|
}
|
|
30
26
|
/**
|
|
31
27
|
* Cross-Platform Animated View Component
|
|
28
|
+
* Uses React Native Animated for lightweight cross-platform animations
|
|
32
29
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
30
|
+
* Features:
|
|
31
|
+
* - Multiple animation types (fade, fadeSlideUp, fadeSlideDown, scale)
|
|
32
|
+
* - Configurable enter duration
|
|
33
|
+
* - Optional delay for staggered animations
|
|
34
|
+
* - Respects reduced motion accessibility preference
|
|
37
35
|
*
|
|
38
36
|
* Usage:
|
|
39
37
|
* ```tsx
|
|
40
|
-
*
|
|
41
|
-
* <AnimatedView
|
|
38
|
+
* // Simple fade
|
|
39
|
+
* <AnimatedView>
|
|
40
|
+
* {children}
|
|
41
|
+
* </AnimatedView>
|
|
42
|
+
*
|
|
43
|
+
* // Fade with slide up
|
|
44
|
+
* <AnimatedView type="fadeSlideUp">
|
|
45
|
+
* {children}
|
|
46
|
+
* </AnimatedView>
|
|
47
|
+
*
|
|
48
|
+
* // With delay (for staggered lists)
|
|
49
|
+
* <AnimatedView type="fadeSlideUp" delay={100}>
|
|
50
|
+
* {children}
|
|
51
|
+
* </AnimatedView>
|
|
42
52
|
* ```
|
|
43
53
|
*/
|
|
44
|
-
export declare function AnimatedView({ children, type, enterDuration, delay, style,
|
|
54
|
+
export declare function AnimatedView({ children, type, enterDuration, delay, style, ...props }: AnimatedViewProps): React.JSX.Element;
|
|
55
|
+
export {};
|
|
@@ -1,80 +1,39 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { sanitizeWebStyle } from "../lib/styles.js";
|
|
5
|
-
// Entrance keyframes. Slide distance (8px) and initial scale (0.95) mirror the
|
|
6
|
-
// previous useStaggeredEntrance defaults. css.keyframes must be defined inline
|
|
7
|
-
// and unconditionally in the same file as the css.create that references them
|
|
8
|
-
// (StyleX can't fold a cross-module or ternary-gated keyframe name). On native
|
|
9
|
-
// css.keyframes is a no-op and animationName has no effect, so views appear
|
|
10
|
-
// instantly — documented animation fidelity gap (no Reanimated in the package).
|
|
11
|
-
const fadeFrames = css.keyframes({
|
|
12
|
-
"0%": { opacity: 0 },
|
|
13
|
-
"100%": { opacity: 1 },
|
|
14
|
-
});
|
|
15
|
-
const fadeSlideUpFrames = css.keyframes({
|
|
16
|
-
"0%": { opacity: 0, transform: "translateY(8px)" },
|
|
17
|
-
"100%": { opacity: 1, transform: "translateY(0)" },
|
|
18
|
-
});
|
|
19
|
-
const fadeSlideDownFrames = css.keyframes({
|
|
20
|
-
"0%": { opacity: 0, transform: "translateY(-8px)" },
|
|
21
|
-
"100%": { opacity: 1, transform: "translateY(0)" },
|
|
22
|
-
});
|
|
23
|
-
const scaleFrames = css.keyframes({
|
|
24
|
-
"0%": { opacity: 0, transform: "scale(0.95)" },
|
|
25
|
-
"100%": { opacity: 1, transform: "scale(1)" },
|
|
26
|
-
});
|
|
27
|
-
// Duration/delay are runtime props, so they flow in as function-rule args.
|
|
28
|
-
// animationFillMode:"both" holds the 0% state during the delay (so staggered
|
|
29
|
-
// items stay hidden until their turn) and the 100% state after completion.
|
|
30
|
-
const styles = css.create({
|
|
31
|
-
fade: (duration, delay) => ({
|
|
32
|
-
animationName: fadeFrames,
|
|
33
|
-
animationDuration: duration,
|
|
34
|
-
animationDelay: delay,
|
|
35
|
-
animationTimingFunction: "ease-out",
|
|
36
|
-
animationFillMode: "both",
|
|
37
|
-
}),
|
|
38
|
-
fadeSlideUp: (duration, delay) => ({
|
|
39
|
-
animationName: fadeSlideUpFrames,
|
|
40
|
-
animationDuration: duration,
|
|
41
|
-
animationDelay: delay,
|
|
42
|
-
animationTimingFunction: "ease-out",
|
|
43
|
-
animationFillMode: "both",
|
|
44
|
-
}),
|
|
45
|
-
fadeSlideDown: (duration, delay) => ({
|
|
46
|
-
animationName: fadeSlideDownFrames,
|
|
47
|
-
animationDuration: duration,
|
|
48
|
-
animationDelay: delay,
|
|
49
|
-
animationTimingFunction: "ease-out",
|
|
50
|
-
animationFillMode: "both",
|
|
51
|
-
}),
|
|
52
|
-
scale: (duration, delay) => ({
|
|
53
|
-
animationName: scaleFrames,
|
|
54
|
-
animationDuration: duration,
|
|
55
|
-
animationDelay: delay,
|
|
56
|
-
animationTimingFunction: "ease-out",
|
|
57
|
-
animationFillMode: "both",
|
|
58
|
-
}),
|
|
59
|
-
});
|
|
2
|
+
import { Animated } from "react-native";
|
|
3
|
+
import { useStaggeredEntrance } from "../hooks/useStaggeredEntrance.js";
|
|
60
4
|
/**
|
|
61
5
|
* Cross-Platform Animated View Component
|
|
6
|
+
* Uses React Native Animated for lightweight cross-platform animations
|
|
62
7
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Multiple animation types (fade, fadeSlideUp, fadeSlideDown, scale)
|
|
10
|
+
* - Configurable enter duration
|
|
11
|
+
* - Optional delay for staggered animations
|
|
12
|
+
* - Respects reduced motion accessibility preference
|
|
67
13
|
*
|
|
68
14
|
* Usage:
|
|
69
15
|
* ```tsx
|
|
70
|
-
*
|
|
71
|
-
* <AnimatedView
|
|
16
|
+
* // Simple fade
|
|
17
|
+
* <AnimatedView>
|
|
18
|
+
* {children}
|
|
19
|
+
* </AnimatedView>
|
|
20
|
+
*
|
|
21
|
+
* // Fade with slide up
|
|
22
|
+
* <AnimatedView type="fadeSlideUp">
|
|
23
|
+
* {children}
|
|
24
|
+
* </AnimatedView>
|
|
25
|
+
*
|
|
26
|
+
* // With delay (for staggered lists)
|
|
27
|
+
* <AnimatedView type="fadeSlideUp" delay={100}>
|
|
28
|
+
* {children}
|
|
29
|
+
* </AnimatedView>
|
|
72
30
|
* ```
|
|
73
31
|
*/
|
|
74
|
-
export function AnimatedView({ children, type = "fade", enterDuration = 200, delay = 0, style,
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
32
|
+
export function AnimatedView({ children, type = "fade", enterDuration = 200, delay = 0, style, ...props }) {
|
|
33
|
+
const entranceStyle = useStaggeredEntrance({
|
|
34
|
+
type,
|
|
35
|
+
delay,
|
|
36
|
+
duration: enterDuration,
|
|
37
|
+
});
|
|
38
|
+
return (_jsx(Animated.View, { style: [style, entranceStyle], ...props, children: children }));
|
|
80
39
|
}
|