@mrmeg/expo-ui 0.13.0-rsd.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LLM_USAGE.md +15 -0
  3. package/README.md +15 -1
  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 +29 -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
@@ -1,9 +1,12 @@
1
1
  import React from "react";
2
+ import { ViewStyle, TextStyle, StyleProp } from "react-native";
2
3
  import { TextProps } from "./StyledText";
3
- import { type LooseStyle } from "../lib/styles";
4
4
  export interface CardProps {
5
+ /** Card contents */
5
6
  children?: React.ReactNode;
6
- style?: LooseStyle;
7
+ /** Custom style override */
8
+ style?: StyleProp<ViewStyle>;
9
+ /** Visual variant */
7
10
  variant?: "default" | "outline" | "ghost";
8
11
  /** Make card pressable with scale animation */
9
12
  onPress?: () => void;
@@ -13,27 +16,27 @@ export interface CardProps {
13
16
  declare function Card({ children, style: styleOverride, variant, onPress, disabled }: CardProps): React.JSX.Element;
14
17
  export interface CardHeaderProps {
15
18
  children?: React.ReactNode;
16
- style?: LooseStyle;
19
+ style?: StyleProp<ViewStyle>;
17
20
  }
18
21
  declare function CardHeader({ children, style: styleOverride }: CardHeaderProps): React.JSX.Element;
19
22
  export interface CardContentProps {
20
23
  children?: React.ReactNode;
21
- style?: LooseStyle;
24
+ style?: StyleProp<ViewStyle>;
22
25
  }
23
26
  declare function CardContent({ children, style: styleOverride }: CardContentProps): React.JSX.Element;
24
27
  export interface CardFooterProps {
25
28
  children?: React.ReactNode;
26
- style?: LooseStyle;
29
+ style?: StyleProp<ViewStyle>;
27
30
  }
28
31
  declare function CardFooter({ children, style: styleOverride }: CardFooterProps): React.JSX.Element;
29
32
  export interface CardTitleProps extends Omit<TextProps, "style"> {
30
33
  children?: React.ReactNode;
31
- style?: LooseStyle;
34
+ style?: StyleProp<TextStyle>;
32
35
  }
33
36
  declare function CardTitle({ children, style: styleOverride, ...props }: CardTitleProps): React.JSX.Element;
34
37
  export interface CardDescriptionProps extends Omit<TextProps, "style"> {
35
38
  children?: React.ReactNode;
36
- style?: LooseStyle;
39
+ style?: StyleProp<TextStyle>;
37
40
  }
38
41
  declare function CardDescription({ children, style: styleOverride, ...props }: CardDescriptionProps): React.JSX.Element;
39
42
  export { Card, CardHeader, CardContent, CardFooter, CardTitle, CardDescription };
@@ -1,110 +1,125 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, use, useState } from "react";
3
- import { css, html } from "react-strict-dom";
2
+ import { createContext, use } from "react";
3
+ import { View, Pressable, StyleSheet, Platform, Animated } from "react-native";
4
4
  import { StyledText } from "./StyledText.js";
5
5
  import { useTheme } from "../hooks/useTheme.js";
6
- import { useReducedMotion } from "../hooks/useReduceMotion.js";
7
- import { sanitizeWebStyle } from "../lib/styles.js";
8
- // Card only needs to share the theme down to its subcomponents.
9
- const CardContext = createContext(false);
10
- // StyleX rejects imported constants (spacing.*) used statically inside
11
- // css.create, so spacing values are inlined as numeric literals here (mirrors
12
- // constants/spacing.ts: radiusLg=8, lg=24, xs=4).
13
- const styles = css.create({
14
- card: { borderRadius: 8 },
15
- default: (backgroundColor, borderColor) => ({
16
- backgroundColor,
17
- borderWidth: 1,
18
- borderStyle: "solid",
19
- borderColor,
20
- }),
21
- outline: (borderColor) => ({
22
- backgroundColor: "transparent",
23
- borderWidth: 1,
24
- borderStyle: "solid",
25
- borderColor,
26
- }),
27
- ghost: { backgroundColor: "transparent" },
28
- shadow: (boxShadow) => ({ boxShadow }),
29
- pressable: {
30
- cursor: "pointer",
31
- appearance: "none",
32
- backgroundColor: "transparent",
33
- borderWidth: 0,
34
- padding: 0,
35
- margin: 0,
36
- textAlign: "inherit",
37
- alignSelf: "stretch",
38
- transitionProperty: "transform",
39
- transitionDuration: "0.1s",
40
- },
41
- pressed: { transform: "scale(0.98)" },
42
- header: {
43
- display: "flex",
44
- flexDirection: "column",
45
- padding: 24,
46
- paddingBottom: 4,
47
- gap: 4,
48
- },
49
- content: {
50
- display: "flex",
51
- flexDirection: "column",
52
- paddingLeft: 24,
53
- paddingRight: 24,
54
- paddingBottom: 24,
55
- },
56
- footer: {
57
- display: "flex",
58
- flexDirection: "row",
59
- alignItems: "center",
60
- paddingLeft: 24,
61
- paddingRight: 24,
62
- paddingBottom: 24,
63
- paddingTop: 0,
64
- },
65
- });
66
- const TITLE_STYLE = { fontSize: 18, lineHeight: 24, letterSpacing: 0 };
67
- const DESC_STYLE = { fontSize: 14, lineHeight: 20 };
6
+ import { useScalePress } from "../hooks/useScalePress.js";
7
+ import { spacing } from "../constants/spacing.js";
8
+ /**
9
+ * Card Component
10
+ *
11
+ * A themed container component with header, content, and footer sections.
12
+ * Follows shadcn/ui patterns with consistent styling and theme integration.
13
+ *
14
+ * Usage:
15
+ * ```tsx
16
+ * <Card>
17
+ * <CardHeader>
18
+ * <CardTitle>Card Title</CardTitle>
19
+ * <CardDescription>Card description text</CardDescription>
20
+ * </CardHeader>
21
+ * <CardContent>
22
+ * <Text>Main content goes here</Text>
23
+ * </CardContent>
24
+ * <CardFooter>
25
+ * <Button>Action</Button>
26
+ * </CardFooter>
27
+ * </Card>
28
+ * ```
29
+ */
30
+ const CardContext = createContext(null);
31
+ function useCardContext() {
32
+ const ctx = use(CardContext);
33
+ // useTheme must run unconditionally (Rules of Hooks); createCardStyles is a
34
+ // plain function, so the `??` keeps the fallback styles lazy.
35
+ const { theme } = useTheme();
36
+ // Fallback for standalone usage without a Card parent.
37
+ return ctx ?? { theme, styles: createCardStyles(theme) };
38
+ }
68
39
  function Card({ children, style: styleOverride, variant = "default", onPress, disabled }) {
69
40
  const { theme, getShadowStyle } = useTheme();
70
- const reduceMotion = useReducedMotion();
71
- const [pressed, setPressed] = useState(false);
41
+ const styles = createCardStyles(theme);
72
42
  const shadowStyle = getShadowStyle("subtle");
73
- const surface = [
74
- styles.card,
75
- variant === "default" ? styles.default(theme.colors.card, theme.colors.border) : null,
76
- variant === "default" ? styles.shadow(shadowStyle.boxShadow ?? "none") : null,
77
- variant === "outline" ? styles.outline(theme.colors.border) : null,
78
- variant === "ghost" ? styles.ghost : null,
79
- sanitizeWebStyle(styleOverride),
80
- ];
81
- const cardContent = (_jsx(html.div, { style: surface, children: children }));
43
+ const ctx = { theme, styles };
44
+ const { animatedStyle: scaleStyle, pressHandlers } = useScalePress({
45
+ disabled: !onPress || !!disabled,
46
+ scaleTo: 0.98,
47
+ haptic: false,
48
+ });
49
+ const cardContent = (_jsx(View, { style: [
50
+ styles.card,
51
+ variant === "default" && styles.cardDefault,
52
+ variant === "default" && shadowStyle,
53
+ variant === "outline" && styles.cardOutline,
54
+ variant === "ghost" && styles.cardGhost,
55
+ styleOverride,
56
+ ], children: children }));
82
57
  if (onPress) {
83
- return (_jsx(CardContext.Provider, { value: true, children: _jsx(html.button, { type: "button", "aria-disabled": disabled, disabled: disabled, onClick: () => { if (!disabled)
84
- onPress(); }, onPointerDown: () => !disabled && setPressed(true), onPointerUp: () => setPressed(false), onPointerLeave: () => setPressed(false), style: [
85
- styles.pressable,
86
- pressed && !disabled && !reduceMotion ? styles.pressed : null,
87
- ], children: cardContent }) }));
58
+ return (_jsx(CardContext.Provider, { value: ctx, children: _jsx(Pressable, { onPress: onPress, disabled: disabled, accessibilityRole: "button", accessibilityState: { disabled: !!disabled }, ...pressHandlers, style: Platform.OS === "web" ? { cursor: "pointer" } : undefined, children: _jsx(Animated.View, { style: scaleStyle, children: cardContent }) }) }));
88
59
  }
89
- return (_jsx(CardContext.Provider, { value: true, children: cardContent }));
60
+ return (_jsx(CardContext.Provider, { value: ctx, children: cardContent }));
90
61
  }
91
62
  function CardHeader({ children, style: styleOverride }) {
92
- return (_jsx(html.div, { style: [styles.header, sanitizeWebStyle(styleOverride)], children: children }));
63
+ const { styles } = useCardContext();
64
+ return (_jsx(View, { style: [styles.header, styleOverride], children: children }));
93
65
  }
94
66
  function CardContent({ children, style: styleOverride }) {
95
- return (_jsx(html.div, { style: [styles.content, sanitizeWebStyle(styleOverride)], children: children }));
67
+ const { styles } = useCardContext();
68
+ return (_jsx(View, { style: [styles.content, styleOverride], children: children }));
96
69
  }
97
70
  function CardFooter({ children, style: styleOverride }) {
98
- return (_jsx(html.div, { style: [styles.footer, sanitizeWebStyle(styleOverride)], children: children }));
71
+ const { styles } = useCardContext();
72
+ return (_jsx(View, { style: [styles.footer, styleOverride], children: children }));
99
73
  }
100
74
  function CardTitle({ children, style: styleOverride, ...props }) {
101
- const { theme } = useTheme();
102
- use(CardContext);
103
- return (_jsx(StyledText, { fontWeight: "semibold", ...props, style: [{ ...TITLE_STYLE, color: theme.colors.text }, styleOverride], children: children }));
75
+ const { theme, styles } = useCardContext();
76
+ return (_jsx(StyledText, { fontWeight: "semibold", ...props, style: [styles.title, { color: theme.colors.text }, styleOverride], children: children }));
104
77
  }
105
78
  function CardDescription({ children, style: styleOverride, ...props }) {
106
- const { theme } = useTheme();
107
- use(CardContext);
108
- return (_jsx(StyledText, { ...props, style: [{ ...DESC_STYLE, color: theme.colors.textDim }, styleOverride], children: children }));
79
+ const { theme, styles } = useCardContext();
80
+ return (_jsx(StyledText, { ...props, style: [styles.description, { color: theme.colors.textDim }, styleOverride], children: children }));
109
81
  }
82
+ const createCardStyles = (theme) => StyleSheet.create({
83
+ card: {
84
+ borderRadius: spacing.radiusLg,
85
+ },
86
+ cardDefault: {
87
+ backgroundColor: theme.colors.card,
88
+ borderWidth: 1,
89
+ borderColor: theme.colors.border,
90
+ },
91
+ cardOutline: {
92
+ backgroundColor: "transparent",
93
+ borderWidth: 1,
94
+ borderColor: theme.colors.border,
95
+ },
96
+ cardGhost: {
97
+ backgroundColor: "transparent",
98
+ },
99
+ header: {
100
+ padding: spacing.lg,
101
+ paddingBottom: spacing.xs,
102
+ gap: spacing.xs,
103
+ },
104
+ content: {
105
+ paddingHorizontal: spacing.lg,
106
+ paddingBottom: spacing.lg,
107
+ },
108
+ footer: {
109
+ flexDirection: "row",
110
+ alignItems: "center",
111
+ paddingHorizontal: spacing.lg,
112
+ paddingBottom: spacing.lg,
113
+ paddingTop: 0,
114
+ },
115
+ title: {
116
+ fontSize: 18,
117
+ lineHeight: 24,
118
+ letterSpacing: 0,
119
+ },
120
+ description: {
121
+ fontSize: 14,
122
+ lineHeight: 20,
123
+ },
124
+ });
110
125
  export { Card, CardHeader, CardContent, CardFooter, CardTitle, CardDescription };
@@ -1,16 +1,11 @@
1
1
  import React from "react";
2
- import { type LooseStyle } from "../lib/styles";
2
+ import { StyleProp, ViewStyle } from "react-native";
3
+ import * as CheckboxPrimitive from "@rn-primitives/checkbox";
3
4
  /**
4
5
  * Size variants for Checkbox
5
6
  */
6
7
  export type CheckboxSize = "sm" | "md" | "lg";
7
- export interface CheckboxProps {
8
- /** Whether the checkbox is checked */
9
- checked?: boolean;
10
- /** Called with the next checked value when toggled */
11
- onCheckedChange?: (checked: boolean) => void;
12
- /** Whether the checkbox is disabled */
13
- disabled?: boolean;
8
+ export interface CheckboxProps extends Omit<CheckboxPrimitive.RootProps, "style"> {
14
9
  /**
15
10
  * Size variant
16
11
  * @default "md"
@@ -29,24 +24,17 @@ export interface CheckboxProps {
29
24
  */
30
25
  error?: boolean;
31
26
  /**
32
- * Custom style override for the box
27
+ * Custom style override (uses StyleSheet.flatten for web compatibility)
33
28
  */
34
- style?: LooseStyle;
29
+ style?: StyleProp<ViewStyle>;
35
30
  /**
36
- * Style for the label row container
31
+ * Style for the label text
37
32
  */
38
- labelStyle?: LooseStyle;
33
+ labelStyle?: StyleProp<ViewStyle>;
39
34
  /**
40
35
  * Whether the field is required (shows asterisk in label)
41
36
  */
42
37
  required?: boolean;
43
- /** Forwarded accessibility label (defaults to `label`). */
44
- "aria-label"?: string;
45
- /** Accessibility label (RN-API alias of aria-label). */
46
- accessibilityLabel?: string;
47
- /** Forwarded test id (also accepts data-testid). */
48
- testID?: string;
49
- "data-testid"?: string;
50
38
  }
51
- declare function Checkbox({ size, label, indeterminate, error, style: styleOverride, labelStyle, required, checked, onCheckedChange, disabled, "aria-label": ariaLabelProp, accessibilityLabel, testID, "data-testid": dataTestId, }: CheckboxProps): React.JSX.Element;
39
+ declare function Checkbox({ size, label, indeterminate, error, style: styleOverride, labelStyle, required, checked, onCheckedChange, disabled, ...props }: CheckboxProps): React.JSX.Element;
52
40
  export { Checkbox };
@@ -1,102 +1,105 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { css, html } from "react-strict-dom";
2
+ import { useCallback, useEffect, useRef } from "react";
3
+ import { View, StyleSheet, Pressable, Platform, Animated } from "react-native";
3
4
  import { Icon } from "./Icon.js";
4
5
  import { StyledText } from "./StyledText.js";
5
6
  import { useTheme } from "../hooks/useTheme.js";
7
+ import { spacing } from "../constants/spacing.js";
6
8
  import { hapticLight } from "../lib/haptics.js";
7
9
  import { useReducedMotion } from "../hooks/useReduceMotion.js";
8
- import { sanitizeWebStyle } from "../lib/styles.js";
10
+ import * as CheckboxPrimitive from "@rn-primitives/checkbox";
11
+ const DEFAULT_HIT_SLOP = 8;
9
12
  const SIZE_CONFIGS = {
10
- sm: { size: 16, iconSize: 12 },
11
- md: { size: 20, iconSize: 16 },
12
- lg: { size: 24, iconSize: 20 },
13
+ sm: { size: 16, iconSize: 12, strokeWidth: 2.5 },
14
+ md: { size: 20, iconSize: 16, strokeWidth: 3 },
15
+ lg: { size: 24, iconSize: 20, strokeWidth: 3.5 },
13
16
  };
14
- const styles = css.create({
15
- // borderRadius literal (4) = spacing.radiusSm.
16
- box: (size, borderColor, backgroundColor) => ({
17
- width: size,
18
- height: size,
19
- borderRadius: 4,
20
- borderWidth: 1,
21
- borderStyle: "solid",
22
- borderColor,
23
- backgroundColor,
24
- display: "flex",
25
- alignItems: "center",
26
- justifyContent: "center",
27
- padding: 0,
28
- margin: 0,
29
- appearance: "none",
30
- cursor: "pointer",
31
- }),
32
- disabledBox: { opacity: 0.5, cursor: "not-allowed" },
33
- indicator: (opacity, animated) => ({
34
- display: "flex",
35
- alignItems: "center",
36
- justifyContent: "center",
37
- opacity,
38
- transitionProperty: animated ? "opacity" : "none",
39
- transitionDuration: animated ? "0.06s" : "0s",
40
- }),
41
- // minHeight literal (44) = spacing.touchTarget; gap (8) = spacing.sm.
42
- container: {
43
- display: "flex",
44
- flexDirection: "row",
45
- alignItems: "center",
46
- gap: 8,
47
- minHeight: 44,
48
- appearance: "none",
49
- backgroundColor: "transparent",
50
- borderWidth: 0,
51
- padding: 0,
52
- margin: 0,
53
- textAlign: "inherit",
54
- cursor: "pointer",
55
- },
56
- disabledContainer: { cursor: "not-allowed" },
57
- labelContainer: { flexGrow: 1, flexShrink: 1, flexBasis: "0%" },
58
- });
59
- const LABEL_TEXT_STYLE = { fontSize: 14 };
60
- function Checkbox({ size = "md", label, indeterminate = false, error = false, style: styleOverride, labelStyle, required = false, checked, onCheckedChange, disabled, "aria-label": ariaLabelProp, accessibilityLabel, testID, "data-testid": dataTestId, }) {
17
+ function Checkbox({ size = "md", label, indeterminate = false, error = false, style: styleOverride, labelStyle, required = false, checked, onCheckedChange, disabled, ...props }) {
61
18
  const { theme, getContrastingColor } = useTheme();
62
- const ariaLabel = ariaLabelProp ?? accessibilityLabel;
63
19
  const reduceMotion = useReducedMotion();
64
20
  const sizeConfig = SIZE_CONFIGS[size];
21
+ // Simple fast opacity for the checkmark icon
22
+ const checkOpacity = useRef(new Animated.Value(checked || indeterminate ? 1 : 0)).current;
65
23
  const isVisuallyChecked = !!checked || indeterminate;
66
- const toggle = () => {
67
- if (disabled)
68
- return;
69
- const next = !checked;
24
+ const animateCheckOpacity = useCallback((nextVisible) => {
25
+ Animated.timing(checkOpacity, {
26
+ toValue: nextVisible ? 1 : 0,
27
+ duration: reduceMotion ? 0 : 60,
28
+ useNativeDriver: true,
29
+ }).start();
30
+ }, [checkOpacity, reduceMotion]);
31
+ useEffect(() => {
32
+ animateCheckOpacity(isVisuallyChecked);
33
+ }, [animateCheckOpacity, isVisuallyChecked]);
34
+ const wrappedOnCheckedChange = (next) => {
70
35
  if (next)
71
36
  hapticLight();
37
+ animateCheckOpacity(next);
72
38
  onCheckedChange?.(next);
73
39
  };
74
- // Dynamic border color with sufficient contrast against the background.
40
+ // Dynamic border color with sufficient contrast against background
75
41
  const borderColor = error
76
42
  ? theme.colors.destructive
77
43
  : isVisuallyChecked
78
44
  ? theme.colors.primary
79
45
  : getContrastingColor(theme.colors.background, theme.colors.text, theme.colors.textDim);
80
- const ariaChecked = indeterminate ? "mixed" : !!checked;
81
- const boxStyle = [
82
- styles.box(sizeConfig.size, borderColor, isVisuallyChecked ? theme.colors.primary : theme.colors.background),
83
- disabled ? styles.disabledBox : null,
84
- sanitizeWebStyle(styleOverride),
85
- ];
86
- const indicator = (_jsx(html.span, { style: styles.indicator(isVisuallyChecked ? 1 : 0, !reduceMotion), children: _jsx(Icon, { name: indeterminate ? "minus" : "check", size: sizeConfig.iconSize, color: theme.colors.primaryForeground, decorative: true }) }));
87
- // Without a label the box itself is the interactive checkbox control.
46
+ // Flatten style override for web compatibility
47
+ const flattenedStyle = styleOverride ? StyleSheet.flatten(styleOverride) : undefined;
48
+ const checkboxElement = (_jsx(CheckboxPrimitive.Root, { ...props, checked: checked, onCheckedChange: wrappedOnCheckedChange, disabled: disabled, style: {
49
+ ...styles.box,
50
+ borderColor,
51
+ backgroundColor: isVisuallyChecked ? theme.colors.primary : theme.colors.background,
52
+ width: sizeConfig.size,
53
+ height: sizeConfig.size,
54
+ opacity: disabled ? 0.5 : 1,
55
+ ...(Platform.OS === "web" && { cursor: disabled ? "not-allowed" : "pointer" }),
56
+ ...(flattenedStyle || {}),
57
+ }, hitSlop: DEFAULT_HIT_SLOP, accessibilityRole: "checkbox", accessibilityState: {
58
+ checked: indeterminate ? "mixed" : checked,
59
+ disabled: !!disabled,
60
+ }, accessibilityLabel: label, children: _jsx(CheckboxPrimitive.Indicator, { style: {
61
+ justifyContent: "center",
62
+ alignItems: "center",
63
+ }, children: _jsx(Animated.View, { style: { opacity: checkOpacity }, children: indeterminate ? (_jsx(Icon, { name: "minus", size: sizeConfig.iconSize, color: theme.colors.primaryForeground })) : (_jsx(Icon, { name: "check", size: sizeConfig.iconSize, color: theme.colors.primaryForeground })) }) }) }));
64
+ // If no label, return just the checkbox
88
65
  if (!label) {
89
- return (_jsx(html.button, { type: "button", role: "checkbox", "aria-checked": ariaChecked, "aria-disabled": disabled, "aria-label": ariaLabel, disabled: disabled, "data-testid": dataTestId ?? testID, onClick: toggle, style: boxStyle, children: indicator }));
66
+ return checkboxElement;
90
67
  }
91
- // With a label the whole row is the control; the box is a decorative visual.
92
- return (_jsxs(html.button, { type: "button", role: "checkbox", "aria-checked": ariaChecked, "aria-disabled": disabled, "aria-label": ariaLabel ?? label, disabled: disabled, "data-testid": dataTestId ?? testID, onClick: toggle, style: [
93
- styles.container,
94
- disabled ? styles.disabledContainer : null,
95
- sanitizeWebStyle(labelStyle),
96
- ], children: [_jsx(html.span, { style: boxStyle, children: indicator }), _jsx(html.span, { style: styles.labelContainer, children: _jsxs(StyledText, { selectable: false, style: {
97
- ...LABEL_TEXT_STYLE,
98
- color: error ? theme.colors.destructive : theme.colors.text,
99
- opacity: disabled ? 0.5 : 1,
100
- }, children: [label, required && (_jsx(StyledText, { selectable: false, fontWeight: "bold", style: { color: theme.colors.destructive }, children: " *" }))] }) })] }));
68
+ // With label, wrap in a container
69
+ return (_jsxs(Pressable, { onPress: () => !disabled && wrappedOnCheckedChange(!checked), style: [styles.container, labelStyle], disabled: disabled, accessibilityRole: "checkbox", accessibilityState: {
70
+ checked: indeterminate ? "mixed" : checked,
71
+ disabled: !!disabled,
72
+ }, accessibilityLabel: label, children: [checkboxElement, _jsx(View, { style: styles.labelContainer, children: _jsxs(StyledText, { selectable: false, style: [
73
+ styles.label,
74
+ { color: theme.colors.text },
75
+ disabled && styles.disabledLabel,
76
+ error && { color: theme.colors.destructive },
77
+ ], children: [label, required && (_jsx(StyledText, { selectable: false, style: [styles.required, { color: theme.colors.destructive }], children: " *" }))] }) })] }));
101
78
  }
79
+ const styles = StyleSheet.create({
80
+ box: {
81
+ borderRadius: spacing.radiusSm,
82
+ borderWidth: 1,
83
+ justifyContent: "center",
84
+ alignItems: "center",
85
+ },
86
+ container: {
87
+ flexDirection: "row",
88
+ alignItems: "center",
89
+ gap: spacing.sm,
90
+ minHeight: spacing.touchTarget,
91
+ },
92
+ labelContainer: {
93
+ flex: 1,
94
+ },
95
+ label: {
96
+ fontSize: 14,
97
+ },
98
+ disabledLabel: {
99
+ opacity: 0.5,
100
+ },
101
+ required: {
102
+ fontWeight: "bold",
103
+ },
104
+ });
102
105
  export { Checkbox };
@@ -1,68 +1,68 @@
1
1
  import * as React from "react";
2
- import { type LooseStyle } from "../lib/styles";
3
- export interface CollapsibleProps {
4
- /** Controlled open state. */
5
- open?: boolean;
6
- /** Uncontrolled initial open state. */
7
- defaultOpen?: boolean;
8
- /** Open-state change handler. */
9
- onOpenChange?: (open: boolean) => void;
10
- /** Disables the trigger. */
11
- disabled?: boolean;
12
- children?: React.ReactNode;
13
- /** Custom style override for the root container. */
14
- style?: LooseStyle;
15
- }
2
+ import * as CollapsiblePrimitive from "@rn-primitives/collapsible";
16
3
  /**
17
- * Collapsible Component (react-strict-dom)
18
- *
19
- * A wrapper component that provides expand/collapse functionality. The trigger
20
- * toggles a `shown` boolean shared via context; the content mounts only while
21
- * open (or always, when `forceMount`).
4
+ * Collapsible Component (Root)
5
+ * A wrapper component that provides expand/collapse functionality with smooth animations
6
+ * Using @rn-primitives/collapsible with DaisyUI theme integration
22
7
  *
23
8
  * Usage:
24
9
  * ```tsx
25
10
  * const [open, setOpen] = useState(false);
26
11
  * <Collapsible open={open} onOpenChange={setOpen}>
27
12
  * <CollapsibleTrigger>
28
- * <StyledText>Toggle</StyledText>
13
+ * <SansSerifText>Toggle</SansSerifText>
29
14
  * </CollapsibleTrigger>
30
15
  * <CollapsibleContent>
31
- * <StyledText>Content that expands and collapses</StyledText>
16
+ * <SansSerifText>Content that expands and collapses</SansSerifText>
32
17
  * </CollapsibleContent>
33
18
  * </Collapsible>
34
19
  * ```
35
20
  */
36
- declare function Collapsible({ open: openProp, defaultOpen, onOpenChange, disabled, children, style, }: CollapsibleProps): React.JSX.Element;
37
- export interface CollapsibleTriggerProps {
38
- /** Render the single child as the trigger (clones it, injecting onPress). */
39
- asChild?: boolean;
40
- /** Disables this trigger only. */
41
- disabled?: boolean;
42
- children?: React.ReactNode;
43
- /** Custom style override. */
44
- style?: LooseStyle;
45
- }
21
+ type CollapsibleProps = CollapsiblePrimitive.RootProps;
22
+ declare function Collapsible({ children, ...props }: CollapsibleProps): React.JSX.Element;
46
23
  /**
47
24
  * CollapsibleTrigger Component
48
- * The element that toggles the collapsible state. Supports `asChild` to use a
49
- * custom interactive child (e.g. Button) as the trigger.
25
+ * The trigger button/element that toggles the collapsible state
26
+ * Supports asChild pattern for custom trigger components
27
+ *
28
+ * Usage:
29
+ * ```tsx
30
+ * // Simple trigger
31
+ * <CollapsibleTrigger>
32
+ * <SansSerifText>Click to expand</SansSerifText>
33
+ * </CollapsibleTrigger>
34
+ *
35
+ * // With asChild (using Button)
36
+ * <CollapsibleTrigger asChild>
37
+ * <Button preset="outline">
38
+ * <SansSerifText>Toggle</SansSerifText>
39
+ * </Button>
40
+ * </CollapsibleTrigger>
41
+ * ```
50
42
  */
51
- declare function CollapsibleTrigger({ asChild, disabled: triggerDisabled, style, children }: CollapsibleTriggerProps): React.JSX.Element;
52
- export interface CollapsibleContentProps {
53
- /**
54
- * Keep the content mounted (hidden) when closed instead of unmounting it.
55
- * Default: false (content unmounts when closed).
56
- */
57
- forceMount?: boolean;
58
- children?: React.ReactNode;
59
- /** Custom style override. */
60
- style?: LooseStyle;
61
- }
43
+ type CollapsibleTriggerProps = CollapsiblePrimitive.TriggerProps;
44
+ declare function CollapsibleTrigger({ style: styleOverride, ...props }: CollapsibleTriggerProps): React.JSX.Element;
62
45
  /**
63
46
  * CollapsibleContent Component
64
- * The content that expands and collapses. Unmounts when closed unless
65
- * `forceMount` is set, in which case it stays mounted but hidden.
47
+ * The content that expands and collapses
48
+ * Includes exit animation on native platforms
49
+ *
50
+ * Usage:
51
+ * ```tsx
52
+ * <CollapsibleContent>
53
+ * <View>
54
+ * <SansSerifText>This content will animate in and out</SansSerifText>
55
+ * </View>
56
+ * </CollapsibleContent>
57
+ * ```
66
58
  */
67
- declare function CollapsibleContent({ forceMount, style, children }: CollapsibleContentProps): React.JSX.Element | null;
59
+ type CollapsibleContentProps = CollapsiblePrimitive.ContentProps & {
60
+ /**
61
+ * Whether to force remove the content from the tree when closed
62
+ * Default: false (content remains in tree but hidden)
63
+ */
64
+ forceMount?: boolean;
65
+ };
66
+ declare function CollapsibleContent({ forceMount, style: styleOverride, children, ...props }: CollapsibleContentProps): React.JSX.Element;
68
67
  export { Collapsible, CollapsibleContent, CollapsibleTrigger };
68
+ export type { CollapsibleContentProps, CollapsibleProps, CollapsibleTriggerProps };