Skip to content

Commit 63b77b2

Browse files
authored
feat: redesign settings page (#294)
1 parent e78f053 commit 63b77b2

3 files changed

Lines changed: 206 additions & 134 deletions

File tree

app/pages/settings.vue

Lines changed: 184 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
1613
onKeyStroke('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
2320
useSeoMeta({
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>

i18n/locales/en.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,21 @@
4444
"popular_packages": "Popular packages",
4545
"search": "search",
4646
"settings": "settings",
47-
"back": "Back"
47+
"back": "back"
4848
},
4949
"settings": {
50+
"title": "settings",
51+
"tagline": "customize your npmx experience",
52+
"meta_description": "Customize your npmx.dev experience with theme, language, and display preferences.",
53+
"sections": {
54+
"appearance": "Appearance",
55+
"display": "Display",
56+
"language": "Language"
57+
},
5058
"relative_dates": "Relative dates",
59+
"relative_dates_description": "Show \"3 days ago\" instead of full dates",
5160
"include_types": "Include {'@'}types in install",
61+
"include_types_description": "Add {'@'}types package to install commands for untyped packages",
5262
"theme": "Theme",
5363
"theme_light": "Light",
5464
"theme_dark": "Dark",

lunaria/files/en-US.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,21 @@
4444
"popular_packages": "Popular packages",
4545
"search": "search",
4646
"settings": "settings",
47-
"back": "Back"
47+
"back": "back"
4848
},
4949
"settings": {
50+
"title": "settings",
51+
"tagline": "customize your npmx experience",
52+
"meta_description": "Customize your npmx.dev experience with theme, language, and display preferences.",
53+
"sections": {
54+
"appearance": "Appearance",
55+
"display": "Display",
56+
"language": "Language"
57+
},
5058
"relative_dates": "Relative dates",
59+
"relative_dates_description": "Show \"3 days ago\" instead of full dates",
5160
"include_types": "Include {'@'}types in install",
61+
"include_types_description": "Add {'@'}types package to install commands for untyped packages",
5262
"theme": "Theme",
5363
"theme_light": "Light",
5464
"theme_dark": "Dark",

0 commit comments

Comments
 (0)