import "./timeline.css"; import type * as React from "react"; import { type ReactElement, type ReactNode } from "react"; import { useRender } from "@base-ui/react/use-render"; import { type ColorName } from "./color_tokens"; import { type IconName } from "./icon"; import { type StackedMark } from "./mark_stack"; import { type Statement } from "./row"; import { type OptionMark } from "./status"; import { type StyleProps } from "./style_props"; import { type InkColor } from "./text_ink"; export interface EntryMember { id: string; name: string | null; image?: string | null; email?: string | null; } export interface EntryFile { id: string; filename: string; mime_type: string; url?: string; thumbnail_url?: string; } /** A union, since a log is written by people, automations and outside parties: a bare member id would call * a system-sent email "unknown member". */ export type EntryAuthor = { kind: "member"; memberId: string; } | { kind: "system"; name?: string; } | { kind: "party"; name: string; mark?: StackedMark; }; /** The lead of an entry no person wrote or party signed: a glyph in a tinted disc (a spinner over it while * `loading`), or what the entry is as a row leads with it (`StackedMark`): a thing (`kind` absent) by its picture, * `null` the placeholder tile of the one it lacks, so the column keeps its edge; someone by their face, `null` their * initials. A mark states no name. */ export type EntryMark = { icon: IconName; color?: InkColor; loading?: boolean; } | { image: StackedMark["image"]; kind?: StackedMark["kind"]; }; /** Where an entry stands — a status option's dot and word; `fail`, a stop not taken, drawn crossed out. */ export interface EntryStage { key: string; label: string; color?: ColorName; /** The option's own mark, drawn in the dot's place. */ mark?: OptionMark; verdict?: "pass" | "fail"; } /** An option run with its mark, drawn in the register cell's mark and hue at the line's rung. */ export interface EntryRun { label: string; mark?: OptionMark; color?: ColorName; } /** A run on an entry's line: words, a reference, an option run, or a node that draws its own subject — a * member with their face, a linked row with its picture. */ export type EntryLineRun = Statement | EntryRun | ReactElement; export interface Entry { id: string; /** Absent where the list never states authors: the title is the when alone. A `system` author instead * would credit automation with people's entries. Exclusive with `mark`. */ author?: EntryAuthor; mark?: EntryMark; /** The entry's name: present, it leads and who/when move to the first line; absent, who/when lead as in * a diary. Two lines, since it is a scan target. */ title?: string; content: string; /** The supporting lines, as the caller states them: `[["Email", "Sent"], ["Partner clinic"]]`, a * `{ reference }` never breaking inside. Two at most (a third throws), each a `Row.Line`. An option run * leads with its mark ({@link EntryRun}). Money goes here unless it is the one figure compared down the * list — `figure`. */ lines?: readonly (readonly EntryLineRun[])[]; /** The option's dot and word, as the register draws it (`Row.Stage`). */ stage?: EntryStage; /** `badge` (default) stands it where a row's stage stands; `line` states it on the last supporting line. */ stageOn?: "badge" | "line"; /** Preformatted, on the right edge (`Row.Figure`); struck when `stage` is a failed outcome. */ figure?: string; error?: string; files?: EntryFile[] | null; /** The axis: an ISO datetime, or a bare date, which files under its day and prints no clock. Entries whose * `at` ties keep the order they are passed in. Absent, the entry is a plan not yet placed in time: drawn first, * under the pack's "Upcoming", quiet — its way to a time is its own act (`renderActs`). */ at?: string; /** The words standing where the clock goes, for an entry placed by a part of its day ("Morning") rather * than a time; the formatted `at` otherwise. */ when?: string; edited_at?: string; /** Something that happened to the subject — a stage reached — among the list's own entries: drawn in its * place, never a door nor an answer. */ event?: true; } export interface TimelineLabels { empty: string; edit: string; delete: string; edited: string; save: string; cancel: string; actions: string; unknownMember: string; editPlaceholder: string; /** The standing worn by the entry that answers the list. */ answer: string; /** Named by whose entry it is: identical presses are one control to a screen reader. */ markAnswer: (author: string) => string; automation: string; /** Named by the entry: identical "Open" presses are one control to a screen reader. */ open: (name: string) => string; /** The noun a clamped entry's disclosure shows and hides. */ whole: string; } export interface EntryEditFormProps { entry: Entry; onSave: (content: string, files?: EntryFile[] | null) => void; onCancel: () => void; submitting: boolean; labels: TimelineLabels; } /** The list's contract, not the row's, because exclusivity is a fact about the set: a per-row flag would * let two replies both read as the answer. Without `onMark` the standing is drawn, not offered. */ export interface EntryAnswer { /** `null` while the question stands open. */ id: string | null; onMark?: (id: string) => void | Promise; } /** The list's own composer: a new entry written by its words. */ export interface TimelineCompose { /** Handed the trimmed words as the field clears; answering `false` hands them back to it — a refused * write loses nothing typed. */ onPost: (text: string) => boolean | Promise; /** The field's hint and name; defaults to the pack's. */ placeholder?: string; /** The send verb's name; defaults to the pack's. */ postLabel?: string; } /** A day head's count from a first day, figure and verb; requires `group: "day"`. */ export interface TimelineDay { /** The day counted as day 1 (an ISO date): each head from it on reads "Day N — ", as a trip's or a * course's days are named; a day before it reads its date alone. */ from?: string; /** The day's figure, handed the day's entries as drawn. */ total?: (key: string, entries: readonly Entry[]) => ReactNode; /** The day head's add verb, handed the day so the form opens dated. */ onAdd?: (key: string) => void; addLabel?: string; } export interface TimelineProps extends StyleProps { entries: readonly Entry[]; /** `oldest` (default) for a correspondence or schedule, with a composer under the last; `newest` for a * log. */ order?: "oldest" | "newest"; /** A head per day in the kit's date words with its count, days in `order`, each entry's when its time * alone. Off by default: three replies want no heads. */ group?: "day"; /** A log's rail: `dots` marks each entry, `numbered` writes its ordinal within its day. Under * `group: "day"` it runs per day from the head. Off by default: a thread's time order is not a * sequence. An agenda — `oldest` under `group: "day"` — draws none: its day heads are headings and each * entry leads with its own mark, so a node beside it says nothing twice. */ rail?: "dots" | "numbered"; day?: TimelineDay; /** Where the list's own entries are written: on top of a `newest` log, under the last of an `oldest` * thread, so the words land where they will be read. Omit for a list nobody writes here. */ compose?: TimelineCompose; answer?: EntryAnswer; /** The viewer's member id, for the author-only edit and delete; null hides them. */ currentMemberId?: string | null; /** Absent, every member author reads as `unknownMember`. */ resolveMember?: (memberId: string) => EntryMember | null; /** Present, each entry's band opens it (not while editing) and its words are a three-line preview; * absent, the words are whole. */ onOpen?: (id: string) => void; /** Lines an entry's words take before the rest folds behind a disclosure, for a log read here. Each * unfolds alone; a fitting entry draws none. `onOpen`'s preview wins. */ clamp?: number; /** An entry's own acts, drawn with its row's controls; nothing returned draws none. */ renderActs?: (entry: Entry) => React.ReactNode; /** Omit for a read-only list. */ onEdit?: (id: string, content: string, files?: EntryFile[] | null) => void | Promise; /** Omit to hide delete. */ onDelete?: (id: string) => void | Promise; /** Defaults to picture tiles opening the viewer, then badges and names for other files. */ renderFiles?: (files: EntryFile[]) => React.ReactNode; /** An entry's words and whatever else; defaults to the stored text. Called for every entry; nothing * returned draws no box; a richer entry returns an `EntryBody`. The notation is the consumer's: parsing * everything would render a plain comment's `**` as emphasis. */ renderContent?: (entry: Entry) => React.ReactNode; /** Defaults to a text-only editor that keeps existing files. */ renderEditForm?: (props: EntryEditFormProps) => React.ReactNode; /** Defaults to the kit's date words (the clock only where the stamp has one), or the time alone under * `group`; a given formatter is used as is. */ formatTimestamp?: (iso: string) => string; emptyState?: React.ReactNode; labels?: Partial; testID?: string; ref?: React.Ref; render?: useRender.RenderProp; } /** An entry placed in time. */ type Dated = Required>; /** The day a `Timeline` files each dated entry under, for a caller folding on the heads; reads the pack's zone. */ export declare function useEntryDay(): (entry: Dated) => string; /** Callers that split the list split this order, so what they keep in view is what the list draws first. */ export declare function orderEntries(entries: readonly T[], order: "oldest" | "newest"): T[]; export interface EntryBodyProps extends StyleProps { children?: ReactNode; testID?: string; ref?: React.Ref; render?: useRender.RenderProp; } export interface EntryBodyRungProps { children?: ReactNode; } /** An entry richer than its words: three rungs drawn in one order — `Words` (says), `Evidence` (shows), * `Detail` (states, or keeps behind a fold). Sorted, not CSS-`order`ed, so a screen reader hears the * drawn order; empty rungs draw nothing. Under an opening list the words are the preview, evidence takes * its own presses, and detail is left to the open. No rung names a content kind. */ export declare function EntryBody(props: EntryBodyProps): ReactElement> | null; /** A preview under an opening list, else held to `clamp` with a disclosure only when the words overflow — * measured off the drawn box, since the wrap is the column's; measured while clamped, remembered while * open. */ declare function EntryBodyWords({ children }: EntryBodyRungProps): React.JSX.Element; declare function EntryBodyEvidence({ children }: EntryBodyRungProps): React.JSX.Element; declare function EntryBodyDetail({ children }: EntryBodyRungProps): React.JSX.Element; export declare namespace EntryBody { var Words: typeof EntryBodyWords; } export declare namespace EntryBody { var Evidence: typeof EntryBodyEvidence; } export declare namespace EntryBody { var Detail: typeof EntryBodyDetail; } /** The timeline: one entry per row from `order`'s end, under day heads with `group`, on the kit's rail * with `rail`, with author-only edit and delete. Pair it with the kit `Composer` (attach = * `actionsButton` + `files`), never a bespoke comment box. */ export declare function Timeline(props: TimelineProps): ReactElement>;