@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.
Files changed (131) hide show
  1. package/package.json +14 -2
  2. package/src/a11y/focus-trap.ts +64 -0
  3. package/src/a11y/keyboard.ts +43 -0
  4. package/src/components/alert/alert.css +111 -0
  5. package/src/components/alert/alert.ts +107 -0
  6. package/src/components/avatar/avatar.css +112 -0
  7. package/src/components/avatar/avatar.ts +175 -0
  8. package/src/components/breadcrumb/breadcrumb.css +31 -0
  9. package/src/components/breadcrumb/breadcrumb.ts +71 -0
  10. package/src/components/button/button.css +108 -0
  11. package/src/components/button/button.ts +140 -0
  12. package/src/components/card/card.css +52 -0
  13. package/src/components/card/card.ts +87 -0
  14. package/src/components/collapse/collapse.css +76 -0
  15. package/src/components/collapse/collapse.ts +168 -0
  16. package/src/components/dialog/dialog.css +78 -0
  17. package/src/components/dialog/dialog.ts +164 -0
  18. package/src/components/drawer/drawer.css +73 -0
  19. package/src/components/drawer/drawer.ts +131 -0
  20. package/src/components/empty/empty.css +36 -0
  21. package/src/components/empty/empty.ts +85 -0
  22. package/src/components/form/checkbox.css +56 -0
  23. package/src/components/form/checkbox.ts +119 -0
  24. package/src/components/form/radio.css +57 -0
  25. package/src/components/form/radio.ts +153 -0
  26. package/src/components/form/select.css +91 -0
  27. package/src/components/form/select.ts +174 -0
  28. package/src/components/form/slider.css +56 -0
  29. package/src/components/form/slider.ts +148 -0
  30. package/src/components/input/input.css +92 -0
  31. package/src/components/input/input.ts +162 -0
  32. package/src/components/layout/divider.css +32 -0
  33. package/src/components/layout/divider.ts +42 -0
  34. package/src/components/layout/row.css +64 -0
  35. package/src/components/layout/row.ts +57 -0
  36. package/src/components/layout/space.css +14 -0
  37. package/src/components/layout/space.ts +48 -0
  38. package/src/components/loading/loading.css +37 -0
  39. package/src/components/loading/loading.ts +46 -0
  40. package/src/components/menu/menu.css +121 -0
  41. package/src/components/menu/menu.ts +187 -0
  42. package/src/components/message/message.css +64 -0
  43. package/src/components/message/message.ts +96 -0
  44. package/src/components/popover/popover.css +73 -0
  45. package/src/components/popover/popover.ts +279 -0
  46. package/src/components/progress/progress.css +112 -0
  47. package/src/components/progress/progress.ts +171 -0
  48. package/src/components/steps/steps.css +127 -0
  49. package/src/components/steps/steps.ts +102 -0
  50. package/src/components/styles/components.css +28 -0
  51. package/src/components/styles/reset.css +24 -0
  52. package/src/components/styles/tokens.css +248 -0
  53. package/src/components/styles/variables.css +24 -0
  54. package/src/components/switch/switch.css +53 -0
  55. package/src/components/switch/switch.ts +103 -0
  56. package/src/components/table/table.css +192 -0
  57. package/src/components/table/table.ts +370 -0
  58. package/src/components/tabs/tabs.css +138 -0
  59. package/src/components/tabs/tabs.ts +211 -0
  60. package/src/components/tag/tag.css +123 -0
  61. package/src/components/tag/tag.ts +112 -0
  62. package/src/components/tooltip/tooltip.css +66 -0
  63. package/src/components/tooltip/tooltip.ts +185 -0
  64. package/src/core/animator.ts +124 -0
  65. package/src/core/timeline.ts +128 -0
  66. package/src/core/utils.ts +47 -0
  67. package/src/effects/glitch.ts +99 -0
  68. package/src/effects/particle.ts +134 -0
  69. package/src/effects/text-split.ts +95 -0
  70. package/src/effects/wave-text.ts +88 -0
  71. package/src/gesture/draggable.ts +130 -0
  72. package/src/gesture/spring.ts +152 -0
  73. package/src/index.ts +162 -0
  74. package/src/interactive/coverflow.ts +100 -0
  75. package/src/interactive/cursor-trail.ts +113 -0
  76. package/src/interactive/flip-card.ts +114 -0
  77. package/src/interactive/magnetic.ts +121 -0
  78. package/src/micro/hover-lift.ts +94 -0
  79. package/src/micro/ripple.ts +130 -0
  80. package/src/motion/component-motion.ts +177 -0
  81. package/src/nuxt/module.ts +46 -0
  82. package/src/presets/index.ts +69 -0
  83. package/src/scroll/scroll-trigger.ts +104 -0
  84. package/src/styles/animations.css +135 -0
  85. package/src/styles/element-plus.css +174 -0
  86. package/src/text/count-up.ts +108 -0
  87. package/src/text/typewriter.ts +109 -0
  88. package/src/theme/dark.css +19 -0
  89. package/src/theme/light.css +19 -0
  90. package/src/theme/theme.ts +65 -0
  91. package/src/transitions/blur-reveal.ts +92 -0
  92. package/src/transitions/collapse.ts +112 -0
  93. package/src/transitions/lazy-image.ts +87 -0
  94. package/src/transitions/list.ts +75 -0
  95. package/src/transitions/loading.ts +95 -0
  96. package/src/transitions/parallax.ts +60 -0
  97. package/src/transitions/shimmer.ts +105 -0
  98. package/src/transitions/toast.ts +151 -0
  99. package/src/types.d.ts +4 -0
  100. package/src/vite/plugin.ts +45 -0
  101. package/src/vue/button.ts +28 -9
  102. package/src/vue/card.ts +28 -8
  103. package/src/vue/composables/index.ts +4 -0
  104. package/src/vue/composables/useLoading.ts +12 -0
  105. package/src/vue/composables/useMessage.ts +16 -0
  106. package/src/vue/composables/useMotion.ts +19 -0
  107. package/src/vue/composables/useTheme.ts +12 -0
  108. package/src/vue/dialog.ts +69 -17
  109. package/src/vue/index.ts +4 -21
  110. package/src/vue/input.ts +35 -11
  111. package/src/vue/slider.ts +22 -4
  112. package/src/vue/switch.ts +16 -9
  113. package/src/vue/alert.ts +0 -32
  114. package/src/vue/avatar.ts +0 -34
  115. package/src/vue/breadcrumb.ts +0 -32
  116. package/src/vue/checkbox.ts +0 -32
  117. package/src/vue/collapse.ts +0 -33
  118. package/src/vue/divider.ts +0 -32
  119. package/src/vue/drawer.ts +0 -33
  120. package/src/vue/empty.ts +0 -33
  121. package/src/vue/menu.ts +0 -33
  122. package/src/vue/popover.ts +0 -34
  123. package/src/vue/progress.ts +0 -33
  124. package/src/vue/row.ts +0 -32
  125. package/src/vue/select.ts +0 -33
  126. package/src/vue/space.ts +0 -32
  127. package/src/vue/steps.ts +0 -33
  128. package/src/vue/table.ts +0 -33
  129. package/src/vue/tabs.ts +0 -33
  130. package/src/vue/tag.ts +0 -33
  131. 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,4 @@
1
+ declare module '*.css' {
2
+ const content: string
3
+ export default content
4
+ }
@@ -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,4 @@
1
+ export { useMkTheme } from './useTheme.js'
2
+ export { useMkMotion } from './useMotion.js'
3
+ export { useMkLoading } from './useLoading.js'
4
+ export { useMkMessage } from './useMessage.js'
@@ -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
+ }