@transferwise/components 46.126.0 → 46.128.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 (128) hide show
  1. package/build/alert/Alert.js +3 -0
  2. package/build/alert/Alert.js.map +1 -1
  3. package/build/alert/Alert.mjs +3 -0
  4. package/build/alert/Alert.mjs.map +1 -1
  5. package/build/index.js +1 -0
  6. package/build/index.js.map +1 -1
  7. package/build/index.mjs +1 -1
  8. package/build/inputs/SelectInput.js +81 -12
  9. package/build/inputs/SelectInput.js.map +1 -1
  10. package/build/inputs/SelectInput.mjs +81 -13
  11. package/build/inputs/SelectInput.mjs.map +1 -1
  12. package/build/listItem/Prompt/ListItemPrompt.js +5 -4
  13. package/build/listItem/Prompt/ListItemPrompt.js.map +1 -1
  14. package/build/listItem/Prompt/ListItemPrompt.mjs +6 -2
  15. package/build/listItem/Prompt/ListItemPrompt.mjs.map +1 -1
  16. package/build/main.css +36 -28
  17. package/build/prompt/ActionPrompt/ActionPrompt.js +6 -4
  18. package/build/prompt/ActionPrompt/ActionPrompt.js.map +1 -1
  19. package/build/prompt/ActionPrompt/ActionPrompt.mjs +6 -4
  20. package/build/prompt/ActionPrompt/ActionPrompt.mjs.map +1 -1
  21. package/build/prompt/InfoPrompt/InfoPrompt.js.map +1 -1
  22. package/build/prompt/InfoPrompt/InfoPrompt.mjs.map +1 -1
  23. package/build/prompt/InlinePrompt/InlinePrompt.js +1 -1
  24. package/build/prompt/InlinePrompt/InlinePrompt.js.map +1 -1
  25. package/build/prompt/InlinePrompt/InlinePrompt.mjs +1 -1
  26. package/build/prompt/InlinePrompt/InlinePrompt.mjs.map +1 -1
  27. package/build/styles/main.css +36 -28
  28. package/build/styles/prompt/ActionPrompt/ActionPrompt.css +4 -0
  29. package/build/styles/prompt/InfoPrompt/InfoPrompt.css +7 -5
  30. package/build/styles/prompt/InlinePrompt/InlinePrompt.css +3 -2
  31. package/build/styles/prompt/PrimitivePrompt/PrimitivePrompt.css +1 -0
  32. package/build/styles/sentimentSurface/SentimentSurface.css +21 -21
  33. package/build/types/alert/Alert.d.ts +15 -0
  34. package/build/types/alert/Alert.d.ts.map +1 -1
  35. package/build/types/index.d.ts +1 -1
  36. package/build/types/index.d.ts.map +1 -1
  37. package/build/types/inputs/SelectInput.d.ts +19 -0
  38. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  39. package/build/types/listItem/ListItem.d.ts +1 -1
  40. package/build/types/listItem/Prompt/ListItemPrompt.d.ts +2 -3
  41. package/build/types/listItem/Prompt/ListItemPrompt.d.ts.map +1 -1
  42. package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts +7 -0
  43. package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts.map +1 -1
  44. package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts +4 -2
  45. package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts.map +1 -1
  46. package/package.json +3 -3
  47. package/src/accordion/Accordion.test.js +0 -6
  48. package/src/accordion/AccordionItem/AccordionItem.test.js +0 -10
  49. package/src/actionButton/ActionButton.test.tsx +0 -4
  50. package/src/alert/Alert.story.tsx +4 -0
  51. package/src/alert/Alert.test.story.tsx +1 -1
  52. package/src/alert/Alert.tsx +16 -0
  53. package/src/avatarWrapper/AvatarWrapper.test.tsx +0 -53
  54. package/src/checkbox/Checkbox.test.tsx +0 -5
  55. package/src/chevron/Chevron.test.tsx +0 -7
  56. package/src/chips/Chips.test.tsx +0 -8
  57. package/src/common/RadioButton/RadioButton.test.tsx +0 -18
  58. package/src/common/bottomSheet/BottomSheet.test.tsx +0 -9
  59. package/src/common/card/Card.test.tsx +0 -6
  60. package/src/common/closeButton/CloseButton.test.tsx +0 -4
  61. package/src/common/panel/Panel.test.tsx +0 -6
  62. package/src/flowNavigation/FlowNavigation.test.js +0 -10
  63. package/src/iconButton/IconButton.story.tsx +173 -48
  64. package/src/iconButton/IconButton.test.story.tsx +194 -0
  65. package/src/index.ts +1 -0
  66. package/src/inputs/SelectInput.story.tsx +33 -20
  67. package/src/inputs/SelectInput.test.story.tsx +1285 -5
  68. package/src/inputs/SelectInput.tsx +93 -15
  69. package/src/listItem/Prompt/ListItemPrompt.story.tsx +71 -9
  70. package/src/listItem/Prompt/ListItemPrompt.test.tsx +31 -0
  71. package/src/listItem/Prompt/ListItemPrompt.tsx +8 -2
  72. package/src/listItem/_stories/ListItem.story.tsx +0 -1
  73. package/src/logo/Logo.story.tsx +24 -5
  74. package/src/main.css +36 -28
  75. package/src/overlayHeader/OverlayHeader.test.tsx +0 -3
  76. package/src/popover/Popover.test.tsx +0 -25
  77. package/src/promoCard/PromoCard.test.tsx +0 -6
  78. package/src/promoCard/PromoCardGroup.test.tsx +0 -5
  79. package/src/prompt/ActionPrompt/ActionPrompt.accessibility.docs.mdx +2 -18
  80. package/src/prompt/ActionPrompt/ActionPrompt.css +4 -0
  81. package/src/prompt/ActionPrompt/ActionPrompt.less +5 -1
  82. package/src/prompt/ActionPrompt/ActionPrompt.story.tsx +323 -108
  83. package/src/prompt/ActionPrompt/ActionPrompt.test.story.tsx +86 -3
  84. package/src/prompt/ActionPrompt/ActionPrompt.tsx +17 -6
  85. package/src/prompt/InfoPrompt/InfoPrompt.accessibility.docs.mdx +79 -0
  86. package/src/prompt/InfoPrompt/InfoPrompt.css +7 -5
  87. package/src/prompt/InfoPrompt/InfoPrompt.less +8 -8
  88. package/src/prompt/InfoPrompt/InfoPrompt.story.tsx +112 -82
  89. package/src/prompt/InfoPrompt/InfoPrompt.test.story.tsx +54 -1
  90. package/src/prompt/InfoPrompt/InfoPrompt.tsx +4 -2
  91. package/src/prompt/InlinePrompt/InlinePrompt.accessibility.docs.mdx +63 -0
  92. package/src/prompt/InlinePrompt/InlinePrompt.css +3 -2
  93. package/src/prompt/InlinePrompt/InlinePrompt.less +2 -2
  94. package/src/prompt/InlinePrompt/InlinePrompt.story.tsx +38 -40
  95. package/src/prompt/InlinePrompt/InlinePrompt.test.story.tsx +21 -0
  96. package/src/prompt/InlinePrompt/InlinePrompt.test.tsx +23 -4
  97. package/src/prompt/InlinePrompt/InlinePrompt.tsx +1 -1
  98. package/src/prompt/PrimitivePrompt/PrimitivePrompt.css +1 -0
  99. package/src/prompt/PrimitivePrompt/PrimitivePrompt.less +2 -1
  100. package/src/sentimentSurface/SentimentSurface.css +21 -21
  101. package/src/sentimentSurface/SentimentSurface.docs.mdx +1 -1
  102. package/src/sentimentSurface/SentimentSurface.less +13 -13
  103. package/src/sentimentSurface/SentimentSurface.test.story.tsx +47 -0
  104. package/src/tile/Tile.test.tsx +0 -10
  105. package/src/tooltip/Tooltip.test.tsx +0 -10
  106. package/src/accordion/AccordionItem/__snapshots__/AccordionItem.test.js.snap +0 -124
  107. package/src/accordion/__snapshots__/Accordion.test.js.snap +0 -3
  108. package/src/actionButton/__snapshots__/ActionButton.test.tsx.snap +0 -12
  109. package/src/avatarWrapper/__snapshots__/AvatarWrapper.test.tsx.snap +0 -156
  110. package/src/checkbox/__snapshots__/Checkbox.test.tsx.snap +0 -40
  111. package/src/chevron/__snapshots__/Chevron.test.tsx.snap +0 -24
  112. package/src/chips/__snapshots__/Chips.test.tsx.snap +0 -153
  113. package/src/common/RadioButton/__snapshots__/RadioButton.test.tsx.snap +0 -58
  114. package/src/common/bottomSheet/__snapshots__/BottomSheet.test.tsx.snap +0 -80
  115. package/src/common/card/__snapshots__/Card.test.tsx.snap +0 -10
  116. package/src/common/closeButton/__snapshots__/CloseButton.test.tsx.snap +0 -30
  117. package/src/common/flowHeader/FlowHeader.test.tsx +0 -22
  118. package/src/common/flowHeader/__snapshots__/FlowHeader.test.tsx.snap +0 -33
  119. package/src/common/panel/__snapshots__/Panel.test.tsx.snap +0 -3
  120. package/src/flowNavigation/__snapshots__/FlowNavigation.test.js.snap +0 -262
  121. package/src/logo/Logo.test.tsx +0 -55
  122. package/src/logo/__snapshots__/Logo.test.tsx.snap +0 -281
  123. package/src/overlayHeader/__snapshots__/OverlayHeader.test.tsx.snap +0 -65
  124. package/src/popover/__snapshots__/Popover.test.tsx.snap +0 -51
  125. package/src/promoCard/__snapshots__/PromoCard.test.tsx.snap +0 -40
  126. package/src/promoCard/__snapshots__/PromoCardGroup.test.tsx.snap +0 -80
  127. package/src/tile/__snapshots__/Tile.test.tsx.snap +0 -55
  128. package/src/tooltip/__snapshots__/Tooltip.test.tsx.snap +0 -32
@@ -197,6 +197,52 @@ function sortSelectInputItems<T>(
197
197
  return flattenedOption.sort((a, b) => compareFn(a, b, searchQuery));
198
198
  }
199
199
 
200
+ /**
201
+ * A prebuilt sort function for `sortFilteredOptions` that sorts options by relevance to the search query.
202
+ * Prioritizes: exact matches > starts with > contains > alphabetical.
203
+ *
204
+ * @param getLabel - Function to extract the label string from the option value. Defaults to using `title` property.
205
+ *
206
+ * @example
207
+ * ```tsx
208
+ * <SelectInput
209
+ * filterable
210
+ * sortFilteredOptions={sortByRelevance((value) => value.name)}
211
+ * // ...
212
+ * />
213
+ * ```
214
+ */
215
+ export function sortByRelevance<T>(
216
+ getLabel: (value: T) => string = (value) => (value as { title: string }).title,
217
+ ): (a: SelectInputOptionItem<T>, b: SelectInputOptionItem<T>, searchQuery: string) => number {
218
+ return (a, b, searchQuery) => {
219
+ const normalizedQuery = searchQuery.toLowerCase();
220
+ const labelA = getLabel(a.value).toLowerCase();
221
+ const labelB = getLabel(b.value).toLowerCase();
222
+
223
+ // Prioritize exact matches
224
+ const aExactMatch = labelA === normalizedQuery;
225
+ const bExactMatch = labelB === normalizedQuery;
226
+ if (aExactMatch && !bExactMatch) return -1;
227
+ if (!aExactMatch && bExactMatch) return 1;
228
+
229
+ // Then prioritize options where label starts with the search query
230
+ const aStartsWith = labelA.startsWith(normalizedQuery);
231
+ const bStartsWith = labelB.startsWith(normalizedQuery);
232
+ if (aStartsWith && !bStartsWith) return -1;
233
+ if (!aStartsWith && bStartsWith) return 1;
234
+
235
+ // Then prioritize options where label contains the search query
236
+ const aContains = labelA.includes(normalizedQuery);
237
+ const bContains = labelB.includes(normalizedQuery);
238
+ if (aContains && !bContains) return -1;
239
+ if (!aContains && bContains) return 1;
240
+
241
+ // Finally sort alphabetically
242
+ return labelA.localeCompare(labelB);
243
+ };
244
+ }
245
+
200
246
  export interface SelectInputProps<T = string, M extends boolean = false> {
201
247
  id?: string;
202
248
  /**
@@ -558,6 +604,8 @@ export function SelectInput<T = string, M extends boolean = false>({
558
604
  );
559
605
  }
560
606
 
607
+ SelectInput.sortByRelevance = sortByRelevance;
608
+
561
609
  const SelectInputTriggerButtonPropsContext = createContext<{
562
610
  ref?: React.ForwardedRef<HTMLButtonElement | null>;
563
611
  id?: string;
@@ -741,15 +789,35 @@ function SelectInputOptions<T = string>({
741
789
  return items;
742
790
  }
743
791
 
744
- const filtered = filterSelectInputItems(dedupeSelectInputItems(items, compareValues), (item) =>
745
- selectInputOptionItemIncludesNeedle(item, needle),
746
- );
792
+ const dedupedItems = dedupeSelectInputItems(items, compareValues);
747
793
 
748
794
  if (sortFilteredOptions) {
795
+ // When sorting, filter out non-matching items completely to avoid ghost items
796
+ const filtered = dedupedItems.map((item) => {
797
+ if (item.type === 'option') {
798
+ return selectInputOptionItemIncludesNeedle(item, needle)
799
+ ? item
800
+ : { ...item, value: undefined };
801
+ }
802
+ if (item.type === 'group') {
803
+ return {
804
+ ...item,
805
+ options: item.options.map((option) =>
806
+ selectInputOptionItemIncludesNeedle(option, needle)
807
+ ? option
808
+ : { ...option, value: undefined },
809
+ ),
810
+ };
811
+ }
812
+ return item;
813
+ });
814
+
749
815
  return sortSelectInputItems(filtered, sortFilteredOptions, filterQuery);
750
816
  }
751
817
 
752
- return filtered;
818
+ return filterSelectInputItems(dedupedItems, (item) =>
819
+ selectInputOptionItemIncludesNeedle(item, needle),
820
+ );
753
821
  // eslint-disable-next-line react-hooks/exhaustive-deps
754
822
  }, [needle, items, compareValues]);
755
823
  const resultsEmpty = needle != null && filteredItems.length === 0;
@@ -760,17 +828,28 @@ function SelectInputOptions<T = string>({
760
828
  // the scroll position may jump around inadvertently. Pattern adopted from:
761
829
  // https://inokawa.github.io/virtua/?path=/story/advanced-keep-offscreen-items--append-only
762
830
  const [mountedIndexes, setMountedIndexes] = useState<number[]>([]);
831
+ const prevNeedleRef = useRef(needle);
832
+
763
833
  useEffect(() => {
764
- // Ensure the 'End' key works as intended by keeping the last item mounted
765
- setMountedIndexes((prevMountedIndexes) => {
766
- const indexes = new Set(prevMountedIndexes);
767
- indexes.add(filteredItems.length - 1);
768
- return [...indexes]; // Sorting is redundant by nature here
769
- });
770
- }, [
771
- needle, // Needed as `filteredItems.length` may be equal between two updates
772
- filteredItems.length,
773
- ]);
834
+ const needleChanged = prevNeedleRef.current !== needle;
835
+ prevNeedleRef.current = needle;
836
+
837
+ if (needleChanged) {
838
+ // Reset mounted indexes when search changes to avoid stale scroll positions
839
+ setMountedIndexes([]);
840
+ return;
841
+ }
842
+
843
+ // Ensure the 'End' key works as intended by keeping the last item mounted.
844
+ // Skipped on needle change to prevent auto-scrolling on search.
845
+ if (filteredItems.length > 0) {
846
+ setMountedIndexes((prevMountedIndexes) => {
847
+ const indexes = new Set(prevMountedIndexes);
848
+ indexes.add(filteredItems.length - 1);
849
+ return [...indexes]; // Sorting is redundant by nature here
850
+ });
851
+ }
852
+ }, [needle, filteredItems.length]);
774
853
 
775
854
  const listboxContainerRef = useRef<HTMLDivElement>(null);
776
855
  useEffect(() => {
@@ -927,7 +1006,6 @@ function SelectInputOptions<T = string>({
927
1006
  ) : (
928
1007
  <Virtualizer
929
1008
  ref={virtualiserHandlerRef}
930
- key={needle}
931
1009
  data={filteredItems}
932
1010
  keepMounted={mountedIndexes}
933
1011
  scrollRef={listboxRef} // `VList` doesn't expose this
@@ -11,6 +11,7 @@ import {
11
11
  } from '../_stories/subcomponents';
12
12
  import { ListItem } from '../ListItem';
13
13
  import { Prompt, type ListItemPromptProps } from './ListItemPrompt';
14
+ import { Clock } from '@transferwise/icons';
14
15
 
15
16
  const meta: Meta<ListItemPromptProps> = {
16
17
  component: Prompt,
@@ -66,7 +67,7 @@ export const Playground: Story = {
66
67
  subtitle={lorem10}
67
68
  media={MEDIA.avatarSingle}
68
69
  control={CONTROLS.switch}
69
- prompt={<Prompt {...args} />}
70
+ prompt={<ListItem.Prompt {...args} />}
70
71
  />
71
72
  </List>
72
73
  ),
@@ -86,28 +87,89 @@ export const Sentiment: Story = {
86
87
  subtitle={lorem10}
87
88
  media={MEDIA.avatarSingle}
88
89
  control={CONTROLS.switch}
89
- prompt={<Prompt sentiment={Sentiments.NEUTRAL}>This is a neutral prompt.</Prompt>}
90
+ prompt={
91
+ <ListItem.Prompt sentiment={Sentiments.NEUTRAL}>
92
+ This is a neutral prompt.
93
+ </ListItem.Prompt>
94
+ }
90
95
  />
91
96
  <ListItem
92
97
  title={lorem5}
93
98
  subtitle={lorem10}
94
99
  media={MEDIA.avatarSingle}
95
100
  control={CONTROLS.switch}
96
- prompt={<Prompt sentiment={Sentiments.POSITIVE}>This is a positive prompt.</Prompt>}
101
+ prompt={
102
+ <ListItem.Prompt sentiment={Sentiments.POSITIVE}>
103
+ This is a positive prompt.
104
+ </ListItem.Prompt>
105
+ }
97
106
  />
98
107
  <ListItem
99
108
  title={lorem5}
100
109
  subtitle={lorem10}
101
110
  media={MEDIA.avatarSingle}
102
111
  control={CONTROLS.switch}
103
- prompt={<Prompt sentiment={Sentiments.WARNING}>This is a warning prompt.</Prompt>}
112
+ prompt={
113
+ <ListItem.Prompt sentiment={Sentiments.WARNING}>
114
+ This is a warning prompt.
115
+ </ListItem.Prompt>
116
+ }
104
117
  />
105
118
  <ListItem
106
119
  title={lorem5}
107
120
  subtitle={lorem10}
108
121
  media={MEDIA.avatarSingle}
109
122
  control={CONTROLS.switch}
110
- prompt={<Prompt sentiment={Sentiments.NEGATIVE}>This is a negative prompt.</Prompt>}
123
+ prompt={
124
+ <ListItem.Prompt sentiment={Sentiments.NEGATIVE}>
125
+ This is a negative prompt.
126
+ </ListItem.Prompt>
127
+ }
128
+ />
129
+ </List>
130
+ ),
131
+ };
132
+
133
+ /**
134
+ * While all main sentiments (`warning`, `negative`, `positive` and `neutral`) are associated with a
135
+ * default `StatusIcon`s, we also allow for Icon overrides to bring the prompt's visual language
136
+ * closer to the prompt's content. <br /><br />
137
+ * It's also possible to override the default StatusIcon's accessible name announced by screen
138
+ * readers via `mediaLabel` prop, which is especially useful for the `proposition` sentiment.
139
+ * <br /><br />
140
+ * **NB**: If you're setting a label on a custom Icon, the accessible name should be provided via
141
+ * Icon's `title` prop instead.
142
+ */
143
+ export const IconOverrides: Story = {
144
+ parameters: {
145
+ controls: { disable: true },
146
+ actions: { disable: true },
147
+ a11y: { disable: true },
148
+ knobs: { disable: true },
149
+ },
150
+ render: (args) => (
151
+ <List>
152
+ <ListItem
153
+ title={lorem5}
154
+ subtitle={lorem10}
155
+ media={MEDIA.avatarSingle}
156
+ control={CONTROLS.switch}
157
+ prompt={
158
+ <ListItem.Prompt sentiment={Sentiments.WARNING} mediaLabel="Processing: ">
159
+ This prompt uses default Icon, but with a custom label for screen readers.
160
+ </ListItem.Prompt>
161
+ }
162
+ />
163
+ <ListItem
164
+ title={lorem5}
165
+ subtitle={lorem10}
166
+ media={MEDIA.avatarSingle}
167
+ control={CONTROLS.switch}
168
+ prompt={
169
+ <ListItem.Prompt sentiment={Sentiments.WARNING} media={<Clock title="Processing: " />}>
170
+ This prompt uses custom Icon with a custom label for screen readers.
171
+ </ListItem.Prompt>
172
+ }
111
173
  />
112
174
  </List>
113
175
  ),
@@ -135,13 +197,13 @@ export const Interactivity: Story = {
135
197
  media={MEDIA.avatarSingle}
136
198
  control={CONTROLS.switch}
137
199
  prompt={
138
- <Prompt sentiment={args.sentiment}>
200
+ <ListItem.Prompt sentiment={args.sentiment}>
139
201
  This prompt includes a{' '}
140
202
  <Link href="https://wise.com" target="_blank" rel="noreferrer">
141
203
  link to some resource
142
204
  </Link>{' '}
143
205
  to help the user in their journey.
144
- </Prompt>
206
+ </ListItem.Prompt>
145
207
  }
146
208
  />
147
209
 
@@ -151,13 +213,13 @@ export const Interactivity: Story = {
151
213
  media={MEDIA.avatarSingle}
152
214
  control={CONTROLS.switch}
153
215
  prompt={
154
- <Prompt sentiment={args.sentiment}>
216
+ <ListItem.Prompt sentiment={args.sentiment}>
155
217
  {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
156
218
  This prompt includes an <Link onClick={action('inline button')}>
157
219
  inline button
158
220
  </Link>{' '}
159
221
  than can e.g. trigger a modal.
160
- </Prompt>
222
+ </ListItem.Prompt>
161
223
  }
162
224
  />
163
225
  </List>
@@ -1,3 +1,4 @@
1
+ import { Clock } from '@transferwise/icons';
1
2
  import { mockMatchMedia, render, screen } from '../../test-utils';
2
3
  import { Sentiment } from '../../common';
3
4
  import { ListItem } from '../ListItem';
@@ -18,6 +19,8 @@ describe('ListItem.Prompt', () => {
18
19
  });
19
20
 
20
21
  describe('render icon', () => {
22
+ const customLabel = 'Custom icon label';
23
+
21
24
  it.each([
22
25
  [Sentiment.NEUTRAL, 'info-icon'],
23
26
  [Sentiment.POSITIVE, 'check-icon'],
@@ -32,6 +35,34 @@ describe('ListItem.Prompt', () => {
32
35
  );
33
36
  expect(screen.getByTestId(iconId)).toBeInTheDocument();
34
37
  });
38
+
39
+ it('should accept accessible name override for a status icon', () => {
40
+ render(
41
+ <ListItem
42
+ title="Test Title"
43
+ prompt={
44
+ <ListItem.Prompt sentiment={Sentiment.NEGATIVE} mediaLabel={customLabel}>
45
+ Message
46
+ </ListItem.Prompt>
47
+ }
48
+ />,
49
+ );
50
+ expect(screen.getByLabelText(customLabel)).toBeInTheDocument();
51
+ });
52
+
53
+ it('should accept icon accessible name override', () => {
54
+ render(
55
+ <ListItem
56
+ title="Test Title"
57
+ prompt={
58
+ <ListItem.Prompt sentiment={Sentiment.NEGATIVE} media={<Clock title={customLabel} />}>
59
+ Message
60
+ </ListItem.Prompt>
61
+ }
62
+ />,
63
+ );
64
+ expect(screen.getByLabelText(customLabel)).toBeInTheDocument();
65
+ });
35
66
  });
36
67
 
37
68
  describe('muted state', () => {
@@ -3,7 +3,10 @@ import { Sentiment } from '../../common';
3
3
  import { ListItemContext, type ListItemContextData } from '../ListItemContext';
4
4
  import { InlinePrompt, type InlinePromptProps } from '../../prompt';
5
5
 
6
- export type ListItemPromptProps = Pick<InlinePromptProps, 'children' | 'sentiment' | 'mediaLabel'>;
6
+ export type ListItemPromptProps = Pick<
7
+ InlinePromptProps,
8
+ 'children' | 'sentiment' | 'mediaLabel' | 'media' | 'loading'
9
+ >;
7
10
 
8
11
  /**
9
12
  * This component allows for rendering an Inline Prompt. <br />
@@ -15,6 +18,8 @@ export const Prompt = ({
15
18
  sentiment = Sentiment.NEUTRAL,
16
19
  mediaLabel,
17
20
  children,
21
+ media,
22
+ loading,
18
23
  }: ListItemPromptProps) => {
19
24
  const { ids, props } = useContext<ListItemContextData>(ListItemContext);
20
25
  const isLongLivedMuted = props.disabled && Boolean(props.disabledPromptMessage);
@@ -23,6 +28,8 @@ export const Prompt = ({
23
28
  <InlinePrompt
24
29
  id={ids.prompt}
25
30
  sentiment={sentiment}
31
+ media={media}
32
+ loading={loading}
26
33
  mediaLabel={mediaLabel}
27
34
  muted={isLongLivedMuted}
28
35
  className="wds-list-item-prompt"
@@ -33,4 +40,3 @@ export const Prompt = ({
33
40
  };
34
41
 
35
42
  Prompt.displayName = 'ListItem.Prompt';
36
- export default Prompt;
@@ -252,7 +252,6 @@ const getPropsForPreview = (args: PreviewStoryArgs): [ListItemProps, Partial<Lis
252
252
  };
253
253
 
254
254
  export const Playground: StoryObj<PreviewStoryArgs> = {
255
- tags: ['!autodocs'],
256
255
  render: (args: PreviewStoryArgs) => {
257
256
  const [props, previewProps] = getPropsForPreview(args);
258
257
 
@@ -1,15 +1,29 @@
1
1
  import { Meta, StoryObj } from '@storybook/react-webpack5';
2
2
 
3
- import Logo from '.';
3
+ import Logo, { LogoType } from '.';
4
+ import { withVariantConfig } from '../../.storybook/helpers';
4
5
 
5
6
  export default {
6
7
  component: Logo,
7
8
  title: 'Content/Logo',
8
- render: ({ inverse, type }) => {
9
+ render: () => {
9
10
  return (
10
- <div className={`${inverse ? 'bg--dark' : ''} p-a-3`}>
11
- <Logo type={type} inverse={inverse} />
12
- </div>
11
+ <>
12
+ <div>
13
+ {Object.values(LogoType).map((type) => (
14
+ <div key={type} className="m-a-2">
15
+ <Logo type={type} />
16
+ </div>
17
+ ))}
18
+ </div>
19
+ <div className="bg--dark">
20
+ {Object.values(LogoType).map((type) => (
21
+ <div key={type} className="m-a-2">
22
+ <Logo type={type} inverse />
23
+ </div>
24
+ ))}
25
+ </div>
26
+ </>
13
27
  );
14
28
  },
15
29
  } satisfies Meta<typeof Logo>;
@@ -22,3 +36,8 @@ export const Basic: Story = {
22
36
  inverse: false,
23
37
  },
24
38
  };
39
+
40
+ export const Mobile: Story = {
41
+ ...Basic,
42
+ ...withVariantConfig(['mobile']),
43
+ };
package/src/main.css CHANGED
@@ -304,18 +304,18 @@
304
304
  --color-sentiment-interactive-primary: #454745;
305
305
  --color-sentiment-interactive-primary-hover: #353635;
306
306
  --color-sentiment-interactive-primary-active: #232423;
307
- --color-sentiment-interactive-secondary: #F1F1ED;
308
- --color-sentiment-interactive-secondary-hover: #E7E7E1;
309
- --color-sentiment-interactive-secondary-active: #DFDED5;
310
- --color-sentiment-interactive-secondary-neutral: #E4E4DC;
311
- --color-sentiment-interactive-secondary-neutral-hover: #DCDCD2;
312
- --color-sentiment-interactive-secondary-neutral-active: #D3D2C6;
307
+ --color-sentiment-interactive-secondary: rgba(62, 59, 7, 0.07);
308
+ --color-sentiment-interactive-secondary-hover: rgba(62, 59, 7, 0.12);
309
+ --color-sentiment-interactive-secondary-active: rgba(62, 59, 7, 0.17);
310
+ --color-sentiment-interactive-secondary-neutral: rgba(62, 59, 7, 0.07);
311
+ --color-sentiment-interactive-secondary-neutral-hover: rgba(62, 59, 7, 0.12);
312
+ --color-sentiment-interactive-secondary-neutral-active: rgba(62, 59, 7, 0.17);
313
313
  --color-sentiment-interactive-control: #F1F1ED;
314
314
  --color-sentiment-interactive-control-hover: #E7E7E1;
315
315
  --color-sentiment-interactive-control-active: #DFDED5;
316
- --color-sentiment-background-surface: #F1F1ED;
317
- --color-sentiment-background-surface-hover: #E7E7E1;
318
- --color-sentiment-background-surface-active: #DFDED5;
316
+ --color-sentiment-background-surface: rgba(62, 59, 7, 0.07);
317
+ --color-sentiment-background-surface-hover: rgba(62, 59, 7, 0.12);
318
+ --color-sentiment-background-surface-active: rgba(62, 59, 7, 0.17);
319
319
  }
320
320
  .np-theme-personal .wds-sentiment-surface-neutral-elevated,
321
321
  .np-theme-business .wds-sentiment-surface-neutral-elevated,
@@ -331,9 +331,9 @@
331
331
  --color-sentiment-interactive-secondary: #454745;
332
332
  --color-sentiment-interactive-secondary-hover: #353635;
333
333
  --color-sentiment-interactive-secondary-active: #232423;
334
- --color-sentiment-interactive-secondary-neutral: #5A5C5A;
335
- --color-sentiment-interactive-secondary-neutral-hover: #6D6F6D;
336
- --color-sentiment-interactive-secondary-neutral-active: #727472;
334
+ --color-sentiment-interactive-secondary-neutral: #585958;
335
+ --color-sentiment-interactive-secondary-neutral-hover: #6A6C6A;
336
+ --color-sentiment-interactive-secondary-neutral-active: #7D7E7D;
337
337
  --color-sentiment-interactive-control: #454745;
338
338
  --color-sentiment-interactive-control-hover: #353635;
339
339
  --color-sentiment-interactive-control-active: #232423;
@@ -352,18 +352,18 @@
352
352
  --color-sentiment-interactive-primary: #F1F1ED;
353
353
  --color-sentiment-interactive-primary-hover: #E7E7E1;
354
354
  --color-sentiment-interactive-primary-active: #DFDED5;
355
- --color-sentiment-interactive-secondary: #2A2C29;
356
- --color-sentiment-interactive-secondary-hover: #414441;
357
- --color-sentiment-interactive-secondary-active: #595B58;
358
- --color-sentiment-interactive-secondary-neutral: #424441;
359
- --color-sentiment-interactive-secondary-neutral-hover: #4C4E4B;
360
- --color-sentiment-interactive-secondary-neutral-active: #565955;
355
+ --color-sentiment-interactive-secondary: rgba(255, 255, 255, 0.1);
356
+ --color-sentiment-interactive-secondary-hover: rgba(255, 255, 255, 0.2);
357
+ --color-sentiment-interactive-secondary-active: rgba(255, 255, 255, 0.3);
358
+ --color-sentiment-interactive-secondary-neutral: rgba(255, 255, 255, 0.1);
359
+ --color-sentiment-interactive-secondary-neutral-hover: rgba(255, 255, 255, 0.2);
360
+ --color-sentiment-interactive-secondary-neutral-active: rgba(255, 255, 255, 0.3);
361
361
  --color-sentiment-interactive-control: #2A2C29;
362
362
  --color-sentiment-interactive-control-hover: #414441;
363
363
  --color-sentiment-interactive-control-active: #595B58;
364
- --color-sentiment-background-surface: #2A2C29;
365
- --color-sentiment-background-surface-hover: #414441;
366
- --color-sentiment-background-surface-active: #595B58;
364
+ --color-sentiment-background-surface: rgba(255, 255, 255, 0.1);
365
+ --color-sentiment-background-surface-hover: rgba(255, 255, 255, 0.2);
366
+ --color-sentiment-background-surface-active: rgba(255, 255, 255, 0.3);
367
367
  }
368
368
  .np-theme-personal--dark .wds-sentiment-surface-neutral-elevated,
369
369
  .np-theme-business--dark .wds-sentiment-surface-neutral-elevated,
@@ -5346,6 +5346,7 @@ html:not([dir="rtl"]) .np-navigation-option {
5346
5346
  padding: var(--Prompt-padding, var(--padding-x-small));
5347
5347
  text-align: left;
5348
5348
  word-break: break-word;
5349
+ width: 100%;
5349
5350
  }
5350
5351
  .wds-prompt__content-wrapper {
5351
5352
  display: grid;
@@ -5405,8 +5406,9 @@ html:not([dir="rtl"]) .np-navigation-option {
5405
5406
  .wds-inline-prompt:has(button):active {
5406
5407
  background-color: var(--color-sentiment-background-surface-active);
5407
5408
  }
5408
- .wds-inline-prompt--full-width {
5409
- width: 100%;
5409
+ .wds-inline-prompt--auto-width {
5410
+ width: auto;
5411
+ width: initial;
5410
5412
  }
5411
5413
  .wds-inline-prompt--muted {
5412
5414
  opacity: 0.93;
@@ -5442,13 +5444,16 @@ html:not([dir="rtl"]) .np-navigation-option {
5442
5444
  stroke: currentColor;
5443
5445
  }
5444
5446
  .wds-info-prompt {
5447
+ --Prompt-border-radius: var(--radius-medium);
5445
5448
  --Prompt-gap: var(--size-8);
5446
- --Prompt-padding: 12px;
5449
+ --Prompt-padding: var(--size-12);
5447
5450
  }
5448
5451
  .wds-info-prompt__content {
5449
5452
  display: flex;
5450
5453
  flex-direction: column;
5451
5454
  justify-content: center;
5455
+ max-width: calc(48px * 10);
5456
+ max-width: calc(var(--size-48) * 10);
5452
5457
  }
5453
5458
  .wds-info-prompt__content:has(.wds-info-prompt__title) {
5454
5459
  justify-content: flex-start;
@@ -5457,17 +5462,16 @@ html:not([dir="rtl"]) .np-navigation-option {
5457
5462
  .wds-info-prompt__title,
5458
5463
  .wds-info-prompt__description {
5459
5464
  display: block;
5460
- color: var(--color-sentiment-primary);
5461
5465
  }
5462
5466
  .wds-info-prompt__action {
5467
+ align-self: flex-start;
5463
5468
  margin-top: var(--Prompt-gap);
5464
5469
  }
5465
- .wds-info-prompt__media {
5466
- display: flex;
5467
- }
5468
5470
  .wds-info-prompt__media svg {
5469
5471
  width: 24px;
5472
+ width: var(--size-24);
5470
5473
  height: 24px;
5474
+ height: var(--size-24);
5471
5475
  }
5472
5476
  .wds-info-prompt .wds-prompt__media-wrapper {
5473
5477
  padding: 0;
@@ -7484,3 +7488,7 @@ html:not([dir="rtl"]) .np-navigation-option {
7484
7488
  min-width: fit-content;
7485
7489
  }
7486
7490
  }
7491
+ .wds-action-prompt__content {
7492
+ max-width: calc(48px * 10);
7493
+ max-width: calc(var(--size-48) * 10);
7494
+ }
@@ -11,9 +11,6 @@ describe('OverlayHeader', () => {
11
11
  logo: <img alt="logo_desktop" src="img_desktop" width="138" height="24" />,
12
12
  onClose: jest.fn(),
13
13
  };
14
- it('renders as expected', () => {
15
- expect(render(<OverlayHeader {...props} />).container).toMatchSnapshot();
16
- });
17
14
 
18
15
  it('renders separator only if avatar and onClose are provided', () => {
19
16
  const { container } = render(<OverlayHeader {...props} />);
@@ -22,19 +22,6 @@ describe('Popover', () => {
22
22
  let rerender;
23
23
 
24
24
  describe('on desktop', () => {
25
- it('renders when is open', async () => {
26
- render(
27
- <Popover {...props}>
28
- <button type="button">Open</button>
29
- </Popover>,
30
- );
31
-
32
- await userEvent.click(screen.getByText('Open'));
33
-
34
- await waitForPanel();
35
- expect(getPanel()).toMatchSnapshot();
36
- });
37
-
38
25
  describe('title', () => {
39
26
  it('renders title', async () => {
40
27
  ({ container, rerender } = render(
@@ -122,18 +109,6 @@ describe('Popover', () => {
122
109
  global.innerWidth = Breakpoint.SMALL - 1;
123
110
  });
124
111
 
125
- it('renders when is open', async () => {
126
- ({ container } = render(
127
- <Popover {...props}>
128
- <button type="button">Open</button>
129
- </Popover>,
130
- ));
131
-
132
- await userEvent.click(screen.getByText('Open'));
133
-
134
- expect(container).toMatchSnapshot();
135
- });
136
-
137
112
  it('renders BottomSheet onClick', async () => {
138
113
  render(
139
114
  <Popover {...props}>
@@ -26,12 +26,6 @@ describe('PromoCard', () => {
26
26
  };
27
27
  });
28
28
 
29
- it('matches snapshot', () => {
30
- const { container } = render(<PromoCard {...defaultProps} id="test-promo-card" />);
31
-
32
- expect(container.firstChild).toMatchSnapshot();
33
- });
34
-
35
29
  it('renders', () => {
36
30
  const props = {
37
31
  ...defaultProps,
@@ -41,11 +41,6 @@ describe('PromoCardGroup', () => {
41
41
  };
42
42
  });
43
43
 
44
- it('matches snapshot', () => {
45
- const { container } = render(<PromoCardGroup {...defaultProps} />);
46
- expect(container.firstChild).toMatchSnapshot();
47
- });
48
-
49
44
  it('renders', () => {
50
45
  const props = {
51
46
  ...defaultProps,
@@ -1,6 +1,6 @@
1
1
  import { Meta, Canvas, Source } from '@storybook/addon-docs/blocks';
2
2
 
3
- <Meta title="Prompts/ActionPrompt/Accessibility" />
3
+ <Meta title="Prompts/ActionPrompt/Accessibility" tags={['new']} />
4
4
 
5
5
  # Accessibility
6
6
 
@@ -31,23 +31,7 @@ If you want to provide a custom label for screen readers, you can use the `aria-
31
31
 
32
32
  ## Media
33
33
 
34
- You can use the `aria-hidden` attribute on media assets to hide them from screen readers if they're not providing any additional information. This is useful when the media is purely decorative or when its content is already conveyed through other means (e.g., text).
35
-
36
- <Source
37
- dark
38
- code={`
39
- <ActionPrompt
40
- media={{
41
- 'aria-hidden': true,
42
- avatar: { asset: <People /> },
43
- }}
44
- title="Sync contacts for a faster experience"
45
- ...
46
- />
47
- `}
48
- />
49
-
50
- You can also use `aria-label` on media assets to provide a custom label for screen readers.
34
+ You can use `aria-label` on media assets to provide a custom label for screen readers.
51
35
 
52
36
  <Source
53
37
  dark
@@ -19,3 +19,7 @@
19
19
  min-width: fit-content;
20
20
  }
21
21
  }
22
+ .wds-action-prompt__content {
23
+ max-width: calc(48px * 10);
24
+ max-width: calc(var(--size-48) * 10);
25
+ }