[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: add accent color picker to settings (#173)

authored by

rshigg and committed by
GitHub
86a1818e d18813bf

+116 -9
+3
app/app.vue
··· 4 4 const route = useRoute() 5 5 const router = useRouter() 6 6 7 + // Initialize accent color before hydration to prevent flash 8 + initAccentOnPrehydrate() 9 + 7 10 const isHomepage = computed(() => route.path === '/') 8 11 9 12 useHead({
+29
app/components/AccentColorPicker.vue
··· 1 + <script setup lang="ts"> 2 + import { useAccentColor } from '~/composables/useSettings' 3 + 4 + const { accentColors, selectedAccentColor, setAccentColor } = useAccentColor() 5 + </script> 6 + 7 + <template> 8 + <div role="listbox" aria-label="Accent colors" class="flex items-center justify-between"> 9 + <button 10 + v-for="color in accentColors" 11 + :key="color.id" 12 + type="button" 13 + role="option" 14 + :aria-selected="selectedAccentColor === color.id" 15 + :aria-label="color.name" 16 + class="size-6 rounded-full transition-transform duration-150 motion-safe:hover:scale-110 focus-ring aria-selected:(ring-2 ring-fg ring-offset-2 ring-offset-bg-subtle)" 17 + :style="{ backgroundColor: color.value }" 18 + @click="setAccentColor(color.id)" 19 + /> 20 + <button 21 + type="button" 22 + aria-label="Clear accent color" 23 + class="size-6 rounded-full transition-transform duration-150 motion-safe:hover:scale-110 focus-ring flex items-center justify-center bg-accent-fallback" 24 + @click="setAccentColor(null)" 25 + > 26 + <span class="i-carbon-error size-4 text-bg" aria-hidden="true" /> 27 + </button> 28 + </div> 29 + </template>
+1 -1
app/components/AppHeader.vue
··· 24 24 :aria-label="$t('header.home')" 25 25 class="header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded" 26 26 > 27 - <span class="text-fg-subtle"><span style="letter-spacing: -0.2em">.</span>/</span>npmx 27 + <span class="text-accent"><span class="-tracking-0.2em">.</span>/</span>npmx 28 28 </NuxtLink> 29 29 <!-- Spacer when logo is hidden --> 30 30 <span v-else class="w-1" />
+4
app/components/SettingsMenu.vue
··· 163 163 </a> 164 164 </div> 165 165 </div> 166 + 167 + <div class="p-3 border-t border-border"> 168 + <AccentColorPicker /> 169 + </div> 166 170 </div> 167 171 </Transition> 168 172 </div>
+59
app/composables/useSettings.ts
··· 1 1 import type { RemovableRef } from '@vueuse/core' 2 2 import { useLocalStorage } from '@vueuse/core' 3 + import { ACCENT_COLORS } from '#shared/utils/constants' 4 + 5 + type AccentColorId = keyof typeof ACCENT_COLORS 3 6 4 7 /** 5 8 * Application settings stored in localStorage ··· 9 12 relativeDates: boolean 10 13 /** Include @types/* package in install command for packages without built-in types */ 11 14 includeTypesInInstall: boolean 15 + /** Accent color theme */ 16 + accentColorId: AccentColorId | null 12 17 } 13 18 14 19 const DEFAULT_SETTINGS: AppSettings = { 15 20 relativeDates: false, 16 21 includeTypesInInstall: true, 22 + accentColorId: null, 17 23 } 18 24 19 25 const STORAGE_KEY = 'npmx-settings' ··· 45 51 const { settings } = useSettings() 46 52 return computed(() => settings.value.relativeDates) 47 53 } 54 + 55 + /** 56 + * Composable for managing accent color. 57 + */ 58 + export function useAccentColor() { 59 + const { settings } = useSettings() 60 + 61 + const accentColors = Object.entries(ACCENT_COLORS).map(([id, value]) => ({ 62 + id: id as AccentColorId, 63 + name: id, 64 + value, 65 + })) 66 + 67 + function setAccentColor(id: AccentColorId | null) { 68 + const color = id ? ACCENT_COLORS[id] : null 69 + if (color) { 70 + document.documentElement.style.setProperty('--accent-color', color) 71 + } else { 72 + document.documentElement.style.removeProperty('--accent-color') 73 + } 74 + settings.value.accentColorId = id 75 + } 76 + 77 + return { 78 + accentColors, 79 + selectedAccentColor: computed(() => settings.value.accentColorId), 80 + setAccentColor, 81 + } 82 + } 83 + 84 + /** 85 + * Applies accent color before hydration to prevent flash of default color. 86 + * Call this from app.vue to ensure accent color is applied on every page. 87 + */ 88 + export function initAccentOnPrehydrate() { 89 + // Callback is stringified by Nuxt - external variables won't be available. 90 + // Colors must be hardcoded since ACCENT_COLORS can't be referenced. 91 + onPrehydrate(() => { 92 + const colors: Record<AccentColorId, string> = { 93 + rose: '#e9aeba', 94 + amber: '#fbbf24', 95 + emerald: '#34d399', 96 + sky: '#38bdf8', 97 + violet: '#a78bfa', 98 + coral: '#fb7185', 99 + } 100 + const settings = JSON.parse(localStorage.getItem('npmx-settings') || '{}') 101 + const color = settings.accentColorId ? colors[settings.accentColorId as AccentColorId] : null 102 + if (color) { 103 + document.documentElement.style.setProperty('--accent-color', color) 104 + } 105 + }) 106 + }
+4 -4
app/pages/index.vue
··· 27 27 <h1 28 28 class="font-mono text-5xl sm:text-7xl md:text-8xl font-medium tracking-tight mb-4 animate-fade-in animate-fill-both" 29 29 > 30 - <span class="text-fg-subtle"><span style="letter-spacing: -0.2em">.</span>/</span>npmx 30 + <span class="text-accent"><span class="-tracking-0.2em">.</span>/</span>npmx 31 31 </h1> 32 32 33 33 <p ··· 56 56 57 57 <div class="search-box relative flex items-center"> 58 58 <span 59 - class="absolute left-4 text-fg-subtle font-mono text-sm pointer-events-none transition-colors duration-200 group-focus-within:text-fg-muted z-1" 59 + class="absolute left-4 text-fg-subtle font-mono text-sm pointer-events-none transition-colors duration-200 group-focus-within:text-accent z-1" 60 60 > 61 61 / 62 62 </span> ··· 69 69 :placeholder="$t('search.placeholder')" 70 70 autocomplete="off" 71 71 autofocus 72 - class="w-full bg-bg-subtle border border-border rounded-lg pl-8 pr-24 py-4 font-mono text-base text-fg placeholder:text-fg-subtle transition-all duration-300 focus:(border-border-hover outline-none)" 72 + class="w-full bg-bg-subtle border border-border rounded-lg pl-8 pr-24 py-4 font-mono text-base text-fg placeholder:text-fg-subtle transition-all duration-300 focus:(border-accent outline-none)" 73 73 @input="handleSearch" 74 74 @focus="isSearchFocused = true" 75 75 @blur="isSearchFocused = false" ··· 103 103 class="link-subtle font-mono text-sm inline-flex items-center gap-2 group" 104 104 > 105 105 <span 106 - class="w-1 h-1 rounded-full bg-fg-subtle group-hover:bg-fg transition-colors duration-200" 106 + class="w-1 h-1 rounded-full bg-accent group-hover:bg-fg transition-colors duration-200" 107 107 /> 108 108 {{ pkg }} 109 109 </NuxtLink>
+2 -2
app/pages/search.vue
··· 356 356 357 357 <div class="search-box relative flex items-center"> 358 358 <span 359 - class="absolute left-4 text-fg-subtle font-mono text-base pointer-events-none transition-colors duration-200 group-focus-within:text-fg-muted" 359 + class="absolute left-4 text-fg-subtle font-mono text-base pointer-events-none transition-colors duration-200 group-focus-within:text-accent" 360 360 aria-hidden="true" 361 361 > 362 362 / ··· 372 372 autocomplete="off" 373 373 autocorrect="off" 374 374 spellcheck="false" 375 - class="w-full max-w-full bg-bg-subtle border border-border rounded-lg pl-8 pr-10 py-3 font-mono text-base text-fg placeholder:text-fg-subtle transition-colors duration-300 focus:border-border-hover focus-visible:outline-none appearance-none" 375 + class="w-full max-w-full bg-bg-subtle border border-border rounded-lg pl-8 pr-10 py-3 font-mono text-base text-fg placeholder:text-fg-subtle transition-colors duration-300 focus:border-accent focus-visible:outline-none appearance-none" 376 376 @focus="isSearchFocused = true" 377 377 @blur="isSearchFocused = false" 378 378 @keydown="handleResultsKeydown"
+10
shared/utils/constants.ts
··· 16 16 export const ERROR_JSR_FETCH_FAILED = 'Failed to fetch package from JSR registry.' 17 17 export const ERROR_NPM_FETCH_FAILED = 'Failed to fetch package from npm registry.' 18 18 export const ERROR_SUGGESTIONS_FETCH_FAILED = 'Failed to fetch suggestions.' 19 + 20 + // Theming 21 + export const ACCENT_COLORS = { 22 + rose: '#e9aeba', 23 + amber: '#fbbf24', 24 + emerald: '#34d399', 25 + sky: '#38bdf8', 26 + violet: '#a78bfa', 27 + coral: '#fb7185', 28 + } as const
+2
test/nuxt/components/DateTime.spec.ts
··· 9 9 useSettings: () => ({ 10 10 settings: ref({ relativeDates: mockRelativeDates.value }), 11 11 }), 12 + useAccentColor: () => ({}), 13 + initAccentOnPrehydrate: () => {}, 12 14 })) 13 15 14 16 describe('DateTime', () => {
+2 -2
uno.config.ts
··· 47 47 hover: '#404040', 48 48 }, 49 49 accent: { 50 - DEFAULT: '#ffffff', 51 - muted: '#e5e5e5', 50 + DEFAULT: 'var(--accent-color, #666666)', 51 + fallback: '#666666', 52 52 }, 53 53 // Syntax highlighting colors (inspired by GitHub Dark) 54 54 syntax: {