Last active
April 26, 2019 05:29
-
-
Save hnordt/a16d1a67400e3307b95f01e71beb634d to your computer and use it in GitHub Desktop.
useCSS Hook
This file contains 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 { useGlobalCSS } from "./useCSS"; | |
const App = () => { | |
useGlobalCSS({ | |
"*": { | |
boxSizing: "border-box", | |
margin: 0, | |
padding: 0 | |
}, | |
ul: { | |
listStylePosition: "inside" | |
} | |
}); | |
return ( | |
<ThemeContext.Provider value={theme}> | |
<Suspense fallback={<LoadingScreen />}> | |
<Router> | |
<HomeScreen path="/" /> | |
<NotFoundScreen default /> | |
</Router> | |
</Suspense> | |
</ThemeContext.Provider> | |
); | |
}; |
This file contains 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 from "react"; | |
import * as RR from "@reach/router"; | |
import useCSS from "./useCSS"; | |
const Link = ({ to, children }) => ( | |
<RR.Link | |
className={useCSS(theme => ({ | |
color: theme.colors.blue, | |
textDecoration: "none", | |
"&:hover": { | |
textDecoration: "underline" | |
} | |
}))} | |
to={to} | |
> | |
{children} | |
</RR.Link> | |
); | |
export default Link; |
This file contains 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 { useContext, useMemo, useLayoutEffect } from "react"; | |
import * as R from "ramda"; | |
import { ThemeContext } from "Kits/CoreKit"; | |
// ******************** | |
// Constants | |
const UNITLESS_PROPS = { | |
fontWeight: true, | |
lineHeight: true, | |
flexGrow: true, | |
flexShrink: true, | |
zIndex: true, | |
opacity: true | |
}; | |
// ******************** | |
// Factories | |
const createStyleSheet = () => { | |
const styleEl = document.createElement("style"); | |
document.head.appendChild(styleEl); | |
return styleEl.sheet; | |
}; | |
const createRule = (selector, declarations) => | |
`${selector} { ${declarations.join("")} }`; | |
const createDeclaration = (prop, value) => | |
`${toKebab(prop)}: ${appendPxWhenNecessary(prop, value)};`; | |
// ******************** | |
// Singletons | |
const styleSheet = createStyleSheet(); | |
const cache = {}; | |
// ******************** | |
// Getters | |
const getClassName = css => { | |
const str = JSON.stringify(css); | |
let hash = 5381; | |
let i = str.length; | |
while (i) hash = (hash * 33) ^ str.charCodeAt(--i); | |
return "_" + (hash >>> 0).toString(36); | |
}; | |
// ******************** | |
// Inserters | |
const insertCSS = (css, selector = "") => { | |
const declarations = []; | |
Object.entries(css).forEach(([propOrSelector, valueOrCSS]) => { | |
if (valueOrCSS === undefined || valueOrCSS === null) return; | |
if (typeof valueOrCSS === "object") | |
return insertCSS( | |
valueOrCSS, | |
replaceAmpersandWith(selector, propOrSelector) | |
); | |
declarations.push(createDeclaration(propOrSelector, valueOrCSS)); | |
}); | |
insertRule(selector, declarations); | |
}; | |
const insertRule = (selector, declarations) => | |
declarations.length === 0 | |
? -1 | |
: styleSheet.insertRule( | |
createRule(selector, declarations), | |
styleSheet.cssRules.length | |
); | |
// ******************** | |
// Utils | |
const toKebab = R.pipe( | |
R.replace(/[A-Z]/g, "-$&"), | |
R.toLower | |
); | |
const replaceAmpersandWith = R.replace(/&/g); | |
const appendPxWhenNecessary = (prop, value) => | |
typeof value === "number" && !UNITLESS_PROPS[prop] ? value + "px" : value; | |
const maybeInsertCSS = css => { | |
const className = getClassName(css); | |
if (cache[className]) return cache[className]; | |
insertCSS(css, "." + className); | |
cache[className] = className; | |
return cache[className]; | |
}; | |
// ******************** | |
export const useGlobalCSS = css => | |
useLayoutEffect(() => void insertCSS(css), [css]); | |
const useCSS = (css, deps = []) => { | |
const theme = useContext(ThemeContext); | |
const className = useMemo( | |
R.pipe( | |
R.always(css), | |
R.when(R.is(Function), R.applyTo(theme)), | |
maybeInsertCSS | |
), | |
deps | |
); | |
return className; | |
}; | |
export default useCSS; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment