diff --git a/apps/docs/components/CodeDemo.tsx b/apps/docs/components/CodeDemo.tsx index 8c519d6d..0ac1f700 100644 --- a/apps/docs/components/CodeDemo.tsx +++ b/apps/docs/components/CodeDemo.tsx @@ -121,15 +121,16 @@ export const CodeDemo = ({ )} {code.length > 0 && ( - + + + )} diff --git a/apps/docs/components/Tabs.tsx b/apps/docs/components/Tabs.tsx index f22e874d..69fe31af 100644 --- a/apps/docs/components/Tabs.tsx +++ b/apps/docs/components/Tabs.tsx @@ -17,7 +17,7 @@ export const Tabs = withStaticProperties( const time = setTimeout(() => { const [anchor] = asPath.match(/#(.*)$/g) || []; if (anchor) { - let target = document.querySelector(`${anchor}`); + const target = document.querySelector(`${anchor}`); if (!target) return; @@ -32,7 +32,7 @@ export const Tabs = withStaticProperties( }, 200); return () => clearTimeout(time); } - }, [isReady]); + }, [isReady, asPath]); return ( { + if (!config.plugins) { + config.plugins = []; + } + config.plugins.push( + new webpack.DefinePlugin({ + __DEV__: dev, + }) + ); + // if (isServer) { + // config.plugins.push( + // new webpack.ProvidePlugin({ + // requestAnimationFrame: path.resolve( + // __dirname, + // './node_modules/raf/polyfill.js' + // ), + // }) + // ); + // } + return config; + }, }) ); diff --git a/apps/docs/package.json b/apps/docs/package.json index e108d09c..000139ed 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -27,6 +27,7 @@ "nextra": "latest", "nextra-theme-docs": "latest", "postcss": "^8.4.27", + "raf": "^3.4.1", "react": "^18.2.0", "react-code-blocks": "^0.1.3", "react-dom": "^18.2.0", @@ -36,6 +37,7 @@ }, "devDependencies": { "@types/node": "18.11.10", + "babel-preset-react-native": "^4.0.1", "typescript": "^5.1.6" } } diff --git a/apps/docs/pages/ui/_meta.json b/apps/docs/pages/ui/_meta.json index 959b1151..1a33a83e 100644 --- a/apps/docs/pages/ui/_meta.json +++ b/apps/docs/pages/ui/_meta.json @@ -2,6 +2,9 @@ "layout": { "title": "Layout" }, + "animations": { + "title": "Animations" + }, "typography": { "title": "Typography" }, diff --git a/apps/docs/pages/ui/animations/animate.mdx b/apps/docs/pages/ui/animations/animate.mdx new file mode 100644 index 00000000..4d4f6686 --- /dev/null +++ b/apps/docs/pages/ui/animations/animate.mdx @@ -0,0 +1,26 @@ +import { AnimateDemo } from '@mergeui/demo'; +import { CodeDemo } from 'components/CodeDemo'; +import { getStaticDemo } from 'components/getStaticDemo'; +import { Tabs } from 'components/Tabs'; +import { Table } from 'components/Table'; + +export const getStaticProps = getStaticDemo('ui/animations/Animate'); + +# Animate + +Use moti js + + + + + + + ```tsx + + ``` + + + ## Animate + + + diff --git a/apps/docs/pages/ui/forms/switch.mdx b/apps/docs/pages/ui/forms/switch.mdx index ca605642..6d8f73a9 100644 --- a/apps/docs/pages/ui/forms/switch.mdx +++ b/apps/docs/pages/ui/forms/switch.mdx @@ -1,12 +1,16 @@ import { CodeDemo } from 'components/CodeDemo'; import { Tabs } from 'components/Tabs'; -import { Table, Tr, Td, Th } from 'nextra/components'; +import { getStaticDemo } from 'components/getStaticDemo'; +import { SwitchDemo } from '@mergeui/demo'; +import { Table } from 'components/Table'; + +export const getStaticProps = getStaticDemo("ui/forms/Switch") # Switch - + ## Simple @@ -26,41 +30,7 @@ import { Table, Tr, Td, Th } from 'nextra/components'; ## Button -
- - - - - - - - - - - - - - - -
PropsdefaultDescription
disabledfalseDescription
loadingfalsedsfsd dsfds dsfdsd dsfsd dsfds dsfdsd dsfsd dsfds dsfdsd dsfsd dsfds dsfdsd dsfsd dsfds dsfdsd
- -## Button.Text + -
- - - - - -
PropsdefaultDescription
- ## Button.Icon - - - - - - -
PropsdefaultDescription
-
diff --git a/packages/core/package.json b/packages/core/package.json index 602d0633..4b8800e0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -40,31 +40,24 @@ "@types/react-native": "^0.72.2", "babel-plugin-transform-remove-console": "^6.9.4", "jest": "^29.5.0", + "moti": "^0.26.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-native": "^0.72.3", "react-native-web": "^0.19.7", + "react-native-reanimated": "^3.4.2", "tsconfig": "*" }, "dependencies": { "@mergeui/class-variance-authority": "*", - "inline-style-prefixer": "^6.0.1", - "normalize-css-color": "^1.0.2", "twrnc": "^3.6.3" }, - "react-native-builder-bob": { - "source": "src", - "output": "lib", - "targets": [ - "commonjs", - [ - "module" - ], - "typescript" - ] - }, "files": [ "lib/", "src/" - ] + ], + "peerDependencies": { + "moti": "^0.26.0", + "react-native-reanimated": "^3.4.2" + } } diff --git a/packages/core/src/composeEventHandlers.tsx b/packages/core/src/composeEventHandlers.tsx index b8b18a95..a49ca08b 100644 --- a/packages/core/src/composeEventHandlers.tsx +++ b/packages/core/src/composeEventHandlers.tsx @@ -3,8 +3,8 @@ type Events = Object; export type EventHandler = (event: E) => void; export function composeEventHandlers( - og?: EventHandler | null, - next?: EventHandler | null, + og?: EventHandler | null | false, + next?: EventHandler | null | false, { checkDefaultPrevented = true } = {} ) { if (!og || !next) { diff --git a/packages/core/src/extractPropertyState.ts b/packages/core/src/extractPropertyState.ts new file mode 100644 index 00000000..f07eafed --- /dev/null +++ b/packages/core/src/extractPropertyState.ts @@ -0,0 +1,142 @@ +import type { + Base, + BaseWithState, + ConfigSchema, + ConfigSchemaTheme, + Props, + State, + StateName, +} from './types'; + +export const extractState = >( + themeConfigProps: ConfigSchemaTheme, + variantProps: Props, + keyProperty: keyof Pick, 'animate' | 'className'>, + { + activeTheme, + hoverTheme, + focusTheme, + disabledTheme, + }: { + activeTheme?: Base

; + hoverTheme?: Base

; + focusTheme?: Base

; + disabledTheme?: Base

; + } = {} +) => { + const { variants, compoundVariants = [], defaultVariants } = themeConfigProps; + + const compounedVariant = compoundVariants?.reduce<{ + [key in keyof Required>]: string[]; + }>( + ( + acc, + { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + 'className': cvClassName, + 'animate': _animate, + ':active': _activeCompoundVariant, + ':hover': _hoverCompoundVariant, + ':focus': _focusCompoundVariant, + ':disabled': _disabledCompoundVariant, + ...compoundVariantOptions + } + ) => { + return Object.entries(compoundVariantOptions).every(([key, value]) => + Array.isArray(value) + ? value.includes( + { + ...defaultVariants, + ...variantProps, + }[key] + ) + : { + ...defaultVariants, + ...variantProps, + }[key] === value + ) + ? { + ':active': [ + ...acc[':active'], + ...(_activeCompoundVariant?.[keyProperty] || []), + ], + ':hover': [ + ...acc[':hover'], + ...(_hoverCompoundVariant?.[keyProperty] || []), + ], + ':focus': [ + ...acc[':focus'], + ...(_focusCompoundVariant?.[keyProperty] || []), + ], + ':disabled': [ + ...acc[':disabled'], + ...(_disabledCompoundVariant?.[keyProperty] || []), + ], + } + : acc; + }, + { + ':active': [], + ':hover': [], + ':focus': [], + ':disabled': [], + } + ); + const result = Object.entries(variantProps).reduce<{ + [key in StateName]: string[]; + }>( + (acc, [keyVariant, valueVariant]) => { + if (valueVariant !== undefined && valueVariant !== null) { + const variantList = (variants as T)[keyVariant]; + + const { + ':active': _activeCompoundVariant, + ':hover': _hoverCompoundVariant, + ':focus': _focusCompoundVariant, + ':disabled': _disabledCompoundVariant, + } = compounedVariant; + + const { + ':active': _active, + ':hover': _hover, + ':focus': _focus, + ':disabled': _disabled, + } = (variantList[valueVariant.toString()] || + {}) satisfies BaseWithState

; + + acc.active = [ + ...acc.active, + ...(_active?.[keyProperty] || []), + ...(_activeCompoundVariant || []), + ]; + + acc.hover = [ + ...acc.hover, + ...(_hover?.[keyProperty] || []), + ...(_hoverCompoundVariant || []), + ]; + + acc.focus = [ + ...acc.focus, + ...(_focus?.[keyProperty] || []), + ...(_focusCompoundVariant || []), + ]; + + acc.disabled = [ + ...acc.disabled, + ...(_disabled?.[keyProperty] || []), + ...(_disabledCompoundVariant || []), + ]; + } + return acc; + }, + { + active: activeTheme?.[keyProperty] || [], + hover: hoverTheme?.[keyProperty] || [], + focus: focusTheme?.[keyProperty] || [], + disabled: disabledTheme?.[keyProperty] || [], + } + ); + + return result; +}; diff --git a/packages/core/src/styled.tsx b/packages/core/src/styled.tsx index f0337ab0..716764ee 100644 --- a/packages/core/src/styled.tsx +++ b/packages/core/src/styled.tsx @@ -1,20 +1,19 @@ +import 'raf/polyfill'; import type { ComponentType } from 'react'; -import { forwardRef, useState } from 'react'; +import { forwardRef, useMemo, useState } from 'react'; import { mergeui, merge } from '@mergeui/class-variance-authority'; import type { ConfigSchema as ConfigSchemaCVA } from '@mergeui/class-variance-authority'; import tw from 'twrnc'; import { Platform, StyleSheet } from 'react-native'; import type { ClassValue } from '@mergeui/class-variance-authority/src/types'; -import type { - Base, - BaseWithState, - ConfigSchema, - ConfigSchemaTheme, - Props, - State, - StateName, -} from './types'; +import type { Base, ConfigSchema, ConfigSchemaTheme, Props } from './types'; import { withStaticProperties } from './withStaticProperties'; +import type { MotiPressableProp } from 'moti/interactions'; +import { extractState } from './extractPropertyState'; +import { composeEventHandlers } from './composeEventHandlers'; + +const camelToSnakeCase = (str: string) => + str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`); const cleanForMergeui = >( variantsDeclaration?: ConfigSchemaTheme @@ -40,7 +39,7 @@ const cleanForMergeui = >( const variantListT = Object.entries(variantValues).reduce< Record >((acc2, [keyInVar, value]) => { - acc2[keyInVar] = value.className; + acc2[keyInVar] = value.animate || value.className; return acc2; }, {}); acc[variantKey] = variantListT; @@ -76,7 +75,7 @@ const cleanForMergeui = >( return { base, config }; }; -const extractStateClassName = >( +const useExtractStateClassName = >( themeConfigProps: ConfigSchemaTheme, variantProps: Props, { @@ -91,156 +90,44 @@ const extractStateClassName = >( disabledTheme?: Base

; } = {} ) => { - const { variants, compoundVariants = [], defaultVariants } = themeConfigProps; - - const compounedVariant = compoundVariants?.reduce<{ - [key in keyof Required>]: string[]; - }>( - ( - acc, - { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - 'className': cvClassName, - ':active': _activeCompoundVariant, - ':hover': _hoverCompoundVariant, - ':focus': _focusCompoundVariant, - ':disabled': _disabledCompoundVariant, - ...compoundVariantOptions - } - ) => { - return Object.entries(compoundVariantOptions).every(([key, value]) => - Array.isArray(value) - ? value.includes( - { - ...defaultVariants, - ...variantProps, - }[key] - ) - : { - ...defaultVariants, - ...variantProps, - }[key] === value - ) - ? { - ':active': [ - ...acc[':active'], - ...(_activeCompoundVariant?.className || []), - ], - ':hover': [ - ...acc[':hover'], - ...(_hoverCompoundVariant?.className || []), - ], - ':focus': [ - ...acc[':focus'], - ...(_focusCompoundVariant?.className || []), - ], - ':disabled': [ - ...acc[':disabled'], - ...(_disabledCompoundVariant?.className || []), - ], - } - : acc; - }, - { - ':active': [], - ':hover': [], - ':focus': [], - ':disabled': [], - } - ); - - const result = Object.entries(variantProps).reduce<{ - [key in StateName as `${key}Classname`]: string[]; - }>( - (acc, [keyVariant, valueVariant]) => { - if (valueVariant !== undefined && valueVariant !== null) { - const variantList = (variants as T)[keyVariant]; - - const { - ':active': _activeCompoundVariant, - ':hover': _hoverCompoundVariant, - ':focus': _focusCompoundVariant, - ':disabled': _disabledCompoundVariant, - } = compounedVariant; - - const { - ':active': _active, - ':hover': _hover, - ':focus': _focus, - ':disabled': _disabled, - } = (variantList[valueVariant.toString()] || - {}) satisfies BaseWithState

; - - acc.activeClassname = [ - ...acc.activeClassname, - ...(_active?.className || []), - ...(_activeCompoundVariant || []), - ]; - - acc.hoverClassname = [ - ...acc.hoverClassname, - ...(_hover?.className || []), - ...(_hoverCompoundVariant || []), - ]; - - acc.focusClassname = [ - ...acc.focusClassname, - ...(_focus?.className || []), - ...(_focusCompoundVariant || []), - ]; - - acc.disabledClassname = [ - ...acc.disabledClassname, - ...(_disabled?.className || []), - ...(_disabledCompoundVariant || []), - ]; - } - return acc; - }, - { - activeClassname: !activeTheme?.className - ? [] - : Array.isArray(activeTheme.className) - ? activeTheme.className - : [activeTheme.className], - hoverClassname: !hoverTheme?.className - ? [] - : Array.isArray(hoverTheme.className) - ? hoverTheme.className - : [hoverTheme.className], - focusClassname: !focusTheme?.className - ? [] - : Array.isArray(focusTheme.className) - ? focusTheme.className - : [focusTheme.className], - disabledClassname: !disabledTheme?.className - ? [] - : Array.isArray(disabledTheme.className) - ? disabledTheme.className - : [disabledTheme.className], - } - ); + const { + active: activeClassname, + hover: hoverClassname, + focus: focusClassname, + disabled: disabledClassname, + } = extractState(themeConfigProps, variantProps, 'className', { + activeTheme, + hoverTheme, + focusTheme, + disabledTheme, + }); - return result; + return { + activeClassname, + hoverClassname, + focusClassname, + disabledClassname, + }; }; export const styled = < P extends Record = {}, T extends ConfigSchema

= {} >( - Component: ComponentType

, + Component: ComponentType

& { styles?: any }, themeConfigProps: ConfigSchemaTheme ) => { const { - props, + 'props': propsTheme, ':active': activeTheme, ':focus': focusTheme, ':hover': hoverTheme, ':disabled': disabledTheme, ...themeConfig } = themeConfigProps || {}; - const { as: asTheme, ...propsTheme } = props || {}; + // const { as: asTheme, ...propsTheme } = props || {}; const variants = themeConfig?.variants || {}; + const isMotiComponent = (Component as any).render?.name.startsWith('Moti'); const { base, config } = cleanForMergeui(themeConfigProps); const stylesClass = mergeui(base, config) as (props?: Props) => string; @@ -249,11 +136,12 @@ export const styled = < any, { className?: string; - as?: string | ComponentType; + animations?: boolean; states?: { isActive?: boolean; isFocus?: boolean; isHover?: boolean }; } & P & Props - >(({ as: asProps, className, disabled, states, ...props }, ref) => { + >((props, ref) => { + const { className, disabled, states } = props; const [active, setActive] = useState(false); const [hover, setHover] = useState(false); const [focus, setFocus] = useState(false); @@ -265,7 +153,8 @@ export const styled = < }>( (acc, [propsName, valueProps]) => { if (variantNames.includes(propsName)) { - acc.variantProps[propsName as keyof Props] = valueProps; + acc.variantProps[propsName as keyof Props] = + valueProps?.toString() || undefined; } else { (acc.componentProps as any)[propsName] = valueProps; } @@ -277,105 +166,173 @@ export const styled = < } as any ); - const { as: asVariant, ...propsVariant } = Object.entries( - variantProps - ).reduce((acc, [keyVariant, valueVariant]) => { - if (valueVariant !== undefined && valueVariant !== null) { - const variantList = (variants as any)[keyVariant]; - const classNameTmp = - variantList[valueVariant.toString()]?.className || []; - return { - ...acc, - className: classNameTmp, - }; - } - return acc; - }, {}); - const { activeClassname = [], hoverClassname = [], focusClassname = [], disabledClassname = [], - } = extractStateClassName(themeConfigProps, variantProps, { + } = useExtractStateClassName(themeConfigProps, variantProps, { activeTheme, focusTheme, hoverTheme, disabledTheme, }); - const asPropsOrTheme = asProps || asVariant || asTheme || undefined; - const AsComp = ( - Platform.OS === 'web' ? asPropsOrTheme || Component : Component - ) as any; + const { + active: activeAnimate = [], + hover: hoverAnimate = [], + focus: focusAnimate = [], + disabled: disabledAnimate = [], + } = extractState(themeConfigProps, variantProps, 'animate', { + activeTheme, + hoverTheme, + focusTheme, + disabledTheme, + }); + + const hasAnimate = + [...hoverAnimate, ...activeAnimate, ...disabledAnimate, ...focusAnimate] + .length > 0; - const finalClassName = merge( - AsComp.styles?.({ - ...propsTheme, - ...propsVariant, - ...componentProps, - }), - stylesClass(variantProps as any).split(' '), - className, - ...(states?.isFocus || focus ? focusClassname : []), - ...(states?.isHover || hover ? hoverClassname : []), - ...(states?.isActive || active ? activeClassname : []), - ...(disabled ? disabledClassname : []) - ); + const extendClassName = useMemo(() => { + return Component.styles?.(props); + }, [props]); + + const baseClassName = useMemo(() => { + return merge(extendClassName, stylesClass(props).split(' '), className); + }, [extendClassName, props, className]); - const propsTmp = - Platform.OS === 'web' - ? asPropsOrTheme - ? { - className: finalClassName, - } - : { - style: StyleSheet.create({ - root: { - $$css: true, - ...finalClassName.split(' ').reduce((acc, className) => { - acc[`___${className}`] = className; - return acc; - }, {}), + const finalClassName = useMemo(() => { + return merge( + baseClassName, + ...(states?.isFocus || focus ? focusClassname : []), + ...(states?.isHover || hover ? hoverClassname : []), + ...(states?.isActive || active ? activeClassname : []), + ...(disabled ? disabledClassname : []), + ...(isMotiComponent + ? [] + : [ + ...focusAnimate, + ...hoverAnimate, + ...activeAnimate, + ...disabledAnimate, + ].map((key) => { + const property = tw.style(key); + const [p] = Object.keys(property); + const propertyT = camelToSnakeCase(p); + return `transition-[${propertyT}]`; + })), + ...(!isMotiComponent && (states?.isFocus || focus) ? focusAnimate : []), + ...(!isMotiComponent && (states?.isHover || hover) ? hoverAnimate : []), + ...(!isMotiComponent && (states?.isActive || active) + ? activeAnimate + : []), + ...(!isMotiComponent && disabled ? disabledAnimate : []) + ); + }, [ + baseClassName, + states?.isFocus, + states?.isHover, + states?.isActive, + focus, + focusClassname, + hover, + hoverClassname, + active, + activeClassname, + disabled, + disabledClassname, + focusAnimate, + hoverAnimate, + activeAnimate, + disabledAnimate, + ]); + + const animate = useMemo( + () => + !hasAnimate + ? undefined + : ({ hovered, pressed }) => { + 'worklet'; + const classNameAnimate = merge( + baseClassName, + ...(focus ? focusAnimate : []), + ...(hovered ? hoverAnimate : []), + ...(pressed ? activeAnimate : []), + ...(disabled ? disabledAnimate : []) + ); + const resultStyle = tw.style(classNameAnimate); + + const resultString = Object.entries(resultStyle).reduce( + (acc, [key, value]) => { + return { + ...acc, + [key]: value.toString(), + }; }, - }).root, - } + {} + ); + return resultString; + }, + [ + baseClassName, + hasAnimate, + hoverAnimate, + activeAnimate, + disabled, + disabledAnimate, + focus, + focusAnimate, + ] + ); + + const propsTmp = useMemo(() => { + return Platform.OS === 'web' + ? { + style: StyleSheet.create({ + root: { + $$css: true, + ...finalClassName.split(' ').reduce((acc, className) => { + acc[`___${className}`] = className; + return acc; + }, {}), + }, + }).root, + } : { style: tw.style(finalClassName) }; + }, [finalClassName]); + + const motiCompatibleProps = isMotiComponent ? { animate } : {}; return ( - { - componentProps?.onPressIn?.(e); + {...propsTmp} + {...componentProps} + {...motiCompatibleProps} + {...propsTheme} + onPressIn={composeEventHandlers(componentProps?.onPressIn, () => { setActive(true); - }} - onPressOut={(e: any) => { - componentProps?.onPressOut?.(e); + })} + onPressOut={composeEventHandlers(componentProps?.onPressIn, () => { setActive(false); - }} - onBlur={(e: any) => { - componentProps?.onBlur?.(e); - setFocus(false); - }} - onFocus={(e: any) => { - componentProps?.onFocus?.(e); - setFocus(true); - }} - onPointerOver={(e: any) => { - componentProps?.onPointerOver?.(e); + })} + onHoverIn={composeEventHandlers(() => { setHover(true); - }} - onPointerOut={(e: any) => { - componentProps?.onPointerOut?.(e); + }, componentProps?.onHoverIn)} + onHoverOut={composeEventHandlers(() => { setHover(false); - }} - {...propsTheme} - {...propsVariant} - {...propsTmp} - {...componentProps} + }, componentProps?.onHoverOut)} + onBlur={composeEventHandlers(componentProps?.onBlur, () => { + setFocus(false); + })} + onFocus={composeEventHandlers(componentProps?.onFocus, () => { + setFocus(true); + })} /> ); }); + return withStaticProperties(NewComponent, { styles: (p?: Props) => merge((Component as any).styles?.(p), stylesClass(p)), diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 8ed9652f..083a76cc 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -4,10 +4,15 @@ import type { } from '@mergeui/class-variance-authority'; import type { StringToBoolean } from '@mergeui/class-variance-authority/src/types'; -import type { ComponentType } from 'react'; +import type { + ComponentType, + ForwardRefExoticComponent, + RefAttributes, +} from 'react'; export type Base

= { className?: string[]; + animate?: string[]; props?: P & { as?: string | ComponentType }; }; @@ -42,3 +47,25 @@ export type CVAThemed> = { export type ConfigSchemaTheme> = BaseWithState

& CVAThemed; + +export type GetProps = A extends ComponentType< + infer Props +> + ? Props + : A extends new (props: infer Props) => any + ? Props + : {}; + +export type ConvertInterfaceToType = { + [key in keyof A]: A[key]; +}; + +export type ReactComponentWithRef = ForwardRefExoticComponent< + Props & RefAttributes +>; + +export type StylableComponent = + | ComponentType + | ForwardRefExoticComponent + | ReactComponentWithRef + | (new (props: any) => any); diff --git a/packages/demo/src/core/styled.tsx b/packages/demo/src/core/styled.tsx index 745f638d..a63e4cdf 100644 --- a/packages/demo/src/core/styled.tsx +++ b/packages/demo/src/core/styled.tsx @@ -4,9 +4,9 @@ import type { Props } from '../props'; import { YBox } from '@mergeui/ui'; const ButtonFrame = styled(Pressable, { - 'className': ['bg-blue-500', 'rounded'], + 'className': ['bg-blue-500', 'rounded', 'transition-[bg]'], ':hover': { - className: ['bg-blue-400'], + animate: ['bg-blue-400'], }, ':active': { className: ['bg-blue-600'], diff --git a/packages/demo/src/ui/animations/Animate.tsx b/packages/demo/src/ui/animations/Animate.tsx new file mode 100644 index 00000000..a5b5e2a0 --- /dev/null +++ b/packages/demo/src/ui/animations/Animate.tsx @@ -0,0 +1,18 @@ +import { Animate, Box, Button } from '@mergeui/ui'; +import { useState } from 'react'; + +export const AnimateDemo = () => { + const [key, remount] = useState(); + return ( + +