@@ -9,156 +9,208 @@ const availableLocales = computed(() =>
99 locales .value .map (l => (typeof l === ' string' ? { code: l , name: l } : l )),
1010)
1111
12- function goBack() {
13- router .back ()
14- }
15-
12+ // Escape to go back (but not when focused on form elements)
1613onKeyStroke (' Escape' , e => {
1714 const target = e .target as HTMLElement
1815 if (! [' INPUT' , ' SELECT' , ' TEXTAREA' ].includes (target ?.tagName )) {
19- goBack ()
16+ router . back ()
2017 }
2118})
2219
2320useSeoMeta ({
24- title: ' Settings - npmx' ,
21+ title : () => ` ${$t (' settings.title' )} - npmx ` ,
22+ description : () => $t (' settings.meta_description' ),
23+ })
24+
25+ defineOgImageComponent (' Default' , {
26+ title : () => $t (' settings.title' ),
27+ description : () => $t (' settings.tagline' ),
2528})
2629 </script >
2730
2831<template >
29- <main class =" container py-8 sm:py-12 w-full" >
30- <!-- Back button -->
31- <button
32- type =" button"
33- class =" inline-flex items-center gap-2 mb-6 text-sm text-fg-muted hover:text-fg transition-colors duration-150 motion-reduce:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50 rounded"
34- @click =" goBack"
35- >
36- <span class =" i-carbon-arrow-left w-4 h-4" aria-hidden =" true" />
37- {{ $t('nav.back') }}
38- </button >
32+ <main class =" container py-12 sm:py-16 min-h-screen w-full" >
33+ <article class =" max-w-2xl mx-auto" >
34+ <!-- Header -->
35+ <header class =" mb-12" >
36+ <div class =" flex items-baseline justify-between gap-4 mb-4" >
37+ <h1 class =" font-mono text-3xl sm:text-4xl font-medium" >
38+ {{ $t('settings.title') }}
39+ </h1 >
40+ <button
41+ type =" button"
42+ class =" inline-flex items-center gap-2 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 shrink-0"
43+ @click =" router.back()"
44+ >
45+ <span class =" i-carbon-arrow-left w-4 h-4" aria-hidden =" true" />
46+ <span class =" hidden sm:inline" >{{ $t('nav.back') }}</span >
47+ </button >
48+ </div >
49+ <p class =" text-fg-muted text-lg" >
50+ {{ $t('settings.tagline') }}
51+ </p >
52+ </header >
3953
40- <div class =" space-y-1 p-4 rounded-lg bg-bg-muted border border-border" >
41- <button
42- type =" button"
43- class =" w-full flex items-center justify-between gap-3 px-2 py-2 rounded-md hover:bg-bg-muted transition-[background-color] duration-150 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
44- role =" menuitemcheckbox"
45- :aria-checked =" settings.relativeDates"
46- @click =" settings.relativeDates = !settings.relativeDates"
47- >
48- <span class =" text-sm text-fg select-none" >{{ $t('settings.relative_dates') }}</span >
49- <span
50- class =" relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border-2 border-transparent transition-[background-color] duration-200 ease-in-out motion-reduce:transition-none shadow"
51- :class =" settings.relativeDates ? 'bg-fg' : 'bg-bg'"
52- aria-hidden =" true"
53- >
54- <span
55- class =" pointer-events-none inline-block h-4 w-4 rounded-full shadow-sm ring-0 transition-transform duration-200 ease-in-out motion-reduce:transition-none"
56- :class ="
57- settings.relativeDates ? 'translate-x-4 bg-bg-subtle' : 'translate-x-0 bg-fg-muted'
58- "
59- />
60- </span >
61- </button >
54+ <!-- Settings sections -->
55+ <div class =" space-y-8" >
56+ <!-- APPEARANCE Section -->
57+ <section >
58+ <h2 class =" text-xs text-fg-subtle uppercase tracking-wider mb-4" >
59+ {{ $t('settings.sections.appearance') }}
60+ </h2 >
61+ <div class =" bg-bg-subtle border border-border rounded-lg p-4 sm:p-6 space-y-6" >
62+ <!-- Theme selector -->
63+ <div class =" space-y-2" >
64+ <label for =" theme-select" class =" block text-sm text-fg font-medium" >
65+ {{ $t('settings.theme') }}
66+ </label >
67+ <select
68+ id =" theme-select"
69+ :value =" colorMode.preference"
70+ class =" w-full sm:w-auto min-w-48 bg-bg border border-border rounded-md px-3 py-2 text-sm text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 cursor-pointer"
71+ @change ="
72+ colorMode.preference = ($event.target as HTMLSelectElement).value as
73+ | 'light'
74+ | 'dark'
75+ | 'system'
76+ "
77+ >
78+ <option value =" system" >{{ $t('settings.theme_system') }}</option >
79+ <option value =" light" >{{ $t('settings.theme_light') }}</option >
80+ <option value =" dark" >{{ $t('settings.theme_dark') }}</option >
81+ </select >
82+ </div >
6283
63- <!-- Include @types in install toggle -->
64- <button
65- type =" button"
66- class =" w-full flex items-center justify-between gap-3 px-2 py-2 rounded-md hover:bg-bg-muted transition-[background-color] duration-150 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
67- role =" menuitemcheckbox"
68- :aria-checked =" settings.includeTypesInInstall"
69- @click =" settings.includeTypesInInstall = !settings.includeTypesInInstall"
70- >
71- <span class =" text-sm text-fg select-none text-left" >{{
72- $t('settings.include_types')
73- }}</span >
74- <span
75- class =" relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border-2 border-transparent transition-[background-color] duration-200 ease-in-out motion-reduce:transition-none border border-border shadow"
76- :class =" settings.includeTypesInInstall ? 'bg-fg' : 'bg-bg'"
77- aria-hidden =" true"
78- >
79- <span
80- class =" pointer-events-none inline-block h-4 w-4 rounded-full shadow-sm ring-0 transition-transform duration-200 ease-in-out motion-reduce:transition-none"
81- :class ="
82- settings.includeTypesInInstall
83- ? 'translate-x-4 bg-bg-subtle'
84- : 'translate-x-0 bg-fg-muted'
85- "
86- />
87- </span >
88- </button >
84+ <!-- Accent colors -->
85+ <div class =" space-y-3" >
86+ <span class =" block text-sm text-fg font-medium" >
87+ {{ $t('settings.accent_colors') }}
88+ </span >
89+ <AccentColorPicker />
90+ </div >
91+ </div >
92+ </section >
8993
90- <!-- Theme selector -->
91- <div class =" pt-2 mt-2 border-t border-border" >
92- <div class =" px-2 py-1" >
93- <label for =" theme-select" class =" text-xs text-fg-subtle uppercase tracking-wider" >
94- {{ $t('settings.theme') }}
95- </label >
96- </div >
97- <div class =" px-2 py-1" >
98- <select
99- id =" theme-select"
100- :value =" colorMode.preference"
101- class =" w-full bg-bg-muted border border-border rounded-md px-2 py-1.5 text-sm text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 cursor-pointer"
102- @change ="
103- colorMode.preference = ($event.target as HTMLSelectElement).value as
104- | 'light'
105- | 'dark'
106- | 'system'
107- "
108- >
109- <option value =" system" >{{ $t('settings.theme_system') }}</option >
110- <option value =" light" >{{ $t('settings.theme_light') }}</option >
111- <option value =" dark" >{{ $t('settings.theme_dark') }}</option >
112- </select >
113- </div >
114- </div >
94+ <!-- DISPLAY Section -->
95+ <section >
96+ <h2 class =" text-xs text-fg-subtle uppercase tracking-wider mb-4" >
97+ {{ $t('settings.sections.display') }}
98+ </h2 >
99+ <div class =" bg-bg-subtle border border-border rounded-lg p-4 sm:p-6 space-y-4" >
100+ <!-- Relative dates toggle -->
101+ <div class =" space-y-2" >
102+ <button
103+ type =" button"
104+ class =" w-full flex items-center justify-between gap-4 group"
105+ role =" switch"
106+ :aria-checked =" settings.relativeDates"
107+ @click =" settings.relativeDates = !settings.relativeDates"
108+ >
109+ <span class =" text-sm text-fg font-medium text-left" >
110+ {{ $t('settings.relative_dates') }}
111+ </span >
112+ <span
113+ class =" relative inline-flex h-6 w-11 shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out motion-reduce:transition-none shadow-sm cursor-pointer"
114+ :class =" settings.relativeDates ? 'bg-accent' : 'bg-bg border border-border'"
115+ aria-hidden =" true"
116+ >
117+ <span
118+ class =" pointer-events-none inline-block h-5 w-5 rounded-full shadow-sm ring-0 transition-transform duration-200 ease-in-out motion-reduce:transition-none"
119+ :class ="
120+ settings.relativeDates ? 'translate-x-5 bg-bg' : 'translate-x-0 bg-fg-muted'
121+ "
122+ />
123+ </span >
124+ </button >
125+ <p class =" text-sm text-fg-muted" >
126+ {{ $t('settings.relative_dates_description') }}
127+ </p >
128+ </div >
115129
116- <!-- Language selector -->
117- <div class =" pt-2 mt-2 border-t border-border" >
118- <div class =" px-2 py-1" >
119- <label for =" language-select" class =" text-xs text-fg-subtle uppercase tracking-wider" >
120- {{ $t('settings.language') }}
121- </label >
122- </div >
123- <div class =" px-2 py-1 space-y-2" >
124- <select
125- id =" language-select"
126- :value =" locale"
127- class =" w-full bg-bg-muted border border-border rounded-md px-2 py-1.5 text-sm text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 cursor-pointer"
128- @change =" setLocale(($event.target as HTMLSelectElement).value as typeof locale)"
129- >
130- <option v-for =" loc in availableLocales" :key =" loc.code" :value =" loc.code" >
131- {{ loc.name }}
132- </option >
133- </select >
134- </div >
130+ <!-- Divider -->
131+ <div class =" border-t border-border" />
135132
136- <!-- Translation helper for non-source locales -->
137- <div v-if =" currentLocaleStatus && !isSourceLocale" class =" px-2 py-2" >
138- <TranslationHelper :status =" currentLocaleStatus" />
139- </div >
133+ <!-- Include @types in install toggle -->
134+ <div class =" space-y-2" >
135+ <button
136+ type =" button"
137+ class =" w-full flex items-center justify-between gap-4 group"
138+ role =" switch"
139+ :aria-checked =" settings.includeTypesInInstall"
140+ @click =" settings.includeTypesInInstall = !settings.includeTypesInInstall"
141+ >
142+ <span class =" text-sm text-fg font-medium text-left" >
143+ {{ $t('settings.include_types') }}
144+ </span >
145+ <span
146+ class =" relative inline-flex h-6 w-11 shrink-0 items-center rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out motion-reduce:transition-none shadow-sm cursor-pointer"
147+ :class ="
148+ settings.includeTypesInInstall ? 'bg-accent' : 'bg-bg border border-border'
149+ "
150+ aria-hidden =" true"
151+ >
152+ <span
153+ class =" pointer-events-none inline-block h-5 w-5 rounded-full shadow-sm ring-0 transition-transform duration-200 ease-in-out motion-reduce:transition-none"
154+ :class ="
155+ settings.includeTypesInInstall
156+ ? 'translate-x-5 bg-bg'
157+ : 'translate-x-0 bg-fg-muted'
158+ "
159+ />
160+ </span >
161+ </button >
162+ <p class =" text-sm text-fg-muted" >
163+ {{ $t('settings.include_types_description') }}
164+ </p >
165+ </div >
166+ </div >
167+ </section >
140168
141- <!-- Simple help link for source locale -->
142- <a
143- v-else
144- href =" https://github.com/npmx-dev/npmx.dev/tree/main/i18n/locales"
145- target =" _blank"
146- rel =" noopener noreferrer"
147- class =" flex items-center gap-1.5 px-2 py-1.5 text-xs text-fg-muted hover:text-fg rounded transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
148- >
149- <span class =" i-carbon-logo-github w-3.5 h-3.5" aria-hidden =" true" />
150- {{ $t('settings.help_translate') }}
151- </a >
152- </div >
169+ <!-- LANGUAGE Section -->
170+ <section >
171+ <h2 class =" text-xs text-fg-subtle uppercase tracking-wider mb-4" >
172+ {{ $t('settings.sections.language') }}
173+ </h2 >
174+ <div class =" bg-bg-subtle border border-border rounded-lg p-4 sm:p-6 space-y-4" >
175+ <!-- Language selector -->
176+ <div class =" space-y-2" >
177+ <label for =" language-select" class =" block text-sm text-fg font-medium" >
178+ {{ $t('settings.language') }}
179+ </label >
180+ <select
181+ id =" language-select"
182+ :value =" locale"
183+ class =" w-full sm:w-auto min-w-48 bg-bg border border-border rounded-md px-3 py-2 text-sm text-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 cursor-pointer"
184+ @change =" setLocale(($event.target as HTMLSelectElement).value as typeof locale)"
185+ >
186+ <option v-for =" loc in availableLocales" :key =" loc.code" :value =" loc.code" >
187+ {{ loc.name }}
188+ </option >
189+ </select >
190+ </div >
153191
154- <div class =" pt-2 mt-2 border-t border-border" >
155- <div class =" text-xs text-fg-subtle uppercase tracking-wider px-2 py-1" >
156- {{ $t('settings.accent_colors') }}
157- </div >
158- <div class =" px-2 py-2" >
159- <AccentColorPicker />
160- </div >
192+ <!-- Translation helper for non-source locales -->
193+ <template v-if =" currentLocaleStatus && ! isSourceLocale " >
194+ <div class =" border-t border-border pt-4" >
195+ <TranslationHelper :status =" currentLocaleStatus" />
196+ </div >
197+ </template >
198+
199+ <!-- Simple help link for source locale -->
200+ <template v-else >
201+ <a
202+ href =" https://github.com/npmx-dev/npmx.dev/tree/main/i18n/locales"
203+ target =" _blank"
204+ rel =" noopener noreferrer"
205+ class =" inline-flex items-center gap-2 text-sm text-fg-muted hover:text-fg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
206+ >
207+ <span class =" i-carbon-logo-github w-4 h-4" aria-hidden =" true" />
208+ {{ $t('settings.help_translate') }}
209+ </a >
210+ </template >
211+ </div >
212+ </section >
161213 </div >
162- </div >
214+ </article >
163215 </main >
164216</template >
0 commit comments