@telefonica/mistica 17.5.0 → 17.6.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 (273) hide show
  1. package/css/mistica.css +1 -1
  2. package/dist/accordion.css-mistica.js +6 -6
  3. package/dist/align.css-mistica.js +1 -1
  4. package/dist/autocomplete.css-mistica.js +1 -1
  5. package/dist/avatar.css-mistica.js +1 -1
  6. package/dist/badge.css-mistica.js +1 -1
  7. package/dist/box.css-mistica.js +13 -13
  8. package/dist/boxed.css-mistica.js +24 -24
  9. package/dist/button-group.css-mistica.js +1 -1
  10. package/dist/button-layout.css-mistica.js +14 -14
  11. package/dist/button.css-mistica.js +34 -34
  12. package/dist/callout.css-mistica.js +11 -11
  13. package/dist/card-cover.d.ts +2 -2
  14. package/dist/card-data.d.ts +2 -2
  15. package/dist/card-data.js +6 -8
  16. package/dist/card-internal.css-mistica.js +47 -29
  17. package/dist/card-internal.css.d.ts +4 -0
  18. package/dist/card-internal.d.ts +38 -3
  19. package/dist/card-internal.js +394 -301
  20. package/dist/card-media.d.ts +2 -2
  21. package/dist/card-media.js +7 -9
  22. package/dist/card-naked.d.ts +2 -2
  23. package/dist/card-naked.js +9 -11
  24. package/dist/carousel.css-mistica.js +8 -8
  25. package/dist/checkbox.css-mistica.js +11 -11
  26. package/dist/checkbox.d.ts +2 -0
  27. package/dist/checkbox.js +10 -10
  28. package/dist/chip.css-mistica.js +15 -15
  29. package/dist/circle.css-mistica.js +1 -1
  30. package/dist/community/advanced-data-card.css-mistica.js +20 -11
  31. package/dist/community/advanced-data-card.css.d.ts +2 -0
  32. package/dist/community/advanced-data-card.d.ts +7 -3
  33. package/dist/community/advanced-data-card.js +164 -142
  34. package/dist/community/ai-card.css-mistica.js +13 -13
  35. package/dist/community/ai-card.js +1 -1
  36. package/dist/community/blocks.css-mistica.js +1 -1
  37. package/dist/community/cyber/example-component.css-mistica.js +1 -1
  38. package/dist/community/example-component.css-mistica.js +1 -1
  39. package/dist/counter.css-mistica.js +1 -1
  40. package/dist/cover-hero.css-mistica.js +2 -2
  41. package/dist/credit-card-number-field.css-mistica.js +3 -3
  42. package/dist/date-field.css-mistica.js +1 -1
  43. package/dist/date-time-picker.css-mistica.js +1 -1
  44. package/dist/dialog.css-mistica.js +4 -4
  45. package/dist/divider.css-mistica.js +5 -5
  46. package/dist/double-field.css-mistica.js +2 -2
  47. package/dist/drawer.css-mistica.js +1 -1
  48. package/dist/empty-state-card.css-mistica.js +1 -1
  49. package/dist/empty-state.css-mistica.js +5 -5
  50. package/dist/fade-in.css-mistica.js +1 -1
  51. package/dist/fade-out.css-mistica.js +1 -1
  52. package/dist/feedback.css-mistica.js +1 -1
  53. package/dist/file-upload.css-mistica.js +7 -7
  54. package/dist/fixed-footer-layout.css-mistica.js +2 -2
  55. package/dist/form.css-mistica.js +1 -1
  56. package/dist/generated/mistica-icons/icon-document-information-filled.js +165 -0
  57. package/dist/generated/mistica-icons/icon-document-information-light.js +165 -0
  58. package/dist/generated/mistica-icons/icon-document-information-regular.js +165 -0
  59. package/dist/generated/mistica-icons/icon-internet-roaming-filled.js +159 -0
  60. package/dist/generated/mistica-icons/icon-internet-roaming-light.js +159 -0
  61. package/dist/generated/mistica-icons/icon-internet-roaming-regular.js +159 -0
  62. package/dist/generated/mistica-icons/icon-mobile-information-filled.js +165 -0
  63. package/dist/generated/mistica-icons/icon-mobile-information-light.js +173 -0
  64. package/dist/generated/mistica-icons/icon-mobile-information-regular.js +165 -0
  65. package/dist/generated/mistica-icons/icon-search-ai-filled.js +165 -0
  66. package/dist/generated/mistica-icons/icon-search-ai-light.js +165 -0
  67. package/dist/generated/mistica-icons/icon-search-ai-regular.js +165 -0
  68. package/dist/grid-layout.css-mistica.js +3 -3
  69. package/dist/grid.css-mistica.js +120 -120
  70. package/dist/header.css-mistica.js +1 -1
  71. package/dist/hero.css-mistica.js +2 -2
  72. package/dist/horizontal-scroll.css-mistica.js +1 -1
  73. package/dist/icon-button.css-mistica.js +53 -53
  74. package/dist/icons/icon-chevron.css-mistica.js +2 -2
  75. package/dist/icons/icon-error.css-mistica.js +1 -1
  76. package/dist/image.css-mistica.js +11 -11
  77. package/dist/index.d.ts +12 -0
  78. package/dist/index.js +48 -0
  79. package/dist/inline.css-mistica.js +11 -11
  80. package/dist/list.css-mistica.js +7 -7
  81. package/dist/loading-bar.css-mistica.js +1 -1
  82. package/dist/loading-screen.css-mistica.js +4 -4
  83. package/dist/logo.css-mistica.js +5 -5
  84. package/dist/menu.css-mistica.js +13 -13
  85. package/dist/mosaic.css-mistica.js +1 -1
  86. package/dist/navigation-bar.css-mistica.js +9 -9
  87. package/dist/navigation-breadcrumbs.css-mistica.js +1 -1
  88. package/dist/navigation-shared.css-mistica.js +9 -9
  89. package/dist/package-version.js +1 -1
  90. package/dist/pagination.css-mistica.js +18 -18
  91. package/dist/pin-field.css-mistica.js +1 -1
  92. package/dist/popover.css-mistica.js +1 -1
  93. package/dist/progress-bar.css-mistica.js +6 -6
  94. package/dist/radio-button.css-mistica.js +19 -19
  95. package/dist/radio-button.d.ts +3 -1
  96. package/dist/radio-button.js +73 -72
  97. package/dist/rating.css-mistica.js +2 -2
  98. package/dist/responsive-layout.css-mistica.js +6 -6
  99. package/dist/screen-reader-only.css-mistica.js +1 -1
  100. package/dist/select.css-mistica.js +15 -15
  101. package/dist/sheet-action-row.css-mistica.js +1 -1
  102. package/dist/sheet-common.css-mistica.js +1 -1
  103. package/dist/sheet-info.css-mistica.js +1 -1
  104. package/dist/sidenav-bar-layout.css-mistica.js +1 -1
  105. package/dist/sidenav-bar.css-mistica.js +71 -71
  106. package/dist/skeletons.css-mistica.js +6 -6
  107. package/dist/skins/skin-contract.css-mistica.js +738 -738
  108. package/dist/skip-link.css-mistica.js +1 -1
  109. package/dist/slider.css-mistica.js +18 -18
  110. package/dist/snackbar.css-mistica.js +4 -4
  111. package/dist/spinner.css-mistica.js +1 -1
  112. package/dist/square.css-mistica.js +1 -1
  113. package/dist/stack.css-mistica.js +5 -5
  114. package/dist/stacking-group.css-mistica.js +1 -1
  115. package/dist/stepper.css-mistica.js +2 -2
  116. package/dist/switch-component.css-mistica.js +35 -35
  117. package/dist/switch-component.d.ts +2 -0
  118. package/dist/switch-component.js +19 -18
  119. package/dist/table.css-mistica.js +9 -9
  120. package/dist/tabs.css-mistica.js +17 -17
  121. package/dist/tag.css-mistica.js +1 -1
  122. package/dist/text-field-base.css-mistica.js +16 -16
  123. package/dist/text-field-components.css-mistica.js +3 -3
  124. package/dist/text-link.css-mistica.js +6 -6
  125. package/dist/text.css-mistica.js +6 -6
  126. package/dist/timeline.css-mistica.js +9 -9
  127. package/dist/timer.css-mistica.js +6 -6
  128. package/dist/tooltip.css-mistica.js +1 -1
  129. package/dist/touchable.css-mistica.js +1 -1
  130. package/dist/utils/aspect-ratio-support.css-mistica.js +2 -2
  131. package/dist/video.css-mistica.js +1 -1
  132. package/dist/vivinho-loading-animation/vivinho-loading-animation.css-mistica.js +1 -1
  133. package/dist-es/accordion.css-mistica.js +6 -6
  134. package/dist-es/align.css-mistica.js +1 -1
  135. package/dist-es/autocomplete.css-mistica.js +1 -1
  136. package/dist-es/avatar.css-mistica.js +1 -1
  137. package/dist-es/badge.css-mistica.js +1 -1
  138. package/dist-es/box.css-mistica.js +13 -13
  139. package/dist-es/boxed.css-mistica.js +23 -23
  140. package/dist-es/button-group.css-mistica.js +1 -1
  141. package/dist-es/button-layout.css-mistica.js +14 -14
  142. package/dist-es/button.css-mistica.js +34 -34
  143. package/dist-es/callout.css-mistica.js +11 -11
  144. package/dist-es/card-data.js +10 -12
  145. package/dist-es/card-internal.css-mistica.js +25 -19
  146. package/dist-es/card-internal.js +482 -395
  147. package/dist-es/card-media.js +11 -13
  148. package/dist-es/card-naked.js +14 -16
  149. package/dist-es/carousel.css-mistica.js +8 -8
  150. package/dist-es/checkbox.css-mistica.js +11 -11
  151. package/dist-es/checkbox.js +19 -19
  152. package/dist-es/chip.css-mistica.js +15 -15
  153. package/dist-es/circle.css-mistica.js +1 -1
  154. package/dist-es/community/advanced-data-card.css-mistica.js +10 -7
  155. package/dist-es/community/advanced-data-card.js +214 -192
  156. package/dist-es/community/ai-card.css-mistica.js +4 -4
  157. package/dist-es/community/ai-card.js +1 -1
  158. package/dist-es/community/blocks.css-mistica.js +1 -1
  159. package/dist-es/community/cyber/example-component.css-mistica.js +1 -1
  160. package/dist-es/community/example-component.css-mistica.js +1 -1
  161. package/dist-es/counter.css-mistica.js +1 -1
  162. package/dist-es/cover-hero.css-mistica.js +2 -2
  163. package/dist-es/credit-card-number-field.css-mistica.js +3 -3
  164. package/dist-es/date-field.css-mistica.js +1 -1
  165. package/dist-es/date-time-picker.css-mistica.js +1 -1
  166. package/dist-es/dialog.css-mistica.js +4 -4
  167. package/dist-es/divider.css-mistica.js +5 -5
  168. package/dist-es/double-field.css-mistica.js +2 -2
  169. package/dist-es/drawer.css-mistica.js +1 -1
  170. package/dist-es/empty-state-card.css-mistica.js +1 -1
  171. package/dist-es/empty-state.css-mistica.js +5 -5
  172. package/dist-es/fade-in.css-mistica.js +1 -1
  173. package/dist-es/fade-out.css-mistica.js +1 -1
  174. package/dist-es/feedback.css-mistica.js +1 -1
  175. package/dist-es/file-upload.css-mistica.js +7 -7
  176. package/dist-es/fixed-footer-layout.css-mistica.js +2 -2
  177. package/dist-es/form.css-mistica.js +1 -1
  178. package/dist-es/generated/mistica-icons/icon-document-information-filled.js +115 -0
  179. package/dist-es/generated/mistica-icons/icon-document-information-light.js +115 -0
  180. package/dist-es/generated/mistica-icons/icon-document-information-regular.js +115 -0
  181. package/dist-es/generated/mistica-icons/icon-internet-roaming-filled.js +109 -0
  182. package/dist-es/generated/mistica-icons/icon-internet-roaming-light.js +109 -0
  183. package/dist-es/generated/mistica-icons/icon-internet-roaming-regular.js +109 -0
  184. package/dist-es/generated/mistica-icons/icon-mobile-information-filled.js +115 -0
  185. package/dist-es/generated/mistica-icons/icon-mobile-information-light.js +123 -0
  186. package/dist-es/generated/mistica-icons/icon-mobile-information-regular.js +115 -0
  187. package/dist-es/generated/mistica-icons/icon-search-ai-filled.js +115 -0
  188. package/dist-es/generated/mistica-icons/icon-search-ai-light.js +115 -0
  189. package/dist-es/generated/mistica-icons/icon-search-ai-regular.js +115 -0
  190. package/dist-es/grid-layout.css-mistica.js +3 -3
  191. package/dist-es/grid.css-mistica.js +120 -120
  192. package/dist-es/header.css-mistica.js +1 -1
  193. package/dist-es/hero.css-mistica.js +2 -2
  194. package/dist-es/horizontal-scroll.css-mistica.js +1 -1
  195. package/dist-es/icon-button.css-mistica.js +53 -53
  196. package/dist-es/icons/icon-chevron.css-mistica.js +2 -2
  197. package/dist-es/icons/icon-error.css-mistica.js +1 -1
  198. package/dist-es/image.css-mistica.js +4 -4
  199. package/dist-es/index.js +1501 -1489
  200. package/dist-es/inline.css-mistica.js +11 -11
  201. package/dist-es/list.css-mistica.js +7 -7
  202. package/dist-es/loading-bar.css-mistica.js +1 -1
  203. package/dist-es/loading-screen.css-mistica.js +4 -4
  204. package/dist-es/logo.css-mistica.js +5 -5
  205. package/dist-es/menu.css-mistica.js +13 -13
  206. package/dist-es/mosaic.css-mistica.js +1 -1
  207. package/dist-es/navigation-bar.css-mistica.js +9 -9
  208. package/dist-es/navigation-breadcrumbs.css-mistica.js +1 -1
  209. package/dist-es/navigation-shared.css-mistica.js +9 -9
  210. package/dist-es/package-version.js +1 -1
  211. package/dist-es/pagination.css-mistica.js +18 -18
  212. package/dist-es/pin-field.css-mistica.js +1 -1
  213. package/dist-es/popover.css-mistica.js +1 -1
  214. package/dist-es/progress-bar.css-mistica.js +6 -6
  215. package/dist-es/radio-button.css-mistica.js +19 -19
  216. package/dist-es/radio-button.js +91 -90
  217. package/dist-es/rating.css-mistica.js +2 -2
  218. package/dist-es/responsive-layout.css-mistica.js +6 -6
  219. package/dist-es/screen-reader-only.css-mistica.js +1 -1
  220. package/dist-es/select.css-mistica.js +15 -15
  221. package/dist-es/sheet-action-row.css-mistica.js +1 -1
  222. package/dist-es/sheet-common.css-mistica.js +1 -1
  223. package/dist-es/sheet-info.css-mistica.js +1 -1
  224. package/dist-es/sidenav-bar-layout.css-mistica.js +1 -1
  225. package/dist-es/sidenav-bar.css-mistica.js +71 -71
  226. package/dist-es/skeletons.css-mistica.js +6 -6
  227. package/dist-es/skins/skin-contract.css-mistica.js +738 -738
  228. package/dist-es/skip-link.css-mistica.js +1 -1
  229. package/dist-es/slider.css-mistica.js +18 -18
  230. package/dist-es/snackbar.css-mistica.js +4 -4
  231. package/dist-es/spinner.css-mistica.js +1 -1
  232. package/dist-es/square.css-mistica.js +1 -1
  233. package/dist-es/stack.css-mistica.js +5 -5
  234. package/dist-es/stacking-group.css-mistica.js +1 -1
  235. package/dist-es/stepper.css-mistica.js +2 -2
  236. package/dist-es/style.css +1 -1
  237. package/dist-es/switch-component.css-mistica.js +35 -35
  238. package/dist-es/switch-component.js +34 -33
  239. package/dist-es/table.css-mistica.js +9 -9
  240. package/dist-es/tabs.css-mistica.js +17 -17
  241. package/dist-es/tag.css-mistica.js +1 -1
  242. package/dist-es/text-field-base.css-mistica.js +16 -16
  243. package/dist-es/text-field-components.css-mistica.js +3 -3
  244. package/dist-es/text-link.css-mistica.js +6 -6
  245. package/dist-es/text.css-mistica.js +6 -6
  246. package/dist-es/timeline.css-mistica.js +9 -9
  247. package/dist-es/timer.css-mistica.js +6 -6
  248. package/dist-es/tooltip.css-mistica.js +1 -1
  249. package/dist-es/touchable.css-mistica.js +1 -1
  250. package/dist-es/utils/aspect-ratio-support.css-mistica.js +2 -2
  251. package/dist-es/video.css-mistica.js +1 -1
  252. package/dist-es/vivinho-loading-animation/vivinho-loading-animation.css-mistica.js +1 -1
  253. package/doc/components.md +35 -0
  254. package/package.json +4 -3
  255. package/src/card-cover.tsx +2 -2
  256. package/src/card-data.tsx +3 -4
  257. package/src/card-internal.css.ts +36 -0
  258. package/src/card-internal.tsx +162 -9
  259. package/src/card-media.tsx +3 -4
  260. package/src/card-naked.tsx +3 -4
  261. package/src/checkbox.tsx +6 -1
  262. package/src/community/__screenshot_tests__/__image_snapshots__/ai-card-screenshot-test-tsx-ai-card-line-break-at-chars-1-snap.png +0 -0
  263. package/src/community/__screenshot_tests__/__image_snapshots__/ai-card-screenshot-test-tsx-ai-card-static-text-1-snap.png +0 -0
  264. package/src/community/__screenshot_tests__/ai-card-screenshot-test.tsx +28 -0
  265. package/src/community/__stories__/advanced-data-card-story.tsx +5 -0
  266. package/src/community/__type_tests__/advanced-data-card-type-test.tsx +3 -0
  267. package/src/community/advanced-data-card.css.ts +26 -1
  268. package/src/community/advanced-data-card.tsx +123 -73
  269. package/src/community/ai-card.tsx +3 -3
  270. package/src/index.tsx +12 -0
  271. package/src/package-version.tsx +1 -1
  272. package/src/radio-button.tsx +7 -1
  273. package/src/switch-component.tsx +7 -3
@@ -269,3 +269,39 @@ export const stretchedLink = style({
269
269
  cursor: 'pointer',
270
270
  },
271
271
  });
272
+
273
+ const selectionOutlineWidth = 2;
274
+ const selectionOutlineOffset = 2;
275
+
276
+ export const selectionOutline = style({
277
+ selectors: {
278
+ '&::after': {
279
+ content: '',
280
+ position: 'absolute',
281
+ inset: -(selectionOutlineWidth + selectionOutlineOffset),
282
+ border: `${selectionOutlineWidth}px solid transparent`,
283
+ borderRadius: `calc(${skinVars.borderRadii.container} + 3px)`,
284
+ pointerEvents: 'none',
285
+ },
286
+ },
287
+ });
288
+
289
+ export const selectionOutlineColor = styleVariants({
290
+ default: {selectors: {'&::after': {borderColor: skinVars.colors.controlActivated}}},
291
+ alternative: {selectors: {'&::after': {borderColor: skinVars.colors.controlActivated}}},
292
+ brand: {selectors: {'&::after': {borderColor: skinVars.colors.controlActivatedBrand}}},
293
+ media: {selectors: {'&::after': {borderColor: skinVars.colors.controlActivated}}},
294
+ negative: {selectors: {'&::after': {borderColor: skinVars.colors.controlActivatedNegative}}},
295
+ });
296
+
297
+ export const selectionControl = style({borderRadius: skinVars.borderRadii.container});
298
+
299
+ export const selectionControlContent = style({
300
+ display: 'flex',
301
+ flexDirection: 'column',
302
+ width: '100%',
303
+ height: '100%',
304
+ flexGrow: 1,
305
+ borderRadius: 'inherit',
306
+ outlineOffset: 0,
307
+ });
@@ -20,6 +20,9 @@ import {getPrefixedDataAttributes} from './utils/dom';
20
20
  import {applyCssVars} from './utils/css';
21
21
  import {InternalBoxed} from './boxed';
22
22
  import {BaseTouchable} from './touchable';
23
+ import Checkbox from './checkbox';
24
+ import Switch from './switch-component';
25
+ import RadioButton, {useRadioContext} from './radio-button';
23
26
  import {AspectRatioContainer, aspectRatioToNumber} from './utils/aspect-ratio-support';
24
27
  import {vars as skinVars} from './skins/skin-contract.css';
25
28
  import {IconButton, ToggleIconButton} from './icon-button';
@@ -42,7 +45,98 @@ import type {ButtonLink, ButtonPrimary, ButtonSecondary} from './button';
42
45
  import type {NonDeprecatedVariant, Variant} from './theme-variant-context';
43
46
  import type {VideoElement, VideoSource, AspectRatio as VideoAspectRatio} from './video';
44
47
  import type {AspectRatio as ImageAspectRatio} from './image';
45
- import type {PressHandler, TouchableElement} from './touchable';
48
+ import type {PressHandler, TouchableElement, TouchableProps as BaseTouchableProps} from './touchable';
49
+
50
+ type ControlProps = {
51
+ name?: string;
52
+ value?: boolean;
53
+ defaultValue?: boolean;
54
+ onChange?: (checked: boolean) => void;
55
+ disabled?: boolean;
56
+ };
57
+
58
+ export type CardSelectionProps = ExclusifyUnion<
59
+ {checkbox: ControlProps} | {switch: ControlProps} | {radioValue: string}
60
+ >;
61
+
62
+ export const useControlState = ({
63
+ value,
64
+ defaultValue,
65
+ onChange,
66
+ }: {
67
+ value?: boolean;
68
+ defaultValue?: boolean;
69
+ onChange?: (isChecked: boolean) => void;
70
+ } = {}): {isChecked: boolean; toggleChecked: () => void} => {
71
+ const isControlledByParent = value !== undefined;
72
+ const [isChecked, setIsChecked] = React.useState<boolean>(!!defaultValue);
73
+
74
+ const toggleChecked = () => {
75
+ if (!isControlledByParent) {
76
+ setIsChecked(!isChecked);
77
+ }
78
+ onChange?.(isControlledByParent ? !value : !isChecked);
79
+ };
80
+
81
+ return {isChecked: isControlledByParent ? !!value : isChecked, toggleChecked};
82
+ };
83
+
84
+ type CardControlProps = {
85
+ checkbox?: ControlProps;
86
+ switch?: ControlProps;
87
+ radioValue?: string;
88
+ checked: boolean;
89
+ onChange: () => void;
90
+ };
91
+
92
+ export const CardWithControl = ({
93
+ checkbox,
94
+ switch: switchProps,
95
+ radioValue,
96
+ checked,
97
+ onChange,
98
+ ...props
99
+ }: BaseTouchableProps & CardSelectionProps & Pick<CardControlProps, 'checked' | 'onChange'>): JSX.Element => {
100
+ const id = React.useId();
101
+ const render = ({controlElement}: {controlElement: React.ReactElement}) => (
102
+ <>
103
+ {props.children}
104
+ <div className={styles.topActionsContainer} data-testid="cardSelector" aria-hidden>
105
+ {controlElement}
106
+ </div>
107
+ </>
108
+ );
109
+ const renderWithControl = (control: React.ReactNode) => (
110
+ <div className={classnames(styles.selectionControl, props.className)} style={props.style}>
111
+ {control}
112
+ </div>
113
+ );
114
+
115
+ if (switchProps || checkbox) {
116
+ const Control = switchProps ? Switch : Checkbox;
117
+ return renderWithControl(
118
+ <Control
119
+ className={styles.selectionControlContent}
120
+ name={switchProps?.name ?? checkbox?.name ?? id}
121
+ checked={checked}
122
+ onChange={onChange}
123
+ disabled={switchProps?.disabled ?? checkbox?.disabled}
124
+ aria-label={props['aria-label']}
125
+ aria-labelledby={props['aria-labelledby']}
126
+ render={render}
127
+ />
128
+ );
129
+ }
130
+ return renderWithControl(
131
+ <RadioButton
132
+ className={styles.selectionControlContent}
133
+ value={radioValue}
134
+ aria-label={props['aria-label']}
135
+ aria-labelledby={props['aria-labelledby']}
136
+ render={render}
137
+ />
138
+ );
139
+ };
46
140
 
47
141
  export type CardAspectRatio = '1:1' | '16:9' | '7:10' | '9:10' | 'auto' | number;
48
142
  export type MediaAspectRatio = ImageAspectRatio | VideoAspectRatio | 'auto' | number;
@@ -67,6 +161,7 @@ type ContainerProps = {
67
161
  type: CardType;
68
162
  size: CardSize;
69
163
  variant?: Variant;
164
+ selected?: boolean;
70
165
  width?: string | number;
71
166
  height?: string | number;
72
167
  /** Gradient overlay color for cover cards. If not set it uses the theme color */
@@ -198,7 +293,11 @@ type TouchableProps = {
198
293
  >;
199
294
 
200
295
  type TouchableCard<T> = T & TouchableProps;
201
- export type MaybeTouchableCard<T> = ExclusifyUnion<TouchableCard<T> | T>;
296
+ export type CardInteractionProps<T> = ExclusifyUnion<
297
+ | (TouchableCard<T> & {selected?: boolean})
298
+ | (T & {selected?: boolean})
299
+ | (Omit<T, 'topActions' | 'onClose' | 'selected'> & CardSelectionProps)
300
+ >;
202
301
 
203
302
  type PrivateContainerProps = {
204
303
  children?: React.ReactNode;
@@ -219,6 +318,7 @@ const Container = React.forwardRef<HTMLDivElement, ContainerProps & MediaProps &
219
318
  dataAttributes,
220
319
  backgroundColor,
221
320
  variant,
321
+ selected,
222
322
  },
223
323
  ref
224
324
  ): JSX.Element => {
@@ -249,6 +349,10 @@ const Container = React.forwardRef<HTMLDivElement, ContainerProps & MediaProps &
249
349
  }}
250
350
  >
251
351
  <div
352
+ className={classnames({
353
+ [styles.selectionOutline]: !!selected,
354
+ [styles.selectionOutlineColor[normalizeVariant(variant || 'default')]]: !!selected,
355
+ })}
252
356
  style={{
253
357
  display: 'flex',
254
358
  flexDirection: 'column',
@@ -1189,7 +1293,7 @@ const TextContent = ({
1189
1293
  );
1190
1294
  };
1191
1295
 
1192
- type CardTouchableProps = {
1296
+ type CardTouchableProps = CardControlProps & {
1193
1297
  children: React.ReactNode;
1194
1298
  isTouchable: boolean;
1195
1299
  touchableAriaLabel?: string;
@@ -1205,6 +1309,11 @@ type CardTouchableProps = {
1205
1309
  };
1206
1310
 
1207
1311
  const CardTouchable = ({
1312
+ checkbox,
1313
+ switch: switchProps,
1314
+ radioValue,
1315
+ checked,
1316
+ onChange,
1208
1317
  children,
1209
1318
  isTouchable,
1210
1319
  touchableAriaLabel,
@@ -1239,6 +1348,29 @@ const CardTouchable = ({
1239
1348
  </div>
1240
1349
  );
1241
1350
 
1351
+ if (checkbox || switchProps || radioValue !== undefined) {
1352
+ return (
1353
+ <CardWithControl
1354
+ maybe
1355
+ {...(checkbox
1356
+ ? {checkbox}
1357
+ : switchProps
1358
+ ? {switch: switchProps}
1359
+ : {radioValue: radioValue ?? ''})}
1360
+ checked={checked}
1361
+ onChange={onChange}
1362
+ aria-label={touchableAriaLabel}
1363
+ aria-labelledby={ariaLabeledByProp}
1364
+ aria-description={ariaDescriptionProp}
1365
+ aria-describedby={ariaDescribedByProp}
1366
+ className={classnames(styles.touchable, styles.touchableContainer)}
1367
+ style={contentRadiusStyle}
1368
+ >
1369
+ {content}
1370
+ </CardWithControl>
1371
+ );
1372
+ }
1373
+
1242
1374
  if (isTouchable && segregateTouchableContent) {
1243
1375
  return hasTouchableInContent ? (
1244
1376
  <div className={classnames(styles.touchable, styles.touchableContainer)}>{content}</div>
@@ -1287,7 +1419,7 @@ const replaceRgbaWithColor = (stringWithRgbaColors: string, newColor: string): s
1287
1419
  return stringWithRgbaColors.replace(RGBA_REGEX, (_, alpha) => applyAlpha(newColor, parseFloat(alpha)));
1288
1420
  };
1289
1421
 
1290
- export const InternalCard = React.forwardRef<HTMLDivElement, MaybeTouchableCard<CardProps>>(
1422
+ export const InternalCard = React.forwardRef<HTMLDivElement, CardInteractionProps<CardProps>>(
1291
1423
  (
1292
1424
  {
1293
1425
  type,
@@ -1341,17 +1473,32 @@ export const InternalCard = React.forwardRef<HTMLDivElement, MaybeTouchableCard<
1341
1473
  videoLoop,
1342
1474
  videoAutoPlay,
1343
1475
  videoDataAttributes,
1476
+ selected,
1477
+ checkbox,
1478
+ switch: switchProps,
1479
+ radioValue,
1344
1480
  segregateTouchableContent = false,
1345
1481
  touchableAriaLabel: touchableAriaLabelProp,
1346
1482
  ...touchableProps
1347
1483
  },
1348
1484
  ref
1349
1485
  ): JSX.Element => {
1486
+ const {isChecked, toggleChecked} = useControlState(switchProps || checkbox);
1487
+ const radioContext = useRadioContext();
1488
+ const isSelectable = !!switchProps || !!checkbox || radioValue !== undefined;
1489
+ const isSelected =
1490
+ switchProps || checkbox
1491
+ ? isChecked
1492
+ : radioValue !== undefined
1493
+ ? radioContext.selectedValue === radioValue
1494
+ : selected;
1350
1495
  const {text: slotText, ref: slotRef} = useInnerText();
1351
1496
  const {text: headlineText, ref: headlineRef} = useInnerText();
1352
1497
  const touchableContentRef = React.useRef<TouchableElement>(null);
1353
- const isTouchable = !!(touchableProps.href || touchableProps.to || touchableProps.onPress);
1498
+ const isTouchable =
1499
+ isSelectable || !!(touchableProps.href || touchableProps.to || touchableProps.onPress);
1354
1500
  const hasTouchableInContent = !!(
1501
+ !isSelectable &&
1355
1502
  segregateTouchableContent &&
1356
1503
  !touchableAriaLabelProp &&
1357
1504
  (title || pretitle || headline || subtitle || description)
@@ -1379,7 +1526,7 @@ export const InternalCard = React.forwardRef<HTMLDivElement, MaybeTouchableCard<
1379
1526
 
1380
1527
  const shouldShowVideo = hasMediaVideo || hasBackgroundVideo;
1381
1528
  const {video, videoAction} = useVideoWithControls({
1382
- src: shouldShowVideo ? videoSrc : undefined,
1529
+ src: shouldShowVideo && !isSelectable ? videoSrc : undefined,
1383
1530
  poster: imageSrc,
1384
1531
  ref: videoRef,
1385
1532
  autoHeight:
@@ -1407,7 +1554,7 @@ export const InternalCard = React.forwardRef<HTMLDivElement, MaybeTouchableCard<
1407
1554
 
1408
1555
  const showButtonsInBody = !shouldShowFooter && hasButtons;
1409
1556
 
1410
- const topActionsLengthWithoutVideo = (topActions?.length || 0) + (onClose ? 1 : 0);
1557
+ const topActionsLengthWithoutVideo = isSelectable ? 1 : (topActions?.length || 0) + (onClose ? 1 : 0);
1411
1558
  const topActionsLength = videoAction
1412
1559
  ? topActionsLengthWithoutVideo + 1
1413
1560
  : topActionsLengthWithoutVideo;
@@ -1474,6 +1621,7 @@ export const InternalCard = React.forwardRef<HTMLDivElement, MaybeTouchableCard<
1474
1621
  dataAttributes={dataAttributes}
1475
1622
  ref={ref}
1476
1623
  variant={variant}
1624
+ selected={isSelected}
1477
1625
  width={width}
1478
1626
  height={height}
1479
1627
  aspectRatio={aspectRatio}
@@ -1515,13 +1663,18 @@ export const InternalCard = React.forwardRef<HTMLDivElement, MaybeTouchableCard<
1515
1663
  )}
1516
1664
 
1517
1665
  <CardTouchable
1666
+ checkbox={checkbox}
1667
+ switch={switchProps}
1668
+ radioValue={radioValue}
1669
+ checked={isChecked}
1670
+ onChange={toggleChecked}
1518
1671
  isTouchable={isTouchable}
1519
1672
  touchableAriaLabel={touchableAriaLabel}
1520
1673
  ariaLabeledByProp={ariaLabeledByProp}
1521
1674
  ariaDescriptionProp={ariaDescriptionProp}
1522
1675
  ariaDescribedByProp={ariaDescribedByProp}
1523
1676
  touchableProps={touchableProps}
1524
- segregateTouchableContent={segregateTouchableContent}
1677
+ segregateTouchableContent={isSelectable ? false : segregateTouchableContent}
1525
1678
  hasTouchableInContent={hasTouchableInContent}
1526
1679
  overlayClassname={overlayStyle}
1527
1680
  contentStyle={{
@@ -1558,7 +1711,7 @@ export const InternalCard = React.forwardRef<HTMLDivElement, MaybeTouchableCard<
1558
1711
  />
1559
1712
  )}
1560
1713
  <div
1561
- aria-hidden={isTouchable && !segregateTouchableContent}
1714
+ aria-hidden={isTouchable && !isSelectable && !segregateTouchableContent}
1562
1715
  data-testid="body"
1563
1716
  className={classnames(styles.touchable, {
1564
1717
  [styles.containerPaddingTopVariants[size]]:
@@ -10,7 +10,7 @@ import type {
10
10
  CardActionButtonSecondary,
11
11
  CardAspectRatio,
12
12
  CardSize,
13
- MaybeTouchableCard,
13
+ CardInteractionProps,
14
14
  MediaAspectRatio,
15
15
  SlotAlignment,
16
16
  TopActionsArray,
@@ -73,8 +73,8 @@ export type MediaCardProps = {
73
73
  footerDivider?: boolean;
74
74
  };
75
75
 
76
- export const MediaCard = React.forwardRef<HTMLDivElement, MaybeTouchableCard<MediaCardProps>>(
77
- ({size = 'default', slot, topActions, buttonPrimary, dataAttributes, ...rest}, ref) => {
76
+ export const MediaCard = React.forwardRef<HTMLDivElement, CardInteractionProps<MediaCardProps>>(
77
+ ({size = 'default', slot, buttonPrimary, dataAttributes, ...rest}, ref) => {
78
78
  return (
79
79
  <InternalCard
80
80
  type="media"
@@ -85,7 +85,6 @@ export const MediaCard = React.forwardRef<HTMLDivElement, MaybeTouchableCard<Med
85
85
  ...dataAttributes,
86
86
  }}
87
87
  slot={slot}
88
- topActions={topActions}
89
88
  buttonPrimary={buttonPrimary}
90
89
  ref={ref}
91
90
  {...rest}
@@ -4,12 +4,12 @@ import {InternalCard} from './card-internal';
4
4
  import {useThemeVariant} from './theme-variant-context';
5
5
 
6
6
  import type {MediaCardProps} from './card-media';
7
- import type {MaybeTouchableCard} from './card-internal';
7
+ import type {CardInteractionProps} from './card-internal';
8
8
 
9
9
  type NakedCardProps = Omit<MediaCardProps, 'footerBackgroundColor' | 'variant' | 'footerVariant'>;
10
10
 
11
- export const NakedCard = React.forwardRef<HTMLDivElement, MaybeTouchableCard<NakedCardProps>>(
12
- ({size = 'default', slot, topActions, buttonPrimary, dataAttributes, ...rest}, ref) => {
11
+ export const NakedCard = React.forwardRef<HTMLDivElement, CardInteractionProps<NakedCardProps>>(
12
+ ({size = 'default', slot, buttonPrimary, dataAttributes, ...rest}, ref) => {
13
13
  const variant = useThemeVariant();
14
14
  return (
15
15
  <InternalCard
@@ -21,7 +21,6 @@ export const NakedCard = React.forwardRef<HTMLDivElement, MaybeTouchableCard<Nak
21
21
  type="naked"
22
22
  size={size}
23
23
  slot={slot}
24
- topActions={topActions}
25
24
  buttonPrimary={buttonPrimary}
26
25
  ref={ref}
27
26
  {...rest}
package/src/checkbox.tsx CHANGED
@@ -86,6 +86,7 @@ type RenderProps = {
86
86
  'aria-label'?: string;
87
87
  role?: 'checkbox' | 'menuitemcheckbox';
88
88
  dataAttributes?: DataAttributes;
89
+ className?: string;
89
90
  };
90
91
 
91
92
  type ChildrenProps = {
@@ -101,6 +102,7 @@ type ChildrenProps = {
101
102
  'aria-labelledby'?: string;
102
103
  role?: 'checkbox' | 'menuitemcheckbox';
103
104
  dataAttributes?: DataAttributes;
105
+ className?: string;
104
106
  };
105
107
 
106
108
  const Checkbox = React.forwardRef<HTMLDivElement, RenderProps | ChildrenProps>((props, ref) => {
@@ -154,7 +156,10 @@ const Checkbox = React.forwardRef<HTMLDivElement, RenderProps | ChildrenProps>((
154
156
  }}
155
157
  tabIndex={disabled ? undefined : 0}
156
158
  ref={combineRefs(ref, focusableRef)}
157
- className={disabled ? styles.checkboxContainerDisabled : styles.checkboxContainer}
159
+ className={classnames(
160
+ disabled ? styles.checkboxContainerDisabled : styles.checkboxContainer,
161
+ props.className
162
+ )}
158
163
  aria-label={ariaLabel}
159
164
  aria-labelledby={ariaLabel ? undefined : labelId}
160
165
  aria-disabled={disabled}
@@ -0,0 +1,28 @@
1
+ import {openStoryPage, screen} from '../../test-utils';
2
+
3
+ const expectAiCardSnapshot = async () => {
4
+ const element = await screen.findByTestId('ai-card');
5
+ const image = await element.screenshot();
6
+
7
+ expect(image).toMatchImageSnapshot();
8
+ };
9
+
10
+ test('AiCard static text', async () => {
11
+ await openStoryPage({
12
+ id: 'community-vivo-aicard--default',
13
+ device: 'MOBILE_IOS',
14
+ args: {text: 'Lorem ipsum '},
15
+ });
16
+
17
+ await expectAiCardSnapshot();
18
+ });
19
+
20
+ test('AiCard line break at chars', async () => {
21
+ await openStoryPage({
22
+ id: 'community-vivo-aicard--default',
23
+ device: 'MOBILE_IOS',
24
+ args: {text: 'Lorem ipsum ', lineBreakAtChars: 8},
25
+ });
26
+
27
+ await expectAiCardSnapshot();
28
+ });
@@ -15,6 +15,7 @@ export default {
15
15
  };
16
16
 
17
17
  type Args = {
18
+ selected?: boolean;
18
19
  headlineType: TagType;
19
20
  headline: string;
20
21
  pretitle: string;
@@ -33,6 +34,7 @@ type Args = {
33
34
  };
34
35
 
35
36
  export const Default: StoryComponent<Args> = ({
37
+ selected,
36
38
  headlineType,
37
39
  headline,
38
40
  pretitle,
@@ -77,6 +79,7 @@ export const Default: StoryComponent<Args> = ({
77
79
 
78
80
  return (
79
81
  <AdvancedDataCard
82
+ selected={selected}
80
83
  dataAttributes={{testid: 'advanced-data-card'}}
81
84
  stackingGroup={
82
85
  stackingGroup ? (
@@ -113,6 +116,7 @@ export const Default: StoryComponent<Args> = ({
113
116
 
114
117
  Default.storyName = 'Advanced Data Card';
115
118
  Default.args = {
119
+ selected: undefined,
116
120
  headlineType: 'promo',
117
121
  headline: 'headline',
118
122
  pretitle: 'pretitle',
@@ -130,6 +134,7 @@ Default.args = {
130
134
  noSlotDivider: false,
131
135
  };
132
136
  Default.argTypes = {
137
+ selected: {type: 'boolean', options: [undefined, true, false], control: {type: 'select'}},
133
138
  headlineType: {
134
139
  options: ['promo', 'active', 'inactive', 'success', 'warning', 'error', 'info'],
135
140
  control: {type: 'select'},
@@ -38,3 +38,6 @@ import {AdvancedDataCard} from '../advanced-data-card';
38
38
  <AdvancedDataCard title="title" onPress={() => {}} to="/" />;
39
39
  // @ts-expect-error href and to can't be used together
40
40
  <AdvancedDataCard title="title" href="/" to="/" />;
41
+
42
+ <AdvancedDataCard title="Selected" selected />;
43
+ <AdvancedDataCard title="Unselected" selected={false} />;
@@ -1,4 +1,4 @@
1
- import {createVar, style, styleVariants} from '@vanilla-extract/css';
1
+ import {createVar, style, styleVariants, globalStyle} from '@vanilla-extract/css';
2
2
  import {sprinkles} from '../sprinkles.css';
3
3
  import * as mq from '../media-queries.css';
4
4
  import {vars as skinVars} from '../skins/skin-contract.css';
@@ -269,3 +269,28 @@ export const flexColumn = style({
269
269
  display: 'flex',
270
270
  flexDirection: 'column',
271
271
  });
272
+
273
+ export const selectionTouchable = style({
274
+ borderRadius: `calc(${skinVars.borderRadii.container} - 1px)`,
275
+ });
276
+
277
+ // Draw the focus ring inside Boxed to prevent its overflow from clipping the native outline.
278
+ globalStyle(`${selectionTouchable} > div:focus-visible`, {outline: 'none'});
279
+ globalStyle(`${selectionTouchable} > div:focus-visible::before`, {
280
+ content: '""',
281
+ position: 'absolute',
282
+ inset: 0,
283
+ border: '1.5px solid',
284
+ borderColor: skinVars.colors.controlActivated,
285
+ borderRadius: 'inherit',
286
+ pointerEvents: 'none',
287
+ zIndex: 2,
288
+ });
289
+
290
+ const switchWidth = {default: 42, ios: 51};
291
+ const controlSpacing = 16;
292
+
293
+ export const switchSpace = styleVariants({
294
+ default: {width: switchWidth.default + controlSpacing, flexShrink: 0},
295
+ ios: {width: switchWidth.ios + controlSpacing, flexShrink: 0},
296
+ });