mirror of
https://github.com/zhigang1992/nativewind.git
synced 2026-06-11 08:03:37 +08:00
feat: allow for styled to parse additional props
This commit is contained in:
@@ -21,6 +21,7 @@ module.exports = {
|
||||
"error",
|
||||
{
|
||||
allowList: {
|
||||
prop: true,
|
||||
props: true,
|
||||
Props: true,
|
||||
ref: true,
|
||||
|
||||
30
__tests__/styled/__snapshots__/props.tsx.snap
Normal file
30
__tests__/styled/__snapshots__/props.tsx.snap
Normal file
@@ -0,0 +1,30 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Styled - Custom Props can style custom props 1`] = `
|
||||
Array [
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginBottom": 4,
|
||||
"marginLeft": 4,
|
||||
"marginRight": 4,
|
||||
"marginTop": 4,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>,
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"paddingBottom": 8,
|
||||
"paddingLeft": 8,
|
||||
"paddingRight": 8,
|
||||
"paddingTop": 8,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>,
|
||||
]
|
||||
`;
|
||||
30
__tests__/styled/props.tsx
Normal file
30
__tests__/styled/props.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { render } from "@testing-library/react-native";
|
||||
import { View, ViewProps, ViewStyle } from "react-native";
|
||||
import { styled } from "../../src";
|
||||
import { TestProvider } from "../tailwindcss/runner";
|
||||
|
||||
const StyledTestComponent = styled(
|
||||
({ style, style2 }: ViewProps & { style2: ViewStyle }) => {
|
||||
return (
|
||||
<>
|
||||
<View style={style} />
|
||||
<View style={style2} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
{
|
||||
props: ["style2"],
|
||||
}
|
||||
) as any;
|
||||
|
||||
describe("Styled - Custom Props", () => {
|
||||
test("can style custom props", () => {
|
||||
const tree = render(
|
||||
<TestProvider css="m-1 p-2">
|
||||
<StyledTestComponent className="m-1" style2="p-2" />
|
||||
</TestProvider>
|
||||
).toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -3,17 +3,14 @@ import {
|
||||
FunctionComponent,
|
||||
ComponentClass,
|
||||
PropsWithChildren,
|
||||
Children,
|
||||
cloneElement,
|
||||
ComponentProps,
|
||||
} from "react";
|
||||
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";
|
||||
import { ComponentContext } from "./context";
|
||||
import { matchChildAtRule } from "./match-at-rule";
|
||||
import { useStyledProps } from "./use-styled-props";
|
||||
import { useStyledChildren } from "./use-styled-children";
|
||||
|
||||
type StyledProps<P> = PropsWithChildren<
|
||||
P & {
|
||||
@@ -25,68 +22,66 @@ type StyledProps<P> = PropsWithChildren<
|
||||
|
||||
type Component<P> = string | FunctionComponent<P> | ComponentClass<P>;
|
||||
|
||||
export interface StyledOptions<P> {
|
||||
props?: boolean | Array<keyof P & string>;
|
||||
}
|
||||
|
||||
// Transform no props
|
||||
export function styled<P>(
|
||||
Component: Component<P>
|
||||
): FunctionComponent<StyledProps<P>> {
|
||||
Component: Component<P>,
|
||||
options?: { props: false }
|
||||
): FunctionComponent<StyledProps<P>>;
|
||||
// Transform extra props
|
||||
export function styled<P>(
|
||||
Component: Component<P>,
|
||||
options: { props: Array<keyof P & string> }
|
||||
): FunctionComponent<StyledProps<P & Record<keyof P, string>>>;
|
||||
// Transform all props
|
||||
export function styled<P>(
|
||||
Component: Component<P>,
|
||||
options: { props: true }
|
||||
): FunctionComponent<StyledProps<P & Record<keyof P, string>>>;
|
||||
// Implementation
|
||||
export function styled<P>(
|
||||
Component: Component<P>,
|
||||
{ props: propsToTransform }: StyledOptions<P> = {}
|
||||
) {
|
||||
function Styled({
|
||||
className,
|
||||
tw,
|
||||
style: styleProperty,
|
||||
style: componentStyles,
|
||||
children: componentChildren,
|
||||
...props
|
||||
...componentProps
|
||||
}: StyledProps<P>) {
|
||||
const { hover, focus, active, ...handlers } = useInteraction(props);
|
||||
const { hover, focus, active, ...handlers } =
|
||||
useInteraction(componentProps);
|
||||
|
||||
const classes = tw ?? className ?? "";
|
||||
|
||||
const tailwindStyles = useTailwind({
|
||||
const twCallback = useTailwind({
|
||||
hover,
|
||||
focus,
|
||||
active,
|
||||
flatten: false,
|
||||
})(classes);
|
||||
});
|
||||
|
||||
const style = styleProperty
|
||||
? [tailwindStyles, styleProperty]
|
||||
: tailwindStyles;
|
||||
const { childStyles, ...styledProps } = useStyledProps({
|
||||
tw: twCallback,
|
||||
classes,
|
||||
componentStyles,
|
||||
propsToTransform,
|
||||
componentProps,
|
||||
});
|
||||
|
||||
let children = isFragment(componentChildren)
|
||||
? // This probably needs to be recursive
|
||||
componentChildren.props.children
|
||||
: componentChildren;
|
||||
|
||||
if (tailwindStyles[ChildClassNameSymbol]) {
|
||||
children = Children.map(children, (child, index) => {
|
||||
const childStyles: P[] = [];
|
||||
for (const { atRules, ...styles } of tailwindStyles[
|
||||
ChildClassNameSymbol
|
||||
] ?? []) {
|
||||
const matches = atRules.every(([rule, params]) => {
|
||||
return matchChildAtRule({
|
||||
nthChild: index + 1,
|
||||
rule,
|
||||
params,
|
||||
});
|
||||
});
|
||||
if (matches) {
|
||||
childStyles.push(styles as P);
|
||||
}
|
||||
}
|
||||
|
||||
return cloneElement(child, {
|
||||
style: child.props.style
|
||||
? [child.props.style, childStyles]
|
||||
: childStyles.length > 0
|
||||
? childStyles
|
||||
: undefined,
|
||||
});
|
||||
});
|
||||
}
|
||||
const children = useStyledChildren({
|
||||
componentChildren,
|
||||
childStyles,
|
||||
});
|
||||
|
||||
const element = createElement(Component, {
|
||||
...props,
|
||||
...componentProps,
|
||||
...handlers,
|
||||
style,
|
||||
...styledProps,
|
||||
children,
|
||||
} as unknown as P);
|
||||
|
||||
|
||||
47
src/use-styled-children.ts
Normal file
47
src/use-styled-children.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { ReactNode, Children, cloneElement } from "react";
|
||||
import { isFragment } from "react-is";
|
||||
import { matchChildAtRule } from "./match-at-rule";
|
||||
import { AtRuleRecord } from "./types/common";
|
||||
|
||||
export interface UseStyledChildrenOptions {
|
||||
componentChildren: ReactNode;
|
||||
childStyles?: AtRuleRecord[];
|
||||
}
|
||||
|
||||
export function useStyledChildren({
|
||||
componentChildren,
|
||||
childStyles,
|
||||
}: UseStyledChildrenOptions): ReactNode {
|
||||
let children = isFragment(componentChildren)
|
||||
? // This probably needs to be recursive
|
||||
componentChildren.props.children
|
||||
: componentChildren;
|
||||
|
||||
if (childStyles) {
|
||||
children = Children.map(children, (child, index) => {
|
||||
const matchingStyles = [];
|
||||
for (const { atRules, ...styles } of childStyles) {
|
||||
const matches = atRules.every(([rule, params]) => {
|
||||
return matchChildAtRule({
|
||||
nthChild: index + 1,
|
||||
rule,
|
||||
params,
|
||||
});
|
||||
});
|
||||
if (matches) {
|
||||
matchingStyles.push(styles);
|
||||
}
|
||||
}
|
||||
|
||||
return cloneElement(child, {
|
||||
style: child.props.style
|
||||
? [child.props.style, matchingStyles]
|
||||
: matchingStyles.length > 0
|
||||
? matchingStyles
|
||||
: undefined,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
47
src/use-styled-props.ts
Normal file
47
src/use-styled-props.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { StyleProp } from "react-native";
|
||||
import { UseTailwindCallback } from "./use-tailwind";
|
||||
import { ChildClassNameSymbol } from "./utils/child-styles";
|
||||
|
||||
export interface UseStyledPropsOptions {
|
||||
tw: UseTailwindCallback<any>;
|
||||
classes: string | undefined;
|
||||
componentStyles: StyleProp<any>;
|
||||
propsToTransform?: boolean | string[];
|
||||
componentProps: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export function useStyledProps({
|
||||
tw,
|
||||
classes,
|
||||
componentStyles,
|
||||
propsToTransform,
|
||||
componentProps,
|
||||
}: UseStyledPropsOptions) {
|
||||
const mainStyles = tw(classes);
|
||||
|
||||
const style = componentStyles ? [mainStyles, componentStyles] : mainStyles;
|
||||
|
||||
const styledProps: Record<string, unknown> = {};
|
||||
|
||||
if (propsToTransform) {
|
||||
if (propsToTransform === true) {
|
||||
propsToTransform = Object.keys(componentProps);
|
||||
}
|
||||
|
||||
for (const prop of propsToTransform) {
|
||||
const value = componentProps[prop];
|
||||
|
||||
if (typeof value === "string") {
|
||||
styledProps[prop] = tw(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(styledProps);
|
||||
|
||||
return {
|
||||
childStyles: mainStyles[ChildClassNameSymbol],
|
||||
style,
|
||||
...styledProps,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user