@activecollab/components
Version:
ActiveCollab Components
357 lines (342 loc) • 17.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TaskSkeleton = exports.TaskPlaceholder = exports.TaskCard = void 0;
var _react = _interopRequireDefault(require("react"));
var _styledComponents = _interopRequireWildcard(require("styled-components"));
var _shared = require("./shared");
var _Icons = require("../../../components/Icons");
var _Loaders = require("../../../components/Loaders");
var _Signifier = require("../../../components/Signifier");
var _StackedCard = require("../../../components/StackedCard");
var _Tag = require("../../../components/Tag");
var _Typography = require("../../../components/Typography");
var _Typography2 = require("../../../components/Typography/Typography");
var _shared2 = require("../../shared");
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
/**
* TaskCard — the baseline-preservation card.
*
* This is the replacement for today's column card, so it must reproduce every
* behavior that card has (note 52104 §B2) — none of them are optional, each is
* a silent-regression risk. Every one is a prop here so the story can toggle it:
*
* - task-number prefix in the title (config-gated, mirrors `show_task_id`)
* - overdue date colouring (red ONLY when overdue AND not completed)
* - dependency counter (behind a feature-flag prop)
* - hidden-from-clients signifier (shown only when it DIFFERS from the
* project default)
* - inline assignee affordance (avatar, or an add-assignee placeholder)
* - completed (teal tint, no "…" menu, no overdue colour)
* - assignees appear ONCE (in the info line when the card has no
* date range, in the date row when it
* has one — never in both)
*
* The pending/optimistic and priority states are SHELL states (dimmed +
* pointer-events off; red left bar), so the card is wrapped in a `<StackedCard
* pending>` / `<StackedCard priorityColor>` by the host — they are not the
* card's own concern and live on the shell, not here.
*
* Everything is measured against the app: `ColumnCardBody.jsx`,
* `ColumnCardBodyInfo.jsx`, `ColumnCardDate.jsx` and
* `view.project_tasks_column_mode.less`.
*/
/**
* Eight labels, so the default card demonstrates the collapsed form. Colours are
* the labels' own, which is why they are literals.
*/
var DEMO_LABELS = [{
text: "DESIGN",
color: "#378ADD"
}, {
text: "BLOCKED",
color: "#D85A30"
}, {
text: "READY",
color: "#639922"
}, {
text: "REVIEW",
color: "#EF9F27"
}, {
text: "RESEARCH",
color: "#7F77DD"
}, {
text: "COPY",
color: "#8C5A2B"
}, {
text: "QA",
color: "#2F5DA8"
}, {
text: "RELEASE",
color: "#8A3E6B"
}];
/**
* How many labels read as text before the line has to collapse them, and how
* many dots stand in for the rest. Two colour-text `Tag`s fit a 260px card; past
* that the app's own answer (and the design system's mapping) is coloured dots
* plus an overflow count.
*/
var LABELS_AS_TEXT_MAX = 2;
var LABEL_DOTS_MAX = 5;
/* The completed tint fills the card body; the shell keeps the priority bar and
selection ring on top of it. */
var StyledBody = _styledComponents.default.div.withConfig({
displayName: "TaskCard__StyledBody",
componentId: "sc-5iuhg8-0"
})(["display:flex;flex-direction:column;", ""], function (_ref) {
var $completed = _ref.$completed;
return $completed && (0, _styledComponents.css)(["background:var(--sc-completed-tint,rgba(72,201,176,0.16));"]);
});
/**
* The info line — labels, then signifiers, then (only when the card has no date
* range) the assignees.
*
* The separator is the app's, rule for rule: a 2px round dot in
* `--color-theme-600` with `margin: 0 6px`, drawn by `::before` on every item
* EXCEPT the first (`view.project_tasks_column_mode.less`:
* `.c-signifier:not(:first-child):before`, `.label_single_container:not(:first-child)
* .label_item:before`, `.dot:before`). Because the rule keys off position rather
* than off "am I a signifier", a line that starts with a signifier — a task with
* counters but no labels — cannot open with a dangling dot.
*
* `font-size: 11px` is the `.labels` block's own; the vertical rhythm the app
* gets from `margin-top: 10px` comes from the `meta` row's role padding here.
*/
var StyledInfoLine = _styledComponents.default.div.withConfig({
displayName: "TaskCard__StyledInfoLine",
componentId: "sc-5iuhg8-1"
})(["display:flex;flex-wrap:wrap;align-items:center;width:100%;font-size:11px;> *{display:flex;align-items:center;min-height:18px;}> *:not(:first-child):not(.sc-assignees)::before{content:\"\";flex-shrink:0;width:2px;height:2px;margin:0 6px;border-radius:100%;background-color:var(--color-theme-600);}.sc-label{max-width:74px;}.sc-label .c-tag > div{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;max-width:60px;}.sc-dots{gap:4px;}.sc-assignees{flex:1 1 auto;justify-content:flex-end;}"]);
var StyledDot = _styledComponents.default.span.withConfig({
displayName: "TaskCard__StyledDot",
componentId: "sc-5iuhg8-2"
})(["width:11px;height:11px;border-radius:50%;background:", ";"], function (_ref2) {
var $color = _ref2.$color;
return $color;
});
/**
* The date row's text.
*
* A raw `<span>` in this Storybook inherits the browser's serif default at the
* ancestor's size, which is why the date used to read as huge serif type. The
* app sets the same four values in CSS on a plain `div` (`.due_date`: 13px,
* bold, 24px tall, `--color-theme-900`, `--red-alert` when `.overdue`), so the
* fix is to state them here — plus the font stack the missing reset does not
* supply.
*/
var StyledDate = _styledComponents.default.span.withConfig({
displayName: "TaskCard__StyledDate",
componentId: "sc-5iuhg8-3"
})(["height:24px;font-family:", ";font-size:13px;font-weight:bold;line-height:24px;letter-spacing:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;color:", ";"], _shared.CARD_FONT, function (_ref3) {
var $overdue = _ref3.$overdue;
return $overdue ? "var(--red-alert)" : "var(--color-theme-900)";
});
/* Avatars are limited to 3 and overlap, as MultiAssigneesPicker does. */
var StyledAssignees = _styledComponents.default.span.withConfig({
displayName: "TaskCard__StyledAssignees",
componentId: "sc-5iuhg8-4"
})(["display:flex;align-items:center;justify-content:flex-end;"]);
/* Inline assignee affordance placeholder — a dashed add-assignee target that
stands in until the real picker is wired at integration. */
var StyledAddAssignee = _styledComponents.default.button.withConfig({
displayName: "TaskCard__StyledAddAssignee",
componentId: "sc-5iuhg8-5"
})(["display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:1.5px dashed var(--color-theme-400);border-radius:50%;background:transparent;color:var(--color-theme-600);cursor:pointer;svg{width:14px;height:14px;display:block;}&:hover{border-color:var(--sc-selection-ring);color:var(--color-theme-800);}"]);
/** The app's assignee limit (MultiAssigneesPicker `limit={3}`). */
var ASSIGNEE_LIMIT = 3;
var TaskCard = exports.TaskCard = function TaskCard(_ref4) {
var _ref4$title = _ref4.title,
title = _ref4$title === void 0 ? "Implement the design from the reference on this page" : _ref4$title,
_ref4$cover = _ref4.cover,
cover = _ref4$cover === void 0 ? false : _ref4$cover,
_ref4$playable = _ref4.playable,
playable = _ref4$playable === void 0 ? false : _ref4$playable,
_ref4$coverControls = _ref4.coverControls,
coverControls = _ref4$coverControls === void 0 ? false : _ref4$coverControls,
_ref4$showTaskId = _ref4.showTaskId,
showTaskId = _ref4$showTaskId === void 0 ? false : _ref4$showTaskId,
_ref4$taskId = _ref4.taskId,
taskId = _ref4$taskId === void 0 ? 4521 : _ref4$taskId,
_ref4$showProjectName = _ref4.showProjectName,
showProjectName = _ref4$showProjectName === void 0 ? false : _ref4$showProjectName,
_ref4$projectName = _ref4.projectName,
projectName = _ref4$projectName === void 0 ? "Marketing Site" : _ref4$projectName,
_ref4$labels = _ref4.labels,
labels = _ref4$labels === void 0 ? DEMO_LABELS : _ref4$labels,
_ref4$subtaskCount = _ref4.subtaskCount,
subtaskCount = _ref4$subtaskCount === void 0 ? 2 : _ref4$subtaskCount,
_ref4$commentCount = _ref4.commentCount,
commentCount = _ref4$commentCount === void 0 ? 7 : _ref4$commentCount,
_ref4$dateLabel = _ref4.dateLabel,
dateLabel = _ref4$dateLabel === void 0 ? "May 8 – Sep 10" : _ref4$dateLabel,
_ref4$overdue = _ref4.overdue,
overdue = _ref4$overdue === void 0 ? false : _ref4$overdue,
_ref4$showDependencie = _ref4.showDependencies,
showDependencies = _ref4$showDependencie === void 0 ? false : _ref4$showDependencie,
_ref4$dependencyCount = _ref4.dependencyCount,
dependencyCount = _ref4$dependencyCount === void 0 ? 2 : _ref4$dependencyCount,
_ref4$hiddenFromClien = _ref4.hiddenFromClients,
hiddenFromClients = _ref4$hiddenFromClien === void 0 ? false : _ref4$hiddenFromClien,
_ref4$projectDefaultH = _ref4.projectDefaultHidden,
projectDefaultHidden = _ref4$projectDefaultH === void 0 ? false : _ref4$projectDefaultH,
_ref4$assigned = _ref4.assigned,
assigned = _ref4$assigned === void 0 ? true : _ref4$assigned,
assignees = _ref4.assignees,
_ref4$completed = _ref4.completed,
completed = _ref4$completed === void 0 ? false : _ref4$completed;
// Signifier appears only when the task's visibility differs from the default.
var showHiddenSignifier = hiddenFromClients !== projectDefaultHidden;
// Overdue colour is suppressed on completed tasks.
var dateIsOverdue = overdue && !completed;
var hasDate = dateLabel != null && dateLabel !== "";
var collapseLabels = labels.length > LABELS_AS_TEXT_MAX;
/* The assignee slot: the real avatars when the host has them, the demo avatar
when it only says "assigned", the add affordance when it says nothing is. */
var assigneeSlot = assigned ? /*#__PURE__*/_react.default.createElement(StyledAssignees, {
className: "sc-assignees",
"data-fixed": true
}, assignees ? assignees.slice(0, ASSIGNEE_LIMIT).map(function (person) {
return /*#__PURE__*/_react.default.createElement(_shared2.RoundAvatar, {
key: person.name,
$bg: person.color,
$size: 24,
$overlap: true
}, person.initials);
}) : /*#__PURE__*/_react.default.createElement(_shared2.RoundAvatar, {
$bg: _shared2.AVATAR_COLORS.teal,
$size: 24,
$bordered: false
}, "MS")) : /*#__PURE__*/_react.default.createElement(StyledAssignees, {
className: "sc-assignees",
"data-fixed": true
}, /*#__PURE__*/_react.default.createElement(StyledAddAssignee, {
type: "button",
"aria-label": "Assign task"
}, /*#__PURE__*/_react.default.createElement(_Icons.PersonPlusIcon, null)));
/* Signifiers in the app's fixed order: subtasks → comments → dependencies →
hidden-from-clients. Each is one child of the info line, so the line's own
separator rule spaces them. */
var signifiers = [];
if (subtaskCount > 0) {
signifiers.push(/*#__PURE__*/_react.default.createElement(_Signifier.Signifier, {
key: "subtasks",
type: _Icons.ChecklistSmallIcon,
value: subtaskCount,
tooltipText: "Subtasks"
}));
}
if (commentCount > 0) {
signifiers.push(/*#__PURE__*/_react.default.createElement(_Signifier.Signifier, {
key: "comments",
type: _Icons.MessageSmallIcon,
value: commentCount,
tooltipText: "Comments"
}));
}
if (showDependencies && dependencyCount > 0) {
signifiers.push(/*#__PURE__*/_react.default.createElement(_Signifier.Signifier, {
key: "dependencies",
type: _Icons.DependencySmallIcon,
value: dependencyCount,
tooltipText: "Dependencies"
}));
}
if (showHiddenSignifier) {
signifiers.push(/*#__PURE__*/_react.default.createElement(_Signifier.Signifier, {
key: "hidden",
type: _Icons.EyeOffSmallIcon,
tooltipText: "Hidden from clients"
}));
}
/* The info line exists when it has something to say — labels, signifiers, or
the assignees it has to carry because there is no date row. */
var showInfoLine = labels.length > 0 || signifiers.length > 0 || !hasDate;
return /*#__PURE__*/_react.default.createElement(StyledBody, {
$completed: completed
}, cover ? /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardCover, {
aspectRatio: _StackedCard.STACKED_CARD_COVER_RATIO,
centerSlot: playable ? _shared.playControl : undefined,
topRightSlot: coverControls ? (0, _shared.coverManagement)() : undefined
}, /*#__PURE__*/_react.default.createElement("div", {
style: {
width: "100%",
height: "100%",
background: "var(--color-theme-300)"
}
})) : null, /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, {
role: "title"
}, /*#__PURE__*/_react.default.createElement(_shared.StyledTitleBlock, null, showProjectName ? /*#__PURE__*/_react.default.createElement(_shared.CardProjectLine, null, projectName) : null, /*#__PURE__*/_react.default.createElement(_shared.StyledClamp, null, showTaskId ? /*#__PURE__*/_react.default.createElement(_Typography2.Typography, {
as: "span",
variant: "Caption 1",
color: "tertiary"
}, "#".concat(taskId, " ")) : null, title)), completed ? null : /*#__PURE__*/_react.default.createElement(_shared.EntityDots, {
"aria-label": "Task options"
})), showInfoLine ? /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, {
role: "meta"
}, /*#__PURE__*/_react.default.createElement(StyledInfoLine, null, collapseLabels ? /*#__PURE__*/_react.default.createElement("span", {
className: "sc-dots"
}, labels.slice(0, LABEL_DOTS_MAX).map(function (label) {
return /*#__PURE__*/_react.default.createElement(StyledDot, {
key: label.text,
$color: label.color
});
}), labels.length > LABEL_DOTS_MAX ? /*#__PURE__*/_react.default.createElement(_Typography.Caption1, {
color: "tertiary"
}, "+".concat(labels.length - LABEL_DOTS_MAX)) : null) : labels.map(function (label) {
return /*#__PURE__*/_react.default.createElement("span", {
className: "sc-label",
key: label.text
}, /*#__PURE__*/_react.default.createElement(_Tag.Tag, {
name: label.text,
color: label.color,
showDot: false
}));
}), signifiers, hasDate ? null : assigneeSlot)) : null, hasDate ? /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, {
role: "footer"
}, /*#__PURE__*/_react.default.createElement(StyledDate, {
$overdue: dateIsOverdue
}, dateLabel), assigneeSlot) : null);
};
/* ---- placeholder + skeleton ---- */
var TaskPlaceholder = exports.TaskPlaceholder = function TaskPlaceholder(props) {
return /*#__PURE__*/_react.default.createElement(_shared.Placeholder, _extends({
icon: /*#__PURE__*/_react.default.createElement(_Icons.ChecklistSmallIcon, null),
label: "Name this task"
}, props));
};
/* Skeleton mirrors the task anatomy row for row: title, meta, footer. */
var TaskSkeleton = exports.TaskSkeleton = function TaskSkeleton() {
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, {
role: "title"
}, /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, {
style: {
height: 14,
width: "90%"
}
})), /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, {
role: "meta"
}, /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, {
style: {
height: 10,
width: "55%"
}
})), /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, {
role: "footer"
}, /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, {
style: {
height: 12,
width: 64
},
"data-fixed": true
}), /*#__PURE__*/_react.default.createElement("span", null), /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, {
"data-fixed": true,
style: {
width: 24,
height: 24,
borderRadius: "50%"
}
})));
};
//# sourceMappingURL=TaskCard.js.map