feat(ui): introduces storybook

- adds the necessary dependencies and configuration for Storybook.
- refactors the `Button` component and adds some stories for it.
This commit is contained in:
Brian McGee
2025-05-26 12:26:38 +01:00
parent 7503784699
commit bf993af56f
35 changed files with 6805 additions and 56 deletions

View File

@@ -0,0 +1,32 @@
import { createRequire } from "module";
import { dirname, join } from "path";
import { mergeConfig } from "vite";
import type { StorybookConfig } from "@kachurun/storybook-solid-vite";
const require = createRequire(import.meta.url);
const getAbsolutePath = (pkg: string) =>
dirname(require.resolve(join(pkg, "package.json")));
const config: StorybookConfig = {
stories: ["../src/components/**/*.mdx", "../src/components/**/*.stories.tsx"],
addons: [
getAbsolutePath("@storybook/addon-links"),
getAbsolutePath("@storybook/addon-essentials"),
getAbsolutePath("@chromatic-com/storybook"),
getAbsolutePath("@storybook/addon-interactions"),
],
framework: {
name: "@kachurun/storybook-solid-vite",
options: {},
},
async viteFinal(config) {
return mergeConfig(config, {
define: { "process.env": {} },
});
},
docs: {
autodocs: "tag",
},
};
export default config;

View File

@@ -0,0 +1,25 @@
import type { Preview } from "@kachurun/storybook-solid";
export const preview: Preview = {
tags: ["autodocs"],
parameters: {
docs: { toc: true },
backgrounds: {
values: [
{ name: "Dark", value: "#333" },
{ name: "Light", value: "#F7F9F2" },
],
default: "Light",
},
// automatically create action args for all props that start with "on"
actions: { argTypesRegex: "^on.*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
};
export default preview;

View File

@@ -31,6 +31,12 @@ Solid in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br> Your app is The build is minified and the filenames include the hashes.<br> Your app is
ready to be deployed! ready to be deployed!
### `npm run storybook`
Starts an instance of [storybook](https://storybook.js.org/).
For more info on how to write stories, please [see here](https://storybook.js.org/docs).
## Deployment ## Deployment
You can deploy the `dist` folder to any static host provider (netlify, surge, You can deploy the `dist` folder to any static host provider (netlify, surge,

File diff suppressed because it is too large Load Diff

View File

@@ -10,12 +10,22 @@
"convert-html": "node gtk.webview.js", "convert-html": "node gtk.webview.js",
"serve": "vite preview", "serve": "vite preview",
"check": "tsc --noEmit --skipLibCheck && eslint ./src", "check": "tsc --noEmit --skipLibCheck && eslint ./src",
"test": "vitest run --typecheck" "test": "vitest run --typecheck",
"storybook": "storybook dev -p 6006"
}, },
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-import-attributes": "^7.27.1",
"@chromatic-com/storybook": "^3.2.6",
"@eslint/js": "^9.3.0", "@eslint/js": "^9.3.0",
"@kachurun/storybook-solid": "^8.6.7",
"@kachurun/storybook-solid-vite": "^8.6.7",
"@storybook/addon-essentials": "^8.6.14",
"@storybook/addon-interactions": "^8.6.14",
"@storybook/addon-links": "^8.6.14",
"@storybook/addon-viewport": "^8.6.14",
"@storybook/builder-vite": "^8.6.14",
"@storybook/test-runner": "^0.22.0",
"@tailwindcss/typography": "^0.5.13", "@tailwindcss/typography": "^0.5.13",
"@types/json-schema": "^7.0.15", "@types/json-schema": "^7.0.15",
"@types/node": "^22.15.19", "@types/node": "^22.15.19",
@@ -30,6 +40,7 @@
"postcss-url": "^10.1.3", "postcss-url": "^10.1.3",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"solid-devtools": "^0.34.0", "solid-devtools": "^0.34.0",
"storybook": "^8.6.14",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"typescript-eslint": "^8.32.1", "typescript-eslint": "^8.32.1",

View File

@@ -10,7 +10,7 @@ import {
import { Portal } from "solid-js/web"; import { Portal } from "solid-js/web";
import { useFloating } from "../base"; import { useFloating } from "../base";
import { autoUpdate, flip, hide, offset, shift, size } from "@floating-ui/dom"; import { autoUpdate, flip, hide, offset, shift, size } from "@floating-ui/dom";
import { Button } from "@/src/components/button"; import { Button } from "../../components/Button/Button";
import { import {
InputBase, InputBase,
InputError, InputError,

View File

@@ -20,7 +20,7 @@ import { createEffect, For, JSX, Match, Show, Switch } from "solid-js";
import cx from "classnames"; import cx from "classnames";
import { Label } from "../base/label"; import { Label } from "../base/label";
import { SelectInput } from "../fields/Select"; import { SelectInput } from "../fields/Select";
import { Button } from "@/src/components/button"; import { Button } from "../../components/Button/Button";
import Icon from "@/src/components/icon"; import Icon from "@/src/components/icon";
function generateDefaults(schema: JSONSchema7): unknown { function generateDefaults(schema: JSONSchema7): unknown {

View File

@@ -5,7 +5,7 @@ import {
SubmitHandler, SubmitHandler,
} from "@modular-forms/solid"; } from "@modular-forms/solid";
import { TextInput } from "@/src/Form/fields/TextInput"; import { TextInput } from "@/src/Form/fields/TextInput";
import { Button } from "./components/button"; import { Button } from "./components/Button/Button";
import { callApi } from "./api"; import { callApi } from "./api";
import { API } from "@/api/API"; import { API } from "@/api/API";
import { createSignal, Match, Switch } from "solid-js"; import { createSignal, Match, Switch } from "solid-js";

View File

@@ -1,5 +1,5 @@
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
import { Button } from "./button"; import { Button } from "./Button/Button";
import Icon from "./icon"; import Icon from "./icon";
export const BackButton = () => { export const BackButton = () => {

View File

@@ -1,6 +1,6 @@
@import "./button-light.css"; @import "Button-Light.css";
@import "./button-dark.css"; @import "Button-Dark.css";
@import "./button-ghost.css"; @import "Button-Ghost.css";
.button { .button {
@apply inline-flex items-center flex-shrink gap-1 justify-center p-4 font-semibold; @apply inline-flex items-center flex-shrink gap-1 justify-center p-4 font-semibold;

View File

@@ -0,0 +1,43 @@
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Button, ButtonProps } from "./Button";
import FlashIcon from "@/icons/flash.svg";
const meta: Meta<ButtonProps> = {
title: "Components/Button",
component: Button,
};
export default meta;
type Story = StoryObj<ButtonProps>;
const children = "click me";
const startIcon = <FlashIcon width={16} height={16} viewBox="0 0 48 48" />;
export const Default: Story = {
args: {
children,
startIcon,
},
};
export const Small: Story = {
args: {
...Default.args,
size: "s",
},
};
export const Light: Story = {
args: {
...Default.args,
variant: "light",
},
};
export const Ghost: Story = {
args: {
...Default.args,
variant: "ghost",
},
};

View File

@@ -1,8 +1,8 @@
import { splitProps, type JSX } from "solid-js"; import { splitProps, type JSX } from "solid-js";
import cx from "classnames"; import cx from "classnames";
import { Typography } from "../Typography"; import { Typography } from "../Typography";
//import './css/index.css'
import "./css/index.css"; import "./Button-Base.css";
type Variants = "dark" | "light" | "ghost"; type Variants = "dark" | "light" | "ghost";
type Size = "default" | "s"; type Size = "default" | "s";
@@ -42,7 +42,8 @@ const sizeFont: Record<Size, string> = {
s: cx("text-[0.75rem]"), s: cx("text-[0.75rem]"),
}; };
interface ButtonProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> { export interface ButtonProps
extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: Variants; variant?: Variants;
size?: Size; size?: Size;
children?: JSX.Element; children?: JSX.Element;

View File

@@ -9,7 +9,7 @@ import {
shift, shift,
} from "@floating-ui/dom"; } from "@floating-ui/dom";
import cx from "classnames"; import cx from "classnames";
import { Button } from "./button"; import { Button } from "./Button/Button";
interface MenuProps { interface MenuProps {
/** /**

View File

@@ -1,6 +1,6 @@
import { createSignal, JSX, Show } from "solid-js"; import { createSignal, JSX, Show } from "solid-js";
import Icon from "../icon"; import Icon from "../icon";
import { Button } from "../button"; import { Button } from "../Button/Button";
import cx from "classnames"; import cx from "classnames";
import "./accordion.css"; import "./accordion.css";

View File

@@ -1,27 +0,0 @@
import { Button } from ".";
import FlashIcon from "@/icons/flash.svg";
export const Test = () => {
<div class="p-8">
<Button>Label</Button>
<Button
startIcon={<FlashIcon width={16} height={16} viewBox="0 0 48 48" />}
>
Label
</Button>
<Button
variant="light"
endIcon={<FlashIcon width={16} height={16} viewBox="0 0 48 48" />}
>
Label
</Button>
<Button size="s">Label</Button>
<Button
variant="light"
size="s"
endIcon={<FlashIcon width={13} height={13} viewBox="0 0 48 48" />}
>
Label
</Button>
</div>;
};

View File

@@ -1,6 +1,6 @@
import Dialog from "corvu/dialog"; import Dialog from "corvu/dialog";
import { createSignal, JSX } from "solid-js"; import { createSignal, JSX } from "solid-js";
import { Button } from "../button"; import { Button } from "../Button/Button";
import Icon from "../icon"; import Icon from "../icon";
import cx from "classnames"; import cx from "classnames";

View File

@@ -9,7 +9,7 @@ import {
import toast from "solid-toast"; import toast from "solid-toast";
import { TextInput } from "@/src/Form/fields/TextInput"; import { TextInput } from "@/src/Form/fields/TextInput";
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
import { Button } from "@/src/components/button"; import { Button } from "../../components/Button/Button";
import Icon from "@/src/components/icon"; import Icon from "@/src/components/icon";
import { useClanContext } from "@/src/contexts/clan"; import { useClanContext } from "@/src/contexts/clan";

View File

@@ -13,7 +13,7 @@ import {
} from "@modular-forms/solid"; } from "@modular-forms/solid";
import { TextInput } from "@/src/Form/fields/TextInput"; import { TextInput } from "@/src/Form/fields/TextInput";
import toast from "solid-toast"; import toast from "solid-toast";
import { Button } from "@/src/components/button"; import { Button } from "../../components/Button/Button";
import Icon from "@/src/components/icon"; import Icon from "@/src/components/icon";
import { Header } from "@/src/layout/header"; import { Header } from "@/src/layout/header";
import { clanMetaQuery } from "@/src/queries/clan-meta"; import { clanMetaQuery } from "@/src/queries/clan-meta";

View File

@@ -3,7 +3,7 @@ import { useFloating } from "@/src/floating";
import { autoUpdate, flip, hide, offset, shift } from "@floating-ui/dom"; import { autoUpdate, flip, hide, offset, shift } from "@floating-ui/dom";
import { A, useNavigate } from "@solidjs/router"; import { A, useNavigate } from "@solidjs/router";
import { registerClan } from "@/src/hooks"; import { registerClan } from "@/src/hooks";
import { Button } from "@/src/components/button"; import { Button } from "../../components/Button/Button";
import Icon from "@/src/components/icon"; import Icon from "@/src/components/icon";
import { useClanContext } from "@/src/contexts/clan"; import { useClanContext } from "@/src/contexts/clan";
import { clanURIs, setActiveClanURI } from "@/src/stores/clan"; import { clanURIs, setActiveClanURI } from "@/src/stores/clan";

View File

@@ -1,4 +1,4 @@
import { Button } from "@/src/components/button"; import { Button } from "../../components/Button/Button";
import { InputBase, InputLabel } from "@/src/components/inputBase"; import { InputBase, InputLabel } from "@/src/components/inputBase";
import { TextInput } from "@/src/Form/fields"; import { TextInput } from "@/src/Form/fields";
import { Header } from "@/src/layout/header"; import { Header } from "@/src/layout/header";

View File

@@ -1,5 +1,5 @@
import { callApi } from "@/src/api"; import { callApi } from "@/src/api";
import { Button } from "@/src/components/button"; import { Button } from "../../components/Button/Button";
// Icon is used in CustomFileField, ensure it's available or remove if not needed there // Icon is used in CustomFileField, ensure it's available or remove if not needed there
import Icon from "@/src/components/icon"; import Icon from "@/src/components/icon";
import { Typography } from "@/src/components/Typography"; import { Typography } from "@/src/components/Typography";

View File

@@ -1,6 +1,6 @@
import { type Component, createSignal, For, Show } from "solid-js"; import { type Component, createSignal, For, Show } from "solid-js";
import { OperationResponse, callApi } from "@/src/api"; import { OperationResponse, callApi } from "@/src/api";
import { Button } from "@/src/components/button"; import { Button } from "../../components/Button/Button";
import Icon from "@/src/components/icon"; import Icon from "@/src/components/icon";
type ServiceModel = Extract< type ServiceModel = Extract<

View File

@@ -1,5 +1,5 @@
import { callApi, OperationArgs } from "@/src/api"; import { callApi, OperationArgs } from "@/src/api";
import { Button } from "@/src/components/button"; import { Button } from "../../components/Button/Button";
import Icon from "@/src/components/icon"; import Icon from "@/src/components/icon";
import { TextInput } from "@/src/Form/fields/TextInput"; import { TextInput } from "@/src/Form/fields/TextInput";
import { Header } from "@/src/layout/header"; import { Header } from "@/src/layout/header";

View File

@@ -10,7 +10,7 @@ import { useNavigate, useParams, useSearchParams } from "@solidjs/router";
import { createQuery, useQuery, useQueryClient } from "@tanstack/solid-query"; import { createQuery, useQuery, useQueryClient } from "@tanstack/solid-query";
import { createEffect, createSignal, For, Match, Show, Switch } from "solid-js"; import { createEffect, createSignal, For, Match, Show, Switch } from "solid-js";
import { Button } from "@/src/components/button"; import { Button } from "../../components/Button/Button";
import Icon from "@/src/components/icon"; import Icon from "@/src/components/icon";
import { TextInput } from "@/src/Form/fields/TextInput"; import { TextInput } from "@/src/Form/fields/TextInput";
import Accordion from "@/src/components/accordion"; import Accordion from "@/src/components/accordion";

View File

@@ -1,5 +1,5 @@
import { callApi } from "@/src/api"; import { callApi } from "@/src/api";
import { Button } from "@/src/components/button"; import { Button } from "../../../components/Button/Button";
import Icon from "@/src/components/icon"; import Icon from "@/src/components/icon";
import { InputError, InputLabel } from "@/src/components/inputBase"; import { InputError, InputLabel } from "@/src/components/inputBase";
import { FieldLayout } from "@/src/Form/fields/layout"; import { FieldLayout } from "@/src/Form/fields/layout";

View File

@@ -14,7 +14,7 @@ import toast from "solid-toast";
import { useNavigate, useParams, useSearchParams } from "@solidjs/router"; import { useNavigate, useParams, useSearchParams } from "@solidjs/router";
import { StepProps } from "./hardware-step"; import { StepProps } from "./hardware-step";
import { BackButton } from "@/src/components/BackButton"; import { BackButton } from "@/src/components/BackButton";
import { Button } from "@/src/components/button"; import { Button } from "../../../components/Button/Button";
import { useClanContext } from "@/src/contexts/clan"; import { useClanContext } from "@/src/contexts/clan";
export type VarsValues = FieldValues & Record<string, Record<string, string>>; export type VarsValues = FieldValues & Record<string, Record<string, string>>;

View File

@@ -3,7 +3,7 @@ import { callApi, OperationResponse } from "@/src/api";
import { MachineListItem } from "@/src/components/machine-list-item"; import { MachineListItem } from "@/src/components/machine-list-item";
import { useQuery, useQueryClient } from "@tanstack/solid-query"; import { useQuery, useQueryClient } from "@tanstack/solid-query";
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
import { Button } from "@/src/components/button"; import { Button } from "../../components/Button/Button";
import Icon from "@/src/components/icon"; import Icon from "@/src/components/icon";
import { Header } from "@/src/layout/header"; import { Header } from "@/src/layout/header";
import { makePersisted } from "@solid-primitives/storage"; import { makePersisted } from "@solid-primitives/storage";

View File

@@ -7,7 +7,7 @@ import { createQuery } from "@tanstack/solid-query";
import { JSONSchema7 } from "json-schema"; import { JSONSchema7 } from "json-schema";
import { SubmitHandler } from "@modular-forms/solid"; import { SubmitHandler } from "@modular-forms/solid";
import { DynForm } from "@/src/Form/form"; import { DynForm } from "@/src/Form/form";
import { Button } from "@/src/components/button"; import { Button } from "../../components/Button/Button";
import Icon from "@/src/components/icon"; import Icon from "@/src/components/icon";
import { useClanContext } from "@/src/contexts/clan"; import { useClanContext } from "@/src/contexts/clan";
import { activeClanURI } from "@/src/stores/clan"; import { activeClanURI } from "@/src/stores/clan";

View File

@@ -1,5 +1,5 @@
import { SuccessData } from "@/src/api"; import { SuccessData } from "@/src/api";
import { Button } from "@/src/components/button"; import { Button } from "../../components/Button/Button";
import { Header } from "@/src/layout/header"; import { Header } from "@/src/layout/header";
import { createModulesQuery } from "@/src/queries"; import { createModulesQuery } from "@/src/queries";
import { A, useNavigate } from "@solidjs/router"; import { A, useNavigate } from "@solidjs/router";

View File

@@ -1,4 +1,4 @@
import { Button } from "@/src/components/button"; import { Button } from "../../components/Button/Button";
import { registerClan } from "@/src/hooks"; import { registerClan } from "@/src/hooks";
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
import { useClanContext } from "@/src/contexts/clan"; import { useClanContext } from "@/src/contexts/clan";

View File

@@ -1,6 +1,6 @@
import { createEffect, createSignal, onCleanup, onMount, Show } from "solid-js"; import { createEffect, createSignal, onCleanup, onMount, Show } from "solid-js";
import * as THREE from "three"; import * as THREE from "three";
import { Button } from "./components/button"; import { Button } from "./components/Button/Button";
import Icon from "./components/icon"; import Icon from "./components/icon";
function addCubesSpiral({ function addCubesSpiral({

View File

@@ -1,4 +1,8 @@
{ {
"exclude": [
// ignore storybook stories
"**/*.stories.tsx"
],
"compilerOptions": { "compilerOptions": {
"strict": true, "strict": true,
"target": "ESNext", "target": "ESNext",