Skip to content

Commit 65ed368

Browse files
committed
feat: add accent color picker to settings
1 parent bf55ec9 commit 65ed368

9 files changed

Lines changed: 165 additions & 8 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<script setup lang="ts">
2+
import { ACCENT_COLORS, useAccentColor, type ColorId } from '~/composables/useAccentColor'
3+
4+
const { accentColorId, setAccentColor } = useAccentColor()
5+
6+
const popoverRef = ref<HTMLElement | null>(null)
7+
8+
function selectColor(id: ColorId) {
9+
setAccentColor(id)
10+
popoverRef.value?.hidePopover()
11+
}
12+
13+
function clearColor() {
14+
setAccentColor(null)
15+
popoverRef.value?.hidePopover()
16+
}
17+
</script>
18+
19+
<template>
20+
<div class="flex items-center justify-between">
21+
<button
22+
v-for="color in ACCENT_COLORS"
23+
:key="color.id"
24+
type="button"
25+
role="option"
26+
:aria-selected="accentColorId === color.id"
27+
:aria-label="color.name"
28+
class="size-6 rounded-full transition-transform duration-150 hover:scale-110 focus-ring"
29+
:class="{
30+
'ring-2 ring-fg ring-offset-2 ring-offset-bg-subtle': accentColorId === color.id,
31+
}"
32+
:style="{ backgroundColor: color.value }"
33+
@click="selectColor(color.id)"
34+
/>
35+
<button
36+
type="button"
37+
aria-label="Clear accent color"
38+
class="size-6 rounded-full transition-transform duration-150 hover:scale-110 focus-ring flex items-center justify-center bg-accent-fallback"
39+
@click="clearColor"
40+
>
41+
<span class="i-carbon-error size-4 text-bg" />
42+
</button>
43+
</div>
44+
</template>

app/components/AppHeader.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ withDefaults(
2020
aria-label="npmx home"
2121
class="header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
2222
>
23-
<span class="text-fg-subtle"><span style="letter-spacing: -0.2em">.</span>/</span>npmx
23+
<span class="text-accent"><span class="-tracking-0.2em">.</span>/</span>npmx
2424
</NuxtLink>
2525
<!-- Spacer when logo is hidden -->
2626
<span v-else class="w-1" />

app/components/SearchInput.vue

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<script setup lang="ts">
2+
const props = withDefaults(
3+
defineProps<{
4+
id?: string
5+
placeholder?: string
6+
hideSubmitButton?: boolean
7+
}>(),
8+
{
9+
id: 'home-search',
10+
placeholder: 'search packages...',
11+
hideSubmitButton: false,
12+
},
13+
)
14+
15+
const modelValue = defineModel<string>({ default: '' })
16+
const emit = defineEmits<{
17+
submit: []
18+
keydown: [event: KeyboardEvent]
19+
}>()
20+
21+
const inputRef = ref<HTMLInputElement>()
22+
23+
function focus() {
24+
inputRef.value?.focus()
25+
}
26+
27+
defineExpose({ focus, inputRef })
28+
</script>
29+
30+
<template>
31+
<div
32+
class="group rounded-lg border border-border search-box relative grid grid-cols-[auto_1fr_auto] px-2 bg-bg-subtle items-center focus-within:border-accent/70"
33+
>
34+
<span
35+
class="px-2 text-fg-subtle font-mono text-sm pointer-events-none transition-colors duration-200 group-focus-within:text-accent z-1"
36+
>
37+
/
38+
</span>
39+
40+
<input
41+
:id="props.id"
42+
ref="inputRef"
43+
v-model="modelValue"
44+
type="search"
45+
name="q"
46+
:placeholder="props.placeholder"
47+
autocomplete="off"
48+
class="bg-bg-subtle py-4 font-mono text-base text-fg placeholder:text-fg-subtle transition-all duration-300 focus:outline-none"
49+
@keydown="emit('keydown', $event)"
50+
/>
51+
52+
<button
53+
type="submit"
54+
class="px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md transition-all duration-200 hover:bg-fg/90 active:scale-95"
55+
:class="{ 'sr-only': hideSubmitButton }"
56+
>
57+
search
58+
</button>
59+
</div>
60+
</template>

app/components/SettingsMenu.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ onKeyStroke(',', e => {
128128
</span>
129129
</button>
130130
</div>
131+
132+
<div class="p-3 border-t border-border">
133+
<AccentColorPicker />
134+
</div>
131135
</div>
132136
</Transition>
133137
</div>

app/composables/useAccentColor.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { computed } from 'vue'
2+
import { useLocalStorage } from '@vueuse/core'
3+
4+
export interface AccentColor {
5+
id: string
6+
name: string
7+
value: string
8+
}
9+
10+
export const ACCENT_COLORS = [
11+
{ id: 'rose', name: 'Rose', value: '#e9aeba' },
12+
{ id: 'amber', name: 'Amber', value: '#fbbf24' },
13+
{ id: 'emerald', name: 'Emerald', value: '#34d399' },
14+
{ id: 'sky', name: 'Sky', value: '#38bdf8' },
15+
{ id: 'violet', name: 'Violet', value: '#a78bfa' },
16+
{ id: 'coral', name: 'Coral', value: '#fb7185' },
17+
] as const satisfies readonly AccentColor[]
18+
19+
export type ColorId = (typeof ACCENT_COLORS)[number]['id']
20+
21+
function applyColorToDocument(color: string | null) {
22+
if (color) {
23+
document.documentElement.style.setProperty('--accent-color', color)
24+
} else {
25+
document.documentElement.style.removeProperty('--accent-color')
26+
}
27+
}
28+
29+
export function useAccentColor() {
30+
const accentColorId = useLocalStorage<string | null>('app-accent', null)
31+
32+
function setAccentColor(id: ColorId | null) {
33+
const chosenColor = ACCENT_COLORS.find(color => color.id === id)?.value ?? null
34+
applyColorToDocument(chosenColor)
35+
accentColorId.value = id
36+
}
37+
38+
return {
39+
accentColorId: computed(() => accentColorId.value),
40+
setAccentColor,
41+
}
42+
}

app/pages/index.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ defineOgImageComponent('Default')
2727
<h1
2828
class="font-mono text-5xl sm:text-7xl md:text-8xl font-medium tracking-tight mb-4 animate-fade-in animate-fill-both"
2929
>
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
3131
</h1>
3232

3333
<p
@@ -54,7 +54,7 @@ defineOgImageComponent('Default')
5454

5555
<div class="search-box relative flex items-center">
5656
<span
57-
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"
57+
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"
5858
>
5959
/
6060
</span>
@@ -66,7 +66,7 @@ defineOgImageComponent('Default')
6666
name="q"
6767
placeholder="search packages..."
6868
autocomplete="off"
69-
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)"
69+
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)"
7070
@input="handleSearch"
7171
@focus="isSearchFocused = true"
7272
@blur="isSearchFocused = false"
@@ -100,7 +100,7 @@ defineOgImageComponent('Default')
100100
class="link-subtle font-mono text-sm inline-flex items-center gap-2 group"
101101
>
102102
<span
103-
class="w-1 h-1 rounded-full bg-fg-subtle group-hover:bg-fg transition-colors duration-200"
103+
class="w-1 h-1 rounded-full bg-accent group-hover:bg-fg transition-colors duration-200"
104104
/>
105105
{{ pkg }}
106106
</NuxtLink>

app/pages/search.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ defineOgImageComponent('Default', {
355355

356356
<div class="search-box relative flex items-center">
357357
<span
358-
class="absolute left-4 text-fg-subtle font-mono text-base pointer-events-none transition-colors duration-200 group-focus-within:text-fg-muted"
358+
class="absolute left-4 text-fg-subtle font-mono text-base pointer-events-none transition-colors duration-200 group-focus-within:text-accent"
359359
aria-hidden="true"
360360
>
361361
/
@@ -371,7 +371,7 @@ defineOgImageComponent('Default', {
371371
autocomplete="off"
372372
autocorrect="off"
373373
spellcheck="false"
374-
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"
374+
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"
375375
@focus="isSearchFocused = true"
376376
@blur="isSearchFocused = false"
377377
@keydown="handleResultsKeydown"

nuxt.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ export default defineNuxtConfig({
3131
href: '/opensearch.xml',
3232
},
3333
],
34+
script: [
35+
{
36+
innerHTML: `(function(){var c={rose:'#e9aeba',amber:'#fbbf24',emerald:'#34d399',sky:'#38bdf8',violet:'#a78bfa',coral:'#fb7185'};var s=localStorage.getItem('npmx-accent');document.documentElement.style.setProperty('--accent-color',s&&c[s]||'#666666')})()`,
37+
type: 'text/javascript',
38+
},
39+
],
3440
},
3541
},
3642

uno.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ export default defineConfig({
4747
hover: '#404040',
4848
},
4949
accent: {
50-
DEFAULT: '#ffffff',
50+
DEFAULT: 'var(--accent-color, #666666)',
51+
fallback: '#666666',
5152
muted: '#e5e5e5',
5253
},
5354
// Syntax highlighting colors (inspired by GitHub Dark)

0 commit comments

Comments
 (0)