vueless 1.5.1-beta.2 → 1.5.1-beta.4

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/index.d.ts CHANGED
@@ -27,7 +27,15 @@ export {
27
27
  } from "./utils/ui";
28
28
  export { addToRequestQueue, removeFromRequestQueue } from "./utils/requestQueue";
29
29
  export { isMac, isPWA, isIOS, isAndroid, isMobileApp, isWindows } from "./utils/platform";
30
- export { getTheme, setTheme, resetTheme, normalizeThemeConfig, cssVar, setRootCSSVariables } from "./utils/theme";
30
+ export {
31
+ getTheme,
32
+ setTheme,
33
+ resetTheme,
34
+ getThemeCookieName,
35
+ normalizeThemeConfig,
36
+ cssVar,
37
+ setRootCSSVariables,
38
+ } from "./utils/theme";
31
39
  export { getArgs, getArgTypes, getSlotNames, getSlotsFragment, getSource, getDocsDescription } from "./utils/storybook";
32
40
  /* adapters */
33
41
  export { default as defaultEnLocale } from "./adapter.locale/locales/en";
package/index.ts CHANGED
@@ -33,7 +33,15 @@ export {
33
33
  } from "./utils/ui";
34
34
  export { addToRequestQueue, removeFromRequestQueue } from "./utils/requestQueue";
35
35
  export { isMac, isPWA, isIOS, isAndroid, isMobileApp, isWindows } from "./utils/platform";
36
- export { getTheme, setTheme, resetTheme, normalizeThemeConfig, cssVar, setRootCSSVariables } from "./utils/theme";
36
+ export {
37
+ getTheme,
38
+ setTheme,
39
+ resetTheme,
40
+ getThemeCookieName,
41
+ normalizeThemeConfig,
42
+ cssVar,
43
+ setRootCSSVariables,
44
+ } from "./utils/theme";
37
45
  export { getArgs, getArgTypes, getSlotNames, getSlotsFragment, getSource, getDocsDescription } from "./utils/storybook";
38
46
  /* adapters */
39
47
  export { default as defaultEnLocale } from "./adapter.locale/locales/en";
package/modules.d.ts CHANGED
@@ -33,3 +33,4 @@ declare module "virtual:vueless/vue-i18n" {
33
33
  }
34
34
 
35
35
  declare const __VUELESS_DEV__: boolean;
36
+ declare const __VUELESS_APP_NAME__: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.5.1-beta.2",
3
+ "version": "1.5.1-beta.4",
4
4
  "description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
5
5
  "author": "Johnny Grid <hello@vueless.com> (https://vueless.com)",
6
6
  "homepage": "https://vueless.com",
package/plugin-vite.js CHANGED
@@ -24,6 +24,7 @@ import { overrideComponents, restoreComponents } from "./utils/node/componentOve
24
24
  import {
25
25
  getNuxtDirs,
26
26
  getVueDirs,
27
+ getAppName,
27
28
  getVuelessAppDirs,
28
29
  cacheMergedConfigs,
29
30
  autoImportUserConfigs,
@@ -74,6 +75,9 @@ export const Vueless = function (options = {}) {
74
75
 
75
76
  const vuelessSrcDir = isInternalEnv ? VUELESS_LOCAL_DIR : VUELESS_PACKAGE_DIR;
76
77
 
78
+ /* app name for theme cookie prefixing */
79
+ const appName = getAppName();
80
+
77
81
  // Cache to store previous icon content for change detection
78
82
  const iconContentCache = new Map();
79
83
 
@@ -109,6 +113,7 @@ export const Vueless = function (options = {}) {
109
113
  define: {
110
114
  "process.env": {},
111
115
  __VUELESS_DEV__: process.argv.includes("--watch") || process.argv.includes("-w"),
116
+ __VUELESS_APP_NAME__: JSON.stringify(appName),
112
117
  },
113
118
  optimizeDeps: {
114
119
  include: isInternalEnv
package/types.ts CHANGED
@@ -183,6 +183,11 @@ export interface Config extends ThemeConfig {
183
183
  */
184
184
  directives?: Partial<Directives>;
185
185
 
186
+ /**
187
+ * Theme cookie name prefix (defaults to the app `package.json` name, `""` disables prefixing).
188
+ */
189
+ cookiePrefix?: string;
190
+
186
191
  /**
187
192
  * Tailwind CSS theme config.
188
193
  * https://tailwindcss.com/docs/theme
@@ -54,6 +54,7 @@ const props = withDefaults(defineProps<Props<TModelValue>>(), {
54
54
  modelValue: undefined,
55
55
  minDate: undefined,
56
56
  maxDate: undefined,
57
+ dayClasses: undefined,
57
58
  });
58
59
 
59
60
  const emit = defineEmits([
@@ -918,6 +919,7 @@ const {
918
919
  :date-format="actualDateFormat"
919
920
  :locale="locale"
920
921
  :config="config"
922
+ :day-classes="dayClasses"
921
923
  :is-arrow-key-dirty="isArrowKeyDirty"
922
924
  :data-test="getDataTest('day-view')"
923
925
  @input="onInputDate"
@@ -232,6 +232,10 @@ function onMouseleaveDayView() {
232
232
  }
233
233
  }
234
234
 
235
+ function getDayCustomClasses(day: Date) {
236
+ return props.dayClasses?.(day);
237
+ }
238
+
235
239
  /**
236
240
  * Get element / nested component attributes for each config token ✨
237
241
  * Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
@@ -279,6 +283,7 @@ defineExpose({
279
283
  size="md"
280
284
  square
281
285
  v-bind="selectedDayAttrs"
286
+ :class="getDayCustomClasses(day)"
282
287
  :disabled="dateIsOutOfRange(day, minDate, maxDate, locale, dateFormat)"
283
288
  :label="formatDate(day, 'j', locale)"
284
289
  @mousedown.prevent.capture
@@ -294,6 +299,7 @@ defineExpose({
294
299
  size="md"
295
300
  square
296
301
  v-bind="selectedDayAttrs"
302
+ :class="getDayCustomClasses(day)"
297
303
  :disabled="dateIsOutOfRange(day, minDate, maxDate, locale, dateFormat)"
298
304
  :label="formatDate(day, 'j', locale)"
299
305
  @mousedown.prevent.capture
@@ -311,6 +317,7 @@ defineExpose({
311
317
  size="md"
312
318
  square
313
319
  v-bind="firstDayInRangeAttrs"
320
+ :class="getDayCustomClasses(day)"
314
321
  :disabled="dateIsOutOfRange(day, minDate, maxDate, locale, dateFormat)"
315
322
  :label="formatDate(day, 'j', locale)"
316
323
  @mousedown.prevent.capture
@@ -328,6 +335,7 @@ defineExpose({
328
335
  size="md"
329
336
  square
330
337
  v-bind="lastDayInRangeAttrs"
338
+ :class="getDayCustomClasses(day)"
331
339
  :disabled="dateIsOutOfRange(day, minDate, maxDate, locale, dateFormat)"
332
340
  :label="formatDate(day, 'j', locale)"
333
341
  @mousedown.prevent.capture
@@ -343,6 +351,7 @@ defineExpose({
343
351
  size="md"
344
352
  square
345
353
  v-bind="anotherMonthFirstDayInRangeAttrs"
354
+ :class="getDayCustomClasses(day)"
346
355
  :disabled="dateIsOutOfRange(day, minDate, maxDate, locale, dateFormat)"
347
356
  :label="formatDate(day, 'j', locale)"
348
357
  @mousedown.prevent.capture
@@ -358,6 +367,7 @@ defineExpose({
358
367
  size="md"
359
368
  square
360
369
  v-bind="anotherMonthLastDayInRangeAttrs"
370
+ :class="getDayCustomClasses(day)"
361
371
  :disabled="dateIsOutOfRange(day, minDate, maxDate, locale, dateFormat)"
362
372
  :label="formatDate(day, 'j', locale)"
363
373
  @mousedown.prevent.capture
@@ -373,6 +383,7 @@ defineExpose({
373
383
  size="md"
374
384
  square
375
385
  v-bind="lastDayInRangeAttrs"
386
+ :class="getDayCustomClasses(day)"
376
387
  :disabled="dateIsOutOfRange(day, minDate, maxDate, locale, dateFormat)"
377
388
  :label="formatDate(day, 'j', locale)"
378
389
  @mousedown.prevent.capture
@@ -391,6 +402,7 @@ defineExpose({
391
402
  size="md"
392
403
  square
393
404
  v-bind="anotherMonthDayInRangeAttrs"
405
+ :class="getDayCustomClasses(day)"
394
406
  :disabled="dateIsOutOfRange(day, minDate, maxDate, locale, dateFormat)"
395
407
  :label="formatDate(day, 'j', locale)"
396
408
  @mousedown.prevent.capture
@@ -409,6 +421,7 @@ defineExpose({
409
421
  size="md"
410
422
  square
411
423
  v-bind="currentDayInRangeAttrs"
424
+ :class="getDayCustomClasses(day)"
412
425
  :disabled="dateIsOutOfRange(day, minDate, maxDate, locale, dateFormat)"
413
426
  :label="formatDate(day, 'j', locale)"
414
427
  @mousedown.prevent.capture
@@ -424,6 +437,7 @@ defineExpose({
424
437
  size="md"
425
438
  square
426
439
  v-bind="dayInRangeAttrs"
440
+ :class="getDayCustomClasses(day)"
427
441
  :disabled="dateIsOutOfRange(day, minDate, maxDate, locale, dateFormat)"
428
442
  :label="formatDate(day, 'j', locale)"
429
443
  @mousedown.prevent.capture
@@ -439,6 +453,7 @@ defineExpose({
439
453
  size="md"
440
454
  square
441
455
  v-bind="currentDayAttrs"
456
+ :class="getDayCustomClasses(day)"
442
457
  :disabled="dateIsOutOfRange(day, minDate, maxDate, locale, dateFormat)"
443
458
  :label="formatDate(day, 'j', locale)"
444
459
  @mousedown.prevent.capture
@@ -454,6 +469,7 @@ defineExpose({
454
469
  size="md"
455
470
  square
456
471
  v-bind="activeDayAttrs"
472
+ :class="getDayCustomClasses(day)"
457
473
  :disabled="dateIsOutOfRange(day, minDate, maxDate, locale, dateFormat)"
458
474
  :label="formatDate(day, 'j', locale)"
459
475
  @mousedown.prevent.capture
@@ -469,6 +485,7 @@ defineExpose({
469
485
  size="md"
470
486
  square
471
487
  v-bind="anotherMonthDayAttrs"
488
+ :class="getDayCustomClasses(day)"
472
489
  :disabled="dateIsOutOfRange(day, minDate, maxDate, locale, dateFormat)"
473
490
  :label="formatDate(day, 'j', locale)"
474
491
  @mousedown.prevent.capture
@@ -484,6 +501,7 @@ defineExpose({
484
501
  size="md"
485
502
  square
486
503
  v-bind="dayAttrs"
504
+ :class="getDayCustomClasses(day)"
487
505
  :disabled="dateIsOutOfRange(day, minDate, maxDate, locale, dateFormat)"
488
506
  :label="formatDate(day, 'j', locale)"
489
507
  @mousedown.prevent.capture
@@ -147,3 +147,29 @@ MinMax.parameters = {
147
147
  },
148
148
  },
149
149
  };
150
+
151
+ export const DayClasses = DefaultTemplate.bind({});
152
+ DayClasses.args = {
153
+ dayClasses: (day: Date) => {
154
+ return [5, 12, 19].includes(day.getDate()) ? "bg-success" : "";
155
+ },
156
+ };
157
+ DayClasses.parameters = {
158
+ docs: {
159
+ description: {
160
+ story:
161
+ // eslint-disable-next-line vue/max-len
162
+ "Use the `dayClasses` prop to apply extra CSS classes to individual day cells, e.g. to highlight dates by status.",
163
+ },
164
+ source: {
165
+ code: `
166
+ <UCalendar
167
+ v-model="args.modelValue"
168
+ :day-classes="(day) => ([5, 12, 19].includes(day.getDate()) ? 'bg-success' : '')"
169
+ />
170
+
171
+ <UText color="neutral" class="mt-4">{{ args.modelValue }}</UText>
172
+ `,
173
+ },
174
+ },
175
+ };
@@ -295,6 +295,19 @@ describe("UCalendar.vue", () => {
295
295
  expect(updatedValue.to).toMatch(dateFormatRegex);
296
296
  });
297
297
 
298
+ it("Day Classes – passes dayClasses to UCalendarDayView", () => {
299
+ const dayClasses = (day: Date) => (day.getDate() === 15 ? "bg-mint-highlight" : "");
300
+
301
+ const component = mount(UCalendar, {
302
+ props: {
303
+ modelValue: null,
304
+ dayClasses,
305
+ },
306
+ });
307
+
308
+ expect(component.findComponent(DayView).props("dayClasses")).toBe(dayClasses);
309
+ });
310
+
298
311
  it("Tabindex – sets tabindex attribute on wrapper", () => {
299
312
  const tabindex = 5;
300
313
 
@@ -386,6 +386,39 @@ describe("UCalendarDayView.vue", () => {
386
386
  });
387
387
  });
388
388
 
389
+ describe("Day Classes Prop", () => {
390
+ it("applies function-based dayClasses only to the matching day", () => {
391
+ const component = mount(UCalendarDayView, {
392
+ props: {
393
+ ...defaultProps,
394
+ selectedDate: null,
395
+ dayClasses: (day: Date) => (day.getDate() === 10 ? "bg-mint-highlight" : ""),
396
+ },
397
+ });
398
+
399
+ const dayButtons = component.findAll('[vl-key="day"]');
400
+ const targetDay = dayButtons.find((button) => button.text() === "10");
401
+ const otherDay = dayButtons.find((button) => button.text() === "12");
402
+
403
+ expect(targetDay?.classes()).toContain("bg-mint-highlight");
404
+ expect(otherDay?.classes()).not.toContain("bg-mint-highlight");
405
+ });
406
+
407
+ it("renders regular day cells without error when dayClasses prop is not provided", () => {
408
+ const component = mount(UCalendarDayView, {
409
+ props: {
410
+ ...defaultProps,
411
+ selectedDate: null,
412
+ },
413
+ });
414
+
415
+ const regularDay = component.find('[vl-key="day"]');
416
+
417
+ expect(regularDay.exists()).toBe(true);
418
+ expect(regularDay.classes()).not.toContain("bg-mint-highlight");
419
+ });
420
+ });
421
+
389
422
  describe("Exposed Properties", () => {
390
423
  it("exposes days computed property", () => {
391
424
  const component = mount(UCalendarDayView, {
@@ -72,6 +72,11 @@ export interface Props<TModelValue extends DateValue> {
72
72
  */
73
73
  maxDate?: Date | string;
74
74
 
75
+ /**
76
+ * Extra CSS classes to apply to each day cell, on top of the built-in state classes.
77
+ */
78
+ dayClasses?: (day: Date) => string;
79
+
75
80
  /**
76
81
  * Component config object.
77
82
  */
@@ -93,5 +98,6 @@ export interface UCalendarViewProps {
93
98
  isArrowKeyDirty: boolean;
94
99
  maxDate?: Date | string;
95
100
  minDate?: Date | string;
101
+ dayClasses?: (day: Date) => string;
96
102
  config: Config;
97
103
  }
@@ -34,6 +34,7 @@ const props = withDefaults(defineProps<Props<TModelValue>>(), {
34
34
  modelValue: undefined,
35
35
  minDate: undefined,
36
36
  maxDate: undefined,
37
+ dayClasses: undefined,
37
38
  placeholder: "",
38
39
  label: "",
39
40
  });
@@ -364,6 +365,7 @@ watchEffect(() => {
364
365
  :user-date-time-format="userDateTimeFormat"
365
366
  :max-date="maxDate"
366
367
  :min-date="minDate"
368
+ :day-classes="dayClasses"
367
369
  v-bind="datepickerCalendarAttrs"
368
370
  :data-test="getDataTest('calendar')"
369
371
  @keydown.esc="deactivate"
@@ -454,3 +454,32 @@ Slots.parameters = {
454
454
  },
455
455
  },
456
456
  };
457
+
458
+ export const DayClasses = DefaultTemplate.bind({});
459
+ DayClasses.args = {
460
+ modelValue: dateValue,
461
+ dayClasses: (day: Date) => {
462
+ return [5, 12, 19].includes(day.getDate()) ? "bg-success" : "";
463
+ },
464
+ };
465
+ DayClasses.parameters = {
466
+ docs: {
467
+ description: {
468
+ story:
469
+ // eslint-disable-next-line vue/max-len
470
+ "Use the `dayClasses` prop to apply extra CSS classes to individual day cells, e.g. to highlight dates by status.",
471
+ },
472
+ source: {
473
+ code: `
474
+ <UDatePicker
475
+ v-model="args.modelValue"
476
+ label="Select a date"
477
+ :day-classes="(day) => ([5, 12, 19].includes(day.getDate()) ? 'bg-success' : '')"
478
+ class="max-w-96"
479
+ />
480
+
481
+ <UText color="neutral" class="mt-4">{{ args.modelValue }}</UText>
482
+ `,
483
+ },
484
+ },
485
+ };
@@ -284,6 +284,19 @@ describe("UDatePicker.vue", () => {
284
284
  expect(component.findComponent({ name: "UCalendar" }).props("maxDate")).toBe(maxDate);
285
285
  });
286
286
 
287
+ it("Day Classes – passes dayClasses to UCalendar", () => {
288
+ const dayClasses = (day: Date) => (day.getDate() === 15 ? "bg-mint-highlight" : "");
289
+
290
+ const component = mount(UDatePicker, {
291
+ props: {
292
+ modelValue: new Date(),
293
+ dayClasses,
294
+ },
295
+ });
296
+
297
+ expect(component.findComponent({ name: "UCalendar" }).props("dayClasses")).toBe(dayClasses);
298
+ });
299
+
287
300
  it("Id – sets id attribute on input", () => {
288
301
  const idValue = "date-picker-id";
289
302
 
@@ -103,6 +103,11 @@ export interface Props<TModelValue> {
103
103
  */
104
104
  rightIcon?: string;
105
105
 
106
+ /**
107
+ * Extra CSS classes to apply to each day cell, on top of the built-in state classes.
108
+ */
109
+ dayClasses?: (day: Date) => string;
110
+
106
111
  /**
107
112
  * Unique element id.
108
113
  */
@@ -73,9 +73,12 @@ defineOptions({ inheritAttrs: false });
73
73
  const props = withDefaults(defineProps<Props<TModelValue>>(), {
74
74
  ...getDefaults<Props<TModelValue>, Config>(defaultConfig, COMPONENT_NAME),
75
75
  customRangeButton: () => ({ range: { from: null, to: null } }),
76
+ periods: () =>
77
+ getDefaults<Props<TModelValue>, Config>(defaultConfig, COMPONENT_NAME).periods || [],
76
78
  modelValue: undefined,
77
79
  minDate: undefined,
78
80
  maxDate: undefined,
81
+ dayClasses: undefined,
79
82
  placeholder: "",
80
83
  label: "",
81
84
  });
@@ -129,7 +132,21 @@ const activeDate: Ref<string | Date | null> = ref(
129
132
  ? parseDate<SortedLocale>(props.modelValue.from, props.dateFormat, locale.value)
130
133
  : new Date(),
131
134
  );
132
- const period = ref(Period.OwnRange);
135
+ let isInvalidPeriodsWarned = false;
136
+ const allowedPeriods = computed(() => getAllowedPeriods());
137
+ const isPeriodSwitchShown = computed(() => {
138
+ const hasCustomRange = Boolean(
139
+ props.customRangeButton.range.from && props.customRangeButton.range.to,
140
+ );
141
+ const periodButtonCount = allowedPeriods.value.filter((type) => type !== Period.Custom).length;
142
+
143
+ return periodButtonCount + Number(hasCustomRange) > 1;
144
+ });
145
+ const rawPeriod = ref(Period.OwnRange);
146
+ const period = computed({
147
+ get: () => getAllowedPeriod(rawPeriod.value),
148
+ set: (value: Period) => (rawPeriod.value = value),
149
+ });
133
150
  const rangeStart = ref("");
134
151
  const rangeEnd = ref("");
135
152
  const inputRangeFromError = ref("");
@@ -319,6 +336,29 @@ function deactivate() {
319
336
  isShownMenu.value = false;
320
337
  }
321
338
 
339
+ function getAllowedPeriods(): readonly Period[] {
340
+ const allPeriods = Object.values(Period);
341
+ const validPeriods = allPeriods.filter((type) => props.periods.includes(type));
342
+
343
+ if (validPeriods.length) return validPeriods;
344
+
345
+ if (import.meta.env.DEV && !isInvalidPeriodsWarned) {
346
+ // eslint-disable-next-line no-console
347
+ console.warn(`[${COMPONENT_NAME}] No valid "periods" provided, falling back to all periods.`);
348
+ isInvalidPeriodsWarned = true;
349
+ }
350
+
351
+ return allPeriods;
352
+ }
353
+
354
+ function getAllowedPeriod(detectedPeriod: Period): Period {
355
+ if (detectedPeriod === Period.Custom || allowedPeriods.value.includes(detectedPeriod)) {
356
+ return detectedPeriod;
357
+ }
358
+
359
+ return allowedPeriods.value.includes(Period.OwnRange) ? Period.OwnRange : allowedPeriods.value[0];
360
+ }
361
+
322
362
  setDefaultPeriodForButton();
323
363
 
324
364
  function setDefaultPeriodForButton() {
@@ -404,16 +444,12 @@ function shiftRangeNext(to: Date, from: Date, daysDifference: number) {
404
444
  return;
405
445
  }
406
446
 
407
- let nextDate = periodDateList.value.find(
408
- (item) => localValue.value.to && item.endRange > localValue.value.to,
409
- );
447
+ let nextDate = periodDateList.value.find((item) => item.startRange > from);
410
448
 
411
449
  if (!nextDate) {
412
450
  onClickShiftDatesList(ShiftAction.Next);
413
451
 
414
- nextDate = periodDateList.value.find(
415
- (item) => localValue.value.to && item.endRange > localValue.value.to,
416
- );
452
+ nextDate = periodDateList.value.find((item) => item.startRange > from);
417
453
  }
418
454
 
419
455
  if (nextDate && isDatePeriodOutOfRange(nextDate)) return;
@@ -468,8 +504,19 @@ function shiftRangePrev(to: Date, from: Date, daysDifference: number) {
468
504
  }
469
505
  }
470
506
 
507
+ function getPeriodDateList(date: Date): DatePeriodRange[] {
508
+ if (isPeriod.value.week) return getWeekDateList(date, locale.value.months.shorthand);
509
+ if (isPeriod.value.month) return getMonthsDateList(date, locale.value.months.longhand);
510
+ if (isPeriod.value.quarter) return getQuartersDateList(date, locale.value.quarter);
511
+ if (isPeriod.value.year) return getYearDateList(date);
512
+
513
+ return [];
514
+ }
515
+
471
516
  function onClickShiftRange(action: ShiftActions) {
472
- if (isPeriod.value.custom) {
517
+ const isLeavingCustom = isPeriod.value.custom;
518
+
519
+ if (isLeavingCustom) {
473
520
  period.value = Period.OwnRange;
474
521
  }
475
522
 
@@ -487,6 +534,10 @@ function onClickShiftRange(action: ShiftActions) {
487
534
  if (!from) return;
488
535
  to = to || addDays(from, 1);
489
536
 
537
+ if (isLeavingCustom && !isPeriod.value.ownRange) {
538
+ periodDateList.value = getPeriodDateList(from);
539
+ }
540
+
490
541
  const daysDifference = Math.ceil(Math.abs(getDatesDifference(from, to)) / millisecondsPerDay);
491
542
 
492
543
  action === ShiftAction.Next
@@ -568,6 +619,7 @@ const mutatedProps = computed(() => ({
568
619
  quarter: isPeriod.value.quarter,
569
620
  year: isPeriod.value.year,
570
621
  customRangeButtonDescription: Boolean(props.customRangeButton.description),
622
+ periodSwitch: isPeriodSwitchShown.value,
571
623
  }));
572
624
 
573
625
  const {
@@ -730,6 +782,8 @@ watchEffect(() => {
730
782
  v-model:period="period"
731
783
  :config="config"
732
784
  :is-period="isPeriod"
785
+ :allowed-periods="allowedPeriods"
786
+ :is-period-switch-shown="isPeriodSwitchShown"
733
787
  :custom-range-button="customRangeButton"
734
788
  :locale="locale"
735
789
  :date-format="dateFormat"
@@ -781,6 +835,7 @@ watchEffect(() => {
781
835
  :min-date="minDate"
782
836
  :max-date="maxDate"
783
837
  :date-format="dateFormat"
838
+ :day-classes="dayClasses"
784
839
  v-bind="datepickerCalendarAttrs as KeyAttrsWithConfig<UCalendarConfig>"
785
840
  range
786
841
  :data-test="getDataTest('calendar')"
@@ -36,12 +36,20 @@ const isDatePeriodOutOfRange = inject<IsDatePeriodOutOfRange | null>(
36
36
  null,
37
37
  );
38
38
 
39
- const periods = computed(() => [
40
- { name: Period.Week, title: props.locale.week },
41
- { name: Period.Month, title: props.locale.month },
42
- { name: Period.Quarter, title: props.locale.quarter },
43
- { name: Period.Year, title: props.locale.year },
44
- ]);
39
+ const periods = computed(() =>
40
+ [
41
+ { name: Period.Week, title: props.locale.week },
42
+ { name: Period.Month, title: props.locale.month },
43
+ { name: Period.Quarter, title: props.locale.quarter },
44
+ { name: Period.Year, title: props.locale.year },
45
+ ].filter(({ name }) => props.allowedPeriods.includes(name)),
46
+ );
47
+
48
+ const isOwnRangeAllowed = computed(() => props.allowedPeriods.includes(Period.OwnRange));
49
+
50
+ const hasCustomRange = computed(() =>
51
+ Boolean(props.customRangeButton.range.from && props.customRangeButton.range.to),
52
+ );
45
53
 
46
54
  const rangeSwitchTitle = computed(() => {
47
55
  if (props.isPeriod.month || props.isPeriod.quarter) {
@@ -148,7 +156,11 @@ function getDatePeriodState(date: DatePeriodRange) {
148
156
  </script>
149
157
 
150
158
  <template>
151
- <div v-bind="attrs.periodRowAttrs.value" :data-test="getDataTest()">
159
+ <div
160
+ v-if="isPeriodSwitchShown && periods.length"
161
+ v-bind="attrs.periodRowAttrs.value"
162
+ :data-test="getDataTest()"
163
+ >
152
164
  <template v-for="periodButton in periods" :key="periodButton.name">
153
165
  <UButton
154
166
  v-if="periodButton.name !== period"
@@ -176,27 +188,12 @@ function getDatePeriodState(date: DatePeriodRange) {
176
188
  </template>
177
189
  </div>
178
190
 
179
- <div v-bind="attrs.periodRowAttrs.value">
180
- <UButton
181
- v-if="customRangeButton.range.to && customRangeButton.range.from && Period.Custom !== period"
182
- square
183
- size="xs"
184
- color="grayscale"
185
- variant="soft"
186
- :data-test="getDataTest('custom-range')"
187
- v-bind="attrs.customRangeButtonAttrs.value"
188
- @click="onClickCustomRangeButton"
189
- >
190
- {{ customRangeButton.label }}
191
- <span
192
- v-if="customRangeButton.description"
193
- v-bind="attrs.customRangeDescriptionAttrs.value"
194
- v-text="customRangeButton.description"
195
- />
196
- </UButton>
197
-
191
+ <div
192
+ v-if="isPeriodSwitchShown && (hasCustomRange || isOwnRangeAllowed)"
193
+ v-bind="attrs.periodRowAttrs.value"
194
+ >
198
195
  <UButton
199
- v-if="customRangeButton.range.to && customRangeButton.range.from && Period.Custom === period"
196
+ v-if="hasCustomRange"
200
197
  square
201
198
  size="xs"
202
199
  color="grayscale"
@@ -214,7 +211,7 @@ function getDatePeriodState(date: DatePeriodRange) {
214
211
  </UButton>
215
212
 
216
213
  <UButton
217
- v-if="Period.OwnRange !== period"
214
+ v-if="isOwnRangeAllowed && Period.OwnRange !== period"
218
215
  square
219
216
  size="xs"
220
217
  color="grayscale"
@@ -227,7 +224,7 @@ function getDatePeriodState(date: DatePeriodRange) {
227
224
  />
228
225
 
229
226
  <UButton
230
- v-else
227
+ v-else-if="isOwnRangeAllowed"
231
228
  square
232
229
  size="xs"
233
230
  color="grayscale"