@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,764 @@
|
|
|
1
|
+
# InputEmail
|
|
2
|
+
|
|
3
|
+
A specialized email input component built with Vue 3 Composition API and TypeScript. The InputEmail component provides intelligent email validation, domain suggestions, typo detection, and visual feedback with smooth animations and accessibility features.
|
|
4
|
+
|
|
5
|
+
## Installation/Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { InputEmail } from "@umbra-ui/core";
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Dependencies:**
|
|
12
|
+
|
|
13
|
+
- Vue 3.x
|
|
14
|
+
- @umbra-ui/icons (for envelope, warning, and check icons)
|
|
15
|
+
|
|
16
|
+
## Basic Usage
|
|
17
|
+
|
|
18
|
+
```vue
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { ref } from "vue";
|
|
21
|
+
import { InputEmail } from "@umbra-ui/core";
|
|
22
|
+
|
|
23
|
+
const email = ref("");
|
|
24
|
+
const isValid = ref(false);
|
|
25
|
+
const suggestion = ref("");
|
|
26
|
+
|
|
27
|
+
const handleEmailUpdate = (value: string) => {
|
|
28
|
+
console.log("Email:", value);
|
|
29
|
+
email.value = value;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const handleValidUpdate = (valid: boolean) => {
|
|
33
|
+
console.log("Is valid:", valid);
|
|
34
|
+
isValid.value = valid;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleSuggestionUpdate = (suggestedEmail: string) => {
|
|
38
|
+
console.log("Suggestion selected:", suggestedEmail);
|
|
39
|
+
suggestion.value = suggestedEmail;
|
|
40
|
+
};
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<template>
|
|
44
|
+
<div>
|
|
45
|
+
<InputEmail
|
|
46
|
+
v-model:value="email"
|
|
47
|
+
placeholder="name@example.com"
|
|
48
|
+
:showSuggestions="true"
|
|
49
|
+
:validateOnType="true"
|
|
50
|
+
@update:value="handleEmailUpdate"
|
|
51
|
+
@update:valid="handleValidUpdate"
|
|
52
|
+
@update:suggestion="handleSuggestionUpdate"
|
|
53
|
+
/>
|
|
54
|
+
|
|
55
|
+
<div v-if="suggestion" class="suggestion-info">
|
|
56
|
+
<p>Selected suggestion: {{ suggestion }}</p>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Props
|
|
63
|
+
|
|
64
|
+
| Prop Name | Type | Required | Default | Description |
|
|
65
|
+
| ----------------- | ------------------------------------------------------------- | -------- | -------------------- | ----------------------------------------------- |
|
|
66
|
+
| `value` | `string` | No | `""` | The email address value |
|
|
67
|
+
| `placeholder` | `string` | No | `"name@example.com"` | Placeholder text displayed when empty |
|
|
68
|
+
| `showSuggestions` | `boolean` | No | `true` | Whether to show email domain suggestions |
|
|
69
|
+
| `validateOnType` | `boolean` | No | `true` | Whether to validate email while typing |
|
|
70
|
+
| `allowSubdomains` | `boolean` | No | `true` | Whether to allow subdomains in email validation |
|
|
71
|
+
| `state` | `"normal" \| "active" \| "disabled" \| "readonly" \| "error"` | No | `"normal"` | Current state of the input field |
|
|
72
|
+
|
|
73
|
+
## Events
|
|
74
|
+
|
|
75
|
+
| Event Name | Payload Type | Description |
|
|
76
|
+
| ------------------- | ------------ | ------------------------------------- |
|
|
77
|
+
| `update:value` | `string` | Emitted when the email value changes |
|
|
78
|
+
| `update:valid` | `boolean` | Emitted when validation state changes |
|
|
79
|
+
| `update:suggestion` | `string` | Emitted when a suggestion is selected |
|
|
80
|
+
|
|
81
|
+
## Slots
|
|
82
|
+
|
|
83
|
+
This component does not use slots.
|
|
84
|
+
|
|
85
|
+
## Exposed Methods/Refs
|
|
86
|
+
|
|
87
|
+
This component does not expose any methods or refs.
|
|
88
|
+
|
|
89
|
+
## Features
|
|
90
|
+
|
|
91
|
+
### Email Validation
|
|
92
|
+
|
|
93
|
+
- **RFC 5322 Compliant**: Follows email address specification standards
|
|
94
|
+
- **Domain Validation**: Validates domain structure and TLD requirements
|
|
95
|
+
- **Length Limits**: Enforces local part (64 chars) and domain (253 chars) limits
|
|
96
|
+
- **Character Restrictions**: Prevents invalid characters and consecutive dots
|
|
97
|
+
- **TLD Validation**: Ensures top-level domain is at least 2 characters and letters only
|
|
98
|
+
|
|
99
|
+
### Smart Suggestions
|
|
100
|
+
|
|
101
|
+
- **Common Domains**: Suggests popular email providers (Gmail, Yahoo, Outlook, etc.)
|
|
102
|
+
- **Typo Detection**: Detects and suggests corrections for common domain typos
|
|
103
|
+
- **Keyboard Navigation**: Full keyboard support for suggestion selection
|
|
104
|
+
- **Real-time Filtering**: Filters suggestions based on partial domain input
|
|
105
|
+
|
|
106
|
+
### Typo Detection
|
|
107
|
+
|
|
108
|
+
The component detects common typos in popular email domains:
|
|
109
|
+
|
|
110
|
+
- `gmial.com` → `gmail.com`
|
|
111
|
+
- `yahooo.com` → `yahoo.com`
|
|
112
|
+
- `hotmial.com` → `hotmail.com`
|
|
113
|
+
- `outloook.com` → `outlook.com`
|
|
114
|
+
|
|
115
|
+
## CSS Customization
|
|
116
|
+
|
|
117
|
+
The InputEmail component uses CSS custom properties for theming. It inherits from the general input theme:
|
|
118
|
+
|
|
119
|
+
```css
|
|
120
|
+
/* Input colors */
|
|
121
|
+
--input-background-normal: #ffffff;
|
|
122
|
+
--input-background-filled: #f8f9fa;
|
|
123
|
+
--input-text-empty: #6c757d;
|
|
124
|
+
--input-text-filled: #212529;
|
|
125
|
+
--input-border: #ced4da;
|
|
126
|
+
--input-border-filled: #80bdff;
|
|
127
|
+
--input-focus-border: #007bff;
|
|
128
|
+
--input-placeholder: #6c757d;
|
|
129
|
+
--input-placeholder-filled: #adb5bd;
|
|
130
|
+
|
|
131
|
+
/* State-specific colors */
|
|
132
|
+
--input-disabled-bg: #e9ecef;
|
|
133
|
+
--input-disabled-text: #6c757d;
|
|
134
|
+
--input-disabled-border: #ced4da;
|
|
135
|
+
--input-disabled-placeholder: #adb5bd;
|
|
136
|
+
|
|
137
|
+
--input-readonly-bg: #f8f9fa;
|
|
138
|
+
--input-readonly-text: #495057;
|
|
139
|
+
--input-readonly-border: #ced4da;
|
|
140
|
+
--input-readonly-placeholder: #6c757d;
|
|
141
|
+
|
|
142
|
+
--input-error-bg: #f8d7da;
|
|
143
|
+
--input-error-text: #721c24;
|
|
144
|
+
--input-error-border: #dc3545;
|
|
145
|
+
--input-text-error: #dc3545;
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Examples
|
|
149
|
+
|
|
150
|
+
### Basic Email Input
|
|
151
|
+
|
|
152
|
+
```vue
|
|
153
|
+
<script setup lang="ts">
|
|
154
|
+
import { ref } from "vue";
|
|
155
|
+
import { InputEmail } from "@umbra-ui/core";
|
|
156
|
+
|
|
157
|
+
const email = ref("");
|
|
158
|
+
</script>
|
|
159
|
+
|
|
160
|
+
<template>
|
|
161
|
+
<div class="email-form">
|
|
162
|
+
<h3>Contact Information</h3>
|
|
163
|
+
|
|
164
|
+
<div class="form-group">
|
|
165
|
+
<label>Email Address</label>
|
|
166
|
+
<InputEmail
|
|
167
|
+
v-model:value="email"
|
|
168
|
+
placeholder="your.email@example.com"
|
|
169
|
+
:showSuggestions="true"
|
|
170
|
+
/>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</template>
|
|
174
|
+
|
|
175
|
+
<style module>
|
|
176
|
+
.email-form {
|
|
177
|
+
max-width: 400px;
|
|
178
|
+
padding: 1.5rem;
|
|
179
|
+
border: 1px solid #e0e0e0;
|
|
180
|
+
border-radius: 8px;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.form-group {
|
|
184
|
+
margin-bottom: 1rem;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
label {
|
|
188
|
+
display: block;
|
|
189
|
+
margin-bottom: 0.5rem;
|
|
190
|
+
font-weight: 500;
|
|
191
|
+
color: #333;
|
|
192
|
+
}
|
|
193
|
+
</style>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Registration Form
|
|
197
|
+
|
|
198
|
+
```vue
|
|
199
|
+
<script setup lang="ts">
|
|
200
|
+
import { ref, computed } from "vue";
|
|
201
|
+
import { InputEmail } from "@umbra-ui/core";
|
|
202
|
+
|
|
203
|
+
const formData = ref({
|
|
204
|
+
firstName: "",
|
|
205
|
+
lastName: "",
|
|
206
|
+
email: "",
|
|
207
|
+
emailValid: false,
|
|
208
|
+
password: "",
|
|
209
|
+
confirmPassword: "",
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const canSubmit = computed(() => {
|
|
213
|
+
return (
|
|
214
|
+
formData.value.firstName &&
|
|
215
|
+
formData.value.lastName &&
|
|
216
|
+
formData.value.email &&
|
|
217
|
+
formData.value.emailValid &&
|
|
218
|
+
formData.value.password &&
|
|
219
|
+
formData.value.confirmPassword &&
|
|
220
|
+
formData.value.password === formData.value.confirmPassword
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const handleSubmit = () => {
|
|
225
|
+
if (canSubmit.value) {
|
|
226
|
+
console.log("Registration data:", formData.value);
|
|
227
|
+
// Process registration logic here
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
</script>
|
|
231
|
+
|
|
232
|
+
<template>
|
|
233
|
+
<div class="registration-form">
|
|
234
|
+
<h3>Create Account</h3>
|
|
235
|
+
|
|
236
|
+
<form @submit.prevent="handleSubmit">
|
|
237
|
+
<div class="form-row">
|
|
238
|
+
<div class="form-group">
|
|
239
|
+
<label>First Name</label>
|
|
240
|
+
<input
|
|
241
|
+
v-model="formData.firstName"
|
|
242
|
+
type="text"
|
|
243
|
+
placeholder="John"
|
|
244
|
+
required
|
|
245
|
+
/>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
<div class="form-group">
|
|
249
|
+
<label>Last Name</label>
|
|
250
|
+
<input
|
|
251
|
+
v-model="formData.lastName"
|
|
252
|
+
type="text"
|
|
253
|
+
placeholder="Doe"
|
|
254
|
+
required
|
|
255
|
+
/>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
<div class="form-group">
|
|
260
|
+
<label>Email Address</label>
|
|
261
|
+
<InputEmail
|
|
262
|
+
v-model:value="formData.email"
|
|
263
|
+
@update:valid="formData.emailValid = $event"
|
|
264
|
+
placeholder="john.doe@example.com"
|
|
265
|
+
:showSuggestions="true"
|
|
266
|
+
:validateOnType="true"
|
|
267
|
+
/>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
<div class="form-group">
|
|
271
|
+
<label>Password</label>
|
|
272
|
+
<input
|
|
273
|
+
v-model="formData.password"
|
|
274
|
+
type="password"
|
|
275
|
+
placeholder="Enter your password"
|
|
276
|
+
required
|
|
277
|
+
/>
|
|
278
|
+
</div>
|
|
279
|
+
|
|
280
|
+
<div class="form-group">
|
|
281
|
+
<label>Confirm Password</label>
|
|
282
|
+
<input
|
|
283
|
+
v-model="formData.confirmPassword"
|
|
284
|
+
type="password"
|
|
285
|
+
placeholder="Confirm your password"
|
|
286
|
+
required
|
|
287
|
+
/>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
<button type="submit" :disabled="!canSubmit">Create Account</button>
|
|
291
|
+
</form>
|
|
292
|
+
</div>
|
|
293
|
+
</template>
|
|
294
|
+
|
|
295
|
+
<style module>
|
|
296
|
+
.registration-form {
|
|
297
|
+
max-width: 500px;
|
|
298
|
+
padding: 1.5rem;
|
|
299
|
+
border: 1px solid #e0e0e0;
|
|
300
|
+
border-radius: 8px;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.form-row {
|
|
304
|
+
display: grid;
|
|
305
|
+
grid-template-columns: 1fr 1fr;
|
|
306
|
+
gap: 1rem;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.form-group {
|
|
310
|
+
margin-bottom: 1rem;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
label {
|
|
314
|
+
display: block;
|
|
315
|
+
margin-bottom: 0.5rem;
|
|
316
|
+
font-weight: 500;
|
|
317
|
+
color: #333;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
input {
|
|
321
|
+
width: 100%;
|
|
322
|
+
padding: 0.5rem;
|
|
323
|
+
border: 1px solid #ddd;
|
|
324
|
+
border-radius: 4px;
|
|
325
|
+
font-size: 1rem;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
button {
|
|
329
|
+
width: 100%;
|
|
330
|
+
padding: 0.75rem;
|
|
331
|
+
background-color: #007bff;
|
|
332
|
+
color: white;
|
|
333
|
+
border: none;
|
|
334
|
+
border-radius: 4px;
|
|
335
|
+
cursor: pointer;
|
|
336
|
+
font-size: 1rem;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
button:disabled {
|
|
340
|
+
background-color: #ccc;
|
|
341
|
+
cursor: not-allowed;
|
|
342
|
+
}
|
|
343
|
+
</style>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Different States
|
|
347
|
+
|
|
348
|
+
```vue
|
|
349
|
+
<script setup lang="ts">
|
|
350
|
+
import { ref } from "vue";
|
|
351
|
+
import { InputEmail } from "@umbra-ui/core";
|
|
352
|
+
|
|
353
|
+
const normalEmail = ref("");
|
|
354
|
+
const disabledEmail = ref("user@example.com");
|
|
355
|
+
const readonlyEmail = ref("admin@company.com");
|
|
356
|
+
const errorEmail = ref("invalid-email");
|
|
357
|
+
</script>
|
|
358
|
+
|
|
359
|
+
<template>
|
|
360
|
+
<div class="states-example">
|
|
361
|
+
<h3>Email Input States</h3>
|
|
362
|
+
|
|
363
|
+
<div class="state-group">
|
|
364
|
+
<label>Normal State</label>
|
|
365
|
+
<InputEmail
|
|
366
|
+
v-model:value="normalEmail"
|
|
367
|
+
placeholder="Enter your email"
|
|
368
|
+
state="normal"
|
|
369
|
+
/>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
<div class="state-group">
|
|
373
|
+
<label>Disabled State</label>
|
|
374
|
+
<InputEmail v-model:value="disabledEmail" state="disabled" />
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
<div class="state-group">
|
|
378
|
+
<label>Readonly State</label>
|
|
379
|
+
<InputEmail v-model:value="readonlyEmail" state="readonly" />
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
<div class="state-group">
|
|
383
|
+
<label>Error State (Invalid Email)</label>
|
|
384
|
+
<InputEmail v-model:value="errorEmail" state="normal" />
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
</template>
|
|
388
|
+
|
|
389
|
+
<style module>
|
|
390
|
+
.states-example {
|
|
391
|
+
max-width: 500px;
|
|
392
|
+
padding: 1.5rem;
|
|
393
|
+
border: 1px solid #e0e0e0;
|
|
394
|
+
border-radius: 8px;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.state-group {
|
|
398
|
+
margin-bottom: 1.5rem;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
label {
|
|
402
|
+
display: block;
|
|
403
|
+
margin-bottom: 0.5rem;
|
|
404
|
+
font-weight: 500;
|
|
405
|
+
color: #333;
|
|
406
|
+
}
|
|
407
|
+
</style>
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Suggestion Features Demo
|
|
411
|
+
|
|
412
|
+
```vue
|
|
413
|
+
<script setup lang="ts">
|
|
414
|
+
import { ref } from "vue";
|
|
415
|
+
import { InputEmail } from "@umbra-ui/core";
|
|
416
|
+
|
|
417
|
+
const email = ref("");
|
|
418
|
+
const selectedSuggestion = ref("");
|
|
419
|
+
|
|
420
|
+
const testEmails = ref([
|
|
421
|
+
"user@gmial.com", // Typo: should suggest gmail.com
|
|
422
|
+
"test@yahooo.com", // Typo: should suggest yahoo.com
|
|
423
|
+
"admin@hotmial.com", // Typo: should suggest hotmail.com
|
|
424
|
+
"contact@outloook.com", // Typo: should suggest outlook.com
|
|
425
|
+
]);
|
|
426
|
+
|
|
427
|
+
const testEmail = (testEmail: string) => {
|
|
428
|
+
email.value = testEmail;
|
|
429
|
+
};
|
|
430
|
+
</script>
|
|
431
|
+
|
|
432
|
+
<template>
|
|
433
|
+
<div class="suggestions-demo">
|
|
434
|
+
<h3>Email Suggestions & Typo Detection</h3>
|
|
435
|
+
|
|
436
|
+
<div class="demo-input">
|
|
437
|
+
<InputEmail
|
|
438
|
+
v-model:value="email"
|
|
439
|
+
@update:suggestion="selectedSuggestion = $event"
|
|
440
|
+
placeholder="Try typing with typos..."
|
|
441
|
+
:showSuggestions="true"
|
|
442
|
+
:validateOnType="true"
|
|
443
|
+
/>
|
|
444
|
+
|
|
445
|
+
<div v-if="selectedSuggestion" class="suggestion-info">
|
|
446
|
+
<p><strong>Selected Suggestion:</strong> {{ selectedSuggestion }}</p>
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
|
|
450
|
+
<div class="test-emails">
|
|
451
|
+
<h4>Test Emails with Common Typos</h4>
|
|
452
|
+
<div class="test-buttons">
|
|
453
|
+
<button
|
|
454
|
+
v-for="testEmail in testEmails"
|
|
455
|
+
:key="testEmail"
|
|
456
|
+
@click="testEmail(testEmail)"
|
|
457
|
+
class="test-button"
|
|
458
|
+
>
|
|
459
|
+
{{ testEmail }}
|
|
460
|
+
</button>
|
|
461
|
+
</div>
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
</template>
|
|
465
|
+
|
|
466
|
+
<style module>
|
|
467
|
+
.suggestions-demo {
|
|
468
|
+
max-width: 600px;
|
|
469
|
+
padding: 1.5rem;
|
|
470
|
+
border: 1px solid #e0e0e0;
|
|
471
|
+
border-radius: 8px;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.demo-input {
|
|
475
|
+
margin-bottom: 2rem;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.suggestion-info {
|
|
479
|
+
margin-top: 1rem;
|
|
480
|
+
padding: 1rem;
|
|
481
|
+
background-color: #d4edda;
|
|
482
|
+
border-radius: 4px;
|
|
483
|
+
color: #155724;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.test-emails h4 {
|
|
487
|
+
margin-bottom: 1rem;
|
|
488
|
+
color: #333;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.test-buttons {
|
|
492
|
+
display: grid;
|
|
493
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
494
|
+
gap: 0.5rem;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.test-button {
|
|
498
|
+
padding: 0.5rem;
|
|
499
|
+
background-color: #6c757d;
|
|
500
|
+
color: white;
|
|
501
|
+
border: none;
|
|
502
|
+
border-radius: 4px;
|
|
503
|
+
cursor: pointer;
|
|
504
|
+
font-size: 0.9rem;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.test-button:hover {
|
|
508
|
+
background-color: #5a6268;
|
|
509
|
+
}
|
|
510
|
+
</style>
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Newsletter Signup
|
|
514
|
+
|
|
515
|
+
```vue
|
|
516
|
+
<script setup lang="ts">
|
|
517
|
+
import { ref, computed } from "vue";
|
|
518
|
+
import { InputEmail } from "@umbra-ui/core";
|
|
519
|
+
|
|
520
|
+
const email = ref("");
|
|
521
|
+
const isValid = ref(false);
|
|
522
|
+
const isSubscribed = ref(false);
|
|
523
|
+
|
|
524
|
+
const canSubscribe = computed(() => {
|
|
525
|
+
return email.value && isValid.value;
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
const handleSubscribe = () => {
|
|
529
|
+
if (canSubscribe.value) {
|
|
530
|
+
console.log("Subscribing:", email.value);
|
|
531
|
+
isSubscribed.value = true;
|
|
532
|
+
email.value = "";
|
|
533
|
+
isValid.value = false;
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
</script>
|
|
537
|
+
|
|
538
|
+
<template>
|
|
539
|
+
<div class="newsletter-signup">
|
|
540
|
+
<h3>Stay Updated</h3>
|
|
541
|
+
<p>Subscribe to our newsletter for the latest updates and news.</p>
|
|
542
|
+
|
|
543
|
+
<div v-if="!isSubscribed" class="signup-form">
|
|
544
|
+
<InputEmail
|
|
545
|
+
v-model:value="email"
|
|
546
|
+
@update:valid="isValid = $event"
|
|
547
|
+
placeholder="your.email@example.com"
|
|
548
|
+
:showSuggestions="true"
|
|
549
|
+
:validateOnType="true"
|
|
550
|
+
/>
|
|
551
|
+
|
|
552
|
+
<button
|
|
553
|
+
@click="handleSubscribe"
|
|
554
|
+
:disabled="!canSubscribe"
|
|
555
|
+
class="subscribe-button"
|
|
556
|
+
>
|
|
557
|
+
Subscribe
|
|
558
|
+
</button>
|
|
559
|
+
</div>
|
|
560
|
+
|
|
561
|
+
<div v-else class="success-message">
|
|
562
|
+
<p>✅ Thank you for subscribing! Check your email for confirmation.</p>
|
|
563
|
+
</div>
|
|
564
|
+
</div>
|
|
565
|
+
</template>
|
|
566
|
+
|
|
567
|
+
<style module>
|
|
568
|
+
.newsletter-signup {
|
|
569
|
+
max-width: 400px;
|
|
570
|
+
padding: 1.5rem;
|
|
571
|
+
border: 1px solid #e0e0e0;
|
|
572
|
+
border-radius: 8px;
|
|
573
|
+
text-align: center;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.newsletter-signup h3 {
|
|
577
|
+
margin-bottom: 0.5rem;
|
|
578
|
+
color: #333;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.newsletter-signup p {
|
|
582
|
+
margin-bottom: 1.5rem;
|
|
583
|
+
color: #666;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.signup-form {
|
|
587
|
+
display: flex;
|
|
588
|
+
flex-direction: column;
|
|
589
|
+
gap: 1rem;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.subscribe-button {
|
|
593
|
+
padding: 0.75rem;
|
|
594
|
+
background-color: #28a745;
|
|
595
|
+
color: white;
|
|
596
|
+
border: none;
|
|
597
|
+
border-radius: 4px;
|
|
598
|
+
cursor: pointer;
|
|
599
|
+
font-size: 1rem;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
.subscribe-button:disabled {
|
|
603
|
+
background-color: #ccc;
|
|
604
|
+
cursor: not-allowed;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.success-message {
|
|
608
|
+
padding: 1rem;
|
|
609
|
+
background-color: #d4edda;
|
|
610
|
+
border-radius: 4px;
|
|
611
|
+
color: #155724;
|
|
612
|
+
}
|
|
613
|
+
</style>
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
### Contact Form
|
|
617
|
+
|
|
618
|
+
```vue
|
|
619
|
+
<script setup lang="ts">
|
|
620
|
+
import { ref, computed } from "vue";
|
|
621
|
+
import { InputEmail } from "@umbra-ui/core";
|
|
622
|
+
|
|
623
|
+
const contactData = ref({
|
|
624
|
+
name: "",
|
|
625
|
+
email: "",
|
|
626
|
+
emailValid: false,
|
|
627
|
+
subject: "",
|
|
628
|
+
message: "",
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
const canSubmit = computed(() => {
|
|
632
|
+
return (
|
|
633
|
+
contactData.value.name &&
|
|
634
|
+
contactData.value.email &&
|
|
635
|
+
contactData.value.emailValid &&
|
|
636
|
+
contactData.value.subject &&
|
|
637
|
+
contactData.value.message
|
|
638
|
+
);
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
const handleSubmit = () => {
|
|
642
|
+
if (canSubmit.value) {
|
|
643
|
+
console.log("Contact form submitted:", contactData.value);
|
|
644
|
+
// Process contact form logic here
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
</script>
|
|
648
|
+
|
|
649
|
+
<template>
|
|
650
|
+
<div class="contact-form">
|
|
651
|
+
<h3>Contact Us</h3>
|
|
652
|
+
|
|
653
|
+
<form @submit.prevent="handleSubmit">
|
|
654
|
+
<div class="form-group">
|
|
655
|
+
<label>Name</label>
|
|
656
|
+
<input
|
|
657
|
+
v-model="contactData.name"
|
|
658
|
+
type="text"
|
|
659
|
+
placeholder="Your full name"
|
|
660
|
+
required
|
|
661
|
+
/>
|
|
662
|
+
</div>
|
|
663
|
+
|
|
664
|
+
<div class="form-group">
|
|
665
|
+
<label>Email</label>
|
|
666
|
+
<InputEmail
|
|
667
|
+
v-model:value="contactData.email"
|
|
668
|
+
@update:valid="contactData.emailValid = $event"
|
|
669
|
+
placeholder="your.email@example.com"
|
|
670
|
+
:showSuggestions="true"
|
|
671
|
+
/>
|
|
672
|
+
</div>
|
|
673
|
+
|
|
674
|
+
<div class="form-group">
|
|
675
|
+
<label>Subject</label>
|
|
676
|
+
<input
|
|
677
|
+
v-model="contactData.subject"
|
|
678
|
+
type="text"
|
|
679
|
+
placeholder="What's this about?"
|
|
680
|
+
required
|
|
681
|
+
/>
|
|
682
|
+
</div>
|
|
683
|
+
|
|
684
|
+
<div class="form-group">
|
|
685
|
+
<label>Message</label>
|
|
686
|
+
<textarea
|
|
687
|
+
v-model="contactData.message"
|
|
688
|
+
placeholder="Tell us more..."
|
|
689
|
+
rows="5"
|
|
690
|
+
required
|
|
691
|
+
></textarea>
|
|
692
|
+
</div>
|
|
693
|
+
|
|
694
|
+
<button type="submit" :disabled="!canSubmit">Send Message</button>
|
|
695
|
+
</form>
|
|
696
|
+
</div>
|
|
697
|
+
</template>
|
|
698
|
+
|
|
699
|
+
<style module>
|
|
700
|
+
.contact-form {
|
|
701
|
+
max-width: 500px;
|
|
702
|
+
padding: 1.5rem;
|
|
703
|
+
border: 1px solid #e0e0e0;
|
|
704
|
+
border-radius: 8px;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.form-group {
|
|
708
|
+
margin-bottom: 1rem;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
label {
|
|
712
|
+
display: block;
|
|
713
|
+
margin-bottom: 0.5rem;
|
|
714
|
+
font-weight: 500;
|
|
715
|
+
color: #333;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
input,
|
|
719
|
+
textarea {
|
|
720
|
+
width: 100%;
|
|
721
|
+
padding: 0.5rem;
|
|
722
|
+
border: 1px solid #ddd;
|
|
723
|
+
border-radius: 4px;
|
|
724
|
+
font-size: 1rem;
|
|
725
|
+
font-family: inherit;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
textarea {
|
|
729
|
+
resize: vertical;
|
|
730
|
+
min-height: 120px;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
button {
|
|
734
|
+
width: 100%;
|
|
735
|
+
padding: 0.75rem;
|
|
736
|
+
background-color: #007bff;
|
|
737
|
+
color: white;
|
|
738
|
+
border: none;
|
|
739
|
+
border-radius: 4px;
|
|
740
|
+
cursor: pointer;
|
|
741
|
+
font-size: 1rem;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
button:disabled {
|
|
745
|
+
background-color: #ccc;
|
|
746
|
+
cursor: not-allowed;
|
|
747
|
+
}
|
|
748
|
+
</style>
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
## Notes
|
|
752
|
+
|
|
753
|
+
- The component automatically converts input to lowercase for consistency
|
|
754
|
+
- Email validation follows RFC 5322 standards with additional domain structure checks
|
|
755
|
+
- Suggestions appear when the user types "@" and include common email providers
|
|
756
|
+
- Typo detection works for popular email domains with automatic correction suggestions
|
|
757
|
+
- The component includes smooth animations for suggestions dropdown and validation states
|
|
758
|
+
- Keyboard navigation is fully supported (Arrow keys, Enter, Escape)
|
|
759
|
+
- Paste operations automatically extract email addresses from common formats
|
|
760
|
+
- The component prevents multiple "@" symbols and removes spaces automatically
|
|
761
|
+
- Visual feedback includes envelope icon, validation checkmark, and error warning
|
|
762
|
+
- The component is fully accessible with proper ARIA attributes and keyboard navigation
|
|
763
|
+
- Input is optimized for mobile devices with proper input modes and font sizes
|
|
764
|
+
- Validation can be configured to run on every keystroke or only when appropriate
|