UNPKG

@activecollab/components

Version:

ActiveCollab Components

366 lines (346 loc) 17.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LinkSkeleton = exports.LinkPlaceholder = exports.LinkCard = void 0; var _react = _interopRequireDefault(require("react")); var _styledComponents = _interopRequireDefault(require("styled-components")); var _shared = require("./shared"); var _unfurl = require("./unfurl"); var _Icons = require("../../../components/Icons"); var _Loaders = require("../../../components/Loaders"); var _StackedCard = require("../../../components/StackedCard"); var _Typography = require("../../../components/Typography"); var _Typography2 = require("../../../components/Typography/Typography"); 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); } /* There is no global CSS reset in this Storybook, so a raw <button> falls back to the UA font (Arial) instead of the DS stack. Every button below is text, so they all need it. */ var CONTROL_FONT = "-apple-system, BlinkMacSystemFont, \"Roboto\",\n \"Helvetica Neue\", Arial, sans-serif"; /** * LinkCard — a link preview as a LIFECYCLE, not two static layouts. * * Data climbs a progressive ladder (url → basic → full); the anatomy is * CONSTANT at every tier, so each slot swaps a skeleton for real content in * place and the card never reflows on upgrade. Four statuses layer on top: * resolving — climbing the ladder, aspect + rows reserved by skeletons * resolved — the full card * stale — resolved, but past its cache window: shows `fetchedAt` and a * re-unfurl affordance * error — the unfurl failed: favicon + raw URL + retry, NO media * * Compact ↔ expanded is a user-switchable DISPLAY LEVEL on the same link * entity (the data lives in the controller, so switching never refetches or * loses it) — not two separate card types. */ /* ------------------------------------------------------------------ */ /* Small building blocks */ /* ------------------------------------------------------------------ */ var StyledDomainRow = _styledComponents.default.div.withConfig({ displayName: "LinkCard__StyledDomainRow", componentId: "sc-qyea73-0" })(["display:flex;align-items:center;gap:8px;min-height:16px;"]); var StyledChips = _styledComponents.default.div.withConfig({ displayName: "LinkCard__StyledChips", componentId: "sc-qyea73-1" })(["display:flex;align-items:center;gap:6px;min-height:18px;"]); /* The stale strip — a re-unfurl affordance with the fetch time. */ var StyledStaleStrip = _styledComponents.default.div.withConfig({ displayName: "LinkCard__StyledStaleStrip", componentId: "sc-qyea73-2" })(["display:flex;align-items:center;gap:8px;color:var(--color-theme-600);> span{flex:1 1 auto;min-width:0;}"]); var StyledReunfurl = _styledComponents.default.button.withConfig({ displayName: "LinkCard__StyledReunfurl", componentId: "sc-qyea73-3" })(["display:inline-flex;align-items:center;gap:4px;flex:0 0 auto;padding:3px 8px;border:1px solid var(--border-primary);border-radius:6px;background:var(--page-paper-main);color:var(--color-theme-800);font-family:", ";font-size:11px;cursor:pointer;svg{width:12px;height:12px;}&:hover{background:var(--color-theme-200);border-color:var(--sc-selection-ring);}"], CONTROL_FONT); /* The error body — favicon + raw URL + retry, no media (per spec §5). It is a column row like any other: the padding comes from the role, so the error state lines up with the states it replaces instead of setting its own inset. Only the gap is widened — three stacked blocks, not four text lines. */ var StyledErrorRow = (0, _styledComponents.default)(_StackedCard.StackedCardRow).withConfig({ displayName: "LinkCard__StyledErrorRow", componentId: "sc-qyea73-4" })(["gap:10px;.err-head{display:flex;align-items:center;gap:8px;color:var(--red-alert);}.err-head svg{width:16px;height:16px;flex:0 0 auto;}.err-url{display:flex;align-items:center;gap:8px;word-break:break-all;}"]); var StyledRetry = _styledComponents.default.button.withConfig({ displayName: "LinkCard__StyledRetry", componentId: "sc-qyea73-5" })(["align-self:flex-start;display:inline-flex;align-items:center;gap:6px;padding:5px 10px;border:1px solid var(--border-primary);border-radius:6px;background:var(--page-paper-main);color:var(--color-theme-800);font-family:", ";cursor:pointer;svg{width:14px;height:14px;}&:hover{background:var(--color-theme-200);border-color:var(--sc-selection-ring);}"], CONTROL_FONT); /* The resolved cover. A production unfurl stores the OpenGraph image, so when the ladder returns one we render a real <img> (the Cover fill already gives it `object-fit: cover`); the fill colour is the fallback for links that carry no image at all. */ var resolvedCover = function resolvedCover(data) { var _data$heroColor; return data.heroSrc ? /*#__PURE__*/_react.default.createElement("img", { src: data.heroSrc, alt: "" }) : /*#__PURE__*/_react.default.createElement(_shared.StyledColorCover, { $color: (_data$heroColor = data.heroColor) !== null && _data$heroColor !== void 0 ? _data$heroColor : "#1D9E75" }); }; /* The expanded footer band — tinted, holds the title + domain under the hero. */ var StyledFooterTitle = (0, _styledComponents.default)(_Typography.Body2).withConfig({ displayName: "LinkCard__StyledFooterTitle", componentId: "sc-qyea73-6" })(["display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;"]); /* ------------------------------------------------------------------ */ /* The display-level switch — lives in the entity "…" slot */ /* ------------------------------------------------------------------ */ var StyledDisplayToggle = _styledComponents.default.button.withConfig({ displayName: "LinkCard__StyledDisplayToggle", componentId: "sc-qyea73-7" })(["display:inline-flex;align-items:center;padding:2px 8px;border:1px solid var(--border-primary);border-radius:10px;background:var(--color-theme-200);color:var(--color-theme-700);font-family:", ";font-size:11px;cursor:pointer;white-space:nowrap;&:hover{background:var(--color-theme-300);}"], CONTROL_FONT); var DisplayToggle = function DisplayToggle(_ref) { var display = _ref.display, onToggle = _ref.onToggle; return /*#__PURE__*/_react.default.createElement(StyledDisplayToggle, { type: "button", "data-fixed": true, className: _StackedCard.STACKED_CARD_CONTROL_CLASS, "aria-label": "Switch display level", onClick: onToggle }, display === "compact" ? "Expand ▾" : "Compact ▴"); }; /* ------------------------------------------------------------------ */ /* LinkCard */ /* ------------------------------------------------------------------ */ var LinkCard = exports.LinkCard = function LinkCard(_ref2) { var _data$faviconColor2; var unfurl = _ref2.unfurl, _ref2$display = _ref2.display, display = _ref2$display === void 0 ? "compact" : _ref2$display, onToggleDisplay = _ref2.onToggleDisplay; var status = unfurl.status, tier = unfurl.tier, data = unfurl.data, fetchedAt = unfurl.fetchedAt, retry = unfurl.retry, reunfurl = unfurl.reunfurl; // ── Error: favicon + raw URL + retry, no media ────────────────── if (status === "error") { return /*#__PURE__*/_react.default.createElement(StyledErrorRow, { role: "custom", direction: "column" }, /*#__PURE__*/_react.default.createElement("div", { className: "err-head" }, /*#__PURE__*/_react.default.createElement(_Icons.WarningTriangleIcon, null), /*#__PURE__*/_react.default.createElement(_Typography.Caption1, { weight: "bold", color: "tertiary" }, "Couldn\u2019t load a preview")), /*#__PURE__*/_react.default.createElement("div", { className: "err-url" }, /*#__PURE__*/_react.default.createElement(_shared.StyledFavicon, { $bg: "var(--color-theme-400)", "data-fixed": true }), /*#__PURE__*/_react.default.createElement(_Typography.Caption1, { color: "secondary" }, data.url)), /*#__PURE__*/_react.default.createElement(StyledRetry, { type: "button", onClick: retry }, /*#__PURE__*/_react.default.createElement(_Icons.ArrowRefreshIcon, null), /*#__PURE__*/_react.default.createElement(_Typography2.Typography, { as: "span", variant: "Caption 1" }, "Retry"))); } var hasBasic = tier === "basic" || tier === "full"; var hasFull = tier === "full"; /* The display level belongs to the host, so the switch only appears when a host actually owns one. A surface that fixes the card at one level (the whiteboard pins it to expanded) gets no dead control. */ var toggle = onToggleDisplay ? /*#__PURE__*/_react.default.createElement(DisplayToggle, { display: display, onToggle: onToggleDisplay }) : null; // ── Expanded: hero + tinted footer band (title + domain) ──────── if (display === "expanded") { var _data$faviconColor; return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardCover, { aspectRatio: _shared.COVER_RATIO_HERO, centerSlot: hasFull && data.playable ? _shared.playControl : undefined, topRightSlot: (0, _shared.coverManagement)() }, hasFull ? resolvedCover(data) : /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { style: { width: "100%", height: "100%" } })), /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, { role: "footer", direction: "column", background: "var(--color-theme-200)" }, /*#__PURE__*/_react.default.createElement(StyledDomainRow, null, hasBasic ? /*#__PURE__*/_react.default.createElement(_shared.StyledFavicon, { $bg: (_data$faviconColor = data.faviconColor) !== null && _data$faviconColor !== void 0 ? _data$faviconColor : "#0F6E56", "data-fixed": true }) : /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { "data-fixed": true, style: { width: 16, height: 16, borderRadius: 4 } }), hasBasic ? /*#__PURE__*/_react.default.createElement(_Typography.Caption1, { color: "tertiary" }, data.domain) : /*#__PURE__*/_react.default.createElement(_Typography.Caption1, { color: "tertiary" }, data.url), /*#__PURE__*/_react.default.createElement("span", { style: { flex: "1 1 auto" } }), toggle), hasBasic ? /*#__PURE__*/_react.default.createElement(StyledFooterTitle, { weight: "bold" }, data.title) : /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { style: { height: 14, width: "90%" } }), hasFull ? /*#__PURE__*/_react.default.createElement(_Typography.Caption1, { color: "tertiary" }, data.description) : /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { style: { height: 10, width: "70%" } })), status === "stale" ? /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, { role: "meta" }, /*#__PURE__*/_react.default.createElement(StaleStrip, { fetchedAt: fetchedAt, onReunfurl: reunfurl })) : null); } // ── Compact: cover + favicon/domain + title + chips ───────────── return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardCover, { aspectRatio: _StackedCard.STACKED_CARD_COVER_RATIO, centerSlot: hasFull && data.playable ? _shared.playControl : undefined }, hasFull ? resolvedCover(data) : /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { style: { width: "100%", height: "100%" } })), /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, { role: "custom" }, /*#__PURE__*/_react.default.createElement(StyledDomainRow, null, hasBasic ? /*#__PURE__*/_react.default.createElement(_shared.StyledFavicon, { $bg: (_data$faviconColor2 = data.faviconColor) !== null && _data$faviconColor2 !== void 0 ? _data$faviconColor2 : "#0F6E56", "data-fixed": true }) : /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { "data-fixed": true, style: { width: 16, height: 16, borderRadius: 4 } }), hasBasic ? /*#__PURE__*/_react.default.createElement(_Typography.Caption1, { color: "tertiary" }, data.domain) : /*#__PURE__*/_react.default.createElement(_Typography.Caption1, { color: "tertiary" }, data.url), /*#__PURE__*/_react.default.createElement("span", { style: { flex: "1 1 auto" } }), toggle)), /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, { role: "title" }, hasBasic ? /*#__PURE__*/_react.default.createElement(_shared.StyledClamp, null, data.title) : /*#__PURE__*/_react.default.createElement("div", { style: { width: "100%" } }, /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { style: { height: 14, width: "92%" } }), /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { style: { height: 14, width: "60%", marginTop: 6 } }))), /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, { role: "custom" }, /*#__PURE__*/_react.default.createElement(StyledChips, null, hasFull ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, data.kind ? /*#__PURE__*/_react.default.createElement(_shared.StyledChip, { "data-fixed": true }, data.kind) : null, data.duration ? /*#__PURE__*/_react.default.createElement(_shared.StyledChip, { "data-fixed": true }, data.duration) : null) : /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { style: { height: 18, width: 96, borderRadius: 10 } }))), status === "stale" ? /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, { role: "meta" }, /*#__PURE__*/_react.default.createElement(StaleStrip, { fetchedAt: fetchedAt, onReunfurl: reunfurl })) : null); }; /* The stale affordance — fetch time + a manual re-unfurl button. */ var StaleStrip = function StaleStrip(_ref3) { var fetchedAt = _ref3.fetchedAt, onReunfurl = _ref3.onReunfurl; return /*#__PURE__*/_react.default.createElement(StyledStaleStrip, null, /*#__PURE__*/_react.default.createElement(_Typography2.Typography, { as: "span", variant: "Caption 1", color: "tertiary" }, "Preview may be out of date \xB7 fetched ", (0, _unfurl.formatFetchedAt)(fetchedAt)), /*#__PURE__*/_react.default.createElement(StyledReunfurl, { type: "button", onClick: onReunfurl }, /*#__PURE__*/_react.default.createElement(_Icons.ArrowRefreshIcon, null), "Re-unfurl")); }; /* ---- skeleton ---- */ /* Mirrors the compact anatomy row for row (this is the `resolving` render at tier `url`), so a resolving link never reflows as it climbs the ladder. */ var LinkSkeleton = exports.LinkSkeleton = function LinkSkeleton() { return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardCover, { aspectRatio: _StackedCard.STACKED_CARD_COVER_RATIO }, /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { style: { width: "100%", height: "100%" } })), /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, { role: "custom" }, /*#__PURE__*/_react.default.createElement(StyledDomainRow, null, /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { "data-fixed": true, style: { width: 16, height: 16, borderRadius: 4 } }), /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { style: { height: 10, width: "40%" } }))), /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, { role: "title" }, /*#__PURE__*/_react.default.createElement("div", { style: { width: "100%" } }, /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { style: { height: 14, width: "92%" } }), /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { style: { height: 14, width: "60%", marginTop: 6 } }))), /*#__PURE__*/_react.default.createElement(_StackedCard.StackedCardRow, { role: "custom" }, /*#__PURE__*/_react.default.createElement(StyledChips, null, /*#__PURE__*/_react.default.createElement(_Loaders.SkeletonLoader, { style: { height: 18, width: 96, borderRadius: 10 } })))); }; /* ---- placeholder ---- */ var LinkPlaceholder = exports.LinkPlaceholder = function LinkPlaceholder(props) { return /*#__PURE__*/_react.default.createElement(_shared.Placeholder, _extends({ icon: /*#__PURE__*/_react.default.createElement(_Icons.InsertLinkIcon, null), label: "Add a link" }, props)); }; //# sourceMappingURL=LinkCard.js.map