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
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { Pin02Icon } from '@hugeicons/core-free-icons'
|
|
2
|
+
|
|
3
|
+
export const topBarHubRoutes = [
|
|
4
|
+
'NavLayoutsHub',
|
|
5
|
+
'NavDataDisplayHub',
|
|
6
|
+
'NavFormsInputHub',
|
|
7
|
+
'NavFeedbackStatusHub',
|
|
8
|
+
'NavPatternsHub',
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
export function helloWorld() {
|
|
12
|
+
alert('Hello World')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const navSectionHubs = {
|
|
16
|
+
NavLayoutsHub: {
|
|
17
|
+
title: 'Layouts',
|
|
18
|
+
subtitle: 'Page structure, navigation, and application shell',
|
|
19
|
+
sectionId: 'nav-layouts',
|
|
20
|
+
links: [
|
|
21
|
+
{
|
|
22
|
+
name: 'PageStructure',
|
|
23
|
+
options: { description: 'Configure the example app shell' },
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'ThemePreview',
|
|
27
|
+
title: 'Theme Switcher',
|
|
28
|
+
options: { description: 'Drop-in themes on Femtocrank' },
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'ViewItem',
|
|
32
|
+
title: 'View item',
|
|
33
|
+
options: { params: { id: 1 }, description: 'Detail view with breadcrumbs' },
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'NavigationGridExample',
|
|
37
|
+
title: 'Navigation Grid',
|
|
38
|
+
options: { description: 'Icon grid for settings hubs' },
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'Admin',
|
|
42
|
+
options: { description: 'Admin-style section layout' },
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'TabsExample',
|
|
46
|
+
title: 'Tabs',
|
|
47
|
+
options: { description: 'Tabbed content regions' },
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
NavDataDisplayHub: {
|
|
52
|
+
title: 'Data display',
|
|
53
|
+
subtitle: 'Tables, calendars, and read-only output',
|
|
54
|
+
sectionId: 'nav-data',
|
|
55
|
+
links: [
|
|
56
|
+
{
|
|
57
|
+
name: 'TableExample',
|
|
58
|
+
title: 'Table',
|
|
59
|
+
options: { description: 'Sorting, pagination, and filters' },
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'TableRemoteExample',
|
|
63
|
+
title: 'Remote table',
|
|
64
|
+
options: { description: 'Server-style fetchRows pagination and filters' },
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'CalendarExample',
|
|
68
|
+
title: 'Calendar',
|
|
69
|
+
options: { description: 'Month view with events' },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'ReadOnlyTextAreaExample',
|
|
73
|
+
title: 'Read-only output',
|
|
74
|
+
options: { description: 'Copyable read-only text areas' },
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
NavFormsInputHub: {
|
|
79
|
+
title: 'Forms & input',
|
|
80
|
+
subtitle: 'Buttons, forms, and authentication',
|
|
81
|
+
sectionId: 'nav-forms',
|
|
82
|
+
links: [
|
|
83
|
+
{
|
|
84
|
+
name: 'ButtonsExample',
|
|
85
|
+
title: 'Buttons',
|
|
86
|
+
options: { description: 'Button variants and toolbars' },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'FormExample',
|
|
90
|
+
title: 'Forms',
|
|
91
|
+
options: { description: 'Labels, inputs, and fieldsets' },
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'LoginExample',
|
|
95
|
+
title: 'Login',
|
|
96
|
+
options: { description: 'Auth form with OAuth tabs' },
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
NavFeedbackStatusHub: {
|
|
101
|
+
title: 'Feedback & status',
|
|
102
|
+
subtitle: 'Notifications, dialogs, and inline status',
|
|
103
|
+
sectionId: 'nav-feedback',
|
|
104
|
+
links: [
|
|
105
|
+
{
|
|
106
|
+
name: 'StatusExample',
|
|
107
|
+
title: 'Status & notifications',
|
|
108
|
+
options: { description: 'Block, inline, and popup notifications' },
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'DialogExample',
|
|
112
|
+
title: 'Dialog',
|
|
113
|
+
options: { description: 'Modal dialogs and confirmations' },
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
NavPatternsHub: {
|
|
118
|
+
title: 'Patterns',
|
|
119
|
+
subtitle: 'Reusable UI patterns and interactions',
|
|
120
|
+
sectionId: 'nav-patterns',
|
|
121
|
+
links: [
|
|
122
|
+
{
|
|
123
|
+
type: 'callback',
|
|
124
|
+
title: 'Callback example',
|
|
125
|
+
callback: helloWorld,
|
|
126
|
+
options: {
|
|
127
|
+
icon: Pin02Icon,
|
|
128
|
+
description: 'Navigation item that runs a function instead of routing',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function registerNavLink(nav, link, { onHelloWorld = helloWorld } = {}) {
|
|
136
|
+
if (link.type === 'callback') {
|
|
137
|
+
const callback = link.callback ?? onHelloWorld
|
|
138
|
+
nav.addCallback(link.title, callback, link.options ?? {})
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
nav.addRouterLink(link.name, link.title ?? null, link.options ?? {})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Flat example list for the sidebar — hub routes are not registered here. */
|
|
146
|
+
export function registerSidebarNavigation(nav, { onHelloWorld = helloWorld } = {}) {
|
|
147
|
+
if (!nav) {
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
nav.addRouterLink('Welcome')
|
|
152
|
+
|
|
153
|
+
for (const routeName of topBarHubRoutes) {
|
|
154
|
+
const hub = navSectionHubs[routeName]
|
|
155
|
+
nav.addSection(hub.title, { name: hub.sectionId })
|
|
156
|
+
|
|
157
|
+
for (const link of hub.links) {
|
|
158
|
+
registerNavLink(nav, link, { onHelloWorld })
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Hub category links for the header top bar. */
|
|
164
|
+
export function registerTopBarNavigation(nav) {
|
|
165
|
+
if (!nav) {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const routeName of topBarHubRoutes) {
|
|
170
|
+
nav.addRouterLink(routeName)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Example links for a single hub page (NavigationGrid). */
|
|
175
|
+
export function registerHubNavigation(nav, hubRouteName, { onHelloWorld = helloWorld } = {}) {
|
|
176
|
+
const hub = navSectionHubs[hubRouteName]
|
|
177
|
+
if (!nav || !hub) {
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (const link of hub.links) {
|
|
182
|
+
registerNavLink(nav, link, { onHelloWorld })
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Hub overview grid for the welcome page. */
|
|
187
|
+
export function registerWelcomeHubGrid(nav) {
|
|
188
|
+
if (!nav) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for (const routeName of topBarHubRoutes) {
|
|
193
|
+
const hub = navSectionHubs[routeName]
|
|
194
|
+
nav.addRouterLink(routeName, hub.title, { description: hub.subtitle })
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { navSectionHubs, topBarHubRoutes } from './navSectionHubs.js'
|
|
2
|
+
|
|
3
|
+
/** Example route name → hub route name (from navSectionHubs). */
|
|
4
|
+
const routeToHub = Object.fromEntries(
|
|
5
|
+
topBarHubRoutes.flatMap((hubRouteName) =>
|
|
6
|
+
navSectionHubs[hubRouteName].links
|
|
7
|
+
.filter((link) => link.name)
|
|
8
|
+
.map((link) => [link.name, hubRouteName]),
|
|
9
|
+
),
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
export function welcomeCrumb() {
|
|
13
|
+
return { name: 'Welcome', to: { name: 'Welcome' } }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function hubMenuItems() {
|
|
17
|
+
return topBarHubRoutes.map((hubRouteName) => {
|
|
18
|
+
const hub = navSectionHubs[hubRouteName]
|
|
19
|
+
return {
|
|
20
|
+
name: hub.title,
|
|
21
|
+
to: { name: hubRouteName },
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Hub section picker — all example categories in one dropdown. */
|
|
27
|
+
export function hubMenuCrumb(activeHubRouteName) {
|
|
28
|
+
const hub = navSectionHubs[activeHubRouteName]
|
|
29
|
+
return {
|
|
30
|
+
name: hub.title,
|
|
31
|
+
to: { name: activeHubRouteName },
|
|
32
|
+
menu: hubMenuItems(),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function routeCrumb(routeName, title, params = undefined) {
|
|
37
|
+
return {
|
|
38
|
+
name: title,
|
|
39
|
+
to: params ? { name: routeName, params } : { name: routeName },
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Welcome → hub section (hub landing pages). */
|
|
44
|
+
export function hubSectionBreadcrumbs(hubRouteName) {
|
|
45
|
+
return () => [welcomeCrumb(), hubMenuCrumb(hubRouteName)]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Welcome → hub menu → example page (most grid-linked examples). */
|
|
49
|
+
export function hubChildBreadcrumbs(hubRouteName, routeName, title) {
|
|
50
|
+
return () => [
|
|
51
|
+
welcomeCrumb(),
|
|
52
|
+
hubMenuCrumb(hubRouteName),
|
|
53
|
+
routeCrumb(routeName, title),
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Resolve hub for a route name when only the child is known. */
|
|
58
|
+
export function hubForRoute(routeName) {
|
|
59
|
+
return routeToHub[routeName] ?? null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const viewItemLabels = {
|
|
63
|
+
1: 'Acme deployment',
|
|
64
|
+
Alice: 'Alice',
|
|
65
|
+
Bob: 'Bob',
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function viewItemLabel(id) {
|
|
69
|
+
const key = String(id)
|
|
70
|
+
if (viewItemLabels[key]) {
|
|
71
|
+
return viewItemLabels[key]
|
|
72
|
+
}
|
|
73
|
+
return key.charAt(0).toUpperCase() + key.slice(1)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Table row detail: Welcome → hub menu → Table → record. */
|
|
77
|
+
export function viewItemBreadcrumbs(route) {
|
|
78
|
+
const id = route.params.id
|
|
79
|
+
return [
|
|
80
|
+
welcomeCrumb(),
|
|
81
|
+
hubMenuCrumb('NavDataDisplayHub'),
|
|
82
|
+
routeCrumb('TableExample', 'Table'),
|
|
83
|
+
routeCrumb('ViewItem', viewItemLabel(id), { id }),
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Standalone pages with no hub parent. */
|
|
88
|
+
export function standaloneBreadcrumbs(routeName, title) {
|
|
89
|
+
return () => [welcomeCrumb(), routeCrumb(routeName, title)]
|
|
90
|
+
}
|
package/vue/examples/Admin.vue
CHANGED
|
@@ -10,28 +10,28 @@
|
|
|
10
10
|
<p>Welcome to the administrative panel. This is a placeholder for administrative functionality.</p>
|
|
11
11
|
|
|
12
12
|
<div class="grid-boxed">
|
|
13
|
-
<div class="stat-display">
|
|
13
|
+
<div class="stat-display good">
|
|
14
14
|
<h4>Total Users</h4>
|
|
15
|
-
<
|
|
16
|
-
<
|
|
15
|
+
<span class="stat">1,247</span>
|
|
16
|
+
<span class="subtle">+12% from last month</span>
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
<div class="stat-display">
|
|
19
|
+
<div class="stat-display bad">
|
|
20
20
|
<h4>Active Sessions</h4>
|
|
21
|
-
<
|
|
22
|
-
<
|
|
21
|
+
<span class="stat">89</span>
|
|
22
|
+
<span class="subtle">-3% from last hour</span>
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
|
-
<div class="stat-display">
|
|
25
|
+
<div class="stat-display warning">
|
|
26
26
|
<h4>System Load</h4>
|
|
27
|
-
<
|
|
28
|
-
<
|
|
27
|
+
<span class="stat">67%</span>
|
|
28
|
+
<span class="subtle">Moderate</span>
|
|
29
29
|
</div>
|
|
30
30
|
|
|
31
|
-
<div class="stat-display">
|
|
31
|
+
<div class="stat-display good">
|
|
32
32
|
<h4>Storage Used</h4>
|
|
33
|
-
<
|
|
34
|
-
<
|
|
33
|
+
<span class="stat">2.4 GB</span>
|
|
34
|
+
<span class="subtle">45% of total</span>
|
|
35
35
|
</div>
|
|
36
36
|
</div>
|
|
37
37
|
</div>
|
package/vue/examples/App.vue
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Navigation ref="navigation">
|
|
3
|
-
<
|
|
3
|
+
<Navigation ref="topBarNavigation">
|
|
4
|
+
<Header
|
|
5
|
+
username="Guest"
|
|
6
|
+
@toggleSidebar="toggleSidebar"
|
|
7
|
+
@logoClick="goToIndex"
|
|
8
|
+
@userClick="goToUserDetails"
|
|
9
|
+
title="PicoCrank"
|
|
10
|
+
:logoUrl="logoUrl"
|
|
11
|
+
:sidebarEnabled="sidebarEnabled"
|
|
12
|
+
:navigation="navigation"
|
|
13
|
+
:topBarNavigation="topBarNavigation"
|
|
14
|
+
:topBarEnabled="topBarEnabled"
|
|
15
|
+
:showBranding="brandingEnabled"
|
|
16
|
+
:breadcrumbs="breadcrumbsEnabled"
|
|
17
|
+
:themeToggleEnabled="themeToggleEnabled"
|
|
18
|
+
>
|
|
4
19
|
<template #toolbar>
|
|
5
20
|
<ExampleHeaderThemeSwitcher
|
|
6
21
|
v-if="themeSwitcherEnabled"
|
|
@@ -16,6 +31,7 @@
|
|
|
16
31
|
/>
|
|
17
32
|
</template>
|
|
18
33
|
</Header>
|
|
34
|
+
</Navigation>
|
|
19
35
|
|
|
20
36
|
<div id = "layout">
|
|
21
37
|
<Sidebar v-if="sidebarEnabled" ref = "sidebar" />
|
|
@@ -38,7 +54,7 @@
|
|
|
38
54
|
<script setup>
|
|
39
55
|
import { ref, onMounted, provide, watch } from 'vue';
|
|
40
56
|
import { useRouter } from 'vue-router';
|
|
41
|
-
import {
|
|
57
|
+
import { UserIcon } from '@hugeicons/core-free-icons'
|
|
42
58
|
|
|
43
59
|
import QuickSearch from './../components/QuickSearch.vue'
|
|
44
60
|
import ExampleHeaderThemeSwitcher from './ExampleHeaderThemeSwitcher.vue'
|
|
@@ -48,12 +64,18 @@
|
|
|
48
64
|
import '../composables/useTheme.js'
|
|
49
65
|
import { initCustomTheme } from '../composables/useCustomTheme.js'
|
|
50
66
|
import { examplePeople } from '../data/examplePeople.js'
|
|
67
|
+
import {
|
|
68
|
+
helloWorld,
|
|
69
|
+
registerSidebarNavigation,
|
|
70
|
+
registerTopBarNavigation,
|
|
71
|
+
} from '../data/navSectionHubs.js'
|
|
51
72
|
import logoUrl from '/logo.png';
|
|
52
73
|
|
|
53
74
|
const router = useRouter();
|
|
54
75
|
const quickSearchRef = ref(null)
|
|
55
76
|
const sidebar = ref(null);
|
|
56
77
|
const navigation = ref(null);
|
|
78
|
+
const topBarNavigation = ref(null);
|
|
57
79
|
const sidebarEnabled = ref(true);
|
|
58
80
|
const brandingEnabled = ref(true);
|
|
59
81
|
const quickSearchEnabled = ref(true);
|
|
@@ -62,7 +84,7 @@
|
|
|
62
84
|
const themeSwitcherEnabled = ref(true);
|
|
63
85
|
const includeSupplementalThemes = ref(true);
|
|
64
86
|
const topBarEnabled = ref(false);
|
|
65
|
-
const breadcrumbsEnabled = ref(
|
|
87
|
+
const breadcrumbsEnabled = ref(true);
|
|
66
88
|
|
|
67
89
|
const { discoverThemes } = initCustomTheme({ includeSupplementalThemes: true });
|
|
68
90
|
|
|
@@ -188,35 +210,8 @@
|
|
|
188
210
|
onMounted(() => {
|
|
189
211
|
discoverThemes()
|
|
190
212
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
navigation.value.addSection('Layouts', { name: 'nav-layouts' })
|
|
195
|
-
navigation.value.addRouterLink('PageStructure', 'Page structure')
|
|
196
|
-
navigation.value.addRouterLink('ThemePreview', 'Theme Switcher')
|
|
197
|
-
navigation.value.addRouterLink('ViewItem', 'View item', { params: { id: 1 } })
|
|
198
|
-
navigation.value.addRouterLink('NavigationGridExample', 'Navigation Grid')
|
|
199
|
-
navigation.value.addRouterLink('Admin')
|
|
200
|
-
navigation.value.addRouterLink('TabsExample', 'Tabs')
|
|
201
|
-
|
|
202
|
-
navigation.value.addSection('Data display', { name: 'nav-data' })
|
|
203
|
-
navigation.value.addRouterLink('TableExample', 'Table')
|
|
204
|
-
navigation.value.addRouterLink('TableRemoteExample', 'Remote table')
|
|
205
|
-
navigation.value.addRouterLink('CalendarExample', 'Calendar')
|
|
206
|
-
navigation.value.addRouterLink('ReadOnlyTextAreaExample', 'Read-only output')
|
|
207
|
-
|
|
208
|
-
navigation.value.addSection('Forms & input', { name: 'nav-forms' })
|
|
209
|
-
navigation.value.addRouterLink('ButtonsExample', 'Buttons')
|
|
210
|
-
navigation.value.addRouterLink('FormExample', 'Forms')
|
|
211
|
-
navigation.value.addRouterLink('LoginExample', 'Login')
|
|
212
|
-
|
|
213
|
-
navigation.value.addSection('Feedback & status', { name: 'nav-feedback' })
|
|
214
|
-
navigation.value.addRouterLink('StatusExample', 'Status & notifications')
|
|
215
|
-
navigation.value.addRouterLink('DialogExample', 'Dialog')
|
|
216
|
-
|
|
217
|
-
navigation.value.addSection('Patterns', { name: 'nav-patterns' })
|
|
218
|
-
navigation.value.addCallback('Callback example', helloWorld, { icon: Pin02Icon })
|
|
219
|
-
}
|
|
213
|
+
registerSidebarNavigation(navigation.value, { onHelloWorld: helloWorld })
|
|
214
|
+
registerTopBarNavigation(topBarNavigation.value)
|
|
220
215
|
|
|
221
216
|
if (sidebar.value) {
|
|
222
217
|
sidebar.value.open();
|
|
@@ -226,8 +221,4 @@
|
|
|
226
221
|
registerQuickSearchItems()
|
|
227
222
|
});
|
|
228
223
|
|
|
229
|
-
function helloWorld() {
|
|
230
|
-
alert('Hello World')
|
|
231
|
-
}
|
|
232
|
-
|
|
233
224
|
</script>
|
|
@@ -53,6 +53,20 @@ import {
|
|
|
53
53
|
AlertCircleIcon,
|
|
54
54
|
} from '@hugeicons/core-free-icons'
|
|
55
55
|
|
|
56
|
+
/** Static event palette — background + accessible dark foreground for light pastels in any theme. */
|
|
57
|
+
const EVENT_COLORS = {
|
|
58
|
+
weekend: { color: '#e8d5f5', foregroundColor: '#4a2d6b' },
|
|
59
|
+
teamMeeting: { color: '#d1fae5', foregroundColor: '#14532d' },
|
|
60
|
+
clientCall: { color: '#e0f2fe', foregroundColor: '#075985' },
|
|
61
|
+
projectReview: { color: '#ede9fe', foregroundColor: '#5b21b6' },
|
|
62
|
+
lunchBreak: { color: '#ffedd5', foregroundColor: '#9a3412' },
|
|
63
|
+
codeReview: { color: '#ccfbf1', foregroundColor: '#115e59' },
|
|
64
|
+
conference: { color: '#d6e8ff', foregroundColor: '#1e40af' },
|
|
65
|
+
vacation: { color: '#fff0d6', foregroundColor: '#92400e' },
|
|
66
|
+
holiday: { color: '#ffe0e0', foregroundColor: '#991b1b' },
|
|
67
|
+
deadline: { color: '#fde8e8', foregroundColor: '#7f1d1d' },
|
|
68
|
+
}
|
|
69
|
+
|
|
56
70
|
const events = ref([])
|
|
57
71
|
const loading = ref(false)
|
|
58
72
|
const error = ref(null)
|
|
@@ -99,30 +113,32 @@ function generateDummyEventsForMonth(monthIndex, year) {
|
|
|
99
113
|
date: new Date(year, monthIndex, day, 10, 0),
|
|
100
114
|
description: 'Relaxing weekend activity',
|
|
101
115
|
icon: PartyIcon,
|
|
102
|
-
|
|
116
|
+
...EVENT_COLORS.weekend,
|
|
103
117
|
})
|
|
104
118
|
}
|
|
105
119
|
|
|
106
120
|
// Add some random events
|
|
107
121
|
if (random() > 0.7) {
|
|
108
122
|
const eventTypes = [
|
|
109
|
-
{ title: 'Team Meeting', time: [9, 0], duration: 1 },
|
|
110
|
-
{ title: 'Client Call', time: [14, 30], duration: 0.5 },
|
|
111
|
-
{ title: 'Project Review', time: [16, 0], duration: 2 },
|
|
112
|
-
{ title: 'Lunch Break', time: [12, 0], duration: 1 },
|
|
113
|
-
{ title: 'Code Review', time: [10, 0], duration: 1.5 }
|
|
123
|
+
{ title: 'Team Meeting', time: [9, 0], duration: 1, ...EVENT_COLORS.teamMeeting },
|
|
124
|
+
{ title: 'Client Call', time: [14, 30], duration: 0.5, ...EVENT_COLORS.clientCall },
|
|
125
|
+
{ title: 'Project Review', time: [16, 0], duration: 2, ...EVENT_COLORS.projectReview },
|
|
126
|
+
{ title: 'Lunch Break', time: [12, 0], duration: 1, ...EVENT_COLORS.lunchBreak },
|
|
127
|
+
{ title: 'Code Review', time: [10, 0], duration: 1.5, ...EVENT_COLORS.codeReview },
|
|
114
128
|
]
|
|
115
129
|
|
|
116
130
|
const eventType = eventTypes[Math.floor(random() * eventTypes.length)]
|
|
117
|
-
const
|
|
131
|
+
const { title, time, color, foregroundColor } = eventType
|
|
132
|
+
const [hours, minutes] = time
|
|
118
133
|
|
|
119
134
|
dummyEvents.push({
|
|
120
135
|
id: `event-${year}-${monthIndex}-${day}-${Math.random().toString(36).substr(2, 9)}`,
|
|
121
|
-
title
|
|
136
|
+
title,
|
|
122
137
|
date: new Date(year, monthIndex, day, hours, minutes),
|
|
123
|
-
description: `Scheduled ${
|
|
124
|
-
icon:
|
|
125
|
-
color
|
|
138
|
+
description: `Scheduled ${title.toLowerCase()}`,
|
|
139
|
+
icon: title === 'Team Meeting' ? MeetingRoomIcon : undefined,
|
|
140
|
+
color,
|
|
141
|
+
foregroundColor,
|
|
126
142
|
})
|
|
127
143
|
}
|
|
128
144
|
}
|
|
@@ -135,7 +151,7 @@ function generateDummyEventsForMonth(monthIndex, year) {
|
|
|
135
151
|
endDate: new Date(year, monthIndex, 7, 17, 0),
|
|
136
152
|
description: 'Annual technology conference',
|
|
137
153
|
icon: Calendar01Icon,
|
|
138
|
-
|
|
154
|
+
...EVENT_COLORS.conference,
|
|
139
155
|
})
|
|
140
156
|
|
|
141
157
|
dummyEvents.push({
|
|
@@ -145,7 +161,7 @@ function generateDummyEventsForMonth(monthIndex, year) {
|
|
|
145
161
|
endDate: new Date(year, monthIndex, 18, 23, 59),
|
|
146
162
|
description: 'Family vacation time',
|
|
147
163
|
icon: BeachIcon,
|
|
148
|
-
|
|
164
|
+
...EVENT_COLORS.vacation,
|
|
149
165
|
})
|
|
150
166
|
|
|
151
167
|
// Add some all-day events
|
|
@@ -155,7 +171,7 @@ function generateDummyEventsForMonth(monthIndex, year) {
|
|
|
155
171
|
date: new Date(year, monthIndex, 1, 0, 0),
|
|
156
172
|
description: 'National holiday - office closed',
|
|
157
173
|
icon: PartyIcon,
|
|
158
|
-
|
|
174
|
+
...EVENT_COLORS.holiday,
|
|
159
175
|
})
|
|
160
176
|
|
|
161
177
|
if (daysInMonth >= 25) {
|
|
@@ -165,7 +181,7 @@ function generateDummyEventsForMonth(monthIndex, year) {
|
|
|
165
181
|
date: new Date(year, monthIndex, 25, 0, 0),
|
|
166
182
|
description: 'Important project deadline',
|
|
167
183
|
icon: AlertCircleIcon,
|
|
168
|
-
|
|
184
|
+
...EVENT_COLORS.deadline,
|
|
169
185
|
})
|
|
170
186
|
}
|
|
171
187
|
|
|
@@ -204,12 +220,6 @@ function getEventsForSurroundingMonths(monthIndex, year) {
|
|
|
204
220
|
]
|
|
205
221
|
}
|
|
206
222
|
|
|
207
|
-
// Generate dummy events for the current month (backward compatibility wrapper)
|
|
208
|
-
function generateDummyEvents() {
|
|
209
|
-
const now = new Date()
|
|
210
|
-
return getEventsForMonth(now.getMonth(), now.getFullYear())
|
|
211
|
-
}
|
|
212
|
-
|
|
213
223
|
function handleEventClick(event) {
|
|
214
224
|
console.log('Event clicked:', event)
|
|
215
225
|
alert(`Event: ${event.title}\nDescription: ${event.description || 'No description'}`)
|
|
@@ -232,7 +242,7 @@ function handleDateRangeSelect(start, end) {
|
|
|
232
242
|
function handleEventMoveRequest({ respond, event, sourceDate, targetDate }) {
|
|
233
243
|
window.setTimeout(() => {
|
|
234
244
|
const ok = window.confirm(
|
|
235
|
-
`Move "${event.title}" to ${targetDate.toLocaleDateString()}? (Cancel = decline)`
|
|
245
|
+
`Move "${event.title}" from ${sourceDate.toLocaleDateString()} to ${targetDate.toLocaleDateString()}? (Cancel = decline)`
|
|
236
246
|
)
|
|
237
247
|
respond(ok)
|
|
238
248
|
}, 50)
|
|
@@ -56,7 +56,7 @@ header .header-actions .theme-switcher--compact select {
|
|
|
56
56
|
border: 0;
|
|
57
57
|
border-radius: 0;
|
|
58
58
|
background-color: transparent;
|
|
59
|
-
color: var(--header-fg-color
|
|
59
|
+
color: var(--header-fg-color);
|
|
60
60
|
padding-top: 0;
|
|
61
61
|
padding-bottom: 0;
|
|
62
62
|
padding-left: 0.6em;
|
package/vue/examples/Login.vue
CHANGED
|
@@ -9,12 +9,9 @@
|
|
|
9
9
|
</template>
|
|
10
10
|
|
|
11
11
|
<script setup>
|
|
12
|
-
import { useRouter } from 'vue-router';
|
|
13
12
|
import Section from '../components/Section.vue';
|
|
14
13
|
import Login from '../components/Login.vue';
|
|
15
14
|
|
|
16
|
-
const router = useRouter();
|
|
17
|
-
|
|
18
15
|
// Handle local login
|
|
19
16
|
function handleLocalLogin(credentials) {
|
|
20
17
|
console.log('Local login attempt:', {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
@tab-change="onTabChange"
|
|
9
9
|
>
|
|
10
10
|
<!-- Custom tab: Magic Link Login -->
|
|
11
|
-
<template #tab-magic-link
|
|
11
|
+
<template #tab-magic-link>
|
|
12
12
|
<div class="login-section">
|
|
13
13
|
<form @submit.prevent="handleMagicLinkLogin" class="magic-link-form">
|
|
14
14
|
<input
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
</template>
|
|
31
31
|
|
|
32
32
|
<!-- Custom tab: SSO Login -->
|
|
33
|
-
<template #tab-sso
|
|
33
|
+
<template #tab-sso>
|
|
34
34
|
<div class="login-section">
|
|
35
35
|
<div class="sso-providers">
|
|
36
36
|
<button
|
|
@@ -54,12 +54,9 @@
|
|
|
54
54
|
|
|
55
55
|
<script setup>
|
|
56
56
|
import { ref } from 'vue';
|
|
57
|
-
import { useRouter } from 'vue-router';
|
|
58
57
|
import Section from '../components/Section.vue';
|
|
59
58
|
import Login from '../components/Login.vue';
|
|
60
59
|
|
|
61
|
-
const router = useRouter();
|
|
62
|
-
|
|
63
60
|
// Custom tabs configuration
|
|
64
61
|
const customTabs = ref([
|
|
65
62
|
{ id: 'magic-link', label: 'Magic Link' },
|
|
@@ -232,19 +229,10 @@ function handleSSOLogin(provider) {
|
|
|
232
229
|
}
|
|
233
230
|
|
|
234
231
|
.magic-link-form input {
|
|
235
|
-
padding: 0.75rem;
|
|
236
|
-
border-radius: 4px;
|
|
237
|
-
font-size: 1em;
|
|
238
232
|
width: 100%;
|
|
239
233
|
box-sizing: border-box;
|
|
240
234
|
}
|
|
241
235
|
|
|
242
|
-
.magic-link-form input:focus {
|
|
243
|
-
outline: none;
|
|
244
|
-
border-color: var(--primary-color, #007bff);
|
|
245
|
-
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
236
|
.magic-link-form button,
|
|
249
237
|
.sso-button {
|
|
250
238
|
width: 100%;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Section v-if="hub" :title="hub.title" :subtitle="hub.subtitle">
|
|
3
|
+
<Navigation ref="localNavigation">
|
|
4
|
+
<NavigationGrid />
|
|
5
|
+
</Navigation>
|
|
6
|
+
</Section>
|
|
7
|
+
<Section
|
|
8
|
+
v-else
|
|
9
|
+
title="Unknown section"
|
|
10
|
+
subtitle="This navigation hub is not configured."
|
|
11
|
+
>
|
|
12
|
+
<p class="subtle">
|
|
13
|
+
Return to the welcome page and pick an example category from the top bar or sidebar.
|
|
14
|
+
</p>
|
|
15
|
+
<router-link :to="{ name: 'Welcome' }" class="button neutral">
|
|
16
|
+
Back to welcome
|
|
17
|
+
</router-link>
|
|
18
|
+
</Section>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup>
|
|
22
|
+
import { ref, onMounted, computed } from 'vue'
|
|
23
|
+
import { useRoute } from 'vue-router'
|
|
24
|
+
import Section from '../components/Section.vue'
|
|
25
|
+
import Navigation from '../components/Navigation.vue'
|
|
26
|
+
import NavigationGrid from '../components/NavigationGrid.vue'
|
|
27
|
+
import { navSectionHubs, registerHubNavigation } from '../data/navSectionHubs.js'
|
|
28
|
+
|
|
29
|
+
const route = useRoute()
|
|
30
|
+
const localNavigation = ref(null)
|
|
31
|
+
const hub = computed(() => navSectionHubs[route.name])
|
|
32
|
+
|
|
33
|
+
onMounted(() => {
|
|
34
|
+
registerHubNavigation(localNavigation.value, route.name)
|
|
35
|
+
})
|
|
36
|
+
</script>
|