@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,147 @@
1
+ /**
2
+ * SelectSheet Helper Functions
3
+ *
4
+ * Pure utility functions for SelectSheet component.
5
+ * All values derived from tokens/scales.ts for consistency.
6
+ *
7
+ * @see tokens/scales.ts - SELECT_SHEET_LAYOUT
8
+ */
9
+
10
+ import { SELECT_SHEET_LAYOUT } from "../../../tokens/scales";
11
+ import type {
12
+ SelectSheetOption,
13
+ } from "./SelectSheet.types";
14
+
15
+ // =============================================================================
16
+ // SELECTION LOGIC HELPERS
17
+ // =============================================================================
18
+
19
+ /**
20
+ * Normalize value to array format for internal use.
21
+ */
22
+ export function normalizeValue<T>(
23
+ value: T | T[] | null,
24
+ isMultiple: boolean
25
+ ): T[] {
26
+ if (value === null || value === undefined) {
27
+ return [];
28
+ }
29
+ if (isMultiple) {
30
+ return Array.isArray(value) ? value : [value];
31
+ }
32
+ return Array.isArray(value) ? value : [value];
33
+ }
34
+
35
+ /**
36
+ * Denormalize array back to expected format based on mode.
37
+ */
38
+ export function denormalizeValue<T>(
39
+ values: T[],
40
+ isMultiple: boolean
41
+ ): T | T[] | null {
42
+ if (isMultiple) {
43
+ return values;
44
+ }
45
+ return values.length > 0 ? values[0] : null;
46
+ }
47
+
48
+ /**
49
+ * Toggle selection in array.
50
+ */
51
+ export function toggleSelection<T>(
52
+ currentSelections: T[],
53
+ value: T,
54
+ isMultiple: boolean
55
+ ): T[] {
56
+ if (isMultiple) {
57
+ const index = currentSelections.indexOf(value);
58
+ if (index >= 0) {
59
+ return currentSelections.filter((v) => v !== value);
60
+ }
61
+ return [...currentSelections, value];
62
+ }
63
+ // Single mode - replace selection
64
+ return [value];
65
+ }
66
+
67
+ /**
68
+ * Check if value is selected.
69
+ */
70
+ export function isValueSelected<T>(selections: T[], value: T): boolean {
71
+ return selections.includes(value);
72
+ }
73
+
74
+ // =============================================================================
75
+ // DISPLAY HELPERS
76
+ // =============================================================================
77
+
78
+ /**
79
+ * Get display label for selected value(s).
80
+ */
81
+ export function getDisplayLabel<T>(
82
+ options: SelectSheetOption<T>[],
83
+ value: T | T[] | null,
84
+ placeholder: string = "Select..."
85
+ ): string {
86
+ if (value === null || value === undefined) {
87
+ return placeholder;
88
+ }
89
+
90
+ const values = Array.isArray(value) ? value : [value];
91
+ if (values.length === 0) {
92
+ return placeholder;
93
+ }
94
+
95
+ const labels = values
96
+ .map((v) => options.find((o) => o.value === v)?.label)
97
+ .filter(Boolean);
98
+
99
+ if (labels.length === 0) {
100
+ return placeholder;
101
+ }
102
+
103
+ if (labels.length === 1) {
104
+ return labels[0] as string;
105
+ }
106
+
107
+ return `${labels[0]} +${labels.length - 1} more`;
108
+ }
109
+
110
+ // =============================================================================
111
+ // LAYOUT CALCULATION HELPERS
112
+ // =============================================================================
113
+
114
+ /**
115
+ * Calculate list height based on custom prop or default.
116
+ * If customListHeight is provided, uses that.
117
+ * Otherwise uses the default fixed height from tokens.
118
+ */
119
+ export function calculateListHeight(
120
+ optionCount: number,
121
+ screenHeight: number,
122
+ customListHeight?: number
123
+ ): number {
124
+ if (customListHeight) {
125
+ return customListHeight;
126
+ }
127
+
128
+ // Use fixed default height, but don't exceed viewport height
129
+ return Math.min(SELECT_SHEET_LAYOUT.defaultListHeight, screenHeight);
130
+ }
131
+
132
+ /**
133
+ * Filter options by search query.
134
+ */
135
+ export function filterOptions<T>(
136
+ options: SelectSheetOption<T>[],
137
+ query: string
138
+ ): SelectSheetOption<T>[] {
139
+ if (!query.trim()) {
140
+ return options;
141
+ }
142
+
143
+ const normalizedQuery = query.toLowerCase().trim();
144
+ return options.filter((option) =>
145
+ option.label.toLowerCase().includes(normalizedQuery)
146
+ );
147
+ }
@@ -0,0 +1,247 @@
1
+ /**
2
+ * SelectSheet Component
3
+ *
4
+ * @description Selection dialog for picking from a list of options - Molecule
5
+ *
6
+ * SelectSheet presents a scrollable list of options in a modal dialog.
7
+ * Supports single and multiple selection modes with radio/checkbox indicators.
8
+ * Built on top of the Modal component for consistent overlay behavior.
9
+ *
10
+ * ## Selection Modes
11
+ * | Mode | Indicator | Behavior |
12
+ * |----------|------------|-----------------------------------|
13
+ * | single | RadioButton| Selects one, closes on select |
14
+ * | multiple | Checkbox | Selects many, "Done" to confirm |
15
+ *
16
+ * ## Width Behavior
17
+ * | Viewport | Width |
18
+ * |----------|------------------------------------------|
19
+ * | Phone | Full width minus lg (16px) horizontal |
20
+ * | Tablet | Capped at 480px max-width |
21
+ *
22
+ * ## Positions
23
+ * - `center`: Standard dialog centered on screen (default)
24
+ * - `top`: Anchored to top with slide-down animation
25
+ * - `bottom`: Anchored to bottom with slide-up animation
26
+ *
27
+ * ## Features
28
+ * - Single and multiple selection modes
29
+ * - Radio/checkbox indicators (left or right position)
30
+ * - Optional icons and images per option
31
+ * - Virtualized list (FlatList) for performance
32
+ * - Configurable list height
33
+ * - Built on Modal for consistent overlay UX
34
+ * - Full accessibility support
35
+ * - Memoized for performance
36
+ *
37
+ * @see SelectSheet.types.ts - Type definitions
38
+ * @see SelectSheet.helpers.ts - Pure calculation functions
39
+ * @see SelectSheet.a11y.ts - Accessibility prop generation
40
+ * @see SelectSheetOption.tsx - Individual option row
41
+ * @see tokens/scales.ts - SELECT_SHEET_LAYOUT
42
+ *
43
+ * @example
44
+ * // Single select
45
+ * <SelectSheet
46
+ * visible={isOpen}
47
+ * onClose={() => setOpen(false)}
48
+ * options={[
49
+ * { value: "usd", label: "US Dollar" },
50
+ * { value: "eur", label: "Euro" },
51
+ * ]}
52
+ * value="usd"
53
+ * onChange={setCurrency}
54
+ * />
55
+ *
56
+ * @example
57
+ * // Multiple select with icons
58
+ * <SelectSheet
59
+ * visible={isOpen}
60
+ * onClose={() => setOpen(false)}
61
+ * mode="multiple"
62
+ * title="Select Categories"
63
+ * options={categories.map(c => ({ value: c.id, label: c.name, icon: c.icon }))}
64
+ * value={selectedIds}
65
+ * onChange={setSelectedIds}
66
+ * position="bottom"
67
+ * />
68
+ */
69
+
70
+ import React, { memo, useCallback } from "react";
71
+ import { useDesignSystem } from "../../../provider";
72
+ import { SELECT_SHEET_LAYOUT } from "../../../tokens/scales";
73
+ import { Button } from "../../actions/Button";
74
+ import { Box, FlatList } from "../../layout";
75
+ import type { ModalPosition } from "../../overlays/Modal";
76
+ import { Modal } from "../../overlays/Modal";
77
+ import type { SelectSheetOption, SelectSheetProps } from "./SelectSheet.types";
78
+ import { SelectSheetOptionRow } from "./SelectSheetOption";
79
+ import { useSelectSheetLogic } from "./useSelectSheetLogic";
80
+
81
+ // =============================================================================
82
+ // DEFAULT VALUES
83
+ // =============================================================================
84
+
85
+ const DEFAULT_TITLE = "Select";
86
+
87
+ // =============================================================================
88
+ // COMPONENT IMPLEMENTATION
89
+ // =============================================================================
90
+
91
+ function SelectSheetComponent<T = string>({
92
+ visible,
93
+ onClose,
94
+ options,
95
+ value,
96
+ onChange,
97
+ mode = "single",
98
+ position = "center",
99
+ title = DEFAULT_TITLE,
100
+ doneText,
101
+ listHeight: customListHeight,
102
+ showDividers = true,
103
+ closeOnSelect,
104
+ indicatorPosition = "right",
105
+ testID,
106
+ accessibilityLabel,
107
+ }: SelectSheetProps<T>) {
108
+ const { labels: t } = useDesignSystem();
109
+ const effectiveDoneText = doneText ?? t.designSystem.selectSheet.doneLabel;
110
+
111
+ const {
112
+ filteredOptions,
113
+ handleOptionPress,
114
+ handleConfirm,
115
+ handleCancel,
116
+ isSelected,
117
+ isMultiple,
118
+ layout,
119
+ sheetA11yProps,
120
+ getOptionA11y,
121
+ } = useSelectSheetLogic({
122
+ value,
123
+ mode,
124
+ visible,
125
+ options,
126
+ onChange,
127
+ onClose,
128
+ listHeight: customListHeight,
129
+ title,
130
+ position,
131
+ accessibilityLabel,
132
+ });
133
+
134
+ // Map SelectSheet position to Modal position
135
+ const modalPosition: ModalPosition = position;
136
+
137
+ // Render option item
138
+ const renderOption = useCallback(
139
+ ({ item, index }: { item: SelectSheetOption<T>; index: number }) => (
140
+ <SelectSheetOptionRow
141
+ option={item}
142
+ isSelected={isSelected(item.value)}
143
+ isMultiple={isMultiple}
144
+ onPress={() => handleOptionPress(item.value)}
145
+ showDivider={showDividers && index < filteredOptions.length - 1}
146
+ indicatorPosition={indicatorPosition}
147
+ a11yProps={getOptionA11y({
148
+ label: item.label,
149
+ isSelected: isSelected(item.value),
150
+ isDisabled: item.disabled || false,
151
+ isMultiple,
152
+ index,
153
+ total: filteredOptions.length,
154
+ })}
155
+ testID={testID ? `${testID}-option-${index}` : undefined}
156
+ />
157
+ ),
158
+ [
159
+ isSelected,
160
+ isMultiple,
161
+ handleOptionPress,
162
+ showDividers,
163
+ filteredOptions.length,
164
+ indicatorPosition,
165
+ getOptionA11y,
166
+ testID,
167
+ ],
168
+ );
169
+
170
+ const keyExtractor = useCallback(
171
+ (item: SelectSheetOption<T>, index: number) =>
172
+ `option-${index}-${String(item.value)}`,
173
+ [],
174
+ );
175
+
176
+ const getItemLayout = useCallback(
177
+ (
178
+ _data: ArrayLike<SelectSheetOption<T>> | null | undefined,
179
+ index: number,
180
+ ) => ({
181
+ length: SELECT_SHEET_LAYOUT.optionRowHeight,
182
+ offset: SELECT_SHEET_LAYOUT.optionRowHeight * index,
183
+ index,
184
+ }),
185
+ [],
186
+ );
187
+
188
+ return (
189
+ <Modal
190
+ visible={visible}
191
+ onClose={handleCancel}
192
+ title={title}
193
+ position={modalPosition}
194
+ closeOnBackdropPress
195
+ showCloseButton={true}
196
+ accessibilityLabel={accessibilityLabel}
197
+ >
198
+ <Box testID={testID} {...sheetA11yProps}>
199
+ {/* Options List */}
200
+ <Box style={{ maxHeight: layout.listHeight }}>
201
+ <FlatList
202
+ data={filteredOptions}
203
+ renderItem={renderOption}
204
+ keyExtractor={keyExtractor}
205
+ getItemLayout={getItemLayout}
206
+ showsVerticalScrollIndicator={false}
207
+ bounces={false}
208
+ initialNumToRender={10}
209
+ maxToRenderPerBatch={10}
210
+ windowSize={5}
211
+ />
212
+ </Box>
213
+
214
+ {/* Footer - Done button for multiple mode */}
215
+ {isMultiple && (
216
+ <Box
217
+ paddingHorizontal="lg"
218
+ paddingVertical="md"
219
+ borderTopWidth={1}
220
+ borderTopColor="borderSubtle"
221
+ flexDirection="row"
222
+ justifyContent="flex-end"
223
+ >
224
+ <Button
225
+ title={effectiveDoneText}
226
+ variant="ghost"
227
+ size="md"
228
+ onPress={handleConfirm}
229
+ testID={testID ? `${testID}-done` : undefined}
230
+ />
231
+ </Box>
232
+ )}
233
+ </Box>
234
+ </Modal>
235
+ );
236
+ }
237
+
238
+ // =============================================================================
239
+ // EXPORTS
240
+ // =============================================================================
241
+
242
+ export const SelectSheet = memo(SelectSheetComponent) as <T = string>(
243
+ props: SelectSheetProps<T>,
244
+ ) => React.ReactElement;
245
+
246
+ // @ts-expect-error - displayName on generic component
247
+ SelectSheet.displayName = "SelectSheet";
@@ -0,0 +1,196 @@
1
+ /**
2
+ * SelectSheet Component Types
3
+ *
4
+ * Type definitions for the SelectSheet selection dialog component.
5
+ * Follows Modal pattern with consistent width behavior:
6
+ * - Phone: Full width minus lg (16px) horizontal padding
7
+ * - Tablet (≥768px): Capped at 480px max-width (phone-like experience)
8
+ *
9
+ * @see tokens/scales.ts - SELECT_SHEET_LAYOUT
10
+ * @see core/restyle/restyleTheme.ts - selectSheetVariants
11
+ */
12
+
13
+ import { IconName } from "../../content/Icon";
14
+
15
+ // =============================================================================
16
+ // POSITION TYPES
17
+ // =============================================================================
18
+
19
+ /**
20
+ * SelectSheet position controlling vertical placement.
21
+ *
22
+ * - `center`: Centered vertically (default, standard dialog)
23
+ * - `top`: Anchored to top of screen
24
+ * - `bottom`: Bottom sheet anchored to screen bottom
25
+ */
26
+ export type SelectSheetPosition = "center" | "top" | "bottom";
27
+
28
+ // =============================================================================
29
+ // OPTION TYPES
30
+ // =============================================================================
31
+
32
+ /**
33
+ * Individual option for the SelectSheet
34
+ */
35
+ export interface SelectSheetOption<T = string> {
36
+ /** Unique value for the option */
37
+ value: T;
38
+ /** Display label */
39
+ label: string;
40
+ /** Optional icon name from MaterialIcons */
41
+ icon?: IconName;
42
+ /** Optional image URL to display */
43
+ imageUrl?: string;
44
+ /** Whether this option is disabled */
45
+ disabled?: boolean;
46
+ }
47
+
48
+ /**
49
+ * Position of the selection indicator (checkbox/radio button)
50
+ */
51
+ export type SelectSheetIndicatorPosition = "left" | "right";
52
+
53
+ // =============================================================================
54
+ // MODE TYPES
55
+ // =============================================================================
56
+
57
+ /**
58
+ * Selection mode - single or multiple
59
+ */
60
+ export type SelectSheetMode = "single" | "multiple";
61
+
62
+ // =============================================================================
63
+ // COMPONENT PROPS
64
+ // =============================================================================
65
+
66
+ /**
67
+ * SelectSheet component props.
68
+ *
69
+ * @template T - Type of option values (defaults to string)
70
+ */
71
+ export interface SelectSheetProps<T = string> {
72
+ // -------------------------------------------------------------------------
73
+ // Required Props
74
+ // -------------------------------------------------------------------------
75
+
76
+ /** Whether the sheet is visible */
77
+ visible: boolean;
78
+ /** Callback when sheet should close */
79
+ onClose: () => void;
80
+ /** Available options */
81
+ options: SelectSheetOption<T>[];
82
+ /** Currently selected value(s) */
83
+ value: T | T[] | null;
84
+ /** Callback when selection changes */
85
+ onChange: (value: T | T[] | null) => void;
86
+
87
+ // -------------------------------------------------------------------------
88
+ // Position Props
89
+ // -------------------------------------------------------------------------
90
+
91
+ /**
92
+ * Vertical position of the sheet
93
+ * - "center": Centered modal dialog
94
+ * - "top": Anchored to top of screen
95
+ * - "bottom": Bottom sheet anchored to screen bottom
96
+ * @default "center"
97
+ */
98
+ position?: SelectSheetPosition;
99
+
100
+ // -------------------------------------------------------------------------
101
+ // Behavior Props
102
+ // -------------------------------------------------------------------------
103
+
104
+ /**
105
+ * Selection mode
106
+ * @default "single"
107
+ */
108
+ mode?: SelectSheetMode;
109
+
110
+ /**
111
+ * Whether to close immediately on single selection
112
+ * @default true (for single mode), false (for multiple mode)
113
+ */
114
+ closeOnSelect?: boolean;
115
+
116
+ /**
117
+ * Position of the selection indicator (checkbox/radio button)
118
+ * @default "right"
119
+ */
120
+ indicatorPosition?: SelectSheetIndicatorPosition;
121
+
122
+ // -------------------------------------------------------------------------
123
+ // Content Props
124
+ // -------------------------------------------------------------------------
125
+
126
+ /** Dialog/sheet title */
127
+ title?: string;
128
+ /** Done button text (multiple mode) */
129
+ doneText?: string;
130
+
131
+ // -------------------------------------------------------------------------
132
+ // Layout Props
133
+ // -------------------------------------------------------------------------
134
+
135
+ /**
136
+ * Custom height of the list container.
137
+ * If not provided, calculates based on option count (max 70% of viewport).
138
+ */
139
+ listHeight?: number;
140
+
141
+ /** Whether to show dividers between options */
142
+ showDividers?: boolean;
143
+
144
+ // -------------------------------------------------------------------------
145
+ // Accessibility Props
146
+ // -------------------------------------------------------------------------
147
+
148
+ /** Test ID for testing */
149
+ testID?: string;
150
+ /** Accessibility label */
151
+ accessibilityLabel?: string;
152
+ }
153
+
154
+ // =============================================================================
155
+ // INTERNAL TYPES
156
+ // =============================================================================
157
+
158
+ /**
159
+ * Internal hook state for managing temporary selections.
160
+ */
161
+ export interface SelectSheetState<T = string> {
162
+ /** Temporary selections before confirming (multiple mode) */
163
+ tempSelections: T[];
164
+ /** Search query for filtering options */
165
+ searchQuery: string;
166
+ }
167
+
168
+ /**
169
+ * Option row component props.
170
+ */
171
+ export interface SelectSheetOptionRowProps<T = string> {
172
+ /** Option data */
173
+ option: SelectSheetOption<T>;
174
+ /** Whether this option is selected */
175
+ isSelected: boolean;
176
+ /** Whether multiple selection is enabled */
177
+ isMultiple: boolean;
178
+ /** Callback when option is pressed */
179
+ onPress: () => void;
180
+ /** Whether to show bottom divider */
181
+ showDivider: boolean;
182
+ /** Position of the selection indicator */
183
+ indicatorPosition: SelectSheetIndicatorPosition;
184
+ /** Pre-computed accessibility props from hook */
185
+ a11yProps: import("react-native").AccessibilityProps;
186
+ /** Test ID for testing */
187
+ testID?: string;
188
+ }
189
+
190
+ /**
191
+ * Computed layout values from hook.
192
+ */
193
+ export interface SelectSheetLayout {
194
+ /** Calculated list height */
195
+ listHeight: number;
196
+ }