feat: add hover, focus & active pseudo-classes

This commit is contained in:
Mark Lawlor
2022-05-11 13:58:51 +10:00
parent 5b779d08be
commit da3c8f4e2b
24 changed files with 203 additions and 6 deletions

View File

@@ -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" },
],
},
],
]);

View File

@@ -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];

View File

@@ -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;

View File

@@ -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<P> = PropsWithChildren<
P & {
@@ -35,8 +36,13 @@ export function styled<P>(
children: componentChildren,
...props
}: StyledProps<P>) {
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<P>(
return createElement(Component, {
...props,
...handlers,
style,
children,
} as unknown as P);

View File

@@ -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<NativePluginOptions | undefined>(
translate(helpers, notSupported);
skew(helpers, notSupported);
boxShadow(helpers, notSupported);
pseudoClasses(helpers, notSupported);
};
},
function ({ rem = 16 } = {}) {

View File

@@ -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");
};

143
src/use-interaction.ts Normal file
View File

@@ -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<NonNullable<PressableProps["onFocus"]>>(
(event) => {
if (disabled) {
return;
}
if (focusable) {
if (onFocus) {
onFocus(event);
}
setFocus(true);
}
},
[disabled, focusable, onFocus, setFocus]
);
const handleBlur = useCallback<NonNullable<PressableProps["onFocus"]>>(
(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,
};
}

View File

@@ -39,6 +39,9 @@ export function useTailwind<P extends RWNCssStyle>(
* White space for visual clarity :)
*/
export function useTailwind<P>({
hover,
focus,
active,
[ChildClassNameSymbol]: inheritedClassNames = "",
nthChild: initialNthChild = 0,
}: UseTailwindOptions = {}) {
@@ -90,9 +93,13 @@ export function useTailwind<P>({
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,

View File

@@ -15,5 +15,8 @@ export type UseTailwindCallback<P> = (className?: string) => StyleProp<P> &
export interface UseTailwindOptions {
nthChild?: number;
hover?: boolean;
focus?: boolean;
active?: boolean;
[ChildClassNameSymbol]?: string;
}