feat(drawer): sticky toolbar API per handoff Screen 3 line 279

RecordDrawer теперь принимает optional toolbar prop:
  - search (string) — field name filter
  - onlyFilled (boolean) — show only filled fields toggle
  - counterVisible/Total (number) — 'N / M полей' caption

Sticky position между header и body, h=44 per handoff style. Toolbar
rendered только если caller передал. SchemaDrivenForm callsite (записать
filter в каждое FieldRenderer) — separate follow-up, требует refactor
form internals (currently 3 buckets identity/description/extra).

Section anchor chips + wide-mode ToC rail — deferred до backend
x-section schema metadata (CEO open question #5 в handoff).
This commit is contained in:
Zimin A.N.
2026-05-11 16:02:11 +03:00
parent a09c4113c1
commit d4000ddd3d
@@ -88,6 +88,18 @@ export type RecordDrawerProps = {
onDelete?: () => void onDelete?: () => void
/** Show [История] button + handler (только edit mode). */ /** Show [История] button + handler (только edit mode). */
onHistory?: () => void onHistory?: () => void
/** Optional sticky toolbar per handoff line 279: search полей by name +
* "только заполненные" toggle + counter. Caller controls state. Pass
* undefined чтобы скрыть toolbar (e.g. create mode without filter context). */
toolbar?: {
search: string
onSearchChange: (next: string) => void
onlyFilled: boolean
onOnlyFilledChange: (next: boolean) => void
/** "N / M полей" caption — visible / total. */
counterVisible: number
counterTotal: number
}
children: ReactNode children: ReactNode
} }
@@ -101,6 +113,7 @@ export function RecordDrawer({
isPending = false, isPending = false,
onDelete, onDelete,
onHistory, onHistory,
toolbar,
children, children,
}: RecordDrawerProps) { }: RecordDrawerProps) {
const { t } = useTranslation() const { t } = useTranslation()
@@ -177,10 +190,36 @@ export function RecordDrawer({
</div> </div>
</header> </header>
{/* === TOOLBAR (sticky) per handoff line 279 ===
* Optional — caller passes toolbar prop с search/onlyFilled state. */}
{toolbar && (
<div className="shrink-0 border-b border-line-2 px-5 py-2 flex items-center gap-3 bg-surface">
<input
type="search"
value={toolbar.search}
onChange={(e) => toolbar.onSearchChange(e.target.value)}
placeholder={t('drawer.searchPlaceholder', { defaultValue: 'Поиск полей' })}
className="flex-1 h-7 px-2.5 rounded-md border border-line bg-surface-2 text-body text-ink placeholder:text-mute focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
/>
<label className="inline-flex items-center gap-1.5 text-cell text-ink-2 select-none cursor-pointer shrink-0">
<input
type="checkbox"
checked={toolbar.onlyFilled}
onChange={(e) => toolbar.onOnlyFilledChange(e.target.checked)}
className="size-3.5 rounded-sm border-line accent-accent"
/>
{t('drawer.onlyFilled', { defaultValue: 'только заполненные' })}
</label>
<span className="text-mono text-mute tabular-nums shrink-0">
{toolbar.counterVisible}/{toolbar.counterTotal}
</span>
</div>
)}
{/* === BODY (scrolling) === {/* === BODY (scrolling) ===
* Phase 2 здесь будет: sticky toolbar (search + filled-only toggle + * Phase B остатки (deferred — требует x-section schema metadata):
* counter) → section anchor chips strip → optional left ToC rail в * - section anchor chips strip над form sections
* wide mode (CSS grid 180px / 1fr). Пока children — SchemaDrivenForm. */} * - wide-mode left ToC rail (180px) в CSS grid 180px/1fr layout */}
<div className="flex-1 overflow-y-auto px-5 py-4">{children}</div> <div className="flex-1 overflow-y-auto px-5 py-4">{children}</div>
{/* === FOOTER (sticky bottom) === */} {/* === FOOTER (sticky bottom) === */}