add storybook setup and initial stories
This commit is contained in:
parent
94fdcf1c6b
commit
bbec620d00
28 changed files with 3477 additions and 24 deletions
|
|
@ -1,20 +1,29 @@
|
||||||
import type { StorybookConfig } from '@storybook/sveltekit';
|
import type { StorybookConfig } from '@storybook/sveltekit';
|
||||||
|
import remarkGfm from 'remark-gfm';
|
||||||
|
|
||||||
const config: StorybookConfig = {
|
const config: StorybookConfig = {
|
||||||
"stories": [
|
stories: ['../src/stories/**/*.mdx', '../src/stories/**/*.stories.@(js|ts|svelte)'],
|
||||||
"../src/**/*.mdx",
|
addons: [
|
||||||
"../src/**/*.stories.@(js|ts|svelte)"
|
'@storybook/addon-svelte-csf',
|
||||||
],
|
'@chromatic-com/storybook',
|
||||||
"addons": [
|
{
|
||||||
"@storybook/addon-svelte-csf",
|
name: '@storybook/addon-docs',
|
||||||
"@chromatic-com/storybook",
|
options: {
|
||||||
"@storybook/addon-docs",
|
mdxPluginOptions: {
|
||||||
"@storybook/addon-a11y",
|
mdxCompileOptions: {
|
||||||
"@storybook/addon-vitest"
|
remarkPlugins: [remarkGfm]
|
||||||
],
|
}
|
||||||
"framework": {
|
}
|
||||||
"name": "@storybook/sveltekit",
|
}
|
||||||
"options": {}
|
},
|
||||||
}
|
'@storybook/addon-a11y',
|
||||||
|
'@storybook/addon-vitest'
|
||||||
|
],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/sveltekit',
|
||||||
|
options: {}
|
||||||
|
},
|
||||||
|
staticDirs: ['../static']
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
10
.storybook/preview-head.html
Normal file
10
.storybook/preview-head.html
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<link rel="preload" href="/fonts/gk-variable.woff2" as="font" type="font/woff2" crossorigin />
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Goalking';
|
||||||
|
src: url('/fonts/gk-variable.woff2') format('woff2-variations');
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,14 +1,54 @@
|
||||||
import type { Preview } from '@storybook/sveltekit'
|
import type { Preview } from '@storybook/sveltekit';
|
||||||
|
import '$src/app.scss';
|
||||||
|
import './storybook-overrides.css';
|
||||||
|
|
||||||
const preview: Preview = {
|
const preview: Preview = {
|
||||||
parameters: {
|
parameters: {
|
||||||
controls: {
|
controls: {
|
||||||
matchers: {
|
matchers: {
|
||||||
color: /(background|color)$/i,
|
color: /(background|color)$/i,
|
||||||
date: /Date$/i,
|
date: /Date$/i
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
},
|
viewport: {
|
||||||
|
options: {
|
||||||
|
phone: { name: 'Phone', styles: { width: '375px', height: '667px' } },
|
||||||
|
tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } },
|
||||||
|
laptop: { name: 'Laptop', styles: { width: '1280px', height: '800px' } },
|
||||||
|
desktop: { name: 'Desktop', styles: { width: '1920px', height: '1080px' } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgrounds: {
|
||||||
|
options: {
|
||||||
|
light: { name: 'light', value: '#f5f5f5' },
|
||||||
|
dark: { name: 'dark', value: '#191919' },
|
||||||
|
"card-light": { name: 'card-light', value: '#ffffff' },
|
||||||
|
"card-dark": { name: 'card-dark', value: '#212121' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
docs: {
|
||||||
|
toc: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
globalTypes: {
|
||||||
|
theme: {
|
||||||
|
name: 'Theme',
|
||||||
|
description: 'Global theme for components',
|
||||||
|
defaultValue: 'light',
|
||||||
|
toolbar: {
|
||||||
|
icon: 'circlehollow',
|
||||||
|
items: ['light', 'dark'],
|
||||||
|
showName: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
initialGlobals: {
|
||||||
|
backgrounds: {
|
||||||
|
value: 'light'
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default preview;
|
export default preview;
|
||||||
16
.storybook/storybook-overrides.css
Normal file
16
.storybook/storybook-overrides.css
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
/* Storybook-specific overrides */
|
||||||
|
|
||||||
|
/* Allow scrolling in Storybook (app.scss sets overflow: hidden for custom layout scrolling) */
|
||||||
|
body {
|
||||||
|
overflow: auto !important;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure docs pages can scroll */
|
||||||
|
.sbdocs-wrapper {
|
||||||
|
overflow: auto !important;
|
||||||
|
}
|
||||||
223
src/stories/components/game/DatabaseCells.stories.svelte
Normal file
223
src/stories/components/game/DatabaseCells.stories.svelte
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import CharacterImageCell from '$lib/components/database/cells/CharacterImageCell.svelte';
|
||||||
|
import WeaponImageCell from '$lib/components/database/cells/WeaponImageCell.svelte';
|
||||||
|
import SummonImageCell from '$lib/components/database/cells/SummonImageCell.svelte';
|
||||||
|
import ElementCell from '$lib/components/database/cells/ElementCell.svelte';
|
||||||
|
import ProficiencyCell from '$lib/components/database/cells/ProficiencyCell.svelte';
|
||||||
|
import CharacterUncapCell from '$lib/components/database/cells/CharacterUncapCell.svelte';
|
||||||
|
import WeaponUncapCell from '$lib/components/database/cells/WeaponUncapCell.svelte';
|
||||||
|
import SummonUncapCell from '$lib/components/database/cells/SummonUncapCell.svelte';
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/Game/Database Cells',
|
||||||
|
tags: ['autodocs']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock row data for cells
|
||||||
|
const mockCharacterRow = {
|
||||||
|
granblueId: '3040000000',
|
||||||
|
name: { en: 'Narmaya', ja: 'ナルメア' },
|
||||||
|
element: 5, // Dark
|
||||||
|
proficiency: [1, 2], // Sabre, Dagger
|
||||||
|
special: false,
|
||||||
|
uncap: { flb: true, ulb: true, transcendence: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockSpecialCharacterRow = {
|
||||||
|
granblueId: '3040100000',
|
||||||
|
name: { en: 'Cagliostro', ja: 'カリオストロ' },
|
||||||
|
element: 4, // Earth
|
||||||
|
proficiency: [5], // Staff
|
||||||
|
special: true,
|
||||||
|
uncap: { flb: true, ulb: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockWeaponRow = {
|
||||||
|
granblueId: '1040000000',
|
||||||
|
name: { en: 'Sword of Bahamut', ja: 'バハムートソード' },
|
||||||
|
element: 5, // Dark
|
||||||
|
proficiency: 1, // Sabre
|
||||||
|
rarity: 3,
|
||||||
|
uncap: { flb: true, ulb: true, transcendence: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockSummonRow = {
|
||||||
|
granblueId: '2040001000',
|
||||||
|
name: { en: 'Bahamut', ja: 'バハムート' },
|
||||||
|
element: 5, // Dark
|
||||||
|
rarity: 3,
|
||||||
|
uncap: { flb: true, ulb: true, transcendence: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
// All elements for comparison
|
||||||
|
const elements = [
|
||||||
|
{ id: 1, name: 'Wind' },
|
||||||
|
{ id: 2, name: 'Fire' },
|
||||||
|
{ id: 3, name: 'Water' },
|
||||||
|
{ id: 4, name: 'Earth' },
|
||||||
|
{ id: 5, name: 'Dark' },
|
||||||
|
{ id: 6, name: 'Light' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const proficiencies = [
|
||||||
|
{ id: 1, name: 'Sabre' },
|
||||||
|
{ id: 2, name: 'Dagger' },
|
||||||
|
{ id: 3, name: 'Axe' },
|
||||||
|
{ id: 4, name: 'Spear' },
|
||||||
|
{ id: 5, name: 'Staff' },
|
||||||
|
{ id: 6, name: 'Gun' },
|
||||||
|
{ id: 7, name: 'Melee' },
|
||||||
|
{ id: 8, name: 'Bow' },
|
||||||
|
{ id: 9, name: 'Harp' },
|
||||||
|
{ id: 10, name: 'Katana' }
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Character Image Cell -->
|
||||||
|
<Story name="Character Image Cell">
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; height: 80px;"
|
||||||
|
>
|
||||||
|
<CharacterImageCell row={mockCharacterRow} />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Weapon Image Cell -->
|
||||||
|
<Story name="Weapon Image Cell">
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; height: 80px;"
|
||||||
|
>
|
||||||
|
<WeaponImageCell row={mockWeaponRow} />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Summon Image Cell -->
|
||||||
|
<Story name="Summon Image Cell">
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; height: 80px;"
|
||||||
|
>
|
||||||
|
<SummonImageCell row={mockSummonRow} />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Element Cells -->
|
||||||
|
<Story name="Element Cell - All Elements">
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; height: 60px; align-items: center;"
|
||||||
|
>
|
||||||
|
{#each elements as el}
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 4px;">
|
||||||
|
<ElementCell row={{ element: el.id }} />
|
||||||
|
<span style="font-size: 10px; color: #666;">{el.name}</span>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Proficiency Cell -->
|
||||||
|
<Story name="Proficiency Cell - All Types">
|
||||||
|
<div
|
||||||
|
style="display: flex; flex-wrap: wrap; gap: 12px; background: #f5f5f5; padding: 16px; border-radius: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
{#each proficiencies as prof}
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 4px;">
|
||||||
|
<ProficiencyCell row={{ proficiency: [prof.id] }} />
|
||||||
|
<span style="font-size: 10px; color: #666;">{prof.name}</span>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Character Uncap Cell -->
|
||||||
|
<Story name="Character Uncap Cell">
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
<span style="width: 100px; font-size: 12px; color: #666;">Regular FLB:</span>
|
||||||
|
<CharacterUncapCell row={{ uncap: { flb: true }, special: false }} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
<span style="width: 100px; font-size: 12px; color: #666;">Regular ULB:</span>
|
||||||
|
<CharacterUncapCell row={{ uncap: { flb: true, ulb: true }, special: false }} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
<span style="width: 100px; font-size: 12px; color: #666;">Transcendence:</span>
|
||||||
|
<CharacterUncapCell
|
||||||
|
row={{ uncap: { flb: true, ulb: true, transcendence: true }, special: false }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
<span style="width: 100px; font-size: 12px; color: #666;">Special ULB:</span>
|
||||||
|
<CharacterUncapCell row={{ uncap: { flb: true, ulb: true }, special: true }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Weapon Uncap Cell -->
|
||||||
|
<Story name="Weapon Uncap Cell">
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
<span style="width: 100px; font-size: 12px; color: #666;">MLB (3★):</span>
|
||||||
|
<WeaponUncapCell row={{ uncap: {}, rarity: 3 }} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
<span style="width: 100px; font-size: 12px; color: #666;">FLB (4★):</span>
|
||||||
|
<WeaponUncapCell row={{ uncap: { flb: true }, rarity: 3 }} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
<span style="width: 100px; font-size: 12px; color: #666;">ULB (5★):</span>
|
||||||
|
<WeaponUncapCell row={{ uncap: { flb: true, ulb: true }, rarity: 3 }} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
<span style="width: 100px; font-size: 12px; color: #666;">Transcendence:</span>
|
||||||
|
<WeaponUncapCell row={{ uncap: { flb: true, ulb: true, transcendence: true }, rarity: 3 }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Summon Uncap Cell -->
|
||||||
|
<Story name="Summon Uncap Cell">
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
<span style="width: 100px; font-size: 12px; color: #666;">MLB (3★):</span>
|
||||||
|
<SummonUncapCell row={{ uncap: {}, rarity: 3 }} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
<span style="width: 100px; font-size: 12px; color: #666;">FLB (4★):</span>
|
||||||
|
<SummonUncapCell row={{ uncap: { flb: true }, rarity: 3 }} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
<span style="width: 100px; font-size: 12px; color: #666;">ULB (5★):</span>
|
||||||
|
<SummonUncapCell row={{ uncap: { flb: true, ulb: true }, rarity: 3 }} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 16px; background: #f5f5f5; padding: 16px; border-radius: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
<span style="width: 100px; font-size: 12px; color: #666;">Transcendence:</span>
|
||||||
|
<SummonUncapCell row={{ uncap: { flb: true, ulb: true, transcendence: true }, rarity: 3 }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
87
src/stories/components/game/ElementLabel.stories.svelte
Normal file
87
src/stories/components/game/ElementLabel.stories.svelte
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf'
|
||||||
|
import ElementLabel from '$lib/components/labels/ElementLabel.svelte'
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/Game/ElementLabel',
|
||||||
|
component: ElementLabel,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
element: {
|
||||||
|
control: {
|
||||||
|
type: 'select',
|
||||||
|
labels: {
|
||||||
|
0: 'Null',
|
||||||
|
1: 'Wind',
|
||||||
|
2: 'Fire',
|
||||||
|
3: 'Water',
|
||||||
|
4: 'Earth',
|
||||||
|
5: 'Dark',
|
||||||
|
6: 'Light'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: [0, 1, 2, 3, 4, 5, 6],
|
||||||
|
description: 'Element type (0=Null, 1=Wind, 2=Fire, 3=Water, 4=Earth, 5=Dark, 6=Light)'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['small', 'medium', 'large', 'xlarge', 'natural'],
|
||||||
|
description: 'Icon size'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const elements = [
|
||||||
|
{ id: 1, name: 'Wind' },
|
||||||
|
{ id: 2, name: 'Fire' },
|
||||||
|
{ id: 3, name: 'Water' },
|
||||||
|
{ id: 4, name: 'Earth' },
|
||||||
|
{ id: 5, name: 'Dark' },
|
||||||
|
{ id: 6, name: 'Light' }
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default - Interactive single component for autodocs (args-only, no children) -->
|
||||||
|
<Story name="Default" args={{ element: 1, size: 'large' }} />
|
||||||
|
|
||||||
|
<!-- All Elements (custom layout - uses asChild to prevent double render) -->
|
||||||
|
<Story name="All Elements" asChild>
|
||||||
|
<div style="display: flex; gap: 16px; align-items: center;">
|
||||||
|
{#each elements as el}
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 4px;">
|
||||||
|
<ElementLabel element={el.id} />
|
||||||
|
<span style="font-size: 12px; color: #666;">{el.name}</span>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Size Comparison (custom layout) -->
|
||||||
|
<Story name="Size Comparison" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 80px; font-size: 12px; color: #666;">Small:</span>
|
||||||
|
{#each elements as el}
|
||||||
|
<ElementLabel element={el.id} size="small" />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 80px; font-size: 12px; color: #666;">Medium:</span>
|
||||||
|
{#each elements as el}
|
||||||
|
<ElementLabel element={el.id} size="medium" />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 80px; font-size: 12px; color: #666;">Large:</span>
|
||||||
|
{#each elements as el}
|
||||||
|
<ElementLabel element={el.id} size="large" />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 80px; font-size: 12px; color: #666;">X-Large:</span>
|
||||||
|
{#each elements as el}
|
||||||
|
<ElementLabel element={el.id} size="xlarge" />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
102
src/stories/components/game/JobItem.stories.svelte
Normal file
102
src/stories/components/game/JobItem.stories.svelte
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf'
|
||||||
|
import JobItem from '$lib/components/job/JobItem.svelte'
|
||||||
|
import { mockJob, mockJobNoUM, mockJobMultiProf } from '../../mocks/jobs'
|
||||||
|
import { fn } from 'storybook/test'
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/Game/JobItem',
|
||||||
|
component: JobItem,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
selected: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Whether the job is selected'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
onClick: fn()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let selectedJob = $state(null)
|
||||||
|
let interactiveSelected = $state(null)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default - args-only for autodocs -->
|
||||||
|
<Story name="Default" args={{ job: mockJob }} />
|
||||||
|
|
||||||
|
<!-- Selected -->
|
||||||
|
<Story name="Selected" args={{ job: mockJob, selected: true }} />
|
||||||
|
|
||||||
|
<!-- With Ultimate Mastery -->
|
||||||
|
<Story name="With Ultimate Mastery" asChild>
|
||||||
|
<div style="max-width: 400px; display: flex; flex-direction: column; gap: 8px;">
|
||||||
|
<JobItem job={mockJob} />
|
||||||
|
<span style="font-size: 12px; color: #666;">Has UM badge</span>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Without Ultimate Mastery -->
|
||||||
|
<Story name="Without Ultimate Mastery" asChild>
|
||||||
|
<div style="max-width: 400px; display: flex; flex-direction: column; gap: 8px;">
|
||||||
|
<JobItem job={mockJobNoUM} />
|
||||||
|
<span style="font-size: 12px; color: #666;">No UM badge</span>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Multi Proficiency -->
|
||||||
|
<Story name="Multi Proficiency" asChild>
|
||||||
|
<div style="max-width: 400px; display: flex; flex-direction: column; gap: 8px;">
|
||||||
|
<JobItem job={mockJobMultiProf} />
|
||||||
|
<span style="font-size: 12px; color: #666;">Shows multiple proficiency icons</span>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Job List -->
|
||||||
|
<Story name="Job List" asChild>
|
||||||
|
<div style="max-width: 400px; display: flex; flex-direction: column; gap: 8px;">
|
||||||
|
<JobItem
|
||||||
|
job={mockJob}
|
||||||
|
selected={selectedJob === mockJob.id}
|
||||||
|
onClick={() => (selectedJob = mockJob.id)}
|
||||||
|
/>
|
||||||
|
<JobItem
|
||||||
|
job={mockJobNoUM}
|
||||||
|
selected={selectedJob === mockJobNoUM.id}
|
||||||
|
onClick={() => (selectedJob = mockJobNoUM.id)}
|
||||||
|
/>
|
||||||
|
<JobItem
|
||||||
|
job={mockJobMultiProf}
|
||||||
|
selected={selectedJob === mockJobMultiProf.id}
|
||||||
|
onClick={() => (selectedJob = mockJobMultiProf.id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Interactive Selection -->
|
||||||
|
<Story name="Interactive Selection" asChild>
|
||||||
|
<div style="max-width: 400px; display: flex; flex-direction: column; gap: 8px;">
|
||||||
|
<p style="font-size: 12px; color: #666; margin-bottom: 8px;">Click to select a job</p>
|
||||||
|
<JobItem
|
||||||
|
job={mockJob}
|
||||||
|
selected={interactiveSelected === 'job-1'}
|
||||||
|
onClick={() => (interactiveSelected = 'job-1')}
|
||||||
|
/>
|
||||||
|
<JobItem
|
||||||
|
job={mockJobNoUM}
|
||||||
|
selected={interactiveSelected === 'job-2'}
|
||||||
|
onClick={() => (interactiveSelected = 'job-2')}
|
||||||
|
/>
|
||||||
|
<JobItem
|
||||||
|
job={mockJobMultiProf}
|
||||||
|
selected={interactiveSelected === 'job-3'}
|
||||||
|
onClick={() => (interactiveSelected = 'job-3')}
|
||||||
|
/>
|
||||||
|
<p style="font-size: 12px; margin-top: 8px;">
|
||||||
|
Selected: {interactiveSelected ? `Job ${interactiveSelected}` : 'None'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
168
src/stories/components/game/JobPortrait.stories.svelte
Normal file
168
src/stories/components/game/JobPortrait.stories.svelte
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf'
|
||||||
|
import JobPortrait from '$lib/components/job/JobPortrait.svelte'
|
||||||
|
import { Gender } from '$lib/utils/jobUtils'
|
||||||
|
import { mockJob, mockJobNoUM, mockJobMultiProf } from '../../mocks/jobs'
|
||||||
|
import { fn } from 'storybook/test'
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/Game/JobPortrait',
|
||||||
|
component: JobPortrait,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['small', 'medium', 'large'],
|
||||||
|
description: 'Portrait size'
|
||||||
|
},
|
||||||
|
gender: {
|
||||||
|
control: {
|
||||||
|
type: 'select',
|
||||||
|
labels: {
|
||||||
|
[Gender.Gran]: 'Gran',
|
||||||
|
[Gender.Djeeta]: 'Djeeta'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: [Gender.Gran, Gender.Djeeta],
|
||||||
|
description: 'Character gender'
|
||||||
|
},
|
||||||
|
element: {
|
||||||
|
control: {
|
||||||
|
type: 'select',
|
||||||
|
labels: {
|
||||||
|
1: 'Wind',
|
||||||
|
2: 'Fire',
|
||||||
|
3: 'Water',
|
||||||
|
4: 'Earth',
|
||||||
|
5: 'Light',
|
||||||
|
6: 'Dark'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: [undefined, 1, 2, 3, 4, 5, 6],
|
||||||
|
description: 'Element for border color'
|
||||||
|
},
|
||||||
|
showPlaceholder: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Show placeholder when no job'
|
||||||
|
},
|
||||||
|
clickable: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Enable click interaction'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
onclick: fn()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default - args-only for autodocs -->
|
||||||
|
<Story name="Default" args={{ job: mockJob, size: 'medium' }} />
|
||||||
|
|
||||||
|
<!-- All Sizes -->
|
||||||
|
<Story name="All Sizes" asChild>
|
||||||
|
<div style="display: flex; gap: 24px; align-items: flex-end;">
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJob} size="small" />
|
||||||
|
<span style="font-size: 12px; color: #666;">Small</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJob} size="medium" />
|
||||||
|
<span style="font-size: 12px; color: #666;">Medium</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJob} size="large" />
|
||||||
|
<span style="font-size: 12px; color: #666;">Large</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Gender Options -->
|
||||||
|
<Story name="Gender Options" asChild>
|
||||||
|
<div style="display: flex; gap: 24px;">
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJob} gender={Gender.Gran} />
|
||||||
|
<span style="font-size: 12px; color: #666;">Gran</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJob} gender={Gender.Djeeta} />
|
||||||
|
<span style="font-size: 12px; color: #666;">Djeeta</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Element Borders -->
|
||||||
|
<Story name="Element Borders" asChild>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 16px;">
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJob} element={1} />
|
||||||
|
<span style="font-size: 12px; color: #666;">Wind</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJob} element={2} />
|
||||||
|
<span style="font-size: 12px; color: #666;">Fire</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJob} element={3} />
|
||||||
|
<span style="font-size: 12px; color: #666;">Water</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJob} element={4} />
|
||||||
|
<span style="font-size: 12px; color: #666;">Earth</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJob} element={5} />
|
||||||
|
<span style="font-size: 12px; color: #666;">Light</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJob} element={6} />
|
||||||
|
<span style="font-size: 12px; color: #666;">Dark</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Empty / Placeholder -->
|
||||||
|
<Story name="Placeholder" asChild>
|
||||||
|
<div style="display: flex; gap: 24px;">
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait showPlaceholder={true} />
|
||||||
|
<span style="font-size: 12px; color: #666;">With Placeholder</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait showPlaceholder={false} />
|
||||||
|
<span style="font-size: 12px; color: #666;">No Placeholder</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Clickable -->
|
||||||
|
<Story name="Clickable" asChild>
|
||||||
|
<div style="display: flex; gap: 24px;">
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJob} clickable onclick={() => alert('Clicked!')} />
|
||||||
|
<span style="font-size: 12px; color: #666;">Clickable (hover to see effect)</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJob} />
|
||||||
|
<span style="font-size: 12px; color: #666;">Non-clickable</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Different Jobs -->
|
||||||
|
<Story name="Different Jobs" asChild>
|
||||||
|
<div style="display: flex; gap: 24px;">
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJob} />
|
||||||
|
<span style="font-size: 12px; color: #666;">{mockJob.name.en}</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJobNoUM} />
|
||||||
|
<span style="font-size: 12px; color: #666;">{mockJobNoUM.name.en}</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
|
||||||
|
<JobPortrait job={mockJobMultiProf} />
|
||||||
|
<span style="font-size: 12px; color: #666;">{mockJobMultiProf.name.en}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
120
src/stories/components/game/ProficiencyLabel.stories.svelte
Normal file
120
src/stories/components/game/ProficiencyLabel.stories.svelte
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf'
|
||||||
|
import ProficiencyLabel from '$lib/components/labels/ProficiencyLabel.svelte'
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/Game/ProficiencyLabel',
|
||||||
|
component: ProficiencyLabel,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
proficiency: {
|
||||||
|
control: {
|
||||||
|
type: 'select',
|
||||||
|
labels: {
|
||||||
|
1: 'Sabre',
|
||||||
|
2: 'Dagger',
|
||||||
|
3: 'Axe',
|
||||||
|
4: 'Spear',
|
||||||
|
5: 'Staff',
|
||||||
|
6: 'Gun',
|
||||||
|
7: 'Melee',
|
||||||
|
8: 'Bow',
|
||||||
|
9: 'Harp',
|
||||||
|
10: 'Katana'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||||
|
description:
|
||||||
|
'Proficiency type (1=Sabre, 2=Dagger, 3=Axe, 4=Spear, 5=Staff, 6=Gun, 7=Melee, 8=Bow, 9=Harp, 10=Katana)'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['small', 'medium', 'large', 'xlarge', 'natural'],
|
||||||
|
description: 'Icon size'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const proficiencies = [
|
||||||
|
{ id: 1, name: 'Sabre' },
|
||||||
|
{ id: 2, name: 'Dagger' },
|
||||||
|
{ id: 3, name: 'Axe' },
|
||||||
|
{ id: 4, name: 'Spear' },
|
||||||
|
{ id: 5, name: 'Staff' },
|
||||||
|
{ id: 6, name: 'Gun' },
|
||||||
|
{ id: 7, name: 'Melee' },
|
||||||
|
{ id: 8, name: 'Bow' },
|
||||||
|
{ id: 9, name: 'Harp' },
|
||||||
|
{ id: 10, name: 'Katana' }
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default - Interactive single component for autodocs (args-only, no children) -->
|
||||||
|
<Story name="Default" args={{ proficiency: 1, size: 'large' }} />
|
||||||
|
|
||||||
|
<!-- All Proficiencies (custom layout - uses asChild to prevent double render) -->
|
||||||
|
<Story name="All Proficiencies" asChild>
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px;">
|
||||||
|
{#each proficiencies as prof}
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; gap: 4px;">
|
||||||
|
<ProficiencyLabel proficiency={prof.id} />
|
||||||
|
<span style="font-size: 12px; color: #666;">{prof.name}</span>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Size Comparison (custom layout) -->
|
||||||
|
<Story name="Size Comparison" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 80px; font-size: 12px; color: #666;">Small:</span>
|
||||||
|
{#each proficiencies.slice(0, 5) as prof}
|
||||||
|
<ProficiencyLabel proficiency={prof.id} size="small" />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 80px; font-size: 12px; color: #666;">Medium:</span>
|
||||||
|
{#each proficiencies.slice(0, 5) as prof}
|
||||||
|
<ProficiencyLabel proficiency={prof.id} size="medium" />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 80px; font-size: 12px; color: #666;">Large:</span>
|
||||||
|
{#each proficiencies.slice(0, 5) as prof}
|
||||||
|
<ProficiencyLabel proficiency={prof.id} size="large" />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 80px; font-size: 12px; color: #666;">X-Large:</span>
|
||||||
|
{#each proficiencies.slice(0, 5) as prof}
|
||||||
|
<ProficiencyLabel proficiency={prof.id} size="xlarge" />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- In Context - Character Proficiencies -->
|
||||||
|
<Story name="In Context - Character Info" asChild>
|
||||||
|
<div
|
||||||
|
style="display: inline-flex; flex-direction: column; gap: 8px; padding: 16px; background: #f5f5f5; border-radius: 8px;"
|
||||||
|
>
|
||||||
|
<span style="font-weight: 600; margin-bottom: 4px;">Proficiencies</span>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<ProficiencyLabel proficiency={1} size="medium" />
|
||||||
|
<span style="font-size: 14px;">Sabre</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<ProficiencyLabel proficiency={10} size="medium" />
|
||||||
|
<span style="font-size: 14px;">Katana</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Dual Proficiency Display -->
|
||||||
|
<Story name="Dual Proficiency Display" asChild>
|
||||||
|
<div style="display: flex; align-items: center; gap: 4px;">
|
||||||
|
<ProficiencyLabel proficiency={1} size="medium" />
|
||||||
|
<ProficiencyLabel proficiency={10} size="medium" />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
188
src/stories/components/game/UncapIndicator.stories.svelte
Normal file
188
src/stories/components/game/UncapIndicator.stories.svelte
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import UncapIndicator from '$lib/components/uncap/UncapIndicator.svelte';
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/Game/UncapIndicator',
|
||||||
|
component: UncapIndicator,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
type: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['character', 'weapon', 'summon'],
|
||||||
|
description: 'Type of unit'
|
||||||
|
},
|
||||||
|
uncapLevel: {
|
||||||
|
control: { type: 'range', min: 0, max: 6, step: 1 },
|
||||||
|
description: 'Current uncap level (number of filled stars)'
|
||||||
|
},
|
||||||
|
transcendenceStage: {
|
||||||
|
control: { type: 'range', min: 0, max: 5, step: 1 },
|
||||||
|
description: 'Transcendence stage (0-5)',
|
||||||
|
if: { arg: 'transcendence', eq: true }
|
||||||
|
},
|
||||||
|
flb: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Has FLB (4th star available)'
|
||||||
|
},
|
||||||
|
ulb: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Has ULB (5th star available)',
|
||||||
|
if: { arg: 'flb', eq: true }
|
||||||
|
},
|
||||||
|
transcendence: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Has transcendence (6th star available)',
|
||||||
|
if: { arg: 'ulb', eq: true }
|
||||||
|
},
|
||||||
|
special: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Special character (Story SRs - 3 base stars)',
|
||||||
|
if: { arg: 'type', eq: 'character' }
|
||||||
|
},
|
||||||
|
editable: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Allow interactive editing'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let editableUncap = $state(2);
|
||||||
|
let editableTrans = $state(0);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default - args-only for autodocs -->
|
||||||
|
<Story name="Default" args={{ type: 'character', uncapLevel: 4 }} />
|
||||||
|
|
||||||
|
<!-- Character - MLB (4 stars) -->
|
||||||
|
<Story name="Character - MLB" args={{ type: 'character', uncapLevel: 4 }} />
|
||||||
|
|
||||||
|
<!-- Character - FLB (5 stars) -->
|
||||||
|
<Story name="Character - FLB" args={{ type: 'character', flb: true, uncapLevel: 5 }} />
|
||||||
|
|
||||||
|
<!-- Character - With Transcendence -->
|
||||||
|
<Story name="Character - Transcendence" args={{ type: 'character', flb: true, ulb: true, transcendence: true, uncapLevel: 5, transcendenceStage: 3 }} />
|
||||||
|
|
||||||
|
<!-- Character - Special (Story SRs) -->
|
||||||
|
<Story name="Character - Special (Story SR)" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 100px; font-size: 12px;">3★ MLB:</span>
|
||||||
|
<UncapIndicator type="character" special uncapLevel={3} />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 100px; font-size: 12px;">4★ FLB:</span>
|
||||||
|
<UncapIndicator type="character" special flb uncapLevel={4} />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 100px; font-size: 12px;">5★ ULB:</span>
|
||||||
|
<UncapIndicator type="character" special flb ulb uncapLevel={5} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Weapon - All Stages -->
|
||||||
|
<Story name="Weapon - All Stages" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 150px; font-size: 12px;">3★ MLB:</span>
|
||||||
|
<UncapIndicator type="weapon" uncapLevel={3} />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 150px; font-size: 12px;">4★ FLB:</span>
|
||||||
|
<UncapIndicator type="weapon" flb uncapLevel={4} />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 150px; font-size: 12px;">5★ ULB:</span>
|
||||||
|
<UncapIndicator type="weapon" flb ulb uncapLevel={5} />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 150px; font-size: 12px;">6★ Transcendence:</span>
|
||||||
|
<UncapIndicator type="weapon" flb ulb transcendence uncapLevel={5} transcendenceStage={5} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Summon - All Stages -->
|
||||||
|
<Story name="Summon - All Stages" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 150px; font-size: 12px;">3★ MLB:</span>
|
||||||
|
<UncapIndicator type="summon" uncapLevel={3} />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 150px; font-size: 12px;">4★ FLB:</span>
|
||||||
|
<UncapIndicator type="summon" flb uncapLevel={4} />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 150px; font-size: 12px;">5★ ULB:</span>
|
||||||
|
<UncapIndicator type="summon" flb ulb uncapLevel={5} />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 150px; font-size: 12px;">6★ Transcendence:</span>
|
||||||
|
<UncapIndicator type="summon" flb ulb transcendence uncapLevel={5} transcendenceStage={5} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Partially Uncapped -->
|
||||||
|
<Story name="Partially Uncapped" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 100px; font-size: 12px;">0/4:</span>
|
||||||
|
<UncapIndicator type="character" uncapLevel={0} />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 100px; font-size: 12px;">1/4:</span>
|
||||||
|
<UncapIndicator type="character" uncapLevel={1} />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 100px; font-size: 12px;">2/4:</span>
|
||||||
|
<UncapIndicator type="character" uncapLevel={2} />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 100px; font-size: 12px;">3/4:</span>
|
||||||
|
<UncapIndicator type="character" uncapLevel={3} />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 100px; font-size: 12px;">4/4:</span>
|
||||||
|
<UncapIndicator type="character" uncapLevel={4} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Transcendence Stages -->
|
||||||
|
<Story name="Transcendence Stages" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
{#each [0, 1, 2, 3, 4, 5] as stage}
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="width: 100px; font-size: 12px;">Stage {stage}:</span>
|
||||||
|
<UncapIndicator type="weapon" flb ulb transcendence uncapLevel={5} transcendenceStage={stage} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Editable -->
|
||||||
|
<Story name="Editable" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<p style="font-size: 12px; color: #666;">Click stars to change uncap level</p>
|
||||||
|
<UncapIndicator
|
||||||
|
type="weapon"
|
||||||
|
flb
|
||||||
|
ulb
|
||||||
|
transcendence
|
||||||
|
uncapLevel={editableUncap}
|
||||||
|
transcendenceStage={editableTrans}
|
||||||
|
editable
|
||||||
|
updateUncap={(level) => (editableUncap = level)}
|
||||||
|
updateTranscendence={(stage) => (editableTrans = stage)}
|
||||||
|
/>
|
||||||
|
<p style="font-size: 12px;">
|
||||||
|
Current: {editableUncap}★ / Transcendence: {editableTrans}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
186
src/stories/components/ui/Button.stories.svelte
Normal file
186
src/stories/components/ui/Button.stories.svelte
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf'
|
||||||
|
import Button from '$lib/components/ui/Button.svelte'
|
||||||
|
import { fn } from 'storybook/test'
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/UI/Button',
|
||||||
|
component: Button,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
variant: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['primary', 'secondary', 'ghost', 'text', 'destructive', 'notice', 'subtle'],
|
||||||
|
description: 'Visual style variant'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['small', 'medium', 'large', 'icon'],
|
||||||
|
description: 'Button size'
|
||||||
|
},
|
||||||
|
shape: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['default', 'circular', 'pill'],
|
||||||
|
description: 'Border radius shape'
|
||||||
|
},
|
||||||
|
element: {
|
||||||
|
control: 'select',
|
||||||
|
options: [undefined, 'wind', 'fire', 'water', 'earth', 'dark', 'light'],
|
||||||
|
description: 'Element color theme'
|
||||||
|
},
|
||||||
|
elementStyle: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Apply element-specific button styling'
|
||||||
|
},
|
||||||
|
contained: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Contained background style'
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Disabled state'
|
||||||
|
},
|
||||||
|
fullWidth: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Full width button'
|
||||||
|
},
|
||||||
|
iconOnly: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Icon only mode (no text)'
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Active/pressed state'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
onclick: fn()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const variants = ['primary', 'secondary', 'ghost', 'text', 'destructive', 'notice', 'subtle']
|
||||||
|
const sizes = ['small', 'medium', 'large']
|
||||||
|
const elements = ['wind', 'fire', 'water', 'earth', 'dark', 'light']
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default - args-only for autodocs -->
|
||||||
|
<Story name="Default" args={{ variant: 'secondary' }}>
|
||||||
|
{#snippet children()}Button{/snippet}
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- All Variants -->
|
||||||
|
<Story name="All Variants" asChild>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 12px; align-items: center;">
|
||||||
|
{#each variants as variant}
|
||||||
|
<Button {variant}>{variant}</Button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- All Sizes -->
|
||||||
|
<Story name="All Sizes" asChild>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 12px; align-items: center;">
|
||||||
|
{#each sizes as size}
|
||||||
|
<Button {size}>{size}</Button>
|
||||||
|
{/each}
|
||||||
|
<Button size="icon" icon="settings" iconOnly />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Disabled States -->
|
||||||
|
<Story name="Disabled States" asChild>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 12px; align-items: center;">
|
||||||
|
{#each variants as variant}
|
||||||
|
<Button {variant} disabled>{variant}</Button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Shapes -->
|
||||||
|
<Story name="Shapes" asChild>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 12px; align-items: center;">
|
||||||
|
<Button shape="default">Default</Button>
|
||||||
|
<Button shape="pill">Pill</Button>
|
||||||
|
<Button shape="circular" icon="plus" iconOnly />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- With Icons -->
|
||||||
|
<Story name="With Icons" asChild>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 12px; align-items: center;">
|
||||||
|
<Button icon="plus" iconPosition="left">Add Item</Button>
|
||||||
|
<Button icon="arrow-right" iconPosition="right">Continue</Button>
|
||||||
|
<Button icon="settings" iconOnly size="icon" />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Element Colors -->
|
||||||
|
<Story name="Element Colors" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<div>
|
||||||
|
<h4 style="margin: 0 0 8px; font-size: 14px; color: #666;">Without elementStyle</h4>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 12px;">
|
||||||
|
{#each elements as element}
|
||||||
|
<Button {element}>{element}</Button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 style="margin: 0 0 8px; font-size: 14px; color: #666;">With elementStyle</h4>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 12px;">
|
||||||
|
{#each elements as element}
|
||||||
|
<Button {element} elementStyle>{element}</Button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Contained -->
|
||||||
|
<Story name="Contained" asChild>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 12px; align-items: center;">
|
||||||
|
<Button contained>Contained</Button>
|
||||||
|
<Button contained variant="primary">Primary Contained</Button>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Full Width -->
|
||||||
|
<Story name="Full Width" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 12px; max-width: 300px;">
|
||||||
|
<Button fullWidth>Full Width Button</Button>
|
||||||
|
<Button fullWidth variant="primary">Primary Full Width</Button>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Size Variant Matrix -->
|
||||||
|
<Story name="Size Variant Matrix" asChild>
|
||||||
|
<div style="display: grid; gap: 8px;">
|
||||||
|
<div
|
||||||
|
style="display: grid; grid-template-columns: 80px repeat({variants.length}, 1fr); gap: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
<span></span>
|
||||||
|
{#each variants as variant}
|
||||||
|
<span style="font-size: 12px; text-align: center; color: #666;">{variant}</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#each sizes as size}
|
||||||
|
<div
|
||||||
|
style="display: grid; grid-template-columns: 80px repeat({variants.length}, 1fr); gap: 8px; align-items: center;"
|
||||||
|
>
|
||||||
|
<span style="font-size: 12px; color: #666;">{size}</span>
|
||||||
|
{#each variants as variant}
|
||||||
|
<Button {variant} {size}>{size}</Button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Active States -->
|
||||||
|
<Story name="Active States" asChild>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 12px; align-items: center;">
|
||||||
|
<Button active>Active</Button>
|
||||||
|
<Button active variant="primary">Active Primary</Button>
|
||||||
|
<Button active variant="ghost">Active Ghost</Button>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
178
src/stories/components/ui/Checkbox.stories.svelte
Normal file
178
src/stories/components/ui/Checkbox.stories.svelte
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf'
|
||||||
|
import Checkbox from '$lib/components/ui/checkbox/Checkbox.svelte'
|
||||||
|
import { fn } from 'storybook/test'
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/UI/Checkbox',
|
||||||
|
component: Checkbox,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
checked: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Whether the checkbox is checked'
|
||||||
|
},
|
||||||
|
indeterminate: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Indeterminate state (partial selection)'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['small', 'medium', 'large'],
|
||||||
|
description: 'Checkbox size'
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['default', 'bound'],
|
||||||
|
description: 'Visual variant'
|
||||||
|
},
|
||||||
|
contained: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Contained background style (alias for variant=bound)'
|
||||||
|
},
|
||||||
|
element: {
|
||||||
|
control: 'select',
|
||||||
|
options: [undefined, 'wind', 'fire', 'water', 'earth', 'dark', 'light'],
|
||||||
|
description: 'Element color theme for checked state'
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Disabled state'
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Required field'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
onCheckedChange: fn()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default - args-only for autodocs -->
|
||||||
|
<Story name="Default" args={{ checked: false }} />
|
||||||
|
|
||||||
|
<!-- Checked -->
|
||||||
|
<Story name="Checked" args={{ checked: true }} />
|
||||||
|
|
||||||
|
<!-- Indeterminate -->
|
||||||
|
<Story name="Indeterminate" args={{ indeterminate: true }} />
|
||||||
|
|
||||||
|
<!-- All Sizes -->
|
||||||
|
<Story name="All Sizes" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 60px; font-size: 12px; color: #666;">Small</span>
|
||||||
|
<Checkbox size="small" />
|
||||||
|
<Checkbox size="small" checked />
|
||||||
|
<Checkbox size="small" indeterminate />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 60px; font-size: 12px; color: #666;">Medium</span>
|
||||||
|
<Checkbox size="medium" />
|
||||||
|
<Checkbox size="medium" checked />
|
||||||
|
<Checkbox size="medium" indeterminate />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 60px; font-size: 12px; color: #666;">Large</span>
|
||||||
|
<Checkbox size="large" />
|
||||||
|
<Checkbox size="large" checked />
|
||||||
|
<Checkbox size="large" indeterminate />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Variants -->
|
||||||
|
<Story name="Variants" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 80px; font-size: 12px; color: #666;">Default</span>
|
||||||
|
<Checkbox variant="default" />
|
||||||
|
<Checkbox variant="default" checked />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 80px; font-size: 12px; color: #666;">Bound</span>
|
||||||
|
<Checkbox variant="bound" />
|
||||||
|
<Checkbox variant="bound" checked />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 80px; font-size: 12px; color: #666;">Contained</span>
|
||||||
|
<Checkbox contained />
|
||||||
|
<Checkbox contained checked />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Element Colors -->
|
||||||
|
<Story name="Element Colors" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
{#each ['wind', 'fire', 'water', 'earth', 'dark', 'light'] as element}
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 50px; font-size: 12px; color: #666;">{element}</span>
|
||||||
|
<Checkbox {element} />
|
||||||
|
<Checkbox {element} checked />
|
||||||
|
<Checkbox {element} indeterminate />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Disabled States -->
|
||||||
|
<Story name="Disabled" asChild>
|
||||||
|
<div style="display: flex; gap: 12px; align-items: center;">
|
||||||
|
<Checkbox disabled />
|
||||||
|
<Checkbox disabled checked />
|
||||||
|
<Checkbox disabled indeterminate />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- With Labels -->
|
||||||
|
<Story name="With Labels" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||||
|
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||||||
|
<Checkbox />
|
||||||
|
<span>Accept terms and conditions</span>
|
||||||
|
</label>
|
||||||
|
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||||||
|
<Checkbox checked />
|
||||||
|
<span>Subscribe to newsletter</span>
|
||||||
|
</label>
|
||||||
|
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||||||
|
<Checkbox size="small" />
|
||||||
|
<span style="font-size: 14px;">Small checkbox with label</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Checklist Example -->
|
||||||
|
<Story name="Checklist Example" asChild>
|
||||||
|
<div
|
||||||
|
style="display: flex; flex-direction: column; gap: 0; max-width: 250px; background: #f5f5f5; border-radius: 8px; padding: 8px 0;"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
style="display: flex; align-items: center; gap: 12px; padding: 8px 16px; cursor: pointer;"
|
||||||
|
>
|
||||||
|
<Checkbox checked />
|
||||||
|
<span>Complete profile</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
style="display: flex; align-items: center; gap: 12px; padding: 8px 16px; cursor: pointer;"
|
||||||
|
>
|
||||||
|
<Checkbox checked />
|
||||||
|
<span>Verify email</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
style="display: flex; align-items: center; gap: 12px; padding: 8px 16px; cursor: pointer;"
|
||||||
|
>
|
||||||
|
<Checkbox />
|
||||||
|
<span>Add payment method</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
style="display: flex; align-items: center; gap: 12px; padding: 8px 16px; cursor: pointer;"
|
||||||
|
>
|
||||||
|
<Checkbox />
|
||||||
|
<span>Enable 2FA</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
161
src/stories/components/ui/Dialog.stories.svelte
Normal file
161
src/stories/components/ui/Dialog.stories.svelte
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf'
|
||||||
|
import Dialog from '$lib/components/ui/Dialog.svelte'
|
||||||
|
import Button from '$lib/components/ui/Button.svelte'
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/UI/Dialog',
|
||||||
|
tags: ['autodocs']
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let defaultOpen = $state(false)
|
||||||
|
let withDescOpen = $state(false)
|
||||||
|
let withFooterOpen = $state(false)
|
||||||
|
let longContentOpen = $state(false)
|
||||||
|
let formOpen = $state(false)
|
||||||
|
let confirmOpen = $state(false)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default -->
|
||||||
|
<Story name="Default">
|
||||||
|
<div>
|
||||||
|
<Button onclick={() => (defaultOpen = true)}>Open Dialog</Button>
|
||||||
|
<Dialog bind:open={defaultOpen} title="Dialog Title">
|
||||||
|
{#snippet children()}
|
||||||
|
<p>This is the dialog content. You can put any content here.</p>
|
||||||
|
{/snippet}
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- With Description -->
|
||||||
|
<Story name="With Description">
|
||||||
|
<div>
|
||||||
|
<Button onclick={() => (withDescOpen = true)}>Open Dialog</Button>
|
||||||
|
<Dialog
|
||||||
|
bind:open={withDescOpen}
|
||||||
|
title="Account Settings"
|
||||||
|
description="Make changes to your account settings here."
|
||||||
|
>
|
||||||
|
{#snippet children()}
|
||||||
|
<p>Your account settings form would go here.</p>
|
||||||
|
{/snippet}
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- With Footer -->
|
||||||
|
<Story name="With Footer">
|
||||||
|
<div>
|
||||||
|
<Button onclick={() => (withFooterOpen = true)}>Open Dialog</Button>
|
||||||
|
<Dialog bind:open={withFooterOpen} title="Confirm Action">
|
||||||
|
{#snippet children()}
|
||||||
|
<p>Are you sure you want to proceed with this action?</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet footer()}
|
||||||
|
<Button variant="secondary" onclick={() => (withFooterOpen = false)}>Cancel</Button>
|
||||||
|
<Button variant="primary" onclick={() => (withFooterOpen = false)}>Confirm</Button>
|
||||||
|
{/snippet}
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Long Content -->
|
||||||
|
<Story name="Long Content">
|
||||||
|
<div>
|
||||||
|
<Button onclick={() => (longContentOpen = true)}>Open Long Dialog</Button>
|
||||||
|
<Dialog bind:open={longContentOpen} title="Terms and Conditions">
|
||||||
|
{#snippet children()}
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<p>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt
|
||||||
|
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
||||||
|
ullamco laboris.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
|
||||||
|
nulla pariatur. Excepteur sint occaecat cupidatat non proident.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
|
||||||
|
laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium
|
||||||
|
voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id
|
||||||
|
quod maxime placeat facere possimus.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet footer()}
|
||||||
|
<Button variant="secondary" onclick={() => (longContentOpen = false)}>Decline</Button>
|
||||||
|
<Button variant="primary" onclick={() => (longContentOpen = false)}>Accept</Button>
|
||||||
|
{/snippet}
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Form Dialog -->
|
||||||
|
<Story name="Form Dialog">
|
||||||
|
<div>
|
||||||
|
<Button onclick={() => (formOpen = true)}>Edit Profile</Button>
|
||||||
|
<Dialog bind:open={formOpen} title="Edit Profile" description="Update your profile information.">
|
||||||
|
{#snippet children()}
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
style="display: block; font-size: 14px; font-weight: 500; margin-bottom: 4px;"
|
||||||
|
for="name">Name</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter your name"
|
||||||
|
style="width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
style="display: block; font-size: 14px; font-weight: 500; margin-bottom: 4px;"
|
||||||
|
for="email">Email</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
style="width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet footer()}
|
||||||
|
<Button variant="secondary" onclick={() => (formOpen = false)}>Cancel</Button>
|
||||||
|
<Button variant="primary" onclick={() => (formOpen = false)}>Save Changes</Button>
|
||||||
|
{/snippet}
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Confirmation Dialog -->
|
||||||
|
<Story name="Confirmation Dialog">
|
||||||
|
<div>
|
||||||
|
<Button variant="destructive" onclick={() => (confirmOpen = true)}>Delete Item</Button>
|
||||||
|
<Dialog bind:open={confirmOpen} title="Delete Item">
|
||||||
|
{#snippet children()}
|
||||||
|
<p>
|
||||||
|
Are you sure you want to delete this item? This action cannot be undone and all associated
|
||||||
|
data will be permanently removed.
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet footer()}
|
||||||
|
<Button variant="secondary" onclick={() => (confirmOpen = false)}>Cancel</Button>
|
||||||
|
<Button variant="destructive" onclick={() => (confirmOpen = false)}>Delete</Button>
|
||||||
|
{/snippet}
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
133
src/stories/components/ui/DropdownMenu.stories.svelte
Normal file
133
src/stories/components/ui/DropdownMenu.stories.svelte
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf'
|
||||||
|
import DropdownMenu from '$lib/components/ui/DropdownMenu.svelte'
|
||||||
|
import { DropdownMenu as DropdownMenuBase } from 'bits-ui'
|
||||||
|
import Button from '$lib/components/ui/Button.svelte'
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/UI/DropdownMenu',
|
||||||
|
tags: ['autodocs']
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default -->
|
||||||
|
<Story name="Default">
|
||||||
|
<DropdownMenu>
|
||||||
|
{#snippet trigger({ props })}
|
||||||
|
<Button {...props}>Open Menu</Button>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet menu()}
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Edit</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Duplicate</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Archive</DropdownMenuBase.Item>
|
||||||
|
{/snippet}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- With Separator -->
|
||||||
|
<Story name="With Separator">
|
||||||
|
<DropdownMenu>
|
||||||
|
{#snippet trigger({ props })}
|
||||||
|
<Button variant="secondary" {...props}>Actions</Button>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet menu()}
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">New File</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">New Folder</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Separator class="dropdown-menu-separator" />
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Import</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Export</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Separator class="dropdown-menu-separator" />
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item danger">Delete</DropdownMenuBase.Item>
|
||||||
|
{/snippet}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- With Icons -->
|
||||||
|
<Story name="With Icons">
|
||||||
|
<DropdownMenu>
|
||||||
|
{#snippet trigger({ props })}
|
||||||
|
<Button {...props}>File</Button>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet menu()}
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">New Document</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">New Folder</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Separator class="dropdown-menu-separator" />
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Save</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Save As...</DropdownMenuBase.Item>
|
||||||
|
{/snippet}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Danger Actions -->
|
||||||
|
<Story name="Danger Actions">
|
||||||
|
<DropdownMenu>
|
||||||
|
{#snippet trigger({ props })}
|
||||||
|
<Button variant="secondary" {...props}>Manage</Button>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet menu()}
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Edit</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Move</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Separator class="dropdown-menu-separator" />
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item danger">Delete</DropdownMenuBase.Item>
|
||||||
|
{/snippet}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- User Menu Example -->
|
||||||
|
<Story name="User Menu Example">
|
||||||
|
<DropdownMenu>
|
||||||
|
{#snippet trigger({ props })}
|
||||||
|
<Button variant="subtle" {...props}>
|
||||||
|
{#snippet leftAccessory()}
|
||||||
|
<span
|
||||||
|
style="width: 24px; height: 24px; background: #6366f1; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-weight: 600; font-size: 12px;"
|
||||||
|
>J</span
|
||||||
|
>
|
||||||
|
{/snippet}
|
||||||
|
John Doe
|
||||||
|
</Button>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet menu()}
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Profile</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Settings</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Help</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Separator class="dropdown-menu-separator" />
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item danger">Sign out</DropdownMenuBase.Item>
|
||||||
|
{/snippet}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Icon Button Trigger -->
|
||||||
|
<Story name="Icon Button Trigger">
|
||||||
|
<DropdownMenu>
|
||||||
|
{#snippet trigger({ props })}
|
||||||
|
<Button size="small" iconOnly icon="ellipsis" {...props} />
|
||||||
|
{/snippet}
|
||||||
|
{#snippet menu()}
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">View</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Edit</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Share</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Separator class="dropdown-menu-separator" />
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item danger">Delete</DropdownMenuBase.Item>
|
||||||
|
{/snippet}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Long Menu -->
|
||||||
|
<Story name="Long Menu">
|
||||||
|
<DropdownMenu>
|
||||||
|
{#snippet trigger({ props })}
|
||||||
|
<Button {...props}>Select Category</Button>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet menu()}
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Characters</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Weapons</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Summons</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Classes</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Skills</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Raids</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Materials</DropdownMenuBase.Item>
|
||||||
|
<DropdownMenuBase.Item class="dropdown-menu-item">Items</DropdownMenuBase.Item>
|
||||||
|
{/snippet}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Story>
|
||||||
171
src/stories/components/ui/Input.stories.svelte
Normal file
171
src/stories/components/ui/Input.stories.svelte
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf'
|
||||||
|
import Input from '$lib/components/ui/Input.svelte'
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/UI/Input',
|
||||||
|
component: Input,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
variant: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['default', 'contained', 'duration', 'number', 'range'],
|
||||||
|
description: 'Input variant style'
|
||||||
|
},
|
||||||
|
contained: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Contained background style'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Field label'
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Error message'
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Placeholder text'
|
||||||
|
},
|
||||||
|
leftIcon: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Left icon name'
|
||||||
|
},
|
||||||
|
rightIcon: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Right icon name'
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Disabled state'
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Required field'
|
||||||
|
},
|
||||||
|
readonly: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Read-only field'
|
||||||
|
},
|
||||||
|
fullWidth: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Full width input'
|
||||||
|
},
|
||||||
|
maxLength: {
|
||||||
|
control: 'number',
|
||||||
|
description: 'Maximum character length'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default - args-only for autodocs -->
|
||||||
|
<Story name="Default" args={{ placeholder: 'Enter text...' }} />
|
||||||
|
|
||||||
|
<!-- With Label -->
|
||||||
|
<Story name="With Label" args={{ label: 'Username', placeholder: 'Enter username' }} />
|
||||||
|
|
||||||
|
<!-- Required Field -->
|
||||||
|
<Story name="Required Field" args={{ label: 'Email', placeholder: 'Enter email', required: true }} />
|
||||||
|
|
||||||
|
<!-- With Error -->
|
||||||
|
<Story
|
||||||
|
name="With Error"
|
||||||
|
args={{
|
||||||
|
label: 'Password',
|
||||||
|
type: 'password',
|
||||||
|
error: 'Password must be at least 8 characters',
|
||||||
|
value: 'abc'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Contained Variant -->
|
||||||
|
<Story name="Contained" args={{ variant: 'contained', placeholder: 'Contained input' }} />
|
||||||
|
|
||||||
|
<!-- With Icons -->
|
||||||
|
<Story name="With Icons" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 12px; max-width: 300px;">
|
||||||
|
<Input leftIcon="search" placeholder="Search..." />
|
||||||
|
<Input rightIcon="info" placeholder="With right icon" />
|
||||||
|
<Input leftIcon="user" rightIcon="check" placeholder="Both icons" />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- With Character Counter -->
|
||||||
|
<Story name="Character Counter" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 12px; max-width: 300px;">
|
||||||
|
<Input placeholder="Type something..." counter maxLength={100} label="With max length" />
|
||||||
|
<Input placeholder="No max limit" counter label="Counter only" />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Number Input -->
|
||||||
|
<Story name="Number Input" asChild>
|
||||||
|
<div style="display: flex; gap: 12px; align-items: center;">
|
||||||
|
<span style="color: #666;">Quantity:</span>
|
||||||
|
<Input variant="number" type="number" value="0" />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Range Input -->
|
||||||
|
<Story name="Range Input" asChild>
|
||||||
|
<div style="display: flex; gap: 12px; align-items: center;">
|
||||||
|
<span style="color: #666;">Level:</span>
|
||||||
|
<Input variant="range" type="number" value="1" />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Disabled State -->
|
||||||
|
<Story name="Disabled" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 12px; max-width: 300px;">
|
||||||
|
<Input placeholder="Disabled input" disabled />
|
||||||
|
<Input label="Disabled with label" placeholder="Cannot edit" disabled />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Read-only State -->
|
||||||
|
<Story
|
||||||
|
name="Read-only"
|
||||||
|
args={{ label: 'Read-only field', value: 'This cannot be changed', readonly: true }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Full Width -->
|
||||||
|
<Story
|
||||||
|
name="Full Width"
|
||||||
|
args={{ label: 'Full width input', placeholder: 'Takes full container width', fullWidth: true }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Password Input -->
|
||||||
|
<Story
|
||||||
|
name="Password Input"
|
||||||
|
args={{ type: 'password', label: 'Password', placeholder: 'Enter password' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- All Variants -->
|
||||||
|
<Story name="All Variants" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px; max-width: 300px;">
|
||||||
|
<Input variant="default" placeholder="Default variant" label="Default" />
|
||||||
|
<Input variant="contained" placeholder="Contained variant" label="Contained" />
|
||||||
|
<div style="display: flex; gap: 12px; align-items: end;">
|
||||||
|
<div>
|
||||||
|
<span style="font-size: 12px; color: #666; display: block; margin-bottom: 4px;">Number</span>
|
||||||
|
<Input variant="number" type="number" value="42" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span style="font-size: 12px; color: #666; display: block; margin-bottom: 4px;">Range</span>
|
||||||
|
<Input variant="range" type="number" value="100" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Form Example -->
|
||||||
|
<Story name="Form Example" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px; max-width: 350px;">
|
||||||
|
<Input label="Full Name" placeholder="John Doe" required />
|
||||||
|
<Input label="Email" type="email" placeholder="john@example.com" required leftIcon="mail" />
|
||||||
|
<Input label="Password" type="password" placeholder="Min 8 characters" required />
|
||||||
|
<Input label="Bio" placeholder="Tell us about yourself..." counter maxLength={200} />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
138
src/stories/components/ui/SegmentedControl.stories.svelte
Normal file
138
src/stories/components/ui/SegmentedControl.stories.svelte
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf'
|
||||||
|
import SegmentedControl from '$lib/components/ui/segmented-control/SegmentedControl.svelte'
|
||||||
|
import Segment from '$lib/components/ui/segmented-control/Segment.svelte'
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/UI/SegmentedControl',
|
||||||
|
tags: ['autodocs']
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let viewValue = $state('grid')
|
||||||
|
let twoOptValue = $state('on')
|
||||||
|
let blendedValue = $state('all')
|
||||||
|
let bgValue = $state('day')
|
||||||
|
let elementValue = $state('wind')
|
||||||
|
let gapValue = $state('first')
|
||||||
|
let growValue = $state('characters')
|
||||||
|
let tabValue = $state('characters')
|
||||||
|
let disabledValue = $state('enabled')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default -->
|
||||||
|
<Story name="Default">
|
||||||
|
<SegmentedControl bind:value={viewValue}>
|
||||||
|
<Segment value="grid">Grid</Segment>
|
||||||
|
<Segment value="list">List</Segment>
|
||||||
|
<Segment value="compact">Compact</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Two Options -->
|
||||||
|
<Story name="Two Options">
|
||||||
|
<SegmentedControl bind:value={twoOptValue}>
|
||||||
|
<Segment value="on">On</Segment>
|
||||||
|
<Segment value="off">Off</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Blended Variant -->
|
||||||
|
<Story name="Blended Variant">
|
||||||
|
<SegmentedControl variant="blended" bind:value={blendedValue}>
|
||||||
|
<Segment value="all">All</Segment>
|
||||||
|
<Segment value="active">Active</Segment>
|
||||||
|
<Segment value="archived">Archived</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Background Variant -->
|
||||||
|
<Story name="Background Variant">
|
||||||
|
<SegmentedControl variant="background" bind:value={bgValue}>
|
||||||
|
<Segment value="day">Day</Segment>
|
||||||
|
<Segment value="week">Week</Segment>
|
||||||
|
<Segment value="month">Month</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Element Colors -->
|
||||||
|
<Story name="Element Colors">
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<SegmentedControl bind:value={elementValue} element="wind">
|
||||||
|
<Segment value="wind">Wind</Segment>
|
||||||
|
<Segment value="other">Other</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
<SegmentedControl element="fire" value="fire">
|
||||||
|
<Segment value="fire">Fire</Segment>
|
||||||
|
<Segment value="other">Other</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
<SegmentedControl element="water" value="water">
|
||||||
|
<Segment value="water">Water</Segment>
|
||||||
|
<Segment value="other">Other</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
<SegmentedControl element="earth" value="earth">
|
||||||
|
<Segment value="earth">Earth</Segment>
|
||||||
|
<Segment value="other">Other</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
<SegmentedControl element="dark" value="dark">
|
||||||
|
<Segment value="dark">Dark</Segment>
|
||||||
|
<Segment value="other">Other</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
<SegmentedControl element="light" value="light">
|
||||||
|
<Segment value="light">Light</Segment>
|
||||||
|
<Segment value="other">Other</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- With Gap -->
|
||||||
|
<Story name="With Gap">
|
||||||
|
<SegmentedControl gap bind:value={gapValue}>
|
||||||
|
<Segment value="first">First</Segment>
|
||||||
|
<Segment value="second">Second</Segment>
|
||||||
|
<Segment value="third">Third</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Grow to Fill -->
|
||||||
|
<Story name="Grow to Fill">
|
||||||
|
<div style="width: 400px; border: 1px dashed #ccc; padding: 16px;">
|
||||||
|
<SegmentedControl grow bind:value={growValue}>
|
||||||
|
<Segment value="characters">Characters</Segment>
|
||||||
|
<Segment value="weapons">Weapons</Segment>
|
||||||
|
<Segment value="summons">Summons</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Tab Example -->
|
||||||
|
<Story name="Tab Example">
|
||||||
|
<div style="max-width: 500px;">
|
||||||
|
<SegmentedControl bind:value={tabValue} grow>
|
||||||
|
<Segment value="characters">Characters</Segment>
|
||||||
|
<Segment value="weapons">Weapons</Segment>
|
||||||
|
<Segment value="summons">Summons</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
<div
|
||||||
|
style="margin-top: 16px; padding: 16px; background: #f5f5f5; border-radius: 8px; min-height: 100px;"
|
||||||
|
>
|
||||||
|
{#if tabValue === 'characters'}
|
||||||
|
<p>Character content goes here</p>
|
||||||
|
{:else if tabValue === 'weapons'}
|
||||||
|
<p>Weapon content goes here</p>
|
||||||
|
{:else}
|
||||||
|
<p>Summon content goes here</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Disabled Segment -->
|
||||||
|
<Story name="Disabled Segment">
|
||||||
|
<SegmentedControl bind:value={disabledValue}>
|
||||||
|
<Segment value="enabled">Enabled</Segment>
|
||||||
|
<Segment value="also-enabled">Also Enabled</Segment>
|
||||||
|
<Segment value="disabled" disabled>Disabled</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
</Story>
|
||||||
154
src/stories/components/ui/Select.stories.svelte
Normal file
154
src/stories/components/ui/Select.stories.svelte
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf'
|
||||||
|
import Select from '$lib/components/ui/Select.svelte'
|
||||||
|
import { fn } from 'storybook/test'
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/UI/Select',
|
||||||
|
component: Select,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['small', 'medium', 'large'],
|
||||||
|
description: 'Select size'
|
||||||
|
},
|
||||||
|
contained: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Contained background style'
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Disabled state'
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Required field'
|
||||||
|
},
|
||||||
|
fullWidth: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Full width select'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Field label'
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Error message'
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Placeholder text'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
onValueChange: fn()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const basicOptions = [
|
||||||
|
{ value: 'wind', label: 'Wind' },
|
||||||
|
{ value: 'fire', label: 'Fire' },
|
||||||
|
{ value: 'water', label: 'Water' },
|
||||||
|
{ value: 'earth', label: 'Earth' },
|
||||||
|
{ value: 'light', label: 'Light' },
|
||||||
|
{ value: 'dark', label: 'Dark' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const numberOptions = [
|
||||||
|
{ value: 1, label: 'Level 1' },
|
||||||
|
{ value: 2, label: 'Level 2' },
|
||||||
|
{ value: 3, label: 'Level 3' },
|
||||||
|
{ value: 4, label: 'Level 4' },
|
||||||
|
{ value: 5, label: 'Level 5' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const disabledOptions = [
|
||||||
|
{ value: 'option1', label: 'Available Option' },
|
||||||
|
{ value: 'option2', label: 'Also Available' },
|
||||||
|
{ value: 'option3', label: 'Unavailable', disabled: true },
|
||||||
|
{ value: 'option4', label: 'Another Available' }
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default - args-only for autodocs -->
|
||||||
|
<Story name="Default" args={{ options: basicOptions, placeholder: 'Select element...' }} />
|
||||||
|
|
||||||
|
<!-- With Value -->
|
||||||
|
<Story name="With Value" args={{ options: basicOptions, value: 'fire' }} />
|
||||||
|
|
||||||
|
<!-- With Label -->
|
||||||
|
<Story
|
||||||
|
name="With Label"
|
||||||
|
args={{ options: basicOptions, label: 'Element', placeholder: 'Choose element' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Required Field -->
|
||||||
|
<Story
|
||||||
|
name="Required Field"
|
||||||
|
args={{ options: basicOptions, label: 'Primary Element', placeholder: 'Required', required: true }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- With Error -->
|
||||||
|
<Story
|
||||||
|
name="With Error"
|
||||||
|
args={{ options: basicOptions, label: 'Element', error: 'Please select an element' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Contained Variant -->
|
||||||
|
<Story name="Contained" args={{ options: basicOptions, contained: true, placeholder: 'Contained select' }} />
|
||||||
|
|
||||||
|
<!-- All Sizes -->
|
||||||
|
<Story name="All Sizes" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 12px; max-width: 200px;">
|
||||||
|
<Select options={basicOptions} size="small" value="wind" />
|
||||||
|
<Select options={basicOptions} size="medium" value="fire" />
|
||||||
|
<Select options={basicOptions} size="large" value="water" />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Size Comparison -->
|
||||||
|
<Story name="Size Comparison" asChild>
|
||||||
|
<div style="display: flex; gap: 12px; align-items: start; flex-wrap: wrap;">
|
||||||
|
<div>
|
||||||
|
<span style="font-size: 12px; color: #666; display: block; margin-bottom: 4px;">Small</span>
|
||||||
|
<Select options={basicOptions} size="small" value="wind" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span style="font-size: 12px; color: #666; display: block; margin-bottom: 4px;">Medium</span>
|
||||||
|
<Select options={basicOptions} size="medium" value="fire" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span style="font-size: 12px; color: #666; display: block; margin-bottom: 4px;">Large</span>
|
||||||
|
<Select options={basicOptions} size="large" value="water" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- With Number Values -->
|
||||||
|
<Story name="Number Values" args={{ options: numberOptions, label: 'Select Level', value: 3 }} />
|
||||||
|
|
||||||
|
<!-- Disabled Options -->
|
||||||
|
<Story
|
||||||
|
name="Disabled Options"
|
||||||
|
args={{ options: disabledOptions, label: 'Select Option', placeholder: 'Some options disabled' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Disabled State -->
|
||||||
|
<Story name="Disabled State" args={{ options: basicOptions, disabled: true, value: 'earth' }} />
|
||||||
|
|
||||||
|
<!-- Full Width -->
|
||||||
|
<Story
|
||||||
|
name="Full Width"
|
||||||
|
args={{ options: basicOptions, label: 'Full Width Select', fullWidth: true, placeholder: 'Takes full width' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Form Example -->
|
||||||
|
<Story name="Form Example" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px; max-width: 300px;">
|
||||||
|
<Select options={basicOptions} label="Main Element" placeholder="Select..." required />
|
||||||
|
<Select options={basicOptions} label="Secondary Element" placeholder="Optional" />
|
||||||
|
<Select options={numberOptions} label="Skill Level" value={1} />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
134
src/stories/components/ui/Switch.stories.svelte
Normal file
134
src/stories/components/ui/Switch.stories.svelte
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf'
|
||||||
|
import Switch from '$lib/components/ui/switch/Switch.svelte'
|
||||||
|
import { fn } from 'storybook/test'
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/UI/Switch',
|
||||||
|
component: Switch,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
checked: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Whether the switch is checked'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['small', 'medium', 'large'],
|
||||||
|
description: 'Switch size'
|
||||||
|
},
|
||||||
|
element: {
|
||||||
|
control: 'select',
|
||||||
|
options: [undefined, 'wind', 'fire', 'water', 'earth', 'dark', 'light'],
|
||||||
|
description: 'Element color theme for checked state'
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Disabled state'
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Required field'
|
||||||
|
},
|
||||||
|
fullWidth: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Full width switch'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
onCheckedChange: fn()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default - args-only for autodocs -->
|
||||||
|
<Story name="Default" args={{ checked: false }} />
|
||||||
|
|
||||||
|
<!-- Checked -->
|
||||||
|
<Story name="Checked" args={{ checked: true }} />
|
||||||
|
|
||||||
|
<!-- All Sizes -->
|
||||||
|
<Story name="All Sizes" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 60px; font-size: 12px; color: #666;">Small</span>
|
||||||
|
<Switch size="small" />
|
||||||
|
<Switch size="small" checked />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 60px; font-size: 12px; color: #666;">Medium</span>
|
||||||
|
<Switch size="medium" />
|
||||||
|
<Switch size="medium" checked />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 60px; font-size: 12px; color: #666;">Large</span>
|
||||||
|
<Switch size="large" />
|
||||||
|
<Switch size="large" checked />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Element Colors -->
|
||||||
|
<Story name="Element Colors" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
{#each ['wind', 'fire', 'water', 'earth', 'dark', 'light'] as element}
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="width: 50px; font-size: 12px; color: #666;">{element}</span>
|
||||||
|
<Switch {element} />
|
||||||
|
<Switch {element} checked />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Disabled States -->
|
||||||
|
<Story name="Disabled" asChild>
|
||||||
|
<div style="display: flex; gap: 16px; align-items: center;">
|
||||||
|
<Switch disabled />
|
||||||
|
<Switch disabled checked />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- With Labels -->
|
||||||
|
<Story name="With Labels" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px;">
|
||||||
|
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
|
||||||
|
<Switch />
|
||||||
|
<span>Enable notifications</span>
|
||||||
|
</label>
|
||||||
|
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
|
||||||
|
<Switch checked />
|
||||||
|
<span>Dark mode</span>
|
||||||
|
</label>
|
||||||
|
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
|
||||||
|
<Switch size="small" />
|
||||||
|
<span style="font-size: 14px;">Small switch with label</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Settings Example -->
|
||||||
|
<Story name="Settings Example" asChild>
|
||||||
|
<div
|
||||||
|
style="display: flex; flex-direction: column; gap: 0; max-width: 300px; background: #f5f5f5; border-radius: 8px; padding: 4px 0;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style="display: flex; justify-content: space-between; align-items: center; padding: 12px 16px;"
|
||||||
|
>
|
||||||
|
<span>Push Notifications</span>
|
||||||
|
<Switch size="small" checked />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display: flex; justify-content: space-between; align-items: center; padding: 12px 16px;"
|
||||||
|
>
|
||||||
|
<span>Email Updates</span>
|
||||||
|
<Switch size="small" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display: flex; justify-content: space-between; align-items: center; padding: 12px 16px;"
|
||||||
|
>
|
||||||
|
<span>Auto-save</span>
|
||||||
|
<Switch size="small" checked />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
89
src/stories/components/ui/Tooltip.stories.svelte
Normal file
89
src/stories/components/ui/Tooltip.stories.svelte
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf'
|
||||||
|
import Tooltip from '$lib/components/ui/Tooltip.svelte'
|
||||||
|
import Button from '$lib/components/ui/Button.svelte'
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/UI/Tooltip',
|
||||||
|
tags: ['autodocs']
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default -->
|
||||||
|
<Story name="Default">
|
||||||
|
<Tooltip content="This is a tooltip">
|
||||||
|
<Button>Hover me</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- With Long Text -->
|
||||||
|
<Story name="Long Text">
|
||||||
|
<Tooltip
|
||||||
|
content="This is a longer tooltip message that contains more detailed information about the element."
|
||||||
|
>
|
||||||
|
<Button>Hover for details</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Custom Delay -->
|
||||||
|
<Story name="Custom Delay">
|
||||||
|
<div style="display: flex; gap: 16px;">
|
||||||
|
<Tooltip content="Instant tooltip" delayDuration={0}>
|
||||||
|
<Button variant="secondary">No delay</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Default delay (200ms)">
|
||||||
|
<Button variant="secondary">Default</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Slow tooltip" delayDuration={500}>
|
||||||
|
<Button variant="secondary">500ms delay</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Disabled -->
|
||||||
|
<Story name="Disabled">
|
||||||
|
<div style="display: flex; gap: 16px;">
|
||||||
|
<Tooltip content="This tooltip is visible" disabled={false}>
|
||||||
|
<Button>Enabled</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="This tooltip won't show" disabled>
|
||||||
|
<Button variant="secondary">Disabled</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- On Different Elements -->
|
||||||
|
<Story name="On Different Elements">
|
||||||
|
<div style="display: flex; gap: 24px; align-items: center;">
|
||||||
|
<Tooltip content="Button tooltip">
|
||||||
|
<Button size="small">Button</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Text tooltip">
|
||||||
|
<span style="text-decoration: underline; cursor: help;">Hover this text</span>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Icon tooltip">
|
||||||
|
<span
|
||||||
|
style="display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; background: #f0f0f0; border-radius: 50%; cursor: pointer;"
|
||||||
|
>?</span
|
||||||
|
>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Icon Button Example -->
|
||||||
|
<Story name="Icon Button Example">
|
||||||
|
<div style="display: flex; gap: 8px;">
|
||||||
|
<Tooltip content="Edit">
|
||||||
|
<Button size="small" variant="secondary">Edit</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Delete">
|
||||||
|
<Button size="small" variant="secondary">Delete</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Share">
|
||||||
|
<Button size="small" variant="secondary">Share</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Favorite">
|
||||||
|
<Button size="small" variant="secondary">Fav</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
174
src/stories/components/ui/Typeahead.stories.svelte
Normal file
174
src/stories/components/ui/Typeahead.stories.svelte
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf'
|
||||||
|
import Typeahead from '$lib/components/ui/Typeahead.svelte'
|
||||||
|
import { fn } from 'storybook/test'
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Components/UI/Typeahead',
|
||||||
|
component: Typeahead,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
searchable: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Enable search/filtering'
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Allow multiple selections'
|
||||||
|
},
|
||||||
|
creatable: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Allow creating new options'
|
||||||
|
},
|
||||||
|
clearable: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Show clear button'
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Disabled state'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['small', 'medium', 'large'],
|
||||||
|
description: 'Component size'
|
||||||
|
},
|
||||||
|
contained: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Contained background style'
|
||||||
|
},
|
||||||
|
fullWidth: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Full width'
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Placeholder text'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
onValueChange: fn()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const elementOptions = [
|
||||||
|
{ value: 'wind', label: 'Wind' },
|
||||||
|
{ value: 'fire', label: 'Fire' },
|
||||||
|
{ value: 'water', label: 'Water' },
|
||||||
|
{ value: 'earth', label: 'Earth' },
|
||||||
|
{ value: 'light', label: 'Light' },
|
||||||
|
{ value: 'dark', label: 'Dark' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const characterOptions = [
|
||||||
|
{ value: 'narmaya', label: 'Narmaya', element: 'dark' },
|
||||||
|
{ value: 'cagliostro', label: 'Cagliostro', element: 'earth' },
|
||||||
|
{ value: 'zeta', label: 'Zeta', element: 'fire' },
|
||||||
|
{ value: 'yurius', label: 'Yurius', element: 'wind' },
|
||||||
|
{ value: 'lily', label: 'Lily', element: 'water' },
|
||||||
|
{ value: 'lucio', label: 'Lucio', element: 'light' },
|
||||||
|
{ value: 'olivia', label: 'Olivia', element: 'dark' },
|
||||||
|
{ value: 'anila', label: 'Anila', element: 'fire' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const tagOptions = [
|
||||||
|
{ value: 'attack', label: 'Attack' },
|
||||||
|
{ value: 'defense', label: 'Defense' },
|
||||||
|
{ value: 'support', label: 'Support' },
|
||||||
|
{ value: 'healing', label: 'Healing' },
|
||||||
|
{ value: 'buffer', label: 'Buffer' },
|
||||||
|
{ value: 'debuffer', label: 'Debuffer' }
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Default - args-only for autodocs -->
|
||||||
|
<Story name="Default" args={{ options: elementOptions, placeholder: 'Select element...' }} />
|
||||||
|
|
||||||
|
<!-- With Label -->
|
||||||
|
<Story
|
||||||
|
name="With Label"
|
||||||
|
args={{ options: elementOptions, label: 'Element', placeholder: 'Search elements...' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Required Field -->
|
||||||
|
<Story
|
||||||
|
name="Required Field"
|
||||||
|
args={{ options: elementOptions, label: 'Primary Element', placeholder: 'Required', required: true }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- With Error -->
|
||||||
|
<Story
|
||||||
|
name="With Error"
|
||||||
|
args={{ options: elementOptions, label: 'Element', error: 'Please select an element' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Multiple Selection -->
|
||||||
|
<Story name="Multiple Selection" asChild>
|
||||||
|
<div style="max-width: 400px;">
|
||||||
|
<Typeahead options={tagOptions} multiple label="Tags" placeholder="Select tags..." />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Multiple with Max -->
|
||||||
|
<Story name="Multiple with Max" asChild>
|
||||||
|
<div style="max-width: 400px;">
|
||||||
|
<Typeahead
|
||||||
|
options={characterOptions}
|
||||||
|
multiple
|
||||||
|
max={3}
|
||||||
|
label="Select up to 3 characters"
|
||||||
|
placeholder="Choose characters..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Creatable -->
|
||||||
|
<Story name="Creatable" asChild>
|
||||||
|
<div style="max-width: 300px;">
|
||||||
|
<Typeahead
|
||||||
|
options={tagOptions}
|
||||||
|
creatable
|
||||||
|
label="Tags (can create new)"
|
||||||
|
placeholder="Type to add..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- All Sizes -->
|
||||||
|
<Story name="All Sizes" asChild>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 16px; max-width: 300px;">
|
||||||
|
<Typeahead options={elementOptions} size="small" value="wind" label="Small" />
|
||||||
|
<Typeahead options={elementOptions} size="medium" value="fire" label="Medium" />
|
||||||
|
<Typeahead options={elementOptions} size="large" value="water" label="Large" />
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Contained -->
|
||||||
|
<Story name="Contained" args={{ options: elementOptions, contained: true, placeholder: 'Contained style' }} />
|
||||||
|
|
||||||
|
<!-- Disabled -->
|
||||||
|
<Story name="Disabled" args={{ options: elementOptions, disabled: true, value: 'earth' }} />
|
||||||
|
|
||||||
|
<!-- Not Clearable -->
|
||||||
|
<Story
|
||||||
|
name="Not Clearable"
|
||||||
|
args={{ options: elementOptions, clearable: false, value: 'dark', label: 'Cannot clear' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Character Search Example -->
|
||||||
|
<Story name="Character Search Example" asChild>
|
||||||
|
<div style="max-width: 350px;">
|
||||||
|
<Typeahead
|
||||||
|
options={characterOptions}
|
||||||
|
label="Search Character"
|
||||||
|
placeholder="Type character name..."
|
||||||
|
searchable
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
<!-- Full Width -->
|
||||||
|
<Story
|
||||||
|
name="Full Width"
|
||||||
|
args={{ options: elementOptions, label: 'Full Width Select', fullWidth: true, placeholder: 'Takes full width' }}
|
||||||
|
/>
|
||||||
238
src/stories/foundations/Colors.mdx
Normal file
238
src/stories/foundations/Colors.mdx
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
import { Meta, ColorPalette, ColorItem } from '@storybook/addon-docs/blocks';
|
||||||
|
|
||||||
|
<Meta title="Foundations/Colors" />
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
|
||||||
|
The Hensei color system is built around a neutral grey scale, accent colors, and element-specific palettes for the six Granblue Fantasy elements.
|
||||||
|
|
||||||
|
## Grey Scale
|
||||||
|
|
||||||
|
The foundation of our color system. Used for backgrounds, text, borders, and UI elements.
|
||||||
|
|
||||||
|
<ColorPalette>
|
||||||
|
<ColorItem
|
||||||
|
title="Grey 00"
|
||||||
|
subtitle="$grey-00"
|
||||||
|
colors={{ Black: '#000000' }}
|
||||||
|
/>
|
||||||
|
<ColorItem
|
||||||
|
title="Grey 10-20"
|
||||||
|
subtitle="Dark backgrounds"
|
||||||
|
colors={{ 'Grey 10': '#111111', 'Grey 15': '#191919', 'Grey 20': '#212121' }}
|
||||||
|
/>
|
||||||
|
<ColorItem
|
||||||
|
title="Grey 30-50"
|
||||||
|
subtitle="Dark mode UI"
|
||||||
|
colors={{ 'Grey 30': '#2f2f2f', 'Grey 40': '#444444', 'Grey 50': '#777777' }}
|
||||||
|
/>
|
||||||
|
<ColorItem
|
||||||
|
title="Grey 60-80"
|
||||||
|
subtitle="Secondary text, borders"
|
||||||
|
colors={{ 'Grey 60': '#a9a9a9', 'Grey 70': '#c6c6c6', 'Grey 80': '#e9e9e9' }}
|
||||||
|
/>
|
||||||
|
<ColorItem
|
||||||
|
title="Grey 85-100"
|
||||||
|
subtitle="Light backgrounds"
|
||||||
|
colors={{ 'Grey 85': '#efefef', 'Grey 90': '#f5f5f5', 'Grey 100': '#ffffff' }}
|
||||||
|
/>
|
||||||
|
</ColorPalette>
|
||||||
|
|
||||||
|
## Accent Colors
|
||||||
|
|
||||||
|
Primary accent colors for interactive elements and highlights.
|
||||||
|
|
||||||
|
<ColorPalette>
|
||||||
|
<ColorItem
|
||||||
|
title="Blue (Primary)"
|
||||||
|
subtitle="Links, primary actions"
|
||||||
|
colors={{ 'Light': '#275dc5', 'Light Focus': '#0c398d', 'Dark': '#6195f4', 'Dark Focus': '#275dc5' }}
|
||||||
|
/>
|
||||||
|
<ColorItem
|
||||||
|
title="Yellow (Highlight)"
|
||||||
|
subtitle="Selected items, highlights"
|
||||||
|
colors={{ 'Light': '#c89d39', 'Dark': '#f9cc64', 'Highlight': '#ffed4c' }}
|
||||||
|
/>
|
||||||
|
<ColorItem
|
||||||
|
title="Error"
|
||||||
|
subtitle="Destructive actions"
|
||||||
|
colors={{ 'Red': '#d13a3a', 'Error BG Light': '#fce8e8', 'Error BG Dark': '#3d1515' }}
|
||||||
|
/>
|
||||||
|
</ColorPalette>
|
||||||
|
|
||||||
|
## Element Colors
|
||||||
|
|
||||||
|
Granblue Fantasy element-specific colors used for theming characters, weapons, and summons.
|
||||||
|
|
||||||
|
### Wind
|
||||||
|
|
||||||
|
<ColorPalette>
|
||||||
|
<ColorItem
|
||||||
|
title="Wind"
|
||||||
|
subtitle="Green element"
|
||||||
|
colors={{
|
||||||
|
'Text Light': '#006a45',
|
||||||
|
'Text Dark': '#1dc688',
|
||||||
|
'BG': '#3ee489',
|
||||||
|
'Portrait': '#cdffed'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ColorPalette>
|
||||||
|
|
||||||
|
### Fire
|
||||||
|
|
||||||
|
<ColorPalette>
|
||||||
|
<ColorItem
|
||||||
|
title="Fire"
|
||||||
|
subtitle="Red element"
|
||||||
|
colors={{
|
||||||
|
'Text Light': '#6e0000',
|
||||||
|
'Text Dark': '#ec5c5c',
|
||||||
|
'BG': '#fa6d6d',
|
||||||
|
'Portrait': '#ffcdcd'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ColorPalette>
|
||||||
|
|
||||||
|
### Water
|
||||||
|
|
||||||
|
<ColorPalette>
|
||||||
|
<ColorItem
|
||||||
|
title="Water"
|
||||||
|
subtitle="Blue element"
|
||||||
|
colors={{
|
||||||
|
'Text Light': '#00639c',
|
||||||
|
'Text Dark': '#5cb7ec',
|
||||||
|
'BG': '#6cc9ff',
|
||||||
|
'Portrait': '#cdedff'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ColorPalette>
|
||||||
|
|
||||||
|
### Earth
|
||||||
|
|
||||||
|
<ColorPalette>
|
||||||
|
<ColorItem
|
||||||
|
title="Earth"
|
||||||
|
subtitle="Orange element"
|
||||||
|
colors={{
|
||||||
|
'Text Light': '#8e3c0b',
|
||||||
|
'Text Dark': '#ec985c',
|
||||||
|
'BG': '#fd9f5b',
|
||||||
|
'Portrait': '#ffe2cd'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ColorPalette>
|
||||||
|
|
||||||
|
### Light
|
||||||
|
|
||||||
|
<ColorPalette>
|
||||||
|
<ColorItem
|
||||||
|
title="Light"
|
||||||
|
subtitle="Yellow element"
|
||||||
|
colors={{
|
||||||
|
'Text Light': '#715100',
|
||||||
|
'Text Dark': '#c59c0c',
|
||||||
|
'BG': '#e8d633',
|
||||||
|
'Portrait': '#fffacd'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ColorPalette>
|
||||||
|
|
||||||
|
### Dark
|
||||||
|
|
||||||
|
<ColorPalette>
|
||||||
|
<ColorItem
|
||||||
|
title="Dark"
|
||||||
|
subtitle="Purple element"
|
||||||
|
colors={{
|
||||||
|
'Text Light': '#560075',
|
||||||
|
'Text Dark': '#c65cec',
|
||||||
|
'BG': '#de7bff',
|
||||||
|
'Portrait': '#f2cdff'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ColorPalette>
|
||||||
|
|
||||||
|
## Special Purpose Colors
|
||||||
|
|
||||||
|
### Extra Weapons (Purple)
|
||||||
|
|
||||||
|
<ColorPalette>
|
||||||
|
<ColorItem
|
||||||
|
title="Extra Purple"
|
||||||
|
subtitle="Additional weapon slots"
|
||||||
|
colors={{
|
||||||
|
'BG Light': '#ecebff',
|
||||||
|
'BG Dark': '#635fb7',
|
||||||
|
'Card Light': '#d5d3f6',
|
||||||
|
'Primary': '#8c86ff'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ColorPalette>
|
||||||
|
|
||||||
|
### Subaura Summons (Orange)
|
||||||
|
|
||||||
|
<ColorPalette>
|
||||||
|
<ColorItem
|
||||||
|
title="Subaura Orange"
|
||||||
|
subtitle="Sub-aura summon slots"
|
||||||
|
colors={{
|
||||||
|
'BG Light': '#ffebd9',
|
||||||
|
'BG Dark': '#6b401b',
|
||||||
|
'Card Light': '#facea7',
|
||||||
|
'Primary': '#d08f57'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ColorPalette>
|
||||||
|
|
||||||
|
### Game Tokens
|
||||||
|
|
||||||
|
<ColorPalette>
|
||||||
|
<ColorItem
|
||||||
|
title="Charge Attack"
|
||||||
|
subtitle="Ougi/Charge attack indicator"
|
||||||
|
colors={{ 'Background': '#ffb461', 'Text': '#885243' }}
|
||||||
|
/>
|
||||||
|
<ColorItem
|
||||||
|
title="Full Auto"
|
||||||
|
subtitle="Full auto mode indicator"
|
||||||
|
colors={{ 'Background': '#ffed4c', 'Text': '#a39200' }}
|
||||||
|
/>
|
||||||
|
<ColorItem
|
||||||
|
title="Auto Guard"
|
||||||
|
subtitle="Auto guard indicator"
|
||||||
|
colors={{ 'Background': '#b6b2fc', 'Text': '#4f3c79' }}
|
||||||
|
/>
|
||||||
|
</ColorPalette>
|
||||||
|
|
||||||
|
## CSS Custom Properties
|
||||||
|
|
||||||
|
All colors are available as CSS custom properties through the theme system. They automatically switch between light and dark mode values.
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Usage in components */
|
||||||
|
.my-component {
|
||||||
|
background: var(--card-bg);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Element-specific theming */
|
||||||
|
.wind-themed {
|
||||||
|
background: var(--wind-bg);
|
||||||
|
color: var(--wind-text);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Variables
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `--background` | Page background |
|
||||||
|
| `--card-bg` | Card/panel background |
|
||||||
|
| `--text-primary` | Primary text color |
|
||||||
|
| `--text-secondary` | Secondary/muted text |
|
||||||
|
| `--border-subtle` | Subtle borders |
|
||||||
|
| `--accent-blue` | Primary accent |
|
||||||
|
| `--accent-yellow` | Highlight/selection |
|
||||||
232
src/stories/foundations/Elements.mdx
Normal file
232
src/stories/foundations/Elements.mdx
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
import { Meta } from '@storybook/addon-docs/blocks';
|
||||||
|
|
||||||
|
<Meta title="Foundations/Elements" />
|
||||||
|
|
||||||
|
# Elements
|
||||||
|
|
||||||
|
Granblue Fantasy features six elemental types that form a core part of the game's mechanics. Each element has a distinct color palette used throughout Hensei for theming characters, weapons, summons, and UI components.
|
||||||
|
|
||||||
|
## Element Wheel
|
||||||
|
|
||||||
|
The six elements form a weakness/advantage cycle:
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center', margin: '32px 0' }}>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px', maxWidth: '400px' }}>
|
||||||
|
<div style={{ padding: '16px', background: '#cdffed', borderRadius: '8px', textAlign: 'center' }}>
|
||||||
|
<div style={{ fontWeight: 600, color: '#006a45' }}>Wind</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: '16px', background: '#ffcdcd', borderRadius: '8px', textAlign: 'center' }}>
|
||||||
|
<div style={{ fontWeight: 600, color: '#6e0000' }}>Fire</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: '16px', background: '#cdedff', borderRadius: '8px', textAlign: 'center' }}>
|
||||||
|
<div style={{ fontWeight: 600, color: '#00639c' }}>Water</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: '16px', background: '#ffe2cd', borderRadius: '8px', textAlign: 'center' }}>
|
||||||
|
<div style={{ fontWeight: 600, color: '#8e3c0b' }}>Earth</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: '16px', background: '#fffacd', borderRadius: '8px', textAlign: 'center' }}>
|
||||||
|
<div style={{ fontWeight: 600, color: '#715100' }}>Light</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: '16px', background: '#f2cdff', borderRadius: '8px', textAlign: 'center' }}>
|
||||||
|
<div style={{ fontWeight: 600, color: '#560075' }}>Dark</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
**Advantage cycle:**
|
||||||
|
- Wind → Earth → Water → Fire → Wind
|
||||||
|
- Light ↔ Dark (mutual advantage)
|
||||||
|
|
||||||
|
## Element Color Palettes
|
||||||
|
|
||||||
|
Each element has multiple color values for different contexts:
|
||||||
|
|
||||||
|
### Wind (Green)
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#006a45', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'white', fontSize: '11px' }}>Text Light</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#1dc688', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Text Dark</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#3ee489', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Background</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#cdffed', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Portrait</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Fire (Red)
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#6e0000', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'white', fontSize: '11px' }}>Text Light</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#ec5c5c', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Text Dark</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#fa6d6d', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Background</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#ffcdcd', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Portrait</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Water (Blue)
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#00639c', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'white', fontSize: '11px' }}>Text Light</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#5cb7ec', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Text Dark</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#6cc9ff', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Background</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#cdedff', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Portrait</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Earth (Orange)
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#8e3c0b', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'white', fontSize: '11px' }}>Text Light</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#ec985c', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Text Dark</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#fd9f5b', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Background</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#ffe2cd', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Portrait</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Light (Yellow)
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#715100', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'white', fontSize: '11px' }}>Text Light</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#c59c0c', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Text Dark</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#e8d633', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Background</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#fffacd', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Portrait</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Dark (Purple)
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#560075', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'white', fontSize: '11px' }}>Text Light</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#c65cec', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Text Dark</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#de7bff', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Background</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: '80px', height: '60px', background: '#f2cdff', borderRadius: '4px', display: 'flex', alignItems: 'end', padding: '4px' }}>
|
||||||
|
<span style={{ color: 'black', fontSize: '11px' }}>Portrait</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## CSS Custom Properties
|
||||||
|
|
||||||
|
Element colors are available as CSS variables that adapt to light/dark themes:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Each element provides these variables */
|
||||||
|
--{element}-bg /* Main background color */
|
||||||
|
--{element}-bg-hover /* Hover state background */
|
||||||
|
--{element}-text /* Primary text color */
|
||||||
|
--{element}-text-hover /* Hover state text */
|
||||||
|
--{element}-portrait-bg /* Portrait/card background */
|
||||||
|
--{element}-shadow /* Box shadow color */
|
||||||
|
--{element}-accent /* Accent color */
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Usage
|
||||||
|
|
||||||
|
```css
|
||||||
|
.wind-card {
|
||||||
|
background: var(--wind-bg);
|
||||||
|
color: var(--wind-text);
|
||||||
|
box-shadow: 0 2px 8px var(--wind-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind-card:hover {
|
||||||
|
background: var(--wind-bg-hover);
|
||||||
|
color: var(--wind-text-hover);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Element Buttons
|
||||||
|
|
||||||
|
Each element has a dedicated button color:
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', margin: '16px 0' }}>
|
||||||
|
<button style={{ padding: '8px 16px', background: '#1dc688', border: 'none', borderRadius: '6px', color: 'white', fontWeight: 500 }}>Wind</button>
|
||||||
|
<button style={{ padding: '8px 16px', background: '#ec5c5c', border: 'none', borderRadius: '6px', color: 'white', fontWeight: 500 }}>Fire</button>
|
||||||
|
<button style={{ padding: '8px 16px', background: '#5cb7ec', border: 'none', borderRadius: '6px', color: 'white', fontWeight: 500 }}>Water</button>
|
||||||
|
<button style={{ padding: '8px 16px', background: '#8e3c0b', border: 'none', borderRadius: '6px', color: 'white', fontWeight: 500 }}>Earth</button>
|
||||||
|
<button style={{ padding: '8px 16px', background: '#c59c0c', border: 'none', borderRadius: '6px', color: 'white', fontWeight: 500 }}>Light</button>
|
||||||
|
<button style={{ padding: '8px 16px', background: '#c65cec', border: 'none', borderRadius: '6px', color: 'white', fontWeight: 500 }}>Dark</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Navigation Selected States
|
||||||
|
|
||||||
|
Elements have specific colors for selected navigation items:
|
||||||
|
|
||||||
|
| Element | Background | Text |
|
||||||
|
|---------|------------|------|
|
||||||
|
| Wind | `#cdffed` | `#006a45` |
|
||||||
|
| Fire | `#ffcdcd` | `#6e0000` |
|
||||||
|
| Water | `#cdedff` | `#00639c` |
|
||||||
|
| Earth | `#ffe2cd` | `#863504` |
|
||||||
|
| Light | `#fffacd` | `#715100` |
|
||||||
|
| Dark | `#f2cdff` | `#560075` |
|
||||||
|
|
||||||
|
## Focus Ring Mixins
|
||||||
|
|
||||||
|
SCSS mixins for element-specific focus rings:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
@use 'themes/colors';
|
||||||
|
|
||||||
|
.wind-input:focus {
|
||||||
|
@include colors.focus-ring-wind();
|
||||||
|
}
|
||||||
|
|
||||||
|
.fire-input:focus {
|
||||||
|
@include colors.focus-ring-fire();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each mixin applies:
|
||||||
|
- Appropriate border color
|
||||||
|
- Matching box shadow with transparency
|
||||||
|
- Removes default outline
|
||||||
|
|
||||||
|
## Components with Element Support
|
||||||
|
|
||||||
|
The following components support element theming:
|
||||||
|
|
||||||
|
- **Button** - `element` prop for colored buttons
|
||||||
|
- **SegmentedControl** - `element` prop for colored segments
|
||||||
|
- **ElementLabel** - Displays element icon with color
|
||||||
|
- **CharacterUnit** - Border color based on character element
|
||||||
|
- **WeaponUnit** - Border color based on weapon element
|
||||||
|
- **SummonUnit** - Border color based on summon element
|
||||||
143
src/stories/foundations/Spacing.mdx
Normal file
143
src/stories/foundations/Spacing.mdx
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
import { Meta } from '@storybook/addon-docs/blocks';
|
||||||
|
|
||||||
|
<Meta title="Foundations/Spacing" />
|
||||||
|
|
||||||
|
# Spacing
|
||||||
|
|
||||||
|
Hensei uses an 8px base unit spacing system for consistent rhythm throughout the interface.
|
||||||
|
|
||||||
|
## Base Unit
|
||||||
|
|
||||||
|
```scss
|
||||||
|
$unit: 8px;
|
||||||
|
```
|
||||||
|
|
||||||
|
All spacing values are multiples of this base unit, creating visual harmony and predictable layouts.
|
||||||
|
|
||||||
|
## Spacing Scale
|
||||||
|
|
||||||
|
<div style={{ display: 'grid', gap: '16px', marginTop: '24px' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
||||||
|
<div style={{ width: '120px', fontFamily: 'monospace', fontSize: '13px' }}>$unit-fourth</div>
|
||||||
|
<div style={{ width: '2px', height: '24px', background: '#275dc5' }}></div>
|
||||||
|
<span style={{ fontSize: '13px', color: '#666' }}>2px - Micro spacing</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
||||||
|
<div style={{ width: '120px', fontFamily: 'monospace', fontSize: '13px' }}>$unit-half</div>
|
||||||
|
<div style={{ width: '4px', height: '24px', background: '#275dc5' }}></div>
|
||||||
|
<span style={{ fontSize: '13px', color: '#666' }}>4px - Tight spacing</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
||||||
|
<div style={{ width: '120px', fontFamily: 'monospace', fontSize: '13px' }}>$unit</div>
|
||||||
|
<div style={{ width: '8px', height: '24px', background: '#275dc5' }}></div>
|
||||||
|
<span style={{ fontSize: '13px', color: '#666' }}>8px - Base unit</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
||||||
|
<div style={{ width: '120px', fontFamily: 'monospace', fontSize: '13px' }}>$unit-2x</div>
|
||||||
|
<div style={{ width: '16px', height: '24px', background: '#275dc5' }}></div>
|
||||||
|
<span style={{ fontSize: '13px', color: '#666' }}>16px - Default gap</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
||||||
|
<div style={{ width: '120px', fontFamily: 'monospace', fontSize: '13px' }}>$unit-3x</div>
|
||||||
|
<div style={{ width: '24px', height: '24px', background: '#275dc5' }}></div>
|
||||||
|
<span style={{ fontSize: '13px', color: '#666' }}>24px - Medium spacing</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
||||||
|
<div style={{ width: '120px', fontFamily: 'monospace', fontSize: '13px' }}>$unit-4x</div>
|
||||||
|
<div style={{ width: '32px', height: '24px', background: '#275dc5' }}></div>
|
||||||
|
<span style={{ fontSize: '13px', color: '#666' }}>32px - Section spacing</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
||||||
|
<div style={{ width: '120px', fontFamily: 'monospace', fontSize: '13px' }}>$unit-6x</div>
|
||||||
|
<div style={{ width: '48px', height: '24px', background: '#275dc5' }}></div>
|
||||||
|
<span style={{ fontSize: '13px', color: '#666' }}>48px - Large spacing</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
||||||
|
<div style={{ width: '120px', fontFamily: 'monospace', fontSize: '13px' }}>$unit-8x</div>
|
||||||
|
<div style={{ width: '64px', height: '24px', background: '#275dc5' }}></div>
|
||||||
|
<span style={{ fontSize: '13px', color: '#666' }}>64px - XL spacing</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Full Scale Reference
|
||||||
|
|
||||||
|
| Token | Value | Pixels | Usage |
|
||||||
|
|-------|-------|--------|-------|
|
||||||
|
| `$unit-fourth` | $unit / 4 | 2px | Borders, micro adjustments |
|
||||||
|
| `$unit-half` | $unit / 2 | 4px | Tight gaps, small padding |
|
||||||
|
| `$unit-three-quarter` | $unit * 0.75 | 6px | Small gaps |
|
||||||
|
| `$unit` | 8px | 8px | Base unit |
|
||||||
|
| `$unit-2x` | $unit * 2 | 16px | Standard gap/padding |
|
||||||
|
| `$unit-3x` | $unit * 3 | 24px | Medium spacing |
|
||||||
|
| `$unit-4x` | $unit * 4 | 32px | Section spacing |
|
||||||
|
| `$unit-5x` | $unit * 5 | 40px | Large gaps |
|
||||||
|
| `$unit-6x` | $unit * 6 | 48px | XL gaps |
|
||||||
|
| `$unit-8x` | $unit * 8 | 64px | Page margins |
|
||||||
|
| `$unit-10x` | $unit * 10 | 80px | Hero spacing |
|
||||||
|
| `$unit-12x` | $unit * 12 | 96px | Section dividers |
|
||||||
|
|
||||||
|
## CSS Custom Properties
|
||||||
|
|
||||||
|
Semantic spacing aliases are available as CSS variables:
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--spacing-xs: 8px; /* $unit */
|
||||||
|
--spacing-sm: 16px; /* $unit-2x */
|
||||||
|
--spacing-md: 32px; /* $unit-4x */
|
||||||
|
--spacing-lg: 64px; /* $unit-8x */
|
||||||
|
--spacing-xl: 96px; /* $unit-12x */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Breakpoints
|
||||||
|
|
||||||
|
Responsive breakpoints for different device sizes:
|
||||||
|
|
||||||
|
| Token | Value | Device |
|
||||||
|
|-------|-------|--------|
|
||||||
|
| `$phone` | 375px | Mobile phones |
|
||||||
|
| `$tablet` | 768px | Tablets |
|
||||||
|
| `$laptop` | 1280px | Laptops |
|
||||||
|
| `$desktop` | 1920px | Desktop monitors |
|
||||||
|
|
||||||
|
### Grid Width
|
||||||
|
|
||||||
|
The standard grid/content width:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
$grid-width: 780px;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component Heights
|
||||||
|
|
||||||
|
Standard heights for grid representation components:
|
||||||
|
|
||||||
|
| Token | Value | Component |
|
||||||
|
|-------|-------|-----------|
|
||||||
|
| `$character-rep-height` | 111px | Character unit/rep |
|
||||||
|
| `$weapon-rep-height` | 109.75px | Weapon unit/rep |
|
||||||
|
| `$summon-rep-height` | 117px | Summon unit/rep |
|
||||||
|
|
||||||
|
## Usage in SCSS
|
||||||
|
|
||||||
|
```scss
|
||||||
|
@use 'themes/spacing' as spacing;
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: spacing.$unit-2x;
|
||||||
|
margin-bottom: spacing.$unit-3x;
|
||||||
|
gap: spacing.$unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-section {
|
||||||
|
padding: spacing.$unit-8x 0;
|
||||||
|
margin-bottom: spacing.$unit-6x;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: spacing.$tablet) {
|
||||||
|
.card {
|
||||||
|
padding: spacing.$unit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
119
src/stories/foundations/Typography.mdx
Normal file
119
src/stories/foundations/Typography.mdx
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
import { Meta, Typeset } from '@storybook/addon-docs/blocks';
|
||||||
|
|
||||||
|
<Meta title="Foundations/Typography" />
|
||||||
|
|
||||||
|
# Typography
|
||||||
|
|
||||||
|
Hensei uses the **Goalking** variable font as its primary typeface, with system fonts as fallbacks.
|
||||||
|
|
||||||
|
## Font Family
|
||||||
|
|
||||||
|
```css
|
||||||
|
--font-family: 'Goalking', system-ui, sans-serif;
|
||||||
|
```
|
||||||
|
|
||||||
|
The Goalking font is a variable font supporting weights from 100-900, loaded from `/fonts/gk-variable.woff2`.
|
||||||
|
|
||||||
|
## Font Weights
|
||||||
|
|
||||||
|
| Weight | Value | Usage |
|
||||||
|
|--------|-------|-------|
|
||||||
|
| Normal | 400 | Body text, descriptions |
|
||||||
|
| Medium | 500 | Labels, emphasized text |
|
||||||
|
| Bold | 600 | Headings, important text |
|
||||||
|
|
||||||
|
## Font Sizes
|
||||||
|
|
||||||
|
Our font sizes use `rem` units based on a 10px base (62.5% of 16px), making calculations simple: 1rem = 10px.
|
||||||
|
|
||||||
|
<Typeset
|
||||||
|
fontSizes={[
|
||||||
|
'11px',
|
||||||
|
'13px',
|
||||||
|
'15px',
|
||||||
|
'16px',
|
||||||
|
'18px',
|
||||||
|
'21px',
|
||||||
|
'24px',
|
||||||
|
'28px',
|
||||||
|
]}
|
||||||
|
fontWeight={400}
|
||||||
|
sampleText="The quick brown fox jumps over the lazy dog"
|
||||||
|
fontFamily="'Goalking', system-ui, sans-serif"
|
||||||
|
/>
|
||||||
|
|
||||||
|
### Size Scale
|
||||||
|
|
||||||
|
| Token | Size | Pixels | Usage |
|
||||||
|
|-------|------|--------|-------|
|
||||||
|
| `$font-tiny` | 1.1rem | 11px | Badges, captions |
|
||||||
|
| `$font-small` | 1.3rem | 13px | Secondary text, metadata |
|
||||||
|
| `$font-button` | 1.5rem | 15px | Button text |
|
||||||
|
| `$font-name` | 1.5rem | 15px | Character/item names |
|
||||||
|
| `$font-regular` | 1.6rem | 16px | Body text |
|
||||||
|
| `$font-medium` | 1.8rem | 18px | Subheadings |
|
||||||
|
| `$font-large` | 2.1rem | 21px | Section headers |
|
||||||
|
| `$font-xlarge` | 2.4rem | 24px | Page titles |
|
||||||
|
| `$font-xxlarge` | 2.8rem | 28px | Hero text |
|
||||||
|
|
||||||
|
## Font Weight Examples
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px', margin: '24px 0' }}>
|
||||||
|
<div style={{ fontSize: '18px', fontWeight: 400 }}>Normal (400): The quick brown fox jumps over the lazy dog</div>
|
||||||
|
<div style={{ fontSize: '18px', fontWeight: 500 }}>Medium (500): The quick brown fox jumps over the lazy dog</div>
|
||||||
|
<div style={{ fontSize: '18px', fontWeight: 600 }}>Bold (600): The quick brown fox jumps over the lazy dog</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Usage in SCSS
|
||||||
|
|
||||||
|
Import the typography module to use font variables:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
@use 'themes/typography' as typography;
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
font-size: typography.$font-large;
|
||||||
|
font-weight: typography.$bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body-text {
|
||||||
|
font-size: typography.$font-regular;
|
||||||
|
font-weight: typography.$normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption {
|
||||||
|
font-size: typography.$font-small;
|
||||||
|
font-weight: typography.$medium;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Text Color Variables
|
||||||
|
|
||||||
|
Text colors are available as CSS custom properties and automatically adapt to the current theme:
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `--text-primary` | Main body text |
|
||||||
|
| `--text-secondary` | Muted/secondary text |
|
||||||
|
| `--text-tertiary` | Disabled/placeholder text |
|
||||||
|
| `--link-text-hover` | Link hover state |
|
||||||
|
|
||||||
|
```css
|
||||||
|
.primary-text {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.muted-text {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Font Smoothing
|
||||||
|
|
||||||
|
For optimal rendering, we apply antialiasing to all text:
|
||||||
|
|
||||||
|
```css
|
||||||
|
body {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
```
|
||||||
62
src/stories/mocks/characters.ts
Normal file
62
src/stories/mocks/characters.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import type { Character, GridCharacter } from '$lib/types/api/character';
|
||||||
|
|
||||||
|
/** Mock character data for Storybook stories */
|
||||||
|
export const mockCharacter: Character = {
|
||||||
|
id: 'char-1',
|
||||||
|
granblueId: '3040000000',
|
||||||
|
name: { en: 'Narmaya', ja: 'ナルメア' },
|
||||||
|
element: 5, // Dark
|
||||||
|
rarity: 3, // SSR
|
||||||
|
special: false,
|
||||||
|
uncap: { flb: true, ulb: true, transcendence: false },
|
||||||
|
proficiency: [1, 2] // Katana, Dagger
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockSpecialCharacter: Character = {
|
||||||
|
id: 'char-2',
|
||||||
|
granblueId: '3040100000',
|
||||||
|
name: { en: 'Cagliostro', ja: 'カリオストロ' },
|
||||||
|
element: 4, // Earth
|
||||||
|
rarity: 3, // SSR
|
||||||
|
special: true, // Limited
|
||||||
|
uncap: { flb: true, ulb: true, transcendence: false },
|
||||||
|
proficiency: [6] // Staff
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockGridCharacter: GridCharacter = {
|
||||||
|
id: 'grid-char-1',
|
||||||
|
position: 1,
|
||||||
|
uncapLevel: 5,
|
||||||
|
transcendenceStep: 0,
|
||||||
|
perpetuity: false,
|
||||||
|
character: mockCharacter
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockGridCharacterWithRing: GridCharacter = {
|
||||||
|
id: 'grid-char-2',
|
||||||
|
position: 2,
|
||||||
|
uncapLevel: 6,
|
||||||
|
transcendenceStep: 3,
|
||||||
|
perpetuity: true,
|
||||||
|
character: mockCharacter
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Characters organized by element for element-specific stories */
|
||||||
|
export const mockCharactersByElement = {
|
||||||
|
wind: { ...mockCharacter, id: 'char-wind', element: 1, name: { en: 'Tiamat', ja: 'ティアマト' } },
|
||||||
|
fire: { ...mockCharacter, id: 'char-fire', element: 2, name: { en: 'Colossus', ja: 'コロッサス' } },
|
||||||
|
water: {
|
||||||
|
...mockCharacter,
|
||||||
|
id: 'char-water',
|
||||||
|
element: 3,
|
||||||
|
name: { en: 'Leviathan', ja: 'リヴァイアサン' }
|
||||||
|
},
|
||||||
|
earth: { ...mockCharacter, id: 'char-earth', element: 4, name: { en: 'Yggdrasil', ja: 'ユグドラシル' } },
|
||||||
|
dark: { ...mockCharacter, id: 'char-dark', element: 5, name: { en: 'Celeste', ja: 'セレスト' } },
|
||||||
|
light: {
|
||||||
|
...mockCharacter,
|
||||||
|
id: 'char-light',
|
||||||
|
element: 6,
|
||||||
|
name: { en: 'Luminiera', ja: 'シュヴァリエ' }
|
||||||
|
}
|
||||||
|
};
|
||||||
38
src/stories/mocks/jobs.ts
Normal file
38
src/stories/mocks/jobs.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import type { Job } from '$lib/types/api/job';
|
||||||
|
|
||||||
|
/** Mock job data for Storybook stories */
|
||||||
|
export const mockJob: Job = {
|
||||||
|
id: 'job-1',
|
||||||
|
granblueId: '180001',
|
||||||
|
name: { en: 'Lumberjack', ja: 'ランバージャック' },
|
||||||
|
proficiency: [3], // Axe
|
||||||
|
row: 5,
|
||||||
|
ultimateMastery: true
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockJobNoUM: Job = {
|
||||||
|
id: 'job-2',
|
||||||
|
granblueId: '100001',
|
||||||
|
name: { en: 'Dark Fencer', ja: 'ダークフェンサー' },
|
||||||
|
proficiency: [1, 2], // Sword, Dagger
|
||||||
|
row: 3,
|
||||||
|
ultimateMastery: false
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockJobMultiProf: Job = {
|
||||||
|
id: 'job-3',
|
||||||
|
granblueId: '180101',
|
||||||
|
name: { en: 'Kengo', ja: 'ケンゴウ' },
|
||||||
|
proficiency: [1, 2], // Sword, Katana
|
||||||
|
row: 5,
|
||||||
|
ultimateMastery: true
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Jobs organized by row/tier */
|
||||||
|
export const mockJobsByRow = {
|
||||||
|
row1: { ...mockJob, id: 'job-row1', granblueId: '100001', row: 1, ultimateMastery: false },
|
||||||
|
row2: { ...mockJob, id: 'job-row2', granblueId: '110001', row: 2, ultimateMastery: false },
|
||||||
|
row3: { ...mockJob, id: 'job-row3', granblueId: '120001', row: 3, ultimateMastery: false },
|
||||||
|
row4: { ...mockJob, id: 'job-row4', granblueId: '130001', row: 4, ultimateMastery: true },
|
||||||
|
row5: { ...mockJob, id: 'job-row5', granblueId: '180001', row: 5, ultimateMastery: true }
|
||||||
|
};
|
||||||
51
src/stories/mocks/summons.ts
Normal file
51
src/stories/mocks/summons.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import type { Summon, GridSummon } from '$lib/types/api/summon';
|
||||||
|
|
||||||
|
/** Mock summon data for Storybook stories */
|
||||||
|
export const mockSummon: Summon = {
|
||||||
|
id: 'summon-1',
|
||||||
|
granblueId: '2040000000',
|
||||||
|
name: { en: 'Bahamut', ja: 'バハムート' },
|
||||||
|
element: 5, // Dark
|
||||||
|
rarity: 3, // SSR
|
||||||
|
uncap: { flb: true, ulb: true, transcendence: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockGridSummon: GridSummon = {
|
||||||
|
id: 'grid-summon-1',
|
||||||
|
position: 0,
|
||||||
|
uncapLevel: 5,
|
||||||
|
transcendenceStep: 0,
|
||||||
|
main: false,
|
||||||
|
friend: false,
|
||||||
|
summon: mockSummon
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockMainSummon: GridSummon = {
|
||||||
|
id: 'grid-summon-main',
|
||||||
|
position: -1,
|
||||||
|
uncapLevel: 6,
|
||||||
|
transcendenceStep: 5,
|
||||||
|
main: true,
|
||||||
|
friend: false,
|
||||||
|
summon: mockSummon
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockFriendSummon: GridSummon = {
|
||||||
|
id: 'grid-summon-friend',
|
||||||
|
position: 6,
|
||||||
|
uncapLevel: 5,
|
||||||
|
transcendenceStep: 0,
|
||||||
|
main: false,
|
||||||
|
friend: true,
|
||||||
|
summon: mockSummon
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Summons organized by element */
|
||||||
|
export const mockSummonsByElement = {
|
||||||
|
wind: { ...mockSummon, id: 'summon-wind', element: 1, name: { en: 'Tiamat', ja: 'ティアマト' } },
|
||||||
|
fire: { ...mockSummon, id: 'summon-fire', element: 2, name: { en: 'Colossus', ja: 'コロッサス' } },
|
||||||
|
water: { ...mockSummon, id: 'summon-water', element: 3, name: { en: 'Leviathan', ja: 'リヴァイアサン' } },
|
||||||
|
earth: { ...mockSummon, id: 'summon-earth', element: 4, name: { en: 'Yggdrasil', ja: 'ユグドラシル' } },
|
||||||
|
dark: { ...mockSummon, id: 'summon-dark', element: 5, name: { en: 'Celeste', ja: 'セレスト' } },
|
||||||
|
light: { ...mockSummon, id: 'summon-light', element: 6, name: { en: 'Luminiera', ja: 'シュヴァリエ' } }
|
||||||
|
};
|
||||||
89
src/stories/mocks/weapons.ts
Normal file
89
src/stories/mocks/weapons.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
import type { Weapon, GridWeapon } from '$lib/types/api/weapon';
|
||||||
|
|
||||||
|
/** Mock weapon data for Storybook stories */
|
||||||
|
export const mockWeapon: Weapon = {
|
||||||
|
id: 'weapon-1',
|
||||||
|
granblueId: '1040000000',
|
||||||
|
name: { en: 'Luminiera Sword Omega', ja: 'シュヴァリエソード・マグナ' },
|
||||||
|
element: 6, // Light
|
||||||
|
rarity: 3, // SSR
|
||||||
|
series: 1, // Omega
|
||||||
|
proficiency: 1, // Sword
|
||||||
|
uncap: { flb: true, ulb: true, transcendence: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockOpusWeapon: Weapon = {
|
||||||
|
id: 'weapon-opus',
|
||||||
|
granblueId: '1040900000',
|
||||||
|
name: { en: 'Cosmic Sword', ja: 'コスミックソード' },
|
||||||
|
element: 6,
|
||||||
|
rarity: 3,
|
||||||
|
series: 2, // Opus
|
||||||
|
proficiency: 1,
|
||||||
|
uncap: { flb: true, ulb: true, transcendence: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockDraconicWeapon: Weapon = {
|
||||||
|
id: 'weapon-draconic',
|
||||||
|
granblueId: '1040800000',
|
||||||
|
name: { en: 'Draconic Harp', ja: 'ドラゴニックハープ' },
|
||||||
|
element: 1, // Wind
|
||||||
|
rarity: 3,
|
||||||
|
series: 3, // Draconic
|
||||||
|
proficiency: 4, // Harp
|
||||||
|
uncap: { flb: true, ulb: true, transcendence: false }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockGridWeapon: GridWeapon = {
|
||||||
|
id: 'grid-weapon-1',
|
||||||
|
position: 0,
|
||||||
|
uncapLevel: 5,
|
||||||
|
transcendenceStep: 0,
|
||||||
|
mainhand: false,
|
||||||
|
weapon: mockWeapon,
|
||||||
|
awakening: null,
|
||||||
|
weaponKeys: [],
|
||||||
|
ax: []
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockMainhandWeapon: GridWeapon = {
|
||||||
|
id: 'grid-weapon-main',
|
||||||
|
position: -1,
|
||||||
|
uncapLevel: 6,
|
||||||
|
transcendenceStep: 5,
|
||||||
|
mainhand: true,
|
||||||
|
weapon: mockOpusWeapon,
|
||||||
|
awakening: { id: 'awk-1', type: { slug: 'attack', name: { en: 'Attack', ja: '攻撃' } } },
|
||||||
|
weaponKeys: [],
|
||||||
|
ax: []
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Weapons organized by element */
|
||||||
|
export const mockWeaponsByElement = {
|
||||||
|
wind: { ...mockWeapon, id: 'weapon-wind', element: 1, name: { en: 'Tiamat Bolt', ja: 'ティアマトボルト' } },
|
||||||
|
fire: {
|
||||||
|
...mockWeapon,
|
||||||
|
id: 'weapon-fire',
|
||||||
|
element: 2,
|
||||||
|
name: { en: 'Colossus Cane', ja: 'コロッサスケーン' }
|
||||||
|
},
|
||||||
|
water: {
|
||||||
|
...mockWeapon,
|
||||||
|
id: 'weapon-water',
|
||||||
|
element: 3,
|
||||||
|
name: { en: 'Leviathan Gaze', ja: 'リヴァイアサンゲイズ' }
|
||||||
|
},
|
||||||
|
earth: {
|
||||||
|
...mockWeapon,
|
||||||
|
id: 'weapon-earth',
|
||||||
|
element: 4,
|
||||||
|
name: { en: 'Yggdrasil Bow', ja: 'ユグドラシルボウ' }
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
...mockWeapon,
|
||||||
|
id: 'weapon-dark',
|
||||||
|
element: 5,
|
||||||
|
name: { en: 'Celeste Claw', ja: 'セレストクロー' }
|
||||||
|
},
|
||||||
|
light: { ...mockWeapon, id: 'weapon-light', element: 6, name: { en: 'Luminiera Sword', ja: 'シュヴァリエソード' } }
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue