Closed ricokahler closed 4 years ago
Alright so I've been experimenting and failing desperately (with lots of hacky code) so I'm now posting here as a lil call for help lol 😅
Anyway, I'm currently working on optimizing the CSS-in-JS solution which is a relatively involved task. Here is what I've done/what I'm trying to do:
react-style-system
To clarify, I'd like to ship both the standalone implementation and the SSR implementation. I think both will be useful depending the environment you're running code in (e.g. the standalone version is good for codesandbox, the SSR version is good for JAMstack apps).
I answered this question over on the readme of react-style-system
. The gist is: I really like Material UI's style system. It's really close to the features I'm looking for but there are a few things that could take it to the next level (in particular, near zero-runtime CSS support similar to linaria).
This issue is priority for me because I want a solid styling system foundation before we write more CSS.
First you import the function createStyles
which returns a hook
import React from 'react';
import { createStyles } from 'hacker-ui';
const useStyles = createStyles(({ css, theme }) => ({
root: css`
color: background-color: ${theme.colors.brand},
`,
title: css`
font-weight: bold;
`,
}));
This hook then intercepts your props. This allows it to compose over the incoming style-related props like className
and style
.
function MyComponent(props) {
// `Root` and `styles` are created by `useStyles`.
// `title` comes from this component's API
const { Root, styles, title, ...restOfProps } = useStyles(props);
return <Root>
<h1 className={styles.title}>{title}</h1>
</Root>
}
This is important because it enables the useStyles
hook to add style related props to the component's API including the styles
API. The styles API is exactly the same as the classes
API from Material UI (if you're familiar).
TL;DR, this useStyles
way of styling components is very nice because it's component-centric. I can go into details on why if you're curious.
Currently, the alpha version styling solution works 100% in the browser. It creates CSS classes during the component's mount via a useLayoutEffect
.
This is obviously less than ideal because the layout effect slows down the initial render. You can feel this when switching between pages in the docs that have lots of elements on them.
There are two paths I think we should take to optimizing the styling solution:
I have a some-what working proof of concept in my react-style-system repo. The gist of how it works is:
createStyles
so that the useStyles
hook can be invoked and injected with some mock values. Using some logic, we can determine whether a template literal expression should in a class definition should be converted into a CSS variable placeholder or executed with a static value. The end result is some static css that looks like this:.Anchor--28fdf510-root {
color: var(--Anchor--28fdf510-root-0);
-webkit-text-decoration: underline;
text-decoration: underline;
-webkit-transition: color 250ms;
transition: color 250ms;
}
.Anchor--28fdf510-root:active {
color: var(--Anchor--28fdf510-root-1);
}
createStyles
calls with CSS into the createStyles
calls that populate the CSS variables.// before transform
import { readableColor } from 'polished';
const useStyles = createStyles(({ css, theme }) => ({
// ignore the contents of the styles, they don't make sense
root: css`
background-color: ${theme.colors.brand};
color: ${readableColor(theme.colors.brand)};
`,
title: css`
color: ${theme.colors.brand};
`,
}));
// after transform
import { readableColor } from 'polished';
const useStyles = createStyles(({ theme }) => ({
root: [theme.colors.brand, readableColor(theme.colors.brand)],
title: [theme.colors.brand],
classNamePrefix: 'Example--32fa343e',
}));
The implementation of createStyles
will also be swapped out with an SSR version that takes the values from above and creates a style object that simply assigns CSS variables via the React style
prop. Below is a simplified version of the code to communicate what I mean.
// vastly simplified ssr createStyles
function createStylesSsr(styleFn) {
function useStyles(incomingProps, Component = 'div') {
const theme = useTheme();
const cssVariableValues = useMemo(() => {
const { classNamePrefix, ...classes } = styleFn({theme});
const flattenedEntries = Object.entries(classes).map(([classKey, cssVariableArray]) => {
return cssVariableArray.map((cssValue, i) => [`${classNamePrefix}-${classKey}-${i}`, cssValue]);
}).flat();
return Object.fromEntries(flattenedEntries);
}, [theme, styleFn]);
function Root(rootProps) {
return <Component
style={{
...rootProps.style,
...incomingProps.style,
...cssVariableValues,
}}
/>
}
return { Root, /* ... */ };
}
return useStyles;
}
the result of cssVariableValues
will look something like this:
{
'Example--32fa343e-root-0': '#00f',
'Example--32fa343e-root-1': '#000',
'Example--32fa343e-title-0': '#000',
}
and the way the hook is setup to return the Root
component, this root component can easily apply these style variables internally while still exposing a normal component API externally.
Long story short, I'd really like some help getting all of this working. I have a proof of concept (with some really hacky code) in the v0.1.0 branch in react-style-system
but I'm struggling getting this to work smoothly with babel.
If you're interested in this project. Let me know and we can set up some communication channels!
A lot of progress on this has been made in #27 . Take a look if you're interested!
I've eventually figured most of this out 🤷♀️
In case you missed it, Hacker UI ships with its own CSS-in-JS solution.
I feel very good about the direction of this CSS-in-JS solution however it needs more work until its stable and optimized. I started working on it in this repo until I realized it would be more organized to split it off into a separate project.
You can check the progress of that library here. It's called
react-style-system
.This issue will be up until this library uses
react-style-system
.