UNPKG

@activecollab/components

Version:

ActiveCollab Components

115 lines 6.53 kB
import React, { ReactNode } from "react"; import { StackedCardResizeAxis, StackedCardResizeSource } from "./resizePolicy"; /** * StackedCard — the hollow shell for cards on drag-and-drop surfaces. * * This is NOT the base for cards in lists. It is the card for surfaces where * dragging is a primary interaction: column boards and canvases. Content is * laid out top to bottom in `StackedCardRow`s, which makes every card a * portrait presentation of its data. * * ── The rule the whole subsystem hangs on ────────────────────────────────── * THE SHELL STAYS HOLLOW. There is no `contentType` enum here, no per-type * prop soup, no `if task …`. The shell owns only VISUAL STATES (selected, * disabled, pending, priority, drag source/preview, entered) and one intent * callback (`onResizeDelta`). Content is passed as `children` and composes the * layout primitives. Typed cards — an ImageCard, a LinkCard, a TaskCard — are * facades built on top of it in the consuming app, and adding one requires * ZERO changes in this file. * * ── What the shell does NOT do ──────────────────────────────────────────── * It implements no drag-and-drop, no selection bookkeeping, no resize policy * and no keyboard navigation. The card reports; the host decides. Drag * choreography (the drag layer, the landing indicator, the drop) belongs to * the host surface, which then feeds the result back as `isDragSource` / * `isDragPreview`. The shell forwards its ref and spreads DOM props, so a host * can attach `tabIndex`, ARIA, pointer and keyboard handlers to it. * * ── Resize ──────────────────────────────────────────────────────────────── * The grip is opt-in (`resizable`) and reports intent as a gesture: one * `onResizeStart`, a stream of `onResizeDelta` previews measured FROM THE * GESTURE START, and one `onResizeCommit` at the end — and only if the gesture * moved anything. A host applies previews for live feedback and records an undo * entry on the commit alone (spec §7e). * * Because the deltas are gesture-relative rather than incremental, a host can * resolve every event against the size it snapshotted on start, and never has * to keep a running accumulator. That is what makes a slow, few-pixels-at-a-time * drag keep growing instead of stalling under a snap step. * * The grip itself is a focusable `role="separator"` and handles arrow keys * (Shift for a coarse nudge). For the full policy — bounds, proportional mode, * step snapping, content minimums, Home/End and the live `aria-valuenow` — use * `useStackedCardResize` and spread its result through `resizeHandleProps`; * those handlers then own the grip and the three callbacks above stay quiet. * * ── Theming ─────────────────────────────────────────────────────────────── * Colours resolve through `--sc-*` custom properties with built-in fallbacks: * `--sc-selection-ring` (selection / focus / entered) and the cover scrim * family `--sc-scrim`, `--sc-scrim-strong`, `--sc-scrim-fg`. Declare any of * them on an ancestor to retint a whole surface. */ export interface IStackedCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onResize"> { /** Host-owned selection state — a ring around the card. */ selected?: boolean; /** Policy state: the card cannot be acted on. Dimmed, inert. */ disabled?: boolean; /** * Optimistic in-flight state — created or moved, not yet confirmed. Dimmed, * non-interactive and not draggable, but distinct from `disabled`: it is * transient, not a policy. */ pending?: boolean; /** * The full-size outline placeholder left behind in the source position while * a drag is in flight. Content is hidden but keeps its box, so neighbouring * cards never move. */ isDragSource?: boolean; /** * The 1:1 copy the host floats in its drag layer. Elevated shadow only — no * tilt, no scale, no translate. */ isDragPreview?: boolean; /** Focus model: the card has been entered and focus is on an inner control. */ entered?: boolean; /** Pin the hover treatment on, including revealed controls. For docs and specs. */ forceHover?: boolean; /** Show the bottom-right resize grip. Opt-in, off by default. */ resizable?: boolean; /** * Which axes the grip offers, as a cursor: `width` reads as a horizontal * handle for an auto-height card. Purely the affordance — what a delta means * is still the host's call. Defaults to `both`. */ resizeAxis?: StackedCardResizeAxis; /** Accessible name for the resize grip. Defaults to "Resize card". */ resizeLabel?: string; /** * Draws the emphasis border down the left edge. A data colour, passed in by * the caller (high priority, a label colour) — never baked into the shell. */ priorityColor?: string; /** Fired once when a resize gesture begins, before the first delta. */ onResizeStart?: (source: StackedCardResizeSource) => void; /** * Fired throughout a resize gesture with the delta measured FROM THE GESTURE * START — not from the previous event. The card reports; the host decides what * a delta means. Previews are live feedback, never an undoable change. */ onResizeDelta?: (dx: number, dy: number, source: StackedCardResizeSource) => void; /** * Fired once when the gesture ends (pointer up, lost pointer capture, key up), * and only if it moved the card. A bare click on the grip is not a change. */ onResizeCommit?: (source: StackedCardResizeSource) => void; /** * Props spread onto the grip, overriding the built-in behaviour entirely — * this is where `useStackedCardResize`'s return value goes when a host wants * the full policy instead of raw deltas. */ resizeHandleProps?: React.HTMLAttributes<HTMLSpanElement>; children: ReactNode; } export declare const StackedCard: React.ForwardRefExoticComponent<IStackedCardProps & React.RefAttributes<HTMLDivElement>>; //# sourceMappingURL=StackedCard.d.ts.map