diff --git a/.eslintrc b/.eslintrc index edcd86592..a6810f60a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,6 @@ { "plugins": ["matrix-org"], - "extends": ["plugin:matrix-org/typescript", "plugin:matrix-org/react", "prettier"], + "extends": ["plugin:matrix-org/typescript", "plugin:matrix-org/react", "plugin:storybook/recommended", "prettier"], "parserOptions": { "project": ["./tsconfig.json", "./packages/*/tsconfig.json"] }, diff --git a/.storybook/base.tsx b/.storybook/base.tsx new file mode 100644 index 000000000..4a2432091 --- /dev/null +++ b/.storybook/base.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import "@fontsource/inter/variable.css"; +import "@fontsource/noto-serif/latin.css"; +import "../src/ui/App.css"; +import Storybook from "../src/ui/storybook/Storybook"; + +export const baseDecorator = (Story) => { + return ( + + + + ); +}; diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 000000000..0ed2f6b4a --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,34 @@ +import type { StorybookConfig } from "@storybook/react-vite"; + +const EXCLUDED_VITE_PLUGIN = ["vite-plugin-mpa-router", "vite-plugin-service-worker"]; + +const config: StorybookConfig = { + stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], + addons: ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions"], + core: { + builder: "@storybook/builder-vite", + }, + framework: { + name: "@storybook/react-vite", + options: {}, + }, + docs: { + autodocs: "tag", + }, + async viteFinal(config) { + return { + ...config, + appType: undefined, + base: "/storybook", + plugins: config.plugins?.filter((p) => { + if (p && "name" in p && typeof p.name === "string") { + const exclude = EXCLUDED_VITE_PLUGIN.includes(p.name); + return !exclude; + } + return true; + }), + test: undefined, + }; + }, +}; +export default config; diff --git a/.storybook/preview.ts b/.storybook/preview.ts new file mode 100644 index 000000000..285c445d9 --- /dev/null +++ b/.storybook/preview.ts @@ -0,0 +1,18 @@ +import type { Preview } from "@storybook/react"; +import { baseDecorator } from "./base"; + +export const decorators = [baseDecorator]; + +const preview: Preview = { + parameters: { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + }, +}; + +export default preview; diff --git a/netlify.toml b/netlify.toml index cea40b963..1b096ff85 100644 --- a/netlify.toml +++ b/netlify.toml @@ -17,6 +17,11 @@ to = "/logviewer/index.html" status = 200 +[[redirects]] + from = "/storybook" + to = "/storybook/index.html" + status = 200 + [[redirects]] from = "/*" to = "/index.html" diff --git a/package.json b/package.json index 21a6fa7f4..5a0d2480d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build && npm run docs:build", + "build": "tsc && vite build && npm run docs:build && npm run build-storybook", "build:scripting-runtime": "./src/engine/scripting/emscripten/build.sh", "build:examples": "./examples/build.sh", "typecheck": "tsc --noEmit", @@ -23,7 +23,9 @@ "docs:dev": "npm run docs:build-api && vitepress dev docs", "docs:build": "npm run docs:build-api && vitepress build docs", "docs:preview": "vitepress preview docs", - "docs:build-api": "typedoc" + "docs:build-api": "typedoc", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build -o dist/storybook" }, "dependencies": { "3d-tiles-renderer": "https://github.com/matrix-org/3DTilesRendererJS.git#v0.3.9-patch", @@ -80,6 +82,14 @@ "yoga-wasm-web": "^0.3.3" }, "devDependencies": { + "@storybook/addon-essentials": "^7.0.20", + "@storybook/addon-interactions": "^7.0.20", + "@storybook/addon-links": "^7.0.20", + "@storybook/blocks": "^7.0.20", + "@storybook/builder-vite": "^7.0.21", + "@storybook/react": "^7.0.20", + "@storybook/react-vite": "^7.0.20", + "@storybook/testing-library": "^0.0.14-next.2", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react-hooks": "^8.0.0", "@types/babel__core": "^7.1.19", @@ -113,6 +123,7 @@ "eslint-plugin-matrix-org": "^1.1.0", "eslint-plugin-react": "^7.29.4", "eslint-plugin-react-hooks": "^4.4.0", + "eslint-plugin-storybook": "^0.6.12", "eslint-plugin-unicorn": "^46.0.0", "husky": "^8.0.3", "jsdom": "^21.1.0", @@ -120,6 +131,7 @@ "postcss-preset-env": "^8.0.1", "postinstall-postinstall": "^2.1.0", "prettier": "^2.6.1", + "storybook": "^7.0.20", "stylelint": "^15.2.0", "stylelint-config-prettier": "^9.0.3", "stylelint-config-standard": "^30.0.1", diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 89ce4a81d..e244102ec 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -1,4 +1,4 @@ -import { lazy, ReactNode, Suspense, useEffect } from "react"; +import { lazy, Suspense, useEffect } from "react"; import { Route, Routes } from "react-router-dom"; import { useFocusVisible } from "@react-aria/interactions"; import { serviceWorkerFile } from "virtual:vite-plugin-service-worker"; @@ -32,23 +32,6 @@ function FocusOutlineManager() { return <>; } -let storybookRoute: ReactNode = null; - -if (import.meta.env.VITE_NETLIFY_DEPLOY_CONTEXT !== "production") { - const Storybook = lazy(() => import("./storybook/Storybook")); - - storybookRoute = ( - - - - } - /> - ); -} - const LandingPage = lazy(() => import("./site/LandingPage")); const PreviewBlog = lazy(() => import("./site/PreviewBlog")); const LoginView = lazy(() => import("./views/login/LoginView")); @@ -147,7 +130,6 @@ export function App() { } /> - {storybookRoute} } /> diff --git a/src/ui/atoms/avatar/Avatar.stories.tsx b/src/ui/atoms/avatar/Avatar.stories.tsx index f955c2299..b4effc1d9 100644 --- a/src/ui/atoms/avatar/Avatar.stories.tsx +++ b/src/ui/atoms/avatar/Avatar.stories.tsx @@ -1,12 +1,17 @@ +import { Meta } from "@storybook/react"; + import { Avatar } from "./Avatar"; import { AvatarPile } from "./AvatarPile"; import { AvatarBadgeWrapper } from "./AvatarBadgeWrapper"; import { AvatarOutline } from "./AvatarOutline"; import { StatusBadge } from "../badge/StatusBadge"; -export const title = "Avatar"; +export default { + title: "Avatar", + component: Avatar, +} as Meta; -export default function AvatarStories() { +export const AvatarStories = () => { const imgSrc = "https://cdn.britannica.com/92/80592-050-86EF29F3/Mouflon-ram.jpg"; return ( @@ -104,4 +109,4 @@ export default function AvatarStories() { ); -} +}; diff --git a/src/ui/atoms/badge/NotificationBadge.stories.tsx b/src/ui/atoms/badge/NotificationBadge.stories.tsx index 2dc70dd9c..5e0b18d50 100644 --- a/src/ui/atoms/badge/NotificationBadge.stories.tsx +++ b/src/ui/atoms/badge/NotificationBadge.stories.tsx @@ -1,8 +1,13 @@ +import { Meta } from "@storybook/react"; + import { NotificationBadge } from "./NotificationBadge"; -export const title = "NotificationBadge"; +export default { + title: "NotificationBadge", + component: NotificationBadge, +} as Meta; -export default function NotificationBadgeStories() { +export const NotificationBadgeStories = () => { return (
@@ -11,4 +16,4 @@ export default function NotificationBadgeStories() {
); -} +}; diff --git a/src/ui/atoms/badge/StatusBadge.stories.tsx b/src/ui/atoms/badge/StatusBadge.stories.tsx index b599fa43c..c7d5aee82 100644 --- a/src/ui/atoms/badge/StatusBadge.stories.tsx +++ b/src/ui/atoms/badge/StatusBadge.stories.tsx @@ -1,8 +1,13 @@ +import { Meta } from "@storybook/react"; + import { StatusBadge } from "./StatusBadge"; -export const title = "StatusBadge"; +export default { + title: "StatusBadge", + component: StatusBadge, +} as Meta; -export default function StatusBadgeStories() { +export function StatusBadgeStories() { return (
diff --git a/src/ui/atoms/button/Button.stories.tsx b/src/ui/atoms/button/Button.stories.tsx index 8e3fdd938..d524f4d7d 100644 --- a/src/ui/atoms/button/Button.stories.tsx +++ b/src/ui/atoms/button/Button.stories.tsx @@ -1,10 +1,15 @@ +import { Meta } from "@storybook/react"; + import { Button } from "./Button"; import { Icon } from "../icon/Icon"; import HomeIC from "../../../../res/ic/home.svg"; -export const title = "Button"; +export default { + title: "Button", + component: Button, +} as Meta; -export default function ButtonStories() { +export function ButtonStories() { return (
diff --git a/src/ui/atoms/button/IconButton.stories.tsx b/src/ui/atoms/button/IconButton.stories.tsx index b83678c32..2e203fc94 100644 --- a/src/ui/atoms/button/IconButton.stories.tsx +++ b/src/ui/atoms/button/IconButton.stories.tsx @@ -1,9 +1,14 @@ +import { Meta } from "@storybook/react"; + import { IconButton } from "./IconButton"; import HomeIC from "../../../../res/ic/home.svg"; -export const title = "IconButton"; +export default { + title: "IconButton", + component: IconButton, +} as Meta; -export default function IconButtonStories() { +export function IconButtonStories() { return (
diff --git a/src/ui/atoms/button/Switch.stories.tsx b/src/ui/atoms/button/Switch.stories.tsx index acaa57454..138418dac 100644 --- a/src/ui/atoms/button/Switch.stories.tsx +++ b/src/ui/atoms/button/Switch.stories.tsx @@ -1,8 +1,13 @@ +import { Meta } from "@storybook/react"; + import { Switch } from "./Switch"; -export const title = "Switch"; +export default { + title: "Switch", + component: Switch, +} as Meta; -export default function SwitchStories() { +export function SwitchStories() { return (
console.log(v)} /> diff --git a/src/ui/atoms/checkbox/Checkbox.stories.tsx b/src/ui/atoms/checkbox/Checkbox.stories.tsx index 6c464bd23..a26800cef 100644 --- a/src/ui/atoms/checkbox/Checkbox.stories.tsx +++ b/src/ui/atoms/checkbox/Checkbox.stories.tsx @@ -1,8 +1,13 @@ +import { Meta } from "@storybook/react"; + import { Checkbox } from "./Checkbox"; -export const title = "Checkbox"; +export default { + title: "Checkbox", + component: Checkbox, +} as Meta; -export default function CheckboxStories() { +export function CheckboxStories() { return (
console.log(v)} /> diff --git a/src/ui/atoms/chip/Chip.stories.tsx b/src/ui/atoms/chip/Chip.stories.tsx index defebb3e7..4c9d6e8d5 100644 --- a/src/ui/atoms/chip/Chip.stories.tsx +++ b/src/ui/atoms/chip/Chip.stories.tsx @@ -1,10 +1,15 @@ +import { Meta } from "@storybook/react"; + import { Chip } from "./Chip"; import { Text } from "../text/Text"; import { Avatar } from "../avatar/Avatar"; -export const title = "Chip"; +export default { + title: "Chip", + component: Chip, +} as Meta; -export default function ChipStories() { +export function ChipStories() { return (
diff --git a/src/ui/atoms/content/Content.stories.tsx b/src/ui/atoms/content/Content.stories.tsx index f035eccdf..cad2d358e 100644 --- a/src/ui/atoms/content/Content.stories.tsx +++ b/src/ui/atoms/content/Content.stories.tsx @@ -1,8 +1,13 @@ +import { Meta } from "@storybook/react"; + import { Content } from "./Content"; -export const title = "Content"; +export default { + title: "Content", + component: Content, +} as Meta; -export default function ContentStories() { +export function ContentStories() { return (
diff --git a/src/ui/atoms/dialog/Dialog.stories.tsx b/src/ui/atoms/dialog/Dialog.stories.tsx index 5a07e258a..e042d5382 100644 --- a/src/ui/atoms/dialog/Dialog.stories.tsx +++ b/src/ui/atoms/dialog/Dialog.stories.tsx @@ -1,11 +1,16 @@ +import { Meta } from "@storybook/react"; + import { Dialog } from "./Dialog"; import { Button } from "../button/Button"; import { Header } from "../header/Header"; import { HeaderTitle } from "../header/HeaderTitle"; -export const title = "Dialog"; +export default { + title: "Dialog", + component: Dialog, +} as Meta; -export default function DialogStories() { +export function DialogStories() { return ( Open Dialog}>
Dialog} /> diff --git a/src/ui/atoms/footer/Footer.stories.tsx b/src/ui/atoms/footer/Footer.stories.tsx index 59096bd4e..c5c7634bd 100644 --- a/src/ui/atoms/footer/Footer.stories.tsx +++ b/src/ui/atoms/footer/Footer.stories.tsx @@ -1,8 +1,13 @@ +import { Meta } from "@storybook/react"; + import { Footer } from "./Footer"; -export const title = "Footer"; +export default { + title: "Footer", + component: Footer, +} as Meta; -export default function FooterStories() { +export function FooterStories() { return (