@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
@@ -254,7 +254,16 @@ export const buttonTextVariants = cva(['font-medium', 'text-center'], {
254
254
  },
255
255
  });
256
256
 
257
- export const buttonSpinnerVariants = cva([], {
257
+ export const buttonSpinnerVariants = cva([
258
+ 'data-[icon=inline-start]:me-1.5',
259
+ 'data-[icon=inline-end]:ms-1.5',
260
+ ]);
261
+
262
+ // Spinner color is set via Uniwind's `colorClassName` prop (maps to the native
263
+ // `color` prop on `ActivityIndicator`). `accent-*` utilities ONLY take effect
264
+ // when passed through `colorClassName` — putting them on `className` is a no-op
265
+ // because the underlying `color` prop is not part of the `style` object.
266
+ export const buttonSpinnerColorVariants = cva([], {
258
267
  variants: {
259
268
  variant: {
260
269
  solid: [],
@@ -349,46 +358,49 @@ export const buttonGroupVariants = cva([], {
349
358
  },
350
359
  });
351
360
 
352
- export const buttonIconVariants = cva([], {
353
- variants: {
354
- variant: {
355
- solid: [],
356
- outline: [],
357
- ghost: [],
358
- },
359
- color: {
360
- action: [],
361
- danger: [],
362
- warning: [],
363
- success: [],
364
- info: [],
365
- },
366
- size: {
367
- default: 'size-5',
368
- small: 'size-4',
361
+ export const buttonIconVariants = cva(
362
+ ['data-[icon=inline-start]:me-1.5', 'data-[icon=inline-end]:ms-1.5'],
363
+ {
364
+ variants: {
365
+ variant: {
366
+ solid: [],
367
+ outline: [],
368
+ ghost: [],
369
+ },
370
+ color: {
371
+ action: [],
372
+ danger: [],
373
+ warning: [],
374
+ success: [],
375
+ info: [],
376
+ },
377
+ size: {
378
+ default: 'size-5',
379
+ small: 'size-4',
380
+ },
369
381
  },
370
- },
371
- compoundVariants: [
372
- // ── solid × color (filled foreground icon) ──────────────────────
373
- { variant: 'solid', color: 'action', className: 'text-content-action-on-strong' },
374
- { variant: 'solid', color: 'danger', className: 'text-content-danger-on-strong' },
375
- { variant: 'solid', color: 'warning', className: 'text-content-warning-on-strong' },
376
- { variant: 'solid', color: 'success', className: 'text-content-success-on-strong' },
377
- { variant: 'solid', color: 'info', className: 'text-content-info-on-strong' },
382
+ compoundVariants: [
383
+ // ── solid × color (filled foreground icon) ──────────────────────
384
+ { variant: 'solid', color: 'action', className: 'text-content-action-on-strong' },
385
+ { variant: 'solid', color: 'danger', className: 'text-content-danger-on-strong' },
386
+ { variant: 'solid', color: 'warning', className: 'text-content-warning-on-strong' },
387
+ { variant: 'solid', color: 'success', className: 'text-content-success-on-strong' },
388
+ { variant: 'solid', color: 'info', className: 'text-content-info-on-strong' },
378
389
 
379
- // ── outline/ghost × color (transparent foreground icon) ──────────
380
- { variant: ['outline', 'ghost'], color: 'action', className: 'text-content-action' },
381
- { variant: ['outline', 'ghost'], color: 'danger', className: 'text-content-danger' },
382
- { variant: ['outline', 'ghost'], color: 'warning', className: 'text-content-warning' },
383
- { variant: ['outline', 'ghost'], color: 'success', className: 'text-content-success' },
384
- { variant: ['outline', 'ghost'], color: 'info', className: 'text-content-info' },
385
- ],
386
- defaultVariants: {
387
- variant: 'solid',
388
- color: 'action',
389
- size: 'default',
390
+ // ── outline/ghost × color (transparent foreground icon) ──────────
391
+ { variant: ['outline', 'ghost'], color: 'action', className: 'text-content-action' },
392
+ { variant: ['outline', 'ghost'], color: 'danger', className: 'text-content-danger' },
393
+ { variant: ['outline', 'ghost'], color: 'warning', className: 'text-content-warning' },
394
+ { variant: ['outline', 'ghost'], color: 'success', className: 'text-content-success' },
395
+ { variant: ['outline', 'ghost'], color: 'info', className: 'text-content-info' },
396
+ ],
397
+ defaultVariants: {
398
+ variant: 'solid',
399
+ color: 'action',
400
+ size: 'default',
401
+ },
390
402
  },
391
- });
403
+ );
392
404
 
393
405
  export type ButtonVariantProps = VariantProps<typeof buttonRootVariants>;
394
406
  export type ButtonGroupVariantProps = VariantProps<typeof buttonGroupVariants>;
@@ -13,17 +13,117 @@ Styled components in `@cdx-ui/components` follow a consistent pattern. Read the
13
13
  - `docs/internal/practices/types.md` — prop types, `I*Props` vs `*Props`, exporting
14
14
  - `docs/internal/practices/styling.md` — `cn()`, CVA, token classes, `Platform.select()`
15
15
  - `docs/internal/practices/state.md` — `useControllableState`, `composeEventHandlers`
16
- - `docs/internal/practices/data-attributes.md` — `data-[state]` and `data-slot`
16
+ - `docs/internal/practices/data-attributes.md` — `data-[state]`, `data-slot`, and `data-icon` conventions
17
+ - `docs/internal/practices/as-child.md` — styled-layer `asChild` via re-exported `Slot`
18
+
19
+ ## Six standards (Standards 4–6 apply here)
20
+
21
+ Standards 1–3 govern primitive-layer authoring (see `packages/primitives/src/CLAUDE.md`).
22
+ Standards 4–6 govern the styled layer:
23
+
24
+ ### Standard 4 — `data-slot` attributes
25
+
26
+ Every public styled slot emits a stable `data-slot="<component>-<part>"` attribute on its outermost rendered element. The convention is lowercase, kebab-case, `<component>` then `-<slot>` if not the root.
27
+
28
+ | Slot | Attribute |
29
+ | ------------------ | ---------------------------- |
30
+ | `<Button>` root | `data-slot="button"` |
31
+ | `<Button.Label>` | `data-slot="button-label"` |
32
+ | `<Button.Icon>` | `data-slot="button-icon"` |
33
+ | `<Button.Spinner>` | `data-slot="button-spinner"` |
34
+ | `<Button.Group>` | `data-slot="button-group"` |
35
+ | `<IconButton>` | `data-slot="icon-button"` |
36
+ | `<Dialog.Content>` | `data-slot="dialog-content"` |
37
+
38
+ Use `dataAttributes({ slot: '<component>-<part>' })` from `@cdx-ui/primitives` so the helper handles native (flat `data-*` keys) and web (RNW `dataSet`) correctly.
39
+
40
+ #### Combining multiple data-attribute sources — use `mergeDataAttributes`
41
+
42
+ When the same element receives `data-*` from more than one source (e.g. a slot identifier plus a position hint, or consumer-supplied data attrs flowing in through props), do **not** chain multiple `{...dataAttributes(...)}` spreads — on web each call returns `{ dataSet: {...} }` and a plain JSX spread replaces the `dataSet` key entirely, so the second source wipes out the first. Use `mergeDataAttributes(...)` from `@cdx-ui/primitives` to combine sources; later args win on key conflicts:
43
+
44
+ ```tsx
45
+ import { dataAttributes, mergeDataAttributes } from '@cdx-ui/primitives';
46
+
47
+ <Element
48
+ ref={ref}
49
+ className={...}
50
+ style={style}
51
+ {...props}
52
+ {...mergeDataAttributes(
53
+ props, // pulls in any consumer-supplied data attrs
54
+ dataAttributes({ slot: '<component>-<part>' }),
55
+ positionToDataIcon(position), // optional, may be undefined
56
+ )}
57
+ />
58
+ ```
59
+
60
+ Spread `{...props}` first, then the merge — so any keys that `mergeDataAttributes` produces overwrite their counterparts in props with the merged values.
61
+
62
+ When the styled component renders a primitive Root, the primitive Root applies the same pattern internally — incoming data attrs (slot from the styled layer) are merged with the primitive's own interaction-state data attrs and emitted once. The styled layer doesn't need to do anything special; just pass `data-slot` through and the primitive's merge does the rest.
63
+
64
+ ### Standard 5 — Static design variants vs. interaction state
65
+
66
+ Variant functions take only static design inputs (`variant`/`color`/`size`/`fullWidth`/etc.). Interaction states (`hover`/`focus`/`active`/`disabled`/`focus-visible`) are expressed as `data-[...]:` selectors **inside** CVA variant strings, never as variant inputs.
67
+
68
+ - CSS-layer state changes are free of React work: when the primitive flips `data-hover=true`, the CSS engine (web) and Uniwind's native style layer reconcile styles without re-rendering React, re-running CVA, or invoking `cn()`/`tailwind-merge`.
69
+ - Variant-input state changes cascade React work: CVA re-runs, `cn()` re-merges, and className-prop reconciliation cascades through the tree.
70
+ - Sub-slots can react to root state via parent selectors (`data-[hover=true]:`) without each slot subscribing to context (per Standard 1).
71
+
72
+ ### Standard 6 — Position hints (`data-icon="inline-start|inline-end"`)
73
+
74
+ Icon-/spinner-bearing styled slots accept an optional `position?: 'leading' | 'trailing'` prop that emits `data-icon="inline-start|inline-end"`. Spacing rules in CVA target the `data-icon` attribute via logical-property utilities (`me-*`/`ms-*`) so RTL is handled for free.
75
+
76
+ Mapping (via the shared `positionToDataIcon` helper at `packages/components/src/utils/positionToDataIcon.ts`):
77
+
78
+ - `position="leading"` → `data-icon="inline-start"`
79
+ - `position="trailing"` → `data-icon="inline-end"`
80
+ - `position` omitted → no `data-icon` attribute, baseline spacing applies
81
+
82
+ ```tsx
83
+ import { positionToDataIcon, type IconSlotPosition } from '../../utils/positionToDataIcon';
84
+
85
+ interface ButtonIconProps {
86
+ position?: IconSlotPosition;
87
+ // ...
88
+ }
89
+
90
+ <Icon {...positionToDataIcon(position)} {...props} />;
91
+ ```
92
+
93
+ ```tsx
94
+ <Button>
95
+ <Button.Icon as={CheckIcon} position="leading" />
96
+ <Button.Label>Confirm</Button.Label>
97
+ </Button>
98
+ ```
99
+
100
+ CVA expresses padding via logical-property selectors:
101
+
102
+ ```ts
103
+ cva(['data-[icon=inline-start]:me-1.5 data-[icon=inline-end]:ms-1.5'], { ... });
104
+ ```
105
+
106
+ Where: **styled layer.** Pure styling hint with no behavior or a11y semantics — fails Standard 3, so no primitive involvement.
107
+
108
+ ## Canonical reference
109
+
110
+ `packages/components/src/components/Button/index.tsx` + `Button/styles.ts` is the canonical example for Standards 4–6:
111
+
112
+ - `data-slot` on `Button`, `Button.Label`, `Button.Icon`, `Button.Spinner`, `Button.Group`
113
+ - `asChild` re-uses `Slot` from `@cdx-ui/primitives` — no new primitive needed for sub-slots
114
+ - `buttonIconVariants` / `buttonSpinnerVariants` carry `data-[icon=inline-start]:me-1.5 data-[icon=inline-end]:ms-1.5` so leading/trailing padding works without per-call overrides
115
+
116
+ `packages/components/src/components/IconButton/index.tsx` shows the second axis of Standard 1: a styled-layer slot (the glyph) is the place that opts into `useButtonContext()` when its styling reacts to interaction state. The pattern is the same as the primitive Root — call `useButtonContext()`, spread `dataAttributes({ ... })` — only it lives in the styled layer.
17
117
 
18
118
  ### Pattern summary
19
119
 
20
- Each component follows this structure (see `Button/index.tsx` as the canonical example):
120
+ Each component follows this structure (see `Button/index.tsx`):
21
121
 
22
- 1. **Imports** — RN base components, primitive from `@cdx-ui/primitives`, `cn` + style context from `@cdx-ui/utils`, variant functions from `./styles`
23
- 2. **Scope constant** — `const SCOPE = 'COMPONENT_NAME'` for `withStyleContext` / `useStyleContext`
24
- 3. **Root wrapping** — `const Root = withStyleContext(BaseRNComponent, SCOPE)`
25
- 4. **Primitive instantiation** — `const Primitive = createComponent({ Root, Text, ... })` using the primitive factory
26
- 5. **Styled sub-components** — each wraps a single element, uses `forwardRef`, destructures variant props with defaults, computes `cn(variantFn({ ...variants }), className)`, passes `context` to the primitive root
122
+ 1. **Imports** — RN base components, primitive `Root` and `Slot` from `@cdx-ui/primitives`, `cn` + style context from `@cdx-ui/utils`, variant functions from `./styles`
123
+ 2. **Scope constant** — `const SCOPE = 'COMPONENT_NAME'` for `withStyleContext` / `useStyleContext` (or a `ParentContext.Provider` wrapper for static variant context)
124
+ 3. **Root component** — renders the primitive Root (or a host element directly when no primitive exists), spreads `dataAttributes({ slot: '<component>' })`
125
+ 4. **Sub-slots** — each accepts `asChild`, renders a host element via `const Comp = asChild ? Slot : DefaultElement`, spreads `dataAttributes({ slot: '<component>-<slot>' })`. Sub-slots **do not** call `useButtonContext()` (or its equivalent) unless their own styling reacts to interaction state.
126
+ 5. **Position-aware sub-slots** — icon/spinner-style slots accept `position?: 'leading' | 'trailing'` and emit `data-icon` via `dataAttributes({ icon: '...' })`.
27
127
  6. **Compound export** — `Object.assign(RootComponent, { Sub1, Sub2 }) as CompoundType`
28
128
 
29
129
  ### Key rules
@@ -36,6 +136,9 @@ Each component follows this structure (see `Button/index.tsx` as the canonical e
36
136
  - Never hardcode colors — use semantic token classes (`bg-surface-*`, `text-content-*`, `border-stroke-*`)
37
137
  - Event handlers composed via `composeEventHandlers` from `@cdx-ui/utils`
38
138
  - Third-party libraries fully abstracted — no adopted library types leak to consumers
139
+ - **`data-slot`** emitted on every styled slot via `dataAttributes({ slot: '<component>-<part>' })`
140
+ - **`asChild`** supported on every styled slot — `const Comp = asChild ? Slot : DefaultElement`
141
+ - **`position`** prop on icon/spinner-style slots maps to `data-icon="inline-start|inline-end"`
39
142
 
40
143
  ---
41
144
 
@@ -44,7 +147,7 @@ Each component follows this structure (see `Button/index.tsx` as the canonical e
44
147
  Variant definitions live in co-located `styles.ts` files using CVA. Read the full guides before making changes:
45
148
 
46
149
  - `docs/internal/practices/styling.md` — `cn()`, CVA, token architecture, dark mode, `Platform.select()`
47
- - `docs/internal/practices/data-attributes.md` — `data-[state]` selectors for interaction states
150
+ - `docs/internal/practices/data-attributes.md` — `data-[state]` and `data-icon` selectors
48
151
 
49
152
  ### Pattern summary
50
153
 
@@ -57,11 +160,11 @@ import { DISABLED_CURSOR, TRANSITION_COLORS } from '../../styles/primitives';
57
160
 
58
161
  export const componentRootVariants = cva(
59
162
  [
60
- /* base classes */
163
+ /* base classes + interaction state selectors (data-[hover=true]:..., data-[active=true]:...) */
61
164
  ],
62
165
  {
63
166
  variants: {
64
- /* variant axes */
167
+ /* static design axes only — variant, color, size, fullWidth, etc. */
65
168
  },
66
169
  compoundVariants: [
67
170
  /* variant × variant combinations */
@@ -78,7 +181,8 @@ export type ComponentVariantProps = VariantProps<typeof componentRootVariants>;
78
181
  ### Key rules
79
182
 
80
183
  - **Semantic token classes only** — use `bg-surface-action-strong`, `text-content-primary`, `border-stroke-primary`, etc. Never hardcode hex colors, `bg-white`, `text-black`, or raw Tailwind color scales for themed values.
81
- - **`data-[state]` selectors** — use `data-[disabled=true]:`, `data-[hover=true]:`, `data-[active=true]:`, `data-[focus-visible=true]:` for interaction states. Primitives emit these data attributes.
184
+ - **`data-[state]` selectors** — use `data-[disabled=true]:`, `data-[hover=true]:`, `data-[active=true]:`, `data-[focus-visible=true]:` for interaction states. Primitives emit these data attributes (Standard 1). **Interaction state is never a CVA variant input** (Standard 5).
185
+ - **`data-[icon=inline-start|inline-end]` selectors** for icon/spinner position spacing (Standard 6) — pair with logical-property utilities (`me-*`/`ms-*`) so RTL flips automatically.
82
186
  - **`Platform.select()`** for platform-specific classes — web gets `data-[hover=true]` styles (hover exists on web); native responds only to `data-[active=true]` (press). Always set `default` (native) and `web` keys.
83
187
  - **Shared style primitives** — import `DISABLED_CURSOR`, `TRANSITION_COLORS`, etc. from `../../styles/primitives` for cross-component consistency.
84
188
  - **`web:` prefix** for web-only utilities — e.g., `web:outline-none`, `web:focus-visible:ring-2`.
@@ -1,7 +1,15 @@
1
1
  import { forwardRef, type ReactNode } from 'react';
2
- import { Pressable, Text, type TextProps, View, type ViewProps } from 'react-native';
2
+ import { Text, View, type TextProps, type ViewProps } from 'react-native';
3
3
  import { Check, CheckIndeterminateSmall } from '@cdx-ui/icons';
4
- import { createCheckbox, type ICheckboxProps } from '@cdx-ui/primitives';
4
+ import {
5
+ CheckboxRoot as CheckboxRootPrimitive,
6
+ CheckboxGroup as CheckboxGroupPrimitive,
7
+ Slot,
8
+ dataAttributes,
9
+ mergeDataAttributes,
10
+ useCheckboxContext,
11
+ type ICheckboxProps,
12
+ } from '@cdx-ui/primitives';
5
13
  import { cn } from '@cdx-ui/utils';
6
14
  import { Icon, IconProps } from '../Icon';
7
15
  import {
@@ -12,14 +20,6 @@ import {
12
20
  checkboxRootVariants,
13
21
  } from './styles';
14
22
 
15
- const CheckboxPrimitive = createCheckbox({
16
- Root: Pressable,
17
- Indicator: View,
18
- Icon: View,
19
- Label: Text,
20
- Group: View,
21
- });
22
-
23
23
  // =============================================================================
24
24
  // CHECKBOX ROOT
25
25
  // =============================================================================
@@ -34,20 +34,19 @@ const CheckboxRoot = forwardRef<View, CheckboxProps>(
34
34
  const computedClassName = cn(checkboxRootVariants(), className);
35
35
 
36
36
  return (
37
- <CheckboxPrimitive
37
+ <CheckboxRootPrimitive
38
38
  ref={ref as any}
39
39
  className={computedClassName}
40
40
  isIndeterminate={isIndeterminate}
41
41
  style={style}
42
42
  {...props}
43
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'checkbox' }))}
43
44
  >
44
45
  <CheckboxIndicator>
45
- <CheckboxPrimitive.Icon>
46
- <CheckboxIcon as={isIndeterminate ? CheckIndeterminateSmall : Check} />
47
- </CheckboxPrimitive.Icon>
46
+ <CheckboxIconSlot isIndeterminate={isIndeterminate} />
48
47
  </CheckboxIndicator>
49
48
  {children}
50
- </CheckboxPrimitive>
49
+ </CheckboxRootPrimitive>
51
50
  );
52
51
  },
53
52
  );
@@ -55,27 +54,56 @@ const CheckboxRoot = forwardRef<View, CheckboxProps>(
55
54
  CheckboxRoot.displayName = 'Checkbox';
56
55
 
57
56
  // =============================================================================
58
- // CHECKBOX INDICATOR
57
+ // CHECKBOX INDICATOR (styled-only, reads context for data-* styling)
59
58
  // =============================================================================
60
59
 
61
60
  export interface CheckboxIndicatorProps extends ViewProps {
61
+ asChild?: boolean;
62
62
  className?: string;
63
63
  children?: ReactNode;
64
64
  }
65
65
 
66
66
  const CheckboxIndicator = forwardRef<View, CheckboxIndicatorProps>(
67
- ({ className, children, style, ...props }, ref) => {
67
+ ({ asChild, className, children, style, ...props }, ref) => {
68
+ const {
69
+ isChecked,
70
+ isDisabled,
71
+ isHovered,
72
+ isInvalid,
73
+ isReadOnly,
74
+ isPressed,
75
+ isFocused,
76
+ isIndeterminate,
77
+ isFocusVisible,
78
+ } = useCheckboxContext();
79
+
68
80
  const computedClassName = cn(checkboxIndicatorVariants(), className);
81
+ const Comp = asChild ? Slot : View;
69
82
 
70
83
  return (
71
- <CheckboxPrimitive.Indicator
72
- ref={ref as any}
84
+ <Comp
85
+ ref={ref}
73
86
  className={computedClassName}
74
87
  style={style}
75
88
  {...props}
89
+ {...mergeDataAttributes(
90
+ props,
91
+ dataAttributes({
92
+ slot: 'checkbox-indicator',
93
+ hover: isHovered,
94
+ checked: isChecked,
95
+ disabled: isDisabled,
96
+ focusVisible: isFocusVisible,
97
+ invalid: isInvalid,
98
+ readonly: isReadOnly,
99
+ active: isPressed,
100
+ focused: isFocused,
101
+ indeterminate: isIndeterminate,
102
+ }),
103
+ )}
76
104
  >
77
105
  {children}
78
- </CheckboxPrimitive.Indicator>
106
+ </Comp>
79
107
  );
80
108
  },
81
109
  );
@@ -83,7 +111,7 @@ const CheckboxIndicator = forwardRef<View, CheckboxIndicatorProps>(
83
111
  CheckboxIndicator.displayName = 'Checkbox.Indicator';
84
112
 
85
113
  // =============================================================================
86
- // CHECKBOX ICON
114
+ // CHECKBOX ICON (styled-only, reads context for conditional mount)
87
115
  // =============================================================================
88
116
 
89
117
  export interface CheckboxIconProps extends Omit<IconProps, 'children'> {}
@@ -91,33 +119,66 @@ export interface CheckboxIconProps extends Omit<IconProps, 'children'> {}
91
119
  const CheckboxIcon = ({ className, style, ...props }: CheckboxIconProps) => {
92
120
  const computedClassName = cn(checkboxIconVariants(), className);
93
121
 
94
- return <Icon className={computedClassName} style={style} {...props} />;
122
+ return (
123
+ <Icon
124
+ className={computedClassName}
125
+ style={style}
126
+ {...props}
127
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'checkbox-icon' }))}
128
+ />
129
+ );
95
130
  };
96
131
 
97
132
  CheckboxIcon.displayName = 'Checkbox.Icon';
98
133
 
134
+ // Internal helper that reads context for conditional rendering
135
+ const CheckboxIconSlot = ({
136
+ isIndeterminate: isIndeterminateProp,
137
+ }: {
138
+ isIndeterminate?: boolean;
139
+ }) => {
140
+ const { isChecked, isIndeterminate } = useCheckboxContext();
141
+ const showIcon = isChecked || isIndeterminateProp || isIndeterminate;
142
+
143
+ if (!showIcon) return null;
144
+
145
+ return (
146
+ <CheckboxIcon as={isIndeterminate || isIndeterminateProp ? CheckIndeterminateSmall : Check} />
147
+ );
148
+ };
149
+
99
150
  // =============================================================================
100
- // CHECKBOX LABEL
151
+ // CHECKBOX LABEL (styled-only)
101
152
  // =============================================================================
102
153
 
103
154
  export interface CheckboxLabelProps extends TextProps {
155
+ asChild?: boolean;
104
156
  className?: string;
105
157
  children?: ReactNode;
106
158
  }
107
159
 
108
160
  const CheckboxLabel = forwardRef<Text, CheckboxLabelProps>(
109
- ({ className, children, style, ...props }, ref) => {
161
+ ({ asChild, className, children, style, ...props }, ref) => {
162
+ const { isDisabled } = useCheckboxContext();
110
163
  const computedClassName = cn(checkboxLabelVariants(), className);
164
+ const Comp = asChild ? Slot : Text;
111
165
 
112
166
  return (
113
- <CheckboxPrimitive.Label
114
- ref={ref as any}
167
+ <Comp
168
+ ref={ref}
115
169
  className={computedClassName}
116
170
  style={style}
117
171
  {...props}
172
+ {...mergeDataAttributes(
173
+ props,
174
+ dataAttributes({
175
+ slot: 'checkbox-label',
176
+ disabled: isDisabled,
177
+ }),
178
+ )}
118
179
  >
119
180
  {children}
120
- </CheckboxPrimitive.Label>
181
+ </Comp>
121
182
  );
122
183
  },
123
184
  );
@@ -125,7 +186,7 @@ const CheckboxLabel = forwardRef<Text, CheckboxLabelProps>(
125
186
  CheckboxLabel.displayName = 'Checkbox.Label';
126
187
 
127
188
  // =============================================================================
128
- // CHECKBOX GROUP
189
+ // CHECKBOX GROUP (wraps the primitive)
129
190
  // =============================================================================
130
191
 
131
192
  export interface CheckboxGroupProps extends ViewProps {
@@ -158,8 +219,8 @@ const CheckboxGroup = forwardRef<View, CheckboxGroupProps>(
158
219
  const computedClassName = cn(checkboxGroupVariants({ direction }), className);
159
220
 
160
221
  return (
161
- <CheckboxPrimitive.Group
162
- ref={ref as any}
222
+ <CheckboxGroupPrimitive
223
+ ref={ref}
163
224
  className={computedClassName}
164
225
  style={style}
165
226
  value={value ?? []}
@@ -167,10 +228,10 @@ const CheckboxGroup = forwardRef<View, CheckboxGroupProps>(
167
228
  isDisabled={isDisabled}
168
229
  isInvalid={isInvalid}
169
230
  isReadOnly={isReadOnly}
170
- {...viewProps}
231
+ {...mergeDataAttributes(viewProps, dataAttributes({ slot: 'checkbox-group' }))}
171
232
  >
172
233
  {children}
173
- </CheckboxPrimitive.Group>
234
+ </CheckboxGroupPrimitive>
174
235
  );
175
236
  },
176
237
  );
@@ -62,7 +62,7 @@ export const checkboxIndicatorVariants = cva([
62
62
  'web:active:data-[hover=true]:data-[indeterminate=true]:data-[invalid=true]:border-[--color-surface-danger-strong-active]',
63
63
  // Focus ring
64
64
  'web:data-[focus-visible=true]:outline-none',
65
- 'web:data-[focus-visible=true]:ring-2 web:data-[focus-visible=true]:ring-[--color-stroke-ring] web:data-[focus-visible=true]:ring-offset-2',
65
+ 'web:data-[focus-visible=true]:ring-2 web:data-[focus-visible=true]:ring-stroke-focus web:data-[focus-visible=true]:ring-offset-2',
66
66
  ].join(' '),
67
67
  default: [
68
68
  // Invalid bg is NOT styled on native. Compound data-[] selectors cannot express