@activecollab/components
Version:
ActiveCollab Components
115 lines • 6.53 kB
TypeScript
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