generated from gitea_admin/default
226 lines
7.2 KiB
Vue
226 lines
7.2 KiB
Vue
<template>
|
|
<component
|
|
:is="componentTag"
|
|
v-bind="$attrs"
|
|
:to="isNuxtLink ? to : undefined"
|
|
:href="isAnchor ? href : undefined"
|
|
:type="isButton ? type : undefined"
|
|
:disabled="isButton ? disabled || loading : undefined"
|
|
:aria-disabled="(!isButton && (disabled || loading)) ? 'true' : undefined"
|
|
:class="[
|
|
'ds-button',
|
|
`ds-button--${variant}`,
|
|
`ds-button--${size}`,
|
|
props.textColor ? `ds-button--text-${props.textColor}` : '',
|
|
props.borderColor ? `ds-button--border-${props.borderColor}` : '',
|
|
{ 'ds-button--disabled': disabled, 'ds-button--loading': loading }
|
|
]"
|
|
@click="onClick"
|
|
>
|
|
<span class="ds-button__content">
|
|
<span v-if="$slots.iconLeft" class="ds-button__icon ds-button__icon--left">
|
|
<slot name="iconLeft" />
|
|
</span>
|
|
|
|
<span class="ds-button__label">
|
|
<slot />
|
|
</span>
|
|
|
|
<span v-if="$slots.iconRight" class="ds-button__icon ds-button__icon--right">
|
|
<slot name="iconRight" />
|
|
</span>
|
|
</span>
|
|
|
|
<span v-if="loading" class="ds-button__spinner" aria-hidden="true" />
|
|
</component>
|
|
</template>
|
|
|
|
<script setup>
|
|
defineOptions({ inheritAttrs: false }) // désactive la transmission automatique des attributs HTML, utile pour v-bind="$attrs"
|
|
import { computed, resolveComponent } from 'vue'
|
|
|
|
const props = defineProps({
|
|
variant: { type: String, default: 'primary' }, // primary/secondary/ghost/link
|
|
size: { type: String, default: 'md' }, // sm/md/lg
|
|
|
|
to: { type: [String, Object], default: '' }, // NuxtLink
|
|
href: { type: String, default: '' }, // <a>
|
|
type: { type: String, default: 'button' }, // button/submit/reset (pour éviter des soumissions involontaires on met button pas défaut, si pas submit non désiré)
|
|
|
|
disabled: { type: Boolean, default: false },
|
|
loading: { type: Boolean, default: false },
|
|
textColor: { type: String, default: '' }, // accepts CSS color or tone name (ex: invert)
|
|
borderColor: { type: String, default: '' }, // accepts CSS color or tone name (ex: invert)
|
|
})
|
|
|
|
// TYPE D'ÉLÉMENT : NUXTLINK, A ou BUTTON ?
|
|
// quelle est la prop fournie ?
|
|
const isNuxtLink = computed(() => !!props.to) // NuxtLink si prop "to" présente
|
|
const isAnchor = computed(() => !props.to && !!props.href) // <a> si prop "href" présente et pas "to"
|
|
const isButton = computed(() => !props.to && !props.href) // <button> si pas "href" et pas "to"
|
|
// décision de l'élément en fonction de la prop fournie
|
|
const componentTag = computed(() => {
|
|
if (isNuxtLink.value) resolveComponent('NuxtLink')
|
|
if (isAnchor.value) return 'a'
|
|
return 'button'
|
|
})
|
|
|
|
const emit = defineEmits(['click']) // déclare que ce composant peut émettre un événement click.
|
|
// onClick(e) est appelé quand on clique sur le bouton, sauf en cas de disabled ou loading
|
|
function onClick(e) {
|
|
if (props.disabled || props.loading) {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
return
|
|
}
|
|
emit('click', e)
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.ds-button {
|
|
position: relative;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--sp-2);
|
|
border-radius: var(--r-md);
|
|
border: 2px solid transparent;
|
|
font-family: var(--font-roboto);
|
|
font-weight: var(--fw-medium);
|
|
line-height: 1;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
white-space: nowrap;
|
|
|
|
transition:
|
|
transform 120ms ease,
|
|
background-color 120ms ease,
|
|
border-color 120ms ease,
|
|
box-shadow 120ms ease,
|
|
color 120ms ease;
|
|
|
|
&:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
&:focus-visible {
|
|
outline: none;
|
|
box-shadow: 0 0 0 3px var(--c-focus);
|
|
}
|
|
|
|
&--disabled,
|
|
&--loading {
|
|
opacity: 0.55;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* Sizes */
|
|
&--sm {
|
|
font-size: var(--text-sm);
|
|
padding: var(--sp-4) var(--sp-8);
|
|
min-height: 2.25rem;
|
|
}
|
|
|
|
&--md {
|
|
font-size: var(--text-md);
|
|
padding: var(--sp-6) var(--sp-12);
|
|
min-height: 2.75rem;
|
|
}
|
|
|
|
&--lg {
|
|
font-size: var(--text-lg);
|
|
padding: var(--sp-8) var(--sp-16);
|
|
min-height: 3.25rem;
|
|
}
|
|
|
|
/* Variants */
|
|
&--primary {
|
|
background: var(--c-brand);
|
|
color: var(--ds-button-text, var(--c-brand-contrast));
|
|
border-color: var(--ds-button-border, transparent);
|
|
}
|
|
|
|
&--secondary {
|
|
background: var(--c-surface);
|
|
color: var(--ds-button-text, var(--c-text));
|
|
border-color: var(--ds-button-border, var(--c-border-strong));
|
|
box-shadow: var(--sh-soft);
|
|
}
|
|
&--secondary:hover {
|
|
background: var(--c-hover);
|
|
}
|
|
|
|
&--ghost {
|
|
background: transparent;
|
|
color: var(--ds-button-text, var(--c-text));
|
|
border-color: var(--ds-button-border, transparent);
|
|
}
|
|
&--ghost:hover {
|
|
background: var(--c-hover);
|
|
}
|
|
|
|
&--link {
|
|
background: transparent;
|
|
color: var(--ds-button-text, var(--c-brand));
|
|
padding: 0;
|
|
min-height: auto;
|
|
border-color: var(--ds-button-border, transparent);
|
|
}
|
|
&--link:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
&--text-default { --ds-button-text: var(--c-text); }
|
|
&--text-muted { --ds-button-text: var(--c-text-muted); }
|
|
&--text-invert { --ds-button-text: var(--c-text-invert); }
|
|
&--text-brand_rouge { --ds-button-text: var(--c-brand_rouge); }
|
|
&--text-brand_rouge-weak { --ds-button-text: var(--c-brand_rouge-weak); }
|
|
&--text-success { --ds-button-text: var(--c-success); }
|
|
&--text-warning { --ds-button-text: var(--c-warning); }
|
|
&--text-danger { --ds-button-text: var(--c-danger); }
|
|
&--text-bleu_fonce { --ds-button-text: var(--c-bleu_fonce); }
|
|
&--text-bleu_clair { --ds-button-text: var(--c-bleu_clair); }
|
|
&--text-info { --ds-button-text: var(--c-info); }
|
|
|
|
&--border-default { --ds-button-border: var(--c-text); }
|
|
&--border-muted { --ds-button-border: var(--c-text-muted); }
|
|
&--border-invert { --ds-button-border: var(--c-text-invert); }
|
|
&--border-brand_rouge { --ds-button-border: var(--c-brand_rouge); }
|
|
&--border-brand_rouge-weak { --ds-button-border: var(--c-brand_rouge-weak); }
|
|
&--border-success { --ds-button-border: var(--c-success); }
|
|
&--border-warning { --ds-button-border: var(--c-warning); }
|
|
&--border-danger { --ds-button-border: var(--c-danger); }
|
|
&--border-bleu_fonce { --ds-button-border: var(--c-bleu_fonce); }
|
|
&--border-bleu_clair { --ds-button-border: var(--c-bleu_clair); }
|
|
&--border-info { --ds-button-border: var(--c-info); }
|
|
&--border { --ds-button-border: var(--c-border); }
|
|
&--border-strong { --ds-button-border: var(--c-border-strong); }
|
|
}
|
|
.ds-button__content {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--sp-2);
|
|
}
|
|
|
|
.ds-button__icon {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.ds-button__spinner {
|
|
position: absolute;
|
|
width: 1rem;
|
|
height: 1rem;
|
|
border-radius: 999px;
|
|
border: 2px solid rgba(255,255,255,0.45);
|
|
border-top-color: rgba(255,255,255,0.95);
|
|
animation: ds-spin 700ms linear infinite;
|
|
}
|
|
|
|
@keyframes ds-spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
</style>
|