Compare commits
10 Commits
53b9a8bf7a
...
9268e6c3cf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9268e6c3cf | ||
|
|
0b2eae33b1 | ||
|
|
3f9f6e3f93 | ||
|
|
45e3ef77c6 | ||
|
|
ef9d97dde0 | ||
|
|
5bbc19566d | ||
|
|
fc245407a1 | ||
|
|
30f5d01370 | ||
|
|
cb3d05b094 | ||
|
|
ac2eb6ba0b |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
.yarn
|
||||
|
||||
# Output
|
||||
.output
|
||||
@@ -22,6 +23,8 @@ Thumbs.db
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
# npm (use bun instead)
|
||||
package-lock.json
|
||||
|
||||
# Git worktrees
|
||||
.worktrees
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { StorybookConfig } from '@storybook/sveltekit';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
"stories": [
|
||||
"../src/**/*.mdx",
|
||||
"../src/**/*.stories.@(js|ts|svelte)"
|
||||
],
|
||||
"addons": [
|
||||
"@storybook/addon-svelte-csf",
|
||||
"@chromatic-com/storybook",
|
||||
"@storybook/addon-vitest",
|
||||
"@storybook/addon-a11y",
|
||||
"@storybook/addon-docs"
|
||||
],
|
||||
"framework": "@storybook/sveltekit"
|
||||
};
|
||||
export default config;
|
||||
3
.yarnrc.yml
Normal file
3
.yarnrc.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Disable Plug'n'Play, use node_modules
|
||||
nodeLinker: node-modules
|
||||
enableGlobalCache: false
|
||||
59
config/storybook/main.ts
Normal file
59
config/storybook/main.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { StorybookConfig } from '@storybook/svelte-vite';
|
||||
import {
|
||||
dirname,
|
||||
resolve,
|
||||
} from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import {
|
||||
loadConfigFromFile,
|
||||
mergeConfig,
|
||||
} from 'vite';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: [
|
||||
'../../src/**/*.mdx',
|
||||
'../../src/**/*.stories.@(js|ts|svelte)',
|
||||
],
|
||||
addons: [
|
||||
{
|
||||
name: '@storybook/addon-svelte-csf',
|
||||
options: {
|
||||
// Use modern template syntax for better performance
|
||||
legacyTemplate: false,
|
||||
},
|
||||
},
|
||||
'@chromatic-com/storybook',
|
||||
'@storybook/addon-vitest',
|
||||
'@storybook/addon-a11y',
|
||||
'@storybook/addon-docs',
|
||||
],
|
||||
framework: '@storybook/svelte-vite',
|
||||
async viteFinal(config) {
|
||||
// This attempts to find your actual vite.config.ts
|
||||
const { config: userConfig } = await loadConfigFromFile(
|
||||
{ command: 'serve', mode: 'development' },
|
||||
resolve(__dirname, '../../vite.config.ts'),
|
||||
) || {};
|
||||
|
||||
const mergedConfig = mergeConfig(config, {
|
||||
// Merge resolve/alias parts to maintain path aliases
|
||||
resolve: userConfig?.resolve || {},
|
||||
});
|
||||
|
||||
const { svelte } = await import('@sveltejs/vite-plugin-svelte');
|
||||
mergedConfig.plugins = mergedConfig.plugins || [];
|
||||
|
||||
// Add Svelte plugin to process @storybook's .svelte files
|
||||
// This prevents Vite from trying to parse them before compilation
|
||||
mergedConfig.plugins.unshift(svelte({
|
||||
include: [/node_modules\/@storybook\/.+\.svelte$/],
|
||||
}));
|
||||
|
||||
return mergedConfig;
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,3 +1,4 @@
|
||||
import '../../src/app/styles/app.css';
|
||||
import type { Preview } from '@storybook/sveltekit'
|
||||
|
||||
const preview: Preview = {
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect, afterEach } from 'vitest';
|
||||
import { afterEach } from 'vitest';
|
||||
import { cleanup } from '@testing-library/svelte';
|
||||
|
||||
afterEach(() => {
|
||||
4370
package-lock.json
generated
4370
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "allmywork",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.11.0",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -17,43 +18,46 @@
|
||||
"test": "vitest",
|
||||
"test:watch": "vitest --watch",
|
||||
"test:e2e": "playwright test",
|
||||
"test:all": "bun run test && bun run test:e2e",
|
||||
"test:all": "yarn test && yarn test:e2e",
|
||||
"test:coverage": "vitest --coverage",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
"storybook": "storybook dev -p 6006 --config-dir config/storybook",
|
||||
"build-storybook": "storybook build --config-dir config/storybook"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.5",
|
||||
"@chromatic-com/storybook": "4.1.3",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@storybook/addon-a11y": "10.1.11",
|
||||
"@storybook/addon-docs": "10.1.11",
|
||||
"@storybook/addon-svelte-csf": "5.0.10",
|
||||
"@storybook/addon-vitest": "10.1.11",
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"@sveltejs/kit": "^2.50.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||
"@tailwindcss/postcss": "^4.2.1",
|
||||
"@testing-library/svelte": "^5.3.1",
|
||||
"@types/node": "^25.3.3",
|
||||
"@vitest/browser-playwright": "^4.0.18",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"@vitest/ui": "^4.0.18",
|
||||
"autoprefixer": "^10.4.27",
|
||||
"jsdom": "^28.1.0",
|
||||
"playwright": "^1.58.2",
|
||||
"postcss": "^8.5.8",
|
||||
"svelte": "^5.51.0",
|
||||
"storybook": "10.1.11",
|
||||
"svelte": "5.46.1",
|
||||
"svelte-adapter-bun": "^1.0.1",
|
||||
"svelte-check": "^4.4.2",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vitest": "^4.0.18",
|
||||
"storybook": "^10.2.16",
|
||||
"@storybook/sveltekit": "^10.2.16",
|
||||
"@storybook/addon-svelte-csf": "^5.0.11",
|
||||
"@chromatic-com/storybook": "^5.0.1",
|
||||
"@storybook/addon-vitest": "^10.2.16",
|
||||
"@storybook/addon-a11y": "^10.2.16",
|
||||
"@storybook/addon-docs": "^10.2.16",
|
||||
"playwright": "^1.58.2",
|
||||
"@vitest/browser-playwright": "^4.0.18"
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/builder-vite": "10.1.11",
|
||||
"@storybook/csf-plugin": "10.1.11",
|
||||
"@storybook/svelte": "10.1.11",
|
||||
"@storybook/svelte-vite": "10.1.11",
|
||||
"clsx": "^2.1.1",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./src/tests/e2e",
|
||||
testDir: "./src",
|
||||
testMatch: "**/*.e2e.{ts,js}",
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
@@ -26,7 +27,7 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
webServer: {
|
||||
command: "npm run build && npm run preview",
|
||||
command: "bun run build && bun run preview",
|
||||
port: 4173,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import '../../app.css';
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
import favicon from '$shared/assets/favicon.svg';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
55
src/shared/ui/Badge/Badge.stories.svelte
Normal file
55
src/shared/ui/Badge/Badge.stories.svelte
Normal file
@@ -0,0 +1,55 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import Badge from './Badge.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Shared/Badge',
|
||||
component: Badge,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: { description: { component: 'Brutalist inline label. Uppercase, 3px border, zero radius.' } },
|
||||
},
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['default', 'primary', 'secondary', 'outline'],
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template(args)}
|
||||
<Badge {...args}>Tag</Badge>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Primary" args={{ variant: 'primary' }}>
|
||||
{#snippet template(args)}
|
||||
<Badge {...args}>Primary</Badge>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Secondary" args={{ variant: 'secondary' }}>
|
||||
{#snippet template(args)}
|
||||
<Badge {...args}>Secondary</Badge>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Outline" args={{ variant: 'outline' }}>
|
||||
{#snippet template(args)}
|
||||
<Badge {...args}>Outline</Badge>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="All variants">
|
||||
{#snippet template()}
|
||||
<div class="flex gap-3 flex-wrap">
|
||||
<Badge variant="default">Default</Badge>
|
||||
<Badge variant="primary">Primary</Badge>
|
||||
<Badge variant="secondary">Secondary</Badge>
|
||||
<Badge variant="outline">Outline</Badge>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
37
src/shared/ui/Badge/Badge.svelte
Normal file
37
src/shared/ui/Badge/Badge.svelte
Normal file
@@ -0,0 +1,37 @@
|
||||
<!--
|
||||
Component: Badge
|
||||
Inline label for tags and status indicators. Brutalist border, uppercase, no radius.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { cn } from '$shared/lib/cn';
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { BadgeVariant } from './types';
|
||||
|
||||
interface Props {
|
||||
/** Visual style variant @default 'default' */
|
||||
variant?: BadgeVariant;
|
||||
children?: Snippet;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { variant = 'default', children, class: className }: Props = $props();
|
||||
|
||||
const variantStyles: Record<BadgeVariant, string> = {
|
||||
default: 'brutal-border bg-carbon-black text-ochre-clay',
|
||||
primary: 'brutal-border bg-burnt-oxide text-ochre-clay',
|
||||
secondary: 'brutal-border bg-slate-indigo text-ochre-clay',
|
||||
outline: 'brutal-border bg-transparent text-carbon-black',
|
||||
};
|
||||
|
||||
const classes = $derived(cn(
|
||||
'inline-block px-3 py-1 text-xs uppercase tracking-wider',
|
||||
variantStyles[variant],
|
||||
className,
|
||||
));
|
||||
</script>
|
||||
|
||||
<span class={classes}>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</span>
|
||||
20
src/shared/ui/Badge/Badge.test.ts
Normal file
20
src/shared/ui/Badge/Badge.test.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { render } from '@testing-library/svelte';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import Badge from './Badge.svelte';
|
||||
|
||||
describe('Badge', () => {
|
||||
it('renders children text', () => {
|
||||
const { getByText } = render(Badge, { props: { children: 'hello' } });
|
||||
expect(getByText('hello')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('applies default variant class', () => {
|
||||
const { container } = render(Badge, { props: { variant: 'default' } });
|
||||
expect(container.querySelector('span')?.className).toContain('bg-carbon-black');
|
||||
});
|
||||
|
||||
it('applies primary variant class', () => {
|
||||
const { container } = render(Badge, { props: { variant: 'primary' } });
|
||||
expect(container.querySelector('span')?.className).toContain('bg-burnt-oxide');
|
||||
});
|
||||
});
|
||||
2
src/shared/ui/Badge/index.ts
Normal file
2
src/shared/ui/Badge/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as Badge } from './Badge.svelte';
|
||||
export type { BadgeVariant } from './types';
|
||||
1
src/shared/ui/Badge/types.ts
Normal file
1
src/shared/ui/Badge/types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type BadgeVariant = 'default' | 'primary' | 'secondary' | 'outline';
|
||||
@@ -1,40 +0,0 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
type?: 'button' | 'submit' | 'reset';
|
||||
variant?: 'primary' | 'secondary' | 'danger';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
disabled?: boolean;
|
||||
onclick?: () => void;
|
||||
children: any;
|
||||
}
|
||||
|
||||
let {
|
||||
type = 'button',
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
disabled = false,
|
||||
onclick,
|
||||
children
|
||||
}: Props = $props();
|
||||
|
||||
const variants = {
|
||||
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
||||
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
|
||||
danger: 'bg-red-600 text-white hover:bg-red-700'
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
sm: 'px-3 py-1.5 text-sm',
|
||||
md: 'px-4 py-2 text-base',
|
||||
lg: 'px-6 py-3 text-lg'
|
||||
};
|
||||
</script>
|
||||
|
||||
<button
|
||||
{type}
|
||||
{disabled}
|
||||
onclick={onclick}
|
||||
class="rounded-md font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed {variants[variant]} {sizes[size]}"
|
||||
>
|
||||
{@render children()}
|
||||
</button>
|
||||
58
src/shared/ui/Button/Button.stories.svelte
Normal file
58
src/shared/ui/Button/Button.stories.svelte
Normal file
@@ -0,0 +1,58 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import Button from './Button.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Shared/Button',
|
||||
component: Button,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: { description: { component: 'Brutalist CTA button. Hard shadow, uppercase, hover translates 2px.' } },
|
||||
},
|
||||
argTypes: {
|
||||
variant: { control: 'select', options: ['primary', 'secondary', 'outline', 'ghost'] },
|
||||
size: { control: 'select', options: ['sm', 'md', 'lg'] },
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Primary" args={{ variant: 'primary', size: 'md' }}>
|
||||
{#snippet template(args)}
|
||||
<Button {...args}>Primary</Button>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Secondary" args={{ variant: 'secondary', size: 'md' }}>
|
||||
{#snippet template(args)}
|
||||
<Button {...args}>Secondary</Button>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Outline" args={{ variant: 'outline', size: 'md' }}>
|
||||
{#snippet template(args)}
|
||||
<Button {...args}>Outline</Button>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Ghost" args={{ variant: 'ghost', size: 'md' }}>
|
||||
{#snippet template(args)}
|
||||
<Button {...args}>Ghost</Button>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="All sizes">
|
||||
{#snippet template()}
|
||||
<div class="flex gap-4 items-center">
|
||||
<Button size="sm">Small</Button>
|
||||
<Button size="md">Medium</Button>
|
||||
<Button size="lg">Large</Button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Disabled">
|
||||
{#snippet template()}
|
||||
<Button disabled>Disabled</Button>
|
||||
{/snippet}
|
||||
</Story>
|
||||
58
src/shared/ui/Button/Button.svelte
Normal file
58
src/shared/ui/Button/Button.svelte
Normal file
@@ -0,0 +1,58 @@
|
||||
<!--
|
||||
Component: Button
|
||||
Brutalist button. 3px border, hard shadow on hover, uppercase, zero radius.
|
||||
Hover: translates 2px down-right with reduced shadow for press feel.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { cn } from '$shared/lib/cn';
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { HTMLButtonAttributes } from 'svelte/elements';
|
||||
import type { ButtonSize, ButtonVariant } from './types';
|
||||
|
||||
interface Props extends HTMLButtonAttributes {
|
||||
/** Visual style @default 'primary' */
|
||||
variant?: ButtonVariant;
|
||||
/** Size preset @default 'md' */
|
||||
size?: ButtonSize;
|
||||
children?: Snippet;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
children,
|
||||
class: className,
|
||||
type = 'button',
|
||||
disabled,
|
||||
...rest
|
||||
}: Props = $props();
|
||||
|
||||
const base =
|
||||
'brutal-border transition-all duration-200 ' +
|
||||
'hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[6px_6px_0_var(--carbon-black)] ' +
|
||||
'active:translate-x-[4px] active:translate-y-[4px] active:shadow-[4px_4px_0_var(--carbon-black)] ' +
|
||||
'uppercase tracking-wider inline-flex items-center justify-center ' +
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none';
|
||||
|
||||
const variantStyles: Record<ButtonVariant, string> = {
|
||||
primary: 'brutal-shadow bg-burnt-oxide text-ochre-clay',
|
||||
secondary: 'brutal-shadow bg-slate-indigo text-ochre-clay',
|
||||
outline: 'brutal-shadow bg-transparent text-carbon-black',
|
||||
ghost: 'bg-ochre-clay text-carbon-black shadow-none hover:shadow-[6px_6px_0_var(--carbon-black)]',
|
||||
};
|
||||
|
||||
const sizeStyles: Record<ButtonSize, string> = {
|
||||
sm: 'px-4 py-2 text-sm',
|
||||
md: 'px-6 py-3 text-base',
|
||||
lg: 'px-8 py-4 text-lg',
|
||||
};
|
||||
|
||||
const classes = $derived(cn(base, variantStyles[variant], sizeStyles[size], className));
|
||||
</script>
|
||||
|
||||
<button {type} {disabled} class={classes} {...rest}>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</button>
|
||||
20
src/shared/ui/Button/Button.test.ts
Normal file
20
src/shared/ui/Button/Button.test.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { render } from '@testing-library/svelte';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import Button from './Button.svelte';
|
||||
|
||||
describe('Button', () => {
|
||||
it('renders children', () => {
|
||||
const { getByText } = render(Button, { props: { children: () => 'Click me' } });
|
||||
expect(getByText('Click me')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('is disabled when disabled prop is true', () => {
|
||||
const { container } = render(Button, { props: { disabled: true } });
|
||||
expect(container.querySelector('button')?.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('applies primary variant styles', () => {
|
||||
const { container } = render(Button, { props: { variant: 'primary' } });
|
||||
expect(container.querySelector('button')?.className).toContain('bg-burnt-oxide');
|
||||
});
|
||||
});
|
||||
2
src/shared/ui/Button/index.ts
Normal file
2
src/shared/ui/Button/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as Button } from './Button.svelte';
|
||||
export type { ButtonVariant, ButtonSize } from './types';
|
||||
2
src/shared/ui/Button/types.ts
Normal file
2
src/shared/ui/Button/types.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost';
|
||||
export type ButtonSize = 'sm' | 'md' | 'lg';
|
||||
45
src/shared/ui/Card/Card.stories.svelte
Normal file
45
src/shared/ui/Card/Card.stories.svelte
Normal file
@@ -0,0 +1,45 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import Card from './Card.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Shared/Card',
|
||||
component: Card,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'padded',
|
||||
docs: { description: { component: 'Brutalist card. 3px border, hard shadow, three background presets.' } },
|
||||
},
|
||||
argTypes: {
|
||||
background: { control: 'select', options: ['ochre', 'slate', 'white'] },
|
||||
noPadding: { control: 'boolean' },
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Ochre (default)" args={{ background: 'ochre' }}>
|
||||
{#snippet template(args)}
|
||||
<Card {...args}>Card content goes here</Card>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Slate" args={{ background: 'slate' }}>
|
||||
{#snippet template(args)}
|
||||
<Card {...args}>Card content goes here</Card>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="White" args={{ background: 'white' }}>
|
||||
{#snippet template(args)}
|
||||
<Card {...args}>Card content goes here</Card>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="No Padding" args={{ noPadding: true }}>
|
||||
{#snippet template(args)}
|
||||
<Card {...args}>
|
||||
<div class="p-4 brutal-border-bottom">Header</div>
|
||||
<div class="p-4">Body</div>
|
||||
</Card>
|
||||
{/snippet}
|
||||
</Story>
|
||||
40
src/shared/ui/Card/Card.svelte
Normal file
40
src/shared/ui/Card/Card.svelte
Normal file
@@ -0,0 +1,40 @@
|
||||
<!--
|
||||
Component: Card
|
||||
Brutalist content container. 3px border, 8px hard shadow, zero radius.
|
||||
Backgrounds: ochre (default), slate (dark), white.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { cn } from '$shared/lib/cn';
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { CardBackground } from './types';
|
||||
|
||||
interface Props {
|
||||
/** Background color preset @default 'ochre' */
|
||||
background?: CardBackground;
|
||||
/** Remove default padding @default false */
|
||||
noPadding?: boolean;
|
||||
children?: Snippet;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { background = 'ochre', noPadding = false, children, class: className }: Props = $props();
|
||||
|
||||
const backgroundStyles: Record<CardBackground, string> = {
|
||||
ochre: 'bg-ochre-clay text-carbon-black',
|
||||
slate: 'bg-slate-indigo text-ochre-clay',
|
||||
white: 'bg-white text-carbon-black',
|
||||
};
|
||||
|
||||
const classes = $derived(cn(
|
||||
'brutal-border brutal-shadow',
|
||||
backgroundStyles[background],
|
||||
!noPadding && 'p-6 md:p-8',
|
||||
className,
|
||||
));
|
||||
</script>
|
||||
|
||||
<div class={classes}>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</div>
|
||||
2
src/shared/ui/Card/index.ts
Normal file
2
src/shared/ui/Card/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as Card } from './Card.svelte';
|
||||
export type { CardBackground } from './types';
|
||||
1
src/shared/ui/Card/types.ts
Normal file
1
src/shared/ui/Card/types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type CardBackground = 'ochre' | 'slate' | 'white';
|
||||
@@ -1,26 +0,0 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
type?: 'text' | 'email' | 'password' | 'number';
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
value?: string;
|
||||
oninput?: (e: Event) => void;
|
||||
}
|
||||
|
||||
let {
|
||||
type = 'text',
|
||||
placeholder = '',
|
||||
disabled = false,
|
||||
value = '',
|
||||
oninput
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<input
|
||||
{type}
|
||||
{placeholder}
|
||||
{disabled}
|
||||
{value}
|
||||
oninput={oninput}
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
/>
|
||||
51
src/shared/ui/Input/Input.stories.svelte
Normal file
51
src/shared/ui/Input/Input.stories.svelte
Normal file
@@ -0,0 +1,51 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import Input from './Input.svelte';
|
||||
import Textarea from './Textarea.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Shared/Input',
|
||||
component: Input,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'padded',
|
||||
docs: { description: { component: 'Brutalist text input with optional label and error state.' } },
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Default">
|
||||
{#snippet template()}
|
||||
<Input placeholder="Enter text..." />
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="With Label">
|
||||
{#snippet template()}
|
||||
<Input label="Email address" type="email" placeholder="you@example.com" />
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="With Error">
|
||||
{#snippet template()}
|
||||
<Input label="Email address" type="email" value="bad-email" error="Please enter a valid email address." />
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Disabled">
|
||||
{#snippet template()}
|
||||
<Input label="Read-only field" disabled value="Cannot edit this" />
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Textarea">
|
||||
{#snippet template()}
|
||||
<Textarea label="Message" placeholder="Write your message..." rows={5} />
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Textarea with Error">
|
||||
{#snippet template()}
|
||||
<Textarea label="Message" error="Message is required." />
|
||||
{/snippet}
|
||||
</Story>
|
||||
39
src/shared/ui/Input/Input.svelte
Normal file
39
src/shared/ui/Input/Input.svelte
Normal file
@@ -0,0 +1,39 @@
|
||||
<!--
|
||||
Component: Input
|
||||
Brutalist text input. 3px border, white background, burnt-oxide focus ring.
|
||||
Optional label (uppercase) and inline error message.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { cn } from '$shared/lib/cn';
|
||||
import type { HTMLInputAttributes } from 'svelte/elements';
|
||||
|
||||
interface Props extends HTMLInputAttributes {
|
||||
/** Uppercase label rendered above the input */
|
||||
label?: string;
|
||||
/** Inline error message rendered below the input */
|
||||
error?: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { label, error, class: className, ...rest }: Props = $props();
|
||||
|
||||
const inputClasses = $derived(cn(
|
||||
'brutal-border bg-white px-4 py-3 text-carbon-black w-full',
|
||||
'focus:outline-none focus:ring-2 focus:ring-burnt-oxide focus:ring-offset-2 focus:ring-offset-ochre-clay',
|
||||
'transition-all duration-150',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
className,
|
||||
));
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
{#if label}
|
||||
<label class="text-carbon-black">{label}</label>
|
||||
{/if}
|
||||
|
||||
<input class={inputClasses} {...rest} />
|
||||
|
||||
{#if error}
|
||||
<span class="text-sm text-burnt-oxide">{error}</span>
|
||||
{/if}
|
||||
</div>
|
||||
38
src/shared/ui/Input/Textarea.svelte
Normal file
38
src/shared/ui/Input/Textarea.svelte
Normal file
@@ -0,0 +1,38 @@
|
||||
<!--
|
||||
Component: Textarea
|
||||
Brutalist multi-line input. Same styling as Input but <textarea>.
|
||||
Non-resizable by default (matches prototype).
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { cn } from '$shared/lib/cn';
|
||||
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
||||
|
||||
interface Props extends HTMLTextareaAttributes {
|
||||
label?: string;
|
||||
error?: string;
|
||||
rows?: number;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { label, error, rows = 4, class: className, ...rest }: Props = $props();
|
||||
|
||||
const textareaClasses = $derived(cn(
|
||||
'brutal-border bg-white px-4 py-3 text-carbon-black w-full resize-none',
|
||||
'focus:outline-none focus:ring-2 focus:ring-burnt-oxide focus:ring-offset-2 focus:ring-offset-ochre-clay',
|
||||
'transition-all duration-150',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
className,
|
||||
));
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
{#if label}
|
||||
<label class="text-carbon-black">{label}</label>
|
||||
{/if}
|
||||
|
||||
<textarea {rows} class={textareaClasses} {...rest}></textarea>
|
||||
|
||||
{#if error}
|
||||
<span class="text-sm text-burnt-oxide">{error}</span>
|
||||
{/if}
|
||||
</div>
|
||||
3
src/shared/ui/Input/index.ts
Normal file
3
src/shared/ui/Input/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as Input } from './Input.svelte';
|
||||
export { default as Textarea } from './Textarea.svelte';
|
||||
export type { InputSize } from './types';
|
||||
1
src/shared/ui/Input/types.ts
Normal file
1
src/shared/ui/Input/types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type InputSize = 'sm' | 'md' | 'lg';
|
||||
32
src/shared/ui/Section/Container.svelte
Normal file
32
src/shared/ui/Section/Container.svelte
Normal file
@@ -0,0 +1,32 @@
|
||||
<!--
|
||||
Component: Container
|
||||
Centered max-width wrapper. Three size presets for responsive layouts up to 4K.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { cn } from '$shared/lib/cn';
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { ContainerSize } from './types';
|
||||
|
||||
interface Props {
|
||||
/** Max-width preset @default 'default' */
|
||||
size?: ContainerSize;
|
||||
children?: Snippet;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { size = 'default', children, class: className }: Props = $props();
|
||||
|
||||
const sizeStyles: Record<ContainerSize, string> = {
|
||||
'default': 'max-w-7xl',
|
||||
'wide': 'max-w-[1920px]',
|
||||
'ultra-wide': 'max-w-[2560px]',
|
||||
};
|
||||
|
||||
const classes = $derived(cn('mx-auto px-6 md:px-12 lg:px-16', sizeStyles[size], className));
|
||||
</script>
|
||||
|
||||
<div class={classes}>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</div>
|
||||
42
src/shared/ui/Section/Section.stories.svelte
Normal file
42
src/shared/ui/Section/Section.stories.svelte
Normal file
@@ -0,0 +1,42 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import Container from './Container.svelte';
|
||||
import Section from './Section.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Shared/Section',
|
||||
component: Section,
|
||||
tags: ['autodocs'],
|
||||
parameters: { layout: 'fullscreen' },
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Ochre (default)">
|
||||
{#snippet template()}
|
||||
<Section>
|
||||
<Container>
|
||||
<p class="py-16">Section content</p>
|
||||
</Container>
|
||||
</Section>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Slate">
|
||||
{#snippet template()}
|
||||
<Section background="slate">
|
||||
<Container>
|
||||
<p class="py-16">Section content on slate</p>
|
||||
</Container>
|
||||
</Section>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Bordered">
|
||||
{#snippet template()}
|
||||
<Section bordered>
|
||||
<Container>
|
||||
<p class="py-16">Section with brutal top and bottom borders</p>
|
||||
</Container>
|
||||
</Section>
|
||||
{/snippet}
|
||||
</Story>
|
||||
37
src/shared/ui/Section/Section.svelte
Normal file
37
src/shared/ui/Section/Section.svelte
Normal file
@@ -0,0 +1,37 @@
|
||||
<!--
|
||||
Component: Section
|
||||
Full-width page section. Controls background and optional top/bottom brutal borders.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { cn } from '$shared/lib/cn';
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { SectionBackground } from './types';
|
||||
|
||||
interface Props {
|
||||
background?: SectionBackground;
|
||||
/** Add brutal border on top and bottom @default false */
|
||||
bordered?: boolean;
|
||||
children?: Snippet;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { background = 'ochre', bordered = false, children, class: className }: Props = $props();
|
||||
|
||||
const backgroundStyles: Record<SectionBackground, string> = {
|
||||
ochre: 'bg-ochre-clay text-carbon-black',
|
||||
slate: 'bg-slate-indigo text-ochre-clay',
|
||||
white: 'bg-white text-carbon-black',
|
||||
};
|
||||
|
||||
const classes = $derived(cn(
|
||||
backgroundStyles[background],
|
||||
bordered && 'brutal-border-top brutal-border-bottom',
|
||||
className,
|
||||
));
|
||||
</script>
|
||||
|
||||
<section class={classes}>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</section>
|
||||
3
src/shared/ui/Section/index.ts
Normal file
3
src/shared/ui/Section/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as Section } from './Section.svelte';
|
||||
export { default as Container } from './Container.svelte';
|
||||
export type { SectionBackground, ContainerSize } from './types';
|
||||
2
src/shared/ui/Section/types.ts
Normal file
2
src/shared/ui/Section/types.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export type SectionBackground = 'ochre' | 'slate' | 'white';
|
||||
export type ContainerSize = 'default' | 'wide' | 'ultra-wide';
|
||||
45
src/shared/ui/TechStackBrick/TechStackBrick.stories.svelte
Normal file
45
src/shared/ui/TechStackBrick/TechStackBrick.stories.svelte
Normal file
@@ -0,0 +1,45 @@
|
||||
<script module>
|
||||
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||
import TechStackBrick from './TechStackBrick.svelte';
|
||||
import TechStackGrid from './TechStackGrid.svelte';
|
||||
|
||||
const { Story } = defineMeta({
|
||||
title: 'Shared/TechStackBrick',
|
||||
component: TechStackBrick,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'padded',
|
||||
docs: { description: { component: 'Brutalist skill pills. Collapse shadow on hover for pressed feel.' } },
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Single brick">
|
||||
{#snippet template()}
|
||||
<TechStackBrick name="SvelteKit" />
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Grid of skills">
|
||||
{#snippet template()}
|
||||
<TechStackGrid
|
||||
skills={[
|
||||
"Svelte", "SvelteKit", "TypeScript",
|
||||
"Tailwind CSS", "Bun", "Docker"
|
||||
]}
|
||||
/>
|
||||
{/snippet}
|
||||
</Story>
|
||||
|
||||
<Story name="Large grid">
|
||||
{#snippet template()}
|
||||
<TechStackGrid
|
||||
skills={[
|
||||
"Svelte", "SvelteKit", "TypeScript",
|
||||
"Tailwind CSS", "Bun", "Docker",
|
||||
"Vite", "Vitest", "Playwright",
|
||||
"Biome", "Git", "Linux"
|
||||
]}
|
||||
/>
|
||||
{/snippet}
|
||||
</Story>
|
||||
25
src/shared/ui/TechStackBrick/TechStackBrick.svelte
Normal file
25
src/shared/ui/TechStackBrick/TechStackBrick.svelte
Normal file
@@ -0,0 +1,25 @@
|
||||
<!--
|
||||
Component: TechStackBrick
|
||||
Single skill pill. Brutal border, white bg, hard shadow that collapses on hover (lifted press feel).
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { cn } from '$shared/lib/cn';
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { name, class: className }: Props = $props();
|
||||
|
||||
const classes = $derived(cn(
|
||||
'brutal-border brutal-shadow bg-white px-4 py-3 text-center',
|
||||
'transition-all duration-200',
|
||||
'hover:shadow-none hover:translate-x-[2px] hover:translate-y-[2px]',
|
||||
className,
|
||||
));
|
||||
</script>
|
||||
|
||||
<div class={classes}>
|
||||
<span class="text-sm uppercase tracking-wide">{name}</span>
|
||||
</div>
|
||||
26
src/shared/ui/TechStackBrick/TechStackGrid.svelte
Normal file
26
src/shared/ui/TechStackBrick/TechStackGrid.svelte
Normal file
@@ -0,0 +1,26 @@
|
||||
<!--
|
||||
Component: TechStackGrid
|
||||
Responsive grid of TechStackBrick items. Adjusts columns from 2 to 6 across breakpoints.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { cn } from '$shared/lib/cn';
|
||||
import TechStackBrick from './TechStackBrick.svelte';
|
||||
|
||||
interface Props {
|
||||
skills: string[];
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { skills, class: className }: Props = $props();
|
||||
|
||||
const classes = $derived(cn(
|
||||
'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4',
|
||||
className,
|
||||
));
|
||||
</script>
|
||||
|
||||
<div class={classes}>
|
||||
{#each skills as skill (skill)}
|
||||
<TechStackBrick name={skill} />
|
||||
{/each}
|
||||
</div>
|
||||
2
src/shared/ui/TechStackBrick/index.ts
Normal file
2
src/shared/ui/TechStackBrick/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as TechStackBrick } from './TechStackBrick.svelte';
|
||||
export { default as TechStackGrid } from './TechStackGrid.svelte';
|
||||
@@ -1,2 +1,6 @@
|
||||
export { default as Button } from './Button.svelte';
|
||||
export { default as Input } from './Input.svelte';
|
||||
export * from './Badge';
|
||||
export * from './Button';
|
||||
export * from './Card';
|
||||
export * from './Section';
|
||||
export * from './Input';
|
||||
export * from './TechStackBrick';
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('Basic tests', () => {
|
||||
it('should add numbers correctly', () => {
|
||||
expect(1 + 1).toBe(2);
|
||||
});
|
||||
|
||||
it('should subtract numbers correctly', () => {
|
||||
expect(5 - 3).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('homepage loads correctly', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page).toHaveTitle(/allmywork/);
|
||||
});
|
||||
@@ -13,7 +13,7 @@ const config = {
|
||||
routes: "src/app/routes",
|
||||
},
|
||||
alias: {
|
||||
$lib: path.resolve("src/lib"),
|
||||
$lib: path.resolve("src/shared/lib"),
|
||||
$shared: path.resolve("src/shared"),
|
||||
$pages: path.resolve("src/pages"),
|
||||
$features: path.resolve("src/features"),
|
||||
|
||||
@@ -6,7 +6,13 @@ export default defineConfig({
|
||||
plugins: [sveltekit()],
|
||||
resolve: {
|
||||
alias: {
|
||||
$lib: path.resolve("./src/lib"),
|
||||
$lib: path.resolve("./src/shared/lib"),
|
||||
$shared: path.resolve("./src/shared"),
|
||||
$pages: path.resolve("./src/pages"),
|
||||
$features: path.resolve("./src/features"),
|
||||
$entities: path.resolve("./src/entities"),
|
||||
$widgets: path.resolve("./src/widgets"),
|
||||
"$/*": "./src/*",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
103
vitest.config.ts
103
vitest.config.ts
@@ -1,52 +1,63 @@
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import { playwright } from "@vitest/browser-playwright";
|
||||
import { defineConfig } from "vitest/config";
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
|
||||
import { playwright } from '@vitest/browser-playwright';
|
||||
const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const dirname =
|
||||
typeof __dirname !== "undefined"
|
||||
? __dirname
|
||||
: path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
|
||||
export default defineConfig({
|
||||
plugins: [svelte({
|
||||
hot: !process.env.VITEST
|
||||
})],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
setupFiles: ["./src/tests/setup.ts"],
|
||||
include: ["src/**/*.{test,spec}.{js,ts}"],
|
||||
exclude: ["src/tests/e2e/**/*.{test,spec}.{js,ts}"],
|
||||
projects: [{
|
||||
extends: true,
|
||||
plugins: [
|
||||
// The plugin will run tests for the stories defined in your Storybook config
|
||||
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
|
||||
storybookTest({
|
||||
configDir: path.join(dirname, '.storybook')
|
||||
})],
|
||||
test: {
|
||||
name: 'storybook',
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
provider: playwright({}),
|
||||
instances: [{
|
||||
browser: 'chromium'
|
||||
}]
|
||||
},
|
||||
setupFiles: ['.storybook/vitest.setup.ts']
|
||||
}
|
||||
}]
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
$lib: path.resolve("./src/lib"),
|
||||
$shared: path.resolve("./src/shared"),
|
||||
$pages: path.resolve("./src/pages"),
|
||||
$features: path.resolve("./src/features"),
|
||||
$entities: path.resolve("./src/entities"),
|
||||
$widgets: path.resolve("./src/widgets")
|
||||
}
|
||||
}
|
||||
});
|
||||
plugins: [
|
||||
svelte({
|
||||
hot: !process.env.VITEST,
|
||||
}),
|
||||
],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
setupFiles: ["./config/vitest/setup.ts"],
|
||||
include: ["src/**/*.test.{js,ts}"],
|
||||
exclude: ["src/**/*.e2e.{js,ts}"],
|
||||
projects: [
|
||||
{
|
||||
extends: true,
|
||||
plugins: [
|
||||
// The plugin will run tests for the stories defined in your Storybook config
|
||||
// See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
|
||||
storybookTest({
|
||||
configDir: path.join(dirname, "config/storybook"),
|
||||
}),
|
||||
],
|
||||
test: {
|
||||
include: ["src/**/*.stories.@(js|ts|svelte)"],
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
provider: playwright({}),
|
||||
instances: [
|
||||
{
|
||||
browser: "chromium",
|
||||
},
|
||||
],
|
||||
},
|
||||
setupFiles: ["./config/storybook/vitest.setup.ts"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
$lib: path.resolve("./src/shared/lib"),
|
||||
$shared: path.resolve("./src/shared"),
|
||||
$pages: path.resolve("./src/pages"),
|
||||
$features: path.resolve("./src/features"),
|
||||
$entities: path.resolve("./src/entities"),
|
||||
$widgets: path.resolve("./src/widgets"),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
1
vitest.shims.d.ts
vendored
Normal file
1
vitest.shims.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="@vitest/browser-playwright" />
|
||||
@@ -3,7 +3,7 @@ import { fileURLToPath } from "node:url";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
const dirname =
|
||||
const _dirname =
|
||||
typeof __dirname !== "undefined"
|
||||
? __dirname
|
||||
: path.dirname(fileURLToPath(import.meta.url));
|
||||
@@ -17,13 +17,13 @@ export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
setupFiles: ["./src/tests/setup.ts"],
|
||||
include: ["src/**/*.{test,spec}.{js,ts}"],
|
||||
exclude: ["src/tests/e2e/**/*.{test,spec}.{js,ts}"],
|
||||
setupFiles: ["./config/vitest/setup.ts"],
|
||||
include: ["src/**/*.test.{js,ts}"],
|
||||
exclude: ["src/**/*.e2e.{js,ts}"],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
$lib: path.resolve("./src/lib"),
|
||||
$lib: path.resolve("./src/shared/lib"),
|
||||
$shared: path.resolve("./src/shared"),
|
||||
$pages: path.resolve("./src/pages"),
|
||||
$features: path.resolve("./src/features"),
|
||||
|
||||
Reference in New Issue
Block a user