@tpzdsp/tpz-dsp3-ui-kit
v1.4.1
Published
A library template built by Vite, React, Typescript, TailwindCSS and Storybook
Readme
Vite, React, Typescript and Tailwind npm package
Developing components
To develop the components, follow these steps:
- Write your component code in the
srcfolder. - Export the component in the
src/index.tsfile. - Create a corresponding story file next to the component to showcase it in Storybook.
- Run
yarn storybookto view your developed component live on the Storybook viewer.
Publish
To publish the package on Gitlab Package Registry, follow these steps:
- Modify the name and version in the
package.jsonfile according to this pattern:@your-scope/package-name. - Generate a developer token from your GitLab account. This token will be needed in step 6.
- Create a repository on GitLab for your library.
- Add an
.npmrcfile at the root of this package with the following content:
@your-scope:registry=https://gitlab.com/api/v4/projects/your_project_id/packages/npm/
//gitlab.com/api/v4/projects/your_project_id/packages/npm/:_authToken="${NPM_TOKEN}"Replace your_project_id with the Project ID that can be found in the GitLab repository.
- Run the command
NPM_TOKEN=token npm publishto publish the package to the npm registry.
To publish the package on Github Package Registry, follow these steps:
To publish the package on GitHub Package Registry, follow these steps:
- Modify the name and version in the
package.jsonfile according to this pattern:@your-scope/package-name. - Create a personal access token (PAT) with the
write:packagesscope. You can create a PAT by going to your GitHub account settings and navigating to "Developer settings" > "Personal access tokens". - Create a
.npmrcfile at the root of this package with the following content:
@NAMESPACE:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_PATReplace NAMESPACE with your GitHub username and YOUR_PAT with your personal access token.
- login to npm on scope level via
$ npm login --scope=@NAMESPACE --auth-type=legacy --registry=https://npm.pkg.github.com
> Username: USERNAME
> Password: TOKEN
- Run the command
npm publishto publish the package to the GitHub Package Registry.
✔️ Installing the package
The library does not bundle the Tailwind CSS library, Therefore, you need to configure Tailwind CSS on your project to use this component library For more information, refer to the Tailwind CSS installation guide.
To use the UI Library in your project, follow these steps:
- Provide the
.npmrcfile with the following credentials on the target repository. - Clone the released package on the target repository using the command
npm i @your-scope/ui-kit. - Configure Tailwind to scan the library components by adding this line to the content array on the
tailwind.config.js.
content: [
...
'./node_modules/@your-scope/ui-kit/dist/ui-kit.umd.js',
],- Import the library in your component, for example:
import {Button} from '@scope/ui-kit'. - Use the component:
<Button color="blue" > Click me! </Button>.
Local Development
- In this repo build your latest changes:
yarn build - In this repo Publish your latest changes:
yalc publish - In the app repo, install package (this needs done on each change):
yalc add -D @tpzdsp/tpz-dsp3-ui-kit
Styling
The Tailwind config has been designed to be a mixture of modern Tailwind practices and naming and GDS names for consistency and semantics.
Colours
The available colours are a reduced palette have been taken from the GDS colour palette. Each colour has shades 100 to 900 (900 being the darkest), where 500 is the exact hex code of the colour in the palette. For example, if you need background: govuk-colour("red"), that would be bg-red-500.
As per the GDS, there are colours that have secondary names that should be used for specific contexts, which are shown below, along side a list of the available colours:
| GDS Name | Tailwind Name | Contextual Name(s) |
| ------------ | ------------- | ----------------------------------------------------------- |
| white | white | |
| black | black | text-primary, focus-text, border-input, link-active |
| red | red-500 | error |
| yellow | yellow | focus |
| green | green | success, brand, link, link-hover (green-600) |
| orange | orange | warning |
| dark-grey | gray | text-secondary |
| mid-grey | slate | border |
| light-grey | stone | |
| purple | purple | link-visited |
States
Focus
Any element with focus must have a yellow background and black text, which is achieved with bg-focus and text-focus-text. Certain components, like buttons, may have a different focus style depending on whether the user is also hovering or interacting with the compoennt, but the focus colour must always be the same.
Hover / Active
For hover states, the colour of the element should darken one shade. For example, bg-green-500 hover:bg-green-600.
Active states will depend on the component. Links should use their respective contextual names, so link-hover, link-active, etc.
Text & Font
The GDS Transport font file is not included in this repository due to the license, however the font family name is the first one in the font family list so it will attempt to use it if available.
The font file should be supplied by each project individually.
Text Size
The sizing is based off of the GDS modern typography scale. The names of the font sizes have been altered:
| GovUK Name | Tailwind Name |
| ---------------- | ------------- |
| 16 | base |
| 19 | lg |
| 24 | xl |
| 27 (rarely used) | 2xl |
| 36 | 3xl |
| 48 | 4xl |
| 80 (rarely used) | 5xl |
The default text-... class name is automatically responsive, based on the 2 differrent font size tables provided by the GDS for small or large screens. This means writing text-xl, for example, will apply the font size for small screens, and optionally, if the screen is large enough, the size for large screens. You should not need to write responsive font size classes for this reason.
If you need a font size that is not responsive, you can use the static names. For example, text-static-xl will apply the small screen point 24 font size and it will not change (text-static-lg-xl would be large screen).
Breakpoints
There are 4 breakpoints which map exactly to the govuk-frontend:
| GovUK Name | Tailwind Name | Size |
| ---------- | ------------- | ------ |
| mobile | xs | 320px |
| tablet | sm | 740px |
| desktop | md | 980px |
| wide | lg | 1300px |
Notes
There are a few utility variants defined in the Tailwind config:
focus-idle:: used when the element is focussed but not active or being hoveredhover-enabled:: used when an element is hovered, but the element is not disabledactive-enabled: same as above, but for if the element is active
Creating Components
References
There are 2 important references to use:
The GDS provides both all of the recomended styles, and reason as to why and where they should/shouldn't be used, and examples of all the components in the GovUK frontend components. (Note: The exaple Components on the GDS use slightly outdated styles).
The best reference for creating the component will be from within the GovUK frontend repository directly. This will show what semantic elements are used for each component, how they structure those element and the exact up-to-date styling they used. The styles are in SCSS but can be translated into Tailwind styles.
This way, designing the components is much easier as the work is mostly done for us when it comes to accessibility, styling, sematically correct elements, etc.
Props
As we are developing a component library, all components should expose all of their available props so we don't run into issues using the library where we haven't defined a prop we require for a component. For example, we might define the button props as:
export type ButtonProps = {
type: 'primary' | 'secondary' | 'inverse';
onClick: () => void;
disabled?: boolean;
children?: React.ReactNode;
}But what if we wanted to give the button an id or type='submit' for a form? This would be an issue since those props are missing. It would be a lot of back and forth to add missing props, and that opens is to issues with types, where a prop takes a string but we want to pass a ReactNode instead.
Instead, we have a utility type called ExtendProps which we should use on every component to forward all the props of the base element. Usage example:
type Props = {
type: 'primary' | 'secondary' | 'inverse';
}
export type ButtonProps = ExtendProps<'button', Props>;ButtonProps now inherits all of the props from the button element, and we still add our own type prop to it. The second generic argument (Props) is optional and can be excluded if we do not need to add or modify any props of the original element. For example:
export type ParagraphProps = ExtendProps<'p'>;In this case, we don't change or add any new props, so we just define the paragraph props to be the existing props of the p element.
Lastly, by using this utility type, we must make sure to spread the props into the component itself, so the element receives any of the extra props. Usage example:
export const Button = ({
type = 'primary',
children,
...props // We must capture the rest of the props.
}: ButtonProps) => {
return (
<button
className="..."
{...props} {/* And we must spread them into the element. */}
>
{children}
</button>
);
};Class names
We want these components to be customisable in specific circumstances, so they should also be able to take custom class names (which will be allowed by default when using the above ExtendProps).
Due to the use of Tailwind, adding extra class names can sometimes cause conflicts and unexpected behaviour, so we have added a library tailwind-merge which is used to solve this.
When you add the className prop to your component, it should use the twMerge function to merge your class names and the class names optionally provided by the user. For example:
<div
className={twMerge("tailwind classes", className)} {/* `className` is the user provided classes */}
>
{children}
</div>Storybook
We want to try and keep the codebase consistent, so that means sticking to a consistent format to building the components in storybook. There are 2 formats that should be used, based on the situation at hand.
Template Binding (multiple stories)
Template binding is used when you want each instance of the component to have its own story (this is a subjective choice, but the baseline is that if you think all of the variations of the component should be visible at the same time, do not use template bindings). The format of template bindings is as follows:
import { Meta, StoryFn } from '@storybook/react';
export default {
children: '<COMPONENT NAME>',
component: Component,
} as Meta;
const Template: StoryFn<ComponentProps> = (args) => <Component {...args} />;
// This is a single story. Try to make the variables names as descriptive possible.
export const Variant1 = Template.bind({});
Variant1.args = {
// Modify any of the `ComponentProps` props in here
children: 'Hello, this is some simple text',
};
// Another story
export const Variant2 = Template.bind({});
Variant1.args = {
children: <p>Hello, this is some simple text</p>,
color: "red",
hasError: true,
};Single story (all components on a single story)
If template binding was not suitable (such as needing conditional components, etc) instead you should render all variations of the component to a single story. An example of this is in the Button component.
The format of the single story is as follows:
import { Meta, StoryObj } from '@storybook/react';
export default {
children: '<COMPONENT NAME>',
component: Component,
} as Meta;
// `AllComponents` should use the name of the component, for example: `AllButtons` for buttons.
export const AllComponents: StoryObj<typeof Component> = {
// You could have condition rendering in here, for example.
render: () => (
<div>
<Component single={true}/>
<Component>
<div>Example text</div>
</Component>
</div>
),
};Keeping all stories to these 2 formats will keep the codebase consistent.
