feat(proj): init

This commit is contained in:
vl.arkhangelskii
2026-09-21 04:06:43 +03:00
commit c956b94983
1076 changed files with 50876 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
import type { ReactNode } from "react";
import { AppButton } from "./AppButton";
import { AppText } from "./AppText";
export function AppSpinner() {
return <span className="app-spinner" role="status" aria-label="Загрузка" />;
}
export function AppSkeleton({ width = "100%", height = 12 }: { width?: string | number; height?: number }) {
return <span className="app-skeleton" style={{ width, height }} />;
}
export function AppProgress({
spent,
total,
label,
}: {
spent: number;
total: number;
label?: string;
}) {
const pct = total > 0 ? Math.min(100, Math.max(0, (spent / total) * 100)) : 0;
const tone = pct >= 100 ? "is-danger" : pct >= 85 ? "is-warn" : "";
return (
<div className="app-progress">
<div
className="app-progress__track"
role="progressbar"
aria-valuenow={Math.round(pct)}
aria-valuemin={0}
aria-valuemax={100}
aria-label={label ?? "Потрачено от бюджета"}
>
<span className={`app-progress__bar ${tone}`.trim()} style={{ width: `${pct}%` }} />
</div>
</div>
);
}
export function AppEmptyView({
title = "Пусто",
message,
action,
}: {
title?: string;
message?: ReactNode;
action?: ReactNode;
}) {
return (
<div className="app-empty">
<AppText variant="headline">{title}</AppText>
{message ? <AppText variant="subhead">{message}</AppText> : null}
{action}
</div>
);
}
export function AppErrorView({
message,
onRetry,
}: {
message: string;
onRetry?: () => void;
}) {
return (
<div className="app-error">
<AppText variant="headline">Не удалось загрузить</AppText>
<AppText variant="subhead">{message}</AppText>
{onRetry ? (
<AppButton label="Повторить" buttonStyle="tinted" size="medium" expanded={false} onClick={onRetry} />
) : null}
</div>
);
}
export function AppChip({
label,
selected = false,
onClick,
disabled,
}: {
label: string;
selected?: boolean;
onClick?: () => void;
disabled?: boolean;
}) {
return (
<button type="button" className={`app-chip${selected ? " is-on" : ""}`} onClick={onClick} disabled={disabled}>
{label}
</button>
);
}
export function AppAvatar({ initials }: { initials: string }) {
return <span className="app-avatar">{initials}</span>;
}