UNPKG

@activecollab/components

Version:

ActiveCollab Components

203 lines (195 loc) 7.65 kB
import React from "react"; import styled from "styled-components"; /** * FeatureAvailability — a compact, data-driven block that states where a * feature is available: on which hosting, on which plans and to which roles. * * It is the reusable version of the availability panel used in the Help Docs * example articles, extracted so several stories (and, later, the real * PortableText renderer) can share one implementation and one visual language: * * • green check → available * • analog clock → decided but not shipped yet ("to be determined") * • strikethrough → not included * * The component is theme-agnostic (all colors come from `--color-*` tokens) and * self-contained (it carries its own typography, so it renders correctly * outside the article body as well). */ /* ------------------------------------------------------------------ */ /* Marks */ /* ------------------------------------------------------------------ */ const CheckMark = () => /*#__PURE__*/React.createElement("svg", { className: "ap-ic ap-yes", viewBox: "0 0 16 16", width: "15", height: "15", "aria-hidden": true }, /*#__PURE__*/React.createElement("path", { d: "M13 4.6 L6.5 11.4 L3 8", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })); const ClockMark = () => /*#__PURE__*/React.createElement("svg", { className: "ap-ic ap-clock", viewBox: "0 0 16 16", width: "14", height: "14", "aria-hidden": true }, /*#__PURE__*/React.createElement("circle", { cx: "8", cy: "8", r: "6.2", fill: "none", stroke: "currentColor", strokeWidth: "1.4" }), /*#__PURE__*/React.createElement("path", { d: "M8 8 L8 4.4 M8 8 L10.5 9.3", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" })); const StatusIcon = _ref => { let status = _ref.status; if (status === "available") { return /*#__PURE__*/React.createElement(CheckMark, null); } if (status === "pending") { return /*#__PURE__*/React.createElement(ClockMark, null); } return null; }; /** A nested mark inside parentheses, e.g. "PM ✓". */ const ChildMark = _ref2 => { let mark = _ref2.mark; return mark.status === "excluded" ? /*#__PURE__*/React.createElement("span", { className: "ap-excluded" }, mark.label) : /*#__PURE__*/React.createElement(React.Fragment, null, mark.label, " ", /*#__PURE__*/React.createElement(StatusIcon, { status: mark.status })); }; const Mark = _ref3 => { var _mark$children; let mark = _ref3.mark, iconPosition = _ref3.iconPosition; const hasChildren = Boolean(mark.children && mark.children.length > 0); const excluded = mark.status === "excluded"; const icon = hasChildren ? null : /*#__PURE__*/React.createElement(StatusIcon, { status: mark.status }); return /*#__PURE__*/React.createElement("span", { className: excluded ? "ap-item ap-excluded" : "ap-item" }, iconPosition === "before" && icon, mark.label, mark.note && /*#__PURE__*/React.createElement("span", { className: "ap-sub" }, mark.note), mark.pending && /*#__PURE__*/React.createElement("span", { className: "ap-tbd" }, mark.pending), hasChildren && /*#__PURE__*/React.createElement("span", { className: "ap-sub" }, "(", (_mark$children = mark.children) == null ? void 0 : _mark$children.map((child, index) => /*#__PURE__*/React.createElement(React.Fragment, { key: child.label }, index > 0 && " / ", /*#__PURE__*/React.createElement(ChildMark, { mark: child }))), ")"), iconPosition === "after" && icon); }; /* ------------------------------------------------------------------ */ /* Panel */ /* ------------------------------------------------------------------ */ const StyledPanel = styled.div.withConfig({ displayName: "FeatureAvailability__StyledPanel", componentId: "sc-42kyhy-0" })(["font-family:-apple-system,BlinkMacSystemFont,\"Roboto\",\"Helvetica Neue\",Arial,sans-serif;font-size:13px;line-height:20px;color:var(--color-theme-900);padding:12px 16px;border:1px solid var(--color-theme-300);border-radius:10px;background:var(--color-theme-100);.ap-row{display:flex;gap:10px;padding:6px 0;align-items:baseline;flex-wrap:wrap;}.ap-row:first-of-type{padding-top:0;}.ap-row:last-of-type{padding-bottom:0;}.ap-row + .ap-row{border-top:1px dashed var(--color-theme-300);}.ap-label{flex:0 0 84px;color:var(--color-theme-700);font-weight:600;font-size:13px;}.ap-vals{flex:1 1 240px;display:flex;flex-wrap:wrap;gap:4px 14px;align-items:center;}.ap-item{display:inline-flex;align-items:center;gap:5px;white-space:nowrap;}.ap-sub{color:var(--color-theme-600);}.ap-tbd{color:var(--color-theme-600);font-style:italic;}.ap-sep{color:var(--color-theme-400);}.ap-excluded{text-decoration:line-through;color:var(--color-theme-500);}.ap-ic{flex:none;vertical-align:-2px;}.ap-yes{color:var(--color-green-olive-drab);}.ap-clock{color:var(--color-theme-600);}"]); export const FeatureAvailability = _ref4 => { let rows = _ref4.rows, className = _ref4.className; return /*#__PURE__*/React.createElement(StyledPanel, { className: className, "aria-label": "Feature availability" }, rows.map(row => { var _row$iconPosition; const iconPosition = (_row$iconPosition = row.iconPosition) != null ? _row$iconPosition : "after"; return /*#__PURE__*/React.createElement("div", { className: "ap-row", key: row.label }, /*#__PURE__*/React.createElement("span", { className: "ap-label" }, row.label), /*#__PURE__*/React.createElement("span", { className: "ap-vals" }, row.items.map((item, index) => /*#__PURE__*/React.createElement(React.Fragment, { key: item.label }, index > 0 && row.separated && /*#__PURE__*/React.createElement("span", { className: "ap-sep" }, "\xB7"), /*#__PURE__*/React.createElement(Mark, { mark: item, iconPosition: iconPosition }))))); })); }; /* ------------------------------------------------------------------ */ /* Illustrative defaults */ /* ------------------------------------------------------------------ */ /** * A realistic GA feature: shipped on Cloud, self-hosted still to be scheduled, * on every plan, for internal roles but not for clients. Exercises all three * marks (check / clock / strikethrough) out of the box. */ export const DEFAULT_FEATURE_AVAILABILITY = { rows: [{ label: "Availability", iconPosition: "before", separated: true, items: [{ label: "Cloud", status: "available", note: "(stable, since Jun 17 2026)" }, { label: "Self-Hosted", status: "pending", pending: "to be determined" }] }, { label: "Plans", items: [{ label: "S–Mega", status: "available" }, { label: "Plus", status: "available" }, { label: "Pro", status: "available" }, { label: "Pro+Get-Paid", status: "available" }] }, { label: "Roles", items: [{ label: "Owner", status: "available" }, { label: "Member", status: "available" }, { label: "Member+", status: "available", children: [{ label: "PM", status: "available" }, { label: "FM", status: "available" }] }, { label: "Client", status: "excluded" }, { label: "Client+", status: "excluded" }] }] }; //# sourceMappingURL=FeatureAvailability.js.map