@mrmeg/expo-ui 0.12.0 → 0.13.0-rsd.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 (79) hide show
  1. package/dist/components/Accordion.d.ts +38 -18
  2. package/dist/components/Accordion.js +137 -121
  3. package/dist/components/AnimatedView.d.ts +13 -24
  4. package/dist/components/AnimatedView.js +70 -29
  5. package/dist/components/Badge.d.ts +2 -2
  6. package/dist/components/Badge.js +45 -47
  7. package/dist/components/BottomSheet.d.ts +30 -73
  8. package/dist/components/BottomSheet.js +240 -188
  9. package/dist/components/Button.d.ts +43 -56
  10. package/dist/components/Button.js +189 -203
  11. package/dist/components/Card.d.ts +7 -10
  12. package/dist/components/Card.js +90 -105
  13. package/dist/components/Checkbox.d.ts +20 -8
  14. package/dist/components/Checkbox.js +77 -80
  15. package/dist/components/Collapsible.d.ts +47 -47
  16. package/dist/components/Collapsible.js +108 -29
  17. package/dist/components/Dialog.d.ts +80 -121
  18. package/dist/components/Dialog.js +225 -132
  19. package/dist/components/DismissKeyboard.d.ts +1 -1
  20. package/dist/components/DismissKeyboard.js +70 -26
  21. package/dist/components/Drawer.d.ts +37 -76
  22. package/dist/components/Drawer.js +252 -486
  23. package/dist/components/DropdownMenu.d.ts +106 -113
  24. package/dist/components/DropdownMenu.js +350 -204
  25. package/dist/components/EmptyState.d.ts +2 -2
  26. package/dist/components/EmptyState.js +41 -34
  27. package/dist/components/InputOTP.d.ts +19 -53
  28. package/dist/components/InputOTP.js +81 -102
  29. package/dist/components/KeyboardAvoidingView.d.ts +6 -3
  30. package/dist/components/KeyboardAvoidingView.js +18 -5
  31. package/dist/components/Label.d.ts +12 -17
  32. package/dist/components/Label.js +38 -51
  33. package/dist/components/MaxWidthContainer.d.ts +5 -16
  34. package/dist/components/MaxWidthContainer.js +28 -29
  35. package/dist/components/Notification.d.ts +5 -9
  36. package/dist/components/Notification.js +190 -187
  37. package/dist/components/Popover.d.ts +38 -66
  38. package/dist/components/Popover.js +158 -69
  39. package/dist/components/Progress.d.ts +5 -4
  40. package/dist/components/Progress.js +65 -77
  41. package/dist/components/RadioGroup.d.ts +30 -24
  42. package/dist/components/RadioGroup.js +90 -94
  43. package/dist/components/Select.d.ts +62 -74
  44. package/dist/components/Select.js +241 -154
  45. package/dist/components/Separator.d.ts +13 -23
  46. package/dist/components/Separator.js +29 -36
  47. package/dist/components/Skeleton.d.ts +8 -7
  48. package/dist/components/Skeleton.js +74 -61
  49. package/dist/components/StyledText.context.d.ts +6 -2
  50. package/dist/components/StyledText.context.js +3 -0
  51. package/dist/components/StyledText.d.ts +29 -7
  52. package/dist/components/StyledText.js +92 -29
  53. package/dist/components/Switch.d.ts +18 -6
  54. package/dist/components/Switch.js +112 -106
  55. package/dist/components/Tabs.d.ts +26 -16
  56. package/dist/components/Tabs.js +189 -91
  57. package/dist/components/TextInput.d.ts +6 -19
  58. package/dist/components/TextInput.js +261 -195
  59. package/dist/components/Toggle.d.ts +23 -41
  60. package/dist/components/Toggle.js +84 -98
  61. package/dist/components/ToggleGroup.d.ts +37 -29
  62. package/dist/components/ToggleGroup.js +113 -108
  63. package/dist/components/Tooltip.d.ts +41 -111
  64. package/dist/components/Tooltip.js +156 -118
  65. package/dist/components/UIProvider.d.ts +1 -1
  66. package/dist/components/UIProvider.js +1 -1
  67. package/dist/components/keyboardFocusRegistry.d.ts +15 -0
  68. package/dist/components/keyboardFocusRegistry.js +27 -0
  69. package/dist/hooks/useTheme.d.ts +34 -10
  70. package/dist/hooks/useTheme.js +20 -8
  71. package/dist/lib/index.d.ts +2 -0
  72. package/dist/lib/index.js +2 -0
  73. package/dist/lib/portal.d.ts +16 -0
  74. package/dist/lib/portal.js +84 -0
  75. package/dist/lib/styles.d.ts +32 -0
  76. package/dist/lib/styles.js +91 -0
  77. package/dist/lib/useAnchoredPosition.d.ts +57 -0
  78. package/dist/lib/useAnchoredPosition.js +120 -0
  79. package/package.json +4 -22
@@ -1,28 +1,33 @@
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> & {
1
+ import * as React from "react";
2
+ import { type LooseStyle } from "../lib/styles";
3
+ type BaseAccordionRootProps = {
4
4
  disabled?: boolean;
5
+ /** Single-type only: allow closing the open item by re-pressing it. */
5
6
  collapsible?: boolean;
7
+ /** Platform: WEB ONLY (kept for API compat; unused). */
6
8
  dir?: "ltr" | "rtl";
9
+ /** Platform: WEB ONLY (kept for API compat; unused). */
7
10
  orientation?: "vertical" | "horizontal";
8
- style?: ViewStyle;
11
+ style?: LooseStyle;
12
+ children?: React.ReactNode;
9
13
  };
10
- type WebSingleAccordionRootProps = BaseAccordionRootProps & {
14
+ type SingleAccordionRootProps = BaseAccordionRootProps & {
11
15
  type: "single";
12
16
  defaultValue?: string;
13
17
  value?: string;
14
18
  onValueChange?: (value: string | undefined) => void;
15
19
  };
16
- type WebMultipleAccordionRootProps = BaseAccordionRootProps & {
20
+ type MultipleAccordionRootProps = BaseAccordionRootProps & {
17
21
  type: "multiple";
18
22
  defaultValue?: string[];
19
23
  value?: string[];
20
24
  onValueChange?: (value: string[]) => void;
21
25
  };
22
- type AccordionRootProps = WebSingleAccordionRootProps | WebMultipleAccordionRootProps;
26
+ type AccordionRootProps = SingleAccordionRootProps | MultipleAccordionRootProps;
23
27
  /**
24
- * Accordion Root Component
25
- * Container for accordion items with support for single or multiple open items
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.
26
31
  *
27
32
  * Usage:
28
33
  * <Accordion type="single" collapsible>
@@ -32,23 +37,38 @@ type AccordionRootProps = WebSingleAccordionRootProps | WebMultipleAccordionRoot
32
37
  * </AccordionItem>
33
38
  * </Accordion>
34
39
  */
35
- declare function Accordion({ children, style, ...props }: AccordionRootProps): import("react").JSX.Element;
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
+ }
36
47
  /**
37
48
  * Accordion Item Component
38
- * Individual accordion item with border styling
49
+ * Individual accordion item with a bottom border. Shares its expanded state
50
+ * with the Trigger (chevron) and Content via context.
39
51
  */
40
- declare function AccordionItem({ children, value, style: styleOverride, ...props }: AccordionPrimitive.ItemProps & React.RefAttributes<AccordionPrimitive.ItemRef>): import("react").JSX.Element;
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
+ }
41
57
  /**
42
58
  * Accordion Trigger Component
43
- * Clickable header that expands/collapses the content
44
- * Includes animated chevron icon
59
+ * Clickable header that expands/collapses the item. Includes an animated
60
+ * chevron that rotates with the expanded state.
45
61
  */
46
- declare function AccordionTrigger({ children, style: styleOverride, ...props }: AccordionPrimitive.TriggerProps & {
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;
47
66
  children?: React.ReactNode;
48
- } & React.RefAttributes<AccordionPrimitive.TriggerRef>): import("react").JSX.Element;
67
+ style?: LooseStyle;
68
+ }
49
69
  /**
50
70
  * Accordion Content Component
51
- * Expandable content area with animations
71
+ * Expandable content area. Unmounts when collapsed unless `forceMount` is set.
52
72
  */
53
- declare function AccordionContent({ children, style: styleOverride, ...props }: AccordionPrimitive.ContentProps & React.RefAttributes<AccordionPrimitive.ContentRef>): import("react").JSX.Element;
73
+ declare function AccordionContent({ forceMount, children, style }: AccordionContentProps): React.JSX.Element | null;
54
74
  export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
@@ -1,62 +1,31 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from "react";
3
- import { Animated, Platform, Pressable, View } from "react-native";
2
+ import { createContext, use, useCallback, useMemo, useState } from "react";
3
+ import { css, html } from "react-strict-dom";
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 { 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 }) }));
34
- }
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 }) }));
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;
50
16
  }
51
- function WebAccordionRoot(props) {
52
- if (props.type === "multiple") {
53
- return _jsx(WebMultipleAccordionRoot, { ...props });
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>");
54
22
  }
55
- return _jsx(WebSingleAccordionRoot, { ...props });
23
+ return ctx;
56
24
  }
57
25
  /**
58
- * Accordion Root Component
59
- * Container for accordion items with support for single or multiple open items
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.
60
29
  *
61
30
  * Usage:
62
31
  * <Accordion type="single" collapsible>
@@ -66,92 +35,139 @@ function WebAccordionRoot(props) {
66
35
  * </AccordionItem>
67
36
  * </Accordion>
68
37
  */
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 }) }));
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 }) }));
74
78
  }
79
+ const itemStyles = css.create({
80
+ base: (borderBottomColor) => ({
81
+ borderBottomWidth: 1,
82
+ borderBottomStyle: "solid",
83
+ borderBottomColor,
84
+ overflow: "hidden",
85
+ }),
86
+ });
75
87
  /**
76
88
  * Accordion Item Component
77
- * Individual accordion item with border styling
89
+ * Individual accordion item with a bottom border. Shares its expanded state
90
+ * with the Trigger (chevron) and Content via context.
78
91
  */
79
- function AccordionItem({ children, value, style: styleOverride, ...props }) {
92
+ function AccordionItem({ value, disabled: itemDisabled, children, style }) {
80
93
  const { theme } = useTheme();
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
- : []),
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),
91
103
  ], children: children }) }));
92
104
  }
93
- const Trigger = Platform.OS === "web" ? View : Pressable;
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
+ });
94
135
  /**
95
136
  * Accordion Trigger Component
96
- * Clickable header that expands/collapses the content
97
- * Includes animated chevron icon
137
+ * Clickable header that expands/collapses the item. Includes an animated
138
+ * chevron that rotates with the expanded state.
98
139
  */
99
- function AccordionTrigger({ children, style: styleOverride, ...props }) {
140
+ function AccordionTrigger({ children, style }) {
100
141
  const { theme } = useTheme();
101
142
  const reduceMotion = useReducedMotion();
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 }) })] }) }) }) }) }));
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 }) })] }) }) }));
140
150
  }
151
+ const contentStyles = css.create({
152
+ // paddingBottom (8) = spacing.sm.
153
+ base: {
154
+ paddingBottom: 8,
155
+ overflow: "hidden",
156
+ },
157
+ hidden: { display: "none" },
158
+ });
141
159
  /**
142
160
  * Accordion Content Component
143
- * Expandable content area with animations
161
+ * Expandable content area. Unmounts when collapsed unless `forceMount` is set.
144
162
  */
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 }) }) }));
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 }) }));
156
172
  }
157
173
  export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
@@ -1,10 +1,10 @@
1
1
  import React from "react";
2
- import { ViewProps } from "react-native";
2
+ import { type LooseStyle } from "../lib/styles";
3
3
  /**
4
4
  * Animation type options
5
5
  */
6
6
  export type AnimationType = "fade" | "fadeSlideUp" | "fadeSlideDown" | "scale";
7
- interface AnimatedViewProps extends ViewProps {
7
+ export interface AnimatedViewProps {
8
8
  children: React.ReactNode;
9
9
  /**
10
10
  * Type of animation to use
@@ -22,34 +22,23 @@ interface AnimatedViewProps extends ViewProps {
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;
25
29
  }
26
30
  /**
27
31
  * Cross-Platform Animated View Component
28
- * Uses React Native Animated for lightweight cross-platform animations
29
32
  *
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
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.
35
37
  *
36
38
  * Usage:
37
39
  * ```tsx
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>
40
+ * <AnimatedView>{children}</AnimatedView>
41
+ * <AnimatedView type="fadeSlideUp" delay={100}>{children}</AnimatedView>
52
42
  * ```
53
43
  */
54
- export declare function AnimatedView({ children, type, enterDuration, delay, style, ...props }: AnimatedViewProps): React.JSX.Element;
55
- export {};
44
+ export declare function AnimatedView({ children, type, enterDuration, delay, style, testID, }: AnimatedViewProps): React.JSX.Element;
@@ -1,39 +1,80 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Animated } from "react-native";
3
- import { useStaggeredEntrance } from "../hooks/useStaggeredEntrance.js";
2
+ import { css, html } from "react-strict-dom";
3
+ import { useReducedMotion } from "../hooks/useReduceMotion.js";
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
+ });
4
60
  /**
5
61
  * Cross-Platform Animated View Component
6
- * Uses React Native Animated for lightweight cross-platform animations
7
62
  *
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
63
+ * Plays a one-shot entrance animation via CSS keyframes on web. Respects the
64
+ * reduced-motion accessibility preference (renders statically when set). On
65
+ * native the keyframes are a no-op, so children appear immediately — the
66
+ * package ships no Reanimated, a documented animation gap.
13
67
  *
14
68
  * Usage:
15
69
  * ```tsx
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>
70
+ * <AnimatedView>{children}</AnimatedView>
71
+ * <AnimatedView type="fadeSlideUp" delay={100}>{children}</AnimatedView>
30
72
  * ```
31
73
  */
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 }));
74
+ export function AnimatedView({ children, type = "fade", enterDuration = 200, delay = 0, style, testID, }) {
75
+ const reduceMotion = useReducedMotion();
76
+ const duration = `${enterDuration}ms`;
77
+ const delayMs = `${delay}ms`;
78
+ const animation = reduceMotion ? null : styles[type](duration, delayMs);
79
+ return (_jsx(html.div, { "data-testid": testID, style: [animation, sanitizeWebStyle(style)], children: children }));
39
80
  }
@@ -1,11 +1,11 @@
1
1
  import React from "react";
2
- import { StyleProp, ViewStyle } from "react-native";
2
+ import { type LooseStyle } from "../lib/styles";
3
3
  export type BadgeVariant = "default" | "secondary" | "outline" | "destructive";
4
4
  export interface BadgeProps {
5
5
  children?: React.ReactNode;
6
6
  text?: string;
7
7
  variant?: BadgeVariant;
8
- style?: StyleProp<ViewStyle>;
8
+ style?: LooseStyle;
9
9
  }
10
10
  /**
11
11
  * Badge Component