Styling
We needed a strong foundation for building our components as the building blocks for dynamic UI design. There are tons of styling solutions aiming to solve various styling problems.
A CSS-in-JS solution overcomes many of dynamic UI design limitations, and unlocks many great features (theme nesting, dynamic styles, self-support, etc.).
We are using ReactJSS (css-in-js styling solution) to styling our components. And since Material-UI v4 was using the same styling solution with such a User-friendly API that led to great DX (Developer Experience), we have decided to use an API which is similar to Material-UI's.
For the sake of simplicity, we expose the styling solution used in @sonnat/ui
components as the @sonnat/ui/styles
package.
The @sonnat/ui/styles API
This section covers API and usages of @sonnat/ui/styles
.
makeStyles
Links a style sheet with a function component using the hook pattern.
function makeStyles(styles, options?) => useStylesHook;
Name | Type | Description |
---|---|---|
styles | function | object | A function generating the styles or a styles object. It will be linked to the component. Use the function signature if you need to have access to the theme. It's provided as the first argument. |
options? | object | options.name?: string : The name of the style sheet. Useful for debugging.The other keys are jss.createStyleSheet's options. |
|
Example
import makeStyles from "@sonnat/ui/styles/makeStyles";const useThemedStyles = makeStyles(theme => ({root: { color: theme.colors.text.dark.primary }}),{ name: "MyThemedStyles" });function ComponentA(props) {const classes = useThemedStyles();return <div className={classes.root} />;}const useUnthemedStyles = makeStyles({root: {color: "yellow"}},{ name: "MyUnthemedStyles" });function ComponentB(props) {const classes = useUnthemedStyles();return <div className={classes.root} />;}const useDynamicStyles = makeStyles({root: {color: props => props.color}},{ name: "MyDynamicStyles" });function ComponentC(props) {const classes = useDynamicStyles(props);return <div className={classes.root} />;}
createTheme
Generates a theme object base on the options provided.
function createTheme<CustomOptions extends object = {}>(themeInput?: Partial<ThemeInput> & {custom?: CustomProps | ((theme: ThemeBase) => CustomProps);}): ThemeBase & { custom: CustomProps };
export interface ThemeInput {breakpoints: { values?: "xxs" | "xs" | "sm" | "md" | "lg" | "xl" | "xlg" };colors: {primary?: string | { origin?: string; light?: string; dark?: string };secondary?: string | { origin?: string; light?: string; dark?: string };error?: string | { origin?: string; light?: string; dark?: string };warning?: string | { origin?: string; light?: string; dark?: string };info?: string | { origin?: string; light?: string; dark?: string };success?: string | { origin?: string; light?: string; dark?: string };contrastThreshold?: number;};typography: {fontSize?: number;htmlFontSize?: number;ltrFontFamily?: string;rtlFontFamily?: string;monospaceFontFamily?: string;};mixins: {disableUserSelect?: () => CSSProperties;asIconWrapper?: (size?: number) => CSSProperties;[P: string]: any;};spacings: { spacer?: number };zIndexes: {sticky?: number;header?: number;drawer?: number;backdrop?: number;modal?: number;popover?: number;[P: string]: any;};direction: "ltr" | "rtl";darkMode: boolean;}export interface ThemeBase {mixins: ThemeInput["mixins"] & Mixins;zIndexes: ThemeInput["zIndexes"] & ZIndexes;spacings: Spacings;radius: Radius;breakpoints: Breakpoints;direction: Direction;colors: Colors;typography: Typography;darkMode: boolean;swatches: Swatches;hacks: {safariTransitionRadiusOverflowCombinationFix: React.CSSProperties;backfaceVisibilityFix: React.CSSProperties;};}
Example
import createTheme from "@sonnat/ui/styles/createTheme";interface CustomOptions {primaryHover: string;primaryActive: string;myOrangeColor: string;}const theme = createTheme<CustomOptions>({direction: 'rtl',colors: {primary: "blue",},typography?: {ltrFontFamily?: "Roboto";rtlFontFamily?: 'IRANSans';monospaceFontFamily?: "RobotoMono";},custom: (theme) => {return {primaryHover: theme.colors.createPrimaryColor({ alpha: 0.5 }),primaryActive: theme.colors.createPrimaryColor({ alpha: 0.7 }),myOrangeColor: "orange"}}});export default theme;
<ThemeProvider>
This component makes the provided theme
object available down the React-Tree.
Name | Type | Default | Description |
---|---|---|---|
children? | node | - | The component tree. |
theme | function | object | defaultTheme | If it is Function, then it's being applied to the theme from a parent ThemeProvider. If the result is an Object it will be passed down the react tree, throws otherwise. If it is Object, then it gets merged with the theme from a parent ThemeProvider and passed down the react tree. |
|
Example
const theme = { themed: true };const patch = { merged: true };<ThemeProvider theme={theme}>{/* { ...initializerTheme, themed: true } */}<ThemeProvider theme={patch}><DemoBox /> {/* { ...initializerTheme, themed: true, merged: true } */}</ThemeProvider></ThemeProvider>;
const theme = { themed: true };const augment = outerTheme => ({...outerTheme, { augmented: true }});<ThemeProvider theme={theme}>{/* { ...initializerTheme, themed: true } */}<ThemeProvider theme={augment}><DemoBox /> {/* { ...initializerTheme, themed: true, augmented: true } */}</ThemeProvider></ThemeProvider>;
useTheme
This hook returns the theme object so it can be used inside a function component.
function useTheme<Theme = DefaultTheme>(): Theme;
Example
import useTheme from "@sonnat/ui/styles/useTheme";export default function Component() {const theme = useTheme();return <div style={{ color: theme.colors.primary.origin }}></div>;}
<SonnatInitializer>
This component allows you to change the behavior of the styling solution. It makes the options available down the React tree thanks to the context.
It should preferably be used once and at the root of your component tree.
Name | Type | Default | Description |
---|---|---|---|
children? | node | - | Your component tree. |
generateClassName? | function | - | JSS's class name generator. |
jss? | instanceOf(jss.constructor) | - | JSS's new instance. You can create a new instance of jss with your desired configuration and change the Sonnat's styling solution with the help of this prop. |
theme? | object | defaultTheme | The application's main theme object. (the parent theme object) The initializer is going to use <ThemeProvider> with the provided them object. |
disableGeneration? | boolean | false | You can disable the generation of the styles with this option. You can significantly speed up traversing the React tree with this property. |
injectFirst? | boolean | false | By default, the styles are injected last in the <head> element of the page. As a result, they gain more specificity than any other style sheet. If you want to override Sonnat's styles, set this prop. |
|
ServerStyleSheets
This hook returns the theme object so it can be used inside a function component.
export interface ServerStyleSheetsOptions {/** The id attribute for <style> tag. */id?: string;/** JSS's class name generator. */generateClassName?: ClassNameGeneratorFn;}class ServerStyleSheets {constructor(options?: ServerStyleSheetsOptions);collect(children: React.ReactNode): React.ReactElement;toString(): string;getStyleElementId(): string;getStyleElement(props?: object): React.ReactElement;}
Field | Description |
---|---|
collect(children: React.ReactNode) | The method wraps a provider element around your React node. It tries to collect the style sheets during the rendering so they can be later sent to the client. |
toString() | The method returns the collected critical styles. Note that you must call .collect() before using this method. |
getStyleElementId() | Returns the id attribute for <style> element. |
getStyleElement(props?: object) | The method is an alternative to .toString() . It returns a <style> element with all the collected critical styles injected into it.Note that you must call .collect() before using this method. |
|
createGenerateClassName
A function which returns a class name generator function.
interface GenerateClassNameOptions {disableGlobal?: boolean;productionPrefix?: string;seed?: string;}function createGenerateClassName(options?: GenerateClassNameOptions): ClassNameGeneratorFn;
Name | Type | Description |
---|---|---|
options? | object | options.disableGlobal?: boolean : Disable the generation of deterministic class names (Defaults to false ).options.productionPrefix?: string : The string used to prefix the class names in production (Defaults to "jss" ).options.seed?: string : The string used to uniquely identify the generator. It can be used to avoid class name collisions when using multiple generators in the same document (Defaults to "" ). |
|
jssPreset
This function returns a set of jss plugins that @sonnat/ui
is using at its core. The following (which is a subset of jss-preset-default) are included:
- jss-plugin-rule-value-function
- jss-plugin-global
- jss-plugin-nested
- jss-plugin-extend
- jss-plugin-camel-case
- jss-plugin-default-unit
- jss-plugin-vendor-prefixer
- jss-plugin-props-sort
type JssPreset = { plugins: readonly JssPlugin[] };function jssPreset(): JssPreset;
useDarkMode
This hook gets a isDarkMode
boolean flag and a theme
object and returns the manipulated theme object base on the given flag, so it can be used inside a function component or pass down the React-Tree via <ThemeProvider> or <SonnatInitializer>.
function useDarkMode(isDarkMode?: boolean, theme?: Theme): Theme;
Example
import useStore from "some/state/manager";import SonnatInitializer from "@sonnat/ui/styles/SonnatInitializer";import useDarkMode from "@sonnat/ui/styles/useDarkMode";import theme from "./theme";export default function App() {const isDarkMode = useStore(state => state.isDarkMode);const newTheme = useDarkMode(isDarkMode, theme);return <SonnatInitializer theme={newTheme}>...</SonnatInitializer>;}