@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,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
+ }