@bagelink/vue 0.0.1100 → 0.0.1104

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- import { Btn, TextInput } from '@bagelink/vue'
3
- import { computed, ref } from 'vue'
2
+ import { Btn, NumberInput } from '@bagelink/vue'
3
+ import { Dropdown as VDropdown } from 'floating-vue'
4
4
 
5
5
  const props = withDefaults(
6
6
  defineProps<{
@@ -10,293 +10,337 @@ const props = withDefaults(
10
10
  small?: boolean
11
11
  enableTime?: boolean
12
12
  modelValue?: string | Date
13
- defaultValue?: string | Date
14
- rtl?: boolean
15
13
  min?: string | Date
16
14
  max?: string | Date
17
- showInput?: boolean
18
- timezone?: string // e.g. 'America/New_York', 'Europe/London'
15
+ timezone?: string
19
16
  }>(),
20
17
  {
21
18
  enableTime: false,
22
19
  editMode: true,
23
20
  small: false,
24
- rtl: false,
25
- showInput: true,
26
21
  timezone: 'UTC'
27
22
  },
28
23
  )
29
24
 
30
25
  const emit = defineEmits(['update:modelValue'])
31
- const isOpen = ref(false)
32
- const currentMonth = ref(new Date())
26
+ let isOpen = $ref(false)
27
+ let currentMonth = $ref(new Date())
28
+ type ViewMode = 'days' | 'months' | 'years'
29
+ let currentView = $ref<ViewMode>('days')
33
30
 
34
- const inputType = computed(() => props.enableTime ? 'datetime-local' : 'date')
31
+ const inputType = $computed(() => props.enableTime ? 'datetime-local' : 'date')
35
32
 
36
- function toLocalDate(date: Date): Date {
37
- if (!date) return date
38
- try {
39
- return new Date(date.toLocaleString('en-US', { timeZone: props.timezone }))
40
- } catch (e) {
41
- console.warn(`Invalid timezone: ${props.timezone}, falling back to UTC`)
42
- return date
43
- }
33
+ function formatDate(date: Date | string | undefined): string {
34
+ if (!date) return ''
35
+ const dateObj = typeof date === 'string' ? new Date(date) : date
36
+ return props.enableTime ? dateObj.toISOString().slice(0, 16) : dateObj.toISOString().split('T')[0]
44
37
  }
45
38
 
46
- function fromLocalDate(date: Date): Date {
47
- if (!date) return date
48
- try {
49
- const localDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }))
50
- const tzDate = new Date(date.toLocaleString('en-US', { timeZone: props.timezone }))
51
- const diff = localDate.getTime() - tzDate.getTime()
52
- return new Date(date.getTime() + diff)
53
- } catch (e) {
54
- console.warn(`Invalid timezone: ${props.timezone}, falling back to UTC`)
55
- return date
56
- }
57
- }
58
-
59
- const formattedValue = computed(() => {
60
- if (!props.modelValue) return ''
61
- const date = typeof props.modelValue === 'string' ? new Date(props.modelValue) : props.modelValue
62
- const localDate = toLocalDate(date)
63
-
64
- if (props.enableTime) {
65
- return localDate.toISOString().slice(0, 16) // Format: YYYY-MM-DDTHH:mm
66
- }
67
- return localDate.toISOString().split('T')[0] // Format: YYYY-MM-DD
68
- })
69
-
70
- const formattedMin = computed(() => {
71
- if (!props.min) return undefined
72
- const date = typeof props.min === 'string' ? new Date(props.min) : props.min
73
- const localDate = toLocalDate(date)
74
- return props.enableTime ? localDate.toISOString().slice(0, 16) : localDate.toISOString().split('T')[0]
75
- })
76
-
77
- const formattedMax = computed(() => {
78
- if (!props.max) return undefined
79
- const date = typeof props.max === 'string' ? new Date(props.max) : props.max
80
- const localDate = toLocalDate(date)
81
- return props.enableTime ? localDate.toISOString().slice(0, 16) : localDate.toISOString().split('T')[0]
82
- })
39
+ const formattedValue = $computed(() => formatDate(props.modelValue))
40
+ const formattedMin = $computed(() => formatDate(props.min))
41
+ const formattedMax = $computed(() => formatDate(props.max))
83
42
 
84
- const selectedDate = computed(() => {
43
+ const selectedDate = $computed(() => {
85
44
  if (!props.modelValue) return null
86
- const date = typeof props.modelValue === 'string' ? new Date(props.modelValue) : props.modelValue
87
- return toLocalDate(date)
45
+ return typeof props.modelValue === 'string' ? new Date(props.modelValue) : props.modelValue
88
46
  })
89
47
 
90
- const currentMonthDays = computed(() => {
91
- const localCurrentMonth = toLocalDate(currentMonth.value)
92
- const year = localCurrentMonth.getFullYear()
93
- const month = localCurrentMonth.getMonth()
48
+ const currentMonthDays = $computed(() => {
49
+ const year = currentMonth.getFullYear()
50
+ const month = currentMonth.getMonth()
94
51
  const firstDay = new Date(year, month, 1)
95
52
  const lastDay = new Date(year, month + 1, 0)
96
53
  const days = []
97
54
 
98
- // Add empty slots for days before the first of the month
99
- const firstDayOfWeek = firstDay.getDay()
100
- for (let i = 0; i < firstDayOfWeek; i++) {
101
- days.push(null)
102
- }
103
-
104
- // Add all days of the month
105
- for (let i = 1; i <= lastDay.getDate(); i++) {
106
- days.push(new Date(year, month, i))
107
- }
55
+ for (let i = 0; i < firstDay.getDay(); i++) days.push(null)
56
+ for (let i = 1; i <= lastDay.getDate(); i++) days.push(new Date(year, month, i))
108
57
 
109
58
  return days
110
59
  })
111
60
 
112
- const monthYear = computed(() => {
113
- const localCurrentMonth = toLocalDate(currentMonth.value)
114
- return localCurrentMonth.toLocaleString('default', {
115
- month: 'long',
116
- year: 'numeric',
117
- timeZone: props.timezone
118
- })
61
+ const currentMonthValue = $computed(() => ({
62
+ month: currentMonth.getMonth(),
63
+ year: currentMonth.getFullYear(),
64
+ formatted: {
65
+ month: currentMonth.toLocaleString('default', { month: 'long', timeZone: props.timezone }),
66
+ year: currentMonth.toLocaleString('default', { year: 'numeric', timeZone: props.timezone })
67
+ }
68
+ }))
69
+
70
+ const months = $computed(() => Array.from({ length: 12 }, (_, i) => {
71
+ const date = new Date(currentMonthValue.year, i, 1)
72
+ return {
73
+ name: date.toLocaleString('default', { month: 'short' }),
74
+ value: i,
75
+ disabled: isDateDisabled(date)
76
+ }
119
77
  })
78
+ )
120
79
 
121
- function handleInput(event: Event) {
122
- const input = event.target as HTMLInputElement
123
- if (!input.value) {
124
- emit('update:modelValue', '')
125
- return
126
- }
80
+ const years = $computed(() => {
81
+ const startYear = currentMonthValue.year - 10
82
+ return Array.from({ length: 21 }, (_, i) => ({
83
+ value: startYear + i,
84
+ disabled: isYearDisabled(startYear + i)
85
+ }))
86
+ })
127
87
 
128
- let date = new Date(input.value)
129
- date = fromLocalDate(date)
88
+ function isDateDisabled(date: Date | null) {
89
+ if (!date) return true
90
+ const minDate = props.min ? new Date(props.min) : null
91
+ const maxDate = props.max ? new Date(props.max) : null
130
92
 
131
- if (props.enableTime) {
132
- emit('update:modelValue', date.toISOString())
133
- } else {
134
- emit('update:modelValue', date.toISOString().split('T')[0])
135
- }
93
+ if (minDate && date < minDate) return true
94
+ if (maxDate && date > maxDate) return true
95
+ return false
96
+ }
97
+
98
+ function isYearDisabled(year: number) {
99
+ const minDate = props.min ? new Date(props.min) : null
100
+ const maxDate = props.max ? new Date(props.max) : null
101
+
102
+ if (minDate && year < minDate.getFullYear()) return true
103
+ if (maxDate && year > maxDate.getFullYear()) return true
104
+ return false
105
+ }
106
+
107
+ function selectMonth(monthIndex: number) {
108
+ currentMonth = new Date(currentMonth.getFullYear(), monthIndex, 1)
109
+ currentView = 'days'
110
+ }
111
+
112
+ function selectYear(year: number) {
113
+ currentMonth = new Date(year, currentMonth.getMonth(), 1)
114
+ currentView = 'months'
136
115
  }
137
116
 
138
117
  function previousMonth() {
139
- const localCurrentMonth = toLocalDate(currentMonth.value)
140
- currentMonth.value = new Date(
141
- localCurrentMonth.getFullYear(),
142
- localCurrentMonth.getMonth() - 1,
143
- 1
144
- )
118
+ currentMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1)
145
119
  }
146
120
 
147
121
  function nextMonth() {
148
- const localCurrentMonth = toLocalDate(currentMonth.value)
149
- currentMonth.value = new Date(
150
- localCurrentMonth.getFullYear(),
151
- localCurrentMonth.getMonth() + 1,
152
- 1
153
- )
122
+ currentMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1)
123
+ }
124
+
125
+ function previousYear() {
126
+ const offset = currentView === 'months' ? 1 : 21
127
+ currentMonth = new Date(currentMonth.getFullYear() - offset, currentMonth.getMonth(), 1)
128
+ }
129
+
130
+ function nextYear() {
131
+ const offset = currentView === 'months' ? 1 : 21
132
+ currentMonth = new Date(currentMonth.getFullYear() + offset, currentMonth.getMonth(), 1)
154
133
  }
155
134
 
156
135
  function selectDate(date: Date | null) {
157
136
  if (!date || !props.editMode) return
158
137
 
159
- let selectedTime = { hours: 0, minutes: 0 }
160
- if (props.enableTime && selectedDate.value) {
161
- selectedTime = {
162
- hours: selectedDate.value.getHours(),
163
- minutes: selectedDate.value.getMinutes()
164
- }
138
+ // Create date at start of day in the target timezone
139
+ const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0)
140
+
141
+ // If time is enabled, preserve existing time or set to current time
142
+ if (props.enableTime) {
143
+ const currentHours = selectedDate?.getHours() ?? new Date().getHours()
144
+ const currentMinutes = selectedDate?.getMinutes() ?? new Date().getMinutes()
145
+ newDate.setHours(currentHours)
146
+ newDate.setMinutes(currentMinutes)
147
+ emit('update:modelValue', newDate.toISOString())
148
+ } else {
149
+ emit('update:modelValue', newDate.toISOString().split('T')[0])
150
+ isOpen = false
165
151
  }
152
+ }
166
153
 
167
- const localDate = new Date(date)
168
- localDate.setHours(selectedTime.hours)
169
- localDate.setMinutes(selectedTime.minutes)
154
+ function handleInput(event: Event) {
155
+ const input = event.target as HTMLInputElement
156
+ if (!input.value) {
157
+ emit('update:modelValue', '')
158
+ return
159
+ }
160
+
161
+ const date = new Date(input.value)
162
+ emit('update:modelValue', props.enableTime ? date.toISOString() : date.toISOString().split('T')[0])
163
+ }
170
164
 
171
- const utcDate = fromLocalDate(localDate)
165
+ const hours = $computed(() => selectedDate?.getHours() ?? 0)
166
+ const minutes = $computed(() => selectedDate?.getMinutes() ?? 0)
167
+
168
+ function handleHourInput(value: number) {
169
+ if (!selectedDate) return
170
+ const newDate = new Date(selectedDate)
171
+ newDate.setHours(value)
172
+ emit('update:modelValue', newDate.toISOString())
173
+ }
172
174
 
173
- emit('update:modelValue', props.enableTime ? utcDate.toISOString() : utcDate.toISOString().split('T')[0])
174
- isOpen.value = false
175
+ function handleMinuteInput(value: number) {
176
+ if (!selectedDate) return
177
+ const newDate = new Date(selectedDate)
178
+ newDate.setMinutes(value)
179
+ emit('update:modelValue', newDate.toISOString())
175
180
  }
176
181
 
177
182
  function isSelected(date: Date | null) {
178
- if (!date || !selectedDate.value) return false
179
- const localDate = toLocalDate(date)
180
- return localDate.toISOString().split('T')[0] === selectedDate.value.toISOString().split('T')[0]
183
+ if (!date || !selectedDate) return false
184
+ return date.toISOString().split('T')[0] === selectedDate.toISOString().split('T')[0]
181
185
  }
182
186
 
183
187
  function isToday(date: Date | null) {
184
188
  if (!date) return false
185
- const today = toLocalDate(new Date())
186
- const localDate = toLocalDate(date)
187
- return localDate.toISOString().split('T')[0] === today.toISOString().split('T')[0]
189
+ return date.toISOString().split('T')[0] === new Date().toISOString().split('T')[0]
188
190
  }
189
191
 
190
- function isDisabled(date: Date | null) {
191
- if (!date) return true
192
- const localDate = toLocalDate(date)
193
- const minDate = props.min ? toLocalDate(new Date(props.min)) : null
194
- const maxDate = props.max ? toLocalDate(new Date(props.max)) : null
195
-
196
- if (minDate && localDate < minDate) return true
197
- if (maxDate && localDate > maxDate) return true
198
- return false
199
- }
200
-
201
- // Add timezone display if time is enabled
202
- const timezoneDisplay = computed(() => {
192
+ const timezoneDisplay = $computed(() => {
203
193
  if (!props.enableTime) return ''
204
194
  try {
205
- const date = new Date()
206
- return date.toLocaleString('en-US', {
195
+ return new Date().toLocaleString('en-US', {
207
196
  timeZoneName: 'short',
208
197
  timeZone: props.timezone
209
198
  }).split(' ').pop()
210
- } catch (e) {
199
+ } catch {
211
200
  return 'UTC'
212
201
  }
213
202
  })
214
203
  </script>
215
204
 
216
205
  <template>
217
- <div
218
- class="bagel-input"
219
- :class="{ small, 'rtl-date': rtl }"
220
- :title="label"
221
- >
206
+ <div class="bagel-input" :class="{ small }" :title="label">
222
207
  <label v-if="label">
223
208
  {{ label }}
224
209
  <span v-if="required" class="required">*</span>
225
210
  </label>
226
211
 
227
- <div class="date-picker-container">
228
- <TextInput @input="handleInput" @click="isOpen = true" />
229
- <!-- <input
230
- v-if="showInput"
231
- :type="inputType"
232
- :value="formattedValue"
233
- :min="formattedMin"
234
- :max="formattedMax"
235
- :required="required"
236
- :disabled="!editMode"
237
- class="date-input"
238
- > -->
239
-
240
- <div
241
- v-if="!showInput"
242
- class="date-display"
243
- :class="{ disabled: !editMode }"
244
- @click="editMode && (isOpen = !isOpen)"
245
- >
246
- {{ formattedValue || 'Select date' }}
247
- <span v-if="enableTime" class="timezone-display">{{ timezoneDisplay }}</span>
212
+ <VDropdown
213
+ :shown="isOpen"
214
+ :triggers="[]"
215
+ placement="bottom-start"
216
+ :distance="4"
217
+ @apply-show="isOpen = true"
218
+ @apply-hide="isOpen = false"
219
+ >
220
+ <div class="date-picker-container">
221
+ <input
222
+ :type="inputType"
223
+ :value="formattedValue"
224
+ :min="formattedMin"
225
+ :max="formattedMax"
226
+ :required="required"
227
+ :disabled="!editMode"
228
+ class="date-input"
229
+ @input="handleInput"
230
+ @click="isOpen = true"
231
+ >
248
232
  </div>
249
233
 
250
- <div
251
- v-if="isOpen"
252
- class="calendar-popup"
253
- @click.stop
254
- >
255
- <div class="calendar-header">
256
- <Btn flat icon="chevron_left" @click="previousMonth" />
257
- <span class="month-year">{{ monthYear }}</span>
258
- <Btn flat icon="chevron_right" @click="nextMonth" />
259
- </div>
260
-
261
- <div class="calendar-grid">
262
- <div
263
- v-for="day in ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']"
264
- :key="day"
265
- class="weekday"
266
- >
267
- {{ day }}
234
+ <template #popper>
235
+ <div class="calendar-container">
236
+ <div class="calendar-section">
237
+ <div class="calendar-header">
238
+ <template v-if="currentView === 'days'">
239
+ <Btn flat icon="chevron_left" @click="previousMonth" />
240
+ <div class="month-year-selector">
241
+ <button class="month-btn" @click="currentView = 'months'">
242
+ {{ currentMonthValue.formatted.month }}
243
+ </button>
244
+ <button class="year-btn" @click="currentView = 'years'">
245
+ {{ currentMonthValue.formatted.year }}
246
+ </button>
247
+ </div>
248
+ <Btn flat icon="chevron_right" @click="nextMonth" />
249
+ </template>
250
+ <template v-else>
251
+ <Btn flat icon="chevron_left" @click="previousYear" />
252
+ <span class="month-year">{{ currentMonthValue.formatted.year }}</span>
253
+ <Btn flat icon="chevron_right" @click="nextYear" />
254
+ </template>
255
+ </div>
256
+
257
+ <div v-if="currentView === 'days'" class="calendar-grid">
258
+ <div v-for="day in ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']" :key="day" class="weekday">
259
+ {{ day }}
260
+ </div>
261
+
262
+ <button
263
+ v-for="(date, index) in currentMonthDays"
264
+ :key="index"
265
+ type="button"
266
+ class="day"
267
+ :class="{
268
+ selected: isSelected(date),
269
+ today: isToday(date),
270
+ disabled: isDateDisabled(date),
271
+ }"
272
+ :disabled="isDateDisabled(date)"
273
+ @click="selectDate(date)"
274
+ >
275
+ {{ date?.getDate() }}
276
+ </button>
277
+ </div>
278
+
279
+ <div v-else-if="currentView === 'months'" class="month-grid">
280
+ <button
281
+ v-for="month in months"
282
+ :key="month.value"
283
+ class="month-item"
284
+ :class="{
285
+ selected: month.value === currentMonthValue.month,
286
+ disabled: month.disabled,
287
+ }"
288
+ :disabled="month.disabled"
289
+ @click="selectMonth(month.value)"
290
+ >
291
+ {{ month.name }}
292
+ </button>
293
+ </div>
294
+
295
+ <div v-else class="year-grid">
296
+ <button
297
+ v-for="year in years"
298
+ :key="year.value"
299
+ class="year-item"
300
+ :class="{
301
+ selected: year.value === currentMonthValue.year,
302
+ disabled: year.disabled,
303
+ }"
304
+ :disabled="year.disabled"
305
+ @click="selectYear(year.value)"
306
+ >
307
+ {{ year.value }}
308
+ </button>
309
+ </div>
268
310
  </div>
269
311
 
270
- <button
271
- v-for="(date, index) in currentMonthDays"
272
- :key="index"
273
- type="button"
274
- class="day"
275
- :class="{
276
- selected: isSelected(date),
277
- today: isToday(date),
278
- disabled: isDisabled(date),
279
- }"
280
- :disabled="isDisabled(date)"
281
- @click="selectDate(date)"
282
- >
283
- {{ date?.getDate() }}
284
- </button>
285
- </div>
286
-
287
- <div
288
- v-if="enableTime && selectedDate"
289
- class="time-picker"
290
- >
291
- <input
292
- type="time"
293
- :value="selectedDate.toISOString().slice(11, 16)"
294
- @input="handleInput"
295
- >
296
- <span class="timezone-display">{{ timezoneDisplay }}</span>
312
+ <div v-if="enableTime && currentView === 'days'" class="time-picker">
313
+ <div class="time-input-group">
314
+ <NumberInput
315
+ center
316
+ :model-value="hours"
317
+ :disabled="!selectedDate"
318
+ :min="0"
319
+ :max="23"
320
+ layout="vertical"
321
+ :padZero="2"
322
+ @update:model-value="handleHourInput"
323
+ />
324
+ <span>:</span>
325
+ <NumberInput
326
+ center
327
+ :model-value="minutes"
328
+ :disabled="!selectedDate"
329
+ :min="0"
330
+ :max="59"
331
+ :padZero="2"
332
+ layout="vertical"
333
+ @update:model-value="handleMinuteInput"
334
+ />
335
+ </div>
336
+ <span class="timezone-display">{{ timezoneDisplay }}</span>
337
+ <Btn v-if="selectedDate" flat @click="isOpen = false">
338
+ Done
339
+ </Btn>
340
+ </div>
297
341
  </div>
298
- </div>
299
- </div>
342
+ </template>
343
+ </VDropdown>
300
344
  </div>
301
345
  </template>
302
346
 
@@ -312,8 +356,7 @@ const timezoneDisplay = computed(() => {
312
356
  position: relative;
313
357
  }
314
358
 
315
- .date-input,
316
- .date-display {
359
+ .date-input {
317
360
  padding: 0.5rem;
318
361
  border: 1px solid #ddd;
319
362
  border-radius: 4px;
@@ -322,17 +365,7 @@ const timezoneDisplay = computed(() => {
322
365
  background: white;
323
366
  }
324
367
 
325
- .date-display {
326
- cursor: pointer;
327
- }
328
-
329
- .date-display.disabled {
330
- background-color: #f5f5f5;
331
- cursor: not-allowed;
332
- }
333
-
334
- .date-input:focus,
335
- .date-display:focus {
368
+ .date-input:focus {
336
369
  outline: none;
337
370
  border-color: #4a90e2;
338
371
  box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
@@ -343,32 +376,30 @@ const timezoneDisplay = computed(() => {
343
376
  cursor: not-allowed;
344
377
  }
345
378
 
346
- .small .date-input,
347
- .small .date-display {
379
+ .small .date-input {
348
380
  padding: 0.25rem;
349
381
  font-size: 0.875rem;
350
382
  }
351
383
 
352
- .rtl-date input {
353
- direction: rtl;
354
- }
355
-
356
384
  .required {
357
385
  color: #ff4d4f;
358
386
  margin-left: 4px;
359
387
  }
360
388
 
361
389
  .calendar-popup {
362
- position: absolute;
363
- top: 100%;
364
- left: 0;
365
- z-index: 1000;
366
390
  background: white;
367
391
  border: 1px solid #ddd;
368
392
  border-radius: 4px;
369
393
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
370
394
  padding: 1rem;
371
- margin-top: 0.5rem;
395
+ }
396
+
397
+ .calendar-container {
398
+ display: flex;
399
+ gap: 1rem;
400
+ }
401
+
402
+ .calendar-section {
372
403
  width: 300px;
373
404
  }
374
405
 
@@ -379,21 +410,25 @@ const timezoneDisplay = computed(() => {
379
410
  margin-bottom: 1rem;
380
411
  }
381
412
 
382
- .nav-button {
413
+ .month-year-selector {
414
+ display: flex;
415
+ gap: 0.5rem;
416
+ align-items: center;
417
+ }
418
+
419
+ .month-btn,
420
+ .year-btn {
383
421
  background: none;
384
422
  border: none;
385
- font-size: 1.5rem;
423
+ font-weight: 500;
386
424
  cursor: pointer;
387
- padding: 0 0.5rem;
388
- color: #666;
389
- }
390
-
391
- .nav-button:hover {
392
- color: #4a90e2;
425
+ padding: 0.25rem 0.5rem;
426
+ border-radius: 4px;
393
427
  }
394
428
 
395
- .month-year {
396
- font-weight: 500;
429
+ .month-btn:hover,
430
+ .year-btn:hover {
431
+ background-color: #f5f5f5;
397
432
  }
398
433
 
399
434
  .calendar-grid {
@@ -402,6 +437,54 @@ const timezoneDisplay = computed(() => {
402
437
  gap: 0.25rem;
403
438
  }
404
439
 
440
+ .month-grid {
441
+ display: grid;
442
+ grid-template-columns: repeat(3, 1fr);
443
+ grid-template-rows: repeat(4, 1fr);
444
+ gap: 0.5rem;
445
+ padding: 0.5rem;
446
+ }
447
+
448
+ .year-grid {
449
+ display: grid;
450
+ grid-template-columns: repeat(3, 1fr);
451
+ grid-template-rows: repeat(7, 1fr);
452
+ gap: 0.5rem;
453
+ padding: 0.5rem;
454
+ }
455
+
456
+ .month-item,
457
+ .year-item {
458
+ display: flex;
459
+ align-items: center;
460
+ justify-content: center;
461
+ border: none;
462
+ background: none;
463
+ cursor: pointer;
464
+ border-radius: 4px;
465
+ font-size: 0.875rem;
466
+ color: #333;
467
+ padding: 0.5rem;
468
+ min-height: 2.5rem;
469
+ }
470
+
471
+ .month-item:hover:not(.disabled),
472
+ .year-item:hover:not(.disabled) {
473
+ background-color: #f5f5f5;
474
+ }
475
+
476
+ .month-item.selected,
477
+ .year-item.selected {
478
+ background-color: #4a90e2;
479
+ color: white;
480
+ }
481
+
482
+ .month-item.disabled,
483
+ .year-item.disabled {
484
+ color: #ccc;
485
+ cursor: not-allowed;
486
+ }
487
+
405
488
  .weekday {
406
489
  text-align: center;
407
490
  font-size: 0.875rem;
@@ -442,18 +525,29 @@ const timezoneDisplay = computed(() => {
442
525
  }
443
526
 
444
527
  .timezone-display {
445
- margin-left: 0.5rem;
446
528
  color: #666;
447
529
  font-size: 0.875rem;
530
+ text-align: center;
448
531
  }
449
532
 
450
533
  .time-picker {
451
534
  display: flex;
535
+ flex-direction: column;
536
+ gap: 1rem;
537
+ border-left: 1px solid #ddd;
538
+ width: 100px;
452
539
  align-items: center;
453
- gap: 0.5rem;
454
540
  }
455
541
 
456
- .time-picker input {
457
- flex: 1;
542
+ .time-input-group {
543
+ display: flex;
544
+ align-items: center;
545
+ gap: 0.25rem;
546
+ }
547
+
548
+ .time-input-group span {
549
+ font-size: 1.25rem;
550
+ color: #666;
551
+ padding: 0 0.25rem;
458
552
  }
459
553
  </style>