Code Monkey home page Code Monkey logo

react-native-tailwind.macro's Introduction

react-native-tailwind.macro ✨

npm version github workflow status

demo

Babel macro for easily writing responsive Tailwind styles in React Native (+ Web).


Easily style components using the tw prop:

import "react-native-tailwind.macro"
import { View, Text } from "react-native"

const Badge = () => (
  <View tw="px-3 py-0.5 rounded-xl ios:rounded-full bg-blue-100 dark:bg-blue-800">
    <Text tw="text-sm lg:text-lg font-medium text-blue-800">Badge</Text>
  </View>
)

Apply conditions and create memoized and complex styles using useTailwindStyles:

import { useTailwindStyles } from "react-native-tailwind.macro"
import { View, Text } from "react-native"

const Status = ({ isActive, labelColor }) => {
  const { box, text } = useTailwindStyles(
    (tw) => ({
      box: [tw`rounded-3xl px-8 py-4 bg-gray-100`, isActive && tw`bg-blue-800`],
      text: [tw`font-medium`, isActive && tw`font-bold`, { color: labelColor }],
    }),
    [isActive, labelColor]
  )

  return (
    <View style={box}>
      <Text style={text}>{isActive ? "Active" : "Inactive"}</Text>
    </View>
  )
}

Features

  • ⚡️   Responsive styles on native and web (with CSS Media Queries and SSR support - no more layout flashes!)

    bg-blue-500 md:bg-purple-500 xl:bg-indigo-500

  • 🌓   Dark Mode

    text-black dark:text-white

  • 📱   Platform selectors

    ios:bg-blue-800 android:bg-purple-800

  • 🖥   Web only selectors

    focus:bg-blue-400 active:bg-indigo-400

  • 🕺   Stackable selectors

    ios:md:font-bold android:(text-blue-800 sm:dark:text-blue-100)

  • 🧠   All styles are statically generated at build time and memoized

  • 🛠   useTailwindStyles hook for optimized conditional and complex styles

  • 📝   Respects your tailwind.config.js and allows for custom classes and plugins made possible by the amazing tailwind-react-native-classnames

Installation

This library relies on babel-plugin-macros.

yarn add --dev babel-plugin-macros

yarn add react-native-tailwind.macro

Add babel-plugin-macros to your .babelrc or babel.config.js and you're all set up!

{
  "plugins": ["macros"]
}

Usage

Using the tw prop

The best and easiest usage is to simply use the tw prop that is artificially added to all JSX elements. Under the hood, the macro removes the tw prop completely and instead applies or extends a style prop and also adds a web-only data-tw id used to apply CSS-based media queries.

All you have to do is have any import of react-native-tailwind.macro in your file, either import "react-native-tailwind.macro" or import { /* whatever import you need */ } from "react-native-tailwind.macro".

Example:

import "react-native-tailwind.macro"
import { View } from "react-native"
import { MyComponent } from "./my-component"

const Example = () => (
  <>
    <View tw="w-[100px] h-[100px] bg-blue-500 md:bg-purple-500" />

    {/* Only works if MyComponent accepts a "style" prop */}
    <MyComponent tw="bg-pink-500 ios:dark:bg-indigo-800" />
  </>
)

⚠️ NOTE: In order for <MyComponent /> to render responsive styles on the web, you need to also pass down a dataSet prop to the element receiving the style. The easiest way to achieve this would be to use the rest-spread syntax for MyComponent's props and pass all non-used props to the style-carrying element:

const MyComponent = ({ disabled, ...props }) => (
  <View {...props}>
    <Text>{disabled ? "Disabled" : "Enabled"}</Text>
  </View>
)

useTailwindStyles

In cases where you don't have access to a style prop or need to apply styles using other props, like contentContainerStyle on a ScrollView, you can use useTailwindStyles to produce the desired style objects.

import { useTailwindStyles } from "react-native-tailwind.macro"
import { ScrollView } from "react-native"

const Example = () => {
  const styles = useTailwindStyles((tw) => ({
    contentContainer: tw`py-8`,
  }))

  return (
    <ScrollView contentContainerStyle={styles.contentContainer} {/* ... */}>
      {/* Content */}
    </ScrollView>
  )
}

This also comes in handy, when you want to apply styles conditionally, pass in Reanimated animated styles or do any other fancy stuff with your styles.

import { useTailwindStyles } from "react-native-tailwind.macro"
import { TouchableOpacity, Text } from "react-native"
import Animated, {
  useSharedValue,
  useAnimatedStyle,
} from "react-native-reanimated"

const Example = ({ rounded, backgroundColor }) => {
  // Reanimated 🔥
  const offset = useSharedValue(0)
  const animatedStyles = useAnimatedStyle(() => {
    return {
      transform: [{ translateX: offset.value * 255 }],
    }
  })

  const styles = useTailwindStyles(
    (tw) => ({
      box: [
        tw`w-[100px] h-[100px] lg:(w-[300px] h-[300px])`,
        rounded && tw`rounded-3xl`, // conditional Tailwind style
        animatedStyles, // apply Reanimated styles
        { backgroundColor }, // apply passed in backgroundColor
      ],
      text: tw`text-lg font-bold`,
    }),
    // only recompute when any of these deps change, skip all unnecessary re-renders
    [rounded, animatedStyles, backgroundColor]
  )

  return (
    <>
      <Animated.View
        style={styles.box}
        {/* Required for responsive styles on the web, hence it's preferred to apply all responsive styles through the `tw` prop */}
        dataSet={{ tw: styles.box.id }}
      />
      <TouchableOpacity
        {/* Mix and match tw prop and styles in your code */}
        tw="mt-8"
        onPress={() => (offset.value = Math.random())}
      >
        <Text style={styles.text}>Move</Text>
      </TouchableOpacity>
    </>
  )
}

Pro Tip 👻

Even if you don't rely on any Tailwind styles, useTailwindStyles can be used to generate memoized styles as performance boost, see this gist for explanation.

const styles = useTailwindStyles(
  (tw) => ({
    box: [{ backgroundColor: "red" }, isActive && { backgroundColor: "pink" }],
  }),
  [isActive]
)

TailwindProvider

By default, the device's color scheme preference is used to enable dark mode. If you want to dynamically change whether dark mode is enabled, you can wrap your App with TailwindProvider and pass in your dark mode preference.

On the web, the set value will automatically be persisted in a cookie to enable SSR and SSG without flashes on load.

import {
  TailwindProvider,
  getInitialColorScheme,
} from "react-native-tailwind.macro"

const App = () => {
  const [darkMode, setDarkMode] = useState(getInitialColorScheme() === "dark")

  return <TailwindProvider dark={darkMode}>{/* ... */}</TailwindProvider>
}

getInitialColorScheme

Returns either the cookie-persisted preference on web or falls back to the system preference.

import { getInitialColorScheme } from "react-native-tailwind.macro"

getInitialColorScheme() // returns "light" or "dark"

Macro Options

You can apply options to the macro by adding a babel-plugin-macros.config.js or specifying them in your package.json like below:

// babel-plugin-macros.config.js
module.exports = {
  reactNativeTailwind: {
    // your options
  },
}

Alternatively:

// package.json
{
  //...
  "babelMacros": {
    "reactNativeTailwind": {
      // your options
    }
  }
}
Available Options
Name Default Description
config "./tailwind.config.js" The path to your Tailwind config.

Next.js SSR Setup

In order to enable SSR support via media queries on Next.js, update your custom document as follows:

// pages/_document.js

+ import { flush } from "react-native-tailwind.macro"

/* ... */

export class Document extends NextDocument {
  static async getInitialProps({ renderPage }) {
    AppRegistry.registerComponent("Main", () => Main)
    const { getStyleElement } = AppRegistry.getApplication("Main")
    const page = renderPage()
    const styles = [
      getStyleElement(),
+     flush(),
    ]
    return { ...page, styles: React.Children.toArray(styles) }
  }

  render() {
    /* ... */
  }
}

How it works

Behind the scenes, react-native-tailwind.macro turns your simple code from this

import "react-native-tailwind.macro"

const Example = () => (
  <View tw="w-[100px] h-[100px] bg-purple-500 dark:ios:lg:bg-pink-500 hover:bg-indigo-500" />
)

... to something along the lines of this:

// Import the necessary utilities
import * as ReactNativeTailwindMacro from "react-native-tailwind.macro/exports"

// Creates a hook based on the static output from Tailwind style compilation
const useStyles = ReactNativeTailwindMacro.createUseTailwindStyles({
  // Compiled Tailwind styles with unique id and information on when to apply
  a7gsbs: [
    {
      dark: false,
      selectors: [],
      style: {
        width: 100,
        height: 100,
        backgroundColor: "#8b5cf6",
      },
    },
    {
      dark: true,
      breakpoint: {
        label: "lg",
        minWidth: "1024px",
      },
      selectors: [],
      platform: "ios",
      style: {
        backgroundColor: "#ec4899",
      },
    },
    {
      dark: false,
      // Styles on web will only be applied on web
      selectors: ["hover"],
      style: {
        backgroundColor: "#6366f1",
      },
    },
  ],
})

const Example = () => {
  // Call to the produced hook, takes into account the current context and returns
  // memoized styles that only change when the context changes
  const tailwindStyles = useStyles()

  return (
    <View
      // Apply the memoized style
      style={tailwindStyles["a7gsbs"]}
      // Apply data-tw id for CSS-based media queries
      dataSet={{ tw: tailwindStyles["a7gsbs"].id }}
    />
  )
}

For more examples and use cases, check the macro test snapshots.

Caveats

  • Only works in function components due to dependency on context

  • useTailwindStyles doesn't properly support responsive styles on the web, prefer to use the tw prop for responsive styles if possible

  • <View tw="..."/> and tw`...` only accept static styles without string interpolation

Credits

Contributing

See the contributing guide to learn how to contribute to the repository and the development workflow.

License

MIT

react-native-tailwind.macro's People

Contributors

finkef avatar maxkicw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

react-native-tailwind.macro's Issues

!important are not managed correctly

Hi,

I'm not sure this bug is related to this lib or tailwind-react-native-classnames but using ! (to make CCS rule as important) prefix seems not working.

Use case:

I need to hide elements for small screens

  • When using no breakpoint: display: none is defined in the element style
  • When using a breakpoint: display: none is defined as !important in the media query

so i need anyway to make the override !important in the media query

What i tried:

  • use prefix directly in style (hidden md:!flex) => Do not work, the !important flag is not added to the MQ
  • create a dedicated utility:
const plugin = require('tailwindcss/plugin')

{
...
plugins: [
    plugin(({ addUtilities }) => {
      addUtilities({
        show: {
          display: 'flex !important',
        },
      })
    }),
  ],

this generates a bad formatted CSS Rule (display: flex !important !important), since it adds an !important flag when it detect a ! char (but it doesn't remove it), i also tried with !flex / flex !, but it's the same result (adds the flag but keeps the ! char making the rule invalid

Workaround

I've found a Q&D workaround to make it works on web: display: 'flex /* ! */ (which results display: flex /* ! */ !important), but while it's working on web and was working on our previous native releases, it breaks our last native versions (upgraded RN and using hermes engine)

[Expo + Next] Media Queries not generated

Hi,

First of all, thanks for your work on this package which looks amazing.

I'm trying to use it on our app (which uses Expo + Next + SSR), but i'm in trouble with media queries on web. any styles are handled well excepted those using media queries (heading styles are empty) and i can't figure why.

I compared with your next example (that works well on my setup) and didn't noticed slight differences (excepted that our app is quite huge and uses a lot of next plugins), i also tried using hook and tw prop but without success.

Example:

This code:

<Ionicons
  name="chevron-back"
  size={14}
  tw="bg-blue-500 lg:bg-yellow-500"
/>

Generates this html:

<div dir="auto" data-tw="" class="css-text-901oao r-fontSize-1b43r93" style="background-color: rgb(245, 158, 11); font-family: ionicons; font-style: normal; font-weight: normal;"></div>

And heading:

<style id="rntwm-server-class"></style>
<noscript><style id="rntwm-server-media"></style></noscript>

Any suggestion to fix it ?

Thanks

Module not found: Can't resolve 'fs'

I am noticing the following error. I guess it is trying to use fs on the frontend?

error - ../../node_modules/react-native-tailwind.macro/dist/macro/resolve-tailwind-config.js:3:0
Module not found: Can't resolve 'fs'

Import trace for requested module:
../../node_modules/react-native-tailwind.macro/dist/macro/index.js
../../node_modules/react-native-tailwind.macro/dist/index.js
./src/pages/index.tsx

Tailwind intellisense support in prefix

everyone who have any classRegex to make Tailwind intellisense work in prefix ex: ios:(text-blue-500)
I have try "tw=[\"']([^"']*)", but it work only outside prefix.

Screen Shot 2021-12-16 at 12 29 10 AM

But it work if the class have space around ( )
ios:( text-blue-500 )

Screen Shot 2021-12-16 at 12 34 42 AM

Use `useDeviceContext` from twrnc to get dark, light, and {spacing}-screen classes

I've been trying to use the w-screen class, but I keep getting a twrnc warning that says to use useDeviceContext so that it can calculate the vw and vh values. I looked through the source code, and it doesn't look like this project it using it. What I would recommend is exporting the tw variable, importing it into the context file, and using the hook on it so that we can use these classes. This would be extremely helpful. Thanks! Love this project!

(P.S. If you want me to open a PR, I can do that also.)

Prop `style` did not match. Server:

I managed to get the next.js example app running but noticed in the chrome console Prop style did not match. Server:

From my experience, the way around this is to pass the styles as initial props in getStaticProps/getInitialProps. This is what I have been doing until now. It comes with its drawbacks so maybe it won't work here 🤔

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.