@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,177 @@
1
+ /**
2
+ * SelectSheetOption Component
3
+ *
4
+ * @description Individual option row for the SelectSheet - Atom
5
+ *
6
+ * Renders a single selectable option with radio/checkbox indicator,
7
+ * optional media content (icon or image), and label text.
8
+ * Follows the design system's atomic primacy and touch target rules.
9
+ *
10
+ * ## Layout
11
+ * | Position | Left Indicator | Right Indicator (default) |
12
+ * |----------|-------------------------|---------------------------|
13
+ * | left | Radio/Checkbox → Media → Label | |
14
+ * | right | Media → Label → Radio/Checkbox | |
15
+ *
16
+ * ## Features
17
+ * - Supports icon (MaterialIcons) or image URL media
18
+ * - Configurable indicator position (left/right)
19
+ * - Consistent sizing with touch targets (52px row height)
20
+ * - Press feedback with background color change
21
+ * - Disabled state with reduced opacity
22
+ * - Full accessibility support
23
+ * - Memoized for performance
24
+ *
25
+ * @see SelectSheet.types.ts - Type definitions
26
+ * @see SelectSheet.a11y.ts - Accessibility prop generation
27
+ * @see tokens/scales.ts - SELECT_SHEET_LAYOUT
28
+ *
29
+ * @example
30
+ * // Basic usage (internal to SelectSheet)
31
+ * <SelectSheetOptionRow
32
+ * option={{ value: "a", label: "Option A" }}
33
+ * isSelected={true}
34
+ * isMultiple={false}
35
+ * onPress={handlePress}
36
+ * showDivider={true}
37
+ * />
38
+ *
39
+ * @example
40
+ * // With icon and left indicator
41
+ * <SelectSheetOptionRow
42
+ * option={{ value: "b", label: "Option B", icon: "star" }}
43
+ * isSelected={false}
44
+ * isMultiple={true}
45
+ * onPress={handlePress}
46
+ * indicatorPosition="left"
47
+ * />
48
+ */
49
+
50
+ import React, { memo, useCallback } from "react";
51
+ import { Image } from "react-native";
52
+ import { SELECT_SHEET_LAYOUT } from "../../../tokens/scales";
53
+ import { Icon } from "../../content/Icon";
54
+ import { Box, Pressable } from "../../layout";
55
+ import { Divider } from "../../layout/Divider";
56
+ import { Text } from "../../typography";
57
+ import { Checkbox } from "../Checkbox";
58
+ import { RadioButton } from "../RadioButton";
59
+ import type { SelectSheetOptionRowProps } from "./SelectSheet.types";
60
+
61
+ // =============================================================================
62
+ // CONSTANTS
63
+ // =============================================================================
64
+
65
+ /** Standard image size for option media */
66
+ const IMAGE_SIZE = 32;
67
+ /** Standard icon/control size */
68
+ const CONTROL_SIZE = "md" as const;
69
+
70
+ // =============================================================================
71
+ // COMPONENT IMPLEMENTATION
72
+ // =============================================================================
73
+
74
+ function SelectSheetOptionRowComponent<T>({
75
+ option,
76
+ isSelected,
77
+ isMultiple,
78
+ onPress,
79
+ showDivider,
80
+ indicatorPosition = "right",
81
+ a11yProps,
82
+ testID,
83
+ }: SelectSheetOptionRowProps<T>) {
84
+
85
+ // Handler for pressable
86
+ const handlePress = useCallback(() => {
87
+ if (!option.disabled) {
88
+ onPress();
89
+ }
90
+ }, [option.disabled, onPress]);
91
+
92
+ // Selection indicator component
93
+ const SelectionIndicator = isMultiple ? (
94
+ <Checkbox
95
+ checked={isSelected}
96
+ onPress={onPress}
97
+ disabled={option.disabled}
98
+ size={CONTROL_SIZE}
99
+ />
100
+ ) : (
101
+ <RadioButton
102
+ checked={isSelected}
103
+ onPress={onPress}
104
+ disabled={option.disabled}
105
+ size={CONTROL_SIZE}
106
+ />
107
+ );
108
+
109
+ // Media content (icon or image)
110
+ const MediaContent = option.imageUrl ? (
111
+ <Image
112
+ source={{ uri: option.imageUrl }}
113
+ style={{
114
+ width: IMAGE_SIZE,
115
+ height: IMAGE_SIZE,
116
+ borderRadius: IMAGE_SIZE / 2,
117
+ }}
118
+ accessibilityIgnoresInvertColors
119
+ />
120
+ ) : option.icon ? (
121
+ <Icon
122
+ name={option.icon}
123
+ size={CONTROL_SIZE}
124
+ color={isSelected ? "accentPrimary" : "textSecondary"}
125
+ />
126
+ ) : null;
127
+
128
+ return (
129
+ <>
130
+ <Pressable
131
+ onPress={handlePress}
132
+ disabled={option.disabled}
133
+ testID={testID}
134
+ {...a11yProps}
135
+ >
136
+ {({ pressed }) => (
137
+ <Box
138
+ height={SELECT_SHEET_LAYOUT.optionRowHeight}
139
+ paddingHorizontal="lg"
140
+ paddingVertical="md"
141
+ flexDirection="row"
142
+ alignItems="center"
143
+ gap="md"
144
+ backgroundColor={
145
+ pressed && !option.disabled ? "backgroundTertiary" : "transparent"
146
+ }
147
+ opacity={option.disabled ? 0.5 : 1}
148
+ >
149
+ {/* Selection indicator on left */}
150
+ {indicatorPosition === "left" && SelectionIndicator}
151
+
152
+ {/* Media content (icon or image) */}
153
+ {MediaContent}
154
+
155
+ {/* Option label */}
156
+ <Box flex={1}>
157
+ <Text
158
+ variant="bodyMedium"
159
+ color={isSelected ? "accentPrimary" : "textPrimary"}
160
+ >
161
+ {option.label}
162
+ </Text>
163
+ </Box>
164
+
165
+ {/* Selection indicator on right (default) */}
166
+ {indicatorPosition === "right" && SelectionIndicator}
167
+ </Box>
168
+ )}
169
+ </Pressable>
170
+ {showDivider && <Divider />}
171
+ </>
172
+ );
173
+ }
174
+
175
+ export const SelectSheetOptionRow = memo(
176
+ SelectSheetOptionRowComponent,
177
+ ) as typeof SelectSheetOptionRowComponent;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * SelectSheet Component
3
+ *
4
+ * Barrel export for the SelectSheet component and related types.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * import { SelectSheet, type SelectSheetOption, type SelectSheetPosition } from '@/design-system/primitives/inputs/SelectSheet';
9
+ *
10
+ * <SelectSheet
11
+ * visible={true}
12
+ * options={options}
13
+ * value={selected}
14
+ * onChange={setSelected}
15
+ * onClose={handleClose}
16
+ * position="center"
17
+ * />
18
+ * ```
19
+ */
20
+
21
+ export { SelectSheet } from "./SelectSheet";
22
+ export { SelectSheetOptionRow } from "./SelectSheetOption";
23
+ export { useSelectSheetLogic } from "./useSelectSheetLogic";
24
+
25
+ export type {
26
+ SelectSheetProps,
27
+ SelectSheetOption,
28
+ SelectSheetMode,
29
+ SelectSheetPosition,
30
+ SelectSheetLayout,
31
+ SelectSheetOptionRowProps,
32
+ SelectSheetIndicatorPosition,
33
+ } from "./SelectSheet.types";
34
+
35
+ export {
36
+ normalizeValue,
37
+ denormalizeValue,
38
+ toggleSelection,
39
+ isValueSelected,
40
+ getDisplayLabel,
41
+ calculateListHeight,
42
+ filterOptions,
43
+ } from "./SelectSheet.helpers";
44
+
45
+ export {
46
+ getSheetA11yProps,
47
+ getOptionA11yProps,
48
+ } from "./SelectSheet.a11y";
@@ -0,0 +1,309 @@
1
+ /**
2
+ * Orchestrates SelectSheet rendering logic and state management.
3
+ *
4
+ * Handles:
5
+ * - Selection state management (single/multiple modes)
6
+ * - Temporary selections during multi-select (confirm/cancel)
7
+ * - Search/filtering of options
8
+ * - List height calculation based on screen size
9
+ * - Option press handling with mode-specific behavior
10
+ * - Confirm/cancel handlers for multi-select
11
+ * - Selection state checking
12
+ *
13
+ * @param params - Configuration object for select sheet behavior
14
+ * @param params.value - Current selected value(s)
15
+ * @param params.mode - Selection mode ('single' | 'multiple')
16
+ * @param params.visible - Whether sheet is visible
17
+ * @param params.options - Available options array
18
+ * @param params.onChange - Callback when value changes
19
+ * @param params.onClose - Callback when sheet should close
20
+ * @param params.listHeight - Custom list height override
21
+ * @param params.closeOnSelect - Whether to close immediately on single selection
22
+ *
23
+ * @returns Computed values for rendering SelectSheet
24
+ * @returns {T[]} tempSelections - Temporary selections (multi-select mode)
25
+ * @returns {SelectSheetOption<T>[]} filteredOptions - Filtered options by search
26
+ * @returns {string} searchQuery - Current search query
27
+ * @returns {function} handleOptionPress - Option press handler
28
+ * @returns {function} handleConfirm - Confirm selection (multi-select)
29
+ * @returns {function} handleCancel - Cancel selection (multi-select)
30
+ * @returns {function} handleSearchChange - Search query change handler
31
+ * @returns {function} isSelected - Check if option is selected
32
+ * @returns {SelectSheetLayout} layout - Layout configuration
33
+ * @returns {boolean} isMultiple - Whether in multiple mode
34
+ * @returns {boolean} hasSelection - Whether any options selected
35
+ *
36
+ * @performance
37
+ * - Uses useMemo() for filtered options and layout calculations
38
+ * - useCallback() for handler memoization
39
+ * - Syncs temp selections only when sheet becomes visible
40
+ *
41
+ * @example
42
+ * const {
43
+ * filteredOptions,
44
+ * handleOptionPress,
45
+ * handleConfirm,
46
+ * isSelected,
47
+ * layout
48
+ * } = useSelectSheetLogic({
49
+ * value: ["option1", "option3"],
50
+ * mode: "multiple",
51
+ * visible: true,
52
+ * options: myOptions,
53
+ * onChange: setValue,
54
+ * onClose: () => setVisible(false),
55
+ * });
56
+ */
57
+
58
+ import { useCallback, useEffect, useMemo, useState } from "react";
59
+ import { AccessibilityProps, useWindowDimensions } from "react-native";
60
+
61
+ import { useDesignSystem } from "../../../provider";
62
+ import {
63
+ calculateListHeight,
64
+ denormalizeValue,
65
+ filterOptions,
66
+ normalizeValue,
67
+ toggleSelection,
68
+ } from "./SelectSheet.helpers";
69
+ import { getSheetA11yProps, getOptionA11yProps } from "./SelectSheet.a11y";
70
+ import type {
71
+ SelectSheetLayout,
72
+ SelectSheetMode,
73
+ SelectSheetOption,
74
+ SelectSheetPosition,
75
+ } from "./SelectSheet.types";
76
+
77
+ // =============================================================================
78
+ // TYPES
79
+ // =============================================================================
80
+
81
+ interface UseSelectSheetLogicParams<T> {
82
+ /** Current value(s) */
83
+ value: T | T[] | null;
84
+ /** Selection mode */
85
+ mode: SelectSheetMode;
86
+ /** Whether sheet is visible */
87
+ visible: boolean;
88
+ /** Available options */
89
+ options: SelectSheetOption<T>[];
90
+ /** Callback when value changes */
91
+ onChange: (value: T | T[] | null) => void;
92
+ /** Callback when sheet should close */
93
+ onClose: () => void;
94
+ /** Custom list height override */
95
+ listHeight?: number;
96
+ /** Whether to close immediately on single selection */
97
+ closeOnSelect?: boolean;
98
+ /** Sheet title for a11y */
99
+ title?: string;
100
+ /** Sheet position for a11y */
101
+ position?: SelectSheetPosition;
102
+ /** Custom accessibility label */
103
+ accessibilityLabel?: string;
104
+ }
105
+
106
+ interface UseSelectSheetLogicReturn<T> {
107
+ // Selection state
108
+ tempSelections: T[];
109
+ filteredOptions: SelectSheetOption<T>[];
110
+ searchQuery: string;
111
+
112
+ // Handlers
113
+ handleOptionPress: (optionValue: T) => void;
114
+ handleConfirm: () => void;
115
+ handleCancel: () => void;
116
+ handleSearchChange: (query: string) => void;
117
+ isSelected: (optionValue: T) => boolean;
118
+
119
+ // Layout
120
+ layout: SelectSheetLayout;
121
+
122
+ // Computed flags
123
+ isMultiple: boolean;
124
+ hasSelection: boolean;
125
+
126
+ // Accessibility
127
+ sheetA11yProps: ReturnType<typeof getSheetA11yProps>;
128
+
129
+ // Option a11y factory
130
+ getOptionA11y: (params: {
131
+ label: string;
132
+ isSelected: boolean;
133
+ isDisabled: boolean;
134
+ isMultiple: boolean;
135
+ index: number;
136
+ total: number;
137
+ }) => AccessibilityProps;
138
+ }
139
+
140
+ // =============================================================================
141
+ // HOOK IMPLEMENTATION
142
+ // =============================================================================
143
+
144
+ export function useSelectSheetLogic<T>({
145
+ value,
146
+ mode,
147
+ visible,
148
+ options,
149
+ onChange,
150
+ onClose,
151
+ listHeight: customListHeight,
152
+ closeOnSelect,
153
+ title,
154
+ position = "center",
155
+ accessibilityLabel,
156
+ }: UseSelectSheetLogicParams<T>): UseSelectSheetLogicReturn<T> {
157
+ const isMultiple = mode === "multiple";
158
+ const { height: screenHeight } = useWindowDimensions();
159
+ const { labels: t } = useDesignSystem();
160
+
161
+ // -------------------------------------------------------------------------
162
+ // Selection State
163
+ // -------------------------------------------------------------------------
164
+
165
+ const [tempSelections, setTempSelections] = useState<T[]>(() =>
166
+ normalizeValue(value, isMultiple)
167
+ );
168
+ const [searchQuery, setSearchQuery] = useState("");
169
+
170
+ // Sync temp selections when value or visibility changes
171
+ useEffect(() => {
172
+ if (visible) {
173
+ setTempSelections(normalizeValue(value, isMultiple));
174
+ setSearchQuery(""); // Reset search on open
175
+ }
176
+ }, [value, visible, isMultiple]);
177
+
178
+ // -------------------------------------------------------------------------
179
+ // Filtered Options
180
+ // -------------------------------------------------------------------------
181
+
182
+ const filteredOptions = useMemo(
183
+ () => filterOptions(options, searchQuery),
184
+ [options, searchQuery]
185
+ );
186
+
187
+ // -------------------------------------------------------------------------
188
+ // Layout Calculations
189
+ // -------------------------------------------------------------------------
190
+
191
+ const layout = useMemo(
192
+ () => ({
193
+ listHeight: calculateListHeight(
194
+ options.length,
195
+ screenHeight,
196
+ customListHeight
197
+ ),
198
+ }),
199
+ [options.length, screenHeight, customListHeight]
200
+ );
201
+
202
+ // -------------------------------------------------------------------------
203
+ // Selection Logic
204
+ // -------------------------------------------------------------------------
205
+
206
+ const isSelected = useCallback(
207
+ (optionValue: T): boolean => tempSelections.includes(optionValue),
208
+ [tempSelections]
209
+ );
210
+
211
+ const hasSelection = tempSelections.length > 0;
212
+
213
+ const handleOptionPress = useCallback(
214
+ (optionValue: T) => {
215
+ if (isMultiple) {
216
+ // Multiple mode - toggle in temp selections
217
+ setTempSelections((prev) =>
218
+ toggleSelection(prev, optionValue, isMultiple)
219
+ );
220
+ } else {
221
+ // Single mode - select and optionally close immediately
222
+ onChange(optionValue);
223
+ if (closeOnSelect !== false) {
224
+ onClose();
225
+ }
226
+ }
227
+ },
228
+ [isMultiple, onChange, onClose, closeOnSelect]
229
+ );
230
+
231
+ const handleConfirm = useCallback(() => {
232
+ onChange(denormalizeValue(tempSelections, isMultiple));
233
+ onClose();
234
+ }, [tempSelections, isMultiple, onChange, onClose]);
235
+
236
+ const handleCancel = useCallback(() => {
237
+ setTempSelections(normalizeValue(value, isMultiple));
238
+ onClose();
239
+ }, [value, isMultiple, onClose]);
240
+
241
+ const handleSearchChange = useCallback((query: string) => {
242
+ setSearchQuery(query);
243
+ }, []);
244
+
245
+ // Accessibility
246
+ const positionLabels = useMemo(() => ({
247
+ bottom: t.designSystem.selectSheet.positionBottom,
248
+ top: t.designSystem.selectSheet.positionTop,
249
+ center: t.designSystem.selectSheet.positionCenter,
250
+ }), [t]);
251
+
252
+ const sheetA11yProps = useMemo(
253
+ () => getSheetA11yProps({
254
+ title,
255
+ position,
256
+ accessibilityLabel,
257
+ positionLabels,
258
+ fallbackTitle: t.designSystem.selectSheet.fallbackTitle,
259
+ }),
260
+ [title, position, accessibilityLabel, positionLabels, t],
261
+ );
262
+
263
+ const optionI18n = useMemo(() => ({
264
+ selectedLabel: t.designSystem.selectSheet.selected,
265
+ disabledLabel: t.designSystem.selectSheet.disabled,
266
+ ofLabel: t.designSystem.selectSheet.of,
267
+ }), [t]);
268
+
269
+ const getOptionA11y = useCallback(
270
+ (params: {
271
+ label: string;
272
+ isSelected: boolean;
273
+ isDisabled: boolean;
274
+ isMultiple: boolean;
275
+ index: number;
276
+ total: number;
277
+ }) => getOptionA11yProps({ ...params, ...optionI18n }),
278
+ [optionI18n],
279
+ );
280
+
281
+ // -------------------------------------------------------------------------
282
+ // Return Values
283
+ // -------------------------------------------------------------------------
284
+
285
+ return {
286
+ // Selection state
287
+ tempSelections,
288
+ filteredOptions,
289
+ searchQuery,
290
+
291
+ // Handlers
292
+ handleOptionPress,
293
+ handleConfirm,
294
+ handleCancel,
295
+ handleSearchChange,
296
+ isSelected,
297
+
298
+ // Layout
299
+ layout,
300
+
301
+ // Computed flags
302
+ isMultiple,
303
+ hasSelection,
304
+
305
+ // Accessibility
306
+ sheetA11yProps,
307
+ getOptionA11y,
308
+ };
309
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Switch Component Helpers
3
+ *
4
+ * Pure functions for computing Switch styles, dimensions, and colors.
5
+ * All calculations are derived from the centralized scales.ts token file.
6
+ */
7
+
8
+ import { Insets } from "react-native";
9
+ import {
10
+ calculateSwitchThumbSize,
11
+ SWITCH_DIMENSIONS,
12
+ SWITCH_HIT_SLOP,
13
+ SWITCH_THUMB_INSET,
14
+ } from "../../../tokens/scales";
15
+ import {
16
+ ResolveSwitchStyleParams,
17
+ SwitchDimensions,
18
+ SwitchSize,
19
+ SwitchStyleResult,
20
+ } from "./Switch.types";
21
+
22
+ // =============================================================================
23
+ // DIMENSION RESOLUTION
24
+ // =============================================================================
25
+
26
+ /**
27
+ * Get switch dimensions based on size.
28
+ *
29
+ * Uses SWITCH_DIMENSIONS from scales.ts for consistent sizing.
30
+ * Thumb size is calculated using SWITCH_THUMB_INSET for consistent padding.
31
+ *
32
+ * | Size | Track W | Track H | Thumb | Inset |
33
+ * |------|---------|---------|-------|-------|
34
+ * | sm | 40px | 24px | 20px | 2px |
35
+ * | md | 48px | 28px | 24px | 2px |
36
+ * | lg | 56px | 32px | 28px | 2px |
37
+ *
38
+ * Thumb size formula: trackHeight - (SWITCH_THUMB_INSET * 2)
39
+ *
40
+ * @param size - The switch size variant
41
+ * @returns Track width, track height, and thumb size
42
+ */
43
+ export function getSwitchDimensions(size: SwitchSize): SwitchDimensions {
44
+ const dimensions = SWITCH_DIMENSIONS[size] ?? SWITCH_DIMENSIONS.md;
45
+ const thumbSize = calculateSwitchThumbSize(dimensions.trackHeight);
46
+
47
+ return {
48
+ trackWidth: dimensions.trackWidth,
49
+ trackHeight: dimensions.trackHeight,
50
+ thumbSize,
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Get the thumb inset value.
56
+ * This is the padding between the track edge and the thumb.
57
+ *
58
+ * Exposed for components that need to calculate thumb position.
59
+ *
60
+ * @returns The thumb inset in pixels
61
+ */
62
+ export function getSwitchThumbInset(): number {
63
+ return SWITCH_THUMB_INSET;
64
+ }
65
+
66
+ // =============================================================================
67
+ // TOUCH TARGET (hitSlop)
68
+ // =============================================================================
69
+
70
+ /**
71
+ * Get hitSlop based on switch size.
72
+ *
73
+ * Uses pre-calculated values from scales.ts to ensure
74
+ * all switches meet the 44px minimum touch target.
75
+ *
76
+ * | Size | Track H | hitSlop | Total |
77
+ * |------|---------|---------|-------|
78
+ * | sm | 24px | 10px | 44px |
79
+ * | md | 28px | 8px | 44px |
80
+ * | lg | 32px | 6px | 44px |
81
+ *
82
+ * @param size - The switch size variant
83
+ * @returns Insets for touch target expansion
84
+ */
85
+ export function getSwitchHitSlop(size: SwitchSize): Insets {
86
+ return SWITCH_HIT_SLOP[size];
87
+ }
88
+
89
+ // =============================================================================
90
+ // STYLE RESOLUTION
91
+ // =============================================================================
92
+
93
+ /**
94
+ * Resolve dynamic style properties for the Switch container.
95
+ *
96
+ * This function handles disabled state opacity.
97
+ * Track and thumb colors are handled by animated interpolations
98
+ * in the useSwitchLogic hook.
99
+ *
100
+ * @param params - Resolution parameters
101
+ * @returns Computed style object
102
+ */
103
+ export function resolveSwitchStyle(
104
+ params: ResolveSwitchStyleParams
105
+ ): SwitchStyleResult {
106
+ return {
107
+ opacity: 1,
108
+ };
109
+ }