fse-theme-default 0.3.0 → 0.4.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fse-theme-default",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Default theme for full_stack_engine apps — 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",
@@ -2,16 +2,21 @@
2
2
  /**
3
3
  * Extension point: the logo shown in the sidebar, the mobile header and the
4
4
  * auth pages. A child theme either replaces src/assets/logo.svg or
5
- * overrides this file (e.g. separate light and dark images).
5
+ * overrides this file (e.g. separate light and dark images, other sizes per
6
+ * `variant`).
6
7
  */
7
8
  import { Image } from "astro:assets";
8
9
  import logo from "../assets/logo.svg";
9
10
 
10
11
  interface Props {
12
+ /** Where it is drawn; picks the default size. */
13
+ variant?: "sidebar" | "mobile" | "auth";
14
+ /** Replaces the variant's size classes entirely. */
11
15
  class?: string;
12
16
  }
13
17
 
14
- const { class: className = "h-9 w-auto" } = Astro.props;
18
+ const sizes = { sidebar: "h-9 w-auto", mobile: "h-8 w-auto", auth: "h-12 w-auto" };
19
+ const { variant = "sidebar", class: className } = Astro.props;
15
20
  ---
16
21
 
17
- <Image src={logo} alt="Logo" class={className} />
22
+ <Image src={logo} alt="Logo" class={className ?? sizes[variant]} />
@@ -0,0 +1,48 @@
1
+ ---
2
+ /**
3
+ * The named 18px icons sidebar entries use. A model picks one with
4
+ * `#[model(nav(icon = "heart"))]` (or `public_nav(...)`); the framework sends
5
+ * `table` when it names none. An unknown name draws nothing — add it here
6
+ * (or in a child theme's override of this file).
7
+ *
8
+ * home, grid, calendar, heart, run, user, users, settings, table, list,
9
+ * box, cart, file, chart, tag, mail, map-pin, star
10
+ */
11
+ import type { SsrValue } from "fse-ssr/ssr";
12
+
13
+ interface Props {
14
+ name: string | SsrValue<string>;
15
+ }
16
+
17
+ const { name } = Astro.props;
18
+ const a = {
19
+ xmlns: "http://www.w3.org/2000/svg",
20
+ width: "18",
21
+ height: "18",
22
+ viewBox: "0 0 24 24",
23
+ fill: "none",
24
+ stroke: "currentColor",
25
+ "stroke-width": "1.5",
26
+ "stroke-linecap": "round",
27
+ "stroke-linejoin": "round",
28
+ } as const;
29
+ ---
30
+
31
+ {name === "home" && (<svg {...a}><path d="M3 10.5 12 3l9 7.5"></path><path d="M5 9.5V21h14V9.5"></path><path d="M10 21v-6h4v6"></path></svg>)}
32
+ {name === "grid" && (<svg {...a}><rect width="7.5" height="7.5" x="2.75" y="2.75" rx="1.9"></rect><rect width="7.5" height="7.5" x="13.75" y="2.75" rx="1.9"></rect><rect width="7.5" height="7.5" x="13.75" y="13.75" rx="1.9"></rect><rect width="7.5" height="7.5" x="2.75" y="13.75" rx="1.9"></rect></svg>)}
33
+ {name === "calendar" && (<svg {...a}><rect width="18" height="18" x="3" y="4" rx="2" ry="2"></rect><path d="M16 2v4M8 2v4m-5 4h18"></path></svg>)}
34
+ {name === "heart" && (<svg {...a}><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>)}
35
+ {name === "run" && (<svg {...a}><path d="M13 4a1 1 0 1 0 2 0 1 1 0 0 0-2 0"></path><path d="m7 21 3-6 2 2 3-4 3 8"></path><path d="m5 14 2-5 5 1 2-4"></path></svg>)}
36
+ {name === "user" && (<svg {...a}><circle cx="12" cy="7" r="4"></circle><path d="M5 21v-2a7 7 0 0 1 14 0v2"></path></svg>)}
37
+ {name === "users" && (<svg {...a}><circle cx="9" cy="7" r="4"></circle><path d="M3 21v-2a6 6 0 0 1 12 0v2"></path><path d="M16 3.13a4 4 0 0 1 0 7.75M21 21v-2a6 6 0 0 0-4-5.66"></path></svg>)}
38
+ {name === "settings" && (<svg {...a}><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9c.26.6.85 1 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>)}
39
+ {name === "table" && (<svg {...a}><rect width="18" height="18" x="3" y="3" rx="2"></rect><path d="M3 9h18M9 21V9"></path></svg>)}
40
+ {name === "list" && (<svg {...a}><path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"></path></svg>)}
41
+ {name === "box" && (<svg {...a}><path d="M21 8 12 3 3 8v8l9 5 9-5z"></path><path d="m3 8 9 5 9-5M12 13v8"></path></svg>)}
42
+ {name === "cart" && (<svg {...a}><circle cx="9" cy="20" r="1"></circle><circle cx="18" cy="20" r="1"></circle><path d="M2 3h3l2.7 12.4a2 2 0 0 0 2 1.6h7.6a2 2 0 0 0 2-1.6L21 7H6"></path></svg>)}
43
+ {name === "file" && (<svg {...a}><path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><path d="M14 3v6h6M8 13h8M8 17h5"></path></svg>)}
44
+ {name === "chart" && (<svg {...a}><path d="M3 3v18h18"></path><path d="M7 15v2M12 10v7M17 6v11"></path></svg>)}
45
+ {name === "tag" && (<svg {...a}><path d="M20.6 13.4 13.4 20.6a2 2 0 0 1-2.8 0L3 13V3h10l7.6 7.6a2 2 0 0 1 0 2.8z"></path><circle cx="7.5" cy="7.5" r="1.5"></circle></svg>)}
46
+ {name === "mail" && (<svg {...a}><rect width="18" height="14" x="3" y="5" rx="2"></rect><path d="m3 7 9 6 9-6"></path></svg>)}
47
+ {name === "map-pin" && (<svg {...a}><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>)}
48
+ {name === "star" && (<svg {...a}><path d="m12 2 3.1 6.3 6.9 1-5 4.9 1.2 6.8L12 17.8 5.8 21l1.2-6.8-5-4.9 6.9-1z"></path></svg>)}
@@ -0,0 +1,27 @@
1
+ ---
2
+ /**
3
+ * Extension point: the app's own sections of the settings page, rendered
4
+ * between "account security" and "delete account". The default shows the
5
+ * account's name. A child theme overrides this file to put its own forms
6
+ * here (a profile, preferences, ...) — the data comes from the app through
7
+ * `FrameworkApp::page_context("settings", ...)`, and its POST handlers
8
+ * re-render the page with `auth_module::settings_context`.
9
+ */
10
+ import { ssr } from "fse-ssr/ssr";
11
+ import type { SettingsPage } from "../types/pages";
12
+
13
+ const { t, first_name, last_name } = ssr<SettingsPage>();
14
+ ---
15
+
16
+ {(first_name || last_name) && (
17
+ <div class="py-6">
18
+ <h3 class="text-base font-semibold text-secondary-900">
19
+ {t.settings.name}
20
+ </h3>
21
+ <div class="mt-3 px-4 py-3 rounded-lg bg-secondary-50 border border-secondary-100">
22
+ <span class="text-sm font-medium text-secondary-900">
23
+ {first_name} {last_name}
24
+ </span>
25
+ </div>
26
+ </div>
27
+ )}
@@ -1,11 +1,14 @@
1
1
  ---
2
2
  /**
3
3
  * The app shell's navigation. Everything is data-driven: the framework
4
- * injects `nav` (the admin page of every model the user may read) and
5
- * `user`; app-specific links come from the SidebarLinks extension point.
4
+ * injects `nav` (the models' sidebar entries — label, icon and order come
5
+ * from `#[model(nav(...), public_nav(...))]` and the app's translations)
6
+ * and `user`; links that aren't a model come from the SidebarLinks
7
+ * extension point.
6
8
  */
7
9
  import { ssr } from "fse-ssr/ssr";
8
10
  import Logo from "./Logo.astro";
11
+ import NavIcon from "./NavIcon.astro";
9
12
  import NavLink from "./NavLink.astro";
10
13
  import SidebarLinks from "./SidebarLinks.astro";
11
14
  import type { ShellContext } from "../types";
@@ -33,7 +36,7 @@ const { t, nav, user, lang_prefix } = ssr<ShellContext>();
33
36
  class="sidebar-full-logo flex items-center gap-2 focus:outline-hidden focus-visible:ring-2 focus-visible:ring-primary-500 rounded-md"
34
37
  href={`${lang_prefix}/`}
35
38
  >
36
- <Logo class="h-9 w-auto" />
39
+ <Logo variant="sidebar" />
37
40
  </a>
38
41
 
39
42
  <button
@@ -70,10 +73,8 @@ const { t, nav, user, lang_prefix } = ssr<ShellContext>();
70
73
  <SidebarLinks />
71
74
 
72
75
  {nav.map((item) => (
73
- <NavLink href={`${lang_prefix}${item.href}`} label={t.models[item.table].title ?? item.table}>
74
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
75
- <rect width="18" height="18" x="3" y="3" rx="2"></rect><path d="M3 9h18M9 21V9"></path>
76
- </svg>
76
+ <NavLink href={`${lang_prefix}${item.href}`} label={item.label}>
77
+ <NavIcon name={item.icon} />
77
78
  </NavLink>
78
79
  ))}
79
80
  </nav>
@@ -73,7 +73,7 @@ const { lang } = ssr();
73
73
  <line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line>
74
74
  </svg>
75
75
  </button>
76
- <a href="/"><Logo class="h-8 w-auto" /></a>
76
+ <a href="/"><Logo variant="mobile" /></a>
77
77
  </div>
78
78
  )}
79
79
  <slot />
@@ -14,7 +14,7 @@ const messages = t.messages as Record<string, string>;
14
14
  <div class="w-full max-w-sm">
15
15
  <div class="flex justify-center mb-8">
16
16
  <a href="/">
17
- <Logo class="h-12 w-auto" />
17
+ <Logo variant="auth" />
18
18
  </a>
19
19
  </div>
20
20
 
@@ -30,7 +30,7 @@ const { t, nav, user, lang_prefix } = ssr<ShellContext>();
30
30
  {nav.map((item) => (
31
31
  <a href={`${lang_prefix}${item.href}`}>
32
32
  <Card accent="primary" hover>
33
- <h2 class="text-lg font-bold text-secondary-900">{t.models[item.table].title ?? item.table}</h2>
33
+ <h2 class="text-lg font-bold text-secondary-900">{item.label}</h2>
34
34
  <p class="text-sm text-secondary-500">{t.dashboard.manage ?? "Manage entries"}</p>
35
35
  </Card>
36
36
  </a>
@@ -16,7 +16,7 @@ const messages = t.messages as Record<string, string>;
16
16
  <!-- Logo -->
17
17
  <div class="flex justify-center mb-8">
18
18
  <a href="/">
19
- <Logo class="h-12 w-auto" />
19
+ <Logo variant="auth" />
20
20
  </a>
21
21
  </div>
22
22
 
@@ -14,7 +14,7 @@ const errors = t.errors as Record<string, string>;
14
14
  <!-- Logo -->
15
15
  <div class="flex justify-center mb-8">
16
16
  <a href="/">
17
- <Logo class="h-12 w-auto" />
17
+ <Logo variant="auth" />
18
18
  </a>
19
19
  </div>
20
20
 
@@ -5,13 +5,12 @@ import Card from "../components/Card.astro";
5
5
  import Header from "../components/Header.astro";
6
6
  import Input from "../components/Input.astro";
7
7
  import Modal from "../components/Modal.astro";
8
+ import SettingsSections from "../components/SettingsSections.astro";
8
9
  import type { SettingsPage } from "../types/pages";
9
10
 
10
11
  const {
11
12
  t,
12
13
  current_email,
13
- first_name,
14
- last_name,
15
14
  success,
16
15
  error,
17
16
  email_success,
@@ -29,20 +28,6 @@ const errors = t.errors as Record<string, string>;
29
28
  <Card>
30
29
  <div class="flex flex-col divide-y divide-secondary-100">
31
30
 
32
- <!-- Name -->
33
- {(first_name || last_name) && (
34
- <div class="pb-6">
35
- <h3 class="text-base font-semibold text-secondary-900">
36
- {t.settings.name}
37
- </h3>
38
- <div class="mt-3 px-4 py-3 rounded-lg bg-secondary-50 border border-secondary-100">
39
- <span class="text-sm font-medium text-secondary-900">
40
- {first_name} {last_name}
41
- </span>
42
- </div>
43
- </div>
44
- )}
45
-
46
31
  <!-- Email Setting -->
47
32
  <div class="py-6 first:pt-0">
48
33
  <div class="flex items-start justify-between mb-3 flex-wrap gap-2">
@@ -89,6 +74,9 @@ const errors = t.errors as Record<string, string>;
89
74
  )}
90
75
  </div>
91
76
 
77
+ <!-- The app's sections (default: the account's name) -->
78
+ <SettingsSections />
79
+
92
80
  <!-- Danger Zone -->
93
81
  <div class="pt-6">
94
82
  <div class="flex items-start justify-between flex-wrap gap-3">
@@ -56,7 +56,7 @@ const roleLabels = t.roles as Record<string, string>;
56
56
  <!-- Actions -->
57
57
  <div class="flex sm:justify-end pt-5 border-t border-secondary-100">
58
58
  <button type="submit" class="btn-primary w-full sm:w-auto">
59
- <span>{t.common.save}</span>
59
+ <span>{t.users.save}</span>
60
60
  </button>
61
61
  </div>
62
62
  </form>
package/src/types.ts CHANGED
@@ -136,6 +136,12 @@ export interface ModelDetailPage {
136
136
  export interface NavItem {
137
137
  table: string;
138
138
  href: string;
139
+ /** Already translated (`t.models.<table>.nav` / `.public_nav` / `.title`). */
140
+ label: string;
141
+ /** A `NavIcon` name. */
142
+ icon: string;
143
+ /** The public list's entry rather than the admin page. */
144
+ public: boolean;
139
145
  }
140
146
 
141
147
  /** The signed-in user as injected by the framework (absent when signed out). */