feat(ui): color (see "using color" in dev:ui-docs)
This commit is contained in:
		
							parent
							
								
									925d2db0a6
								
							
						
					
					
						commit
						e583c51a54
					
				| 
						 | 
				
			
			@ -52,6 +52,7 @@
 | 
			
		|||
    "standardized-audio-context": "25.3.60",
 | 
			
		||||
    "text-clipper": "2.2.0",
 | 
			
		||||
    "transliteration": "2.3.5",
 | 
			
		||||
    "type-fest": "4.30.1",
 | 
			
		||||
    "universal-cookie": "4.0.4",
 | 
			
		||||
    "vite-plugin-pwa": "0.14.4",
 | 
			
		||||
    "vue": "3.5.13",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,14 +1,15 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import { useColorOrPastel, type PastelProps } from '~/composables/colors'
 | 
			
		||||
import { type PastelProps, propsToColor } from '~/composables/colors';
 | 
			
		||||
 | 
			
		||||
const props = defineProps<PastelProps>()
 | 
			
		||||
const color = useColorOrPastel(() => props.color, 'blue')
 | 
			
		||||
type Props = PastelProps
 | 
			
		||||
 | 
			
		||||
const props = defineProps<Props>()
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    class="funkwhale is-colored alert"
 | 
			
		||||
    :class="[color]"
 | 
			
		||||
    class="funkwhale is-colored solid alert"
 | 
			
		||||
    v-bind="propsToColor(props)"
 | 
			
		||||
  >
 | 
			
		||||
    <slot />
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,10 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import { ref, computed, useSlots, onMounted } from 'vue'
 | 
			
		||||
import { type ColorProps, useColor } from '~/composables/colors'
 | 
			
		||||
import { type ColorProps, type VariantProps, propsToColor } from '~/composables/colors';
 | 
			
		||||
 | 
			
		||||
import Loader from '~/components/ui/Loader.vue'
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  variant?: 'solid' | 'outline' | 'ghost'
 | 
			
		||||
type Props = {
 | 
			
		||||
  width?: 'standard' | 'auto' | 'full'
 | 
			
		||||
  alignText?: 'left' | 'center' | 'right'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -19,14 +18,12 @@ interface Props {
 | 
			
		|||
  onClick?: (...args: any[]) => void | Promise<void>
 | 
			
		||||
 | 
			
		||||
  autofocus? : boolean
 | 
			
		||||
}
 | 
			
		||||
} & ColorProps & VariantProps
 | 
			
		||||
 | 
			
		||||
const props = defineProps<Props & ColorProps>()
 | 
			
		||||
 | 
			
		||||
const color = useColor(() => props.color)
 | 
			
		||||
const props = defineProps<Props>()
 | 
			
		||||
 | 
			
		||||
const slots = useSlots()
 | 
			
		||||
const iconOnly = computed(() => !!props.icon && !slots.default)
 | 
			
		||||
const isIconOnly = computed(() => !!props.icon && !slots.default)
 | 
			
		||||
 | 
			
		||||
const internalLoader = ref(false)
 | 
			
		||||
const isLoading = computed(() => props.isLoading || internalLoader.value)
 | 
			
		||||
| 
						 | 
				
			
			@ -48,20 +45,22 @@ onMounted(() => {
 | 
			
		|||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <button ref="button" class="funkwhale is-colored button" :class="[
 | 
			
		||||
    color,
 | 
			
		||||
    'is-' + (variant ?? 'solid'),
 | 
			
		||||
  <button ref="button"
 | 
			
		||||
    v-bind="propsToColor({...props, interactive:true})"
 | 
			
		||||
    class="funkwhale is-colored button"
 | 
			
		||||
    :class="[
 | 
			
		||||
      'is-' + (width ?? 'standard'),
 | 
			
		||||
      'is-aligned-' + (alignText ?? 'center'),
 | 
			
		||||
      {
 | 
			
		||||
        'is-active': isActive,
 | 
			
		||||
        'is-loading': isLoading,
 | 
			
		||||
      'icon-only': iconOnly,
 | 
			
		||||
        'is-icon-only': isIconOnly,
 | 
			
		||||
        'has-icon': !!icon,
 | 
			
		||||
        'is-round': round,
 | 
			
		||||
        'is-shadow': shadow
 | 
			
		||||
      }
 | 
			
		||||
  ]" @click="click">
 | 
			
		||||
    ]" @click="click"
 | 
			
		||||
  >
 | 
			
		||||
    <i v-if="icon" :class="['bi', icon]" />
 | 
			
		||||
 | 
			
		||||
    <span>
 | 
			
		||||
| 
						 | 
				
			
			@ -73,5 +72,81 @@ onMounted(() => {
 | 
			
		|||
</template>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import './button.scss'
 | 
			
		||||
.funkwhale {
 | 
			
		||||
  &.button {
 | 
			
		||||
 | 
			
		||||
    position: relative;
 | 
			
		||||
    display: inline-flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
 | 
			
		||||
    font-family: $font-main;
 | 
			
		||||
    font-weight: 900;
 | 
			
		||||
    font-size: 0.875em;
 | 
			
		||||
 | 
			
		||||
    line-height: 1em;
 | 
			
		||||
 | 
			
		||||
    padding: 0.642857142857em 0.714em 0.714em 0.714em;
 | 
			
		||||
    &.is-icon-only {
 | 
			
		||||
      padding: 0.675em 0.714em 0.678em 0.714em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    border-radius: var(--fw-border-radius);
 | 
			
		||||
    margin: 0 0.5ch;
 | 
			
		||||
 | 
			
		||||
    transform: translateX(var(--fw-translate-x)) translateY(var(--fw-translate-y)) scale(var(--fw-scale));
 | 
			
		||||
    transition: all .2s ease;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    &.is-aligned-center {
 | 
			
		||||
      justify-content: center;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.is-aligned-left {
 | 
			
		||||
      justify-content: flex-start;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.is-aligned-right {
 | 
			
		||||
      justify-content: flex-end;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.is-shadow {
 | 
			
		||||
      box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &:not(.is-icon-only):not(.is-auto) {
 | 
			
		||||
      min-width: 8.5rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.is-full {
 | 
			
		||||
      display: block;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.is-round {
 | 
			
		||||
      border-radius: 100vh;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &[disabled] {
 | 
			
		||||
      font-weight: normal;
 | 
			
		||||
      pointer-events: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.is-loading {
 | 
			
		||||
      @extend .is-active;
 | 
			
		||||
 | 
			
		||||
      > span {
 | 
			
		||||
        opacity: 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    i.bi {
 | 
			
		||||
      font-size: 1.2rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    i.bi + span:not(:empty) {
 | 
			
		||||
      margin-left: 1ch;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,8 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import { useCssModule } from 'vue'
 | 
			
		||||
import { computed } from 'vue'
 | 
			
		||||
 | 
			
		||||
import { type RouterLinkProps, RouterLink } from 'vue-router';
 | 
			
		||||
import { type Pastel } from '~/composables/colors';
 | 
			
		||||
import { type ColorProps, type PastelProps, type RaisedProps, type VariantProps, propsToColor } from '~/composables/colors';
 | 
			
		||||
 | 
			
		||||
import Pill from './Pill.vue'
 | 
			
		||||
import Alert from './Alert.vue'
 | 
			
		||||
| 
						 | 
				
			
			@ -13,12 +12,11 @@ import Spacer from './layout/Spacer.vue';
 | 
			
		|||
interface Props extends Partial<RouterLinkProps> {
 | 
			
		||||
  title: string
 | 
			
		||||
  category?: true | "h1" | "h2" | "h3" | "h4" | "h5"
 | 
			
		||||
  color?: Pastel
 | 
			
		||||
  image?: string | { src: string, style?: "withPadding" }
 | 
			
		||||
  tags?: string[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<Props>()
 | 
			
		||||
const props = defineProps<Props & (PastelProps | ColorProps) & RaisedProps & VariantProps>()
 | 
			
		||||
 | 
			
		||||
const image = typeof props.image === 'string' ? { src: props.image } : props.image
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -146,7 +144,11 @@ const isExternalLink = computed(() => {
 | 
			
		|||
</style>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <Layout stack :class="{ [$style.card]: true, [$style['is-category']]: category }" style="--gap:16px">
 | 
			
		||||
  <Layout stack
 | 
			
		||||
    :class="{ [$style.card]: true, [$style['is-category']]: category }"
 | 
			
		||||
    style="--gap:16px"
 | 
			
		||||
    v-bind="propsToColor({...(props.to? { interactive: true, solid: true, default: true } : {}), ...props})"
 | 
			
		||||
  >
 | 
			
		||||
 | 
			
		||||
    <!-- Link -->
 | 
			
		||||
    <a v-if="props.to && isExternalLink" :class="$style.covering" :href="to?.toString()" target="_blank" />
 | 
			
		||||
| 
						 | 
				
			
			@ -164,7 +166,7 @@ const isExternalLink = computed(() => {
 | 
			
		|||
    <!-- Content -->
 | 
			
		||||
    <component :class="$style.title" :is="typeof category === 'string' ? category : 'h6'">{{ title }}</component>
 | 
			
		||||
 | 
			
		||||
    <Alert v-if="$slots.alert" :class="$style.alert">
 | 
			
		||||
    <Alert blue v-if="$slots.alert" :class="$style.alert">
 | 
			
		||||
      <slot name="alert" />
 | 
			
		||||
    </Alert>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,24 +1,34 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import { type RouterLinkProps, RouterLink } from 'vue-router';
 | 
			
		||||
import { type ColorProps, useColor } from '~/composables/colors';
 | 
			
		||||
const { to, icon, color, inline } = defineProps<RouterLinkProps & ColorProps & {
 | 
			
		||||
import { type ColorProps, propsToColor } from '~/composables/colors';
 | 
			
		||||
const { to, icon, inline, ...otherProps } = defineProps<RouterLinkProps
 | 
			
		||||
  & ColorProps
 | 
			
		||||
  & {
 | 
			
		||||
    icon?: string;
 | 
			
		||||
    inline?: true
 | 
			
		||||
  }>()
 | 
			
		||||
 | 
			
		||||
const colorClass = useColor(() => color)
 | 
			
		||||
 | 
			
		||||
const isExternalLink = computed(() => {
 | 
			
		||||
  return typeof to === 'string' && to.startsWith('http')
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <a v-if="isExternalLink" :class="[$style.external, colorClass, color && 'is-colored', inline && $style.inline]" :href="to?.toString()" target="_blank">
 | 
			
		||||
  <a v-if="isExternalLink"
 | 
			
		||||
    :v-bind="propsToColor(otherProps)"
 | 
			
		||||
    :class="[$style.link, $style.external, inline && $style.inline]"
 | 
			
		||||
    :href="to?.toString()"
 | 
			
		||||
    target="_blank"
 | 
			
		||||
  >
 | 
			
		||||
    <i v-if="icon" :class="['bi', icon]" />
 | 
			
		||||
    <slot />
 | 
			
		||||
  </a>
 | 
			
		||||
  <RouterLink v-if="to && !isExternalLink" :to="to" :class="[colorClass, color && 'is-colored', inline && $style.inline]">
 | 
			
		||||
  <RouterLink v-else
 | 
			
		||||
    :to="to"
 | 
			
		||||
    :v-bind="propsToColor(otherProps)"
 | 
			
		||||
    :class="[$style.link, inline && $style.inline]"
 | 
			
		||||
  >
 | 
			
		||||
    <i v-if="icon" :class="['bi', icon]" />
 | 
			
		||||
    <slot />
 | 
			
		||||
  </RouterLink>
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +38,7 @@ const isExternalLink = computed(() => {
 | 
			
		|||
  .active { outline: 3px solid red; }
 | 
			
		||||
  .external { outline: 3px dotted blue; }
 | 
			
		||||
  .inline { display:inline-flex; }
 | 
			
		||||
  a {
 | 
			
		||||
  .link {
 | 
			
		||||
    background-color: var(--fw-bg-color);
 | 
			
		||||
    color: var(--fw-text-color);
 | 
			
		||||
    border: 1px solid var(--fw-bg-color);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ const isOpen = defineModel<boolean>({ default:false })
 | 
			
		|||
        <div @click.stop class="funkwhale modal" :class="$slots.alert && 'has-alert'" >
 | 
			
		||||
          <h2>
 | 
			
		||||
            {{ title }}
 | 
			
		||||
            <Button icon="bi-x-lg" color="secondary" variant="ghost" @click="isOpen = false" />
 | 
			
		||||
            <Button icon="bi-x-lg" ghost @click="isOpen = false" />
 | 
			
		||||
          </h2>
 | 
			
		||||
 | 
			
		||||
          <div class="modal-content">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,21 +1,20 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import { useColorOrPastel, type ColorProps, type PastelProps } from '~/composables/colors'
 | 
			
		||||
import { type ColorProps, type PastelProps, propsToColor } from '~/composables/colors';
 | 
			
		||||
 | 
			
		||||
const props = defineProps<ColorProps | PastelProps>()
 | 
			
		||||
const color = useColorOrPastel(() => props.color, 'secondary')
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
  click: [event: MouseEvent]
 | 
			
		||||
}>()
 | 
			
		||||
const handleClick = (event: MouseEvent) => {
 | 
			
		||||
  emit('click', event)
 | 
			
		||||
}
 | 
			
		||||
const props = defineProps<PastelProps | ColorProps>()
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <button
 | 
			
		||||
    type="button"
 | 
			
		||||
    class="funkwhale is-colored pill"
 | 
			
		||||
    :class="[color]"
 | 
			
		||||
    v-bind="propsToColor({...props, interactive:true})"
 | 
			
		||||
    @click.stop="handleClick"
 | 
			
		||||
  >
 | 
			
		||||
    <div v-if="!!$slots.image" class="pill-image">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -186,17 +186,17 @@ const focus = () => textarea.value.focus()
 | 
			
		|||
      @keydown.ctrl.shift.x.exact.prevent="strikethrough" @keydown.ctrl.k.exact.prevent="link" :maxlength="max"
 | 
			
		||||
      :placeholder="placeholder" v-model="model" id="textarea_id" />
 | 
			
		||||
    <div class="textarea-buttons">
 | 
			
		||||
      <Button @click="preview = !preview" icon="bi-eye" color="secondary" :is-active="preview" />
 | 
			
		||||
      <Button @click="preview = !preview" icon="bi-eye" color="secondary" :aria-pressed="preview" />
 | 
			
		||||
 | 
			
		||||
      <div class="separator" />
 | 
			
		||||
 | 
			
		||||
      <Button @click="heading1" icon="bi-type-h1" color="secondary" :is-active="isHeading1" :disabled="preview" />
 | 
			
		||||
      <Button @click="heading2" icon="bi-type-h2" color="secondary" :is-active="isHeading2" :disabled="preview" />
 | 
			
		||||
      <Button @click="paragraph" icon="bi-paragraph" color="secondary" :is-active="isParagraph" :disabled="preview" />
 | 
			
		||||
      <Button @click="quote" icon="bi-quote" color="secondary" :is-active="isQuote" :disabled="preview" />
 | 
			
		||||
      <Button @click="orderedList" icon="bi-list-ol" color="secondary" :is-active="isOrderedList"
 | 
			
		||||
      <Button @click="heading1" icon="bi-type-h1" color="secondary" :aria-pressed="isHeading1" :disabled="preview" />
 | 
			
		||||
      <Button @click="heading2" icon="bi-type-h2" color="secondary" :aria-pressed="isHeading2" :disabled="preview" />
 | 
			
		||||
      <Button @click="paragraph" icon="bi-paragraph" color="secondary" :aria-pressed="isParagraph" :disabled="preview" />
 | 
			
		||||
      <Button @click="quote" icon="bi-quote" color="secondary" :aria-pressed="isQuote" :disabled="preview" />
 | 
			
		||||
      <Button @click="orderedList" icon="bi-list-ol" color="secondary" :aria-pressed="isOrderedList"
 | 
			
		||||
        :disabled="preview" />
 | 
			
		||||
      <Button @click="unorderedList" icon="bi-list-ul" color="secondary" :is-active="isUnorderedList"
 | 
			
		||||
      <Button @click="unorderedList" icon="bi-list-ul" color="secondary" :aria-pressed="isUnorderedList"
 | 
			
		||||
        :disabled="preview" />
 | 
			
		||||
 | 
			
		||||
      <div class="separator" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,37 +1,37 @@
 | 
			
		|||
.funkwhale.alert {
 | 
			
		||||
  color: var(--fw-gray-900);
 | 
			
		||||
  // color: var(--fw-gray-900);
 | 
			
		||||
 | 
			
		||||
  @include light-theme {
 | 
			
		||||
    background-color: var(--fw-pastel-1, var(--fw-bg-color));
 | 
			
		||||
  // @include light-theme {
 | 
			
		||||
  //   background-color: var(--fw-pastel-1, var(--fw-bg-color));
 | 
			
		||||
 | 
			
		||||
    > .actions .funkwhale.button {
 | 
			
		||||
      --fw-bg-color: var(--fw-pastel-2);
 | 
			
		||||
  //   > .actions .funkwhale.button {
 | 
			
		||||
  //     --fw-bg-color: var(--fw-pastel-2);
 | 
			
		||||
 | 
			
		||||
      &:hover, &.is-hovered {
 | 
			
		||||
        --fw-bg-color: var(--fw-pastel-3);
 | 
			
		||||
      }
 | 
			
		||||
  //     &:hover, &.is-hovered {
 | 
			
		||||
  //       --fw-bg-color: var(--fw-pastel-3);
 | 
			
		||||
  //     }
 | 
			
		||||
 | 
			
		||||
      &:active, &.is-active {
 | 
			
		||||
        --fw-bg-color: var(--fw-pastel-4);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  //     &:active, &.is-active {
 | 
			
		||||
  //       --fw-bg-color: var(--fw-pastel-4);
 | 
			
		||||
  //     }
 | 
			
		||||
  //   }
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
  @include dark-theme {
 | 
			
		||||
    background-color: var(--fw-pastel-3, var(--fw-bg-color));
 | 
			
		||||
  // @include dark-theme {
 | 
			
		||||
  //   background-color: var(--fw-pastel-3, var(--fw-bg-color));
 | 
			
		||||
 | 
			
		||||
    > .actions .funkwhale.button {
 | 
			
		||||
      --fw-bg-color: var(--fw-pastel-4);
 | 
			
		||||
  //   > .actions .funkwhale.button {
 | 
			
		||||
  //     --fw-bg-color: var(--fw-pastel-4);
 | 
			
		||||
 | 
			
		||||
      &:hover, &.is-hovered {
 | 
			
		||||
        --fw-bg-color: var(--fw-pastel-2);
 | 
			
		||||
      }
 | 
			
		||||
  //     &:hover, &.is-hovered {
 | 
			
		||||
  //       --fw-bg-color: var(--fw-pastel-2);
 | 
			
		||||
  //     }
 | 
			
		||||
 | 
			
		||||
      &:active, &.is-active {
 | 
			
		||||
        --fw-bg-color: var(--fw-pastel-1);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  //     &:active, &.is-active {
 | 
			
		||||
  //       --fw-bg-color: var(--fw-pastel-1);
 | 
			
		||||
  //     }
 | 
			
		||||
  //   }
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
  padding: 0.625rem 2rem;
 | 
			
		||||
  line-height: 1.2;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,14 +2,10 @@
 | 
			
		|||
  &.pill {
 | 
			
		||||
    color: var(--fw-text-color);
 | 
			
		||||
 | 
			
		||||
    @include light-theme {
 | 
			
		||||
      background-color: var(--fw-pastel-2, var(--fw-bg-color));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @include dark-theme {
 | 
			
		||||
      --fw-darken-pastel: color-mix(in srgb, var(--fw-pastel-4) 90%, black);
 | 
			
		||||
      background-color: var(--fw-darken-pastel, var(--fw-bg-color));
 | 
			
		||||
    }
 | 
			
		||||
    // @include dark-theme {
 | 
			
		||||
    //   --fw-darken-pastel: color-mix(in srgb, var(--fw-pastel-4) 90%, black);
 | 
			
		||||
    //   background-color: var(--fw-darken-pastel, var(--fw-bg-color));
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    position: relative;
 | 
			
		||||
    display: inline-flex;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,24 +1,61 @@
 | 
			
		|||
import { toValue, type MaybeRefOrGetter } from "@vueuse/core"
 | 
			
		||||
import type { Entry, Join, KeysOfUnion, RequireExactlyOne, RequireOneOrNone, Simplify, SingleKeyObject, UnionToIntersection } from "type-fest"
 | 
			
		||||
import { computed } from 'vue'
 | 
			
		||||
 | 
			
		||||
export function useColor(color: MaybeRefOrGetter<Color | undefined>, defaultColor: Color = 'primary') {
 | 
			
		||||
  return computed(() => `is-${toValue(color) ?? defaultColor}`)
 | 
			
		||||
}
 | 
			
		||||
export type DefaultProps =
 | 
			
		||||
  | { default?: true }
 | 
			
		||||
export type Default = KeysOfUnion<DefaultProps>
 | 
			
		||||
 | 
			
		||||
export function usePastel(color: MaybeRefOrGetter<Pastel | undefined>, defaultColor: Pastel = 'blue') {
 | 
			
		||||
  return computed(() => `is-${toValue(color) ?? defaultColor}`)
 | 
			
		||||
}
 | 
			
		||||
export type ColorProps =
 | 
			
		||||
  | { primary?: true}
 | 
			
		||||
  | { secondary?: true }
 | 
			
		||||
  | { destructive?: true }
 | 
			
		||||
export type Color = KeysOfUnion<ColorProps>
 | 
			
		||||
 | 
			
		||||
export function useColorOrPastel<T extends Color | Pastel>(color: MaybeRefOrGetter<T | undefined>, defaultColor: T) {
 | 
			
		||||
  return computed(() => `is-${toValue(color) ?? defaultColor}`)
 | 
			
		||||
}
 | 
			
		||||
export type PastelProps =
 | 
			
		||||
  | { red?:true }
 | 
			
		||||
  | { blue?:true }
 | 
			
		||||
  | { purple?:true }
 | 
			
		||||
  | { green?: true }
 | 
			
		||||
  | { yellow?:true }
 | 
			
		||||
export type Pastel = KeysOfUnion<PastelProps>
 | 
			
		||||
 | 
			
		||||
export type Color = 'primary' | 'secondary' | 'destructive'
 | 
			
		||||
export interface ColorProps {
 | 
			
		||||
  color?: Color
 | 
			
		||||
}
 | 
			
		||||
export type VariantProps =
 | 
			
		||||
  | { solid?:true }
 | 
			
		||||
  | { outline?:true }
 | 
			
		||||
  | { ghost?:true }
 | 
			
		||||
export type Variant = KeysOfUnion<VariantProps>
 | 
			
		||||
 | 
			
		||||
export type Pastel = 'red' | 'blue' | 'purple' | 'green' | 'yellow'
 | 
			
		||||
export interface PastelProps {
 | 
			
		||||
  color?: Pastel
 | 
			
		||||
}
 | 
			
		||||
export type InteractiveProps =
 | 
			
		||||
| { interactive?: true }
 | 
			
		||||
export type Interactive = KeysOfUnion<DefaultProps>
 | 
			
		||||
 | 
			
		||||
export type RaisedProps =
 | 
			
		||||
| { raised?: true }
 | 
			
		||||
export type Raised = KeysOfUnion<RaisedProps>
 | 
			
		||||
 | 
			
		||||
export type Props =
 | 
			
		||||
  DefaultProps & ColorProps & PastelProps & VariantProps & InteractiveProps & RaisedProps
 | 
			
		||||
 | 
			
		||||
export type ColorSelector =
 | 
			
		||||
  `${Color | Pastel | Default}${'' | ` ${Variant}${'' | ' interactive'}${'' | ' raised'}`}`
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Apply a color class for setting `color`, `background-color` and `border` styles.
 | 
			
		||||
 * You can override it with `style="..."`.
 | 
			
		||||
 *
 | 
			
		||||
 * @param color Choose a semantic color or the default page background.
 | 
			
		||||
 * This will affect the text color.
 | 
			
		||||
 * To add an outline or filled background, add a `Variant`: `'solid' | 'outline'`.
 | 
			
		||||
 * If the surface should stand out, add `raised`.
 | 
			
		||||
 * If the surface reacts to mouse input, add `interactive`.
 | 
			
		||||
 * @returns the corresponding `class` object
 | 
			
		||||
 *
 | 
			
		||||
 * Note: Make sure to implement the necessary classes in `colors.scss`!
 | 
			
		||||
 */
 | 
			
		||||
export const color = (color: ColorSelector) =>
 | 
			
		||||
  ({ class: color })
 | 
			
		||||
 | 
			
		||||
// Color from Props
 | 
			
		||||
export const propsToColor = (props: Partial<Props>) =>
 | 
			
		||||
  ({ class: Object.entries(props).filter(([key, value])=>value && key).map(([key,_])=>key).join(" ") })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -87,7 +87,7 @@
 | 
			
		|||
  --fw-pastel-yellow-3: #fed100;
 | 
			
		||||
  --fw-pastel-yellow-4: #efa300;
 | 
			
		||||
 | 
			
		||||
  // Override Bulma
 | 
			
		||||
  // Same in light and dark theme
 | 
			
		||||
  --fw-primary: var(--fw-blue-500);
 | 
			
		||||
  --fw-secondary: #ff6600;
 | 
			
		||||
  --fw-destructive: var(--fw-red-500);
 | 
			
		||||
| 
						 | 
				
			
			@ -96,82 +96,214 @@
 | 
			
		|||
  --fw-page-bg-color: var(--fw-gray-960);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.funkwhale {
 | 
			
		||||
:is(.VPDoc .vp-doc, .funkwhale){
 | 
			
		||||
 | 
			
		||||
  :is(button, input):focus-visible {
 | 
			
		||||
    outline: 3px solid var(--fw-secondary);
 | 
			
		||||
    outline-offset: 2px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    // Variants
 | 
			
		||||
  .solid, .alert>.actions>button, button:not(:is(.ghost,.outline,.tabs-item)) {
 | 
			
		||||
    color: var(--color);
 | 
			
		||||
    background-color:var(--background-color);
 | 
			
		||||
    border: 1px solid var(--background-color);
 | 
			
		||||
 | 
			
		||||
    &.interactive {
 | 
			
		||||
      &[aria-pressed=true] {
 | 
			
		||||
        color: var(--pressed-color, var(--active-color));
 | 
			
		||||
        background-color: var(--pressed-background-color, var(--active-background-color));
 | 
			
		||||
        border-color: var(--pressed-background-color, var(--active-background-color));
 | 
			
		||||
      }
 | 
			
		||||
      &:hover{
 | 
			
		||||
        color:var(--hover-color);
 | 
			
		||||
        background-color:var(--hover-background-color);
 | 
			
		||||
        border-color: var(--hover-background-color);
 | 
			
		||||
      }
 | 
			
		||||
      &:active{
 | 
			
		||||
        color:var(--active-color);
 | 
			
		||||
        background-color:var(--active-background-color);
 | 
			
		||||
        border-color: var(--active-background-color);
 | 
			
		||||
      }
 | 
			
		||||
      &[disabled] {
 | 
			
		||||
        color: var(--disabled-color);
 | 
			
		||||
        border-color: var(--disabled-border-color);
 | 
			
		||||
        background-color:var(--disabled-background-color);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  .ghost {
 | 
			
		||||
    color: var(--color);
 | 
			
		||||
    border: 1px solid transparent;
 | 
			
		||||
 | 
			
		||||
    &.interactive{
 | 
			
		||||
      &:hover{
 | 
			
		||||
        border: 1px solid var(--hover-background-color);
 | 
			
		||||
      }
 | 
			
		||||
      &:active{
 | 
			
		||||
        border: 1px solid var(--active-background-color);
 | 
			
		||||
        &.router-link-exact-active {
 | 
			
		||||
          border: 1px solid var(--exact-active-background-color);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      &[disabled] {
 | 
			
		||||
        opacity:.5;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .outline {
 | 
			
		||||
    color: var(--color);
 | 
			
		||||
    border: 1px solid var(--border-color);
 | 
			
		||||
 | 
			
		||||
    &.interactive{
 | 
			
		||||
      &:hover{
 | 
			
		||||
        border: 1px solid var(--hover-background-color);
 | 
			
		||||
      }
 | 
			
		||||
      &:active{
 | 
			
		||||
        border: 1px solid var(--active-background-color);
 | 
			
		||||
        &.router-link-exact-active {
 | 
			
		||||
          background: 1px solid var(--exact-active-background-color);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
:root{
 | 
			
		||||
  @include light-theme {
 | 
			
		||||
    .is-primary {
 | 
			
		||||
      --fw-bg-color: var(--fw-blue-400);
 | 
			
		||||
      --fw-text-color: var(--fw-blue-010);
 | 
			
		||||
    --fw-page-bg-color: var(--fw-beige-100);
 | 
			
		||||
 | 
			
		||||
      &.is-colored {
 | 
			
		||||
        &[disabled] {
 | 
			
		||||
          --fw-bg-color: var(--fw-blue-100) !important;
 | 
			
		||||
          --fw-text-color: var(--fw-blue-900) !important;
 | 
			
		||||
        }
 | 
			
		||||
    .default {
 | 
			
		||||
      --color: var(--fw-gray-900);
 | 
			
		||||
      --background-color: var(--fw-beige-100);
 | 
			
		||||
      --border-color:var(--fw-gray-300);
 | 
			
		||||
 | 
			
		||||
        &.is-hovered,
 | 
			
		||||
        &:hover {
 | 
			
		||||
          --fw-bg-color: var(--fw-blue-500);
 | 
			
		||||
        }
 | 
			
		||||
      --hover-color:var(--fw-gray-800);
 | 
			
		||||
      --hover-background-color:var(--fw-beige-200);
 | 
			
		||||
      --hover-border-color:var(--fw-gray-800);
 | 
			
		||||
 | 
			
		||||
        &.is-active,
 | 
			
		||||
        &:active {
 | 
			
		||||
          --fw-bg-color: var(--fw-blue-600);
 | 
			
		||||
          &.router-link-exact-active {
 | 
			
		||||
            --fw-bg-color: var(--fw-blue-700);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      --active-color:var(--fw-red-40);
 | 
			
		||||
      --active-background-color:var(--fw-beige-400);
 | 
			
		||||
      --active-border-color:var(--fw-gray-600);
 | 
			
		||||
 | 
			
		||||
      --pressed-color:var(--fw-red-40);
 | 
			
		||||
      --pressed-background-color:var(--fw-gray-900);
 | 
			
		||||
 | 
			
		||||
      --disabled-color:var(--fw-gray-500);
 | 
			
		||||
      --disabled-background-color:var(--fw-beige-100);
 | 
			
		||||
      --disabled-border-color:var(--fw-beige-100);
 | 
			
		||||
 | 
			
		||||
      &.raised{
 | 
			
		||||
        --background-color:var(--fw-beige-300);
 | 
			
		||||
        --border-color:var(--fw-beige-400);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .is-secondary {
 | 
			
		||||
      --fw-bg-color: var(--fw-gray-200);
 | 
			
		||||
      --fw-text-color: var(--fw-gray-900);
 | 
			
		||||
    .primary {
 | 
			
		||||
      --color: var(--fw-blue-010);
 | 
			
		||||
      --background-color:var(--fw-blue-400);
 | 
			
		||||
      --border-color:var(--fw-blue-010);
 | 
			
		||||
 | 
			
		||||
      &.is-colored {
 | 
			
		||||
        &[disabled] {
 | 
			
		||||
          --fw-bg-color: var(--fw-gray-100) !important;
 | 
			
		||||
        }
 | 
			
		||||
      --hover-color: var(--fw-blue-010);
 | 
			
		||||
      --hover-background-color:var(--fw-blue-500);
 | 
			
		||||
 | 
			
		||||
        &.is-hovered,
 | 
			
		||||
        &:hover {
 | 
			
		||||
          --fw-bg-color: var(--fw-gray-200);
 | 
			
		||||
        }
 | 
			
		||||
      --active-color: var(--fw-blue-010);
 | 
			
		||||
      --active-background-color:var(--fw-blue-600);
 | 
			
		||||
 | 
			
		||||
        &.is-active,
 | 
			
		||||
        &.active,
 | 
			
		||||
        &:active {
 | 
			
		||||
          --fw-bg-color: var(--fw-gray-300);
 | 
			
		||||
          &.router-link-exact-active {
 | 
			
		||||
            --fw-bg-color: var(--fw-gray-500);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      --pressed-color:var(--fw-blue-010);
 | 
			
		||||
      --pressed-background-color:var(--fw-blue-800);
 | 
			
		||||
 | 
			
		||||
      --disabled-color:var(--fw-blue-900);
 | 
			
		||||
      --disabled-background-color:var(--fw-blue-100);
 | 
			
		||||
      --disabled-border-color:var(--fw-blue-100);
 | 
			
		||||
 | 
			
		||||
      &.raised {
 | 
			
		||||
        --background-color:var(--fw-blue-500);
 | 
			
		||||
        --hover-background-color:var(--fw-blue-600);
 | 
			
		||||
        --active-background-color:var(--fw-blue-700);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .is-destructive {
 | 
			
		||||
      --fw-bg-color: var(--fw-red-400);
 | 
			
		||||
      --fw-text-color: var(--fw-red-010);
 | 
			
		||||
    .secondary, button {
 | 
			
		||||
      --color: var(--fw-gray-700);
 | 
			
		||||
      --background-color: var(--fw-gray-200);
 | 
			
		||||
      --border-color:var(--fw-gray-700);
 | 
			
		||||
 | 
			
		||||
      &.is-colored {
 | 
			
		||||
        &[disabled] {
 | 
			
		||||
          --fw-bg-color: var(--fw-red-100) !important;
 | 
			
		||||
          --fw-text-color: var(--fw-blue-900) !important;
 | 
			
		||||
      --hover-color:var(--fw-gray-800);
 | 
			
		||||
      --hover-background-color:var(--fw-gray-300);
 | 
			
		||||
      --hover-border-color:var(--fw-gray-800);
 | 
			
		||||
 | 
			
		||||
      --active-color:var(--fw-gray-970);
 | 
			
		||||
      --active-background-color:var(--fw-gray-400);
 | 
			
		||||
      --active-border-color:var(--fw-gray-400);
 | 
			
		||||
 | 
			
		||||
      --pressed-color:var(--fw-beige-200);
 | 
			
		||||
      --pressed-background-color:var(--fw-gray-900);
 | 
			
		||||
 | 
			
		||||
      --disabled-color:var(--fw-gray-500);
 | 
			
		||||
      --disabled-background-color:var(--fw-gray-100);
 | 
			
		||||
      --disabled-border-color:var(--fw-gray-100);
 | 
			
		||||
 | 
			
		||||
      &.raised {
 | 
			
		||||
        --background-color:var(--fw-gray-300);
 | 
			
		||||
        --border-color:var(--fw-gray-300);
 | 
			
		||||
        --hover-background-color:var(--fw-gray-400);
 | 
			
		||||
        --active-background-color:var(--fw-gray-500);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        &.is-hovered,
 | 
			
		||||
        &:hover {
 | 
			
		||||
          --fw-bg-color: var(--fw-red-600);
 | 
			
		||||
    .destructive {
 | 
			
		||||
      --color: var(--fw-red-010);
 | 
			
		||||
      --background-color: var(--fw-red-500);
 | 
			
		||||
      --border-color:var(--fw-red-500);
 | 
			
		||||
 | 
			
		||||
      --hover-color:var(--fw-gray-100);
 | 
			
		||||
      --hover-background-color:var(--fw-red-600);
 | 
			
		||||
      --hover-border-color:var(--fw-red-600);
 | 
			
		||||
 | 
			
		||||
      --active-color:var(--fw-gray-100);
 | 
			
		||||
      --active-background-color:var(--fw-red-700);
 | 
			
		||||
      --active-border-color:var(--fw-red-700);
 | 
			
		||||
 | 
			
		||||
      --disabled-color:var(--fw-gray-500);
 | 
			
		||||
      --disabled-background-color:var(--fw-gray-100);
 | 
			
		||||
      --disabled-border-color:var(--fw-gray-100);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        &.is-active,
 | 
			
		||||
        &.active,
 | 
			
		||||
        &:active {
 | 
			
		||||
          --fw-bg-color: var(--fw-red-700);
 | 
			
		||||
          &.router-link-exact-active {
 | 
			
		||||
            --fw-bg-color: var(--fw-red-800);
 | 
			
		||||
    .blue {
 | 
			
		||||
      --color: var(--fw-blue-900);
 | 
			
		||||
      --background-color: var(--fw-pastel-blue-1);
 | 
			
		||||
      .raised, button {
 | 
			
		||||
        --background-color: var(--fw-pastel-blue-2);
 | 
			
		||||
        --hover-background-color: var(--fw-pastel-blue-3);
 | 
			
		||||
        --active-background-color: var(--fw-pastel-blue-4);
 | 
			
		||||
        --disabled-color: var(--fw-gray-700);
 | 
			
		||||
        --disabled-border-color: var(--fw-gray-400);
 | 
			
		||||
        --disabled-background-color: transparent;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .red {
 | 
			
		||||
      --color: var(--fw-red-900);
 | 
			
		||||
      --background-color: var(--fw-pastel-red-2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .purple {
 | 
			
		||||
      --color: var(--fw-gray-970);
 | 
			
		||||
      --background-color: var(--fw-pastel-purple-1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .green {
 | 
			
		||||
      --color: var(--fw-gray-900);
 | 
			
		||||
      --background-color: var(--fw-pastel-green-1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .yellow {
 | 
			
		||||
      --color: var(--fw-gray-900);
 | 
			
		||||
      --background-color: var(--fw-pastel-yellow-1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -209,7 +341,7 @@
 | 
			
		|||
      &.is-colored {
 | 
			
		||||
        &.is-hovered,
 | 
			
		||||
        &:hover {
 | 
			
		||||
          --fw-bg-color: var(--fw-gray-800);
 | 
			
		||||
          --fw-bg-color: var(--fw-gray-950);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &.is-active,
 | 
			
		||||
| 
						 | 
				
			
			@ -217,7 +349,7 @@
 | 
			
		|||
        &:active {
 | 
			
		||||
          --fw-bg-color: var(--fw-gray-900);
 | 
			
		||||
          &.router-link-exact-active {
 | 
			
		||||
            --fw-bg-color: var(--fw-gray-950);
 | 
			
		||||
            --fw-bg-color: var(--fw-gray-850);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			@ -252,6 +384,7 @@
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.funkwhale {
 | 
			
		||||
  @each $pastel in ("blue", "red", "green", "purple", "yellow") {
 | 
			
		||||
    &.is-#{$pastel} {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ import { ref, onMounted } from 'vue'
 | 
			
		|||
import { useUploadsStore } from '../stores/upload'
 | 
			
		||||
import { useI18n } from 'vue-i18n'
 | 
			
		||||
import { useStore } from '~/store'
 | 
			
		||||
import { color } from '~/composables/colors'
 | 
			
		||||
 | 
			
		||||
import Input from '~/components/ui/Input.vue'
 | 
			
		||||
import Link from '~/components/ui/Link.vue'
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +24,7 @@ const uploads = useUploadsStore()
 | 
			
		|||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <aside :class="[$style.sidebar, $style['sticky-content']]">
 | 
			
		||||
  <aside :class="[$style.sidebar, $style['sticky-content']]" v-bind="color('default solid raised')">
 | 
			
		||||
      <nav :class="$style['quick-actions']">
 | 
			
		||||
        <Link to="/">
 | 
			
		||||
          <img
 | 
			
		||||
| 
						 | 
				
			
			@ -135,8 +136,6 @@ const uploads = useUploadsStore()
 | 
			
		|||
 | 
			
		||||
<style module lang="scss">
 | 
			
		||||
.sidebar {
 | 
			
		||||
  background-color: var(--fw-bg-raised)
 | 
			
		||||
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  display:flex;
 | 
			
		||||
  flex-direction:column;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,7 +80,7 @@ const currentFilter = ref(filterItems[0])
 | 
			
		|||
    title="Upload music to library"
 | 
			
		||||
  >
 | 
			
		||||
    <template #alert="closeAlert">
 | 
			
		||||
      <Alert>
 | 
			
		||||
      <Alert yellow>
 | 
			
		||||
        Before uploading, please ensure your files are tagged properly.
 | 
			
		||||
        We recommend using Picard for that purpose.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,91 +13,83 @@ import Button from "~/components/ui/Button.vue"
 | 
			
		|||
 | 
			
		||||
Funkwhale alerts support a range of pastel colors for visual appeal.
 | 
			
		||||
 | 
			
		||||
::: details Colors
 | 
			
		||||
 | 
			
		||||
- Red
 | 
			
		||||
- Blue
 | 
			
		||||
- Purple
 | 
			
		||||
- Green
 | 
			
		||||
- Yellow
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
### Blue
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Alert color="blue">
 | 
			
		||||
<Alert blue>
 | 
			
		||||
  Blue alert
 | 
			
		||||
</Alert>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Alert color="blue">
 | 
			
		||||
<Alert blue>
 | 
			
		||||
  Blue alert
 | 
			
		||||
</Alert>
 | 
			
		||||
 | 
			
		||||
### Red
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Alert color="red">
 | 
			
		||||
<Alert red>
 | 
			
		||||
  Red alert
 | 
			
		||||
</Alert>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Alert color="red">
 | 
			
		||||
<Alert red>
 | 
			
		||||
  Red alert
 | 
			
		||||
</Alert>
 | 
			
		||||
 | 
			
		||||
### Purple
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Alert color="purple">
 | 
			
		||||
<Alert purple>
 | 
			
		||||
  Purple alert
 | 
			
		||||
</Alert>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Alert color="purple">
 | 
			
		||||
  Purple alert
 | 
			
		||||
<Alert purple>
 | 
			
		||||
  Purple burglar alert
 | 
			
		||||
</Alert>
 | 
			
		||||
 | 
			
		||||
### Green
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Alert color="green">
 | 
			
		||||
<Alert green>
 | 
			
		||||
  Green alert
 | 
			
		||||
</Alert>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Alert color="green">
 | 
			
		||||
<Alert green>
 | 
			
		||||
  Green alert
 | 
			
		||||
</Alert>
 | 
			
		||||
 | 
			
		||||
### Yellow
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Alert color="yellow">
 | 
			
		||||
<Alert yellow>
 | 
			
		||||
  Yellow alert
 | 
			
		||||
</Alert>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Alert color="yellow">
 | 
			
		||||
<Alert yellow>
 | 
			
		||||
  Yellow alert
 | 
			
		||||
</Alert>
 | 
			
		||||
 | 
			
		||||
## Alert actions
 | 
			
		||||
 | 
			
		||||
```vue-html{2-4}
 | 
			
		||||
<Alert>
 | 
			
		||||
<Alert blue>
 | 
			
		||||
  Awesome artist
 | 
			
		||||
 | 
			
		||||
  <template #actions>
 | 
			
		||||
    <Button disabled>Deny</Button>
 | 
			
		||||
    <Button>Got it</Button>
 | 
			
		||||
  </template>
 | 
			
		||||
</Alert>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Alert>
 | 
			
		||||
<Alert blue>
 | 
			
		||||
  Awesome artist
 | 
			
		||||
  <template #actions>
 | 
			
		||||
    <Button disabled>Deny</Button>
 | 
			
		||||
    <Button>Got it</Button>
 | 
			
		||||
  </template>
 | 
			
		||||
</Alert>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,14 +9,14 @@ const click = () => new Promise(resolve => setTimeout(resolve, 1000))
 | 
			
		|||
Buttons are UI elements that users can interact with to perform actions. Funkwhale uses buttons in many contexts.
 | 
			
		||||
 | 
			
		||||
| Prop           | Data type | Required? | Default | Description                                    |
 | 
			
		||||
| ------------ | ----------------------------------------- | --------- | --------- | ------------------------------------------------------------------ |
 | 
			
		||||
| `variant`    | `solid` \| `outline` \| `ghost`           | No        | `solid`   | Whether to render the button as an solid, outline or ghost button. |
 | 
			
		||||
| -------------- | --------- | --------- | ------- | ---------------------------------------------- |
 | 
			
		||||
| `shadow`       | Boolean   | No        | `false` | Whether to render the button with a shadow     |
 | 
			
		||||
| `round`        | Boolean   | No        | `false` | Whether to render the button as a round button |
 | 
			
		||||
| `icon`         | String    | No        |         | The icon attached to the button                |
 | 
			
		||||
| `is-active`  | Boolean                                   | No        | `false`   | Whether the button is in an active state                           |
 | 
			
		||||
| `aria-pressed` | Boolean   | No        | `false` | Whether the button is in an active state       |
 | 
			
		||||
| `is-loading`   | Boolean   | No        | `false` | Whether the button is in a loading state       |
 | 
			
		||||
| `color`      | `primary` \| `secondary` \| `destructive` | No        | `primary` | Renders a colored button                                           |
 | 
			
		||||
 | 
			
		||||
In addition, use [Colors] and [Variants]
 | 
			
		||||
 | 
			
		||||
## Button colors
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -31,12 +31,12 @@ This is the default type. If you don't specify a type, a primary button is rende
 | 
			
		|||
:::
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Button>
 | 
			
		||||
<Button primary>
 | 
			
		||||
  Primary button
 | 
			
		||||
</Button>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Button>
 | 
			
		||||
<Button primary>
 | 
			
		||||
  Primary button
 | 
			
		||||
</Button>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -45,12 +45,12 @@ This is the default type. If you don't specify a type, a primary button is rende
 | 
			
		|||
Secondary buttons represent **neutral** actions such as cancelling a change or dismissing a notification.
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Button color="secondary">
 | 
			
		||||
<Button secondary>
 | 
			
		||||
  Secondary button
 | 
			
		||||
</Button>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Button color="secondary">
 | 
			
		||||
<Button secondary>
 | 
			
		||||
  Secondary button
 | 
			
		||||
</Button>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -59,12 +59,12 @@ Secondary buttons represent **neutral** actions such as cancelling a change or d
 | 
			
		|||
Desctrutive buttons represent **dangerous** actions including deleting items or purging domain information.
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Button color="destructive">
 | 
			
		||||
<Button destructive>
 | 
			
		||||
  Destructive button
 | 
			
		||||
</Button>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Button color="destructive">
 | 
			
		||||
<Button destructive>
 | 
			
		||||
  Destructive button
 | 
			
		||||
</Button>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -84,23 +84,31 @@ This is the default style. If you don't specify a style, a solid button is rende
 | 
			
		|||
<Button>
 | 
			
		||||
  Filled button
 | 
			
		||||
</Button>
 | 
			
		||||
 | 
			
		||||
<Button solid>
 | 
			
		||||
  Also filled button
 | 
			
		||||
</Button>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Button>
 | 
			
		||||
  Filled button
 | 
			
		||||
</Button>
 | 
			
		||||
 | 
			
		||||
<Button solid>
 | 
			
		||||
  Also filled button
 | 
			
		||||
</Button>
 | 
			
		||||
 | 
			
		||||
### Outline
 | 
			
		||||
 | 
			
		||||
Outline buttons have a transparent background. Use these to deemphasize the action the button performs.
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Button variant="outline" color="secondary">
 | 
			
		||||
<Button outline secondary>
 | 
			
		||||
  Outline button
 | 
			
		||||
</Button>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Button variant="outline" color="secondary">
 | 
			
		||||
<Button outline secondary>
 | 
			
		||||
  Outline button
 | 
			
		||||
</Button>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -109,12 +117,12 @@ Outline buttons have a transparent background. Use these to deemphasize the acti
 | 
			
		|||
Ghost buttons have a transparent background and border. Use these to deemphasize the action the button performs.
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Button variant="ghost" color="secondary">
 | 
			
		||||
<Button ghost secondary>
 | 
			
		||||
  Ghost button
 | 
			
		||||
</Button>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Button variant="ghost" color="secondary">
 | 
			
		||||
<Button ghost secondary>
 | 
			
		||||
  Ghost button
 | 
			
		||||
</Button>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -176,15 +184,35 @@ You can pass a state to indicate whether a user can interact with a button.
 | 
			
		|||
 | 
			
		||||
### Active
 | 
			
		||||
 | 
			
		||||
A button is active when clicked by a user. You can force an active state by passing an `is-active` prop.
 | 
			
		||||
You can force an active state by passing an `aria-pressed` prop.
 | 
			
		||||
 | 
			
		||||
This can be useful for toggle buttons (if you don't want to use a [Toggle component](toggle))
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Button is-active>
 | 
			
		||||
<Button aria-pressed>
 | 
			
		||||
  Active button
 | 
			
		||||
</Button>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Button is-active>
 | 
			
		||||
**Secondary (default):**
 | 
			
		||||
 | 
			
		||||
<Button>
 | 
			
		||||
  Inactive button
 | 
			
		||||
</Button>
 | 
			
		||||
 | 
			
		||||
<Button aria-pressed>
 | 
			
		||||
  Active button
 | 
			
		||||
</Button>
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
**Primary:**
 | 
			
		||||
 | 
			
		||||
<Button primary>
 | 
			
		||||
  Inactive button
 | 
			
		||||
</Button>
 | 
			
		||||
 | 
			
		||||
<Button primary aria-pressed>
 | 
			
		||||
  Active button
 | 
			
		||||
</Button>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -270,9 +298,9 @@ Icon buttons shrink down to the icon size if you don't pass any content. If you
 | 
			
		|||
</Button>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Button color="secondary" icon="bi-three-dots-vertical" />
 | 
			
		||||
<Button color="secondary" round icon="bi-x" />
 | 
			
		||||
<Button icon="bi-save"> </Button>
 | 
			
		||||
<Button color="destructive" icon="bi-trash">
 | 
			
		||||
<Button icon="bi-three-dots-vertical" />
 | 
			
		||||
<Button round icon="bi-x" />
 | 
			
		||||
<Button primary icon="bi-save"> </Button>
 | 
			
		||||
<Button destructive icon="bi-trash">
 | 
			
		||||
  Delete
 | 
			
		||||
</Button>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,14 +25,14 @@ Add a 16px gap between adjacent items.
 | 
			
		|||
<Layout flex>
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Alert color="green">A</Alert>
 | 
			
		||||
<Alert color="red">B</Alert>
 | 
			
		||||
<Alert green">A</Alert>
 | 
			
		||||
<Alert red">B</Alert>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<div class="preview">
 | 
			
		||||
 | 
			
		||||
<Alert color="green">A</Alert>
 | 
			
		||||
<Alert color="red">B</Alert>
 | 
			
		||||
<Alert green>A</Alert>
 | 
			
		||||
<Alert purple>B</Alert>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,15 +43,15 @@ Add a 16px gap between adjacent items.
 | 
			
		|||
<Layout flex>
 | 
			
		||||
 | 
			
		||||
```vue-html{2}
 | 
			
		||||
<Alert color="green">A</Alert>
 | 
			
		||||
<Alert green">A</Alert>
 | 
			
		||||
<Spacer/>
 | 
			
		||||
<Alert color="red">B</Alert>
 | 
			
		||||
<Alert red">B</Alert>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<div class="preview">
 | 
			
		||||
<Alert color="green">A</Alert>
 | 
			
		||||
<Alert green>A</Alert>
 | 
			
		||||
<Spacer/>
 | 
			
		||||
<Alert color="red">B</Alert>
 | 
			
		||||
<Alert purple>B</Alert>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
</Layout>
 | 
			
		||||
| 
						 | 
				
			
			@ -62,19 +62,19 @@ Add a 16px gap between adjacent items.
 | 
			
		|||
 | 
			
		||||
```vue-html{4}
 | 
			
		||||
<Layout flex>
 | 
			
		||||
  <Alert color="blue">A</Alert>
 | 
			
		||||
  <Alert color="green">A</Alert>
 | 
			
		||||
  <Alert blue">A</Alert>
 | 
			
		||||
  <Alert yellow">B</Alert>
 | 
			
		||||
  <Spacer/>
 | 
			
		||||
  <Alert color="red">B</Alert>
 | 
			
		||||
  <Alert red">C</Alert>
 | 
			
		||||
</Layout>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<div class="preview">
 | 
			
		||||
<Layout flex>
 | 
			
		||||
<Alert color="blue">A</Alert>
 | 
			
		||||
<Alert color="green">A</Alert>
 | 
			
		||||
<Alert blue>A</Alert>
 | 
			
		||||
<Alert yellow>B</Alert>
 | 
			
		||||
<Spacer/>
 | 
			
		||||
<Alert color="red">B</Alert>
 | 
			
		||||
<Alert red>C</Alert>
 | 
			
		||||
</Layout>
 | 
			
		||||
</div>
 | 
			
		||||
</Layout>
 | 
			
		||||
| 
						 | 
				
			
			@ -91,10 +91,9 @@ const size = ref(1);
 | 
			
		|||
<template>
 | 
			
		||||
  <Input v-model="size" type="range" />
 | 
			
		||||
 | 
			
		||||
  <Alert color="blue">A</Alert>
 | 
			
		||||
  <Alert color="green">A</Alert>
 | 
			
		||||
  <Alert yellow>A</Alert>
 | 
			
		||||
  <Spacer :size="size" />
 | 
			
		||||
  <Alert color="red">B</Alert>
 | 
			
		||||
  <Alert purple>B</Alert>
 | 
			
		||||
</template>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -103,9 +102,9 @@ const size = ref(1);
 | 
			
		|||
  {{ size }}px
 | 
			
		||||
</div>
 | 
			
		||||
<div class="preview">
 | 
			
		||||
  <Alert color="blue">A</Alert>
 | 
			
		||||
  <Alert yellow>A</Alert>
 | 
			
		||||
  <Spacer :size="size" />
 | 
			
		||||
  <Alert color="red">B</Alert>
 | 
			
		||||
  <Alert purple>B</Alert>
 | 
			
		||||
</div>
 | 
			
		||||
</Layout>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -123,14 +122,14 @@ const size = ref(1);
 | 
			
		|||
  <Layout stack no-gap
 | 
			
		||||
    :style="{ height: size + '%' }"
 | 
			
		||||
  >
 | 
			
		||||
    <Alert>A</Alert>
 | 
			
		||||
    <Alert blue>A</Alert>
 | 
			
		||||
    <Spacer grow title="grow" />
 | 
			
		||||
    <Alert>B</Alert>
 | 
			
		||||
    <Alert>C</Alert>
 | 
			
		||||
    <Alert red>B</Alert>
 | 
			
		||||
    <Alert green>C</Alert>
 | 
			
		||||
    <Spacer shrink title="shrink" />
 | 
			
		||||
    <Alert>D</Alert>
 | 
			
		||||
    <Alert purple>D</Alert>
 | 
			
		||||
    <Spacer grow shrink title="grow shrink" />
 | 
			
		||||
    <Alert>E</Alert>
 | 
			
		||||
    <Alert yellow>E</Alert>
 | 
			
		||||
  </Layout>
 | 
			
		||||
</Layout>
 | 
			
		||||
```
 | 
			
		||||
| 
						 | 
				
			
			@ -141,14 +140,14 @@ const size = ref(1);
 | 
			
		|||
<Input v-model="size" type="range" style="writing-mode: vertical-lr; height:100%"><template #input-right>{{ size }}%</template></Input>
 | 
			
		||||
 | 
			
		||||
<Layout stack no-gap :style="{ height: size + '%'}">
 | 
			
		||||
  <Alert>A</Alert>
 | 
			
		||||
  <Alert blue>A</Alert>
 | 
			
		||||
  <Spacer grow title="grow" />
 | 
			
		||||
  <Alert>B</Alert>
 | 
			
		||||
  <Alert>C</Alert>
 | 
			
		||||
  <Alert red>B</Alert>
 | 
			
		||||
  <Alert green>C</Alert>
 | 
			
		||||
  <Spacer shrink title="shrink" />
 | 
			
		||||
  <Alert>D</Alert>
 | 
			
		||||
  <Alert purple>D</Alert>
 | 
			
		||||
  <Spacer grow shrink title="grow shrink" />
 | 
			
		||||
  <Alert>E</Alert>
 | 
			
		||||
  <Alert yellow>E</Alert>
 | 
			
		||||
</Layout>
 | 
			
		||||
 | 
			
		||||
</Layout>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,7 +70,7 @@ Make sure to add `autofocus` to the preferred button.
 | 
			
		|||
  Modal content
 | 
			
		||||
 | 
			
		||||
  <template #actions>
 | 
			
		||||
    <Button @click="isOpen = false" color="secondary">
 | 
			
		||||
    <Button @click="isOpen = false">
 | 
			
		||||
      Cancel
 | 
			
		||||
    </Button>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -88,7 +88,7 @@ Make sure to add `autofocus` to the preferred button.
 | 
			
		|||
<Modal v-model="isOpen2" title="My modal">
 | 
			
		||||
  Modal content
 | 
			
		||||
  <template #actions>
 | 
			
		||||
    <Button @click="isOpen2 = false" color="secondary">
 | 
			
		||||
    <Button @click="isOpen2 = false">
 | 
			
		||||
      Cancel
 | 
			
		||||
    </Button>
 | 
			
		||||
    <Button autofocus @click="isOpen2 = false">
 | 
			
		||||
| 
						 | 
				
			
			@ -120,13 +120,13 @@ Note that confirmation dialogs interrupt the user's workflow. Consider adding a
 | 
			
		|||
:::
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Button @click="isOpen = true" color="destructive">
 | 
			
		||||
<Button @click="isOpen = true" destructive>
 | 
			
		||||
  Delete my account ...
 | 
			
		||||
</Button>
 | 
			
		||||
 | 
			
		||||
<Modal v-model="isOpen" title="Delete account?">
 | 
			
		||||
  <template #alert>
 | 
			
		||||
    <Alert color="red">
 | 
			
		||||
    <Alert red>
 | 
			
		||||
1 082 music files that you uploaded will be deleted.<br />
 | 
			
		||||
7 879 items in your collections will be unlinked.
 | 
			
		||||
    </Alert>
 | 
			
		||||
| 
						 | 
				
			
			@ -135,22 +135,22 @@ Do you want to delete your account forever?
 | 
			
		|||
 | 
			
		||||
You will not be able to restore your account.
 | 
			
		||||
  <template #actions>
 | 
			
		||||
    <Button autofocus @click="isOpen = false" color="secondary">
 | 
			
		||||
    <Button autofocus @click="isOpen = false" >
 | 
			
		||||
      Keep my account
 | 
			
		||||
    </Button>
 | 
			
		||||
    <Button color="destructive" @click="isOpen = false">
 | 
			
		||||
    <Button destructive @click="isOpen = false">
 | 
			
		||||
      I understand. Delete my account now!
 | 
			
		||||
    </Button>
 | 
			
		||||
  </template>
 | 
			
		||||
</Modal>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Button @click="isOpen6 = true" color="destructive">
 | 
			
		||||
<Button @click="isOpen6 = true" destructive>
 | 
			
		||||
Delete my account ...
 | 
			
		||||
</Button>
 | 
			
		||||
<Modal v-model="isOpen6" title="Delete account?">
 | 
			
		||||
<template #alert>
 | 
			
		||||
<Alert color="red">
 | 
			
		||||
<Alert red>
 | 
			
		||||
1 082 music files that you uploaded will be deleted.<br />
 | 
			
		||||
7 879 items in your collections will be unlinked.
 | 
			
		||||
</Alert>
 | 
			
		||||
| 
						 | 
				
			
			@ -159,10 +159,10 @@ Do you want to delete your account forever?
 | 
			
		|||
 | 
			
		||||
You will not be able to restore your account.
 | 
			
		||||
<template #actions>
 | 
			
		||||
<Button autofocus @click="isOpen6 = false" color="secondary">
 | 
			
		||||
<Button autofocus @click="isOpen6 = false">
 | 
			
		||||
Keep my account
 | 
			
		||||
</Button>
 | 
			
		||||
<Button color="destructive" @click="isOpen6 = false">
 | 
			
		||||
<Button destructive @click="isOpen6 = false">
 | 
			
		||||
I understand. Delete my account now!
 | 
			
		||||
</Button>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -239,7 +239,7 @@ You can nest [Funkwhale alerts](./alert) to visually highlight content within th
 | 
			
		|||
<Modal v-model="isOpen3" title="My modal">
 | 
			
		||||
  Modal content
 | 
			
		||||
  <template #alert v-if="alertOpen">
 | 
			
		||||
    <Alert>
 | 
			
		||||
    <Alert blue>
 | 
			
		||||
      Alert content
 | 
			
		||||
      <template #actions>
 | 
			
		||||
        <Button autofocus @click="alertOpen = false">Close alert</Button>
 | 
			
		||||
| 
						 | 
				
			
			@ -250,7 +250,7 @@ You can nest [Funkwhale alerts](./alert) to visually highlight content within th
 | 
			
		|||
    <Button @click="isOpen3 = false" color="secondary">
 | 
			
		||||
      Cancel
 | 
			
		||||
    </Button>
 | 
			
		||||
    <Button @click="isOpen3 = false">
 | 
			
		||||
    <Button primary @click="isOpen3 = false">
 | 
			
		||||
      Ok
 | 
			
		||||
    </Button>
 | 
			
		||||
  </template>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,29 +12,17 @@ You can add text to pills by adding it between the `<Pill>` tags.
 | 
			
		|||
| ------- | ----------------------------------------------------------------------------------------------- | --------- | ----------- | ---------------------- |
 | 
			
		||||
| `color` | `primary` \| `secondary` \| `destructive` \| `blue` \| `red` \| `purple` \| `green` \| `yellow` | No        | `secondary` | Renders a colored pill |
 | 
			
		||||
 | 
			
		||||
## Pill types
 | 
			
		||||
 | 
			
		||||
You can assign a type to your pill to indicate what kind of information it conveys.
 | 
			
		||||
 | 
			
		||||
::: details Types
 | 
			
		||||
 | 
			
		||||
- Primary
 | 
			
		||||
- Secondary
 | 
			
		||||
- Destructive
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
### Primary
 | 
			
		||||
 | 
			
		||||
Primary pills convey **positive** information.
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Pill color="primary">
 | 
			
		||||
<Pill primary>
 | 
			
		||||
  Primary pill
 | 
			
		||||
</Pill>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Pill color="primary">
 | 
			
		||||
<Pill primary>
 | 
			
		||||
  Primary pill
 | 
			
		||||
</Pill>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -61,12 +49,12 @@ This is the default type for pills. If you don't specify a type, a **secondary**
 | 
			
		|||
Destructive pills convey **destructive** or **negative** information. Use these to indicate that information could cause issues such as data loss.
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Pill color="destructive">
 | 
			
		||||
<Pill destructive>
 | 
			
		||||
  Destructive pill
 | 
			
		||||
</Pill>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Pill color="destructive">
 | 
			
		||||
<Pill destructive>
 | 
			
		||||
  Destructive pill
 | 
			
		||||
</Pill>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -74,73 +62,63 @@ Destructive pills convey **destructive** or **negative** information. Use these
 | 
			
		|||
 | 
			
		||||
Funkwhale pills support a range of pastel colors to create visually appealing interfaces.
 | 
			
		||||
 | 
			
		||||
::: details Colors
 | 
			
		||||
 | 
			
		||||
- Red
 | 
			
		||||
- Blue
 | 
			
		||||
- Purple
 | 
			
		||||
- Green
 | 
			
		||||
- Yellow
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
### Blue
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Pill color="blue">
 | 
			
		||||
<Pill blue>
 | 
			
		||||
  Blue pill
 | 
			
		||||
</Pill>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Pill color="blue">
 | 
			
		||||
<Pill blue>
 | 
			
		||||
  Blue pill
 | 
			
		||||
</Pill>
 | 
			
		||||
 | 
			
		||||
### Red
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Pill color="red">
 | 
			
		||||
<Pill red>
 | 
			
		||||
  Red pill
 | 
			
		||||
</Pill>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Pill color="red">
 | 
			
		||||
<Pill red>
 | 
			
		||||
  Red pill
 | 
			
		||||
</Pill>
 | 
			
		||||
 | 
			
		||||
### Purple
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Pill color="purple">
 | 
			
		||||
<Pill purple>
 | 
			
		||||
  Purple pill
 | 
			
		||||
</Pill>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Pill color="purple">
 | 
			
		||||
<Pill purple>
 | 
			
		||||
  Purple pill
 | 
			
		||||
</Pill>
 | 
			
		||||
 | 
			
		||||
### Green
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Pill color="green">
 | 
			
		||||
<Pill green>
 | 
			
		||||
  Green pill
 | 
			
		||||
</Pill>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Pill color="green">
 | 
			
		||||
<Pill green>
 | 
			
		||||
  Green pill
 | 
			
		||||
</Pill>
 | 
			
		||||
 | 
			
		||||
### Yellow
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Pill color="yellow">
 | 
			
		||||
<Pill yellow>
 | 
			
		||||
  Yellow pill
 | 
			
		||||
</Pill>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Pill color="yellow">
 | 
			
		||||
<Pill yellow>
 | 
			
		||||
  Yellow pill
 | 
			
		||||
</Pill>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1,292 @@
 | 
			
		|||
<script setup>
 | 
			
		||||
import { color } from "~/composables/colors.ts"
 | 
			
		||||
import Button from "~/components/ui/Button.vue"
 | 
			
		||||
import Card from "~/components/ui/Card.vue"
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
# Using Color
 | 
			
		||||
 | 
			
		||||
## Add color via props
 | 
			
		||||
 | 
			
		||||
[Alerts](components/ui/alert) support [Pastel](#pastel) attributes. [Buttons](components/ui/button) accept [Color](#color) and [Variant](#variant) attributes. [Cards](components/ui/card) accept [Pastel](#pastel), [Variant](#variant), [Neutral](#neutral) and [Raised](#raised) attributes.
 | 
			
		||||
 | 
			
		||||
```vue-html
 | 
			
		||||
<Card title="solid red" solid red />
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<Card title="solid red" solid red />
 | 
			
		||||
 | 
			
		||||
## Add color to a any component or Html tag
 | 
			
		||||
 | 
			
		||||
1. Choose a
 | 
			
		||||
   - [base color](#colors) (`primary | secondary | destructive`) or
 | 
			
		||||
   - [pastel color](#pastel) (`blue | red | green | yellow`) or
 | 
			
		||||
   - [neutral beige or gray](#neutral) (`default`) for surfaces
 | 
			
		||||
2. Choose a [variant](#color-variants) (`solid | ghost | outline`)
 | 
			
		||||
3. Add [interactivity and raise the surface](#interactive-andor-raised)
 | 
			
		||||
 | 
			
		||||
```vue
 | 
			
		||||
<script setup>
 | 
			
		||||
import { color } from "~/composables/colors.ts";
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div v-bind="color('primary solid interactive raised')" />
 | 
			
		||||
</template>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('primary solid interactive raised')" />
 | 
			
		||||
 | 
			
		||||
## Base colors
 | 
			
		||||
 | 
			
		||||
### Neutral
 | 
			
		||||
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('default solid')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('default solid interactive')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('default solid raised')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('default solid interactive')" />
 | 
			
		||||
 | 
			
		||||
### Color
 | 
			
		||||
 | 
			
		||||
Primary
 | 
			
		||||
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('primary solid')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('primary solid interactive')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('primary solid raised')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('primary solid interactive')" />
 | 
			
		||||
 | 
			
		||||
Secondary
 | 
			
		||||
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('secondary solid')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('secondary solid interactive')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('secondary solid raised')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('secondary solid interactive')" />
 | 
			
		||||
 | 
			
		||||
Destructive
 | 
			
		||||
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('destructive solid')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('destructive solid interactive')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('destructive solid raised')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('destructive solid interactive')" />
 | 
			
		||||
 | 
			
		||||
### Pastel
 | 
			
		||||
 | 
			
		||||
Blue, Red, Purple, Green, Yellow
 | 
			
		||||
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('blue solid interactive')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('red solid interactive')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('purple solid interactive')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('green solid interactive')" />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('yellow solid interactive')" />
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### Variant
 | 
			
		||||
 | 
			
		||||
Solid (default), Ghost, Outline
 | 
			
		||||
 | 
			
		||||
<Button round shadow icon="bi-x" solid />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('solid raised')">
 | 
			
		||||
<Button round icon="bi-x" ghost />
 | 
			
		||||
<Button round icon="bi-x" outline />
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<br/>
 | 
			
		||||
 | 
			
		||||
<Button round shadow icon="bi-x" primary solid />
 | 
			
		||||
<div :class="$style.swatch" v-bind="color('primary solid')">
 | 
			
		||||
<Button round icon="bi-x" primary ghost />
 | 
			
		||||
<Button round icon="bi-x" primary outline />
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### Interactive and/or Raised
 | 
			
		||||
 | 
			
		||||
<div v-bind="color('default solid')" style="display:inline-flex;">
 | 
			
		||||
    <div :class="$style.swatch" v-bind="color('solid')" />
 | 
			
		||||
    <div :class="$style.swatch" v-bind="color('solid interactive')" />
 | 
			
		||||
</div>
 | 
			
		||||
<div v-bind="color('secondary solid')" style="display:inline-flex;">
 | 
			
		||||
    <div :class="$style.swatch" v-bind="color('solid')" />
 | 
			
		||||
    <div :class="$style.swatch" v-bind="color('solid interactive')" />
 | 
			
		||||
</div>
 | 
			
		||||
<div v-bind="color('primary solid')" style="display:inline-flex;">
 | 
			
		||||
    <div :class="$style.swatch" v-bind="color('solid')" />
 | 
			
		||||
    <div :class="$style.swatch" v-bind="color('solid interactive')" />
 | 
			
		||||
</div>
 | 
			
		||||
<br/>
 | 
			
		||||
<div v-bind="color('default raised solid')" style="display:inline-flex;">
 | 
			
		||||
    <div :class="$style.swatch" v-bind="color('solid raised')" />
 | 
			
		||||
    <div :class="$style.swatch" v-bind="color('solid interactive raised')" />
 | 
			
		||||
</div>
 | 
			
		||||
<div v-bind="color('secondary raised solid')" style="display:inline-flex;">
 | 
			
		||||
    <div :class="$style.swatch" v-bind="color('solid raised')" />
 | 
			
		||||
    <div :class="$style.swatch" v-bind="color('solid interactive raised')" />
 | 
			
		||||
</div>
 | 
			
		||||
<div v-bind="color('primary raised solid')" style="display:inline-flex;">
 | 
			
		||||
    <div :class="$style.swatch" v-bind="color('solid raised')" />
 | 
			
		||||
    <div :class="$style.swatch" v-bind="color('solid interactive raised')" />
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
## Palette
 | 
			
		||||
 | 
			
		||||
The color palette consists of Blues, Reds, Grays, Beiges, as well as pastel blue/red/green/yellow.
 | 
			
		||||
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-blue-010)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-blue-100)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-blue-400)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-blue-500)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-blue-600)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-blue-700)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-blue-800)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-blue-900)" />
 | 
			
		||||
 | 
			
		||||
<br/>
 | 
			
		||||
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-red-010)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-red-100)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-red-400)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-red-500)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-red-600)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-red-700)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-red-800)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-red-900)" />
 | 
			
		||||
 | 
			
		||||
<br/>
 | 
			
		||||
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-gray-100)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-gray-200)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-gray-300)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-gray-400)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-gray-500)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-gray-600)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-gray-700)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-gray-800)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.tiny]" style="background:var(--fw-gray-850)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-gray-900)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.tiny]" style="background:var(--fw-gray-950)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.tiny]" style="background:var(--fw-gray-960)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.tiny]" style="background:var(--fw-gray-970)" />
 | 
			
		||||
 | 
			
		||||
<br/>
 | 
			
		||||
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-beige-100)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-beige-200)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-beige-300)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-beige-400)" />
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-blue-1)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-blue-2)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-blue-3)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-blue-4)" />
 | 
			
		||||
 | 
			
		||||
<br/>
 | 
			
		||||
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-red-1)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-red-2)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-red-3)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-red-4)" />
 | 
			
		||||
 | 
			
		||||
<br/>
 | 
			
		||||
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-purple-1)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-purple-2)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-purple-3)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-purple-4)" />
 | 
			
		||||
 | 
			
		||||
<br/>
 | 
			
		||||
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-green-1)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-green-2)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-green-3)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-green-4)" />
 | 
			
		||||
 | 
			
		||||
<br/>
 | 
			
		||||
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-yellow-1)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-yellow-2)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-yellow-3)" />
 | 
			
		||||
<div :class="[$style.swatch, $style.small]" style="background:var(--fw-pastel-yellow-4)" />
 | 
			
		||||
 | 
			
		||||
In addition, we have a single shade of orange used for secondary indicators such as active tabs:
 | 
			
		||||
 | 
			
		||||
<div :class="$style.swatch" style="background:var(--fw-secondary)" />
 | 
			
		||||
 | 
			
		||||
## Theme
 | 
			
		||||
 | 
			
		||||
Many browsers automatically turn on "night mode" to lower the light emission of the screen. Users can override the browser theme in their settings menu.
 | 
			
		||||
 | 
			
		||||
In both "dark mode" and "light mode", the colors must provide adequate contrast and consistency.
 | 
			
		||||
 | 
			
		||||
## Semantic colors
 | 
			
		||||
 | 
			
		||||
We use semantic color variables that can mean a different shade depending on the currently chosen theme
 | 
			
		||||
 | 
			
		||||
- primary
 | 
			
		||||
- secondary (default)
 | 
			
		||||
- destructive
 | 
			
		||||
 | 
			
		||||
## Color variants
 | 
			
		||||
 | 
			
		||||
For each semantic color, we set a foreground and a background. In addition, we need to check context: A button should have a stronger shade when the mouse is hovering over it. When a surface is raised over another surface, it should have a stronger shade, too.
 | 
			
		||||
 | 
			
		||||
- ghost (default for most other things)
 | 
			
		||||
 | 
			
		||||
  - text color
 | 
			
		||||
 | 
			
		||||
- outline
 | 
			
		||||
 | 
			
		||||
  - border color
 | 
			
		||||
 | 
			
		||||
- solid (default for buttons)
 | 
			
		||||
  - bg color
 | 
			
		||||
  - border color
 | 
			
		||||
 | 
			
		||||
Variants can be made interactive and/or raised:
 | 
			
		||||
 | 
			
		||||
- no alteration (default)
 | 
			
		||||
 | 
			
		||||
- raised
 | 
			
		||||
 | 
			
		||||
  - text color
 | 
			
		||||
  - border color
 | 
			
		||||
  - bg color
 | 
			
		||||
 | 
			
		||||
- interactive
 | 
			
		||||
 | 
			
		||||
  - can be disabled
 | 
			
		||||
  - can be active
 | 
			
		||||
  - can be exact-active
 | 
			
		||||
  - can be hovering
 | 
			
		||||
 | 
			
		||||
- interactive-raised
 | 
			
		||||
  - combine `raised` and `interactive`
 | 
			
		||||
 | 
			
		||||
<style module>
 | 
			
		||||
    .swatch {
 | 
			
		||||
        border-radius: 2em;
 | 
			
		||||
        min-width: 3.2em;
 | 
			
		||||
        min-height: 3.2em;
 | 
			
		||||
        margin: 1ch;
 | 
			
		||||
        display: inline-flex;
 | 
			
		||||
        box-shadow: 1px 2px 7px #0003, 0px 0px 1px #0009;
 | 
			
		||||
        align-items:center;
 | 
			
		||||
        justify-items:center;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .small{
 | 
			
		||||
        margin: .25rem;
 | 
			
		||||
        min-width: 1.6em;
 | 
			
		||||
        min-height: 1.6em;
 | 
			
		||||
        box-shadow: 1px 2px 4px #0002, 0px 0px 1px #0007;
 | 
			
		||||
    }
 | 
			
		||||
    .tiny{
 | 
			
		||||
        margin:  .5rem .25rem;
 | 
			
		||||
        min-width: 1em;
 | 
			
		||||
        min-height: 1em;
 | 
			
		||||
        box-shadow: 1px 2px 4px #0002, 0px 0px 1px #0007;
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,7 +71,96 @@ import Button from "~/components/ui/Button.vue";
 | 
			
		|||
<style module></style>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <Alert />
 | 
			
		||||
  <Alert yellow />
 | 
			
		||||
  <Button />
 | 
			
		||||
</template>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Limitations of component props
 | 
			
		||||
 | 
			
		||||
While Vue can infer props based on a type, it will fail with mysterious errors if the type is an exclusive union or has union types as keys.
 | 
			
		||||
 | 
			
		||||
I hope this will be resolved soon so we can use this more elegant way of injecting non-trivial props with full autocomplete, 21st century style:
 | 
			
		||||
 | 
			
		||||
```vue
 | 
			
		||||
<script setup>
 | 
			
		||||
type A = 'either' | 'or'
 | 
			
		||||
type B = 'definitely'
 | 
			
		||||
type Props = { [k in `${A}-${B}`]?: true }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <Component either-definitely />
 | 
			
		||||
  <Component or-definitely />
 | 
			
		||||
  <Component />
 | 
			
		||||
  {{ Error: <Component either /> }} {{ Error: <Component definitely /> }}
 | 
			
		||||
</template>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
::: details Example
 | 
			
		||||
 | 
			
		||||
````ts
 | 
			
		||||
// Color from props
 | 
			
		||||
 | 
			
		||||
type SingleOrNoProp<T extends string> = RequireOneOrNone<Record<T, true>, T>;
 | 
			
		||||
type SingleProp<T extends string> = RequireExactlyOne<Record<T, true>, T>;
 | 
			
		||||
 | 
			
		||||
export type Props = Simplify<
 | 
			
		||||
  SingleProp<Color | Default | Pastel> &
 | 
			
		||||
    SingleOrNoProp<Variant> &
 | 
			
		||||
    SingleOrNoProp<"interactive"> &
 | 
			
		||||
    SingleOrNoProp<"raised">
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
// Limit the choices:
 | 
			
		||||
 | 
			
		||||
export type ColorProps = Simplify<
 | 
			
		||||
  SingleProp<Color> & SingleOrNoProp<Variant> & SingleOrNoProp<"raised">
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
export type PastelProps = Simplify<
 | 
			
		||||
  SingleProp<Pastel> & SingleOrNoProp<"raised">
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
// Note that as of now, Vue does not support unions of props.
 | 
			
		||||
// So instead, we give it a single string:
 | 
			
		||||
 | 
			
		||||
export type ColorProp = Simplify<`${Color}${
 | 
			
		||||
  | ""
 | 
			
		||||
  | `-${Variant}${"" | "-raised"}`}`>;
 | 
			
		||||
export type PastelProp = Simplify<`${Pastel}${"" | "-raised"}`>;
 | 
			
		||||
 | 
			
		||||
// Using like this:
 | 
			
		||||
//   type Props = {...} & { [k in ColorProp]? : true }
 | 
			
		||||
// This will also lead to runtime errors. Why?
 | 
			
		||||
 | 
			
		||||
export const isColorProp = (k: string) =>
 | 
			
		||||
  !![...colors, ...defaults, ...pastels].find(k.startsWith);
 | 
			
		||||
 | 
			
		||||
console.log(true, isColorProp("primary"));
 | 
			
		||||
console.log(true, isColorProp("secondary"));
 | 
			
		||||
console.log(true, isColorProp("red"));
 | 
			
		||||
console.log(false, isColorProp("Jes"));
 | 
			
		||||
console.log(false, isColorProp("raised"));
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convenience function in case you want to hand over the props in the form
 | 
			
		||||
 * ```
 | 
			
		||||
 * <Component primary solid interactive raised >...</Component>
 | 
			
		||||
 * ```
 | 
			
		||||
 *
 | 
			
		||||
 * @param props Any superset of type `Props`
 | 
			
		||||
 * @returns the corresponding `class` object
 | 
			
		||||
 *
 | 
			
		||||
 * Note: Make sure to implement the necessary classes in `colors.scss`!
 | 
			
		||||
 */
 | 
			
		||||
export const colorFromProps = (props: Record<string, unknown>) =>
 | 
			
		||||
  color(
 | 
			
		||||
    Object.keys(props)
 | 
			
		||||
      .filter(isColorProp)
 | 
			
		||||
      .join(" ")
 | 
			
		||||
      .replace("-", " ") as ColorSelector,
 | 
			
		||||
  );
 | 
			
		||||
````
 | 
			
		||||
 | 
			
		||||
:::
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10668,6 +10668,11 @@ type-detect@^4.0.0, type-detect@^4.1.0:
 | 
			
		|||
  resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c"
 | 
			
		||||
  integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==
 | 
			
		||||
 | 
			
		||||
type-fest@4.30.1:
 | 
			
		||||
  version "4.30.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.30.1.tgz#120b9e15177310ec4e9d5d6f187d86c0f4b55e0e"
 | 
			
		||||
  integrity sha512-ojFL7eDMX2NF0xMbDwPZJ8sb7ckqtlAi1GsmgsFXvErT9kFTk1r0DuQKvrCh73M6D4nngeHJmvogF9OluXs7Hw==
 | 
			
		||||
 | 
			
		||||
type-fest@^0.16.0:
 | 
			
		||||
  version "0.16.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue