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
+160
View File
@@ -0,0 +1,160 @@
// Imports
@import './_variables.scss'
@import './_mixins.sass'
// Theme
+theme(v-timeline) using ($material)
&::before
background: map-get($material, 'dividers')
.v-timeline-item
&__dot
background: map-get($material, 'cards')
.v-card
&::before
border-right-color: $shadow-key-ambient-opacity
// Elements
.v-timeline
padding-top: $timeline-item-padding
position: relative
&:before
bottom: 0
content: ''
height: 100%
position: absolute
top: 0
width: $timeline-line-width
.v-timeline-item
display: flex
padding-bottom: $timeline-item-padding
.v-timeline-item__body
position: relative
height: 100%
flex: 1 1 auto
.v-timeline-item__divider
position: relative
min-width: $timeline-divider-width
display: flex
align-items: center
justify-content: center
.v-timeline-item__dot
z-index: 2
border-radius: 50%
+elevation(1)
+timeline-dots($timeline-dot-regular-size, $timeline-inner-dot-regular-size)
&--small
+timeline-dots($timeline-dot-small-size, $timeline-inner-dot-small-size)
&--large
+timeline-dots($timeline-dot-large-size, $timeline-inner-dot-large-size)
.v-timeline-item__inner-dot
border-radius: 50%
display: flex
justify-content: center
align-items: center
.v-timeline-item__opposite
flex: 1 1 auto
align-self: center
max-width: calc(#{$timeline-divider-center} - #{$timeline-divider-width / 2})
// Before/after directions
.v-timeline:not(.v-timeline--dense):not(.v-timeline--reverse)
.v-timeline-item
&:nth-child(odd):not(.v-timeline-item--before), &--after
+timeline-item-align(left)
+timeline-wedge-align(left)
.v-timeline-item__body
max-width: calc(#{100% - $timeline-divider-center} - #{$timeline-divider-width / 2})
&:nth-child(even):not(.v-timeline-item--after), &--before
+timeline-item-align(right)
+timeline-wedge-align(right)
.v-timeline-item__body
max-width: calc(#{$timeline-divider-center} - #{$timeline-divider-width / 2})
// Wedge
.v-timeline-item__body
> .v-card:not(.v-card--flat)
&:before, &:after
content: ''
position: absolute
border-top: $timeline-wedge-size solid transparent
border-bottom: $timeline-wedge-size solid transparent
border-right: $timeline-wedge-size solid black
top: calc(50% - #{$timeline-wedge-size})
&:after
border-right-color: inherit
&:before
top: calc(50% - #{$timeline-wedge-size} + 2px)
// Modifiers
.v-timeline--align-top
.v-timeline-item__dot
align-self: start
.v-timeline-item__body
> .v-card
&:before
top: calc(0% + #{$timeline-wedge-size} + 2px)
&:after
top: calc(0% + #{$timeline-wedge-size})
.v-timeline:not(.v-timeline--dense):not(.v-timeline--reverse)
+timeline-line-align($timeline-divider-center, left, right)
.v-timeline--reverse:not(.v-timeline--dense)
+timeline-line-align($timeline-divider-center, right, left)
.v-timeline-item
&:nth-child(odd):not(.v-timeline-item--after), &--before
+timeline-item-align(right)
+timeline-wedge-align(right)
.v-timeline-item__body
max-width: calc(#{100% - $timeline-divider-center} - #{$timeline-divider-width / 2})
&:nth-child(even):not(.v-timeline-item--before), &--after
+timeline-item-align(left)
+timeline-wedge-align(left)
.v-timeline-item__body
max-width: calc(#{$timeline-divider-center} - #{$timeline-divider-width / 2})
.v-timeline--reverse.v-timeline--dense
+timeline-line-align($timeline-divider-width / 2, right, left)
.v-timeline--dense:not(.v-timeline--reverse)
+timeline-line-align($timeline-divider-width / 2, left, right)
.v-timeline--dense
.v-timeline-item
flex-direction: row-reverse !important
+timeline-wedge-align(left)
.v-timeline-item__body
max-width: calc(100% - #{$timeline-divider-width})
.v-timeline-item__opposite
display: none
.v-timeline--reverse.v-timeline--dense
.v-timeline-item
flex-direction: row !important
+timeline-wedge-align(right)
.v-timeline-item--fill-dot
.v-timeline-item__inner-dot
height: inherit
margin: 0
width: inherit
+44
View File
@@ -0,0 +1,44 @@
// Styles
import './VTimeline.sass'
// Types
import { VNode } from 'vue'
import mixins from '../../util/mixins'
// Mixins
import Themeable from '../../mixins/themeable'
export default mixins(
Themeable
/* @vue/component */
).extend({
name: 'v-timeline',
provide (): object {
return { timeline: this }
},
props: {
alignTop: Boolean,
dense: Boolean,
reverse: Boolean,
},
computed: {
classes (): {} {
return {
'v-timeline--align-top': this.alignTop,
'v-timeline--dense': this.dense,
'v-timeline--reverse': this.reverse,
...this.themeClasses,
}
},
},
render (h): VNode {
return h('div', {
staticClass: 'v-timeline',
class: this.classes,
}, this.$slots.default)
},
})
+121
View File
@@ -0,0 +1,121 @@
// Types
import mixins, { ExtractVue } from '../../util/mixins'
import { VNode, VNodeData } from 'vue'
// Components
import VTimeline from './VTimeline'
import VIcon from '../VIcon'
// Mixins
import Themeable from '../../mixins/themeable'
import Colorable from '../../mixins/colorable'
const baseMixins = mixins(
Colorable,
Themeable
/* @vue/component */
)
type VTimelineInstance = InstanceType<typeof VTimeline>
interface options extends ExtractVue<typeof baseMixins> {
timeline: VTimelineInstance
}
export default baseMixins.extend<options>().extend({
name: 'v-timeline-item',
inject: ['timeline'],
props: {
color: {
type: String,
default: 'primary',
},
fillDot: Boolean,
hideDot: Boolean,
icon: String,
iconColor: String,
large: Boolean,
left: Boolean,
right: Boolean,
small: Boolean,
},
computed: {
hasIcon (): boolean {
return !!this.icon || !!this.$slots.icon
},
},
methods: {
genBody () {
return this.$createElement('div', {
staticClass: 'v-timeline-item__body',
}, this.$slots.default)
},
genIcon (): VNode | VNode[] {
if (this.$slots.icon) {
return this.$slots.icon
}
return this.$createElement(VIcon, {
props: {
color: this.iconColor,
dark: !this.theme.isDark,
small: this.small,
},
}, this.icon)
},
genInnerDot () {
const data: VNodeData = this.setBackgroundColor(this.color)
return this.$createElement('div', {
staticClass: 'v-timeline-item__inner-dot',
...data,
}, [this.hasIcon && this.genIcon()])
},
genDot () {
return this.$createElement('div', {
staticClass: 'v-timeline-item__dot',
class: {
'v-timeline-item__dot--small': this.small,
'v-timeline-item__dot--large': this.large,
},
}, [this.genInnerDot()])
},
genDivider () {
const children = []
if (!this.hideDot) children.push(this.genDot())
return this.$createElement('div', {
staticClass: 'v-timeline-item__divider',
}, children)
},
genOpposite () {
return this.$createElement('div', {
staticClass: 'v-timeline-item__opposite',
}, this.$slots.opposite)
},
},
render (h): VNode {
const children = [
this.genBody(),
this.genDivider(),
]
if (this.$slots.opposite) children.push(this.genOpposite())
return h('div', {
staticClass: 'v-timeline-item',
class: {
'v-timeline-item--fill-dot': this.fillDot,
'v-timeline-item--before': this.timeline.reverse ? this.right : this.left,
'v-timeline-item--after': this.timeline.reverse ? this.left : this.right,
...this.themeClasses,
},
}, children)
},
})
@@ -0,0 +1,10 @@
import VTimeline from '../VTimeline'
import { mount } from '@vue/test-utils'
describe('VTimeline.ts', () => {
it('should match snapshot', () => {
const wrapper = mount(VTimeline)
expect(wrapper.html()).toMatchSnapshot()
})
})
@@ -0,0 +1,76 @@
import VTimelineItem from '../VTimelineItem'
import {
mount,
MountOptions,
Wrapper,
} from '@vue/test-utils'
describe('VTimelineItem.ts', () => {
type Instance = InstanceType<typeof VTimelineItem>
let mountFunction: (options?: MountOptions<Instance>) => Wrapper<Instance>
beforeEach(() => {
mountFunction = (options?: MountOptions<Instance>) => {
return mount(VTimelineItem, options)
}
})
it('should conditionally render dot', () => {
const wrapper = mountFunction({
propsData: {
hideDot: true,
},
provide: {
timeline: {
reverse: false,
},
},
})
expect(wrapper.html()).toMatchSnapshot()
wrapper.setProps({ hideDot: false })
expect(wrapper.html()).toMatchSnapshot()
})
it('should conditionally render an icon or icon slot', () => {
expect(mountFunction({
slots: {
icon: [{
render: h => h('div', 'foo'),
}],
},
provide: {
timeline: {
reverse: false,
},
},
}).html()).toMatchSnapshot()
expect(mountFunction({
propsData: { icon: 'foo' },
provide: {
timeline: {
reverse: false,
},
},
}).html()).toMatchSnapshot()
})
it('should render opposite slot', () => {
const wrapper = mountFunction({
slots: {
opposite: [{
render: h => h('div', 'foo'),
}],
},
provide: {
timeline: {
reverse: false,
},
},
})
expect(wrapper.html()).toMatchSnapshot()
})
})
@@ -0,0 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VTimeline.ts should match snapshot 1`] = `
<div class="v-timeline theme--light">
</div>
`;
@@ -0,0 +1,75 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VTimelineItem.ts should conditionally render an icon or icon slot 1`] = `
<div class="v-timeline-item theme--light">
<div class="v-timeline-item__body">
</div>
<div class="v-timeline-item__divider">
<div class="v-timeline-item__dot">
<div class="v-timeline-item__inner-dot primary">
<div>
foo
</div>
</div>
</div>
</div>
</div>
`;
exports[`VTimelineItem.ts should conditionally render an icon or icon slot 2`] = `
<div class="v-timeline-item theme--light">
<div class="v-timeline-item__body">
</div>
<div class="v-timeline-item__divider">
<div class="v-timeline-item__dot">
<div class="v-timeline-item__inner-dot primary">
<i aria-hidden="true"
class="v-icon notranslate material-icons theme--dark"
>
foo
</i>
</div>
</div>
</div>
</div>
`;
exports[`VTimelineItem.ts should conditionally render dot 1`] = `
<div class="v-timeline-item theme--light">
<div class="v-timeline-item__body">
</div>
<div class="v-timeline-item__divider">
</div>
</div>
`;
exports[`VTimelineItem.ts should conditionally render dot 2`] = `
<div class="v-timeline-item theme--light">
<div class="v-timeline-item__body">
</div>
<div class="v-timeline-item__divider">
<div class="v-timeline-item__dot">
<div class="v-timeline-item__inner-dot primary">
</div>
</div>
</div>
</div>
`;
exports[`VTimelineItem.ts should render opposite slot 1`] = `
<div class="v-timeline-item theme--light">
<div class="v-timeline-item__body">
</div>
<div class="v-timeline-item__divider">
<div class="v-timeline-item__dot">
<div class="v-timeline-item__inner-dot primary">
</div>
</div>
</div>
<div class="v-timeline-item__opposite">
<div>
foo
</div>
</div>
</div>
`;
+42
View File
@@ -0,0 +1,42 @@
@mixin timeline-dots($dot-size, $inner-dot-size)
height: $dot-size
left: calc(50% - #{$dot-size / 2})
width: $dot-size
.v-timeline-item__inner-dot
height: $inner-dot-size
margin: #{($dot-size - $inner-dot-size) / 2}
width: $inner-dot-size
@mixin timeline-wedge-align($direction)
.v-timeline-item__body
> .v-card:before, .v-card:after
+ltr()
transform: rotate(if($direction == right, 180deg, 0))
#{$direction}: -($timeline-wedge-size)
#{if($direction == left, right, left)}: initial
+rtl()
transform: rotate(if($direction == right, 0, 180deg))
#{$direction}: initial
#{if($direction == left, right, left)}: -($timeline-wedge-size)
@mixin timeline-item-align($direction)
flex-direction: if($direction == left, row-reverse, row)
.v-timeline-item__opposite
+ltr()
text-align: if($direction == left, right, left)
+rtl()
text-align: if($direction == left, left, right)
@mixin timeline-line-align($center, $first, $second)
&:before
+ltr()
#{$first}: calc(#{$center} - #{$timeline-line-width / 2})
#{$second}: initial
+rtl()
#{$first}: initial
#{$second}: calc(#{$center} - #{$timeline-line-width / 2})
+18
View File
@@ -0,0 +1,18 @@
@import '../../styles/styles.sass';
$timeline-divider-center: 50% !default;
$timeline-divider-width: 96px !default;
$timeline-item-padding: 24px !default;
$timeline-line-width: 2px !default;
$timeline-wedge-size: 10px !default;
$timeline-dot-small-size: 24px !default;
$timeline-dot-regular-size: 38px !default;
$timeline-dot-large-size: 52px !default;
$timeline-inner-dot-small-size: 18px !default;
$timeline-inner-dot-regular-size: 30px !default;
$timeline-inner-dot-large-size: 42px !default;
+11
View File
@@ -0,0 +1,11 @@
import VTimeline from './VTimeline'
import VTimelineItem from './VTimelineItem'
export { VTimeline, VTimelineItem }
export default {
$_vuetify_subcomponents: {
VTimeline,
VTimelineItem,
},
}