gatsby-plugin-eufemia-theme-handler
v1.9.0
Published
Gatsby Plugin to handle visual themes for @dnb/eufemia
Downloads
1,138
Maintainers
Readme
Gatsby Plugin to load Eufemia Themes
This plugin is a easy to use drop-in solution to load different DNB Eufemia Themes. It also support changing a theme in production runtime.
It looks for @dnb/eufemia
– nearest located from where this plugin is used (node_modules).
Features
- The current theme used is stored in the Browsers localStorage under the key
eufemia-theme
- You can define a theme in the URL:
https://eufemia.dnb.no/?eufemia-theme=sbanken
- Automatically splits theme styles into separate Webpack chunks, not matter if you have imported them already in your app or not
- Supports both build an dev mode with fast refresh and hot module replacement
- Loads only one theme package at a time. When the user switches to another theme, a new CSS theme file will be downloaded.
- Wraps your app with the Eufemia
<Theme>
provider. - Loads the core styles by default.
- Ensures page navigation do not scroll with animation.
How to use
- Remove theme imports, but keep the core:
- import '@dnb/eufemia/style/core'
- import '@dnb/eufemia/style/themes/ui'
- import '@dnb/eufemia/style/themes/sbanken'
- This plugin also wraps your application with the
<Theme>
provider. If you want to wrap your apps by yourself, you can disable this by using this option:wrapWithThemeProvider: false
. You could make your own wrapper like so:
import { Theme } from '@dnb/eufemia/shared'
import { useThemeHandler } from 'gatsby-plugin-eufemia-theme-handler'
function ThemeProvider({ children }) {
const theme = useThemeHandler()
return <Theme {...theme}>{children}</Theme>
}
- Install
yarn add gatsby-plugin-eufemia-theme-handler
and add it to yourgatsby-config.js
file:
// gatsby-config.js
{
plugins: [
{
resolve: 'gatsby-plugin-eufemia-theme-handler',
options: {
// (required) define your default theme
defaultTheme: 'ui',
// (required) define your themes
themes: {
ui: { name: 'DNB Eufemia' },
sbanken: { name: 'DNB Sbanken', hide: true },
},
// (optional) defaults to "eufemia-theme" (localStorage)
storageId: 'your-custom-id',
// (optional) whether to include your own local styles. More information down below.
includeLocalStyles: true,
// (optional) defines with a glob where the styles are placed inside of @dnb/eufemia/...
filesGlobs: [
'**/style/dnb-ui-core.min.css',
'**/style/themes/**/*-theme-{basis,components}.min.css',
],
// (optional) The file order does matter. Define a glob inside an array.
includeFiles: [
'**/dnb-ui-core.*',
'**/*-theme-components.*',
'**/*-theme-basis.*',
],
// (optional) An array of RegExp that defines what should be threaded and splitted into themes.
themeMatchers: [/\/themes\/[^/]*theme-([^/.]*)[/.]/],
// (optional) when set to false, all theme styles will be loaded as separate files.
inlineDefaultTheme: true,
// (optional) when set to false, your app will not be wrapped with the needed `<Theme>` provider.
wrapWithThemeProvider: true,
// (optional) when set to true, the page scroll behaviour will not be effected.
omitScrollBehavior: false,
// (optional) informs you with all relevant files.
verbose: false,
},
},
]
}
<<<<<<< HEAD
You can also import local themes. They need to start with ./
when defined in filesGlobs
and the files need to include theme-{theme-name}
in the name:
// Your Config
import {
filesGlobsFallback,
includeFilesFallback,
} from 'gatsby-plugin-eufemia-theme-handler/config.js'
export default {
plugins: [
'gatsby-plugin-sass',
{
resolve: 'gatsby-plugin-eufemia-theme-handler',
options: {
verbose: true,
defaultTheme: 'ui',
storageId: 'eufemia-ui',
filesGlobs: [
// Eufemia Styles
...filesGlobsFallback,
// Local themes
'./**/styles/themes/**/*.css',
],
includeFiles: [
// Eufemia Styles
...includeFilesFallback,
// Local themes
'**/styles/themes/**/*.css',
],
themes: {
ui: { name: 'DNB Eufemia' },
sbanken: { name: 'Sbanken' },
},
},
},
],
}
You can also use the interceptor methods from inside your components:
Your own theme styles
You can also import your own local themes by enabling includeLocalStyles
.
c0bcd82 (feat: import local themes (#27))
// gatsby-config.js
{
plugins: [
'gatsby-plugin-sass',
{
resolve: 'gatsby-plugin-eufemia-theme-handler',
options: {
defaultTheme: 'ui',
includeLocalStyles: true,
themes: {
ui: { name: 'DNB Eufemia' },
sbanken: { name: 'Sbanken' },
},
},
},
],
}
Your file structure would then need to be as so (this can be customized):
/src/.../styles/themes/theme-ui.scss
/src/.../styles/themes/theme-sbanken.scss
or
/src/.../styles/themes/.../theme-ui.scss
/src/.../styles/themes/.../theme-sbanken.scss
Further local theme styles customization
They need to start with ./
when defined in filesGlobs
:
import {
filesGlobsFallback,
includeFilesFallback,
} from 'gatsby-plugin-eufemia-theme-handler/config.js'
export default {
plugins: [
'gatsby-plugin-sass',
{
resolve: 'gatsby-plugin-eufemia-theme-handler',
options: {
verbose: true,
defaultTheme: 'ui',
storageId: 'eufemia-ui',
filesGlobs: [
// Eufemia Styles
...filesGlobsFallback,
// Local themes
'./**/styles/themes/**/*.css',
],
includeFiles: [
// Eufemia Styles
...includeFilesFallback,
// Local themes
'**/styles/themes/**/*.css',
],
themes: {
ui: { name: 'DNB Eufemia' },
sbanken: { name: 'Sbanken' },
},
},
},
],
}
The file and folder structure is defined in themeMatchers
and can also be customized if needed.
Switch a theme in runtime
You can also use the interceptor methods from inside your components:
// Your React Component
import {
getThemes,
getTheme,
setTheme,
useThemeHandler,
} from 'gatsby-plugin-eufemia-theme-handler'
// Example usage
setTheme({ name })
// or with callback
setTheme({ name }, (theme: ThemesItem) =>
console.log(`Theme updated to: ${theme.name}`)
)
const { name } = getTheme()
const themes = getThemes() // Same as in the plugin config
function Component() {
const { name } = useThemeHandler()
}
How it works
Gatsby bundles all styles into one single Webpack chunk (commons.css) and inlines it into every HTML page as inline styles with the attribute data-identity="gatsby-global-css"
.
What this plugin does is:
- Collect all
eufemia-theme
files ({scss,css}
) – also check if they are located in/src
or needs to be collected from/build
. Both are used by the Eufemia repo/portal. - After we have collected all available theme files, we create or update a static import
load-eufemia-styles.js
, which is git-ignored. - Split theme styles into separate CSS files (Webpack chunks) inside
gatsby-node.js
- Inserts some JavaScript in the HTML head in order to handle what theme file should be shown (
inlineScript
andinlineScriptDev
) - Load these inline scripts via Webpack inline module loaders:
!raw-loader!terser-loader!
- By using localStorage, we block the HTML rendering, this way we do avoid flickering of a default theme
In prod
- Leave the default theme style as a separate inline style, but move it after
commons.css
– this is how the styles should be imported in the first place (theme files after all other css packages) – if not, visual tests will fail, as we get wrong CSS specificity - Remove all other themes styles to be inlined, but keep track on the CSS files, defined in
data-href
- Only set the link href if the current theme is not the default one
In dev
During dev, we do not get any inline styles from Gatsby – they are handled by Webpack only via the hot module replacement.
- Now, that we have split out the themes styles in separate CSS files
/${key}.css
, we simply need to load them as plain css files via a link with href inside the head element. - During runtime, we need to ensure that our link with the id
eufemia-style-theme
is placed aftercommons.css
. We do that withheadElement.appendChild(styleElement)
- Use
uniqueId
to reload css files as there is not unique build hash, unlike we get during production - Use
MutationObserver
to reload the current theme file, because Webpack uses hot module replacement, so we need to reload as well
Sorting order
The order of the extracted styles can influence CSS specificity. Therefore, the extracted theme styles (/ui.css
) should always be placed below the /commons.css
.
Releases
Releases are made with semantic-release.
Decorate your pull requests with either fix: your release message
or feat: your release message
.
When a pull request is merged into the main
branch, a new release will be published.