@telefonica/mistica 17.9.0 → 17.11.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 (272) hide show
  1. package/css/blau.css +40 -0
  2. package/css/community/cyber.css +40 -0
  3. package/css/esimflag.css +40 -0
  4. package/css/mistica.css +1 -1
  5. package/css/movistar.css +40 -0
  6. package/css/o2.css +41 -0
  7. package/css/telefonica.css +42 -0
  8. package/css/vivo-evolution.css +48 -0
  9. package/css/vivo.css +40 -0
  10. package/dist/accordion.css-mistica.js +8 -8
  11. package/dist/align.css-mistica.js +1 -1
  12. package/dist/autocomplete.css-mistica.js +4 -4
  13. package/dist/avatar.css-mistica.js +3 -3
  14. package/dist/badge.css-mistica.js +3 -3
  15. package/dist/box.css-mistica.js +13 -13
  16. package/dist/boxed.css-mistica.js +24 -24
  17. package/dist/button-group.css-mistica.js +3 -3
  18. package/dist/button-layout.css-mistica.js +15 -15
  19. package/dist/button.css-mistica.js +64 -64
  20. package/dist/callout.css-mistica.js +11 -11
  21. package/dist/card-internal.css-mistica.js +34 -34
  22. package/dist/carousel.css-mistica.js +35 -35
  23. package/dist/checkbox.css-mistica.js +15 -15
  24. package/dist/chip.css-mistica.js +20 -20
  25. package/dist/circle.css-mistica.js +1 -1
  26. package/dist/community/advanced-data-card.css-mistica.js +18 -18
  27. package/dist/community/ai-card.css-mistica.js +9 -9
  28. package/dist/community/blocks.css-mistica.js +3 -3
  29. package/dist/community/cyber/example-component.css-mistica.js +1 -1
  30. package/dist/community/example-component.css-mistica.js +1 -1
  31. package/dist/community/skins/cyber-skin.js +20 -0
  32. package/dist/counter.css-mistica.js +7 -7
  33. package/dist/cover-hero.css-mistica.js +13 -13
  34. package/dist/credit-card-number-field.css-mistica.js +5 -5
  35. package/dist/date-field.css-mistica.js +1 -1
  36. package/dist/date-time-picker.css-mistica.js +1 -1
  37. package/dist/dialog.css-mistica.js +8 -8
  38. package/dist/divider.css-mistica.js +5 -5
  39. package/dist/double-field.css-mistica.js +4 -4
  40. package/dist/drawer.css-mistica.js +10 -10
  41. package/dist/drawer.d.ts +5 -1
  42. package/dist/drawer.js +131 -82
  43. package/dist/empty-state-card.css-mistica.js +1 -1
  44. package/dist/empty-state.css-mistica.js +12 -12
  45. package/dist/fade-in.css-mistica.js +1 -1
  46. package/dist/fade-out.css-mistica.js +1 -1
  47. package/dist/feedback.css-mistica.js +3 -3
  48. package/dist/file-upload.css-mistica.js +12 -12
  49. package/dist/fixed-footer-layout.css-mistica.js +5 -5
  50. package/dist/form.css-mistica.js +1 -1
  51. package/dist/form.d.ts +1 -1
  52. package/dist/grid-layout.css-mistica.js +3 -3
  53. package/dist/grid-layout.d.ts +6 -1
  54. package/dist/grid-layout.js +33 -18
  55. package/dist/grid.css-mistica.js +120 -120
  56. package/dist/header.css-mistica.js +1 -1
  57. package/dist/hero.css-mistica.js +9 -9
  58. package/dist/horizontal-scroll.css-mistica.js +1 -1
  59. package/dist/icon-button.css-mistica.js +58 -58
  60. package/dist/icons/icon-chevron.css-mistica.js +2 -2
  61. package/dist/icons/icon-error.css-mistica.js +1 -1
  62. package/dist/image.css-mistica.js +7 -7
  63. package/dist/index.d.ts +2 -0
  64. package/dist/index.js +4 -0
  65. package/dist/inline.css-mistica.js +11 -11
  66. package/dist/list.css-mistica.js +32 -32
  67. package/dist/loading-bar.css-mistica.js +1 -1
  68. package/dist/loading-screen.css-mistica.js +9 -9
  69. package/dist/logo.css-mistica.js +5 -5
  70. package/dist/menu.css-mistica.js +12 -12
  71. package/dist/message.css-mistica.js +172 -0
  72. package/dist/message.css.d.ts +40 -0
  73. package/dist/message.d.ts +48 -0
  74. package/dist/message.js +389 -0
  75. package/dist/mosaic.css-mistica.js +1 -1
  76. package/dist/navigation-bar.css-mistica.js +20 -20
  77. package/dist/navigation-breadcrumbs.css-mistica.js +1 -1
  78. package/dist/navigation-shared.css-mistica.js +18 -18
  79. package/dist/package-version.js +1 -1
  80. package/dist/pagination.css-mistica.js +30 -29
  81. package/dist/pagination.css.d.ts +1 -2
  82. package/dist/pagination.d.ts +0 -16
  83. package/dist/pagination.js +108 -115
  84. package/dist/pin-field.css-mistica.js +8 -8
  85. package/dist/popover.css-mistica.js +1 -1
  86. package/dist/progress-bar.css-mistica.js +7 -7
  87. package/dist/radio-button.css-mistica.js +32 -32
  88. package/dist/rating.css-mistica.js +4 -4
  89. package/dist/responsive-layout.css-mistica.js +6 -6
  90. package/dist/screen-reader-only.css-mistica.js +1 -1
  91. package/dist/select.css-mistica.js +22 -22
  92. package/dist/sheet-action-row.css-mistica.js +2 -2
  93. package/dist/sheet-common.css-mistica.js +9 -9
  94. package/dist/sheet-info.css-mistica.js +3 -3
  95. package/dist/sidenav-bar-layout.css-mistica.js +2 -2
  96. package/dist/sidenav-bar.css-mistica.js +107 -107
  97. package/dist/skeletons.css-mistica.js +10 -10
  98. package/dist/skins/blau.js +20 -0
  99. package/dist/skins/esimflag.js +20 -0
  100. package/dist/skins/movistar.js +20 -0
  101. package/dist/skins/o2.d.ts +1 -0
  102. package/dist/skins/o2.js +21 -0
  103. package/dist/skins/skin-contract.css-mistica.js +769 -749
  104. package/dist/skins/skin-contract.css.d.ts +20 -0
  105. package/dist/skins/telefonica.js +20 -0
  106. package/dist/skins/types/colors.d.ts +10 -0
  107. package/dist/skins/vivo-evolution.js +20 -0
  108. package/dist/skins/vivo.js +20 -0
  109. package/dist/skip-link.css-mistica.js +2 -2
  110. package/dist/slider.css-mistica.js +27 -27
  111. package/dist/snackbar.css-mistica.js +17 -17
  112. package/dist/spinner.css-mistica.js +5 -5
  113. package/dist/square.css-mistica.js +1 -1
  114. package/dist/stack.css-mistica.js +5 -5
  115. package/dist/stacking-group.css-mistica.js +2 -2
  116. package/dist/stepper.css-mistica.js +10 -10
  117. package/dist/switch-component.css-mistica.js +39 -39
  118. package/dist/table.css-mistica.js +9 -9
  119. package/dist/tabs.css-mistica.js +19 -19
  120. package/dist/tag.css-mistica.js +1 -1
  121. package/dist/text-field-base.css-mistica.js +26 -26
  122. package/dist/text-field-components.css-mistica.js +12 -12
  123. package/dist/text-link.css-mistica.js +6 -6
  124. package/dist/text-tokens.d.ts +2 -0
  125. package/dist/text-tokens.js +46 -30
  126. package/dist/text.css-mistica.js +6 -6
  127. package/dist/timeline.css-mistica.js +13 -13
  128. package/dist/timer.css-mistica.js +8 -8
  129. package/dist/tooltip.css-mistica.js +3 -3
  130. package/dist/tooltip.js +88 -88
  131. package/dist/touchable.css-mistica.js +5 -5
  132. package/dist/utils/aspect-ratio-support.css-mistica.js +4 -4
  133. package/dist/video.css-mistica.js +2 -2
  134. package/dist/vivinho-loading-animation/vivinho-loading-animation.css-mistica.js +1 -1
  135. package/dist-es/accordion.css-mistica.js +7 -7
  136. package/dist-es/align.css-mistica.js +1 -1
  137. package/dist-es/autocomplete.css-mistica.js +2 -2
  138. package/dist-es/avatar.css-mistica.js +2 -2
  139. package/dist-es/badge.css-mistica.js +2 -2
  140. package/dist-es/box.css-mistica.js +13 -13
  141. package/dist-es/boxed.css-mistica.js +23 -23
  142. package/dist-es/button-group.css-mistica.js +2 -2
  143. package/dist-es/button-layout.css-mistica.js +15 -15
  144. package/dist-es/button.css-mistica.js +43 -43
  145. package/dist-es/callout.css-mistica.js +11 -11
  146. package/dist-es/card-internal.css-mistica.js +23 -23
  147. package/dist-es/carousel.css-mistica.js +10 -10
  148. package/dist-es/checkbox.css-mistica.js +13 -13
  149. package/dist-es/chip.css-mistica.js +17 -17
  150. package/dist-es/circle.css-mistica.js +1 -1
  151. package/dist-es/community/advanced-data-card.css-mistica.js +9 -9
  152. package/dist-es/community/ai-card.css-mistica.js +3 -3
  153. package/dist-es/community/blocks.css-mistica.js +2 -2
  154. package/dist-es/community/cyber/example-component.css-mistica.js +1 -1
  155. package/dist-es/community/example-component.css-mistica.js +1 -1
  156. package/dist-es/community/skins/cyber-skin.js +20 -0
  157. package/dist-es/counter.css-mistica.js +2 -2
  158. package/dist-es/cover-hero.css-mistica.js +3 -3
  159. package/dist-es/credit-card-number-field.css-mistica.js +4 -4
  160. package/dist-es/date-field.css-mistica.js +1 -1
  161. package/dist-es/date-time-picker.css-mistica.js +1 -1
  162. package/dist-es/dialog.css-mistica.js +5 -5
  163. package/dist-es/divider.css-mistica.js +5 -5
  164. package/dist-es/double-field.css-mistica.js +4 -4
  165. package/dist-es/drawer.css-mistica.js +2 -2
  166. package/dist-es/drawer.js +167 -118
  167. package/dist-es/empty-state-card.css-mistica.js +1 -1
  168. package/dist-es/empty-state.css-mistica.js +7 -7
  169. package/dist-es/fade-in.css-mistica.js +1 -1
  170. package/dist-es/fade-out.css-mistica.js +1 -1
  171. package/dist-es/feedback.css-mistica.js +2 -2
  172. package/dist-es/file-upload.css-mistica.js +8 -8
  173. package/dist-es/fixed-footer-layout.css-mistica.js +3 -3
  174. package/dist-es/form.css-mistica.js +1 -1
  175. package/dist-es/grid-layout.css-mistica.js +3 -3
  176. package/dist-es/grid-layout.js +37 -22
  177. package/dist-es/grid.css-mistica.js +120 -120
  178. package/dist-es/header.css-mistica.js +1 -1
  179. package/dist-es/hero.css-mistica.js +3 -3
  180. package/dist-es/horizontal-scroll.css-mistica.js +1 -1
  181. package/dist-es/icon-button.css-mistica.js +56 -56
  182. package/dist-es/icons/icon-chevron.css-mistica.js +2 -2
  183. package/dist-es/icons/icon-error.css-mistica.js +1 -1
  184. package/dist-es/image.css-mistica.js +4 -4
  185. package/dist-es/index.js +2355 -2354
  186. package/dist-es/inline.css-mistica.js +11 -11
  187. package/dist-es/list.css-mistica.js +8 -8
  188. package/dist-es/loading-bar.css-mistica.js +1 -1
  189. package/dist-es/loading-screen.css-mistica.js +5 -5
  190. package/dist-es/logo.css-mistica.js +5 -5
  191. package/dist-es/menu.css-mistica.js +12 -12
  192. package/dist-es/message.css-mistica.js +41 -0
  193. package/dist-es/message.js +334 -0
  194. package/dist-es/mosaic.css-mistica.js +1 -1
  195. package/dist-es/navigation-bar.css-mistica.js +10 -10
  196. package/dist-es/navigation-breadcrumbs.css-mistica.js +1 -1
  197. package/dist-es/navigation-shared.css-mistica.js +10 -10
  198. package/dist-es/package-version.js +1 -1
  199. package/dist-es/pagination.css-mistica.js +23 -19
  200. package/dist-es/pagination.js +137 -141
  201. package/dist-es/pin-field.css-mistica.js +2 -2
  202. package/dist-es/popover.css-mistica.js +1 -1
  203. package/dist-es/progress-bar.css-mistica.js +7 -7
  204. package/dist-es/radio-button.css-mistica.js +24 -24
  205. package/dist-es/rating.css-mistica.js +4 -4
  206. package/dist-es/responsive-layout.css-mistica.js +6 -6
  207. package/dist-es/screen-reader-only.css-mistica.js +1 -1
  208. package/dist-es/select.css-mistica.js +18 -18
  209. package/dist-es/sheet-action-row.css-mistica.js +2 -2
  210. package/dist-es/sheet-common.css-mistica.js +2 -2
  211. package/dist-es/sheet-info.css-mistica.js +2 -2
  212. package/dist-es/sidenav-bar-layout.css-mistica.js +2 -2
  213. package/dist-es/sidenav-bar.css-mistica.js +72 -72
  214. package/dist-es/skeletons.css-mistica.js +7 -7
  215. package/dist-es/skins/blau.js +20 -0
  216. package/dist-es/skins/esimflag.js +20 -0
  217. package/dist-es/skins/movistar.js +20 -0
  218. package/dist-es/skins/o2.js +21 -0
  219. package/dist-es/skins/skin-contract.css-mistica.js +769 -749
  220. package/dist-es/skins/telefonica.js +20 -0
  221. package/dist-es/skins/vivo-evolution.js +20 -0
  222. package/dist-es/skins/vivo.js +20 -0
  223. package/dist-es/skip-link.css-mistica.js +2 -2
  224. package/dist-es/slider.css-mistica.js +20 -20
  225. package/dist-es/snackbar.css-mistica.js +5 -5
  226. package/dist-es/spinner.css-mistica.js +2 -2
  227. package/dist-es/square.css-mistica.js +1 -1
  228. package/dist-es/stack.css-mistica.js +5 -5
  229. package/dist-es/stacking-group.css-mistica.js +2 -2
  230. package/dist-es/stepper.css-mistica.js +4 -4
  231. package/dist-es/style.css +1 -1
  232. package/dist-es/switch-component.css-mistica.js +37 -37
  233. package/dist-es/table.css-mistica.js +9 -9
  234. package/dist-es/tabs.css-mistica.js +18 -18
  235. package/dist-es/tag.css-mistica.js +1 -1
  236. package/dist-es/text-field-base.css-mistica.js +17 -17
  237. package/dist-es/text-field-components.css-mistica.js +4 -4
  238. package/dist-es/text-link.css-mistica.js +6 -6
  239. package/dist-es/text-tokens.js +26 -16
  240. package/dist-es/text.css-mistica.js +6 -6
  241. package/dist-es/timeline.css-mistica.js +10 -10
  242. package/dist-es/timer.css-mistica.js +7 -7
  243. package/dist-es/tooltip.css-mistica.js +2 -2
  244. package/dist-es/tooltip.js +109 -109
  245. package/dist-es/touchable.css-mistica.js +2 -2
  246. package/dist-es/utils/aspect-ratio-support.css-mistica.js +3 -3
  247. package/dist-es/video.css-mistica.js +2 -2
  248. package/dist-es/vivinho-loading-animation/vivinho-loading-animation.css-mistica.js +1 -1
  249. package/doc/layout.md +1 -1
  250. package/doc/llms/figma-mcp.md +1 -0
  251. package/package.json +1 -1
  252. package/src/community/skins/cyber-skin.tsx +20 -0
  253. package/src/drawer.tsx +43 -12
  254. package/src/form.tsx +1 -1
  255. package/src/grid-layout.tsx +20 -0
  256. package/src/index.tsx +2 -0
  257. package/src/message.css.ts +148 -0
  258. package/src/message.tsx +303 -0
  259. package/src/package-version.tsx +1 -1
  260. package/src/pagination.css.ts +15 -18
  261. package/src/pagination.tsx +100 -102
  262. package/src/skins/blau.tsx +20 -0
  263. package/src/skins/esimflag.tsx +20 -0
  264. package/src/skins/movistar.tsx +20 -0
  265. package/src/skins/o2.tsx +21 -0
  266. package/src/skins/skin-contract.css.ts +10 -0
  267. package/src/skins/telefonica.tsx +20 -0
  268. package/src/skins/types/colors.tsx +10 -0
  269. package/src/skins/vivo-evolution.tsx +20 -0
  270. package/src/skins/vivo.tsx +20 -0
  271. package/src/text-tokens.tsx +14 -0
  272. package/src/tooltip.tsx +5 -2
package/src/drawer.tsx CHANGED
@@ -17,9 +17,13 @@ import {ButtonLink, ButtonPrimary, ButtonSecondary} from './button';
17
17
  import Divider from './divider';
18
18
  import {getPrefixedDataAttributes} from './utils/dom';
19
19
  import * as tokens from './text-tokens';
20
+ import Form from './form';
20
21
 
22
+ import type {FormProps} from './form';
21
23
  import type {DataAttributes, HeadingType, TrackingEvent} from './utils/types';
22
24
 
25
+ type DrawerFormProps = Omit<FormProps, 'children'>;
26
+
23
27
  /**
24
28
  * Renders divider or a div with transparent border to avoid the small but noticeable layout shift on scroll
25
29
  */
@@ -31,6 +35,7 @@ type DrawerLayoutProps = {
31
35
  children: React.ReactNode;
32
36
  onClose: () => void;
33
37
  onDismiss?: () => void;
38
+ form?: DrawerFormProps;
34
39
  };
35
40
 
36
41
  type DrawerPropsRef = {
@@ -39,7 +44,7 @@ type DrawerPropsRef = {
39
44
  };
40
45
 
41
46
  const DrawerLayout = React.forwardRef<DrawerPropsRef, DrawerLayoutProps>(
42
- ({width, children, onClose, onDismiss}, ref) => {
47
+ ({width, children, onClose, onDismiss, form}, ref) => {
43
48
  useSetModalStateEffect();
44
49
  const {isMobile} = useScreenSize();
45
50
  const [isOpen, setIsOpen] = React.useState(false);
@@ -79,6 +84,17 @@ const DrawerLayout = React.forwardRef<DrawerPropsRef, DrawerLayoutProps>(
79
84
  };
80
85
  }, [dismiss]);
81
86
 
87
+ const content = (
88
+ <div
89
+ data-testid="drawerLayout"
90
+ ref={open}
91
+ style={{width: widthStyle}}
92
+ className={classnames(styles.container, isOpen ? styles.open : styles.closed)}
93
+ >
94
+ {children}
95
+ </div>
96
+ );
97
+
82
98
  return (
83
99
  <Portal>
84
100
  <FocusTrap>
@@ -91,14 +107,16 @@ const DrawerLayout = React.forwardRef<DrawerPropsRef, DrawerLayoutProps>(
91
107
  isOpen ? styles.overlayOpen : styles.overlayClosed
92
108
  )}
93
109
  />
94
- <div
95
- data-testid="drawerLayout"
96
- ref={open}
97
- style={{width: widthStyle}}
98
- className={classnames(styles.container, isOpen ? styles.open : styles.closed)}
99
- >
100
- {children}
101
- </div>
110
+ {form ? (
111
+ <Form
112
+ {...form}
113
+ onSubmit={async (...args) => Promise.resolve(form.onSubmit(...args)).then(close)}
114
+ >
115
+ {content}
116
+ </Form>
117
+ ) : (
118
+ content
119
+ )}
102
120
  </FocusTrap>
103
121
  </Portal>
104
122
  );
@@ -140,10 +158,12 @@ type DrawerProps = {
140
158
  * width is ignored in mobile viewport
141
159
  */
142
160
  width?: number;
161
+ /** if form is set, button onPress is ignored. Use form.onSubmit */
143
162
  button?: ButtonProps;
144
163
  secondaryButton?: ButtonProps;
145
164
  buttonLink?: ButtonProps;
146
165
  dataAttributes?: DataAttributes;
166
+ form?: DrawerFormProps | undefined;
147
167
  };
148
168
 
149
169
  const Drawer = ({
@@ -160,6 +180,7 @@ const Drawer = ({
160
180
  secondaryButton,
161
181
  buttonLink,
162
182
  dataAttributes,
183
+ form,
163
184
  }: DrawerProps): JSX.Element => {
164
185
  const layoutRef = React.useRef<DrawerPropsRef>(null);
165
186
  const hasActions = !!(button || secondaryButton || buttonLink);
@@ -182,7 +203,7 @@ const Drawer = ({
182
203
  });
183
204
 
184
205
  return (
185
- <DrawerLayout width={width} ref={layoutRef} onClose={onClose} onDismiss={onDismiss}>
206
+ <DrawerLayout width={width} ref={layoutRef} onClose={onClose} onDismiss={onDismiss} form={form}>
186
207
  <section
187
208
  role="dialog"
188
209
  aria-modal="true"
@@ -243,7 +264,17 @@ const Drawer = ({
243
264
  <div className={styles.buttonsLayoutContainer}>
244
265
  <ButtonLayout
245
266
  primaryButton={
246
- button && (
267
+ button &&
268
+ (form ? (
269
+ <ButtonPrimary
270
+ submit
271
+ trackEvent={button.trackEvent}
272
+ trackingEvent={button.trackingEvent}
273
+ disabled={button.disabled}
274
+ >
275
+ {button.text}
276
+ </ButtonPrimary>
277
+ ) : (
247
278
  <ButtonPrimary
248
279
  trackEvent={button.trackEvent}
249
280
  trackingEvent={button.trackingEvent}
@@ -252,7 +283,7 @@ const Drawer = ({
252
283
  >
253
284
  {button.text}
254
285
  </ButtonPrimary>
255
- )
286
+ ))
256
287
  }
257
288
  secondaryButton={
258
289
  secondaryButton && (
package/src/form.tsx CHANGED
@@ -26,7 +26,7 @@ export type FormValues = {[name: string]: any};
26
26
 
27
27
  type HTMLFieldElement = HTMLSelectElement | HTMLInputElement;
28
28
 
29
- type FormProps = {
29
+ export type FormProps = {
30
30
  id?: string;
31
31
  onSubmit: (values: FormValues, rawValues: FormValues) => Promise<void> | void;
32
32
  initialValues?: FormValues;
@@ -32,6 +32,12 @@ type PropsTemplate8plus4 = {
32
32
  right: React.ReactNode;
33
33
  };
34
34
 
35
+ type PropsTemplate7plus5 = {
36
+ template: '7+5';
37
+ left: React.ReactNode;
38
+ right: React.ReactNode;
39
+ };
40
+
35
41
  type PropsTemplate4plus6 = {
36
42
  template: '4+6';
37
43
  left: React.ReactNode;
@@ -65,6 +71,7 @@ type Props = CommonProps &
65
71
  | PropsChildren
66
72
  | PropsTemplate6plus6
67
73
  | PropsTemplate8plus4
74
+ | PropsTemplate7plus5
68
75
  | PropsTemplate4plus6
69
76
  | PropsTemplate5plus4
70
77
  | PropsTemplate3plus9
@@ -125,6 +132,19 @@ const GridLayout = ({
125
132
  );
126
133
  }
127
134
 
135
+ if (template === '7+5') {
136
+ return (
137
+ <div {...gridStyles} {...prefixedDataAttributes}>
138
+ <DesktopContainerTypeContextProvider value="medium">
139
+ <div {...spanStyles(7)}>{left}</div>
140
+ </DesktopContainerTypeContextProvider>
141
+ <DesktopContainerTypeContextProvider value="small">
142
+ <div {...spanStyles(5)}>{right}</div>
143
+ </DesktopContainerTypeContextProvider>
144
+ </div>
145
+ );
146
+ }
147
+
128
148
  if (template === '4+6') {
129
149
  return (
130
150
  <div {...gridStyles} {...prefixedDataAttributes}>
package/src/index.tsx CHANGED
@@ -17,6 +17,8 @@ export {default as ButtonLayout} from './button-layout';
17
17
  export {default as Counter} from './counter';
18
18
  export {default as FixedFooterLayout} from './fixed-footer-layout';
19
19
  export {default as ButtonFixedFooterLayout} from './button-fixed-footer-layout';
20
+ export {default as Message} from './message';
21
+ export type {MessageProps} from './message';
20
22
  export {default as Snackbar} from './snackbar';
21
23
  export {useSnackbar} from './snackbar-context';
22
24
  export {Portal} from './portal';
@@ -0,0 +1,148 @@
1
+ import {globalStyle, style, styleVariants} from '@vanilla-extract/css';
2
+ import {vars} from './skins/skin-contract.css';
3
+
4
+ const bubbleRadius = '4px';
5
+ export const minimumBubbleSize = 48;
6
+ export const bubblePaddingY = 12;
7
+ export const bubblePaddingX = 16;
8
+ export const minimumErrorWidth = 184;
9
+ const boxedErrorInset = 16;
10
+ const actionsGap = 16;
11
+ const actionsInset = 4;
12
+
13
+ export const message = style({display: 'flex', width: 'fit-content', minWidth: 0});
14
+ export const direction = styleVariants({
15
+ incoming: {},
16
+ outgoing: {flexDirection: 'row-reverse', marginLeft: 'auto'},
17
+ });
18
+ export const asset = style({flexShrink: 0});
19
+ export const assetAlignment = styleVariants({
20
+ incoming: {alignSelf: 'flex-start', marginRight: 8},
21
+ outgoing: {alignSelf: 'flex-start', marginLeft: 8},
22
+ });
23
+ export const unboxedAssetSpacing = styleVariants({
24
+ incoming: {marginRight: 12},
25
+ outgoing: {marginLeft: 12},
26
+ });
27
+ export const messageContent = style({display: 'flex', flexDirection: 'column', minWidth: 0});
28
+ export const row = style({display: 'flex', width: '100%', minWidth: 0, alignItems: 'flex-start'});
29
+ export const rowDirection = styleVariants({incoming: {}, outgoing: {flexDirection: 'row-reverse'}});
30
+ export const column = style({display: 'flex', flexDirection: 'column', minWidth: 0});
31
+ export const touchable = style({textAlign: 'left', minWidth: 0, borderRadius: vars.borderRadii.container});
32
+ export const corners = styleVariants({
33
+ 'incoming-standalone': {borderTopLeftRadius: bubbleRadius},
34
+ 'incoming-first': {borderBottomLeftRadius: bubbleRadius},
35
+ 'incoming-middle': {borderTopLeftRadius: bubbleRadius, borderBottomLeftRadius: bubbleRadius},
36
+ 'incoming-last': {borderTopLeftRadius: bubbleRadius},
37
+ 'outgoing-standalone': {borderBottomRightRadius: bubbleRadius},
38
+ 'outgoing-first': {borderBottomRightRadius: bubbleRadius},
39
+ 'outgoing-middle': {borderTopRightRadius: bubbleRadius, borderBottomRightRadius: bubbleRadius},
40
+ 'outgoing-last': {borderTopRightRadius: bubbleRadius},
41
+ });
42
+ export const bubble = style({
43
+ display: 'flex',
44
+ flexDirection: 'column',
45
+ gap: 4,
46
+ borderRadius: 'inherit',
47
+ overflowWrap: 'anywhere',
48
+ });
49
+ export const boxed = style({
50
+ boxSizing: 'border-box',
51
+ padding: `${bubblePaddingY}px ${bubblePaddingX}px`,
52
+ minWidth: minimumBubbleSize,
53
+ minHeight: minimumBubbleSize,
54
+ });
55
+ export const incomingBackground = styleVariants({
56
+ default: {background: vars.colors.messageBubbleBackgroundIncoming},
57
+ brand: {background: vars.colors.messageBubbleBackgroundIncomingBrand},
58
+ negative: {background: vars.colors.messageBubbleBackgroundIncomingNegative},
59
+ alternative: {background: vars.colors.messageBubbleBackgroundIncomingAlternative},
60
+ media: {background: vars.colors.messageBubbleBackgroundIncomingMedia},
61
+ });
62
+ export const outgoingBackground = styleVariants({
63
+ default: {background: vars.colors.messageBubbleBackgroundOutgoing},
64
+ brand: {background: vars.colors.messageBubbleBackgroundOutgoingBrand},
65
+ negative: {background: vars.colors.messageBubbleBackgroundOutgoingNegative},
66
+ alternative: {background: vars.colors.messageBubbleBackgroundOutgoingAlternative},
67
+ media: {background: vars.colors.messageBubbleBackgroundOutgoingMedia},
68
+ });
69
+ export const errorBackground = style({background: vars.colors.backgroundContainerError});
70
+ export const interactive = style({
71
+ selectors: {
72
+ [`${touchable}:hover &`]: {
73
+ backgroundImage: `linear-gradient(${vars.colors.backgroundContainerHover}, ${vars.colors.backgroundContainerHover})`,
74
+ },
75
+ [`${touchable}:active &`]: {
76
+ backgroundImage: `linear-gradient(${vars.colors.backgroundContainerPressed}, ${vars.colors.backgroundContainerPressed})`,
77
+ },
78
+ },
79
+ });
80
+ export const content = style({
81
+ display: 'flex',
82
+ flexDirection: 'column',
83
+ gap: 4,
84
+ minWidth: 0,
85
+ whiteSpace: 'pre-wrap',
86
+ });
87
+ export const contentSlot = style({minWidth: 0, maxWidth: '100%', overflowX: 'auto', overflowY: 'hidden'});
88
+ globalStyle(`${contentSlot} > *`, {maxWidth: '100%'});
89
+ export const timestamp = style({alignSelf: 'flex-end'});
90
+ export const actions = style({display: 'flex', flexShrink: 0, alignItems: 'center', gap: actionsGap});
91
+ export const footerActions = style({marginTop: actionsInset, flexWrap: 'wrap'});
92
+ export const sideActions = styleVariants({
93
+ incoming: {marginLeft: actionsInset},
94
+ outgoing: {marginRight: actionsInset},
95
+ });
96
+ export const outgoingFooterActions = style({justifyContent: 'flex-end'});
97
+ const maximumSideSlotWidth = '40%';
98
+ export const sideSlot = style({
99
+ display: 'flex',
100
+ flexShrink: 0,
101
+ alignItems: 'center',
102
+ minWidth: 0,
103
+ maxWidth: maximumSideSlotWidth,
104
+ overflowX: 'auto',
105
+ overflowY: 'hidden',
106
+ });
107
+ globalStyle(`${sideSlot} > *`, {maxWidth: '100%'});
108
+ export const unboxedActions = style({marginLeft: -8});
109
+ export const error = style({
110
+ boxSizing: 'border-box',
111
+ display: 'grid',
112
+ gridTemplateColumns: 'auto minmax(0, 1fr)',
113
+ alignItems: 'flex-start',
114
+ gap: 4,
115
+ marginTop: 4,
116
+ minWidth: 0,
117
+ overflowWrap: 'anywhere',
118
+ });
119
+ export const boxedError = style({paddingInline: boxedErrorInset});
120
+ export const errorIcon = style({
121
+ display: 'block',
122
+ width: ['1rem', '1lh'],
123
+ height: ['1rem', '1lh'],
124
+ });
125
+
126
+ export const body = style({display: 'flex', flexDirection: 'column', gap: 4});
127
+ export const inlineTimestamp = style({flexDirection: 'row', alignItems: 'baseline', gap: 8});
128
+ export const text = style({display: 'inline-block', maxWidth: '100%'});
129
+
130
+ export const footer = style({
131
+ minWidth: 0,
132
+ maxWidth: '100%',
133
+ overflowWrap: 'anywhere',
134
+ overflowX: 'auto',
135
+ overflowY: 'hidden',
136
+ });
137
+ globalStyle(`${footer} > *`, {maxWidth: '100%'});
138
+ export const errorFooter = style({width: 0, minWidth: '100%', overflowWrap: 'anywhere'});
139
+ export const shortErrorLayout = styleVariants({
140
+ incoming: {display: 'flex', flexDirection: 'column', alignItems: 'flex-start'},
141
+ outgoing: {display: 'flex', flexDirection: 'column', alignItems: 'flex-end'},
142
+ });
143
+ export const shortErrorFooter = style({
144
+ width: 'max-content',
145
+ minWidth: 0,
146
+ maxWidth: `max(100%, ${minimumErrorWidth}px)`,
147
+ flexShrink: 0,
148
+ });
@@ -0,0 +1,303 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import classnames from 'classnames';
5
+ import Touchable from './touchable';
6
+ import Avatar from './avatar';
7
+ import {Text1, Text2} from './text';
8
+ import {IconButton} from './icon-button';
9
+ import IconWarningRegular from './generated/mistica-icons/icon-warning-regular';
10
+ import ScreenReaderOnly from './screen-reader-only';
11
+ import {ThemeVariant, useThemeVariant} from './theme-variant-context';
12
+ import {useElementDimensions, useTheme} from './hooks';
13
+ import {vars} from './skins/skin-contract.css';
14
+ import {getPrefixedDataAttributes} from './utils/dom';
15
+ import * as tokens from './text-tokens';
16
+ import * as styles from './message.css';
17
+
18
+ import type {IconButtonProps} from './icon-button';
19
+ import type {PressHandler, TouchableComponentProps} from './touchable';
20
+ import type {ExclusifyUnion} from './utils/utility-types';
21
+
22
+ type MessageAction = IconButtonProps & {
23
+ small?: never;
24
+ type?: never;
25
+ bleedLeft?: never;
26
+ bleedRight?: never;
27
+ bleedY?: never;
28
+ };
29
+ type MessageActions = readonly [MessageAction] | readonly [MessageAction, MessageAction];
30
+ const avatarSize = 40;
31
+ const maximumActions = 2;
32
+
33
+ export type MessageProps = TouchableComponentProps<
34
+ {
35
+ type: 'incoming' | 'outgoing';
36
+ senderName?: string;
37
+ senderNameAs?: 'span' | 'p' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
38
+ senderNameLabel?: string;
39
+ timestamp?: string;
40
+ timestampLabel?: string;
41
+ asset?: React.ReactElement;
42
+ boxed?: boolean;
43
+ /** Maximum width of the whole message, including its asset and side actions. Defaults to 80%.
44
+ * Error feedback can exceed this limit up to 184px to avoid illegible wrapping.
45
+ */
46
+ maxWidth?: number | string;
47
+ position?: 'standalone' | 'first' | 'middle' | 'last';
48
+ } & ExclusifyUnion<
49
+ {onAssetPress: PressHandler; assetLabel: string} | {onAssetPress?: undefined; assetLabel?: undefined}
50
+ > & {
51
+ contentText?: string;
52
+ contentSlot?: React.ReactNode;
53
+ footerSlot?: React.ReactNode;
54
+ sideSlot?: React.ReactNode;
55
+ /** Up to two actions. Extra actions from untyped consumers are ignored. */
56
+ actions?: MessageActions;
57
+ actionsPosition?: 'side' | 'footer';
58
+ } & ExclusifyUnion<{error: true; errorMessage: string} | {error?: false}>
59
+ >;
60
+
61
+ const Message = ({
62
+ type,
63
+ senderName,
64
+ senderNameAs = 'span',
65
+ senderNameLabel,
66
+ timestamp,
67
+ timestampLabel,
68
+ asset,
69
+ onAssetPress,
70
+ assetLabel,
71
+ boxed = true,
72
+ maxWidth = '80%',
73
+ position = 'standalone',
74
+ contentText,
75
+ contentSlot,
76
+ footerSlot,
77
+ sideSlot,
78
+ actions,
79
+ actionsPosition = 'footer',
80
+ error = false,
81
+ errorMessage,
82
+ dataAttributes,
83
+ ...touchableProps
84
+ }: MessageProps): JSX.Element => {
85
+ const {t} = useTheme();
86
+ const variant = useThemeVariant();
87
+ const id = React.useId();
88
+ const {width: textWidth, ref: textRef} = useElementDimensions();
89
+ const {width: bubbleContentWidth, height: bubbleContentHeight, ref: bubbleRef} = useElementDimensions();
90
+ const bubbleHeight = bubbleContentHeight + (boxed ? styles.bubblePaddingY * 2 : 0);
91
+ const bubbleWidth = bubbleContentWidth + (boxed ? styles.bubblePaddingX * 2 : 0);
92
+ const shortError = error && bubbleContentWidth > 0 && bubbleWidth < styles.minimumErrorWidth;
93
+ const inlineTimestamp = boxed && !contentSlot && textWidth > 0 && textWidth < styles.minimumBubbleSize;
94
+ const showTimestamp = !!timestamp && !(error && type === 'outgoing');
95
+ const isInteractive = !!(touchableProps.onPress || touchableProps.href || touchableProps.to);
96
+ const directionLabel = t(type === 'incoming' ? tokens.messageIncoming : tokens.messageOutgoing);
97
+ const renderActions = () =>
98
+ actions
99
+ ?.slice(0, maximumActions)
100
+ .map((action, index) => <IconButton key={index} {...action} small type="neutral" />);
101
+
102
+ const accessibleIds = [
103
+ `${id}-direction`,
104
+ (senderName || senderNameLabel) && `${id}-sender`,
105
+ `${id}-content`,
106
+ showTimestamp && `${id}-timestamp`,
107
+ error && `${id}-error`,
108
+ ]
109
+ .filter(Boolean)
110
+ .join(' ');
111
+
112
+ return (
113
+ <div
114
+ className={classnames(styles.message, styles.direction[type])}
115
+ style={{maxWidth}}
116
+ {...getPrefixedDataAttributes({testid: 'Message', ...dataAttributes})}
117
+ >
118
+ {asset && (
119
+ <div
120
+ className={classnames(styles.asset, styles.assetAlignment[type], {
121
+ [styles.unboxedAssetSpacing[type]]: !boxed,
122
+ })}
123
+ style={
124
+ type === 'outgoing' && bubbleHeight
125
+ ? {transform: `translateY(calc(${bubbleHeight}px - 100%))`}
126
+ : undefined
127
+ }
128
+ >
129
+ <Touchable maybe onPress={onAssetPress} aria-label={assetLabel}>
130
+ <span aria-hidden>
131
+ {React.isValidElement<{size: number}>(asset) && asset.type === Avatar
132
+ ? React.cloneElement(asset, {size: avatarSize})
133
+ : asset}
134
+ </span>
135
+ </Touchable>
136
+ </div>
137
+ )}
138
+ <div className={classnames(styles.messageContent, {[styles.shortErrorLayout[type]]: shortError})}>
139
+ <div className={classnames(styles.row, styles.rowDirection[type])}>
140
+ <div className={styles.column}>
141
+ <Touchable
142
+ {...touchableProps}
143
+ maybe
144
+ aria-labelledby={
145
+ touchableProps['aria-label']
146
+ ? undefined
147
+ : touchableProps['aria-labelledby'] ??
148
+ (isInteractive ? accessibleIds : undefined)
149
+ }
150
+ className={classnames(
151
+ styles.touchable,
152
+ styles.corners[`${type}-${error ? 'standalone' : position}`]
153
+ )}
154
+ >
155
+ <ThemeVariant variant={boxed ? 'default' : variant}>
156
+ <div
157
+ ref={bubbleRef}
158
+ data-testid="MessageBubble"
159
+ className={classnames(styles.bubble, {
160
+ [styles.boxed]: boxed,
161
+ [styles.incomingBackground[variant]]:
162
+ boxed && type === 'incoming' && !error,
163
+ [styles.outgoingBackground[variant]]:
164
+ boxed && type === 'outgoing' && !error,
165
+ [styles.errorBackground]: boxed && error,
166
+ [styles.interactive]: boxed && isInteractive,
167
+ })}
168
+ >
169
+ <ScreenReaderOnly>
170
+ <span id={`${id}-direction`}>{directionLabel}: </span>
171
+ </ScreenReaderOnly>
172
+ {(senderName || senderNameLabel) &&
173
+ (senderName ? (
174
+ <Text1
175
+ medium
176
+ as={senderNameAs}
177
+ id={`${id}-sender`}
178
+ color={
179
+ !boxed &&
180
+ variant !== 'default' &&
181
+ variant !== 'alternative'
182
+ ? vars.colors.textPrimary
183
+ : vars.colors.textBrand
184
+ }
185
+ >
186
+ {senderNameLabel ? (
187
+ <>
188
+ <span aria-hidden>{senderName}</span>
189
+ <ScreenReaderOnly>
190
+ <span>{senderNameLabel}</span>
191
+ </ScreenReaderOnly>
192
+ </>
193
+ ) : (
194
+ senderName
195
+ )}
196
+ </Text1>
197
+ ) : (
198
+ <ScreenReaderOnly>
199
+ <span id={`${id}-sender`}>{senderNameLabel}</span>
200
+ </ScreenReaderOnly>
201
+ ))}
202
+ <div
203
+ className={classnames(styles.body, {
204
+ [styles.inlineTimestamp]: inlineTimestamp,
205
+ })}
206
+ >
207
+ <div className={styles.content} id={`${id}-content`}>
208
+ {contentText !== undefined && (
209
+ <Text2 regular as="p">
210
+ <span ref={textRef} className={styles.text}>
211
+ {contentText}
212
+ </span>
213
+ </Text2>
214
+ )}
215
+ {contentSlot !== undefined && (
216
+ <div className={styles.contentSlot}>{contentSlot}</div>
217
+ )}
218
+ </div>
219
+ {showTimestamp && (
220
+ <div className={boxed ? styles.timestamp : undefined}>
221
+ <Text1
222
+ regular
223
+ as="span"
224
+ id={`${id}-timestamp`}
225
+ color={
226
+ boxed
227
+ ? type === 'outgoing'
228
+ ? vars.colors.textPrimary
229
+ : vars.colors.textSecondary
230
+ : undefined
231
+ }
232
+ >
233
+ {timestampLabel ? (
234
+ <>
235
+ <span aria-hidden>{timestamp}</span>
236
+ <ScreenReaderOnly>
237
+ <span>{timestampLabel}</span>
238
+ </ScreenReaderOnly>
239
+ </>
240
+ ) : (
241
+ timestamp
242
+ )}
243
+ </Text1>
244
+ </div>
245
+ )}
246
+ </div>
247
+ </div>
248
+ </ThemeVariant>
249
+ </Touchable>
250
+ {footerSlot && (
251
+ <div className={styles.footer} id={`${id}-footer`}>
252
+ {footerSlot}
253
+ </div>
254
+ )}
255
+ {actions && actionsPosition === 'footer' && (
256
+ <div
257
+ className={classnames(styles.actions, styles.footerActions, {
258
+ [styles.unboxedActions]: !boxed,
259
+ [styles.outgoingFooterActions]: type === 'outgoing',
260
+ })}
261
+ >
262
+ {renderActions()}
263
+ </div>
264
+ )}
265
+ </div>
266
+ {sideSlot && (
267
+ <div className={styles.sideSlot} style={{height: bubbleHeight || undefined}}>
268
+ {sideSlot}
269
+ </div>
270
+ )}
271
+ {actions && actionsPosition === 'side' && (
272
+ <div
273
+ className={classnames(styles.actions, styles.sideActions[type])}
274
+ style={{height: bubbleHeight || undefined}}
275
+ >
276
+ {renderActions()}
277
+ </div>
278
+ )}
279
+ </div>
280
+ {error && (
281
+ <div className={classnames(styles.errorFooter, {[styles.shortErrorFooter]: shortError})}>
282
+ <div
283
+ className={classnames(styles.error, {
284
+ [styles.boxedError]: boxed,
285
+ })}
286
+ role="alert"
287
+ id={`${id}-error`}
288
+ >
289
+ <Text1 regular color={vars.colors.textError}>
290
+ <IconWarningRegular color="currentColor" className={styles.errorIcon} />
291
+ </Text1>
292
+ <Text1 regular color={vars.colors.textError}>
293
+ {errorMessage}
294
+ </Text1>
295
+ </div>
296
+ </div>
297
+ )}
298
+ </div>
299
+ </div>
300
+ );
301
+ };
302
+
303
+ export default Message;
@@ -1,2 +1,2 @@
1
1
  // DO NOT EDIT THIS FILE. It's autogenerated by set-version.js
2
- export const PACKAGE_VERSION = '17.9.0' as string;
2
+ export const PACKAGE_VERSION = '17.11.0' as string;