Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Text, View } from "react-native";
import { Button, Text, View } from "react-native";

import { StatusBar } from "expo-status-bar";

Expand All @@ -10,6 +10,7 @@ export default function App() {
<Text className="text-red-800 text-2xl font-bold animate-bounce">
Hello world!!!
</Text>
<Button title="test2" className="text-red-500" />
<StatusBar style="auto" />
</View>
);
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/native/container-queries.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ test("container query width", () => {
},
});

expect(parent.props.style).toStrictEqual([{ width: 200 }, { width: 500 }]);
expect(parent.props.style).toStrictEqual({ width: 500 });

expect(child.props.style).toStrictEqual({
color: "#00f",
Expand Down
25 changes: 5 additions & 20 deletions src/__tests__/native/specificity.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ test("inline styles", () => {
/>,
).getByTestId(testID);

expect(component.props.style).toStrictEqual([
{ backgroundColor: "#f00" },
{ backgroundColor: "blue" },
]);
expect(component.props.style).toStrictEqual({ backgroundColor: "blue" });
});

test("specificity order", () => {
Expand Down Expand Up @@ -60,10 +57,7 @@ test("important - requires sorting", () => {
<Text testID={testID} className="blue red" />,
).getByTestId(testID);

expect(component.props.style).toStrictEqual([
{ color: "#f00" },
{ color: "#00f" },
]);
expect(component.props.style).toStrictEqual({ color: "#00f" });
});

test("important - inline", () => {
Expand All @@ -79,10 +73,7 @@ test("important - inline", () => {
/>,
).getByTestId(testID);

expect(component.props.style).toStrictEqual([
{ backgroundColor: "red" },
{ backgroundColor: "#00f" },
]);
expect(component.props.style).toStrictEqual({ backgroundColor: "#00f" });
});

test("important - modifiers", () => {
Expand All @@ -96,17 +87,11 @@ test("important - modifiers", () => {
<Text testID={testID} className="blue red" />,
).getByTestId(testID);

expect(component.props.style).toStrictEqual([
{ color: "#f00" },
{ color: "#00f" },
]);
expect(component.props.style).toStrictEqual({ color: "#00f" });

fireEvent(component, "hoverIn");

expect(component.props.style).toStrictEqual([
{ color: "#008000" },
{ color: "#00f" },
]);
expect(component.props.style).toStrictEqual({ color: "#00f" });
});

test("passThrough - inline", () => {
Expand Down
19 changes: 13 additions & 6 deletions src/native/react/useNativeCss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,29 +172,36 @@ export function useNativeCss(
export function mappingToConfig(mapping: StyledConfiguration<any>) {
return Object.entries(mapping).flatMap(([key, value]): Config => {
if (value === true) {
return { source: key, target: key };
return {
source: key,
target: key,
};
} else if (value === false) {
return { source: key, target: false };
} else if (typeof value === "string") {
return { source: key, target: value.split(".") };
} else if (typeof value === "object") {
const nativeStyleMapping = value.nativeStyleMapping as
| Record<string, string>
| undefined;

if (Array.isArray(value)) {
return { source: key, target: value };
return { source: key, target: value, nativeStyleMapping };
}

if ("target" in value) {
if (value.target === false) {
return { source: key, target: false };
return { source: key, target: false, nativeStyleMapping };
} else if (typeof value.target === "string") {
const target = value.target.split(".");

if (target.length === 1) {
return { source: key, target: target[0]! };
return { source: key, target: target[0]!, nativeStyleMapping };
} else {
return { source: key, target };
return { source: key, target, nativeStyleMapping };
}
} else if (Array.isArray(value.target)) {
return { source: key, target: value.target };
return { source: key, target: value.target, nativeStyleMapping };
}
}
}
Expand Down
81 changes: 72 additions & 9 deletions src/native/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,19 @@ export function getStyledProps(
const styledProps = state.stylesObs?.get(state.styleEffect);

for (const config of state.configs) {
result = deepMergeConfig(config, styledProps?.normal, inline, true);
result = deepMergeConfig(
config,
nativeStyleMapping(config, styledProps?.normal),
inline,
true,
);

if (styledProps?.important) {
result = deepMergeConfig(config, result, styledProps.important);
result = deepMergeConfig(
config,
result,
nativeStyleMapping(config, styledProps.important),
);
}

// Apply the handlers
Expand Down Expand Up @@ -122,11 +131,11 @@ function deepMergeConfig(
right: Record<string, any> | undefined | null,
rightIsInline = false,
) {
if (!config.target || !right) {
if (!right) {
return { ...left };
}

let result = Object.assign({}, left, right);
let result = config.target ? Object.assign({}, left, right) : { ...left };

if (
right &&
Expand All @@ -140,7 +149,7 @@ function deepMergeConfig(
/**
* If target is a path, deep merge until we get to the last key
*/
if (Array.isArray(config.target) && config.target.length > 1) {
if (Array.isArray(config.target)) {
for (let i = 0; i < config.target.length - 1; i++) {
const key = config.target[i];

Expand All @@ -159,11 +168,9 @@ function deepMergeConfig(
return result;
}

const target = Array.isArray(config.target)
? config.target[0]
: config.target;
const target = config.target;

if (target === undefined) {
if (target === undefined || target === false) {
return result;
}

Expand Down Expand Up @@ -196,3 +203,59 @@ function deepMergeConfig(

return result;
}

function nativeStyleMapping(
config: Config,
props: Record<string, any> | undefined,
) {
if (!config.nativeStyleMapping || !props) {
return props;
}

let source: Record<string, any> | undefined;

if (typeof config.target === "string") {
source = props[config.target];
} else if (config.target === false) {
source = props["style"];
} else {
const tokens = [...config.target];
const lastToken = tokens.pop()!;

source = props;
for (const token of tokens) {
source = source[token];
if (!source) {
return props;
}
}

source = source[lastToken];
}

if (!source) {
return props;
}

for (const [key, path] of Object.entries(config.nativeStyleMapping)) {
const styleValue = source[key];

delete source[key];

if (styleValue === undefined) {
continue;
}

let target = props;
const tokens = path.split(".");
const lastToken = tokens.pop();
for (const token of tokens) {
target[token] ??= {};
target = target[token];
}

target[lastToken!] = styleValue;
}

return props;
}
3 changes: 3 additions & 0 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ declare module "@react-native/virtualized-lists" {
}

declare module "react-native" {
interface ButtonProps {
className?: string;
}
interface ScrollViewProps
extends ViewProps,
ScrollViewPropsIOS,
Expand Down