refactor(buttons): introduce shadcn-vue Button, gold-pill variants
Add the shadcn-vue Button component at src/components/ui/button/ and sweep every site CTA through it instead of repeating the same rounded-md class string across nine views. The Button's variants are tuned to the new Wix-inspired aesthetic: - default: gold fill, cream text, rounded-full, uppercase tracking 0.2em - outline: gold outline + gold text on transparent, same shape - ghost / link / secondary / destructive: rounded-full equivalents Two follow-ups noted: 1. components.json had a stale "framework" key from older shadcn-vue schemas; dropped. The CLI still rejects the file on a path-resolve check against tsconfig — needs deeper investigation, so I wrote the Button by hand for now. 2. SiteHeader's nav-dropdown toggles and the locale switcher are not routed through Button on purpose — they're a different control pattern from CTAs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fe31fce725
commit
0a3bdb004b
10 changed files with 125 additions and 92 deletions
26
src/components/ui/button/Button.vue
Normal file
26
src/components/ui/button/Button.vue
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { Primitive, type PrimitiveProps } from 'reka-ui'
|
||||
import { type ButtonVariants, buttonVariants } from '.'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface Props extends PrimitiveProps {
|
||||
variant?: ButtonVariants['variant']
|
||||
size?: ButtonVariants['size']
|
||||
class?: HTMLAttributes['class']
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
as: 'button',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Primitive
|
||||
:as="as"
|
||||
:as-child="asChild"
|
||||
:class="cn(buttonVariants({ variant, size }), props.class)"
|
||||
>
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
34
src/components/ui/button/index.ts
Normal file
34
src/components/ui/button/index.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
export { default as Button } from './Button.vue'
|
||||
|
||||
export const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center whitespace-nowrap font-display uppercase tracking-[0.2em] ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'rounded-full bg-primary text-primary-foreground hover:opacity-90',
|
||||
outline:
|
||||
'rounded-full border border-accent/50 bg-transparent text-accent hover:bg-accent/10',
|
||||
ghost: 'rounded-full text-accent hover:bg-accent/10',
|
||||
link: 'text-accent underline-offset-4 hover:underline',
|
||||
secondary:
|
||||
'rounded-full bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
destructive:
|
||||
'rounded-full bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
},
|
||||
size: {
|
||||
default: 'h-12 px-6 py-3 text-xs',
|
||||
sm: 'h-9 px-4 text-[10px]',
|
||||
lg: 'h-14 px-8 text-sm',
|
||||
icon: 'h-10 w-10 rounded-full',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export type ButtonVariants = VariantProps<typeof buttonVariants>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const { t, tm, rt } = useI18n()
|
||||
|
||||
|
|
@ -178,18 +179,14 @@ const exteriorItems = tm('accommodation.exterior.items') as string[]
|
|||
<!-- CTAs -->
|
||||
<section class="border-t border-border bg-secondary/30">
|
||||
<div class="mx-auto flex max-w-7xl flex-wrap items-center gap-3 px-4 py-12 lg:px-6">
|
||||
<a
|
||||
href="mailto:chateaudufaune@ariege.io?subject=R%C3%A9servation%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune"
|
||||
class="rounded-md bg-primary px-5 py-3 text-sm font-medium text-primary-foreground hover:opacity-90"
|
||||
>
|
||||
<Button as-child>
|
||||
<a href="mailto:chateaudufaune@ariege.io?subject=R%C3%A9servation%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune">
|
||||
{{ t('accommodation.ctaReserve') }}
|
||||
</a>
|
||||
<RouterLink
|
||||
to="/reservations"
|
||||
class="rounded-md border border-border px-5 py-3 text-sm font-medium hover:bg-muted"
|
||||
>
|
||||
← {{ t('accommodation.ctaBack') }}
|
||||
</RouterLink>
|
||||
</Button>
|
||||
<Button as-child variant="outline">
|
||||
<RouterLink to="/reservations">← {{ t('accommodation.ctaBack') }}</RouterLink>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
|
@ -90,12 +91,9 @@ const events = [
|
|||
</ul>
|
||||
|
||||
<div class="mt-12 text-center">
|
||||
<RouterLink
|
||||
to="/symposium"
|
||||
class="inline-block rounded-md border border-border px-5 py-3 text-sm font-medium hover:bg-muted"
|
||||
>
|
||||
{{ t('nav.symposium') }} →
|
||||
</RouterLink>
|
||||
<Button as-child variant="outline">
|
||||
<RouterLink to="/symposium">{{ t('nav.symposium') }} →</RouterLink>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import cosmicStag from '@/assets/cosmic-stag.webp'
|
||||
import heroLandscape from '@/assets/hero-landscape.webp'
|
||||
|
||||
|
|
@ -73,12 +74,9 @@ const featuredEvents = [
|
|||
<p class="mt-6 text-base leading-relaxed text-foreground/90 md:text-lg">
|
||||
{{ t('home.welcome.body') }}
|
||||
</p>
|
||||
<RouterLink
|
||||
to="/concept"
|
||||
class="mt-8 inline-block rounded-full border border-accent/50 px-6 py-3 text-xs uppercase tracking-[0.2em] text-accent hover:bg-accent/10"
|
||||
>
|
||||
{{ t('home.welcome.cta') }}
|
||||
</RouterLink>
|
||||
<Button as-child variant="outline" class="mt-8">
|
||||
<RouterLink to="/concept">{{ t('home.welcome.cta') }}</RouterLink>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -103,14 +101,11 @@ const featuredEvents = [
|
|||
>
|
||||
{{ t('home.bouge.body') }}
|
||||
</h3>
|
||||
<a
|
||||
href="https://bouge.ariege.io"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="mt-10 inline-block rounded-full bg-primary px-6 py-3 text-xs uppercase tracking-[0.2em] text-primary-foreground hover:opacity-90"
|
||||
>
|
||||
<Button as-child class="mt-10">
|
||||
<a href="https://bouge.ariege.io" target="_blank" rel="noopener">
|
||||
{{ t('home.bouge.cta') }}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -188,12 +183,9 @@ const featuredEvents = [
|
|||
</RouterLink>
|
||||
</div>
|
||||
<div class="mt-10 text-center">
|
||||
<RouterLink
|
||||
to="/events"
|
||||
class="inline-block rounded-full bg-primary px-6 py-3 text-xs uppercase tracking-[0.2em] text-primary-foreground hover:opacity-90"
|
||||
>
|
||||
{{ t('home.events.seeAll') }}
|
||||
</RouterLink>
|
||||
<Button as-child>
|
||||
<RouterLink to="/events">{{ t('home.events.seeAll') }}</RouterLink>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
|
@ -62,18 +63,12 @@ const paths = ['exchange', 'rental', 'partial', 'funded'] as const
|
|||
{{ t('longStays.symposiumTeaser.body') }}
|
||||
</p>
|
||||
<div class="mt-8 flex flex-wrap items-center gap-3">
|
||||
<RouterLink
|
||||
to="/symposium"
|
||||
class="rounded-md bg-primary px-5 py-3 text-sm font-medium text-primary-foreground hover:opacity-90"
|
||||
>
|
||||
{{ t('longStays.ctaSymposium') }}
|
||||
</RouterLink>
|
||||
<a
|
||||
href="mailto:chateaudufaune@ariege.io"
|
||||
class="rounded-md border border-border px-5 py-3 text-sm font-medium hover:bg-muted"
|
||||
>
|
||||
{{ t('longStays.ctaContact') }}
|
||||
</a>
|
||||
<Button as-child>
|
||||
<RouterLink to="/symposium">{{ t('longStays.ctaSymposium') }}</RouterLink>
|
||||
</Button>
|
||||
<Button as-child variant="outline">
|
||||
<a href="mailto:chateaudufaune@ariege.io">{{ t('longStays.ctaContact') }}</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
|
@ -47,12 +48,11 @@ const categories = ['fresh', 'pantry', 'craft'] as const
|
|||
<p class="font-serif text-lg italic text-foreground/85">
|
||||
{{ t('marketplace.notice') }}
|
||||
</p>
|
||||
<a
|
||||
href="mailto:chateaudufaune@ariege.io?subject=Boutique%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune"
|
||||
class="mt-6 inline-block rounded-md border border-border bg-card px-5 py-3 text-sm font-medium hover:bg-muted"
|
||||
>
|
||||
<Button as-child variant="outline" class="mt-6">
|
||||
<a href="mailto:chateaudufaune@ariege.io?subject=Boutique%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune">
|
||||
{{ t('marketplace.ctaContact') }}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
|
@ -78,18 +79,14 @@ const applyKeys = ['model', 'window', 'open'] as const
|
|||
</article>
|
||||
</div>
|
||||
<div class="mt-10 flex flex-wrap items-center gap-3">
|
||||
<a
|
||||
href="mailto:chateaudufaune@ariege.io?subject=Application%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune"
|
||||
class="rounded-md bg-primary px-5 py-3 text-sm font-medium text-primary-foreground hover:opacity-90"
|
||||
>
|
||||
<Button as-child>
|
||||
<a href="mailto:chateaudufaune@ariege.io?subject=Application%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune">
|
||||
{{ t('opportunities.ctaApply') }}
|
||||
</a>
|
||||
<RouterLink
|
||||
to="/symposium"
|
||||
class="rounded-md border border-border px-5 py-3 text-sm font-medium hover:bg-muted"
|
||||
>
|
||||
{{ t('opportunities.ctaSymposium') }}
|
||||
</RouterLink>
|
||||
</Button>
|
||||
<Button as-child variant="outline">
|
||||
<RouterLink to="/symposium">{{ t('opportunities.ctaSymposium') }}</RouterLink>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
|
@ -73,18 +74,16 @@ const kinds = ['weekend', 'retreat', 'gathering', 'residency'] as const
|
|||
<p class="font-serif italic">{{ t('reservations.formPlaceholder') }}</p>
|
||||
</div>
|
||||
<div class="mt-6 flex flex-wrap items-center gap-3">
|
||||
<a
|
||||
href="mailto:chateaudufaune@ariege.io?subject=R%C3%A9servation%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune"
|
||||
class="rounded-md bg-primary px-5 py-3 text-sm font-medium text-primary-foreground hover:opacity-90"
|
||||
>
|
||||
<Button as-child>
|
||||
<a href="mailto:chateaudufaune@ariege.io?subject=R%C3%A9servation%20%E2%80%94%20Ch%C3%A2teau%20du%20Faune">
|
||||
{{ t('reservations.ctaEmail') }}
|
||||
</a>
|
||||
<RouterLink
|
||||
to="/accommodation"
|
||||
class="rounded-md border border-border px-5 py-3 text-sm font-medium hover:bg-muted"
|
||||
>
|
||||
</Button>
|
||||
<Button as-child variant="outline">
|
||||
<RouterLink to="/accommodation">
|
||||
{{ t('reservations.ctaAccommodation') }}
|
||||
</RouterLink>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
|
@ -102,18 +103,12 @@ const applySteps = ['stepOne', 'stepTwo', 'stepThree'] as const
|
|||
</li>
|
||||
</ol>
|
||||
<div class="mt-10 flex flex-wrap items-center gap-3">
|
||||
<RouterLink
|
||||
to="/opportunities"
|
||||
class="rounded-md bg-primary px-5 py-3 text-sm font-medium text-primary-foreground hover:opacity-90"
|
||||
>
|
||||
{{ t('symposium.ctaApply') }}
|
||||
</RouterLink>
|
||||
<a
|
||||
href="mailto:chateaudufaune@ariege.io"
|
||||
class="rounded-md border border-border px-5 py-3 text-sm font-medium hover:bg-muted"
|
||||
>
|
||||
{{ t('symposium.ctaContact') }}
|
||||
</a>
|
||||
<Button as-child>
|
||||
<RouterLink to="/opportunities">{{ t('symposium.ctaApply') }}</RouterLink>
|
||||
</Button>
|
||||
<Button as-child variant="outline">
|
||||
<a href="mailto:chateaudufaune@ariege.io">{{ t('symposium.ctaContact') }}</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue