@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.
Files changed (187) hide show
  1. package/dist/index.d.mts +52556 -0
  2. package/dist/index.d.ts +52556 -0
  3. package/dist/index.js +8753 -0
  4. package/dist/index.mjs +8777 -0
  5. package/package.json +70 -0
  6. package/src/__test-utils__/index.tsx +39 -0
  7. package/src/components/CalendarStrip/CalendarStrip.helpers.ts +106 -0
  8. package/src/components/CalendarStrip/CalendarStrip.tsx +83 -0
  9. package/src/components/CalendarStrip/CalendarStrip.types.ts +133 -0
  10. package/src/components/CalendarStrip/DayCard/DayCard.helpers.ts +44 -0
  11. package/src/components/CalendarStrip/DayCard/DayCard.tsx +71 -0
  12. package/src/components/CalendarStrip/DayCard/DayCard.types.ts +134 -0
  13. package/src/components/CalendarStrip/DayCard/index.ts +2 -0
  14. package/src/components/CalendarStrip/DayCard/useDayCardLogic.ts +45 -0
  15. package/src/components/CalendarStrip/index.ts +9 -0
  16. package/src/components/CalendarStrip/useCalendarStripLogic.ts +53 -0
  17. package/src/components/EmptyState/EmptyState.helpers.ts +104 -0
  18. package/src/components/EmptyState/EmptyState.tsx +205 -0
  19. package/src/components/EmptyState/EmptyState.types.ts +213 -0
  20. package/src/components/EmptyState/index.ts +44 -0
  21. package/src/components/EmptyState/useEmptyStateLogic.ts +131 -0
  22. package/src/components/Header/Header.helpers.ts +93 -0
  23. package/src/components/Header/Header.tsx +185 -0
  24. package/src/components/Header/Header.types.ts +153 -0
  25. package/src/components/Header/index.ts +44 -0
  26. package/src/components/Header/useHeaderLogic.ts +146 -0
  27. package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.helpers.ts +50 -0
  28. package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.tsx +78 -0
  29. package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.types.ts +99 -0
  30. package/src/components/ScheduleItem/ScheduleItem/index.ts +16 -0
  31. package/src/components/ScheduleItem/ScheduleItem/useScheduleItemLogic.ts +31 -0
  32. package/src/components/ScheduleItem/index.ts +15 -0
  33. package/src/components/index.ts +40 -0
  34. package/src/core/index.ts +34 -0
  35. package/src/core/restyle/RestyleThemeProviderWrapper.tsx +31 -0
  36. package/src/core/restyle/index.ts +38 -0
  37. package/src/core/restyle/restylePresetRegistry.ts +195 -0
  38. package/src/core/restyle/restyleTheme.ts +1352 -0
  39. package/src/core/restyle/restyleTypes.ts +8 -0
  40. package/src/core/restyle/useRestyleTheme.ts +10 -0
  41. package/src/hooks/animations/index.ts +3 -0
  42. package/src/hooks/animations/useAnimatedValue.ts +10 -0
  43. package/src/hooks/animations/useEntranceAnimation.ts +106 -0
  44. package/src/hooks/animations/usePulseAnimation.ts +63 -0
  45. package/src/hooks/index.ts +30 -0
  46. package/src/hooks/useReducedMotion.ts +60 -0
  47. package/src/i18n/index.ts +2 -0
  48. package/src/i18n/labels/en.ts +120 -0
  49. package/src/i18n/labels/es.ts +120 -0
  50. package/src/i18n/labels/index.ts +6 -0
  51. package/src/i18n/labels/types.ts +165 -0
  52. package/src/index.tsx +215 -0
  53. package/src/primitives/actions/Button/Button.helpers.ts +243 -0
  54. package/src/primitives/actions/Button/Button.tsx +198 -0
  55. package/src/primitives/actions/Button/Button.types.ts +207 -0
  56. package/src/primitives/actions/Button/index.ts +41 -0
  57. package/src/primitives/actions/Button/useButtonLogic.ts +160 -0
  58. package/src/primitives/actions/IconButton/IconButton.helpers.ts +235 -0
  59. package/src/primitives/actions/IconButton/IconButton.tsx +177 -0
  60. package/src/primitives/actions/IconButton/IconButton.types.ts +273 -0
  61. package/src/primitives/actions/IconButton/index.ts +30 -0
  62. package/src/primitives/actions/IconButton/useIconButtonLogic.ts +172 -0
  63. package/src/primitives/actions/index.ts +20 -0
  64. package/src/primitives/content/Avatar/Avatar.helpers.ts +177 -0
  65. package/src/primitives/content/Avatar/Avatar.tsx +199 -0
  66. package/src/primitives/content/Avatar/Avatar.types.ts +222 -0
  67. package/src/primitives/content/Avatar/index.ts +46 -0
  68. package/src/primitives/content/Avatar/useAvatarLogic.ts +149 -0
  69. package/src/primitives/content/Badge/Badge.helpers.ts +175 -0
  70. package/src/primitives/content/Badge/Badge.tsx +174 -0
  71. package/src/primitives/content/Badge/Badge.types.ts +223 -0
  72. package/src/primitives/content/Badge/index.ts +40 -0
  73. package/src/primitives/content/Badge/useBadgeLogic.ts +128 -0
  74. package/src/primitives/content/Card/Card.helpers.ts +27 -0
  75. package/src/primitives/content/Card/Card.tsx +123 -0
  76. package/src/primitives/content/Card/Card.types.ts +95 -0
  77. package/src/primitives/content/Card/index.ts +20 -0
  78. package/src/primitives/content/Card/useCardLogic.ts +48 -0
  79. package/src/primitives/content/Chip/Chip.helpers.ts +304 -0
  80. package/src/primitives/content/Chip/Chip.tsx +205 -0
  81. package/src/primitives/content/Chip/Chip.types.ts +234 -0
  82. package/src/primitives/content/Chip/index.ts +47 -0
  83. package/src/primitives/content/Chip/useChipLogic.ts +167 -0
  84. package/src/primitives/content/Icon/Icon.helpers.ts +54 -0
  85. package/src/primitives/content/Icon/Icon.tsx +110 -0
  86. package/src/primitives/content/Icon/Icon.types.ts +95 -0
  87. package/src/primitives/content/Icon/index.ts +20 -0
  88. package/src/primitives/content/Icon/useIconLogic.ts +73 -0
  89. package/src/primitives/content/index.ts +45 -0
  90. package/src/primitives/feedback/ProgressBar/ProgressBar.helpers.ts +122 -0
  91. package/src/primitives/feedback/ProgressBar/ProgressBar.tsx +154 -0
  92. package/src/primitives/feedback/ProgressBar/ProgressBar.types.ts +178 -0
  93. package/src/primitives/feedback/ProgressBar/index.ts +17 -0
  94. package/src/primitives/feedback/ProgressBar/useProgressBarLogic.ts +120 -0
  95. package/src/primitives/feedback/Skeleton/Skeleton.helpers.ts +145 -0
  96. package/src/primitives/feedback/Skeleton/Skeleton.tsx +155 -0
  97. package/src/primitives/feedback/Skeleton/Skeleton.types.ts +223 -0
  98. package/src/primitives/feedback/Skeleton/index.ts +44 -0
  99. package/src/primitives/feedback/Skeleton/useSkeletonLogic.ts +125 -0
  100. package/src/primitives/feedback/Spinner/Spinner.helpers.ts +40 -0
  101. package/src/primitives/feedback/Spinner/Spinner.tsx +105 -0
  102. package/src/primitives/feedback/Spinner/Spinner.types.ts +114 -0
  103. package/src/primitives/feedback/Spinner/index.ts +18 -0
  104. package/src/primitives/feedback/Spinner/useSpinnerLogic.ts +84 -0
  105. package/src/primitives/feedback/Toast/Toast.helpers.ts +163 -0
  106. package/src/primitives/feedback/Toast/Toast.tsx +190 -0
  107. package/src/primitives/feedback/Toast/Toast.types.ts +270 -0
  108. package/src/primitives/feedback/Toast/ToastContext.tsx +96 -0
  109. package/src/primitives/feedback/Toast/ToastProvider.tsx +241 -0
  110. package/src/primitives/feedback/Toast/index.ts +59 -0
  111. package/src/primitives/feedback/Toast/useToastLogic.ts +112 -0
  112. package/src/primitives/feedback/index.ts +45 -0
  113. package/src/primitives/index.ts +158 -0
  114. package/src/primitives/inputs/Checkbox/Checkbox.helpers.ts +132 -0
  115. package/src/primitives/inputs/Checkbox/Checkbox.tsx +150 -0
  116. package/src/primitives/inputs/Checkbox/Checkbox.types.ts +106 -0
  117. package/src/primitives/inputs/Checkbox/index.ts +30 -0
  118. package/src/primitives/inputs/Checkbox/useCheckboxLogic.ts +121 -0
  119. package/src/primitives/inputs/RadioButton/RadioButton.helpers.ts +123 -0
  120. package/src/primitives/inputs/RadioButton/RadioButton.tsx +159 -0
  121. package/src/primitives/inputs/RadioButton/RadioButton.types.ts +106 -0
  122. package/src/primitives/inputs/RadioButton/index.ts +25 -0
  123. package/src/primitives/inputs/RadioButton/useRadioButtonLogic.ts +117 -0
  124. package/src/primitives/inputs/SegmentedControl/SegmentedControl.helpers.ts +174 -0
  125. package/src/primitives/inputs/SegmentedControl/SegmentedControl.tsx +224 -0
  126. package/src/primitives/inputs/SegmentedControl/SegmentedControl.types.ts +187 -0
  127. package/src/primitives/inputs/SegmentedControl/index.ts +39 -0
  128. package/src/primitives/inputs/SegmentedControl/useSegmentedControlLogic.ts +151 -0
  129. package/src/primitives/inputs/SelectSheet/SelectSheet.helpers.ts +147 -0
  130. package/src/primitives/inputs/SelectSheet/SelectSheet.tsx +247 -0
  131. package/src/primitives/inputs/SelectSheet/SelectSheet.types.ts +196 -0
  132. package/src/primitives/inputs/SelectSheet/SelectSheetOption.tsx +177 -0
  133. package/src/primitives/inputs/SelectSheet/index.ts +48 -0
  134. package/src/primitives/inputs/SelectSheet/useSelectSheetLogic.ts +309 -0
  135. package/src/primitives/inputs/Switch/Switch.helpers.ts +109 -0
  136. package/src/primitives/inputs/Switch/Switch.tsx +191 -0
  137. package/src/primitives/inputs/Switch/Switch.types.ts +154 -0
  138. package/src/primitives/inputs/Switch/index.ts +40 -0
  139. package/src/primitives/inputs/Switch/useSwitchLogic.ts +192 -0
  140. package/src/primitives/inputs/TextInput/TextInput.helpers.ts +206 -0
  141. package/src/primitives/inputs/TextInput/TextInput.tsx +392 -0
  142. package/src/primitives/inputs/TextInput/TextInput.types.ts +216 -0
  143. package/src/primitives/inputs/TextInput/index.ts +37 -0
  144. package/src/primitives/inputs/TextInput/useTextInputLogic.ts +195 -0
  145. package/src/primitives/inputs/index.ts +52 -0
  146. package/src/primitives/layout/AnimatedBox.tsx +44 -0
  147. package/src/primitives/layout/Box.tsx +71 -0
  148. package/src/primitives/layout/Divider/Divider.helpers.ts +115 -0
  149. package/src/primitives/layout/Divider/Divider.tsx +139 -0
  150. package/src/primitives/layout/Divider/Divider.types.ts +178 -0
  151. package/src/primitives/layout/Divider/index.ts +24 -0
  152. package/src/primitives/layout/Divider/useDividerLogic.ts +109 -0
  153. package/src/primitives/layout/FlatList.tsx +66 -0
  154. package/src/primitives/layout/Pressable.tsx +74 -0
  155. package/src/primitives/layout/ScrollView.tsx +63 -0
  156. package/src/primitives/layout/Stack.tsx +69 -0
  157. package/src/primitives/layout/index.ts +40 -0
  158. package/src/primitives/navigation/index.ts +6 -0
  159. package/src/primitives/overlays/Modal/Modal.helpers.ts +31 -0
  160. package/src/primitives/overlays/Modal/Modal.tsx +264 -0
  161. package/src/primitives/overlays/Modal/Modal.types.ts +193 -0
  162. package/src/primitives/overlays/Modal/index.ts +43 -0
  163. package/src/primitives/overlays/Modal/useModalLogic.ts +103 -0
  164. package/src/primitives/overlays/index.ts +12 -0
  165. package/src/primitives/typography/Text.tsx +51 -0
  166. package/src/primitives/typography/index.ts +1 -0
  167. package/src/provider/DesignSystemContext.ts +22 -0
  168. package/src/provider/DesignSystemProvider.tsx +121 -0
  169. package/src/provider/index.ts +7 -0
  170. package/src/providers/ThemeProvider/createTheme.ts +304 -0
  171. package/src/providers/ThemeProvider/defaultTheme.ts +70 -0
  172. package/src/providers/ThemeProvider/index.ts +34 -0
  173. package/src/providers/ThemeProvider/types.ts +249 -0
  174. package/src/providers/index.ts +29 -0
  175. package/src/tokens/colors.ts +371 -0
  176. package/src/tokens/index.ts +145 -0
  177. package/src/tokens/motion.ts +176 -0
  178. package/src/tokens/radii.ts +82 -0
  179. package/src/tokens/scales.ts +588 -0
  180. package/src/tokens/shadows.ts +190 -0
  181. package/src/tokens/spacing.ts +140 -0
  182. package/src/tokens/tokens.json +207 -0
  183. package/src/tokens/typography.ts +251 -0
  184. package/src/types.ts +50 -0
  185. package/src/utils/accessibility.ts +169 -0
  186. package/src/utils/index.ts +25 -0
  187. 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";