@umbra.ui/core 0.1.17 → 0.1.19
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/Button/Button.vue +417 -0
- package/dist/components/controls/Button/README.md +348 -0
- package/dist/components/controls/Button/theme.css +200 -0
- package/dist/components/controls/Checkbox/Checkbox.vue +164 -0
- package/dist/components/controls/Checkbox/README.md +441 -0
- package/dist/components/controls/Checkbox/theme.css +36 -0
- package/dist/components/controls/Dropdown/Dropdown.vue +476 -0
- package/dist/components/controls/Dropdown/README.md +370 -0
- package/dist/components/controls/Dropdown/theme.css +50 -0
- package/dist/components/controls/Dropdown/types.ts +6 -0
- package/dist/components/controls/IconButton/IconButton.vue +267 -0
- package/dist/components/controls/IconButton/README.md +502 -0
- package/dist/components/controls/IconButton/theme.css +89 -0
- package/dist/components/controls/Radio/README.md +591 -0
- package/dist/components/controls/Radio/Radio.vue +89 -0
- package/dist/components/controls/Radio/theme.css +14 -0
- package/dist/components/controls/RangeSlider/README.md +608 -0
- package/dist/components/controls/RangeSlider/RangeSlider.vue +535 -0
- package/dist/components/controls/RangeSlider/theme.css +80 -0
- package/dist/components/controls/SegmentedControl/README.md +587 -0
- package/dist/components/controls/SegmentedControl/SegmentedControl.vue +284 -0
- package/dist/components/controls/SegmentedControl/theme.css +60 -0
- package/dist/components/controls/SegmentedControl/types.ts +5 -0
- package/dist/components/controls/Slider/README.md +627 -0
- package/dist/components/controls/Slider/Slider.vue +260 -0
- package/dist/components/controls/Slider/theme.css +74 -0
- package/dist/components/controls/Stepper/README.md +601 -0
- package/dist/components/controls/Stepper/Stepper.vue +103 -0
- package/dist/components/controls/Stepper/theme.css +53 -0
- package/dist/components/controls/Switch/README.md +667 -0
- package/dist/components/controls/Switch/Switch.vue +127 -0
- package/dist/components/controls/Switch/theme.css +42 -0
- package/dist/components/dialogs/Alert/Alert.vue +218 -0
- package/dist/components/dialogs/Alert/README.md +450 -0
- package/dist/components/dialogs/Alert/theme.css +44 -0
- package/dist/components/dialogs/Alert/types.ts +11 -0
- package/dist/components/dialogs/Toast/README.md +522 -0
- package/dist/components/dialogs/Toast/Toast.vue +296 -0
- package/dist/components/dialogs/Toast/ToastContainer.vue +330 -0
- package/dist/components/dialogs/Toast/theme.css +44 -0
- package/dist/components/dialogs/Toast/types.ts +46 -0
- package/dist/components/dialogs/Toast/useToast.ts +127 -0
- package/dist/components/indicators/ProgressBar/ProgressBar.vue +98 -0
- package/dist/components/indicators/ProgressBar/README.md +744 -0
- package/dist/components/indicators/ProgressBar/theme.css +36 -0
- package/dist/components/indicators/Tooltip/README.md +723 -0
- package/dist/components/indicators/Tooltip/TooltipProvider.vue +142 -0
- package/dist/components/indicators/Tooltip/theme.css +18 -0
- package/dist/components/indicators/Tooltip/tooltip.ts +48 -0
- package/dist/components/indicators/Tooltip/types.ts +15 -0
- package/dist/components/indicators/Tooltip/useTooltip.ts +71 -0
- package/dist/components/inputs/AutogrowTextView/AutogrowTextView.vue +110 -0
- package/dist/components/inputs/AutogrowTextView/README.md +643 -0
- package/dist/components/inputs/AutogrowTextView/theme.css +28 -0
- package/dist/components/inputs/InputCard/InputCard.vue +600 -0
- package/dist/components/inputs/InputCard/README.md +636 -0
- package/dist/components/inputs/InputEmail/InputEmail.vue +698 -0
- package/dist/components/inputs/InputEmail/README.md +764 -0
- package/dist/components/inputs/InputNumber/InputNumber.vue +300 -0
- package/dist/components/inputs/InputNumber/README.md +749 -0
- package/dist/components/inputs/InputPhone/InputPhone.vue +645 -0
- package/dist/components/inputs/InputPhone/README.md +636 -0
- package/dist/components/inputs/InputSecure/InputSecure.vue +646 -0
- package/dist/components/inputs/InputSecure/README.md +771 -0
- package/dist/components/inputs/InputText/InputText.vue +225 -0
- package/dist/components/inputs/InputText/README.md +844 -0
- package/dist/components/inputs/OTP/OTP.vue +349 -0
- package/dist/components/inputs/OTP/README.md +736 -0
- package/dist/components/inputs/OTP/theme.css +50 -0
- package/dist/components/inputs/StringCapture/README.md +718 -0
- package/dist/components/inputs/StringCapture/StringCapture.vue +315 -0
- package/dist/components/inputs/StringCapture/theme.css +86 -0
- package/dist/components/inputs/Tags/README.md +897 -0
- package/dist/components/inputs/Tags/TagBar.vue +793 -0
- package/dist/components/inputs/Tags/TagCreation.vue +219 -0
- package/dist/components/inputs/Tags/TagPicker.vue +380 -0
- package/dist/components/inputs/Tags/tag-bar-styles.ts +354 -0
- package/dist/components/inputs/Tags/theme.css +121 -0
- package/dist/components/inputs/Tags/types.ts +346 -0
- package/dist/components/inputs/search/README.md +759 -0
- package/dist/components/inputs/search/SearchBar.vue +394 -0
- package/dist/components/inputs/search/SearchResults.vue +310 -0
- package/dist/components/inputs/search/theme.css +187 -0
- package/dist/components/inputs/search/types.ts +8 -0
- package/dist/components/inputs/theme.css +102 -0
- package/dist/components/menus/ActionMenu/ActionMenu.vue +383 -0
- package/dist/components/menus/ActionMenu/README.md +825 -0
- package/dist/components/menus/ActionMenu/theme.css +93 -0
- package/dist/components/models/Popover/Popover.vue +551 -0
- package/dist/components/models/Popover/README.md +885 -0
- package/dist/components/models/Popover/theme.css +52 -0
- package/dist/components/models/Sheet/README.md +1159 -0
- package/dist/components/models/Sheet/Sheet.vue +465 -0
- package/dist/components/models/Sheet/theme.css +72 -0
- package/dist/components/models/Sidebar/README.md +1228 -0
- package/dist/components/models/Sidebar/Sidebar.vue +480 -0
- package/dist/components/models/Sidebar/theme.css +90 -0
- package/dist/components/navigation/adaptive/AdaptiveLayout.vue +779 -0
- package/dist/components/navigation/adaptive/AdaptiveLayoutBreadcrumbs.vue +192 -0
- package/dist/components/navigation/adaptive/AdaptiveLayoutMenuButton.vue +149 -0
- package/dist/components/navigation/adaptive/README.md +768 -0
- package/dist/components/navigation/adaptive/types.ts +19 -0
- package/dist/components/navigation/adaptive/useAdaptiveLayout.ts +89 -0
- package/dist/components/navigation/adaptive/useBreakpoints.ts +41 -0
- package/dist/components/navigation/adaptive/useContainerMonitor.ts +214 -0
- package/dist/components/navigation/adaptive/useViewAnimation.ts +721 -0
- package/dist/components/navigation/adaptive/useViewResize.ts +211 -0
- package/dist/components/navigation/navstack/NavigationStack.vue +180 -0
- package/dist/components/navigation/navstack/README.md +994 -0
- package/dist/components/navigation/navstack/useNavigationStack.ts +164 -0
- package/dist/components/navigation/slideover/README.md +1275 -0
- package/dist/components/navigation/slideover/SlideoverController.vue +287 -0
- package/dist/components/navigation/slideover/useSlideoverController.ts +320 -0
- package/dist/components/navigation/splitview/README.md +1115 -0
- package/dist/components/navigation/splitview/SplitViewController.vue +176 -0
- package/dist/components/navigation/splitview/useSplitViewController.ts +388 -0
- package/dist/components/navigation/tabcontroller/README.md +919 -0
- package/dist/components/navigation/tabcontroller/TabController.vue +307 -0
- package/dist/components/navigation/tabcontroller/TabItem.vue +57 -0
- package/dist/components/navigation/tabcontroller/types.ts +24 -0
- package/dist/components/navigation/tabcontroller/useTabController.ts +18 -0
- package/dist/components/navigation/theme.css +91 -0
- package/dist/components/navigation/types.ts +7 -0
- package/dist/components/pickers/CollectionPicker/CollectionPicker.vue +398 -0
- package/dist/components/pickers/CollectionPicker/README.md +1115 -0
- package/dist/components/pickers/CollectionPicker/theme.css +14 -0
- package/dist/components/pickers/CollectionPicker/types.ts +11 -0
- package/dist/components/pickers/ColorPicker/ColorPicker.vue +376 -0
- package/dist/components/pickers/ColorPicker/README.md +1439 -0
- package/dist/components/pickers/ColorPicker/colors.ts +299 -0
- package/dist/components/pickers/ColorPicker/theme.css +32 -0
- package/dist/components/pickers/DatePicker/DatePicker.vue +660 -0
- package/dist/components/pickers/DatePicker/README.md +1195 -0
- package/dist/components/pickers/DatePicker/theme.css +22 -0
- package/dist/components/pickers/FilePicker/FilePicker.vue +534 -0
- package/dist/components/pickers/FilePicker/README.md +1542 -0
- package/dist/components/pickers/FilePicker/theme.css +48 -0
- package/dist/components/pickers/FilePicker/types.ts +10 -0
- package/dist/components/pickers/IconPicker/IconPicker.vue +327 -0
- package/dist/components/pickers/IconPicker/README.md +1161 -0
- package/dist/components/pickers/IconPicker/theme.css +28 -0
- package/dist/components/pickers/theme.css +82 -0
- package/dist/components/views/MarkdownViewer/MarkdownViewer.vue +442 -0
- package/dist/components/views/MarkdownViewer/README.md +833 -0
- package/dist/components/views/MarkdownViewer/theme.css +130 -0
- package/package.json +4 -3
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
# Dropdown
|
|
2
|
+
|
|
3
|
+
A fully-featured dropdown component built with Vue 3 Composition API and TypeScript. The Dropdown component provides a customizable select interface with keyboard navigation, accessibility features, and smooth animations.
|
|
4
|
+
|
|
5
|
+
## Installation/Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Dropdown } from "@umbra-ui/core";
|
|
9
|
+
import type { DropdownItem } from "@umbra-ui/core";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**Dependencies:**
|
|
13
|
+
|
|
14
|
+
- Vue 3.x
|
|
15
|
+
- @umbra-ui/icons (for chevron icon)
|
|
16
|
+
- @umbra-ui/core (Button component)
|
|
17
|
+
|
|
18
|
+
## Basic Usage
|
|
19
|
+
|
|
20
|
+
```vue
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { ref } from "vue";
|
|
23
|
+
import { Dropdown } from "@umbra-ui/core";
|
|
24
|
+
import type { DropdownItem } from "@umbra-ui/core";
|
|
25
|
+
|
|
26
|
+
const selectedItem = ref<DropdownItem | null>(null);
|
|
27
|
+
|
|
28
|
+
const dropdownItems: DropdownItem[] = [
|
|
29
|
+
{ id: "1", title: "Option 1" },
|
|
30
|
+
{ id: "2", title: "Option 2" },
|
|
31
|
+
{ id: "3", title: "Option 3" },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const handleSelectionChange = (item: DropdownItem | null) => {
|
|
35
|
+
console.log("Selected item:", item);
|
|
36
|
+
selectedItem.value = item;
|
|
37
|
+
};
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<Dropdown
|
|
42
|
+
:items="dropdownItems"
|
|
43
|
+
v-model="selectedItem"
|
|
44
|
+
placeholder="Choose an option"
|
|
45
|
+
@change="handleSelectionChange"
|
|
46
|
+
/>
|
|
47
|
+
</template>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Props
|
|
51
|
+
|
|
52
|
+
| Prop Name | Type | Required | Default | Description |
|
|
53
|
+
| ------------- | ---------------------- | -------- | ------------------------------------------------------------------------------------- | -------------------------------------------- |
|
|
54
|
+
| `items` | `DropdownItem[]` | Yes | `[{ id: "1", title: "One" }, { id: "2", title: "Two" }, { id: "3", title: "Three" }]` | Array of dropdown items to display |
|
|
55
|
+
| `modelValue` | `DropdownItem \| null` | No | `null` | Currently selected item (v-model support) |
|
|
56
|
+
| `showOverlay` | `boolean` | No | `true` | Whether to show background overlay when open |
|
|
57
|
+
| `placeholder` | `string` | No | `"Select an option"` | Placeholder text when no item is selected |
|
|
58
|
+
|
|
59
|
+
### DropdownItem Interface
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
interface DropdownItem {
|
|
63
|
+
id: string | number;
|
|
64
|
+
title: string;
|
|
65
|
+
subtitle?: string;
|
|
66
|
+
disabled?: boolean;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Events
|
|
71
|
+
|
|
72
|
+
| Event Name | Payload Type | Description |
|
|
73
|
+
| ------------------- | ---------------------- | ------------------------------------------- |
|
|
74
|
+
| `update:modelValue` | `DropdownItem \| null` | Emitted when selection changes (v-model) |
|
|
75
|
+
| `change` | `DropdownItem \| null` | Emitted when selection changes (additional) |
|
|
76
|
+
|
|
77
|
+
### Event Examples
|
|
78
|
+
|
|
79
|
+
```vue
|
|
80
|
+
<script setup lang="ts">
|
|
81
|
+
import { ref } from "vue";
|
|
82
|
+
import { Dropdown } from "@umbra-ui/core";
|
|
83
|
+
import type { DropdownItem } from "@umbra-ui/core";
|
|
84
|
+
|
|
85
|
+
const selectedItem = ref<DropdownItem | null>(null);
|
|
86
|
+
|
|
87
|
+
const handleModelValueUpdate = (item: DropdownItem | null) => {
|
|
88
|
+
console.log("Model value updated:", item);
|
|
89
|
+
selectedItem.value = item;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleChange = (item: DropdownItem | null) => {
|
|
93
|
+
console.log("Selection changed:", item);
|
|
94
|
+
};
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<template>
|
|
98
|
+
<Dropdown
|
|
99
|
+
:items="dropdownItems"
|
|
100
|
+
v-model="selectedItem"
|
|
101
|
+
@update:model-value="handleModelValueUpdate"
|
|
102
|
+
@change="handleChange"
|
|
103
|
+
/>
|
|
104
|
+
</template>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Slots
|
|
108
|
+
|
|
109
|
+
This component does not use slots. All content is controlled via props.
|
|
110
|
+
|
|
111
|
+
## Exposed Methods/Refs
|
|
112
|
+
|
|
113
|
+
This component does not expose any methods or refs via `defineExpose`.
|
|
114
|
+
|
|
115
|
+
## CSS Customization
|
|
116
|
+
|
|
117
|
+
The Dropdown component uses CSS custom properties that can be overridden to customize the appearance:
|
|
118
|
+
|
|
119
|
+
### Color Variables
|
|
120
|
+
|
|
121
|
+
```css
|
|
122
|
+
/* Dropdown popover colors */
|
|
123
|
+
--dropdown-popover-bg: #ffffff;
|
|
124
|
+
--dropdown-popover-border: 1px solid rgba(0, 0, 0, 0.1);
|
|
125
|
+
--dropdown-shadow-dark: transparent;
|
|
126
|
+
--dropdown-shadow-light: rgba(255, 255, 255, 0.7);
|
|
127
|
+
|
|
128
|
+
/* Dropdown item colors */
|
|
129
|
+
--dropdown-item-border: rgba(0, 0, 0, 0.08);
|
|
130
|
+
--dropdown-item-hover-bg: #f0f2f4;
|
|
131
|
+
--dropdown-item-focused-bg: rgba(0, 0, 0, 0.04);
|
|
132
|
+
--dropdown-item-selected-bg: rgba(0, 144, 255, 0.1);
|
|
133
|
+
--dropdown-item-selected-hover-bg: rgba(0, 144, 255, 0.15);
|
|
134
|
+
--dropdown-item-subtitle: #60646c;
|
|
135
|
+
|
|
136
|
+
/* Icon colors */
|
|
137
|
+
--dropdown-icon-color: #1a1d23;
|
|
138
|
+
--dropdown-icon-color-default: #1a1d23;
|
|
139
|
+
--dropdown-icon-color-secondary: #60646c;
|
|
140
|
+
|
|
141
|
+
/* Overlay color */
|
|
142
|
+
--dropdown-overlay-bg: rgba(0, 0, 0, 0.1);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Custom Styling Example
|
|
146
|
+
|
|
147
|
+
```vue
|
|
148
|
+
<template>
|
|
149
|
+
<Dropdown :items="items" v-model="selectedItem" class="custom-dropdown" />
|
|
150
|
+
</template>
|
|
151
|
+
|
|
152
|
+
<style>
|
|
153
|
+
.custom-dropdown {
|
|
154
|
+
--dropdown-item-selected-bg: #ff6b6b;
|
|
155
|
+
--dropdown-item-selected-hover-bg: #ff5252;
|
|
156
|
+
--dropdown-popover-bg: #f8f9fa;
|
|
157
|
+
}
|
|
158
|
+
</style>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Examples
|
|
162
|
+
|
|
163
|
+
### Basic Dropdown
|
|
164
|
+
|
|
165
|
+
```vue
|
|
166
|
+
<script setup lang="ts">
|
|
167
|
+
import { ref } from "vue";
|
|
168
|
+
import { Dropdown } from "@umbra-ui/core";
|
|
169
|
+
import type { DropdownItem } from "@umbra-ui/core";
|
|
170
|
+
|
|
171
|
+
const selectedItem = ref<DropdownItem | null>(null);
|
|
172
|
+
|
|
173
|
+
const items: DropdownItem[] = [
|
|
174
|
+
{ id: "apple", title: "Apple" },
|
|
175
|
+
{ id: "banana", title: "Banana" },
|
|
176
|
+
{ id: "cherry", title: "Cherry" },
|
|
177
|
+
{ id: "date", title: "Date" },
|
|
178
|
+
];
|
|
179
|
+
</script>
|
|
180
|
+
|
|
181
|
+
<template>
|
|
182
|
+
<Dropdown
|
|
183
|
+
:items="items"
|
|
184
|
+
v-model="selectedItem"
|
|
185
|
+
placeholder="Select a fruit"
|
|
186
|
+
/>
|
|
187
|
+
</template>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Dropdown with Subtitles
|
|
191
|
+
|
|
192
|
+
```vue
|
|
193
|
+
<script setup lang="ts">
|
|
194
|
+
import { ref } from "vue";
|
|
195
|
+
import { Dropdown } from "@umbra-ui/core";
|
|
196
|
+
import type { DropdownItem } from "@umbra-ui/core";
|
|
197
|
+
|
|
198
|
+
const selectedUser = ref<DropdownItem | null>(null);
|
|
199
|
+
|
|
200
|
+
const users: DropdownItem[] = [
|
|
201
|
+
{ id: "1", title: "John Doe", subtitle: "Software Engineer" },
|
|
202
|
+
{ id: "2", title: "Jane Smith", subtitle: "Product Manager" },
|
|
203
|
+
{ id: "3", title: "Bob Johnson", subtitle: "Designer" },
|
|
204
|
+
{ id: "4", title: "Alice Brown", subtitle: "Marketing Lead" },
|
|
205
|
+
];
|
|
206
|
+
</script>
|
|
207
|
+
|
|
208
|
+
<template>
|
|
209
|
+
<Dropdown
|
|
210
|
+
:items="users"
|
|
211
|
+
v-model="selectedUser"
|
|
212
|
+
placeholder="Select a team member"
|
|
213
|
+
/>
|
|
214
|
+
</template>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Dropdown with Disabled Items
|
|
218
|
+
|
|
219
|
+
```vue
|
|
220
|
+
<script setup lang="ts">
|
|
221
|
+
import { ref } from "vue";
|
|
222
|
+
import { Dropdown } from "@umbra-ui/core";
|
|
223
|
+
import type { DropdownItem } from "@umbra-ui/core";
|
|
224
|
+
|
|
225
|
+
const selectedPlan = ref<DropdownItem | null>(null);
|
|
226
|
+
|
|
227
|
+
const plans: DropdownItem[] = [
|
|
228
|
+
{ id: "free", title: "Free Plan", subtitle: "Basic features" },
|
|
229
|
+
{ id: "pro", title: "Pro Plan", subtitle: "Advanced features" },
|
|
230
|
+
{
|
|
231
|
+
id: "enterprise",
|
|
232
|
+
title: "Enterprise Plan",
|
|
233
|
+
subtitle: "All features",
|
|
234
|
+
disabled: true,
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
</script>
|
|
238
|
+
|
|
239
|
+
<template>
|
|
240
|
+
<Dropdown :items="plans" v-model="selectedPlan" placeholder="Choose a plan" />
|
|
241
|
+
</template>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Form Integration
|
|
245
|
+
|
|
246
|
+
```vue
|
|
247
|
+
<script setup lang="ts">
|
|
248
|
+
import { ref } from "vue";
|
|
249
|
+
import { Dropdown } from "@umbra-ui/core";
|
|
250
|
+
import type { DropdownItem } from "@umbra-ui/core";
|
|
251
|
+
|
|
252
|
+
interface FormData {
|
|
253
|
+
country: DropdownItem | null;
|
|
254
|
+
city: DropdownItem | null;
|
|
255
|
+
language: DropdownItem | null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const formData = ref<FormData>({
|
|
259
|
+
country: null,
|
|
260
|
+
city: null,
|
|
261
|
+
language: null,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const countries: DropdownItem[] = [
|
|
265
|
+
{ id: "us", title: "United States" },
|
|
266
|
+
{ id: "ca", title: "Canada" },
|
|
267
|
+
{ id: "uk", title: "United Kingdom" },
|
|
268
|
+
{ id: "de", title: "Germany" },
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
const cities: DropdownItem[] = [
|
|
272
|
+
{ id: "nyc", title: "New York" },
|
|
273
|
+
{ id: "la", title: "Los Angeles" },
|
|
274
|
+
{ id: "chicago", title: "Chicago" },
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
const languages: DropdownItem[] = [
|
|
278
|
+
{ id: "en", title: "English" },
|
|
279
|
+
{ id: "es", title: "Spanish" },
|
|
280
|
+
{ id: "fr", title: "French" },
|
|
281
|
+
{ id: "de", title: "German" },
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
const submitForm = () => {
|
|
285
|
+
if (!formData.value.country || !formData.value.language) {
|
|
286
|
+
alert("Please fill in all required fields");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
console.log("Form submitted:", formData.value);
|
|
291
|
+
};
|
|
292
|
+
</script>
|
|
293
|
+
|
|
294
|
+
<template>
|
|
295
|
+
<form class="form-example" @submit.prevent="submitForm">
|
|
296
|
+
<div class="form-group">
|
|
297
|
+
<label>Country *</label>
|
|
298
|
+
<Dropdown
|
|
299
|
+
:items="countries"
|
|
300
|
+
v-model="formData.country"
|
|
301
|
+
placeholder="Select country"
|
|
302
|
+
/>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
<div class="form-group">
|
|
306
|
+
<label>City</label>
|
|
307
|
+
<Dropdown
|
|
308
|
+
:items="cities"
|
|
309
|
+
v-model="formData.city"
|
|
310
|
+
placeholder="Select city"
|
|
311
|
+
/>
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
<div class="form-group">
|
|
315
|
+
<label>Language *</label>
|
|
316
|
+
<Dropdown
|
|
317
|
+
:items="languages"
|
|
318
|
+
v-model="formData.language"
|
|
319
|
+
placeholder="Select language"
|
|
320
|
+
/>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<button type="submit">Submit</button>
|
|
324
|
+
</form>
|
|
325
|
+
</template>
|
|
326
|
+
|
|
327
|
+
<style module>
|
|
328
|
+
.form-example {
|
|
329
|
+
display: flex;
|
|
330
|
+
flex-direction: column;
|
|
331
|
+
gap: 1rem;
|
|
332
|
+
max-width: 400px;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.form-group {
|
|
336
|
+
display: flex;
|
|
337
|
+
flex-direction: column;
|
|
338
|
+
gap: 0.5rem;
|
|
339
|
+
}
|
|
340
|
+
</style>
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Keyboard Navigation
|
|
344
|
+
|
|
345
|
+
The Dropdown component supports full keyboard navigation:
|
|
346
|
+
|
|
347
|
+
- **Enter/Space/Arrow Down**: Open dropdown when closed
|
|
348
|
+
- **Arrow Down**: Focus next item
|
|
349
|
+
- **Arrow Up**: Focus previous item
|
|
350
|
+
- **Enter**: Select focused item
|
|
351
|
+
- **Escape**: Close dropdown
|
|
352
|
+
|
|
353
|
+
## Accessibility Features
|
|
354
|
+
|
|
355
|
+
- Full ARIA support with proper roles (`combobox`, `listbox`, `option`)
|
|
356
|
+
- Keyboard navigation support
|
|
357
|
+
- Screen reader friendly
|
|
358
|
+
- Focus management
|
|
359
|
+
- Proper labeling and descriptions
|
|
360
|
+
|
|
361
|
+
## Notes
|
|
362
|
+
|
|
363
|
+
- The dropdown automatically positions itself above or below the button based on available space
|
|
364
|
+
- Global overlay system prevents multiple dropdowns from interfering with each other
|
|
365
|
+
- Smooth animations for opening/closing with proper transitions
|
|
366
|
+
- Responsive positioning that adjusts to window resize and scroll events
|
|
367
|
+
- Support for both light and dark themes
|
|
368
|
+
- The component uses a shared overlay system across all instances for better performance
|
|
369
|
+
- Items can be disabled by setting the `disabled` property to `true`
|
|
370
|
+
- The dropdown supports both simple titles and titles with subtitles
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* Light theme - semantic naming for dropdown properties */
|
|
2
|
+
:root {
|
|
3
|
+
/* Dropdown popover colors */
|
|
4
|
+
--dropdown-popover-bg: #ffffff;
|
|
5
|
+
--dropdown-popover-border: 1px solid rgba(0, 0, 0, 0.1);
|
|
6
|
+
--dropdown-shadow-dark: transparent;
|
|
7
|
+
--dropdown-shadow-light: rgba(255, 255, 255, 0.7);
|
|
8
|
+
|
|
9
|
+
/* Dropdown item colors */
|
|
10
|
+
--dropdown-item-border: rgba(0, 0, 0, 0.08);
|
|
11
|
+
--dropdown-item-hover-bg: #f0f2f4;
|
|
12
|
+
--dropdown-item-focused-bg: rgba(0, 0, 0, 0.04);
|
|
13
|
+
--dropdown-item-selected-bg: rgba(0, 144, 255, 0.1);
|
|
14
|
+
--dropdown-item-selected-hover-bg: rgba(0, 144, 255, 0.15);
|
|
15
|
+
--dropdown-item-subtitle: #60646c;
|
|
16
|
+
|
|
17
|
+
/* Icon colors - default and secondary styles */
|
|
18
|
+
--dropdown-icon-color: #1a1d23;
|
|
19
|
+
--dropdown-icon-color-default: #1a1d23;
|
|
20
|
+
--dropdown-icon-color-secondary: #60646c;
|
|
21
|
+
|
|
22
|
+
/* Overlay color */
|
|
23
|
+
--dropdown-overlay-bg: rgba(0, 0, 0, 0.1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Dark theme */
|
|
27
|
+
.dark,
|
|
28
|
+
.dark-theme {
|
|
29
|
+
/* Dropdown popover colors */
|
|
30
|
+
--dropdown-popover-bg: #484848;
|
|
31
|
+
--dropdown-popover-border: none;
|
|
32
|
+
--dropdown-shadow-dark: rgba(0, 0, 0, 0.21);
|
|
33
|
+
--dropdown-shadow-light: rgba(255, 255, 255, 0.1);
|
|
34
|
+
|
|
35
|
+
/* Dropdown item colors */
|
|
36
|
+
--dropdown-item-border: rgba(255, 255, 255, 0.15);
|
|
37
|
+
--dropdown-item-hover-bg: #222222;
|
|
38
|
+
--dropdown-item-focused-bg: rgba(255, 255, 255, 0.05);
|
|
39
|
+
--dropdown-item-selected-bg: rgba(255, 255, 255, 0.15);
|
|
40
|
+
--dropdown-item-selected-hover-bg: rgba(255, 255, 255, 0.25);
|
|
41
|
+
--dropdown-item-subtitle: #b4b4b4;
|
|
42
|
+
|
|
43
|
+
/* Icon colors - keeping default and secondary styles */
|
|
44
|
+
--dropdown-icon-color: #eeeeee;
|
|
45
|
+
--dropdown-icon-color-default: #eeeeee;
|
|
46
|
+
--dropdown-icon-color-secondary: #60646c;
|
|
47
|
+
|
|
48
|
+
/* Overlay color */
|
|
49
|
+
--dropdown-overlay-bg: rgba(0, 0, 0, 0.5);
|
|
50
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { CircleAnimOutlineIcon, icons, type IconKey } from "@umbra.ui/icons";
|
|
4
|
+
import type { TooltipConfig } from "../../indicators/Tooltip/types";
|
|
5
|
+
import "./theme.css";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
iconName?: IconKey;
|
|
9
|
+
buttonType?: "round" | "square" | "plain";
|
|
10
|
+
buttonStyle?: "primary" | "secondary" | "tertiary" | "quaternary";
|
|
11
|
+
state?: "normal" | "active" | "disabled";
|
|
12
|
+
buttonSize?: number;
|
|
13
|
+
tooltip?: string | TooltipConfig;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
17
|
+
iconName: "heart",
|
|
18
|
+
buttonType: "round",
|
|
19
|
+
buttonStyle: "primary",
|
|
20
|
+
state: "normal",
|
|
21
|
+
buttonSize: 16,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const emit = defineEmits(["update:state", "click"]);
|
|
25
|
+
|
|
26
|
+
const handleClick = (event: MouseEvent) => {
|
|
27
|
+
emit("click", event);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const buttonColor = computed(() => {
|
|
31
|
+
switch (props.buttonStyle) {
|
|
32
|
+
case "primary":
|
|
33
|
+
return "var(--iconbutton-primary-bg)";
|
|
34
|
+
case "secondary":
|
|
35
|
+
return "var(--iconbutton-secondary-bg)";
|
|
36
|
+
case "tertiary":
|
|
37
|
+
return "var(--iconbutton-tertiary-bg)";
|
|
38
|
+
case "quaternary":
|
|
39
|
+
return "var(--iconbutton-quaternary-bg)";
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const buttonHoverColor = computed(() => {
|
|
44
|
+
switch (props.buttonStyle) {
|
|
45
|
+
case "primary":
|
|
46
|
+
return "var(--iconbutton-primary-hover-bg)";
|
|
47
|
+
case "secondary":
|
|
48
|
+
return "var(--iconbutton-secondary-hover-bg)";
|
|
49
|
+
case "tertiary":
|
|
50
|
+
return "var(--iconbutton-tertiary-hover-bg)";
|
|
51
|
+
case "quaternary":
|
|
52
|
+
return "var(--iconbutton-quaternary-hover-bg)";
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const buttonColorWithOpacity = computed(() => {
|
|
57
|
+
return `color-mix(in srgb, var(--button-color) 20%, transparent)`;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const iconColor = computed(() => {
|
|
61
|
+
// All predefined styles use white
|
|
62
|
+
return "var(--iconbutton-icon-color)";
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const IconComponent = computed(() => {
|
|
66
|
+
if (!props.iconName) return null;
|
|
67
|
+
return icons[props.iconName as keyof typeof icons] || null;
|
|
68
|
+
});
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<template>
|
|
72
|
+
<div
|
|
73
|
+
v-tooltip="tooltip"
|
|
74
|
+
@click="handleClick"
|
|
75
|
+
:class="[
|
|
76
|
+
$style.container,
|
|
77
|
+
$style[buttonType!],
|
|
78
|
+
$style[buttonStyle!],
|
|
79
|
+
$style[state!],
|
|
80
|
+
]"
|
|
81
|
+
:style="{
|
|
82
|
+
'--button-color': buttonColor,
|
|
83
|
+
'--button-hover-color': buttonHoverColor,
|
|
84
|
+
'--button-color-opacity': buttonColorWithOpacity,
|
|
85
|
+
'--button-size': `${buttonSize}px`,
|
|
86
|
+
'--icon-color': iconColor,
|
|
87
|
+
}"
|
|
88
|
+
>
|
|
89
|
+
<component v-if="IconComponent" :is="IconComponent" :class="$style.icon" />
|
|
90
|
+
<div :class="$style.animated" v-if="state === 'active'">
|
|
91
|
+
<CircleAnimOutlineIcon />
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</template>
|
|
95
|
+
|
|
96
|
+
<style module>
|
|
97
|
+
.container {
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
justify-content: center;
|
|
101
|
+
background-color: var(--button-color);
|
|
102
|
+
padding: 0.5rem;
|
|
103
|
+
transition: 0.3s ease-in-out transform, 0.3s ease-in-out background-color,
|
|
104
|
+
0.3s ease-in-out box-shadow, 0.3s ease-in-out border, 0.3s ease-in-out color,
|
|
105
|
+
0.3s ease-in-out opacity;
|
|
106
|
+
will-change: transform;
|
|
107
|
+
width: fit-content;
|
|
108
|
+
height: fit-content;
|
|
109
|
+
cursor: pointer;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.container:hover {
|
|
113
|
+
transform: scale(1.05);
|
|
114
|
+
}
|
|
115
|
+
.container:active {
|
|
116
|
+
transform: scale(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.icon {
|
|
120
|
+
width: var(--button-size);
|
|
121
|
+
height: var(--button-size);
|
|
122
|
+
color: var(--icon-color);
|
|
123
|
+
}
|
|
124
|
+
.round {
|
|
125
|
+
border-radius: 50%;
|
|
126
|
+
}
|
|
127
|
+
.square {
|
|
128
|
+
border-radius: 0.353rem;
|
|
129
|
+
padding: 0.5rem;
|
|
130
|
+
}
|
|
131
|
+
.plain {
|
|
132
|
+
border-radius: 0;
|
|
133
|
+
padding: 0;
|
|
134
|
+
color: var(--button-color);
|
|
135
|
+
background-color: transparent;
|
|
136
|
+
}
|
|
137
|
+
.plain .icon {
|
|
138
|
+
color: var(--button-color);
|
|
139
|
+
}
|
|
140
|
+
.plain:hover {
|
|
141
|
+
transform: scale(1.15);
|
|
142
|
+
}
|
|
143
|
+
.plain:active {
|
|
144
|
+
transform: scale(1);
|
|
145
|
+
}
|
|
146
|
+
.plain.primary {
|
|
147
|
+
background-color: transparent;
|
|
148
|
+
color: var(--iconbutton-plain-primary);
|
|
149
|
+
box-shadow: none;
|
|
150
|
+
}
|
|
151
|
+
.plain.primary:hover {
|
|
152
|
+
background-color: transparent;
|
|
153
|
+
box-shadow: none;
|
|
154
|
+
}
|
|
155
|
+
.plain.secondary {
|
|
156
|
+
background-color: transparent;
|
|
157
|
+
color: var(--iconbutton-plain-secondary);
|
|
158
|
+
border: none;
|
|
159
|
+
}
|
|
160
|
+
.plain.secondary:hover {
|
|
161
|
+
background-color: transparent;
|
|
162
|
+
box-shadow: none;
|
|
163
|
+
border: none;
|
|
164
|
+
}
|
|
165
|
+
.plain.secondary .icon {
|
|
166
|
+
color: var(--iconbutton-plain-secondary);
|
|
167
|
+
}
|
|
168
|
+
.plain.tertiary .icon {
|
|
169
|
+
color: var(--iconbutton-plain-tertiary);
|
|
170
|
+
}
|
|
171
|
+
.plain.quaternary .icon {
|
|
172
|
+
color: var(--iconbutton-plain-quaternary);
|
|
173
|
+
}
|
|
174
|
+
.plain.tertiary {
|
|
175
|
+
background-color: transparent;
|
|
176
|
+
color: var(--iconbutton-plain-tertiary);
|
|
177
|
+
border: none;
|
|
178
|
+
box-shadow: none;
|
|
179
|
+
}
|
|
180
|
+
.plain.tertiary:hover {
|
|
181
|
+
background-color: transparent;
|
|
182
|
+
box-shadow: none;
|
|
183
|
+
border: none;
|
|
184
|
+
}
|
|
185
|
+
.plain.quaternary {
|
|
186
|
+
background-color: transparent;
|
|
187
|
+
color: var(--iconbutton-plain-quaternary);
|
|
188
|
+
border: none;
|
|
189
|
+
}
|
|
190
|
+
.plain.quaternary:hover {
|
|
191
|
+
background-color: transparent;
|
|
192
|
+
box-shadow: none;
|
|
193
|
+
border: none;
|
|
194
|
+
}
|
|
195
|
+
.primary {
|
|
196
|
+
background-color: var(--iconbutton-primary-bg);
|
|
197
|
+
box-shadow: inset 0 0 0 1px var(--iconbutton-primary-border);
|
|
198
|
+
color: var(--iconbutton-icon-primary-color);
|
|
199
|
+
}
|
|
200
|
+
.primary.round .icon {
|
|
201
|
+
color: var(--iconbutton-icon-primary-color);
|
|
202
|
+
}
|
|
203
|
+
.primary.square .icon {
|
|
204
|
+
color: var(--iconbutton-icon-primary-color);
|
|
205
|
+
}
|
|
206
|
+
.primary:hover {
|
|
207
|
+
background-color: var(--button-hover-color);
|
|
208
|
+
box-shadow: inset 0 0 0 1px var(--iconbutton-primary-hover-border);
|
|
209
|
+
}
|
|
210
|
+
.secondary {
|
|
211
|
+
background-color: var(--iconbutton-secondary-bg);
|
|
212
|
+
border: 1px solid var(--iconbutton-secondary-border);
|
|
213
|
+
}
|
|
214
|
+
.secondary:hover {
|
|
215
|
+
background-color: var(--button-hover-color);
|
|
216
|
+
border: 1px solid var(--iconbutton-secondary-hover-border);
|
|
217
|
+
}
|
|
218
|
+
.tertiary {
|
|
219
|
+
background-color: var(--iconbutton-tertiary-bg);
|
|
220
|
+
border: 1px solid var(--iconbutton-tertiary-border);
|
|
221
|
+
}
|
|
222
|
+
.tertiary:hover {
|
|
223
|
+
background-color: var(--iconbutton-tertiary-hover-bg);
|
|
224
|
+
border: 1px solid var(--iconbutton-tertiary-hover-border);
|
|
225
|
+
}
|
|
226
|
+
.quaternary {
|
|
227
|
+
background-color: var(--iconbutton-quaternary-bg);
|
|
228
|
+
border: 1px solid var(--iconbutton-quaternary-border);
|
|
229
|
+
}
|
|
230
|
+
.quaternary:hover {
|
|
231
|
+
background-color: var(--iconbutton-quaternary-hover-bg);
|
|
232
|
+
border: 1px solid var(--iconbutton-quaternary-hover-border);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.active {
|
|
236
|
+
opacity: var(--iconbutton-active-opacity);
|
|
237
|
+
cursor: pointer;
|
|
238
|
+
pointer-events: none;
|
|
239
|
+
}
|
|
240
|
+
.plain.active {
|
|
241
|
+
padding: 0.5rem;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.disabled {
|
|
245
|
+
opacity: var(--iconbutton-disabled-opacity);
|
|
246
|
+
cursor: not-allowed;
|
|
247
|
+
pointer-events: none;
|
|
248
|
+
filter: grayscale(100%);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.animated {
|
|
252
|
+
position: absolute;
|
|
253
|
+
top: 0;
|
|
254
|
+
left: 0;
|
|
255
|
+
bottom: 0;
|
|
256
|
+
right: 0;
|
|
257
|
+
border-radius: inherit;
|
|
258
|
+
display: flex;
|
|
259
|
+
align-items: center;
|
|
260
|
+
justify-content: center;
|
|
261
|
+
overflow: visible;
|
|
262
|
+
}
|
|
263
|
+
.animated svg {
|
|
264
|
+
width: 90%;
|
|
265
|
+
height: 90%;
|
|
266
|
+
}
|
|
267
|
+
</style>
|