Skip to content

Commit

Permalink
feat(react-native): more complex twrnc style cases (#1505)
Browse files Browse the repository at this point in the history
* feat: support more complex styling in twrnc

* chore: linting
  • Loading branch information
max-arias authored Jul 30, 2024
1 parent 28f6650 commit 383f69f
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 21 deletions.
5 changes: 5 additions & 0 deletions .changeset/few-bulldogs-swim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@builder.io/mitosis': patch
---

feat: support more complex RN styling with twrnc
Original file line number Diff line number Diff line change
Expand Up @@ -10312,6 +10312,72 @@ export default MyComponent;
"
`;

exports[`React Native > twrnc state complex style 1`] = `
"import * as React from \\"react\\";
import {
FlatList,
ScrollView,
View,
StyleSheet,
Image,
Text,
Pressable,
TextInput,
} from \\"react-native\\";
import tw from \\"twrnc\\";
import { clsx } from \\"clsx\\";

function Button(props) {
function buttonClasses() {
return clsx(\\"px-4 py-2 rounded transition-colors\\", {
\\"bg-blue-500 hover:bg-blue-600 text-white\\": props.type === \\"primary\\",
\\"bg-gray-300 hover:bg-gray-400 text-black\\": props.type === \\"secondary\\",
});
}

return (
<Pressable
onPress={(event) => props.onClick}
style={tw.style(buttonClasses())}
>
{props.children}
</Pressable>
);
}

export default Button;
"
`;

exports[`React Native > twrnc state style 1`] = `
"import * as React from \\"react\\";
import {
FlatList,
ScrollView,
View,
StyleSheet,
Image,
Text,
Pressable,
TextInput,
} from \\"react-native\\";
import { useState } from \\"react\\";
import tw from \\"twrnc\\";

function MyBasicComponent(props) {
const [classState, setClassState] = useState(() => \\"testClassName\\");

return (
<View style={tw.style(classState)}>
<Text>Hello! I can run in React, Vue, Solid, or Liquid!</Text>
</View>
);
}

export default MyBasicComponent;
"
`;

exports[`React Native > twrnc style 1`] = `
"import * as React from \\"react\\";
import {
Expand All @@ -10328,7 +10394,9 @@ import tw from \\"twrnc\\";

function MyComponent(props) {
return (
<View style=\\"{tw\`bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded\`}\\">
<View
style={tw\`bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded\`}
>
<Pressable onPress={(e) => console.log(\\"event\\")}>
<Text>Hello</Text>
</Pressable>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useStore } from '@builder.io/mitosis';
import { clsx } from 'clsx';

type ButtonType = 'primary' | 'secondary';

interface ButtonProps {
type: ButtonType;
onClick: () => void;
children: any;
}

export default function Button(props: ButtonProps) {
const state = useStore({
get buttonClasses() {
return clsx('px-4 py-2 rounded transition-colors', {
'bg-blue-500 hover:bg-blue-600 text-white': props.type === 'primary',
'bg-gray-300 hover:bg-gray-400 text-black': props.type === 'secondary',
});
},
});

return (
<button class={state.buttonClasses} onClick={props.onClick}>
{props.children}
</button>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useState } from '@builder.io/mitosis';

export default function MyBasicComponent() {
const [classState, setClassState] = useState('testClassName');

return <div class={classState}>Hello! I can run in React, Vue, Solid, or Liquid!</div>;
}
20 changes: 20 additions & 0 deletions packages/core/src/__tests__/react-native.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { componentToReactNative } from '../generators/react-native';
import { runTestsForTarget } from './test-generator';

import { parseJsx } from '..';
import twrncStateComplexStyledComponentRN from './data/react-native/twrnc-state-complex-styled-component.raw.tsx?raw';
import twrncStateStyledComponentRN from './data/react-native/twrnc-state-styled-component.raw.tsx?raw';
import twrncStyledComponentRN from './data/react-native/twrnc-styled-component.raw.tsx?raw';

describe('React Native', () => {
Expand All @@ -16,6 +18,24 @@ describe('React Native', () => {
expect(output).toMatchSnapshot();
});

test('twrnc state style', () => {
const component = parseJsx(twrncStateStyledComponentRN);
const output = componentToReactNative({
stylesType: 'twrnc',
})({ component });

expect(output).toMatchSnapshot();
});

test('twrnc state complex style', () => {
const component = parseJsx(twrncStateComplexStyledComponentRN);
const output = componentToReactNative({
stylesType: 'twrnc',
})({ component });

expect(output).toMatchSnapshot();
});

test('native-wind style', () => {
const component = parseJsx(twrncStyledComponentRN);
const output = componentToReactNative({
Expand Down
58 changes: 38 additions & 20 deletions packages/core/src/generators/react-native/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,31 +174,49 @@ const TWRNC_STYLES_PLUGIN: Plugin = () => ({
post: (json: MitosisComponent) => {
traverse(json).forEach(function (node) {
if (isMitosisNode(node)) {
let combinedClasses = [
node.properties.class,
node.properties.className,
node.bindings.class,
node.bindings.className,
]
let staticClasses = [node.properties.class, node.properties.className]
.filter(Boolean)
.join(' ');

if (combinedClasses) {
node.properties.style = `{tw\`${combinedClasses}\`}`;
}
let dynamicClasses = [node.bindings.class, node.bindings.className].filter(Boolean);

if (node.properties.class) {
delete node.properties.class;
}
if (node.properties.className) {
delete node.properties.className;
}
if (node.bindings.class) {
delete node.bindings.class;
}
if (node.bindings.className) {
delete node.bindings.className;
if (staticClasses || dynamicClasses.length) {
let styleCode = '';

if (staticClasses) {
styleCode = `tw\`${staticClasses}\``;
}

if (dynamicClasses.length) {
let dynamicCode = dynamicClasses
.map((dc) => (dc && dc.code ? dc.code : null))
.filter(Boolean)
.join(', ');

if (dynamicCode) {
if (styleCode) {
// If we have both static and dynamic classes
styleCode = `tw.style(${styleCode}, ${dynamicCode})`;
} else if (dynamicClasses.length > 1) {
// If we have multiple dynamic classes
styleCode = `tw.style([${dynamicCode}])`;
} else {
// If we have a single dynamic class
styleCode = `tw.style(${dynamicCode})`;
}
}
}

if (styleCode) {
node.bindings.style = createSingleBinding({ code: styleCode });
}
}

// Clean up original class and className properties/bindings
delete node.properties.class;
delete node.properties.className;
delete node.bindings.class;
delete node.bindings.className;
}
});
},
Expand Down

0 comments on commit 383f69f

Please sign in to comment.