/
Alex_Ural
/
opencode
Обзор
Документация
Войти
/
Alex_Ural
/
opencode
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
packages/console/app/src/component/header.tsx
288 строк
10 KB
Adam
feat(web): data link
10 июн 2026, 20:55
Не верифицирован
10 июн 2026, 20:55
8688ed7
Код
Авторство
О чём код?
import logoLight from "../asset/logo-ornate-light.svg" import logoDark from "../asset/logo-ornate-dark.svg" import copyLogoLight from "../asset/lander/logo-light.svg" import copyLogoDark from "../asset/lander/logo-dark.svg" import copyWordmarkLight from "../asset/lander/wordmark-light.svg" import copyWordmarkDark from "../asset/lander/wordmark-dark.svg" import copyBrandAssetsLight from "../asset/lander/brand-assets-light.svg" import copyBrandAssetsDark from "../asset/lander/brand-assets-dark.svg" // SVG files for copying (separate from button icons) // Replace these with your actual SVG files for copying import copyLogoSvgLight from "../asset/lander/opencode-logo-light.svg" import copyLogoSvgDark from "../asset/lander/opencode-logo-dark.svg" import copyWordmarkSvgLight from "../asset/lander/opencode-wordmark-light.svg" import copyWordmarkSvgDark from "../asset/lander/opencode-wordmark-dark.svg" import { A, useNavigate } from "@solidjs/router" import { createMemo, Match, Show, Switch } from "solid-js" import { createStore } from "solid-js/store" import { createEffect, onCleanup } from "solid-js" import { config } from "~/config" import { useI18n } from "~/context/i18n" import { useLanguage } from "~/context/language" import "./header-context-menu.css" const isDarkMode = () => window.matchMedia("(prefers-color-scheme: dark)").matches const fetchSvgContent = async (svgPath: string): Promise<string> => { try { const response = await fetch(svgPath) const svgText = await response.text() return svgText } catch (err) { console.error("Failed to fetch SVG content:", err) throw err } } export function Header(props: { zen?: boolean; go?: boolean; hideGetStarted?: boolean }) { const navigate = useNavigate() const i18n = useI18n() const language = useLanguage() const [store, setStore] = createStore({ mobileMenuOpen: false, contextMenuOpen: false, contextMenuPosition: { x: 0, y: 0 }, }) createEffect(() => { const handleClickOutside = () => { setStore("contextMenuOpen", false) } const handleContextMenu = (event: MouseEvent) => { event.preventDefault() setStore("contextMenuOpen", false) } const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") { setStore("contextMenuOpen", false) } } if (store.contextMenuOpen) { document.addEventListener("click", handleClickOutside) document.addEventListener("contextmenu", handleContextMenu) document.addEventListener("keydown", handleKeyDown) onCleanup(() => { document.removeEventListener("click", handleClickOutside) document.removeEventListener("contextmenu", handleContextMenu) document.removeEventListener("keydown", handleKeyDown) }) } }) const handleLogoContextMenu = (event: MouseEvent) => { event.preventDefault() const logoElement = (event.currentTarget as HTMLElement).querySelector("a") if (logoElement) { const rect = logoElement.getBoundingClientRect() setStore("contextMenuPosition", { x: rect.left - 16, y: rect.bottom + 8, }) } setStore("contextMenuOpen", true) } const copyWordmarkToClipboard = async () => { try { const isDark = isDarkMode() const wordmarkSvgPath = isDark ? copyWordmarkSvgDark : copyWordmarkSvgLight const wordmarkSvg = await fetchSvgContent(wordmarkSvgPath) await navigator.clipboard.writeText(wordmarkSvg) } catch (err) { console.error("Failed to copy wordmark to clipboard:", err) } } const copyLogoToClipboard = async () => { try { const isDark = isDarkMode() const logoSvgPath = isDark ? copyLogoSvgDark : copyLogoSvgLight const logoSvg = await fetchSvgContent(logoSvgPath) await navigator.clipboard.writeText(logoSvg) } catch (err) { console.error("Failed to copy logo to clipboard:", err) } } return ( <section data-component="top"> <div onContextMenu={handleLogoContextMenu}> <A href={language.route("/")}> <img data-slot="logo light" src={logoLight} alt={i18n.t("nav.logoAlt")} width="189" height="34" /> <img data-slot="logo dark" src={logoDark} alt={i18n.t("nav.logoAlt")} width="189" height="34" /> </A> </div> <Show when={store.contextMenuOpen}> <div class="context-menu" style={`left: ${store.contextMenuPosition.x}px; top: ${store.contextMenuPosition.y}px;`} > <button class="context-menu-item" onClick={copyLogoToClipboard}> <img data-slot="copy light" src={copyLogoLight} alt="" /> <img data-slot="copy dark" src={copyLogoDark} alt="" /> {i18n.t("nav.context.copyLogo")} </button> <button class="context-menu-item" onClick={copyWordmarkToClipboard}> <img data-slot="copy light" src={copyWordmarkLight} alt="" /> <img data-slot="copy dark" src={copyWordmarkDark} alt="" /> {i18n.t("nav.context.copyWordmark")} </button> <button class="context-menu-item" onClick={() => navigate(language.route("/brand"))}> <img data-slot="copy light" src={copyBrandAssetsLight} alt="" /> <img data-slot="copy dark" src={copyBrandAssetsDark} alt="" /> {i18n.t("nav.context.brandAssets")} </button> </div> </Show> <nav data-component="nav-desktop"> <ul> <li> <a href={config.github.repoUrl} target="_blank" style="white-space: nowrap;"> {i18n.t("nav.github")} </a> </li> <li> <a href={language.route("/docs")}>{i18n.t("nav.docs")}</a> </li> <li> <a href={language.route("/data")}>{i18n.t("nav.data")}</a> </li> <li> <A href={language.route("/zen")}>{i18n.t("nav.zen")}</A> </li> <li> <A href={language.route("/go")}>{i18n.t("nav.go")}</A> </li> <li> <A href={language.route("/enterprise")}>{i18n.t("nav.enterprise")}</A> </li> <Show when={props.zen || props.go}> <li> <a href="/auth">{i18n.t("nav.login")}</a> </li> </Show> <Show when={!props.hideGetStarted}> <li> <A href={language.route("/download")} data-slot="cta-button"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" style="flex-shrink: 0;" > <path d="M12.1875 9.75L9.00001 12.9375L5.8125 9.75M9.00001 2.0625L9 12.375M14.4375 15.9375H3.5625" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" /> </svg> {i18n.t("nav.free")} </A> </li> </Show> </ul> </nav> <nav data-component="nav-mobile"> <button type="button" data-component="nav-mobile-toggle" aria-expanded="false" aria-controls="nav-mobile-menu" class="nav-toggle" onClick={() => setStore("mobileMenuOpen", !store.mobileMenuOpen)} > <span class="sr-only">{i18n.t("nav.openMenu")}</span> <Switch> <Match when={store.mobileMenuOpen}> <svg class="icon icon-close" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" > <path d="M12.7071 11.9993L18.0104 17.3026L17.3033 18.0097L12 12.7064L6.6967 18.0097L5.98959 17.3026L11.2929 11.9993L5.98959 6.69595L6.6967 5.98885L12 11.2921L17.3033 5.98885L18.0104 6.69595L12.7071 11.9993Z" fill="currentColor" /> </svg> </Match> <Match when={!store.mobileMenuOpen}> <svg class="icon icon-hamburger" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" > <path d="M19 17H5V16H19V17Z" fill="currentColor" /> <path d="M19 8H5V7H19V8Z" fill="currentColor" /> </svg> </Match> </Switch> </button> <Show when={store.mobileMenuOpen}> <div id="nav-mobile-menu" data-component="nav-mobile"> <nav data-component="nav-mobile-menu-list"> <ul> <li> <A href={language.route("/")}>{i18n.t("nav.home")}</A> </li> <li> <a href={config.github.repoUrl} target="_blank" style="white-space: nowrap;"> {i18n.t("nav.github")} </a> </li> <li> <a href={language.route("/docs")}>{i18n.t("nav.docs")}</a> </li> <li> <a href={language.route("/data")}>{i18n.t("nav.data")}</a> </li> <Show when={!props.zen}> <li> <A href={language.route("/zen")}>{i18n.t("nav.zen")}</A> </li> </Show> <Show when={!props.go}> <li> <A href={language.route("/go")}>{i18n.t("nav.go")}</A> </li> </Show> <li> <A href={language.route("/enterprise")}>{i18n.t("nav.enterprise")}</A> </li> <Show when={props.zen || props.go}> <li> <a href="/auth">{i18n.t("nav.login")}</a> </li> </Show> <Show when={!props.hideGetStarted}> <li> <A href={language.route("/download")} data-slot="cta-button"> {i18n.t("nav.getStartedFree")} </A> </li> </Show> </ul> </nav> </div> </Show> </nav> </section> ) }