Skip to content

Commit 0a11339

Browse files
committed
feat: add accent color picker to settings
1 parent 6c27312 commit 0a11339

8 files changed

Lines changed: 164 additions & 7 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/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
@@ -163,6 +163,10 @@ onKeyStroke(',', e => {
163163
</a>
164164
</div>
165165
</div>
166+
167+
<div class="p-3 border-t border-border">
168+
<AccentColorPicker />
169+
</div>
166170
</div>
167171
</Transition>
168172
</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
@@ -56,7 +56,7 @@ defineOgImageComponent('Default')
5656

5757
<div class="search-box relative flex items-center">
5858
<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"
6060
>
6161
/
6262
</span>
@@ -69,7 +69,7 @@ defineOgImageComponent('Default')
6969
:placeholder="$t('search.placeholder')"
7070
autocomplete="off"
7171
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)"
7373
@input="handleSearch"
7474
@focus="isSearchFocused = true"
7575
@blur="isSearchFocused = false"
@@ -103,7 +103,7 @@ defineOgImageComponent('Default')
103103
class="link-subtle font-mono text-sm inline-flex items-center gap-2 group"
104104
>
105105
<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"
107107
/>
108108
{{ pkg }}
109109
</NuxtLink>

app/pages/search.vue

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

357357
<div class="search-box relative flex items-center">
358358
<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"
360360
aria-hidden="true"
361361
>
362362
/
@@ -372,7 +372,7 @@ defineOgImageComponent('Default', {
372372
autocomplete="off"
373373
autocorrect="off"
374374
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"
376376
@focus="isSearchFocused = true"
377377
@blur="isSearchFocused = false"
378378
@keydown="handleResultsKeydown"

nuxt.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ export default defineNuxtConfig({
4646
href: '/opensearch.xml',
4747
},
4848
],
49+
script: [
50+
{
51+
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')})()`,
52+
type: 'text/javascript',
53+
},
54+
],
4955
},
5056
},
5157

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)