@cdx-ui/primitives 0.0.1-beta.2 → 0.0.1-beta.21

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 (226) hide show
  1. package/README.md +38 -24
  2. package/lib/commonjs/index.js +36 -0
  3. package/lib/commonjs/index.js.map +1 -1
  4. package/lib/commonjs/list-item/context.js +11 -0
  5. package/lib/commonjs/list-item/context.js.map +1 -0
  6. package/lib/commonjs/list-item/createListItemContent.js +30 -0
  7. package/lib/commonjs/list-item/createListItemContent.js.map +1 -0
  8. package/lib/commonjs/list-item/createListItemDescription.js +25 -0
  9. package/lib/commonjs/list-item/createListItemDescription.js.map +1 -0
  10. package/lib/commonjs/list-item/createListItemLeadingSlot.js +34 -0
  11. package/lib/commonjs/list-item/createListItemLeadingSlot.js.map +1 -0
  12. package/lib/commonjs/list-item/createListItemMeta.js +25 -0
  13. package/lib/commonjs/list-item/createListItemMeta.js.map +1 -0
  14. package/lib/commonjs/list-item/createListItemRoot.js +157 -0
  15. package/lib/commonjs/list-item/createListItemRoot.js.map +1 -0
  16. package/lib/commonjs/list-item/createListItemSectionHeader.js +54 -0
  17. package/lib/commonjs/list-item/createListItemSectionHeader.js.map +1 -0
  18. package/lib/commonjs/list-item/createListItemTitle.js +25 -0
  19. package/lib/commonjs/list-item/createListItemTitle.js.map +1 -0
  20. package/lib/commonjs/list-item/createListItemTrailingSlot.js +28 -0
  21. package/lib/commonjs/list-item/createListItemTrailingSlot.js.map +1 -0
  22. package/lib/commonjs/list-item/index.js +55 -0
  23. package/lib/commonjs/list-item/index.js.map +1 -0
  24. package/lib/commonjs/list-item/types.js +6 -0
  25. package/lib/commonjs/list-item/types.js.map +1 -0
  26. package/lib/commonjs/radio/context.js +14 -0
  27. package/lib/commonjs/radio/context.js.map +1 -0
  28. package/lib/commonjs/radio/createRadioGroup.js +66 -0
  29. package/lib/commonjs/radio/createRadioGroup.js.map +1 -0
  30. package/lib/commonjs/radio/createRadioIndicator.js +43 -0
  31. package/lib/commonjs/radio/createRadioIndicator.js.map +1 -0
  32. package/lib/commonjs/radio/createRadioLabel.js +38 -0
  33. package/lib/commonjs/radio/createRadioLabel.js.map +1 -0
  34. package/lib/commonjs/radio/createRadioRoot.js +95 -0
  35. package/lib/commonjs/radio/createRadioRoot.js.map +1 -0
  36. package/lib/commonjs/radio/createRadioRoot.web.js +87 -0
  37. package/lib/commonjs/radio/createRadioRoot.web.js.map +1 -0
  38. package/lib/commonjs/radio/index.js +26 -0
  39. package/lib/commonjs/radio/index.js.map +1 -0
  40. package/lib/commonjs/radio/types.js +6 -0
  41. package/lib/commonjs/radio/types.js.map +1 -0
  42. package/lib/commonjs/radio/useRadioRoot.js +64 -0
  43. package/lib/commonjs/radio/useRadioRoot.js.map +1 -0
  44. package/lib/commonjs/tile/context.js +30 -0
  45. package/lib/commonjs/tile/context.js.map +1 -0
  46. package/lib/commonjs/tile/createTileContent.js +30 -0
  47. package/lib/commonjs/tile/createTileContent.js.map +1 -0
  48. package/lib/commonjs/tile/createTileDescription.js +28 -0
  49. package/lib/commonjs/tile/createTileDescription.js.map +1 -0
  50. package/lib/commonjs/tile/createTileGroup.js +112 -0
  51. package/lib/commonjs/tile/createTileGroup.js.map +1 -0
  52. package/lib/commonjs/tile/createTileIndicator.js +46 -0
  53. package/lib/commonjs/tile/createTileIndicator.js.map +1 -0
  54. package/lib/commonjs/tile/createTileLeadingSlot.js +34 -0
  55. package/lib/commonjs/tile/createTileLeadingSlot.js.map +1 -0
  56. package/lib/commonjs/tile/createTileRoot.js +133 -0
  57. package/lib/commonjs/tile/createTileRoot.js.map +1 -0
  58. package/lib/commonjs/tile/createTileTitle.js +28 -0
  59. package/lib/commonjs/tile/createTileTitle.js.map +1 -0
  60. package/lib/commonjs/tile/createTileTrailingSlot.js +35 -0
  61. package/lib/commonjs/tile/createTileTrailingSlot.js.map +1 -0
  62. package/lib/commonjs/tile/index.js +55 -0
  63. package/lib/commonjs/tile/index.js.map +1 -0
  64. package/lib/commonjs/tile/types.js +6 -0
  65. package/lib/commonjs/tile/types.js.map +1 -0
  66. package/lib/module/index.js +3 -0
  67. package/lib/module/index.js.map +1 -1
  68. package/lib/module/list-item/context.js +5 -0
  69. package/lib/module/list-item/context.js.map +1 -0
  70. package/lib/module/list-item/createListItemContent.js +24 -0
  71. package/lib/module/list-item/createListItemContent.js.map +1 -0
  72. package/lib/module/list-item/createListItemDescription.js +19 -0
  73. package/lib/module/list-item/createListItemDescription.js.map +1 -0
  74. package/lib/module/list-item/createListItemLeadingSlot.js +28 -0
  75. package/lib/module/list-item/createListItemLeadingSlot.js.map +1 -0
  76. package/lib/module/list-item/createListItemMeta.js +19 -0
  77. package/lib/module/list-item/createListItemMeta.js.map +1 -0
  78. package/lib/module/list-item/createListItemRoot.js +151 -0
  79. package/lib/module/list-item/createListItemRoot.js.map +1 -0
  80. package/lib/module/list-item/createListItemSectionHeader.js +48 -0
  81. package/lib/module/list-item/createListItemSectionHeader.js.map +1 -0
  82. package/lib/module/list-item/createListItemTitle.js +19 -0
  83. package/lib/module/list-item/createListItemTitle.js.map +1 -0
  84. package/lib/module/list-item/createListItemTrailingSlot.js +22 -0
  85. package/lib/module/list-item/createListItemTrailingSlot.js.map +1 -0
  86. package/lib/module/list-item/index.js +39 -0
  87. package/lib/module/list-item/index.js.map +1 -0
  88. package/lib/module/list-item/types.js +4 -0
  89. package/lib/module/list-item/types.js.map +1 -0
  90. package/lib/module/radio/context.js +7 -0
  91. package/lib/module/radio/context.js.map +1 -0
  92. package/lib/module/radio/createRadioGroup.js +61 -0
  93. package/lib/module/radio/createRadioGroup.js.map +1 -0
  94. package/lib/module/radio/createRadioIndicator.js +38 -0
  95. package/lib/module/radio/createRadioIndicator.js.map +1 -0
  96. package/lib/module/radio/createRadioLabel.js +33 -0
  97. package/lib/module/radio/createRadioLabel.js.map +1 -0
  98. package/lib/module/radio/createRadioRoot.js +90 -0
  99. package/lib/module/radio/createRadioRoot.js.map +1 -0
  100. package/lib/module/radio/createRadioRoot.web.js +82 -0
  101. package/lib/module/radio/createRadioRoot.web.js.map +1 -0
  102. package/lib/module/radio/index.js +22 -0
  103. package/lib/module/radio/index.js.map +1 -0
  104. package/lib/module/radio/types.js +4 -0
  105. package/lib/module/radio/types.js.map +1 -0
  106. package/lib/module/radio/useRadioRoot.js +60 -0
  107. package/lib/module/radio/useRadioRoot.js.map +1 -0
  108. package/lib/module/tile/context.js +21 -0
  109. package/lib/module/tile/context.js.map +1 -0
  110. package/lib/module/tile/createTileContent.js +24 -0
  111. package/lib/module/tile/createTileContent.js.map +1 -0
  112. package/lib/module/tile/createTileDescription.js +22 -0
  113. package/lib/module/tile/createTileDescription.js.map +1 -0
  114. package/lib/module/tile/createTileGroup.js +106 -0
  115. package/lib/module/tile/createTileGroup.js.map +1 -0
  116. package/lib/module/tile/createTileIndicator.js +40 -0
  117. package/lib/module/tile/createTileIndicator.js.map +1 -0
  118. package/lib/module/tile/createTileLeadingSlot.js +28 -0
  119. package/lib/module/tile/createTileLeadingSlot.js.map +1 -0
  120. package/lib/module/tile/createTileRoot.js +127 -0
  121. package/lib/module/tile/createTileRoot.js.map +1 -0
  122. package/lib/module/tile/createTileTitle.js +22 -0
  123. package/lib/module/tile/createTileTitle.js.map +1 -0
  124. package/lib/module/tile/createTileTrailingSlot.js +29 -0
  125. package/lib/module/tile/createTileTrailingSlot.js.map +1 -0
  126. package/lib/module/tile/index.js +39 -0
  127. package/lib/module/tile/index.js.map +1 -0
  128. package/lib/module/tile/types.js +4 -0
  129. package/lib/module/tile/types.js.map +1 -0
  130. package/lib/typescript/index.d.ts +3 -0
  131. package/lib/typescript/index.d.ts.map +1 -1
  132. package/lib/typescript/list-item/context.d.ts +6 -0
  133. package/lib/typescript/list-item/context.d.ts.map +1 -0
  134. package/lib/typescript/list-item/createListItemContent.d.ts +3 -0
  135. package/lib/typescript/list-item/createListItemContent.d.ts.map +1 -0
  136. package/lib/typescript/list-item/createListItemDescription.d.ts +3 -0
  137. package/lib/typescript/list-item/createListItemDescription.d.ts.map +1 -0
  138. package/lib/typescript/list-item/createListItemLeadingSlot.d.ts +4 -0
  139. package/lib/typescript/list-item/createListItemLeadingSlot.d.ts.map +1 -0
  140. package/lib/typescript/list-item/createListItemMeta.d.ts +3 -0
  141. package/lib/typescript/list-item/createListItemMeta.d.ts.map +1 -0
  142. package/lib/typescript/list-item/createListItemRoot.d.ts +4 -0
  143. package/lib/typescript/list-item/createListItemRoot.d.ts.map +1 -0
  144. package/lib/typescript/list-item/createListItemSectionHeader.d.ts +4 -0
  145. package/lib/typescript/list-item/createListItemSectionHeader.d.ts.map +1 -0
  146. package/lib/typescript/list-item/createListItemTitle.d.ts +3 -0
  147. package/lib/typescript/list-item/createListItemTitle.d.ts.map +1 -0
  148. package/lib/typescript/list-item/createListItemTrailingSlot.d.ts +3 -0
  149. package/lib/typescript/list-item/createListItemTrailingSlot.d.ts.map +1 -0
  150. package/lib/typescript/list-item/index.d.ts +16 -0
  151. package/lib/typescript/list-item/index.d.ts.map +1 -0
  152. package/lib/typescript/list-item/types.d.ts +86 -0
  153. package/lib/typescript/list-item/types.d.ts.map +1 -0
  154. package/lib/typescript/radio/context.d.ts +21 -0
  155. package/lib/typescript/radio/context.d.ts.map +1 -0
  156. package/lib/typescript/radio/createRadioGroup.d.ts +3 -0
  157. package/lib/typescript/radio/createRadioGroup.d.ts.map +1 -0
  158. package/lib/typescript/radio/createRadioIndicator.d.ts +5 -0
  159. package/lib/typescript/radio/createRadioIndicator.d.ts.map +1 -0
  160. package/lib/typescript/radio/createRadioLabel.d.ts +5 -0
  161. package/lib/typescript/radio/createRadioLabel.d.ts.map +1 -0
  162. package/lib/typescript/radio/createRadioRoot.d.ts +3 -0
  163. package/lib/typescript/radio/createRadioRoot.d.ts.map +1 -0
  164. package/lib/typescript/radio/createRadioRoot.web.d.ts +3 -0
  165. package/lib/typescript/radio/createRadioRoot.web.d.ts.map +1 -0
  166. package/lib/typescript/radio/index.d.ts +10 -0
  167. package/lib/typescript/radio/index.d.ts.map +1 -0
  168. package/lib/typescript/radio/types.d.ts +54 -0
  169. package/lib/typescript/radio/types.d.ts.map +1 -0
  170. package/lib/typescript/radio/useRadioRoot.d.ts +149 -0
  171. package/lib/typescript/radio/useRadioRoot.d.ts.map +1 -0
  172. package/lib/typescript/tile/context.d.ts +9 -0
  173. package/lib/typescript/tile/context.d.ts.map +1 -0
  174. package/lib/typescript/tile/createTileContent.d.ts +3 -0
  175. package/lib/typescript/tile/createTileContent.d.ts.map +1 -0
  176. package/lib/typescript/tile/createTileDescription.d.ts +3 -0
  177. package/lib/typescript/tile/createTileDescription.d.ts.map +1 -0
  178. package/lib/typescript/tile/createTileGroup.d.ts +4 -0
  179. package/lib/typescript/tile/createTileGroup.d.ts.map +1 -0
  180. package/lib/typescript/tile/createTileIndicator.d.ts +4 -0
  181. package/lib/typescript/tile/createTileIndicator.d.ts.map +1 -0
  182. package/lib/typescript/tile/createTileLeadingSlot.d.ts +4 -0
  183. package/lib/typescript/tile/createTileLeadingSlot.d.ts.map +1 -0
  184. package/lib/typescript/tile/createTileRoot.d.ts +4 -0
  185. package/lib/typescript/tile/createTileRoot.d.ts.map +1 -0
  186. package/lib/typescript/tile/createTileTitle.d.ts +3 -0
  187. package/lib/typescript/tile/createTileTitle.d.ts.map +1 -0
  188. package/lib/typescript/tile/createTileTrailingSlot.d.ts +9 -0
  189. package/lib/typescript/tile/createTileTrailingSlot.d.ts.map +1 -0
  190. package/lib/typescript/tile/index.d.ts +15 -0
  191. package/lib/typescript/tile/index.d.ts.map +1 -0
  192. package/lib/typescript/tile/types.d.ts +119 -0
  193. package/lib/typescript/tile/types.d.ts.map +1 -0
  194. package/package.json +5 -2
  195. package/src/index.ts +3 -0
  196. package/src/list-item/context.tsx +5 -0
  197. package/src/list-item/createListItemContent.tsx +23 -0
  198. package/src/list-item/createListItemDescription.tsx +19 -0
  199. package/src/list-item/createListItemLeadingSlot.tsx +30 -0
  200. package/src/list-item/createListItemMeta.tsx +17 -0
  201. package/src/list-item/createListItemRoot.tsx +178 -0
  202. package/src/list-item/createListItemSectionHeader.tsx +53 -0
  203. package/src/list-item/createListItemTitle.tsx +17 -0
  204. package/src/list-item/createListItemTrailingSlot.tsx +21 -0
  205. package/src/list-item/index.ts +88 -0
  206. package/src/list-item/types.ts +122 -0
  207. package/src/radio/context.tsx +21 -0
  208. package/src/radio/createRadioGroup.tsx +67 -0
  209. package/src/radio/createRadioIndicator.tsx +32 -0
  210. package/src/radio/createRadioLabel.tsx +28 -0
  211. package/src/radio/createRadioRoot.tsx +100 -0
  212. package/src/radio/createRadioRoot.web.tsx +81 -0
  213. package/src/radio/index.ts +37 -0
  214. package/src/radio/types.ts +67 -0
  215. package/src/radio/useRadioRoot.ts +69 -0
  216. package/src/tile/context.tsx +23 -0
  217. package/src/tile/createTileContent.tsx +23 -0
  218. package/src/tile/createTileDescription.tsx +19 -0
  219. package/src/tile/createTileGroup.tsx +134 -0
  220. package/src/tile/createTileIndicator.tsx +38 -0
  221. package/src/tile/createTileLeadingSlot.tsx +30 -0
  222. package/src/tile/createTileRoot.tsx +124 -0
  223. package/src/tile/createTileTitle.tsx +19 -0
  224. package/src/tile/createTileTrailingSlot.tsx +25 -0
  225. package/src/tile/index.ts +88 -0
  226. package/src/tile/types.ts +153 -0
@@ -0,0 +1,178 @@
1
+ import React, { forwardRef, useMemo } from 'react';
2
+ import type { PressableProps } from 'react-native';
3
+ import { composeEventHandlers, mergeRefs } from '@cdx-ui/utils';
4
+ import { useHover, usePress } from '@react-native-aria/interactions';
5
+ import { dataAttributes } from '../utils/dataAttributes';
6
+ import { ListItemProvider } from './context';
7
+ import type { IListItemProps, IListItemPressablePassthrough } from './types';
8
+
9
+ function listItemRootDataAttrs(
10
+ size: IListItemProps['size'],
11
+ surface: IListItemProps['surface'],
12
+ showSeparator: boolean | undefined,
13
+ crossAlign: IListItemProps['crossAlign'],
14
+ ) {
15
+ return dataAttributes({
16
+ slot: 'list-item',
17
+ size: size ?? 'default',
18
+ surface: surface ?? 'default',
19
+ separator: showSeparator ?? true,
20
+ crossAlign: crossAlign ?? 'center',
21
+ });
22
+ }
23
+
24
+ function rowStyleForCrossAlign(crossAlign: IListItemProps['crossAlign']) {
25
+ return {
26
+ flexDirection: 'row' as const,
27
+ alignSelf: 'stretch' as const,
28
+ alignItems: crossAlign === 'start' ? ('flex-start' as const) : ('center' as const),
29
+ };
30
+ }
31
+
32
+ export const createListItemRoot = <V, P>(
33
+ BaseView: React.ComponentType<V>,
34
+ BasePressable: React.ComponentType<P>,
35
+ ) =>
36
+ forwardRef(
37
+ (
38
+ {
39
+ asChild = false,
40
+ children,
41
+ onPress,
42
+ size = 'default',
43
+ surface = 'default',
44
+ showSeparator = true,
45
+ crossAlign = 'center',
46
+ disabled = false,
47
+ style,
48
+ ...restProps
49
+ }: IListItemProps,
50
+ ref: React.Ref<unknown>,
51
+ ) => {
52
+ const childOnPress = React.isValidElement(children)
53
+ ? ((children.props as { onPress?: unknown }).onPress as
54
+ | IListItemProps['onPress']
55
+ | undefined)
56
+ : undefined;
57
+
58
+ // asChild always wins when explicitly set with a valid element — even when the press
59
+ // surface is built internally by the child (e.g. `Link` adds its onPress via `useLink`,
60
+ // not as a JSX-level prop). The clone path below preserves the child's own press by
61
+ // *omitting* `onPress` when there is nothing to compose.
62
+ const asChildInteractive = asChild && React.isValidElement(children);
63
+ const isPressableRoot = !!onPress && !asChildInteractive;
64
+
65
+ const pressState = usePress({
66
+ isDisabled: !isPressableRoot || disabled,
67
+ });
68
+ const isPressed = pressState.isPressed;
69
+ const pressProps = pressState.pressProps as Pick<PressableProps, 'onPressIn' | 'onPressOut'>;
70
+ const { hoverProps, isHovered } = useHover();
71
+
72
+ const slotAttrs = listItemRootDataAttrs(size, surface, showSeparator, crossAlign);
73
+
74
+ const contextValue = useMemo(
75
+ () => ({
76
+ isPressed: isPressableRoot ? isPressed : false,
77
+ isHovered,
78
+ isDisabled: disabled,
79
+ crossAlign: crossAlign ?? 'center',
80
+ }),
81
+ [isPressableRoot, isPressed, isHovered, disabled, crossAlign],
82
+ );
83
+
84
+ if (asChildInteractive) {
85
+ const child = children as React.ReactElement<Record<string, unknown>>;
86
+
87
+ const childDisabled = !!(child.props as { disabled?: boolean }).disabled;
88
+ const resolvedDisabled = disabled || childDisabled;
89
+
90
+ const cloneProps: Record<string, unknown> = {
91
+ ...restProps,
92
+ ...slotAttrs,
93
+ ...dataAttributes({
94
+ active: false,
95
+ hover: false,
96
+ disabled: resolvedDisabled,
97
+ }),
98
+ ...(resolvedDisabled && { accessibilityState: { disabled: true } }),
99
+ disabled: resolvedDisabled,
100
+ ref: mergeRefs(ref, child.props.ref as React.Ref<unknown>),
101
+ style: [rowStyleForCrossAlign(crossAlign), style, child.props.style],
102
+ };
103
+
104
+ // Only override the child's `onPress` when we have something to add (a parent handler)
105
+ // or need to suppress (disabled). Otherwise leave the child's own onPress alone — e.g.
106
+ // `Link` builds its navigation handler inside `useLink`, not as a JSX-level prop, and
107
+ // overriding here would shadow it.
108
+ if (resolvedDisabled) {
109
+ cloneProps.onPress = undefined;
110
+ } else if (onPress) {
111
+ cloneProps.onPress = composeEventHandlers(childOnPress, onPress);
112
+ }
113
+
114
+ return (
115
+ <ListItemProvider
116
+ value={{
117
+ isPressed: false,
118
+ isHovered: false,
119
+ isDisabled: resolvedDisabled,
120
+ crossAlign: crossAlign ?? 'center',
121
+ }}
122
+ >
123
+ {React.cloneElement(child, cloneProps)}
124
+ </ListItemProvider>
125
+ );
126
+ }
127
+
128
+ if (isPressableRoot) {
129
+ const interactionAttrs = dataAttributes({
130
+ active: isPressed,
131
+ hover: isHovered,
132
+ disabled,
133
+ });
134
+
135
+ const {
136
+ onPressIn: onPressInProp,
137
+ onPressOut: onPressOutProp,
138
+ onHoverIn: onHoverInProp,
139
+ onHoverOut: onHoverOutProp,
140
+ } = restProps as IListItemPressablePassthrough;
141
+
142
+ return (
143
+ <ListItemProvider value={contextValue}>
144
+ <BasePressable
145
+ {...(restProps as P)}
146
+ {...slotAttrs}
147
+ {...interactionAttrs}
148
+ {...(disabled && { accessibilityState: { disabled: true } })}
149
+ disabled={disabled}
150
+ ref={ref as React.Ref<P>}
151
+ style={[rowStyleForCrossAlign(crossAlign), style]}
152
+ onPress={disabled ? undefined : onPress}
153
+ onPressIn={composeEventHandlers(onPressInProp, pressProps.onPressIn)}
154
+ onPressOut={composeEventHandlers(onPressOutProp, pressProps.onPressOut)}
155
+ onHoverIn={composeEventHandlers(onHoverInProp, hoverProps.onHoverIn)}
156
+ onHoverOut={composeEventHandlers(onHoverOutProp, hoverProps.onHoverOut)}
157
+ >
158
+ {children}
159
+ </BasePressable>
160
+ </ListItemProvider>
161
+ );
162
+ }
163
+
164
+ return (
165
+ <ListItemProvider value={contextValue}>
166
+ <BaseView
167
+ {...(restProps as V)}
168
+ {...slotAttrs}
169
+ {...(disabled ? dataAttributes({ disabled: true }) : undefined)}
170
+ ref={ref as React.Ref<V>}
171
+ style={[rowStyleForCrossAlign(crossAlign), style]}
172
+ >
173
+ {children}
174
+ </BaseView>
175
+ </ListItemProvider>
176
+ );
177
+ },
178
+ );
@@ -0,0 +1,53 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { View } from 'react-native';
3
+ import { dataAttributes } from '../utils/dataAttributes';
4
+ import type { IListItemSectionHeaderProps } from './types';
5
+
6
+ const row = {
7
+ flexDirection: 'row' as const,
8
+ alignItems: 'center' as const,
9
+ justifyContent: 'space-between' as const,
10
+ gap: 8,
11
+ };
12
+
13
+ const labelCell = {
14
+ flex: 1,
15
+ minWidth: 0,
16
+ /** Keeps the label column from stretching to a taller trailing sibling (e.g. Chip). */
17
+ alignSelf: 'center' as const,
18
+ };
19
+
20
+ const trailingCell = {
21
+ flexShrink: 0,
22
+ /** Prevents the trailing slot from stretching vertically and “filling” the row. */
23
+ alignSelf: 'center' as const,
24
+ };
25
+
26
+ export const createListItemSectionHeader = <T,>(Base: React.ComponentType<T>) =>
27
+ forwardRef(
28
+ (
29
+ {
30
+ children,
31
+ trailing,
32
+ showDivider = true,
33
+ style,
34
+ accessibilityRole = 'header',
35
+ ...props
36
+ }: IListItemSectionHeaderProps,
37
+ ref: React.Ref<unknown>,
38
+ ) => (
39
+ <Base
40
+ {...(props as T)}
41
+ {...dataAttributes({
42
+ slot: 'list-section-header',
43
+ divider: showDivider,
44
+ })}
45
+ accessibilityRole={accessibilityRole}
46
+ ref={ref as React.Ref<T>}
47
+ style={[row, style]}
48
+ >
49
+ <View style={labelCell}>{children}</View>
50
+ {trailing ? <View style={trailingCell}>{trailing}</View> : null}
51
+ </Base>
52
+ ),
53
+ );
@@ -0,0 +1,17 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { dataAttributes } from '../utils/dataAttributes';
3
+ import type { IListItemTitleProps } from './types';
4
+
5
+ export const createListItemTitle = <T,>(Base: React.ComponentType<T>) =>
6
+ forwardRef(({ children, style, ...props }: IListItemTitleProps, ref: React.Ref<unknown>) => (
7
+ <Base
8
+ {...(props as T)}
9
+ {...dataAttributes({
10
+ slot: 'list-item-title',
11
+ })}
12
+ ref={ref as React.Ref<T>}
13
+ style={style}
14
+ >
15
+ {children}
16
+ </Base>
17
+ ));
@@ -0,0 +1,21 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { dataAttributes } from '../utils/dataAttributes';
3
+ import type { IListItemTrailingSlotProps } from './types';
4
+
5
+ const shrinkZero = { flexShrink: 0 as const };
6
+
7
+ export const createListItemTrailingSlot = <T,>(Base: React.ComponentType<T>) =>
8
+ forwardRef(
9
+ ({ children, style, ...props }: IListItemTrailingSlotProps, ref: React.Ref<unknown>) => (
10
+ <Base
11
+ {...(props as T)}
12
+ {...dataAttributes({
13
+ slot: 'list-item-trailing',
14
+ })}
15
+ ref={ref as React.Ref<T>}
16
+ style={[shrinkZero, style]}
17
+ >
18
+ {children}
19
+ </Base>
20
+ ),
21
+ );
@@ -0,0 +1,88 @@
1
+ import type React from 'react';
2
+ import { createListItemContent } from './createListItemContent';
3
+ import { createListItemDescription } from './createListItemDescription';
4
+ import { createListItemLeadingSlot } from './createListItemLeadingSlot';
5
+ import { createListItemMeta } from './createListItemMeta';
6
+ import { createListItemRoot } from './createListItemRoot';
7
+ import { createListItemSectionHeader } from './createListItemSectionHeader';
8
+ import { createListItemTitle } from './createListItemTitle';
9
+ import { createListItemTrailingSlot } from './createListItemTrailingSlot';
10
+ import type { IListItemComponentType } from './types';
11
+
12
+ export type {
13
+ IListItemComponentType,
14
+ IListItemContentProps,
15
+ IListItemContextValue,
16
+ IListItemDescriptionProps,
17
+ IListItemLeadingSlotProps,
18
+ IListItemMetaProps,
19
+ IListItemPressablePassthrough,
20
+ IListItemProps,
21
+ IListItemSectionHeaderProps,
22
+ IListItemTitleProps,
23
+ IListItemTrailingSlotProps,
24
+ ListItemCrossAlign,
25
+ ListItemSize,
26
+ ListItemSurface,
27
+ } from './types';
28
+
29
+ export { ListItemProvider, useListItemContext } from './context';
30
+
31
+ export function createListItem<
32
+ ViewComponent,
33
+ PressableComponent,
34
+ LeadingSlot,
35
+ Content,
36
+ Title,
37
+ Description,
38
+ Meta,
39
+ TrailingSlot,
40
+ SectionHeader,
41
+ >(BaseComponents: {
42
+ View: React.ComponentType<ViewComponent>;
43
+ Pressable: React.ComponentType<PressableComponent>;
44
+ LeadingSlot: React.ComponentType<LeadingSlot>;
45
+ Content: React.ComponentType<Content>;
46
+ Title: React.ComponentType<Title>;
47
+ Description: React.ComponentType<Description>;
48
+ Meta: React.ComponentType<Meta>;
49
+ TrailingSlot: React.ComponentType<TrailingSlot>;
50
+ SectionHeader: React.ComponentType<SectionHeader>;
51
+ }) {
52
+ const ListItem = createListItemRoot(BaseComponents.View, BaseComponents.Pressable);
53
+ const LeadingSlot = createListItemLeadingSlot(BaseComponents.LeadingSlot);
54
+ const Content = createListItemContent(BaseComponents.Content);
55
+ const Title = createListItemTitle(BaseComponents.Title);
56
+ const Description = createListItemDescription(BaseComponents.Description);
57
+ const Meta = createListItemMeta(BaseComponents.Meta);
58
+ const TrailingSlot = createListItemTrailingSlot(BaseComponents.TrailingSlot);
59
+ const SectionHeader = createListItemSectionHeader(BaseComponents.SectionHeader);
60
+
61
+ ListItem.displayName = 'ListItemPrimitive';
62
+ LeadingSlot.displayName = 'ListItemPrimitive.LeadingSlot';
63
+ Content.displayName = 'ListItemPrimitive.Content';
64
+ Title.displayName = 'ListItemPrimitive.Title';
65
+ Description.displayName = 'ListItemPrimitive.Description';
66
+ Meta.displayName = 'ListItemPrimitive.Meta';
67
+ TrailingSlot.displayName = 'ListItemPrimitive.TrailingSlot';
68
+ SectionHeader.displayName = 'ListItemPrimitive.SectionHeader';
69
+
70
+ return Object.assign(ListItem, {
71
+ LeadingSlot,
72
+ Content,
73
+ Title,
74
+ Description,
75
+ Meta,
76
+ TrailingSlot,
77
+ SectionHeader,
78
+ }) as IListItemComponentType<
79
+ ViewComponent | PressableComponent,
80
+ LeadingSlot,
81
+ Content,
82
+ Title,
83
+ Description,
84
+ Meta,
85
+ TrailingSlot,
86
+ SectionHeader
87
+ >;
88
+ }
@@ -0,0 +1,122 @@
1
+ import type { ForwardRefExoticComponent, PropsWithoutRef, ReactNode, RefAttributes } from 'react';
2
+ import type { PressableProps, TextProps, ViewProps } from 'react-native';
3
+
4
+ export type ListItemSize = 'default' | 'compact';
5
+
6
+ export type ListItemSurface = 'default' | 'negative';
7
+
8
+ /**
9
+ * Cross-axis alignment for the row (`flex-direction: row`).
10
+ * - **center**: Leading, content, and trailing are vertically centered (typical transactions).
11
+ * - **start**: Top-aligned — use when trailing text should align with the title row (e.g. branch distance “2.04 mi”) or status chips sit above a tall content stack.
12
+ */
13
+ export type ListItemCrossAlign = 'center' | 'start';
14
+
15
+ export type IListItemPressablePassthrough = Partial<
16
+ Pick<PressableProps, 'onPressIn' | 'onPressOut' | 'onHoverIn' | 'onHoverOut'>
17
+ >;
18
+
19
+ export interface IListItemProps extends ViewProps, IListItemPressablePassthrough {
20
+ /**
21
+ * Disables press handling when the root is pressable (maps to Pressable `disabled`).
22
+ */
23
+ disabled?: boolean;
24
+ /**
25
+ * When set (and `asChild` is not used), root renders as pressable with interaction props.
26
+ */
27
+ onPress?: PressableProps['onPress'];
28
+ /**
29
+ * Vertical density; mapped at styled layer via `data-size`.
30
+ * @default 'default'
31
+ */
32
+ size?: ListItemSize;
33
+ /**
34
+ * Semantic row surface (e.g. canceled transaction); mapped via `data-surface`.
35
+ * @default 'default'
36
+ */
37
+ surface?: ListItemSurface;
38
+ /**
39
+ * Whether the row shows a bottom separator; mapped via `data-separator`.
40
+ * @default true
41
+ */
42
+ showSeparator?: boolean;
43
+ /**
44
+ * Vertical alignment of leading / content / trailing along the row cross axis.
45
+ * @default 'center'
46
+ */
47
+ crossAlign?: ListItemCrossAlign;
48
+ /**
49
+ * Merge interaction props onto a single child instead of rendering the default pressable root.
50
+ * Intended for use with `onPress`.
51
+ */
52
+ asChild?: boolean;
53
+ }
54
+
55
+ export interface IListItemContextValue {
56
+ isPressed: boolean;
57
+ isHovered: boolean;
58
+ isDisabled: boolean;
59
+ crossAlign: ListItemCrossAlign;
60
+ }
61
+
62
+ export interface IListItemLeadingSlotProps extends ViewProps {
63
+ /**
64
+ * Decorative leading content defaults to hidden from the accessibility tree.
65
+ * Set to `false` when the leading region contains meaningful controls.
66
+ * @default true
67
+ */
68
+ 'aria-hidden'?: boolean;
69
+ }
70
+
71
+ export type IListItemContentProps = ViewProps;
72
+
73
+ export type IListItemTitleProps = TextProps;
74
+
75
+ export type IListItemDescriptionProps = TextProps;
76
+
77
+ export type IListItemMetaProps = TextProps;
78
+
79
+ export type IListItemTrailingSlotProps = ViewProps;
80
+
81
+ export interface IListItemSectionHeaderProps extends ViewProps {
82
+ /** Primary section label (e.g. date heading). */
83
+ children: ReactNode;
84
+ /** Optional right-aligned adornment (e.g. section Chip). */
85
+ trailing?: ReactNode;
86
+ /**
87
+ * Whether to expose the top divider marker for styling (`data-divider`).
88
+ * @default true
89
+ */
90
+ showDivider?: boolean;
91
+ }
92
+
93
+ export type IListItemComponentType<
94
+ RootRef,
95
+ LeadingSlot,
96
+ Content,
97
+ Title,
98
+ Description,
99
+ Meta,
100
+ TrailingSlot,
101
+ SectionHeader,
102
+ > = ForwardRefExoticComponent<RefAttributes<RootRef> & IListItemProps> & {
103
+ LeadingSlot: ForwardRefExoticComponent<
104
+ RefAttributes<LeadingSlot> & PropsWithoutRef<LeadingSlot> & IListItemLeadingSlotProps
105
+ >;
106
+ Content: ForwardRefExoticComponent<
107
+ RefAttributes<Content> & PropsWithoutRef<Content> & IListItemContentProps
108
+ >;
109
+ Title: ForwardRefExoticComponent<
110
+ RefAttributes<Title> & PropsWithoutRef<Title> & IListItemTitleProps
111
+ >;
112
+ Description: ForwardRefExoticComponent<
113
+ RefAttributes<Description> & PropsWithoutRef<Description> & IListItemDescriptionProps
114
+ >;
115
+ Meta: ForwardRefExoticComponent<RefAttributes<Meta> & PropsWithoutRef<Meta> & IListItemMetaProps>;
116
+ TrailingSlot: ForwardRefExoticComponent<
117
+ RefAttributes<TrailingSlot> & PropsWithoutRef<TrailingSlot> & IListItemTrailingSlotProps
118
+ >;
119
+ SectionHeader: ForwardRefExoticComponent<
120
+ RefAttributes<SectionHeader> & PropsWithoutRef<SectionHeader> & IListItemSectionHeaderProps
121
+ >;
122
+ };
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { createContext } from '@cdx-ui/utils';
3
+ import type { RadioGroupState } from '@react-stately/radio';
4
+ import type { IRadioContextValue } from './types';
5
+
6
+ export const [RadioProvider, useRadioContext] = createContext<IRadioContextValue>('RadioContext');
7
+
8
+ export interface IRadioGroupState {
9
+ isReadOnly: boolean;
10
+ isDisabled: boolean;
11
+ isInvalid: boolean;
12
+ isRequired: boolean;
13
+ isSelected: (value: string) => boolean;
14
+ selectValue: (value: string) => void;
15
+ }
16
+
17
+ export const RadioGroupContext = React.createContext<{
18
+ state: IRadioGroupState;
19
+ radioGroupState: RadioGroupState;
20
+ name?: string;
21
+ } | null>(null);
@@ -0,0 +1,67 @@
1
+ import { forwardRef } from 'react';
2
+ import { useFormControlContext } from '@cdx-ui/utils';
3
+ import { useRadioGroup } from '@react-native-aria/radio';
4
+ import { useRadioGroupState } from '@react-stately/radio';
5
+ import { dataAttributes } from '../utils/dataAttributes';
6
+ import { RadioGroupContext } from './context';
7
+ import type { IRadioGroupProps } from './types';
8
+
9
+ export const createRadioGroup = <T,>(BaseRadioGroup: React.ComponentType<T>) =>
10
+ forwardRef(({ children, ...props }: IRadioGroupProps, ref?: React.Ref<T>) => {
11
+ const formControlContext = useFormControlContext();
12
+
13
+ const combinedProps = {
14
+ ...formControlContext,
15
+ ...props,
16
+ };
17
+
18
+ const radioGroupState = useRadioGroupState({
19
+ ...combinedProps,
20
+ validationState: combinedProps.isInvalid ? 'invalid' : 'valid',
21
+ });
22
+
23
+ const { radioGroupProps } = useRadioGroup(
24
+ {
25
+ ...combinedProps,
26
+ 'aria-label': combinedProps['aria-label'],
27
+ },
28
+ radioGroupState,
29
+ );
30
+
31
+ const isDisabled = combinedProps.isDisabled ?? false;
32
+ const isInvalid = combinedProps.isInvalid ?? false;
33
+ const isRequired = combinedProps.isRequired ?? false;
34
+ const isReadOnly = combinedProps.isReadOnly ?? false;
35
+
36
+ return (
37
+ <RadioGroupContext.Provider
38
+ value={{
39
+ state: {
40
+ isDisabled,
41
+ isInvalid,
42
+ isRequired,
43
+ isReadOnly,
44
+ isSelected: (value: string) => radioGroupState.selectedValue === value,
45
+ selectValue: (value: string) => radioGroupState.setSelectedValue(value),
46
+ },
47
+ radioGroupState,
48
+ name: combinedProps.name,
49
+ }}
50
+ >
51
+ <BaseRadioGroup
52
+ {...radioGroupProps}
53
+ {...(combinedProps as unknown as T)}
54
+ ref={ref}
55
+ aria-required={isRequired || undefined}
56
+ aria-readonly={isReadOnly || undefined}
57
+ {...dataAttributes({
58
+ slot: 'radio-group',
59
+ disabled: isDisabled,
60
+ invalid: isInvalid,
61
+ })}
62
+ >
63
+ {children}
64
+ </BaseRadioGroup>
65
+ </RadioGroupContext.Provider>
66
+ );
67
+ });
@@ -0,0 +1,32 @@
1
+ import { forwardRef } from 'react';
2
+ import { dataAttributes } from '../utils/dataAttributes';
3
+ import { useRadioContext } from './context';
4
+ import type { IRadioIndicatorProps } from './types';
5
+
6
+ export const createRadioIndicator = <T,>(BaseRadioIndicator: React.ComponentType<T>) =>
7
+ forwardRef<unknown, IRadioIndicatorProps & { className?: string }>(
8
+ ({ children, className, ...props }, ref) => {
9
+ const { isChecked, isDisabled, isHovered, isInvalid, isReadOnly, isPressed, isFocusVisible } =
10
+ useRadioContext();
11
+
12
+ return (
13
+ <BaseRadioIndicator
14
+ className={className}
15
+ {...dataAttributes({
16
+ slot: 'radio-indicator',
17
+ hover: isHovered,
18
+ checked: isChecked,
19
+ disabled: isDisabled,
20
+ focusVisible: isFocusVisible,
21
+ invalid: isInvalid,
22
+ readonly: isReadOnly,
23
+ active: isPressed,
24
+ })}
25
+ {...(props as T)}
26
+ ref={ref}
27
+ >
28
+ {children}
29
+ </BaseRadioIndicator>
30
+ );
31
+ },
32
+ );
@@ -0,0 +1,28 @@
1
+ import { forwardRef } from 'react';
2
+ import { dataAttributes } from '../utils/dataAttributes';
3
+ import { useRadioContext } from './context';
4
+ import type { IRadioLabelProps } from './types';
5
+
6
+ export const createRadioLabel = <T,>(BaseRadioLabel: React.ComponentType<T>) =>
7
+ forwardRef<unknown, IRadioLabelProps & { className?: string }>(
8
+ ({ children, className, ...props }, ref) => {
9
+ const { isChecked, isDisabled, isHovered, isInvalid, isReadOnly } = useRadioContext();
10
+
11
+ return (
12
+ <BaseRadioLabel
13
+ className={className}
14
+ {...dataAttributes({
15
+ hover: isHovered,
16
+ checked: isChecked,
17
+ disabled: isDisabled,
18
+ invalid: isInvalid,
19
+ readonly: isReadOnly,
20
+ })}
21
+ {...(props as T)}
22
+ ref={ref}
23
+ >
24
+ {children}
25
+ </BaseRadioLabel>
26
+ );
27
+ },
28
+ );