elysia-rack 0.2.0 → 0.2.2

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.
@@ -0,0 +1,19 @@
1
+ // src/react/donate.ts
2
+ var DEFAULT_DONATE_URL = "https://ko-fi.com/notoofly_manu";
3
+ var DEFAULT_DONATE_LABEL = "☕ Donate";
4
+ var globalDonate = { enabled: true, url: DEFAULT_DONATE_URL, label: DEFAULT_DONATE_LABEL };
5
+ function setGlobalDonate(enabled) {
6
+ globalDonate = { enabled: enabled !== false, url: DEFAULT_DONATE_URL, label: DEFAULT_DONATE_LABEL };
7
+ }
8
+ function getGlobalDonate() {
9
+ return globalDonate;
10
+ }
11
+ function resolveDonateProp(prop) {
12
+ if (prop === false)
13
+ return { enabled: false, url: DEFAULT_DONATE_URL, label: DEFAULT_DONATE_LABEL };
14
+ if (prop === true || prop === undefined)
15
+ return getGlobalDonate();
16
+ return getGlobalDonate();
17
+ }
18
+
19
+ export { setGlobalDonate, resolveDonateProp };
@@ -0,0 +1,39 @@
1
+ // src/react/donate.ts
2
+ var DEFAULT_DONATE_URL = "https://ko-fi.com/notoofly_manu";
3
+ var DEFAULT_DONATE_LABEL = "☕ Donate";
4
+ var globalDonate = { enabled: true, url: DEFAULT_DONATE_URL, label: DEFAULT_DONATE_LABEL };
5
+ function setGlobalDonate(cfg) {
6
+ if (cfg === false) {
7
+ globalDonate = { enabled: false, url: DEFAULT_DONATE_URL, label: DEFAULT_DONATE_LABEL };
8
+ return;
9
+ }
10
+ if (!cfg) {
11
+ globalDonate = { enabled: true, url: DEFAULT_DONATE_URL, label: DEFAULT_DONATE_LABEL };
12
+ return;
13
+ }
14
+ globalDonate = {
15
+ enabled: cfg.enabled !== false,
16
+ url: cfg.url ?? DEFAULT_DONATE_URL,
17
+ label: cfg.label ?? DEFAULT_DONATE_LABEL
18
+ };
19
+ }
20
+ function getGlobalDonate() {
21
+ return globalDonate;
22
+ }
23
+ function resolveDonateProp(prop) {
24
+ if (prop === false)
25
+ return { enabled: false, url: DEFAULT_DONATE_URL, label: DEFAULT_DONATE_LABEL };
26
+ if (prop === undefined)
27
+ return getGlobalDonate();
28
+ if (typeof prop === "object" && prop !== null) {
29
+ const base = getGlobalDonate();
30
+ return {
31
+ enabled: prop.enabled !== false,
32
+ url: prop.url ?? base.url,
33
+ label: prop.label ?? base.label
34
+ };
35
+ }
36
+ return getGlobalDonate();
37
+ }
38
+
39
+ export { setGlobalDonate, resolveDonateProp };
@@ -0,0 +1,187 @@
1
+ // src/react/page/components/Breadcrumb.tsx
2
+ import { jsxDEV } from "react/jsx-dev-runtime";
3
+ function Breadcrumb({ trail }) {
4
+ return /* @__PURE__ */ jsxDEV("nav", {
5
+ "aria-label": "Breadcrumb",
6
+ className: "pb-3 text-sm",
7
+ children: /* @__PURE__ */ jsxDEV("ol", {
8
+ className: "flex flex-wrap items-center gap-1.5",
9
+ children: trail.map((item, i) => {
10
+ const last = i === trail.length - 1;
11
+ return /* @__PURE__ */ jsxDEV("li", {
12
+ className: "flex items-center gap-1.5",
13
+ children: [
14
+ i > 0 ? /* @__PURE__ */ jsxDEV("span", {
15
+ "aria-hidden": "true",
16
+ className: "text-text-muted",
17
+ children: "/"
18
+ }, undefined, false, undefined, this) : null,
19
+ item.href && !last ? /* @__PURE__ */ jsxDEV("a", {
20
+ href: item.href,
21
+ className: "text-text-muted hover:text-foreground",
22
+ children: item.label
23
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV("span", {
24
+ "aria-current": last ? "page" : undefined,
25
+ className: last ? "text-foreground font-semibold" : "text-text-muted",
26
+ children: item.label
27
+ }, undefined, false, undefined, this)
28
+ ]
29
+ }, `${item.label}-${i}`, true, undefined, this);
30
+ })
31
+ }, undefined, false, undefined, this)
32
+ }, undefined, false, undefined, this);
33
+ }
34
+
35
+ // src/react/page/components/Sidebar.tsx
36
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
37
+ function SidebarItemView({ item, depth = 0 }) {
38
+ const hasChildren = item.children && item.children.length > 0;
39
+ return /* @__PURE__ */ jsxDEV2("li", {
40
+ children: [
41
+ /* @__PURE__ */ jsxDEV2("a", {
42
+ href: item.href,
43
+ "aria-current": item.active ? "page" : undefined,
44
+ className: item.active ? "bg-sidebar-primary text-sidebar-primary-foreground flex items-center gap-2 rounded-md px-2 py-1.5 text-sm font-semibold" : "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex items-center gap-2 rounded-md px-2 py-1.5 text-sm",
45
+ style: depth > 0 ? { paddingLeft: `${8 + depth * 12}px` } : undefined,
46
+ children: [
47
+ item.icon ? /* @__PURE__ */ jsxDEV2("span", {
48
+ "aria-hidden": "true",
49
+ children: item.icon
50
+ }, undefined, false, undefined, this) : null,
51
+ /* @__PURE__ */ jsxDEV2("span", {
52
+ children: item.label
53
+ }, undefined, false, undefined, this)
54
+ ]
55
+ }, undefined, true, undefined, this),
56
+ hasChildren ? /* @__PURE__ */ jsxDEV2("ul", {
57
+ className: "border-sidebar-border mt-0.5 ml-2 flex flex-col gap-0.5 border-l py-1 pl-2",
58
+ children: item.children.map((child) => /* @__PURE__ */ jsxDEV2(SidebarItemView, {
59
+ item: child,
60
+ depth: depth + 1
61
+ }, child.id, false, undefined, this))
62
+ }, undefined, false, undefined, this) : null
63
+ ]
64
+ }, undefined, true, undefined, this);
65
+ }
66
+ function hasActive(items) {
67
+ for (const it of items) {
68
+ if (it.active)
69
+ return true;
70
+ if (it.children && hasActive(it.children))
71
+ return true;
72
+ }
73
+ return false;
74
+ }
75
+ function Sidebar({ title, groups }) {
76
+ return /* @__PURE__ */ jsxDEV2("aside", {
77
+ className: "bg-sidebar text-sidebar-foreground border-sidebar-border min-h-0 w-64 shrink-0 overflow-y-auto rounded-lg border",
78
+ children: [
79
+ /* @__PURE__ */ jsxDEV2("div", {
80
+ className: "border-sidebar-border border-b px-4 py-3",
81
+ children: /* @__PURE__ */ jsxDEV2("p", {
82
+ className: "font-koran text-xl font-black tracking-tight",
83
+ children: title
84
+ }, undefined, false, undefined, this)
85
+ }, undefined, false, undefined, this),
86
+ /* @__PURE__ */ jsxDEV2("nav", {
87
+ className: "flex flex-col gap-4 p-3",
88
+ children: groups.map((group) => /* @__PURE__ */ jsxDEV2("section", {
89
+ children: /* @__PURE__ */ jsxDEV2("details", {
90
+ className: "group",
91
+ open: hasActive(group.items) || undefined,
92
+ children: [
93
+ /* @__PURE__ */ jsxDEV2("summary", {
94
+ className: "font-koran border-sidebar-border flex cursor-pointer list-none items-center justify-between border-b px-2 pb-1 text-base font-bold tracking-tight [&::-webkit-details-marker]:hidden",
95
+ children: [
96
+ /* @__PURE__ */ jsxDEV2("span", {
97
+ children: group.name
98
+ }, undefined, false, undefined, this),
99
+ /* @__PURE__ */ jsxDEV2("span", {
100
+ "aria-hidden": "true",
101
+ className: "text-text-muted text-xs transition group-open:rotate-180",
102
+ children: "▾"
103
+ }, undefined, false, undefined, this)
104
+ ]
105
+ }, undefined, true, undefined, this),
106
+ /* @__PURE__ */ jsxDEV2("ul", {
107
+ className: "border-sidebar-border mt-1 ml-2 flex flex-col gap-0.5 border-l py-1 pl-2",
108
+ children: group.items.map((item) => /* @__PURE__ */ jsxDEV2(SidebarItemView, {
109
+ item
110
+ }, item.id, false, undefined, this))
111
+ }, undefined, false, undefined, this)
112
+ ]
113
+ }, undefined, true, undefined, this)
114
+ }, group.name, false, undefined, this))
115
+ }, undefined, false, undefined, this)
116
+ ]
117
+ }, undefined, true, undefined, this);
118
+ }
119
+
120
+ // src/react/page/components/Masthead.tsx
121
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
122
+ function Masthead({ title, group, resource, total }) {
123
+ const today = new Date().toLocaleDateString("en-US", {
124
+ weekday: "long",
125
+ day: "numeric",
126
+ month: "long",
127
+ year: "numeric"
128
+ });
129
+ return /* @__PURE__ */ jsxDEV3("header", {
130
+ className: "bg-header text-header-foreground",
131
+ children: [
132
+ /* @__PURE__ */ jsxDEV3("div", {
133
+ className: "flex items-center justify-between gap-4 py-2 text-xs tracking-widest text-text-muted uppercase",
134
+ children: [
135
+ /* @__PURE__ */ jsxDEV3("span", {
136
+ children: today
137
+ }, undefined, false, undefined, this),
138
+ /* @__PURE__ */ jsxDEV3("span", {
139
+ className: "hidden sm:inline",
140
+ children: "Daily Edition"
141
+ }, undefined, false, undefined, this),
142
+ /* @__PURE__ */ jsxDEV3("button", {
143
+ id: "koran-theme-toggle",
144
+ type: "button",
145
+ className: "border-border hover:bg-navigation-hover rounded-full border px-3 py-1 tracking-widest uppercase",
146
+ children: "◐ Theme"
147
+ }, undefined, false, undefined, this)
148
+ ]
149
+ }, undefined, true, undefined, this),
150
+ /* @__PURE__ */ jsxDEV3("h1", {
151
+ className: "font-koran py-2 text-center text-5xl font-black tracking-tight",
152
+ children: title
153
+ }, undefined, false, undefined, this),
154
+ /* @__PURE__ */ jsxDEV3("div", {
155
+ className: "flex items-center justify-center gap-2 pb-3 text-sm text-text-muted",
156
+ children: [
157
+ group ? /* @__PURE__ */ jsxDEV3("span", {
158
+ className: "uppercase",
159
+ children: group
160
+ }, undefined, false, undefined, this) : null,
161
+ group && (resource || total !== undefined) ? /* @__PURE__ */ jsxDEV3("span", {
162
+ "aria-hidden": "true",
163
+ children: "·"
164
+ }, undefined, false, undefined, this) : null,
165
+ resource ? /* @__PURE__ */ jsxDEV3("span", {
166
+ className: "font-mono text-xs",
167
+ children: [
168
+ "/",
169
+ resource
170
+ ]
171
+ }, undefined, true, undefined, this) : null,
172
+ total !== undefined ? /* @__PURE__ */ jsxDEV3("span", {
173
+ children: [
174
+ total,
175
+ " records"
176
+ ]
177
+ }, undefined, true, undefined, this) : null
178
+ ]
179
+ }, undefined, true, undefined, this),
180
+ /* @__PURE__ */ jsxDEV3("div", {
181
+ className: "koran-rule-double"
182
+ }, undefined, false, undefined, this)
183
+ ]
184
+ }, undefined, true, undefined, this);
185
+ }
186
+
187
+ export { Breadcrumb, Sidebar, Masthead };
@@ -6,6 +6,8 @@ export interface DashboardOptions {
6
6
  title?: string;
7
7
  /** React page registry key (default "/dashboard") */
8
8
  pagePath?: string;
9
+ /** Donate badge — false to hide, true/undefined to show */
10
+ donate?: boolean;
9
11
  }
10
12
  /**
11
13
  * Dashboard plugin — mounts `GET {path}` that renders
@@ -50,6 +52,7 @@ export declare function dashboard(options?: DashboardOptions): Elysia<"", {
50
52
  path: string;
51
53
  props: {
52
54
  resource: string | undefined;
55
+ donate?: boolean | undefined;
53
56
  name?: string | undefined;
54
57
  };
55
58
  };
@@ -1149,6 +1149,7 @@ function dashboard(options) {
1149
1149
  path: pagePath,
1150
1150
  props: {
1151
1151
  ...title !== undefined ? { name: title } : {},
1152
+ ...options?.donate !== undefined ? { donate: options.donate } : {},
1152
1153
  resource: typeof query.resource === "string" ? query.resource : undefined
1153
1154
  }
1154
1155
  }));
@@ -0,0 +1,10 @@
1
+ export declare const DEFAULT_DONATE_URL = "https://ko-fi.com/notoofly_manu";
2
+ export declare const DEFAULT_DONATE_LABEL = "\u2615 Donate";
3
+ export type ResolvedDonate = {
4
+ enabled: boolean;
5
+ url: string;
6
+ label: string;
7
+ };
8
+ export declare function setGlobalDonate(enabled?: boolean): void;
9
+ export declare function getGlobalDonate(): ResolvedDonate;
10
+ export declare function resolveDonateProp(prop?: boolean): ResolvedDonate;
@@ -1,3 +1,6 @@
1
+ import {
2
+ setGlobalDonate
3
+ } from "../index-6v0ns1xn.js";
1
4
  import {
2
5
  __require
3
6
  } from "../index-37x76zdn.js";
@@ -152,8 +155,8 @@ function page(path, props) {
152
155
  };
153
156
  }
154
157
  var pages = {
155
- "/dashboard": () => import("../Dashboard-s8nfyf7s.js"),
156
- "/panel": () => import("../Panel-b7jhgvnm.js")
158
+ "/dashboard": () => import("../Dashboard-7gmx8c9y.js"),
159
+ "/panel": () => import("../Panel-wjxjtbmr.js")
157
160
  };
158
161
  var defaultPages = pages;
159
162
  function mergeRegistries(base, extra) {
@@ -163,6 +166,7 @@ function mergeRegistries(base, extra) {
163
166
  return list.reduce((acc, r) => ({ ...acc, ...r }), { ...base });
164
167
  }
165
168
  function reactPlugin(options = {}) {
169
+ setGlobalDonate(options.donate);
166
170
  const registry = mergeRegistries(defaultPages, options.pages);
167
171
  const render = createRenderer(registry);
168
172
  const panelCssOverride = pickPanelCssOption(options);
@@ -1,4 +1,6 @@
1
1
  export interface DashboardProps {
2
2
  name?: string;
3
+ resource?: string;
4
+ donate?: boolean;
3
5
  }
4
6
  export default function Dashboard(props: DashboardProps): Promise<import("react").JSX.Element>;
@@ -1,5 +1,6 @@
1
1
  import type { FieldDescriptor } from "../../rack/adapters/index";
2
2
  export interface PanelProps {
3
+ donate?: boolean;
3
4
  resource?: string;
4
5
  metadata?: {
5
6
  label?: string;
@@ -26,5 +26,10 @@ export declare namespace ReactRack {
26
26
  */
27
27
  css?: PanelCssOverride;
28
28
  panelCss?: PanelCssOverride;
29
+ /**
30
+ * Donate badge/link — shown in Dashboard & Panel footer.
31
+ * @default true (show Ko-fi)
32
+ */
33
+ donate?: boolean;
29
34
  }
30
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elysia-rack",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Model-driven CRUD rack for Elysia: REST + QUERY routes, validation, authorization, OpenAPI metadata, and React panel rendering.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -81,5 +81,9 @@
81
81
  "react": "^19.2.8",
82
82
  "react-dom": "^19.2.8",
83
83
  "typescript": "^5"
84
+ },
85
+ "funding": {
86
+ "type": "ko-fi",
87
+ "url": "https://ko-fi.com/notoofly_manu"
84
88
  }
85
89
  }