From da3c8f4e2bc72b53f6c78ed286aba0a6aeef8f6f Mon Sep 17 00:00:00 2001 From: Mark Lawlor Date: Wed, 11 May 2022 13:58:51 +1000 Subject: [PATCH] feat: add hover, focus & active pseudo-classes --- __tests__/tailwindcss/pseudo-classes.ts | 28 ++++ __tests__/tailwindcss/runner/index.ts | 4 +- src/babel/utils/get-tailwind-config.ts | 2 +- src/styled.tsx | 7 + src/{plugin => tailwind}/LICENSE | 0 src/{plugin => tailwind}/index.ts | 0 src/{plugin => tailwind}/native/box-shadow.ts | 0 src/{plugin => tailwind}/native/divide.ts | 0 src/{plugin => tailwind}/native/elevation.ts | 0 src/{plugin => tailwind}/native/font-size.ts | 0 src/{plugin => tailwind}/native/gap.ts | 0 src/{plugin => tailwind}/native/index.ts | 2 + .../native/line-height.ts | 0 src/tailwind/native/pseudo-classes.ts | 7 + src/{plugin => tailwind}/native/rotate.ts | 0 src/{plugin => tailwind}/native/scale.ts | 0 src/{plugin => tailwind}/native/skew.ts | 0 src/{plugin => tailwind}/native/space.ts | 0 src/{plugin => tailwind}/native/translate.ts | 0 src/{plugin => tailwind}/native/types.d.ts | 0 src/{plugin => tailwind}/native/utils.ts | 0 src/use-interaction.ts | 143 ++++++++++++++++++ src/use-tailwind.native.ts | 13 +- src/use-tailwind.ts | 3 + 24 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 __tests__/tailwindcss/pseudo-classes.ts rename src/{plugin => tailwind}/LICENSE (100%) rename src/{plugin => tailwind}/index.ts (100%) rename src/{plugin => tailwind}/native/box-shadow.ts (100%) rename src/{plugin => tailwind}/native/divide.ts (100%) rename src/{plugin => tailwind}/native/elevation.ts (100%) rename src/{plugin => tailwind}/native/font-size.ts (100%) rename src/{plugin => tailwind}/native/gap.ts (100%) rename src/{plugin => tailwind}/native/index.ts (98%) rename src/{plugin => tailwind}/native/line-height.ts (100%) create mode 100644 src/tailwind/native/pseudo-classes.ts rename src/{plugin => tailwind}/native/rotate.ts (100%) rename src/{plugin => tailwind}/native/scale.ts (100%) rename src/{plugin => tailwind}/native/skew.ts (100%) rename src/{plugin => tailwind}/native/space.ts (100%) rename src/{plugin => tailwind}/native/translate.ts (100%) rename src/{plugin => tailwind}/native/types.d.ts (100%) rename src/{plugin => tailwind}/native/utils.ts (100%) create mode 100644 src/use-interaction.ts diff --git a/__tests__/tailwindcss/pseudo-classes.ts b/__tests__/tailwindcss/pseudo-classes.ts new file mode 100644 index 0000000..48f5139 --- /dev/null +++ b/__tests__/tailwindcss/pseudo-classes.ts @@ -0,0 +1,28 @@ +import { tailwindRunner } from "./runner"; + +tailwindRunner("Pseudo-classes - hover", [ + [ + "hover:text-green-500", + { + "hover_text-green-500": [ + { atRules: [["pseudo-class", "hover"]], color: "#22c55e" }, + ], + }, + ], + [ + "focus:text-green-500", + { + "focus_text-green-500": [ + { atRules: [["pseudo-class", "focus"]], color: "#22c55e" }, + ], + }, + ], + [ + "active:text-green-500", + { + "active_text-green-500": [ + { atRules: [["pseudo-class", "active"]], color: "#22c55e" }, + ], + }, + ], +]); diff --git a/__tests__/tailwindcss/runner/index.ts b/__tests__/tailwindcss/runner/index.ts index ee2931c..d12cbed 100644 --- a/__tests__/tailwindcss/runner/index.ts +++ b/__tests__/tailwindcss/runner/index.ts @@ -2,8 +2,8 @@ import { TailwindConfig } from "tailwindcss/tailwind-config"; import { extractStyles } from "../../../src/postcss/extract-styles"; import { StyleError, StyleRecord } from "../../../src/types/common"; -import plugin from "../../../src/plugin"; -import { nativePlugin } from "../../../src/plugin/native"; +import plugin from "../../../src/tailwind"; +import { nativePlugin } from "../../../src/tailwind/native"; export type Test = [string, StyleRecord] | [string, StyleRecord, true]; diff --git a/src/babel/utils/get-tailwind-config.ts b/src/babel/utils/get-tailwind-config.ts index bd3f2d6..6f12e29 100644 --- a/src/babel/utils/get-tailwind-config.ts +++ b/src/babel/utils/get-tailwind-config.ts @@ -4,7 +4,7 @@ import { existsSync } from "node:fs"; import resolveTailwindConfig from "tailwindcss/resolveConfig"; import { TailwindConfig } from "tailwindcss/tailwind-config"; -import { nativePlugin, NativePluginOptions } from "../../plugin/native"; +import { nativePlugin, NativePluginOptions } from "../../tailwind/native"; export interface GetTailwindConfigOptions extends NativePluginOptions { tailwindConfigPath?: string; diff --git a/src/styled.tsx b/src/styled.tsx index 9758cae..9acd85c 100644 --- a/src/styled.tsx +++ b/src/styled.tsx @@ -10,6 +10,7 @@ import { ImageStyle, StyleProp, TextStyle, ViewStyle } from "react-native"; import { useTailwind } from "./use-tailwind"; import { ChildClassNameSymbol } from "./utils/child-styles"; import { isFragment } from "react-is"; +import { useInteraction } from "./use-interaction"; type StyledProps

= PropsWithChildren< P & { @@ -35,8 +36,13 @@ export function styled

( children: componentChildren, ...props }: StyledProps

) { + const { hover, focus, active, ...handlers } = useInteraction(props); + const tailwindStyles = useTailwind({ nthChild, + hover, + focus, + active, [ChildClassNameSymbol]: inheritedClassName, })(tw ?? className); @@ -60,6 +66,7 @@ export function styled

( return createElement(Component, { ...props, + ...handlers, style, children, } as unknown as P); diff --git a/src/plugin/LICENSE b/src/tailwind/LICENSE similarity index 100% rename from src/plugin/LICENSE rename to src/tailwind/LICENSE diff --git a/src/plugin/index.ts b/src/tailwind/index.ts similarity index 100% rename from src/plugin/index.ts rename to src/tailwind/index.ts diff --git a/src/plugin/native/box-shadow.ts b/src/tailwind/native/box-shadow.ts similarity index 100% rename from src/plugin/native/box-shadow.ts rename to src/tailwind/native/box-shadow.ts diff --git a/src/plugin/native/divide.ts b/src/tailwind/native/divide.ts similarity index 100% rename from src/plugin/native/divide.ts rename to src/tailwind/native/divide.ts diff --git a/src/plugin/native/elevation.ts b/src/tailwind/native/elevation.ts similarity index 100% rename from src/plugin/native/elevation.ts rename to src/tailwind/native/elevation.ts diff --git a/src/plugin/native/font-size.ts b/src/tailwind/native/font-size.ts similarity index 100% rename from src/plugin/native/font-size.ts rename to src/tailwind/native/font-size.ts diff --git a/src/plugin/native/gap.ts b/src/tailwind/native/gap.ts similarity index 100% rename from src/plugin/native/gap.ts rename to src/tailwind/native/gap.ts diff --git a/src/plugin/native/index.ts b/src/tailwind/native/index.ts similarity index 98% rename from src/plugin/native/index.ts rename to src/tailwind/native/index.ts index 4b5bf51..fb2a916 100644 --- a/src/plugin/native/index.ts +++ b/src/tailwind/native/index.ts @@ -10,6 +10,7 @@ import { elevation } from "./elevation"; import { fontSize } from "./font-size"; import { gap } from "./gap"; import { lineHeight } from "./line-height"; +import { pseudoClasses } from "./pseudo-classes"; import { rotate } from "./rotate"; import { scale } from "./scale"; import { skew } from "./skew"; @@ -46,6 +47,7 @@ export const nativePlugin = plugin.withOptions( translate(helpers, notSupported); skew(helpers, notSupported); boxShadow(helpers, notSupported); + pseudoClasses(helpers, notSupported); }; }, function ({ rem = 16 } = {}) { diff --git a/src/plugin/native/line-height.ts b/src/tailwind/native/line-height.ts similarity index 100% rename from src/plugin/native/line-height.ts rename to src/tailwind/native/line-height.ts diff --git a/src/tailwind/native/pseudo-classes.ts b/src/tailwind/native/pseudo-classes.ts new file mode 100644 index 0000000..54643e3 --- /dev/null +++ b/src/tailwind/native/pseudo-classes.ts @@ -0,0 +1,7 @@ +import { CustomPluginFunction } from "./types"; + +export const pseudoClasses: CustomPluginFunction = ({ addVariant }) => { + addVariant("hover", "@pseudo-class hover"); + addVariant("focus", "@pseudo-class focus"); + addVariant("active", "@pseudo-class active"); +}; diff --git a/src/plugin/native/rotate.ts b/src/tailwind/native/rotate.ts similarity index 100% rename from src/plugin/native/rotate.ts rename to src/tailwind/native/rotate.ts diff --git a/src/plugin/native/scale.ts b/src/tailwind/native/scale.ts similarity index 100% rename from src/plugin/native/scale.ts rename to src/tailwind/native/scale.ts diff --git a/src/plugin/native/skew.ts b/src/tailwind/native/skew.ts similarity index 100% rename from src/plugin/native/skew.ts rename to src/tailwind/native/skew.ts diff --git a/src/plugin/native/space.ts b/src/tailwind/native/space.ts similarity index 100% rename from src/plugin/native/space.ts rename to src/tailwind/native/space.ts diff --git a/src/plugin/native/translate.ts b/src/tailwind/native/translate.ts similarity index 100% rename from src/plugin/native/translate.ts rename to src/tailwind/native/translate.ts diff --git a/src/plugin/native/types.d.ts b/src/tailwind/native/types.d.ts similarity index 100% rename from src/plugin/native/types.d.ts rename to src/tailwind/native/types.d.ts diff --git a/src/plugin/native/utils.ts b/src/tailwind/native/utils.ts similarity index 100% rename from src/plugin/native/utils.ts rename to src/tailwind/native/utils.ts diff --git a/src/use-interaction.ts b/src/use-interaction.ts new file mode 100644 index 0000000..f90b987 --- /dev/null +++ b/src/use-interaction.ts @@ -0,0 +1,143 @@ +import { useCallback, useState } from "react"; +import { GestureResponderEvent, PressableProps } from "react-native"; + +declare module "react-native" { + interface PressableProps { + onHoverIn?: (event: MouseEvent) => void; + onHoverOut?: (event: MouseEvent) => void; + } +} + +export function useInteraction({ + disabled = false, + focusable = true, + onFocus, + onBlur, + onHoverIn, + onHoverOut, + onPressIn, + onPressOut, + onPress, +}: PressableProps = {}) { + const [hover, setHover] = useState(false); + const [focus, setFocus] = useState(false); + const [active, setActive] = useState(false); + + const handleFocus = useCallback>( + (event) => { + if (disabled) { + return; + } + + if (focusable) { + if (onFocus) { + onFocus(event); + } + + setFocus(true); + } + }, + [disabled, focusable, onFocus, setFocus] + ); + + const handleBlur = useCallback>( + (event) => { + if (disabled) { + return; + } + + if (onBlur) { + onBlur(event); + } + + setFocus(false); + }, + [disabled, onBlur, setFocus] + ); + + const handleHoverIn = useCallback( + (event: MouseEvent) => { + if (disabled) { + return; + } + if (onHoverIn) { + onHoverIn(event); + } + + setHover(true); + }, + [disabled, onHoverIn, setHover] + ); + + const handleHoverOut = useCallback( + (event: MouseEvent) => { + if (disabled) { + return; + } + + if (onHoverOut) { + onHoverOut(event); + } + + setHover(false); + }, + [disabled, onHoverOut, setHover] + ); + + const handlePressIn = useCallback( + (event: GestureResponderEvent) => { + if (disabled) { + return; + } + + if (onPressIn) { + onPressIn(event); + } + + setActive(true); + setFocus(false); + }, + [disabled, onPressIn, setActive] + ); + + const handlePressOut = useCallback( + (event: GestureResponderEvent) => { + if (disabled) { + return; + } + + if (onPressOut) { + onPressOut(event); + } + + setActive(false); + }, + [disabled, onPressOut, setActive] + ); + + const handlePress = useCallback( + (event: GestureResponderEvent) => { + if (disabled) { + return; + } + + if (onPress) { + onPress(event); + } + }, + [disabled, onPress, setActive] + ); + + return { + active, + hover, + focus, + onBlur: handleBlur, + onFocus: handleFocus, + onHoverIn: handleHoverIn, + onHoverOut: handleHoverOut, + onPress: handlePress, + onPressIn: handlePressIn, + onPressOut: handlePressOut, + }; +} diff --git a/src/use-tailwind.native.ts b/src/use-tailwind.native.ts index 85b148a..ee7736c 100644 --- a/src/use-tailwind.native.ts +++ b/src/use-tailwind.native.ts @@ -39,6 +39,9 @@ export function useTailwind

( * White space for visual clarity :) */ export function useTailwind

({ + hover, + focus, + active, [ChildClassNameSymbol]: inheritedClassNames = "", nthChild: initialNthChild = 0, }: UseTailwindOptions = {}) { @@ -90,9 +93,13 @@ export function useTailwind

({ if (rule === "selector" && params === "(> * + *)") { isForChildren = !name.startsWith(">"); return nthChild > 1; - } - - if (rule === "media") { + } else if (rule === "pseudo-class" && params === "hover") { + return hover; + } else if (rule === "pseudo-class" && params === "focus") { + return focus; + } else if (rule === "pseudo-class" && params === "active") { + return active; + } else if (rule === "media") { return match(params, { "aspect-ratio": width / height, "device-aspect-ratio": width / height, diff --git a/src/use-tailwind.ts b/src/use-tailwind.ts index 2ce3bcf..93733c2 100644 --- a/src/use-tailwind.ts +++ b/src/use-tailwind.ts @@ -15,5 +15,8 @@ export type UseTailwindCallback

= (className?: string) => StyleProp

& export interface UseTailwindOptions { nthChild?: number; + hover?: boolean; + focus?: boolean; + active?: boolean; [ChildClassNameSymbol]?: string; }