@varlet/cli 1.23.11 → 1.23.12-alpha.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@varlet/cli",
3
- "version": "1.23.11",
3
+ "version": "1.23.12-alpha.25+4a1d7746",
4
4
  "description": "cli of varlet",
5
5
  "bin": {
6
6
  "varlet-cli": "./lib/index.js"
@@ -31,7 +31,7 @@
31
31
  "dev": "tsc --watch",
32
32
  "build": "tsc"
33
33
  },
34
- "gitHead": "449df88f0ff65a2ad89fa48a5d443ab864bd0bb3",
34
+ "gitHead": "4a1d774686bd66a53827bfb64a8495be97f9cf33",
35
35
  "dependencies": {
36
36
  "@babel/core": "^7.14.8",
37
37
  "@babel/preset-env": "^7.14.8",
@@ -0,0 +1,107 @@
1
+ <template>
2
+ <div class="var-site-code-example">
3
+ <div class="var-site-code-example__toolbar">
4
+ <var-site-button text round @click="toggle" v-if="fold">
5
+ <var-site-icon name="xml" size="18" />
6
+ </var-site-button>
7
+
8
+ <var-site-button
9
+ :id="`clip-trigger-${cid}`"
10
+ :data-clipboard-target="`#clip-target-${cid}`"
11
+ text
12
+ round
13
+ v-if="clipboard"
14
+ >
15
+ <var-site-icon name="content-copy" size="18" />
16
+ </var-site-button>
17
+ </div>
18
+ <div
19
+ :id="`clip-target-${cid}`"
20
+ class="var-site-code-example__code"
21
+ ref="code"
22
+ :style="{
23
+ height: height >= 0 ? `${height}px` : undefined,
24
+ }"
25
+ >
26
+ <slot/>
27
+ </div>
28
+ </div>
29
+ </template>
30
+
31
+ <script lang="ts">
32
+ import Icon from '../icon'
33
+ import Button from '../button'
34
+ import Snackbar from '../snackbar'
35
+ import Clipboard from 'clipboard'
36
+ import config from '@config'
37
+ import { defineComponent, nextTick, ref, onMounted } from 'vue'
38
+ import { doubleRaf } from '../utils/elements'
39
+ import { get } from 'lodash-es'
40
+ import { getPCLocationInfo } from '../../utils'
41
+ import type { Ref } from 'vue'
42
+
43
+ let clipId = 0
44
+ const offset = 10
45
+
46
+ export default defineComponent({
47
+ name: 'VarSiteCodeExample',
48
+ components: {
49
+ [Button.name]: Button,
50
+ [Icon.name]: Icon
51
+ },
52
+ setup() {
53
+ const code: Ref<HTMLElement | null> = ref(null)
54
+ const cid: Ref<number> = ref(clipId++)
55
+ const fold: Ref = ref(get(config, 'pc.fold'))
56
+ const clipboard: Ref = ref(get(config, 'pc.clipboard', {}))
57
+ const height: Ref<number> = ref(fold.value?.defaultFold ? fold.value?.foldHeight : -1)
58
+
59
+ const toggle = async () => {
60
+ const foldHeight = fold.value.foldHeight
61
+
62
+ if (height.value === foldHeight) {
63
+ height.value = -1
64
+ await nextTick()
65
+ const { offsetHeight } = code.value as HTMLElement
66
+
67
+ height.value = foldHeight
68
+ await doubleRaf()
69
+
70
+ if (offsetHeight - foldHeight < offset) {
71
+ Snackbar(get(config, `pc.fold.message.${getPCLocationInfo().language}`))
72
+ height.value = foldHeight
73
+ } else {
74
+ height.value = offsetHeight
75
+ }
76
+ } else {
77
+ const { offsetHeight } = code.value as HTMLElement
78
+
79
+ height.value = offsetHeight
80
+ await doubleRaf()
81
+ height.value = foldHeight
82
+ }
83
+ }
84
+
85
+ onMounted(() => {
86
+ const trigger = new Clipboard(`#clip-trigger-${cid.value}`)
87
+
88
+ trigger.on('success', () => {
89
+ Snackbar.success(clipboard.value[getPCLocationInfo().language])
90
+ })
91
+ })
92
+
93
+ return {
94
+ code,
95
+ height,
96
+ cid,
97
+ fold,
98
+ clipboard,
99
+ toggle
100
+ }
101
+ }
102
+ })
103
+ </script>
104
+
105
+ <style lang="less">
106
+ @import "./codeExample";
107
+ </style>
@@ -0,0 +1,23 @@
1
+ .var-site-code-example {
2
+ margin-top: 12px;
3
+ position: relative;
4
+
5
+ &__toolbar {
6
+ display: flex;
7
+ align-items: center;
8
+ position: absolute;
9
+ z-index: 1;
10
+ right: 10px;
11
+ top: 20px;
12
+
13
+ button {
14
+ color: white !important;
15
+ }
16
+ }
17
+
18
+ &__code {
19
+ transition: all .25s;
20
+ overflow: hidden;
21
+ border-radius: 4px;
22
+ }
23
+ }
@@ -0,0 +1,10 @@
1
+ import type { App } from 'vue'
2
+ import CodeExample from './CodeExample.vue'
3
+
4
+ CodeExample.install = function (app: App) {
5
+ app.component(CodeExample.name, CodeExample)
6
+ }
7
+
8
+ export const _CodeExampleComponent = CodeExample
9
+
10
+ export default CodeExample
@@ -12,6 +12,7 @@
12
12
  align-items: center;
13
13
  font-size: var(--site-icon-size);
14
14
  color: inherit;
15
+ vertical-align: bottom;
15
16
 
16
17
  &--shrinking {
17
18
  transform: scale(0);
@@ -1,8 +1,8 @@
1
1
  import type { PropType } from 'vue'
2
2
 
3
- type LoadingType = 'circle' | 'wave' | 'cube' | 'rect' | 'disappear'
3
+ export type LoadingType = 'circle' | 'wave' | 'cube' | 'rect' | 'disappear'
4
4
 
5
- type LoadingSize = 'normal' | 'mini' | 'small' | 'large'
5
+ export type LoadingSize = 'normal' | 'mini' | 'small' | 'large'
6
6
 
7
7
  export function typeValidator(type: string): boolean {
8
8
  return ['circle', 'wave', 'cube', 'rect', 'disappear'].includes(type)
@@ -0,0 +1,38 @@
1
+ <template>
2
+ <teleport :to="teleport" :disabled="disabled">
3
+ <transition name="var-site-snackbar-fade" @after-enter="onOpened" @after-leave="onClosed">
4
+ <var-site-snackbar-core v-bind="$props" class="var-site-snackbar-transition">
5
+ <slot>{{ content }}</slot>
6
+ <template #action>
7
+ <slot name="action" />
8
+ </template>
9
+ </var-site-snackbar-core>
10
+ </transition>
11
+ </teleport>
12
+ </template>
13
+
14
+ <script>
15
+ import VarSiteSnackbarCore from './core'
16
+ import { defineComponent } from 'vue'
17
+ import { useTeleport } from '../utils/components'
18
+ import { props } from './props'
19
+
20
+ export default defineComponent({
21
+ name: 'VarSiteSnackbar',
22
+ components: {
23
+ VarSiteSnackbarCore,
24
+ },
25
+ props,
26
+ setup() {
27
+ const { disabled } = useTeleport()
28
+
29
+ return {
30
+ disabled,
31
+ }
32
+ },
33
+ })
34
+ </script>
35
+
36
+ <style lang="less">
37
+ @import '../styles/common';
38
+ </style>
@@ -0,0 +1,117 @@
1
+ <template>
2
+ <div class="var-site-snackbar" :style="{ pointerEvents: isForbidClick ? 'auto' : 'none', zIndex }" v-show="show">
3
+ <div :class="snackbarClass" :style="{ zIndex }">
4
+ <div class="var-site-snackbar__content" :class="[contentClass]">
5
+ <slot>{{ content }}</slot>
6
+ </div>
7
+ <div class="var-site-snackbar__action">
8
+ <var-site-icon v-if="iconName" :name="iconName" />
9
+ <var-site-loading v-if="type === 'loading'" :type="loadingType" :size="loadingSize" />
10
+ <slot name="action" />
11
+ </div>
12
+ </div>
13
+ </div>
14
+ </template>
15
+
16
+ <script lang="ts">
17
+ import { defineComponent, watch, ref, onMounted, computed } from 'vue'
18
+ import VarSiteLoading from '../loading'
19
+ import VarSiteIcon from '../icon'
20
+ import { useZIndex } from '../context/zIndex'
21
+ import { props } from './props'
22
+ import { useLock } from '../context/lock'
23
+ import { SNACKBAR_TYPE } from './index'
24
+ import type { Ref, ComputedRef } from 'vue'
25
+ import type { SnackbarType } from './index'
26
+
27
+ const ICON_TYPE_DICT: Record<SnackbarType, string> = {
28
+ success: 'checkbox-marked-circle',
29
+ warning: 'warning',
30
+ info: 'information',
31
+ error: 'error',
32
+ loading: '',
33
+ }
34
+
35
+ export default defineComponent({
36
+ name: 'VarSiteSnackbarCore',
37
+ components: {
38
+ VarSiteLoading,
39
+ VarSiteIcon,
40
+ },
41
+ props,
42
+ setup(props) {
43
+ const timer: Ref = ref(null)
44
+ const { zIndex } = useZIndex(() => props.show, 1)
45
+
46
+ useLock(props, 'show', 'lockScroll')
47
+
48
+ const snackbarClass: ComputedRef<string> = computed(() => {
49
+ const { position, vertical, type } = props
50
+
51
+ const baseClass = `var-site-snackbar__wrapper var-site-snackbar__wrapper-${position} var-site-elevation--4`
52
+ const verticalClass = vertical ? ' var-site-snackbar__vertical' : ''
53
+ const typeClass = type && SNACKBAR_TYPE.includes(type) ? ` var-site-snackbar__wrapper-${type}` : ''
54
+
55
+ return `${baseClass}${verticalClass}${typeClass}`
56
+ })
57
+
58
+ const isForbidClick: ComputedRef<boolean> = computed(() => props.type === 'loading' || props.forbidClick)
59
+
60
+ const iconName: ComputedRef<string> = computed(() => {
61
+ if (!props.type) return ''
62
+
63
+ return ICON_TYPE_DICT[props.type]
64
+ })
65
+
66
+ const updateAfterDuration = () => {
67
+ timer.value = setTimeout(() => {
68
+ props.type !== 'loading' && props['onUpdate:show']?.(false)
69
+ }, props.duration)
70
+ }
71
+
72
+ watch(
73
+ () => props.show,
74
+ (show) => {
75
+ if (show) {
76
+ props.onOpen?.()
77
+ updateAfterDuration()
78
+ } else if (show === false) {
79
+ clearTimeout(timer.value)
80
+ props.onClose?.()
81
+ }
82
+ }
83
+ )
84
+
85
+ watch(
86
+ () => props._update,
87
+ () => {
88
+ clearTimeout(timer.value)
89
+ updateAfterDuration()
90
+ }
91
+ )
92
+
93
+ onMounted(() => {
94
+ if (props.show) {
95
+ props.onOpen?.()
96
+ updateAfterDuration()
97
+ }
98
+ })
99
+
100
+ return {
101
+ zIndex,
102
+ snackbarClass,
103
+ iconName,
104
+ isForbidClick,
105
+ }
106
+ },
107
+ })
108
+ </script>
109
+
110
+ <style lang="less">
111
+ @import '../styles/common';
112
+ @import '../styles/elevation';
113
+ @import '../loading/loading';
114
+ @import '../button/button';
115
+ @import '../icon/icon';
116
+ @import './snackbar';
117
+ </style>
@@ -0,0 +1,270 @@
1
+ import VarSiteSnackbarCore from './core.vue'
2
+ import VarSiteSnackbar from './Snackbar.vue'
3
+ import context from '../context'
4
+ import { reactive, TransitionGroup } from 'vue'
5
+ import { mountInstance } from '../utils/components'
6
+ import { isPlainObject, toNumber } from '../utils/shared'
7
+ import type { LoadingType, LoadingSize } from '../loading/props'
8
+ import type { App, Component, TeleportProps } from 'vue'
9
+
10
+ export type SnackbarType = 'success' | 'warning' | 'info' | 'error' | 'loading'
11
+
12
+ export const SNACKBAR_TYPE: Array<SnackbarType> = ['loading', 'success', 'warning', 'info', 'error']
13
+
14
+ interface SnackbarHandel {
15
+ clear: () => void
16
+ }
17
+
18
+ interface SnackbarOptions {
19
+ type?: SnackbarType
20
+ content?: string
21
+ position?: 'top' | 'center' | 'bottom'
22
+ loadingType?: LoadingType
23
+ loadingSize?: LoadingSize
24
+ lockScroll?: boolean
25
+ contentClass?: string
26
+ duration?: number
27
+ vertical?: boolean
28
+ show?: boolean
29
+ forbidClick?: boolean
30
+ onOpen?: () => void
31
+ onClose?: () => void
32
+ onOpened?: () => void
33
+ onClosed?: () => void
34
+ // internal
35
+ teleport?: TeleportProps['to']
36
+ }
37
+
38
+ interface UniqSnackbarOptions {
39
+ id: number
40
+ reactiveSnackOptions: SnackbarOptions
41
+ _update?: string
42
+ animationEnd?: boolean
43
+ }
44
+
45
+ interface Snackbar {
46
+ (options: SnackbarOptions | string): SnackbarHandel
47
+
48
+ install(app: App): void
49
+
50
+ allowMultiple(bool: boolean): void
51
+
52
+ success(options: SnackbarOptions | string): SnackbarHandel
53
+
54
+ warning(options: SnackbarOptions | string): SnackbarHandel
55
+
56
+ info(options: SnackbarOptions | string): SnackbarHandel
57
+
58
+ error(options: SnackbarOptions | string): SnackbarHandel
59
+
60
+ loading(options: SnackbarOptions | string): SnackbarHandel
61
+
62
+ clear(): void
63
+
64
+ Component: Component
65
+ }
66
+
67
+ let sid = 0
68
+ let isMount = false
69
+ let unmount: () => void
70
+ let isAllowMultiple = false
71
+ let uniqSnackbarOptions: Array<UniqSnackbarOptions> = reactive<UniqSnackbarOptions[]>([])
72
+
73
+ const defaultOption: Partial<Record<keyof SnackbarOptions, any>> = {
74
+ type: undefined,
75
+ content: '',
76
+ position: 'top',
77
+ duration: 3000,
78
+ vertical: false,
79
+ contentClass: undefined,
80
+ loadingType: 'circle',
81
+ loadingSize: 'normal',
82
+ lockScroll: false,
83
+ teleport: 'body',
84
+ forbidClick: false,
85
+ onOpen: () => {},
86
+ onOpened: () => {},
87
+ onClose: () => {},
88
+ onClosed: () => {},
89
+ }
90
+
91
+ const transitionGroupProps: any = {
92
+ name: 'var-site-snackbar-fade',
93
+ tag: 'div',
94
+ class: 'var-site-transition-group',
95
+ }
96
+
97
+ const TransitionGroupHost = {
98
+ setup: function() {
99
+ return () => {
100
+ const snackbarList = uniqSnackbarOptions.map(({ id, reactiveSnackOptions, _update }: UniqSnackbarOptions) => {
101
+ const transitionGroupEl = document.querySelector('.var-site-transition-group')
102
+ if (reactiveSnackOptions.forbidClick || reactiveSnackOptions.type === 'loading') {
103
+ ;(transitionGroupEl as HTMLElement).classList.add('var-site-pointer-auto')
104
+ } else {
105
+ ;(transitionGroupEl as HTMLElement).classList.remove('var-site-pointer-auto')
106
+ }
107
+
108
+ if (isAllowMultiple) reactiveSnackOptions.position = 'top'
109
+
110
+ const position = isAllowMultiple ? 'relative' : 'absolute' // avoid stylelint value-keyword-case error
111
+
112
+ const style = {
113
+ position,
114
+ ...getTop(reactiveSnackOptions.position)
115
+ }
116
+
117
+ return (
118
+ <VarSiteSnackbarCore
119
+ {...reactiveSnackOptions}
120
+ key={id}
121
+ style={style}
122
+ data-id={id}
123
+ _update={_update}
124
+ v-model={[reactiveSnackOptions.show, 'show']}
125
+ />
126
+ )
127
+ })
128
+
129
+ const zindex = context.zIndex // avoid stylelint value-keyword-case error
130
+
131
+ return (
132
+ <TransitionGroup
133
+ {...transitionGroupProps}
134
+ style={{ zIndex: zindex }}
135
+ onAfterEnter={opened}
136
+ onAfterLeave={removeUniqOption}
137
+ >
138
+ {snackbarList}
139
+ </TransitionGroup>
140
+ )
141
+ }
142
+ }
143
+ }
144
+
145
+ const Snackbar: Snackbar = function (options: SnackbarOptions | string): SnackbarHandel {
146
+ const snackOptions: SnackbarOptions = isPlainObject(options) ? options : { content: options }
147
+ const reactiveSnackOptions: SnackbarOptions = reactive<SnackbarOptions>({
148
+ ...defaultOption,
149
+ ...snackOptions,
150
+ })
151
+ reactiveSnackOptions.show = true
152
+
153
+ if (!isMount) {
154
+ isMount = true
155
+ unmount = mountInstance(TransitionGroupHost).unmountInstance
156
+ }
157
+
158
+ const { length } = uniqSnackbarOptions
159
+ const uniqSnackbarOptionItem: UniqSnackbarOptions = {
160
+ id: sid++,
161
+ reactiveSnackOptions,
162
+ }
163
+
164
+ if (length === 0 || isAllowMultiple) {
165
+ addUniqOption(uniqSnackbarOptionItem)
166
+ } else {
167
+ const _update = `update-${sid}`
168
+ updateUniqOption(reactiveSnackOptions, _update)
169
+ }
170
+
171
+ return {
172
+ clear() {
173
+ if (!isAllowMultiple && uniqSnackbarOptions.length) {
174
+ uniqSnackbarOptions[0].reactiveSnackOptions.show = false
175
+ } else {
176
+ reactiveSnackOptions.show = false
177
+ }
178
+ },
179
+ }
180
+ } as Snackbar
181
+
182
+ SNACKBAR_TYPE.forEach((type) => {
183
+ Snackbar[type] = (options: SnackbarOptions | string): SnackbarHandel => {
184
+ if (isPlainObject(options)) {
185
+ options.type = type
186
+ } else {
187
+ options = {
188
+ content: options,
189
+ type,
190
+ }
191
+ }
192
+ return Snackbar(options)
193
+ }
194
+ })
195
+
196
+ Snackbar.install = function (app: App) {
197
+ app.component(VarSiteSnackbar.name, VarSiteSnackbar)
198
+ }
199
+
200
+ Snackbar.allowMultiple = function (bool = false) {
201
+ if (bool !== isAllowMultiple) {
202
+ uniqSnackbarOptions.forEach((option: UniqSnackbarOptions) => {
203
+ option.reactiveSnackOptions.show = false
204
+ })
205
+
206
+ isAllowMultiple = bool
207
+ }
208
+ }
209
+
210
+ Snackbar.clear = function () {
211
+ uniqSnackbarOptions.forEach((option: UniqSnackbarOptions) => {
212
+ option.reactiveSnackOptions.show = false
213
+ })
214
+ }
215
+
216
+ Snackbar.Component = VarSiteSnackbar
217
+
218
+ function opened(element: HTMLElement): void {
219
+ const id = element.getAttribute('data-id')
220
+ const option = uniqSnackbarOptions.find((option) => option.id === toNumber(id))
221
+ if (option) option.reactiveSnackOptions.onOpened?.()
222
+ }
223
+
224
+ function removeUniqOption(element: HTMLElement): void {
225
+ element.parentElement && element.parentElement.classList.remove('var-site-pointer-auto')
226
+ const id = element.getAttribute('data-id')
227
+
228
+ const option = uniqSnackbarOptions.find((option) => option.id === toNumber(id))
229
+ if (option) {
230
+ option.animationEnd = true
231
+ option.reactiveSnackOptions.onClosed?.()
232
+ }
233
+
234
+ const isAllAnimationEnd = uniqSnackbarOptions.every((option) => option.animationEnd)
235
+
236
+ if (isAllAnimationEnd) {
237
+ unmount?.()
238
+ uniqSnackbarOptions = reactive<UniqSnackbarOptions[]>([])
239
+ isMount = false
240
+ }
241
+ }
242
+
243
+ function addUniqOption(uniqSnackbarOptionItem: UniqSnackbarOptions) {
244
+ uniqSnackbarOptions.push(uniqSnackbarOptionItem)
245
+ }
246
+
247
+ function updateUniqOption(reactiveSnackOptions: SnackbarOptions, _update: string) {
248
+ const [firstOption] = uniqSnackbarOptions
249
+
250
+ firstOption.reactiveSnackOptions = {
251
+ ...firstOption.reactiveSnackOptions,
252
+ ...reactiveSnackOptions,
253
+ }
254
+
255
+ firstOption._update = _update
256
+ }
257
+
258
+ function getTop(position = 'top') {
259
+ if (position === 'bottom') return { [position]: '5%' }
260
+
261
+ return { top: position === 'top' ? '5%' : '45%' }
262
+ }
263
+
264
+ VarSiteSnackbar.install = function (app: App) {
265
+ app.component(VarSiteSnackbar.name, VarSiteSnackbar)
266
+ }
267
+
268
+ export const _SnackbarComponent = VarSiteSnackbar
269
+
270
+ export default Snackbar
@@ -0,0 +1,94 @@
1
+ import { pickProps } from '../utils/components'
2
+ import { props as loadingProps } from '../loading/props'
3
+ import { SNACKBAR_TYPE, SnackbarType } from './index'
4
+ import type { PropType, TeleportProps } from 'vue'
5
+
6
+ export function positionValidator(position: string): boolean {
7
+ const validPositions = ['top', 'center', 'bottom']
8
+ return validPositions.includes(position)
9
+ }
10
+
11
+ export function typeValidator(type: SnackbarType): boolean {
12
+ return SNACKBAR_TYPE.includes(type)
13
+ }
14
+
15
+ export const props = {
16
+ type: {
17
+ type: String as PropType<SnackbarType>,
18
+ validator: typeValidator,
19
+ },
20
+ // snackbar显示的位置
21
+ position: {
22
+ type: String,
23
+ default: 'top',
24
+ validator: positionValidator,
25
+ },
26
+ // content内容
27
+ content: {
28
+ type: String,
29
+ },
30
+ // 为snackbar content添加自定义类名
31
+ contentClass: {
32
+ type: String,
33
+ },
34
+ // snackbar 持续时间
35
+ duration: {
36
+ type: Number,
37
+ default: 3000,
38
+ },
39
+ // 是否将消息条内容堆叠在操作(按钮)之上
40
+ vertical: {
41
+ type: Boolean,
42
+ default: false,
43
+ },
44
+ // 加载动画类型
45
+ loadingType: pickProps(loadingProps, 'type'),
46
+ // 加载动画尺寸
47
+ loadingSize: pickProps(loadingProps, 'size'),
48
+ // 是否禁止滚动穿透
49
+ lockScroll: {
50
+ type: Boolean,
51
+ default: false,
52
+ },
53
+ // 控制组件可见还是隐藏
54
+ show: {
55
+ type: Boolean,
56
+ default: false,
57
+ },
58
+ // teleport
59
+ teleport: {
60
+ type: String as PropType<TeleportProps['to']>,
61
+ default: 'body',
62
+ },
63
+ // 是否禁止点击背景
64
+ forbidClick: {
65
+ type: Boolean,
66
+ default: false,
67
+ },
68
+ // 打开时的回调函数
69
+ onOpen: {
70
+ type: Function,
71
+ default: () => {},
72
+ },
73
+ // 打开动画结束时的回调
74
+ onOpened: {
75
+ type: Function,
76
+ default: () => {},
77
+ },
78
+ // 关闭时的回调函数
79
+ onClose: {
80
+ type: Function,
81
+ default: () => {},
82
+ },
83
+ // 关闭动画结束时的回调
84
+ onClosed: {
85
+ type: Function,
86
+ default: () => {},
87
+ },
88
+ 'onUpdate:show': {
89
+ type: Function,
90
+ },
91
+ _update: {
92
+ type: String,
93
+ },
94
+ }
@@ -0,0 +1,135 @@
1
+ @site-snackbar-width: 256px;
2
+ @site-snackbar-color: rgba(255, 255, 255, 0.87);
3
+ @site-snackbar-border-radius: 4px;
4
+ @site-snackbar-background: #333;
5
+ @site-snackbar-font-size: var(--site-font-size-md);
6
+ @site-snackbar-margin: 6px 24px;
7
+ @site-snackbar-border-color: currentColor;
8
+ @site-snackbar-success-background: var(--site-color-success);
9
+ @site-snackbar-info-background: var(--site-color-info);
10
+ @site-snackbar-error-background: var(--site-color-danger);
11
+ @site-snackbar-warning-background: var(--site-color-warning);
12
+ @site-snackbar-content-padding: 14px 16px;
13
+ @site-snackbar-action-margin: 0 16px 0 0;
14
+
15
+ :root {
16
+ --site-snackbar-width: @site-snackbar-width;
17
+ --site-snackbar-color: @site-snackbar-color;
18
+ --site-snackbar-border-radius: @site-snackbar-border-radius;
19
+ --site-snackbar-background: @site-snackbar-background;
20
+ --site-snackbar-font-size: @site-snackbar-font-size;
21
+ --site-snackbar-margin: @site-snackbar-margin;
22
+ --site-snackbar-border-color: @site-snackbar-border-color;
23
+ --site-snackbar-success-background: @site-snackbar-success-background;
24
+ --site-snackbar-info-background: @site-snackbar-info-background;
25
+ --site-snackbar-error-background: @site-snackbar-error-background;
26
+ --site-snackbar-warning-background: @site-snackbar-warning-background;
27
+ --site-snackbar-content-padding: @site-snackbar-content-padding;
28
+ --site-snackbar-action-margin: @site-snackbar-action-margin;
29
+ }
30
+
31
+ .var-site-transition-group {
32
+ position: fixed;
33
+ left: 0;
34
+ right: 0;
35
+ top: 0;
36
+ bottom: 0;
37
+ pointer-events: none;
38
+ }
39
+
40
+ .var-site-pointer-auto {
41
+ pointer-events: auto;
42
+ }
43
+
44
+ .var-site {
45
+ &-snackbar {
46
+ display: flex;
47
+ justify-content: center;
48
+ align-items: baseline;
49
+ left: 0;
50
+ right: 0;
51
+ bottom: 0;
52
+ transition: all 0.15s var(--site-cubic-bezier);
53
+
54
+ &__wrapper {
55
+ width: var(--site-snackbar-width);
56
+ display: flex;
57
+ border-radius: var(--site-snackbar-border-radius);
58
+ color: var(--site-snackbar-color);
59
+ background: var(--site-snackbar-background);
60
+ font-size: var(--site-snackbar-font-size);
61
+ margin: var(--site-snackbar-margin);
62
+ align-items: center;
63
+ border-color: var(--site-snackbar-border-color);
64
+ pointer-events: auto;
65
+ transition: 0.3s var(--site-cubic-bezier);
66
+
67
+ &-success {
68
+ background: var(--site-snackbar-success-background);
69
+ }
70
+
71
+ &-info {
72
+ background: var(--site-snackbar-info-background);
73
+ }
74
+
75
+ &-warning {
76
+ background: var(--site-snackbar-warning-background);
77
+ }
78
+
79
+ &-error {
80
+ background: var(--site-snackbar-error-background);
81
+ }
82
+ }
83
+
84
+ &__content {
85
+ flex-grow: 1;
86
+ padding: var(--site-snackbar-content-padding);
87
+ }
88
+
89
+ &__action {
90
+ margin: var(--site-snackbar-action-margin);
91
+ display: flex;
92
+ }
93
+
94
+ &__vertical {
95
+ flex-direction: column;
96
+ align-items: flex-start;
97
+
98
+ .var-site-snackbar__action {
99
+ align-self: flex-end;
100
+ margin-bottom: 8px;
101
+ }
102
+ }
103
+
104
+ &-fade-leave-active {
105
+ position: absolute;
106
+ }
107
+
108
+ &-fade-enter-from,
109
+ &-fade-leave-to {
110
+ opacity: 0;
111
+ transform: translateY(-30px);
112
+ }
113
+ }
114
+
115
+ &-snackbar-transition {
116
+ top: 0;
117
+ position: fixed;
118
+
119
+ .var-site-snackbar__wrapper {
120
+ position: absolute;
121
+
122
+ &-top {
123
+ top: 5%;
124
+ }
125
+
126
+ &-center {
127
+ top: 45%;
128
+ }
129
+
130
+ &-bottom {
131
+ bottom: 5%;
132
+ }
133
+ }
134
+ }
135
+ }
@@ -1,5 +1,6 @@
1
1
  import { isNumber, isString, toNumber, kebabCase } from './shared'
2
2
  import type { StyleVars } from '../../utils'
3
+ import { requestAnimationFrame } from '@varlet/ui/src/utils/elements'
3
4
 
4
5
  export function getLeft(element: HTMLElement): number {
5
6
  const { left } = element.getBoundingClientRect()
@@ -81,3 +82,11 @@ export function formatStyleVars(styleVars: StyleVars | null) {
81
82
  return styles
82
83
  }, {} as StyleVars)
83
84
  }
85
+
86
+ export function doubleRaf() {
87
+ return new Promise((resolve) => {
88
+ requestAnimationFrame(() => {
89
+ requestAnimationFrame(resolve)
90
+ })
91
+ })
92
+ }
@@ -12,6 +12,9 @@ export const toNumber = (val: number | string | boolean | undefined | null): num
12
12
  return val
13
13
  }
14
14
 
15
+ export const isPlainObject = (val: unknown): val is Record<string, any> =>
16
+ Object.prototype.toString.call(val) === '[object Object]'
17
+
15
18
  export function kebabCase(str: string): string {
16
19
  const reg = /([^-])([A-Z])/g
17
20
 
@@ -86,7 +86,6 @@
86
86
  </template>
87
87
 
88
88
  <script lang="ts">
89
- // @ts-ignore
90
89
  import config from '@config'
91
90
  import { computed, ComputedRef, defineComponent, ref, Ref, watch } from 'vue'
92
91
  import { useRoute } from 'vue-router'
@@ -98,7 +97,7 @@ import {
98
97
  removeEmpty,
99
98
  setThemes,
100
99
  watchLang,
101
- watchThemes
100
+ watchThemes,
102
101
  } from '../utils'
103
102
  import { get } from 'lodash-es'
104
103
 
@@ -112,11 +111,12 @@ export default defineComponent({
112
111
  const languages: Ref<Record<string, string>> = ref(get(config, 'mobile.header.i18n'))
113
112
  const nonEmptyLanguages: ComputedRef<Record<string, string>> = computed(() => removeEmpty(languages.value))
114
113
  const redirect = get(config, 'mobile.redirect', '')
114
+ const themesKey = get(config, 'themesKey')
115
115
  const github: Ref<string> = ref(get(config, 'mobile.header.github'))
116
116
  const darkMode: Ref<string> = ref(get(config, 'mobile.header.darkMode'))
117
- const currentThemes = ref(getBrowserThemes())
117
+ const currentThemes = ref(getBrowserThemes(themesKey))
118
118
 
119
- const changeLanguage = (lang) => {
119
+ const changeLanguage = (lang: string) => {
120
120
  language.value = lang
121
121
  showMenu.value = false
122
122
  window.location.href = `./mobile.html#${route.path}?language=${language.value}&replace=${route.query.replace}`
@@ -136,7 +136,7 @@ export default defineComponent({
136
136
 
137
137
  const toGithub = () => {
138
138
  if (inIframe() && !isPhone()) {
139
- window.top.open(github.value)
139
+ window.top!.open(github.value)
140
140
  } else {
141
141
  window.location.href = github.value
142
142
  }
@@ -161,7 +161,7 @@ export default defineComponent({
161
161
  const setCurrentThemes = (themes: 'themes' | 'darkThemes') => {
162
162
  currentThemes.value = themes
163
163
  setThemes(config, currentThemes.value)
164
- window.localStorage.setItem('currentThemes', currentThemes.value)
164
+ window.localStorage.setItem(themesKey, currentThemes.value)
165
165
  }
166
166
 
167
167
  const toggleTheme = () => {
@@ -0,0 +1,4 @@
1
+ declare module 'clipboard'
2
+ declare module '@pc-routes'
3
+ declare module '@mobile-routes'
4
+ declare module '@config'
package/site/pc/App.vue CHANGED
@@ -29,11 +29,10 @@
29
29
  </template>
30
30
 
31
31
  <script lang="ts">
32
- // @ts-ignore
33
32
  import config from '@config'
34
- import AppMobile from './components/AppMobile'
35
- import AppHeader from './components/AppHeader'
36
- import AppSidebar from './components/AppSidebar'
33
+ import AppMobile from './components/AppMobile.vue'
34
+ import AppHeader from './components/AppHeader.vue'
35
+ import AppSidebar from './components/AppSidebar.vue'
37
36
  import { defineComponent, nextTick, onMounted, ref, Ref, watch } from 'vue'
38
37
  import { useRoute } from 'vue-router'
39
38
  import { get } from 'lodash-es'
@@ -52,7 +51,6 @@ export default defineComponent({
52
51
  AppSidebar
53
52
  },
54
53
  setup() {
55
- // config
56
54
  const defaultLanguage = get(config, 'defaultLanguage')
57
55
  const menu: Ref<Menu[]> = ref(get(config, 'pc.menu', []))
58
56
  const useMobile = ref(get(config, 'useMobile'))
@@ -79,7 +77,7 @@ export default defineComponent({
79
77
 
80
78
  nextTick(() => {
81
79
  const children = document
82
- .querySelector('.varlet-site-sidebar')
80
+ .querySelector('.varlet-site-sidebar')!
83
81
  .getElementsByClassName('var-site-cell')
84
82
  const index = menu.value.findIndex((item) => item.doc === menuName)
85
83
 
@@ -93,7 +91,7 @@ export default defineComponent({
93
91
  }
94
92
 
95
93
  const handleSidebarChange = (menu: Menu) => {
96
- doc.value.scrollTop = 0
94
+ doc.value!.scrollTop = 0
97
95
  componentName.value = getComponentNameByMenuName(menu.doc)
98
96
  menuName.value = menu.doc
99
97
  }
@@ -287,17 +285,17 @@ iframe {
287
285
  }
288
286
 
289
287
  pre {
290
- margin: 20px 0 0;
288
+ margin: 10px 0 0;
291
289
  }
292
290
 
293
291
  code {
294
292
  position: relative;
295
293
  display: block;
296
- padding: 16px;
294
+ padding: 10px 16px;
297
295
  overflow-x: auto;
298
296
  font-size: 13px;
299
297
  font-family: Consolas, Monaco, monospace;
300
- line-height: 26px;
298
+ line-height: 31px;
301
299
  white-space: pre-wrap;
302
300
  word-wrap: break-word;
303
301
  color: #fff;
@@ -61,15 +61,14 @@
61
61
  </template>
62
62
 
63
63
  <script lang="ts">
64
- // @ts-ignore
65
64
  import config from '@config'
66
- import { ref, computed } from 'vue'
65
+ import { ref, computed, defineComponent } from 'vue'
67
66
  import { get } from 'lodash-es'
68
67
  import { getBrowserThemes, getPCLocationInfo, removeEmpty, setThemes, watchThemes } from '../../utils'
69
68
  import { useRouter } from 'vue-router'
70
69
  import type { Ref, ComputedRef } from 'vue'
71
70
 
72
- export default {
71
+ export default defineComponent({
73
72
  name: 'AppHeader',
74
73
  props: {
75
74
  language: {
@@ -77,19 +76,19 @@ export default {
77
76
  },
78
77
  },
79
78
  setup() {
80
- // config
81
79
  const title: Ref<string> = ref(get(config, 'title'))
82
80
  const logo: Ref<string> = ref(get(config, 'logo'))
81
+ const themesKey = get(config, 'themesKey')
83
82
  const languages: Ref<Record<string, string>> = ref(get(config, 'pc.header.i18n'))
84
83
  const github: Ref<Record<string, string>> = ref(get(config, 'pc.header.github'))
85
84
  const darkMode: Ref<Record<string, string>> = ref(get(config, 'pc.header.darkMode'))
86
- const currentThemes = ref(getBrowserThemes())
85
+ const currentThemes = ref(getBrowserThemes(themesKey))
87
86
 
88
87
  const isOpenMenu: Ref<boolean> = ref(false)
89
88
  const router = useRouter()
90
89
  const nonEmptyLanguages: ComputedRef<Record<string, string>> = computed(() => removeEmpty(languages.value))
91
90
 
92
- const handleLanguageChange = (language) => {
91
+ const handleLanguageChange = (language: string) => {
93
92
  const { menuName } = getPCLocationInfo()
94
93
  router.replace(`/${language}/${menuName}`)
95
94
  isOpenMenu.value = false
@@ -98,7 +97,7 @@ export default {
98
97
  const setCurrentThemes = (themes: 'themes' | 'darkThemes') => {
99
98
  currentThemes.value = themes
100
99
  setThemes(config, currentThemes.value)
101
- window.localStorage.setItem('currentThemes', currentThemes.value)
100
+ window.localStorage.setItem(themesKey, currentThemes.value)
102
101
  }
103
102
 
104
103
  const getThemesMessage = () => ({ action: 'themesChange', from: 'pc', data: currentThemes.value })
@@ -106,7 +105,7 @@ export default {
106
105
  const toggleTheme = () => {
107
106
  setCurrentThemes(currentThemes.value === 'darkThemes' ? 'themes' : 'darkThemes')
108
107
  window.postMessage(getThemesMessage(), '*')
109
- ;(document.getElementById('mobile') as HTMLIFrameElement).contentWindow.postMessage(getThemesMessage(), '*')
108
+ ;(document.getElementById('mobile') as HTMLIFrameElement).contentWindow!.postMessage(getThemesMessage(), '*')
110
109
  }
111
110
 
112
111
  watchThemes((themes, from) => {
@@ -129,7 +128,7 @@ export default {
129
128
  toggleTheme,
130
129
  }
131
130
  },
132
- }
131
+ })
133
132
  </script>
134
133
 
135
134
  <style scoped lang="less">
@@ -27,12 +27,12 @@
27
27
  // @ts-ignore
28
28
  import config from '@config'
29
29
  import { MenuTypes } from '../../utils'
30
- import { reactive, ref } from 'vue'
30
+ import { reactive, ref, defineComponent } from 'vue'
31
31
  import type { PropType } from 'vue'
32
- import type { Menu } from '../App'
32
+ import type { Menu } from '../App.vue'
33
33
  import { get } from 'lodash-es'
34
34
 
35
- export default {
35
+ export default defineComponent({
36
36
  name: 'AppSidebar',
37
37
  props: {
38
38
  menu: {
@@ -50,7 +50,7 @@ export default {
50
50
  const menuTypes = reactive(MenuTypes)
51
51
  const themes = ref(get(config, 'themes'))
52
52
 
53
- const changeRoute = (item) => {
53
+ const changeRoute = (item: Menu) => {
54
54
  if (item.type === MenuTypes.TITLE || props.menuName === item.doc) {
55
55
  return
56
56
  }
@@ -64,7 +64,7 @@ export default {
64
64
  changeRoute
65
65
  }
66
66
  }
67
- }
67
+ })
68
68
  </script>
69
69
 
70
70
  <style scoped lang="less">
package/site/pc/main.ts CHANGED
@@ -1,13 +1,14 @@
1
1
  import App from './App.vue'
2
2
  import '@varlet/touch-emulator'
3
- // @ts-ignore
4
3
  import routes from '@pc-routes'
5
- // @ts-ignore
6
4
  import config from '@config'
7
5
 
8
6
  import Icon from '../components/icon'
9
7
  import Cell from '../components/cell'
10
8
  import Ripple from '../components/ripple'
9
+ import CodeExample from '../components/code-example'
10
+ import Snackbar from '../components/snackbar'
11
+
11
12
  import '../components/styles/common.less'
12
13
  import '../components/styles/elevation.less'
13
14
 
@@ -20,6 +21,8 @@ const defaultLanguage = get(config, 'defaultLanguage')
20
21
  const redirect = get(config, 'pc.redirect')
21
22
  const mobileRedirect = get(config, 'mobile.redirect')
22
23
 
24
+ Snackbar.allowMultiple(true)
25
+
23
26
  redirect &&
24
27
  routes.push({
25
28
  path: '/:pathMatch(.*)*',
@@ -85,4 +88,7 @@ createApp(App)
85
88
  .use(Ripple)
86
89
  // @ts-ignore
87
90
  .use(Icon)
91
+ // @ts-ignore
92
+ .use(CodeExample)
93
+ .use(Snackbar)
88
94
  .mount('#app')
@@ -1,16 +1,11 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "outDir": "./lib",
4
3
  "target": "es5",
5
- "module": "commonjs",
6
- "sourceMap": false,
7
4
  "strict": true,
8
5
  "downlevelIteration": true,
9
- "declaration": true,
10
6
  "skipLibCheck": true,
11
7
  "esModuleInterop": true,
12
8
  "jsx": "preserve",
13
9
  "lib": ["esnext", "dom"]
14
- },
15
- "include": ["*"]
10
+ }
16
11
  }
@@ -7,11 +7,15 @@ import { getBrowserThemes, mountInstance, watchThemes } from './utils'
7
7
  import { get } from 'lodash-es'
8
8
 
9
9
  function getColor(themes?: 'themes' | 'darkThemes') {
10
- return get(config, `${themes ?? getBrowserThemes()}.color-progress`)
10
+ const themesKey = get(config, 'themesKey')
11
+
12
+ return get(config, `${themes ?? getBrowserThemes(themesKey)}.color-progress`)
11
13
  }
12
14
 
13
15
  function getTrackColor(themes?: 'themes' | 'darkThemes') {
14
- return get(config, `${themes ?? getBrowserThemes()}.color-progress-track`)
16
+ const themesKey = get(config, 'themesKey')
17
+
18
+ return get(config, `${themes ?? getBrowserThemes(themesKey)}.color-progress-track`)
15
19
  }
16
20
 
17
21
  export function useProgress() {
package/site/utils.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { onMounted, onUnmounted } from 'vue'
2
2
  import { get } from 'lodash-es'
3
3
  import { formatStyleVars } from './components/utils/elements'
4
+ import config from '@config'
4
5
 
5
6
  export * from './components/utils/components'
6
7
  export * from './components/utils/elements'
@@ -138,12 +139,12 @@ export function setThemes(config: Record<string, any>, name: 'themes' | 'darkThe
138
139
  StyleProvider(styleVars)
139
140
  }
140
141
 
141
- export function getBrowserThemes(): 'darkThemes' | 'themes' {
142
- let currentThemes = window.localStorage.getItem('currentThemes') as 'darkThemes' | 'themes'
142
+ export function getBrowserThemes(themes = 'VARLET_THEMES'): 'darkThemes' | 'themes' {
143
+ let currentThemes = window.localStorage.getItem(themes) as 'darkThemes' | 'themes'
143
144
 
144
145
  if (!currentThemes) {
145
146
  currentThemes = window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'darkThemes' : 'themes'
146
- window.localStorage.setItem('currentThemes', currentThemes)
147
+ window.localStorage.setItem(themes, currentThemes)
147
148
  }
148
149
 
149
150
  return currentThemes
package/tsconfig.json CHANGED
@@ -2,8 +2,6 @@
2
2
  "compilerOptions": {
3
3
  "outDir": "./lib",
4
4
  "target": "es5",
5
- "module": "commonjs",
6
- "sourceMap": false,
7
5
  "strict": true,
8
6
  "downlevelIteration": true,
9
7
  "declaration": true,
@@ -4,6 +4,7 @@ module.exports = {
4
4
  host: 'localhost',
5
5
  port: 8080,
6
6
  title: 'VARLET',
7
+ themesKey: 'VARLET_THEMES',
7
8
  logo: 'https://varlet.gitee.io/varlet-ui/varlet_icon.png',
8
9
  defaultLanguage: 'zh-CN',
9
10
  highlight: {
@@ -31,6 +32,18 @@ module.exports = {
31
32
  github: 'https://github.com/haoziqaq/varlet',
32
33
  darkMode: true,
33
34
  },
35
+ clipboard: {
36
+ 'zh-CN': '代码已复制到剪切板',
37
+ 'en-US': 'The code has been copied to the clipboard',
38
+ },
39
+ fold: {
40
+ message: {
41
+ 'zh-CN': '已显示完整代码',
42
+ 'en-US': 'Complete code displayed',
43
+ },
44
+ defaultFold: true,
45
+ foldHeight: 60,
46
+ },
34
47
  },
35
48
  mobile: {
36
49
  redirect: '/home',