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'),
|
||||
'is-' + (width ?? 'standard'),
|
||||
'is-aligned-' + (alignText ?? 'center'),
|
||||
{
|
||||
'is-active': isActive,
|
||||
'is-loading': isLoading,
|
||||
'icon-only': iconOnly,
|
||||
'has-icon': !!icon,
|
||||
'is-round': round,
|
||||
'is-shadow': shadow
|
||||
}
|
||||
]" @click="click">
|
||||
<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,
|
||||
'is-icon-only': isIconOnly,
|
||||
'has-icon': !!icon,
|
||||
'is-round': round,
|
||||
'is-shadow': shadow
|
||||
}
|
||||
]" @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">
|
||||
<slot />
|
||||
<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,83 +96,215 @@
|
|||
--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);
|
||||
|
||||
&.is-hovered,
|
||||
&:hover {
|
||||
--fw-bg-color: var(--fw-red-600);
|
||||
}
|
||||
--active-color:var(--fw-gray-970);
|
||||
--active-background-color:var(--fw-gray-400);
|
||||
--active-border-color:var(--fw-gray-400);
|
||||
|
||||
&.is-active,
|
||||
&.active,
|
||||
&:active {
|
||||
--fw-bg-color: var(--fw-red-700);
|
||||
&.router-link-exact-active {
|
||||
--fw-bg-color: var(--fw-red-800);
|
||||
}
|
||||
}
|
||||
--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);
|
||||
}
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
|
@ -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>
|
||||
|
|
|
@ -8,15 +8,15 @@ 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 |
|
||||
| `is-loading` | Boolean | No | `false` | Whether the button is in a loading state |
|
||||
| `color` | `primary` \| `secondary` \| `destructive` | No | `primary` | Renders a colored button |
|
||||
| Prop | Data type | Required? | Default | Description |
|
||||
| -------------- | --------- | --------- | ------- | ---------------------------------------------- |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|
||||
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