@telefonica/mistica 16.62.0-beta.1 → 16.63.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 +26 -7
- package/dist/menu.js +170 -143
- 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 +213 -186
- 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 +4 -4
- package/doc/layout.md +1 -0
- 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 +99 -30
- package/src/package-version.tsx +1 -1
|
@@ -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,24 +53,44 @@ 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;
|
|
59
|
-
onPress: (item: number) => void;
|
|
60
60
|
dataAttributes?: DataAttributes;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
interface
|
|
63
|
+
interface MenuItemOnPressProps extends MenuItemBaseProps {
|
|
64
|
+
onPress: (item: number) => void;
|
|
65
|
+
controlType?: 'checkbox';
|
|
66
|
+
checked?: boolean;
|
|
67
|
+
href?: undefined;
|
|
68
|
+
to?: undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface MenuItemHrefProps extends MenuItemBaseProps {
|
|
72
|
+
href: string;
|
|
73
|
+
newTab?: boolean;
|
|
74
|
+
loadOnTop?: boolean;
|
|
75
|
+
onNavigate?: () => void | Promise<void>;
|
|
76
|
+
onPress?: undefined;
|
|
77
|
+
to?: undefined;
|
|
64
78
|
controlType?: undefined;
|
|
65
79
|
checked?: undefined;
|
|
66
80
|
}
|
|
67
81
|
|
|
68
|
-
interface
|
|
69
|
-
|
|
70
|
-
|
|
82
|
+
interface MenuItemToProps extends MenuItemBaseProps {
|
|
83
|
+
to: string;
|
|
84
|
+
newTab?: boolean;
|
|
85
|
+
fullPageOnWebView?: boolean;
|
|
86
|
+
onNavigate?: () => void | Promise<void>;
|
|
87
|
+
onPress?: undefined;
|
|
88
|
+
href?: undefined;
|
|
89
|
+
controlType?: undefined;
|
|
90
|
+
checked?: undefined;
|
|
71
91
|
}
|
|
72
92
|
|
|
73
|
-
type MenuItemProps = ExclusifyUnion<
|
|
93
|
+
type MenuItemProps = ExclusifyUnion<MenuItemOnPressProps | MenuItemHrefProps | MenuItemToProps>;
|
|
74
94
|
|
|
75
95
|
export const MenuItem = ({
|
|
76
96
|
label,
|
|
@@ -78,8 +98,15 @@ export const MenuItem = ({
|
|
|
78
98
|
destructive,
|
|
79
99
|
disabled,
|
|
80
100
|
onPress,
|
|
101
|
+
href,
|
|
102
|
+
to,
|
|
103
|
+
newTab,
|
|
104
|
+
loadOnTop,
|
|
105
|
+
fullPageOnWebView,
|
|
106
|
+
onNavigate,
|
|
81
107
|
controlType,
|
|
82
108
|
checked,
|
|
109
|
+
description,
|
|
83
110
|
dataAttributes,
|
|
84
111
|
}: MenuItemProps): JSX.Element => {
|
|
85
112
|
const {focusedItem, setFocusedItem, closeMenu, isMenuOpen} = useMenuContext();
|
|
@@ -93,6 +120,30 @@ export const MenuItem = ({
|
|
|
93
120
|
|
|
94
121
|
const menuItemDataAttributes = {testid: 'MenuItem', ...dataAttributes};
|
|
95
122
|
|
|
123
|
+
const renderTextContent = (id?: string) => (
|
|
124
|
+
<div id={id} className={styles.itemTextContent}>
|
|
125
|
+
<Text3 regular color={contentColor}>
|
|
126
|
+
{label}
|
|
127
|
+
</Text3>
|
|
128
|
+
{description && (
|
|
129
|
+
<Text2 regular color={vars.colors.textSecondary}>
|
|
130
|
+
{description}
|
|
131
|
+
</Text2>
|
|
132
|
+
)}
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const renderItemContent = (labelId?: string) => (
|
|
137
|
+
<div className={styles.itemContent}>
|
|
138
|
+
{Icon && (
|
|
139
|
+
<div className={styles.iconContainer}>
|
|
140
|
+
<Icon size={24} color={contentColor} />
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
{renderTextContent(labelId)}
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
|
|
96
147
|
const renderContent = () =>
|
|
97
148
|
controlType === 'checkbox' ? (
|
|
98
149
|
<Checkbox
|
|
@@ -101,37 +152,64 @@ export const MenuItem = ({
|
|
|
101
152
|
checked={checked}
|
|
102
153
|
onChange={() => {
|
|
103
154
|
if (isMenuOpen && itemIndex !== null) {
|
|
104
|
-
onPress(itemIndex);
|
|
155
|
+
onPress?.(itemIndex);
|
|
105
156
|
closeMenu();
|
|
106
157
|
}
|
|
107
158
|
}}
|
|
108
159
|
disabled={disabled}
|
|
109
160
|
role="menuitemcheckbox"
|
|
110
161
|
dataAttributes={menuItemDataAttributes}
|
|
111
|
-
render={({controlElement}) => (
|
|
162
|
+
render={({controlElement, labelId}) => (
|
|
112
163
|
<Box paddingX={8} paddingY={12}>
|
|
113
164
|
<Inline space="between" alignItems="center">
|
|
114
|
-
|
|
115
|
-
{Icon && (
|
|
116
|
-
<div className={styles.iconContainer}>
|
|
117
|
-
<Icon size={24} color={contentColor} />
|
|
118
|
-
</div>
|
|
119
|
-
)}
|
|
120
|
-
<Text3 regular color={contentColor}>
|
|
121
|
-
{label}
|
|
122
|
-
</Text3>
|
|
123
|
-
</div>
|
|
165
|
+
{renderItemContent(labelId)}
|
|
124
166
|
<Box paddingLeft={16}>{controlElement}</Box>
|
|
125
167
|
</Inline>
|
|
126
168
|
</Box>
|
|
127
169
|
)}
|
|
128
170
|
/>
|
|
171
|
+
) : href ? (
|
|
172
|
+
<Touchable
|
|
173
|
+
ref={itemRef}
|
|
174
|
+
href={href}
|
|
175
|
+
newTab={newTab}
|
|
176
|
+
loadOnTop={loadOnTop}
|
|
177
|
+
onNavigate={() => {
|
|
178
|
+
closeMenu();
|
|
179
|
+
onNavigate?.();
|
|
180
|
+
}}
|
|
181
|
+
disabled={disabled}
|
|
182
|
+
role="menuitem"
|
|
183
|
+
dataAttributes={menuItemDataAttributes}
|
|
184
|
+
>
|
|
185
|
+
<Box paddingX={8} paddingY={12}>
|
|
186
|
+
{renderItemContent()}
|
|
187
|
+
</Box>
|
|
188
|
+
</Touchable>
|
|
189
|
+
) : to ? (
|
|
190
|
+
<Touchable
|
|
191
|
+
ref={itemRef}
|
|
192
|
+
to={to}
|
|
193
|
+
newTab={newTab}
|
|
194
|
+
fullPageOnWebView={fullPageOnWebView}
|
|
195
|
+
onNavigate={() => {
|
|
196
|
+
closeMenu();
|
|
197
|
+
onNavigate?.();
|
|
198
|
+
}}
|
|
199
|
+
disabled={disabled}
|
|
200
|
+
role="menuitem"
|
|
201
|
+
dataAttributes={menuItemDataAttributes}
|
|
202
|
+
>
|
|
203
|
+
<Box paddingX={8} paddingY={12}>
|
|
204
|
+
{renderItemContent()}
|
|
205
|
+
</Box>
|
|
206
|
+
</Touchable>
|
|
129
207
|
) : (
|
|
130
208
|
<Touchable
|
|
131
209
|
ref={itemRef}
|
|
132
210
|
onPress={() => {
|
|
133
211
|
if (isMenuOpen && itemIndex !== null) {
|
|
134
|
-
onPress(itemIndex);
|
|
212
|
+
onPress?.(itemIndex);
|
|
135
213
|
closeMenu();
|
|
136
214
|
}
|
|
137
215
|
}}
|
|
@@ -140,16 +218,7 @@ export const MenuItem = ({
|
|
|
140
218
|
dataAttributes={menuItemDataAttributes}
|
|
141
219
|
>
|
|
142
220
|
<Box paddingX={8} paddingY={12}>
|
|
143
|
-
|
|
144
|
-
{Icon && (
|
|
145
|
-
<div className={styles.iconContainer}>
|
|
146
|
-
<Icon size={24} color={contentColor} />
|
|
147
|
-
</div>
|
|
148
|
-
)}
|
|
149
|
-
<Text3 regular color={contentColor}>
|
|
150
|
-
{label}
|
|
151
|
-
</Text3>
|
|
152
|
-
</div>
|
|
221
|
+
{renderItemContent()}
|
|
153
222
|
</Box>
|
|
154
223
|
</Touchable>
|
|
155
224
|
);
|
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.
|
|
2
|
+
export const PACKAGE_VERSION = '16.63.0' as string;
|