@mrmeg/expo-ui 0.13.0-rsd.0 → 0.14.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 +42 -0
- package/LLM_USAGE.md +19 -0
- package/README.md +22 -4
- 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 +28 -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,48 @@
|
|
|
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.14.0]
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- Aligned the declared compatibility floor with the package's `@expo/ui`
|
|
11
|
+
requirement: Expo 56+, React Native 0.85+, and their matching Expo modules.
|
|
12
|
+
- Kept Expo 57 and React Native 0.86 support in the same compatibility window.
|
|
13
|
+
- Removed the duplicate workspace dev copy of
|
|
14
|
+
`react-native-keyboard-controller`; the root consumer now supplies the peer
|
|
15
|
+
consistently for tests, builds, and native autolinking.
|
|
16
|
+
|
|
17
|
+
## [0.13.0]
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **`Drawer.Header` now supports a compact app-brand layout.** Use the new
|
|
22
|
+
`icon`, `title`, and `action` slots for a leading brand icon and title with a
|
|
23
|
+
trailing close or `Drawer.ToggleCollapse` control. Existing child-based
|
|
24
|
+
headers remain supported.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Expanded peer compatibility through Expo 57, React Native 0.86, and their
|
|
29
|
+
matching `@expo/ui` and gesture-handler releases.
|
|
30
|
+
|
|
31
|
+
## [0.12.1]
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **`BottomSheet` now dismisses the keyboard when tapping outside a focused
|
|
36
|
+
field on Android.** The native sheet hosts its content in a separate window
|
|
37
|
+
(via `@expo/ui`'s `RNHostView`), outside the app's `KeyboardProvider` — so the
|
|
38
|
+
app-wide tap-away never reached sheet content, and `useKeyboardState()` /
|
|
39
|
+
`KeyboardController.dismiss()` are blind to that window. iOS papered over this
|
|
40
|
+
(SwiftUI resigns first-responder on outside taps for free); Compose did not,
|
|
41
|
+
leaving a focused field stuck open. `BottomSheet.Content` now mounts a
|
|
42
|
+
transparent dismiss overlay while a field is focused, detecting focus via the
|
|
43
|
+
window-independent `keyboardFocusRegistry` and resigning the field through its
|
|
44
|
+
own native `blur()` ref. The registry gained `subscribeKeyboardFocus`,
|
|
45
|
+
`hasKeyboardFocusedInput`, and `dismissKeyboardFocusedInput`, and `TextInput`
|
|
46
|
+
now registers a `blur` handle on focus.
|
|
47
|
+
|
|
6
48
|
## [0.12.0]
|
|
7
49
|
|
|
8
50
|
### Added
|
package/LLM_USAGE.md
CHANGED
|
@@ -29,6 +29,10 @@ Use only exported package paths: root, `components`, `components/*`,
|
|
|
29
29
|
`constants`, `constants/*`, `hooks`, `hooks/*`, `state`, and `lib`. Do not
|
|
30
30
|
import from `@mrmeg/expo-ui/dist/*` or from a source checkout path.
|
|
31
31
|
|
|
32
|
+
Supported hosts are Expo 56–57, React 19.2, React Native 0.85–0.86, and React
|
|
33
|
+
Native Web 0.21. Install the peer versions recommended by the consuming app's
|
|
34
|
+
Expo SDK.
|
|
35
|
+
|
|
32
36
|
## Required App Setup
|
|
33
37
|
|
|
34
38
|
Call `useResources()` once near the Expo app root. Mount `UIProvider` once
|
|
@@ -212,6 +216,7 @@ Use this table before creating a new app-local primitive.
|
|
|
212
216
|
- Use `EmptyState` for no-data or recoverable error regions.
|
|
213
217
|
- Use `Skeleton` for loading content with stable layout.
|
|
214
218
|
- Use `Progress` for real progress or indeterminate long-running work.
|
|
219
|
+
- 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
220
|
|
|
216
221
|
## Minimal Examples
|
|
217
222
|
|
|
@@ -249,6 +254,20 @@ import { Button, Switch, TextInput } from "@mrmeg/expo-ui/components";
|
|
|
249
254
|
</Button>
|
|
250
255
|
```
|
|
251
256
|
|
|
257
|
+
```tsx
|
|
258
|
+
import { Drawer, Icon } from "@mrmeg/expo-ui/components";
|
|
259
|
+
|
|
260
|
+
<Drawer.Header
|
|
261
|
+
icon={<Icon name="hexagon" color="accent" />}
|
|
262
|
+
title="Acme"
|
|
263
|
+
action={
|
|
264
|
+
<Drawer.ToggleCollapse>
|
|
265
|
+
<Icon name="sidebar" decorative />
|
|
266
|
+
</Drawer.ToggleCollapse>
|
|
267
|
+
}
|
|
268
|
+
/>
|
|
269
|
+
```
|
|
270
|
+
|
|
252
271
|
```tsx
|
|
253
272
|
import { EmptyState, Progress, SkeletonCard } from "@mrmeg/expo-ui/components";
|
|
254
273
|
import { notify } from "@mrmeg/expo-ui/state";
|
package/README.md
CHANGED
|
@@ -28,9 +28,9 @@ bun add @mrmeg/expo-ui
|
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
Consumers must also install the native and Expo peer dependencies listed in
|
|
31
|
-
`package.json`. The tested
|
|
32
|
-
Native 0.85, and React Native Web 0.21. UI animations and
|
|
33
|
-
sheet offsets use React Native Animated by default.
|
|
31
|
+
`package.json`. The tested compatibility window is Expo SDK 56–57 with React
|
|
32
|
+
19.2, React Native 0.85–0.86, and React Native Web 0.21. UI animations and
|
|
33
|
+
keyboard-aware sheet offsets use React Native Animated by default.
|
|
34
34
|
`@rn-primitives/*` packages are managed by `@mrmeg/expo-ui` because they are
|
|
35
35
|
implementation details of the exported UI components. Native bottom sheet
|
|
36
36
|
keyboard avoidance uses React Native keyboard events. i18n setup is optional;
|
|
@@ -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.
|
|
@@ -414,6 +428,9 @@ The release command requires a clean working tree by default. Commit current
|
|
|
414
428
|
changes first, or pass `--allow-dirty` when you intentionally want to release
|
|
415
429
|
from uncommitted local changes.
|
|
416
430
|
|
|
431
|
+
Release validation includes `bun run packages:peer-check`; CI also installs and
|
|
432
|
+
exports packed consumers against Expo 56 and 57.
|
|
433
|
+
|
|
417
434
|
If npm login email is unavailable, publish through GitHub Actions trusted
|
|
418
435
|
publishing instead:
|
|
419
436
|
|
|
@@ -445,6 +462,7 @@ reruns do not bump again.
|
|
|
445
462
|
Manual package checks:
|
|
446
463
|
|
|
447
464
|
```sh
|
|
465
|
+
bun run packages:peer-check
|
|
448
466
|
bun run ui:typecheck
|
|
449
467
|
bun run ui:test
|
|
450
468
|
bun run ui:build
|
|
@@ -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 {};
|