@youcan/celeste 0.7.2 → 0.8.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 (73) hide show
  1. package/dist/celeste.css +495 -30
  2. package/dist/cjs/calendar.cjs +2 -0
  3. package/dist/cjs/chunks/ComboboxContent.cjs +1 -1
  4. package/dist/cjs/chunks/DialogPortal.cjs +1 -1
  5. package/dist/cjs/chunks/DismissableLayer.cjs +0 -34
  6. package/dist/cjs/chunks/FocusScope.cjs +2 -2
  7. package/dist/cjs/chunks/MenuContentImpl.cjs +1 -1
  8. package/dist/cjs/chunks/SelectItemAlignedPosition.cjs +1 -1
  9. package/dist/cjs/chunks/calendar.cjs +923 -0
  10. package/dist/cjs/chunks/components.cjs +9 -1
  11. package/dist/cjs/chunks/counter-input.cjs +2 -13
  12. package/dist/cjs/chunks/date-picker.cjs +99 -0
  13. package/dist/cjs/chunks/defu.cjs +34 -0
  14. package/dist/cjs/chunks/popover-content.cjs +1 -1
  15. package/dist/cjs/chunks/range-calendar.cjs +1170 -0
  16. package/dist/cjs/chunks/range-picker.cjs +231 -0
  17. package/dist/cjs/chunks/text-input.cjs +1 -1
  18. package/dist/cjs/chunks/tooltip-content.cjs +3 -2
  19. package/dist/cjs/chunks/useCalendar.cjs +2439 -0
  20. package/dist/cjs/chunks/useLocale.cjs +20 -0
  21. package/dist/cjs/date-picker.cjs +2 -0
  22. package/dist/cjs/index.cjs +9 -1
  23. package/dist/cjs/range-calendar.cjs +2 -0
  24. package/dist/cjs/range-picker.cjs +2 -0
  25. package/dist/components/calendar/calendar.d.ts +19 -0
  26. package/dist/components/calendar/index.d.ts +2 -0
  27. package/dist/components/date-picker/date-picker.d.ts +27 -0
  28. package/dist/components/date-picker/index.d.ts +2 -0
  29. package/dist/components/index.d.ts +4 -0
  30. package/dist/components/range-calendar/index.d.ts +2 -0
  31. package/dist/components/range-calendar/range-calendar.d.ts +24 -0
  32. package/dist/components/range-picker/index.d.ts +2 -0
  33. package/dist/components/range-picker/range-picker.d.ts +38 -0
  34. package/dist/components/rich-editor/rich-editor.d.ts +1 -1
  35. package/dist/components/text-input/text-input.d.ts +2 -2
  36. package/dist/es/calendar.js +2 -0
  37. package/dist/es/chunks/ComboboxContent.js +1 -1
  38. package/dist/es/chunks/DialogPortal.js +1 -1
  39. package/dist/es/chunks/DismissableLayer.js +1 -29
  40. package/dist/es/chunks/FocusScope.js +1 -1
  41. package/dist/es/chunks/MenuContentImpl.js +1 -1
  42. package/dist/es/chunks/SelectItemAlignedPosition.js +1 -1
  43. package/dist/es/chunks/calendar.js +916 -0
  44. package/dist/es/chunks/components.js +9 -1
  45. package/dist/es/chunks/counter-input.js +1 -12
  46. package/dist/es/chunks/date-picker.js +92 -0
  47. package/dist/es/chunks/defu.js +29 -0
  48. package/dist/es/chunks/popover-content.js +1 -1
  49. package/dist/es/chunks/range-calendar.js +1163 -0
  50. package/dist/es/chunks/range-picker.js +224 -0
  51. package/dist/es/chunks/text-input.js +1 -1
  52. package/dist/es/chunks/tooltip-content.js +2 -1
  53. package/dist/es/chunks/useCalendar.js +2331 -0
  54. package/dist/es/chunks/useLocale.js +14 -0
  55. package/dist/es/date-picker.js +2 -0
  56. package/dist/es/index.js +6 -2
  57. package/dist/es/range-calendar.js +2 -0
  58. package/dist/es/range-picker.js +2 -0
  59. package/package.json +4 -3
  60. package/src/components/calendar/calendar.vue +305 -0
  61. package/src/components/calendar/index.ts +2 -0
  62. package/src/components/calendar/stories/calendar.stories.ts +76 -0
  63. package/src/components/date-picker/date-picker.vue +91 -0
  64. package/src/components/date-picker/index.ts +2 -0
  65. package/src/components/date-picker/stories/date-picker.stories.ts +92 -0
  66. package/src/components/index.ts +4 -0
  67. package/src/components/range-calendar/index.ts +2 -0
  68. package/src/components/range-calendar/range-calendar.vue +363 -0
  69. package/src/components/range-calendar/stories/range-calendar.stories.ts +46 -0
  70. package/src/components/range-picker/index.ts +2 -0
  71. package/src/components/range-picker/range-picker.vue +237 -0
  72. package/src/components/range-picker/stories/range-picker.stories.ts +97 -0
  73. package/src/components/text-input/text-input.vue +2 -1
@@ -0,0 +1,237 @@
1
+ <script setup lang="ts">
2
+ import type { DateRange } from 'reka-ui';
3
+ import type { RangeCalendarProps } from '@/components/range-calendar/range-calendar.vue';
4
+ import { DateFormatter, getLocalTimeZone, today } from '@internationalized/date';
5
+ import clsx from 'clsx';
6
+ import { computed, nextTick, ref, watch } from 'vue';
7
+ import Button from '@/components/button/button.vue';
8
+ import RangeCalendar from '@/components/range-calendar/range-calendar.vue';
9
+ import { useDelegatedProps } from '@/composables/use-delegated-props';
10
+
11
+ export interface RangePickerPreset {
12
+ label: string;
13
+ value: DateRange | (() => DateRange);
14
+ }
15
+
16
+ export interface RangePickerProps extends Omit<RangeCalendarProps, 'modelValue'> {
17
+ showPresets?: boolean;
18
+ footer?: boolean;
19
+ presets?: RangePickerPreset[];
20
+ rangeLabel?: string;
21
+ placeholderLabel?: string;
22
+ cancelLabel?: string;
23
+ applyLabel?: string;
24
+ }
25
+
26
+ const props = withDefaults(defineProps<RangePickerProps>(), {
27
+ showPresets: true,
28
+ footer: true,
29
+ rangeLabel: 'Range:',
30
+ placeholderLabel: 'Select a range',
31
+ cancelLabel: 'Cancel',
32
+ applyLabel: 'Apply',
33
+ });
34
+
35
+ const emits = defineEmits<{
36
+ apply: [value: DateRange];
37
+ cancel: [];
38
+ }>();
39
+
40
+ function defaultPresets(): RangePickerPreset[] {
41
+ const now = today(getLocalTimeZone());
42
+ return [
43
+ { label: 'Today', value: { start: now, end: now } },
44
+ { label: 'Last 7 days', value: { start: now.subtract({ days: 6 }), end: now } },
45
+ { label: 'Last 30 days', value: { start: now.subtract({ days: 29 }), end: now } },
46
+ { label: 'Last 3 months', value: { start: now.subtract({ months: 3 }), end: now } },
47
+ { label: 'Last 12 months', value: { start: now.subtract({ months: 12 }), end: now } },
48
+ { label: 'Month to date', value: { start: now.set({ day: 1 }), end: now } },
49
+ { label: 'Year to date', value: { start: now.set({ month: 1, day: 1 }), end: now } },
50
+ { label: 'All time', value: { start: undefined, end: undefined } },
51
+ ];
52
+ }
53
+
54
+ const modelValue = defineModel<DateRange>({
55
+ default: () => ({ start: undefined, end: undefined }),
56
+ });
57
+
58
+ const presetList = computed(() => props.presets ?? defaultPresets());
59
+ const activePreset = ref<string | null>(null);
60
+
61
+ const calendarProps = useDelegatedProps(props, [
62
+ 'class',
63
+ 'showPresets',
64
+ 'footer',
65
+ 'presets',
66
+ 'rangeLabel',
67
+ 'placeholderLabel',
68
+ 'cancelLabel',
69
+ 'applyLabel',
70
+ ]);
71
+
72
+ let settingFromPreset = false;
73
+
74
+ function resolvePreset(preset: RangePickerPreset): DateRange {
75
+ return typeof preset.value === 'function' ? preset.value() : preset.value;
76
+ }
77
+
78
+ function selectPreset(preset: RangePickerPreset): void {
79
+ settingFromPreset = true;
80
+ modelValue.value = resolvePreset(preset);
81
+ activePreset.value = preset.label;
82
+ nextTick(() => {
83
+ settingFromPreset = false;
84
+ });
85
+ }
86
+
87
+ watch(modelValue, () => {
88
+ if (!settingFromPreset)
89
+ activePreset.value = null;
90
+ }, { deep: true });
91
+
92
+ const rangeFormatter = computed(
93
+ () => new DateFormatter(props.locale ?? 'en-US', { month: 'short', day: '2-digit', year: 'numeric' }),
94
+ );
95
+
96
+ const formattedRange = computed(() => {
97
+ const value = modelValue.value;
98
+ if (!value?.start || !value?.end)
99
+ return '';
100
+
101
+ const tz = getLocalTimeZone();
102
+ return `${rangeFormatter.value.format(value.start.toDate(tz))} - ${rangeFormatter.value.format(value.end.toDate(tz))}`;
103
+ });
104
+ </script>
105
+
106
+ <template>
107
+ <div :class="clsx('celeste-range-picker', props.class)">
108
+ <aside v-if="showPresets" class="celeste-range-picker-presets">
109
+ <button
110
+ v-for="preset in presetList"
111
+ :key="preset.label"
112
+ type="button"
113
+ class="celeste-range-picker-preset"
114
+ :data-active="activePreset === preset.label ? '' : undefined"
115
+ @click="selectPreset(preset)"
116
+ >
117
+ {{ preset.label }}
118
+ </button>
119
+ </aside>
120
+
121
+ <div class="celeste-range-picker-content">
122
+ <div class="celeste-range-picker-calendar">
123
+ <RangeCalendar v-model="modelValue" v-bind="calendarProps" />
124
+ </div>
125
+
126
+ <div v-if="footer" class="celeste-range-picker-footer">
127
+ <p class="celeste-range-picker-hint">
128
+ <template v-if="formattedRange">
129
+ <span class="celeste-range-picker-hint-label">{{ rangeLabel }}&nbsp;</span>
130
+ <span class="celeste-range-picker-hint-value">{{ formattedRange }}</span>
131
+ </template>
132
+ <span v-else class="celeste-range-picker-hint-label">{{ placeholderLabel }}</span>
133
+ </p>
134
+ <Button
135
+ variant="stroke"
136
+ intent="neutral"
137
+ @click="emits('cancel')"
138
+ >
139
+ {{ cancelLabel }}
140
+ </Button>
141
+ <Button
142
+ variant="fill"
143
+ intent="primary"
144
+ @click="emits('apply', modelValue)"
145
+ >
146
+ {{ applyLabel }}
147
+ </Button>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </template>
152
+
153
+ <style scoped lang="scss">
154
+ .celeste-range-picker {
155
+ display: flex;
156
+ box-sizing: border-box;
157
+ align-items: stretch;
158
+ max-width: 100%;
159
+ overflow: hidden;
160
+ border: 1px solid var(--color-stroke-soft-200);
161
+ border-radius: var(--radius-20);
162
+ background-color: var(--color-bg-white-0);
163
+ box-shadow: var(--shadow-regular-md);
164
+ }
165
+
166
+ .celeste-range-picker-presets {
167
+ display: flex;
168
+ box-sizing: border-box;
169
+ flex-direction: column;
170
+ width: 200px;
171
+ padding: 20px var(--spacing-16) var(--spacing-2);
172
+ gap: var(--spacing-8);
173
+ border-inline-end: 1px solid var(--color-stroke-soft-200);
174
+ }
175
+
176
+ .celeste-range-picker-preset {
177
+ display: flex;
178
+ align-items: center;
179
+ padding: var(--spacing-8) var(--spacing-8) var(--spacing-8) var(--spacing-12);
180
+ transition-property: background-color, color;
181
+ transition-duration: var(--animation-fast);
182
+ transition-timing-function: ease-out;
183
+ border: none;
184
+ border-radius: var(--radius-8);
185
+ background-color: var(--color-bg-white-0);
186
+ color: var(--color-text-sub-600);
187
+ font: var(--label-sm);
188
+ text-align: start;
189
+ cursor: pointer;
190
+
191
+ &:hover {
192
+ background-color: var(--color-bg-weak-50);
193
+ color: var(--color-text-strong-950);
194
+ }
195
+
196
+ &:focus-visible {
197
+ outline: none;
198
+ box-shadow: var(--shadow-buttons-important-focus);
199
+ }
200
+
201
+ &[data-active] {
202
+ background-color: var(--color-bg-weak-50);
203
+ color: var(--color-text-strong-950);
204
+ }
205
+ }
206
+
207
+ .celeste-range-picker-content {
208
+ display: flex;
209
+ flex: 1;
210
+ flex-direction: column;
211
+ min-width: 0;
212
+ }
213
+
214
+ .celeste-range-picker-calendar {
215
+ border-block-end: 1px solid var(--color-stroke-soft-200);
216
+ }
217
+
218
+ .celeste-range-picker-footer {
219
+ display: flex;
220
+ align-items: center;
221
+ padding: var(--spacing-16) var(--spacing-16) var(--spacing-16) 24px;
222
+ gap: var(--spacing-16);
223
+ }
224
+
225
+ .celeste-range-picker-hint {
226
+ flex: 1;
227
+ min-width: 0;
228
+ margin: 0;
229
+ color: var(--color-text-sub-600);
230
+ font: var(--paragraph-sm);
231
+ }
232
+
233
+ .celeste-range-picker-hint-value {
234
+ color: var(--color-text-strong-950);
235
+ font: var(--label-sm);
236
+ }
237
+ </style>
@@ -0,0 +1,97 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3-vite';
2
+ import type { DateRange } from 'reka-ui';
3
+ import { CalendarDate } from '@internationalized/date';
4
+ import { ref } from 'vue';
5
+ import Button from '@/components/button/button.vue';
6
+ import PopoverContent from '@/components/popover/popover-content.vue';
7
+ import PopoverTrigger from '@/components/popover/popover-trigger.vue';
8
+ import Popover from '@/components/popover/popover.vue';
9
+ import RangePicker from '../range-picker.vue';
10
+
11
+ const meta: Meta<typeof RangePicker> = {
12
+ title: 'Components/Range Picker',
13
+ component: RangePicker,
14
+ parameters: { layout: 'centered' },
15
+ argTypes: {
16
+ showPresets: {
17
+ control: { type: 'boolean' },
18
+ description: 'Toggles the left-hand preset (time filter) sidebar.',
19
+ },
20
+ footer: {
21
+ control: { type: 'boolean' },
22
+ description: 'Toggles the footer with the range hint and Cancel / Apply actions.',
23
+ },
24
+ numberOfMonths: {
25
+ control: { type: 'number' },
26
+ description: 'How many months to render side by side.',
27
+ },
28
+ weekStartsOn: {
29
+ control: { type: 'select' },
30
+ options: [0, 1, 2, 3, 4, 5, 6],
31
+ },
32
+ locale: { control: { type: 'text' } },
33
+ rangeLabel: { control: { type: 'text' } },
34
+ },
35
+ args: {
36
+ showPresets: true,
37
+ footer: true,
38
+ numberOfMonths: 2,
39
+ weekStartsOn: 1,
40
+ locale: 'en-US',
41
+ rangeLabel: 'Range:',
42
+ },
43
+ };
44
+
45
+ export default meta;
46
+ type Story = StoryObj<typeof RangePicker>;
47
+
48
+ export const Default: Story = {
49
+ render: args => ({
50
+ components: { RangePicker },
51
+ setup() {
52
+ const value = ref<DateRange>({
53
+ start: new CalendarDate(2023, 12, 18),
54
+ end: new CalendarDate(2024, 1, 7),
55
+ });
56
+ const markedDates = [new CalendarDate(2023, 12, 8), new CalendarDate(2024, 1, 11)];
57
+ return { args, value, markedDates };
58
+ },
59
+ template: `<RangePicker v-model="value" v-bind="args" :marked-dates="markedDates" />`,
60
+ }),
61
+ };
62
+
63
+ export const NoPresets: Story = {
64
+ args: { showPresets: false },
65
+ render: args => ({
66
+ components: { RangePicker },
67
+ setup() {
68
+ const value = ref<DateRange>({ start: undefined, end: undefined });
69
+ return { args, value };
70
+ },
71
+ template: `<RangePicker v-model="value" v-bind="args" />`,
72
+ }),
73
+ };
74
+
75
+ export const InPopover: Story = {
76
+ render: args => ({
77
+ components: { RangePicker, Popover, PopoverContent, PopoverTrigger, Button },
78
+ setup() {
79
+ const value = ref<DateRange>({ start: undefined, end: undefined });
80
+ const open = ref(false);
81
+ return { args, value, open };
82
+ },
83
+ template: `
84
+ <Popover v-model:open="open">
85
+ <PopoverTrigger as-child>
86
+ <Button variant="stroke" intent="neutral">
87
+ <i class="i-celeste-calendar-line" />
88
+ Select date range
89
+ </Button>
90
+ </PopoverTrigger>
91
+ <PopoverContent :show-tail="false" :dismissible="false" style="padding: 0; border: none; box-shadow: none; max-width: none;">
92
+ <RangePicker v-model="value" v-bind="args" @apply="open = false" @cancel="open = false" />
93
+ </PopoverContent>
94
+ </Popover>
95
+ `,
96
+ }),
97
+ };
@@ -7,7 +7,8 @@ import { uid } from '@/utils/crypto';
7
7
  import TextInputButton from './text-input-button.vue';
8
8
 
9
9
  export interface TextInputProps extends /* @vue-ignore */ InputHTMLAttributes {
10
- type?: 'text' | 'email' | 'password' | 'search' | 'tel' | 'url';
10
+ // Remove type: 'date' when the DatePicker component is available
11
+ type?: 'text' | 'email' | 'password' | 'search' | 'tel' | 'url' | 'date';
11
12
  size?: 'xs' | 'sm' | 'md';
12
13
  hasError?: boolean;
13
14
  }