UNPKG

@activecollab/components

Version:

ActiveCollab Components

335 lines (309 loc) 13.3 kB
import React, { useCallback, useContext, useState } from "react"; import styled from "styled-components"; import { screenBelow } from "../../components/BreakPoints"; import { Button } from "../../components/Button"; import { Columns } from "../../components/Columns"; import { IconButton } from "../../components/IconButton"; import { CancelCrossIcon, ThumbUpOutlineIcon } from "../../components/Icons"; import { InfoBox } from "../../components/InfoBox"; import { Media } from "../../components/Media"; import { Modal } from "../../components/Modal"; import { StyledLink as InlineLink } from "../../components/PortableText/Styles"; import { Row } from "../../components/Row"; import { Stack } from "../../components/Stack"; import { Tiles } from "../../components/Tiles"; import { TitledText } from "../../components/TitledText"; import { Tooltip } from "../../components/Tooltip"; import { Body2 } from "../../components/Typography"; import { Typography } from "../../components/Typography/Typography"; /** * The in-app message dialog, as a Presentation mock. * * Two pieces, mirroring how the product is split: * * - {@link MessageDialog} — the hollow dialog shell (width, closing, * feedback), built only from design-system parts. In the product this shell * lives in the application as `InAppMessageDialog`. * - {@link messageComponents} — a demo message element catalog injected into * the design-system Portable Text engine, exactly like the typed cards * injected into the hollow StackedCard shell. In the product the catalog is * application code; the presentation ships this demo copy so the stories * need no app imports. */ /* ---- the dialog shell ---------------------------------------------- */ const DIALOG_WIDTH = { sm: "520px", md: "720px", lg: "880px" }; /* Feedback — the thumbs up / down slot. Down-vote reuses the up icon, rotated (the DS set only ships ThumbUpOutlineIcon). */ const StyledFeedback = styled.div.withConfig({ displayName: "MessageDialog__StyledFeedback", componentId: "sc-1kt5lil-0" })(["display:flex;align-items:center;justify-content:center;gap:8px;padding:8px 24px 28px;", "{padding:4px 24px 24px;}.fb-question{margin-right:4px;}.fb-btn svg{width:18px;height:18px;}.fb-down svg{transform:rotate(180deg);}"], screenBelow.sm); const DialogFeedback = _ref => { let onVote = _ref.onVote; const _useState = useState(null), voted = _useState[0], setVoted = _useState[1]; const vote = useCallback(value => { setVoted(value); onVote == null || onVote(value); }, [onVote]); return /*#__PURE__*/React.createElement(StyledFeedback, null, voted ? /*#__PURE__*/React.createElement(Body2, { color: "secondary" }, "Thanks for your feedback!") : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Body2, { color: "secondary", className: "fb-question" }, "Was this helpful?"), /*#__PURE__*/React.createElement(Tooltip, { title: "Yes, helpful" }, /*#__PURE__*/React.createElement(IconButton, { type: "button", variant: "text gray", className: "fb-btn", "aria-label": "Yes, helpful", onClick: () => vote("up") }, /*#__PURE__*/React.createElement(ThumbUpOutlineIcon, null))), /*#__PURE__*/React.createElement(Tooltip, { title: "No, not helpful" }, /*#__PURE__*/React.createElement(IconButton, { type: "button", variant: "text gray", className: "fb-btn fb-down", "aria-label": "No, not helpful", onClick: () => vote("down") }, /*#__PURE__*/React.createElement(ThumbUpOutlineIcon, null))))); }; /* The shell is behaviour only: it sizes itself, closes on click-outside / Esc (DS Modal) / the X, and shows the feedback slot when an onVote handler is supplied. Content is whatever the caller renders as children — usually the Portable Text engine with the demo catalog injected. */ const StyledDialog = styled.div.withConfig({ displayName: "MessageDialog__StyledDialog", componentId: "sc-1kt5lil-1" })(["position:relative;box-sizing:border-box;display:flex;flex-direction:column;width:calc(100% - 32px);max-width:", ";max-height:calc(100vh - 96px);margin:56px auto 40px;overflow-y:auto;background-color:var(--page-paper-main);color:var(--color-theme-900);border-radius:12px;box-shadow:var(--shadow-primary);", "{margin:16px auto;}.dlg-close{position:absolute;top:12px;right:12px;z-index:2;}.dlg-content{padding:40px;}", "{.dlg-content{padding:32px 20px 24px;}}"], _ref2 => { let $size = _ref2.$size; return DIALOG_WIDTH[$size]; }, screenBelow.sm, screenBelow.sm); export const MessageDialog = _ref3 => { let open = _ref3.open, onClose = _ref3.onClose, _ref3$size = _ref3.size, size = _ref3$size === void 0 ? "md" : _ref3$size, onVote = _ref3.onVote, children = _ref3.children; return /*#__PURE__*/React.createElement(Modal, { open: open, onClose: onClose }, /*#__PURE__*/React.createElement(StyledDialog, { $size: size }, /*#__PURE__*/React.createElement("div", { className: "dlg-close" }, /*#__PURE__*/React.createElement(Tooltip, { title: "Close" }, /*#__PURE__*/React.createElement(IconButton, { type: "button", variant: "text gray", "aria-label": "Close", onClick: onClose }, /*#__PURE__*/React.createElement(CancelCrossIcon, null)))), /*#__PURE__*/React.createElement("div", { className: "dlg-content" }, children), onVote ? /*#__PURE__*/React.createElement(DialogFeedback, { onVote: onVote }) : null)); }; /* ---- doc-link wiring ------------------------------------------------ */ /** How a doc-link mark opens its article (by a slug-like reference). The surface that renders the message supplies the handler — in the product it closes the dialog and opens the article Sheet. */ export const DocLinkContext = /*#__PURE__*/React.createContext(() => undefined); /* ---- the demo message element catalog ------------------------------- */ /* Inline marks ------------------------------------------------------- */ /* interface-name — the name of an interface element, e.g. the Security page. */ const StyledInterfaceName = styled.span.withConfig({ displayName: "MessageDialog__StyledInterfaceName", componentId: "sc-1kt5lil-2" })(["padding:0 4px;border-radius:4px;background-color:var(--color-theme-300);color:var(--color-theme-900);font-weight:600;white-space:nowrap;"]); const InterfaceNameMark = _ref4 => { let children = _ref4.children; return /*#__PURE__*/React.createElement(StyledInterfaceName, null, children); }; /* keyboard-key — a keyboard key cap. */ const StyledKeyboardKey = styled.kbd.withConfig({ displayName: "MessageDialog__StyledKeyboardKey", componentId: "sc-1kt5lil-3" })(["display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;border:1px solid var(--border-primary);border-bottom-width:2px;border-radius:5px;background-color:var(--color-theme-200);color:var(--color-theme-900);font-family:ui-monospace,\"SFMono-Regular\",Menlo,Consolas,monospace;font-size:11px;font-weight:600;line-height:1;vertical-align:baseline;"]); const KeyboardKeyMark = _ref5 => { let children = _ref5.children; return /*#__PURE__*/React.createElement(StyledKeyboardKey, null, children); }; /* doc-link — an inline link that opens a full help article. The mark carries an article reference; the open behaviour comes from DocLinkContext, so the authored content stays declarative. */ /* Built on the engine's inline anchor so a doc-link sits in the sentence exactly like a default link mark — same size, weight and line box as the surrounding copy — just recoloured. */ const StyledDocLink = styled(InlineLink).withConfig({ displayName: "MessageDialog__StyledDocLink", componentId: "sc-1kt5lil-4" })(["color:var(--color-secondary);text-decoration-color:var(--color-secondary);"]); const DocLinkMark = _ref6 => { let mark = _ref6.mark, children = _ref6.children; const openArticle = useContext(DocLinkContext); return /*#__PURE__*/React.createElement(StyledDocLink, { href: "#", onClick: event => { event.preventDefault(); openArticle(mark == null ? void 0 : mark.article); } }, children); }; /* Component objects --------------------------------------------------- */ /* callout — rendered with the DS InfoBox; the catalog keeps its own tone vocabulary and maps it to InfoBox types. */ const CALLOUT_TYPE = { info: "default", note: "note", success: "success" }; const CalloutObject = _ref7 => { var _ref8; let node = _ref7.node, children = _ref7.children; return /*#__PURE__*/React.createElement(InfoBox, { type: CALLOUT_TYPE[(_ref8 = node.tone) != null ? _ref8 : "info"], title: node.title, showIcon: true }, children); }; /* button-reference — a real DS Button shown for reference as its own block, so a message can point at a button instead of screenshotting it. It is intentionally non-interactive. */ const StyledButtonReference = styled.div.withConfig({ displayName: "MessageDialog__StyledButtonReference", componentId: "sc-1kt5lil-5" })(["display:flex;.c-button{pointer-events:none;}"]); const ButtonReferenceObject = _ref9 => { var _ref0; let node = _ref9.node; if (typeof node.label !== "string") { return null; } return /*#__PURE__*/React.createElement(StyledButtonReference, { "aria-hidden": "true" }, /*#__PURE__*/React.createElement(Button, { variant: (_ref0 = node.variant) != null ? _ref0 : "primary", size: "small", type: "button", tabIndex: -1 }, node.label)); }; /* row — a horizontal run of blocks (e.g. two button references). */ const RowObject = _ref1 => { var _ref10; let node = _ref1.node, children = _ref1.children; return /*#__PURE__*/React.createElement(Row, { space: (_ref10 = node.space) != null ? _ref10 : "sm" }, children); }; /* tiles — an equal-size mosaic of blocks. */ const TilesObject = _ref11 => { var _ref12; let node = _ref11.node, children = _ref11.children; return /*#__PURE__*/React.createElement(Tiles, { columns: (_ref12 = node.columns) != null ? _ref12 : 3, space: "md", collapseBelow: "sm" }, children); }; /* split — text beside media, with the one layout rule the messages follow: screenshots sit on the right, flat illustrations on the left. `widths` is authored as [text, media] and reordered with the columns. */ const SplitObject = _ref13 => { var _ref14; let node = _ref13.node, children = _ref13.children; const media = node.media; const widths = (_ref14 = node.widths) != null ? _ref14 : [1, 1]; const textColumn = /*#__PURE__*/React.createElement(Stack, { space: "lg" }, children); // A split without media degrades to its text column instead of crashing. if (!(media != null && media.src)) { return textColumn; } const screenshot = media.variant === "screenshot"; const mediaColumn = /*#__PURE__*/React.createElement(Media, { src: media.src, alt: media.alt, variant: media.variant, caption: media.caption }); return /*#__PURE__*/React.createElement(Columns, { widths: screenshot ? widths : [...widths].reverse(), space: "xl", alignY: "center", collapseBelow: "md" }, screenshot ? textColumn : mediaColumn, screenshot ? mediaColumn : textColumn); }; /* Default-block overrides --------------------------------------------- */ /* The engine's defaults use Body 1 primary; the messages are designed on the quieter Body 2 secondary voice, so the catalog overrides the text blocks — demonstrating the `blocks` half of the injection contract. */ const ParagraphBlock = _ref15 => { let children = _ref15.children; return /*#__PURE__*/React.createElement(Body2, { color: "secondary", lineHeight: "loose" }, children); }; const ListItemBlock = _ref16 => { let children = _ref16.children; return /*#__PURE__*/React.createElement(Typography, { variant: "Body 2", as: "li", color: "secondary", lineHeight: "loose" }, children); }; /* Titled sections speak in the quiet Header2 subhead voice rather than the engine's default Title2 for level 2, and keep a roomier rhythm between a titled section's content nodes. */ const TitledTextBlock = _ref17 => { let block = _ref17.block, children = _ref17.children; const _ref18 = block, title = _ref18.title, level = _ref18.level; return /*#__PURE__*/React.createElement(TitledText, { title: title, variant: level === 1 ? "title1" : "header2" }, children ? /*#__PURE__*/React.createElement(Stack, { space: "sm" }, children) : undefined); }; /** The demo catalog the presentations inject into the Portable Text engine — the stand-in for the application's `messageComponents` map. */ export const messageComponents = { marks: { "interface-name": InterfaceNameMark, "keyboard-key": KeyboardKeyMark, "doc-link": DocLinkMark }, objects: { callout: CalloutObject, "button-reference": ButtonReferenceObject, row: RowObject, tiles: TilesObject, split: SplitObject }, blocks: { paragraph: ParagraphBlock, "list-item": ListItemBlock, "titled-text": TitledTextBlock } }; //# sourceMappingURL=MessageDialog.js.map