@genomicx/ui 0.6.0 → 0.7.1

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,11 @@
1
+ export interface FileUploadProps {
2
+ files: File[];
3
+ onFilesChange: (files: File[]) => void;
4
+ disabled?: boolean;
5
+ multiple?: boolean;
6
+ accept?: string;
7
+ label?: string;
8
+ hint?: string;
9
+ filterFn?: (file: File) => boolean;
10
+ }
11
+ export declare function FileUpload({ files, onFilesChange, disabled, multiple, accept, label, hint, filterFn, }: FileUploadProps): import("react/jsx-runtime").JSX.Element;
@@ -5,9 +5,11 @@ export interface NavBarProps {
5
5
  version?: string;
6
6
  /** Custom SVG icon element. Defaults to GenomicX rings logo. */
7
7
  icon?: ReactNode;
8
+ /** GitHub repository URL shown in the nav header */
9
+ githubUrl?: string;
8
10
  /** Extra items in the desktop nav (right side, before theme toggle) */
9
11
  actions?: ReactNode;
10
12
  /** Extra items in the mobile dropdown */
11
13
  mobileActions?: ReactNode;
12
14
  }
13
- export declare function NavBar({ appName, appSubtitle, version, icon, actions, mobileActions }: NavBarProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function NavBar({ appName, appSubtitle, version, icon, githubUrl, actions, mobileActions }: NavBarProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ export interface ProgressBarProps {
2
+ value: number;
3
+ label?: string;
4
+ }
5
+ export declare function ProgressBar({ value, label }: ProgressBarProps): import("react/jsx-runtime").JSX.Element;
package/dist/index.d.ts CHANGED
@@ -5,6 +5,10 @@ export { AppFooter } from './components/AppFooter';
5
5
  export { BugReportModal } from './components/BugReportModal';
6
6
  export { AppShell } from './components/AppShell';
7
7
  export { LogConsole } from './components/LogConsole';
8
+ export { FileUpload } from './components/FileUpload';
9
+ export type { FileUploadProps } from './components/FileUpload';
10
+ export { ProgressBar } from './components/ProgressBar';
11
+ export type { ProgressBarProps } from './components/ProgressBar';
8
12
  export { loadWasmModule, createModuleInstance } from './wasm/loader';
9
13
  export type { EmscriptenModule, WasmModuleFactory } from './wasm/types';
10
14
  export { downloadBlob, downloadText, downloadBuffer } from './utils/download';
package/dist/index.js CHANGED
@@ -1,18 +1,18 @@
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
- import { Link as d } from "react-router-dom";
4
- import u from "react-hot-toast";
5
- function p({ disabled: n = !1 }) {
6
- const [a, l] = m(
1
+ import { jsxs as l, jsx as e, Fragment as p } from "react/jsx-runtime";
2
+ import { useState as f, useRef as b, useCallback as x } from "react";
3
+ import { Link as g } from "react-router-dom";
4
+ import v from "react-hot-toast";
5
+ function N({ disabled: n = !1 }) {
6
+ const [a, o] = f(
7
7
  () => document.documentElement.getAttribute("data-theme") || "dark"
8
- ), t = (c) => {
9
- l(c), document.documentElement.setAttribute("data-theme", c), localStorage.setItem("gx-theme", c);
8
+ ), r = (s) => {
9
+ o(s), document.documentElement.setAttribute("data-theme", s), localStorage.setItem("gx-theme", s);
10
10
  };
11
- return /* @__PURE__ */ o("div", { className: `gx-theme-toggle${n ? " disabled" : ""}`, title: n ? "Theme switching disabled" : void 0, children: [
11
+ return /* @__PURE__ */ l("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 && t("dark"),
15
+ onClick: () => !n && r("dark"),
16
16
  className: `gx-theme-btn${a === "dark" ? " active" : ""}`,
17
17
  "aria-label": "Dark theme",
18
18
  disabled: n,
@@ -22,7 +22,7 @@ function p({ disabled: n = !1 }) {
22
22
  /* @__PURE__ */ e(
23
23
  "button",
24
24
  {
25
- onClick: () => !n && t("light"),
25
+ onClick: () => !n && r("light"),
26
26
  className: `gx-theme-btn${a === "light" ? " active" : ""}`,
27
27
  "aria-label": "Light theme",
28
28
  disabled: n,
@@ -31,129 +31,129 @@ function p({ disabled: n = !1 }) {
31
31
  )
32
32
  ] });
33
33
  }
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: [
34
+ const y = () => /* @__PURE__ */ l("svg", { className: "gx-nav-logo-icon", viewBox: "0 0 24 24", fill: "none", stroke: "var(--gx-accent)", strokeWidth: "2", children: [
35
35
  /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "10" }),
36
36
  /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "6" }),
37
37
  /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "2" })
38
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: [
39
+ function M({ appName: n, appSubtitle: a, version: o, icon: r, githubUrl: s, actions: i, mobileActions: c }) {
40
+ const [t, m] = f(!1);
41
+ return /* @__PURE__ */ l("nav", { className: "gx-nav", children: [
42
+ /* @__PURE__ */ e("div", { className: "gx-nav-inner", children: /* @__PURE__ */ l("div", { className: "gx-nav-row", children: [
43
+ /* @__PURE__ */ l(g, { to: "/", className: "gx-nav-logo", children: [
44
+ r ?? /* @__PURE__ */ e(y, {}),
45
+ /* @__PURE__ */ l("div", { children: [
46
+ /* @__PURE__ */ l("h1", { className: "gx-nav-logo-name", children: [
47
47
  n,
48
- l && /* @__PURE__ */ o("span", { className: "gx-nav-logo-version", children: [
48
+ o && /* @__PURE__ */ l("span", { className: "gx-nav-logo-version", children: [
49
49
  "v",
50
- l
50
+ o
51
51
  ] })
52
52
  ] }),
53
53
  a && /* @__PURE__ */ e("p", { className: "gx-nav-logo-sub", children: a })
54
54
  ] })
55
55
  ] }),
56
- /* @__PURE__ */ o("div", { className: "gx-nav-desktop", children: [
57
- c,
58
- /* @__PURE__ */ e(d, { to: "/about", className: "gx-nav-link", children: "About" }),
59
- /* @__PURE__ */ o("a", { href: "https://github.com/happykhan", target: "_blank", rel: "noopener noreferrer", className: "gx-nav-link", children: [
56
+ /* @__PURE__ */ l("div", { className: "gx-nav-desktop", children: [
57
+ i,
58
+ /* @__PURE__ */ e(g, { to: "/about", className: "gx-nav-link", children: "About" }),
59
+ s && /* @__PURE__ */ l("a", { href: s, target: "_blank", rel: "noopener noreferrer", className: "gx-nav-link", children: [
60
60
  "GitHub",
61
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" }) })
62
62
  ] }),
63
- /* @__PURE__ */ e(p, {})
63
+ /* @__PURE__ */ e(N, {})
64
64
  ] }),
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" }) }) })
65
+ /* @__PURE__ */ l("div", { className: "gx-nav-mobile-toggle", children: [
66
+ /* @__PURE__ */ e(N, {}),
67
+ /* @__PURE__ */ e("button", { onClick: () => m(!t), className: "gx-nav-hamburger", "aria-label": "Toggle menu", children: t ? /* @__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" }) }) })
68
68
  ] })
69
69
  ] }) }),
70
- s && /* @__PURE__ */ o("div", { className: "gx-nav-dropdown", children: [
71
- r,
72
- /* @__PURE__ */ e(d, { to: "/about", onClick: () => i(!1), className: "gx-nav-dropdown-link", children: "About" }),
73
- /* @__PURE__ */ e("a", { href: "https://github.com/happykhan", target: "_blank", rel: "noopener noreferrer", className: "gx-nav-dropdown-link", children: "GitHub ↗" })
70
+ t && /* @__PURE__ */ l("div", { className: "gx-nav-dropdown", children: [
71
+ c,
72
+ /* @__PURE__ */ e(g, { to: "/about", onClick: () => m(!1), className: "gx-nav-dropdown-link", children: "About" }),
73
+ s && /* @__PURE__ */ e("a", { href: s, target: "_blank", rel: "noopener noreferrer", className: "gx-nav-dropdown-link", children: "GitHub ↗" })
74
74
  ] })
75
75
  ] });
76
76
  }
77
- const w = [
77
+ const B = [
78
78
  "A description of what happened and what you expected",
79
79
  "Your input files (if applicable)",
80
80
  "Browser name and version",
81
81
  "Steps to reproduce the issue"
82
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: [
83
+ function C({ onClose: n, bugReportEmail: a, bugReportUrl: o, bugReportItems: r }) {
84
+ const s = r ?? B;
85
+ return /* @__PURE__ */ e("div", { className: "gx-modal-overlay", onClick: n, children: /* @__PURE__ */ l("div", { className: "gx-modal", onClick: (i) => i.stopPropagation(), children: [
86
+ /* @__PURE__ */ l("div", { className: "gx-modal-header", children: [
87
87
  /* @__PURE__ */ e("h3", { className: "gx-modal-title", children: "Report a Bug" }),
88
88
  /* @__PURE__ */ e("button", { className: "gx-modal-close", onClick: n, "aria-label": "Close", children: "×" })
89
89
  ] }),
90
- /* @__PURE__ */ o("div", { className: "gx-modal-body", children: [
90
+ /* @__PURE__ */ l("div", { className: "gx-modal-body", children: [
91
91
  /* @__PURE__ */ e("p", { children: "To report a bug, please email the following to:" }),
92
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: [
93
+ /* @__PURE__ */ l("div", { className: "gx-modal-checklist", children: [
94
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)) })
95
+ /* @__PURE__ */ e("ol", { className: "gx-modal-checklist-items", children: s.map((i, c) => /* @__PURE__ */ e("li", { children: i }, c)) })
96
96
  ] }),
97
- l && /* @__PURE__ */ o("p", { className: "gx-modal-github-hint", children: [
97
+ o && /* @__PURE__ */ l("p", { className: "gx-modal-github-hint", children: [
98
98
  "You can also open an issue on",
99
99
  " ",
100
- /* @__PURE__ */ e("a", { href: l, target: "_blank", rel: "noopener noreferrer", children: "GitHub" }),
100
+ /* @__PURE__ */ e("a", { href: o, target: "_blank", rel: "noopener noreferrer", children: "GitHub" }),
101
101
  "."
102
102
  ] })
103
103
  ] })
104
104
  ] }) });
105
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();
106
+ function L({ appName: n = "GenomicX", bugReportEmail: a, bugReportUrl: o, onReportBug: r, bugReportItems: s }) {
107
+ const [i, c] = f(!1);
108
+ function t() {
109
+ a ? c(!0) : r && r();
110
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: [
111
+ return /* @__PURE__ */ l(p, { children: [
112
+ /* @__PURE__ */ e("footer", { className: "gx-footer", children: /* @__PURE__ */ e("div", { className: "gx-footer-inner", children: /* @__PURE__ */ l("div", { className: "gx-footer-content", children: [
113
+ /* @__PURE__ */ l("div", { className: "gx-footer-text", children: [
114
+ /* @__PURE__ */ l("p", { className: "gx-footer-text-title", children: [
115
115
  n,
116
116
  " — Powered by WebAssembly"
117
117
  ] }),
118
118
  /* @__PURE__ */ e("p", { className: "gx-footer-text-sub", children: "All processing runs locally in your browser — no data leaves your computer" })
119
119
  ] }),
120
- /* @__PURE__ */ o("div", { className: "gx-footer-links", children: [
120
+ /* @__PURE__ */ l("div", { className: "gx-footer-links", children: [
121
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" })
122
+ (a || o || r) && /* @__PURE__ */ e("button", { onClick: t, className: "gx-footer-link", children: "Report Bug" })
123
123
  ] })
124
124
  ] }) }) }),
125
- r && a && /* @__PURE__ */ e(
126
- b,
125
+ i && a && /* @__PURE__ */ e(
126
+ C,
127
127
  {
128
- onClose: () => s(!1),
128
+ onClose: () => c(!1),
129
129
  bugReportEmail: a,
130
- bugReportUrl: l,
131
- bugReportItems: c
130
+ bugReportUrl: o,
131
+ bugReportItems: s
132
132
  }
133
133
  )
134
134
  ] });
135
135
  }
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 }),
136
+ function W({ children: n, onReportBug: a, ...o }) {
137
+ return /* @__PURE__ */ l("div", { style: { minHeight: "100vh", display: "flex", flexDirection: "column", background: "var(--gx-bg)" }, children: [
138
+ /* @__PURE__ */ e(M, { ...o }),
139
139
  /* @__PURE__ */ e("main", { style: { flex: 1 }, children: n }),
140
- /* @__PURE__ */ e(y, { appName: l.appName, onReportBug: a })
140
+ /* @__PURE__ */ e(L, { appName: o.appName, onReportBug: a })
141
141
  ] });
142
142
  }
143
- function j({ logs: n, progress: a, title: l = "Console" }) {
144
- const t = f(null), c = () => {
143
+ function H({ logs: n, progress: a, title: o = "Console" }) {
144
+ const r = b(null), s = () => {
145
145
  navigator.clipboard.writeText(n.join(`
146
146
  `)).then(() => {
147
- u.success("Logs copied to clipboard!");
147
+ v.success("Logs copied to clipboard!");
148
148
  }).catch(() => {
149
- u.error("Failed to copy logs");
149
+ v.error("Failed to copy logs");
150
150
  });
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: [
151
+ }, i = a && a.step !== "idle" && a.step !== "Complete!";
152
+ return /* @__PURE__ */ l("div", { className: "gx-console", children: [
153
+ i && /* @__PURE__ */ l("div", { className: "gx-console-progress", children: [
154
+ /* @__PURE__ */ l("div", { className: "gx-console-progress-row", children: [
155
155
  /* @__PURE__ */ e("span", { className: "gx-console-progress-step", children: a.step }),
156
- /* @__PURE__ */ o("span", { className: "gx-console-progress-pct", children: [
156
+ /* @__PURE__ */ l("span", { className: "gx-console-progress-pct", children: [
157
157
  a.percent,
158
158
  "%"
159
159
  ] })
@@ -161,68 +161,155 @@ function j({ logs: n, progress: a, title: l = "Console" }) {
161
161
  /* @__PURE__ */ e("div", { className: "progress-bg", children: /* @__PURE__ */ e("div", { className: "progress-bar", style: { width: `${a.percent}%` } }) }),
162
162
  a.message && /* @__PURE__ */ e("div", { className: "gx-console-progress-msg", children: a.message })
163
163
  ] }),
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: [
164
+ /* @__PURE__ */ l("div", { className: "gx-console-header", children: [
165
+ /* @__PURE__ */ l("div", { className: "gx-console-title-row", children: [
166
+ /* @__PURE__ */ e("h3", { className: "gx-console-title", children: o }),
167
+ /* @__PURE__ */ l("span", { className: "gx-console-count", children: [
168
168
  "(",
169
169
  n.length,
170
170
  " messages)"
171
171
  ] })
172
172
  ] }),
173
- /* @__PURE__ */ o("button", { onClick: c, className: "gx-console-copy", disabled: n.length === 0, children: [
173
+ /* @__PURE__ */ l("button", { onClick: s, className: "gx-console-copy", disabled: n.length === 0, children: [
174
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" }) }),
175
175
  "Copy"
176
176
  ] })
177
177
  ] }),
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)) })
178
+ /* @__PURE__ */ e("div", { ref: r, className: "gx-console-body", children: n.length === 0 ? /* @__PURE__ */ e("div", { className: "gx-console-empty", children: "No logs yet..." }) : n.map((c, t) => /* @__PURE__ */ e("div", { className: "gx-console-line", children: c }, t)) })
179
179
  ] });
180
180
  }
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([
181
+ function R({
182
+ files: n,
183
+ onFilesChange: a,
184
+ disabled: o = !1,
185
+ multiple: r = !0,
186
+ accept: s = ".fasta,.fa,.fna,.fsa,.fasta.gz,.fa.gz,.fna.gz",
187
+ label: i = "Drop files here or click to browse",
188
+ hint: c,
189
+ filterFn: t
190
+ }) {
191
+ const m = x(
192
+ (d) => {
193
+ if (!d.target.files) return;
194
+ let h = Array.from(d.target.files);
195
+ t && (h = h.filter(t)), h.length > 0 && a(h);
196
+ },
197
+ [a, t]
198
+ ), w = x(
199
+ (d) => {
200
+ if (d.preventDefault(), !d.dataTransfer.files) return;
201
+ let h = Array.from(d.dataTransfer.files);
202
+ t && (h = h.filter(t)), h.length > 0 && a(h);
203
+ },
204
+ [a, t]
205
+ );
206
+ return /* @__PURE__ */ e("div", { className: "gx-file-upload", onDrop: w, onDragOver: (d) => d.preventDefault(), children: /* @__PURE__ */ l("label", { className: "gx-file-upload-area", children: [
207
+ /* @__PURE__ */ e(
208
+ "input",
209
+ {
210
+ type: "file",
211
+ multiple: r,
212
+ accept: s,
213
+ onChange: m,
214
+ disabled: o
215
+ }
216
+ ),
217
+ /* @__PURE__ */ l(
218
+ "svg",
219
+ {
220
+ className: "gx-file-upload-icon",
221
+ viewBox: "0 0 24 24",
222
+ fill: "none",
223
+ stroke: "currentColor",
224
+ strokeWidth: "1.5",
225
+ strokeLinecap: "round",
226
+ strokeLinejoin: "round",
227
+ "aria-hidden": "true",
228
+ children: [
229
+ /* @__PURE__ */ e("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
230
+ /* @__PURE__ */ e("polyline", { points: "17 8 12 3 7 8" }),
231
+ /* @__PURE__ */ e("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
232
+ ]
233
+ }
234
+ ),
235
+ n.length === 0 ? /* @__PURE__ */ l(p, { children: [
236
+ /* @__PURE__ */ e("div", { className: "gx-file-upload-label", children: i }),
237
+ c && /* @__PURE__ */ e("div", { className: "gx-file-upload-hint", children: c })
238
+ ] }) : /* @__PURE__ */ l(p, { children: [
239
+ /* @__PURE__ */ l("div", { className: "gx-file-upload-label", children: [
240
+ n.length,
241
+ " file",
242
+ n.length !== 1 ? "s" : "",
243
+ " selected"
244
+ ] }),
245
+ /* @__PURE__ */ e("ul", { className: "gx-file-list", children: n.map((d) => /* @__PURE__ */ e("li", { children: d.name }, d.name)) })
246
+ ] })
247
+ ] }) });
248
+ }
249
+ function _({ value: n, label: a }) {
250
+ const o = Math.min(100, Math.max(0, n));
251
+ return /* @__PURE__ */ l("div", { className: "gx-progress-wrap", children: [
252
+ /* @__PURE__ */ e(
253
+ "div",
254
+ {
255
+ className: "progress-bg",
256
+ role: "progressbar",
257
+ "aria-valuenow": Math.round(o),
258
+ "aria-valuemin": 0,
259
+ "aria-valuemax": 100,
260
+ children: /* @__PURE__ */ e("div", { className: "progress-bar", style: { width: `${o}%` } })
261
+ }
262
+ ),
263
+ a && /* @__PURE__ */ e("p", { className: "gx-progress-label", children: a })
264
+ ] });
265
+ }
266
+ const A = "https://static.genomicx.org/wasm", u = /* @__PURE__ */ new Map();
267
+ async function $(n, a = A) {
268
+ const o = `${a}/${n}`;
269
+ if (u.has(o)) return u.get(o);
270
+ const [r, s] = await Promise.all([
186
271
  fetch(`${a}/${n}.js`),
187
272
  fetch(`${a}/${n}.wasm`)
188
273
  ]);
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;
274
+ if (!r.ok) throw new Error(`Failed to fetch ${n}.js: ${r.status}`);
275
+ if (!s.ok) throw new Error(`Failed to fetch ${n}.wasm: ${s.status}`);
276
+ const [i, c] = await Promise.all([
277
+ r.text(),
278
+ s.arrayBuffer()
279
+ ]), m = { factory: new Function("Module", i + "; return Module;")({}), wasmBinary: c };
280
+ return u.set(o, m), m;
196
281
  }
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),
282
+ async function S(n, a) {
283
+ const { factory: o, wasmBinary: r } = await $(n, a), s = [], i = [], c = await o({
284
+ wasmBinary: r.slice(0),
285
+ print: (t) => s.push(t),
286
+ printErr: (t) => i.push(t),
202
287
  noInitialRun: !0
203
288
  });
204
- return s._stdout = c, s._stderr = r, s;
289
+ return c._stdout = s, c._stderr = i, c;
205
290
  }
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);
291
+ function k(n, a) {
292
+ const o = URL.createObjectURL(n), r = document.createElement("a");
293
+ r.href = o, r.download = a, r.click(), URL.revokeObjectURL(o);
209
294
  }
210
- function R(n, a, l = "text/plain") {
211
- x(new Blob([n], { type: l }), a);
295
+ function E(n, a, o = "text/plain") {
296
+ k(new Blob([n], { type: o }), a);
212
297
  }
213
- function W(n, a) {
214
- x(new Blob([n]), a);
298
+ function I(n, a) {
299
+ k(new Blob([n]), a);
215
300
  }
216
301
  export {
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,
224
- x as downloadBlob,
225
- W as downloadBuffer,
226
- R as downloadText,
227
- C as loadWasmModule
302
+ L as AppFooter,
303
+ W as AppShell,
304
+ C as BugReportModal,
305
+ R as FileUpload,
306
+ H as LogConsole,
307
+ M as NavBar,
308
+ _ as ProgressBar,
309
+ N as ThemeToggle,
310
+ S as createModuleInstance,
311
+ k as downloadBlob,
312
+ I as downloadBuffer,
313
+ E as downloadText,
314
+ $ as loadWasmModule
228
315
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genomicx/ui",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Shared UI components, styles, and WASM loader for GenomicX tools",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -517,3 +517,76 @@ code {
517
517
  margin: 0;
518
518
  }
519
519
  .gx-modal-github-hint a { color: var(--gx-accent); text-decoration: underline; }
520
+
521
+ /* ── FileUpload ──────────────────────────────── */
522
+ .gx-file-upload {
523
+ width: 100%;
524
+ }
525
+
526
+ .gx-file-upload-area {
527
+ display: flex;
528
+ flex-direction: column;
529
+ align-items: center;
530
+ justify-content: center;
531
+ min-height: 140px;
532
+ border: 2px dashed var(--gx-border);
533
+ border-radius: var(--gx-radius-lg);
534
+ padding: 1.5rem;
535
+ text-align: center;
536
+ cursor: pointer;
537
+ transition: border-color 0.2s, background 0.2s;
538
+ background: var(--gx-bg-alt);
539
+ }
540
+
541
+ .gx-file-upload-area:hover {
542
+ border-color: var(--gx-accent);
543
+ background: var(--gx-accent-dim);
544
+ }
545
+
546
+ .gx-file-upload-area input[type="file"] {
547
+ display: none;
548
+ }
549
+
550
+ .gx-file-upload-icon {
551
+ width: 32px;
552
+ height: 32px;
553
+ margin-bottom: 0.75rem;
554
+ color: var(--gx-accent);
555
+ opacity: 0.8;
556
+ }
557
+
558
+ .gx-file-upload-label {
559
+ font-size: 0.95rem;
560
+ font-weight: 500;
561
+ color: var(--gx-text);
562
+ margin-bottom: 0.25rem;
563
+ }
564
+
565
+ .gx-file-upload-hint {
566
+ font-size: 0.8rem;
567
+ color: var(--gx-text-muted);
568
+ }
569
+
570
+ .gx-file-list {
571
+ list-style: none;
572
+ margin: 0.5rem 0 0;
573
+ padding: 0;
574
+ display: flex;
575
+ flex-direction: column;
576
+ gap: 0.2rem;
577
+ font-size: 0.85rem;
578
+ color: var(--gx-text-muted);
579
+ max-height: 120px;
580
+ overflow-y: auto;
581
+ }
582
+
583
+ /* ── ProgressBar ─────────────────────────────── */
584
+ .gx-progress-wrap {
585
+ width: 100%;
586
+ }
587
+
588
+ .gx-progress-label {
589
+ margin: 0.5rem 0 0;
590
+ font-size: 0.85rem;
591
+ color: var(--gx-text-muted);
592
+ }