UNPKG

@activecollab/components

Version:

ActiveCollab Components

243 lines (230 loc) 9.31 kB
import React, { useCallback, useEffect, useRef, useState } from "react"; import styled, { createGlobalStyle } from "styled-components"; import { Button } from "../../components/Button"; import { Checkbox } from "../../components/Checkbox"; import { Dialog } from "../../components/Dialog"; import { GitHubIcon, GitIcon, GitLabIcon } from "../../components/Icons"; import { Label } from "../../components/Label"; import { Select } from "../../components/Select"; import { SelectTrigger } from "../../components/SelectTrigger"; import { Body2 } from "../../components/Typography"; /** * "Connect Repository" — the project-side counterpart to the "Connect Project" * dialog on the Repositories page (Apps & Integrations). Same operation from * the other end: pick a repository, choose which features to enable, and make * the connection. Built as a shared component because more than one story links * a repository this way. * * Also exports `RepositorySelect`, the grouped repository picker (repositories * grouped under their connection name, everything sorted by name, filterable * via the built-in search) reused wherever a repository is chosen. */ const SANS = '-apple-system, BlinkMacSystemFont, "Roboto", "Helvetica Neue", Arial, sans-serif'; /* ---- Data model + mock data ----------------------------------------- */ /** Human-readable provider label used in the group header "name (type)". */ export const serviceLabel = service => service === "github" ? "GitHub" : "GitLab"; /** Provider glyph for a connection: GitHub / GitLab, Git for anything else. */ export const connectionIcon = service => { if (service === "github") { return /*#__PURE__*/React.createElement(GitHubIcon, null); } if (service === "gitlab") { return /*#__PURE__*/React.createElement(GitLabIcon, null); } return /*#__PURE__*/React.createElement(GitIcon, null); }; export const CONNECTIONS = [{ id: "gh-ac", name: "ActiveCollab", service: "github" }, { id: "gl-ops", name: "a51-ops", service: "gitlab" }]; /** Repositories already connected to the project (shown in link pickers). */ export const PROJECT_REPOSITORIES = [{ id: "backend", name: "activecollab/backend", connectionId: "gh-ac" }, { id: "frontend", name: "activecollab/frontend", connectionId: "gh-ac" }, { id: "infra", name: "activecollab/infrastructure", connectionId: "gl-ops" }]; /** Repositories available to connect (not yet linked to the project). */ export const CONNECTABLE_REPOSITORIES = [{ id: "mobile", name: "activecollab/mobile", connectionId: "gh-ac" }, { id: "design-system", name: "activecollab/design-system", connectionId: "gh-ac" }, { id: "deploy-scripts", name: "a51-ops/deploy-scripts", connectionId: "gl-ops" }, { id: "runners", name: "a51-ops/ci-runners", connectionId: "gl-ops" }]; /** * Build grouped Select options — groups and options both sorted by name. Each * repository option carries a leading provider icon (GitHub / GitLab / Git) via * the Select's `icon` slot; the group header is just the connection name, since * the icon now conveys the service (no "(GitHub)" / "(GitLab)" suffix). */ export const buildRepoOptions = (repositories, connections) => [...connections].map(c => ({ id: c.id, name: c.name, options: repositories.filter(r => r.connectionId === c.id).sort((a, b) => a.name.localeCompare(b.name)).map(r => ({ id: r.id, name: r.name, icon: connectionIcon(c.service) })) })).filter(group => group.options.length > 0).sort((a, b) => a.name.localeCompare(b.name)); /* ---- RepositorySelect ------------------------------------------------ */ /** * The grouped repository menu renders through a portal, so the "match the * trigger width" override has to be global, scoped to this instance's class. */ const RepoMenuGlobalStyle = createGlobalStyle([".c-select.", "{", "}"], p => p.$cls, p => p.$w ? "width: " + p.$w + "px !important; max-width: none !important;" : ""); const RepoSelectWrap = styled.div.withConfig({ displayName: "ConnectRepositoryDialog__RepoSelectWrap", componentId: "sc-7az7te-0" })(["font-family:", ";.repo-trigger{width:100%;max-width:none;}"], SANS); export const RepositorySelect = _ref => { var _repositories$find$na, _repositories$find; let repositories = _ref.repositories, connections = _ref.connections, selected = _ref.selected, onChange = _ref.onChange, invalid = _ref.invalid, _ref$placeholder = _ref.placeholder, placeholder = _ref$placeholder === void 0 ? "Choose repository" : _ref$placeholder, className = _ref.className; const wrapRef = useRef(null); const _useState = useState(), menuWidth = _useState[0], setMenuWidth = _useState[1]; const _useState2 = useState(() => "repo-select-menu-" + Math.random().toString(36).slice(2)), menuCls = _useState2[0]; const measure = useCallback(() => { if (wrapRef.current) { setMenuWidth(wrapRef.current.offsetWidth); } }, []); useEffect(() => { measure(); }, [measure]); const options = buildRepoOptions(repositories, connections); const selectedName = (_repositories$find$na = (_repositories$find = repositories.find(r => r.id === selected)) == null ? void 0 : _repositories$find.name) != null ? _repositories$find$na : placeholder; return /*#__PURE__*/React.createElement(RepoSelectWrap, { ref: wrapRef, className: className }, /*#__PURE__*/React.createElement(RepoMenuGlobalStyle, { $cls: menuCls, $w: menuWidth }), /*#__PURE__*/React.createElement(Select, { options: options, selected: selected === null ? undefined : selected, onChange: v => onChange(String(v)), placeholder: "Search repositories", selectClassName: menuCls, onSelectOpen: measure, forceCloseMenu: true, target: /*#__PURE__*/React.createElement(SelectTrigger, { className: "repo-trigger", invalid: invalid, typographyProps: selected === null ? { color: "tertiary" } : undefined }, selectedName) })); }; /* ---- ConnectRepositoryDialog ---------------------------------------- */ const StyledConnectBody = styled.div.withConfig({ displayName: "ConnectRepositoryDialog__StyledConnectBody", componentId: "sc-7az7te-1" })(["font-family:", ";.cr-field{margin-bottom:16px;}.cr-label{display:block;margin-bottom:6px;}.cr-check{display:flex;align-items:center;gap:8px;}.cr-check-hint{margin-top:6px;}"], SANS); export const ConnectRepositoryDialog = _ref2 => { let open = _ref2.open, onClose = _ref2.onClose, onConnect = _ref2.onConnect, _ref2$repositories = _ref2.repositories, repositories = _ref2$repositories === void 0 ? CONNECTABLE_REPOSITORIES : _ref2$repositories, _ref2$connections = _ref2.connections, connections = _ref2$connections === void 0 ? CONNECTIONS : _ref2$connections; const _useState3 = useState(null), selected = _useState3[0], setSelected = _useState3[1]; const _useState4 = useState(false), issueSync = _useState4[0], setIssueSync = _useState4[1]; const _useState5 = useState(false), attempted = _useState5[0], setAttempted = _useState5[1]; // Reset the form each time the dialog opens. useEffect(() => { if (open) { setSelected(null); setIssueSync(false); setAttempted(false); } }, [open]); const invalid = attempted && selected === null; const handleConnect = () => { const repo = repositories.find(r => r.id === selected); if (!repo) { setAttempted(true); return; } onConnect(repo); onClose(); }; return /*#__PURE__*/React.createElement(Dialog, { open: open, onClose: onClose, disableCloseOnEsc: true }, /*#__PURE__*/React.createElement(Dialog.Title, null, "Connect Repository"), /*#__PURE__*/React.createElement(Dialog.ContentDivider, null), /*#__PURE__*/React.createElement(Dialog.Content, null, /*#__PURE__*/React.createElement(StyledConnectBody, null, /*#__PURE__*/React.createElement("div", { className: "cr-field" }, /*#__PURE__*/React.createElement(Label, { size: "small", className: "cr-label", invalid: invalid }, "Repository"), /*#__PURE__*/React.createElement(RepositorySelect, { repositories: repositories, connections: connections, selected: selected, onChange: setSelected, invalid: invalid })), /*#__PURE__*/React.createElement("div", { className: "cr-check" }, /*#__PURE__*/React.createElement(Checkbox, { id: "cr-issue-sync", checked: issueSync, onChange: e => setIssueSync(e.target.checked) }), /*#__PURE__*/React.createElement(Label, { htmlFor: "cr-issue-sync", weight: "regular" }, "Enable issue sync")), /*#__PURE__*/React.createElement(Body2, { className: "cr-check-hint", color: "tertiary" }, "Issues from this repository will create and update tasks in this project."))), /*#__PURE__*/React.createElement(Dialog.ContentDivider, null), /*#__PURE__*/React.createElement(Dialog.Actions, null, /*#__PURE__*/React.createElement(Button, { variant: "primary", style: { marginRight: 12 }, onClick: handleConnect }, "Connect"), /*#__PURE__*/React.createElement(Button, { variant: "secondary", onClick: onClose }, "Cancel"))); }; //# sourceMappingURL=ConnectRepositoryDialog.js.map