diff --git a/packages/docs/src/examples/v-alert/prop-duration.vue b/packages/docs/src/examples/v-alert/prop-duration.vue new file mode 100644 index 00000000000..4d7ee8f6d50 --- /dev/null +++ b/packages/docs/src/examples/v-alert/prop-duration.vue @@ -0,0 +1,25 @@ + + + diff --git a/packages/docs/src/pages/en/components/alerts.md b/packages/docs/src/pages/en/components/alerts.md index 97701b717a5..7959420d393 100644 --- a/packages/docs/src/pages/en/components/alerts.md +++ b/packages/docs/src/pages/en/components/alerts.md @@ -131,6 +131,12 @@ The **closable** prop adds a [v-icon](/components/icons) on the far right, after +#### Duration + +The **duration** prop causes the alert to automatically dismiss itself after the specified number of milliseconds. Set to `-1` (the default) to disable auto-dismiss. Combine with **closable** to give users both auto-dismiss and manual control. + + + The close icon automatically applies a default `aria-label` and is configurable by using the **close-label** prop or changing **close** value in your locale. ::: info diff --git a/packages/vuetify/src/components/VAlert/VAlert.tsx b/packages/vuetify/src/components/VAlert/VAlert.tsx index 42672ddc0e0..b30684c40d7 100644 --- a/packages/vuetify/src/components/VAlert/VAlert.tsx +++ b/packages/vuetify/src/components/VAlert/VAlert.tsx @@ -25,7 +25,7 @@ import { makeThemeProps, provideTheme } from '@/composables/theme' import { genOverlays, makeVariantProps, useVariant } from '@/composables/variant' // Utilities -import { toRef } from 'vue' +import { onMounted, onScopeDispose, toRef, watch } from 'vue' import { genericComponent, propsFactory } from '@/util' // Types @@ -57,6 +57,10 @@ export const makeVAlertProps = propsFactory({ type: String, default: '$vuetify.close', }, + duration: { + type: [Number, String], + default: -1, + }, icon: { type: [Boolean, String, Function, Object] as PropType, default: null, @@ -107,6 +111,28 @@ export const VAlert = genericComponent()({ setup (props, { emit, slots }) { const isActive = useProxiedModel(props, 'modelValue') + + let dismissTimer = -1 + function clearDismissTimer () { + if (dismissTimer !== -1) { + window.clearTimeout(dismissTimer) + dismissTimer = -1 + } + } + function scheduleDismiss () { + clearDismissTimer() + const ms = Number(props.duration) + if (ms > 0 && isActive.value) { + dismissTimer = window.setTimeout(() => { + isActive.value = false + dismissTimer = -1 + }, ms) + } + } + onMounted(scheduleDismiss) + watch(() => [props.duration, isActive.value], scheduleDismiss) + onScopeDispose(clearDismissTimer) + const icon = toRef(() => { if (props.icon === false) return undefined if (!props.type) return props.icon diff --git a/packages/vuetify/src/components/VSkeletonLoader/VSkeletonLoader.sass b/packages/vuetify/src/components/VSkeletonLoader/VSkeletonLoader.sass index d75b6de9aaa..e0c3e81f4f9 100644 --- a/packages/vuetify/src/components/VSkeletonLoader/VSkeletonLoader.sass +++ b/packages/vuetify/src/components/VSkeletonLoader/VSkeletonLoader.sass @@ -103,6 +103,27 @@ height: $skeleton-loader-chip-height max-width: $skeleton-loader-chip-width + &__chip-group + flex-wrap: wrap + + .v-skeleton-loader__chip + flex: 0 0 auto + + &__timeline-item, + &__timeline-item-two, + &__timeline-item-three + flex-wrap: nowrap + align-items: flex-start + + > .v-skeleton-loader__avatar + flex: 0 0 auto + align-self: center + + > .v-skeleton-loader__heading, + > .v-skeleton-loader__sentences, + > .v-skeleton-loader__paragraph + flex: 1 1 auto + + .v-skeleton-loader__bone flex: 1 1 auto margin-inline-start: 0 diff --git a/packages/vuetify/src/components/VSkeletonLoader/VSkeletonLoader.tsx b/packages/vuetify/src/components/VSkeletonLoader/VSkeletonLoader.tsx index 17e9477de10..12a33414d06 100644 --- a/packages/vuetify/src/components/VSkeletonLoader/VSkeletonLoader.tsx +++ b/packages/vuetify/src/components/VSkeletonLoader/VSkeletonLoader.tsx @@ -28,6 +28,7 @@ export const rootTypes = { card: 'image, heading', 'card-avatar': 'image, list-item-avatar', chip: 'chip', + 'chip-group': 'chip@5', 'date-picker': 'list-item, heading, divider, date-picker-options, date-picker-days, actions', 'date-picker-options': 'text, avatar@2', 'date-picker-days': 'avatar@28', @@ -52,6 +53,9 @@ export const rootTypes = { 'table-row': 'text@6', 'table-tfoot': 'text@2, avatar@2', text: 'text', + 'timeline-item': 'avatar, heading', + 'timeline-item-two': 'avatar, sentences', + 'timeline-item-three': 'avatar, paragraph', } as const function genBone (type: string, children: VSkeletonBones = []) {