@castui/cast-ui 4.9.0 → 4.10.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/README.md +23 -1
- package/dist/components/Accordion/Accordion.js +4 -3
- package/dist/components/Backdrop/Backdrop.js +7 -8
- package/dist/components/BottomSheet/BottomSheet.js +12 -14
- package/dist/components/Drawer/Drawer.js +12 -14
- package/dist/components/Progress/Progress.js +5 -4
- package/dist/components/Skeleton/Skeleton.js +11 -13
- package/dist/components/SpeedDial/SpeedDial.js +3 -5
- package/dist/components/Spinner/Spinner.js +6 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +14 -3
- package/dist/theme/ThemeContext.d.ts +12 -1
- package/dist/theme/ThemeContext.js +5 -2
- package/dist/theme/applyCastTheme.d.ts +32 -2
- package/dist/theme/applyCastTheme.js +72 -0
- package/dist/theme/index.d.ts +1 -0
- package/dist/theme/index.js +3 -1
- package/dist/theme/useMotion.d.ts +32 -0
- package/dist/theme/useMotion.js +55 -0
- package/dist/tokens/index.d.ts +1 -0
- package/dist/tokens/index.js +12 -1
- package/dist/tokens/motion.d.ts +196 -0
- package/dist/tokens/motion.js +175 -0
- package/package.json +2 -1
- package/skills/cast-ui-component/SKILL.md +834 -0
- package/skills/cast-ui-docs-site/SKILL.md +114 -0
- package/skills/cast-ui-usage/SKILL.md +587 -0
package/README.md
CHANGED
|
@@ -18,8 +18,18 @@ so what designers see in Figma is what ships in the app. Every component
|
|
|
18
18
|
supports light and dark mode, three spacing densities, and your own brand
|
|
19
19
|
colours — all switchable while the app is running, with no rebuild.
|
|
20
20
|
|
|
21
|
+
Documentation site — live examples, patterns, templates, themes, motion,
|
|
22
|
+
and the system graph: **https://connagh.github.io/cast-ui/**
|
|
23
|
+
|
|
21
24
|
Browse every component live in the
|
|
22
|
-
[hosted Storybook](https://main--6990f00d7b8682c18d2ed5f3.chromatic.com)
|
|
25
|
+
[hosted Storybook](https://main--6990f00d7b8682c18d2ed5f3.chromatic.com),
|
|
26
|
+
or grab the open source
|
|
27
|
+
[Figma kit](https://www.figma.com/community/file/1648821010844688421/cast-ui-kit-for-react-native).
|
|
28
|
+
|
|
29
|
+
Motion is part of the token system too: durations, easing curves, and
|
|
30
|
+
springs live in the kit's `motion` variable collection, ship as
|
|
31
|
+
`theme.motion`, honour the OS reduce-motion setting, and can be retimed at
|
|
32
|
+
runtime like any colour.
|
|
23
33
|
|
|
24
34
|
## Installation
|
|
25
35
|
|
|
@@ -265,6 +275,18 @@ npm run build # compile to dist/
|
|
|
265
275
|
| `npm run build-storybook` | Build static Storybook |
|
|
266
276
|
| `npm run build` | TypeScript compilation to `dist/` |
|
|
267
277
|
|
|
278
|
+
### Documentation site
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
cd site
|
|
282
|
+
npm install
|
|
283
|
+
npm run dev # local dev server
|
|
284
|
+
npm run smoke # render every route + evaluate every live example
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The site aliases `@castui/cast-ui` to `../src`, so it always documents the
|
|
288
|
+
code in your working tree.
|
|
289
|
+
|
|
268
290
|
## CI/CD
|
|
269
291
|
|
|
270
292
|
| Workflow | Trigger | Purpose |
|
|
@@ -81,6 +81,7 @@ function toOpenArray(v, type) {
|
|
|
81
81
|
function AccordionItem({ value, title, leadingIcon, disabled = false, children, style, accessibilityLabel, }) {
|
|
82
82
|
const { openValues, toggle, size } = useAccordionContext('AccordionItem');
|
|
83
83
|
const { components, colors, scheme } = (0, theme_1.useTheme)();
|
|
84
|
+
const motion = (0, theme_1.useMotion)();
|
|
84
85
|
const [isHovered, setIsHovered] = (0, react_1.useState)(false);
|
|
85
86
|
const sizeTokens = components.accordion[size];
|
|
86
87
|
const isOpen = openValues.includes(value);
|
|
@@ -89,11 +90,11 @@ function AccordionItem({ value, title, leadingIcon, disabled = false, children,
|
|
|
89
90
|
(0, react_1.useEffect)(() => {
|
|
90
91
|
react_native_1.Animated.timing(spin, {
|
|
91
92
|
toValue: isOpen ? 1 : 0,
|
|
92
|
-
duration:
|
|
93
|
-
easing:
|
|
93
|
+
duration: motion.scale(motion.transition.expand.duration),
|
|
94
|
+
easing: motion.transition.expand.easing,
|
|
94
95
|
useNativeDriver: true,
|
|
95
96
|
}).start();
|
|
96
|
-
}, [isOpen, spin]);
|
|
97
|
+
}, [isOpen, spin, motion]);
|
|
97
98
|
const rotate = spin.interpolate({
|
|
98
99
|
inputRange: [0, 1],
|
|
99
100
|
outputRange: ['0deg', '90deg'],
|
|
@@ -23,15 +23,12 @@ const theme_1 = require("../../theme");
|
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
24
|
// Constants
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
|
-
/** Fade timing. */
|
|
27
|
-
const DURATION = 220;
|
|
28
|
-
/** react-native-web does not support the native animation driver. */
|
|
29
|
-
const USE_NATIVE_DRIVER = react_native_1.Platform.OS !== 'web';
|
|
30
26
|
// ---------------------------------------------------------------------------
|
|
31
27
|
// Component
|
|
32
28
|
// ---------------------------------------------------------------------------
|
|
33
29
|
function Backdrop({ open, onPress, invisible = false, children, style, accessibilityLabel, }) {
|
|
34
30
|
const { scheme } = (0, theme_1.useTheme)();
|
|
31
|
+
const motion = (0, theme_1.useMotion)();
|
|
35
32
|
const targetOpacity = invisible ? 0 : scheme.overlay.scrimOpacity;
|
|
36
33
|
const fade = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
|
|
37
34
|
const [mounted, setMounted] = (0, react_1.useState)(open);
|
|
@@ -40,15 +37,17 @@ function Backdrop({ open, onPress, invisible = false, children, style, accessibi
|
|
|
40
37
|
setMounted(true);
|
|
41
38
|
react_native_1.Animated.timing(fade, {
|
|
42
39
|
toValue: 1,
|
|
43
|
-
duration:
|
|
44
|
-
|
|
40
|
+
duration: motion.scale(motion.transition.standard.duration),
|
|
41
|
+
easing: motion.transition.standard.easing,
|
|
42
|
+
useNativeDriver: motion.useNativeDriver,
|
|
45
43
|
}).start();
|
|
46
44
|
}
|
|
47
45
|
else if (mounted) {
|
|
48
46
|
react_native_1.Animated.timing(fade, {
|
|
49
47
|
toValue: 0,
|
|
50
|
-
duration:
|
|
51
|
-
|
|
48
|
+
duration: motion.scale(motion.transition.standard.duration),
|
|
49
|
+
easing: motion.transition.standard.easing,
|
|
50
|
+
useNativeDriver: motion.useNativeDriver,
|
|
52
51
|
}).start(({ finished }) => {
|
|
53
52
|
if (finished)
|
|
54
53
|
setMounted(false);
|
|
@@ -34,10 +34,6 @@ const tokens_1 = require("../../tokens");
|
|
|
34
34
|
// ---------------------------------------------------------------------------
|
|
35
35
|
/** The sheet never grows past this share of the screen height. */
|
|
36
36
|
const MAX_HEIGHT_RATIO = 0.9;
|
|
37
|
-
/** Animation timing. */
|
|
38
|
-
const DURATION = 220;
|
|
39
|
-
/** react-native-web does not support the native animation driver. */
|
|
40
|
-
const USE_NATIVE_DRIVER = react_native_1.Platform.OS !== 'web';
|
|
41
37
|
/** Upward shadow for web (matches Figma shadow/lg, cast above the sheet). */
|
|
42
38
|
const SHADOW_WEB = {
|
|
43
39
|
boxShadow: '0px -4px 6px rgba(0,0,0,0.04), 0px -10px 15px rgba(0,0,0,0.08)',
|
|
@@ -97,6 +93,7 @@ function BottomSheetContent({ title: titleText, showHandle = true, children, sty
|
|
|
97
93
|
// ---------------------------------------------------------------------------
|
|
98
94
|
function BottomSheet({ open, onClose, closeOnBackdropPress = true, ...contentProps }) {
|
|
99
95
|
const { scheme } = (0, theme_1.useTheme)();
|
|
96
|
+
const motion = (0, theme_1.useMotion)();
|
|
100
97
|
const scrimOpacity = scheme.overlay.scrimOpacity;
|
|
101
98
|
const screenHeight = react_native_1.Dimensions.get('window').height;
|
|
102
99
|
const translateY = (0, react_1.useRef)(new react_native_1.Animated.Value(screenHeight)).current;
|
|
@@ -108,15 +105,14 @@ function BottomSheet({ open, onClose, closeOnBackdropPress = true, ...contentPro
|
|
|
108
105
|
react_native_1.Animated.parallel([
|
|
109
106
|
react_native_1.Animated.timing(backdrop, {
|
|
110
107
|
toValue: 1,
|
|
111
|
-
duration:
|
|
112
|
-
|
|
108
|
+
duration: motion.scale(motion.transition.standard.duration),
|
|
109
|
+
easing: motion.transition.standard.easing,
|
|
110
|
+
useNativeDriver: motion.useNativeDriver,
|
|
113
111
|
}),
|
|
114
112
|
react_native_1.Animated.spring(translateY, {
|
|
115
113
|
toValue: 0,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
mass: 0.9,
|
|
119
|
-
useNativeDriver: USE_NATIVE_DRIVER,
|
|
114
|
+
...motion.spring.overlay,
|
|
115
|
+
useNativeDriver: motion.useNativeDriver,
|
|
120
116
|
}),
|
|
121
117
|
]).start();
|
|
122
118
|
}
|
|
@@ -124,13 +120,15 @@ function BottomSheet({ open, onClose, closeOnBackdropPress = true, ...contentPro
|
|
|
124
120
|
react_native_1.Animated.parallel([
|
|
125
121
|
react_native_1.Animated.timing(backdrop, {
|
|
126
122
|
toValue: 0,
|
|
127
|
-
duration:
|
|
128
|
-
|
|
123
|
+
duration: motion.scale(motion.transition.standard.duration),
|
|
124
|
+
easing: motion.transition.standard.easing,
|
|
125
|
+
useNativeDriver: motion.useNativeDriver,
|
|
129
126
|
}),
|
|
130
127
|
react_native_1.Animated.timing(translateY, {
|
|
131
128
|
toValue: screenHeight,
|
|
132
|
-
duration:
|
|
133
|
-
|
|
129
|
+
duration: motion.scale(motion.transition.standard.duration),
|
|
130
|
+
easing: motion.transition.standard.easing,
|
|
131
|
+
useNativeDriver: motion.useNativeDriver,
|
|
134
132
|
}),
|
|
135
133
|
]).start(({ finished }) => {
|
|
136
134
|
if (finished)
|
|
@@ -36,10 +36,6 @@ const tokens_1 = require("../../tokens");
|
|
|
36
36
|
const DEFAULT_WIDTH = 320;
|
|
37
37
|
/** Top/bottom panels never grow past this share of the screen height. */
|
|
38
38
|
const MAX_HEIGHT_RATIO = 0.9;
|
|
39
|
-
/** Animation timing. */
|
|
40
|
-
const DURATION = 240;
|
|
41
|
-
/** react-native-web does not support the native animation driver. */
|
|
42
|
-
const USE_NATIVE_DRIVER = react_native_1.Platform.OS !== 'web';
|
|
43
39
|
const SHADOW_WEB = {
|
|
44
40
|
boxShadow: '0px 0px 6px rgba(0,0,0,0.04), 0px 0px 15px rgba(0,0,0,0.08)',
|
|
45
41
|
};
|
|
@@ -99,6 +95,7 @@ function DrawerContent({ anchor = 'left', title: titleText, children, style, acc
|
|
|
99
95
|
// ---------------------------------------------------------------------------
|
|
100
96
|
function Drawer({ open, onClose, closeOnBackdropPress = true, anchor = 'left', ...contentProps }) {
|
|
101
97
|
const { scheme } = (0, theme_1.useTheme)();
|
|
98
|
+
const motion = (0, theme_1.useMotion)();
|
|
102
99
|
const scrimOpacity = scheme.overlay.scrimOpacity;
|
|
103
100
|
const screen = react_native_1.Dimensions.get('window');
|
|
104
101
|
const horizontal = isHorizontal(anchor);
|
|
@@ -113,15 +110,14 @@ function Drawer({ open, onClose, closeOnBackdropPress = true, anchor = 'left', .
|
|
|
113
110
|
react_native_1.Animated.parallel([
|
|
114
111
|
react_native_1.Animated.timing(backdrop, {
|
|
115
112
|
toValue: 1,
|
|
116
|
-
duration:
|
|
117
|
-
|
|
113
|
+
duration: motion.scale(motion.transition.standard.duration),
|
|
114
|
+
easing: motion.transition.standard.easing,
|
|
115
|
+
useNativeDriver: motion.useNativeDriver,
|
|
118
116
|
}),
|
|
119
117
|
react_native_1.Animated.spring(offset, {
|
|
120
118
|
toValue: 0,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
mass: 0.9,
|
|
124
|
-
useNativeDriver: USE_NATIVE_DRIVER,
|
|
119
|
+
...motion.spring.overlay,
|
|
120
|
+
useNativeDriver: motion.useNativeDriver,
|
|
125
121
|
}),
|
|
126
122
|
]).start();
|
|
127
123
|
}
|
|
@@ -129,13 +125,15 @@ function Drawer({ open, onClose, closeOnBackdropPress = true, anchor = 'left', .
|
|
|
129
125
|
react_native_1.Animated.parallel([
|
|
130
126
|
react_native_1.Animated.timing(backdrop, {
|
|
131
127
|
toValue: 0,
|
|
132
|
-
duration:
|
|
133
|
-
|
|
128
|
+
duration: motion.scale(motion.transition.standard.duration),
|
|
129
|
+
easing: motion.transition.standard.easing,
|
|
130
|
+
useNativeDriver: motion.useNativeDriver,
|
|
134
131
|
}),
|
|
135
132
|
react_native_1.Animated.timing(offset, {
|
|
136
133
|
toValue: distance * sign,
|
|
137
|
-
duration:
|
|
138
|
-
|
|
134
|
+
duration: motion.scale(motion.transition.standard.duration),
|
|
135
|
+
easing: motion.transition.standard.easing,
|
|
136
|
+
useNativeDriver: motion.useNativeDriver,
|
|
139
137
|
}),
|
|
140
138
|
]).start(({ finished }) => {
|
|
141
139
|
if (finished)
|
|
@@ -38,6 +38,7 @@ const clamp = (n) => Math.max(0, Math.min(100, n));
|
|
|
38
38
|
// ---------------------------------------------------------------------------
|
|
39
39
|
function Progress({ value, intent = 'brand', size = 'default', style, accessibilityLabel = 'Loading', }) {
|
|
40
40
|
const { components, colors, scheme } = (0, theme_1.useTheme)();
|
|
41
|
+
const motion = (0, theme_1.useMotion)();
|
|
41
42
|
const { trackHeight } = components.progress[size];
|
|
42
43
|
const borderRadius = components.progress.borderRadius;
|
|
43
44
|
const fill = colors[intent].bold.default.bg;
|
|
@@ -50,18 +51,18 @@ function Progress({ value, intent = 'brand', size = 'default', style, accessibil
|
|
|
50
51
|
const onLayout = (e) => setTrackWidth(e.nativeEvent.layout.width);
|
|
51
52
|
const slide = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
|
|
52
53
|
(0, react_1.useEffect)(() => {
|
|
53
|
-
if (!isIndeterminate || trackWidth === 0)
|
|
54
|
+
if (!isIndeterminate || trackWidth === 0 || motion.reduceMotion)
|
|
54
55
|
return;
|
|
55
56
|
slide.setValue(0);
|
|
56
57
|
const loop = react_native_1.Animated.loop(react_native_1.Animated.timing(slide, {
|
|
57
58
|
toValue: 1,
|
|
58
|
-
duration:
|
|
59
|
-
easing:
|
|
59
|
+
duration: motion.loop.indeterminate.duration,
|
|
60
|
+
easing: motion.loop.indeterminate.easing,
|
|
60
61
|
useNativeDriver: true,
|
|
61
62
|
}));
|
|
62
63
|
loop.start();
|
|
63
64
|
return () => loop.stop();
|
|
64
|
-
}, [isIndeterminate, trackWidth, slide]);
|
|
65
|
+
}, [isIndeterminate, trackWidth, slide, motion]);
|
|
65
66
|
const barWidth = trackWidth * INDETERMINATE_BAR_FRACTION;
|
|
66
67
|
const translateX = slide.interpolate({
|
|
67
68
|
inputRange: [0, 1],
|
|
@@ -19,11 +19,6 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
19
19
|
const react_1 = require("react");
|
|
20
20
|
const react_native_1 = require("react-native");
|
|
21
21
|
const theme_1 = require("../../theme");
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
// Constants
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// react-native-web has no native driver and warns if asked for one.
|
|
26
|
-
const USE_NATIVE_DRIVER = react_native_1.Platform.OS !== 'web';
|
|
27
22
|
/** Default size + corner radius per shape (radius/2, surface/overlay/radius, radius/full). */
|
|
28
23
|
const SHAPE_DEFAULTS = {
|
|
29
24
|
text: { width: 120, height: 12, radius: 4 },
|
|
@@ -35,29 +30,32 @@ const SHAPE_DEFAULTS = {
|
|
|
35
30
|
// ---------------------------------------------------------------------------
|
|
36
31
|
function Skeleton({ shape = 'text', width, height, radius, animated = true, style, accessibilityLabel = 'Loading', }) {
|
|
37
32
|
const { scheme } = (0, theme_1.useTheme)();
|
|
33
|
+
const motion = (0, theme_1.useMotion)();
|
|
38
34
|
const skeletonColors = scheme.skeleton;
|
|
39
35
|
const defaults = SHAPE_DEFAULTS[shape];
|
|
40
36
|
const opacity = (0, react_1.useRef)(new react_native_1.Animated.Value(1)).current;
|
|
41
37
|
(0, react_1.useEffect)(() => {
|
|
42
|
-
if (!animated) {
|
|
38
|
+
if (!animated || motion.reduceMotion) {
|
|
43
39
|
opacity.setValue(1);
|
|
44
40
|
return;
|
|
45
41
|
}
|
|
46
42
|
const loop = react_native_1.Animated.loop(react_native_1.Animated.sequence([
|
|
47
43
|
react_native_1.Animated.timing(opacity, {
|
|
48
|
-
toValue:
|
|
49
|
-
duration:
|
|
50
|
-
|
|
44
|
+
toValue: motion.loop.pulse.to,
|
|
45
|
+
duration: motion.loop.pulse.duration,
|
|
46
|
+
easing: motion.loop.pulse.easing,
|
|
47
|
+
useNativeDriver: motion.useNativeDriver,
|
|
51
48
|
}),
|
|
52
49
|
react_native_1.Animated.timing(opacity, {
|
|
53
|
-
toValue:
|
|
54
|
-
duration:
|
|
55
|
-
|
|
50
|
+
toValue: motion.loop.pulse.from,
|
|
51
|
+
duration: motion.loop.pulse.duration,
|
|
52
|
+
easing: motion.loop.pulse.easing,
|
|
53
|
+
useNativeDriver: motion.useNativeDriver,
|
|
56
54
|
}),
|
|
57
55
|
]));
|
|
58
56
|
loop.start();
|
|
59
57
|
return () => loop.stop();
|
|
60
|
-
}, [animated, opacity]);
|
|
58
|
+
}, [animated, opacity, motion]);
|
|
61
59
|
return ((0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { accessibilityRole: "image", accessibilityLabel: accessibilityLabel, style: [
|
|
62
60
|
{
|
|
63
61
|
width: width ?? defaults.width,
|
|
@@ -58,9 +58,6 @@ const ACTION_ICON = {
|
|
|
58
58
|
default: 'default',
|
|
59
59
|
large: 'default',
|
|
60
60
|
};
|
|
61
|
-
const DURATION_IN = 160;
|
|
62
|
-
const DURATION_OUT = 140;
|
|
63
|
-
const USE_NATIVE_DRIVER = react_native_1.Platform.OS !== 'web';
|
|
64
61
|
const SHADOW_WEB = {
|
|
65
62
|
boxShadow: '0px 4px 6px -1px rgba(0,0,0,0.12), 0px 2px 4px -2px rgba(0,0,0,0.1)',
|
|
66
63
|
};
|
|
@@ -111,6 +108,7 @@ function SpeedDialAction({ icon, label, onPress, disabled = false }) {
|
|
|
111
108
|
// ---------------------------------------------------------------------------
|
|
112
109
|
function SpeedDial({ children, icon = 'add', openIcon = 'close', open: controlledOpen, onOpenChange, defaultOpen = false, direction = 'up', intent = 'brand', size = 'default', backdrop = true, style, accessibilityLabel, }) {
|
|
113
110
|
const { components, colors, scheme } = (0, theme_1.useTheme)();
|
|
111
|
+
const motion = (0, theme_1.useMotion)();
|
|
114
112
|
const { fabSize, actionSize, gap } = components.speedDial[size];
|
|
115
113
|
const fabColors = colors[intent].bold.default;
|
|
116
114
|
const isControlled = controlledOpen !== undefined;
|
|
@@ -128,10 +126,10 @@ function SpeedDial({ children, icon = 'add', openIcon = 'close', open: controlle
|
|
|
128
126
|
(0, react_1.useEffect)(() => {
|
|
129
127
|
if (open) {
|
|
130
128
|
setActionsMounted(true);
|
|
131
|
-
react_native_1.Animated.timing(anim, { toValue: 1, duration:
|
|
129
|
+
react_native_1.Animated.timing(anim, { toValue: 1, duration: motion.scale(motion.transition.enter.duration), easing: motion.transition.enter.easing, useNativeDriver: motion.useNativeDriver }).start();
|
|
132
130
|
}
|
|
133
131
|
else if (actionsMounted) {
|
|
134
|
-
react_native_1.Animated.timing(anim, { toValue: 0, duration:
|
|
132
|
+
react_native_1.Animated.timing(anim, { toValue: 0, duration: motion.scale(motion.transition.exit.duration), easing: motion.transition.exit.easing, useNativeDriver: motion.useNativeDriver }).start(({ finished }) => {
|
|
135
133
|
if (finished)
|
|
136
134
|
setActionsMounted(false);
|
|
137
135
|
});
|
|
@@ -36,28 +36,29 @@ const theme_1 = require("../../theme");
|
|
|
36
36
|
// ---------------------------------------------------------------------------
|
|
37
37
|
// Constants
|
|
38
38
|
// ---------------------------------------------------------------------------
|
|
39
|
-
/** One full rotation, in milliseconds. */
|
|
40
|
-
const ROTATION_DURATION = 800;
|
|
41
39
|
// ---------------------------------------------------------------------------
|
|
42
40
|
// Component
|
|
43
41
|
// ---------------------------------------------------------------------------
|
|
44
42
|
function Spinner({ intent = 'brand', size = 'default', style, accessibilityLabel = 'Loading', }) {
|
|
45
43
|
const { components, colors, scheme } = (0, theme_1.useTheme)();
|
|
44
|
+
const motion = (0, theme_1.useMotion)();
|
|
46
45
|
const { diameter, stroke } = components.spinner[size];
|
|
47
46
|
const arc = colors[intent].bold.default.bg;
|
|
48
47
|
const trackColor = scheme.spinner.track;
|
|
49
48
|
const spin = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
|
|
50
49
|
(0, react_1.useEffect)(() => {
|
|
51
50
|
spin.setValue(0);
|
|
51
|
+
if (motion.reduceMotion)
|
|
52
|
+
return;
|
|
52
53
|
const loop = react_native_1.Animated.loop(react_native_1.Animated.timing(spin, {
|
|
53
54
|
toValue: 1,
|
|
54
|
-
duration:
|
|
55
|
-
easing:
|
|
55
|
+
duration: motion.loop.spin.duration,
|
|
56
|
+
easing: motion.loop.spin.easing,
|
|
56
57
|
useNativeDriver: true,
|
|
57
58
|
}));
|
|
58
59
|
loop.start();
|
|
59
60
|
return () => loop.stop();
|
|
60
|
-
}, [spin]);
|
|
61
|
+
}, [spin, motion]);
|
|
61
62
|
const rotate = spin.interpolate({
|
|
62
63
|
inputRange: [0, 1],
|
|
63
64
|
outputRange: ['0deg', '360deg'],
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { lightColors, darkColors, colorSchemes, intentColors, disabledColors, controlTokens, surfaceTokens, textTokens, overlayTokens, selectColors, menuColors, tagTokens, errorTokens, listColors, checkboxColors, toggleColors, progressColors, tabsColors, radioColors, avatarColors, skeletonColors, sliderColors, tableColors, fontFamily, fontWeight, label, title, body, heading, display, caption, type IntentName, type ProminenceName, type StateName, type ColorMode, type ColorScheme, type LabelSize, iconSize, type IconSize, breakpoints, breakpointOrder, resolveBreakpoint, resolveResponsiveValue, type Breakpoint, type BreakpointKey, } from './tokens';
|
|
2
|
-
export { ThemeProvider, useTheme, themes, applyCastTheme, type Theme, type ThemeProviderProps, type CastThemeFile, type CastThemeProps, type DensityTheme, type ComponentTokens, type ButtonSizeTokens, type ButtonThemeTokens, type DialogSizeTokens, type DialogThemeTokens, type InputSizeTokens, type InputThemeTokens, type SelectContentTokens, type SelectOptionTokens, type SelectGroupTokens, type SelectSeparatorTokens, type SelectThemeTokens, type ListItemTokens, type ListSubheaderTokens, type ListThemeTokens, type CheckboxSizeTokens, type CheckboxThemeTokens, type AlertSizeTokens, type AlertThemeTokens, type ToggleSizeTokens, type ToggleThemeTokens, type CardSizeTokens, type CardThemeTokens, type BadgeSizeTokens, type BadgeThemeTokens, type RadioSizeTokens, type RadioThemeTokens, type ToastSizeTokens, type ToastThemeTokens, type ChipSizeTokens, type ChipThemeTokens, type AvatarSizeTokens, type AvatarThemeTokens, type PopoverSizeTokens, type PopoverThemeTokens, type TooltipSizeTokens, type TooltipThemeTokens, type ProgressSizeTokens, type ProgressThemeTokens, type TabsSizeTokens, type TabsThemeTokens, type SpinnerSizeTokens, type SpinnerThemeTokens, type BottomSheetThemeTokens, type LinkSizeTokens, type LinkThemeTokens, type BreadcrumbsSizeTokens, type BreadcrumbsThemeTokens, type CodeBlockSizeTokens, type CodeBlockThemeTokens, type DrawerThemeTokens, type MenuItemTokens, type MenuGroupTokens, type MenuThemeTokens, type ToggleButtonGroupSizeTokens, type ToggleButtonGroupThemeTokens, type AppBarSizeTokens, type AppBarThemeTokens, type SliderSizeTokens, type SliderThemeTokens, type SpeedDialSizeTokens, type SpeedDialThemeTokens, type TableSizeTokens, type TableThemeTokens, type DeepPartial, } from './theme';
|
|
1
|
+
export { lightColors, darkColors, colorSchemes, intentColors, disabledColors, controlTokens, surfaceTokens, textTokens, overlayTokens, selectColors, menuColors, tagTokens, errorTokens, listColors, checkboxColors, toggleColors, progressColors, tabsColors, radioColors, avatarColors, skeletonColors, sliderColors, tableColors, fontFamily, fontWeight, label, title, body, heading, display, caption, type IntentName, type ProminenceName, type StateName, type ColorMode, type ColorScheme, type LabelSize, iconSize, type IconSize, breakpoints, breakpointOrder, resolveBreakpoint, resolveResponsiveValue, type Breakpoint, type BreakpointKey, duration, cycle, easing, easingBezier, spring, transition, feedback, loop, motionTokens, resolveMotion, type MotionTokens, type MotionTransition, type MotionOverrides, type MotionDurations, type MotionCycles, type EasingName, type EasingBezierPoints, type SpringConfig, } from './tokens';
|
|
2
|
+
export { ThemeProvider, useTheme, useMotion, themes, applyCastTheme, type Theme, type Motion, type ThemeProviderProps, type CastThemeFile, type CastThemeProps, type DensityTheme, type ComponentTokens, type ButtonSizeTokens, type ButtonThemeTokens, type DialogSizeTokens, type DialogThemeTokens, type InputSizeTokens, type InputThemeTokens, type SelectContentTokens, type SelectOptionTokens, type SelectGroupTokens, type SelectSeparatorTokens, type SelectThemeTokens, type ListItemTokens, type ListSubheaderTokens, type ListThemeTokens, type CheckboxSizeTokens, type CheckboxThemeTokens, type AlertSizeTokens, type AlertThemeTokens, type ToggleSizeTokens, type ToggleThemeTokens, type CardSizeTokens, type CardThemeTokens, type BadgeSizeTokens, type BadgeThemeTokens, type RadioSizeTokens, type RadioThemeTokens, type ToastSizeTokens, type ToastThemeTokens, type ChipSizeTokens, type ChipThemeTokens, type AvatarSizeTokens, type AvatarThemeTokens, type PopoverSizeTokens, type PopoverThemeTokens, type TooltipSizeTokens, type TooltipThemeTokens, type ProgressSizeTokens, type ProgressThemeTokens, type TabsSizeTokens, type TabsThemeTokens, type SpinnerSizeTokens, type SpinnerThemeTokens, type BottomSheetThemeTokens, type LinkSizeTokens, type LinkThemeTokens, type BreadcrumbsSizeTokens, type BreadcrumbsThemeTokens, type CodeBlockSizeTokens, type CodeBlockThemeTokens, type DrawerThemeTokens, type MenuItemTokens, type MenuGroupTokens, type MenuThemeTokens, type ToggleButtonGroupSizeTokens, type ToggleButtonGroupThemeTokens, type AppBarSizeTokens, type AppBarThemeTokens, type SliderSizeTokens, type SliderThemeTokens, type SpeedDialSizeTokens, type SpeedDialThemeTokens, type TableSizeTokens, type TableThemeTokens, type DeepPartial, } from './theme';
|
|
3
3
|
export { useBreakpoint, useMinWidth, useResponsiveValue, } from './hooks';
|
|
4
4
|
export { Button, type ButtonProps, type ButtonSize } from './components/Button';
|
|
5
5
|
export { Icon, type IconProps } from './components/Icon';
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.
|
|
5
|
-
exports.Autocomplete = exports.TableCell = exports.TableRow = exports.TableBody = exports.TableHead = void 0;
|
|
3
|
+
exports.themes = exports.useMotion = exports.useTheme = exports.ThemeProvider = exports.resolveMotion = exports.motionTokens = exports.loop = exports.feedback = exports.transition = exports.spring = exports.easingBezier = exports.easing = exports.cycle = exports.duration = exports.resolveResponsiveValue = exports.resolveBreakpoint = exports.breakpointOrder = exports.breakpoints = exports.iconSize = exports.caption = exports.display = exports.heading = exports.body = exports.title = exports.label = exports.fontWeight = exports.fontFamily = exports.tableColors = exports.sliderColors = exports.skeletonColors = exports.avatarColors = exports.radioColors = exports.tabsColors = exports.progressColors = exports.toggleColors = exports.checkboxColors = exports.listColors = exports.errorTokens = exports.tagTokens = exports.menuColors = exports.selectColors = exports.overlayTokens = exports.textTokens = exports.surfaceTokens = exports.controlTokens = exports.disabledColors = exports.intentColors = exports.colorSchemes = exports.darkColors = exports.lightColors = void 0;
|
|
4
|
+
exports.Menu = exports.DrawerContent = exports.Drawer = exports.CodeBlock = exports.Breadcrumb = exports.Breadcrumbs = exports.Backdrop = exports.Link = exports.AccordionItem = exports.Accordion = exports.Tab = exports.Tabs = exports.BottomSheetContent = exports.BottomSheet = exports.Spinner = exports.Progress = exports.Text = exports.Tooltip = exports.Skeleton = exports.Popover = exports.Avatar = exports.Divider = exports.Chip = exports.Toast = exports.RadioGroup = exports.Radio = exports.Input = exports.Badge = exports.Card = exports.Toggle = exports.Alert = exports.Checkbox = exports.ListDivider = exports.ListSubheader = exports.ListItem = exports.List = exports.SelectDropdown = exports.SelectTag = exports.SelectSeparator = exports.SelectGroup = exports.SelectOption = exports.Select = exports.DialogContent = exports.Dialog = exports.Icon = exports.Button = exports.useResponsiveValue = exports.useMinWidth = exports.useBreakpoint = exports.applyCastTheme = void 0;
|
|
5
|
+
exports.Autocomplete = exports.TableCell = exports.TableRow = exports.TableBody = exports.TableHead = exports.Table = exports.SpeedDialAction = exports.SpeedDial = exports.Slider = exports.AppBar = exports.ToggleButton = exports.ToggleButtonGroup = exports.MenuContent = exports.MenuLabel = exports.MenuDivider = exports.MenuItem = void 0;
|
|
6
6
|
// Cast UI — Cross-platform design system component library
|
|
7
7
|
//
|
|
8
8
|
// Tokens
|
|
@@ -43,10 +43,21 @@ Object.defineProperty(exports, "breakpoints", { enumerable: true, get: function
|
|
|
43
43
|
Object.defineProperty(exports, "breakpointOrder", { enumerable: true, get: function () { return tokens_1.breakpointOrder; } });
|
|
44
44
|
Object.defineProperty(exports, "resolveBreakpoint", { enumerable: true, get: function () { return tokens_1.resolveBreakpoint; } });
|
|
45
45
|
Object.defineProperty(exports, "resolveResponsiveValue", { enumerable: true, get: function () { return tokens_1.resolveResponsiveValue; } });
|
|
46
|
+
Object.defineProperty(exports, "duration", { enumerable: true, get: function () { return tokens_1.duration; } });
|
|
47
|
+
Object.defineProperty(exports, "cycle", { enumerable: true, get: function () { return tokens_1.cycle; } });
|
|
48
|
+
Object.defineProperty(exports, "easing", { enumerable: true, get: function () { return tokens_1.easing; } });
|
|
49
|
+
Object.defineProperty(exports, "easingBezier", { enumerable: true, get: function () { return tokens_1.easingBezier; } });
|
|
50
|
+
Object.defineProperty(exports, "spring", { enumerable: true, get: function () { return tokens_1.spring; } });
|
|
51
|
+
Object.defineProperty(exports, "transition", { enumerable: true, get: function () { return tokens_1.transition; } });
|
|
52
|
+
Object.defineProperty(exports, "feedback", { enumerable: true, get: function () { return tokens_1.feedback; } });
|
|
53
|
+
Object.defineProperty(exports, "loop", { enumerable: true, get: function () { return tokens_1.loop; } });
|
|
54
|
+
Object.defineProperty(exports, "motionTokens", { enumerable: true, get: function () { return tokens_1.motionTokens; } });
|
|
55
|
+
Object.defineProperty(exports, "resolveMotion", { enumerable: true, get: function () { return tokens_1.resolveMotion; } });
|
|
46
56
|
// Theme
|
|
47
57
|
var theme_1 = require("./theme");
|
|
48
58
|
Object.defineProperty(exports, "ThemeProvider", { enumerable: true, get: function () { return theme_1.ThemeProvider; } });
|
|
49
59
|
Object.defineProperty(exports, "useTheme", { enumerable: true, get: function () { return theme_1.useTheme; } });
|
|
60
|
+
Object.defineProperty(exports, "useMotion", { enumerable: true, get: function () { return theme_1.useMotion; } });
|
|
50
61
|
Object.defineProperty(exports, "themes", { enumerable: true, get: function () { return theme_1.themes; } });
|
|
51
62
|
Object.defineProperty(exports, "applyCastTheme", { enumerable: true, get: function () { return theme_1.applyCastTheme; } });
|
|
52
63
|
// Hooks
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
import React from 'react';
|
|
40
40
|
import { intentColors as defaultIntentColors } from '../tokens/colors';
|
|
41
41
|
import type { ColorMode, ColorScheme, IntentName } from '../tokens/colors';
|
|
42
|
+
import type { MotionTokens, MotionOverrides } from '../tokens/motion';
|
|
42
43
|
import type { DensityTheme, ComponentTokens, DeepPartial } from './types';
|
|
43
44
|
type IntentColorMap = typeof defaultIntentColors;
|
|
44
45
|
export type Theme = {
|
|
@@ -52,6 +53,8 @@ export type Theme = {
|
|
|
52
53
|
colors: IntentColorMap;
|
|
53
54
|
/** Disabled colours of the active scheme — kept for backwards compatibility. */
|
|
54
55
|
disabledColors: ColorScheme['disabled'];
|
|
56
|
+
/** Motion tokens — animation durations, easings, springs. Constant across density and colour mode. */
|
|
57
|
+
motion: MotionTokens;
|
|
55
58
|
};
|
|
56
59
|
export type ThemeProviderProps = {
|
|
57
60
|
/** Density theme — controls spacing and padding across all components. */
|
|
@@ -71,9 +74,17 @@ export type ThemeProviderProps = {
|
|
|
71
74
|
* Usually you don't set this by hand — `applyCastTheme` builds it for you.
|
|
72
75
|
*/
|
|
73
76
|
scheme?: DeepPartial<ColorScheme>;
|
|
77
|
+
/**
|
|
78
|
+
* Primitive-level motion overrides — durations, cycle lengths, easing
|
|
79
|
+
* beziers, springs. Semantic roles (transition/feedback/loop) are rebuilt
|
|
80
|
+
* from these, so one duration change flows into every role that uses it.
|
|
81
|
+
* Usually you don't set this by hand — `applyCastTheme` maps a
|
|
82
|
+
* cast-theme.json `motion` block onto it.
|
|
83
|
+
*/
|
|
84
|
+
motion?: MotionOverrides;
|
|
74
85
|
children: React.ReactNode;
|
|
75
86
|
};
|
|
76
|
-
export declare function ThemeProvider({ density, colorMode, colors, scheme: schemeOverride, children, }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
87
|
+
export declare function ThemeProvider({ density, colorMode, colors, scheme: schemeOverride, motion: motionOverrides, children, }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
77
88
|
/**
|
|
78
89
|
* Access the current theme — density tokens, intent colours, and component tokens.
|
|
79
90
|
* Must be called within a ThemeProvider; falls back to the "default" density if not.
|
|
@@ -44,6 +44,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
44
44
|
const react_1 = require("react");
|
|
45
45
|
const themes_1 = require("./themes");
|
|
46
46
|
const colors_1 = require("../tokens/colors");
|
|
47
|
+
const motion_1 = require("../tokens/motion");
|
|
47
48
|
// ---------------------------------------------------------------------------
|
|
48
49
|
// Deep merge utility (for partial colour overrides)
|
|
49
50
|
// ---------------------------------------------------------------------------
|
|
@@ -77,9 +78,10 @@ const defaultTheme = {
|
|
|
77
78
|
scheme: colors_1.colorSchemes.light,
|
|
78
79
|
colors: colors_1.colorSchemes.light.intents,
|
|
79
80
|
disabledColors: colors_1.colorSchemes.light.disabled,
|
|
81
|
+
motion: motion_1.motionTokens,
|
|
80
82
|
};
|
|
81
83
|
const ThemeContext = (0, react_1.createContext)(defaultTheme);
|
|
82
|
-
function ThemeProvider({ density = 'default', colorMode = 'light', colors, scheme: schemeOverride, children, }) {
|
|
84
|
+
function ThemeProvider({ density = 'default', colorMode = 'light', colors, scheme: schemeOverride, motion: motionOverrides, children, }) {
|
|
83
85
|
const theme = (0, react_1.useMemo)(() => {
|
|
84
86
|
const baseScheme = colors_1.colorSchemes[colorMode];
|
|
85
87
|
const resolvedIntents = colors
|
|
@@ -98,8 +100,9 @@ function ThemeProvider({ density = 'default', colorMode = 'light', colors, schem
|
|
|
98
100
|
scheme,
|
|
99
101
|
colors: scheme.intents,
|
|
100
102
|
disabledColors: scheme.disabled,
|
|
103
|
+
motion: (0, motion_1.resolveMotion)(motionOverrides),
|
|
101
104
|
};
|
|
102
|
-
}, [density, colorMode, colors, schemeOverride]);
|
|
105
|
+
}, [density, colorMode, colors, schemeOverride, motionOverrides]);
|
|
103
106
|
return ((0, jsx_runtime_1.jsx)(ThemeContext.Provider, { value: theme, children: children }));
|
|
104
107
|
}
|
|
105
108
|
// ---------------------------------------------------------------------------
|
|
@@ -42,7 +42,7 @@ export type CastThemeFile = {
|
|
|
42
42
|
name?: string;
|
|
43
43
|
description?: string;
|
|
44
44
|
generatedAt?: string;
|
|
45
|
-
/** Theme-file format version emitted by the plugin (
|
|
45
|
+
/** Theme-file format version emitted by the plugin (version 4 adds motion). */
|
|
46
46
|
version?: number;
|
|
47
47
|
/** Optional explicit schema version for consumer validation. */
|
|
48
48
|
schemaVersion?: number;
|
|
@@ -61,10 +61,40 @@ export type CastThemeFile = {
|
|
|
61
61
|
}>>;
|
|
62
62
|
typography?: Record<string, unknown>;
|
|
63
63
|
shadows?: Record<string, unknown>;
|
|
64
|
+
/**
|
|
65
|
+
* Motion block exported from the kit's `motion` variable collection
|
|
66
|
+
* (cast-theme version 4+). `easing` carries cubic-bezier control points
|
|
67
|
+
* as [x1, y1, x2, y2]. Motion is mode-independent, so this block is not
|
|
68
|
+
* keyed by colour mode.
|
|
69
|
+
*/
|
|
70
|
+
motion?: {
|
|
71
|
+
duration?: Record<string, number>;
|
|
72
|
+
cycle?: Record<string, number>;
|
|
73
|
+
easing?: Record<string, readonly number[]>;
|
|
74
|
+
spring?: Record<string, {
|
|
75
|
+
damping?: number;
|
|
76
|
+
stiffness?: number;
|
|
77
|
+
mass?: number;
|
|
78
|
+
}>;
|
|
79
|
+
feedback?: {
|
|
80
|
+
press?: {
|
|
81
|
+
scale?: number;
|
|
82
|
+
};
|
|
83
|
+
shake?: {
|
|
84
|
+
amplitude?: number;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
loop?: {
|
|
88
|
+
pulse?: {
|
|
89
|
+
from?: number;
|
|
90
|
+
to?: number;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
};
|
|
64
94
|
[key: string]: unknown;
|
|
65
95
|
};
|
|
66
96
|
/** The subset of ThemeProvider props this helper produces. */
|
|
67
|
-
export type CastThemeProps = Pick<ThemeProviderProps, 'colorMode' | 'colors' | 'scheme'>;
|
|
97
|
+
export type CastThemeProps = Pick<ThemeProviderProps, 'colorMode' | 'colors' | 'scheme' | 'motion'>;
|
|
68
98
|
/**
|
|
69
99
|
* Build ThemeProvider props from a cast-theme file for a given colour mode.
|
|
70
100
|
*
|
|
@@ -33,6 +33,77 @@
|
|
|
33
33
|
*/
|
|
34
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
35
|
exports.applyCastTheme = applyCastTheme;
|
|
36
|
+
const EASING_NAMES = ['standard', 'entrance', 'exit', 'emphasized', 'linear'];
|
|
37
|
+
const DURATION_KEYS = ['instant', 'fast', 'base', 'slow'];
|
|
38
|
+
const CYCLE_KEYS = ['pulse', 'spin', 'sweep'];
|
|
39
|
+
function pickNumbers(source, keys) {
|
|
40
|
+
if (!source)
|
|
41
|
+
return undefined;
|
|
42
|
+
const out = {};
|
|
43
|
+
for (const key of keys) {
|
|
44
|
+
const value = source[key];
|
|
45
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
46
|
+
out[key] = value;
|
|
47
|
+
}
|
|
48
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
49
|
+
}
|
|
50
|
+
/** Map the file's `motion` block onto ThemeProvider motion overrides. */
|
|
51
|
+
function mapMotion(fileMotion) {
|
|
52
|
+
if (!fileMotion || typeof fileMotion !== 'object')
|
|
53
|
+
return undefined;
|
|
54
|
+
const out = {};
|
|
55
|
+
const durations = pickNumbers(fileMotion.duration, DURATION_KEYS);
|
|
56
|
+
if (durations)
|
|
57
|
+
out.duration = durations;
|
|
58
|
+
const cycles = pickNumbers(fileMotion.cycle, CYCLE_KEYS);
|
|
59
|
+
if (cycles)
|
|
60
|
+
out.cycle = cycles;
|
|
61
|
+
if (fileMotion.easing) {
|
|
62
|
+
const beziers = {};
|
|
63
|
+
for (const name of EASING_NAMES) {
|
|
64
|
+
const pts = fileMotion.easing[name];
|
|
65
|
+
if (Array.isArray(pts) &&
|
|
66
|
+
pts.length === 4 &&
|
|
67
|
+
pts.every((n) => typeof n === 'number' && Number.isFinite(n))) {
|
|
68
|
+
beziers[name] = [pts[0], pts[1], pts[2], pts[3]];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (Object.keys(beziers).length > 0)
|
|
72
|
+
out.easingBezier = beziers;
|
|
73
|
+
}
|
|
74
|
+
const overlay = fileMotion.spring?.overlay;
|
|
75
|
+
if (overlay && typeof overlay === 'object') {
|
|
76
|
+
const springOut = {};
|
|
77
|
+
if (typeof overlay.damping === 'number')
|
|
78
|
+
springOut.damping = overlay.damping;
|
|
79
|
+
if (typeof overlay.stiffness === 'number')
|
|
80
|
+
springOut.stiffness = overlay.stiffness;
|
|
81
|
+
if (typeof overlay.mass === 'number')
|
|
82
|
+
springOut.mass = overlay.mass;
|
|
83
|
+
if (Object.keys(springOut).length > 0)
|
|
84
|
+
out.spring = { overlay: springOut };
|
|
85
|
+
}
|
|
86
|
+
const press = fileMotion.feedback?.press;
|
|
87
|
+
const shake = fileMotion.feedback?.shake;
|
|
88
|
+
const fb = {};
|
|
89
|
+
if (press && typeof press.scale === 'number')
|
|
90
|
+
fb.press = { scale: press.scale };
|
|
91
|
+
if (shake && typeof shake.amplitude === 'number')
|
|
92
|
+
fb.shake = { amplitude: shake.amplitude };
|
|
93
|
+
if (Object.keys(fb).length > 0)
|
|
94
|
+
out.feedback = fb;
|
|
95
|
+
const pulse = fileMotion.loop?.pulse;
|
|
96
|
+
if (pulse && typeof pulse === 'object') {
|
|
97
|
+
const pulseOut = {};
|
|
98
|
+
if (typeof pulse.from === 'number')
|
|
99
|
+
pulseOut.from = pulse.from;
|
|
100
|
+
if (typeof pulse.to === 'number')
|
|
101
|
+
pulseOut.to = pulse.to;
|
|
102
|
+
if (Object.keys(pulseOut).length > 0)
|
|
103
|
+
out.loop = { pulse: pulseOut };
|
|
104
|
+
}
|
|
105
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
106
|
+
}
|
|
36
107
|
/** Map the file's `text` block onto the scheme's `text` slots (matching keys only). */
|
|
37
108
|
function mapText(fileText) {
|
|
38
109
|
if (!fileText)
|
|
@@ -91,5 +162,6 @@ function applyCastTheme(theme, mode = 'light') {
|
|
|
91
162
|
colorMode: mode,
|
|
92
163
|
colors: intents,
|
|
93
164
|
scheme: Object.keys(schemeOverride).length > 0 ? schemeOverride : undefined,
|
|
165
|
+
motion: mapMotion(theme?.motion),
|
|
94
166
|
};
|
|
95
167
|
}
|