[READ-ONLY] a fast, modern browser for the npm registry
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: redesign settings page (#294)

authored by

Daniel Roe and committed by
GitHub
63b77b24 e78f0539

+206 -134
+184 -132
app/pages/settings.vue
··· 9 9 locales.value.map(l => (typeof l === 'string' ? { code: l, name: l } : l)), 10 10 ) 11 11 12 - function goBack() { 13 - router.back() 14 - } 15 - 12 + // Escape to go back (but not when focused on form elements) 16 13 onKeyStroke('Escape', e => { 17 14 const target = e.target as HTMLElement 18 15 if (!['INPUT', 'SELECT', 'TEXTAREA'].includes(target?.tagName)) { 19 - goBack() 16 + router.back() 20 17 } 21 18 }) 22 19 23 20 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'), 25 28 }) 26 29 </script> 27 30 28 31 <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> 39 53 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> 62 83 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> 89 93 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> 115 129 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" /> 135 132 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> 140 168 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> 191 + 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> 153 198 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> 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> 161 213 </div> 162 - </div> 214 + </article> 163 215 </main> 164 216 </template>
+11 -1
i18n/locales/en.json
··· 44 44 "popular_packages": "Popular packages", 45 45 "search": "search", 46 46 "settings": "settings", 47 - "back": "Back" 47 + "back": "back" 48 48 }, 49 49 "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 + }, 50 58 "relative_dates": "Relative dates", 59 + "relative_dates_description": "Show \"3 days ago\" instead of full dates", 51 60 "include_types": "Include {'@'}types in install", 61 + "include_types_description": "Add {'@'}types package to install commands for untyped packages", 52 62 "theme": "Theme", 53 63 "theme_light": "Light", 54 64 "theme_dark": "Dark",
+11 -1
lunaria/files/en-US.json
··· 44 44 "popular_packages": "Popular packages", 45 45 "search": "search", 46 46 "settings": "settings", 47 - "back": "Back" 47 + "back": "back" 48 48 }, 49 49 "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 + }, 50 58 "relative_dates": "Relative dates", 59 + "relative_dates_description": "Show \"3 days ago\" instead of full dates", 51 60 "include_types": "Include {'@'}types in install", 61 + "include_types_description": "Add {'@'}types package to install commands for untyped packages", 52 62 "theme": "Theme", 53 63 "theme_light": "Light", 54 64 "theme_dark": "Dark",