@xlui/xux-ui 0.1.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.
@@ -0,0 +1,416 @@
1
+ <template>
2
+ <div class="x-checkboxes">
3
+ <!-- 选项列表 -->
4
+ <div
5
+ class="options-container"
6
+ :class="[
7
+ `options-container--${layout}`,
8
+ { 'options-container--disabled': disabled }
9
+ ]"
10
+ :style="layout === 'grid' ? { gridTemplateColumns: `repeat(${columns}, 1fr)` } : {}"
11
+ >
12
+ <div
13
+ v-for="option in options"
14
+ :key="option.value"
15
+ class="option-item"
16
+ :class="{
17
+ 'option-selected': isSelected(option.value),
18
+ 'option-disabled': disabled || option.disabled
19
+ }"
20
+ @click="handleOptionClick(option)"
21
+ >
22
+ <div class="checkbox-wrapper">
23
+ <input
24
+ type="checkbox"
25
+ :checked="isSelected(option.value)"
26
+ :disabled="disabled || option.disabled"
27
+ @change.stop="toggleOption(option.value)"
28
+ class="checkbox-input"
29
+ />
30
+ <div class="checkbox-custom">
31
+ <svg v-if="isSelected(option.value)" class="check-icon" viewBox="0 0 24 24">
32
+ <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
33
+ </svg>
34
+ </div>
35
+ </div>
36
+ <span class="option-label">{{ option.label }}</span>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </template>
41
+
42
+ <script setup lang="ts">
43
+ /**
44
+ * Checkboxes 多选框组件
45
+ * @displayName XCheckboxes
46
+ */
47
+
48
+ export interface CheckboxOption {
49
+ value: string | number
50
+ label: string
51
+ disabled?: boolean
52
+ }
53
+
54
+ export interface CheckboxesProps {
55
+ options: CheckboxOption[]
56
+ modelValue?: (string | number)[]
57
+ placeholder?: string
58
+ maxSelections?: number
59
+ disabled?: boolean
60
+ layout?: 'vertical' | 'horizontal' | 'grid'
61
+ columns?: number
62
+ }
63
+
64
+ const props = withDefaults(defineProps<CheckboxesProps>(), {
65
+ modelValue: () => [],
66
+ placeholder: '请选择选项',
67
+ maxSelections: 0, // 0表示无限制
68
+ disabled: false,
69
+ layout: 'vertical',
70
+ columns: 3
71
+ })
72
+
73
+ const emit = defineEmits<{
74
+ (e: 'update:modelValue', value: (string | number)[]): void
75
+ (e: 'change', value: (string | number)[]): void
76
+ }>()
77
+
78
+ // 检查选项是否被选中
79
+ const isSelected = (value: string | number) => {
80
+ return props.modelValue && props.modelValue.includes(value)
81
+ }
82
+
83
+ // 切换选项
84
+ const toggleOption = (value: string | number) => {
85
+ if (props.disabled) return
86
+
87
+ const option = props.options.find(opt => opt.value === value)
88
+ if (option?.disabled) return
89
+
90
+ const newValue = [...props.modelValue]
91
+ const index = newValue.indexOf(value)
92
+
93
+ if (index > -1) {
94
+ // 移除选项
95
+ newValue.splice(index, 1)
96
+ } else {
97
+ // 添加选项
98
+ if (props.maxSelections > 0 && newValue.length >= props.maxSelections) {
99
+ return // 达到最大选择数量
100
+ }
101
+ newValue.push(value)
102
+ }
103
+
104
+ emit('update:modelValue', newValue)
105
+ emit('change', newValue)
106
+ }
107
+
108
+ // 处理选项点击
109
+ const handleOptionClick = (option: CheckboxOption) => {
110
+ if (!props.disabled && !option.disabled) {
111
+ toggleOption(option.value)
112
+ }
113
+ }
114
+
115
+ // 移除单个选项
116
+ const removeOption = (value: string | number) => {
117
+ toggleOption(value)
118
+ }
119
+
120
+ // 清除所有选项
121
+ const clearAll = () => {
122
+ if (!props.disabled) {
123
+ emit('update:modelValue', [])
124
+ emit('change', [])
125
+ }
126
+ }
127
+
128
+ // 全选
129
+ const selectAll = () => {
130
+ if (props.disabled) return
131
+
132
+ const enabledOptions = props.options.filter(opt => !opt.disabled)
133
+ const allValues = enabledOptions.map(opt => opt.value)
134
+
135
+ if (props.maxSelections > 0) {
136
+ emit('update:modelValue', allValues.slice(0, props.maxSelections))
137
+ emit('change', allValues.slice(0, props.maxSelections))
138
+ } else {
139
+ emit('update:modelValue', allValues)
140
+ emit('change', allValues)
141
+ }
142
+ }
143
+
144
+ // 暴露方法
145
+ defineExpose({
146
+ clearAll,
147
+ selectAll,
148
+ removeOption
149
+ })
150
+ </script>
151
+
152
+ <style scoped>
153
+ .x-checkboxes {
154
+ width: 100%;
155
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
156
+ }
157
+
158
+ /* 标签容器 */
159
+ .tags-container {
160
+ display: flex;
161
+ align-items: center;
162
+ gap: 8px;
163
+ padding: 12px 16px;
164
+ background-color: #f8f9fa;
165
+ border: 1px solid #e9ecef;
166
+ border-radius: 8px;
167
+ margin-bottom: 8px;
168
+ min-height: 48px;
169
+ }
170
+
171
+ .tags-wrapper {
172
+ display: flex;
173
+ flex-wrap: wrap;
174
+ gap: 6px;
175
+ flex: 1;
176
+ }
177
+
178
+ .tag-item {
179
+ display: inline-flex;
180
+ align-items: center;
181
+ gap: 6px;
182
+ padding: 4px 8px;
183
+ background-color: #1a1a1a;
184
+ color: white;
185
+ border-radius: 16px;
186
+ font-size: 14px;
187
+ font-weight: 500;
188
+ transition: all 0.2s ease;
189
+ }
190
+
191
+ .tag-item:hover {
192
+ background-color: #333333;
193
+ }
194
+
195
+ .tag-text {
196
+ white-space: nowrap;
197
+ }
198
+
199
+ .tag-remove {
200
+ display: flex;
201
+ align-items: center;
202
+ justify-content: center;
203
+ width: 16px;
204
+ height: 16px;
205
+ border: none;
206
+ background: none;
207
+ color: white;
208
+ cursor: pointer;
209
+ border-radius: 50%;
210
+ transition: background-color 0.2s ease;
211
+ }
212
+
213
+ .tag-remove:hover {
214
+ background-color: rgba(255, 255, 255, 0.2);
215
+ }
216
+
217
+ .clear-all-btn {
218
+ display: flex;
219
+ align-items: center;
220
+ justify-content: center;
221
+ width: 32px;
222
+ height: 32px;
223
+ border: none;
224
+ background: none;
225
+ color: #6c757d;
226
+ cursor: pointer;
227
+ border-radius: 6px;
228
+ transition: all 0.2s ease;
229
+ }
230
+
231
+ .clear-all-btn:hover {
232
+ background-color: #e9ecef;
233
+ color: #495057;
234
+ }
235
+
236
+ /* 选项容器 */
237
+ .options-container {
238
+ border: 1px solid var(--x-color-gray-200, #e9ecef);
239
+ border-radius: var(--x-radius-lg, 8px);
240
+ overflow: hidden;
241
+ }
242
+
243
+ /* 垂直布局(默认) */
244
+ .options-container--vertical {
245
+ display: flex;
246
+ flex-direction: column;
247
+ }
248
+
249
+ /* 水平布局 */
250
+ .options-container--horizontal {
251
+ display: flex;
252
+ flex-wrap: wrap;
253
+ gap: 12px;
254
+ border: none;
255
+ padding: 0;
256
+ }
257
+
258
+ .options-container--horizontal .option-item {
259
+ border: 1px solid var(--x-color-gray-200, #e9ecef);
260
+ border-radius: var(--x-radius-md, 6px);
261
+ flex: 0 0 auto;
262
+ }
263
+
264
+ /* 网格布局 */
265
+ .options-container--grid {
266
+ display: grid;
267
+ gap: 12px;
268
+ border: none;
269
+ padding: 0;
270
+ }
271
+
272
+ .options-container--grid .option-item {
273
+ border: 1px solid var(--x-color-gray-200, #e9ecef);
274
+ border-radius: var(--x-radius-md, 6px);
275
+ }
276
+
277
+ /* 选项项 */
278
+ .option-item {
279
+ display: flex;
280
+ align-items: center;
281
+ gap: 12px;
282
+ padding: 12px 16px;
283
+ cursor: pointer;
284
+ transition: all var(--x-transition, 0.2s ease);
285
+ border-bottom: 1px solid var(--x-color-gray-100, #f8f9fa);
286
+ user-select: none;
287
+ }
288
+
289
+ .options-container--vertical .option-item:last-child {
290
+ border-bottom: none;
291
+ }
292
+
293
+ .option-item:hover:not(.option-disabled) {
294
+ background-color: var(--x-color-gray-50, #f8f9fa);
295
+ }
296
+
297
+ .option-selected {
298
+ background-color: rgba(26, 26, 26, 0.04);
299
+ }
300
+
301
+ .option-selected:hover:not(.option-disabled) {
302
+ background-color: rgba(26, 26, 26, 0.08);
303
+ }
304
+
305
+ .option-disabled {
306
+ cursor: not-allowed;
307
+ opacity: 0.5;
308
+ }
309
+
310
+ .options-container--disabled {
311
+ opacity: 0.6;
312
+ pointer-events: none;
313
+ }
314
+
315
+ /* 复选框样式 */
316
+ .checkbox-wrapper {
317
+ position: relative;
318
+ display: flex;
319
+ align-items: center;
320
+ justify-content: center;
321
+ }
322
+
323
+ .checkbox-input {
324
+ position: absolute;
325
+ opacity: 0;
326
+ cursor: pointer;
327
+ height: 0;
328
+ width: 0;
329
+ }
330
+
331
+ .checkbox-custom {
332
+ width: 20px;
333
+ height: 20px;
334
+ border: 2px solid var(--x-color-gray-400, #d1d5db);
335
+ border-radius: var(--x-radius-sm, 4px);
336
+ background-color: white;
337
+ display: flex;
338
+ align-items: center;
339
+ justify-content: center;
340
+ transition: all var(--x-transition-fast, 0.15s ease);
341
+ position: relative;
342
+ flex-shrink: 0;
343
+ }
344
+
345
+ .option-item:hover:not(.option-disabled) .checkbox-custom {
346
+ border-color: var(--x-color-gray-500, #9ca3af);
347
+ }
348
+
349
+ .checkbox-input:checked + .checkbox-custom {
350
+ background-color: var(--x-color-primary, #1a1a1a);
351
+ border-color: var(--x-color-primary, #1a1a1a);
352
+ }
353
+
354
+ .checkbox-input:focus + .checkbox-custom {
355
+ box-shadow: 0 0 0 3px rgba(26, 26, 26, 0.1);
356
+ }
357
+
358
+ .checkbox-input:disabled + .checkbox-custom {
359
+ background-color: var(--x-color-gray-100, #f3f4f6);
360
+ border-color: var(--x-color-gray-300, #d1d5db);
361
+ cursor: not-allowed;
362
+ }
363
+
364
+ .check-icon {
365
+ width: 14px;
366
+ height: 14px;
367
+ fill: white;
368
+ opacity: 0;
369
+ transform: scale(0);
370
+ transition: all 0.2s ease;
371
+ }
372
+
373
+ .checkbox-input:checked + .checkbox-custom .check-icon {
374
+ opacity: 1;
375
+ transform: scale(1);
376
+ }
377
+
378
+ .option-label {
379
+ font-size: 14px;
380
+ color: #374151;
381
+ font-weight: 500;
382
+ flex: 1;
383
+ }
384
+
385
+ /* 响应式设计 */
386
+ @media (max-width: 768px) {
387
+ .tags-container {
388
+ padding: 10px 12px;
389
+ }
390
+
391
+ .option-item {
392
+ padding: 10px 12px;
393
+ }
394
+
395
+ .tag-item {
396
+ font-size: 13px;
397
+ padding: 3px 6px;
398
+ }
399
+ }
400
+
401
+ /* 动画效果 */
402
+ @keyframes fadeIn {
403
+ from {
404
+ opacity: 0;
405
+ transform: translateY(-4px);
406
+ }
407
+ to {
408
+ opacity: 1;
409
+ transform: translateY(0);
410
+ }
411
+ }
412
+
413
+ .tag-item {
414
+ animation: fadeIn 0.2s ease;
415
+ }
416
+ </style>