Skip to content

Instantly share code, notes, and snippets.

@hnordt
Last active April 26, 2019 05:29
Show Gist options
  • Save hnordt/a16d1a67400e3307b95f01e71beb634d to your computer and use it in GitHub Desktop.
Save hnordt/a16d1a67400e3307b95f01e71beb634d to your computer and use it in GitHub Desktop.
useCSS Hook
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>
);
};
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