refactor(frontend): normalize vertical rhythm scale

This commit is contained in:
lizerk 2026-04-13 15:21:54 +03:00
parent 318016aee7
commit c96b9453a5
4 changed files with 22 additions and 5 deletions

View File

@ -11,6 +11,23 @@
Эта шкала обязательна для новых экранов и перерабатываемых блоков.
## Целевая шкала вертикального ритма
Радиусы сами по себе не делают систему согласованной. Если блоки одного веса выглядят похоже, у них должен быть одинаковый spacing.
Нормативная шкала:
- `gap-2` для compact control clusters и плотных inline actions
- `space-y-2` для плотного стека внутри одного secondary/detail block
- `space-y-3` для стека однотипных rows или peer secondary cards внутри одной primary panel
- `space-y-4` для стека крупных grouped blocks или major subsections внутри одной primary panel
- `space-y-6` для page-level section stack
Правило:
- spacing выбирается по весу блока и уровню иерархии, а не по конкретному экрану
- одинаковые `app-surface-muted rounded-xl` блоки не должны жить на `space-y-2` в одной зоне и на `space-y-4` в другой без явной UX-причины
## Controls
К controls относятся:

View File

@ -171,7 +171,7 @@ export default function EmployeesPage() {
return (
<AppShell>
<section className="app-surface rounded-2xl p-4">
<div className="space-y-2">
<div className="space-y-3">
{sortedEmployees.map((employee) => {
const fullName =
`${employee.last_name || ""} ${employee.first_name || ""} ${employee.patronymic || ""}`.trim();

View File

@ -6,7 +6,7 @@ import { useUser } from "@/contexts/UserContext";
import Link from "next/link";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Archive, ArrowRightLeft, ArrowUpDown, Check, ChevronDown, ChevronRight, Copy, Download, ExternalLink, Filter, MessageSquare, Monitor, Pencil, Plus, QrCode, Search, Shield, Trash2, Wrench, X } from "lucide-react";
import { Archive, ArrowRightLeft, ArrowUpDown, Check, ChevronDown, ChevronRight, Copy, Download, ExternalLink, Filter, MessageSquare, Monitor, Pencil, Plus, QrCode, Search, Shield, Trash2, Wrench } from "lucide-react";
import { SearchableSelectSingle } from "@/components/shared/SearchableSelect";
import { formatDate, formatMoney } from "@/lib/shared";
import { useEquipmentPage } from "@/hooks/useEquipmentPage";
@ -549,7 +549,7 @@ function EquipmentPageContent() {
{/* Filters panel */}
{filtersOpen && (
<div className="app-surface-muted mb-3 flex flex-col gap-2 rounded-xl p-3">
<div className="app-surface-muted mb-4 flex flex-col gap-2 rounded-xl p-3">
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-4">
<select value={statusFilter} onChange={(e) => setStatusFilter(e.target.value)} className="app-select rounded-lg px-3 py-2 text-sm">
<option value="">Все статусы</option>
@ -591,7 +591,7 @@ function EquipmentPageContent() {
)}
{/* Items list */}
<div className="space-y-2">
<div className="space-y-3">
{filteredItems.length === 0 ? (
<div className="app-surface-muted rounded-xl p-8 text-center">
<Monitor size={22} className="app-text-muted mx-auto mb-2" />

View File

@ -130,7 +130,7 @@ export function RequestListControls({
</div>
{state.filtersOpen ? (
<div className="app-surface-muted mb-3 flex flex-col gap-2 rounded-xl p-3">
<div className="app-surface-muted mb-4 flex flex-col gap-2 rounded-xl p-3">
<select
value={state.view}
onChange={(event) => actions.setView(event.target.value as "" | "mine" | "addressed")}