97 lines
2.3 KiB
TypeScript
97 lines
2.3 KiB
TypeScript
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>;
|
|
}
|