UNPKG

@activecollab/components

Version:

ActiveCollab Components

267 lines 13.1 kB
import { ReactElement } from "react"; import { ProjectCardLabel, ProjectCardPerson, TaskCardLabel } from "../stackedCard/content"; /** * The board's third "draw a box, then say what goes in it" element, after the * link and the file card. The gesture ladder is deliberately the same one those * two already established, because it is the only one the dock teaches: * * armed the Project Elements dock button opens a submenu (Project / * Task); picking one arms the tool and puts the canvas in * crosshair mode. * drawn mouse down → drag → up leaves a dashed PLACEHOLDER at the drawn * box (clamped to a card-sized minimum), exactly where the real * card will sit. * picking the release opens the picker — a centred popover replicating the * app's `MenuSelector/ProjectList` (and, for tasks, the two-step * `ProjectTaskMenuSelector`: projects, then that project's tasks). * resolving a short skeleton flash in the placeholder's own box, standing in * for "the board is fetching this record's details". * resolved the placeholder becomes the real card, anchored at the * placeholder's top-left and grown to the card's natural size. * * Escape or a click on the overlay cancels: the picker closes and the * placeholder is thrown away, so a cancelled gesture leaves no debris. */ export declare const PLACEHOLDER_MIN_WIDTH = 260; export declare const PLACEHOLDER_MIN_HEIGHT = 120; export declare const PROJECT_CARD_WIDTH = 300; export declare const TASK_CARD_WIDTH = 260; export declare const RESOLVE_MS = 480; export type ProjectElementKind = "project" | "task"; export type ProjectElementStatus = "placeholder" | "resolving" | "resolved"; /** True for the two board tools this module owns. */ export declare const isProjectTool: (tool: string) => tool is ProjectElementKind; /** * The people and labels the board carries are exactly what the shared content * cards read, so the picker's types ARE the cards' types — one shape, so a card * cannot be fed something it does not understand. */ export type PickerPerson = ProjectCardPerson; export type PickerTaskLabel = TaskCardLabel; export interface PickerTask { id: string; /** The task number the app prefixes the name with (`#123: …`). */ number: number; name: string; /** The task list the task belongs to — the picker's group header. */ taskList: string; label?: PickerTaskLabel; subtasks?: number; comments?: number; date?: string; assignees: PickerPerson[]; } /** * A project label. The app renders it as a FILLED pill whose background is the * label's own colour and whose text is a darker shade of it * (`ProjectLabel.jsx`: `EntityLabel` with `backgroundColor: label.color` and * `color: label.darker_text_color`), which is the design system's `Chip` — not * the colour-text `Tag` the column cards use. Both colours are data. The shape * is the shared `ProjectCard`'s own. */ export type PickerProjectLabel = ProjectCardLabel; export interface PickerProject { id: string; name: string; /** * The client company, and the picker's group header. NULL for an internal * project: the card then reads "Internal" rather than repeating the owner * company's own name back at the reader, which says nothing. */ client: string | null; /** The description excerpt, when the project has one. */ description?: string; /** A project need not have a leader. */ lead?: PickerPerson; /** A project need not have a label. */ label?: PickerProjectLabel; tasks: PickerTask[]; } /** * What an internal project's client line says. The word belongs to the card (it * is the card that has to decide what to print for a project with no client), so * the board re-exports the card's own constant rather than keeping a second copy * that could drift. */ export declare const INTERNAL_CLIENT_LABEL = "Internal"; export declare const projectClientLabel: (project: PickerProject) => string; export interface PickerProjectGroup { client: string; projects: PickerProject[]; } export interface PickerTaskGroup { taskList: string; tasks: PickerTask[]; } export declare const PICKER_PEOPLE: Record<string, PickerPerson>; export declare const PICKER_LABELS: Record<string, PickerTaskLabel>; export declare const PICKER_PROJECT_LABELS: Record<string, PickerProjectLabel>; export declare const PICKER_PROJECTS: PickerProject[]; export declare const groupProjects: (projects: PickerProject[]) => PickerProjectGroup[]; export declare const groupTasks: (tasks: PickerTask[]) => PickerTaskGroup[]; /** `#123: Name` — `utils/taskDisplayName`, the app's own task display name. */ export declare const taskDisplayName: (task: PickerTask) => string; /** * A board is shared with people who do not all see the same projects. When a * card points at a task or project the current viewer cannot open, the board * cannot just drop it: the element is part of everyone else's layout, and a * hole would be a worse lie than a blank. So the card stays and says exactly * one thing — something is here, and it is not yours to see. * * This follows the app's existing limited-access language (Workload): * - the wording is "Limited Access", the phrase `EntityPlaceholder`, * `WorkloadCalendarEvent`, Expenses and Timelist all already use; * - nothing else is rendered. Workload's own limited cell * (`.workload_null_project_cell`) shows an aggregate and no name, and the * shape it is fed is literally `{ hoursOccupied, leftOffset }` — a quantity * and a position, never an identity; * - the surface is muted and hatched, the treatment the app reserves for * "this area is not live" (weekend / day-off cells, `--muted-surface`); * - no affordance suggests opening: Workload points the link at "#", disables * the name tooltip so the title cannot leak, and marks blocked interactions * `cursor: not-allowed`. */ export declare const LIMITED_ACCESS_LABEL = "Limited Access"; export declare const LIMITED_ACCESS_MESSAGE = "You don't have access to this item."; /** A project / task element on the board. Position lives in `positions`. */ export interface ProjectElementDef { id: string; kind: ProjectElementKind; /** * The element's width — the drawn placeholder box first, then the card's * natural width, written once when the card resolves. * * It is one field and not two (a placeholder box plus a derived card width) * because the Inspector edits this: a derived width would be read-only by * construction, and a second stored width would be a copy that can disagree. */ width: number; /** The placeholder box height. A resolved card is auto-height. */ height: number; status: ProjectElementStatus; /** * The viewer cannot access what this card points at, so there is nothing to * fill `project` / `task` with and never will be — the details are withheld * by the server, not merely unfetched. Everything the card can do is * withheld with them: it does not move, resize or open. */ limited?: boolean; /** Filled once the picker returns; for a task this is its project. */ project: PickerProject | null; task: PickerTask | null; } /** The natural width a resolved card of this kind grows to. */ export declare const projectCardWidth: (kind: ProjectElementKind) => number; /** * What the card is OF — the task's or the project's own name. * * These cards are a view of a record that lives in the project, so the board * does not get to name them: the title is the entity's, and it is read-only * wherever it appears. A placeholder has no entity yet, so it says what it is * still waiting for. */ export declare const projectElementTitle: (element: ProjectElementDef) => string; export declare const SEEDED_PROJECT_ELEMENTS: ProjectElementDef[]; export declare const SEEDED_PROJECT_ELEMENT_POSITIONS: Record<string, { x: number; y: number; }>; export declare const StyledProjectElementBox: import("styled-components").StyledComponent<"div", any, {}, never>; export declare const StyledProjectDraft: import("styled-components").StyledComponent<"div", any, {}, never>; export declare const StyledPlaceholder: import("styled-components").StyledComponent<"div", any, {}, never>; export declare const StyledResolving: import("styled-components").StyledComponent<"div", any, {}, never>; /** * The limited-access face — the card-shaped translation of Workload's black * `.workload_null_project_cell`. * * The app hardcodes that cell `#000000` on `#ffffff` text, which works there * because it always sits on light paper. A canvas is not always light: on the * dark board a black slab would sink into the background instead of reading as * something withheld. So the same idea is expressed with the design system's * own "not live" tokens — `--muted-surface` / `--muted-surface-text` are the * pair the app itself substitutes for muted surfaces in dark themes, and they * are a legible, deliberately flat combination in every theme. * * The -45° hatch is copied stroke for stroke from the app's unavailable * surfaces (weekend and day-off cells): `transparent 5px, <line> 5px, <line> * 5.7px`, with `--stripe-pattern` as the line — the token workload.less uses * for exactly this in dark themes. */ export declare const StyledLimitedCard: import("styled-components").StyledComponent<"div", any, {}, never>; export interface TaskFaceProps { project: PickerProject; task: PickerTask; selected?: boolean; } /** * The task face — the shared `TaskCard`, with the project line switched on. * * The board writes no anatomy of its own: the title clamp, the info line (labels * as colour text, the counter signifiers and their 2px dot separators) and the * date / assignees row all live in the content card, where the column-view * baseline they preserve is documented. The one thing the board asks for is the * project line above the title: on a canvas a task card has lost the column that * used to say which project it came from. * * The title is the app's own display name (`#123: Name`, `taskDisplayName`), so * the card's separate task-number prefix stays off. */ export declare const TaskElementCard: ({ project, task, selected, }: TaskFaceProps) => ReactElement; /** * The project face — the shared `ProjectCard`. * * The whiteboard's face spec and the content type's are the SAME spec, so it is * implemented once, in the card: name and client, an optional description * excerpt, and one row carrying the leader on the left and the label on the * right. No progress indicator, and no row at all when the project has neither a * leader nor a label. */ export declare const ProjectElementCard: ({ project, selected, }: { project: PickerProject; selected?: boolean; }) => ReactElement; /** * The limited-access card. Deliberately the shortest component in this file: * one phrase, no icon, no entity data of any kind. * * No icon because the app uses none — Expenses and Timelist both render the * bare `Caption1 weight="bold"` "Limited Access", and an icon here would be a * second signal competing with the hatch. Even the KIND (task vs project) is * withheld: the viewer is told that a card exists, not what sort of thing it * points at. * * The tooltip is allowed to exist because it carries the viewer's situation, * not the entity's identity — the opposite of the tooltip Workload disables on * a limited event, which would have leaked the name. */ export declare const LimitedElementCard: () => ReactElement; /** One board element, in whichever of its three states it currently is. */ export declare const ProjectElementNode: ({ element, selected, }: { element: ProjectElementDef; /** * Host-owned selection. A resolved card is a `StackedCard`, and the shell draws * its own ring from `--sc-selection-ring` (the board sets it to the selection * blue) — the same arrangement the link and file cards already use, which is * why their wrapper carries `is-card` to suppress the box's second ring. */ selected?: boolean; }) => ReactElement; export interface PickerRowProps { id: string; name: string; onSelect: () => void; } export interface ProjectElementPickerProps { kind: ProjectElementKind; onSelectProject: (project: PickerProject) => void; onSelectTask: (project: PickerProject, task: PickerTask) => void; onCancel: () => void; } /** * One picker, two flows — the same split the app makes between * `MenuSelector/ProjectList` (one step) and `ProjectTaskMenuSelector` (project, * then task). The task flow deliberately drops the app's "Directly on project" * default option: a task card has to be a task. */ export declare const ProjectElementPicker: ({ kind, onSelectProject, onSelectTask, onCancel, }: ProjectElementPickerProps) => ReactElement; //# sourceMappingURL=projectElements.d.ts.map