@umbra.ui/core 0.1.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.
- package/dist/components/controls/Dropdown/types.d.ts +5 -0
- package/dist/components/controls/Dropdown/types.d.ts.map +1 -0
- package/dist/components/controls/Dropdown/types.js +1 -0
- package/dist/components/controls/SegmentedControl/types.d.ts +6 -0
- package/dist/components/controls/SegmentedControl/types.d.ts.map +1 -0
- package/dist/components/controls/SegmentedControl/types.js +1 -0
- package/dist/components/dialogs/Alert/types.d.ts +7 -0
- package/dist/components/dialogs/Alert/types.d.ts.map +1 -0
- package/dist/components/dialogs/Alert/types.js +1 -0
- package/dist/components/dialogs/Toast/types.d.ts +34 -0
- package/dist/components/dialogs/Toast/types.d.ts.map +1 -0
- package/dist/components/dialogs/Toast/types.js +10 -0
- package/dist/components/dialogs/Toast/useToast.d.ts +36 -0
- package/dist/components/dialogs/Toast/useToast.d.ts.map +1 -0
- package/dist/components/dialogs/Toast/useToast.js +90 -0
- package/dist/components/indicators/Tooltip/tooltip.d.ts +3 -0
- package/dist/components/indicators/Tooltip/tooltip.d.ts.map +1 -0
- package/dist/components/indicators/Tooltip/tooltip.js +33 -0
- package/dist/components/indicators/Tooltip/types.d.ts +14 -0
- package/dist/components/indicators/Tooltip/types.d.ts.map +1 -0
- package/dist/components/indicators/Tooltip/types.js +1 -0
- package/dist/components/indicators/Tooltip/useTooltip.d.ts +18 -0
- package/dist/components/indicators/Tooltip/useTooltip.d.ts.map +1 -0
- package/dist/components/indicators/Tooltip/useTooltip.js +57 -0
- package/dist/components/inputs/Tags/tag-bar-styles.d.ts +14 -0
- package/dist/components/inputs/Tags/tag-bar-styles.d.ts.map +1 -0
- package/dist/components/inputs/Tags/tag-bar-styles.js +313 -0
- package/dist/components/inputs/Tags/types.d.ts +93 -0
- package/dist/components/inputs/Tags/types.d.ts.map +1 -0
- package/dist/components/inputs/Tags/types.js +216 -0
- package/dist/components/inputs/search/types.d.ts +9 -0
- package/dist/components/inputs/search/types.d.ts.map +1 -0
- package/dist/components/inputs/search/types.js +1 -0
- package/dist/components/navigation/adaptive/types.d.ts +16 -0
- package/dist/components/navigation/adaptive/types.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/types.js +1 -0
- package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts +27 -0
- package/dist/components/navigation/adaptive/useAdaptiveLayout.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useAdaptiveLayout.js +40 -0
- package/dist/components/navigation/adaptive/useBreakpoints.d.ts +6 -0
- package/dist/components/navigation/adaptive/useBreakpoints.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useBreakpoints.js +37 -0
- package/dist/components/navigation/adaptive/useContainerMonitor.d.ts +93 -0
- package/dist/components/navigation/adaptive/useContainerMonitor.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useContainerMonitor.js +145 -0
- package/dist/components/navigation/adaptive/useViewAnimation.d.ts +31 -0
- package/dist/components/navigation/adaptive/useViewAnimation.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useViewAnimation.js +591 -0
- package/dist/components/navigation/adaptive/useViewResize.d.ts +52 -0
- package/dist/components/navigation/adaptive/useViewResize.d.ts.map +1 -0
- package/dist/components/navigation/adaptive/useViewResize.js +146 -0
- package/dist/components/navigation/navstack/useNavigationStack.d.ts +25 -0
- package/dist/components/navigation/navstack/useNavigationStack.d.ts.map +1 -0
- package/dist/components/navigation/navstack/useNavigationStack.js +133 -0
- package/dist/components/navigation/slideover/useSlideoverController.d.ts +20 -0
- package/dist/components/navigation/slideover/useSlideoverController.d.ts.map +1 -0
- package/dist/components/navigation/slideover/useSlideoverController.js +267 -0
- package/dist/components/navigation/splitview/useSplitViewController.d.ts +20 -0
- package/dist/components/navigation/splitview/useSplitViewController.d.ts.map +1 -0
- package/dist/components/navigation/splitview/useSplitViewController.js +325 -0
- package/dist/components/navigation/tabcontroller/types.d.ts +21 -0
- package/dist/components/navigation/tabcontroller/types.d.ts.map +1 -0
- package/dist/components/navigation/tabcontroller/types.js +1 -0
- package/dist/components/navigation/tabcontroller/useTabController.d.ts +5 -0
- package/dist/components/navigation/tabcontroller/useTabController.d.ts.map +1 -0
- package/dist/components/navigation/tabcontroller/useTabController.js +10 -0
- package/dist/components/navigation/types.d.ts +8 -0
- package/dist/components/navigation/types.d.ts.map +1 -0
- package/dist/components/navigation/types.js +1 -0
- package/dist/components/pickers/CollectionPicker/types.d.ts +11 -0
- package/dist/components/pickers/CollectionPicker/types.d.ts.map +1 -0
- package/dist/components/pickers/CollectionPicker/types.js +1 -0
- package/dist/components/pickers/ColorPicker/colors.d.ts +13 -0
- package/dist/components/pickers/ColorPicker/colors.d.ts.map +1 -0
- package/dist/components/pickers/ColorPicker/colors.js +266 -0
- package/dist/components/pickers/FilePicker/types.d.ts +10 -0
- package/dist/components/pickers/FilePicker/types.d.ts.map +1 -0
- package/dist/components/pickers/FilePicker/types.js +1 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +196 -0
- package/dist/theme.d.ts +73 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +279 -0
- package/dist/themes/blank.d.ts +7 -0
- package/dist/themes/blank.d.ts.map +1 -0
- package/dist/themes/blank.js +543 -0
- package/dist/themes/crimson-dark.d.ts +4 -0
- package/dist/themes/crimson-dark.d.ts.map +1 -0
- package/dist/themes/crimson-dark.js +552 -0
- package/dist/themes/cyan-light.d.ts +4 -0
- package/dist/themes/cyan-light.d.ts.map +1 -0
- package/dist/themes/cyan-light.js +552 -0
- package/dist/themes/dark.d.ts +4 -0
- package/dist/themes/dark.d.ts.map +1 -0
- package/dist/themes/dark.js +551 -0
- package/dist/themes/gold-dark.d.ts +4 -0
- package/dist/themes/gold-dark.d.ts.map +1 -0
- package/dist/themes/gold-dark.js +552 -0
- package/dist/themes/grass-dark.d.ts +4 -0
- package/dist/themes/grass-dark.d.ts.map +1 -0
- package/dist/themes/grass-dark.js +552 -0
- package/dist/themes/indigo.d.ts +4 -0
- package/dist/themes/indigo.d.ts.map +1 -0
- package/dist/themes/indigo.js +552 -0
- package/dist/themes/light.d.ts +4 -0
- package/dist/themes/light.d.ts.map +1 -0
- package/dist/themes/light.js +551 -0
- package/dist/themes/orange-dark.d.ts +4 -0
- package/dist/themes/orange-dark.d.ts.map +1 -0
- package/dist/themes/orange-dark.js +551 -0
- package/dist/themes/orange-light.d.ts +4 -0
- package/dist/themes/orange-light.d.ts.map +1 -0
- package/dist/themes/orange-light.js +551 -0
- package/package.json +62 -0
- package/src/components/controls/Button/Button.vue +417 -0
- package/src/components/controls/Button/README.md +348 -0
- package/src/components/controls/Button/theme.css +200 -0
- package/src/components/controls/Checkbox/Checkbox.vue +164 -0
- package/src/components/controls/Checkbox/README.md +441 -0
- package/src/components/controls/Checkbox/theme.css +36 -0
- package/src/components/controls/Dropdown/Dropdown.vue +476 -0
- package/src/components/controls/Dropdown/README.md +370 -0
- package/src/components/controls/Dropdown/theme.css +50 -0
- package/src/components/controls/Dropdown/types.ts +6 -0
- package/src/components/controls/IconButton/IconButton.vue +267 -0
- package/src/components/controls/IconButton/README.md +502 -0
- package/src/components/controls/IconButton/theme.css +89 -0
- package/src/components/controls/Radio/README.md +591 -0
- package/src/components/controls/Radio/Radio.vue +89 -0
- package/src/components/controls/Radio/theme.css +14 -0
- package/src/components/controls/RangeSlider/README.md +608 -0
- package/src/components/controls/RangeSlider/RangeSlider.vue +535 -0
- package/src/components/controls/RangeSlider/theme.css +80 -0
- package/src/components/controls/SegmentedControl/README.md +587 -0
- package/src/components/controls/SegmentedControl/SegmentedControl.vue +284 -0
- package/src/components/controls/SegmentedControl/theme.css +60 -0
- package/src/components/controls/SegmentedControl/types.ts +5 -0
- package/src/components/controls/Slider/README.md +627 -0
- package/src/components/controls/Slider/Slider.vue +260 -0
- package/src/components/controls/Slider/theme.css +74 -0
- package/src/components/controls/Stepper/README.md +601 -0
- package/src/components/controls/Stepper/Stepper.vue +103 -0
- package/src/components/controls/Stepper/theme.css +53 -0
- package/src/components/controls/Switch/README.md +667 -0
- package/src/components/controls/Switch/Switch.vue +127 -0
- package/src/components/controls/Switch/theme.css +42 -0
- package/src/components/dialogs/Alert/Alert.vue +218 -0
- package/src/components/dialogs/Alert/README.md +450 -0
- package/src/components/dialogs/Alert/theme.css +44 -0
- package/src/components/dialogs/Alert/types.ts +11 -0
- package/src/components/dialogs/Toast/README.md +522 -0
- package/src/components/dialogs/Toast/Toast.vue +296 -0
- package/src/components/dialogs/Toast/ToastContainer.vue +330 -0
- package/src/components/dialogs/Toast/theme.css +44 -0
- package/src/components/dialogs/Toast/types.ts +46 -0
- package/src/components/dialogs/Toast/useToast.ts +127 -0
- package/src/components/indicators/ProgressBar/ProgressBar.vue +98 -0
- package/src/components/indicators/ProgressBar/README.md +744 -0
- package/src/components/indicators/ProgressBar/theme.css +36 -0
- package/src/components/indicators/Tooltip/README.md +723 -0
- package/src/components/indicators/Tooltip/TooltipProvider.vue +142 -0
- package/src/components/indicators/Tooltip/theme.css +18 -0
- package/src/components/indicators/Tooltip/tooltip.ts +48 -0
- package/src/components/indicators/Tooltip/types.ts +15 -0
- package/src/components/indicators/Tooltip/useTooltip.ts +71 -0
- package/src/components/inputs/AutogrowTextView/AutogrowTextView.vue +110 -0
- package/src/components/inputs/AutogrowTextView/README.md +643 -0
- package/src/components/inputs/AutogrowTextView/theme.css +28 -0
- package/src/components/inputs/InputCard/InputCard.vue +600 -0
- package/src/components/inputs/InputCard/README.md +636 -0
- package/src/components/inputs/InputEmail/InputEmail.vue +698 -0
- package/src/components/inputs/InputEmail/README.md +764 -0
- package/src/components/inputs/InputNumber/InputNumber.vue +300 -0
- package/src/components/inputs/InputNumber/README.md +749 -0
- package/src/components/inputs/InputPhone/InputPhone.vue +645 -0
- package/src/components/inputs/InputPhone/README.md +636 -0
- package/src/components/inputs/InputSecure/InputSecure.vue +646 -0
- package/src/components/inputs/InputSecure/README.md +771 -0
- package/src/components/inputs/InputText/InputText.vue +225 -0
- package/src/components/inputs/InputText/README.md +844 -0
- package/src/components/inputs/OTP/OTP.vue +349 -0
- package/src/components/inputs/OTP/README.md +736 -0
- package/src/components/inputs/OTP/theme.css +50 -0
- package/src/components/inputs/StringCapture/README.md +718 -0
- package/src/components/inputs/StringCapture/StringCapture.vue +315 -0
- package/src/components/inputs/StringCapture/theme.css +86 -0
- package/src/components/inputs/Tags/README.md +897 -0
- package/src/components/inputs/Tags/TagBar.vue +793 -0
- package/src/components/inputs/Tags/TagCreation.vue +219 -0
- package/src/components/inputs/Tags/TagPicker.vue +380 -0
- package/src/components/inputs/Tags/tag-bar-styles.ts +354 -0
- package/src/components/inputs/Tags/theme.css +121 -0
- package/src/components/inputs/Tags/types.ts +346 -0
- package/src/components/inputs/search/README.md +759 -0
- package/src/components/inputs/search/SearchBar.vue +394 -0
- package/src/components/inputs/search/SearchResults.vue +310 -0
- package/src/components/inputs/search/theme.css +187 -0
- package/src/components/inputs/search/types.ts +8 -0
- package/src/components/inputs/theme.css +102 -0
- package/src/components/menus/ActionMenu/ActionMenu.vue +383 -0
- package/src/components/menus/ActionMenu/README.md +825 -0
- package/src/components/menus/ActionMenu/theme.css +93 -0
- package/src/components/models/Popover/Popover.vue +551 -0
- package/src/components/models/Popover/README.md +885 -0
- package/src/components/models/Popover/theme.css +52 -0
- package/src/components/models/Sheet/README.md +1159 -0
- package/src/components/models/Sheet/Sheet.vue +465 -0
- package/src/components/models/Sheet/theme.css +72 -0
- package/src/components/models/Sidebar/README.md +1228 -0
- package/src/components/models/Sidebar/Sidebar.vue +480 -0
- package/src/components/models/Sidebar/theme.css +90 -0
- package/src/components/navigation/adaptive/AdaptiveLayout.vue +779 -0
- package/src/components/navigation/adaptive/AdaptiveLayoutBreadcrumbs.vue +192 -0
- package/src/components/navigation/adaptive/AdaptiveLayoutMenuButton.vue +149 -0
- package/src/components/navigation/adaptive/README.md +768 -0
- package/src/components/navigation/adaptive/types.ts +19 -0
- package/src/components/navigation/adaptive/useAdaptiveLayout.ts +89 -0
- package/src/components/navigation/adaptive/useBreakpoints.ts +41 -0
- package/src/components/navigation/adaptive/useContainerMonitor.ts +214 -0
- package/src/components/navigation/adaptive/useViewAnimation.ts +721 -0
- package/src/components/navigation/adaptive/useViewResize.ts +211 -0
- package/src/components/navigation/navstack/NavigationStack.vue +180 -0
- package/src/components/navigation/navstack/README.md +994 -0
- package/src/components/navigation/navstack/useNavigationStack.ts +164 -0
- package/src/components/navigation/slideover/README.md +1275 -0
- package/src/components/navigation/slideover/SlideoverController.vue +287 -0
- package/src/components/navigation/slideover/useSlideoverController.ts +320 -0
- package/src/components/navigation/splitview/README.md +1115 -0
- package/src/components/navigation/splitview/SplitViewController.vue +176 -0
- package/src/components/navigation/splitview/useSplitViewController.ts +388 -0
- package/src/components/navigation/tabcontroller/README.md +919 -0
- package/src/components/navigation/tabcontroller/TabController.vue +307 -0
- package/src/components/navigation/tabcontroller/TabItem.vue +57 -0
- package/src/components/navigation/tabcontroller/types.ts +24 -0
- package/src/components/navigation/tabcontroller/useTabController.ts +18 -0
- package/src/components/navigation/theme.css +91 -0
- package/src/components/navigation/types.ts +7 -0
- package/src/components/pickers/CollectionPicker/CollectionPicker.vue +398 -0
- package/src/components/pickers/CollectionPicker/README.md +1115 -0
- package/src/components/pickers/CollectionPicker/theme.css +14 -0
- package/src/components/pickers/CollectionPicker/types.ts +11 -0
- package/src/components/pickers/ColorPicker/ColorPicker.vue +376 -0
- package/src/components/pickers/ColorPicker/README.md +1439 -0
- package/src/components/pickers/ColorPicker/colors.ts +299 -0
- package/src/components/pickers/ColorPicker/theme.css +32 -0
- package/src/components/pickers/DatePicker/DatePicker.vue +660 -0
- package/src/components/pickers/DatePicker/README.md +1195 -0
- package/src/components/pickers/DatePicker/theme.css +22 -0
- package/src/components/pickers/FilePicker/FilePicker.vue +534 -0
- package/src/components/pickers/FilePicker/README.md +1542 -0
- package/src/components/pickers/FilePicker/theme.css +48 -0
- package/src/components/pickers/FilePicker/types.ts +10 -0
- package/src/components/pickers/IconPicker/IconPicker.vue +327 -0
- package/src/components/pickers/IconPicker/README.md +1161 -0
- package/src/components/pickers/IconPicker/theme.css +28 -0
- package/src/components/pickers/theme.css +82 -0
- package/src/components/views/MarkdownViewer/MarkdownViewer.vue +442 -0
- package/src/components/views/MarkdownViewer/README.md +833 -0
- package/src/components/views/MarkdownViewer/theme.css +130 -0
- package/src/index.ts +263 -0
- package/src/theme.ts +378 -0
- package/src/themes/crimson-dark.ts +556 -0
- package/src/themes/cyan-light.ts +556 -0
- package/src/themes/dark.ts +557 -0
- package/src/themes/gold-dark.ts +556 -0
- package/src/themes/grass-dark.ts +556 -0
- package/src/themes/indigo.ts +556 -0
- package/src/themes/light.ts +557 -0
- package/src/themes/orange-dark.ts +557 -0
- package/src/themes/orange-light.ts +557 -0
- package/src/vue.d.ts +45 -0
|
@@ -0,0 +1,1195 @@
|
|
|
1
|
+
# DatePicker
|
|
2
|
+
|
|
3
|
+
A comprehensive date picker component built with Vue 3 Composition API and TypeScript. The DatePicker provides an intuitive interface for selecting dates with support for both compact and full calendar views, smart positioning using Floating UI, and smooth scrolling through months.
|
|
4
|
+
|
|
5
|
+
## Installation/Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { DatePicker } from "@umbra-ui/core";
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Dependencies:**
|
|
12
|
+
|
|
13
|
+
- Vue 3.x
|
|
14
|
+
- @floating-ui/vue (for positioning)
|
|
15
|
+
- @umbra-ui/icons (for calendar and chevron icons)
|
|
16
|
+
|
|
17
|
+
## Basic Usage
|
|
18
|
+
|
|
19
|
+
```vue
|
|
20
|
+
<script setup lang="ts">
|
|
21
|
+
import { ref } from "vue";
|
|
22
|
+
import { DatePicker } from "@umbra-ui/core";
|
|
23
|
+
|
|
24
|
+
const selectedDate = ref(new Date());
|
|
25
|
+
|
|
26
|
+
const handleDateChange = (date: Date) => {
|
|
27
|
+
console.log("Selected date:", date);
|
|
28
|
+
console.log("Formatted date:", date.toLocaleDateString());
|
|
29
|
+
};
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div class="app">
|
|
34
|
+
<h2>Select a Date</h2>
|
|
35
|
+
|
|
36
|
+
<DatePicker v-model:date="selectedDate" @update:date="handleDateChange" />
|
|
37
|
+
|
|
38
|
+
<div v-if="selectedDate" class="date-info">
|
|
39
|
+
<p>Selected: {{ selectedDate.toLocaleDateString() }}</p>
|
|
40
|
+
<p>ISO String: {{ selectedDate.toISOString() }}</p>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<style module>
|
|
46
|
+
.app {
|
|
47
|
+
padding: 24px;
|
|
48
|
+
max-width: 400px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.date-info {
|
|
52
|
+
margin-top: 20px;
|
|
53
|
+
padding: 16px;
|
|
54
|
+
background: #f8f9fa;
|
|
55
|
+
border-radius: 8px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.date-info p {
|
|
59
|
+
margin: 0 0 8px 0;
|
|
60
|
+
color: #495057;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.date-info p:last-child {
|
|
64
|
+
margin-bottom: 0;
|
|
65
|
+
}
|
|
66
|
+
</style>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Props
|
|
70
|
+
|
|
71
|
+
| Prop Name | Type | Required | Default | Description |
|
|
72
|
+
| --------- | ------ | -------- | ------------ | ----------------------- |
|
|
73
|
+
| `date` | `Date` | Yes | `new Date()` | Currently selected date |
|
|
74
|
+
|
|
75
|
+
## Events
|
|
76
|
+
|
|
77
|
+
| Event Name | Payload Type | Description |
|
|
78
|
+
| ------------- | ------------ | ------------------------------- |
|
|
79
|
+
| `update:date` | `Date` | Emitted when a date is selected |
|
|
80
|
+
|
|
81
|
+
## CSS Customization
|
|
82
|
+
|
|
83
|
+
### Layout Variables
|
|
84
|
+
|
|
85
|
+
```css
|
|
86
|
+
.date-picker {
|
|
87
|
+
--picker-button-bg: #ffffff;
|
|
88
|
+
--picker-button-border: 1px solid #e9ecef;
|
|
89
|
+
--picker-button-hover-bg: #f8f9fa;
|
|
90
|
+
--picker-button-hover-border: 1px solid #dee2e6;
|
|
91
|
+
--picker-button-hover-shadow: rgba(0, 0, 0, 0.1);
|
|
92
|
+
--picker-button-hover-inset-shadow: rgba(255, 255, 255, 0.1);
|
|
93
|
+
--picker-button-selected-bg: #e3f2fd;
|
|
94
|
+
--picker-picker-bg: #ffffff;
|
|
95
|
+
--picker-picker-border: 1px solid #e9ecef;
|
|
96
|
+
--picker-picker-shadow: rgba(0, 0, 0, 0.1);
|
|
97
|
+
--picker-picker-inset-shadow: rgba(255, 255, 255, 0.1);
|
|
98
|
+
--picker-bg-subtle: #f8f9fa;
|
|
99
|
+
--picker-border-light: #e9ecef;
|
|
100
|
+
--picker-text-primary: #212529;
|
|
101
|
+
--picker-text-secondary: #6c757d;
|
|
102
|
+
--picker-bg-hover: #f8f9fa;
|
|
103
|
+
--picker-overlay-bg: rgba(0, 0, 0, 0.1);
|
|
104
|
+
--datepicker-weekdays-opacity: 0.5;
|
|
105
|
+
--datepicker-day-selected-bg: #acd8fc;
|
|
106
|
+
--datepicker-day-selected-border: #5eb1ef;
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Container Styling
|
|
111
|
+
|
|
112
|
+
```css
|
|
113
|
+
.date-picker {
|
|
114
|
+
display: flex;
|
|
115
|
+
flex-direction: column;
|
|
116
|
+
gap: 0.235rem;
|
|
117
|
+
align-items: start;
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Button Styling
|
|
122
|
+
|
|
123
|
+
```css
|
|
124
|
+
.date-picker .button {
|
|
125
|
+
padding: 0.588rem;
|
|
126
|
+
border-radius: 0.353rem;
|
|
127
|
+
cursor: default;
|
|
128
|
+
user-select: none;
|
|
129
|
+
display: flex;
|
|
130
|
+
align-items: center;
|
|
131
|
+
gap: 0.588rem;
|
|
132
|
+
transition: all 0.3s ease;
|
|
133
|
+
background-color: var(--picker-button-bg);
|
|
134
|
+
border: var(--picker-button-border);
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Calendar Styling
|
|
139
|
+
|
|
140
|
+
```css
|
|
141
|
+
.date-picker .picker {
|
|
142
|
+
position: absolute;
|
|
143
|
+
background-color: var(--picker-picker-bg);
|
|
144
|
+
border-radius: 0.353rem;
|
|
145
|
+
overflow: hidden;
|
|
146
|
+
box-shadow: var(--picker-picker-shadow);
|
|
147
|
+
border: var(--picker-picker-border);
|
|
148
|
+
z-index: 1000;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.date-picker .days {
|
|
152
|
+
display: grid;
|
|
153
|
+
grid-template-columns: repeat(7, 1fr);
|
|
154
|
+
gap: 0;
|
|
155
|
+
align-items: center;
|
|
156
|
+
max-height: 32rem;
|
|
157
|
+
overflow: auto;
|
|
158
|
+
color: var(--picker-text-primary);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Examples
|
|
163
|
+
|
|
164
|
+
### Event Date Selector
|
|
165
|
+
|
|
166
|
+
```vue
|
|
167
|
+
<script setup lang="ts">
|
|
168
|
+
import { ref } from "vue";
|
|
169
|
+
import { DatePicker } from "@umbra-ui/core";
|
|
170
|
+
|
|
171
|
+
const eventDate = ref(new Date());
|
|
172
|
+
const eventTitle = ref("");
|
|
173
|
+
|
|
174
|
+
const handleDateChange = (date: Date) => {
|
|
175
|
+
console.log("Event date changed to:", date);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const isDateInPast = (date: Date) => {
|
|
179
|
+
const today = new Date();
|
|
180
|
+
today.setHours(0, 0, 0, 0);
|
|
181
|
+
return date < today;
|
|
182
|
+
};
|
|
183
|
+
</script>
|
|
184
|
+
|
|
185
|
+
<template>
|
|
186
|
+
<div class="event-planner">
|
|
187
|
+
<div class="planner-header">
|
|
188
|
+
<h3>Event Planner</h3>
|
|
189
|
+
<p>Schedule your next event</p>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<div class="form-section">
|
|
193
|
+
<label class="form-label">Event Title</label>
|
|
194
|
+
<input
|
|
195
|
+
v-model="eventTitle"
|
|
196
|
+
type="text"
|
|
197
|
+
placeholder="Enter event title..."
|
|
198
|
+
class="form-input"
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<div class="form-section">
|
|
203
|
+
<label class="form-label">Event Date</label>
|
|
204
|
+
<DatePicker v-model:date="eventDate" @update:date="handleDateChange" />
|
|
205
|
+
|
|
206
|
+
<div v-if="isDateInPast(eventDate)" class="warning">
|
|
207
|
+
⚠️ This date is in the past
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<div class="event-preview">
|
|
212
|
+
<h4>Event Preview</h4>
|
|
213
|
+
<div class="preview-card">
|
|
214
|
+
<h5>{{ eventTitle || "Untitled Event" }}</h5>
|
|
215
|
+
<p>
|
|
216
|
+
{{
|
|
217
|
+
eventDate.toLocaleDateString("en-US", {
|
|
218
|
+
weekday: "long",
|
|
219
|
+
year: "numeric",
|
|
220
|
+
month: "long",
|
|
221
|
+
day: "numeric",
|
|
222
|
+
})
|
|
223
|
+
}}
|
|
224
|
+
</p>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
</template>
|
|
229
|
+
|
|
230
|
+
<style module>
|
|
231
|
+
.event-planner {
|
|
232
|
+
padding: 24px;
|
|
233
|
+
max-width: 500px;
|
|
234
|
+
background: #ffffff;
|
|
235
|
+
border-radius: 12px;
|
|
236
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.planner-header {
|
|
240
|
+
margin-bottom: 32px;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.planner-header h3 {
|
|
244
|
+
font-size: 24px;
|
|
245
|
+
font-weight: 600;
|
|
246
|
+
color: #1a202c;
|
|
247
|
+
margin: 0 0 8px 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.planner-header p {
|
|
251
|
+
color: #718096;
|
|
252
|
+
margin: 0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.form-section {
|
|
256
|
+
margin-bottom: 24px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.form-label {
|
|
260
|
+
display: block;
|
|
261
|
+
font-size: 14px;
|
|
262
|
+
font-weight: 500;
|
|
263
|
+
color: #374151;
|
|
264
|
+
margin-bottom: 8px;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.form-input {
|
|
268
|
+
width: 100%;
|
|
269
|
+
padding: 12px;
|
|
270
|
+
border: 1px solid #d1d5db;
|
|
271
|
+
border-radius: 6px;
|
|
272
|
+
font-size: 14px;
|
|
273
|
+
transition: border-color 0.2s ease;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.form-input:focus {
|
|
277
|
+
outline: none;
|
|
278
|
+
border-color: #3b82f6;
|
|
279
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.warning {
|
|
283
|
+
margin-top: 8px;
|
|
284
|
+
padding: 8px 12px;
|
|
285
|
+
background: #fef3c7;
|
|
286
|
+
color: #92400e;
|
|
287
|
+
border-radius: 4px;
|
|
288
|
+
font-size: 12px;
|
|
289
|
+
font-weight: 500;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.event-preview {
|
|
293
|
+
margin-top: 32px;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.event-preview h4 {
|
|
297
|
+
font-size: 18px;
|
|
298
|
+
font-weight: 500;
|
|
299
|
+
color: #2d3748;
|
|
300
|
+
margin: 0 0 16px 0;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.preview-card {
|
|
304
|
+
padding: 16px;
|
|
305
|
+
background: #f7fafc;
|
|
306
|
+
border-radius: 8px;
|
|
307
|
+
border-left: 4px solid #3b82f6;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.preview-card h5 {
|
|
311
|
+
font-size: 16px;
|
|
312
|
+
font-weight: 600;
|
|
313
|
+
color: #2d3748;
|
|
314
|
+
margin: 0 0 8px 0;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.preview-card p {
|
|
318
|
+
color: #4a5568;
|
|
319
|
+
margin: 0;
|
|
320
|
+
}
|
|
321
|
+
</style>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Appointment Scheduler
|
|
325
|
+
|
|
326
|
+
```vue
|
|
327
|
+
<script setup lang="ts">
|
|
328
|
+
import { ref, computed } from "vue";
|
|
329
|
+
import { DatePicker } from "@umbra-ui/core";
|
|
330
|
+
|
|
331
|
+
const appointmentDate = ref(new Date());
|
|
332
|
+
const appointmentTime = ref("09:00");
|
|
333
|
+
const appointmentType = ref("consultation");
|
|
334
|
+
|
|
335
|
+
const availableTimes = [
|
|
336
|
+
"09:00",
|
|
337
|
+
"09:30",
|
|
338
|
+
"10:00",
|
|
339
|
+
"10:30",
|
|
340
|
+
"11:00",
|
|
341
|
+
"11:30",
|
|
342
|
+
"14:00",
|
|
343
|
+
"14:30",
|
|
344
|
+
"15:00",
|
|
345
|
+
"15:30",
|
|
346
|
+
"16:00",
|
|
347
|
+
"16:30",
|
|
348
|
+
];
|
|
349
|
+
|
|
350
|
+
const isWeekend = computed(() => {
|
|
351
|
+
const day = appointmentDate.value.getDay();
|
|
352
|
+
return day === 0 || day === 6; // Sunday or Saturday
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const isDateInPast = computed(() => {
|
|
356
|
+
const today = new Date();
|
|
357
|
+
today.setHours(0, 0, 0, 0);
|
|
358
|
+
return appointmentDate.value < today;
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const canSchedule = computed(() => {
|
|
362
|
+
return !isWeekend.value && !isDateInPast.value && appointmentTime.value;
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const handleDateChange = (date: Date) => {
|
|
366
|
+
console.log("Appointment date changed to:", date);
|
|
367
|
+
};
|
|
368
|
+
</script>
|
|
369
|
+
|
|
370
|
+
<template>
|
|
371
|
+
<div class="appointment-scheduler">
|
|
372
|
+
<div class="scheduler-header">
|
|
373
|
+
<h3>Schedule Appointment</h3>
|
|
374
|
+
<p>Book your appointment with us</p>
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
<div class="form-section">
|
|
378
|
+
<label class="form-label">Appointment Type</label>
|
|
379
|
+
<select v-model="appointmentType" class="form-select">
|
|
380
|
+
<option value="consultation">Consultation</option>
|
|
381
|
+
<option value="follow-up">Follow-up</option>
|
|
382
|
+
<option value="checkup">Checkup</option>
|
|
383
|
+
<option value="emergency">Emergency</option>
|
|
384
|
+
</select>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
<div class="form-section">
|
|
388
|
+
<label class="form-label">Date</label>
|
|
389
|
+
<DatePicker
|
|
390
|
+
v-model:date="appointmentDate"
|
|
391
|
+
@update:date="handleDateChange"
|
|
392
|
+
/>
|
|
393
|
+
|
|
394
|
+
<div v-if="isWeekend" class="error">
|
|
395
|
+
❌ We don't schedule appointments on weekends
|
|
396
|
+
</div>
|
|
397
|
+
<div v-else-if="isDateInPast" class="error">
|
|
398
|
+
❌ Cannot schedule appointments in the past
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
|
|
402
|
+
<div class="form-section">
|
|
403
|
+
<label class="form-label">Time</label>
|
|
404
|
+
<div class="time-grid">
|
|
405
|
+
<button
|
|
406
|
+
v-for="time in availableTimes"
|
|
407
|
+
:key="time"
|
|
408
|
+
:class="['time-btn', { active: appointmentTime === time }]"
|
|
409
|
+
@click="appointmentTime = time"
|
|
410
|
+
>
|
|
411
|
+
{{ time }}
|
|
412
|
+
</button>
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
|
|
416
|
+
<div class="appointment-summary">
|
|
417
|
+
<h4>Appointment Summary</h4>
|
|
418
|
+
<div class="summary-card">
|
|
419
|
+
<div class="summary-item">
|
|
420
|
+
<span class="label">Type:</span>
|
|
421
|
+
<span class="value">{{ appointmentType }}</span>
|
|
422
|
+
</div>
|
|
423
|
+
<div class="summary-item">
|
|
424
|
+
<span class="label">Date:</span>
|
|
425
|
+
<span class="value">{{ appointmentDate.toLocaleDateString() }}</span>
|
|
426
|
+
</div>
|
|
427
|
+
<div class="summary-item">
|
|
428
|
+
<span class="label">Time:</span>
|
|
429
|
+
<span class="value">{{ appointmentTime }}</span>
|
|
430
|
+
</div>
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
<button :disabled="!canSchedule" class="schedule-btn">
|
|
434
|
+
{{ canSchedule ? "Schedule Appointment" : "Select Valid Date & Time" }}
|
|
435
|
+
</button>
|
|
436
|
+
</div>
|
|
437
|
+
</div>
|
|
438
|
+
</template>
|
|
439
|
+
|
|
440
|
+
<style module>
|
|
441
|
+
.appointment-scheduler {
|
|
442
|
+
padding: 24px;
|
|
443
|
+
max-width: 600px;
|
|
444
|
+
background: #ffffff;
|
|
445
|
+
border-radius: 12px;
|
|
446
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.scheduler-header {
|
|
450
|
+
margin-bottom: 32px;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.scheduler-header h3 {
|
|
454
|
+
font-size: 24px;
|
|
455
|
+
font-weight: 600;
|
|
456
|
+
color: #1a202c;
|
|
457
|
+
margin: 0 0 8px 0;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.scheduler-header p {
|
|
461
|
+
color: #718096;
|
|
462
|
+
margin: 0;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.form-section {
|
|
466
|
+
margin-bottom: 24px;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.form-label {
|
|
470
|
+
display: block;
|
|
471
|
+
font-size: 14px;
|
|
472
|
+
font-weight: 500;
|
|
473
|
+
color: #374151;
|
|
474
|
+
margin-bottom: 8px;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.form-select {
|
|
478
|
+
width: 100%;
|
|
479
|
+
padding: 12px;
|
|
480
|
+
border: 1px solid #d1d5db;
|
|
481
|
+
border-radius: 6px;
|
|
482
|
+
font-size: 14px;
|
|
483
|
+
background: white;
|
|
484
|
+
cursor: pointer;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.error {
|
|
488
|
+
margin-top: 8px;
|
|
489
|
+
padding: 8px 12px;
|
|
490
|
+
background: #fee2e2;
|
|
491
|
+
color: #dc2626;
|
|
492
|
+
border-radius: 4px;
|
|
493
|
+
font-size: 12px;
|
|
494
|
+
font-weight: 500;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.time-grid {
|
|
498
|
+
display: grid;
|
|
499
|
+
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
|
|
500
|
+
gap: 8px;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.time-btn {
|
|
504
|
+
padding: 8px 12px;
|
|
505
|
+
border: 1px solid #d1d5db;
|
|
506
|
+
border-radius: 6px;
|
|
507
|
+
background: white;
|
|
508
|
+
color: #374151;
|
|
509
|
+
font-size: 12px;
|
|
510
|
+
font-weight: 500;
|
|
511
|
+
cursor: pointer;
|
|
512
|
+
transition: all 0.2s ease;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.time-btn:hover {
|
|
516
|
+
border-color: #3b82f6;
|
|
517
|
+
background: #f0f9ff;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.time-btn.active {
|
|
521
|
+
background: #3b82f6;
|
|
522
|
+
color: white;
|
|
523
|
+
border-color: #3b82f6;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.appointment-summary {
|
|
527
|
+
margin-top: 32px;
|
|
528
|
+
padding-top: 24px;
|
|
529
|
+
border-top: 1px solid #e5e7eb;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.appointment-summary h4 {
|
|
533
|
+
font-size: 18px;
|
|
534
|
+
font-weight: 500;
|
|
535
|
+
color: #2d3748;
|
|
536
|
+
margin: 0 0 16px 0;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.summary-card {
|
|
540
|
+
background: #f7fafc;
|
|
541
|
+
border-radius: 8px;
|
|
542
|
+
padding: 16px;
|
|
543
|
+
margin-bottom: 16px;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.summary-item {
|
|
547
|
+
display: flex;
|
|
548
|
+
justify-content: space-between;
|
|
549
|
+
align-items: center;
|
|
550
|
+
margin-bottom: 8px;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.summary-item:last-child {
|
|
554
|
+
margin-bottom: 0;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.label {
|
|
558
|
+
font-weight: 500;
|
|
559
|
+
color: #4a5568;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.value {
|
|
563
|
+
color: #2d3748;
|
|
564
|
+
font-weight: 600;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.schedule-btn {
|
|
568
|
+
width: 100%;
|
|
569
|
+
padding: 12px;
|
|
570
|
+
background: #3b82f6;
|
|
571
|
+
color: white;
|
|
572
|
+
border: none;
|
|
573
|
+
border-radius: 6px;
|
|
574
|
+
font-size: 16px;
|
|
575
|
+
font-weight: 600;
|
|
576
|
+
cursor: pointer;
|
|
577
|
+
transition: background 0.2s ease;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.schedule-btn:hover:not(:disabled) {
|
|
581
|
+
background: #2563eb;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.schedule-btn:disabled {
|
|
585
|
+
background: #9ca3af;
|
|
586
|
+
cursor: not-allowed;
|
|
587
|
+
}
|
|
588
|
+
</style>
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Date Range Selector
|
|
592
|
+
|
|
593
|
+
```vue
|
|
594
|
+
<script setup lang="ts">
|
|
595
|
+
import { ref, computed } from "vue";
|
|
596
|
+
import { DatePicker } from "@umbra-ui/core";
|
|
597
|
+
|
|
598
|
+
const startDate = ref(new Date());
|
|
599
|
+
const endDate = ref(new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)); // 7 days from now
|
|
600
|
+
|
|
601
|
+
const dateRange = computed(() => {
|
|
602
|
+
const diffTime = Math.abs(
|
|
603
|
+
endDate.value.getTime() - startDate.value.getTime()
|
|
604
|
+
);
|
|
605
|
+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
606
|
+
return diffDays;
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
const isValidRange = computed(() => {
|
|
610
|
+
return endDate.value >= startDate.value;
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
const handleStartDateChange = (date: Date) => {
|
|
614
|
+
startDate.value = date;
|
|
615
|
+
// Ensure end date is not before start date
|
|
616
|
+
if (endDate.value < startDate.value) {
|
|
617
|
+
endDate.value = new Date(startDate.value.getTime() + 24 * 60 * 60 * 1000);
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
const handleEndDateChange = (date: Date) => {
|
|
622
|
+
endDate.value = date;
|
|
623
|
+
};
|
|
624
|
+
</script>
|
|
625
|
+
|
|
626
|
+
<template>
|
|
627
|
+
<div class="date-range-selector">
|
|
628
|
+
<div class="selector-header">
|
|
629
|
+
<h3>Date Range Selector</h3>
|
|
630
|
+
<p>Select your travel dates</p>
|
|
631
|
+
</div>
|
|
632
|
+
|
|
633
|
+
<div class="date-inputs">
|
|
634
|
+
<div class="date-input">
|
|
635
|
+
<label class="form-label">Check-in Date</label>
|
|
636
|
+
<DatePicker
|
|
637
|
+
v-model:date="startDate"
|
|
638
|
+
@update:date="handleStartDateChange"
|
|
639
|
+
/>
|
|
640
|
+
</div>
|
|
641
|
+
|
|
642
|
+
<div class="date-input">
|
|
643
|
+
<label class="form-label">Check-out Date</label>
|
|
644
|
+
<DatePicker v-model:date="endDate" @update:date="handleEndDateChange" />
|
|
645
|
+
</div>
|
|
646
|
+
</div>
|
|
647
|
+
|
|
648
|
+
<div v-if="!isValidRange" class="error">
|
|
649
|
+
❌ Check-out date must be after check-in date
|
|
650
|
+
</div>
|
|
651
|
+
|
|
652
|
+
<div v-else class="range-summary">
|
|
653
|
+
<h4>Booking Summary</h4>
|
|
654
|
+
<div class="summary-grid">
|
|
655
|
+
<div class="summary-item">
|
|
656
|
+
<span class="label">Check-in:</span>
|
|
657
|
+
<span class="value">{{ startDate.toLocaleDateString() }}</span>
|
|
658
|
+
</div>
|
|
659
|
+
<div class="summary-item">
|
|
660
|
+
<span class="label">Check-out:</span>
|
|
661
|
+
<span class="value">{{ endDate.toLocaleDateString() }}</span>
|
|
662
|
+
</div>
|
|
663
|
+
<div class="summary-item">
|
|
664
|
+
<span class="label">Duration:</span>
|
|
665
|
+
<span class="value"
|
|
666
|
+
>{{ dateRange }} {{ dateRange === 1 ? "day" : "days" }}</span
|
|
667
|
+
>
|
|
668
|
+
</div>
|
|
669
|
+
</div>
|
|
670
|
+
|
|
671
|
+
<button :disabled="!isValidRange" class="book-btn">
|
|
672
|
+
Book for {{ dateRange }} {{ dateRange === 1 ? "day" : "days" }}
|
|
673
|
+
</button>
|
|
674
|
+
</div>
|
|
675
|
+
</div>
|
|
676
|
+
</template>
|
|
677
|
+
|
|
678
|
+
<style module>
|
|
679
|
+
.date-range-selector {
|
|
680
|
+
padding: 24px;
|
|
681
|
+
max-width: 600px;
|
|
682
|
+
background: #ffffff;
|
|
683
|
+
border-radius: 12px;
|
|
684
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.selector-header {
|
|
688
|
+
margin-bottom: 32px;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
.selector-header h3 {
|
|
692
|
+
font-size: 24px;
|
|
693
|
+
font-weight: 600;
|
|
694
|
+
color: #1a202c;
|
|
695
|
+
margin: 0 0 8px 0;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.selector-header p {
|
|
699
|
+
color: #718096;
|
|
700
|
+
margin: 0;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.date-inputs {
|
|
704
|
+
display: grid;
|
|
705
|
+
grid-template-columns: 1fr 1fr;
|
|
706
|
+
gap: 24px;
|
|
707
|
+
margin-bottom: 24px;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.date-input {
|
|
711
|
+
text-align: center;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.form-label {
|
|
715
|
+
display: block;
|
|
716
|
+
font-size: 14px;
|
|
717
|
+
font-weight: 500;
|
|
718
|
+
color: #374151;
|
|
719
|
+
margin-bottom: 12px;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.error {
|
|
723
|
+
margin-bottom: 24px;
|
|
724
|
+
padding: 12px;
|
|
725
|
+
background: #fee2e2;
|
|
726
|
+
color: #dc2626;
|
|
727
|
+
border-radius: 6px;
|
|
728
|
+
text-align: center;
|
|
729
|
+
font-weight: 500;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
.range-summary {
|
|
733
|
+
padding-top: 24px;
|
|
734
|
+
border-top: 1px solid #e5e7eb;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
.range-summary h4 {
|
|
738
|
+
font-size: 18px;
|
|
739
|
+
font-weight: 500;
|
|
740
|
+
color: #2d3748;
|
|
741
|
+
margin: 0 0 16px 0;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
.summary-grid {
|
|
745
|
+
display: grid;
|
|
746
|
+
grid-template-columns: repeat(3, 1fr);
|
|
747
|
+
gap: 16px;
|
|
748
|
+
margin-bottom: 24px;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
.summary-item {
|
|
752
|
+
text-align: center;
|
|
753
|
+
padding: 16px;
|
|
754
|
+
background: #f7fafc;
|
|
755
|
+
border-radius: 8px;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
.label {
|
|
759
|
+
display: block;
|
|
760
|
+
font-size: 12px;
|
|
761
|
+
font-weight: 500;
|
|
762
|
+
color: #6b7280;
|
|
763
|
+
margin-bottom: 4px;
|
|
764
|
+
text-transform: uppercase;
|
|
765
|
+
letter-spacing: 0.05em;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
.value {
|
|
769
|
+
display: block;
|
|
770
|
+
font-size: 16px;
|
|
771
|
+
font-weight: 600;
|
|
772
|
+
color: #1f2937;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.book-btn {
|
|
776
|
+
width: 100%;
|
|
777
|
+
padding: 16px;
|
|
778
|
+
background: #059669;
|
|
779
|
+
color: white;
|
|
780
|
+
border: none;
|
|
781
|
+
border-radius: 8px;
|
|
782
|
+
font-size: 16px;
|
|
783
|
+
font-weight: 600;
|
|
784
|
+
cursor: pointer;
|
|
785
|
+
transition: background 0.2s ease;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.book-btn:hover:not(:disabled) {
|
|
789
|
+
background: #047857;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
.book-btn:disabled {
|
|
793
|
+
background: #9ca3af;
|
|
794
|
+
cursor: not-allowed;
|
|
795
|
+
}
|
|
796
|
+
</style>
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
### Birthday Selector
|
|
800
|
+
|
|
801
|
+
```vue
|
|
802
|
+
<script setup lang="ts">
|
|
803
|
+
import { ref, computed } from "vue";
|
|
804
|
+
import { DatePicker } from "@umbra-ui/core";
|
|
805
|
+
|
|
806
|
+
const birthday = ref(new Date(1990, 0, 1)); // Default to January 1, 1990
|
|
807
|
+
|
|
808
|
+
const age = computed(() => {
|
|
809
|
+
const today = new Date();
|
|
810
|
+
const birthDate = new Date(birthday.value);
|
|
811
|
+
let age = today.getFullYear() - birthDate.getFullYear();
|
|
812
|
+
const monthDiff = today.getMonth() - birthDate.getMonth();
|
|
813
|
+
|
|
814
|
+
if (
|
|
815
|
+
monthDiff < 0 ||
|
|
816
|
+
(monthDiff === 0 && today.getDate() < birthDate.getDate())
|
|
817
|
+
) {
|
|
818
|
+
age--;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
return age;
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
const zodiacSign = computed(() => {
|
|
825
|
+
const month = birthday.value.getMonth() + 1;
|
|
826
|
+
const day = birthday.value.getDate();
|
|
827
|
+
|
|
828
|
+
const signs = [
|
|
829
|
+
{ name: "Capricorn", start: [12, 22], end: [1, 19] },
|
|
830
|
+
{ name: "Aquarius", start: [1, 20], end: [2, 18] },
|
|
831
|
+
{ name: "Pisces", start: [2, 19], end: [3, 20] },
|
|
832
|
+
{ name: "Aries", start: [3, 21], end: [4, 19] },
|
|
833
|
+
{ name: "Taurus", start: [4, 20], end: [5, 20] },
|
|
834
|
+
{ name: "Gemini", start: [5, 21], end: [6, 20] },
|
|
835
|
+
{ name: "Cancer", start: [6, 21], end: [7, 22] },
|
|
836
|
+
{ name: "Leo", start: [7, 23], end: [8, 22] },
|
|
837
|
+
{ name: "Virgo", start: [8, 23], end: [9, 22] },
|
|
838
|
+
{ name: "Libra", start: [9, 23], end: [10, 22] },
|
|
839
|
+
{ name: "Scorpio", start: [10, 23], end: [11, 21] },
|
|
840
|
+
{ name: "Sagittarius", start: [11, 22], end: [12, 21] },
|
|
841
|
+
];
|
|
842
|
+
|
|
843
|
+
for (const sign of signs) {
|
|
844
|
+
const [startMonth, startDay] = sign.start;
|
|
845
|
+
const [endMonth, endDay] = sign.end;
|
|
846
|
+
|
|
847
|
+
if (
|
|
848
|
+
(month === startMonth && day >= startDay) ||
|
|
849
|
+
(month === endMonth && day <= endDay) ||
|
|
850
|
+
(startMonth > endMonth && (month > startMonth || month < endMonth))
|
|
851
|
+
) {
|
|
852
|
+
return sign.name;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
return "Unknown";
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
const isFutureDate = computed(() => {
|
|
860
|
+
const today = new Date();
|
|
861
|
+
today.setHours(0, 0, 0, 0);
|
|
862
|
+
return birthday.value > today;
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
const handleBirthdayChange = (date: Date) => {
|
|
866
|
+
birthday.value = date;
|
|
867
|
+
console.log("Birthday changed to:", date);
|
|
868
|
+
};
|
|
869
|
+
</script>
|
|
870
|
+
|
|
871
|
+
<template>
|
|
872
|
+
<div class="birthday-selector">
|
|
873
|
+
<div class="selector-header">
|
|
874
|
+
<h3>Birthday Information</h3>
|
|
875
|
+
<p>Tell us about your special day</p>
|
|
876
|
+
</div>
|
|
877
|
+
|
|
878
|
+
<div class="form-section">
|
|
879
|
+
<label class="form-label">Birthday</label>
|
|
880
|
+
<DatePicker v-model:date="birthday" @update:date="handleBirthdayChange" />
|
|
881
|
+
|
|
882
|
+
<div v-if="isFutureDate" class="warning">
|
|
883
|
+
⚠️ This date is in the future
|
|
884
|
+
</div>
|
|
885
|
+
</div>
|
|
886
|
+
|
|
887
|
+
<div class="birthday-info">
|
|
888
|
+
<h4>Your Birthday Details</h4>
|
|
889
|
+
<div class="info-grid">
|
|
890
|
+
<div class="info-item">
|
|
891
|
+
<span class="label">Age</span>
|
|
892
|
+
<span class="value">{{ age }} years old</span>
|
|
893
|
+
</div>
|
|
894
|
+
<div class="info-item">
|
|
895
|
+
<span class="label">Zodiac Sign</span>
|
|
896
|
+
<span class="value">{{ zodiacSign }}</span>
|
|
897
|
+
</div>
|
|
898
|
+
<div class="info-item">
|
|
899
|
+
<span class="label">Birthday</span>
|
|
900
|
+
<span class="value">{{
|
|
901
|
+
birthday.toLocaleDateString("en-US", {
|
|
902
|
+
weekday: "long",
|
|
903
|
+
year: "numeric",
|
|
904
|
+
month: "long",
|
|
905
|
+
day: "numeric",
|
|
906
|
+
})
|
|
907
|
+
}}</span>
|
|
908
|
+
</div>
|
|
909
|
+
</div>
|
|
910
|
+
|
|
911
|
+
<div class="birthday-message">
|
|
912
|
+
<p>🎉 Your birthday is a special day worth celebrating!</p>
|
|
913
|
+
</div>
|
|
914
|
+
</div>
|
|
915
|
+
</div>
|
|
916
|
+
</template>
|
|
917
|
+
|
|
918
|
+
<style module>
|
|
919
|
+
.birthday-selector {
|
|
920
|
+
padding: 24px;
|
|
921
|
+
max-width: 500px;
|
|
922
|
+
background: #ffffff;
|
|
923
|
+
border-radius: 12px;
|
|
924
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.selector-header {
|
|
928
|
+
margin-bottom: 32px;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.selector-header h3 {
|
|
932
|
+
font-size: 24px;
|
|
933
|
+
font-weight: 600;
|
|
934
|
+
color: #1a202c;
|
|
935
|
+
margin: 0 0 8px 0;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
.selector-header p {
|
|
939
|
+
color: #718096;
|
|
940
|
+
margin: 0;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
.form-section {
|
|
944
|
+
margin-bottom: 32px;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.form-label {
|
|
948
|
+
display: block;
|
|
949
|
+
font-size: 14px;
|
|
950
|
+
font-weight: 500;
|
|
951
|
+
color: #374151;
|
|
952
|
+
margin-bottom: 12px;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
.warning {
|
|
956
|
+
margin-top: 8px;
|
|
957
|
+
padding: 8px 12px;
|
|
958
|
+
background: #fef3c7;
|
|
959
|
+
color: #92400e;
|
|
960
|
+
border-radius: 4px;
|
|
961
|
+
font-size: 12px;
|
|
962
|
+
font-weight: 500;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
.birthday-info {
|
|
966
|
+
padding-top: 24px;
|
|
967
|
+
border-top: 1px solid #e5e7eb;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
.birthday-info h4 {
|
|
971
|
+
font-size: 18px;
|
|
972
|
+
font-weight: 500;
|
|
973
|
+
color: #2d3748;
|
|
974
|
+
margin: 0 0 20px 0;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
.info-grid {
|
|
978
|
+
display: grid;
|
|
979
|
+
gap: 16px;
|
|
980
|
+
margin-bottom: 24px;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
.info-item {
|
|
984
|
+
display: flex;
|
|
985
|
+
justify-content: space-between;
|
|
986
|
+
align-items: center;
|
|
987
|
+
padding: 16px;
|
|
988
|
+
background: #f7fafc;
|
|
989
|
+
border-radius: 8px;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
.label {
|
|
993
|
+
font-weight: 500;
|
|
994
|
+
color: #4a5568;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
.value {
|
|
998
|
+
color: #2d3748;
|
|
999
|
+
font-weight: 600;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
.birthday-message {
|
|
1003
|
+
text-align: center;
|
|
1004
|
+
padding: 20px;
|
|
1005
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
1006
|
+
color: white;
|
|
1007
|
+
border-radius: 8px;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
.birthday-message p {
|
|
1011
|
+
margin: 0;
|
|
1012
|
+
font-size: 16px;
|
|
1013
|
+
font-weight: 500;
|
|
1014
|
+
}
|
|
1015
|
+
</style>
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
## Advanced Usage
|
|
1019
|
+
|
|
1020
|
+
### Custom Date Validation
|
|
1021
|
+
|
|
1022
|
+
```vue
|
|
1023
|
+
<script setup lang="ts">
|
|
1024
|
+
import { ref, computed } from "vue";
|
|
1025
|
+
import { DatePicker } from "@umbra-ui/core";
|
|
1026
|
+
|
|
1027
|
+
const selectedDate = ref(new Date());
|
|
1028
|
+
const minDate = new Date(2024, 0, 1); // January 1, 2024
|
|
1029
|
+
const maxDate = new Date(2024, 11, 31); // December 31, 2024
|
|
1030
|
+
|
|
1031
|
+
const isDateValid = computed(() => {
|
|
1032
|
+
return selectedDate.value >= minDate && selectedDate.value <= maxDate;
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
const validationMessage = computed(() => {
|
|
1036
|
+
if (selectedDate.value < minDate) {
|
|
1037
|
+
return `Date must be after ${minDate.toLocaleDateString()}`;
|
|
1038
|
+
}
|
|
1039
|
+
if (selectedDate.value > maxDate) {
|
|
1040
|
+
return `Date must be before ${maxDate.toLocaleDateString()}`;
|
|
1041
|
+
}
|
|
1042
|
+
return "Date is valid";
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
const handleDateChange = (date: Date) => {
|
|
1046
|
+
selectedDate.value = date;
|
|
1047
|
+
console.log("Date validation:", isDateValid.value);
|
|
1048
|
+
};
|
|
1049
|
+
</script>
|
|
1050
|
+
|
|
1051
|
+
<template>
|
|
1052
|
+
<div class="date-validation">
|
|
1053
|
+
<h3>Date Validation Example</h3>
|
|
1054
|
+
|
|
1055
|
+
<div class="form-section">
|
|
1056
|
+
<label class="form-label">Select Date (2024 only)</label>
|
|
1057
|
+
<DatePicker v-model:date="selectedDate" @update:date="handleDateChange" />
|
|
1058
|
+
|
|
1059
|
+
<div
|
|
1060
|
+
:class="[
|
|
1061
|
+
'validation-message',
|
|
1062
|
+
{
|
|
1063
|
+
valid: isDateValid,
|
|
1064
|
+
invalid: !isDateValid,
|
|
1065
|
+
},
|
|
1066
|
+
]"
|
|
1067
|
+
>
|
|
1068
|
+
{{ validationMessage }}
|
|
1069
|
+
</div>
|
|
1070
|
+
</div>
|
|
1071
|
+
|
|
1072
|
+
<div class="date-info">
|
|
1073
|
+
<p><strong>Selected:</strong> {{ selectedDate.toLocaleDateString() }}</p>
|
|
1074
|
+
<p><strong>Valid:</strong> {{ isDateValid ? "Yes" : "No" }}</p>
|
|
1075
|
+
</div>
|
|
1076
|
+
</div>
|
|
1077
|
+
</template>
|
|
1078
|
+
|
|
1079
|
+
<style module>
|
|
1080
|
+
.date-validation {
|
|
1081
|
+
padding: 24px;
|
|
1082
|
+
max-width: 400px;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
.form-section {
|
|
1086
|
+
margin-bottom: 24px;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
.form-label {
|
|
1090
|
+
display: block;
|
|
1091
|
+
font-size: 14px;
|
|
1092
|
+
font-weight: 500;
|
|
1093
|
+
color: #374151;
|
|
1094
|
+
margin-bottom: 12px;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
.validation-message {
|
|
1098
|
+
margin-top: 8px;
|
|
1099
|
+
padding: 8px 12px;
|
|
1100
|
+
border-radius: 4px;
|
|
1101
|
+
font-size: 12px;
|
|
1102
|
+
font-weight: 500;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
.validation-message.valid {
|
|
1106
|
+
background: #d1fae5;
|
|
1107
|
+
color: #065f46;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
.validation-message.invalid {
|
|
1111
|
+
background: #fee2e2;
|
|
1112
|
+
color: #dc2626;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
.date-info {
|
|
1116
|
+
padding: 16px;
|
|
1117
|
+
background: #f7fafc;
|
|
1118
|
+
border-radius: 8px;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
.date-info p {
|
|
1122
|
+
margin: 0 0 8px 0;
|
|
1123
|
+
color: #4a5568;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
.date-info p:last-child {
|
|
1127
|
+
margin-bottom: 0;
|
|
1128
|
+
}
|
|
1129
|
+
</style>
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
## Performance Considerations
|
|
1133
|
+
|
|
1134
|
+
- **Floating UI**: Smart positioning with automatic updates and cleanup
|
|
1135
|
+
- **Teleport**: Overlay and picker are teleported to body for proper z-index management
|
|
1136
|
+
- **Event Cleanup**: Proper cleanup of auto-update listeners on unmount
|
|
1137
|
+
- **Lazy Loading**: Months are generated on-demand during scrolling
|
|
1138
|
+
- **Smooth Scrolling**: Optimized scroll handling with debouncing
|
|
1139
|
+
|
|
1140
|
+
## Accessibility
|
|
1141
|
+
|
|
1142
|
+
- **Keyboard Navigation**: Support for keyboard interaction
|
|
1143
|
+
- **Screen Reader Support**: Proper ARIA labels and semantic structure
|
|
1144
|
+
- **Focus Management**: Focus is properly managed during interactions
|
|
1145
|
+
- **Date Information**: Displays formatted date strings for clarity
|
|
1146
|
+
- **High Contrast**: Supports high contrast mode with appropriate styling
|
|
1147
|
+
|
|
1148
|
+
## Browser Support
|
|
1149
|
+
|
|
1150
|
+
- **Modern Browsers**: Chrome 88+, Firefox 85+, Safari 14+, Edge 88+
|
|
1151
|
+
- **Mobile Browsers**: iOS Safari 14+, Chrome Mobile 88+
|
|
1152
|
+
- **CSS Features**: Uses CSS Grid and modern layout features
|
|
1153
|
+
- **JavaScript**: Requires ES2020+ support
|
|
1154
|
+
|
|
1155
|
+
## Troubleshooting
|
|
1156
|
+
|
|
1157
|
+
### Common Issues
|
|
1158
|
+
|
|
1159
|
+
1. **Picker not positioning correctly**: Ensure the trigger button has proper positioning context
|
|
1160
|
+
2. **Date not updating**: Check that the v-model binding is correctly set up
|
|
1161
|
+
3. **Calendar not showing**: Verify that the showPopover state is properly managed
|
|
1162
|
+
4. **Scrolling issues**: Ensure the scroll container has proper height constraints
|
|
1163
|
+
|
|
1164
|
+
### Debug Mode
|
|
1165
|
+
|
|
1166
|
+
```vue
|
|
1167
|
+
<script setup lang="ts">
|
|
1168
|
+
import { ref, watch } from "vue";
|
|
1169
|
+
import { DatePicker } from "@umbra-ui/core";
|
|
1170
|
+
|
|
1171
|
+
const selectedDate = ref(new Date());
|
|
1172
|
+
|
|
1173
|
+
// Watch for changes
|
|
1174
|
+
watch(selectedDate, (newDate) => {
|
|
1175
|
+
console.log("Selected date changed:", newDate);
|
|
1176
|
+
console.log("Date string:", newDate.toLocaleDateString());
|
|
1177
|
+
});
|
|
1178
|
+
</script>
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
## Migration Guide
|
|
1182
|
+
|
|
1183
|
+
### From v1 to v2
|
|
1184
|
+
|
|
1185
|
+
- `v-model` prop is now `v-model:date`
|
|
1186
|
+
- Event names have been updated to use kebab-case
|
|
1187
|
+
- Calendar system has been improved with better scrolling
|
|
1188
|
+
- Floating UI integration has been enhanced
|
|
1189
|
+
|
|
1190
|
+
### Breaking Changes
|
|
1191
|
+
|
|
1192
|
+
- Removed `value` prop in favor of `v-model:date`
|
|
1193
|
+
- Changed event names from camelCase to kebab-case
|
|
1194
|
+
- Updated date prop to use Date object instead of string
|
|
1195
|
+
- Modified CSS class naming convention
|