@cdx-ui/components 0.0.1-beta.65 → 0.0.1-beta.67

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 (242) hide show
  1. package/lib/commonjs/components/AlertDialog/index.js +12 -10
  2. package/lib/commonjs/components/AlertDialog/index.js.map +1 -1
  3. package/lib/commonjs/components/Avatar/index.js +125 -25
  4. package/lib/commonjs/components/Avatar/index.js.map +1 -1
  5. package/lib/commonjs/components/Button/index.js +127 -28
  6. package/lib/commonjs/components/Button/index.js.map +1 -1
  7. package/lib/commonjs/components/Button/styles.js +9 -3
  8. package/lib/commonjs/components/Button/styles.js.map +1 -1
  9. package/lib/commonjs/components/CLAUDE.md +115 -11
  10. package/lib/commonjs/components/Checkbox/index.js +69 -22
  11. package/lib/commonjs/components/Checkbox/index.js.map +1 -1
  12. package/lib/commonjs/components/Checkbox/styles.js +1 -1
  13. package/lib/commonjs/components/Checkbox/styles.js.map +1 -1
  14. package/lib/commonjs/components/Chip/index.js +44 -36
  15. package/lib/commonjs/components/Chip/index.js.map +1 -1
  16. package/lib/commonjs/components/Chip/styles.js +6 -35
  17. package/lib/commonjs/components/Chip/styles.js.map +1 -1
  18. package/lib/commonjs/components/Dialog/index.js +92 -72
  19. package/lib/commonjs/components/Dialog/index.js.map +1 -1
  20. package/lib/commonjs/components/Field/index.js +70 -26
  21. package/lib/commonjs/components/Field/index.js.map +1 -1
  22. package/lib/commonjs/components/Form/index.js +5 -12
  23. package/lib/commonjs/components/Form/index.js.map +1 -1
  24. package/lib/commonjs/components/IconButton/index.js +5 -33
  25. package/lib/commonjs/components/IconButton/index.js.map +1 -1
  26. package/lib/commonjs/components/Input/index.js +50 -20
  27. package/lib/commonjs/components/Input/index.js.map +1 -1
  28. package/lib/commonjs/components/Link/index.js +14 -7
  29. package/lib/commonjs/components/Link/index.js.map +1 -1
  30. package/lib/commonjs/components/ListItem/index.js +129 -54
  31. package/lib/commonjs/components/ListItem/index.js.map +1 -1
  32. package/lib/commonjs/components/OtpInput/index.js +94 -28
  33. package/lib/commonjs/components/OtpInput/index.js.map +1 -1
  34. package/lib/commonjs/components/ProgressBar/index.js +8 -13
  35. package/lib/commonjs/components/ProgressBar/index.js.map +1 -1
  36. package/lib/commonjs/components/Radio/index.js +51 -53
  37. package/lib/commonjs/components/Radio/index.js.map +1 -1
  38. package/lib/commonjs/components/Select/index.js +112 -46
  39. package/lib/commonjs/components/Select/index.js.map +1 -1
  40. package/lib/commonjs/components/Select/styles.js +2 -2
  41. package/lib/commonjs/components/Select/styles.js.map +1 -1
  42. package/lib/commonjs/components/Switch/BaseSwitch.js +23 -13
  43. package/lib/commonjs/components/Switch/BaseSwitch.js.map +1 -1
  44. package/lib/commonjs/components/Switch/BaseSwitch.web.js +25 -21
  45. package/lib/commonjs/components/Switch/BaseSwitch.web.js.map +1 -1
  46. package/lib/commonjs/components/Switch/index.js +34 -3
  47. package/lib/commonjs/components/Switch/index.js.map +1 -1
  48. package/lib/commonjs/components/Tile/index.js +150 -77
  49. package/lib/commonjs/components/Tile/index.js.map +1 -1
  50. package/lib/commonjs/figma/Chip.figma.js +2 -2
  51. package/lib/commonjs/figma/Chip.figma.js.map +1 -1
  52. package/lib/commonjs/utils/positionToDataIcon.js +41 -0
  53. package/lib/commonjs/utils/positionToDataIcon.js.map +1 -0
  54. package/lib/module/components/AlertDialog/index.js +13 -11
  55. package/lib/module/components/AlertDialog/index.js.map +1 -1
  56. package/lib/module/components/Avatar/index.js +128 -28
  57. package/lib/module/components/Avatar/index.js.map +1 -1
  58. package/lib/module/components/Button/index.js +130 -32
  59. package/lib/module/components/Button/index.js.map +1 -1
  60. package/lib/module/components/Button/styles.js +8 -2
  61. package/lib/module/components/Button/styles.js.map +1 -1
  62. package/lib/module/components/CLAUDE.md +115 -11
  63. package/lib/module/components/Checkbox/index.js +72 -25
  64. package/lib/module/components/Checkbox/index.js.map +1 -1
  65. package/lib/module/components/Checkbox/styles.js +1 -1
  66. package/lib/module/components/Checkbox/styles.js.map +1 -1
  67. package/lib/module/components/Chip/index.js +47 -39
  68. package/lib/module/components/Chip/index.js.map +1 -1
  69. package/lib/module/components/Chip/styles.js +6 -35
  70. package/lib/module/components/Chip/styles.js.map +1 -1
  71. package/lib/module/components/Dialog/index.js +96 -76
  72. package/lib/module/components/Dialog/index.js.map +1 -1
  73. package/lib/module/components/Field/index.js +74 -31
  74. package/lib/module/components/Field/index.js.map +1 -1
  75. package/lib/module/components/Form/index.js +7 -14
  76. package/lib/module/components/Form/index.js.map +1 -1
  77. package/lib/module/components/IconButton/index.js +6 -34
  78. package/lib/module/components/IconButton/index.js.map +1 -1
  79. package/lib/module/components/Input/index.js +53 -23
  80. package/lib/module/components/Input/index.js.map +1 -1
  81. package/lib/module/components/Link/index.js +16 -8
  82. package/lib/module/components/Link/index.js.map +1 -1
  83. package/lib/module/components/ListItem/index.js +134 -59
  84. package/lib/module/components/ListItem/index.js.map +1 -1
  85. package/lib/module/components/OtpInput/index.js +94 -28
  86. package/lib/module/components/OtpInput/index.js.map +1 -1
  87. package/lib/module/components/ProgressBar/index.js +9 -14
  88. package/lib/module/components/ProgressBar/index.js.map +1 -1
  89. package/lib/module/components/Radio/index.js +54 -57
  90. package/lib/module/components/Radio/index.js.map +1 -1
  91. package/lib/module/components/Select/index.js +118 -52
  92. package/lib/module/components/Select/index.js.map +1 -1
  93. package/lib/module/components/Select/styles.js +2 -2
  94. package/lib/module/components/Select/styles.js.map +1 -1
  95. package/lib/module/components/Switch/BaseSwitch.js +24 -14
  96. package/lib/module/components/Switch/BaseSwitch.js.map +1 -1
  97. package/lib/module/components/Switch/BaseSwitch.web.js +26 -22
  98. package/lib/module/components/Switch/BaseSwitch.web.js.map +1 -1
  99. package/lib/module/components/Switch/index.js +35 -4
  100. package/lib/module/components/Switch/index.js.map +1 -1
  101. package/lib/module/components/Tile/index.js +155 -83
  102. package/lib/module/components/Tile/index.js.map +1 -1
  103. package/lib/module/figma/Chip.figma.js +2 -2
  104. package/lib/module/figma/Chip.figma.js.map +1 -1
  105. package/lib/module/utils/positionToDataIcon.js +38 -0
  106. package/lib/module/utils/positionToDataIcon.js.map +1 -0
  107. package/lib/typescript/components/AlertDialog/index.d.ts.map +1 -1
  108. package/lib/typescript/components/Avatar/index.d.ts +6 -2
  109. package/lib/typescript/components/Avatar/index.d.ts.map +1 -1
  110. package/lib/typescript/components/Button/index.d.ts +31 -8
  111. package/lib/typescript/components/Button/index.d.ts.map +1 -1
  112. package/lib/typescript/components/Button/styles.d.ts +2 -1
  113. package/lib/typescript/components/Button/styles.d.ts.map +1 -1
  114. package/lib/typescript/components/Checkbox/index.d.ts +3 -1
  115. package/lib/typescript/components/Checkbox/index.d.ts.map +1 -1
  116. package/lib/typescript/components/Chip/index.d.ts +19 -9
  117. package/lib/typescript/components/Chip/index.d.ts.map +1 -1
  118. package/lib/typescript/components/Chip/styles.d.ts +0 -1
  119. package/lib/typescript/components/Chip/styles.d.ts.map +1 -1
  120. package/lib/typescript/components/Dialog/index.d.ts +12 -6
  121. package/lib/typescript/components/Dialog/index.d.ts.map +1 -1
  122. package/lib/typescript/components/Dialog/styles.d.ts +1 -1
  123. package/lib/typescript/components/Field/index.d.ts +14 -7
  124. package/lib/typescript/components/Field/index.d.ts.map +1 -1
  125. package/lib/typescript/components/Form/index.d.ts +4 -4
  126. package/lib/typescript/components/Form/index.d.ts.map +1 -1
  127. package/lib/typescript/components/IconButton/index.d.ts.map +1 -1
  128. package/lib/typescript/components/Input/index.d.ts +11 -3
  129. package/lib/typescript/components/Input/index.d.ts.map +1 -1
  130. package/lib/typescript/components/Link/index.d.ts +2 -0
  131. package/lib/typescript/components/Link/index.d.ts.map +1 -1
  132. package/lib/typescript/components/ListItem/index.d.ts +28 -6
  133. package/lib/typescript/components/ListItem/index.d.ts.map +1 -1
  134. package/lib/typescript/components/OtpInput/index.d.ts +1 -1
  135. package/lib/typescript/components/OtpInput/index.d.ts.map +1 -1
  136. package/lib/typescript/components/ProgressBar/index.d.ts +4 -4
  137. package/lib/typescript/components/ProgressBar/index.d.ts.map +1 -1
  138. package/lib/typescript/components/Radio/index.d.ts +4 -0
  139. package/lib/typescript/components/Radio/index.d.ts.map +1 -1
  140. package/lib/typescript/components/Select/index.d.ts +35 -2
  141. package/lib/typescript/components/Select/index.d.ts.map +1 -1
  142. package/lib/typescript/components/Select/styles.d.ts.map +1 -1
  143. package/lib/typescript/components/Switch/BaseSwitch.d.ts +12 -5
  144. package/lib/typescript/components/Switch/BaseSwitch.d.ts.map +1 -1
  145. package/lib/typescript/components/Switch/BaseSwitch.web.d.ts +11 -5
  146. package/lib/typescript/components/Switch/BaseSwitch.web.d.ts.map +1 -1
  147. package/lib/typescript/components/Switch/index.d.ts +5 -1
  148. package/lib/typescript/components/Switch/index.d.ts.map +1 -1
  149. package/lib/typescript/components/Tile/index.d.ts +8 -2
  150. package/lib/typescript/components/Tile/index.d.ts.map +1 -1
  151. package/lib/typescript/utils/positionToDataIcon.d.ts +29 -0
  152. package/lib/typescript/utils/positionToDataIcon.d.ts.map +1 -0
  153. package/package.json +5 -5
  154. package/src/components/AlertDialog/index.tsx +13 -14
  155. package/src/components/Avatar/index.tsx +134 -44
  156. package/src/components/Button/index.tsx +216 -44
  157. package/src/components/Button/styles.ts +50 -38
  158. package/src/components/CLAUDE.md +115 -11
  159. package/src/components/Checkbox/index.tsx +93 -32
  160. package/src/components/Checkbox/styles.ts +1 -1
  161. package/src/components/Chip/index.tsx +88 -61
  162. package/src/components/Chip/styles.ts +6 -18
  163. package/src/components/Dialog/index.tsx +139 -99
  164. package/src/components/Field/index.tsx +113 -49
  165. package/src/components/Form/index.tsx +18 -14
  166. package/src/components/IconButton/index.tsx +7 -31
  167. package/src/components/Input/index.tsx +75 -32
  168. package/src/components/Link/index.tsx +38 -12
  169. package/src/components/ListItem/index.tsx +158 -73
  170. package/src/components/OtpInput/index.tsx +126 -43
  171. package/src/components/ProgressBar/index.tsx +11 -16
  172. package/src/components/Radio/index.tsx +76 -54
  173. package/src/components/Select/index.tsx +174 -59
  174. package/src/components/Select/styles.ts +1 -2
  175. package/src/components/Switch/BaseSwitch.tsx +38 -16
  176. package/src/components/Switch/BaseSwitch.web.tsx +28 -35
  177. package/src/components/Switch/index.tsx +51 -4
  178. package/src/components/Tile/index.tsx +145 -94
  179. package/src/utils/positionToDataIcon.ts +34 -0
  180. package/lib/commonjs/components/Field/FieldLabel.js +0 -33
  181. package/lib/commonjs/components/Field/FieldLabel.js.map +0 -1
  182. package/lib/commonjs/components/Field/FieldLabel.web.js +0 -22
  183. package/lib/commonjs/components/Field/FieldLabel.web.js.map +0 -1
  184. package/lib/commonjs/components/Form/FormRoot.js +0 -25
  185. package/lib/commonjs/components/Form/FormRoot.js.map +0 -1
  186. package/lib/commonjs/components/Form/FormRoot.web.js +0 -17
  187. package/lib/commonjs/components/Form/FormRoot.web.js.map +0 -1
  188. package/lib/commonjs/components/Input/BaseInput.android.js +0 -20
  189. package/lib/commonjs/components/Input/BaseInput.android.js.map +0 -1
  190. package/lib/commonjs/components/Input/BaseInput.js +0 -13
  191. package/lib/commonjs/components/Input/BaseInput.js.map +0 -1
  192. package/lib/commonjs/components/Input/BaseInput.web.js +0 -50
  193. package/lib/commonjs/components/Input/BaseInput.web.js.map +0 -1
  194. package/lib/commonjs/components/Select/SelectTriggerHost.js +0 -10
  195. package/lib/commonjs/components/Select/SelectTriggerHost.js.map +0 -1
  196. package/lib/commonjs/components/Select/SelectTriggerHost.web.js +0 -102
  197. package/lib/commonjs/components/Select/SelectTriggerHost.web.js.map +0 -1
  198. package/lib/module/components/Field/FieldLabel.js +0 -29
  199. package/lib/module/components/Field/FieldLabel.js.map +0 -1
  200. package/lib/module/components/Field/FieldLabel.web.js +0 -17
  201. package/lib/module/components/Field/FieldLabel.web.js.map +0 -1
  202. package/lib/module/components/Form/FormRoot.js +0 -21
  203. package/lib/module/components/Form/FormRoot.js.map +0 -1
  204. package/lib/module/components/Form/FormRoot.web.js +0 -12
  205. package/lib/module/components/Form/FormRoot.web.js.map +0 -1
  206. package/lib/module/components/Input/BaseInput.android.js +0 -16
  207. package/lib/module/components/Input/BaseInput.android.js.map +0 -1
  208. package/lib/module/components/Input/BaseInput.js +0 -4
  209. package/lib/module/components/Input/BaseInput.js.map +0 -1
  210. package/lib/module/components/Input/BaseInput.web.js +0 -45
  211. package/lib/module/components/Input/BaseInput.web.js.map +0 -1
  212. package/lib/module/components/Select/SelectTriggerHost.js +0 -7
  213. package/lib/module/components/Select/SelectTriggerHost.js.map +0 -1
  214. package/lib/module/components/Select/SelectTriggerHost.web.js +0 -97
  215. package/lib/module/components/Select/SelectTriggerHost.web.js.map +0 -1
  216. package/lib/typescript/components/Field/FieldLabel.d.ts +0 -18
  217. package/lib/typescript/components/Field/FieldLabel.d.ts.map +0 -1
  218. package/lib/typescript/components/Field/FieldLabel.web.d.ts +0 -16
  219. package/lib/typescript/components/Field/FieldLabel.web.d.ts.map +0 -1
  220. package/lib/typescript/components/Form/FormRoot.d.ts +0 -14
  221. package/lib/typescript/components/Form/FormRoot.d.ts.map +0 -1
  222. package/lib/typescript/components/Form/FormRoot.web.d.ts +0 -10
  223. package/lib/typescript/components/Form/FormRoot.web.d.ts.map +0 -1
  224. package/lib/typescript/components/Input/BaseInput.android.d.ts +0 -3
  225. package/lib/typescript/components/Input/BaseInput.android.d.ts.map +0 -1
  226. package/lib/typescript/components/Input/BaseInput.d.ts +0 -2
  227. package/lib/typescript/components/Input/BaseInput.d.ts.map +0 -1
  228. package/lib/typescript/components/Input/BaseInput.web.d.ts +0 -3
  229. package/lib/typescript/components/Input/BaseInput.web.d.ts.map +0 -1
  230. package/lib/typescript/components/Select/SelectTriggerHost.d.ts +0 -3
  231. package/lib/typescript/components/Select/SelectTriggerHost.d.ts.map +0 -1
  232. package/lib/typescript/components/Select/SelectTriggerHost.web.d.ts +0 -16
  233. package/lib/typescript/components/Select/SelectTriggerHost.web.d.ts.map +0 -1
  234. package/src/components/Field/FieldLabel.tsx +0 -33
  235. package/src/components/Field/FieldLabel.web.tsx +0 -25
  236. package/src/components/Form/FormRoot.tsx +0 -20
  237. package/src/components/Form/FormRoot.web.tsx +0 -12
  238. package/src/components/Input/BaseInput.android.tsx +0 -13
  239. package/src/components/Input/BaseInput.tsx +0 -1
  240. package/src/components/Input/BaseInput.web.tsx +0 -41
  241. package/src/components/Select/SelectTriggerHost.tsx +0 -4
  242. package/src/components/Select/SelectTriggerHost.web.tsx +0 -143
@@ -1,7 +1,15 @@
1
- import { forwardRef, type ReactNode } from 'react';
1
+ import { forwardRef, useEffect, useMemo, type ReactNode } from 'react';
2
2
  import { Image, Text, View, type ImageProps, type TextProps, type ViewProps } from 'react-native';
3
- import { createAvatar, type IAvatarImageProps, type IAvatarProps } from '@cdx-ui/primitives';
4
- import { cn, useStyleContext, withStyleContext } from '@cdx-ui/utils';
3
+ import {
4
+ AvatarRoot as AvatarRootPrimitive,
5
+ Slot,
6
+ dataAttributes,
7
+ mergeDataAttributes,
8
+ useAvatarContext,
9
+ type IAvatarImageProps,
10
+ type IAvatarProps,
11
+ } from '@cdx-ui/primitives';
12
+ import { cn, ParentContext, useParentContext, useStyleContext } from '@cdx-ui/utils';
5
13
  import { Icon as IconComponent, type IconProps } from '../Icon';
6
14
  import {
7
15
  type AvatarVariantProps,
@@ -14,17 +22,29 @@ import {
14
22
 
15
23
  const SCOPE = 'AVATAR';
16
24
 
17
- const Root = withStyleContext(View, SCOPE);
18
-
19
25
  const useAvatarStyleContext = () => useStyleContext(SCOPE) as AvatarVariantProps;
20
26
 
21
- const AvatarPrimitive = createAvatar({
22
- Root,
23
- Image,
24
- Text,
25
- Icon: IconComponent,
26
- Badge: View,
27
- });
27
+ // =============================================================================
28
+ // UTILITIES
29
+ // =============================================================================
30
+
31
+ function getInitials(name: string): string {
32
+ const trimmed = name.trim();
33
+ const words = trimmed.split(/\s+/);
34
+
35
+ if (words.length === 1 && trimmed.length <= 2) {
36
+ return trimmed.toUpperCase();
37
+ }
38
+
39
+ let result = '';
40
+ for (const word of words) {
41
+ if (word.length > 0) {
42
+ result += word.charAt(0);
43
+ }
44
+ if (result.length >= 2) break;
45
+ }
46
+ return result.toUpperCase();
47
+ }
28
48
 
29
49
  // =============================================================================
30
50
  // AVATAR ROOT
@@ -39,16 +59,21 @@ const AvatarRoot = forwardRef<View, AvatarProps>(
39
59
  ({ size = 'md', className, children, style, ...props }, ref) => {
40
60
  const computedClassName = cn(avatarRootVariants({ size }), className);
41
61
 
62
+ const parent = useParentContext();
63
+ const ctx = useMemo(() => ({ ...parent, [SCOPE]: { size } }), [parent, size]);
64
+
42
65
  return (
43
- <AvatarPrimitive
44
- ref={ref as never}
45
- className={computedClassName}
46
- style={style}
47
- context={{ size }}
48
- {...props}
49
- >
50
- {children}
51
- </AvatarPrimitive>
66
+ <ParentContext.Provider value={ctx}>
67
+ <AvatarRootPrimitive
68
+ ref={ref}
69
+ className={computedClassName}
70
+ style={style}
71
+ {...props}
72
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'avatar' }))}
73
+ >
74
+ {children}
75
+ </AvatarRootPrimitive>
76
+ </ParentContext.Provider>
52
77
  );
53
78
  },
54
79
  );
@@ -60,21 +85,52 @@ AvatarRoot.displayName = 'Avatar';
60
85
  // =============================================================================
61
86
 
62
87
  export interface AvatarImageProps extends Omit<ImageProps, 'source'>, IAvatarImageProps {
88
+ asChild?: boolean;
63
89
  className?: string;
64
90
  }
65
91
 
66
- const AvatarImage = forwardRef<Image, AvatarImageProps>(({ className, style, ...props }, ref) => {
67
- const computedClassName = cn(avatarImageVariants(), className);
92
+ const AvatarImage = forwardRef<Image, AvatarImageProps>(
93
+ ({ asChild, className, style, src, source, onLoad, onError, ...props }, ref) => {
94
+ const { imageError, setImageLoaded, setImageError } = useAvatarContext();
95
+ const computedClassName = cn(avatarImageVariants(), className);
68
96
 
69
- return (
70
- <AvatarPrimitive.Image
71
- ref={ref as never}
72
- className={computedClassName}
73
- style={style}
74
- {...props}
75
- />
76
- );
77
- });
97
+ const resolvedSource = useMemo(() => (src ? { uri: src } : source), [src, source]);
98
+
99
+ useEffect(() => {
100
+ if (!resolvedSource) {
101
+ setImageError(true);
102
+ } else {
103
+ setImageError(false);
104
+ setImageLoaded(false);
105
+ }
106
+ }, [resolvedSource, setImageError, setImageLoaded]);
107
+
108
+ if (!resolvedSource || imageError) {
109
+ return null;
110
+ }
111
+
112
+ const Comp = asChild ? Slot : Image;
113
+
114
+ return (
115
+ <Comp
116
+ ref={ref}
117
+ source={resolvedSource}
118
+ className={computedClassName}
119
+ style={style}
120
+ onLoad={(e: unknown) => {
121
+ setImageLoaded(true);
122
+ if (onLoad) (onLoad as (e: unknown) => void)(e);
123
+ }}
124
+ onError={(e: unknown) => {
125
+ setImageError(true);
126
+ if (onError) (onError as (e: unknown) => void)(e);
127
+ }}
128
+ {...props}
129
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'avatar-image' }))}
130
+ />
131
+ );
132
+ },
133
+ );
78
134
 
79
135
  AvatarImage.displayName = 'Avatar.Image';
80
136
 
@@ -83,24 +139,32 @@ AvatarImage.displayName = 'Avatar.Image';
83
139
  // =============================================================================
84
140
 
85
141
  export interface AvatarTextProps extends TextProps {
142
+ asChild?: boolean;
86
143
  className?: string;
87
144
  children?: ReactNode;
88
145
  }
89
146
 
90
147
  const AvatarText = forwardRef<Text, AvatarTextProps>(
91
- ({ className, children, style, ...props }, ref) => {
148
+ ({ asChild, className, children, style, ...props }, ref) => {
92
149
  const { size } = useAvatarStyleContext();
150
+ const { imageLoaded, imageError } = useAvatarContext();
151
+ const visible = !imageLoaded || imageError;
93
152
  const computedClassName = cn(avatarTextVariants({ size }), className);
94
153
 
154
+ const text = typeof children === 'string' ? getInitials(children) : children;
155
+
156
+ const Comp = asChild ? Slot : Text;
157
+
95
158
  return (
96
- <AvatarPrimitive.Text
97
- ref={ref as never}
159
+ <Comp
160
+ ref={ref}
98
161
  className={computedClassName}
99
- style={style}
162
+ style={[style, !visible && { opacity: 0 }]}
100
163
  {...props}
164
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'avatar-text' }))}
101
165
  >
102
- {children}
103
- </AvatarPrimitive.Text>
166
+ {text}
167
+ </Comp>
104
168
  );
105
169
  },
106
170
  );
@@ -112,14 +176,35 @@ AvatarText.displayName = 'Avatar.Text';
112
176
  // =============================================================================
113
177
 
114
178
  export interface AvatarIconProps extends Omit<IconProps, 'children'> {
179
+ asChild?: boolean;
115
180
  className?: string;
116
181
  }
117
182
 
118
- const AvatarIcon = ({ className, style, ...props }: AvatarIconProps) => {
183
+ const AvatarIcon = ({ asChild, className, style, ...props }: AvatarIconProps) => {
119
184
  const { size } = useAvatarStyleContext();
185
+ const { imageLoaded, imageError } = useAvatarContext();
186
+ const visible = !imageLoaded || imageError;
120
187
  const computedClassName = cn(avatarIconVariants({ size }), className);
121
188
 
122
- return <AvatarPrimitive.Icon className={computedClassName} style={style} {...props} />;
189
+ if (asChild) {
190
+ return (
191
+ <Slot
192
+ className={computedClassName}
193
+ style={[style, !visible && { opacity: 0 }]}
194
+ {...props}
195
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'avatar-icon' }))}
196
+ />
197
+ );
198
+ }
199
+
200
+ return (
201
+ <IconComponent
202
+ className={computedClassName}
203
+ style={[style, !visible && { opacity: 0 }]}
204
+ {...props}
205
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'avatar-icon' }))}
206
+ />
207
+ );
123
208
  };
124
209
 
125
210
  AvatarIcon.displayName = 'Avatar.Icon';
@@ -129,24 +214,27 @@ AvatarIcon.displayName = 'Avatar.Icon';
129
214
  // =============================================================================
130
215
 
131
216
  export interface AvatarBadgeProps extends ViewProps {
217
+ asChild?: boolean;
132
218
  className?: string;
133
219
  children?: ReactNode;
134
220
  }
135
221
 
136
222
  const AvatarBadge = forwardRef<View, AvatarBadgeProps>(
137
- ({ className, children, style, ...props }, ref) => {
223
+ ({ asChild, className, children, style, ...props }, ref) => {
138
224
  const { size } = useAvatarStyleContext();
139
225
  const computedClassName = cn(avatarBadgeVariants({ size }), className);
226
+ const Comp = asChild ? Slot : View;
140
227
 
141
228
  return (
142
- <AvatarPrimitive.Badge
143
- ref={ref as never}
229
+ <Comp
230
+ ref={ref}
144
231
  className={computedClassName}
145
232
  style={style}
146
233
  {...props}
234
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'avatar-badge' }))}
147
235
  >
148
236
  {children}
149
- </AvatarPrimitive.Badge>
237
+ </Comp>
150
238
  );
151
239
  },
152
240
  );
@@ -170,3 +258,5 @@ export const Avatar = Object.assign(AvatarRoot, {
170
258
  Icon: AvatarIcon,
171
259
  Badge: AvatarBadge,
172
260
  }) as AvatarCompoundComponent;
261
+
262
+ export type { AvatarVariantProps };
@@ -1,7 +1,6 @@
1
- import { forwardRef, useEffect, type ReactElement, type ReactNode } from 'react';
1
+ import React, { forwardRef, useEffect, useMemo, type ReactElement, type ReactNode } from 'react';
2
2
  import {
3
3
  ActivityIndicator,
4
- Pressable,
5
4
  Text,
6
5
  View,
7
6
  type ActivityIndicatorProps,
@@ -9,33 +8,37 @@ import {
9
8
  type ViewProps,
10
9
  type TextProps,
11
10
  } from 'react-native';
12
- import { createButton, type IButtonProps } from '@cdx-ui/primitives';
13
- import { cn, useStyleContext, withStyleContext } from '@cdx-ui/utils';
11
+ import {
12
+ ButtonRoot as ButtonRootPrimitive,
13
+ Slot,
14
+ dataAttributes,
15
+ mergeDataAttributes,
16
+ type IButtonProps,
17
+ } from '@cdx-ui/primitives';
18
+ import {
19
+ cn,
20
+ flattenChildren,
21
+ ParentContext,
22
+ useParentContext,
23
+ useStyleContext,
24
+ } from '@cdx-ui/utils';
14
25
  import { Icon, IconProps } from '../Icon';
26
+ import { positionToDataIcon, type IconSlotPosition } from '../../utils/positionToDataIcon';
15
27
  import {
16
28
  type ButtonGroupVariantProps,
17
29
  type ButtonVariantProps,
18
30
  buttonGroupVariants,
19
31
  buttonIconVariants,
20
32
  buttonRootVariants,
33
+ buttonSpinnerColorVariants,
21
34
  buttonSpinnerVariants,
22
35
  buttonTextVariants,
23
36
  } from './styles';
24
37
 
25
38
  const SCOPE = 'BUTTON';
26
39
 
27
- const Root = withStyleContext(Pressable, SCOPE);
28
-
29
40
  const useButtonStyleContext = () => useStyleContext(SCOPE) as ButtonVariantProps;
30
41
 
31
- const ButtonPrimitive = createButton({
32
- Root,
33
- Text,
34
- Group: View,
35
- Spinner: ActivityIndicator,
36
- Icon: View,
37
- });
38
-
39
42
  // =============================================================================
40
43
  // STYLED ROOT COMPONENT
41
44
  // =============================================================================
@@ -98,16 +101,24 @@ const ButtonRoot = forwardRef<View, ButtonProps>(
98
101
  className,
99
102
  );
100
103
 
104
+ const parent = useParentContext();
105
+ const ctx = useMemo(
106
+ () => ({ ...parent, [SCOPE]: { variant, color, size, fullWidth } }),
107
+ [parent, variant, color, size, fullWidth],
108
+ );
109
+
101
110
  return (
102
- <ButtonPrimitive
103
- ref={ref}
104
- className={computedClassName}
105
- style={style}
106
- context={{ variant, color, size, fullWidth }}
107
- {...props}
108
- >
109
- {children}
110
- </ButtonPrimitive>
111
+ <ParentContext.Provider value={ctx}>
112
+ <ButtonRootPrimitive
113
+ ref={ref}
114
+ className={computedClassName}
115
+ style={style}
116
+ {...props}
117
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'button' }))}
118
+ >
119
+ {children}
120
+ </ButtonRootPrimitive>
121
+ </ParentContext.Provider>
111
122
  );
112
123
  },
113
124
  );
@@ -115,92 +126,253 @@ const ButtonRoot = forwardRef<View, ButtonProps>(
115
126
  ButtonRoot.displayName = 'Button';
116
127
 
117
128
  // =============================================================================
118
- // STYLED TEXT COMPONENT
129
+ // STYLED LABEL COMPONENT
119
130
  // =============================================================================
120
131
 
121
- export interface ButtonTextProps extends TextProps {
132
+ export interface ButtonLabelProps extends TextProps {
133
+ asChild?: boolean;
122
134
  className?: string;
123
135
  children?: ReactNode;
124
136
  }
125
137
 
126
- const ButtonLabel = forwardRef<Text, ButtonTextProps>(
127
- ({ className, children, style, ...props }, ref) => {
138
+ const ButtonLabel = forwardRef<Text, ButtonLabelProps>(
139
+ ({ asChild, className, children, style, ...props }, ref) => {
128
140
  const { variant, color, size } = useButtonStyleContext();
129
-
130
141
  const computedClassName = cn(buttonTextVariants({ variant, color, size }), className);
142
+ const Comp = asChild ? Slot : Text;
131
143
 
132
144
  return (
133
- <ButtonPrimitive.Text ref={ref} className={computedClassName} style={style} {...props}>
145
+ <Comp
146
+ ref={ref}
147
+ className={computedClassName}
148
+ style={style}
149
+ {...props}
150
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'button-label' }))}
151
+ >
134
152
  {children}
135
- </ButtonPrimitive.Text>
153
+ </Comp>
136
154
  );
137
155
  },
138
156
  );
139
157
 
140
158
  ButtonLabel.displayName = 'Button.Label';
141
159
 
160
+ // =============================================================================
161
+ // STYLED GROUP COMPONENT
162
+ // =============================================================================
163
+
142
164
  // TODO: Use ButtonGroupVariantProps
143
165
  export interface ButtonGroupProps extends Omit<ViewProps, 'children'> {
166
+ asChild?: boolean;
144
167
  className?: string;
145
168
  children: ReactElement | ReactElement[];
146
169
  flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
147
170
  isDisabled?: boolean;
148
171
  isAttached?: boolean;
149
172
  reversed?: boolean;
173
+ isReversed?: boolean;
150
174
  }
151
175
 
152
176
  const ButtonGroup = forwardRef<View, ButtonGroupProps>(
153
- ({ className, children, style, flexDirection = 'row', isAttached, ...props }, ref) => {
177
+ (
178
+ {
179
+ asChild,
180
+ className,
181
+ children,
182
+ style,
183
+ flexDirection = 'row',
184
+ isAttached,
185
+ isDisabled,
186
+ isReversed,
187
+ reversed,
188
+ ...props
189
+ },
190
+ ref,
191
+ ) => {
192
+ const direction = flexDirection.includes('reverse')
193
+ ? flexDirection === 'column-reverse'
194
+ ? 'column'
195
+ : 'row'
196
+ : flexDirection;
197
+
198
+ let childrenArray = React.Children.toArray(flattenChildren(children));
199
+ childrenArray = isReversed || reversed ? [...childrenArray].reverse() : childrenArray;
200
+
201
+ const computedChildren = childrenArray.map((child: any, index: number) => {
202
+ if (typeof child === 'string' || typeof child === 'number') {
203
+ return child;
204
+ }
205
+
206
+ const attachedStyles: Record<string, number> = {};
207
+
208
+ if (index === 0) {
209
+ if (direction === 'column') {
210
+ attachedStyles.borderBottomLeftRadius = 0;
211
+ attachedStyles.borderBottomRightRadius = 0;
212
+ } else {
213
+ attachedStyles.borderTopRightRadius = 0;
214
+ attachedStyles.borderBottomRightRadius = 0;
215
+ }
216
+ } else if (index === childrenArray.length - 1) {
217
+ if (direction === 'column') {
218
+ attachedStyles.borderTopLeftRadius = 0;
219
+ attachedStyles.borderTopRightRadius = 0;
220
+ } else {
221
+ attachedStyles.borderTopLeftRadius = 0;
222
+ attachedStyles.borderBottomLeftRadius = 0;
223
+ }
224
+ } else {
225
+ attachedStyles.borderRadius = 0;
226
+ }
227
+
228
+ const childProps = {
229
+ isDisabled,
230
+ ...child.props,
231
+ style: {
232
+ ...(isAttached ? attachedStyles : {}),
233
+ ...child.props.style,
234
+ },
235
+ };
236
+
237
+ return (
238
+ <React.Fragment key={child.key ?? `spaced-child-${index.toString()}`}>
239
+ {React.cloneElement(child, childProps)}
240
+ </React.Fragment>
241
+ );
242
+ });
243
+
154
244
  const groupClassName = cn(
155
245
  buttonGroupVariants({ flexDirection, isAttached: !!isAttached }),
156
246
  className,
157
247
  );
248
+ const Comp = asChild ? Slot : View;
249
+ const gapProp = isAttached ? { gap: 0 } : {};
158
250
 
159
251
  return (
160
- <ButtonPrimitive.Group
252
+ <Comp
161
253
  ref={ref}
162
254
  className={groupClassName}
163
- style={[{ flexDirection }, style]}
164
- isAttached={isAttached}
255
+ style={[{ flexDirection }, style, gapProp]}
165
256
  {...props}
257
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'button-group' }))}
166
258
  >
167
- {children}
168
- </ButtonPrimitive.Group>
259
+ {computedChildren}
260
+ </Comp>
169
261
  );
170
262
  },
171
263
  );
172
264
 
173
265
  ButtonGroup.displayName = 'Button.Group';
174
266
 
175
- export interface ButtonIconProps extends Omit<IconProps, 'children'> {}
267
+ // =============================================================================
268
+ // STYLED ICON COMPONENT
269
+ // =============================================================================
176
270
 
177
- const ButtonIcon = ({ className, style, as, ...props }: ButtonIconProps) => {
178
- const { variant, color, size } = useButtonStyleContext();
271
+ export interface ButtonIconProps extends Omit<IconProps, 'children' | 'as'> {
272
+ asChild?: boolean;
273
+ /**
274
+ * Forge icon component to render. Required unless `asChild` is set, in which
275
+ * case the consumer's substituted element is rendered instead.
276
+ */
277
+ as?: IconProps['as'];
278
+ /**
279
+ * Position hint that emits `data-icon="inline-start"` or `data-icon="inline-end"`.
280
+ * CVA uses this attribute to apply leading/trailing padding logically (RTL-aware).
281
+ * Omit to apply the symmetric baseline gap.
282
+ */
283
+ position?: IconSlotPosition;
284
+ }
179
285
 
286
+ const ButtonIcon = ({ asChild, className, style, as, position, ...props }: ButtonIconProps) => {
287
+ const { variant, color, size } = useButtonStyleContext();
180
288
  const computedClassName = cn(buttonIconVariants({ variant, color, size }), className);
181
289
 
182
- return <Icon as={as} className={computedClassName} style={style} {...props} />;
290
+ if (asChild) {
291
+ return (
292
+ <Slot
293
+ className={computedClassName}
294
+ style={style}
295
+ {...props}
296
+ {...mergeDataAttributes(
297
+ props,
298
+ dataAttributes({ slot: 'button-icon' }),
299
+ positionToDataIcon(position),
300
+ )}
301
+ />
302
+ );
303
+ }
304
+
305
+ if (!as) {
306
+ return null;
307
+ }
308
+
309
+ return (
310
+ <Icon
311
+ as={as}
312
+ className={computedClassName}
313
+ style={style}
314
+ {...props}
315
+ {...mergeDataAttributes(
316
+ props,
317
+ dataAttributes({ slot: 'button-icon' }),
318
+ positionToDataIcon(position),
319
+ )}
320
+ />
321
+ );
183
322
  };
184
323
 
185
324
  ButtonIcon.displayName = 'Button.Icon';
186
325
 
326
+ // =============================================================================
327
+ // STYLED SPINNER COMPONENT
328
+ // =============================================================================
329
+
187
330
  export interface ButtonSpinnerProps extends ActivityIndicatorProps {
331
+ asChild?: boolean;
188
332
  className?: string;
189
333
  colorClassName?: string;
334
+ /**
335
+ * Position hint that emits `data-icon="inline-start"` or `data-icon="inline-end"`.
336
+ * CVA uses this attribute to apply leading/trailing padding logically (RTL-aware).
337
+ * Omit to apply the symmetric baseline gap.
338
+ */
339
+ position?: IconSlotPosition;
190
340
  }
191
341
 
192
342
  const ButtonSpinner = forwardRef<ActivityIndicator, ButtonSpinnerProps>(
193
- ({ className, colorClassName, style, ...props }, ref) => {
343
+ (
344
+ {
345
+ asChild,
346
+ className,
347
+ colorClassName,
348
+ style,
349
+ position,
350
+ accessibilityLabel = 'Loading',
351
+ ...props
352
+ },
353
+ ref,
354
+ ) => {
194
355
  const { variant, color } = useButtonStyleContext();
195
- const computedColorClassName = cn(buttonSpinnerVariants({ variant, color }), colorClassName);
356
+ const computedClassName = cn(buttonSpinnerVariants(), className);
357
+ const computedColorClassName = cn(
358
+ buttonSpinnerColorVariants({ variant, color }),
359
+ colorClassName,
360
+ );
361
+ const Comp = asChild ? Slot : ActivityIndicator;
196
362
 
197
363
  return (
198
- <ButtonPrimitive.Spinner
364
+ <Comp
199
365
  ref={ref}
200
- className={className}
366
+ className={computedClassName}
201
367
  colorClassName={computedColorClassName}
202
368
  style={style}
369
+ accessibilityLabel={accessibilityLabel}
203
370
  {...props}
371
+ {...mergeDataAttributes(
372
+ props,
373
+ dataAttributes({ slot: 'button-spinner' }),
374
+ positionToDataIcon(position),
375
+ )}
204
376
  />
205
377
  );
206
378
  },