@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,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SegmentedControl Component
|
|
3
|
+
*
|
|
4
|
+
* @description Toggle control for switching between multiple options - Atom
|
|
5
|
+
*
|
|
6
|
+
* SegmentedControl provides a horizontal row of mutually exclusive options.
|
|
7
|
+
* Ideal for filtering, view switching, and binary choices.
|
|
8
|
+
*
|
|
9
|
+
* ## Size Scale
|
|
10
|
+
* | Size | Height | Segment H | Text Variant | Use Case |
|
|
11
|
+
* |------|--------|-----------|--------------|----------|
|
|
12
|
+
* | sm | 36px | 28px | labelSmall | Compact filters |
|
|
13
|
+
* | md | 44px | 36px | labelMedium | Standard (default) |
|
|
14
|
+
* | lg | 52px | 44px | labelLarge | Prominent controls |
|
|
15
|
+
*
|
|
16
|
+
* ## Variants
|
|
17
|
+
* - `default`: Neutral background, white selected segment
|
|
18
|
+
* - `filled`: Subtle background, primary color selected segment
|
|
19
|
+
* - `outline`: Bordered container, primary color selected segment
|
|
20
|
+
*
|
|
21
|
+
* ## Features
|
|
22
|
+
* - Responsive size prop (phone/tablet breakpoints)
|
|
23
|
+
* - Three visual variants
|
|
24
|
+
* - Individual segment disable support
|
|
25
|
+
* - Theme-aware colors via Restyle
|
|
26
|
+
* - Full accessibility support (radiogroup pattern)
|
|
27
|
+
* - Memoized for performance
|
|
28
|
+
* - Spreads rest props for composability
|
|
29
|
+
*
|
|
30
|
+
* @see SegmentedControl.types.ts - Type definitions
|
|
31
|
+
* @see SegmentedControl.helpers.ts - Style calculation functions
|
|
32
|
+
* @see SegmentedControl.a11y.ts - Accessibility props
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // Basic usage
|
|
36
|
+
* <SegmentedControl
|
|
37
|
+
* options={[
|
|
38
|
+
* { value: "day", label: "Day" },
|
|
39
|
+
* { value: "week", label: "Week" },
|
|
40
|
+
* { value: "month", label: "Month" },
|
|
41
|
+
* ]}
|
|
42
|
+
* selectedValue={view}
|
|
43
|
+
* onValueChange={setView}
|
|
44
|
+
* />
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // Different sizes and variants
|
|
48
|
+
* <SegmentedControl
|
|
49
|
+
* options={options}
|
|
50
|
+
* selectedValue={value}
|
|
51
|
+
* onValueChange={setValue}
|
|
52
|
+
* size="lg"
|
|
53
|
+
* variant="filled"
|
|
54
|
+
* />
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // With margin (composable via rest props)
|
|
58
|
+
* <SegmentedControl
|
|
59
|
+
* options={options}
|
|
60
|
+
* selectedValue={value}
|
|
61
|
+
* onValueChange={setValue}
|
|
62
|
+
* marginBottom="lg"
|
|
63
|
+
* />
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
import {
|
|
67
|
+
backgroundColor,
|
|
68
|
+
border,
|
|
69
|
+
createRestyleComponent,
|
|
70
|
+
createVariant,
|
|
71
|
+
layout,
|
|
72
|
+
spacing,
|
|
73
|
+
spacingShorthand,
|
|
74
|
+
} from "@shopify/restyle";
|
|
75
|
+
import React, { memo, useCallback } from "react";
|
|
76
|
+
import { Pressable } from "react-native";
|
|
77
|
+
import { RestyleTheme } from "../../../core/restyle";
|
|
78
|
+
import { Box } from "../../layout";
|
|
79
|
+
import { Text } from "../../typography";
|
|
80
|
+
import { SEGMENT_TEXT_VARIANT } from "./SegmentedControl.helpers";
|
|
81
|
+
import {
|
|
82
|
+
BaseSegmentedControlProps,
|
|
83
|
+
SegmentedControlProps,
|
|
84
|
+
SegmentedControlSize,
|
|
85
|
+
} from "./SegmentedControl.types";
|
|
86
|
+
import { useSegmentedControlLogic } from "./useSegmentedControlLogic";
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Base SegmentedControl container with Restyle variant support.
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
const BaseSegmentedControlContainer = createRestyleComponent<
|
|
93
|
+
BaseSegmentedControlProps,
|
|
94
|
+
RestyleTheme
|
|
95
|
+
>(
|
|
96
|
+
[
|
|
97
|
+
createVariant({ themeKey: "segmentedControlVariants" }),
|
|
98
|
+
createVariant({ themeKey: "segmentedControlSizes", property: "size" }),
|
|
99
|
+
backgroundColor,
|
|
100
|
+
border,
|
|
101
|
+
layout,
|
|
102
|
+
spacing,
|
|
103
|
+
spacingShorthand,
|
|
104
|
+
],
|
|
105
|
+
Box,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Base Segment container with Restyle variant support.
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
const BaseSegmentContainer = createRestyleComponent<
|
|
113
|
+
BaseSegmentedControlProps,
|
|
114
|
+
RestyleTheme
|
|
115
|
+
>(
|
|
116
|
+
[
|
|
117
|
+
createVariant({
|
|
118
|
+
themeKey: "segmentedControlSegmentSizes",
|
|
119
|
+
property: "size",
|
|
120
|
+
}),
|
|
121
|
+
backgroundColor,
|
|
122
|
+
border,
|
|
123
|
+
layout,
|
|
124
|
+
spacing,
|
|
125
|
+
spacingShorthand,
|
|
126
|
+
],
|
|
127
|
+
Box,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Base Pressable segment with Restyle support.
|
|
132
|
+
* @internal
|
|
133
|
+
*/
|
|
134
|
+
const BaseSegmentPressable = createRestyleComponent<
|
|
135
|
+
BaseSegmentedControlProps & { onPress?: () => void; disabled?: boolean },
|
|
136
|
+
RestyleTheme
|
|
137
|
+
>([layout, spacing, spacingShorthand], Pressable);
|
|
138
|
+
|
|
139
|
+
function SegmentedControlComponent({
|
|
140
|
+
options,
|
|
141
|
+
selectedValue,
|
|
142
|
+
onValueChange,
|
|
143
|
+
size = "md",
|
|
144
|
+
variant = "default",
|
|
145
|
+
disabled = false,
|
|
146
|
+
testID,
|
|
147
|
+
style,
|
|
148
|
+
...rest
|
|
149
|
+
}: SegmentedControlProps) {
|
|
150
|
+
const {
|
|
151
|
+
containerStyles,
|
|
152
|
+
getSegmentStyles,
|
|
153
|
+
getTextColor,
|
|
154
|
+
handlePress,
|
|
155
|
+
containerA11y,
|
|
156
|
+
getSegmentA11y,
|
|
157
|
+
} = useSegmentedControlLogic({
|
|
158
|
+
options,
|
|
159
|
+
selectedValue,
|
|
160
|
+
onValueChange,
|
|
161
|
+
size,
|
|
162
|
+
variant,
|
|
163
|
+
disabled,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const handleSegmentPress = useCallback(
|
|
167
|
+
(value: string, segmentDisabled?: boolean) => {
|
|
168
|
+
handlePress(value, segmentDisabled);
|
|
169
|
+
},
|
|
170
|
+
[handlePress],
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<BaseSegmentedControlContainer
|
|
175
|
+
variant={variant}
|
|
176
|
+
size={size}
|
|
177
|
+
backgroundColor={containerStyles.backgroundColor}
|
|
178
|
+
opacity={disabled ? 0.5 : 1}
|
|
179
|
+
testID={testID}
|
|
180
|
+
style={style}
|
|
181
|
+
{...containerA11y}
|
|
182
|
+
{...rest}
|
|
183
|
+
>
|
|
184
|
+
{options.map((option, index) => {
|
|
185
|
+
const isSelected = option.value === selectedValue;
|
|
186
|
+
const isDisabled = disabled || option.disabled === true;
|
|
187
|
+
const segmentStyles = getSegmentStyles(isSelected, isDisabled);
|
|
188
|
+
const textColor = getTextColor(isSelected, isDisabled);
|
|
189
|
+
const segmentA11yProps = getSegmentA11y(option, index, isSelected);
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<BaseSegmentPressable
|
|
193
|
+
key={option.value}
|
|
194
|
+
flex={1}
|
|
195
|
+
onPress={() => handleSegmentPress(option.value, option.disabled)}
|
|
196
|
+
disabled={isDisabled}
|
|
197
|
+
testID={testID ? `${testID}-segment-${option.value}` : undefined}
|
|
198
|
+
{...segmentA11yProps}
|
|
199
|
+
>
|
|
200
|
+
<BaseSegmentContainer
|
|
201
|
+
size={size}
|
|
202
|
+
backgroundColor={segmentStyles.backgroundColor}
|
|
203
|
+
borderRadius={segmentStyles.borderRadius}
|
|
204
|
+
>
|
|
205
|
+
<Text
|
|
206
|
+
variant={
|
|
207
|
+
SEGMENT_TEXT_VARIANT[size as SegmentedControlSize] ??
|
|
208
|
+
"labelMedium"
|
|
209
|
+
}
|
|
210
|
+
color={textColor}
|
|
211
|
+
numberOfLines={1}
|
|
212
|
+
>
|
|
213
|
+
{option.label}
|
|
214
|
+
</Text>
|
|
215
|
+
</BaseSegmentContainer>
|
|
216
|
+
</BaseSegmentPressable>
|
|
217
|
+
);
|
|
218
|
+
})}
|
|
219
|
+
</BaseSegmentedControlContainer>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export const SegmentedControl = memo(SegmentedControlComponent);
|
|
224
|
+
SegmentedControl.displayName = "SegmentedControl";
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SegmentedControl Component Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the SegmentedControl component and its related hooks/helpers.
|
|
5
|
+
* All types are explicitly exported for external consumption.
|
|
6
|
+
*
|
|
7
|
+
* @see SegmentedControl.tsx - Component implementation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
BoxProps,
|
|
12
|
+
ResponsiveValue,
|
|
13
|
+
VariantProps,
|
|
14
|
+
} from "@shopify/restyle";
|
|
15
|
+
import { StyleProp, ViewProps, ViewStyle } from "react-native";
|
|
16
|
+
import { RestyleTheme } from "../../../core/restyle";
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// VARIANT TYPES
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Segment option configuration.
|
|
24
|
+
*/
|
|
25
|
+
export interface SegmentOption {
|
|
26
|
+
/** Unique identifier for the segment */
|
|
27
|
+
value: string;
|
|
28
|
+
/** Display label for the segment */
|
|
29
|
+
label: string;
|
|
30
|
+
/** Optional icon name to display */
|
|
31
|
+
icon?: string;
|
|
32
|
+
/** Whether this segment is disabled */
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Size preset for the SegmentedControl component.
|
|
38
|
+
*
|
|
39
|
+
* - `sm`: 36px height - Compact filters, dense UIs
|
|
40
|
+
* - `md`: 44px height - Standard control (default)
|
|
41
|
+
* - `lg`: 52px height - Prominent controls, tablets
|
|
42
|
+
*/
|
|
43
|
+
export type SegmentedControlSize = Exclude<
|
|
44
|
+
keyof RestyleTheme["segmentedControlSizes"],
|
|
45
|
+
"defaults"
|
|
46
|
+
>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Visual variant for the SegmentedControl component.
|
|
50
|
+
*
|
|
51
|
+
* - `default`: Neutral background, white selected segment
|
|
52
|
+
* - `outline`: Bordered container, primary color selected
|
|
53
|
+
*/
|
|
54
|
+
export type SegmentedControlVariant = Exclude<
|
|
55
|
+
keyof RestyleTheme["segmentedControlVariants"],
|
|
56
|
+
"defaults"
|
|
57
|
+
>;
|
|
58
|
+
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// RESTYLE PROP TYPES
|
|
61
|
+
// =============================================================================
|
|
62
|
+
|
|
63
|
+
type SegmentedControlVariantProps = VariantProps<
|
|
64
|
+
RestyleTheme,
|
|
65
|
+
"segmentedControlVariants"
|
|
66
|
+
>;
|
|
67
|
+
type SegmentedControlSizeProps = VariantProps<
|
|
68
|
+
RestyleTheme,
|
|
69
|
+
"segmentedControlSizes",
|
|
70
|
+
"size"
|
|
71
|
+
>;
|
|
72
|
+
|
|
73
|
+
/** Base props combining Restyle Box props with ViewProps and variant props */
|
|
74
|
+
export type BaseSegmentedControlProps = SegmentedControlVariantProps &
|
|
75
|
+
SegmentedControlSizeProps &
|
|
76
|
+
BoxProps<RestyleTheme> &
|
|
77
|
+
ViewProps;
|
|
78
|
+
|
|
79
|
+
// =============================================================================
|
|
80
|
+
// COMPONENT PROPS
|
|
81
|
+
// =============================================================================
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Props for SegmentedControl component.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* // Basic usage
|
|
88
|
+
* <SegmentedControl
|
|
89
|
+
* options={[
|
|
90
|
+
* { value: "day", label: "Day" },
|
|
91
|
+
* { value: "week", label: "Week" },
|
|
92
|
+
* ]}
|
|
93
|
+
* selectedValue={view}
|
|
94
|
+
* onValueChange={setView}
|
|
95
|
+
* />
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* // With different size and variant
|
|
99
|
+
* <SegmentedControl
|
|
100
|
+
* options={options}
|
|
101
|
+
* selectedValue={value}
|
|
102
|
+
* onValueChange={setValue}
|
|
103
|
+
* size="lg"
|
|
104
|
+
* variant="outline"
|
|
105
|
+
* />
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* // With spacing props (composable)
|
|
109
|
+
* <SegmentedControl
|
|
110
|
+
* options={options}
|
|
111
|
+
* selectedValue={value}
|
|
112
|
+
* onValueChange={setValue}
|
|
113
|
+
* marginBottom="lg"
|
|
114
|
+
* />
|
|
115
|
+
*/
|
|
116
|
+
export interface SegmentedControlProps
|
|
117
|
+
extends Omit<BaseSegmentedControlProps, "variant"> {
|
|
118
|
+
/** Array of segment options */
|
|
119
|
+
options: SegmentOption[];
|
|
120
|
+
/** Currently selected value */
|
|
121
|
+
selectedValue: string;
|
|
122
|
+
/** Callback when selection changes */
|
|
123
|
+
onValueChange: (value: string) => void;
|
|
124
|
+
/** Size variant (supports responsive values) */
|
|
125
|
+
size?: ResponsiveValue<SegmentedControlSize, RestyleTheme["breakpoints"]>;
|
|
126
|
+
/** Visual variant (supports responsive values) */
|
|
127
|
+
variant?: ResponsiveValue<SegmentedControlVariant, RestyleTheme["breakpoints"]>;
|
|
128
|
+
/** Whether the entire control is disabled */
|
|
129
|
+
disabled?: boolean;
|
|
130
|
+
/** Test ID for testing */
|
|
131
|
+
testID?: string;
|
|
132
|
+
/** Custom style overrides */
|
|
133
|
+
style?: StyleProp<ViewStyle>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// =============================================================================
|
|
137
|
+
// HOOK TYPES
|
|
138
|
+
// =============================================================================
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Props passed to the logic hook.
|
|
142
|
+
*/
|
|
143
|
+
export interface UseSegmentedControlLogicProps {
|
|
144
|
+
options: SegmentOption[];
|
|
145
|
+
selectedValue: string;
|
|
146
|
+
onValueChange: (value: string) => void;
|
|
147
|
+
size: ResponsiveValue<SegmentedControlSize, RestyleTheme["breakpoints"]>;
|
|
148
|
+
variant: ResponsiveValue<SegmentedControlVariant, RestyleTheme["breakpoints"]>;
|
|
149
|
+
disabled: boolean;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Return type for useSegmentedControlLogic hook.
|
|
154
|
+
*/
|
|
155
|
+
export interface UseSegmentedControlLogicReturn {
|
|
156
|
+
/** Computed container styles */
|
|
157
|
+
containerStyles: {
|
|
158
|
+
backgroundColor: keyof RestyleTheme["colors"];
|
|
159
|
+
borderRadius: keyof RestyleTheme["borderRadii"];
|
|
160
|
+
padding: keyof RestyleTheme["spacing"];
|
|
161
|
+
};
|
|
162
|
+
/** Computed segment styles based on selection state */
|
|
163
|
+
getSegmentStyles: (
|
|
164
|
+
isSelected: boolean,
|
|
165
|
+
isDisabled: boolean
|
|
166
|
+
) => {
|
|
167
|
+
backgroundColor: keyof RestyleTheme["colors"];
|
|
168
|
+
borderRadius: keyof RestyleTheme["borderRadii"];
|
|
169
|
+
paddingVertical: keyof RestyleTheme["spacing"];
|
|
170
|
+
paddingHorizontal: keyof RestyleTheme["spacing"];
|
|
171
|
+
};
|
|
172
|
+
/** Computed text color based on selection state */
|
|
173
|
+
getTextColor: (
|
|
174
|
+
isSelected: boolean,
|
|
175
|
+
isDisabled: boolean
|
|
176
|
+
) => keyof RestyleTheme["colors"];
|
|
177
|
+
/** Handle segment press */
|
|
178
|
+
handlePress: (value: string, disabled?: boolean) => void;
|
|
179
|
+
/** Accessibility props for container */
|
|
180
|
+
containerA11y: Record<string, unknown>;
|
|
181
|
+
/** Get accessibility props for a segment */
|
|
182
|
+
getSegmentA11y: (
|
|
183
|
+
option: SegmentOption,
|
|
184
|
+
index: number,
|
|
185
|
+
isSelected: boolean
|
|
186
|
+
) => Record<string, unknown>;
|
|
187
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SegmentedControl Component
|
|
3
|
+
*
|
|
4
|
+
* @description Toggle control for switching between multiple options
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* import { SegmentedControl } from "@/design-system";
|
|
8
|
+
*
|
|
9
|
+
* <SegmentedControl
|
|
10
|
+
* options={[
|
|
11
|
+
* { value: "day", label: "Day" },
|
|
12
|
+
* { value: "week", label: "Week" },
|
|
13
|
+
* ]}
|
|
14
|
+
* selectedValue={view}
|
|
15
|
+
* onValueChange={setView}
|
|
16
|
+
* />
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export { SegmentedControl } from "./SegmentedControl";
|
|
20
|
+
export type {
|
|
21
|
+
BaseSegmentedControlProps,
|
|
22
|
+
SegmentedControlProps,
|
|
23
|
+
SegmentedControlSize,
|
|
24
|
+
SegmentedControlVariant,
|
|
25
|
+
SegmentOption,
|
|
26
|
+
UseSegmentedControlLogicProps,
|
|
27
|
+
UseSegmentedControlLogicReturn,
|
|
28
|
+
} from "./SegmentedControl.types";
|
|
29
|
+
export { useSegmentedControlLogic } from "./useSegmentedControlLogic";
|
|
30
|
+
export {
|
|
31
|
+
getContainerStyles,
|
|
32
|
+
getSegmentStyles,
|
|
33
|
+
getTextColor,
|
|
34
|
+
validateOptions,
|
|
35
|
+
} from "./SegmentedControl.helpers";
|
|
36
|
+
export {
|
|
37
|
+
getSegmentA11y,
|
|
38
|
+
getSegmentedControlContainerA11y,
|
|
39
|
+
} from "./SegmentedControl.a11y";
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { useDesignSystem } from "../../../provider";
|
|
2
|
+
import { useResponsiveProp } from "@shopify/restyle";
|
|
3
|
+
import { useCallback, useMemo } from "react";
|
|
4
|
+
import {
|
|
5
|
+
getSegmentA11y,
|
|
6
|
+
getSegmentedControlContainerA11y,
|
|
7
|
+
} from "./SegmentedControl.a11y";
|
|
8
|
+
import {
|
|
9
|
+
getContainerStyles,
|
|
10
|
+
getSegmentStyles as getSegmentStylesHelper,
|
|
11
|
+
getTextColor as getTextColorHelper,
|
|
12
|
+
} from "./SegmentedControl.helpers";
|
|
13
|
+
import type {
|
|
14
|
+
SegmentedControlSize,
|
|
15
|
+
SegmentedControlVariant,
|
|
16
|
+
SegmentOption,
|
|
17
|
+
UseSegmentedControlLogicProps,
|
|
18
|
+
UseSegmentedControlLogicReturn,
|
|
19
|
+
} from "./SegmentedControl.types";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Orchestrates SegmentedControl rendering logic and state management.
|
|
23
|
+
*
|
|
24
|
+
* Handles:
|
|
25
|
+
* - Responsive size and variant resolution
|
|
26
|
+
* - Container style calculation
|
|
27
|
+
* - Segment style computation based on selection state
|
|
28
|
+
* - Text color determination per segment
|
|
29
|
+
* - Press handling with disabled state check
|
|
30
|
+
* - Accessibility props for container and individual segments
|
|
31
|
+
*
|
|
32
|
+
* @param props - Configuration object for segmented control behavior
|
|
33
|
+
* @param props.options - Array of segment options with values and labels
|
|
34
|
+
* @param props.selectedValue - Currently selected segment value
|
|
35
|
+
* @param props.onValueChange - Callback when selection changes
|
|
36
|
+
* @param props.size - Size variant ('sm' | 'md' | 'lg')
|
|
37
|
+
* @param props.variant - Visual variant ('default' | 'emphasized')
|
|
38
|
+
* @param props.disabled - Whether entire control is disabled
|
|
39
|
+
*
|
|
40
|
+
* @returns Computed values for rendering SegmentedControl
|
|
41
|
+
* @returns {object} containerStyles - Container style configuration
|
|
42
|
+
* @returns {function} getSegmentStyles - Returns styles for a segment
|
|
43
|
+
* @returns {function} getTextColor - Returns text color for a segment
|
|
44
|
+
* @returns {function} handlePress - Press handler for segments
|
|
45
|
+
* @returns {object} containerA11y - Container accessibility props
|
|
46
|
+
* @returns {function} getSegmentA11y - Returns a11y props for a segment
|
|
47
|
+
*
|
|
48
|
+
* @performance This hook uses useMemo() and useCallback() for optimization
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* const {
|
|
52
|
+
* containerStyles,
|
|
53
|
+
* getSegmentStyles,
|
|
54
|
+
* getTextColor,
|
|
55
|
+
* handlePress,
|
|
56
|
+
* getSegmentA11y
|
|
57
|
+
* } = useSegmentedControlLogic({
|
|
58
|
+
* options: [
|
|
59
|
+
* { value: "day", label: "Day" },
|
|
60
|
+
* { value: "week", label: "Week" },
|
|
61
|
+
* { value: "month", label: "Month" }
|
|
62
|
+
* ],
|
|
63
|
+
* selectedValue: "week",
|
|
64
|
+
* onValueChange: setValue,
|
|
65
|
+
* size: "md",
|
|
66
|
+
* });
|
|
67
|
+
*/
|
|
68
|
+
export function useSegmentedControlLogic({
|
|
69
|
+
options,
|
|
70
|
+
selectedValue,
|
|
71
|
+
onValueChange,
|
|
72
|
+
size,
|
|
73
|
+
variant,
|
|
74
|
+
disabled,
|
|
75
|
+
}: UseSegmentedControlLogicProps): UseSegmentedControlLogicReturn {
|
|
76
|
+
const { labels: t } = useDesignSystem();
|
|
77
|
+
const resolvedSize = (useResponsiveProp(size) ?? "md") as SegmentedControlSize;
|
|
78
|
+
const resolvedVariant = (useResponsiveProp(variant) ?? "default") as SegmentedControlVariant;
|
|
79
|
+
|
|
80
|
+
// Memoize container styles
|
|
81
|
+
const containerStyles = getContainerStyles(resolvedSize, resolvedVariant);
|
|
82
|
+
|
|
83
|
+
// Memoize i18n params for container a11y
|
|
84
|
+
const containerI18n = useMemo(
|
|
85
|
+
() => ({
|
|
86
|
+
containerLabel: t.designSystem.segmentedControl.containerLabel,
|
|
87
|
+
optionsLabel: t.designSystem.segmentedControl.options,
|
|
88
|
+
}),
|
|
89
|
+
[t],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Memoize container accessibility
|
|
93
|
+
const containerA11y = getSegmentedControlContainerA11y(options.length, containerI18n);
|
|
94
|
+
|
|
95
|
+
// Memoize i18n params for segment a11y
|
|
96
|
+
const segmentI18n = useMemo(
|
|
97
|
+
() => ({
|
|
98
|
+
selectedLabel: t.designSystem.segmentedControl.selected,
|
|
99
|
+
currentlySelectedHint: t.designSystem.segmentedControl.currentlySelected,
|
|
100
|
+
doubleTapToSelectHint: t.designSystem.segmentedControl.doubleTapToSelect,
|
|
101
|
+
ofLabel: t.designSystem.segmentedControl.of,
|
|
102
|
+
}),
|
|
103
|
+
[t],
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Get segment styles based on selection state
|
|
107
|
+
const getSegmentStyles = useCallback(
|
|
108
|
+
(isSelected: boolean, isDisabled: boolean) =>
|
|
109
|
+
getSegmentStylesHelper(
|
|
110
|
+
resolvedSize,
|
|
111
|
+
resolvedVariant,
|
|
112
|
+
isSelected,
|
|
113
|
+
isDisabled,
|
|
114
|
+
),
|
|
115
|
+
[resolvedSize, resolvedVariant],
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Get text color based on selection state
|
|
119
|
+
const getTextColor = useCallback(
|
|
120
|
+
(isSelected: boolean, isDisabled: boolean) =>
|
|
121
|
+
getTextColorHelper(resolvedVariant, isSelected, isDisabled),
|
|
122
|
+
[resolvedVariant],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Handle segment press
|
|
126
|
+
const handlePress = useCallback(
|
|
127
|
+
(value: string, segmentDisabled?: boolean) => {
|
|
128
|
+
if (disabled || segmentDisabled || value === selectedValue) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
onValueChange(value);
|
|
132
|
+
},
|
|
133
|
+
[disabled, selectedValue, onValueChange],
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Get accessibility props for a segment
|
|
137
|
+
const getSegmentA11yProps = useCallback(
|
|
138
|
+
(option: SegmentOption, index: number, isSelected: boolean) =>
|
|
139
|
+
getSegmentA11y(option, index, options.length, isSelected, segmentI18n),
|
|
140
|
+
[options.length, segmentI18n],
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
containerStyles,
|
|
145
|
+
getSegmentStyles,
|
|
146
|
+
getTextColor,
|
|
147
|
+
handlePress,
|
|
148
|
+
containerA11y,
|
|
149
|
+
getSegmentA11y: getSegmentA11yProps,
|
|
150
|
+
};
|
|
151
|
+
}
|