This commit is contained in:
darenhsu
2022-07-17 13:16:16 +08:00
parent 84759556ff
commit befd344ab0
28070 changed files with 4008428 additions and 1 deletions
+106
View File
@@ -0,0 +1,106 @@
// Imports
@import './_variables.scss'
+theme(v-badge) using ($material)
.v-badge__badge::after
border-color: map-get($material, 'cards')
.v-badge
display: inline-block
line-height: $badge-line-height
position: relative
&__badge
border-radius: $badge-border-radius
color: $badge-color
display: inline-block
font-size: $badge-font-size
height: $badge-height
letter-spacing: $badge-letter-spacing
line-height: 1
min-width: $badge-min-width
padding: $badge-padding
pointer-events: auto
position: absolute
text-align: center
text-indent: 0
top: $badge-top
transition: $primary-transition
white-space: nowrap
+ltr()
right: $badge-right
+rtl()
left: $badge-right
.v-icon
color: inherit
font-size: $badge-font-size
margin: $badge-icon-margin
.v-img
height: $badge-font-size
width: $badge-font-size
&__wrapper
flex: 0 1
height: 100%
left: 0
pointer-events: none
position: absolute
top: 0
width: 100%
&--avatar
.v-badge__badge
padding: 0
.v-avatar
height: $badge-height !important
min-width: 0 !important
max-width: $badge-min-width !important
&--bordered
.v-badge__badge::after
border-radius: inherit
border-width: $badge-bordered-width
border-style: solid
bottom: 0
content: ''
left: 0
position: absolute
right: 0
top: 0
transform: scale(1.15)
&--dot
.v-badge__badge
border-radius: $badge-dot-border-radius
height: $badge-dot-height
min-width: 0
padding: 0
width: $badge-dot-width
&::after
border-width: $badge-dot-border-width
&--icon
.v-badge__badge
padding: $badge-icon-padding
&--inline
align-items: center
display: inline-flex
justify-content: center
.v-badge__badge,
.v-badge__wrapper
position: relative
.v-badge__wrapper
margin: $badge-wrapper-margin
&--tile
.v-badge__badge
border-radius: 0
+198
View File
@@ -0,0 +1,198 @@
// Styles
import './VBadge.sass'
// Components
import VIcon from '../VIcon/VIcon'
// Mixins
import Colorable from '../../mixins/colorable'
import Themeable from '../../mixins/themeable'
import Toggleable from '../../mixins/toggleable'
import Transitionable from '../../mixins/transitionable'
import { factory as PositionableFactory } from '../../mixins/positionable'
// Utilities
import mixins from '../../util/mixins'
import {
convertToUnit,
getSlot,
} from '../../util/helpers'
// Types
import { VNode } from 'vue'
export default mixins(
Colorable,
PositionableFactory(['left', 'bottom']),
Themeable,
Toggleable,
Transitionable,
/* @vue/component */
).extend({
name: 'v-badge',
props: {
avatar: Boolean,
bordered: Boolean,
color: {
type: String,
default: 'primary',
},
content: { required: false },
dot: Boolean,
label: {
type: String,
default: '$vuetify.badge',
},
icon: String,
inline: Boolean,
offsetX: [Number, String],
offsetY: [Number, String],
overlap: Boolean,
tile: Boolean,
transition: {
type: String,
default: 'scale-rotate-transition',
},
value: { default: true },
},
computed: {
classes (): object {
return {
'v-badge--avatar': this.avatar,
'v-badge--bordered': this.bordered,
'v-badge--bottom': this.bottom,
'v-badge--dot': this.dot,
'v-badge--icon': this.icon != null,
'v-badge--inline': this.inline,
'v-badge--left': this.left,
'v-badge--overlap': this.overlap,
'v-badge--tile': this.tile,
...this.themeClasses,
}
},
computedBottom (): string {
return this.bottom ? 'auto' : this.computedYOffset
},
computedLeft (): string {
if (this.isRtl) {
return this.left ? this.computedXOffset : 'auto'
}
return this.left ? 'auto' : this.computedXOffset
},
computedRight (): string {
if (this.isRtl) {
return this.left ? 'auto' : this.computedXOffset
}
return !this.left ? 'auto' : this.computedXOffset
},
computedTop (): string {
return this.bottom ? this.computedYOffset : 'auto'
},
computedXOffset (): string {
return this.calcPosition(this.offsetX)
},
computedYOffset (): string {
return this.calcPosition(this.offsetY)
},
isRtl (): boolean {
return this.$vuetify.rtl
},
// Default fallback if offsetX
// or offsetY are undefined.
offset (): number {
if (this.overlap) return this.dot ? 8 : 12
return this.dot ? 2 : 4
},
styles (): object {
if (this.inline) return {}
return {
bottom: this.computedBottom,
left: this.computedLeft,
right: this.computedRight,
top: this.computedTop,
}
},
},
methods: {
calcPosition (offset: string | number): string {
return `calc(100% - ${convertToUnit(offset || this.offset)})`
},
genBadge () {
const lang = this.$vuetify.lang
const label = this.$attrs['aria-label'] || lang.t(this.label)
const data = this.setBackgroundColor(this.color, {
staticClass: 'v-badge__badge',
style: this.styles,
attrs: {
'aria-atomic': this.$attrs['aria-atomic'] || 'true',
'aria-label': label,
'aria-live': this.$attrs['aria-live'] || 'polite',
title: this.$attrs.title,
role: this.$attrs.role || 'status',
},
directives: [{
name: 'show',
value: this.isActive,
}],
})
const badge = this.$createElement('span', data, [this.genBadgeContent()])
if (!this.transition) return badge
return this.$createElement('transition', {
props: {
name: this.transition,
origin: this.origin,
mode: this.mode,
},
}, [badge])
},
genBadgeContent () {
// Dot prop shows no content
if (this.dot) return undefined
const slot = getSlot(this, 'badge')
if (slot) return slot
if (this.content) return String(this.content)
if (this.icon) return this.$createElement(VIcon, this.icon)
return undefined
},
genBadgeWrapper () {
return this.$createElement('span', {
staticClass: 'v-badge__wrapper',
}, [this.genBadge()])
},
},
render (h): VNode {
const badge = [this.genBadgeWrapper()]
const children = [getSlot(this)]
const {
'aria-atomic': _x,
'aria-label': _y,
'aria-live': _z,
role,
title,
...attrs
} = this.$attrs
if (this.inline && this.left) children.unshift(badge)
else children.push(badge)
return h('span', {
staticClass: 'v-badge',
attrs,
class: this.classes,
}, children)
},
})
+133
View File
@@ -0,0 +1,133 @@
// Components
import VBadge from '../VBadge'
// Utilities
import {
mount,
Wrapper,
} from '@vue/test-utils'
import { compileToFunctions } from 'vue-template-compiler'
// Types
import { ExtractVue } from '../../../util/mixins'
describe('VBadge.ts', () => {
type Instance = ExtractVue<typeof VBadge>
let mountFunction: (options?: object) => Wrapper<Instance>
beforeEach(() => {
mountFunction = (options = {}) => {
return mount(VBadge, {
mocks: {
$vuetify: {
lang: { t: (text = '') => text },
},
},
...options,
})
}
})
it('should render component and match snapshot', async () => {
const wrapper = mountFunction({
slots: {
badge: [compileToFunctions('<span>content</span>')],
default: [compileToFunctions('<span>element</span>')],
},
})
expect(wrapper.html()).toMatchSnapshot()
})
it('should render component with with value=false and match snapshot', async () => {
const wrapper = mountFunction({
propsData: {
value: false,
},
slots: {
badge: [compileToFunctions('<span>content</span>')],
default: [compileToFunctions('<span>element</span>')],
},
})
expect(wrapper.html()).toMatchSnapshot()
})
it('should render component with bottom prop', () => {
const wrapper = mountFunction({
propsData: {
bottom: true,
},
})
expect(wrapper.classes('v-badge--bottom')).toBeTruthy()
})
it('should render component with left prop', () => {
const wrapper = mountFunction({
propsData: {
left: true,
},
})
expect(wrapper.classes('v-badge--left')).toBeTruthy()
})
it('should render component with overlap prop', () => {
const wrapper = mountFunction({
propsData: {
overlap: true,
},
})
expect(wrapper.classes('v-badge--overlap')).toBeTruthy()
})
it('should render component with color prop', () => {
const wrapper = mountFunction({
propsData: {
color: 'green lighten-1',
},
slots: {
badge: [compileToFunctions('<span>content</span>')],
},
})
const badge = wrapper.find('.v-badge__badge')
expect(badge.classes('green')).toBeTruthy()
expect(badge.classes('lighten-1')).toBeTruthy()
})
it('should render component with transition element', () => {
const transitionStub = {
name: 'transition',
render: jest.fn(),
}
mountFunction({
stubs: {
transition: transitionStub,
},
})
expect(transitionStub.render).toHaveBeenCalled()
})
it('should render component without transition element', () => {
const transitionStub = {
name: 'transition',
render: jest.fn(),
}
mountFunction({
propsData: {
transition: '',
},
stubs: {
transition: transitionStub,
},
})
expect(transitionStub.render).not.toHaveBeenCalled()
})
})
@@ -0,0 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VBadge.ts should render component and match snapshot 1`] = `
<span class="v-badge theme--light">
<span>
element
</span>
<span class="v-badge__wrapper">
<span aria-atomic="true"
aria-label="$vuetify.badge"
aria-live="polite"
role="status"
class="v-badge__badge primary"
>
<span>
content
</span>
</span>
</span>
</span>
`;
exports[`VBadge.ts should render component with with value=false and match snapshot 1`] = `
<span class="v-badge theme--light">
<span>
element
</span>
<span class="v-badge__wrapper">
<span aria-atomic="true"
aria-label="$vuetify.badge"
aria-live="polite"
role="status"
class="v-badge__badge primary"
style="display: none;"
>
<span>
content
</span>
</span>
</span>
</span>
`;
+21
View File
@@ -0,0 +1,21 @@
@import '../../styles/styles.sass';
$badge-border-radius: 10px !default;
$badge-bordered-width: 2px !default;
$badge-color: map-get($shades, 'white') !default;
$badge-dot-border-radius: 4.5px;
$badge-dot-border-width: 1.5px !default;
$badge-dot-height: 9px !default;
$badge-dot-width: 9px !default;
$badge-font-family: $body-font-family !default;
$badge-font-size: 12px !default;
$badge-height: 20px !default;
$badge-icon-margin: 0 -2px !default;
$badge-icon-padding: 4px 6px !default;
$badge-letter-spacing: 0 !default;
$badge-line-height: 1 !default;
$badge-min-width: 20px !default;
$badge-padding: 4px 6px !default;
$badge-right: auto !default;
$badge-top: auto !default;
$badge-wrapper-margin: 0 4px !default;
+4
View File
@@ -0,0 +1,4 @@
import VBadge from './VBadge'
export { VBadge }
export default VBadge