srcdev-nuxt-components 6.1.33 → 6.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Teleport to="body">
|
|
3
3
|
<div
|
|
4
|
-
v-if="
|
|
4
|
+
v-if="privateDisplayToast"
|
|
5
|
+
ref="toastElement"
|
|
5
6
|
class="display-toast"
|
|
6
7
|
:class="[
|
|
7
8
|
elementClasses,
|
|
8
9
|
cssStateClass,
|
|
10
|
+
positionClasses,
|
|
9
11
|
{
|
|
10
12
|
'has-theme': !slots.default,
|
|
11
|
-
'auto-dismiss': autoDismiss,
|
|
12
13
|
},
|
|
13
14
|
]"
|
|
14
15
|
:data-theme="theme"
|
|
16
|
+
:role="toastRole"
|
|
17
|
+
:aria-live="ariaLive"
|
|
18
|
+
:tabindex="slots.default ? undefined : '0'"
|
|
19
|
+
:aria-describedby="slots.default ? undefined : 'toast-message-' + toastId"
|
|
20
|
+
@keydown.escape="setDismissToast"
|
|
15
21
|
>
|
|
16
22
|
<slot v-if="slots.default"></slot>
|
|
17
23
|
|
|
18
24
|
<div v-else class="display-toast-inner">
|
|
19
25
|
<div class="toast-icon" aria-hidden="true">
|
|
20
26
|
<slot name="customToastIcon">
|
|
21
|
-
<Icon :name="defaultThemeIcons[
|
|
27
|
+
<Icon :name="customIcon || defaultThemeIcons[theme] || 'akar-icons:info'" class="icon" />
|
|
22
28
|
</slot>
|
|
23
29
|
</div>
|
|
24
|
-
<div class="toast-message">{{ toastDisplayText }}</div>
|
|
30
|
+
<div class="toast-message" :id="'toast-message-' + toastId">{{ toastDisplayText }}</div>
|
|
25
31
|
<div class="toast-action">
|
|
26
|
-
<button @click.prevent="
|
|
32
|
+
<button @click.prevent="setDismissToast()">
|
|
27
33
|
<Icon name="material-symbols:close" class="icon" />
|
|
28
34
|
<span class="sr-only">Close</span>
|
|
29
35
|
</button>
|
|
@@ -33,37 +39,103 @@
|
|
|
33
39
|
</div>
|
|
34
40
|
</Teleport>
|
|
35
41
|
</template>
|
|
42
|
+
|
|
43
|
+
<script lang="ts">
|
|
44
|
+
/**
|
|
45
|
+
* DisplayToast - Configurable toast notification component
|
|
46
|
+
*
|
|
47
|
+
* Example usage with config object:
|
|
48
|
+
* <DisplayToast
|
|
49
|
+
* v-model="showToast"
|
|
50
|
+
* :config="{
|
|
51
|
+
* appearance: { theme: 'success', position: 'top', alignment: 'right' },
|
|
52
|
+
* behavior: { autoDismiss: true, duration: 3000 },
|
|
53
|
+
* content: { text: 'Operation completed successfully!' }
|
|
54
|
+
* }"
|
|
55
|
+
* />
|
|
56
|
+
*
|
|
57
|
+
* Types exported for use in other components:
|
|
58
|
+
* - DisplayToastConfig
|
|
59
|
+
* - DisplayToastProps
|
|
60
|
+
* - DisplayToastTheme
|
|
61
|
+
* - DisplayToastAppearanceConfig
|
|
62
|
+
* - DisplayToastBehaviorConfig
|
|
63
|
+
* - DisplayToastContentConfig
|
|
64
|
+
* - ToastSlots
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
export type DisplayToastTheme =
|
|
68
|
+
| "primary"
|
|
69
|
+
| "secondary"
|
|
70
|
+
| "tertiary"
|
|
71
|
+
| "ghost"
|
|
72
|
+
| "error"
|
|
73
|
+
| "info"
|
|
74
|
+
| "success"
|
|
75
|
+
| "warning"
|
|
76
|
+
|
|
77
|
+
export type DisplayToastPosition = "top" | "bottom"
|
|
78
|
+
export type DisplayToastAlignment = "left" | "center" | "right"
|
|
79
|
+
|
|
80
|
+
export interface DisplayToastAppearanceConfig {
|
|
81
|
+
theme?: DisplayToastTheme
|
|
82
|
+
position?: DisplayToastPosition
|
|
83
|
+
alignment?: DisplayToastAlignment
|
|
84
|
+
fullWidth?: boolean
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface DisplayToastBehaviorConfig {
|
|
88
|
+
autoDismiss?: boolean
|
|
89
|
+
duration?: number
|
|
90
|
+
revealDuration?: number
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface DisplayToastContentConfig {
|
|
94
|
+
text?: string
|
|
95
|
+
customIcon?: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface DisplayToastConfig {
|
|
99
|
+
appearance?: DisplayToastAppearanceConfig
|
|
100
|
+
behavior?: DisplayToastBehaviorConfig
|
|
101
|
+
content?: DisplayToastContentConfig
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface DisplayToastProps {
|
|
105
|
+
config?: DisplayToastConfig
|
|
106
|
+
styleClassPassthrough?: string | string[]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ToastSlots {
|
|
110
|
+
default?(props?: {}): any
|
|
111
|
+
customToastIcon?(props?: {}): any
|
|
112
|
+
}
|
|
113
|
+
</script>
|
|
114
|
+
|
|
36
115
|
<script setup lang="ts">
|
|
37
|
-
const props = defineProps({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
116
|
+
const props = withDefaults(defineProps<DisplayToastProps>(), {
|
|
117
|
+
config: () => ({
|
|
118
|
+
appearance: {
|
|
119
|
+
theme: "ghost" as DisplayToastTheme,
|
|
120
|
+
position: "top" as DisplayToastPosition,
|
|
121
|
+
alignment: "right" as DisplayToastAlignment,
|
|
122
|
+
fullWidth: false,
|
|
123
|
+
},
|
|
124
|
+
behavior: {
|
|
125
|
+
autoDismiss: true,
|
|
126
|
+
duration: 5000,
|
|
127
|
+
revealDuration: 550,
|
|
128
|
+
},
|
|
129
|
+
content: {
|
|
130
|
+
text: "",
|
|
131
|
+
customIcon: undefined,
|
|
43
132
|
},
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
type: Number,
|
|
47
|
-
default: 550,
|
|
48
|
-
},
|
|
49
|
-
autoDismiss: {
|
|
50
|
-
type: Boolean,
|
|
51
|
-
default: true,
|
|
52
|
-
},
|
|
53
|
-
duration: {
|
|
54
|
-
type: Number,
|
|
55
|
-
default: 5000,
|
|
56
|
-
},
|
|
57
|
-
toastDisplayText: {
|
|
58
|
-
type: String,
|
|
59
|
-
default: "",
|
|
60
|
-
},
|
|
61
|
-
styleClassPassthrough: {
|
|
62
|
-
type: [String, Array] as PropType<string | string[]>,
|
|
63
|
-
default: () => [],
|
|
64
|
-
},
|
|
133
|
+
}),
|
|
134
|
+
styleClassPassthrough: () => [],
|
|
65
135
|
})
|
|
66
136
|
|
|
137
|
+
const slots = defineSlots<ToastSlots>()
|
|
138
|
+
|
|
67
139
|
const defaultThemeIcons = {
|
|
68
140
|
primary: "akar-icons:info",
|
|
69
141
|
secondary: "akar-icons:info",
|
|
@@ -75,51 +147,70 @@ const defaultThemeIcons = {
|
|
|
75
147
|
warning: "akar-icons:circle-alert",
|
|
76
148
|
}
|
|
77
149
|
|
|
78
|
-
const slots = useSlots()
|
|
79
150
|
const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
|
|
80
151
|
|
|
81
|
-
//
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
152
|
+
// Computed properties for accessing config values with defaults
|
|
153
|
+
const theme = computed(() => props.config?.appearance?.theme ?? "ghost")
|
|
154
|
+
const position = computed(() => props.config?.appearance?.position ?? "top")
|
|
155
|
+
const alignment = computed(() => props.config?.appearance?.alignment ?? "right")
|
|
156
|
+
const fullWidth = computed(() => props.config?.appearance?.fullWidth ?? false)
|
|
157
|
+
const autoDismiss = computed(() => props.config?.behavior?.autoDismiss ?? true)
|
|
158
|
+
const duration = computed(() => props.config?.behavior?.duration ?? 5000)
|
|
159
|
+
const revealDuration = computed(() => props.config?.behavior?.revealDuration ?? 550)
|
|
160
|
+
const toastDisplayText = computed(() => props.config?.content?.text ?? "")
|
|
161
|
+
const customIcon = computed(() => props.config?.content?.customIcon)
|
|
162
|
+
|
|
163
|
+
// Computed classes for positioning
|
|
164
|
+
const positionClasses = computed(() => {
|
|
165
|
+
const classes = []
|
|
166
|
+
classes.push(position.value)
|
|
167
|
+
if (fullWidth.value) {
|
|
168
|
+
classes.push("full-width")
|
|
169
|
+
} else {
|
|
170
|
+
classes.push(alignment.value)
|
|
171
|
+
}
|
|
172
|
+
return classes
|
|
85
173
|
})
|
|
86
174
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
175
|
+
/*
|
|
176
|
+
* Accessibility setup
|
|
177
|
+
*/
|
|
178
|
+
const toastId = useId()
|
|
179
|
+
const toastElement = ref<HTMLElement>()
|
|
92
180
|
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
181
|
+
// Determine appropriate ARIA attributes based on theme
|
|
182
|
+
const toastRole = computed(() => {
|
|
183
|
+
return ["error", "warning"].includes(theme.value) ? "alert" : "status"
|
|
184
|
+
})
|
|
97
185
|
|
|
98
|
-
const
|
|
99
|
-
|
|
186
|
+
const ariaLive = computed(() => {
|
|
187
|
+
return ["error", "warning"].includes(theme.value) ? "assertive" : "polite"
|
|
188
|
+
})
|
|
100
189
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
190
|
+
/*
|
|
191
|
+
* Setup component state
|
|
192
|
+
*/
|
|
193
|
+
const externalTriggerModel = defineModel<boolean>({ default: false })
|
|
194
|
+
const privateDisplayToast = ref(false)
|
|
195
|
+
const transitionalState = ref(false)
|
|
196
|
+
const cssStateClass = computed(() => {
|
|
197
|
+
return transitionalState.value ? "show" : "hide"
|
|
198
|
+
})
|
|
105
199
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
state.value = "hiding"
|
|
121
|
-
await useSleep(revealDurationInt.value)
|
|
122
|
-
updateToIdle()
|
|
200
|
+
/*
|
|
201
|
+
* Computed properties for durations (in ms for CSS)
|
|
202
|
+
*/
|
|
203
|
+
const revealDurationMs = computed(() => revealDuration.value + "ms")
|
|
204
|
+
const displayDurationMs = computed(() => duration.value + "ms")
|
|
205
|
+
|
|
206
|
+
/*
|
|
207
|
+
* Lifecycle hooks
|
|
208
|
+
*/
|
|
209
|
+
const setDismissToast = async () => {
|
|
210
|
+
transitionalState.value = false
|
|
211
|
+
await useSleep(revealDuration.value)
|
|
212
|
+
externalTriggerModel.value = false
|
|
213
|
+
privateDisplayToast.value = false
|
|
123
214
|
}
|
|
124
215
|
|
|
125
216
|
watch(
|
|
@@ -130,69 +221,35 @@ watch(
|
|
|
130
221
|
)
|
|
131
222
|
|
|
132
223
|
watch(
|
|
133
|
-
() =>
|
|
224
|
+
() => externalTriggerModel.value,
|
|
134
225
|
async (newValue, previousValue) => {
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
226
|
+
if (newValue) {
|
|
227
|
+
privateDisplayToast.value = true
|
|
228
|
+
transitionalState.value = true
|
|
229
|
+
|
|
230
|
+
// Focus management for accessibility when not using custom slots
|
|
231
|
+
if (!slots.default) {
|
|
232
|
+
await nextTick()
|
|
233
|
+
// Wait for animation to start before focusing
|
|
234
|
+
setTimeout(() => {
|
|
235
|
+
toastElement.value?.focus()
|
|
236
|
+
}, 100)
|
|
237
|
+
}
|
|
145
238
|
|
|
146
|
-
|
|
147
|
-
|
|
239
|
+
if (autoDismiss.value) {
|
|
240
|
+
await useSleep(duration.value)
|
|
241
|
+
setDismissToast()
|
|
242
|
+
}
|
|
148
243
|
}
|
|
149
244
|
}
|
|
150
245
|
)
|
|
151
246
|
</script>
|
|
152
247
|
|
|
153
248
|
<style scoped lang="css">
|
|
154
|
-
@keyframes slide-in {
|
|
155
|
-
from {
|
|
156
|
-
opacity: 0;
|
|
157
|
-
visibility: hidden;
|
|
158
|
-
transform: translateY(20px);
|
|
159
|
-
}
|
|
160
|
-
to {
|
|
161
|
-
opacity: 1;
|
|
162
|
-
visibility: visible;
|
|
163
|
-
transform: translateY(0);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
@keyframes slide-out {
|
|
168
|
-
from {
|
|
169
|
-
opacity: 1;
|
|
170
|
-
visibility: visible;
|
|
171
|
-
transform: translateY(0);
|
|
172
|
-
}
|
|
173
|
-
to {
|
|
174
|
-
opacity: 0;
|
|
175
|
-
visibility: hidden;
|
|
176
|
-
transform: translateY(20px);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
@keyframes slide-in-out {
|
|
181
|
-
5% {
|
|
182
|
-
opacity: 1;
|
|
183
|
-
visibility: visible;
|
|
184
|
-
transform: translateY(0);
|
|
185
|
-
}
|
|
186
|
-
95% {
|
|
187
|
-
opacity: 1;
|
|
188
|
-
transform: translateY(0);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
249
|
@keyframes show {
|
|
193
250
|
to {
|
|
194
251
|
opacity: 1;
|
|
195
|
-
visibility: visible;
|
|
252
|
+
/* visibility: visible; */
|
|
196
253
|
transform: translateY(0);
|
|
197
254
|
}
|
|
198
255
|
}
|
|
@@ -200,12 +257,12 @@ watch(
|
|
|
200
257
|
@keyframes hide {
|
|
201
258
|
0% {
|
|
202
259
|
opacity: 1;
|
|
203
|
-
visibility: visible;
|
|
260
|
+
/* visibility: visible; */
|
|
204
261
|
transform: translateY(0);
|
|
205
262
|
}
|
|
206
263
|
100% {
|
|
207
264
|
opacity: 0;
|
|
208
|
-
visibility: hidden;
|
|
265
|
+
/* visibility: hidden; */
|
|
209
266
|
transform: translateY(-30px);
|
|
210
267
|
}
|
|
211
268
|
}
|
|
@@ -222,39 +279,26 @@ watch(
|
|
|
222
279
|
position: fixed;
|
|
223
280
|
margin: 0;
|
|
224
281
|
opacity: 0;
|
|
225
|
-
visibility: hidden;
|
|
282
|
+
/* visibility: hidden; */
|
|
226
283
|
|
|
227
284
|
z-index: 100;
|
|
228
285
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
286
|
+
/* Focus styles for accessibility */
|
|
287
|
+
&:focus {
|
|
288
|
+
outline: 2px solid var(--colour-theme-3, #007acc);
|
|
289
|
+
outline-offset: 2px;
|
|
233
290
|
}
|
|
234
291
|
|
|
235
|
-
&:not(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
animation: show v-bind(revealDuration) var(--spring-easing) forwards;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
&.visible {
|
|
242
|
-
/* if you want a steady state style, add here */
|
|
243
|
-
opacity: 1;
|
|
244
|
-
visibility: visible;
|
|
245
|
-
transform: translateY(0);
|
|
246
|
-
}
|
|
292
|
+
&:focus:not(:focus-visible) {
|
|
293
|
+
outline: none;
|
|
294
|
+
}
|
|
247
295
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
animation: hide v-bind(revealDuration) var(--spring-easing) forwards;
|
|
251
|
-
}
|
|
296
|
+
&.show {
|
|
297
|
+
animation: show v-bind(revealDurationMs) var(--spring-easing) forwards;
|
|
252
298
|
}
|
|
253
299
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
animation-play-state: paused;
|
|
257
|
-
}
|
|
300
|
+
&.hide {
|
|
301
|
+
animation: hide v-bind(revealDurationMs) var(--spring-easing) forwards;
|
|
258
302
|
}
|
|
259
303
|
|
|
260
304
|
&.full-width {
|
|
@@ -272,8 +316,9 @@ watch(
|
|
|
272
316
|
}
|
|
273
317
|
|
|
274
318
|
&.center {
|
|
275
|
-
|
|
276
|
-
|
|
319
|
+
inset-inline: 0;
|
|
320
|
+
margin-inline: auto;
|
|
321
|
+
width: max-content;
|
|
277
322
|
}
|
|
278
323
|
}
|
|
279
324
|
|
|
@@ -289,7 +334,6 @@ watch(
|
|
|
289
334
|
/*
|
|
290
335
|
* Styles for the display toast component
|
|
291
336
|
*/
|
|
292
|
-
|
|
293
337
|
&.has-theme {
|
|
294
338
|
padding-inline-start: 6px;
|
|
295
339
|
background-color: var(--colour-theme-8);
|
|
@@ -369,7 +413,8 @@ watch(
|
|
|
369
413
|
vertical-align: middle;
|
|
370
414
|
}
|
|
371
415
|
|
|
372
|
-
&:hover
|
|
416
|
+
&:hover,
|
|
417
|
+
&:focus-visible {
|
|
373
418
|
box-shadow: none;
|
|
374
419
|
background-color: var(--colour-theme-8);
|
|
375
420
|
color: var(--colour-theme-0);
|
|
@@ -391,7 +436,7 @@ watch(
|
|
|
391
436
|
transform-origin: right;
|
|
392
437
|
background: linear-gradient(to right, var(--colour-theme-2), var(--colour-theme-8));
|
|
393
438
|
border-radius: inherit;
|
|
394
|
-
animation: progress v-bind(
|
|
439
|
+
animation: progress v-bind(displayDurationMs) linear forwards;
|
|
395
440
|
}
|
|
396
441
|
}
|
|
397
442
|
</style>
|
package/app/types/index.ts
CHANGED
|
@@ -2,3 +2,4 @@ export * from "../components/responsive-header/ResponsiveHeader.vue"
|
|
|
2
2
|
export * from "../components/display-chip/DisplayChip.vue"
|
|
3
3
|
export * from "../components/carousel-basic/CarouselBasic.vue"
|
|
4
4
|
export * from "../components/image-galleries/SliderGallery.vue"
|
|
5
|
+
export * from "../components/display-toast/DisplayToast.vue"
|
package/package.json
CHANGED