@ledgerhq/lumen-ui-rnative 0.0.46 → 0.0.48

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