UNPKG

@activecollab/components

Version:

ActiveCollab Components

357 lines (342 loc) 17.4 kB
"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