picocrank 1.18.2 → 1.19.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/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 +28 -2
- 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 +33 -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
package/index.html
CHANGED
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
<meta name = "viewport" content = "width=device-width, initial-scale=1.0, user-scalable=no" />
|
|
8
8
|
|
|
9
|
+
<script>
|
|
10
|
+
(function () {
|
|
11
|
+
var key = 'picocrank-theme';
|
|
12
|
+
var stored = localStorage.getItem(key);
|
|
13
|
+
var theme = stored === 'light' || stored === 'dark'
|
|
14
|
+
? stored
|
|
15
|
+
: (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
|
16
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
17
|
+
})();
|
|
18
|
+
</script>
|
|
9
19
|
</head>
|
|
10
20
|
|
|
11
21
|
<body>
|
package/package.json
CHANGED
package/router.js
CHANGED
|
@@ -7,7 +7,11 @@ import { SecurityValidationIcon } from '@hugeicons/core-free-icons';
|
|
|
7
7
|
import { CalendarIcon } from '@hugeicons/core-free-icons';
|
|
8
8
|
import { EditIcon } from '@hugeicons/core-free-icons';
|
|
9
9
|
import { Menu01Icon } from '@hugeicons/core-free-icons';
|
|
10
|
+
import { Navigation01Icon } from '@hugeicons/core-free-icons';
|
|
10
11
|
import { ClipboardCopyIcon } from '@hugeicons/core-free-icons';
|
|
12
|
+
import { CursorPointer01Icon } from '@hugeicons/core-free-icons';
|
|
13
|
+
import { LayoutGridIcon } from '@hugeicons/core-free-icons';
|
|
14
|
+
import { StatusIcon } from '@hugeicons/core-free-icons';
|
|
11
15
|
|
|
12
16
|
const routes = [
|
|
13
17
|
{
|
|
@@ -19,13 +23,22 @@ const routes = [
|
|
|
19
23
|
icon: HomeIcon,
|
|
20
24
|
}
|
|
21
25
|
},
|
|
26
|
+
{
|
|
27
|
+
name: 'PageStructure',
|
|
28
|
+
path: '/page-structure',
|
|
29
|
+
component: () => import('./vue/views/PageStructure.vue'),
|
|
30
|
+
meta: {
|
|
31
|
+
title: 'Page structure',
|
|
32
|
+
icon: LayoutGridIcon,
|
|
33
|
+
}
|
|
34
|
+
},
|
|
22
35
|
{
|
|
23
36
|
name: 'TableExample',
|
|
24
37
|
path: '/table-example',
|
|
25
38
|
title: 'Table Example',
|
|
26
39
|
component: () => import('./vue/views/TableExample.vue'),
|
|
27
40
|
meta: {
|
|
28
|
-
title: 'Table
|
|
41
|
+
title: 'Table',
|
|
29
42
|
icon: TableIcon,
|
|
30
43
|
}
|
|
31
44
|
},
|
|
@@ -35,7 +48,7 @@ const routes = [
|
|
|
35
48
|
component: () => import('./vue/views/ViewItem.vue'),
|
|
36
49
|
props: true,
|
|
37
50
|
meta: {
|
|
38
|
-
title: 'View
|
|
51
|
+
title: 'View item',
|
|
39
52
|
icon: ViewIcon,
|
|
40
53
|
}
|
|
41
54
|
},
|
|
@@ -54,7 +67,7 @@ const routes = [
|
|
|
54
67
|
component: () => import('./vue/views/CalendarExample.vue'),
|
|
55
68
|
props: true,
|
|
56
69
|
meta: {
|
|
57
|
-
title: 'Calendar
|
|
70
|
+
title: 'Calendar',
|
|
58
71
|
icon: CalendarIcon,
|
|
59
72
|
}
|
|
60
73
|
},
|
|
@@ -67,12 +80,21 @@ const routes = [
|
|
|
67
80
|
icon: SecurityValidationIcon,
|
|
68
81
|
}
|
|
69
82
|
},
|
|
83
|
+
{
|
|
84
|
+
name: 'ButtonsExample',
|
|
85
|
+
path: '/buttons-example',
|
|
86
|
+
component: () => import('./vue/views/ButtonsExample.vue'),
|
|
87
|
+
meta: {
|
|
88
|
+
title: 'Buttons',
|
|
89
|
+
icon: CursorPointer01Icon,
|
|
90
|
+
}
|
|
91
|
+
},
|
|
70
92
|
{
|
|
71
93
|
name: 'FormExample',
|
|
72
94
|
path: '/form-example',
|
|
73
95
|
component: () => import('./vue/views/FormExample.vue'),
|
|
74
96
|
meta: {
|
|
75
|
-
title: '
|
|
97
|
+
title: 'Forms',
|
|
76
98
|
icon: EditIcon,
|
|
77
99
|
}
|
|
78
100
|
},
|
|
@@ -81,26 +103,35 @@ const routes = [
|
|
|
81
103
|
path: '/readonly-textarea-example',
|
|
82
104
|
component: () => import('./vue/views/ReadOnlyTextAreaExample.vue'),
|
|
83
105
|
meta: {
|
|
84
|
-
title: '
|
|
106
|
+
title: 'Read-only output',
|
|
85
107
|
icon: ClipboardCopyIcon,
|
|
86
108
|
}
|
|
87
109
|
},
|
|
110
|
+
{
|
|
111
|
+
name: 'StatusExample',
|
|
112
|
+
path: '/status-example',
|
|
113
|
+
component: () => import('./vue/views/StatusExample.vue'),
|
|
114
|
+
meta: {
|
|
115
|
+
title: 'Status & notifications',
|
|
116
|
+
icon: StatusIcon,
|
|
117
|
+
}
|
|
118
|
+
},
|
|
88
119
|
{
|
|
89
120
|
name: 'TabsExample',
|
|
90
121
|
path: '/tabs-example',
|
|
91
122
|
component: () => import('./vue/views/TabsExample.vue'),
|
|
92
123
|
meta: {
|
|
93
|
-
title: 'Tabs
|
|
124
|
+
title: 'Tabs',
|
|
94
125
|
icon: Menu01Icon,
|
|
95
126
|
}
|
|
96
127
|
},
|
|
97
128
|
{
|
|
98
|
-
name: '
|
|
99
|
-
path: '/
|
|
100
|
-
component: () => import('./vue/views/
|
|
129
|
+
name: 'NavigationGridExample',
|
|
130
|
+
path: '/navigation-grid',
|
|
131
|
+
component: () => import('./vue/views/NavigationGridExample.vue'),
|
|
101
132
|
meta: {
|
|
102
|
-
title: '
|
|
103
|
-
icon:
|
|
133
|
+
title: 'Navigation Grid',
|
|
134
|
+
icon: Navigation01Icon,
|
|
104
135
|
}
|
|
105
136
|
},
|
|
106
137
|
{
|
|
@@ -108,7 +139,7 @@ const routes = [
|
|
|
108
139
|
path: '/login-example',
|
|
109
140
|
component: () => import('./vue/views/LoginExample.vue'),
|
|
110
141
|
meta: {
|
|
111
|
-
title: 'Login
|
|
142
|
+
title: 'Login',
|
|
112
143
|
icon: SecurityValidationIcon,
|
|
113
144
|
}
|
|
114
145
|
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
html[data-theme="dark"] {
|
|
3
|
+
color-scheme: dark;
|
|
4
|
+
|
|
5
|
+
--border-color: #595959;
|
|
6
|
+
--hover-background-color: #1d345c;
|
|
7
|
+
--header-hover-background-color: #1d345c;
|
|
8
|
+
--standout-bg-color: #1f1f1f;
|
|
9
|
+
--disabled-background-color: #333;
|
|
10
|
+
--disabled-text-color: #999;
|
|
11
|
+
|
|
12
|
+
& header {
|
|
13
|
+
background-color: black;
|
|
14
|
+
box-shadow: 0px 0px 1em #1c1c1c;
|
|
15
|
+
border-bottom: 1px solid #222;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
& body {
|
|
19
|
+
background-color: #262626;
|
|
20
|
+
color: #ddd;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
& section {
|
|
24
|
+
background-color: #343434;
|
|
25
|
+
color: #ddd;
|
|
26
|
+
box-shadow: 0 0 .5em #121212;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
& section .section-header {
|
|
30
|
+
background-color: #343434;
|
|
31
|
+
border-bottom-color: var(--border-color);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
& section .section-subheader {
|
|
35
|
+
border-bottom-color: var(--border-color);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
& button,
|
|
39
|
+
& .button {
|
|
40
|
+
color: #ddd;
|
|
41
|
+
border: 1px solid var(--border-color);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
& button:hover,
|
|
45
|
+
& .button:hover,
|
|
46
|
+
& .button:visited {
|
|
47
|
+
background-color: var(--hover-background-color);
|
|
48
|
+
color: white;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
& footer a,
|
|
52
|
+
& footer a:visited {
|
|
53
|
+
color: lightblue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
& header ul[role="menubar"] a,
|
|
57
|
+
& header ul[role="menubar"] a:visited {
|
|
58
|
+
background-color: black;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
& header ul[role="menubar"] a:hover {
|
|
62
|
+
background-color: var(--hover-background-color);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
& header div > ul > li > a {
|
|
66
|
+
background-color: black;
|
|
67
|
+
color: white;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
& ul[role="menubar"] div ul a:hover {
|
|
71
|
+
background-color: var(--hover-background-color);
|
|
72
|
+
color: white;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
& ul[role="menubar"] li:hover {
|
|
76
|
+
background-color: var(--hover-background-color);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
& label:has(input[type="checkbox"]):hover,
|
|
80
|
+
& label:has(input[type="radio"]):hover {
|
|
81
|
+
background-color: var(--hover-background-color);
|
|
82
|
+
color: white;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
& label:has(input:checked) {
|
|
86
|
+
background-color: #488448;
|
|
87
|
+
color: white;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
& label:has(input:checked):hover {
|
|
91
|
+
background-color: #3a6f3a;
|
|
92
|
+
color: white;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
& a,
|
|
96
|
+
& a:visited,
|
|
97
|
+
& aside a,
|
|
98
|
+
& aside a:visited {
|
|
99
|
+
color: lightblue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
& table,
|
|
103
|
+
& th,
|
|
104
|
+
& td {
|
|
105
|
+
border-color: var(--border-color) !important;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
& table.row-hover tr:hover td {
|
|
109
|
+
background-color: var(--hover-background-color);
|
|
110
|
+
color: white;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
& footer span {
|
|
114
|
+
background-color: #444;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
& input[type="text"],
|
|
118
|
+
& input[type="email"],
|
|
119
|
+
& input[type="password"],
|
|
120
|
+
& input[type="url"],
|
|
121
|
+
& input[type="number"],
|
|
122
|
+
& input[type="color"],
|
|
123
|
+
& input[type="datetime-local"] {
|
|
124
|
+
background-color: #4e4e4e;
|
|
125
|
+
color: #ddd;
|
|
126
|
+
border-color: var(--border-color);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
& select {
|
|
130
|
+
background-color: #4e4e4e;
|
|
131
|
+
color: #ddd;
|
|
132
|
+
border-color: var(--border-color);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
& textarea {
|
|
136
|
+
background-color: #4e4e4e;
|
|
137
|
+
color: #ddd;
|
|
138
|
+
border-color: var(--border-color);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
& .grid-boxed .stat-display {
|
|
142
|
+
background-color: var(--standout-bg-color);
|
|
143
|
+
border-color: var(--border-color) !important;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
& pre {
|
|
147
|
+
background-color: var(--standout-bg-color);
|
|
148
|
+
color: #ddd;
|
|
149
|
+
border: 1px solid var(--border-color);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
& .radio-group {
|
|
153
|
+
border-color: var(--border-color);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
& .radio-group label {
|
|
157
|
+
background-color: #555;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
& aside {
|
|
161
|
+
background-color: #000;
|
|
162
|
+
box-shadow: 0 0 .5em #121212;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
& aside ul li a:hover {
|
|
166
|
+
background-color: var(--hover-background-color);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
& dt {
|
|
170
|
+
border-color: var(--border-color);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
& .annotation,
|
|
174
|
+
& .tag,
|
|
175
|
+
& .inline-notification {
|
|
176
|
+
background-color: var(--standout-bg-color);
|
|
177
|
+
color: #ddd;
|
|
178
|
+
border-color: var(--border-color);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
& .annotation-val {
|
|
182
|
+
color: #ddd;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
html[data-theme="light"] {
|
|
188
|
+
color-scheme: light;
|
|
189
|
+
}
|
package/styles.css
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, computed, watch, onUnmounted } from 'vue'
|
|
3
|
+
import { HugeiconsIcon } from '@hugeicons/vue'
|
|
3
4
|
|
|
4
5
|
export interface CalendarEvent {
|
|
5
6
|
id: string | number
|
|
@@ -7,6 +8,10 @@ export interface CalendarEvent {
|
|
|
7
8
|
startDate?: Date | string | null
|
|
8
9
|
endDate?: Date | string | null
|
|
9
10
|
date?: Date | string | null
|
|
11
|
+
/** Optional Hugeicons icon (from @hugeicons/core-free-icons). */
|
|
12
|
+
icon?: unknown
|
|
13
|
+
/** Optional CSS color overriding the default event background. */
|
|
14
|
+
color?: string
|
|
10
15
|
[key: string]: any
|
|
11
16
|
}
|
|
12
17
|
|
|
@@ -228,6 +233,11 @@ function formatEventTimeDefault(event: CalendarEvent, date: Date): string {
|
|
|
228
233
|
return 'All day'
|
|
229
234
|
}
|
|
230
235
|
|
|
236
|
+
function getEventStyle(event: CalendarEvent): Record<string, string> | undefined {
|
|
237
|
+
if (!event.color) return undefined
|
|
238
|
+
return { '--event-color': event.color }
|
|
239
|
+
}
|
|
240
|
+
|
|
231
241
|
// Get ordinal suffix for a number (1st, 2nd, 3rd, 4th, etc.)
|
|
232
242
|
function getOrdinalSuffix(day: number): string {
|
|
233
243
|
const j = day % 10
|
|
@@ -655,8 +665,10 @@ watch([currentMonth, currentYear], () => {
|
|
|
655
665
|
'multi-day-start': isMultiDayEvent(event) && getMultiDayPosition(event, day.date) === 'start',
|
|
656
666
|
'multi-day-middle': isMultiDayEvent(event) && getMultiDayPosition(event, day.date) === 'middle',
|
|
657
667
|
'multi-day-end': isMultiDayEvent(event) && getMultiDayPosition(event, day.date) === 'end',
|
|
658
|
-
'drag-source': draggingEventId === event.id
|
|
668
|
+
'drag-source': draggingEventId === event.id,
|
|
669
|
+
'has-custom-color': Boolean(event.color)
|
|
659
670
|
}"
|
|
671
|
+
:style="getEventStyle(event)"
|
|
660
672
|
:draggable="eventDragEnabled"
|
|
661
673
|
@dragstart.stop="onEventDragStart(event, day.date, $event)"
|
|
662
674
|
@dragend.stop="onEventDragEnd"
|
|
@@ -666,6 +678,13 @@ watch([currentMonth, currentYear], () => {
|
|
|
666
678
|
<slot name="event" :event="event" :date="day.date" :position="getMultiDayPosition(event, day.date)">
|
|
667
679
|
<div class="event-content">
|
|
668
680
|
<div class="event-title">
|
|
681
|
+
<HugeiconsIcon
|
|
682
|
+
v-if="event.icon"
|
|
683
|
+
:icon="event.icon"
|
|
684
|
+
class="event-icon"
|
|
685
|
+
width="0.9em"
|
|
686
|
+
height="0.9em"
|
|
687
|
+
/>
|
|
669
688
|
{{ event.title }}
|
|
670
689
|
<span v-if="isMultiDayEvent(event)" class="multi-day-indicator">
|
|
671
690
|
{{ getMultiDayPosition(event, day.date) === 'start' ? '▶' :
|
|
@@ -932,6 +951,21 @@ watch([currentMonth, currentYear], () => {
|
|
|
932
951
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
|
|
933
952
|
}
|
|
934
953
|
|
|
954
|
+
.calendar-event.has-custom-color {
|
|
955
|
+
background: var(--event-color);
|
|
956
|
+
border-color: color-mix(in srgb, var(--event-color) 75%, black);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
.calendar-event.has-custom-color:hover {
|
|
960
|
+
background: var(--event-color);
|
|
961
|
+
border-color: color-mix(in srgb, var(--event-color) 75%, black);
|
|
962
|
+
filter: brightness(0.92);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
.event-icon {
|
|
966
|
+
flex-shrink: 0;
|
|
967
|
+
}
|
|
968
|
+
|
|
935
969
|
.calendar-event.multi-day-start {
|
|
936
970
|
border-top-left-radius: 4px;
|
|
937
971
|
border-bottom-left-radius: 4px;
|
|
@@ -1034,7 +1068,7 @@ watch([currentMonth, currentYear], () => {
|
|
|
1034
1068
|
}
|
|
1035
1069
|
}
|
|
1036
1070
|
|
|
1037
|
-
|
|
1071
|
+
html[data-theme="dark"] {
|
|
1038
1072
|
.calendar-container {
|
|
1039
1073
|
background: #565656;
|
|
1040
1074
|
border-color: #374151;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<header>
|
|
3
3
|
<div
|
|
4
4
|
:id = "sidebarEnabled ? 'sidebar-button' : null"
|
|
5
|
-
:class="['image-and-title', 'flex-row', { 'disabled-branding': !showBranding }]"
|
|
5
|
+
:class="['image-and-title', 'flex-row', { 'disabled-branding': !showBranding, 'logo-home-link': !sidebarEnabled && showBranding }]"
|
|
6
6
|
@click = "onImageAndTitleClick"
|
|
7
7
|
>
|
|
8
8
|
<img v-if="showBranding" :src = "logoUrl" alt = "Logo" class = "logo" />
|
|
@@ -23,6 +23,16 @@
|
|
|
23
23
|
|
|
24
24
|
<div class = "fg1"></div>
|
|
25
25
|
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
class="theme-toggle neutral"
|
|
29
|
+
:aria-label="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
|
|
30
|
+
:title="isDark ? 'Light mode' : 'Dark mode'"
|
|
31
|
+
@click="toggleTheme"
|
|
32
|
+
>
|
|
33
|
+
<HugeiconsIcon :icon="isDark ? Sun01Icon : Moon02Icon" width="1em" height="1em" />
|
|
34
|
+
</button>
|
|
35
|
+
|
|
26
36
|
<slot name = "user-info">
|
|
27
37
|
<div class = "user-info">
|
|
28
38
|
<span v-if="username">{{ username }}</span>
|
|
@@ -33,10 +43,13 @@
|
|
|
33
43
|
|
|
34
44
|
<script setup>
|
|
35
45
|
import { HugeiconsIcon } from "@hugeicons/vue";
|
|
36
|
-
import { Menu01Icon } from "@hugeicons/core-free-icons";
|
|
46
|
+
import { Menu01Icon, Moon02Icon, Sun01Icon } from "@hugeicons/core-free-icons";
|
|
37
47
|
|
|
38
48
|
import Breadcrumbs from "./Breadcrumbs.vue";
|
|
39
49
|
import TopBar from "./TopBar.vue";
|
|
50
|
+
import { useTheme } from '../composables/useTheme.js';
|
|
51
|
+
|
|
52
|
+
const { isDark, toggleTheme } = useTheme();
|
|
40
53
|
|
|
41
54
|
const emit = defineEmits(["toggleSidebar", "logoClick"]);
|
|
42
55
|
|
|
@@ -98,7 +111,20 @@ button:hover {
|
|
|
98
111
|
width: auto;
|
|
99
112
|
}
|
|
100
113
|
|
|
114
|
+
.logo-home-link {
|
|
115
|
+
cursor: pointer;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.logo-home-link:hover {
|
|
119
|
+
background-color: var(--header-hover-background-color);
|
|
120
|
+
color: var(--header-hover-text-color);
|
|
121
|
+
}
|
|
122
|
+
|
|
101
123
|
.user-info span {
|
|
102
124
|
padding-right: 0.5rem;
|
|
103
125
|
}
|
|
126
|
+
|
|
127
|
+
.theme-toggle {
|
|
128
|
+
margin-right: 0.25rem;
|
|
129
|
+
}
|
|
104
130
|
</style>
|
|
@@ -90,6 +90,15 @@ function addSeparator(id) {
|
|
|
90
90
|
addNavigationLink(separator)
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
function addSection(title, options = {}) {
|
|
94
|
+
const section = {
|
|
95
|
+
name: options.name || `nav-section-${title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')}`,
|
|
96
|
+
type: 'section',
|
|
97
|
+
title,
|
|
98
|
+
}
|
|
99
|
+
addNavigationLink(section)
|
|
100
|
+
}
|
|
101
|
+
|
|
93
102
|
function addHtml(html, options = {}) {
|
|
94
103
|
const htmlLink = {
|
|
95
104
|
name: options.name || `html-item-${Date.now()}`,
|
|
@@ -153,6 +162,7 @@ provide('navigation', {
|
|
|
153
162
|
addNavigationLink,
|
|
154
163
|
addCallback,
|
|
155
164
|
addSeparator,
|
|
165
|
+
addSection,
|
|
156
166
|
addHtml,
|
|
157
167
|
removeNavigationLink,
|
|
158
168
|
clearNavigationLinks,
|
|
@@ -167,6 +177,7 @@ defineExpose({
|
|
|
167
177
|
addNavigationLink,
|
|
168
178
|
addCallback,
|
|
169
179
|
addSeparator,
|
|
180
|
+
addSection,
|
|
170
181
|
addHtml,
|
|
171
182
|
removeNavigationLink,
|
|
172
183
|
clearNavigationLinks,
|
|
@@ -90,7 +90,7 @@ const filteredLinks = computed(() => {
|
|
|
90
90
|
// Exclude separators and HTML items by default
|
|
91
91
|
if (props.excludeNonButtons) {
|
|
92
92
|
links = links.filter(link =>
|
|
93
|
-
link.type !== 'separator' && link.type !== 'html'
|
|
93
|
+
link.type !== 'separator' && link.type !== 'html' && link.type !== 'section'
|
|
94
94
|
);
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -286,7 +286,7 @@ function handleLinkClick(link) {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
/* Dark theme support */
|
|
289
|
-
|
|
289
|
+
html[data-theme="dark"] {
|
|
290
290
|
.nav-button {
|
|
291
291
|
background: transparent;
|
|
292
292
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="notification-popup notification show"
|
|
4
|
+
:class="popup.class"
|
|
5
|
+
role="status"
|
|
6
|
+
>
|
|
7
|
+
<button
|
|
8
|
+
type="button"
|
|
9
|
+
class="notification-popup-dismiss"
|
|
10
|
+
aria-label="Dismiss notification"
|
|
11
|
+
@click.stop="emit('dismiss', popup.id)"
|
|
12
|
+
>
|
|
13
|
+
<HugeiconsIcon :icon="Cancel01Icon" width="1em" height="1em" />
|
|
14
|
+
</button>
|
|
15
|
+
|
|
16
|
+
<div class="notification-popup-body">
|
|
17
|
+
<p class="notification-popup-message">
|
|
18
|
+
<strong v-if="popup.label">{{ popup.label }}:</strong>
|
|
19
|
+
{{ popup.message }}
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<RouterLink
|
|
23
|
+
v-if="popup.linkTo && !isExternalLink"
|
|
24
|
+
:to="popup.linkTo"
|
|
25
|
+
class="notification-popup-link"
|
|
26
|
+
@click="emit('dismiss', popup.id)"
|
|
27
|
+
>
|
|
28
|
+
{{ popup.linkLabel || 'View' }}
|
|
29
|
+
</RouterLink>
|
|
30
|
+
|
|
31
|
+
<a
|
|
32
|
+
v-else-if="popup.linkTo && isExternalLink"
|
|
33
|
+
:href="popup.linkTo"
|
|
34
|
+
class="notification-popup-link"
|
|
35
|
+
target="_blank"
|
|
36
|
+
rel="noopener noreferrer"
|
|
37
|
+
@click="emit('dismiss', popup.id)"
|
|
38
|
+
>
|
|
39
|
+
{{ popup.linkLabel || 'View' }}
|
|
40
|
+
</a>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script setup>
|
|
46
|
+
import { computed } from 'vue'
|
|
47
|
+
import { RouterLink } from 'vue-router'
|
|
48
|
+
import { HugeiconsIcon } from '@hugeicons/vue'
|
|
49
|
+
import { Cancel01Icon } from '@hugeicons/core-free-icons'
|
|
50
|
+
|
|
51
|
+
const props = defineProps({
|
|
52
|
+
popup: {
|
|
53
|
+
type: Object,
|
|
54
|
+
required: true,
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const emit = defineEmits(['dismiss'])
|
|
59
|
+
|
|
60
|
+
const isExternalLink = computed(() =>
|
|
61
|
+
typeof props.popup.linkTo === 'string' && /^https?:\/\//.test(props.popup.linkTo)
|
|
62
|
+
)
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<style scoped>
|
|
66
|
+
.notification-popup {
|
|
67
|
+
position: relative;
|
|
68
|
+
width: min(22rem, calc(100vw - 2rem));
|
|
69
|
+
margin: 0;
|
|
70
|
+
padding: 0.75rem 2.25rem 0.75rem 0.75rem;
|
|
71
|
+
cursor: default;
|
|
72
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.notification-popup-message {
|
|
76
|
+
margin: 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.notification-popup-body {
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
gap: 0.5rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.notification-popup-link {
|
|
86
|
+
color: inherit;
|
|
87
|
+
font-weight: bold;
|
|
88
|
+
text-decoration: underline;
|
|
89
|
+
align-self: flex-start;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.notification-popup-link:hover {
|
|
93
|
+
opacity: 0.85;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.notification-popup-dismiss {
|
|
97
|
+
position: absolute;
|
|
98
|
+
top: 0.35rem;
|
|
99
|
+
right: 0.35rem;
|
|
100
|
+
display: inline-flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: center;
|
|
103
|
+
width: 1.75rem;
|
|
104
|
+
height: 1.75rem;
|
|
105
|
+
padding: 0;
|
|
106
|
+
border: 0;
|
|
107
|
+
border-radius: 0.35rem;
|
|
108
|
+
background: transparent;
|
|
109
|
+
color: inherit;
|
|
110
|
+
cursor: pointer;
|
|
111
|
+
opacity: 0.75;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.notification-popup-dismiss:hover {
|
|
115
|
+
opacity: 1;
|
|
116
|
+
background: rgba(0, 0, 0, 0.08);
|
|
117
|
+
}
|
|
118
|
+
</style>
|