picocrank 1.21.5 → 1.22.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/package.json +8 -8
- package/public/themes/README.md +29 -0
- package/public/themes/catppuccin/theme.css +195 -0
- package/public/themes/dracula/theme.css +181 -0
- package/public/themes/index.json +7 -0
- package/public/themes/waffles/theme.css +143 -0
- package/router.js +20 -0
- package/styles/dark-theme.css +15 -1
- package/styles.css +18 -2
- package/vite.config.js +57 -0
- package/vue/components/CheckGroup.vue +156 -0
- package/vue/components/FormField.vue +14 -2
- package/vue/components/Header.vue +139 -33
- package/vue/components/QuickSearch.vue +325 -348
- package/vue/components/RadioGroup.vue +191 -0
- package/vue/components/Sidebar.vue +226 -262
- package/vue/components/SidebarNavLink.vue +161 -0
- package/vue/composables/useCustomTheme.js +162 -0
- package/vue/data/examplePeople.js +45 -0
- package/vue/views/App.vue +85 -15
- package/vue/views/FormExample.vue +64 -29
- package/vue/views/PageStructure.vue +168 -95
- package/vue/views/TableExample.vue +2 -76
- package/vue/views/ThemePreview.vue +172 -0
- package/vue/views/UserDetails.vue +39 -0
- package/vue/views/Welcome.vue +9 -6
|
@@ -10,103 +10,132 @@
|
|
|
10
10
|
</Section>
|
|
11
11
|
|
|
12
12
|
<Section title="Header" subtitle="Branding, search, and top navigation">
|
|
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
|
-
:checked="topBarEnabled"
|
|
47
|
-
@change="toggleTopBarEnabled"
|
|
13
|
+
<FormLayout @submit.prevent>
|
|
14
|
+
<FormField label="Logo and title" fake>
|
|
15
|
+
<RadioGroup
|
|
16
|
+
name="ps-branding"
|
|
17
|
+
variant="boolean"
|
|
18
|
+
aria-label="Logo and title"
|
|
19
|
+
:model-value="brandingEnabled"
|
|
20
|
+
:options="showHideOptions"
|
|
21
|
+
@update:model-value="setBrandingEnabled"
|
|
22
|
+
/>
|
|
23
|
+
</FormField>
|
|
24
|
+
|
|
25
|
+
<FormField label="QuickSearch" fake>
|
|
26
|
+
<RadioGroup
|
|
27
|
+
name="ps-quicksearch"
|
|
28
|
+
variant="boolean"
|
|
29
|
+
aria-label="QuickSearch"
|
|
30
|
+
:model-value="quickSearchEnabled"
|
|
31
|
+
:options="showHideOptions"
|
|
32
|
+
@update:model-value="setQuickSearchEnabled"
|
|
33
|
+
/>
|
|
34
|
+
</FormField>
|
|
35
|
+
|
|
36
|
+
<FormField label="Dummy background search" fake :disabled="!quickSearchEnabled">
|
|
37
|
+
<div>
|
|
38
|
+
<RadioGroup
|
|
39
|
+
name="ps-dummy-search"
|
|
40
|
+
variant="boolean"
|
|
41
|
+
aria-label="Dummy background search"
|
|
42
|
+
:model-value="dummySearchFetchEnabled"
|
|
43
|
+
:options="onOffOptions"
|
|
44
|
+
:disabled="!quickSearchEnabled"
|
|
45
|
+
@update:model-value="setDummySearchFetchEnabled"
|
|
48
46
|
/>
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
47
|
+
<p class="subtle">
|
|
48
|
+
Turns on QuickSearch’s background-fetch loading UI. Local matches still
|
|
49
|
+
appear immediately while the dummy request runs.
|
|
50
|
+
</p>
|
|
51
|
+
</div>
|
|
52
|
+
</FormField>
|
|
53
|
+
|
|
54
|
+
<FormField label="Dark/Light mode button" fake>
|
|
55
|
+
<RadioGroup
|
|
56
|
+
name="ps-theme-toggle"
|
|
57
|
+
variant="boolean"
|
|
58
|
+
aria-label="Dark/Light mode button"
|
|
59
|
+
:model-value="themeToggleEnabled"
|
|
60
|
+
:options="showHideOptions"
|
|
61
|
+
@update:model-value="setThemeToggleEnabled"
|
|
62
|
+
/>
|
|
63
|
+
</FormField>
|
|
64
|
+
|
|
65
|
+
<FormField label="Top bar" fake>
|
|
66
|
+
<div>
|
|
67
|
+
<RadioGroup
|
|
68
|
+
name="ps-topbar"
|
|
69
|
+
variant="boolean"
|
|
70
|
+
aria-label="Top bar"
|
|
71
|
+
:model-value="topBarEnabled"
|
|
72
|
+
:options="showHideOptions"
|
|
73
|
+
@update:model-value="setTopBarEnabled"
|
|
64
74
|
/>
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
<p class="subtle">
|
|
76
|
+
On narrow screens: if both are on, the sidebar takes precedence and the top bar is hidden.
|
|
77
|
+
If only the top bar is on, Header collapses it into a mobile sidebar automatically.
|
|
78
|
+
</p>
|
|
79
|
+
</div>
|
|
80
|
+
</FormField>
|
|
81
|
+
|
|
82
|
+
<FormField label="Breadcrumbs" fake>
|
|
83
|
+
<RadioGroup
|
|
84
|
+
name="ps-breadcrumbs"
|
|
85
|
+
variant="boolean"
|
|
86
|
+
aria-label="Breadcrumbs"
|
|
87
|
+
:model-value="breadcrumbsEnabled"
|
|
88
|
+
:options="showHideOptions"
|
|
89
|
+
@update:model-value="setBreadcrumbsEnabled"
|
|
90
|
+
/>
|
|
91
|
+
</FormField>
|
|
92
|
+
</FormLayout>
|
|
69
93
|
</Section>
|
|
70
94
|
|
|
71
95
|
<Section title="Sidebar" subtitle="Primary navigation panel">
|
|
72
|
-
<
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
<FormLayout @submit.prevent>
|
|
97
|
+
<FormField label="Sidebar navigation" fake>
|
|
98
|
+
<div>
|
|
99
|
+
<RadioGroup
|
|
100
|
+
name="ps-sidebar"
|
|
101
|
+
variant="boolean"
|
|
102
|
+
aria-label="Sidebar navigation"
|
|
103
|
+
:model-value="sidebarEnabled"
|
|
104
|
+
:options="showHideOptions"
|
|
105
|
+
@update:model-value="setSidebarEnabled"
|
|
80
106
|
/>
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
:
|
|
94
|
-
|
|
107
|
+
<p class="subtle">
|
|
108
|
+
When enabled, use the menu button in the header to open and pin the sidebar.
|
|
109
|
+
</p>
|
|
110
|
+
</div>
|
|
111
|
+
</FormField>
|
|
112
|
+
|
|
113
|
+
<FormField label="Example link states" fake>
|
|
114
|
+
<div>
|
|
115
|
+
<RadioGroup
|
|
116
|
+
name="ps-example-links"
|
|
117
|
+
variant="boolean"
|
|
118
|
+
aria-label="Example link states"
|
|
119
|
+
:model-value="exampleLinksEnabled"
|
|
120
|
+
:options="onOffOptions"
|
|
121
|
+
@update:model-value="setExampleLinks"
|
|
95
122
|
/>
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
</dl>
|
|
123
|
+
<p class="subtle">
|
|
124
|
+
Adds a “Link states” section to the sidebar with an attention indicator,
|
|
125
|
+
a notification count badge, and a disabled item.
|
|
126
|
+
</p>
|
|
127
|
+
</div>
|
|
128
|
+
</FormField>
|
|
129
|
+
</FormLayout>
|
|
104
130
|
</Section>
|
|
105
131
|
</template>
|
|
106
132
|
|
|
107
133
|
<script setup>
|
|
108
134
|
import { inject, ref, onBeforeUnmount } from 'vue'
|
|
109
135
|
import Section from '../components/Section.vue'
|
|
136
|
+
import FormLayout from '../components/FormLayout.vue'
|
|
137
|
+
import FormField from '../components/FormField.vue'
|
|
138
|
+
import RadioGroup from '../components/RadioGroup.vue'
|
|
110
139
|
import {
|
|
111
140
|
Notification01Icon,
|
|
112
141
|
Alert02Icon,
|
|
@@ -121,12 +150,26 @@ const EXAMPLE_LINK_NAMES = [
|
|
|
121
150
|
'example-disabled',
|
|
122
151
|
]
|
|
123
152
|
|
|
153
|
+
const showHideOptions = [
|
|
154
|
+
{ label: 'Shown', value: true },
|
|
155
|
+
{ label: 'Hidden', value: false },
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
const onOffOptions = [
|
|
159
|
+
{ label: 'On', value: true },
|
|
160
|
+
{ label: 'Off', value: false },
|
|
161
|
+
]
|
|
162
|
+
|
|
124
163
|
const sidebarEnabled = inject('sidebarEnabled')
|
|
125
164
|
const toggleSidebarEnabled = inject('toggleSidebarEnabled')
|
|
126
165
|
const brandingEnabled = inject('brandingEnabled')
|
|
127
166
|
const toggleBrandingEnabled = inject('toggleBrandingEnabled')
|
|
128
167
|
const quickSearchEnabled = inject('quickSearchEnabled')
|
|
129
168
|
const toggleQuickSearchEnabled = inject('toggleQuickSearchEnabled')
|
|
169
|
+
const dummySearchFetchEnabled = inject('dummySearchFetchEnabled')
|
|
170
|
+
const toggleDummySearchFetchEnabled = inject('toggleDummySearchFetchEnabled')
|
|
171
|
+
const themeToggleEnabled = inject('themeToggleEnabled')
|
|
172
|
+
const toggleThemeToggleEnabled = inject('toggleThemeToggleEnabled')
|
|
130
173
|
const topBarEnabled = inject('topBarEnabled')
|
|
131
174
|
const toggleTopBarEnabled = inject('toggleTopBarEnabled')
|
|
132
175
|
const breadcrumbsEnabled = inject('breadcrumbsEnabled')
|
|
@@ -135,6 +178,40 @@ const navigation = inject('navigation', null)
|
|
|
135
178
|
|
|
136
179
|
const exampleLinksEnabled = ref(false)
|
|
137
180
|
|
|
181
|
+
function setFlag(flag, toggle, want) {
|
|
182
|
+
if (flag.value !== want) {
|
|
183
|
+
toggle()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function setBrandingEnabled(want) {
|
|
188
|
+
setFlag(brandingEnabled, toggleBrandingEnabled, want)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function setQuickSearchEnabled(want) {
|
|
192
|
+
setFlag(quickSearchEnabled, toggleQuickSearchEnabled, want)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function setDummySearchFetchEnabled(want) {
|
|
196
|
+
setFlag(dummySearchFetchEnabled, toggleDummySearchFetchEnabled, want)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function setThemeToggleEnabled(want) {
|
|
200
|
+
setFlag(themeToggleEnabled, toggleThemeToggleEnabled, want)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function setTopBarEnabled(want) {
|
|
204
|
+
setFlag(topBarEnabled, toggleTopBarEnabled, want)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function setBreadcrumbsEnabled(want) {
|
|
208
|
+
setFlag(breadcrumbsEnabled, toggleBreadcrumbsEnabled, want)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function setSidebarEnabled(want) {
|
|
212
|
+
setFlag(sidebarEnabled, toggleSidebarEnabled, want)
|
|
213
|
+
}
|
|
214
|
+
|
|
138
215
|
function removeExampleLinks() {
|
|
139
216
|
if (!navigation) {
|
|
140
217
|
return
|
|
@@ -178,9 +255,12 @@ function addExampleLinks() {
|
|
|
178
255
|
})
|
|
179
256
|
}
|
|
180
257
|
|
|
181
|
-
function
|
|
182
|
-
exampleLinksEnabled.value
|
|
183
|
-
|
|
258
|
+
function setExampleLinks(want) {
|
|
259
|
+
if (exampleLinksEnabled.value === want) {
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
exampleLinksEnabled.value = want
|
|
263
|
+
if (want) {
|
|
184
264
|
if (!sidebarEnabled.value) {
|
|
185
265
|
toggleSidebarEnabled()
|
|
186
266
|
}
|
|
@@ -198,14 +278,7 @@ onBeforeUnmount(() => {
|
|
|
198
278
|
</script>
|
|
199
279
|
|
|
200
280
|
<style scoped>
|
|
201
|
-
.
|
|
202
|
-
display: flex;
|
|
203
|
-
align-items: center;
|
|
204
|
-
gap: 0.5rem;
|
|
205
|
-
cursor: pointer;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.page-structure-control + .subtle {
|
|
281
|
+
.subtle {
|
|
209
282
|
margin-top: 0.5rem;
|
|
210
283
|
}
|
|
211
284
|
</style>
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
<script setup>
|
|
32
32
|
import { ref, onMounted } from 'vue'
|
|
33
|
+
import { getExampleTableRows } from '../data/examplePeople.js'
|
|
33
34
|
|
|
34
35
|
const headers = ref([
|
|
35
36
|
{ key: "name", label: "Name", sortable: true, hidden: false, width: "20%" },
|
|
@@ -40,81 +41,6 @@
|
|
|
40
41
|
const data = ref([])
|
|
41
42
|
|
|
42
43
|
onMounted(() => {
|
|
43
|
-
data.value =
|
|
44
|
-
{ name: "Alice", age: 30, city: "New York", foo: "bar" },
|
|
45
|
-
{ name: "Bob", age: 25, city: "Los Angeles" },
|
|
46
|
-
{ name: "Charlie", age: 35, city: "Chicago" },
|
|
47
|
-
{ name: "David", age: 28, city: "Houston" },
|
|
48
|
-
{ name: "Eve", age: 22, city: "Phoenix" },
|
|
49
|
-
{ name: "Frank", age: 40, city: "Philadelphia" },
|
|
50
|
-
{ name: "Grace", age: 29, city: "San Antonio" },
|
|
51
|
-
{ name: "Hannah", age: 32, city: "San Diego" },
|
|
52
|
-
{ name: "Ian", age: 27, city: "Dallas" },
|
|
53
|
-
{ name: "Jack", age: 31, city: "San Jose" },
|
|
54
|
-
{ name: "Kathy", age: 26, city: "Austin" },
|
|
55
|
-
{ name: "Leo", age: 33, city: "Jacksonville" },
|
|
56
|
-
{ name: "Mia", age: 24, city: "Fort Worth" },
|
|
57
|
-
{ name: "Nina", age: 38, city: "Columbus" },
|
|
58
|
-
{ name: "Oscar", age: 36, city: "Charlotte" },
|
|
59
|
-
{ name: "Paul", age: 34, city: "San Francisco" },
|
|
60
|
-
{ name: "Quinn", age: 23, city: "Indianapolis" },
|
|
61
|
-
{ name: "Rita", age: 37, city: "Seattle" },
|
|
62
|
-
{ name: "Sam", age: 39, city: "Denver" },
|
|
63
|
-
{ name: "Tina", age: 21, city: "Washington D.C." },
|
|
64
|
-
{ name: "Uma", age: 28, city: "Miami" },
|
|
65
|
-
{ name: "Victor", age: 30, city: "Portland" },
|
|
66
|
-
{ name: "Wendy", age: 29, city: "Nashville" },
|
|
67
|
-
{ name: "Xavier", age: 31, city: "Oklahoma City" },
|
|
68
|
-
{ name: "Yara", age: 27, city: "Tampa" },
|
|
69
|
-
{ name: "Zane", age: 32, city: "Albuquerque" },
|
|
70
|
-
{ name: "Ava", age: 26, city: "Las Vegas" },
|
|
71
|
-
{ name: "Ben", age: 33, city: "New Orleans" },
|
|
72
|
-
{ name: "Cara", age: 25, city: "San Francisco" },
|
|
73
|
-
{ name: "Drew", age: 34, city: "Seattle" },
|
|
74
|
-
{ name: "Ethan", age: 28, city: "Denver" },
|
|
75
|
-
{ name: "Fiona", age: 30, city: "Washington D.C." },
|
|
76
|
-
{ name: "George", age: 29, city: "Miami" },
|
|
77
|
-
{ name: "Hannah", age: 32, city: "San Diego" },
|
|
78
|
-
{ name: "Ian", age: 27, city: "Dallas" },
|
|
79
|
-
{ name: "Jack", age: 31, city: "San Jose" },
|
|
80
|
-
{ name: "Kathy", age: 26, city: "Austin" },
|
|
81
|
-
{ name: "Leo", age: 33, city: "Jacksonville" },
|
|
82
|
-
{ name: "Mia", age: 24, city: "Fort Worth" },
|
|
83
|
-
{ name: "Nina", age: 38, city: "Columbus" },
|
|
84
|
-
{ name: "Oscar", age: 36, city: "Charlotte" },
|
|
85
|
-
{ name: "Paul", age: 34, city: "San Francisco" },
|
|
86
|
-
{ name: "Quinn", age: 23, city: "Indianapolis" },
|
|
87
|
-
{ name: "Rita", age: 37, city: "Seattle" },
|
|
88
|
-
{ name: "Sam", age: 39, city: "Denver" },
|
|
89
|
-
{ name: "Tina", age: 21, city: "Washington D.C." },
|
|
90
|
-
{ name: "Uma", age: 28, city: "Miami" },
|
|
91
|
-
{ name: "Victor", age: 30, city: "Portland" },
|
|
92
|
-
{ name: "Wendy", age: 29, city: "Nashville" },
|
|
93
|
-
{ name: "Xavier", age: 31, city: "Oklahoma City" },
|
|
94
|
-
{ name: "Yara", age: 27, city: "Tampa" },
|
|
95
|
-
{ name: "Zane", age: 32, city: "Albuquerque" },
|
|
96
|
-
{ name: "Ava", age: 26, city: "Las Vegas" },
|
|
97
|
-
{ name: "Ben", age: 33, city: "New Orleans" },
|
|
98
|
-
{ name: "Cara", age: 25, city: "San Francisco" },
|
|
99
|
-
{ name: "Drew", age: 34, city: "Seattle" },
|
|
100
|
-
{ name: "Ethan", age: 28, city: "Denver" },
|
|
101
|
-
{ name: "Fiona", age: 30, city: "Washington D.C." },
|
|
102
|
-
{ name: "George", age: 29, city: "Miami" },
|
|
103
|
-
{ name: "Hannah", age: 32, city: "San Diego" },
|
|
104
|
-
{ name: "Ian", age: 27, city: "Dallas" },
|
|
105
|
-
{ name: "Jack", age: 31, city: "San Jose" },
|
|
106
|
-
{ name: "Kathy", age: 26, city: "Austin" },
|
|
107
|
-
{ name: "Leo", age: 33, city: "Jacksonville" },
|
|
108
|
-
{ name: "Mia", age: 24, city: "Fort Worth" },
|
|
109
|
-
{ name: "Nina", age: 38, city: "Columbus" },
|
|
110
|
-
{ name: "Oscar", age: 36, city: "Charlotte" },
|
|
111
|
-
{ name: "Paul", age: 34, city: "San Francisco" },
|
|
112
|
-
{ name: "Quinn", age: 23, city: "Indianapolis" },
|
|
113
|
-
{ name: "Rita", age: 37, city: "Seattle" },
|
|
114
|
-
{ name: "Sam", age: 39, city: "Denver" },
|
|
115
|
-
{ name: "Tina", age: 21, city: "Washington D.C." },
|
|
116
|
-
{ name: "Uma", age: 28, city: "Miami" },
|
|
117
|
-
{ name: "Victor", age: 30, city: "Portland" },
|
|
118
|
-
]
|
|
44
|
+
data.value = getExampleTableRows()
|
|
119
45
|
})
|
|
120
46
|
</script>
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Section
|
|
3
|
+
title="Theme Switcher"
|
|
4
|
+
subtitle="Drop-in themes layered on Femtocrank — not part of the Vite CSS/JS bundle"
|
|
5
|
+
>
|
|
6
|
+
<p>
|
|
7
|
+
Themes live under <code>public/themes/<name>/theme.css</code>. Drop a folder
|
|
8
|
+
in and restart the dev server (or rebuild); PicoCrank discovers themes from
|
|
9
|
+
<code>index.json</code> and applies them with <code>useCustomTheme()</code>.
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<label class="theme-picker">
|
|
13
|
+
<span>Active theme</span>
|
|
14
|
+
<select :value="themePreference" @change="onThemeChange">
|
|
15
|
+
<option value="">Default (Femtocrank)</option>
|
|
16
|
+
<option
|
|
17
|
+
v-for="name in availableThemes"
|
|
18
|
+
:key="name"
|
|
19
|
+
:value="name"
|
|
20
|
+
>
|
|
21
|
+
{{ name }}
|
|
22
|
+
</option>
|
|
23
|
+
</select>
|
|
24
|
+
</label>
|
|
25
|
+
|
|
26
|
+
<p v-if="availableThemes.length === 0" class="subtle">
|
|
27
|
+
No drop-in themes found. Add <code>public/themes/<name>/theme.css</code>
|
|
28
|
+
and restart Vite.
|
|
29
|
+
</p>
|
|
30
|
+
<p v-else class="subtle">
|
|
31
|
+
Preference is stored in localStorage and applies across the whole examples site.
|
|
32
|
+
Light/dark mode (header sun/moon) is separate from drop-in themes.
|
|
33
|
+
</p>
|
|
34
|
+
</Section>
|
|
35
|
+
|
|
36
|
+
<Section title="Buttons" subtitle="Default and karma variants">
|
|
37
|
+
<div role="toolbar" class="preview-row">
|
|
38
|
+
<button type="button">Default</button>
|
|
39
|
+
<button type="button" class="neutral">Neutral</button>
|
|
40
|
+
<button type="button" class="good">Good</button>
|
|
41
|
+
<button type="button" class="bad">Bad</button>
|
|
42
|
+
<button type="button" class="warning">Warning</button>
|
|
43
|
+
<button type="button" disabled>Disabled</button>
|
|
44
|
+
</div>
|
|
45
|
+
</Section>
|
|
46
|
+
|
|
47
|
+
<Section title="Notifications" subtitle="Block-level karma status">
|
|
48
|
+
<div class="notification show good" role="status">
|
|
49
|
+
<strong>GOOD:</strong> Theme tokens are applying to karma backgrounds.
|
|
50
|
+
</div>
|
|
51
|
+
<div class="notification show bad" role="status">
|
|
52
|
+
<strong>BAD:</strong> Check contrast on error surfaces.
|
|
53
|
+
</div>
|
|
54
|
+
<div class="notification show warning" role="status">
|
|
55
|
+
<strong>WARNING:</strong> Accent and border colors should stay readable.
|
|
56
|
+
</div>
|
|
57
|
+
<p class="fg-success show inline-notification">
|
|
58
|
+
<strong>SUCCESS:</strong> Inline notification with foreground karma.
|
|
59
|
+
</p>
|
|
60
|
+
<p class="fg-warning show inline-notification">
|
|
61
|
+
<strong>WARNING:</strong> Another inline status line.
|
|
62
|
+
</p>
|
|
63
|
+
</Section>
|
|
64
|
+
|
|
65
|
+
<Section title="Form controls" subtitle="Inputs inherit theme variables">
|
|
66
|
+
<FormLayout @submit.prevent>
|
|
67
|
+
<FormField label="Name" for="theme-preview-name">
|
|
68
|
+
<input id="theme-preview-name" type="text" value="PicoCrank" />
|
|
69
|
+
</FormField>
|
|
70
|
+
<FormField label="Status" for="theme-preview-status">
|
|
71
|
+
<select id="theme-preview-status">
|
|
72
|
+
<option>Ready</option>
|
|
73
|
+
<option>Busy</option>
|
|
74
|
+
</select>
|
|
75
|
+
</FormField>
|
|
76
|
+
<FormField label="Notes" for="theme-preview-notes">
|
|
77
|
+
<textarea id="theme-preview-notes" rows="3">Override Femtocrank tokens in theme.css</textarea>
|
|
78
|
+
</FormField>
|
|
79
|
+
<template #actions>
|
|
80
|
+
<button type="submit" class="good">Save</button>
|
|
81
|
+
<button type="button" class="neutral">Cancel</button>
|
|
82
|
+
</template>
|
|
83
|
+
</FormLayout>
|
|
84
|
+
</Section>
|
|
85
|
+
|
|
86
|
+
<Section title="Content chrome" subtitle="Sections, links, and muted text">
|
|
87
|
+
<p>
|
|
88
|
+
Body copy should pick up <code>--text-color</code>.
|
|
89
|
+
<a href="#">Sample link</a> uses the theme link color.
|
|
90
|
+
</p>
|
|
91
|
+
<p class="subtle">
|
|
92
|
+
Muted / subtle text uses secondary contrast — useful for captions and hints.
|
|
93
|
+
</p>
|
|
94
|
+
<table class="preview-table">
|
|
95
|
+
<thead>
|
|
96
|
+
<tr>
|
|
97
|
+
<th>Token</th>
|
|
98
|
+
<th>Role</th>
|
|
99
|
+
</tr>
|
|
100
|
+
</thead>
|
|
101
|
+
<tbody>
|
|
102
|
+
<tr>
|
|
103
|
+
<td><code>--border-color</code></td>
|
|
104
|
+
<td>Borders and rules</td>
|
|
105
|
+
</tr>
|
|
106
|
+
<tr>
|
|
107
|
+
<td><code>--standout-bg-color</code></td>
|
|
108
|
+
<td>Raised / standout surfaces</td>
|
|
109
|
+
</tr>
|
|
110
|
+
<tr>
|
|
111
|
+
<td><code>--karma-*</code></td>
|
|
112
|
+
<td>Status backgrounds and accents</td>
|
|
113
|
+
</tr>
|
|
114
|
+
</tbody>
|
|
115
|
+
</table>
|
|
116
|
+
</Section>
|
|
117
|
+
</template>
|
|
118
|
+
|
|
119
|
+
<script setup>
|
|
120
|
+
import Section from '../components/Section.vue'
|
|
121
|
+
import FormLayout from '../components/FormLayout.vue'
|
|
122
|
+
import FormField from '../components/FormField.vue'
|
|
123
|
+
import { useCustomTheme } from '../composables/useCustomTheme.js'
|
|
124
|
+
|
|
125
|
+
const { availableThemes, themePreference, setTheme, discoverThemes } = useCustomTheme()
|
|
126
|
+
|
|
127
|
+
discoverThemes()
|
|
128
|
+
|
|
129
|
+
function onThemeChange(event) {
|
|
130
|
+
setTheme(event.target.value)
|
|
131
|
+
}
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<style scoped>
|
|
135
|
+
.theme-picker {
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-wrap: wrap;
|
|
138
|
+
align-items: center;
|
|
139
|
+
gap: 0.75rem;
|
|
140
|
+
margin: 1rem 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.theme-picker select {
|
|
144
|
+
min-width: 14rem;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.preview-row {
|
|
148
|
+
display: flex;
|
|
149
|
+
flex-wrap: wrap;
|
|
150
|
+
gap: 0.5rem;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.preview-table {
|
|
154
|
+
width: 100%;
|
|
155
|
+
border-collapse: collapse;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.preview-table th,
|
|
159
|
+
.preview-table td {
|
|
160
|
+
text-align: left;
|
|
161
|
+
padding: 0.5rem 0.75rem;
|
|
162
|
+
border-bottom: 1px solid var(--border-color, #d7d7d7);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.notification {
|
|
166
|
+
margin-bottom: 0.5rem;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.inline-notification {
|
|
170
|
+
margin: 0.5rem 0;
|
|
171
|
+
}
|
|
172
|
+
</style>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Section
|
|
3
|
+
title="User details"
|
|
4
|
+
subtitle="Signed-in account for this example app"
|
|
5
|
+
>
|
|
6
|
+
<p class="subtle">
|
|
7
|
+
This is a minimal profile page used by the header username control.
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<dl>
|
|
11
|
+
<dt>Display name</dt>
|
|
12
|
+
<dd>{{ user.displayName }}</dd>
|
|
13
|
+
|
|
14
|
+
<dt>Username</dt>
|
|
15
|
+
<dd>{{ user.username }}</dd>
|
|
16
|
+
|
|
17
|
+
<dt>Email</dt>
|
|
18
|
+
<dd><a :href="`mailto:${user.email}`">{{ user.email }}</a></dd>
|
|
19
|
+
|
|
20
|
+
<dt>Role</dt>
|
|
21
|
+
<dd>{{ user.role }}</dd>
|
|
22
|
+
|
|
23
|
+
<dt>Status</dt>
|
|
24
|
+
<dd><span class="tag good">{{ user.status }}</span></dd>
|
|
25
|
+
</dl>
|
|
26
|
+
</Section>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup>
|
|
30
|
+
import Section from '../components/Section.vue'
|
|
31
|
+
|
|
32
|
+
const user = {
|
|
33
|
+
displayName: 'Guest User',
|
|
34
|
+
username: 'guest',
|
|
35
|
+
email: 'guest@example.com',
|
|
36
|
+
role: 'Viewer',
|
|
37
|
+
status: 'Active',
|
|
38
|
+
}
|
|
39
|
+
</script>
|
package/vue/views/Welcome.vue
CHANGED
|
@@ -110,6 +110,12 @@ const { show: showPopup } = useNotificationPopups()
|
|
|
110
110
|
const welcomeNavigation = ref(null)
|
|
111
111
|
|
|
112
112
|
const popularExamples = [
|
|
113
|
+
{
|
|
114
|
+
name: 'ThemePreview',
|
|
115
|
+
category: 'Layouts',
|
|
116
|
+
label: 'Theme Switcher',
|
|
117
|
+
description: 'Switch drop-in themes live',
|
|
118
|
+
},
|
|
113
119
|
{
|
|
114
120
|
name: 'TableExample',
|
|
115
121
|
category: 'Data display',
|
|
@@ -128,12 +134,6 @@ const popularExamples = [
|
|
|
128
134
|
label: 'Buttons',
|
|
129
135
|
description: 'Variants, toolbars, and form actions',
|
|
130
136
|
},
|
|
131
|
-
{
|
|
132
|
-
name: 'StatusExample',
|
|
133
|
-
category: 'Feedback',
|
|
134
|
-
label: 'Status',
|
|
135
|
-
description: 'Inline messages and notification popups',
|
|
136
|
-
},
|
|
137
137
|
]
|
|
138
138
|
|
|
139
139
|
function showWelcomeToast() {
|
|
@@ -156,6 +156,9 @@ onMounted(() => {
|
|
|
156
156
|
nav.addRouterLink('PageStructure', 'Page structure', {
|
|
157
157
|
description: 'Sidebar, header, QuickSearch, and breadcrumbs',
|
|
158
158
|
})
|
|
159
|
+
nav.addRouterLink('ThemePreview', 'Theme Switcher', {
|
|
160
|
+
description: 'Drop-in themes on Femtocrank',
|
|
161
|
+
})
|
|
159
162
|
nav.addRouterLink('TableExample', 'Table', {
|
|
160
163
|
description: 'Lists with sorting and pagination',
|
|
161
164
|
})
|