@planningcenter/chat-react-native 1.6.2-rc.2 → 1.7.0-rc.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/build/components/conversation/message_reaction.d.ts +8 -0
  2. package/build/components/conversation/message_reaction.d.ts.map +1 -0
  3. package/build/components/conversation/message_reaction.js +44 -0
  4. package/build/components/conversation/message_reaction.js.map +1 -0
  5. package/build/components/display/badge.d.ts +58 -0
  6. package/build/components/display/badge.d.ts.map +1 -0
  7. package/build/components/display/badge.js +186 -0
  8. package/build/components/display/badge.js.map +1 -0
  9. package/build/components/display/button_color_utils.d.ts +1 -1
  10. package/build/components/display/index.d.ts +1 -0
  11. package/build/components/display/index.d.ts.map +1 -1
  12. package/build/components/display/index.js +1 -0
  13. package/build/components/display/index.js.map +1 -1
  14. package/build/hooks/use_conversation_messages.d.ts +45 -0
  15. package/build/hooks/use_conversation_messages.d.ts.map +1 -0
  16. package/build/hooks/use_conversation_messages.js +33 -0
  17. package/build/hooks/use_conversation_messages.js.map +1 -0
  18. package/build/hooks/use_suspense_api.d.ts +3 -2
  19. package/build/hooks/use_suspense_api.d.ts.map +1 -1
  20. package/build/hooks/use_suspense_api.js +3 -2
  21. package/build/hooks/use_suspense_api.js.map +1 -1
  22. package/build/navigation/index.d.ts +5 -0
  23. package/build/navigation/index.d.ts.map +1 -1
  24. package/build/navigation/index.js +9 -3
  25. package/build/navigation/index.js.map +1 -1
  26. package/build/screens/conversation_screen.d.ts.map +1 -1
  27. package/build/screens/conversation_screen.js +91 -37
  28. package/build/screens/conversation_screen.js.map +1 -1
  29. package/build/screens/design_system_screen.d.ts.map +1 -1
  30. package/build/screens/design_system_screen.js +407 -338
  31. package/build/screens/design_system_screen.js.map +1 -1
  32. package/build/screens/message_actions_screen.d.ts +10 -0
  33. package/build/screens/message_actions_screen.d.ts.map +1 -0
  34. package/build/screens/message_actions_screen.js +128 -0
  35. package/build/screens/message_actions_screen.js.map +1 -0
  36. package/build/types/api_primitives.d.ts +1 -2
  37. package/build/types/api_primitives.d.ts.map +1 -1
  38. package/build/types/api_primitives.js.map +1 -1
  39. package/build/utils/cache/index.d.ts +2 -0
  40. package/build/utils/cache/index.d.ts.map +1 -0
  41. package/build/utils/cache/index.js +2 -0
  42. package/build/utils/cache/index.js.map +1 -0
  43. package/build/utils/cache/page_mutations.d.ts +37 -0
  44. package/build/utils/cache/page_mutations.d.ts.map +1 -0
  45. package/build/utils/cache/page_mutations.js +42 -0
  46. package/build/utils/cache/page_mutations.js.map +1 -0
  47. package/build/utils/index.d.ts +1 -0
  48. package/build/utils/index.d.ts.map +1 -1
  49. package/build/utils/index.js +1 -0
  50. package/build/utils/index.js.map +1 -1
  51. package/build/utils/theme.d.ts +16 -0
  52. package/build/utils/theme.d.ts.map +1 -1
  53. package/build/utils/theme.js +31 -0
  54. package/build/utils/theme.js.map +1 -1
  55. package/build/vendor/tapestry/tokens.d.ts +30 -0
  56. package/build/vendor/tapestry/tokens.d.ts.map +1 -1
  57. package/build/vendor/tapestry/tokens.js +30 -0
  58. package/build/vendor/tapestry/tokens.js.map +1 -1
  59. package/package.json +3 -2
  60. package/src/__tests__/utils/cache/page_mutations.ts +206 -0
  61. package/src/components/conversation/message_reaction.tsx +57 -0
  62. package/src/components/display/badge.tsx +323 -0
  63. package/src/components/display/index.ts +1 -0
  64. package/src/hooks/use_conversation_messages.ts +43 -0
  65. package/src/hooks/use_suspense_api.ts +16 -2
  66. package/src/navigation/index.tsx +9 -3
  67. package/src/screens/conversation_screen.tsx +111 -40
  68. package/src/screens/design_system_screen.tsx +712 -580
  69. package/src/screens/message_actions_screen.tsx +167 -0
  70. package/src/types/api_primitives.ts +1 -1
  71. package/src/utils/cache/index.ts +1 -0
  72. package/src/utils/cache/page_mutations.ts +66 -0
  73. package/src/utils/index.ts +1 -0
  74. package/src/utils/theme.ts +47 -0
  75. package/src/vendor/tapestry/tokens.ts +30 -0
@@ -1,8 +1,11 @@
1
1
  import React, { useState } from 'react';
2
2
  import { Alert, Pressable, ScrollView, StyleSheet, View } from 'react-native';
3
3
  import { useTheme } from '../hooks';
4
- import { Avatar, AvatarGroup, Button, Heading, Icon, IconButton, Image, Spinner, Switch, Text, TextButton, TextInlineButton, } from '../components/display';
4
+ import { Avatar, AvatarGroup, Badge, Button, Heading, Icon, IconButton, Image, Spinner, Switch, Text, TextButton, TextInlineButton, } from '../components/display';
5
5
  import { space, MAX_FONT_SIZE_MULTIPLIER, platformPressedOpacityStyle, platformFontWeightMedium, } from '../utils';
6
+ // =================================
7
+ // ====== Docs Utils ===============
8
+ // =================================
6
9
  const URL = {
7
10
  image: 'https://picsum.photos/seed/picsum/200',
8
11
  broken: 'https://broken.url',
@@ -22,349 +25,412 @@ const URL = {
22
25
  ],
23
26
  };
24
27
  const buttonPress = () => Alert.alert('Button clicked');
28
+ // =================================
29
+ // ====== Component ================
30
+ // =================================
25
31
  export function DesignSystemScreen() {
32
+ const styles = useStyles();
33
+ return (<ScrollView contentContainerStyle={styles.container} style={styles.scrollView}>
34
+ <ThemeSection />
35
+ {/* TODO: Enable & update when we install @planningcenter/tapestry */}
36
+ {/* <TokensSection /> */}
37
+ <IndicatorsSection />
38
+ <HeadingTextSection />
39
+ <PressablesSection />
40
+ <ImageIconsSection />
41
+ <FormControlsSection />
42
+ <StatusComponentsSection isLast/>
43
+ </ScrollView>);
44
+ }
45
+ function ThemeSection({ isLast }) {
46
+ return (<CollapsableSection title="Theme" isLast={isLast}>
47
+ <TextGroup>
48
+ <Text>There are four main parts to our theming system…</Text>
49
+ <TextRow>
50
+ <Heading variant="h3">Default theme</Heading>
51
+ <Text>
52
+ Start at `src/utils/theme` when adding new theme values or checking what target apps
53
+ have access to. The file has more instructions and examples.
54
+ </Text>
55
+ <Text>At a high level, it provides…</Text>
56
+ <TextListItem label="1.">
57
+ Access to consuming app targets of what theme values that they can be customized.
58
+ </TextListItem>
59
+ <TextListItem label="2.">
60
+ Fallback values for our components to use if the values weren't overriden.
61
+ </TextListItem>
62
+ </TextRow>
63
+ <TextRow>
64
+ <Heading variant="h3">Customizing the theme</Heading>
65
+ <Text>
66
+ Apps can override any default theme value by passing a `theme` object to our
67
+ `ChatProvider` that holds a `theme` and a `colorScheme`.
68
+ </Text>
69
+ <Text>
70
+ Currently types can be enforced by setting the parent theme object to
71
+ `CreateChatThemeProps`.
72
+ </Text>
73
+ <Text variant="footnote">
74
+ Example setup: `apps/mobile/src/context/chat_context_provider.tsx`
75
+ </Text>
76
+ </TextRow>
77
+ <TextRow>
78
+ <Heading variant="h3">Merged theme</Heading>
79
+ <Text>
80
+ In `src/contexts/chat_context.tsx` we merge the default theme and any custom values
81
+ coming from a product target with the `useCreateChatTheme` hook. It creates a single
82
+ `ChatTheme` type.
83
+ </Text>
84
+ </TextRow>
85
+ <TextRow>
86
+ <Heading variant="h3">Using theme values</Heading>
87
+ <Text>
88
+ Inside of our own `chat-react-native` components we can access the merged `ChatTheme`
89
+ object via our own `useTheme` hook.
90
+ </Text>
91
+ <Text variant="footnote">Example setup: `src/components/display/button.tsx`</Text>
92
+ </TextRow>
93
+ </TextGroup>
94
+ </CollapsableSection>);
95
+ }
96
+ // function TokensSection({ isLast }: SectionProps) {
97
+ // return (
98
+ // <CollapsableSection title="Tokens" isLast={isLast}>
99
+ // <TextGroup>
100
+ // <TextRow>
101
+ // <Heading variant="h3">What are they?</Heading>
102
+ // <Text>
103
+ // Tokens are UX approved CSS values that we can use to style our UI in a consistent way.
104
+ // (e.g. colors, spacing amounts, and font weights.)
105
+ // </Text>
106
+ // </TextRow>
107
+ // <TextRow>
108
+ // <Heading variant="h3">Where do they come from?</Heading>
109
+ // <Text>
110
+ // Tokens primarily come from our internal `@planningcenter/tapestry` package. However, at
111
+ // this time the package only support light mode colors, so Chat uses a workaround.
112
+ // </Text>
113
+ // <Text>Color-based tokens are infused into our theming system with two local files…</Text>
114
+ // <TextListItem label="1. `src/vendor/tapestry/tokens`:">
115
+ // Primitive color values are stored* here. Primitives capture light or dark mode values,
116
+ // but don't take into account the devices's colors scheme.
117
+ // </TextListItem>
118
+ // <TextListItem label="2. `src/vendor/tapestry/alias_tokens_color_map`:">
119
+ // Alias tokens reference the primitive color values token file* in light and dark mode
120
+ // specfic objects. Our theming system then selects the right color to use based on the
121
+ // device's color scheme.
122
+ // </TextListItem>
123
+ // <Text variant="footnote">
124
+ // *If available, reference the color from the Tapestry package instead.
125
+ // </Text>
126
+ // </TextRow>
127
+ // <TextRow>
128
+ // <Heading variant="h3">How do we use them?</Heading>
129
+ // <Text>There are two places to reference tokens at this time…</Text>
130
+ // <TextListItem label="• Color tokens:">
131
+ // Reference them from our internal `useTheme` hook to ensure the correct light or dark
132
+ // mode color token is used.
133
+ // </TextListItem>
134
+ // <TextListItem label="• All other tokens:">
135
+ // Use the `computedToken` function from `@planningcenter/tapestry` by passing it a string
136
+ // with the token's name. This function provides Typescript support and maps to the token's
137
+ // raw CSS value. There is another function called `token`, but it maps to CSS custom
138
+ // properties which React Native doesn't support.
139
+ // </TextListItem>
140
+ // </TextRow>
141
+ // </TextGroup>
142
+ // </CollapsableSection>
143
+ // )
144
+ // }
145
+ function IndicatorsSection({ isLast }) {
146
+ const styles = useStyles();
147
+ return (<CollapsableSection title="Indicators" isLast={isLast}>
148
+ <Group title="Spinner" description="Loading indicators that can be used within or close to atomic components. Not intended for full-screen loading.">
149
+ <Row style={styles.spinnerContainer}>
150
+ <Spinner size={24}/>
151
+ </Row>
152
+ </Group>
153
+ </CollapsableSection>);
154
+ }
155
+ function HeadingTextSection({ isLast }) {
156
+ return (<CollapsableSection title="Heading & Text" isLast={isLast}>
157
+ <Group title="Heading" description="Use for headings & titles as it includes the a11y 'header' role. Change the size and style with the h1-h4 variants.">
158
+ <Row>
159
+ <Heading>Heading 1</Heading>
160
+ <Heading variant="h2">Heading 2</Heading>
161
+ <Heading variant="h3">Heading 3</Heading>
162
+ <Heading variant="h4">Heading 4</Heading>
163
+ </Row>
164
+ </Group>
165
+ <Group title="Text" description="Use for body copy and supporting text.">
166
+ <Row>
167
+ <Text>Plain text</Text>
168
+ <Text variant="secondary">Secondary</Text>
169
+ <Text variant="tertiary">Tertiary</Text>
170
+ <Text variant="footnote">Footnote</Text>
171
+ </Row>
172
+ </Group>
173
+ </CollapsableSection>);
174
+ }
175
+ function PressablesSection({ isLast }) {
176
+ const styles = useStyles();
177
+ return (<CollapsableSection title="Pressables" isLast={isLast}>
178
+ <Group title="Button" description="Feature fill and outline variants for primary and danger usecases, along with disabled & loading states. Optionally shows icons to the left and right of the text.">
179
+ <Row>
180
+ <Button onPress={buttonPress} title="Default" size="sm"/>
181
+ <Button onPress={buttonPress} title="Default" size="md"/>
182
+ <Button onPress={buttonPress} title="Danger" appearance="danger" size="lg"/>
183
+ </Row>
184
+ <Row>
185
+ <Button disabled onPress={buttonPress} title="Disabled" size="sm"/>
186
+ <Button disabled onPress={buttonPress} title="Disabled" size="md"/>
187
+ <Button disabled onPress={buttonPress} title="Disabled" appearance="danger" size="lg"/>
188
+ </Row>
189
+ <Row>
190
+ <Button onPress={buttonPress} title="Default" size="sm" iconNameLeft="general.plus" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
191
+ <Button onPress={buttonPress} title="Default" size="md" iconNameRight="churchCenter.sort" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
192
+ <Button onPress={buttonPress} title="Danger" appearance="danger" size="lg" iconNameLeft="groups.cards" iconNameRight="general.downChevron" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
193
+ </Row>
194
+ <Row>
195
+ <Button loading onPress={buttonPress} title="Default" size="sm"/>
196
+ <Button loading onPress={buttonPress} title="Default" size="md"/>
197
+ <Button loading onPress={buttonPress} title="Danger" appearance="danger" size="lg"/>
198
+ </Row>
199
+ <Row>
200
+ <Button onPress={buttonPress} title="Default" size="sm" variant="outline"/>
201
+ <Button onPress={buttonPress} title="Default" size="md" variant="outline"/>
202
+ <Button onPress={buttonPress} title="Danger" appearance="danger" size="lg" variant="outline"/>
203
+ </Row>
204
+ <Row>
205
+ <Button onPress={buttonPress} title="Default" size="sm" variant="outline" iconNameLeft="general.plus" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
206
+ <Button onPress={buttonPress} title="Default" size="md" variant="outline" iconNameRight="churchCenter.sort" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
207
+ <Button onPress={buttonPress} title="Danger" appearance="danger" size="lg" variant="outline" iconNameLeft="groups.cards" iconNameRight="general.downChevron" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
208
+ </Row>
209
+ <Row>
210
+ <Button disabled onPress={buttonPress} title="Disabled" size="sm" variant="outline" iconNameLeft="general.plus" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
211
+ <Button disabled onPress={buttonPress} title="Disabled" size="md" variant="outline" iconNameRight="churchCenter.sort" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
212
+ <Button disabled onPress={buttonPress} title="Disabled" appearance="danger" size="lg" variant="outline" iconNameLeft="groups.cards" iconNameRight="general.downChevron" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
213
+ </Row>
214
+ <Row>
215
+ <Button loading onPress={buttonPress} title="Default" size="sm" variant="outline" iconNameLeft="general.plus" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
216
+ <Button loading onPress={buttonPress} title="Default" size="md" variant="outline" iconNameRight="churchCenter.sort" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
217
+ <Button loading onPress={buttonPress} title="Danger" appearance="danger" size="lg" variant="outline" iconNameLeft="groups.cards" iconNameRight="general.downChevron" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
218
+ </Row>
219
+ </Group>
220
+ <Group title="IconButton" description="Supports different appearances, sizes, along with loading & disabled states. Use `iconStyle` for custom colors and font sizes. Requires `accessibilityLabel` as icon's don't provide context to screen readers.">
221
+ <Row>
222
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" size="md"/>
223
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" appearance="danger" size="lg"/>
224
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" appearance="interaction" size="xl"/>
225
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" iconStyle={styles.customIconButtonColor} size="xxl"/>
226
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" size="xxxl"/>
227
+ </Row>
228
+ <Row>
229
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" size="md" disabled/>
230
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" appearance="danger" size="lg" disabled/>
231
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" appearance="interaction" size="xl" disabled/>
232
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" iconStyle={styles.customIconButtonColor} size="xxl" disabled/>
233
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" size="xxxl" disabled/>
234
+ </Row>
235
+ <Row>
236
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" size="md" loading/>
237
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" appearance="danger" size="lg" loading/>
238
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" appearance="interaction" size="xl" loading/>
239
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" iconStyle={styles.customIconButtonColor} size="xxl" loading/>
240
+ <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" size="xxxl" loading/>
241
+ </Row>
242
+ </Group>
243
+ <Group title="TextButton" description="Pressable text with default & danger appearance options. Can be disabled and accept `Text` variance props.">
244
+ <Row>
245
+ <TextButton onPress={buttonPress}>Default</TextButton>
246
+ <TextButton onPress={buttonPress} variant="secondary">
247
+ Default
248
+ </TextButton>
249
+ <TextButton onPress={buttonPress} variant="tertiary">
250
+ Default
251
+ </TextButton>
252
+ <TextButton onPress={buttonPress} variant="footnote">
253
+ Default
254
+ </TextButton>
255
+ </Row>
256
+ <Row>
257
+ <TextButton onPress={buttonPress} appearance="danger">
258
+ Danger
259
+ </TextButton>
260
+ <TextButton onPress={buttonPress} variant="secondary" appearance="danger">
261
+ Danger
262
+ </TextButton>
263
+ <TextButton onPress={buttonPress} variant="tertiary" appearance="danger">
264
+ Danger
265
+ </TextButton>
266
+ <TextButton onPress={buttonPress} variant="footnote" appearance="danger">
267
+ Danger
268
+ </TextButton>
269
+ </Row>
270
+ <Row>
271
+ <TextButton onPress={buttonPress} disabled>
272
+ Disabled
273
+ </TextButton>
274
+ <TextButton onPress={buttonPress} variant="secondary" disabled>
275
+ Disabled
276
+ </TextButton>
277
+ <TextButton onPress={buttonPress} variant="tertiary" disabled>
278
+ Disabled
279
+ </TextButton>
280
+ <TextButton onPress={buttonPress} variant="footnote" disabled>
281
+ Disabled
282
+ </TextButton>
283
+ </Row>
284
+ </Group>
285
+ <Group title="TextInlineButton" description="Supports nesting within `Text`. Temporary component until React Native fixes a layout bug in `Pressable` which used in `TextButton`.">
286
+ <Row style={styles.alignRowLeft}>
287
+ <Text>
288
+ This text is next to{' '}
289
+ <TextInlineButton onPress={buttonPress}>default button text</TextInlineButton> Lorem
290
+ ipsum dolor{' '}
291
+ <TextInlineButton onPress={buttonPress} appearance="danger">
292
+ danger button text
293
+ </TextInlineButton>{' '}
294
+ consectetur{' '}
295
+ <TextInlineButton onPress={buttonPress} disabled>
296
+ disabled button text
297
+ </TextInlineButton>{' '}
298
+ elit.
299
+ </Text>
300
+ <Text variant="secondary">
301
+ This text is next to{' '}
302
+ <TextInlineButton variant="secondary" onPress={buttonPress}>
303
+ default button text
304
+ </TextInlineButton>{' '}
305
+ Lorem ipsum dolor{' '}
306
+ <TextInlineButton variant="secondary" onPress={buttonPress} appearance="danger">
307
+ danger button text
308
+ </TextInlineButton>{' '}
309
+ consectetur{' '}
310
+ <TextInlineButton variant="secondary" onPress={buttonPress} disabled>
311
+ disabled button text
312
+ </TextInlineButton>{' '}
313
+ elit.
314
+ </Text>
315
+ <Text variant="tertiary">
316
+ This text is next to{' '}
317
+ <TextInlineButton variant="tertiary" onPress={buttonPress}>
318
+ default button text
319
+ </TextInlineButton>{' '}
320
+ Lorem ipsum dolor{' '}
321
+ <TextInlineButton variant="tertiary" onPress={buttonPress} appearance="danger">
322
+ danger button text
323
+ </TextInlineButton>{' '}
324
+ consectetur{' '}
325
+ <TextInlineButton variant="tertiary" onPress={buttonPress} disabled>
326
+ disabled button text
327
+ </TextInlineButton>{' '}
328
+ elit.
329
+ </Text>
330
+ <Text variant="footnote">
331
+ This text is next to{' '}
332
+ <TextInlineButton variant="footnote" onPress={buttonPress}>
333
+ default button text
334
+ </TextInlineButton>{' '}
335
+ Lorem ipsum dolor{' '}
336
+ <TextInlineButton variant="footnote" onPress={buttonPress} appearance="danger">
337
+ danger button text
338
+ </TextInlineButton>{' '}
339
+ consectetur{' '}
340
+ <TextInlineButton variant="footnote" onPress={buttonPress} disabled>
341
+ disabled button text
342
+ </TextInlineButton>{' '}
343
+ elit.
344
+ </Text>
345
+ </Row>
346
+ </Group>
347
+ </CollapsableSection>);
348
+ }
349
+ function ImageIconsSection({ isLast }) {
26
350
  const styles = useStyles();
27
351
  const { colors } = useTheme();
352
+ return (<CollapsableSection title="Images & Icons" isLast={isLast}>
353
+ <Group title="Image" description="Foundational way of displaying images. Loading or broken images will fallback to show a spinner. Hide decortive images from screen readers with `alt=''`.">
354
+ <Row>
355
+ <Image source={{ uri: URL.broken }} style={styles.image} alt="Mountain sunrise"/>
356
+ <Image source={{ uri: URL.image }} style={styles.image} alt="Mountain sunrise"/>
357
+ <Image source={{ uri: URL.image }} style={styles.image} alt=""/>
358
+ </Row>
359
+ </Group>
360
+ <Group title="Avatar" description='Displays the profile image for a user in different sizes and has a loading fallback. Can optionally show an online/offline "presence" indicator.'>
361
+ <Row>
362
+ <Avatar sourceUri={URL.broken}/>
363
+ <Avatar size="md" sourceUri={URL.avatar_fallback}/>
364
+ <Avatar sourceUri={URL.avatar}/>
365
+ </Row>
366
+ <Row>
367
+ <Avatar presence="offline" sourceUri={URL.broken}/>
368
+ <Avatar presence="online" size="md" sourceUri={URL.avatar_fallback}/>
369
+ <Avatar presence="offline" sourceUri={URL.avatar}/>
370
+ </Row>
371
+ </Group>
372
+ <Group title="AvatarGroup" description="Shows 1-4 images in a grid at different sizes. Loading fallback shows until all images successfully load.">
373
+ <Row>
374
+ <AvatarGroup sourceUris={[URL.broken]}/>
375
+ <AvatarGroup sourceUris={[URL.broken, URL.broken, ...URL.two_avatars]}/>
376
+ <AvatarGroup sourceUris={[URL.avatar]}/>
377
+ <AvatarGroup sourceUris={URL.two_avatars}/>
378
+ <AvatarGroup sourceUris={URL.three_avatars}/>
379
+ <AvatarGroup sourceUris={URL.four_avatars}/>
380
+ </Row>
381
+ </Group>
382
+ <Group title="Icon" description="Displays any icon from @planningcenter/icons. Missing icons will fallback to a grey circle. Styling with `fontSize` will allow it to scale with the device's text a11y size.">
383
+ <Row>
384
+ <Icon name="missingIcon" size={20}/>
385
+ <Icon name="general.textMessage" size={20}/>
386
+ <Icon name="general.bell" size={20} color={colors.needsDesignPass}/>
387
+ <Icon name="churchCenter.sort" style={styles.icon}/>
388
+ </Row>
389
+ </Group>
390
+ </CollapsableSection>);
391
+ }
392
+ function FormControlsSection({ isLast }) {
28
393
  const [switchEnabled, setSwitchEnabled] = useState(false);
29
- return (<ScrollView contentContainerStyle={styles.container} style={styles.scrollView}>
30
- <CollapsableSection title="Molecules">
394
+ return (<CollapsableSection title="Form Controls" isLast={isLast}>
395
+ <Group title="Switch" description="Use to toggle a boolean value for some sort of contained setting. (ie. Muting a conversation) This is a light wrapper that takes into account themed colors.">
31
396
  <Row>
32
- <Text>🚧 Coming soon! 🚧</Text>
397
+ <Switch value={switchEnabled} onValueChange={value => setSwitchEnabled(value)}/>
398
+ <Switch disabled/>
33
399
  </Row>
34
- </CollapsableSection>
35
- <CollapsableSection title="Atoms">
36
- <Group title="Spinner" description="Loading indicators that can be used within or close to atomic components. Not intended for full-screen loading.">
37
- <Row style={styles.spinnerContainer}>
38
- <Spinner size={24}/>
39
- </Row>
40
- </Group>
41
- <Group title="Image" description="Foundational way of displaying images. Loading or broken images will fallback to show a spinner. Hide decortive images from screen readers with `alt=''`.">
42
- <Row>
43
- <Image source={{ uri: URL.broken }} style={styles.image} alt="Mountain sunrise"/>
44
- <Image source={{ uri: URL.image }} style={styles.image} alt="Mountain sunrise"/>
45
- <Image source={{ uri: URL.image }} style={styles.image} alt=""/>
46
- </Row>
47
- </Group>
48
- <Group title="Avatar" description='Displays the profile image for a user in different sizes and has a loading fallback. Can optionally show an online/offline "presence" indicator.'>
49
- <Row>
50
- <Avatar sourceUri={URL.broken}/>
51
- <Avatar size="md" sourceUri={URL.avatar_fallback}/>
52
- <Avatar sourceUri={URL.avatar}/>
53
- </Row>
54
- <Row>
55
- <Avatar presence="offline" sourceUri={URL.broken}/>
56
- <Avatar presence="online" size="md" sourceUri={URL.avatar_fallback}/>
57
- <Avatar presence="offline" sourceUri={URL.avatar}/>
58
- </Row>
59
- </Group>
60
- <Group title="AvatarGroup" description="Shows 1-4 images in a grid at different sizes. Loading fallback shows until all images successfully load.">
61
- <Row>
62
- <AvatarGroup sourceUris={[URL.broken]}/>
63
- <AvatarGroup sourceUris={[URL.broken, URL.broken, ...URL.two_avatars]}/>
64
- <AvatarGroup sourceUris={[URL.avatar]}/>
65
- <AvatarGroup sourceUris={URL.two_avatars}/>
66
- <AvatarGroup sourceUris={URL.three_avatars}/>
67
- <AvatarGroup sourceUris={URL.four_avatars}/>
68
- </Row>
69
- </Group>
70
- <Group title="Icon" description="Displays any icon from @planningcenter/icons. Missing icons will fallback to a grey circle. Styling with `fontSize` will allow it to scale with the device's text a11y size.">
71
- <Row>
72
- <Icon name="missingIcon" size={20}/>
73
- <Icon name="general.textMessage" size={20}/>
74
- <Icon name="general.bell" size={20} color={colors.needsDesignPass}/>
75
- <Icon name="churchCenter.sort" style={styles.icon}/>
76
- </Row>
77
- </Group>
78
- <Group title="Button" description="Feature fill and outline variants for primary and danger usecases, along with disabled & loading states. Optionally shows icons to the left and right of the text.">
79
- <Row>
80
- <Button onPress={buttonPress} title="Default" size="sm"/>
81
- <Button onPress={buttonPress} title="Default" size="md"/>
82
- <Button onPress={buttonPress} title="Danger" appearance="danger" size="lg"/>
83
- </Row>
84
- <Row>
85
- <Button disabled onPress={buttonPress} title="Disabled" size="sm"/>
86
- <Button disabled onPress={buttonPress} title="Disabled" size="md"/>
87
- <Button disabled onPress={buttonPress} title="Disabled" appearance="danger" size="lg"/>
88
- </Row>
89
- <Row>
90
- <Button onPress={buttonPress} title="Default" size="sm" iconNameLeft="general.plus" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
91
- <Button onPress={buttonPress} title="Default" size="md" iconNameRight="churchCenter.sort" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
92
- <Button onPress={buttonPress} title="Danger" appearance="danger" size="lg" iconNameLeft="groups.cards" iconNameRight="general.downChevron" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
93
- </Row>
94
- <Row>
95
- <Button loading onPress={buttonPress} title="Default" size="sm"/>
96
- <Button loading onPress={buttonPress} title="Default" size="md"/>
97
- <Button loading onPress={buttonPress} title="Danger" appearance="danger" size="lg"/>
98
- </Row>
99
- <Row>
100
- <Button onPress={buttonPress} title="Default" size="sm" variant="outline"/>
101
- <Button onPress={buttonPress} title="Default" size="md" variant="outline"/>
102
- <Button onPress={buttonPress} title="Danger" appearance="danger" size="lg" variant="outline"/>
103
- </Row>
104
- <Row>
105
- <Button onPress={buttonPress} title="Default" size="sm" variant="outline" iconNameLeft="general.plus" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
106
- <Button onPress={buttonPress} title="Default" size="md" variant="outline" iconNameRight="churchCenter.sort" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
107
- <Button onPress={buttonPress} title="Danger" appearance="danger" size="lg" variant="outline" iconNameLeft="groups.cards" iconNameRight="general.downChevron" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
108
- </Row>
109
- <Row>
110
- <Button disabled onPress={buttonPress} title="Disabled" size="sm" variant="outline" iconNameLeft="general.plus" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
111
- <Button disabled onPress={buttonPress} title="Disabled" size="md" variant="outline" iconNameRight="churchCenter.sort" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
112
- <Button disabled onPress={buttonPress} title="Disabled" appearance="danger" size="lg" variant="outline" iconNameLeft="groups.cards" iconNameRight="general.downChevron" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
113
- </Row>
114
- <Row>
115
- <Button loading onPress={buttonPress} title="Default" size="sm" variant="outline" iconNameLeft="general.plus" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
116
- <Button loading onPress={buttonPress} title="Default" size="md" variant="outline" iconNameRight="churchCenter.sort" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
117
- <Button loading onPress={buttonPress} title="Danger" appearance="danger" size="lg" variant="outline" iconNameLeft="groups.cards" iconNameRight="general.downChevron" maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER}/>
118
- </Row>
119
- </Group>
120
- <Group title="IconButton" description="Supports different appearances, sizes, along with loading & disabled states. Use `iconStyle` for custom colors and font sizes. Requires `accessibilityLabel` as icon's don't provide context to screen readers.">
121
- <Row>
122
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" size="md"/>
123
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" appearance="danger" size="lg"/>
124
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" appearance="interaction" size="xl"/>
125
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" iconStyle={styles.customIconButtonColor} size="xxl"/>
126
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" size="xxxl"/>
127
- </Row>
128
- <Row>
129
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" size="md" disabled/>
130
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" appearance="danger" size="lg" disabled/>
131
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" appearance="interaction" size="xl" disabled/>
132
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" iconStyle={styles.customIconButtonColor} size="xxl" disabled/>
133
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" size="xxxl" disabled/>
134
- </Row>
135
- <Row>
136
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" size="md" loading/>
137
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" appearance="danger" size="lg" loading/>
138
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" appearance="interaction" size="xl" loading/>
139
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" iconStyle={styles.customIconButtonColor} size="xxl" loading/>
140
- <IconButton onPress={buttonPress} name="general.paperclip" accessibilityLabel="Add attachment" accessibilityHint="Opens your device's image gallary" size="xxxl" loading/>
141
- </Row>
142
- </Group>
143
- <Group title="TextButton" description="Pressable text with default & danger appearance options. Can be disabled and accept `Text` variance props.">
144
- <Row>
145
- <TextButton onPress={buttonPress}>Default</TextButton>
146
- <TextButton onPress={buttonPress} variant="secondary">
147
- Default
148
- </TextButton>
149
- <TextButton onPress={buttonPress} variant="tertiary">
150
- Default
151
- </TextButton>
152
- <TextButton onPress={buttonPress} variant="footnote">
153
- Default
154
- </TextButton>
155
- </Row>
156
- <Row>
157
- <TextButton onPress={buttonPress} appearance="danger">
158
- Danger
159
- </TextButton>
160
- <TextButton onPress={buttonPress} variant="secondary" appearance="danger">
161
- Danger
162
- </TextButton>
163
- <TextButton onPress={buttonPress} variant="tertiary" appearance="danger">
164
- Danger
165
- </TextButton>
166
- <TextButton onPress={buttonPress} variant="footnote" appearance="danger">
167
- Danger
168
- </TextButton>
169
- </Row>
170
- <Row>
171
- <TextButton onPress={buttonPress} disabled>
172
- Disabled
173
- </TextButton>
174
- <TextButton onPress={buttonPress} variant="secondary" disabled>
175
- Disabled
176
- </TextButton>
177
- <TextButton onPress={buttonPress} variant="tertiary" disabled>
178
- Disabled
179
- </TextButton>
180
- <TextButton onPress={buttonPress} variant="footnote" disabled>
181
- Disabled
182
- </TextButton>
183
- </Row>
184
- </Group>
185
- <Group title="TextInlineButton" description="Supports nesting within `Text`. Temporary component until React Native fixes a layout bug in `Pressable` which used in `TextButton`.">
186
- <Row style={styles.alignRowLeft}>
187
- <Text>
188
- This text is next to{' '}
189
- <TextInlineButton onPress={buttonPress}>default button text</TextInlineButton> Lorem
190
- ipsum dolor{' '}
191
- <TextInlineButton onPress={buttonPress} appearance="danger">
192
- danger button text
193
- </TextInlineButton>{' '}
194
- consectetur{' '}
195
- <TextInlineButton onPress={buttonPress} disabled>
196
- disabled button text
197
- </TextInlineButton>{' '}
198
- elit.
199
- </Text>
200
- <Text variant="secondary">
201
- This text is next to{' '}
202
- <TextInlineButton variant="secondary" onPress={buttonPress}>
203
- default button text
204
- </TextInlineButton>{' '}
205
- Lorem ipsum dolor{' '}
206
- <TextInlineButton variant="secondary" onPress={buttonPress} appearance="danger">
207
- danger button text
208
- </TextInlineButton>{' '}
209
- consectetur{' '}
210
- <TextInlineButton variant="secondary" onPress={buttonPress} disabled>
211
- disabled button text
212
- </TextInlineButton>{' '}
213
- elit.
214
- </Text>
215
- <Text variant="tertiary">
216
- This text is next to{' '}
217
- <TextInlineButton variant="tertiary" onPress={buttonPress}>
218
- default button text
219
- </TextInlineButton>{' '}
220
- Lorem ipsum dolor{' '}
221
- <TextInlineButton variant="tertiary" onPress={buttonPress} appearance="danger">
222
- danger button text
223
- </TextInlineButton>{' '}
224
- consectetur{' '}
225
- <TextInlineButton variant="tertiary" onPress={buttonPress} disabled>
226
- disabled button text
227
- </TextInlineButton>{' '}
228
- elit.
229
- </Text>
230
- <Text variant="footnote">
231
- This text is next to{' '}
232
- <TextInlineButton variant="footnote" onPress={buttonPress}>
233
- default button text
234
- </TextInlineButton>{' '}
235
- Lorem ipsum dolor{' '}
236
- <TextInlineButton variant="footnote" onPress={buttonPress} appearance="danger">
237
- danger button text
238
- </TextInlineButton>{' '}
239
- consectetur{' '}
240
- <TextInlineButton variant="footnote" onPress={buttonPress} disabled>
241
- disabled button text
242
- </TextInlineButton>{' '}
243
- elit.
244
- </Text>
245
- </Row>
246
- </Group>
247
- <Group title="Heading" description="Use for headings & titles as it includes the a11y 'header' role. Change the size and style with the h1-h4 variants.">
248
- <Row>
249
- <Heading>Heading 1</Heading>
250
- <Heading variant="h2">Heading 2</Heading>
251
- <Heading variant="h3">Heading 3</Heading>
252
- <Heading variant="h4">Heading 4</Heading>
253
- </Row>
254
- </Group>
255
- <Group title="Text" description="Use for body copy and supporting text.">
256
- <Row>
257
- <Text>Plain text</Text>
258
- <Text variant="secondary">Secondary</Text>
259
- <Text variant="tertiary">Tertiary</Text>
260
- <Text variant="footnote">Footnote</Text>
261
- </Row>
262
- </Group>
263
- <Group title="Switch" description="Use to toggle a boolean value for some sort of contained setting. (ie. Muting a conversation) This is a light wrapper that takes into account themed colors.">
264
- <Row>
265
- <Switch value={switchEnabled} onValueChange={value => setSwitchEnabled(value)}/>
266
- <Switch disabled/>
267
- </Row>
268
- </Group>
269
- </CollapsableSection>
270
- <CollapsableSection title="Theme" isLast>
271
- <TextGroup>
272
- <Text>There are four main parts to our theming system…</Text>
273
- <TextRow>
274
- <Heading variant="h3">Default theme</Heading>
275
- <Text>
276
- Start at `src/utils/theme` when adding new theme values. The file has more
277
- instructions and examples.
278
- </Text>
279
- <Text>At a high level, it provides…</Text>
280
- <TextListItem label="1.">
281
- Access to consuming app targets of what theme values that they can be customized.
282
- </TextListItem>
283
- <TextListItem label="2.">
284
- Fallback values for our components to use if the values weren't overriden.
285
- </TextListItem>
286
- </TextRow>
287
- <TextRow>
288
- <Heading variant="h3">Customizing the theme</Heading>
289
- <Text>
290
- Apps can override any default theme value by passing a `theme` object to our
291
- `ChatProvider` that holds a `theme` and a `colorScheme`.
292
- </Text>
293
- <Text>
294
- Currently types can be enforced by setting the parent theme object to
295
- `CreateChatThemeProps`.
296
- </Text>
297
- <Text variant="footnote">
298
- Example setup: `apps/mobile/src/context/chat_context_provider.tsx`
299
- </Text>
300
- </TextRow>
301
- <TextRow>
302
- <Heading variant="h3">Merged theme</Heading>
303
- <Text>
304
- In `src/contexts/chat_context.tsx` we merge the default theme and any custom values
305
- coming from a product target with the `useCreateChatTheme` hook. It creates a single
306
- `ChatTheme` type.
307
- </Text>
308
- </TextRow>
309
- <TextRow>
310
- <Heading variant="h3">Using theme values</Heading>
311
- <Text>
312
- Inside of our own `chat-react-native` components we can access the merged `ChatTheme`
313
- object via our own `useTheme` hook.
314
- </Text>
315
- <Text variant="footnote">Example setup: `src/components/display/button.tsx`</Text>
316
- </TextRow>
317
- </TextGroup>
318
- </CollapsableSection>
319
- {/* TODO: Enable & update when we install @planningcenter/tapestry */}
320
- {/* <CollapsableSection title="Tokens" isLast>
321
- <TextGroup>
322
- <TextRow>
323
- <Heading variant="h3">What are they?</Heading>
324
- <Text>
325
- Tokens are UX approved CSS values that we can use to style our UI in a consistent way.
326
- (e.g. colors, spacing amounts, and font weights.)
327
- </Text>
328
- </TextRow>
329
- <TextRow>
330
- <Heading variant="h3">Where do they come from?</Heading>
331
- <Text>
332
- Tokens primarily come from our internal `@planningcenter/tapestry` package. However,
333
- at this time the package only support light mode colors, so Chat uses a workaround.
334
- </Text>
335
- <Text>
336
- Color-based tokens are infused into our theming system with two local files…
337
- </Text>
338
- <TextListItem label="1. `src/vendor/tapestry/tokens`:">
339
- Primitive color values are stored* here. Primitives capture light or dark mode values,
340
- but don't take into account the devices's colors scheme.
341
- </TextListItem>
342
- <TextListItem label="2. `src/vendor/tapestry/alias_tokens_color_map`:">
343
- Alias tokens reference the primitive color values token file* in light and dark mode
344
- specfic objects. Our theming system then selects the right color to use based on the
345
- device's color scheme.
346
- </TextListItem>
347
- <Text variant="footnote">
348
- *If available, reference the color from the Tapestry package instead.
349
- </Text>
350
- </TextRow>
351
- <TextRow>
352
- <Heading variant="h3">How do we use them?</Heading>
353
- <Text>There are two places to reference tokens at this time…</Text>
354
- <TextListItem label="• Color tokens:">
355
- Reference them from our internal `useTheme` hook to ensure the correct light or dark
356
- mode color token is used.
357
- </TextListItem>
358
- <TextListItem label="• All other tokens:">
359
- Use the `computedToken` function from `@planningcenter/tapestry` by passing it a
360
- string with the token's name. This function provides Typescript support and maps to
361
- the token's raw CSS value. There is another function called `token`, but it maps to
362
- CSS custom properties which React Native doesn't support.
363
- </TextListItem>
364
- </TextRow>
365
- </TextGroup>
366
- </CollapsableSection> */}
367
- </ScrollView>);
400
+ </Group>
401
+ </CollapsableSection>);
402
+ }
403
+ function StatusComponentsSection({ isLast }) {
404
+ return (<CollapsableSection title="Status components" isLast={isLast}>
405
+ <Group title="Badge" description="Badge that can convey a status and show an icon. It also supports a meta label and product logo for the conversation list and conversation header. Target products can change colors and hide the logo via theming.">
406
+ <Row>
407
+ <Badge label="Neutral" appearance="neutral"/>
408
+ <Badge label="Error" appearance="error"/>
409
+ <Badge label="Info" appearance="info"/>
410
+ <Badge label="Success" appearance="success"/>
411
+ <Badge label="Warning" appearance="warning"/>
412
+ </Row>
413
+ <Row>
414
+ <Badge label="Neutral" appearance="neutral" iconName="general.star"/>
415
+ <Badge label="Error" appearance="error" iconName="general.exclamationTriangle"/>
416
+ <Badge label="Info" appearance="info" iconName="general.outlinedInfoCircle"/>
417
+ <Badge label="Success" appearance="success" iconName="general.check"/>
418
+ <Badge label="Warning" appearance="warning" iconName="general.shieldExclamation"/>
419
+ </Row>
420
+ <Row>
421
+ <Badge variant="meta" productLogoName="groups" label="Group" metaLabel="Worlds longest group name that will probably overflow if it gets very much longer"/>
422
+ <Badge variant="meta" productLogoName="groups" label="Group" metaLabel="Young adults"/>
423
+ <Badge variant="meta" productLogoName="services" label="Plan" metaLabel="June 19, 2025"/>
424
+ <Badge variant="meta" productLogoName="services" label="Team"/>
425
+ </Row>
426
+ <Row>
427
+ <Badge variant="metaSubtle" productLogoName="groups" label="Group" metaLabel="Worlds longest group name that will probably overflow if it gets very much longer"/>
428
+ <Badge variant="metaSubtle" productLogoName="groups" label="Group" metaLabel="Young adults"/>
429
+ <Badge variant="metaSubtle" productLogoName="services" label="Plan" metaLabel="June 19, 2025"/>
430
+ <Badge variant="metaSubtle" productLogoName="services" label="Team"/>
431
+ </Row>
432
+ </Group>
433
+ </CollapsableSection>);
368
434
  }
369
435
  function CollapsableSection({ children, title, isLast = false }) {
370
436
  const styles = useStyles();
@@ -409,6 +475,9 @@ function TextListItem({ label, children }) {
409
475
  {children}
410
476
  </Text>);
411
477
  }
478
+ // =================================
479
+ // ====== Styles ===================
480
+ // =================================
412
481
  const useStyles = () => {
413
482
  const { colors } = useTheme();
414
483
  return StyleSheet.create({