vueless 0.0.168 → 0.0.170

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,14 +1,22 @@
1
1
  import { unref } from "vue";
2
2
 
3
3
  function clickOutside(target, handler, options) {
4
- const { capture = true } = options;
4
+ const { capture = true, ignore = [] } = options;
5
5
 
6
6
  let shouldListen = true;
7
7
 
8
+ const ignoreList = unref(ignore).map((item) => unref(item));
8
9
  const el = unref(target);
9
10
 
10
11
  function onClick(event) {
11
- if (!el || el === event.target || event.composedPath().includes(el)) return;
12
+ if (
13
+ !el ||
14
+ el === event.target ||
15
+ event.composedPath().includes(el) ||
16
+ ignoreList.includes(event.target)
17
+ ) {
18
+ return;
19
+ }
12
20
 
13
21
  if (!shouldListen) {
14
22
  shouldListen = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "0.0.168",
3
+ "version": "0.0.170",
4
4
  "license": "MIT",
5
5
  "description": "Vue Styleless Component Framework.",
6
6
  "homepage": "https://vueless.com",
@@ -21,6 +21,7 @@
21
21
  <script setup>
22
22
  import { computed } from "vue";
23
23
 
24
+ import { cx } from "../../service.ui";
24
25
  import { formatDate, dateIsOutOfRange } from "../services/calendar.service";
25
26
  import {
26
27
  isToday,
@@ -210,33 +211,40 @@ function getDay(date, dayNumber) {
210
211
  }
211
212
 
212
213
  function getDayClasses(day) {
214
+ const isDayInRange =
215
+ props.range &&
216
+ localSelectedDate.value &&
217
+ props.selectedDateTo &&
218
+ !dateIsOutOfRange(
219
+ day,
220
+ props.selectedDate,
221
+ props.selectedDateTo,
222
+ props.locale,
223
+ props.dateFormat,
224
+ );
225
+
213
226
  const isNotSelectedDate =
214
227
  (!isSelectedDay(day) && !isSelectedToDay(day)) || props.selectedDate === null;
215
228
 
216
229
  if (isToday(day) && isNotSelectedDate) {
217
- return [currentDayAttrs.value.class];
230
+ return cx([isDayInRange && inRangeDayAttrs.value.class, currentDayAttrs.value.class]);
218
231
  }
219
232
 
220
233
  if (props.range && isSelectedDay(day)) {
221
- return [
222
- inRangeFirstDayAttrs.value.class,
234
+ return cx([
223
235
  isAnotherMothDay(day, activeMonthDate.value) && anotherMonthDayAttrs.value.class,
224
- ];
236
+ inRangeFirstDayAttrs.value.class,
237
+ ]);
225
238
  }
226
239
 
227
240
  if (props.range && isSelectedToDay(day)) {
228
- return [
229
- inRangeLastDayAttrs.value.class,
241
+ return cx([
230
242
  isAnotherMothDay(day, activeMonthDate.value) && anotherMonthDayAttrs.value.class,
231
- ];
243
+ inRangeLastDayAttrs.value.class,
244
+ ]);
232
245
  }
233
246
 
234
- if (
235
- props.range &&
236
- localSelectedDate.value &&
237
- props.selectedDateTo &&
238
- !dateIsOutOfRange(day, props.selectedDate, props.selectedDateTo, props.locale, props.dateFormat)
239
- ) {
247
+ if (isDayInRange) {
240
248
  return [inRangeDayAttrs.value.class];
241
249
  }
242
250
 
@@ -64,7 +64,7 @@ Timepicker.args = { value: new Date(2024, 2, 14, 12, 24, 14), timepicker: true }
64
64
 
65
65
  export const MinMax = DefaultTemplate.bind({});
66
66
  MinMax.args = {
67
- minDate: "2022-02-22",
67
+ minDate: new Date(2022, 2, 22),
68
68
  maxDate: new Date(2022, 2, 26),
69
69
  value: new Date(2022, 2, 24),
70
70
  };
@@ -579,17 +579,23 @@ function onInputDate(newDate) {
579
579
  const isFullReset =
580
580
  localValue.value.to || !localValue.value.from || date <= localValue.value.from;
581
581
 
582
- localValue.value = isFullReset
582
+ const updatedValue = isFullReset
583
583
  ? { from: date, to: null }
584
584
  : { from: localValue.value.from, to: date };
585
+
586
+ localValue.value = updatedValue;
587
+
588
+ emit("input", updatedValue);
585
589
  } else {
586
590
  localValue.value = date;
587
- }
588
591
 
589
- activeDate.value = null;
590
- activeMonth.value = null;
592
+ emit("input", localValue.value);
593
+ }
591
594
 
592
- emit("input", localValue.value);
595
+ if (!props.range) {
596
+ activeDate.value = null;
597
+ activeMonth.value = null;
598
+ }
593
599
 
594
600
  wrapperRef.value.focus();
595
601
  }
@@ -102,7 +102,6 @@ export default /*tw*/ {
102
102
  },
103
103
  },
104
104
  day: "font-medium w-full h-10 text-sm mb-0.5",
105
- currentDay: "text-white bg-brand-900 hover:bg-brand-900 hover:text-white",
106
105
  weekDay: "text-sm size-10",
107
106
  month: "font-medium",
108
107
  selectedMonth: "bg-zinc-100 text-brand-900 hover:text-white",
@@ -119,6 +118,8 @@ export default /*tw*/ {
119
118
  dateFormatWithDot: "Date should be in format 'dd.mm.yyyy'.",
120
119
  notCorrectMonthNumber: "Wrong month number.",
121
120
  notCorrectDayNumber: "Wrong day in month.",
121
+ fromDateGraterThanSecond: "The first date should be less than the second.",
122
+ toDateSmallerThanFirst: "The second date should be greater than the first.",
122
123
  weekdays: {
123
124
  shorthand: {
124
125
  sunday: "Sun",
@@ -29,6 +29,7 @@
29
29
 
30
30
  <div v-if="isVariant.button" v-bind="buttonWrapperAttrs">
31
31
  <UButton
32
+ ref="buttonPrevRef"
32
33
  square
33
34
  :size="size"
34
35
  :disabled="disabled"
@@ -40,6 +41,7 @@
40
41
 
41
42
  <UButton
42
43
  :id="id"
44
+ ref="buttonRef"
43
45
  :size="size"
44
46
  :disabled="disabled"
45
47
  :label="userFormatDate"
@@ -48,6 +50,7 @@
48
50
  />
49
51
 
50
52
  <UButton
53
+ ref="buttonNextRef"
51
54
  square
52
55
  :size="size"
53
56
  :disabled="disabled"
@@ -62,11 +65,10 @@
62
65
  <div
63
66
  v-if="isShownMenu"
64
67
  ref="menuRef"
68
+ v-click-outside="[deactivate, clickOutsideOptions]"
65
69
  tabindex="-1"
66
70
  v-bind="menuAttrs"
67
- @blur="onBlur"
68
71
  @keydown.esc="deactivate"
69
- @mouseleave="onMouseoutMenu"
70
72
  >
71
73
  <div v-bind="periodsRowAttrs">
72
74
  <div
@@ -146,28 +148,26 @@
146
148
  <UInput
147
149
  ref="rangeInputStartRef"
148
150
  v-model="rangeStart"
149
- :error="inputRangeStartError"
151
+ :error="inputRangeFromError"
150
152
  size="sm"
151
153
  v-bind="rangeInputAttrs"
152
154
  :name="rangeInputName"
153
155
  @input="onInputRangeInput($event, INPUT_RANGE_TYPE.start)"
154
- @blur="onBlurRangeInput"
155
156
  />
156
157
 
157
158
  <UInput
158
159
  ref="rangeInputEndRef"
159
160
  v-model="rangeEnd"
160
- :error="inputRangeEndError"
161
+ :error="inputRangeToError"
161
162
  size="sm"
162
163
  v-bind="rangeInputAttrs"
163
164
  :name="rangeInputName"
164
165
  @input="onInputRangeInput($event, INPUT_RANGE_TYPE.end)"
165
- @blur="onBlurRangeInput"
166
166
  />
167
167
  </div>
168
168
 
169
169
  <div v-bind="inputRangeErrorAttrs">
170
- {{ inputRangeEndError || inputRangeStartError }}
170
+ {{ inputRangeToError || inputRangeFromError }}
171
171
  </div>
172
172
 
173
173
  <UCalendar
@@ -179,6 +179,7 @@
179
179
  :date-format="dateFormat"
180
180
  range
181
181
  @mouseenter="onMouseoverCalendar"
182
+ @input="onInputCalendar"
182
183
  />
183
184
  </div>
184
185
  </Transition>
@@ -195,6 +196,8 @@ import UButton from "../ui.button";
195
196
  import UCalendar from "../ui.form-calendar";
196
197
  import { LOCALE_TYPE } from "../ui.form-calendar/constants";
197
198
 
199
+ import vClickOutside from "../directive.clickOutside";
200
+
198
201
  import UIService, { getRandomId } from "../service.ui";
199
202
 
200
203
  import {
@@ -227,7 +230,11 @@ import {
227
230
  getMonthsDateList,
228
231
  } from "./services/dateRange.service";
229
232
 
230
- import { wrongDateFormat, wrongMonthNumber, wrongDayNumber } from "./services/validation.service";
233
+ import {
234
+ isWrongDateFormat,
235
+ isWrongMonthNumber,
236
+ isWrongDayNumber,
237
+ } from "./services/validation.service";
231
238
  import useAttrs from "./composables/attrs.composable";
232
239
  import { useLocale } from "../composable.locale";
233
240
  import useBreakpoint from "../composable.breakpoint";
@@ -395,6 +402,8 @@ const props = defineProps({
395
402
  },
396
403
  });
397
404
 
405
+ const inputRangeFormat = "d.m.Y";
406
+
398
407
  const emit = defineEmits(["update:modelValue"]);
399
408
 
400
409
  const isShownMenu = ref(false);
@@ -402,6 +411,9 @@ const wrapperRef = ref(null);
402
411
  const menuRef = ref(null);
403
412
  const rangeInputStartRef = ref(null);
404
413
  const rangeInputEndRef = ref(null);
414
+ const buttonRef = ref(null);
415
+ const buttonPrevRef = ref(null);
416
+ const buttonNextRef = ref(null);
405
417
 
406
418
  const { isTop, isRight, adjustPositionY, adjustPositionX } = useAdjustElementPosition(
407
419
  wrapperRef,
@@ -444,6 +456,10 @@ const i18nGlobal = tm(UDatePickerRange);
444
456
 
445
457
  const currentLocale = computed(() => merge(defaultConfig.i18n, i18nGlobal, props.config.i18n));
446
458
 
459
+ const clickOutsideOptions = computed(() => ({
460
+ ignore: [buttonRef.value.buttonRef, buttonPrevRef.value.buttonRef, buttonNextRef.value.buttonRef],
461
+ }));
462
+
447
463
  const locale = computed(() => {
448
464
  const { months, weekdays } = currentLocale.value;
449
465
 
@@ -470,9 +486,9 @@ const activeDate = ref(
470
486
  const period = ref(PERIOD.ownRange);
471
487
  const rangeStart = ref("");
472
488
  const rangeEnd = ref("");
473
- const inputRangeStartError = ref("");
474
- const inputRangeEndError = ref("");
475
- const isHoverEvent = ref(false);
489
+ const inputRangeFromError = ref("");
490
+ const inputRangeToError = ref("");
491
+ const calendarInnerValue = ref({ from: "", to: "" });
476
492
  const periodDateList = ref(null);
477
493
 
478
494
  const { isMobileBreakpoint } = useBreakpoint();
@@ -611,9 +627,19 @@ const userFormatDate = computed(() => {
611
627
  startYear = "";
612
628
  }
613
629
 
614
- const fromTitle = isSameMonth(from, to)
615
- ? from.getDate()
616
- : `${from.getDate()} ${startMonthName} ${startYear}`;
630
+ const isDatesToSameMonth = isSameMonth(from, to);
631
+ const isDatesToSameYear = from.getFullYear() === to.getFullYear();
632
+
633
+ let fromTitle = `${from.getDate()} ${startMonthName} ${startYear}`;
634
+
635
+ if (isDatesToSameMonth && isDatesToSameYear) {
636
+ fromTitle = from.getDate();
637
+ }
638
+
639
+ if (!isDatesToSameMonth && isDatesToSameYear) {
640
+ fromTitle = `${from.getDate()} ${startMonthName}`;
641
+ }
642
+
617
643
  const toTitle = to ? `${to.getDate()} ${endMonthName} ${endYear}` : "";
618
644
 
619
645
  title = `${fromTitle} – ${toTitle}`;
@@ -680,13 +706,15 @@ watch(
680
706
  const parsedDateTo = parseDate(props.modelValue.to, props.dateFormat, locale.value);
681
707
 
682
708
  rangeStart.value = props.modelValue.from
683
- ? formatDate(parsedDateFrom, "d.m.Y", locale.value)
709
+ ? formatDate(parsedDateFrom, inputRangeFormat, locale.value)
684
710
  : "";
685
711
 
686
- rangeEnd.value = props.modelValue.to ? formatDate(parsedDateTo, "d.m.Y", locale.value) : "";
712
+ rangeEnd.value = props.modelValue.to
713
+ ? formatDate(parsedDateTo, inputRangeFormat, locale.value)
714
+ : "";
687
715
 
688
- inputRangeStartError.value = "";
689
- inputRangeEndError.value = "";
716
+ inputRangeFromError.value = "";
717
+ inputRangeToError.value = "";
690
718
  },
691
719
  { deep: true, immediate: true },
692
720
  );
@@ -820,72 +848,75 @@ function activate() {
820
848
 
821
849
  function deactivate() {
822
850
  isShownMenu.value = false;
823
-
824
- isHoverEvent.value = false;
825
851
  }
826
852
 
827
- function onBlur(event) {
828
- const { relatedTarget } = event;
853
+ function isGraterThanTo(value) {
854
+ if (!value) return false;
829
855
 
830
- if (!menuRef.value?.contains(relatedTarget)) {
831
- deactivate();
832
- }
833
- }
856
+ const parsedValue = parseDate(value, inputRangeFormat, locale.value);
857
+ const parsedTo = parseDate(localValue.value.to, props.dateFormat, locale.value);
834
858
 
835
- function onBlurRangeInput(event) {
836
- const { relatedTarget } = event;
837
- const isRangeInputFocus = relatedTarget?.name === rangeInputName.value;
859
+ return parsedValue > parsedTo;
860
+ }
838
861
 
839
- if (!isRangeInputFocus) {
840
- menuRef.value.focus();
841
- }
862
+ function isSmallerThanFrom(value) {
863
+ if (!value) return false;
842
864
 
843
- if (!menuRef.value?.contains(relatedTarget) && !isHoverEvent.value) {
844
- deactivate();
845
- }
865
+ const parsedValue = parseDate(value, inputRangeFormat, locale.value);
866
+ const parsedFrom = parseDate(localValue.value.from, props.dateFormat, locale.value);
846
867
 
847
- isHoverEvent.value = false;
868
+ return parsedValue < parsedFrom;
848
869
  }
849
870
 
850
871
  function onInputRangeInput(value, type) {
851
- const isInvalidDateFormat = !wrongDateFormat(value);
852
- const isInvalidMonthNumber = !wrongMonthNumber(value);
853
- const isInvalidDayNumber = !wrongDayNumber(value);
872
+ const isInvalidDateFormat = isWrongDateFormat(value);
854
873
 
855
874
  let error = "";
856
875
 
857
876
  if (isInvalidDateFormat && value) {
858
877
  error = locale.value.dateFormatWithDot;
859
- } else if (isInvalidMonthNumber && value) {
878
+ } else if (isWrongMonthNumber(value) && value) {
860
879
  error = locale.value.notCorrectMonthNumber;
861
- } else if (isInvalidDayNumber && value) {
880
+ } else if (isWrongDayNumber(value) && value) {
862
881
  error = locale.value.notCorrectDayNumber;
882
+ } else if (isGraterThanTo(value) && type === INPUT_RANGE_TYPE.start) {
883
+ error = locale.value.fromDateGraterThanSecond;
884
+ } else if (isSmallerThanFrom(value) && type === INPUT_RANGE_TYPE.end) {
885
+ error = locale.value.toDateSmallerThanFirst;
863
886
  }
864
887
 
865
888
  if (type === INPUT_RANGE_TYPE.start) {
866
- inputRangeStartError.value = error;
889
+ inputRangeFromError.value = error;
867
890
  }
868
891
 
869
892
  if (type === INPUT_RANGE_TYPE.end) {
870
- inputRangeEndError.value = error;
893
+ inputRangeToError.value = error;
871
894
  }
872
895
 
873
- const parsedValue = parseDate(value || new Date(), props.dateFormat, locale.value);
874
- const isOutOfRange = dateIsOutOfRange(
875
- parsedValue,
876
- props.minDate,
877
- props.maxDate,
878
- locale.value,
879
- props.dateFormat,
880
- );
881
- const isToLessThanFrom = parsedValue <= localValue.value.from;
896
+ if (!isInvalidDateFormat) {
897
+ const parsedValue = parseDate(value || new Date(), inputRangeFormat, locale.value);
882
898
 
883
- if (type === INPUT_RANGE_TYPE.start && !error && !isOutOfRange) {
884
- localValue.value.from = value ? parsedValue : "";
885
- }
899
+ const isOutOfRange = dateIsOutOfRange(
900
+ parsedValue,
901
+ props.minDate,
902
+ props.maxDate,
903
+ locale.value,
904
+ props.dateFormat,
905
+ );
906
+
907
+ if (type === INPUT_RANGE_TYPE.start && !error && !isOutOfRange) {
908
+ localValue.value = {
909
+ from: value ? parsedValue : "",
910
+ to: localValue.value.to,
911
+ };
912
+ }
886
913
 
887
- if (type === INPUT_RANGE_TYPE.end && !error && !isOutOfRange && !isToLessThanFrom) {
888
- localValue.value.to = value ? parsedValue : "";
914
+ if (type === INPUT_RANGE_TYPE.end && !error && !isOutOfRange) {
915
+ localValue.value = {
916
+ from: localValue.value.from,
917
+ to: value ? parsedValue : "",
918
+ };
919
+ }
889
920
  }
890
921
  }
891
922
 
@@ -1009,26 +1040,25 @@ function onMouseoverCalendar() {
1009
1040
 
1010
1041
  if (isRangeInputFocus || !rangeInputStartRef.value || !rangeInputEndRef.value) return;
1011
1042
 
1012
- if ((rangeStart.value && rangeEnd.value && !inputRangeEndError.value) || !rangeStart.value) {
1043
+ const hasValues =
1044
+ calendarInnerValue.value.from && calendarInnerValue.value.to && !inputRangeToError.value;
1045
+ const hasOnlyFromValue =
1046
+ calendarInnerValue.value.from && !calendarInnerValue.value.to && !inputRangeFromError.value;
1047
+
1048
+ if (hasValues || !rangeStart.value) {
1013
1049
  rangeInputStartRef.value.input.focus();
1014
1050
 
1015
1051
  return;
1016
1052
  }
1017
1053
 
1018
- if (rangeStart.value && !rangeEnd.value && !inputRangeStartError.value) {
1054
+ if (hasOnlyFromValue) {
1019
1055
  rangeInputEndRef.value.input.focus();
1020
1056
 
1021
1057
  return;
1022
1058
  }
1023
1059
  }
1024
1060
 
1025
- function onMouseoutMenu() {
1026
- const isRangeInputFocus = document.activeElement.name === rangeInputName.value;
1027
-
1028
- if (isRangeInputFocus) {
1029
- isHoverEvent.value = true;
1030
-
1031
- document.activeElement.blur();
1032
- }
1061
+ function onInputCalendar(value) {
1062
+ calendarInnerValue.value = value;
1033
1063
  }
1034
1064
  </script>
@@ -1,22 +1,22 @@
1
1
  import { getDaysInMonth } from "../../ui.form-calendar/services/date.service";
2
2
 
3
- const DATE_WITH_DOT_FORMAT_REG_EXP = /^\d{2}([./-])\d{2}\1\d{4}$/;
3
+ const datePattern = /^\d{1,2}\.\d{1,2}\.\d{4}$/;
4
4
 
5
- export function wrongDateFormat(value) {
6
- return !!value.match(DATE_WITH_DOT_FORMAT_REG_EXP);
5
+ export function isWrongDateFormat(value) {
6
+ return !datePattern.test(value);
7
7
  }
8
8
 
9
- export function wrongMonthNumber(value) {
9
+ export function isWrongMonthNumber(value) {
10
10
  const splitDate = value.split(".");
11
11
  const month = splitDate[1];
12
12
 
13
- return Number(month) >= 1 && Number(month) <= 12;
13
+ return !(Number(month) >= 1 && Number(month) <= 12);
14
14
  }
15
15
 
16
- export function wrongDayNumber(value) {
16
+ export function isWrongDayNumber(value) {
17
17
  const [day, month, year] = value.split(".");
18
18
 
19
19
  const daysInMonth = getDaysInMonth(new Date(year, month - 1));
20
20
 
21
- return Number(day) >= 1 && Number(day) <= Number(daysInMonth);
21
+ return !(Number(day) >= 1 && Number(day) <= Number(daysInMonth));
22
22
  }
@@ -28,6 +28,7 @@ export function useAttrs(props) {
28
28
  cvaWrapper({
29
29
  color: getColor(props.color),
30
30
  bordered: props.bordered,
31
+ variant: props.variant,
31
32
  }),
32
33
  props.color,
33
34
  ),
@@ -37,6 +38,9 @@ export function useAttrs(props) {
37
38
 
38
39
  const wrapperAttrs = getAttrs("wrapper", { classes: wrapperClasses });
39
40
  const bodyAttrs = getAttrs("body", { classes: bodyClasses });
41
+ const titleAttrs = getAttrs("title");
42
+ const descriptionAttrs = getAttrs("description");
43
+
40
44
  const buttonAttrs = getAttrs("button", { isComponent: true });
41
45
  const iconAttrs = getAttrs("icon", { isComponent: true });
42
46
 
@@ -47,5 +51,7 @@ export function useAttrs(props) {
47
51
  buttonAttrs,
48
52
  iconAttrs,
49
53
  hasSlotContent,
54
+ titleAttrs,
55
+ descriptionAttrs,
50
56
  };
51
57
  }
@@ -1,19 +1,21 @@
1
1
  export default /*tw*/ {
2
2
  wrapper: {
3
- base: "p-4 space-x-4 flex justify-between items-start rounded-lg bg-{color}-50 text-{color}-700",
3
+ base: "p-4 flex flex-col rounded-lg",
4
4
  variants: {
5
- color: {
6
- grayscale: "bg-gray-100 text-gray-900",
7
- },
8
- bordered: {
9
- true: "border border-{color}-100",
5
+ variant: {
6
+ primary: `bg-{color}-500 text-white`,
7
+ secondary: `bg-transparent border border-{color}-500`,
8
+ thirdary: `bg-{color}-50 text-{color}-700`,
10
9
  },
11
10
  },
12
- compoundVariants: [{ color: "grayscale", bordered: true, class: "border-gray-200" }],
11
+ compoundVariants: [
12
+ { color: "grayscale", bordered: true, class: "border-gray-200" },
13
+ { variant: "thirdary", bordered: true, class: "border border-{color}-100" },
14
+ ],
13
15
  },
14
16
  body: {
15
17
  base: `
16
- space-y-4 font-normal leading-normal
18
+ flex gap-2 items-baseline font-normal
17
19
  [&_b]:font-bold [&_i]:italic [&_p]:font-normal
18
20
  [&_a:not([class])]:underline [&_a:not([class])]:underline-offset-4
19
21
  [&_a:not([class]):hover]:no-underline [&_a:not([class])]:font-bold
@@ -32,20 +34,25 @@ export default /*tw*/ {
32
34
  },
33
35
  },
34
36
  },
37
+ title: "font-bold text-lg leading-tight",
38
+ description: "text-sm",
35
39
  button: "",
36
40
  icon: "",
37
41
  iconName: "close",
38
42
  defaultVariants: {
39
- color: "grayscale",
43
+ variant: "thirdary",
44
+ color: "brand",
40
45
  size: "md",
41
46
  timeout: 0,
42
47
  html: undefined,
43
48
  bordered: false,
44
- closeIcon: false,
49
+ closable: false,
45
50
  },
46
51
  safelist: (colors) => [
47
52
  { pattern: `bg-(${colors})-50` },
53
+ { pattern: `bg-(${colors})-500` },
48
54
  { pattern: `text-(${colors})-700` },
49
55
  { pattern: `border-(${colors})-100` },
56
+ { pattern: `border-(${colors})-500` },
50
57
  ],
51
58
  };
@@ -2,6 +2,8 @@ import { getArgTypes, getSlotNames } from "../service.storybook";
2
2
 
3
3
  import UAlert from "../ui.text-alert";
4
4
  import URow from "../ui.container-row";
5
+ import UGroup from "../ui.container-group";
6
+ import UIcon from "../ui.image-icon";
5
7
 
6
8
  /**
7
9
  * The `UAlert` component. | [View on GitHub](https://github.com/vuelessjs/vueless/tree/main/src/ui.text-alert)
@@ -12,7 +14,7 @@ export default {
12
14
  component: UAlert,
13
15
  args: {
14
16
  text: "UHint",
15
- slotTemplate: `
17
+ slotDefaultTemplate: `
16
18
  <template #default>
17
19
  <p>
18
20
  <b>Lorem ipsum dolor sit amet,</b>
@@ -30,19 +32,42 @@ export default {
30
32
  };
31
33
 
32
34
  const DefaultTemplate = (args) => ({
33
- components: { UAlert },
35
+ components: { UAlert, UIcon },
34
36
  setup() {
35
37
  const slots = getSlotNames(UAlert.name);
36
38
 
37
39
  return { args, slots };
38
40
  },
39
41
  template: `
40
- <UAlert v-bind="args">
41
- ${args.slotTemplate || ""}
42
+ <UAlert v-bind="args" v-model="args.value">
43
+ ${args.slotDefaultTemplate}
44
+ ${args.slotTemplate || ""}
42
45
  </UAlert>
43
46
  `,
44
47
  });
45
48
 
49
+ const VariantsTemplate = (args, { argTypes } = {}) => ({
50
+ components: { UAlert, UGroup },
51
+ setup() {
52
+ return {
53
+ args,
54
+ variants: argTypes.variant.options,
55
+ };
56
+ },
57
+ template: `
58
+ <UGroup>
59
+ <UAlert
60
+ v-for="(variant, index) in variants"
61
+ v-bind="args"
62
+ :variant="variant"
63
+ :key="index"
64
+ :title="variant"
65
+ color="gray"
66
+ />
67
+ </UGroup>
68
+ `,
69
+ });
70
+
46
71
  const ColorsTemplate = (args, { argTypes } = {}) => ({
47
72
  components: { UAlert, URow },
48
73
  setup() {
@@ -57,10 +82,9 @@ const ColorsTemplate = (args, { argTypes } = {}) => ({
57
82
  v-for="(color, index) in colors"
58
83
  v-bind="args"
59
84
  :color="color"
85
+ :title="color"
60
86
  :key="index"
61
- >
62
- ${args.slotTemplate}
63
- </UAlert>
87
+ />
64
88
  </URow>
65
89
  `,
66
90
  });
@@ -81,48 +105,60 @@ const SizeTemplate = (args, { argTypes } = {}) => ({
81
105
  :size="size"
82
106
  :key="index"
83
107
  >
84
- ${args.slotTemplate}
108
+ text
85
109
  </UAlert>
86
110
  </URow>
87
111
  `,
88
112
  });
89
113
 
114
+ const HTMLTemplate = (args) => ({
115
+ components: { UAlert },
116
+ setup() {
117
+ return { args };
118
+ },
119
+ template: `
120
+ <UAlert :html="args.html" />
121
+ `,
122
+ });
123
+
90
124
  export const Default = DefaultTemplate.bind({});
91
125
  Default.args = {};
92
126
 
127
+ export const variants = VariantsTemplate.bind({});
128
+ variants.args = {};
129
+
93
130
  export const colors = ColorsTemplate.bind({});
94
- colors.args = {
95
- closeIcon: true,
96
- slotTemplate: `
97
- <template #default>
98
- text
99
- </template>
100
- `,
101
- };
131
+ colors.args = {};
102
132
 
103
- export const size = SizeTemplate.bind({});
104
- size.args = {
105
- closeIcon: true,
106
- slotTemplate: `
107
- <template #default>
108
- text
109
- </template>
133
+ export const sizes = SizeTemplate.bind({});
134
+ sizes.args = {};
135
+
136
+ export const HTML = HTMLTemplate.bind({});
137
+ HTML.args = {
138
+ html: `
139
+ <p>
140
+ <b>Lorem ipsum dolor sit amet,</b>
141
+ <u>consectetur adipiscing elit,</u>
142
+ <em>sed do eiusmod tempor incididunt
143
+ ut labore et dolore magna aliqua.</em>
144
+ <a href="https://uk.wikipedia.org/wiki/Lorem_ipsum" target="_blank">Wikipedia</a>
145
+ </p>
110
146
  `,
111
147
  };
112
148
 
113
- export const closeIcon = DefaultTemplate.bind({});
114
- closeIcon.args = {
115
- closeIcon: true,
116
- slotTemplate: `
117
- <template #default>
118
- some text
119
- </template>
149
+ export const closable = DefaultTemplate.bind({});
150
+ closable.args = {
151
+ closable: true,
152
+ slotDefaultTemplate: `
153
+ <template #default>
154
+ some text
155
+ </template>
120
156
  `,
121
157
  };
122
158
 
123
159
  export const paragraphs = DefaultTemplate.bind({});
124
160
  paragraphs.args = {
125
- slotTemplate: `
161
+ slotDefaultTemplate: `
126
162
  <template #default>
127
163
  <p>
128
164
  Lorem ipsum dolor sit amet, consectetur adipiscing elit,
@@ -141,8 +177,7 @@ paragraphs.args = {
141
177
 
142
178
  export const list = DefaultTemplate.bind({});
143
179
  list.args = {
144
- slotTemplate: `
145
- <template #default>
180
+ slotDefaultTemplate: `
146
181
  <URow>
147
182
  <ul>
148
183
  <li> Lorem ipsum dolor </li>
@@ -156,6 +191,65 @@ list.args = {
156
191
  <li> Lorem ipsum dolor </li>
157
192
  </ol>
158
193
  </URow>
159
- </template>
160
194
  `,
161
195
  };
196
+
197
+ export const slotTitleAndDescription = DefaultTemplate.bind({});
198
+ slotTitleAndDescription.args = {
199
+ slotTemplate: `
200
+ <template #title>
201
+ <div>Alert Title</div>
202
+ </template>
203
+ <template #description>
204
+ <div>This is a custom description for the alert.</div>
205
+ </template>
206
+ `,
207
+ };
208
+
209
+ export const slotAlertLeft = DefaultTemplate.bind({});
210
+ slotAlertLeft.args = {
211
+ slotTemplate: `
212
+ <template #left>
213
+ <UIcon
214
+ name="star"
215
+ color="gray"
216
+ />
217
+ </template>
218
+ `,
219
+ };
220
+
221
+ export const slotAlertRight = DefaultTemplate.bind({});
222
+ slotAlertRight.args = {
223
+ slotTemplate: `
224
+ <template #right>
225
+ <UIcon
226
+ name="star"
227
+ color="gray"
228
+ />
229
+ </template>
230
+ `,
231
+ };
232
+
233
+ export const slotAlertTop = DefaultTemplate.bind({});
234
+ slotAlertTop.args = {
235
+ slotTemplate: `
236
+ <template #top>
237
+ <UIcon
238
+ name="star"
239
+ color="gray"
240
+ />
241
+ </template>
242
+ `,
243
+ };
244
+
245
+ export const slotAlertBottom = DefaultTemplate.bind({});
246
+ slotAlertBottom.args = {
247
+ slotTemplate: `
248
+ <template #bottom>
249
+ <UIcon
250
+ name="star"
251
+ color="gray"
252
+ />
253
+ </template>
254
+ `,
255
+ };
@@ -1,30 +1,51 @@
1
1
  <template>
2
2
  <div v-if="isShownAlert" :data-cy="dataCy" v-bind="wrapperAttrs">
3
+ <!-- @slot Use it to add something above text. -->
4
+ <slot name="top" />
5
+
6
+ <!-- Use it to add something instead of the title. -->
7
+ <slot name="title" :title="title">
8
+ <div v-bind="titleAttrs" v-text="title" />
9
+ </slot>
10
+
11
+ <!-- @slot Use it to add something instead of the description. -->
12
+ <slot name="description" :description="description">
13
+ <div v-bind="descriptionAttrs" v-text="description" />
14
+ </slot>
15
+
3
16
  <div v-bind="bodyAttrs">
4
- <!-- @slot Use it to add something inside. -->
17
+ <!-- @slot Use it to add something before text. -->
18
+ <slot name="left" />
19
+
20
+ <!-- @slot Default slot. -->
5
21
  <slot />
22
+ <div v-if="!hasSlotContent($slots.default)" v-bind="bodyAttrs" v-html="html" />
6
23
 
7
- <div v-if="!hasSlotContent($slots['default'])" v-html="html" />
24
+ <UButton
25
+ v-if="closable"
26
+ size="sm"
27
+ variant="thirdary"
28
+ :color="color"
29
+ square
30
+ v-bind="buttonAttrs"
31
+ @click="onClickClose"
32
+ >
33
+ <UIcon
34
+ internal
35
+ size="xs"
36
+ :color="color"
37
+ :name="config.iconName"
38
+ :data-cy="`${dataCy}-button`"
39
+ v-bind="iconAttrs"
40
+ />
41
+ </UButton>
42
+
43
+ <!-- @slot Use it to add something after text. -->
44
+ <slot name="right" />
8
45
  </div>
9
46
 
10
- <UButton
11
- v-if="closeIcon"
12
- size="sm"
13
- variant="thirdary"
14
- :color="color"
15
- square
16
- v-bind="buttonAttrs"
17
- >
18
- <UIcon
19
- internal
20
- size="xs"
21
- :color="color"
22
- :name="config.iconName"
23
- :data-cy="`${dataCy}-button`"
24
- v-bind="iconAttrs"
25
- @click="onHidden"
26
- />
27
- </UButton>
47
+ <!-- @slot Use it to add something under text. -->
48
+ <slot name="bottom" />
28
49
  </div>
29
50
  </template>
30
51
 
@@ -43,6 +64,15 @@ import { useAttrs } from "./composables/attrs.composable";
43
64
  defineOptions({ name: "UAlert" });
44
65
 
45
66
  const props = defineProps({
67
+ /**
68
+ * Alert variant.
69
+ * @values primary, secondary, thirdary
70
+ */
71
+ variant: {
72
+ type: String,
73
+ default: UIService.get(defaultConfig, UAlert).default.variant,
74
+ },
75
+
46
76
  /**
47
77
  * HTML or plain text.
48
78
  */
@@ -87,9 +117,9 @@ const props = defineProps({
87
117
  /**
88
118
  * Show close button.
89
119
  */
90
- closeIcon: {
120
+ closable: {
91
121
  type: Boolean,
92
- default: UIService.get(defaultConfig, UAlert).default.closeIcon,
122
+ default: UIService.get(defaultConfig, UAlert).default.closable,
93
123
  },
94
124
 
95
125
  /**
@@ -107,21 +137,46 @@ const props = defineProps({
107
137
  type: String,
108
138
  default: "",
109
139
  },
140
+
141
+ /**
142
+ * Alert title.
143
+ */
144
+ title: {
145
+ type: String,
146
+ default: "",
147
+ },
148
+
149
+ /**
150
+ * Alert description.
151
+ */
152
+ description: {
153
+ type: String,
154
+ default: "",
155
+ },
110
156
  });
111
157
 
112
158
  const emit = defineEmits(["hidden"]);
113
159
 
114
160
  const isShownAlert = ref(true);
115
161
 
116
- const { config, wrapperAttrs, bodyAttrs, iconAttrs, buttonAttrs, hasSlotContent } = useAttrs(props);
162
+ const {
163
+ config,
164
+ wrapperAttrs,
165
+ bodyAttrs,
166
+ titleAttrs,
167
+ descriptionAttrs,
168
+ iconAttrs,
169
+ buttonAttrs,
170
+ hasSlotContent,
171
+ } = useAttrs(props);
117
172
 
118
173
  onMounted(() => {
119
174
  if (props.timeout > 0) {
120
- setTimeout(() => onHidden(), props.timeout);
175
+ setTimeout(() => onClickClose(), props.timeout);
121
176
  }
122
177
  });
123
178
 
124
- function onHidden() {
179
+ function onClickClose() {
125
180
  isShownAlert.value = false;
126
181
  emit("hidden");
127
182
  }
package/web-types.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "framework": "vue",
3
3
  "name": "vueless",
4
- "version": "0.0.168",
4
+ "version": "0.0.170",
5
5
  "contributions": {
6
6
  "html": {
7
7
  "description-markup": "markdown",
@@ -254,6 +254,15 @@
254
254
  "name": "UAlert",
255
255
  "description": "",
256
256
  "attributes": [
257
+ {
258
+ "name": "variant",
259
+ "description": "Alert variant.",
260
+ "value": {
261
+ "kind": "expression",
262
+ "type": "'primary' | 'secondary' | 'thirdary'"
263
+ },
264
+ "default": "thirdary"
265
+ },
257
266
  {
258
267
  "name": "html",
259
268
  "description": "HTML or plain text.",
@@ -278,7 +287,7 @@
278
287
  "kind": "expression",
279
288
  "type": "'brand' | 'grayscale' | 'gray' | 'red' | 'orange' | 'amber' | 'yellow' | 'lime' | 'green' | 'emerald' | 'teal' | 'cyan' | 'sky' | 'blue' | 'indigo' | 'violet' | 'purple' | 'fuchsia' | 'pink' | 'rose' | 'white'"
280
289
  },
281
- "default": "grayscale"
290
+ "default": "brand"
282
291
  },
283
292
  {
284
293
  "name": "bordered",
@@ -299,7 +308,7 @@
299
308
  "default": "0"
300
309
  },
301
310
  {
302
- "name": "closeIcon",
311
+ "name": "closable",
303
312
  "description": "Show close button.",
304
313
  "value": {
305
314
  "kind": "expression",
@@ -324,6 +333,24 @@
324
333
  "type": "string"
325
334
  },
326
335
  "default": "\"\""
336
+ },
337
+ {
338
+ "name": "title",
339
+ "description": "Alert title.",
340
+ "value": {
341
+ "kind": "expression",
342
+ "type": "string"
343
+ },
344
+ "default": "\"\""
345
+ },
346
+ {
347
+ "name": "description",
348
+ "description": "Alert description.",
349
+ "value": {
350
+ "kind": "expression",
351
+ "type": "string"
352
+ },
353
+ "default": "\"\""
327
354
  }
328
355
  ],
329
356
  "events": [
@@ -332,9 +359,32 @@
332
359
  }
333
360
  ],
334
361
  "slots": [
362
+ {
363
+ "name": "top",
364
+ "description": "Use it to add something above text."
365
+ },
366
+ {
367
+ "name": "title"
368
+ },
369
+ {
370
+ "name": "description",
371
+ "description": "Use it to add something instead of the description."
372
+ },
373
+ {
374
+ "name": "left",
375
+ "description": "Use it to add something before text."
376
+ },
335
377
  {
336
378
  "name": "default",
337
- "description": "Use it to add something inside."
379
+ "description": "Default slot."
380
+ },
381
+ {
382
+ "name": "right",
383
+ "description": "Use it to add something after text."
384
+ },
385
+ {
386
+ "name": "bottom",
387
+ "description": "Use it to add something under text."
338
388
  }
339
389
  ],
340
390
  "source": {