feat(shared/ui): add Badge component with brutalist variants and Storybook story
This commit is contained in:
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';
|
||||
Reference in New Issue
Block a user