@momo-kits/foundation 0.157.1-beta.2 → 0.157.1-beta.5-viewboundary-test

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.
@@ -111,7 +111,7 @@ const HeaderToolkitAction: React.FC<any> = ({
111
111
  undefined,
112
112
  );
113
113
  } else {
114
- navigator?.maxApi?.dispatchFunction?.('goHome');
114
+ navigator?.maxApi?.dispatchFunction?.('dismissAll');
115
115
  }
116
116
  };
117
117
 
@@ -1,11 +1,5 @@
1
- import React, { FC, useContext, useEffect } from 'react';
1
+ import React, { FC, useContext } from 'react';
2
2
  import { TouchableOpacity, View } from 'react-native';
3
- import Animated, {
4
- interpolateColor,
5
- useAnimatedStyle,
6
- useSharedValue,
7
- withSpring,
8
- } from 'react-native-reanimated';
9
3
  import { CheckBoxProps } from './types';
10
4
  import styles from './styles';
11
5
  import { useComponentId } from '../Application';
@@ -16,7 +10,6 @@ import {
16
10
  } from '../Context';
17
11
  import { Text } from '../Text';
18
12
  import { Icon } from '../Icon';
19
- import { getSpringConfig } from '../Animation';
20
13
 
21
14
  const CheckBox: FC<CheckBoxProps> = ({
22
15
  value,
@@ -26,7 +19,6 @@ const CheckBox: FC<CheckBoxProps> = ({
26
19
  label,
27
20
  indeterminate,
28
21
  params,
29
- animationSpec,
30
22
  accessibilityState,
31
23
  ...props
32
24
  }) => {
@@ -34,6 +26,8 @@ const CheckBox: FC<CheckBoxProps> = ({
34
26
  const context = useContext<any>(MiniAppContext);
35
27
  const haveValue = value || indeterminate;
36
28
  const iconSource = indeterminate ? 'ic_minus' : 'ic_checked';
29
+ let borderColor = theme.colors.text.default;
30
+ let backgroundColor = 'transparent';
37
31
  const componentName = 'CheckBox';
38
32
  const { componentId } = useComponentId(
39
33
  `${componentName}${label ? `/${label}` : ''}`,
@@ -42,47 +36,19 @@ const CheckBox: FC<CheckBoxProps> = ({
42
36
 
43
37
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
44
38
 
45
- const progress = useSharedValue(haveValue ? 1 : 0);
46
- const springConfig = animationSpec ?? getSpringConfig('expressive', 'fast');
39
+ if (haveValue) {
40
+ borderColor = theme.colors.primary;
41
+ backgroundColor = theme.colors.primary;
42
+ }
47
43
 
48
- useEffect(() => {
49
- progress.value = withSpring(haveValue ? 1 : 0, springConfig);
50
- }, [haveValue, springConfig.stiffness, springConfig.damping]);
51
-
52
- const boxAnimatedStyle = useAnimatedStyle(() => {
53
- const borderColor = disabled
54
- ? interpolateColor(
55
- progress.value,
56
- [0, 1],
57
- [theme.colors.border.disable, theme.colors.background.tonal],
58
- )
59
- : interpolateColor(
60
- progress.value,
61
- [0, 1],
62
- [theme.colors.text.default, theme.colors.primary],
63
- );
64
- const backgroundColor = disabled
65
- ? interpolateColor(
66
- progress.value,
67
- [0, 1],
68
- ['transparent', theme.colors.background.tonal],
69
- )
70
- : interpolateColor(
71
- progress.value,
72
- [0, 1],
73
- ['transparent', theme.colors.primary],
74
- );
75
- return {
76
- borderColor,
77
- backgroundColor,
78
- borderWidth: 1,
79
- };
80
- });
81
-
82
- const iconAnimatedStyle = useAnimatedStyle(() => ({
83
- opacity: progress.value,
84
- transform: [{ scale: progress.value }],
85
- }));
44
+ if (disabled) {
45
+ borderColor = theme.colors.border.disable;
46
+ backgroundColor = 'transparent';
47
+ if (haveValue) {
48
+ borderColor = theme.colors.background.tonal;
49
+ backgroundColor = theme.colors.background.tonal;
50
+ }
51
+ }
86
52
 
87
53
  return (
88
54
  <ComponentContext.Provider
@@ -111,17 +77,20 @@ const CheckBox: FC<CheckBoxProps> = ({
111
77
  showBaseLineDebug && styles.debugBaseLine,
112
78
  ]}
113
79
  >
114
- <Animated.View style={[styles.checkbox, boxAnimatedStyle]}>
80
+ <View
81
+ style={[
82
+ { borderColor, backgroundColor, borderWidth: 1 },
83
+ styles.checkbox,
84
+ ]}
85
+ >
115
86
  {haveValue && (
116
- <Animated.View style={iconAnimatedStyle} pointerEvents="none">
117
- <Icon
118
- color={theme.colors.background.surface}
119
- size={20}
120
- source={iconSource}
121
- />
122
- </Animated.View>
87
+ <Icon
88
+ color={theme.colors.background.surface}
89
+ size={20}
90
+ source={iconSource}
91
+ />
123
92
  )}
124
- </Animated.View>
93
+ </View>
125
94
  {!!label && (
126
95
  <Text typography={'body_default_regular'} style={styles.label}>
127
96
  {label}
package/CheckBox/types.ts CHANGED
@@ -34,9 +34,4 @@ export interface CheckBoxProps extends TouchableOpacityProps {
34
34
  * Optional. Params auto tracking component.
35
35
  */
36
36
  params?: any;
37
-
38
- /**
39
- * Optional. Spring config for check animation.
40
- */
41
- animationSpec?: import('../Animation').SpringConfig;
42
37
  }
@@ -1,57 +1,12 @@
1
- import React, { forwardRef, useCallback } from 'react';
1
+ import React from 'react';
2
2
  import { FlashList, FlashListProps } from '@shopify/flash-list';
3
- import Animated from 'react-native-reanimated';
4
3
  import { FlatListProps } from 'react-native';
5
- import type { FoundationListProps, FoundationListRef } from './types';
6
4
 
7
- type CombinedListProps<ItemT> = Omit<FoundationListProps, 'renderItem'> &
8
- FlatListProps<ItemT> &
9
- Partial<FlashListProps<ItemT>> & {
10
- renderItem?: FlashListProps<ItemT>['renderItem'];
11
- };
5
+ type CombinedListProps<ItemT> = FlatListProps<ItemT> &
6
+ Partial<FlashListProps<ItemT>>;
12
7
 
13
- const FoundationListInner = <ItemT,>(
14
- props: CombinedListProps<ItemT>,
15
- ref: React.Ref<FoundationListRef>
16
- ) => {
17
- const { animationConfig, renderItem: renderItemProp, ...rest } = props;
18
-
19
- const renderItem = useCallback(
20
- (args: Parameters<NonNullable<typeof renderItemProp>>[0]) => {
21
- const content = renderItemProp?.(args);
22
- if (!content) return null;
23
-
24
- if (!animationConfig?.entering && !animationConfig?.exiting) {
25
- return content;
26
- }
27
-
28
- const entering =
29
- typeof animationConfig.entering === 'function'
30
- ? animationConfig.entering(args.index)
31
- : animationConfig.entering;
32
-
33
- return (
34
- <Animated.View
35
- entering={entering as any}
36
- exiting={animationConfig.exiting as any}
37
- >
38
- {content}
39
- </Animated.View>
40
- );
41
- },
42
- [renderItemProp, animationConfig]
43
- );
44
-
45
- const finalRenderItem =
46
- animationConfig && renderItemProp ? renderItem : renderItemProp;
47
-
48
- return (
49
- <FlashList ref={ref} {...rest} renderItem={finalRenderItem} />
50
- );
8
+ const FoundationList = <ItemT,>(props: CombinedListProps<ItemT>) => {
9
+ return <FlashList {...props} />;
51
10
  };
52
11
 
53
- const FoundationList = forwardRef(FoundationListInner) as <ItemT>(
54
- props: CombinedListProps<ItemT> & { ref?: React.Ref<FoundationListRef> }
55
- ) => React.ReactElement;
56
-
57
12
  export { FoundationList };
@@ -1,26 +1,6 @@
1
1
  import { FlashListProps, FlashListRef } from '@shopify/flash-list';
2
2
 
3
- /**
4
- * Animation builder for list item enter/exit.
5
- * Use Reanimated's FadeInUp, FadeIn, FadeOut, etc.
6
- */
7
- export type ListItemAnimationBuilder =
8
- | { duration: (ms: number) => any; delay: (ms: number) => any }
9
- | ((index: number) => any);
10
-
11
- interface FoundationListProps extends FlashListProps<any> {
12
- /**
13
- * Optional. Animation config for list items.
14
- * - entering: Animation when item appears (e.g. FadeInUp.duration(300).delay(index * 40))
15
- * - exiting: Animation when item is removed
16
- * Note: Call listRef.current?.prepareForLayoutAnimationRender() before inserting/removing items.
17
- * Layout transitions (reorder) are not supported by FlashList.
18
- */
19
- animationConfig?: {
20
- entering?: ListItemAnimationBuilder;
21
- exiting?: ListItemAnimationBuilder;
22
- };
23
- }
3
+ interface FoundationListProps extends FlashListProps<any> {}
24
4
 
25
5
  interface FoundationListRef extends FlashListRef<any> {}
26
6
 
@@ -1,15 +1,9 @@
1
1
  import React, { useContext } from 'react';
2
2
  import {
3
- GestureResponderEvent,
4
3
  StyleSheet,
5
4
  TouchableOpacity,
6
5
  TouchableOpacityProps,
7
6
  } from 'react-native';
8
- import Animated, {
9
- useAnimatedStyle,
10
- useSharedValue,
11
- withTiming,
12
- } from 'react-native-reanimated';
13
7
  import {
14
8
  ApplicationContext,
15
9
  ComponentContext,
@@ -47,30 +41,13 @@ const IconButton: React.FC<IconButtonProps> = ({
47
41
  icon,
48
42
  size,
49
43
  params,
50
- onPressIn,
51
- onPressOut,
52
44
  ...rest
53
45
  }) => {
54
46
  const { theme } = useContext(ApplicationContext);
55
47
  const context = useContext<any>(MiniAppContext);
56
- const pressAnim = useSharedValue(0);
57
48
 
58
49
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
59
50
 
60
- const handlePressIn = (e: GestureResponderEvent) => {
61
- onPressIn?.(e);
62
- pressAnim.value = withTiming(1, { duration: 100 });
63
- };
64
-
65
- const handlePressOut = (e: GestureResponderEvent) => {
66
- onPressOut?.(e);
67
- pressAnim.value = withTiming(0, { duration: 100 });
68
- };
69
-
70
- const animatedStyle = useAnimatedStyle(() => ({
71
- transform: [{ scale: 1 - pressAnim.value * 0.1 }],
72
- }));
73
-
74
51
  /**
75
52
  * get size icon button
76
53
  */
@@ -156,13 +133,9 @@ const IconButton: React.FC<IconButtonProps> = ({
156
133
  {...rest}
157
134
  disabled={type === 'disabled'}
158
135
  activeOpacity={activeOpacity}
159
- onPressIn={handlePressIn}
160
- onPressOut={handlePressOut}
161
136
  style={[buttonStyle, showBaseLineDebug && styles.debugBaseLine]}
162
137
  >
163
- <Animated.View style={animatedStyle}>
164
- <Icon size={iconSize} source={icon} color={getIconColor()} />
165
- </Animated.View>
138
+ <Icon size={iconSize} source={icon} color={getIconColor()} />
166
139
  </TouchableOpacity>
167
140
  </ComponentContext.Provider>
168
141
  );
package/Radio/index.tsx CHANGED
@@ -1,12 +1,6 @@
1
- import React, { FC, useContext, useEffect } from 'react';
2
- import { TouchableOpacity } from 'react-native';
3
- import Animated, {
4
- interpolate,
5
- interpolateColor,
6
- useAnimatedStyle,
7
- useSharedValue,
8
- withSpring,
9
- } from 'react-native-reanimated';
1
+ import React, { FC, useContext } from 'react';
2
+ import { TouchableOpacity, View } from 'react-native';
3
+
10
4
  import { RadioProps } from './types';
11
5
  import { Text } from '../Text';
12
6
  import styles from './styles';
@@ -17,7 +11,6 @@ import {
17
11
  MiniAppContext,
18
12
  } from '../Context';
19
13
  import { Spacing } from '../Consts';
20
- import { getSpringConfig } from '../Animation';
21
14
 
22
15
  const Radio: FC<RadioProps> = ({
23
16
  value,
@@ -27,7 +20,6 @@ const Radio: FC<RadioProps> = ({
27
20
  label,
28
21
  style,
29
22
  params,
30
- animationSpec,
31
23
  accessibilityState,
32
24
  accessibilityLabel,
33
25
  ...props
@@ -42,33 +34,25 @@ const Radio: FC<RadioProps> = ({
42
34
  );
43
35
 
44
36
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
37
+ let disabledStyle = {};
38
+ let checkBoxStyle = {
39
+ borderWidth: 2,
40
+ borderColor: theme.colors.text.default,
41
+ };
45
42
 
46
- const progress = useSharedValue(selected ? 1 : 0);
47
- const springConfig = animationSpec ?? getSpringConfig('expressive', 'fast');
48
-
49
- useEffect(() => {
50
- progress.value = withSpring(selected ? 1 : 0, springConfig);
51
- }, [selected, springConfig.stiffness, springConfig.damping]);
52
-
53
- const radioAnimatedStyle = useAnimatedStyle(() => {
54
- const borderWidth = interpolate(progress.value, [0, 1], [2, 6]);
55
- const borderColor = disabled
56
- ? interpolateColor(
57
- progress.value,
58
- [0, 1],
59
- [theme.colors.text.disable, theme.colors.background.tonal],
60
- )
61
- : interpolateColor(
62
- progress.value,
63
- [0, 1],
64
- [theme.colors.text.default, theme.colors.primary],
65
- );
66
- return {
67
- borderWidth,
68
- borderColor,
69
- marginRight: label ? Spacing.S : 0,
43
+ if (selected) {
44
+ checkBoxStyle = {
45
+ borderWidth: 6,
46
+ borderColor: theme.colors.primary,
47
+ };
48
+ }
49
+ if (disabled) {
50
+ disabledStyle = {
51
+ borderColor: selected
52
+ ? theme.colors.background.tonal
53
+ : theme.colors.text.disable,
70
54
  };
71
- });
55
+ }
72
56
 
73
57
  return (
74
58
  <ComponentContext.Provider
@@ -99,7 +83,14 @@ const Radio: FC<RadioProps> = ({
99
83
  }}
100
84
  accessibilityLabel={componentId}
101
85
  >
102
- <Animated.View style={[styles.radio, radioAnimatedStyle]} />
86
+ <View
87
+ style={[
88
+ styles.radio,
89
+ checkBoxStyle,
90
+ disabledStyle,
91
+ { marginRight: label ? Spacing.S : 0 },
92
+ ]}
93
+ />
103
94
  {!!label && (
104
95
  <Text
105
96
  typography={'body_default_regular'}
package/Radio/types.ts CHANGED
@@ -28,9 +28,4 @@ export interface RadioProps extends TouchableOpacityProps {
28
28
  * Optional. Params auto tracking component.
29
29
  */
30
30
  params?: any;
31
-
32
- /**
33
- * Optional. Spring config for selection animation.
34
- */
35
- animationSpec?: import('../Animation').SpringConfig;
36
31
  }
package/Switch/index.tsx CHANGED
@@ -1,22 +1,10 @@
1
- import React, { FC, useContext, useEffect } from 'react';
1
+ import React, { FC, useContext } from 'react';
2
2
  import { TouchableOpacity, View } from 'react-native';
3
- import Animated, {
4
- interpolateColor,
5
- useAnimatedStyle,
6
- useSharedValue,
7
- withSpring,
8
- } from 'react-native-reanimated';
9
3
  import { SwitchProps } from './types';
10
4
  import styles from './styles';
11
5
  import { Colors } from '../Consts';
12
6
  import { useComponentId } from '../Application';
13
- import {
14
- ComponentContext,
15
- MiniAppContext,
16
- } from '../Context';
17
- import { getSpringConfig } from '../Animation';
18
-
19
- const THUMB_OFFSET = 16;
7
+ import { ComponentContext, MiniAppContext } from '../Context';
20
8
 
21
9
  const Switch: FC<SwitchProps> = ({
22
10
  value = false,
@@ -24,52 +12,22 @@ const Switch: FC<SwitchProps> = ({
24
12
  disabled = false,
25
13
  style,
26
14
  params,
27
- animationSpec,
28
15
  accessibilityLabel,
29
16
  ...props
30
17
  }) => {
31
18
  const context = useContext<any>(MiniAppContext);
19
+ const circleBackgroundColor = value ? Colors.black_01 : Colors.black_03;
20
+ const circleAlign = value ? 'flex-end' : 'flex-start';
32
21
  const componentName = 'Switch';
33
22
 
34
23
  const { componentId } = useComponentId(componentName, accessibilityLabel);
35
24
 
36
25
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
37
26
 
38
- const progress = useSharedValue(value ? 1 : 0);
39
-
40
- const springConfig = animationSpec ?? getSpringConfig('expressive', 'fast');
41
-
42
- useEffect(() => {
43
- progress.value = withSpring(value ? 1 : 0, springConfig);
44
- }, [value, springConfig.stiffness, springConfig.damping]);
45
-
46
- const trackAnimatedStyle = useAnimatedStyle(() => {
47
- const trackColor = disabled
48
- ? interpolateColor(
49
- progress.value,
50
- [0, 1],
51
- [Colors.black_05, Colors.green_09]
52
- )
53
- : interpolateColor(
54
- progress.value,
55
- [0, 1],
56
- [Colors.black_07, Colors.green_03]
57
- );
58
- return { backgroundColor: trackColor };
59
- });
60
-
61
- const thumbAnimatedStyle = useAnimatedStyle(() => ({
62
- transform: [{ translateX: progress.value * THUMB_OFFSET }],
63
- }));
64
-
65
- const circleBgColor = value ? Colors.black_01 : Colors.black_03;
66
- const innerDotColor = disabled
67
- ? value
68
- ? Colors.green_09
69
- : Colors.black_05
70
- : value
71
- ? Colors.green_03
72
- : Colors.black_07;
27
+ let backgroundColor = value ? Colors.green_03 : Colors.black_07;
28
+ if (disabled) {
29
+ backgroundColor = value ? Colors.green_09 : Colors.black_05;
30
+ }
73
31
 
74
32
  return (
75
33
  <ComponentContext.Provider
@@ -93,23 +51,15 @@ const Switch: FC<SwitchProps> = ({
93
51
  style={[
94
52
  style,
95
53
  styles.container,
96
- // { circleBgColor},
54
+ { backgroundColor, alignItems: circleAlign },
97
55
  showBaseLineDebug && styles.debugBaseLine,
98
56
  ]}
99
57
  >
100
- <Animated.View style={[styles.track, trackAnimatedStyle]}>
101
- <Animated.View
102
- style={[
103
- styles.circle,
104
- { backgroundColor: circleBgColor },
105
- thumbAnimatedStyle,
106
- ]}
107
- >
108
- <View
109
- style={[styles.circleSmall, { backgroundColor: innerDotColor }]}
110
- />
111
- </Animated.View>
112
- </Animated.View>
58
+ <View
59
+ style={[styles.circle, { backgroundColor: circleBackgroundColor }]}
60
+ >
61
+ <View style={[styles.circleSmall, { backgroundColor }]} />
62
+ </View>
113
63
  </TouchableOpacity>
114
64
  </ComponentContext.Provider>
115
65
  );
package/Switch/styles.ts CHANGED
@@ -8,13 +8,6 @@ export default StyleSheet.create({
8
8
  borderRadius: 20,
9
9
  justifyContent: 'center',
10
10
  paddingHorizontal: 4,
11
- // overflow: 'hidden',
12
- },
13
- track: {
14
- width: 30,
15
- height: 24,
16
- borderRadius: 12,
17
- justifyContent: 'center',
18
11
  },
19
12
  circle: {
20
13
  width: 14,
package/Switch/types.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import {TouchableOpacityProps} from 'react-native';
2
- import type {SpringConfig} from '../Animation';
3
2
 
4
3
  export interface SwitchProps extends TouchableOpacityProps {
5
4
  /**
@@ -17,9 +16,4 @@ export interface SwitchProps extends TouchableOpacityProps {
17
16
  * Optional. Params auto tracking component.
18
17
  */
19
18
  params?: any;
20
-
21
- /**
22
- * Optional. Spring config for thumb/track animation. Uses default tokens when not provided.
23
- */
24
- animationSpec?: SpringConfig;
25
19
  }
@@ -0,0 +1,64 @@
1
+ import React from 'react';
2
+ import { NativeModules, ViewStyle, StyleProp } from 'react-native';
3
+ import { MiniAppContext } from '../Context';
4
+
5
+ export interface ViewBoundaryProps {
6
+ children: React.ReactNode;
7
+ fallback?: React.ReactNode;
8
+ fallbackStyle?: StyleProp<ViewStyle>;
9
+ onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
10
+ }
11
+
12
+ interface ViewBoundaryState {
13
+ hasError: boolean;
14
+ error: Error | null;
15
+ }
16
+
17
+ class ViewBoundary extends React.Component<ViewBoundaryProps, ViewBoundaryState> {
18
+ static contextType = MiniAppContext;
19
+
20
+ constructor(props: ViewBoundaryProps) {
21
+ super(props);
22
+ this.state = { hasError: false, error: null };
23
+ }
24
+
25
+ static getDerivedStateFromError(error: Error): ViewBoundaryState {
26
+ return { hasError: true, error };
27
+ }
28
+
29
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
30
+ this.props.onError?.(error, errorInfo);
31
+
32
+ const context = (this as any).context ?? {};
33
+
34
+ try {
35
+ NativeModules.MiniAppModule?.onJSWidgetCrash({
36
+ hostId: context.hostId ?? '',
37
+ appId: context.appId ?? '',
38
+ code: context.code ?? '',
39
+ buildNumber: context.buildNumber ?? '',
40
+ tag: 'ViewBoundary',
41
+ error: {
42
+ errorName: error.name,
43
+ errorMessage: error.message,
44
+ componentStack: errorInfo?.componentStack?.substring(0, 500) ?? '',
45
+ },
46
+ });
47
+ console.info('[ViewBoundary] onJSWidgetCrash success!');
48
+ } catch (_e) {
49
+ console.warn('[ViewBoundary] onJSWidgetCrash failed:', _e);
50
+ }
51
+ }
52
+
53
+ render() {
54
+ if (this.state.hasError) {
55
+ if (this.props.fallback !== undefined) {
56
+ return this.props.fallback;
57
+ }
58
+ return null;
59
+ }
60
+ return this.props.children;
61
+ }
62
+ }
63
+
64
+ export { ViewBoundary };
package/index.ts CHANGED
@@ -43,9 +43,9 @@ export * from './Loader';
43
43
  export * from './Loader/types';
44
44
  export * from './Title';
45
45
  export * from './Title/types';
46
- export * from './Animation';
47
46
  export * from './Badge';
48
47
  export * from './Badge/types';
49
48
  export * from './FoundationList';
50
49
  export * from './FoundationList/types';
50
+ export * from './ViewBoundary';
51
51
  export { TouchableOpacity };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.157.1-beta.2",
3
+ "version": "0.157.1-beta.5-viewboundary-test",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},
@@ -1 +0,0 @@
1
- export * from './tokens';
@@ -1,117 +0,0 @@
1
- /**
2
- * Animation tokens inspired by M3 Material Motion and Motion.dev
3
- * Spring/timing configs for consistent animation across components
4
- */
5
-
6
- export type SpringConfig = {
7
- stiffness: number;
8
- damping: number;
9
- mass?: number;
10
- };
11
-
12
- export type TimingConfig = {
13
- duration: number;
14
- easing?: 'ease' | 'easeIn' | 'easeOut' | 'easeInOut' | 'linear';
15
- };
16
-
17
- export type MotionScheme = 'expressive' | 'standard';
18
-
19
- /**
20
- * M3 Expressive - overshoots final values, adds bounce
21
- * Use for hero moments and key interactions
22
- */
23
- export const EXPRESSIVE_FAST: SpringConfig = {
24
- stiffness: 400,
25
- damping: 15,
26
- mass: 1,
27
- };
28
-
29
- export const EXPRESSIVE_DEFAULT: SpringConfig = {
30
- stiffness: 300,
31
- damping: 20,
32
- mass: 1,
33
- };
34
-
35
- export const EXPRESSIVE_SLOW: SpringConfig = {
36
- stiffness: 200,
37
- damping: 25,
38
- mass: 1,
39
- };
40
-
41
- /**
42
- * M3 Standard - minimal bounce, utilitarian
43
- * Use for utilitarian products
44
- */
45
- export const STANDARD_FAST: SpringConfig = {
46
- stiffness: 500,
47
- damping: 30,
48
- mass: 1,
49
- };
50
-
51
- export const STANDARD_DEFAULT: SpringConfig = {
52
- stiffness: 400,
53
- damping: 35,
54
- mass: 1,
55
- };
56
-
57
- export const STANDARD_SLOW: SpringConfig = {
58
- stiffness: 300,
59
- damping: 40,
60
- mass: 1,
61
- };
62
-
63
- /**
64
- * Timing config for opacity, color (effects)
65
- * No overshoot for effects
66
- */
67
- export const TIMING_FAST: TimingConfig = {
68
- duration: 150,
69
- easing: 'easeOut',
70
- };
71
-
72
- export const TIMING_DEFAULT: TimingConfig = {
73
- duration: 250,
74
- easing: 'easeInOut',
75
- };
76
-
77
- export const TIMING_SLOW: TimingConfig = {
78
- duration: 400,
79
- easing: 'easeInOut',
80
- };
81
-
82
- /**
83
- * Get spring config by scheme and speed
84
- */
85
- export function getSpringConfig(
86
- scheme: MotionScheme = 'expressive',
87
- speed: 'fast' | 'default' | 'slow' = 'fast'
88
- ): SpringConfig {
89
- if (scheme === 'expressive') {
90
- switch (speed) {
91
- case 'fast':
92
- return { ...EXPRESSIVE_FAST };
93
- case 'slow':
94
- return { ...EXPRESSIVE_SLOW };
95
- default:
96
- return { ...EXPRESSIVE_DEFAULT };
97
- }
98
- }
99
- switch (speed) {
100
- case 'fast':
101
- return { ...STANDARD_FAST };
102
- case 'slow':
103
- return { ...STANDARD_SLOW };
104
- default:
105
- return { ...STANDARD_DEFAULT };
106
- }
107
- }
108
-
109
- /**
110
- * Create custom spring config with overrides
111
- */
112
- export function createSpringConfig(
113
- base: SpringConfig,
114
- overrides?: Partial<SpringConfig>
115
- ): SpringConfig {
116
- return { ...base, ...overrides };
117
- }