picocrank 1.25.1 → 1.26.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.
Files changed (44) hide show
  1. package/eslint.config.js +68 -0
  2. package/package.json +13 -3
  3. package/router.js +74 -0
  4. package/styles/dark-theme.css +21 -9
  5. package/styles/picocrank-extensions.css +131 -2
  6. package/supplemental-themes/catppuccin-latte-frappe/theme.css +12 -22
  7. package/supplemental-themes/dracula-alucard/theme.css +12 -22
  8. package/supplemental-themes/gruvbox-dark-light/theme.css +12 -22
  9. package/supplemental-themes/waffles/theme.css +2 -12
  10. package/vue/components/Breadcrumbs.vue +192 -45
  11. package/vue/components/Calendar.vue +93 -180
  12. package/vue/components/DangerZone.vue +186 -0
  13. package/vue/components/Header.vue +22 -6
  14. package/vue/components/Login.vue +6 -7
  15. package/vue/components/NavigationGrid.vue +26 -204
  16. package/vue/components/NotificationBlock.vue +120 -0
  17. package/vue/components/NotificationPopup.vue +44 -41
  18. package/vue/components/QuickSearch.vue +21 -47
  19. package/vue/components/RadioGroup.vue +4 -4
  20. package/vue/components/ReadOnlyTextArea.vue +1 -1
  21. package/vue/components/SidebarNavLink.vue +3 -3
  22. package/vue/components/Table.vue +109 -81
  23. package/vue/components/TableColumnFilterPopover.vue +7 -17
  24. package/vue/components/TableColumnOptionsPopover.vue +22 -27
  25. package/vue/components/Tabs.vue +366 -82
  26. package/vue/components/TopBar.vue +2 -2
  27. package/vue/composables/tableColumnPriorities.js +33 -0
  28. package/vue/composables/tableFilters.js +1 -1
  29. package/vue/data/navSectionHubs.js +196 -0
  30. package/vue/data/routeBreadcrumbs.js +90 -0
  31. package/vue/examples/Admin.vue +12 -12
  32. package/vue/examples/App.vue +27 -36
  33. package/vue/examples/CalendarExample.vue +32 -22
  34. package/vue/examples/ExampleHeaderThemeSwitcher.vue +1 -1
  35. package/vue/examples/Login.vue +0 -3
  36. package/vue/examples/LoginExample.vue +2 -14
  37. package/vue/examples/NavSectionHub.vue +36 -0
  38. package/vue/examples/PageStructure.vue +6 -3
  39. package/vue/examples/StatusExample.vue +248 -129
  40. package/vue/examples/TableExample.vue +68 -67
  41. package/vue/examples/TabsExample.vue +200 -255
  42. package/vue/examples/ThemePreview.vue +21 -23
  43. package/vue/examples/ViewItem.vue +32 -5
  44. package/vue/examples/Welcome.vue +3 -112
@@ -0,0 +1,186 @@
1
+ <template>
2
+ <section class="with-header-and-content danger-zone">
3
+ <details
4
+ class="danger-zone-details"
5
+ :open="open"
6
+ @toggle="onToggle"
7
+ >
8
+ <summary class="danger-zone-summary section-header flex-row">
9
+ <div class="fg1 danger-zone-heading">
10
+ <h2 :title="subtitle || undefined" class="danger-zone-title">
11
+ <HugeiconsIcon
12
+ v-if="icon"
13
+ :icon="icon"
14
+ class="danger-zone-title-icon"
15
+ width="1.1em"
16
+ height="1.1em"
17
+ :strokeWidth="2"
18
+ aria-hidden="true"
19
+ />
20
+ <span>{{ title }}</span>
21
+ </h2>
22
+ <span v-if="subtitle" class="danger-zone-subtitle subtle">{{ subtitle }}</span>
23
+ <span v-if="description" class="danger-zone-description subtle">{{ description }}</span>
24
+ </div>
25
+ <span class="danger-zone-chevron" aria-hidden="true">▸</span>
26
+ </summary>
27
+ <Section
28
+ :title="title"
29
+ :subtitle="subtitle"
30
+ :padding="padding"
31
+ :icon="icon"
32
+ :id="sectionId"
33
+ classes="danger-zone-section"
34
+ >
35
+ <template v-if="$slots.toolbar" #toolbar>
36
+ <slot name="toolbar" />
37
+ </template>
38
+ <p v-if="warning" class="danger-zone-warning">{{ warning }}</p>
39
+ <slot />
40
+ </Section>
41
+ </details>
42
+ </section>
43
+ </template>
44
+
45
+ <script setup>
46
+ import { HugeiconsIcon } from '@hugeicons/vue'
47
+ import { Alert02Icon } from '@hugeicons/core-free-icons'
48
+ import Section from './Section.vue'
49
+
50
+ defineProps({
51
+ title: {
52
+ type: String,
53
+ default: 'Danger zone',
54
+ },
55
+ subtitle: {
56
+ type: String,
57
+ default: '',
58
+ },
59
+ description: {
60
+ type: String,
61
+ default: '',
62
+ },
63
+ warning: {
64
+ type: String,
65
+ default: 'These actions are permanent and cannot be undone.',
66
+ },
67
+ padding: {
68
+ type: Boolean,
69
+ default: true,
70
+ },
71
+ icon: {
72
+ type: [Object, Array, Function],
73
+ default: () => Alert02Icon,
74
+ },
75
+ sectionId: {
76
+ type: String,
77
+ default: '',
78
+ },
79
+ })
80
+
81
+ const open = defineModel('open', {
82
+ type: Boolean,
83
+ default: false,
84
+ })
85
+
86
+ function onToggle(event) {
87
+ open.value = event.target.open
88
+ }
89
+ </script>
90
+
91
+ <style scoped>
92
+ .danger-zone-details {
93
+ display: block;
94
+ }
95
+
96
+ .danger-zone-summary {
97
+ cursor: pointer;
98
+ list-style: none;
99
+ user-select: none;
100
+ transition: background-color 0.2s ease;
101
+ }
102
+
103
+ .danger-zone-heading {
104
+ display: flex;
105
+ flex-wrap: wrap;
106
+ align-items: center;
107
+ gap: 0.35em 0.75em;
108
+ min-width: 0;
109
+ }
110
+
111
+ .danger-zone-subtitle,
112
+ .danger-zone-description {
113
+ line-height: 1.4;
114
+ }
115
+
116
+ .danger-zone-chevron {
117
+ flex-shrink: 0;
118
+ margin-left: 0.75em;
119
+ transition: transform 0.15s ease;
120
+ }
121
+
122
+ .danger-zone-details[open] > .danger-zone-summary .danger-zone-chevron {
123
+ transform: rotate(90deg);
124
+ }
125
+
126
+ .danger-zone-details[open] > .danger-zone-summary {
127
+ background: var(--danger-zone-summary-hover-bg);
128
+ border-bottom-color: var(--danger-zone-border);
129
+ }
130
+
131
+ .danger-zone-details:not([open]) > .danger-zone-summary {
132
+ border-radius: 0.4em;
133
+ border-bottom: none;
134
+ }
135
+
136
+ .danger-zone-summary::-webkit-details-marker {
137
+ display: none;
138
+ }
139
+
140
+ .danger-zone-summary:hover {
141
+ background: var(--danger-zone-summary-hover-bg);
142
+ }
143
+
144
+ .danger-zone-summary:focus {
145
+ outline: none;
146
+ }
147
+
148
+ .danger-zone-summary:focus-visible {
149
+ outline: 2px solid var(--danger-zone-focus-outline);
150
+ outline-offset: -2px;
151
+ }
152
+
153
+ .danger-zone-title {
154
+ display: inline-flex;
155
+ align-items: center;
156
+ gap: 0.45em;
157
+ margin: 0;
158
+ padding-top: 0.5em;
159
+ padding-bottom: 0.5em;
160
+ }
161
+
162
+ .danger-zone-title-icon {
163
+ flex: 0 0 auto;
164
+ }
165
+
166
+ .danger-zone :deep(.danger-zone-section.with-header-and-content) {
167
+ margin: 0;
168
+ border: none;
169
+ border-radius: 0;
170
+ box-shadow: none;
171
+ background: transparent;
172
+ }
173
+
174
+ .danger-zone :deep(.danger-zone-section .section-header) {
175
+ display: none;
176
+ }
177
+
178
+ .danger-zone :deep(.danger-zone-section .section-content) {
179
+ border-top: none;
180
+ }
181
+
182
+ .danger-zone-warning {
183
+ margin: 0 0 1rem;
184
+ color: var(--danger-zone-warning-fg);
185
+ }
186
+ </style>
@@ -24,9 +24,9 @@
24
24
  </button>
25
25
  </div>
26
26
 
27
- <TopBar v-if="showTopBar" :navigation="navigation" />
27
+ <Breadcrumbs v-if="showBreadcrumbsBar" />
28
28
 
29
- <Breadcrumbs v-if="breadcrumbs" />
29
+ <TopBar v-if="showTopBar" :navigation="topBarNavigation" />
30
30
 
31
31
  <div class = "fg1"></div>
32
32
 
@@ -138,15 +138,23 @@
138
138
  type: Object,
139
139
  default: null,
140
140
  },
141
+ topBarNavigation: {
142
+ type: Object,
143
+ default: null,
144
+ },
141
145
  });
142
146
 
143
147
  const fallbackSidebar = ref(null);
144
148
 
145
- const { showTopBar, showSidebarChrome, needsFallbackSidebar } = useResponsiveNav(
149
+ const { showTopBar, showSidebarChrome, needsFallbackSidebar, isMobile } = useResponsiveNav(
146
150
  () => props.sidebarEnabled,
147
151
  () => props.topBarEnabled,
148
152
  );
149
153
 
154
+ const showBreadcrumbsBar = computed(
155
+ () => props.breadcrumbs && !isMobile.value,
156
+ );
157
+
150
158
  function toggleNavSidebar() {
151
159
  if (needsFallbackSidebar.value) {
152
160
  fallbackSidebar.value?.toggle();
@@ -165,8 +173,15 @@
165
173
  </script>
166
174
 
167
175
  <style scoped>
176
+ #sidebar-button {
177
+ flex-shrink: 0;
178
+ width: 14em;
179
+ min-width: 14em;
180
+ }
181
+
168
182
  #sidebar-button.disabled-branding {
169
183
  width: auto;
184
+ min-width: 0;
170
185
  }
171
186
 
172
187
  .logo-home-link {
@@ -194,6 +209,7 @@
194
209
  .header-actions {
195
210
  align-self: stretch;
196
211
  gap: 0.25rem;
212
+ flex-shrink: 0;
197
213
  }
198
214
 
199
215
  .theme-toggle {
@@ -205,7 +221,7 @@
205
221
  .header-actions :deep(button) {
206
222
  border: 0;
207
223
  border-radius: 0;
208
- color: var(--header-fg-color, var(--header-text-color, #fff));
224
+ color: var(--header-fg-color);
209
225
  background-color: transparent;
210
226
  align-self: stretch;
211
227
  display: inline-flex;
@@ -227,7 +243,7 @@
227
243
  #sidebar-toggler-button {
228
244
  border: 0;
229
245
  border-radius: 0;
230
- color: var(--header-fg-color, var(--header-text-color, #fff));
246
+ color: var(--header-fg-color);
231
247
  background-color: transparent;
232
248
  align-self: stretch;
233
249
  display: inline-flex;
@@ -260,7 +276,7 @@ header .search-trigger,
260
276
  header .search-trigger.neutral {
261
277
  border: 0;
262
278
  border-radius: 0;
263
- color: var(--header-fg-color, var(--header-text-color, #fff));
279
+ color: var(--header-fg-color);
264
280
  background-color: transparent;
265
281
  align-self: stretch;
266
282
  height: 100%;
@@ -247,10 +247,9 @@ defineExpose({
247
247
  box-sizing: border-box;
248
248
  }
249
249
 
250
- .local-login-form input:focus {
251
- outline: none;
252
- border-color: var(--primary-color, #007bff);
253
- box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
250
+ .local-login-form input:focus-visible {
251
+ outline: 2px solid var(--login-input-focus-outline);
252
+ outline-offset: 2px;
254
253
  }
255
254
 
256
255
  .local-login-form button {
@@ -303,8 +302,8 @@ defineExpose({
303
302
 
304
303
  .error-message {
305
304
  padding: 0.75rem;
306
- background-color: var(--error-bg, #fee);
307
- color: var(--error-color, #c33);
305
+ background-color: var(--login-error-bg);
306
+ color: var(--login-error-fg);
308
307
  border-radius: 4px;
309
308
  font-size: 0.9em;
310
309
  margin-top: 0.5rem;
@@ -314,7 +313,7 @@ defineExpose({
314
313
  .no-providers {
315
314
  padding: 2rem;
316
315
  text-align: center;
317
- color: var(--text-muted, #666);
316
+ color: var(--login-muted-fg);
318
317
  }
319
318
 
320
319
  @media (min-width: 768px) {
@@ -19,20 +19,22 @@
19
19
  class="nav-button-indicator"
20
20
  aria-label="Requires attention"
21
21
  />
22
- <div class="nav-button-icon" :style="getIconStyle(link)">
23
- <HugeiconsIcon
24
- :icon="link.icon"
25
- :width="iconSize"
26
- :height="iconSize"
27
- />
28
- </div>
29
- <div class="nav-button-label">{{ link.title }}</div>
30
- <div v-if="link.description" class="nav-button-description">
31
- {{ link.description }}
22
+ <div class="nav-button-heading">
23
+ <div class="nav-button-icon" :style="getIconStyle(link)">
24
+ <HugeiconsIcon
25
+ :icon="link.icon"
26
+ :width="iconSize"
27
+ :height="iconSize"
28
+ />
32
29
  </div>
30
+ <div class="nav-button-label">{{ link.title }}</div>
31
+ </div>
32
+ <div v-if="link.description" class="nav-button-description">
33
+ {{ link.description }}
34
+ </div>
33
35
  </button>
34
36
  </div>
35
- <div v-if="filteredLinks.length === 0" class="no-links">
37
+ <div v-if="filteredLinks.length === 0" class="navigation-grid-empty">
36
38
  <p>No navigation links available</p>
37
39
  </div>
38
40
  </div>
@@ -40,7 +42,7 @@
40
42
 
41
43
  <script setup>
42
44
  import { computed, inject, ref } from 'vue';
43
- import { useRouter, useRoute } from 'vue-router';
45
+ import { useRouter } from 'vue-router';
44
46
  import { HugeiconsIcon } from '@hugeicons/vue';
45
47
 
46
48
  const props = defineProps({
@@ -63,7 +65,7 @@ const props = defineProps({
63
65
  // Icon size
64
66
  iconSize: {
65
67
  type: String,
66
- default: '2em'
68
+ default: '1.5em'
67
69
  },
68
70
  // Exclude separator and html items
69
71
  excludeNonButtons: {
@@ -73,7 +75,6 @@ const props = defineProps({
73
75
  });
74
76
 
75
77
  const router = useRouter();
76
- const route = useRoute();
77
78
 
78
79
  // Inject navigation from Navigation component
79
80
  const navigation = inject('navigation', null);
@@ -157,203 +158,24 @@ function handleLinkClick(link) {
157
158
  </script>
158
159
 
159
160
  <style scoped>
160
- .navigation-grid-container {
161
- width: 100%;
162
- }
163
-
164
- .navigation-grid {
165
- display: grid;
166
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
167
- gap: 1rem;
168
- padding: 1rem 0;
169
- }
170
-
171
- .navigation-grid.compact {
172
- grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
173
- gap: 0.75rem;
174
- }
175
-
176
- .nav-button {
161
+ .nav-button-heading {
177
162
  display: flex;
178
- flex-direction: column;
179
- align-items: top;
180
- justify-content: flex-start;
181
- gap: 0.75rem;
182
- padding: 1.25rem 0.75rem;
183
- background: transparent;
184
- border: 1px solid var(--border-color, #e1e5e9);
185
- border-radius: 8px;
186
- cursor: pointer;
187
- transition: all 0.2s ease;
188
- text-align: left;
189
- position: relative;
190
- }
191
-
192
- .navigation-grid.compact .nav-button {
193
- padding: 0.75rem 0.5rem;
163
+ align-items: center;
194
164
  gap: 0.5rem;
165
+ width: 100%;
166
+ min-width: 0;
195
167
  }
196
168
 
197
- .nav-button:hover {
198
- border-color: var(--primary-color, #007bff);
199
- transform: translateY(-2px);
200
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
201
- }
202
-
203
- .nav-button:active {
204
- transform: translateY(0);
205
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
206
- }
207
-
208
- .nav-button.active {
209
- background: var(--primary-color, #007bff);
210
- border-color: var(--primary-color, #007bff);
211
- color: #fff;
212
- }
213
-
214
- .nav-button.active .nav-button-icon {
215
- color: #fff;
216
- }
217
-
218
- .nav-button-icon {
219
- display: flex;
220
- align-items: start;
221
- justify-content: start;
222
- color: var(--primary-color, #007bff);
223
- transition: color 0.2s ease;
224
- flex-shrink: 0;
225
- }
226
-
227
- .nav-button.active .nav-button-icon {
228
- color: #fff;
229
- }
230
-
231
- .nav-button:disabled,
232
- .nav-button.disabled {
233
- cursor: not-allowed;
234
- opacity: 0.6;
235
- }
236
-
237
- .nav-button:disabled:hover,
238
- .nav-button.disabled:hover {
239
- border-color: var(--border-color, #e1e5e9);
240
- transform: none;
241
- box-shadow: none;
242
- background: transparent;
243
- }
244
-
245
- .nav-button:disabled .nav-button-icon,
246
- .nav-button.disabled .nav-button-icon {
247
- color: var(--text-muted, #999);
248
- }
249
-
250
- .nav-button-indicator {
251
- position: absolute;
252
- top: 0.75rem;
253
- right: 0.75rem;
254
- width: 0.625rem;
255
- height: 0.625rem;
256
- border-radius: 50%;
257
- background: var(--indicator-color, #dc3545);
258
- box-shadow: 0 0 0 2px var(--background-color, #fff);
259
- flex-shrink: 0;
260
- }
261
-
262
- .nav-button-count {
263
- position: absolute;
264
- top: 0.5rem;
265
- right: 0.5rem;
266
- min-width: 1.25rem;
267
- padding: 0.1rem 0.4rem;
268
- border-radius: 999px;
269
- background: var(--indicator-color, #dc3545);
270
- color: #fff;
271
- font-size: 0.75em;
272
- font-weight: 600;
273
- line-height: 1.2;
274
- text-align: center;
275
- box-shadow: 0 0 0 2px var(--background-color, #fff);
276
- flex-shrink: 0;
277
- }
278
-
279
- .navigation-grid.compact .nav-button-indicator {
280
- top: 0.5rem;
281
- right: 0.5rem;
282
- }
283
-
284
- .navigation-grid.compact .nav-button-count {
285
- top: 0.35rem;
286
- right: 0.35rem;
287
- }
288
-
289
- .nav-button.active .nav-button-indicator,
290
- .nav-button.active .nav-button-count {
291
- box-shadow: 0 0 0 2px var(--primary-color, #007bff);
292
- }
293
-
294
- .nav-button-label {
295
- font-weight: bold;
296
- font-size: 1.125rem;
297
- word-break: break-word;
298
- text-align: left;
299
- line-height: 1.2;
300
- }
301
-
302
- .nav-button.active .nav-button-label {
303
- color: #fff;
304
- }
305
-
306
- .nav-button-description {
307
- font-weight: normal;
308
- color: var(--text-muted, #666);
309
- word-break: break-word;
310
- text-align: left;
311
- line-height: 1.5;
312
- }
313
-
314
- .nav-button.active .nav-button-description {
315
- color: rgba(255, 255, 255, 0.9);
316
- }
317
-
318
- .no-links {
319
- padding: 2rem;
320
- text-align: center;
321
- color: var(--text-muted, #666);
169
+ .navigation-grid.compact .nav-button-heading {
170
+ gap: 0.4rem;
322
171
  }
323
172
 
324
- /* Dark theme support */
325
- html[data-theme="dark"] {
326
- .nav-button {
327
- background: transparent;
328
- }
329
-
330
- .nav-button:hover {
331
- background: var(--hover-background-color, rgba(96, 165, 250, 0.1));
332
- border-color: var(--primary-color, #60a5fa);
333
- }
334
-
335
- .nav-button.active {
336
- background: var(--primary-color, #60a5fa);
337
- border-color: var(--primary-color, #60a5fa);
338
- }
339
-
340
- .nav-button-description {
341
- color: var(--text-muted, #999);
342
- }
343
-
344
- .nav-button-icon {
345
- color: var(--primary-color, #60a5fa);
346
- }
173
+ .nav-button-heading .nav-button-icon {
174
+ align-items: center;
347
175
  }
348
176
 
349
- @media (max-width: 768px) {
350
- .navigation-grid {
351
- grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
352
- gap: 0.75rem;
353
- }
354
-
355
- .nav-button {
356
- padding: 1rem 0.5rem;
357
- }
177
+ .nav-button-heading .nav-button-label {
178
+ flex: 1;
179
+ min-width: 0;
358
180
  }
359
181
  </style>
@@ -0,0 +1,120 @@
1
+ <template>
2
+ <div
3
+ :class="rootClasses"
4
+ :role="role"
5
+ @click="handleClick"
6
+ >
7
+ <span class="notification-key">{{ displayLabel }}</span>
8
+ <span class="notification-body">
9
+ <slot>
10
+ <template v-if="hasLink">
11
+ <template v-if="messagePrefix">{{ messagePrefix }}</template><RouterLink
12
+ v-if="linkTo"
13
+ :to="linkTo"
14
+ @click.stop
15
+ >{{ linkLabel }}</RouterLink><a
16
+ v-else
17
+ :href="linkHref || '#'"
18
+ @click.stop
19
+ >{{ linkLabel }}</a><template v-if="messageSuffix">{{ messageSuffix }}</template>
20
+ </template>
21
+ <template v-else>{{ message }}</template>
22
+ </slot>
23
+ </span>
24
+ </div>
25
+ </template>
26
+
27
+ <script setup>
28
+ import { computed } from 'vue'
29
+ import { RouterLink } from 'vue-router'
30
+
31
+ const TYPE_LABELS = {
32
+ info: 'INFO:',
33
+ note: 'NOTE:',
34
+ success: 'SUCCESS:',
35
+ good: 'SUCCESS:',
36
+ warning: 'WARNING:',
37
+ severe: 'SEVERE:',
38
+ critical: 'CRITICAL:',
39
+ error: 'CRITICAL:',
40
+ bad: 'CRITICAL:',
41
+ old: 'OLD:',
42
+ }
43
+
44
+ const props = defineProps({
45
+ type: {
46
+ type: String,
47
+ default: 'info',
48
+ validator: (value) => (
49
+ ['info', 'note', 'success', 'good', 'warning', 'severe', 'critical', 'error', 'bad', 'old'].includes(value)
50
+ ),
51
+ },
52
+ label: {
53
+ type: String,
54
+ default: '',
55
+ },
56
+ message: {
57
+ type: String,
58
+ default: '',
59
+ },
60
+ messagePrefix: {
61
+ type: String,
62
+ default: '',
63
+ },
64
+ linkLabel: {
65
+ type: String,
66
+ default: '',
67
+ },
68
+ linkHref: {
69
+ type: String,
70
+ default: '',
71
+ },
72
+ linkTo: {
73
+ type: [String, Object],
74
+ default: null,
75
+ },
76
+ messageSuffix: {
77
+ type: String,
78
+ default: '',
79
+ },
80
+ clickable: {
81
+ type: Boolean,
82
+ default: false,
83
+ },
84
+ dismissible: {
85
+ type: Boolean,
86
+ default: false,
87
+ },
88
+ role: {
89
+ type: String,
90
+ default: 'status',
91
+ },
92
+ })
93
+
94
+ const emit = defineEmits(['click', 'dismiss'])
95
+
96
+ const displayLabel = computed(() => {
97
+ const trimmed = props.label.trim()
98
+ if (trimmed) {
99
+ return trimmed.endsWith(':') ? trimmed : `${trimmed}:`
100
+ }
101
+ return TYPE_LABELS[props.type]
102
+ })
103
+
104
+ const hasLink = computed(() => Boolean(
105
+ props.linkLabel && (props.linkHref || props.linkTo)
106
+ ))
107
+
108
+ const rootClasses = computed(() => ({
109
+ notification: true,
110
+ [props.type]: true,
111
+ clickable: props.clickable || props.dismissible,
112
+ }))
113
+
114
+ function handleClick(event) {
115
+ emit('click', event)
116
+ if (props.dismissible) {
117
+ emit('dismiss', event)
118
+ }
119
+ }
120
+ </script>