@ledgerhq/lumen-ui-rnative 0.0.46 → 0.0.47

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 (99) hide show
  1. package/dist/package.json +5 -1
  2. package/dist/src/lib/Components/Icon/Icon.d.ts.map +1 -1
  3. package/dist/src/lib/Components/Icon/Icon.js +3 -4
  4. package/dist/src/lib/Components/Icon/createIcon.d.ts.map +1 -1
  5. package/dist/src/lib/Components/Icon/createIcon.js +1 -1
  6. package/dist/src/lib/Components/ListItem/ListItem.d.ts +88 -34
  7. package/dist/src/lib/Components/ListItem/ListItem.d.ts.map +1 -1
  8. package/dist/src/lib/Components/ListItem/ListItem.js +242 -97
  9. package/dist/src/lib/Components/ListItem/ListItem.stories.d.ts +2 -3
  10. package/dist/src/lib/Components/ListItem/ListItem.stories.d.ts.map +1 -1
  11. package/dist/src/lib/Components/ListItem/ListItem.stories.js +63 -73
  12. package/dist/src/lib/Components/ListItem/index.d.ts +1 -1
  13. package/dist/src/lib/Components/ListItem/index.d.ts.map +1 -1
  14. package/dist/src/lib/Components/ListItem/index.js +1 -1
  15. package/dist/src/lib/Components/ListItem/types.d.ts +106 -0
  16. package/dist/src/lib/Components/ListItem/types.d.ts.map +1 -0
  17. package/dist/src/lib/Components/Spot/types.d.ts +1 -1
  18. package/dist/src/lib/Components/Spot/types.d.ts.map +1 -1
  19. package/dist/src/lib/utils/index.d.ts +3 -4
  20. package/dist/src/lib/utils/index.d.ts.map +1 -1
  21. package/dist/src/lib/utils/index.js +3 -4
  22. package/dist/src/lib/utils/react/{extract-text-from-children.d.ts → extractTextFromChildren.d.ts} +1 -1
  23. package/dist/src/lib/utils/react/extractTextFromChildren.d.ts.map +1 -0
  24. package/dist/src/lib/utils/react/index.d.ts +1 -1
  25. package/dist/src/lib/utils/react/index.d.ts.map +1 -1
  26. package/dist/src/lib/utils/react/index.js +1 -1
  27. package/dist/src/lib/utils/startTransition/index.d.ts +2 -0
  28. package/dist/src/lib/utils/startTransition/index.d.ts.map +1 -0
  29. package/dist/src/lib/utils/startTransition/index.js +1 -0
  30. package/dist/src/lib/utils/{start-transition/start-transition.d.ts → startTransition/startTransition.d.ts} +1 -1
  31. package/dist/src/lib/utils/startTransition/startTransition.d.ts.map +1 -0
  32. package/dist/src/lib/utils/useControllableState/index.d.ts +2 -0
  33. package/dist/src/lib/utils/useControllableState/index.d.ts.map +1 -0
  34. package/dist/src/lib/utils/useControllableState/index.js +1 -0
  35. package/dist/src/lib/utils/{use-controllable-state/use-controllable-state.d.ts → useControllableState/useControllableState.d.ts} +1 -1
  36. package/dist/src/lib/utils/useControllableState/useControllableState.d.ts.map +1 -0
  37. package/dist/src/lib/utils/{use-controllable-state/use-controllable-state.js → useControllableState/useControllableState.js} +2 -2
  38. package/dist/src/lib/utils/useEvent/index.d.ts +3 -0
  39. package/dist/src/lib/utils/useEvent/index.d.ts.map +1 -0
  40. package/dist/src/lib/utils/useEvent/index.js +2 -0
  41. package/dist/src/lib/utils/{use-event/use-event.d.ts → useEvent/useEvent.d.ts} +1 -1
  42. package/dist/src/lib/utils/useEvent/useEvent.d.ts.map +1 -0
  43. package/dist/src/lib/utils/{use-event/use-event.js → useEvent/useEvent.js} +1 -1
  44. package/dist/src/lib/utils/{use-event/use-get.d.ts → useEvent/useGet.d.ts} +1 -1
  45. package/dist/src/lib/utils/useEvent/useGet.d.ts.map +1 -0
  46. package/dist/src/styles/provider/LumenStyleSheetProvider.d.ts +1 -1
  47. package/dist/src/styles/provider/LumenStyleSheetProvider.d.ts.map +1 -1
  48. package/dist/src/styles/provider/LumenStyleSheetProvider.js +1 -2
  49. package/package.json +6 -2
  50. package/src/lib/Components/Checkbox/Checkbox.mdx +1 -0
  51. package/src/lib/Components/Icon/Icon.tsx +3 -5
  52. package/src/lib/Components/Icon/createIcon.ts +1 -1
  53. package/src/lib/Components/ListItem/ListItem.mdx +402 -124
  54. package/src/lib/Components/ListItem/ListItem.stories.tsx +357 -228
  55. package/src/lib/Components/ListItem/ListItem.tsx +437 -181
  56. package/src/lib/Components/ListItem/index.ts +1 -1
  57. package/src/lib/Components/ListItem/types.ts +121 -0
  58. package/src/lib/Components/Spot/types.ts +5 -1
  59. package/src/lib/Components/Switch/Switch.mdx +1 -0
  60. package/src/lib/utils/index.ts +3 -4
  61. package/src/lib/utils/react/index.ts +1 -1
  62. package/src/lib/utils/startTransition/index.ts +1 -0
  63. package/src/lib/utils/useControllableState/index.ts +1 -0
  64. package/src/lib/utils/{use-controllable-state/use-controllable-state.ts → useControllableState/useControllableState.ts} +2 -2
  65. package/src/lib/utils/useEvent/index.ts +2 -0
  66. package/src/lib/utils/{use-event/use-event.ts → useEvent/useEvent.ts} +1 -1
  67. package/src/styles/provider/LumenStyleSheetProvider.tsx +2 -3
  68. package/dist/src/lib/Components/ListItem/ListItem.types.d.ts +0 -31
  69. package/dist/src/lib/Components/ListItem/ListItem.types.d.ts.map +0 -1
  70. package/dist/src/lib/utils/react/extract-text-from-children.d.ts.map +0 -1
  71. package/dist/src/lib/utils/start-transition/index.d.ts +0 -2
  72. package/dist/src/lib/utils/start-transition/index.d.ts.map +0 -1
  73. package/dist/src/lib/utils/start-transition/index.js +0 -1
  74. package/dist/src/lib/utils/start-transition/start-transition.d.ts.map +0 -1
  75. package/dist/src/lib/utils/string-utils.d.ts +0 -6
  76. package/dist/src/lib/utils/string-utils.d.ts.map +0 -1
  77. package/dist/src/lib/utils/string-utils.js +0 -12
  78. package/dist/src/lib/utils/use-controllable-state/index.d.ts +0 -2
  79. package/dist/src/lib/utils/use-controllable-state/index.d.ts.map +0 -1
  80. package/dist/src/lib/utils/use-controllable-state/index.js +0 -1
  81. package/dist/src/lib/utils/use-controllable-state/use-controllable-state.d.ts.map +0 -1
  82. package/dist/src/lib/utils/use-event/index.d.ts +0 -3
  83. package/dist/src/lib/utils/use-event/index.d.ts.map +0 -1
  84. package/dist/src/lib/utils/use-event/index.js +0 -2
  85. package/dist/src/lib/utils/use-event/use-event.d.ts.map +0 -1
  86. package/dist/src/lib/utils/use-event/use-get.d.ts.map +0 -1
  87. package/src/lib/Components/ListItem/ListItem.types.ts +0 -31
  88. package/src/lib/utils/start-transition/index.ts +0 -1
  89. package/src/lib/utils/string-utils.test.ts +0 -73
  90. package/src/lib/utils/string-utils.ts +0 -11
  91. package/src/lib/utils/use-controllable-state/index.ts +0 -1
  92. package/src/lib/utils/use-event/index.ts +0 -2
  93. /package/dist/src/lib/Components/ListItem/{ListItem.types.js → types.js} +0 -0
  94. /package/dist/src/lib/utils/react/{extract-text-from-children.js → extractTextFromChildren.js} +0 -0
  95. /package/dist/src/lib/utils/{start-transition/start-transition.js → startTransition/startTransition.js} +0 -0
  96. /package/dist/src/lib/utils/{use-event/use-get.js → useEvent/useGet.js} +0 -0
  97. /package/src/lib/utils/react/{extract-text-from-children.ts → extractTextFromChildren.ts} +0 -0
  98. /package/src/lib/utils/{start-transition/start-transition.ts → startTransition/startTransition.ts} +0 -0
  99. /package/src/lib/utils/{use-event/use-get.ts → useEvent/useGet.ts} +0 -0
@@ -1,93 +1,60 @@
1
+ import {
2
+ createSafeContext,
3
+ isTextChildren,
4
+ } from '@ledgerhq/lumen-utils-shared';
1
5
  import React from 'react';
2
- import { StyleSheet, Text, View } from 'react-native';
3
- import { useStyleSheet } from '../../../styles';
4
- import { Pressable } from '../Utility';
6
+ import { StyleSheet, View } from 'react-native';
7
+ import { useStyleSheet, useTheme } from '../../../styles';
8
+ import { Spot } from '../Spot';
9
+ import { Box, Pressable, Text } from '../Utility';
10
+ import {
11
+ ListItemContentProps,
12
+ ListItemContextValue,
13
+ ListItemDescriptionProps,
14
+ ListItemIconProps,
15
+ ListItemLeadingProps,
16
+ ListItemProps,
17
+ ListItemSpotProps,
18
+ ListItemTitleProps,
19
+ ListItemTrailingProps,
20
+ ListItemTruncateProps,
21
+ } from './types';
5
22
 
6
- import { ListItemProps } from './ListItem.types';
23
+ const [ListItemProvider, useListItemContext] =
24
+ createSafeContext<ListItemContextValue>('ListItem', {});
7
25
 
8
- const useStyles = ({
9
- disabled,
10
- pressed,
11
- }: {
12
- disabled: boolean;
13
- pressed: boolean;
14
- }) => {
26
+ const [ListItemTrailingProvider, useListItemTrailingContext] =
27
+ createSafeContext<{ isInTrailing: boolean }>('ListItemTrailing', {
28
+ isInTrailing: false,
29
+ });
30
+
31
+ const useRootStyles = ({ pressed }: { pressed: boolean }) => {
15
32
  return useStyleSheet(
16
- (t) => {
17
- return {
18
- container: StyleSheet.flatten([
19
- {
20
- flexDirection: 'row',
21
- alignItems: 'center',
22
- height: t.sizes.s64,
23
- width: t.sizes.full,
24
- gap: t.spacings.s16,
25
- borderRadius: t.borderRadius.md,
26
- backgroundColor: 'transparent',
27
- paddingHorizontal: t.spacings.s8,
28
- paddingVertical: t.spacings.s12,
29
- },
30
- pressed && {
31
- backgroundColor: t.colors.bg.baseTransparentPressed,
32
- },
33
- disabled && {
34
- backgroundColor: 'transparent',
35
- },
36
- ]),
37
- contentWrapper: {
38
- flex: 1,
39
- minWidth: 0,
40
- flexDirection: 'row',
41
- alignItems: 'center',
42
- gap: t.spacings.s12,
43
- },
44
- textWrapper: {
45
- flex: 1,
46
- minWidth: 0,
47
- flexDirection: 'column',
48
- gap: t.spacings.s4,
49
- },
50
- title: StyleSheet.flatten([
51
- t.typographies.body2SemiBold,
52
- {
53
- color: disabled ? t.colors.text.disabled : t.colors.text.base,
54
- },
55
- ]),
56
- descriptionRow: {
33
+ (t) => ({
34
+ container: StyleSheet.flatten([
35
+ {
57
36
  flexDirection: 'row',
58
37
  alignItems: 'center',
59
- gap: t.spacings.s4,
38
+ height: t.sizes.s64,
39
+ width: t.sizes.full,
40
+ gap: t.spacings.s16,
41
+ borderRadius: t.borderRadius.md,
42
+ backgroundColor: 'transparent',
43
+ paddingHorizontal: t.spacings.s8,
44
+ paddingVertical: t.spacings.s12,
60
45
  },
61
- descriptionTextWrapper: {
62
- minWidth: 0,
63
- flexShrink: 1,
46
+ pressed && {
47
+ backgroundColor: t.colors.bg.baseTransparentPressed,
64
48
  },
65
- description: StyleSheet.flatten([
66
- t.typographies.body3,
67
- {
68
- color: disabled ? t.colors.text.disabled : t.colors.text.muted,
69
- minWidth: 0,
70
- flexShrink: 1,
71
- },
72
- ]),
73
- descriptionTagWrapper: {
74
- height: t.sizes.s16,
75
- flexShrink: 0,
76
- flexDirection: 'row',
77
- alignItems: 'center',
78
- },
79
- trailingWrapper: {
80
- flexShrink: 0,
81
- },
82
- };
83
- },
84
- [disabled, pressed],
49
+ ]),
50
+ }),
51
+ [pressed],
85
52
  );
86
53
  };
87
54
 
88
55
  /**
89
- * A flexible list item component that displays a required title and optional description (with possible tag), leading and trailing content.
90
- * It functions as a clickable button with hover and active states.
56
+ * A flexible list item component that provides a composable structure for displaying
57
+ * interactive list items with leading content, title, description, and trailing content.
91
58
  *
92
59
  * @see {@link https://ldls.vercel.app/?path=/docs/react-native_containment-listitem--docs Storybook}
93
60
  * @see {@link https://ldls.vercel.app/?path=/docs/react-native_containment-listitem--docs#dos-and-donts Guidelines}
@@ -96,53 +63,36 @@ const useStyles = ({
96
63
  * Do not use it to modify the list item's core appearance (colors, padding, etc).
97
64
  *
98
65
  * @example
99
- * // Basic item
100
- * import { ListItem } from '@ledgerhq/lumen-ui-rnative';
101
- *
102
- * <ListItem
103
- * title="Basic Item"
104
- * description="Optional description"
105
- * onPress={() => console.log('Clicked!')}
106
- * />
107
- *
108
- * // Icon trailing content with leading Spot
109
- * import { ListItem, Spot } from '@ledgerhq/lumen-ui-rnative';
110
- * import { Wallet, Settings } from '@ledgerhq/lumen-ui-rnative/symbols';
66
+ * import {
67
+ * ListItem,
68
+ * ListItemLeading,
69
+ * ListItemSpot,
70
+ * ListItemContent,
71
+ * ListItemTitle,
72
+ * ListItemDescription,
73
+ * ListItemTrailing,
74
+ * } from '@ledgerhq/lumen-ui-rnative';
75
+ * import { Wallet, ChevronRight } from '@ledgerhq/lumen-ui-rnative/symbols';
111
76
  *
112
- * <ListItem
113
- * title="Balance"
114
- * leadingContent={<Spot appearance="icon" icon={Wallet} />}
115
- * trailingContent={<Settings />}
116
- * />
117
- *
118
- * // Chevron trailing content
119
- * import { ListItem } from '@ledgerhq/lumen-ui-rnative';
120
- * import { ChevronRight } from '@ledgerhq/lumen-ui-rnative/symbols';
121
- *
122
- * <ListItem
123
- * title="Settings"
124
- * trailingContent={<ChevronRight size={24} />}
125
- * />
77
+ * <ListItem onPress={() => console.log('Clicked!')}>
78
+ * <ListItemLeading>
79
+ * <ListItemSpot appearance="icon" icon={Wallet} />
80
+ * <ListItemContent>
81
+ * <ListItemTitle>Balance</ListItemTitle>
82
+ * <ListItemDescription>Optional description</ListItemDescription>
83
+ * </ListItemContent>
84
+ * </ListItemLeading>
85
+ * <ListItemTrailing>
86
+ * <ChevronRight size={24} />
87
+ * </ListItemTrailing>
88
+ * </ListItem>
126
89
  */
127
90
  export const ListItem = React.forwardRef<
128
91
  React.ElementRef<typeof Pressable>,
129
92
  ListItemProps
130
- >(
131
- (
132
- {
133
- title,
134
- description,
135
- leadingContent,
136
- descriptionTag,
137
- trailingContent,
138
- lx = {},
139
- style,
140
- disabled = false,
141
- ...touchableProps
142
- },
143
- ref,
144
- ) => {
145
- return (
93
+ >(({ children, lx = {}, style, disabled = false, ...pressableProps }, ref) => {
94
+ return (
95
+ <ListItemProvider value={{ disabled }}>
146
96
  <Pressable
147
97
  ref={ref}
148
98
  lx={lx}
@@ -150,79 +100,385 @@ export const ListItem = React.forwardRef<
150
100
  disabled={disabled}
151
101
  accessibilityRole='button'
152
102
  accessibilityState={{ disabled }}
153
- {...touchableProps}
103
+ {...pressableProps}
154
104
  >
155
105
  {({ pressed }) => (
156
- <ListItemContent
157
- disabled={disabled}
158
- pressed={pressed}
159
- title={title}
160
- description={description}
161
- leadingContent={leadingContent}
162
- descriptionTag={descriptionTag}
163
- trailingContent={trailingContent}
164
- />
106
+ <ListItemInner pressed={pressed}>{children}</ListItemInner>
165
107
  )}
166
108
  </Pressable>
109
+ </ListItemProvider>
110
+ );
111
+ });
112
+
113
+ ListItem.displayName = 'ListItem';
114
+
115
+ /**
116
+ * Internal component to handle pressed state styling
117
+ */
118
+ const ListItemInner = ({
119
+ pressed,
120
+ children,
121
+ }: {
122
+ pressed: boolean;
123
+ children: React.ReactNode;
124
+ }) => {
125
+ const styles = useRootStyles({ pressed });
126
+ return (
127
+ <View style={styles.container} testID='list-item-content'>
128
+ {children}
129
+ </View>
130
+ );
131
+ };
132
+
133
+ /**
134
+ * Container for the leading (left) part of the list item.
135
+ * Contains the visual element (ListItemSpot, Avatar, Icon) and the content (title + description).
136
+ */
137
+ export const ListItemLeading = React.forwardRef<View, ListItemLeadingProps>(
138
+ ({ children, lx = {}, style, ...viewProps }, ref) => {
139
+ const styles = useStyleSheet(
140
+ (t) => ({
141
+ leading: {
142
+ flex: 1,
143
+ minWidth: 0,
144
+ flexDirection: 'row',
145
+ alignItems: 'center',
146
+ gap: t.spacings.s12,
147
+ },
148
+ }),
149
+ [],
150
+ );
151
+
152
+ return (
153
+ <Box
154
+ ref={ref}
155
+ lx={lx}
156
+ style={StyleSheet.flatten([styles.leading, style])}
157
+ {...viewProps}
158
+ >
159
+ {children}
160
+ </Box>
167
161
  );
168
162
  },
169
163
  );
170
164
 
171
- type ListItemContentProps = {
172
- disabled: boolean;
173
- pressed: boolean;
174
- title: string;
175
- description?: string;
176
- leadingContent?: React.ReactNode;
177
- descriptionTag?: React.ReactNode;
178
- trailingContent?: React.ReactNode;
165
+ ListItemLeading.displayName = 'ListItemLeading';
166
+
167
+ /**
168
+ * Container for the text content (title and description) within the leading area.
169
+ */
170
+ export const ListItemContent = React.forwardRef<View, ListItemContentProps>(
171
+ ({ children, lx = {}, style, ...viewProps }, ref) => {
172
+ const styles = useStyleSheet(
173
+ (t) => ({
174
+ content: {
175
+ flex: 1,
176
+ minWidth: 0,
177
+ flexDirection: 'column',
178
+ gap: t.spacings.s4,
179
+ },
180
+ }),
181
+ [],
182
+ );
183
+
184
+ return (
185
+ <Box
186
+ ref={ref}
187
+ lx={lx}
188
+ style={StyleSheet.flatten([styles.content, style])}
189
+ {...viewProps}
190
+ >
191
+ {children}
192
+ </Box>
193
+ );
194
+ },
195
+ );
196
+
197
+ ListItemContent.displayName = 'ListItemContent';
198
+
199
+ /**
200
+ * The main title of the list item. Can contain text directly or
201
+ * ListItemTruncate + Tag for more complex layouts.
202
+ */
203
+ export const ListItemTitle = React.forwardRef<View, ListItemTitleProps>(
204
+ ({ children, lx = {}, style, ...viewProps }, ref) => {
205
+ const { disabled } = useListItemContext({
206
+ consumerName: 'ListItemTitle',
207
+ contextRequired: true,
208
+ });
209
+ const { isInTrailing } = useListItemTrailingContext({
210
+ consumerName: 'ListItemTitle',
211
+ contextRequired: false,
212
+ });
213
+
214
+ const styles = useStyleSheet(
215
+ (t) => {
216
+ const { boxStyle } = StyleSheet.create({
217
+ boxStyle: {
218
+ flexDirection: 'row',
219
+ alignItems: 'center',
220
+ gap: t.spacings.s4,
221
+ width: '100%',
222
+ textAlign: isInTrailing ? 'right' : 'left',
223
+ justifyContent: isInTrailing ? 'flex-end' : 'flex-start',
224
+ } as const,
225
+ });
226
+
227
+ return {
228
+ asBox: boxStyle,
229
+ asText: StyleSheet.flatten([
230
+ t.typographies.body2SemiBold,
231
+ {
232
+ ...boxStyle,
233
+ color: disabled ? t.colors.text.disabled : t.colors.text.base,
234
+ },
235
+ ]),
236
+ };
237
+ },
238
+ [disabled],
239
+ );
240
+
241
+ // If children is a string, render it directly as Text with truncation
242
+ if (isTextChildren(children)) {
243
+ return (
244
+ <Text
245
+ ref={ref as React.Ref<React.ElementRef<typeof Text>>}
246
+ lx={lx}
247
+ style={StyleSheet.flatten([styles.asText, style])}
248
+ numberOfLines={1}
249
+ ellipsizeMode='tail'
250
+ >
251
+ {children}
252
+ </Text>
253
+ );
254
+ }
255
+
256
+ // Otherwise, render as a row container for ListItemTruncate + Tag
257
+ return (
258
+ <Box
259
+ ref={ref}
260
+ lx={lx}
261
+ style={StyleSheet.flatten([styles.asBox, style])}
262
+ {...viewProps}
263
+ >
264
+ {children}
265
+ </Box>
266
+ );
267
+ },
268
+ );
269
+
270
+ ListItemTitle.displayName = 'ListItemTitle';
271
+
272
+ /**
273
+ * Optional description below the title. Can contain text directly or
274
+ * ListItemTruncate + Tag for more complex layouts.
275
+ * Automatically applies disabled styling when the parent ListItem is disabled.
276
+ */
277
+ export const ListItemDescription = React.forwardRef<
278
+ View,
279
+ ListItemDescriptionProps
280
+ >(({ children, lx = {}, style, ...viewProps }, ref) => {
281
+ const { disabled } = useListItemContext({
282
+ consumerName: 'ListItemDescription',
283
+ contextRequired: true,
284
+ });
285
+ const { isInTrailing } = useListItemTrailingContext({
286
+ consumerName: 'ListItemDescription',
287
+ contextRequired: false,
288
+ });
289
+
290
+ const styles = useStyleSheet(
291
+ (t) => {
292
+ const { boxStyle } = StyleSheet.create({
293
+ boxStyle: {
294
+ flexDirection: 'row',
295
+ alignItems: 'center',
296
+ gap: t.spacings.s4,
297
+ width: '100%',
298
+ textAlign: isInTrailing ? 'right' : 'left',
299
+ justifyContent: isInTrailing ? 'flex-end' : 'flex-start',
300
+ } as const,
301
+ });
302
+
303
+ return {
304
+ asBox: boxStyle,
305
+ asText: StyleSheet.flatten([
306
+ t.typographies.body3,
307
+ {
308
+ ...boxStyle,
309
+ color: disabled ? t.colors.text.disabled : t.colors.text.muted,
310
+ },
311
+ ]),
312
+ };
313
+ },
314
+ [disabled, isInTrailing],
315
+ );
316
+
317
+ // If children is a string, render it directly as Text with truncation
318
+ if (isTextChildren(children)) {
319
+ return (
320
+ <Text
321
+ ref={ref as React.Ref<React.ElementRef<typeof Text>>}
322
+ lx={lx}
323
+ style={StyleSheet.flatten([styles.asText, style])}
324
+ numberOfLines={1}
325
+ ellipsizeMode='tail'
326
+ >
327
+ {children}
328
+ </Text>
329
+ );
330
+ }
331
+
332
+ // Otherwise, render as a row container for ListItemTruncate + Tag
333
+ return (
334
+ <Box
335
+ ref={ref}
336
+ lx={lx}
337
+ style={StyleSheet.flatten([styles.asBox, style])}
338
+ {...viewProps}
339
+ >
340
+ {children}
341
+ </Box>
342
+ );
343
+ });
344
+
345
+ ListItemDescription.displayName = 'ListItemDescription';
346
+
347
+ /**
348
+ * Container for the trailing (right) content of the list item.
349
+ * Used for icons, switches, values, tags, chevrons, etc.
350
+ */
351
+ export const ListItemTrailing = React.forwardRef<View, ListItemTrailingProps>(
352
+ ({ children, lx = {}, style, ...viewProps }, ref) => {
353
+ const styles = useStyleSheet(
354
+ () => ({
355
+ trailing: {
356
+ flexShrink: 0,
357
+ flexDirection: 'row',
358
+ alignItems: 'center',
359
+ },
360
+ }),
361
+ [],
362
+ );
363
+
364
+ return (
365
+ <ListItemTrailingProvider value={{ isInTrailing: true }}>
366
+ <Box
367
+ ref={ref}
368
+ lx={lx}
369
+ style={StyleSheet.flatten([styles.trailing, style])}
370
+ {...viewProps}
371
+ >
372
+ {children}
373
+ </Box>
374
+ </ListItemTrailingProvider>
375
+ );
376
+ },
377
+ );
378
+
379
+ ListItemTrailing.displayName = 'ListItemTrailing';
380
+
381
+ /**
382
+ * Spot adapter for ListItem. Automatically inherits disabled state from parent ListItem.
383
+ * Fixed at size 48 for consistent list item appearance.
384
+ */
385
+ export const ListItemSpot = (props: ListItemSpotProps) => {
386
+ const { disabled } = useListItemContext({
387
+ consumerName: 'ListItemSpot',
388
+ contextRequired: true,
389
+ });
390
+
391
+ return <Spot {...props} size={48} disabled={disabled} />;
179
392
  };
180
393
 
181
- const ListItemContent: React.FC<ListItemContentProps> = ({
182
- disabled,
183
- pressed,
184
- title,
185
- description,
186
- leadingContent,
187
- descriptionTag,
188
- trailingContent,
189
- }) => {
190
- const styles = useStyles({ disabled, pressed });
394
+ ListItemSpot.displayName = 'ListItemSpot';
395
+
396
+ /**
397
+ * Icon adapter for ListItem. Automatically inherits disabled state from parent ListItem.
398
+ * Fixed at size 24 for consistent list item appearance.
399
+ */
400
+ export const ListItemIcon = ({
401
+ icon: Icon,
402
+ color,
403
+ lx = {},
404
+ style,
405
+ ...viewProps
406
+ }: ListItemIconProps) => {
407
+ const { theme } = useTheme();
408
+ const { disabled } = useListItemContext({
409
+ consumerName: 'ListItemIcon',
410
+ contextRequired: true,
411
+ });
191
412
 
192
413
  return (
193
- <View style={styles.container} testID='list-item-content'>
194
- <View style={styles.contentWrapper}>
195
- {leadingContent}
196
- <View style={styles.textWrapper}>
197
- <Text style={styles.title} numberOfLines={1} ellipsizeMode='tail'>
198
- {title}
199
- </Text>
200
-
201
- {description && (
202
- <View style={styles.descriptionRow}>
203
- <View style={styles.descriptionTextWrapper}>
204
- <Text
205
- numberOfLines={1}
206
- ellipsizeMode='tail'
207
- style={styles.description}
208
- >
209
- {description}
210
- </Text>
211
- </View>
212
- {descriptionTag && (
213
- <View style={styles.descriptionTagWrapper}>
214
- {descriptionTag}
215
- </View>
216
- )}
217
- </View>
218
- )}
219
- </View>
220
- </View>
221
- {trailingContent && (
222
- <View style={styles.trailingWrapper}>{trailingContent}</View>
223
- )}
224
- </View>
414
+ <Box
415
+ lx={lx}
416
+ style={StyleSheet.flatten([{ flexShrink: 0 }, style])}
417
+ {...viewProps}
418
+ >
419
+ <Icon
420
+ size={24}
421
+ style={{
422
+ color: disabled ? theme.colors.text.disabled : (color ?? undefined),
423
+ }}
424
+ />
425
+ </Box>
225
426
  );
226
427
  };
227
428
 
228
- ListItem.displayName = 'ListItem';
429
+ ListItemIcon.displayName = 'ListItemIcon';
430
+
431
+ /**
432
+ * Text wrapper that truncates when space is limited.
433
+ * Use inside ListItemTitle or ListItemDescription when combining text with a Tag.
434
+ * Set variant='title' for title styling or variant='description' (default) for description styling.
435
+ */
436
+ export const ListItemTruncate = React.forwardRef<
437
+ React.ElementRef<typeof Text>,
438
+ ListItemTruncateProps
439
+ >(
440
+ (
441
+ { children, variant = 'description', lx = {}, style, ...textProps },
442
+ ref,
443
+ ) => {
444
+ const { disabled } = useListItemContext({
445
+ consumerName: 'ListItemTruncate',
446
+ contextRequired: true,
447
+ });
448
+
449
+ const styles = useStyleSheet(
450
+ (t) => ({
451
+ truncate: StyleSheet.flatten([
452
+ variant === 'title'
453
+ ? t.typographies.body2SemiBold
454
+ : t.typographies.body3,
455
+ {
456
+ color: disabled
457
+ ? t.colors.text.disabled
458
+ : variant === 'title'
459
+ ? t.colors.text.base
460
+ : t.colors.text.muted,
461
+ minWidth: 0,
462
+ flexShrink: 1,
463
+ },
464
+ ]),
465
+ }),
466
+ [disabled, variant],
467
+ );
468
+
469
+ return (
470
+ <Text
471
+ ref={ref}
472
+ lx={lx}
473
+ style={StyleSheet.flatten([styles.truncate, style])}
474
+ numberOfLines={1}
475
+ ellipsizeMode='tail'
476
+ {...textProps}
477
+ >
478
+ {children}
479
+ </Text>
480
+ );
481
+ },
482
+ );
483
+
484
+ ListItemTruncate.displayName = 'ListItemTruncate';
@@ -1,2 +1,2 @@
1
1
  export * from './ListItem';
2
- export * from './ListItem.types';
2
+ export * from './types';