@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,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button Component
|
|
3
|
+
*
|
|
4
|
+
* @description Text button for primary actions - Atom
|
|
5
|
+
*
|
|
6
|
+
* Button provides labeled interactive controls for triggering actions.
|
|
7
|
+
* Supports optional icons, loading states, and multiple visual variants.
|
|
8
|
+
*
|
|
9
|
+
* ## Ratios & Constraints
|
|
10
|
+
* - Icon matches text visual weight (ICON_RATIOS.textAdjacent = 1.0)
|
|
11
|
+
* - All sizes meet 44px minimum touch target
|
|
12
|
+
*
|
|
13
|
+
* ## Size Scale
|
|
14
|
+
* | Size | Height | Icon | Text Variant | Padding |
|
|
15
|
+
* |------|--------|------|--------------|---------|
|
|
16
|
+
* | sm | 36px | 16px | labelSmall | sm (8px)|
|
|
17
|
+
* | md | 44px | 20px | labelMedium | md (12px)|
|
|
18
|
+
* | lg | 52px | 24px | labelLarge | lg (16px)|
|
|
19
|
+
*
|
|
20
|
+
* ## Variants
|
|
21
|
+
* - `solid`: Solid background (default)
|
|
22
|
+
* - `outline`: Border only, transparent background
|
|
23
|
+
* - `ghost`: Text only, no background or border
|
|
24
|
+
*
|
|
25
|
+
* ## Features
|
|
26
|
+
* - Responsive size prop (phone/tablet breakpoints)
|
|
27
|
+
* - Optional leading icon
|
|
28
|
+
* - Loading state with spinner
|
|
29
|
+
* - Full accessibility support
|
|
30
|
+
*
|
|
31
|
+
* @performance
|
|
32
|
+
* - Wrapped with React.memo() to prevent unnecessary re-renders
|
|
33
|
+
* - Uses useMemo() for expensive calculations (style resolution, color computation)
|
|
34
|
+
* - Icon and spinner calculations cached for 60fps performance
|
|
35
|
+
* - Restyle theme variant resolution optimized with memoization
|
|
36
|
+
*
|
|
37
|
+
* @see Button.types.ts - Type definitions
|
|
38
|
+
* @see Button.helpers.ts - Pure calculation functions
|
|
39
|
+
* @see Button.a11y.ts - Accessibility prop generation
|
|
40
|
+
* @see tokens/scales.ts - Mathematical ratios
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // Basic usage
|
|
44
|
+
* <Button title="Save" onPress={handleSave} />
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // With icon and loading state
|
|
48
|
+
* <Button
|
|
49
|
+
* title="Save"
|
|
50
|
+
* iconName="save"
|
|
51
|
+
* onPress={handleSave}
|
|
52
|
+
* isLoading={isSaving}
|
|
53
|
+
* variant="solid"
|
|
54
|
+
* accessibilityLabel="Save changes"
|
|
55
|
+
* />
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // Responsive with custom color
|
|
59
|
+
* <Button
|
|
60
|
+
* title="Delete"
|
|
61
|
+
* onPress={handleDelete}
|
|
62
|
+
* variant="outline"
|
|
63
|
+
* size={{ phone: "md", tablet: "lg" }}
|
|
64
|
+
* color="feedbackError"
|
|
65
|
+
* />
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
import {
|
|
69
|
+
backgroundColor,
|
|
70
|
+
border,
|
|
71
|
+
createRestyleComponent,
|
|
72
|
+
createVariant,
|
|
73
|
+
layout,
|
|
74
|
+
spacing,
|
|
75
|
+
spacingShorthand,
|
|
76
|
+
} from "@shopify/restyle";
|
|
77
|
+
import React, { forwardRef, memo, useCallback } from "react";
|
|
78
|
+
import { Pressable, View } from "react-native";
|
|
79
|
+
import { RestyleTheme } from "../../../core/restyle";
|
|
80
|
+
import { BaseThemeColor } from "../../../types";
|
|
81
|
+
import { Icon } from "../../content/Icon";
|
|
82
|
+
import { Spinner } from "../../feedback/Spinner";
|
|
83
|
+
import { Text } from "../../typography";
|
|
84
|
+
import { BaseButtonProps, ButtonProps } from "./Button.types";
|
|
85
|
+
import { useButtonLogic } from "./useButtonLogic";
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Base container with Restyle variant support.
|
|
89
|
+
* @internal
|
|
90
|
+
*/
|
|
91
|
+
const BaseButtonContainer = createRestyleComponent<
|
|
92
|
+
BaseButtonProps,
|
|
93
|
+
RestyleTheme
|
|
94
|
+
>(
|
|
95
|
+
[
|
|
96
|
+
createVariant({ themeKey: "buttonVariants" }),
|
|
97
|
+
createVariant({ themeKey: "buttonSizes", property: "size" }),
|
|
98
|
+
backgroundColor,
|
|
99
|
+
border,
|
|
100
|
+
layout,
|
|
101
|
+
spacing,
|
|
102
|
+
spacingShorthand,
|
|
103
|
+
],
|
|
104
|
+
Pressable,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const ButtonComponent = forwardRef<View, ButtonProps>(
|
|
108
|
+
function ButtonComponent({
|
|
109
|
+
title,
|
|
110
|
+
onPress,
|
|
111
|
+
variant = "solid",
|
|
112
|
+
size = "md",
|
|
113
|
+
color = "accentPrimary",
|
|
114
|
+
disabled = false,
|
|
115
|
+
isLoading = false,
|
|
116
|
+
iconName,
|
|
117
|
+
testID,
|
|
118
|
+
accessibilityHint,
|
|
119
|
+
accessibilityLabel,
|
|
120
|
+
style,
|
|
121
|
+
...rest
|
|
122
|
+
}: ButtonProps, ref) {
|
|
123
|
+
const {
|
|
124
|
+
themeVariantKey,
|
|
125
|
+
textVariant,
|
|
126
|
+
textColor,
|
|
127
|
+
iconSize,
|
|
128
|
+
iconColor,
|
|
129
|
+
containerStyle,
|
|
130
|
+
hitSlop,
|
|
131
|
+
a11yProps,
|
|
132
|
+
showSpinner,
|
|
133
|
+
spinnerSize,
|
|
134
|
+
spinnerColor,
|
|
135
|
+
spinnerAccessibilityLabel,
|
|
136
|
+
} = useButtonLogic({
|
|
137
|
+
title,
|
|
138
|
+
variant,
|
|
139
|
+
size,
|
|
140
|
+
color,
|
|
141
|
+
disabled,
|
|
142
|
+
isLoading,
|
|
143
|
+
iconName,
|
|
144
|
+
accessibilityLabel,
|
|
145
|
+
accessibilityHint,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const handlePress = useCallback(() => {
|
|
149
|
+
if (!disabled && !isLoading) {
|
|
150
|
+
onPress();
|
|
151
|
+
}
|
|
152
|
+
}, [disabled, isLoading, onPress]);
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<BaseButtonContainer
|
|
156
|
+
ref={ref}
|
|
157
|
+
variant={themeVariantKey}
|
|
158
|
+
size={size}
|
|
159
|
+
onPress={handlePress}
|
|
160
|
+
disabled={disabled || isLoading}
|
|
161
|
+
hitSlop={hitSlop}
|
|
162
|
+
testID={testID}
|
|
163
|
+
flexDirection="row"
|
|
164
|
+
alignItems="center"
|
|
165
|
+
justifyContent="center"
|
|
166
|
+
gap="xs"
|
|
167
|
+
borderRadius="md"
|
|
168
|
+
style={[containerStyle, style]}
|
|
169
|
+
{...a11yProps}
|
|
170
|
+
{...rest}
|
|
171
|
+
>
|
|
172
|
+
{showSpinner ? (
|
|
173
|
+
<Spinner
|
|
174
|
+
size={spinnerSize}
|
|
175
|
+
color={spinnerColor}
|
|
176
|
+
accessibilityLabel={spinnerAccessibilityLabel}
|
|
177
|
+
/>
|
|
178
|
+
) : (
|
|
179
|
+
<>
|
|
180
|
+
{iconName && iconSize && (
|
|
181
|
+
<Icon
|
|
182
|
+
name={iconName}
|
|
183
|
+
size={iconSize}
|
|
184
|
+
color={iconColor}
|
|
185
|
+
accessibilityLabel={iconName}
|
|
186
|
+
/>
|
|
187
|
+
)}
|
|
188
|
+
<Text variant={textVariant} color={textColor as BaseThemeColor}>
|
|
189
|
+
{title}
|
|
190
|
+
</Text>
|
|
191
|
+
</>
|
|
192
|
+
)}
|
|
193
|
+
</BaseButtonContainer>
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
export const Button = memo(ButtonComponent);
|
|
198
|
+
Button.displayName = "Button";
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button Component Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the Button 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, StyleProp, View, ViewStyle } from "react-native";
|
|
10
|
+
import { RestyleTheme } from "../../../core/restyle";
|
|
11
|
+
import { RestyleColor } from "../../../types";
|
|
12
|
+
import { IconName, IconSize } from "../../content/Icon";
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// VARIANT TYPES (Explicitly exported for external use)
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Visual variant for the Button component.
|
|
20
|
+
* Derived from the restyle theme's `buttonVariants` keys.
|
|
21
|
+
*
|
|
22
|
+
* - `solid`: Filled background with the specified color
|
|
23
|
+
* - `outline`: Transparent background with a colored border
|
|
24
|
+
* - `ghost`: Completely transparent, text only
|
|
25
|
+
*/
|
|
26
|
+
export type ButtonVariant = Exclude<
|
|
27
|
+
keyof RestyleTheme["buttonVariants"],
|
|
28
|
+
"defaults" | "disabled"
|
|
29
|
+
>;
|
|
30
|
+
|
|
31
|
+
/** Ref type for the Button component — the underlying React Native View instance. */
|
|
32
|
+
export type ButtonRef = View;
|
|
33
|
+
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// RESTYLE PROP TYPES
|
|
36
|
+
// =============================================================================
|
|
37
|
+
|
|
38
|
+
type ButtonVariantProps = VariantProps<RestyleTheme, "buttonVariants">;
|
|
39
|
+
type ButtonSizeProps = VariantProps<RestyleTheme, "buttonSizes", "size">;
|
|
40
|
+
type ButtonRestyleProps = ButtonVariantProps &
|
|
41
|
+
ButtonSizeProps &
|
|
42
|
+
BoxProps<RestyleTheme>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Size variant for the Button component.
|
|
46
|
+
*
|
|
47
|
+
* - `sm`: Small button - Dense UIs, secondary actions
|
|
48
|
+
* - `md`: Medium button - Default size (meets 44px touch target)
|
|
49
|
+
* - `lg`: Large button - Tablets, high-emphasis actions
|
|
50
|
+
*/
|
|
51
|
+
export type ButtonSize = Exclude<keyof RestyleTheme["buttonSizes"], "defaults">;
|
|
52
|
+
|
|
53
|
+
export type ButtonTextVariant = Exclude<
|
|
54
|
+
keyof RestyleTheme["textVariants"],
|
|
55
|
+
"defaults"
|
|
56
|
+
>;
|
|
57
|
+
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// RESPONSIVE VARIANT TYPES (Shared across ButtonProps and UseButtonLogicParams)
|
|
60
|
+
// =============================================================================
|
|
61
|
+
|
|
62
|
+
export type ButtonResponsiveVariant = ResponsiveValue<
|
|
63
|
+
ButtonVariant,
|
|
64
|
+
RestyleTheme["breakpoints"]
|
|
65
|
+
>;
|
|
66
|
+
|
|
67
|
+
export type ButtonResponsiveSize = ResponsiveValue<
|
|
68
|
+
ButtonSize,
|
|
69
|
+
RestyleTheme["breakpoints"]
|
|
70
|
+
>;
|
|
71
|
+
|
|
72
|
+
export type BaseButtonProps = ButtonRestyleProps & PressableProps;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Props for the Button component.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* // Basic usage
|
|
79
|
+
* <Button title="Save" onPress={handleSave} />
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* // With icon and loading state
|
|
83
|
+
* <Button
|
|
84
|
+
* title="Save"
|
|
85
|
+
* iconName="save"
|
|
86
|
+
* onPress={handleSave}
|
|
87
|
+
* isLoading={isSaving}
|
|
88
|
+
* variant="solid"
|
|
89
|
+
* accessibilityLabel="Save changes"
|
|
90
|
+
* />
|
|
91
|
+
*/
|
|
92
|
+
export interface ButtonProps extends Omit<
|
|
93
|
+
BaseButtonProps,
|
|
94
|
+
"variant" | "style"
|
|
95
|
+
> {
|
|
96
|
+
/** Button title/label text */
|
|
97
|
+
title: string;
|
|
98
|
+
/** Press handler callback */
|
|
99
|
+
onPress: () => void;
|
|
100
|
+
/** Visual variant of the button (supports responsive values) */
|
|
101
|
+
variant?: ButtonResponsiveVariant;
|
|
102
|
+
/** Size of the button (supports responsive values) */
|
|
103
|
+
size?: ButtonResponsiveSize;
|
|
104
|
+
/** Primary color - controls background (solid) or border/text (outline/ghost) */
|
|
105
|
+
color?: RestyleColor;
|
|
106
|
+
/** Whether the button is disabled */
|
|
107
|
+
disabled?: boolean;
|
|
108
|
+
/** Whether the button is in a loading state */
|
|
109
|
+
isLoading?: boolean;
|
|
110
|
+
/** Optional icon name from MaterialIcons to display before the title */
|
|
111
|
+
iconName?: IconName;
|
|
112
|
+
/** Test ID for testing */
|
|
113
|
+
testID?: string;
|
|
114
|
+
/** Accessibility label (required for screen readers) */
|
|
115
|
+
accessibilityLabel?: string;
|
|
116
|
+
/** Accessibility hint for additional context */
|
|
117
|
+
accessibilityHint?: string;
|
|
118
|
+
/** Custom style overrides */
|
|
119
|
+
style?: StyleProp<ViewStyle>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Parameters for the useButtonLogic hook.
|
|
124
|
+
*/
|
|
125
|
+
export interface UseButtonLogicParams {
|
|
126
|
+
title: string;
|
|
127
|
+
variant: ButtonResponsiveVariant;
|
|
128
|
+
size: ButtonResponsiveSize;
|
|
129
|
+
color: RestyleColor;
|
|
130
|
+
disabled: boolean;
|
|
131
|
+
isLoading: boolean;
|
|
132
|
+
iconName?: IconName;
|
|
133
|
+
accessibilityLabel?: string;
|
|
134
|
+
accessibilityHint?: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Return value from the useButtonLogic hook.
|
|
139
|
+
*/
|
|
140
|
+
export interface UseButtonLogicReturn {
|
|
141
|
+
/** Theme variant key for the base component */
|
|
142
|
+
themeVariantKey: ButtonVariant;
|
|
143
|
+
/** Text variant based on button size */
|
|
144
|
+
textVariant: ButtonTextVariant;
|
|
145
|
+
/** Text color based on variant and disabled state */
|
|
146
|
+
textColor: RestyleColor;
|
|
147
|
+
/** Icon size (only if iconName provided) */
|
|
148
|
+
iconSize: IconSize | undefined;
|
|
149
|
+
/** Icon color based on variant and disabled state */
|
|
150
|
+
iconColor: RestyleColor;
|
|
151
|
+
/** Container style with dynamic colors and opacity */
|
|
152
|
+
containerStyle: ViewStyle;
|
|
153
|
+
/** hitSlop insets to ensure 44px minimum touch target */
|
|
154
|
+
hitSlop: Insets;
|
|
155
|
+
/** Accessibility props */
|
|
156
|
+
a11yProps: ButtonA11yProps;
|
|
157
|
+
/** Whether to show spinner instead of content */
|
|
158
|
+
showSpinner: boolean;
|
|
159
|
+
/** Spinner size based on button size */
|
|
160
|
+
spinnerSize: "sm" | "lg";
|
|
161
|
+
/** Spinner color based on variant */
|
|
162
|
+
spinnerColor: RestyleColor;
|
|
163
|
+
/** Translated loading label for spinner accessibility */
|
|
164
|
+
spinnerAccessibilityLabel: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Parameters for the getButtonA11y function.
|
|
169
|
+
*/
|
|
170
|
+
export interface ButtonA11yParams {
|
|
171
|
+
title: string;
|
|
172
|
+
accessibilityLabel?: string;
|
|
173
|
+
accessibilityHint?: string;
|
|
174
|
+
disabled: boolean;
|
|
175
|
+
isLoading: boolean;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Accessibility props returned by getButtonA11y.
|
|
180
|
+
*/
|
|
181
|
+
export interface ButtonA11yProps {
|
|
182
|
+
accessibilityRole: "button";
|
|
183
|
+
accessibilityLabel: string;
|
|
184
|
+
accessibilityHint?: string;
|
|
185
|
+
accessibilityState: { disabled: boolean; busy: boolean };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Parameters for resolveButtonStyle function.
|
|
190
|
+
*/
|
|
191
|
+
export interface ResolveButtonStyleParams {
|
|
192
|
+
variant: ButtonVariant;
|
|
193
|
+
disabled: boolean;
|
|
194
|
+
isLoading: boolean;
|
|
195
|
+
color: RestyleColor;
|
|
196
|
+
theme: RestyleTheme;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Result from resolveButtonStyle function.
|
|
201
|
+
*/
|
|
202
|
+
export interface ButtonStyleResult {
|
|
203
|
+
backgroundColor: string;
|
|
204
|
+
borderColor: string;
|
|
205
|
+
borderWidth: number;
|
|
206
|
+
opacity: number;
|
|
207
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button Component Module
|
|
3
|
+
*
|
|
4
|
+
* A flexible button component for triggering actions.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Three variants: solid, outline, ghost
|
|
8
|
+
* - Three sizes: sm, md, lg (supports responsive values)
|
|
9
|
+
* - Optional icon before text
|
|
10
|
+
* - Loading state with spinner
|
|
11
|
+
* - Full accessibility support
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* import { Button } from '@/src/design-system/primitives/actions/Button';
|
|
15
|
+
*
|
|
16
|
+
* // Basic usage
|
|
17
|
+
* <Button title="Save" onPress={handleSave} />
|
|
18
|
+
*
|
|
19
|
+
* // With icon and loading state
|
|
20
|
+
* <Button
|
|
21
|
+
* title="Save"
|
|
22
|
+
* iconName="save"
|
|
23
|
+
* onPress={handleSave}
|
|
24
|
+
* isLoading={isSaving}
|
|
25
|
+
* variant="solid"
|
|
26
|
+
* accessibilityLabel="Save changes"
|
|
27
|
+
* />
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export { Button } from "./Button";
|
|
31
|
+
|
|
32
|
+
export type {
|
|
33
|
+
ButtonProps,
|
|
34
|
+
ButtonRef,
|
|
35
|
+
ButtonVariant,
|
|
36
|
+
ButtonSize,
|
|
37
|
+
ButtonA11yParams,
|
|
38
|
+
ButtonA11yProps,
|
|
39
|
+
} from "./Button.types";
|
|
40
|
+
|
|
41
|
+
export { getButtonA11y } from "./Button.a11y";
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { useResponsiveProp } from "@shopify/restyle";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { ViewStyle } from "react-native";
|
|
4
|
+
import { useRestyleTheme } from "../../../core/restyle";
|
|
5
|
+
import { useDesignSystem } from "../../../provider";
|
|
6
|
+
import { getButtonA11y } from "./Button.a11y";
|
|
7
|
+
import {
|
|
8
|
+
getButtonHitSlop,
|
|
9
|
+
getButtonIconColor,
|
|
10
|
+
getButtonIconSize,
|
|
11
|
+
getButtonSpinnerColor,
|
|
12
|
+
getButtonSpinnerSize,
|
|
13
|
+
getTextColor,
|
|
14
|
+
getTextVariant,
|
|
15
|
+
resolveButtonStyle,
|
|
16
|
+
} from "./Button.helpers";
|
|
17
|
+
import {
|
|
18
|
+
ButtonSize,
|
|
19
|
+
ButtonVariant,
|
|
20
|
+
UseButtonLogicParams,
|
|
21
|
+
UseButtonLogicReturn,
|
|
22
|
+
} from "./Button.types";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Orchestrates Button rendering logic and state management.
|
|
26
|
+
*
|
|
27
|
+
* Handles:
|
|
28
|
+
* - Responsive size and variant resolution
|
|
29
|
+
* - Theme variant key mapping for Restyle
|
|
30
|
+
* - Text, icon, and spinner styling calculations
|
|
31
|
+
* - Container styles (background, border, opacity)
|
|
32
|
+
* - Hit slop calculation for 44px minimum touch target
|
|
33
|
+
* - Accessibility props generation
|
|
34
|
+
*
|
|
35
|
+
* @param params - Configuration object for button behavior
|
|
36
|
+
* @param params.title - Button label text
|
|
37
|
+
* @param params.variant - Visual variant ('solid' | 'outline' | 'ghost')
|
|
38
|
+
* @param params.size - Size variant ('sm' | 'md' | 'lg')
|
|
39
|
+
* @param params.color - Theme color token for background/text
|
|
40
|
+
* @param params.disabled - Whether button is disabled
|
|
41
|
+
* @param params.isLoading - Whether to show loading spinner
|
|
42
|
+
* @param params.iconName - Optional icon to display
|
|
43
|
+
* @param params.accessibilityLabel - Custom a11y label
|
|
44
|
+
* @param params.accessibilityHint - Describes action result
|
|
45
|
+
*
|
|
46
|
+
* @returns Computed values for rendering Button
|
|
47
|
+
* @returns {string} themeVariantKey - Restyle theme variant identifier
|
|
48
|
+
* @returns {string} textVariant - Text component variant
|
|
49
|
+
* @returns {string} textColor - Text color token
|
|
50
|
+
* @returns {number} iconSize - Icon size in pixels
|
|
51
|
+
* @returns {string} iconColor - Icon color token
|
|
52
|
+
* @returns {ViewStyle} containerStyle - Dynamic container styles
|
|
53
|
+
* @returns {Insets} hitSlop - Touch target expansion
|
|
54
|
+
* @returns {object} a11yProps - Accessibility props
|
|
55
|
+
* @returns {boolean} showSpinner - Whether to display spinner
|
|
56
|
+
* @returns {string} spinnerSize - Spinner size variant
|
|
57
|
+
* @returns {string} spinnerColor - Spinner color token
|
|
58
|
+
*
|
|
59
|
+
* @performance This hook uses useMemo() for expensive calculations
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* const { themeVariantKey, textColor, iconSize, a11yProps } = useButtonLogic({
|
|
63
|
+
* title: "Save Changes",
|
|
64
|
+
* variant: "solid",
|
|
65
|
+
* size: "md",
|
|
66
|
+
* color: "accentPrimary",
|
|
67
|
+
* iconName: "save",
|
|
68
|
+
* accessibilityLabel: "Save your changes"
|
|
69
|
+
* });
|
|
70
|
+
*/
|
|
71
|
+
export function useButtonLogic(
|
|
72
|
+
params: UseButtonLogicParams,
|
|
73
|
+
): UseButtonLogicReturn {
|
|
74
|
+
const {
|
|
75
|
+
title,
|
|
76
|
+
variant,
|
|
77
|
+
size,
|
|
78
|
+
color,
|
|
79
|
+
disabled,
|
|
80
|
+
isLoading,
|
|
81
|
+
iconName,
|
|
82
|
+
accessibilityLabel,
|
|
83
|
+
accessibilityHint,
|
|
84
|
+
} = params;
|
|
85
|
+
|
|
86
|
+
const { labels: t } = useDesignSystem();
|
|
87
|
+
const theme = useRestyleTheme();
|
|
88
|
+
const resolvedSize = (useResponsiveProp(size) ?? "md") as ButtonSize;
|
|
89
|
+
const resolvedVariant = (useResponsiveProp(variant) ?? "solid") as ButtonVariant;
|
|
90
|
+
|
|
91
|
+
// Theme variant key — ButtonVariant is derived from restyle, so it maps directly
|
|
92
|
+
const themeVariantKey = resolvedVariant;
|
|
93
|
+
|
|
94
|
+
// Text styling
|
|
95
|
+
const textVariant = getTextVariant(resolvedSize);
|
|
96
|
+
const textColor = getTextColor(resolvedVariant, color, disabled);
|
|
97
|
+
|
|
98
|
+
// Icon styling (only computed if iconName provided)
|
|
99
|
+
const iconSize = iconName ? getButtonIconSize(resolvedSize) : undefined;
|
|
100
|
+
const iconColor = getButtonIconColor(resolvedVariant, color, disabled);
|
|
101
|
+
|
|
102
|
+
// Touch target - ensure 44px minimum
|
|
103
|
+
const hitSlop = getButtonHitSlop(resolvedSize);
|
|
104
|
+
|
|
105
|
+
// Spinner styling
|
|
106
|
+
const spinnerSize = getButtonSpinnerSize(resolvedSize);
|
|
107
|
+
const spinnerColor = getButtonSpinnerColor(resolvedVariant, color, disabled);
|
|
108
|
+
|
|
109
|
+
// Whether to show spinner instead of content
|
|
110
|
+
const showSpinner = isLoading;
|
|
111
|
+
|
|
112
|
+
// Translated spinner label
|
|
113
|
+
const spinnerAccessibilityLabel = t.designSystem.button.loadingLabel;
|
|
114
|
+
|
|
115
|
+
// Container style - only dynamic colors and opacity
|
|
116
|
+
const containerStyle = useMemo((): ViewStyle => {
|
|
117
|
+
const styleResult = resolveButtonStyle({
|
|
118
|
+
variant: resolvedVariant,
|
|
119
|
+
disabled,
|
|
120
|
+
isLoading,
|
|
121
|
+
color,
|
|
122
|
+
theme,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
backgroundColor: styleResult.backgroundColor,
|
|
127
|
+
borderColor: styleResult.borderColor,
|
|
128
|
+
borderWidth: styleResult.borderWidth,
|
|
129
|
+
opacity: styleResult.opacity,
|
|
130
|
+
};
|
|
131
|
+
}, [resolvedVariant, disabled, isLoading, color, theme]);
|
|
132
|
+
|
|
133
|
+
// Accessibility props
|
|
134
|
+
const a11yProps = useMemo(
|
|
135
|
+
() =>
|
|
136
|
+
getButtonA11y({
|
|
137
|
+
title,
|
|
138
|
+
accessibilityLabel,
|
|
139
|
+
accessibilityHint,
|
|
140
|
+
disabled,
|
|
141
|
+
isLoading,
|
|
142
|
+
}),
|
|
143
|
+
[title, accessibilityLabel, accessibilityHint, disabled, isLoading],
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
themeVariantKey,
|
|
148
|
+
textVariant,
|
|
149
|
+
textColor,
|
|
150
|
+
iconSize,
|
|
151
|
+
iconColor,
|
|
152
|
+
containerStyle,
|
|
153
|
+
hitSlop,
|
|
154
|
+
a11yProps,
|
|
155
|
+
showSpinner,
|
|
156
|
+
spinnerSize,
|
|
157
|
+
spinnerColor,
|
|
158
|
+
spinnerAccessibilityLabel,
|
|
159
|
+
};
|
|
160
|
+
}
|