@skyservice-developers/vue-dev-kit 1.5.7 → 1.5.9

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyservice-developers/vue-dev-kit",
3
- "version": "1.5.7",
3
+ "version": "1.5.9",
4
4
  "description": "Vue 2 and Vue 3 developer toolkit - components and helpers",
5
5
  "type": "module",
6
6
  "main": "./dist/vue3/vue-dev-kit.cjs",
@@ -56,16 +56,17 @@ export interface DialogEmits {
56
56
 
57
57
  export declare const Dialog: DefineComponent<DialogProps>
58
58
 
59
- // Button component
60
- export interface ButtonProps {
59
+ // SkyButton component
60
+ export interface SkyButtonProps {
61
61
  variant?: 'primary' | 'danger' | 'secondary' | 'outline'
62
62
  loading?: boolean
63
63
  disabled?: boolean
64
64
  block?: boolean
65
+ icon?: boolean
65
66
  }
66
67
 
67
- export interface ButtonSlots {
68
+ export interface SkyButtonSlots {
68
69
  'default'?: () => any
69
70
  }
70
71
 
71
- export declare const Button: DefineComponent<ButtonProps>
72
+ export declare const SkyButton: DefineComponent<SkyButtonProps>
@@ -1,7 +1,14 @@
1
1
  <template>
2
2
  <button
3
3
  class="sky-btn"
4
- :class="[`sky-btn-${variant}`, { 'sky-btn-block': block, 'sky-btn-loading': loading }]"
4
+ :class="[
5
+ `sky-btn-${variant}`,
6
+ {
7
+ 'sky-btn-block': block,
8
+ 'sky-btn-loading': loading,
9
+ 'sky-btn-icon': icon,
10
+ }
11
+ ]"
5
12
  :disabled="disabled || loading"
6
13
  v-bind="$attrs"
7
14
  >
@@ -12,7 +19,7 @@
12
19
 
13
20
  <script>
14
21
  export default {
15
- name: 'Button',
22
+ name: 'SkyButton',
16
23
  inheritAttrs: false,
17
24
  props: {
18
25
  variant: {
@@ -31,6 +38,10 @@ export default {
31
38
  block: {
32
39
  type: Boolean,
33
40
  default: false
41
+ },
42
+ icon: {
43
+ type: Boolean,
44
+ default: false
34
45
  }
35
46
  }
36
47
  }
@@ -53,6 +64,11 @@ export default {
53
64
  user-select: none;
54
65
  }
55
66
 
67
+ .sky-btn-icon {
68
+ padding: var(--sky-btn-icon-padding, 10px);
69
+ border-radius: var(--sky-btn-icon-radius, 6px);
70
+ }
71
+
56
72
  .sky-btn-block {
57
73
  width: 100%;
58
74
  }
@@ -0,0 +1,263 @@
1
+ <template>
2
+ <div
3
+ ref="root"
4
+ class="sky-select"
5
+ :class="{
6
+ 'sky-select-block': block,
7
+ 'sky-select-open': open,
8
+ 'sky-select-disabled': disabled,
9
+ }"
10
+ >
11
+ <button
12
+ type="button"
13
+ class="sky-select-trigger"
14
+ :disabled="disabled"
15
+ @click="toggle"
16
+ @keydown="onKeydown"
17
+ >
18
+ <span
19
+ class="sky-select-value"
20
+ :class="{ 'sky-select-placeholder': selectedOption === null }"
21
+ >
22
+ {{ selectedOption ? selectedOption.label : placeholder }}
23
+ </span>
24
+ <svg class="sky-select-chevron" viewBox="0 0 16 16" fill="none" aria-hidden="true">
25
+ <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
26
+ </svg>
27
+ </button>
28
+
29
+ <div v-if="open" class="sky-select-dropdown">
30
+ <div
31
+ v-for="(option, idx) in normalizedOptions"
32
+ :key="option.value"
33
+ class="sky-select-option"
34
+ :class="{
35
+ 'sky-select-option-selected': option.value === value,
36
+ 'sky-select-option-focused': idx === focusedIdx,
37
+ }"
38
+ @click="select(option)"
39
+ @mouseenter="focusedIdx = idx"
40
+ >
41
+ <span>{{ option.label }}</span>
42
+ <svg v-if="option.value === value" class="sky-select-check" viewBox="0 0 16 16" fill="none" aria-hidden="true">
43
+ <path d="M3 8l4 4 6-6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
44
+ </svg>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </template>
49
+
50
+ <script>
51
+ export default {
52
+ name: 'SkySelect',
53
+ inheritAttrs: false,
54
+ props: {
55
+ value: {
56
+ default: null,
57
+ },
58
+ options: {
59
+ type: Array,
60
+ default: () => [],
61
+ },
62
+ placeholder: {
63
+ type: String,
64
+ default: '',
65
+ },
66
+ disabled: {
67
+ type: Boolean,
68
+ default: false,
69
+ },
70
+ block: {
71
+ type: Boolean,
72
+ default: false,
73
+ },
74
+ },
75
+ data() {
76
+ return {
77
+ open: false,
78
+ focusedIdx: -1,
79
+ };
80
+ },
81
+ computed: {
82
+ normalizedOptions() {
83
+ return this.options.map((opt) =>
84
+ typeof opt === 'string' ? { label: opt, value: opt } : opt
85
+ );
86
+ },
87
+ selectedOption() {
88
+ return this.normalizedOptions.find((o) => o.value === this.value) ?? null;
89
+ },
90
+ },
91
+ methods: {
92
+ toggle() {
93
+ if (this.disabled) return;
94
+ this.open ? this.close() : this.openDropdown();
95
+ },
96
+ openDropdown() {
97
+ this.open = true;
98
+ this.focusedIdx = this.normalizedOptions.findIndex((o) => o.value === this.value);
99
+ this.$nextTick(() => document.addEventListener('mousedown', this.onOutsideClick));
100
+ },
101
+ close() {
102
+ this.open = false;
103
+ document.removeEventListener('mousedown', this.onOutsideClick);
104
+ },
105
+ select(option) {
106
+ this.$emit('input', option.value);
107
+ this.close();
108
+ },
109
+ onOutsideClick(e) {
110
+ if (!this.$refs.root.contains(e.target)) this.close();
111
+ },
112
+ onKeydown(e) {
113
+ if (!this.open) {
114
+ if (e.key === 'Enter' || e.key === ' ') {
115
+ e.preventDefault();
116
+ this.openDropdown();
117
+ }
118
+ return;
119
+ }
120
+ if (e.key === 'Escape') {
121
+ this.close();
122
+ } else if (e.key === 'ArrowDown') {
123
+ e.preventDefault();
124
+ this.focusedIdx = Math.min(this.focusedIdx + 1, this.normalizedOptions.length - 1);
125
+ } else if (e.key === 'ArrowUp') {
126
+ e.preventDefault();
127
+ this.focusedIdx = Math.max(this.focusedIdx - 1, 0);
128
+ } else if (e.key === 'Enter') {
129
+ e.preventDefault();
130
+ if (this.focusedIdx >= 0) this.select(this.normalizedOptions[this.focusedIdx]);
131
+ }
132
+ },
133
+ },
134
+ beforeDestroy() {
135
+ document.removeEventListener('mousedown', this.onOutsideClick);
136
+ },
137
+ };
138
+ </script>
139
+
140
+ <style scoped>
141
+ .sky-select {
142
+ position: relative;
143
+ display: inline-block;
144
+ font-size: var(--sky-select-font-size, 14px);
145
+ }
146
+
147
+ .sky-select-block {
148
+ display: block;
149
+ width: 100%;
150
+ }
151
+
152
+ .sky-select-block .sky-select-trigger {
153
+ width: 100%;
154
+ }
155
+
156
+ /* Trigger */
157
+ .sky-select-trigger {
158
+ display: inline-flex;
159
+ align-items: center;
160
+ justify-content: space-between;
161
+ gap: 8px;
162
+ width: 100%;
163
+ padding: var(--sky-select-padding, 10px 14px);
164
+ background: var(--sky-select-bg, #fff);
165
+ border: var(--sky-select-border, 1px solid #d1d5db);
166
+ border-radius: var(--sky-select-radius, 6px);
167
+ font-size: inherit;
168
+ font-weight: var(--sky-select-font-weight, 400);
169
+ color: var(--sky-select-color, #374151);
170
+ cursor: pointer;
171
+ user-select: none;
172
+ text-align: left;
173
+ white-space: nowrap;
174
+ }
175
+
176
+ .sky-select-trigger:hover:not(:disabled) {
177
+ border-color: var(--sky-select-border-hover, #9ca3af);
178
+ }
179
+
180
+ .sky-select-open .sky-select-trigger {
181
+ border-color: var(--sky-select-border-focus, #6b7280);
182
+ outline: none;
183
+ }
184
+
185
+ .sky-select-trigger:focus-visible {
186
+ outline: 2px solid var(--sky-select-focus-ring, #24973f);
187
+ outline-offset: 2px;
188
+ }
189
+
190
+ .sky-select-disabled .sky-select-trigger {
191
+ opacity: 0.6;
192
+ cursor: not-allowed;
193
+ }
194
+
195
+ /* Value / Placeholder */
196
+ .sky-select-value {
197
+ overflow: hidden;
198
+ text-overflow: ellipsis;
199
+ flex: 1;
200
+ min-width: 0;
201
+ }
202
+
203
+ .sky-select-placeholder {
204
+ color: var(--sky-select-placeholder-color, #9ca3af);
205
+ }
206
+
207
+ /* Chevron */
208
+ .sky-select-chevron {
209
+ width: 16px;
210
+ height: 16px;
211
+ flex-shrink: 0;
212
+ color: var(--sky-select-chevron-color, #6b7280);
213
+ transition: transform 0.15s ease;
214
+ }
215
+
216
+ .sky-select-open .sky-select-chevron {
217
+ transform: rotate(180deg);
218
+ }
219
+
220
+ /* Dropdown */
221
+ .sky-select-dropdown {
222
+ position: absolute;
223
+ top: calc(100% + 4px);
224
+ left: 0;
225
+ right: 0;
226
+ z-index: var(--sky-select-dropdown-z-index, 100);
227
+ background: var(--sky-select-dropdown-bg, #fff);
228
+ border: var(--sky-select-dropdown-border, 1px solid #d1d5db);
229
+ border-radius: var(--sky-select-dropdown-radius, 6px);
230
+ box-shadow: var(--sky-select-dropdown-shadow, 0 4px 12px rgba(0, 0, 0, 0.1));
231
+ max-height: var(--sky-select-dropdown-max-height, 220px);
232
+ overflow-y: auto;
233
+ padding: 4px;
234
+ }
235
+
236
+ /* Option */
237
+ .sky-select-option {
238
+ display: flex;
239
+ align-items: center;
240
+ justify-content: space-between;
241
+ padding: var(--sky-select-option-padding, 9px 10px);
242
+ border-radius: var(--sky-select-option-radius, 4px);
243
+ cursor: pointer;
244
+ color: var(--sky-select-option-color, #374151);
245
+ }
246
+
247
+ .sky-select-option-focused,
248
+ .sky-select-option:hover {
249
+ background: var(--sky-select-option-hover-bg, #f3f4f6);
250
+ }
251
+
252
+ .sky-select-option-selected {
253
+ color: var(--sky-select-option-selected-color, #24973f);
254
+ font-weight: 500;
255
+ }
256
+
257
+ .sky-select-check {
258
+ width: 14px;
259
+ height: 14px;
260
+ flex-shrink: 0;
261
+ color: var(--sky-select-check-color, #24973f);
262
+ }
263
+ </style>
@@ -1,5 +1,6 @@
1
1
  export { default as Header } from './Header.vue'
2
2
  export { default as Modal } from './Modal.vue'
3
3
  export { default as Dialog } from './Dialog.vue'
4
- export { default as Button } from './Button.vue'
4
+ export { default as SkyButton } from './SkyButton.vue'
5
+ export { default as SkySelect } from './SkySelect.vue'
5
6
  export { default as BaseTeleport } from './BaseTeleport.vue'
@@ -1,7 +1,14 @@
1
1
  <template>
2
2
  <button
3
3
  class="sky-btn"
4
- :class="[`sky-btn-${variant}`, { 'sky-btn-block': block, 'sky-btn-loading': loading }]"
4
+ :class="[
5
+ `sky-btn-${variant}`,
6
+ {
7
+ 'sky-btn-block': block,
8
+ 'sky-btn-loading': loading,
9
+ 'sky-btn-icon': icon,
10
+ }
11
+ ]"
5
12
  :disabled="disabled || loading"
6
13
  v-bind="$attrs"
7
14
  >
@@ -28,6 +35,10 @@ defineProps({
28
35
  block: {
29
36
  type: Boolean,
30
37
  default: false
38
+ },
39
+ icon: {
40
+ type: Boolean,
41
+ default: false
31
42
  }
32
43
  })
33
44
  </script>
@@ -49,6 +60,11 @@ defineProps({
49
60
  user-select: none;
50
61
  }
51
62
 
63
+ .sky-btn-icon {
64
+ padding: var(--sky-btn-icon-padding, 10px);
65
+ border-radius: var(--sky-btn-icon-radius, 6px);
66
+ }
67
+
52
68
  .sky-btn-block {
53
69
  width: 100%;
54
70
  }
@@ -0,0 +1,266 @@
1
+ <template>
2
+ <div
3
+ ref="root"
4
+ class="sky-select"
5
+ :class="{
6
+ 'sky-select-block': block,
7
+ 'sky-select-open': open,
8
+ 'sky-select-disabled': disabled,
9
+ }"
10
+ >
11
+ <button
12
+ type="button"
13
+ class="sky-select-trigger"
14
+ :disabled="disabled"
15
+ @click="toggle"
16
+ @keydown="onKeydown"
17
+ >
18
+ <span
19
+ class="sky-select-value"
20
+ :class="{ 'sky-select-placeholder': selectedOption === null }"
21
+ >
22
+ {{ selectedOption ? selectedOption.label : placeholder }}
23
+ </span>
24
+ <svg class="sky-select-chevron" viewBox="0 0 16 16" fill="none" aria-hidden="true">
25
+ <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
26
+ </svg>
27
+ </button>
28
+
29
+ <div v-if="open" class="sky-select-dropdown">
30
+ <div
31
+ v-for="(option, idx) in normalizedOptions"
32
+ :key="option.value"
33
+ class="sky-select-option"
34
+ :class="{
35
+ 'sky-select-option-selected': option.value === modelValue,
36
+ 'sky-select-option-focused': idx === focusedIdx,
37
+ }"
38
+ @click="select(option)"
39
+ @mouseenter="focusedIdx = idx"
40
+ >
41
+ <span>{{ option.label }}</span>
42
+ <svg v-if="option.value === modelValue" class="sky-select-check" viewBox="0 0 16 16" fill="none" aria-hidden="true">
43
+ <path d="M3 8l4 4 6-6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
44
+ </svg>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </template>
49
+
50
+ <script setup>
51
+ import { ref, computed, onBeforeUnmount } from 'vue';
52
+
53
+ const props = defineProps({
54
+ modelValue: {
55
+ default: null,
56
+ },
57
+ options: {
58
+ type: Array,
59
+ default: () => [],
60
+ },
61
+ placeholder: {
62
+ type: String,
63
+ default: '',
64
+ },
65
+ disabled: {
66
+ type: Boolean,
67
+ default: false,
68
+ },
69
+ block: {
70
+ type: Boolean,
71
+ default: false,
72
+ },
73
+ });
74
+
75
+ const emit = defineEmits(['update:modelValue']);
76
+
77
+ const root = ref(null);
78
+ const open = ref(false);
79
+ const focusedIdx = ref(-1);
80
+
81
+ const normalizedOptions = computed(() =>
82
+ props.options.map((opt) =>
83
+ typeof opt === 'string' ? { label: opt, value: opt } : opt
84
+ )
85
+ );
86
+
87
+ const selectedOption = computed(() =>
88
+ normalizedOptions.value.find((o) => o.value === props.modelValue) ?? null
89
+ );
90
+
91
+ function toggle() {
92
+ if (props.disabled) return;
93
+ open.value ? close() : openDropdown();
94
+ }
95
+
96
+ function openDropdown() {
97
+ open.value = true;
98
+ focusedIdx.value = normalizedOptions.value.findIndex((o) => o.value === props.modelValue);
99
+ document.addEventListener('mousedown', onOutsideClick);
100
+ }
101
+
102
+ function close() {
103
+ open.value = false;
104
+ document.removeEventListener('mousedown', onOutsideClick);
105
+ }
106
+
107
+ function select(option) {
108
+ emit('update:modelValue', option.value);
109
+ close();
110
+ }
111
+
112
+ function onOutsideClick(e) {
113
+ if (!root.value?.contains(e.target)) close();
114
+ }
115
+
116
+ function onKeydown(e) {
117
+ if (!open.value) {
118
+ if (e.key === 'Enter' || e.key === ' ') {
119
+ e.preventDefault();
120
+ openDropdown();
121
+ }
122
+ return;
123
+ }
124
+ if (e.key === 'Escape') {
125
+ close();
126
+ } else if (e.key === 'ArrowDown') {
127
+ e.preventDefault();
128
+ focusedIdx.value = Math.min(focusedIdx.value + 1, normalizedOptions.value.length - 1);
129
+ } else if (e.key === 'ArrowUp') {
130
+ e.preventDefault();
131
+ focusedIdx.value = Math.max(focusedIdx.value - 1, 0);
132
+ } else if (e.key === 'Enter') {
133
+ e.preventDefault();
134
+ if (focusedIdx.value >= 0) select(normalizedOptions.value[focusedIdx.value]);
135
+ }
136
+ }
137
+
138
+ onBeforeUnmount(() => {
139
+ document.removeEventListener('mousedown', onOutsideClick);
140
+ });
141
+ </script>
142
+
143
+ <style scoped>
144
+ .sky-select {
145
+ position: relative;
146
+ display: inline-block;
147
+ font-size: var(--sky-select-font-size, 14px);
148
+ }
149
+
150
+ .sky-select-block {
151
+ display: block;
152
+ width: 100%;
153
+ }
154
+
155
+ .sky-select-block .sky-select-trigger {
156
+ width: 100%;
157
+ }
158
+
159
+ /* Trigger */
160
+ .sky-select-trigger {
161
+ display: inline-flex;
162
+ align-items: center;
163
+ justify-content: space-between;
164
+ gap: 8px;
165
+ width: 100%;
166
+ padding: var(--sky-select-padding, 10px 14px);
167
+ background: var(--sky-select-bg, #fff);
168
+ border: var(--sky-select-border, 1px solid #d1d5db);
169
+ border-radius: var(--sky-select-radius, 6px);
170
+ font-size: inherit;
171
+ font-weight: var(--sky-select-font-weight, 400);
172
+ color: var(--sky-select-color, #374151);
173
+ cursor: pointer;
174
+ user-select: none;
175
+ text-align: left;
176
+ white-space: nowrap;
177
+ }
178
+
179
+ .sky-select-trigger:hover:not(:disabled) {
180
+ border-color: var(--sky-select-border-hover, #9ca3af);
181
+ }
182
+
183
+ .sky-select-open .sky-select-trigger {
184
+ border-color: var(--sky-select-border-focus, #6b7280);
185
+ outline: none;
186
+ }
187
+
188
+ .sky-select-trigger:focus-visible {
189
+ outline: 2px solid var(--sky-select-focus-ring, #24973f);
190
+ outline-offset: 2px;
191
+ }
192
+
193
+ .sky-select-disabled .sky-select-trigger {
194
+ opacity: 0.6;
195
+ cursor: not-allowed;
196
+ }
197
+
198
+ /* Value / Placeholder */
199
+ .sky-select-value {
200
+ overflow: hidden;
201
+ text-overflow: ellipsis;
202
+ flex: 1;
203
+ min-width: 0;
204
+ }
205
+
206
+ .sky-select-placeholder {
207
+ color: var(--sky-select-placeholder-color, #9ca3af);
208
+ }
209
+
210
+ /* Chevron */
211
+ .sky-select-chevron {
212
+ width: 16px;
213
+ height: 16px;
214
+ flex-shrink: 0;
215
+ color: var(--sky-select-chevron-color, #6b7280);
216
+ transition: transform 0.15s ease;
217
+ }
218
+
219
+ .sky-select-open .sky-select-chevron {
220
+ transform: rotate(180deg);
221
+ }
222
+
223
+ /* Dropdown */
224
+ .sky-select-dropdown {
225
+ position: absolute;
226
+ top: calc(100% + 4px);
227
+ left: 0;
228
+ right: 0;
229
+ z-index: var(--sky-select-dropdown-z-index, 100);
230
+ background: var(--sky-select-dropdown-bg, #fff);
231
+ border: var(--sky-select-dropdown-border, 1px solid #d1d5db);
232
+ border-radius: var(--sky-select-dropdown-radius, 6px);
233
+ box-shadow: var(--sky-select-dropdown-shadow, 0 4px 12px rgba(0, 0, 0, 0.1));
234
+ max-height: var(--sky-select-dropdown-max-height, 220px);
235
+ overflow-y: auto;
236
+ padding: 4px;
237
+ }
238
+
239
+ /* Option */
240
+ .sky-select-option {
241
+ display: flex;
242
+ align-items: center;
243
+ justify-content: space-between;
244
+ padding: var(--sky-select-option-padding, 9px 10px);
245
+ border-radius: var(--sky-select-option-radius, 4px);
246
+ cursor: pointer;
247
+ color: var(--sky-select-option-color, #374151);
248
+ }
249
+
250
+ .sky-select-option-focused,
251
+ .sky-select-option:hover {
252
+ background: var(--sky-select-option-hover-bg, #f3f4f6);
253
+ }
254
+
255
+ .sky-select-option-selected {
256
+ color: var(--sky-select-option-selected-color, #24973f);
257
+ font-weight: 500;
258
+ }
259
+
260
+ .sky-select-check {
261
+ width: 14px;
262
+ height: 14px;
263
+ flex-shrink: 0;
264
+ color: var(--sky-select-check-color, #24973f);
265
+ }
266
+ </style>
@@ -1,7 +1,8 @@
1
1
  export { default as Header } from './Header.vue'
2
2
  export { default as Dialog } from './Dialog.vue'
3
3
  export { default as Modal } from './Modal.vue'
4
- export { default as Button } from './Button.vue'
4
+ export { default as SkyButton } from './SkyButton.vue'
5
+ export { default as SkySelect } from './SkySelect.vue'
5
6
  export { default as BaseTeleport } from './BaseTeleport.vue'
6
7
 
7
8