@praxiis/ui 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +52556 -0
- package/dist/index.d.ts +52556 -0
- package/dist/index.js +8753 -0
- package/dist/index.mjs +8777 -0
- package/package.json +70 -0
- package/src/__test-utils__/index.tsx +39 -0
- package/src/components/CalendarStrip/CalendarStrip.helpers.ts +106 -0
- package/src/components/CalendarStrip/CalendarStrip.tsx +83 -0
- package/src/components/CalendarStrip/CalendarStrip.types.ts +133 -0
- package/src/components/CalendarStrip/DayCard/DayCard.helpers.ts +44 -0
- package/src/components/CalendarStrip/DayCard/DayCard.tsx +71 -0
- package/src/components/CalendarStrip/DayCard/DayCard.types.ts +134 -0
- package/src/components/CalendarStrip/DayCard/index.ts +2 -0
- package/src/components/CalendarStrip/DayCard/useDayCardLogic.ts +45 -0
- package/src/components/CalendarStrip/index.ts +9 -0
- package/src/components/CalendarStrip/useCalendarStripLogic.ts +53 -0
- package/src/components/EmptyState/EmptyState.helpers.ts +104 -0
- package/src/components/EmptyState/EmptyState.tsx +205 -0
- package/src/components/EmptyState/EmptyState.types.ts +213 -0
- package/src/components/EmptyState/index.ts +44 -0
- package/src/components/EmptyState/useEmptyStateLogic.ts +131 -0
- package/src/components/Header/Header.helpers.ts +93 -0
- package/src/components/Header/Header.tsx +185 -0
- package/src/components/Header/Header.types.ts +153 -0
- package/src/components/Header/index.ts +44 -0
- package/src/components/Header/useHeaderLogic.ts +146 -0
- package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.helpers.ts +50 -0
- package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.tsx +78 -0
- package/src/components/ScheduleItem/ScheduleItem/ScheduleItem.types.ts +99 -0
- package/src/components/ScheduleItem/ScheduleItem/index.ts +16 -0
- package/src/components/ScheduleItem/ScheduleItem/useScheduleItemLogic.ts +31 -0
- package/src/components/ScheduleItem/index.ts +15 -0
- package/src/components/index.ts +40 -0
- package/src/core/index.ts +34 -0
- package/src/core/restyle/RestyleThemeProviderWrapper.tsx +31 -0
- package/src/core/restyle/index.ts +38 -0
- package/src/core/restyle/restylePresetRegistry.ts +195 -0
- package/src/core/restyle/restyleTheme.ts +1352 -0
- package/src/core/restyle/restyleTypes.ts +8 -0
- package/src/core/restyle/useRestyleTheme.ts +10 -0
- package/src/hooks/animations/index.ts +3 -0
- package/src/hooks/animations/useAnimatedValue.ts +10 -0
- package/src/hooks/animations/useEntranceAnimation.ts +106 -0
- package/src/hooks/animations/usePulseAnimation.ts +63 -0
- package/src/hooks/index.ts +30 -0
- package/src/hooks/useReducedMotion.ts +60 -0
- package/src/i18n/index.ts +2 -0
- package/src/i18n/labels/en.ts +120 -0
- package/src/i18n/labels/es.ts +120 -0
- package/src/i18n/labels/index.ts +6 -0
- package/src/i18n/labels/types.ts +165 -0
- package/src/index.tsx +215 -0
- package/src/primitives/actions/Button/Button.helpers.ts +243 -0
- package/src/primitives/actions/Button/Button.tsx +198 -0
- package/src/primitives/actions/Button/Button.types.ts +207 -0
- package/src/primitives/actions/Button/index.ts +41 -0
- package/src/primitives/actions/Button/useButtonLogic.ts +160 -0
- package/src/primitives/actions/IconButton/IconButton.helpers.ts +235 -0
- package/src/primitives/actions/IconButton/IconButton.tsx +177 -0
- package/src/primitives/actions/IconButton/IconButton.types.ts +273 -0
- package/src/primitives/actions/IconButton/index.ts +30 -0
- package/src/primitives/actions/IconButton/useIconButtonLogic.ts +172 -0
- package/src/primitives/actions/index.ts +20 -0
- package/src/primitives/content/Avatar/Avatar.helpers.ts +177 -0
- package/src/primitives/content/Avatar/Avatar.tsx +199 -0
- package/src/primitives/content/Avatar/Avatar.types.ts +222 -0
- package/src/primitives/content/Avatar/index.ts +46 -0
- package/src/primitives/content/Avatar/useAvatarLogic.ts +149 -0
- package/src/primitives/content/Badge/Badge.helpers.ts +175 -0
- package/src/primitives/content/Badge/Badge.tsx +174 -0
- package/src/primitives/content/Badge/Badge.types.ts +223 -0
- package/src/primitives/content/Badge/index.ts +40 -0
- package/src/primitives/content/Badge/useBadgeLogic.ts +128 -0
- package/src/primitives/content/Card/Card.helpers.ts +27 -0
- package/src/primitives/content/Card/Card.tsx +123 -0
- package/src/primitives/content/Card/Card.types.ts +95 -0
- package/src/primitives/content/Card/index.ts +20 -0
- package/src/primitives/content/Card/useCardLogic.ts +48 -0
- package/src/primitives/content/Chip/Chip.helpers.ts +304 -0
- package/src/primitives/content/Chip/Chip.tsx +205 -0
- package/src/primitives/content/Chip/Chip.types.ts +234 -0
- package/src/primitives/content/Chip/index.ts +47 -0
- package/src/primitives/content/Chip/useChipLogic.ts +167 -0
- package/src/primitives/content/Icon/Icon.helpers.ts +54 -0
- package/src/primitives/content/Icon/Icon.tsx +110 -0
- package/src/primitives/content/Icon/Icon.types.ts +95 -0
- package/src/primitives/content/Icon/index.ts +20 -0
- package/src/primitives/content/Icon/useIconLogic.ts +73 -0
- package/src/primitives/content/index.ts +45 -0
- package/src/primitives/feedback/ProgressBar/ProgressBar.helpers.ts +122 -0
- package/src/primitives/feedback/ProgressBar/ProgressBar.tsx +154 -0
- package/src/primitives/feedback/ProgressBar/ProgressBar.types.ts +178 -0
- package/src/primitives/feedback/ProgressBar/index.ts +17 -0
- package/src/primitives/feedback/ProgressBar/useProgressBarLogic.ts +120 -0
- package/src/primitives/feedback/Skeleton/Skeleton.helpers.ts +145 -0
- package/src/primitives/feedback/Skeleton/Skeleton.tsx +155 -0
- package/src/primitives/feedback/Skeleton/Skeleton.types.ts +223 -0
- package/src/primitives/feedback/Skeleton/index.ts +44 -0
- package/src/primitives/feedback/Skeleton/useSkeletonLogic.ts +125 -0
- package/src/primitives/feedback/Spinner/Spinner.helpers.ts +40 -0
- package/src/primitives/feedback/Spinner/Spinner.tsx +105 -0
- package/src/primitives/feedback/Spinner/Spinner.types.ts +114 -0
- package/src/primitives/feedback/Spinner/index.ts +18 -0
- package/src/primitives/feedback/Spinner/useSpinnerLogic.ts +84 -0
- package/src/primitives/feedback/Toast/Toast.helpers.ts +163 -0
- package/src/primitives/feedback/Toast/Toast.tsx +190 -0
- package/src/primitives/feedback/Toast/Toast.types.ts +270 -0
- package/src/primitives/feedback/Toast/ToastContext.tsx +96 -0
- package/src/primitives/feedback/Toast/ToastProvider.tsx +241 -0
- package/src/primitives/feedback/Toast/index.ts +59 -0
- package/src/primitives/feedback/Toast/useToastLogic.ts +112 -0
- package/src/primitives/feedback/index.ts +45 -0
- package/src/primitives/index.ts +158 -0
- package/src/primitives/inputs/Checkbox/Checkbox.helpers.ts +132 -0
- package/src/primitives/inputs/Checkbox/Checkbox.tsx +150 -0
- package/src/primitives/inputs/Checkbox/Checkbox.types.ts +106 -0
- package/src/primitives/inputs/Checkbox/index.ts +30 -0
- package/src/primitives/inputs/Checkbox/useCheckboxLogic.ts +121 -0
- package/src/primitives/inputs/RadioButton/RadioButton.helpers.ts +123 -0
- package/src/primitives/inputs/RadioButton/RadioButton.tsx +159 -0
- package/src/primitives/inputs/RadioButton/RadioButton.types.ts +106 -0
- package/src/primitives/inputs/RadioButton/index.ts +25 -0
- package/src/primitives/inputs/RadioButton/useRadioButtonLogic.ts +117 -0
- package/src/primitives/inputs/SegmentedControl/SegmentedControl.helpers.ts +174 -0
- package/src/primitives/inputs/SegmentedControl/SegmentedControl.tsx +224 -0
- package/src/primitives/inputs/SegmentedControl/SegmentedControl.types.ts +187 -0
- package/src/primitives/inputs/SegmentedControl/index.ts +39 -0
- package/src/primitives/inputs/SegmentedControl/useSegmentedControlLogic.ts +151 -0
- package/src/primitives/inputs/SelectSheet/SelectSheet.helpers.ts +147 -0
- package/src/primitives/inputs/SelectSheet/SelectSheet.tsx +247 -0
- package/src/primitives/inputs/SelectSheet/SelectSheet.types.ts +196 -0
- package/src/primitives/inputs/SelectSheet/SelectSheetOption.tsx +177 -0
- package/src/primitives/inputs/SelectSheet/index.ts +48 -0
- package/src/primitives/inputs/SelectSheet/useSelectSheetLogic.ts +309 -0
- package/src/primitives/inputs/Switch/Switch.helpers.ts +109 -0
- package/src/primitives/inputs/Switch/Switch.tsx +191 -0
- package/src/primitives/inputs/Switch/Switch.types.ts +154 -0
- package/src/primitives/inputs/Switch/index.ts +40 -0
- package/src/primitives/inputs/Switch/useSwitchLogic.ts +192 -0
- package/src/primitives/inputs/TextInput/TextInput.helpers.ts +206 -0
- package/src/primitives/inputs/TextInput/TextInput.tsx +392 -0
- package/src/primitives/inputs/TextInput/TextInput.types.ts +216 -0
- package/src/primitives/inputs/TextInput/index.ts +37 -0
- package/src/primitives/inputs/TextInput/useTextInputLogic.ts +195 -0
- package/src/primitives/inputs/index.ts +52 -0
- package/src/primitives/layout/AnimatedBox.tsx +44 -0
- package/src/primitives/layout/Box.tsx +71 -0
- package/src/primitives/layout/Divider/Divider.helpers.ts +115 -0
- package/src/primitives/layout/Divider/Divider.tsx +139 -0
- package/src/primitives/layout/Divider/Divider.types.ts +178 -0
- package/src/primitives/layout/Divider/index.ts +24 -0
- package/src/primitives/layout/Divider/useDividerLogic.ts +109 -0
- package/src/primitives/layout/FlatList.tsx +66 -0
- package/src/primitives/layout/Pressable.tsx +74 -0
- package/src/primitives/layout/ScrollView.tsx +63 -0
- package/src/primitives/layout/Stack.tsx +69 -0
- package/src/primitives/layout/index.ts +40 -0
- package/src/primitives/navigation/index.ts +6 -0
- package/src/primitives/overlays/Modal/Modal.helpers.ts +31 -0
- package/src/primitives/overlays/Modal/Modal.tsx +264 -0
- package/src/primitives/overlays/Modal/Modal.types.ts +193 -0
- package/src/primitives/overlays/Modal/index.ts +43 -0
- package/src/primitives/overlays/Modal/useModalLogic.ts +103 -0
- package/src/primitives/overlays/index.ts +12 -0
- package/src/primitives/typography/Text.tsx +51 -0
- package/src/primitives/typography/index.ts +1 -0
- package/src/provider/DesignSystemContext.ts +22 -0
- package/src/provider/DesignSystemProvider.tsx +121 -0
- package/src/provider/index.ts +7 -0
- package/src/providers/ThemeProvider/createTheme.ts +304 -0
- package/src/providers/ThemeProvider/defaultTheme.ts +70 -0
- package/src/providers/ThemeProvider/index.ts +34 -0
- package/src/providers/ThemeProvider/types.ts +249 -0
- package/src/providers/index.ts +29 -0
- package/src/tokens/colors.ts +371 -0
- package/src/tokens/index.ts +145 -0
- package/src/tokens/motion.ts +176 -0
- package/src/tokens/radii.ts +82 -0
- package/src/tokens/scales.ts +588 -0
- package/src/tokens/shadows.ts +190 -0
- package/src/tokens/spacing.ts +140 -0
- package/src/tokens/tokens.json +207 -0
- package/src/tokens/typography.ts +251 -0
- package/src/types.ts +50 -0
- package/src/utils/accessibility.ts +169 -0
- package/src/utils/index.ts +25 -0
- package/src/utils/platform.ts +72 -0
|
@@ -0,0 +1,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";
|