@activecollab/components
Version:
ActiveCollab Components
128 lines (122 loc) • 6.07 kB
JavaScript
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