fix(ui):[WIP] replace popover menus, buttons, messages

This commit is contained in:
ArneBo 2024-12-07 11:46:42 +01:00 committed by upsiflu
parent 481fee8f5f
commit 4d78c2143c
11 changed files with 311 additions and 302 deletions

View File

@ -1,6 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import SemanticModal from '~/components/semantic/Modal.vue' import SemanticModal from '~/components/semantic/Modal.vue'
import ChannelUploadForm from '~/components/channels/UploadForm.vue' import ChannelUploadForm from '~/components/channels/UploadForm.vue'
import Popover from '~/components/ui/Popover.vue'
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
import Button from '~/components/ui/Button.vue'
import { humanSize } from '~/utils/filters' import { humanSize } from '~/utils/filters'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -45,6 +48,8 @@ const statusInfo = computed(() => {
const step = ref(1) const step = ref(1)
const isLoading = ref(false) const isLoading = ref(false)
const dropdownOpen = ref(false)
</script> </script>
<template> <template>
@ -86,70 +91,70 @@ const isLoading = ref(false)
</template> </template>
</div> </div>
<div class="ui hidden clearing divider mobile-only" /> <div class="ui hidden clearing divider mobile-only" />
<button <Button
v-if="step === 1" v-if="step === 1"
class="ui basic cancel button" color="secondary"
variant="outline"
@click="update(false)"
> >
{{ $t('components.channels.UploadModal.button.cancel') }} {{ $t('components.channels.UploadModal.button.cancel') }}
</button> </Button>
<button <Button
v-else-if="step < 3" v-else-if="step < 3"
class="ui basic button" color="secondary"
variant="outline"
@click.stop.prevent="uploadForm.step -= 1" @click.stop.prevent="uploadForm.step -= 1"
> >
{{ $t('components.channels.UploadModal.button.previous') }} {{ $t('components.channels.UploadModal.button.previous') }}
</button> </Button>
<button <Button
v-else-if="step === 3" v-else-if="step === 3"
class="ui basic button" color="secondary"
@click.stop.prevent="uploadForm.step -= 1" @click.stop.prevent="uploadForm.step -= 1"
> >
{{ $t('components.channels.UploadModal.button.update') }} {{ $t('components.channels.UploadModal.button.update') }}
</button> </Button>
<button <Button
v-if="step === 1" v-if="step === 1"
class="ui primary button" color="secondary"
@click.stop.prevent="uploadForm.step += 1" @click.stop.prevent="uploadForm.step += 1"
> >
{{ $t('components.channels.UploadModal.button.next') }} {{ $t('components.channels.UploadModal.button.next') }}
</button> </Button>
<div <div class="ui primary buttons">
v-if="step === 2" <Button
class="ui primary buttons" :is-loading="isLoading"
>
<button
:class="['ui', 'primary button', {loading: isLoading}]"
type="submit" type="submit"
:disabled="!statusData?.canSubmit || undefined" :disabled="!statusData?.canSubmit"
@click.prevent.stop="uploadForm.publish" @click.prevent.stop="uploadForm.publish"
> >
{{ $t('components.channels.UploadModal.button.publish') }} {{ $t('components.channels.UploadModal.button.publish') }}
</button> </Button>
<button
ref="dropdown" <Popover v-model:open="dropdownOpen">
v-dropdown <template #default="{ toggleOpen }">
class="ui floating dropdown icon button" <Button
:disabled="!statusData?.canSubmit || undefined" color="primary"
> icon="bi-chevron-down"
<i class="dropdown icon" /> :disabled="!statusData?.canSubmit"
<div class="menu"> @click="toggleOpen"
<div />
role="button" </template>
class="basic item" <template #items>
@click="update(false)" <PopoverItem @click="update(false)">
>
{{ $t('components.channels.UploadModal.button.finishLater') }} {{ $t('components.channels.UploadModal.button.finishLater') }}
</PopoverItem>
</template>
</Popover>
</div> </div>
</div>
</button> <Button
</div>
<button
v-if="step === 4" v-if="step === 4"
class="ui basic cancel button" color="secondary"
@click="update(false)" @click="update(false)"
> >
{{ $t('components.channels.UploadModal.button.close') }} {{ $t('components.channels.UploadModal.button.close') }}
</button> </Button>
</div> </div>
</semantic-modal> </semantic-modal>
</template> </template>

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import SemanticModal from '~/components/semantic/Modal.vue' import SemanticModal from '~/components/semantic/Modal.vue'
import Button from '~/components/ui/Button.vue'
import { ref } from 'vue' import { ref } from 'vue'
interface Events { interface Events {
@ -9,7 +10,7 @@ interface Events {
interface Props { interface Props {
action?: () => void action?: () => void
disabled?: boolean disabled?: boolean
confirmColor?: 'danger' | 'success' confirmColor?: 'destructive' | 'primary'
} }
const emit = defineEmits<Events>() const emit = defineEmits<Events>()
@ -30,7 +31,8 @@ const confirm = () => {
<template> <template>
<button <button
:class="[{disabled: disabled}]" class="funkwhale dangerous-button"
:class="{ 'is-disabled': disabled }"
:disabled="disabled" :disabled="disabled"
@click.prevent.stop="showModal = true" @click.prevent.stop="showModal = true"
> >
@ -51,17 +53,21 @@ const confirm = () => {
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">
<button class="ui basic cancel button"> <Button
color="secondary"
variant="outline"
@click="showModal = false"
>
{{ $t('components.common.DangerousButton.button.cancel') }} {{ $t('components.common.DangerousButton.button.cancel') }}
</button> </Button>
<button <Button
:class="['ui', 'confirm', confirmColor, 'button']" :color="confirmColor"
@click="confirm" @click="confirm"
> >
<slot name="modal-confirm"> <slot name="modal-confirm">
{{ $t('components.common.DangerousButton.button.confirm') }} {{ $t('components.common.DangerousButton.button.confirm') }}
</slot> </slot>
</button> </Button>
</div> </div>
</semantic-modal> </semantic-modal>
</button> </button>

View File

@ -1,35 +1,49 @@
<script setup lang="ts"> <script setup lang="ts">
import $ from 'jquery'
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { useStore } from '~/store' import { useStore } from '~/store'
import Alert from '~/components/ui/Alert.vue'
interface Message { interface Message {
content: string content: string
key: string key: string
color?: 'blue' | 'red' | 'purple' | 'green' | 'yellow'
error?: boolean | string
date?: Date
} }
const props = defineProps<{ message: Message }>() const props = defineProps<{ message: Message }>()
const isVisible = ref(true)
const store = useStore() const store = useStore()
onMounted(() => {
const params = { const messageColor = computed(() => {
context: '#app', if (props.message.color) {
message: props.message.content, return props.message.color
showProgress: 'top',
position: 'bottom right',
progressUp: true,
onRemove () {
store.commit('ui/removeMessage', props.message.key)
},
...props.message
} }
// @ts-expect-error fomantic ui if (props.message.error || props.message.content?.toLowerCase().includes('error')) {
$('body').toast(params) return 'red'
$('.ui.toast.visible').last().attr('role', 'alert') }
return 'blue'
})
onMounted(() => {
setTimeout(() => {
isVisible.value = false
store.commit('ui/removeMessage', props.message.key)
}, 5000)
}) })
</script> </script>
<template> <template>
<div /> <Transition name="fade">
<Alert
v-if="isVisible"
role="alert"
:color="messageColor"
class="is-notification"
>
{{ message.content }}
</Alert>
</Transition>
</template> </template>

View File

@ -10,6 +10,10 @@ import useReport from '~/composables/moderation/useReport'
import EmbedWizard from '~/components/audio/EmbedWizard.vue' import EmbedWizard from '~/components/audio/EmbedWizard.vue'
import SemanticModal from '~/components/semantic/Modal.vue' import SemanticModal from '~/components/semantic/Modal.vue'
import Button from '~/components/ui/Button.vue'
import Popover from '~/components/ui/Popover.vue'
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
import DangerousButton from '~/components/common/DangerousButton.vue'
interface Events { interface Events {
(e: 'remove'): void (e: 'remove'): void
@ -43,6 +47,8 @@ const musicbrainzUrl = computed(() => props.object?.mbid ? `https://musicbrainz.
const discogsUrl = computed(() => `https://discogs.com/search/?type=release&title=${encodeURI(props.object?.title)}&artist=${encodeURI(props.object?.artist_credit[0].artist.name)}`) const discogsUrl = computed(() => `https://discogs.com/search/?type=release&title=${encodeURI(props.object?.title)}&artist=${encodeURI(props.object?.artist_credit[0].artist.name)}`)
const remove = () => emit('remove') const remove = () => emit('remove')
const open = ref(false)
</script> </script>
<template> <template>
@ -65,120 +71,116 @@ const remove = () => emit('remove')
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">
<button class="ui basic deny button"> <Button
color="secondary"
variant="outline"
@click="showEmbedModal = false"
>
{{ $t('components.library.AlbumDropdown.button.cancel') }} {{ $t('components.library.AlbumDropdown.button.cancel') }}
</button> </Button>
</div> </div>
</semantic-modal> </semantic-modal>
<button <Popover v-model:open="open">
v-dropdown="{direction: 'downward'}" <template #default="{ toggleOpen }">
class="ui floating dropdown circular icon basic button" <Button
variant="ghost"
color="secondary"
round
icon="bi-three-dots-vertical"
:title="labels.more" :title="labels.more"
> @click="toggleOpen"
<i class="ellipsis vertical icon" /> />
<div class="menu"> </template>
<a
<template #items>
<PopoverItem
v-if="domain != $store.getters['instance/domain']" v-if="domain != $store.getters['instance/domain']"
:href="object.fid" :href="object.fid"
target="_blank" target="_blank"
class="basic item"
> >
<i class="external icon" /> <i class="bi bi-box-arrow-up-right" />
{{ $t('components.library.AlbumDropdown.link.domain') }} {{ $t('components.library.AlbumDropdown.link.domain') }}
</a> </PopoverItem>
<div <PopoverItem
v-if="isEmbedable" v-if="isEmbedable"
role="button"
class="basic item"
@click="showEmbedModal = !showEmbedModal" @click="showEmbedModal = !showEmbedModal"
> >
<i class="code icon" /> <i class="bi bi-code" />
{{ $t('components.library.AlbumDropdown.button.embed') }} {{ $t('components.library.AlbumDropdown.button.embed') }}
</div> </PopoverItem>
<a
<PopoverItem
v-if="isAlbum && musicbrainzUrl" v-if="isAlbum && musicbrainzUrl"
:href="musicbrainzUrl" :href="musicbrainzUrl"
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
class="basic item"
> >
<i class="external icon" /> <i class="bi bi-box-arrow-up-right" />
{{ $t('components.library.AlbumDropdown.link.musicbrainz') }} {{ $t('components.library.AlbumDropdown.link.musicbrainz') }}
</a> </PopoverItem>
<a
<PopoverItem
v-if="!isChannel && isAlbum" v-if="!isChannel && isAlbum"
:href="discogsUrl" :href="discogsUrl"
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
class="basic item"
> >
<i class="external icon" /> <i class="bi bi-box-arrow-up-right" />
{{ $t('components.library.AlbumDropdown.link.discogs') }} {{ $t('components.library.AlbumDropdown.link.discogs') }}
</a> </PopoverItem>
<router-link
<PopoverItem
v-if="object.is_local" v-if="object.is_local"
:to="{name: 'library.albums.edit', params: {id: object.id }}" :to="{name: 'library.albums.edit', params: {id: object.id }}"
class="basic item"
> >
<i class="edit icon" /> <i class="bi bi-pencil" />
{{ $t('components.library.AlbumDropdown.button.edit') }} {{ $t('components.library.AlbumDropdown.button.edit') }}
</router-link> </PopoverItem>
<dangerous-button
<PopoverItem
v-if="artistCredit[0] && $store.state.auth.authenticated && artistCredit[0].artist.channel && artistCredit[0].artist.attributed_to?.full_username === $store.state.auth.fullUsername" v-if="artistCredit[0] && $store.state.auth.authenticated && artistCredit[0].artist.channel && artistCredit[0].artist.attributed_to?.full_username === $store.state.auth.fullUsername"
:class="['ui', {loading: isLoading}, 'item']" >
<DangerousButton
:is-loading="isLoading"
@confirm="remove()" @confirm="remove()"
> >
<i class="ui trash icon" /> <i class="bi bi-trash" />
{{ $t('components.library.AlbumDropdown.button.delete') }} {{ $t('components.library.AlbumDropdown.button.delete') }}
<template #modal-header> </DangerousButton>
<p> </PopoverItem>
{{ $t('components.library.AlbumDropdown.modal.delete.header') }}
</p> <hr>
</template>
<template #modal-content> <PopoverItem
<div>
<p>
{{ $t('components.library.AlbumDropdown.modal.delete.content.warning') }}
</p>
</div>
</template>
<template #modal-confirm>
<p>
{{ $t('components.library.AlbumDropdown.button.delete') }}
</p>
</template>
</dangerous-button>
<div class="divider" />
<div
v-for="obj in getReportableObjects({album: object, channel: artistCredit[0]?.artist.channel})" v-for="obj in getReportableObjects({album: object, channel: artistCredit[0]?.artist.channel})"
:key="obj.target.type + obj.target.id" :key="obj.target.type + obj.target.id"
role="button" @click="report(obj)"
class="basic item"
@click.stop.prevent="report(obj)"
> >
<i class="share icon" /> {{ obj.label }} <i class="bi bi-flag" />
</div> {{ obj.label }}
<div class="divider" /> </PopoverItem>
<router-link
<hr>
<PopoverItem
v-if="$store.state.auth.availablePermissions['library']" v-if="$store.state.auth.availablePermissions['library']"
class="basic item"
:to="{name: 'manage.library.albums.detail', params: {id: object.id}}" :to="{name: 'manage.library.albums.detail', params: {id: object.id}}"
> >
<i class="wrench icon" /> <i class="bi bi-wrench" />
{{ $t('components.library.AlbumDropdown.link.moderation') }} {{ $t('components.library.AlbumDropdown.link.moderation') }}
</router-link> </PopoverItem>
<a
<PopoverItem
v-if="$store.state.auth.profile && $store.state.auth.profile?.is_superuser" v-if="$store.state.auth.profile && $store.state.auth.profile?.is_superuser"
class="basic item"
:href="$store.getters['instance/absoluteUrl'](`/api/admin/music/album/${object.id}`)" :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/album/${object.id}`)"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<i class="wrench icon" /> <i class="bi bi-wrench" />
{{ $t('components.library.AlbumDropdown.link.django') }} {{ $t('components.library.AlbumDropdown.link.django') }}
</a> </PopoverItem>
</div> </template>
</button> </Popover>
</span> </span>
</template> </template>

View File

@ -14,6 +14,8 @@ import SemanticModal from '~/components/semantic/Modal.vue'
import PlayButton from '~/components/audio/PlayButton.vue' import PlayButton from '~/components/audio/PlayButton.vue'
import RadioButton from '~/components/radios/Button.vue' import RadioButton from '~/components/radios/Button.vue'
import TagsList from '~/components/tags/List.vue' import TagsList from '~/components/tags/List.vue'
import Popover from '~/components/ui/Popover.vue'
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
import useReport from '~/composables/moderation/useReport' import useReport from '~/composables/moderation/useReport'
import useLogger from '~/composables/useLogger' import useLogger from '~/composables/useLogger'
@ -36,8 +38,6 @@ const nextTracksUrl = ref(null)
const totalAlbums = ref(0) const totalAlbums = ref(0)
const totalTracks = ref(0) const totalTracks = ref(0)
const dropdown = ref()
const logger = useLogger() const logger = useLogger()
const store = useStore() const store = useStore()
const router = useRouter() const router = useRouter()
@ -166,107 +166,103 @@ watch(() => props.id, fetchData, { immediate: true })
</div> </div>
</semantic-modal> </semantic-modal>
<div class="ui buttons"> <div class="ui buttons">
<Popover>
<template #default="{ toggleOpen }">
<button <button
class="ui button" class="ui button"
@click="dropdown.click()" @click="toggleOpen"
> >
{{ $t('components.library.ArtistBase.button.more') }} {{ $t('components.library.ArtistBase.button.more') }}
</button>
<button
ref="dropdown"
v-dropdown
class="ui floating dropdown icon button"
>
<i class="dropdown icon" /> <i class="dropdown icon" />
<div class="menu"> </button>
<a </template>
<template #items>
<PopoverItem
v-if="domain != $store.getters['instance/domain']" v-if="domain != $store.getters['instance/domain']"
:href="object.fid" :href="object.fid"
target="_blank" target="_blank"
class="basic item" class="funkwhale item"
> >
<i class="external icon" /> <i class="external icon" />
{{ $t('components.library.ArtistBase.link.domain', {domain: domain}) }} {{ $t('components.library.ArtistBase.link.domain', {domain: domain}) }}
</a> </PopoverItem>
<button <PopoverItem
v-if="publicLibraries.length > 0" v-if="publicLibraries.length > 0"
role="button" @click="showEmbedModal = !showEmbedModal"
class="basic item"
@click.prevent="showEmbedModal = !showEmbedModal"
> >
<i class="code icon" /> <i class="code icon" />
{{ $t('components.library.ArtistBase.button.embed') }} {{ $t('components.library.ArtistBase.button.embed') }}
</button> </PopoverItem>
<a
<PopoverItem
:href="wikipediaUrl" :href="wikipediaUrl"
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
class="basic item"
> >
<i class="wikipedia w icon" /> <i class="wikipedia w icon" />
{{ $t('components.library.ArtistBase.link.wikipedia') }} {{ $t('components.library.ArtistBase.link.wikipedia') }}
</a> </PopoverItem>
<a
<PopoverItem
v-if="musicbrainzUrl" v-if="musicbrainzUrl"
:href="musicbrainzUrl" :href="musicbrainzUrl"
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
class="basic item"
> >
<i class="external icon" /> <i class="external icon" />
{{ $t('components.library.ArtistBase.link.musicbrainz') }} {{ $t('components.library.ArtistBase.link.musicbrainz') }}
</a> </PopoverItem>
<a
<PopoverItem
:href="discogsUrl" :href="discogsUrl"
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
class="basic item"
> >
<i class="external icon" /> <i class="external icon" />
{{ $t('components.library.ArtistBase.link.discogs') }} {{ $t('components.library.ArtistBase.link.discogs') }}
</a> </PopoverItem>
<router-link
<PopoverItem
v-if="object.is_local" v-if="object.is_local"
:to="{name: 'library.artists.edit', params: {id: object.id }}" :to="{name: 'library.artists.edit', params: {id: object.id }}"
class="basic item"
> >
<i class="edit icon" /> <i class="edit icon" />
{{ $t('components.library.ArtistBase.button.edit') }} {{ $t('components.library.ArtistBase.button.edit') }}
</router-link> </PopoverItem>
<div class="divider" />
<div <hr>
<PopoverItem
v-for="obj in getReportableObjects({artist: object})" v-for="obj in getReportableObjects({artist: object})"
:key="obj.target.type + obj.target.id" :key="obj.target.type + obj.target.id"
role="button" @click="report(obj)"
class="basic item"
@click.stop.prevent="report(obj)"
> >
<i class="share icon" /> {{ obj.label }} <i class="share icon" /> {{ obj.label }}
</div> </PopoverItem>
<div class="divider" /> <hr>
<router-link
<PopoverItem
v-if="$store.state.auth.availablePermissions['library']" v-if="$store.state.auth.availablePermissions['library']"
class="basic item"
:to="{name: 'manage.library.artists.detail', params: {id: object.id}}" :to="{name: 'manage.library.artists.detail', params: {id: object.id}}"
> >
<i class="wrench icon" /> <i class="wrench icon" />
{{ $t('components.library.ArtistBase.link.moderation') }} {{ $t('components.library.ArtistBase.link.moderation') }}
</router-link> </PopoverItem>
<a
<PopoverItem
v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser" v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
class="basic item"
:href="$store.getters['instance/absoluteUrl'](`/api/admin/music/artist/${object.id}`)" :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/artist/${object.id}`)"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<i class="wrench icon" /> <i class="wrench icon" />
{{ $t('components.library.ArtistBase.link.django') }} {{ $t('components.library.ArtistBase.link.django') }}
</a> </PopoverItem>
</div> </template>
</button> </Popover>
</div>
</div> </div>
</div> </div>
</section> </section>

View File

@ -74,14 +74,12 @@ const toggleRadio = () => {
</script> </script>
<template> <template>
<button <Button
:class="['ui', 'primary', {'inverted': running}, 'icon', 'labeled', 'button']" :is-active="running"
color="primary"
icon="bi-broadcast"
@click="toggleRadio" @click="toggleRadio"
> >
<i
class="ui feed icon"
role="button"
/>
{{ buttonLabel }} {{ buttonLabel }}
</button> </Button>
</template> </template>

View File

@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import $ from 'jquery'
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap' import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
import { computed, onBeforeUnmount, ref, watchEffect } from 'vue' import { computed, onBeforeUnmount, ref, watchEffect } from 'vue'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { useStore } from '~/store' import { useStore } from '~/store'
import Modal from '~/components/ui/Modal.vue'
interface Events { interface Events {
(e: 'update:show', show: boolean): void (e: 'update:show', show: boolean): void
@ -26,55 +26,10 @@ const props = withDefaults(defineProps<Props>(), {
scrolling: false, scrolling: false,
additionalClasses: () => [] additionalClasses: () => []
}) })
const modal = ref()
const { activate, deactivate, pause, unpause } = useFocusTrap(modal, {
allowOutsideClick: true
})
const show = useVModel(props, 'show', emit)
const control = ref<JQuery | undefined>()
const initModal = () => {
control.value = $(modal.value).modal({
duration: 100,
onApprove: () => emit('approved'),
onDeny: () => emit('deny'),
onHidden: () => (show.value = false)
})
}
watchEffect(() => {
if (show.value) {
initModal()
emit('show')
control.value?.modal('show')
activate()
unpause()
document.body.classList.add('scrolling')
return
}
if (control.value) {
emit('hide')
control.value.modal('hide')
control.value.remove()
deactivate()
pause()
document.body.classList.remove('scrolling')
}
})
onBeforeUnmount(() => {
control.value?.modal('hide')
})
const store = useStore() const store = useStore()
const classes = computed(() => [ const classes = computed(() => [
...props.additionalClasses, ...props.additionalClasses,
'ui', 'modal',
{ {
active: show.value,
scrolling: props.scrolling, scrolling: props.scrolling,
'overlay fullscreen': props.fullscreen && ['phone', 'tablet'].includes(store.getters['ui/windowSize']) 'overlay fullscreen': props.fullscreen && ['phone', 'tablet'].includes(store.getters['ui/windowSize'])
} }
@ -82,14 +37,15 @@ const classes = computed(() => [
</script> </script>
<template> <template>
<div <Modal
ref="modal" :model-value="show"
@update:model-value="(value) => emit('update:show', value)"
:class="classes" :class="classes"
@approve="emit('approved')"
@deny="emit('deny')"
@show="emit('show')"
@hide="emit('hide')"
> >
<i <slot />
tabindex="0" </Modal>
class="close inside icon"
/>
<slot v-if="show" />
</div>
</template> </template>

View File

@ -3,10 +3,21 @@ import { useColorOrPastel, type ColorProps, type PastelProps } from '~/composabl
const props = defineProps<ColorProps | PastelProps>() const props = defineProps<ColorProps | PastelProps>()
const color = useColorOrPastel(() => props.color, 'secondary') const color = useColorOrPastel(() => props.color, 'secondary')
const emit = defineEmits<{
click: [event: MouseEvent]
}>()
const handleClick = (event: MouseEvent) => {
emit('click', event)
}
</script> </script>
<template> <template>
<button class="funkwhale is-colored pill" :class="[color]"> <button
type="button"
class="funkwhale is-colored pill"
:class="[color]"
@click.stop="handleClick"
>
<div v-if="!!$slots.image" class="pill-image"> <div v-if="!!$slots.image" class="pill-image">
<slot name="image" /> <slot name="image" />
</div> </div>

View File

@ -41,4 +41,25 @@
> .actions { > .actions {
margin-left: auto; margin-left: auto;
} }
// Add styles for when alert is used as a notification
&.is-notification {
position: fixed;
bottom: 1rem;
right: 1rem;
z-index: 1000;
min-width: 200px;
max-width: 400px;
&.fade-enter-active,
&.fade-leave-active {
transition: all 0.3s ease;
}
&.fade-enter-from,
&.fade-leave-to {
opacity: 0;
transform: translateY(1rem);
}
}
} }

View File

@ -81,7 +81,7 @@
} }
} }
@include docs { @if $docs {
color: var(--fw-text-color) !important; color: var(--fw-text-color) !important;
} }

View File

@ -2,7 +2,7 @@
import { inject, ref } from 'vue' import { inject, ref } from 'vue'
import { POPOVER_CONTEXT_INJECTION_KEY, type PopoverContext } from '~/injection-keys' import { POPOVER_CONTEXT_INJECTION_KEY, type PopoverContext } from '~/injection-keys'
const setId = defineEmit<[value: number]>('internal:id') const emit = defineEmits<{'internal:id': [value: number]}>()
const { parentPopoverContext } = defineProps<{ parentPopoverContext?: PopoverContext }>() const { parentPopoverContext } = defineProps<{ parentPopoverContext?: PopoverContext }>()
const { items, hoveredItem } = parentPopoverContext ?? inject(POPOVER_CONTEXT_INJECTION_KEY, { const { items, hoveredItem } = parentPopoverContext ?? inject(POPOVER_CONTEXT_INJECTION_KEY, {
@ -11,7 +11,7 @@ const { items, hoveredItem } = parentPopoverContext ?? inject(POPOVER_CONTEXT_IN
}) })
const id = items.value++ const id = items.value++
setId(id) emit('internal:id', id)
</script> </script>
<template> <template>