All Projects → Nozbe → Zacs

Nozbe / Zacs

Licence: mit
Zero Abstraction Cost Styling ⚡️👨‍🎨 (for React DOM & React Native)

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to Zacs

Ava
Node.js test runner that lets you develop with confidence 🚀
Stars: ✭ 19,458 (+4954.03%)
Mutual labels:  hacktoberfest
Openwisp Controller
Network and WiFi controller: provisioning, configuration management and updates, (pull via openwisp-config or push via SSH), x509 PKI management and more. Mainly OpenWRT, but designed to work also on other systems.
Stars: ✭ 377 (-2.08%)
Mutual labels:  hacktoberfest
Dynamicalsystems.jl
Award winning software library for nonlinear dynamics
Stars: ✭ 381 (-1.04%)
Mutual labels:  hacktoberfest
Schema Generator
PHP Model Scaffolding from Schema.org and other RDF vocabularies
Stars: ✭ 379 (-1.56%)
Mutual labels:  hacktoberfest
Orbit Mvi
An MVI framework for Kotlin and Android
Stars: ✭ 381 (-1.04%)
Mutual labels:  hacktoberfest
Pypng
Pure Python library for PNG image encoding/decoding
Stars: ✭ 380 (-1.3%)
Mutual labels:  hacktoberfest
Redash
Make Your Company Data Driven. Connect to any data source, easily visualize, dashboard and share your data.
Stars: ✭ 20,147 (+5132.99%)
Mutual labels:  hacktoberfest
Fullstaq Ruby Server Edition
A server-optimized Ruby distribution: less memory, faster, easy to install and security-patch via APT/YUM
Stars: ✭ 384 (-0.26%)
Mutual labels:  hacktoberfest
Memento
Memento is a development-only tool that caches HTTP calls once they have been executed.
Stars: ✭ 380 (-1.3%)
Mutual labels:  hacktoberfest
Nanovna Saver
A tool for reading, displaying and saving data from the NanoVNA
Stars: ✭ 382 (-0.78%)
Mutual labels:  hacktoberfest
Nusmods
🏫 Official course planning platform for National University of Singapore.
Stars: ✭ 379 (-1.56%)
Mutual labels:  hacktoberfest
Deno Nessie
A modular Deno library for PostgreSQL, MySQL, MariaDB and SQLite migrations
Stars: ✭ 381 (-1.04%)
Mutual labels:  hacktoberfest
Gpozaurr
Group Policy Eater is a PowerShell module that aims to gather information about Group Policies but also allows fixing issues that you may find in them.
Stars: ✭ 381 (-1.04%)
Mutual labels:  hacktoberfest
React Navigation
Routing and navigation for your React Native apps
Stars: ✭ 20,650 (+5263.64%)
Mutual labels:  hacktoberfest
Date Io
Abstraction over common javascript date management libraries
Stars: ✭ 382 (-0.78%)
Mutual labels:  hacktoberfest
Excalidraw
Virtual whiteboard for sketching hand-drawn like diagrams
Stars: ✭ 25,509 (+6525.71%)
Mutual labels:  hacktoberfest
Pext
Python-based extendable tool
Stars: ✭ 380 (-1.3%)
Mutual labels:  hacktoberfest
Kaamelott Soundboard
Ou : chante Sloubi. Nous, on va faire que la soundboard de Kaamelott.
Stars: ✭ 384 (-0.26%)
Mutual labels:  hacktoberfest
Onedal
oneAPI Data Analytics Library (oneDAL)
Stars: ✭ 382 (-0.78%)
Mutual labels:  hacktoberfest
Ejml
A fast and easy to use linear algebra library written in Java for dense, sparse, real, and complex matrices.
Stars: ✭ 378 (-1.82%)
Mutual labels:  hacktoberfest

ZACS: Zero Abstraction Cost Styling

👨‍🎨 Component styling with no performance penalty for React and React Native ⚡️

MIT License npm

What is zacs?

React Native EU: A Successful Web & React Native Sharing Strategy

📺 A Successful Web & React Native Sharing Strategy

ZACS turns React components that look like this:

import zacs from '@nozbe/zacs'
import style from './style'

const Box = zacs.view(style.box, { isHighlighted: style.highlighted })

const rendered = <Box isHighlighted />

Into optimized code that looks like this (web):

const rendered = <div className={style.box + ' ' + style.highlighted} />

Or this (React Native):

import { View } from 'react-native'

const rendered = <View style={[style.box, style.highlighted]} />

Pitch

ZACS (Zero Abstraction Cost Styling) is a super-fast component styling library for cross-platform React web and React Native apps.

Super-fast as in: there is no difference between using ZACS and writing <div className> and <View style> manually. That's because the library doesn't actually exist at runtime, it's entirely implemented as a Babel plugin, which compiles the "styled components" syntax down to bare metal.

And because ZACS hides the API difference between web (DOM) and React Native, you can build a web and RN app with shared codebase without react-native-web.

Installation

npm install @nozbe/zacs

or

yarn add @nozbe/zacs

Then add ZACS to your Babel config (.babelrc or babel.config.js):

{
  "plugins": [
+   ["@nozbe/zacs/babel", {
+     "platform": "web", // "web" or "native"
+     "production": false / true, // pass `false` to enable debug attributes
+     "keepDeclarations": false // (optional) pass `true` to keep zacs.xxx variable declarations in output
+   }]
  ]
}

Using zacs

Unstyled view or text

import zacs from '@nozbe/zacs'

const Box = zacs.view() // or zacs.view(null)
const Label = zacs.text()

const rendered = <Box><Label>Hello</Label></Box>
See compiled output

Web:

const rendered = <div><span>Hello</span></div>

React Native:

import { View, Text } from 'react-native'

const rendered = <View><Text>Hello</Text></View>

Simple styled view or text

import styles from './styles'

const Box = zacs.view(styles.box) // or zacs.text

const rendered = <Box />
See compiled output

Web:

const rendered = <div className={styles.box} />

React Native:

import { View } from 'react-native'

const rendered = <View style={styles.box} />

Conditional styles

const Box = zacs.view(styles.box, {
  isHighlighted: styles.isHighlighted,
  isVisible: styles.isVisible,
})

const rendered = <Box isHighlighted={reactions > 0} isVisible />

Declare conditional styles as { [propName: string]: RNStyleOrClassName }. If a specified prop is passed to the component with a truthy value, the corresponding style will be added.

See compiled output

Web:

const rendered = <div className={styles.box
                                + ' ' + styles.isVisible
                                + (reactions > 0) ? (' ' + styles.isHighlighted) : ''} />

Please note:

  • conditions are inlined whenever possible (when a constant is passed to a styling prop)
  • styling props are removed from the compiled output

React Native:

import { View } from 'react-native'

const rendered = <View style={[styles.box,
                              styles.isVisible,
                              reactions > 0 && styles.isHighlighted]} />

Adding style attributes (via individual props)

const Box = zacs.view(styles.box, null, {
  width: 'width',
  color: 'backgroundColor',
})

const rendered = <Box width={100} color="#80EADC" />

Declare CSS / React Native StyleSheet attributes available as component properties with { [propName: string]: CSSOrRNStyleAttributeName }.

Gotcha: If you pass a style attribute at all, it will override the main and conditional styles, even if the value is undefined.

See compiled output

Web:

const rendered = <div className={styles.box} style={{ width: 100, backgroundColor: '#80EADC' }} />

React Native:

import { View } from 'react-native'

const rendered = <View style={[styles.box, { width: 100, backgroundColor: '#80EADC' }]} />

Adding styles directly

const Box = zacs.view(styles.box)

const rendered = <Box zacs:style={{ width: 100, color: '#80EADC' }} />

This is equivalent to the example above, but instead of predefining list of props that turn into styles, we pass styles directly. Note that this only works on ZACS components.

Multiple unconditional styles

import styles from './styles'

const TitleText = zacs.text([styles.text, styles.titleText])

const rendered = <TitleText />
See compiled output

Web:

const rendered = <span className={styles.text + ' ' + styles.titleText} />

React Native:

import { Text } from 'react-native'

const rendered = <Text style={[styles.text, styles.titleText]} />

Styling custom components

import Touchable from 'components/Touchable'

const Button = zacs.styled(Touchable, styles.button, null, {
  width: 'width'
})

const rendered = <Button width={500} />
See compiled output

Web:

import Touchable from 'components/Touchable'

const rendered = <Touchable className={styles.button} style={{ width: 500 }} />

React Native:

import Touchable from 'components/Touchable'

const rendered = <Touchable style={[styles.button, { width: 500 }]} />

Making stylable components

To define new components that you can style using zacs.styled, use the special zacs:inherit prop to let ZACS know you want styles from props.style / props.className added in.

const Root = zacs.view(styles.root)

export default const Touchable = props => {
  return <Root zacs:inherit={props} />
}
See compiled output

Web:

export default const Touchable = props => {
  return <div className={styles.root + ' ' + (props.className || '')} style={props.style} />
}

React Native:

import { View } from 'react-native'

export default const Touchable = props => {
  return <View style={[styles.root].concat(props.style || [])} />
}

Configuring output component/element types

Sometimes you need to style a different component on web and native. To do this, use zacs.styled with { web: ComponentType, native: ComponentType } instead of a direct component reference.

const Paragraph = zacs.styled({ web: 'p', native: zacs.text }, styles.paragraph)

const rendered = <Paragraph>Hello world!</Paragraph>

As parameters, you can pass:

  • built in element type (string - p, div, form)
  • a component reference
  • magic zacs.text or zacs.view (this is so you can easily fall back to RN View/Text without importing react-native)

If you're only building for one platform, you can also reference built-ins like this:

const Paragraph = zacs.styled('p', styles.paragraph) // NOTE: No web/native, because this is web-only code

TODO: Passing zacs.text/view as parameter seems magic and gross. If you have a better idea for this API, let us know!

See compiled output

Web:

const rendered = <p className={styles.paragraph}>Hello world!</p>

React Native:

import { Text } from 'react-native'

const rendered = <Text style={styles.paragraph}>Hello world!</Text>

Exporting ZACS components

zacs.view/text/styled are special declarations for the compiler, not real components — that's the whole point of "zero abstraction cost styling".

Unfortunately, this means that you can only use those components in the same file in which they're defined, and you can't export it. And this is how you should use zacs most of the time. But sometimes, to avoid repetitive code, you really need this.

In that case, use zacs.createView/Text/Styled, which actually creates a real component:

export const Box = zacs.createView(styles.box)
export const Label = zacs.createText(styles.label, {
  isBold: style.labelBold,
}, null, ['title', 'numberOfLines'])
export const Wrapper = zacs.createView(styles.wrapper, null, null, ['ref'])

You must declare (in the last argument) all non-zacs props you want to be able to pass into the component you're styling (component props, DOM attributes, ref, and zacs:inherit, zacs:style).

Hey, that's really annoying, why do I need to do this?

A distinction between view and createView is necessary because Babel is a single file compiler, and it does not have visibility to imports, so an imported component can't be magically transformed into a <div /> or <View />.

So we have to de-optimize and do the next best thing -- export an actual component. Not quite zero abstraction cost, but almost.

There is another limitation: because the declaration doesn't see the callsite, we don't know whether someone wants to pass props (DOM attributes or View/Text RN props) to the underlying component, and we can't use {...props}, because you can't pass arbitrary attributes to DOM elements in ReactDOM (it will throw errors and can have unexpected side effects).

Honestly, needing to declare all used props is super annoying and I hate it. If you have a better idea on how to tackle this while staying as close as possible to the zero abstraction cost ideal, please let us know!.

See compiled output

Web:

export const Box = (props) => {
  return <div className={styles.box}>{props.children}</div>
}

export const Label = (props) => {
  return (
    // Note that `numberOfLines` is not passed on because it's not a DOM attribute
    <span className={styles.label + (props.isBold ? ' ' + styles.labelBold : '')} title={props.title}>
      {props.children}
    </span>
  )
}

// We add forwardRef if `ref` is an allowed attribute
export const Wrapper = React.forwardRef((props, ref) => {
  return <div className={styles.wrapper} ref={ref}>{props.children}</div>
})

React Native:

import { View, Text } from 'react-native'

export const Box = (props) => {
  return <Text style={styles.box}>{props.children}</Text>
}

export const Label = (props) => {
  return (
    <Text style={[styles.label, props.isBold && styles.labelBold]}
          title={props.title}
          numberOfLines={props.numberOfLines}>
      {props.children}
    </Text>
  )
}

export const Wrapper = React.forwardRef((props, ref) => {
  return <Text style={styles.wrapper} ref={ref}>{props.children}</Text>
})

Style precedence

From least important to most important:

  • main style (first argument to zacs.xxx())
  • conditional styles (second argument to zacs.xxx())
  • styles added via props (third argument to zacs.xxx())
  • styles added via zacs:style
  • styles added via zacs:inherit

For example, width passed via zacs:inherit will override width added via props.

Defining styles

Unlike popular "CSS-in-JS" libraries, zacs only provides the "component styling" part, but styles themselves are defined in a separate file. Here is how you define them.

React Native

// style.native.js
import { StyleSheet } from 'react-native'

export default StyleSheet.create({
  box: {
    backgroundColor: "#80EADC",
    width: 500,
  },
  highlighted: {
    // ...
  },
})

See React Native documentation for more details.

Web

We recommend using PostCSS in your Webpack config to make CSS styles importable from JS.

/* style.web.css */
.box {
  background: #80EADC;
  width: 500px;
}

.highlighted {
  /* ... */
}

ZACS shared styles

We're thinking of extending ZACS to defining styles, so that you can declare styles once in CSS and have them compile to both CSS and React Native StyleSheet in a "zero abstraction cost" fashion. If you're interested in this project — please contact us!

Troubleshooting

WIP - Please contribute!

Contributing

We need you

ZACS is an open-source project and it needs your help to thrive!

If there's a missing feature, a bug, poor documentation, or other improvement you'd like, don't ask what we can do to help you, ask what you can do to help the community. Feel free to open an issue to get some guidance, and then please send a pull request addressing your issue!

If you make a non-trivial contribution, email me, and I'll send you a nice ZACS sticker!

If you make an app using ZACS, please let us know!

Author and license

ZACS was created by @Nozbe. Main author and maintainer is Radek Pietruszewski.

ZACS is available under the MIT license. See the LICENSE file for more info.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].