@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.
Files changed (81) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/LLM_USAGE.md +19 -0
  3. package/README.md +22 -4
  4. package/dist/components/Accordion.d.ts +18 -38
  5. package/dist/components/Accordion.js +121 -137
  6. package/dist/components/AnimatedView.d.ts +24 -13
  7. package/dist/components/AnimatedView.js +29 -70
  8. package/dist/components/Badge.d.ts +2 -2
  9. package/dist/components/Badge.js +47 -45
  10. package/dist/components/BottomSheet.d.ts +73 -30
  11. package/dist/components/BottomSheet.js +225 -191
  12. package/dist/components/Button.d.ts +56 -43
  13. package/dist/components/Button.js +203 -189
  14. package/dist/components/Card.d.ts +10 -7
  15. package/dist/components/Card.js +105 -90
  16. package/dist/components/Checkbox.d.ts +8 -20
  17. package/dist/components/Checkbox.js +80 -77
  18. package/dist/components/Collapsible.d.ts +47 -47
  19. package/dist/components/Collapsible.js +29 -108
  20. package/dist/components/Dialog.d.ts +121 -80
  21. package/dist/components/Dialog.js +132 -225
  22. package/dist/components/DismissKeyboard.js +2 -33
  23. package/dist/components/Drawer.d.ts +83 -37
  24. package/dist/components/Drawer.js +512 -252
  25. package/dist/components/DropdownMenu.d.ts +113 -106
  26. package/dist/components/DropdownMenu.js +204 -350
  27. package/dist/components/EmptyState.d.ts +2 -2
  28. package/dist/components/EmptyState.js +34 -41
  29. package/dist/components/InputOTP.d.ts +53 -19
  30. package/dist/components/InputOTP.js +102 -81
  31. package/dist/components/KeyboardAvoidingView.d.ts +3 -6
  32. package/dist/components/KeyboardAvoidingView.js +5 -18
  33. package/dist/components/Label.d.ts +17 -12
  34. package/dist/components/Label.js +51 -38
  35. package/dist/components/MaxWidthContainer.d.ts +16 -5
  36. package/dist/components/MaxWidthContainer.js +29 -28
  37. package/dist/components/Notification.d.ts +9 -5
  38. package/dist/components/Notification.js +187 -190
  39. package/dist/components/Popover.d.ts +66 -38
  40. package/dist/components/Popover.js +69 -158
  41. package/dist/components/Progress.d.ts +4 -5
  42. package/dist/components/Progress.js +77 -65
  43. package/dist/components/RadioGroup.d.ts +24 -30
  44. package/dist/components/RadioGroup.js +94 -90
  45. package/dist/components/Select.d.ts +74 -62
  46. package/dist/components/Select.js +154 -241
  47. package/dist/components/Separator.d.ts +23 -13
  48. package/dist/components/Separator.js +36 -29
  49. package/dist/components/Skeleton.d.ts +7 -8
  50. package/dist/components/Skeleton.js +61 -74
  51. package/dist/components/StyledText.context.d.ts +2 -6
  52. package/dist/components/StyledText.context.js +0 -3
  53. package/dist/components/StyledText.d.ts +7 -29
  54. package/dist/components/StyledText.js +29 -92
  55. package/dist/components/Switch.d.ts +6 -18
  56. package/dist/components/Switch.js +106 -112
  57. package/dist/components/Tabs.d.ts +16 -26
  58. package/dist/components/Tabs.js +91 -189
  59. package/dist/components/TextInput.d.ts +19 -6
  60. package/dist/components/TextInput.js +198 -226
  61. package/dist/components/Toggle.d.ts +41 -23
  62. package/dist/components/Toggle.js +98 -84
  63. package/dist/components/ToggleGroup.d.ts +29 -37
  64. package/dist/components/ToggleGroup.js +108 -113
  65. package/dist/components/Tooltip.d.ts +111 -41
  66. package/dist/components/Tooltip.js +118 -156
  67. package/dist/components/UIProvider.d.ts +1 -1
  68. package/dist/components/UIProvider.js +1 -1
  69. package/dist/components/keyboardFocusRegistry.d.ts +15 -1
  70. package/dist/components/keyboardFocusRegistry.js +39 -2
  71. package/dist/hooks/useTheme.d.ts +10 -34
  72. package/dist/hooks/useTheme.js +8 -20
  73. package/dist/lib/index.d.ts +0 -2
  74. package/dist/lib/index.js +0 -2
  75. package/package.json +28 -11
  76. package/dist/lib/portal.d.ts +0 -16
  77. package/dist/lib/portal.js +0 -84
  78. package/dist/lib/styles.d.ts +0 -32
  79. package/dist/lib/styles.js +0 -91
  80. package/dist/lib/useAnchoredPosition.d.ts +0 -57
  81. 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 baseline is Expo SDK 56 with React 19.2, React
32
- Native 0.85, and React Native Web 0.21. UI animations and keyboard-aware
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 * as React from "react";
2
- import { type LooseStyle } from "../lib/styles";
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?: LooseStyle;
12
- children?: React.ReactNode;
8
+ style?: ViewStyle;
13
9
  };
14
- type SingleAccordionRootProps = BaseAccordionRootProps & {
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 MultipleAccordionRootProps = BaseAccordionRootProps & {
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 = SingleAccordionRootProps | MultipleAccordionRootProps;
22
+ type AccordionRootProps = WebSingleAccordionRootProps | WebMultipleAccordionRootProps;
27
23
  /**
28
- * Accordion Root Component (react-strict-dom)
29
- * Container for accordion items with single or multiple open items. Supports
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): React.JSX.Element;
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 a bottom border. Shares its expanded state
50
- * with the Trigger (chevron) and Content via context.
38
+ * Individual accordion item with border styling
51
39
  */
52
- declare function AccordionItem({ value, disabled: itemDisabled, children, style }: AccordionItemProps): React.JSX.Element;
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 item. Includes an animated
60
- * chevron that rotates with the expanded state.
43
+ * Clickable header that expands/collapses the content
44
+ * Includes animated chevron icon
61
45
  */
62
- declare function AccordionTrigger({ children, style }: AccordionTriggerProps): React.JSX.Element;
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
- style?: LooseStyle;
68
- }
48
+ } & React.RefAttributes<AccordionPrimitive.TriggerRef>): import("react").JSX.Element;
69
49
  /**
70
50
  * Accordion Content Component
71
- * Expandable content area. Unmounts when collapsed unless `forceMount` is set.
51
+ * Expandable content area with animations
72
52
  */
73
- declare function AccordionContent({ forceMount, children, style }: AccordionContentProps): React.JSX.Element | null;
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 { createContext, use, useCallback, useMemo, useState } from "react";
3
- import { css, html } from "react-strict-dom";
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 { sanitizeWebStyle } from "../lib/styles.js";
9
- const AccordionContext = createContext(null);
10
- function useAccordionContext() {
11
- const ctx = use(AccordionContext);
12
- if (!ctx) {
13
- throw new Error("Accordion compound components must be used within an <Accordion>");
14
- }
15
- return ctx;
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
- const AccordionItemContext = createContext(null);
18
- function useAccordionItemContext() {
19
- const ctx = use(AccordionItemContext);
20
- if (!ctx) {
21
- throw new Error("AccordionTrigger/AccordionContent must be used within an <AccordionItem>");
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 ctx;
55
+ return _jsx(WebSingleAccordionRoot, { ...props });
24
56
  }
25
57
  /**
26
- * Accordion Root Component (react-strict-dom)
27
- * Container for accordion items with single or multiple open items. Supports
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
- const { children, style, disabled = false } = props;
40
- const isControlled = props.value !== undefined;
41
- // Uncontrolled state — always stored as a string[] internally, projected to
42
- // single/multiple at the boundary.
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 a bottom border. Shares its expanded state
90
- * with the Trigger (chevron) and Content via context.
77
+ * Individual accordion item with border styling
91
78
  */
92
- function AccordionItem({ value, disabled: itemDisabled, children, style }) {
79
+ function AccordionItem({ children, value, style: styleOverride, ...props }) {
93
80
  const { theme } = useTheme();
94
- const { isExpanded, disabled: rootDisabled } = useAccordionContext();
95
- const itemContext = useMemo(() => ({
96
- value,
97
- isExpanded: isExpanded(value),
98
- disabled: !!itemDisabled || rootDisabled,
99
- }), [value, isExpanded, itemDisabled, rootDisabled]);
100
- return (_jsx(AccordionItemContext.Provider, { value: itemContext, children: _jsx(html.div, { style: [
101
- itemStyles.base(theme.colors.border),
102
- sanitizeWebStyle(style),
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 triggerStyles = css.create({
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 item. Includes an animated
138
- * chevron that rotates with the expanded state.
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 { toggle } = useAccordionContext();
144
- const { value, isExpanded, disabled } = useAccordionItemContext();
145
- return (_jsx(TextClassContext.Provider, { value: "", children: _jsx(TextSelectabilityContext.Provider, { value: false, children: _jsxs(html.button, { type: "button", "aria-expanded": isExpanded, "aria-disabled": disabled, disabled: disabled, onClick: () => !disabled && toggle(value), style: [
146
- triggerStyles.base,
147
- disabled ? triggerStyles.disabled : null,
148
- sanitizeWebStyle(style),
149
- ], children: [_jsx(_Fragment, { children: children }), _jsx(html.span, { style: triggerStyles.chevron(isExpanded ? "rotate(180deg)" : "rotate(0deg)", !reduceMotion), children: _jsx(Icon, { name: "chevron-down", size: 16, color: theme.colors.textDim, decorative: true }) })] }) }) }));
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. Unmounts when collapsed unless `forceMount` is set.
143
+ * Expandable content area with animations
162
144
  */
163
- function AccordionContent({ forceMount, children, style }) {
164
- const { isExpanded } = useAccordionItemContext();
165
- if (!isExpanded && !forceMount)
166
- return null;
167
- return (_jsx(TextClassContext.Provider, { value: "", children: _jsx(html.div, { style: [
168
- contentStyles.base,
169
- !isExpanded ? contentStyles.hidden : null,
170
- sanitizeWebStyle(style),
171
- ], children: children }) }));
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 { type LooseStyle } from "../lib/styles";
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
- export interface AnimatedViewProps {
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
- * Plays a one-shot entrance animation via CSS keyframes on web. Respects the
34
- * reduced-motion accessibility preference (renders statically when set). On
35
- * native the keyframes are a no-op, so children appear immediately — the
36
- * package ships no Reanimated, a documented animation gap.
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
- * <AnimatedView>{children}</AnimatedView>
41
- * <AnimatedView type="fadeSlideUp" delay={100}>{children}</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, testID, }: AnimatedViewProps): React.JSX.Element;
54
+ export declare function AnimatedView({ children, type, enterDuration, delay, style, ...props }: AnimatedViewProps): React.JSX.Element;
55
+ export {};