@utilitywarehouse/hearth-react-native 0.16.2 → 0.18.0

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 (75) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +14 -14
  3. package/CHANGELOG.md +174 -0
  4. package/build/components/BodyText/BodyText.js +2 -2
  5. package/build/components/Card/CardAction/CardActionRoot.js +12 -2
  6. package/build/components/Card/CardActions.context.d.ts +6 -0
  7. package/build/components/Card/CardActions.context.js +5 -0
  8. package/build/components/Card/CardActions.d.ts +7 -0
  9. package/build/components/Card/CardActions.js +29 -0
  10. package/build/components/Card/CardRoot.js +16 -104
  11. package/build/components/Card/helpers.d.ts +8 -0
  12. package/build/components/Card/helpers.js +146 -0
  13. package/build/components/Card/index.d.ts +2 -0
  14. package/build/components/Card/index.js +2 -0
  15. package/build/components/ExpandableCard/ExpandableCardGroup.d.ts +1 -1
  16. package/build/components/ExpandableCard/ExpandableCardGroup.js +2 -2
  17. package/build/components/ExpandableCard/ExpandableCardGroup.props.d.ts +4 -0
  18. package/build/components/IconButton/IconButton.props.d.ts +19 -0
  19. package/build/components/IconButton/IconButtonRoot.d.ts +1 -1
  20. package/build/components/IconButton/IconButtonRoot.js +43 -2
  21. package/build/components/Input/Input.js +4 -3
  22. package/build/components/Input/Input.props.d.ts +9 -0
  23. package/build/components/List/List.context.d.ts +4 -2
  24. package/build/components/List/List.context.js +0 -2
  25. package/build/components/List/List.d.ts +1 -1
  26. package/build/components/List/List.js +25 -38
  27. package/build/components/List/List.props.d.ts +1 -0
  28. package/build/components/List/ListAction/ListAction.js +24 -7
  29. package/build/components/List/ListAction/ListAction.props.d.ts +1 -0
  30. package/build/components/List/ListItem/ListItemHelperText.d.ts +1 -1
  31. package/build/components/List/ListItem/ListItemHelperText.js +2 -2
  32. package/build/components/List/ListItem/ListItemRoot.js +12 -4
  33. package/build/utils/isThemedImageProps.d.ts +1 -1
  34. package/package.json +2 -2
  35. package/src/components/BodyText/BodyText.tsx +2 -2
  36. package/src/components/Card/Card.docs.mdx +224 -66
  37. package/src/components/Card/Card.stories.tsx +29 -25
  38. package/src/components/Card/CardAction/CardAction.stories.tsx +239 -93
  39. package/src/components/Card/CardAction/CardActionRoot.tsx +15 -2
  40. package/src/components/Card/CardActions.context.ts +12 -0
  41. package/src/components/Card/CardActions.tsx +40 -0
  42. package/src/components/Card/CardRoot.tsx +27 -132
  43. package/src/components/Card/helpers.tsx +195 -0
  44. package/src/components/Card/index.ts +2 -0
  45. package/src/components/ExpandableCard/ExpandableCard.figma.tsx +33 -38
  46. package/src/components/ExpandableCard/ExpandableCardGroup.figma.tsx +34 -17
  47. package/src/components/ExpandableCard/ExpandableCardGroup.props.ts +5 -0
  48. package/src/components/ExpandableCard/ExpandableCardGroup.tsx +2 -0
  49. package/src/components/HighlightBanner/HighlightBanner.figma.tsx +46 -0
  50. package/src/components/IconButton/IconButton.docs.mdx +91 -9
  51. package/src/components/IconButton/IconButton.figma.tsx +20 -30
  52. package/src/components/IconButton/IconButton.props.ts +19 -0
  53. package/src/components/IconButton/IconButton.stories.tsx +56 -0
  54. package/src/components/IconButton/IconButtonRoot.tsx +54 -1
  55. package/src/components/IconContainer/IconContainer.figma.tsx +7 -13
  56. package/src/components/IndicatorIconButton/IndicatorIconButton.figma.tsx +16 -0
  57. package/src/components/Input/Input.docs.mdx +55 -15
  58. package/src/components/Input/Input.figma.tsx +106 -40
  59. package/src/components/Input/Input.props.ts +9 -0
  60. package/src/components/Input/Input.tsx +21 -0
  61. package/src/components/Link/Link.figma.tsx +31 -38
  62. package/src/components/List/List.context.ts +2 -4
  63. package/src/components/List/List.docs.mdx +10 -5
  64. package/src/components/List/List.figma.tsx +42 -28
  65. package/src/components/List/List.props.ts +1 -0
  66. package/src/components/List/List.stories.tsx +43 -0
  67. package/src/components/List/List.tsx +38 -51
  68. package/src/components/List/ListAction/ListAction.figma.tsx +5 -13
  69. package/src/components/List/ListAction/ListAction.props.ts +1 -0
  70. package/src/components/List/ListAction/ListAction.tsx +40 -10
  71. package/src/components/List/ListItem/ListItem.figma.tsx +43 -27
  72. package/src/components/List/ListItem/ListItemHelperText.tsx +2 -2
  73. package/src/components/List/ListItem/ListItemRoot.tsx +15 -4
  74. package/src/utils/isThemedImageProps.ts +1 -1
  75. package/src/components/InlineLink/InlineLink.figma.tsx +0 -33
@@ -1,57 +1,123 @@
1
1
  import figma from '@figma/code-connect';
2
- import Input from './Input';
2
+ import { Input } from '../';
3
3
 
4
- /**
5
- * -- This file was auto-generated by Code Connect --
6
- * `props` includes a mapping from your code props to Figma properties.
7
- * You should check this is correct, and update the `example` function
8
- * to return the code example you'd like to see in Figma
9
- */
4
+ const props = {
5
+ disabled: figma.enum('State', {
6
+ Disabled: true,
7
+ }),
8
+ readonly: figma.enum('State', {
9
+ 'Read-only': true,
10
+ }),
11
+ placeholder: figma.enum('Value type', {
12
+ Placeholder: figma.string('Value'),
13
+ }),
14
+ value: figma.enum('Value type', {
15
+ Filled: figma.string('Value'),
16
+ }),
17
+ label: figma.string('Label'),
18
+ labelVariant: figma.enum('Label variant', {
19
+ Body: 'body',
20
+ Heading: 'heading',
21
+ }),
22
+ helperText: figma.boolean('Helper text?', {
23
+ true: figma.string('Helper text'),
24
+ }),
25
+ validationStatus: figma.enum('State', {
26
+ Invalid: 'invalid',
27
+ Valid: 'valid',
28
+ }),
29
+ invalidText: figma.enum('State', {
30
+ Invalid: figma.string('Validation'),
31
+ }),
32
+ validText: figma.enum('State', {
33
+ Valid: figma.string('Validation'),
34
+ }),
35
+ };
10
36
 
11
37
  figma.connect(Input, 'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR?node-id=2685%3A7021', {
12
38
  props: {
13
- // These props were automatically mapped based on your linked code:
14
- disabled: figma.enum('State', {
15
- Disabled: true,
39
+ ...props,
40
+ required: figma.boolean('Optional?', {
41
+ true: false,
42
+ false: true,
16
43
  }),
17
- readonly: figma.enum('State', {
18
- 'Read-only': true,
44
+ prefix: figma.boolean('Prefix?', {
45
+ true: figma.string('Prefix'),
19
46
  }),
20
- focused: figma.boolean('Focus?'),
21
- placeholder: figma.string('Suffix'),
22
- focusable: figma.boolean('Focus?'),
23
- hasTVPreferredFocus: figma.boolean('Focus?'),
24
- 'aria-disabled': figma.enum('State', {
25
- Disabled: true,
47
+ suffix: figma.boolean('Suffix?', {
48
+ true: figma.string('Suffix'),
26
49
  }),
27
- // No matching props could be found for these Figma properties:
28
- // "helperText": figma.boolean('Helper text?'),
29
- // "label": figma.string('Label'),
30
- // "validation": figma.string('Validation'),
31
- // "helperText": figma.string('Helper text'),
32
- // "value": figma.string('Value'),
33
- // "suffix": figma.boolean('Suffix?'),
34
- // "prefix": figma.boolean('Prefix?'),
35
- // "prefix": figma.string('Prefix'),
36
- // "suffix": figma.string('Suffix'),
37
- // "optional": figma.boolean('Optional?'),
38
- // "valueType": figma.enum('Value type', {
39
- // "Empty": "empty",
40
- // "Placeholder": "placeholder",
41
- // "Filled": "filled"
42
- // }),
43
- // "labelVariant": figma.enum('Label variant', {
44
- // "Body": "body",
45
- // "Heading": "heading"
46
- // })
47
50
  },
48
51
  example: props => (
49
52
  <Input
50
53
  disabled={props.disabled}
51
54
  readonly={props.readonly}
52
- focused={props.focused}
53
55
  placeholder={props.placeholder}
54
- focusable={props.focusable}
56
+ value={props.value}
57
+ label={props.label}
58
+ labelVariant={props.labelVariant}
59
+ helperText={props.helperText}
60
+ required={props.required}
61
+ prefix={props.prefix}
62
+ suffix={props.suffix}
63
+ validationStatus={props.validationStatus}
64
+ invalidText={props.invalidText}
65
+ validText={props.validText}
55
66
  />
56
67
  ),
57
68
  });
69
+
70
+ figma.connect(
71
+ Input,
72
+ 'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=2251-10106&t=3uUSBVdxldgG5uz3-4',
73
+ {
74
+ props,
75
+ example: props => (
76
+ <Input
77
+ type="password"
78
+ disabled={props.disabled}
79
+ readonly={props.readonly}
80
+ placeholder={props.placeholder}
81
+ value={props.value}
82
+ label={props.label}
83
+ labelVariant={props.labelVariant}
84
+ helperText={props.helperText}
85
+ validationStatus={props.validationStatus}
86
+ invalidText={props.invalidText}
87
+ validText={props.validText}
88
+ />
89
+ ),
90
+ }
91
+ );
92
+
93
+ figma.connect(
94
+ Input,
95
+ 'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=2161-1311&t=3uUSBVdxldgG5uz3-4',
96
+ {
97
+ props: {
98
+ loading: figma.enum('State', {
99
+ Loading: true,
100
+ }),
101
+ placeholder: figma.enum('State', {
102
+ Placeholder: figma.string('Value'),
103
+ }),
104
+ value: figma.enum('State', {
105
+ Filled: figma.string('Value'),
106
+ }),
107
+ label: figma.string('Label'),
108
+ helperText: figma.boolean('Helper text?', {
109
+ true: figma.string('Helper text'),
110
+ }),
111
+ },
112
+ example: props => (
113
+ <Input
114
+ type="search"
115
+ loading={props.loading}
116
+ placeholder={props.placeholder}
117
+ value={props.value}
118
+ label={props.label}
119
+ helperText={props.helperText}
120
+ />
121
+ ),
122
+ }
123
+ );
@@ -1,4 +1,5 @@
1
1
  import type { ComponentType } from 'react';
2
+ import React from 'react';
2
3
  import type { TextInputProps, ViewProps } from 'react-native';
3
4
 
4
5
  // Base props common to all input types
@@ -47,6 +48,8 @@ export interface InputWithChildrenProps extends InputBaseProps, ViewProps {
47
48
  onClear?: never;
48
49
  leadingIcon?: never;
49
50
  trailingIcon?: never;
51
+ prefix?: never;
52
+ suffix?: never;
50
53
  }
51
54
 
52
55
  // Base for inputs without children
@@ -55,6 +58,8 @@ interface InputWithoutChildrenBaseProps extends InputBaseProps, Omit<TextInputPr
55
58
  leadingIcon?: ComponentType;
56
59
  trailingIcon?: ComponentType;
57
60
  required?: boolean;
61
+ prefix?: string | number | React.ReactNode;
62
+ suffix?: string | number | React.ReactNode;
58
63
  }
59
64
 
60
65
  // Specific input types with their unique props
@@ -74,6 +79,8 @@ interface PasswordInputSpecificProps extends InputWithoutChildrenBaseProps {
74
79
  loading?: never;
75
80
  clearable?: never;
76
81
  onClear?: never;
82
+ prefix?: never;
83
+ suffix?: never;
77
84
  }
78
85
 
79
86
  interface SearchInputSpecificProps extends InputWithoutChildrenBaseProps {
@@ -83,6 +90,8 @@ interface SearchInputSpecificProps extends InputWithoutChildrenBaseProps {
83
90
  onClear?: () => void;
84
91
  showPasswordToggle?: never;
85
92
  format?: never;
93
+ prefix?: never;
94
+ suffix?: never;
86
95
  }
87
96
 
88
97
  // Union of all input types
@@ -10,6 +10,7 @@ import {
10
10
  SearchMediumIcon,
11
11
  } from '@utilitywarehouse/hearth-react-native-icons';
12
12
  import { useTheme } from '../../hooks';
13
+ import { BodyText } from '../BodyText';
13
14
  import { FormField, useFormFieldContext } from '../FormField';
14
15
  import { Spinner } from '../Spinner';
15
16
  import { UnstyledIconButton } from '../UnstyledIconButton';
@@ -54,6 +55,8 @@ const Input = forwardRef<TextInput, InputProps>(
54
55
  helperIcon,
55
56
  validText,
56
57
  invalidText,
58
+ prefix,
59
+ suffix,
57
60
  ...props
58
61
  },
59
62
  ref
@@ -169,6 +172,15 @@ const Input = forwardRef<TextInput, InputProps>(
169
172
  <InputIcon as={leadingIconComponent} />
170
173
  </InputSlot>
171
174
  )}
175
+ {!!prefix && (
176
+ <InputSlot>
177
+ {typeof prefix === 'string' || typeof prefix === 'number' ? (
178
+ <BodyText>{prefix}</BodyText>
179
+ ) : (
180
+ prefix
181
+ )}
182
+ </InputSlot>
183
+ )}
172
184
  <InputField
173
185
  // @ts-expect-error - ref forwarding issue
174
186
  ref={inputRef}
@@ -197,6 +209,15 @@ const Input = forwardRef<TextInput, InputProps>(
197
209
  />
198
210
  </InputSlot>
199
211
  )}
212
+ {!!suffix && (
213
+ <InputSlot>
214
+ {typeof suffix === 'string' || typeof suffix === 'number' ? (
215
+ <BodyText>{suffix}</BodyText>
216
+ ) : (
217
+ suffix
218
+ )}
219
+ </InputSlot>
220
+ )}
200
221
  {!!trailingIcon && (
201
222
  <InputSlot>
202
223
  <InputIcon as={trailingIcon} />
@@ -1,42 +1,35 @@
1
- import React from "react"
2
- import Link from "./Link"
3
- import figma from "@figma/code-connect"
1
+ import figma from '@figma/code-connect';
2
+ import { Link } from '../';
4
3
 
5
- /**
6
- * -- This file was auto-generated by Code Connect --
7
- * `props` includes a mapping from your code props to Figma properties.
8
- * You should check this is correct, and update the `example` function
9
- * to return the code example you'd like to see in Figma
10
- */
11
-
12
- figma.connect(
13
- Link,
14
- "https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR?node-id=163%3A562",
15
- {
16
- props: {
17
- // These props were automatically mapped based on your linked code:
18
- inverted: figma.boolean("Inverted?"),
19
- disabled: figma.enum("State", {
20
- Active: true,
4
+ figma.connect(Link, 'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR?node-id=163%3A562', {
5
+ props: {
6
+ inverted: figma.boolean('Inverted?'),
7
+ showIcon: figma.boolean('Icon right?', {
8
+ false: figma.boolean('Icon left?', {
9
+ false: false,
10
+ }),
11
+ }),
12
+ iconPosition: figma.boolean('Icon right?', {
13
+ false: figma.boolean('Icon left?', {
14
+ true: 'left',
21
15
  }),
22
- showIcon: figma.boolean("Icon right?"),
23
- focusable: figma.enum("State", {
24
- Focus: true,
16
+ }),
17
+ text: figma.string('Text'),
18
+ icon: figma.boolean('Icon right?', {
19
+ true: figma.instance('Icon right-20'),
20
+ false: figma.boolean('Icon left?', {
21
+ true: figma.instance('Icon left-20'),
25
22
  }),
26
- // No matching props could be found for these Figma properties:
27
- // "iconLeft": figma.boolean('Icon left?'),
28
- // "iconRight": figma.boolean('Icon right?'),
29
- // "iconRight20": figma.instance('Icon right-20'),
30
- // "iconLeft20": figma.instance('Icon left-20'),
31
- // "text": figma.string('Text')
32
- },
33
- example: (props) => (
34
- <Link
35
- inverted={props.inverted}
36
- disabled={props.disabled}
37
- showIcon={props.showIcon}
38
- focusable={props.focusable}
39
- />
40
- ),
23
+ }),
41
24
  },
42
- )
25
+ example: props => (
26
+ <Link
27
+ icon={props.icon}
28
+ showIcon={props.showIcon}
29
+ iconPosition={props.iconPosition}
30
+ inverted={props.inverted}
31
+ >
32
+ {props.text}
33
+ </Link>
34
+ ),
35
+ });
@@ -5,6 +5,8 @@ export const ListContext = createContext<{
5
5
  loading?: ListProps['loading'];
6
6
  disabled?: ListProps['disabled'];
7
7
  container?: ListProps['container'];
8
+ firstItemId?: string;
9
+ registerItem?: (id: string) => () => void;
8
10
  }>({});
9
11
 
10
12
  export const useListContext = () => {
@@ -12,7 +14,3 @@ export const useListContext = () => {
12
14
 
13
15
  return context;
14
16
  };
15
-
16
- export const ListFirstItemContext = createContext<boolean>(false);
17
-
18
- export const useListFirstItemContext = () => useContext(ListFirstItemContext);
@@ -100,6 +100,7 @@ const MyComponent = () => (
100
100
  | heading | `string` | | The text to display in the heading of the list. |
101
101
  | helperText | `string` | | The supporting text to display in the heading of the list. |
102
102
  | headerTrailingContent | `ReactNode` | | Optional content to display on the right side of the header. |
103
+ | invalidText | `string` | | Validation error text to display in the heading of the list. |
103
104
  | loading | `boolean` | `false` | Whether to show the list items in loading state. |
104
105
  | disabled | `boolean` | `false` | Whether to disable the list. |
105
106
 
@@ -121,13 +122,17 @@ const MyComponent = () => (
121
122
  | truncateHeading | `boolean` | `false` | Whether to truncate the heading text if it overflows. |
122
123
  | truncateHelperText | `boolean` | `false` | Whether to truncate the helper text if it overflows. |
123
124
 
125
+ First-item styling is applied to the first rendered `ListItem` or `ListAction`. Wrapper components that render `null`
126
+ are ignored, so conditional list items will not affect which item loses the top border.
127
+
124
128
  ### `ListAction`
125
129
 
126
- | Name | Type | Default | Description |
127
- | -------- | ------------ | ------- | -------------------------------------------- |
128
- | heading | `string` | | The text to display in the list action item. |
129
- | onPress | `() => void` | | A callback function to be called |
130
- | disabled | `boolean` | `false` | Whether to disable the list action item. |
130
+ | Name | Type | Default | Description |
131
+ | -------- | ------------ | ------- | ---------------------------------------------------------------------- |
132
+ | heading | `string` | | The text to display in the list action item. |
133
+ | onPress | `() => void` | | A callback function to be called when the list action item is pressed. |
134
+ | disabled | `boolean` | `false` | Whether to disable the list action item. |
135
+ | loading | `boolean` | `false` | Whether to show the list action in loading state. |
131
136
 
132
137
  #### - `ListItemLeadingContent`
133
138
 
@@ -1,31 +1,45 @@
1
- import React from "react"
2
- import List from "./List"
3
- import figma from "@figma/code-connect"
1
+ import figma from '@figma/code-connect';
2
+ import { List } from '../';
4
3
 
5
- /**
6
- * -- This file was auto-generated by Code Connect --
7
- * `props` includes a mapping from your code props to Figma properties.
8
- * You should check this is correct, and update the `example` function
9
- * to return the code example you'd like to see in Figma
10
- */
11
-
12
- figma.connect(
13
- List,
14
- "https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR?node-id=2437%3A621",
15
- {
16
- props: {
17
- // These props were automatically mapped based on your linked code:
18
- container: figma.enum("Container", {
19
- None: "none",
20
- "Subtle White": "subtleWhite",
21
- "Emphasis White": "emphasisWhite",
22
- "Subtle Warm White": "subtleWarmWhite",
23
- "Emphasis Warm White": "emphasisWarmWhite",
4
+ figma.connect(List, 'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR?node-id=2437%3A621', {
5
+ props: {
6
+ container: figma.enum('Container', {
7
+ 'Subtle White': 'subtleWhite',
8
+ 'Emphasis White': 'emphasisWhite',
9
+ 'Subtle Warm White': 'subtleWarmWhite',
10
+ 'Emphasis Warm White': 'emphasisWarmWhite',
11
+ }),
12
+ sectionHeader: figma.boolean('Section header?', {
13
+ true: figma.nestedProps('Section Header', {
14
+ heading: figma.string('Heading'),
15
+ helperText: figma.boolean('Helper text?', {
16
+ true: figma.string('Helper text'),
17
+ }),
18
+ trailingContent: figma.boolean('Trailing content?', {
19
+ true: figma.nestedProps('Trailing content', {
20
+ headerTrailingContent: figma.instance('Variant'),
21
+ }),
22
+ }),
23
+ invalidText: figma.enum('State', {
24
+ Invalid: figma.nestedProps('Validation Text', {
25
+ invalidText: figma.string('Text'),
26
+ }),
27
+ }),
24
28
  }),
25
- // No matching props could be found for these Figma properties:
26
- // "sectionHeader": figma.boolean('Section header?'),
27
- // "listAction": figma.boolean('List action?')
28
- },
29
- example: (props) => <List container={props.container} />,
29
+ }),
30
+ listItems: figma.children('List Item'),
31
+ listActions: figma.children('List Action'),
30
32
  },
31
- )
33
+ example: props => (
34
+ <List
35
+ container={props.container}
36
+ heading={props.sectionHeader?.heading}
37
+ helperText={props.sectionHeader?.helperText}
38
+ headerTrailingContent={props.sectionHeader?.trailingContent?.headerTrailingContent}
39
+ invalidText={props.sectionHeader?.invalidText?.invalidText}
40
+ >
41
+ {props.listItems}
42
+ {props.listActions}
43
+ </List>
44
+ ),
45
+ });
@@ -7,6 +7,7 @@ interface ListProps extends ViewProps {
7
7
  headerTrailingContent?: React.ReactNode;
8
8
  disabled?: boolean;
9
9
  loading?: boolean;
10
+ invalidText?: string;
10
11
  }
11
12
 
12
13
  export default ListProps;
@@ -233,6 +233,49 @@ export const WithCustomListItemComponent: Story = {
233
233
  ),
234
234
  };
235
235
 
236
+ const CustomListAction = () => (
237
+ <ListAction
238
+ heading="Custom List Action"
239
+ onPress={() => console.log('Custom List Action pressed')}
240
+ />
241
+ );
242
+
243
+ const CustomNull = () => null;
244
+
245
+ export const WithMappedCustomListItems: Story = {
246
+ parameters: {
247
+ controls: { include: [] },
248
+ },
249
+ render: () => {
250
+ const listData = [
251
+ { heading: 'Custom Item 1', helperText: 'Supporting text 1' },
252
+ { heading: 'Custom Item 2', helperText: 'Supporting text 2' },
253
+ { heading: 'Custom Item 3', helperText: 'Supporting text 3' },
254
+ ];
255
+
256
+ return (
257
+ <List container="subtleWarmWhite">
258
+ <CustomNull />
259
+ <ListItem
260
+ heading="Refer a friend"
261
+ helperText="Get rewarded with a friend"
262
+ leadingContent={<ListItemIcon as={UserMediumIcon} />}
263
+ trailingContent={<ListItemTrailingIcon as={ChevronRightSmallIcon} />}
264
+ onPress={() => console.log('Refer a friend pressed')}
265
+ />
266
+ {listData.map((item, index) => (
267
+ <CustomListItem key={index} />
268
+ ))}
269
+ {listData.map((item, index) => (
270
+ <CustomListAction key={index} />
271
+ ))}
272
+ <CustomListAction />
273
+ <CustomListAction />
274
+ </List>
275
+ );
276
+ },
277
+ };
278
+
236
279
  export const WithListAction: Story = {
237
280
  parameters: {
238
281
  controls: { include: [] },
@@ -1,55 +1,22 @@
1
- import React, { ReactNode, useMemo } from 'react';
2
- import { View, ViewProps } from 'react-native';
1
+ import React, { useCallback, useRef, useState } from 'react';
2
+ import { View } from 'react-native';
3
3
  import { StyleSheet } from 'react-native-unistyles';
4
4
  import { Card } from '../Card';
5
5
  import { SectionHeader } from '../SectionHeader';
6
- import { ListContext, ListFirstItemContext } from './List.context';
6
+ import { ListContext } from './List.context';
7
7
  import type ListProps from './List.props';
8
- import { ListAction } from './ListAction';
9
- import { ListItem } from './ListItem';
10
8
 
11
- const markFirstListItem = (children: ReactNode): ViewProps['children'] => {
12
- let found = false;
13
-
14
- const recursiveClone = (children: ReactNode): ReactNode => {
15
- return React.Children.map(children, (child: ReactNode): ReactNode => {
16
- if (!React.isValidElement(child)) return child;
17
-
18
- // Check if the current element is the ListItem or ListAction and hasn't been marked yet
19
- if (!found) {
20
- if (child.type === ListItem || child.type === ListAction) {
21
- found = true;
22
- return (
23
- <ListFirstItemContext.Provider value={true}>{child}</ListFirstItemContext.Provider>
24
- );
25
- }
26
-
27
- // If the child has nested children, process them recursively
28
- if (
29
- React.isValidElement(child) &&
30
- child.props &&
31
- typeof child.props === 'object' &&
32
- child.props !== null &&
33
- 'children' in child.props &&
34
- child.props.children
35
- ) {
36
- const clonedChildren = recursiveClone((child.props as any).children);
37
- return React.cloneElement(child, { children: clonedChildren } as any);
38
- }
39
-
40
- found = true;
41
- return <ListFirstItemContext.Provider value={true}>{child}</ListFirstItemContext.Provider>;
42
- }
43
-
44
- return child;
45
- });
46
- };
47
-
48
- return recursiveClone(children) as ViewProps['children'];
49
- };
50
-
51
- const List = ({ children, heading, helperText, headerTrailingContent, ...props }: ListProps) => {
9
+ const List = ({
10
+ children,
11
+ heading,
12
+ helperText,
13
+ headerTrailingContent,
14
+ invalidText,
15
+ ...props
16
+ }: ListProps) => {
52
17
  const { loading, disabled, container = 'none' } = props;
18
+ const orderRef = useRef<string[]>([]);
19
+ const [firstItemId, setFirstItemId] = useState<string | undefined>(undefined);
53
20
  const containerToCard: {
54
21
  variant: 'subtle' | 'emphasis';
55
22
  colorScheme: 'neutralStrong' | 'neutralSubtle';
@@ -60,8 +27,27 @@ const List = ({ children, heading, helperText, headerTrailingContent, ...props }
60
27
  ? 'neutralStrong'
61
28
  : 'neutralSubtle',
62
29
  };
63
- const updatedChildren = markFirstListItem(children);
64
- const value = useMemo(() => ({ loading, disabled, container }), [loading, disabled, container]);
30
+
31
+ const registerItem = useCallback((id: string) => {
32
+ if (!orderRef.current.includes(id)) {
33
+ orderRef.current.push(id);
34
+ }
35
+ const nextFirst = orderRef.current[0];
36
+ setFirstItemId(prev => (prev === nextFirst ? prev : nextFirst));
37
+ return () => {
38
+ orderRef.current = orderRef.current.filter(currentId => currentId !== id);
39
+ const nextFirst = orderRef.current[0];
40
+ setFirstItemId(prev => (prev === nextFirst ? prev : nextFirst));
41
+ };
42
+ }, []);
43
+
44
+ const value = {
45
+ loading,
46
+ disabled,
47
+ container,
48
+ firstItemId,
49
+ registerItem,
50
+ };
65
51
  styles.useVariants({ disabled });
66
52
  return (
67
53
  <ListContext.Provider value={value}>
@@ -71,14 +57,15 @@ const List = ({ children, heading, helperText, headerTrailingContent, ...props }
71
57
  heading={heading}
72
58
  helperText={helperText}
73
59
  trailingContent={headerTrailingContent}
60
+ invalidText={invalidText}
74
61
  />
75
62
  ) : null}
76
63
  {container === 'none' ? (
77
- <View>{updatedChildren}</View>
64
+ <View>{children}</View>
78
65
  ) : (
79
- React.Children.count(updatedChildren) > 0 && (
66
+ React.Children.count(children) > 0 && (
80
67
  <Card {...containerToCard} noPadding style={styles.card}>
81
- <>{updatedChildren}</>
68
+ <>{children}</>
82
69
  </Card>
83
70
  )
84
71
  )}
@@ -1,29 +1,21 @@
1
1
  import figma from '@figma/code-connect';
2
2
  import ListAction from './ListAction';
3
3
 
4
- /**
5
- * -- This file was auto-generated by Code Connect --
6
- * `props` includes a mapping from your code props to Figma properties.
7
- * You should check this is correct, and update the `example` function
8
- * to return the code example you'd like to see in Figma
9
- */
10
-
11
4
  figma.connect(
12
5
  ListAction,
13
6
  'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR?node-id=9661%3A5128',
14
7
  {
15
8
  props: {
16
- // These props were automatically mapped based on your linked code:
17
9
  heading: figma.string('Action heading'),
18
10
  disabled: figma.enum('State', {
19
11
  Disabled: true,
20
12
  }),
21
- 'aria-disabled': figma.enum('State', {
22
- Disabled: true,
13
+ loading: figma.enum('State', {
14
+ Loading: true,
23
15
  }),
24
- // No matching props could be found for these Figma properties:
25
- // "actionHeading": figma.string('Action heading')
26
16
  },
27
- example: props => <ListAction heading={props.heading} disabled={props.disabled} />,
17
+ example: props => (
18
+ <ListAction heading={props.heading} disabled={props.disabled} loading={props.loading} />
19
+ ),
28
20
  }
29
21
  );
@@ -3,6 +3,7 @@ import type { PressableProps } from 'react-native';
3
3
  interface ListActionProps extends Omit<PressableProps, 'children'> {
4
4
  heading: string;
5
5
  disabled?: boolean;
6
+ loading?: boolean;
6
7
  variant?: 'subtle' | 'emphasis';
7
8
  }
8
9