@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,120 @@
1
+ /**
2
+ * ProgressBar Logic Hook
3
+ *
4
+ * Extracts all computational logic from the ProgressBar component.
5
+ * Returns memoized values for optimal render performance.
6
+ *
7
+ * Responsibilities:
8
+ * - Clamp and validate progress value
9
+ * - Calculate width percentage
10
+ * - Pass through the `color` prop as `indicatorColorToken`
11
+ * - Always use `"borderSubtle"` as the track color
12
+ * - Resolve size dimensions
13
+ * - Generate accessibility props
14
+ *
15
+ * @see ProgressBar.tsx - Component consuming this hook
16
+ * @see ProgressBar.helpers.ts - Pure calculation functions
17
+ * @see ProgressBar.a11y.ts - Accessibility prop generation
18
+ */
19
+
20
+ import { useResponsiveProp } from "@shopify/restyle";
21
+ import { useMemo } from "react";
22
+ import { useDesignSystem } from "../../../provider";
23
+ import { getProgressBarA11y } from "./ProgressBar.a11y";
24
+ import {
25
+ clampProgress,
26
+ getProgressBarBorderRadius,
27
+ getWidthPercent,
28
+ progressToPercent,
29
+ } from "./ProgressBar.helpers";
30
+ import {
31
+ ProgressBarSize,
32
+ UseProgressBarLogicParams,
33
+ UseProgressBarLogicReturn,
34
+ } from "./ProgressBar.types";
35
+
36
+ /**
37
+ * Orchestrates ProgressBar rendering logic and state management.
38
+ *
39
+ * Handles:
40
+ * - Responsive size resolution
41
+ * - Progress value clamping (0.0 to 1.0 range)
42
+ * - Width percentage calculation for indicator
43
+ * - Color token pass-through for indicator; track is always "borderSubtle"
44
+ * - Border radius calculation based on size
45
+ * - Accessibility props with progress value
46
+ *
47
+ * @param params - Configuration object for progress bar behavior
48
+ * @param params.progress - Progress value (0.0 to 1.0, values outside clamped)
49
+ * @param params.color - Color token for the indicator (RestyleColor)
50
+ * @param params.size - Size variant ('sm' | 'md' | 'lg')
51
+ * @param params.accessibilityLabel - Custom a11y label
52
+ *
53
+ * @returns Computed values for rendering ProgressBar
54
+ *
55
+ * @performance This hook uses useMemo() for expensive calculations
56
+ *
57
+ * @example
58
+ * const {
59
+ * clampedProgress,
60
+ * widthPercent,
61
+ * indicatorColorToken,
62
+ * trackColorToken,
63
+ * borderRadius,
64
+ * a11yProps,
65
+ * } = useProgressBarLogic({
66
+ * progress: 0.75,
67
+ * color: "feedbackSuccess",
68
+ * size: "md",
69
+ * });
70
+ */
71
+ export function useProgressBarLogic({
72
+ progress,
73
+ color,
74
+ size,
75
+ accessibilityLabel,
76
+ }: UseProgressBarLogicParams): UseProgressBarLogicReturn {
77
+ const { labels: t } = useDesignSystem();
78
+ // Resolve responsive size
79
+ const resolvedSize = (useResponsiveProp(size) ?? "md") as ProgressBarSize;
80
+
81
+ // Memoized progress calculations
82
+ const progressValues = useMemo(() => {
83
+ const clamped = clampProgress(progress);
84
+ return {
85
+ clampedProgress: clamped,
86
+ progressPercent: progressToPercent(clamped),
87
+ widthPercent: getWidthPercent(clamped),
88
+ };
89
+ }, [progress]);
90
+
91
+ // Color tokens: indicator uses the color prop directly, track is always borderSubtle
92
+ const colorTokens = useMemo(
93
+ () => ({
94
+ indicatorColorToken: color,
95
+ trackColorToken: "borderSubtle" as const,
96
+ }),
97
+ [color]
98
+ );
99
+
100
+ // Border radius based on size
101
+ const borderRadius = getProgressBarBorderRadius(resolvedSize);
102
+
103
+ // Accessibility props
104
+ const a11yProps = useMemo(
105
+ () =>
106
+ getProgressBarA11y({
107
+ progress,
108
+ accessibilityLabel,
109
+ fallbackLabel: t.designSystem.progressBar.fallbackLabel,
110
+ }),
111
+ [progress, accessibilityLabel, t.designSystem.progressBar.fallbackLabel]
112
+ );
113
+
114
+ return {
115
+ ...progressValues,
116
+ ...colorTokens,
117
+ borderRadius,
118
+ a11yProps,
119
+ };
120
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Skeleton Component Helpers
3
+ *
4
+ * Pure functions for calculating skeleton dimensions and styles.
5
+ * All values are derived from design tokens for consistency.
6
+ *
7
+ * ## Size Scale by Variant
8
+ *
9
+ * ### Text Variant (rounded rectangle)
10
+ * | Size | Width | Height | Use Case |
11
+ * |------|--------|--------|----------|
12
+ * | sm | 100% | 16px | Caption, labels |
13
+ * | md | 100% | 20px | Body text (default) |
14
+ * | lg | 100% | 28px | Headings |
15
+ *
16
+ * ### Circular Variant (avatar/icon)
17
+ * | Size | Width | Height | Use Case |
18
+ * |------|--------|--------|----------|
19
+ * | sm | 32px | 32px | Small avatar |
20
+ * | md | 40px | 40px | Default avatar |
21
+ * | lg | 56px | 56px | Large avatar |
22
+ *
23
+ * ### Rectangular Variant (image/card)
24
+ * | Size | Width | Height | Use Case |
25
+ * |------|--------|--------|----------|
26
+ * | sm | 100% | 80px | Thumbnail |
27
+ * | md | 100% | 120px | Card image |
28
+ * | lg | 100% | 200px | Hero image |
29
+ */
30
+
31
+ import { RestyleTheme } from "../../../core/restyle";
32
+ import {
33
+ ResolveSkeletonDimensionsParams,
34
+ SkeletonDimensionsResult,
35
+ SkeletonSize,
36
+ SkeletonSizeConfig,
37
+ SkeletonVariant,
38
+ } from "./Skeleton.types";
39
+
40
+ /**
41
+ * Predefined size configurations for each variant.
42
+ * Following 8px grid system for consistency.
43
+ */
44
+ export const SKELETON_SIZE_CONFIG: SkeletonSizeConfig = {
45
+ text: {
46
+ sm: { width: "100%", height: 16 },
47
+ md: { width: "100%", height: 20 },
48
+ lg: { width: "100%", height: 28 },
49
+ },
50
+ circular: {
51
+ sm: { width: 32, height: 32 },
52
+ md: { width: 40, height: 40 },
53
+ lg: { width: 56, height: 56 },
54
+ },
55
+ rectangular: {
56
+ sm: { width: "100%", height: 80 },
57
+ md: { width: "100%", height: 120 },
58
+ lg: { width: "100%", height: 200 },
59
+ },
60
+ } as const;
61
+
62
+ /**
63
+ * Border radius mapping by variant.
64
+ * - text: small radius for text-like appearance
65
+ * - circular: full radius for perfect circle
66
+ * - rectangular: medium radius for card-like appearance
67
+ */
68
+ export const SKELETON_BORDER_RADIUS: Record<SkeletonVariant, keyof RestyleTheme["borderRadii"]> = {
69
+ text: "xs",
70
+ circular: "full",
71
+ rectangular: "md",
72
+ } as const;
73
+
74
+ /**
75
+ * Animation timing constants.
76
+ */
77
+ export const SKELETON_ANIMATION = {
78
+ /** Default pulse animation duration in ms */
79
+ DEFAULT_DURATION: 1500,
80
+ /** Minimum opacity during pulse */
81
+ MIN_OPACITY: 0.4,
82
+ /** Maximum opacity during pulse */
83
+ MAX_OPACITY: 1,
84
+ } as const;
85
+
86
+ /**
87
+ * Get dimensions for a skeleton based on variant and size.
88
+ *
89
+ * @param params - Variant, size, and optional custom dimensions
90
+ * @returns Resolved width and height values
91
+ *
92
+ * @example
93
+ * getSkeletonDimensions({ variant: "text", size: "md" })
94
+ * // { width: "100%", height: 20 }
95
+ *
96
+ * @example
97
+ * getSkeletonDimensions({ variant: "circular", size: "lg", customWidth: 64 })
98
+ * // { width: 64, height: 64 } // circular maintains aspect ratio
99
+ */
100
+ export function getSkeletonDimensions(
101
+ params: ResolveSkeletonDimensionsParams
102
+ ): SkeletonDimensionsResult {
103
+ const { variant, size, customWidth, customHeight } = params;
104
+ const config = SKELETON_SIZE_CONFIG[variant][size];
105
+
106
+ let width = customWidth ?? config.width;
107
+ let height = customHeight ?? config.height;
108
+
109
+ // For circular variant, maintain 1:1 aspect ratio
110
+ if (variant === "circular") {
111
+ if (customWidth !== undefined && customHeight === undefined) {
112
+ height = typeof customWidth === "number" ? customWidth : config.height;
113
+ } else if (customHeight !== undefined && customWidth === undefined) {
114
+ width = customHeight;
115
+ }
116
+ }
117
+
118
+ return { width, height };
119
+ }
120
+
121
+ /**
122
+ * Get border radius token for variant.
123
+ *
124
+ * @param variant - Skeleton shape variant
125
+ * @returns Border radius token key
126
+ */
127
+ export function getSkeletonBorderRadius(
128
+ variant: SkeletonVariant
129
+ ): keyof RestyleTheme["borderRadii"] {
130
+ return SKELETON_BORDER_RADIUS[variant];
131
+ }
132
+
133
+ /**
134
+ * Get default dimensions for a size preset.
135
+ *
136
+ * @param variant - Shape variant
137
+ * @param size - Size preset
138
+ * @returns Default dimensions
139
+ */
140
+ export function getSkeletonSizePreset(
141
+ variant: SkeletonVariant,
142
+ size: SkeletonSize
143
+ ): SkeletonDimensionsResult {
144
+ return SKELETON_SIZE_CONFIG[variant][size];
145
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Skeleton Component
3
+ *
4
+ * @description Animated placeholder for loading states - Atom
5
+ *
6
+ * Skeleton provides visual feedback during content loading, reducing
7
+ * perceived wait time and preventing layout shift. Uses performant
8
+ * native driver animations for smooth 60fps rendering.
9
+ *
10
+ * ## Variants
11
+ * | Variant | Shape | Use Case |
12
+ * |-------------|--------------------| ---------|
13
+ * | text | Rounded rectangle | Text placeholders |
14
+ * | circular | Perfect circle | Avatars, icons |
15
+ * | rectangular | Sharp corners | Images, cards |
16
+ *
17
+ * ## Size Presets
18
+ * | Size | Text Height | Circular | Rectangular |
19
+ * |------|-------------|----------|-------------|
20
+ * | sm | 16px | 32×32 | 80px height |
21
+ * | md | 20px | 40×40 | 120px height |
22
+ * | lg | 28px | 56×56 | 200px height |
23
+ *
24
+ * ## Animation Types
25
+ * - `pulse`: Smooth opacity fade (default, best performance)
26
+ * - `wave`: Shimmer effect (premium feel)
27
+ * - `none`: Static placeholder
28
+ *
29
+ * ## Features
30
+ * - Three shape variants (text, circular, rectangular)
31
+ * - Three size presets with custom override support
32
+ * - Smooth pulse animation using native driver
33
+ * - Theme-aware skeleton color
34
+ * - Hidden from screen readers (decorative)
35
+ * - Responsive size support
36
+ *
37
+ * @performance
38
+ * - Wrapped with React.memo() to prevent unnecessary re-renders
39
+ * - Uses native driver for smooth 60fps pulse animations
40
+ * - Animated loop runs independently per skeleton instance
41
+ * - Respects reduced motion preferences for accessibility
42
+ * - Dimension calculations cached with useMemo()
43
+ *
44
+ * ## Best Practices
45
+ * - Match skeleton dimensions to actual content
46
+ * - Use consistent animation across loading states
47
+ * - Provide actual loading state via aria-live regions
48
+ * - Avoid excessive skeleton count (cognitive load)
49
+ *
50
+ * @see Skeleton.types.ts - Type definitions
51
+ * @see Skeleton.helpers.ts - Dimension calculations
52
+ * @see Skeleton.a11y.ts - Accessibility (hidden from AT)
53
+ *
54
+ * @example
55
+ * // Basic text skeleton
56
+ * <Skeleton />
57
+ *
58
+ * @example
59
+ * // Avatar skeleton
60
+ * <Skeleton variant="circular" size="lg" />
61
+ *
62
+ * @example
63
+ * // Custom dimensions
64
+ * <Skeleton variant="rectangular" width={300} height={200} />
65
+ *
66
+ * @example
67
+ * // Responsive size
68
+ * <Skeleton size={{ phone: "sm", tablet: "lg" }} />
69
+ *
70
+ * @example
71
+ * // Text block placeholder
72
+ * <Box gap="xs">
73
+ * <Skeleton width="90%" />
74
+ * <Skeleton width="75%" />
75
+ * <Skeleton width="85%" />
76
+ * </Box>
77
+ *
78
+ * @example
79
+ * // Card skeleton
80
+ * <Card>
81
+ * <Skeleton variant="rectangular" height={150} />
82
+ * <Box padding="md" gap="sm">
83
+ * <Skeleton variant="text" size="lg" width="60%" />
84
+ * <Skeleton variant="text" size="sm" />
85
+ * <Skeleton variant="text" size="sm" width="80%" />
86
+ * </Box>
87
+ * </Card>
88
+ */
89
+
90
+ import {
91
+ createRestyleComponent,
92
+ layout,
93
+ spacing,
94
+ spacingShorthand,
95
+ } from "@shopify/restyle";
96
+ import React, { memo } from "react";
97
+ import { RestyleTheme } from "../../../core/restyle";
98
+ import { BaseThemeColor } from "../../../types";
99
+ import { AnimatedBox } from "../../layout";
100
+ import { BaseSkeletonContainerProps, SkeletonProps } from "./Skeleton.types";
101
+ import { useSkeletonLogic } from "./useSkeletonLogic";
102
+
103
+ /**
104
+ * Base Skeleton container with Restyle support.
105
+ * @internal
106
+ */
107
+ const BaseSkeletonContainer = createRestyleComponent<
108
+ BaseSkeletonContainerProps,
109
+ RestyleTheme
110
+ >([layout, spacing, spacingShorthand], AnimatedBox);
111
+
112
+ function SkeletonComponent({
113
+ variant = "text",
114
+ size = "md",
115
+ width,
116
+ height,
117
+ animation = "pulse",
118
+ color = "specialSkeleton",
119
+ duration = 1500,
120
+ testID,
121
+ ...rest
122
+ }: SkeletonProps) {
123
+ const {
124
+ resolvedWidth,
125
+ resolvedHeight,
126
+ borderRadius,
127
+ animatedOpacity,
128
+ backgroundColor,
129
+ a11yProps,
130
+ } = useSkeletonLogic({
131
+ variant,
132
+ size,
133
+ width,
134
+ height,
135
+ animation,
136
+ color,
137
+ duration,
138
+ });
139
+
140
+ return (
141
+ <BaseSkeletonContainer
142
+ testID={testID}
143
+ backgroundColor={backgroundColor as BaseThemeColor}
144
+ borderRadius={borderRadius}
145
+ width={resolvedWidth as any}
146
+ height={resolvedHeight}
147
+ style={{ opacity: animatedOpacity }}
148
+ {...a11yProps}
149
+ {...rest}
150
+ />
151
+ );
152
+ }
153
+
154
+ export const Skeleton = memo(SkeletonComponent);
155
+ Skeleton.displayName = "Skeleton";
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Skeleton Component Types
3
+ *
4
+ * Type definitions for the Skeleton component and its related hooks/helpers.
5
+ * All types are explicitly exported for external consumption.
6
+ *
7
+ * @see Skeleton.tsx - Component implementation
8
+ */
9
+
10
+ import { BoxProps, ResponsiveValue } from "@shopify/restyle";
11
+ import { Animated, ViewProps } from "react-native";
12
+ import { RestyleTheme } from "../../../core/restyle";
13
+ import { RestyleColor } from "../../../types";
14
+
15
+ // =============================================================================
16
+ // VARIANT TYPES
17
+ // =============================================================================
18
+
19
+ /**
20
+ * Shape variant for the Skeleton component.
21
+ * Derived from the restyle theme's `skeletonVariants` keys.
22
+ *
23
+ * - `text`: Rounded rectangle for text placeholders (default)
24
+ * - `circular`: Perfect circle for avatars/icons
25
+ * - `rectangular`: Sharp corners for images/cards
26
+ */
27
+ export type SkeletonVariant = Exclude<keyof RestyleTheme["skeletonVariants"], "defaults">;
28
+
29
+ /**
30
+ * Size preset for the Skeleton component.
31
+ * Derived from the restyle theme's `skeletonSizes` keys.
32
+ * Maps to predefined dimensions for common use cases.
33
+ *
34
+ * - `sm`: Small - compact UI elements (16px height for text)
35
+ * - `md`: Medium - standard content (20px height for text)
36
+ * - `lg`: Large - headings/prominent elements (28px height for text)
37
+ */
38
+ export type SkeletonSize = Exclude<keyof RestyleTheme["skeletonSizes"], "defaults">;
39
+
40
+ /**
41
+ * Animation style for the Skeleton component.
42
+ *
43
+ * - `pulse`: Opacity fades in/out (default, performant)
44
+ * - `wave`: Shimmer effect moving left to right (premium feel)
45
+ * - `none`: No animation (static placeholder)
46
+ */
47
+ export type SkeletonAnimation = "pulse" | "wave" | "none";
48
+
49
+ // =============================================================================
50
+ // COMPONENT PROPS
51
+ // =============================================================================
52
+
53
+ /** Base props combining Restyle Box props with ViewProps */
54
+ export type BaseSkeletonProps = BoxProps<RestyleTheme> & ViewProps;
55
+
56
+ /**
57
+ * Base props for the Skeleton container.
58
+ * @internal
59
+ */
60
+ export type BaseSkeletonContainerProps = BoxProps<RestyleTheme> & ViewProps;
61
+
62
+ /**
63
+ * Props for the Skeleton component.
64
+ *
65
+ * @example
66
+ * // Text skeleton (default)
67
+ * <Skeleton width={200} />
68
+ *
69
+ * @example
70
+ * // Avatar skeleton
71
+ * <Skeleton variant="circular" size="lg" />
72
+ *
73
+ * @example
74
+ * // Image placeholder
75
+ * <Skeleton variant="rectangular" width={300} height={200} />
76
+ *
77
+ * @example
78
+ * // Multiple text lines
79
+ * <Skeleton.Group>
80
+ * <Skeleton width="80%" />
81
+ * <Skeleton width="60%" />
82
+ * <Skeleton width="90%" />
83
+ * </Skeleton.Group>
84
+ */
85
+ export interface SkeletonProps extends Omit<BaseSkeletonProps, "width" | "height"> {
86
+ /**
87
+ * Shape variant of the skeleton (supports responsive values).
88
+ * @default "text"
89
+ */
90
+ variant?: ResponsiveValue<SkeletonVariant, RestyleTheme["breakpoints"]>;
91
+
92
+ /**
93
+ * Size preset (supports responsive values).
94
+ * Provides predefined dimensions based on variant.
95
+ * @default "md"
96
+ */
97
+ size?: ResponsiveValue<SkeletonSize, RestyleTheme["breakpoints"]>;
98
+
99
+ /**
100
+ * Custom width. Overrides size preset width.
101
+ * Accepts number (pixels), string (percentage), or responsive value.
102
+ */
103
+ width?: ResponsiveValue<number | string, RestyleTheme["breakpoints"]>;
104
+
105
+ /**
106
+ * Custom height. Overrides size preset height.
107
+ * Accepts number (pixels) or responsive value.
108
+ */
109
+ height?: ResponsiveValue<number, RestyleTheme["breakpoints"]>;
110
+
111
+ /**
112
+ * Animation style.
113
+ * @default "pulse"
114
+ */
115
+ animation?: SkeletonAnimation;
116
+
117
+ /**
118
+ * Background color token from theme.
119
+ * @default "specialSkeleton"
120
+ */
121
+ color?: RestyleColor;
122
+
123
+ /**
124
+ * Animation duration in milliseconds.
125
+ * @default 1500
126
+ */
127
+ duration?: number;
128
+
129
+ /** Test ID for testing frameworks */
130
+ testID?: string;
131
+ }
132
+
133
+ // =============================================================================
134
+ // HOOK TYPES
135
+ // =============================================================================
136
+
137
+ /**
138
+ * Parameters for the useSkeletonLogic hook.
139
+ */
140
+ export interface UseSkeletonLogicParams {
141
+ variant: ResponsiveValue<SkeletonVariant, RestyleTheme["breakpoints"]>;
142
+ size: ResponsiveValue<SkeletonSize, RestyleTheme["breakpoints"]>;
143
+ width?: ResponsiveValue<number | string, RestyleTheme["breakpoints"]>;
144
+ height?: ResponsiveValue<number, RestyleTheme["breakpoints"]>;
145
+ animation: SkeletonAnimation;
146
+ color: RestyleColor;
147
+ duration: number;
148
+ }
149
+
150
+ /**
151
+ * Return value from the useSkeletonLogic hook.
152
+ */
153
+ export interface UseSkeletonLogicReturn {
154
+ /** Resolved width value */
155
+ resolvedWidth: number | string;
156
+ /** Resolved height value */
157
+ resolvedHeight: number;
158
+ /** Border radius based on variant */
159
+ borderRadius: keyof RestyleTheme["borderRadii"];
160
+ /** Animated opacity value for pulse animation */
161
+ animatedOpacity: Animated.Value;
162
+ /** Background color token */
163
+ backgroundColor: RestyleColor;
164
+ /** Accessibility props */
165
+ a11yProps: SkeletonA11yProps;
166
+ }
167
+
168
+ // =============================================================================
169
+ // HELPER TYPES
170
+ // =============================================================================
171
+
172
+ /**
173
+ * Dimensions configuration for skeleton sizes.
174
+ */
175
+ export interface SkeletonDimensions {
176
+ width: number | string;
177
+ height: number;
178
+ }
179
+
180
+ /**
181
+ * Size configuration map by variant.
182
+ */
183
+ export type SkeletonSizeConfig = Record<SkeletonVariant, Record<SkeletonSize, SkeletonDimensions>>;
184
+
185
+ /**
186
+ * Parameters for resolving skeleton dimensions.
187
+ */
188
+ export interface ResolveSkeletonDimensionsParams {
189
+ variant: SkeletonVariant;
190
+ size: SkeletonSize;
191
+ customWidth?: number | string;
192
+ customHeight?: number;
193
+ }
194
+
195
+ /**
196
+ * Result from resolving skeleton dimensions.
197
+ */
198
+ export interface SkeletonDimensionsResult {
199
+ width: number | string;
200
+ height: number;
201
+ }
202
+
203
+ // =============================================================================
204
+ // ACCESSIBILITY TYPES
205
+ // =============================================================================
206
+
207
+ /**
208
+ * Parameters for generating skeleton accessibility props.
209
+ */
210
+ export interface SkeletonA11yParams {
211
+ /** Optional label for screen readers */
212
+ accessibilityLabel?: string;
213
+ }
214
+
215
+ /**
216
+ * Accessibility props returned by getSkeletonA11y.
217
+ */
218
+ export interface SkeletonA11yProps {
219
+ accessible: boolean;
220
+ accessibilityRole: "none";
221
+ accessibilityElementsHidden: boolean;
222
+ importantForAccessibility: "no-hide-descendants";
223
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Skeleton Component
3
+ *
4
+ * @description Animated placeholder for loading states
5
+ *
6
+ * @example
7
+ * import { Skeleton } from "@/design-system";
8
+ *
9
+ * // Text skeleton
10
+ * <Skeleton />
11
+ *
12
+ * // Avatar skeleton
13
+ * <Skeleton variant="circular" size="lg" />
14
+ *
15
+ * // Image placeholder
16
+ * <Skeleton variant="rectangular" width={300} height={200} />
17
+ */
18
+
19
+ export { Skeleton } from "./Skeleton";
20
+ export type {
21
+ BaseSkeletonProps,
22
+ ResolveSkeletonDimensionsParams,
23
+ SkeletonA11yParams,
24
+ SkeletonA11yProps,
25
+ SkeletonAnimation,
26
+ SkeletonDimensions,
27
+ SkeletonDimensionsResult,
28
+ SkeletonProps,
29
+ SkeletonSize,
30
+ SkeletonSizeConfig,
31
+ SkeletonVariant,
32
+ UseSkeletonLogicParams,
33
+ UseSkeletonLogicReturn,
34
+ } from "./Skeleton.types";
35
+ export {
36
+ getSkeletonBorderRadius,
37
+ getSkeletonDimensions,
38
+ getSkeletonSizePreset,
39
+ SKELETON_ANIMATION,
40
+ SKELETON_BORDER_RADIUS,
41
+ SKELETON_SIZE_CONFIG,
42
+ } from "./Skeleton.helpers";
43
+ export { getSkeletonA11y } from "./Skeleton.a11y";
44
+ export { useSkeletonLogic } from "./useSkeletonLogic";