@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,192 @@
1
+ .mk-table-wrapper {
2
+ width: 100%;
3
+ border: 1px solid var(--mk-border);
4
+ border-radius: var(--mk-radius);
5
+ overflow: hidden;
6
+ background: var(--mk-surface);
7
+ }
8
+
9
+ .mk-table {
10
+ width: 100%;
11
+ border-collapse: collapse;
12
+ font-size: 13px;
13
+ }
14
+
15
+ .mk-table thead {
16
+ background: rgba(255,255,255,0.03);
17
+ }
18
+
19
+ .mk-table th {
20
+ padding: 10px 16px;
21
+ text-align: left;
22
+ font-weight: 500;
23
+ color: var(--mk-text-secondary);
24
+ border-bottom: 1px solid var(--mk-border);
25
+ font-size: 12px;
26
+ text-transform: uppercase;
27
+ letter-spacing: 0.05em;
28
+ white-space: nowrap;
29
+ cursor: default;
30
+ user-select: none;
31
+ }
32
+
33
+ .mk-table th.is-sortable {
34
+ cursor: pointer;
35
+ }
36
+
37
+ .mk-table th.is-sortable:hover {
38
+ color: var(--mk-text);
39
+ background: rgba(255,255,255,0.03);
40
+ }
41
+
42
+ .mk-table__sort-icon {
43
+ margin-left: 4px;
44
+ font-size: 10px;
45
+ opacity: 0.4;
46
+ display: inline-block;
47
+ width: 12px;
48
+ text-align: center;
49
+ }
50
+
51
+ .mk-table__sort-icon.is-active {
52
+ opacity: 1;
53
+ color: var(--mk-primary);
54
+ }
55
+
56
+ .mk-table td {
57
+ padding: 10px 16px;
58
+ color: var(--mk-text);
59
+ border-bottom: 1px solid var(--mk-border);
60
+ vertical-align: middle;
61
+ }
62
+
63
+ .mk-table tbody tr:last-child td {
64
+ border-bottom: none;
65
+ }
66
+
67
+ .mk-table tbody tr:hover {
68
+ background: rgba(99,102,241,0.03);
69
+ }
70
+
71
+ .mk-table tbody tr.is-editing td {
72
+ background: rgba(99,102,241,0.05);
73
+ }
74
+
75
+ /* Inline edit input */
76
+ .mk-table__edit-input {
77
+ width: 100%;
78
+ padding: 6px 10px;
79
+ font-size: 13px;
80
+ color: var(--mk-text);
81
+ background: var(--mk-bg);
82
+ border: 1px solid var(--mk-border);
83
+ border-radius: 6px;
84
+ outline: none;
85
+ transition: var(--mk-transition);
86
+ box-sizing: border-box;
87
+ }
88
+
89
+ .mk-table__edit-input:focus {
90
+ border-color: var(--mk-primary);
91
+ }
92
+
93
+ /* Cell actions */
94
+ .mk-table__actions {
95
+ display: flex;
96
+ gap: 8px;
97
+ }
98
+
99
+ .mk-table__action-btn {
100
+ font-size: 12px;
101
+ color: var(--mk-primary);
102
+ cursor: pointer;
103
+ padding: 2px 6px;
104
+ border-radius: 4px;
105
+ transition: var(--mk-transition);
106
+ }
107
+
108
+ .mk-table__action-btn:hover {
109
+ background: rgba(99,102,241,0.1);
110
+ }
111
+
112
+ .mk-table__action-btn.is-danger {
113
+ color: var(--mk-danger);
114
+ }
115
+
116
+ .mk-table__action-btn.is-danger:hover {
117
+ background: rgba(239,68,68,0.1);
118
+ }
119
+
120
+ /* Empty state */
121
+ .mk-table__empty {
122
+ padding: 48px 24px;
123
+ text-align: center;
124
+ color: var(--mk-text-tertiary);
125
+ font-size: 14px;
126
+ }
127
+
128
+ /* Pagination */
129
+ .mk-pagination {
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: center;
133
+ gap: 4px;
134
+ padding: 12px 16px;
135
+ border-top: 1px solid var(--mk-border);
136
+ }
137
+
138
+ .mk-pagination__btn {
139
+ min-width: 32px;
140
+ height: 32px;
141
+ padding: 0 8px;
142
+ display: flex;
143
+ align-items: center;
144
+ justify-content: center;
145
+ font-size: 13px;
146
+ color: var(--mk-text-secondary);
147
+ background: transparent;
148
+ border: 1px solid transparent;
149
+ border-radius: 6px;
150
+ cursor: pointer;
151
+ transition: var(--mk-transition);
152
+ }
153
+
154
+ .mk-pagination__btn:hover:not(:disabled) {
155
+ background: rgba(255,255,255,0.05);
156
+ color: var(--mk-text);
157
+ }
158
+
159
+ .mk-pagination__btn.is-active {
160
+ background: var(--mk-primary);
161
+ color: #fff;
162
+ border-color: var(--mk-primary);
163
+ }
164
+
165
+ .mk-pagination__btn:disabled {
166
+ opacity: 0.3;
167
+ cursor: not-allowed;
168
+ }
169
+
170
+ .mk-pagination__total {
171
+ font-size: 12px;
172
+ color: var(--mk-text-tertiary);
173
+ margin-right: auto;
174
+ }
175
+
176
+ .mk-pagination__size {
177
+ margin-left: auto;
178
+ font-size: 12px;
179
+ color: var(--mk-text-secondary);
180
+ }
181
+
182
+ .mk-pagination__size select {
183
+ margin-left: 6px;
184
+ padding: 4px 8px;
185
+ font-size: 12px;
186
+ color: var(--mk-text);
187
+ background: var(--mk-bg);
188
+ border: 1px solid var(--mk-border);
189
+ border-radius: 4px;
190
+ cursor: pointer;
191
+ outline: none;
192
+ }
@@ -0,0 +1,370 @@
1
+ import './table.css'
2
+
3
+ export interface TableColumn {
4
+ key: string
5
+ title: string
6
+ width?: string
7
+ sortable?: boolean
8
+ editable?: boolean
9
+ render?: (value: any, row: Record<string, any>, index: number) => HTMLElement | string
10
+ }
11
+
12
+ export interface TableOptions {
13
+ columns: TableColumn[]
14
+ data: Record<string, any>[]
15
+ pageSize?: number
16
+ onEdit?: (row: Record<string, any>, rowIndex: number) => void
17
+ onDelete?: (row: Record<string, any>, rowIndex: number) => void
18
+ onRowClick?: (row: Record<string, any>, rowIndex: number) => void
19
+ }
20
+
21
+ export class MkTable {
22
+ el: HTMLDivElement
23
+ private table: HTMLTableElement
24
+ private tbody: HTMLTableSectionElement
25
+ private columns: TableColumn[]
26
+ private data: Record<string, any>[]
27
+ private pageSize: number
28
+ private currentPage = 1
29
+ private sortState: { key: string; order: 'asc' | 'desc' } | null = null
30
+ private editingRow: number | null = null
31
+ private editCache: Record<string, any> | null = null
32
+ private filteredData: Record<string, any>[]
33
+
34
+ private callbacks: Pick<TableOptions, 'onEdit' | 'onDelete' | 'onRowClick'>
35
+
36
+ constructor(container: HTMLElement | string, options: TableOptions) {
37
+ const parent =
38
+ typeof container === 'string'
39
+ ? document.querySelector(container)!
40
+ : container
41
+
42
+ this.columns = options.columns
43
+ this.data = options.data
44
+ this.pageSize = options.pageSize || 10
45
+ this.filteredData = [...this.data]
46
+ this.callbacks = options
47
+
48
+ this.el = document.createElement('div')
49
+ this.el.className = 'mk-table-wrapper'
50
+
51
+ this.table = document.createElement('table')
52
+ this.table.className = 'mk-table'
53
+ this.table.setAttribute('role', 'table')
54
+
55
+ const thead = document.createElement('thead')
56
+ thead.setAttribute('role', 'rowgroup')
57
+ const headerRow = document.createElement('tr')
58
+ headerRow.setAttribute('role', 'row')
59
+ this.columns.forEach((col) => {
60
+ const th = document.createElement('th')
61
+ th.setAttribute('role', 'columnheader')
62
+ th.textContent = col.title
63
+ if (col.width) th.style.width = col.width
64
+ if (col.sortable) {
65
+ th.classList.add('is-sortable')
66
+ th.setAttribute('aria-sort', 'none')
67
+ const icon = document.createElement('span')
68
+ icon.className = 'mk-table__sort-icon'
69
+ icon.textContent = '⇅'
70
+ th.appendChild(icon)
71
+ th.addEventListener('click', () => this.handleSort(col.key))
72
+ }
73
+ headerRow.appendChild(th)
74
+ })
75
+ thead.appendChild(headerRow)
76
+ this.table.appendChild(thead)
77
+
78
+ this.tbody = document.createElement('tbody')
79
+ this.tbody.setAttribute('role', 'rowgroup')
80
+ this.table.appendChild(this.tbody)
81
+ this.el.appendChild(this.table)
82
+
83
+ parent.appendChild(this.el)
84
+ this.render()
85
+ }
86
+
87
+ private getDisplayData(): Record<string, any>[] {
88
+ if (this.sortState) {
89
+ const sorted = [...this.filteredData].sort((a, b) => {
90
+ const av = a[this.sortState!.key]
91
+ const bv = b[this.sortState!.key]
92
+ if (av < bv) return this.sortState!.order === 'asc' ? -1 : 1
93
+ if (av > bv) return this.sortState!.order === 'asc' ? 1 : -1
94
+ return 0
95
+ })
96
+ return sorted
97
+ }
98
+ return this.filteredData
99
+ }
100
+
101
+ private handleSort(key: string): void {
102
+ if (!this.sortState || this.sortState.key !== key) {
103
+ this.sortState = { key, order: 'asc' }
104
+ } else if (this.sortState.order === 'asc') {
105
+ this.sortState.order = 'desc'
106
+ } else {
107
+ this.sortState = null
108
+ }
109
+ this.render()
110
+ }
111
+
112
+ render(): void {
113
+ this.tbody.innerHTML = ''
114
+ const displayData = this.getDisplayData()
115
+ const start = (this.currentPage - 1) * this.pageSize
116
+ const pageData = displayData.slice(start, start + this.pageSize)
117
+
118
+ if (pageData.length === 0) {
119
+ const row = document.createElement('tr')
120
+ row.setAttribute('role', 'row')
121
+ const cell = document.createElement('td')
122
+ cell.setAttribute('role', 'cell')
123
+ cell.colSpan = this.columns.length
124
+ cell.className = 'mk-table__empty'
125
+ cell.textContent = '暂无数据'
126
+ row.appendChild(cell)
127
+ this.tbody.appendChild(row)
128
+ } else {
129
+ pageData.forEach((rowData, pageIndex) => {
130
+ const rowIndex = start + pageIndex
131
+ const tr = document.createElement('tr')
132
+ tr.setAttribute('role', 'row')
133
+ if (this.editingRow === rowIndex) tr.classList.add('is-editing')
134
+ tr.addEventListener('click', () => {
135
+ this.callbacks.onRowClick?.(rowData, rowIndex)
136
+ })
137
+
138
+ this.columns.forEach((col) => {
139
+ const td = document.createElement('td')
140
+ td.setAttribute('role', 'cell')
141
+ if (this.editingRow === rowIndex && col.editable && col.key !== 'actions') {
142
+ const input = document.createElement('input')
143
+ input.className = 'mk-table__edit-input'
144
+ input.value = String(this.editCache![col.key] ?? rowData[col.key] ?? '')
145
+ input.addEventListener('input', (e) => {
146
+ this.editCache![col.key] = (e.target as HTMLInputElement).value
147
+ })
148
+ input.addEventListener('keydown', (e) => {
149
+ if (e.key === 'Enter') this.saveEdit()
150
+ if (e.key === 'Escape') this.cancelEdit()
151
+ })
152
+ td.appendChild(input)
153
+ } else if (col.render) {
154
+ const rendered = col.render(rowData[col.key], rowData, rowIndex)
155
+ if (rendered instanceof HTMLElement) {
156
+ td.appendChild(rendered)
157
+ } else if (typeof rendered === 'string' && rendered.includes('<')) {
158
+ td.innerHTML = rendered
159
+ } else {
160
+ td.textContent = String(rendered)
161
+ }
162
+ } else {
163
+ td.textContent = rowData[col.key] !== undefined ? String(rowData[col.key]) : ''
164
+ }
165
+ tr.appendChild(td)
166
+ })
167
+ this.tbody.appendChild(tr)
168
+ })
169
+ }
170
+
171
+ this.renderPagination(displayData.length)
172
+ this.updateSortIcons()
173
+ }
174
+
175
+ private updateSortIcons(): void {
176
+ const headers = this.table.querySelectorAll('th')
177
+ headers.forEach((th) => {
178
+ const icon = th.querySelector('.mk-table__sort-icon')
179
+ if (icon) {
180
+ icon.classList.remove('is-active')
181
+ icon.textContent = '⇅'
182
+ th.setAttribute('aria-sort', 'none')
183
+ }
184
+ })
185
+ if (this.sortState) {
186
+ const colIndex = this.columns.findIndex((c) => c.key === this.sortState!.key)
187
+ if (colIndex >= 0) {
188
+ const th = headers[colIndex]
189
+ const icon = th?.querySelector('.mk-table__sort-icon')
190
+ if (icon) {
191
+ icon.classList.add('is-active')
192
+ icon.textContent = this.sortState.order === 'asc' ? '↑' : '↓'
193
+ }
194
+ if (th) {
195
+ th.setAttribute('aria-sort', this.sortState.order === 'asc' ? 'ascending' : 'descending')
196
+ }
197
+ }
198
+ }
199
+ }
200
+
201
+ private renderPagination(total: number): void {
202
+ const existing = this.el.querySelector('.mk-pagination')
203
+ existing?.remove()
204
+
205
+ const totalPages = Math.max(1, Math.ceil(total / this.pageSize))
206
+ const pagination = document.createElement('div')
207
+ pagination.className = 'mk-pagination'
208
+
209
+ const totalEl = document.createElement('span')
210
+ totalEl.className = 'mk-pagination__total'
211
+ totalEl.textContent = `共 ${total} 条`
212
+ pagination.appendChild(totalEl)
213
+
214
+ const prev = document.createElement('button')
215
+ prev.className = 'mk-pagination__btn'
216
+ prev.textContent = '‹'
217
+ prev.disabled = this.currentPage <= 1
218
+ prev.addEventListener('click', () => {
219
+ if (this.currentPage > 1) {
220
+ this.currentPage--
221
+ this.render()
222
+ }
223
+ })
224
+ pagination.appendChild(prev)
225
+
226
+ const maxVisible = 5
227
+ let startPage = Math.max(1, this.currentPage - Math.floor(maxVisible / 2))
228
+ let endPage = Math.min(totalPages, startPage + maxVisible - 1)
229
+ if (endPage - startPage + 1 < maxVisible) {
230
+ startPage = Math.max(1, endPage - maxVisible + 1)
231
+ }
232
+
233
+ if (startPage > 1) {
234
+ pagination.appendChild(this.createPageBtn(1))
235
+ if (startPage > 2) {
236
+ const dots = document.createElement('span')
237
+ dots.className = 'mk-pagination__btn'
238
+ dots.textContent = '...'
239
+ dots.style.cursor = 'default'
240
+ pagination.appendChild(dots)
241
+ }
242
+ }
243
+
244
+ for (let i = startPage; i <= endPage; i++) {
245
+ pagination.appendChild(this.createPageBtn(i))
246
+ }
247
+
248
+ if (endPage < totalPages) {
249
+ if (endPage < totalPages - 1) {
250
+ const dots = document.createElement('span')
251
+ dots.className = 'mk-pagination__btn'
252
+ dots.textContent = '...'
253
+ dots.style.cursor = 'default'
254
+ pagination.appendChild(dots)
255
+ }
256
+ pagination.appendChild(this.createPageBtn(totalPages))
257
+ }
258
+
259
+ const next = document.createElement('button')
260
+ next.className = 'mk-pagination__btn'
261
+ next.textContent = '›'
262
+ next.disabled = this.currentPage >= totalPages
263
+ next.addEventListener('click', () => {
264
+ if (this.currentPage < totalPages) {
265
+ this.currentPage++
266
+ this.render()
267
+ }
268
+ })
269
+ pagination.appendChild(next)
270
+
271
+ const sizeEl = document.createElement('span')
272
+ sizeEl.className = 'mk-pagination__size'
273
+ sizeEl.innerHTML = `
274
+ 每页
275
+ <select>
276
+ <option value="10" ${this.pageSize === 10 ? 'selected' : ''}>10</option>
277
+ <option value="20" ${this.pageSize === 20 ? 'selected' : ''}>20</option>
278
+ <option value="50" ${this.pageSize === 50 ? 'selected' : ''}>50</option>
279
+ </select>
280
+
281
+ `
282
+ sizeEl.querySelector('select')!.addEventListener('change', (e) => {
283
+ this.pageSize = Number((e.target as HTMLSelectElement).value)
284
+ this.currentPage = 1
285
+ this.render()
286
+ })
287
+ pagination.appendChild(sizeEl)
288
+
289
+ this.el.appendChild(pagination)
290
+ }
291
+
292
+ private createPageBtn(page: number): HTMLButtonElement {
293
+ const btn = document.createElement('button')
294
+ btn.className = 'mk-pagination__btn'
295
+ btn.textContent = String(page)
296
+ if (page === this.currentPage) btn.classList.add('is-active')
297
+ btn.addEventListener('click', () => {
298
+ this.currentPage = page
299
+ this.render()
300
+ })
301
+ return btn
302
+ }
303
+
304
+ setData(data: Record<string, any>[]): void {
305
+ this.data = data
306
+ this.filteredData = [...data]
307
+ this.currentPage = 1
308
+ this.render()
309
+ }
310
+
311
+ filter(predicate: (row: Record<string, any>) => boolean): void {
312
+ this.filteredData = this.data.filter(predicate)
313
+ this.currentPage = 1
314
+ this.render()
315
+ }
316
+
317
+ clearFilter(): void {
318
+ this.filteredData = [...this.data]
319
+ this.currentPage = 1
320
+ this.render()
321
+ }
322
+
323
+ startEdit(rowIndex: number): void {
324
+ this.editingRow = rowIndex
325
+ this.editCache = { ...this.filteredData[rowIndex] }
326
+ this.render()
327
+ const input = this.tbody.querySelector('.mk-table__edit-input') as HTMLInputElement
328
+ input?.focus()
329
+ }
330
+
331
+ saveEdit(): void {
332
+ if (this.editingRow !== null && this.editCache) {
333
+ const rowData = this.editCache
334
+ this.filteredData[this.editingRow] = rowData
335
+ const origIndex = this.data.findIndex(
336
+ (r, i) => i === this.editingRow || JSON.stringify(r) === JSON.stringify(rowData)
337
+ )
338
+ if (origIndex >= 0) this.data[origIndex] = rowData
339
+ this.callbacks.onEdit?.(rowData, this.editingRow)
340
+ }
341
+ this.editingRow = null
342
+ this.editCache = null
343
+ this.render()
344
+ }
345
+
346
+ cancelEdit(): void {
347
+ this.editingRow = null
348
+ this.editCache = null
349
+ this.render()
350
+ }
351
+
352
+ deleteRow(rowIndex: number): void {
353
+ const row = this.filteredData[rowIndex]
354
+ this.data = this.data.filter((r) => r !== row)
355
+ this.filteredData = this.filteredData.filter((r) => r !== row)
356
+ this.callbacks.onDelete?.(row, rowIndex)
357
+ if (this.filteredData.length <= (this.currentPage - 1) * this.pageSize && this.currentPage > 1) {
358
+ this.currentPage--
359
+ }
360
+ this.render()
361
+ }
362
+
363
+ destroy(): void {
364
+ this.el.remove()
365
+ }
366
+ }
367
+
368
+ export function createTable(container: HTMLElement | string, options: TableOptions): MkTable {
369
+ return new MkTable(container, options)
370
+ }
@@ -0,0 +1,138 @@
1
+ .mk-tabs {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--mk-space-3);
5
+ }
6
+
7
+ .mk-tabs__header {
8
+ position: relative;
9
+ display: flex;
10
+ align-items: center;
11
+ gap: var(--mk-space-1);
12
+ }
13
+
14
+ .mk-tabs__item {
15
+ position: relative;
16
+ display: inline-flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ padding: 0 var(--mk-space-3);
20
+ height: 36px;
21
+ font-size: var(--mk-text-sm);
22
+ font-weight: var(--mk-font-medium);
23
+ color: var(--mk-text-secondary);
24
+ background: transparent;
25
+ border: none;
26
+ border-radius: var(--mk-radius);
27
+ cursor: pointer;
28
+ outline: none;
29
+ white-space: nowrap;
30
+ transition: var(--mk-transition-colors);
31
+ }
32
+
33
+ .mk-tabs__item:hover:not(.is-disabled) {
34
+ color: var(--mk-text);
35
+ background: var(--mk-surface-hover);
36
+ }
37
+
38
+ .mk-tabs__item.is-active {
39
+ color: var(--mk-text);
40
+ }
41
+
42
+ .mk-tabs__item.is-disabled {
43
+ opacity: 0.4;
44
+ cursor: not-allowed;
45
+ }
46
+
47
+ /* Line type */
48
+ .mk-tabs--line .mk-tabs__header {
49
+ border-bottom: 1px solid var(--mk-border);
50
+ gap: 0;
51
+ }
52
+
53
+ .mk-tabs--line .mk-tabs__item {
54
+ border-radius: 0;
55
+ margin-bottom: -1px;
56
+ }
57
+
58
+ .mk-tabs--line .mk-tabs__item.is-active {
59
+ color: var(--mk-primary);
60
+ }
61
+
62
+ .mk-tabs__indicator {
63
+ position: absolute;
64
+ bottom: 0;
65
+ height: 2px;
66
+ background: var(--mk-primary);
67
+ border-radius: 1px;
68
+ transition: left var(--mk-duration-normal) var(--mk-ease-out-expo),
69
+ width var(--mk-duration-normal) var(--mk-ease-out-expo);
70
+ }
71
+
72
+ /* Card type */
73
+ .mk-tabs--card .mk-tabs__header {
74
+ gap: 0;
75
+ background: var(--mk-surface);
76
+ border: 1px solid var(--mk-border);
77
+ border-radius: var(--mk-radius);
78
+ padding: 3px;
79
+ }
80
+
81
+ .mk-tabs--card .mk-tabs__item {
82
+ flex: 1;
83
+ border-radius: var(--mk-radius-sm);
84
+ }
85
+
86
+ .mk-tabs--card .mk-tabs__item.is-active {
87
+ background: var(--mk-surface-raised);
88
+ box-shadow: var(--mk-shadow-sm);
89
+ }
90
+
91
+ /* Pill type */
92
+ .mk-tabs--pill .mk-tabs__header {
93
+ gap: var(--mk-space-1);
94
+ background: var(--mk-surface);
95
+ border: 1px solid var(--mk-border);
96
+ border-radius: var(--mk-radius-full);
97
+ padding: 3px;
98
+ }
99
+
100
+ .mk-tabs--pill .mk-tabs__item {
101
+ border-radius: var(--mk-radius-full);
102
+ }
103
+
104
+ .mk-tabs--pill .mk-tabs__item.is-active {
105
+ background: var(--mk-primary);
106
+ color: #fff;
107
+ }
108
+
109
+ .mk-tabs--pill .mk-tabs__item:hover:not(.is-disabled):not(.is-active) {
110
+ background: var(--mk-surface-hover);
111
+ }
112
+
113
+ /* Content */
114
+ .mk-tabs__content {
115
+ position: relative;
116
+ min-height: 0;
117
+ }
118
+
119
+ .mk-tabs__panel {
120
+ display: none;
121
+ font-size: var(--mk-text-sm);
122
+ color: var(--mk-text-secondary);
123
+ line-height: var(--mk-leading-normal);
124
+ opacity: 0;
125
+ transform: translateY(4px);
126
+ transition: opacity var(--mk-duration-normal) var(--mk-ease-default),
127
+ transform var(--mk-duration-normal) var(--mk-ease-out);
128
+ }
129
+
130
+ .mk-tabs__panel.is-active {
131
+ opacity: 1;
132
+ transform: translateY(0);
133
+ }
134
+
135
+ .mk-tabs__panel.is-entering {
136
+ opacity: 0;
137
+ transform: translateY(4px);
138
+ }