diff --git a/src/app/components/Header.tsx b/src/app/components/Header.tsx index 8091f987..8fafffda 100644 --- a/src/app/components/Header.tsx +++ b/src/app/components/Header.tsx @@ -1,7 +1,9 @@ -import { getCurrentUrl, Link, route } from 'preact-router' +import { getCurrentUrl, Link } from 'preact-router' +import { useCallback, useMemo, useRef, useState } from 'preact/hooks' +import type { ConfigGenerator } from '../Config.js' import config from '../Config.js' import { useLocale, useTheme, useTitle, useVersion } from '../contexts/index.js' -import { checkVersion } from '../services/index.js' +import { useFocus } from '../hooks/useFocus.js' import { cleanUrl, getGenerator, SOURCE_REPO_URL } from '../Utils.js' import { Btn, BtnMenu, Icons, Octicon } from './index.js' @@ -14,22 +16,16 @@ const Themes: Record = { export function Header() { const { lang, locale, changeLocale: changeLanguage } = useLocale() const { theme, changeTheme } = useTheme() - const { version } = useVersion() const { title } = useTitle() const url = getCurrentUrl() const gen = getGenerator(url) return
-
- {Icons.home} -

{title}

- {gen && - {config.generators - .filter(g => g.tags?.[0] === gen?.tags?.[0] && checkVersion(version, g.minVersion)) - .map(g => - route(cleanUrl(g.url))} /> - )} - } +
+ {Icons.home} + {gen + ? + :

{title}

}
} + +interface GeneratorTitleProps { + title: string + gen: ConfigGenerator +} +function GeneratorTitle({ title, gen }: GeneratorTitleProps) { + const { locale } = useLocale() + const { version } = useVersion() + + const [active, setActive] = useFocus() + const [search, setSearch] = useState('') + const inputRef = useRef(null) + + const icon = Object.keys(Icons).includes(gen.id) ? gen.id as keyof typeof Icons : undefined + + const generators = useMemo(() => { + let result = config.generators + .filter(g => !g.dependency) + .map(g => ({ ...g, name: locale(`generator.${g.id}`).toLowerCase() })) + if (search) { + const parts = search.split(' ') + result = result.filter(g => parts.some(p => g.name.includes(p)) + || parts.some(p => g.tags?.some(t => t.includes(p)) ?? false)) + } + result.sort((a, b) => a.name.localeCompare(b.name)) + if (search) { + result.sort((a, b) => (b.name.startsWith(search) ? 1 : 0) - (a.name.startsWith(search) ? 1 : 0)) + } + return result + }, [locale, version, search]) + + const open = useCallback(() => { + setActive(true) + setTimeout(() => { + inputRef.current?.select() + }) + }, [setActive, inputRef]) + + return
+

+ {title} + {icon && Icons[icon]} +

+
+ setSearch((e.target as HTMLInputElement).value)} onClick={e => e.stopPropagation()} /> + {active &&
+ {generators.length === 0 && {locale('generators.no_results')}} + {generators.map(g => + setActive(false)}> + {locale(`generator.${g.id}`)} + {Object.keys(Icons).includes(g.id) ? Icons[g.id as keyof typeof Icons] : undefined} + + )} +
} +
+
+} diff --git a/src/styles/global.css b/src/styles/global.css index 51a08214..3ed46091 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -136,18 +136,20 @@ header { background-color: var(--background-2); } -.title { - display: flex; - align-items: center; -} - .title h1 { - font-size: 27px; color: var(--nav); } -.home-link { - margin: 0 8px 0 0; +.title h1 svg { + width: 24px; + height: 24px; + fill: var(--nav); + margin-left: 8px; + transition: margin 0.2s; +} + +.title h1:hover svg { + margin-left: 14px; } .home-link svg { @@ -223,6 +225,31 @@ nav li .btn svg { height: 24px; } +.gen-menu { + background-color: var(--background-2); + color: var(--text-2); +} + +.gen-menu input { + background-color: var(--background-1); +} + +.gen-results > a svg { + width: 16px; + height: 16px; + fill: var(--nav); + margin-left: 8px; + transition: margin 0.2s; +} + +.gen-results > a:hover { + background-color: var(--background-3); +} + +.gen-results > a:hover svg { + margin-left: 14px; +} + header .btn-menu > .btn { background: none !important; padding: 0; @@ -3049,10 +3076,6 @@ hr { grid-template-columns: 1fr; } - .title h1 { - font-size: 18px; - } - body nav li { margin: 0 8px; }