Skip to content

Commit

Permalink
Add Tailwind example to Storybook (#5422)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukebennett88 authored Oct 27, 2022
1 parent 39b71ea commit bbeb4a5
Show file tree
Hide file tree
Showing 9 changed files with 704 additions and 366 deletions.
3 changes: 3 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[build]
command = "yarn build:docs"
publish = "docs/dist"

[build.environment]
YARN_VERSION = "1.22.19"
15 changes: 13 additions & 2 deletions storybook/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import type { StorybookConfig } from '@storybook/core-common';
import postcss from 'postcss';

const config: StorybookConfig = {
addons: ['@storybook/addon-a11y', '@storybook/addon-essentials'],
addons: [
'@storybook/addon-a11y',
'@storybook/addon-essentials',
{
name: '@storybook/addon-postcss',
options: {
postcssLoaderOptions: {
implementation: postcss,
},
},
},
],
core: {
builder: 'webpack4',
},
features: {
postcss: false,
/**
* Enable code splitting
* @see https://storybook.js.org/docs/react/builders/webpack#code-splitting
Expand Down
19 changes: 19 additions & 0 deletions storybook/data/index.ts → storybook/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,25 @@ export const groupedOptions: readonly GroupedOption[] = [
},
];

export type PersonOption = {
readonly id: number;
readonly name: string;
readonly online: boolean;
};

export const people: readonly PersonOption[] = [
{ id: 1, name: 'Carla Conroy', online: true },
{ id: 2, name: 'Beverly Wehner', online: false },
{ id: 3, name: 'Clifton Schimmel', online: true },
{ id: 4, name: 'Charles Greenfelder', online: true },
{ id: 5, name: 'Nadine Hoeger', online: false },
{ id: 6, name: 'Freddie Klocko', online: false },
{ id: 7, name: 'Della Crona', online: false },
{ id: 8, name: 'Leigh Herzog', online: false },
{ id: 9, name: 'Winston Schultz', online: true },
{ id: 10, name: 'Andrew Ryan', online: true },
];

export const defaultArgs = {
defaultMenuIsOpen: false,
defaultValue: undefined,
Expand Down
16 changes: 11 additions & 5 deletions storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,22 @@
"@emotion/babel-plugin": "^11.10.2",
"@emotion/babel-preset-css-prop": "^11.10.0",
"babel-loader": "^8.2.5",
"classnames": "^2.3.2",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"remeda": "^1.1.0",
"webpack": "^4.30.0"
},
"devDependencies": {
"@storybook/addon-a11y": "^6.5.12",
"@storybook/addon-essentials": "^6.5.12",
"@storybook/builder-webpack4": "^6.5.12",
"@storybook/manager-webpack4": "^6.5.12",
"@storybook/react": "^6.5.12",
"@storybook/addon-a11y": "^6.5.13",
"@storybook/addon-essentials": "^6.5.13",
"@storybook/addon-postcss": "^2.0.0",
"@storybook/builder-webpack4": "^6.5.13",
"@storybook/manager-webpack4": "^6.5.13",
"@storybook/react": "^6.5.13",
"autoprefixer": "^10.4.12",
"postcss": "^8.4.18",
"tailwindcss": "^3.2.1",
"typescript": "^4.1.3"
}
}
3 changes: 3 additions & 0 deletions storybook/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
plugins: [require('tailwindcss'), require('autoprefixer')],
};
116 changes: 116 additions & 0 deletions storybook/stories/Tailwind.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import '../styles/tailwind.css';

import { ComponentMeta, ComponentStory } from '@storybook/react';
import classNames from 'classnames';
import * as React from 'react';
import type { GroupBase, OptionProps, ValueContainerProps } from 'react-select';
import Select, { components } from 'react-select';
import { omit } from 'remeda';

import { Field } from '../components/field';
import type { PersonOption } from '../data';
import { defaultArgs, people } from '../data';

export default {
title: 'Select/Tailwind',
component: Select,
argTypes: {},
} as ComponentMeta<typeof Select>;

function StatusCircle({ online }: { online?: boolean }) {
return (
<div
className={classNames(
online ? 'bg-green-500' : 'bg-gray-200',
'h-2 w-2 rounded-full'
)}
/>
);
}

function CheckIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
{...props}
>
<path
fillRule="evenodd"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
clipRule="evenodd"
/>
</svg>
);
}

function ValueContainer<
IsMulti extends boolean = false,
Group extends GroupBase<PersonOption> = GroupBase<PersonOption>
>({ children, ...props }: ValueContainerProps<PersonOption, IsMulti, Group>) {
const { value, isMulti } = props.selectProps;
// @ts-expect-error selectProps can't infer `online` here.
const online: boolean = value?.online;

return (
<components.ValueContainer {...props}>
<span className="flex flex-1 items-center gap-2 text-left">
{isMulti ? null : <StatusCircle online={online} />}
<span className="flex w-full items-center">{children}</span>
</span>
</components.ValueContainer>
);
}

function Option<
IsMulti extends boolean = false,
Group extends GroupBase<PersonOption> = GroupBase<PersonOption>
>({ children, ...props }: OptionProps<PersonOption, IsMulti, Group>) {
return (
<components.Option {...props}>
<span className="flex flex-1 items-center gap-2 text-left">
<StatusCircle online={Boolean(props.data?.online)} />
<span className="flex w-full items-center">{children}</span>
{/* @ts-expect-error selectProps can't infer `value` here. */}
{props.selectProps.value?.id === props.data.id && (
<CheckIcon className="h-5 w-5" aria-hidden="true" />
)}
</span>
</components.Option>
);
}

const Template: ComponentStory<typeof Select> = ({
inputId = 'react-select',
...props
}) => {
const [value, setValue] = React.useState<PersonOption | null>(people[0]);

return (
<div className="p-4 bg-gray-100 flex flex-col gap-2">
<Field htmlFor={inputId} label="Assigned to">
{/* @ts-ignore */}
<Select<PersonOption>
{...props}
inputId={inputId}
components={{ ValueContainer, Option }}
options={people}
getOptionLabel={(option) => option.name}
getOptionValue={(option) => String(option.id)}
value={value}
onChange={(newValue) => setValue(newValue)}
/>
</Field>
{value && !value.online ? (
<p className="text-gray-700">User {value.name} is currently offline.</p>
) : undefined}
</div>
);
};

export const Tailwind = Template.bind({});
Tailwind.args = {
...omit(defaultArgs, ['defaultValue', 'isMulti', 'options']),
};
3 changes: 3 additions & 0 deletions storybook/styles/tailwind.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
8 changes: 8 additions & 0 deletions storybook/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./stories/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
};
Loading

0 comments on commit bbeb4a5

Please sign in to comment.