feat: improve feedback component, brand color picker

This commit is contained in:
taskylizard 2025-03-27 12:54:11 +00:00
parent 93ed69ddbf
commit 1622da5db9
No known key found for this signature in database
GPG key ID: 1820131ED1A24120
8 changed files with 390 additions and 31 deletions

View file

@ -29,8 +29,8 @@ onMounted(() => {
>
<ClientOnly>
<Transition name="fade" mode="out-in">
<VPIconSun v-if="!isDark" class="sun" />
<VPIconMoon v-else class="moon" />
<div v-if="!isDark" class="sun text-xl i-ph-sun-duotone" />
<div v-else class="moon text-xl i-ph-moon-duotone" />
</Transition>
</ClientOnly>
</button>

View file

@ -0,0 +1,88 @@
<script setup lang="ts">
import { colors } from '@fmhy/colors'
import { useStorage, useStyleTag } from '@vueuse/core'
import { watch } from 'vue'
const colorScales = [
'50',
'100',
'200',
'300',
'400',
'500',
'600',
'700',
'800',
'900',
'950'
] as const
type ColorNames = keyof typeof colors
const selectedColor = useStorage<ColorNames>('preferred-color', 'swarm')
const colorOptions = Object.keys(colors).filter(
(key) => typeof colors[key as keyof typeof colors] === 'object'
) as Array<ColorNames>
const { css } = useStyleTag('', { id: 'brand-color' })
const updateThemeColor = (colorName: ColorNames) => {
const colorSet = colors[colorName]
const cssVars = colorScales
.map((scale) => `--vp-c-brand-${scale}: ${colorSet[scale]};`)
.join('\n ')
css.value = `
:root {
${cssVars}
--vp-c-brand-1: ${colorSet[500]};
--vp-c-brand-2: ${colorSet[600]};
--vp-c-brand-3: ${colorSet[800]};
--vp-c-brand-soft: ${colorSet[400]};
}
.dark {
${cssVars}
--vp-c-brand-1: ${colorSet[400]};
--vp-c-brand-2: ${colorSet[500]};
--vp-c-brand-3: ${colorSet[700]};
--vp-c-brand-soft: ${colorSet[300]};
}
`
}
// Initialize theme color
updateThemeColor(selectedColor.value)
watch(selectedColor, updateThemeColor)
const normalizeColorName = (colorName: string) =>
colorName.replaceAll(/-/g, ' ').charAt(0).toUpperCase() +
colorName.slice(1).replaceAll(/-/g, ' ')
</script>
<template>
<div>
<div class="flex flex-wrap gap-2">
<div v-for="color in colorOptions" :key="color">
<button
:class="[
'inline-block w-6 h-6 rounded-full transition-all duration-200'
]"
@click="selectedColor = color"
:title="normalizeColorName(color)"
>
<span
class="inline-block w-6 h-6 rounded-full"
:style="{ backgroundColor: colors[color][500] }"
/>
</button>
</div>
</div>
<div class="mt-2 text-sm text-$vp-c-text-2">
Selected: {{ normalizeColorName(selectedColor) }}
</div>
</div>
</template>

View file

@ -69,10 +69,12 @@ const isDisabled = computed(() => {
})
const router = useRouter()
// prettier-ignore
const feedback = reactive<
Pick<FeedbackType, 'message' | 'page'> & Partial<Pick<FeedbackType, 'type'>>
>({
const feedback = reactive<{
message: string
page: string
type?: FeedbackType['type']
}>({
page: router.route.path,
message: ''
})
@ -142,17 +144,45 @@ const toggleCard = () => (isCardShown.value = !isCardShown.value)
</button>
</template>
<template v-else>
<button
class="bg-$vp-c-default-soft text-primary px2 py1 border-$vp-c-default-soft hover:border-primary mt-2 select-none rounded border-2 border-solid font-bold transition-all duration-300"
@click="toggleCard()"
<div
class="mt-2 p-4 border-2 border-solid bg-brand-50 border-brand-300 dark:bg-brand-950 dark:border-brand-800 rounded-xl col-span-3 transition-colors duration-250"
>
<span
:class="
isCardShown === false ? `i-lucide:mail mr-2` : `i-lucide:mail-x mr-2`
"
/>
<span>Send Feedback</span>
</button>
<div class="flex items-start md:items-center gap-3">
<div class="pt-1 md:pt-0">
<div
class="w-10 h-10 rounded-full flex items-center justify-center bg-brand-500 dark:bg-brand-400"
>
<span
:class="
isCardShown === false
? `i-lucide:mail w-6 h-6 text-white dark:text-brand-950`
: `i-lucide:mail-x w-6 h-6 text-white dark:text-brand-950`
"
/>
</div>
</div>
<div
class="flex-grow flex items-start md:items-center gap-3 flex-col md:flex-row"
>
<div class="flex-grow">
<div class="font-semibold text-brand-950 dark:text-brand-50">
Got feedback?
</div>
<div class="text-sm text-brand-800 dark:text-brand-100">
We'd love to know what you think about this page.
</div>
</div>
<div>
<button
class="inline-block text-center rounded-full px-4 py-2.5 text-sm font-medium border-2 border-solid text-brand-700 border-brand-300 dark:text-brand-100 dark:border-brand-800"
@click="toggleCard()"
>
Share Feedback
</button>
</div>
</div>
</div>
</div>
</template>
<Transition name="fade" mode="out-in">
@ -254,7 +284,7 @@ const toggleCard = () => (isCardShown.value = !isCardShown.value)
</button>
<button
type="submit"
class="border border-div rounded-lg transition-colors duration-250 inline-block text-14px font-500 leading-1.5 px-3 py-3 text-center align-middle whitespace-nowrap disabled:opacity-50 text-text-2 bg-swarm-100 dark:bg-swarm-700 border-swarm-800 dark:border-swarm-700 disabled:bg-swarm-100 dark:disabled:bg-swarm-900 hover:border-swarm-900 dark:hover:border-swarm-800 hover:bg-swarm-200 dark:hover:bg-swarm-800"
class="border border-div rounded-lg transition-colors duration-250 inline-block text-14px font-500 leading-1.5 px-3 py-3 text-center align-middle whitespace-nowrap disabled:opacity-50 text-text-2 bg-brand-100 dark:bg-brand-700 border-brand-800 dark:border-brand-700 disabled:bg-brand-100 dark:disabled:bg-brand-900 hover:border-brand-900 dark:hover:border-brand-800 hover:bg-brand-200 dark:hover:bg-brand-800"
:disabled="isDisabled"
@click="handleSubmit()"
>

View file

@ -1,5 +1,6 @@
<script setup lang="ts">
import Field from './CardField.vue'
import ColorPicker from './ColorPicker.vue'
import InputField from './InputField.vue'
import ToggleStarred from './ToggleStarred.vue'
</script>
@ -16,7 +17,7 @@ import ToggleStarred from './ToggleStarred.vue'
<Field icon="i-twemoji-globe-with-meridians">Indexes</Field>
<Field icon="i-twemoji-repeat-button">Storage Links</Field>
<Field icon="i-twemoji-star">Recommendations</Field>
<div class="align-center mb-4 flex justify-between">
<div class="align-center mb-4 mt-4 flex justify-between">
<div class="text-$vp-c-text-1 lh-relaxed text-sm font-bold">Options</div>
</div>
<InputField id="toggle-starred" label="Toggle Starred">
@ -25,16 +26,6 @@ import ToggleStarred from './ToggleStarred.vue'
</template>
</InputField>
<Field icon="i-lucide:github">
<a
href="https://github.com/fmhy/edit"
target="_blank"
rel="noopener noreferrer"
aria-label="Star FMHY on GitHub"
class="text-primary underline font-bold"
>
Star on GitHub
</a>
</Field>
<ColorPicker />
</div>
</template>

View file

@ -257,6 +257,71 @@
font-weight: bold;
}
.info.custom-block a {
color: var(--vp-custom-block-info-text);
font-weight: 500;
text-decoration: underline;
text-underline-offset: 2px;
transition: opacity 0.25s;
}
.info.custom-block a:hover {
opacity: 0.7;
color: var(--vp-custom-block-info-text-deep);
}
.note.custom-block a {
color: var(--vp-custom-block-info-text);
font-weight: 500;
text-decoration: underline;
text-underline-offset: 2px;
transition: opacity 0.25s;
}
.note.custom-block a:hover {
opacity: 0.7;
color: var(--vp-custom-block-note-text-deep);
}
.tip.custom-block a {
color: var(--vp-custom-block-tip-text);
font-weight: 500;
text-decoration: underline;
text-underline-offset: 2px;
transition: opacity 0.25s;
}
.tip.custom-block a:hover {
opacity: 0.7;
color: var(--vp-custom-block-tip-text-deep);
}
.warning.custom-block a {
color: var(--vp-custom-block-warning-text);
font-weight: 500;
text-decoration: underline;
text-underline-offset: 2px;
transition: opacity 0.25s;
}
.warning.custom-block a:hover {
opacity: 0.7;
color: var(--vp-custom-block-warning-text-deep);
}
.danger.custom-block a {
color: var(--vp-custom-block-danger-text);
font-weight: 500;
text-decoration: underline;
text-underline-offset: 2px;
transition: opacity 0.25s;
}
.danger.custom-block a:hover {
opacity: 0.7;
color: var(--vp-custom-block-danger-text-deep);
}
.info.custom-block {
--icon: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWluZm8iPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIi8+PHBhdGggZD0iTTEyIDE2di00Ii8+PHBhdGggZD0iTTEyIDhoLjAxIi8+PC9zdmc+');
}