picocrank 1.24.0 → 1.25.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.
Files changed (41) hide show
  1. package/AGENTS.md +2 -0
  2. package/docs/filter-query-v1.md +180 -0
  3. package/logo.svg +7 -7
  4. package/package.json +7 -5
  5. package/public/themes/README.md +34 -2
  6. package/public/themes/index.json +1 -5
  7. package/styles/dark-theme.css +42 -186
  8. package/styles/picocrank-extensions.css +15 -0
  9. package/styles.css +1 -0
  10. package/supplemental-themes/README.md +9 -0
  11. package/supplemental-themes/catppuccin-latte-frappe/theme.css +211 -0
  12. package/supplemental-themes/dracula-alucard/theme.css +193 -0
  13. package/supplemental-themes/gruvbox-dark-light/theme.css +199 -0
  14. package/supplemental-themes/index.json +17 -0
  15. package/{public/themes → supplemental-themes}/waffles/theme.css +69 -46
  16. package/vite.config.js +91 -0
  17. package/vue/components/Header.vue +3 -3
  18. package/vue/components/Login.vue +1 -3
  19. package/vue/components/Section.vue +45 -14
  20. package/vue/components/Sidebar.vue +6 -11
  21. package/vue/components/SidebarNavLink.vue +6 -9
  22. package/vue/components/Table.vue +1115 -75
  23. package/vue/components/TableColumnFilterPopover.vue +263 -44
  24. package/vue/components/TableColumnOptionsPopover.vue +1116 -0
  25. package/vue/components/Tabs.vue +4 -25
  26. package/vue/components/ThemeSwitcher.vue +158 -0
  27. package/vue/components/TopBar.vue +2 -2
  28. package/vue/composables/tableColumnPresets.js +296 -0
  29. package/vue/composables/tableFilterQuery.js +202 -0
  30. package/vue/composables/tableFilters.js +167 -3
  31. package/vue/composables/useCustomTheme.js +167 -24
  32. package/vue/data/examplePeople.js +2 -6
  33. package/vue/examples/App.vue +14 -1
  34. package/vue/examples/ExampleHeaderThemeSwitcher.vue +78 -0
  35. package/vue/examples/LoginExample.vue +1 -3
  36. package/vue/examples/PageStructure.vue +17 -0
  37. package/vue/examples/TableExample.vue +329 -11
  38. package/vue/examples/TableRemoteExample.vue +112 -4
  39. package/vue/examples/ThemePreview.vue +45 -38
  40. package/public/themes/catppuccin/theme.css +0 -195
  41. package/public/themes/dracula/theme.css +0 -181
@@ -9,21 +9,52 @@
9
9
  html {
10
10
  --waffle-cream: #fff8e7;
11
11
  --waffle-batter: #f5e6c8;
12
+ --waffle-section: #edd9b0;
13
+ --waffle-sidebar: #e5cf9a;
12
14
  --waffle-crust: #c4863b;
13
15
  --waffle-syrup: #8b4513;
14
16
  --waffle-butter: #ffd966;
15
17
  --waffle-berry: #c23b5a;
18
+ --waffle-accent: #a05020;
19
+
20
+ --body-bg-color: var(--waffle-cream);
21
+ --surface-bg-color: #fffdf6;
22
+ --standout-bg-color: var(--waffle-batter);
23
+ --section-header-bg-color: var(--waffle-batter);
24
+ --shadow-color: rgba(139, 69, 19, 0.12);
16
25
 
17
26
  --text-color: var(--waffle-syrup);
27
+ --muted-text-color: #a88860;
28
+ --link-color: var(--waffle-accent);
18
29
  --border-color: #e0c896;
19
- --standout-bg-color: var(--waffle-batter);
30
+
20
31
  --hover-background-color: #f0d9a8;
21
32
  --hover-text-color: var(--waffle-syrup);
33
+
34
+ --header-fg-color: var(--waffle-cream);
22
35
  --header-hover-background-color: var(--waffle-crust);
23
36
  --header-hover-text-color: var(--waffle-cream);
37
+ --header-button-hover-bg-color: var(--waffle-crust);
38
+
39
+ --sidebar-bg-color: var(--waffle-sidebar);
40
+ --sidebar-link-color: var(--waffle-syrup);
41
+ --sidebar-hover-bg-color: var(--hover-background-color);
42
+ --sidebar-border-color: var(--waffle-crust);
43
+
44
+ --input-bg-color: var(--waffle-cream);
45
+ --input-fg-color: var(--waffle-syrup);
46
+ --control-option-bg-color: var(--waffle-butter);
47
+ --control-hover-bg-color: #ffe14d;
48
+
24
49
  --disabled-background-color: #efe4cb;
25
50
  --disabled-text-color: #a88860;
26
51
 
52
+ --button-good-bg: #9ccc65;
53
+ --button-good-hover-bg: #9ccc65;
54
+ --button-bad-bg: var(--waffle-berry);
55
+ --button-bad-hover-bg: var(--waffle-berry);
56
+ --button-on-accent-fg: #fff8e7;
57
+
27
58
  --karma-good: #b8d98a;
28
59
  --karma-bad: #f0a0a8;
29
60
  --karma-warning: var(--waffle-butter);
@@ -44,8 +75,8 @@ html {
44
75
  }
45
76
 
46
77
  body {
47
- background-color: var(--waffle-cream);
48
- color: var(--waffle-syrup);
78
+ background-color: var(--body-bg-color);
79
+ color: var(--text-color);
49
80
  background-image:
50
81
  repeating-linear-gradient(
51
82
  0deg,
@@ -66,45 +97,11 @@ body {
66
97
  header {
67
98
  background: linear-gradient(180deg, var(--waffle-crust), #a86e2e);
68
99
  border-bottom: 3px solid var(--waffle-syrup);
69
- color: var(--waffle-cream);
70
100
  box-shadow: 0 2px 8px rgba(139, 69, 19, 0.25);
71
101
  }
72
102
 
73
- header a,
74
- header button,
75
- header .button {
76
- color: var(--waffle-cream);
77
- }
78
-
79
- #sidebar-toggler-button,
80
- #sidebar-toggler-button.neutral,
81
- header .theme-toggle,
82
- header .theme-toggle.neutral,
83
- header .search-trigger,
84
- header .search-trigger.neutral {
85
- background-color: transparent;
86
- border: 0;
87
- color: var(--waffle-cream);
88
- }
89
-
90
- #sidebar-toggler-button:hover,
91
- header .theme-toggle:hover,
92
- header .search-trigger:hover,
93
- header .user-info:hover {
94
- background-color: var(--header-hover-background-color);
95
- color: var(--header-hover-text-color);
96
- }
97
-
98
- section {
99
- background-color: #fffdf6;
100
- border: 1px solid var(--border-color);
101
- box-shadow: 0 2px 6px rgba(139, 69, 19, 0.12);
102
- }
103
-
104
103
  section .section-header {
105
- background-color: var(--waffle-batter);
106
104
  border-bottom: 2px solid var(--waffle-crust);
107
- color: var(--waffle-syrup);
108
105
  }
109
106
 
110
107
  button,
@@ -121,23 +118,49 @@ button:hover,
121
118
 
122
119
  button.good,
123
120
  .button.good {
124
- background-color: #9ccc65;
125
- border-color: #6a9a38;
126
121
  color: #2a4010;
127
122
  }
128
123
 
129
124
  button.bad,
130
125
  .button.bad {
131
- background-color: var(--waffle-berry);
132
126
  border-color: #8a2040;
133
- color: #fff8e7;
134
- }
135
-
136
- a {
137
- color: #a05020;
138
127
  }
139
128
 
140
129
  footer {
141
- color: var(--waffle-syrup);
142
130
  border-top: 1px dashed var(--border-color);
143
131
  }
132
+
133
+ html[data-theme="dark"] {
134
+ --waffle-foreground: #3a1f08;
135
+ --waffle-foreground-muted: #5c3a18;
136
+ --waffle-accent-dark: #6b3410;
137
+
138
+ --text-color: var(--waffle-foreground);
139
+ --muted-text-color: var(--waffle-foreground-muted);
140
+ --link-color: var(--waffle-accent-dark);
141
+ --border-color: #c9a66a;
142
+ --standout-bg-color: var(--waffle-batter);
143
+ --hover-background-color: #f0d9a8;
144
+ --hover-text-color: var(--waffle-foreground);
145
+ --disabled-text-color: #7a6548;
146
+
147
+ --body-bg-color: var(--waffle-cream);
148
+ --surface-bg-color: var(--waffle-batter);
149
+ --section-header-bg-color: var(--waffle-section);
150
+ --sidebar-bg-color: var(--waffle-sidebar);
151
+ --sidebar-link-color: var(--waffle-foreground);
152
+ --sidebar-hover-bg-color: var(--hover-background-color);
153
+ --sidebar-border-color: var(--waffle-crust);
154
+
155
+ --input-bg-color: var(--waffle-cream);
156
+ --input-fg-color: var(--waffle-foreground);
157
+ }
158
+
159
+ html[data-theme="dark"] section .section-header,
160
+ html[data-theme="dark"] section .section-subheader {
161
+ border-bottom: 2px solid var(--waffle-crust);
162
+ }
163
+
164
+ html[data-theme="dark"] a:visited {
165
+ color: #5c2d0e;
166
+ }
package/vite.config.js CHANGED
@@ -4,9 +4,13 @@ import Components from 'unplugin-vue-components/vite'
4
4
  import fs from 'node:fs'
5
5
  import path from 'node:path'
6
6
  import { fileURLToPath } from 'node:url'
7
+ import { createReadStream, existsSync } from 'node:fs'
8
+ import { stat } from 'node:fs/promises'
7
9
 
8
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
9
11
  const themesDir = path.join(__dirname, 'public', 'themes')
12
+ const supplementalThemesDir = path.join(__dirname, 'supplemental-themes')
13
+ const includeSupplementalThemes = process.env.VITE_INCLUDE_SUPPLEMENTAL_THEMES !== 'false'
10
14
 
11
15
  function scanThemeNames() {
12
16
  if (!fs.existsSync(themesDir)) {
@@ -58,6 +62,92 @@ function themesIndexPlugin() {
58
62
  }
59
63
  }
60
64
 
65
+ function contentTypeFor(filePath) {
66
+ if (filePath.endsWith('.css')) {
67
+ return 'text/css; charset=utf-8'
68
+ }
69
+ if (filePath.endsWith('.json')) {
70
+ return 'application/json; charset=utf-8'
71
+ }
72
+ if (filePath.endsWith('.png')) {
73
+ return 'image/png'
74
+ }
75
+ if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) {
76
+ return 'image/jpeg'
77
+ }
78
+ if (filePath.endsWith('.webp')) {
79
+ return 'image/webp'
80
+ }
81
+ if (filePath.endsWith('.svg')) {
82
+ return 'image/svg+xml'
83
+ }
84
+ return 'application/octet-stream'
85
+ }
86
+
87
+ function copyDirectoryRecursive(sourceDir, targetDir) {
88
+ fs.mkdirSync(targetDir, { recursive: true })
89
+
90
+ for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
91
+ const sourcePath = path.join(sourceDir, entry.name)
92
+ const targetPath = path.join(targetDir, entry.name)
93
+
94
+ if (entry.isDirectory()) {
95
+ copyDirectoryRecursive(sourcePath, targetPath)
96
+ continue
97
+ }
98
+
99
+ fs.copyFileSync(sourcePath, targetPath)
100
+ }
101
+ }
102
+
103
+ function supplementalThemesPlugin() {
104
+ return {
105
+ name: 'picocrank-supplemental-themes',
106
+ configureServer(server) {
107
+ if (!includeSupplementalThemes || !fs.existsSync(supplementalThemesDir)) {
108
+ return
109
+ }
110
+
111
+ server.middlewares.use('/supplemental-themes', (req, res, next) => {
112
+ const requestPath = decodeURIComponent((req.url || '/').split('?')[0])
113
+ const relativePath = requestPath.replace(/^\/+/, '')
114
+ const filePath = path.resolve(supplementalThemesDir, relativePath)
115
+
116
+ if (!filePath.startsWith(path.resolve(supplementalThemesDir))) {
117
+ res.statusCode = 403
118
+ res.end('Forbidden')
119
+ return
120
+ }
121
+
122
+ if (!existsSync(filePath)) {
123
+ next()
124
+ return
125
+ }
126
+
127
+ stat(filePath).then((fileStat) => {
128
+ if (!fileStat.isFile()) {
129
+ next()
130
+ return
131
+ }
132
+
133
+ res.setHeader('Content-Type', contentTypeFor(filePath))
134
+ createReadStream(filePath).pipe(res)
135
+ }).catch(() => {
136
+ next()
137
+ })
138
+ })
139
+ },
140
+ closeBundle() {
141
+ if (!includeSupplementalThemes || !fs.existsSync(supplementalThemesDir)) {
142
+ return
143
+ }
144
+
145
+ const outDir = path.resolve(__dirname, 'dist', 'supplemental-themes')
146
+ copyDirectoryRecursive(supplementalThemesDir, outDir)
147
+ },
148
+ }
149
+ }
150
+
61
151
  export default defineConfig({
62
152
  base: '/',
63
153
  build: {
@@ -93,6 +183,7 @@ export default defineConfig({
93
183
  },
94
184
  plugins: [
95
185
  themesIndexPlugin(),
186
+ supplementalThemesPlugin(),
96
187
  Components({
97
188
  dirs: ['./vue/components/'],
98
189
  extensions: ['vue'],
@@ -205,7 +205,7 @@
205
205
  .header-actions :deep(button) {
206
206
  border: 0;
207
207
  border-radius: 0;
208
- color: #fff;
208
+ color: var(--header-fg-color, var(--header-text-color, #fff));
209
209
  background-color: transparent;
210
210
  align-self: stretch;
211
211
  display: inline-flex;
@@ -227,7 +227,7 @@
227
227
  #sidebar-toggler-button {
228
228
  border: 0;
229
229
  border-radius: 0;
230
- color: #fff;
230
+ color: var(--header-fg-color, var(--header-text-color, #fff));
231
231
  background-color: transparent;
232
232
  align-self: stretch;
233
233
  display: inline-flex;
@@ -260,7 +260,7 @@ header .search-trigger,
260
260
  header .search-trigger.neutral {
261
261
  border: 0;
262
262
  border-radius: 0;
263
- color: #fff;
263
+ color: var(--header-fg-color, var(--header-text-color, #fff));
264
264
  background-color: transparent;
265
265
  align-self: stretch;
266
266
  height: 100%;
@@ -7,6 +7,7 @@
7
7
  <form @submit.prevent="handleLocalLogin" class="local-login-form">
8
8
  <input
9
9
  id="username"
10
+ type="text"
10
11
  v-model="localLogin.username"
11
12
  required
12
13
  autocomplete="username"
@@ -240,11 +241,8 @@ defineExpose({
240
241
 
241
242
  .local-login-form input {
242
243
  padding: 0.75rem;
243
- border: 1px solid var(--border-color, #ccc);
244
244
  border-radius: 4px;
245
245
  font-size: 1em;
246
- background-color: var(--input-bg, #fff);
247
- color: var(--text-color, #000);
248
246
  width: 100%;
249
247
  box-sizing: border-box;
250
248
  }
@@ -1,27 +1,41 @@
1
1
  <template>
2
- <section :class = "'with-header-and-content ' + classes" :id = "id">
3
- <div class = "section-header flex-row">
4
- <div class = "fg1">
5
- <h2 :title = "subtitle">
6
- <slot name="title">{{ title }}</slot>
2
+ <section :class="'with-header-and-content ' + classes" :id="id">
3
+ <div class="section-header flex-row">
4
+ <div class="fg1">
5
+ <h2 :title="subtitle">
6
+ <span v-if="icon" class="section-title-with-icon">
7
+ <HugeiconsIcon
8
+ :icon="icon"
9
+ class="section-title-icon"
10
+ width="1.1em"
11
+ height="1.1em"
12
+ :strokeWidth="2"
13
+ aria-hidden="true"
14
+ />
15
+ <span class="section-title-text">
16
+ <slot name="title">{{ title }}</slot>
17
+ </span>
18
+ </span>
19
+ <slot v-else name="title">{{ title }}</slot>
7
20
  </h2>
8
21
  </div>
9
- <div role = "toolbar">
22
+ <div role="toolbar">
10
23
  <slot name="toolbar" />
11
24
  </div>
12
25
  </div>
13
- <div :class = "padding ? 'section-content padding' : 'section-content'">
26
+ <div :class="padding ? 'section-content padding' : 'section-content'">
14
27
  <slot />
15
28
  </div>
16
29
  </section>
17
30
  </template>
18
31
 
19
32
  <script setup>
33
+ import { HugeiconsIcon } from '@hugeicons/vue'
20
34
 
21
- const props = defineProps({
35
+ defineProps({
22
36
  title: {
23
37
  type: String,
24
- default: "Untitled Section",
38
+ default: 'Untitled Section',
25
39
  },
26
40
  padding: {
27
41
  type: Boolean,
@@ -29,15 +43,32 @@ const props = defineProps({
29
43
  },
30
44
  subtitle: {
31
45
  type: String,
32
- default: "",
46
+ default: '',
33
47
  },
34
48
  id: {
35
49
  type: String,
36
- default: "",
50
+ default: '',
37
51
  },
38
52
  classes: {
39
53
  type: String,
40
- default: "",
54
+ default: '',
41
55
  },
42
- });
43
- </script>
56
+ icon: {
57
+ type: [Object, Array, Function],
58
+ default: null,
59
+ },
60
+ })
61
+ </script>
62
+
63
+ <style scoped>
64
+ .section-title-with-icon {
65
+ display: inline-flex;
66
+ align-items: center;
67
+ gap: 0.45em;
68
+ vertical-align: middle;
69
+ }
70
+
71
+ .section-title-icon {
72
+ flex: 0 0 auto;
73
+ }
74
+ </style>
@@ -414,14 +414,15 @@ button {
414
414
  text-align: left;
415
415
  font-weight: 600;
416
416
  font-size: 1em;
417
- color: inherit;
417
+ color: var(--text-color);
418
418
  background: transparent;
419
419
  border-radius: 0;
420
420
  cursor: pointer;
421
421
  }
422
422
 
423
- .nav-section-header:hover {
424
- background-color: var(--hover-background-color);
423
+ .nav-section-header:hover,
424
+ .nav-section-header:focus-visible {
425
+ background-color: var(--sidebar-hover-bg-color);
425
426
  color: var(--hover-text-color);
426
427
  }
427
428
 
@@ -436,14 +437,8 @@ button {
436
437
  text-align: center;
437
438
  }
438
439
 
439
- html[data-theme="dark"] {
440
- .nav-section-header {
441
- color: #f8f9fa;
442
- }
443
-
444
- .supplemental-links {
445
- border-top: 1px solid #444;
446
- }
440
+ .supplemental-links {
441
+ border-top: 1px solid var(--border-color);
447
442
  }
448
443
 
449
444
  @media (max-width: 768px) {
@@ -105,6 +105,7 @@ a {
105
105
  padding: .75em;
106
106
  border-radius: 0;
107
107
  position: relative;
108
+ color: var(--text-color);
108
109
  }
109
110
 
110
111
  a.active {
@@ -145,17 +146,13 @@ li.disabled a {
145
146
 
146
147
  .separator {
147
148
  height: 1px;
148
- background-color: #eee;
149
+ background-color: var(--border-color);
149
150
  margin: 0.5rem 0.75rem;
150
151
  }
151
152
 
152
- html[data-theme="dark"] {
153
- a {
154
- color: #f8f9fa;
155
- }
156
-
157
- .separator {
158
- background-color: #444;
159
- }
153
+ a:hover,
154
+ a:focus-visible {
155
+ background-color: var(--sidebar-hover-bg-color);
156
+ color: var(--hover-text-color);
160
157
  }
161
158
  </style>