refactor(ui): composable widths; default colors and widths; consistent props;

This commit is contained in:
upsiflu 2024-12-22 21:19:49 +01:00
parent c65b4bd4f0
commit 4069d2ba71
23 changed files with 524 additions and 337 deletions

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { type PastelProps, propsToColor } from '~/composables/colors' import { type PastelProps, color } from '~/composables/colors'
type Props = PastelProps type Props = PastelProps
@ -9,7 +9,7 @@ const props = defineProps<Props>()
<template> <template>
<div <div
class="funkwhale is-colored solid alert" class="funkwhale is-colored solid alert"
v-bind="propsToColor(props)" v-bind="color(props)"
> >
<slot /> <slot />

View File

@ -1,13 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, useSlots, onMounted } from 'vue' import { ref, computed, useSlots, onMounted } from 'vue'
import { type ColorProps, type VariantProps, type DefaultProps, type RaisedProps, propsToColor } from '~/composables/colors';
import { type ColorProps, type VariantProps, type DefaultProps, type RaisedProps, color } from '~/composables/colors';
import { type WidthProps, width } from '~/composables/widths'
import Loader from '~/components/ui/Loader.vue' import Loader from '~/components/ui/Loader.vue'
const props = defineProps<{ const props = defineProps<{
width?: 'standard' | 'auto' | 'full'
alignText?: 'left' | 'center' | 'right' alignText?: 'left' | 'center' | 'right'
alignSelf?: 'start' | 'center' | 'end' alignSelf?: 'start' | 'center' | 'end' | 'baseline'
thin?: true thin?: true
isActive?: boolean isActive?: boolean
@ -21,7 +22,14 @@ const props = defineProps<{
autofocus? : boolean autofocus? : boolean
ariaPressed? : true ariaPressed? : true
} & (ColorProps | DefaultProps) & VariantProps & RaisedProps>() } & (ColorProps | DefaultProps)
& VariantProps
& RaisedProps
& WidthProps>()
if ('minContent' in props) {
console.log("MIN CONTENT")
}
const slots = useSlots() const slots = useSlots()
const isIconOnly = computed(() => !!props.icon && !slots.default) const isIconOnly = computed(() => !!props.icon && !slots.default)
@ -33,6 +41,11 @@ const fontWeight = props.thin ? 400 : 900
const button = ref() const button = ref()
const attributes = computed(() => ({
...color(props, ['interactive']),
...width(props, [isIconOnly.value ? 'minContent' : 'buttonWidth'])
}))
const click = async (...args: any[]) => { const click = async (...args: any[]) => {
internalLoader.value = true internalLoader.value = true
@ -50,10 +63,9 @@ onMounted(() => {
<template> <template>
<button ref="button" <button ref="button"
:aria-pressed="props.ariaPressed" :aria-pressed="props.ariaPressed"
v-bind="propsToColor({...props, interactive:true})" v-bind="attributes"
class="funkwhale button" class="funkwhale button"
:class="[ :class="[
'is-' + width,
'is-text-aligned-' + (alignText ?? 'center'), 'is-text-aligned-' + (alignText ?? 'center'),
'is-self-aligned-' + (alignSelf ?? 'center'), 'is-self-aligned-' + (alignSelf ?? 'center'),
{ {
@ -79,6 +91,8 @@ onMounted(() => {
<style lang="scss"> <style lang="scss">
.funkwhale { .funkwhale {
&.button { &.button {
--padding: 10px;
position: relative; position: relative;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@ -86,11 +100,11 @@ onMounted(() => {
font-family: $font-main; font-family: $font-main;
font-weight: v-bind(fontWeight); font-weight: v-bind(fontWeight);
font-size: 0.875em; font-size: 14px;
line-height: 1em; line-height: 1em;
padding: 9px 10px 11px 10px; padding: 9px var(--padding) 11px var(--padding);
&.is-icon-only { &.is-icon-only {
padding: 10px; padding: 10px;
} }
@ -117,10 +131,6 @@ onMounted(() => {
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.2); box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.2);
} }
&:not(.is-icon-only):not(.is-auto), &.is-standard {
min-width: 8.5rem;
}
align-self: start; align-self: start;
&.is-self-aligned-start { &.is-self-aligned-start {
@ -135,6 +145,10 @@ onMounted(() => {
align-self: flex-end; align-self: flex-end;
} }
&.is-self-aligned-baseline {
align-self: baseline;
}
&.is-full { &.is-full {
align-self: stretch; align-self: stretch;
} }
@ -157,16 +171,16 @@ onMounted(() => {
} }
i.bi { i.bi {
font-size: 1.2rem; font-size: 1.2rem;/* Must not increase the height of the button */
margin: -0.1rem;
&.large { &.large {
font-size:2rem; font-size:2rem;
margin: -0.1rem;
} }
/* Must not increase the height of the button */
margin: -0.5rem 0;
} }
i.bi + span:not(:empty) { i.bi + span:not(:empty) {
margin-left: 1ch; margin-left: 10px;
} }
} }
} }

View File

@ -2,7 +2,8 @@
import { computed } from 'vue' import { computed } from 'vue'
import { type RouterLinkProps, RouterLink } from 'vue-router'; import { type RouterLinkProps, RouterLink } from 'vue-router';
import { type ColorProps, type DefaultProps, type PastelProps, type RaisedProps, type VariantProps, propsToColor } from '~/composables/colors' import { type ColorProps, type DefaultProps, type PastelProps, type RaisedProps, type VariantProps, color } from '~/composables/colors'
import { type WidthProps, width } from '~/composables/widths'
import Pill from './Pill.vue' import Pill from './Pill.vue'
import Alert from './Alert.vue' import Alert from './Alert.vue'
@ -16,36 +17,29 @@ const props = defineProps<{
tags?: string[] tags?: string[]
image?: string | { src: string, style?: "withPadding" } image?: string | { src: string, style?: "withPadding" }
icon?: string icon?: string
} & ( } & Partial<RouterLinkProps>
{ [Width in 'large' | 'medium' | 'small' | 'auto']?: true } | { width?: string } & (PastelProps | ColorProps | DefaultProps)
) & Partial<RouterLinkProps> & (PastelProps | ColorProps | DefaultProps) & RaisedProps & VariantProps>() & RaisedProps
& VariantProps
& WidthProps
>()
const image = typeof props.image === 'string' ? { src: props.image } : props.image const image = typeof props.image === 'string' ? { src: props.image } : props.image
const width =
'width' in props && props.width
? props.width
: 'auto' in props && props.auto
? 'auto'
: 'large' in props && props.large ?
'304px'
: 'medium' in props && props.medium ?
'208px'
: 'small' in props && props.small ?
'min-content'
: '304px'
console.log("WIDTH", width, props)
const isExternalLink = computed(() => { const isExternalLink = computed(() => {
return typeof props.to === 'string' && props.to.startsWith('http') return typeof props.to === 'string' && props.to.startsWith('http')
}) })
const attributes = computed(() => ({
...color(props, props.to ? ['interactive', 'solid'] : []),
...width(props, ['medium'])
}))
</script> </script>
<template> <template>
<Layout stack no-gap <Layout stack no-gap
:class="{ [$style.card]: true, [$style['is-category']]: category }" :class="{ [$style.card]: true, [$style['is-category']]: category }"
v-bind="propsToColor({...(props.to? { interactive: true, solid: true, default: true } : {}), ...props})" v-bind="attributes"
> >
<!-- Link --> <!-- Link -->
@ -68,7 +62,11 @@ const isExternalLink = computed(() => {
<!-- Title --> <!-- Title -->
<component :class="$style.title" :is="typeof category === 'string' ? category : 'h6'">{{ title }}</component> <component :is="typeof category === 'string' ? category : 'h6'"
:class="$style.title"
>
{{ title }}
</component>
<!-- Content --> <!-- Content -->
@ -86,9 +84,10 @@ const isExternalLink = computed(() => {
<slot /> <slot />
</Layout> </Layout>
<Spacer grow :size="0" /> <Spacer grow :size=0 />
<!-- Footer and Action --> <!-- Footer and Action -->
<div v-if="$slots.footer" :class="$style.footer"> <div v-if="$slots.footer" :class="$style.footer">
<slot name="footer" /> <slot name="footer" />
</div> </div>
@ -104,7 +103,6 @@ const isExternalLink = computed(() => {
<style module> <style module>
.card { .card {
--fw-card-width: v-bind(width);
--fw-card-padding: v-bind("props.small ? '16px' : '24px'"); --fw-card-padding: v-bind("props.small ? '16px' : '24px'");
position: relative; position: relative;
@ -116,8 +114,6 @@ const isExternalLink = computed(() => {
border-radius: var(--fw-border-radius); border-radius: var(--fw-border-radius);
font-size: 1rem; font-size: 1rem;
width: var(--fw-card-width);
>.covering { >.covering {
position: absolute; position: absolute;
inset: 0; inset: 0;
@ -125,7 +121,6 @@ const isExternalLink = computed(() => {
&~:is(.title, .content) { &~:is(.title, .content) {
pointer-events:none; pointer-events:none;
} }
&:hover~:is(.content) { &:hover~:is(.content) {
text-decoration: underline text-decoration: underline
} }
@ -151,10 +146,12 @@ const isExternalLink = computed(() => {
>.icon { >.icon {
position: absolute; position: absolute;
top: 22px; top: 20px;
right: 22px; right: 20px;
font-size: 1rem; font-size: 1rem;
pointer-events: none;
} }
&:has(>.image)>.icon { &:has(>.image)>.icon {
top: var(--fw-card-padding); top: var(--fw-card-padding);
@ -175,7 +172,7 @@ const isExternalLink = computed(() => {
overflow: hidden; overflow: hidden;
flex-shrink: 0; flex-shrink: 0;
} }
&:has(>.icon)>.title { &:has(>.icon):not(:has(.image))>.title {
padding-right:calc(var(--fw-card-padding) + 22px); padding-right:calc(var(--fw-card-padding) + 22px);
} }
&.is-category>.title { &.is-category>.title {
@ -189,6 +186,7 @@ const isExternalLink = computed(() => {
>.alert { >.alert {
padding-left: var(--fw-card-padding); padding-left: var(--fw-card-padding);
padding-right: var(--fw-card-padding); padding-right: var(--fw-card-padding);
margin-top: 8px;
} }
>.tags { >.tags {
@ -229,12 +227,9 @@ const isExternalLink = computed(() => {
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0;
border: 8px solid transparent; border: 8px solid transparent;
&:not(:first-child) { &:not(:first-child) {
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
} }
&:not(:last-child) { &:not(:last-child) {
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }

View File

@ -6,13 +6,13 @@ const props = defineProps<{
noWrap?:true noWrap?:true
} }
& { [P in "stack" | "grid" | "flex" | "columns"]?: true | string } & { [P in "stack" | "grid" | "flex" | "columns"]?: true | string }
& { [C in "nav" | "aside" | "header" | "footer" | "main"]?:true }>() & { [C in "nav" | "aside" | "header" | "footer" | "main" | "h1" | "h2" | "h3" | "h4" | "h5"]?:true }>()
const columnWidth = props.columnWidth ?? 320 const columnWidth = props.columnWidth ?? 46
</script> </script>
<template> <template>
<component <component
:is="props.nav ? 'nav' : props.aside ? 'aside' : props.header ? 'header' : props.footer ? 'footer' : props.main ? 'main' : 'div'" :is="props.nav ? 'nav' : props.aside ? 'aside' : props.header ? 'header' : props.footer ? 'footer' : props.main ? 'main' : props.h1? 'h1' : props.h2? 'h2' : props.h3? 'h3' : props.h4? 'h4' : props.h5? 'h5' : 'div'"
:class="[ :class="[
$style.layout, $style.layout,
noGap || $style.gap, noGap || $style.gap,
@ -59,7 +59,7 @@ const columnWidth = props.columnWidth ?? 320
&.grid { &.grid {
display: grid; display: grid;
grid-template-columns: grid-template-columns:
repeat(auto-fit, minmax(min-content, calc(v-bind(columnWidth) * 1px))); repeat(auto-fit, calc(v-bind(columnWidth) * 1px));
grid-auto-flow: row dense; grid-auto-flow: row dense;
place-content: center; /* If the grid has a fixed size smaller than its container, center it */ place-content: center; /* If the grid has a fixed size smaller than its container, center it */
} }

View File

@ -1,31 +1,42 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { type RouterLinkProps, RouterLink } from 'vue-router'
import { type ColorProps, type DefaultProps, type VariantProps, propsToColor } from '~/composables/colors';
const { to, icon, thickWhenActive, round, ...colorProps } = defineProps<{ import { type RouterLinkProps, RouterLink } from 'vue-router'
width?: 'standard' | 'auto' | 'full' import { type ColorProps, type DefaultProps, type VariantProps, color, isNoColors } from '~/composables/colors';
import { type WidthProps, width } from '~/composables/widths'
const props = defineProps<{
alignText?: 'left' | 'center' | 'right' | 'stretch' alignText?: 'left' | 'center' | 'right' | 'stretch'
alignSelf?: 'start' | 'center' | 'end' alignSelf?: 'start' | 'center' | 'end'
thickWhenActive?: true thickWhenActive?: true
thin?: true
icon?: string; icon?: string;
round?: true; round?: true;
} & RouterLinkProps & (ColorProps | DefaultProps) & VariantProps>() } & RouterLinkProps
& (ColorProps | DefaultProps)
& VariantProps
& WidthProps>()
const isExternalLink = computed(() => { const isExternalLink = computed(() => {
return typeof to === 'string' && to.startsWith('http') return typeof props.to === 'string' && props.to.startsWith('http')
}) })
const [fontWeight, activeFontWeight] = thickWhenActive ? [600, 900] : [400, 400] const [fontWeight, activeFontWeight] = props.thickWhenActive ? [600, 900] : [400, 400]
const isIconOnly = computed(() => !!icon) const isIconOnly = computed(() => !!props.icon)
const isSimple = propsToColor(colorProps).class === '' const isSimple = isNoColors(props)
const attributes = computed(() => ({
...color(props, ['interactive']),
...width(props, ['auto'])
}))
</script> </script>
<template> <template>
<a v-if="isExternalLink" <a v-if="isExternalLink"
v-bind="propsToColor({ ...colorProps, interactive: true })" v-bind="attributes"
:class="[ :class="[
$style.link, $style.link,
$style['is-' + width], $style['is-' + width],
@ -46,7 +57,7 @@ const isSimple = propsToColor(colorProps).class === ''
</a> </a>
<RouterLink v-else <RouterLink v-else
:to="to" :to="to"
v-bind="propsToColor({ ...colorProps, interactive: true })" v-bind="color(props, ['interactive'])"
:class="[ :class="[
$style.link, $style.link,
$style['is-' + width], $style['is-' + width],

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { type ColorProps, type DefaultProps, propsToColor } from '~/composables/colors'; import { type ColorProps, type DefaultProps, color } from '~/composables/colors';
import { watchEffect } from 'vue'; import { watchEffect } from 'vue';
import onKeyboardShortcut from '~/composables/onKeyboardShortcut'; import onKeyboardShortcut from '~/composables/onKeyboardShortcut';
@ -34,7 +34,7 @@ onKeyboardShortcut('escape', () => isOpen.value = false)
<div @click.stop <div @click.stop
class="funkwhale modal raised" class="funkwhale modal raised"
:class="[$slots.alert && 'has-alert', overPopover && 'over-popover']" :class="[$slots.alert && 'has-alert', overPopover && 'over-popover']"
v-bind="propsToColor(colorProps)" v-bind="color(colorProps)"
> >
<h2> <h2>
{{ title }} {{ title }}

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { type ColorProps, type PastelProps, propsToColor } from '~/composables/colors' import { type ColorProps, type PastelProps, color } from '~/composables/colors'
const emit = defineEmits<{ const emit = defineEmits<{
click: [event: MouseEvent] click: [event: MouseEvent]
@ -13,7 +13,7 @@ const props = defineProps<PastelProps | ColorProps>()
<template> <template>
<button <button
class="funkwhale pill outline" class="funkwhale pill outline"
v-bind="propsToColor({...props, interactive:true})" v-bind="color(props, ['interactive'])"
@click.stop="handleClick" @click.stop="handleClick"
> >
<div v-if="!!$slots.image" class="pill-image"> <div v-if="!!$slots.image" class="pill-image">

View File

@ -4,7 +4,7 @@ import { whenever, useElementBounding, onClickOutside } from '@vueuse/core'
import { isMobileView, useScreenSize } from '~/composables/screen' import { isMobileView, useScreenSize } from '~/composables/screen'
import { POPOVER_INJECTION_KEY, POPOVER_CONTEXT_INJECTION_KEY } from '~/injection-keys' import { POPOVER_INJECTION_KEY, POPOVER_CONTEXT_INJECTION_KEY } from '~/injection-keys'
import { type ColorProps, type DefaultProps, type RaisedProps, propsToColor } from '~/composables/colors'; import { type ColorProps, type DefaultProps, type RaisedProps, color } from '~/composables/colors';
/* TODO: Basic accessibility /* TODO: Basic accessibility
@ -144,7 +144,7 @@ watch(open, (isOpen) => {
:style="position" :style="position"
:class="{ 'is-mobile': isMobile }" :class="{ 'is-mobile': isMobile }"
class="funkwhale popover" class="funkwhale popover"
v-bind="propsToColor(colorProps)" v-bind="color(colorProps)"
style="display:flex; flex-direction:column;" style="display:flex; flex-direction:column;"
> >
<slot name="items" /> <slot name="items" />

View File

@ -67,6 +67,8 @@
grid-template-columns: auto 1fr auto; grid-template-columns: auto 1fr auto;
gap: 12px; gap: 12px;
grid-column: span 4;
> .activity-image { > .activity-image {
position: relative; position: relative;
width: 40px; width: 40px;

View File

@ -32,28 +32,71 @@ export type RaisedProps =
| { raised?: true } | { raised?: true }
export type Raised = KeysOfUnion<RaisedProps> export type Raised = KeysOfUnion<RaisedProps>
export type Props = /* Props to Classes */
DefaultProps & ColorProps & PastelProps & VariantProps & InteractiveProps & RaisedProps
export type ColorSelector = export type Props =
`${Color | Pastel | Default}${'' | ` ${Variant}${'' | ' interactive'}${'' | ' raised'}`}` (DefaultProps | ColorProps | PastelProps) & VariantProps & InteractiveProps & RaisedProps
export type Key =
KeysOfUnion<Props>
// You can only have one color
const conflicts: Set<Key>[] = [
new Set(['default', 'primary', 'secondary', 'destructive', 'red', 'blue', 'purple', 'green', 'yellow']),
new Set(['solid', 'outline', 'ghost'])
]
const classes = {
default: "default",
primary: "primary",
secondary: "secondary",
destructive: "destructive",
red: "red",
blue: "blue",
purple: "purple",
green: "green",
yellow: "yellow",
solid: "solid",
outline: "outline",
ghost: "ghost",
raised: "raised",
interactive: "interactive"
} satisfies Record<Key, string>
/** /**
* Apply a color class for setting `color`, `background-color` and `border` styles. * @param props A superset of `{ Key? : true }`
* You can override it with `style="..."`. * @returns the number of actually applied classes (if there are no defaults)
*
* @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) => export const isNoColors = (props: Partial<Props>) =>
({ class: color }) color(props).class===''
// Color from Props /**
export const propsToColor = (props: Partial<Props>) => * Add color classes to your component.
({ class: Object.entries(props).filter(([key, value])=>value && key).map(([key,_])=>key).join(" ") }) * Color classes are defined in `colors.scss`. Make sure to implement the correct style there!
*
* (1) Add a subset of `& (DefaultProps | ColorProps | PastelProps) & VariantProps & InteractiveProps & RaisedProps` to your `Props` type
* (2) Call `v-bind="propsToColor(props)"` on your component template
* (3) Now your component accepts color props such as `secondary outline raised`.
*
* @param props Your component's props (or ...rest props if you have destructured them already)
* @returns the corresponding `class` object
*/
export const color = (props: Partial<Props>, defaults?: Key[]) => ({
class:
Object.entries(props).reduce(
((acc, [key, value]) =>
value && key in classes ?
acc.filter(accKey => !conflicts.find(set => set.has(accKey) && set.has(key as Key)))
.concat([key as Key])
: acc
),
defaults || []
).join(' ')
})
type ColorSelector =
`${Color | Pastel | Default}${'' | ` ${Variant}${'' | ' interactive'}${'' | ' raised'}`}`
// Convenience function for applying default colors. Prefer using `color`
export const setColors = (color: ColorSelector) =>
({ class: color })

View File

@ -0,0 +1,51 @@
import type { KeysOfUnion } from "type-fest"
export type WidthProps =
| { minContent?: true }
| { tiny?: true }
| { buttonWidth?: true }
| { small?: true }
| { medium?: true }
| { auto?: true }
| { full?: true }
export type Key = KeysOfUnion<WidthProps>
const styles : Record<Key, string> = {
minContent: 'width: min-content;',
tiny: "width: 124px; grid-column: span 2;",
buttonWidth: "width: 136px; grid-column: span 2; flex-grow:0;",
small: "width: 202px; grid-column: span 3;",
medium: "width: 280px; grid-column: span 4;",
auto: "width: auto;",
full: "width: auto; grid-column: 1 / -1; align-self: auto;",
};
// All keys are exclusive
const conflicts: Set<Key>[] = [
new Set(Object.keys(styles) as Key[])
]
/**
* Add a width style to your component.
* Widths are designed to work both in a page-grid context and in a flex or normal context.
*
* (1) Add `& WidthProps` to your `Props` type
* (2) Call `v-bind="propsToWidth(props)"` on your component template
* (3) Now your component accepts width props such as `small`, `medium`, `stretch`.
*
* @param props Your component's props (or ...rest props if you have destructured them already)
* @returns the corresponding `style` object
*/
export const width = (props: Partial<WidthProps>, defaults: Key[] = []) => ({
style:
Object.entries(props).reduce(
((acc, [key, value]) =>
value && key in styles ?
acc.filter(accKey => !conflicts.find(set => set.has(accKey) && set.has(key as Key)))
.concat([key as Key])
: acc
),
defaults
).map(key => styles[key])
.join(' ')
})

View File

@ -17,7 +17,7 @@ onMounted(async () => {
<template> <template>
<div class="funkwhale grid"> <div class="funkwhale grid">
<Sidebar/> <Sidebar/>
<RouterView v-bind="color('default solid')" /> <RouterView v-bind="color({}, ['default', 'solid'])" />
<LanguagesModal /> <LanguagesModal />
<ShortcutsModal /> <ShortcutsModal />
</div> </div>

View File

@ -33,7 +33,7 @@ const uploads = useUploadsStore()
</script> </script>
<template> <template>
<Layout aside :class="[$style.sidebar, $style['sticky-content']]" v-bind="color('default solid raised')"> <Layout aside :class="[$style.sidebar, $style['sticky-content']]" v-bind="color({}, ['default', 'solid', 'raised'])">
<Layout flex no-gap header style="justify-content:space-between; padding-right:8px;"> <Layout flex no-gap header style="justify-content:space-between; padding-right:8px;">
<Link to="/" <Link to="/"
:class="$style['logo']" :class="$style['logo']"

View File

@ -45,13 +45,12 @@ const labels = computed(() => ({
<template> <template>
<Popover raised v-model:open="isOpen"> <Popover raised v-model:open="isOpen">
<Button <Button
min-content
@click="isOpen = !isOpen" @click="isOpen = !isOpen"
round round
default default
raised raised
icon=""
ghost ghost
class="is-icon-only"
:ariaPressed="isOpen ? true : undefined" :ariaPressed="isOpen ? true : undefined"
> >
<img <img

View File

@ -29,12 +29,11 @@ const isOpen = computed({
:title="t('components.common.UserMenu.label.language')" :title="t('components.common.UserMenu.label.language')"
v-model="isOpen" v-model="isOpen"
> >
<Layout columns :column-width="140"> <Layout columns :column-width="200">
<Button ghost thin <Button ghost thin small
v-for="(language, key) in SUPPORTED_LOCALES" v-for="(language, key) in SUPPORTED_LOCALES"
width="full"
align-text="left" align-text="left"
:aria-pressed="key===locale" :aria-pressed="key===locale || undefined"
:key="key" :key="key"
@click="setI18nLanguage(key)" @click="setI18nLanguage(key)"
> >

View File

@ -10,15 +10,28 @@ 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. Buttons are UI elements that users can interact with to perform actions. Funkwhale uses buttons in many contexts.
| Prop | Data type | Required? | Default | Description | ```ts
| -------------- | --------- | --------- | ------- | ---------------------------------------------- | {
| `shadow` | Boolean | No | `false` | Whether to render the button with a shadow | alignText?: 'left' | 'center' | 'right'
| `round` | Boolean | No | `false` | Whether to render the button as a round button | alignSelf?: 'start' | 'center' | 'end'
| `icon` | String | No | | The icon attached to the button | thin?: true
| `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] isActive?: boolean
isLoading?: boolean
shadow?: boolean
round?: boolean
icon?: string
onClick?: (...args: any[]) => void | Promise<void>
autofocus? : boolean
ariaPressed? : true
} & (ColorProps | DefaultProps)
& VariantProps
& RaisedProps
& WidthProps
```
## Button colors ## Button colors
@ -307,27 +320,31 @@ Icon buttons shrink down to the icon size if you don't pass any content. If you
```vue-html ```vue-html
<Button icon="bi-three-dots-vertical" /> <Button icon="bi-three-dots-vertical" />
<Button round icon="bi-x" /> <Button round icon="bi-x" />
<Button primary icon="bi-save" width="standard" /> <Button primary icon="bi-save" buttonWidth/>
<Button destructive icon="bi-trash"> <Button destructive icon="bi-trash">
Delete Delete
</Button> </Button>
``` ```
<Layout flex>
<Button icon="bi-three-dots-vertical" /> <Button icon="bi-three-dots-vertical" />
<Button round icon="bi-x" /> <Button round secondary icon="bi-x large" />
<Button primary icon="bi-save" width="standard" /> <Button primary icon="bi-save" buttonWidth/>
<Button destructive icon="bi-trash"> <Button destructive icon="bi-trash">
Delete Delete
</Button> </Button>
</Layout>
## Width and alignment ## Width and alignment
<Layout flex> <Layout flex>
```vue-html ```vue-html
<Button width="auto">·</Button> <Button min-content>·</Button>
<Button width="standard">·</Button> <Button small>·</Button>
<Button width="full">·</Button> <Button buttonWidth>·</Button>
<Button medium>·</Button>
<Button auto>·</Button>
<hr /> <hr />
@ -342,10 +359,12 @@ Icon buttons shrink down to the icon size if you don't pass any content. If you
<Button alignText="right">·</Button> <Button alignText="right">·</Button>
``` ```
<Layout class="preview solid secondary" stack no-gap> <Layout class="preview solid primary" stack no-gap>
<Button width="auto">·</Button> <Button min-content>·</Button>
<Button width="standard">·</Button> <Button tiny>·</Button>
<Button width="full">·</Button> <Button buttonWidth>·</Button>
<Button small>·</Button>
<Button auto>·</Button>
<hr /> <hr />
<Button alignSelf="start">·</Button> <Button alignSelf="start">·</Button>
<Button alignSelf="center">·</Button> <Button alignSelf="center">·</Button>
@ -354,6 +373,5 @@ Icon buttons shrink down to the icon size if you don't pass any content. If you
<Button alignText="left">·</Button> <Button alignText="left">·</Button>
<Button alignText="center">·</Button> <Button alignText="center">·</Button>
<Button alignText="right">·</Button> <Button alignText="right">·</Button>
(Text to stretch the column)
</Layout> </Layout>
</Layout> </Layout>

View File

@ -14,32 +14,33 @@
Funkwhale cards are used to contain textual information, links, and interactive buttons. You can use these to create visually pleasing links to content or to present information. Funkwhale cards are used to contain textual information, links, and interactive buttons. You can use these to create visually pleasing links to content or to present information.
::: details Parameters ::: details Props
#### Props
```ts ```ts
interface Props extends Partial<RouterLinkProps> { {
title: string; title: string
category?: true | "h1" | "h2" | "h3" | "h4" | "h5"; category?: true | "h1" | "h2" | "h3" | "h4" | "h5"
color?: Pastel;
image?: string | { src: string; style?: "withPadding" }; tags?: string[]
tags?: string[]; image?: string | { src: string, style?: "withPadding" }
} icon?: string
} & Partial<RouterLinkProps>
& (PastelProps | ColorProps | DefaultProps)
& RaisedProps
& VariantProps
& WidthProps
``` ```
You have to set a title for the card by passing a `title` prop.
#### Style
- Set `--width` on the element to override the default Card width.
::: :::
<Layout grid :column-width=290> <Layout grid class="preview">
<div style="grid-column: span 5; grid-row: span 2;">
```vue-html ```vue-html
<Card title="For music lovers"> <Card large
title="For music lovers"
>
Access your personal music Access your personal music
collection from anywhere. collection from anywhere.
Funkwhale gives you access to Funkwhale gives you access to
@ -49,11 +50,11 @@ You have to set a title for the card by passing a `title` prop.
</Card> </Card>
``` ```
<div class="preview"> </div>
<Card title="For music lovers">
<Card medium title="For music lovers">
Access your personal music collection from anywhere. Funkwhale gives you access to publication and sharing tools that you can use to promote your content across the web. Access your personal music collection from anywhere. Funkwhale gives you access to publication and sharing tools that you can use to promote your content across the web.
</Card> </Card>
</div>
</Layout> </Layout>
@ -61,56 +62,60 @@ You have to set a title for the card by passing a `title` prop.
Add a `:to` prop, either containing an external link (`"https://..."`) or a Vue Router destination: Add a `:to` prop, either containing an external link (`"https://..."`) or a Vue Router destination:
<Layout flex> <Layout flex class="preview">
```ts ```ts
<Card small title="Link" <Card min-content title="Link"
:to="{name: 'library.albums.detail', params: {id: album.id}}" :to="{name: 'library.albums.detail', params: {id: album.id}}"
/> />
``` ```
<Layout class="preview"> <Card min-content title="Link"
<Card small title="Link"
:to="{name: 'library.albums.detail', params: {id: 1}}" :to="{name: 'library.albums.detail', params: {id: 1}}"
/> />
</Layout> </Layout>
</Layout>
## Card as a Category header ## Card as a Category header
Category cards are basic cards that contain only a title. To create a category card, pass a `category` prop. Category cards are basic cards that contain only a title. To create a category card, pass a `category` prop.
<Layout flex> <Layout flex class="preview">
```vue-html{1,5}
<Card category min-content
title="Good Translations"
/>
```vue-html{1}
<Card category <Card category
title="Example Translations" title="Bad Translations"
/> />
``` ```
<div class="preview"> <Layout stack>
<Card category <Card category min-content
title="Example Translations" title="Good Translations"
/> />
</div> <Card category
title="Bad Translations"
/>
</Layout>
</Layout> </Layout>
## Add an Image ## Add an Image
Pass an image source to the `image` prop or set both `image.src` and `image.style` by passing an object. Pass an image source to the `image` prop or set both `image.src` and `image.style` by passing an object.
<Layout grid> <Layout grid class="preview">
<div style="max-width: 320px;"> <div style="grid-column: span 5; grid-row: span 2">
```vue-html{4,11-12} ```vue-html{3,8-9}
<Card medium <Card
title="For music lovers" title="For music lovers"
image="https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb"> image="https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb">
/> />
<Card
<Card medium
title="For music lovers" title="For music lovers"
:image="{ src:'https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb', :image="{ src:'https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb',
style:'withPadding' }" style:'withPadding' }"
@ -118,40 +123,57 @@ Pass an image source to the `image` prop or set both `image.src` and `image.styl
``` ```
</div> </div>
<Card
<Layout stack class="preview">
<Card medium
title="For music lovers" title="For music lovers"
image="https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb" image="https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb"
/> />
<Card medium <Card
title="For music lovers" title="For music lovers"
:image="{ src:'https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb', :image="{ src:'https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb',
style:'withPadding' }" style:'withPadding' }"
/> />
</Layout> </Layout>
</Layout>
## Add an Icon ## Add an Icon
<Layout columns> <Layout grid class="preview">
```vue-html{4} <div style="grid-column: span 5; grid-row: span 2;">
<Card
```vue-html{4,10}
<Card large
title="Uploading..." title="Uploading..."
image="https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb" image="https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb"
icon="bi-cloud-arrow-up-fill" icon="bi-cloud-arrow-up-fill"
/> />
<Card large
to="./"
title="Find out more"
icon="bi-box-arrow-up-right"
>
Visit the Docs and learn more about developing Funkwhale
</Card>
``` ```
<Layout class="preview"> </div>
<Card
<Card medium
title="Uploading..." title="Uploading..."
image="https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb" image="https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb"
icon="bi-cloud-arrow-up-fill" icon="bi-cloud-arrow-up-fill"
/> />
</Layout>
<Card medium
to="./"
title="Find out more"
icon="bi-box-arrow-up-right">
Visit the Docs and learn more about developing Funkwhale
</Card>
</Layout> </Layout>
You can combine this prop with any other prop configuration. If you combine it with an image, keep an eye on the contrast ratio between the icon color and the image. You can combine this prop with any other prop configuration. If you combine it with an image, keep an eye on the contrast ratio between the icon color and the image.
@ -177,10 +199,10 @@ You can combine this prop with any other prop configuration. If you combine it w
Items in this region are secondary and will be displayed smaller than the main content. Items in this region are secondary and will be displayed smaller than the main content.
```vue-html{3-9} ```vue-html{3-9}
<Card title="My items"> <Card large title="My items">
<template #alert> There are no items in this list </template> <template #alert> There are no items in this list </template>
<template #footer> <template #footer>
<Button variant="outline" icon="bi-upload" @click="alert('Uploaded. Press OK!')"> <Button outline icon="bi-upload" @click="alert('Uploaded. Press OK!')">
Upload Upload
</Button> </Button>
<Spacer style="flex-grow: 1" /> <Spacer style="flex-grow: 1" />
@ -190,13 +212,13 @@ Items in this region are secondary and will be displayed smaller than the main c
``` ```
<div class="preview"> <div class="preview">
<Card title="My items"> <Card medium title="My items">
<template #alert>There are no items in this list <template #alert>There are no items in this list
</template> </template>
<template #footer> <template #footer>
<Button variant="outline" icon="bi-upload" @click="alert('Uploaded. Press OK!')">Upload</Button> <Button outline icon="bi-upload" @click="alert('Uploaded. Press OK!')">Upload</Button>
<Spacer style="flex-grow: 1" /> <Spacer style="flex-grow: 1" />
<OptionsButton /> <OptionsButton />
</template> </template>
@ -209,20 +231,24 @@ Items in this region are secondary and will be displayed smaller than the main c
Large Buttons or links at the bottom edge of the card serve as Call-to-Actions (CTA). Large Buttons or links at the bottom edge of the card serve as Call-to-Actions (CTA).
```vue-html{3-6} ```vue-html{3-6}
<Card title="Join an existing pod"> <Card large
title="Join an existing pod"
>
The easiest way to get started with Funkwhale is to register an account on a public pod. The easiest way to get started with Funkwhale is to register an account on a public pod.
<template #action> <template #action>
<Button secondary @click="alert('Open the pod picker')">Action! <Button secondary full @click="alert('Open the pod picker')">Action!
</Button> </Button>
</template> </template>
</Card> </Card>
``` ```
<div class="preview"> <div class="preview">
<Card title="Join an existing pod"> <Card medium
title="Join an existing pod"
>
The easiest way to get started with Funkwhale is to register an account on a public pod. The easiest way to get started with Funkwhale is to register an account on a public pod.
<template #action> <template #action>
<Button secondary @click="alert('Open the pod picker')">Action! <Button secondary full @click="alert('Open the pod picker')">Action!
</Button> </Button>
</template> </template>
</Card> </Card>
@ -245,14 +271,21 @@ If there are multiple actions, they will be presented in a row:
``` ```
<div class="preview"> <div class="preview">
<Card title="Creating a new playlist..."> <Card full title="Creating a new playlist...">
All items have been assimilated. Ready to go! All items have been assimilated. Ready to go!
<template #action> <template #action>
<Button secondary ghost style="width:50%; justify-content: flex-start;" icon="bi-chevron-left"> <Button secondary ghost min-content
Back alignText="left"
icon="bi-chevron-left"
>
Items
</Button> </Button>
<Button style="width:50%" primary @click="alert('Yay')"> <Spacer h />
Next: Pick a color <Button primary @click="alert('Yay')">
OK
</Button>
<Button destructive @click="alert('Yay')">
Cancel
</Button> </Button>
</template> </template>
</Card> </Card>
@ -272,8 +305,12 @@ You can include tags on a card by adding a list of `tags`. These are rendered as
``` ```
<div class="preview"> <div class="preview">
<Card <Card medium
title="For music lovers" title="For music lovers"
:tags="['rock', 'folk', 'punk']"> :tags="['rock', 'folk', 'punk']">
Access your personal music collection from anywhere. Funkwhale gives you access to publication and sharing tools that you can use to promote your content across the web. Access your personal music collection from anywhere. Funkwhale gives you access to publication and sharing tools that you can use to promote your content across the web.
</Card></div> </Card></div>
### Sizes
`large` (304px), `medium` (208px), `auto`, `small`

View File

@ -76,10 +76,10 @@ const noGap = ref(true);
--- ---
<Layout flex :no-gap="noGap || undefined"> <Layout flex :no-gap="noGap || undefined">
<Card title="A" small /> <Card title="A" tiny />
<Card title="B" small /> <Card title="B" tiny />
<Card title="C" small /> <Card title="C" tiny />
<Card title="D" small /> <Card title="D" tiny />
</Layout> </Layout>
</div> </div>

View File

@ -30,7 +30,7 @@ Let items flow like words on a printed newspaper, Great for very long lists of b
Normal paragraph. Text flows like in a newspaper. Set the column width in px. Columns will be equally distributed over the width of the container. Normal paragraph. Text flows like in a newspaper. Set the column width in px. Columns will be equally distributed over the width of the container.
<Card small title="Cards...">...break over columns...</Card> <Card min-content title="Cards...">...break over columns...</Card>
<Button icon="bi-star"/> <Button icon="bi-star"/>
<Button icon="bi-star"/> <Button icon="bi-star"/>
@ -50,7 +50,7 @@ Normal paragraph. Text flows like in a newspaper. Set the column width in px. Co
--- ---
<Card small title="Cards...">...break over columns...</Card> <Card min-content title="Cards...">...break over columns...</Card>
--- ---

View File

@ -30,14 +30,14 @@ You can either specify the column width (default: 320px) or set the desired num
:style="`width:${2 * 90 + 32}px`" :style="`width:${2 * 90 + 32}px`"
> >
<Alert yellow /> <Alert yellow />
<Card small title="brown" /> <Card min-content title="brown" />
<Alert blue /> <Alert blue />
</Layout> </Layout>
``` ```
<Layout grid :column-width="90" class="preview" :style="`width:${2 * 90 + 32}px`"> <Layout grid :column-width="90" class="preview" :style="`width:${2 * 90 + 32}px`">
<Alert yellow /> <Alert yellow />
<Card small title="brown" /> <Card min-content title="brown" />
<Alert blue /> <Alert blue />
</Layout> </Layout>

View File

@ -159,10 +159,10 @@ const size = ref(1);
<Layout flex style="align-items:flex-start"> <Layout flex style="align-items:flex-start">
<Input v-model="size" type="range" /> <Input v-model="size" type="range" />
<Card small title="h"> <Card min-content title="h">
<Spacer h :size="size * 4" style="border:5px dashed;" /> <Spacer h :size="size * 4" style="border:5px dashed;" />
</Card> </Card>
<Card small title="v"> <Card min-content title="v">
<Spacer v :size="size * 4" style="border:5px dashed;" /> <Spacer v :size="size * 4" style="border:5px dashed;" />
</Card> </Card>
</Layout> </Layout>

BIN
front/ui-docs/image-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { color } from "~/composables/colors.ts" import { color, setColors } from "~/composables/colors.ts"
import { useRoute } from "vue-router" import { useRoute } from "vue-router"
import Button from "~/components/ui/Button.vue" import Button from "~/components/ui/Button.vue"
@ -59,16 +59,37 @@ import { color } from "~/composables/colors.ts";
</script> </script>
<template> <template>
<div v-bind="color('primary solid interactive raised')" /> <div v-bind="setColors('primary solid interactive raised')" />
</template> </template>
``` ```
<div class="preview"> <div class="preview">
<div :class="$style.swatch" v-bind="color('primary solid interactive raised')" /> <div :class="$style.swatch" v-bind="setColors('primary solid interactive raised')" />
</div> </div>
</Layout> </Layout>
## Make your component accept color props
`color` accepts two parameters. The first is your props object, the second is a list of defaults.
In this case, if the user of your component doesn't specify any color, it will be 'primary'.
Since the user cannot specify the variant, it will always be 'default'.
```vue
<script setup>
import { type ColorProps, color } from "~/composables/colors.ts";
const props = defineProps<{
...
} & ColorProps>()
</script>
<template>
<div v-bind="color(props, ['primary', 'solid'])" />
</template>
```
## Base colors ## Base colors
Keep most of the screen neutral. Secondary surfaces indicate actionability. Use them sparingly. Primary and destructive surfaces immediately catch the eye. Use them only once or twice per screen. Keep most of the screen neutral. Secondary surfaces indicate actionability. Use them sparingly. Primary and destructive surfaces immediately catch the eye. Use them only once or twice per screen.
@ -79,22 +100,22 @@ Use neutral colors for non-interactive surfaces
<Layout flex> <Layout flex>
<div class="force-dark-theme"> <div class="force-dark-theme">
<div v-bind="color('default solid')"> <div v-bind="setColors('default solid')">
<div :class="$style.swatch" v-bind="color('default solid')" /> <div :class="$style.swatch" v-bind="setColors('default solid')" />
<div :class="[$style.swatch, $style.deemphasized]" v-bind="color('default solid interactive')" /> <div :class="[$style.swatch, $style.deemphasized]" v-bind="setColors('default solid interactive')" />
</div><div v-bind="color('default solid raised')"> </div><div v-bind="setColors('default solid raised')">
<div :class="$style.swatch" v-bind="color('default solid raised')" /> <div :class="$style.swatch" v-bind="setColors('default solid raised')" />
<div :class="[$style.swatch, $style.deemphasized]" v-bind="color('default solid raised interactive')" /> <div :class="[$style.swatch, $style.deemphasized]" v-bind="setColors('default solid raised interactive')" />
</div> </div>
</div> </div>
<div class="force-light-theme"> <div class="force-light-theme">
<div v-bind="color('default solid')"> <div v-bind="setColors('default solid')">
<div :class="$style.swatch" v-bind="color('default solid')" /> <div :class="$style.swatch" v-bind="setColors('default solid')" />
<div :class="[$style.swatch, $style.deemphasized]" v-bind="color('default solid interactive')" /> <div :class="[$style.swatch, $style.deemphasized]" v-bind="setColors('default solid interactive')" />
</div><div v-bind="color('default solid raised')"> </div><div v-bind="setColors('default solid raised')">
<div :class="$style.swatch" v-bind="color('default solid raised')" /> <div :class="$style.swatch" v-bind="setColors('default solid raised')" />
<div :class="[$style.swatch, $style.deemphasized]" v-bind="color('default solid raised interactive')" /> <div :class="[$style.swatch, $style.deemphasized]" v-bind="setColors('default solid raised interactive')" />
</div> </div>
</div> </div>
</Layout> </Layout>
@ -107,22 +128,22 @@ Only use for at most one call-to-action on a screen
<Layout flex> <Layout flex>
<div class="force-dark-theme"> <div class="force-dark-theme">
<div v-bind="color('default solid')"> <div v-bind="setColors('default solid')">
<div :class="[$style.swatch, $style.deemphasized]" v-bind="color('primary solid')" /> <div :class="[$style.swatch, $style.deemphasized]" v-bind="setColors('primary solid')" />
<div :class="[$style.swatch]" v-bind="color('primary solid interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('primary solid interactive')" />
</div><div v-bind="color('default solid raised')"> </div><div v-bind="setColors('default solid raised')">
<div :class="[$style.swatch, $style.deemphasized]" v-bind="color('primary solid raised')" /> <div :class="[$style.swatch, $style.deemphasized]" v-bind="setColors('primary solid raised')" />
<div :class="[$style.swatch]" v-bind="color('primary solid raised interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('primary solid raised interactive')" />
</div> </div>
</div> </div>
<div class="force-light-theme"> <div class="force-light-theme">
<div v-bind="color('default solid')"> <div v-bind="setColors('default solid')">
<div :class="[$style.swatch, $style.deemphasized]" v-bind="color('primary solid')" /> <div :class="[$style.swatch, $style.deemphasized]" v-bind="setColors('primary solid')" />
<div :class="[$style.swatch]" v-bind="color('primary solid interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('primary solid interactive')" />
</div><div v-bind="color('default solid raised')"> </div><div v-bind="setColors('default solid raised')">
<div :class="[$style.swatch, $style.deemphasized]" v-bind="color('primary solid raised')" /> <div :class="[$style.swatch, $style.deemphasized]" v-bind="setColors('primary solid raised')" />
<div :class="[$style.swatch]" v-bind="color('primary solid raised interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('primary solid raised interactive')" />
</div> </div>
</div> </div>
</Layout> </Layout>
@ -133,22 +154,22 @@ Use for interactive items and non-permanent surfaces such as menus and modals
<Layout flex> <Layout flex>
<div class="force-dark-theme"> <div class="force-dark-theme">
<div v-bind="color('default solid')"> <div v-bind="setColors('default solid')">
<div :class="$style.swatch" v-bind="color('secondary solid')" /> <div :class="$style.swatch" v-bind="setColors('secondary solid')" />
<div :class="[$style.swatch]" v-bind="color('secondary solid interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('secondary solid interactive')" />
</div><div v-bind="color('default solid raised')"> </div><div v-bind="setColors('default solid raised')">
<div :class="$style.swatch" v-bind="color('secondary solid raised')" /> <div :class="$style.swatch" v-bind="setColors('secondary solid raised')" />
<div :class="[$style.swatch]" v-bind="color('secondary solid raised interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('secondary solid raised interactive')" />
</div> </div>
</div> </div>
<div class="force-light-theme"> <div class="force-light-theme">
<div v-bind="color('default solid')"> <div v-bind="setColors('default solid')">
<div :class="$style.swatch" v-bind="color('secondary solid')" /> <div :class="$style.swatch" v-bind="setColors('secondary solid')" />
<div :class="[$style.swatch]" v-bind="color('secondary solid interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('secondary solid interactive')" />
</div><div v-bind="color('default solid raised')"> </div><div v-bind="setColors('default solid raised')">
<div :class="$style.swatch" v-bind="color('secondary solid raised')" /> <div :class="$style.swatch" v-bind="setColors('secondary solid raised')" />
<div :class="[$style.swatch]" v-bind="color('secondary solid raised interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('secondary solid raised interactive')" />
</div> </div>
</div> </div>
</Layout> </Layout>
@ -159,22 +180,22 @@ Use for dangerous actions
<Layout flex> <Layout flex>
<div class="force-dark-theme"> <div class="force-dark-theme">
<div v-bind="color('default solid')"> <div v-bind="setColors('default solid')">
<div :class="[$style.swatch, $style.deemphasized]" v-bind="color('destructive solid')" /> <div :class="[$style.swatch, $style.deemphasized]" v-bind="setColors('destructive solid')" />
<div :class="[$style.swatch]" v-bind="color('destructive solid interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('destructive solid interactive')" />
</div><div v-bind="color('default solid raised')"> </div><div v-bind="setColors('default solid raised')">
<div :class="[$style.swatch, $style.deemphasized]" v-bind="color('destructive solid raised')" /> <div :class="[$style.swatch, $style.deemphasized]" v-bind="setColors('destructive solid raised')" />
<div :class="[$style.swatch]" v-bind="color('destructive solid raised interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('destructive solid raised interactive')" />
</div> </div>
</div> </div>
<div class="force-light-theme"> <div class="force-light-theme">
<div v-bind="color('default solid')"> <div v-bind="setColors('default solid')">
<div :class="[$style.swatch, $style.deemphasized]" v-bind="color('destructive solid')" /> <div :class="[$style.swatch, $style.deemphasized]" v-bind="setColors('destructive solid')" />
<div :class="[$style.swatch]" v-bind="color('destructive solid interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('destructive solid interactive')" />
</div><div v-bind="color('default solid raised')"> </div><div v-bind="setColors('default solid raised')">
<div :class="[$style.swatch, $style.deemphasized]" v-bind="color('destructive solid raised')" /> <div :class="[$style.swatch, $style.deemphasized]" v-bind="setColors('destructive solid raised')" />
<div :class="[$style.swatch]" v-bind="color('destructive solid raised interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('destructive solid raised interactive')" />
</div> </div>
</div> </div>
</Layout> </Layout>
@ -184,11 +205,11 @@ Use for dangerous actions
Use `Blue`, `Red`, `Purple`, `Green`, `Yellow` for user-defined tags and for friendly messages such as Use `Blue`, `Red`, `Purple`, `Green`, `Yellow` for user-defined tags and for friendly messages such as
Alerts Alerts
<div :class="$style.swatch" v-bind="color('blue solid interactive')" /> <div :class="$style.swatch" v-bind="setColors('blue solid interactive')" />
<div :class="$style.swatch" v-bind="color('red solid interactive')" /> <div :class="$style.swatch" v-bind="setColors('red solid interactive')" />
<div :class="$style.swatch" v-bind="color('purple solid interactive')" /> <div :class="$style.swatch" v-bind="setColors('purple solid interactive')" />
<div :class="$style.swatch" v-bind="color('green solid interactive')" /> <div :class="$style.swatch" v-bind="setColors('green solid interactive')" />
<div :class="$style.swatch" v-bind="color('yellow solid interactive')" /> <div :class="$style.swatch" v-bind="setColors('yellow solid interactive')" />
### Variant ### Variant
@ -198,7 +219,7 @@ You can de-emphasize interactive elements by hiding their background and/or outl
<Button round shadow icon="bi-x" solid /> <Button round shadow icon="bi-x" solid />
<div :class="$style.swatch" v-bind="color('solid raised')"> <div :class="$style.swatch" v-bind="setColors('solid raised')">
<Button round icon="bi-x" ghost /> <Button round icon="bi-x" ghost />
<Spacer /> <Spacer />
<Button round icon="bi-x" outline /> <Button round icon="bi-x" outline />
@ -208,7 +229,7 @@ You can de-emphasize interactive elements by hiding their background and/or outl
<Button round shadow icon="bi-x" primary solid /> <Button round shadow icon="bi-x" primary solid />
<div :class="$style.swatch" v-bind="color('primary solid')"> <div :class="$style.swatch" v-bind="setColors('primary solid')">
<Button round icon="bi-x" primary ghost /> <Button round icon="bi-x" primary ghost />
<Spacer /> <Spacer />
<Button round icon="bi-x" primary outline /> <Button round icon="bi-x" primary outline />
@ -223,91 +244,88 @@ Space out interactive surfaces.
<Layout> <Layout>
<div class="force-light-theme"> <div class="force-light-theme">
<Layout flex> <Layout flex>
<div> <div>
<div v-bind="color('default solid')"> <div v-bind="setColors('default solid')">
<div :class="[$style.swatch]" v-bind="color('solid')" /> <div :class="[$style.swatch]" v-bind="setColors('solid')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive')" disabled/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" disabled/>
<div :class="[$style.swatch]" v-bind="color('solid interactive')" aria-pressed="true"/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" aria-pressed />
</div> </div>
<div v-bind="color('secondary solid')"> <div v-bind="setColors('secondary solid')">
<div :class="[$style.swatch]" v-bind="color('solid')" /> <div :class="[$style.swatch]" v-bind="setColors('solid')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive')" disabled/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" disabled/>
<div :class="[$style.swatch]" v-bind="color('solid interactive')" aria-pressed="true"/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" aria-pressed />
</div> </div>
<div v-bind="color('primary solid')"> <div v-bind="setColors('primary solid')">
<div :class="[$style.swatch]" v-bind="color('solid')" /> <div :class="[$style.swatch]" v-bind="setColors('solid')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive')" disabled/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" disabled/>
<div :class="[$style.swatch]" v-bind="color('solid interactive')" aria-pressed="true"/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" aria-pressed />
</div> </div>
</div><div> </div><div>
<div v-bind="color('default raised solid')"> <div v-bind="setColors('default raised solid')">
<div :class="[$style.swatch]" v-bind="color('solid raised')" /> <div :class="[$style.swatch]" v-bind="setColors('solid raised')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" /> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" disabled/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" disabled/>
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" aria-pressed="true"/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" aria-pressed />
</div> </div>
<div v-bind="color('secondary raised solid')"> <div v-bind="setColors('secondary raised solid')">
<div :class="[$style.swatch]" v-bind="color('solid raised')" /> <div :class="[$style.swatch]" v-bind="setColors('solid raised')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" /> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" disabled/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" disabled/>
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" aria-pressed="true"/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" aria-pressed />
</div> </div>
<div v-bind="color('primary raised solid')"> <div v-bind="setColors('primary raised solid')">
<div :class="[$style.swatch]" v-bind="color('solid raised')" /> <div :class="[$style.swatch]" v-bind="setColors('solid raised')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" /> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" disabled/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" disabled/>
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" aria-pressed="true"/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" aria-pressed />
</div> </div>
</div> </div>
</Layout> </Layout>
</div> </div>
<div class="force-dark-theme"> <div class="force-dark-theme">
<Layout flex> <Layout flex>
<div> <div>
<div v-bind="color('default solid')"> <div v-bind="setColors('default solid')">
<div :class="[$style.swatch]" v-bind="color('solid')" /> <div :class="[$style.swatch]" v-bind="setColors('solid')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive')" disabled/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" disabled/>
<div :class="[$style.swatch]" v-bind="color('solid interactive')" aria-pressed="true"/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" aria-pressed />
</div> </div>
<div v-bind="color('secondary solid')"> <div v-bind="setColors('secondary solid')">
<div :class="[$style.swatch]" v-bind="color('solid')" /> <div :class="[$style.swatch]" v-bind="setColors('solid')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive')" disabled/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" disabled/>
<div :class="[$style.swatch]" v-bind="color('solid interactive')" aria-pressed="true"/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" aria-pressed />
</div> </div>
<div v-bind="color('primary solid')"> <div v-bind="setColors('primary solid')">
<div :class="[$style.swatch]" v-bind="color('solid')" /> <div :class="[$style.swatch]" v-bind="setColors('solid')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive')" /> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive')" disabled/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" disabled/>
<div :class="[$style.swatch]" v-bind="color('solid interactive')" aria-pressed="true"/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive')" aria-pressed />
</div><br/>Normal </div><br/>Normal
</div><div> </div><div>
<div v-bind="color('default raised solid')"> <div v-bind="setColors('default raised solid')">
<div :class="[$style.swatch]" v-bind="color('solid raised')" /> <div :class="[$style.swatch]" v-bind="setColors('solid raised')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" /> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" disabled/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" disabled/>
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" aria-pressed="true"/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" aria-pressed />
</div> </div>
<div v-bind="color('secondary raised solid')"> <div v-bind="setColors('secondary raised solid')">
<div :class="[$style.swatch]" v-bind="color('solid raised')" /> <div :class="[$style.swatch]" v-bind="setColors('solid raised')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" /> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" disabled/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" disabled/>
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" aria-pressed="true"/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" aria-pressed />
</div> </div>
<div v-bind="color('primary raised solid')"> <div v-bind="setColors('primary raised solid')">
<div :class="[$style.swatch]" v-bind="color('solid raised')" /> <div :class="[$style.swatch]" v-bind="setColors('solid raised')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" /> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" />
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" disabled/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" disabled/>
<div :class="[$style.swatch]" v-bind="color('solid interactive raised')" aria-pressed="true"/> <div :class="[$style.swatch]" v-bind="setColors('solid interactive raised')" aria-pressed />
</div><br/><strong>Raised</strong> </div><br/><strong>Raised</strong>
</div> </div>
</Layout> </Layout>
@ -604,7 +622,7 @@ Here is the meaning the styles should convey:
<Layout flex> <Layout flex>
<Card small title="Default" solid default> <Card min-content title="Default" solid default>
<span> <span>
Inline <Link to=""> Link </Link> and <Link to=""> Link </Link> Inline <Link to=""> Link </Link> and <Link to=""> Link </Link>
</span> </span>
@ -642,11 +660,11 @@ Here is the meaning the styles should convey:
<Button raised ghost> Button raised ghost </Button> <Button raised ghost> Button raised ghost </Button>
<Button raised outline> Button raised outline </Button> <Button raised outline> Button raised outline </Button>
<Button raised solid> Button raised solid </Button> <Button raised solid> Button raised solid </Button>
<Button raised aria-pressed="true"> Button active </Button> <Button raised aria-pressed > Button active </Button>
<Button raised disabled> Button disabled </Button> <Button raised disabled> Button disabled </Button>
</Card> </Card>
<Card small title="Default raised" solid default raised> <Card min-content title="Default raised" solid default raised>
<span> <span>
Inline <Link to=""> Link </Link> and <Link to=""> Link </Link> Inline <Link to=""> Link </Link> and <Link to=""> Link </Link>
</span> </span>
@ -684,11 +702,11 @@ Here is the meaning the styles should convey:
<Button raised ghost> Button raised ghost </Button> <Button raised ghost> Button raised ghost </Button>
<Button raised outline> Button raised outline </Button> <Button raised outline> Button raised outline </Button>
<Button raised solid> Button raised solid </Button> <Button raised solid> Button raised solid </Button>
<Button raised aria-pressed="true"> Button active </Button> <Button raised aria-pressed > Button active </Button>
<Button raised disabled> Button disabled </Button> <Button raised disabled> Button disabled </Button>
</Card> </Card>
<Card small title="Secondary" solid secondary> <Card min-content title="Secondary" solid secondary>
<span> <span>
Inline <Link to=""> Link </Link> and <Link to=""> Link </Link> Inline <Link to=""> Link </Link> and <Link to=""> Link </Link>
</span> </span>
@ -726,11 +744,11 @@ Here is the meaning the styles should convey:
<Button raised ghost> Button raised ghost </Button> <Button raised ghost> Button raised ghost </Button>
<Button raised outline> Button raised outline </Button> <Button raised outline> Button raised outline </Button>
<Button raised solid> Button raised solid </Button> <Button raised solid> Button raised solid </Button>
<Button raised aria-pressed="true"> Button raised active </Button> <Button raised aria-pressed > Button raised active </Button>
<Button raised disabled> Button raised disabled </Button> <Button raised disabled> Button raised disabled </Button>
</Card> </Card>
<Card small title="Secondary raised" solid secondary raised> <Card min-content title="Secondary raised" solid secondary raised>
<span> <span>
Inline <Link to=""> Link </Link> and <Link to=""> Link </Link> Inline <Link to=""> Link </Link> and <Link to=""> Link </Link>
</span> </span>