mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-24 07:37:10 +00:00
Add new generator switcher menu
This commit is contained in:
@@ -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 config from '../Config.js'
|
||||||
import { useLocale, useTheme, useTitle, useVersion } from '../contexts/index.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 { cleanUrl, getGenerator, SOURCE_REPO_URL } from '../Utils.js'
|
||||||
import { Btn, BtnMenu, Icons, Octicon } from './index.js'
|
import { Btn, BtnMenu, Icons, Octicon } from './index.js'
|
||||||
|
|
||||||
@@ -14,22 +16,16 @@ const Themes: Record<string, keyof typeof Octicon> = {
|
|||||||
export function Header() {
|
export function Header() {
|
||||||
const { lang, locale, changeLocale: changeLanguage } = useLocale()
|
const { lang, locale, changeLocale: changeLanguage } = useLocale()
|
||||||
const { theme, changeTheme } = useTheme()
|
const { theme, changeTheme } = useTheme()
|
||||||
const { version } = useVersion()
|
|
||||||
const { title } = useTitle()
|
const { title } = useTitle()
|
||||||
const url = getCurrentUrl()
|
const url = getCurrentUrl()
|
||||||
const gen = getGenerator(url)
|
const gen = getGenerator(url)
|
||||||
|
|
||||||
return <header>
|
return <header>
|
||||||
<div class="title">
|
<div class="title flex items-center">
|
||||||
<Link class="home-link" href="/" aria-label={locale('home')}>{Icons.home}</Link>
|
<Link class="home-link pr-1" href="/" aria-label={locale('home')}>{Icons.home}</Link>
|
||||||
<h1 class="font-bold">{title}</h1>
|
{gen
|
||||||
{gen && <BtnMenu icon="chevron_down" tooltip={locale('switch_generator')}>
|
? <GeneratorTitle title={title} gen={gen} />
|
||||||
{config.generators
|
: <h1 class="font-bold px-1 text-lg sm:text-2xl">{title}</h1>}
|
||||||
.filter(g => g.tags?.[0] === gen?.tags?.[0] && checkVersion(version, g.minVersion))
|
|
||||||
.map(g =>
|
|
||||||
<Btn label={locale(`generator.${g.id}`)} active={g.id === gen.id} onClick={() => route(cleanUrl(g.url))} />
|
|
||||||
)}
|
|
||||||
</BtnMenu>}
|
|
||||||
</div>
|
</div>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -58,3 +54,60 @@ export function Header() {
|
|||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<HTMLInputElement>(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 <div class="px-1 relative">
|
||||||
|
<h1 class="font-bold flex items-center cursor-pointer text-lg sm:text-2xl" onClick={open}>
|
||||||
|
{title}
|
||||||
|
{icon && Icons[icon]}
|
||||||
|
</h1>
|
||||||
|
<div class={`gen-menu absolute flex flex-col gap-2 p-2 rounded-lg drop-shadow-xl ${active ? '' : 'hidden'}`}>
|
||||||
|
<input ref={inputRef} type="text" class="py-1 px-2 w-full rounded" value={search} placeholder={locale('generators.search')} onInput={(e) => setSearch((e.target as HTMLInputElement).value)} onClick={e => e.stopPropagation()} />
|
||||||
|
{active && <div class="gen-results overflow-y-auto overscroll-none flex flex-col pr-2 h-96 max-h-max min-w-max">
|
||||||
|
{generators.length === 0 && <span class="note">{locale('generators.no_results')}</span>}
|
||||||
|
{generators.map(g =>
|
||||||
|
<Link class="flex items-center cursor-pointer no-underline rounded p-1" href={cleanUrl(g.url)} onClick={() => setActive(false)}>
|
||||||
|
{locale(`generator.${g.id}`)}
|
||||||
|
{Object.keys(Icons).includes(g.id) ? Icons[g.id as keyof typeof Icons] : undefined}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|||||||
@@ -136,18 +136,20 @@ header {
|
|||||||
background-color: var(--background-2);
|
background-color: var(--background-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title h1 {
|
.title h1 {
|
||||||
font-size: 27px;
|
|
||||||
color: var(--nav);
|
color: var(--nav);
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-link {
|
.title h1 svg {
|
||||||
margin: 0 8px 0 0;
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
fill: var(--nav);
|
||||||
|
margin-left: 8px;
|
||||||
|
transition: margin 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title h1:hover svg {
|
||||||
|
margin-left: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-link svg {
|
.home-link svg {
|
||||||
@@ -223,6 +225,31 @@ nav li .btn svg {
|
|||||||
height: 24px;
|
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 {
|
header .btn-menu > .btn {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -3049,10 +3076,6 @@ hr {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title h1 {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body nav li {
|
body nav li {
|
||||||
margin: 0 8px;
|
margin: 0 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user