chore(ui): make imports consistent; modernize `useProps` and `useModel`
WIP: #2355
This commit is contained in:
parent
8d54d7b87a
commit
0e045bda11
|
@ -1,69 +1,61 @@
|
|||
<script setup lang="ts">
|
||||
import { FwOptionsButton, FwPlayButton } from '~/components'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { Track, User } from '~/types/models'
|
||||
import { type Track, type User } from '~/types'
|
||||
|
||||
import OptionsButton from '~/components/ui/button/Options.vue'
|
||||
import PlayButton from '~/components/ui/button/Play.vue'
|
||||
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const play = defineEmit<[track: Track]>()
|
||||
const emit = defineEmits<{ play: [track: Track] }>()
|
||||
|
||||
const track = defineProp<Track>('track', { required: true })
|
||||
const user = defineProp<User>('user', { required: true })
|
||||
const { track, user } = defineProps<{ track: Track, user: User }>()
|
||||
|
||||
const artist_credit = track.artist_credit || []
|
||||
const firstArtist = artist_credit.length > 0 ? artist_credit[0].artist : null
|
||||
|
||||
const profileParams = computed(() => {
|
||||
const [username, domain] = user.value.full_username.split('@')
|
||||
const [username, domain] = user.full_username.split('@')
|
||||
return { username, domain }
|
||||
})
|
||||
|
||||
let navigate = (to: 'track' | 'artist' | 'user') => {}
|
||||
let navigate = (to: 'track' | 'artist' | 'user') => { }
|
||||
|
||||
if (import.meta.env.PROD) {
|
||||
const router = useRouter()
|
||||
navigate = (to: 'track' | 'artist' | 'user') => to === 'track'
|
||||
? router.push({ name: 'library.tracks.detail', params: { id: track.value.id } })
|
||||
? router.push({ name: 'library.tracks.detail', params: { id: track.id } })
|
||||
: to === 'artist'
|
||||
? router.push({ name: 'library.artists.detail', params: { id: track.value.artist.id } })
|
||||
? router.push({ name: 'library.artists.detail', params: { id: firstArtist?.id } /* TODO: Multi-Artist! */ })
|
||||
: router.push({ name: 'profile.full', params: profileParams.value })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="funkwhale activity"
|
||||
@click="navigate('track')"
|
||||
>
|
||||
<div class="funkwhale activity" @click="navigate('track')">
|
||||
<div class="activity-image">
|
||||
<img :src="track.cover.urls.original" />
|
||||
<fw-play-button
|
||||
@play="play(track)"
|
||||
:round="false"
|
||||
:shadow="false"
|
||||
/>
|
||||
<img :src="track.cover?.urls.original" />
|
||||
<PlayButton @play="emit('play', track)" :round="false" :shadow="false" />
|
||||
</div>
|
||||
<div class="activity-content">
|
||||
<div class="track-title">{{ track.name }}</div>
|
||||
<a
|
||||
@click.stop="navigate('artist')"
|
||||
class="funkwhale link artist"
|
||||
>
|
||||
{{ track.artist.name }}
|
||||
<div class="track-title">{{ track.title }}</div>
|
||||
<a @click.stop="navigate('artist')" class="funkwhale link artist">
|
||||
{{ firstArtist?.name /* TODO: Multi-Artist! */ }}
|
||||
</a>
|
||||
<a
|
||||
@click.stop="navigate('user')"
|
||||
class="funkwhale link user"
|
||||
>
|
||||
{{ t('vui.by-user', { username: user.username}) }}
|
||||
<a @click.stop="navigate('user')" class="funkwhale link user">
|
||||
{{ t('vui.by-user', { username: user.username }) }}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<fw-options-button />
|
||||
<OptionsButton />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss'
|
||||
@import './activity.scss'
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { useColorOrPastel } from '~/composables/colors'
|
||||
import type { PastelProps } from '~/types/common-props'
|
||||
import { useColorOrPastel, type PastelProps } from '~/composables/colors'
|
||||
|
||||
const props = defineProps<PastelProps>()
|
||||
const color = useColorOrPastel(() => props.color, 'blue')
|
||||
|
@ -20,5 +19,5 @@ const color = useColorOrPastel(() => props.color, 'blue')
|
|||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss'
|
||||
@import './alert.scss'
|
||||
</style>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import { useColor } from '~/composables/colors'
|
||||
import { FwLoader } from '~/components'
|
||||
import { ref, computed, useSlots } from 'vue'
|
||||
import type { ColorProps } from '~/types/common-props'
|
||||
import { useColor, type ColorProps } from '~/composables/colors'
|
||||
|
||||
import Loader from '~/components/ui/Loader.vue'
|
||||
|
||||
interface Props {
|
||||
variant?: 'solid' | 'outline' | 'ghost'
|
||||
|
@ -41,40 +41,30 @@ const click = async (...args: any[]) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<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"
|
||||
>
|
||||
<i
|
||||
v-if="icon"
|
||||
:class="['bi', icon]"
|
||||
/>
|
||||
<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">
|
||||
<i v-if="icon" :class="['bi', icon]" />
|
||||
|
||||
<span>
|
||||
<slot />
|
||||
</span>
|
||||
|
||||
<fw-loader
|
||||
v-if="isLoading"
|
||||
:container="false"
|
||||
/>
|
||||
<Loader v-if="isLoading" :container="false" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss'
|
||||
@import './button.scss'
|
||||
</style>
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<script setup lang="ts">
|
||||
import { useCssModule } from 'vue'
|
||||
import { FwPill } from '~/components'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { type RouterLinkProps, RouterLink } from 'vue-router';
|
||||
import type { Pastel } from '~/types/common-props';
|
||||
import Layout from '../layout/Layout.vue';
|
||||
import Spacer from '../layout/Spacer.vue';
|
||||
import { type Pastel } from '~/composables/colors';
|
||||
|
||||
import Pill from './Pill.vue'
|
||||
import Alert from './Alert.vue'
|
||||
import Layout from './Layout.vue';
|
||||
import Spacer from './layout/Spacer.vue';
|
||||
|
||||
interface Props extends Partial<RouterLinkProps> {
|
||||
title: string
|
||||
|
@ -161,14 +163,14 @@ const c = useCssModule()
|
|||
<!-- Content -->
|
||||
<component :class="c.title" :is="typeof category === 'string' ? category : 'h6'">{{ title }}</component>
|
||||
|
||||
<fw-alert v-if="$slots.alert" :class="c.alert">
|
||||
<Alert v-if="$slots.alert" :class="c.alert">
|
||||
<slot name="alert" />
|
||||
</fw-alert>
|
||||
</Alert>
|
||||
|
||||
<div v-if="tags" :class="c.tags">
|
||||
<fw-pill v-for="tag in tags" :key="tag">
|
||||
<Pill v-for="tag in tags" :key="tag">
|
||||
#{{ tag }}
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
</div>
|
||||
|
||||
<div v-if="$slots.default" :class="c.content">
|
||||
|
|
|
@ -1,28 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const icon = defineProp<string>()
|
||||
const placeholder = defineProp<string>()
|
||||
const { icon, placeholder } = defineProps<{ icon?: string, placeholder:string }>()
|
||||
|
||||
const modelValue = defineModel<string>({
|
||||
required: true
|
||||
})
|
||||
const model = defineModel<string|number>()
|
||||
|
||||
const input = ref()
|
||||
|
||||
// TODO(A11y): Add `inputmode="numeric" pattern="[0-9]*"` to input if model type is number:
|
||||
// https://technology.blog.gov.uk/2020/02/24/why-the-gov-uk-design-system-team-changed-the-input-type-for-numbers/
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<label
|
||||
:class="{ 'has-icon': !!icon }"
|
||||
class="funkwhale input"
|
||||
@click="input.focus()"
|
||||
>
|
||||
<div v-if="icon" class="prefix">
|
||||
<i :class="['bi', icon]" />
|
||||
</div>
|
||||
<input
|
||||
v-bind="$attrs"
|
||||
v-model="modelValue"
|
||||
v-model="model"
|
||||
ref="input"
|
||||
:placeholder="placeholder"
|
||||
@click.stop
|
||||
|
@ -30,9 +29,9 @@ const input = ref()
|
|||
<div v-if="$slots['input-right']" class="input-right">
|
||||
<slot name="input-right" />
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss'
|
||||
@import './input.scss'
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
const container = defineProp('container', { default: true })
|
||||
const { container = true } = defineProps<{ container?: boolean }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -15,5 +15,5 @@ const container = defineProp('container', { default: true })
|
|||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss'
|
||||
@import './loader.scss'
|
||||
</style>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { FwSanitizedHtml } from '~/components'
|
||||
|
||||
import { char, createRegExp, exactly, global, oneOrMore, word } from 'magic-regexp/further-magic'
|
||||
|
||||
import showdown from 'showdown'
|
||||
|
||||
import SanitizedHtml from './SanitizedHtml.vue'
|
||||
|
||||
interface Props {
|
||||
md: string
|
||||
}
|
||||
|
@ -89,5 +88,5 @@ const html = computed(() => markdown.makeHtml(props.md))
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<fw-sanitized-html :html="html" />
|
||||
<SanitizedHtml :html="html" />
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
const title = defineProp<string>('title', { required: true })
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
|
||||
const { title } = defineProps<{ title:string }>()
|
||||
const open = defineModel<boolean>({ required: true })
|
||||
</script>
|
||||
|
||||
|
@ -10,7 +12,7 @@ const open = defineModel<boolean>({ required: true })
|
|||
<div @click.stop class="funkwhale modal" :class="$slots.alert && 'has-alert'" >
|
||||
<h2>
|
||||
{{ title }}
|
||||
<FwButton icon="bi-x-lg" color="secondary" variant="ghost" @click="open = false" />
|
||||
<Button icon="bi-x-lg" color="secondary" variant="ghost" @click="open = false" />
|
||||
</h2>
|
||||
|
||||
<div class="modal-content">
|
||||
|
@ -34,5 +36,5 @@ const open = defineModel<boolean>({ required: true })
|
|||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss'
|
||||
@import './modal.scss'
|
||||
</style>
|
||||
|
|
|
@ -5,12 +5,15 @@ import { ref, computed } from 'vue'
|
|||
import { isMobileView } from '~/composables/screen'
|
||||
import { preventNonNumeric } from '~/utils/event-validators'
|
||||
|
||||
import Button from './Button.vue'
|
||||
import Input from './Input.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const pages = defineProp<number>('pages', {
|
||||
required: true,
|
||||
validator: (value: number) => value > 0
|
||||
})
|
||||
type NonnegativeInteger<T extends number> =
|
||||
`${T}` extends `-${any}` | `${any}.${any}` ? never : T
|
||||
|
||||
const { pages } = defineProps<{ pages: NonnegativeInteger<number> }>()
|
||||
|
||||
const page = defineModel<number>('page', {
|
||||
required: true,
|
||||
|
@ -23,18 +26,18 @@ const range = (start: number, end: number) => Array.from({ length: end - start +
|
|||
|
||||
const renderPages = computed(() => {
|
||||
const start = range(2, 5)
|
||||
const end = range(pages.value - 4, pages.value - 1)
|
||||
const end = range(pages - 4, pages - 1)
|
||||
|
||||
const pagesArray = [1]
|
||||
|
||||
if (page.value < 5) pagesArray.push(...start)
|
||||
if (page.value >= 5 && page.value <= pages.value - 4) {
|
||||
if (page.value >= 5 && page.value <= pages - 4) {
|
||||
pagesArray.push(page.value - 1)
|
||||
pagesArray.push(page.value)
|
||||
pagesArray.push(page.value + 1)
|
||||
}
|
||||
if (page.value > pages.value - 4) pagesArray.push(...end)
|
||||
pagesArray.push(pages.value)
|
||||
if (page.value > pages - 4) pagesArray.push(...end)
|
||||
pagesArray.push(pages)
|
||||
|
||||
return pagesArray.filter((page, index, pages) => pages.indexOf(page) === index)
|
||||
})
|
||||
|
@ -45,7 +48,7 @@ const isSmall = isMobileView(width)
|
|||
|
||||
const setPage = () => {
|
||||
if (goTo.value == null) return
|
||||
page.value = Math.min(pages.value, Math.max(1, goTo.value))
|
||||
page.value = Math.min(pages, Math.max(1, goTo.value))
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -54,65 +57,66 @@ const setPage = () => {
|
|||
class="funkwhale pagination" role="navigation">
|
||||
<ul class="pages">
|
||||
<li>
|
||||
<fw-button @click="page -= 1" :disabled="page <= 1" :aria-label="t('vui.aria.pagination.gotoPrevious')"
|
||||
<Button @click="page -= 1" :disabled="page <= 1" :aria-label="t('vui.aria.pagination.gotoPrevious')"
|
||||
color="secondary" outline>
|
||||
<i class="bi bi-chevron-left" />
|
||||
<span v-if="!isSmall">{{ t('vui.pagination.previous') }}</span>
|
||||
</fw-button>
|
||||
</Button>
|
||||
</li>
|
||||
|
||||
<template v-if="!isSmall">
|
||||
<template v-for="(i, index) in renderPages" :key="i">
|
||||
<li>
|
||||
<fw-button v-if="i <= pages && i > 0 && pages > 3" @click="page = i" color="secondary"
|
||||
<Button v-if="i <= pages && i > 0 && pages > 3" @click="page = i" color="secondary"
|
||||
:aria-label="page !== i ? t('vui.aria.pagination.gotoPage', i) : t('vui.aria.pagination.currentPage', page)"
|
||||
:outline="page !== i">
|
||||
{{ i }}
|
||||
</fw-button>
|
||||
</Button>
|
||||
</li>
|
||||
<li v-if="i + 1 < renderPages[index + 1]">…</li>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<li>
|
||||
<fw-button @click="page = 1" color="secondary"
|
||||
<Button @click="page = 1" color="secondary"
|
||||
:aria-label="page !== 1 ? t('vui.aria.pagination.gotoPage', page) : t('vui.aria.pagination.currentPage', page)"
|
||||
:outline="page !== 1">
|
||||
1
|
||||
</fw-button>
|
||||
</Button>
|
||||
</li>
|
||||
<li v-if="page === 1 || page === pages">…</li>
|
||||
<li v-else>
|
||||
<fw-button color="secondary" :aria-label="t('vui.aria.pagination.currentPage', page)" aria-current="true">
|
||||
<Button color="secondary" :aria-label="t('vui.aria.pagination.currentPage', page)" aria-current="true">
|
||||
{{ page }}
|
||||
</fw-button>
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
<fw-button @click="page = pages" color="secondary"
|
||||
<Button @click="page = pages" color="secondary"
|
||||
:aria-label="page !== pages ? t('vui.aria.pagination.gotoPage', page) : t('vui.aria.pagination.currentPage', page)"
|
||||
:outline="page !== pages">
|
||||
{{ pages }}
|
||||
</fw-button>
|
||||
</Button>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<li>
|
||||
<fw-button @click="page += 1" :disabled="page >= pages" :aria-label="t('vui.aria.pagination.gotoNext')"
|
||||
<Button @click="page += 1" :disabled="page >= pages" :aria-label="t('vui.aria.pagination.gotoNext')"
|
||||
color="secondary" outline>
|
||||
<span v-if="!isSmall">{{ t('vui.pagination.next') }}</span>
|
||||
<i class="bi bi-chevron-right" />
|
||||
</fw-button>
|
||||
</Button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="goto">
|
||||
{{ t('vui.go-to') }}
|
||||
<fw-input :placeholder="page.toString()" @keyup.enter="setPage" @keydown="preventNonNumeric"
|
||||
<Input :placeholder="page.toString()" @keyup.enter="setPage" @keydown="preventNonNumeric"
|
||||
inputmode="numeric" pattern="[0-9]* // TODO: Move number input functionality into `Input` component"
|
||||
v-model.number="goTo" />
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss'
|
||||
@import './pagination.scss'
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { useColorOrPastel } from '~/composables/colors'
|
||||
import type { ColorProps, PastelProps } from '~/types/common-props'
|
||||
import { useColorOrPastel, type ColorProps, type PastelProps } from '~/composables/colors'
|
||||
|
||||
const props = defineProps<ColorProps | PastelProps>()
|
||||
const color = useColorOrPastel(() => props.color, 'secondary')
|
||||
|
@ -18,5 +17,5 @@ const color = useColorOrPastel(() => props.color, 'secondary')
|
|||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss';
|
||||
@import './pill.scss';
|
||||
</style>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, inject, provide, shallowReactive, watch, onScopeDispose } from 'vue'
|
||||
import { whenever, useElementBounding, onClickOutside } from '@vueuse/core'
|
||||
import { POPOVER_INJECTION_KEY, POPOVER_CONTEXT_INJECTION_KEY } from '~/injection-keys'
|
||||
|
||||
import { isMobileView, useScreenSize } from '~/composables/screen'
|
||||
import { POPOVER_INJECTION_KEY, POPOVER_CONTEXT_INJECTION_KEY } from '~/injection-keys'
|
||||
|
||||
const open = defineModel('open', { default: false })
|
||||
const positioning = defineProp<'horizontal' | 'vertical'>('positioning', { default: 'vertical' })
|
||||
const { positioning = 'vertical' } = defineProps<{ positioning?:'horizontal' | 'vertical' }>()
|
||||
|
||||
// Template refs
|
||||
const popover = ref()
|
||||
|
@ -36,7 +37,7 @@ whenever(open, update, { immediate: true })
|
|||
|
||||
const { width: screenWidth, height: screenHeight } = useScreenSize()
|
||||
const position = computed(() => {
|
||||
if (positioning.value === 'vertical' || isMobile.value) {
|
||||
if (positioning === 'vertical' || isMobile.value) {
|
||||
let offsetTop = top.value + height.value
|
||||
if (offsetTop + popoverHeight.value > screenHeight.value) {
|
||||
offsetTop -= popoverHeight.value + height.value
|
||||
|
@ -131,5 +132,5 @@ watch(open, (isOpen) => {
|
|||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss'
|
||||
@import './popover.scss'
|
||||
</style>
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import DOMPurify from 'dompurify'
|
||||
import { computed, h } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const as = defineProp<string>('as', { default: 'div' })
|
||||
const rawHtml = defineProp<string>('html', { required: true })
|
||||
const { as = 'div', html:rawHtml } = defineProps<{ as?:string, html:string }>()
|
||||
|
||||
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
|
||||
// set all elements owning target to target=_blank
|
||||
|
@ -12,6 +11,9 @@ DOMPurify.addHook('afterSanitizeAttributes', (node) => {
|
|||
}
|
||||
})
|
||||
|
||||
const html = computed(() => DOMPurify.sanitize(rawHtml.value))
|
||||
defineRender(() => h(as.value, { innerHTML: html.value }))
|
||||
const html = computed(() => DOMPurify.sanitize(rawHtml))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="as" v-html="html" />
|
||||
</template>
|
||||
|
|
|
@ -3,18 +3,17 @@ import { TABS_INJECTION_KEY } from '~/injection-keys'
|
|||
import { whenever } from '@vueuse/core'
|
||||
import { inject, ref } from 'vue'
|
||||
|
||||
const title = defineProp<string>('title', { required: true })
|
||||
const icon = defineProp<string|undefined>('icon')
|
||||
const { title, icon } = defineProps<{ title:string, icon?:string }>()
|
||||
|
||||
const { currentTab, tabs, icons } = inject(TABS_INJECTION_KEY, {
|
||||
currentTab: ref(title.value),
|
||||
currentTab: ref(title),
|
||||
tabs: [],
|
||||
icons: [],
|
||||
})
|
||||
|
||||
whenever(() => !tabs.includes(title.value), () => {
|
||||
tabs.push(title.value)
|
||||
icons.push(icon.value)
|
||||
whenever(() => !tabs.includes(title), () => {
|
||||
tabs.push(title)
|
||||
icons.push(icon)
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
|
|
|
@ -41,5 +41,5 @@ watch(() => tabs.length, (to, from) => {
|
|||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss'
|
||||
@import './tabs.scss'
|
||||
</style>
|
||||
|
|
|
@ -1,22 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import { useTextareaAutosize, computedWithControl, useManualRefHistory, watchDebounced } from '@vueuse/core'
|
||||
import { FwButton } from '~/components'
|
||||
import { nextTick, computed, ref, type ComputedRef } from 'vue'
|
||||
import { useTextareaAutosize, computedWithControl, useManualRefHistory, watchDebounced } from '@vueuse/core'
|
||||
|
||||
const max = defineProp<number>('max', {
|
||||
default: Infinity
|
||||
})
|
||||
import Button from './Button.vue'
|
||||
import Markdown from './Markdown.vue'
|
||||
|
||||
const placeholder = defineProp<string>('placeholder', {
|
||||
default: ''
|
||||
})
|
||||
const { max=Infinity, placeholder='' } = defineProps<{ max?:number,placeholder?:string }>()
|
||||
|
||||
const { modelValue: value } = defineModels<{
|
||||
modelValue: string
|
||||
}>()
|
||||
const model = defineModel<string>({ default: '' })
|
||||
|
||||
const { undo, redo, commit: commitHistory, last } = useManualRefHistory(value)
|
||||
const { textarea, triggerResize } = useTextareaAutosize({ input: value })
|
||||
const { undo, redo, commit: commitHistory, last } = useManualRefHistory(model)
|
||||
const { textarea, triggerResize } = useTextareaAutosize({ input: model })
|
||||
|
||||
const commit = () => {
|
||||
triggerResize()
|
||||
|
@ -25,28 +19,28 @@ const commit = () => {
|
|||
|
||||
const preview = ref(false)
|
||||
|
||||
watchDebounced(value, (value) => {
|
||||
watchDebounced(model, (value) => {
|
||||
if (value !== last.value.snapshot) {
|
||||
commit()
|
||||
}
|
||||
}, { debounce: 300 })
|
||||
|
||||
const lineNumber = computedWithControl(
|
||||
() => [textarea.value, value.value],
|
||||
() => [textarea.value, model],
|
||||
() => {
|
||||
const { selectionStart } = textarea.value ?? {}
|
||||
return value.value.slice(0, selectionStart).split('\n').length - 1
|
||||
return model.value.slice(0, selectionStart).split('\n').length - 1
|
||||
}
|
||||
)
|
||||
|
||||
const updateLineNumber = () => setTimeout(lineNumber.trigger, 0)
|
||||
|
||||
const currentLine = computed({
|
||||
get: () => value.value.split('\n')[lineNumber.value],
|
||||
get: () => model.value.split('\n')[lineNumber.value],
|
||||
set: (line) => {
|
||||
const content = value.value.split('\n')
|
||||
const content = model.value.split('\n')
|
||||
content[lineNumber.value] = line
|
||||
value.value = content.join('\n')
|
||||
model.value = content.join('\n')
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -54,7 +48,7 @@ const currentLine = computed({
|
|||
const splice = async (start: number, deleteCount: number, items?: string) => {
|
||||
let { selectionStart, selectionEnd } = textarea.value
|
||||
|
||||
const lineBeginning = value.value.slice(0, selectionStart).lastIndexOf('\n') + 1
|
||||
const lineBeginning = model.value.slice(0, selectionStart).lastIndexOf('\n') + 1
|
||||
let lineStart = selectionStart - lineBeginning
|
||||
let lineEnd = selectionEnd - lineBeginning
|
||||
|
||||
|
@ -154,7 +148,7 @@ newlineOperation(/^\* /, (line) => splice(line.length, 0, `\n* `))
|
|||
// Inline operations
|
||||
const inlineOperation = (chars: string) => async () => {
|
||||
const { selectionStart, selectionEnd } = textarea.value
|
||||
const lineBeginning = value.value.slice(0, selectionStart).lastIndexOf('\n') + 1
|
||||
const lineBeginning = model.value.slice(0, selectionStart).lastIndexOf('\n') + 1
|
||||
await splice(selectionStart - lineBeginning, 0, chars)
|
||||
await splice(selectionEnd - lineBeginning + chars.length, 0, chars)
|
||||
|
||||
|
@ -171,7 +165,7 @@ const strikethrough = inlineOperation('~~')
|
|||
|
||||
const link = async () => {
|
||||
const { selectionStart, selectionEnd } = textarea.value
|
||||
const lineBeginning = value.value.slice(0, selectionStart).lastIndexOf('\n') + 1
|
||||
const lineBeginning = model.value.slice(0, selectionStart).lastIndexOf('\n') + 1
|
||||
await splice(selectionStart - lineBeginning, 0, '[')
|
||||
await splice(selectionEnd - lineBeginning + 1, 0, '](url)')
|
||||
textarea.value.setSelectionRange(selectionEnd + 3, selectionEnd + 6)
|
||||
|
@ -184,39 +178,39 @@ const focus = () => textarea.value.focus()
|
|||
|
||||
<template>
|
||||
<div :class="{ 'has-preview': preview }" class="funkwhale textarea" @mousedown.prevent="focus" @mouseup.prevent="focus">
|
||||
<fw-markdown :md="value" class="preview" />
|
||||
<Markdown :md="model" class="preview" />
|
||||
<textarea ref="textarea" @click="updateLineNumber" @mousedown.stop @mouseup.stop @keydown.left="updateLineNumber"
|
||||
@keydown.right="updateLineNumber" @keydown.up="updateLineNumber" @keydown.down="updateLineNumber"
|
||||
@keydown.enter="newline" @keydown.ctrl.shift.z.exact.prevent="redo" @keydown.ctrl.z.exact.prevent="undo"
|
||||
@keydown.ctrl.b.exact.prevent="bold" @keydown.ctrl.i.exact.prevent="italics"
|
||||
@keydown.ctrl.shift.x.exact.prevent="strikethrough" @keydown.ctrl.k.exact.prevent="link" :maxlength="max"
|
||||
:placeholder="placeholder" v-model="value" id="textarea_id" />
|
||||
:placeholder="placeholder" v-model="model" id="textarea_id" />
|
||||
<div class="textarea-buttons">
|
||||
<fw-button @click="preview = !preview" icon="bi-eye" color="secondary" :is-active="preview" />
|
||||
<Button @click="preview = !preview" icon="bi-eye" color="secondary" :is-active="preview" />
|
||||
|
||||
<div class="separator" />
|
||||
|
||||
<fw-button @click="heading1" icon="bi-type-h1" color="secondary" :is-active="isHeading1" :disabled="preview" />
|
||||
<fw-button @click="heading2" icon="bi-type-h2" color="secondary" :is-active="isHeading2" :disabled="preview" />
|
||||
<fw-button @click="paragraph" icon="bi-paragraph" color="secondary" :is-active="isParagraph" :disabled="preview" />
|
||||
<fw-button @click="quote" icon="bi-quote" color="secondary" :is-active="isQuote" :disabled="preview" />
|
||||
<fw-button @click="orderedList" icon="bi-list-ol" color="secondary" :is-active="isOrderedList"
|
||||
<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"
|
||||
:disabled="preview" />
|
||||
<fw-button @click="unorderedList" icon="bi-list-ul" color="secondary" :is-active="isUnorderedList"
|
||||
<Button @click="unorderedList" icon="bi-list-ul" color="secondary" :is-active="isUnorderedList"
|
||||
:disabled="preview" />
|
||||
|
||||
<div class="separator" />
|
||||
|
||||
<fw-button @click="bold" icon="bi-type-bold" color="secondary" :disabled="preview" />
|
||||
<fw-button @click="italics" icon="bi-type-italic" color="secondary" :disabled="preview" />
|
||||
<fw-button @click="strikethrough" icon="bi-type-strikethrough" color="secondary" :disabled="preview" />
|
||||
<fw-button @click="link" icon="bi-link-45deg" color="secondary" :disabled="preview" />
|
||||
<Button @click="bold" icon="bi-type-bold" color="secondary" :disabled="preview" />
|
||||
<Button @click="italics" icon="bi-type-italic" color="secondary" :disabled="preview" />
|
||||
<Button @click="strikethrough" icon="bi-type-strikethrough" color="secondary" :disabled="preview" />
|
||||
<Button @click="link" icon="bi-link-45deg" color="secondary" :disabled="preview" />
|
||||
|
||||
<span v-if="max !== Infinity && typeof max === 'number'" class="letter-count">{{ max - value.length }}</span>
|
||||
<span v-if="max !== Infinity && typeof max === 'number'" class="letter-count">{{ max - model.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss'
|
||||
@import './textarea.scss';
|
||||
</style>
|
||||
|
|
|
@ -3,13 +3,11 @@ import { computed, ref, watchEffect } from 'vue'
|
|||
import { slugify } from 'transliteration'
|
||||
import { useScroll } from '@vueuse/core';
|
||||
|
||||
const heading = defineProp<'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'>('heading', {
|
||||
default: 'h1'
|
||||
})
|
||||
const {heading='h1'} = defineProps<{heading?:'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'}>()
|
||||
|
||||
const toc = ref()
|
||||
|
||||
const headings = computed(() => toc.value?.querySelectorAll(heading.value) ?? [])
|
||||
const headings = computed(() => toc.value?.querySelectorAll(heading) ?? [])
|
||||
watchEffect(() => {
|
||||
for (const heading of headings.value) {
|
||||
heading.id = slugify(heading.textContent)
|
||||
|
@ -48,5 +46,5 @@ watchEffect(() => {
|
|||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss'
|
||||
@import './toc.scss'
|
||||
</style>
|
||||
|
|
|
@ -5,9 +5,7 @@ interface Props {
|
|||
|
||||
defineProps<Props>()
|
||||
|
||||
const { modelValue: enabled } = defineModels<{
|
||||
modelValue: boolean
|
||||
}>()
|
||||
const enabled = defineModel<boolean>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -20,5 +18,5 @@ const { modelValue: enabled } = defineModels<{
|
|||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss'
|
||||
@import './toggle.scss'
|
||||
</style>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import { FwButton } from '~/components'
|
||||
import Button from '../Button.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<fw-button icon="bi-three-dots-vertical" class="options-button" color="secondary" variant="ghost" />
|
||||
<Button icon="bi-three-dots-vertical" class="options-button" color="secondary" variant="ghost" />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss'
|
||||
@import './options.scss'
|
||||
</style>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import { FwButton } from '~/components'
|
||||
import Button from '../Button.vue'
|
||||
|
||||
const play = defineEmit()
|
||||
const play = defineEmits()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<fw-button @click="play()" icon="bi-play-fill" class="play-button" shadow round />
|
||||
<Button @click="play()" icon="bi-play-fill" class="play-button" shadow round />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './style.scss';
|
||||
@import './play.scss';
|
||||
</style>
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
const value = defineModel<boolean>('modelValue', { required: true })
|
||||
import PopoverItem from './PopoverItem.vue';
|
||||
|
||||
const value = defineModel<boolean>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<fw-popover-item
|
||||
<PopoverItem
|
||||
@click="value = !value"
|
||||
class="checkbox"
|
||||
>
|
||||
|
@ -13,5 +15,5 @@ const value = defineModel<boolean>('modelValue', { required: true })
|
|||
<template #after>
|
||||
<slot name="after" />
|
||||
</template>
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
</template>
|
||||
|
|
|
@ -4,8 +4,8 @@ import { POPOVER_CONTEXT_INJECTION_KEY, type PopoverContext } from '~/injection-
|
|||
|
||||
const setId = defineEmit<[value: number]>('internal:id')
|
||||
|
||||
const parentPopoverContext = defineProp<PopoverContext>('vuiParentPopoverContext')
|
||||
const { items, hoveredItem } = parentPopoverContext.value ?? inject(POPOVER_CONTEXT_INJECTION_KEY, {
|
||||
const { parentPopoverContext } = defineProps<{ parentPopoverContext?: PopoverContext }>()
|
||||
const { items, hoveredItem } = parentPopoverContext ?? inject(POPOVER_CONTEXT_INJECTION_KEY, {
|
||||
items: ref(0),
|
||||
hoveredItem: ref(-2)
|
||||
})
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import PopoverRadioItem from './PopoverRadioItem.vue';
|
||||
|
||||
import { computed } from 'vue'
|
||||
|
||||
const choices = defineProp<string[]>('choices', {
|
||||
required: true,
|
||||
})
|
||||
const { choices } = defineProps<{ choices:string[] }>()
|
||||
|
||||
const filteredChoices = computed(() => new Set(choices.value))
|
||||
const filteredChoices = computed(() => new Set(choices))
|
||||
|
||||
const value = defineModel<string>('modelValue', { required: true })
|
||||
|
||||
|
@ -25,7 +25,7 @@ const choiceValues = new Proxy<Record<string, boolean>>(Object.create(null), {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<fw-popover-radio-item v-for="choice of filteredChoices" :key="choice" v-model="choiceValues[choice]">
|
||||
<PopoverRadioItem v-for="choice of filteredChoices" :key="choice" v-model="choiceValues[choice]">
|
||||
{{ choice }}
|
||||
</fw-popover-radio-item>
|
||||
</PopoverRadioItem>
|
||||
</template>
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import PopoverItem from './PopoverItem.vue';
|
||||
|
||||
const value = defineModel<boolean>('modelValue', { required: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<fw-popover-item @click="value = !value" class="checkbox">
|
||||
<PopoverItem @click="value = !value" class="checkbox">
|
||||
<i :class="['bi', value ? 'bi-record-circle' : 'bi-circle']" />
|
||||
<slot />
|
||||
|
||||
<template #after>
|
||||
<slot name="after" />
|
||||
</template>
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
</template>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import FwPopover from '../Popover.vue'
|
||||
import { inject, ref, watchEffect } from 'vue'
|
||||
import { POPOVER_CONTEXT_INJECTION_KEY } from '~/injection-keys';
|
||||
|
||||
import Popover from '../Popover.vue'
|
||||
import PopoverItem from './PopoverItem.vue'
|
||||
|
||||
const context = inject(POPOVER_CONTEXT_INJECTION_KEY, {
|
||||
items: ref(0),
|
||||
hoveredItem: ref(-2)
|
||||
|
@ -16,11 +18,11 @@ watchEffect(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<fw-popover v-model:open="open" positioning="horizontal">
|
||||
<fw-popover-item
|
||||
<Popover v-model:open="open" positioning="horizontal">
|
||||
<PopoverItem
|
||||
@click="open = !open"
|
||||
@internal:id="id = $event"
|
||||
:vui-parent-popover-context="context"
|
||||
:parent-popover-context="context"
|
||||
class="submenu"
|
||||
>
|
||||
<slot />
|
||||
|
@ -30,10 +32,10 @@ watchEffect(() => {
|
|||
<i class="bi bi-chevron-right" />
|
||||
</slot>
|
||||
</template>
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
|
||||
<template #items>
|
||||
<slot name="items" />
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { toValue, type MaybeRefOrGetter } from "@vueuse/core"
|
||||
import { computed } from 'vue'
|
||||
import type { Color, Pastel } from '~/types/common-props'
|
||||
|
||||
export function useColor(color: MaybeRefOrGetter<Color | undefined>, defaultColor: Color = 'primary') {
|
||||
return computed(() => `is-${toValue(color) ?? defaultColor}`)
|
||||
|
@ -13,3 +12,13 @@ export function usePastel(color: MaybeRefOrGetter<Pastel | undefined>, defaultCo
|
|||
export function useColorOrPastel<T extends Color | Pastel>(color: MaybeRefOrGetter<T | undefined>, defaultColor: T) {
|
||||
return computed(() => `is-${toValue(color) ?? defaultColor}`)
|
||||
}
|
||||
|
||||
export type Color = 'primary' | 'secondary' | 'destructive'
|
||||
export interface ColorProps {
|
||||
color?: Color
|
||||
}
|
||||
|
||||
export type Pastel = 'red' | 'blue' | 'purple' | 'green' | 'yellow'
|
||||
export interface PastelProps {
|
||||
color?: Pastel
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export const preventNonNumeric = (e: KeyboardEvent) => {
|
||||
if (!e.key.match(/![0-9]$/)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
|
@ -1,18 +1,94 @@
|
|||
<script setup lang="ts">
|
||||
const track = {
|
||||
name: 'Some lovely track',
|
||||
artist: {
|
||||
name: 'Artist'
|
||||
import type { Track, User } from "~/types";
|
||||
|
||||
import Activity from "~/components/ui/Activity.vue"
|
||||
|
||||
const track: Track = {
|
||||
id: 0,
|
||||
fid: "",
|
||||
|
||||
title: 'Some lovely track',
|
||||
description: {
|
||||
content_type: 'text/markdown',
|
||||
text: `**New:** Music for the eyes!`
|
||||
},
|
||||
cover: {
|
||||
uuid: "",
|
||||
urls: {
|
||||
original: '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'
|
||||
original: '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',
|
||||
medium_square_crop: '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',
|
||||
large_square_crop: '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'
|
||||
}
|
||||
}
|
||||
},
|
||||
tags: ["example"],
|
||||
uploads: [],
|
||||
downloads_count: 1927549377,
|
||||
artist_credit: [{
|
||||
artist: {
|
||||
id: 0,
|
||||
fid: "",
|
||||
|
||||
name: "The Artist",
|
||||
description: {
|
||||
content_type: 'text/markdown',
|
||||
text: `I'm a musician based on the internet.
|
||||
|
||||
Find all my music on [Funkwhale](https://funkwhale.audio)!`},
|
||||
tags: [],
|
||||
|
||||
content_category: 'music',
|
||||
albums: [],
|
||||
tracks_count: 1,
|
||||
attributed_to: {
|
||||
id: 0,
|
||||
summary: "",
|
||||
preferred_username: "User12345",
|
||||
full_username: "User12345",
|
||||
is_local: false,
|
||||
domain: "myDomain.io"
|
||||
},
|
||||
is_local: false,
|
||||
is_playable: true
|
||||
},
|
||||
credit: "",
|
||||
joinphrase: " and ",
|
||||
index: 22
|
||||
}],
|
||||
disc_number: 7,
|
||||
|
||||
listen_url: "https://funkwhale.audio",
|
||||
creation_date: "12345",
|
||||
attributed_to: {
|
||||
id: 0,
|
||||
summary: "",
|
||||
preferred_username: "User12345",
|
||||
full_username: "User12345",
|
||||
is_local: false,
|
||||
domain: "myDomain.io"
|
||||
},
|
||||
|
||||
is_playable: true,
|
||||
is_local: false
|
||||
}
|
||||
|
||||
const user = {
|
||||
username: 'username'
|
||||
const user: User = {
|
||||
id: 12,
|
||||
avatar: {
|
||||
uuid: "",
|
||||
urls: {
|
||||
original: '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',
|
||||
medium_square_crop: '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',
|
||||
large_square_crop: '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'
|
||||
}
|
||||
},
|
||||
email: "user12345@example.org",
|
||||
summary: { text: "Hi! I'm Example from The Internet.", content_type: "text" },
|
||||
username: "user12345",
|
||||
full_username: "user12345",
|
||||
instance_support_message_display_date: "?",
|
||||
funkwhale_support_message_display_date: "?",
|
||||
is_superuser: true,
|
||||
privacy_level: "everyone"
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -24,7 +100,7 @@ Activities display history entries for a Funkwhale pod. Each item contains the f
|
|||
- A track title
|
||||
- An artist name
|
||||
- A username
|
||||
- A [popover](./../popover.md)
|
||||
- A [popover](./popover.md)
|
||||
|
||||
| Prop | Data type | Required? | Description |
|
||||
| ------- | ------------ | --------- | -------------------------------------------- |
|
||||
|
@ -33,29 +109,25 @@ Activities display history entries for a Funkwhale pod. Each item contains the f
|
|||
|
||||
## Single items
|
||||
|
||||
You can render a single activity item by passing the track and user information to the `<fw-activity>` component.
|
||||
You can render a single activity item by passing the track and user information to the `<Activity>` component.
|
||||
|
||||
```vue-html
|
||||
<fw-activity :track="track" :user="user" />
|
||||
<Activity :track="track" :user="user" />
|
||||
```
|
||||
|
||||
<fw-activity :track="track" :user="user" />
|
||||
<Activity :track="track" :user="user" />
|
||||
|
||||
## Activity lists
|
||||
|
||||
You can display a list of activity items by passing a `v-for` directive and adding a `key` to the item. The `key` must be unique to the list.
|
||||
You can display a list of activity items by passing a `v-for` directive and adding a `key` to the item. The `key` must
|
||||
be unique to the list.
|
||||
|
||||
::: info
|
||||
Items in a list are visually separated by a 1px border.
|
||||
:::
|
||||
|
||||
```vue-html{4-5}
|
||||
<fw-activity
|
||||
:track="track"
|
||||
:user="user"
|
||||
v-for="i in 3"
|
||||
:key="i"
|
||||
/>
|
||||
<Activity :track="track" :user="user" v-for="i in 3" :key="i" />
|
||||
```
|
||||
|
||||
<fw-activity :track="track" :user="user" v-for="i in 3" :key="i" />
|
||||
<Activity :track="track" :user="user" v-for="i in 3" :key="i" />
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
<script setup>
|
||||
import Alert from "~/components/ui/Alert.vue"
|
||||
import Button from "~/components/ui/Button.vue"
|
||||
</script>
|
||||
|
||||
# Alert
|
||||
|
||||
| Prop | Data type | Required? | Default | Description |
|
||||
|
@ -21,78 +26,78 @@ Funkwhale alerts support a range of pastel colors for visual appeal.
|
|||
### Blue
|
||||
|
||||
```vue-html
|
||||
<fw-alert color="blue">
|
||||
<Alert color="blue">
|
||||
Blue alert
|
||||
</fw-alert>
|
||||
</Alert>
|
||||
```
|
||||
|
||||
<fw-alert color="blue">
|
||||
<Alert color="blue">
|
||||
Blue alert
|
||||
</fw-alert>
|
||||
</Alert>
|
||||
|
||||
### Red
|
||||
|
||||
```vue-html
|
||||
<fw-alert color="red">
|
||||
<Alert color="red">
|
||||
Red alert
|
||||
</fw-alert>
|
||||
</Alert>
|
||||
```
|
||||
|
||||
<fw-alert color="red">
|
||||
<Alert color="red">
|
||||
Red alert
|
||||
</fw-alert>
|
||||
</Alert>
|
||||
|
||||
### Purple
|
||||
|
||||
```vue-html
|
||||
<fw-alert color="purple">
|
||||
<Alert color="purple">
|
||||
Purple alert
|
||||
</fw-alert>
|
||||
</Alert>
|
||||
```
|
||||
|
||||
<fw-alert color="purple">
|
||||
<Alert color="purple">
|
||||
Purple alert
|
||||
</fw-alert>
|
||||
</Alert>
|
||||
|
||||
### Green
|
||||
|
||||
```vue-html
|
||||
<fw-alert color="green">
|
||||
<Alert color="green">
|
||||
Green alert
|
||||
</fw-alert>
|
||||
</Alert>
|
||||
```
|
||||
|
||||
<fw-alert color="green">
|
||||
<Alert color="green">
|
||||
Green alert
|
||||
</fw-alert>
|
||||
</Alert>
|
||||
|
||||
### Yellow
|
||||
|
||||
```vue-html
|
||||
<fw-alert color="yellow">
|
||||
<Alert color="yellow">
|
||||
Yellow alert
|
||||
</fw-alert>
|
||||
</Alert>
|
||||
```
|
||||
|
||||
<fw-alert color="yellow">
|
||||
<Alert color="yellow">
|
||||
Yellow alert
|
||||
</fw-alert>
|
||||
</Alert>
|
||||
|
||||
## Alert actions
|
||||
|
||||
```vue-html{2-4}
|
||||
<fw-alert>
|
||||
<Alert>
|
||||
Awesome artist
|
||||
|
||||
<template #actions>
|
||||
<fw-button>Got it</fw-button>
|
||||
<Button>Got it</Button>
|
||||
</template>
|
||||
</fw-alert>
|
||||
</Alert>
|
||||
```
|
||||
|
||||
<fw-alert>
|
||||
<Alert>
|
||||
Awesome artist
|
||||
<template #actions>
|
||||
<fw-button>Got it</fw-button>
|
||||
<Button>Got it</Button>
|
||||
</template>
|
||||
</fw-alert>
|
||||
</Alert>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<script setup>
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
|
||||
const click = () => new Promise(resolve => setTimeout(resolve, 1000))
|
||||
</script>
|
||||
|
||||
|
@ -29,42 +31,42 @@ This is the default type. If you don't specify a type, a primary button is rende
|
|||
:::
|
||||
|
||||
```vue-html
|
||||
<fw-button>
|
||||
<Button>
|
||||
Primary button
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-button>
|
||||
<Button>
|
||||
Primary button
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
### Secondary
|
||||
|
||||
Secondary buttons represent **neutral** actions such as cancelling a change or dismissing a notification.
|
||||
|
||||
```vue-html
|
||||
<fw-button color="secondary">
|
||||
<Button color="secondary">
|
||||
Secondary button
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-button color="secondary">
|
||||
<Button color="secondary">
|
||||
Secondary button
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
### Destructive
|
||||
|
||||
Desctrutive buttons represent **dangerous** actions including deleting items or purging domain information.
|
||||
|
||||
```vue-html
|
||||
<fw-button color="destructive">
|
||||
<Button color="destructive">
|
||||
Destructive button
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-button color="destructive">
|
||||
<Button color="destructive">
|
||||
Destructive button
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
## Button variants
|
||||
|
||||
|
@ -79,42 +81,42 @@ This is the default style. If you don't specify a style, a solid button is rende
|
|||
:::
|
||||
|
||||
```vue-html
|
||||
<fw-button>
|
||||
<Button>
|
||||
Filled button
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-button>
|
||||
<Button>
|
||||
Filled button
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
### Outline
|
||||
|
||||
Outline buttons have a transparent background. Use these to deemphasize the action the button performs.
|
||||
|
||||
```vue-html
|
||||
<fw-button variant="outline" color="secondary">
|
||||
<Button variant="outline" color="secondary">
|
||||
Outline button
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-button variant="outline" color="secondary">
|
||||
<Button variant="outline" color="secondary">
|
||||
Outline button
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
### Ghost
|
||||
|
||||
Ghost buttons have a transparent background and border. Use these to deemphasize the action the button performs.
|
||||
|
||||
```vue-html
|
||||
<fw-button variant="ghost" color="secondary">
|
||||
<Button variant="ghost" color="secondary">
|
||||
Ghost button
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-button variant="ghost" color="secondary">
|
||||
<Button variant="ghost" color="secondary">
|
||||
Ghost button
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
## Button styles
|
||||
|
||||
|
@ -123,14 +125,14 @@ Ghost buttons have a transparent background and border. Use these to deemphasize
|
|||
You can give a button a shadow to add depth.
|
||||
|
||||
```vue-html
|
||||
<fw-button shadow>
|
||||
<Button shadow>
|
||||
Shadow button
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-button shadow>
|
||||
<Button shadow>
|
||||
Shadow button
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
## Button shapes
|
||||
|
||||
|
@ -145,28 +147,28 @@ This is the default shape. If you don't specify a type, a normal button is rende
|
|||
:::
|
||||
|
||||
```vue-html
|
||||
<fw-button>
|
||||
<Button>
|
||||
Normal button
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-button>
|
||||
<Button>
|
||||
Normal button
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
### Round
|
||||
|
||||
Round buttons have fully rounded edges.
|
||||
|
||||
```vue-html
|
||||
<fw-button round>
|
||||
<Button round>
|
||||
Round button
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-button round>
|
||||
<Button round>
|
||||
Round button
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
## Button states
|
||||
|
||||
|
@ -177,49 +179,49 @@ You can pass a state to indicate whether a user can interact with a button.
|
|||
A button is active when clicked by a user. You can force an active state by passing an `is-active` prop.
|
||||
|
||||
```vue-html
|
||||
<fw-button is-active>
|
||||
<Button is-active>
|
||||
Active button
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-button is-active>
|
||||
<Button is-active>
|
||||
Active button
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
### Disabled
|
||||
|
||||
Disabled buttons are non-interactive and inherit a less bold color than the one provided. You can apply a disabled state by passing a `disabled` prop.
|
||||
|
||||
```vue-html
|
||||
<fw-button disabled>
|
||||
<Button disabled>
|
||||
Disabled button
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-button disabled>
|
||||
<Button disabled>
|
||||
Disabled button
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
### Loading
|
||||
|
||||
If a user can't interact with a button until something has finished loading, you can add a spinner by passing the `is-loading` prop.
|
||||
|
||||
```vue-html
|
||||
<fw-button is-loading>
|
||||
<Button is-loading>
|
||||
Loading button
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-button is-loading>
|
||||
<Button is-loading>
|
||||
Loading button
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
### Promise handling in `@click`
|
||||
|
||||
When a function passed to `@click` returns a promise, the button automatically toggles a loading state on click. When the promise resolves or is rejected, the loading state turns off.
|
||||
|
||||
::: danger
|
||||
There is no promise rejection mechanism implemented in the `<fw-button>` component. Make sure the `@click` handler never rejects.
|
||||
There is no promise rejection mechanism implemented in the `<Button>` component. Make sure the `@click` handler never rejects.
|
||||
:::
|
||||
|
||||
```vue
|
||||
|
@ -228,25 +230,25 @@ const click = () => new Promise((resolve) => setTimeout(resolve, 1000));
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<fw-button @click="click"> Click me </fw-button>
|
||||
<Button @click="click"> Click me </Button>
|
||||
</template>
|
||||
```
|
||||
|
||||
<fw-button @click="click">
|
||||
<Button @click="click">
|
||||
Click me
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
You can override the promise state by passing a false `is-loading` prop.
|
||||
|
||||
```vue-html
|
||||
<fw-button :is-loading="false">
|
||||
<Button :is-loading="false">
|
||||
Click me
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-button :is-loading="false">
|
||||
<Button :is-loading="false">
|
||||
Click me
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
## Icons
|
||||
|
||||
|
@ -257,20 +259,20 @@ Icon buttons shrink down to the icon size if you don't pass any content. If you
|
|||
:::
|
||||
|
||||
```vue-html
|
||||
<fw-button color="secondary" icon="bi-three-dots-vertical" />
|
||||
<Button color="secondary" icon="bi-three-dots-vertical" />
|
||||
|
||||
<fw-button color="secondary" is-round icon="bi-x" />
|
||||
<Button color="secondary" is-round icon="bi-x" />
|
||||
|
||||
<fw-button icon="bi-save"> </fw-button>
|
||||
<Button icon="bi-save"> </Button>
|
||||
|
||||
<fw-button color="destructive" icon="bi-trash">
|
||||
<Button color="destructive" icon="bi-trash">
|
||||
Delete
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-button color="secondary" icon="bi-three-dots-vertical" />
|
||||
<fw-button color="secondary" round icon="bi-x" />
|
||||
<fw-button icon="bi-save"> </fw-button>
|
||||
<fw-button color="destructive" icon="bi-trash">
|
||||
<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">
|
||||
Delete
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
<script setup>
|
||||
import OptionsButton from '~/components/ui/button/Options.vue'
|
||||
</script>
|
||||
|
||||
# Options Button
|
||||
|
||||
-> For use cases, see [components/popover](../popover)
|
||||
|
||||
```vue-html
|
||||
<fw-options-button />
|
||||
<OptionsButton />
|
||||
```
|
||||
|
||||
<fw-options-button />
|
||||
<OptionsButton />
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
<script setup>
|
||||
import PlayButton from '~/components/ui/button/Play.vue'
|
||||
</script>
|
||||
|
||||
# Play Button
|
||||
|
||||
The play button is a specialized button used in many places across the Funkwhale app. Map a function to the `@play` event handler to toggle it on click.
|
||||
|
||||
```vue-html
|
||||
<fw-play-button @play="play" />
|
||||
<PlayButton />
|
||||
```
|
||||
|
||||
<fw-play-button />
|
||||
<PlayButton />
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import Card from '~/components/card/Card.vue'
|
||||
import Spacer from '~/components/layout/Spacer.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
import Card from '~/components/ui/Card.vue'
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
import OptionsButton from '~/components/ui/button/Options.vue'
|
||||
import Spacer from '~/components/ui/layout/Spacer.vue'
|
||||
// import { useRoute } from 'vue-router'
|
||||
|
||||
const alert = (message: string) => window.alert(message)
|
||||
const router = useRouter()
|
||||
const alert = ( message: string ) => window.alert(message)
|
||||
// const router = useRouter()
|
||||
</script>
|
||||
|
||||
# Card
|
||||
|
@ -33,16 +36,26 @@ You have to set a title for the card by passing a `title` prop.
|
|||
|
||||
:::
|
||||
|
||||
<Layout grid :column-width=290>
|
||||
|
||||
```vue-html
|
||||
<Card 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
|
||||
you can use to promote that
|
||||
your content across the web.
|
||||
</Card>
|
||||
```
|
||||
|
||||
<div class="preview">
|
||||
<Card 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.
|
||||
</Card></div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
</Layout>
|
||||
|
||||
## Card as a Link
|
||||
|
||||
|
@ -86,6 +99,8 @@ Category cards are basic cards that contain only a title. To create a category c
|
|||
|
||||
Pass an image source to the `image` prop or set `image.src` and `image.style`.
|
||||
|
||||
<Layout flex>
|
||||
|
||||
```vue-html{4,11-12}
|
||||
<Card
|
||||
style="--width:208px"
|
||||
|
@ -102,7 +117,6 @@ Pass an image source to the `image` prop or set `image.src` and `image.style`.
|
|||
</Card>
|
||||
```
|
||||
|
||||
<fw-layout flex>
|
||||
<div class="preview">
|
||||
<Card
|
||||
style="--width:208px"
|
||||
|
@ -117,7 +131,7 @@ 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',
|
||||
style:'withPadding' }" />
|
||||
</div>
|
||||
</fw-layout>
|
||||
</Layout>
|
||||
|
||||
## Add an Alert
|
||||
|
||||
|
@ -143,11 +157,11 @@ Items in this region are secondary and will be displayed smaller than the main c
|
|||
<Card title="My items">
|
||||
<template #alert> There are no items in this list </template>
|
||||
<template #footer>
|
||||
<fw-button variant="outline" icon="bi-upload" @click="alert('Uploaded. Press OK!')">
|
||||
<Button variant="outline" icon="bi-upload" @click="alert('Uploaded. Press OK!')">
|
||||
Upload
|
||||
</fw-button>
|
||||
</Button>
|
||||
<Spacer style="flex-grow: 1" />
|
||||
<fw-options-button />
|
||||
<OptionsButton />
|
||||
</template>
|
||||
</Card>
|
||||
```
|
||||
|
@ -159,9 +173,9 @@ Items in this region are secondary and will be displayed smaller than the main c
|
|||
</template>
|
||||
|
||||
<template #footer>
|
||||
<fw-button variant="outline" icon="bi-upload" @click="alert('Uploaded. Press OK!')">Upload</fw-button>
|
||||
<Button variant="outline" icon="bi-upload" @click="alert('Uploaded. Press OK!')">Upload</Button>
|
||||
<Spacer style="flex-grow: 1" />
|
||||
<fw-options-button />
|
||||
<OptionsButton />
|
||||
</template>
|
||||
|
||||
</Card>
|
||||
|
@ -175,8 +189,8 @@ Large Buttons or links at the bottom edge of the card serve as Call-to-Actions (
|
|||
<Card title="Join an existing pod">
|
||||
The easiest way to get started with Funkwhale is to register an account on a public pod.
|
||||
<template #action>
|
||||
<fw-button @click="alert('Open the pod picker')">Action!
|
||||
</fw-button>
|
||||
<Button @click="alert('Open the pod picker')">Action!
|
||||
</Button>
|
||||
</template>
|
||||
</Card>
|
||||
```
|
||||
|
@ -185,8 +199,8 @@ Large Buttons or links at the bottom edge of the card serve as Call-to-Actions (
|
|||
<Card title="Join an existing pod">
|
||||
The easiest way to get started with Funkwhale is to register an account on a public pod.
|
||||
<template #action>
|
||||
<fw-button @click="alert('Open the pod picker')">Action!
|
||||
</fw-button>
|
||||
<Button @click="alert('Open the pod picker')">Action!
|
||||
</Button>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
|
@ -197,12 +211,12 @@ If there are multiple actions, they will be presented in a row:
|
|||
<Card title="Delete this pod?">
|
||||
You cannot undo this action.
|
||||
<template #action>
|
||||
<fw-button style="justify-content: flex-start;" icon="bi-chevron-left" color="secondary" variant="ghost">
|
||||
<Button style="justify-content: flex-start;" icon="bi-chevron-left" color="secondary" variant="ghost">
|
||||
Back
|
||||
</fw-button>
|
||||
<fw-button style="flex-grow:0;" color="destructive" @click="alert('Deleted')">
|
||||
</Button>
|
||||
<Button style="flex-grow:0;" color="destructive" @click="alert('Deleted')">
|
||||
Delete
|
||||
</fw-button>
|
||||
</Button>
|
||||
</template>
|
||||
</Card>
|
||||
```
|
||||
|
@ -211,12 +225,12 @@ If there are multiple actions, they will be presented in a row:
|
|||
<Card title="Delete this pod?">
|
||||
You cannot undo this action.
|
||||
<template #action>
|
||||
<fw-button style="width:50%; justify-content: flex-start;" icon="bi-chevron-left" color="secondary" variant="ghost">
|
||||
<Button style="width:50%; justify-content: flex-start;" icon="bi-chevron-left" color="secondary" variant="ghost">
|
||||
Back
|
||||
</fw-button>
|
||||
<fw-button style="width:50%" color="destructive" @click="alert('Deleted')">
|
||||
</Button>
|
||||
<Button style="width:50%" color="destructive" @click="alert('Deleted')">
|
||||
Delete
|
||||
</fw-button>
|
||||
</Button>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
@ -1,54 +1,53 @@
|
|||
<script setup>
|
||||
import Input from "~/components/ui/Input.vue"
|
||||
</script>
|
||||
|
||||
# Input
|
||||
|
||||
Inputs are areas in which users can enter information. In Funkwhale, these mostly take the form of search fields.
|
||||
|
||||
| Prop | Data type | Required? | Description |
|
||||
| --------------- | --------- | --------- | --------------------------------------------------------------------------- |
|
||||
| `placeholder` | String | No | The placeholder text that appears when the input is empty. |
|
||||
| `icon` | String | No | The [Bootstrap icon](https://icons.getbootstrap.com/) to show on the input. |
|
||||
| `v-model:value` | String | Yes | The text entered in the input. |
|
||||
| Prop | Data type | Required? | Description |
|
||||
| ---- | --------- | --------- | ----------- |
|
||||
|
||||
|
|
||||
| `placeholder` | String | No | The placeholder text that appears when the input is empty. |
|
||||
| `icon` | String | No | The [Bootstrap icon](https://icons.getbootstrap.com/) to show on the input. |
|
||||
| `v-model:value` | String | Yes | The text entered in the input. |
|
||||
|
||||
## Input model
|
||||
|
||||
You can link a user's input to form data by referencing the data in a `v-model` directive.
|
||||
|
||||
```vue-html{2}
|
||||
<fw-input
|
||||
v-model="value"
|
||||
placeholder="Search"
|
||||
/>
|
||||
<Input v-model="value" placeholder="Search" />
|
||||
```
|
||||
|
||||
<fw-input placeholder="Search" />
|
||||
<Input placeholder="Search" />
|
||||
|
||||
## Input icons
|
||||
|
||||
Add a [Bootstrap icon](https://icons.getbootstrap.com/) to an input to make its purpose more visually clear.
|
||||
|
||||
```vue-html{3}
|
||||
<fw-input
|
||||
v-model="value"
|
||||
icon="bi-search"
|
||||
placeholder="Search"
|
||||
/>
|
||||
<Input v-model="value" icon="bi-search" placeholder="Search" />
|
||||
```
|
||||
|
||||
<fw-input icon="bi-search" placeholder="Search" />
|
||||
<Input icon="bi-search" placeholder="Search" />
|
||||
|
||||
## Input-right slot
|
||||
|
||||
You can add a template on the right-hand side of the input to guide the user's input.
|
||||
|
||||
```vue-html{2-4}
|
||||
<fw-input v-model="value" placeholder="Search">
|
||||
<Input v-model="value" placeholder="Search">
|
||||
<template #input-right>
|
||||
suffix
|
||||
</template>
|
||||
</fw-input>
|
||||
</Input>
|
||||
```
|
||||
|
||||
<fw-input placeholder="Search">
|
||||
<Input placeholder="Search">
|
||||
<template #input-right>
|
||||
suffix
|
||||
</template>
|
||||
</fw-input>
|
||||
</Input>
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import Layout from '~/components/layout/Layout.vue'
|
||||
import Card from '~/components/card/Card.vue'
|
||||
import Alert from '~/components/alert/Alert.vue'
|
||||
import Card from '~/components/ui/Card.vue'
|
||||
import Alert from '~/components/ui/Alert.vue'
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
import Tab from '~/components/ui/Tab.vue'
|
||||
import Tabs from '~/components/ui/Tabs.vue'
|
||||
</script>
|
||||
|
||||
# Layout
|
||||
|
||||
The following containers are responsive. Change your window's size or select a device preset from your browser's dev tools to see how layouts are affected by available space.
|
||||
|
||||
<fw-tabs>
|
||||
<fw-tab title="Flex (default)" icon="⠖">
|
||||
<Tabs>
|
||||
<Tab title="Flex (default)" icon="⠖">
|
||||
|
||||
Items are laid out in a row and wrapped as they overflow the container.
|
||||
By default, all items in a row assume the same (maximum) height.
|
||||
|
@ -23,7 +25,7 @@ By default, all items in a row assume the same (maximum) height.
|
|||
</Layout>
|
||||
```
|
||||
|
||||
<div class="preview">
|
||||
<div class="preview">
|
||||
<Layout flex>
|
||||
<Card title="A" style="width:100px; min-width:100px"></Card>
|
||||
<Card title="B" :tags="['funk', 'dunk', 'punk']"></Card>
|
||||
|
@ -37,22 +39,20 @@ By default, all items in a row assume the same (maximum) height.
|
|||
Find a list of all styles here: [Flexbox guide on css-tricks.com](https://css-tricks.com/snippets/css/a-guide-to-flexbox/)
|
||||
|
||||
<Layout flex>
|
||||
|
||||
<div class="preview" style="font-size:11px; font-weight:bold; mix-blend-mode:luminosity;">
|
||||
<Layout flex style="--gap: 4px;"> --gap: 4px
|
||||
<Alert style="align-self: flex-end">align-self: flex-end</Alert>
|
||||
<Alert style="flex-grow: 1">flex-grow: 1</Alert>
|
||||
<Alert style="height: 5rem;">height: 5rem</Alert>
|
||||
<Alert style="width: 100%">width: 100%</Alert>
|
||||
</Layout>
|
||||
</div>
|
||||
|
||||
<div class="preview" style="font-size:11px; font-weight:bold; mix-blend-mode:luminosity;">
|
||||
<Layout flex style="--gap: 4px;"> --gap: 4px
|
||||
<Alert style="align-self: flex-end">align-self: flex-end</Alert>
|
||||
<Alert style="flex-grow: 1">flex-grow: 1</Alert>
|
||||
<Alert style="height: 5rem;">height: 5rem</Alert>
|
||||
<Alert style="width: 100%">width: 100%</Alert>
|
||||
</Layout>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
:::
|
||||
|
||||
</fw-tab>
|
||||
<fw-tab title="Grid" icon="ꖛ">
|
||||
</Tab>
|
||||
<Tab title="Grid" icon="ꖛ">
|
||||
|
||||
Align items both vertically and horizontally
|
||||
|
||||
|
@ -112,9 +112,9 @@ Align items both vertically and horizontally
|
|||
<Card title="D"></Card>
|
||||
</Layout>
|
||||
|
||||
</fw-tab>
|
||||
</Tab>
|
||||
|
||||
<fw-tab title="Stack" icon="𝌆">
|
||||
<Tab title="Stack" icon="𝌆">
|
||||
|
||||
Add space between vertically stacked items
|
||||
|
||||
|
@ -142,6 +142,6 @@ Add space between vertically stacked items
|
|||
|
||||
</Layout>
|
||||
|
||||
</fw-tab>
|
||||
</Tab>
|
||||
|
||||
</fw-tabs>
|
||||
</Tabs>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import Layout from '~/components/layout/Layout.vue'
|
||||
import Spacer from '~/components/layout/Spacer.vue'
|
||||
import Alert from '~/components/alert/Alert.vue'
|
||||
import Alert from '~/components/ui/Alert.vue'
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
import Spacer from '~/components/ui/layout/Spacer.vue'
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import Loader from '~/components/ui/Loader.vue'
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.docs-loader-container div[style^=width] {
|
||||
border: 1px solid #666;
|
||||
|
@ -23,7 +27,7 @@ Loaders visually indicate when an operation is loading. This makes it visually c
|
|||
|
||||
<div class="docs-loader-container">
|
||||
<div style="width: 50%">
|
||||
<fw-loader />
|
||||
<Loader />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -34,7 +38,7 @@ By default the `<fw-loader />` component creates a container that takes up 100%
|
|||
```vue-html
|
||||
<div style="position: relative">
|
||||
<div style="width: 50%">
|
||||
<fw-loader :container="false" />
|
||||
<Loader :container="false" />
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
@ -42,7 +46,7 @@ By default the `<fw-loader />` component creates a container that takes up 100%
|
|||
<div class="docs-loader-container">
|
||||
<div style="position: relative">
|
||||
<div style="width: 50%">
|
||||
<fw-loader :container="false" />
|
||||
<Loader :container="false" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, watchEffect } from 'vue'
|
||||
|
||||
import Alert from '~/components/ui/Alert.vue'
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
import Modal from '~/components/ui/Modal.vue'
|
||||
|
||||
const open = ref(false)
|
||||
const open2 = ref(false)
|
||||
|
||||
|
@ -27,99 +31,99 @@ const open5 = ref(false)
|
|||
## Modal open
|
||||
|
||||
```vue-html
|
||||
<fw-modal v-model="open" title="My modal">
|
||||
<Modal v-model="open" title="My modal">
|
||||
Modal content
|
||||
</fw-modal>
|
||||
</Modal>
|
||||
|
||||
<fw-button @click="open = true">
|
||||
<Button @click="open = true">
|
||||
Open modal
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-modal v-model="open" title="My modal">
|
||||
<Modal v-model="open" title="My modal">
|
||||
Modal content
|
||||
</fw-modal>
|
||||
<fw-button @click="open = true">
|
||||
</Modal>
|
||||
<Button @click="open = true">
|
||||
Open modal
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
## Modal actions
|
||||
|
||||
Use the `#actions` slot to add actions to a modal. Actions typically take the form of [buttons](/components/button).
|
||||
Use the `#actions` slot to add actions to a modal. Actions typically take the form of [buttons](./button).
|
||||
|
||||
```vue-html
|
||||
<fw-modal v-model="open" title="My modal">
|
||||
<Modal v-model="open" title="My modal">
|
||||
Modal content
|
||||
|
||||
<template #actions>
|
||||
<fw-button @click="open = false" color="secondary">
|
||||
<Button @click="open = false" color="secondary">
|
||||
Cancel
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
<fw-button @click="open = false">
|
||||
<Button @click="open = false">
|
||||
Ok
|
||||
</fw-button>
|
||||
</Button>
|
||||
</template>
|
||||
</fw-modal>
|
||||
</Modal>
|
||||
|
||||
<fw-button @click="open = true">
|
||||
<Button @click="open = true">
|
||||
Open modal
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-modal v-model="open2" title="My modal">
|
||||
<Modal v-model="open2" title="My modal">
|
||||
Modal content
|
||||
<template #actions>
|
||||
<fw-button @click="open2 = false" color="secondary">
|
||||
<Button @click="open2 = false" color="secondary">
|
||||
Cancel
|
||||
</fw-button>
|
||||
<fw-button @click="open2 = false">
|
||||
</Button>
|
||||
<Button @click="open2 = false">
|
||||
Ok
|
||||
</fw-button>
|
||||
</Button>
|
||||
</template>
|
||||
</fw-modal>
|
||||
<fw-button @click="open2 = true">
|
||||
</Modal>
|
||||
<Button @click="open2 = true">
|
||||
Open modal
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
## Nested modals
|
||||
|
||||
You can nest modals to allow users to open a modal from inside another modal. This can be useful when creating a multi-step workflow.
|
||||
|
||||
```vue-html
|
||||
<fw-modal v-model="open" title="My modal">
|
||||
<fw-modal v-model="openNested" title="My modal">
|
||||
<Modal v-model="open" title="My modal">
|
||||
<Modal v-model="openNested" title="My modal">
|
||||
Nested modal content
|
||||
</fw-modal>
|
||||
</Modal>
|
||||
|
||||
<fw-button @click="openNested = true">
|
||||
<Button @click="openNested = true">
|
||||
Open nested modal
|
||||
</fw-button>
|
||||
</fw-modal>
|
||||
</Button>
|
||||
</Modal>
|
||||
|
||||
<fw-button @click="open = true">
|
||||
<Button @click="open = true">
|
||||
Open modal
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-modal v-model="open4" title="My modal">
|
||||
<fw-modal v-model="open5" title="My modal">
|
||||
<Modal v-model="open4" title="My modal">
|
||||
<Modal v-model="open5" title="My modal">
|
||||
Nested modal content
|
||||
</fw-modal>
|
||||
<fw-button @click="open5 = true">
|
||||
</Modal>
|
||||
<Button @click="open5 = true">
|
||||
Open nested modal
|
||||
</fw-button>
|
||||
</fw-modal>
|
||||
<fw-button @click="open4 = true">
|
||||
</Button>
|
||||
</Modal>
|
||||
<Button @click="open4 = true">
|
||||
Open modal
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
||||
## Alert inside modal
|
||||
|
||||
You can nest [Funkwhale alerts](/components/alert) to visually highlight content within the modal.
|
||||
You can nest [Funkwhale alerts](./alert) to visually highlight content within the modal.
|
||||
|
||||
```vue-html
|
||||
<fw-modal v-model="open" title="My modal">
|
||||
<Modal v-model="open" title="My modal">
|
||||
Modal content
|
||||
|
||||
<template #alert v-if="alertOpen">
|
||||
|
@ -127,36 +131,36 @@ You can nest [Funkwhale alerts](/components/alert) to visually highlight content
|
|||
Alert content
|
||||
|
||||
<template #actions>
|
||||
<fw-button @click="alertOpen = false">Close alert</fw-button>
|
||||
<Button @click="alertOpen = false">Close alert</Button>
|
||||
</template>
|
||||
</fw-alert>
|
||||
</template>
|
||||
</fw-modal>
|
||||
</Modal>
|
||||
|
||||
<fw-button @click="open = true">
|
||||
<Button @click="open = true">
|
||||
Open modal
|
||||
</fw-button>
|
||||
</Button>
|
||||
```
|
||||
|
||||
<fw-modal v-model="open3" title="My modal">
|
||||
<Modal v-model="open3" title="My modal">
|
||||
Modal content
|
||||
<template #alert v-if="alertOpen">
|
||||
<fw-alert>
|
||||
<Alert>
|
||||
Alert content
|
||||
<template #actions>
|
||||
<fw-button @click="alertOpen = false">Close alert</fw-button>
|
||||
<Button @click="alertOpen = false">Close alert</Button>
|
||||
</template>
|
||||
</fw-alert>
|
||||
</Alert>
|
||||
</template>
|
||||
<template #actions>
|
||||
<fw-button @click="open3 = false" color="secondary">
|
||||
<Button @click="open3 = false" color="secondary">
|
||||
Cancel
|
||||
</fw-button>
|
||||
<fw-button @click="open3 = false">
|
||||
</Button>
|
||||
<Button @click="open3 = false">
|
||||
Ok
|
||||
</fw-button>
|
||||
</Button>
|
||||
</template>
|
||||
</fw-modal>
|
||||
<fw-button @click="open3 = true">
|
||||
</Modal>
|
||||
<Button @click="open3 = true">
|
||||
Open modal
|
||||
</fw-button>
|
||||
</Button>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
import Pagination from "~/components/ui/Pagination.vue"
|
||||
|
||||
const page = ref(1)
|
||||
</script>
|
||||
|
||||
|
@ -18,7 +20,7 @@ The pagination component helps users navigate through large lists of results by
|
|||
Create a pagination bar by passing the number of pages to the `pages` prop. Use `v-model` to sync the selected page to your page data. Users can click on each button or input a specific page and hit `return`.
|
||||
|
||||
```vue-html
|
||||
<fw-pagination :pages="8" v-model:page="page" />
|
||||
<Pagination :pages="8" v-model:page="page" />
|
||||
```
|
||||
|
||||
<fw-pagination :pages="9" v-model:page="page" />
|
||||
<Pagination :pages="9" v-model:page="page" />
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
<script setup>
|
||||
import Pill from '~/components/ui/Pill.vue'
|
||||
</script>
|
||||
|
||||
# Pill
|
||||
|
||||
Pills are decorative elements that display information about content they attach to. They can be links to other content or simple colored labels.
|
||||
|
||||
You can add text to pills by adding it between the `<fw-pill>` tags.
|
||||
You can add text to pills by adding it between the `<Pill>` tags.
|
||||
|
||||
| Prop | Data type | Required? | Default | Description |
|
||||
| ------- | ----------------------------------------------------------------------------------------------- | --------- | ----------- | ---------------------- |
|
||||
|
@ -25,14 +29,14 @@ You can assign a type to your pill to indicate what kind of information it conve
|
|||
Primary pills convey **positive** information.
|
||||
|
||||
```vue-html
|
||||
<fw-pill color="primary">
|
||||
<Pill color="primary">
|
||||
Primary pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
```
|
||||
|
||||
<fw-pill color="primary">
|
||||
<Pill color="primary">
|
||||
Primary pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
|
||||
### Secondary
|
||||
|
||||
|
@ -43,28 +47,28 @@ This is the default type for pills. If you don't specify a type, a **secondary**
|
|||
:::
|
||||
|
||||
```vue-html
|
||||
<fw-pill>
|
||||
<Pill>
|
||||
Secondary pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
```
|
||||
|
||||
<fw-pill>
|
||||
<Pill>
|
||||
Secondary pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
|
||||
### Destructive
|
||||
|
||||
Destructive pills convey **destructive** or **negative** information. Use these to indicate that information could cause issues such as data loss.
|
||||
|
||||
```vue-html
|
||||
<fw-pill color="destructive">
|
||||
<Pill color="destructive">
|
||||
Destructive pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
```
|
||||
|
||||
<fw-pill color="destructive">
|
||||
<Pill color="destructive">
|
||||
Destructive pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
|
||||
## Pill colors
|
||||
|
||||
|
@ -83,79 +87,79 @@ Funkwhale pills support a range of pastel colors to create visually appealing in
|
|||
### Blue
|
||||
|
||||
```vue-html
|
||||
<fw-pill color="blue">
|
||||
<Pill color="blue">
|
||||
Blue pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
```
|
||||
|
||||
<fw-pill color="blue">
|
||||
<Pill color="blue">
|
||||
Blue pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
|
||||
### Red
|
||||
|
||||
```vue-html
|
||||
<fw-pill color="red">
|
||||
<Pill color="red">
|
||||
Red pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
```
|
||||
|
||||
<fw-pill color="red">
|
||||
<Pill color="red">
|
||||
Red pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
|
||||
### Purple
|
||||
|
||||
```vue-html
|
||||
<fw-pill color="purple">
|
||||
<Pill color="purple">
|
||||
Purple pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
```
|
||||
|
||||
<fw-pill color="purple">
|
||||
<Pill color="purple">
|
||||
Purple pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
|
||||
### Green
|
||||
|
||||
```vue-html
|
||||
<fw-pill color="green">
|
||||
<Pill color="green">
|
||||
Green pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
```
|
||||
|
||||
<fw-pill color="green">
|
||||
<Pill color="green">
|
||||
Green pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
|
||||
### Yellow
|
||||
|
||||
```vue-html
|
||||
<fw-pill color="yellow">
|
||||
<Pill color="yellow">
|
||||
Yellow pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
```
|
||||
|
||||
<fw-pill color="yellow">
|
||||
<Pill color="yellow">
|
||||
Yellow pill
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
|
||||
## Image pills
|
||||
|
||||
Image pills contain a small circular image on their left. These can be used for decorative links such as artist links. To created an image pill, insert a link to the image between the pill tags as a `<template>`.
|
||||
|
||||
```vue-html{2-4}
|
||||
<fw-pill>
|
||||
<Pill>
|
||||
<template #image>
|
||||
<img src="/images/awesome-artist.png" />
|
||||
</template>
|
||||
Awesome artist
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
```
|
||||
|
||||
<fw-pill>
|
||||
<Pill>
|
||||
<template #image>
|
||||
<div style="background-color: #0004" />
|
||||
</template>
|
||||
Awesome artist
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
import Button from "~/components/ui/Button.vue"
|
||||
import OptionsButton from "~/components/ui/button/Options.vue"
|
||||
import Pill from "~/components/ui/Pill.vue"
|
||||
import Popover from "~/components/ui/Popover.vue"
|
||||
import PopoverCheckbox from "~/components/ui/popover/PopoverCheckbox.vue"
|
||||
import PopoverItem from "~/components/ui/popover/PopoverItem.vue"
|
||||
import PopoverRadio from "~/components/ui/popover/PopoverRadio.vue"
|
||||
import PopoverSubmenu from "~/components/ui/popover/PopoverSubmenu.vue"
|
||||
|
||||
// String values
|
||||
|
||||
const privacyChoices = ['public', 'private', 'pod']
|
||||
|
@ -32,7 +41,7 @@ const fullMenu= ref(false)
|
|||
|
||||
# Popover
|
||||
|
||||
Popovers (`fw-popover`) are visually hidden menus of items activated by a simple button. Use popovers to create complex menus in a visually appealing way.
|
||||
Popovers (`Popover`) are visually hidden menus of items activated by a simple button. Use popovers to create complex menus in a visually appealing way.
|
||||
|
||||
| Prop | Data type | Required? | Description |
|
||||
| ------ | --------- | --------- | ---------------------------------------------------------- |
|
||||
|
@ -40,7 +49,7 @@ Popovers (`fw-popover`) are visually hidden menus of items activated by a simple
|
|||
|
||||
## Options button
|
||||
|
||||
The options button (`fw-options-button`) is a stylized button you can use to hide and show your popover. Use [Vue event handling](https://vuejs.org/guide/essentials/event-handling.html) to map the button to a boolean value.
|
||||
The options button (`OptionsButton`) is a stylized button you can use to hide and show your popover. Use [Vue event handling](https://vuejs.org/guide/essentials/event-handling.html) to map the button to a boolean value.
|
||||
|
||||
```vue{7}
|
||||
<script setup lang="ts">
|
||||
|
@ -48,15 +57,15 @@ The options button (`fw-options-button`) is a stylized button you can use to hid
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<fw-popover v-model:open="open">
|
||||
<fw-options-button @click="open = !open" />
|
||||
</fw-popover>
|
||||
<Popover v-model:open="open">
|
||||
<OptionsButton @click="open = !open" />
|
||||
</Popover>
|
||||
</template>
|
||||
```
|
||||
|
||||
<fw-popover v-model:open="emptyMenu">
|
||||
<fw-options-button @click="emptyMenu = !emptyMenu" />
|
||||
</fw-popover>
|
||||
<Popover v-model:open="emptyMenu">
|
||||
<OptionsButton @click="emptyMenu = !emptyMenu" />
|
||||
</Popover>
|
||||
|
||||
You can also use the `toggleOpen` prop in the `<template #default`> tag if you prefer not to use refs to control the menu's visibility.
|
||||
|
||||
|
@ -67,41 +76,41 @@ const bcPrivacy = ref('pod')
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<fw-popover>
|
||||
<Popover>
|
||||
<template #default="{ toggleOpen }">
|
||||
<fw-pill @click.stop="toggleOpen" :blue="bcPrivacy === 'pod'" :red="bcPrivacy === 'public'">
|
||||
<Pill @click.stop="toggleOpen" :blue="bcPrivacy === 'pod'" :red="bcPrivacy === 'public'">
|
||||
{{ bcPrivacy }}
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
</template>
|
||||
<template #items>
|
||||
<fw-popover-radio v-model="bcPrivacy" :choices="privacyChoices"/>
|
||||
<PopoverRadio v-model="bcPrivacy" :choices="privacyChoices"/>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
```
|
||||
|
||||
<fw-popover>
|
||||
<Popover>
|
||||
<template #default="{ toggleOpen }">
|
||||
<fw-pill @click.stop="toggleOpen" :blue="bcPrivacy === 'pod'" :red="bcPrivacy === 'public'">
|
||||
<Pill @click.stop="toggleOpen" :blue="bcPrivacy === 'pod'" :red="bcPrivacy === 'public'">
|
||||
{{ bcPrivacy }}
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
</template>
|
||||
<template #items>
|
||||
<fw-popover-radio v-model="bcPrivacy" :choices="privacyChoices"/>
|
||||
<PopoverRadio v-model="bcPrivacy" :choices="privacyChoices"/>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
|
||||
## Items
|
||||
|
||||
Popovers contain a list of menu items. Items can contain different information based on their type.
|
||||
|
||||
::: info
|
||||
Lists of items must be nested inside a `<template #items>` tag directly under the `<fw-popover>` tag.
|
||||
Lists of items must be nested inside a `<template #items>` tag directly under the `<Popover>` tag.
|
||||
:::
|
||||
|
||||
### Popover item
|
||||
|
||||
The popover item (`fw-popover-item`) is a simple button that uses [Vue event handling](https://vuejs.org/guide/essentials/event-handling.html). Each item contains a [slot](https://vuejs.org/guide/components/slots.html) which you can use to add a menu label and icon.
|
||||
The popover item (`PopoverItem`) is a simple button that uses [Vue event handling](https://vuejs.org/guide/essentials/event-handling.html). Each item contains a [slot](https://vuejs.org/guide/components/slots.html) which you can use to add a menu label and icon.
|
||||
|
||||
```vue{10-13}
|
||||
<script setup lang="ts">
|
||||
|
@ -110,31 +119,31 @@ The popover item (`fw-popover-item`) is a simple button that uses [Vue event han
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<fw-popover v-model:open="open">
|
||||
<fw-options-button @click="open = !open" />
|
||||
<Popover v-model:open="open">
|
||||
<OptionsButton @click="open = !open" />
|
||||
<template #items>
|
||||
<fw-popover-item @click="alert('Report this object?')">
|
||||
<PopoverItem @click="alert('Report this object?')">
|
||||
<i class="bi bi-exclamation" />
|
||||
Report
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
```
|
||||
|
||||
<fw-popover v-model:open="singleItemMenu">
|
||||
<fw-options-button @click="singleItemMenu = !singleItemMenu" />
|
||||
<Popover v-model:open="singleItemMenu">
|
||||
<OptionsButton @click="singleItemMenu = !singleItemMenu" />
|
||||
<template #items>
|
||||
<fw-popover-item @click="alert('Report this object?')">
|
||||
<PopoverItem @click="alert('Report this object?')">
|
||||
<i class="bi bi-exclamation" />
|
||||
Report
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
|
||||
### Checkbox
|
||||
|
||||
The checkbox (`fw-popover-checkbox`) is an item that acts as a selectable box. Use [`v-model`](https://vuejs.org/api/built-in-directives.html#v-model) to bind the checkbox to a boolean value. Each checkbox contains a [slot](https://vuejs.org/guide/components/slots.html) which you can use to add a menu label.
|
||||
The checkbox (`PopoverCheckbox`) is an item that acts as a selectable box. Use [`v-model`](https://vuejs.org/api/built-in-directives.html#v-model) to bind the checkbox to a boolean value. Each checkbox contains a [slot](https://vuejs.org/guide/components/slots.html) which you can use to add a menu label.
|
||||
|
||||
```vue{11-16}
|
||||
<script setup lang="ts">
|
||||
|
@ -144,35 +153,35 @@ const open = ref(false)
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<fw-popover v-model:open="open">
|
||||
<fw-options-button @click="open = !open" />
|
||||
<Popover v-model:open="open">
|
||||
<OptionsButton @click="open = !open" />
|
||||
<template #items>
|
||||
<fw-popover-checkbox v-model="bc">
|
||||
<PopoverCheckbox v-model="bc">
|
||||
Bandcamp
|
||||
</fw-popover-checkbox>
|
||||
<fw-popover-checkbox v-model="cc">
|
||||
</PopoverCheckbox>
|
||||
<PopoverCheckbox v-model="cc">
|
||||
Creative commons
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
```
|
||||
|
||||
<fw-popover v-model:open="checkboxMenu">
|
||||
<fw-options-button @click="checkboxMenu = !checkboxMenu" />
|
||||
<Popover v-model:open="checkboxMenu">
|
||||
<OptionsButton @click="checkboxMenu = !checkboxMenu" />
|
||||
<template #items>
|
||||
<fw-popover-checkbox v-model="bc">
|
||||
<PopoverCheckbox v-model="bc">
|
||||
Bandcamp
|
||||
</fw-popover-checkbox>
|
||||
<fw-popover-checkbox v-model="cc">
|
||||
</PopoverCheckbox>
|
||||
<PopoverCheckbox v-model="cc">
|
||||
Creative commons
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
|
||||
### Radio
|
||||
|
||||
The radio (`fw-popover-radio`) is an item that acts as a radio selector.
|
||||
The radio (`PopoverRadio`) is an item that acts as a radio selector.
|
||||
|
||||
| Prop | Data type | Required? | Description |
|
||||
| ------------ | --------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
@ -187,21 +196,21 @@ const privacy = ["public", "private", "pod"];
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<fw-popover v-model:open="open">
|
||||
<fw-options-button @click="open = !open" />
|
||||
<Popover v-model:open="open">
|
||||
<OptionsButton @click="open = !open" />
|
||||
<template #items>
|
||||
<fw-popover-radio v-model="currentChoice" :choices="choices" />
|
||||
<PopoverRadio v-model="currentChoice" :choices="choices" />
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
```
|
||||
|
||||
<fw-popover v-model:open="radioMenu">
|
||||
<fw-options-button @click="radioMenu = !radioMenu" />
|
||||
<Popover v-model:open="radioMenu">
|
||||
<OptionsButton @click="radioMenu = !radioMenu" />
|
||||
<template #items>
|
||||
<fw-popover-radio v-model="bcPrivacy" :choices="privacyChoices"/>
|
||||
<PopoverRadio v-model="bcPrivacy" :choices="privacyChoices"/>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
|
||||
### Separator
|
||||
|
||||
|
@ -215,37 +224,37 @@ const open = ref(false)
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<fw-popover v-model:open="open">
|
||||
<fw-options-button @click="open = !open" />
|
||||
<Popover v-model:open="open">
|
||||
<OptionsButton @click="open = !open" />
|
||||
<template #items>
|
||||
<fw-popover-checkbox v-model="bc">
|
||||
<PopoverCheckbox v-model="bc">
|
||||
Bandcamp
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
<hr>
|
||||
<fw-popover-checkbox v-model="cc">
|
||||
<PopoverCheckbox v-model="cc">
|
||||
Creative commons
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
```
|
||||
|
||||
<fw-popover v-model:open="seperatorMenu">
|
||||
<fw-options-button @click="seperatorMenu = !seperatorMenu" />
|
||||
<Popover v-model:open="seperatorMenu">
|
||||
<OptionsButton @click="seperatorMenu = !seperatorMenu" />
|
||||
<template #items>
|
||||
<fw-popover-checkbox v-model="bc">
|
||||
<PopoverCheckbox v-model="bc">
|
||||
Bandcamp
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
<hr>
|
||||
<fw-popover-checkbox v-model="cc">
|
||||
<PopoverCheckbox v-model="cc">
|
||||
Creative commons
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
|
||||
## Submenus
|
||||
|
||||
To create more complex menus, you can use submenus (`fw-popover-submenu`). Submenus are menu items which contain other menu items.
|
||||
To create more complex menus, you can use submenus (`PopoverSubmenu`). Submenus are menu items which contain other menu items.
|
||||
|
||||
```vue{10-18}
|
||||
<script setup lang="ts">
|
||||
|
@ -254,37 +263,37 @@ const open = ref(false)
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<fw-popover v-model:open="open">
|
||||
<fw-options-button @click="open = !open" />
|
||||
<Popover v-model:open="open">
|
||||
<OptionsButton @click="open = !open" />
|
||||
<template #items>
|
||||
<fw-popover-submenu>
|
||||
<PopoverSubmenu>
|
||||
<i class="bi bi-collection" />
|
||||
Organize and share
|
||||
<template #items>
|
||||
<fw-popover-checkbox v-model="bc">
|
||||
<PopoverCheckbox v-model="bc">
|
||||
Bandcamp
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
</template>
|
||||
</fw-popover-submenu>
|
||||
</PopoverSubmenu>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
```
|
||||
|
||||
<fw-popover v-model:open="subMenu">
|
||||
<fw-options-button @click="subMenu = !subMenu" />
|
||||
<Popover v-model:open="subMenu">
|
||||
<OptionsButton @click="subMenu = !subMenu" />
|
||||
<template #items>
|
||||
<fw-popover-submenu>
|
||||
<PopoverSubmenu>
|
||||
<i class="bi bi-collection" />
|
||||
Organize and share
|
||||
<template #items>
|
||||
<fw-popover-checkbox v-model="bc">
|
||||
<PopoverCheckbox v-model="bc">
|
||||
Bandcamp
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
</template>
|
||||
</fw-popover-submenu>
|
||||
</PopoverSubmenu>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
|
||||
## Extra items
|
||||
|
||||
|
@ -299,77 +308,77 @@ const open = ref(false)
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<fw-popover v-model:open="open">
|
||||
<fw-options-button @click="open = !open" />
|
||||
<Popover v-model:open="open">
|
||||
<OptionsButton @click="open = !open" />
|
||||
<template #items>
|
||||
<fw-popover-submenu>
|
||||
<PopoverSubmenu>
|
||||
<i class="bi bi-collection" />
|
||||
Organize and share
|
||||
<template #items>
|
||||
<fw-popover-checkbox v-model="bc">
|
||||
<PopoverCheckbox v-model="bc">
|
||||
Bandcamp
|
||||
<template #after>
|
||||
<fw-popover>
|
||||
<Popover>
|
||||
<template #default="{ toggleOpen }">
|
||||
<fw-pill @click.stop="toggleOpen" :blue="bcPrivacy === 'pod'" :red="bcPrivacy === 'public'">
|
||||
<Pill @click.stop="toggleOpen" :blue="bcPrivacy === 'pod'" :red="bcPrivacy === 'public'">
|
||||
{{ bcPrivacy }}
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
</template>
|
||||
<template #items>
|
||||
<fw-popover-radio v-model="bcPrivacy" :choices="privacyChoices"/>
|
||||
<PopoverRadio v-model="bcPrivacy" :choices="privacyChoices"/>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
<hr>
|
||||
<fw-popover-checkbox v-model="share">
|
||||
<PopoverCheckbox v-model="share">
|
||||
Share by link
|
||||
<template #after>
|
||||
<fw-button @click.stop="alert('Link copied to clipboard')" color="secondary" round icon="bi-link" />
|
||||
<fw-button @click.stop="alert('Here is your code')" color="secondary" round icon="bi-code" />
|
||||
<Button @click.stop="alert('Link copied to clipboard')" color="secondary" round icon="bi-link" />
|
||||
<Button @click.stop="alert('Here is your code')" color="secondary" round icon="bi-code" />
|
||||
</template>
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
</template>
|
||||
</fw-popover-submenu>
|
||||
</PopoverSubmenu>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
```
|
||||
|
||||
<fw-popover v-model:open="extraItemsMenu">
|
||||
<fw-options-button @click="extraItemsMenu = !extraItemsMenu" />
|
||||
<Popover v-model:open="extraItemsMenu">
|
||||
<OptionsButton @click="extraItemsMenu = !extraItemsMenu" />
|
||||
<template #items>
|
||||
<fw-popover-submenu>
|
||||
<PopoverSubmenu>
|
||||
<i class="bi bi-collection" />
|
||||
Organize and share
|
||||
<template #items>
|
||||
<fw-popover-checkbox v-model="bc">
|
||||
<PopoverCheckbox v-model="bc">
|
||||
Bandcamp
|
||||
<template #after>
|
||||
<fw-popover>
|
||||
<Popover>
|
||||
<template #default="{ toggleOpen }">
|
||||
<fw-pill @click.stop="toggleOpen" :blue="bcPrivacy === 'pod'" :red="bcPrivacy === 'public'">
|
||||
<Pill @click.stop="toggleOpen" :blue="bcPrivacy === 'pod'" :red="bcPrivacy === 'public'">
|
||||
{{ bcPrivacy }}
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
</template>
|
||||
<template #items>
|
||||
<fw-popover-radio v-model="bcPrivacy" :choices="privacyChoices"/>
|
||||
<PopoverRadio v-model="bcPrivacy" :choices="privacyChoices"/>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
<hr>
|
||||
<fw-popover-checkbox v-model="share">
|
||||
<PopoverCheckbox v-model="share">
|
||||
Share by link
|
||||
<template #after>
|
||||
<fw-button @click.stop="alert('Link copied to clipboard')" secondary round icon="bi-link" />
|
||||
<fw-button @click.stop="alert('Here is your code')" secondary round icon="bi-code" />
|
||||
<Button @click.stop="alert('Link copied to clipboard')" secondary round icon="bi-link" />
|
||||
<Button @click.stop="alert('Here is your code')" secondary round icon="bi-code" />
|
||||
</template>
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
</template>
|
||||
</fw-popover-submenu>
|
||||
</PopoverSubmenu>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
|
||||
## Menu
|
||||
|
||||
|
@ -387,202 +396,196 @@ const privacyChoices = ["private", "pod", "public"];
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<fw-popover v-model:open="open">
|
||||
<fw-options-button @click="open = !open" />
|
||||
<Popover v-model:open="open">
|
||||
<OptionsButton @click="open = !open" />
|
||||
<template #items>
|
||||
<fw-popover-item>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-arrow-up-right" />
|
||||
Play next
|
||||
</fw-popover-item>
|
||||
<fw-popover-item>
|
||||
</PopoverItem>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-arrow-down-right" />
|
||||
Append to queue
|
||||
</fw-popover-item>
|
||||
<fw-popover-submenu>
|
||||
</PopoverItem>
|
||||
<PopoverSubmenu>
|
||||
<i class="bi bi-music-note-list" />
|
||||
Add to playlist
|
||||
<template #items>
|
||||
<fw-popover-item>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-music-note-list" />
|
||||
Sample playlist
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
<hr />
|
||||
<fw-popover-item>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-plus-lg" />
|
||||
New playlist
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
</template>
|
||||
</fw-popover-submenu>
|
||||
</PopoverSubmenu>
|
||||
<hr />
|
||||
<fw-popover-item>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-heart" />
|
||||
Add to favorites
|
||||
</fw-popover-item>
|
||||
<fw-popover-submenu>
|
||||
</PopoverItem>
|
||||
<PopoverSubmenu>
|
||||
<i class="bi bi-collection" />
|
||||
Organize and share
|
||||
<template #items>
|
||||
<fw-popover-checkbox v-model="bc">
|
||||
<PopoverCheckbox v-model="bc">
|
||||
Bandcamp
|
||||
<template #after>
|
||||
<fw-popover>
|
||||
<Popover>
|
||||
<template #default="{ toggleOpen }">
|
||||
<fw-pill
|
||||
<Pill
|
||||
@click.stop="toggleOpen"
|
||||
:blue="bcPrivacy === 'pod'"
|
||||
:red="bcPrivacy === 'public'"
|
||||
>
|
||||
{{ bcPrivacy }}
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
</template>
|
||||
<template #items>
|
||||
<fw-popover-radio
|
||||
v-model="bcPrivacy"
|
||||
:choices="privacyChoices"
|
||||
/>
|
||||
<PopoverRadio v-model="bcPrivacy" :choices="privacyChoices" />
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
</fw-popover-checkbox>
|
||||
<fw-popover-checkbox v-model="cc">
|
||||
</PopoverCheckbox>
|
||||
<PopoverCheckbox v-model="cc">
|
||||
Creative Commons
|
||||
<template #after>
|
||||
<fw-popover>
|
||||
<Popover>
|
||||
<template #default="{ toggleOpen }">
|
||||
<fw-pill
|
||||
<Pill
|
||||
@click.stop="toggleOpen"
|
||||
:blue="ccPrivacy === 'pod'"
|
||||
:red="ccPrivacy === 'public'"
|
||||
>
|
||||
{{ ccPrivacy }}
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
</template>
|
||||
<template #items>
|
||||
<fw-popover-radio
|
||||
v-model="ccPrivacy"
|
||||
:choices="privacyChoices"
|
||||
/>
|
||||
<PopoverRadio v-model="ccPrivacy" :choices="privacyChoices" />
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
<hr />
|
||||
<fw-popover-item>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-plus-lg" />
|
||||
New library
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
<hr />
|
||||
<fw-popover-checkbox v-model="share">
|
||||
<PopoverCheckbox v-model="share">
|
||||
Share by link
|
||||
<template #after>
|
||||
<fw-button @click.stop color="secondary" round icon="bi-link" />
|
||||
<fw-button @click.stop color="secondary" round icon="bi-code" />
|
||||
<Button @click.stop color="secondary" round icon="bi-link" />
|
||||
<Button @click.stop color="secondary" round icon="bi-code" />
|
||||
</template>
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
</template>
|
||||
</fw-popover-submenu>
|
||||
<fw-popover-item>
|
||||
</PopoverSubmenu>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-cloud-download" />
|
||||
Download
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
<hr />
|
||||
<fw-popover-item>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-exclamation" />
|
||||
Report
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
```
|
||||
|
||||
<fw-popover v-model:open="fullMenu">
|
||||
<fw-options-button @click="fullMenu = !fullMenu" />
|
||||
<Popover v-model:open="fullMenu">
|
||||
<OptionsButton @click="fullMenu = !fullMenu" />
|
||||
<template #items>
|
||||
<fw-popover-item>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-arrow-up-right" />
|
||||
Play next
|
||||
</fw-popover-item>
|
||||
<fw-popover-item>
|
||||
</PopoverItem>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-arrow-down-right" />
|
||||
Append to queue
|
||||
</fw-popover-item>
|
||||
<fw-popover-submenu>
|
||||
</PopoverItem>
|
||||
<PopoverSubmenu>
|
||||
<i class="bi bi-music-note-list" />
|
||||
Add to playlist
|
||||
<template #items>
|
||||
<fw-popover-item>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-music-note-list" />
|
||||
Sample playlist
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
<hr>
|
||||
<fw-popover-item>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-plus-lg" />
|
||||
New playlist
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
</template>
|
||||
</fw-popover-submenu>
|
||||
</PopoverSubmenu>
|
||||
<hr>
|
||||
<fw-popover-item>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-heart" />
|
||||
Add to favorites
|
||||
</fw-popover-item>
|
||||
<fw-popover-submenu>
|
||||
</PopoverItem>
|
||||
<PopoverSubmenu>
|
||||
<i class="bi bi-collection" />
|
||||
Organize and share
|
||||
<template #items>
|
||||
<fw-popover-checkbox v-model="bc">
|
||||
<PopoverCheckbox v-model="bc">
|
||||
Bandcamp
|
||||
<template #after>
|
||||
<fw-popover>
|
||||
<Popover>
|
||||
<template #default="{ toggleOpen }">
|
||||
<fw-pill @click.stop="toggleOpen" :blue="bcPrivacy === 'pod'" :red="bcPrivacy === 'public'">
|
||||
<Pill @click.stop="toggleOpen" :blue="bcPrivacy === 'pod'" :red="bcPrivacy === 'public'">
|
||||
{{ bcPrivacy }}
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
</template>
|
||||
<template #items>
|
||||
<fw-popover-radio v-model="bcPrivacy" :choices="privacyChoices"/>
|
||||
<PopoverRadio v-model="bcPrivacy" :choices="privacyChoices"/>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
</fw-popover-checkbox>
|
||||
<fw-popover-checkbox v-model="cc">
|
||||
</PopoverCheckbox>
|
||||
<PopoverCheckbox v-model="cc">
|
||||
Creative Commons
|
||||
<template #after>
|
||||
<fw-popover>
|
||||
<Popover>
|
||||
<template #default="{ toggleOpen }">
|
||||
<fw-pill @click.stop="toggleOpen" :blue="ccPrivacy === 'pod'" :red="ccPrivacy === 'public'">
|
||||
<Pill @click.stop="toggleOpen" :blue="ccPrivacy === 'pod'" :red="ccPrivacy === 'public'">
|
||||
{{ ccPrivacy }}
|
||||
</fw-pill>
|
||||
</Pill>
|
||||
</template>
|
||||
<template #items>
|
||||
<fw-popover-radio v-model="ccPrivacy" :choices="privacyChoices"/>
|
||||
<PopoverRadio v-model="ccPrivacy" :choices="privacyChoices"/>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
</template>
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
<hr>
|
||||
<fw-popover-item>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-plus-lg" />
|
||||
New library
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
<hr>
|
||||
<fw-popover-checkbox v-model="share">
|
||||
<PopoverCheckbox v-model="share">
|
||||
Share by link
|
||||
<template #after>
|
||||
<fw-button @click.stop color="secondary" round icon="bi-link" />
|
||||
<fw-button @click.stop color="secondary" round icon="bi-code" />
|
||||
<Button @click.stop color="secondary" round icon="bi-link" />
|
||||
<Button @click.stop color="secondary" round icon="bi-code" />
|
||||
</template>
|
||||
</fw-popover-checkbox>
|
||||
</PopoverCheckbox>
|
||||
</template>
|
||||
</fw-popover-submenu>
|
||||
<fw-popover-item>
|
||||
</PopoverSubmenu>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-cloud-download" />
|
||||
Download
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
<hr>
|
||||
<fw-popover-item>
|
||||
<PopoverItem>
|
||||
<i class="bi bi-exclamation" />
|
||||
Report
|
||||
</fw-popover-item>
|
||||
</PopoverItem>
|
||||
</template>
|
||||
</fw-popover>
|
||||
</Popover>
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import Input from '~/components/ui/Input.vue'
|
||||
import Tabs from '~/components/ui/Tabs.vue'
|
||||
import Tab from '~/components/ui/Tab.vue'
|
||||
</script>
|
||||
|
||||
# Tabs
|
||||
|
||||
Tabs are used to hide information until a user chooses to see it. You can use tabs to show two sets of information on the same page without the user needing to navigate away.
|
||||
|
@ -9,59 +15,59 @@ Tabs are used to hide information until a user chooses to see it. You can use ta
|
|||
## Tabbed elements
|
||||
|
||||
::: warning
|
||||
The `<fw-tab>` component must be nested inside a `<fw-tabs>` component.
|
||||
The `<Tab>` component must be nested inside a `<Tabs>` component.
|
||||
:::
|
||||
|
||||
```vue-html
|
||||
<fw-tabs>
|
||||
<fw-tab title="Overview">Overview content</fw-tab>
|
||||
<fw-tab title="Activity">Activity content</fw-tab>
|
||||
</fw-tabs>
|
||||
<Tabs>
|
||||
<Tab title="Overview">Overview content</Tab>
|
||||
<Tab title="Activity">Activity content</Tab>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
<fw-tabs>
|
||||
<fw-tab title="Overview">Overview content</fw-tab>
|
||||
<fw-tab title="Activity">Activity content</fw-tab>
|
||||
</fw-tabs>
|
||||
<Tabs>
|
||||
<Tab title="Overview">Overview content</Tab>
|
||||
<Tab title="Activity">Activity content</Tab>
|
||||
</Tabs>
|
||||
|
||||
::: info
|
||||
If you add the same tab multiple times, the tab is rendered once with the combined content from the duplicates.
|
||||
:::
|
||||
|
||||
```vue-html{2,4}
|
||||
<fw-tabs>
|
||||
<fw-tab title="Overview">Overview content</fw-tab>
|
||||
<fw-tab title="Activity">Activity content</fw-tab>
|
||||
<fw-tab title="Overview">More overview content</fw-tab>
|
||||
</fw-tabs>
|
||||
<Tabs>
|
||||
<Tab title="Overview">Overview content</Tab>
|
||||
<Tab title="Activity">Activity content</Tab>
|
||||
<Tab title="Overview">More overview content</Tab>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
<fw-tabs>
|
||||
<fw-tab title="Overview">Overview content</fw-tab>
|
||||
<fw-tab title="Activity">Activity content</fw-tab>
|
||||
<fw-tab title="Overview">More overview content</fw-tab>
|
||||
</fw-tabs>
|
||||
<Tabs>
|
||||
<Tab title="Overview">Overview content</Tab>
|
||||
<Tab title="Activity">Activity content</Tab>
|
||||
<Tab title="Overview">More overview content</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Tabs-right slot
|
||||
|
||||
You can add a template to the right side of the tabs using the `#tabs-right` directive.
|
||||
|
||||
```vue-html{5-7}
|
||||
<fw-tabs>
|
||||
<fw-tab title="Overview">Overview content</fw-tab>
|
||||
<fw-tab title="Activity">Activity content</fw-tab>
|
||||
<Tabs>
|
||||
<Tab title="Overview">Overview content</Tab>
|
||||
<Tab title="Activity">Activity content</Tab>
|
||||
|
||||
<template #tabs-right>
|
||||
<fw-input icon="bi-search" placeholder="Search" />
|
||||
<Input icon="bi-search" placeholder="Search" />
|
||||
</template>
|
||||
</fw-tabs>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
<fw-tabs>
|
||||
<fw-tab title="Overview">Overview content</fw-tab>
|
||||
<fw-tab title="Activity">Activity content</fw-tab>
|
||||
<Tabs>
|
||||
<Tab title="Overview">Overview content</Tab>
|
||||
<Tab title="Activity">Activity content</Tab>
|
||||
|
||||
<template #tabs-right>
|
||||
<fw-input icon="bi-search" placeholder="Search" />
|
||||
<Input icon="bi-search" placeholder="Search" />
|
||||
</template>
|
||||
</fw-tabs>
|
||||
</Tabs>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import Textarea from '~/components/ui/Textarea.vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const text1 = ref('# Funk\nwhale')
|
||||
|
@ -25,13 +26,13 @@ Funkwhale supports Markdown syntax in textarea blocks.
|
|||
Create a textarea and attach its input to a value using a `v-model` directive.
|
||||
|
||||
```vue-html{2}
|
||||
<fw-textarea
|
||||
<Textarea
|
||||
v-model="text"
|
||||
/>
|
||||
```
|
||||
|
||||
<ClientOnly>
|
||||
<fw-textarea v-model="text1" />
|
||||
<Textarea v-model="text1" />
|
||||
</ClientOnly>
|
||||
|
||||
## Textarea max length
|
||||
|
@ -39,14 +40,14 @@ Create a textarea and attach its input to a value using a `v-model` directive.
|
|||
You can set the maximum length (in characters) that a user can enter in a textarea by passing a `max` prop.
|
||||
|
||||
```vue-html{3}
|
||||
<fw-textarea
|
||||
<Textarea
|
||||
v-model="text"
|
||||
:max="20"
|
||||
/>
|
||||
```
|
||||
|
||||
<ClientOnly>
|
||||
<fw-textarea v-model="text2" :max="20" />
|
||||
<Textarea v-model="text2" :max="20" />
|
||||
</ClientOnly>
|
||||
|
||||
## Textarea placeholder
|
||||
|
@ -54,12 +55,12 @@ You can set the maximum length (in characters) that a user can enter in a textar
|
|||
Add a placeholder to a textarea to guide users on what they should enter by passing a `placeholder` prop.
|
||||
|
||||
```vue-html{3}
|
||||
<fw-textarea
|
||||
<Textarea
|
||||
v-model="text"
|
||||
placeholder="Describe this track here…"
|
||||
/>
|
||||
```
|
||||
|
||||
<ClientOnly>
|
||||
<fw-textarea v-model="text3" placeholder="Describe this track here…" />
|
||||
<Textarea v-model="text3" placeholder="Describe this track here…" />
|
||||
</ClientOnly>
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import Toc from '~/components/ui/Toc.vue'
|
||||
</script>
|
||||
|
||||
# Table of Contents
|
||||
|
||||
The table of contents component renders a navigation bar on the right of the screen. Users can click on the items in the contents bar to skip to specific headers.
|
||||
|
@ -22,17 +26,17 @@ The table of contents component renders a navigation bar on the right of the scr
|
|||
By default table of contents only renders `<h1>` tags
|
||||
|
||||
```vue-html
|
||||
<fw-toc>
|
||||
<Toc>
|
||||
<h1>This is a Table of Contents</h1>
|
||||
Content...
|
||||
|
||||
<h1>It automatically generates from headings</h1>
|
||||
More content...
|
||||
</fw-toc>
|
||||
</Toc>
|
||||
```
|
||||
|
||||
<ClientOnly>
|
||||
<fw-toc>
|
||||
<Toc>
|
||||
<h1>This is a Table of Contents</h1>
|
||||
<p>
|
||||
In expedita ratione consequatur rerum et ullam architecto. Qui ut doloremque laboriosam perferendis corporis voluptatibus voluptates. Ad ducimus adipisci vitae mollitia quis. Aut placeat quaerat maxime velit et eius voluptas fugit. Omnis et et perspiciatis mollitia occaecati.
|
||||
|
@ -53,7 +57,7 @@ By default table of contents only renders `<h1>` tags
|
|||
<p>
|
||||
Qui impedit dicta earum. Qui repudiandae est magnam. Illum sit ratione exercitationem fugiat aut tempore. Ut sit deserunt ratione ut architecto deleniti ea magnam. Voluptatibus dignissimos voluptatem rem fugiat.
|
||||
</p>
|
||||
</fw-toc>
|
||||
</Toc>
|
||||
</ClientOnly>
|
||||
|
||||
## Custom headings
|
||||
|
@ -61,7 +65,7 @@ By default table of contents only renders `<h1>` tags
|
|||
You can specify the heading level you want to render in the table of contents by passing it to the `heading` prop.
|
||||
|
||||
```vue-html{2}
|
||||
<fw-toc
|
||||
<Toc
|
||||
heading="h2"
|
||||
>
|
||||
<h1>This is a Table of Contents</h1>
|
||||
|
@ -69,11 +73,11 @@ You can specify the heading level you want to render in the table of contents by
|
|||
|
||||
<h2>It automatically generates from headings</h2>
|
||||
More content...
|
||||
</fw-toc>
|
||||
</Toc>
|
||||
```
|
||||
|
||||
<ClientOnly>
|
||||
<fw-toc heading="h2">
|
||||
<Toc heading="h2">
|
||||
<h1>This is a Table of Contents</h1>
|
||||
<p>
|
||||
In expedita ratione consequatur rerum et ullam architecto. Qui ut doloremque laboriosam perferendis corporis voluptatibus voluptates. Ad ducimus adipisci vitae mollitia quis. Aut placeat quaerat maxime velit et eius voluptas fugit. Omnis et et perspiciatis mollitia occaecati.
|
||||
|
@ -94,5 +98,5 @@ You can specify the heading level you want to render in the table of contents by
|
|||
<p>
|
||||
Qui impedit dicta earum. Qui repudiandae est magnam. Illum sit ratione exercitationem fugiat aut tempore. Ut sit deserunt ratione ut architecto deleniti ea magnam. Voluptatibus dignissimos voluptatem rem fugiat.
|
||||
</p>
|
||||
</fw-toc>
|
||||
</Toc>
|
||||
</ClientOnly>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import Toggle from '~/components/ui/Toggle.vue'
|
||||
|
||||
import { ref } from 'vue'
|
||||
|
||||
const toggle = ref(false)
|
||||
|
@ -19,20 +21,20 @@ Toggles are basic form inputs that visually represent a boolean value. Toggles c
|
|||
Link your toggle to an input using the `v-model` directive.
|
||||
|
||||
```vue-html
|
||||
<fw-toggle v-model="toggle" />
|
||||
<Toggle v-model="toggle" />
|
||||
```
|
||||
|
||||
<fw-toggle v-model="toggle" />
|
||||
<Toggle v-model="toggle" />
|
||||
|
||||
## Big toggle
|
||||
|
||||
Pass a `big` prop to create a larger toggle.
|
||||
|
||||
```vue-html{2}
|
||||
<fw-toggle
|
||||
<Toggle
|
||||
big
|
||||
v-model="toggle"
|
||||
/>
|
||||
```
|
||||
|
||||
<fw-toggle big v-model="toggle2" />
|
||||
<Toggle big v-model="toggle2" />
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<script setup lang="ts">
|
||||
import Layout from '../src/components/ui/Layout.vue'
|
||||
import Card from '../src/components/ui/Card.vue'
|
||||
|
||||
// import { RouterLink, RouterView } from "vue-router";
|
||||
|
||||
// import { createWebHistory, createRouter } from 'vue-router'
|
||||
|
||||
// import HomeView from './components/layout.md'
|
||||
// import AboutView from './components/card.md'
|
||||
// import HomeView from './components/ui/layout.md'
|
||||
// import AboutView from './components/ui/card.md'
|
||||
|
||||
// const routes = [
|
||||
// { path: '/', component: HomeView },
|
||||
|
@ -43,9 +44,9 @@ in [Vue.js](https://vuejs.org) and [Sass](https://sass-lang.com).
|
|||
<Layout stack>
|
||||
|
||||
<a href="https://design.funkwhale.audio" style="text-decoration: none">
|
||||
<fw-card title="Looking for the designs?">
|
||||
<Card title="Looking for the designs?">
|
||||
Check out the design system on our Penpot.
|
||||
</fw-card>
|
||||
</Card>
|
||||
</a>
|
||||
|
||||
::: warning Deprecation of Activity, AlbumCard
|
||||
|
|
Loading…
Reference in New Issue