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