fse-theme-default 0.4.0 → 0.5.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 +19 -2
- package/package.json +3 -3
- package/src/components/Head.astro +5 -1
- package/src/components/SettingsSections.astro +9 -4
- package/src/components/Sidebar.astro +19 -9
- package/src/components/SidebarLinks.astro +10 -3
- package/src/pages/fse/form.astro +33 -1
- package/src/pages/index.astro +7 -2
- package/src/types.ts +30 -0
- package/theme.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,8 @@ A child theme overrides any file under `src/` by creating the same path in
|
|
|
33
33
|
its own `src/` — the parent's pages then use the child's version:
|
|
34
34
|
|
|
35
35
|
- `src/layouts/Layout.astro` — the whole shell,
|
|
36
|
-
- `src/components/SidebarLinks.astro` — app-specific sidebar entries (
|
|
36
|
+
- `src/components/SidebarLinks.astro` — app-specific sidebar entries (renders
|
|
37
|
+
the `sidebar-links` slot; overriding it replaces every contribution),
|
|
37
38
|
- `src/components/Logo.astro` — the logo in the sidebar, mobile header and
|
|
38
39
|
auth pages (or replace just `src/assets/logo.svg`),
|
|
39
40
|
- `src/components/Head.astro` — extra `<head>` tags on every page (favicon,
|
|
@@ -41,7 +42,23 @@ its own `src/` — the parent's pages then use the child's version:
|
|
|
41
42
|
- `src/styles/global.css` — the Tailwind root (import `src/styles/theme.css`
|
|
42
43
|
and override tokens after it).
|
|
43
44
|
|
|
44
|
-
`@parent/...` imports the parent's original file (e.g. to wrap it)
|
|
45
|
+
`@parent/...` imports the parent's original file (e.g. to wrap it);
|
|
46
|
+
`@theme/...` the most specific theme's version of a file.
|
|
47
|
+
|
|
48
|
+
### Slots
|
|
49
|
+
|
|
50
|
+
Where several child themes and modules add to one place, the theme has a
|
|
51
|
+
slot: every `src/slots/<slot>/<Name>.astro` of the child theme and of the
|
|
52
|
+
app's modules is rendered there (this theme's first, modules next, the
|
|
53
|
+
child last; a same-named file of a more specific layer replaces the other):
|
|
54
|
+
|
|
55
|
+
- `sidebar-links` — sidebar entries between "Home" and the model links,
|
|
56
|
+
- `settings-sections` — sections of the settings page (data through
|
|
57
|
+
`page_context("settings", ...)`),
|
|
58
|
+
- `head` — extra `<head>` tags,
|
|
59
|
+
- `dashboard` — widgets below the home page's model cards,
|
|
60
|
+
- `record-panels` — panels below a generated record form (check
|
|
61
|
+
`meta.table`).
|
|
45
62
|
|
|
46
63
|
## Development
|
|
47
64
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fse-theme-default",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Default theme for full_stack_engine apps
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Default theme for full_stack_engine apps \u2014 a complete Astro project: app shell, design system, auth pages, emails and the generic metadata-driven CRUD pages. Extend it with a child theme.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT OR Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"astro": ">=7.0.0",
|
|
28
|
-
"fse-ssr": ">=0.
|
|
28
|
+
"fse-ssr": ">=0.3.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@astrojs/check": "^0.9.9",
|
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Extension point: extra <head> tags for every page (favicon, fonts, meta
|
|
4
4
|
* tags). A child theme overrides this file; its version replaces this one,
|
|
5
|
-
* so it should link its own favicon too.
|
|
5
|
+
* so it should link its own favicon too. Tags that only add (a module's
|
|
6
|
+
* stylesheet, a meta tag) go into the `head` slot instead:
|
|
7
|
+
* `src/slots/head/*.astro`.
|
|
6
8
|
*/
|
|
9
|
+
import Slot from "fse-ssr/Slot.astro";
|
|
7
10
|
---
|
|
8
11
|
|
|
9
12
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
13
|
+
<Slot name="head" />
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Extension point: the app's own sections of the settings page, rendered
|
|
4
4
|
* between "account security" and "delete account". The default shows the
|
|
5
|
-
* account's name
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* account's name, followed by the `settings-sections` slot: every
|
|
6
|
+
* `src/slots/settings-sections/*.astro` of a child theme or a module (a
|
|
7
|
+
* profile, preferences, ...). Their data comes through
|
|
8
|
+
* `page_context("settings", ...)` (app or module), and POST handlers
|
|
9
|
+
* re-render the page with `auth_module::settings_context`. Overriding this
|
|
10
|
+
* file replaces the default and every contribution.
|
|
9
11
|
*/
|
|
12
|
+
import Slot from "fse-ssr/Slot.astro";
|
|
10
13
|
import { ssr } from "fse-ssr/ssr";
|
|
11
14
|
import type { SettingsPage } from "../types/pages";
|
|
12
15
|
|
|
@@ -25,3 +28,5 @@ const { t, first_name, last_name } = ssr<SettingsPage>();
|
|
|
25
28
|
</div>
|
|
26
29
|
</div>
|
|
27
30
|
)}
|
|
31
|
+
|
|
32
|
+
<Slot name="settings-sections" />
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
3
|
* The app shell's navigation. Everything is data-driven: the framework
|
|
4
|
-
* injects `
|
|
5
|
-
* from `#[model(nav(...), public_nav(...))]` and the app's
|
|
6
|
-
*
|
|
7
|
-
* extension
|
|
4
|
+
* injects `nav_groups` (the models' sidebar entries — label, icon and order
|
|
5
|
+
* come from `#[model(nav(...), public_nav(...))]` and the app's
|
|
6
|
+
* translations — grouped by module, each module's under its title) and
|
|
7
|
+
* `user`; links that aren't a model come from the SidebarLinks extension
|
|
8
|
+
* point.
|
|
8
9
|
*/
|
|
9
10
|
import { ssr } from "fse-ssr/ssr";
|
|
10
11
|
import Logo from "./Logo.astro";
|
|
@@ -13,7 +14,7 @@ import NavLink from "./NavLink.astro";
|
|
|
13
14
|
import SidebarLinks from "./SidebarLinks.astro";
|
|
14
15
|
import type { ShellContext } from "../types";
|
|
15
16
|
|
|
16
|
-
const { t,
|
|
17
|
+
const { t, nav_groups, user, lang_prefix } = ssr<ShellContext>();
|
|
17
18
|
---
|
|
18
19
|
|
|
19
20
|
<div
|
|
@@ -72,10 +73,19 @@ const { t, nav, user, lang_prefix } = ssr<ShellContext>();
|
|
|
72
73
|
|
|
73
74
|
<SidebarLinks />
|
|
74
75
|
|
|
75
|
-
{
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
{nav_groups.map((group) => (
|
|
77
|
+
<div class="space-y-0.5">
|
|
78
|
+
{group.label && (
|
|
79
|
+
<p class="sidebar-label px-3 pt-4 pb-1 text-xs font-semibold uppercase tracking-wide text-secondary-400 truncate">
|
|
80
|
+
{group.label}
|
|
81
|
+
</p>
|
|
82
|
+
)}
|
|
83
|
+
{group.entries.map((item) => (
|
|
84
|
+
<NavLink href={`${lang_prefix}${item.href}`} label={item.label}>
|
|
85
|
+
<NavIcon name={item.icon} />
|
|
86
|
+
</NavLink>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
79
89
|
))}
|
|
80
90
|
</nav>
|
|
81
91
|
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
3
|
* Extension point: app-specific sidebar entries, rendered between "Home"
|
|
4
|
-
* and the generated model links.
|
|
5
|
-
*
|
|
4
|
+
* and the generated model links. Filled through the `sidebar-links` slot:
|
|
5
|
+
* every `src/slots/sidebar-links/*.astro` of a child theme or a module is
|
|
6
|
+
* rendered here, so several can add links side by side:
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
+
* // src/slots/sidebar-links/Products.astro
|
|
9
|
+
* import NavLink from "@theme/components/NavLink.astro";
|
|
8
10
|
* <NavLink href="/products" label={t.nav.products}>…icon…</NavLink>
|
|
11
|
+
*
|
|
12
|
+
* Overriding this file instead replaces every contribution.
|
|
9
13
|
*/
|
|
14
|
+
import Slot from "fse-ssr/Slot.astro";
|
|
10
15
|
---
|
|
16
|
+
|
|
17
|
+
<Slot name="sidebar-links" />
|
package/src/pages/fse/form.astro
CHANGED
|
@@ -7,13 +7,19 @@
|
|
|
7
7
|
* row) the same form renders disabled and without a save button. An
|
|
8
8
|
* existing row also shows its allowed row actions and links to its nested
|
|
9
9
|
* models and link tables; a nested model links back to its parent row.
|
|
10
|
+
* Below the form: `related` — the rows of other models (and modules)
|
|
11
|
+
* that point at this one through a foreign key, one panel each — then the
|
|
12
|
+
* `record-panels` slot: extra panels from a child theme or modules
|
|
13
|
+
* (`src/slots/record-panels/*.astro`, which check `meta.table` to pick the
|
|
14
|
+
* models they extend).
|
|
10
15
|
*/
|
|
16
|
+
import Slot from "fse-ssr/Slot.astro";
|
|
11
17
|
import { ssr } from "fse-ssr/ssr";
|
|
12
18
|
import type { SsrValue } from "fse-ssr/ssr";
|
|
13
19
|
import Layout from "../../layouts/Layout.astro";
|
|
14
20
|
import type { ModelFormPage } from "../../types";
|
|
15
21
|
|
|
16
|
-
const { t, lang_prefix, meta, row, errors, is_new, can_edit, can_delete, actions, parent } =
|
|
22
|
+
const { t, lang_prefix, meta, row, errors, is_new, can_edit, can_delete, actions, parent, related } =
|
|
17
23
|
ssr<ModelFormPage>();
|
|
18
24
|
const title = t.models[meta.table].title ?? meta.table;
|
|
19
25
|
const formErrors = t.form_errors as Record<string, SsrValue<string>>;
|
|
@@ -155,6 +161,32 @@ const inputClass =
|
|
|
155
161
|
))}
|
|
156
162
|
</div>
|
|
157
163
|
)}
|
|
164
|
+
|
|
165
|
+
{related.map((panel) => (
|
|
166
|
+
<section class="mt-8">
|
|
167
|
+
<h2 class="mb-2 text-base font-semibold text-secondary-900">
|
|
168
|
+
{t.models[panel.table].title ?? panel.table}
|
|
169
|
+
{panel.label_column && (
|
|
170
|
+
<span class="font-normal text-secondary-500"> · {t.models[panel.table].fields[panel.label_column] ?? panel.label_column}</span>
|
|
171
|
+
)}
|
|
172
|
+
<span class="ml-1 text-sm font-normal text-secondary-500">({panel.total})</span>
|
|
173
|
+
</h2>
|
|
174
|
+
<ul class="divide-y divide-secondary-100 rounded-lg border border-secondary-100 bg-white">
|
|
175
|
+
{panel.rows.map((item) => (
|
|
176
|
+
<li>
|
|
177
|
+
<a class="block px-4 py-2 text-sm text-secondary-800 hover:bg-secondary-50" href={`${lang_prefix}${panel.base_path}/${item.id}`}>
|
|
178
|
+
{item.title}
|
|
179
|
+
</a>
|
|
180
|
+
</li>
|
|
181
|
+
))}
|
|
182
|
+
</ul>
|
|
183
|
+
{panel.more && (
|
|
184
|
+
<p class="mt-1 text-xs text-secondary-500">{t.model_ui.more_related ?? "Showing the first 10."}</p>
|
|
185
|
+
)}
|
|
186
|
+
</section>
|
|
187
|
+
))}
|
|
188
|
+
|
|
189
|
+
<Slot name="record-panels" />
|
|
158
190
|
</main>
|
|
159
191
|
|
|
160
192
|
<script>
|
package/src/pages/index.astro
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
|
-
* Default home page: a dashboard linking every model the user may manage
|
|
4
|
-
*
|
|
3
|
+
* Default home page: a dashboard linking every model the user may manage,
|
|
4
|
+
* then the `dashboard` slot (widgets from a child theme or modules:
|
|
5
|
+
* `src/slots/dashboard/*.astro`). Apps usually replace it (a child theme's
|
|
6
|
+
* own src/pages/index.astro).
|
|
5
7
|
*/
|
|
8
|
+
import Slot from "fse-ssr/Slot.astro";
|
|
6
9
|
import { ssr } from "fse-ssr/ssr";
|
|
7
10
|
import Card from "../components/Card.astro";
|
|
8
11
|
import Header from "../components/Header.astro";
|
|
@@ -37,5 +40,7 @@ const { t, nav, user, lang_prefix } = ssr<ShellContext>();
|
|
|
37
40
|
))}
|
|
38
41
|
</div>
|
|
39
42
|
)}
|
|
43
|
+
|
|
44
|
+
<Slot name="dashboard" />
|
|
40
45
|
</main>
|
|
41
46
|
</Layout>
|
package/src/types.ts
CHANGED
|
@@ -116,6 +116,22 @@ export interface ModelFormPage {
|
|
|
116
116
|
actions: string[];
|
|
117
117
|
/** The parent row, for a nested model (else null). */
|
|
118
118
|
parent: ParentContext | null;
|
|
119
|
+
/** Rows of other models (and modules) pointing at this one. */
|
|
120
|
+
related: RelatedPanel[];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* One model's rows that point at a record through a foreign key — the
|
|
125
|
+
* first ten, with `more` when there are others.
|
|
126
|
+
*/
|
|
127
|
+
export interface RelatedPanel {
|
|
128
|
+
table: string;
|
|
129
|
+
base_path: string;
|
|
130
|
+
/** Set when the model points here through several columns. */
|
|
131
|
+
label_column: string | null;
|
|
132
|
+
total: number;
|
|
133
|
+
more: boolean;
|
|
134
|
+
rows: { id: number; title: string }[];
|
|
119
135
|
}
|
|
120
136
|
|
|
121
137
|
/** `fse/links` — the rows of another model linked to one row. */
|
|
@@ -142,6 +158,19 @@ export interface NavItem {
|
|
|
142
158
|
icon: string;
|
|
143
159
|
/** The public list's entry rather than the admin page. */
|
|
144
160
|
public: boolean;
|
|
161
|
+
/** The cargo package defining the model (the app's or a module's). */
|
|
162
|
+
krate: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* `nav` grouped by origin: the app's own models first (`module` and
|
|
167
|
+
* `label` null), then one group per enabled module, labelled
|
|
168
|
+
* `t.modules.<package>.title`.
|
|
169
|
+
*/
|
|
170
|
+
export interface NavGroup {
|
|
171
|
+
module: string | null;
|
|
172
|
+
label: string | null;
|
|
173
|
+
entries: NavItem[];
|
|
145
174
|
}
|
|
146
175
|
|
|
147
176
|
/** The signed-in user as injected by the framework (absent when signed out). */
|
|
@@ -155,6 +184,7 @@ export interface SessionUser {
|
|
|
155
184
|
/** Context keys every page receives, whatever renders it. */
|
|
156
185
|
export interface ShellContext {
|
|
157
186
|
nav: NavItem[];
|
|
187
|
+
nav_groups: NavGroup[];
|
|
158
188
|
user?: SessionUser;
|
|
159
189
|
lang_prefix: string;
|
|
160
190
|
}
|
package/theme.json
CHANGED