@nuxt/ui 4.8.1 → 4.9.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.
Files changed (101) hide show
  1. package/dist/module.d.mts +7 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +2 -2
  4. package/dist/runtime/components/Avatar.vue +1 -1
  5. package/dist/runtime/components/Banner.vue +0 -1
  6. package/dist/runtime/components/BlogPost.vue +8 -10
  7. package/dist/runtime/components/Calendar.d.vue.ts +42 -6
  8. package/dist/runtime/components/Calendar.vue +180 -61
  9. package/dist/runtime/components/Calendar.vue.d.ts +42 -6
  10. package/dist/runtime/components/ChatMessages.d.vue.ts +7 -3
  11. package/dist/runtime/components/ChatMessages.vue +4 -1
  12. package/dist/runtime/components/ChatMessages.vue.d.ts +7 -3
  13. package/dist/runtime/components/ChatPrompt.vue +8 -2
  14. package/dist/runtime/components/ChatPromptSubmit.d.vue.ts +2 -2
  15. package/dist/runtime/components/ChatPromptSubmit.vue +2 -2
  16. package/dist/runtime/components/ChatPromptSubmit.vue.d.ts +2 -2
  17. package/dist/runtime/components/CommandPalette.d.vue.ts +2 -2
  18. package/dist/runtime/components/CommandPalette.vue +10 -4
  19. package/dist/runtime/components/CommandPalette.vue.d.ts +2 -2
  20. package/dist/runtime/components/DashboardSearch.d.vue.ts +7 -1
  21. package/dist/runtime/components/DashboardSearch.vue +9 -2
  22. package/dist/runtime/components/DashboardSearch.vue.d.ts +7 -1
  23. package/dist/runtime/components/DashboardSearchButton.d.vue.ts +2 -2
  24. package/dist/runtime/components/DashboardSearchButton.vue +2 -2
  25. package/dist/runtime/components/DashboardSearchButton.vue.d.ts +2 -2
  26. package/dist/runtime/components/FileUpload.d.vue.ts +5 -2
  27. package/dist/runtime/components/FileUpload.vue +8 -6
  28. package/dist/runtime/components/FileUpload.vue.d.ts +5 -2
  29. package/dist/runtime/components/Form.d.vue.ts +3 -3
  30. package/dist/runtime/components/Form.vue +2 -1
  31. package/dist/runtime/components/Form.vue.d.ts +3 -3
  32. package/dist/runtime/components/InputDate.d.vue.ts +2 -2
  33. package/dist/runtime/components/InputDate.vue +1 -0
  34. package/dist/runtime/components/InputDate.vue.d.ts +2 -2
  35. package/dist/runtime/components/InputMenu.vue +12 -1
  36. package/dist/runtime/components/InputNumber.d.vue.ts +1 -1
  37. package/dist/runtime/components/InputNumber.vue +3 -2
  38. package/dist/runtime/components/InputNumber.vue.d.ts +1 -1
  39. package/dist/runtime/components/InputTime.d.vue.ts +2 -2
  40. package/dist/runtime/components/InputTime.vue +1 -0
  41. package/dist/runtime/components/InputTime.vue.d.ts +2 -2
  42. package/dist/runtime/components/Link.d.vue.ts +1 -0
  43. package/dist/runtime/components/Link.vue +1 -1
  44. package/dist/runtime/components/Link.vue.d.ts +1 -0
  45. package/dist/runtime/components/LinkBase.d.vue.ts +1 -1
  46. package/dist/runtime/components/LinkBase.vue +1 -1
  47. package/dist/runtime/components/LinkBase.vue.d.ts +1 -1
  48. package/dist/runtime/components/Modal.d.vue.ts +6 -0
  49. package/dist/runtime/components/Modal.vue +3 -1
  50. package/dist/runtime/components/Modal.vue.d.ts +6 -0
  51. package/dist/runtime/components/PinInput.d.vue.ts +14 -2
  52. package/dist/runtime/components/PinInput.vue +36 -11
  53. package/dist/runtime/components/PinInput.vue.d.ts +14 -2
  54. package/dist/runtime/components/Popover.d.vue.ts +2 -0
  55. package/dist/runtime/components/Popover.vue +2 -2
  56. package/dist/runtime/components/Popover.vue.d.ts +2 -0
  57. package/dist/runtime/components/ScrollArea.d.vue.ts +8 -0
  58. package/dist/runtime/components/ScrollArea.vue +10 -0
  59. package/dist/runtime/components/ScrollArea.vue.d.ts +8 -0
  60. package/dist/runtime/components/Select.vue +8 -1
  61. package/dist/runtime/components/SelectMenu.vue +23 -5
  62. package/dist/runtime/components/Sidebar.d.vue.ts +5 -0
  63. package/dist/runtime/components/Sidebar.vue +3 -1
  64. package/dist/runtime/components/Sidebar.vue.d.ts +5 -0
  65. package/dist/runtime/components/Slideover.d.vue.ts +6 -0
  66. package/dist/runtime/components/Slideover.vue +3 -1
  67. package/dist/runtime/components/Slideover.vue.d.ts +6 -0
  68. package/dist/runtime/components/Toast.d.vue.ts +8 -1
  69. package/dist/runtime/components/Toast.vue +5 -5
  70. package/dist/runtime/components/Toast.vue.d.ts +8 -1
  71. package/dist/runtime/components/content/ContentSearch.d.vue.ts +7 -1
  72. package/dist/runtime/components/content/ContentSearch.vue +9 -2
  73. package/dist/runtime/components/content/ContentSearch.vue.d.ts +7 -1
  74. package/dist/runtime/components/content/ContentSearchButton.d.vue.ts +2 -2
  75. package/dist/runtime/components/content/ContentSearchButton.vue +2 -2
  76. package/dist/runtime/components/content/ContentSearchButton.vue.d.ts +2 -2
  77. package/dist/runtime/components/prose/Kbd.d.vue.ts +2 -1
  78. package/dist/runtime/components/prose/Kbd.vue.d.ts +2 -1
  79. package/dist/runtime/components/prose/Pre.vue +0 -1
  80. package/dist/runtime/composables/index.d.ts +1 -0
  81. package/dist/runtime/composables/index.js +1 -0
  82. package/dist/runtime/composables/useComponentProps.d.ts +3 -3
  83. package/dist/runtime/composables/useTour.d.ts +80 -0
  84. package/dist/runtime/composables/useTour.js +102 -0
  85. package/dist/runtime/index.css +1 -1
  86. package/dist/runtime/keyframes.css +1 -1
  87. package/dist/runtime/locale/index.d.ts +1 -0
  88. package/dist/runtime/locale/index.js +1 -0
  89. package/dist/runtime/locale/lv.d.ts +3 -0
  90. package/dist/runtime/locale/lv.js +152 -0
  91. package/dist/runtime/plugins/colors.js +5 -4
  92. package/dist/runtime/types/tv.d.ts +15 -3
  93. package/dist/runtime/utils/tv.d.ts +41 -1
  94. package/dist/runtime/utils/tv.js +91 -2
  95. package/dist/runtime/vue/stubs/base.d.ts +1 -1
  96. package/dist/runtime/vue/stubs/base.js +1 -1
  97. package/dist/shared/{ui.BzjkAvk7.mjs → ui.BfHSFiIO.mjs} +377 -234
  98. package/dist/unplugin.d.mts +8 -0
  99. package/dist/unplugin.mjs +2 -2
  100. package/dist/vite.mjs +1 -1
  101. package/package.json +61 -44
package/dist/module.d.mts CHANGED
@@ -40,6 +40,13 @@ interface ModuleOptions {
40
40
  * @see https://ui.nuxt.com/docs/getting-started/installation/nuxt#themetransitions
41
41
  */
42
42
  transitions?: boolean;
43
+ /**
44
+ * Remove all default theme classes from components, keeping only their
45
+ * structure and the classes you supply via `class`, `ui` or `app.config.ui`.
46
+ * @defaultValue `false`
47
+ * @see https://ui.nuxt.com/docs/getting-started/installation/nuxt#themeunstyled
48
+ */
49
+ unstyled?: boolean;
43
50
  /**
44
51
  * The default variants to use for components
45
52
  * @see https://ui.nuxt.com/docs/getting-started/installation/nuxt#themedefaultvariants
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxt/ui",
3
- "version": "4.8.1",
3
+ "version": "4.9.0",
4
4
  "docs": "https://ui.nuxt.com/docs/getting-started/installation/nuxt",
5
5
  "configKey": "ui",
6
6
  "compatibility": {
package/dist/module.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defu } from 'defu';
2
2
  import { defineNuxtModule, createResolver, addPlugin, hasNuxtModule, addComponentsDir, addImportsDir, addImports } from '@nuxt/kit';
3
- import { d as defaultOptions, r as resolveColors, g as getDefaultConfig, p as publicComposables, a as addTemplates } from './shared/ui.BzjkAvk7.mjs';
3
+ import { d as defaultOptions, r as resolveColors, g as getDefaultConfig, p as publicComposables, a as addTemplates } from './shared/ui.BfHSFiIO.mjs';
4
4
  import '../dist/runtime/utils/index.js';
5
5
  import 'node:url';
6
6
  import 'scule';
@@ -11,7 +11,7 @@ import 'pathe';
11
11
  import 'tinyglobby';
12
12
 
13
13
  const name = "@nuxt/ui";
14
- const version = "4.8.1";
14
+ const version = "4.9.0";
15
15
 
16
16
  const module$1 = defineNuxtModule({
17
17
  meta: {
@@ -43,7 +43,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...appConfig.ui?.avatar || {}
43
43
  }));
44
44
  const rootClass = computed(() => ui.value.root({ class: [props.ui?.root, props.class] }));
45
45
  const sizePx = computed(() => {
46
- const sizeClass = rootClass.value.split(" ").find((c) => /^size-\d+$/.test(c));
46
+ const sizeClass = (rootClass.value || "").split(" ").find((c) => /^size-\d+$/.test(c));
47
47
  if (sizeClass) {
48
48
  const num = Number.parseFloat(sizeClass.split("-")[1] ?? "");
49
49
  if (!Number.isNaN(num)) return num * 4;
@@ -100,7 +100,6 @@ function onClose() {
100
100
  :aria-label="props.title"
101
101
  v-bind="{ to: props.to, target: props.target, ...$attrs }"
102
102
  :class="prefix('focus:outline-none')"
103
- tabindex="-1"
104
103
  raw
105
104
  >
106
105
  <span :class="prefix('absolute inset-0')" aria-hidden="true" />
@@ -74,6 +74,14 @@ const ariaLabel = computed(() => {
74
74
 
75
75
  <template>
76
76
  <Primitive :as="props.as" :data-orientation="props.orientation" data-slot="root" :class="ui.root({ class: [props.ui?.root, props.class] })" @click="props.onClick">
77
+ <ULink
78
+ v-if="props.to"
79
+ :aria-label="ariaLabel"
80
+ v-bind="{ to: props.to, target: props.target, ...$attrs }"
81
+ :class="prefix('focus:outline-none absolute inset-0')"
82
+ raw
83
+ />
84
+
77
85
  <div v-if="props.image || !!slots.header" data-slot="header" :class="ui.header({ class: props.ui?.header })">
78
86
  <slot name="header" :ui="ui">
79
87
  <component
@@ -86,16 +94,6 @@ const ariaLabel = computed(() => {
86
94
  </div>
87
95
 
88
96
  <div data-slot="body" :class="ui.body({ class: props.ui?.body })">
89
- <ULink
90
- v-if="props.to"
91
- :aria-label="ariaLabel"
92
- v-bind="{ to: props.to, target: props.target, ...$attrs }"
93
- :class="prefix('focus:outline-none peer')"
94
- raw
95
- >
96
- <span :class="prefix('absolute inset-0')" aria-hidden="true" />
97
- </ULink>
98
-
99
97
  <slot name="body">
100
98
  <div v-if="date || !!slots.date || (props.badge || !!slots.badge)" data-slot="meta" :class="ui.meta({ class: props.ui?.meta })">
101
99
  <slot name="badge">
@@ -1,4 +1,4 @@
1
- import type { CalendarRootProps, CalendarRootEmits, RangeCalendarRootProps, RangeCalendarRootEmits, DateRange, CalendarCellTriggerProps } from 'reka-ui';
1
+ import type { CalendarRootProps, CalendarRootEmits, RangeCalendarRootProps, RangeCalendarRootEmits, MonthPickerRootProps, YearPickerRootProps, DateRange, CalendarCellTriggerProps } from 'reka-ui';
2
2
  import type { VNode } from 'vue';
3
3
  import type { DateValue } from '@internationalized/date';
4
4
  import type { AppConfig } from '@nuxt/schema';
@@ -6,16 +6,28 @@ import theme from '#build/ui/calendar';
6
6
  import type { ButtonProps, IconProps, LinkPropsKeys } from '../types';
7
7
  import type { ComponentConfig } from '../types/tv';
8
8
  type Calendar = ComponentConfig<typeof theme, AppConfig, 'calendar'>;
9
+ export type CalendarType = 'date' | 'month' | 'year';
10
+ export type CalendarView = 'day' | 'month' | 'year';
9
11
  type CalendarDefaultValue<R extends boolean = false, M extends boolean = false> = R extends true ? DateRange : M extends true ? DateValue[] : DateValue;
10
12
  type CalendarModelValue<R extends boolean = false, M extends boolean = false> = R extends true ? (DateRange | null) : M extends true ? (DateValue[] | undefined) : (DateValue | undefined);
11
- type _CalendarRootProps = Omit<CalendarRootProps, 'as' | 'asChild' | 'modelValue' | 'defaultValue' | 'dir' | 'locale' | 'calendarLabel' | 'multiple'>;
12
- type _RangeCalendarRootProps = Omit<RangeCalendarRootProps, 'as' | 'asChild' | 'modelValue' | 'defaultValue' | 'dir' | 'locale' | 'calendarLabel' | 'multiple'>;
13
- export interface CalendarProps<R extends boolean = false, M extends boolean = false> extends _RangeCalendarRootProps, _CalendarRootProps {
13
+ type _CalendarRootProps = Omit<CalendarRootProps, 'as' | 'asChild' | 'modelValue' | 'defaultValue' | 'dir' | 'calendarLabel' | 'multiple'>;
14
+ type _RangeCalendarRootProps = Omit<RangeCalendarRootProps, 'as' | 'asChild' | 'modelValue' | 'defaultValue' | 'dir' | 'calendarLabel' | 'multiple'>;
15
+ type _MonthPickerRootProps = Pick<MonthPickerRootProps, 'isMonthDisabled' | 'isMonthUnavailable'>;
16
+ type _YearPickerRootProps = Pick<YearPickerRootProps, 'isYearDisabled' | 'isYearUnavailable'>;
17
+ export interface CalendarProps<R extends boolean = false, M extends boolean = false> extends _RangeCalendarRootProps, _CalendarRootProps, _MonthPickerRootProps, _YearPickerRootProps {
14
18
  /**
15
19
  * The element or component this component should render as.
16
20
  * @defaultValue 'div'
17
21
  */
18
22
  as?: any;
23
+ /**
24
+ * The type of picker.
25
+ * - `date` renders a day calendar whose heading can switch to a month then year view.
26
+ * - `month` renders a standalone month picker.
27
+ * - `year` renders a standalone year picker.
28
+ * @defaultValue 'date'
29
+ */
30
+ type?: CalendarType;
19
31
  /**
20
32
  * The icon to use for the next year control.
21
33
  * @defaultValue appConfig.ui.icons.chevronDoubleRight
@@ -60,6 +72,13 @@ export interface CalendarProps<R extends boolean = false, M extends boolean = fa
60
72
  * `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
61
73
  */
62
74
  prevMonth?: Omit<ButtonProps, LinkPropsKeys>;
75
+ /**
76
+ * Whether to make the heading a button that switches between the day, month and year views.
77
+ * Has no effect when `type` is `year`. Can be an object to override the button props.
78
+ * `{ color: 'neutral', variant: 'ghost', block: true }`{lang="ts-type"}
79
+ * @defaultValue true
80
+ */
81
+ viewControl?: boolean | Omit<ButtonProps, LinkPropsKeys>;
63
82
  /**
64
83
  * @defaultValue 'primary'
65
84
  */
@@ -88,22 +107,39 @@ export interface CalendarProps<R extends boolean = false, M extends boolean = fa
88
107
  }
89
108
  export interface CalendarEmits<R extends boolean = false, M extends boolean = false> extends Omit<CalendarRootEmits & RangeCalendarRootEmits, 'update:modelValue'> {
90
109
  'update:modelValue': [value: CalendarModelValue<R, M>];
110
+ 'update:placeholder': [date: DateValue];
111
+ 'update:startValue': [date: DateValue | undefined];
112
+ 'update:validModelValue': [date: DateRange];
91
113
  }
92
114
  export interface CalendarSlots {
93
115
  'heading'?: (props: {
94
116
  value: string;
117
+ view: CalendarView;
118
+ date: DateValue;
119
+ setView: (view: CalendarView) => void;
120
+ setPlaceholder: (date: DateValue) => void;
95
121
  }) => VNode[];
96
122
  'day'?: (props: Pick<CalendarCellTriggerProps, 'day'>) => VNode[];
97
123
  'week-day'?: (props: {
98
124
  day: string;
99
125
  }) => VNode[];
126
+ 'month-cell'?: (props: {
127
+ month: DateValue;
128
+ selected: boolean;
129
+ disabled: boolean;
130
+ }) => VNode[];
131
+ 'year-cell'?: (props: {
132
+ year: DateValue;
133
+ selected: boolean;
134
+ disabled: boolean;
135
+ }) => VNode[];
100
136
  }
101
137
  declare const _default: typeof __VLS_export;
102
138
  export default _default;
103
139
  declare const __VLS_export: <R extends boolean, M extends boolean>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
104
140
  props: import("vue").PublicProps & __VLS_PrettifyLocal<CalendarProps<R, M> & {
105
141
  "onUpdate:modelValue"?: ((value: CalendarModelValue<R, M>) => any) | undefined;
106
- "onUpdate:placeholder"?: ((...args: DateValue[]) => any) | undefined;
142
+ "onUpdate:placeholder"?: ((date: DateValue) => any) | undefined;
107
143
  "onUpdate:validModelValue"?: ((date: DateRange) => any) | undefined;
108
144
  "onUpdate:startValue"?: ((date: DateValue | undefined) => any) | undefined;
109
145
  }> & (typeof globalThis extends {
@@ -112,7 +148,7 @@ declare const __VLS_export: <R extends boolean, M extends boolean>(__VLS_props:
112
148
  expose: (exposed: {}) => void;
113
149
  attrs: any;
114
150
  slots: CalendarSlots;
115
- emit: ((evt: "update:modelValue", value: CalendarModelValue<R, M>) => void) & ((evt: "update:placeholder", ...args: [date: DateValue] & [date: DateValue]) => void) & ((evt: "update:validModelValue", date: DateRange) => void) & ((evt: "update:startValue", date: DateValue | undefined) => void);
151
+ emit: ((evt: "update:modelValue", value: CalendarModelValue<R, M>) => void) & ((evt: "update:placeholder", date: DateValue) => void) & ((evt: "update:validModelValue", date: DateRange) => void) & ((evt: "update:startValue", date: DateValue | undefined) => void);
116
152
  }>) => import("vue").VNode & {
117
153
  __ctx?: Awaited<typeof __VLS_setup>;
118
154
  };
@@ -1,13 +1,13 @@
1
1
  <script>
2
2
  import { getWeekNumber } from "reka-ui/date";
3
+ import { getLocalTimeZone, today } from "@internationalized/date";
3
4
  import theme from "#build/ui/calendar";
4
5
  </script>
5
6
 
6
7
  <script setup>
7
- import { computed } from "vue";
8
- import {} from "reka-ui";
8
+ import { computed, ref, shallowRef, watch } from "vue";
9
9
  import { useForwardProps } from "../composables/useForwardProps";
10
- import { Calendar as SingleCalendar, RangeCalendar } from "reka-ui/namespaced";
10
+ import { Calendar as SingleCalendar, RangeCalendar, MonthPicker, MonthRangePicker, YearPicker, YearRangePicker } from "reka-ui/namespaced";
11
11
  import { reactiveOmit } from "@vueuse/core";
12
12
  import { useAppConfig } from "#imports";
13
13
  import { useComponentProps } from "../composables/useComponentProps";
@@ -16,6 +16,7 @@ import { tv } from "../utils/tv";
16
16
  import UButton from "./Button.vue";
17
17
  const _props = defineProps({
18
18
  as: { type: null, required: false },
19
+ type: { type: String, required: false, default: "date" },
19
20
  nextYearIcon: { type: null, required: false },
20
21
  nextYear: { type: Object, required: false },
21
22
  nextMonthIcon: { type: null, required: false },
@@ -24,6 +25,7 @@ const _props = defineProps({
24
25
  prevYear: { type: Object, required: false },
25
26
  prevMonthIcon: { type: null, required: false },
26
27
  prevMonth: { type: Object, required: false },
28
+ viewControl: { type: [Boolean, Object], required: false, default: true },
27
29
  color: { type: null, required: false },
28
30
  variant: { type: null, required: false },
29
31
  size: { type: null, required: false },
@@ -47,6 +49,7 @@ const _props = defineProps({
47
49
  fixedWeeks: { type: Boolean, required: false, default: true },
48
50
  maxValue: { type: Object, required: false },
49
51
  minValue: { type: Object, required: false },
52
+ locale: { type: String, required: false },
50
53
  numberOfMonths: { type: Number, required: false },
51
54
  disabled: { type: Boolean, required: false },
52
55
  readonly: { type: Boolean, required: false },
@@ -57,71 +60,181 @@ const _props = defineProps({
57
60
  nextPage: { type: Function, required: false },
58
61
  prevPage: { type: Function, required: false },
59
62
  disableDaysOutsideCurrentView: { type: Boolean, required: false },
60
- fixedDate: { type: String, required: false }
63
+ fixedDate: { type: String, required: false },
64
+ isMonthDisabled: { type: Function, required: false },
65
+ isMonthUnavailable: { type: Function, required: false },
66
+ isYearDisabled: { type: Function, required: false },
67
+ isYearUnavailable: { type: Function, required: false }
61
68
  });
62
- const emits = defineEmits(["update:modelValue", "update:placeholder", "update:validModelValue", "update:startValue"]);
69
+ const emits = defineEmits(["update:modelValue", "update:placeholder", "update:startValue", "update:validModelValue"]);
63
70
  defineSlots();
64
71
  const props = useComponentProps("calendar", _props);
65
72
  const { dir, t, locale } = useLocale();
66
73
  const appConfig = useAppConfig();
67
- const rootProps = useForwardProps(reactiveOmit(props, "range", "modelValue", "defaultValue", "color", "variant", "size", "monthControls", "yearControls", "class", "ui"), emits);
74
+ const VIEWS = ["day", "month", "year"];
75
+ const minView = computed(() => props.type === "year" ? "year" : props.type === "month" ? "month" : "day");
76
+ const maxView = "year";
77
+ const view = ref(minView.value);
78
+ watch(() => props.type, () => {
79
+ view.value = minView.value;
80
+ });
81
+ const switchable = computed(() => minView.value !== maxView);
82
+ const isMinView = computed(() => view.value === minView.value);
83
+ function clampView(value) {
84
+ const min = VIEWS.indexOf(minView.value);
85
+ const max = VIEWS.indexOf(maxView);
86
+ return VIEWS[Math.min(Math.max(VIEWS.indexOf(value), min), max)];
87
+ }
88
+ function setView(value) {
89
+ view.value = clampView(value);
90
+ }
91
+ function cycleView() {
92
+ const max = VIEWS.indexOf(maxView);
93
+ const next = VIEWS.indexOf(view.value) >= max ? minView.value : VIEWS[VIEWS.indexOf(view.value) + 1];
94
+ view.value = next;
95
+ }
96
+ function resolveDateValue(value) {
97
+ if (Array.isArray(value)) {
98
+ return value[0];
99
+ }
100
+ if (!value) {
101
+ return void 0;
102
+ }
103
+ if ("start" in value || "end" in value) {
104
+ const range = value;
105
+ return range.start ?? range.end ?? void 0;
106
+ }
107
+ return value;
108
+ }
109
+ const placeholder = shallowRef(
110
+ props.placeholder ?? resolveDateValue(props.modelValue) ?? resolveDateValue(props.defaultValue) ?? today(getLocalTimeZone())
111
+ );
112
+ watch(() => props.placeholder, (value) => {
113
+ if (value) {
114
+ placeholder.value = value;
115
+ }
116
+ });
117
+ function setPlaceholder(date) {
118
+ placeholder.value = date;
119
+ emits("update:placeholder", date);
120
+ }
121
+ function onSelect(value) {
122
+ if (isMinView.value) {
123
+ emits("update:modelValue", value);
124
+ return;
125
+ }
126
+ const resolved = resolveDateValue(value);
127
+ if (resolved) {
128
+ setPlaceholder(resolved);
129
+ }
130
+ setView(VIEWS[VIEWS.indexOf(view.value) - 1]);
131
+ }
132
+ function paginateYear(date, sign) {
133
+ return sign === -1 ? date.subtract({ years: 1 }) : date.add({ years: 1 });
134
+ }
135
+ const Picker = computed(() => {
136
+ const range = props.range && isMinView.value;
137
+ if (view.value === "year") {
138
+ return range ? YearRangePicker : YearPicker;
139
+ }
140
+ if (view.value === "month") {
141
+ return range ? MonthRangePicker : MonthPicker;
142
+ }
143
+ return props.range ? RangeCalendar : SingleCalendar;
144
+ });
145
+ const omittedProps = ["type", "placeholder", "range", "modelValue", "defaultValue", "color", "variant", "size", "monthControls", "yearControls", "viewControl", "class", "ui"];
146
+ const dayOnlyProps = ["pagedNavigation", "weekStartsOn", "weekdayFormat", "fixedWeeks", "numberOfMonths", "isDateDisabled", "isDateUnavailable", "isDateHighlightable", "disableDaysOutsideCurrentView", "maximumDays"];
147
+ const monthOnlyProps = ["isMonthDisabled", "isMonthUnavailable"];
148
+ const yearOnlyProps = ["isYearDisabled", "isYearUnavailable"];
149
+ const rangeOnlyProps = ["allowNonContiguousRanges", "fixedDate"];
150
+ const rootProps = useForwardProps(reactiveOmit(
151
+ props,
152
+ (_, key) => omittedProps.includes(key) || view.value !== "day" && dayOnlyProps.includes(key) || view.value !== "month" && monthOnlyProps.includes(key) || view.value !== "year" && yearOnlyProps.includes(key) || !isMinView.value && rangeOnlyProps.includes(key)
153
+ ));
154
+ function cellProps(cellDate, monthValue) {
155
+ if (view.value === "month") {
156
+ return { month: cellDate };
157
+ }
158
+ if (view.value === "year") {
159
+ return { year: cellDate };
160
+ }
161
+ return { day: cellDate, month: monthValue };
162
+ }
68
163
  const nextYearIcon = computed(() => props.nextYearIcon || (dir.value === "rtl" ? appConfig.ui.icons.chevronDoubleLeft : appConfig.ui.icons.chevronDoubleRight));
69
164
  const nextMonthIcon = computed(() => props.nextMonthIcon || (dir.value === "rtl" ? appConfig.ui.icons.chevronLeft : appConfig.ui.icons.chevronRight));
70
165
  const prevYearIcon = computed(() => props.prevYearIcon || (dir.value === "rtl" ? appConfig.ui.icons.chevronDoubleRight : appConfig.ui.icons.chevronDoubleLeft));
71
166
  const prevMonthIcon = computed(() => props.prevMonthIcon || (dir.value === "rtl" ? appConfig.ui.icons.chevronRight : appConfig.ui.icons.chevronLeft));
167
+ const prevLabel = computed(() => view.value === "day" ? t("calendar.prevMonth") : t("calendar.prevYear"));
168
+ const nextLabel = computed(() => view.value === "day" ? t("calendar.nextMonth") : t("calendar.nextYear"));
72
169
  const ui = computed(() => tv({ extend: tv(theme), ...appConfig.ui?.calendar || {} })({
73
170
  color: props.color,
74
171
  size: props.size,
75
172
  variant: props.variant,
76
- weekNumbers: props.weekNumbers
173
+ weekNumbers: props.weekNumbers,
174
+ view: view.value
77
175
  }));
78
- function paginateYear(date, sign) {
79
- if (sign === -1) {
80
- return date.subtract({ years: 1 });
81
- }
82
- return date.add({ years: 1 });
83
- }
84
- const Calendar = computed(() => props.range ? RangeCalendar : SingleCalendar);
85
176
  </script>
86
177
 
87
178
  <template>
88
- <Calendar.Root
89
- v-slot="{ weekDays, grid }"
179
+ <Picker.Root
180
+ v-slot="{ weekDays, grid, date }"
90
181
  v-bind="rootProps"
91
- :model-value="props.modelValue"
92
- :default-value="props.defaultValue"
182
+ :model-value="isMinView ? props.modelValue : void 0"
183
+ :default-value="isMinView ? props.defaultValue : void 0"
184
+ :placeholder="placeholder"
93
185
  data-slot="root"
94
186
  :class="ui.root({ class: [props.ui?.root, props.class] })"
187
+ @update:placeholder="setPlaceholder"
188
+ @update:model-value="onSelect"
189
+ @update:start-value="(value) => emits('update:startValue', value)"
190
+ @update:valid-model-value="(value) => emits('update:validModelValue', value)"
95
191
  >
96
- <Calendar.Header data-slot="header" :class="ui.header({ class: props.ui?.header })">
97
- <Calendar.Prev v-if="props.yearControls" :prev-page="(date) => paginateYear(date, -1)" :aria-label="t('calendar.prevYear')" as-child>
192
+ <Picker.Header data-slot="header" :class="ui.header({ class: props.ui?.header })">
193
+ <Picker.Prev v-if="view === 'day' && props.yearControls" :prev-page="(date) => paginateYear(date, -1)" :aria-label="t('calendar.prevYear')" as-child>
98
194
  <UButton :icon="prevYearIcon" :size="props.size" color="neutral" variant="ghost" v-bind="props.prevYear" />
99
- </Calendar.Prev>
100
- <Calendar.Prev v-if="props.monthControls" :aria-label="t('calendar.prevMonth')" as-child>
195
+ </Picker.Prev>
196
+ <Picker.Prev v-if="view !== 'day' || props.monthControls" :aria-label="prevLabel" as-child>
101
197
  <UButton :icon="prevMonthIcon" :size="props.size" color="neutral" variant="ghost" v-bind="props.prevMonth" />
102
- </Calendar.Prev>
103
- <Calendar.Heading v-slot="{ headingValue }" data-slot="heading" :class="ui.heading({ class: props.ui?.heading })">
104
- <slot name="heading" :value="headingValue">
105
- {{ headingValue }}
198
+ </Picker.Prev>
199
+ <Picker.Heading v-slot="{ headingValue }" data-slot="heading" :class="ui.heading({ class: props.ui?.heading })">
200
+ <slot
201
+ name="heading"
202
+ :value="headingValue"
203
+ :view="view"
204
+ :date="date"
205
+ :set-view="setView"
206
+ :set-placeholder="setPlaceholder"
207
+ >
208
+ <UButton
209
+ v-if="switchable && props.viewControl"
210
+ :label="headingValue"
211
+ :size="props.size"
212
+ color="neutral"
213
+ variant="ghost"
214
+ block
215
+ v-bind="typeof props.viewControl === 'object' ? props.viewControl : {}"
216
+ @click="cycleView"
217
+ />
218
+ <span v-else data-slot="headingLabel" :class="ui.headingLabel({ class: props.ui?.headingLabel })">{{ headingValue }}</span>
106
219
  </slot>
107
- </Calendar.Heading>
108
- <Calendar.Next v-if="props.monthControls" :aria-label="t('calendar.nextMonth')" as-child>
220
+ </Picker.Heading>
221
+ <Picker.Next v-if="view !== 'day' || props.monthControls" :aria-label="nextLabel" as-child>
109
222
  <UButton :icon="nextMonthIcon" :size="props.size" color="neutral" variant="ghost" v-bind="props.nextMonth" />
110
- </Calendar.Next>
111
- <Calendar.Next v-if="props.yearControls" :next-page="(date) => paginateYear(date, 1)" :aria-label="t('calendar.nextYear')" as-child>
223
+ </Picker.Next>
224
+ <Picker.Next v-if="view === 'day' && props.yearControls" :next-page="(date) => paginateYear(date, 1)" :aria-label="t('calendar.nextYear')" as-child>
112
225
  <UButton :icon="nextYearIcon" :size="props.size" color="neutral" variant="ghost" v-bind="props.nextYear" />
113
- </Calendar.Next>
114
- </Calendar.Header>
226
+ </Picker.Next>
227
+ </Picker.Header>
115
228
  <div data-slot="body" :class="ui.body({ class: props.ui?.body })">
116
- <Calendar.Grid
117
- v-for="month in grid"
229
+ <Picker.Grid
230
+ v-for="month in Array.isArray(grid) ? grid : [grid]"
118
231
  :key="month.value.toString()"
119
232
  data-slot="grid"
120
233
  :class="ui.grid({ class: props.ui?.grid })"
121
234
  >
122
- <Calendar.GridHead>
123
- <Calendar.GridRow data-slot="gridWeekDaysRow" :class="ui.gridWeekDaysRow({ class: props.ui?.gridWeekDaysRow })">
124
- <Calendar.HeadCell
235
+ <Picker.GridHead v-if="'GridHead' in Picker">
236
+ <Picker.GridRow data-slot="gridWeekDaysRow" :class="ui.gridWeekDaysRow({ class: props.ui?.gridWeekDaysRow })">
237
+ <Picker.HeadCell
125
238
  v-for="day in weekDays"
126
239
  :key="day"
127
240
  data-slot="headCell"
@@ -130,45 +243,51 @@ const Calendar = computed(() => props.range ? RangeCalendar : SingleCalendar);
130
243
  <slot name="week-day" :day="day">
131
244
  {{ day }}
132
245
  </slot>
133
- </Calendar.HeadCell>
134
- </Calendar.GridRow>
135
- </Calendar.GridHead>
136
- <Calendar.GridBody data-slot="gridBody" :class="ui.gridBody({ class: props.ui?.gridBody })">
137
- <Calendar.GridRow
138
- v-for="(weekDates, index) in month.rows"
139
- :key="`weekDate-${index}`"
246
+ </Picker.HeadCell>
247
+ </Picker.GridRow>
248
+ </Picker.GridHead>
249
+ <Picker.GridBody data-slot="gridBody" :class="ui.gridBody({ class: props.ui?.gridBody })">
250
+ <Picker.GridRow
251
+ v-for="(row, index) in month.rows"
252
+ :key="`row-${index}`"
140
253
  data-slot="gridRow"
141
254
  :class="ui.gridRow({ class: props.ui?.gridRow })"
142
255
  >
143
256
  <td
144
- v-if="props.weekNumbers && weekDates[0]"
257
+ v-if="view === 'day' && props.weekNumbers && row[0]"
145
258
  role="gridcell"
146
259
  data-slot="cellWeek"
147
260
  :class="ui.cellWeek({ class: props.ui?.cellWeek })"
148
261
  >
149
- {{ getWeekNumber(weekDates[0], locale.code) }}
262
+ {{ getWeekNumber(row[0], props.locale ?? locale.code) }}
150
263
  </td>
151
- <Calendar.Cell
152
- v-for="weekDate in weekDates"
153
- :key="weekDate.toString()"
154
- :date="weekDate"
264
+ <Picker.Cell
265
+ v-for="cellDate in row"
266
+ :key="cellDate.toString()"
267
+ :date="cellDate"
155
268
  data-slot="cell"
156
269
  :class="ui.cell({ class: props.ui?.cell })"
157
270
  >
158
- <Calendar.CellTrigger
159
- :day="weekDate"
160
- :month="month.value"
271
+ <Picker.CellTrigger
272
+ v-slot="cell"
273
+ v-bind="cellProps(cellDate, month.value)"
161
274
  data-slot="cellTrigger"
162
275
  :class="ui.cellTrigger({ class: props.ui?.cellTrigger })"
163
276
  >
164
- <slot name="day" :day="weekDate">
165
- {{ weekDate.day }}
277
+ <slot v-if="view === 'day'" name="day" :day="cellDate">
278
+ {{ cellDate.day }}
279
+ </slot>
280
+ <slot v-else-if="view === 'month'" name="month-cell" :month="cellDate" :selected="cell.selected" :disabled="cell.disabled">
281
+ {{ cell.monthValue }}
282
+ </slot>
283
+ <slot v-else name="year-cell" :year="cellDate" :selected="cell.selected" :disabled="cell.disabled">
284
+ {{ cell.yearValue }}
166
285
  </slot>
167
- </Calendar.CellTrigger>
168
- </Calendar.Cell>
169
- </Calendar.GridRow>
170
- </Calendar.GridBody>
171
- </Calendar.Grid>
286
+ </Picker.CellTrigger>
287
+ </Picker.Cell>
288
+ </Picker.GridRow>
289
+ </Picker.GridBody>
290
+ </Picker.Grid>
172
291
  </div>
173
- </Calendar.Root>
292
+ </Picker.Root>
174
293
  </template>
@@ -1,4 +1,4 @@
1
- import type { CalendarRootProps, CalendarRootEmits, RangeCalendarRootProps, RangeCalendarRootEmits, DateRange, CalendarCellTriggerProps } from 'reka-ui';
1
+ import type { CalendarRootProps, CalendarRootEmits, RangeCalendarRootProps, RangeCalendarRootEmits, MonthPickerRootProps, YearPickerRootProps, DateRange, CalendarCellTriggerProps } from 'reka-ui';
2
2
  import type { VNode } from 'vue';
3
3
  import type { DateValue } from '@internationalized/date';
4
4
  import type { AppConfig } from '@nuxt/schema';
@@ -6,16 +6,28 @@ import theme from '#build/ui/calendar';
6
6
  import type { ButtonProps, IconProps, LinkPropsKeys } from '../types';
7
7
  import type { ComponentConfig } from '../types/tv';
8
8
  type Calendar = ComponentConfig<typeof theme, AppConfig, 'calendar'>;
9
+ export type CalendarType = 'date' | 'month' | 'year';
10
+ export type CalendarView = 'day' | 'month' | 'year';
9
11
  type CalendarDefaultValue<R extends boolean = false, M extends boolean = false> = R extends true ? DateRange : M extends true ? DateValue[] : DateValue;
10
12
  type CalendarModelValue<R extends boolean = false, M extends boolean = false> = R extends true ? (DateRange | null) : M extends true ? (DateValue[] | undefined) : (DateValue | undefined);
11
- type _CalendarRootProps = Omit<CalendarRootProps, 'as' | 'asChild' | 'modelValue' | 'defaultValue' | 'dir' | 'locale' | 'calendarLabel' | 'multiple'>;
12
- type _RangeCalendarRootProps = Omit<RangeCalendarRootProps, 'as' | 'asChild' | 'modelValue' | 'defaultValue' | 'dir' | 'locale' | 'calendarLabel' | 'multiple'>;
13
- export interface CalendarProps<R extends boolean = false, M extends boolean = false> extends _RangeCalendarRootProps, _CalendarRootProps {
13
+ type _CalendarRootProps = Omit<CalendarRootProps, 'as' | 'asChild' | 'modelValue' | 'defaultValue' | 'dir' | 'calendarLabel' | 'multiple'>;
14
+ type _RangeCalendarRootProps = Omit<RangeCalendarRootProps, 'as' | 'asChild' | 'modelValue' | 'defaultValue' | 'dir' | 'calendarLabel' | 'multiple'>;
15
+ type _MonthPickerRootProps = Pick<MonthPickerRootProps, 'isMonthDisabled' | 'isMonthUnavailable'>;
16
+ type _YearPickerRootProps = Pick<YearPickerRootProps, 'isYearDisabled' | 'isYearUnavailable'>;
17
+ export interface CalendarProps<R extends boolean = false, M extends boolean = false> extends _RangeCalendarRootProps, _CalendarRootProps, _MonthPickerRootProps, _YearPickerRootProps {
14
18
  /**
15
19
  * The element or component this component should render as.
16
20
  * @defaultValue 'div'
17
21
  */
18
22
  as?: any;
23
+ /**
24
+ * The type of picker.
25
+ * - `date` renders a day calendar whose heading can switch to a month then year view.
26
+ * - `month` renders a standalone month picker.
27
+ * - `year` renders a standalone year picker.
28
+ * @defaultValue 'date'
29
+ */
30
+ type?: CalendarType;
19
31
  /**
20
32
  * The icon to use for the next year control.
21
33
  * @defaultValue appConfig.ui.icons.chevronDoubleRight
@@ -60,6 +72,13 @@ export interface CalendarProps<R extends boolean = false, M extends boolean = fa
60
72
  * `{ color: 'neutral', variant: 'ghost' }`{lang="ts-type"}
61
73
  */
62
74
  prevMonth?: Omit<ButtonProps, LinkPropsKeys>;
75
+ /**
76
+ * Whether to make the heading a button that switches between the day, month and year views.
77
+ * Has no effect when `type` is `year`. Can be an object to override the button props.
78
+ * `{ color: 'neutral', variant: 'ghost', block: true }`{lang="ts-type"}
79
+ * @defaultValue true
80
+ */
81
+ viewControl?: boolean | Omit<ButtonProps, LinkPropsKeys>;
63
82
  /**
64
83
  * @defaultValue 'primary'
65
84
  */
@@ -88,22 +107,39 @@ export interface CalendarProps<R extends boolean = false, M extends boolean = fa
88
107
  }
89
108
  export interface CalendarEmits<R extends boolean = false, M extends boolean = false> extends Omit<CalendarRootEmits & RangeCalendarRootEmits, 'update:modelValue'> {
90
109
  'update:modelValue': [value: CalendarModelValue<R, M>];
110
+ 'update:placeholder': [date: DateValue];
111
+ 'update:startValue': [date: DateValue | undefined];
112
+ 'update:validModelValue': [date: DateRange];
91
113
  }
92
114
  export interface CalendarSlots {
93
115
  'heading'?: (props: {
94
116
  value: string;
117
+ view: CalendarView;
118
+ date: DateValue;
119
+ setView: (view: CalendarView) => void;
120
+ setPlaceholder: (date: DateValue) => void;
95
121
  }) => VNode[];
96
122
  'day'?: (props: Pick<CalendarCellTriggerProps, 'day'>) => VNode[];
97
123
  'week-day'?: (props: {
98
124
  day: string;
99
125
  }) => VNode[];
126
+ 'month-cell'?: (props: {
127
+ month: DateValue;
128
+ selected: boolean;
129
+ disabled: boolean;
130
+ }) => VNode[];
131
+ 'year-cell'?: (props: {
132
+ year: DateValue;
133
+ selected: boolean;
134
+ disabled: boolean;
135
+ }) => VNode[];
100
136
  }
101
137
  declare const _default: typeof __VLS_export;
102
138
  export default _default;
103
139
  declare const __VLS_export: <R extends boolean, M extends boolean>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
104
140
  props: import("vue").PublicProps & __VLS_PrettifyLocal<CalendarProps<R, M> & {
105
141
  "onUpdate:modelValue"?: ((value: CalendarModelValue<R, M>) => any) | undefined;
106
- "onUpdate:placeholder"?: ((...args: DateValue[]) => any) | undefined;
142
+ "onUpdate:placeholder"?: ((date: DateValue) => any) | undefined;
107
143
  "onUpdate:validModelValue"?: ((date: DateRange) => any) | undefined;
108
144
  "onUpdate:startValue"?: ((date: DateValue | undefined) => any) | undefined;
109
145
  }> & (typeof globalThis extends {
@@ -112,7 +148,7 @@ declare const __VLS_export: <R extends boolean, M extends boolean>(__VLS_props:
112
148
  expose: (exposed: {}) => void;
113
149
  attrs: any;
114
150
  slots: CalendarSlots;
115
- emit: ((evt: "update:modelValue", value: CalendarModelValue<R, M>) => void) & ((evt: "update:placeholder", ...args: [date: DateValue] & [date: DateValue]) => void) & ((evt: "update:validModelValue", date: DateRange) => void) & ((evt: "update:startValue", date: DateValue | undefined) => void);
151
+ emit: ((evt: "update:modelValue", value: CalendarModelValue<R, M>) => void) & ((evt: "update:placeholder", date: DateValue) => void) & ((evt: "update:validModelValue", date: DateRange) => void) & ((evt: "update:startValue", date: DateValue | undefined) => void);
116
152
  }>) => import("vue").VNode & {
117
153
  __ctx?: Awaited<typeof __VLS_setup>;
118
154
  };