@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,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IconButton Component Helpers
|
|
3
|
+
*
|
|
4
|
+
* Pure functions for computing IconButton styles, sizes, and colors.
|
|
5
|
+
* All calculations are derived from the centralized scales.ts token file.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Insets } from "react-native";
|
|
9
|
+
import {
|
|
10
|
+
GHOST_ICON_BUTTON_HIT_SLOP,
|
|
11
|
+
ICON_BUTTON_HIT_SLOP,
|
|
12
|
+
ICON_BUTTON_ICON_SIZE,
|
|
13
|
+
SPINNER_SIZE,
|
|
14
|
+
} from "../../../tokens/scales";
|
|
15
|
+
import { BaseThemeColor, RestyleColor } from "../../../types";
|
|
16
|
+
import { IconSize } from "../../content/Icon";
|
|
17
|
+
import {
|
|
18
|
+
IconButtonSize,
|
|
19
|
+
IconButtonStyleResult,
|
|
20
|
+
IconButtonVariant,
|
|
21
|
+
ResolveIconButtonStyleParams,
|
|
22
|
+
} from "./IconButton.types";
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// ICON SIZE RESOLUTION
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get icon size based on button size.
|
|
30
|
+
*
|
|
31
|
+
* Uses ICON_BUTTON_ICON_SIZE from scales.ts for mathematical consistency.
|
|
32
|
+
* Target ratio: ~45% (ICON_RATIOS.circularButton)
|
|
33
|
+
*
|
|
34
|
+
* | Size | Container | Icon | Ratio |
|
|
35
|
+
* |------|-----------|------|-------|
|
|
36
|
+
* | sm | 36px | 16px | 44% |
|
|
37
|
+
* | md | 44px | 20px | 45% |
|
|
38
|
+
* | lg | 56px | 24px | 43% |
|
|
39
|
+
*
|
|
40
|
+
* @param size - The button size variant
|
|
41
|
+
* @returns The corresponding icon size
|
|
42
|
+
*/
|
|
43
|
+
export function getIconButtonIconSize(size: IconButtonSize): IconSize {
|
|
44
|
+
return ICON_BUTTON_ICON_SIZE[size];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// TOUCH TARGET (hitSlop)
|
|
49
|
+
// =============================================================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get hitSlop based on button size and variant.
|
|
53
|
+
*
|
|
54
|
+
* Uses pre-calculated values from scales.ts to ensure
|
|
55
|
+
* all buttons meet the 44px minimum touch target.
|
|
56
|
+
*
|
|
57
|
+
* For contained/outlined variants:
|
|
58
|
+
* | Size | Container | hitSlop | Total |
|
|
59
|
+
* |------|-----------|---------|-------|
|
|
60
|
+
* | sm | 36px | 4px | 44px |
|
|
61
|
+
* | md | 44px | 0px | 44px |
|
|
62
|
+
* | lg | 56px | 0px | 56px |
|
|
63
|
+
*
|
|
64
|
+
* For ghost variant (icon size only):
|
|
65
|
+
* | Size | Icon | hitSlop | Total |
|
|
66
|
+
* |------|--------|---------|-------|
|
|
67
|
+
* | sm | 16px | 14px | 44px |
|
|
68
|
+
* | md | 20px | 12px | 44px |
|
|
69
|
+
* | lg | 24px | 10px | 44px |
|
|
70
|
+
*
|
|
71
|
+
* @param size - The button size variant
|
|
72
|
+
* @param variant - The visual variant
|
|
73
|
+
* @returns Insets for touch target expansion
|
|
74
|
+
*/
|
|
75
|
+
export function getIconButtonHitSlop(
|
|
76
|
+
size: IconButtonSize,
|
|
77
|
+
variant: IconButtonVariant = "contained"
|
|
78
|
+
): Insets {
|
|
79
|
+
// Ghost variant has no container, so use icon-based hitSlop
|
|
80
|
+
if (variant === "ghost") {
|
|
81
|
+
return GHOST_ICON_BUTTON_HIT_SLOP[size];
|
|
82
|
+
}
|
|
83
|
+
return ICON_BUTTON_HIT_SLOP[size];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// =============================================================================
|
|
87
|
+
// SPINNER SIZE
|
|
88
|
+
// =============================================================================
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get spinner size based on button size.
|
|
92
|
+
*
|
|
93
|
+
* Uses SPINNER_SIZE from scales.ts for consistency across all components.
|
|
94
|
+
*
|
|
95
|
+
* | Button Size | Spinner Size |
|
|
96
|
+
* |-------------|--------------|
|
|
97
|
+
* | sm | sm |
|
|
98
|
+
* | md | sm |
|
|
99
|
+
* | lg | lg |
|
|
100
|
+
*
|
|
101
|
+
* @param size - The button size variant
|
|
102
|
+
* @returns The spinner size
|
|
103
|
+
*/
|
|
104
|
+
export function getIconButtonSpinnerSize(size: IconButtonSize): "sm" | "lg" {
|
|
105
|
+
return SPINNER_SIZE[size];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// =============================================================================
|
|
109
|
+
// COLOR RESOLUTION
|
|
110
|
+
// =============================================================================
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get icon color based on variant and disabled state.
|
|
114
|
+
*
|
|
115
|
+
* | Variant | Normal | Disabled |
|
|
116
|
+
* |-----------|--------------|--------------|
|
|
117
|
+
* | contained | textInverse | textDisabled |
|
|
118
|
+
* | outlined | color prop | textDisabled |
|
|
119
|
+
* | ghost | color prop | textDisabled |
|
|
120
|
+
*
|
|
121
|
+
* @param variant - The visual variant
|
|
122
|
+
* @param color - The primary color prop
|
|
123
|
+
* @param disabled - Whether the button is disabled
|
|
124
|
+
* @returns The computed icon color
|
|
125
|
+
*/
|
|
126
|
+
export function getIconButtonIconColor(
|
|
127
|
+
variant: IconButtonVariant,
|
|
128
|
+
color: RestyleColor,
|
|
129
|
+
disabled: boolean
|
|
130
|
+
): RestyleColor {
|
|
131
|
+
if (disabled) {
|
|
132
|
+
return "textDisabled";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Contained variant uses inverse text color for contrast
|
|
136
|
+
if (variant === "contained") {
|
|
137
|
+
return "textInverse";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Outlined and ghost variants use the specified color
|
|
141
|
+
return color;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get spinner color based on variant and disabled state.
|
|
146
|
+
*
|
|
147
|
+
* Follows the same logic as icon color for visual consistency.
|
|
148
|
+
*
|
|
149
|
+
* @param variant - The visual variant
|
|
150
|
+
* @param color - The primary color prop
|
|
151
|
+
* @param disabled - Whether the button is disabled
|
|
152
|
+
* @returns The computed spinner color
|
|
153
|
+
*/
|
|
154
|
+
export function getIconButtonSpinnerColor(
|
|
155
|
+
variant: IconButtonVariant,
|
|
156
|
+
color: RestyleColor,
|
|
157
|
+
disabled: boolean
|
|
158
|
+
): RestyleColor {
|
|
159
|
+
// Spinner uses same color logic as icon
|
|
160
|
+
return getIconButtonIconColor(variant, color, disabled);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// =============================================================================
|
|
164
|
+
// THEME VARIANT KEY
|
|
165
|
+
// =============================================================================
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Maps component variant to theme variant key.
|
|
169
|
+
*
|
|
170
|
+
* Currently 1:1 mapping, but this abstraction allows for
|
|
171
|
+
* future divergence between component API and theme structure.
|
|
172
|
+
*
|
|
173
|
+
* @param variant - The component variant
|
|
174
|
+
* @returns The theme variant key
|
|
175
|
+
*/
|
|
176
|
+
export function getIconButtonThemeVariantKey(
|
|
177
|
+
variant: IconButtonVariant
|
|
178
|
+
): IconButtonVariant {
|
|
179
|
+
return variant;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// =============================================================================
|
|
183
|
+
// STYLE RESOLUTION
|
|
184
|
+
// =============================================================================
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Resolve dynamic style properties for the IconButton container.
|
|
188
|
+
*
|
|
189
|
+
* Static styles (borderRadius) come from theme variants.
|
|
190
|
+
* This function handles dynamic state-based values:
|
|
191
|
+
* - backgroundColor based on variant and disabled state
|
|
192
|
+
* - borderColor based on variant and disabled state
|
|
193
|
+
* - opacity based on disabled/loading state
|
|
194
|
+
*
|
|
195
|
+
* @param params - Resolution parameters
|
|
196
|
+
* @returns Computed style object
|
|
197
|
+
*/
|
|
198
|
+
export function resolveIconButtonStyle(
|
|
199
|
+
params: ResolveIconButtonStyleParams
|
|
200
|
+
): IconButtonStyleResult {
|
|
201
|
+
const { variant, disabled, isLoading, color, theme } = params;
|
|
202
|
+
|
|
203
|
+
const isDisabledOrLoading = disabled || isLoading;
|
|
204
|
+
|
|
205
|
+
let backgroundColor: string;
|
|
206
|
+
let borderColor: string;
|
|
207
|
+
let borderWidth: number;
|
|
208
|
+
|
|
209
|
+
switch (variant) {
|
|
210
|
+
case "contained":
|
|
211
|
+
backgroundColor = isDisabledOrLoading
|
|
212
|
+
? theme.colors.interactiveDisabled
|
|
213
|
+
: theme.colors[color as BaseThemeColor];
|
|
214
|
+
borderColor = "transparent";
|
|
215
|
+
borderWidth = 0;
|
|
216
|
+
break;
|
|
217
|
+
|
|
218
|
+
case "outlined":
|
|
219
|
+
backgroundColor = "transparent";
|
|
220
|
+
borderColor = isDisabledOrLoading
|
|
221
|
+
? theme.colors.borderDefault
|
|
222
|
+
: theme.colors[color as BaseThemeColor];
|
|
223
|
+
borderWidth = 2;
|
|
224
|
+
break;
|
|
225
|
+
|
|
226
|
+
case "ghost":
|
|
227
|
+
default:
|
|
228
|
+
backgroundColor = "transparent";
|
|
229
|
+
borderColor = "transparent";
|
|
230
|
+
borderWidth = 0;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return { backgroundColor, borderColor, borderWidth, opacity: 1 };
|
|
235
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IconButton Component
|
|
3
|
+
*
|
|
4
|
+
* @description Circular icon-only button for actions - Atom
|
|
5
|
+
*
|
|
6
|
+
* IconButton provides icon-only interactive controls for common actions
|
|
7
|
+
* like favorites, settings, close, etc. It's optimized for touch with
|
|
8
|
+
* proper hit targets and accessibility.
|
|
9
|
+
*
|
|
10
|
+
* ## Ratios & Constraints
|
|
11
|
+
* - Icon = Container × ~45% (ICON_RATIOS.circularButton)
|
|
12
|
+
* - All sizes meet 44px minimum touch target via hitSlop
|
|
13
|
+
*
|
|
14
|
+
* ## Size Scale
|
|
15
|
+
* | Size | Container | Icon | hitSlop | Touch Target |
|
|
16
|
+
* |------|-----------|------|---------|--------------|
|
|
17
|
+
* | sm | 36px | 16px | 4px | 44px |
|
|
18
|
+
* | md | 44px | 20px | 0px | 44px |
|
|
19
|
+
* | lg | 56px | 24px | 0px | 56px |
|
|
20
|
+
*
|
|
21
|
+
* ## Variants
|
|
22
|
+
* - `contained`: Solid background (default)
|
|
23
|
+
* - `outlined`: Border only, transparent background
|
|
24
|
+
* - `ghost`: Icon only, no container (minimal footprint)
|
|
25
|
+
*
|
|
26
|
+
* ## Features
|
|
27
|
+
* - Responsive size prop (phone/tablet breakpoints)
|
|
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, icon sizing)
|
|
34
|
+
* - Icon calculations cached using ~45% container ratio for 60fps performance
|
|
35
|
+
* - Ghost variant dynamic sizing optimized with conditional style computation
|
|
36
|
+
*
|
|
37
|
+
* @see IconButton.types.ts - Type definitions
|
|
38
|
+
* @see IconButton.helpers.ts - Pure calculation functions
|
|
39
|
+
* @see IconButton.a11y.ts - Accessibility prop generation
|
|
40
|
+
* @see tokens/scales.ts - Mathematical ratios
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // Basic usage
|
|
44
|
+
* <IconButton
|
|
45
|
+
* iconName="favorite"
|
|
46
|
+
* onPress={handleLike}
|
|
47
|
+
* accessibilityLabel="Add to favorites"
|
|
48
|
+
* />
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* // Responsive with loading state
|
|
52
|
+
* <IconButton
|
|
53
|
+
* iconName="save"
|
|
54
|
+
* onPress={handleSave}
|
|
55
|
+
* variant="outlined"
|
|
56
|
+
* size={{ phone: "md", tablet: "lg" }}
|
|
57
|
+
* isLoading={isSaving}
|
|
58
|
+
* accessibilityLabel="Save changes"
|
|
59
|
+
* />
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
import {
|
|
63
|
+
backgroundColor,
|
|
64
|
+
border,
|
|
65
|
+
createRestyleComponent,
|
|
66
|
+
createVariant,
|
|
67
|
+
layout,
|
|
68
|
+
spacing,
|
|
69
|
+
spacingShorthand,
|
|
70
|
+
} from "@shopify/restyle";
|
|
71
|
+
import React, { memo, useCallback } from "react";
|
|
72
|
+
import { Pressable } from "react-native";
|
|
73
|
+
import { RestyleTheme } from "../../../core/restyle";
|
|
74
|
+
import { Icon } from "../../content/Icon";
|
|
75
|
+
import { Spinner } from "../../feedback/Spinner";
|
|
76
|
+
import {
|
|
77
|
+
BaseIconButtonContainerProps,
|
|
78
|
+
IconButtonProps,
|
|
79
|
+
} from "./IconButton.types";
|
|
80
|
+
import { useIconButtonLogic } from "./useIconButtonLogic";
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Base container with Restyle variant support.
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
const BaseIconButtonContainer = createRestyleComponent<
|
|
87
|
+
BaseIconButtonContainerProps,
|
|
88
|
+
RestyleTheme
|
|
89
|
+
>(
|
|
90
|
+
[
|
|
91
|
+
createVariant({ themeKey: "iconButtonVariants" }),
|
|
92
|
+
createVariant({ themeKey: "iconButtonSizes", property: "size" }),
|
|
93
|
+
backgroundColor,
|
|
94
|
+
border,
|
|
95
|
+
layout,
|
|
96
|
+
spacing,
|
|
97
|
+
spacingShorthand,
|
|
98
|
+
],
|
|
99
|
+
Pressable,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
function IconButtonComponent({
|
|
103
|
+
iconName,
|
|
104
|
+
onPress,
|
|
105
|
+
variant = "contained",
|
|
106
|
+
size = "md",
|
|
107
|
+
color = "accentPrimary",
|
|
108
|
+
disabled = false,
|
|
109
|
+
isLoading = false,
|
|
110
|
+
testID,
|
|
111
|
+
accessibilityLabel,
|
|
112
|
+
accessibilityHint,
|
|
113
|
+
...rest
|
|
114
|
+
}: IconButtonProps) {
|
|
115
|
+
const {
|
|
116
|
+
iconSize,
|
|
117
|
+
iconColor,
|
|
118
|
+
containerStyle,
|
|
119
|
+
hitSlop,
|
|
120
|
+
a11yProps,
|
|
121
|
+
showSpinner,
|
|
122
|
+
spinnerSize,
|
|
123
|
+
spinnerColor,
|
|
124
|
+
themeVariantKey,
|
|
125
|
+
spinnerAccessibilityLabel,
|
|
126
|
+
} = useIconButtonLogic({
|
|
127
|
+
iconName,
|
|
128
|
+
variant,
|
|
129
|
+
size,
|
|
130
|
+
color,
|
|
131
|
+
disabled,
|
|
132
|
+
isLoading,
|
|
133
|
+
accessibilityLabel,
|
|
134
|
+
accessibilityHint,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const handlePress = useCallback(() => {
|
|
138
|
+
if (!disabled && !isLoading) {
|
|
139
|
+
onPress();
|
|
140
|
+
}
|
|
141
|
+
}, [disabled, isLoading, onPress]);
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<BaseIconButtonContainer
|
|
145
|
+
variant={themeVariantKey}
|
|
146
|
+
// Only apply size variant for contained/outlined - ghost should wrap icon
|
|
147
|
+
size={variant === "ghost" ? undefined : size}
|
|
148
|
+
onPress={handlePress}
|
|
149
|
+
disabled={disabled || isLoading}
|
|
150
|
+
hitSlop={hitSlop}
|
|
151
|
+
testID={testID}
|
|
152
|
+
alignItems="center"
|
|
153
|
+
justifyContent="center"
|
|
154
|
+
style={containerStyle}
|
|
155
|
+
{...a11yProps}
|
|
156
|
+
{...rest}
|
|
157
|
+
>
|
|
158
|
+
{showSpinner ? (
|
|
159
|
+
<Spinner
|
|
160
|
+
size={spinnerSize}
|
|
161
|
+
color={spinnerColor}
|
|
162
|
+
accessibilityLabel={spinnerAccessibilityLabel}
|
|
163
|
+
/>
|
|
164
|
+
) : (
|
|
165
|
+
<Icon
|
|
166
|
+
name={iconName}
|
|
167
|
+
size={iconSize}
|
|
168
|
+
color={iconColor}
|
|
169
|
+
accessibilityLabel={iconName}
|
|
170
|
+
/>
|
|
171
|
+
)}
|
|
172
|
+
</BaseIconButtonContainer>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export const IconButton = memo(IconButtonComponent);
|
|
177
|
+
IconButton.displayName = "IconButton";
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IconButton Component Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the IconButton 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, 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 style variants for IconButton.
|
|
20
|
+
*
|
|
21
|
+
* - `contained`: Solid background with inverse icon color
|
|
22
|
+
* - `outlined`: Transparent background with colored border
|
|
23
|
+
* - `ghost`: No background or border, icon only (minimal footprint)
|
|
24
|
+
*/
|
|
25
|
+
export type IconButtonVariant = Exclude<
|
|
26
|
+
keyof RestyleTheme["iconButtonVariants"],
|
|
27
|
+
"defaults"
|
|
28
|
+
>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Size variants derived from theme.
|
|
32
|
+
* Maps to iconButtonSizes in restyleTheme.ts
|
|
33
|
+
*
|
|
34
|
+
* | Size | Container | Touch Target |
|
|
35
|
+
* |------|-----------|--------------|
|
|
36
|
+
* | sm | 36px | 44px (hitSlop) |
|
|
37
|
+
* | md | 44px | 44px (native) |
|
|
38
|
+
* | lg | 56px | 56px (native) |
|
|
39
|
+
*/
|
|
40
|
+
export type IconButtonSize = Exclude<
|
|
41
|
+
keyof RestyleTheme["iconButtonSizes"],
|
|
42
|
+
"defaults"
|
|
43
|
+
>;
|
|
44
|
+
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// RESTYLE PROP TYPES
|
|
47
|
+
// =============================================================================
|
|
48
|
+
|
|
49
|
+
type IconButtonVariantProps = VariantProps<RestyleTheme, "iconButtonVariants">;
|
|
50
|
+
type IconButtonSizeVariantProps = VariantProps<
|
|
51
|
+
RestyleTheme,
|
|
52
|
+
"iconButtonSizes",
|
|
53
|
+
"size"
|
|
54
|
+
>;
|
|
55
|
+
type IconButtonRestyleProps = IconButtonVariantProps &
|
|
56
|
+
IconButtonSizeVariantProps &
|
|
57
|
+
BoxProps<RestyleTheme>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Base props for the IconButton container component.
|
|
61
|
+
* Combines Restyle props with Pressable props.
|
|
62
|
+
*/
|
|
63
|
+
export type BaseIconButtonContainerProps = IconButtonRestyleProps &
|
|
64
|
+
PressableProps;
|
|
65
|
+
|
|
66
|
+
// =============================================================================
|
|
67
|
+
// COMPONENT PROPS
|
|
68
|
+
// =============================================================================
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Props for the IconButton component.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* // Basic usage
|
|
75
|
+
* <IconButton
|
|
76
|
+
* iconName="favorite"
|
|
77
|
+
* onPress={handleLike}
|
|
78
|
+
* accessibilityLabel="Add to favorites"
|
|
79
|
+
* />
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* // Responsive size with loading state
|
|
83
|
+
* <IconButton
|
|
84
|
+
* iconName="save"
|
|
85
|
+
* onPress={handleSave}
|
|
86
|
+
* variant="outlined"
|
|
87
|
+
* size={{ phone: "md", tablet: "lg" }}
|
|
88
|
+
* color="accentPrimary"
|
|
89
|
+
* isLoading={isSaving}
|
|
90
|
+
* accessibilityLabel="Save changes"
|
|
91
|
+
* accessibilityHint="Double tap to save your changes"
|
|
92
|
+
* />
|
|
93
|
+
*/
|
|
94
|
+
export interface IconButtonProps
|
|
95
|
+
extends Omit<IconButtonRestyleProps, "size" | "variant"> {
|
|
96
|
+
/** Icon name from MaterialIcons */
|
|
97
|
+
iconName: IconName;
|
|
98
|
+
|
|
99
|
+
/** Press handler callback */
|
|
100
|
+
onPress: () => void;
|
|
101
|
+
|
|
102
|
+
/** Visual variant of the button (supports responsive values) */
|
|
103
|
+
variant?: ResponsiveValue<IconButtonVariant, RestyleTheme["breakpoints"]>;
|
|
104
|
+
|
|
105
|
+
/** Size of the button (supports responsive values) */
|
|
106
|
+
size?: ResponsiveValue<IconButtonSize, RestyleTheme["breakpoints"]>;
|
|
107
|
+
|
|
108
|
+
/** Primary color - controls background (contained) or border/icon (outlined/ghost) */
|
|
109
|
+
color?: RestyleColor;
|
|
110
|
+
|
|
111
|
+
/** Whether the button is disabled */
|
|
112
|
+
disabled?: boolean;
|
|
113
|
+
|
|
114
|
+
/** Whether the button is in a loading state */
|
|
115
|
+
isLoading?: boolean;
|
|
116
|
+
|
|
117
|
+
/** Test ID for testing */
|
|
118
|
+
testID?: string;
|
|
119
|
+
|
|
120
|
+
/** Accessibility label (required for screen readers) */
|
|
121
|
+
accessibilityLabel: string;
|
|
122
|
+
|
|
123
|
+
/** Accessibility hint for additional context */
|
|
124
|
+
accessibilityHint?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// =============================================================================
|
|
128
|
+
// LOGIC HOOK TYPES
|
|
129
|
+
// =============================================================================
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Parameters for the useIconButtonLogic hook.
|
|
133
|
+
*/
|
|
134
|
+
export interface UseIconButtonLogicParams {
|
|
135
|
+
/** Icon name from MaterialIcons */
|
|
136
|
+
iconName: IconName;
|
|
137
|
+
|
|
138
|
+
/** Visual variant of the button */
|
|
139
|
+
variant: ResponsiveValue<IconButtonVariant, RestyleTheme["breakpoints"]>;
|
|
140
|
+
|
|
141
|
+
/** Size of the button (supports responsive values) */
|
|
142
|
+
size: ResponsiveValue<IconButtonSize, RestyleTheme["breakpoints"]>;
|
|
143
|
+
|
|
144
|
+
/** Primary color for the button */
|
|
145
|
+
color: RestyleColor;
|
|
146
|
+
|
|
147
|
+
/** Whether the button is disabled */
|
|
148
|
+
disabled: boolean;
|
|
149
|
+
|
|
150
|
+
/** Whether the button is in a loading state */
|
|
151
|
+
isLoading: boolean;
|
|
152
|
+
|
|
153
|
+
/** Accessibility label for screen readers */
|
|
154
|
+
accessibilityLabel: string;
|
|
155
|
+
|
|
156
|
+
/** Accessibility hint for additional context */
|
|
157
|
+
accessibilityHint?: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Return value from the useIconButtonLogic hook.
|
|
162
|
+
* Contains all computed values needed to render the IconButton.
|
|
163
|
+
*/
|
|
164
|
+
export interface UseIconButtonLogicReturn {
|
|
165
|
+
/** Resolved icon size from scale mapping */
|
|
166
|
+
iconSize: IconSize;
|
|
167
|
+
|
|
168
|
+
/** Computed icon color based on variant and state */
|
|
169
|
+
iconColor: RestyleColor;
|
|
170
|
+
|
|
171
|
+
/** Dynamic container styles (backgroundColor, borderColor, opacity) */
|
|
172
|
+
containerStyle: ViewStyle;
|
|
173
|
+
|
|
174
|
+
/** Touch target expansion based on visual size */
|
|
175
|
+
hitSlop: Insets;
|
|
176
|
+
|
|
177
|
+
/** Accessibility properties object */
|
|
178
|
+
a11yProps: IconButtonA11yProps;
|
|
179
|
+
|
|
180
|
+
/** Whether to show spinner instead of icon */
|
|
181
|
+
showSpinner: boolean;
|
|
182
|
+
|
|
183
|
+
/** Spinner size derived from button size */
|
|
184
|
+
spinnerSize: "sm" | "lg";
|
|
185
|
+
|
|
186
|
+
/** Spinner color matching icon color logic */
|
|
187
|
+
spinnerColor: RestyleColor;
|
|
188
|
+
|
|
189
|
+
/** Theme variant key for BaseIconButtonContainer */
|
|
190
|
+
themeVariantKey: IconButtonVariant;
|
|
191
|
+
|
|
192
|
+
/** Translated loading label for spinner accessibility */
|
|
193
|
+
spinnerAccessibilityLabel: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// =============================================================================
|
|
197
|
+
// ACCESSIBILITY TYPES
|
|
198
|
+
// =============================================================================
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Parameters for the getIconButtonA11y function.
|
|
202
|
+
*/
|
|
203
|
+
export interface IconButtonA11yParams {
|
|
204
|
+
/** Accessibility label for screen readers */
|
|
205
|
+
accessibilityLabel: string;
|
|
206
|
+
|
|
207
|
+
/** Optional accessibility hint */
|
|
208
|
+
accessibilityHint?: string;
|
|
209
|
+
|
|
210
|
+
/** Whether the button is disabled */
|
|
211
|
+
disabled: boolean;
|
|
212
|
+
|
|
213
|
+
/** Whether the button is loading */
|
|
214
|
+
isLoading: boolean;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Accessibility props returned by getIconButtonA11y.
|
|
219
|
+
*/
|
|
220
|
+
export interface IconButtonA11yProps {
|
|
221
|
+
/** Label read by screen readers */
|
|
222
|
+
accessibilityLabel: string;
|
|
223
|
+
|
|
224
|
+
/** Hint read by screen readers (optional) */
|
|
225
|
+
accessibilityHint?: string;
|
|
226
|
+
|
|
227
|
+
/** Role for screen readers */
|
|
228
|
+
accessibilityRole: "button";
|
|
229
|
+
|
|
230
|
+
/** State for screen readers */
|
|
231
|
+
accessibilityState: { disabled: boolean; busy: boolean };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// =============================================================================
|
|
235
|
+
// STYLE RESOLUTION TYPES
|
|
236
|
+
// =============================================================================
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Parameters for the resolveIconButtonStyle function.
|
|
240
|
+
*/
|
|
241
|
+
export interface ResolveIconButtonStyleParams {
|
|
242
|
+
/** Visual variant of the button */
|
|
243
|
+
variant: IconButtonVariant;
|
|
244
|
+
|
|
245
|
+
/** Whether the button is disabled */
|
|
246
|
+
disabled: boolean;
|
|
247
|
+
|
|
248
|
+
/** Whether the button is loading */
|
|
249
|
+
isLoading: boolean;
|
|
250
|
+
|
|
251
|
+
/** Primary color for the button */
|
|
252
|
+
color: RestyleColor;
|
|
253
|
+
|
|
254
|
+
/** The Restyle theme object */
|
|
255
|
+
theme: RestyleTheme;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Result from resolveIconButtonStyle function.
|
|
260
|
+
*/
|
|
261
|
+
export interface IconButtonStyleResult {
|
|
262
|
+
/** Background color value */
|
|
263
|
+
backgroundColor: string;
|
|
264
|
+
|
|
265
|
+
/** Border color value */
|
|
266
|
+
borderColor: string;
|
|
267
|
+
|
|
268
|
+
/** Border width in pixels */
|
|
269
|
+
borderWidth: number;
|
|
270
|
+
|
|
271
|
+
/** Opacity value (0-1) */
|
|
272
|
+
opacity: number;
|
|
273
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IconButton Module
|
|
3
|
+
*
|
|
4
|
+
* Atom component for circular icon-only buttons.
|
|
5
|
+
*
|
|
6
|
+
* ## Mathematical Ratios
|
|
7
|
+
* - Icon = Container × ~45% (ICON_RATIOS.circularButton)
|
|
8
|
+
* - All sizes meet 44px minimum touch target
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* import { IconButton } from "@/design-system/primitives/actions/IconButton";
|
|
12
|
+
*
|
|
13
|
+
* <IconButton
|
|
14
|
+
* iconName="favorite"
|
|
15
|
+
* onPress={handleLike}
|
|
16
|
+
* accessibilityLabel="Add to favorites"
|
|
17
|
+
* />
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export { IconButton } from "./IconButton";
|
|
21
|
+
|
|
22
|
+
export type {
|
|
23
|
+
IconButtonProps,
|
|
24
|
+
IconButtonVariant,
|
|
25
|
+
IconButtonSize,
|
|
26
|
+
IconButtonA11yParams,
|
|
27
|
+
IconButtonA11yProps,
|
|
28
|
+
} from "./IconButton.types";
|
|
29
|
+
|
|
30
|
+
export { getIconButtonA11y } from "./IconButton.a11y";
|