chore(stories): eslint/tsconfig migration
Migrate Storybook package to eslint flat config + composite tsconfig.
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
storybook-static
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import type { StorybookConfig } from '@storybook/vue3-vite';
|
||||||
|
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
stories: ['../stories/**/*.stories.ts'],
|
||||||
|
addons: [
|
||||||
|
'@storybook/addon-docs',
|
||||||
|
'@storybook/addon-a11y',
|
||||||
|
],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/vue3-vite',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
docs: {
|
||||||
|
defaultName: 'Docs',
|
||||||
|
},
|
||||||
|
typescript: {
|
||||||
|
check: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
.sb-dialog-trigger,
|
||||||
|
.sb-dialog-close,
|
||||||
|
.sb-toggle {
|
||||||
|
font: inherit;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #888;
|
||||||
|
background: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-toggle[aria-pressed='true'] { background: #222; color: #fff; }
|
||||||
|
.sb-toggle[data-disabled] { opacity: 0.5; cursor: not-allowed; }
|
||||||
|
|
||||||
|
.sb-dialog-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.4); }
|
||||||
|
|
||||||
|
.sb-dialog-content {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: #fff;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
min-width: 320px;
|
||||||
|
max-width: 90vw;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||||
|
display: grid;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-dialog-title { margin: 0; font-size: 1.125rem; font-weight: 600; }
|
||||||
|
.sb-dialog-desc { margin: 0; color: #555; }
|
||||||
|
|
||||||
|
.sb-switch {
|
||||||
|
position: relative;
|
||||||
|
width: 44px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid #888;
|
||||||
|
background: #ddd;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
.sb-switch[aria-checked='true'] { background: #10b981; border-color: #10b981; }
|
||||||
|
.sb-switch-thumb {
|
||||||
|
display: block;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||||
|
transform: translateX(2px);
|
||||||
|
transition: transform 0.15s;
|
||||||
|
}
|
||||||
|
.sb-switch[aria-checked='true'] .sb-switch-thumb { transform: translateX(22px); }
|
||||||
|
|
||||||
|
.sb-progress {
|
||||||
|
position: relative;
|
||||||
|
width: 240px;
|
||||||
|
height: 8px;
|
||||||
|
background: #eee;
|
||||||
|
border-radius: 999px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.sb-progress-ind { height: 100%; background: #3b82f6; transition: width 0.2s; }
|
||||||
|
.sb-progress[data-state='indeterminate'] .sb-progress-ind {
|
||||||
|
animation: sb-progress-indeterminate 1.2s ease-in-out infinite;
|
||||||
|
background: linear-gradient(90deg, transparent, #3b82f6, transparent);
|
||||||
|
}
|
||||||
|
@keyframes sb-progress-indeterminate {
|
||||||
|
from { transform: translateX(-100%); }
|
||||||
|
to { transform: translateX(100%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-collapsible { font-family: system-ui; max-width: 320px; }
|
||||||
|
.sb-collapsible-trigger {
|
||||||
|
font: inherit;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #888;
|
||||||
|
background: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.sb-collapsible-content {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border: 1px dashed #bbb;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 6px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-avatar {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #e5e7eb;
|
||||||
|
font-family: system-ui;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.sb-avatar-img { width: 100%; height: 100%; object-fit: cover; }
|
||||||
|
.sb-avatar-fallback { display: inline-flex; align-items: center; justify-content: center; width: 100%; height: 100%; }
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import type { Preview } from '@storybook/vue3-vite';
|
||||||
|
import './preview-styles.css';
|
||||||
|
|
||||||
|
const preview: Preview = {
|
||||||
|
parameters: {
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
a11y: {
|
||||||
|
test: 'todo',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { base, compose, imports, stylistic, typescript } from '@robonen/eslint';
|
||||||
|
|
||||||
|
export default compose(base, typescript, imports, stylistic, {
|
||||||
|
name: 'stories/overrides',
|
||||||
|
files: ['**/*.vue', '**/*.stories.ts'],
|
||||||
|
rules: {
|
||||||
|
'@stylistic/no-multiple-empty-lines': 'off',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "@robonen/stories",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"description": "Storybook for @robonen/primitives",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"lint:check": "eslint .",
|
||||||
|
"lint:fix": "eslint . --fix",
|
||||||
|
"dev": "storybook dev -p 6006 --no-open",
|
||||||
|
"build": "storybook build -o storybook-static"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@robonen/primitives": "workspace:*",
|
||||||
|
"@robonen/vue": "workspace:*",
|
||||||
|
"vue": "catalog:"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@robonen/eslint": "workspace:*",
|
||||||
|
"@robonen/tsconfig": "workspace:*",
|
||||||
|
"@storybook/addon-a11y": "^10.2.1",
|
||||||
|
"@storybook/addon-docs": "^10.2.1",
|
||||||
|
"@storybook/vue3-vite": "^10.2.1",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.6",
|
||||||
|
"eslint": "catalog:",
|
||||||
|
"storybook": "^10.2.1",
|
||||||
|
"vite": "^7.1.9"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
||||||
|
import { AspectRatio } from '@robonen/primitives';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Layout/AspectRatio',
|
||||||
|
component: AspectRatio,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
ratio: { control: { type: 'number', min: 0.1, step: 0.1 } },
|
||||||
|
},
|
||||||
|
args: { ratio: 16 / 9 },
|
||||||
|
} satisfies Meta<typeof AspectRatio>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Widescreen: Story = {
|
||||||
|
render: args => ({
|
||||||
|
components: { AspectRatio },
|
||||||
|
setup: () => ({ args }),
|
||||||
|
template: `
|
||||||
|
<div style="width: 400px; border-radius: 8px; overflow: hidden">
|
||||||
|
<AspectRatio v-bind="args">
|
||||||
|
<img
|
||||||
|
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=800"
|
||||||
|
alt="landscape"
|
||||||
|
style="width:100%;height:100%;object-fit:cover"
|
||||||
|
/>
|
||||||
|
</AspectRatio>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Square: Story = {
|
||||||
|
args: { ratio: 1 },
|
||||||
|
render: args => ({
|
||||||
|
components: { AspectRatio },
|
||||||
|
setup: () => ({ args }),
|
||||||
|
template: `
|
||||||
|
<div style="width: 200px; background: #eee; border-radius: 8px; overflow: hidden">
|
||||||
|
<AspectRatio v-bind="args">
|
||||||
|
<div style="display:flex;align-items:center;justify-content:center;height:100%;font-family:system-ui">1 : 1</div>
|
||||||
|
</AspectRatio>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
||||||
|
import { AvatarFallback, AvatarImage, AvatarRoot } from '@robonen/primitives';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Media/Avatar',
|
||||||
|
component: AvatarRoot,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
} satisfies Meta<typeof AvatarRoot>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Loaded: Story = {
|
||||||
|
render: () => ({
|
||||||
|
components: { AvatarRoot, AvatarImage, AvatarFallback },
|
||||||
|
template: `
|
||||||
|
<AvatarRoot class="sb-avatar">
|
||||||
|
<AvatarImage src="https://i.pravatar.cc/96?img=5" alt="User" class="sb-avatar-img" />
|
||||||
|
<AvatarFallback class="sb-avatar-fallback" :delayMs="300">CT</AvatarFallback>
|
||||||
|
</AvatarRoot>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Fallback: Story = {
|
||||||
|
render: () => ({
|
||||||
|
components: { AvatarRoot, AvatarImage, AvatarFallback },
|
||||||
|
template: `
|
||||||
|
<AvatarRoot class="sb-avatar">
|
||||||
|
<AvatarImage src="https://invalid.example.com/missing.png" alt="User" class="sb-avatar-img" />
|
||||||
|
<AvatarFallback class="sb-avatar-fallback">AB</AvatarFallback>
|
||||||
|
</AvatarRoot>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
||||||
|
import { CollapsibleContent, CollapsibleRoot, CollapsibleTrigger } from '@robonen/primitives';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Disclosure/Collapsible',
|
||||||
|
component: CollapsibleRoot,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
args: { defaultOpen: false, disabled: false },
|
||||||
|
} satisfies Meta<typeof CollapsibleRoot>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: args => ({
|
||||||
|
components: { CollapsibleRoot, CollapsibleTrigger, CollapsibleContent },
|
||||||
|
setup: () => ({ args }),
|
||||||
|
template: `
|
||||||
|
<CollapsibleRoot v-bind="args" class="sb-collapsible">
|
||||||
|
<CollapsibleTrigger class="sb-collapsible-trigger">
|
||||||
|
<template #default="{ open }">{{ open ? 'Hide' : 'Show' }} details</template>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent class="sb-collapsible-content">
|
||||||
|
<p style="margin:0.5rem 0 0">Hidden content revealed when the trigger is activated.</p>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</CollapsibleRoot>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OpenByDefault: Story = { args: { defaultOpen: true }, render: Default.render };
|
||||||
|
export const Disabled: Story = { args: { disabled: true }, render: Default.render };
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
||||||
|
import {
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogPortal,
|
||||||
|
DialogRoot,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '@robonen/primitives';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Overlays/Dialog',
|
||||||
|
component: DialogRoot,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
modal: { control: 'boolean' },
|
||||||
|
defaultOpen: { control: 'boolean' },
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
modal: true,
|
||||||
|
defaultOpen: false,
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof DialogRoot>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
const render = (args: Record<string, unknown>) => ({
|
||||||
|
components: {
|
||||||
|
DialogRoot,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogPortal,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
DialogClose,
|
||||||
|
},
|
||||||
|
setup: () => ({ args }),
|
||||||
|
template: `
|
||||||
|
<DialogRoot v-bind="args">
|
||||||
|
<DialogTrigger class="sb-dialog-trigger">Open Dialog</DialogTrigger>
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogOverlay class="sb-dialog-overlay" />
|
||||||
|
<DialogContent class="sb-dialog-content">
|
||||||
|
<DialogTitle class="sb-dialog-title">Dialog Title</DialogTitle>
|
||||||
|
<DialogDescription class="sb-dialog-desc">
|
||||||
|
Traps focus, locks scroll, and dismisses on Escape or outside click.
|
||||||
|
</DialogDescription>
|
||||||
|
<DialogClose class="sb-dialog-close">Close</DialogClose>
|
||||||
|
</DialogContent>
|
||||||
|
</DialogPortal>
|
||||||
|
</DialogRoot>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Default: Story = { render };
|
||||||
|
|
||||||
|
export const OpenByDefault: Story = {
|
||||||
|
args: { defaultOpen: true },
|
||||||
|
render,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NonModal: Story = {
|
||||||
|
args: { modal: false },
|
||||||
|
render: args => ({
|
||||||
|
components: {
|
||||||
|
DialogRoot,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogPortal,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
DialogClose,
|
||||||
|
},
|
||||||
|
setup: () => ({ args }),
|
||||||
|
template: `
|
||||||
|
<DialogRoot v-bind="args">
|
||||||
|
<DialogTrigger class="sb-dialog-trigger">Open non-modal</DialogTrigger>
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogContent class="sb-dialog-content">
|
||||||
|
<DialogTitle class="sb-dialog-title">Non-modal dialog</DialogTitle>
|
||||||
|
<DialogDescription class="sb-dialog-desc">
|
||||||
|
No overlay, no scroll lock, no focus trap.
|
||||||
|
</DialogDescription>
|
||||||
|
<DialogClose class="sb-dialog-close">Close</DialogClose>
|
||||||
|
</DialogContent>
|
||||||
|
</DialogPortal>
|
||||||
|
</DialogRoot>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
||||||
|
import { Label } from '@robonen/primitives';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Forms/Label',
|
||||||
|
component: Label,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
} satisfies Meta<typeof Label>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: () => ({
|
||||||
|
components: { Label },
|
||||||
|
template: `
|
||||||
|
<div style="font-family: system-ui; display: grid; gap: 0.25rem; max-width: 300px">
|
||||||
|
<Label for="email">Email address</Label>
|
||||||
|
<input id="email" type="email" placeholder="you@example.com" style="padding:0.5rem;border:1px solid #888;border-radius:4px" />
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithCheckbox: Story = {
|
||||||
|
render: () => ({
|
||||||
|
components: { Label },
|
||||||
|
template: `
|
||||||
|
<div style="font-family: system-ui">
|
||||||
|
<Label style="display:flex;align-items:center;gap:0.5rem;cursor:pointer">
|
||||||
|
<input type="checkbox" />
|
||||||
|
Subscribe to the newsletter
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
||||||
|
import { ProgressIndicator, ProgressRoot } from '@robonen/primitives';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Feedback/Progress',
|
||||||
|
component: ProgressRoot,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
modelValue: { control: { type: 'number', min: 0, max: 100, step: 1 } },
|
||||||
|
max: { control: { type: 'number', min: 1 } },
|
||||||
|
},
|
||||||
|
args: { modelValue: 40, max: 100 },
|
||||||
|
} satisfies Meta<typeof ProgressRoot>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Determinate: Story = {
|
||||||
|
render: args => ({
|
||||||
|
components: { ProgressRoot, ProgressIndicator },
|
||||||
|
setup: () => ({ args }),
|
||||||
|
template: `
|
||||||
|
<ProgressRoot v-bind="args" class="sb-progress">
|
||||||
|
<template #default="{ value, max }">
|
||||||
|
<ProgressIndicator
|
||||||
|
class="sb-progress-ind"
|
||||||
|
:style="{ width: value == null ? '100%' : (value / max * 100) + '%' }"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</ProgressRoot>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Indeterminate: Story = {
|
||||||
|
args: { modelValue: null },
|
||||||
|
render: Determinate.render,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Complete: Story = {
|
||||||
|
args: { modelValue: 100 },
|
||||||
|
render: Determinate.render,
|
||||||
|
};
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
||||||
|
import { Separator } from '@robonen/primitives';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Layout/Separator',
|
||||||
|
component: Separator,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
orientation: { control: 'radio', options: ['horizontal', 'vertical'] },
|
||||||
|
decorative: { control: 'boolean' },
|
||||||
|
},
|
||||||
|
args: { orientation: 'horizontal', decorative: false },
|
||||||
|
} satisfies Meta<typeof Separator>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
const baseStyle = `
|
||||||
|
background: #888;
|
||||||
|
display: block;
|
||||||
|
`;
|
||||||
|
const horizontal = `${baseStyle} height: 1px; width: 100%;`;
|
||||||
|
const vertical = `${baseStyle} height: 24px; width: 1px; display: inline-block; margin: 0 0.75rem;`;
|
||||||
|
|
||||||
|
export const Horizontal: Story = {
|
||||||
|
render: args => ({
|
||||||
|
components: { Separator },
|
||||||
|
setup: () => ({ args, horizontal }),
|
||||||
|
template: `
|
||||||
|
<div style="max-width: 320px; font-family: system-ui">
|
||||||
|
<p style="margin:0 0 0.5rem">Section one</p>
|
||||||
|
<Separator v-bind="args" :style="horizontal" />
|
||||||
|
<p style="margin:0.5rem 0 0">Section two</p>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Vertical: Story = {
|
||||||
|
args: { orientation: 'vertical' },
|
||||||
|
render: args => ({
|
||||||
|
components: { Separator },
|
||||||
|
setup: () => ({ args, vertical }),
|
||||||
|
template: `
|
||||||
|
<nav style="font-family: system-ui">
|
||||||
|
<a href="#">Home</a>
|
||||||
|
<Separator v-bind="args" :style="vertical" />
|
||||||
|
<a href="#">About</a>
|
||||||
|
<Separator v-bind="args" :style="vertical" />
|
||||||
|
<a href="#">Contact</a>
|
||||||
|
</nav>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
||||||
|
import { Label, Switch } from '@robonen/primitives';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Forms/Switch',
|
||||||
|
component: Switch,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
} satisfies Meta<typeof Switch>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const BooleanDefault: Story = {
|
||||||
|
name: 'Boolean (default)',
|
||||||
|
render: () => ({
|
||||||
|
components: { Switch, Label },
|
||||||
|
template: `
|
||||||
|
<div style="display:flex;align-items:center;gap:0.5rem;font-family:system-ui">
|
||||||
|
<Switch id="airplane" class="sb-switch">
|
||||||
|
<span class="sb-switch-thumb" />
|
||||||
|
</Switch>
|
||||||
|
<Label for="airplane">Airplane mode</Label>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StringPair: Story = {
|
||||||
|
name: 'String pair ("on" / "off")',
|
||||||
|
render: () => ({
|
||||||
|
components: { Switch, Label },
|
||||||
|
setup() {
|
||||||
|
const value = ref<'on' | 'off'>('off');
|
||||||
|
return { value };
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div style="display:flex;align-items:center;gap:0.5rem;font-family:system-ui">
|
||||||
|
<Switch
|
||||||
|
v-model="value"
|
||||||
|
truthy="on"
|
||||||
|
falsy="off"
|
||||||
|
id="mode"
|
||||||
|
class="sb-switch"
|
||||||
|
>
|
||||||
|
<span class="sb-switch-thumb" />
|
||||||
|
</Switch>
|
||||||
|
<Label for="mode">Mode: {{ value }}</Label>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ObjectPair: Story = {
|
||||||
|
name: 'Object pair (generic)',
|
||||||
|
render: () => ({
|
||||||
|
components: { Switch, Label },
|
||||||
|
setup() {
|
||||||
|
const LIGHT = { theme: 'light' as const };
|
||||||
|
const DARK = { theme: 'dark' as const };
|
||||||
|
const value = ref<typeof LIGHT | typeof DARK>(LIGHT);
|
||||||
|
return { value, LIGHT, DARK };
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div style="display:flex;align-items:center;gap:0.5rem;font-family:system-ui">
|
||||||
|
<Switch
|
||||||
|
v-model="value"
|
||||||
|
:truthy="DARK"
|
||||||
|
:falsy="LIGHT"
|
||||||
|
id="theme"
|
||||||
|
class="sb-switch"
|
||||||
|
>
|
||||||
|
<span class="sb-switch-thumb" />
|
||||||
|
</Switch>
|
||||||
|
<Label for="theme">Theme: {{ value.theme }}</Label>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Disabled: Story = {
|
||||||
|
name: 'Disabled',
|
||||||
|
render: () => ({
|
||||||
|
components: { Switch, Label },
|
||||||
|
template: `
|
||||||
|
<div style="display:flex;align-items:center;gap:0.5rem;font-family:system-ui">
|
||||||
|
<Switch id="disabled-sw" class="sb-switch" disabled :default-value="true">
|
||||||
|
<span class="sb-switch-thumb" />
|
||||||
|
</Switch>
|
||||||
|
<Label for="disabled-sw">Disabled (checked)</Label>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
};
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
||||||
|
import { Toggle } from '@robonen/primitives';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Forms/Toggle',
|
||||||
|
component: Toggle,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: { disabled: { control: 'boolean' }, defaultPressed: { control: 'boolean' } },
|
||||||
|
args: { disabled: false, defaultPressed: false },
|
||||||
|
} satisfies Meta<typeof Toggle>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
const template = `
|
||||||
|
<Toggle v-bind="args" class="sb-toggle">
|
||||||
|
<template #default="{ pressed }">{{ pressed ? 'Bold ●' : 'Bold' }}</template>
|
||||||
|
</Toggle>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: args => ({ components: { Toggle }, setup: () => ({ args }), template }),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Pressed: Story = {
|
||||||
|
args: { defaultPressed: true },
|
||||||
|
render: args => ({ components: { Toggle }, setup: () => ({ args }), template }),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Disabled: Story = {
|
||||||
|
args: { disabled: true },
|
||||||
|
render: args => ({ components: { Toggle }, setup: () => ({ args }), template }),
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.src.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "@robonen/tsconfig/tsconfig.node.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo"
|
||||||
|
},
|
||||||
|
"include": ["*.config.ts", ".storybook/**/*.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "@robonen/tsconfig/tsconfig.vue.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"allowImportingTsExtensions": false,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.src.tsbuildinfo"
|
||||||
|
},
|
||||||
|
"include": ["stories/**/*.ts", "stories/**/*.vue"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user