@telefonica/mistica 16.62.0-beta.1 → 16.62.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 (220) hide show
  1. package/css/mistica.css +1 -1
  2. package/dist/accordion.css-mistica.js +16 -16
  3. package/dist/align.css-mistica.js +2 -2
  4. package/dist/autocomplete.css-mistica.js +6 -6
  5. package/dist/avatar.css-mistica.js +3 -3
  6. package/dist/badge.css-mistica.js +7 -7
  7. package/dist/box.css-mistica.js +15 -15
  8. package/dist/boxed.css-mistica.js +31 -31
  9. package/dist/button-group.css-mistica.js +10 -10
  10. package/dist/button-layout.css-mistica.js +21 -21
  11. package/dist/button.css-mistica.js +51 -51
  12. package/dist/callout.css-mistica.js +16 -16
  13. package/dist/card-internal.css-mistica.js +38 -38
  14. package/dist/carousel.css-mistica.js +18 -18
  15. package/dist/checkbox.css-mistica.js +21 -21
  16. package/dist/chip.css-mistica.js +30 -30
  17. package/dist/circle.css-mistica.js +2 -2
  18. package/dist/community/advanced-data-card.css-mistica.js +26 -26
  19. package/dist/community/ai-card.css-mistica.js +48 -0
  20. package/dist/community/ai-card.css.d.ts +13 -0
  21. package/dist/community/ai-card.d.ts +19 -0
  22. package/dist/community/ai-card.js +312 -0
  23. package/dist/community/blocks.css-mistica.js +3 -3
  24. package/dist/community/example-component.css-mistica.js +2 -2
  25. package/dist/community/index.d.ts +1 -0
  26. package/dist/community/index.js +4 -0
  27. package/dist/counter.css-mistica.js +12 -12
  28. package/dist/cover-hero.css-mistica.js +16 -16
  29. package/dist/credit-card-number-field.css-mistica.js +6 -6
  30. package/dist/date-field.css-mistica.js +1 -1
  31. package/dist/date-time-picker.css-mistica.js +2 -2
  32. package/dist/dialog.css-mistica.js +15 -15
  33. package/dist/divider.css-mistica.js +5 -5
  34. package/dist/double-field.css-mistica.js +4 -4
  35. package/dist/drawer.css-mistica.js +15 -15
  36. package/dist/empty-state-card.css-mistica.js +4 -4
  37. package/dist/empty-state.css-mistica.js +14 -14
  38. package/dist/fade-in.css-mistica.js +1 -1
  39. package/dist/feedback.css-mistica.js +14 -14
  40. package/dist/file-upload.css-mistica.js +14 -14
  41. package/dist/fixed-footer-layout.css-mistica.js +12 -12
  42. package/dist/form.css-mistica.js +2 -2
  43. package/dist/generated/mistica-icons/icon-artificial-intelligence-filled.js +7 -7
  44. package/dist/generated/mistica-icons/icon-artificial-intelligence-light.js +9 -9
  45. package/dist/generated/mistica-icons/icon-artificial-intelligence-regular.js +12 -12
  46. package/dist/grid-layout.css-mistica.js +9 -9
  47. package/dist/grid.css-mistica.js +147 -147
  48. package/dist/header.css-mistica.js +5 -5
  49. package/dist/hero.css-mistica.js +11 -11
  50. package/dist/horizontal-scroll.css-mistica.js +3 -3
  51. package/dist/icon-button.css-mistica.js +66 -66
  52. package/dist/icons/icon-chevron.css-mistica.js +6 -6
  53. package/dist/icons/icon-error.css-mistica.js +3 -3
  54. package/dist/image.css-mistica.js +11 -11
  55. package/dist/index.d.ts +1 -0
  56. package/dist/index.js +4 -0
  57. package/dist/inline.css-mistica.js +16 -16
  58. package/dist/list.css-mistica.js +15 -15
  59. package/dist/loading-bar.css-mistica.js +5 -5
  60. package/dist/loading-screen.css-mistica.js +15 -15
  61. package/dist/logo.css-mistica.js +9 -9
  62. package/dist/menu.css-mistica.js +27 -24
  63. package/dist/menu.css.d.ts +1 -0
  64. package/dist/menu.d.ts +2 -1
  65. package/dist/menu.js +139 -132
  66. package/dist/mosaic.css-mistica.js +3 -3
  67. package/dist/navigation-bar.css-mistica.js +45 -45
  68. package/dist/navigation-breadcrumbs.css-mistica.js +5 -5
  69. package/dist/package-version.js +2 -2
  70. package/dist/pin-field.css-mistica.js +10 -10
  71. package/dist/popover.css-mistica.js +2 -2
  72. package/dist/progress-bar.css-mistica.js +12 -12
  73. package/dist/radio-button.css-mistica.js +33 -33
  74. package/dist/rating.css-mistica.js +12 -12
  75. package/dist/responsive-layout.css-mistica.js +20 -20
  76. package/dist/screen-reader-only.css-mistica.js +2 -2
  77. package/dist/select.css-mistica.js +29 -29
  78. package/dist/sheet-action-row.css-mistica.js +2 -2
  79. package/dist/sheet-common.css-mistica.js +16 -16
  80. package/dist/sheet-info.css-mistica.js +4 -4
  81. package/dist/skeletons.css-mistica.js +12 -12
  82. package/dist/skins/skin-contract.css-mistica.js +686 -686
  83. package/dist/skip-link.css-mistica.js +3 -3
  84. package/dist/slider.css-mistica.js +28 -28
  85. package/dist/snackbar.css-mistica.js +16 -16
  86. package/dist/spinner.css-mistica.js +5 -5
  87. package/dist/square.css-mistica.js +2 -2
  88. package/dist/stack.css-mistica.js +9 -9
  89. package/dist/stacking-group.css-mistica.js +2 -2
  90. package/dist/stepper.css-mistica.js +16 -16
  91. package/dist/switch-component.css-mistica.js +53 -53
  92. package/dist/table.css-mistica.js +24 -24
  93. package/dist/tabs.css-mistica.js +30 -30
  94. package/dist/tag.css-mistica.js +5 -5
  95. package/dist/text-field-base.css-mistica.js +30 -30
  96. package/dist/text-field-components.css-mistica.js +19 -19
  97. package/dist/text-link.css-mistica.js +10 -10
  98. package/dist/text.css-mistica.js +13 -13
  99. package/dist/theme-context.css-mistica.js +2 -2
  100. package/dist/timeline.css-mistica.js +18 -18
  101. package/dist/timer.css-mistica.js +13 -13
  102. package/dist/tooltip.css-mistica.js +12 -12
  103. package/dist/touchable.css-mistica.js +5 -5
  104. package/dist/utils/aspect-ratio-support.css-mistica.js +7 -7
  105. package/dist/video.css-mistica.js +2 -2
  106. package/dist/vivinho-loading-animation/vivinho-loading-animation.css-mistica.js +3 -3
  107. package/dist-es/accordion.css-mistica.js +7 -7
  108. package/dist-es/align.css-mistica.js +2 -2
  109. package/dist-es/autocomplete.css-mistica.js +2 -2
  110. package/dist-es/avatar.css-mistica.js +2 -2
  111. package/dist-es/badge.css-mistica.js +2 -2
  112. package/dist-es/box.css-mistica.js +15 -15
  113. package/dist-es/boxed.css-mistica.js +25 -25
  114. package/dist-es/button-group.css-mistica.js +2 -2
  115. package/dist-es/button-layout.css-mistica.js +16 -16
  116. package/dist-es/button.css-mistica.js +38 -38
  117. package/dist-es/callout.css-mistica.js +12 -12
  118. package/dist-es/card-internal.css-mistica.js +20 -20
  119. package/dist-es/carousel.css-mistica.js +10 -10
  120. package/dist-es/checkbox.css-mistica.js +14 -14
  121. package/dist-es/chip.css-mistica.js +17 -17
  122. package/dist-es/circle.css-mistica.js +2 -2
  123. package/dist-es/community/advanced-data-card.css-mistica.js +7 -7
  124. package/dist-es/community/ai-card.css-mistica.js +4 -0
  125. package/dist-es/community/ai-card.js +257 -0
  126. package/dist-es/community/blocks.css-mistica.js +2 -2
  127. package/dist-es/community/example-component.css-mistica.js +2 -2
  128. package/dist-es/community/index.js +5 -4
  129. package/dist-es/counter.css-mistica.js +2 -2
  130. package/dist-es/cover-hero.css-mistica.js +4 -4
  131. package/dist-es/credit-card-number-field.css-mistica.js +4 -4
  132. package/dist-es/date-field.css-mistica.js +1 -1
  133. package/dist-es/date-time-picker.css-mistica.js +2 -2
  134. package/dist-es/dialog.css-mistica.js +5 -5
  135. package/dist-es/divider.css-mistica.js +5 -5
  136. package/dist-es/double-field.css-mistica.js +4 -4
  137. package/dist-es/drawer.css-mistica.js +2 -2
  138. package/dist-es/empty-state-card.css-mistica.js +2 -2
  139. package/dist-es/empty-state.css-mistica.js +7 -7
  140. package/dist-es/fade-in.css-mistica.js +1 -1
  141. package/dist-es/feedback.css-mistica.js +2 -2
  142. package/dist-es/file-upload.css-mistica.js +8 -8
  143. package/dist-es/fixed-footer-layout.css-mistica.js +4 -4
  144. package/dist-es/form.css-mistica.js +2 -2
  145. package/dist-es/generated/mistica-icons/icon-artificial-intelligence-filled.js +12 -12
  146. package/dist-es/generated/mistica-icons/icon-artificial-intelligence-light.js +15 -15
  147. package/dist-es/generated/mistica-icons/icon-artificial-intelligence-regular.js +17 -17
  148. package/dist-es/grid-layout.css-mistica.js +4 -4
  149. package/dist-es/grid.css-mistica.js +127 -127
  150. package/dist-es/header.css-mistica.js +2 -2
  151. package/dist-es/hero.css-mistica.js +4 -4
  152. package/dist-es/horizontal-scroll.css-mistica.js +2 -2
  153. package/dist-es/icon-button.css-mistica.js +56 -56
  154. package/dist-es/icons/icon-chevron.css-mistica.js +4 -4
  155. package/dist-es/icons/icon-error.css-mistica.js +2 -2
  156. package/dist-es/image.css-mistica.js +4 -4
  157. package/dist-es/index.js +2183 -2182
  158. package/dist-es/inline.css-mistica.js +10 -10
  159. package/dist-es/list.css-mistica.js +2 -2
  160. package/dist-es/loading-bar.css-mistica.js +2 -2
  161. package/dist-es/loading-screen.css-mistica.js +5 -5
  162. package/dist-es/logo.css-mistica.js +7 -7
  163. package/dist-es/menu.css-mistica.js +15 -15
  164. package/dist-es/menu.js +186 -179
  165. package/dist-es/mosaic.css-mistica.js +2 -2
  166. package/dist-es/navigation-bar.css-mistica.js +20 -20
  167. package/dist-es/navigation-breadcrumbs.css-mistica.js +2 -2
  168. package/dist-es/package-version.js +2 -2
  169. package/dist-es/pin-field.css-mistica.js +2 -2
  170. package/dist-es/popover.css-mistica.js +2 -2
  171. package/dist-es/progress-bar.css-mistica.js +8 -8
  172. package/dist-es/radio-button.css-mistica.js +25 -25
  173. package/dist-es/rating.css-mistica.js +4 -4
  174. package/dist-es/responsive-layout.css-mistica.js +7 -7
  175. package/dist-es/screen-reader-only.css-mistica.js +2 -2
  176. package/dist-es/select.css-mistica.js +20 -20
  177. package/dist-es/sheet-action-row.css-mistica.js +2 -2
  178. package/dist-es/sheet-common.css-mistica.js +2 -2
  179. package/dist-es/sheet-info.css-mistica.js +2 -2
  180. package/dist-es/skeletons.css-mistica.js +8 -8
  181. package/dist-es/skins/skin-contract.css-mistica.js +686 -686
  182. package/dist-es/skip-link.css-mistica.js +2 -2
  183. package/dist-es/slider.css-mistica.js +20 -20
  184. package/dist-es/snackbar.css-mistica.js +5 -5
  185. package/dist-es/spinner.css-mistica.js +2 -2
  186. package/dist-es/square.css-mistica.js +2 -2
  187. package/dist-es/stack.css-mistica.js +7 -7
  188. package/dist-es/stacking-group.css-mistica.js +2 -2
  189. package/dist-es/stepper.css-mistica.js +4 -4
  190. package/dist-es/style.css +1 -1
  191. package/dist-es/switch-component.css-mistica.js +41 -41
  192. package/dist-es/table.css-mistica.js +11 -11
  193. package/dist-es/tabs.css-mistica.js +21 -21
  194. package/dist-es/tag.css-mistica.js +2 -2
  195. package/dist-es/text-field-base.css-mistica.js +17 -17
  196. package/dist-es/text-field-components.css-mistica.js +4 -4
  197. package/dist-es/text-link.css-mistica.js +9 -9
  198. package/dist-es/text.css-mistica.js +8 -8
  199. package/dist-es/theme-context.css-mistica.js +2 -2
  200. package/dist-es/timeline.css-mistica.js +11 -11
  201. package/dist-es/timer.css-mistica.js +7 -7
  202. package/dist-es/tooltip.css-mistica.js +3 -3
  203. package/dist-es/touchable.css-mistica.js +2 -2
  204. package/dist-es/utils/aspect-ratio-support.css-mistica.js +4 -4
  205. package/dist-es/video.css-mistica.js +2 -2
  206. package/dist-es/vivinho-loading-animation/vivinho-loading-animation.css-mistica.js +2 -2
  207. package/doc/components.md +0 -19
  208. package/doc/layout.md +1 -20
  209. package/doc/llms.md +0 -7
  210. package/doc/patterns.md +17 -6
  211. package/package.json +1 -1
  212. package/src/community/__stories__/ai-card-story.tsx +101 -0
  213. package/src/community/ai-card.css.ts +135 -0
  214. package/src/community/ai-card.tsx +231 -0
  215. package/src/community/index.tsx +1 -0
  216. package/src/index.tsx +1 -0
  217. package/src/menu.css.ts +6 -0
  218. package/src/menu.tsx +19 -8
  219. package/src/package-version.tsx +1 -1
  220. package/doc/figma-mcp.md +0 -136
@@ -0,0 +1,101 @@
1
+ import * as React from 'react';
2
+ import AiCard from '../ai-card';
3
+ import Box from '../../box';
4
+ import ResponsiveLayout from '../../responsive-layout';
5
+ import IconArtificialIntelligenceFilled from '../../generated/mistica-icons/icon-artificial-intelligence-filled';
6
+ import {vars} from '../../skins/skin-contract.css';
7
+
8
+ export default {
9
+ title: 'Community/AiCard',
10
+ parameters: {fullScreen: true},
11
+ };
12
+
13
+ const assetOptions: Record<string, React.ReactElement> = {
14
+ 'AI Icon (gradient)': (
15
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
16
+ <path
17
+ d="M6.53957 16.0123C6.71957 15.6223 7.26973 15.6223 7.44973 16.0123L8.23977 17.7623L9.99953 18.5523C10.3894 18.7324 10.3895 19.2825 9.99953 19.4625L8.24953 20.2525L7.45949 22.0123C7.27949 22.4022 6.71964 22.4021 6.53957 22.0123L5.74953 20.2623L3.99953 19.4722C3.60953 19.2922 3.60953 18.7421 3.99953 18.5621L5.74953 17.772L6.53957 16.0123ZM15.0073 5.99861C15.3574 5.21869 16.4767 5.21864 16.8267 5.99861L18.4165 9.49861L21.9165 11.0885C22.6965 11.4485 22.6965 12.5588 21.9165 12.9088L18.4165 14.4986L16.8267 17.9986C16.4666 18.7783 15.3575 18.7783 15.0073 17.9986L13.4165 14.4986L9.91652 12.9088C9.13679 12.5487 9.13685 11.4386 9.91652 11.0885L13.4165 9.49861L15.0073 5.99861ZM4.33254 1.97322C4.5126 1.58347 5.06166 1.58347 5.24172 1.97322L6.03176 3.72322L7.7925 4.51326C8.18211 4.69337 8.18215 5.24334 7.7925 5.42342L6.0425 6.21345L5.25246 7.97322C5.07248 8.36318 4.5126 8.3631 4.33254 7.97322L3.5425 6.22322L1.7925 5.43318C1.4025 5.25318 1.4025 4.70302 1.7925 4.52302L3.5425 3.73299L4.33254 1.97322Z"
18
+ fill="url(#ai-icon-gradient)"
19
+ />
20
+ <defs>
21
+ <linearGradient
22
+ id="ai-icon-gradient"
23
+ x1="18.84"
24
+ y1="5.30652"
25
+ x2="5.4224"
26
+ y2="18.9388"
27
+ gradientUnits="userSpaceOnUse"
28
+ >
29
+ <stop stopColor="#AE42E4" />
30
+ <stop offset="0.32" stopColor="#BD4AFF" />
31
+ <stop offset="1" stopColor="#EF7E9C" />
32
+ </linearGradient>
33
+ </defs>
34
+ </svg>
35
+ ),
36
+ 'AI Icon (brand)': <IconArtificialIntelligenceFilled color={vars.colors.textBrand} />,
37
+ };
38
+
39
+ type Args = {
40
+ text: string;
41
+ words: string | Array<string>;
42
+ deleteChars: number;
43
+ lineBreakAtChars: number;
44
+ borderColor: string;
45
+ asset: string;
46
+ };
47
+
48
+ const parseWords = (raw: string | Array<string>): Array<string> =>
49
+ Array.isArray(raw)
50
+ ? raw.map((w) => String(w).trim()).filter(Boolean)
51
+ : String(raw)
52
+ .split(',')
53
+ .map((word) => word.trim())
54
+ .filter(Boolean);
55
+
56
+ export const Default: StoryComponent<Args> = ({
57
+ text,
58
+ words,
59
+ deleteChars,
60
+ lineBreakAtChars,
61
+ borderColor,
62
+ asset,
63
+ }) => (
64
+ <ResponsiveLayout>
65
+ <Box paddingY={24}>
66
+ <AiCard
67
+ text={text}
68
+ words={parseWords(words)}
69
+ deleteChars={deleteChars > 0 ? deleteChars : undefined}
70
+ lineBreakAtChars={lineBreakAtChars > 0 ? lineBreakAtChars : undefined}
71
+ borderColor={borderColor || undefined}
72
+ asset={assetOptions[asset]}
73
+ onPress={() => {}}
74
+ dataAttributes={{testid: 'ai-card'}}
75
+ />
76
+ </Box>
77
+ </ResponsiveLayout>
78
+ );
79
+
80
+ Default.storyName = 'AiCard';
81
+ Default.args = {
82
+ text: 'Lorem ipsum dolor sit amet, ',
83
+ words: ['consectetur', 'praesent', 'tempor', 'aliquam'],
84
+ deleteChars: 0,
85
+ lineBreakAtChars: 0,
86
+ borderColor: 'linear-gradient(200deg, #AE42E459 17.51%, #BD4AFF59 38.3%, #EB3C7D59 82.5%)',
87
+ asset: 'AI Icon (gradient)',
88
+ };
89
+ Default.argTypes = {
90
+ deleteChars: {control: {type: 'number', min: 0, step: 1}},
91
+ lineBreakAtChars: {control: {type: 'number', min: 0, step: 1}},
92
+ borderColor: {control: {type: 'text'}},
93
+ words: {
94
+ control: {type: 'array'},
95
+ description: 'List of words for the AiCard animation.',
96
+ },
97
+ asset: {
98
+ options: Object.keys(assetOptions),
99
+ control: {type: 'select'},
100
+ },
101
+ };
@@ -0,0 +1,135 @@
1
+ import {createVar, globalStyle, style, keyframes} from '@vanilla-extract/css';
2
+ import {sprinkles} from '../sprinkles.css';
3
+ import {vars as skinVars} from '../skins/skin-contract.css';
4
+ import * as mq from '../media-queries.css';
5
+
6
+ const borderColorVar = createVar();
7
+
8
+ export const vars = {borderColorVar};
9
+
10
+ const fill = (color: string) => `linear-gradient(${color}, ${color}) padding-box`;
11
+ const borderLayer = `${borderColorVar} border-box`;
12
+
13
+ const containerBackground = (overlayColor: string) =>
14
+ `${fill(overlayColor)}, ${fill(skinVars.colors.backgroundContainer)}, ${borderLayer}`;
15
+
16
+ export const container = style([
17
+ sprinkles({
18
+ display: 'flex',
19
+ alignItems: 'center',
20
+ padding: 16,
21
+ width: '100%',
22
+ }),
23
+ {
24
+ position: 'relative',
25
+ maxWidth: '100%',
26
+ minWidth: 288,
27
+ minHeight: 64,
28
+ gap: 8,
29
+ borderRadius: skinVars.borderRadii.container,
30
+ border: '1px solid transparent',
31
+ boxSizing: 'border-box',
32
+ textAlign: 'left',
33
+ vars: {
34
+ [borderColorVar]: `linear-gradient(${skinVars.colors.border}, ${skinVars.colors.border})`,
35
+ },
36
+ background: containerBackground('transparent'),
37
+ },
38
+ ]);
39
+
40
+ export const containerInteractive = style({
41
+ transition: 'background 120ms ease',
42
+ ':focus-visible': {
43
+ outline: `2px solid ${skinVars.colors.borderSelected}`,
44
+ outlineOffset: 2,
45
+ },
46
+ ':active': {
47
+ background: containerBackground(skinVars.colors.backgroundContainerPressed),
48
+ },
49
+ '@media': {
50
+ [mq.supportsHover]: {
51
+ ':hover': {
52
+ background: containerBackground(skinVars.colors.backgroundContainerHover),
53
+ },
54
+ ':active': {
55
+ background: containerBackground(skinVars.colors.backgroundContainerPressed),
56
+ },
57
+ },
58
+ [mq.touchableOnly]: {
59
+ transition: 'none',
60
+ },
61
+ },
62
+ });
63
+
64
+ export const textLine = style([
65
+ sprinkles({
66
+ display: 'flex',
67
+ alignItems: 'center',
68
+ }),
69
+ {
70
+ flex: '1 1 auto',
71
+ minWidth: 0,
72
+ maxWidth: '100%',
73
+ },
74
+ ]);
75
+
76
+ export const slot = style([
77
+ sprinkles({
78
+ display: 'flex',
79
+ alignItems: 'center',
80
+ justifyContent: 'center',
81
+ }),
82
+ {
83
+ flex: '0 0 auto',
84
+ lineHeight: 0,
85
+ },
86
+ ]);
87
+
88
+ globalStyle(`${slot} > svg`, {
89
+ display: 'block',
90
+ });
91
+
92
+ export const textWrapper = style({
93
+ display: 'grid',
94
+ width: '100%',
95
+ alignItems: 'center',
96
+ alignContent: 'center',
97
+ });
98
+
99
+ const textContent = style({
100
+ gridArea: '1 / 1',
101
+ whiteSpace: 'normal',
102
+ overflowWrap: 'anywhere',
103
+ wordBreak: 'break-word',
104
+ });
105
+
106
+ export const ghost = style([
107
+ textContent,
108
+ {
109
+ visibility: 'hidden',
110
+ pointerEvents: 'none',
111
+ userSelect: 'none',
112
+ },
113
+ ]);
114
+
115
+ export const visibleContent = textContent;
116
+
117
+ const caretBlink = keyframes({
118
+ '0%': {opacity: 1},
119
+ '50%': {opacity: 0},
120
+ });
121
+
122
+ export const caret = style({
123
+ color: skinVars.colors.textBrand,
124
+ });
125
+
126
+ export const caretHidden = style({
127
+ visibility: 'hidden',
128
+ });
129
+
130
+ export const caretBlinking = style({
131
+ animationName: caretBlink,
132
+ animationDuration: '1060ms',
133
+ animationTimingFunction: 'step-end',
134
+ animationIterationCount: 'infinite',
135
+ });
@@ -0,0 +1,231 @@
1
+ 'use client';
2
+ import * as React from 'react';
3
+ import classnames from 'classnames';
4
+ import {BaseTouchable} from '../touchable';
5
+ import {Text3} from '../text';
6
+ import {vars} from '../skins/skin-contract.css';
7
+ import {useIsInViewport} from '../hooks';
8
+ import {isClientSide} from '../utils/environment';
9
+ import {applyCssVars} from '../utils/css';
10
+ import * as styles from './ai-card.css';
11
+
12
+ import type {TouchableComponentProps} from '../touchable';
13
+
14
+ type CommonProps = {
15
+ /** Static text shown before the animated words. */
16
+ text: string;
17
+ /** Words to animate in sequence. typed, held, then erased one by one. Maximum of 4 words. */
18
+ words?: ReadonlyArray<string>;
19
+ /** Number of characters to keep at the start of each word during deletion. Omit to erase fully. */
20
+ deleteChars?: number;
21
+ /** Wraps the text line after this many characters. */
22
+ lineBreakAtChars?: number;
23
+ /** Border color. Accepts any CSS color or gradient string. Defaults to the skin border token. */
24
+ borderColor?: string;
25
+ /** Icon or element rendered on the left side of the card. */
26
+ asset?: React.ReactElement;
27
+ };
28
+
29
+ export type AiCardProps = TouchableComponentProps<CommonProps>;
30
+
31
+ type AnimationStage = 'typing' | 'holding' | 'deleting' | 'done';
32
+
33
+ const TYPING_SPEEDS = [50, 60, 70, 80];
34
+ const DELETE_SPEED = 30;
35
+ const HOLD_DURATION = 2000;
36
+ const MAX_WORDS = 4;
37
+
38
+ const getRandomTypingSpeed = (previousSpeed?: number) => {
39
+ const candidates = TYPING_SPEEDS.filter((speed) => speed !== previousSpeed);
40
+ return candidates[Math.floor(Math.random() * candidates.length)];
41
+ };
42
+
43
+ const getDeleteFloor = (currentWord: string, nextWord: string, deleteChars?: number) => {
44
+ const sharedStartFloor = currentWord[0]?.toLowerCase() === nextWord[0]?.toLowerCase() ? 1 : 0;
45
+ const configuredDeleteFloor = typeof deleteChars === 'number' ? Math.max(0, Math.floor(deleteChars)) : 0;
46
+ return Math.max(sharedStartFloor, configuredDeleteFloor);
47
+ };
48
+
49
+ const getPrefersReducedMotion = () =>
50
+ isClientSide() && !!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
51
+
52
+ type AnimationState = {index: number; text: string; stage: AnimationStage};
53
+
54
+ const INITIAL_STATE: AnimationState = {index: 0, text: '', stage: 'typing'};
55
+
56
+ const tick = (state: AnimationState, words: ReadonlyArray<string>, deleteChars?: number): AnimationState => {
57
+ const {index, text, stage} = state;
58
+ const currentWord = words[index] || '';
59
+ const nextWord = words[index + 1] || '';
60
+ const deleteFloor = getDeleteFloor(currentWord, nextWord, deleteChars);
61
+
62
+ if (stage === 'typing') {
63
+ if (text.length < currentWord.length) return {...state, text: currentWord.slice(0, text.length + 1)};
64
+ return {...state, stage: 'holding'};
65
+ }
66
+ if (stage === 'holding') {
67
+ if (index === words.length - 1) return {...state, stage: 'done'};
68
+ return text.length > deleteFloor
69
+ ? {...state, stage: 'deleting'}
70
+ : {index: index + 1, text, stage: 'typing'};
71
+ }
72
+ if (stage === 'deleting') {
73
+ if (text.length <= deleteFloor) {
74
+ let commonLen = 0;
75
+ while (
76
+ commonLen < text.length &&
77
+ commonLen < nextWord.length &&
78
+ text[commonLen] === nextWord[commonLen]
79
+ ) {
80
+ commonLen++;
81
+ }
82
+ return {index: index + 1, text: text.slice(0, commonLen), stage: 'typing'};
83
+ }
84
+ return {...state, text: text.slice(0, -1)};
85
+ }
86
+ return state;
87
+ };
88
+
89
+ const useAiCardAnimation = ({
90
+ words,
91
+ deleteChars,
92
+ prefersReducedMotion,
93
+ isInViewport,
94
+ }: {
95
+ words: ReadonlyArray<string>;
96
+ deleteChars?: number;
97
+ prefersReducedMotion: boolean;
98
+ isInViewport: boolean;
99
+ }) => {
100
+ const wordsKey = words.join('\0');
101
+ const wordCount = words.length;
102
+ const [state, setState] = React.useState<AnimationState>(INITIAL_STATE);
103
+ const prevSpeedRef = React.useRef<number | undefined>(undefined);
104
+ const wordsRef = React.useRef(words);
105
+ wordsRef.current = words;
106
+
107
+ React.useEffect(() => {
108
+ setState(
109
+ prefersReducedMotion && wordCount
110
+ ? {index: wordCount - 1, text: wordsRef.current.at(-1) || '', stage: 'done'}
111
+ : INITIAL_STATE
112
+ );
113
+ }, [wordsKey, prefersReducedMotion, wordCount]);
114
+
115
+ React.useEffect(() => {
116
+ if (state.stage === 'done' || !isInViewport || prefersReducedMotion || !wordCount) return;
117
+ let speed: number;
118
+ if (state.stage === 'typing') {
119
+ speed = getRandomTypingSpeed(prevSpeedRef.current);
120
+ prevSpeedRef.current = speed;
121
+ } else if (state.stage === 'holding') {
122
+ speed = HOLD_DURATION;
123
+ } else {
124
+ speed = DELETE_SPEED;
125
+ }
126
+ const id = window.setTimeout(() => setState((s) => tick(s, wordsRef.current, deleteChars)), speed);
127
+ return () => window.clearTimeout(id);
128
+ }, [state, isInViewport, prefersReducedMotion, wordCount, deleteChars]);
129
+
130
+ return {
131
+ dynamicText: state.text,
132
+ isDone: state.stage === 'done',
133
+ shouldBlinkCaret: !prefersReducedMotion && !!wordCount && state.stage === 'holding' && isInViewport,
134
+ };
135
+ };
136
+
137
+ const AiCard = ({
138
+ text,
139
+ words = [],
140
+ deleteChars,
141
+ lineBreakAtChars,
142
+ borderColor,
143
+ asset,
144
+ dataAttributes,
145
+ 'aria-label': ariaLabel,
146
+ ...touchableProps
147
+ }: AiCardProps): JSX.Element => {
148
+ const textLineRef = React.useRef<HTMLDivElement>(null);
149
+ const prefersReducedMotion = getPrefersReducedMotion();
150
+ const isInViewport = useIsInViewport(textLineRef, false);
151
+ const isInteractive = 'onPress' in touchableProps || 'href' in touchableProps || 'to' in touchableProps;
152
+
153
+ const safeWords = React.useMemo(
154
+ () =>
155
+ words
156
+ .map((word) => word.trim())
157
+ .filter((word) => word.length > 0)
158
+ .slice(0, MAX_WORDS),
159
+ [words]
160
+ );
161
+
162
+ const longestWord = React.useMemo(
163
+ () => safeWords.reduce((acc, word) => (word.length > acc.length ? word : acc), safeWords[0] || ''),
164
+ [safeWords]
165
+ );
166
+
167
+ const {dynamicText, isDone, shouldBlinkCaret} = useAiCardAnimation({
168
+ words: safeWords,
169
+ deleteChars,
170
+ prefersReducedMotion,
171
+ isInViewport,
172
+ });
173
+
174
+ const lastWord = safeWords.at(-1) ?? '';
175
+ const autoAriaLabel = [text, lastWord].filter(Boolean).join('') || undefined;
176
+
177
+ const textLineStyle: React.CSSProperties =
178
+ typeof lineBreakAtChars === 'number'
179
+ ? {maxWidth: `min(100%, ${Math.max(1, Math.floor(lineBreakAtChars))}ch)`}
180
+ : {};
181
+
182
+ return (
183
+ <BaseTouchable
184
+ className={classnames(styles.container, {[styles.containerInteractive]: isInteractive})}
185
+ style={
186
+ borderColor
187
+ ? applyCssVars({
188
+ [styles.vars.borderColorVar]: /gradient/.test(borderColor)
189
+ ? borderColor
190
+ : `linear-gradient(${borderColor}, ${borderColor})`,
191
+ })
192
+ : undefined
193
+ }
194
+ dataAttributes={{'component-name': 'AiCard', testid: 'AiCard', ...dataAttributes}}
195
+ {...(touchableProps as any)}
196
+ aria-label={ariaLabel ?? autoAriaLabel}
197
+ >
198
+ {asset && (
199
+ <span className={styles.slot} aria-hidden="true">
200
+ {asset}
201
+ </span>
202
+ )}
203
+ <div ref={textLineRef} className={styles.textLine} aria-hidden="true" style={textLineStyle}>
204
+ <Text3 regular color={vars.colors.textPrimary} as="span">
205
+ <span className={styles.textWrapper}>
206
+ <span className={styles.ghost}>
207
+ {text}
208
+ {longestWord}
209
+ </span>
210
+ <span className={styles.visibleContent}>
211
+ {text}
212
+ {dynamicText}
213
+ {!prefersReducedMotion && !!safeWords.length && (
214
+ <span
215
+ className={classnames(styles.caret, {
216
+ [styles.caretBlinking]: shouldBlinkCaret,
217
+ [styles.caretHidden]: isDone,
218
+ })}
219
+ >
220
+ |
221
+ </span>
222
+ )}
223
+ </span>
224
+ </span>
225
+ </Text3>
226
+ </div>
227
+ </BaseTouchable>
228
+ );
229
+ };
230
+
231
+ export default AiCard;
@@ -1,3 +1,4 @@
1
+ export {default as AiCard} from './ai-card';
1
2
  export {default as ExampleComponent} from './example-component';
2
3
  export {default as AdvancedDataCard} from './advanced-data-card';
3
4
  export {
package/src/index.tsx CHANGED
@@ -274,6 +274,7 @@ export type {TextToken, Dictionary} from './text-tokens';
274
274
  * the community.js export has issues because it exports an ES module and next12 interterprets it as a CommonJS module
275
275
  * importing from /dist/ is not an option because those modules don't get the context from the theme provider
276
276
  */
277
+ export {default as CommunityAiCard} from './community/ai-card';
277
278
  export {default as CommunityExampleComponent} from './community/example-component';
278
279
  export {default as CommunityAdvancedDataCard} from './community/advanced-data-card';
279
280
  export {
package/src/menu.css.ts CHANGED
@@ -126,6 +126,12 @@ export const itemContent = style({
126
126
  alignItems: 'center',
127
127
  });
128
128
 
129
+ export const itemTextContent = style({
130
+ display: 'flex',
131
+ flexDirection: 'column',
132
+ gap: 2,
133
+ });
134
+
129
135
  export const iconContainer = style({
130
136
  display: 'flex',
131
137
  paddingRight: 8,
package/src/menu.tsx CHANGED
@@ -10,7 +10,7 @@ import {Portal} from './portal';
10
10
  import Box from './box';
11
11
  import Inline from './inline';
12
12
  import Touchable from './touchable';
13
- import {Text3} from './text';
13
+ import {Text2, Text3} from './text';
14
14
  import {vars} from './skins/skin-contract.css';
15
15
  import Divider from './divider';
16
16
  import Checkbox from './checkbox';
@@ -53,6 +53,7 @@ const getItemIndexInMenu = (menu: HTMLElement | null, item: HTMLElement | null):
53
53
 
54
54
  interface MenuItemBaseProps {
55
55
  label: string;
56
+ description?: string;
56
57
  Icon?: (props: IconProps) => JSX.Element;
57
58
  destructive?: boolean;
58
59
  disabled?: boolean;
@@ -80,6 +81,7 @@ export const MenuItem = ({
80
81
  onPress,
81
82
  controlType,
82
83
  checked,
84
+ description,
83
85
  dataAttributes,
84
86
  }: MenuItemProps): JSX.Element => {
85
87
  const {focusedItem, setFocusedItem, closeMenu, isMenuOpen} = useMenuContext();
@@ -93,6 +95,19 @@ export const MenuItem = ({
93
95
 
94
96
  const menuItemDataAttributes = {testid: 'MenuItem', ...dataAttributes};
95
97
 
98
+ const renderTextContent = (id?: string) => (
99
+ <div id={id} className={styles.itemTextContent}>
100
+ <Text3 regular color={contentColor}>
101
+ {label}
102
+ </Text3>
103
+ {description && (
104
+ <Text2 regular color={vars.colors.textSecondary}>
105
+ {description}
106
+ </Text2>
107
+ )}
108
+ </div>
109
+ );
110
+
96
111
  const renderContent = () =>
97
112
  controlType === 'checkbox' ? (
98
113
  <Checkbox
@@ -108,7 +123,7 @@ export const MenuItem = ({
108
123
  disabled={disabled}
109
124
  role="menuitemcheckbox"
110
125
  dataAttributes={menuItemDataAttributes}
111
- render={({controlElement}) => (
126
+ render={({controlElement, labelId}) => (
112
127
  <Box paddingX={8} paddingY={12}>
113
128
  <Inline space="between" alignItems="center">
114
129
  <div className={styles.itemContent}>
@@ -117,9 +132,7 @@ export const MenuItem = ({
117
132
  <Icon size={24} color={contentColor} />
118
133
  </div>
119
134
  )}
120
- <Text3 regular color={contentColor}>
121
- {label}
122
- </Text3>
135
+ {renderTextContent(labelId)}
123
136
  </div>
124
137
  <Box paddingLeft={16}>{controlElement}</Box>
125
138
  </Inline>
@@ -146,9 +159,7 @@ export const MenuItem = ({
146
159
  <Icon size={24} color={contentColor} />
147
160
  </div>
148
161
  )}
149
- <Text3 regular color={contentColor}>
150
- {label}
151
- </Text3>
162
+ {renderTextContent()}
152
163
  </div>
153
164
  </Box>
154
165
  </Touchable>
@@ -1,2 +1,2 @@
1
1
  // DO NOT EDIT THIS FILE. It's autogenerated by set-version.js
2
- export const PACKAGE_VERSION = '16.62.0-beta.1' as string;
2
+ export const PACKAGE_VERSION = '16.62.0' as string;