@plutocms/pluto 0.10.0 → 0.12.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/FEATURES.md +17 -0
  3. package/README.md +30 -0
  4. package/app/assets/css/admin-theme.css +207 -0
  5. package/app/assets/css/pluto-layer.css +40 -0
  6. package/app/components/ColorModeButton.vue +4 -2
  7. package/app/components/Pluto/Content/PlutoContentForm.vue +325 -50
  8. package/app/components/Pluto/Content/PlutoContentList.vue +192 -108
  9. package/app/components/Pluto/Dashboard/PlutoDashboardContentActivity.vue +114 -0
  10. package/app/components/Pluto/Dashboard/PlutoDashboardContentOverview.vue +103 -0
  11. package/app/components/Pluto/Dashboard/PlutoDashboardQuickActions.vue +70 -0
  12. package/app/components/Pluto/Navbar/PlutoNavbarAdmin.vue +6 -1
  13. package/app/components/Pluto/View/PlutoViewAdminPanel.vue +119 -0
  14. package/app/components/Pluto/View/PlutoViewAside.vue +79 -0
  15. package/app/components/Pluto/View/PlutoViewToolbar.vue +64 -0
  16. package/app/composables/pluto-admin-theme.ts +14 -0
  17. package/app/composables/pluto-command-palette.ts +59 -0
  18. package/app/composables/pluto-content-actions.ts +17 -0
  19. package/app/composables/pluto-content-activity.ts +37 -0
  20. package/app/composables/pluto-content-stats.ts +78 -0
  21. package/app/composables/pluto-extension.ts +1 -0
  22. package/app/composables/pluto-registry.ts +1 -0
  23. package/app/composables/pluto-view.ts +9 -0
  24. package/app/layouts/admin.vue +59 -62
  25. package/app/layouts/auth.vue +4 -2
  26. package/app/pages/admin/home.vue +34 -7
  27. package/app/plugins/pluto-core-registrations.ts +34 -0
  28. package/modules/pluto-css/css-entries.ts +182 -0
  29. package/modules/pluto-css/index.ts +86 -0
  30. package/nuxt.config.ts +3 -1
  31. package/package.json +23 -22
  32. package/scripts/generate-admin-theme.cjs +96 -0
  33. package/server/api/_pluto/content/[type]/index.get.ts +4 -1
  34. package/server/api/_pluto/content-activity/[type].get.ts +15 -0
  35. package/server/utils/pluto-content-handlers.ts +65 -1
  36. package/shared/types/content.ts +37 -0
  37. package/shared/types/registry.ts +30 -0
  38. package/shared/utils/content-map.ts +15 -0
  39. package/app/assets/css/tailwind.css +0 -11
package/CHANGELOG.md CHANGED
@@ -1,6 +1,33 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## [0.12.0](https://github.com/plutocms/pluto/compare/v0.11.0...v0.12.0) (2026-10-17)
5
+
6
+
7
+ ### ⚠ BREAKING CHANGES
8
+
9
+ * **admin:** An app with its own Tailwind entry must import @nuxt/ui and #build/pluto.css after tailwindcss:
10
+
11
+ ### Features
12
+
13
+ * **admin:** add a Cmd/Ctrl+K command palette ([#150](https://github.com/plutocms/pluto/issues/150)) ([540a4fb](https://github.com/plutocms/pluto/commit/540a4fb5f6fb5ed8b3d32fc05efe70f862222ff8))
14
+ * **admin:** add a content overview widget to the dashboard ([#152](https://github.com/plutocms/pluto/issues/152)) ([b925f83](https://github.com/plutocms/pluto/commit/b925f8383d64b64b47738bfad2617a8ecbeb3172))
15
+ * **admin:** make the view aside resizable ([#147](https://github.com/plutocms/pluto/issues/147)) ([f400cc1](https://github.com/plutocms/pluto/commit/f400cc1fa54fbcab7b4bc30c18d8f3cddad026d9))
16
+ * **admin:** redesign the dashboard, isolate the admin theme, and use one Tailwind build ([#153](https://github.com/plutocms/pluto/issues/153)) ([e1461b9](https://github.com/plutocms/pluto/commit/e1461b9a58267e71b42e2bceadc7c1f6d471861e))
17
+ * **content:** add loading, empty, and error states to the content list ([#151](https://github.com/plutocms/pluto/issues/151)) ([7d26540](https://github.com/plutocms/pluto/commit/7d2654027c4329b8fc08843a672b43d73a4e7e55))
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * **admin:** start the dashboard group below the navbar again ([#149](https://github.com/plutocms/pluto/issues/149)) ([32d4970](https://github.com/plutocms/pluto/commit/32d497013c487b317d376376e645a502f9054a35))
23
+
24
+ ## [0.11.0](https://github.com/plutocms/pluto/compare/v0.10.0...v0.11.0) (2026-10-16)
25
+
26
+
27
+ ### Features
28
+
29
+ * **admin:** rebuild admin layout on Nuxt UI dashboard components ([#145](https://github.com/plutocms/pluto/issues/145)) ([621e57b](https://github.com/plutocms/pluto/commit/621e57b83d7650b49242c4b83c5374b33c7236bc))
30
+
4
31
  ## [0.10.0](https://github.com/plutocms/pluto/compare/v0.9.1...v0.10.0) (2026-09-13)
5
32
 
6
33
 
package/FEATURES.md CHANGED
@@ -19,3 +19,20 @@ policies shared by every project here.
19
19
  admin UI (list/form components, eight built-in field widgets, generated
20
20
  `/admin/content/**` pages, and nav/pages derivation). No real adapter yet.
21
21
  @.claude/skills/content-model/SKILL.md
22
+ - Admin view chrome: per-view shell components (`PlutoViewAdminPanel`, `PlutoViewToolbar`,
23
+ `PlutoViewAside`), the generic content form's publish workflow, and the `contentActions`
24
+ registry bucket that lets a layer add its own buttons to that form.
25
+ @.claude/skills/admin-view-chrome/SKILL.md
26
+ - Command palette: Cmd/Ctrl+K and a sidebar search button open a `UDashboardSearch` with
27
+ "New <type>" commands for writable content types, every admin nav destination, and theme
28
+ commands. @.claude/skills/command-palette/SKILL.md
29
+ - Dashboard: the admin home with a date and title header, a create menu, and core widgets:
30
+ content tiles (total, +N this week, published/draft meter), a `UChart` weekly activity
31
+ chart backed by the optional adapter `countCreated`, and quick actions.
32
+ @.claude/skills/dashboard/SKILL.md
33
+ - Admin theme: the admin keeps its own palette, chart colors, surfaces, and font under
34
+ `data-pluto-admin`, so the consumer app's site theme does not reach it.
35
+ @.claude/skills/admin-theme/SKILL.md
36
+ - Single Tailwind build: the app owns the only Tailwind build. The pluto-css module generates
37
+ `#build/pluto.css` from each layer's `pluto-layer.css`, adds a fallback entry, and warns
38
+ about a wrong CSS setup. @.claude/skills/single-tailwind-build/SKILL.md
package/README.md CHANGED
@@ -135,6 +135,36 @@ export default defineNuxtConfig({
135
135
  })
136
136
  ```
137
137
 
138
+ ### Styles: one Tailwind build
139
+
140
+ Your app owns the only Tailwind build. Pluto layers do not register their own. Start your
141
+ CSS entry with these three imports, then add your own theme after them:
142
+
143
+ ```css
144
+ /* app/assets/css/main.css */
145
+ @import 'tailwindcss';
146
+ @import '@nuxt/ui';
147
+ @import '#build/pluto.css';
148
+
149
+ @theme {
150
+ /* your site's tokens */
151
+ }
152
+ ```
153
+
154
+ ```ts
155
+ // nuxt.config.ts
156
+ export default defineNuxtConfig({
157
+ css: ['~/assets/css/main.css'],
158
+ })
159
+ ```
160
+
161
+ `#build/pluto.css` brings in the CSS of every Pluto layer, including the admin theme. Nuxt UI
162
+ already scans the source of every layer. If your app has no Tailwind entry, Pluto uses a
163
+ fallback entry. If the setup is wrong, the build prints a `pluto-css` warning with the fix.
164
+
165
+ The admin keeps its own palette and font. Your site's `app.config` colors and `@theme`
166
+ tokens do not change the admin.
167
+
138
168
  ## Dashboard & headless usage
139
169
 
140
170
  Although Pluto is built on Nuxt, it is not limited to Nuxt-only consumption.
@@ -0,0 +1,207 @@
1
+ /*
2
+ * The Pluto admin theme.
3
+ *
4
+ * Two selectors carry it:
5
+ * - `:root[data-pluto-admin]`: usePlutoAdminTheme() sets this attribute on
6
+ * <html> while an admin layout is mounted, so the theme also covers
7
+ * overlays (modals, slideovers, menus, toasts, the command palette) that
8
+ * render at the end of <body>.
9
+ * - `[data-pluto-admin-scope]`: for admin chrome that also shows on the
10
+ * public site, such as the admin navbar for a logged-in admin.
11
+ *
12
+ * Every value is a literal. The consumer app styles its public site through
13
+ * its own app.config colors and @theme. Both reach the admin unless the admin
14
+ * pins its own values: app.config colors set Nuxt UI's --ui-color-* scales on
15
+ * :root, and @theme can redefine a Tailwind color such as --color-red-400.
16
+ * These selectors are more specific than Nuxt UI's :root and .dark rules.
17
+ *
18
+ * The semantic tokens (--ui-primary, --ui-bg, --ui-text, ...) are declared
19
+ * again here, not only the color scales. A custom property inherits as a
20
+ * resolved value, so on a scoped element the :root tokens would still hold
21
+ * the app's colors.
22
+ *
23
+ * Chart colors are a validated categorical palette: lightness band, chroma
24
+ * floor, colorblind separation, and 3:1 contrast pass on white (light) and
25
+ * on zinc-900 (dark). This file is generated; keep the values in sync with
26
+ * scripts/generate-admin-theme.cjs: run it from the repo root, never edit this
27
+ * file by hand.
28
+ */
29
+ :root[data-pluto-admin],
30
+ [data-pluto-admin-scope] {
31
+ /* primary: indigo */
32
+ --ui-color-primary-50: oklch(96.2% 0.018 272.314);
33
+ --ui-color-primary-100: oklch(93% 0.034 272.788);
34
+ --ui-color-primary-200: oklch(87% 0.065 274.039);
35
+ --ui-color-primary-300: oklch(78.5% 0.115 274.713);
36
+ --ui-color-primary-400: oklch(67.3% 0.182 276.935);
37
+ --ui-color-primary-500: oklch(58.5% 0.233 277.117);
38
+ --ui-color-primary-600: oklch(51.1% 0.262 276.966);
39
+ --ui-color-primary-700: oklch(45.7% 0.24 277.023);
40
+ --ui-color-primary-800: oklch(39.8% 0.195 277.366);
41
+ --ui-color-primary-900: oklch(35.9% 0.144 278.697);
42
+ --ui-color-primary-950: oklch(25.7% 0.09 281.288);
43
+
44
+ /* secondary: violet */
45
+ --ui-color-secondary-50: oklch(96.9% 0.016 293.756);
46
+ --ui-color-secondary-100: oklch(94.3% 0.029 294.588);
47
+ --ui-color-secondary-200: oklch(89.4% 0.057 293.283);
48
+ --ui-color-secondary-300: oklch(81.1% 0.111 293.571);
49
+ --ui-color-secondary-400: oklch(70.2% 0.183 293.541);
50
+ --ui-color-secondary-500: oklch(60.6% 0.25 292.717);
51
+ --ui-color-secondary-600: oklch(54.1% 0.281 293.009);
52
+ --ui-color-secondary-700: oklch(49.1% 0.27 292.581);
53
+ --ui-color-secondary-800: oklch(43.2% 0.232 292.759);
54
+ --ui-color-secondary-900: oklch(38% 0.189 293.745);
55
+ --ui-color-secondary-950: oklch(28.3% 0.141 291.089);
56
+
57
+ /* success: green */
58
+ --ui-color-success-50: oklch(98.2% 0.018 155.826);
59
+ --ui-color-success-100: oklch(96.2% 0.044 156.743);
60
+ --ui-color-success-200: oklch(92.5% 0.084 155.995);
61
+ --ui-color-success-300: oklch(87.1% 0.15 154.449);
62
+ --ui-color-success-400: oklch(79.2% 0.209 151.711);
63
+ --ui-color-success-500: oklch(72.3% 0.219 149.579);
64
+ --ui-color-success-600: oklch(62.7% 0.194 149.214);
65
+ --ui-color-success-700: oklch(52.7% 0.154 150.069);
66
+ --ui-color-success-800: oklch(44.8% 0.119 151.328);
67
+ --ui-color-success-900: oklch(39.3% 0.095 152.535);
68
+ --ui-color-success-950: oklch(26.6% 0.065 152.934);
69
+
70
+ /* info: blue */
71
+ --ui-color-info-50: oklch(97% 0.014 254.604);
72
+ --ui-color-info-100: oklch(93.2% 0.032 255.585);
73
+ --ui-color-info-200: oklch(88.2% 0.059 254.128);
74
+ --ui-color-info-300: oklch(80.9% 0.105 251.813);
75
+ --ui-color-info-400: oklch(70.7% 0.165 254.624);
76
+ --ui-color-info-500: oklch(62.3% 0.214 259.815);
77
+ --ui-color-info-600: oklch(54.6% 0.245 262.881);
78
+ --ui-color-info-700: oklch(48.8% 0.243 264.376);
79
+ --ui-color-info-800: oklch(42.4% 0.199 265.638);
80
+ --ui-color-info-900: oklch(37.9% 0.146 265.522);
81
+ --ui-color-info-950: oklch(28.2% 0.091 267.935);
82
+
83
+ /* warning: amber */
84
+ --ui-color-warning-50: oklch(98.7% 0.022 95.277);
85
+ --ui-color-warning-100: oklch(96.2% 0.059 95.617);
86
+ --ui-color-warning-200: oklch(92.4% 0.12 95.746);
87
+ --ui-color-warning-300: oklch(87.9% 0.169 91.605);
88
+ --ui-color-warning-400: oklch(82.8% 0.189 84.429);
89
+ --ui-color-warning-500: oklch(76.9% 0.188 70.08);
90
+ --ui-color-warning-600: oklch(66.6% 0.179 58.318);
91
+ --ui-color-warning-700: oklch(55.5% 0.163 48.998);
92
+ --ui-color-warning-800: oklch(47.3% 0.137 46.201);
93
+ --ui-color-warning-900: oklch(41.4% 0.112 45.904);
94
+ --ui-color-warning-950: oklch(27.9% 0.077 45.635);
95
+
96
+ /* error: red */
97
+ --ui-color-error-50: oklch(97.1% 0.013 17.38);
98
+ --ui-color-error-100: oklch(93.6% 0.032 17.717);
99
+ --ui-color-error-200: oklch(88.5% 0.062 18.334);
100
+ --ui-color-error-300: oklch(80.8% 0.114 19.571);
101
+ --ui-color-error-400: oklch(70.4% 0.191 22.216);
102
+ --ui-color-error-500: oklch(63.7% 0.237 25.331);
103
+ --ui-color-error-600: oklch(57.7% 0.245 27.325);
104
+ --ui-color-error-700: oklch(50.5% 0.213 27.518);
105
+ --ui-color-error-800: oklch(44.4% 0.177 26.899);
106
+ --ui-color-error-900: oklch(39.6% 0.141 25.723);
107
+ --ui-color-error-950: oklch(25.8% 0.092 26.042);
108
+
109
+ /* neutral: zinc */
110
+ --ui-color-neutral-50: oklch(98.5% 0 none);
111
+ --ui-color-neutral-100: oklch(96.7% 0.001 286.375);
112
+ --ui-color-neutral-200: oklch(92% 0.004 286.32);
113
+ --ui-color-neutral-300: oklch(87.1% 0.006 286.286);
114
+ --ui-color-neutral-400: oklch(70.5% 0.015 286.067);
115
+ --ui-color-neutral-500: oklch(55.2% 0.016 285.938);
116
+ --ui-color-neutral-600: oklch(44.2% 0.017 285.786);
117
+ --ui-color-neutral-700: oklch(37% 0.013 285.805);
118
+ --ui-color-neutral-800: oklch(27.4% 0.006 286.033);
119
+ --ui-color-neutral-900: oklch(21% 0.006 285.885);
120
+ --ui-color-neutral-950: oklch(14.1% 0.005 285.823);
121
+
122
+ --ui-primary: var(--ui-color-primary-500);
123
+ --ui-secondary: var(--ui-color-secondary-500);
124
+ --ui-success: var(--ui-color-success-500);
125
+ --ui-info: var(--ui-color-info-500);
126
+ --ui-warning: var(--ui-color-warning-500);
127
+ --ui-error: var(--ui-color-error-500);
128
+
129
+ --ui-text-dimmed: var(--ui-color-neutral-400);
130
+ --ui-text-muted: var(--ui-color-neutral-500);
131
+ --ui-text-toned: var(--ui-color-neutral-600);
132
+ --ui-text: var(--ui-color-neutral-700);
133
+ --ui-text-highlighted: var(--ui-color-neutral-900);
134
+ --ui-text-inverted: #fff;
135
+ --ui-bg: #fff;
136
+ --ui-bg-muted: var(--ui-color-neutral-50);
137
+ --ui-bg-elevated: var(--ui-color-neutral-100);
138
+ --ui-bg-accented: var(--ui-color-neutral-200);
139
+ --ui-bg-inverted: var(--ui-color-neutral-900);
140
+ --ui-border: var(--ui-color-neutral-200);
141
+ --ui-border-muted: var(--ui-color-neutral-200);
142
+ --ui-border-accented: var(--ui-color-neutral-300);
143
+ --ui-border-inverted: var(--ui-color-neutral-900);
144
+
145
+ --ui-chart-1: var(--ui-primary);
146
+ --ui-chart-2: oklch(66.6% 0.179 58.318); /* amber-600 */
147
+ --ui-chart-3: oklch(60% 0.118 184.704); /* teal-600 */
148
+ --ui-chart-4: oklch(64.5% 0.246 16.439); /* rose-500 */
149
+ --ui-chart-5: oklch(58.8% 0.158 241.966); /* sky-600 */
150
+ --ui-chart-6: oklch(53.2% 0.157 131.589); /* lime-700 */
151
+
152
+ /* Surfaces */
153
+ --pluto-admin-canvas: #fff;
154
+ --pluto-admin-sidebar: oklch(98.5% 0 none);
155
+ --pluto-admin-navbar: #fff;
156
+
157
+ --ui-radius: 0.375rem;
158
+ --font-sans: "Outfit", ui-sans-serif, system-ui, sans-serif;
159
+ }
160
+
161
+ :root[data-pluto-admin].dark,
162
+ .dark [data-pluto-admin-scope] {
163
+ --ui-primary: var(--ui-color-primary-400);
164
+ --ui-secondary: var(--ui-color-secondary-400);
165
+ --ui-success: var(--ui-color-success-400);
166
+ --ui-info: var(--ui-color-info-400);
167
+ --ui-warning: var(--ui-color-warning-400);
168
+ --ui-error: var(--ui-color-error-400);
169
+
170
+ --ui-text-dimmed: var(--ui-color-neutral-500);
171
+ --ui-text-muted: var(--ui-color-neutral-400);
172
+ --ui-text-toned: var(--ui-color-neutral-300);
173
+ --ui-text: var(--ui-color-neutral-200);
174
+ --ui-text-highlighted: #fff;
175
+ --ui-text-inverted: var(--ui-color-neutral-900);
176
+ --ui-bg: var(--ui-color-neutral-900);
177
+ --ui-bg-muted: var(--ui-color-neutral-800);
178
+ --ui-bg-elevated: var(--ui-color-neutral-800);
179
+ --ui-bg-accented: var(--ui-color-neutral-700);
180
+ --ui-bg-inverted: #fff;
181
+ --ui-border: var(--ui-color-neutral-800);
182
+ --ui-border-muted: var(--ui-color-neutral-700);
183
+ --ui-border-accented: var(--ui-color-neutral-700);
184
+ --ui-border-inverted: #fff;
185
+
186
+ --ui-chart-1: var(--ui-primary);
187
+ --ui-chart-2: oklch(66.6% 0.179 58.318); /* amber-600 */
188
+ --ui-chart-3: oklch(60% 0.118 184.704); /* teal-600 */
189
+ --ui-chart-4: oklch(64.5% 0.246 16.439); /* rose-500 */
190
+ --ui-chart-5: oklch(58.8% 0.158 241.966); /* sky-600 */
191
+ --ui-chart-6: oklch(64.8% 0.2 131.684); /* lime-600 */
192
+
193
+ /* Cards (--ui-bg, zinc-900) sit one step above the canvas (zinc-950). */
194
+ --pluto-admin-canvas: oklch(14.1% 0.005 285.823);
195
+ --pluto-admin-sidebar: oklch(14.1% 0.005 285.823);
196
+ --pluto-admin-navbar: oklch(14.1% 0.005 285.823);
197
+ }
198
+
199
+ :root[data-pluto-admin] body {
200
+ background-color: var(--pluto-admin-canvas);
201
+ }
202
+
203
+ :root[data-pluto-admin] body,
204
+ [data-pluto-admin-scope] {
205
+ font-family: var(--font-sans);
206
+ color: var(--ui-text);
207
+ }
@@ -0,0 +1,40 @@
1
+ /*
2
+ * The pluto layer's CSS. It is not a Tailwind entry.
3
+ *
4
+ * The app's CSS entry owns the one Tailwind build of the app:
5
+ *
6
+ * @import 'tailwindcss';
7
+ * @import '@nuxt/ui';
8
+ * @import '#build/pluto.css';
9
+ *
10
+ * `#build/pluto.css` imports this file (and the same file of every other
11
+ * Pluto layer). Rules for this file:
12
+ * - Do not import tailwindcss or @nuxt/ui here.
13
+ * - Use only @theme, @custom-variant, @utility, unlayered rules, and
14
+ * imports of plain CSS files next to this one.
15
+ * - Add no @source for app/: Nuxt UI already scans every layer's app/ dir.
16
+ * See the single-tailwind-build skill.
17
+ */
18
+ @import './admin-theme.css';
19
+
20
+ @theme {
21
+ --color-admin-content: var(--color-zinc-800);
22
+ --color-admin-sidebar: var(--color-zinc-900);
23
+
24
+ --font-outfit: 'Outfit', sans-serif;
25
+ }
26
+
27
+ /*
28
+ * Set the header height outside a layer. This overrides Nuxt UI's own
29
+ * `@layer theme` value for the same variable. The values match the
30
+ * navbar's own height classes (`h-14` / `lg:h-10`).
31
+ */
32
+ :root {
33
+ --ui-header-height: 3.5rem;
34
+ }
35
+
36
+ @media (width >= 64rem) {
37
+ :root {
38
+ --ui-header-height: 2.5rem;
39
+ }
40
+ }
@@ -15,9 +15,11 @@ const isDark = computed({
15
15
  <ClientOnly v-if="!colorMode?.forced">
16
16
  <UButton
17
17
  :icon="isDark ? 'i-lucide-moon' : 'i-lucide-sun'"
18
+ :aria-label="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
18
19
  color="neutral"
19
- variant="solid"
20
- size="sm"
20
+ variant="ghost"
21
+ size="md"
22
+ square
21
23
  @click="isDark = !isDark"
22
24
  />
23
25