Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(spinner): ♻️ move Animated Component with hook implementation #70

Draft
wants to merge 7 commits into
base: ariakit-system
Choose a base branch
from
29 changes: 29 additions & 0 deletions example/src/modules/primitives/BadgeScreen.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Box className="flex-1 justify-center items-center bg-white-900">
{/* <AnimatedBox
className="h-10 w-10 bg-grayDark-200 rounded-md border-red-200"
style={animatedStyles}
/>
<AdaptAnimatedBox
className="h-10 w-10 bg-grayDark-200 rounded-md border-[1.5px] border-red-200"
style={animatedStyles}
/> */}
<Button onPress={() => (x.value = Math.random())}>Animate</Button>
<SpinnerNew track="visible" />
<BadgeNew size="lg" className="my-1" themeColor="secondary">
Scheduled
</BadgeNew>
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
129 changes: 129 additions & 0 deletions src/components/spinner-new/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -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<SpinnerOptions>(
({
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<SpinnerOptions>(props => {
const htmlProps = useSpinner(props);

return createElement(AnimatedBox, htmlProps);
}, "Spinner");

export type SpinnerOptions<T extends As = typeof AnimatedBox> = Omit<
AnimatedBoxOptions<T>,
"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<T extends As = typeof AnimatedBox> = Props<
SpinnerOptions<T>
>;
1 change: 1 addition & 0 deletions src/components/spinner-new/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./Spinner";
41 changes: 41 additions & 0 deletions src/primitives/animated-box/AdaptAnimatedBox.tsx
Original file line number Diff line number Diff line change
@@ -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<AdaptAnimatedBoxOptions>(
({ __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<AdaptAnimatedBoxOptions>(
props => {
const htmlProps = useAdaptAnimatedBox(props);

return createElement(AnimatedBox, htmlProps);
},
);

export type AdaptAnimatedBoxOptions<T extends As = typeof AnimatedBox> =
AnimatedBoxOptions<T> & {
className?: string;
};

export type AdaptAnimatedBoxProps<T extends As = typeof AnimatedBox> = Props<
AdaptAnimatedBoxOptions<T>
>;
27 changes: 25 additions & 2 deletions src/primitives/animated-box/AnimatedBox.tsx
Original file line number Diff line number Diff line change
@@ -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<AnimatedBoxOptions>(
({ __TYPE__, ...props }) => {
return props;
},
);

export const AnimatedBox = createComponent<AnimatedBoxOptions>(props => {
const htmlProps = useAnimatedBox(props);
return createElement(RNAnimatedBox, htmlProps);
});

export type AnimatedBoxOptions<T extends As = typeof RNAnimatedBox> =
ComponentOptions<T>;

export type AnimatedBoxProps<T extends As = typeof RNAnimatedBox> = Props<
AnimatedBoxOptions<T>
>;
1 change: 1 addition & 0 deletions src/primitives/animated-box/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./AdaptAnimatedBox";
export * from "./AnimatedBox";
4 changes: 3 additions & 1 deletion src/utils/styleAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ViewStyle>
| ((state: PressableStateCallbackType) => StyleProp<ViewStyle>),
| ((state: PressableStateCallbackType) => StyleProp<ViewStyle>)
| StyleProp<Animated.AnimateStyle<StyleProp<ViewStyle>>>,
touchState?: PressableStateCallbackType,
): ViewStyle | Falsy | RegisteredStyle<ViewStyle> => {
const _style = touchState ? runIfFn(style, touchState) : style;
Expand Down