picocrank 1.12.5 → 1.13.1
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/package.json +1 -1
- package/vue/components/Header.vue +26 -3
- package/vue/components/TopBar.vue +132 -0
- package/vue/views/App.vue +36 -11
- package/vue/views/FormExample.vue +197 -216
- package/vue/views/Welcome.vue +24 -4
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<header>
|
|
3
|
-
<div
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
<div
|
|
4
|
+
:id = "sidebarEnabled ? 'sidebar-button' : null"
|
|
5
|
+
:class="['image-and-title', 'flex-row', { 'disabled-branding': !showBranding }]"
|
|
6
|
+
@click = "sidebarEnabled && emit('toggleSidebar')"
|
|
7
|
+
>
|
|
8
|
+
<img v-if="showBranding" :src = "logoUrl" alt = "Logo" class = "logo" />
|
|
9
|
+
<h1 v-if="showBranding">{{ title }}</h1>
|
|
6
10
|
|
|
7
11
|
<div class = "fg1" />
|
|
8
12
|
|
|
@@ -11,6 +15,8 @@
|
|
|
11
15
|
</button>
|
|
12
16
|
</div>
|
|
13
17
|
|
|
18
|
+
<TopBar v-if="topBarEnabled" :navigation="navigation" />
|
|
19
|
+
|
|
14
20
|
<Breadcrumbs v-if="breadcrumbs" />
|
|
15
21
|
|
|
16
22
|
<slot name="toolbar" />
|
|
@@ -30,6 +36,7 @@
|
|
|
30
36
|
import { Menu01Icon } from "@hugeicons/core-free-icons";
|
|
31
37
|
|
|
32
38
|
import Breadcrumbs from "./Breadcrumbs.vue";
|
|
39
|
+
import TopBar from "./TopBar.vue";
|
|
33
40
|
|
|
34
41
|
const emit = defineEmits(["toggleSidebar"]);
|
|
35
42
|
|
|
@@ -54,6 +61,18 @@
|
|
|
54
61
|
type: Boolean,
|
|
55
62
|
default: true,
|
|
56
63
|
},
|
|
64
|
+
showBranding: {
|
|
65
|
+
type: Boolean,
|
|
66
|
+
default: true,
|
|
67
|
+
},
|
|
68
|
+
topBarEnabled: {
|
|
69
|
+
type: Boolean,
|
|
70
|
+
default: false,
|
|
71
|
+
},
|
|
72
|
+
navigation: {
|
|
73
|
+
type: Object,
|
|
74
|
+
default: null,
|
|
75
|
+
},
|
|
57
76
|
});
|
|
58
77
|
</script>
|
|
59
78
|
|
|
@@ -67,6 +86,10 @@ button:hover {
|
|
|
67
86
|
background-color: transparent;
|
|
68
87
|
}
|
|
69
88
|
|
|
89
|
+
#sidebar-button.disabled-branding {
|
|
90
|
+
width: auto;
|
|
91
|
+
}
|
|
92
|
+
|
|
70
93
|
.user-info span {
|
|
71
94
|
padding-right: 0.5rem;
|
|
72
95
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<nav class="topbar">
|
|
3
|
+
<div class="topbar-links flex-row">
|
|
4
|
+
<template v-for="link in navigationLinks" :key="link.name">
|
|
5
|
+
<!-- Render separator as a divider -->
|
|
6
|
+
<div v-if="link.type === 'separator'" class="separator"></div>
|
|
7
|
+
<!-- Render callback link -->
|
|
8
|
+
<a v-else-if="link.type === 'callback'" href="#" @click.prevent="handleLinkClick(() => link.callback())" class="button topbar-link">
|
|
9
|
+
<HugeiconsIcon :icon="link.icon" width="1em" height="1em" />
|
|
10
|
+
<span>{{ link.title }}</span>
|
|
11
|
+
</a>
|
|
12
|
+
<!-- Render HTML content -->
|
|
13
|
+
<div v-else-if="link.type === 'html'" v-html="link.html" class="topbar-html"></div>
|
|
14
|
+
<!-- Render router link -->
|
|
15
|
+
<router-link v-else v-bind="link.props || {}" :to="link.to || link.path" :class="['button', 'topbar-link', { active: isActive(link) }]">
|
|
16
|
+
<HugeiconsIcon :icon="link.icon" width="1em" height="1em" />
|
|
17
|
+
<span>{{ link.title }}</span>
|
|
18
|
+
</router-link>
|
|
19
|
+
</template>
|
|
20
|
+
</div>
|
|
21
|
+
</nav>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script setup>
|
|
25
|
+
import { computed, inject } from 'vue'
|
|
26
|
+
import { HugeiconsIcon } from '@hugeicons/vue'
|
|
27
|
+
|
|
28
|
+
const props = defineProps({
|
|
29
|
+
navigation: {
|
|
30
|
+
type: Object,
|
|
31
|
+
default: null
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// Try to get navigation from inject first, fall back to prop
|
|
36
|
+
const injectedNavigation = inject('navigation', null)
|
|
37
|
+
|
|
38
|
+
// Get navigation object - handle both component ref and direct object
|
|
39
|
+
const navigation = computed(() => {
|
|
40
|
+
const nav = props.navigation || injectedNavigation
|
|
41
|
+
if (!nav) return null
|
|
42
|
+
|
|
43
|
+
// If it's a component ref, access its exposed API
|
|
44
|
+
if (nav.value) {
|
|
45
|
+
return nav.value
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Otherwise, assume it's the navigation object directly
|
|
49
|
+
return nav
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// Get navigation links and isActive function from navigation
|
|
53
|
+
// navigationLinks is a ref, so we return it directly (Vue will unwrap it in template)
|
|
54
|
+
const navigationLinks = computed(() => {
|
|
55
|
+
const nav = navigation.value
|
|
56
|
+
if (!nav || !nav.navigationLinks) {
|
|
57
|
+
return []
|
|
58
|
+
}
|
|
59
|
+
// Return the ref itself - Vue will unwrap it in the template
|
|
60
|
+
return nav.navigationLinks
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// Get isActive function - create a function that accesses the navigation computed
|
|
64
|
+
function isActive(link) {
|
|
65
|
+
const nav = navigation.value
|
|
66
|
+
if (!nav || !nav.isActive) {
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
return nav.isActive(link)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function handleLinkClick(callback = null) {
|
|
73
|
+
if (callback) {
|
|
74
|
+
callback()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<style scoped>
|
|
80
|
+
.topbar {
|
|
81
|
+
width: 100%;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.topbar-links {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: 0.125rem;
|
|
88
|
+
flex-wrap: wrap;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.topbar-link {
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: 0.375rem;
|
|
95
|
+
padding: 0.5em 0.75em;
|
|
96
|
+
border-radius: 0.25em;
|
|
97
|
+
text-decoration: none;
|
|
98
|
+
color: inherit;
|
|
99
|
+
transition: background-color 0.15s ease;
|
|
100
|
+
white-space: nowrap;
|
|
101
|
+
border: 0;
|
|
102
|
+
font-weight: normal;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.topbar-link.active {
|
|
106
|
+
text-decoration: underline;
|
|
107
|
+
font-weight: 500;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.topbar-html {
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.separator {
|
|
116
|
+
width: 1px;
|
|
117
|
+
height: 1.5em;
|
|
118
|
+
background-color: currentColor;
|
|
119
|
+
opacity: 0.3;
|
|
120
|
+
margin: 0 0.125rem;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@media (prefers-color-scheme: dark) {
|
|
124
|
+
.topbar-link {
|
|
125
|
+
color: #f8f9fa;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.separator {
|
|
129
|
+
opacity: 0.5;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
</style>
|
package/vue/views/App.vue
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Header username = "Guest" @toggleSidebar="toggleSidebar" title = "PicoCrank" :logoUrl="logoUrl" :sidebarEnabled="sidebarEnabled">
|
|
2
|
+
<Header username = "Guest" @toggleSidebar="toggleSidebar" title = "PicoCrank" :logoUrl="logoUrl" :sidebarEnabled="sidebarEnabled" :navigation="navigation" :topBarEnabled="topBarEnabled" :showBranding="brandingEnabled">
|
|
3
3
|
<template #toolbar>
|
|
4
4
|
<QuickSearch
|
|
5
|
+
v-if="quickSearchEnabled"
|
|
5
6
|
ref="quickSearchRef"
|
|
6
7
|
placeholder="Search items..."
|
|
7
8
|
:search-fields="['title', 'name']"
|
|
@@ -42,6 +43,9 @@
|
|
|
42
43
|
const sidebar = ref(null);
|
|
43
44
|
const navigation = ref(null);
|
|
44
45
|
const sidebarEnabled = ref(true);
|
|
46
|
+
const brandingEnabled = ref(true);
|
|
47
|
+
const quickSearchEnabled = ref(true);
|
|
48
|
+
const topBarEnabled = ref(false);
|
|
45
49
|
|
|
46
50
|
function toggleSidebar() {
|
|
47
51
|
if (sidebar.value) {
|
|
@@ -53,9 +57,28 @@
|
|
|
53
57
|
sidebarEnabled.value = !sidebarEnabled.value;
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
function toggleBrandingEnabled() {
|
|
61
|
+
brandingEnabled.value = !brandingEnabled.value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function toggleQuickSearchEnabled() {
|
|
65
|
+
quickSearchEnabled.value = !quickSearchEnabled.value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function toggleTopBarEnabled() {
|
|
69
|
+
topBarEnabled.value = !topBarEnabled.value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Provide sidebar, QuickSearch, and TopBar state and toggle functions for child components
|
|
57
73
|
provide('sidebarEnabled', sidebarEnabled);
|
|
58
74
|
provide('toggleSidebarEnabled', toggleSidebarEnabled);
|
|
75
|
+
provide('brandingEnabled', brandingEnabled);
|
|
76
|
+
provide('toggleBrandingEnabled', toggleBrandingEnabled);
|
|
77
|
+
|
|
78
|
+
provide('quickSearchEnabled', quickSearchEnabled);
|
|
79
|
+
provide('toggleQuickSearchEnabled', toggleQuickSearchEnabled);
|
|
80
|
+
provide('topBarEnabled', topBarEnabled);
|
|
81
|
+
provide('toggleTopBarEnabled', toggleTopBarEnabled);
|
|
59
82
|
|
|
60
83
|
onMounted(() => {
|
|
61
84
|
if (navigation.value) {
|
|
@@ -84,15 +107,17 @@
|
|
|
84
107
|
sidebar.value.stick();
|
|
85
108
|
}
|
|
86
109
|
|
|
87
|
-
quickSearchRef.value
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
110
|
+
if (quickSearchRef.value) {
|
|
111
|
+
quickSearchRef.value.addItem({
|
|
112
|
+
id: 'hello-world',
|
|
113
|
+
title: 'Hello World',
|
|
114
|
+
name: 'hello-world',
|
|
115
|
+
description: 'This is a test item',
|
|
116
|
+
category: 'Actions',
|
|
117
|
+
type: 'callback',
|
|
118
|
+
callback: helloWorld
|
|
119
|
+
})
|
|
120
|
+
}
|
|
96
121
|
});
|
|
97
122
|
|
|
98
123
|
function helloWorld() {
|
|
@@ -1,102 +1,186 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Section
|
|
3
3
|
title="Form Example"
|
|
4
|
-
subtitle="A simple form
|
|
5
|
-
|
|
4
|
+
subtitle="A simple form demonstrating Femtocrank form layout"
|
|
5
|
+
>
|
|
6
6
|
<template #toolbar>
|
|
7
7
|
<button @click="resetForm">Reset</button>
|
|
8
8
|
</template>
|
|
9
9
|
|
|
10
10
|
<form @submit.prevent="handleSubmit">
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
placeholder="Enter your message"
|
|
52
|
-
rows="4"
|
|
53
|
-
></textarea>
|
|
11
|
+
<!-- Name -->
|
|
12
|
+
<label for="name">Name</label>
|
|
13
|
+
<input
|
|
14
|
+
id="name"
|
|
15
|
+
v-model="formData.name"
|
|
16
|
+
type="text"
|
|
17
|
+
placeholder="Enter your name"
|
|
18
|
+
required
|
|
19
|
+
/>
|
|
20
|
+
|
|
21
|
+
<!-- Role (radio group) -->
|
|
22
|
+
<span class="fake-label">Role</span>
|
|
23
|
+
<div class="radio-group">
|
|
24
|
+
<label>
|
|
25
|
+
<input
|
|
26
|
+
type="radio"
|
|
27
|
+
name="role"
|
|
28
|
+
value="sales"
|
|
29
|
+
v-model="formData.role"
|
|
30
|
+
/>
|
|
31
|
+
<span>Sales</span>
|
|
32
|
+
</label>
|
|
33
|
+
<label>
|
|
34
|
+
<input
|
|
35
|
+
type="radio"
|
|
36
|
+
name="role"
|
|
37
|
+
value="engineering"
|
|
38
|
+
v-model="formData.role"
|
|
39
|
+
/>
|
|
40
|
+
<span>Engineering</span>
|
|
41
|
+
</label>
|
|
42
|
+
<label>
|
|
43
|
+
<input
|
|
44
|
+
type="radio"
|
|
45
|
+
name="role"
|
|
46
|
+
value="hr"
|
|
47
|
+
v-model="formData.role"
|
|
48
|
+
/>
|
|
49
|
+
<span>HR</span>
|
|
50
|
+
</label>
|
|
54
51
|
</div>
|
|
55
52
|
|
|
56
|
-
|
|
53
|
+
<!-- Salary (select) -->
|
|
54
|
+
<label for="salary">Salary</label>
|
|
55
|
+
<select id="salary" v-model="formData.salary">
|
|
56
|
+
<option value="0">Select a salary</option>
|
|
57
|
+
<option value="10000">£10,000</option>
|
|
58
|
+
<option value="20000">£20,000</option>
|
|
59
|
+
<option value="30000">£30,000</option>
|
|
60
|
+
<option value="40000">£40,000</option>
|
|
61
|
+
<option value="50000">£50,000</option>
|
|
62
|
+
</select>
|
|
63
|
+
|
|
64
|
+
<!-- Email -->
|
|
65
|
+
<label for="email">Email</label>
|
|
66
|
+
<input
|
|
67
|
+
id="email"
|
|
68
|
+
v-model="formData.email"
|
|
69
|
+
type="email"
|
|
70
|
+
placeholder="Enter your email"
|
|
71
|
+
required
|
|
72
|
+
/>
|
|
73
|
+
|
|
74
|
+
<!-- Administrator checkbox -->
|
|
75
|
+
<label>Is Administrator?</label>
|
|
76
|
+
<input
|
|
77
|
+
type="checkbox"
|
|
78
|
+
id="is-admin"
|
|
79
|
+
v-model="formData.isAdmin"
|
|
80
|
+
/>
|
|
81
|
+
|
|
82
|
+
<!-- Website -->
|
|
83
|
+
<label for="website">Website</label>
|
|
84
|
+
<input
|
|
85
|
+
id="website"
|
|
86
|
+
v-model="formData.website"
|
|
87
|
+
type="url"
|
|
88
|
+
placeholder="https://example.com"
|
|
89
|
+
/>
|
|
90
|
+
|
|
91
|
+
<!-- Favourite food (disabled) -->
|
|
92
|
+
<label for="favourite-food">Favourite Food</label>
|
|
93
|
+
<input
|
|
94
|
+
id="favourite-food"
|
|
95
|
+
v-model="formData.favouriteFood"
|
|
96
|
+
type="text"
|
|
97
|
+
placeholder="e.g. Pizza"
|
|
98
|
+
disabled
|
|
99
|
+
/>
|
|
100
|
+
|
|
101
|
+
<!-- Favourite colour -->
|
|
102
|
+
<label for="favourite-colour">Favourite Colour</label>
|
|
103
|
+
<input
|
|
104
|
+
id="favourite-colour"
|
|
105
|
+
v-model="formData.favouriteColour"
|
|
106
|
+
type="color"
|
|
107
|
+
/>
|
|
108
|
+
|
|
109
|
+
<!-- Favourite number -->
|
|
110
|
+
<label for="favourite-number">Favourite Number</label>
|
|
111
|
+
<input
|
|
112
|
+
id="favourite-number"
|
|
113
|
+
v-model.number="formData.favouriteNumber"
|
|
114
|
+
type="number"
|
|
115
|
+
min="0"
|
|
116
|
+
max="100"
|
|
117
|
+
step="1"
|
|
118
|
+
/>
|
|
119
|
+
|
|
120
|
+
<!-- Newsletters (checkbox group) -->
|
|
121
|
+
<span class="fake-label">
|
|
122
|
+
Newsletters
|
|
123
|
+
</span>
|
|
124
|
+
<div>
|
|
57
125
|
<label>
|
|
58
126
|
<input
|
|
59
|
-
type="checkbox"
|
|
60
|
-
|
|
127
|
+
type="checkbox"
|
|
128
|
+
id="newsletter1"
|
|
129
|
+
v-model="formData.newsletter1"
|
|
61
130
|
/>
|
|
62
|
-
|
|
131
|
+
<span>Newsletter 1</span>
|
|
63
132
|
</label>
|
|
64
|
-
</div>
|
|
65
133
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
/>
|
|
75
|
-
Email
|
|
76
|
-
</label>
|
|
77
|
-
<label>
|
|
78
|
-
<input
|
|
79
|
-
type="radio"
|
|
80
|
-
v-model="formData.contactMethod"
|
|
81
|
-
value="phone"
|
|
82
|
-
/>
|
|
83
|
-
Phone
|
|
84
|
-
</label>
|
|
85
|
-
<label>
|
|
86
|
-
<input
|
|
87
|
-
type="radio"
|
|
88
|
-
v-model="formData.contactMethod"
|
|
89
|
-
value="mail"
|
|
90
|
-
/>
|
|
91
|
-
Mail
|
|
92
|
-
</label>
|
|
93
|
-
</div>
|
|
134
|
+
<label>
|
|
135
|
+
<input
|
|
136
|
+
type="checkbox"
|
|
137
|
+
id="newsletter2"
|
|
138
|
+
v-model="formData.newsletter2"
|
|
139
|
+
/>
|
|
140
|
+
<span>Newsletter 2</span>
|
|
141
|
+
</label>
|
|
94
142
|
</div>
|
|
95
143
|
|
|
96
|
-
|
|
144
|
+
<!-- Comments -->
|
|
145
|
+
<label for="comments">Comments</label>
|
|
146
|
+
<textarea
|
|
147
|
+
id="comments"
|
|
148
|
+
v-model="formData.comments"
|
|
149
|
+
></textarea>
|
|
150
|
+
|
|
151
|
+
<!-- Readonly textarea -->
|
|
152
|
+
<label for="readonly">Readonly</label>
|
|
153
|
+
<textarea
|
|
154
|
+
id="readonly"
|
|
155
|
+
v-model="formData.readonlyText"
|
|
156
|
+
readonly
|
|
157
|
+
></textarea>
|
|
158
|
+
|
|
159
|
+
<!-- Disabled textarea -->
|
|
160
|
+
<label for="disabled">Disabled</label>
|
|
161
|
+
<textarea
|
|
162
|
+
id="disabled"
|
|
163
|
+
v-model="formData.disabledText"
|
|
164
|
+
disabled
|
|
165
|
+
></textarea>
|
|
166
|
+
|
|
167
|
+
<!-- Datetime -->
|
|
168
|
+
<label for="datetime">
|
|
169
|
+
Datetime
|
|
170
|
+
</label>
|
|
171
|
+
<input
|
|
172
|
+
id="datetime"
|
|
173
|
+
type="datetime-local"
|
|
174
|
+
v-model="formData.datetime"
|
|
175
|
+
/>
|
|
176
|
+
|
|
177
|
+
<!-- Buttons in fieldset to match simple.html -->
|
|
178
|
+
<fieldset>
|
|
97
179
|
<button type="submit">Submit</button>
|
|
98
|
-
<button type="button" @click="
|
|
99
|
-
|
|
180
|
+
<button type="button" @click="handleCancel">Cancel</button>
|
|
181
|
+
<button type="reset" @click="resetForm">Reset</button>
|
|
182
|
+
<button type="submit" disabled>Disabled</button>
|
|
183
|
+
</fieldset>
|
|
100
184
|
</form>
|
|
101
185
|
|
|
102
186
|
<div v-if="submitted" class="form-result">
|
|
@@ -112,11 +196,20 @@ import Section from '../components/Section.vue'
|
|
|
112
196
|
|
|
113
197
|
const formData = ref({
|
|
114
198
|
name: '',
|
|
199
|
+
role: '',
|
|
200
|
+
salary: '0',
|
|
115
201
|
email: '',
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
202
|
+
isAdmin: false,
|
|
203
|
+
website: '',
|
|
204
|
+
favouriteFood: '',
|
|
205
|
+
favouriteColour: '#dee3e7',
|
|
206
|
+
favouriteNumber: 50,
|
|
207
|
+
newsletter1: false,
|
|
208
|
+
newsletter2: false,
|
|
209
|
+
comments: '',
|
|
210
|
+
readonlyText: 'This is a readonly textarea.',
|
|
211
|
+
disabledText: 'This is a disabled textarea.',
|
|
212
|
+
datetime: ''
|
|
120
213
|
})
|
|
121
214
|
|
|
122
215
|
const submitted = ref(false)
|
|
@@ -128,141 +221,29 @@ function handleSubmit() {
|
|
|
128
221
|
alert('Form submitted! Check the console and the form result below.')
|
|
129
222
|
}
|
|
130
223
|
|
|
224
|
+
function handleCancel() {
|
|
225
|
+
// For demo purposes, just reset submitted state
|
|
226
|
+
submitted.value = false
|
|
227
|
+
}
|
|
228
|
+
|
|
131
229
|
function resetForm() {
|
|
132
230
|
formData.value = {
|
|
133
231
|
name: '',
|
|
232
|
+
role: '',
|
|
233
|
+
salary: '0',
|
|
134
234
|
email: '',
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
235
|
+
isAdmin: false,
|
|
236
|
+
website: '',
|
|
237
|
+
favouriteFood: '',
|
|
238
|
+
favouriteColour: '#dee3e7',
|
|
239
|
+
favouriteNumber: 50,
|
|
240
|
+
newsletter1: false,
|
|
241
|
+
newsletter2: false,
|
|
242
|
+
comments: '',
|
|
243
|
+
readonlyText: 'This is a readonly textarea.',
|
|
244
|
+
disabledText: 'This is a disabled textarea.',
|
|
245
|
+
datetime: ''
|
|
139
246
|
}
|
|
140
247
|
submitted.value = false
|
|
141
248
|
}
|
|
142
|
-
</script>
|
|
143
|
-
|
|
144
|
-
<style scoped>
|
|
145
|
-
.form-group {
|
|
146
|
-
margin-bottom: 1.5rem;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.form-group label {
|
|
150
|
-
display: block;
|
|
151
|
-
margin-bottom: 0.5rem;
|
|
152
|
-
font-weight: 500;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.form-group input[type="text"],
|
|
156
|
-
.form-group input[type="email"],
|
|
157
|
-
.form-group select,
|
|
158
|
-
.form-group textarea {
|
|
159
|
-
width: 100%;
|
|
160
|
-
padding: 0.5rem;
|
|
161
|
-
border: 1px solid #ccc;
|
|
162
|
-
border-radius: 0.25rem;
|
|
163
|
-
font-size: 1rem;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.form-group textarea {
|
|
167
|
-
resize: vertical;
|
|
168
|
-
font-family: inherit;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.form-group input[type="checkbox"],
|
|
172
|
-
.form-group input[type="radio"] {
|
|
173
|
-
margin-right: 0.5rem;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
.form-group label:has(input[type="checkbox"]) {
|
|
177
|
-
display: flex;
|
|
178
|
-
align-items: center;
|
|
179
|
-
font-weight: normal;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
.radio-group {
|
|
183
|
-
display: flex;
|
|
184
|
-
gap: 1.5rem;
|
|
185
|
-
flex-wrap: wrap;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
.radio-group label {
|
|
189
|
-
display: flex;
|
|
190
|
-
align-items: center;
|
|
191
|
-
font-weight: normal;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
.form-actions {
|
|
195
|
-
display: flex;
|
|
196
|
-
gap: 1rem;
|
|
197
|
-
margin-top: 2rem;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.form-actions button {
|
|
201
|
-
padding: 0.75rem 1.5rem;
|
|
202
|
-
border: none;
|
|
203
|
-
border-radius: 0.25rem;
|
|
204
|
-
font-size: 1rem;
|
|
205
|
-
cursor: pointer;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.form-actions button[type="submit"] {
|
|
209
|
-
background-color: #0366d6;
|
|
210
|
-
color: white;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.form-actions button[type="submit"]:hover {
|
|
214
|
-
background-color: #0256c2;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
.form-actions button[type="button"] {
|
|
218
|
-
background-color: #6c757d;
|
|
219
|
-
color: white;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.form-actions button[type="button"]:hover {
|
|
223
|
-
background-color: #5a6268;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
.form-result {
|
|
227
|
-
margin-top: 2rem;
|
|
228
|
-
padding: 1rem;
|
|
229
|
-
background-color: #f8f9fa;
|
|
230
|
-
border-radius: 0.25rem;
|
|
231
|
-
border: 1px solid #dee2e6;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
.form-result h3 {
|
|
235
|
-
margin-top: 0;
|
|
236
|
-
margin-bottom: 1rem;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
.form-result pre {
|
|
240
|
-
margin: 0;
|
|
241
|
-
padding: 1rem;
|
|
242
|
-
background-color: white;
|
|
243
|
-
border-radius: 0.25rem;
|
|
244
|
-
overflow-x: auto;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
@media (prefers-color-scheme: dark) {
|
|
248
|
-
.form-group input[type="text"],
|
|
249
|
-
.form-group input[type="email"],
|
|
250
|
-
.form-group select,
|
|
251
|
-
.form-group textarea {
|
|
252
|
-
background-color: #333;
|
|
253
|
-
border-color: #555;
|
|
254
|
-
color: #fff;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
.form-result {
|
|
258
|
-
background-color: #2d2d2d;
|
|
259
|
-
border-color: #444;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
.form-result pre {
|
|
263
|
-
background-color: #1a1a1a;
|
|
264
|
-
color: #fff;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
</style>
|
|
268
|
-
|
|
249
|
+
</script>
|
package/vue/views/Welcome.vue
CHANGED
|
@@ -5,14 +5,28 @@
|
|
|
5
5
|
<HugeiconsIcon :icon="Menu01Icon" width="1em" height="1em" />
|
|
6
6
|
{{ sidebarEnabled ? 'Disable Sidebar' : 'Enable Sidebar' }}
|
|
7
7
|
</button>
|
|
8
|
+
<button @click="toggleBrandingEnabled" :class="brandingEnabled ? 'neutral' : 'good'">
|
|
9
|
+
<HugeiconsIcon :icon="HighlighterIcon" width="1em" height="1em" />
|
|
10
|
+
{{ brandingEnabled ? 'Hide Logo & Title' : 'Show Logo & Title' }}
|
|
11
|
+
</button>
|
|
12
|
+
<div style="display: flex; gap: 1rem; align-items: center;">
|
|
13
|
+
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
|
14
|
+
<input type="checkbox" :checked="quickSearchEnabled" @change="toggleQuickSearchEnabled" />
|
|
15
|
+
<span>Enable QuickSearch</span>
|
|
16
|
+
</label>
|
|
17
|
+
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
|
18
|
+
<input type="checkbox" :checked="topBarEnabled" @change="toggleTopBarEnabled" />
|
|
19
|
+
<span>Enable TopBar</span>
|
|
20
|
+
</label>
|
|
21
|
+
</div>
|
|
8
22
|
</template>
|
|
9
23
|
<p>
|
|
10
|
-
Welcome to
|
|
11
|
-
To get started, you can either upload a file or paste your code directly into the editor.
|
|
24
|
+
Welcome to <strong>PicoCrank</strong>! This is a flexible example application to showcase a modular Vue 3 UI with a focus on ease of navigation, live UI controls, and modern design elements. Use the toolbar above to interactively enable or disable interface features like the sidebar, branding, QuickSearch, and the top navigation bar.
|
|
12
25
|
</p>
|
|
13
26
|
<p>
|
|
14
|
-
|
|
27
|
+
Explore the sections below to see reusable components in action, including toolbars, buttons, and layout options. PicoCrank is designed to be a starting point for your own rapid Vue projects—inspect the code and try customizing the UI!
|
|
15
28
|
</p>
|
|
29
|
+
|
|
16
30
|
</Section>
|
|
17
31
|
|
|
18
32
|
<Section title = "Lorum" id = "lorem">
|
|
@@ -83,7 +97,13 @@
|
|
|
83
97
|
import { HighlighterIcon } from '@hugeicons/core-free-icons';
|
|
84
98
|
import { Menu01Icon } from '@hugeicons/core-free-icons';
|
|
85
99
|
|
|
86
|
-
// Inject sidebar state and toggle
|
|
100
|
+
// Inject sidebar and branding state and toggle functions from App.vue
|
|
87
101
|
const sidebarEnabled = inject('sidebarEnabled');
|
|
88
102
|
const toggleSidebarEnabled = inject('toggleSidebarEnabled');
|
|
103
|
+
const brandingEnabled = inject('brandingEnabled');
|
|
104
|
+
const toggleBrandingEnabled = inject('toggleBrandingEnabled');
|
|
105
|
+
const quickSearchEnabled = inject('quickSearchEnabled');
|
|
106
|
+
const toggleQuickSearchEnabled = inject('toggleQuickSearchEnabled');
|
|
107
|
+
const topBarEnabled = inject('topBarEnabled');
|
|
108
|
+
const toggleTopBarEnabled = inject('toggleTopBarEnabled');
|
|
89
109
|
</script>
|