Skip to content

Commit

Permalink
[docs] Add "Building design system components" guide with Pigment CSS (
Browse files Browse the repository at this point in the history
  • Loading branch information
siriwatknp committed Apr 2, 2024
1 parent 1e8beab commit 1e11691
Show file tree
Hide file tree
Showing 5 changed files with 430 additions and 0 deletions.
247 changes: 247 additions & 0 deletions packages/pigment-css-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -854,3 +854,250 @@ function App() {
)
}
```

## Building reusable components for UI libraries

The purpose of this guide is to demonstrate how to create reusable components for a UI library that can be shared across multiple projects and used to implement different design systems through custom theming.
The approach outlined here is not necessary when constructing components to be consumed and themed in a single project.
It's most relevant for developers who want to build a component library that could be published as a package to be consumed and themed by other developers.

The steps below will walk you through how to create a statistics component that could serve as part of a reusable UI library built with Pigment CSS.
This process has three parts:

1. [Create component slots](#1-create-component-slots).
2. [Compose slots to create the component](#2-create-the-component).
3. [Style slots based on props](#3-style-slots-based-on-props).

### 1. Create component slots

Slots let the consumers customize each individual element of the component by targeting its respective name in the [theme's styleOverrides](#themeable-statistics-component).

This statistics component is composed of three slots:

- `root`: the container of the component
- `value`: the number to be displayed
- `unit`: the unit or description of the value

> 💡 Though you can give these slots any names you prefer, we recommend using `root` for the outermost container element for consistency with the rest of the library.
Use the `styled` API with `name` and `slot` parameters to create the slots, as shown below:

```js
// /path/to/Stat.js
import * as React from 'react';
import { styled } from '@pigment-css/react';

const StatRoot = styled('div', {
name: 'PigmentStat', // The component name
slot: 'root', // The slot name
})({
display: 'flex',
flexDirection: 'column',
gap: '1rem',
padding: '0.75rem 1rem',
backgroundColor: '#f9f9f9',
borderRadius: '8px',
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
letterSpacing: '-0.025em',
fontWeight: 600,
});

const StatValue = styled('div', {
name: 'PigmentStat',
slot: 'value',
})({
font: '1.2rem "Fira Sans", sans-serif',
});

const StatUnit = styled('div', {
name: 'PigmentStat',
slot: 'unit',
})({
font: '0.875rem "Fira Sans", sans-serif',
color: '#121212',
});
```

### 2. Create the component

Assemble the component using the slots created in the previous step:

```js
// /path/to/Stat.js
import * as React from 'react';

// ...slot styled-components

const Stat = React.forwardRef(function Stat(props, ref) {
const { value, unit, ...other } = props;

return (
<StatRoot ref={ref} {...other}>
<StatValue>{value}</StatValue>
<StatUnit>{unit}</StatUnit>
</StatRoot>
);
});

export default Stat;
```

### 3. Style slots based on props

In this example, a prop named `variant` is defined to let consumers change the appearance of the `Stat` component.

Pass down the `variant` prop to `<StatRoot>` to style the `root` slot, as shown below:

```diff
const Stat = React.forwardRef(function Stat(props, ref) {
+ const { value, unit, variant, ...other } = props;

return (
- <StatRoot ref={ref} {...other}>
- <StatValue>{value}</StatValue>
- <StatUnit>{unit}</StatUnit>
- </StatRoot>
+ <StatRoot ref={ref} variant={variant} {...other}>
+ <StatValue>{value}</StatValue>
+ <StatUnit>{unit}</StatUnit>
+ </StatRoot>
);
});
```

Then you can use Pigment CSS variants API to style it when `variant` prop has a value of `outlined`:

```diff
const StatRoot = styled('div', {
name: 'PigmentStat',
slot: 'root',
})({
display: 'flex',
flexDirection: 'column',
gap: '1rem',
padding: '0.75rem 1rem',
backgroundColor: '#f9f9f9',
borderRadius: '8px',
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
letterSpacing: '-0.025em',
fontWeight: 600,
+ variants: [
+ {
+ props: { variant: 'outlined' },
+ style: {
+ border: `2px solid #e9e9e9`,
+ },
+ },
+ ],
});
```

This completes the reusable statistics component.
If this were a real UI library, the component would be ready to upload to a package registry so others could use it.

### Consumer usage

Developers using your component must first install your package as well as the Pigment CSS packages that correspond to the [framework](#start-with-nextjs) they're using.

```bash
npm install your-package-name @pigment-css/react
npm install -D @pigment-css/nextjs-plugin
```

Next, they must set up Pigment CSS in their project:

```js
// framework config file, for example next.config.js
const { withPigment } = require('@pigment-css/nextjs-plugin');

module.exports = withPigment(
{
// ... Your nextjs config.
},
{ transformLibraries: ['your-package-name'] },
);
```

Finally, they must import the stylesheet in the root layout file:

```js
// index.tsx
import '@pigment-css/react/styles.css';
```

Then they can use your component in their project:

```jsx
import Stat from 'your-package-name/Stat';

function App() {
return <Stat value={42} unit="km/h" variant="outlined" />;
}
```

### Consumer theming

Developers can customize the component's styles using the theme's `styleOverrides` key and the component name and slots you defined in [step 2](#2-create-the-component).
For example, the custom theme below sets the background color of the statistics component's root slot to `tomato`:

```js
module.exports = withPigment(
{ ...nextConfig },
{
theme: {
styleOverrides: {
PigmentStat: {
root: {
backgroundColor: 'tomato',
},
value: {
color: 'white',
},
unit: {
color: 'white',
},
},
},
},
},
);
```

Developers can also access theme values and apply styles based on the component's props using the `variants` key:

```js
module.exports = withPigment(
{ ...nextConfig },
{
theme: {
// user defined colors
colors: {
primary: 'tomato',
primaryLight: 'lightcoral',
},
styleOverrides: {
PigmentStat: {
root: ({ theme }) => ({
backgroundColor: 'tomato',
variants: [
{
props: { variant: 'outlined' },
style: {
border: `2px solid ${theme.colors.primary}`,
backgroundColor: theme.colors.primaryLight,
},
},
],
}),
value: {
color: 'white',
},
unit: {
color: 'white',
},
},
},
},
},
);
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from 'react';
import { styled } from '@pigment-css/react';

const StatRoot = styled('div', {
name: 'PigmentStat', // The component name
slot: 'root', // The slot name
})(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: '1rem',
padding: '0.75rem 1rem',
backgroundColor: theme.colors.primary.background,
borderRadius: theme.radius.xs,
boxShadow: theme.shadow.sm,
letterSpacing: '-0.025em',
fontWeight: 600,
variants: [
{
props: { variant: 'outlined' },
style: {
border: `2px solid #e9e9e9`,
},
},
],
}));

const StatValue = styled('div', {
name: 'PigmentStat',
slot: 'value',
})(({ theme }) => ({
...theme.typography.h3,
}));

const StatUnit = styled('div', {
name: 'PigmentStat',
slot: 'unit',
})(({ theme }) => ({
...theme.typography.body2,
color: theme.colors.neutral.foreground,
}));

const Stat = React.forwardRef(function Stat(props, ref) {
const { value, unit, ...other } = props;

return (
<StatRoot ref={ref} {...other}>
<StatValue>{value}</StatValue>
<StatUnit>{unit}</StatUnit>
</StatRoot>
);
});

export default Stat;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.si9gu6v {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 0.75rem 1rem;
background-color: #ebf5ff;
border-radius: 0.25rem;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
letter-spacing: -0.025em;
font-weight: 600;
}
.si9gu6v-1 {
border: 2px solid #e9e9e9;
}
.sbfbm5t {
font-size: 2rem;
}
.s1xscf0o {
font-size: 1rem;
color: #6f7f95;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { styled as _styled3 } from '@pigment-css/react';
import _theme3 from '@pigment-css/react/theme';
import { styled as _styled2 } from '@pigment-css/react';
import _theme2 from '@pigment-css/react/theme';
import { styled as _styled } from '@pigment-css/react';
import _theme from '@pigment-css/react/theme';
import * as React from 'react';
const StatRoot = /*#__PURE__*/ _styled('div', {
name: 'PigmentStat',
// The component name
slot: 'root', // The slot name
})({
classes: ['si9gu6v'],
variants: [
{
props: {
variant: 'outlined',
},
className: 'si9gu6v-1',
},
],
});
const StatValue = /*#__PURE__*/ _styled2('div', {
name: 'PigmentStat',
slot: 'value',
})({
classes: ['sbfbm5t'],
});
const StatUnit = /*#__PURE__*/ _styled3('div', {
name: 'PigmentStat',
slot: 'unit',
})({
classes: ['s1xscf0o'],
});
const Stat = React.forwardRef(function Stat(props, ref) {
const { value, unit, ...other } = props;
return (
<StatRoot ref={ref} {...other}>
<StatValue>{value}</StatValue>
<StatUnit>{unit}</StatUnit>
</StatRoot>
);
});
export default Stat;

0 comments on commit 1e11691

Please sign in to comment.