@umituz/react-native-design-system 1.0.0

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 (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/package.json +43 -0
  4. package/src/index.ts +345 -0
  5. package/src/presentation/atoms/AtomicAvatar.tsx +157 -0
  6. package/src/presentation/atoms/AtomicAvatarGroup.tsx +169 -0
  7. package/src/presentation/atoms/AtomicBadge.tsx +232 -0
  8. package/src/presentation/atoms/AtomicButton.tsx +124 -0
  9. package/src/presentation/atoms/AtomicCard.tsx +112 -0
  10. package/src/presentation/atoms/AtomicChip.tsx +223 -0
  11. package/src/presentation/atoms/AtomicDatePicker.tsx +347 -0
  12. package/src/presentation/atoms/AtomicDivider.tsx +114 -0
  13. package/src/presentation/atoms/AtomicFab.tsx +104 -0
  14. package/src/presentation/atoms/AtomicFilter.tsx +154 -0
  15. package/src/presentation/atoms/AtomicFormError.tsx +105 -0
  16. package/src/presentation/atoms/AtomicIcon.tsx +29 -0
  17. package/src/presentation/atoms/AtomicImage.tsx +149 -0
  18. package/src/presentation/atoms/AtomicInput.tsx +232 -0
  19. package/src/presentation/atoms/AtomicNumberInput.tsx +182 -0
  20. package/src/presentation/atoms/AtomicPicker.tsx +458 -0
  21. package/src/presentation/atoms/AtomicProgress.tsx +143 -0
  22. package/src/presentation/atoms/AtomicSearchBar.tsx +114 -0
  23. package/src/presentation/atoms/AtomicSkeleton.tsx +146 -0
  24. package/src/presentation/atoms/AtomicSort.tsx +145 -0
  25. package/src/presentation/atoms/AtomicSwitch.tsx +166 -0
  26. package/src/presentation/atoms/AtomicText.tsx +50 -0
  27. package/src/presentation/atoms/AtomicTextArea.tsx +198 -0
  28. package/src/presentation/atoms/AtomicTouchable.tsx +233 -0
  29. package/src/presentation/atoms/fab/styles/fabStyles.ts +69 -0
  30. package/src/presentation/atoms/fab/types/index.ts +88 -0
  31. package/src/presentation/atoms/filter/styles/filterStyles.ts +32 -0
  32. package/src/presentation/atoms/filter/types/index.ts +89 -0
  33. package/src/presentation/atoms/index.ts +378 -0
  34. package/src/presentation/atoms/input/hooks/useInputState.ts +15 -0
  35. package/src/presentation/atoms/input/styles/inputStyles.ts +66 -0
  36. package/src/presentation/atoms/input/types/index.ts +25 -0
  37. package/src/presentation/atoms/picker/styles/pickerStyles.ts +200 -0
  38. package/src/presentation/atoms/picker/types/index.ts +40 -0
  39. package/src/presentation/atoms/touchable/styles/touchableStyles.ts +71 -0
  40. package/src/presentation/atoms/touchable/types/index.ts +162 -0
  41. package/src/presentation/hooks/useAppDesignTokens.ts +78 -0
  42. package/src/presentation/hooks/useResponsive.ts +180 -0
  43. package/src/presentation/loading/index.ts +40 -0
  44. package/src/presentation/loading/presentation/components/LoadingSpinner.tsx +116 -0
  45. package/src/presentation/loading/presentation/components/LoadingState.tsx +200 -0
  46. package/src/presentation/loading/presentation/hooks/useLoading.ts +100 -0
  47. package/src/presentation/molecules/AtomicConfirmationModal.tsx +263 -0
  48. package/src/presentation/molecules/EmptyState.tsx +130 -0
  49. package/src/presentation/molecules/FormField.tsx +128 -0
  50. package/src/presentation/molecules/GridContainer.tsx +124 -0
  51. package/src/presentation/molecules/IconContainer.tsx +94 -0
  52. package/src/presentation/molecules/LanguageSwitcher.tsx +42 -0
  53. package/src/presentation/molecules/ListItem.tsx +36 -0
  54. package/src/presentation/molecules/ScreenHeader.tsx +140 -0
  55. package/src/presentation/molecules/SearchBar.tsx +85 -0
  56. package/src/presentation/molecules/SectionCard.tsx +74 -0
  57. package/src/presentation/molecules/SectionContainer.tsx +106 -0
  58. package/src/presentation/molecules/SectionHeader.tsx +125 -0
  59. package/src/presentation/molecules/confirmation-modal/styles/confirmationModalStyles.ts +133 -0
  60. package/src/presentation/molecules/confirmation-modal/types/index.ts +107 -0
  61. package/src/presentation/molecules/index.ts +42 -0
  62. package/src/presentation/molecules/languageswitcher/config/languageSwitcherConfig.ts +5 -0
  63. package/src/presentation/molecules/languageswitcher/hooks/useLanguageNavigation.ts +15 -0
  64. package/src/presentation/molecules/listitem/styles/listItemStyles.ts +19 -0
  65. package/src/presentation/molecules/listitem/types/index.ts +17 -0
  66. package/src/presentation/organisms/AppHeader.tsx +136 -0
  67. package/src/presentation/organisms/FormContainer.tsx +180 -0
  68. package/src/presentation/organisms/ScreenLayout.tsx +209 -0
  69. package/src/presentation/organisms/index.ts +25 -0
  70. package/src/presentation/tokens/AppDesignTokens.ts +57 -0
  71. package/src/presentation/tokens/commonStyles.ts +253 -0
  72. package/src/presentation/tokens/core/BaseTokens.ts +394 -0
  73. package/src/presentation/tokens/core/ColorPalette.ts +398 -0
  74. package/src/presentation/tokens/core/TokenFactory.ts +120 -0
  75. package/src/presentation/utils/platformConstants.ts +124 -0
  76. package/src/presentation/utils/responsive.ts +516 -0
  77. package/src/presentation/utils/variants/compound.ts +29 -0
  78. package/src/presentation/utils/variants/core.ts +39 -0
  79. package/src/presentation/utils/variants/helpers.ts +13 -0
  80. package/src/presentation/utils/variants.ts +3 -0
@@ -0,0 +1,40 @@
1
+ import { ViewStyle, TextStyle } from 'react-native';
2
+ import { AtomicIconColor } from '../../AtomicIcon';
3
+
4
+ /**
5
+ * Picker option item
6
+ *
7
+ * icon: Any MaterialIcons name
8
+ * @see https://fonts.google.com/icons
9
+ */
10
+ export interface PickerOption {
11
+ label: string;
12
+ value: string;
13
+ icon?: string; // MaterialIcons name
14
+ disabled?: boolean;
15
+ description?: string;
16
+ testID?: string;
17
+ }
18
+
19
+ export type PickerSize = 'sm' | 'md' | 'lg';
20
+
21
+ export interface AtomicPickerProps {
22
+ value: string | string[];
23
+ onChange: (value: string | string[]) => void;
24
+ options: PickerOption[];
25
+ label?: string;
26
+ placeholder?: string;
27
+ error?: string;
28
+ disabled?: boolean;
29
+ multiple?: boolean;
30
+ searchable?: boolean;
31
+ clearable?: boolean;
32
+ autoClose?: boolean;
33
+ color?: AtomicIconColor;
34
+ size?: PickerSize;
35
+ modalTitle?: string;
36
+ emptyMessage?: string;
37
+ style?: ViewStyle | ViewStyle[];
38
+ labelStyle?: TextStyle | TextStyle[];
39
+ testID?: string;
40
+ }
@@ -0,0 +1,71 @@
1
+ import { ViewStyle } from 'react-native';
2
+ import { FeedbackStrength } from '../types';
3
+
4
+ /**
5
+ * Get opacity value based on feedback strength
6
+ */
7
+ export const getOpacityValue = (strength: FeedbackStrength): number => {
8
+ switch (strength) {
9
+ case 'subtle':
10
+ return 0.8;
11
+ case 'normal':
12
+ return 0.6;
13
+ case 'strong':
14
+ return 0.4;
15
+ default:
16
+ return 0.6;
17
+ }
18
+ };
19
+
20
+ /**
21
+ * Get base touchable container style
22
+ * Ensures minimum touch target size (iOS HIG: 48x48)
23
+ */
24
+ export const getTouchableContainerStyle = (): ViewStyle => ({
25
+ minWidth: 48,
26
+ minHeight: 48,
27
+ justifyContent: 'center',
28
+ alignItems: 'center',
29
+ });
30
+
31
+ /**
32
+ * Get disabled touchable style
33
+ */
34
+ export const getDisabledStyle = (): ViewStyle => ({
35
+ opacity: 0.5,
36
+ });
37
+
38
+ /**
39
+ * Get loading container style
40
+ * Centers the loading indicator
41
+ */
42
+ export const getLoadingContainerStyle = (): ViewStyle => ({
43
+ justifyContent: 'center',
44
+ alignItems: 'center',
45
+ });
46
+
47
+ /**
48
+ * Convert number to HitSlop object
49
+ * If hitSlop is a number, apply it to all sides
50
+ */
51
+ export const normalizeHitSlop = (
52
+ hitSlop: number | { top?: number; bottom?: number; left?: number; right?: number } | undefined
53
+ ): { top: number; bottom: number; left: number; right: number } | undefined => {
54
+ if (hitSlop === undefined) return undefined;
55
+
56
+ if (typeof hitSlop === 'number') {
57
+ return {
58
+ top: hitSlop,
59
+ bottom: hitSlop,
60
+ left: hitSlop,
61
+ right: hitSlop,
62
+ };
63
+ }
64
+
65
+ return {
66
+ top: hitSlop.top || 0,
67
+ bottom: hitSlop.bottom || 0,
68
+ left: hitSlop.left || 0,
69
+ right: hitSlop.right || 0,
70
+ };
71
+ };
@@ -0,0 +1,162 @@
1
+ import { StyleProp, ViewStyle, GestureResponderEvent } from 'react-native';
2
+
3
+ /**
4
+ * Touchable feedback variant
5
+ * - opacity: iOS-style opacity feedback (default)
6
+ * - highlight: Android-style highlight feedback
7
+ * - ripple: Material Design ripple effect (Android only)
8
+ * - none: No visual feedback
9
+ */
10
+ export type TouchableFeedback = 'opacity' | 'highlight' | 'ripple' | 'none';
11
+
12
+ /**
13
+ * Feedback strength for visual feedback
14
+ * - subtle: Light feedback (0.8 opacity)
15
+ * - normal: Standard feedback (0.6 opacity)
16
+ * - strong: Strong feedback (0.4 opacity)
17
+ */
18
+ export type FeedbackStrength = 'subtle' | 'normal' | 'strong';
19
+
20
+ /**
21
+ * Hit slop configuration
22
+ * Extends the touchable area beyond the component's bounds
23
+ */
24
+ export interface HitSlop {
25
+ top?: number;
26
+ bottom?: number;
27
+ left?: number;
28
+ right?: number;
29
+ }
30
+
31
+ /**
32
+ * AtomicTouchable component props
33
+ *
34
+ * A unified touchable wrapper with consistent behavior across platforms.
35
+ * Uses React Native's Pressable API for modern, accessible touch handling.
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * <AtomicTouchable
40
+ * onPress={handlePress}
41
+ * feedback="opacity"
42
+ * strength="normal"
43
+ * disabled={isLoading}
44
+ * loading={isLoading}
45
+ * hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
46
+ * style={styles.touchable}
47
+ * >
48
+ * <AtomicText>Press Me</AtomicText>
49
+ * </AtomicTouchable>
50
+ * ```
51
+ */
52
+ export interface AtomicTouchableProps {
53
+ /**
54
+ * Content to render inside the touchable
55
+ */
56
+ children: React.ReactNode;
57
+
58
+ /**
59
+ * Callback fired when the touchable is pressed
60
+ */
61
+ onPress?: (event: GestureResponderEvent) => void;
62
+
63
+ /**
64
+ * Callback fired when press starts (finger down)
65
+ */
66
+ onPressIn?: (event: GestureResponderEvent) => void;
67
+
68
+ /**
69
+ * Callback fired when press ends (finger up)
70
+ */
71
+ onPressOut?: (event: GestureResponderEvent) => void;
72
+
73
+ /**
74
+ * Callback fired on long press (500ms default)
75
+ */
76
+ onLongPress?: (event: GestureResponderEvent) => void;
77
+
78
+ /**
79
+ * Feedback variant
80
+ * @default 'opacity'
81
+ */
82
+ feedback?: TouchableFeedback;
83
+
84
+ /**
85
+ * Feedback strength
86
+ * @default 'normal'
87
+ */
88
+ strength?: FeedbackStrength;
89
+
90
+ /**
91
+ * Disable the touchable
92
+ * @default false
93
+ */
94
+ disabled?: boolean;
95
+
96
+ /**
97
+ * Show loading indicator (disables touch)
98
+ * @default false
99
+ */
100
+ loading?: boolean;
101
+
102
+ /**
103
+ * Hit slop - extends touchable area
104
+ * Useful for small touch targets
105
+ * @default undefined
106
+ */
107
+ hitSlop?: HitSlop | number;
108
+
109
+ /**
110
+ * Custom style for the touchable container
111
+ */
112
+ style?: StyleProp<ViewStyle>;
113
+
114
+ /**
115
+ * Style applied when pressed
116
+ */
117
+ pressedStyle?: StyleProp<ViewStyle>;
118
+
119
+ /**
120
+ * Style applied when disabled
121
+ */
122
+ disabledStyle?: StyleProp<ViewStyle>;
123
+
124
+ /**
125
+ * Accessibility label for screen readers
126
+ */
127
+ accessibilityLabel?: string;
128
+
129
+ /**
130
+ * Accessibility hint for screen readers
131
+ */
132
+ accessibilityHint?: string;
133
+
134
+ /**
135
+ * Accessibility role
136
+ * @default 'button'
137
+ */
138
+ accessibilityRole?: 'button' | 'link' | 'none';
139
+
140
+ /**
141
+ * Test ID for E2E testing
142
+ */
143
+ testID?: string;
144
+
145
+ /**
146
+ * Delay before onLongPress is triggered (ms)
147
+ * @default 500
148
+ */
149
+ delayLongPress?: number;
150
+
151
+ /**
152
+ * Ripple color (Android only, for 'ripple' feedback)
153
+ * @default theme primary color with alpha
154
+ */
155
+ rippleColor?: string;
156
+
157
+ /**
158
+ * Border radius for ripple effect (Android only)
159
+ * @default 0
160
+ */
161
+ rippleRadius?: number;
162
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * useAppDesignTokens Hook - Dynamic Theme-Aware Design Tokens
3
+ *
4
+ * ✅ ZERO DUPLICATION - Uses TokenFactory (Single Source of Truth)
5
+ * ✅ DYNAMIC theme switching (light/dark)
6
+ * ✅ Type-safe design tokens
7
+ * ✅ Automatic re-render on theme change
8
+ * ✅ Graceful fallback to light theme
9
+ * ✅ NO CIRCULAR DEPENDENCY - Relative imports break barrel export cycle
10
+ *
11
+ * CRITICAL: Uses RELATIVE imports to break circular dependency!
12
+ * - Relative import for useTheme (not barrel '@domains/theme')
13
+ * - Relative import for TokenFactory (not barrel through AppDesignTokens)
14
+ * - NOT exported from AppDesignTokens.ts (only from design-system/index.ts)
15
+ *
16
+ * This architecture prevents cycle:
17
+ * - useAppDesignTokens → useTheme (relative, not through barrel)
18
+ * - theme → design-system (through barrel, but useAppDesignTokens not in token barrel)
19
+ * - No cycle detected!
20
+ *
21
+ * @module useAppDesignTokens
22
+ */
23
+
24
+ import { useMemo } from 'react';
25
+ import { useTheme } from '../../../theme/infrastructure/stores/themeStore';
26
+ import { createDesignTokens, STATIC_DESIGN_TOKENS, type ThemeMode } from '../tokens/core/TokenFactory';
27
+
28
+ /**
29
+ * 🎯 DYNAMIC DESIGN TOKENS HOOK
30
+ *
31
+ * USE THIS HOOK in all components for theme-aware design tokens!
32
+ *
33
+ * ✅ Colors are DYNAMIC (update when theme changes)
34
+ * ✅ Typography, spacing, etc. are STATIC (performance optimization)
35
+ * ✅ Automatic re-render on theme change
36
+ * ✅ Zero duplication (uses TokenFactory)
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * import { useAppDesignTokens } from '@domains/design-system';
41
+ *
42
+ * const MyComponent = () => {
43
+ * const tokens = useAppDesignTokens();
44
+ * return (
45
+ * <View style={{
46
+ * backgroundColor: tokens.colors.primary,
47
+ * padding: tokens.spacing.md,
48
+ * borderRadius: tokens.borders.radius.md
49
+ * }}>
50
+ * <Text style={tokens.typography.bodyLarge}>Hello!</Text>
51
+ * </View>
52
+ * );
53
+ * };
54
+ * ```
55
+ */
56
+ export const useAppDesignTokens = () => {
57
+ // ✅ Hooks must be called unconditionally at the top level
58
+ const { theme, themeMode: mode } = useTheme();
59
+
60
+ return useMemo(() => {
61
+ try {
62
+ // Validate theme mode
63
+ const themeMode: ThemeMode = mode === 'dark' ? 'dark' : 'light';
64
+
65
+ // Validate theme colors exist
66
+ if (!theme?.colors || typeof theme.colors !== 'object' || !theme.colors.primary) {
67
+ console.warn('useAppDesignTokens: Invalid theme, using light theme fallback');
68
+ return STATIC_DESIGN_TOKENS; // Fallback to light theme
69
+ }
70
+
71
+ // ✅ Create tokens using TokenFactory (ZERO duplication!)
72
+ return createDesignTokens(themeMode);
73
+ } catch (error) {
74
+ console.warn('useAppDesignTokens: Error accessing theme, using fallback:', error);
75
+ return STATIC_DESIGN_TOKENS; // Fallback to light theme
76
+ }
77
+ }, [theme?.colors, mode]); // Re-compute when colors or mode changes
78
+ };
@@ -0,0 +1,180 @@
1
+ /**
2
+ * useResponsive Hook
3
+ *
4
+ * React Hook for accessing responsive utilities with real-time dimension updates
5
+ * and safe area insets integration.
6
+ *
7
+ * Usage:
8
+ * ```tsx
9
+ * const { logoSize, inputHeight, fabPosition, isSmallDevice } = useResponsive();
10
+ * ```
11
+ */
12
+
13
+ import { useWindowDimensions } from 'react-native';
14
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
15
+ import {
16
+ getResponsiveLogoSize,
17
+ getResponsiveInputHeight,
18
+ getResponsiveHorizontalPadding,
19
+ getResponsiveBottomPosition,
20
+ getResponsiveFABPosition,
21
+ getResponsiveModalMaxHeight,
22
+ getResponsiveMinModalHeight,
23
+ getResponsiveIconContainerSize,
24
+ getResponsiveGridColumns,
25
+ getResponsiveMaxWidth,
26
+ getResponsiveFontSize,
27
+ isSmallPhone,
28
+ isTablet,
29
+ isLandscape,
30
+ getDeviceType,
31
+ getMinTouchTargetSize,
32
+ getSpacingMultiplier,
33
+ getOnboardingIconMarginTop,
34
+ getOnboardingIconMarginBottom,
35
+ getOnboardingTitleMarginBottom,
36
+ getOnboardingTextPadding,
37
+ getOnboardingDescriptionMarginTop,
38
+ getOnboardingIconSize,
39
+ getFormBottomPadding,
40
+ getInputIconSize,
41
+ getFormContentWidth,
42
+ getFormElementSpacing,
43
+ DeviceType,
44
+ } from '../utils/responsive';
45
+
46
+ export interface UseResponsiveReturn {
47
+ // Device info
48
+ width: number;
49
+ height: number;
50
+ isSmallDevice: boolean;
51
+ isTabletDevice: boolean;
52
+ isLandscapeMode: boolean;
53
+ deviceType: DeviceType;
54
+
55
+ // Safe area insets
56
+ insets: {
57
+ top: number;
58
+ bottom: number;
59
+ left: number;
60
+ right: number;
61
+ };
62
+
63
+ // Responsive sizes
64
+ logoSize: number;
65
+ inputHeight: number;
66
+ iconContainerSize: number;
67
+ maxContentWidth: number;
68
+ minTouchTarget: number;
69
+
70
+ // Responsive positioning
71
+ horizontalPadding: number;
72
+ bottomPosition: number;
73
+ fabPosition: { bottom: number; right: number };
74
+
75
+ // Responsive layout
76
+ modalMaxHeight: string;
77
+ modalMinHeight: number;
78
+ gridColumns: number;
79
+ spacingMultiplier: number;
80
+
81
+ // Onboarding-specific spacing (pre-calculated, device-aware)
82
+ onboardingIconMarginTop: number;
83
+ onboardingIconMarginBottom: number;
84
+ onboardingIconSize: number;
85
+ onboardingTitleMarginBottom: number;
86
+ onboardingTextPadding: number;
87
+ onboardingDescriptionMarginTop: number;
88
+
89
+ // Form-specific spacing (pre-calculated, universal)
90
+ formBottomPadding: number;
91
+ inputIconSize: number;
92
+ formContentWidth: number | undefined;
93
+ formElementSpacing: number;
94
+
95
+ // Utility functions
96
+ getLogoSize: (baseSize?: number) => number;
97
+ getInputHeight: (baseHeight?: number) => number;
98
+ getIconSize: (baseSize?: number) => number;
99
+ getMaxWidth: (baseWidth?: number) => number;
100
+ getFontSize: (baseFontSize: number) => number;
101
+ getGridCols: (mobile?: number, tablet?: number) => number;
102
+ }
103
+
104
+ /**
105
+ * Hook for responsive design utilities
106
+ * Automatically updates when screen dimensions or orientation changes
107
+ */
108
+ export const useResponsive = (): UseResponsiveReturn => {
109
+ const { width, height } = useWindowDimensions();
110
+ const insets = useSafeAreaInsets();
111
+
112
+ return {
113
+ // Device info
114
+ width,
115
+ height,
116
+ isSmallDevice: isSmallPhone(),
117
+ isTabletDevice: isTablet(),
118
+ isLandscapeMode: isLandscape(),
119
+ deviceType: getDeviceType(),
120
+
121
+ // Safe area insets
122
+ insets,
123
+
124
+ // Responsive sizes (with default values)
125
+ logoSize: getResponsiveLogoSize(),
126
+ inputHeight: getResponsiveInputHeight(),
127
+ iconContainerSize: getResponsiveIconContainerSize(),
128
+ maxContentWidth: getResponsiveMaxWidth(),
129
+ minTouchTarget: getMinTouchTargetSize(),
130
+
131
+ // Responsive positioning
132
+ horizontalPadding: getResponsiveHorizontalPadding(16, insets),
133
+ bottomPosition: getResponsiveBottomPosition(32, insets),
134
+ fabPosition: getResponsiveFABPosition(insets),
135
+
136
+ // Responsive layout
137
+ modalMaxHeight: getResponsiveModalMaxHeight(),
138
+ modalMinHeight: getResponsiveMinModalHeight(),
139
+ gridColumns: getResponsiveGridColumns(),
140
+ spacingMultiplier: getSpacingMultiplier(),
141
+
142
+ // Onboarding-specific spacing (pre-calculated, no component calculations)
143
+ onboardingIconMarginTop: getOnboardingIconMarginTop(),
144
+ onboardingIconMarginBottom: getOnboardingIconMarginBottom(),
145
+ onboardingIconSize: getOnboardingIconSize(),
146
+ onboardingTitleMarginBottom: getOnboardingTitleMarginBottom(),
147
+ onboardingTextPadding: getOnboardingTextPadding(),
148
+ onboardingDescriptionMarginTop: getOnboardingDescriptionMarginTop(),
149
+
150
+ // Form-specific spacing (pre-calculated, universal)
151
+ formBottomPadding: getFormBottomPadding(insets.bottom),
152
+ inputIconSize: getInputIconSize(),
153
+ formContentWidth: getFormContentWidth(),
154
+ formElementSpacing: getFormElementSpacing(),
155
+
156
+ // Utility functions (allow custom base values)
157
+ getLogoSize: (baseSize) => getResponsiveLogoSize(baseSize),
158
+ getInputHeight: (baseHeight) => getResponsiveInputHeight(baseHeight),
159
+ getIconSize: (baseSize) => getResponsiveIconContainerSize(baseSize),
160
+ getMaxWidth: (baseWidth) => getResponsiveMaxWidth(baseWidth),
161
+ getFontSize: (baseFontSize) => getResponsiveFontSize(baseFontSize),
162
+ getGridCols: (mobile, tablet) => getResponsiveGridColumns(mobile, tablet),
163
+ };
164
+ };
165
+
166
+ /**
167
+ * Shorthand hook for just responsive sizes
168
+ */
169
+ export const useResponsiveSizes = () => {
170
+ const { logoSize, inputHeight, iconContainerSize, maxContentWidth } = useResponsive();
171
+ return { logoSize, inputHeight, iconContainerSize, maxContentWidth };
172
+ };
173
+
174
+ /**
175
+ * Shorthand hook for just device type checks
176
+ */
177
+ export const useDeviceType = () => {
178
+ const { isSmallDevice, isTabletDevice, deviceType } = useResponsive();
179
+ return { isSmallDevice, isTabletDevice, deviceType };
180
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Loading Domain - Public API
3
+ *
4
+ * Domain-Driven Design (DDD) Architecture
5
+ * Theme: {{THEME_NAME}} ({{CATEGORY}} category)
6
+ *
7
+ * This is the SINGLE SOURCE OF TRUTH for the Loading domain.
8
+ * ALL imports from this domain MUST go through this file.
9
+ *
10
+ * Architecture:
11
+ * - presentation/components: UI components (LoadingState, LoadingSpinner)
12
+ * - presentation/hooks: React hooks (useLoading)
13
+ *
14
+ * Usage:
15
+ * import { LoadingState, LoadingSpinner, useLoading } from '@domains/design-system';
16
+ */
17
+
18
+ // =============================================================================
19
+ // PRESENTATION LAYER - Components
20
+ // =============================================================================
21
+
22
+ export { LoadingState } from './presentation/components/LoadingState';
23
+ export type { LoadingStateProps, LoadingStateSize } from './presentation/components/LoadingState';
24
+
25
+ export { LoadingSpinner } from './presentation/components/LoadingSpinner';
26
+ export type {
27
+ LoadingSpinnerProps,
28
+ LoadingSpinnerSize,
29
+ LoadingSpinnerColor,
30
+ } from './presentation/components/LoadingSpinner';
31
+
32
+ // =============================================================================
33
+ // PRESENTATION LAYER - Hooks
34
+ // =============================================================================
35
+
36
+ export { useLoading } from './presentation/hooks/useLoading';
37
+ export type {
38
+ LoadingConfig,
39
+ UseLoadingReturn,
40
+ } from './presentation/hooks/useLoading';
@@ -0,0 +1,116 @@
1
+ /**
2
+ * LoadingSpinner - Theme-Aware Activity Indicator
3
+ *
4
+ * Refactored from AtomicLoadingSpinner - now part of Loading domain
5
+ * Uses central useAppDesignTokens() hook for automatic theme switching
6
+ * Theme: {{THEME_NAME}} ({{CATEGORY}} category)
7
+ *
8
+ * Features:
9
+ * - ✅ AUTOMATIC theme switching via useAppDesignTokens()
10
+ * - ✅ Multiple size variants (small, medium, large)
11
+ * - ✅ Dynamic color customization
12
+ * - ✅ Overlay support
13
+ * - ✅ Message display
14
+ */
15
+
16
+ import React from 'react';
17
+ import { View, ActivityIndicator, ViewStyle } from 'react-native';
18
+ import { useAppDesignTokens } from '../../../hooks/useAppDesignTokens';
19
+ import { withAlpha } from '../../../tokens/AppDesignTokens';
20
+ import { AtomicText } from '../../../atoms/AtomicText';
21
+
22
+ // =============================================================================
23
+ // TYPE DEFINITIONS
24
+ // =============================================================================
25
+
26
+ export type LoadingSpinnerSize = 'small' | 'medium' | 'large';
27
+ export type LoadingSpinnerColor = 'primary' | 'secondary' | 'white';
28
+
29
+ export interface LoadingSpinnerProps {
30
+ size?: LoadingSpinnerSize;
31
+ color?: LoadingSpinnerColor;
32
+ message?: string;
33
+ overlay?: boolean;
34
+ style?: ViewStyle;
35
+ }
36
+
37
+ // =============================================================================
38
+ // SIZE VARIANTS
39
+ // =============================================================================
40
+
41
+ const sizeVariants: Record<LoadingSpinnerSize, 'small' | 'large'> = {
42
+ small: 'small',
43
+ medium: 'large',
44
+ large: 'large',
45
+ };
46
+
47
+ // =============================================================================
48
+ // COMPONENT IMPLEMENTATION
49
+ // =============================================================================
50
+
51
+ export const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
52
+ size = 'medium',
53
+ color = 'primary',
54
+ message,
55
+ overlay = false,
56
+ style,
57
+ }) => {
58
+ // ✅ DYNAMIC tokens from central hook
59
+ const tokens = useAppDesignTokens();
60
+
61
+ const spinnerSize = sizeVariants[size];
62
+
63
+ /**
64
+ * Get spinner color from dynamic theme
65
+ * ✅ Automatically updates when theme changes
66
+ */
67
+ const getSpinnerColor = (): string => {
68
+ switch (color) {
69
+ case 'primary':
70
+ return tokens.colors.primary;
71
+ case 'secondary':
72
+ return tokens.colors.secondary;
73
+ case 'white':
74
+ return tokens.colors.textInverse;
75
+ default:
76
+ return tokens.colors.primary;
77
+ }
78
+ };
79
+
80
+ const spinnerColor = getSpinnerColor();
81
+
82
+ const containerStyle: ViewStyle = overlay
83
+ ? {
84
+ position: 'absolute',
85
+ top: 0,
86
+ left: 0,
87
+ right: 0,
88
+ bottom: 0,
89
+ backgroundColor: withAlpha(tokens.colors.black, 0.5),
90
+ justifyContent: 'center',
91
+ alignItems: 'center',
92
+ zIndex: 9999,
93
+ }
94
+ : {
95
+ justifyContent: 'center',
96
+ alignItems: 'center',
97
+ padding: tokens.spacing.lg,
98
+ };
99
+
100
+ return (
101
+ <View style={[containerStyle, style]}>
102
+ <ActivityIndicator size={spinnerSize} color={spinnerColor} />
103
+ {message && (
104
+ <AtomicText
105
+ type="bodyMedium"
106
+ color={overlay ? 'inverse' : 'primary'}
107
+ style={{ marginTop: tokens.spacing.md, textAlign: 'center' }}
108
+ >
109
+ {message}
110
+ </AtomicText>
111
+ )}
112
+ </View>
113
+ );
114
+ };
115
+
116
+ export default LoadingSpinner;