feat(DotIndicator): create DotIndicator ui component

This commit is contained in:
Ilia Mashkov
2026-02-22 11:25:53 +03:00
parent 12222634d3
commit 7f2fcb1797
2 changed files with 249 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import DotIndicator from './DotIndicator.svelte';
const { Story } = defineMeta({
title: 'Shared/DotIndicator',
component: DotIndicator,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component:
'Circular status indicator with size options and pulse animation. Use for showing online/offline status, loading states, or feedback indicators.',
},
story: { inline: false },
},
layout: 'centered',
},
argTypes: {
variant: {
control: 'select',
options: ['online', 'offline', 'busy', 'away', 'success', 'warning', 'error'],
description: 'Status variant of the indicator',
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
description: 'Size of the indicator',
},
pulse: {
control: 'boolean',
description: 'Enable pulse animation',
},
},
});
</script>
<script lang="ts">
import type { Snippet } from 'svelte';
</script>
<Story name="Default">
{#snippet template(args)}
<DotIndicator {...args} />
{/snippet}
</Story>
<Story name="Variants">
{#snippet template(args)}
<div class="flex gap-4 items-center">
<div class="flex items-center gap-2">
<DotIndicator variant="online" {...args} />
<span class="text-sm">Online</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="offline" {...args} />
<span class="text-sm">Offline</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="busy" {...args} />
<span class="text-sm">Busy</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="away" {...args} />
<span class="text-sm">Away</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="success" {...args} />
<span class="text-sm">Success</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="warning" {...args} />
<span class="text-sm">Warning</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="error" {...args} />
<span class="text-sm">Error</span>
</div>
</div>
{/snippet}
</Story>
<Story name="Sizes">
{#snippet template(args)}
<div class="flex gap-4 items-center">
<div class="flex items-center gap-2">
<DotIndicator size="sm" {...args} />
<span class="text-sm text-xs">Small</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator size="md" {...args} />
<span class="text-sm">Medium</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator size="lg" {...args} />
<span class="text-sm">Large</span>
</div>
</div>
{/snippet}
</Story>
<Story name="With Pulse">
{#snippet template(args)}
<div class="flex gap-4 items-center">
<div class="flex items-center gap-2">
<DotIndicator variant="online" pulse {...args} />
<span class="text-sm">Online</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="busy" pulse {...args} />
<span class="text-sm">Busy</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="away" pulse {...args} />
<span class="text-sm">Away</span>
</div>
</div>
{/snippet}
</Story>
<Story name="All Combinations">
{#snippet template(args)}
<div class="space-y-3">
<h3 class="text-sm font-semibold">Sizes</h3>
<div class="flex gap-4 items-center">
<div class="flex items-center gap-2">
<DotIndicator variant="online" size="sm" {...args} />
<span class="text-sm text-xs">Small</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="online" size="md" {...args} />
<span class="text-sm">Medium</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="online" size="lg" {...args} />
<span class="text-sm">Large</span>
</div>
</div>
<h3 class="text-sm font-semibold mt-4">Status Variants</h3>
<div class="flex gap-4 items-center">
<div class="flex items-center gap-2">
<DotIndicator variant="online" {...args} />
<span class="text-sm">Online</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="offline" {...args} />
<span class="text-sm">Offline</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="busy" {...args} />
<span class="text-sm">Busy</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="away" {...args} />
<span class="text-sm">Away</span>
</div>
</div>
<h3 class="text-sm font-semibold mt-4">Feedback Variants</h3>
<div class="flex gap-4 items-center">
<div class="flex items-center gap-2">
<DotIndicator variant="success" {...args} />
<span class="text-sm">Success</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="warning" {...args} />
<span class="text-sm">Warning</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="error" {...args} />
<span class="text-sm">Error</span>
</div>
</div>
<h3 class="text-sm font-semibold mt-4">With Pulse Animation</h3>
<div class="flex gap-4 items-center">
<div class="flex items-center gap-2">
<DotIndicator variant="online" pulse {...args} />
<span class="text-sm">Online</span>
</div>
<div class="flex items-center gap-2">
<DotIndicator variant="busy" pulse {...args} />
<span class="text-sm">Busy</span>
</div>
</div>
</div>
{/snippet}
</Story>

View File

@@ -0,0 +1,60 @@
<!--
Component: DotIndicator
Circular status indicator with size options and pulse animation
-->
<script lang="ts">
interface Props {
/**
* Status variant of the indicator
* @default online
*/
variant?: 'online' | 'offline' | 'busy' | 'away' | 'success' | 'warning' | 'error';
/**
* Size of the indicator
* @default md
*/
size?: 'sm' | 'md' | 'lg';
/**
* Enable pulse animation
* @default false
*/
pulse?: boolean;
}
const {
variant = 'online',
size = 'md',
pulse = false,
}: Props = $props();
const baseClasses = 'rounded-full';
const variantClasses = $derived(
variant === 'online' || variant === 'success'
? 'bg-green-500'
: variant === 'offline'
? 'bg-gray-400'
: variant === 'busy' || variant === 'error'
? 'bg-red-500'
: variant === 'away'
? 'bg-yellow-500'
: variant === 'warning'
? 'bg-yellow-500'
: 'bg-gray-400',
);
const sizeClasses = $derived(
size === 'sm'
? 'w-1.5 h-1.5'
: size === 'lg'
? 'w-3 h-3'
: 'w-2 h-2',
);
const pulseAnimation = $derived(pulse && 'animate-pulse');
</script>
<span
class="{baseClasses} {variantClasses} {sizeClasses} {pulseAnimation}"
aria-label="Status indicator"
></span>