diff --git a/example/src/modules/primitives/BadgeScreen.tsx b/example/src/modules/primitives/BadgeScreen.tsx index 5d37970c..1102a9cb 100644 --- a/example/src/modules/primitives/BadgeScreen.tsx +++ b/example/src/modules/primitives/BadgeScreen.tsx @@ -1,14 +1,43 @@ import React from "react"; +// import { +// useAnimatedStyle, +// useSharedValue, +// withSpring, +// } from "react-native-reanimated"; import { + // AdaptAnimatedBox, + // AnimatedBox, BadgeNew, BadgeText, BadgeWrapper, Box, + Button, + SpinnerNew, } from "@adaptui/react-native-tailwind"; export const BadgeScreen = () => { + // const x = useSharedValue(1); + // const animatedStyles = useAnimatedStyle(() => { + // return { + // transform: [ + // { scale: withSpring(x.value) }, + // { translateX: withSpring(x.value * 37) }, + // { translateY: withSpring(x.value * 23) }, + // ], + // }; + // }); return ( + {/* + */} + + Scheduled diff --git a/src/components/index.ts b/src/components/index.ts index f1d53ee9..0866308f 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -14,6 +14,7 @@ export * from "./radio"; export * from "./select"; export * from "./slider"; export * from "./spinner"; +export * from "./spinner-new"; export * from "./switch"; export * from "./tag"; export * from "./tooltip"; diff --git a/src/components/spinner-new/Spinner.tsx b/src/components/spinner-new/Spinner.tsx new file mode 100644 index 00000000..af6dc684 --- /dev/null +++ b/src/components/spinner-new/Spinner.tsx @@ -0,0 +1,129 @@ +import { useEffect } from "react"; +import { + Easing, + useAnimatedStyle, + useSharedValue, + withRepeat, + withTiming, +} from "react-native-reanimated"; + +import { + AnimatedBox, + AnimatedBoxOptions, + useAnimatedBox, +} from "../../primitives/animated-box"; +import { useTheme } from "../../theme"; +import { cx, styleAdapter } from "../../utils"; +import { GetThemeValue } from "../../utils/global-types"; +import { + As, + createComponentType, + createElement, + createHook, + Props, +} from "../../utils/system"; + +export const useSpinner = createHook( + ({ + size = "md", + themeColor = "base", + track = "transparent", + label = "Loading...", + ...props + }) => { + const tailwind = useTheme(); + const spinnerTheme = useTheme("spinner"); + + const spinnerLoopAnimation = useSharedValue(0); + useEffect(() => { + spinnerLoopAnimation.value = withRepeat( + withTiming(360, { + duration: 1000, + easing: Easing.linear, + }), + -1, + false, + ); + }, [spinnerLoopAnimation]); + const spinnerLoadingStyle = useAnimatedStyle(() => { + return { + transform: [ + { + rotate: `${spinnerLoopAnimation.value}deg`, + }, + ], + }; + }); + + const style = [ + tailwind.style( + cx( + spinnerTheme.base, + spinnerTheme.themeColor[themeColor], + track === "visible" + ? spinnerTheme.track[track]?.[themeColor] + : spinnerTheme.track.transparent, + spinnerTheme.size[size], + ), + ), + { borderWidth: spinnerTheme.borderWidth }, + styleAdapter(props.style), + spinnerLoadingStyle, + ]; + + props = { + ...props, + accessibilityLabel: label, + style, + // Web Prop + "data-testid": "testid-spinner", + }; + props = useAnimatedBox(props); + + return props; + }, +); + +export const SpinnerNew = createComponentType(props => { + const htmlProps = useSpinner(props); + + return createElement(AnimatedBox, htmlProps); +}, "Spinner"); + +export type SpinnerOptions = Omit< + AnimatedBoxOptions, + "size" +> & { + /** + * How large should the spinner be? + * + * @default md + */ + size?: keyof GetThemeValue<"spinner", "size">; + + /** + * How the spinner should be themed? + * + * @default base + */ + themeColor?: keyof GetThemeValue<"spinner", "themeColor">; + + /** + * How the spinner should be displayed? + * + * @default transparent + */ + track?: keyof GetThemeValue<"spinner", "track">; + + /** + * For accessibility, it is important to add a fallback loading text. + * This text will be visible to screen readers. + * + * @default Loading... + */ + label?: string; +}; + +export type SpinnerNewProps = Props< + SpinnerOptions +>; diff --git a/src/components/spinner-new/index.ts b/src/components/spinner-new/index.ts new file mode 100644 index 00000000..2f83b969 --- /dev/null +++ b/src/components/spinner-new/index.ts @@ -0,0 +1 @@ +export * from "./Spinner"; diff --git a/src/primitives/animated-box/AdaptAnimatedBox.tsx b/src/primitives/animated-box/AdaptAnimatedBox.tsx new file mode 100644 index 00000000..51eb32b7 --- /dev/null +++ b/src/primitives/animated-box/AdaptAnimatedBox.tsx @@ -0,0 +1,41 @@ +import { useTheme } from "../../theme"; +import { styleAdapter } from "../../utils"; +import { + As, + createComponent, + createElement, + createHook, + Props, +} from "../../utils/system"; +import { useBox } from "../box"; + +import { AnimatedBox, AnimatedBoxOptions, useAnimatedBox } from "./AnimatedBox"; + +export const useAdaptAnimatedBox = createHook( + ({ __TYPE__, className, ...props }) => { + const tailwind = useTheme(); + const style = [tailwind.style(className), styleAdapter(props.style)]; + + props = useAnimatedBox(props); + props = useBox(props); + + return { ...props, style }; + }, +); + +export const AdaptAnimatedBox = createComponent( + props => { + const htmlProps = useAdaptAnimatedBox(props); + + return createElement(AnimatedBox, htmlProps); + }, +); + +export type AdaptAnimatedBoxOptions = + AnimatedBoxOptions & { + className?: string; + }; + +export type AdaptAnimatedBoxProps = Props< + AdaptAnimatedBoxOptions +>; diff --git a/src/primitives/animated-box/AnimatedBox.tsx b/src/primitives/animated-box/AnimatedBox.tsx index 4de00de9..9740b37f 100644 --- a/src/primitives/animated-box/AnimatedBox.tsx +++ b/src/primitives/animated-box/AnimatedBox.tsx @@ -1,9 +1,32 @@ import Animated from "react-native-reanimated"; -import { createComponent } from "../../utils"; +import { + As, + ComponentOptions, + createComponent, + createElement, + createHook, + Props, +} from "../../utils/system"; import { Box } from "../box"; // @ts-ignore const RNAnimatedBox = Animated.createAnimatedComponent(Box); -export const AnimatedBox = createComponent(RNAnimatedBox, { shouldMemo: true }); +export const useAnimatedBox = createHook( + ({ __TYPE__, ...props }) => { + return props; + }, +); + +export const AnimatedBox = createComponent(props => { + const htmlProps = useAnimatedBox(props); + return createElement(RNAnimatedBox, htmlProps); +}); + +export type AnimatedBoxOptions = + ComponentOptions; + +export type AnimatedBoxProps = Props< + AnimatedBoxOptions +>; diff --git a/src/primitives/animated-box/index.ts b/src/primitives/animated-box/index.ts index ec70e066..95d57cb7 100644 --- a/src/primitives/animated-box/index.ts +++ b/src/primitives/animated-box/index.ts @@ -1 +1,2 @@ +export * from "./AdaptAnimatedBox"; export * from "./AnimatedBox"; diff --git a/src/utils/styleAdapter.ts b/src/utils/styleAdapter.ts index efa15486..4ba8c2f1 100644 --- a/src/utils/styleAdapter.ts +++ b/src/utils/styleAdapter.ts @@ -6,13 +6,15 @@ import { StyleSheet, ViewStyle, } from "react-native"; +import Animated from "react-native-reanimated"; import { runIfFn } from "./react"; export const styleAdapter = ( style: | StyleProp - | ((state: PressableStateCallbackType) => StyleProp), + | ((state: PressableStateCallbackType) => StyleProp) + | StyleProp>>, touchState?: PressableStateCallbackType, ): ViewStyle | Falsy | RegisteredStyle => { const _style = touchState ? runIfFn(style, touchState) : style;