@veritree/ui 0.94.2 → 0.95.1
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/mixins/floating-ui-item.js +1 -1
- package/nuxt.js +1 -0
- package/package.json +1 -1
- package/src/components/Listbox/VTListboxItem.vue +1 -5
- package/src/components/Toast/README.md +263 -0
- package/src/components/Toast/VTToast.vue +150 -0
- package/src/components/Toast/VTToastAction.vue +25 -0
- package/src/components/Toast/VTToastClose.vue +52 -0
- package/src/components/Toast/VTToastContent.vue +25 -0
- package/src/components/Toast/VTToastDescription.vue +36 -0
- package/src/components/Toast/VTToastIcon.vue +72 -0
- package/src/components/Toast/VTToastItem.vue +180 -0
- package/src/components/Toast/VTToastTitle.vue +34 -0
- package/test/toast.test.js +575 -0
package/nuxt.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# Toast Component
|
|
2
|
+
|
|
3
|
+
A toast notification system using native HTML `<dialog>` elements with CSS transitions, supporting 4 corner positions, auto-dismiss, vertical stacking, and composable child components.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ 4 corner positions (top-left, top-right, bottom-left, bottom-right)
|
|
8
|
+
- ✅ Auto-dismiss with configurable duration (default 5s)
|
|
9
|
+
- ✅ Pause on hover
|
|
10
|
+
- ✅ Vertical stacking of multiple toasts
|
|
11
|
+
- ✅ Variant support (default, success, error, warning)
|
|
12
|
+
- ✅ HTML `<dialog>` element with `@starting-style` animations
|
|
13
|
+
- ✅ Auto-show when first item added, auto-hide when empty
|
|
14
|
+
- ✅ Composable child components
|
|
15
|
+
- ✅ Headless mode support
|
|
16
|
+
- ✅ Accessible (ARIA attributes)
|
|
17
|
+
|
|
18
|
+
## Components
|
|
19
|
+
|
|
20
|
+
- `VTToast` - Container component (renders `<dialog>`)
|
|
21
|
+
- `VTToastItem` - Individual toast wrapper
|
|
22
|
+
- `VTToastIcon` - Icon column with variant-based default icons
|
|
23
|
+
- `VTToastContent` - Content wrapper for Title and Description
|
|
24
|
+
- `VTToastTitle` - Title text
|
|
25
|
+
- `VTToastDescription` - Description text
|
|
26
|
+
- `VTToastAction` - Action buttons area
|
|
27
|
+
- `VTToastClose` - Close button
|
|
28
|
+
|
|
29
|
+
## Basic Usage
|
|
30
|
+
|
|
31
|
+
### Simple Toast
|
|
32
|
+
|
|
33
|
+
```vue
|
|
34
|
+
<template>
|
|
35
|
+
<VTToast position="top-right">
|
|
36
|
+
<VTToastItem variant="success">
|
|
37
|
+
<VTToastIcon />
|
|
38
|
+
<VTToastContent>
|
|
39
|
+
<VTToastTitle>Success!</VTToastTitle>
|
|
40
|
+
<VTToastDescription>Your changes have been saved.</VTToastDescription>
|
|
41
|
+
</VTToastContent>
|
|
42
|
+
<VTToastClose />
|
|
43
|
+
</VTToastItem>
|
|
44
|
+
</VTToast>
|
|
45
|
+
</template>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### With Action Button
|
|
49
|
+
|
|
50
|
+
```vue
|
|
51
|
+
<template>
|
|
52
|
+
<VTToast position="bottom-right">
|
|
53
|
+
<VTToastItem variant="warning" :duration="10000">
|
|
54
|
+
<VTToastIcon />
|
|
55
|
+
<VTToastContent>
|
|
56
|
+
<VTToastTitle>Session Expiring</VTToastTitle>
|
|
57
|
+
<VTToastDescription
|
|
58
|
+
>Your session will expire in 5 minutes.</VTToastDescription
|
|
59
|
+
>
|
|
60
|
+
</VTToastContent>
|
|
61
|
+
<VTToastAction>
|
|
62
|
+
<VTButton size="small" variant="primary" @click="extendSession">
|
|
63
|
+
Extend
|
|
64
|
+
</VTButton>
|
|
65
|
+
</VTToastAction>
|
|
66
|
+
<VTToastClose />
|
|
67
|
+
</VTToastItem>
|
|
68
|
+
</VTToast>
|
|
69
|
+
</template>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Multiple Toasts (Stacked)
|
|
73
|
+
|
|
74
|
+
```vue
|
|
75
|
+
<template>
|
|
76
|
+
<VTToast position="top-right">
|
|
77
|
+
<VTToastItem variant="success" :duration="5000">
|
|
78
|
+
<VTToastIcon />
|
|
79
|
+
<VTToastContent>
|
|
80
|
+
<VTToastTitle>File uploaded</VTToastTitle>
|
|
81
|
+
<VTToastDescription>document.pdf has been uploaded.</VTToastDescription>
|
|
82
|
+
</VTToastContent>
|
|
83
|
+
<VTToastClose />
|
|
84
|
+
</VTToastItem>
|
|
85
|
+
|
|
86
|
+
<VTToastItem variant="info" :duration="7000">
|
|
87
|
+
<VTToastIcon />
|
|
88
|
+
<VTToastContent>
|
|
89
|
+
<VTToastTitle>Processing</VTToastTitle>
|
|
90
|
+
<VTToastDescription>Your file is being processed.</VTToastDescription>
|
|
91
|
+
</VTToastContent>
|
|
92
|
+
<VTToastClose />
|
|
93
|
+
</VTToastItem>
|
|
94
|
+
|
|
95
|
+
<VTToastItem variant="error" :duration="0">
|
|
96
|
+
<VTToastIcon />
|
|
97
|
+
<VTToastContent>
|
|
98
|
+
<VTToastTitle>Upload Failed</VTToastTitle>
|
|
99
|
+
<VTToastDescription>Failed to upload image.png</VTToastDescription>
|
|
100
|
+
</VTToastContent>
|
|
101
|
+
<VTToastAction>
|
|
102
|
+
<VTButton size="small" @click="retry">Retry</VTButton>
|
|
103
|
+
</VTToastAction>
|
|
104
|
+
<VTToastClose />
|
|
105
|
+
</VTToastItem>
|
|
106
|
+
</VTToast>
|
|
107
|
+
</template>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Custom Icon
|
|
111
|
+
|
|
112
|
+
```vue
|
|
113
|
+
<template>
|
|
114
|
+
<VTToast position="bottom-left">
|
|
115
|
+
<VTToastItem variant="default">
|
|
116
|
+
<VTToastIcon>
|
|
117
|
+
<IconBell class="h-5 w-5" />
|
|
118
|
+
</VTToastIcon>
|
|
119
|
+
<VTToastContent>
|
|
120
|
+
<VTToastTitle>New Notification</VTToastTitle>
|
|
121
|
+
<VTToastDescription>You have a new message.</VTToastDescription>
|
|
122
|
+
</VTToastContent>
|
|
123
|
+
<VTToastClose />
|
|
124
|
+
</VTToastItem>
|
|
125
|
+
</VTToast>
|
|
126
|
+
</template>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Minimal Toast (Title Only)
|
|
130
|
+
|
|
131
|
+
```vue
|
|
132
|
+
<template>
|
|
133
|
+
<VTToast position="top-left">
|
|
134
|
+
<VTToastItem variant="success" :dismissable="false">
|
|
135
|
+
<VTToastIcon />
|
|
136
|
+
<VTToastContent>
|
|
137
|
+
<VTToastTitle>Saved successfully</VTToastTitle>
|
|
138
|
+
</VTToastContent>
|
|
139
|
+
</VTToastItem>
|
|
140
|
+
</VTToast>
|
|
141
|
+
</template>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Props
|
|
145
|
+
|
|
146
|
+
### VTToast
|
|
147
|
+
|
|
148
|
+
| Prop | Type | Default | Description |
|
|
149
|
+
| ---------- | ------- | ------------- | ------------------------------------------------------------------------------------------- |
|
|
150
|
+
| `position` | String | `'top-right'` | Position of toast container: `'top-left'`, `'top-right'`, `'bottom-left'`, `'bottom-right'` |
|
|
151
|
+
| `headless` | Boolean | `false` | Render without default styles (semantic class only) |
|
|
152
|
+
|
|
153
|
+
### VTToastItem
|
|
154
|
+
|
|
155
|
+
| Prop | Type | Default | Description |
|
|
156
|
+
| ------------- | ------- | ----------- | ---------------------------------------------------------------- |
|
|
157
|
+
| `variant` | String | `'default'` | Visual variant: `'default'`, `'success'`, `'error'`, `'warning'` |
|
|
158
|
+
| `dismissable` | Boolean | `true` | Show/hide close button |
|
|
159
|
+
| `duration` | Number | `5000` | Auto-dismiss duration in ms (0 = no auto-dismiss) |
|
|
160
|
+
| `headless` | Boolean | `false` | Render without default styles |
|
|
161
|
+
| `as` | String | `'div'` | HTML element to render as |
|
|
162
|
+
|
|
163
|
+
### VTToastIcon, VTToastContent, VTToastTitle, VTToastDescription, VTToastAction, VTToastClose
|
|
164
|
+
|
|
165
|
+
| Prop | Type | Default | Description |
|
|
166
|
+
| ---------- | ------- | ------- | ----------------------------- |
|
|
167
|
+
| `headless` | Boolean | `false` | Render without default styles |
|
|
168
|
+
| `as` | String | `'div'` | HTML element to render as |
|
|
169
|
+
|
|
170
|
+
## Events
|
|
171
|
+
|
|
172
|
+
### VTToastItem
|
|
173
|
+
|
|
174
|
+
| Event | Payload | Description |
|
|
175
|
+
| ------- | ------- | ------------------------------------------------ |
|
|
176
|
+
| `close` | - | Emitted when toast is dismissed (auto or manual) |
|
|
177
|
+
|
|
178
|
+
### VTToastClose
|
|
179
|
+
|
|
180
|
+
| Event | Payload | Description |
|
|
181
|
+
| ------- | ------- | ------------------------------------ |
|
|
182
|
+
| `click` | - | Emitted when close button is clicked |
|
|
183
|
+
|
|
184
|
+
## Behavior
|
|
185
|
+
|
|
186
|
+
### Auto-show/Auto-hide
|
|
187
|
+
|
|
188
|
+
The `<dialog>` element automatically:
|
|
189
|
+
|
|
190
|
+
- Shows when the first `VTToastItem` is registered
|
|
191
|
+
- Hides when the last `VTToastItem` is unregistered
|
|
192
|
+
|
|
193
|
+
### Auto-dismiss
|
|
194
|
+
|
|
195
|
+
Toast items automatically dismiss after the `duration` (in milliseconds):
|
|
196
|
+
|
|
197
|
+
- Default: 5000ms (5 seconds)
|
|
198
|
+
- Set to `0` to disable auto-dismiss
|
|
199
|
+
- Timer pauses on `mouseenter`, resumes on `mouseleave`
|
|
200
|
+
|
|
201
|
+
### Stacking
|
|
202
|
+
|
|
203
|
+
Multiple toast items stack vertically with spacing (`gap-3`) at the configured position.
|
|
204
|
+
|
|
205
|
+
## Layout
|
|
206
|
+
|
|
207
|
+
Toast items use a 3-column grid layout:
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
┌─────────┬──────────────────────┬───────────────┐
|
|
211
|
+
│ Icon │ Content (Title + │ Action + │
|
|
212
|
+
│ │ Description) │ Close │
|
|
213
|
+
└─────────┴──────────────────────┴───────────────┘
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Grid: `grid-cols-[auto_1fr_auto]`
|
|
217
|
+
|
|
218
|
+
- Column 1: Icon (auto-sized)
|
|
219
|
+
- Column 2: Content wrapper (fills remaining space)
|
|
220
|
+
- Column 3: Actions + Close button (auto-sized)
|
|
221
|
+
|
|
222
|
+
## Accessibility
|
|
223
|
+
|
|
224
|
+
- `role="alert"` on toast items
|
|
225
|
+
- `aria-live="polite"` for screen reader announcements
|
|
226
|
+
- Unique IDs for title and description (for future `aria-labelledby`/`aria-describedby` support)
|
|
227
|
+
|
|
228
|
+
## Headless Mode
|
|
229
|
+
|
|
230
|
+
All components support headless mode for custom styling:
|
|
231
|
+
|
|
232
|
+
```vue
|
|
233
|
+
<VTToast headless position="top-right">
|
|
234
|
+
<VTToastItem headless variant="success">
|
|
235
|
+
<VTToastIcon headless />
|
|
236
|
+
<VTToastContent headless>
|
|
237
|
+
<VTToastTitle headless>Custom styled toast</VTToastTitle>
|
|
238
|
+
</VTToastContent>
|
|
239
|
+
</VTToastItem>
|
|
240
|
+
</VTToast>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Headless classes:
|
|
244
|
+
|
|
245
|
+
- `.toast` - Container
|
|
246
|
+
- `.toast-item` - Individual toast
|
|
247
|
+
- `.toast-item--{variant}` - Variant modifier
|
|
248
|
+
- `.toast-icon` - Icon
|
|
249
|
+
- `.toast-content` - Content wrapper
|
|
250
|
+
- `.toast-title` - Title
|
|
251
|
+
- `.toast-description` - Description
|
|
252
|
+
- `.toast-action` - Action area
|
|
253
|
+
- `.toast-close` - Close button
|
|
254
|
+
|
|
255
|
+
## Browser Support
|
|
256
|
+
|
|
257
|
+
The `@starting-style` CSS feature is used for dialog animations. Supported in:
|
|
258
|
+
|
|
259
|
+
- Chrome 117+
|
|
260
|
+
- Safari 17.5+
|
|
261
|
+
- Firefox 129+
|
|
262
|
+
|
|
263
|
+
For older browsers, toasts will still work but without the initial slide animation.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Portal>
|
|
3
|
+
<dialog
|
|
4
|
+
ref="dialog"
|
|
5
|
+
:id="id"
|
|
6
|
+
:class="
|
|
7
|
+
headless
|
|
8
|
+
? 'toast'
|
|
9
|
+
: `fixed ${positionClass} z-50 flex flex-col gap-2 border-none bg-transparent p-0`
|
|
10
|
+
"
|
|
11
|
+
>
|
|
12
|
+
<slot></slot>
|
|
13
|
+
</dialog>
|
|
14
|
+
</Portal>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script>
|
|
18
|
+
import { Portal } from '@linusborg/vue-simple-portal';
|
|
19
|
+
import { genId } from '../../utils/ids';
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
name: 'VTToast',
|
|
23
|
+
|
|
24
|
+
components: {
|
|
25
|
+
Portal,
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
provide() {
|
|
29
|
+
return {
|
|
30
|
+
apiToast: () => {
|
|
31
|
+
const componentId = this.componentId;
|
|
32
|
+
|
|
33
|
+
const registerItem = (item) => {
|
|
34
|
+
if (!item) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.items.push(item);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const unregisterItem = (item) => {
|
|
42
|
+
if (!item) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const index = this.items.indexOf(item);
|
|
47
|
+
|
|
48
|
+
if (index > -1) {
|
|
49
|
+
this.items.splice(index, 1);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
componentId,
|
|
55
|
+
registerItem,
|
|
56
|
+
unregisterItem,
|
|
57
|
+
position: this.position,
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
props: {
|
|
64
|
+
headless: {
|
|
65
|
+
type: Boolean,
|
|
66
|
+
default: false,
|
|
67
|
+
},
|
|
68
|
+
position: {
|
|
69
|
+
type: String,
|
|
70
|
+
default: 'top-right',
|
|
71
|
+
validator: (value) => {
|
|
72
|
+
return [
|
|
73
|
+
'top-left',
|
|
74
|
+
'top-right',
|
|
75
|
+
'bottom-left',
|
|
76
|
+
'bottom-right',
|
|
77
|
+
].includes(value);
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
data() {
|
|
83
|
+
return {
|
|
84
|
+
componentId: genId(),
|
|
85
|
+
items: [],
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
computed: {
|
|
90
|
+
id() {
|
|
91
|
+
return `toast-${this.componentId}`;
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
positionClass() {
|
|
95
|
+
switch (this.position) {
|
|
96
|
+
case 'top-left':
|
|
97
|
+
return 'top-6 left-10 bottom-auto right-auto';
|
|
98
|
+
case 'top-right':
|
|
99
|
+
return 'top-6 right-10 bottom-auto left-auto';
|
|
100
|
+
case 'bottom-left':
|
|
101
|
+
return 'bottom-6 left-10 top-auto right-auto';
|
|
102
|
+
case 'bottom-right':
|
|
103
|
+
return 'bottom-6 right-10 top-auto left-auto';
|
|
104
|
+
default:
|
|
105
|
+
return 'top-6 right-10 bottom-auto left-auto';
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
isOpen() {
|
|
110
|
+
return this.items.length > 0;
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
watch: {
|
|
115
|
+
isOpen(newValue, oldValue) {
|
|
116
|
+
if (newValue && !oldValue) {
|
|
117
|
+
// Show dialog when first item is added
|
|
118
|
+
this.$refs.dialog?.show();
|
|
119
|
+
} else if (!newValue && oldValue) {
|
|
120
|
+
// Close dialog when last item is removed
|
|
121
|
+
this.$refs.dialog?.close();
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<style scoped>
|
|
129
|
+
dialog {
|
|
130
|
+
transition: opacity 300ms ease-out;
|
|
131
|
+
opacity: 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
dialog[open] {
|
|
135
|
+
opacity: 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Initial state - just fade in */
|
|
139
|
+
@starting-style {
|
|
140
|
+
dialog[open] {
|
|
141
|
+
opacity: 0;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Remove default dialog backdrop and allow clicks through */
|
|
146
|
+
dialog::backdrop {
|
|
147
|
+
background: transparent;
|
|
148
|
+
pointer-events: none;
|
|
149
|
+
}
|
|
150
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="as"
|
|
4
|
+
:class="[headless ? 'toast-action' : 'flex flex-col gap-2']"
|
|
5
|
+
>
|
|
6
|
+
<slot></slot>
|
|
7
|
+
</component>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script>
|
|
11
|
+
export default {
|
|
12
|
+
name: 'VTToastAction',
|
|
13
|
+
|
|
14
|
+
props: {
|
|
15
|
+
headless: {
|
|
16
|
+
type: Boolean,
|
|
17
|
+
default: false,
|
|
18
|
+
},
|
|
19
|
+
as: {
|
|
20
|
+
type: String,
|
|
21
|
+
default: 'div',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
</script>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<VTButton
|
|
3
|
+
variant="icon"
|
|
4
|
+
:id="id"
|
|
5
|
+
:class="[
|
|
6
|
+
headless
|
|
7
|
+
? 'toast-close'
|
|
8
|
+
: 'self-start text-gray-400 size-2.5 focus:bg-transparent hover:bg-transparent',
|
|
9
|
+
]"
|
|
10
|
+
@click.prevent="close"
|
|
11
|
+
>
|
|
12
|
+
<slot>
|
|
13
|
+
<IconClose class="h-4 w-4" />
|
|
14
|
+
</slot>
|
|
15
|
+
</VTButton>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import { IconClose } from '@veritree/icons';
|
|
20
|
+
import VTButton from '../Button/VTButton.vue';
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
name: 'VTToastClose',
|
|
24
|
+
|
|
25
|
+
components: {
|
|
26
|
+
IconClose,
|
|
27
|
+
VTButton,
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
inject: ['apiToastItem'],
|
|
31
|
+
|
|
32
|
+
props: {
|
|
33
|
+
headless: {
|
|
34
|
+
type: Boolean,
|
|
35
|
+
default: false,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
computed: {
|
|
40
|
+
id() {
|
|
41
|
+
return `toast-close-${this.apiToastItem().componentId}`;
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
methods: {
|
|
46
|
+
close() {
|
|
47
|
+
this.apiToastItem().close();
|
|
48
|
+
this.$emit('click');
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
</script>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="as"
|
|
4
|
+
:class="[headless ? 'toast-content' : 'flex flex-col gap-0.5']"
|
|
5
|
+
>
|
|
6
|
+
<slot></slot>
|
|
7
|
+
</component>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script>
|
|
11
|
+
export default {
|
|
12
|
+
name: 'VTToastContent',
|
|
13
|
+
|
|
14
|
+
props: {
|
|
15
|
+
headless: {
|
|
16
|
+
type: Boolean,
|
|
17
|
+
default: false,
|
|
18
|
+
},
|
|
19
|
+
as: {
|
|
20
|
+
type: String,
|
|
21
|
+
default: 'div',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
</script>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="as"
|
|
4
|
+
:id="id"
|
|
5
|
+
:class="[
|
|
6
|
+
headless ? 'toast-description' : 'text-xs opacity-90 text-gray-400',
|
|
7
|
+
]"
|
|
8
|
+
>
|
|
9
|
+
<slot></slot>
|
|
10
|
+
</component>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
export default {
|
|
15
|
+
name: 'VTToastDescription',
|
|
16
|
+
|
|
17
|
+
inject: ['apiToastItem'],
|
|
18
|
+
|
|
19
|
+
props: {
|
|
20
|
+
headless: {
|
|
21
|
+
type: Boolean,
|
|
22
|
+
default: false,
|
|
23
|
+
},
|
|
24
|
+
as: {
|
|
25
|
+
type: String,
|
|
26
|
+
default: 'div',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
computed: {
|
|
31
|
+
id() {
|
|
32
|
+
return `toast-description-${this.apiToastItem().componentId}`;
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
</script>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="as"
|
|
4
|
+
:class="[
|
|
5
|
+
headless
|
|
6
|
+
? 'toast-icon'
|
|
7
|
+
: 'flex h-5 w-5 shrink-0 items-center justify-center',
|
|
8
|
+
]"
|
|
9
|
+
>
|
|
10
|
+
<slot>
|
|
11
|
+
<!-- Default icons based on variant -->
|
|
12
|
+
<IconCheck v-if="isSuccess" :class="iconColorClass" />
|
|
13
|
+
<IconWarning v-else-if="isError" :class="iconColorClass" />
|
|
14
|
+
<IconInfo v-else :class="iconColorClass" />
|
|
15
|
+
</slot>
|
|
16
|
+
</component>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
import { IconCheck, IconWarning, IconInfo } from '@veritree/icons';
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
name: 'VTToastIcon',
|
|
24
|
+
|
|
25
|
+
components: {
|
|
26
|
+
IconCheck,
|
|
27
|
+
IconWarning,
|
|
28
|
+
IconInfo,
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
inject: ['apiToastItem'],
|
|
32
|
+
|
|
33
|
+
props: {
|
|
34
|
+
headless: {
|
|
35
|
+
type: Boolean,
|
|
36
|
+
default: false,
|
|
37
|
+
},
|
|
38
|
+
as: {
|
|
39
|
+
type: String,
|
|
40
|
+
default: 'div',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
computed: {
|
|
45
|
+
variant() {
|
|
46
|
+
return this.apiToastItem().variant;
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
isSuccess() {
|
|
50
|
+
return this.variant === 'success';
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
isError() {
|
|
54
|
+
return this.variant === 'error';
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
isWarning() {
|
|
58
|
+
return this.variant === 'warning';
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
iconColorClass() {
|
|
62
|
+
if (this.headless) return '';
|
|
63
|
+
|
|
64
|
+
if (this.isSuccess) return 'text-secondary-200';
|
|
65
|
+
if (this.isError) return 'text-error-500';
|
|
66
|
+
if (this.isWarning) return 'text-warning-200';
|
|
67
|
+
|
|
68
|
+
return 'h-5 w-5';
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
</script>
|