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.

We are not going to explain or even mention any syntax related to react-jss. To learn more about this awesome, blazing-fast and high performance styling library, we encourage you to read their documentation.

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;
makeStyles Specs
NameTypeDescription
stylesfunction | objectA 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?objectoptions.name?: string : The name of the style sheet. Useful for debugging.
The other keys are jss.createStyleSheet's options.
  • Returns useStylesHook: (data?: unknown) => Classes : This hook can be used in a function component. It accepts one argument: the properties that will be used for "interpolation" in the style sheet.
  • Arguments ending with ' ? ' are optional.

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.

ThemeProvider Props
NameTypeDefaultDescription
children?node-The component tree.
themefunction | objectdefaultThemeIf 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.
  • Arguments ending with ' ? ' are optional.

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.

SonnatInitializer Props
NameTypeDefaultDescription
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?objectdefaultThemeThe application's main theme object. (the parent theme object)
The initializer is going to use <ThemeProvider> with the provided them object.
disableGeneration?booleanfalseYou can disable the generation of the styles with this option. You can significantly speed up traversing the React tree with this property.
injectFirst?booleanfalseBy 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.
  • Arguments ending with ' ? ' are optional.

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;
}
ServerStyleSheets Instance Fields
FieldDescription
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.
  • Arguments ending with ' ? ' are optional.

createGenerateClassName

A function which returns a class name generator function.

interface GenerateClassNameOptions {
disableGlobal?: boolean;
productionPrefix?: string;
seed?: string;
}
function createGenerateClassName(
options?: GenerateClassNameOptions
): ClassNameGeneratorFn;
createGenerateClassName Specs
NameTypeDescription
options?objectoptions.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 "").
  • Returns ClassNameGeneratorFn : The generator function which should be provided to JSS.
  • Arguments ending with ' ? ' are optional.

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:

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>;
}