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