@luanlu/mk-motion 1.1.0 → 1.2.1
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 +14 -2
- package/src/a11y/focus-trap.ts +64 -0
- package/src/a11y/keyboard.ts +43 -0
- package/src/components/alert/alert.css +111 -0
- package/src/components/alert/alert.ts +107 -0
- package/src/components/avatar/avatar.css +112 -0
- package/src/components/avatar/avatar.ts +175 -0
- package/src/components/breadcrumb/breadcrumb.css +31 -0
- package/src/components/breadcrumb/breadcrumb.ts +71 -0
- package/src/components/button/button.css +108 -0
- package/src/components/button/button.ts +140 -0
- package/src/components/card/card.css +52 -0
- package/src/components/card/card.ts +87 -0
- package/src/components/collapse/collapse.css +76 -0
- package/src/components/collapse/collapse.ts +168 -0
- package/src/components/dialog/dialog.css +78 -0
- package/src/components/dialog/dialog.ts +164 -0
- package/src/components/drawer/drawer.css +73 -0
- package/src/components/drawer/drawer.ts +131 -0
- package/src/components/empty/empty.css +36 -0
- package/src/components/empty/empty.ts +85 -0
- package/src/components/form/checkbox.css +56 -0
- package/src/components/form/checkbox.ts +119 -0
- package/src/components/form/radio.css +57 -0
- package/src/components/form/radio.ts +153 -0
- package/src/components/form/select.css +91 -0
- package/src/components/form/select.ts +174 -0
- package/src/components/form/slider.css +56 -0
- package/src/components/form/slider.ts +148 -0
- package/src/components/input/input.css +92 -0
- package/src/components/input/input.ts +162 -0
- package/src/components/layout/divider.css +32 -0
- package/src/components/layout/divider.ts +42 -0
- package/src/components/layout/row.css +64 -0
- package/src/components/layout/row.ts +57 -0
- package/src/components/layout/space.css +14 -0
- package/src/components/layout/space.ts +48 -0
- package/src/components/loading/loading.css +37 -0
- package/src/components/loading/loading.ts +46 -0
- package/src/components/menu/menu.css +121 -0
- package/src/components/menu/menu.ts +187 -0
- package/src/components/message/message.css +64 -0
- package/src/components/message/message.ts +96 -0
- package/src/components/popover/popover.css +73 -0
- package/src/components/popover/popover.ts +279 -0
- package/src/components/progress/progress.css +112 -0
- package/src/components/progress/progress.ts +171 -0
- package/src/components/steps/steps.css +127 -0
- package/src/components/steps/steps.ts +102 -0
- package/src/components/styles/components.css +28 -0
- package/src/components/styles/reset.css +24 -0
- package/src/components/styles/tokens.css +248 -0
- package/src/components/styles/variables.css +24 -0
- package/src/components/switch/switch.css +53 -0
- package/src/components/switch/switch.ts +103 -0
- package/src/components/table/table.css +192 -0
- package/src/components/table/table.ts +370 -0
- package/src/components/tabs/tabs.css +138 -0
- package/src/components/tabs/tabs.ts +211 -0
- package/src/components/tag/tag.css +123 -0
- package/src/components/tag/tag.ts +112 -0
- package/src/components/tooltip/tooltip.css +66 -0
- package/src/components/tooltip/tooltip.ts +185 -0
- package/src/core/animator.ts +124 -0
- package/src/core/timeline.ts +128 -0
- package/src/core/utils.ts +47 -0
- package/src/effects/glitch.ts +99 -0
- package/src/effects/particle.ts +134 -0
- package/src/effects/text-split.ts +95 -0
- package/src/effects/wave-text.ts +88 -0
- package/src/gesture/draggable.ts +130 -0
- package/src/gesture/spring.ts +152 -0
- package/src/index.ts +162 -0
- package/src/interactive/coverflow.ts +100 -0
- package/src/interactive/cursor-trail.ts +113 -0
- package/src/interactive/flip-card.ts +114 -0
- package/src/interactive/magnetic.ts +121 -0
- package/src/micro/hover-lift.ts +94 -0
- package/src/micro/ripple.ts +130 -0
- package/src/motion/component-motion.ts +177 -0
- package/src/nuxt/module.ts +46 -0
- package/src/presets/index.ts +69 -0
- package/src/scroll/scroll-trigger.ts +104 -0
- package/src/styles/animations.css +135 -0
- package/src/styles/element-plus.css +174 -0
- package/src/text/count-up.ts +108 -0
- package/src/text/typewriter.ts +109 -0
- package/src/theme/dark.css +19 -0
- package/src/theme/light.css +19 -0
- package/src/theme/theme.ts +65 -0
- package/src/transitions/blur-reveal.ts +92 -0
- package/src/transitions/collapse.ts +112 -0
- package/src/transitions/lazy-image.ts +87 -0
- package/src/transitions/list.ts +75 -0
- package/src/transitions/loading.ts +95 -0
- package/src/transitions/parallax.ts +60 -0
- package/src/transitions/shimmer.ts +105 -0
- package/src/transitions/toast.ts +151 -0
- package/src/types.d.ts +4 -0
- package/src/vite/plugin.ts +45 -0
- package/src/vue/button.ts +28 -9
- package/src/vue/card.ts +28 -8
- package/src/vue/composables/index.ts +4 -0
- package/src/vue/composables/useLoading.ts +12 -0
- package/src/vue/composables/useMessage.ts +16 -0
- package/src/vue/composables/useMotion.ts +19 -0
- package/src/vue/composables/useTheme.ts +12 -0
- package/src/vue/dialog.ts +69 -17
- package/src/vue/index.ts +4 -21
- package/src/vue/input.ts +35 -11
- package/src/vue/slider.ts +22 -4
- package/src/vue/switch.ts +16 -9
- package/src/vue/alert.ts +0 -32
- package/src/vue/avatar.ts +0 -34
- package/src/vue/breadcrumb.ts +0 -32
- package/src/vue/checkbox.ts +0 -32
- package/src/vue/collapse.ts +0 -33
- package/src/vue/divider.ts +0 -32
- package/src/vue/drawer.ts +0 -33
- package/src/vue/empty.ts +0 -33
- package/src/vue/menu.ts +0 -33
- package/src/vue/popover.ts +0 -34
- package/src/vue/progress.ts +0 -33
- package/src/vue/row.ts +0 -32
- package/src/vue/select.ts +0 -33
- package/src/vue/space.ts +0 -32
- package/src/vue/steps.ts +0 -33
- package/src/vue/table.ts +0 -33
- package/src/vue/tabs.ts +0 -33
- package/src/vue/tag.ts +0 -33
- package/src/vue/tooltip.ts +0 -34
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export interface LoadingOptions {
|
|
2
|
+
size?: number
|
|
3
|
+
color?: string
|
|
4
|
+
thickness?: number
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const DEFAULT_LOADING: Required<LoadingOptions> = {
|
|
8
|
+
size: 40,
|
|
9
|
+
color: '#38bdf8',
|
|
10
|
+
thickness: 3,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 在元素上显示 Element Plus 风格的加载旋转器
|
|
15
|
+
*/
|
|
16
|
+
export function showLoading(
|
|
17
|
+
element: HTMLElement | string,
|
|
18
|
+
options: LoadingOptions = {}
|
|
19
|
+
): () => void {
|
|
20
|
+
const el =
|
|
21
|
+
typeof element === 'string'
|
|
22
|
+
? document.querySelector<HTMLElement>(element)!
|
|
23
|
+
: element
|
|
24
|
+
|
|
25
|
+
if (!el) throw new Error('showLoading: element not found')
|
|
26
|
+
|
|
27
|
+
const opts = { ...DEFAULT_LOADING, ...options }
|
|
28
|
+
|
|
29
|
+
el.style.position = 'relative'
|
|
30
|
+
|
|
31
|
+
const mask = document.createElement('div')
|
|
32
|
+
mask.className = 'mk-loading-mask'
|
|
33
|
+
mask.style.cssText = `
|
|
34
|
+
position: absolute;
|
|
35
|
+
inset: 0;
|
|
36
|
+
background: rgba(15, 23, 42, 0.7);
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: center;
|
|
40
|
+
z-index: 100;
|
|
41
|
+
border-radius: inherit;
|
|
42
|
+
`
|
|
43
|
+
|
|
44
|
+
const spinner = document.createElement('div')
|
|
45
|
+
spinner.className = 'mk-transition mk-spin'
|
|
46
|
+
spinner.style.cssText = `
|
|
47
|
+
width: ${opts.size}px;
|
|
48
|
+
height: ${opts.size}px;
|
|
49
|
+
border: ${opts.thickness}px solid rgba(255,255,255,0.1);
|
|
50
|
+
border-top-color: ${opts.color};
|
|
51
|
+
border-radius: 50%;
|
|
52
|
+
`
|
|
53
|
+
|
|
54
|
+
mask.appendChild(spinner)
|
|
55
|
+
el.appendChild(mask)
|
|
56
|
+
|
|
57
|
+
return () => {
|
|
58
|
+
mask.remove()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 创建全屏加载
|
|
64
|
+
*/
|
|
65
|
+
export function fullscreenLoading(options: LoadingOptions = {}): () => void {
|
|
66
|
+
const opts = { ...DEFAULT_LOADING, ...options }
|
|
67
|
+
|
|
68
|
+
const mask = document.createElement('div')
|
|
69
|
+
mask.style.cssText = `
|
|
70
|
+
position: fixed;
|
|
71
|
+
inset: 0;
|
|
72
|
+
background: rgba(0, 0, 0, 0.5);
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
justify-content: center;
|
|
76
|
+
z-index: 9999;
|
|
77
|
+
`
|
|
78
|
+
|
|
79
|
+
const spinner = document.createElement('div')
|
|
80
|
+
spinner.className = 'mk-transition mk-spin'
|
|
81
|
+
spinner.style.cssText = `
|
|
82
|
+
width: ${opts.size}px;
|
|
83
|
+
height: ${opts.size}px;
|
|
84
|
+
border: ${opts.thickness}px solid rgba(255,255,255,0.15);
|
|
85
|
+
border-top-color: ${opts.color};
|
|
86
|
+
border-radius: 50%;
|
|
87
|
+
`
|
|
88
|
+
|
|
89
|
+
mask.appendChild(spinner)
|
|
90
|
+
document.body.appendChild(mask)
|
|
91
|
+
|
|
92
|
+
return () => {
|
|
93
|
+
mask.remove()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface ParallaxOptions {
|
|
2
|
+
speed?: number // 视差速度,0.1~1
|
|
3
|
+
direction?: 'vertical' | 'horizontal'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const DEFAULT_PARALLAX: Required<ParallaxOptions> = {
|
|
7
|
+
speed: 0.3,
|
|
8
|
+
direction: 'vertical',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 给元素添加视差滚动效果
|
|
13
|
+
*/
|
|
14
|
+
export function parallax(
|
|
15
|
+
element: HTMLElement | string,
|
|
16
|
+
options: ParallaxOptions = {}
|
|
17
|
+
): () => void {
|
|
18
|
+
const el =
|
|
19
|
+
typeof element === 'string'
|
|
20
|
+
? document.querySelector<HTMLElement>(element)!
|
|
21
|
+
: element
|
|
22
|
+
|
|
23
|
+
if (!el) throw new Error('parallax: element not found')
|
|
24
|
+
|
|
25
|
+
const opts = { ...DEFAULT_PARALLAX, ...options }
|
|
26
|
+
|
|
27
|
+
const onScroll = () => {
|
|
28
|
+
const rect = el.getBoundingClientRect()
|
|
29
|
+
const windowHeight = window.innerHeight
|
|
30
|
+
const centerOffset = (rect.top + rect.height / 2 - windowHeight / 2)
|
|
31
|
+
const offset = centerOffset * opts.speed
|
|
32
|
+
|
|
33
|
+
if (opts.direction === 'vertical') {
|
|
34
|
+
el.style.transform = `translateY(${offset}px)`
|
|
35
|
+
} else {
|
|
36
|
+
el.style.transform = `translateX(${offset}px)`
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
window.addEventListener('scroll', onScroll, { passive: true })
|
|
41
|
+
onScroll()
|
|
42
|
+
|
|
43
|
+
return () => {
|
|
44
|
+
window.removeEventListener('scroll', onScroll)
|
|
45
|
+
el.style.transform = ''
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 批量给多个元素添加视差,支持不同速度
|
|
51
|
+
*/
|
|
52
|
+
export function parallaxGroup(
|
|
53
|
+
items: Array<{ selector: string; speed?: number; direction?: 'vertical' | 'horizontal' }>
|
|
54
|
+
): () => void {
|
|
55
|
+
const cleaners = items.map((item) =>
|
|
56
|
+
parallax(item.selector, { speed: item.speed, direction: item.direction })
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return () => cleaners.forEach((fn) => fn())
|
|
60
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export interface ShimmerOptions {
|
|
2
|
+
duration?: number // shimmer 循环周期
|
|
3
|
+
color?: string // shimmer 颜色
|
|
4
|
+
angle?: number // 角度 deg
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const DEFAULT_SHIMMER: Required<ShimmerOptions> = {
|
|
8
|
+
duration: 1500,
|
|
9
|
+
color: 'rgba(255,255,255,0.25)',
|
|
10
|
+
angle: 100,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 给元素添加骨架屏 shimmer 效果
|
|
15
|
+
*/
|
|
16
|
+
export function shimmer(
|
|
17
|
+
element: HTMLElement | string,
|
|
18
|
+
options: ShimmerOptions = {}
|
|
19
|
+
): () => void {
|
|
20
|
+
const el =
|
|
21
|
+
typeof element === 'string'
|
|
22
|
+
? document.querySelector<HTMLElement>(element)!
|
|
23
|
+
: element
|
|
24
|
+
|
|
25
|
+
if (!el) throw new Error('shimmer: element not found')
|
|
26
|
+
|
|
27
|
+
const opts = { ...DEFAULT_SHIMMER, ...options }
|
|
28
|
+
|
|
29
|
+
el.style.background = '#1e293b'
|
|
30
|
+
el.style.position = 'relative'
|
|
31
|
+
el.style.overflow = 'hidden'
|
|
32
|
+
|
|
33
|
+
const shimmerEl = document.createElement('div')
|
|
34
|
+
shimmerEl.style.cssText = `
|
|
35
|
+
position: absolute;
|
|
36
|
+
inset: 0;
|
|
37
|
+
background: linear-gradient(${opts.angle}deg, transparent 30%, ${opts.color} 50%, transparent 70%);
|
|
38
|
+
background-size: 200% 100%;
|
|
39
|
+
animation: mk-shimmer ${opts.duration}ms infinite linear;
|
|
40
|
+
pointer-events: none;
|
|
41
|
+
`
|
|
42
|
+
|
|
43
|
+
// 检查是否已有 shimmer keyframes
|
|
44
|
+
if (!document.getElementById('mk-shimmer-style')) {
|
|
45
|
+
const style = document.createElement('style')
|
|
46
|
+
style.id = 'mk-shimmer-style'
|
|
47
|
+
style.textContent = `
|
|
48
|
+
@keyframes mk-shimmer {
|
|
49
|
+
0% { background-position: 200% 0; }
|
|
50
|
+
100% { background-position: -200% 0; }
|
|
51
|
+
}
|
|
52
|
+
`
|
|
53
|
+
document.head.appendChild(style)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
el.appendChild(shimmerEl)
|
|
57
|
+
|
|
58
|
+
return () => {
|
|
59
|
+
shimmerEl.remove()
|
|
60
|
+
el.style.background = ''
|
|
61
|
+
el.style.position = ''
|
|
62
|
+
el.style.overflow = ''
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 骨架屏预设:生成若干 shimmer 占位条
|
|
68
|
+
*/
|
|
69
|
+
export function skeleton(
|
|
70
|
+
container: HTMLElement | string,
|
|
71
|
+
lines: number = 3,
|
|
72
|
+
options?: ShimmerOptions
|
|
73
|
+
): () => void {
|
|
74
|
+
const el =
|
|
75
|
+
typeof container === 'string'
|
|
76
|
+
? document.querySelector<HTMLElement>(container)!
|
|
77
|
+
: container
|
|
78
|
+
|
|
79
|
+
if (!el) throw new Error('skeleton: container not found')
|
|
80
|
+
|
|
81
|
+
const original = el.innerHTML
|
|
82
|
+
el.innerHTML = ''
|
|
83
|
+
el.style.display = 'flex'
|
|
84
|
+
el.style.flexDirection = 'column'
|
|
85
|
+
el.style.gap = '12px'
|
|
86
|
+
|
|
87
|
+
const cleaners: (() => void)[] = []
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < lines; i++) {
|
|
90
|
+
const line = document.createElement('div')
|
|
91
|
+
line.style.height = i === 0 ? '24px' : '16px'
|
|
92
|
+
line.style.width = i === lines - 1 ? '60%' : '100%'
|
|
93
|
+
line.style.borderRadius = '4px'
|
|
94
|
+
el.appendChild(line)
|
|
95
|
+
cleaners.push(shimmer(line, options))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return () => {
|
|
99
|
+
cleaners.forEach((fn) => fn())
|
|
100
|
+
el.innerHTML = original
|
|
101
|
+
el.style.display = ''
|
|
102
|
+
el.style.flexDirection = ''
|
|
103
|
+
el.style.gap = ''
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
export interface ToastOptions {
|
|
2
|
+
duration?: number // 显示时长
|
|
3
|
+
type?: 'info' | 'success' | 'warning' | 'error'
|
|
4
|
+
position?: 'top' | 'top-right' | 'top-left' | 'bottom' | 'bottom-right' | 'bottom-left'
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const DEFAULT_TOAST: Required<ToastOptions> = {
|
|
8
|
+
duration: 3000,
|
|
9
|
+
type: 'info',
|
|
10
|
+
position: 'top',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const TYPE_COLORS: Record<string, { bg: string; icon: string }> = {
|
|
14
|
+
info: { bg: '#38bdf8', icon: 'ℹ️' },
|
|
15
|
+
success: { bg: '#34d399', icon: '✅' },
|
|
16
|
+
warning: { bg: '#fbbf24', icon: '⚠️' },
|
|
17
|
+
error: { bg: '#f87171', icon: '❌' },
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const POSITION_STYLES: Record<string, string> = {
|
|
21
|
+
top: 'top:20px;left:50%;transform:translateX(-50%)',
|
|
22
|
+
'top-right': 'top:20px;right:20px',
|
|
23
|
+
'top-left': 'top:20px;left:20px',
|
|
24
|
+
bottom: 'bottom:20px;left:50%;transform:translateX(-50%)',
|
|
25
|
+
'bottom-right':'bottom:20px;right:20px',
|
|
26
|
+
'bottom-left':'bottom:20px;left:20px',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 显示一条 Toast 消息(类似 Element Plus Message)
|
|
31
|
+
*/
|
|
32
|
+
export function toast(message: string, options: ToastOptions = {}): () => void {
|
|
33
|
+
const opts = { ...DEFAULT_TOAST, ...options }
|
|
34
|
+
const color = TYPE_COLORS[opts.type]
|
|
35
|
+
const pos = POSITION_STYLES[opts.position]
|
|
36
|
+
|
|
37
|
+
const el = document.createElement('div')
|
|
38
|
+
el.style.cssText = `
|
|
39
|
+
position: fixed;
|
|
40
|
+
${pos};
|
|
41
|
+
background: ${color.bg};
|
|
42
|
+
color: #fff;
|
|
43
|
+
padding: 12px 20px;
|
|
44
|
+
border-radius: 8px;
|
|
45
|
+
font-size: 0.9rem;
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 8px;
|
|
49
|
+
z-index: 9999;
|
|
50
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
|
51
|
+
opacity: 0;
|
|
52
|
+
transform: translateY(-20px);
|
|
53
|
+
transition: all 0.3s ease;
|
|
54
|
+
pointer-events: none;
|
|
55
|
+
`
|
|
56
|
+
el.innerHTML = `<span>${color.icon}</span><span>${message}</span>`
|
|
57
|
+
document.body.appendChild(el)
|
|
58
|
+
|
|
59
|
+
requestAnimationFrame(() => {
|
|
60
|
+
el.style.opacity = '1'
|
|
61
|
+
el.style.transform = opts.position.startsWith('bottom')
|
|
62
|
+
? 'translateY(0)'
|
|
63
|
+
: 'translateY(0)'
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const timer = setTimeout(() => {
|
|
67
|
+
el.style.opacity = '0'
|
|
68
|
+
el.style.transform = opts.position.startsWith('bottom')
|
|
69
|
+
? 'translateY(20px)'
|
|
70
|
+
: 'translateY(-20px)'
|
|
71
|
+
setTimeout(() => el.remove(), 300)
|
|
72
|
+
}, opts.duration)
|
|
73
|
+
|
|
74
|
+
return () => {
|
|
75
|
+
clearTimeout(timer)
|
|
76
|
+
el.remove()
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 显示成功消息
|
|
82
|
+
*/
|
|
83
|
+
export function toastSuccess(message: string, options?: Omit<ToastOptions, 'type'>): () => void {
|
|
84
|
+
return toast(message, { ...options, type: 'success' })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 显示错误消息
|
|
89
|
+
*/
|
|
90
|
+
export function toastError(message: string, options?: Omit<ToastOptions, 'type'>): () => void {
|
|
91
|
+
return toast(message, { ...options, type: 'error' })
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 显示警告消息
|
|
96
|
+
*/
|
|
97
|
+
export function toastWarning(message: string, options?: Omit<ToastOptions, 'type'>): () => void {
|
|
98
|
+
return toast(message, { ...options, type: 'warning' })
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 显示通知(类似 Element Plus Notification,带标题)
|
|
103
|
+
*/
|
|
104
|
+
export function notify(
|
|
105
|
+
title: string,
|
|
106
|
+
message: string,
|
|
107
|
+
options: ToastOptions = {}
|
|
108
|
+
): () => void {
|
|
109
|
+
const opts = { ...DEFAULT_TOAST, ...options }
|
|
110
|
+
const color = TYPE_COLORS[opts.type]
|
|
111
|
+
const pos = POSITION_STYLES[opts.position]
|
|
112
|
+
|
|
113
|
+
const el = document.createElement('div')
|
|
114
|
+
el.style.cssText = `
|
|
115
|
+
position: fixed;
|
|
116
|
+
${pos};
|
|
117
|
+
background: #1e293b;
|
|
118
|
+
border-left: 4px solid ${color.bg};
|
|
119
|
+
color: #e2e8f0;
|
|
120
|
+
padding: 16px 20px;
|
|
121
|
+
border-radius: 8px;
|
|
122
|
+
font-size: 0.9rem;
|
|
123
|
+
z-index: 9999;
|
|
124
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
|
125
|
+
min-width: 280px;
|
|
126
|
+
opacity: 0;
|
|
127
|
+
transform: translateX(30px);
|
|
128
|
+
transition: all 0.3s ease;
|
|
129
|
+
`
|
|
130
|
+
el.innerHTML = `
|
|
131
|
+
<div style="font-weight:600;margin-bottom:4px;color:${color.bg};">${color.icon} ${title}</div>
|
|
132
|
+
<div style="color:#94a3b8;font-size:0.85rem;">${message}</div>
|
|
133
|
+
`
|
|
134
|
+
document.body.appendChild(el)
|
|
135
|
+
|
|
136
|
+
requestAnimationFrame(() => {
|
|
137
|
+
el.style.opacity = '1'
|
|
138
|
+
el.style.transform = 'translateX(0)'
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
const timer = setTimeout(() => {
|
|
142
|
+
el.style.opacity = '0'
|
|
143
|
+
el.style.transform = 'translateX(30px)'
|
|
144
|
+
setTimeout(() => el.remove(), 300)
|
|
145
|
+
}, opts.duration)
|
|
146
|
+
|
|
147
|
+
return () => {
|
|
148
|
+
clearTimeout(timer)
|
|
149
|
+
el.remove()
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Plugin } from 'vite'
|
|
2
|
+
|
|
3
|
+
export interface MkMotionOptions {
|
|
4
|
+
components?: string[]
|
|
5
|
+
motion?: boolean
|
|
6
|
+
theme?: 'dark' | 'light' | 'auto'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function mkMotion(options: MkMotionOptions = {}): Plugin {
|
|
10
|
+
const components = options.components || [
|
|
11
|
+
'Button', 'Card', 'Dialog', 'Input', 'Radio', 'Slider', 'Switch',
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
const cssImports: string[] = []
|
|
15
|
+
if (options.theme === 'dark') {
|
|
16
|
+
cssImports.push(`import '@luanlu/mk-motion/css'`)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
name: 'mk-motion',
|
|
21
|
+
enforce: 'pre',
|
|
22
|
+
transform(code, id) {
|
|
23
|
+
// Only process Vue SFC script blocks
|
|
24
|
+
if (!id.endsWith('.vue')) return code
|
|
25
|
+
|
|
26
|
+
// If component is used but not imported, add auto-import
|
|
27
|
+
const used = components.filter((name) => {
|
|
28
|
+
const tag = `Mk${name}`
|
|
29
|
+
return code.includes(`<${tag}`) || code.includes(`<${tag} `)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
if (used.length === 0) return code
|
|
33
|
+
|
|
34
|
+
const imports = used
|
|
35
|
+
.map((name) => `import { Mk${name} } from '@luanlu/mk-motion/vue'`)
|
|
36
|
+
.join('\n')
|
|
37
|
+
|
|
38
|
+
if (code.includes('import { ' + used.map((n) => `Mk${n}`).join(', ') + ' }')) {
|
|
39
|
+
return code
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return imports + '\n' + code
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/vue/button.ts
CHANGED
|
@@ -5,31 +5,50 @@ import type { ButtonOptions } from '../components/button/button.js'
|
|
|
5
5
|
export const MkButton = defineComponent({
|
|
6
6
|
name: 'MkButton',
|
|
7
7
|
props: {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
type: { type: String as () => ButtonOptions['type'], default: 'default' },
|
|
9
|
+
size: { type: String as () => ButtonOptions['size'], default: 'default' },
|
|
10
|
+
plain: { type: Boolean, default: false },
|
|
11
|
+
round: { type: Boolean, default: false },
|
|
12
|
+
circle: { type: Boolean, default: false },
|
|
13
|
+
disabled: { type: Boolean, default: false },
|
|
14
|
+
loading: { type: Boolean, default: false },
|
|
15
|
+
icon: { type: String, default: '' },
|
|
16
|
+
motion: { type: Object as () => ButtonOptions['motion'], default: undefined },
|
|
10
17
|
},
|
|
11
|
-
|
|
18
|
+
emits: ['click'],
|
|
12
19
|
setup(props, { emit, slots }) {
|
|
13
20
|
const container = ref<HTMLDivElement>()
|
|
14
21
|
let instance: ReturnType<typeof createButton> | null = null
|
|
15
22
|
|
|
23
|
+
const getText = () => {
|
|
24
|
+
const slotContent = slots.default?.()[0]?.children
|
|
25
|
+
return (typeof slotContent === 'string' ? slotContent : '') || ''
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
const create = () => {
|
|
17
29
|
if (!container.value) return
|
|
18
30
|
instance?.destroy()
|
|
19
31
|
instance = createButton(container.value, {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
type: props.type,
|
|
33
|
+
size: props.size,
|
|
34
|
+
plain: props.plain,
|
|
35
|
+
round: props.round,
|
|
36
|
+
circle: props.circle,
|
|
37
|
+
disabled: props.disabled,
|
|
38
|
+
loading: props.loading,
|
|
39
|
+
text: getText(),
|
|
40
|
+
icon: props.icon || undefined,
|
|
41
|
+
motion: props.motion,
|
|
42
|
+
onClick: (e) => emit('click', e),
|
|
23
43
|
})
|
|
24
44
|
}
|
|
25
45
|
|
|
26
46
|
onMounted(create)
|
|
27
|
-
|
|
28
|
-
watch(() => props.timeout, (v) => instance?.setTimeout(v))
|
|
47
|
+
watch(() => [props.type, props.size, props.plain, props.round, props.circle, props.icon, props.motion], create, { deep: true })
|
|
29
48
|
watch(() => props.loading, (v) => instance?.setLoading(v))
|
|
30
49
|
watch(() => props.disabled, (v) => instance?.setDisabled(v))
|
|
31
50
|
onUnmounted(() => instance?.destroy())
|
|
32
51
|
|
|
33
|
-
return () => h('div', { ref: container })
|
|
52
|
+
return () => h('div', { ref: container, style: 'display:inline-block' })
|
|
34
53
|
},
|
|
35
54
|
})
|
package/src/vue/card.ts
CHANGED
|
@@ -5,26 +5,46 @@ import type { CardOptions } from '../components/card/card.js'
|
|
|
5
5
|
export const MkCard = defineComponent({
|
|
6
6
|
name: 'MkCard',
|
|
7
7
|
props: {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
shadow: { type: String as () => CardOptions['shadow'], default: undefined },
|
|
9
|
+
loading: { type: Boolean, default: false },
|
|
10
|
+
image: { type: String, default: '' },
|
|
11
|
+
motion: { type: Object as () => CardOptions['motion'], default: undefined },
|
|
10
12
|
},
|
|
11
|
-
|
|
12
|
-
setup(props, { emit, slots }) {
|
|
13
|
+
setup(props, { slots }) {
|
|
13
14
|
const container = ref<HTMLDivElement>()
|
|
14
15
|
let instance: ReturnType<typeof createCard> | null = null
|
|
15
16
|
|
|
17
|
+
const getTitle = () => {
|
|
18
|
+
const slot = slots.header?.()[0]?.children
|
|
19
|
+
return typeof slot === 'string' ? slot : ''
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const getBody = () => {
|
|
23
|
+
const slot = slots.default?.()[0]?.children
|
|
24
|
+
return typeof slot === 'string' ? slot : ''
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const getFooter = () => {
|
|
28
|
+
const slot = slots.footer?.()[0]?.children
|
|
29
|
+
return typeof slot === 'string' ? slot : ''
|
|
30
|
+
}
|
|
31
|
+
|
|
16
32
|
const create = () => {
|
|
17
33
|
if (!container.value) return
|
|
18
34
|
instance?.destroy()
|
|
19
35
|
instance = createCard(container.value, {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
36
|
+
title: getTitle(),
|
|
37
|
+
body: getBody(),
|
|
38
|
+
footer: getFooter(),
|
|
39
|
+
image: props.image || undefined,
|
|
40
|
+
shadow: props.shadow,
|
|
41
|
+
loading: props.loading,
|
|
42
|
+
motion: props.motion,
|
|
23
43
|
})
|
|
24
44
|
}
|
|
25
45
|
|
|
26
46
|
onMounted(create)
|
|
27
|
-
|
|
47
|
+
watch(() => [props.shadow, props.image, props.loading, props.motion], create, { deep: true })
|
|
28
48
|
watch(() => props.loading, (v) => instance?.setLoading(v))
|
|
29
49
|
onUnmounted(() => instance?.destroy())
|
|
30
50
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
showLoading,
|
|
3
|
+
showFullscreenLoading,
|
|
4
|
+
} from '../../components/loading/loading.js'
|
|
5
|
+
import type { LoadingOptions } from '../../components/loading/loading.js'
|
|
6
|
+
|
|
7
|
+
export function useMkLoading() {
|
|
8
|
+
return {
|
|
9
|
+
showLoading: (options?: LoadingOptions) => showLoading(options),
|
|
10
|
+
showFullscreenLoading: (text?: string) => showFullscreenLoading(text),
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
message,
|
|
3
|
+
messageSuccess,
|
|
4
|
+
messageError,
|
|
5
|
+
messageWarning,
|
|
6
|
+
} from '../../components/message/message.js'
|
|
7
|
+
import type { MessageOptions } from '../../components/message/message.js'
|
|
8
|
+
|
|
9
|
+
export function useMkMessage() {
|
|
10
|
+
return {
|
|
11
|
+
message: (msg: string, options?: MessageOptions) => message(msg, options),
|
|
12
|
+
messageSuccess: (msg: string, options?: Omit<MessageOptions, 'type'>) => messageSuccess(msg, options),
|
|
13
|
+
messageError: (msg: string, options?: Omit<MessageOptions, 'type'>) => messageError(msg, options),
|
|
14
|
+
messageWarning: (msg: string, options?: Omit<MessageOptions, 'type'>) => messageWarning(msg, options),
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { onMounted, onUnmounted } from 'vue'
|
|
2
|
+
import type { Ref } from 'vue'
|
|
3
|
+
import { Animator } from '../../core/animator.js'
|
|
4
|
+
import type { MotionOptions } from '../../motion/component-motion.js'
|
|
5
|
+
|
|
6
|
+
export function useMkMotion(elRef: Ref<HTMLElement | undefined>, _options?: MotionOptions) {
|
|
7
|
+
let animator: Animator | null = null
|
|
8
|
+
onMounted(() => {
|
|
9
|
+
if (!elRef.value) return
|
|
10
|
+
animator = new Animator(elRef.value)
|
|
11
|
+
})
|
|
12
|
+
onUnmounted(() => {
|
|
13
|
+
animator?.reset()
|
|
14
|
+
})
|
|
15
|
+
return {
|
|
16
|
+
animate: (name: string, opts?: any) => animator?.animate(name, opts),
|
|
17
|
+
reset: () => animator?.reset(),
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
const isDark = ref(false)
|
|
4
|
+
|
|
5
|
+
export function useMkTheme() {
|
|
6
|
+
const setTheme = (dark: boolean) => {
|
|
7
|
+
isDark.value = dark
|
|
8
|
+
document.documentElement.setAttribute('data-mk-theme', dark ? 'dark' : 'light')
|
|
9
|
+
}
|
|
10
|
+
const toggle = () => setTheme(!isDark.value)
|
|
11
|
+
return { isDark, setTheme, toggle }
|
|
12
|
+
}
|