chore: Split Chromatic and Storybook stories (#25877)

Fixes #25427
This commit is contained in:
Pawan Kumar 2023-08-03 23:30:17 +05:30 committed by GitHub
parent 56c795206e
commit 57cf92e68d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 510 additions and 13 deletions

View File

@ -1,4 +1,4 @@
name: 'Build Storybook Preview and Publish to Chromatic' name: 'Build Storybook - UI Tests with Chromatic'
on: on:
push: push:
@ -41,6 +41,8 @@ jobs:
- name: Publish to Chromatic - name: Publish to Chromatic
id: chromatic-publish id: chromatic-publish
uses: chromaui/action@v1 uses: chromaui/action@v1
env:
CHROMATIC: 1
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

49
.github/workflows/build-storybook.yml vendored Normal file
View File

@ -0,0 +1,49 @@
name: 'Build Storybook - Docs with Chromatic'
on:
push:
branches:
- release
paths:
- 'app/client/packages/design-system/**'
pull_request:
paths:
- 'app/client/packages/design-system/**'
jobs:
chromatic-deployment:
runs-on: ubuntu-latest
steps:
- name: Checkout PR if pull_request event
if: github.event_name == 'pull_request'
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: refs/pull/${{ github.event.pull_request.number }}/merge
- name: Checkout PR if push event
if: github.event_name == 'push'
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: release
- name: Use Node.js 16.14.0
uses: actions/setup-node@v3
with:
node-version: "16.14.0"
- name: Install Dependencies
working-directory: ./app/client/packages/storybook
run: yarn install --immutable
- name: Publish to Chromatic
id: chromatic-publish
uses: chromaui/action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
projectToken: ${{ secrets.STORYBOOK_PROJECT_TOKEN }}
workingDir: ./app/client/packages/storybook
exitOnceUploaded: true
buildScriptName: "build"

View File

@ -14,7 +14,7 @@ export const StyledSwitch = styled(HeadlessSwitch)<SwitchProps>`
position: relative; position: relative;
width: var(--sizing-8); width: var(--sizing-8);
height: var(--sizing-4); height: var(--sizing-4);
background-color: var(--color-bg-neutral); background-color: var(--color-bd-neutral);
border-radius: var(--knob-size); border-radius: var(--knob-size);
color: var(--color-bg); color: var(--color-bg);
display: inline-flex; display: inline-flex;
@ -36,7 +36,7 @@ export const StyledSwitch = styled(HeadlessSwitch)<SwitchProps>`
} }
&[data-hovered]:not([data-disabled]) [data-icon] { &[data-hovered]:not([data-disabled]) [data-icon] {
--checkbox-border-color: var(--color-bd-neutral-hover); background-color: var(--color-bd-neutral-hover);
} }
/** /**

View File

@ -10,4 +10,6 @@ export * from "./components/Tooltip";
export * from "./components/Flex"; export * from "./components/Flex";
export * from "./components/Radio"; export * from "./components/Radio";
export * from "./components/RadioGroup"; export * from "./components/RadioGroup";
export * from "./components/Switch";
export * from "./components/SwitchGroup";
export * from "./utils"; export * from "./utils";

View File

@ -1,5 +1,6 @@
import * as React from "react"; import * as React from "react";
import styled from "styled-components"; import styled from "styled-components";
import isChromatic from "chromatic/isChromatic";
import { ThemeProvider, useTheme } from "@design-system/theming"; import { ThemeProvider, useTheme } from "@design-system/theming";
const StyledThemeProvider = styled(ThemeProvider)` const StyledThemeProvider = styled(ThemeProvider)`
@ -16,14 +17,17 @@ const StyledThemeProvider = styled(ThemeProvider)`
export const theming = (Story, args) => { export const theming = (Story, args) => {
const { theme } = useTheme({ const { theme } = useTheme({
seedColor: args.globals.accentColor, seedColor: args.globals.accentColor,
colorMode: args.globals.colorMode, colorMode: args.parameters.colorMode || args.globals.colorMode,
borderRadius: args.globals.borderRadius, borderRadius: args.globals.borderRadius,
fontFamily: args.globals.fontFamily, fontFamily: args.globals.fontFamily,
rootUnitRatio: args.globals.rootUnitRatio, rootUnitRatio: args.globals.rootUnitRatio,
}); });
return ( return (
<StyledThemeProvider theme={theme}> <StyledThemeProvider
className={isChromatic() ? "is-chromatic" : ""}
theme={theme}
>
<Story /> <Story />
</StyledThemeProvider> </StyledThemeProvider>
); );

View File

@ -1,5 +1,6 @@
import { dirname, join } from "path"; import { dirname, join } from "path";
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
async function webpackConfig(config) { async function webpackConfig(config) {
config.module.rules.push({ config.module.rules.push({
test: /\.(js|jsx|ts|tsx)$/, test: /\.(js|jsx|ts|tsx)$/,
@ -17,11 +18,20 @@ async function webpackConfig(config) {
config.resolve.plugins.push(new TsconfigPathsPlugin()); config.resolve.plugins.push(new TsconfigPathsPlugin());
return config; return config;
} }
module.exports = {
stories: [ function getStories() {
if (process.env.CHROMATIC) {
return ["../chromatic/**/*.chromatic.stories.@(js|jsx|ts|tsx)"];
}
return [
"../stories/**/*.stories.mdx", "../stories/**/*.stories.mdx",
"../stories/**/*.stories.@(js|jsx|ts|tsx)", "../stories/**/*.stories.@(js|jsx|ts|tsx)",
], ];
}
module.exports = {
stories: getStories(),
addons: [ addons: [
getAbsolutePath("@storybook/addon-viewport"), getAbsolutePath("@storybook/addon-viewport"),
getAbsolutePath("@storybook/addon-docs"), getAbsolutePath("@storybook/addon-docs"),

View File

@ -5,6 +5,11 @@ body,
width: 100%; width: 100%;
} }
/** we are stopping animation for all elements for chromatic so that our snapshopts are consistent */
.is-chromatic * {
animation: none !important;
}
/* fonts */ /* fonts */
@import url("https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap");

View File

@ -0,0 +1,70 @@
import React from "react";
import {
Button,
BUTTON_VARIANTS,
BUTTON_COLORS,
Icon,
} from "@design-system/widgets";
import { importRemixIcon } from "design-system-old";
import type { Meta, StoryObj } from "@storybook/react";
import { StoryGrid } from "../../../helpers/StoryGrid";
import { DataAttrWrapper } from "../../../helpers/DataAttrWrapper";
const StarIcon = importRemixIcon(() => import("remixicon-react/StarFillIcon"));
const variants = Object.values(BUTTON_VARIANTS);
const colors = Object.values(BUTTON_COLORS);
const meta: Meta<typeof Button> = {
component: Button,
title: "Design System/Widgets/Button",
};
export default meta;
type Story = StoryObj<typeof Button>;
const states = [
"",
"data-hovered",
"data-active",
"data-focused",
"aria-disabled",
];
const ICON = (
<Icon>
<StarIcon />
</Icon>
);
export const LightMode: Story = {
render: () => (
<StoryGrid>
{variants.map((variant) =>
colors.map((color) =>
states.map((state) => (
<DataAttrWrapper attr={state} key={`${variant}-${color}-${state}`}>
<Button
color={color}
variant={variant}
>{`${variant} ${color} ${state}`}</Button>
</DataAttrWrapper>
)),
),
)}
<Button icon={ICON}>Button with Start Icon</Button>
<Button icon={ICON} iconPosition="end">
Button with End Icon
</Button>
<Button isLoading>Loading...</Button>
</StoryGrid>
),
};
export const DarkMode: Story = Object.assign({}, LightMode);
DarkMode.parameters = {
colorMode: "dark",
};

View File

@ -0,0 +1,44 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { Checkbox } from "@design-system/widgets";
import { StoryGrid } from "../../../helpers/StoryGrid";
import { DataAttrWrapper } from "../../../helpers/DataAttrWrapper";
const meta: Meta<typeof Checkbox> = {
component: Checkbox,
title: "Design System/Widgets/Checkbox",
};
export default meta;
type Story = StoryObj<typeof Checkbox>;
const states = ["", "data-hovered", "data-focused", "data-disabled"];
export const LightMode: Story = {
render: () => (
<StoryGrid>
<Checkbox isIndeterminate>Indeterminate</Checkbox>
{states.map((state) => (
<>
<DataAttrWrapper attr={state} key={state}>
<Checkbox>unchecked {state}</Checkbox>
</DataAttrWrapper>
<DataAttrWrapper attr={state} key={state}>
<Checkbox defaultSelected>checked {state}</Checkbox>
</DataAttrWrapper>
</>
))}
<Checkbox defaultSelected isReadOnly isRequired>
Readonly
</Checkbox>
</StoryGrid>
),
};
export const DarkMode: Story = Object.assign({}, LightMode);
DarkMode.parameters = {
colorMode: "dark",
};

View File

@ -0,0 +1,49 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import {
CheckboxGroup,
RadioGroup,
Switch,
Checkbox,
SwitchGroup,
Radio,
} from "@design-system/widgets";
import { StoryGrid } from "../../../helpers/StoryGrid";
const meta: Meta<typeof Switch> = {
component: Switch,
title: "Design System/Widgets/Group",
};
export default meta;
type Story = StoryObj<typeof Switch>;
export const LightMode: Story = {
render: () => (
<StoryGrid>
<RadioGroup defaultValue="1">
<Radio value="1">Option 1</Radio>
<Radio value="2"> Option 2</Radio>
<Radio value="3">Opion 3</Radio>
</RadioGroup>
<CheckboxGroup defaultValue={["1"]}>
<Checkbox value="1">Option 1</Checkbox>
<Checkbox value="2">Option 2</Checkbox>
<Checkbox value="3">Option 3</Checkbox>
</CheckboxGroup>
<SwitchGroup defaultValue={["1"]}>
<Switch value="1">Option 1</Switch>
<Switch value="2">Option 2</Switch>
<Switch value="3">Option 3</Switch>
</SwitchGroup>
</StoryGrid>
),
};
export const DarkMode: Story = Object.assign({}, LightMode);
DarkMode.parameters = {
colorMode: "dark",
};

View File

@ -0,0 +1,44 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { Radio, RadioGroup } from "@design-system/widgets";
import { StoryGrid } from "../../../helpers/StoryGrid";
import { DataAttrWrapper } from "../../../helpers/DataAttrWrapper";
const meta: Meta<typeof Radio> = {
component: Radio,
title: "Design System/Widgets/Radio",
};
export default meta;
type Story = StoryObj<typeof Radio>;
const states = ["", "data-hovered", "data-focused", "data-disabled"];
export const LightMode: Story = {
render: () => (
<StoryGrid>
{states.map((state) => (
<>
<RadioGroup>
<DataAttrWrapper attr={state} key={state}>
<Radio value={`${state}-unchecked`}>unchecked {state}</Radio>
</DataAttrWrapper>
</RadioGroup>
<RadioGroup defaultValue={`${state}-checked`}>
<DataAttrWrapper attr={state} key={state}>
<Radio value={`${state}-checked`}>checked {state}</Radio>
</DataAttrWrapper>
</RadioGroup>
</>
))}
</StoryGrid>
),
};
export const DarkMode: Story = Object.assign({}, LightMode);
DarkMode.parameters = {
colorMode: "dark",
};

View File

@ -0,0 +1,40 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { Switch } from "@design-system/widgets";
import { StoryGrid } from "../../../helpers/StoryGrid";
import { DataAttrWrapper } from "../../../helpers/DataAttrWrapper";
const meta: Meta<typeof Switch> = {
component: Switch,
title: "Design System/Widgets/Switch",
};
export default meta;
type Story = StoryObj<typeof Switch>;
const states = ["", "data-hovered", "data-focused", "data-disabled"];
export const LightMode: Story = {
render: () => (
<StoryGrid>
{states.map((state) => (
<>
<DataAttrWrapper attr={state} key={state}>
<Switch>unchecked {state}</Switch>
</DataAttrWrapper>
<DataAttrWrapper attr={state} key={state}>
<Switch defaultSelected>checked {state}</Switch>
</DataAttrWrapper>
</>
))}
</StoryGrid>
),
};
export const DarkMode: Story = Object.assign({}, LightMode);
DarkMode.parameters = {
colorMode: "dark",
};

View File

@ -0,0 +1,61 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { Text } from "@design-system/widgets";
import { StoryGrid } from "../../../helpers/StoryGrid";
import { TypographyColor, TypographyVariant } from "@design-system/theming";
const meta: Meta<typeof Text> = {
component: Text,
title: "Design System/Widgets/Text",
};
export default meta;
type Story = StoryObj<typeof Text>;
export const LightMode: Story = {
storyName: "Text",
render: () => (
<StoryGrid cols="4">
<Text>
Default - Lorem ipsum dolor sit, amet consectetur adipisicing elit.
Reiciendis, obcaecati velit voluptatibus ratione officia consectetur
similique ab rem adipisci atque eum dolores culpa cum reprehenderit
quidem cupiditate impedit modi in.
</Text>
<Text lineClamp={2}>
LineClamp - 2 - Lorem ipsum dolor sit amet consectetur adipisicing elit.
Enim eaque consequatur vel cupiditate nihil! Natus itaque voluptatibus,
possimus nisi expedita, inventore nobis obcaecati aspernatur
necessitatibus, molestias deleniti corrupti aliquam repudiandae.
</Text>
<Text lineClamp={1}>
LineClamp - 1 - Lorem ipsum dolor sit amet consectetur adipisicing elit.
Enim eaque consequatur vel cupiditate nihil! Natus itaque voluptatibus,
possimus nisi expedita, inventore nobis obcaecati aspernatur
necessitatibus, molestias deleniti corrupti aliquam repudiandae.
</Text>
<Text textAlign="center">Text Align Center</Text>
<Text textAlign="right">Text Align Right</Text>
<Text isItalic>Italic</Text>
<Text isBold>Bold</Text>
{Object.values(TypographyVariant).map((variant) => (
<Text key={variant} variant={variant}>
{variant}
</Text>
))}
{Object.values(TypographyColor).map((color) => (
<Text color={color} key={color}>
{color}
</Text>
))}
</StoryGrid>
),
};
export const DarkMode: Story = Object.assign({}, LightMode);
DarkMode.parameters = {
colorMode: "dark",
};

View File

@ -0,0 +1,56 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import {
Button,
TooltipTrigger,
TooltipContent,
TooltipRoot as Tooltip,
} from "@design-system/widgets";
import { StoryGrid } from "../../../helpers/StoryGrid";
const meta: Meta<typeof Tooltip> = {
component: Tooltip,
title: "Design System/Widgets/Tooltip",
};
export default meta;
type Story = StoryObj<typeof Tooltip>;
export const LightMode: Story = {
render: () => (
<StoryGrid>
<Tooltip open placement="left">
<TooltipTrigger>
<Button>Button</Button>
</TooltipTrigger>
<TooltipContent>This is a tooltip</TooltipContent>
</Tooltip>
<Tooltip open placement="top">
<TooltipTrigger>
<Button>Button</Button>
</TooltipTrigger>
<TooltipContent>This is a tooltip</TooltipContent>
</Tooltip>
<Tooltip open placement="bottom">
<TooltipTrigger>
<Button>Button</Button>
</TooltipTrigger>
<TooltipContent>This is a tooltip</TooltipContent>
</Tooltip>
<Tooltip open placement="right">
<TooltipTrigger>
<Button>Button</Button>
</TooltipTrigger>
<TooltipContent>This is a tooltip</TooltipContent>
</Tooltip>
</StoryGrid>
),
};
export const DarkMode: Story = Object.assign({}, LightMode);
DarkMode.parameters = {
colorMode: "dark",
};

View File

@ -0,0 +1,37 @@
import React, { useEffect, useRef } from "react";
type DataAttrWrapperProps = {
children: React.ReactNode;
attr: string;
};
export const DataAttrWrapper = (props: DataAttrWrapperProps) => {
const { attr, children } = props;
// Adding any type here because WDS components has different types for ref
// some are HTMLElement and some are objects only ( For e.g - CheckboxRef )
const ref = useRef<any>(null);
useEffect(() => {
if (attr && ref?.current) {
if (
ref.current.setAttribute &&
typeof ref.current.setAttribute === "function"
) {
ref.current.setAttribute(attr, "");
return;
}
if (typeof ref.current.UNSAFE_getDOMNode === "function") {
const domNode = ref.current.UNSAFE_getDOMNode();
if (domNode) domNode.setAttribute(attr, "");
return;
}
}
}, [attr, ref.current]);
return React.cloneElement(children as React.ReactElement, { ref });
};

View File

@ -0,0 +1,25 @@
import React from "react";
type StoryGrid = {
cols?: number | string;
children: React.ReactNode;
gap?: string;
};
export function StoryGrid(props: StoryGrid) {
const { cols = 5, gap = "10px" } = props;
return (
<div
style={{
display: "grid",
justifyContent: "center",
gap: gap,
gridTemplateColumns: `repeat(${cols} , 1fr)`,
flexWrap: "wrap",
}}
>
{props.children}
</div>
);
}

View File

@ -7,7 +7,7 @@
"scripts": { "scripts": {
"storybook": "storybook dev -p 6006", "storybook": "storybook dev -p 6006",
"build": "storybook build", "build": "storybook build",
"chromatic": "chromatic --project-token CHROMATIC_PROJECT_TOKEN" "chromatic": "CHROMATIC=1 storybook dev -p 6006"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.21.0", "@babel/core": "^7.21.0",

View File

@ -1,7 +1,7 @@
import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs"; import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs";
import EmotionHappyLineIcon from "remixicon-react/EmotionHappyLineIcon"; import EmotionHappyLineIcon from "remixicon-react/EmotionHappyLineIcon";
import { Switch } from "./"; import { Switch } from "@design-system/widgets";
<Meta <Meta
title="Design-system/widgets/Switch" title="Design-system/widgets/Switch"

View File

@ -1,7 +1,6 @@
import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs"; import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs";
import { SwitchGroup } from "./"; import { Switch, SwitchGroup } from "@design-system/widgets";
import { Switch } from "../Switch";
<Meta <Meta
title="Design-system/widgets/SwitchGroup" title="Design-system/widgets/SwitchGroup"