Last active
January 26, 2023 06:03
-
-
Save itsjavi/aa636c02737509a09be117204b07ddcc to your computer and use it in GitHub Desktop.
Simple CSS-in-JS implementation for React, using tagged templates, with scoped class name generation. Zero dependencies.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect } from 'react' | |
type StyledFC = React.FC<{ children?: React.ReactNode }> | |
type StyledModule = <T extends string[]>( | |
...classNames: T | |
) => { | |
Styled: StyledFC | |
classMap: Record<T[number], string> | |
} | |
let cssModuleCounter = 0 | |
function css(strings: TemplateStringsArray, ...values: any[]): StyledModule { | |
const cssStr = String.raw({ raw: strings }, ...values) | |
return (...classNames: string[]) => { | |
cssModuleCounter++ | |
const moduleId = | |
Buffer.from(cssStr + classNames.join(',')) | |
.toString('base64') | |
.replaceAll(/[^a-zA-Z0-9_-]/g, '') | |
.slice(1, 9) + `${cssModuleCounter}` | |
let cssStrModule = cssStr | |
let classMap: Record<string, string> = {} | |
classNames.forEach((className) => { | |
const newClassName = `${className}-${moduleId}` | |
classMap[className] = newClassName | |
cssStrModule = cssStrModule.replace( | |
new RegExp(`(\\.${className})([^a-zA-Z0-9_-])`, 'g'), | |
`.${newClassName}$2` | |
) | |
}) | |
const Styled: StyledFC = (props) => { | |
useEffect(() => { | |
const styleId = 'supercss-' + moduleId | |
if (document.getElementById(styleId)) { | |
console.warn(`Stylesheet ${styleId} already exists. Skipping.`) | |
return | |
} | |
const style = document.createElement('style') | |
style.id = styleId | |
style.innerHTML = cssStrModule | |
document.head.appendChild(style) | |
return () => { | |
document.head.removeChild(style) | |
} | |
}, []) | |
if (!props || !props.children) { | |
return null | |
} | |
return <>{props.children}</> | |
} | |
return { Styled, classMap } | |
} | |
} | |
export default css |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Head from 'next/head' | |
import React from 'react' | |
type StyledFC = React.FC<{ children?: React.ReactNode }> | |
type StyledModule = <T extends string[]>( | |
...classNames: T | |
) => { | |
Styled: StyledFC | |
classMap: Record<T[number], string> | |
} | |
let cssModuleCounter = 0 | |
function css(strings: TemplateStringsArray, ...values: any[]): StyledModule { | |
const cssStr = String.raw({ raw: strings }, ...values) | |
return (...classNames: string[]) => { | |
cssModuleCounter++ | |
const moduleId = | |
Buffer.from(cssStr + classNames.join(',')) | |
.toString('base64') | |
.replaceAll(/[^a-zA-Z0-9_-]/g, '') | |
.slice(1, 9) + `${cssModuleCounter}` | |
let cssStrModule = cssStr | |
let classMap: Record<string, string> = {} | |
classNames.forEach((className) => { | |
const newClassName = `${className}-${moduleId}` | |
classMap[className] = newClassName | |
cssStrModule = cssStrModule.replace( | |
new RegExp(`(\\.${className})([^a-zA-Z0-9_-])`, 'g'), | |
`.${newClassName}$2` | |
) | |
}) | |
const Styled: StyledFC = (props) => { | |
const styleInjection = ( | |
<Head> | |
<style | |
id={'supercss-' + moduleId} | |
dangerouslySetInnerHTML={{ __html: cssStrModule }} | |
/> | |
</Head> | |
) | |
if (!props || !props.children) { | |
return styleInjection | |
} | |
return ( | |
<> | |
{styleInjection} | |
{props.children} | |
</> | |
) | |
} | |
return { Styled, classMap } | |
} | |
} | |
export default css |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Link from 'next/link' | |
import { useRouter } from 'next/router' | |
import css from './CSS-in-JS-withSSR' | |
import { cssVars } from './configs' | |
import { classNames } from './utils' | |
const { Styled, classMap } = css` | |
.root { | |
display: flex; | |
font-size: ${cssVars.fontSizes.xs}; | |
background-color: ${cssVars.colors.dark1}; | |
justify-content: space-between; | |
color: ${cssVars.colors.light2}; | |
} | |
.root > div { | |
padding: ${cssVars.sizes.default}; | |
} | |
.rightLinks a { | |
margin-left: ${cssVars.sizes.m}; | |
} | |
.rightLinks a:hover, | |
.rightLinks a.active { | |
color: ${cssVars.colors.light1}; | |
} | |
a.mainTitle { | |
text-decoration: none; | |
font-weight: bold; | |
font-size: 1.1rem; | |
background: #db5cfe; | |
background: linear-gradient(to right, #db5cfe 0%, #31fab9 100%); | |
background-clip: text; | |
-webkit-background-clip: text; | |
-moz-background-clip: text; | |
-ms-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
-moz-text-fill-color: transparent; | |
-ms-text-fill-color: transparent; | |
line-height: 1; | |
} | |
a.mainTitle img { | |
vertical-align: middle; | |
display: inline-block; | |
aspect-ratio: 1; | |
width: 1.4rem; | |
height: 1.4rem; | |
line-height: 1; | |
margin-right: 0.8rem; | |
} | |
`('root', 'rightLinks', 'mainTitle') | |
export default function Example() { | |
const currentPath = useRouter().pathname | |
return ( | |
<Styled> | |
<div className={classMap.root}> | |
<div> | |
<Link className={classMap.mainTitle} href="/"> | |
<img src="/favicon.png" alt="logo" /> | |
My Admin Tool | |
</Link> | |
</div> | |
<div className={classMap.rightLinks}> | |
<a | |
target="_blank" | |
rel="noreferrer" | |
href="https://github.com/itsjavi" | |
> | |
Github | |
</a> | |
<Link | |
href="/settings" | |
className={classNames([currentPath === '/settings', 'active'])} | |
> | |
Settings | |
</Link> | |
<Link | |
href="/about" | |
className={classNames([currentPath === '/about', 'active'])} | |
> | |
About | |
</Link> | |
</div> | |
</div> | |
</Styled> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
style
element, duplicating them.CSS-in-JS-next.tsx
version uses SSR and NextJS, to add the styles to the head in build time, not on component mount, so it's more convenient and faster.<Styled />
anywhere in your component, it doesn't need to be the root element.css
function checking this example: