diff --git a/packages/docs/src/data/nav-alpha.json b/packages/docs/src/data/nav-alpha.json index 5de76434663..be9f303e2f0 100644 --- a/packages/docs/src/data/nav-alpha.json +++ b/packages/docs/src/data/nav-alpha.json @@ -58,6 +58,7 @@ "images", "item-groups", "lazy", + "lists", "overlays", "sheets", "theme-providers" diff --git a/packages/vuetify/src/components/VAvatar/VAvatar.sass b/packages/vuetify/src/components/VAvatar/VAvatar.sass index 842e07d8d1d..d70a895991b 100644 --- a/packages/vuetify/src/components/VAvatar/VAvatar.sass +++ b/packages/vuetify/src/components/VAvatar/VAvatar.sass @@ -12,7 +12,7 @@ vertical-align: $avatar-vertical-align @include avatar-sizes($avatar-sizes) - @include density('v-avatar', ('height', 'width'), $avatar-density) + @include avatar-density(('height', 'width'), $avatar-density) @include rounded($avatar-border-radius) > * @@ -21,3 +21,13 @@ &--rounded +rounded($avatar-rounded-border-radius) + +// VList +.v-avatar + .v-list-item-avatar & + overflow: visible + height: inherit + width: inherit + + > * + width: auto diff --git a/packages/vuetify/src/components/VAvatar/_mixins.scss b/packages/vuetify/src/components/VAvatar/_mixins.scss index 2acb07dd2b1..ecf5a3ad062 100644 --- a/packages/vuetify/src/components/VAvatar/_mixins.scss +++ b/packages/vuetify/src/components/VAvatar/_mixins.scss @@ -7,3 +7,20 @@ } } } + +@mixin avatar-density ($properties, $densities) { + @each $density, $multiplier in $densities { + $value: calc(var(--v-avatar-height) + #{$multiplier * $spacer}); + + &.v-avatar--density-#{$density} { + @if type-of($properties) == "list" { + @each $property in $properties { + #{$property}: $value; + } + } + @else { + #{$properties}: $value; + } + } + } +} diff --git a/packages/vuetify/src/components/VBtn/VBtn.sass b/packages/vuetify/src/components/VBtn/VBtn.sass index 0d2831d7dd6..c618b2db72e 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.sass +++ b/packages/vuetify/src/components/VBtn/VBtn.sass @@ -25,7 +25,7 @@ @at-root +button-sizes() - +density('v-btn', 'height', $button-density) + +button-density('height', $button-density) +states('.v-btn__overlay') @@ -35,7 +35,7 @@ padding: 0 @at-root & - +density('v-btn', ('width', 'height'), $button-icon-density) + +button-density(('width', 'height'), $button-icon-density) &--border +border($button-border-color, $button-border-style, $button-border-width) @@ -88,7 +88,7 @@ @at-root @include button-sizes($button-stacked-sizes, true) - @include density('v-btn', 'height', $button-stacked-density) + @include button-density('height', $button-stacked-density) .v-btn__overlay background-color: currentColor diff --git a/packages/vuetify/src/components/VBtn/_mixins.scss b/packages/vuetify/src/components/VBtn/_mixins.scss index 38a9548f9db..0d34e34c85f 100644 --- a/packages/vuetify/src/components/VBtn/_mixins.scss +++ b/packages/vuetify/src/components/VBtn/_mixins.scss @@ -18,3 +18,20 @@ } } } + +@mixin button-density ($properties, $densities) { + @each $density, $multiplier in $densities { + $value: calc(var(--v-btn-height) + #{$multiplier * $spacer}); + + &.v-btn--density-#{$density} { + @if type-of($properties) == "list" { + @each $property in $properties { + #{$property}: $value; + } + } + @else { + #{$properties}: $value; + } + } + } +} diff --git a/packages/vuetify/src/components/VCard/_variables.scss b/packages/vuetify/src/components/VCard/_variables.scss index 39c76c83578..c61d8af396a 100644 --- a/packages/vuetify/src/components/VCard/_variables.scss +++ b/packages/vuetify/src/components/VCard/_variables.scss @@ -53,7 +53,7 @@ $card-title-header-padding: 0 !default; $card-title-hyphens: auto !default; $card-title-letter-spacing: map-deep-get($typography, 'h6', 'letter-spacing') !default; $card-title-line-height: map-deep-get($typography, 'h6', 'line-height') !default; -$card-title-overflow-wrap: anywhere !default; +$card-title-overflow-wrap: normal !default; $card-title-padding-top: 1rem !default; $card-title-padding: .5rem 1rem !default; $card-title-text-transform: none !default; @@ -74,6 +74,8 @@ $card-title-densities: ( 'compact': $card-title-compact-line-height ); +$card-subtitle-densities: ('default': 0, 'comfortable': -1, 'compact': -2); + $card-subtitle-densities: () !default; $card-subtitle-densities: ( null: $card-subtitle-line-height, diff --git a/packages/vuetify/src/components/VList/VList.cy.spec.tsx b/packages/vuetify/src/components/VList/VList.cy.spec.tsx new file mode 100644 index 00000000000..d09677d4f63 --- /dev/null +++ b/packages/vuetify/src/components/VList/VList.cy.spec.tsx @@ -0,0 +1,54 @@ +/// + +import { CenteredGrid } from '@/../cypress/templates' +import { VList, VListItem } from '..' + +describe('VList', () => { + function mountFunction (content: JSX.Element) { + return cy.mount(() => content) + } + + it('supports the density property', () => { + const densities = ['default', 'comfortable', 'compact'] as const + const ListItems = densities.map(density => ( + + + density { density } + + + )) + const wrapper = mountFunction(( + +

ListItems by Density

+ + { ListItems } +
+ )) + + wrapper.get('.v-list--density-default').should('exist') + wrapper.get('.v-list--density-comfortable').should('exist') + wrapper.get('.v-list--density-compact').should('exist') + }) + + it('supports the lines property', () => { + const lines = ['one', 'two', 'three'] as const + const ListItems = lines.map(number => ( + + + lines { number } + + + )) + const wrapper = mountFunction(( + +

ListItems by Density

+ + { ListItems } +
+ )) + + wrapper.get('.v-list--one-line').should('exist') + wrapper.get('.v-list--two-line').should('exist') + wrapper.get('.v-list--three-line').should('exist') + }) +}) diff --git a/packages/vuetify/src/components/VList/VList.sass b/packages/vuetify/src/components/VList/VList.sass index 36f68c57806..647650e8b7e 100644 --- a/packages/vuetify/src/components/VList/VList.sass +++ b/packages/vuetify/src/components/VList/VList.sass @@ -1,98 +1,95 @@ -@import './_variables.scss' -@import './_mixins.sass' +// Imports +@import './index' -// Theme .v-list - &.primary, - &.secondary, - &.accent, - &.success, - &.error, - &.warning, - &.info - > .v-list-item - color: map-deep-get($material-dark, 'text', 'primary') - -+theme(v-list) using ($material) - background: map-get($material, 'cards') - color: map-deep-get($material, 'text','primary') - - .v-list--disabled - color: map-deep-get($material, 'text', 'disabled') - - .v-list-group--active:before, - .v-list-group--active:after - background: map-get($material, 'dividers') - -// Sheet -+sheet(v-list, $list-elevation, $list-border-radius, $list-shaped-border-radius) - -// Block -.v-list - display: block + background: $list-background + color: $list-color + overflow: auto padding: $list-padding - position: static - +elevationTransition() - -// Modifier -.v-list--disabled + position: relative + + @include border($list-border...) + @include elevation($list-elevation) + @include position($list-positions) + @include rounded($list-border-radius) + + &--border + border-width: $list-border-thin-width + + &--disabled + pointer-events: none + user-select: none + + > [class*='v-list-'] + opacity: $list-disabled-opacity + + &--subheader + padding-top: $list-subheader-padding-top + +.v-list-img + border-radius: inherit + display: flex + height: 100% + left: 0 + overflow: hidden + position: absolute + top: 0 + width: 100% + z-index: -1 + +.v-list-subheader + $root: & + + align-items: center + background: inherit + display: flex + font-size: $list-subheader-font-size + font-weight: $list-subheader-font-weight + line-height: $list-subheader-line-height + padding-inline-end: $list-subheader-padding-end + padding-inline-start: $list-subheader-padding-start + min-height: $list-subheader-min-height + transition: $list-subheader-transition + + &__text + opacity: $list-subheader-text-opacity + overflow: hidden + text-overflow: ellipsis + white-space: nowrap + + @at-root + @include density('v-list', $list-density) using ($modifier) + #{$root} + min-height: $list-subheader-min-height + ($modifier * $list-subheader-min-height-multiplier) + + &--inset + padding-inline-start: $list-subheader-inset-padding-start + + .v-list--nav & + font-size: $list-nav-subheader-font-size + + .v-list--subheader-sticky & + background: inherit + left: 0 + position: sticky + top: 0 + z-index: 1 + +.v-list__overlay + background-color: currentColor + border-radius: inherit + bottom: 0 + left: 0 + opacity: 0 pointer-events: none + position: absolute + right: 0 + top: 0 + transition: opacity 0.2s ease-in-out -.v-list--flat - .v-list-item:before - display: none - -.v-list--dense - .v-subheader - font-size: $list-dense-subheader-font-size - height: $list-dense-subheader-height - padding: $list-dense-subheader-padding - -.v-list--nav, -.v-list--rounded - .v-list-item:not(:last-child):not(:only-child) - margin-bottom: $list-nav-rounded-item-margin-bottom - - &.v-list--dense .v-list-item, - .v-list-item--dense - &:not(:last-child):not(:only-child) - margin-bottom: $list-nav-rounded-dense-item-margin-bottom - -.v-list--nav - padding-left: $list-nav-padding-left - padding-right: $list-nav-padding-right - - .v-list-item - padding: $list-nav-item-padding - - .v-list-item, - .v-list-item:before - border-radius: $list-nav-border-radius - -.v-list.v-sheet--shaped - +list-shaped($list-item-min-height) +// VNavigationDrawer - &.v-list--two-line - +list-shaped($list-item-two-line-min-height) - - &.v-list--three-line - +list-shaped($list-item-three-line-min-height) - - +ltr() - padding-right: $list-shaped-padding - - +rtl() - padding-left: $list-shaped-padding - -.v-list--rounded - padding: 8px - +list-rounded($list-item-min-height) - - &.v-list--two-line - +list-rounded($list-item-two-line-min-height) - - &.v-list--three-line - +list-rounded($list-item-three-line-min-height) - -.v-list--subheader - padding-top: $list-subheader-padding-top +.v-list + .v-navigation-drawer & + background: transparent + color: inherit diff --git a/packages/vuetify/src/components/VList/VList.ts b/packages/vuetify/src/components/VList/VList.ts deleted file mode 100644 index 765ab4392f8..00000000000 --- a/packages/vuetify/src/components/VList/VList.ts +++ /dev/null @@ -1,104 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// Styles -import './VList.sass' -import VListGroup from './VListGroup' - -// Components -import VSheet from '../VSheet/VSheet' - -// Types -import { VNode } from 'vue' - -type VListGroupInstance = InstanceType - -interface options extends InstanceType { - isInMenu: boolean - isInNav: boolean -} - -/* @vue/component */ -export default VSheet.extend().extend({ - name: 'v-list', - - provide (): object { - return { - isInList: true, - list: this, - } - }, - - inject: { - isInMenu: { - default: false, - }, - isInNav: { - default: false, - }, - }, - - props: { - dense: Boolean, - disabled: Boolean, - expand: Boolean, - flat: Boolean, - nav: Boolean, - rounded: Boolean, - subheader: Boolean, - threeLine: Boolean, - twoLine: Boolean, - }, - - data: () => ({ - groups: [] as VListGroupInstance[], - }), - - computed: { - classes (): object { - return { - ...VSheet.options.computed.classes.call(this), - 'v-list--dense': this.dense, - 'v-list--disabled': this.disabled, - 'v-list--flat': this.flat, - 'v-list--nav': this.nav, - 'v-list--rounded': this.rounded, - 'v-list--subheader': this.subheader, - 'v-list--two-line': this.twoLine, - 'v-list--three-line': this.threeLine, - } - }, - }, - - methods: { - register (content: VListGroupInstance) { - this.groups.push(content) - }, - unregister (content: VListGroupInstance) { - const index = this.groups.findIndex(g => g._uid === content._uid) - - if (index > -1) this.groups.splice(index, 1) - }, - listClick (uid: number) { - if (this.expand) return - - for (const group of this.groups) { - group.toggle(uid) - } - }, - }, - - render (h): VNode { - const data = { - staticClass: 'v-list', - class: this.classes, - style: this.styles, - attrs: { - role: this.isInNav || this.isInMenu ? undefined : 'list', - ...this.attrs$, - }, - } - - return h(this.tag, this.setBackgroundColor(this.color, data), [this.$slots.default]) - }, -}) diff --git a/packages/vuetify/src/components/VList/VList.tsx b/packages/vuetify/src/components/VList/VList.tsx new file mode 100644 index 00000000000..01d14cd9d80 --- /dev/null +++ b/packages/vuetify/src/components/VList/VList.tsx @@ -0,0 +1,92 @@ +// Styles +import './VList.sass' + +// Components +// import { VListItem } from '.' +import { VListSubheader } from './' + +// Composables +import { makeBorderProps, useBorder } from '@/composables/border' +import { makeDensityProps, useDensity } from '@/composables/density' +import { makeDimensionProps, useDimension } from '@/composables/dimensions' +import { makeElevationProps, useElevation } from '@/composables/elevation' +import { makeRoundedProps, useRounded } from '@/composables/rounded' +import { makeTagProps } from '@/composables/tag' +import { useBackgroundColor } from '@/composables/color' +import { makeThemeProps, useTheme } from '@/composables/theme' + +// Utilities +import { defineComponent, toRef } from 'vue' +import { makeProps } from '@/util' + +export default defineComponent({ + name: 'VList', + + props: makeProps({ + color: String, + disabled: Boolean, + lines: { + type: String, + default: 'one', + }, + nav: Boolean, + subheader: { + type: [Boolean, String], + default: false, + }, + ...makeBorderProps(), + ...makeDensityProps(), + ...makeDimensionProps(), + ...makeElevationProps(), + ...makeRoundedProps(), + ...makeTagProps(), + ...makeThemeProps(), + }), + + setup (props, { slots }) { + const { themeClasses } = useTheme(props) + const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(toRef(props, 'color')) + const { borderClasses } = useBorder(props, 'v-list') + const { densityClasses } = useDensity(props, 'v-list') + const { dimensionStyles } = useDimension(props) + const { elevationClasses } = useElevation(props) + const { roundedClasses } = useRounded(props, 'v-list') + + return () => { + const hasHeader = typeof props.subheader === 'string' || slots.subheader + + return ( + + { hasHeader && ( + slots.subheader + ? slots.subheader() + : { props.subheader } + ) } + + { slots.default?.() } + + ) + } + }, +}) diff --git a/packages/vuetify/src/components/VList/VListGroup.sass b/packages/vuetify/src/components/VList/VListGroup.sass deleted file mode 100644 index 2db6f39304b..00000000000 --- a/packages/vuetify/src/components/VList/VListGroup.sass +++ /dev/null @@ -1,111 +0,0 @@ -@import './_variables.scss' -@import './_mixins.sass' - -// Element -.v-list-group .v-list-group__header - .v-list-item__icon.v-list-group__header__append-icon - align-self: center - margin: 0 - min-width: $list-group-header-icon-min-width - justify-content: flex-end - -.v-list-group--sub-group - align-items: center - display: flex - flex-wrap: wrap - -.v-list-group__header - &.v-list-item--active:not(:hover):not(:focus):before - opacity: 0 - -.v-list-group__items - flex: 1 1 auto - - .v-list-item, - .v-list-group__items - overflow: hidden - -.v-list-group--active > .v-list-group__header - > .v-list-group__header__append-icon .v-icon - transform: rotate(-180deg) - - &.v-list-group__header--sub-group - > .v-list-group__header__prepend-icon .v-icon - transform: rotate(-180deg) - - - .v-list-item, - .v-list-item__content, - .v-list-group__header__prepend-icon .v-icon - color: inherit - -.v-list-group--sub-group - .v-list-item__action, - .v-list-item__avatar, - .v-list-item__icon - &:first-child - +ltr() - margin-right: $list-group-sub-group-child-margin - - +rtl() - margin-left: $list-group-sub-group-child-margin - - .v-list-group__header - +ltr() - padding-left: $list-group-sub-group-header-margin - - +rtl() - padding-right: $list-group-sub-group-header-margin - - - .v-list-group__items - .v-list-item - +ltr() - padding-left: $list-group-items-item-padding - - +rtl() - padding-right: $list-group-items-item-padding - - &.v-list-group--active - .v-list-item__icon.v-list-group__header__prepend-icon .v-icon - transform: rotate(-180deg) - -.v-list-group--no-action - > .v-list-group__items > .v-list-item - +ltr() - padding-left: $list-group-no-action-item-padding - - +rtl() - padding-right: $list-group-no-action-item-padding - - &.v-list-group--sub-group - > .v-list-group__items > .v-list-item - +ltr() - padding-left: $list-group-no-action-sub-group-item-padding - - +rtl() - padding-right: $list-group-no-action-sub-group-item-padding - -.v-list--dense - .v-list-group--sub-group .v-list-group__header - +ltr() - padding-left: $list-group-dense-sub-group-header-padding - - +rtl() - padding-right: $list-group-dense-sub-group-header-padding - - &.v-list--nav .v-list-group--no-action - > .v-list-group__items > .v-list-item - +ltr() - padding-left: $list-group-nav-no-action-item-padding - - +rtl() - padding-right: $list-group-nav-no-action-item-padding - - &.v-list-group--sub-group - > .v-list-group__items > .v-list-item - +ltr() - padding-left: $list-group-sub-group-item-padding - - +rtl() - padding-right: $list-group-sub-group-item-padding diff --git a/packages/vuetify/src/components/VList/VListGroup.ts b/packages/vuetify/src/components/VList/VListGroup.ts deleted file mode 100644 index 8b15875c467..00000000000 --- a/packages/vuetify/src/components/VList/VListGroup.ts +++ /dev/null @@ -1,224 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// Styles -import './VListGroup.sass' - -// Components -import VIcon from '../VIcon' -import VList from './VList' -import VListItem from './VListItem' -import VListItemIcon from './VListItemIcon' - -// Mixins -import BindsAttrs from '../../mixins/binds-attrs' -import Bootable from '../../mixins/bootable' -import Colorable from '../../mixins/colorable' -import Toggleable from '../../mixins/toggleable' -import { inject as RegistrableInject } from '../../mixins/registrable' - -// Directives -import ripple from '../../directives/ripple' - -// Transitions -import { VExpandTransition } from '../transitions' - -// Utils -import mixins, { ExtractVue } from '../../util/mixins' -import { getSlot } from '../../util/helpers' - -// Types -import { VNode } from 'vue' -// import { Route } from 'vue-router' - -const baseMixins = mixins( - BindsAttrs, - Bootable, - Colorable, - RegistrableInject('list'), - Toggleable -) - -type VListInstance = InstanceType - -interface options extends ExtractVue { - list: VListInstance - $refs: { - group: HTMLElement - } - $route: Route -} - -export default baseMixins.extend().extend({ - name: 'v-list-group', - - directives: { ripple }, - - props: { - activeClass: { - type: String, - default: '', - }, - appendIcon: { - type: String, - default: '$expand', - }, - color: { - type: String, - default: 'primary', - }, - disabled: Boolean, - group: String, - noAction: Boolean, - prependIcon: String, - ripple: { - type: [Boolean, Object], - default: true, - }, - subGroup: Boolean, - }, - - computed: { - classes (): object { - return { - 'v-list-group--active': this.isActive, - 'v-list-group--disabled': this.disabled, - 'v-list-group--no-action': this.noAction, - 'v-list-group--sub-group': this.subGroup, - } - }, - }, - - watch: { - isActive (val: boolean) { - /* istanbul ignore else */ - if (!this.subGroup && val) { - this.list && this.list.listClick(this._uid) - } - }, - $route: 'onRouteChange', - }, - - created () { - this.list && this.list.register(this) - - if (this.group && - this.$route && - this.value == null - ) { - this.isActive = this.matchRoute(this.$route.path) - } - }, - - beforeDestroy () { - this.list && this.list.unregister(this) - }, - - methods: { - click (e: Event) { - if (this.disabled) return - - this.isBooted = true - - this.$emit('click', e) - this.$nextTick(() => (this.isActive = !this.isActive)) - }, - genIcon (icon: string | false): VNode { - return this.$createElement(VIcon, icon) - }, - genAppendIcon (): VNode | null { - const icon = !this.subGroup ? this.appendIcon : false - - if (!icon && !this.$slots.appendIcon) return null - - return this.$createElement(VListItemIcon, { - staticClass: 'v-list-group__header__append-icon', - }, [ - this.$slots.appendIcon || this.genIcon(icon), - ]) - }, - genHeader (): VNode { - return this.$createElement(VListItem, { - staticClass: 'v-list-group__header', - attrs: { - 'aria-expanded': String(this.isActive), - role: 'button', - }, - class: { - [this.activeClass]: this.isActive, - }, - props: { - inputValue: this.isActive, - }, - directives: [{ - name: 'ripple', - value: this.ripple, - }], - on: { - ...this.listeners$, - click: this.click, - }, - }, [ - this.genPrependIcon(), - this.$slots.activator, - this.genAppendIcon(), - ]) - }, - genItems (): VNode[] { - return this.showLazyContent(() => [ - this.$createElement('div', { - staticClass: 'v-list-group__items', - directives: [{ - name: 'show', - value: this.isActive, - }], - }, getSlot(this)), - ]) - }, - genPrependIcon (): VNode | null { - const icon = this.subGroup && this.prependIcon == null - ? '$subgroup' - : this.prependIcon - - if (!icon && !this.$slots.prependIcon) return null - - return this.$createElement(VListItemIcon, { - staticClass: 'v-list-group__header__prepend-icon', - }, [ - this.$slots.prependIcon || this.genIcon(icon), - ]) - }, - onRouteChange (to: Route) { - /* istanbul ignore if */ - if (!this.group) return - - const isActive = this.matchRoute(to.path) - - /* istanbul ignore else */ - if (isActive && this.isActive !== isActive) { - this.list && this.list.listClick(this._uid) - } - - this.isActive = isActive - }, - toggle (uid: number) { - const isActive = this._uid === uid - - if (isActive) this.isBooted = true - this.$nextTick(() => (this.isActive = isActive)) - }, - matchRoute (to: string) { - return to.match(this.group) !== null - }, - }, - - render (h): VNode { - return h('div', this.setTextColor(this.isActive && this.color, { - staticClass: 'v-list-group', - class: this.classes, - }), [ - this.genHeader(), - h(VExpandTransition, this.genItems()), - ]) - }, -}) diff --git a/packages/vuetify/src/components/VList/VListImg.ts b/packages/vuetify/src/components/VList/VListImg.ts new file mode 100644 index 00000000000..4c1294705c2 --- /dev/null +++ b/packages/vuetify/src/components/VList/VListImg.ts @@ -0,0 +1,3 @@ +import { createSimpleFunctional } from '@/util' + +export default createSimpleFunctional('v-list-img') diff --git a/packages/vuetify/src/components/VList/VListItem.cy.spec.tsx b/packages/vuetify/src/components/VList/VListItem.cy.spec.tsx new file mode 100644 index 00000000000..23e7c162e0c --- /dev/null +++ b/packages/vuetify/src/components/VList/VListItem.cy.spec.tsx @@ -0,0 +1,23 @@ +/// + +import { CenteredGrid } from '@/../cypress/templates' +import { VListItem } from '..' + +describe('VListItem', () => { + function mountFunction (content: JSX.Element) { + return cy.mount(() => content) + } + + it('supports header text props, title and subtitle', () => { + const wrapper = mountFunction(( + +

ListItem Header Text

+ + +
+ )) + + wrapper.get('.v-list-item-title').contains('foo') + wrapper.get('.v-list-item-subtitle').contains('bar') + }) +}) diff --git a/packages/vuetify/src/components/VList/VListItem.sass b/packages/vuetify/src/components/VList/VListItem.sass index 6c67ab18607..da155a35134 100644 --- a/packages/vuetify/src/components/VList/VListItem.sass +++ b/packages/vuetify/src/components/VList/VListItem.sass @@ -1,266 +1,136 @@ -@import './_variables.scss' +@import './index' -// Theme -+theme(v-list-item) using ($material) - &--disabled - color: map-deep-get($material, 'text', 'disabled') - - &:not(.v-list-item--active):not(.v-list-item--disabled) - color: map-deep-get($material, 'text', 'primary') !important - - .v-list-item__mask - color: map-deep-get($material, 'text', 'disabled') - background: map-deep-get($material, 'expansion-panels', 'focus') - - .v-list-item__subtitle, - .v-list-item__action-text - color: map-deep-get($material, 'text', 'secondary') - - +states($material) - - &.v-list-item--highlighted - &::before - opacity: map-deep-get($material, 'states', 'pressed') - -// Block .v-list-item align-items: center display: flex - flex: 1 1 100% - letter-spacing: normal - min-height: $list-item-min-height - outline: none padding: $list-item-padding position: relative - text-decoration: none + outline: none + transition: $list-item-transition + + @include states('.v-list-item__overlay') &--disabled pointer-events: none + user-select: none + opacity: $list-disabled-opacity - &--selectable - user-select: auto + &--link + cursor: pointer - // Fix for IE11 where min-height does not work with - // align-items: center in flex containers - // https://github.com/philipwalton/flexbugs/issues/231 - &::after - content: '' - min-height: inherit - font-size: 0 +.v-list-item-avatar + $root: & -// Element -.v-list-item__action align-self: center - margin: $list-item-action-margin - - // Remove all margins when used - // in an item action. If user - // wants this they can not - // use this component - .v-input, - .v-input__control, - .v-input__slot, - .v-input--selection-controls__input - margin: 0 !important - - .v-input - padding: 0 - - .v-messages - display: none + transition: inherit + transition-property: height, width -.v-list-item__action-text - font-size: $list-item-action-text-font-size + &--start + margin-inline-end: $list-item-avatar-margin-end -.v-list-item__avatar - align-self: center - justify-content: flex-start - margin-bottom: $list-item-avatar-margin-y - margin-top: $list-item-avatar-margin-y - - &.v-list-item__avatar--horizontal - margin-bottom: $list-item-avatar-horizontal-margin - margin-top: $list-item-avatar-horizontal-margin + &--end + margin-inline-start: $list-item-avatar-margin-start - &:first-child - +ltr() - margin-left: $list-item-avatar-horizontal-margin-x + @at-root + @include density('v-list', $list-density) using ($modifier) + #{$root} + height: $list-item-avatar-size + ($modifier * $list-item-avatar-size-multiplier) + width: $list-item-avatar-size + ($modifier * $list-item-avatar-size-multiplier) - +rtl() - margin-right: $list-item-avatar-horizontal-margin-x + &.v-list--three-line #{$root} + margin-top: $list-item-avatar-margin-y - $modifier - &:last-child - +ltr() - margin-left: $list-item-avatar-horizontal-margin-x + .v-list--two-line & + margin-top: $list-item-avatar-margin-y + margin-bottom: $list-item-avatar-margin-y - +rtl() - margin-right: $list-item-avatar-horizontal-margin-x + .v-list--three-line & + align-self: flex-start -.v-list-item__content - align-items: center - align-self: center - display: flex - flex-wrap: wrap - flex: 1 1 - overflow: hidden - padding: $list-item-content-padding - - > * - line-height: 1.1 - flex: 1 0 100% - - &:not(:last-child) - margin-bottom: $list-item-content-children-margin-bottom - -.v-list-item__icon - align-self: flex-start - margin: $list-item-icon-margin - -// https://github.com/vuetifyjs/vuetify/issues/7930 -.v-list-item__action, -.v-list-item__avatar, -.v-list-item__icon - &:last-of-type:not(:only-child) - +ltr() - margin-left: $list-item-child-last-type-margin - - +rtl() - margin-right: $list-item-child-last-type-margin - -.v-list-item__avatar - &:first-child - +ltr() - margin-right: $list-item-avatar-first-child-margin - - +rtl() - margin-left: $list-item-avatar-first-child-margin - -.v-list-item__action, -.v-list-item__icon - &:first-child - +ltr() - margin-right: $list-item-action-icon-margin - - +rtl() - margin-left: $list-item-action-icon-margin - -.v-list-item__action, -.v-list-item__avatar, -.v-list-item__icon - display: inline-flex - min-width: $list-item-child-min-width - -// Increased specificity to beat -// out default typography rules -// line height plays a role in -// proper element sizing -.v-list-item .v-list-item__title, -.v-list-item .v-list-item__subtitle - line-height: $list-item-title-subtitle-line-height - -.v-list-item__title, -.v-list-item__subtitle - flex: 1 1 100% - overflow: hidden - text-overflow: ellipsis - white-space: nowrap +.v-list-item-media + margin-top: $list-item-media-margin-top + margin-bottom: $list-item-media-margin-bottom -.v-list-item__title - align-self: center - font-size: $list-item-title-font-size + &--start + margin-inline-end: $list-item-media-margin-end - > .v-badge - margin-top: 16px + &--end + margin-inline-start: $list-item-media-margin-start -.v-list-item__subtitle - font-size: $list-item-subtitle-font-size + .v-list--two-line & + margin-top: $list-item-media-two-line-margin-top + margin-bottom: $list-item-media-two-line-margin-bottom -// Modifier -.v-list-item--dense, -.v-list--dense .v-list-item - min-height: $list-dense-min-height + .v-list--three-line & + margin-top: $list-item-media-three-line-margin-top + margin-bottom: $list-item-media-three-line-margin-bottom - .v-list-item__icon - height: $list-dense-icon-height - margin-top: $list-dense-icon-margin - margin-bottom: $list-dense-icon-margin +.v-list-item-header + flex: 1 1 auto + min-width: 0 - .v-list-item__content - padding: $list-dense-content-padding +.v-list-item-subtitle + -webkit-box-orient: vertical + display: -webkit-box + opacity: $list-item-subtitle-opacity + overflow: hidden + padding: $list-item-subtitle-padding + text-overflow: ellipsis - .v-list-item__title, - .v-list-item__subtitle - font-size: $list-item-dense-title-font-size - font-weight: $list-item-dense-title-font-weight - line-height: $list-item-dense-title-line-height + .v-list--two-line & + -webkit-line-clamp: 1 - &.v-list-item--two-line - min-height: $list-item-dense-two-line-min-height + .v-list--three-line & + -webkit-line-clamp: 2 - &.v-list-item--three-line - min-height: $list-item-dense-three-line-min-height + @include typography($list-item-subtitle-typography...) -.v-list-item--link - cursor: pointer - user-select: none + .v-list--nav & + @include typography($list-item-nav-subtitle-typography...) - &:before - background-color: currentColor - bottom: 0 - content: '' - left: 0 - opacity: 0 - pointer-events: none - position: absolute - right: 0 - top: 0 - transition: $primary-transition - -// https://github.com/vuetifyjs/vuetify/issues/8327 -.v-list .v-list-item--active - color: inherit - - .v-icon - color: inherit - -.v-list-item__action--stack - align-items: flex-end - align-self: stretch - justify-content: space-between +.v-list-item-title + hyphens: $list-item-title-hyphens + overflow-wrap: $list-item-title-overflow-wrap + overflow: hidden + padding: $list-item-title-padding white-space: nowrap - flex-direction: column - -.v-list--two-line .v-list-item, -.v-list--three-line .v-list-item, -.v-list-item--two-line, -.v-list-item--three-line - .v-list-item__avatar:not(.v-list-item__avatar--horizontal), - .v-list-item__icon - margin-bottom: $list-item-icon-margin-y - margin-top: $list-item-icon-margin-y - -.v-list--two-line .v-list-item, -.v-list-item--two-line - min-height: $list-item-two-line-min-height - - .v-list-item__icon - margin-bottom: $list-item-two-line-icon-margin-bottom - -.v-list--three-line .v-list-item, -.v-list-item--three-line - min-height: $list-item-three-line-min-height - - .v-list-item__avatar, - .v-list-item__action - align-self: flex-start - margin-top: $list-item-three-line-avatar-action-margin - margin-bottom: $list-item-three-line-avatar-action-margin + text-overflow: ellipsis + word-break: $list-item-title-word-break + word-wrap: $list-item-title-word-wrap - .v-list-item__content - align-self: stretch + @include typography($list-item-title-typography...) - .v-list-item__subtitle - white-space: initial - -webkit-line-clamp: 2 - -webkit-box-orient: vertical - display: -webkit-box + .v-list--nav & + @include typography($list-item-nav-title-typography...) + +.v-list-item + $root: & + + @at-root + @include density('v-list', $list-density) using ($modifier) + &.v-list--one-line #{$root} + min-height: $list-item-min-height + $modifier + padding: (nth($list-item-padding, 1) + $modifier) nth($list-item-padding, 2) + + &.v-list--two-line #{$root} + min-height: $list-item-two-line-min-height + $modifier + padding: (nth($list-item-two-line-padding, 1) + $modifier) nth($list-item-two-line-padding, 2) + + &.v-list--three-line #{$root} + min-height: $list-item-three-line-min-height + $modifier + padding: (nth($list-item-three-line-padding, 1) + $modifier) nth($list-item-three-line-padding, 2) + +.v-list-item__overlay + background-color: currentColor + border-radius: inherit + bottom: 0 + left: 0 + opacity: 0 + pointer-events: none + position: absolute + right: 0 + top: 0 + transition: opacity 0.2s ease-in-out + + .v-list-item--active.v-list-item--contained & + --v-theme-overlay-multiplier: 0 diff --git a/packages/vuetify/src/components/VList/VListItem.ts b/packages/vuetify/src/components/VList/VListItem.ts deleted file mode 100644 index 9dad6b99b04..00000000000 --- a/packages/vuetify/src/components/VList/VListItem.ts +++ /dev/null @@ -1,190 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// Styles -import './VListItem.sass' - -// Mixins -import Colorable from '../../mixins/colorable' -import Routable from '../../mixins/routable' -import { factory as GroupableFactory } from '../../mixins/groupable' -import Themeable from '../../mixins/themeable' -import { factory as ToggleableFactory } from '../../mixins/toggleable' - -// Directives -import Ripple from '../../directives/ripple' - -// Utilities -import { keyCodes } from './../../util/helpers' -import { ExtractVue } from './../../util/mixins' -import { removed } from '../../util/console' - -// Types -import mixins from '../../util/mixins' -import { VNode } from 'vue' -import { PropType, PropValidator } from 'vue/types/options' - -const baseMixins = mixins( - Colorable, - Routable, - Themeable, - GroupableFactory('listItemGroup'), - ToggleableFactory('inputValue') -) - -interface options extends ExtractVue { - $el: HTMLElement - isInGroup: boolean - isInList: boolean - isInMenu: boolean - isInNav: boolean -} - -/* @vue/component */ -export default baseMixins.extend().extend({ - name: 'v-list-item', - - directives: { - Ripple, - }, - - inject: { - isInGroup: { - default: false, - }, - isInList: { - default: false, - }, - isInMenu: { - default: false, - }, - isInNav: { - default: false, - }, - }, - - inheritAttrs: false, - - props: { - activeClass: { - type: String, - default (): string | undefined { - if (!this.listItemGroup) return '' - - return this.listItemGroup.activeClass - }, - } as any as PropValidator, - dense: Boolean, - inactive: Boolean, - link: Boolean, - selectable: { - type: Boolean, - }, - tag: { - type: String, - default: 'div', - }, - threeLine: Boolean, - twoLine: Boolean, - value: null as any as PropType, - }, - - data: () => ({ - proxyClass: 'v-list-item--active', - }), - - computed: { - classes (): object { - return { - 'v-list-item': true, - ...Routable.options.computed.classes.call(this), - 'v-list-item--dense': this.dense, - 'v-list-item--disabled': this.disabled, - 'v-list-item--link': this.isClickable && !this.inactive, - 'v-list-item--selectable': this.selectable, - 'v-list-item--three-line': this.threeLine, - 'v-list-item--two-line': this.twoLine, - ...this.themeClasses, - } - }, - isClickable (): boolean { - return Boolean( - Routable.options.computed.isClickable.call(this) || - this.listItemGroup - ) - }, - }, - - created () { - /* istanbul ignore next */ - if (this.$attrs.hasOwnProperty('avatar')) { - removed('avatar', this) - } - }, - - methods: { - click (e: MouseEvent | KeyboardEvent) { - if (e.detail) this.$el.blur() - - this.$emit('click', e) - - this.to || this.toggle() - }, - genAttrs () { - const attrs: Record = { - 'aria-disabled': this.disabled ? true : undefined, - tabindex: this.isClickable && !this.disabled ? 0 : -1, - ...this.$attrs, - } - - if (this.$attrs.hasOwnProperty('role')) { - // do nothing, role already provided - } else if (this.isInNav) { - // do nothing, role is inherit - } else if (this.isInGroup) { - attrs.role = 'option' - attrs['aria-selected'] = String(this.isActive) - } else if (this.isInMenu) { - attrs.role = this.isClickable ? 'menuitem' : undefined - attrs.id = attrs.id || `list-item-${this._uid}` - } else if (this.isInList) { - attrs.role = 'listitem' - } - - return attrs - }, - }, - - render (h): VNode { - let { tag, data } = this.generateRouteLink() - - data.attrs = { - ...data.attrs, - ...this.genAttrs(), - } - data[this.to ? 'nativeOn' : 'on'] = { - ...data[this.to ? 'nativeOn' : 'on'], - keydown: (e: KeyboardEvent) => { - /* istanbul ignore else */ - if (e.keyCode === keyCodes.enter) this.click(e) - - this.$emit('keydown', e) - }, - } - - if (this.inactive) tag = 'div' - if (this.inactive && this.to) { - data.on = data.nativeOn - delete data.nativeOn - } - - const children = this.$scopedSlots.default - ? this.$scopedSlots.default({ - active: this.isActive, - toggle: this.toggle, - }) - : this.$slots.default - - return h(tag, this.setTextColor(this.color, data), children) - }, -}) diff --git a/packages/vuetify/src/components/VList/VListItem.tsx b/packages/vuetify/src/components/VList/VListItem.tsx new file mode 100644 index 00000000000..92b11f64bd9 --- /dev/null +++ b/packages/vuetify/src/components/VList/VListItem.tsx @@ -0,0 +1,163 @@ +// Styles +import './VListItem.sass' + +// Components +import { + VListItemAvatar, + VListItemHeader, + VListItemSubtitle, + VListItemTitle, +} from './' +import { VAvatar } from '@/components/VAvatar' + +// Composables +import { makeBorderProps, useBorder } from '@/composables/border' +import { makeDensityProps, useDensity } from '@/composables/density' +import { makeDimensionProps, useDimension } from '@/composables/dimensions' +import { makeElevationProps, useElevation } from '@/composables/elevation' +import { makeRoundedProps, useRounded } from '@/composables/rounded' +import { makeTagProps } from '@/composables/tag' +import { useColor } from '@/composables/color' +import { makeThemeProps, useTheme } from '@/composables/theme' + +// Directives +import { Ripple } from '@/directives/ripple' + +// Utilities +import { computed, defineComponent } from 'vue' +import { makeProps } from '@/util' + +export default defineComponent({ + name: 'VListItem', + + directives: { Ripple }, + + props: makeProps({ + active: Boolean, + activeColor: String, + activeClass: String, + appendAvatar: String, + appendIcon: String, + color: String, + disabled: Boolean, + link: Boolean, + prependAvatar: String, + prependIcon: String, + subtitle: String, + contained: String, + title: String, + ...makeBorderProps(), + ...makeDensityProps(), + ...makeDimensionProps(), + ...makeElevationProps(), + ...makeRoundedProps(), + ...makeTagProps(), + ...makeThemeProps(), + }), + + setup (props, { attrs, slots }) { + const { themeClasses } = useTheme(props) + const { colorClasses, colorStyles } = useColor(computed(() => { + const key = props.contained && props.active ? 'background' : 'text' + const color = (props.active && props.activeColor) || props.color + + return { [`${key}`]: color } + })) + const { borderClasses } = useBorder(props, 'v-list-item') + const { densityClasses } = useDensity(props, 'v-list-item') + const { dimensionStyles } = useDimension(props) + const { elevationClasses } = useElevation(props) + const { roundedClasses } = useRounded(props, 'v-list-item') + + return () => { + const hasTitle = (slots.title || props.title) + const hasSubtitle = (slots.subtitle || props.subtitle) + const hasHeader = !!(hasTitle || hasSubtitle) + const hasAppend = (slots.append || props.appendAvatar || props.appendIcon) + const hasPrepend = (slots.prepend || props.prependAvatar || props.prependIcon) + const isLink = !!(props.link || attrs.onClick || attrs.onClickOnce) + const isClickable = isLink && !props.disabled + + return ( + + { (isClickable || props.active) && (
) } + + { hasPrepend && ( + slots.prepend + ? slots.prepend() + : ( + + + + ) + ) } + + { hasHeader && ( + + { hasTitle && ( + + { slots.title + ? slots.title() + : props.title + } + + ) } + + { hasSubtitle && ( + + { slots.subtitle + ? slots.subtitle() + : props.subtitle + } + + ) } + + ) } + + { slots.default?.() } + + { hasAppend && ( + slots.append + ? slots.append() + : ( + + + + ) + ) } + + ) + } + }, +}) diff --git a/packages/vuetify/src/components/VList/VListItemAction.ts b/packages/vuetify/src/components/VList/VListItemAction.ts deleted file mode 100644 index 80f89945efd..00000000000 --- a/packages/vuetify/src/components/VList/VListItemAction.ts +++ /dev/null @@ -1,22 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// Types -import Vue, { VNode } from 'vue' - -/* @vue/component */ -export default Vue.extend({ - name: 'v-list-item-action', - - functional: true, - - render (h, { data, children = [] }): VNode { - data.staticClass = data.staticClass ? `v-list-item__action ${data.staticClass}` : 'v-list-item__action' - const filteredChild = children.filter(VNode => { - return VNode.isComment === false && VNode.text !== ' ' - }) - if (filteredChild.length > 1) data.staticClass += ' v-list-item__action--stack' - - return h('div', data, children) - }, -}) diff --git a/packages/vuetify/src/components/VList/VListItemAvatar.ts b/packages/vuetify/src/components/VList/VListItemAvatar.ts deleted file mode 100644 index bff1bf1ca6d..00000000000 --- a/packages/vuetify/src/components/VList/VListItemAvatar.ts +++ /dev/null @@ -1,40 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// Components -import VAvatar from '../VAvatar' - -// Types -import { VNode } from 'vue' - -/* @vue/component */ -export default VAvatar.extend({ - name: 'v-list-item-avatar', - - props: { - horizontal: Boolean, - size: { - type: [Number, String], - default: 40, - }, - }, - - computed: { - classes (): object { - return { - 'v-list-item__avatar--horizontal': this.horizontal, - ...VAvatar.options.computed.classes.call(this), - 'v-avatar--tile': this.tile || this.horizontal, - } - }, - }, - - render (h): VNode { - const render = VAvatar.options.render.call(this, h) - - render.data = render.data || {} - render.data.staticClass += ' v-list-item__avatar' - - return render - }, -}) diff --git a/packages/vuetify/src/components/VList/VListItemAvatar.tsx b/packages/vuetify/src/components/VList/VListItemAvatar.tsx new file mode 100644 index 00000000000..d5f51a5edfa --- /dev/null +++ b/packages/vuetify/src/components/VList/VListItemAvatar.tsx @@ -0,0 +1,33 @@ +// Composables +import { makeTagProps } from '@/composables/tag' + +// Utilities +import { defineComponent } from 'vue' +import { makeProps } from '@/util' + +export default defineComponent({ + name: 'VListItemAvatar', + + props: makeProps({ + left: Boolean, + right: Boolean, + ...makeTagProps(), + }), + + setup (props, { slots }) { + return () => { + return ( + + ) + } + }, +}) diff --git a/packages/vuetify/src/components/VList/VListItemGroup.sass b/packages/vuetify/src/components/VList/VListItemGroup.sass deleted file mode 100644 index 009499bbf28..00000000000 --- a/packages/vuetify/src/components/VList/VListItemGroup.sass +++ /dev/null @@ -1,5 +0,0 @@ -@import './_variables.scss' - -.v-list-item-group - .v-list-item--active - color: inherit diff --git a/packages/vuetify/src/components/VList/VListItemGroup.ts b/packages/vuetify/src/components/VList/VListItemGroup.ts deleted file mode 100644 index 00db0146448..00000000000 --- a/packages/vuetify/src/components/VList/VListItemGroup.ts +++ /dev/null @@ -1,48 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// Styles -import './VListItemGroup.sass' - -// Extensions -import { BaseItemGroup } from '../VItemGroup/VItemGroup' - -// Mixins -import Colorable from '../../mixins/colorable' - -// Utilities -import mixins from '../../util/mixins' - -export default mixins( - BaseItemGroup, - Colorable -).extend({ - name: 'v-list-item-group', - - provide () { - return { - isInGroup: true, - listItemGroup: this, - } - }, - - computed: { - classes (): object { - return { - ...BaseItemGroup.options.computed.classes.call(this), - 'v-list-item-group': true, - } - }, - }, - - methods: { - genData (): object { - return this.setTextColor(this.color, { - ...BaseItemGroup.options.methods.genData.call(this), - attrs: { - role: 'listbox', - }, - }) - }, - }, -}) diff --git a/packages/vuetify/src/components/VList/VListItemHeader.ts b/packages/vuetify/src/components/VList/VListItemHeader.ts new file mode 100644 index 00000000000..ea55f877bcd --- /dev/null +++ b/packages/vuetify/src/components/VList/VListItemHeader.ts @@ -0,0 +1,3 @@ +import { createSimpleFunctional } from '@/util' + +export default createSimpleFunctional('v-list-item-header') diff --git a/packages/vuetify/src/components/VList/VListItemIcon.ts b/packages/vuetify/src/components/VList/VListItemIcon.ts deleted file mode 100644 index f4538e14f16..00000000000 --- a/packages/vuetify/src/components/VList/VListItemIcon.ts +++ /dev/null @@ -1,18 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// Types -import Vue, { VNode } from 'vue' - -/* @vue/component */ -export default Vue.extend({ - name: 'v-list-item-icon', - - functional: true, - - render (h, { data, children }): VNode { - data.staticClass = (`v-list-item__icon ${data.staticClass || ''}`).trim() - - return h('div', data, children) - }, -}) diff --git a/packages/vuetify/src/components/VList/VListItemMedia.tsx b/packages/vuetify/src/components/VList/VListItemMedia.tsx new file mode 100644 index 00000000000..4e62eff1efd --- /dev/null +++ b/packages/vuetify/src/components/VList/VListItemMedia.tsx @@ -0,0 +1,33 @@ +// Composables +import { makeTagProps } from '@/composables/tag' + +// Utilities +import { defineComponent } from 'vue' +import { makeProps } from '@/util' + +export default defineComponent({ + name: 'VListItemMedia', + + props: makeProps({ + left: Boolean, + right: Boolean, + ...makeTagProps(), + }), + + setup (props, { slots }) { + return () => { + return ( + + ) + } + }, +}) diff --git a/packages/vuetify/src/components/VList/VListItemSubtitle.ts b/packages/vuetify/src/components/VList/VListItemSubtitle.ts new file mode 100644 index 00000000000..535d7fd848a --- /dev/null +++ b/packages/vuetify/src/components/VList/VListItemSubtitle.ts @@ -0,0 +1,3 @@ +import { createSimpleFunctional } from '@/util' + +export default createSimpleFunctional('v-list-item-subtitle') diff --git a/packages/vuetify/src/components/VList/VListItemTitle.ts b/packages/vuetify/src/components/VList/VListItemTitle.ts new file mode 100644 index 00000000000..79007b2f72f --- /dev/null +++ b/packages/vuetify/src/components/VList/VListItemTitle.ts @@ -0,0 +1,3 @@ +import { createSimpleFunctional } from '@/util' + +export default createSimpleFunctional('v-list-item-title') diff --git a/packages/vuetify/src/components/VList/VListSubheader.tsx b/packages/vuetify/src/components/VList/VListSubheader.tsx new file mode 100644 index 00000000000..d4047cdd5f0 --- /dev/null +++ b/packages/vuetify/src/components/VList/VListSubheader.tsx @@ -0,0 +1,40 @@ +// Composables +import { makeTagProps } from '@/composables/tag' +import { useTextColor } from '@/composables/color' + +// Utilities +import { defineComponent, toRef } from 'vue' +import { makeProps } from '@/util' + +export default defineComponent({ + name: 'VListSubheader', + + props: makeProps({ + color: String, + inset: Boolean, + ...makeTagProps(), + }), + + setup (props, { slots }) { + const { textColorClasses, textColorStyles } = useTextColor(toRef(props, 'color')) + + return () => ( + + { slots.default && ( +
+ { slots.default() } +
+ ) } +
+ ) + }, +}) diff --git a/packages/vuetify/src/components/VList/__tests__/VList.spec.ts b/packages/vuetify/src/components/VList/__tests__/VList.spec.ts index ca4039023fe..ed157a71e2f 100644 --- a/packages/vuetify/src/components/VList/__tests__/VList.spec.ts +++ b/packages/vuetify/src/components/VList/__tests__/VList.spec.ts @@ -1,88 +1,29 @@ -// @ts-nocheck -/* eslint-disable */ - // Components -// import VList from '../VList' - -// Utilities -import { - mount, - Wrapper, -} from '@vue/test-utils' - -describe.skip('VList.ts', () => { - type Instance = InstanceType - let mountFunction: (options?: object) => Wrapper - - beforeEach(() => { - mountFunction = (options = {}) => { - return mount(VList, { - ...options, - }) - } - }) - - it('should render component and match snapshot', () => { - const wrapper = mountFunction() - - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render a dense component and match snapshot', () => { - const wrapper = mountFunction({ - propsData: { - dense: true, - }, - }) - - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render a subheader component and match snapshot', () => { - const wrapper = mountFunction({ - propsData: { - subheader: true, - }, - }) +import { VList } from '..' - expect(wrapper.html()).toMatchSnapshot() - }) +// Composables +import { createDefaults, VuetifyDefaultsSymbol } from '@/composables/defaults' - it('should render a threeLine component and match snapshot', () => { - const wrapper = mountFunction({ - propsData: { - threeLine: true, +// Utilities +import { createTheme, VuetifyThemeSymbol } from '@/composables/theme' +import { mount } from '@vue/test-utils' + +describe('VList', () => { + function mountFunction (options = {}) { + return mount(VList, { + global: { + provide: { + [VuetifyDefaultsSymbol as symbol]: createDefaults(), + [VuetifyThemeSymbol as symbol]: createTheme(), + }, }, + ...options, }) + } - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render a twoLine component and match snapshot', () => { - const wrapper = mountFunction({ - propsData: { - twoLine: true, - }, - }) + it('should match a snapshot', () => { + const wrapper = mountFunction() expect(wrapper.html()).toMatchSnapshot() }) - - it('should have an inferred role from injections', () => { - const wrapper = mountFunction({ - provide: { isInMenu: true }, - }) - - expect(wrapper.element.getAttribute('role')).toBeNull() - - const wrapper2 = mountFunction({ - provide: { isInNav: true }, - }) - - expect(wrapper2.element.getAttribute('role')).toBeNull() - - const wrapper3 = mountFunction() - - expect(wrapper3.element.getAttribute('role')).toBe('list') - }) }) diff --git a/packages/vuetify/src/components/VList/__tests__/VListGroup.spec.ts b/packages/vuetify/src/components/VList/__tests__/VListGroup.spec.ts deleted file mode 100644 index 82243067255..00000000000 --- a/packages/vuetify/src/components/VList/__tests__/VListGroup.spec.ts +++ /dev/null @@ -1,196 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// Components -// import VListGroup from '../VListGroup' - -// Utilities -import { - mount, - Wrapper, -} from '@vue/test-utils' - -describe.skip('VListGroup.ts', () => { - type Instance = InstanceType - let mountFunction: (options?: object) => Wrapper - - beforeEach(() => { - mountFunction = (options = {}) => { - return mount(VListGroup, { - ...options, - }) - } - }) - - it('should render component and match snapshot', () => { - const wrapper = mountFunction() - - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should open if no value provided and group matches route', async () => { - const $route = { path: '/foo' } - const listClick = jest.fn() - const wrapper = mountFunction({ - provide: { - list: { - listClick, - register: jest.fn(), - unregister: jest.fn(), - }, - }, - propsData: { - group: 'foo', - }, - mocks: { - $route, - }, - }) - - await wrapper.vm.$nextTick() - expect(listClick).toHaveBeenCalledWith(wrapper.vm._uid) - }) - - it('should toggle when clicked', async () => { - const wrapper = mountFunction() - - const input = jest.fn() - wrapper.vm.$on('input', input) - wrapper.vm.click() - await wrapper.vm.$nextTick() - expect(input).toHaveBeenCalledWith(true) - }) - - it('should register when mounted', () => { - const register = jest.fn() - const wrapper = mountFunction({ - provide: { - list: { - register, - unregister: () => {}, - }, - }, - }) - - expect(register).toHaveBeenCalledWith(wrapper.vm) - }) - - it('should unregister when destroyed', async () => { - const unregister = jest.fn() - const wrapper = mountFunction({ - provide: { - list: { - register: () => {}, - unregister, - }, - }, - }) - - wrapper.destroy() - await wrapper.vm.$nextTick() - expect(unregister).toHaveBeenCalledWith(wrapper.vm) - }) - - it('should render a custom affix icons', async () => { - const wrapper = mountFunction({ - slots: { - appendIcon: { - render: h => h('span', 'foo'), - }, - prependIcon: { - render: h => h('span', 'bar'), - }, - }, - }) - - expect(wrapper.html()).toContain('foo') - expect(wrapper.html()).toContain('bar') - }) - - it('should respond to keydown.enter on header', () => { - const click = jest.fn() - const wrapper = mountFunction({ - methods: { click }, - slots: { - activator: { - template: 'foo', - }, - }, - }) - - const span = wrapper.find('span') - - span.trigger('keydown.enter') - - expect(click).toHaveBeenCalled() - }) - - it('should set active state if route changes and group present', async () => { - const listClick = jest.fn() - const $route = { path: '/bar' } - const wrapper = mountFunction({ - provide: { - list: { - listClick, - register: () => {}, - unregister: () => {}, - }, - }, - propsData: { - group: 'foo', - }, - mocks: { $route }, - }) - - expect(wrapper.vm.isActive).toBe(false) - - // Simulate route changing - wrapper.vm.$route.path = '/foo' - wrapper.vm.onRouteChange(wrapper.vm.$route) - - expect(wrapper.vm.isActive).toBe(true) - expect(listClick).toHaveBeenCalledWith(wrapper.vm._uid) - }) - - it('should not react to clicks when disabled', () => { - const wrapper = mountFunction({ - propsData: { - disabled: true, - }, - slots: { - activator: { template: 'foo' }, - }, - }) - - const span = wrapper.find('span.bar') - - expect(wrapper.vm.isActive).toBe(false) - span.trigger('click') - expect(wrapper.vm.isActive).toBe(false) - }) - - it('should toggle is uid matches', async () => { - const wrapper = mountFunction() - - expect(wrapper.vm.isActive).toBe(false) - wrapper.vm.toggle(100) - await wrapper.vm.$nextTick() - expect(wrapper.vm.isActive).toBe(false) - wrapper.vm.toggle(wrapper.vm._uid) - await wrapper.vm.$nextTick() - expect(wrapper.vm.isActive).toBe(true) - }) - - it('should have the correct a11y attributes', () => { - const wrapper = mountFunction() - const header = wrapper.find('.v-list-group__header') - - expect(header.element.tabIndex).toBe(0) - expect(header.element.getAttribute('aria-expanded')).toBe('false') - expect(header.element.getAttribute('role')).toBe('button') - - wrapper.setData({ isActive: true }) - - expect(header.element.getAttribute('aria-expanded')).toBe('true') - }) -}) diff --git a/packages/vuetify/src/components/VList/__tests__/VListItem.spec.ts b/packages/vuetify/src/components/VList/__tests__/VListItem.spec.ts index dc882189a9a..716614fc60b 100644 --- a/packages/vuetify/src/components/VList/__tests__/VListItem.spec.ts +++ b/packages/vuetify/src/components/VList/__tests__/VListItem.spec.ts @@ -1,224 +1,61 @@ -// @ts-nocheck -/* eslint-disable */ - // Components -// import VListItem from '../VListItem' +import { VListItem } from '..' -// Utilities -import { - mount, - Wrapper, -} from '@vue/test-utils' - -describe.skip('VListItem.ts', () => { - type Instance = InstanceType - let mountFunction: (options?: object) => Wrapper - - beforeEach(() => { - mountFunction = (options = {}) => { - return mount(VListItem, { - ...options, - }) - } - }) +// Composables +import { createDefaults, VuetifyDefaultsSymbol } from '@/composables/defaults' - it('should render with a div when inactive is true and href is used', () => { - const wrapper = mountFunction({ - propsData: { - href: 'http://www.google.com', - inactive: true, - }, - }) - - expect(wrapper.is('div')).toBe(true) - expect(wrapper.classes('v-list-item--link')).toBe(false) - }) - - it('should render with a tag when tag is specified', () => { - const wrapper = mountFunction({ - propsData: { - tag: 'code', +// Utilities +import { createTheme, VuetifyThemeSymbol } from '@/composables/theme' +import { mount } from '@vue/test-utils' +import { nextTick } from 'vue' + +describe('VListItem', () => { + function mountFunction (options = {}) { + return mount(VListItem, { + global: { + provide: { + [VuetifyDefaultsSymbol as symbol]: createDefaults(), + [VuetifyThemeSymbol as symbol]: createTheme(), + }, }, + ...options, }) + } - expect(wrapper.is('code')).toBe(true) - }) - - it('should render with a div when href and to are not used', () => { + it('should match a snapshot', () => { const wrapper = mountFunction() - expect(wrapper.is('div')).toBe(true) - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render with when using href prop', () => { - const wrapper = mountFunction({ - propsData: { - href: 'http://www.google.com', - }, - }) - - const a = wrapper.find('a') - - expect(wrapper.is('a')).toBe(true) - expect(a.element.getAttribute('href')).toBe('http://www.google.com') expect(wrapper.html()).toMatchSnapshot() }) - it('should have --link class when href/to prop present or link prop is used', () => { - const wrapper = mountFunction({ - propsData: { - href: '/home', - }, - }) - - expect(wrapper.classes('v-list-item--link')).toBe(true) - - wrapper.setProps({ href: undefined, to: '/foo' }) - expect(wrapper.classes('v-list-item--link')).toBe(true) - - wrapper.setProps({ to: undefined, link: true }) - expect(wrapper.classes('v-list-item--link')).toBe(true) - - wrapper.setProps({ link: false }) - expect(wrapper.classes('v-list-item--link')).toBe(false) - }) - - it('should have --link class when click handler present', () => { - const wrapper = mountFunction({ - listeners: { - click: () => {}, - }, - }) - - expect(wrapper.classes('v-list-item--link')).toBe(true) - }) - - it('should have --link class when click.prevent.stop handler present', () => { - const wrapper = mountFunction({ - listeners: { - '!click': () => {}, - }, - }) - - expect(wrapper.classes('v-list-item--link')).toBe(true) - }) - - it('should have --selectable class if the selectable property is true', () => { - const wrapper = mountFunction({ - propsData: { - selectable: true, - }, - }) - - expect(wrapper.classes('v-list-item--selectable')).toBe(true) - }) - - it('should react to keydown.enter', () => { - const click = jest.fn() - const wrapper = mountFunction({ - methods: { click }, - }) - - wrapper.trigger('keydown.enter') - - expect(click).toHaveBeenCalled() - }) - - it('should react to clicks', async () => { - const blur = jest.fn() - const click = jest.fn() - const toggle = jest.fn() - const wrapper = mountFunction({ - methods: { toggle }, - }) - - wrapper.vm.$el.blur = blur - wrapper.vm.$on('click', click) - - wrapper.trigger('click') - expect(blur).not.toHaveBeenCalled() - expect(click).toHaveBeenCalled() - expect(toggle).toHaveBeenCalled() - - wrapper.vm.click({ detail: 1 }) + it('should add active-class when the active prop is true', async () => { + const activeClass = 'foo' + const wrapper = mountFunction({ props: { activeClass } }) - expect(blur).toHaveBeenCalled() + expect(wrapper.classes()).not.toContain(activeClass) - wrapper.setProps({ to: '/foo' }) - await wrapper.vm.$nextTick() + wrapper.setProps({ active: true }) - expect(toggle).toHaveBeenCalledTimes(2) - wrapper.trigger('click') - expect(toggle).toHaveBeenCalledTimes(2) - }) - - it('should inherit listItemGroup activeClass', () => { - const wrapper = mountFunction({ - provide: { - listItemGroup: { - activeClass: 'foobar', - register: () => {}, - unregister: () => {}, - }, - }, - }) + await nextTick() - expect(wrapper.vm.activeClass).toBe('foobar') + expect(wrapper.classes()).toContain(activeClass) }) - it('should have the correct aria attributes and tabindex', () => { - const wrapper = mountFunction({ - propsData: { disabled: true }, - }) - - expect(wrapper.element.getAttribute('aria-disabled')).toBe('true') - expect(wrapper.element.tabIndex).toBe(-1) - - wrapper.setProps({ - disabled: false, - inputValue: true, - }) + it('should conditionally have the tabindex attribute', async () => { + const wrapper = mountFunction() - expect(wrapper.element.getAttribute('aria-disabled')).toBeNull() - expect(wrapper.element.tabIndex).toBe(-1) + expect(wrapper.attributes('tabindex')).toBeUndefined() wrapper.setProps({ link: true }) - expect(wrapper.element.tabIndex).toBe(0) - }) - - it('should have the correct role', () => { - // Custom provided - const wrapper = mountFunction({ - attrs: { role: 'item' }, - }) - expect(wrapper.element.getAttribute('role')).toBe('item') + await nextTick() - // In nav - const wrapper2 = mountFunction({ - provide: { isInNav: true }, - }) - expect(wrapper2.element.getAttribute('role')).toBeNull() + expect(wrapper.attributes('tabindex')).toBe('0') - // In list-item-group - const wrapper3 = mountFunction({ - provide: { isInGroup: true }, - }) - expect(wrapper3.element.getAttribute('role')).toBe('option') + wrapper.setProps({ disabled: true }) - // In menu - const wrapper4 = mountFunction({ - provide: { isInMenu: true }, - }) - expect(wrapper4.element.getAttribute('role')).toBeNull() - wrapper4.setProps({ href: '#' }) // could be `to` or `link` as well - expect(wrapper4.element.getAttribute('role')).toBe('menuitem') + await nextTick() - // In list not a link - const wrapper5 = mountFunction({ - provide: { isInList: true }, - }) - expect(wrapper5.element.getAttribute('role')).toBe('listitem') + expect(wrapper.attributes('tabindex')).toBeUndefined() }) }) diff --git a/packages/vuetify/src/components/VList/__tests__/VListItemAction.spec.ts b/packages/vuetify/src/components/VList/__tests__/VListItemAction.spec.ts deleted file mode 100644 index e99cecb289f..00000000000 --- a/packages/vuetify/src/components/VList/__tests__/VListItemAction.spec.ts +++ /dev/null @@ -1,82 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// Components -// import VListItemAction from '../VListItemAction' - -// Utilities -// import Vue from 'vue' -import { - mount, - Wrapper, -} from '@vue/test-utils' -// import { functionalContext } from '../../../../test' - -// Types -// import { ExtractVue } from '../../../util/mixins' - -describe.skip('VListItemAction.ts', () => { - type Instance = ExtractVue - let mountFunction: (options?: object) => Wrapper - - beforeEach(() => { - mountFunction = (options = {}) => { - return mount(VListItemAction, { - ...options, - }) - } - }) - - it('should render component and match snapshot', () => { - const wrapper = mountFunction(functionalContext()) - - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render component with static class and match snapshot', () => { - const wrapper = mountFunction(functionalContext({ - staticClass: 'static-class', - })) - - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render component with many children and match snapshot', () => { - const content1 = mount(Vue.component('content1', { - render: h => h('div'), - })).vNode - const content2 = mount(Vue.component('content2', { - render: h => h('span'), - })).vNode - const wrapper = mountFunction(functionalContext({}, [content1, content2])) - - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should render component with one children and match snapshot', () => { - const visible = mount(Vue.component('visible', { - render: h => { return h('div') || h() }, - })).vNode - const notVisible = mount(Vue.component('notVisible', { - render: h => { return h() || h('span') }, - })).vNode - - const wrapper = mountFunction(functionalContext({}, [visible, notVisible])) - - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should work with v-html', () => { - const wrapper = mountFunction({ - context: Object.assign({ - domProps: { - innerHTML: 'something', - }, - data: {}, - props: {}, - }), - }) - - expect(wrapper.html()).toMatchSnapshot() - }) -}) diff --git a/packages/vuetify/src/components/VList/__tests__/VListItemAvatar.spec.ts b/packages/vuetify/src/components/VList/__tests__/VListItemAvatar.spec.ts index 0aa1fa15780..daaa57c23d4 100644 --- a/packages/vuetify/src/components/VList/__tests__/VListItemAvatar.spec.ts +++ b/packages/vuetify/src/components/VList/__tests__/VListItemAvatar.spec.ts @@ -1,31 +1,27 @@ -// @ts-nocheck -/* eslint-disable */ - // Components -// import VListItemAvatar from '../VListItemAvatar' - -// Utilities -import { - mount, - Wrapper, -} from '@vue/test-utils' +import { VListItemAvatar } from '..' -// Types -// import { ExtractVue } from '../../../util/mixins' +// Composables +import { createDefaults, VuetifyDefaultsSymbol } from '@/composables/defaults' -describe.skip('VListItemAvatar.ts', () => { - type Instance = ExtractVue - let mountFunction: (options?: object) => Wrapper +// Utilities +import { createTheme, VuetifyThemeSymbol } from '@/composables/theme' +import { mount } from '@vue/test-utils' - beforeEach(() => { - mountFunction = (options = {}) => { - return mount(VListItemAvatar, { - ...options, - }) - } - }) +describe('VListItemAvatar', () => { + function mountFunction (options = {}) { + return mount(VListItemAvatar, { + global: { + provide: { + [VuetifyDefaultsSymbol as symbol]: createDefaults(), + [VuetifyThemeSymbol as symbol]: createTheme(), + }, + }, + ...options, + }) + } - it('should render component and match snapshot', () => { + it('should match a snapshot', () => { const wrapper = mountFunction() expect(wrapper.html()).toMatchSnapshot() diff --git a/packages/vuetify/src/components/VList/__tests__/VListItemGroup.spec.ts b/packages/vuetify/src/components/VList/__tests__/VListItemGroup.spec.ts deleted file mode 100644 index e4a369f6891..00000000000 --- a/packages/vuetify/src/components/VList/__tests__/VListItemGroup.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// Components -// import VListItemGroup from '../VListItemGroup' - -// Utilities -import { - mount, - Wrapper, -} from '@vue/test-utils' - -describe.skip('VListItemGroup.ts', () => { - type Instance = InstanceType - let mountFunction: (options?: object) => Wrapper - - beforeEach(() => { - mountFunction = (options = {}) => { - return mount(VListItemGroup, { - ...options, - }) - } - }) - - it('should have the correct role', () => { - const wrapper = mountFunction() - - expect(wrapper.element.getAttribute('role')).toBe('listbox') - }) -}) diff --git a/packages/vuetify/src/components/VList/__tests__/VListItemMedia.spec.ts b/packages/vuetify/src/components/VList/__tests__/VListItemMedia.spec.ts new file mode 100644 index 00000000000..cdcadbbe6c8 --- /dev/null +++ b/packages/vuetify/src/components/VList/__tests__/VListItemMedia.spec.ts @@ -0,0 +1,29 @@ +// Components +import { VListItemMedia } from '..' + +// Composables +import { createDefaults, VuetifyDefaultsSymbol } from '@/composables/defaults' + +// Utilities +import { createTheme, VuetifyThemeSymbol } from '@/composables/theme' +import { mount } from '@vue/test-utils' + +describe('VListItemMedia', () => { + function mountFunction (options = {}) { + return mount(VListItemMedia, { + global: { + provide: { + [VuetifyDefaultsSymbol as symbol]: createDefaults(), + [VuetifyThemeSymbol as symbol]: createTheme(), + }, + }, + ...options, + }) + } + + it('should match a snapshot', () => { + const wrapper = mountFunction() + + expect(wrapper.html()).toMatchSnapshot() + }) +}) diff --git a/packages/vuetify/src/components/VList/__tests__/__snapshots__/VList.spec.ts.snap b/packages/vuetify/src/components/VList/__tests__/__snapshots__/VList.spec.ts.snap index c6cd85e7d7b..6f51bf7cd34 100644 --- a/packages/vuetify/src/components/VList/__tests__/__snapshots__/VList.spec.ts.snap +++ b/packages/vuetify/src/components/VList/__tests__/__snapshots__/VList.spec.ts.snap @@ -1,36 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`VList.ts should render a dense component and match snapshot 1`] = ` -
-
-`; - -exports[`VList.ts should render a subheader component and match snapshot 1`] = ` -
-
-`; - -exports[`VList.ts should render a threeLine component and match snapshot 1`] = ` -
-
-`; - -exports[`VList.ts should render a twoLine component and match snapshot 1`] = ` -
-
-`; - -exports[`VList.ts should render component and match snapshot 1`] = ` -
+exports[`VList should match a snapshot 1`] = ` +
`; diff --git a/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListGroup.spec.ts.snap b/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListGroup.spec.ts.snap deleted file mode 100644 index 11d7581b70c..00000000000 --- a/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListGroup.spec.ts.snap +++ /dev/null @@ -1,19 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VListGroup.ts should render component and match snapshot 1`] = ` -
- -
-`; diff --git a/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListItem.spec.ts.snap b/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListItem.spec.ts.snap index 5584e0ee929..bb574392a2a 100644 --- a/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListItem.spec.ts.snap +++ b/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListItem.spec.ts.snap @@ -1,16 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`VListItem.ts should render with
when using href prop 1`] = ` - - -`; - -exports[`VListItem.ts should render with a div when href and to are not used 1`] = ` -
+exports[`VListItem should match a snapshot 1`] = ` +
`; diff --git a/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListItemAction.spec.ts.snap b/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListItemAction.spec.ts.snap deleted file mode 100644 index 2b59049dff0..00000000000 --- a/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListItemAction.spec.ts.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VListItemAction.ts should render component and match snapshot 1`] = ` -
-
-`; - -exports[`VListItemAction.ts should render component with many children and match snapshot 1`] = ` -
-
-`; - -exports[`VListItemAction.ts should render component with one children and match snapshot 1`] = ` -
-
-`; - -exports[`VListItemAction.ts should render component with static class and match snapshot 1`] = ` -
-
-`; - -exports[`VListItemAction.ts should work with v-html 1`] = ` -
- - something - -
-`; diff --git a/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListItemAvatar.spec.ts.snap b/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListItemAvatar.spec.ts.snap index 2ee76fe58dd..200112ec87e 100644 --- a/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListItemAvatar.spec.ts.snap +++ b/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListItemAvatar.spec.ts.snap @@ -1,8 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`VListItemAvatar.ts should render component and match snapshot 1`] = ` -
+exports[`VListItemAvatar should match a snapshot 1`] = ` +
`; diff --git a/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListItemMedia.spec.ts.snap b/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListItemMedia.spec.ts.snap new file mode 100644 index 00000000000..c61febfcbf4 --- /dev/null +++ b/packages/vuetify/src/components/VList/__tests__/__snapshots__/VListItemMedia.spec.ts.snap @@ -0,0 +1,6 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`VListItemMedia should match a snapshot 1`] = ` +
+
+`; diff --git a/packages/vuetify/src/components/VList/_index.scss b/packages/vuetify/src/components/VList/_index.scss new file mode 100644 index 00000000000..6f8b481aec9 --- /dev/null +++ b/packages/vuetify/src/components/VList/_index.scss @@ -0,0 +1,3 @@ +@import '../../styles/styles.sass'; +@import './variables'; +@import './mixins'; diff --git a/packages/vuetify/src/components/VList/_mixins.sass b/packages/vuetify/src/components/VList/_mixins.sass deleted file mode 100644 index ed01d6195f1..00000000000 --- a/packages/vuetify/src/components/VList/_mixins.sass +++ /dev/null @@ -1,18 +0,0 @@ -@mixin list-shaped($size) - .v-list-item - &, - &::before, - > .v-ripple__container - +ltr() - border-bottom-right-radius: #{$size * 0.6666666666666667} !important - border-top-right-radius: #{$size * 0.6666666666666667} !important - +rtl() - border-bottom-left-radius: #{$size * 0.6666666666666667} !important - border-top-left-radius: #{$size * 0.6666666666666667} !important - -@mixin list-rounded($size) - .v-list-item - &, - &::before, - > .v-ripple__container - border-radius: #{$size * 0.6666666666666667} !important diff --git a/packages/vuetify/src/components/VList/_mixins.scss b/packages/vuetify/src/components/VList/_mixins.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/vuetify/src/components/VList/_variables.scss b/packages/vuetify/src/components/VList/_variables.scss index 8779769cb5d..d4e9928a142 100644 --- a/packages/vuetify/src/components/VList/_variables.scss +++ b/packages/vuetify/src/components/VList/_variables.scss @@ -1,67 +1,126 @@ -@import '../../styles/styles.sass'; - -$avatar-margin-x: 16px !default; +// Defaults +$list-background: rgba(var(--v-theme-surface)) !default; +$list-border-color: $border-color-root !default; $list-border-radius: 0 !default; +$list-border-style: $border-style-root !default; +$list-border-thin-width: thin !default; +$list-border-width: 0 !default; +$list-color: rgba(var(--v-theme-on-surface)) !default; +$list-disabled-opacity: 0.6 !default; $list-elevation: 0 !default; -$list-dense-subheader-font-size: 0.75rem !default; -$list-dense-subheader-height: 40px !default; -// TODO: Resolve naming conflicts for -// shaped style propagation to items -$list-shaped-border-radius: 0 !default; +$list-padding: 8px 0 !default; +$list-positions: absolute fixed !default; -$list-item-avatar-margin-y: 8px !default; -$list-item-avatar-horizontal-margin-x: -16px !default; +$list-nav-subheader-font-size: .75rem !default; -$list-item-icon-margin-y: 16px !default; +$list-subheader-font-size: .875rem !default; +$list-subheader-font-weight: 500 !default; +$list-subheader-inset-margin: 56px !default; +$list-subheader-inset-padding-start: 72px !default; +$list-subheader-line-height: 1.375rem !default; +$list-subheader-min-height: 48px !default; +$list-subheader-padding-end: 16px !default; +$list-subheader-padding-start: 16px !default; +$list-subheader-padding-top: 0 !default; +$list-subheader-min-height-multiplier: 1 !default; +$list-subheader-text-opacity: var(--v-medium-emphasis-opacity) !default; +$list-subheader-transition: 0.2s min-height $standard-easing !default; $list-item-min-height: 48px !default; +$list-item-padding: 8px 16px !default; +$list-item-transition: .2s $standard-easing !default; $list-item-two-line-min-height: 64px !default; +$list-item-two-line-padding: 12px 16px !default; $list-item-three-line-min-height: 88px !default; +$list-item-three-line-padding: 16px 16px !default; -$list-item-title-font-size: map-deep-get($typography, 'subtitle-1', 'size') !default; -$list-item-subtitle-font-size: map-deep-get($typography, 'subtitle-2', 'size') !default; +$list-item-avatar-align-self: flex-start !default; +$list-item-avatar-margin-end: 16px !default; +$list-item-avatar-margin-start: 16px !default; +$list-item-avatar-size: 40px !default; +$list-item-avatar-size-multiplier: 1.5 !default; +$list-item-avatar-margin-y: 4px !default; -$list-item-dense-title-font-size: 0.8125rem !default; -$list-item-dense-title-font-weight: 500 !default; -$list-padding: 8px 0 !default; -$list-dense-subheading-padding: 0 8px !default; -$list-nav-border-radius: $border-radius-root !default; -$list-nav-rounded-item-margin-bottom: 8px !default; -$list-nav-rounded-item-dense-margin-bottom: 4px !default; -$list-nav-padding-left: 8px !default; -$list-nav-padding-right: 8px !default; -$list-nav-item-padding: 0 8px !default; -$list-shaped-padding: 8px !default; -$list-subheader-padding-top: 0 !default; -$list-group-header-icon-min-width: 48px !default; -$list-group-sub-group-child-margin: 16px !default; -$list-group-sub-group-header-margin: 32px !default; -$list-group-items-item-padding: 40px !default; -$list-group-no-action-item-padding: 72px !default; -$list-dense-subheader-padding: 0 8px !default; -$list-nav-rounded-dense-item-margin-bottom: 4px !default; -$list-group-no-action-sub-group-item-padding: 88px !default; -$list-group-dense-sub-group-header-padding: 24px !default; -$list-group-nav-no-action-item-padding: 64px !default; -$list-group-sub-group-item-padding: 80px !default; -$list-item-padding: 0 16px !default; -$list-item-action-margin: 12px 0 !default; -$list-item-action-text-font-size: map-deep-get($typography, 'caption', 'size') !default; -$list-item-avatar-horizontal-margin: 8px !default; -$list-item-content-padding: 12px 0 !default; -$list-item-content-children-margin-bottom: 2px !default; -$list-item-icon-margin: 16px 0 !default; -$list-item-child-last-type-margin: 16px !default; -$list-item-child-min-width: 24px !default; -$list-item-title-subtitle-line-height: 1.2 !default; -$list-item-avatar-first-child-margin: 16px !default; -$list-item-action-icon-margin: 32px !default; -$list-dense-icon-height: 24px !default; -$list-dense-icon-margin: 8px !default; -$list-dense-min-height: 40px !default; -$list-item-dense-title-line-height: 1rem !default; -$list-item-dense-two-line-min-height: 60px !default; -$list-item-dense-three-line-min-height: 76px !default; -$list-dense-content-padding: 8px 0 !default; -$list-item-two-line-icon-margin-bottom: 32px !default; -$list-item-three-line-avatar-action-margin: 16px !default; +$list-item-media-margin-bottom: 0 !default; +$list-item-media-margin-end: 16px !default; +$list-item-media-margin-start: 16px !default; +$list-item-media-margin-top: 0 !default; +$list-item-media-two-line-margin-bottom: -4px !default; +$list-item-media-two-line-margin-top: -4px !default; +$list-item-media-three-line-margin-bottom: 0 !default; +$list-item-media-three-line-margin-top: 0 !default; + +$list-item-nav-title-font-size: .8125rem !default; +$list-item-nav-title-font-weight: 500 !default; +$list-item-nav-title-letter-spacing: normal !default; +$list-item-nav-title-line-height: 1rem !default; +$list-item-nav-subtitle-font-size: .75rem !default; +$list-item-nav-subtitle-font-weight: map-deep-get($typography, 'body-2', 'weight') !default; +$list-item-nav-subtitle-letter-spacing: map-deep-get($typography, 'body-2', 'letter-spacing') !default; +$list-item-nav-subtitle-line-height: 1rem !default; + +$list-item-subtitle-opacity: var(--v-medium-emphasis-opacity) !default; +$list-item-subtitle-font-size: map-deep-get($typography, 'body-2', 'size') !default; +$list-item-subtitle-font-weight: map-deep-get($typography, 'body-2', 'weight') !default; +$list-item-subtitle-letter-spacing: map-deep-get($typography, 'body-2', 'letter-spacing') !default; +$list-item-subtitle-line-height: 1rem !default; +$list-item-subtitle-padding: 0 !default; +$list-item-subtitle-text-transform: none !default; + +$list-item-title-font-size: map-deep-get($typography, 'body-1', 'size') !default; +$list-item-title-font-weight: map-deep-get($typography, 'body-1', 'weight') !default; +$list-item-title-header-padding: 0 !default; +$list-item-title-hyphens: auto !default; +$list-item-title-letter-spacing: map-deep-get($typography, 'subtitle-1', 'letter-spacing') !default; +$list-item-title-line-height: map-deep-get($typography, 'body-1', 'line-height') !default; +$list-item-title-overflow-wrap: normal !default; +$list-item-title-padding: 0 !default; +$list-item-title-text-overflow: ellipsis; +$list-item-title-text-transform: none !default; +$list-item-title-word-break: normal !default; +$list-item-title-word-wrap: break-word !default; + +$list-density: ('default': 0, 'comfortable': -1, 'compact': -2) !default; + +$list-border: ( + $list-border-color, + $list-border-style, + $list-border-width +); + +$list-theme: ( + $list-background, + $list-color +); + +$list-item-title-typography: ( + $list-item-title-font-size, + $list-item-title-font-weight, + $list-item-title-letter-spacing, + $list-item-title-line-height, + $list-item-title-text-transform +); + +$list-item-subtitle-typography: ( + $list-item-subtitle-font-size, + $list-item-subtitle-font-weight, + $list-item-subtitle-letter-spacing, + $list-item-subtitle-line-height, + $list-item-subtitle-text-transform +); + +$list-item-nav-title-typography: ( + $list-item-nav-title-font-size, + $list-item-nav-title-font-weight, + $list-item-nav-title-letter-spacing, + $list-item-nav-title-line-height, + null +); + +$list-item-nav-subtitle-typography: ( + $list-item-nav-subtitle-font-size, + $list-item-nav-subtitle-font-weight, + $list-item-nav-subtitle-letter-spacing, + $list-item-nav-subtitle-line-height, + null +); diff --git a/packages/vuetify/src/components/VList/index.ts b/packages/vuetify/src/components/VList/index.ts index 2dbdda8568e..bc4d7047c27 100644 --- a/packages/vuetify/src/components/VList/index.ts +++ b/packages/vuetify/src/components/VList/index.ts @@ -1,40 +1,9 @@ -import { createSimpleFunctional } from '@/util' - -import VList from './VList' -import VListGroup from './VListGroup' -import VListItem from './VListItem' -import VListItemGroup from './VListItemGroup' -import VListItemAction from './VListItemAction' -import VListItemAvatar from './VListItemAvatar' -import VListItemIcon from './VListItemIcon' - -export const VListItemActionText = createSimpleFunctional('v-list-item__action-text', 'span') -export const VListItemContent = createSimpleFunctional('v-list-item__content', 'div') -export const VListItemTitle = createSimpleFunctional('v-list-item__title', 'div') -export const VListItemSubtitle = createSimpleFunctional('v-list-item__subtitle', 'div') - -export { - VList, - VListGroup, - VListItem, - VListItemAction, - VListItemAvatar, - VListItemIcon, - VListItemGroup, -} - -export default { - $_vuetify_subcomponents: { - VList, - VListGroup, - VListItem, - VListItemAction, - VListItemActionText, - VListItemAvatar, - VListItemContent, - VListItemGroup, - VListItemIcon, - VListItemSubtitle, - VListItemTitle, - }, -} +export { default as VList } from './VList' +export { default as VListSubheader } from './VListSubheader' +export { default as VListImg } from './VListImg' +export { default as VListItem } from './VListItem' +export { default as VListItemAvatar } from './VListItemAvatar' +export { default as VListItemHeader } from './VListItemHeader' +export { default as VListItemMedia } from './VListItemMedia' +export { default as VListItemSubtitle } from './VListItemSubtitle' +export { default as VListItemTitle } from './VListItemTitle' diff --git a/packages/vuetify/src/components/VSubheader/VSubheader.sass b/packages/vuetify/src/components/VSubheader/VSubheader.sass deleted file mode 100644 index 0b8ff1657dd..00000000000 --- a/packages/vuetify/src/components/VSubheader/VSubheader.sass +++ /dev/null @@ -1,15 +0,0 @@ -@import './_variables.scss' - -+theme(v-subheader) using ($material) - color: map-deep-get($material, 'text', 'secondary') - -.v-subheader - align-items: center - display: flex - height: $subheader-item-single-height - font-size: map-deep-get($typography, 'body-2', 'size') - font-weight: map-deep-get($typography, 'body-2', 'weight') - padding: 0 $subheader-right-padding 0 $subheader-left-padding - - &--inset - margin-left: $subheader-inset-margin diff --git a/packages/vuetify/src/components/VSubheader/VSubheader.ts b/packages/vuetify/src/components/VSubheader/VSubheader.ts deleted file mode 100644 index 57a0b8c1d45..00000000000 --- a/packages/vuetify/src/components/VSubheader/VSubheader.ts +++ /dev/null @@ -1,35 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// Styles -import './VSubheader.sass' - -// Mixins -import Themeable from '../../mixins/themeable' -import mixins from '../../util/mixins' - -// Types -import { VNode } from 'vue' - -export default mixins( - Themeable - /* @vue/component */ -).extend({ - name: 'v-subheader', - - props: { - inset: Boolean, - }, - - render (h): VNode { - return h('div', { - staticClass: 'v-subheader', - class: { - 'v-subheader--inset': this.inset, - ...this.themeClasses, - }, - attrs: this.$attrs, - on: this.$listeners, - }, this.$slots.default) - }, -}) diff --git a/packages/vuetify/src/components/VSubheader/__tests__/VSubheader.spec.ts b/packages/vuetify/src/components/VSubheader/__tests__/VSubheader.spec.ts deleted file mode 100644 index a0ae6ea49f5..00000000000 --- a/packages/vuetify/src/components/VSubheader/__tests__/VSubheader.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ - -// Components -// import VSubheader from '../VSubheader' - -// Utilities -import { - mount, - Wrapper, -} from '@vue/test-utils' - -describe.skip('VSubheader.ts', () => { - type Instance = InstanceType - let mountFunction: (options?: object) => Wrapper - - beforeEach(() => { - mountFunction = (options = {}) => { - return mount(VSubheader, { - ...options, - }) - } - }) - - it('should have custom class', () => { - const wrapper = mount({ - render: h => h(VSubheader, { staticClass: 'foo' }), - }) - - expect(wrapper.element.classList.contains('foo')).toBe(true) - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should be light', () => { - const wrapper = mountFunction({ - propsData: { light: true }, - }) - - expect(wrapper.element.classList.contains('theme--light')).toBe(true) - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should be dark', () => { - const wrapper = mountFunction({ - propsData: { dark: true }, - }) - - expect(wrapper.element.classList.contains('theme--dark')).toBe(true) - expect(wrapper.html()).toMatchSnapshot() - }) - - it('should be inset', () => { - const wrapper = mountFunction({ - propsData: { inset: true }, - }) - - expect(wrapper.element.classList.contains('v-subheader--inset')).toBe(true) - expect(wrapper.html()).toMatchSnapshot() - }) -}) diff --git a/packages/vuetify/src/components/VSubheader/__tests__/__snapshots__/VSubheader.spec.ts.snap b/packages/vuetify/src/components/VSubheader/__tests__/__snapshots__/VSubheader.spec.ts.snap deleted file mode 100644 index 6c77695b574..00000000000 --- a/packages/vuetify/src/components/VSubheader/__tests__/__snapshots__/VSubheader.spec.ts.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`VSubheader.ts should be dark 1`] = ` -
-
-`; - -exports[`VSubheader.ts should be inset 1`] = ` -
-
-`; - -exports[`VSubheader.ts should be light 1`] = ` -
-
-`; - -exports[`VSubheader.ts should have custom class 1`] = ` -
-
-`; diff --git a/packages/vuetify/src/components/VSubheader/_variables.scss b/packages/vuetify/src/components/VSubheader/_variables.scss deleted file mode 100644 index 08cad2d9ebd..00000000000 --- a/packages/vuetify/src/components/VSubheader/_variables.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import '../../styles/styles.sass'; - -$subheader-inset-margin: 56px !default; -$subheader-item-single-height: 48px !default; -$subheader-left-padding: 16px !default; -$subheader-right-padding: 16px !default; diff --git a/packages/vuetify/src/components/VSubheader/index.ts b/packages/vuetify/src/components/VSubheader/index.ts deleted file mode 100644 index e9d2f7f69e4..00000000000 --- a/packages/vuetify/src/components/VSubheader/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import VSubheader from './VSubheader' - -export { VSubheader } -export default VSubheader diff --git a/packages/vuetify/src/components/index.ts b/packages/vuetify/src/components/index.ts index dc8a1392255..a19b1c3b5db 100644 --- a/packages/vuetify/src/components/index.ts +++ b/packages/vuetify/src/components/index.ts @@ -44,8 +44,8 @@ export * from './VItemGroup' // export * from './VLabel' export * from './VLayout' export * from './VLocaleProvider' +export * from './VList' export * from './VLazy' -// export * from './VList' export * from './VMain' // export * from './VMenu' // export * from './VMessages' @@ -70,7 +70,6 @@ export * from './VResponsive' // export * from './VSparkline' // export * from './VSpeedDial' // export * from './VStepper' -// export * from './VSubheader' // export * from './VSwitch' export * from './VSystemBar' // export * from './VTabs' diff --git a/packages/vuetify/src/composables/density.ts b/packages/vuetify/src/composables/density.ts index 88795984235..ca8521f7fe9 100644 --- a/packages/vuetify/src/composables/density.ts +++ b/packages/vuetify/src/composables/density.ts @@ -5,12 +5,12 @@ import { propsFactory } from '@/util' // Types import type { PropType } from 'vue' -const allowedDensities = ['default', 'comfortable', 'compact'] as const +const allowedDensities = [null, 'default', 'comfortable', 'compact'] as const type Density = typeof allowedDensities[number] export interface DensityProps { - density: Density + density?: Density } // Composables diff --git a/packages/vuetify/src/styles/tools/_density.sass b/packages/vuetify/src/styles/tools/_density.sass index b6f93b64a65..fce1f4d93d2 100644 --- a/packages/vuetify/src/styles/tools/_density.sass +++ b/packages/vuetify/src/styles/tools/_density.sass @@ -1,10 +1,4 @@ -@mixin density($name, $properties, $densities) +@mixin density($name, $densities) @each $density, $multiplier in $densities - $value: calc(var(--#{$name}-height) + #{$multiplier * $spacer}) - - &.#{$name}--density-#{$density} - @if type-of($properties) == 'list' - @each $property in $properties - #{$property}: $value - @else - #{$properties}: $value + .#{$name}--density-#{$density} + @content($multiplier * $spacer) diff --git a/packages/vuetify/src/styles/tools/_index.sass b/packages/vuetify/src/styles/tools/_index.sass index e3bfed62c33..b6539340519 100644 --- a/packages/vuetify/src/styles/tools/_index.sass +++ b/packages/vuetify/src/styles/tools/_index.sass @@ -10,6 +10,7 @@ @import './_sheet' @import './_states' @import './_theme' +@import './_typography' @import './_utilities' @import './_display' diff --git a/packages/vuetify/src/styles/tools/_typography.sass b/packages/vuetify/src/styles/tools/_typography.sass new file mode 100644 index 00000000000..33ad03deae2 --- /dev/null +++ b/packages/vuetify/src/styles/tools/_typography.sass @@ -0,0 +1,6 @@ +@mixin typography ($font-size, $font-weight, $letter-spacing, $line-height, $text-transform) + font-size: $font-size + font-weight: $font-weight + letter-spacing: $letter-spacing + line-height: $line-height + text-transform: $text-transform