@activecollab/components
Version:
ActiveCollab Components
203 lines (195 loc) • 7.65 kB
JavaScript
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