Skip to content

Commit 00fe95a

Browse files
committed
Add new generator switcher menu
1 parent 46b066e commit 00fe95a

File tree

2 files changed

+101
-25
lines changed

2 files changed

+101
-25
lines changed

src/app/components/Header.tsx

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { getCurrentUrl, Link, route } from 'preact-router'
1+
import { getCurrentUrl, Link } from 'preact-router'
2+
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
3+
import type { ConfigGenerator } from '../Config.js'
24
import config from '../Config.js'
35
import { useLocale, useTheme, useTitle, useVersion } from '../contexts/index.js'
4-
import { checkVersion } from '../services/index.js'
6+
import { useFocus } from '../hooks/useFocus.js'
57
import { cleanUrl, getGenerator, SOURCE_REPO_URL } from '../Utils.js'
68
import { Btn, BtnMenu, Icons, Octicon } from './index.js'
79

@@ -14,22 +16,16 @@ const Themes: Record<string, keyof typeof Octicon> = {
1416
export function Header() {
1517
const { lang, locale, changeLocale: changeLanguage } = useLocale()
1618
const { theme, changeTheme } = useTheme()
17-
const { version } = useVersion()
1819
const { title } = useTitle()
1920
const url = getCurrentUrl()
2021
const gen = getGenerator(url)
2122

2223
return <header>
23-
<div class="title">
24-
<Link class="home-link" href="/" aria-label={locale('home')}>{Icons.home}</Link>
25-
<h1 class="font-bold">{title}</h1>
26-
{gen && <BtnMenu icon="chevron_down" tooltip={locale('switch_generator')}>
27-
{config.generators
28-
.filter(g => g.tags?.[0] === gen?.tags?.[0] && checkVersion(version, g.minVersion))
29-
.map(g =>
30-
<Btn label={locale(`generator.${g.id}`)} active={g.id === gen.id} onClick={() => route(cleanUrl(g.url))} />
31-
)}
32-
</BtnMenu>}
24+
<div class="title flex items-center">
25+
<Link class="home-link pr-1" href="/" aria-label={locale('home')}>{Icons.home}</Link>
26+
{gen
27+
? <GeneratorTitle title={title} gen={gen} />
28+
: <h1 class="font-bold px-1 text-lg sm:text-2xl">{title}</h1>}
3329
</div>
3430
<nav>
3531
<ul>
@@ -58,3 +54,60 @@ export function Header() {
5854
</nav>
5955
</header>
6056
}
57+
58+
interface GeneratorTitleProps {
59+
title: string
60+
gen: ConfigGenerator
61+
}
62+
function GeneratorTitle({ title, gen }: GeneratorTitleProps) {
63+
const { locale } = useLocale()
64+
const { version } = useVersion()
65+
66+
const [active, setActive] = useFocus()
67+
const [search, setSearch] = useState('')
68+
const inputRef = useRef<HTMLInputElement>(null)
69+
70+
const icon = Object.keys(Icons).includes(gen.id) ? gen.id as keyof typeof Icons : undefined
71+
72+
const generators = useMemo(() => {
73+
let result = config.generators
74+
.filter(g => !g.dependency)
75+
.map(g => ({ ...g, name: locale(`generator.${g.id}`).toLowerCase() }))
76+
if (search) {
77+
const parts = search.split(' ')
78+
result = result.filter(g => parts.some(p => g.name.includes(p))
79+
|| parts.some(p => g.tags?.some(t => t.includes(p)) ?? false))
80+
}
81+
result.sort((a, b) => a.name.localeCompare(b.name))
82+
if (search) {
83+
result.sort((a, b) => (b.name.startsWith(search) ? 1 : 0) - (a.name.startsWith(search) ? 1 : 0))
84+
}
85+
return result
86+
}, [locale, version, search])
87+
88+
const open = useCallback(() => {
89+
setActive(true)
90+
setTimeout(() => {
91+
inputRef.current?.select()
92+
})
93+
}, [setActive, inputRef])
94+
95+
return <div class="px-1 relative">
96+
<h1 class="font-bold flex items-center cursor-pointer text-lg sm:text-2xl" onClick={open}>
97+
{title}
98+
{icon && Icons[icon]}
99+
</h1>
100+
<div class={`gen-menu absolute flex flex-col gap-2 p-2 rounded-lg drop-shadow-xl ${active ? '' : 'hidden'}`}>
101+
<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()} />
102+
{active && <div class="gen-results overflow-y-auto overscroll-none flex flex-col pr-2 h-96 max-h-max min-w-max">
103+
{generators.length === 0 && <span class="note">{locale('generators.no_results')}</span>}
104+
{generators.map(g =>
105+
<Link class="flex items-center cursor-pointer no-underline rounded p-1" href={cleanUrl(g.url)} onClick={() => setActive(false)}>
106+
{locale(`generator.${g.id}`)}
107+
{Object.keys(Icons).includes(g.id) ? Icons[g.id as keyof typeof Icons] : undefined}
108+
</Link>
109+
)}
110+
</div>}
111+
</div>
112+
</div>
113+
}

src/styles/global.css

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -136,18 +136,20 @@ header {
136136
background-color: var(--background-2);
137137
}
138138

139-
.title {
140-
display: flex;
141-
align-items: center;
142-
}
143-
144139
.title h1 {
145-
font-size: 27px;
146140
color: var(--nav);
147141
}
148142

149-
.home-link {
150-
margin: 0 8px 0 0;
143+
.title h1 svg {
144+
width: 24px;
145+
height: 24px;
146+
fill: var(--nav);
147+
margin-left: 8px;
148+
transition: margin 0.2s;
149+
}
150+
151+
.title h1:hover svg {
152+
margin-left: 14px;
151153
}
152154

153155
.home-link svg {
@@ -223,6 +225,31 @@ nav li .btn svg {
223225
height: 24px;
224226
}
225227

228+
.gen-menu {
229+
background-color: var(--background-2);
230+
color: var(--text-2);
231+
}
232+
233+
.gen-menu input {
234+
background-color: var(--background-1);
235+
}
236+
237+
.gen-results > a svg {
238+
width: 16px;
239+
height: 16px;
240+
fill: var(--nav);
241+
margin-left: 8px;
242+
transition: margin 0.2s;
243+
}
244+
245+
.gen-results > a:hover {
246+
background-color: var(--background-3);
247+
}
248+
249+
.gen-results > a:hover svg {
250+
margin-left: 14px;
251+
}
252+
226253
header .btn-menu > .btn {
227254
background: none !important;
228255
padding: 0;
@@ -3049,10 +3076,6 @@ hr {
30493076
grid-template-columns: 1fr;
30503077
}
30513078

3052-
.title h1 {
3053-
font-size: 18px;
3054-
}
3055-
30563079
body nav li {
30573080
margin: 0 8px;
30583081
}

0 commit comments

Comments
 (0)