ketekny-ui-kit 1.0.114 → 1.0.116
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<main class="flex min-h-0 w-full max-w-none flex-1 flex-col overflow-hidden bg-slate-100">
|
|
3
|
+
<div
|
|
4
|
+
v-if="mobileSidebarOpen && !isDesktopViewport"
|
|
5
|
+
class="fixed inset-0 z-20 bg-slate-950/40 backdrop-blur-[1px] lg:hidden"
|
|
6
|
+
@click="toggleMobileSidebar"
|
|
7
|
+
/>
|
|
8
|
+
|
|
3
9
|
<section
|
|
4
10
|
v-if="hasTopSection"
|
|
5
11
|
class="shrink-0 bg-white px-4 pb-4 pb-0 pt-8 sm:px-6 lg:px-8"
|
|
@@ -44,8 +50,27 @@
|
|
|
44
50
|
</div>
|
|
45
51
|
|
|
46
52
|
<div class="flex min-h-0 flex-1 flex-col overflow-hidden bg-slate-100 py-4">
|
|
53
|
+
<div
|
|
54
|
+
class="lg:hidden"
|
|
55
|
+
:class="mobileSidebarOpen ? 'mx-auto flex w-full max-w-none px-4 pb-3 sm:px-6' : 'fixed left-0 top-1/2 z-20 -translate-y-1/2'"
|
|
56
|
+
>
|
|
57
|
+
<kButton
|
|
58
|
+
:label="mobileSidebarOpen ? 'Hide sidebar' : 'Show sidebar'"
|
|
59
|
+
:icon="mobileSidebarOpen ? 'PanelLeftClose' : 'PanelLeftOpen'"
|
|
60
|
+
variant="soft"
|
|
61
|
+
:icon-only="!mobileSidebarOpen"
|
|
62
|
+
:aria-label="mobileSidebarOpen ? null : 'Show sidebar'"
|
|
63
|
+
:class="mobileSidebarOpen ? '' : 'rounded-full shadow-lg shadow-slate-900/15'"
|
|
64
|
+
@click="toggleMobileSidebar"
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
47
67
|
<div class="mx-auto flex min-h-0 w-full max-w-none flex-1 gap-2">
|
|
48
|
-
<aside
|
|
68
|
+
<aside
|
|
69
|
+
v-if="showSidebar"
|
|
70
|
+
class="min-h-0 shrink-0 overflow-y-auto bg-slate-100 px-4 sm:px-6 lg:ml-8 lg:pr-4 lg:pl-0"
|
|
71
|
+
:class="mobileSidebarClass"
|
|
72
|
+
:style="sidebarStyle"
|
|
73
|
+
>
|
|
49
74
|
<slot name="sidebar" />
|
|
50
75
|
</aside>
|
|
51
76
|
<section class="min-h-0 min-w-0 flex-1 overflow-y-auto px-2">
|
|
@@ -60,6 +85,9 @@
|
|
|
60
85
|
import packageJson from '../../package.json'
|
|
61
86
|
import kButton from '../ui/kButton.vue'
|
|
62
87
|
|
|
88
|
+
const LG_VIEWPORT_QUERY = '(min-width: 1024px)'
|
|
89
|
+
const MAIN_MENU_TOGGLE_EVENT = 'ketekny:two-col-layout-mobile-menu'
|
|
90
|
+
|
|
63
91
|
export default {
|
|
64
92
|
name: 'PageTwoColShell',
|
|
65
93
|
components: { kButton },
|
|
@@ -85,6 +113,13 @@ export default {
|
|
|
85
113
|
default: '300px',
|
|
86
114
|
},
|
|
87
115
|
},
|
|
116
|
+
data() {
|
|
117
|
+
return {
|
|
118
|
+
isDesktopViewport: true,
|
|
119
|
+
mobileSidebarOpen: false,
|
|
120
|
+
viewportQuery: null,
|
|
121
|
+
}
|
|
122
|
+
},
|
|
88
123
|
computed: {
|
|
89
124
|
hasTopSection() {
|
|
90
125
|
return Boolean(
|
|
@@ -105,8 +140,46 @@ export default {
|
|
|
105
140
|
maxWidth: this.sidebarWidth,
|
|
106
141
|
}
|
|
107
142
|
},
|
|
143
|
+
mobileSidebarClass() {
|
|
144
|
+
if (this.isDesktopViewport) return ''
|
|
145
|
+
return 'fixed left-0 top-0 z-20 h-full max-w-[85vw] border-r border-slate-200 bg-slate-100 py-4 shadow-xl shadow-slate-900/20'
|
|
146
|
+
},
|
|
147
|
+
showSidebar() {
|
|
148
|
+
return this.isDesktopViewport || this.mobileSidebarOpen
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
mounted() {
|
|
152
|
+
if (typeof window === 'undefined') return
|
|
153
|
+
this.viewportQuery = window.matchMedia(LG_VIEWPORT_QUERY)
|
|
154
|
+
this.isDesktopViewport = this.viewportQuery.matches
|
|
155
|
+
this.syncSidebarForViewport()
|
|
156
|
+
this.viewportQuery.addEventListener('change', this.handleViewportChange)
|
|
157
|
+
window.addEventListener(MAIN_MENU_TOGGLE_EVENT, this.handleMainMenuToggle)
|
|
158
|
+
},
|
|
159
|
+
beforeUnmount() {
|
|
160
|
+
this.viewportQuery?.removeEventListener?.('change', this.handleViewportChange)
|
|
161
|
+
window.removeEventListener(MAIN_MENU_TOGGLE_EVENT, this.handleMainMenuToggle)
|
|
108
162
|
},
|
|
109
163
|
methods: {
|
|
164
|
+
handleMainMenuToggle(event) {
|
|
165
|
+
if (this.isDesktopViewport) return
|
|
166
|
+
if (event?.detail?.open) {
|
|
167
|
+
this.mobileSidebarOpen = false
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
handleViewportChange(event) {
|
|
171
|
+
this.isDesktopViewport = event.matches
|
|
172
|
+
this.syncSidebarForViewport()
|
|
173
|
+
},
|
|
174
|
+
syncSidebarForViewport() {
|
|
175
|
+
if (!this.isDesktopViewport) {
|
|
176
|
+
this.mobileSidebarOpen = false
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
toggleMobileSidebar() {
|
|
180
|
+
if (this.isDesktopViewport) return
|
|
181
|
+
this.mobileSidebarOpen = !this.mobileSidebarOpen
|
|
182
|
+
},
|
|
110
183
|
openExternalUrl() {
|
|
111
184
|
const target = String(this.externalUrl || '').trim()
|
|
112
185
|
if (!target) return
|
|
@@ -18,6 +18,7 @@ import demoAvatarUrl from '../assets/placeholder-user.jpg'
|
|
|
18
18
|
const FAVORITES_STORAGE_KEY = 'ketekny-ui-sidebar-favorites'
|
|
19
19
|
const MENU_TAB_STORAGE_KEY = 'ketekny-ui-sidebar-menu-tab'
|
|
20
20
|
const LOGO_BASE_URL = 'https://s3.ketekny.gr/public/web-apps/logos'
|
|
21
|
+
const LG_VIEWPORT_QUERY = '(min-width: 1024px)'
|
|
21
22
|
|
|
22
23
|
export default {
|
|
23
24
|
name: 'MultilevelSidebar',
|
|
@@ -86,6 +87,8 @@ export default {
|
|
|
86
87
|
activeMenuTab: 'all',
|
|
87
88
|
query: '',
|
|
88
89
|
collapsed: false,
|
|
90
|
+
isDesktopViewport: true,
|
|
91
|
+
viewportQuery: null,
|
|
89
92
|
openIds: new Set(['projects']),
|
|
90
93
|
favoriteIds: new Set(),
|
|
91
94
|
footerLinks: [
|
|
@@ -232,8 +235,30 @@ export default {
|
|
|
232
235
|
activeMenuTab(value) {
|
|
233
236
|
this.persistActiveMenuTab(value)
|
|
234
237
|
},
|
|
238
|
+
mobileOpen(isOpen) {
|
|
239
|
+
if (isOpen) this.syncCollapsedForViewport()
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
mounted() {
|
|
243
|
+
if (typeof window === 'undefined') return
|
|
244
|
+
this.viewportQuery = window.matchMedia(LG_VIEWPORT_QUERY)
|
|
245
|
+
this.isDesktopViewport = this.viewportQuery.matches
|
|
246
|
+
this.syncCollapsedForViewport()
|
|
247
|
+
this.viewportQuery.addEventListener('change', this.handleViewportChange)
|
|
248
|
+
},
|
|
249
|
+
beforeUnmount() {
|
|
250
|
+
this.viewportQuery?.removeEventListener?.('change', this.handleViewportChange)
|
|
235
251
|
},
|
|
236
252
|
methods: {
|
|
253
|
+
handleViewportChange(event) {
|
|
254
|
+
this.isDesktopViewport = event.matches
|
|
255
|
+
this.syncCollapsedForViewport()
|
|
256
|
+
},
|
|
257
|
+
syncCollapsedForViewport() {
|
|
258
|
+
if (!this.isDesktopViewport) {
|
|
259
|
+
this.collapsed = false
|
|
260
|
+
}
|
|
261
|
+
},
|
|
237
262
|
findMenuItemById(items, id) {
|
|
238
263
|
for (const item of items) {
|
|
239
264
|
if (item.id === id) return item
|
|
@@ -307,6 +332,10 @@ export default {
|
|
|
307
332
|
}
|
|
308
333
|
},
|
|
309
334
|
toggleCollapsed() {
|
|
335
|
+
if (!this.isDesktopViewport) {
|
|
336
|
+
this.collapsed = false
|
|
337
|
+
return
|
|
338
|
+
}
|
|
310
339
|
this.collapsed = !this.collapsed
|
|
311
340
|
},
|
|
312
341
|
toggle(id) {
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
</main>
|
|
40
40
|
|
|
41
41
|
<button
|
|
42
|
-
v-if="!mobileMenuOpen"
|
|
42
|
+
v-if="!mobileMenuOpen && showMobileMenuButton"
|
|
43
43
|
type="button"
|
|
44
|
-
class="fixed bottom-
|
|
44
|
+
class="fixed bottom-0 left-0 z-50 inline-flex size-14 items-center justify-center rounded-tr-2xl transition-colors lg:hidden"
|
|
45
45
|
:class="theme === 'light'
|
|
46
46
|
? 'border border-slate-200 bg-white text-slate-700 shadow-lg shadow-slate-900/10 hover:bg-slate-100'
|
|
47
47
|
: 'bg-sidebar text-white shadow-lg shadow-black/25 hover:bg-sidebar-accent'"
|
|
@@ -56,6 +56,9 @@
|
|
|
56
56
|
import { Menu } from "@lucide/vue";
|
|
57
57
|
import MultilevelSidebar from "./MultilevelSidebar.vue";
|
|
58
58
|
|
|
59
|
+
const LG_VIEWPORT_QUERY = "(min-width: 1024px)";
|
|
60
|
+
const MAIN_MENU_TOGGLE_EVENT = "ketekny:two-col-layout-mobile-menu";
|
|
61
|
+
|
|
59
62
|
export default {
|
|
60
63
|
name: "TwoColLayout",
|
|
61
64
|
components: {
|
|
@@ -105,14 +108,66 @@ export default {
|
|
|
105
108
|
data() {
|
|
106
109
|
return {
|
|
107
110
|
mobileMenuOpen: false,
|
|
111
|
+
desktopViewportQuery: null,
|
|
112
|
+
showMobileMenuButton: true,
|
|
113
|
+
mobileScrollHideTimer: null,
|
|
108
114
|
};
|
|
109
115
|
},
|
|
116
|
+
mounted() {
|
|
117
|
+
if (typeof window === "undefined") return;
|
|
118
|
+
this.desktopViewportQuery = window.matchMedia(LG_VIEWPORT_QUERY);
|
|
119
|
+
this.syncMenuForViewport(this.desktopViewportQuery.matches);
|
|
120
|
+
this.desktopViewportQuery.addEventListener("change", this.handleViewportChange);
|
|
121
|
+
window.addEventListener("scroll", this.handleWindowScroll, { passive: true });
|
|
122
|
+
},
|
|
123
|
+
beforeUnmount() {
|
|
124
|
+
this.desktopViewportQuery?.removeEventListener?.("change", this.handleViewportChange);
|
|
125
|
+
window.removeEventListener("scroll", this.handleWindowScroll);
|
|
126
|
+
if (this.mobileScrollHideTimer) {
|
|
127
|
+
window.clearTimeout(this.mobileScrollHideTimer);
|
|
128
|
+
this.mobileScrollHideTimer = null;
|
|
129
|
+
}
|
|
130
|
+
},
|
|
110
131
|
methods: {
|
|
132
|
+
handleWindowScroll() {
|
|
133
|
+
if (typeof window === "undefined") return;
|
|
134
|
+
if (this.mobileMenuOpen || this.desktopViewportQuery?.matches) return;
|
|
135
|
+
this.showMobileMenuButton = false;
|
|
136
|
+
if (this.mobileScrollHideTimer) {
|
|
137
|
+
window.clearTimeout(this.mobileScrollHideTimer);
|
|
138
|
+
}
|
|
139
|
+
this.mobileScrollHideTimer = window.setTimeout(() => {
|
|
140
|
+
this.showMobileMenuButton = true;
|
|
141
|
+
this.mobileScrollHideTimer = null;
|
|
142
|
+
}, 180);
|
|
143
|
+
},
|
|
144
|
+
emitMobileMenuState() {
|
|
145
|
+
if (typeof window === "undefined") return;
|
|
146
|
+
window.dispatchEvent(
|
|
147
|
+
new CustomEvent(MAIN_MENU_TOGGLE_EVENT, {
|
|
148
|
+
detail: { open: this.mobileMenuOpen },
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
},
|
|
152
|
+
handleViewportChange(event) {
|
|
153
|
+
this.syncMenuForViewport(event.matches);
|
|
154
|
+
},
|
|
155
|
+
syncMenuForViewport(isDesktopViewport) {
|
|
156
|
+
if (!isDesktopViewport) {
|
|
157
|
+
this.mobileMenuOpen = false;
|
|
158
|
+
this.emitMobileMenuState();
|
|
159
|
+
this.showMobileMenuButton = true;
|
|
160
|
+
}
|
|
161
|
+
},
|
|
111
162
|
toggleMenu() {
|
|
112
163
|
this.mobileMenuOpen = !this.mobileMenuOpen;
|
|
164
|
+
this.showMobileMenuButton = !this.mobileMenuOpen;
|
|
165
|
+
this.emitMobileMenuState();
|
|
113
166
|
},
|
|
114
167
|
closeMenu() {
|
|
115
168
|
this.mobileMenuOpen = false;
|
|
169
|
+
this.showMobileMenuButton = true;
|
|
170
|
+
this.emitMobileMenuState();
|
|
116
171
|
},
|
|
117
172
|
handleSelect(id) {
|
|
118
173
|
this.closeMenu();
|
package/src/ui/kMenu.vue
CHANGED
|
@@ -53,6 +53,13 @@
|
|
|
53
53
|
@click="handleLinkClick(link, $event)">
|
|
54
54
|
<kIcon v-if="link.icon" :name="link.icon" :size="16" class="shrink-0 self-center" :class="iconClass(link)" />
|
|
55
55
|
<span class="leading-none">{{ link.label }}</span>
|
|
56
|
+
<kIcon
|
|
57
|
+
v-if="link.checked"
|
|
58
|
+
name="Check"
|
|
59
|
+
:size="16"
|
|
60
|
+
class="ml-auto shrink-0"
|
|
61
|
+
:class="checkedIconClass(link)"
|
|
62
|
+
/>
|
|
56
63
|
</component>
|
|
57
64
|
</li>
|
|
58
65
|
</ul>
|
|
@@ -135,6 +142,10 @@ export default {
|
|
|
135
142
|
if (link?.tone === "danger") return "text-orange-500 dark:text-orange-300";
|
|
136
143
|
return "text-primary/80";
|
|
137
144
|
},
|
|
145
|
+
checkedIconClass(link) {
|
|
146
|
+
if (link?.tone === "danger") return "text-orange-500 dark:text-orange-300";
|
|
147
|
+
return "text-primary dark:text-primary";
|
|
148
|
+
},
|
|
138
149
|
handleLinkClick(link, event) {
|
|
139
150
|
if (link?.disabled) {
|
|
140
151
|
event.preventDefault();
|
package/src/ui/kToolbar.vue
CHANGED
|
@@ -37,24 +37,48 @@
|
|
|
37
37
|
</button>
|
|
38
38
|
|
|
39
39
|
<div v-if="actions.length && !isCompact" class="flex flex-wrap items-center justify-end gap-2 ml-auto shrink-0">
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
40
|
+
<template v-for="(action, index) in actions" :key="`${action.label || 'action'}-${index}`">
|
|
41
|
+
<kMenu
|
|
42
|
+
v-if="isActionGroup(action)"
|
|
43
|
+
:links="resolveActionGroupLinks(action)"
|
|
44
|
+
:aria-label="action.ariaLabel || action.label || 'Action group'"
|
|
45
|
+
>
|
|
46
|
+
<template #trigger>
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
:disabled="disabled || !!action.disabled"
|
|
50
|
+
:aria-label="action.ariaLabel || action.label"
|
|
51
|
+
:class="resolveActionGroupTriggerClass(action)"
|
|
52
|
+
>
|
|
53
|
+
<kIcon
|
|
54
|
+
v-if="action.icon"
|
|
55
|
+
:name="action.icon"
|
|
56
|
+
:size="resolveActionGroupIconSize(action)"
|
|
57
|
+
class="shrink-0"
|
|
58
|
+
/>
|
|
59
|
+
<span v-if="!action.iconOnly" class="truncate">{{ action.label }}</span>
|
|
60
|
+
<kIcon v-if="!action.iconOnly" name="ChevronDown" :size="16" class="shrink-0 opacity-80" />
|
|
61
|
+
</button>
|
|
62
|
+
</template>
|
|
63
|
+
</kMenu>
|
|
64
|
+
<kButton
|
|
65
|
+
v-else
|
|
66
|
+
:label="action.label"
|
|
67
|
+
:icon="action.icon"
|
|
68
|
+
:loading="!!action.loading"
|
|
69
|
+
:disabled="disabled || !!action.disabled"
|
|
70
|
+
:aria-label="action.ariaLabel || action.label"
|
|
71
|
+
:tooltip="action.tooltip"
|
|
72
|
+
:size="resolveActionButtonSize(action)"
|
|
73
|
+
:small="action.small === true"
|
|
74
|
+
:icon-only="!!action.iconOnly"
|
|
75
|
+
:variant="action.variant || 'outlined'"
|
|
76
|
+
:secondary="!!action.secondary"
|
|
77
|
+
:success="!!action.success"
|
|
78
|
+
:warning="!!action.warning"
|
|
79
|
+
:danger="!!action.danger"
|
|
80
|
+
@click="handleActionClick(action)" />
|
|
81
|
+
</template>
|
|
58
82
|
</div>
|
|
59
83
|
</div>
|
|
60
84
|
|
|
@@ -79,20 +103,42 @@
|
|
|
79
103
|
</div>
|
|
80
104
|
|
|
81
105
|
<div class="flex flex-col overflow-y-auto max-h-[60vh]">
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
<template v-for="(action, index) in actions" :key="`mobile-${action.label || 'action'}-${index}`">
|
|
107
|
+
<kMenu
|
|
108
|
+
v-if="isActionGroup(action)"
|
|
109
|
+
class="w-full"
|
|
110
|
+
:links="resolveActionGroupLinks(action)"
|
|
111
|
+
:aria-label="action.ariaLabel || action.label || 'Action group'"
|
|
112
|
+
>
|
|
113
|
+
<template #trigger>
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
:disabled="disabled || !!action.disabled"
|
|
117
|
+
:class="[
|
|
118
|
+
'flex items-center w-full gap-3 px-2 py-3 text-left rounded-lg min-h-11',
|
|
119
|
+
disabled || action.disabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-primary/10 active:bg-primary/15',
|
|
120
|
+
]"
|
|
121
|
+
>
|
|
122
|
+
<kIcon v-if="action.icon" :name="action.icon" :size="18" class="shrink-0 text-slate-600 dark:text-slate-400" />
|
|
123
|
+
<span class="text-sm font-medium text-slate-900 dark:text-slate-200">{{ action.label }}</span>
|
|
124
|
+
<kIcon name="ChevronDown" :size="16" class="ml-auto shrink-0 text-slate-500 dark:text-slate-400" />
|
|
125
|
+
</button>
|
|
126
|
+
</template>
|
|
127
|
+
</kMenu>
|
|
128
|
+
<button
|
|
129
|
+
v-else
|
|
130
|
+
type="button"
|
|
131
|
+
:disabled="disabled || !!action.disabled || !!action.loading"
|
|
132
|
+
:class="[
|
|
133
|
+
'flex items-center w-full gap-3 px-2 py-3 text-left rounded-lg min-h-11',
|
|
134
|
+
disabled || action.disabled || action.loading ? 'opacity-50 cursor-not-allowed' : 'hover:bg-primary/10 active:bg-primary/15',
|
|
135
|
+
]"
|
|
136
|
+
@click="handleMobileAction(action)">
|
|
137
|
+
<kIcon v-if="action.icon" :name="action.icon" :size="18" class="shrink-0 text-slate-600 dark:text-slate-400" />
|
|
138
|
+
<span class="text-sm font-medium text-slate-900 dark:text-slate-200">{{ action.label }}</span>
|
|
139
|
+
<span v-if="action.loading" class="w-4 h-4 ml-auto border-2 rounded-full border-slate-300 border-t-transparent animate-spin dark:border-slate-600"></span>
|
|
140
|
+
</button>
|
|
141
|
+
</template>
|
|
96
142
|
</div>
|
|
97
143
|
</section>
|
|
98
144
|
</div>
|
|
@@ -103,6 +149,13 @@
|
|
|
103
149
|
import { ref, watch, nextTick, onMounted, onBeforeUnmount } from "vue";
|
|
104
150
|
import kButton from "./kButton.vue";
|
|
105
151
|
import kIcon from "./kIcon.vue";
|
|
152
|
+
import kMenu from "./kMenu.vue";
|
|
153
|
+
import {
|
|
154
|
+
K_BUTTON_BASE,
|
|
155
|
+
K_BUTTON_FOCUS_RING,
|
|
156
|
+
K_BUTTON_SIZE_STYLES,
|
|
157
|
+
K_BUTTON_VARIANT_STYLES,
|
|
158
|
+
} from "./themes/kButton.theme";
|
|
106
159
|
|
|
107
160
|
const props = defineProps({
|
|
108
161
|
title: {
|
|
@@ -140,6 +193,18 @@ const isCompact = ref(false);
|
|
|
140
193
|
let resizeObserver = null;
|
|
141
194
|
|
|
142
195
|
const VALID_BUTTON_SIZES = ["small", "normal", "large"];
|
|
196
|
+
const ACTION_GROUP_KEYS = ["items", "children", "links"];
|
|
197
|
+
|
|
198
|
+
const isActionGroup = (action) =>
|
|
199
|
+
ACTION_GROUP_KEYS.some((key) => Array.isArray(action?.[key]) && action[key].length > 0);
|
|
200
|
+
|
|
201
|
+
const resolveActionGroupLinks = (action) => {
|
|
202
|
+
const items = ACTION_GROUP_KEYS.map((key) => action?.[key]).find((value) => Array.isArray(value)) || [];
|
|
203
|
+
return items.map((item) => ({
|
|
204
|
+
...item,
|
|
205
|
+
action: item?.action || item?.onClick,
|
|
206
|
+
}));
|
|
207
|
+
};
|
|
143
208
|
|
|
144
209
|
const handleActionClick = (action) => {
|
|
145
210
|
if (typeof action?.onClick === "function") action.onClick();
|
|
@@ -157,6 +222,52 @@ const resolveActionButtonSize = (action) => {
|
|
|
157
222
|
return "normal";
|
|
158
223
|
};
|
|
159
224
|
|
|
225
|
+
const resolveActionGroupIntent = (action) => {
|
|
226
|
+
if (action?.danger) return "danger";
|
|
227
|
+
if (action?.success) return "success";
|
|
228
|
+
if (action?.warning) return "warning";
|
|
229
|
+
if (action?.secondary) return "secondary";
|
|
230
|
+
return "primary";
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const resolveActionGroupVariant = (action) => {
|
|
234
|
+
if (action?.outlined) return "outlined";
|
|
235
|
+
if (action?.secondary && !action?.variant) return "soft";
|
|
236
|
+
return action?.variant || "outlined";
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const resolveActionGroupSizeKey = (action) => {
|
|
240
|
+
const size = resolveActionButtonSize(action);
|
|
241
|
+
return K_BUTTON_SIZE_STYLES[size] ? size : "normal";
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const resolveActionGroupTriggerClass = (action) => {
|
|
245
|
+
const intent = resolveActionGroupIntent(action);
|
|
246
|
+
const variant = resolveActionGroupVariant(action);
|
|
247
|
+
const sizeSet = K_BUTTON_SIZE_STYLES[resolveActionGroupSizeKey(action)];
|
|
248
|
+
const intentSet = K_BUTTON_VARIANT_STYLES[intent] || K_BUTTON_VARIANT_STYLES.primary;
|
|
249
|
+
const styleSet = intentSet[variant] || intentSet.outlined;
|
|
250
|
+
const variantClass = (action?.disabled || props.disabled) ? styleSet.disabled : styleSet.enabled;
|
|
251
|
+
const sizeClass = action?.iconOnly ? sizeSet.iconOnly : sizeSet.regular;
|
|
252
|
+
|
|
253
|
+
return [
|
|
254
|
+
K_BUTTON_BASE,
|
|
255
|
+
K_BUTTON_FOCUS_RING,
|
|
256
|
+
variantClass,
|
|
257
|
+
sizeClass,
|
|
258
|
+
"gap-2",
|
|
259
|
+
action?.fullWidth ? "w-full justify-center" : action?.iconOnly ? "" : "w-max shrink-0",
|
|
260
|
+
];
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const resolveActionGroupIconSize = (action) => {
|
|
264
|
+
const sizeSet = K_BUTTON_SIZE_STYLES[resolveActionGroupSizeKey(action)];
|
|
265
|
+
const iconClass = sizeSet?.icon || "h-4 w-4";
|
|
266
|
+
const match = /(?:^|\s)(?:size-|h-)(\d+(?:\.\d+)?)/.exec(iconClass);
|
|
267
|
+
if (!match) return 16;
|
|
268
|
+
return Number(match[1]) * 4;
|
|
269
|
+
};
|
|
270
|
+
|
|
160
271
|
const updateCompactMode = () => {
|
|
161
272
|
if (!toolbarRef.value) return;
|
|
162
273
|
isCompact.value = toolbarRef.value.clientWidth < 768;
|