@praxiis/ui 0.0.1

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 (187) hide show
  1. package/dist/index.d.mts +52556 -0
  2. package/dist/index.d.ts +52556 -0
  3. package/dist/index.js +8753 -0
  4. package/dist/index.mjs +8777 -0
  5. package/package.json +70 -0
  6. package/src/__test-utils__/index.tsx +39 -0
  7. package/src/components/CalendarStrip/CalendarStrip.helpers.ts +106 -0
  8. package/src/components/CalendarStrip/CalendarStrip.tsx +83 -0
  9. package/src/components/CalendarStrip/CalendarStrip.types.ts +133 -0
  10. package/src/components/CalendarStrip/DayCard/DayCard.helpers.ts +44 -0
  11. package/src/components/CalendarStrip/DayCard/DayCard.tsx +71 -0
  12. package/src/components/CalendarStrip/DayCard/DayCard.types.ts +134 -0
  13. package/src/components/CalendarStrip/DayCard/index.ts +2 -0
  14. package/src/components/CalendarStrip/DayCard/useDayCardLogic.ts +45 -0
  15. package/src/components/CalendarStrip/index.ts +9 -0
  16. package/src/components/CalendarStrip/useCalendarStripLogic.ts +53 -0
  17. package/src/components/EmptyState/EmptyState.helpers.ts +104 -0
  18. package/src/components/EmptyState/EmptyState.tsx +205 -0
  19. package/src/components/EmptyState/EmptyState.types.ts +213 -0
  20. package/src/components/EmptyState/index.ts +44 -0
  21. package/src/components/EmptyState/useEmptyStateLogic.ts +131 -0
  22. package/src/components/Header/Header.helpers.ts +93 -0
  23. package/src/components/Header/Header.tsx +185 -0
  24. package/src/components/Header/Header.types.ts +153 -0
  25. package/src/components/Header/index.ts +44 -0
  26. package/src/components/Header/useHeaderLogic.ts +146 -0
  27. package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.helpers.ts +50 -0
  28. package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.tsx +78 -0
  29. package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.types.ts +99 -0
  30. package/src/components/ScheduleItem/ScheduleItem/index.ts +16 -0
  31. package/src/components/ScheduleItem/ScheduleItem/useScheduleItemLogic.ts +31 -0
  32. package/src/components/ScheduleItem/index.ts +15 -0
  33. package/src/components/index.ts +40 -0
  34. package/src/core/index.ts +34 -0
  35. package/src/core/restyle/RestyleThemeProviderWrapper.tsx +31 -0
  36. package/src/core/restyle/index.ts +38 -0
  37. package/src/core/restyle/restylePresetRegistry.ts +195 -0
  38. package/src/core/restyle/restyleTheme.ts +1352 -0
  39. package/src/core/restyle/restyleTypes.ts +8 -0
  40. package/src/core/restyle/useRestyleTheme.ts +10 -0
  41. package/src/hooks/animations/index.ts +3 -0
  42. package/src/hooks/animations/useAnimatedValue.ts +10 -0
  43. package/src/hooks/animations/useEntranceAnimation.ts +106 -0
  44. package/src/hooks/animations/usePulseAnimation.ts +63 -0
  45. package/src/hooks/index.ts +30 -0
  46. package/src/hooks/useReducedMotion.ts +60 -0
  47. package/src/i18n/index.ts +2 -0
  48. package/src/i18n/labels/en.ts +120 -0
  49. package/src/i18n/labels/es.ts +120 -0
  50. package/src/i18n/labels/index.ts +6 -0
  51. package/src/i18n/labels/types.ts +165 -0
  52. package/src/index.tsx +215 -0
  53. package/src/primitives/actions/Button/Button.helpers.ts +243 -0
  54. package/src/primitives/actions/Button/Button.tsx +198 -0
  55. package/src/primitives/actions/Button/Button.types.ts +207 -0
  56. package/src/primitives/actions/Button/index.ts +41 -0
  57. package/src/primitives/actions/Button/useButtonLogic.ts +160 -0
  58. package/src/primitives/actions/IconButton/IconButton.helpers.ts +235 -0
  59. package/src/primitives/actions/IconButton/IconButton.tsx +177 -0
  60. package/src/primitives/actions/IconButton/IconButton.types.ts +273 -0
  61. package/src/primitives/actions/IconButton/index.ts +30 -0
  62. package/src/primitives/actions/IconButton/useIconButtonLogic.ts +172 -0
  63. package/src/primitives/actions/index.ts +20 -0
  64. package/src/primitives/content/Avatar/Avatar.helpers.ts +177 -0
  65. package/src/primitives/content/Avatar/Avatar.tsx +199 -0
  66. package/src/primitives/content/Avatar/Avatar.types.ts +222 -0
  67. package/src/primitives/content/Avatar/index.ts +46 -0
  68. package/src/primitives/content/Avatar/useAvatarLogic.ts +149 -0
  69. package/src/primitives/content/Badge/Badge.helpers.ts +175 -0
  70. package/src/primitives/content/Badge/Badge.tsx +174 -0
  71. package/src/primitives/content/Badge/Badge.types.ts +223 -0
  72. package/src/primitives/content/Badge/index.ts +40 -0
  73. package/src/primitives/content/Badge/useBadgeLogic.ts +128 -0
  74. package/src/primitives/content/Card/Card.helpers.ts +27 -0
  75. package/src/primitives/content/Card/Card.tsx +123 -0
  76. package/src/primitives/content/Card/Card.types.ts +95 -0
  77. package/src/primitives/content/Card/index.ts +20 -0
  78. package/src/primitives/content/Card/useCardLogic.ts +48 -0
  79. package/src/primitives/content/Chip/Chip.helpers.ts +304 -0
  80. package/src/primitives/content/Chip/Chip.tsx +205 -0
  81. package/src/primitives/content/Chip/Chip.types.ts +234 -0
  82. package/src/primitives/content/Chip/index.ts +47 -0
  83. package/src/primitives/content/Chip/useChipLogic.ts +167 -0
  84. package/src/primitives/content/Icon/Icon.helpers.ts +54 -0
  85. package/src/primitives/content/Icon/Icon.tsx +110 -0
  86. package/src/primitives/content/Icon/Icon.types.ts +95 -0
  87. package/src/primitives/content/Icon/index.ts +20 -0
  88. package/src/primitives/content/Icon/useIconLogic.ts +73 -0
  89. package/src/primitives/content/index.ts +45 -0
  90. package/src/primitives/feedback/ProgressBar/ProgressBar.helpers.ts +122 -0
  91. package/src/primitives/feedback/ProgressBar/ProgressBar.tsx +154 -0
  92. package/src/primitives/feedback/ProgressBar/ProgressBar.types.ts +178 -0
  93. package/src/primitives/feedback/ProgressBar/index.ts +17 -0
  94. package/src/primitives/feedback/ProgressBar/useProgressBarLogic.ts +120 -0
  95. package/src/primitives/feedback/Skeleton/Skeleton.helpers.ts +145 -0
  96. package/src/primitives/feedback/Skeleton/Skeleton.tsx +155 -0
  97. package/src/primitives/feedback/Skeleton/Skeleton.types.ts +223 -0
  98. package/src/primitives/feedback/Skeleton/index.ts +44 -0
  99. package/src/primitives/feedback/Skeleton/useSkeletonLogic.ts +125 -0
  100. package/src/primitives/feedback/Spinner/Spinner.helpers.ts +40 -0
  101. package/src/primitives/feedback/Spinner/Spinner.tsx +105 -0
  102. package/src/primitives/feedback/Spinner/Spinner.types.ts +114 -0
  103. package/src/primitives/feedback/Spinner/index.ts +18 -0
  104. package/src/primitives/feedback/Spinner/useSpinnerLogic.ts +84 -0
  105. package/src/primitives/feedback/Toast/Toast.helpers.ts +163 -0
  106. package/src/primitives/feedback/Toast/Toast.tsx +190 -0
  107. package/src/primitives/feedback/Toast/Toast.types.ts +270 -0
  108. package/src/primitives/feedback/Toast/ToastContext.tsx +96 -0
  109. package/src/primitives/feedback/Toast/ToastProvider.tsx +241 -0
  110. package/src/primitives/feedback/Toast/index.ts +59 -0
  111. package/src/primitives/feedback/Toast/useToastLogic.ts +112 -0
  112. package/src/primitives/feedback/index.ts +45 -0
  113. package/src/primitives/index.ts +158 -0
  114. package/src/primitives/inputs/Checkbox/Checkbox.helpers.ts +132 -0
  115. package/src/primitives/inputs/Checkbox/Checkbox.tsx +150 -0
  116. package/src/primitives/inputs/Checkbox/Checkbox.types.ts +106 -0
  117. package/src/primitives/inputs/Checkbox/index.ts +30 -0
  118. package/src/primitives/inputs/Checkbox/useCheckboxLogic.ts +121 -0
  119. package/src/primitives/inputs/RadioButton/RadioButton.helpers.ts +123 -0
  120. package/src/primitives/inputs/RadioButton/RadioButton.tsx +159 -0
  121. package/src/primitives/inputs/RadioButton/RadioButton.types.ts +106 -0
  122. package/src/primitives/inputs/RadioButton/index.ts +25 -0
  123. package/src/primitives/inputs/RadioButton/useRadioButtonLogic.ts +117 -0
  124. package/src/primitives/inputs/SegmentedControl/SegmentedControl.helpers.ts +174 -0
  125. package/src/primitives/inputs/SegmentedControl/SegmentedControl.tsx +224 -0
  126. package/src/primitives/inputs/SegmentedControl/SegmentedControl.types.ts +187 -0
  127. package/src/primitives/inputs/SegmentedControl/index.ts +39 -0
  128. package/src/primitives/inputs/SegmentedControl/useSegmentedControlLogic.ts +151 -0
  129. package/src/primitives/inputs/SelectSheet/SelectSheet.helpers.ts +147 -0
  130. package/src/primitives/inputs/SelectSheet/SelectSheet.tsx +247 -0
  131. package/src/primitives/inputs/SelectSheet/SelectSheet.types.ts +196 -0
  132. package/src/primitives/inputs/SelectSheet/SelectSheetOption.tsx +177 -0
  133. package/src/primitives/inputs/SelectSheet/index.ts +48 -0
  134. package/src/primitives/inputs/SelectSheet/useSelectSheetLogic.ts +309 -0
  135. package/src/primitives/inputs/Switch/Switch.helpers.ts +109 -0
  136. package/src/primitives/inputs/Switch/Switch.tsx +191 -0
  137. package/src/primitives/inputs/Switch/Switch.types.ts +154 -0
  138. package/src/primitives/inputs/Switch/index.ts +40 -0
  139. package/src/primitives/inputs/Switch/useSwitchLogic.ts +192 -0
  140. package/src/primitives/inputs/TextInput/TextInput.helpers.ts +206 -0
  141. package/src/primitives/inputs/TextInput/TextInput.tsx +392 -0
  142. package/src/primitives/inputs/TextInput/TextInput.types.ts +216 -0
  143. package/src/primitives/inputs/TextInput/index.ts +37 -0
  144. package/src/primitives/inputs/TextInput/useTextInputLogic.ts +195 -0
  145. package/src/primitives/inputs/index.ts +52 -0
  146. package/src/primitives/layout/AnimatedBox.tsx +44 -0
  147. package/src/primitives/layout/Box.tsx +71 -0
  148. package/src/primitives/layout/Divider/Divider.helpers.ts +115 -0
  149. package/src/primitives/layout/Divider/Divider.tsx +139 -0
  150. package/src/primitives/layout/Divider/Divider.types.ts +178 -0
  151. package/src/primitives/layout/Divider/index.ts +24 -0
  152. package/src/primitives/layout/Divider/useDividerLogic.ts +109 -0
  153. package/src/primitives/layout/FlatList.tsx +66 -0
  154. package/src/primitives/layout/Pressable.tsx +74 -0
  155. package/src/primitives/layout/ScrollView.tsx +63 -0
  156. package/src/primitives/layout/Stack.tsx +69 -0
  157. package/src/primitives/layout/index.ts +40 -0
  158. package/src/primitives/navigation/index.ts +6 -0
  159. package/src/primitives/overlays/Modal/Modal.helpers.ts +31 -0
  160. package/src/primitives/overlays/Modal/Modal.tsx +264 -0
  161. package/src/primitives/overlays/Modal/Modal.types.ts +193 -0
  162. package/src/primitives/overlays/Modal/index.ts +43 -0
  163. package/src/primitives/overlays/Modal/useModalLogic.ts +103 -0
  164. package/src/primitives/overlays/index.ts +12 -0
  165. package/src/primitives/typography/Text.tsx +51 -0
  166. package/src/primitives/typography/index.ts +1 -0
  167. package/src/provider/DesignSystemContext.ts +22 -0
  168. package/src/provider/DesignSystemProvider.tsx +121 -0
  169. package/src/provider/index.ts +7 -0
  170. package/src/providers/ThemeProvider/createTheme.ts +304 -0
  171. package/src/providers/ThemeProvider/defaultTheme.ts +70 -0
  172. package/src/providers/ThemeProvider/index.ts +34 -0
  173. package/src/providers/ThemeProvider/types.ts +249 -0
  174. package/src/providers/index.ts +29 -0
  175. package/src/tokens/colors.ts +371 -0
  176. package/src/tokens/index.ts +145 -0
  177. package/src/tokens/motion.ts +176 -0
  178. package/src/tokens/radii.ts +82 -0
  179. package/src/tokens/scales.ts +588 -0
  180. package/src/tokens/shadows.ts +190 -0
  181. package/src/tokens/spacing.ts +140 -0
  182. package/src/tokens/tokens.json +207 -0
  183. package/src/tokens/typography.ts +251 -0
  184. package/src/types.ts +50 -0
  185. package/src/utils/accessibility.ts +169 -0
  186. package/src/utils/index.ts +25 -0
  187. package/src/utils/platform.ts +72 -0
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Switch Component
3
+ *
4
+ * @description Toggle switch for boolean settings - Atom
5
+ *
6
+ * Switch provides a sliding toggle control for enabling/disabling features.
7
+ * Uses smooth spring animations for thumb position and track color.
8
+ *
9
+ * ## Geometry (W:H ≈ 1.7:1)
10
+ * - Thumb = TrackHeight - (SWITCH_THUMB_INSET × 2)
11
+ * - SWITCH_THUMB_INSET = 2px (padding between thumb and track edge)
12
+ *
13
+ * ## Size Scale
14
+ * | Size | Track W | Track H | Thumb | W:H Ratio | hitSlop | Touch |
15
+ * |------|---------|---------|-------|-----------|---------|-------|
16
+ * | sm | 40px | 24px | 20px | 1.67:1 | 10px | 44px |
17
+ * | md | 48px | 28px | 24px | 1.71:1 | 8px | 44px |
18
+ * | lg | 56px | 32px | 28px | 1.75:1 | 6px | 44px |
19
+ *
20
+ * ## Animation
21
+ * - Uses spring physics for natural feel
22
+ * - Track color interpolates between off (gray) and on (trackColor)
23
+ * - Thumb scales slightly during transition for tactile feedback
24
+ *
25
+ * ## Features
26
+ * - Responsive size prop (phone/tablet breakpoints)
27
+ * - Customizable track and thumb colors
28
+ * - Smooth animated transitions
29
+ * - Full accessibility support (switch role)
30
+ *
31
+ * @performance
32
+ * - Wrapped with React.memo() to prevent unnecessary re-renders
33
+ * - Uses React Native's native driver for smooth 60fps animations
34
+ * - Spring animations with configurable friction/tension for natural feel
35
+ * - Color interpolation cached in Animated.Value for efficient transitions
36
+ * - Respects reduced motion preferences for accessibility
37
+ *
38
+ * @see Switch.types.ts - Type definitions
39
+ * @see Switch.helpers.ts - Dimension and style functions
40
+ * @see Switch.a11y.ts - Accessibility prop generation
41
+ * @see tokens/scales.ts - SWITCH_DIMENSIONS, SWITCH_THUMB_INSET
42
+ *
43
+ * @example
44
+ * // Basic usage
45
+ * <Switch value={isEnabled} onValueChange={setIsEnabled} />
46
+ *
47
+ * @example
48
+ * // With label and accessibility
49
+ * <Switch
50
+ * value={isEnabled}
51
+ * onValueChange={setIsEnabled}
52
+ * size={{ phone: "sm", tablet: "lg" }}
53
+ * trackColor="accentPrimary"
54
+ * label="Enable notifications"
55
+ * accessibilityLabel="Enable push notifications"
56
+ * accessibilityHint="Toggle to receive push notifications"
57
+ * />
58
+ */
59
+
60
+ import {
61
+ createRestyleComponent,
62
+ createVariant,
63
+ layout,
64
+ spacing,
65
+ spacingShorthand,
66
+ } from "@shopify/restyle";
67
+ import React, { forwardRef, memo, useMemo } from "react";
68
+ import { Pressable, View } from "react-native";
69
+ import { RestyleTheme } from "../../../core/restyle";
70
+ import { SHADOW_TOKENS } from "../../../tokens/scales";
71
+ import { AnimatedBox } from "../../layout";
72
+ import { BaseSwitchProps, SwitchProps } from "./Switch.types";
73
+ import { useSwitchLogic } from "./useSwitchLogic";
74
+
75
+ /**
76
+ * Base Switch container with Restyle variant support.
77
+ * @internal
78
+ */
79
+ const BaseSwitch = createRestyleComponent<BaseSwitchProps, RestyleTheme>(
80
+ [
81
+ createVariant({ themeKey: "switchSizes", property: "size" }),
82
+ layout,
83
+ spacing,
84
+ spacingShorthand,
85
+ ],
86
+ Pressable,
87
+ );
88
+
89
+ const SwitchComponent = forwardRef<View, SwitchProps>(function Switch(
90
+ {
91
+ value,
92
+ onValueChange,
93
+ size = "md",
94
+ disabled = false,
95
+ trackColor = "accentPrimary",
96
+ thumbColor = "white",
97
+ label,
98
+ accessibilityLabel,
99
+ accessibilityHint,
100
+ testID,
101
+ ...rest
102
+ },
103
+ ref,
104
+ ) {
105
+ const {
106
+ trackWidth,
107
+ trackHeight,
108
+ thumbSize,
109
+ thumbTranslateX,
110
+ trackBackgroundColor,
111
+ thumbScale,
112
+ finalThumbColor,
113
+ handlePress,
114
+ containerStyle,
115
+ hitSlop,
116
+ a11yProps,
117
+ } = useSwitchLogic({
118
+ value,
119
+ disabled,
120
+ size,
121
+ trackColor,
122
+ thumbColor,
123
+ onValueChange,
124
+ label,
125
+ accessibilityLabel,
126
+ accessibilityHint,
127
+ });
128
+
129
+ const thumbPadding = 2;
130
+ const maxTranslateX = trackWidth - thumbSize - thumbPadding * 2;
131
+ const borderRadius = trackHeight / 2;
132
+
133
+ const trackStyle = useMemo(
134
+ () => ({
135
+ borderRadius,
136
+ backgroundColor: trackBackgroundColor,
137
+ }),
138
+ [borderRadius, trackBackgroundColor],
139
+ );
140
+
141
+ const thumbStyle = useMemo(
142
+ () => ({
143
+ ...SHADOW_TOKENS.switchThumb,
144
+ borderRadius: thumbSize / 2,
145
+ backgroundColor: finalThumbColor,
146
+ transform: [
147
+ {
148
+ translateX: thumbTranslateX.interpolate({
149
+ inputRange: [0, 1],
150
+ outputRange: [thumbPadding, maxTranslateX],
151
+ }),
152
+ },
153
+ { scale: thumbScale },
154
+ ],
155
+ }),
156
+ [
157
+ thumbSize,
158
+ finalThumbColor,
159
+ thumbTranslateX,
160
+ thumbPadding,
161
+ maxTranslateX,
162
+ thumbScale,
163
+ ],
164
+ );
165
+
166
+ return (
167
+ <BaseSwitch
168
+ ref={ref}
169
+ size={size}
170
+ onPress={handlePress}
171
+ disabled={disabled}
172
+ hitSlop={hitSlop}
173
+ testID={testID}
174
+ style={containerStyle}
175
+ {...a11yProps}
176
+ {...rest}
177
+ >
178
+ <AnimatedBox
179
+ width={trackWidth}
180
+ height={trackHeight}
181
+ justifyContent="center"
182
+ style={trackStyle}
183
+ >
184
+ <AnimatedBox width={thumbSize} height={thumbSize} style={thumbStyle} />
185
+ </AnimatedBox>
186
+ </BaseSwitch>
187
+ );
188
+ });
189
+
190
+ export const Switch = memo(SwitchComponent);
191
+ Switch.displayName = "Switch";
@@ -0,0 +1,154 @@
1
+ import { BoxProps, ResponsiveValue, VariantProps } from "@shopify/restyle";
2
+ import { Animated, Insets, PressableProps, View, ViewStyle } from "react-native";
3
+ import { RestyleTheme } from "../../../core/restyle";
4
+ import { RestyleColor } from "../../../types";
5
+
6
+ type SwitchSizeVariantProps = VariantProps<RestyleTheme, "switchSizes", "size">;
7
+ type SwitchRestyleProps = SwitchSizeVariantProps & BoxProps<RestyleTheme>;
8
+
9
+ /**
10
+ * Size variant for the Switch component.
11
+ *
12
+ * - `sm`: 40×24px track - Compact UIs, dense lists
13
+ * - `md`: 48×28px track - Default mobile size
14
+ * - `lg`: 56×32px track - Tablets, emphasized settings
15
+ */
16
+ export type SwitchSize = Exclude<keyof RestyleTheme["switchSizes"], "defaults">;
17
+
18
+
19
+ /** Ref type for the Switch component — the underlying React Native View instance. */
20
+ export type SwitchRef = View;
21
+
22
+ /**
23
+ * Base props for the Switch container component.
24
+ */
25
+ export type BaseSwitchProps = SwitchRestyleProps & PressableProps;
26
+
27
+ /**
28
+ * Props for the Switch component.
29
+ *
30
+ * @example
31
+ * // Basic usage
32
+ * <Switch value={isEnabled} onValueChange={setIsEnabled} />
33
+ *
34
+ * @example
35
+ * // With label and accessibility
36
+ * <Switch
37
+ * value={isEnabled}
38
+ * onValueChange={setIsEnabled}
39
+ * size={{ phone: "sm", tablet: "lg" }}
40
+ * trackColor="accentPrimary"
41
+ * label="Enable notifications"
42
+ * accessibilityLabel="Enable push notifications"
43
+ * accessibilityHint="Toggle to receive push notifications"
44
+ * />
45
+ */
46
+ export interface SwitchProps extends SwitchRestyleProps {
47
+ /** Current value of the switch */
48
+ value: boolean;
49
+ /** Callback when the value changes */
50
+ onValueChange: (value: boolean) => void;
51
+ /** Size of the switch (supports responsive values) */
52
+ size?: ResponsiveValue<SwitchSize, RestyleTheme["breakpoints"]>;
53
+ /** Whether the switch is disabled */
54
+ disabled?: boolean;
55
+ /** Color for the track when enabled */
56
+ trackColor?: RestyleColor;
57
+ /** Color for the thumb */
58
+ thumbColor?: RestyleColor;
59
+ /** Label text for the switch */
60
+ label?: string;
61
+ /** Accessibility label (required for screen readers) */
62
+ accessibilityLabel?: string;
63
+ /** Accessibility hint for additional context */
64
+ accessibilityHint?: string;
65
+ /** Test ID for testing */
66
+ testID?: string;
67
+ }
68
+
69
+ /**
70
+ * Parameters for the useSwitchLogic hook.
71
+ */
72
+ export interface UseSwitchLogicParams {
73
+ value: boolean;
74
+ disabled: boolean;
75
+ size: ResponsiveValue<SwitchSize, RestyleTheme["breakpoints"]>;
76
+ trackColor: RestyleColor;
77
+ thumbColor: RestyleColor;
78
+ onValueChange: (value: boolean) => void;
79
+ label?: string;
80
+ accessibilityLabel?: string;
81
+ accessibilityHint?: string;
82
+ }
83
+
84
+ /**
85
+ * Return value from the useSwitchLogic hook.
86
+ */
87
+ export interface UseSwitchLogicReturn {
88
+ /** Track width based on size */
89
+ trackWidth: number;
90
+ /** Track height based on size */
91
+ trackHeight: number;
92
+ /** Thumb size based on track height */
93
+ thumbSize: number;
94
+ /** Animated value for thumb position */
95
+ thumbTranslateX: Animated.Value;
96
+ /** Animated interpolation for track background color */
97
+ trackBackgroundColor: Animated.AnimatedInterpolation<string | number> | string;
98
+ /** Animated value for thumb scale */
99
+ thumbScale: Animated.Value;
100
+ /** Final thumb color based on disabled state */
101
+ finalThumbColor: string;
102
+ /** Press handler callback */
103
+ handlePress: () => void;
104
+ /** Container style with opacity */
105
+ containerStyle: ViewStyle;
106
+ /** Hit slop for touch target */
107
+ hitSlop: Insets;
108
+ /** Accessibility props */
109
+ a11yProps: SwitchA11yProps;
110
+ }
111
+
112
+ /**
113
+ * Parameters for the getSwitchA11y function.
114
+ */
115
+ export interface SwitchA11yParams {
116
+ label?: string;
117
+ accessibilityLabel?: string;
118
+ accessibilityHint?: string;
119
+ value: boolean;
120
+ disabled: boolean;
121
+ }
122
+
123
+ /**
124
+ * Accessibility props returned by getSwitchA11y.
125
+ */
126
+ export interface SwitchA11yProps {
127
+ accessibilityLabel: string;
128
+ accessibilityHint?: string;
129
+ accessibilityRole: "switch";
130
+ accessibilityState: { checked: boolean; disabled: boolean };
131
+ }
132
+
133
+ /**
134
+ * Parameters for resolveSwitchStyle function.
135
+ */
136
+ export interface ResolveSwitchStyleParams {
137
+ disabled: boolean;
138
+ }
139
+
140
+ /**
141
+ * Result from resolveSwitchStyle function.
142
+ */
143
+ export interface SwitchStyleResult {
144
+ opacity: number;
145
+ }
146
+
147
+ /**
148
+ * Dimensions for the switch track and thumb.
149
+ */
150
+ export interface SwitchDimensions {
151
+ trackWidth: number;
152
+ trackHeight: number;
153
+ thumbSize: number;
154
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Switch Component Module
3
+ *
4
+ * A toggle switch component for boolean values.
5
+ *
6
+ * Features:
7
+ * - Three sizes: sm, md, lg (supports responsive values)
8
+ * - Customizable track and thumb colors
9
+ * - Smooth animated transitions
10
+ * - Full accessibility support
11
+ *
12
+ * @example
13
+ * import { Switch } from '@/src/design-system/primitives/inputs/Switch';
14
+ *
15
+ * // Basic usage
16
+ * <Switch value={isEnabled} onValueChange={setIsEnabled} />
17
+ *
18
+ * // With label and accessibility
19
+ * <Switch
20
+ * value={isEnabled}
21
+ * onValueChange={setIsEnabled}
22
+ * size={{ phone: "sm", tablet: "lg" }}
23
+ * trackColor="accentPrimary"
24
+ * label="Enable notifications"
25
+ * accessibilityLabel="Enable push notifications"
26
+ * accessibilityHint="Toggle to receive push notifications"
27
+ * />
28
+ */
29
+
30
+ export { Switch } from "./Switch";
31
+
32
+ export type {
33
+ SwitchProps,
34
+ SwitchRef,
35
+ SwitchSize,
36
+ SwitchA11yParams,
37
+ SwitchA11yProps,
38
+ } from "./Switch.types";
39
+
40
+ export { getSwitchA11y } from "./Switch.a11y";
@@ -0,0 +1,192 @@
1
+ import { useResponsiveProp } from "@shopify/restyle";
2
+ import { useCallback, useEffect, useMemo } from "react";
3
+ import { Animated, ViewStyle } from "react-native";
4
+ import { useRestyleTheme } from "../../../core/restyle";
5
+ import { BaseThemeColor } from "../../../types";
6
+ import { useReducedMotion } from "../../../hooks/useReducedMotion";
7
+ import { useAnimatedValue } from "../../../hooks/animations";
8
+ import { ANIMATION_TOKENS } from "../../../tokens/scales";
9
+ import { getSwitchA11y } from "./Switch.a11y";
10
+ import {
11
+ getSwitchDimensions,
12
+ getSwitchHitSlop,
13
+ resolveSwitchStyle,
14
+ } from "./Switch.helpers";
15
+ import { SwitchSize, UseSwitchLogicParams, UseSwitchLogicReturn } from "./Switch.types";
16
+
17
+ /**
18
+ * Orchestrates Switch rendering logic and state management.
19
+ *
20
+ * Handles:
21
+ * - Responsive size resolution
22
+ * - Dimension calculations (track width, height, thumb size)
23
+ * - Animated transitions for thumb position and scale
24
+ * - Reduced motion support (instant state changes)
25
+ * - Track and thumb color interpolation
26
+ * - Hit slop calculation for 44px minimum touch target
27
+ * - Press handler with disabled state check
28
+ * - Accessibility props generation with on/off state
29
+ *
30
+ * @param params - Configuration object for switch behavior
31
+ * @param params.value - Current switch state (on/off)
32
+ * @param params.disabled - Whether switch is disabled
33
+ * @param params.size - Size variant ('sm' | 'md' | 'lg')
34
+ * @param params.trackColor - Theme color token for track (when on)
35
+ * @param params.thumbColor - Theme color token for thumb
36
+ * @param params.onValueChange - Callback when value changes
37
+ * @param params.label - Associated label text
38
+ * @param params.accessibilityLabel - Custom a11y label
39
+ * @param params.accessibilityHint - Describes action result
40
+ *
41
+ * @returns Computed values for rendering Switch
42
+ * @returns {number} trackWidth - Track width in pixels
43
+ * @returns {number} trackHeight - Track height in pixels
44
+ * @returns {number} thumbSize - Thumb size in pixels
45
+ * @returns {Animated.Value} thumbTranslateX - Animated thumb position (0-1)
46
+ * @returns {Animated.Value} trackBackgroundColor - Animated track color
47
+ * @returns {Animated.Value} thumbScale - Animated thumb scale (0.9-1)
48
+ * @returns {string} finalThumbColor - Thumb color token
49
+ * @returns {function} handlePress - Press handler
50
+ * @returns {ViewStyle} containerStyle - Dynamic container styles
51
+ * @returns {Insets} hitSlop - Touch target expansion
52
+ * @returns {object} a11yProps - Accessibility props with on/off state
53
+ *
54
+ * @performance
55
+ * - Uses React Native's native driver for smooth 60fps animations
56
+ * - Spring animations with configurable friction/tension
57
+ * - Respects reduced motion preferences for accessibility
58
+ *
59
+ * @example
60
+ * const {
61
+ * trackWidth,
62
+ * thumbTranslateX,
63
+ * trackBackgroundColor,
64
+ * handlePress,
65
+ * a11yProps
66
+ * } = useSwitchLogic({
67
+ * value: true,
68
+ * size: "md",
69
+ * trackColor: "accentPrimary",
70
+ * thumbColor: "white",
71
+ * onValueChange: setValue,
72
+ * label: "Enable notifications",
73
+ * });
74
+ */
75
+ export function useSwitchLogic(
76
+ params: UseSwitchLogicParams
77
+ ): UseSwitchLogicReturn {
78
+ const {
79
+ value,
80
+ disabled,
81
+ size,
82
+ trackColor,
83
+ thumbColor,
84
+ onValueChange,
85
+ label,
86
+ accessibilityLabel,
87
+ accessibilityHint,
88
+ } = params;
89
+
90
+ const theme = useRestyleTheme();
91
+ const resolvedSize = (useResponsiveProp(size) ?? "md") as SwitchSize;
92
+ const prefersReducedMotion = useReducedMotion();
93
+
94
+ // Get dimensions based on resolved size
95
+ const { trackWidth, trackHeight, thumbSize } =
96
+ getSwitchDimensions(resolvedSize);
97
+
98
+ // Get hit slop based on resolved size
99
+ const hitSlop = getSwitchHitSlop(resolvedSize);
100
+
101
+ // Animated values for smooth transitions
102
+ const thumbTranslateX = useAnimatedValue(value ? 1 : 0);
103
+ const thumbScale = useAnimatedValue(1);
104
+
105
+ // Animate on value change
106
+ useEffect(() => {
107
+ if (prefersReducedMotion) {
108
+ // Set immediate state without animation for reduced motion
109
+ thumbTranslateX.setValue(value ? 1 : 0);
110
+ thumbScale.setValue(1);
111
+ } else {
112
+ // Use animated spring transitions
113
+ Animated.parallel([
114
+ Animated.spring(thumbTranslateX, {
115
+ toValue: value ? 1 : 0,
116
+ useNativeDriver: false,
117
+ friction: ANIMATION_TOKENS.switch.toggle.friction,
118
+ tension: ANIMATION_TOKENS.switch.toggle.tension,
119
+ }),
120
+ Animated.sequence([
121
+ Animated.spring(thumbScale, {
122
+ toValue: 0.9,
123
+ useNativeDriver: false,
124
+ friction: ANIMATION_TOKENS.switch.thumbScale.friction,
125
+ tension: ANIMATION_TOKENS.switch.thumbScale.tension,
126
+ }),
127
+ Animated.spring(thumbScale, {
128
+ toValue: 1,
129
+ useNativeDriver: false,
130
+ friction: ANIMATION_TOKENS.switch.thumbScale.friction,
131
+ tension: ANIMATION_TOKENS.switch.thumbScale.tension,
132
+ }),
133
+ ]),
134
+ ]).start();
135
+ }
136
+ }, [value, thumbTranslateX, thumbScale, prefersReducedMotion]);
137
+
138
+ // Animated track background color
139
+ const trackBackgroundColor = disabled
140
+ ? theme.colors.interactiveDisabled
141
+ : thumbTranslateX.interpolate({
142
+ inputRange: [0, 1],
143
+ outputRange: [
144
+ theme.colors.borderDefault,
145
+ theme.colors[trackColor as BaseThemeColor] ?? theme.colors.accentPrimary,
146
+ ],
147
+ });
148
+
149
+ // Final thumb color
150
+ const finalThumbColor = disabled
151
+ ? theme.colors.textDisabled
152
+ : theme.colors[thumbColor as BaseThemeColor] ?? theme.colors.white;
153
+
154
+ // Press handler
155
+ const handlePress = useCallback(() => {
156
+ if (!disabled) {
157
+ onValueChange(!value);
158
+ }
159
+ }, [disabled, value, onValueChange]);
160
+
161
+ // Container style with opacity
162
+ const containerStyle: ViewStyle = {
163
+ opacity: resolveSwitchStyle({ disabled }).opacity,
164
+ };
165
+
166
+ // Accessibility props
167
+ const a11yProps = useMemo(
168
+ () =>
169
+ getSwitchA11y({
170
+ label,
171
+ accessibilityLabel,
172
+ accessibilityHint,
173
+ value,
174
+ disabled,
175
+ }),
176
+ [label, accessibilityLabel, accessibilityHint, value, disabled]
177
+ );
178
+
179
+ return {
180
+ trackWidth,
181
+ trackHeight,
182
+ thumbSize,
183
+ thumbTranslateX,
184
+ trackBackgroundColor,
185
+ thumbScale,
186
+ finalThumbColor,
187
+ handlePress,
188
+ containerStyle,
189
+ hitSlop,
190
+ a11yProps,
191
+ };
192
+ }