mirror of
https://github.com/zhigang1992/nativewind.git
synced 2026-06-16 02:34:28 +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 { extractStyles } from "../../../src/postcss/extract-styles";
|
||||||
import { StyleError, StyleRecord } from "../../../src/types/common";
|
import { StyleError, StyleRecord } from "../../../src/types/common";
|
||||||
|
|
||||||
import plugin from "../../../src/plugin";
|
import plugin from "../../../src/tailwind";
|
||||||
import { nativePlugin } from "../../../src/plugin/native";
|
import { nativePlugin } from "../../../src/tailwind/native";
|
||||||
|
|
||||||
export type Test = [string, StyleRecord] | [string, StyleRecord, true];
|
export type Test = [string, StyleRecord] | [string, StyleRecord, true];
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { existsSync } from "node:fs";
|
|||||||
import resolveTailwindConfig from "tailwindcss/resolveConfig";
|
import resolveTailwindConfig from "tailwindcss/resolveConfig";
|
||||||
import { TailwindConfig } from "tailwindcss/tailwind-config";
|
import { TailwindConfig } from "tailwindcss/tailwind-config";
|
||||||
|
|
||||||
import { nativePlugin, NativePluginOptions } from "../../plugin/native";
|
import { nativePlugin, NativePluginOptions } from "../../tailwind/native";
|
||||||
|
|
||||||
export interface GetTailwindConfigOptions extends NativePluginOptions {
|
export interface GetTailwindConfigOptions extends NativePluginOptions {
|
||||||
tailwindConfigPath?: string;
|
tailwindConfigPath?: string;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { ImageStyle, StyleProp, TextStyle, ViewStyle } from "react-native";
|
|||||||
import { useTailwind } from "./use-tailwind";
|
import { useTailwind } from "./use-tailwind";
|
||||||
import { ChildClassNameSymbol } from "./utils/child-styles";
|
import { ChildClassNameSymbol } from "./utils/child-styles";
|
||||||
import { isFragment } from "react-is";
|
import { isFragment } from "react-is";
|
||||||
|
import { useInteraction } from "./use-interaction";
|
||||||
|
|
||||||
type StyledProps<P> = PropsWithChildren<
|
type StyledProps<P> = PropsWithChildren<
|
||||||
P & {
|
P & {
|
||||||
@@ -35,8 +36,13 @@ export function styled<P>(
|
|||||||
children: componentChildren,
|
children: componentChildren,
|
||||||
...props
|
...props
|
||||||
}: StyledProps<P>) {
|
}: StyledProps<P>) {
|
||||||
|
const { hover, focus, active, ...handlers } = useInteraction(props);
|
||||||
|
|
||||||
const tailwindStyles = useTailwind({
|
const tailwindStyles = useTailwind({
|
||||||
nthChild,
|
nthChild,
|
||||||
|
hover,
|
||||||
|
focus,
|
||||||
|
active,
|
||||||
[ChildClassNameSymbol]: inheritedClassName,
|
[ChildClassNameSymbol]: inheritedClassName,
|
||||||
})(tw ?? className);
|
})(tw ?? className);
|
||||||
|
|
||||||
@@ -60,6 +66,7 @@ export function styled<P>(
|
|||||||
|
|
||||||
return createElement(Component, {
|
return createElement(Component, {
|
||||||
...props,
|
...props,
|
||||||
|
...handlers,
|
||||||
style,
|
style,
|
||||||
children,
|
children,
|
||||||
} as unknown as P);
|
} as unknown as P);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { elevation } from "./elevation";
|
|||||||
import { fontSize } from "./font-size";
|
import { fontSize } from "./font-size";
|
||||||
import { gap } from "./gap";
|
import { gap } from "./gap";
|
||||||
import { lineHeight } from "./line-height";
|
import { lineHeight } from "./line-height";
|
||||||
|
import { pseudoClasses } from "./pseudo-classes";
|
||||||
import { rotate } from "./rotate";
|
import { rotate } from "./rotate";
|
||||||
import { scale } from "./scale";
|
import { scale } from "./scale";
|
||||||
import { skew } from "./skew";
|
import { skew } from "./skew";
|
||||||
@@ -46,6 +47,7 @@ export const nativePlugin = plugin.withOptions<NativePluginOptions | undefined>(
|
|||||||
translate(helpers, notSupported);
|
translate(helpers, notSupported);
|
||||||
skew(helpers, notSupported);
|
skew(helpers, notSupported);
|
||||||
boxShadow(helpers, notSupported);
|
boxShadow(helpers, notSupported);
|
||||||
|
pseudoClasses(helpers, notSupported);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
function ({ rem = 16 } = {}) {
|
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 :)
|
* White space for visual clarity :)
|
||||||
*/
|
*/
|
||||||
export function useTailwind<P>({
|
export function useTailwind<P>({
|
||||||
|
hover,
|
||||||
|
focus,
|
||||||
|
active,
|
||||||
[ChildClassNameSymbol]: inheritedClassNames = "",
|
[ChildClassNameSymbol]: inheritedClassNames = "",
|
||||||
nthChild: initialNthChild = 0,
|
nthChild: initialNthChild = 0,
|
||||||
}: UseTailwindOptions = {}) {
|
}: UseTailwindOptions = {}) {
|
||||||
@@ -90,9 +93,13 @@ export function useTailwind<P>({
|
|||||||
if (rule === "selector" && params === "(> * + *)") {
|
if (rule === "selector" && params === "(> * + *)") {
|
||||||
isForChildren = !name.startsWith(">");
|
isForChildren = !name.startsWith(">");
|
||||||
return nthChild > 1;
|
return nthChild > 1;
|
||||||
}
|
} else if (rule === "pseudo-class" && params === "hover") {
|
||||||
|
return hover;
|
||||||
if (rule === "media") {
|
} 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, {
|
return match(params, {
|
||||||
"aspect-ratio": width / height,
|
"aspect-ratio": width / height,
|
||||||
"device-aspect-ratio": width / height,
|
"device-aspect-ratio": width / height,
|
||||||
|
|||||||
@@ -15,5 +15,8 @@ export type UseTailwindCallback<P> = (className?: string) => StyleProp<P> &
|
|||||||
|
|
||||||
export interface UseTailwindOptions {
|
export interface UseTailwindOptions {
|
||||||
nthChild?: number;
|
nthChild?: number;
|
||||||
|
hover?: boolean;
|
||||||
|
focus?: boolean;
|
||||||
|
active?: boolean;
|
||||||
[ChildClassNameSymbol]?: string;
|
[ChildClassNameSymbol]?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user