feat(proj): init
This commit is contained in:
@@ -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>;
|
||||
}
|
||||
Reference in New Issue
Block a user