@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.
- package/css/mistica.css +1 -1
- package/dist/accordion.css-mistica.js +16 -16
- package/dist/align.css-mistica.js +2 -2
- package/dist/autocomplete.css-mistica.js +6 -6
- package/dist/avatar.css-mistica.js +3 -3
- package/dist/badge.css-mistica.js +7 -7
- package/dist/box.css-mistica.js +15 -15
- package/dist/boxed.css-mistica.js +31 -31
- package/dist/button-group.css-mistica.js +10 -10
- package/dist/button-layout.css-mistica.js +21 -21
- package/dist/button.css-mistica.js +51 -51
- package/dist/callout.css-mistica.js +16 -16
- package/dist/card-internal.css-mistica.js +38 -38
- package/dist/carousel.css-mistica.js +18 -18
- package/dist/checkbox.css-mistica.js +21 -21
- package/dist/chip.css-mistica.js +30 -30
- package/dist/circle.css-mistica.js +2 -2
- package/dist/community/advanced-data-card.css-mistica.js +26 -26
- package/dist/community/ai-card.css-mistica.js +48 -0
- package/dist/community/ai-card.css.d.ts +13 -0
- package/dist/community/ai-card.d.ts +19 -0
- package/dist/community/ai-card.js +312 -0
- package/dist/community/blocks.css-mistica.js +3 -3
- package/dist/community/example-component.css-mistica.js +2 -2
- package/dist/community/index.d.ts +1 -0
- package/dist/community/index.js +4 -0
- package/dist/counter.css-mistica.js +12 -12
- package/dist/cover-hero.css-mistica.js +16 -16
- package/dist/credit-card-number-field.css-mistica.js +6 -6
- package/dist/date-field.css-mistica.js +1 -1
- package/dist/date-time-picker.css-mistica.js +2 -2
- package/dist/dialog.css-mistica.js +15 -15
- package/dist/divider.css-mistica.js +5 -5
- package/dist/double-field.css-mistica.js +4 -4
- package/dist/drawer.css-mistica.js +15 -15
- package/dist/empty-state-card.css-mistica.js +4 -4
- package/dist/empty-state.css-mistica.js +14 -14
- package/dist/fade-in.css-mistica.js +1 -1
- package/dist/feedback.css-mistica.js +14 -14
- package/dist/file-upload.css-mistica.js +14 -14
- package/dist/fixed-footer-layout.css-mistica.js +12 -12
- package/dist/form.css-mistica.js +2 -2
- package/dist/generated/mistica-icons/icon-artificial-intelligence-filled.js +7 -7
- package/dist/generated/mistica-icons/icon-artificial-intelligence-light.js +9 -9
- package/dist/generated/mistica-icons/icon-artificial-intelligence-regular.js +12 -12
- package/dist/grid-layout.css-mistica.js +9 -9
- package/dist/grid.css-mistica.js +147 -147
- package/dist/header.css-mistica.js +5 -5
- package/dist/hero.css-mistica.js +11 -11
- package/dist/horizontal-scroll.css-mistica.js +3 -3
- package/dist/icon-button.css-mistica.js +66 -66
- package/dist/icons/icon-chevron.css-mistica.js +6 -6
- package/dist/icons/icon-error.css-mistica.js +3 -3
- package/dist/image.css-mistica.js +11 -11
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/inline.css-mistica.js +16 -16
- package/dist/list.css-mistica.js +15 -15
- package/dist/loading-bar.css-mistica.js +5 -5
- package/dist/loading-screen.css-mistica.js +15 -15
- package/dist/logo.css-mistica.js +9 -9
- package/dist/menu.css-mistica.js +27 -24
- package/dist/menu.css.d.ts +1 -0
- package/dist/menu.d.ts +2 -1
- package/dist/menu.js +139 -132
- package/dist/mosaic.css-mistica.js +3 -3
- package/dist/navigation-bar.css-mistica.js +45 -45
- package/dist/navigation-breadcrumbs.css-mistica.js +5 -5
- package/dist/package-version.js +2 -2
- package/dist/pin-field.css-mistica.js +10 -10
- package/dist/popover.css-mistica.js +2 -2
- package/dist/progress-bar.css-mistica.js +12 -12
- package/dist/radio-button.css-mistica.js +33 -33
- package/dist/rating.css-mistica.js +12 -12
- package/dist/responsive-layout.css-mistica.js +20 -20
- package/dist/screen-reader-only.css-mistica.js +2 -2
- package/dist/select.css-mistica.js +29 -29
- package/dist/sheet-action-row.css-mistica.js +2 -2
- package/dist/sheet-common.css-mistica.js +16 -16
- package/dist/sheet-info.css-mistica.js +4 -4
- package/dist/skeletons.css-mistica.js +12 -12
- package/dist/skins/skin-contract.css-mistica.js +686 -686
- package/dist/skip-link.css-mistica.js +3 -3
- package/dist/slider.css-mistica.js +28 -28
- package/dist/snackbar.css-mistica.js +16 -16
- package/dist/spinner.css-mistica.js +5 -5
- package/dist/square.css-mistica.js +2 -2
- package/dist/stack.css-mistica.js +9 -9
- package/dist/stacking-group.css-mistica.js +2 -2
- package/dist/stepper.css-mistica.js +16 -16
- package/dist/switch-component.css-mistica.js +53 -53
- package/dist/table.css-mistica.js +24 -24
- package/dist/tabs.css-mistica.js +30 -30
- package/dist/tag.css-mistica.js +5 -5
- package/dist/text-field-base.css-mistica.js +30 -30
- package/dist/text-field-components.css-mistica.js +19 -19
- package/dist/text-link.css-mistica.js +10 -10
- package/dist/text.css-mistica.js +13 -13
- package/dist/theme-context.css-mistica.js +2 -2
- package/dist/timeline.css-mistica.js +18 -18
- package/dist/timer.css-mistica.js +13 -13
- package/dist/tooltip.css-mistica.js +12 -12
- package/dist/touchable.css-mistica.js +5 -5
- package/dist/utils/aspect-ratio-support.css-mistica.js +7 -7
- package/dist/video.css-mistica.js +2 -2
- package/dist/vivinho-loading-animation/vivinho-loading-animation.css-mistica.js +3 -3
- package/dist-es/accordion.css-mistica.js +7 -7
- package/dist-es/align.css-mistica.js +2 -2
- package/dist-es/autocomplete.css-mistica.js +2 -2
- package/dist-es/avatar.css-mistica.js +2 -2
- package/dist-es/badge.css-mistica.js +2 -2
- package/dist-es/box.css-mistica.js +15 -15
- package/dist-es/boxed.css-mistica.js +25 -25
- package/dist-es/button-group.css-mistica.js +2 -2
- package/dist-es/button-layout.css-mistica.js +16 -16
- package/dist-es/button.css-mistica.js +38 -38
- package/dist-es/callout.css-mistica.js +12 -12
- package/dist-es/card-internal.css-mistica.js +20 -20
- package/dist-es/carousel.css-mistica.js +10 -10
- package/dist-es/checkbox.css-mistica.js +14 -14
- package/dist-es/chip.css-mistica.js +17 -17
- package/dist-es/circle.css-mistica.js +2 -2
- package/dist-es/community/advanced-data-card.css-mistica.js +7 -7
- package/dist-es/community/ai-card.css-mistica.js +4 -0
- package/dist-es/community/ai-card.js +257 -0
- package/dist-es/community/blocks.css-mistica.js +2 -2
- package/dist-es/community/example-component.css-mistica.js +2 -2
- package/dist-es/community/index.js +5 -4
- package/dist-es/counter.css-mistica.js +2 -2
- package/dist-es/cover-hero.css-mistica.js +4 -4
- package/dist-es/credit-card-number-field.css-mistica.js +4 -4
- package/dist-es/date-field.css-mistica.js +1 -1
- package/dist-es/date-time-picker.css-mistica.js +2 -2
- package/dist-es/dialog.css-mistica.js +5 -5
- package/dist-es/divider.css-mistica.js +5 -5
- package/dist-es/double-field.css-mistica.js +4 -4
- package/dist-es/drawer.css-mistica.js +2 -2
- package/dist-es/empty-state-card.css-mistica.js +2 -2
- package/dist-es/empty-state.css-mistica.js +7 -7
- package/dist-es/fade-in.css-mistica.js +1 -1
- package/dist-es/feedback.css-mistica.js +2 -2
- package/dist-es/file-upload.css-mistica.js +8 -8
- package/dist-es/fixed-footer-layout.css-mistica.js +4 -4
- package/dist-es/form.css-mistica.js +2 -2
- package/dist-es/generated/mistica-icons/icon-artificial-intelligence-filled.js +12 -12
- package/dist-es/generated/mistica-icons/icon-artificial-intelligence-light.js +15 -15
- package/dist-es/generated/mistica-icons/icon-artificial-intelligence-regular.js +17 -17
- package/dist-es/grid-layout.css-mistica.js +4 -4
- package/dist-es/grid.css-mistica.js +127 -127
- package/dist-es/header.css-mistica.js +2 -2
- package/dist-es/hero.css-mistica.js +4 -4
- package/dist-es/horizontal-scroll.css-mistica.js +2 -2
- package/dist-es/icon-button.css-mistica.js +56 -56
- package/dist-es/icons/icon-chevron.css-mistica.js +4 -4
- package/dist-es/icons/icon-error.css-mistica.js +2 -2
- package/dist-es/image.css-mistica.js +4 -4
- package/dist-es/index.js +2183 -2182
- package/dist-es/inline.css-mistica.js +10 -10
- package/dist-es/list.css-mistica.js +2 -2
- package/dist-es/loading-bar.css-mistica.js +2 -2
- package/dist-es/loading-screen.css-mistica.js +5 -5
- package/dist-es/logo.css-mistica.js +7 -7
- package/dist-es/menu.css-mistica.js +15 -15
- package/dist-es/menu.js +186 -179
- package/dist-es/mosaic.css-mistica.js +2 -2
- package/dist-es/navigation-bar.css-mistica.js +20 -20
- package/dist-es/navigation-breadcrumbs.css-mistica.js +2 -2
- package/dist-es/package-version.js +2 -2
- package/dist-es/pin-field.css-mistica.js +2 -2
- package/dist-es/popover.css-mistica.js +2 -2
- package/dist-es/progress-bar.css-mistica.js +8 -8
- package/dist-es/radio-button.css-mistica.js +25 -25
- package/dist-es/rating.css-mistica.js +4 -4
- package/dist-es/responsive-layout.css-mistica.js +7 -7
- package/dist-es/screen-reader-only.css-mistica.js +2 -2
- package/dist-es/select.css-mistica.js +20 -20
- package/dist-es/sheet-action-row.css-mistica.js +2 -2
- package/dist-es/sheet-common.css-mistica.js +2 -2
- package/dist-es/sheet-info.css-mistica.js +2 -2
- package/dist-es/skeletons.css-mistica.js +8 -8
- package/dist-es/skins/skin-contract.css-mistica.js +686 -686
- package/dist-es/skip-link.css-mistica.js +2 -2
- package/dist-es/slider.css-mistica.js +20 -20
- package/dist-es/snackbar.css-mistica.js +5 -5
- package/dist-es/spinner.css-mistica.js +2 -2
- package/dist-es/square.css-mistica.js +2 -2
- package/dist-es/stack.css-mistica.js +7 -7
- package/dist-es/stacking-group.css-mistica.js +2 -2
- package/dist-es/stepper.css-mistica.js +4 -4
- package/dist-es/style.css +1 -1
- package/dist-es/switch-component.css-mistica.js +41 -41
- package/dist-es/table.css-mistica.js +11 -11
- package/dist-es/tabs.css-mistica.js +21 -21
- package/dist-es/tag.css-mistica.js +2 -2
- package/dist-es/text-field-base.css-mistica.js +17 -17
- package/dist-es/text-field-components.css-mistica.js +4 -4
- package/dist-es/text-link.css-mistica.js +9 -9
- package/dist-es/text.css-mistica.js +8 -8
- package/dist-es/theme-context.css-mistica.js +2 -2
- package/dist-es/timeline.css-mistica.js +11 -11
- package/dist-es/timer.css-mistica.js +7 -7
- package/dist-es/tooltip.css-mistica.js +3 -3
- package/dist-es/touchable.css-mistica.js +2 -2
- package/dist-es/utils/aspect-ratio-support.css-mistica.js +4 -4
- package/dist-es/video.css-mistica.js +2 -2
- package/dist-es/vivinho-loading-animation/vivinho-loading-animation.css-mistica.js +2 -2
- package/doc/components.md +0 -19
- package/doc/layout.md +1 -20
- package/doc/llms.md +0 -7
- package/doc/patterns.md +17 -6
- package/package.json +1 -1
- package/src/community/__stories__/ai-card-story.tsx +101 -0
- package/src/community/ai-card.css.ts +135 -0
- package/src/community/ai-card.tsx +231 -0
- package/src/community/index.tsx +1 -0
- package/src/index.tsx +1 -0
- package/src/menu.css.ts +6 -0
- package/src/menu.tsx +19 -8
- package/src/package-version.tsx +1 -1
- 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;
|
package/src/community/index.tsx
CHANGED
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
|
-
|
|
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
|
-
|
|
150
|
-
{label}
|
|
151
|
-
</Text3>
|
|
162
|
+
{renderTextContent()}
|
|
152
163
|
</div>
|
|
153
164
|
</Box>
|
|
154
165
|
</Touchable>
|
package/src/package-version.tsx
CHANGED
|
@@ -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
|
|
2
|
+
export const PACKAGE_VERSION = '16.62.0' as string;
|