@genomicx/ui 0.3.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.
@@ -1,6 +1,10 @@
1
1
  interface AppFooterProps {
2
2
  appName?: string;
3
+ bugReportEmail?: string;
4
+ bugReportUrl?: string;
5
+ /** @deprecated Use bugReportEmail instead */
3
6
  onReportBug?: () => void;
7
+ bugReportItems?: string[];
4
8
  }
5
- export declare function AppFooter({ appName, onReportBug }: AppFooterProps): import("react/jsx-runtime").JSX.Element;
9
+ export declare function AppFooter({ appName, bugReportEmail, bugReportUrl, onReportBug, bugReportItems }: AppFooterProps): import("react/jsx-runtime").JSX.Element;
6
10
  export {};
@@ -0,0 +1,8 @@
1
+ interface BugReportModalProps {
2
+ onClose: () => void;
3
+ bugReportEmail: string;
4
+ bugReportUrl?: string;
5
+ bugReportItems?: string[];
6
+ }
7
+ export declare function BugReportModal({ onClose, bugReportEmail, bugReportUrl, bugReportItems }: BugReportModalProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -3,9 +3,11 @@ export interface NavBarProps {
3
3
  appName: string;
4
4
  appSubtitle?: string;
5
5
  version?: string;
6
+ /** Custom SVG icon element. Defaults to GenomicX rings logo. */
7
+ icon?: ReactNode;
6
8
  /** Extra items in the desktop nav (right side, before theme toggle) */
7
9
  actions?: ReactNode;
8
10
  /** Extra items in the mobile dropdown */
9
11
  mobileActions?: ReactNode;
10
12
  }
11
- export declare function NavBar({ appName, appSubtitle, version, actions, mobileActions }: NavBarProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function NavBar({ appName, appSubtitle, version, icon, actions, mobileActions }: NavBarProps): import("react/jsx-runtime").JSX.Element;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { ThemeToggle } from './components/ThemeToggle';
2
2
  export { NavBar } from './components/NavBar';
3
3
  export type { NavBarProps } from './components/NavBar';
4
4
  export { AppFooter } from './components/AppFooter';
5
+ export { BugReportModal } from './components/BugReportModal';
5
6
  export { AppShell } from './components/AppShell';
6
7
  export { LogConsole } from './components/LogConsole';
7
8
  export { loadWasmModule, createModuleInstance } from './wasm/loader';
package/dist/index.js CHANGED
@@ -1,19 +1,19 @@
1
- import { jsxs as a, jsx as e } from "react/jsx-runtime";
2
- import { useState as v, useRef as p } from "react";
1
+ import { jsxs as o, jsx as e, Fragment as v } from "react/jsx-runtime";
2
+ import { useState as m, useRef as f } from "react";
3
3
  import { Link as d } from "react-router-dom";
4
- import g from "react-hot-toast";
5
- function u({ disabled: n = !1 }) {
6
- const [o, t] = v(
4
+ import u from "react-hot-toast";
5
+ function p({ disabled: n = !1 }) {
6
+ const [a, l] = m(
7
7
  () => document.documentElement.getAttribute("data-theme") || "dark"
8
- ), l = (r) => {
9
- t(r), document.documentElement.setAttribute("data-theme", r), localStorage.setItem("gx-theme", r);
8
+ ), t = (c) => {
9
+ l(c), document.documentElement.setAttribute("data-theme", c), localStorage.setItem("gx-theme", c);
10
10
  };
11
- return /* @__PURE__ */ a("div", { className: `gx-theme-toggle${n ? " disabled" : ""}`, title: n ? "Theme switching disabled" : void 0, children: [
11
+ return /* @__PURE__ */ o("div", { className: `gx-theme-toggle${n ? " disabled" : ""}`, title: n ? "Theme switching disabled" : void 0, children: [
12
12
  /* @__PURE__ */ e(
13
13
  "button",
14
14
  {
15
- onClick: () => !n && l("dark"),
16
- className: `gx-theme-btn${o === "dark" ? " active" : ""}`,
15
+ onClick: () => !n && t("dark"),
16
+ className: `gx-theme-btn${a === "dark" ? " active" : ""}`,
17
17
  "aria-label": "Dark theme",
18
18
  disabled: n,
19
19
  children: /* @__PURE__ */ e("svg", { className: "gx-theme-btn-icon", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ e("path", { d: "M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" }) })
@@ -22,8 +22,8 @@ function u({ disabled: n = !1 }) {
22
22
  /* @__PURE__ */ e(
23
23
  "button",
24
24
  {
25
- onClick: () => !n && l("light"),
26
- className: `gx-theme-btn${o === "light" ? " active" : ""}`,
25
+ onClick: () => !n && t("light"),
26
+ className: `gx-theme-btn${a === "light" ? " active" : ""}`,
27
27
  "aria-label": "Light theme",
28
28
  disabled: n,
29
29
  children: /* @__PURE__ */ e("svg", { className: "gx-theme-btn-icon", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ e("path", { fillRule: "evenodd", d: "M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z", clipRule: "evenodd" }) })
@@ -31,152 +31,198 @@ function u({ disabled: n = !1 }) {
31
31
  )
32
32
  ] });
33
33
  }
34
- function f({ appName: n, appSubtitle: o, version: t, actions: l, mobileActions: r }) {
35
- const [c, s] = v(!1);
36
- return /* @__PURE__ */ a("nav", { className: "gx-nav", children: [
37
- /* @__PURE__ */ e("div", { className: "gx-nav-inner", children: /* @__PURE__ */ a("div", { className: "gx-nav-row", children: [
38
- /* @__PURE__ */ a(d, { to: "/", className: "gx-nav-logo", children: [
39
- /* @__PURE__ */ a("svg", { className: "gx-nav-logo-icon", viewBox: "0 0 24 24", fill: "none", stroke: "var(--gx-accent)", strokeWidth: "2", children: [
40
- /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "10" }),
41
- /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "6" }),
42
- /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "2" })
43
- ] }),
44
- /* @__PURE__ */ a("div", { children: [
45
- /* @__PURE__ */ a("h1", { className: "gx-nav-logo-name", children: [
34
+ const N = () => /* @__PURE__ */ o("svg", { className: "gx-nav-logo-icon", viewBox: "0 0 24 24", fill: "none", stroke: "var(--gx-accent)", strokeWidth: "2", children: [
35
+ /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "10" }),
36
+ /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "6" }),
37
+ /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "2" })
38
+ ] });
39
+ function k({ appName: n, appSubtitle: a, version: l, icon: t, actions: c, mobileActions: r }) {
40
+ const [s, i] = m(!1);
41
+ return /* @__PURE__ */ o("nav", { className: "gx-nav", children: [
42
+ /* @__PURE__ */ e("div", { className: "gx-nav-inner", children: /* @__PURE__ */ o("div", { className: "gx-nav-row", children: [
43
+ /* @__PURE__ */ o(d, { to: "/", className: "gx-nav-logo", children: [
44
+ t ?? /* @__PURE__ */ e(N, {}),
45
+ /* @__PURE__ */ o("div", { children: [
46
+ /* @__PURE__ */ o("h1", { className: "gx-nav-logo-name", children: [
46
47
  n,
47
- t && /* @__PURE__ */ a("span", { className: "gx-nav-logo-version", children: [
48
+ l && /* @__PURE__ */ o("span", { className: "gx-nav-logo-version", children: [
48
49
  "v",
49
- t
50
+ l
50
51
  ] })
51
52
  ] }),
52
- o && /* @__PURE__ */ e("p", { className: "gx-nav-logo-sub", children: o })
53
+ a && /* @__PURE__ */ e("p", { className: "gx-nav-logo-sub", children: a })
53
54
  ] })
54
55
  ] }),
55
- /* @__PURE__ */ a("div", { className: "gx-nav-desktop", children: [
56
- l,
56
+ /* @__PURE__ */ o("div", { className: "gx-nav-desktop", children: [
57
+ c,
57
58
  /* @__PURE__ */ e(d, { to: "/about", className: "gx-nav-link", children: "About" }),
58
- /* @__PURE__ */ a("a", { href: "https://github.com/happykhan", target: "_blank", rel: "noopener noreferrer", className: "gx-nav-link", children: [
59
+ /* @__PURE__ */ o("a", { href: "https://github.com/happykhan", target: "_blank", rel: "noopener noreferrer", className: "gx-nav-link", children: [
59
60
  "GitHub",
60
61
  /* @__PURE__ */ e("svg", { className: "gx-nav-link-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" }) })
61
62
  ] }),
62
- /* @__PURE__ */ e(u, {})
63
+ /* @__PURE__ */ e(p, {})
63
64
  ] }),
64
- /* @__PURE__ */ a("div", { className: "gx-nav-mobile-toggle", children: [
65
- /* @__PURE__ */ e(u, {}),
66
- /* @__PURE__ */ e("button", { onClick: () => s(!c), className: "gx-nav-hamburger", "aria-label": "Toggle menu", children: c ? /* @__PURE__ */ e("svg", { className: "gx-nav-hamburger-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) : /* @__PURE__ */ e("svg", { className: "gx-nav-hamburger-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h16" }) }) })
65
+ /* @__PURE__ */ o("div", { className: "gx-nav-mobile-toggle", children: [
66
+ /* @__PURE__ */ e(p, {}),
67
+ /* @__PURE__ */ e("button", { onClick: () => i(!s), className: "gx-nav-hamburger", "aria-label": "Toggle menu", children: s ? /* @__PURE__ */ e("svg", { className: "gx-nav-hamburger-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) : /* @__PURE__ */ e("svg", { className: "gx-nav-hamburger-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h16" }) }) })
67
68
  ] })
68
69
  ] }) }),
69
- c && /* @__PURE__ */ a("div", { className: "gx-nav-dropdown", children: [
70
+ s && /* @__PURE__ */ o("div", { className: "gx-nav-dropdown", children: [
70
71
  r,
71
- /* @__PURE__ */ e(d, { to: "/about", onClick: () => s(!1), className: "gx-nav-dropdown-link", children: "About" }),
72
+ /* @__PURE__ */ e(d, { to: "/about", onClick: () => i(!1), className: "gx-nav-dropdown-link", children: "About" }),
72
73
  /* @__PURE__ */ e("a", { href: "https://github.com/happykhan", target: "_blank", rel: "noopener noreferrer", className: "gx-nav-dropdown-link", children: "GitHub ↗" })
73
74
  ] })
74
75
  ] });
75
76
  }
76
- function N({ appName: n = "GenomicX", onReportBug: o }) {
77
- return /* @__PURE__ */ e("footer", { className: "gx-footer", children: /* @__PURE__ */ e("div", { className: "gx-footer-inner", children: /* @__PURE__ */ a("div", { className: "gx-footer-content", children: [
78
- /* @__PURE__ */ a("div", { className: "gx-footer-text", children: [
79
- /* @__PURE__ */ a("p", { className: "gx-footer-text-title", children: [
80
- n,
81
- " — Powered by WebAssembly"
82
- ] }),
83
- /* @__PURE__ */ e("p", { className: "gx-footer-text-sub", children: "All processing runs locally in your browser — no data leaves your computer" })
77
+ const w = [
78
+ "A description of what happened and what you expected",
79
+ "Your input files (if applicable)",
80
+ "Browser name and version",
81
+ "Steps to reproduce the issue"
82
+ ];
83
+ function b({ onClose: n, bugReportEmail: a, bugReportUrl: l, bugReportItems: t }) {
84
+ const c = t ?? w;
85
+ return /* @__PURE__ */ e("div", { className: "gx-modal-overlay", onClick: n, children: /* @__PURE__ */ o("div", { className: "gx-modal", onClick: (r) => r.stopPropagation(), children: [
86
+ /* @__PURE__ */ o("div", { className: "gx-modal-header", children: [
87
+ /* @__PURE__ */ e("h3", { className: "gx-modal-title", children: "Report a Bug" }),
88
+ /* @__PURE__ */ e("button", { className: "gx-modal-close", onClick: n, "aria-label": "Close", children: "×" })
84
89
  ] }),
85
- /* @__PURE__ */ a("div", { className: "gx-footer-links", children: [
86
- /* @__PURE__ */ e("a", { href: "https://genomicx.org", target: "_blank", rel: "noopener noreferrer", className: "gx-footer-link", children: "genomicx.org" }),
87
- o && /* @__PURE__ */ e("button", { onClick: o, className: "gx-footer-link", children: "Report Bug" })
90
+ /* @__PURE__ */ o("div", { className: "gx-modal-body", children: [
91
+ /* @__PURE__ */ e("p", { children: "To report a bug, please email the following to:" }),
92
+ /* @__PURE__ */ e("p", { className: "gx-modal-email", children: /* @__PURE__ */ e("a", { href: `mailto:${a}`, children: a }) }),
93
+ /* @__PURE__ */ o("div", { className: "gx-modal-checklist", children: [
94
+ /* @__PURE__ */ e("p", { className: "gx-modal-checklist-title", children: "Please include:" }),
95
+ /* @__PURE__ */ e("ol", { className: "gx-modal-checklist-items", children: c.map((r, s) => /* @__PURE__ */ e("li", { children: r }, s)) })
96
+ ] }),
97
+ l && /* @__PURE__ */ o("p", { className: "gx-modal-github-hint", children: [
98
+ "You can also open an issue on",
99
+ " ",
100
+ /* @__PURE__ */ e("a", { href: l, target: "_blank", rel: "noopener noreferrer", children: "GitHub" }),
101
+ "."
102
+ ] })
88
103
  ] })
89
- ] }) }) });
104
+ ] }) });
105
+ }
106
+ function y({ appName: n = "GenomicX", bugReportEmail: a, bugReportUrl: l, onReportBug: t, bugReportItems: c }) {
107
+ const [r, s] = m(!1);
108
+ function i() {
109
+ a ? s(!0) : t && t();
110
+ }
111
+ return /* @__PURE__ */ o(v, { children: [
112
+ /* @__PURE__ */ e("footer", { className: "gx-footer", children: /* @__PURE__ */ e("div", { className: "gx-footer-inner", children: /* @__PURE__ */ o("div", { className: "gx-footer-content", children: [
113
+ /* @__PURE__ */ o("div", { className: "gx-footer-text", children: [
114
+ /* @__PURE__ */ o("p", { className: "gx-footer-text-title", children: [
115
+ n,
116
+ " — Powered by WebAssembly"
117
+ ] }),
118
+ /* @__PURE__ */ e("p", { className: "gx-footer-text-sub", children: "All processing runs locally in your browser — no data leaves your computer" })
119
+ ] }),
120
+ /* @__PURE__ */ o("div", { className: "gx-footer-links", children: [
121
+ /* @__PURE__ */ e("a", { href: "https://genomicx.org", target: "_blank", rel: "noopener noreferrer", className: "gx-footer-link", children: "genomicx.org" }),
122
+ (a || l || t) && /* @__PURE__ */ e("button", { onClick: i, className: "gx-footer-link", children: "Report Bug" })
123
+ ] })
124
+ ] }) }) }),
125
+ r && a && /* @__PURE__ */ e(
126
+ b,
127
+ {
128
+ onClose: () => s(!1),
129
+ bugReportEmail: a,
130
+ bugReportUrl: l,
131
+ bugReportItems: c
132
+ }
133
+ )
134
+ ] });
90
135
  }
91
- function B({ children: n, onReportBug: o, ...t }) {
92
- return /* @__PURE__ */ a("div", { style: { minHeight: "100vh", display: "flex", flexDirection: "column", background: "var(--gx-bg)" }, children: [
93
- /* @__PURE__ */ e(f, { ...t }),
136
+ function T({ children: n, onReportBug: a, ...l }) {
137
+ return /* @__PURE__ */ o("div", { style: { minHeight: "100vh", display: "flex", flexDirection: "column", background: "var(--gx-bg)" }, children: [
138
+ /* @__PURE__ */ e(k, { ...l }),
94
139
  /* @__PURE__ */ e("main", { style: { flex: 1 }, children: n }),
95
- /* @__PURE__ */ e(N, { appName: t.appName, onReportBug: o })
140
+ /* @__PURE__ */ e(y, { appName: l.appName, onReportBug: a })
96
141
  ] });
97
142
  }
98
- function C({ logs: n, progress: o, title: t = "Console" }) {
99
- const l = p(null), r = () => {
143
+ function j({ logs: n, progress: a, title: l = "Console" }) {
144
+ const t = f(null), c = () => {
100
145
  navigator.clipboard.writeText(n.join(`
101
146
  `)).then(() => {
102
- g.success("Logs copied to clipboard!");
147
+ u.success("Logs copied to clipboard!");
103
148
  }).catch(() => {
104
- g.error("Failed to copy logs");
149
+ u.error("Failed to copy logs");
105
150
  });
106
- }, c = o && o.step !== "idle" && o.step !== "Complete!";
107
- return /* @__PURE__ */ a("div", { className: "gx-console", children: [
108
- c && /* @__PURE__ */ a("div", { className: "gx-console-progress", children: [
109
- /* @__PURE__ */ a("div", { className: "gx-console-progress-row", children: [
110
- /* @__PURE__ */ e("span", { className: "gx-console-progress-step", children: o.step }),
111
- /* @__PURE__ */ a("span", { className: "gx-console-progress-pct", children: [
112
- o.percent,
151
+ }, r = a && a.step !== "idle" && a.step !== "Complete!";
152
+ return /* @__PURE__ */ o("div", { className: "gx-console", children: [
153
+ r && /* @__PURE__ */ o("div", { className: "gx-console-progress", children: [
154
+ /* @__PURE__ */ o("div", { className: "gx-console-progress-row", children: [
155
+ /* @__PURE__ */ e("span", { className: "gx-console-progress-step", children: a.step }),
156
+ /* @__PURE__ */ o("span", { className: "gx-console-progress-pct", children: [
157
+ a.percent,
113
158
  "%"
114
159
  ] })
115
160
  ] }),
116
- /* @__PURE__ */ e("div", { className: "progress-bg", children: /* @__PURE__ */ e("div", { className: "progress-bar", style: { width: `${o.percent}%` } }) }),
117
- o.message && /* @__PURE__ */ e("div", { className: "gx-console-progress-msg", children: o.message })
161
+ /* @__PURE__ */ e("div", { className: "progress-bg", children: /* @__PURE__ */ e("div", { className: "progress-bar", style: { width: `${a.percent}%` } }) }),
162
+ a.message && /* @__PURE__ */ e("div", { className: "gx-console-progress-msg", children: a.message })
118
163
  ] }),
119
- /* @__PURE__ */ a("div", { className: "gx-console-header", children: [
120
- /* @__PURE__ */ a("div", { className: "gx-console-title-row", children: [
121
- /* @__PURE__ */ e("h3", { className: "gx-console-title", children: t }),
122
- /* @__PURE__ */ a("span", { className: "gx-console-count", children: [
164
+ /* @__PURE__ */ o("div", { className: "gx-console-header", children: [
165
+ /* @__PURE__ */ o("div", { className: "gx-console-title-row", children: [
166
+ /* @__PURE__ */ e("h3", { className: "gx-console-title", children: l }),
167
+ /* @__PURE__ */ o("span", { className: "gx-console-count", children: [
123
168
  "(",
124
169
  n.length,
125
170
  " messages)"
126
171
  ] })
127
172
  ] }),
128
- /* @__PURE__ */ a("button", { onClick: r, className: "gx-console-copy", disabled: n.length === 0, children: [
173
+ /* @__PURE__ */ o("button", { onClick: c, className: "gx-console-copy", disabled: n.length === 0, children: [
129
174
  /* @__PURE__ */ e("svg", { className: "gx-console-copy-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" }) }),
130
175
  "Copy"
131
176
  ] })
132
177
  ] }),
133
- /* @__PURE__ */ e("div", { ref: l, className: "gx-console-body", children: n.length === 0 ? /* @__PURE__ */ e("div", { className: "gx-console-empty", children: "No logs yet..." }) : n.map((s, i) => /* @__PURE__ */ e("div", { className: "gx-console-line", children: s }, i)) })
178
+ /* @__PURE__ */ e("div", { ref: t, className: "gx-console-body", children: n.length === 0 ? /* @__PURE__ */ e("div", { className: "gx-console-empty", children: "No logs yet..." }) : n.map((s, i) => /* @__PURE__ */ e("div", { className: "gx-console-line", children: s }, i)) })
134
179
  ] });
135
180
  }
136
- const k = "https://static.genomicx.org/wasm", h = /* @__PURE__ */ new Map();
137
- async function b(n, o = k) {
138
- const t = `${o}/${n}`;
139
- if (h.has(t)) return h.get(t);
140
- const [l, r] = await Promise.all([
141
- fetch(`${o}/${n}.js`),
142
- fetch(`${o}/${n}.wasm`)
181
+ const M = "https://static.genomicx.org/wasm", h = /* @__PURE__ */ new Map();
182
+ async function C(n, a = M) {
183
+ const l = `${a}/${n}`;
184
+ if (h.has(l)) return h.get(l);
185
+ const [t, c] = await Promise.all([
186
+ fetch(`${a}/${n}.js`),
187
+ fetch(`${a}/${n}.wasm`)
143
188
  ]);
144
- if (!l.ok) throw new Error(`Failed to fetch ${n}.js: ${l.status}`);
145
- if (!r.ok) throw new Error(`Failed to fetch ${n}.wasm: ${r.status}`);
146
- const [c, s] = await Promise.all([
147
- l.text(),
148
- r.arrayBuffer()
149
- ]), m = { factory: new Function("Module", c + "; return Module;")({}), wasmBinary: s };
150
- return h.set(t, m), m;
189
+ if (!t.ok) throw new Error(`Failed to fetch ${n}.js: ${t.status}`);
190
+ if (!c.ok) throw new Error(`Failed to fetch ${n}.wasm: ${c.status}`);
191
+ const [r, s] = await Promise.all([
192
+ t.text(),
193
+ c.arrayBuffer()
194
+ ]), g = { factory: new Function("Module", r + "; return Module;")({}), wasmBinary: s };
195
+ return h.set(l, g), g;
151
196
  }
152
- async function $(n, o) {
153
- const { factory: t, wasmBinary: l } = await b(n, o), r = [], c = [], s = await t({
154
- wasmBinary: l.slice(0),
155
- print: (i) => r.push(i),
156
- printErr: (i) => c.push(i),
197
+ async function z(n, a) {
198
+ const { factory: l, wasmBinary: t } = await C(n, a), c = [], r = [], s = await l({
199
+ wasmBinary: t.slice(0),
200
+ print: (i) => c.push(i),
201
+ printErr: (i) => r.push(i),
157
202
  noInitialRun: !0
158
203
  });
159
- return s._stdout = r, s._stderr = c, s;
204
+ return s._stdout = c, s._stderr = r, s;
160
205
  }
161
- function x(n, o) {
162
- const t = URL.createObjectURL(n), l = document.createElement("a");
163
- l.href = t, l.download = o, l.click(), URL.revokeObjectURL(t);
206
+ function x(n, a) {
207
+ const l = URL.createObjectURL(n), t = document.createElement("a");
208
+ t.href = l, t.download = a, t.click(), URL.revokeObjectURL(l);
164
209
  }
165
- function j(n, o, t = "text/plain") {
166
- x(new Blob([n], { type: t }), o);
210
+ function R(n, a, l = "text/plain") {
211
+ x(new Blob([n], { type: l }), a);
167
212
  }
168
- function A(n, o) {
169
- x(new Blob([n]), o);
213
+ function W(n, a) {
214
+ x(new Blob([n]), a);
170
215
  }
171
216
  export {
172
- N as AppFooter,
173
- B as AppShell,
174
- C as LogConsole,
175
- f as NavBar,
176
- u as ThemeToggle,
177
- $ as createModuleInstance,
217
+ y as AppFooter,
218
+ T as AppShell,
219
+ b as BugReportModal,
220
+ j as LogConsole,
221
+ k as NavBar,
222
+ p as ThemeToggle,
223
+ z as createModuleInstance,
178
224
  x as downloadBlob,
179
- A as downloadBuffer,
180
- j as downloadText,
181
- b as loadWasmModule
225
+ W as downloadBuffer,
226
+ R as downloadText,
227
+ C as loadWasmModule
182
228
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genomicx/ui",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Shared UI components, styles, and WASM loader for GenomicX tools",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -398,3 +398,95 @@ code {
398
398
  border-radius: 999px;
399
399
  transition: width 0.3s ease;
400
400
  }
401
+
402
+ /* ── BugReportModal ──────────────────────────── */
403
+ .gx-modal-overlay {
404
+ position: fixed;
405
+ inset: 0;
406
+ display: flex;
407
+ align-items: center;
408
+ justify-content: center;
409
+ z-index: 50;
410
+ padding: 1rem;
411
+ background: rgba(0, 0, 0, 0.6);
412
+ }
413
+
414
+ .gx-modal {
415
+ background: var(--gx-bg-alt);
416
+ border: 1px solid var(--gx-border);
417
+ border-radius: var(--gx-radius-lg);
418
+ max-width: 28rem;
419
+ width: 100%;
420
+ padding: 1.5rem;
421
+ }
422
+
423
+ .gx-modal-header {
424
+ display: flex;
425
+ justify-content: space-between;
426
+ align-items: center;
427
+ margin-bottom: 1rem;
428
+ }
429
+
430
+ .gx-modal-title {
431
+ font-size: 1.125rem;
432
+ font-weight: 700;
433
+ color: var(--gx-text);
434
+ margin: 0;
435
+ }
436
+
437
+ .gx-modal-close {
438
+ background: none;
439
+ border: none;
440
+ cursor: pointer;
441
+ font-size: 1.25rem;
442
+ line-height: 1;
443
+ color: var(--gx-text-muted);
444
+ padding: 0;
445
+ }
446
+ .gx-modal-close:hover { color: var(--gx-text); }
447
+
448
+ .gx-modal-body {
449
+ font-size: 0.875rem;
450
+ color: var(--gx-text);
451
+ display: flex;
452
+ flex-direction: column;
453
+ gap: 0.75rem;
454
+ }
455
+
456
+ .gx-modal-email {
457
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
458
+ font-weight: 700;
459
+ color: var(--gx-accent);
460
+ margin: 0;
461
+ }
462
+ .gx-modal-email a { color: var(--gx-accent); text-decoration: none; }
463
+ .gx-modal-email a:hover { text-decoration: underline; }
464
+
465
+ .gx-modal-checklist {
466
+ background: var(--gx-bg);
467
+ border: 1px solid var(--gx-border);
468
+ border-radius: 6px;
469
+ padding: 0.75rem 1rem;
470
+ }
471
+
472
+ .gx-modal-checklist-title {
473
+ font-weight: 600;
474
+ margin: 0 0 0.5rem;
475
+ color: var(--gx-text);
476
+ }
477
+
478
+ .gx-modal-checklist-items {
479
+ margin: 0;
480
+ padding-left: 1.25rem;
481
+ color: var(--gx-text-muted);
482
+ display: flex;
483
+ flex-direction: column;
484
+ gap: 0.25rem;
485
+ }
486
+
487
+ .gx-modal-github-hint {
488
+ font-size: 0.75rem;
489
+ color: var(--gx-text-muted);
490
+ margin: 0;
491
+ }
492
+ .gx-modal-github-hint a { color: var(--gx-accent); text-decoration: underline; }
@@ -20,6 +20,7 @@
20
20
  --gx-code-bg: #f1f5f9;
21
21
  --gx-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
22
22
  --gx-transition: 0.2s ease;
23
+ --gx-max-width: 1280px;
23
24
  --gx-radius: 8px;
24
25
  --gx-radius-lg: 12px;
25
26
  }