feat(Label): component redesign with complete storybook coverage

This commit is contained in:
Ilia Mashkov
2026-02-24 17:59:18 +03:00
parent d36ab3c993
commit 12d57c59c1
2 changed files with 257 additions and 29 deletions

View File

@@ -0,0 +1,213 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Label from './Label.svelte';
const { Story } = defineMeta({
title: 'Shared/Label',
component: Label,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component: 'Inline monospace label. The base primitive for all micrographic text.',
},
story: { inline: false },
},
layout: 'centered',
},
argTypes: {
variant: {
control: 'select',
options: ['default', 'accent', 'muted', 'success', 'warning', 'error'],
defaultValue: 'default',
},
size: {
control: 'select',
options: ['xs', 'sm', 'md'],
defaultValue: 'sm',
},
uppercase: {
control: 'boolean',
defaultValue: true,
},
bold: {
control: 'boolean',
defaultValue: false,
},
iconPosition: {
control: 'select',
options: ['left', 'right'],
defaultValue: 'left',
},
},
});
</script>
<script lang="ts">
import AlertTriangleIcon from '@lucide/svelte/icons/alert-triangle';
import CheckIcon from '@lucide/svelte/icons/check';
import CircleIcon from '@lucide/svelte/icons/circle';
</script>
<Story
name="Default/Basic"
parameters={{ docs: { description: { story: 'Standard label with default styling' } } }}
>
{#snippet template(args)}
<Label {...args}>Default Label</Label>
{/snippet}
</Story>
<Story name="All sizes">
{#snippet template()}
<div class="flex flex-col gap-4">
<Label size="xs">Size XS Label</Label>
<Label size="sm">Size SM Label</Label>
<Label size="md">Size MD Label</Label>
</div>
{/snippet}
</Story>
<Story
name="Default variant"
args={{ variant: 'default', size: 'sm' }}
>
{#snippet template(args)}
<Label {...args}>Default Label</Label>
{/snippet}
</Story>
<Story
name="Accent variant"
args={{ variant: 'accent', size: 'sm' }}
>
{#snippet template(args)}
<Label {...args}>Accent Label</Label>
{/snippet}
</Story>
<Story
name="Muted variant"
args={{ variant: 'muted', size: 'sm' }}
>
{#snippet template(args)}
<Label {...args}>Muted Label</Label>
{/snippet}
</Story>
<Story
name="Success variant"
args={{ variant: 'success', size: 'sm' }}
>
{#snippet template(args)}
<Label {...args}>Success Label</Label>
{/snippet}
</Story>
<Story
name="Warning variant"
args={{ variant: 'warning', size: 'sm' }}
>
{#snippet template(args)}
<Label {...args}>Warning Label</Label>
{/snippet}
</Story>
<Story
name="Error variant"
args={{ variant: 'error', size: 'sm' }}
>
{#snippet template(args)}
<Label {...args}>Error Label</Label>
{/snippet}
</Story>
<Story name="All variants">
{#snippet template()}
<div class="flex flex-col gap-4">
<Label variant="default">Default</Label>
<Label variant="accent">Accent</Label>
<Label variant="muted">Muted</Label>
<Label variant="success">Success</Label>
<Label variant="warning">Warning</Label>
<Label variant="error">Error</Label>
</div>
{/snippet}
</Story>
<Story
name="Uppercase"
args={{ uppercase: true, size: 'sm' }}
>
{#snippet template(args)}
<Label {...args}>Uppercase Label</Label>
{/snippet}
</Story>
<Story
name="Lowercase"
args={{ uppercase: false, size: 'sm' }}
>
{#snippet template(args)}
<Label {...args}>lowercase label</Label>
{/snippet}
</Story>
<Story
name="Bold"
args={{ bold: true, size: 'sm' }}
>
{#snippet template(args)}
<Label {...args}>Bold Label</Label>
{/snippet}
</Story>
<Story
name="With icon (left)"
args={{ variant: 'default', size: 'sm', iconPosition: 'left' }}
>
{#snippet template(args)}
<Label {...args}>
{#snippet icon()}
<CircleIcon size={10} />
{/snippet}
Label with icon
</Label>
{/snippet}
</Story>
<Story
name="With icon (right)"
args={{ variant: 'default', size: 'sm', iconPosition: 'right' }}
>
{#snippet template(args)}
<Label {...args}>
Label with icon
{#snippet icon()}
<CircleIcon size={10} />
{/snippet}
</Label>
{/snippet}
</Story>
<Story name="Success with check icon">
{#snippet template()}
<Label variant="success" size="sm">
{#snippet icon()}
<CheckIcon size={10} />
{/snippet}
Completed
</Label>
{/snippet}
</Story>
<Story name="Warning with alert icon">
{#snippet template()}
<Label variant="warning" size="sm">
{#snippet icon()}
<AlertTriangleIcon size={10} />
{/snippet}
Attention Required
</Label>
{/snippet}
</Story>

View File

@@ -1,45 +1,60 @@
<!--
Component: Label
Inline monospace label. The base primitive for all micrographic text.
-->
<script lang="ts"> <script lang="ts">
import { cn } from '$shared/shadcn/utils/shadcn-utils'; import { cn } from '$shared/shadcn/utils/shadcn-utils';
import type { Snippet } from 'svelte';
import {
type LabelSize,
type LabelVariant,
labelSizeConfig,
labelVariantConfig,
} from './config';
interface Props { interface Props {
text?: string; variant?: LabelVariant;
align?: 'left' | 'right' | 'center'; size?: LabelSize;
size?: 'sm' | 'md' | 'lg'; uppercase?: boolean;
onlyText?: boolean; bold?: boolean;
icon?: Snippet;
iconPosition?: 'left' | 'right';
children?: Snippet;
class?: string; class?: string;
} }
const {
text, let {
align = 'left', variant = 'default',
size = 'md', size = 'sm',
onlyText = false, uppercase = true,
bold = false,
icon,
iconPosition = 'left',
children,
class: className, class: className,
}: Props = $props(); }: Props = $props();
</script> </script>
<div <span
class={cn( class={cn(
'grid grid-rows-1 gap-2 items-center w-auto', "font-['Space_Mono'] tracking-widest leading-none",
align === 'left' && 'grid-cols-[max-content_1fr]', 'inline-flex items-center gap-1.5',
align === 'center' && 'grid-cols-[1fr_max-content_1fr]', labelSizeConfig[size],
align === 'right' && 'grig-cols-[1fr_max-content]', labelVariantConfig[variant],
uppercase && 'uppercase',
bold && 'font-bold',
className, className,
)} )}
> >
{#if align !== 'left'} {#if icon && iconPosition === 'left'}
<div class={cn('h-px w-full bg-gray-400/50', onlyText && 'bg-transparent')}></div> <span class="inline-flex">{@render icon()}</span>
{/if} {/if}
<div
class={cn( {#if children}
'text-gray-400 uppercase', <span>{@render children()}</span>
size === 'sm' && 'text-[0.5rem]',
size === 'md' && 'text-[0.625rem]',
size === 'lg' && 'text-[0.75rem]',
)}
>
{text}
</div>
{#if align !== 'right'}
<div class={cn('h-px w-full bg-gray-400/50', onlyText && 'bg-transparent')}></div>
{/if} {/if}
</div>
{#if icon && iconPosition === 'right'}
<span class="inline-flex">{@render icon()}</span>
{/if}
</span>