mirror of
https://github.com/zhigang1992/nativewind.git
synced 2026-06-12 00:24:45 +08:00
feat: add hover, focus & active pseudo-classes
This commit is contained in:
28
__tests__/tailwindcss/pseudo-classes.ts
Normal file
28
__tests__/tailwindcss/pseudo-classes.ts
Normal 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" },
|
||||
],
|
||||
},
|
||||
],
|
||||
]);
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 } = {}) {
|
||||
7
src/tailwind/native/pseudo-classes.ts
Normal file
7
src/tailwind/native/pseudo-classes.ts
Normal 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
143
src/use-interaction.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user