fix tax log
This commit is contained in:
62
frontend/src/lib/ui/Button.svelte
Normal file
62
frontend/src/lib/ui/Button.svelte
Normal file
@@ -0,0 +1,62 @@
|
||||
<!-- [DEF:Button:Component] -->
|
||||
<!--
|
||||
@TIER: TRIVIAL
|
||||
@SEMANTICS: button, ui-atom, interactive
|
||||
@PURPOSE: Standardized button component with variants and loading states.
|
||||
@LAYER: Atom
|
||||
|
||||
@INVARIANT: Always uses Tailwind for styling.
|
||||
@INVARIANT: Supports accessible labels and keyboard navigation.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
// [SECTION: IMPORTS]
|
||||
// [/SECTION: IMPORTS]
|
||||
|
||||
// [SECTION: PROPS]
|
||||
/**
|
||||
* @purpose Define component interface and default values.
|
||||
*/
|
||||
export let variant: 'primary' | 'secondary' | 'danger' | 'ghost' = 'primary';
|
||||
export let size: 'sm' | 'md' | 'lg' = 'md';
|
||||
export let isLoading: boolean = false;
|
||||
export let disabled: boolean = false;
|
||||
export let type: 'button' | 'submit' | 'reset' = 'button';
|
||||
let className: string = "";
|
||||
export { className as class };
|
||||
// [/SECTION: PROPS]
|
||||
|
||||
const baseStyles = "inline-flex items-center justify-center font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 rounded-md";
|
||||
|
||||
const variants = {
|
||||
primary: "bg-blue-600 text-white hover:bg-blue-700 focus-visible:ring-blue-500",
|
||||
secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200 focus-visible:ring-gray-500",
|
||||
danger: "bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-500",
|
||||
ghost: "bg-transparent hover:bg-gray-100 text-gray-700 focus-visible:ring-gray-500"
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
sm: "h-8 px-3 text-xs",
|
||||
md: "h-10 px-4 py-2 text-sm",
|
||||
lg: "h-12 px-6 text-base"
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<button
|
||||
{type}
|
||||
class="{baseStyles} {variants[variant]} {sizes[size]} {className}"
|
||||
disabled={disabled || isLoading}
|
||||
on:click
|
||||
>
|
||||
{#if isLoading}
|
||||
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-current" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
{/if}
|
||||
<slot />
|
||||
</button>
|
||||
<!-- [/SECTION: TEMPLATE] -->
|
||||
|
||||
<!-- [/DEF:Button:Component] -->
|
||||
36
frontend/src/lib/ui/Card.svelte
Normal file
36
frontend/src/lib/ui/Card.svelte
Normal file
@@ -0,0 +1,36 @@
|
||||
<!-- [DEF:Card:Component] -->
|
||||
<!--
|
||||
@TIER: TRIVIAL
|
||||
@SEMANTICS: card, container, ui-atom
|
||||
@PURPOSE: Standardized container with padding and elevation.
|
||||
@LAYER: Atom
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
// [SECTION: PROPS]
|
||||
export let title: string = "";
|
||||
export let padding: 'none' | 'sm' | 'md' | 'lg' = 'md';
|
||||
// [/SECTION: PROPS]
|
||||
|
||||
const paddings = {
|
||||
none: "p-0",
|
||||
sm: "p-3",
|
||||
md: "p-6",
|
||||
lg: "p-8"
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white text-gray-950 shadow-sm">
|
||||
{#if title}
|
||||
<div class="flex flex-col space-y-1.5 p-6 border-b border-gray-100">
|
||||
<h3 class="text-lg font-semibold leading-none tracking-tight">{title}</h3>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="{paddings[padding]}">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<!-- [/SECTION: TEMPLATE] -->
|
||||
|
||||
<!-- [/DEF:Card:Component] -->
|
||||
47
frontend/src/lib/ui/Input.svelte
Normal file
47
frontend/src/lib/ui/Input.svelte
Normal file
@@ -0,0 +1,47 @@
|
||||
<!-- [DEF:Input:Component] -->
|
||||
<!--
|
||||
@TIER: TRIVIAL
|
||||
@SEMANTICS: input, form-field, ui-atom
|
||||
@PURPOSE: Standardized text input component with label and error handling.
|
||||
@LAYER: Atom
|
||||
|
||||
@INVARIANT: Consistent spacing and focus states.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
// [SECTION: PROPS]
|
||||
export let label: string = "";
|
||||
export let value: string = "";
|
||||
export let placeholder: string = "";
|
||||
export let error: string = "";
|
||||
export let disabled: boolean = false;
|
||||
export let type: 'text' | 'password' | 'email' | 'number' = 'text';
|
||||
// [/SECTION: PROPS]
|
||||
|
||||
let id = "input-" + Math.random().toString(36).substr(2, 9);
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="flex flex-col gap-1.5 w-full">
|
||||
{#if label}
|
||||
<label for={id} class="text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<input
|
||||
{id}
|
||||
{type}
|
||||
{placeholder}
|
||||
{disabled}
|
||||
bind:value
|
||||
class="flex h-10 w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 {error ? 'border-red-500' : ''}"
|
||||
/>
|
||||
|
||||
{#if error}
|
||||
<span class="text-xs text-red-500">{error}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- [/SECTION: TEMPLATE] -->
|
||||
|
||||
<!-- [/DEF:Input:Component] -->
|
||||
31
frontend/src/lib/ui/LanguageSwitcher.svelte
Normal file
31
frontend/src/lib/ui/LanguageSwitcher.svelte
Normal file
@@ -0,0 +1,31 @@
|
||||
<!-- [DEF:LanguageSwitcher:Component] -->
|
||||
<!--
|
||||
@TIER: TRIVIAL
|
||||
@SEMANTICS: language-switcher, i18n-ui, ui-atom
|
||||
@PURPOSE: Dropdown component to switch between supported languages.
|
||||
@LAYER: Atom
|
||||
@RELATION: BINDS_TO -> i18n.locale
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
// [SECTION: IMPORTS]
|
||||
import { locale } from '$lib/i18n';
|
||||
import Select from './Select.svelte';
|
||||
// [/SECTION: IMPORTS]
|
||||
|
||||
const options = [
|
||||
{ value: 'ru', label: 'Русский' },
|
||||
{ value: 'en', label: 'English' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="w-32">
|
||||
<Select
|
||||
bind:value={$locale}
|
||||
{options}
|
||||
/>
|
||||
</div>
|
||||
<!-- [/SECTION: TEMPLATE] -->
|
||||
|
||||
<!-- [/DEF:LanguageSwitcher:Component] -->
|
||||
27
frontend/src/lib/ui/PageHeader.svelte
Normal file
27
frontend/src/lib/ui/PageHeader.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<!-- [DEF:PageHeader:Component] -->
|
||||
<!--
|
||||
@TIER: TRIVIAL
|
||||
@SEMANTICS: page-header, layout-atom
|
||||
@PURPOSE: Standardized page header with title and action area.
|
||||
@LAYER: Atom
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
// [SECTION: PROPS]
|
||||
export let title: string = "";
|
||||
// [/SECTION: PROPS]
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<header class="flex items-center justify-between mb-8">
|
||||
<div class="space-y-1">
|
||||
<h1 class="text-3xl font-bold tracking-tight text-gray-900">{title}</h1>
|
||||
<slot name="subtitle" />
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</header>
|
||||
<!-- [/SECTION: TEMPLATE] -->
|
||||
|
||||
<!-- [/DEF:PageHeader:Component] -->
|
||||
41
frontend/src/lib/ui/Select.svelte
Normal file
41
frontend/src/lib/ui/Select.svelte
Normal file
@@ -0,0 +1,41 @@
|
||||
<!-- [DEF:Select:Component] -->
|
||||
<!--
|
||||
@TIER: TRIVIAL
|
||||
@SEMANTICS: select, dropdown, form-field, ui-atom
|
||||
@PURPOSE: Standardized dropdown selection component.
|
||||
@LAYER: Atom
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
// [SECTION: PROPS]
|
||||
export let label: string = "";
|
||||
export let value: string | number = "";
|
||||
export let options: Array<{ value: string | number, label: string }> = [];
|
||||
export let disabled: boolean = false;
|
||||
// [/SECTION: PROPS]
|
||||
|
||||
let id = "select-" + Math.random().toString(36).substr(2, 9);
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="flex flex-col gap-1.5 w-full">
|
||||
{#if label}
|
||||
<label for={id} class="text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<select
|
||||
{id}
|
||||
{disabled}
|
||||
bind:value
|
||||
class="flex h-10 w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{#each options as option}
|
||||
<option value={option.value}>{option.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<!-- [/SECTION: TEMPLATE] -->
|
||||
|
||||
<!-- [/DEF:Select:Component] -->
|
||||
19
frontend/src/lib/ui/index.ts
Normal file
19
frontend/src/lib/ui/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// [DEF:ui:Module]
|
||||
//
|
||||
// @TIER: TRIVIAL
|
||||
// @SEMANTICS: ui, components, library, atomic-design
|
||||
// @PURPOSE: Central export point for standardized UI components.
|
||||
// @LAYER: Atom
|
||||
//
|
||||
// @INVARIANT: All components exported here must follow Semantic Protocol.
|
||||
|
||||
// [SECTION: EXPORTS]
|
||||
export { default as Button } from './Button.svelte';
|
||||
export { default as Input } from './Input.svelte';
|
||||
export { default as Select } from './Select.svelte';
|
||||
export { default as Card } from './Card.svelte';
|
||||
export { default as PageHeader } from './PageHeader.svelte';
|
||||
export { default as LanguageSwitcher } from './LanguageSwitcher.svelte';
|
||||
// [/SECTION: EXPORTS]
|
||||
|
||||
// [/DEF:ui:Module]
|
||||
Reference in New Issue
Block a user