feat(Loader): create loader component with spinner and optional message
This commit is contained in:
33
src/shared/ui/Loader/Loader.stories.svelte
Normal file
33
src/shared/ui/Loader/Loader.stories.svelte
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<script module>
|
||||||
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
||||||
|
import Loader from './Loader.svelte';
|
||||||
|
|
||||||
|
const { Story } = defineMeta({
|
||||||
|
title: 'Shared/Loader',
|
||||||
|
tags: ['autodocs'],
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component: 'Spinner with optional message',
|
||||||
|
},
|
||||||
|
story: { inline: false }, // Render stories in iframe for state isolation
|
||||||
|
},
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
message: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Optional message to display',
|
||||||
|
defaultValue: 'analyzing_data',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: 'number',
|
||||||
|
description: 'Size of the spinner',
|
||||||
|
defaultValue: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Story name="Default">
|
||||||
|
<Loader />
|
||||||
|
</Story>
|
||||||
77
src/shared/ui/Loader/Loader.svelte
Normal file
77
src/shared/ui/Loader/Loader.svelte
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<!--
|
||||||
|
Component: Loader
|
||||||
|
Displays a loading spinner with an optional message.
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/**
|
||||||
|
* Icon size (in pixels)
|
||||||
|
* @default 20
|
||||||
|
*/
|
||||||
|
size?: number;
|
||||||
|
/**
|
||||||
|
* Additional classes for container
|
||||||
|
*/
|
||||||
|
class?: string;
|
||||||
|
/**
|
||||||
|
* Message text
|
||||||
|
* @default analyzing_data
|
||||||
|
*/
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { size = 20, class: className = '', message = 'analyzing_data' }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="absolute inset-x-0 inset-y-0 flex items-center justify-center gap-4 {className}"
|
||||||
|
in:fade={{ duration: 300 }}
|
||||||
|
out:fade={{ duration: 300 }}
|
||||||
|
>
|
||||||
|
<div style:width="{size}px" style:height="{size}px">
|
||||||
|
<svg class="stroke-gray-900 stroke-1" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(12, 12)">
|
||||||
|
<!-- Four corner brackets rotating -->
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
d="M -8 -8 L -4 -8 M -8 -8 L -8 -4"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M 8 -8 L 4 -8 M 8 -8 L 8 -4"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M -8 8 L -4 8 M -8 8 L -8 4"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
<path d="M 8 8 L 4 8 M 8 8 L 8 4" stroke="currentColor" stroke-width="1" stroke-linecap="round" />
|
||||||
|
|
||||||
|
<animateTransform
|
||||||
|
attributeName="transform"
|
||||||
|
type="rotate"
|
||||||
|
from="0"
|
||||||
|
to="360"
|
||||||
|
dur="3s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<!-- Divider -->
|
||||||
|
<div class="w-px h-3 bg-gray-400/50"></div>
|
||||||
|
|
||||||
|
<!-- Message -->
|
||||||
|
<span class="font-mono text-[10px] uppercase tracking-[0.2em] text-gray-600 font-medium">
|
||||||
|
{message}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user