@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,159 @@
1
+ /**
2
+ * RadioButton Component
3
+ *
4
+ * @description Single-selection control for option groups - Atom
5
+ *
6
+ * RadioButton provides a circular indicator for single-selection within groups.
7
+ * Uses a filled inner dot to indicate the checked state.
8
+ *
9
+ * ## Ratios & Constraints
10
+ * - Inner dot = Container × ~40% (RADIO_BUTTON_INNER_SIZE)
11
+ * - All sizes meet 44px minimum touch target via hitSlop
12
+ *
13
+ * ## Size Scale
14
+ * | Size | Container | Inner Dot | hitSlop | Touch Target |
15
+ * |------|-----------|-----------|---------|--------------|
16
+ * | sm | 20px | 8px | 12px | 44px |
17
+ * | md | 24px | 10px | 10px | 44px |
18
+ * | lg | 32px | 14px | 6px | 44px |
19
+ *
20
+ * ## Visual States
21
+ * - Unchecked: Border only, transparent background
22
+ * - Checked: Filled background with white inner dot
23
+ * - Disabled: Reduced opacity (0.5)
24
+ *
25
+ * ## Features
26
+ * - Responsive size prop (phone/tablet breakpoints)
27
+ * - Customizable color when checked
28
+ * - Full accessibility support (radio role)
29
+ *
30
+ * @performance
31
+ * - Wrapped with React.memo() to prevent unnecessary re-renders
32
+ * - Uses useMemo() for expensive calculations (style resolution, inner dot sizing)
33
+ * - Inner dot calculated using ~40% container ratio for instant rendering
34
+ * - Hit slop pre-calculated in lookup tables for 44px touch targets
35
+ *
36
+ * @see RadioButton.types.ts - Type definitions
37
+ * @see RadioButton.helpers.ts - Pure calculation functions
38
+ * @see RadioButton.a11y.ts - Accessibility prop generation
39
+ * @see useRadioButtonLogic.ts - Logic hook
40
+ * @see tokens/scales.ts - Mathematical ratios
41
+ *
42
+ * @example
43
+ * // Basic usage
44
+ * <RadioButton
45
+ * checked={isSelected}
46
+ * onPress={handleSelect}
47
+ * accessibilityLabel="Option A"
48
+ * />
49
+ *
50
+ * @example
51
+ * // Responsive with color
52
+ * <RadioButton
53
+ * checked={isSelected}
54
+ * onPress={handleSelect}
55
+ * size={{ phone: "md", tablet: "lg" }}
56
+ * color="feedbackSuccess"
57
+ * />
58
+ */
59
+
60
+ import {
61
+ backgroundColor,
62
+ border,
63
+ createRestyleComponent,
64
+ createVariant,
65
+ layout,
66
+ spacing,
67
+ spacingShorthand,
68
+ } from "@shopify/restyle";
69
+ import React, { forwardRef, memo, useCallback } from "react";
70
+ import { Pressable, View } from "react-native";
71
+ import { RestyleTheme } from "../../../core/restyle";
72
+ import { Box } from "../../layout";
73
+ import {
74
+ BaseRadioButtonInteractiveContainerProps,
75
+ RadioButtonProps,
76
+ } from "./RadioButton.types";
77
+ import { useRadioButtonLogic } from "./useRadioButtonLogic";
78
+
79
+ /**
80
+ * Base container with Restyle variant support.
81
+ * @internal
82
+ */
83
+ const BaseRadioButtonInteractiveContainer = createRestyleComponent<
84
+ BaseRadioButtonInteractiveContainerProps,
85
+ RestyleTheme
86
+ >(
87
+ [
88
+ createVariant({ themeKey: "radioButtonSizes", property: "size" }),
89
+ backgroundColor,
90
+ border,
91
+ layout,
92
+ spacing,
93
+ spacingShorthand,
94
+ ],
95
+ Pressable,
96
+ );
97
+
98
+ const RadioButtonComponent = forwardRef<View, RadioButtonProps>(
99
+ function RadioButton(
100
+ {
101
+ checked,
102
+ onPress,
103
+ size = "md",
104
+ disabled = false,
105
+ color = "accentPrimary",
106
+ label,
107
+ accessibilityLabel,
108
+ accessibilityHint,
109
+ testID,
110
+ ...rest
111
+ },
112
+ ref,
113
+ ) {
114
+ const { innerDotSize, containerStyle, hitSlop, a11yProps } =
115
+ useRadioButtonLogic({
116
+ checked,
117
+ disabled,
118
+ size,
119
+ color,
120
+ label,
121
+ accessibilityLabel,
122
+ accessibilityHint,
123
+ });
124
+
125
+ const handlePress = useCallback(() => {
126
+ if (!disabled) {
127
+ onPress();
128
+ }
129
+ }, [disabled, onPress]);
130
+
131
+ return (
132
+ <BaseRadioButtonInteractiveContainer
133
+ ref={ref}
134
+ size={size}
135
+ onPress={handlePress}
136
+ disabled={disabled}
137
+ hitSlop={hitSlop}
138
+ testID={testID}
139
+ alignItems="center"
140
+ justifyContent="center"
141
+ style={containerStyle}
142
+ {...a11yProps}
143
+ {...rest}
144
+ >
145
+ {checked && (
146
+ <Box
147
+ width={innerDotSize}
148
+ height={innerDotSize}
149
+ borderRadius="full"
150
+ backgroundColor="textInverse"
151
+ />
152
+ )}
153
+ </BaseRadioButtonInteractiveContainer>
154
+ );
155
+ },
156
+ );
157
+
158
+ export const RadioButton = memo(RadioButtonComponent);
159
+ RadioButton.displayName = "RadioButton";
@@ -0,0 +1,106 @@
1
+ /**
2
+ * RadioButton Component Types
3
+ *
4
+ * Type definitions for the RadioButton component and its related hooks/helpers.
5
+ * All types are explicitly exported for external consumption.
6
+ */
7
+
8
+ import { BoxProps, ResponsiveValue, VariantProps } from "@shopify/restyle";
9
+ import { Insets, PressableProps, View, ViewStyle } from "react-native";
10
+ import { RestyleTheme } from "../../../core/restyle";
11
+ import { RestyleColor } from "../../../types";
12
+
13
+ // =============================================================================
14
+ // VARIANT TYPES (Explicitly exported for external use)
15
+ // =============================================================================
16
+
17
+ /**
18
+ * Size variants derived from theme.
19
+ * Maps to radioButtonSizes in restyleTheme.ts
20
+ *
21
+ * | Size | Container | Touch Target |
22
+ * |------|-----------|--------------|
23
+ * | sm | 20px | 44px (hitSlop) |
24
+ * | md | 24px | 44px (hitSlop) |
25
+ * | lg | 32px | 44px (hitSlop) |
26
+ */
27
+ export type RadioButtonSize = Exclude<
28
+ keyof RestyleTheme["radioButtonSizes"],
29
+ "defaults"
30
+ >;
31
+
32
+ /** Ref type for the RadioButton component — the underlying React Native View instance. */
33
+ export type RadioButtonRef = View;
34
+
35
+ // =============================================================================
36
+ // RESTYLE PROP TYPES
37
+ // =============================================================================
38
+
39
+ type RadioButtonSizeVariantProps = VariantProps<
40
+ RestyleTheme,
41
+ "radioButtonSizes",
42
+ "size"
43
+ >;
44
+ type RadioButtonRestyleProps = RadioButtonSizeVariantProps & BoxProps<RestyleTheme>;
45
+
46
+ export type BaseRadioButtonInteractiveContainerProps = RadioButtonRestyleProps & PressableProps;
47
+
48
+ export interface RadioButtonProps extends Omit<RadioButtonRestyleProps, "size"> {
49
+ checked: boolean;
50
+ onPress: () => void;
51
+ /** Size of the radio button (supports responsive values) */
52
+ size?: ResponsiveValue<RadioButtonSize, RestyleTheme["breakpoints"]>;
53
+ disabled?: boolean;
54
+ color?: RestyleColor;
55
+ label?: string;
56
+ accessibilityLabel?: string;
57
+ accessibilityHint?: string;
58
+ testID?: string;
59
+ }
60
+
61
+ export interface UseRadioButtonLogicParams {
62
+ checked: boolean;
63
+ disabled: boolean;
64
+ size: ResponsiveValue<RadioButtonSize, RestyleTheme["breakpoints"]>;
65
+ color: RestyleColor;
66
+ label?: string;
67
+ accessibilityLabel?: string;
68
+ accessibilityHint?: string;
69
+ }
70
+
71
+ export interface UseRadioButtonLogicReturn {
72
+ innerDotSize: number;
73
+ containerStyle: ViewStyle;
74
+ hitSlop: Insets;
75
+ a11yProps: RadioButtonA11yProps;
76
+ }
77
+
78
+ export interface RadioButtonA11yParams {
79
+ label?: string;
80
+ accessibilityLabel?: string;
81
+ accessibilityHint?: string;
82
+ checked: boolean;
83
+ disabled: boolean;
84
+ /** i18n fallback label when no label or accessibilityLabel provided */
85
+ fallbackLabel: string;
86
+ }
87
+
88
+ export interface RadioButtonA11yProps {
89
+ accessibilityLabel: string;
90
+ accessibilityHint?: string;
91
+ accessibilityRole: "radio";
92
+ accessibilityState: { checked: boolean; disabled: boolean };
93
+ }
94
+
95
+ export interface ResolveRadioButtonStyleParams {
96
+ checked: boolean;
97
+ disabled: boolean;
98
+ color: RestyleColor;
99
+ theme: RestyleTheme;
100
+ }
101
+
102
+ export interface RadioButtonStyleResult {
103
+ backgroundColor: string;
104
+ borderColor: string;
105
+ opacity: number;
106
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * RadioButton Component
3
+ *
4
+ * Barrel export for the RadioButton component and related types.
5
+ */
6
+
7
+ export { RadioButton } from "./RadioButton";
8
+
9
+ export type {
10
+ RadioButtonProps,
11
+ RadioButtonRef,
12
+ RadioButtonSize,
13
+ RadioButtonA11yProps,
14
+ UseRadioButtonLogicParams,
15
+ UseRadioButtonLogicReturn,
16
+ } from "./RadioButton.types";
17
+
18
+ export { getRadioButtonA11y } from "./RadioButton.a11y";
19
+ export { useRadioButtonLogic } from "./useRadioButtonLogic";
20
+ export {
21
+ getRadioButtonInnerSize,
22
+ getRadioButtonHitSlop,
23
+ resolveRadioButtonStyle,
24
+ isValidRadioButtonSize,
25
+ } from "./RadioButton.helpers";
@@ -0,0 +1,117 @@
1
+ /**
2
+ * RadioButton Logic Hook
3
+ *
4
+ * Extracts all computational logic from the RadioButton component.
5
+ * Returns memoized values for optimal render performance.
6
+ *
7
+ * Responsibilities:
8
+ * - Resolve responsive size prop to current breakpoint value
9
+ * - Compute inner dot size from container size (~40% ratio)
10
+ * - Calculate container styles (background, border, opacity)
11
+ * - Compute hitSlop for proper touch targets
12
+ * - Generate accessibility props
13
+ *
14
+ * The component (RadioButton.tsx) is a "dumb view" that only renders
15
+ * the pre-computed values from this hook.
16
+ *
17
+ * @see RadioButton.tsx - Component consuming this hook
18
+ * @see RadioButton.helpers.ts - Pure calculation functions
19
+ * @see tokens/scales.ts - Mathematical ratios and constants
20
+ */
21
+
22
+ import { useDesignSystem } from "../../../provider";
23
+ import { useResponsiveProp } from "@shopify/restyle";
24
+ import { useMemo } from "react";
25
+ import { ViewStyle } from "react-native";
26
+ import { useRestyleTheme } from "../../../core/restyle";
27
+ import { getRadioButtonA11y } from "./RadioButton.a11y";
28
+ import {
29
+ getRadioButtonHitSlop,
30
+ getRadioButtonInnerSize,
31
+ resolveRadioButtonStyle,
32
+ } from "./RadioButton.helpers";
33
+ import { RadioButtonSize, UseRadioButtonLogicParams, UseRadioButtonLogicReturn } from "./RadioButton.types";
34
+
35
+ /**
36
+ * Orchestrates RadioButton rendering logic and state management.
37
+ *
38
+ * Handles:
39
+ * - Responsive size resolution
40
+ * - Inner dot size calculation (~40% of container ratio)
41
+ * - Container styles (background, border, opacity)
42
+ * - Hit slop calculation for 44px minimum touch target
43
+ * - Accessibility props generation with checked state
44
+ *
45
+ * @param params - Configuration object for radio button behavior
46
+ * @param params.checked - Whether radio button is selected
47
+ * @param params.disabled - Whether radio button is disabled
48
+ * @param params.size - Size variant ('sm' | 'md' | 'lg')
49
+ * @param params.color - Theme color token for checked state
50
+ * @param params.label - Associated label text
51
+ * @param params.accessibilityLabel - Custom a11y label
52
+ * @param params.accessibilityHint - Describes action result
53
+ *
54
+ * @returns Computed values for rendering RadioButton
55
+ * @returns {number} innerDotSize - Inner dot size in pixels
56
+ * @returns {ViewStyle} containerStyle - Dynamic container styles
57
+ * @returns {Insets} hitSlop - Touch target expansion
58
+ * @returns {object} a11yProps - Accessibility props with checked state
59
+ *
60
+ * @performance This hook uses useMemo() for expensive calculations
61
+ *
62
+ * @example
63
+ * const { innerDotSize, containerStyle, hitSlop, a11yProps } = useRadioButtonLogic({
64
+ * checked: true,
65
+ * disabled: false,
66
+ * size: "md",
67
+ * color: "accentPrimary",
68
+ * label: "Option A - Standard shipping",
69
+ * });
70
+ */
71
+ export function useRadioButtonLogic(
72
+ params: UseRadioButtonLogicParams
73
+ ): UseRadioButtonLogicReturn {
74
+ const {
75
+ checked,
76
+ disabled,
77
+ size,
78
+ color,
79
+ label,
80
+ accessibilityLabel,
81
+ accessibilityHint,
82
+ } = params;
83
+
84
+ const { labels: t } = useDesignSystem();
85
+ const theme = useRestyleTheme();
86
+ const resolvedSize = (useResponsiveProp(size) ?? "md") as RadioButtonSize;
87
+
88
+ const innerDotSize = getRadioButtonInnerSize(resolvedSize);
89
+ const hitSlop = getRadioButtonHitSlop(resolvedSize);
90
+
91
+ const containerStyle = useMemo((): ViewStyle => {
92
+ const styleResult = resolveRadioButtonStyle({
93
+ checked,
94
+ disabled,
95
+ color,
96
+ theme,
97
+ });
98
+
99
+ return {
100
+ backgroundColor: styleResult.backgroundColor,
101
+ borderColor: styleResult.borderColor,
102
+ opacity: styleResult.opacity,
103
+ };
104
+ }, [checked, disabled, color, theme]);
105
+
106
+ const a11yProps = useMemo(
107
+ () => getRadioButtonA11y({ label, accessibilityLabel, accessibilityHint, checked, disabled, fallbackLabel: t.designSystem.radioButton.fallbackLabel }),
108
+ [label, accessibilityLabel, accessibilityHint, checked, disabled, t.designSystem.radioButton.fallbackLabel]
109
+ );
110
+
111
+ return {
112
+ innerDotSize,
113
+ containerStyle,
114
+ hitSlop,
115
+ a11yProps,
116
+ };
117
+ }
@@ -0,0 +1,174 @@
1
+ import type { RestyleTheme } from "../../../core/restyle";
2
+ import type {
3
+ SegmentedControlSize,
4
+ SegmentedControlVariant,
5
+ } from "./SegmentedControl.types";
6
+
7
+ // =============================================================================
8
+ // TYPOGRAPHY MAPPING
9
+ // =============================================================================
10
+
11
+ /**
12
+ * Map segment size to text typography variant.
13
+ *
14
+ * | Size | Typography |
15
+ * |------|------------- |
16
+ * | sm | labelSmall |
17
+ * | md | labelMedium |
18
+ * | lg | labelLarge |
19
+ */
20
+ export const SEGMENT_TEXT_VARIANT: Record<
21
+ SegmentedControlSize,
22
+ "labelSmall" | "labelMedium" | "labelLarge"
23
+ > = {
24
+ sm: "labelSmall",
25
+ md: "labelMedium",
26
+ lg: "labelLarge",
27
+ };
28
+
29
+ /**
30
+ * Size configuration mapping
31
+ */
32
+ const SIZE_CONFIG: Record<
33
+ SegmentedControlSize,
34
+ {
35
+ paddingVertical: keyof RestyleTheme["spacing"];
36
+ paddingHorizontal: keyof RestyleTheme["spacing"];
37
+ containerPadding: keyof RestyleTheme["spacing"];
38
+ borderRadius: keyof RestyleTheme["borderRadii"];
39
+ }
40
+ > = {
41
+ sm: {
42
+ paddingVertical: "xs",
43
+ paddingHorizontal: "sm",
44
+ containerPadding: "2xs",
45
+ borderRadius: "md",
46
+ },
47
+ md: {
48
+ paddingVertical: "sm",
49
+ paddingHorizontal: "md",
50
+ containerPadding: "2xs",
51
+ borderRadius: "lg",
52
+ },
53
+ lg: {
54
+ paddingVertical: "md",
55
+ paddingHorizontal: "lg",
56
+ containerPadding: "xs",
57
+ borderRadius: "lg",
58
+ },
59
+ };
60
+
61
+ /**
62
+ * Variant configuration mapping
63
+ */
64
+ const VARIANT_CONFIG: Record<
65
+ SegmentedControlVariant,
66
+ {
67
+ containerBackground: keyof RestyleTheme["colors"];
68
+ selectedBackground: keyof RestyleTheme["colors"];
69
+ unselectedBackground: keyof RestyleTheme["colors"];
70
+ selectedTextColor: keyof RestyleTheme["colors"];
71
+ unselectedTextColor: keyof RestyleTheme["colors"];
72
+ disabledTextColor: keyof RestyleTheme["colors"];
73
+ }
74
+ > = {
75
+ default: {
76
+ containerBackground: "backgroundTertiary",
77
+ selectedBackground: "backgroundPrimary",
78
+ unselectedBackground: "transparent",
79
+ selectedTextColor: "accentPrimary",
80
+ unselectedTextColor: "textSecondary",
81
+ disabledTextColor: "textDisabled",
82
+ },
83
+ outline: {
84
+ containerBackground: "transparent",
85
+ selectedBackground: "accentPrimary",
86
+ unselectedBackground: "transparent",
87
+ selectedTextColor: "textInverse",
88
+ unselectedTextColor: "textPrimary",
89
+ disabledTextColor: "textDisabled",
90
+ },
91
+ };
92
+
93
+ /**
94
+ * Get container styles based on size and variant
95
+ */
96
+ export function getContainerStyles(
97
+ size: SegmentedControlSize,
98
+ variant: SegmentedControlVariant
99
+ ): {
100
+ backgroundColor: keyof RestyleTheme["colors"];
101
+ borderRadius: keyof RestyleTheme["borderRadii"];
102
+ padding: keyof RestyleTheme["spacing"];
103
+ } {
104
+ const sizeConfig = SIZE_CONFIG[size] || SIZE_CONFIG.md;
105
+ const variantConfig = VARIANT_CONFIG[variant] || VARIANT_CONFIG.default;
106
+
107
+ return {
108
+ backgroundColor: variantConfig.containerBackground,
109
+ borderRadius: sizeConfig.borderRadius,
110
+ padding: sizeConfig.containerPadding,
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Get segment styles based on selection state
116
+ */
117
+ export function getSegmentStyles(
118
+ size: SegmentedControlSize,
119
+ variant: SegmentedControlVariant,
120
+ isSelected: boolean,
121
+ _isDisabled: boolean
122
+ ): {
123
+ backgroundColor: keyof RestyleTheme["colors"];
124
+ borderRadius: keyof RestyleTheme["borderRadii"];
125
+ paddingVertical: keyof RestyleTheme["spacing"];
126
+ paddingHorizontal: keyof RestyleTheme["spacing"];
127
+ } {
128
+ const sizeConfig = SIZE_CONFIG[size] || SIZE_CONFIG.md;
129
+ const variantConfig = VARIANT_CONFIG[variant] || VARIANT_CONFIG.default;
130
+
131
+ return {
132
+ backgroundColor: isSelected
133
+ ? variantConfig.selectedBackground
134
+ : variantConfig.unselectedBackground,
135
+ borderRadius: sizeConfig.borderRadius,
136
+ paddingVertical: sizeConfig.paddingVertical,
137
+ paddingHorizontal: sizeConfig.paddingHorizontal,
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Get text color based on selection state
143
+ */
144
+ export function getTextColor(
145
+ variant: SegmentedControlVariant,
146
+ isSelected: boolean,
147
+ isDisabled: boolean
148
+ ): keyof RestyleTheme["colors"] {
149
+ const variantConfig = VARIANT_CONFIG[variant] || VARIANT_CONFIG.default;
150
+
151
+ if (isDisabled) {
152
+ return variantConfig.disabledTextColor;
153
+ }
154
+
155
+ return isSelected
156
+ ? variantConfig.selectedTextColor
157
+ : variantConfig.unselectedTextColor;
158
+ }
159
+
160
+ /**
161
+ * Validate options array
162
+ */
163
+ export function validateOptions(
164
+ options: { value: string; label: string }[]
165
+ ): boolean {
166
+ if (!Array.isArray(options) || options.length < 2) {
167
+ return false;
168
+ }
169
+
170
+ const values = options.map((o) => o.value);
171
+ const uniqueValues = new Set(values);
172
+
173
+ return uniqueValues.size === values.length;
174
+ }