Frontend
Theme

Basic

MetaFox Theme system is based on Material-UI (opens in a new tab). A ThemeProvider component is created and you are able to access all standard Material-UI features in MetaFox platform.

Theming

Theming is the ability to systematically customize site look & feel to reflect better your product's brand such as color, spacing, round corner, shadow, background and typography.

MetaFox provides MaterialUI ThemeProvider at RootContainer, you can access to add new variables to Theme Provider.

Theme Custom

You can create a custom theme to customize variable and structure layout.

Example for flatten theme:

Create theme flatten:

/**
 * @type: theme
 * name: flatten
 * system: true
 * bundle: web
 */
import styles from './styles.json';
import processors from './processors';
 
const config = {
  originBlockLayouts: require('./layout.blocks.origin.json'),
  originGridLayouts: require('./layout.grids.origin.json'),
  originItemLayouts: require('./layout.items.origin.json'),
  originNoContentLayouts: require('./layout.noContents.origin.json'),
  originTemplates: require('./layout.templates.origin.json'),
  originPageLayouts: require('./layout.pages.origin.json'),
  originIntergrationLayouts: require('./layout.intergration.origin.json'),
  originSiteBlocks: require('./layout.siteBlocks.origin.json'),
  originSiteDocks: require('./layout.siteDocks.origin.json'),
  originSiteFixedDocks: require('./layout.siteFixedDocks.origin.json'),
  blockLayouts: require('./layout.blocks.json'),
  gridLayouts: require('./layout.grids.json'),
  itemLayouts: require('./layout.items.json'),
  noContentLayouts: require('./layout.noContents.json'),
  templates: require('./layout.templates.json'),
  pageLayouts: require('./layout.pages.json'),
  siteBlocks: require('./layout.siteBlocks.json'),
  pageSizesPriority: {
    medium: ['large']
  },
  processors,
  styles
};
 
export default config;
 

You can pass a custom CSS for custom theme

Component custom

// packages/metafox/theme-flatten/src/processors/Components.ts
import { Theme } from '@mui/material';
 
export default function overridesComponents(theme: Theme): void {
  // https://v4.mui.com/api/button/#css
  if (!theme.components) {
    theme.components = {};
  }
 
  theme.components.MuiTruncateText = {
    defaultProps: {
      component: 'div'
    }
  };
 
  // https://mui.com/api/form-helper-text/#css
  theme.components.MuiFormHelperText = {
    styleOverrides: {
      root: {
        fontSize: '13px',
        color: theme.palette.grey['500'],
        whiteSpace: 'pre-wrap',
        margin: 0,
        marginTop: '6px'
      }
    }
  };
 
  theme.components.MuiSkeleton = {
    variants: [
      {
        props: { variant: 'avatar' },
        style: { borderRadius: '50%' }
      },
      {
        props: { variant: 'button' },
        style: { borderRadius: 4 }
      },
      {
        props: { variant: 'icon-button' },
        style: { borderRadius: '50%' }
      }
    ]
  };
 
  theme.components.MuiDialog = {
    styleOverrides: {
      paper: {
        backgroundImage: 'none',
        transition: 'max-height 1s ease-in-out'
      }
    },
    variants: [
      {
        props: { variant: 'alert' },
        style: {
          '& .MuiDialogContent-root': {
            paddingTop: `${16}px !important`
          }
        }
      }
    ]
  };
 
}
 

Css custom

import { Theme } from '@mui/material';
 
export default function overridesGlobalStyles(theme: Theme) {
  if (!theme.components) {
    theme.components = {};
  }
 
  theme.components.MuiCssBaseline = {
    styleOverrides: {
      html: {
        WebkitFontSmoothing: 'auto',
        fontSize: '16px'
      },
      body: {
        fontFamily: theme.fontFamily,
        overflowX: 'hidden',
        fontSize: '0.8125rem',
        WebkitTapHighlightColor: 'transparent',
        [theme.breakpoints.up('md')]: {
          minHeight: '100vh'
        }
      },
      a: {
        color: theme.palette.primary.main
      }
    }
  };
}

styled

You can write custom styles by using styled, makeStyles, creatStyles features (Material UI (opens in a new tab)), look like the below example:

import { styled, Tooltip } from '@mui/material';
import HtmlViewer from '@metafox/html-viewer';
import React from 'react';
 
const name = 'BlogItemView';
 
const FlagWrapper = styled('span', {
  slot: 'FlagWrapper',
  name
})(({ theme }) => ({
  display: 'inline-flex'
}));

Control className logic

You can control React className properties by classnames or clsx tools. clsx is a choice of Material-UI. Thus, we should not add others tools for the same features.

For more info, you can check trends (opens in a new tab) and clsx (opens in a new tab)

Variables

Declare variables for theme

{
  "default": {
    "searchFormWidth": 320,
    "fontFamily": "system-ui, -apple-system, system-ui, \"Segoe UI\", \".SFNSText-Regular\", Roboto, Helvetica, \"sans-serif\"",
    "breakpoints": {
      "values": {
        "xs": 0,
        "sm": 720,
        "md": 1024,
        "lg": 1281,
        "xl": 1920
      }
    },
    "block": {
      "width": "250px"
    },
    "middleBlock": {
      "maxWidth": "720px"
    },
    "mixins": {},
    "typography": {
      "fontFamily": "system-ui, -apple-system, system-ui, \"Segoe UI\", \".SFNSText-Regular\", Roboto, Helvetica, \"sans-serif\"",
      "htmlFontSize": 16,
      "fontSize": 14,
      "fontWeightLight": 300,
      "fontWeightRegular": 400,
      "fontWeightMedium": 500,
      "fontWeightSemiBold": 600,
      "fontWeightBold": 700,
      "h1": {
        "fontWeight": 700,
        "fontSize": "40px",
        "lineHeight": 1.2,
        "letterSpacing": "0"
      },
      "h2": {
        "fontWeight": 700,
        "fontSize": "32px",
        "lineHeight": 1.25,
        "letterSpacing": "0"
      },
      "h3": {
        "fontWeight": 700,
        "fontSize": "24px",
        "lineHeight": 1.35,
        "letterSpacing": "0"
      },
      "h4": {
        "fontWeight": 700,
        "fontSize": "18px",
        "lineHeight": 1.3333333333333333,
        "letterSpacing": "0"
      },
      "h5": {
        "fontWeight": 700,
        "fontSize": "15px",
        "lineHeight": 1.3333333333333333,
        "letterSpacing": "0"
      },
      "h6": {
        "fontWeight": 700,
        "fontSize": "13px",
        "lineHeight": 1.3076923076923077,
        "letterSpacing": "0"
      },
      "subtitle1": {
        "fontWeight": 700,
        "fontSize": "18px",
        "lineHeight": 1.3333333333333333,
        "letterSpacing": "0"
      },
      "subtitle2": {
        "fontSize": "16px",
        "lineHeight": 1.5
      },
      "body1": {
        "fontSize": "15px",
        "lineHeight": 1.33
      },
      "body2": {
        "fontSize": "0.8125rem",
        "lineHeight": 1.33
      }
    },
    "gutter": 16,
    "shape": {
      "borderRadius": 0
    },
    "blockShadow": "none",
    "avatarSize": {
      "medium": 48,
      "small": 32
    },
    "appBarHeight": {
      "normal": 58,
      "fixed": 1
    },
    "popoverWidth": {
      "appbar": 360
    },
    "gridPoint": 8,
    "nav": {
      "background": "#fff",
      "color": "#828080",
      "menuColor": "red",
      "menuBgHover": "#aeacac",
      "textColor": "#050505",
      "searchColor": "#828080"
    },
    "palette": {
      "default": {
        "main": "#050505",
        "light": "#050505",
        "dark": "#050505",
        "contrastText": "#fff"
      },
      "primary": {
        "main": "#2682d5",
        "light": "#4a97dc",
        "dark": "#0a71cd"
      },
      "success": {
        "main": "#31a24a",
        "light": "#50b967",
        "dark": "#198b32"
      },
      "warning": {
        "main": "#f4b400",
        "light": "#ffca34",
        "dark": "#c08e00"
      },
      "error": {
        "main": "#f02848",
        "light": "#f34e68",
        "dark": "#d20425"
      },
      "info": {
        "main": "#0288d1",
        "light": "#03a9f4",
        "dark": "#01579b"
      },
      "secondary": {
        "main": "#050505",
        "light": "#2d2d2d",
        "dark": "#050505"
      },
      "grey": {
        "50": "#fafafa",
        "100": "#f5f5f5",
        "200": "#ededed",
        "300": "#e0e0e0",
        "400": "#bdbdbd",
        "500": "#aeacac",
        "600": "#828080",
        "700": "#616161",
        "800": "#494949",
        "900": "#2d2d2d",
        "A100": "#212121",
        "A200": "#191919",
        "A400": "#0d0d0d",
        "A700": "#050505"
      },
      "border": {
        "primary": "#2682d5",
        "secondary": "#ededed",
        "outlined": "transparent"
      },
      "background": {
        "default": "#ededed",
        "paper": "#fff",
        "secondary": "#ededed"
      },
      "appBar": "#fff",
      "divider": "#ededed",
      "action": {
        "active": "rgba(0,0,0,0.54)",
        "hover": "rgba(0,0,0,0.04)",
        "hoverOpacity": 0.04,
        "selected": "rgba(0,0,0,0.08)",
        "selectedOpacity": 0.08,
        "disabled": "rgba(0,0,0,0.26)",
        "disabledBackground": "rgba(0,0,0,0.12)",
        "disabledOpacity": 0.38,
        "focus": "rgba(0,0,0,0.12)",
        "focusOpacity": 0.12,
        "activatedOpacity": 0.12
      },
      "button": {
        "hover": "rgb(38, 132, 214, 0.04)"
      },
      "text": {
        "primary": "#050505",
        "secondary": "#616161",
        "disabled": "#bdbdbd",
        "hint": "#aeacac"
      }
    },
    "layoutSlot": {
      "background": {
        "paper": "#f5f5f5"
      },
      "points": {
        "xs1": 306,
        "xs2": 322,
        "xs3": 400,
        "xs": 400,
        "sm1": 600,
        "sm": 720,
        "md": 1024,
        "lg": 1200,
        "xl": 1920
      }
    },
    "blockDivider": {
      "fullWidth": {
        "display": "block",
        "margin": "0",
        "height": "1px",
        "background": "#ededed"
      },
      "middle": {
        "display": "block",
        "margin": "0 16px",
        "height": "1px",
        "background": "#ededed"
      }
    },
    "headerSearchForm": {
      "width": 512,
      "root": {
        "width": 512,
        "left": 0,
        "position": "absolute",
        "transform": ""
      },
      "form": {
        "width": 480
      }
    },
    "siteBackground": "#eef2f4 fixed 0 0"
  },
  "dark": {
    "palette": {
      "default": {
        "light": "#fff",
        "main": "#fff",
        "dark": "#fff",
        "contrastText": "#fff"
      },
      "border": {
        "primary": "#434a55",
        "secondary": "#4b5360",
        "outlined": "#525b69"
      },
      "background": {
        "default": "#2d2d2d",
        "paper": "#434a55",
        "secondary": "#2d2d2d"
      },
      "appBar": "#434a55",
      "divider": "#4b5360",
      "action": {
        "active": "rgba(255, 255, 255, 0.54)",
        "hover": "rgba(255, 255, 255, 0.08)",
        "hoverOpacity": 0.08,
        "selected": "rgba(255, 255, 255, 0.16)",
        "selectedOpacity": 0.16,
        "disabled": "rgba(255, 255, 255, 0.26)",
        "disabledBackground": "rgba(255, 255, 255, 0.12)",
        "disabledOpacity": 0.38,
        "focus": "rgba(255, 255, 255, 0.12)",
        "focusOpacity": 0.12,
        "activatedOpacity": 0.24
      },
      "button": {
        "hover": "rgba(38, 130, 213, 0.08)"
      },
      "text": {
        "primary": "#fff",
        "secondary": "#cecece",
        "disabled": "#bdbdbd",
        "hint": "#828080"
      },
      "error": {
        "main": "#f34e68"
      }
    },
    "blockDivider": {
      "fullWidth": {
        "background": "#828080"
      },
      "middle": {
        "background": "#828080"
      }
    },
    "siteBackground": "#545d6b fixed 0 0"
  }
}
 

Dark mode vs Light mode

Metafox comes with two palette modes: light (the default) and dark.

As example above, we have "dark" property for define custom variable for dark mode.

Typescript

In other to extend declarations, please read Customization of theme (opens in a new tab) and Package Augmentation (opens in a new tab)

Hook

You can use useTheme in pure function to get theme variables as the below example:

import { useTheme } from "@metafox/framework/theme";
 
export const MyComponent = () => {
  const theme = useTheme();
  return (
    <span
      style={{ background: theme.status.background, color: theme.status.color }}
    >
      Using Theme Variable
    </span>
  );
};

HOC

Example:

import { withTheme } from "@metafox/framework/theme";
 
function DeepChildRaw(props) {
  const theme = useTheme();
  return (
    <span
      style={{ background: theme.status.background, color: theme.status.color }}
    >
      Using Theme Variable
    </span>
  );
}
 
const DeepChild = withTheme(DeepChildRaw);