Skip to content

Commit

Permalink
feat: react-native "twrnc" and "native-wind" styling types (#1493)
Browse files Browse the repository at this point in the history
* feat twrnc style type

* feat twrnc style type

* feat: twrnc test

* feat: native wind styling

* chore: prettier

* chore: added changeset

* Update .changeset/strong-cycles-cough.md

---------

Co-authored-by: Sami Jaber <[email protected]>
  • Loading branch information
max-arias and samijaber authored Jul 9, 2024
1 parent 87b26e6 commit f83b8f4
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/strong-cycles-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@builder.io/mitosis': patch
---

Adds two new styling options for the react-native generator: twrnc and native-wind
Original file line number Diff line number Diff line change
Expand Up @@ -9165,6 +9165,33 @@ export default MyBasicWebComponent;
"
`;

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

function MyComponent(props) {
return (
<View className=\\"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>
</View>
);
}

export default MyComponent;
"
`;

exports[`React Native > svelte > Javascript Test > basic 1`] = `
"import * as React from \\"react\\";
import {
Expand Down Expand Up @@ -10264,3 +10291,31 @@ function MyComponent(props: any) {
export default MyComponent;
"
`;

exports[`React Native > twrnc style 1`] = `
"import * as React from \\"react\\";
import {
FlatList,
ScrollView,
View,
StyleSheet,
Image,
Text,
Pressable,
TextInput,
} from \\"react-native\\";
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\`}\\">
<Pressable onPress={(e) => console.log(\\"event\\")}>
<Text>Hello</Text>
</Pressable>
</View>
);
}

export default MyComponent;
"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function MyComponent(props) {
return (
<div class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
<button onClick={(e) => console.log('event')}>Hello</button>
</div>
);
}
21 changes: 21 additions & 0 deletions packages/core/src/__tests__/react-native.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import { componentToReactNative } from '../generators/react-native';
import { runTestsForTarget } from './test-generator';

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

describe('React Native', () => {
runTestsForTarget({ options: {}, target: 'reactNative', generator: componentToReactNative });

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

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

test('native-wind style', () => {
const component = parseJsx(twrncStyledComponentRN);
const output = componentToReactNative({
stylesType: 'native-wind',
})({ component });

expect(output).toMatchSnapshot();
});
});
104 changes: 102 additions & 2 deletions packages/core/src/generators/react-native/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { componentToReact } from '../react';
import { sanitizeReactNativeBlockStyles } from './sanitize-react-native-block-styles';

export interface ToReactNativeOptions extends BaseTranspilerOptions {
stylesType: 'emotion' | 'react-native';
stylesType: 'emotion' | 'react-native' | 'twrnc' | 'native-wind';
stateType: 'useState' | 'mobx' | 'valtio' | 'solid' | 'builder';
}

Expand Down Expand Up @@ -111,7 +111,6 @@ export const collectReactNativeStyles = (json: MitosisComponent): ClassStyleMap
/**
* Plugin that handles necessary transformations from React to React Native:
* - Converts DOM tags to <View /> and <Text />
* - Removes redundant `class`/`className` attributes
*/
const PROCESS_REACT_NATIVE_PLUGIN: Plugin = () => ({
json: {
Expand All @@ -135,6 +134,20 @@ const PROCESS_REACT_NATIVE_PLUGIN: Plugin = () => ({
) {
node.name = 'Text';
}
}
});
},
},
});

/**
* Removes React Native className and class properties from the JSON
*/
const REMOVE_REACT_NATIVE_CLASSES_PLUGIN: Plugin = () => ({
json: {
pre: (json: MitosisComponent) => {
traverse(json).forEach(function (node) {
if (isMitosisNode(node)) {
if (node.properties.class) {
delete node.properties.class;
}
Expand All @@ -153,6 +166,85 @@ const PROCESS_REACT_NATIVE_PLUGIN: Plugin = () => ({
},
});

/**
* Converts class and className properties to twrnc style syntax
*/
const TWRNC_STYLES_PLUGIN: Plugin = () => ({
json: {
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,
]
.filter(Boolean)
.join(' ');

if (combinedClasses) {
node.properties.style = `{tw\`${combinedClasses}\`}`;
}

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;
}
}
});
},
},
});

/**
* Converts class and className properties to native-wind style syntax
* Note: We only support the "with babel" setup: https://www.nativewind.dev/guides/babel
*/
const NATIVE_WIND_STYLES_PLUGIN: Plugin = () => ({
json: {
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,
]
.filter(Boolean)
.join(' ');

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 (combinedClasses) {
node.properties.className = combinedClasses;
}
}
});
},
},
});

const DEFAULT_OPTIONS: ToReactNativeOptions = {
stateType: 'useState',
stylesType: 'react-native',
Expand All @@ -166,5 +258,13 @@ export const componentToReactNative: TranspilerGenerator<Partial<ToReactNativeOp

const options = mergeOptions(DEFAULT_OPTIONS, _options);

if (options.stylesType === 'twrnc') {
options.plugins.push(TWRNC_STYLES_PLUGIN);
} else if (options.stylesType === 'native-wind') {
options.plugins.push(NATIVE_WIND_STYLES_PLUGIN);
} else {
options.plugins.push(REMOVE_REACT_NATIVE_CLASSES_PLUGIN);
}

return componentToReact({ ...options, type: 'native' })({ component: json, path });
};
1 change: 1 addition & 0 deletions packages/core/src/generators/react/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ const _componentToReact = (
}'`
: ''
}
${options.stylesType === 'twrnc' ? `import tw from 'twrnc';\n` : ''}
${
componentHasStyles && options.stylesType === 'emotion' && options.format !== 'lite'
? `/** @jsx jsx */
Expand Down
9 changes: 8 additions & 1 deletion packages/core/src/generators/react/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { BaseTranspilerOptions } from '@/types/transpiler';

export interface ToReactOptions extends BaseTranspilerOptions {
stylesType: 'emotion' | 'styled-components' | 'styled-jsx' | 'react-native' | 'style-tag';
stylesType:
| 'emotion'
| 'styled-components'
| 'styled-jsx'
| 'react-native'
| 'style-tag'
| 'twrnc'
| 'native-wind';
stateType: 'useState' | 'mobx' | 'valtio' | 'solid' | 'builder' | 'variables';
format?: 'lite' | 'safe';
type: 'dom' | 'native' | 'taro';
Expand Down

0 comments on commit f83b8f4

Please sign in to comment.