refactor: migrate ui elements to lib
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
export let variant = "muted";
|
||||
let className = "";
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<span
|
||||
class={cn(
|
||||
"admin-badge",
|
||||
variant === "success" && "admin-badge-success",
|
||||
variant === "danger" && "admin-badge-danger",
|
||||
variant === "warning" && "admin-badge-warning",
|
||||
variant === "muted" && "admin-badge-muted",
|
||||
className,
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</span>
|
||||
@@ -0,0 +1,45 @@
|
||||
<script>
|
||||
import { cva } from "class-variance-authority";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
export let type = "button";
|
||||
export let variant = "default";
|
||||
export let size = "default";
|
||||
export let disabled = false;
|
||||
export let onclick = undefined;
|
||||
|
||||
let className = "";
|
||||
export { className as class };
|
||||
|
||||
const buttonVariants = cva("admin-btn", {
|
||||
variants: {
|
||||
variant: {
|
||||
default: "",
|
||||
primary: "admin-btn-primary",
|
||||
ghost: "admin-btn-ghost",
|
||||
danger: "admin-btn-danger",
|
||||
dangerSoft: "admin-btn-danger-soft",
|
||||
icon: "admin-btn-icon",
|
||||
},
|
||||
size: {
|
||||
default: "",
|
||||
sm: "admin-btn-sm",
|
||||
icon: "admin-btn-icon",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<button
|
||||
class={cn(buttonVariants({ variant, size }), className)}
|
||||
{type}
|
||||
{disabled}
|
||||
onclick={onclick}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script>
|
||||
import { Label } from "$components/ui/primitives.js";
|
||||
|
||||
export let label = "";
|
||||
export let hint = "";
|
||||
</script>
|
||||
|
||||
<Label.Root class="admin-field-label">
|
||||
{#if label}
|
||||
<span>{label}</span>
|
||||
{/if}
|
||||
{#if hint}
|
||||
<small>{hint}</small>
|
||||
{/if}
|
||||
<slot />
|
||||
</Label.Root>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script>
|
||||
import { ChevronLeft, ChevronRight } from "$components/ui/icons.js";
|
||||
import AdminButton from "./AdminButton.svelte";
|
||||
|
||||
export let meta = "";
|
||||
export let prevLabel = "Back";
|
||||
export let nextLabel = "Next";
|
||||
export let prevDisabled = false;
|
||||
export let nextDisabled = false;
|
||||
export let onPrev = () => {};
|
||||
export let onNext = () => {};
|
||||
</script>
|
||||
|
||||
<div class="admin-pagination">
|
||||
<span class="admin-pagination-meta">{meta}</span>
|
||||
<div class="admin-pagination-buttons">
|
||||
<AdminButton size="sm" disabled={prevDisabled} onclick={onPrev}>
|
||||
<ChevronLeft size={14} /> {prevLabel}
|
||||
</AdminButton>
|
||||
<AdminButton size="sm" disabled={nextDisabled} onclick={onNext}>
|
||||
{nextLabel} <ChevronRight size={14} />
|
||||
</AdminButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,38 @@
|
||||
<script>
|
||||
import { Check, ChevronDown } from "$components/ui/icons.js";
|
||||
import { Select } from "$components/ui/primitives.js";
|
||||
|
||||
export let value = "";
|
||||
export let items = [];
|
||||
export let ariaLabel = "";
|
||||
export let placeholder = "";
|
||||
export let disabled = false;
|
||||
export let sideOffset = 6;
|
||||
export let onValueChange = () => {};
|
||||
let className = "";
|
||||
export { className as class };
|
||||
|
||||
$: selected = items.find((item) => item.value === value);
|
||||
|
||||
function handleValueChange(next) {
|
||||
value = next;
|
||||
onValueChange(next);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Select.Root type="single" {value} {items} {disabled} onValueChange={handleValueChange}>
|
||||
<Select.Trigger class={`admin-select-trigger ${className}`.trim()} aria-label={ariaLabel || placeholder}>
|
||||
<span>{selected?.label || placeholder}</span>
|
||||
<ChevronDown size={14} class="admin-select-icon" />
|
||||
</Select.Trigger>
|
||||
<Select.Portal>
|
||||
<Select.Content class="admin-select-content" {sideOffset}>
|
||||
{#each items as item (item.value)}
|
||||
<Select.Item value={item.value} label={item.label} class="admin-select-item">
|
||||
<span>{item.label}</span>
|
||||
<Check size={14} class="admin-select-item-check" />
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Portal>
|
||||
</Select.Root>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
export let skeleton = false;
|
||||
let className = "";
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div class="admin-table-wrap">
|
||||
<table class={cn("admin-table", skeleton && "admin-table-skeleton", className)} aria-hidden={skeleton ? "true" : undefined} {...$$restProps}>
|
||||
<slot />
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,38 @@
|
||||
<script>
|
||||
import Skeleton from "$components/ui/skeleton.svelte";
|
||||
import AdminTable from "./AdminTable.svelte";
|
||||
|
||||
export let headers = [];
|
||||
export let rows = 6;
|
||||
export let actionColumn = false;
|
||||
export let widths = [];
|
||||
|
||||
function widthFor(index) {
|
||||
if (widths[index]) return widths[index];
|
||||
if (actionColumn && index === headers.length - 1) return "92px";
|
||||
if (index === 0) return "48px";
|
||||
if (index === headers.length - 1) return "76px";
|
||||
return index % 3 === 0 ? "56%" : "72%";
|
||||
}
|
||||
</script>
|
||||
|
||||
<AdminTable skeleton>
|
||||
<thead>
|
||||
<tr>
|
||||
{#each headers as header}
|
||||
<th class:admin-cell-actions={actionColumn && header === headers[headers.length - 1]}>{header}</th>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each Array(rows) as _, rowIndex (rowIndex)}
|
||||
<tr>
|
||||
{#each headers as header, colIndex (`${rowIndex}-${colIndex}`)}
|
||||
<td>
|
||||
<Skeleton variant="line" width={widthFor(colIndex)} />
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</AdminTable>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
export let title = "";
|
||||
export let value = "";
|
||||
export let left = "";
|
||||
export let percent = 0;
|
||||
export let warning = false;
|
||||
export let premium = false;
|
||||
export let label = "";
|
||||
|
||||
$: clamped = Math.max(0, Math.min(100, Number(percent) || 0));
|
||||
</script>
|
||||
|
||||
<div class={cn("admin-traffic-card", warning && "admin-traffic-card-warning", premium && "admin-traffic-card-premium")}>
|
||||
<div class="admin-traffic-head">
|
||||
<span>{title}</span>
|
||||
<strong>{value}</strong>
|
||||
</div>
|
||||
<div
|
||||
class={cn("admin-traffic-bar", premium && "admin-traffic-bar-premium")}
|
||||
aria-label={label || title}
|
||||
role="progressbar"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow={Math.round(clamped)}
|
||||
>
|
||||
<span style={`width: ${clamped}%`}></span>
|
||||
</div>
|
||||
<div class="admin-traffic-meta">
|
||||
<span>{left}</span>
|
||||
<span>{clamped}%</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,4 +1,12 @@
|
||||
export { default as AdminBadge } from "./AdminBadge.svelte";
|
||||
export { default as AdminButton } from "./AdminButton.svelte";
|
||||
export { default as AdminDashboardGrid } from "./AdminDashboardGrid.svelte";
|
||||
export { default as AdminDashboardStack } from "./AdminDashboardStack.svelte";
|
||||
export { default as AdminEmptyState } from "./AdminEmptyState.svelte";
|
||||
export { default as AdminField } from "./AdminField.svelte";
|
||||
export { default as AdminPagination } from "./AdminPagination.svelte";
|
||||
export { default as AdminSelect } from "./AdminSelect.svelte";
|
||||
export { default as AdminSectionHeader } from "./AdminSectionHeader.svelte";
|
||||
export { default as AdminTable } from "./AdminTable.svelte";
|
||||
export { default as AdminTableSkeleton } from "./AdminTableSkeleton.svelte";
|
||||
export { default as AdminTrafficCard } from "./AdminTrafficCard.svelte";
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<script>
|
||||
import Skeleton from "$components/ui/skeleton.svelte";
|
||||
|
||||
export let label = "";
|
||||
export let rows = 3;
|
||||
export let actions = 0;
|
||||
export let methods = 2;
|
||||
export let showNote = false;
|
||||
export let showPayButton = true;
|
||||
export let showMeta = true;
|
||||
</script>
|
||||
|
||||
<div class="dialog-skeleton" aria-label={label}>
|
||||
{#if actions}
|
||||
<div class="tariff-action-list">
|
||||
{#each Array(actions) as _, index (index)}
|
||||
<div class="tariff-action-card skeleton-row">
|
||||
<span>
|
||||
<Skeleton variant="title" />
|
||||
<Skeleton variant="short" />
|
||||
</span>
|
||||
<Skeleton class="skeleton-line-price" />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="payment-divider" aria-hidden="true"></div>
|
||||
{/if}
|
||||
|
||||
<div class="option-list">
|
||||
{#each Array(rows) as _, index (index)}
|
||||
<div class={`option-row ${showMeta ? "plan-row" : "change-action-row"} skeleton-row`}>
|
||||
<span class="option-row-main">
|
||||
<Skeleton variant="title" />
|
||||
<Skeleton variant="short" />
|
||||
</span>
|
||||
{#if showMeta}
|
||||
<span class="option-row-meta">
|
||||
<Skeleton class="skeleton-line-price" />
|
||||
<Skeleton variant="tiny" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if showNote}
|
||||
<div class="topup-carryover-note skeleton-carryover-note">
|
||||
<Skeleton variant="line" />
|
||||
<Skeleton variant="short" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if methods}
|
||||
<div class="method-grid">
|
||||
{#each Array(methods) as _, index (index)}
|
||||
<div class="method-card skeleton-method">
|
||||
<Skeleton variant="dot" />
|
||||
<Skeleton class="skeleton-line-method" />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showPayButton}
|
||||
<Skeleton class="skeleton-pay-button" />
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script>
|
||||
import Card from "$components/ui/card.svelte";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let className = "";
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<Card class={cn("empty-card", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</Card>
|
||||
@@ -0,0 +1,71 @@
|
||||
<script>
|
||||
import { Check, ChevronsUpDown, Globe2 } from "$components/ui/icons.js";
|
||||
import { Select } from "$components/ui/primitives.js";
|
||||
|
||||
export let open = false;
|
||||
export let value = "ru";
|
||||
export let currentOption = null;
|
||||
export let userLanguage = "";
|
||||
export let options = [];
|
||||
export let disabled = false;
|
||||
export let clickGuard = false;
|
||||
export let clickGuardArmed = false;
|
||||
export let closeLabel = "Close";
|
||||
export let label = "Language";
|
||||
export let onOpenChange = () => {};
|
||||
export let onValueChange = () => {};
|
||||
|
||||
function closeFromGuard(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (clickGuardArmed) onOpenChange(false);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if open || clickGuard}
|
||||
<button
|
||||
class="language-select-guard"
|
||||
class:language-select-guard--armed={clickGuardArmed}
|
||||
type="button"
|
||||
aria-label={closeLabel}
|
||||
onpointerdown={closeFromGuard}
|
||||
onclick={closeFromGuard}
|
||||
></button>
|
||||
{/if}
|
||||
|
||||
<div class="settings-row settings-row-language">
|
||||
<Globe2 size={21} />
|
||||
<Select.Root
|
||||
type="single"
|
||||
bind:open
|
||||
{value}
|
||||
items={options}
|
||||
{disabled}
|
||||
onOpenChange={onOpenChange}
|
||||
onValueChange={onValueChange}
|
||||
>
|
||||
<Select.Trigger class="language-select-trigger" aria-label={label}>
|
||||
<span class="language-select-copy">
|
||||
<strong>{label}</strong>
|
||||
<small class="language-select-current">
|
||||
<span class="emoji-flag" aria-hidden="true">{currentOption?.flag || "🏳️"}</span>
|
||||
{currentOption?.label || userLanguage}
|
||||
</small>
|
||||
</span>
|
||||
<ChevronsUpDown size={16} />
|
||||
</Select.Trigger>
|
||||
<Select.Content class="language-select-content" side="bottom" align="end" sideOffset={6}>
|
||||
<Select.Viewport class="language-select-viewport">
|
||||
{#each options as option (option.value)}
|
||||
<Select.Item value={option.value} label={option.label} class="language-select-item">
|
||||
<span class="language-select-item-main">
|
||||
<span class="emoji-flag" aria-hidden="true">{option.flag}</span>
|
||||
<span>{option.label}</span>
|
||||
</span>
|
||||
<Check size={15} class="language-select-item-check" />
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Viewport>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
export let value = 0;
|
||||
export let label = "";
|
||||
let className = "";
|
||||
export { className as class };
|
||||
|
||||
$: clamped = Math.max(0, Math.min(100, Number(value) || 0));
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cn("progress", className)}
|
||||
role={label ? "progressbar" : undefined}
|
||||
aria-label={label || undefined}
|
||||
aria-valuemin={label ? "0" : undefined}
|
||||
aria-valuemax={label ? "100" : undefined}
|
||||
aria-valuenow={label ? Math.round(clamped) : undefined}
|
||||
{...$$restProps}
|
||||
>
|
||||
<span style={`width: ${clamped}%`}></span>
|
||||
</div>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script>
|
||||
import { Bitcoin, CreditCard } from "$components/ui/icons.js";
|
||||
|
||||
export let methods = [];
|
||||
export let selectedMethod = "";
|
||||
export let t = (key) => key;
|
||||
export let onSelect = () => {};
|
||||
|
||||
function methodMeta(method) {
|
||||
const id = String(method?.id || "").toLowerCase();
|
||||
if (id.includes("platega_sbp")) return { title: t("wa_method_platega_sbp_card"), icon: CreditCard };
|
||||
if (id.includes("platega_crypto")) return { title: t("wa_method_platega_crypto"), icon: Bitcoin };
|
||||
if (id.includes("yookassa") || id.includes("card")) return { title: t("pay_with_yookassa_button"), icon: null };
|
||||
if (id.includes("severpay")) return { title: t("pay_with_severpay_button"), icon: null };
|
||||
if (id.includes("freekassa")) return { title: t("pay_with_sbp_button"), icon: null };
|
||||
if (id.includes("cryptopay") || id.includes("crypto")) return { title: t("pay_with_cryptopay_button"), icon: null };
|
||||
if (id.includes("stars")) return { title: t("pay_with_stars_button"), icon: null };
|
||||
if (id.includes("sbp")) return { title: t("pay_with_sbp_button"), icon: null };
|
||||
return { title: t("wa_method_other_title"), icon: null };
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="method-grid">
|
||||
{#each methods as method}
|
||||
{@const meta = methodMeta(method)}
|
||||
<button
|
||||
class:active={selectedMethod === method.id}
|
||||
class="method-card"
|
||||
type="button"
|
||||
onclick={() => onSelect(method.id)}
|
||||
>
|
||||
<span class="method-card-main">
|
||||
{#if meta.icon}
|
||||
<svelte:component this={meta.icon} size={19} />
|
||||
{/if}
|
||||
<strong>{meta.title}</strong>
|
||||
</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
export let error = false;
|
||||
let className = "";
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<p class={cn("status-line", error && "error", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</p>
|
||||
@@ -0,0 +1,6 @@
|
||||
export { default as DialogOptionsSkeleton } from "./DialogOptionsSkeleton.svelte";
|
||||
export { default as EmptyCard } from "./EmptyCard.svelte";
|
||||
export { default as LinearProgress } from "./LinearProgress.svelte";
|
||||
export { default as LanguageSelect } from "./LanguageSelect.svelte";
|
||||
export { default as PaymentMethodGrid } from "./PaymentMethodGrid.svelte";
|
||||
export { default as StatusMessage } from "./StatusMessage.svelte";
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { X } from "lucide-svelte";
|
||||
import { X } from "$components/ui/icons.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import Button from "./button.svelte";
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
export {
|
||||
Activity,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
Bitcoin,
|
||||
CalendarDays,
|
||||
Check,
|
||||
CheckCircle2,
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronsUpDown,
|
||||
Circle,
|
||||
CircleX,
|
||||
Coins,
|
||||
Copy,
|
||||
CreditCard,
|
||||
Crown,
|
||||
Database,
|
||||
Download,
|
||||
ExternalLink,
|
||||
Eye,
|
||||
EyeOff,
|
||||
FileText,
|
||||
Gift,
|
||||
Globe2,
|
||||
Home,
|
||||
Info,
|
||||
Key,
|
||||
LayoutDashboard,
|
||||
LockKeyhole,
|
||||
Mail,
|
||||
Map,
|
||||
Megaphone,
|
||||
Menu,
|
||||
MessageSquare,
|
||||
MousePointerClick,
|
||||
Plus,
|
||||
QrCode,
|
||||
Radio,
|
||||
RefreshCw,
|
||||
Repeat2,
|
||||
Save,
|
||||
Send,
|
||||
Server,
|
||||
Settings,
|
||||
Shield,
|
||||
Sliders,
|
||||
Smartphone,
|
||||
Sparkles,
|
||||
Tag,
|
||||
Ticket,
|
||||
Trash2,
|
||||
TrendingDown,
|
||||
TrendingUp,
|
||||
TriangleAlert,
|
||||
User,
|
||||
UserMinus,
|
||||
UserPlus,
|
||||
UserRound,
|
||||
Users,
|
||||
UsersRound,
|
||||
WalletCards,
|
||||
X,
|
||||
Zap,
|
||||
} from "lucide-svelte";
|
||||
@@ -3,5 +3,8 @@ export { default as Button } from "./button.svelte";
|
||||
export { default as Dialog } from "./dialog.svelte";
|
||||
export { default as Input } from "./input.svelte";
|
||||
export { default as LegacyCard } from "./card.svelte";
|
||||
export { default as Skeleton } from "./skeleton.svelte";
|
||||
export { default as Spinner } from "./spinner.svelte";
|
||||
export * as Icons from "./icons.js";
|
||||
export * as Card from "./card/index.js";
|
||||
export { Accordion, Label, Select, Separator, Switch, Tabs, Tooltip } from "./primitives.js";
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
export let variant = "block";
|
||||
export let width = "";
|
||||
export let height = "";
|
||||
let className = "";
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<span
|
||||
class={cn(
|
||||
"ui-skeleton",
|
||||
variant === "line" && "ui-skeleton-line",
|
||||
variant === "title" && "ui-skeleton-line ui-skeleton-title",
|
||||
variant === "short" && "ui-skeleton-line ui-skeleton-short",
|
||||
variant === "tiny" && "ui-skeleton-line ui-skeleton-tiny",
|
||||
variant === "badge" && "ui-skeleton-badge",
|
||||
variant === "dot" && "ui-skeleton-dot",
|
||||
className,
|
||||
)}
|
||||
style={`${width ? `width:${width};` : ""}${height ? `height:${height};` : ""}`}
|
||||
aria-hidden="true"
|
||||
{...$$restProps}
|
||||
></span>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
export let size = "default";
|
||||
export let label = "";
|
||||
let className = "";
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<span
|
||||
class={cn("ui-spinner", size === "sm" && "ui-spinner-sm", size === "lg" && "ui-spinner-lg", className)}
|
||||
role={label ? "status" : undefined}
|
||||
aria-label={label || undefined}
|
||||
aria-hidden={label ? undefined : "true"}
|
||||
{...$$restProps}
|
||||
></span>
|
||||
Reference in New Issue
Block a user