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
+178
View File
@@ -0,0 +1,178 @@
// Imports
@import './_variables.scss'
// Theme
+theme(v-alert) using ($material)
.v-alert--prominent .v-alert__icon
&:after
background: map-get($material, 'dividers')
// Sheet
+sheet(v-alert, $alert-elevation, $alert-border-radius, $alert-shaped-border-radius)
// Block
.v-alert
display: block
font-size: $alert-font-size
margin-bottom: $alert-margin
padding: $alert-padding
position: relative
transition: $primary-transition
&:not(.v-sheet--tile)
border-radius: $alert-border-radius
> .v-icon,
> .v-alert__content
+ltr()
margin-right: $alert-margin
+rtl()
margin-left: $alert-margin
> .v-icon + .v-alert__content
+ltr()
margin-right: 0
+rtl()
margin-left: 0
> .v-alert__content + .v-icon
+ltr()
margin-right: 0
+rtl()
margin-left: 0
// Elements
.v-alert__border
border-style: solid
border-width: $alert-border-width
content: ''
position: absolute
&:not(.v-alert__border--has-color)
opacity: $alert-border-opacity
&--left,
&--right
bottom: 0
top: 0
&--bottom,
&--top
left: 0
right: 0
&--bottom
border-bottom-left-radius: inherit
border-bottom-right-radius: inherit
bottom: 0
&--left
+ltr()
border-top-left-radius: inherit
border-bottom-left-radius: inherit
left: 0
+rtl()
border-top-right-radius: inherit
border-bottom-right-radius: inherit
right: 0
&--right
+ltr()
border-top-right-radius: inherit
border-bottom-right-radius: inherit
right: 0
+rtl()
border-top-left-radius: inherit
border-bottom-left-radius: inherit
left: 0
&--top
border-top-left-radius: inherit
border-top-right-radius: inherit
top: 0
.v-alert__content
flex: 1 1 auto
.v-alert__dismissible
+ltr()
margin: -16px -8px -16px 8px
+rtl()
margin: -16px 8px -16px -8px
.v-alert__icon
align-self: flex-start
border-radius: 50%
height: $alert-icon-size
min-width: $alert-icon-size
position: relative
+ltr()
margin-right: 16px
+rtl()
margin-left: 16px
&.v-icon
font-size: $alert-icon-size
.v-alert__wrapper
align-items: center
border-radius: inherit
display: flex
// Modifiers
.v-alert--dense
padding-top: $alert-padding / 2
padding-bottom: $alert-padding / 2
.v-alert__border
border-width: $alert-dense-border-width
.v-alert--outlined
background: transparent !important
border: $alert-outline !important
.v-alert__icon
color: inherit !important
.v-alert--prominent
.v-alert__icon
align-self: center
height: $alert-prominent-icon-size
min-width: $alert-prominent-icon-size
&:after
background: currentColor !important
border-radius: 50%
bottom: 0
content: ''
left: 0
opacity: 0.16
position: absolute
right: 0
top: 0
&.v-icon
font-size: $alert-prominent-icon-font-size
.v-alert--text
background: transparent !important
&:before
background-color: currentColor
border-radius: inherit
bottom: 0
content: ''
left: 0
opacity: 0.12
position: absolute
pointer-events: none
right: 0
top: 0
+251
View File
@@ -0,0 +1,251 @@
// Styles
import './VAlert.sass'
// Extensions
import VSheet from '../VSheet'
// Components
import VBtn from '../VBtn'
import VIcon from '../VIcon'
// Mixins
import Toggleable from '../../mixins/toggleable'
import Themeable from '../../mixins/themeable'
import Transitionable from '../../mixins/transitionable'
// Utilities
import mixins from '../../util/mixins'
import { breaking } from '../../util/console'
// Types
import { VNodeData } from 'vue'
import { VNode } from 'vue/types'
/* @vue/component */
export default mixins(
VSheet,
Toggleable,
Transitionable
).extend({
name: 'v-alert',
props: {
border: {
type: String,
validator (val: string) {
return [
'top',
'right',
'bottom',
'left',
].includes(val)
},
},
closeLabel: {
type: String,
default: '$vuetify.close',
},
coloredBorder: Boolean,
dense: Boolean,
dismissible: Boolean,
closeIcon: {
type: String,
default: '$cancel',
},
icon: {
default: '',
type: [Boolean, String],
validator (val: boolean | string) {
return typeof val === 'string' || val === false
},
},
outlined: Boolean,
prominent: Boolean,
text: Boolean,
type: {
type: String,
validator (val: string) {
return [
'info',
'error',
'success',
'warning',
].includes(val)
},
},
value: {
type: Boolean,
default: true,
},
},
computed: {
__cachedBorder (): VNode | null {
if (!this.border) return null
let data: VNodeData = {
staticClass: 'v-alert__border',
class: {
[`v-alert__border--${this.border}`]: true,
},
}
if (this.coloredBorder) {
data = this.setBackgroundColor(this.computedColor, data)
data.class['v-alert__border--has-color'] = true
}
return this.$createElement('div', data)
},
__cachedDismissible (): VNode | null {
if (!this.dismissible) return null
const color = this.iconColor
return this.$createElement(VBtn, {
staticClass: 'v-alert__dismissible',
props: {
color,
icon: true,
small: true,
},
attrs: {
'aria-label': this.$vuetify.lang.t(this.closeLabel),
},
on: {
click: () => (this.isActive = false),
},
}, [
this.$createElement(VIcon, {
props: { color },
}, this.closeIcon),
])
},
__cachedIcon (): VNode | null {
if (!this.computedIcon) return null
return this.$createElement(VIcon, {
staticClass: 'v-alert__icon',
props: { color: this.iconColor },
}, this.computedIcon)
},
classes (): object {
const classes: Record<string, boolean> = {
...VSheet.options.computed.classes.call(this),
'v-alert--border': Boolean(this.border),
'v-alert--dense': this.dense,
'v-alert--outlined': this.outlined,
'v-alert--prominent': this.prominent,
'v-alert--text': this.text,
}
if (this.border) {
classes[`v-alert--border-${this.border}`] = true
}
return classes
},
computedColor (): string {
return this.color || this.type
},
computedIcon (): string | boolean {
if (this.icon === false) return false
if (typeof this.icon === 'string' && this.icon) return this.icon
if (!['error', 'info', 'success', 'warning'].includes(this.type)) return false
return `$${this.type}`
},
hasColoredIcon (): boolean {
return (
this.hasText ||
(Boolean(this.border) && this.coloredBorder)
)
},
hasText (): boolean {
return this.text || this.outlined
},
iconColor (): string | undefined {
return this.hasColoredIcon ? this.computedColor : undefined
},
isDark (): boolean {
if (
this.type &&
!this.coloredBorder &&
!this.outlined
) return true
return Themeable.options.computed.isDark.call(this)
},
},
created () {
/* istanbul ignore next */
if (this.$attrs.hasOwnProperty('outline')) {
breaking('outline', 'outlined', this)
}
},
methods: {
genWrapper (): VNode {
const children = [
this.$slots.prepend || this.__cachedIcon,
this.genContent(),
this.__cachedBorder,
this.$slots.append,
this.$scopedSlots.close
? this.$scopedSlots.close({ toggle: this.toggle })
: this.__cachedDismissible,
]
const data: VNodeData = {
staticClass: 'v-alert__wrapper',
}
return this.$createElement('div', data, children)
},
genContent (): VNode {
return this.$createElement('div', {
staticClass: 'v-alert__content',
}, this.$slots.default)
},
genAlert (): VNode {
let data: VNodeData = {
staticClass: 'v-alert',
attrs: {
role: 'alert',
},
class: this.classes,
style: this.styles,
directives: [{
name: 'show',
value: this.isActive,
}],
}
if (!this.coloredBorder) {
const setColor = this.hasText ? this.setTextColor : this.setBackgroundColor
data = setColor(this.computedColor, data)
}
return this.$createElement('div', data, [this.genWrapper()])
},
/** @public */
toggle () {
this.isActive = !this.isActive
},
},
render (h): VNode {
const render = this.genAlert()
if (!this.transition) return render
return h('transition', {
props: {
name: this.transition,
origin: this.origin,
mode: this.mode,
},
}, [render])
},
})
+194
View File
@@ -0,0 +1,194 @@
// Components
import VAlert from '../VAlert'
// Utilities
import {
mount,
Wrapper,
} from '@vue/test-utils'
// Types
import { ExtractVue } from '../../../util/mixins'
describe('VAlert.ts', () => {
type Instance = ExtractVue<typeof VAlert>
let mountFunction: (options?: object) => Wrapper<Instance>
beforeEach(() => {
mountFunction = (options = {}) => {
return mount(VAlert, {
...options,
// https://github.com/vuejs/vue-test-utils/issues/1130
sync: false,
mocks: {
$vuetify: {
lang: {
t: (val: string) => val,
},
},
},
})
}
})
it('should be open by default', async () => {
const wrapper = mountFunction()
expect(wrapper.element.style.display).toBe('')
expect(wrapper.html()).toMatchSnapshot()
wrapper.setProps({ value: false })
await wrapper.vm.$nextTick()
expect(wrapper.element.style.display).toBe('none')
expect(wrapper.html()).toMatchSnapshot()
})
it('should have a close icon', () => {
const wrapper = mountFunction({
propsData: { dismissible: true },
})
expect(wrapper.html()).toMatchSnapshot()
})
it('should be dismissible', async () => {
const wrapper = mountFunction({
propsData: {
dismissible: true,
},
})
const icon = wrapper.find('.v-alert__dismissible')
const input = jest.fn(show => wrapper.setProps({ show }))
wrapper.vm.$on('input', input)
icon.trigger('click')
await wrapper.vm.$nextTick()
expect(input).toHaveBeenCalledWith(false)
expect(wrapper.html()).toMatchSnapshot()
})
it('should have a custom icon', () => {
const wrapper = mountFunction({
propsData: {
icon: 'list',
},
})
const icon = wrapper.find('.v-alert__icon')
expect(icon.text()).toBe('list')
})
it('should have no icon', () => {
const wrapper = mountFunction()
expect(wrapper.contains('.v-icon')).toBe(false)
})
// TODO: this fails without sync, nextTick doesn't help
// https://github.com/vuejs/vue-test-utils/issues/1130
it.skip('should display contextual colors by type', async () => {
const wrapper = mountFunction({
propsData: { type: 'error' },
})
expect(wrapper.classes('error')).toBe(true)
wrapper.setProps({ type: 'success' })
await wrapper.vm.$nextTick()
expect(wrapper.classes('success')).toBe(true)
wrapper.setProps({ type: 'warning' })
await wrapper.vm.$nextTick()
expect(wrapper.classes('warning')).toBe(true)
wrapper.setProps({ type: 'info' })
await wrapper.vm.$nextTick()
expect(wrapper.classes('info')).toBe(true)
})
it('should allow overriding color for contextual alert', () => {
const wrapper = mountFunction({
propsData: {
type: 'error',
color: 'primary',
},
})
expect(wrapper.classes('primary')).toBe(true)
})
it('should allow overriding icon for contextual alert', () => {
const wrapper = mountFunction({
propsData: {
type: 'error',
icon: 'block',
},
})
const icon = wrapper.find('.v-alert__icon')
expect(icon.text()).toBe('block')
})
it('should render custom dismissible icon', () => {
const wrapper = mountFunction({
propsData: {
dismissible: true,
closeIcon: 'foo',
},
})
const icon = wrapper.find('.v-alert__content + .v-btn')
expect(icon.text()).toBe('foo')
})
it('should show border', async () => {
const directions = ['top', 'right', 'bottom', 'left']
const wrapper = mountFunction()
expect(wrapper.classes('v-alert--border')).toBe(false)
for (const border of directions) {
wrapper.setProps({ border })
await wrapper.vm.$nextTick()
expect(wrapper.classes('v-alert--border')).toBe(true)
expect(wrapper.classes(`v-alert--border-${border}`)).toBe(true)
}
})
it('should move color classes to border and icon elements', async () => {
const wrapper = mountFunction({
propsData: {
color: 'pink',
border: 'left',
},
})
const border = wrapper.find('.v-alert__border')
expect(wrapper.classes('pink')).toBe(true)
expect(border.classes('pink')).toBe(false)
wrapper.setProps({ coloredBorder: true })
await wrapper.vm.$nextTick()
expect(wrapper.classes('pink')).toBe(false)
expect(border.classes('pink')).toBe(true)
expect(border.classes('v-alert__border--has-color')).toBe(true)
})
it('should toggle isActive state', () => {
const wrapper = mountFunction()
expect(wrapper.vm.isActive).toBe(true)
wrapper.vm.toggle()
expect(wrapper.vm.isActive).toBe(false)
})
})
@@ -0,0 +1,71 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VAlert.ts should be dismissible 1`] = `
<div role="alert"
class="v-alert v-sheet theme--light"
style="display: none;"
>
<div class="v-alert__wrapper">
<div class="v-alert__content">
</div>
<button type="button"
class="v-alert__dismissible v-btn v-btn--flat v-btn--icon v-btn--round theme--light v-size--small"
aria-label="$vuetify.close"
>
<span class="v-btn__content">
<i aria-hidden="true"
class="v-icon notranslate material-icons theme--light"
>
$cancel
</i>
</span>
</button>
</div>
</div>
`;
exports[`VAlert.ts should be open by default 1`] = `
<div role="alert"
class="v-alert v-sheet theme--light"
>
<div class="v-alert__wrapper">
<div class="v-alert__content">
</div>
</div>
</div>
`;
exports[`VAlert.ts should be open by default 2`] = `
<div role="alert"
class="v-alert v-sheet theme--light"
style="display: none;"
>
<div class="v-alert__wrapper">
<div class="v-alert__content">
</div>
</div>
</div>
`;
exports[`VAlert.ts should have a close icon 1`] = `
<div role="alert"
class="v-alert v-sheet theme--light"
>
<div class="v-alert__wrapper">
<div class="v-alert__content">
</div>
<button type="button"
class="v-alert__dismissible v-btn v-btn--flat v-btn--icon v-btn--round theme--light v-size--small"
aria-label="$vuetify.close"
>
<span class="v-btn__content">
<i aria-hidden="true"
class="v-icon notranslate material-icons theme--light"
>
$cancel
</i>
</span>
</button>
</div>
</div>
`;
+15
View File
@@ -0,0 +1,15 @@
@import '../../styles/styles.sass';
$alert-elevation: 0 !default;
$alert-border-opacity: 0.26 !default;
$alert-border-radius: $border-radius-root !default;
$alert-shaped-border-radius: map-get($rounded, 'xl') $alert-border-radius !default;
$alert-border-width: 4px !default;
$alert-dense-border-width: medium !default;
$alert-font-size: 16px !default;
$alert-icon-size: 24px !default;
$alert-margin: 16px !default;
$alert-outline: thin solid currentColor !default;
$alert-padding: 16px !default;
$alert-prominent-icon-font-size: 32px !default;
$alert-prominent-icon-size: 48px !default;
+4
View File
@@ -0,0 +1,4 @@
import VAlert from './VAlert'
export { VAlert }
export default VAlert