UNPKG

@activecollab/components

Version:

ActiveCollab Components

128 lines (122 loc) 6.07 kB
import React from "react"; import styled from "styled-components"; import { EntityDots, StyledTitleBlock } from "./shared"; import { Chip } from "../../../components/Chip"; import { StackedCardRow as StackRow } from "../../../components/StackedCard"; import { Body2, Caption1, Caption2, Header3 } from "../../../components/Typography"; import { RoundAvatar } from "../../shared"; /** * ProjectCard — a project as a stacked card. * * The sixth content type, and the second one that mirrors a project-management * record rather than something authored on the surface (TaskCard is the first). * It composes the same shell and the same rows; nothing in `components/StackedCard` * knows it exists. * * The face is the app's Projects page, read off `ProjectListGroup.jsx`: * - the NAME is the card's title (`Header3`, as in the grid card); * - the CLIENT sits under it — `Entity.Property "entity-property-client"`, the * company the project is for; * - the DESCRIPTION is the project's `body`, the grid card's `Body2` tertiary * excerpt, clamped because the whole text lives on the project and this is a * reference to it, not a copy; * - the LEADER and the LABEL share one row. The label is 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`, * `color: label.darker_text_color`) — the design system's `Chip`, not the * colour-text `Tag` the column cards use for task labels. * * There is deliberately NO progress indicator. A single percentage is the one * number on a project that cannot be read without knowing how it was computed, * and a card is a place people put a project to talk ABOUT it — the number * invites a conversation about the number instead. */ /** A project label carries both of the colours the app stores for it. */ /** * What an internal project's client line says. * * A project with no client belongs to the account's own company, and printing * that company's name back at someone who works there says nothing. So the line * states the fact instead. */ export const INTERNAL_CLIENT_LABEL = "Internal"; /** One rule for the client line, so no surface can label the same project twice. */ export const projectClientLine = client => client != null ? client : INTERNAL_CLIENT_LABEL; /* The description excerpt. Three lines is the most a card can carry before it stops being a card, so it clamps. */ const StyledExcerpt = styled(Body2).withConfig({ displayName: "ProjectCard__StyledExcerpt", componentId: "sc-1afak94-0" })(["display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;"]); const StyledName = styled(Header3).withConfig({ displayName: "ProjectCard__StyledName", componentId: "sc-1afak94-1" })(["overflow:hidden;white-space:nowrap;text-overflow:ellipsis;"]); const StyledClient = styled(Caption2).withConfig({ displayName: "ProjectCard__StyledClient", componentId: "sc-1afak94-2" })(["display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;"]); /** * One row for the two facts that are each optional: the leader on the LEFT, the * label on the RIGHT. `space-between` is what keeps the label right when there is * no leader beside it, and the row is not rendered at all when the project has * neither — a bare project produces a small card, not a card with empty * furniture in it. * * No rule above it: the card carries no internal separators. The row's own role * padding is what sets it apart from the excerpt, the same way every other row in * every other content type is set apart — a hairline here would be the only line * inside any card in the set. */ const StyledMetaRow = styled(StackRow).withConfig({ displayName: "ProjectCard__StyledMetaRow", componentId: "sc-1afak94-3" })(["justify-content:space-between;.sc-leader{display:flex;align-items:center;gap:8px;min-width:0;}.sc-leader > .typography{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;}.sc-label{flex-shrink:0;display:flex;}"]); export const ProjectCard = _ref => { let _ref$name = _ref.name, name = _ref$name === void 0 ? "Marketing Site" : _ref$name, _ref$client = _ref.client, client = _ref$client === void 0 ? "ActiveCollab Inc." : _ref$client, description = _ref.description, leader = _ref.leader, label = _ref.label; return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(StackRow, { role: "title" }, /*#__PURE__*/React.createElement(StyledTitleBlock, null, /*#__PURE__*/React.createElement(StyledName, null, name), /*#__PURE__*/React.createElement(StyledClient, { color: "tertiary" }, projectClientLine(client))), /*#__PURE__*/React.createElement(EntityDots, { "aria-label": "Project options" })), description ? /*#__PURE__*/React.createElement(StackRow, { role: "custom" }, /*#__PURE__*/React.createElement(StyledExcerpt, { color: "secondary" }, description)) : null, leader || label ? /*#__PURE__*/React.createElement(StyledMetaRow, { role: "footer" }, leader ? /*#__PURE__*/React.createElement("span", { className: "sc-leader" }, /*#__PURE__*/React.createElement(RoundAvatar, { $bg: leader.color, $size: 24, $bordered: false, "data-fixed": true }, leader.initials), /*#__PURE__*/React.createElement(Caption1, { color: "secondary" }, leader.name)) : /*#__PURE__*/ /* Keeps the label on the right when there is no leader to push it there — an empty flex item is cheaper than a second layout. */ React.createElement("span", null), label ? /*#__PURE__*/React.createElement("span", { className: "sc-label", "data-fixed": true }, /*#__PURE__*/React.createElement(Chip, { size: "small", label: label.text, backgroundColor: label.color, color: label.textColor, typographyProps: { variant: "Caption 2", weight: "bold" } })) : null) : null); }; //# sourceMappingURL=ProjectCard.js.map