@antify/ui 4.1.27 → 4.1.29

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.
@@ -1,6 +1,6 @@
1
1
  <script lang="ts" setup>
2
2
  import {
3
- computed, onMounted,
3
+ computed, onMounted, ref,
4
4
  } from 'vue';
5
5
  import AntField from '../forms/AntField.vue';
6
6
  import AntBaseInput from './Elements/AntBaseInput.vue';
@@ -9,9 +9,6 @@ import AntIcon from '../AntIcon.vue';
9
9
  import {
10
10
  Size,
11
11
  } from '../../enums/Size.enum';
12
- import {
13
- useVModel,
14
- } from '@vueuse/core';
15
12
  import {
16
13
  Grouped, InputState, State,
17
14
  } from '../../enums';
@@ -66,7 +63,7 @@ const emit = defineEmits([
66
63
  'update:inputRef',
67
64
  'validate',
68
65
  ]);
69
- const _modelValue = useVModel(props, 'modelValue', emit);
66
+ const _modelValue = ref<string | null>(props.modelValue);
70
67
  const _inputRef = defineModel<HTMLInputElement | null>('inputRef', {
71
68
  default: null,
72
69
  });
@@ -100,6 +97,18 @@ function onClickCalendar() {
100
97
 
101
98
  _inputRef.value?.showPicker();
102
99
  }
100
+
101
+ const onBlur = () => {
102
+ if (!_modelValue.value) {
103
+ _modelValue.value = null;
104
+
105
+ emit('update:modelValue', null);
106
+
107
+ return;
108
+ }
109
+
110
+ emit('update:modelValue', _modelValue.value);
111
+ };
103
112
  </script>
104
113
 
105
114
  <template>
@@ -127,6 +136,7 @@ function onClickCalendar() {
127
136
  :max="max"
128
137
  :grouped="_nullable ? Grouped.left : Grouped.none"
129
138
  v-bind="$attrs"
139
+ @blur="onBlur"
130
140
  @validate="val => $emit('validate', val)"
131
141
  >
132
142
  <template #icon-right>
@@ -44,9 +44,6 @@ import {
44
44
  IconSize,
45
45
  } from '../__types';
46
46
  import AntSelectMenu from './Elements/AntSelectMenu.vue';
47
- import {
48
- useVModel,
49
- } from '@vueuse/core';
50
47
 
51
48
  defineOptions({
52
49
  inheritAttrs: false,
@@ -70,6 +67,7 @@ const props = withDefaults(defineProps<{
70
67
  expanded?: boolean;
71
68
  messages?: string[];
72
69
  inputRef?: HTMLInputElement | null;
70
+ maxHeight?: string;
73
71
  }>(), {
74
72
  state: InputState.base,
75
73
  grouped: Grouped.none,
@@ -81,6 +79,7 @@ const props = withDefaults(defineProps<{
81
79
  expanded: true,
82
80
  messages: () => [],
83
81
  inputRef: null,
82
+ maxHeight: '350px',
84
83
  });
85
84
  const emit = defineEmits([
86
85
  'update:modelValue',
@@ -88,7 +87,9 @@ const emit = defineEmits([
88
87
  'blur',
89
88
  'validate',
90
89
  ]);
91
- const isOpen = ref(false);
90
+ const isOpen = defineModel<boolean>('open', {
91
+ default: false,
92
+ });
92
93
  const _modelValue = computed({
93
94
  get: () => props.modelValue,
94
95
  set: (val: string | number | null) => {
@@ -96,7 +97,15 @@ const _modelValue = computed({
96
97
  },
97
98
  });
98
99
  const hasInputState = computed(() => props.skeleton || props.readonly || props.disabled);
99
- const valueLabel = computed(() => props.options.find(option => option.value === _modelValue.value)?.label || null);
100
+
101
+ const lastValidLabel = ref<string | null>(null);
102
+
103
+ const valueLabel = computed(() => {
104
+ const found = props.options.find(option => option.value === _modelValue.value);
105
+
106
+ return found ? found.label : lastValidLabel.value;
107
+ });
108
+
100
109
  const selectedOption = computed(() => props.options.find(option => option.value === _modelValue.value) || null);
101
110
  const inputClasses = computed(() => {
102
111
  const variants: Record<InputState, string> = {
@@ -223,12 +232,20 @@ function onBlur(e: FocusEvent) {
223
232
  emit('blur', e);
224
233
  }
225
234
 
226
- function onClickOutside() {
235
+ function onClickOutside(e) {
227
236
  if (!isOpen.value) {
228
237
  return;
229
238
  }
230
239
 
240
+ const menuElement = dropDownRef.value?.floating;
241
+
242
+ if (menuElement && menuElement.contains(e.target as Node)) {
243
+ return;
244
+ }
245
+
231
246
  isOpen.value = false;
247
+
248
+ emit('validate', props.modelValue);
232
249
  _inputRef.value?.focus();
233
250
  }
234
251
 
@@ -250,6 +267,27 @@ function onClickRemoveButton() {
250
267
  _inputRef.value?.focus();
251
268
  _modelValue.value = null;
252
269
  }
270
+
271
+ function onElementSelect(value: string | number | null) {
272
+ _modelValue.value = value;
273
+
274
+ emit('validate', value);
275
+
276
+ _inputRef.value?.focus();
277
+ }
278
+
279
+ watch([
280
+ () => props.options,
281
+ () => _modelValue.value,
282
+ ], () => {
283
+ const found = props.options.find(option => option.value === _modelValue.value);
284
+
285
+ if (found) {
286
+ lastValidLabel.value = found.label;
287
+ }
288
+ }, {
289
+ immediate: true,
290
+ });
253
291
  </script>
254
292
 
255
293
  <template>
@@ -290,7 +328,13 @@ function onClickRemoveButton() {
290
328
  :size="size"
291
329
  :state="state"
292
330
  :close-on-enter="true"
331
+ :max-height="maxHeight"
332
+ @select-element="onElementSelect"
293
333
  >
334
+ <template #contentBefore>
335
+ <slot name="selectMenuContentBefore" />
336
+ </template>
337
+
294
338
  <template #contentLeft="props">
295
339
  <slot
296
340
  name="contentLeft"
@@ -305,6 +349,10 @@ function onClickRemoveButton() {
305
349
  />
306
350
  </template>
307
351
 
352
+ <template #empty>
353
+ <slot name="empty" />
354
+ </template>
355
+
308
356
  <AntSkeleton
309
357
  :visible="skeleton"
310
358
  rounded
@@ -187,7 +187,6 @@ function onBlur(e: FocusEvent) {
187
187
  <div
188
188
  v-if="hasSlotContent($slots['default'])"
189
189
  class="relative flex items-center"
190
- :class="props.size === Size.lg || props.size === Size.md ||props.size === Size.sm ? 'h-5' : 'h-4'"
191
190
  >
192
191
  <span :class="valueClasses">
193
192
  <slot />
@@ -22,7 +22,7 @@ import type {
22
22
  Validator,
23
23
  } from '@antify/validate';
24
24
  import {
25
- autoPlacement, autoUpdate, flip, offset, useFloating,
25
+ autoUpdate, flip, offset, useFloating,
26
26
  } from '@floating-ui/vue';
27
27
 
28
28
  const emit = defineEmits([
@@ -43,12 +43,14 @@ const props = withDefaults(defineProps<{
43
43
  closeOnEnter?: boolean;
44
44
  autoSelectFirstOnOpen?: boolean;
45
45
  closeOnSelectItem?: boolean;
46
+ maxHeight?: string;
46
47
  }>(), {
47
48
  state: InputState.base,
48
49
  focusOnOpen: true,
49
50
  closeOnEnter: false,
50
51
  autoSelectFirstOnOpen: true,
51
52
  closeOnSelectItem: true,
53
+ maxHeight: '350px',
52
54
  });
53
55
  const reference = ref<HTMLElement | null | undefined>(props.inputRef);
54
56
  const elementSize = useElementSize(reference);
@@ -80,32 +82,16 @@ const _modelValue = useVModel(props, 'modelValue', emit);
80
82
  const isOpen = useVModel(props, 'open', emit);
81
83
  const focusedDropDownItem = useVModel(props, 'focused', emit);
82
84
  const dropdownClasses = computed(() => {
83
- const variants: Record<InputState, string> = {
84
- [InputState.base]: 'bg-base-300 border-base-300',
85
- [InputState.success]: 'bg-success-500 border-success-500',
86
- [InputState.info]: 'bg-info-500 border-info-500',
87
- [InputState.warning]: 'bg-warning-500 border-warning-500',
88
- [InputState.danger]: 'bg-danger-500 border-danger-500',
89
- };
90
-
91
85
  return {
92
86
  'w-fit border outline-none -mt-px overflow-y-auto shadow-md z-[90] max-h-[250px]': true,
93
87
  'rounded-md': true,
94
- [variants[props.state]]: true,
88
+ 'bg-base-300 border-base-300': true,
95
89
  };
96
90
  });
97
91
  const dropDownItemClasses = computed(() => {
98
- const variants: Record<InputState, string> = {
99
- [InputState.base]: 'bg-white text-for-white-bg-font',
100
- [InputState.success]: 'bg-success-100 border-success-100-font',
101
- [InputState.info]: 'bg-info-100 border-info-100-font',
102
- [InputState.warning]: 'bg-warning-100 border-warning-100-font',
103
- [InputState.danger]: 'bg-danger-100 border-danger-100-font',
104
- };
105
-
106
92
  return {
107
93
  'flex items-center select-none text-ellipsis overflow-hidden whitespace-nowrap min-h-fit': true,
108
- [variants[props.state]]: true,
94
+ 'bg-white text-for-white-bg-font': true,
109
95
  // Size
110
96
  'p-1 text-xs gap-1': props.size === Size.xs2,
111
97
  'p-1.5 text-xs gap1.5': props.size === Size.xs,
@@ -241,17 +227,8 @@ function getActiveDropDownItemClasses(option: SelectOption) {
241
227
  return {};
242
228
  }
243
229
 
244
- const variants: Record<InputState, string> = {
245
- [InputState.base]: '!bg-base-100',
246
- [InputState.success]: 'bg-success-200',
247
- [InputState.info]: 'bg-info-200',
248
- [InputState.warning]: 'bg-warning-200',
249
- [InputState.danger]: 'bg-danger-200',
250
- };
251
-
252
230
  return option.value === focusedDropDownItem.value ? {
253
- 'bg-white': false,
254
- [variants[props.state]]: true,
231
+ '!bg-base-100': true,
255
232
  } : {};
256
233
  }
257
234
 
@@ -272,9 +249,31 @@ function onClickDropDownItem(e: MouseEvent, option: SelectOption) {
272
249
  _modelValue.value = option.value || null;
273
250
  }
274
251
 
252
+ function getOptionClasses(option: SelectOption, index: number) {
253
+ const prevOption = props.options[index - 1];
254
+
255
+ return {
256
+ ...dropDownItemClasses.value,
257
+ ...getActiveDropDownItemClasses(option),
258
+ 'cursor-pointer': !option.isGroupLabel,
259
+ 'text-base-600' : true,
260
+ 'sticky top-[-1px] z-20 font-bold bg-white': option.isGroupLabel,
261
+ 'border-y border-base-300': option.isGroupLabel,
262
+ '-mt-px': option.isGroupLabel,
263
+ 'border-t border-base-300':
264
+ !option.isGroupLabel &&
265
+ index !== 0 &&
266
+ (!prevOption || !prevOption.isGroupLabel),
267
+ };
268
+ }
269
+
275
270
  watch(_modelValue, (val) => {
276
271
  focusedDropDownItem.value = Array.isArray(val) ? val[0] : val;
277
272
  });
273
+
274
+ defineExpose({
275
+ floating,
276
+ });
278
277
  </script>
279
278
 
280
279
  <template>
@@ -288,44 +287,73 @@ watch(_modelValue, (val) => {
288
287
  <div
289
288
  v-if="isOpen"
290
289
  ref="floating"
291
- :class="dropdownClasses"
292
- :style="{minWidth: `${elementSize.width.value}px!important`, ...floatingStyles}"
290
+ :class="[
291
+ dropdownClasses,
292
+ 'flex flex-col overflow-hidden',
293
+ ]"
294
+ :style="{
295
+ minWidth: `${elementSize.width.value}px`,
296
+ maxHeight: props.maxHeight,
297
+ ...floatingStyles}"
293
298
  data-e2e="select-menu"
294
299
  :data-e2e-state="state"
295
300
  >
296
- <div class="flex flex-col gap-px">
301
+ <div
302
+ v-if="$slots.contentBefore"
303
+ class="flex-shrink-0 z-30"
304
+ >
305
+ <slot name="contentBefore" />
306
+ </div>
307
+
308
+ <div class="flex-grow overflow-y-auto min-h-0">
309
+ <div class="flex flex-col -mt-px">
310
+ <div
311
+ v-for="(option, index) in options"
312
+ :key="`option-${index}`"
313
+ data-e2e="select-menu-item"
314
+ :class="getOptionClasses(option, index)"
315
+ @click="(e) => onClickDropDownItem(e, option)"
316
+ @mouseover="() => focusedDropDownItem = !option.isGroupLabel && option.value !== undefined ? option.value : null"
317
+ >
318
+ <div class="flex items-center justify-between w-full">
319
+ <div class="flex items-center gap-2">
320
+ <slot
321
+ name="contentLeft"
322
+ v-bind="option"
323
+ />
324
+ <span>{{ option.label }}</span>
325
+ </div>
326
+
327
+ <div
328
+ v-if="option.tag"
329
+ >
330
+ <span class="px-1 py-0.5 rounded bg-base-200 text-[12px]">
331
+ {{ option.tag }}
332
+ </span>
333
+ </div>
334
+
335
+ <slot
336
+ name="contentRight"
337
+ v-bind="option"
338
+ />
339
+ </div>
340
+ </div>
341
+ </div>
342
+
297
343
  <div
298
- v-for="(option, index) in options"
299
- :key="`option-${index}`"
300
- data-e2e="select-menu-item"
301
- :class="{
302
- ...dropDownItemClasses,
303
- ...getActiveDropDownItemClasses(option),
304
- 'font-bold': option.isGroupLabel,
305
- }"
306
- @click="(e) => onClickDropDownItem(e, option)"
307
- @mouseover="() => focusedDropDownItem = !option.isGroupLabel && option.value !== undefined ? option.value : null"
344
+ v-if="options.length === 0"
345
+ :class="[
346
+ dropDownItemClasses,
347
+ {
348
+ 'flex items-center justify-center p-2 pt-2 bg-white font-medium italic text-center': $slots.contentBefore
349
+ }
350
+ ]"
308
351
  >
309
- <slot
310
- name="contentLeft"
311
- v-bind="option"
312
- />
313
- {{ option.label }}
314
- <slot
315
- name="contentRight"
316
- v-bind="option"
317
- />
352
+ <slot name="empty">
353
+ Keine Einträge vorhanden
354
+ </slot>
318
355
  </div>
319
356
  </div>
320
-
321
- <div
322
- v-if="options.length === 0"
323
- :class="{...dropDownItemClasses}"
324
- >
325
- <slot name="empty">
326
- Keine Einträge vorhanden
327
- </slot>
328
- </div>
329
357
  </div>
330
358
  </teleport>
331
359
  </div>
@@ -22,10 +22,8 @@ const meta = {
22
22
  },
23
23
  argTypes: {
24
24
  modelValue: {
25
- table: {
26
- type: {
27
- summary: "string|null"
28
- }
25
+ control: {
26
+ type: "text"
29
27
  }
30
28
  },
31
29
  type: {
@@ -23,10 +23,8 @@ const meta = {
23
23
  },
24
24
  argTypes: {
25
25
  modelValue: {
26
- table: {
27
- type: {
28
- summary: "string|null"
29
- }
26
+ control: {
27
+ type: "text"
30
28
  }
31
29
  },
32
30
  type: {
@@ -14,4 +14,5 @@ export declare const disabled: Story;
14
14
  export declare const grouped: Story;
15
15
  export declare const withPlaceholder: Story;
16
16
  export declare const ellipsisText: Story;
17
+ export declare const AdvancedCustomDropdown: Story;
17
18
  export declare const summary: Story;
@@ -3,16 +3,17 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.withPlaceholder = exports.withDeleted = exports.summary = exports.skeleton = exports.nullable = exports.manyOptions = exports.longOptions = exports.grouped = exports.ellipsisText = exports.disabled = exports.default = exports.WithSlots = exports.Docs = void 0;
7
- var _Size = require("../../../enums/Size.enum");
6
+ exports.withPlaceholder = exports.withDeleted = exports.summary = exports.skeleton = exports.nullable = exports.manyOptions = exports.longOptions = exports.grouped = exports.ellipsisText = exports.disabled = exports.default = exports.WithSlots = exports.Docs = exports.AdvancedCustomDropdown = void 0;
8
7
  var _AntSelect = _interopRequireDefault(require("../AntSelect.vue"));
9
8
  var _AntIcon = _interopRequireDefault(require("../../AntIcon.vue"));
10
9
  var _AntSelectMenu = _interopRequireDefault(require("../Elements/AntSelectMenu.vue"));
11
10
  var _freeSolidSvgIcons = require("@fortawesome/free-solid-svg-icons");
12
11
  var _vue = require("vue");
13
- var _enums = require("../../../enums");
14
12
  var _AntFormGroup = _interopRequireDefault(require("../../forms/AntFormGroup.vue"));
15
13
  var _AntFormGroupLabel = _interopRequireDefault(require("../../forms/AntFormGroupLabel.vue"));
14
+ var _AntSearch = _interopRequireDefault(require("../AntSearch.vue"));
15
+ var _AntButton = _interopRequireDefault(require("../../AntButton.vue"));
16
+ var _enums = require("../../../enums");
16
17
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
17
18
  const meta = {
18
19
  title: "Inputs/Select",
@@ -49,10 +50,10 @@ const meta = {
49
50
  control: {
50
51
  type: "select"
51
52
  },
52
- options: Object.values(_Size.Size),
53
+ options: Object.values(_enums.Size),
53
54
  table: {
54
55
  defaultValue: {
55
- summary: _Size.Size.md
56
+ summary: _enums.Size.md
56
57
  }
57
58
  }
58
59
  },
@@ -375,6 +376,259 @@ const ellipsisText = exports.ellipsisText = {
375
376
  nullable: true
376
377
  }
377
378
  };
379
+ const AdvancedCustomDropdown = exports.AdvancedCustomDropdown = {
380
+ render: args => ({
381
+ components: {
382
+ AntSelect: _AntSelect.default,
383
+ AntSearch: _AntSearch.default,
384
+ AntButton: _AntButton.default,
385
+ AntIcon: _AntIcon.default
386
+ },
387
+ setup() {
388
+ const searchTerm = (0, _vue.ref)(null);
389
+ const isSelectOpen = (0, _vue.ref)(false);
390
+ const activeFilter = (0, _vue.ref)("all");
391
+ const modelValue = (0, _vue.ref)(args.modelValue);
392
+ const rawPractitioners = [{
393
+ name: "Item 1",
394
+ type: "first",
395
+ loc: "Group Name - First"
396
+ }, {
397
+ name: "Item 1",
398
+ type: "first",
399
+ loc: "Group Name - First"
400
+ }, {
401
+ name: "Item 1",
402
+ type: "first",
403
+ loc: "Group Name - First"
404
+ }, {
405
+ name: "Item 1",
406
+ type: "first",
407
+ loc: "Group Name - First"
408
+ }, {
409
+ name: "Item 1",
410
+ type: "first",
411
+ loc: "Group Name - First"
412
+ }, {
413
+ name: "Item 1",
414
+ type: "first",
415
+ loc: "Group Name - First"
416
+ }, {
417
+ name: "Item 1",
418
+ type: "first",
419
+ loc: "Group Name - First"
420
+ }, {
421
+ name: "Item 2",
422
+ type: "second",
423
+ loc: "Group Name - Second"
424
+ }, {
425
+ name: "Item 3",
426
+ type: "third",
427
+ loc: "Group Name - Third"
428
+ }, {
429
+ name: "Item 3",
430
+ type: "third",
431
+ loc: "Group Name - Third"
432
+ }, {
433
+ name: "Item 3",
434
+ type: "third",
435
+ loc: "Group Name - Third"
436
+ }, {
437
+ name: "Item 3",
438
+ type: "third",
439
+ loc: "Group Name - Third"
440
+ }, {
441
+ name: "Item 3",
442
+ type: "third",
443
+ loc: "Group Name - Third"
444
+ }, {
445
+ name: "Item 3",
446
+ type: "third",
447
+ loc: "Group Name - Third"
448
+ }, {
449
+ name: "Item 3",
450
+ type: "third",
451
+ loc: "Group Name - Third"
452
+ }, {
453
+ name: "Item 3",
454
+ type: "third",
455
+ loc: "Group Name - Third"
456
+ }, {
457
+ name: "Item 3",
458
+ type: "third",
459
+ loc: "Group Name - Third"
460
+ }, {
461
+ name: "Item 3",
462
+ type: "third",
463
+ loc: "Group Name - Third"
464
+ }, {
465
+ name: "Item 3",
466
+ type: "third",
467
+ loc: "Group Name - Third"
468
+ }, {
469
+ name: "Item 3",
470
+ type: "third",
471
+ loc: "Group Name - Third"
472
+ }, {
473
+ name: "Item 3",
474
+ type: "third",
475
+ loc: "Group Name - Third"
476
+ }, {
477
+ name: "Item 3",
478
+ type: "third",
479
+ loc: "Group Name - Third"
480
+ }, {
481
+ name: "Item 3",
482
+ type: "third",
483
+ loc: "Group Name - Third"
484
+ }, {
485
+ name: "Item 4",
486
+ type: "fourth",
487
+ loc: "Group Name - Fourth"
488
+ }, {
489
+ name: "Item 5",
490
+ type: "fifth",
491
+ loc: "Group Name - Fifth"
492
+ }, {
493
+ name: "Item 6",
494
+ type: "sixth",
495
+ loc: "Group Name - Sixth"
496
+ }, {
497
+ name: "Item 7",
498
+ type: "seventh",
499
+ loc: "Group Name - Seventh"
500
+ }];
501
+ const typeToTag = {
502
+ first: "FIRST",
503
+ second: "SECOND",
504
+ third: "THIRD",
505
+ fourth: "FOURTH",
506
+ fifth: "FIFTH",
507
+ sixth: "SIXTH",
508
+ seventh: "SEVENTH"
509
+ };
510
+ const filteredOptions = (0, _vue.computed)(() => {
511
+ const search = searchTerm.value;
512
+ const groups = {};
513
+ rawPractitioners.forEach((p, index) => {
514
+ const matchesFilter = activeFilter.value === "all" || p.type === activeFilter.value;
515
+ const matchesSearch = !search || p.name.toLowerCase().includes(search.toLowerCase());
516
+ if (matchesFilter && matchesSearch) {
517
+ if (!groups[p.loc]) {
518
+ groups[p.loc] = [];
519
+ }
520
+ groups[p.loc].push({
521
+ label: p.name,
522
+ value: `${p.name}-${p.loc}-${index}`,
523
+ tag: typeToTag[p.type] || "Another"
524
+ });
525
+ }
526
+ });
527
+ const result = [];
528
+ Object.keys(groups).sort().forEach(loc => {
529
+ result.push({
530
+ label: loc,
531
+ value: `group-header-${loc}`,
532
+ isGroupLabel: true
533
+ });
534
+ result.push(...groups[loc]);
535
+ });
536
+ return result;
537
+ });
538
+ (0, _vue.watch)(isSelectOpen, val => {
539
+ if (!val) {
540
+ searchTerm.value = null;
541
+ }
542
+ });
543
+ return {
544
+ args,
545
+ isSelectOpen,
546
+ modelValue,
547
+ searchTerm,
548
+ activeFilter,
549
+ filteredOptions,
550
+ State: _enums.State,
551
+ GroupedEnum: _enums.Grouped
552
+ };
553
+ },
554
+ template: `
555
+ <div>
556
+ <AntSelect
557
+ v-bind="args"
558
+ v-model="modelValue"
559
+ v-model:open="isSelectOpen"
560
+ :options="filteredOptions"
561
+ >
562
+ <template #selectMenuContentBefore>
563
+ <div class="flex p-2 border-b border-base-300 bg-white gap-2">
564
+ <AntSearch v-model="searchTerm" placeholder="Search..." />
565
+
566
+ <div class="flex">
567
+ <AntButton
568
+ :state="activeFilter === 'all' ? State.primary : State.base"
569
+ :filled="activeFilter === 'all'"
570
+ :grouped="GroupedEnum.left"
571
+ @click="activeFilter = 'all'"
572
+ >All</AntButton>
573
+ <AntButton
574
+ :state="activeFilter === 'first' ? State.primary : State.base"
575
+ :filled="activeFilter === 'first'"
576
+ :grouped="GroupedEnum.center"
577
+ @click="activeFilter = 'first'"
578
+ >First</AntButton>
579
+ <AntButton
580
+ :state="activeFilter === 'second' ? State.primary : State.base"
581
+ :filled="activeFilter === 'second'"
582
+ :grouped="GroupedEnum.center"
583
+ @click="activeFilter = 'second'"
584
+ >Second</AntButton>
585
+ <AntButton
586
+ :state="activeFilter === 'third' ? State.primary : State.base"
587
+ :filled="activeFilter === 'third'"
588
+ :grouped="GroupedEnum.center"
589
+ @click="activeFilter = 'third'"
590
+ >Third</AntButton>
591
+ <AntButton
592
+ :state="activeFilter === 'fourth' ? State.primary : State.base"
593
+ :filled="activeFilter === 'fourth'"
594
+ :grouped="GroupedEnum.center"
595
+ @click="activeFilter = 'fourth'"
596
+ >Fourth</AntButton>
597
+ <AntButton
598
+ :state="activeFilter === 'fifth' ? State.primary : State.base"
599
+ :filled="activeFilter === 'fifth'"
600
+ :grouped="GroupedEnum.center"
601
+ @click="activeFilter = 'fifth'"
602
+ >Fifth</AntButton>
603
+ <AntButton
604
+ :state="activeFilter === 'sixth' ? State.primary : State.base"
605
+ :filled="activeFilter === 'sixth'"
606
+ :grouped="GroupedEnum.center"
607
+ @click="activeFilter = 'sixth'"
608
+ >Sixth</AntButton>
609
+ <AntButton
610
+ :state="activeFilter === 'seventh' ? State.primary : State.base"
611
+ :filled="activeFilter === 'seventh'"
612
+ :grouped="GroupedEnum.right"
613
+ @click="activeFilter = 'seventh'"
614
+ >Seventh</AntButton>
615
+ </div>
616
+ </div>
617
+ </template>
618
+
619
+ <template #empty>
620
+ Mock message when no match is found
621
+ </template>
622
+ </AntSelect>
623
+ </div>
624
+ `
625
+ }),
626
+ args: {
627
+ label: "Placeholder",
628
+ description: "Sticky groups, Tags and Search inside base AntSelect",
629
+ modelValue: null
630
+ }
631
+ };
378
632
  const summary = exports.summary = {
379
633
  parameters: {
380
634
  chromatic: {
@@ -1,6 +1,3 @@
1
- import {
2
- Size
3
- } from "../../../enums/Size.enum.mjs";
4
1
  import AntSelect from "../AntSelect.vue";
5
2
  import AntIcon from "../../AntIcon.vue";
6
3
  import AntDropdown from "../Elements/AntSelectMenu.vue";
@@ -10,13 +7,19 @@ import {
10
7
  import {
11
8
  computed,
12
9
  onMounted,
13
- ref
10
+ ref,
11
+ watch
14
12
  } from "vue";
15
- import {
16
- InputState
17
- } from "../../../enums/index.mjs";
18
13
  import AntFormGroup from "../../forms/AntFormGroup.vue";
19
14
  import AntFormGroupLabel from "../../forms/AntFormGroupLabel.vue";
15
+ import AntSearch from "../AntSearch.vue";
16
+ import AntButton from "../../AntButton.vue";
17
+ import {
18
+ State,
19
+ Grouped as GroupedEnum,
20
+ InputState,
21
+ Size
22
+ } from "../../../enums/index.mjs";
20
23
  const meta = {
21
24
  title: "Inputs/Select",
22
25
  component: AntSelect,
@@ -412,6 +415,287 @@ export const ellipsisText = {
412
415
  nullable: true
413
416
  }
414
417
  };
418
+ export const AdvancedCustomDropdown = {
419
+ render: (args) => ({
420
+ components: {
421
+ AntSelect,
422
+ AntSearch,
423
+ AntButton,
424
+ AntIcon
425
+ },
426
+ setup() {
427
+ const searchTerm = ref(null);
428
+ const isSelectOpen = ref(false);
429
+ const activeFilter = ref("all");
430
+ const modelValue = ref(args.modelValue);
431
+ const rawPractitioners = [
432
+ {
433
+ name: "Item 1",
434
+ type: "first",
435
+ loc: "Group Name - First"
436
+ },
437
+ {
438
+ name: "Item 1",
439
+ type: "first",
440
+ loc: "Group Name - First"
441
+ },
442
+ {
443
+ name: "Item 1",
444
+ type: "first",
445
+ loc: "Group Name - First"
446
+ },
447
+ {
448
+ name: "Item 1",
449
+ type: "first",
450
+ loc: "Group Name - First"
451
+ },
452
+ {
453
+ name: "Item 1",
454
+ type: "first",
455
+ loc: "Group Name - First"
456
+ },
457
+ {
458
+ name: "Item 1",
459
+ type: "first",
460
+ loc: "Group Name - First"
461
+ },
462
+ {
463
+ name: "Item 1",
464
+ type: "first",
465
+ loc: "Group Name - First"
466
+ },
467
+ {
468
+ name: "Item 2",
469
+ type: "second",
470
+ loc: "Group Name - Second"
471
+ },
472
+ {
473
+ name: "Item 3",
474
+ type: "third",
475
+ loc: "Group Name - Third"
476
+ },
477
+ {
478
+ name: "Item 3",
479
+ type: "third",
480
+ loc: "Group Name - Third"
481
+ },
482
+ {
483
+ name: "Item 3",
484
+ type: "third",
485
+ loc: "Group Name - Third"
486
+ },
487
+ {
488
+ name: "Item 3",
489
+ type: "third",
490
+ loc: "Group Name - Third"
491
+ },
492
+ {
493
+ name: "Item 3",
494
+ type: "third",
495
+ loc: "Group Name - Third"
496
+ },
497
+ {
498
+ name: "Item 3",
499
+ type: "third",
500
+ loc: "Group Name - Third"
501
+ },
502
+ {
503
+ name: "Item 3",
504
+ type: "third",
505
+ loc: "Group Name - Third"
506
+ },
507
+ {
508
+ name: "Item 3",
509
+ type: "third",
510
+ loc: "Group Name - Third"
511
+ },
512
+ {
513
+ name: "Item 3",
514
+ type: "third",
515
+ loc: "Group Name - Third"
516
+ },
517
+ {
518
+ name: "Item 3",
519
+ type: "third",
520
+ loc: "Group Name - Third"
521
+ },
522
+ {
523
+ name: "Item 3",
524
+ type: "third",
525
+ loc: "Group Name - Third"
526
+ },
527
+ {
528
+ name: "Item 3",
529
+ type: "third",
530
+ loc: "Group Name - Third"
531
+ },
532
+ {
533
+ name: "Item 3",
534
+ type: "third",
535
+ loc: "Group Name - Third"
536
+ },
537
+ {
538
+ name: "Item 3",
539
+ type: "third",
540
+ loc: "Group Name - Third"
541
+ },
542
+ {
543
+ name: "Item 3",
544
+ type: "third",
545
+ loc: "Group Name - Third"
546
+ },
547
+ {
548
+ name: "Item 4",
549
+ type: "fourth",
550
+ loc: "Group Name - Fourth"
551
+ },
552
+ {
553
+ name: "Item 5",
554
+ type: "fifth",
555
+ loc: "Group Name - Fifth"
556
+ },
557
+ {
558
+ name: "Item 6",
559
+ type: "sixth",
560
+ loc: "Group Name - Sixth"
561
+ },
562
+ {
563
+ name: "Item 7",
564
+ type: "seventh",
565
+ loc: "Group Name - Seventh"
566
+ }
567
+ ];
568
+ const typeToTag = {
569
+ first: "FIRST",
570
+ second: "SECOND",
571
+ third: "THIRD",
572
+ fourth: "FOURTH",
573
+ fifth: "FIFTH",
574
+ sixth: "SIXTH",
575
+ seventh: "SEVENTH"
576
+ };
577
+ const filteredOptions = computed(() => {
578
+ const search = searchTerm.value;
579
+ const groups = {};
580
+ rawPractitioners.forEach((p, index) => {
581
+ const matchesFilter = activeFilter.value === "all" || p.type === activeFilter.value;
582
+ const matchesSearch = !search || p.name.toLowerCase().includes(search.toLowerCase());
583
+ if (matchesFilter && matchesSearch) {
584
+ if (!groups[p.loc]) {
585
+ groups[p.loc] = [];
586
+ }
587
+ groups[p.loc].push({
588
+ label: p.name,
589
+ value: `${p.name}-${p.loc}-${index}`,
590
+ tag: typeToTag[p.type] || "Another"
591
+ });
592
+ }
593
+ });
594
+ const result = [];
595
+ Object.keys(groups).sort().forEach((loc) => {
596
+ result.push({
597
+ label: loc,
598
+ value: `group-header-${loc}`,
599
+ isGroupLabel: true
600
+ });
601
+ result.push(...groups[loc]);
602
+ });
603
+ return result;
604
+ });
605
+ watch(isSelectOpen, (val) => {
606
+ if (!val) {
607
+ searchTerm.value = null;
608
+ }
609
+ });
610
+ return {
611
+ args,
612
+ isSelectOpen,
613
+ modelValue,
614
+ searchTerm,
615
+ activeFilter,
616
+ filteredOptions,
617
+ State,
618
+ GroupedEnum
619
+ };
620
+ },
621
+ template: `
622
+ <div>
623
+ <AntSelect
624
+ v-bind="args"
625
+ v-model="modelValue"
626
+ v-model:open="isSelectOpen"
627
+ :options="filteredOptions"
628
+ >
629
+ <template #selectMenuContentBefore>
630
+ <div class="flex p-2 border-b border-base-300 bg-white gap-2">
631
+ <AntSearch v-model="searchTerm" placeholder="Search..." />
632
+
633
+ <div class="flex">
634
+ <AntButton
635
+ :state="activeFilter === 'all' ? State.primary : State.base"
636
+ :filled="activeFilter === 'all'"
637
+ :grouped="GroupedEnum.left"
638
+ @click="activeFilter = 'all'"
639
+ >All</AntButton>
640
+ <AntButton
641
+ :state="activeFilter === 'first' ? State.primary : State.base"
642
+ :filled="activeFilter === 'first'"
643
+ :grouped="GroupedEnum.center"
644
+ @click="activeFilter = 'first'"
645
+ >First</AntButton>
646
+ <AntButton
647
+ :state="activeFilter === 'second' ? State.primary : State.base"
648
+ :filled="activeFilter === 'second'"
649
+ :grouped="GroupedEnum.center"
650
+ @click="activeFilter = 'second'"
651
+ >Second</AntButton>
652
+ <AntButton
653
+ :state="activeFilter === 'third' ? State.primary : State.base"
654
+ :filled="activeFilter === 'third'"
655
+ :grouped="GroupedEnum.center"
656
+ @click="activeFilter = 'third'"
657
+ >Third</AntButton>
658
+ <AntButton
659
+ :state="activeFilter === 'fourth' ? State.primary : State.base"
660
+ :filled="activeFilter === 'fourth'"
661
+ :grouped="GroupedEnum.center"
662
+ @click="activeFilter = 'fourth'"
663
+ >Fourth</AntButton>
664
+ <AntButton
665
+ :state="activeFilter === 'fifth' ? State.primary : State.base"
666
+ :filled="activeFilter === 'fifth'"
667
+ :grouped="GroupedEnum.center"
668
+ @click="activeFilter = 'fifth'"
669
+ >Fifth</AntButton>
670
+ <AntButton
671
+ :state="activeFilter === 'sixth' ? State.primary : State.base"
672
+ :filled="activeFilter === 'sixth'"
673
+ :grouped="GroupedEnum.center"
674
+ @click="activeFilter = 'sixth'"
675
+ >Sixth</AntButton>
676
+ <AntButton
677
+ :state="activeFilter === 'seventh' ? State.primary : State.base"
678
+ :filled="activeFilter === 'seventh'"
679
+ :grouped="GroupedEnum.right"
680
+ @click="activeFilter = 'seventh'"
681
+ >Seventh</AntButton>
682
+ </div>
683
+ </div>
684
+ </template>
685
+
686
+ <template #empty>
687
+ Mock message when no match is found
688
+ </template>
689
+ </AntSelect>
690
+ </div>
691
+ `
692
+ }),
693
+ args: {
694
+ label: "Placeholder",
695
+ description: "Sticky groups, Tags and Search inside base AntSelect",
696
+ modelValue: null
697
+ }
698
+ };
415
699
  export const summary = {
416
700
  parameters: {
417
701
  chromatic: {
@@ -3,4 +3,5 @@ export type SelectOption = {
3
3
  value?: string | number;
4
4
  isGroupLabel?: boolean;
5
5
  isDeleted?: boolean;
6
+ tag?: string;
6
7
  } & Record<string, unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antify/ui",
3
- "version": "4.1.27",
3
+ "version": "4.1.29",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {