picocrank 1.18.2 → 1.18.3
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/index.html +10 -0
- package/package.json +1 -1
- package/router.js +43 -12
- package/styles/dark-theme.css +189 -0
- package/styles.css +1 -1
- package/vue/components/Calendar.vue +36 -2
- package/vue/components/Header.vue +18 -1
- package/vue/components/Navigation.vue +11 -0
- package/vue/components/NavigationGrid.vue +2 -2
- package/vue/components/NotificationPopup.vue +118 -0
- package/vue/components/NotificationPopups.vue +73 -0
- package/vue/components/Pagination.vue +1 -1
- package/vue/components/QuickSearch.vue +1 -1
- package/vue/components/Sidebar.vue +185 -31
- package/vue/components/Tabs.vue +1 -1
- package/vue/components/TopBar.vue +4 -2
- package/vue/composables/useNotificationPopups.js +78 -0
- package/vue/composables/useTheme.js +60 -0
- package/vue/views/App.vue +27 -20
- package/vue/views/ButtonsExample.vue +93 -0
- package/vue/views/CalendarExample.vue +26 -7
- package/vue/views/NavigationGridExample.vue +89 -0
- package/vue/views/PageStructure.vue +111 -0
- package/vue/views/StatusExample.vue +273 -0
- package/vue/views/TabsExample.vue +1 -1
- package/vue/views/ViewItem.vue +189 -3
- package/vue/views/Welcome.vue +218 -96
- package/vue/views/UserControlPanel.vue +0 -82
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Section
|
|
3
|
+
title="Buttons"
|
|
4
|
+
subtitle="Femtocrank button styles and semantic types"
|
|
5
|
+
>
|
|
6
|
+
<p>
|
|
7
|
+
Buttons use the global Femtocrank theme. Apply karma classes for intent, or
|
|
8
|
+
rely on <code>type</code> attributes for form semantics.
|
|
9
|
+
</p>
|
|
10
|
+
</Section>
|
|
11
|
+
|
|
12
|
+
<Section title="Variants" subtitle="Default and karma-styled buttons">
|
|
13
|
+
<div role="toolbar" class="buttons-row">
|
|
14
|
+
<button type="button">Default</button>
|
|
15
|
+
<button type="button" class="neutral">Neutral</button>
|
|
16
|
+
<button type="button" class="good">Good</button>
|
|
17
|
+
<button type="button" class="bad">Bad</button>
|
|
18
|
+
<button type="button" class="warning">Warning</button>
|
|
19
|
+
</div>
|
|
20
|
+
</Section>
|
|
21
|
+
|
|
22
|
+
<Section title="Form actions" subtitle="Semantic button types in a fieldset">
|
|
23
|
+
<form class="buttons-demo-form" @submit.prevent="onSubmit">
|
|
24
|
+
<fieldset>
|
|
25
|
+
<button type="submit">Submit</button>
|
|
26
|
+
<button type="cancel">Cancel</button>
|
|
27
|
+
<button type="reset" @click="onReset">Reset</button>
|
|
28
|
+
</fieldset>
|
|
29
|
+
<p v-if="lastAction" class="subtle">{{ lastAction }}</p>
|
|
30
|
+
</form>
|
|
31
|
+
</Section>
|
|
32
|
+
|
|
33
|
+
<Section title="Links as buttons" subtitle="Anchor elements with the .button class">
|
|
34
|
+
<div role="toolbar" class="buttons-row">
|
|
35
|
+
<a href="#" class="button" @click.prevent>Default link</a>
|
|
36
|
+
<a href="#" class="button neutral" @click.prevent>Neutral link</a>
|
|
37
|
+
<a href="#" class="button good" @click.prevent>Good link</a>
|
|
38
|
+
</div>
|
|
39
|
+
</Section>
|
|
40
|
+
|
|
41
|
+
<Section title="Disabled" subtitle="Inactive buttons">
|
|
42
|
+
<div role="toolbar" class="buttons-row">
|
|
43
|
+
<button type="button" disabled>Disabled default</button>
|
|
44
|
+
<button type="submit" disabled>Disabled submit</button>
|
|
45
|
+
<button type="button" class="good" disabled>Disabled good</button>
|
|
46
|
+
</div>
|
|
47
|
+
</Section>
|
|
48
|
+
|
|
49
|
+
<Section title="Toolbars" subtitle="Grouped actions in section headers and content">
|
|
50
|
+
<template #toolbar>
|
|
51
|
+
<button type="button">Preview</button>
|
|
52
|
+
<button type="button" class="good">Save</button>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<p>
|
|
56
|
+
Section toolbars are a common place for primary and secondary actions. The
|
|
57
|
+
toolbar above mirrors the pattern used across other PicoCrank examples.
|
|
58
|
+
</p>
|
|
59
|
+
|
|
60
|
+
<div role="toolbar" class="buttons-row">
|
|
61
|
+
<button type="button" class="neutral">Neutral</button>
|
|
62
|
+
<button type="button" class="good">Good</button>
|
|
63
|
+
<button type="button" class="bad">Bad</button>
|
|
64
|
+
</div>
|
|
65
|
+
</Section>
|
|
66
|
+
</template>
|
|
67
|
+
|
|
68
|
+
<script setup>
|
|
69
|
+
import { ref } from 'vue'
|
|
70
|
+
import Section from '../components/Section.vue'
|
|
71
|
+
|
|
72
|
+
const lastAction = ref('')
|
|
73
|
+
|
|
74
|
+
function onSubmit() {
|
|
75
|
+
lastAction.value = 'Form submitted.'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function onReset() {
|
|
79
|
+
lastAction.value = 'Form reset.'
|
|
80
|
+
}
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<style scoped>
|
|
84
|
+
.buttons-row {
|
|
85
|
+
display: flex;
|
|
86
|
+
flex-wrap: wrap;
|
|
87
|
+
gap: 0.5rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.buttons-demo-form fieldset {
|
|
91
|
+
margin-bottom: 0;
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
@@ -40,6 +40,13 @@
|
|
|
40
40
|
import { ref, onMounted } from 'vue'
|
|
41
41
|
import Calendar from '../components/Calendar.vue'
|
|
42
42
|
import Section from '../components/Section.vue'
|
|
43
|
+
import {
|
|
44
|
+
BeachIcon,
|
|
45
|
+
Calendar01Icon,
|
|
46
|
+
MeetingRoomIcon,
|
|
47
|
+
PartyIcon,
|
|
48
|
+
AlertCircleIcon,
|
|
49
|
+
} from '@hugeicons/core-free-icons'
|
|
43
50
|
|
|
44
51
|
const events = ref([])
|
|
45
52
|
const loading = ref(false)
|
|
@@ -84,7 +91,9 @@ function generateDummyEventsForMonth(monthIndex, year) {
|
|
|
84
91
|
id: `weekend-${year}-${monthIndex}-${day}`,
|
|
85
92
|
title: 'Weekend Activity',
|
|
86
93
|
date: new Date(year, monthIndex, day, 10, 0),
|
|
87
|
-
description: 'Relaxing weekend activity'
|
|
94
|
+
description: 'Relaxing weekend activity',
|
|
95
|
+
icon: PartyIcon,
|
|
96
|
+
color: '#e8d5f5',
|
|
88
97
|
})
|
|
89
98
|
}
|
|
90
99
|
|
|
@@ -105,7 +114,9 @@ function generateDummyEventsForMonth(monthIndex, year) {
|
|
|
105
114
|
id: `event-${year}-${monthIndex}-${day}-${Math.random().toString(36).substr(2, 9)}`,
|
|
106
115
|
title: eventType.title,
|
|
107
116
|
date: new Date(year, monthIndex, day, hours, minutes),
|
|
108
|
-
description: `Scheduled ${eventType.title.toLowerCase()}
|
|
117
|
+
description: `Scheduled ${eventType.title.toLowerCase()}`,
|
|
118
|
+
icon: eventType.title === 'Team Meeting' ? MeetingRoomIcon : undefined,
|
|
119
|
+
color: eventType.title === 'Client Call' ? '#e0f2fe' : undefined,
|
|
109
120
|
})
|
|
110
121
|
}
|
|
111
122
|
}
|
|
@@ -116,7 +127,9 @@ function generateDummyEventsForMonth(monthIndex, year) {
|
|
|
116
127
|
title: 'Tech Conference',
|
|
117
128
|
startDate: new Date(year, monthIndex, 5, 9, 0),
|
|
118
129
|
endDate: new Date(year, monthIndex, 7, 17, 0),
|
|
119
|
-
description: 'Annual technology conference'
|
|
130
|
+
description: 'Annual technology conference',
|
|
131
|
+
icon: Calendar01Icon,
|
|
132
|
+
color: '#d6e8ff',
|
|
120
133
|
})
|
|
121
134
|
|
|
122
135
|
dummyEvents.push({
|
|
@@ -124,7 +137,9 @@ function generateDummyEventsForMonth(monthIndex, year) {
|
|
|
124
137
|
title: 'Vacation',
|
|
125
138
|
startDate: new Date(year, monthIndex, 15, 0, 0),
|
|
126
139
|
endDate: new Date(year, monthIndex, 18, 23, 59),
|
|
127
|
-
description: 'Family vacation time'
|
|
140
|
+
description: 'Family vacation time',
|
|
141
|
+
icon: BeachIcon,
|
|
142
|
+
color: '#fff0d6',
|
|
128
143
|
})
|
|
129
144
|
|
|
130
145
|
// Add some all-day events
|
|
@@ -132,7 +147,9 @@ function generateDummyEventsForMonth(monthIndex, year) {
|
|
|
132
147
|
id: `holiday-${year}-${monthIndex}`,
|
|
133
148
|
title: 'Public Holiday',
|
|
134
149
|
date: new Date(year, monthIndex, 1, 0, 0),
|
|
135
|
-
description: 'National holiday - office closed'
|
|
150
|
+
description: 'National holiday - office closed',
|
|
151
|
+
icon: PartyIcon,
|
|
152
|
+
color: '#ffe0e0',
|
|
136
153
|
})
|
|
137
154
|
|
|
138
155
|
if (daysInMonth >= 25) {
|
|
@@ -140,7 +157,9 @@ function generateDummyEventsForMonth(monthIndex, year) {
|
|
|
140
157
|
id: `deadline-${year}-${monthIndex}`,
|
|
141
158
|
title: 'Project Deadline',
|
|
142
159
|
date: new Date(year, monthIndex, 25, 0, 0),
|
|
143
|
-
description: 'Important project deadline'
|
|
160
|
+
description: 'Important project deadline',
|
|
161
|
+
icon: AlertCircleIcon,
|
|
162
|
+
color: '#fde8e8',
|
|
144
163
|
})
|
|
145
164
|
}
|
|
146
165
|
|
|
@@ -415,7 +434,7 @@ onMounted(() => {
|
|
|
415
434
|
}
|
|
416
435
|
|
|
417
436
|
/* Dark theme support */
|
|
418
|
-
|
|
437
|
+
html[data-theme="dark"] {
|
|
419
438
|
.date-picker-label {
|
|
420
439
|
color: #d1d5db;
|
|
421
440
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Section title="Navigation Grid" subtitle="Icon grid navigation for routes and actions">
|
|
3
|
+
<p>
|
|
4
|
+
<code>NavigationGrid</code> renders links from a nested
|
|
5
|
+
<code>Navigation</code> provider as a grid of icon buttons. Use it for
|
|
6
|
+
settings hubs, dashboards, or anywhere you need compact visual navigation.
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<Navigation ref="localNavigation">
|
|
10
|
+
<NavigationGrid />
|
|
11
|
+
</Navigation>
|
|
12
|
+
</Section>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup>
|
|
16
|
+
import { ref, onMounted } from 'vue'
|
|
17
|
+
import Section from '../components/Section.vue'
|
|
18
|
+
import NavigationGrid from '../components/NavigationGrid.vue'
|
|
19
|
+
import Navigation from '../components/Navigation.vue'
|
|
20
|
+
import {
|
|
21
|
+
ViewIcon,
|
|
22
|
+
EditIcon,
|
|
23
|
+
SecurityValidationIcon,
|
|
24
|
+
HomeIcon,
|
|
25
|
+
} from '@hugeicons/core-free-icons'
|
|
26
|
+
|
|
27
|
+
const localNavigation = ref(null)
|
|
28
|
+
|
|
29
|
+
onMounted(() => {
|
|
30
|
+
if (!localNavigation.value) {
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
localNavigation.value.addRouterLink('PageStructure', 'Page structure', {
|
|
35
|
+
description: 'Configure the example app shell',
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
localNavigation.value.addCallback('Profile', () => {
|
|
39
|
+
alert('Profile action')
|
|
40
|
+
}, {
|
|
41
|
+
icon: ViewIcon,
|
|
42
|
+
name: 'profile',
|
|
43
|
+
description: 'Example callback with icon and description',
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
localNavigation.value.addCallback('Editor', () => {
|
|
47
|
+
alert('Editor action')
|
|
48
|
+
}, {
|
|
49
|
+
icon: EditIcon,
|
|
50
|
+
name: 'editor',
|
|
51
|
+
description: 'Opens an example editor workflow',
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
localNavigation.value.addCallback('Security', () => {
|
|
55
|
+
alert('Security action')
|
|
56
|
+
}, {
|
|
57
|
+
icon: SecurityValidationIcon,
|
|
58
|
+
name: 'security',
|
|
59
|
+
description: 'Demonstrates a standard grid item',
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
localNavigation.value.addCallback('Preferences', () => {
|
|
63
|
+
alert('Preferences action')
|
|
64
|
+
}, {
|
|
65
|
+
icon: HomeIcon,
|
|
66
|
+
name: 'preferences',
|
|
67
|
+
description: 'Another callback entry in the grid',
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
localNavigation.value.addCallback('Alerts', () => {
|
|
71
|
+
alert('Alerts action')
|
|
72
|
+
}, {
|
|
73
|
+
icon: ViewIcon,
|
|
74
|
+
name: 'alerts',
|
|
75
|
+
description: 'Shows an indicator badge on the tile',
|
|
76
|
+
iconColor: '#e67e22',
|
|
77
|
+
indicator: true,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
localNavigation.value.addCallback('Billing', () => {
|
|
81
|
+
alert('This item is disabled')
|
|
82
|
+
}, {
|
|
83
|
+
icon: EditIcon,
|
|
84
|
+
name: 'billing',
|
|
85
|
+
description: 'Demonstrates a disabled grid item',
|
|
86
|
+
disabled: true,
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
</script>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Section
|
|
3
|
+
title="Page structure"
|
|
4
|
+
subtitle="Configure the example application shell and layout"
|
|
5
|
+
>
|
|
6
|
+
<p>
|
|
7
|
+
Use these controls to turn PicoCrank layout features on or off while you
|
|
8
|
+
explore the other examples. Changes apply immediately across the app.
|
|
9
|
+
</p>
|
|
10
|
+
</Section>
|
|
11
|
+
|
|
12
|
+
<Section title="Header" subtitle="Branding, search, and top navigation">
|
|
13
|
+
<dl>
|
|
14
|
+
<dt>Logo and title</dt>
|
|
15
|
+
<dd>
|
|
16
|
+
<label class="page-structure-control">
|
|
17
|
+
<input
|
|
18
|
+
type="checkbox"
|
|
19
|
+
:checked="brandingEnabled"
|
|
20
|
+
@change="toggleBrandingEnabled"
|
|
21
|
+
/>
|
|
22
|
+
<span>Show logo and title in the header</span>
|
|
23
|
+
</label>
|
|
24
|
+
</dd>
|
|
25
|
+
|
|
26
|
+
<dt>QuickSearch</dt>
|
|
27
|
+
<dd>
|
|
28
|
+
<label class="page-structure-control">
|
|
29
|
+
<input
|
|
30
|
+
type="checkbox"
|
|
31
|
+
:checked="quickSearchEnabled"
|
|
32
|
+
@change="toggleQuickSearchEnabled"
|
|
33
|
+
/>
|
|
34
|
+
<span>Show QuickSearch in the header toolbar</span>
|
|
35
|
+
</label>
|
|
36
|
+
</dd>
|
|
37
|
+
|
|
38
|
+
<dt>Top bar</dt>
|
|
39
|
+
<dd>
|
|
40
|
+
<label class="page-structure-control">
|
|
41
|
+
<input
|
|
42
|
+
type="checkbox"
|
|
43
|
+
:checked="topBarEnabled"
|
|
44
|
+
@change="toggleTopBarEnabled"
|
|
45
|
+
/>
|
|
46
|
+
<span>Show horizontal navigation links below the header</span>
|
|
47
|
+
</label>
|
|
48
|
+
</dd>
|
|
49
|
+
|
|
50
|
+
<dt>Breadcrumbs</dt>
|
|
51
|
+
<dd>
|
|
52
|
+
<label class="page-structure-control">
|
|
53
|
+
<input
|
|
54
|
+
type="checkbox"
|
|
55
|
+
:checked="breadcrumbsEnabled"
|
|
56
|
+
@change="toggleBreadcrumbsEnabled"
|
|
57
|
+
/>
|
|
58
|
+
<span>Show route breadcrumbs in the header</span>
|
|
59
|
+
</label>
|
|
60
|
+
</dd>
|
|
61
|
+
</dl>
|
|
62
|
+
</Section>
|
|
63
|
+
|
|
64
|
+
<Section title="Sidebar" subtitle="Primary navigation panel">
|
|
65
|
+
<dl>
|
|
66
|
+
<dt>Sidebar navigation</dt>
|
|
67
|
+
<dd>
|
|
68
|
+
<label class="page-structure-control">
|
|
69
|
+
<input
|
|
70
|
+
type="checkbox"
|
|
71
|
+
:checked="sidebarEnabled"
|
|
72
|
+
@change="toggleSidebarEnabled"
|
|
73
|
+
/>
|
|
74
|
+
<span>Show the sidebar navigation panel</span>
|
|
75
|
+
</label>
|
|
76
|
+
<p class="subtle">
|
|
77
|
+
When enabled, use the menu button in the header to open and pin the sidebar.
|
|
78
|
+
</p>
|
|
79
|
+
</dd>
|
|
80
|
+
</dl>
|
|
81
|
+
</Section>
|
|
82
|
+
</template>
|
|
83
|
+
|
|
84
|
+
<script setup>
|
|
85
|
+
import { inject } from 'vue'
|
|
86
|
+
import Section from '../components/Section.vue'
|
|
87
|
+
|
|
88
|
+
const sidebarEnabled = inject('sidebarEnabled')
|
|
89
|
+
const toggleSidebarEnabled = inject('toggleSidebarEnabled')
|
|
90
|
+
const brandingEnabled = inject('brandingEnabled')
|
|
91
|
+
const toggleBrandingEnabled = inject('toggleBrandingEnabled')
|
|
92
|
+
const quickSearchEnabled = inject('quickSearchEnabled')
|
|
93
|
+
const toggleQuickSearchEnabled = inject('toggleQuickSearchEnabled')
|
|
94
|
+
const topBarEnabled = inject('topBarEnabled')
|
|
95
|
+
const toggleTopBarEnabled = inject('toggleTopBarEnabled')
|
|
96
|
+
const breadcrumbsEnabled = inject('breadcrumbsEnabled')
|
|
97
|
+
const toggleBreadcrumbsEnabled = inject('toggleBreadcrumbsEnabled')
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<style scoped>
|
|
101
|
+
.page-structure-control {
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
gap: 0.5rem;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.page-structure-control + .subtle {
|
|
109
|
+
margin-top: 0.5rem;
|
|
110
|
+
}
|
|
111
|
+
</style>
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Section
|
|
3
|
+
title="Notifications"
|
|
4
|
+
subtitle="Block-level status messages using Femtocrank .notification with karma background classes"
|
|
5
|
+
>
|
|
6
|
+
<template #toolbar>
|
|
7
|
+
<button type="button" @click="resetNotifications">Reset notifications</button>
|
|
8
|
+
<button type="button" class="good" @click="showSampleNotification">Show sample</button>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<p>Click a notification to dismiss it.</p>
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
v-for="notification in visibleNotifications"
|
|
15
|
+
:key="notification.id"
|
|
16
|
+
class="notification show"
|
|
17
|
+
:class="notification.class"
|
|
18
|
+
role="status"
|
|
19
|
+
@click="dismissNotification(notification.id)"
|
|
20
|
+
>
|
|
21
|
+
<strong>{{ notification.label }}:</strong>
|
|
22
|
+
{{ notification.message }}
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<p v-if="visibleNotifications.length === 0" class="subtle">
|
|
26
|
+
All notifications dismissed. Use the toolbar to restore them.
|
|
27
|
+
</p>
|
|
28
|
+
</Section>
|
|
29
|
+
|
|
30
|
+
<Section
|
|
31
|
+
title="Inline notifications"
|
|
32
|
+
subtitle="Status embedded in content flow using .inline-notification with karma foreground or background classes"
|
|
33
|
+
>
|
|
34
|
+
<p class="fg-info show inline-notification">
|
|
35
|
+
<strong>INFO:</strong>
|
|
36
|
+
This is an informational message.
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
<p class="fg-note show inline-notification">
|
|
40
|
+
<strong>NOTE:</strong>
|
|
41
|
+
This is a note.
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
<p class="fg-success show inline-notification">
|
|
45
|
+
<strong>SUCCESS:</strong>
|
|
46
|
+
This is a success message.
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
<p class="fg-important show inline-notification">
|
|
50
|
+
<strong>IMPORTANT:</strong>
|
|
51
|
+
This is an important message.
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
<p class="fg-warning show inline-notification">
|
|
55
|
+
<strong>WARNING:</strong>
|
|
56
|
+
This is a warning message.
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
<p class="fg-severe show inline-notification">
|
|
60
|
+
<strong>SEVERE:</strong>
|
|
61
|
+
This is a severe message.
|
|
62
|
+
</p>
|
|
63
|
+
|
|
64
|
+
<p class="critical show inline-notification">
|
|
65
|
+
<strong>CRITICAL:</strong>
|
|
66
|
+
This is a critical message using a background karma class.
|
|
67
|
+
</p>
|
|
68
|
+
</Section>
|
|
69
|
+
|
|
70
|
+
<Section
|
|
71
|
+
title="Notification popups"
|
|
72
|
+
subtitle="Transient corner toasts via useNotificationPopups()"
|
|
73
|
+
>
|
|
74
|
+
<template #toolbar>
|
|
75
|
+
<button type="button" class="good" @click="showSuccessPopup">Success</button>
|
|
76
|
+
<button type="button" class="warning" @click="showWarningPopup">Warning</button>
|
|
77
|
+
<button type="button" @click="showLinkedPopup">With link</button>
|
|
78
|
+
<button type="button" @click="showStackedPopups">Stack three</button>
|
|
79
|
+
<button type="button" class="bad" @click="dismissAllPopups">Dismiss all</button>
|
|
80
|
+
</template>
|
|
81
|
+
|
|
82
|
+
<p>
|
|
83
|
+
Popups stack in the bottom-right, fade away after 5 seconds, and can be dismissed
|
|
84
|
+
manually or link to another page.
|
|
85
|
+
</p>
|
|
86
|
+
</Section>
|
|
87
|
+
|
|
88
|
+
<Section
|
|
89
|
+
title="In context"
|
|
90
|
+
subtitle="Inline notifications beside or below form fields"
|
|
91
|
+
>
|
|
92
|
+
<form class="status-form" @submit.prevent="submitForm">
|
|
93
|
+
<label for="status-email">Email</label>
|
|
94
|
+
<input
|
|
95
|
+
id="status-email"
|
|
96
|
+
v-model="formEmail"
|
|
97
|
+
type="email"
|
|
98
|
+
placeholder="you@example.com"
|
|
99
|
+
required
|
|
100
|
+
/>
|
|
101
|
+
|
|
102
|
+
<p
|
|
103
|
+
v-if="formMessage"
|
|
104
|
+
class="show inline-notification"
|
|
105
|
+
:class="formMessageClass"
|
|
106
|
+
role="status"
|
|
107
|
+
>
|
|
108
|
+
<strong>{{ formMessageLabel }}:</strong>
|
|
109
|
+
{{ formMessage }}
|
|
110
|
+
</p>
|
|
111
|
+
|
|
112
|
+
<fieldset>
|
|
113
|
+
<button type="submit" class="good">Save</button>
|
|
114
|
+
<button type="button" @click="clearForm">Clear</button>
|
|
115
|
+
</fieldset>
|
|
116
|
+
</form>
|
|
117
|
+
</Section>
|
|
118
|
+
</template>
|
|
119
|
+
|
|
120
|
+
<script setup>
|
|
121
|
+
import { ref, computed } from 'vue'
|
|
122
|
+
import Section from '../components/Section.vue'
|
|
123
|
+
import { useNotificationPopups } from '../composables/useNotificationPopups.js'
|
|
124
|
+
|
|
125
|
+
const { show: showPopup, dismissAll: dismissAllPopups } = useNotificationPopups()
|
|
126
|
+
|
|
127
|
+
const blockNotifications = ref([
|
|
128
|
+
{
|
|
129
|
+
id: 'info',
|
|
130
|
+
class: 'info',
|
|
131
|
+
label: 'INFO',
|
|
132
|
+
message: 'This is an informational message.',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 'note',
|
|
136
|
+
class: 'note',
|
|
137
|
+
label: 'NOTE',
|
|
138
|
+
message: 'This is a note.',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 'success',
|
|
142
|
+
class: 'success',
|
|
143
|
+
label: 'SUCCESS',
|
|
144
|
+
message: 'The operation completed successfully.',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'important',
|
|
148
|
+
class: 'important',
|
|
149
|
+
label: 'IMPORTANT',
|
|
150
|
+
message: 'Please review this before continuing.',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'warning',
|
|
154
|
+
class: 'warning',
|
|
155
|
+
label: 'WARNING',
|
|
156
|
+
message: 'This action may have side effects.',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: 'severe',
|
|
160
|
+
class: 'severe',
|
|
161
|
+
label: 'SEVERE',
|
|
162
|
+
message: 'A severe problem was detected.',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: 'critical',
|
|
166
|
+
class: 'critical',
|
|
167
|
+
label: 'CRITICAL',
|
|
168
|
+
message: 'Immediate attention is required.',
|
|
169
|
+
},
|
|
170
|
+
])
|
|
171
|
+
|
|
172
|
+
const initialNotificationIds = blockNotifications.value.map((notification) => notification.id)
|
|
173
|
+
|
|
174
|
+
const visibleNotificationIds = ref([...initialNotificationIds])
|
|
175
|
+
const sampleCounter = ref(0)
|
|
176
|
+
|
|
177
|
+
const visibleNotifications = computed(() =>
|
|
178
|
+
blockNotifications.value.filter((notification) => visibleNotificationIds.value.includes(notification.id))
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
const formEmail = ref('')
|
|
182
|
+
const formMessage = ref('')
|
|
183
|
+
const formMessageClass = ref('fg-note')
|
|
184
|
+
const formMessageLabel = ref('NOTE')
|
|
185
|
+
|
|
186
|
+
function dismissNotification(id) {
|
|
187
|
+
visibleNotificationIds.value = visibleNotificationIds.value.filter((visibleId) => visibleId !== id)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function resetNotifications() {
|
|
191
|
+
blockNotifications.value = blockNotifications.value.filter((notification) =>
|
|
192
|
+
initialNotificationIds.includes(notification.id)
|
|
193
|
+
)
|
|
194
|
+
visibleNotificationIds.value = [...initialNotificationIds]
|
|
195
|
+
sampleCounter.value = 0
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function showSampleNotification() {
|
|
199
|
+
sampleCounter.value += 1
|
|
200
|
+
const sample = {
|
|
201
|
+
id: `sample-${sampleCounter.value}`,
|
|
202
|
+
class: 'info',
|
|
203
|
+
label: 'INFO',
|
|
204
|
+
message: `Sample notification #${sampleCounter.value}.`,
|
|
205
|
+
}
|
|
206
|
+
blockNotifications.value.push(sample)
|
|
207
|
+
visibleNotificationIds.value = [...visibleNotificationIds.value, sample.id]
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function submitForm() {
|
|
211
|
+
if (!formEmail.value.trim()) {
|
|
212
|
+
formMessageClass.value = 'fg-warning'
|
|
213
|
+
formMessageLabel.value = 'WARNING'
|
|
214
|
+
formMessage.value = 'Please enter an email address.'
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
formMessageClass.value = 'fg-success'
|
|
219
|
+
formMessageLabel.value = 'SUCCESS'
|
|
220
|
+
formMessage.value = `Saved preferences for ${formEmail.value.trim()}.`
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function clearForm() {
|
|
224
|
+
formEmail.value = ''
|
|
225
|
+
formMessage.value = ''
|
|
226
|
+
formMessageClass.value = 'fg-note'
|
|
227
|
+
formMessageLabel.value = 'NOTE'
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function showSuccessPopup() {
|
|
231
|
+
showPopup({
|
|
232
|
+
label: 'SUCCESS',
|
|
233
|
+
class: 'success',
|
|
234
|
+
message: 'Your changes were saved.',
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function showWarningPopup() {
|
|
239
|
+
showPopup({
|
|
240
|
+
label: 'WARNING',
|
|
241
|
+
class: 'warning',
|
|
242
|
+
message: 'Disk space is running low.',
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function showLinkedPopup() {
|
|
247
|
+
showPopup({
|
|
248
|
+
label: 'INFO',
|
|
249
|
+
class: 'info',
|
|
250
|
+
message: 'A new table example is available.',
|
|
251
|
+
linkTo: { name: 'TableExample' },
|
|
252
|
+
linkLabel: 'Open table example',
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function showStackedPopups() {
|
|
257
|
+
const items = [
|
|
258
|
+
{ label: 'NOTE', class: 'note', message: 'First popup in the stack.' },
|
|
259
|
+
{ label: 'INFO', class: 'info', message: 'Second popup in the stack.' },
|
|
260
|
+
{ label: 'SUCCESS', class: 'success', message: 'Third popup in the stack.' },
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
for (const [index, item] of items.entries()) {
|
|
264
|
+
window.setTimeout(() => showPopup(item), index * 250)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
</script>
|
|
268
|
+
|
|
269
|
+
<style scoped>
|
|
270
|
+
.status-form {
|
|
271
|
+
max-width: 32rem;
|
|
272
|
+
}
|
|
273
|
+
</style>
|