@krometra/ui-kit 0.4.0 → 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 +18 -4
- package/dist/ui-kit.css +1 -1
- package/dist/ui-kit.js +3024 -2916
- 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 +32 -8
- 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>
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* tone — 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'neutral'
|
|
12
12
|
* muted — завершённая или неактуальная задача, полоса приглушена
|
|
13
13
|
* segments — несколько отрезков в одной строке (работа с перерывами)
|
|
14
|
+
* dayWidth — минимальная ширина дня в px; свободное место колонки дней делят поровну
|
|
15
|
+
* selected — key выбранной задачи, её строка подсвечивается
|
|
16
|
+
*
|
|
17
|
+
* Клик по полосе или по названию задачи — событие item-click (у полосы с href
|
|
18
|
+
* открывается ссылка).
|
|
14
19
|
*
|
|
15
20
|
* <KGantt :items="tasks" :groups="people" from="2026-10-19" to="2026-11-06"
|
|
16
21
|
* @item-click="open">
|
|
@@ -33,6 +38,7 @@ const props = defineProps({
|
|
|
33
38
|
dayWidth: { type: Number, default: 56 },
|
|
34
39
|
labelWidth: { type: String, default: '18rem' },
|
|
35
40
|
emptyText: { type: String, default: 'Нет задач в этом периоде' },
|
|
41
|
+
selected: { type: [String, Number], default: null },
|
|
36
42
|
})
|
|
37
43
|
|
|
38
44
|
const emit = defineEmits(['item-click'])
|
|
@@ -102,13 +108,16 @@ const rows = computed(() => {
|
|
|
102
108
|
const gridStyle = computed(() => ({
|
|
103
109
|
'--k-gantt-day-w': `${props.dayWidth}px`,
|
|
104
110
|
'--k-gantt-label-w': props.labelWidth,
|
|
105
|
-
gridTemplateColumns: `var(--k-gantt-label-w) repeat(${days.value.length}, var(--k-gantt-day-w))`,
|
|
111
|
+
gridTemplateColumns: `var(--k-gantt-label-w) repeat(${days.value.length}, minmax(var(--k-gantt-day-w), 1fr))`,
|
|
106
112
|
}))
|
|
107
113
|
|
|
108
|
-
const barStyle = (bar) =>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
})
|
|
114
|
+
const barStyle = (bar) => {
|
|
115
|
+
const n = days.value.length
|
|
116
|
+
return {
|
|
117
|
+
left: `calc(${(bar.first / n) * 100}% + 3px)`,
|
|
118
|
+
width: `calc(${((bar.last - bar.first + 1) / n) * 100}% - 6px)`,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
112
121
|
|
|
113
122
|
const dayLines = computed(() => {
|
|
114
123
|
const marks = []
|
|
@@ -157,13 +166,22 @@ function open(item) {
|
|
|
157
166
|
</template>
|
|
158
167
|
|
|
159
168
|
<template v-else>
|
|
160
|
-
<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
|
+
>
|
|
161
179
|
<slot name="label" :item="row.item">
|
|
162
180
|
<span v-if="row.item.code" class="k-gantt__code">{{ row.item.code }}</span>
|
|
163
181
|
<span class="k-gantt__title">{{ row.item.label }}</span>
|
|
164
182
|
</slot>
|
|
165
183
|
</div>
|
|
166
|
-
<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 }">
|
|
167
185
|
<div
|
|
168
186
|
v-for="(bar, b) in row.bars"
|
|
169
187
|
:key="b"
|
|
@@ -218,7 +236,7 @@ function open(item) {
|
|
|
218
236
|
overflow: hidden;
|
|
219
237
|
}
|
|
220
238
|
.k-gantt__scroll { overflow: auto; max-height: inherit; }
|
|
221
|
-
.k-gantt__grid { display: grid; grid-auto-rows: minmax(2.25rem, auto); position: relative; width: max-content;
|
|
239
|
+
.k-gantt__grid { display: grid; grid-auto-rows: minmax(2.25rem, auto); position: relative; min-width: max-content; }
|
|
222
240
|
|
|
223
241
|
.k-gantt__corner,
|
|
224
242
|
.k-gantt__day {
|
|
@@ -284,6 +302,11 @@ function open(item) {
|
|
|
284
302
|
color: var(--k-text);
|
|
285
303
|
}
|
|
286
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); }
|
|
287
310
|
.k-gantt__label--group {
|
|
288
311
|
padding-left: 0.5rem;
|
|
289
312
|
border: none;
|
|
@@ -320,6 +343,7 @@ function open(item) {
|
|
|
320
343
|
border-bottom: 1px solid var(--k-border);
|
|
321
344
|
}
|
|
322
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); }
|
|
323
347
|
.k-gantt__summary {
|
|
324
348
|
position: absolute;
|
|
325
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,
|