@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.
- package/dist/index.d.mts +52556 -0
- package/dist/index.d.ts +52556 -0
- package/dist/index.js +8753 -0
- package/dist/index.mjs +8777 -0
- package/package.json +70 -0
- package/src/__test-utils__/index.tsx +39 -0
- package/src/components/CalendarStrip/CalendarStrip.helpers.ts +106 -0
- package/src/components/CalendarStrip/CalendarStrip.tsx +83 -0
- package/src/components/CalendarStrip/CalendarStrip.types.ts +133 -0
- package/src/components/CalendarStrip/DayCard/DayCard.helpers.ts +44 -0
- package/src/components/CalendarStrip/DayCard/DayCard.tsx +71 -0
- package/src/components/CalendarStrip/DayCard/DayCard.types.ts +134 -0
- package/src/components/CalendarStrip/DayCard/index.ts +2 -0
- package/src/components/CalendarStrip/DayCard/useDayCardLogic.ts +45 -0
- package/src/components/CalendarStrip/index.ts +9 -0
- package/src/components/CalendarStrip/useCalendarStripLogic.ts +53 -0
- package/src/components/EmptyState/EmptyState.helpers.ts +104 -0
- package/src/components/EmptyState/EmptyState.tsx +205 -0
- package/src/components/EmptyState/EmptyState.types.ts +213 -0
- package/src/components/EmptyState/index.ts +44 -0
- package/src/components/EmptyState/useEmptyStateLogic.ts +131 -0
- package/src/components/Header/Header.helpers.ts +93 -0
- package/src/components/Header/Header.tsx +185 -0
- package/src/components/Header/Header.types.ts +153 -0
- package/src/components/Header/index.ts +44 -0
- package/src/components/Header/useHeaderLogic.ts +146 -0
- package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.helpers.ts +50 -0
- package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.tsx +78 -0
- package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.types.ts +99 -0
- package/src/components/ScheduleItem/ScheduleItem/index.ts +16 -0
- package/src/components/ScheduleItem/ScheduleItem/useScheduleItemLogic.ts +31 -0
- package/src/components/ScheduleItem/index.ts +15 -0
- package/src/components/index.ts +40 -0
- package/src/core/index.ts +34 -0
- package/src/core/restyle/RestyleThemeProviderWrapper.tsx +31 -0
- package/src/core/restyle/index.ts +38 -0
- package/src/core/restyle/restylePresetRegistry.ts +195 -0
- package/src/core/restyle/restyleTheme.ts +1352 -0
- package/src/core/restyle/restyleTypes.ts +8 -0
- package/src/core/restyle/useRestyleTheme.ts +10 -0
- package/src/hooks/animations/index.ts +3 -0
- package/src/hooks/animations/useAnimatedValue.ts +10 -0
- package/src/hooks/animations/useEntranceAnimation.ts +106 -0
- package/src/hooks/animations/usePulseAnimation.ts +63 -0
- package/src/hooks/index.ts +30 -0
- package/src/hooks/useReducedMotion.ts +60 -0
- package/src/i18n/index.ts +2 -0
- package/src/i18n/labels/en.ts +120 -0
- package/src/i18n/labels/es.ts +120 -0
- package/src/i18n/labels/index.ts +6 -0
- package/src/i18n/labels/types.ts +165 -0
- package/src/index.tsx +215 -0
- package/src/primitives/actions/Button/Button.helpers.ts +243 -0
- package/src/primitives/actions/Button/Button.tsx +198 -0
- package/src/primitives/actions/Button/Button.types.ts +207 -0
- package/src/primitives/actions/Button/index.ts +41 -0
- package/src/primitives/actions/Button/useButtonLogic.ts +160 -0
- package/src/primitives/actions/IconButton/IconButton.helpers.ts +235 -0
- package/src/primitives/actions/IconButton/IconButton.tsx +177 -0
- package/src/primitives/actions/IconButton/IconButton.types.ts +273 -0
- package/src/primitives/actions/IconButton/index.ts +30 -0
- package/src/primitives/actions/IconButton/useIconButtonLogic.ts +172 -0
- package/src/primitives/actions/index.ts +20 -0
- package/src/primitives/content/Avatar/Avatar.helpers.ts +177 -0
- package/src/primitives/content/Avatar/Avatar.tsx +199 -0
- package/src/primitives/content/Avatar/Avatar.types.ts +222 -0
- package/src/primitives/content/Avatar/index.ts +46 -0
- package/src/primitives/content/Avatar/useAvatarLogic.ts +149 -0
- package/src/primitives/content/Badge/Badge.helpers.ts +175 -0
- package/src/primitives/content/Badge/Badge.tsx +174 -0
- package/src/primitives/content/Badge/Badge.types.ts +223 -0
- package/src/primitives/content/Badge/index.ts +40 -0
- package/src/primitives/content/Badge/useBadgeLogic.ts +128 -0
- package/src/primitives/content/Card/Card.helpers.ts +27 -0
- package/src/primitives/content/Card/Card.tsx +123 -0
- package/src/primitives/content/Card/Card.types.ts +95 -0
- package/src/primitives/content/Card/index.ts +20 -0
- package/src/primitives/content/Card/useCardLogic.ts +48 -0
- package/src/primitives/content/Chip/Chip.helpers.ts +304 -0
- package/src/primitives/content/Chip/Chip.tsx +205 -0
- package/src/primitives/content/Chip/Chip.types.ts +234 -0
- package/src/primitives/content/Chip/index.ts +47 -0
- package/src/primitives/content/Chip/useChipLogic.ts +167 -0
- package/src/primitives/content/Icon/Icon.helpers.ts +54 -0
- package/src/primitives/content/Icon/Icon.tsx +110 -0
- package/src/primitives/content/Icon/Icon.types.ts +95 -0
- package/src/primitives/content/Icon/index.ts +20 -0
- package/src/primitives/content/Icon/useIconLogic.ts +73 -0
- package/src/primitives/content/index.ts +45 -0
- package/src/primitives/feedback/ProgressBar/ProgressBar.helpers.ts +122 -0
- package/src/primitives/feedback/ProgressBar/ProgressBar.tsx +154 -0
- package/src/primitives/feedback/ProgressBar/ProgressBar.types.ts +178 -0
- package/src/primitives/feedback/ProgressBar/index.ts +17 -0
- package/src/primitives/feedback/ProgressBar/useProgressBarLogic.ts +120 -0
- package/src/primitives/feedback/Skeleton/Skeleton.helpers.ts +145 -0
- package/src/primitives/feedback/Skeleton/Skeleton.tsx +155 -0
- package/src/primitives/feedback/Skeleton/Skeleton.types.ts +223 -0
- package/src/primitives/feedback/Skeleton/index.ts +44 -0
- package/src/primitives/feedback/Skeleton/useSkeletonLogic.ts +125 -0
- package/src/primitives/feedback/Spinner/Spinner.helpers.ts +40 -0
- package/src/primitives/feedback/Spinner/Spinner.tsx +105 -0
- package/src/primitives/feedback/Spinner/Spinner.types.ts +114 -0
- package/src/primitives/feedback/Spinner/index.ts +18 -0
- package/src/primitives/feedback/Spinner/useSpinnerLogic.ts +84 -0
- package/src/primitives/feedback/Toast/Toast.helpers.ts +163 -0
- package/src/primitives/feedback/Toast/Toast.tsx +190 -0
- package/src/primitives/feedback/Toast/Toast.types.ts +270 -0
- package/src/primitives/feedback/Toast/ToastContext.tsx +96 -0
- package/src/primitives/feedback/Toast/ToastProvider.tsx +241 -0
- package/src/primitives/feedback/Toast/index.ts +59 -0
- package/src/primitives/feedback/Toast/useToastLogic.ts +112 -0
- package/src/primitives/feedback/index.ts +45 -0
- package/src/primitives/index.ts +158 -0
- package/src/primitives/inputs/Checkbox/Checkbox.helpers.ts +132 -0
- package/src/primitives/inputs/Checkbox/Checkbox.tsx +150 -0
- package/src/primitives/inputs/Checkbox/Checkbox.types.ts +106 -0
- package/src/primitives/inputs/Checkbox/index.ts +30 -0
- package/src/primitives/inputs/Checkbox/useCheckboxLogic.ts +121 -0
- package/src/primitives/inputs/RadioButton/RadioButton.helpers.ts +123 -0
- package/src/primitives/inputs/RadioButton/RadioButton.tsx +159 -0
- package/src/primitives/inputs/RadioButton/RadioButton.types.ts +106 -0
- package/src/primitives/inputs/RadioButton/index.ts +25 -0
- package/src/primitives/inputs/RadioButton/useRadioButtonLogic.ts +117 -0
- package/src/primitives/inputs/SegmentedControl/SegmentedControl.helpers.ts +174 -0
- package/src/primitives/inputs/SegmentedControl/SegmentedControl.tsx +224 -0
- package/src/primitives/inputs/SegmentedControl/SegmentedControl.types.ts +187 -0
- package/src/primitives/inputs/SegmentedControl/index.ts +39 -0
- package/src/primitives/inputs/SegmentedControl/useSegmentedControlLogic.ts +151 -0
- package/src/primitives/inputs/SelectSheet/SelectSheet.helpers.ts +147 -0
- package/src/primitives/inputs/SelectSheet/SelectSheet.tsx +247 -0
- package/src/primitives/inputs/SelectSheet/SelectSheet.types.ts +196 -0
- package/src/primitives/inputs/SelectSheet/SelectSheetOption.tsx +177 -0
- package/src/primitives/inputs/SelectSheet/index.ts +48 -0
- package/src/primitives/inputs/SelectSheet/useSelectSheetLogic.ts +309 -0
- package/src/primitives/inputs/Switch/Switch.helpers.ts +109 -0
- package/src/primitives/inputs/Switch/Switch.tsx +191 -0
- package/src/primitives/inputs/Switch/Switch.types.ts +154 -0
- package/src/primitives/inputs/Switch/index.ts +40 -0
- package/src/primitives/inputs/Switch/useSwitchLogic.ts +192 -0
- package/src/primitives/inputs/TextInput/TextInput.helpers.ts +206 -0
- package/src/primitives/inputs/TextInput/TextInput.tsx +392 -0
- package/src/primitives/inputs/TextInput/TextInput.types.ts +216 -0
- package/src/primitives/inputs/TextInput/index.ts +37 -0
- package/src/primitives/inputs/TextInput/useTextInputLogic.ts +195 -0
- package/src/primitives/inputs/index.ts +52 -0
- package/src/primitives/layout/AnimatedBox.tsx +44 -0
- package/src/primitives/layout/Box.tsx +71 -0
- package/src/primitives/layout/Divider/Divider.helpers.ts +115 -0
- package/src/primitives/layout/Divider/Divider.tsx +139 -0
- package/src/primitives/layout/Divider/Divider.types.ts +178 -0
- package/src/primitives/layout/Divider/index.ts +24 -0
- package/src/primitives/layout/Divider/useDividerLogic.ts +109 -0
- package/src/primitives/layout/FlatList.tsx +66 -0
- package/src/primitives/layout/Pressable.tsx +74 -0
- package/src/primitives/layout/ScrollView.tsx +63 -0
- package/src/primitives/layout/Stack.tsx +69 -0
- package/src/primitives/layout/index.ts +40 -0
- package/src/primitives/navigation/index.ts +6 -0
- package/src/primitives/overlays/Modal/Modal.helpers.ts +31 -0
- package/src/primitives/overlays/Modal/Modal.tsx +264 -0
- package/src/primitives/overlays/Modal/Modal.types.ts +193 -0
- package/src/primitives/overlays/Modal/index.ts +43 -0
- package/src/primitives/overlays/Modal/useModalLogic.ts +103 -0
- package/src/primitives/overlays/index.ts +12 -0
- package/src/primitives/typography/Text.tsx +51 -0
- package/src/primitives/typography/index.ts +1 -0
- package/src/provider/DesignSystemContext.ts +22 -0
- package/src/provider/DesignSystemProvider.tsx +121 -0
- package/src/provider/index.ts +7 -0
- package/src/providers/ThemeProvider/createTheme.ts +304 -0
- package/src/providers/ThemeProvider/defaultTheme.ts +70 -0
- package/src/providers/ThemeProvider/index.ts +34 -0
- package/src/providers/ThemeProvider/types.ts +249 -0
- package/src/providers/index.ts +29 -0
- package/src/tokens/colors.ts +371 -0
- package/src/tokens/index.ts +145 -0
- package/src/tokens/motion.ts +176 -0
- package/src/tokens/radii.ts +82 -0
- package/src/tokens/scales.ts +588 -0
- package/src/tokens/shadows.ts +190 -0
- package/src/tokens/spacing.ts +140 -0
- package/src/tokens/tokens.json +207 -0
- package/src/tokens/typography.ts +251 -0
- package/src/types.ts +50 -0
- package/src/utils/accessibility.ts +169 -0
- package/src/utils/index.ts +25 -0
- 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
|
+
}
|