|
| 1 | +import { useEffect, useState } from 'react' |
| 2 | +import Cookies from 'js-cookie' |
| 3 | +import { UnderlineNav } from '@primer/components' |
| 4 | +import { sendEvent, EventType } from 'components/lib/events' |
| 5 | +import { preserveAnchorNodePosition } from 'scroll-anchoring' |
| 6 | + |
| 7 | +import { useArticleContext } from 'components/context/ArticleContext' |
| 8 | + |
| 9 | +// example: http://localhost:4000/en/codespaces/developing-in-codespaces/creating-a-codespace |
| 10 | + |
| 11 | +// Nota bene: tool === application |
| 12 | +// Nota bene: picker === switcher |
| 13 | + |
| 14 | +const supportedTools = ['cli', 'desktop', 'webui', 'curl', 'codespaces', 'vscode'] |
| 15 | +const toolTitles = { |
| 16 | + webui: 'Web browser', |
| 17 | + cli: 'GitHub CLI', |
| 18 | + curl: 'cURL', |
| 19 | + desktop: 'Desktop', |
| 20 | + codespaces: 'Codespaces', |
| 21 | + vscode: 'Visual Studio Code', |
| 22 | +} as Record<string, string> |
| 23 | + |
| 24 | +// Imperatively modify article content to show only the selected tool |
| 25 | +// find all platform-specific *block* elements and hide or show as appropriate |
| 26 | +// example: {% webui %} block content {% endwebui %} |
| 27 | +function showToolSpecificContent(tool: string) { |
| 28 | + const markdowns = Array.from(document.querySelectorAll<HTMLElement>('.extended-markdown')) |
| 29 | + markdowns |
| 30 | + .filter((el) => supportedTools.some((tool) => el.classList.contains(tool))) |
| 31 | + .forEach((el) => { |
| 32 | + el.style.display = el.classList.contains(tool) ? '' : 'none' |
| 33 | + }) |
| 34 | + |
| 35 | + // find all tool-specific *inline* elements and hide or show as appropriate |
| 36 | + // example: <span class="tool-webui">inline content</span> |
| 37 | + const toolEls = Array.from( |
| 38 | + document.querySelectorAll<HTMLElement>(supportedTools.map((tool) => `.tool-${tool}`).join(', ')) |
| 39 | + ) |
| 40 | + toolEls.forEach((el) => { |
| 41 | + el.style.display = el.classList.contains(`tool-${tool}`) ? '' : 'none' |
| 42 | + }) |
| 43 | +} |
| 44 | + |
| 45 | +function getDefaultTool(defaultTool: string | undefined, detectedTools: Array<string>): string { |
| 46 | + // If there is a default tool and the tool is present on this page |
| 47 | + if (defaultTool && detectedTools.includes(defaultTool)) return defaultTool |
| 48 | + |
| 49 | + // Default to webui if present (this is generally the case where we show UI/CLI/Desktop info) |
| 50 | + if (detectedTools.includes('webui')) return 'webui' |
| 51 | + |
| 52 | + // Default to cli if present (this is generally the case where we show curl/CLI info) |
| 53 | + if (detectedTools.includes('cli')) return 'cli' |
| 54 | + |
| 55 | + // Otherwise, just choose the first detected tool |
| 56 | + return detectedTools[0] |
| 57 | +} |
| 58 | + |
| 59 | +type Props = { |
| 60 | + variant?: 'subnav' | 'tabnav' | 'underlinenav' |
| 61 | +} |
| 62 | +export const ToolPicker = ({ variant = 'subnav' }: Props) => { |
| 63 | + const { defaultTool, detectedTools } = useArticleContext() |
| 64 | + const [currentTool, setCurrentTool] = useState(getDefaultTool(defaultTool, detectedTools)) |
| 65 | + |
| 66 | + const sharedContainerProps = { |
| 67 | + 'data-testid': 'tool-picker', |
| 68 | + 'aria-label': 'Tool picker', |
| 69 | + 'data-default-tool': defaultTool, |
| 70 | + className: 'mb-4', |
| 71 | + } |
| 72 | + |
| 73 | + // Run on mount for client-side only features |
| 74 | + useEffect(() => { |
| 75 | + // If the user selected a tool preference and the tool is present on this page |
| 76 | + // Has to be client-side only for cookie reading |
| 77 | + const cookieValue = Cookies.get('toolPreferred') |
| 78 | + if (cookieValue && detectedTools.includes(cookieValue)) { |
| 79 | + setCurrentTool(cookieValue) |
| 80 | + } |
| 81 | + }, []) |
| 82 | + |
| 83 | + function onClickTool(tool: string) { |
| 84 | + setCurrentTool(tool) |
| 85 | + preserveAnchorNodePosition(document, () => { |
| 86 | + showToolSpecificContent(tool) |
| 87 | + }) |
| 88 | + sendEvent({ |
| 89 | + type: EventType.preference, |
| 90 | + preference_name: 'application', |
| 91 | + preference_value: tool, |
| 92 | + }) |
| 93 | + Cookies.set('toolPreferred', tool, { sameSite: 'strict', secure: true }) |
| 94 | + } |
| 95 | + |
| 96 | + if (variant === 'underlinenav') { |
| 97 | + return ( |
| 98 | + <UnderlineNav {...sharedContainerProps}> |
| 99 | + {detectedTools.map((tool) => ( |
| 100 | + <UnderlineNav.Link |
| 101 | + key={tool} |
| 102 | + data-tool={tool} |
| 103 | + as="button" |
| 104 | + selected={tool === currentTool} |
| 105 | + onClick={() => { |
| 106 | + onClickTool(tool) |
| 107 | + }} |
| 108 | + > |
| 109 | + {toolTitles[tool]} |
| 110 | + </UnderlineNav.Link> |
| 111 | + ))} |
| 112 | + </UnderlineNav> |
| 113 | + ) |
| 114 | + } |
| 115 | + |
| 116 | + return null |
| 117 | +} |
0 commit comments