@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,172 @@
1
+ /**
2
+ * IconButton Logic Hook
3
+ *
4
+ * Extracts all computational logic from the IconButton component.
5
+ * Returns memoized values for optimal render performance.
6
+ *
7
+ * Responsibilities:
8
+ * - Resolve responsive size prop to current breakpoint value
9
+ * - Compute icon size from container size (~45% ratio)
10
+ * - Determine icon/spinner colors based on variant and state
11
+ * - Calculate container styles (background, border, opacity)
12
+ * - Compute hitSlop for proper touch targets
13
+ * - Generate accessibility props
14
+ *
15
+ * The component (IconButton.tsx) is a "dumb view" that only renders
16
+ * the pre-computed values from this hook.
17
+ *
18
+ * @see IconButton.tsx - Component consuming this hook
19
+ * @see IconButton.helpers.ts - Pure calculation functions
20
+ * @see tokens/scales.ts - Mathematical ratios and constants
21
+ */
22
+
23
+ import { useDesignSystem } from "../../../provider";
24
+ import { useResponsiveProp } from "@shopify/restyle";
25
+ import { useMemo } from "react";
26
+ import { ViewStyle } from "react-native";
27
+ import { useRestyleTheme } from "../../../core/restyle";
28
+ import { getIconButtonA11y } from "./IconButton.a11y";
29
+ import {
30
+ getIconButtonHitSlop,
31
+ getIconButtonIconColor,
32
+ getIconButtonIconSize,
33
+ getIconButtonSpinnerColor,
34
+ getIconButtonSpinnerSize,
35
+ getIconButtonThemeVariantKey,
36
+ resolveIconButtonStyle,
37
+ } from "./IconButton.helpers";
38
+ import {
39
+ IconButtonSize,
40
+ IconButtonVariant,
41
+ UseIconButtonLogicParams,
42
+ UseIconButtonLogicReturn,
43
+ } from "./IconButton.types";
44
+
45
+ /**
46
+ * Orchestrates IconButton rendering logic and state management.
47
+ *
48
+ * Handles:
49
+ * - Responsive size and variant resolution
50
+ * - Icon size calculation (~45% of container ratio)
51
+ * - Icon and spinner color determination
52
+ * - Container styles (background, border, opacity)
53
+ * - Hit slop calculation for 44px minimum touch target
54
+ * - Ghost variant dynamic sizing (wraps icon tightly)
55
+ * - Accessibility props generation
56
+ *
57
+ * @param params - Configuration object for icon button behavior
58
+ * @param params.variant - Visual variant ('contained' | 'outlined' | 'ghost')
59
+ * @param params.size - Size variant ('sm' | 'md' | 'lg')
60
+ * @param params.color - Theme color token for background/icon
61
+ * @param params.disabled - Whether button is disabled
62
+ * @param params.isLoading - Whether to show loading spinner
63
+ * @param params.accessibilityLabel - Custom a11y label (required for icon-only)
64
+ * @param params.accessibilityHint - Describes action result
65
+ *
66
+ * @returns Computed values for rendering IconButton
67
+ * @returns {number} iconSize - Icon size in pixels (16-24px)
68
+ * @returns {string} iconColor - Icon color token
69
+ * @returns {ViewStyle} containerStyle - Dynamic container styles
70
+ * @returns {Insets} hitSlop - Touch target expansion
71
+ * @returns {object} a11yProps - Accessibility props
72
+ * @returns {boolean} showSpinner - Whether to display spinner
73
+ * @returns {string} spinnerSize - Spinner size variant
74
+ * @returns {string} spinnerColor - Spinner color token
75
+ * @returns {string} themeVariantKey - Restyle theme variant identifier
76
+ *
77
+ * @performance This hook uses useMemo() for expensive calculations
78
+ *
79
+ * @example
80
+ * const { iconSize, iconColor, containerStyle, hitSlop, a11yProps } = useIconButtonLogic({
81
+ * variant: "contained",
82
+ * size: "md",
83
+ * color: "accentPrimary",
84
+ * disabled: false,
85
+ * isLoading: false,
86
+ * accessibilityLabel: "Add to favorites",
87
+ * });
88
+ */
89
+ export function useIconButtonLogic(
90
+ params: UseIconButtonLogicParams,
91
+ ): UseIconButtonLogicReturn {
92
+ const {
93
+ variant,
94
+ size,
95
+ color,
96
+ disabled,
97
+ isLoading,
98
+ accessibilityLabel,
99
+ accessibilityHint,
100
+ } = params;
101
+
102
+ const { labels: t } = useDesignSystem();
103
+ const theme = useRestyleTheme();
104
+ const resolvedSize = (useResponsiveProp(size) ?? "md") as IconButtonSize;
105
+ const resolvedVariant = (useResponsiveProp(variant) ?? "contained") as IconButtonVariant;
106
+
107
+ const themeVariantKey = getIconButtonThemeVariantKey(resolvedVariant);
108
+ const iconSize = getIconButtonIconSize(resolvedSize);
109
+ const iconColor = getIconButtonIconColor(resolvedVariant, color, disabled);
110
+ const hitSlop = getIconButtonHitSlop(resolvedSize, resolvedVariant);
111
+ const spinnerSize = getIconButtonSpinnerSize(resolvedSize);
112
+ const spinnerColor = getIconButtonSpinnerColor(resolvedVariant, color, disabled);
113
+
114
+ // Only calculate dynamic colors and opacity - structure comes from theme variant
115
+ const containerStyle = useMemo((): ViewStyle => {
116
+ const styleResult = resolveIconButtonStyle({
117
+ variant: resolvedVariant,
118
+ disabled,
119
+ isLoading,
120
+ color,
121
+ theme,
122
+ });
123
+
124
+ const baseStyle: ViewStyle = {
125
+ backgroundColor: styleResult.backgroundColor,
126
+ borderColor: styleResult.borderColor,
127
+ opacity: styleResult.opacity,
128
+ // borderRadius and borderWidth are handled by the theme variant
129
+ };
130
+
131
+ // For ghost variant, remove fixed container size - just wrap the icon
132
+ // Override any size from theme variant
133
+ if (resolvedVariant === "ghost") {
134
+ baseStyle.width = undefined;
135
+ baseStyle.height = undefined;
136
+ baseStyle.minWidth = undefined;
137
+ baseStyle.minHeight = undefined;
138
+ baseStyle.maxWidth = undefined;
139
+ baseStyle.maxHeight = undefined;
140
+ }
141
+
142
+ return baseStyle;
143
+ }, [resolvedVariant, disabled, isLoading, color, theme]);
144
+
145
+ const a11yProps = useMemo(
146
+ () =>
147
+ getIconButtonA11y({
148
+ accessibilityLabel,
149
+ accessibilityHint,
150
+ disabled,
151
+ isLoading,
152
+ }),
153
+ [accessibilityLabel, accessibilityHint, disabled, isLoading],
154
+ );
155
+
156
+ const showSpinner = isLoading;
157
+
158
+ const spinnerAccessibilityLabel = t.designSystem.iconButton.loadingLabel;
159
+
160
+ return {
161
+ iconSize,
162
+ iconColor,
163
+ containerStyle,
164
+ hitSlop,
165
+ a11yProps,
166
+ showSpinner,
167
+ spinnerSize,
168
+ spinnerColor,
169
+ themeVariantKey,
170
+ spinnerAccessibilityLabel,
171
+ };
172
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Actions Components Module
3
+ *
4
+ * Components that trigger user-initiated actions.
5
+ */
6
+
7
+ export {
8
+ Button,
9
+ type ButtonProps,
10
+ type ButtonRef,
11
+ type ButtonSize,
12
+ type ButtonVariant,
13
+ } from "./Button";
14
+
15
+ export {
16
+ IconButton,
17
+ type IconButtonProps,
18
+ type IconButtonSize,
19
+ type IconButtonVariant,
20
+ } from "./IconButton";
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Avatar Component Helpers
3
+ *
4
+ * Pure functions for calculating avatar dimensions, initials, and status colors.
5
+ * All values follow 8px grid system for consistency.
6
+ *
7
+ * ## Size Scale
8
+ * | Size | Dimensions | Font Size | Use Case |
9
+ * |------|------------|-----------|----------|
10
+ * | sm | 32×32 | 12px | Compact lists, comments |
11
+ * | md | 40×40 | 14px | Standard (default) |
12
+ * | lg | 56×56 | 20px | Profile headers |
13
+ *
14
+ * ## Status Indicator Scale
15
+ * | Size | Indicator | Border |
16
+ * |------|-----------|--------|
17
+ * | sm | 8px | 2px |
18
+ * | md | 10px | 2px |
19
+ * | lg | 14px | 2px |
20
+ */
21
+
22
+ import { RestyleColor } from "../../../types";
23
+ import {
24
+ AvatarDimensions,
25
+ AvatarSize,
26
+ AvatarSizeConfig,
27
+ AvatarStatus,
28
+ StatusColorMap,
29
+ StatusDimensions,
30
+ StatusSizeConfig,
31
+ } from "./Avatar.types";
32
+
33
+ // =============================================================================
34
+ // TYPOGRAPHY MAPPING
35
+ // =============================================================================
36
+
37
+ /**
38
+ * Map avatar size to typography variant for initials text.
39
+ *
40
+ * | Size | Typography |
41
+ * |------|------------- |
42
+ * | sm | labelSmall |
43
+ * | md | labelMedium |
44
+ * | lg | labelLarge |
45
+ */
46
+ export const AVATAR_INITIALS_TYPOGRAPHY: Record<
47
+ AvatarSize,
48
+ "labelSmall" | "labelMedium" | "labelLarge"
49
+ > = {
50
+ sm: "labelSmall",
51
+ md: "labelMedium",
52
+ lg: "labelLarge",
53
+ };
54
+
55
+ /**
56
+ * Size configuration for avatar dimensions.
57
+ * Following 8px grid system.
58
+ */
59
+ export const AVATAR_SIZE_CONFIG: AvatarSizeConfig = {
60
+ sm: { size: 32, fontSize: 12, borderRadius: 16 },
61
+ md: { size: 40, fontSize: 14, borderRadius: 20 },
62
+ lg: { size: 56, fontSize: 20, borderRadius: 28 },
63
+ } as const;
64
+
65
+ /**
66
+ * Status indicator size configuration by avatar size.
67
+ */
68
+ export const STATUS_SIZE_CONFIG: StatusSizeConfig = {
69
+ sm: { size: 8, borderWidth: 2, offset: 0 },
70
+ md: { size: 10, borderWidth: 2, offset: 0 },
71
+ lg: { size: 14, borderWidth: 2, offset: 0 },
72
+ } as const;
73
+
74
+ /**
75
+ * Status indicator color mapping.
76
+ */
77
+ export const STATUS_COLORS: StatusColorMap = {
78
+ online: "feedbackSuccess",
79
+ offline: "textTertiary",
80
+ busy: "feedbackError",
81
+ away: "feedbackWarning",
82
+ } as const;
83
+
84
+ /**
85
+ * Get dimensions for an avatar based on size.
86
+ *
87
+ * @param size - Avatar size preset
88
+ * @returns Avatar dimensions (size, fontSize, borderRadius)
89
+ *
90
+ * @example
91
+ * getAvatarDimensions("md")
92
+ * // { size: 40, fontSize: 14, borderRadius: 20 }
93
+ */
94
+ export function getAvatarDimensions(size: AvatarSize): AvatarDimensions {
95
+ return AVATAR_SIZE_CONFIG[size];
96
+ }
97
+
98
+ /**
99
+ * Get status indicator dimensions based on avatar size.
100
+ *
101
+ * @param size - Avatar size preset
102
+ * @returns Status indicator dimensions
103
+ *
104
+ * @example
105
+ * getStatusDimensions("lg")
106
+ * // { size: 14, borderWidth: 2, offset: 0 }
107
+ */
108
+ export function getStatusDimensions(size: AvatarSize): StatusDimensions {
109
+ return STATUS_SIZE_CONFIG[size];
110
+ }
111
+
112
+ /**
113
+ * Get status indicator color.
114
+ *
115
+ * @param status - Avatar status
116
+ * @returns Color token or null if no status
117
+ *
118
+ * @example
119
+ * getStatusColor("online")
120
+ * // "feedbackSuccess"
121
+ */
122
+ export function getStatusColor(status: AvatarStatus): RestyleColor | null {
123
+ if (status === "none") return null;
124
+ return STATUS_COLORS[status];
125
+ }
126
+
127
+ /**
128
+ * Generate initials from a name.
129
+ *
130
+ * Algorithm handles different naming conventions:
131
+ * - 1 word: first letter (e.g., "Luis" → "L")
132
+ * - 2-3 words: first + last (e.g., "John Doe" → "JD")
133
+ * - 4+ words: first + second-to-last (Hispanic pattern)
134
+ * e.g., "Luis Enrique Medina Galvan" → "LM"
135
+ * (First name + paternal surname)
136
+ *
137
+ * @param name - Full name string
138
+ * @returns Initials (1-2 characters, uppercase)
139
+ *
140
+ * @example
141
+ * getInitials("John Doe")
142
+ * // "JD"
143
+ *
144
+ * @example
145
+ * getInitials("Alice")
146
+ * // "A"
147
+ *
148
+ * @example
149
+ * getInitials("María José García López")
150
+ * // "MG" (first name + paternal surname)
151
+ *
152
+ * @example
153
+ * getInitials("Luis Enrique Medina Galvan")
154
+ * // "LM" (first name + paternal surname)
155
+ */
156
+ export function getInitials(name?: string): string {
157
+ if (!name || name.trim().length === 0) return "?";
158
+
159
+ const parts = name.trim().split(/\s+/).filter(Boolean);
160
+
161
+ if (parts.length === 0) return "?";
162
+ if (parts.length === 1) return parts[0].charAt(0).toUpperCase();
163
+
164
+ const first = parts[0].charAt(0);
165
+
166
+ // For 4+ word names (common in Hispanic cultures),
167
+ // use first name + paternal surname (second-to-last)
168
+ // e.g., "Luis Enrique Medina Galvan" → L + M
169
+ if (parts.length >= 4) {
170
+ const paternal = parts[parts.length - 2].charAt(0);
171
+ return (first + paternal).toUpperCase();
172
+ }
173
+
174
+ // For 2-3 word names, use first + last
175
+ const last = parts[parts.length - 1].charAt(0);
176
+ return (first + last).toUpperCase();
177
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Avatar Component
3
+ *
4
+ * @description Profile images with fallback initials and status indicators - Atom
5
+ *
6
+ * Avatar displays user profile images with automatic fallback to initials
7
+ * when no image is available or fails to load. Supports status indicators
8
+ * for presence information.
9
+ *
10
+ * ## Size Scale (from theme avatarSizes)
11
+ * | Size | Dimensions | Use Case |
12
+ * |------|------------|----------|
13
+ * | sm | 32×32 | Compact lists, comments |
14
+ * | md | 40×40 | Standard (default) |
15
+ * | lg | 56×56 | Profile headers |
16
+ *
17
+ * ## Status Indicators
18
+ * - `online`: Green - user is active
19
+ * - `offline`: Gray - user is inactive
20
+ * - `busy`: Red - do not disturb
21
+ * - `away`: Yellow - user is away
22
+ * - `none`: No indicator (default)
23
+ *
24
+ * ## Features
25
+ * - Three size presets with responsive support (from restyle theme)
26
+ * - Automatic initials generation from name
27
+ * - Graceful image load error handling
28
+ * - Status indicator with proper positioning
29
+ * - Theme-aware colors
30
+ * - Accessible with proper labels
31
+ * - Memoized for performance
32
+ *
33
+ * ## Best Practices
34
+ * - Always provide `name` for fallback and accessibility
35
+ * - Use consistent sizes within a context
36
+ * - Consider loading states for async images
37
+ *
38
+ * @see Avatar.types.ts - Type definitions
39
+ * @see Avatar.helpers.ts - Dimension and initials calculations
40
+ * @see Avatar.a11y.ts - Accessibility props
41
+ *
42
+ * @example
43
+ * // Basic with image
44
+ * <Avatar source={{ uri: "https://example.com/avatar.jpg" }} name="John Doe" />
45
+ *
46
+ * @example
47
+ * // Initials fallback
48
+ * <Avatar name="John Doe" />
49
+ *
50
+ * @example
51
+ * // With status
52
+ * <Avatar source={avatarUrl} name="Jane" status="online" />
53
+ *
54
+ * @example
55
+ * // Large profile avatar
56
+ * <Avatar name="John Doe" size="lg" />
57
+ *
58
+ * @example
59
+ * // Responsive size
60
+ * <Avatar name="User" size={{ phone: "sm", tablet: "lg" }} />
61
+ *
62
+ * @example
63
+ * // Custom colors
64
+ * <Avatar
65
+ * name="Brand User"
66
+ * backgroundColor="accentSecondary"
67
+ * initialsColor="white"
68
+ * />
69
+ */
70
+
71
+ import {
72
+ createRestyleComponent,
73
+ createVariant,
74
+ layout,
75
+ spacing,
76
+ spacingShorthand,
77
+ VariantProps,
78
+ } from "@shopify/restyle";
79
+ import React, { memo } from "react";
80
+ import { Image, StyleSheet } from "react-native";
81
+ import { RestyleTheme } from "../../../core/restyle";
82
+ import { BaseThemeColor } from "../../../types";
83
+ import { Box } from "../../layout";
84
+ import { Text } from "../../typography";
85
+ import { AVATAR_INITIALS_TYPOGRAPHY } from "./Avatar.helpers";
86
+ import { AvatarProps, BaseAvatarContainerProps } from "./Avatar.types";
87
+ import { useAvatarLogic } from "./useAvatarLogic";
88
+
89
+ /**
90
+ * Size variant for Avatar.
91
+ * @internal
92
+ */
93
+ const sizeVariant = createVariant<RestyleTheme, "avatarSizes", "size">({
94
+ themeKey: "avatarSizes",
95
+ property: "size",
96
+ });
97
+
98
+ /**
99
+ * Base Avatar container with Restyle support.
100
+ * @internal
101
+ */
102
+ const BaseAvatarContainer = createRestyleComponent<
103
+ BaseAvatarContainerProps & VariantProps<RestyleTheme, "avatarSizes", "size">,
104
+ RestyleTheme
105
+ >([layout, spacing, spacingShorthand, sizeVariant], Box);
106
+
107
+ function AvatarComponent({
108
+ source,
109
+ name,
110
+ size = "md",
111
+ status = "none",
112
+ backgroundColor = "accentPrimary",
113
+ initialsColor = "textInverse",
114
+ accessibilityLabel,
115
+ testID,
116
+ ...rest
117
+ }: AvatarProps) {
118
+ const {
119
+ resolvedSize,
120
+ dimensions,
121
+ showImage,
122
+ initials,
123
+ statusColor,
124
+ statusDimensions,
125
+ a11yProps,
126
+ onImageError,
127
+ } = useAvatarLogic({
128
+ source,
129
+ name,
130
+ size,
131
+ status,
132
+ backgroundColor,
133
+ initialsColor,
134
+ accessibilityLabel,
135
+ });
136
+
137
+ return (
138
+ <BaseAvatarContainer
139
+ testID={testID}
140
+ size={resolvedSize}
141
+ {...a11yProps}
142
+ {...rest}
143
+ >
144
+ {/* Inner container for image/initials with overflow hidden */}
145
+ <Box
146
+ width={dimensions.size}
147
+ height={dimensions.size}
148
+ borderRadius="full"
149
+ backgroundColor={showImage ? undefined : (backgroundColor as BaseThemeColor)}
150
+ overflow="hidden"
151
+ alignItems="center"
152
+ justifyContent="center"
153
+ >
154
+ {showImage ? (
155
+ <Image
156
+ source={source!}
157
+ style={[
158
+ styles.image,
159
+ { width: dimensions.size, height: dimensions.size },
160
+ ]}
161
+ onError={onImageError}
162
+ />
163
+ ) : (
164
+ <Text
165
+ variant={AVATAR_INITIALS_TYPOGRAPHY[resolvedSize]}
166
+ color={initialsColor as BaseThemeColor}
167
+ fontWeight="600"
168
+ >
169
+ {initials}
170
+ </Text>
171
+ )}
172
+ </Box>
173
+
174
+ {/* Status indicator - outside overflow hidden container */}
175
+ {statusColor && statusDimensions && (
176
+ <Box
177
+ position="absolute"
178
+ bottom={statusDimensions.offset}
179
+ right={statusDimensions.offset}
180
+ width={statusDimensions.size}
181
+ height={statusDimensions.size}
182
+ borderRadius="full"
183
+ backgroundColor={statusColor as BaseThemeColor}
184
+ borderWidth={statusDimensions.borderWidth}
185
+ borderColor="backgroundPrimary"
186
+ />
187
+ )}
188
+ </BaseAvatarContainer>
189
+ );
190
+ }
191
+
192
+ const styles = StyleSheet.create({
193
+ image: {
194
+ resizeMode: "cover",
195
+ },
196
+ });
197
+
198
+ export const Avatar = memo(AvatarComponent);
199
+ Avatar.displayName = "Avatar";