funkwhale/front/src/components/ui/Link.vue

172 lines
3.8 KiB
Vue

<script setup lang="ts">
import { computed } from 'vue'
import { type RouterLinkProps, RouterLink } from 'vue-router'
import { type ColorProps, type DefaultProps, type VariantProps, propsToColor } from '~/composables/colors';
const { to, icon, thickWhenActive, round, ...colorProps } = defineProps<{
width?: 'standard' | 'auto' | 'full'
alignText?: 'left' | 'center' | 'right' | 'stretch'
alignSelf?: 'start' | 'center' | 'end'
thickWhenActive?: true
icon?: string;
round?: true;
} & RouterLinkProps & (ColorProps | DefaultProps) & VariantProps>()
const isExternalLink = computed(() => {
return typeof to === 'string' && to.startsWith('http')
})
const [fontWeight, activeFontWeight] = thickWhenActive ? [600, 900] : [400, 400]
const isIconOnly = computed(() => !!icon)
const isSimple = propsToColor(colorProps).class === ''
</script>
<template>
<a v-if="isExternalLink"
v-bind="propsToColor({ ...colorProps, interactive: true })"
:class="[
$style.link,
$style['is-' + width],
$style['is-text-aligned-' + (alignText ?? 'left')],
$style['is-self-aligned-' + (alignSelf ?? 'auto')],
round && $style['is-round'],
isIconOnly && $style['is-icon-only'],
isSimple && $style['force-underline'],
isSimple && $style['no-spacing'],
]"
:href="to.toString()"
target="_blank"
>
<i v-if="icon" :class="['bi', icon]" />
<span>
<slot />
</span>
</a>
<RouterLink v-else
:to="to"
v-bind="propsToColor({ ...colorProps, interactive: true })"
:class="[
$style.link,
$style['is-' + width],
$style['is-text-aligned-' + (alignText ?? 'left')],
$style['is-self-aligned-' + (alignSelf ?? 'auto')],
round && $style['is-round'],
isIconOnly && $style['is-icon-only'],
isSimple && $style['no-spacing'],
isSimple && $style['force-underline']
]"
>
<i v-if="icon" :class="['bi', icon]" />
<span>
<slot />
</span>
</RouterLink>
</template>
<style module lang="scss">
.link {
white-space: nowrap;
justify-content: space-between;
font-family: $font-main;
font-weight: v-bind(fontWeight);
&:global(.router-link-exact-active) {
font-weight: v-bind(activeFontWeight);
}
font-size: 0.875em;
line-height: 1em;
transform: translateX(var(--fw-translate-x)) translateY(var(--fw-translate-y)) scale(var(--fw-scale));
transition:background-color .2s, border-color .3s;
// Icon
i:global(.bi) {
font-size: 1.2rem;
&.large {
font-size:2rem;
}
}
i:global(.bi) + span:not(:empty) {
margin-left: 1ch;
}
&:not(.force-underline) {
text-decoration: none;
background-color: transparent;
border-color: transparent;
}
/* Shape */
border-radius: var(--fw-border-radius);
margin: 0 0.5ch;
padding: 9px 10px 11px 10px;
&.is-icon-only {
padding: 10px;
}
&.no-spacing{
padding: 0;
margin: 0;
font-size: 1em;
}
&.is-round {
border-radius: 100vh;
}
/* Alignment */
display: inline-flex;
align-items: center;
justify-content: flex-start;
&.is-text-aligned-center {
justify-content: center
}
&.is-text-aligned-left {
justify-content: flex-start
}
&.is-text-aligned-right {
justify-content: flex-end;
}
&.is-text-aligned-stretch {
justify-content: stretch;
align-content: stretch;
> span { width:100%; }
}
&.is-self-aligned-start {
align-self: flex-start;
}
&.is-self-aligned-center {
align-self: center;
}
&.is-self-aligned-auto {
align-self: auto;
}
&.is-self-aligned-end {
align-self: flex-end;
}
/* Width */
&.is-full {
align-self: stretch;
}
}
</style>