Getting started
You are now ready to get started. Here you will find a step-by-step guide to making changes in the Eufemia repo. If you are new to the repository, first check out what I should know before getting started.
- Get the repo on your local computer
- Install the dependencies
- Making changes
- Make and run tests
- (Optional) Update change logs
- Commit changes
Get the repo on your local computer
- Clone the repo if you have commit access,
git clone https://github.com/dnbexperience/eufemia.git
- or Fork the repo by clicking
Fork
in the top right corner in Eufemia on GitHub.
Install the dependencies
yarn install
Making changes
Check out a new branch
Make a new working branch and name it e.g. fix/my-branch-name
or feat/my-feature-name
. Check out Git convention for naming.
# Make a Feature branch$ git checkout -b feat/my-feature
Add changes
Inside ./packages/dnb-eufemia
you will find the directory /src/components
or /src/extensions
. There you can place a new directory with all the necessary sub folders. As a reference, take a look at Component folder section in Before getting started.
Run an environment with either yarn dev
(for Storybook) or yarn start
(for Eufemia Portal). Make sure you follow the Code guide under development.
Styling, CSS and SCSS of components
Each component has two or three SCSS files.
All layout and position related styles go here:
./packages/dnb-eufemia/src/components/button/style/dnb-button.scss
CSS packages
SCSS file names starting with dnb-
are later possible to get imported as individual packages:
./packages/dnb-eufemia/src/components/button/style/dnb-button.scss
Style dependencies
In order to test related style dependencies of components, we add style imports in the deps.scss
file, which again is used in Jest tests to perform a snapshot comparison:
./packages/dnb-eufemia/src/components/button/style/deps.scss
SCSS Theming
Styles that belong to a "theming footprint" – like colors or individual variants – can be put inside the /themes
directory:
./packages/dnb-eufemia/src/components/button/style/themes/dnb-button-theme-ui.scss
Theming file names ending with -ui
will during the package release get packed into the global theming package. More details in the theming section.
SCSS utilities
Use the same SASS setup as all the other components. You may re-use all the helper classes:
./packages/dnb-eufemia/src/style/core/utilities.scss
Create a local build
Next, we need to create a local build (prebuild) by using yarn build
again.
Running the build command will walk through all parts and tie together all needed parts (index files of new components) in order to generate valid build bundles.
$ yarn build
You can find the output in the ./packages/dnb-eufemia/build
folder.
Additional component support
Locale support
Put your translation inside: ./packages/dnb-eufemia/src/shared/locales/nb-NO.js
as well as to the en-GB.js
file:
export default {'nb-NO': {MyComponent: {myString: '...',},},}
And use it as so:
import { Context } from '../../shared'import { extendPropsWithContext } from '../../shared/component-helper'import type { LocaleProps } from '../../shared/types'export type ComponentProps = {myParam?: string}export type ComponentAllProps = ComponentProps &LocaleProps &React.HTMLProps<HTMLElement>const defaultProps = {myParam: 'value',}function MyComponent(props: ComponentAllProps) {const context = React.useContext(Context)const { myString } = extendPropsWithContext(props,defaultProps,context.getTranslation(props).MyComponent, // details below 👇// ...)// Use myString ...}
The function getTranslation
will along with the properties support both locale
and the HTML lang
attribute. This way, these properties can be set by a component basis and a context basis.
Provider support
import { Context } from '../../shared'import { extendPropsWithContext } from '../../shared/component-helper'export type ComponentProps = {myParam?: string}export type ComponentAllProps = ComponentProps &LocaleProps &React.HTMLProps<HTMLElement>const defaultProps = {myParam: 'value',}function MyComponent(props: ComponentAllProps) {const context = React.useContext(Context)const { myParam, ...rest } = extendPropsWithContext(props,defaultProps,context.MyComponent,// ...)// Use myParam and spread the ...rest}
"Form element" components
Form elements, like input, checkbox, slider etc. should include some extra functionality in order to be used in various situations.
Basically, components we would place inside a HTML <form>
element.
Label vs fieldset/legend
They should be declared as a form element:
FormComponent._formElement = true
This helps e.g. to detect automated determination of label vs fieldset/legend.
Spacing
And they should be declared to support spacing properties as well:
FormComponent._supportsSpacingProps = true
This is needed in order to fully support Flex layouts.
pickFormElementProps
Usage of In order to support form element properties, such as vertical
or labelDirection
, you can use pickFormElementProps
, so only valid properties will effected the component.
import { Context } from '../../shared'import { extendPropsWithContext } from '../../shared/component-helper'import { pickFormElementProps } from '../../shared/helpers/filterValidProps'const defaultProps = {myParam: 'value',}function FormComponent(props: Types) {const context = React.useContext(Context)const { myParam, skeleton, ...rest } = extendPropsWithContext(props,defaultProps,pickFormElementProps(context?.formElement)context.FormComponent,)// Use myParam and spread the ...rest}
Spacing support
It depends from case to case on how you would make spacing support available. But you may always give the developer to send in the spacing properties to the very root element of your component.
import { Context } from '../../shared'import classnames from 'classnames'import {validateDOMAttributes,extendPropsWithContext,} from '../../shared/component-helper'import { createSpacingClasses } from '../space/SpacingHelper'import type { SpacingProps } from '../../shared/types'export type ComponentProps = {myParam?: string}export type ComponentAllProps = ComponentProps & SpacingPropsconst defaultProps = {myParam: 'value',}function MyComponent(props: ComponentAllProps) {const context = React.useContext(Context)const { myParam, className, ...rest } = extendPropsWithContext(props,defaultProps,// ...)// This helper will remove e.g. all spacing properties so you get only valid HTML attributesvalidateDOMAttributes(props, rest)// This helper will add needed spacing css classes based on the given propertiesrest.className = classnames('dnb-my-component',createSpacingClasses(props),className,)// Spread the ...rest on your root element}
Skeleton support
It depends from case to case on how you would make skeleton support available. There are also more info on how to create a custom skeleton. But in case your component supports the skeleton
boolean property, then you may ensure it both can be set locally on the component, and it reacts on the global Context.
import { Context } from '../../shared'import { extendPropsWithContext } from '../../shared/component-helper'import {skeletonDOMAttributes,createSkeletonClass,} from '../skeleton/SkeletonHelper'import type { SkeletonShow } from '../skeleton/Skeleton'export type ComponentProps = {/*** Skeleton should be applied when loading content* Default: null*/skeleton?: SkeletonShow}export type ComponentAllProps = ComponentProps &React.HTMLProps<HTMLElement>const defaultProps = {}function MyComponent(props: ComponentAllProps) {const context = React.useContext(Context)const { skeleton, className, ...rest } = extendPropsWithContext(props,defaultProps,{ skeleton: context?.skeleton },// ...)// This helper will add some needed HTML attributes like "disabled", "aria-disabled" and "aria-label"skeletonDOMAttributes(rest, skeleton, context)// This helper will add needed skeleton css classes in order to create a custom skeletonrest.className = createSkeletonClass('shape',skeleton,context,className,)// Use skeleton and spread the ...rest}
TypeScript types
import React from 'react'import type { SpacingProps } from '../../shared/types'import type { ComponentProps } from './my-component/types'export type * from './new-component/types'export type ComponentAllProps = ComponentProps &React.HTMLProps<HTMLElement>function MyComponent(props: ComponentAllProps) {}
Write documentation
All components have their own directory inside:
./packages/dnb-design-system-portal/src/docs/uilib/...
You may have a look at Documentation guide and existing docs in order to get the right structure.
Make and run tests
Make tests for the new component (or for your current issue) and set up screenshot tests from the Eufemia portal. The tests should be located under __tests__
in the component folder.
- Tip 1: Create tests for each prop that change your component.
- Tip 2: Always check and make the tests fail when you are writing tests.
More on testing in the UI Library.
Running tests locally
Run the commands from the repository's root folder. Replace breadcrumb
with your component's name in the commands.
- Run the integration tests:
# Run all testsyarn test
# Execute the tests on file (git) changesyarn test:watch# Run all tests including the word 'breadcrumb'yarn test breadcrumb# Or be more specificyarn test /breadcrumb.test.tsx# Run several togetheryarn test breadcrumb avatar button
- Update the changed snapshots:
yarn test:update# More specificyarn test:update breadcrumb avatar
Jest integration tests uses this naming convention: /__tests__/{ComponentName}.test.tsx
- Run visual and end-to-end test:
NB: Make sure you have the portal running locally on port 8000.
Visual tests:
# 1. First start the portalyarn start# 2. Then run screenshot tests for e.g. 'breadcrumb' or 'avatar'yarn test:screenshots breadcrumb avatar# You can also start it in watch modeyarn test:screenshots:watch breadcrumb avatar
Visual tests uses this naming convention: /__tests__/{ComponentName}.e2e.spec.ts
Playwright end-to-end tests:
# 1. First start the portalyarn start# 2. Then run Playwright tests including 'Slider' or 'Button'yarn test:e2e /Slider\|Button/# You can also start it in watch modeyarn test:e2e:watch /Slider\|Button/
Playwright uses this naming convention: /__tests__/{ComponentName}.screenshot.test.ts
- Update eventually new or valid visual PNG snapshots:
# Update screenshot tests including 'breadcrumb'yarn test:screenshots:update breadcrumb
You can also press the u
during a watch mode to update outdated snapshots.
- How to deal with failing visual tests?
When a visual test fails, a visual comparison file (diff) will be created. Its location and name will be:
**/__tests__/__image_snapshots__/__diff_output__/*.snap-diff.png
you can find a report entry (index.html
), that lists all of the failed tests here:
/packages/dnb-eufemia/jest-visual-diff-report/index.html
You may check out the CI/CLI logs for more details.
GitHub Actions: If visual screenshot test is failing on the CI, you can navigate to the test "Summary" where you can find "Artifacts". There you can download the visual-test-artifact zip file, containing the visual diff files as well as the report entry inside /jest-visual-diff-report
.
Support SCSS snapshot test
Add a similar code snippet to your tests for watching changes in the SCSS you just created.
import { loadScss } from '../../../core/jest/jestSetup'describe('Button scss', () => {it('has to match style dependencies css', () => {const css = loadScss(require.resolve('../style/deps.scss'))expect(css).toMatchSnapshot()})it.each(['ui', 'sbanken'])('has to match theme css for %s',(themeName) => {const css = loadScss(require.resolve(`../style/themes/dnb-button-theme-${themeName}.scss`,),)expect(css).toMatchSnapshot()},)})
Support Axe test
Add a similar code snippet to your tests (as the last test). It will test the accessibility of your new component. Read more on Jest Axe.
describe('Breadcrumb aria', () => {it('should validate', async () => {const Component = render(<Breadcrumbdata={[{ href: '/' },{ href: '/page1', text: 'Page 1' },{ href: '/page1/page2', text: 'Page 2' },]}variant="collapse"isCollapsed={false}/>,)expect(await axeComponent(Component)).toHaveNoViolations()})})
(Optional) Update change logs
Changes to @dnb/eufemia
have to be mentioned by using a git commit messages decoration. During the next release, the package CHANGELOG.md
file will be updated and changes will get listed on the GitHub Releases page.
General Eufemia Design System changes have to be written down in the EUFEMIA_CHANGELOG.md
file, located in the docs. This file should only be updated if there is a change in the @dnb/eufemia
package, which affects the components, elements or extensions.
Commit changes
Commit your change and create a Pull Request to the origin/main
branch. Check out the Git convention for how to commit and make Pull Request.
From a Fork:
- Make your changes in your Fork and create a Pull Request back to the Eufemia repo and
origin/main
. - Watch the result of the tests.
From a clone:
- Make your changes and commit it to the repo in a new branch.
- Make a Pull Request to
origin/main
. - Watch the result of the tests.
Submit Algolia search queries locally
In order to submit Algolia search queries to the dev_eufemia_docs
index, you have to:
Create a .env
file inside dnb-design-system-portal
with valid:
ALGOLIA_INDEX_NAME=dev_eufemia_docs
ALGOLIA_APP_ID=SLD6KEYMQ9
ALGOLIA_API_KEY=secret