picocrank 1.25.2 → 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.
- package/eslint.config.js +68 -0
- package/package.json +13 -3
- package/router.js +74 -0
- package/styles/dark-theme.css +20 -11
- package/styles/picocrank-extensions.css +77 -30
- package/supplemental-themes/catppuccin-latte-frappe/theme.css +10 -24
- package/supplemental-themes/dracula-alucard/theme.css +10 -24
- package/supplemental-themes/gruvbox-dark-light/theme.css +10 -24
- package/supplemental-themes/waffles/theme.css +2 -16
- package/vue/components/Breadcrumbs.vue +192 -45
- package/vue/components/Calendar.vue +45 -25
- package/vue/components/DangerZone.vue +186 -0
- package/vue/components/Header.vue +22 -6
- package/vue/components/NavigationGrid.vue +26 -180
- package/vue/components/NotificationBlock.vue +120 -0
- package/vue/components/NotificationPopup.vue +44 -41
- package/vue/components/ReadOnlyTextArea.vue +1 -1
- package/vue/components/SidebarNavLink.vue +3 -3
- package/vue/components/Table.vue +107 -79
- package/vue/components/TableColumnOptionsPopover.vue +9 -0
- package/vue/components/Tabs.vue +363 -79
- package/vue/components/TopBar.vue +2 -2
- package/vue/composables/tableColumnPriorities.js +33 -0
- package/vue/composables/tableFilters.js +1 -1
- package/vue/data/navSectionHubs.js +196 -0
- package/vue/data/routeBreadcrumbs.js +90 -0
- package/vue/examples/Admin.vue +12 -12
- package/vue/examples/App.vue +27 -36
- package/vue/examples/CalendarExample.vue +32 -22
- package/vue/examples/ExampleHeaderThemeSwitcher.vue +1 -1
- package/vue/examples/Login.vue +0 -3
- package/vue/examples/LoginExample.vue +2 -14
- package/vue/examples/NavSectionHub.vue +36 -0
- package/vue/examples/PageStructure.vue +6 -3
- package/vue/examples/StatusExample.vue +248 -129
- package/vue/examples/TableExample.vue +68 -67
- package/vue/examples/TabsExample.vue +200 -255
- package/vue/examples/ThemePreview.vue +21 -23
- package/vue/examples/ViewItem.vue +32 -5
- package/vue/examples/Welcome.vue +3 -40
|
@@ -32,7 +32,7 @@ html[data-theme="dark"] {
|
|
|
32
32
|
--dt-warning-fg: var(--dt-surface);
|
|
33
33
|
|
|
34
34
|
--body-bg-color: var(--dt-background);
|
|
35
|
-
--
|
|
35
|
+
--section-bg-color: var(--dt-surface);
|
|
36
36
|
--standout-bg-color: var(--dt-surface);
|
|
37
37
|
--section-header-bg-color: var(--dt-surface);
|
|
38
38
|
--shadow-color: var(--dt-shadow-strong);
|
|
@@ -41,8 +41,6 @@ html[data-theme="dark"] {
|
|
|
41
41
|
--muted-text-color: var(--dt-muted);
|
|
42
42
|
--annotation-key-color: var(--dt-muted);
|
|
43
43
|
--link-color: var(--dt-accent);
|
|
44
|
-
--nav-grid-accent-fg: var(--link-color);
|
|
45
|
-
--nav-grid-accent-bg: var(--link-color);
|
|
46
44
|
--border-color: var(--dt-border);
|
|
47
45
|
|
|
48
46
|
--hover-background-color: var(--dt-selection);
|
|
@@ -65,6 +63,9 @@ html[data-theme="dark"] {
|
|
|
65
63
|
--input-fg-color: var(--dt-foreground);
|
|
66
64
|
--control-option-bg-color: var(--dt-background);
|
|
67
65
|
--control-hover-bg-color: var(--dt-overlay);
|
|
66
|
+
--control-checked-bg: color-mix(in srgb, var(--dt-green) 35%, var(--standout-bg-color));
|
|
67
|
+
--control-checked-hover-bg: color-mix(in srgb, var(--dt-green) 45%, var(--standout-bg-color));
|
|
68
|
+
--control-checked-fg: var(--dt-foreground);
|
|
68
69
|
--menu-hover-bg-color: var(--dt-selection);
|
|
69
70
|
|
|
70
71
|
--table-row-hover-bg: var(--dt-selection);
|
|
@@ -87,19 +88,11 @@ html[data-theme="dark"] {
|
|
|
87
88
|
--karma-bad: var(--dt-red);
|
|
88
89
|
--karma-warning: var(--dt-yellow);
|
|
89
90
|
--karma-severe: var(--dt-orange);
|
|
90
|
-
--karma-important: var(--dt-purple);
|
|
91
91
|
--karma-note: var(--dt-cyan);
|
|
92
92
|
--karma-info: var(--dt-overlay);
|
|
93
93
|
--karma-old: var(--dt-muted);
|
|
94
94
|
|
|
95
|
-
--karma-
|
|
96
|
-
--karma-bad-fg: var(--dt-red);
|
|
97
|
-
--karma-warning-fg: var(--dt-yellow);
|
|
98
|
-
--karma-severe-fg: var(--dt-orange);
|
|
99
|
-
--karma-important-fg: var(--dt-purple);
|
|
100
|
-
--karma-note-fg: var(--dt-cyan);
|
|
101
|
-
--karma-info-fg: var(--dt-muted);
|
|
102
|
-
--karma-old-fg: var(--dt-muted);
|
|
95
|
+
--karma-on-bg-fg: var(--dt-good-fg);
|
|
103
96
|
}
|
|
104
97
|
|
|
105
98
|
html[data-theme="light"] {
|
|
@@ -126,7 +119,7 @@ html[data-theme="light"] {
|
|
|
126
119
|
--dt-warning-fg: var(--dt-background);
|
|
127
120
|
|
|
128
121
|
--body-bg-color: var(--dt-background);
|
|
129
|
-
--
|
|
122
|
+
--section-bg-color: var(--dt-surface);
|
|
130
123
|
--standout-bg-color: var(--dt-surface);
|
|
131
124
|
--section-header-bg-color: var(--dt-surface);
|
|
132
125
|
--shadow-color: var(--dt-shadow-strong);
|
|
@@ -135,8 +128,6 @@ html[data-theme="light"] {
|
|
|
135
128
|
--muted-text-color: var(--dt-muted);
|
|
136
129
|
--annotation-key-color: var(--dt-muted);
|
|
137
130
|
--link-color: var(--dt-accent);
|
|
138
|
-
--nav-grid-accent-fg: var(--link-color);
|
|
139
|
-
--nav-grid-accent-bg: var(--link-color);
|
|
140
131
|
--border-color: var(--dt-border);
|
|
141
132
|
|
|
142
133
|
--hover-background-color: var(--dt-selection);
|
|
@@ -159,6 +150,9 @@ html[data-theme="light"] {
|
|
|
159
150
|
--input-fg-color: var(--dt-foreground);
|
|
160
151
|
--control-option-bg-color: var(--dt-overlay);
|
|
161
152
|
--control-hover-bg-color: var(--dt-selection);
|
|
153
|
+
--control-checked-bg: color-mix(in srgb, var(--dt-green) 20%, var(--standout-bg-color));
|
|
154
|
+
--control-checked-hover-bg: color-mix(in srgb, var(--dt-green) 28%, var(--standout-bg-color));
|
|
155
|
+
--control-checked-fg: var(--dt-foreground);
|
|
162
156
|
--menu-hover-bg-color: var(--dt-selection);
|
|
163
157
|
|
|
164
158
|
--table-row-hover-bg: var(--dt-selection);
|
|
@@ -181,17 +175,9 @@ html[data-theme="light"] {
|
|
|
181
175
|
--karma-bad: var(--dt-red);
|
|
182
176
|
--karma-warning: var(--dt-yellow);
|
|
183
177
|
--karma-severe: var(--dt-orange);
|
|
184
|
-
--karma-important: var(--dt-purple);
|
|
185
178
|
--karma-note: var(--dt-cyan);
|
|
186
179
|
--karma-info: var(--dt-overlay);
|
|
187
180
|
--karma-old: var(--dt-muted);
|
|
188
181
|
|
|
189
|
-
--karma-
|
|
190
|
-
--karma-bad-fg: var(--dt-red);
|
|
191
|
-
--karma-warning-fg: var(--dt-yellow);
|
|
192
|
-
--karma-severe-fg: var(--dt-orange);
|
|
193
|
-
--karma-important-fg: var(--dt-purple);
|
|
194
|
-
--karma-note-fg: var(--dt-cyan);
|
|
195
|
-
--karma-info-fg: var(--dt-muted);
|
|
196
|
-
--karma-old-fg: var(--dt-muted);
|
|
182
|
+
--karma-on-bg-fg: var(--dt-good-fg);
|
|
197
183
|
}
|
|
@@ -35,7 +35,7 @@ html[data-theme="dark"] {
|
|
|
35
35
|
--gb-good-fg: var(--gb-bg0-hard);
|
|
36
36
|
|
|
37
37
|
--body-bg-color: var(--gb-bg0);
|
|
38
|
-
--
|
|
38
|
+
--section-bg-color: var(--gb-bg1);
|
|
39
39
|
--standout-bg-color: var(--gb-bg1);
|
|
40
40
|
--section-header-bg-color: var(--gb-bg1);
|
|
41
41
|
--shadow-color: var(--gb-shadow-strong);
|
|
@@ -44,8 +44,6 @@ html[data-theme="dark"] {
|
|
|
44
44
|
--muted-text-color: var(--gb-fg3);
|
|
45
45
|
--annotation-key-color: var(--gb-fg3);
|
|
46
46
|
--link-color: var(--gb-accent);
|
|
47
|
-
--nav-grid-accent-fg: var(--link-color);
|
|
48
|
-
--nav-grid-accent-bg: var(--link-color);
|
|
49
47
|
--border-color: var(--gb-bg3);
|
|
50
48
|
|
|
51
49
|
--hover-background-color: var(--gb-bg2);
|
|
@@ -68,6 +66,9 @@ html[data-theme="dark"] {
|
|
|
68
66
|
--input-fg-color: var(--gb-fg0);
|
|
69
67
|
--control-option-bg-color: var(--gb-bg2);
|
|
70
68
|
--control-hover-bg-color: var(--gb-bg3);
|
|
69
|
+
--control-checked-bg: color-mix(in srgb, var(--gb-green) 35%, var(--standout-bg-color));
|
|
70
|
+
--control-checked-hover-bg: color-mix(in srgb, var(--gb-green) 45%, var(--standout-bg-color));
|
|
71
|
+
--control-checked-fg: var(--gb-fg0);
|
|
71
72
|
--menu-hover-bg-color: var(--gb-bg2);
|
|
72
73
|
|
|
73
74
|
--table-row-hover-bg: var(--gb-bg2);
|
|
@@ -90,19 +91,11 @@ html[data-theme="dark"] {
|
|
|
90
91
|
--karma-bad: var(--gb-red);
|
|
91
92
|
--karma-warning: var(--gb-yellow);
|
|
92
93
|
--karma-severe: var(--gb-orange);
|
|
93
|
-
--karma-important: var(--gb-purple);
|
|
94
94
|
--karma-note: var(--gb-aqua);
|
|
95
95
|
--karma-info: var(--gb-bg2);
|
|
96
96
|
--karma-old: var(--gb-gray);
|
|
97
97
|
|
|
98
|
-
--karma-
|
|
99
|
-
--karma-bad-fg: var(--gb-red);
|
|
100
|
-
--karma-warning-fg: var(--gb-yellow);
|
|
101
|
-
--karma-severe-fg: var(--gb-orange);
|
|
102
|
-
--karma-important-fg: var(--gb-purple);
|
|
103
|
-
--karma-note-fg: var(--gb-aqua);
|
|
104
|
-
--karma-info-fg: var(--gb-fg3);
|
|
105
|
-
--karma-old-fg: var(--gb-fg4);
|
|
98
|
+
--karma-on-bg-fg: var(--gb-good-fg);
|
|
106
99
|
}
|
|
107
100
|
|
|
108
101
|
html[data-theme="light"] {
|
|
@@ -132,7 +125,7 @@ html[data-theme="light"] {
|
|
|
132
125
|
--gb-good-fg: var(--gb-bg0);
|
|
133
126
|
|
|
134
127
|
--body-bg-color: var(--gb-bg0);
|
|
135
|
-
--
|
|
128
|
+
--section-bg-color: var(--gb-bg1);
|
|
136
129
|
--standout-bg-color: var(--gb-bg1);
|
|
137
130
|
--section-header-bg-color: var(--gb-bg1);
|
|
138
131
|
--shadow-color: var(--gb-shadow-strong);
|
|
@@ -141,8 +134,6 @@ html[data-theme="light"] {
|
|
|
141
134
|
--muted-text-color: var(--gb-fg3);
|
|
142
135
|
--annotation-key-color: var(--gb-fg3);
|
|
143
136
|
--link-color: var(--gb-accent);
|
|
144
|
-
--nav-grid-accent-fg: var(--link-color);
|
|
145
|
-
--nav-grid-accent-bg: var(--link-color);
|
|
146
137
|
--border-color: var(--gb-bg3);
|
|
147
138
|
|
|
148
139
|
--hover-background-color: var(--gb-bg2);
|
|
@@ -165,6 +156,9 @@ html[data-theme="light"] {
|
|
|
165
156
|
--input-fg-color: var(--gb-fg0);
|
|
166
157
|
--control-option-bg-color: var(--gb-bg2);
|
|
167
158
|
--control-hover-bg-color: var(--gb-bg3);
|
|
159
|
+
--control-checked-bg: color-mix(in srgb, var(--gb-green) 20%, var(--standout-bg-color));
|
|
160
|
+
--control-checked-hover-bg: color-mix(in srgb, var(--gb-green) 28%, var(--standout-bg-color));
|
|
161
|
+
--control-checked-fg: var(--gb-fg0);
|
|
168
162
|
--menu-hover-bg-color: var(--gb-bg2);
|
|
169
163
|
|
|
170
164
|
--table-row-hover-bg: var(--gb-bg2);
|
|
@@ -187,17 +181,9 @@ html[data-theme="light"] {
|
|
|
187
181
|
--karma-bad: var(--gb-red);
|
|
188
182
|
--karma-warning: var(--gb-yellow);
|
|
189
183
|
--karma-severe: var(--gb-orange);
|
|
190
|
-
--karma-important: var(--gb-purple);
|
|
191
184
|
--karma-note: var(--gb-aqua);
|
|
192
185
|
--karma-info: var(--gb-bg2);
|
|
193
186
|
--karma-old: var(--gb-gray);
|
|
194
187
|
|
|
195
|
-
--karma-
|
|
196
|
-
--karma-bad-fg: var(--gb-red);
|
|
197
|
-
--karma-warning-fg: var(--gb-yellow);
|
|
198
|
-
--karma-severe-fg: var(--gb-orange);
|
|
199
|
-
--karma-important-fg: var(--gb-purple);
|
|
200
|
-
--karma-note-fg: var(--gb-aqua);
|
|
201
|
-
--karma-info-fg: var(--gb-fg3);
|
|
202
|
-
--karma-old-fg: var(--gb-fg4);
|
|
188
|
+
--karma-on-bg-fg: var(--gb-good-fg);
|
|
203
189
|
}
|
|
@@ -18,7 +18,7 @@ html {
|
|
|
18
18
|
--waffle-accent: #a05020;
|
|
19
19
|
|
|
20
20
|
--body-bg-color: var(--waffle-cream);
|
|
21
|
-
--
|
|
21
|
+
--section-bg-color: #fffdf6;
|
|
22
22
|
--standout-bg-color: var(--waffle-batter);
|
|
23
23
|
--section-header-bg-color: var(--waffle-batter);
|
|
24
24
|
--shadow-color: rgba(139, 69, 19, 0.12);
|
|
@@ -26,8 +26,6 @@ html {
|
|
|
26
26
|
--text-color: var(--waffle-syrup);
|
|
27
27
|
--muted-text-color: #a88860;
|
|
28
28
|
--link-color: var(--waffle-accent);
|
|
29
|
-
--nav-grid-accent-fg: var(--link-color);
|
|
30
|
-
--nav-grid-accent-bg: var(--link-color);
|
|
31
29
|
--border-color: #e0c896;
|
|
32
30
|
|
|
33
31
|
--hover-background-color: #f0d9a8;
|
|
@@ -61,19 +59,9 @@ html {
|
|
|
61
59
|
--karma-bad: #f0a0a8;
|
|
62
60
|
--karma-warning: var(--waffle-butter);
|
|
63
61
|
--karma-severe: #e08a6a;
|
|
64
|
-
--karma-important: #ffe8a3;
|
|
65
62
|
--karma-note: #a8d4e8;
|
|
66
63
|
--karma-info: var(--waffle-batter);
|
|
67
64
|
--karma-old: #d4c4a0;
|
|
68
|
-
|
|
69
|
-
--karma-good-fg: #4a7a28;
|
|
70
|
-
--karma-bad-fg: var(--waffle-berry);
|
|
71
|
-
--karma-warning-fg: #9a6b00;
|
|
72
|
-
--karma-severe-fg: #a04020;
|
|
73
|
-
--karma-important-fg: #8a6800;
|
|
74
|
-
--karma-note-fg: #2a6a88;
|
|
75
|
-
--karma-info-fg: #7a6540;
|
|
76
|
-
--karma-old-fg: #6a5840;
|
|
77
65
|
}
|
|
78
66
|
|
|
79
67
|
body {
|
|
@@ -140,8 +128,6 @@ html[data-theme="dark"] {
|
|
|
140
128
|
--text-color: var(--waffle-foreground);
|
|
141
129
|
--muted-text-color: var(--waffle-foreground-muted);
|
|
142
130
|
--link-color: var(--waffle-accent-dark);
|
|
143
|
-
--nav-grid-accent-fg: var(--link-color);
|
|
144
|
-
--nav-grid-accent-bg: var(--link-color);
|
|
145
131
|
--border-color: #c9a66a;
|
|
146
132
|
--standout-bg-color: var(--waffle-batter);
|
|
147
133
|
--hover-background-color: #f0d9a8;
|
|
@@ -149,7 +135,7 @@ html[data-theme="dark"] {
|
|
|
149
135
|
--disabled-text-color: #7a6548;
|
|
150
136
|
|
|
151
137
|
--body-bg-color: var(--waffle-cream);
|
|
152
|
-
--
|
|
138
|
+
--section-bg-color: var(--waffle-batter);
|
|
153
139
|
--section-header-bg-color: var(--waffle-section);
|
|
154
140
|
--sidebar-bg-color: var(--waffle-sidebar);
|
|
155
141
|
--sidebar-link-color: var(--waffle-foreground);
|
|
@@ -1,58 +1,205 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
<nav id="breadcrumbs" class="breadcrumbs" aria-label="Breadcrumb">
|
|
3
|
+
<div class="breadcrumbs-links flex-row">
|
|
4
|
+
<template v-for="(link, index) in links" :key="`${link.name}-${index}`">
|
|
5
|
+
<HugeiconsIcon
|
|
6
|
+
v-if="index > 0"
|
|
7
|
+
:icon="ArrowRight01Icon"
|
|
8
|
+
class="separator"
|
|
9
|
+
width="0.85em"
|
|
10
|
+
height="0.85em"
|
|
11
|
+
aria-hidden="true"
|
|
12
|
+
/>
|
|
13
|
+
|
|
14
|
+
<nav
|
|
15
|
+
v-if="hasMenu(link)"
|
|
16
|
+
class="menu breadcrumb-menu"
|
|
17
|
+
:aria-label="`${link.name} sections`"
|
|
18
|
+
>
|
|
19
|
+
<ul>
|
|
20
|
+
<li>
|
|
21
|
+
<span class="submenu">
|
|
22
|
+
<router-link
|
|
23
|
+
v-if="link.to"
|
|
24
|
+
:to="link.to"
|
|
25
|
+
class="button breadcrumb-link"
|
|
26
|
+
:aria-current="isLast(index) ? 'page' : undefined"
|
|
27
|
+
>
|
|
28
|
+
<span>{{ link.name }}</span>
|
|
29
|
+
</router-link>
|
|
30
|
+
<span
|
|
31
|
+
v-else
|
|
32
|
+
class="button breadcrumb-link"
|
|
33
|
+
:aria-current="isLast(index) ? 'page' : undefined"
|
|
34
|
+
>
|
|
35
|
+
{{ link.name }}
|
|
36
|
+
</span>
|
|
37
|
+
</span>
|
|
38
|
+
<div>
|
|
39
|
+
<ul>
|
|
40
|
+
<li v-for="menuItem in link.menu" :key="menuItem.name">
|
|
41
|
+
<router-link
|
|
42
|
+
:to="menuItem.to"
|
|
43
|
+
:class="{ active: isMenuItemActive(link, menuItem) }"
|
|
44
|
+
>
|
|
45
|
+
{{ menuItem.name }}
|
|
46
|
+
</router-link>
|
|
47
|
+
</li>
|
|
48
|
+
</ul>
|
|
49
|
+
</div>
|
|
50
|
+
</li>
|
|
51
|
+
</ul>
|
|
52
|
+
</nav>
|
|
53
|
+
|
|
54
|
+
<router-link
|
|
55
|
+
v-else-if="!isLast(index)"
|
|
56
|
+
:to="link.to ?? link.href"
|
|
57
|
+
class="button breadcrumb-link"
|
|
58
|
+
>
|
|
59
|
+
<span>{{ link.name }}</span>
|
|
60
|
+
</router-link>
|
|
61
|
+
|
|
62
|
+
<span
|
|
63
|
+
v-else
|
|
64
|
+
class="button breadcrumb-link active"
|
|
65
|
+
aria-current="page"
|
|
66
|
+
>
|
|
67
|
+
<span>{{ link.name }}</span>
|
|
68
|
+
</span>
|
|
69
|
+
</template>
|
|
70
|
+
</div>
|
|
71
|
+
</nav>
|
|
10
72
|
</template>
|
|
11
73
|
|
|
74
|
+
<script setup>
|
|
75
|
+
import { ref, watch } from 'vue'
|
|
76
|
+
import { useRoute } from 'vue-router'
|
|
77
|
+
import { HugeiconsIcon } from '@hugeicons/vue'
|
|
78
|
+
import { ArrowRight01Icon } from '@hugeicons/core-free-icons'
|
|
79
|
+
|
|
80
|
+
const route = useRoute()
|
|
81
|
+
const links = ref([])
|
|
82
|
+
|
|
83
|
+
function hasMenu(link) {
|
|
84
|
+
return Array.isArray(link.menu) && link.menu.length > 0
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isLast(index) {
|
|
88
|
+
return index === links.value.length - 1
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isMenuItemActive(link, menuItem) {
|
|
92
|
+
const activeRoute = link.to?.name
|
|
93
|
+
const itemRoute = menuItem.to?.name
|
|
94
|
+
return activeRoute != null && activeRoute === itemRoute
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
watch(() => route.matched, (matched) => {
|
|
98
|
+
links.value = []
|
|
99
|
+
matched.forEach((record) => {
|
|
100
|
+
if (record.meta && record.meta.breadcrumbs) {
|
|
101
|
+
record.meta.breadcrumbs(route).forEach((item) => {
|
|
102
|
+
links.value.push({
|
|
103
|
+
name: item.name,
|
|
104
|
+
to: item.to,
|
|
105
|
+
href: item.href || record.path || '/',
|
|
106
|
+
menu: item.menu,
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
} else if (record.name) {
|
|
110
|
+
links.value.push({
|
|
111
|
+
name: record.meta?.title || record.name,
|
|
112
|
+
to: { name: record.name, params: route.params, query: route.query },
|
|
113
|
+
href: route.fullPath || record.path || '/',
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
}, { immediate: true })
|
|
118
|
+
</script>
|
|
119
|
+
|
|
12
120
|
<style scoped>
|
|
13
|
-
.
|
|
14
|
-
|
|
121
|
+
.breadcrumbs {
|
|
122
|
+
flex: 1 1 auto;
|
|
123
|
+
min-width: 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.breadcrumbs-links {
|
|
127
|
+
display: flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
gap: 0.0625rem;
|
|
130
|
+
flex-wrap: wrap;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.breadcrumb-menu {
|
|
134
|
+
display: inline-flex;
|
|
135
|
+
flex-shrink: 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.breadcrumb-menu > ul {
|
|
139
|
+
list-style: none;
|
|
140
|
+
padding: 0;
|
|
141
|
+
margin: 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.breadcrumb-link {
|
|
145
|
+
display: inline-flex;
|
|
146
|
+
align-items: center;
|
|
147
|
+
gap: 0.375rem;
|
|
148
|
+
padding: 0.5em 0.375em;
|
|
149
|
+
border-radius: 0.25em;
|
|
150
|
+
text-decoration: none;
|
|
151
|
+
color: var(--header-fg-color);
|
|
152
|
+
transition: background-color 0.15s ease, color 0.15s ease;
|
|
153
|
+
white-space: nowrap;
|
|
154
|
+
border: 0;
|
|
155
|
+
font-weight: normal;
|
|
156
|
+
background-color: transparent;
|
|
157
|
+
cursor: default;
|
|
15
158
|
}
|
|
16
159
|
|
|
17
|
-
a {
|
|
18
|
-
|
|
19
|
-
padding: 0.4em;
|
|
20
|
-
border-radius: 0.2em;
|
|
160
|
+
a.breadcrumb-link {
|
|
161
|
+
cursor: pointer;
|
|
21
162
|
}
|
|
22
163
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
164
|
+
.breadcrumb-link.active {
|
|
165
|
+
text-decoration: underline;
|
|
166
|
+
font-weight: 500;
|
|
26
167
|
}
|
|
27
168
|
|
|
169
|
+
.separator {
|
|
170
|
+
flex-shrink: 0;
|
|
171
|
+
color: var(--header-fg-color);
|
|
172
|
+
opacity: 0.4;
|
|
173
|
+
}
|
|
28
174
|
</style>
|
|
29
175
|
|
|
176
|
+
<style>
|
|
177
|
+
header .breadcrumbs a.breadcrumb-link:hover,
|
|
178
|
+
header .breadcrumbs a.breadcrumb-link:focus-visible,
|
|
179
|
+
header .breadcrumbs .breadcrumb-menu.menu > ul > li:hover,
|
|
180
|
+
header .breadcrumbs .breadcrumb-menu.menu > ul > li:focus-within,
|
|
181
|
+
header .breadcrumbs .breadcrumb-menu.menu > ul > li:hover > span > a,
|
|
182
|
+
header .breadcrumbs .breadcrumb-menu.menu > ul > li:focus-within > span > a,
|
|
183
|
+
header .breadcrumbs .breadcrumb-menu.menu > ul div ul a:hover,
|
|
184
|
+
header .breadcrumbs .breadcrumb-menu.menu > ul div ul a:focus-visible {
|
|
185
|
+
background-color: var(--header-hover-background-color);
|
|
186
|
+
color: var(--header-hover-text-color);
|
|
187
|
+
}
|
|
30
188
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
} else if (record.name) {
|
|
51
|
-
links.value.push({
|
|
52
|
-
name: record.name,
|
|
53
|
-
href: record.path || '/'
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
}, { immediate: true });
|
|
58
|
-
</script>
|
|
189
|
+
header .breadcrumbs a.breadcrumb-link:focus-visible {
|
|
190
|
+
outline-color: var(--header-focus-outline-color);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
header .breadcrumb-menu.menu > ul > li {
|
|
194
|
+
border-radius: 0.25em;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
header .breadcrumb-menu.menu > ul li span.submenu .breadcrumb-link {
|
|
198
|
+
text-decoration: inherit;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
header .breadcrumb-menu.menu > ul li span.submenu .breadcrumb-link[aria-current="page"] {
|
|
202
|
+
text-decoration: underline;
|
|
203
|
+
font-weight: 500;
|
|
204
|
+
}
|
|
205
|
+
</style>
|
|
@@ -12,6 +12,8 @@ export interface CalendarEvent {
|
|
|
12
12
|
icon?: unknown
|
|
13
13
|
/** Optional CSS color overriding the default event background. */
|
|
14
14
|
color?: string
|
|
15
|
+
/** Optional CSS color for event title/time when using a custom background. */
|
|
16
|
+
foregroundColor?: string
|
|
15
17
|
[key: string]: any
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -23,7 +25,7 @@ export interface CalendarEventMoveRequest {
|
|
|
23
25
|
/** Calendar cell where the event was dropped. */
|
|
24
26
|
targetDate: Date
|
|
25
27
|
/** Invoke exactly once with the outcome. If omitted until timeout, the move is treated as rejected. */
|
|
26
|
-
respond: (
|
|
28
|
+
respond: (_accepted: boolean) => void
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export interface CalendarProps {
|
|
@@ -41,9 +43,9 @@ export interface CalendarProps {
|
|
|
41
43
|
*/
|
|
42
44
|
eventMoveResponseTimeoutMs?: number
|
|
43
45
|
// Formatter functions
|
|
44
|
-
getEventDate?: (
|
|
45
|
-
getEventDateRange?: (
|
|
46
|
-
formatEventTime?: (
|
|
46
|
+
getEventDate?: (_event: CalendarEvent) => Date | null
|
|
47
|
+
getEventDateRange?: (_event: CalendarEvent) => { start: Date | null; end: Date | null }
|
|
48
|
+
formatEventTime?: (_event: CalendarEvent, _date: Date) => string
|
|
47
49
|
// Customization
|
|
48
50
|
showNavigation?: boolean
|
|
49
51
|
/** When true, day titles show ordinal + full month name (e.g. "21st July") instead of the day number alone. */
|
|
@@ -87,8 +89,8 @@ const currentDate = computed(() => {
|
|
|
87
89
|
return internalCurrentDate.value
|
|
88
90
|
})
|
|
89
91
|
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
+
const viewMonth = computed(() => currentDate.value.getMonth())
|
|
93
|
+
const viewYear = computed(() => currentDate.value.getFullYear())
|
|
92
94
|
|
|
93
95
|
// Compute the first visible day (Monday) for the current month view
|
|
94
96
|
function getStartOfGrid(date: Date): Date {
|
|
@@ -130,7 +132,7 @@ function isPastDay(date: Date): boolean {
|
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
// Get event date range
|
|
133
|
-
function
|
|
135
|
+
function resolveEventDateRange(event: CalendarEvent): { start: Date | null; end: Date | null } {
|
|
134
136
|
if (props.getEventDateRange) {
|
|
135
137
|
return props.getEventDateRange(event)
|
|
136
138
|
}
|
|
@@ -158,7 +160,7 @@ function getEventsForDate(date: Date): CalendarEvent[] {
|
|
|
158
160
|
targetDate.setHours(0, 0, 0, 0)
|
|
159
161
|
|
|
160
162
|
return props.events.filter(event => {
|
|
161
|
-
const { start, end } =
|
|
163
|
+
const { start, end } = resolveEventDateRange(event)
|
|
162
164
|
|
|
163
165
|
if (!start) return false
|
|
164
166
|
|
|
@@ -177,7 +179,7 @@ function getEventsForDate(date: Date): CalendarEvent[] {
|
|
|
177
179
|
|
|
178
180
|
// Check if an event is a multi-day event
|
|
179
181
|
function isMultiDayEvent(event: CalendarEvent): boolean {
|
|
180
|
-
const { start, end } =
|
|
182
|
+
const { start, end } = resolveEventDateRange(event)
|
|
181
183
|
|
|
182
184
|
if (!start || !end) return false
|
|
183
185
|
|
|
@@ -192,7 +194,7 @@ function isMultiDayEvent(event: CalendarEvent): boolean {
|
|
|
192
194
|
|
|
193
195
|
// Get the position of an event within a multi-day range for a specific date
|
|
194
196
|
function getMultiDayPosition(event: CalendarEvent, date: Date): 'start' | 'middle' | 'end' | 'single' {
|
|
195
|
-
const { start, end } =
|
|
197
|
+
const { start, end } = resolveEventDateRange(event)
|
|
196
198
|
|
|
197
199
|
if (!start || !end) return 'single'
|
|
198
200
|
|
|
@@ -218,7 +220,7 @@ function formatEventTimeDefault(event: CalendarEvent, date: Date): string {
|
|
|
218
220
|
return props.formatEventTime(event, date)
|
|
219
221
|
}
|
|
220
222
|
|
|
221
|
-
const { start, end } =
|
|
223
|
+
const { start, end } = resolveEventDateRange(event)
|
|
222
224
|
const position = getMultiDayPosition(event, date)
|
|
223
225
|
|
|
224
226
|
if (!start || !end) return 'No time'
|
|
@@ -237,8 +239,14 @@ function formatEventTimeDefault(event: CalendarEvent, date: Date): string {
|
|
|
237
239
|
}
|
|
238
240
|
|
|
239
241
|
function getEventStyle(event: CalendarEvent): Record<string, string> | undefined {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
+
const style: Record<string, string> = {}
|
|
243
|
+
if (event.color) {
|
|
244
|
+
style['--event-color'] = event.color
|
|
245
|
+
}
|
|
246
|
+
if (event.foregroundColor) {
|
|
247
|
+
style['--event-foreground-color'] = event.foregroundColor
|
|
248
|
+
}
|
|
249
|
+
return Object.keys(style).length > 0 ? style : undefined
|
|
242
250
|
}
|
|
243
251
|
|
|
244
252
|
// Get ordinal suffix for a number (1st, 2nd, 3rd, 4th, etc.)
|
|
@@ -275,25 +283,25 @@ const calendarDays = computed(() => {
|
|
|
275
283
|
// Navigation functions
|
|
276
284
|
function previousMonth() {
|
|
277
285
|
if (props.currentMonth !== undefined && props.currentYear !== undefined) {
|
|
278
|
-
const newMonth =
|
|
279
|
-
const newYear =
|
|
286
|
+
const newMonth = viewMonth.value === 0 ? 11 : viewMonth.value - 1
|
|
287
|
+
const newYear = viewMonth.value === 0 ? viewYear.value - 1 : viewYear.value
|
|
280
288
|
emit('month-change', newMonth, newYear)
|
|
281
289
|
// Optimistically update grid anchor for smooth transition
|
|
282
290
|
gridStartDate.value = getStartOfGrid(new Date(newYear, newMonth, 1))
|
|
283
291
|
} else {
|
|
284
|
-
internalCurrentDate.value = new Date(
|
|
292
|
+
internalCurrentDate.value = new Date(viewYear.value, viewMonth.value - 1, 1)
|
|
285
293
|
gridStartDate.value = getStartOfGrid(internalCurrentDate.value)
|
|
286
294
|
}
|
|
287
295
|
}
|
|
288
296
|
|
|
289
297
|
function nextMonth() {
|
|
290
298
|
if (props.currentMonth !== undefined && props.currentYear !== undefined) {
|
|
291
|
-
const newMonth =
|
|
292
|
-
const newYear =
|
|
299
|
+
const newMonth = viewMonth.value === 11 ? 0 : viewMonth.value + 1
|
|
300
|
+
const newYear = viewMonth.value === 11 ? viewYear.value + 1 : viewYear.value
|
|
293
301
|
emit('month-change', newMonth, newYear)
|
|
294
302
|
gridStartDate.value = getStartOfGrid(new Date(newYear, newMonth, 1))
|
|
295
303
|
} else {
|
|
296
|
-
internalCurrentDate.value = new Date(
|
|
304
|
+
internalCurrentDate.value = new Date(viewYear.value, viewMonth.value + 1, 1)
|
|
297
305
|
gridStartDate.value = getStartOfGrid(internalCurrentDate.value)
|
|
298
306
|
}
|
|
299
307
|
}
|
|
@@ -325,7 +333,7 @@ function adjustByWeeks(weeks: number): void {
|
|
|
325
333
|
|
|
326
334
|
if (props.currentMonth !== undefined && props.currentYear !== undefined) {
|
|
327
335
|
const { month, year } = getVisibleMonthYear(newStart)
|
|
328
|
-
if (month !==
|
|
336
|
+
if (month !== viewMonth.value || year !== viewYear.value) {
|
|
329
337
|
emit('month-change', month, year)
|
|
330
338
|
// Set flag to prevent watcher from resetting our scroll position
|
|
331
339
|
isScrolling.value = true
|
|
@@ -589,9 +597,9 @@ function onDayDrop(dayDate: Date, ev: DragEvent): void {
|
|
|
589
597
|
}
|
|
590
598
|
|
|
591
599
|
// Sync grid start when controlled month/year props change (but not during scrolling)
|
|
592
|
-
watch([
|
|
600
|
+
watch([viewMonth, viewYear], () => {
|
|
593
601
|
if (isScrolling.value) return
|
|
594
|
-
const anchor = new Date(
|
|
602
|
+
const anchor = new Date(viewYear.value, viewMonth.value, 1)
|
|
595
603
|
gridStartDate.value = getStartOfGrid(anchor)
|
|
596
604
|
})
|
|
597
605
|
|
|
@@ -600,7 +608,7 @@ watch([currentMonth, currentYear], () => {
|
|
|
600
608
|
<template>
|
|
601
609
|
<div class="calendar-wrapper">
|
|
602
610
|
<div v-if="showNavigation" class="calendar-header-nav">
|
|
603
|
-
<h2 class="calendar-title">{{ monthNames[
|
|
611
|
+
<h2 class="calendar-title">{{ monthNames[viewMonth] }} {{ viewYear }}</h2>
|
|
604
612
|
<div class="calendar-nav-buttons">
|
|
605
613
|
<slot name="nav-buttons">
|
|
606
614
|
<button @click="previousMonth" class="button neutral">‹</button>
|
|
@@ -638,8 +646,8 @@ watch([currentMonth, currentYear], () => {
|
|
|
638
646
|
:class="{
|
|
639
647
|
'today': day.date.toDateString() === new Date().toDateString(),
|
|
640
648
|
'weekend': day.date.getDay() === 0 || day.date.getDay() === 6,
|
|
641
|
-
'prev-month': day.date.getMonth() !==
|
|
642
|
-
'next-month': day.date.getMonth() !==
|
|
649
|
+
'prev-month': day.date.getMonth() !== viewMonth && day.date.getMonth() !== (viewMonth + 1) % 12,
|
|
650
|
+
'next-month': day.date.getMonth() !== viewMonth && day.date.getMonth() === (viewMonth + 1) % 12,
|
|
643
651
|
'past': isPastDay(day.date),
|
|
644
652
|
'range-single': rangeHighlightForDate(day.date) === 'single',
|
|
645
653
|
'range-start': rangeHighlightForDate(day.date) === 'start',
|
|
@@ -980,6 +988,18 @@ watch([currentMonth, currentYear], () => {
|
|
|
980
988
|
filter: brightness(0.92);
|
|
981
989
|
}
|
|
982
990
|
|
|
991
|
+
.calendar-event.has-custom-color .event-title {
|
|
992
|
+
color: var(--event-foreground-color, var(--calendar-event-title-fg));
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
.calendar-event.has-custom-color .event-time {
|
|
996
|
+
color: var(--event-foreground-color, var(--calendar-muted-fg));
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
.calendar-event.has-custom-color .multi-day-indicator {
|
|
1000
|
+
color: var(--event-foreground-color, var(--calendar-accent-fg));
|
|
1001
|
+
}
|
|
1002
|
+
|
|
983
1003
|
.event-icon {
|
|
984
1004
|
flex-shrink: 0;
|
|
985
1005
|
}
|