@krometra/ui-kit 0.4.1 → 0.5.0
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/README.md +13 -4
- package/dist/ui-kit.css +1 -1
- package/dist/ui-kit.js +3017 -2912
- package/dist/ui-kit.umd.cjs +3 -3
- package/package.json +1 -1
- package/src/components/KCombobox.vue +2 -2
- package/src/components/KDrawer.vue +176 -0
- package/src/components/KGantt.vue +22 -2
- package/src/index.js +3 -2
package/package.json
CHANGED
|
@@ -85,8 +85,8 @@ const service = useMachine(combobox.machine, {
|
|
|
85
85
|
const match = itemFor(props.modelValue)
|
|
86
86
|
return match ? [keyOf(match)] : []
|
|
87
87
|
},
|
|
88
|
-
onOpenChange() {
|
|
89
|
-
filtered.value = items.value
|
|
88
|
+
onOpenChange({ open }) {
|
|
89
|
+
if (!open) filtered.value = items.value
|
|
90
90
|
},
|
|
91
91
|
onInputValueChange({ inputValue }) {
|
|
92
92
|
applyFilter(inputValue)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/**
|
|
3
|
+
* KDrawer — боковая панель на @zag-js/dialog: карточка записи, подробности,
|
|
4
|
+
* фильтры. Выезжает справа или слева, закрывается по Esc и крестику.
|
|
5
|
+
* Открытие управляется v-model.
|
|
6
|
+
*
|
|
7
|
+
* side — 'right' (по умолчанию) | 'left'
|
|
8
|
+
* size — 'sm' | 'md' | 'lg'
|
|
9
|
+
* modal — с затемнением, фокус-трапом и блокировкой скролла (по умолчанию).
|
|
10
|
+
* `:modal="false"` оставляет страницу живой: можно кликать по списку
|
|
11
|
+
* и менять содержимое панели, не закрывая её.
|
|
12
|
+
*
|
|
13
|
+
* <KDrawer v-model="open" :title="issue.key" :subtitle="issue.summary" :modal="false">
|
|
14
|
+
* …
|
|
15
|
+
* <template #actions><KButton variant="ghost" icon="external" /></template>
|
|
16
|
+
* <template #footer><KButton variant="primary">Сохранить</KButton></template>
|
|
17
|
+
* </KDrawer>
|
|
18
|
+
*/
|
|
19
|
+
import * as dialog from '@zag-js/dialog'
|
|
20
|
+
import { normalizeProps, useMachine } from '@zag-js/vue'
|
|
21
|
+
import { computed, useId } from 'vue'
|
|
22
|
+
import KIcon from './KIcon.vue'
|
|
23
|
+
|
|
24
|
+
const props = defineProps({
|
|
25
|
+
modelValue: { type: Boolean, default: false },
|
|
26
|
+
title: { type: String, default: '' },
|
|
27
|
+
subtitle: { type: String, default: '' },
|
|
28
|
+
side: { type: String, default: 'right' },
|
|
29
|
+
size: { type: String, default: 'md' },
|
|
30
|
+
modal: { type: Boolean, default: true },
|
|
31
|
+
closable: { type: Boolean, default: true },
|
|
32
|
+
closeOnOutside: { type: Boolean, default: false },
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const emit = defineEmits(['update:modelValue', 'close'])
|
|
36
|
+
|
|
37
|
+
const service = useMachine(dialog.machine, {
|
|
38
|
+
id: useId(),
|
|
39
|
+
get open() {
|
|
40
|
+
return props.modelValue
|
|
41
|
+
},
|
|
42
|
+
get modal() {
|
|
43
|
+
return props.modal
|
|
44
|
+
},
|
|
45
|
+
get trapFocus() {
|
|
46
|
+
return props.modal
|
|
47
|
+
},
|
|
48
|
+
get preventScroll() {
|
|
49
|
+
return props.modal
|
|
50
|
+
},
|
|
51
|
+
get closeOnInteractOutside() {
|
|
52
|
+
return props.modal && props.closeOnOutside && props.closable
|
|
53
|
+
},
|
|
54
|
+
get closeOnEscape() {
|
|
55
|
+
return props.closable
|
|
56
|
+
},
|
|
57
|
+
onOpenChange({ open }) {
|
|
58
|
+
emit('update:modelValue', open)
|
|
59
|
+
if (!open) emit('close')
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const api = computed(() => dialog.connect(service, normalizeProps))
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<template>
|
|
67
|
+
<Teleport to="body">
|
|
68
|
+
<Transition name="k-drawer" :duration="220">
|
|
69
|
+
<div v-if="api.open" class="k-drawer" :class="[`k-drawer--${side}`, { 'k-drawer--modal': modal }]">
|
|
70
|
+
<div v-if="modal" class="k-drawer__backdrop" v-bind="api.getBackdropProps()" />
|
|
71
|
+
<div class="k-drawer__positioner" v-bind="api.getPositionerProps()">
|
|
72
|
+
<aside class="k-drawer__panel" :class="`k-drawer__panel--${size}`" v-bind="api.getContentProps()">
|
|
73
|
+
<header class="k-drawer__header">
|
|
74
|
+
<div class="k-drawer__heading">
|
|
75
|
+
<slot name="header">
|
|
76
|
+
<h2 class="k-drawer__title" v-bind="api.getTitleProps()">{{ title }}</h2>
|
|
77
|
+
<p v-if="subtitle" class="k-drawer__subtitle" v-bind="api.getDescriptionProps()">{{ subtitle }}</p>
|
|
78
|
+
</slot>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="k-drawer__tools">
|
|
81
|
+
<slot name="actions" />
|
|
82
|
+
<button v-if="closable" class="k-drawer__close" aria-label="Закрыть" v-bind="api.getCloseTriggerProps()">
|
|
83
|
+
<KIcon name="close" :size="18" />
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
</header>
|
|
87
|
+
<div class="k-drawer__body"><slot /></div>
|
|
88
|
+
<footer v-if="$slots.footer" class="k-drawer__footer"><slot name="footer" /></footer>
|
|
89
|
+
</aside>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</Transition>
|
|
93
|
+
</Teleport>
|
|
94
|
+
</template>
|
|
95
|
+
|
|
96
|
+
<style scoped>
|
|
97
|
+
.k-drawer__backdrop {
|
|
98
|
+
position: fixed;
|
|
99
|
+
inset: 0;
|
|
100
|
+
z-index: 1000;
|
|
101
|
+
background: var(--k-overlay);
|
|
102
|
+
backdrop-filter: blur(2px);
|
|
103
|
+
}
|
|
104
|
+
.k-drawer__positioner {
|
|
105
|
+
position: fixed;
|
|
106
|
+
top: 0;
|
|
107
|
+
bottom: 0;
|
|
108
|
+
z-index: 1000;
|
|
109
|
+
display: flex;
|
|
110
|
+
pointer-events: none;
|
|
111
|
+
}
|
|
112
|
+
.k-drawer--right .k-drawer__positioner { right: 0; }
|
|
113
|
+
.k-drawer--left .k-drawer__positioner { left: 0; }
|
|
114
|
+
.k-drawer__panel {
|
|
115
|
+
pointer-events: auto;
|
|
116
|
+
width: min(100vw, var(--k-drawer-w, 32rem));
|
|
117
|
+
height: 100%;
|
|
118
|
+
display: flex;
|
|
119
|
+
flex-direction: column;
|
|
120
|
+
background: var(--k-surface);
|
|
121
|
+
box-shadow: var(--k-shadow-lg);
|
|
122
|
+
outline: none;
|
|
123
|
+
}
|
|
124
|
+
.k-drawer--right .k-drawer__panel { border-left: 1px solid var(--k-border); }
|
|
125
|
+
.k-drawer--left .k-drawer__panel { border-right: 1px solid var(--k-border); }
|
|
126
|
+
.k-drawer__panel--sm { --k-drawer-w: 24rem; }
|
|
127
|
+
.k-drawer__panel--md { --k-drawer-w: 32rem; }
|
|
128
|
+
.k-drawer__panel--lg { --k-drawer-w: 44rem; }
|
|
129
|
+
.k-drawer__header {
|
|
130
|
+
display: flex;
|
|
131
|
+
align-items: flex-start;
|
|
132
|
+
justify-content: space-between;
|
|
133
|
+
gap: 1rem;
|
|
134
|
+
padding: 1.125rem 1.25rem;
|
|
135
|
+
border-bottom: 1px solid var(--k-border);
|
|
136
|
+
}
|
|
137
|
+
.k-drawer__heading { min-width: 0; }
|
|
138
|
+
.k-drawer__title { font-size: 1.0625rem; font-weight: 600; }
|
|
139
|
+
.k-drawer__subtitle { margin-top: 0.15rem; font-size: 0.8125rem; color: var(--k-text-muted); }
|
|
140
|
+
.k-drawer__tools { display: flex; align-items: center; gap: 0.25rem; flex: none; }
|
|
141
|
+
.k-drawer__close {
|
|
142
|
+
background: none;
|
|
143
|
+
border: none;
|
|
144
|
+
color: var(--k-text-muted);
|
|
145
|
+
cursor: pointer;
|
|
146
|
+
padding: 0.15rem;
|
|
147
|
+
border-radius: var(--k-radius-sm);
|
|
148
|
+
}
|
|
149
|
+
.k-drawer__close:hover { background: var(--k-surface-muted); color: var(--k-text); }
|
|
150
|
+
.k-drawer__close:focus-visible { outline: 2px solid var(--k-primary); outline-offset: 1px; }
|
|
151
|
+
.k-drawer__body { flex: 1; padding: 1.25rem; overflow-y: auto; }
|
|
152
|
+
.k-drawer__footer {
|
|
153
|
+
display: flex;
|
|
154
|
+
align-items: center;
|
|
155
|
+
justify-content: flex-end;
|
|
156
|
+
gap: 0.5rem;
|
|
157
|
+
padding: 0.875rem 1.25rem;
|
|
158
|
+
border-top: 1px solid var(--k-border);
|
|
159
|
+
background: var(--k-surface-muted);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.k-drawer-enter-active .k-drawer__panel,
|
|
163
|
+
.k-drawer-leave-active .k-drawer__panel { transition: transform 0.22s ease; }
|
|
164
|
+
.k-drawer-enter-active .k-drawer__backdrop,
|
|
165
|
+
.k-drawer-leave-active .k-drawer__backdrop { transition: opacity 0.22s ease; }
|
|
166
|
+
.k-drawer-enter-from.k-drawer--right .k-drawer__panel,
|
|
167
|
+
.k-drawer-leave-to.k-drawer--right .k-drawer__panel { transform: translateX(100%); }
|
|
168
|
+
.k-drawer-enter-from.k-drawer--left .k-drawer__panel,
|
|
169
|
+
.k-drawer-leave-to.k-drawer--left .k-drawer__panel { transform: translateX(-100%); }
|
|
170
|
+
.k-drawer-enter-from .k-drawer__backdrop,
|
|
171
|
+
.k-drawer-leave-to .k-drawer__backdrop { opacity: 0; }
|
|
172
|
+
@media (prefers-reduced-motion: reduce) {
|
|
173
|
+
.k-drawer-enter-active .k-drawer__panel,
|
|
174
|
+
.k-drawer-leave-active .k-drawer__panel { transition: none; }
|
|
175
|
+
}
|
|
176
|
+
</style>
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
* muted — завершённая или неактуальная задача, полоса приглушена
|
|
13
13
|
* segments — несколько отрезков в одной строке (работа с перерывами)
|
|
14
14
|
* dayWidth — минимальная ширина дня в px; свободное место колонки дней делят поровну
|
|
15
|
+
* selected — key выбранной задачи, её строка подсвечивается
|
|
16
|
+
*
|
|
17
|
+
* Клик по полосе или по названию задачи — событие item-click (у полосы с href
|
|
18
|
+
* открывается ссылка).
|
|
15
19
|
*
|
|
16
20
|
* <KGantt :items="tasks" :groups="people" from="2026-10-19" to="2026-11-06"
|
|
17
21
|
* @item-click="open">
|
|
@@ -34,6 +38,7 @@ const props = defineProps({
|
|
|
34
38
|
dayWidth: { type: Number, default: 56 },
|
|
35
39
|
labelWidth: { type: String, default: '18rem' },
|
|
36
40
|
emptyText: { type: String, default: 'Нет задач в этом периоде' },
|
|
41
|
+
selected: { type: [String, Number], default: null },
|
|
37
42
|
})
|
|
38
43
|
|
|
39
44
|
const emit = defineEmits(['item-click'])
|
|
@@ -161,13 +166,22 @@ function open(item) {
|
|
|
161
166
|
</template>
|
|
162
167
|
|
|
163
168
|
<template v-else>
|
|
164
|
-
<div
|
|
169
|
+
<div
|
|
170
|
+
class="k-gantt__label k-gantt__label--item"
|
|
171
|
+
:class="{ 'k-gantt__label--muted': row.item.muted, 'k-gantt__label--selected': row.item.key === selected }"
|
|
172
|
+
:style="{ gridRow: r + 2 }"
|
|
173
|
+
role="button"
|
|
174
|
+
tabindex="0"
|
|
175
|
+
@click="open(row.item)"
|
|
176
|
+
@keydown.enter.prevent="open(row.item)"
|
|
177
|
+
@keydown.space.prevent="open(row.item)"
|
|
178
|
+
>
|
|
165
179
|
<slot name="label" :item="row.item">
|
|
166
180
|
<span v-if="row.item.code" class="k-gantt__code">{{ row.item.code }}</span>
|
|
167
181
|
<span class="k-gantt__title">{{ row.item.label }}</span>
|
|
168
182
|
</slot>
|
|
169
183
|
</div>
|
|
170
|
-
<div class="k-gantt__track" :style="{ gridRow: r + 2 }">
|
|
184
|
+
<div class="k-gantt__track" :class="{ 'k-gantt__track--selected': row.item.key === selected }" :style="{ gridRow: r + 2 }">
|
|
171
185
|
<div
|
|
172
186
|
v-for="(bar, b) in row.bars"
|
|
173
187
|
:key="b"
|
|
@@ -288,6 +302,11 @@ function open(item) {
|
|
|
288
302
|
color: var(--k-text);
|
|
289
303
|
}
|
|
290
304
|
.k-gantt__label--muted { color: var(--k-text-faint); }
|
|
305
|
+
.k-gantt__label--item { cursor: pointer; }
|
|
306
|
+
.k-gantt__label--item:hover { background: var(--k-surface-muted); }
|
|
307
|
+
.k-gantt__label--item:focus-visible { outline: 2px solid var(--k-primary); outline-offset: -2px; }
|
|
308
|
+
.k-gantt__label--selected,
|
|
309
|
+
.k-gantt__label--selected:hover { background: var(--k-primary-soft); box-shadow: inset 3px 0 0 var(--k-primary); }
|
|
291
310
|
.k-gantt__label--group {
|
|
292
311
|
padding-left: 0.5rem;
|
|
293
312
|
border: none;
|
|
@@ -324,6 +343,7 @@ function open(item) {
|
|
|
324
343
|
border-bottom: 1px solid var(--k-border);
|
|
325
344
|
}
|
|
326
345
|
.k-gantt__track--group { background: var(--k-surface-muted); }
|
|
346
|
+
.k-gantt__track--selected { background: color-mix(in srgb, var(--k-primary-soft) 55%, transparent); }
|
|
327
347
|
.k-gantt__summary {
|
|
328
348
|
position: absolute;
|
|
329
349
|
z-index: 1;
|
package/src/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import KCheckbox from './components/KCheckbox.vue'
|
|
|
25
25
|
import KCombobox from './components/KCombobox.vue'
|
|
26
26
|
import KCountdown from './components/KCountdown.vue'
|
|
27
27
|
import KDescriptions from './components/KDescriptions.vue'
|
|
28
|
+
import KDrawer from './components/KDrawer.vue'
|
|
28
29
|
import KDropdown from './components/KDropdown.vue'
|
|
29
30
|
import KEmpty from './components/KEmpty.vue'
|
|
30
31
|
import KFileUpload from './components/KFileUpload.vue'
|
|
@@ -54,7 +55,7 @@ import KTooltip from './components/KTooltip.vue'
|
|
|
54
55
|
|
|
55
56
|
export {
|
|
56
57
|
KAlert, KAvatar, KBadge, KButton, KCard, KCheckbox, KCombobox, KCountdown,
|
|
57
|
-
KDescriptions, KDropdown, KEmpty, KFileUpload, KFilterBar, KGantt, KIcon,
|
|
58
|
+
KDescriptions, KDrawer, KDropdown, KEmpty, KFileUpload, KFilterBar, KGantt, KIcon,
|
|
58
59
|
KInput, KMenuItem, KModal, KMoney, KPage, KPagination, KProgress,
|
|
59
60
|
KRadioGroup, KSegmented, KSelect, KShell, KStat, KSteps, KSwitch, KTable,
|
|
60
61
|
KTabs, KTextarea, KTimeline, KToast, KTooltip,
|
|
@@ -70,7 +71,7 @@ export * from './format.js'
|
|
|
70
71
|
|
|
71
72
|
const components = {
|
|
72
73
|
KAlert, KAvatar, KBadge, KButton, KCard, KCheckbox, KCombobox, KCountdown,
|
|
73
|
-
KDescriptions, KDropdown, KEmpty, KFileUpload, KFilterBar, KGantt, KIcon,
|
|
74
|
+
KDescriptions, KDrawer, KDropdown, KEmpty, KFileUpload, KFilterBar, KGantt, KIcon,
|
|
74
75
|
KInput, KMenuItem, KModal, KMoney, KPage, KPagination, KProgress,
|
|
75
76
|
KRadioGroup, KSegmented, KSelect, KShell, KStat, KSteps, KSwitch, KTable,
|
|
76
77
|
KTabs, KTextarea, KTimeline, KToast, KTooltip,
|