mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
feat(ContextMenu/DropdownMenu): handle loading field in items
This commit is contained in:
@@ -14,8 +14,8 @@ const items = computed(() => [{
|
|||||||
onUpdateChecked(checked: boolean) {
|
onUpdateChecked(checked: boolean) {
|
||||||
showSidebar.value = checked
|
showSidebar.value = checked
|
||||||
},
|
},
|
||||||
onSelect(e?: Event) {
|
onSelect(e: Event) {
|
||||||
e?.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
label: 'Show Toolbar',
|
label: 'Show Toolbar',
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ const items = computed(() => [{
|
|||||||
onUpdateChecked(checked: boolean) {
|
onUpdateChecked(checked: boolean) {
|
||||||
showBookmarks.value = checked
|
showBookmarks.value = checked
|
||||||
},
|
},
|
||||||
onSelect(e?: Event) {
|
onSelect(e: Event) {
|
||||||
e?.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
label: 'Show History',
|
label: 'Show History',
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ const groups = computed(() => [{
|
|||||||
icon: 'i-heroicons-document-plus',
|
icon: 'i-heroicons-document-plus',
|
||||||
loading: loading.value,
|
loading: loading.value,
|
||||||
onSelect(e: Event) {
|
onSelect(e: Event) {
|
||||||
e?.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
toast.add({ title: 'New file added!' })
|
toast.add({ title: 'New file added!' })
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -48,7 +49,8 @@ const groups = computed(() => [{
|
|||||||
suffix: 'Create a new folder in the current directory or workspace.',
|
suffix: 'Create a new folder in the current directory or workspace.',
|
||||||
icon: 'i-heroicons-folder-plus',
|
icon: 'i-heroicons-folder-plus',
|
||||||
onSelect(e: Event) {
|
onSelect(e: Event) {
|
||||||
e?.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
toast.add({ title: 'New folder added!' })
|
toast.add({ title: 'New folder added!' })
|
||||||
},
|
},
|
||||||
kbds: ['meta', 'F']
|
kbds: ['meta', 'F']
|
||||||
@@ -57,7 +59,8 @@ const groups = computed(() => [{
|
|||||||
suffix: 'Add a hashtag to the current item.',
|
suffix: 'Add a hashtag to the current item.',
|
||||||
icon: 'i-heroicons-hashtag',
|
icon: 'i-heroicons-hashtag',
|
||||||
onSelect(e: Event) {
|
onSelect(e: Event) {
|
||||||
e?.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
toast.add({ title: 'Hashtag added!' })
|
toast.add({ title: 'Hashtag added!' })
|
||||||
},
|
},
|
||||||
kbds: ['meta', 'H']
|
kbds: ['meta', 'H']
|
||||||
@@ -66,7 +69,8 @@ const groups = computed(() => [{
|
|||||||
suffix: 'Add a label to the current item.',
|
suffix: 'Add a label to the current item.',
|
||||||
icon: 'i-heroicons-tag',
|
icon: 'i-heroicons-tag',
|
||||||
onSelect(e: Event) {
|
onSelect(e: Event) {
|
||||||
e?.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
toast.add({ title: 'Label added!' })
|
toast.add({ title: 'Label added!' })
|
||||||
},
|
},
|
||||||
kbds: ['meta', 'L']
|
kbds: ['meta', 'L']
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import theme from '#build/ui/context-menu'
|
import theme from '#build/ui/context-menu'
|
||||||
|
|
||||||
const items = [
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const items = computed(() => [
|
||||||
[{
|
[{
|
||||||
label: 'My account',
|
label: 'My account',
|
||||||
|
type: 'label' as const,
|
||||||
avatar: {
|
avatar: {
|
||||||
src: 'https://github.com/benjamincanac.png'
|
src: 'https://github.com/benjamincanac.png'
|
||||||
}
|
}
|
||||||
@@ -37,7 +40,16 @@ const items = [
|
|||||||
label: 'Collapse Pinned Tabs',
|
label: 'Collapse Pinned Tabs',
|
||||||
disabled: true
|
disabled: true
|
||||||
}], [{
|
}], [{
|
||||||
label: 'Refresh the Page'
|
label: 'Refresh the Page',
|
||||||
|
loading: loading.value,
|
||||||
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
loading.value = false
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
label: 'Clear Cookies and Refresh'
|
label: 'Clear Cookies and Refresh'
|
||||||
}, {
|
}, {
|
||||||
@@ -72,13 +84,13 @@ const items = [
|
|||||||
}
|
}
|
||||||
}]]
|
}]]
|
||||||
}]
|
}]
|
||||||
]
|
])
|
||||||
|
|
||||||
const sizes = Object.keys(theme.variants.size)
|
const sizes = Object.keys(theme.variants.size)
|
||||||
|
|
||||||
const size = ref('md' as const)
|
const size = ref('md' as const)
|
||||||
|
|
||||||
defineShortcuts(extractShortcuts(items))
|
defineShortcuts(extractShortcuts(items.value))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import theme from '#build/ui/dropdown-menu'
|
import theme from '#build/ui/dropdown-menu'
|
||||||
|
|
||||||
const items = [
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const items = computed(() => [
|
||||||
[{
|
[{
|
||||||
label: 'My account',
|
label: 'My account',
|
||||||
avatar: {
|
avatar: {
|
||||||
@@ -45,7 +47,7 @@ const items = [
|
|||||||
icon: 'i-heroicons-link',
|
icon: 'i-heroicons-link',
|
||||||
kbds: ['meta', 'i'],
|
kbds: ['meta', 'i'],
|
||||||
onSelect(e: Event) {
|
onSelect(e: Event) {
|
||||||
e?.preventDefault()
|
e.preventDefault()
|
||||||
console.log('Invite by link clicked')
|
console.log('Invite by link clicked')
|
||||||
}
|
}
|
||||||
}], [{
|
}], [{
|
||||||
@@ -80,8 +82,14 @@ const items = [
|
|||||||
label: 'New team',
|
label: 'New team',
|
||||||
icon: 'i-heroicons-plus',
|
icon: 'i-heroicons-plus',
|
||||||
kbds: ['meta', 'n'],
|
kbds: ['meta', 'n'],
|
||||||
onSelect() {
|
loading: loading.value,
|
||||||
console.log('New team clicked')
|
onSelect(e: Event) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
loading.value = false
|
||||||
|
}, 2000)
|
||||||
}
|
}
|
||||||
}], [{
|
}], [{
|
||||||
label: 'GitHub',
|
label: 'GitHub',
|
||||||
@@ -112,13 +120,13 @@ const items = [
|
|||||||
console.log('Logout clicked')
|
console.log('Logout clicked')
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
]
|
])
|
||||||
|
|
||||||
const sizes = Object.keys(theme.variants.size)
|
const sizes = Object.keys(theme.variants.size)
|
||||||
|
|
||||||
const size = ref('md' as const)
|
const size = ref('md' as const)
|
||||||
|
|
||||||
defineShortcuts(extractShortcuts(items))
|
defineShortcuts(extractShortcuts(items.value))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface ContextMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custo
|
|||||||
*/
|
*/
|
||||||
type?: 'label' | 'separator' | 'link' | 'checkbox'
|
type?: 'label' | 'separator' | 'link' | 'checkbox'
|
||||||
slot?: string
|
slot?: string
|
||||||
|
loading?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
checked?: boolean
|
checked?: boolean
|
||||||
open?: boolean
|
open?: boolean
|
||||||
@@ -43,6 +44,11 @@ export interface ContextMenuProps<T> extends Omit<ContextMenuRootProps, 'dir'> {
|
|||||||
* @defaultValue appConfig.ui.icons.check
|
* @defaultValue appConfig.ui.icons.check
|
||||||
*/
|
*/
|
||||||
checkedIcon?: string
|
checkedIcon?: string
|
||||||
|
/**
|
||||||
|
* The icon displayed when an item is loading.
|
||||||
|
* @defaultValue appConfig.ui.icons.loading
|
||||||
|
*/
|
||||||
|
loadingIcon?: string
|
||||||
/** The content of the menu. */
|
/** The content of the menu. */
|
||||||
content?: Omit<ContextMenuContentProps, 'as' | 'asChild' | 'forceMount'>
|
content?: Omit<ContextMenuContentProps, 'as' | 'asChild' | 'forceMount'>
|
||||||
/**
|
/**
|
||||||
@@ -113,6 +119,7 @@ const ui = computed(() => contextMenu({
|
|||||||
:portal="portal"
|
:portal="portal"
|
||||||
:label-key="labelKey"
|
:label-key="labelKey"
|
||||||
:checked-icon="checkedIcon"
|
:checked-icon="checkedIcon"
|
||||||
|
:loading-icon="loadingIcon"
|
||||||
>
|
>
|
||||||
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
|
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
|
||||||
<slot :name="name" v-bind="slotData" />
|
<slot :name="name" v-bind="slotData" />
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ interface ContextMenuContentProps<T> extends Omit<RadixContextMenuContentProps,
|
|||||||
sub?: boolean
|
sub?: boolean
|
||||||
labelKey: string
|
labelKey: string
|
||||||
checkedIcon?: string
|
checkedIcon?: string
|
||||||
|
loadingIcon?: string
|
||||||
class?: any
|
class?: any
|
||||||
ui: typeof _contextMenu
|
ui: typeof _contextMenu
|
||||||
uiOverride?: any
|
uiOverride?: any
|
||||||
@@ -51,7 +52,8 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
|
|||||||
<DefineItemTemplate v-slot="{ item, active, index }">
|
<DefineItemTemplate v-slot="{ item, active, index }">
|
||||||
<slot :name="item.slot || 'item'" :item="(item as T)" :index="index">
|
<slot :name="item.slot || 'item'" :item="(item as T)" :index="index">
|
||||||
<slot :name="item.slot ? `${item.slot}-leading`: 'item-leading'" :item="(item as T)" :active="active" :index="index">
|
<slot :name="item.slot ? `${item.slot}-leading`: 'item-leading'" :item="(item as T)" :active="active" :index="index">
|
||||||
<UIcon v-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, active })" />
|
<UIcon v-if="item.loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, loading: true })" />
|
||||||
|
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, active })" />
|
||||||
<UAvatar v-else-if="item.avatar" :size="((props.uiOverride?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: uiOverride?.itemLeadingAvatar, active })" />
|
<UAvatar v-else-if="item.avatar" :size="((props.uiOverride?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: uiOverride?.itemLeadingAvatar, active })" />
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
@@ -106,6 +108,8 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
|
|||||||
:items="item.children"
|
:items="item.children"
|
||||||
:align-offset="-4"
|
:align-offset="-4"
|
||||||
:label-key="labelKey"
|
:label-key="labelKey"
|
||||||
|
:checked-icon="checkedIcon"
|
||||||
|
:loading-icon="loadingIcon"
|
||||||
v-bind="item.content"
|
v-bind="item.content"
|
||||||
>
|
>
|
||||||
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
|
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface DropdownMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cust
|
|||||||
*/
|
*/
|
||||||
type?: 'label' | 'separator' | 'link' | 'checkbox'
|
type?: 'label' | 'separator' | 'link' | 'checkbox'
|
||||||
slot?: string
|
slot?: string
|
||||||
|
loading?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
checked?: boolean
|
checked?: boolean
|
||||||
open?: boolean
|
open?: boolean
|
||||||
@@ -43,6 +44,11 @@ export interface DropdownMenuProps<T> extends Omit<DropdownMenuRootProps, 'dir'>
|
|||||||
* @defaultValue appConfig.ui.icons.check
|
* @defaultValue appConfig.ui.icons.check
|
||||||
*/
|
*/
|
||||||
checkedIcon?: string
|
checkedIcon?: string
|
||||||
|
/**
|
||||||
|
* The icon displayed when an item is loading.
|
||||||
|
* @defaultValue appConfig.ui.icons.loading
|
||||||
|
*/
|
||||||
|
loadingIcon?: string
|
||||||
/**
|
/**
|
||||||
* The content of the menu.
|
* The content of the menu.
|
||||||
* @defaultValue { side: 'bottom', sideOffset: 8 }
|
* @defaultValue { side: 'bottom', sideOffset: 8 }
|
||||||
@@ -123,6 +129,7 @@ const ui = computed(() => dropdownMenu({
|
|||||||
:portal="portal"
|
:portal="portal"
|
||||||
:label-key="labelKey"
|
:label-key="labelKey"
|
||||||
:checked-icon="checkedIcon"
|
:checked-icon="checkedIcon"
|
||||||
|
:loading-icon="loadingIcon"
|
||||||
>
|
>
|
||||||
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
|
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
|
||||||
<slot :name="name" v-bind="slotData" />
|
<slot :name="name" v-bind="slotData" />
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ interface DropdownMenuContentProps<T> extends Omit<RadixDropdownMenuContentProps
|
|||||||
sub?: boolean
|
sub?: boolean
|
||||||
labelKey: string
|
labelKey: string
|
||||||
checkedIcon?: string
|
checkedIcon?: string
|
||||||
|
loadingIcon?: string
|
||||||
class?: any
|
class?: any
|
||||||
ui: typeof _dropdownMenu
|
ui: typeof _dropdownMenu
|
||||||
uiOverride?: any
|
uiOverride?: any
|
||||||
@@ -57,7 +58,8 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
|
|||||||
<DefineItemTemplate v-slot="{ item, active, index }">
|
<DefineItemTemplate v-slot="{ item, active, index }">
|
||||||
<slot :name="item.slot || 'item'" :item="(item as T)" :index="index">
|
<slot :name="item.slot || 'item'" :item="(item as T)" :index="index">
|
||||||
<slot :name="item.slot ? `${item.slot}-leading`: 'item-leading'" :item="(item as T)" :active="active" :index="index">
|
<slot :name="item.slot ? `${item.slot}-leading`: 'item-leading'" :item="(item as T)" :active="active" :index="index">
|
||||||
<UIcon v-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, active })" />
|
<UIcon v-if="item.loading" :name="loadingIcon || appConfig.ui.icons.loading" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, loading: true })" />
|
||||||
|
<UIcon v-else-if="item.icon" :name="item.icon" :class="ui.itemLeadingIcon({ class: uiOverride?.itemLeadingIcon, active })" />
|
||||||
<UAvatar v-else-if="item.avatar" :size="((props.uiOverride?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: uiOverride?.itemLeadingAvatar, active })" />
|
<UAvatar v-else-if="item.avatar" :size="((props.uiOverride?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.itemLeadingAvatar({ class: uiOverride?.itemLeadingAvatar, active })" />
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
@@ -115,6 +117,8 @@ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0
|
|||||||
:align-offset="-4"
|
:align-offset="-4"
|
||||||
:side-offset="3"
|
:side-offset="3"
|
||||||
:label-key="labelKey"
|
:label-key="labelKey"
|
||||||
|
:checked-icon="checkedIcon"
|
||||||
|
:loading-icon="loadingIcon"
|
||||||
v-bind="item.content"
|
v-bind="item.content"
|
||||||
>
|
>
|
||||||
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
|
<template v-for="(_, name) in proxySlots" #[name]="slotData: any">
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ export default (options: Required<ModuleOptions>) => ({
|
|||||||
itemLeadingIcon: ['text-[var(--ui-text-dimmed)] group-data-highlighted:text-[var(--ui-text)] group-data-[state=open]:text-[var(--ui-text)]', options.theme.transitions && 'transition-colors']
|
itemLeadingIcon: ['text-[var(--ui-text-dimmed)] group-data-highlighted:text-[var(--ui-text)] group-data-[state=open]:text-[var(--ui-text)]', options.theme.transitions && 'transition-colors']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
loading: {
|
||||||
|
true: {
|
||||||
|
itemLeadingIcon: 'animate-spin'
|
||||||
|
}
|
||||||
|
},
|
||||||
size: {
|
size: {
|
||||||
xs: {
|
xs: {
|
||||||
label: 'p-1 text-xs gap-1',
|
label: 'p-1 text-xs gap-1',
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ export default (options: Required<ModuleOptions>) => ({
|
|||||||
itemLeadingIcon: ['text-[var(--ui-text-dimmed)] group-data-highlighted:text-[var(--ui-text)] group-data-[state=open]:text-[var(--ui-text)]', options.theme.transitions && 'transition-colors']
|
itemLeadingIcon: ['text-[var(--ui-text-dimmed)] group-data-highlighted:text-[var(--ui-text)] group-data-[state=open]:text-[var(--ui-text)]', options.theme.transitions && 'transition-colors']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
loading: {
|
||||||
|
true: {
|
||||||
|
itemLeadingIcon: 'animate-spin'
|
||||||
|
}
|
||||||
|
},
|
||||||
size: {
|
size: {
|
||||||
xs: {
|
xs: {
|
||||||
label: 'p-1 text-xs gap-1',
|
label: 'p-1 text-xs gap-1',
|
||||||
|
|||||||
Reference in New Issue
Block a user