@motor-cms/ui-admin 1.0.1-alpha.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/README.md +77 -0
- package/app/components/form/inputs/CategoryTreeInput.vue +154 -0
- package/app/components/form/inputs/CategoryTreePicker.vue +355 -0
- package/app/components/form/inputs/NestedDraggable.vue +217 -0
- package/app/components/form/inputs/QuicklinksInput.vue +186 -0
- package/app/lang/de/motor-admin/CLAUDE.md +21 -0
- package/app/lang/de/motor-admin/ai_system_prompts.json +12 -0
- package/app/lang/de/motor-admin/categories.json +12 -0
- package/app/lang/de/motor-admin/category_trees.json +14 -0
- package/app/lang/de/motor-admin/clients.json +26 -0
- package/app/lang/de/motor-admin/config_variables.json +14 -0
- package/app/lang/de/motor-admin/domains.json +19 -0
- package/app/lang/de/motor-admin/email_templates.json +38 -0
- package/app/lang/de/motor-admin/global.json +5 -0
- package/app/lang/de/motor-admin/languages.json +16 -0
- package/app/lang/de/motor-admin/permissions.json +14 -0
- package/app/lang/de/motor-admin/roles.json +15 -0
- package/app/lang/de/motor-admin/users.json +22 -0
- package/app/lang/en/motor-admin/CLAUDE.md +7 -0
- package/app/lang/en/motor-admin/ai_system_prompts.json +12 -0
- package/app/lang/en/motor-admin/categories.json +12 -0
- package/app/lang/en/motor-admin/category_trees.json +14 -0
- package/app/lang/en/motor-admin/clients.json +26 -0
- package/app/lang/en/motor-admin/config_variables.json +14 -0
- package/app/lang/en/motor-admin/domains.json +18 -0
- package/app/lang/en/motor-admin/email_templates.json +33 -0
- package/app/lang/en/motor-admin/global.json +5 -0
- package/app/lang/en/motor-admin/languages.json +16 -0
- package/app/lang/en/motor-admin/permissions.json +14 -0
- package/app/lang/en/motor-admin/roles.json +15 -0
- package/app/lang/en/motor-admin/users.json +22 -0
- package/app/pages/dashboard.vue +5 -0
- package/app/pages/index.vue +39 -0
- package/app/pages/login.vue +85 -0
- package/app/pages/motor-admin/ai-system-prompts/CLAUDE.md +7 -0
- package/app/pages/motor-admin/ai-system-prompts/[id]/edit.vue +48 -0
- package/app/pages/motor-admin/ai-system-prompts/create.vue +40 -0
- package/app/pages/motor-admin/ai-system-prompts/index.vue +68 -0
- package/app/pages/motor-admin/category-trees/CLAUDE.md +7 -0
- package/app/pages/motor-admin/category-trees/[id]/CLAUDE.md +7 -0
- package/app/pages/motor-admin/category-trees/[id]/categories/[categoryId]/edit.vue +73 -0
- package/app/pages/motor-admin/category-trees/[id]/categories/create.vue +64 -0
- package/app/pages/motor-admin/category-trees/[id]/edit.vue +45 -0
- package/app/pages/motor-admin/category-trees/[id]/index.vue +81 -0
- package/app/pages/motor-admin/category-trees/create.vue +37 -0
- package/app/pages/motor-admin/category-trees/index.vue +54 -0
- package/app/pages/motor-admin/clients/CLAUDE.md +11 -0
- package/app/pages/motor-admin/clients/[id]/CLAUDE.md +11 -0
- package/app/pages/motor-admin/clients/[id]/edit.vue +45 -0
- package/app/pages/motor-admin/clients/create.vue +37 -0
- package/app/pages/motor-admin/clients/index.vue +46 -0
- package/app/pages/motor-admin/config-variables/CLAUDE.md +11 -0
- package/app/pages/motor-admin/config-variables/[id]/edit.vue +44 -0
- package/app/pages/motor-admin/config-variables/create.vue +36 -0
- package/app/pages/motor-admin/config-variables/index.vue +66 -0
- package/app/pages/motor-admin/domains/CLAUDE.md +11 -0
- package/app/pages/motor-admin/domains/[id]/edit.vue +54 -0
- package/app/pages/motor-admin/domains/create.vue +46 -0
- package/app/pages/motor-admin/domains/index.vue +98 -0
- package/app/pages/motor-admin/email-templates/CLAUDE.md +12 -0
- package/app/pages/motor-admin/email-templates/[id]/CLAUDE.md +7 -0
- package/app/pages/motor-admin/email-templates/[id]/edit.vue +56 -0
- package/app/pages/motor-admin/email-templates/create.vue +48 -0
- package/app/pages/motor-admin/email-templates/index.vue +67 -0
- package/app/pages/motor-admin/index.vue +12 -0
- package/app/pages/motor-admin/languages/CLAUDE.md +7 -0
- package/app/pages/motor-admin/languages/[id]/edit.vue +44 -0
- package/app/pages/motor-admin/languages/create.vue +36 -0
- package/app/pages/motor-admin/languages/index.vue +44 -0
- package/app/pages/motor-admin/permission-groups/CLAUDE.md +14 -0
- package/app/pages/motor-admin/permission-groups/[id]/CLAUDE.md +11 -0
- package/app/pages/motor-admin/permission-groups/[id]/edit.vue +49 -0
- package/app/pages/motor-admin/permission-groups/create.vue +41 -0
- package/app/pages/motor-admin/permission-groups/index.vue +43 -0
- package/app/pages/motor-admin/roles/CLAUDE.md +7 -0
- package/app/pages/motor-admin/roles/[id]/edit.vue +47 -0
- package/app/pages/motor-admin/roles/create.vue +40 -0
- package/app/pages/motor-admin/roles/index.vue +45 -0
- package/app/pages/motor-admin/theme-preview/CLAUDE.md +7 -0
- package/app/pages/motor-admin/theme-preview/index.vue +4801 -0
- package/app/pages/motor-admin/theme-preview/themes/CLAUDE.md +11 -0
- package/app/pages/motor-admin/theme-preview/themes/asymmetric-brutalist.md +381 -0
- package/app/pages/motor-admin/theme-preview/themes/bold-modern.md +231 -0
- package/app/pages/motor-admin/theme-preview/themes/geometric-minimal.md +778 -0
- package/app/pages/motor-admin/theme-preview/themes/gradient-flow.md +1057 -0
- package/app/pages/motor-admin/theme-preview/themes/liquid-glass.md +823 -0
- package/app/pages/motor-admin/theme-preview/themes/neon-amber.md +1223 -0
- package/app/pages/motor-admin/theme-preview/themes/neon-terminal.md +779 -0
- package/app/pages/motor-admin/theme-preview/themes/neon-violet.md +1134 -0
- package/app/pages/motor-admin/theme-preview/themes/professional-clean.md +232 -0
- package/app/pages/motor-admin/theme-preview/themes/refined-brutalist.md +462 -0
- package/app/pages/motor-admin/theme-preview/themes/wild-card.md +263 -0
- package/app/pages/motor-admin/users/CLAUDE.md +17 -0
- package/app/pages/motor-admin/users/[id]/CLAUDE.md +11 -0
- package/app/pages/motor-admin/users/[id]/edit.vue +83 -0
- package/app/pages/motor-admin/users/create.vue +40 -0
- package/app/pages/motor-admin/users/index.vue +66 -0
- package/app/pages/profile.vue +363 -0
- package/app/pages/search.vue +91 -0
- package/app/types/generated/form-meta.ts +258 -0
- package/app/types/generated/grid-meta.ts +172 -0
- package/nuxt.config.ts +1 -0
- package/package.json +26 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# Wild Card Theme: "Botanical Observatory"
|
|
2
|
+
|
|
3
|
+
## Mood / Inspiration
|
|
4
|
+
|
|
5
|
+
**Concept:** A dark, warm admin panel inspired by the aesthetic of Victorian-era natural history museums, botanical illustration studios, and scientific observatories. Think aged leather journals, brass instruments, pressed botanical specimens, and handwritten field notes -- translated into a modern digital interface.
|
|
6
|
+
|
|
7
|
+
**Why it works as a wild card:** Nobody expects an admin panel to feel like stepping into a naturalist's study. Most admin themes oscillate between sterile corporate blue/gray and trendy neon-on-dark. This theme goes in a completely different direction: warm, textured, intimate, and scholarly. It uses a serif heading font (almost unheard of in admin UIs), earthy warm tones instead of cool neutrals, and amber accents instead of blue/purple.
|
|
8
|
+
|
|
9
|
+
**The surprise factor:** Serif typography in a dashboard. Dark backgrounds that feel warm rather than cold. An accent color (amber/gold) that evokes candlelight and brass rather than tech-startup energy. The overall effect is an admin panel that feels like a place of thoughtful craft rather than a utilitarian tool.
|
|
10
|
+
|
|
11
|
+
**Reference aesthetics:** Dark academia, botanical illustration, old-world cartography, observatory control rooms, apothecary shelving systems.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Color Palette
|
|
16
|
+
|
|
17
|
+
### Primary Colors
|
|
18
|
+
|
|
19
|
+
| Role | Name | Hex | OKLCH (Tailwind 4) | Tailwind Family |
|
|
20
|
+
|------|------|-----|---------------------|-----------------|
|
|
21
|
+
| **Background (deep)** | Observatory Dark | `#1C1917` | `oklch(0.205 0.006 56.043)` | `stone-900` |
|
|
22
|
+
| **Background (surface)** | Walnut Panel | `#292524` | `oklch(0.269 0.006 56.043)` | `stone-800` |
|
|
23
|
+
| **Background (elevated)** | Aged Leather | `#44403C` | `oklch(0.371 0.006 56.043)` | `stone-700` |
|
|
24
|
+
| **Border / Divider** | Specimen Frame | `#57534E` | `oklch(0.444 0.006 56.043)` | `stone-600` |
|
|
25
|
+
| **Text (primary)** | Parchment | `#FAFAF9` | `oklch(0.985 0.002 106.424)` | `stone-50` |
|
|
26
|
+
| **Text (secondary)** | Aged Paper | `#D6D3D1` | `oklch(0.87 0.005 56.366)` | `stone-300` |
|
|
27
|
+
| **Text (muted)** | Faded Ink | `#A8A29E` | `oklch(0.709 0.01 56.259)` | `stone-400` |
|
|
28
|
+
|
|
29
|
+
### Accent Colors
|
|
30
|
+
|
|
31
|
+
| Role | Name | Hex | OKLCH (Tailwind 4) | Tailwind Family |
|
|
32
|
+
|------|------|-----|---------------------|-----------------|
|
|
33
|
+
| **Primary accent** | Brass | `#F59E0B` | `oklch(0.769 0.188 70.08)` | `amber-500` |
|
|
34
|
+
| **Primary accent (hover)** | Polished Brass | `#FBBF24` | `oklch(0.828 0.175 74.011)` | `amber-400` |
|
|
35
|
+
| **Primary accent (muted)** | Patina Gold | `#92400E` | `oklch(0.444 0.107 47.604)` | `amber-800` |
|
|
36
|
+
| **Success** | Botanical Green | `#10B981` | `oklch(0.696 0.17 162.48)` | `emerald-500` |
|
|
37
|
+
| **Success (surface)** | Pressed Leaf | `#064E3B` | `oklch(0.378 0.077 168.94)` | `emerald-900` |
|
|
38
|
+
| **Danger** | Sealing Wax | `#EF4444` | `oklch(0.637 0.237 25.331)` | `red-500` |
|
|
39
|
+
| **Warning** | Candlelight | `#EAB308` | `oklch(0.795 0.184 86.047)` | `yellow-500` |
|
|
40
|
+
| **Info** | Compass Blue | `#0EA5E9` | `oklch(0.685 0.169 222.979)` | `sky-500` |
|
|
41
|
+
|
|
42
|
+
### Functional Surface Colors
|
|
43
|
+
|
|
44
|
+
| Role | Hex | Usage |
|
|
45
|
+
|------|-----|-------|
|
|
46
|
+
| **Card background** | `#292524` (stone-800) | Cards, panels, dropdowns |
|
|
47
|
+
| **Card background (hover)** | `#44403C` (stone-700) | Hovered cards, selected rows |
|
|
48
|
+
| **Input background** | `#1C1917` (stone-900) | Text inputs, selects |
|
|
49
|
+
| **Input border** | `#57534E` (stone-600) | Input borders at rest |
|
|
50
|
+
| **Input border (focus)** | `#F59E0B` (amber-500) | Focused input ring |
|
|
51
|
+
| **Sidebar background** | `#0C0A09` (stone-950) | Navigation sidebar |
|
|
52
|
+
| **Header background** | `#1C1917` (stone-900) | Top header bar |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Font Pairing
|
|
57
|
+
|
|
58
|
+
### Heading Font: Fraunces
|
|
59
|
+
|
|
60
|
+
**Google Fonts:** [Fraunces](https://fonts.google.com/specimen/Fraunces)
|
|
61
|
+
|
|
62
|
+
A soft-serif "Old Style" variable typeface inspired by early 20th century display faces. It has four variable axes (weight, optical size, softness, and "wonk") that give it subtle personality -- slightly quirky letterforms that feel hand-drawn without sacrificing legibility. The "wonky" alternates (leaning n/m/h shapes) add the feel of a naturalist's handwriting.
|
|
63
|
+
|
|
64
|
+
- **Use for:** Page titles, section headings, card headers, modal titles
|
|
65
|
+
- **Weight range:** 400-700 (use 600 for headings, 700 for page titles)
|
|
66
|
+
- **Optical size:** 24-48px for headings (uses display-optimized forms automatically)
|
|
67
|
+
- **WONK axis:** Set to 1 for headings to activate playful alternates
|
|
68
|
+
- **SOFT axis:** Set to 50 for a balanced warmth (not too sharp, not too bubbly)
|
|
69
|
+
|
|
70
|
+
**Why it's unexpected:** Serif fonts in admin panels are almost taboo. Fraunces breaks that rule with character and warmth while remaining perfectly legible. Its variable "wonk" axis adds a naturalist's touch that reinforces the botanical theme.
|
|
71
|
+
|
|
72
|
+
### Body Font: Instrument Sans
|
|
73
|
+
|
|
74
|
+
**Google Fonts:** [Instrument Sans](https://fonts.google.com/specimen/Instrument+Sans)
|
|
75
|
+
|
|
76
|
+
A precise, legible sans-serif with subtle playfulness. Clean enough for data-dense tables and forms, but with enough character to complement Fraunces. Features 12 stylistic sets for fine-tuning.
|
|
77
|
+
|
|
78
|
+
- **Use for:** Body text, form labels, table data, navigation items, buttons
|
|
79
|
+
- **Weight range:** 400-600 (400 for body, 500 for labels, 600 for buttons)
|
|
80
|
+
- **Size:** 14-16px for body, 12-13px for captions and metadata
|
|
81
|
+
|
|
82
|
+
### Monospace Font: JetBrains Mono
|
|
83
|
+
|
|
84
|
+
**Google Fonts:** [JetBrains Mono](https://fonts.google.com/specimen/JetBrains+Mono)
|
|
85
|
+
|
|
86
|
+
For code snippets, config values, and technical identifiers. Its ligatures and clear character differentiation fit the "precision instruments" sub-theme.
|
|
87
|
+
|
|
88
|
+
- **Use for:** Code blocks, IDs, technical values
|
|
89
|
+
- **Size:** 13-14px
|
|
90
|
+
|
|
91
|
+
### Google Fonts Import URL
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@0,9..144,400..700,50,1;1,9..144,400..700,50,1&family=Instrument+Sans:ital,wght@0,400..700;1,400..700&family=JetBrains+Mono:wght@400;500&display=swap
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### CSS Font Stack
|
|
98
|
+
|
|
99
|
+
```css
|
|
100
|
+
:root {
|
|
101
|
+
--font-heading: 'Fraunces', Georgia, 'Times New Roman', serif;
|
|
102
|
+
--font-body: 'Instrument Sans', 'Segoe UI', system-ui, sans-serif;
|
|
103
|
+
--font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Component Styling Notes
|
|
110
|
+
|
|
111
|
+
### What Makes This Theme Feel Different
|
|
112
|
+
|
|
113
|
+
1. **Serif headings in a sans-serif world.** Every page title and section header uses Fraunces, creating immediate visual distinction from every other admin panel. The slight "wonkiness" of the letterforms adds warmth and humanity.
|
|
114
|
+
|
|
115
|
+
2. **Warm dark mode.** Most dark themes use cool blue-grays (slate, zinc). This theme uses stone -- warm brownish grays that feel like dark wood and leather rather than a server room. The difference is subtle but profoundly affects mood.
|
|
116
|
+
|
|
117
|
+
3. **Amber instead of blue.** The primary accent is amber/gold instead of the ubiquitous blue. Buttons glow like brass fittings. Focus rings feel like candlelight. Links shimmer rather than screech.
|
|
118
|
+
|
|
119
|
+
4. **Subtle texture hints.** Consider a very faint noise texture overlay (opacity 2-3%) on the sidebar background to evoke aged paper. This is optional but reinforces the tactile quality.
|
|
120
|
+
|
|
121
|
+
5. **Generous spacing.** Slightly more padding than typical admin panels (think 6 instead of 4, 8 instead of 6 in Tailwind units). The theme should feel unhurried and considered, like a well-organized specimen cabinet.
|
|
122
|
+
|
|
123
|
+
### Border Radius
|
|
124
|
+
|
|
125
|
+
- **Small elements** (buttons, inputs, badges): `rounded-md` (6px) -- not fully sharp, not overly rounded
|
|
126
|
+
- **Cards and panels**: `rounded-lg` (8px)
|
|
127
|
+
- **Modals and overlays**: `rounded-xl` (12px)
|
|
128
|
+
- **Avatars**: `rounded-full` (keep circular)
|
|
129
|
+
- **No pill shapes** -- avoid `rounded-full` on buttons; it feels too playful for the scholarly tone
|
|
130
|
+
|
|
131
|
+
### Shadows
|
|
132
|
+
|
|
133
|
+
Use warm-tinted shadows rather than default neutral ones:
|
|
134
|
+
|
|
135
|
+
```css
|
|
136
|
+
--shadow-sm: 0 1px 2px 0 rgba(28, 25, 23, 0.3);
|
|
137
|
+
--shadow-md: 0 4px 6px -1px rgba(28, 25, 23, 0.4), 0 2px 4px -2px rgba(28, 25, 23, 0.3);
|
|
138
|
+
--shadow-lg: 0 10px 15px -3px rgba(28, 25, 23, 0.5), 0 4px 6px -4px rgba(28, 25, 23, 0.4);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Buttons
|
|
142
|
+
|
|
143
|
+
| Variant | Background | Text | Border | Hover |
|
|
144
|
+
|---------|-----------|------|--------|-------|
|
|
145
|
+
| **Primary** | `amber-500` | `stone-900` | none | `amber-400` bg |
|
|
146
|
+
| **Secondary** | `stone-700` | `stone-200` | `stone-600` 1px | `stone-600` bg |
|
|
147
|
+
| **Ghost** | transparent | `stone-300` | none | `stone-800` bg |
|
|
148
|
+
| **Danger** | `red-500/10` | `red-400` | `red-500/20` 1px | `red-500/20` bg |
|
|
149
|
+
|
|
150
|
+
Primary buttons with dark text on amber background create an effect resembling engraved brass plates.
|
|
151
|
+
|
|
152
|
+
### Tables
|
|
153
|
+
|
|
154
|
+
- **Header row:** `stone-800` background, `stone-300` text, `font-medium` in Instrument Sans
|
|
155
|
+
- **Body rows:** `stone-900` background, `stone-200` text
|
|
156
|
+
- **Alternating rows:** Alternate between `stone-900` and `stone-900/50` (very subtle)
|
|
157
|
+
- **Hover row:** `stone-800` background with left border accent in `amber-500` (2px)
|
|
158
|
+
- **Selected row:** `amber-500/5` background with `amber-500` left border (2px)
|
|
159
|
+
|
|
160
|
+
### Sidebar Navigation
|
|
161
|
+
|
|
162
|
+
- **Background:** `stone-950` (deepest dark)
|
|
163
|
+
- **Nav item (default):** `stone-400` text, no background
|
|
164
|
+
- **Nav item (hover):** `stone-200` text, `stone-800` background
|
|
165
|
+
- **Nav item (active):** `amber-500` text, `amber-500/10` background, `amber-500` left border (2px)
|
|
166
|
+
- **Section headings:** Fraunces font at 11px, `stone-500` text, uppercase tracking-wider
|
|
167
|
+
- **Dividers:** `stone-800` with 1px height
|
|
168
|
+
|
|
169
|
+
### Form Inputs
|
|
170
|
+
|
|
171
|
+
- **Background:** `stone-900`
|
|
172
|
+
- **Border:** 1px `stone-600`
|
|
173
|
+
- **Text:** `stone-100`
|
|
174
|
+
- **Placeholder:** `stone-500`
|
|
175
|
+
- **Focus:** `amber-500` ring (2px), `amber-500/10` background tint
|
|
176
|
+
- **Label:** `stone-300`, Instrument Sans 500 weight, 13px
|
|
177
|
+
|
|
178
|
+
### Cards
|
|
179
|
+
|
|
180
|
+
- **Background:** `stone-800`
|
|
181
|
+
- **Border:** 1px `stone-700`
|
|
182
|
+
- **Header text:** Fraunces 600, `stone-100`
|
|
183
|
+
- **Body text:** Instrument Sans 400, `stone-300`
|
|
184
|
+
- **Footer:** `stone-800` with top border `stone-700`
|
|
185
|
+
|
|
186
|
+
### Badges / Tags
|
|
187
|
+
|
|
188
|
+
- **Default:** `stone-700` bg, `stone-300` text
|
|
189
|
+
- **Success:** `emerald-900` bg, `emerald-400` text
|
|
190
|
+
- **Warning:** `amber-900` bg, `amber-400` text
|
|
191
|
+
- **Danger:** `red-900` bg, `red-400` text
|
|
192
|
+
- **Info:** `sky-900` bg, `sky-400` text
|
|
193
|
+
- **Style:** `rounded-md`, `text-xs`, `font-medium` (Instrument Sans)
|
|
194
|
+
|
|
195
|
+
### Toasts / Notifications
|
|
196
|
+
|
|
197
|
+
Follow badge color patterns but with a left border accent (3px) in the status color. Background uses the status color at 5% opacity over `stone-800`.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Implementation Notes for Tailwind / NuxtUI 4
|
|
202
|
+
|
|
203
|
+
### CSS Custom Properties
|
|
204
|
+
|
|
205
|
+
```css
|
|
206
|
+
:root {
|
|
207
|
+
/* Botanical Observatory Theme */
|
|
208
|
+
--ui-bg: oklch(0.205 0.006 56.043); /* stone-900 */
|
|
209
|
+
--ui-bg-elevated: oklch(0.269 0.006 56.043); /* stone-800 */
|
|
210
|
+
--ui-bg-muted: oklch(0.371 0.006 56.043); /* stone-700 */
|
|
211
|
+
--ui-border: oklch(0.444 0.006 56.043); /* stone-600 */
|
|
212
|
+
--ui-text: oklch(0.985 0.002 106.424); /* stone-50 */
|
|
213
|
+
--ui-text-muted: oklch(0.709 0.01 56.259); /* stone-400 */
|
|
214
|
+
--ui-primary: oklch(0.769 0.188 70.08); /* amber-500 */
|
|
215
|
+
--ui-primary-hover: oklch(0.828 0.175 74.011); /* amber-400 */
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### NuxtUI 4 App Config Theme Mapping
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
// app.config.ts
|
|
223
|
+
export default defineAppConfig({
|
|
224
|
+
ui: {
|
|
225
|
+
colors: {
|
|
226
|
+
primary: 'amber',
|
|
227
|
+
secondary: 'stone',
|
|
228
|
+
success: 'emerald',
|
|
229
|
+
info: 'sky',
|
|
230
|
+
warning: 'yellow',
|
|
231
|
+
error: 'red',
|
|
232
|
+
neutral: 'stone' // warm neutrals throughout
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Key Tailwind Utility Classes
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
/* Page background */ bg-stone-900
|
|
242
|
+
/* Card surface */ bg-stone-800 border border-stone-700
|
|
243
|
+
/* Primary button */ bg-amber-500 text-stone-900 hover:bg-amber-400
|
|
244
|
+
/* Heading text */ font-[Fraunces] text-stone-50
|
|
245
|
+
/* Body text */ font-[Instrument_Sans] text-stone-300
|
|
246
|
+
/* Muted text */ text-stone-400
|
|
247
|
+
/* Focus ring */ focus:ring-2 focus:ring-amber-500
|
|
248
|
+
/* Active nav indicator */ border-l-2 border-amber-500 bg-amber-500/10
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Summary
|
|
254
|
+
|
|
255
|
+
The **Botanical Observatory** theme transforms the admin panel from a utilitarian tool into a place that feels curated and considered. By combining:
|
|
256
|
+
|
|
257
|
+
- **Warm stone darks** instead of cold slate/zinc grays
|
|
258
|
+
- **Amber/brass accents** instead of corporate blue
|
|
259
|
+
- **Fraunces serif headings** for scholarly distinction
|
|
260
|
+
- **Instrument Sans body** for clean readability
|
|
261
|
+
- **Generous spacing** and warm shadows
|
|
262
|
+
|
|
263
|
+
...the result is an admin interface that feels like it belongs in a naturalist's mahogany-paneled study, yet remains completely functional for daily CMS operations. It's the theme equivalent of choosing a leather-bound notebook over a spiral-bound one -- both work, but one makes the work feel more meaningful.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<claude-mem-context>
|
|
2
|
+
# Recent Activity
|
|
3
|
+
|
|
4
|
+
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
|
5
|
+
|
|
6
|
+
### Feb 12, 2026
|
|
7
|
+
|
|
8
|
+
| ID | Time | T | Title | Read |
|
|
9
|
+
|----|------|---|-------|------|
|
|
10
|
+
| #24263 | 3:10 PM | 🟣 | Users Create Form with Cached Select Options | ~329 |
|
|
11
|
+
|
|
12
|
+
### Feb 18, 2026
|
|
13
|
+
|
|
14
|
+
| ID | Time | T | Title | Read |
|
|
15
|
+
|----|------|---|-------|------|
|
|
16
|
+
| #27270 | 5:27 PM | 🔵 | Users Grid Page - Bulk Actions Reference Implementation | ~244 |
|
|
17
|
+
</claude-mem-context>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<claude-mem-context>
|
|
2
|
+
# Recent Activity
|
|
3
|
+
|
|
4
|
+
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
|
5
|
+
|
|
6
|
+
### Feb 12, 2026
|
|
7
|
+
|
|
8
|
+
| ID | Time | T | Title | Read |
|
|
9
|
+
|----|------|---|-------|------|
|
|
10
|
+
| #24265 | 3:10 PM | 🟣 | Users Edit Form with Cached Select Options and Loading States | ~343 |
|
|
11
|
+
</claude-mem-context>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<!-- app/pages/motor-admin/users/[id]/edit.vue -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
import { userFormMeta } from '../../../../types/generated/form-meta'
|
|
4
|
+
import { userEditFormConfig, userSelectOptionConfigs, userEditExtraFields } from '@motor-cms/ui-core/app/types/config/user'
|
|
5
|
+
|
|
6
|
+
definePageMeta({ layout: 'default', permission: 'users.write' })
|
|
7
|
+
|
|
8
|
+
const route = useRoute()
|
|
9
|
+
const { t } = useI18n()
|
|
10
|
+
|
|
11
|
+
const { fields: rawFields, schema, groups, state, loading, fetching, fetchError, formRef, selectOptions, selectOptionsLoading, onSubmit, onSaveAndContinue, onSaveAndNew, deleteRecord, deleting } = await useEntityForm({
|
|
12
|
+
apiEndpoint: '/api/v2/users',
|
|
13
|
+
routePrefix: '/motor-admin/users',
|
|
14
|
+
translationPrefix: 'motor-admin.users',
|
|
15
|
+
formMeta: userFormMeta,
|
|
16
|
+
formConfig: userEditFormConfig,
|
|
17
|
+
mode: 'edit',
|
|
18
|
+
id: route.params.id as string,
|
|
19
|
+
selectOptionConfigs: userSelectOptionConfigs,
|
|
20
|
+
extraFields: userEditExtraFields(t),
|
|
21
|
+
extraState: { change_password: false, password: '', password_confirmation: '' },
|
|
22
|
+
beforeSubmit: (data) => {
|
|
23
|
+
// Remove client-only fields from payload
|
|
24
|
+
delete data.change_password
|
|
25
|
+
delete data.password_confirmation
|
|
26
|
+
|
|
27
|
+
// Only send password if toggle is on and value is set
|
|
28
|
+
if (!state.change_password || !data.password) {
|
|
29
|
+
delete data.password
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Add password confirmation validation to schema
|
|
35
|
+
const refinedSchema = schema.superRefine((data, ctx) => {
|
|
36
|
+
if (data.change_password && data.password && data.password !== data.password_confirmation) {
|
|
37
|
+
ctx.addIssue({
|
|
38
|
+
code: 'custom',
|
|
39
|
+
path: ['password_confirmation'],
|
|
40
|
+
message: t('motor-admin.users.password_mismatch')
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Make fields reactive for toggle visibility
|
|
46
|
+
const fields = reactive(rawFields)
|
|
47
|
+
|
|
48
|
+
const passwordField = fields.find(f => f.key === 'password')
|
|
49
|
+
const confirmField = fields.find(f => f.key === 'password_confirmation')
|
|
50
|
+
|
|
51
|
+
watchEffect(() => {
|
|
52
|
+
if (passwordField) passwordField.hidden = !state.change_password
|
|
53
|
+
if (confirmField) confirmField.hidden = !state.change_password
|
|
54
|
+
})
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<template>
|
|
58
|
+
<FormPage
|
|
59
|
+
:title="t('motor-admin.users.edit_title')"
|
|
60
|
+
back-route="/motor-admin/users"
|
|
61
|
+
:loading="fetching"
|
|
62
|
+
:error="fetchError"
|
|
63
|
+
>
|
|
64
|
+
<FormBase
|
|
65
|
+
ref="formRef"
|
|
66
|
+
v-model:state="state"
|
|
67
|
+
:fields="fields"
|
|
68
|
+
:schema="refinedSchema"
|
|
69
|
+
:groups="groups"
|
|
70
|
+
:select-options="selectOptions"
|
|
71
|
+
:select-options-loading="selectOptionsLoading"
|
|
72
|
+
:loading="loading"
|
|
73
|
+
:delete-record="deleteRecord"
|
|
74
|
+
:deleting="deleting"
|
|
75
|
+
cancel-route="/motor-admin/users"
|
|
76
|
+
show-save-and-continue
|
|
77
|
+
show-save-and-new
|
|
78
|
+
@submit="onSubmit"
|
|
79
|
+
@save-and-continue="onSaveAndContinue"
|
|
80
|
+
@save-and-new="onSaveAndNew"
|
|
81
|
+
/>
|
|
82
|
+
</FormPage>
|
|
83
|
+
</template>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<!-- app/pages/motor-admin/users/create.vue -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
import { userFormMeta } from '../../../types/generated/form-meta'
|
|
4
|
+
import { userFormConfig, userSelectOptionConfigs } from '@motor-cms/ui-core/app/types/config/user'
|
|
5
|
+
|
|
6
|
+
definePageMeta({ layout: 'default', permission: 'users.write' })
|
|
7
|
+
|
|
8
|
+
const { t } = useI18n()
|
|
9
|
+
const { fields, schema, groups, state, loading, selectOptions, selectOptionsLoading, formRef, onSubmit, onSaveAndNew } = await useEntityForm({
|
|
10
|
+
apiEndpoint: '/api/v2/users',
|
|
11
|
+
routePrefix: '/motor-admin/users',
|
|
12
|
+
translationPrefix: 'motor-admin.users',
|
|
13
|
+
formMeta: userFormMeta,
|
|
14
|
+
formConfig: userFormConfig,
|
|
15
|
+
mode: 'create',
|
|
16
|
+
selectOptionConfigs: userSelectOptionConfigs
|
|
17
|
+
})
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<FormPage
|
|
22
|
+
:title="t('motor-admin.users.create_title')"
|
|
23
|
+
back-route="/motor-admin/users"
|
|
24
|
+
>
|
|
25
|
+
<FormBase
|
|
26
|
+
ref="formRef"
|
|
27
|
+
v-model:state="state"
|
|
28
|
+
:fields="fields"
|
|
29
|
+
:schema="schema"
|
|
30
|
+
:groups="groups"
|
|
31
|
+
:select-options="selectOptions"
|
|
32
|
+
:select-options-loading="selectOptionsLoading"
|
|
33
|
+
:loading="loading"
|
|
34
|
+
cancel-route="/motor-admin/users"
|
|
35
|
+
show-save-and-new
|
|
36
|
+
@submit="onSubmit"
|
|
37
|
+
@save-and-new="onSaveAndNew"
|
|
38
|
+
/>
|
|
39
|
+
</FormPage>
|
|
40
|
+
</template>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<!-- app/pages/motor-admin/users/index.vue -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
import type { components } from '@motor-cms/ui-core/app/types/generated/api'
|
|
4
|
+
import type { BulkActionDef } from '@motor-cms/ui-core/app/types/grid'
|
|
5
|
+
import { userMeta } from '../../../types/generated/grid-meta'
|
|
6
|
+
|
|
7
|
+
definePageMeta({ permission: 'users.read' })
|
|
8
|
+
|
|
9
|
+
type User = components['schemas']['UserResource']
|
|
10
|
+
|
|
11
|
+
const client = useSanctumClient()
|
|
12
|
+
const { t } = useI18n()
|
|
13
|
+
|
|
14
|
+
const columns = columnsFromMeta<User>(userMeta, t, {
|
|
15
|
+
pick: ['avatar', 'name', 'email', 'roles'],
|
|
16
|
+
overrides: {
|
|
17
|
+
avatar: { label: '', width: '60px', hideable: false },
|
|
18
|
+
email: { sortable: true }
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
columns.push(createdAtColumn(t))
|
|
23
|
+
|
|
24
|
+
const filters = [useClientFilter()]
|
|
25
|
+
|
|
26
|
+
const bulkActions: BulkActionDef[] = [
|
|
27
|
+
{
|
|
28
|
+
key: 'delete',
|
|
29
|
+
label: t('motor-core.grid.delete_selected'),
|
|
30
|
+
icon: 'i-lucide-trash-2',
|
|
31
|
+
color: 'error',
|
|
32
|
+
permission: 'users.delete',
|
|
33
|
+
confirm: count => t('motor-core.grid.confirm_delete', { count }),
|
|
34
|
+
handler: async (ids) => {
|
|
35
|
+
await client('/api/v2/users/bulk-delete', {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
body: { ids }
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
const { fetch: fetchUsers } = useGridData<User>('/api/v2/users')
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<GridPage
|
|
48
|
+
:title="t('motor-admin.users.title')"
|
|
49
|
+
:subtitle="t('motor-admin.users.subtitle')"
|
|
50
|
+
add-route="/motor-admin/users/create"
|
|
51
|
+
:add-label="t('motor-admin.users.add')"
|
|
52
|
+
write-permission="users.write"
|
|
53
|
+
>
|
|
54
|
+
<GridBase
|
|
55
|
+
id="users-grid"
|
|
56
|
+
:fetch="fetchUsers"
|
|
57
|
+
:columns="columns"
|
|
58
|
+
:filters="filters"
|
|
59
|
+
:bulk-actions="bulkActions"
|
|
60
|
+
base-path="/motor-admin/users"
|
|
61
|
+
:row-click-to="(row: any) => `/motor-admin/users/${row.id}/edit`"
|
|
62
|
+
write-permission="users.write"
|
|
63
|
+
delete-permission="users.delete"
|
|
64
|
+
/>
|
|
65
|
+
</GridPage>
|
|
66
|
+
</template>
|