@luanlu/mk-motion 1.2.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 (98) hide show
  1. package/package.json +2 -4
  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/presets/index.ts +69 -0
  82. package/src/scroll/scroll-trigger.ts +104 -0
  83. package/src/styles/animations.css +135 -0
  84. package/src/styles/element-plus.css +174 -0
  85. package/src/text/count-up.ts +108 -0
  86. package/src/text/typewriter.ts +109 -0
  87. package/src/theme/dark.css +19 -0
  88. package/src/theme/light.css +19 -0
  89. package/src/theme/theme.ts +65 -0
  90. package/src/transitions/blur-reveal.ts +92 -0
  91. package/src/transitions/collapse.ts +112 -0
  92. package/src/transitions/lazy-image.ts +87 -0
  93. package/src/transitions/list.ts +75 -0
  94. package/src/transitions/loading.ts +95 -0
  95. package/src/transitions/parallax.ts +60 -0
  96. package/src/transitions/shimmer.ts +105 -0
  97. package/src/transitions/toast.ts +151 -0
  98. package/src/types.d.ts +4 -0
@@ -0,0 +1,148 @@
1
+ import './slider.css'
2
+ import { onKey, Keys } from '../../a11y/keyboard.ts'
3
+
4
+ export interface SliderOptions {
5
+ min?: number
6
+ max?: number
7
+ step?: number
8
+ value?: number
9
+ showValue?: boolean
10
+ onChange?: (value: number) => void
11
+ }
12
+
13
+ export class MkSlider {
14
+ el: HTMLDivElement
15
+ private track: HTMLDivElement
16
+ private fill: HTMLDivElement
17
+ private thumb: HTMLDivElement
18
+ private valueEl?: HTMLDivElement
19
+ private options: Required<Omit<SliderOptions, 'onChange'>> & Pick<SliderOptions, 'onChange'>
20
+ private _value: number
21
+ private dragging = false
22
+ private _cleanupKey?: () => void
23
+
24
+ constructor(container: HTMLElement | string, options: SliderOptions = {}) {
25
+ const parent =
26
+ typeof container === 'string'
27
+ ? document.querySelector(container)!
28
+ : container
29
+
30
+ this.options = {
31
+ min: 0,
32
+ max: 100,
33
+ step: 1,
34
+ value: 0,
35
+ showValue: true,
36
+ ...options,
37
+ }
38
+ this._value = this.options.value
39
+
40
+ this.el = document.createElement('div')
41
+ this.el.className = 'mk-slider'
42
+
43
+ this.track = document.createElement('div')
44
+ this.track.className = 'mk-slider__track'
45
+
46
+ this.fill = document.createElement('div')
47
+ this.fill.className = 'mk-slider__fill'
48
+ this.track.appendChild(this.fill)
49
+
50
+ this.thumb = document.createElement('div')
51
+ this.thumb.className = 'mk-slider__thumb'
52
+ this.thumb.setAttribute('role', 'slider')
53
+ this.thumb.setAttribute('tabindex', '0')
54
+ this.track.appendChild(this.thumb)
55
+
56
+ this.el.appendChild(this.track)
57
+
58
+ if (this.options.showValue) {
59
+ this.valueEl = document.createElement('div')
60
+ this.valueEl.className = 'mk-slider__value'
61
+ this.el.appendChild(this.valueEl)
62
+ }
63
+
64
+ this.updateUI()
65
+
66
+ this._cleanupKey = onKey(this.thumb, [
67
+ { key: Keys.ArrowLeft, handler: () => this.adjustValue(-this.options.step) },
68
+ { key: Keys.ArrowRight, handler: () => this.adjustValue(this.options.step) },
69
+ { key: Keys.Home, handler: () => { this.value = this.options.min } },
70
+ { key: Keys.End, handler: () => { this.value = this.options.max } },
71
+ ])
72
+
73
+ this.track.addEventListener('mousedown', (e) => this.onStart(e.clientX))
74
+ this.thumb.addEventListener('mousedown', (e) => {
75
+ e.stopPropagation()
76
+ this.onStart(e.clientX)
77
+ })
78
+
79
+ document.addEventListener('mousemove', (e) => this.onMove(e.clientX))
80
+ document.addEventListener('mouseup', () => this.onEnd())
81
+
82
+ // Touch
83
+ this.track.addEventListener('touchstart', (e) => this.onStart(e.touches[0].clientX), { passive: true })
84
+ document.addEventListener('touchmove', (e) => this.onMove(e.touches[0].clientX), { passive: true })
85
+ document.addEventListener('touchend', () => this.onEnd())
86
+
87
+ parent.appendChild(this.el)
88
+ }
89
+
90
+ private onStart(clientX: number): void {
91
+ this.dragging = true
92
+ this.updateFromPosition(clientX)
93
+ }
94
+
95
+ private onMove(clientX: number): void {
96
+ if (!this.dragging) return
97
+ this.updateFromPosition(clientX)
98
+ }
99
+
100
+ private onEnd(): void {
101
+ this.dragging = false
102
+ }
103
+
104
+ private updateFromPosition(clientX: number): void {
105
+ const rect = this.track.getBoundingClientRect()
106
+ const percent = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width))
107
+ const raw = this.options.min + percent * (this.options.max - this.options.min)
108
+ const stepped = Math.round(raw / this.options.step) * this.options.step
109
+ this.value = Math.max(this.options.min, Math.min(this.options.max, stepped))
110
+ }
111
+
112
+ get value(): number {
113
+ return this._value
114
+ }
115
+
116
+ set value(v: number) {
117
+ if (this._value !== v) {
118
+ this._value = v
119
+ this.updateUI()
120
+ this.options.onChange?.(v)
121
+ }
122
+ }
123
+
124
+ private updateUI(): void {
125
+ const percent = (this._value - this.options.min) / (this.options.max - this.options.min)
126
+ this.fill.style.width = `${percent * 100}%`
127
+ this.thumb.style.left = `${percent * 100}%`
128
+ if (this.valueEl) {
129
+ this.valueEl.textContent = String(this._value)
130
+ }
131
+ this.updateAria()
132
+ }
133
+
134
+ private updateAria(): void {
135
+ this.thumb.setAttribute('aria-valuenow', String(this._value))
136
+ this.thumb.setAttribute('aria-valuemin', String(this.options.min))
137
+ this.thumb.setAttribute('aria-valuemax', String(this.options.max))
138
+ }
139
+
140
+ private adjustValue(delta: number): void {
141
+ this.value = Math.max(this.options.min, Math.min(this.options.max, this._value + delta))
142
+ }
143
+
144
+ destroy(): void {
145
+ this._cleanupKey?.()
146
+ this.el.remove()
147
+ }
148
+ }
@@ -0,0 +1,92 @@
1
+ .mk-input-wrapper {
2
+ position: relative;
3
+ display: inline-flex;
4
+ width: 100%;
5
+ max-width: 320px;
6
+ align-items: center;
7
+ }
8
+
9
+ .mk-input {
10
+ width: 100%;
11
+ height: 36px;
12
+ padding: 0 12px;
13
+ font-size: 13px;
14
+ color: var(--mk-text);
15
+ background: var(--mk-surface);
16
+ border: 1px solid var(--mk-border);
17
+ border-radius: var(--mk-radius);
18
+ outline: none;
19
+ transition: var(--mk-transition);
20
+ }
21
+
22
+ .mk-input::placeholder { color: var(--mk-text-tertiary); }
23
+
24
+ .mk-input:hover {
25
+ border-color: var(--mk-border-hover);
26
+ }
27
+
28
+ .mk-input:focus {
29
+ border-color: var(--mk-primary);
30
+ }
31
+
32
+ /* Error */
33
+ .mk-input-wrapper.is-error .mk-input {
34
+ border-color: var(--mk-danger);
35
+ animation: mk-input-shake 0.3s ease;
36
+ }
37
+
38
+ .mk-input-wrapper.is-success .mk-input {
39
+ border-color: var(--mk-success);
40
+ }
41
+
42
+ .mk-input__suffix {
43
+ position: absolute;
44
+ right: 10px;
45
+ display: flex;
46
+ align-items: center;
47
+ gap: 6px;
48
+ color: var(--mk-text-tertiary);
49
+ }
50
+
51
+ .mk-input__suffix-item {
52
+ cursor: pointer;
53
+ font-size: 13px;
54
+ width: 20px; height: 20px;
55
+ display: flex; align-items: center; justify-content: center;
56
+ border-radius: 4px;
57
+ transition: var(--mk-transition);
58
+ }
59
+ .mk-input__suffix-item:hover {
60
+ color: var(--mk-text);
61
+ background: var(--mk-surface-hover);
62
+ }
63
+
64
+ .mk-input__clear {
65
+ opacity: 0;
66
+ transition: opacity 0.2s;
67
+ }
68
+ .mk-input-wrapper:hover .mk-input__clear,
69
+ .mk-input:focus ~ .mk-input__suffix .mk-input__clear {
70
+ opacity: 1;
71
+ }
72
+
73
+ .mk-input__errormsg {
74
+ position: absolute;
75
+ bottom: -20px;
76
+ left: 0;
77
+ font-size: 12px;
78
+ color: var(--mk-danger);
79
+ opacity: 0;
80
+ transform: translateY(-2px);
81
+ transition: var(--mk-transition);
82
+ }
83
+ .mk-input__errormsg.show {
84
+ opacity: 1;
85
+ transform: translateY(0);
86
+ }
87
+
88
+ @keyframes mk-input-shake {
89
+ 0%, 100% { transform: translateX(0); }
90
+ 25% { transform: translateX(-4px); }
91
+ 75% { transform: translateX(4px); }
92
+ }
@@ -0,0 +1,162 @@
1
+ import './input.css'
2
+ import { withMotion, type MotionOptions } from '../../motion/component-motion.ts'
3
+
4
+ export interface InputOptions {
5
+ placeholder?: string
6
+ type?: string
7
+ value?: string
8
+ disabled?: boolean
9
+ clearable?: boolean
10
+ showPassword?: boolean
11
+ validate?: (value: string) => string | null
12
+ onInput?: (value: string) => void
13
+ onEnter?: (value: string) => void
14
+ motion?: MotionOptions
15
+ }
16
+
17
+ export class MkInput {
18
+ el: HTMLDivElement
19
+ input: HTMLInputElement
20
+ private options: InputOptions
21
+ private errorMsg: HTMLSpanElement
22
+ private motion: ReturnType<typeof withMotion> | null = null
23
+
24
+ constructor(container: HTMLElement | string, options: InputOptions = {}) {
25
+ const parent =
26
+ typeof container === 'string'
27
+ ? document.querySelector(container)!
28
+ : container
29
+
30
+ this.options = { ...options }
31
+
32
+ this.el = document.createElement('div')
33
+ this.el.className = 'mk-input-wrapper'
34
+
35
+ const inputId = `mk-input-${Math.random().toString(36).slice(2)}`
36
+
37
+ this.input = document.createElement('input')
38
+ this.input.className = 'mk-input'
39
+ this.input.id = inputId
40
+ this.input.type = this.options.type || 'text'
41
+ this.input.placeholder = this.options.placeholder || ''
42
+ this.input.value = this.options.value || ''
43
+ this.input.disabled = !!this.options.disabled
44
+
45
+ this.el.appendChild(this.input)
46
+
47
+ const suffix = document.createElement('span')
48
+ suffix.className = 'mk-input__suffix'
49
+
50
+ if (this.options.clearable) {
51
+ const clear = document.createElement('span')
52
+ clear.className = 'mk-input__suffix-item mk-input__clear'
53
+ clear.innerHTML = '✕'
54
+ clear.addEventListener('click', () => {
55
+ this.input.value = ''
56
+ this.input.focus()
57
+ this.options.onInput?.('')
58
+ this.clearError()
59
+ })
60
+ suffix.appendChild(clear)
61
+ }
62
+
63
+ if (this.options.showPassword && this.input.type === 'password') {
64
+ const eye = document.createElement('span')
65
+ eye.className = 'mk-input__suffix-item'
66
+ eye.innerHTML = '👁'
67
+ eye.title = '显示密码'
68
+ let showing = false
69
+ eye.addEventListener('click', () => {
70
+ showing = !showing
71
+ this.input.type = showing ? 'text' : 'password'
72
+ eye.innerHTML = showing ? '🙈' : '👁'
73
+ })
74
+ suffix.appendChild(eye)
75
+ }
76
+
77
+ if (suffix.children.length > 0) {
78
+ this.el.appendChild(suffix)
79
+ }
80
+
81
+ this.errorMsg = document.createElement('span')
82
+ this.errorMsg.className = 'mk-input__errormsg'
83
+ this.errorMsg.id = `${inputId}-error`
84
+ this.el.appendChild(this.errorMsg)
85
+
86
+ this.input.addEventListener('input', () => {
87
+ this.options.onInput?.(this.input.value)
88
+ this.clearError()
89
+ })
90
+
91
+ this.input.addEventListener('keydown', (e) => {
92
+ if (e.key === 'Enter') {
93
+ this.options.onEnter?.(this.input.value)
94
+ }
95
+ })
96
+
97
+ this.input.addEventListener('blur', () => {
98
+ this.validate()
99
+ })
100
+
101
+ parent.appendChild(this.el)
102
+
103
+ this.motion = withMotion(this.input, options.motion || { focus: 'ring', enter: 'fadeIn', duration: 200 })
104
+ }
105
+
106
+ get value(): string {
107
+ return this.input.value
108
+ }
109
+
110
+ set value(v: string) {
111
+ this.input.value = v
112
+ }
113
+
114
+ validate(): boolean {
115
+ if (!this.options.validate) return true
116
+ const error = this.options.validate(this.input.value)
117
+ if (error) {
118
+ this.showError(error)
119
+ return false
120
+ }
121
+ this.clearError()
122
+ return true
123
+ }
124
+
125
+ showError(msg: string): void {
126
+ this.el.classList.add('is-error')
127
+ this.el.classList.remove('is-success')
128
+ this.errorMsg.textContent = msg
129
+ this.errorMsg.classList.add('show')
130
+ this.input.setAttribute('aria-invalid', 'true')
131
+ this.input.setAttribute('aria-describedby', this.errorMsg.id)
132
+ }
133
+
134
+ showSuccess(): void {
135
+ this.el.classList.remove('is-error')
136
+ this.el.classList.add('is-success')
137
+ this.errorMsg.classList.remove('show')
138
+ }
139
+
140
+ clearError(): void {
141
+ this.el.classList.remove('is-error')
142
+ this.errorMsg.classList.remove('show')
143
+ this.input.removeAttribute('aria-invalid')
144
+ this.input.removeAttribute('aria-describedby')
145
+ }
146
+
147
+ focus(): void {
148
+ this.input.focus()
149
+ }
150
+
151
+ destroy(): void {
152
+ this.motion?.destroy()
153
+ this.el.remove()
154
+ }
155
+ }
156
+
157
+ export function createInput(
158
+ container: HTMLElement | string,
159
+ options?: InputOptions
160
+ ): MkInput {
161
+ return new MkInput(container, options)
162
+ }
@@ -0,0 +1,32 @@
1
+ .mk-divider {
2
+ display: flex;
3
+ align-items: center;
4
+ color: var(--mk-text-tertiary);
5
+ font-size: 13px;
6
+ margin: 16px 0;
7
+ }
8
+
9
+ .mk-divider::before,
10
+ .mk-divider::after {
11
+ content: '';
12
+ flex: 1;
13
+ height: 1px;
14
+ background: var(--mk-border);
15
+ }
16
+
17
+ .mk-divider--horizontal::before { margin-right: 12px; }
18
+ .mk-divider--horizontal::after { margin-left: 12px; }
19
+
20
+ .mk-divider--vertical {
21
+ display: inline-block;
22
+ width: 1px;
23
+ height: 1em;
24
+ background: var(--mk-border);
25
+ margin: 0 8px;
26
+ vertical-align: middle;
27
+ }
28
+
29
+ .mk-divider--dashed::before,
30
+ .mk-divider--dashed::after {
31
+ background: repeating-linear-gradient(90deg, var(--mk-border), var(--mk-border) 4px, transparent 4px, transparent 8px);
32
+ }
@@ -0,0 +1,42 @@
1
+ import './divider.css'
2
+
3
+ export interface DividerOptions {
4
+ text?: string
5
+ direction?: 'horizontal' | 'vertical'
6
+ dashed?: boolean
7
+ }
8
+
9
+ export class MkDivider {
10
+ el: HTMLDivElement
11
+
12
+ constructor(container: HTMLElement | string, options: DividerOptions = {}) {
13
+ const parent =
14
+ typeof container === 'string'
15
+ ? document.querySelector(container)!
16
+ : container
17
+
18
+ this.el = document.createElement('div')
19
+ this.el.className = 'mk-divider'
20
+
21
+ if (options.direction === 'vertical') {
22
+ this.el.classList.add('mk-divider--vertical')
23
+ } else {
24
+ this.el.classList.add('mk-divider--horizontal')
25
+ }
26
+ if (options.dashed) this.el.classList.add('mk-divider--dashed')
27
+ if (options.text) this.el.textContent = options.text
28
+
29
+ parent.appendChild(this.el)
30
+ }
31
+
32
+ destroy(): void {
33
+ this.el.remove()
34
+ }
35
+ }
36
+
37
+ export function createDivider(
38
+ container: HTMLElement | string,
39
+ options?: DividerOptions
40
+ ): MkDivider {
41
+ return new MkDivider(container, options)
42
+ }
@@ -0,0 +1,64 @@
1
+ .mk-row {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ margin: 0 -8px;
5
+ }
6
+
7
+ .mk-row--no-wrap { flex-wrap: nowrap; }
8
+ .mk-row--justify-start { justify-content: flex-start; }
9
+ .mk-row--justify-center { justify-content: center; }
10
+ .mk-row--justify-end { justify-content: flex-end; }
11
+ .mk-row--justify-space-between { justify-content: space-between; }
12
+ .mk-row--justify-space-around { justify-content: space-around; }
13
+ .mk-row--align-top { align-items: flex-start; }
14
+ .mk-row--align-middle { align-items: center; }
15
+ .mk-row--align-bottom { align-items: flex-end; }
16
+
17
+ .mk-col {
18
+ padding: 0 8px;
19
+ flex: 0 0 auto;
20
+ }
21
+
22
+ .mk-col--flex { flex: 1; }
23
+
24
+ /* 24-column grid */
25
+ .mk-col-1 { flex: 0 0 4.1667%; }
26
+ .mk-col-2 { flex: 0 0 8.3333%; }
27
+ .mk-col-3 { flex: 0 0 12.5%; }
28
+ .mk-col-4 { flex: 0 0 16.6667%; }
29
+ .mk-col-5 { flex: 0 0 20.8333%; }
30
+ .mk-col-6 { flex: 0 0 25%; }
31
+ .mk-col-7 { flex: 0 0 29.1667%; }
32
+ .mk-col-8 { flex: 0 0 33.3333%; }
33
+ .mk-col-9 { flex: 0 0 37.5%; }
34
+ .mk-col-10 { flex: 0 0 41.6667%; }
35
+ .mk-col-11 { flex: 0 0 45.8333%; }
36
+ .mk-col-12 { flex: 0 0 50%; }
37
+ .mk-col-13 { flex: 0 0 54.1667%; }
38
+ .mk-col-14 { flex: 0 0 58.3333%; }
39
+ .mk-col-15 { flex: 0 0 62.5%; }
40
+ .mk-col-16 { flex: 0 0 66.6667%; }
41
+ .mk-col-17 { flex: 0 0 70.8333%; }
42
+ .mk-col-18 { flex: 0 0 75%; }
43
+ .mk-col-19 { flex: 0 0 79.1667%; }
44
+ .mk-col-20 { flex: 0 0 83.3333%; }
45
+ .mk-col-21 { flex: 0 0 87.5%; }
46
+ .mk-col-22 { flex: 0 0 91.6667%; }
47
+ .mk-col-23 { flex: 0 0 95.8333%; }
48
+ .mk-col-24 { flex: 0 0 100%; }
49
+
50
+ /* Responsive - md (768px) */
51
+ @media (min-width: 768px) {
52
+ .mk-col-md-6 { flex: 0 0 25%; }
53
+ .mk-col-md-8 { flex: 0 0 33.3333%; }
54
+ .mk-col-md-12 { flex: 0 0 50%; }
55
+ .mk-col-md-24 { flex: 0 0 100%; }
56
+ }
57
+
58
+ /* Responsive - lg (1024px) */
59
+ @media (min-width: 1024px) {
60
+ .mk-col-lg-6 { flex: 0 0 25%; }
61
+ .mk-col-lg-8 { flex: 0 0 33.3333%; }
62
+ .mk-col-lg-12 { flex: 0 0 50%; }
63
+ .mk-col-lg-24 { flex: 0 0 100%; }
64
+ }
@@ -0,0 +1,57 @@
1
+ import './row.css'
2
+
3
+ export interface RowOptions {
4
+ gutter?: number
5
+ justify?: 'start' | 'center' | 'end' | 'space-between' | 'space-around'
6
+ align?: 'top' | 'middle' | 'bottom'
7
+ wrap?: boolean
8
+ }
9
+
10
+ export class MkRow {
11
+ el: HTMLDivElement
12
+
13
+ constructor(container: HTMLElement | string, options: RowOptions = {}) {
14
+ const parent =
15
+ typeof container === 'string'
16
+ ? document.querySelector(container)!
17
+ : container
18
+
19
+ this.el = document.createElement('div')
20
+ this.el.className = 'mk-row'
21
+
22
+ if (options.justify) this.el.classList.add(`mk-row--justify-${options.justify}`)
23
+ if (options.align) this.el.classList.add(`mk-row--align-${options.align}`)
24
+ if (options.wrap === false) this.el.classList.add('mk-row--no-wrap')
25
+ if (options.gutter) {
26
+ this.el.style.margin = `0 -${options.gutter / 2}px`
27
+ }
28
+
29
+ parent.appendChild(this.el)
30
+ }
31
+
32
+ addCol(span: number | 'flex', content?: string | HTMLElement, className?: string): HTMLDivElement {
33
+ const col = document.createElement('div')
34
+ col.className = `mk-col mk-col-${span === 'flex' ? 'flex' : span}`
35
+ if (className) col.classList.add(className)
36
+
37
+ if (typeof content === 'string') {
38
+ col.innerHTML = content
39
+ } else if (content) {
40
+ col.appendChild(content)
41
+ }
42
+
43
+ this.el.appendChild(col)
44
+ return col
45
+ }
46
+
47
+ destroy(): void {
48
+ this.el.remove()
49
+ }
50
+ }
51
+
52
+ export function createRow(
53
+ container: HTMLElement | string,
54
+ options?: RowOptions
55
+ ): MkRow {
56
+ return new MkRow(container, options)
57
+ }
@@ -0,0 +1,14 @@
1
+ .mk-space {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ flex-wrap: wrap;
5
+ }
6
+
7
+ .mk-space--vertical { flex-direction: column; }
8
+
9
+ .mk-space--small { gap: 4px; }
10
+ .mk-space--default { gap: 8px; }
11
+ .mk-space--large { gap: 16px; }
12
+
13
+ .mk-space--wrap { flex-wrap: wrap; }
14
+ .mk-space--nowrap { flex-wrap: nowrap; }
@@ -0,0 +1,48 @@
1
+ import './space.css'
2
+
3
+ export interface SpaceOptions {
4
+ direction?: 'horizontal' | 'vertical'
5
+ size?: 'small' | 'default' | 'large'
6
+ wrap?: boolean
7
+ }
8
+
9
+ export class MkSpace {
10
+ el: HTMLDivElement
11
+
12
+ constructor(container: HTMLElement | string, options: SpaceOptions = {}) {
13
+ const parent =
14
+ typeof container === 'string'
15
+ ? document.querySelector(container)!
16
+ : container
17
+
18
+ this.el = document.createElement('div')
19
+ this.el.className = 'mk-space'
20
+
21
+ if (options.direction === 'vertical') this.el.classList.add('mk-space--vertical')
22
+ if (options.size) this.el.classList.add(`mk-space--${options.size}`)
23
+ if (options.wrap !== false) this.el.classList.add('mk-space--wrap')
24
+
25
+ parent.appendChild(this.el)
26
+ }
27
+
28
+ add(content: string | HTMLElement): void {
29
+ if (typeof content === 'string') {
30
+ const span = document.createElement('span')
31
+ span.innerHTML = content
32
+ this.el.appendChild(span)
33
+ } else {
34
+ this.el.appendChild(content)
35
+ }
36
+ }
37
+
38
+ destroy(): void {
39
+ this.el.remove()
40
+ }
41
+ }
42
+
43
+ export function createSpace(
44
+ container: HTMLElement | string,
45
+ options?: SpaceOptions
46
+ ): MkSpace {
47
+ return new MkSpace(container, options)
48
+ }
@@ -0,0 +1,37 @@
1
+ .mk-loading-mask {
2
+ position: absolute;
3
+ inset: 0;
4
+ background: rgba(9, 9, 11, 0.8);
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ z-index: 100;
9
+ border-radius: inherit;
10
+ flex-direction: column;
11
+ gap: 10px;
12
+ }
13
+
14
+ .mk-loading-mask.is-fullscreen {
15
+ position: fixed;
16
+ background: rgba(9, 9, 11, 0.85);
17
+ z-index: 9999;
18
+ border-radius: 0;
19
+ }
20
+
21
+ .mk-loading__spinner {
22
+ width: 32px;
23
+ height: 32px;
24
+ border: 2px solid var(--mk-border);
25
+ border-top-color: var(--mk-primary);
26
+ border-radius: 50%;
27
+ animation: mk-spin 0.7s linear infinite;
28
+ }
29
+
30
+ .mk-loading__text {
31
+ font-size: 13px;
32
+ color: var(--mk-text-secondary);
33
+ }
34
+
35
+ @keyframes mk-spin {
36
+ to { transform: rotate(360deg); }
37
+ }