@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luanlu/mk-motion",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "A lightweight, modern frontend animation library",
5
5
  "type": "module",
6
6
  "main": "./dist/mk-motion.umd.cjs",
@@ -39,9 +39,7 @@
39
39
  },
40
40
  "files": [
41
41
  "dist",
42
- "src/vue",
43
- "src/nuxt",
44
- "src/vite"
42
+ "src"
45
43
  ],
46
44
  "publishConfig": {
47
45
  "access": "public"
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Focus trap for modals, drawers, and popovers.
3
+ * Keeps keyboard focus within a given container.
4
+ */
5
+ const FOCUSABLE_SELECTOR = [
6
+ 'a[href]',
7
+ 'button:not([disabled])',
8
+ 'input:not([disabled])',
9
+ 'select:not([disabled])',
10
+ 'textarea:not([disabled])',
11
+ '[tabindex]:not([tabindex="-1"])',
12
+ ].join(',')
13
+
14
+ export class FocusTrap {
15
+ private container: HTMLElement
16
+ private previousActiveElement: Element | null = null
17
+ private listeners: Array<() => void> = []
18
+
19
+ constructor(container: HTMLElement) {
20
+ this.container = container
21
+ }
22
+
23
+ activate(initialFocus?: HTMLElement): void {
24
+ this.previousActiveElement = document.activeElement
25
+ const focusable = this.getFocusableElements()
26
+ const toFocus = initialFocus || (focusable.length > 0 ? focusable[0] : null)
27
+ if (toFocus instanceof HTMLElement) {
28
+ toFocus.focus()
29
+ }
30
+
31
+ const handler = (e: KeyboardEvent) => this.handleKeyDown(e)
32
+ this.container.addEventListener('keydown', handler)
33
+ this.listeners.push(() => this.container.removeEventListener('keydown', handler))
34
+ }
35
+
36
+ deactivate(): void {
37
+ this.listeners.forEach((remove) => remove())
38
+ this.listeners = []
39
+ if (this.previousActiveElement instanceof HTMLElement) {
40
+ this.previousActiveElement.focus()
41
+ }
42
+ }
43
+
44
+ private getFocusableElements(): NodeListOf<HTMLElement> {
45
+ return this.container.querySelectorAll(FOCUSABLE_SELECTOR)
46
+ }
47
+
48
+ private handleKeyDown(e: KeyboardEvent): void {
49
+ if (e.key !== 'Tab') return
50
+ const focusable = Array.from(this.getFocusableElements())
51
+ if (focusable.length === 0) return
52
+
53
+ const first = focusable[0]
54
+ const last = focusable[focusable.length - 1]
55
+
56
+ if (e.shiftKey && document.activeElement === first) {
57
+ e.preventDefault()
58
+ last.focus()
59
+ } else if (!e.shiftKey && document.activeElement === last) {
60
+ e.preventDefault()
61
+ first.focus()
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Keyboard navigation helpers for accessible components.
3
+ */
4
+
5
+ export interface KeyHandler {
6
+ key: string
7
+ handler: (e: KeyboardEvent) => void
8
+ preventDefault?: boolean
9
+ }
10
+
11
+ /**
12
+ * Attach keyboard handlers to an element.
13
+ * Returns a cleanup function.
14
+ */
15
+ export function onKey(el: HTMLElement, handlers: KeyHandler[]): () => void {
16
+ const listener = (e: KeyboardEvent) => {
17
+ for (const h of handlers) {
18
+ if (e.key === h.key) {
19
+ if (h.preventDefault !== false) e.preventDefault()
20
+ h.handler(e)
21
+ return
22
+ }
23
+ }
24
+ }
25
+ el.addEventListener('keydown', listener)
26
+ return () => el.removeEventListener('keydown', listener)
27
+ }
28
+
29
+ /**
30
+ * Common ARIA key patterns.
31
+ */
32
+ export const Keys = {
33
+ Enter: 'Enter',
34
+ Escape: 'Escape',
35
+ Tab: 'Tab',
36
+ ArrowUp: 'ArrowUp',
37
+ ArrowDown: 'ArrowDown',
38
+ ArrowLeft: 'ArrowLeft',
39
+ ArrowRight: 'ArrowRight',
40
+ Home: 'Home',
41
+ End: 'End',
42
+ Space: ' ',
43
+ } as const
@@ -0,0 +1,111 @@
1
+ .mk-alert {
2
+ display: flex;
3
+ align-items: flex-start;
4
+ gap: var(--mk-space-3);
5
+ padding: var(--mk-space-3) var(--mk-space-4);
6
+ font-size: var(--mk-text-sm);
7
+ line-height: var(--mk-leading-snug);
8
+ color: var(--mk-text);
9
+ background: var(--mk-surface);
10
+ border: 1px solid var(--mk-border);
11
+ border-left-width: 4px;
12
+ border-radius: var(--mk-radius);
13
+ transition: var(--mk-transition-all);
14
+ overflow: hidden;
15
+ }
16
+
17
+ .mk-alert__icon {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ width: 20px;
22
+ height: 20px;
23
+ flex-shrink: 0;
24
+ font-size: 14px;
25
+ font-weight: var(--mk-font-bold);
26
+ margin-top: 1px;
27
+ }
28
+
29
+ .mk-alert__content {
30
+ flex: 1;
31
+ min-width: 0;
32
+ }
33
+
34
+ .mk-alert__title {
35
+ font-weight: var(--mk-font-semibold);
36
+ margin-bottom: 2px;
37
+ }
38
+
39
+ .mk-alert__description {
40
+ color: var(--mk-text-secondary);
41
+ font-size: var(--mk-text-sm);
42
+ }
43
+
44
+ .mk-alert__close {
45
+ display: inline-flex;
46
+ align-items: center;
47
+ justify-content: center;
48
+ width: 20px;
49
+ height: 20px;
50
+ padding: 0;
51
+ margin: 0;
52
+ font-size: 18px;
53
+ line-height: 1;
54
+ color: var(--mk-text-tertiary);
55
+ background: transparent;
56
+ border: none;
57
+ border-radius: var(--mk-radius-sm);
58
+ cursor: pointer;
59
+ flex-shrink: 0;
60
+ transition: var(--mk-transition-colors);
61
+ }
62
+
63
+ .mk-alert__close:hover {
64
+ color: var(--mk-text);
65
+ background: rgba(255, 255, 255, 0.06);
66
+ }
67
+
68
+ /* Types */
69
+ .mk-alert--info {
70
+ border-left-color: var(--mk-info);
71
+ background: var(--mk-info-soft);
72
+ }
73
+ .mk-alert--info .mk-alert__icon {
74
+ color: var(--mk-info);
75
+ }
76
+
77
+ .mk-alert--success {
78
+ border-left-color: var(--mk-success);
79
+ background: var(--mk-success-soft);
80
+ }
81
+ .mk-alert--success .mk-alert__icon {
82
+ color: var(--mk-success);
83
+ }
84
+
85
+ .mk-alert--warning {
86
+ border-left-color: var(--mk-warning);
87
+ background: var(--mk-warning-soft);
88
+ }
89
+ .mk-alert--warning .mk-alert__icon {
90
+ color: var(--mk-warning);
91
+ }
92
+
93
+ .mk-alert--danger {
94
+ border-left-color: var(--mk-danger);
95
+ background: var(--mk-danger-soft);
96
+ }
97
+ .mk-alert--danger .mk-alert__icon {
98
+ color: var(--mk-danger);
99
+ }
100
+
101
+ /* Closing animation */
102
+ .mk-alert.is-closing {
103
+ transform: scale(0.98);
104
+ opacity: 0;
105
+ max-height: 0;
106
+ padding-top: 0;
107
+ padding-bottom: 0;
108
+ margin-top: 0;
109
+ margin-bottom: 0;
110
+ border-width: 0;
111
+ }
@@ -0,0 +1,107 @@
1
+ import '../../styles/element-plus.css'
2
+ import './alert.css'
3
+
4
+ export interface AlertOptions {
5
+ type?: 'info' | 'success' | 'warning' | 'danger'
6
+ title?: string
7
+ description?: string
8
+ closable?: boolean
9
+ showIcon?: boolean
10
+ onClose?: () => void
11
+ }
12
+
13
+ const typeIcons: Record<string, string> = {
14
+ info: 'ℹ',
15
+ success: '✓',
16
+ warning: '⚠',
17
+ danger: '✕',
18
+ }
19
+
20
+ export class MkAlert {
21
+ el: HTMLDivElement
22
+ private options: AlertOptions
23
+
24
+ constructor(container: HTMLElement | string, options: AlertOptions = {}) {
25
+ const parent =
26
+ typeof container === 'string'
27
+ ? document.querySelector(container)!
28
+ : container
29
+
30
+ this.options = {
31
+ type: 'info',
32
+ showIcon: true,
33
+ ...options,
34
+ }
35
+
36
+ this.el = document.createElement('div')
37
+ this.el.className = this.buildClass()
38
+ this.el.setAttribute('role', 'alert')
39
+
40
+ if (this.options.showIcon) {
41
+ const icon = document.createElement('span')
42
+ icon.className = 'mk-alert__icon'
43
+ icon.textContent = typeIcons[this.options.type!]
44
+ this.el.appendChild(icon)
45
+ }
46
+
47
+ const content = document.createElement('div')
48
+ content.className = 'mk-alert__content'
49
+
50
+ if (this.options.title) {
51
+ const title = document.createElement('div')
52
+ title.className = 'mk-alert__title'
53
+ title.textContent = this.options.title
54
+ content.appendChild(title)
55
+ }
56
+
57
+ if (this.options.description) {
58
+ const desc = document.createElement('div')
59
+ desc.className = 'mk-alert__description'
60
+ desc.textContent = this.options.description
61
+ content.appendChild(desc)
62
+ }
63
+
64
+ this.el.appendChild(content)
65
+
66
+ if (this.options.closable) {
67
+ const closeBtn = document.createElement('button')
68
+ closeBtn.className = 'mk-alert__close'
69
+ closeBtn.setAttribute('aria-label', 'Close')
70
+ closeBtn.innerHTML = '×'
71
+ closeBtn.addEventListener('click', () => this.close())
72
+ this.el.appendChild(closeBtn)
73
+ }
74
+
75
+ parent.appendChild(this.el)
76
+ }
77
+
78
+ private buildClass(): string {
79
+ const classes = ['mk-alert', `mk-alert--${this.options.type}`]
80
+ if (this.options.closable) classes.push('is-closable')
81
+ return classes.join(' ')
82
+ }
83
+
84
+ close(): void {
85
+ this.el.classList.add('is-closing')
86
+ this.el.style.pointerEvents = 'none'
87
+ this.el.addEventListener(
88
+ 'transitionend',
89
+ () => {
90
+ this.destroy()
91
+ this.options.onClose?.()
92
+ },
93
+ { once: true }
94
+ )
95
+ }
96
+
97
+ destroy(): void {
98
+ this.el.remove()
99
+ }
100
+ }
101
+
102
+ export function createAlert(
103
+ container: HTMLElement | string,
104
+ options?: AlertOptions
105
+ ): MkAlert {
106
+ return new MkAlert(container, options)
107
+ }
@@ -0,0 +1,112 @@
1
+ .mk-avatar {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ width: 40px;
6
+ height: 40px;
7
+ font-size: var(--mk-text-sm);
8
+ font-weight: var(--mk-font-medium);
9
+ color: var(--mk-text);
10
+ background: var(--mk-surface-raised);
11
+ border: 1px solid var(--mk-border);
12
+ border-radius: var(--mk-radius-full);
13
+ overflow: hidden;
14
+ flex-shrink: 0;
15
+ user-select: none;
16
+ }
17
+
18
+ .mk-avatar__image {
19
+ width: 100%;
20
+ height: 100%;
21
+ object-fit: cover;
22
+ display: block;
23
+ }
24
+
25
+ .mk-avatar__fallback {
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ width: 100%;
30
+ height: 100%;
31
+ font-size: inherit;
32
+ font-weight: var(--mk-font-semibold);
33
+ color: var(--mk-text-secondary);
34
+ background: var(--mk-surface-hover);
35
+ }
36
+
37
+ /* Sizes */
38
+ .mk-avatar--small {
39
+ width: 28px;
40
+ height: 28px;
41
+ font-size: var(--mk-text-xs);
42
+ }
43
+
44
+ .mk-avatar--large {
45
+ width: 56px;
46
+ height: 56px;
47
+ font-size: var(--mk-text-md);
48
+ }
49
+
50
+ /* Shape */
51
+ .mk-avatar--square {
52
+ border-radius: var(--mk-radius);
53
+ }
54
+
55
+ /* Group */
56
+ .mk-avatar-group {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ }
60
+
61
+ .mk-avatar-group__item {
62
+ margin-left: -10px;
63
+ transition: transform var(--mk-duration-fast) var(--mk-ease-default);
64
+ }
65
+
66
+ .mk-avatar-group__item:first-child {
67
+ margin-left: 0;
68
+ }
69
+
70
+ .mk-avatar-group__item:hover {
71
+ transform: translateY(-2px);
72
+ z-index: 1;
73
+ }
74
+
75
+ .mk-avatar-group__item .mk-avatar {
76
+ border: 2px solid var(--mk-bg);
77
+ }
78
+
79
+ .mk-avatar-group__counter {
80
+ display: inline-flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ width: 40px;
84
+ height: 40px;
85
+ margin-left: -10px;
86
+ font-size: var(--mk-text-xs);
87
+ font-weight: var(--mk-font-semibold);
88
+ color: var(--mk-text-secondary);
89
+ background: var(--mk-surface-raised);
90
+ border: 2px solid var(--mk-bg);
91
+ border-radius: var(--mk-radius-full);
92
+ flex-shrink: 0;
93
+ }
94
+
95
+ .mk-avatar-group--small .mk-avatar-group__counter {
96
+ width: 28px;
97
+ height: 28px;
98
+ }
99
+
100
+ .mk-avatar-group--large .mk-avatar-group__counter {
101
+ width: 56px;
102
+ height: 56px;
103
+ font-size: var(--mk-text-sm);
104
+ }
105
+
106
+ .mk-avatar-group--small .mk-avatar-group__item {
107
+ margin-left: -7px;
108
+ }
109
+
110
+ .mk-avatar-group--large .mk-avatar-group__item {
111
+ margin-left: -14px;
112
+ }
@@ -0,0 +1,175 @@
1
+ import '../../styles/element-plus.css'
2
+ import './avatar.css'
3
+
4
+ export interface AvatarOptions {
5
+ src?: string
6
+ text?: string
7
+ size?: 'small' | 'default' | 'large'
8
+ shape?: 'circle' | 'square'
9
+ icon?: string
10
+ }
11
+
12
+ export class MkAvatar {
13
+ el: HTMLDivElement
14
+ private options: AvatarOptions
15
+
16
+ constructor(container: HTMLElement | string, options: AvatarOptions = {}) {
17
+ const parent =
18
+ typeof container === 'string'
19
+ ? document.querySelector(container)!
20
+ : container
21
+
22
+ this.options = {
23
+ size: 'default',
24
+ shape: 'circle',
25
+ ...options,
26
+ }
27
+
28
+ this.el = document.createElement('div')
29
+ this.el.className = this.buildClass()
30
+
31
+ if (this.options.src) {
32
+ const img = document.createElement('img')
33
+ img.className = 'mk-avatar__image'
34
+ img.src = this.options.src
35
+ img.alt = this.options.text || ''
36
+ img.addEventListener('error', () => this.showFallback())
37
+ this.el.appendChild(img)
38
+ } else {
39
+ this.showFallback()
40
+ }
41
+
42
+ parent.appendChild(this.el)
43
+ }
44
+
45
+ private buildClass(): string {
46
+ const classes = ['mk-avatar']
47
+ if (this.options.size && this.options.size !== 'default') {
48
+ classes.push(`mk-avatar--${this.options.size}`)
49
+ }
50
+ if (this.options.shape && this.options.shape !== 'circle') {
51
+ classes.push(`mk-avatar--${this.options.shape}`)
52
+ }
53
+ return classes.join(' ')
54
+ }
55
+
56
+ private showFallback(): void {
57
+ const existing = this.el.querySelector('.mk-avatar__image')
58
+ if (existing) existing.remove()
59
+
60
+ const fallback = document.createElement('span')
61
+ fallback.className = 'mk-avatar__fallback'
62
+
63
+ if (this.options.icon) {
64
+ fallback.textContent = this.options.icon
65
+ } else if (this.options.text) {
66
+ fallback.textContent = this.options.text
67
+ .split(' ')
68
+ .map((w) => w[0])
69
+ .slice(0, 2)
70
+ .join('')
71
+ .toUpperCase()
72
+ } else {
73
+ fallback.textContent = '?'
74
+ }
75
+
76
+ this.el.appendChild(fallback)
77
+ }
78
+
79
+ setSrc(src: string): void {
80
+ this.options.src = src
81
+ const existingFallback = this.el.querySelector('.mk-avatar__fallback')
82
+ if (existingFallback) existingFallback.remove()
83
+
84
+ let img = this.el.querySelector('.mk-avatar__image') as HTMLImageElement | null
85
+ if (!img) {
86
+ img = document.createElement('img')
87
+ img.className = 'mk-avatar__image'
88
+ img.alt = this.options.text || ''
89
+ img.addEventListener('error', () => this.showFallback())
90
+ this.el.appendChild(img)
91
+ }
92
+ img.src = src
93
+ }
94
+
95
+ setText(text: string): void {
96
+ this.options.text = text
97
+ if (!this.options.src) {
98
+ this.showFallback()
99
+ }
100
+ }
101
+
102
+ destroy(): void {
103
+ this.el.remove()
104
+ }
105
+ }
106
+
107
+ export interface AvatarGroupOptions {
108
+ avatars: AvatarOptions[]
109
+ max?: number
110
+ size?: 'small' | 'default' | 'large'
111
+ }
112
+
113
+ export class MkAvatarGroup {
114
+ el: HTMLDivElement
115
+ private options: AvatarGroupOptions
116
+
117
+ constructor(container: HTMLElement | string, options: AvatarGroupOptions) {
118
+ const parent =
119
+ typeof container === 'string'
120
+ ? document.querySelector(container)!
121
+ : container
122
+
123
+ this.options = {
124
+ size: 'default',
125
+ ...options,
126
+ }
127
+
128
+ this.el = document.createElement('div')
129
+ this.el.className = 'mk-avatar-group'
130
+ if (this.options.size && this.options.size !== 'default') {
131
+ this.el.classList.add(`mk-avatar-group--${this.options.size}`)
132
+ }
133
+
134
+ const display = this.options.max
135
+ ? this.options.avatars.slice(0, this.options.max)
136
+ : this.options.avatars
137
+
138
+ display.forEach((avatarOpts) => {
139
+ const wrapper = document.createElement('div')
140
+ wrapper.className = 'mk-avatar-group__item'
141
+ new MkAvatar(wrapper, { size: this.options.size, ...avatarOpts })
142
+ this.el.appendChild(wrapper)
143
+ })
144
+
145
+ const remaining = this.options.max
146
+ ? this.options.avatars.length - this.options.max
147
+ : 0
148
+ if (remaining > 0) {
149
+ const counter = document.createElement('div')
150
+ counter.className = 'mk-avatar-group__counter'
151
+ counter.textContent = `+${remaining}`
152
+ this.el.appendChild(counter)
153
+ }
154
+
155
+ parent.appendChild(this.el)
156
+ }
157
+
158
+ destroy(): void {
159
+ this.el.remove()
160
+ }
161
+ }
162
+
163
+ export function createAvatar(
164
+ container: HTMLElement | string,
165
+ options?: AvatarOptions
166
+ ): MkAvatar {
167
+ return new MkAvatar(container, options)
168
+ }
169
+
170
+ export function createAvatarGroup(
171
+ container: HTMLElement | string,
172
+ options: AvatarGroupOptions
173
+ ): MkAvatarGroup {
174
+ return new MkAvatarGroup(container, options)
175
+ }
@@ -0,0 +1,31 @@
1
+ .mk-breadcrumb {
2
+ display: flex;
3
+ align-items: center;
4
+ flex-wrap: wrap;
5
+ font-size: var(--mk-text-sm);
6
+ color: var(--mk-text-secondary);
7
+ }
8
+
9
+ .mk-breadcrumb__item {
10
+ transition: var(--mk-transition-colors);
11
+ }
12
+
13
+ .mk-breadcrumb__item.is-link {
14
+ color: var(--mk-text-secondary);
15
+ cursor: pointer;
16
+ }
17
+
18
+ .mk-breadcrumb__item.is-link:hover {
19
+ color: var(--mk-primary);
20
+ }
21
+
22
+ .mk-breadcrumb__item.is-current {
23
+ color: var(--mk-text);
24
+ font-weight: var(--mk-font-medium);
25
+ }
26
+
27
+ .mk-breadcrumb__separator {
28
+ margin: 0 8px;
29
+ color: var(--mk-text-tertiary);
30
+ user-select: none;
31
+ }