devex: Add Storybook for component development (#2556)
Adds Storybook in preparation for UI component refactoring.
This commit is contained in:
parent
c2a11ccf10
commit
78e2dadf0a
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@ -7,6 +7,7 @@
|
|||||||
"redhat.vscode-yaml",
|
"redhat.vscode-yaml",
|
||||||
"streetsidesoftware.code-spell-checker",
|
"streetsidesoftware.code-spell-checker",
|
||||||
"ms-python.black-formatter",
|
"ms-python.black-formatter",
|
||||||
"ms-python.pylint"
|
"ms-python.pylint",
|
||||||
|
"unifiedjs.vscode-mdx"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1 +1,2 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
|
src/stories
|
||||||
|
@ -12,6 +12,7 @@ module.exports = {
|
|||||||
"plugin:import-x/recommended",
|
"plugin:import-x/recommended",
|
||||||
"plugin:wc/recommended",
|
"plugin:wc/recommended",
|
||||||
"plugin:lit/recommended",
|
"plugin:lit/recommended",
|
||||||
|
"plugin:storybook/recommended",
|
||||||
"prettier",
|
"prettier",
|
||||||
],
|
],
|
||||||
plugins: ["@typescript-eslint", "lit"],
|
plugins: ["@typescript-eslint", "lit"],
|
||||||
|
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
@ -27,3 +27,5 @@ custom-elements.json
|
|||||||
/test-results/
|
/test-results/
|
||||||
/playwright-report/
|
/playwright-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
|
|
||||||
|
*storybook.log
|
||||||
|
107
frontend/.storybook/main.ts
Normal file
107
frontend/.storybook/main.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import path from "path";
|
||||||
|
|
||||||
|
import type { StorybookConfig } from "@storybook/web-components-webpack5";
|
||||||
|
import type { WebpackConfiguration } from "webpack-dev-server";
|
||||||
|
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||||
|
addons: [
|
||||||
|
"@storybook/addon-webpack5-compiler-swc",
|
||||||
|
"@storybook/addon-essentials",
|
||||||
|
{
|
||||||
|
name: "@storybook/addon-styling-webpack",
|
||||||
|
options: {
|
||||||
|
// TODO Consolidate with webpack.config.js
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
// Global styles and assets, like fonts and Shoelace,
|
||||||
|
// that get added to document styles
|
||||||
|
test: /\.css$/,
|
||||||
|
sideEffects: true,
|
||||||
|
include: [
|
||||||
|
path.resolve(__dirname, "../src"),
|
||||||
|
path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"../node_modules/@shoelace-style/shoelace",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
exclude: /\.stylesheet\.css$/,
|
||||||
|
use: [
|
||||||
|
require.resolve("style-loader"),
|
||||||
|
{
|
||||||
|
loader: require.resolve("css-loader"),
|
||||||
|
options: {
|
||||||
|
importLoaders: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: require.resolve("postcss-loader"),
|
||||||
|
options: {
|
||||||
|
implementation: require.resolve("postcss"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// CSS loaded as raw string and used as a CSSStyleSheet
|
||||||
|
test: /\.stylesheet\.css$/,
|
||||||
|
sideEffects: true,
|
||||||
|
type: "asset/source",
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: require.resolve("postcss-loader"),
|
||||||
|
options: {
|
||||||
|
implementation: require.resolve("postcss"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
framework: {
|
||||||
|
name: "@storybook/web-components-webpack5",
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
webpackFinal: async (config) => {
|
||||||
|
// Show eslint errors from Storybook files in Webpack overlay
|
||||||
|
const ESLintPlugin = require("eslint-webpack-plugin");
|
||||||
|
|
||||||
|
config.plugins?.push(
|
||||||
|
new ESLintPlugin({
|
||||||
|
files: ["**/stories/*.ts", "**/.storybook/*.ts"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Watch for changes to custom-elements.json to re-render element
|
||||||
|
// attributes whenever the custom elements manifest is generated
|
||||||
|
(config as WebpackConfiguration).devServer = {
|
||||||
|
watchFiles: ["**/custom-elements.json"],
|
||||||
|
};
|
||||||
|
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
swc: {
|
||||||
|
jsc: {
|
||||||
|
parser: {
|
||||||
|
syntax: "typescript",
|
||||||
|
decorators: true,
|
||||||
|
},
|
||||||
|
// TODO Consolidate with tsconfig.json
|
||||||
|
transform: {
|
||||||
|
useDefineForClassFields: false,
|
||||||
|
},
|
||||||
|
baseUrl: path.resolve(__dirname, ".."),
|
||||||
|
// TODO Consolidate with tsconfig.json
|
||||||
|
paths: {
|
||||||
|
"@/*": ["./src/*"],
|
||||||
|
"~assets/*": ["./assets/src/*"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
core: {
|
||||||
|
disableTelemetry: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export default config;
|
25
frontend/.storybook/preview.ts
Normal file
25
frontend/.storybook/preview.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import "../src/global";
|
||||||
|
|
||||||
|
import {
|
||||||
|
setCustomElementsManifest,
|
||||||
|
type Preview,
|
||||||
|
} from "@storybook/web-components";
|
||||||
|
|
||||||
|
import customElements from "../src/__generated__/custom-elements.json";
|
||||||
|
|
||||||
|
// Automatically document component properties
|
||||||
|
setCustomElementsManifest(customElements);
|
||||||
|
|
||||||
|
const preview: Preview = {
|
||||||
|
parameters: {
|
||||||
|
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
18
frontend/custom-elements-manifest.config.mjs
Normal file
18
frontend/custom-elements-manifest.config.mjs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export default {
|
||||||
|
/** Globs to analyze */
|
||||||
|
globs: ["src/**/*.ts"],
|
||||||
|
/** Globs to exclude */
|
||||||
|
exclude: ["__generated__", "__mocks__"],
|
||||||
|
/** Directory to output CEM to */
|
||||||
|
outdir: "src/__generated__",
|
||||||
|
/** Run in dev mode, provides extra logging */
|
||||||
|
// dev: true,
|
||||||
|
/** Run in watch mode, runs on file changes */
|
||||||
|
// watch: true,
|
||||||
|
/** Include third party custom elements manifests */
|
||||||
|
// dependencies: true,
|
||||||
|
/** Output CEM path to `package.json`, defaults to true */
|
||||||
|
packagejson: false,
|
||||||
|
/** Enable special handling for litelement */
|
||||||
|
litelement: true,
|
||||||
|
};
|
@ -56,11 +56,11 @@ class MyCustomComponent extends BtrixElement {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### VS Code Snippet
|
## VS Code Snippet
|
||||||
|
|
||||||
If developing with [Visual Studio Code](https://code.visualstudio.com/), you can generate boilerplate for a `BtrixElement` Browsertrix component by typing in `component` to any TypeScript file and selecting "Btrix Component". Hit ++tab++ to move your cursor between fillable fields in the boilerplate code.
|
If developing with [Visual Studio Code](https://code.visualstudio.com/), you can generate boilerplate for a `BtrixElement` Browsertrix component by typing in `component` to any TypeScript file and selecting "Btrix Component". Hit ++tab++ to move your cursor between fillable fields in the boilerplate code.
|
||||||
|
|
||||||
### Unit Testing
|
## Unit Testing
|
||||||
|
|
||||||
Unit test files live next to the component file and are suffixed with `.test` (ex: `my-custom-component.test.ts`).
|
Unit test files live next to the component file and are suffixed with `.test` (ex: `my-custom-component.test.ts`).
|
||||||
|
|
||||||
|
13
frontend/docs/docs/develop/ui/storybook.md
Normal file
13
frontend/docs/docs/develop/ui/storybook.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Using Storybook
|
||||||
|
|
||||||
|
[Storybook](https://storybook.js.org/) is a tool for documenting and building UI components in isolation. Component documentation is organized into ["stories"](https://storybook.js.org/docs/writing-stories) that show a variety of possible rendered states of a UI component.
|
||||||
|
|
||||||
|
Browsertrix component stories live in `frontend/src/stories`. Component attributes that are public properties (i.e. defined with Lit `@property({ type: Type })`) or documented in a TSDoc comment will automatically appear in stories through the [Custom Elements Manifest](https://custom-elements-manifest.open-wc.org/analyzer/getting-started/) file.
|
||||||
|
|
||||||
|
To develop using Storybook, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn storybook:watch
|
||||||
|
```
|
||||||
|
|
||||||
|
This will open Storybook in your default browser. Changes to Browsertrix components and stories wil automatically refresh the page.
|
@ -94,6 +94,7 @@ nav:
|
|||||||
- UI Development:
|
- UI Development:
|
||||||
- develop/frontend-dev.md
|
- develop/frontend-dev.md
|
||||||
- develop/ui/components.md
|
- develop/ui/components.md
|
||||||
|
- develop/ui/storybook.md
|
||||||
- develop/localization.md
|
- develop/localization.md
|
||||||
- Design:
|
- Design:
|
||||||
- develop/ui/design-action-menus.md
|
- develop/ui/design-action-menus.md
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
"eslint-plugin-import-x": "4.5.1",
|
"eslint-plugin-import-x": "4.5.1",
|
||||||
"eslint-plugin-lit": "^1.11.0",
|
"eslint-plugin-lit": "^1.11.0",
|
||||||
|
"eslint-plugin-storybook": "^0.12.0",
|
||||||
"eslint-plugin-wc": "^2.0.4",
|
"eslint-plugin-wc": "^2.0.4",
|
||||||
"eslint-webpack-plugin": "^4.1.0",
|
"eslint-webpack-plugin": "^4.1.0",
|
||||||
"fork-ts-checker-webpack-plugin": "^6.2.6",
|
"fork-ts-checker-webpack-plugin": "^6.2.6",
|
||||||
@ -110,19 +111,35 @@
|
|||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"format:check": "prettier --check .",
|
"format:check": "prettier --check .",
|
||||||
"localize:extract": "lit-localize extract && prettier --write xliff/*.xlf",
|
"localize:extract": "lit-localize extract && prettier --write xliff/*.xlf",
|
||||||
"localize:build": "lit-localize build"
|
"localize:build": "lit-localize build",
|
||||||
|
"cem": "custom-elements-manifest analyze",
|
||||||
|
"prestorybook": "yarn cem",
|
||||||
|
"prestorybook:build": "yarn cem",
|
||||||
|
"storybook": "storybook dev -p 6006",
|
||||||
|
"storybook:watch": "concurrently 'yarn cem --watch' 'yarn storybook'",
|
||||||
|
"storybook:build": "storybook build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@custom-elements-manifest/analyzer": "^0.10.4",
|
||||||
"@lit/localize-tools": "^0.8.0",
|
"@lit/localize-tools": "^0.8.0",
|
||||||
|
"@storybook/addon-essentials": "^8.6.12",
|
||||||
|
"@storybook/addon-styling-webpack": "^1.0.1",
|
||||||
|
"@storybook/addon-webpack5-compiler-swc": "^3.0.0",
|
||||||
|
"@storybook/blocks": "^8.6.12",
|
||||||
|
"@storybook/test": "^8.6.12",
|
||||||
|
"@storybook/web-components": "^8.6.12",
|
||||||
|
"@storybook/web-components-webpack5": "^8.6.12",
|
||||||
"@types/webpack-bundle-analyzer": "^4.7.0",
|
"@types/webpack-bundle-analyzer": "^4.7.0",
|
||||||
"@web/dev-server-esbuild": "^0.3.3",
|
"@web/dev-server-esbuild": "^0.3.3",
|
||||||
"@web/dev-server-import-maps": "^0.2.0",
|
"@web/dev-server-import-maps": "^0.2.0",
|
||||||
"@web/dev-server-rollup": "^0.6.1",
|
"@web/dev-server-rollup": "^0.6.1",
|
||||||
|
"concurrently": "^9.1.2",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"lint-staged": "^13.1.0",
|
"lint-staged": "^13.1.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.12",
|
"prettier-plugin-tailwindcss": "^0.5.12",
|
||||||
"rollup-plugin-typescript-paths": "^1.4.0",
|
"rollup-plugin-typescript-paths": "^1.4.0",
|
||||||
"sinon": "^12.0.1",
|
"sinon": "^12.0.1",
|
||||||
|
"storybook": "^8.6.12",
|
||||||
"ts-lit-plugin": "^2.0.1",
|
"ts-lit-plugin": "^2.0.1",
|
||||||
"webpack-bundle-analyzer": "^4.10.1",
|
"webpack-bundle-analyzer": "^4.10.1",
|
||||||
"webpack-dev-server": "^5.2.0"
|
"webpack-dev-server": "^5.2.0"
|
||||||
|
@ -37,5 +37,11 @@ module.exports = {
|
|||||||
xmlSelfClosingSpace: false,
|
xmlSelfClosingSpace: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: "**/*.mdx",
|
||||||
|
options: {
|
||||||
|
proseWrap: "always",
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -12,12 +12,7 @@ import { tw } from "@/utils/tailwind";
|
|||||||
type Variant = "neutral" | "danger";
|
type Variant = "neutral" | "danger";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom styled button
|
* Custom styled button.
|
||||||
*
|
|
||||||
* Usage example:
|
|
||||||
* ```ts
|
|
||||||
* <btrix-button>Click me</btrix-button>
|
|
||||||
* ```
|
|
||||||
*/
|
*/
|
||||||
@customElement("btrix-button")
|
@customElement("btrix-button")
|
||||||
export class Button extends TailwindElement {
|
export class Button extends TailwindElement {
|
||||||
|
@ -22,26 +22,6 @@ tableCSS.split("}").forEach((rule: string) => {
|
|||||||
* Low-level component for displaying content as a table.
|
* Low-level component for displaying content as a table.
|
||||||
* To style tables, use TailwindCSS utility classes.
|
* To style tables, use TailwindCSS utility classes.
|
||||||
*
|
*
|
||||||
* @example Usage:
|
|
||||||
* ```ts
|
|
||||||
* <btrix-table>
|
|
||||||
* <btrix-table-head class="border-b">
|
|
||||||
* <btrix-table-header-cell class="border-r">col 1 </btrix-table-header-cell>
|
|
||||||
* <btrix-table-header-cell>col 2</btrix-table-header-cell>
|
|
||||||
* </btrix-table-head>
|
|
||||||
* <btrix-table-body>
|
|
||||||
* <btrix-table-row class="border-b">
|
|
||||||
* <btrix-table-cell class="border-r">row 1 col 1</btrix-table-cell>
|
|
||||||
* <btrix-table-cell>row 1 col 2</btrix-table-cell>
|
|
||||||
* </btrix-table-row>
|
|
||||||
* <btrix-table-row>
|
|
||||||
* <btrix-table-cellclass="border-r">row 2 col 1</btrix-table-cell>
|
|
||||||
* <btrix-table-cell>row 2 col 2</btrix-table-cell>
|
|
||||||
* </btrix-table-row>
|
|
||||||
* </btrix-table-body>
|
|
||||||
* </btrix-table>
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Table columns will be automatically sized according to its content.
|
* Table columns will be automatically sized according to its content.
|
||||||
* To specify column size, use `grid-template-columns`.
|
* To specify column size, use `grid-template-columns`.
|
||||||
*
|
*
|
||||||
|
11
frontend/src/global.ts
Normal file
11
frontend/src/global.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import "broadcastchannel-polyfill";
|
||||||
|
import "construct-style-sheets-polyfill";
|
||||||
|
import "./shoelace";
|
||||||
|
import "./assets/fonts/Inter/inter.css";
|
||||||
|
import "./assets/fonts/Recursive/recursive.css";
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
|
import { theme } from "@/theme";
|
||||||
|
|
||||||
|
// Make theme CSS available in document
|
||||||
|
document.adoptedStyleSheets = [theme];
|
@ -1,4 +1,5 @@
|
|||||||
import "./utils/polyfills";
|
import "./utils/polyfills";
|
||||||
|
import "./global";
|
||||||
|
|
||||||
import { provide } from "@lit/context";
|
import { provide } from "@lit/context";
|
||||||
import { localized, msg, str } from "@lit/localize";
|
import { localized, msg, str } from "@lit/localize";
|
||||||
@ -14,15 +15,9 @@ import { until } from "lit/directives/until.js";
|
|||||||
import { when } from "lit/directives/when.js";
|
import { when } from "lit/directives/when.js";
|
||||||
import isEqual from "lodash/fp/isEqual";
|
import isEqual from "lodash/fp/isEqual";
|
||||||
|
|
||||||
import "broadcastchannel-polyfill";
|
|
||||||
import "construct-style-sheets-polyfill";
|
|
||||||
import "./shoelace";
|
|
||||||
import "./components";
|
import "./components";
|
||||||
import "./features";
|
import "./features";
|
||||||
import "./pages";
|
import "./pages";
|
||||||
import "./assets/fonts/Inter/inter.css";
|
|
||||||
import "./assets/fonts/Recursive/recursive.css";
|
|
||||||
import "./styles.css";
|
|
||||||
|
|
||||||
import { viewStateContext } from "./context/view-state";
|
import { viewStateContext } from "./context/view-state";
|
||||||
import { OrgTab, RouteNamespace } from "./routes";
|
import { OrgTab, RouteNamespace } from "./routes";
|
||||||
@ -38,7 +33,6 @@ import AuthService, {
|
|||||||
import { BtrixElement } from "@/classes/BtrixElement";
|
import { BtrixElement } from "@/classes/BtrixElement";
|
||||||
import type { NavigateEventDetail } from "@/controllers/navigate";
|
import type { NavigateEventDetail } from "@/controllers/navigate";
|
||||||
import type { NotifyEventDetail } from "@/controllers/notify";
|
import type { NotifyEventDetail } from "@/controllers/notify";
|
||||||
import { theme } from "@/theme";
|
|
||||||
import { type Auth } from "@/types/auth";
|
import { type Auth } from "@/types/auth";
|
||||||
import {
|
import {
|
||||||
translatedLocales,
|
translatedLocales,
|
||||||
@ -53,9 +47,6 @@ import { AppStateService } from "@/utils/state";
|
|||||||
import { formatAPIUser } from "@/utils/user";
|
import { formatAPIUser } from "@/utils/user";
|
||||||
import brandLockupColor from "~assets/brand/browsertrix-lockup-color.svg";
|
import brandLockupColor from "~assets/brand/browsertrix-lockup-color.svg";
|
||||||
|
|
||||||
// Make theme CSS available in document
|
|
||||||
document.adoptedStyleSheets = [theme];
|
|
||||||
|
|
||||||
type DialogContent = {
|
type DialogContent = {
|
||||||
label?: TemplateResult | string;
|
label?: TemplateResult | string;
|
||||||
body?: TemplateResult | string;
|
body?: TemplateResult | string;
|
||||||
|
86
frontend/src/stories/Button.stories.ts
Normal file
86
frontend/src/stories/Button.stories.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/web-components";
|
||||||
|
import { html } from "lit";
|
||||||
|
|
||||||
|
import { renderButton, type RenderProps } from "./Button";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
component: "btrix-button",
|
||||||
|
tags: ["autodocs"],
|
||||||
|
render: renderButton,
|
||||||
|
argTypes: {
|
||||||
|
type: {
|
||||||
|
control: { type: "select" },
|
||||||
|
options: ["button", "submit"] satisfies RenderProps["type"][],
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
control: { type: "select" },
|
||||||
|
options: ["neutral", "danger"] satisfies RenderProps["variant"][],
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: { type: "select" },
|
||||||
|
options: ["x-small", "small", "medium"] satisfies RenderProps["size"][],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
label: "Button",
|
||||||
|
filled: true,
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
options: {
|
||||||
|
showPanel: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies Meta<RenderProps>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<RenderProps>;
|
||||||
|
|
||||||
|
export const Raised: Story = {
|
||||||
|
args: {
|
||||||
|
raised: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Loading: Story = {
|
||||||
|
args: {
|
||||||
|
loading: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Variants: Story = {
|
||||||
|
render: () => html`
|
||||||
|
${renderButton({
|
||||||
|
variant: "neutral",
|
||||||
|
label: "Neutral (Default)",
|
||||||
|
filled: true,
|
||||||
|
})}
|
||||||
|
${renderButton({ variant: "danger", label: "Danger", filled: true })}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Sizes: Story = {
|
||||||
|
render: () => html`
|
||||||
|
${renderButton({
|
||||||
|
size: "x-small",
|
||||||
|
label: "X-Small",
|
||||||
|
filled: true,
|
||||||
|
})}
|
||||||
|
${renderButton({
|
||||||
|
size: "small",
|
||||||
|
label: "Small",
|
||||||
|
filled: true,
|
||||||
|
})}
|
||||||
|
${renderButton({
|
||||||
|
size: "medium",
|
||||||
|
label: "Medium (Default",
|
||||||
|
filled: true,
|
||||||
|
})}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Link: Story = {
|
||||||
|
args: {
|
||||||
|
href: "https://webrecorder.net",
|
||||||
|
label: "Button Link",
|
||||||
|
},
|
||||||
|
};
|
30
frontend/src/stories/Button.ts
Normal file
30
frontend/src/stories/Button.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { html } from "lit";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
|
import type { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
import "@/components/ui/button";
|
||||||
|
|
||||||
|
export type RenderProps = Button;
|
||||||
|
|
||||||
|
export const renderButton = ({
|
||||||
|
variant,
|
||||||
|
filled,
|
||||||
|
label,
|
||||||
|
raised,
|
||||||
|
loading,
|
||||||
|
href,
|
||||||
|
}: Partial<RenderProps>) => {
|
||||||
|
return html`
|
||||||
|
<btrix-button
|
||||||
|
variant=${ifDefined(variant)}
|
||||||
|
label=${ifDefined(label)}
|
||||||
|
href=${ifDefined(href)}
|
||||||
|
?filled=${filled}
|
||||||
|
?raised=${raised}
|
||||||
|
?loading=${loading}
|
||||||
|
>
|
||||||
|
${label}
|
||||||
|
</btrix-button>
|
||||||
|
`;
|
||||||
|
};
|
28
frontend/src/stories/Intro.mdx
Normal file
28
frontend/src/stories/Intro.mdx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Meta } from "@storybook/blocks";
|
||||||
|
|
||||||
|
<Meta title="Introduction" />
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
{/* TODO Consolidate with storybook.md in frontend/docs */}
|
||||||
|
|
||||||
|
Browsertrix component stories live in `frontend/src/stories`. Component
|
||||||
|
attributes that are public properties (i.e. defined with Lit
|
||||||
|
`@property({ type: Type })`) or documented in a TSDoc comment will automatically
|
||||||
|
appear in stories through the
|
||||||
|
[Custom Elements Manifest](https://custom-elements-manifest.open-wc.org/analyzer/getting-started/)
|
||||||
|
file.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**Component attributes aren't updating in Storybook**
|
||||||
|
|
||||||
|
Ensure you're running Storybook with `yarn storybook:watch` instead of
|
||||||
|
`yarn storybook`. The "watch" script automatically regenerates
|
||||||
|
`custom-elements.json`, which is the source of element attributes documentation.
|
||||||
|
|
||||||
|
## Storybook Docs
|
||||||
|
|
||||||
|
- [How to write stories](https://storybook.js.org/docs/writing-stories/?renderer=web-components)
|
||||||
|
- [About autodocs](https://storybook.js.org/docs/writing-docs/autodocs/?renderer=web-components)
|
||||||
|
- [Configure Storybook](https://storybook.js.org/docs/configure/?renderer=web-components)
|
31
frontend/src/stories/Table.stories.ts
Normal file
31
frontend/src/stories/Table.stories.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/web-components";
|
||||||
|
|
||||||
|
import { defaultArgs, renderTable, type RenderProps } from "./Table";
|
||||||
|
|
||||||
|
import type { Table as TableComponent } from "@/components/ui/table/table";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
component: "btrix-table",
|
||||||
|
subcomponents: {
|
||||||
|
TableRow: "btrix-table-row",
|
||||||
|
},
|
||||||
|
render: renderTable,
|
||||||
|
tags: ["autodocs"],
|
||||||
|
argTypes: {
|
||||||
|
columns: { table: { disable: true } },
|
||||||
|
rows: { table: { disable: true } },
|
||||||
|
},
|
||||||
|
args: defaultArgs,
|
||||||
|
parameters: {
|
||||||
|
options: {
|
||||||
|
showPanel: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies Meta<RenderProps>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<TableComponent>;
|
||||||
|
|
||||||
|
export const BasicTable: Story = {
|
||||||
|
args: {},
|
||||||
|
};
|
81
frontend/src/stories/Table.ts
Normal file
81
frontend/src/stories/Table.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { html, type TemplateResult } from "lit";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
|
import "@/components/ui/table";
|
||||||
|
|
||||||
|
const columns = {
|
||||||
|
name: {
|
||||||
|
title: "Name",
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
title: "Email",
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
title: "Role",
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
title: html`<span class="sr-only">Remove</span>`,
|
||||||
|
renderItem: () => html`<sl-icon name="trash3"></sl-icon>`,
|
||||||
|
},
|
||||||
|
} satisfies RenderProps["columns"];
|
||||||
|
const rows: { data: Omit<Record<keyof typeof columns, unknown>, "remove"> }[] =
|
||||||
|
[
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
name: "Alice",
|
||||||
|
email: "alice@example.com",
|
||||||
|
role: 40,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: { name: "Bob", email: "bob@example.com", role: 20 },
|
||||||
|
},
|
||||||
|
] satisfies RenderProps["rows"];
|
||||||
|
|
||||||
|
export interface RenderProps {
|
||||||
|
columns: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
title: string | TemplateResult;
|
||||||
|
classes?: string;
|
||||||
|
renderItem?: (data: Record<string, unknown>) => TemplateResult;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
rows: {
|
||||||
|
data: Record<string, unknown>;
|
||||||
|
classes?: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultArgs = { columns, rows } satisfies RenderProps;
|
||||||
|
|
||||||
|
export const renderTable = ({ columns: headers, rows: items }: RenderProps) => {
|
||||||
|
return html`
|
||||||
|
<btrix-table>
|
||||||
|
<btrix-table-head>
|
||||||
|
${Object.values(headers).map(
|
||||||
|
({ title, classes }) => html`
|
||||||
|
<btrix-table-header-cell class=${ifDefined(classes)}>
|
||||||
|
${title}
|
||||||
|
</btrix-table-header-cell>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</btrix-table-head>
|
||||||
|
<btrix-table-body>
|
||||||
|
${items.map(
|
||||||
|
({ classes, data }) => html`
|
||||||
|
<btrix-table-row class=${ifDefined(classes)}>
|
||||||
|
${Object.entries(headers).map(
|
||||||
|
([key, { renderItem }]) => html`
|
||||||
|
<btrix-table-cell class=${ifDefined(classes)}>
|
||||||
|
${renderItem ? renderItem(data) : data[key]}
|
||||||
|
</btrix-table-cell>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</btrix-table-row>
|
||||||
|
`,
|
||||||
|
)}
|
||||||
|
</btrix-table-body>
|
||||||
|
</btrix-table>
|
||||||
|
`;
|
||||||
|
};
|
@ -14,6 +14,7 @@
|
|||||||
"inlineSources": true,
|
"inlineSources": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"useDefineForClassFields": false,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "ts-lit-plugin",
|
"name": "ts-lit-plugin",
|
||||||
|
1527
frontend/yarn.lock
generated
1527
frontend/yarn.lock
generated
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user