@madojs/mado 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.
Files changed (162) hide show
  1. package/AGENTS.md +291 -0
  2. package/CHANGELOG.md +23 -0
  3. package/LICENSE +21 -0
  4. package/README.md +371 -0
  5. package/ROADMAP.md +52 -0
  6. package/dist/src/component.d.ts +48 -0
  7. package/dist/src/component.js +140 -0
  8. package/dist/src/component.js.map +1 -0
  9. package/dist/src/context.d.ts +40 -0
  10. package/dist/src/context.js +67 -0
  11. package/dist/src/context.js.map +1 -0
  12. package/dist/src/css.d.ts +54 -0
  13. package/dist/src/css.js +137 -0
  14. package/dist/src/css.js.map +1 -0
  15. package/dist/src/devtools.d.ts +22 -0
  16. package/dist/src/devtools.js +63 -0
  17. package/dist/src/devtools.js.map +1 -0
  18. package/dist/src/diagnostics.d.ts +11 -0
  19. package/dist/src/diagnostics.js +28 -0
  20. package/dist/src/diagnostics.js.map +1 -0
  21. package/dist/src/each.d.ts +39 -0
  22. package/dist/src/each.js +35 -0
  23. package/dist/src/each.js.map +1 -0
  24. package/dist/src/forms.d.ts +71 -0
  25. package/dist/src/forms.js +161 -0
  26. package/dist/src/forms.js.map +1 -0
  27. package/dist/src/head.d.ts +19 -0
  28. package/dist/src/head.js +97 -0
  29. package/dist/src/head.js.map +1 -0
  30. package/dist/src/html/bindings.d.ts +78 -0
  31. package/dist/src/html/bindings.js +304 -0
  32. package/dist/src/html/bindings.js.map +1 -0
  33. package/dist/src/html/parser.d.ts +64 -0
  34. package/dist/src/html/parser.js +521 -0
  35. package/dist/src/html/parser.js.map +1 -0
  36. package/dist/src/html/template-types.d.ts +27 -0
  37. package/dist/src/html/template-types.js +8 -0
  38. package/dist/src/html/template-types.js.map +1 -0
  39. package/dist/src/html/template.d.ts +45 -0
  40. package/dist/src/html/template.js +119 -0
  41. package/dist/src/html/template.js.map +1 -0
  42. package/dist/src/html.d.ts +16 -0
  43. package/dist/src/html.js +16 -0
  44. package/dist/src/html.js.map +1 -0
  45. package/dist/src/index.d.ts +35 -0
  46. package/dist/src/index.js +39 -0
  47. package/dist/src/index.js.map +1 -0
  48. package/dist/src/lazy.d.ts +38 -0
  49. package/dist/src/lazy.js +73 -0
  50. package/dist/src/lazy.js.map +1 -0
  51. package/dist/src/lifecycle.d.ts +45 -0
  52. package/dist/src/lifecycle.js +66 -0
  53. package/dist/src/lifecycle.js.map +1 -0
  54. package/dist/src/page.d.ts +161 -0
  55. package/dist/src/page.js +38 -0
  56. package/dist/src/page.js.map +1 -0
  57. package/dist/src/persisted.d.ts +47 -0
  58. package/dist/src/persisted.js +119 -0
  59. package/dist/src/persisted.js.map +1 -0
  60. package/dist/src/resource.d.ts +120 -0
  61. package/dist/src/resource.js +275 -0
  62. package/dist/src/resource.js.map +1 -0
  63. package/dist/src/router/manifest.d.ts +56 -0
  64. package/dist/src/router/manifest.js +302 -0
  65. package/dist/src/router/manifest.js.map +1 -0
  66. package/dist/src/router/match.d.ts +62 -0
  67. package/dist/src/router/match.js +117 -0
  68. package/dist/src/router/match.js.map +1 -0
  69. package/dist/src/router/navigation.d.ts +89 -0
  70. package/dist/src/router/navigation.js +263 -0
  71. package/dist/src/router/navigation.js.map +1 -0
  72. package/dist/src/router.d.ts +13 -0
  73. package/dist/src/router.js +13 -0
  74. package/dist/src/router.js.map +1 -0
  75. package/dist/src/signal.d.ts +67 -0
  76. package/dist/src/signal.js +238 -0
  77. package/dist/src/signal.js.map +1 -0
  78. package/docs/README.md +12 -0
  79. package/docs/en/00-the-mado-way.md +106 -0
  80. package/docs/en/01-routing.md +204 -0
  81. package/docs/en/02-project-layout.md +58 -0
  82. package/docs/en/03-static-bake.md +251 -0
  83. package/docs/en/04-ide-setup.md +162 -0
  84. package/docs/en/05-why-mado.md +193 -0
  85. package/docs/en/06-for-backenders.md +422 -0
  86. package/docs/en/07-llm-pitfalls.md +486 -0
  87. package/docs/en/08-llm-zero-history-test.md +56 -0
  88. package/docs/en/09-shadow-vs-light-dom.md +122 -0
  89. package/docs/en/README.md +16 -0
  90. package/docs/fr/00-the-mado-way.md +108 -0
  91. package/docs/fr/01-routing.md +202 -0
  92. package/docs/fr/02-project-layout.md +58 -0
  93. package/docs/fr/03-static-bake.md +290 -0
  94. package/docs/fr/04-ide-setup.md +162 -0
  95. package/docs/fr/05-why-mado.md +193 -0
  96. package/docs/fr/06-for-backenders.md +432 -0
  97. package/docs/fr/07-llm-pitfalls.md +487 -0
  98. package/docs/fr/08-llm-zero-history-test.md +60 -0
  99. package/docs/fr/09-shadow-vs-light-dom.md +121 -0
  100. package/docs/fr/README.md +16 -0
  101. package/docs/ru/00-the-mado-way.md +93 -0
  102. package/docs/ru/01-routing.md +194 -0
  103. package/docs/ru/02-project-layout.md +57 -0
  104. package/docs/ru/03-static-bake.md +251 -0
  105. package/docs/ru/04-ide-setup.md +144 -0
  106. package/docs/ru/05-why-mado.md +193 -0
  107. package/docs/ru/06-for-backenders.md +422 -0
  108. package/docs/ru/07-llm-pitfalls.md +485 -0
  109. package/docs/ru/08-llm-zero-history-test.md +56 -0
  110. package/docs/ru/09-shadow-vs-light-dom.md +122 -0
  111. package/docs/ru/README.md +14 -0
  112. package/docs/uk/00-the-mado-way.md +54 -0
  113. package/docs/uk/01-routing.md +82 -0
  114. package/docs/uk/02-project-layout.md +46 -0
  115. package/docs/uk/03-static-bake.md +49 -0
  116. package/docs/uk/04-ide-setup.md +26 -0
  117. package/docs/uk/05-why-mado.md +34 -0
  118. package/docs/uk/06-for-backenders.md +50 -0
  119. package/docs/uk/07-llm-pitfalls.md +82 -0
  120. package/docs/uk/08-llm-zero-history-test.md +31 -0
  121. package/docs/uk/09-shadow-vs-light-dom.md +40 -0
  122. package/docs/uk/README.md +16 -0
  123. package/llms.txt +155 -0
  124. package/package.json +81 -0
  125. package/scripts/bake.mjs +406 -0
  126. package/scripts/bundle.mjs +146 -0
  127. package/scripts/cli.mjs +382 -0
  128. package/scripts/new.mjs +80 -0
  129. package/scripts/preview.mjs +176 -0
  130. package/scripts/release-notes.mjs +66 -0
  131. package/scripts/showcase-regression.mjs +392 -0
  132. package/server/serve.mjs +292 -0
  133. package/starters/crud/README.md +21 -0
  134. package/starters/crud/index.html +20 -0
  135. package/starters/crud/package.json +17 -0
  136. package/starters/crud/src/components/app-shell.ts +51 -0
  137. package/starters/crud/src/components/ticket-detail.ts +33 -0
  138. package/starters/crud/src/components/ticket-form.ts +69 -0
  139. package/starters/crud/src/components/ticket-list.ts +66 -0
  140. package/starters/crud/src/lib/api.ts +76 -0
  141. package/starters/crud/src/main.ts +12 -0
  142. package/starters/crud/src/pages/home.ts +18 -0
  143. package/starters/crud/src/pages/not-found.ts +12 -0
  144. package/starters/crud/src/pages/ticket-detail.ts +6 -0
  145. package/starters/crud/src/pages/ticket-new.ts +6 -0
  146. package/starters/crud/src/pages/tickets.ts +6 -0
  147. package/starters/crud/src/routes.ts +9 -0
  148. package/starters/crud/src/styles/global.ts +155 -0
  149. package/starters/crud/tsconfig.json +15 -0
  150. package/starters/minimal/README.md +19 -0
  151. package/starters/minimal/index.html +20 -0
  152. package/starters/minimal/package.json +17 -0
  153. package/starters/minimal/src/components/app-counter.ts +31 -0
  154. package/starters/minimal/src/main.ts +9 -0
  155. package/starters/minimal/src/pages/home.ts +18 -0
  156. package/starters/minimal/src/pages/not-found.ts +14 -0
  157. package/starters/minimal/src/routes.ts +6 -0
  158. package/starters/minimal/src/styles/global.ts +60 -0
  159. package/starters/minimal/tsconfig.json +15 -0
  160. package/templates/page-detail.ts +63 -0
  161. package/templates/page-form.ts +94 -0
  162. package/templates/page-list.ts +79 -0
@@ -0,0 +1,20 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>__APP_NAME__</title>
7
+ <script type="importmap">
8
+ {
9
+ "imports": {
10
+ "@madojs/mado": "./node_modules/@madojs/mado/dist/src/index.js",
11
+ "@madojs/mado/": "./node_modules/@madojs/mado/dist/src/"
12
+ }
13
+ }
14
+ </script>
15
+ </head>
16
+ <body>
17
+ <div id="app"></div>
18
+ <script type="module" src="./dist/main.js"></script>
19
+ </body>
20
+ </html>
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "__PACKAGE_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsc -p tsconfig.json",
8
+ "typecheck": "tsc -p tsconfig.json --noEmit",
9
+ "serve": "mado serve"
10
+ },
11
+ "dependencies": {
12
+ "@madojs/mado": "__MADOJS_VERSION__"
13
+ },
14
+ "devDependencies": {
15
+ "typescript": "^6.0.3"
16
+ }
17
+ }
@@ -0,0 +1,51 @@
1
+ import { component, css, html } from "@madojs/mado";
2
+
3
+ component(
4
+ "app-shell",
5
+ () => () => html`
6
+ <header>
7
+ <a class="brand" href="/" data-link>__APP_NAME__</a>
8
+ <nav>
9
+ <a href="/" data-link>Home</a>
10
+ <a href="/tickets" data-link>Tickets</a>
11
+ <a href="/tickets/new" data-link>New ticket</a>
12
+ </nav>
13
+ </header>
14
+ <slot></slot>
15
+ `,
16
+ {
17
+ shadow: false,
18
+ styles: css`
19
+ app-shell {
20
+ min-height: 100vh;
21
+ display: block;
22
+ }
23
+
24
+ app-shell header {
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: space-between;
28
+ gap: 1rem;
29
+ border-bottom: 1px solid var(--line);
30
+ background: white;
31
+ padding: 0.9rem 1.25rem;
32
+ }
33
+
34
+ app-shell nav {
35
+ display: flex;
36
+ gap: 0.75rem;
37
+ flex-wrap: wrap;
38
+ }
39
+
40
+ app-shell a {
41
+ color: #334155;
42
+ text-decoration: none;
43
+ }
44
+
45
+ app-shell .brand {
46
+ color: #111827;
47
+ font-weight: 800;
48
+ }
49
+ `,
50
+ },
51
+ );
@@ -0,0 +1,33 @@
1
+ import { component, html, resource } from "@madojs/mado";
2
+ import { api } from "../lib/api.js";
3
+
4
+ component("ticket-detail", ({ host }) => {
5
+ const id = () => host.getAttribute("ticket-id") ?? "";
6
+ const ticket = resource(
7
+ () => `tickets/${id()}`,
8
+ () => api.getTicket(id()),
9
+ );
10
+
11
+ return () => html`
12
+ <section class="page narrow">
13
+ ${() => ticket.loading()
14
+ ? html`<p>Loading...</p>`
15
+ : ticket.error()
16
+ ? html`<p class="error">${ticket.error()?.message}</p>`
17
+ : html`
18
+ <a href="/tickets" data-link>Back to tickets</a>
19
+ <h1>${ticket.data()?.title}</h1>
20
+ <dl>
21
+ <dt>Customer</dt>
22
+ <dd>${ticket.data()?.customer}</dd>
23
+ <dt>Status</dt>
24
+ <dd><span class=${`badge ${ticket.data()?.status}`}>${ticket.data()?.status}</span></dd>
25
+ <dt>Priority</dt>
26
+ <dd>${ticket.data()?.priority}</dd>
27
+ <dt>Notes</dt>
28
+ <dd>${ticket.data()?.notes}</dd>
29
+ </dl>
30
+ `}
31
+ </section>
32
+ `;
33
+ }, { shadow: false });
@@ -0,0 +1,69 @@
1
+ import { component, html, mutation, navigate, useForm } from "@madojs/mado";
2
+ import { api, type TicketInput } from "../lib/api.js";
3
+
4
+ component("ticket-form", () => {
5
+ const form = useForm({
6
+ title: { required: true, default: "" },
7
+ customer: { required: true, default: "" },
8
+ status: { required: true, default: "open" },
9
+ priority: { required: true, default: "normal" },
10
+ notes: { default: "" },
11
+ });
12
+
13
+ const createTicket = mutation(
14
+ (input: TicketInput) => api.createTicket(input),
15
+ { invalidates: ["tickets*"] },
16
+ );
17
+
18
+ const submit = form.onSubmit(async (values) => {
19
+ const ticket = await createTicket.run(values as TicketInput);
20
+ navigate(`/tickets/${ticket.id}`);
21
+ });
22
+
23
+ return () => html`
24
+ <section class="page narrow">
25
+ <h1>New ticket</h1>
26
+ <form @submit=${submit}>
27
+ <label>
28
+ Title
29
+ <input name="title" @input=${form.onInput} @blur=${form.onBlur}>
30
+ ${() => form.touched().title && form.errors().title ? html`<small>${form.errors().title}</small>` : null}
31
+ </label>
32
+
33
+ <label>
34
+ Customer
35
+ <input name="customer" @input=${form.onInput} @blur=${form.onBlur}>
36
+ ${() => form.touched().customer && form.errors().customer ? html`<small>${form.errors().customer}</small>` : null}
37
+ </label>
38
+
39
+ <label>
40
+ Status
41
+ <select name="status" @input=${form.onInput} @blur=${form.onBlur}>
42
+ <option value="open">Open</option>
43
+ <option value="pending">Pending</option>
44
+ <option value="closed">Closed</option>
45
+ </select>
46
+ </label>
47
+
48
+ <label>
49
+ Priority
50
+ <select name="priority" @input=${form.onInput} @blur=${form.onBlur}>
51
+ <option value="low">Low</option>
52
+ <option value="normal" selected>Normal</option>
53
+ <option value="high">High</option>
54
+ </select>
55
+ </label>
56
+
57
+ <label>
58
+ Notes
59
+ <textarea name="notes" rows="4" @input=${form.onInput}></textarea>
60
+ </label>
61
+
62
+ <div class="actions">
63
+ <a href="/tickets" data-link>Cancel</a>
64
+ <button ?disabled=${() => !form.isValid() || form.submitting()}>Create ticket</button>
65
+ </div>
66
+ </form>
67
+ </section>
68
+ `;
69
+ }, { shadow: false });
@@ -0,0 +1,66 @@
1
+ import { component, computed, each, html, queryParam, resource } from "@madojs/mado";
2
+ import { api, type Ticket } from "../lib/api.js";
3
+
4
+ component("ticket-list", () => {
5
+ const search = queryParam("search", "");
6
+ const status = queryParam("status", "all");
7
+ const tickets = resource(
8
+ () => `tickets?search=${search()}&status=${status()}`,
9
+ () => api.listTickets({ search: search(), status: status() }),
10
+ );
11
+ const openCount = computed(() => (tickets.data() ?? []).filter((ticket) => ticket.status === "open").length);
12
+
13
+ const row = (ticket: Ticket) => html`
14
+ <tr>
15
+ <td><a href=${`/tickets/${ticket.id}`} data-link>${ticket.title}</a></td>
16
+ <td>${ticket.customer}</td>
17
+ <td><span class=${`badge ${ticket.status}`}>${ticket.status}</span></td>
18
+ <td>${ticket.priority}</td>
19
+ </tr>
20
+ `;
21
+
22
+ return () => html`
23
+ <section class="page">
24
+ <div class="toolbar">
25
+ <div>
26
+ <h1>Tickets</h1>
27
+ <p>${() => openCount()} open tickets</p>
28
+ </div>
29
+ <a class="button" href="/tickets/new" data-link>New ticket</a>
30
+ </div>
31
+
32
+ <div class="filters">
33
+ <input
34
+ type="search"
35
+ placeholder="Search tickets"
36
+ .value=${search}
37
+ @input=${(event: Event) => search.set((event.target as HTMLInputElement).value)}
38
+ >
39
+ <select
40
+ .value=${status}
41
+ @change=${(event: Event) => status.set((event.target as HTMLSelectElement).value)}
42
+ >
43
+ <option value="all">All statuses</option>
44
+ <option value="open">Open</option>
45
+ <option value="pending">Pending</option>
46
+ <option value="closed">Closed</option>
47
+ </select>
48
+ </div>
49
+
50
+ ${() => tickets.loading()
51
+ ? html`<p>Loading...</p>`
52
+ : tickets.error()
53
+ ? html`<p class="error">${tickets.error()?.message}</p>`
54
+ : html`
55
+ <table>
56
+ <thead>
57
+ <tr><th>Title</th><th>Customer</th><th>Status</th><th>Priority</th></tr>
58
+ </thead>
59
+ <tbody>
60
+ ${each(tickets.data() ?? [], (ticket) => ticket.id, row)}
61
+ </tbody>
62
+ </table>
63
+ `}
64
+ </section>
65
+ `;
66
+ }, { shadow: false });
@@ -0,0 +1,76 @@
1
+ export type TicketStatus = "open" | "pending" | "closed";
2
+
3
+ export type Ticket = {
4
+ id: string;
5
+ title: string;
6
+ customer: string;
7
+ status: TicketStatus;
8
+ priority: "low" | "normal" | "high";
9
+ notes: string;
10
+ };
11
+
12
+ export type TicketInput = Omit<Ticket, "id">;
13
+
14
+ const delay = (ms = 180) => new Promise((resolve) => setTimeout(resolve, ms));
15
+
16
+ let tickets: Ticket[] = [
17
+ {
18
+ id: "101",
19
+ title: "Invoice export is missing VAT",
20
+ customer: "Northwind",
21
+ status: "open",
22
+ priority: "high",
23
+ notes: "Finance team needs the export before Monday.",
24
+ },
25
+ {
26
+ id: "102",
27
+ title: "Invite email copy update",
28
+ customer: "Acme",
29
+ status: "pending",
30
+ priority: "normal",
31
+ notes: "Waiting for legal approval.",
32
+ },
33
+ {
34
+ id: "103",
35
+ title: "Archive old workspace",
36
+ customer: "Globex",
37
+ status: "closed",
38
+ priority: "low",
39
+ notes: "Done after customer confirmation.",
40
+ },
41
+ ];
42
+
43
+ export const api = {
44
+ async listTickets(params: { search?: string; status?: string }) {
45
+ await delay();
46
+ const search = (params.search ?? "").trim().toLowerCase();
47
+ return tickets.filter((ticket) => {
48
+ const matchesSearch = !search
49
+ || ticket.title.toLowerCase().includes(search)
50
+ || ticket.customer.toLowerCase().includes(search);
51
+ const matchesStatus = !params.status || params.status === "all" || ticket.status === params.status;
52
+ return matchesSearch && matchesStatus;
53
+ });
54
+ },
55
+
56
+ async getTicket(id: string) {
57
+ await delay();
58
+ const ticket = tickets.find((item) => item.id === id);
59
+ if (!ticket) throw new Error("Ticket not found");
60
+ return ticket;
61
+ },
62
+
63
+ async createTicket(input: TicketInput) {
64
+ await delay();
65
+ const ticket = { ...input, id: String(Date.now()) };
66
+ tickets = [ticket, ...tickets];
67
+ return ticket;
68
+ },
69
+
70
+ async updateTicket(id: string, input: TicketInput) {
71
+ await delay();
72
+ const next = { ...input, id };
73
+ tickets = tickets.map((ticket) => ticket.id === id ? next : ticket);
74
+ return next;
75
+ },
76
+ };
@@ -0,0 +1,12 @@
1
+ import { html, render } from "@madojs/mado";
2
+ import "./styles/global.js";
3
+ import "./components/app-shell.js";
4
+ import "./components/ticket-list.js";
5
+ import "./components/ticket-form.js";
6
+ import "./components/ticket-detail.js";
7
+ import router from "./routes.js";
8
+
9
+ const app = document.getElementById("app");
10
+ if (!app) throw new Error("#app not found");
11
+
12
+ render(html`<app-shell>${router.view}</app-shell>`, app);
@@ -0,0 +1,18 @@
1
+ import { html, page } from "@madojs/mado";
2
+
3
+ export default page({
4
+ title: "__APP_NAME__",
5
+ view: () => html`
6
+ <section class="page">
7
+ <div class="hero">
8
+ <p class="eyebrow">Mado CRUD starter</p>
9
+ <h1>Small admin apps without frontend ceremony.</h1>
10
+ <p>
11
+ This starter shows routes, resources, mutations, forms, query params
12
+ and keyed lists in a compact ticket admin.
13
+ </p>
14
+ <a class="button" href="/tickets" data-link>Open tickets</a>
15
+ </div>
16
+ </section>
17
+ `,
18
+ });
@@ -0,0 +1,12 @@
1
+ import { html, page } from "@madojs/mado";
2
+
3
+ export default page({
4
+ title: "Not found",
5
+ view: () => html`
6
+ <section class="page">
7
+ <h1>Not found</h1>
8
+ <p>The page does not exist.</p>
9
+ <a href="/" data-link>Back home</a>
10
+ </section>
11
+ `,
12
+ });
@@ -0,0 +1,6 @@
1
+ import { html, page } from "@madojs/mado";
2
+
3
+ export default page<{ id: string }>({
4
+ title: ({ id }) => `Ticket ${id}`,
5
+ view: ({ params }) => html`<ticket-detail ticket-id=${params.id}></ticket-detail>`,
6
+ });
@@ -0,0 +1,6 @@
1
+ import { html, page } from "@madojs/mado";
2
+
3
+ export default page({
4
+ title: "New ticket",
5
+ view: () => html`<ticket-form></ticket-form>`,
6
+ });
@@ -0,0 +1,6 @@
1
+ import { html, page } from "@madojs/mado";
2
+
3
+ export default page({
4
+ title: "Tickets",
5
+ view: () => html`<ticket-list></ticket-list>`,
6
+ });
@@ -0,0 +1,9 @@
1
+ import { routes } from "@madojs/mado";
2
+
3
+ export default routes({
4
+ "/": () => import("./pages/home.js"),
5
+ "/tickets": () => import("./pages/tickets.js"),
6
+ "/tickets/new": () => import("./pages/ticket-new.js"),
7
+ "/tickets/:id": () => import("./pages/ticket-detail.js"),
8
+ "*": () => import("./pages/not-found.js"),
9
+ });
@@ -0,0 +1,155 @@
1
+ import { css } from "@madojs/mado";
2
+
3
+ const sheet = css`
4
+ :root {
5
+ --bg: #f6f7f9;
6
+ --panel: #ffffff;
7
+ --text: #172033;
8
+ --muted: #64748b;
9
+ --line: #d9dee8;
10
+ --accent: #2563eb;
11
+ font-family:
12
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
13
+ "Segoe UI", sans-serif;
14
+ color: var(--text);
15
+ background: var(--bg);
16
+ }
17
+
18
+ body {
19
+ margin: 0;
20
+ }
21
+
22
+ .page {
23
+ width: min(100% - 2rem, 72rem);
24
+ margin: 0 auto;
25
+ padding: 2rem 0;
26
+ }
27
+
28
+ .narrow {
29
+ width: min(100% - 2rem, 42rem);
30
+ }
31
+
32
+ .hero,
33
+ form,
34
+ table,
35
+ dl {
36
+ border: 1px solid var(--line);
37
+ border-radius: 8px;
38
+ background: var(--panel);
39
+ }
40
+
41
+ .hero,
42
+ form,
43
+ dl {
44
+ padding: 1.25rem;
45
+ }
46
+
47
+ h1 {
48
+ margin: 0 0 0.5rem;
49
+ letter-spacing: 0;
50
+ }
51
+
52
+ .eyebrow,
53
+ p {
54
+ color: var(--muted);
55
+ }
56
+
57
+ .toolbar,
58
+ .filters,
59
+ .actions {
60
+ display: flex;
61
+ align-items: center;
62
+ justify-content: space-between;
63
+ gap: 1rem;
64
+ flex-wrap: wrap;
65
+ }
66
+
67
+ .filters {
68
+ margin: 1rem 0;
69
+ }
70
+
71
+ input,
72
+ select,
73
+ textarea,
74
+ button,
75
+ .button {
76
+ border: 1px solid var(--line);
77
+ border-radius: 6px;
78
+ padding: 0.6rem 0.75rem;
79
+ font: inherit;
80
+ }
81
+
82
+ input,
83
+ select,
84
+ textarea {
85
+ box-sizing: border-box;
86
+ width: 100%;
87
+ background: white;
88
+ }
89
+
90
+ label {
91
+ display: grid;
92
+ gap: 0.35rem;
93
+ margin-bottom: 1rem;
94
+ font-weight: 700;
95
+ }
96
+
97
+ small,
98
+ .error {
99
+ color: #dc2626;
100
+ }
101
+
102
+ button,
103
+ .button {
104
+ display: inline-flex;
105
+ align-items: center;
106
+ background: var(--accent);
107
+ color: white;
108
+ text-decoration: none;
109
+ cursor: pointer;
110
+ }
111
+
112
+ button:disabled {
113
+ opacity: 0.55;
114
+ cursor: not-allowed;
115
+ }
116
+
117
+ table {
118
+ width: 100%;
119
+ border-collapse: collapse;
120
+ overflow: hidden;
121
+ }
122
+
123
+ th,
124
+ td {
125
+ border-bottom: 1px solid var(--line);
126
+ padding: 0.75rem;
127
+ text-align: left;
128
+ }
129
+
130
+ .badge {
131
+ border-radius: 999px;
132
+ padding: 0.2rem 0.55rem;
133
+ background: #e2e8f0;
134
+ color: #334155;
135
+ font-size: 0.8rem;
136
+ font-weight: 800;
137
+ }
138
+
139
+ .badge.open {
140
+ background: #dbeafe;
141
+ color: #1d4ed8;
142
+ }
143
+
144
+ .badge.pending {
145
+ background: #fef3c7;
146
+ color: #92400e;
147
+ }
148
+
149
+ .badge.closed {
150
+ background: #dcfce7;
151
+ color: #166534;
152
+ }
153
+ `;
154
+
155
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022", "DOM"],
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "outDir": "dist",
10
+ "rootDir": "src",
11
+ "declaration": false,
12
+ "sourceMap": true
13
+ },
14
+ "include": ["src/**/*.ts"]
15
+ }
@@ -0,0 +1,19 @@
1
+ # __APP_NAME__
2
+
3
+ Generated with Mado __MADO_VERSION__.
4
+
5
+ ```bash
6
+ npm install
7
+ npm run build
8
+ npm run serve
9
+ ```
10
+
11
+ Open http://localhost:5173.
12
+
13
+ ## Files
14
+
15
+ - `src/main.ts` mounts the router into `#app`.
16
+ - `src/routes.ts` defines lazy page routes.
17
+ - `src/pages/` contains route pages.
18
+ - `src/components/` contains Web Components.
19
+ - `src/styles/global.ts` contains global styles.
@@ -0,0 +1,20 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>__APP_NAME__</title>
7
+ <script type="importmap">
8
+ {
9
+ "imports": {
10
+ "@madojs/mado": "./node_modules/@madojs/mado/dist/src/index.js",
11
+ "@madojs/mado/": "./node_modules/@madojs/mado/dist/src/"
12
+ }
13
+ }
14
+ </script>
15
+ </head>
16
+ <body>
17
+ <div id="app"></div>
18
+ <script type="module" src="./dist/main.js"></script>
19
+ </body>
20
+ </html>
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "__PACKAGE_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsc -p tsconfig.json",
8
+ "typecheck": "tsc -p tsconfig.json --noEmit",
9
+ "serve": "mado serve"
10
+ },
11
+ "dependencies": {
12
+ "@madojs/mado": "__MADOJS_VERSION__"
13
+ },
14
+ "devDependencies": {
15
+ "typescript": "^6.0.3"
16
+ }
17
+ }
@@ -0,0 +1,31 @@
1
+ import { component, css, html, signal } from "@madojs/mado";
2
+
3
+ component(
4
+ "app-counter",
5
+ () => {
6
+ const count = signal(0);
7
+ return () => html`
8
+ <button @click=${() => count.update((n) => n + 1)}>
9
+ Count: ${count}
10
+ </button>
11
+ `;
12
+ },
13
+ {
14
+ styles: css`
15
+ :host {
16
+ display: inline-block;
17
+ margin-top: 1rem;
18
+ }
19
+
20
+ button {
21
+ border: 1px solid #1f2937;
22
+ border-radius: 6px;
23
+ background: #111827;
24
+ color: white;
25
+ padding: 0.65rem 0.9rem;
26
+ font: inherit;
27
+ cursor: pointer;
28
+ }
29
+ `,
30
+ },
31
+ );