@genomicx/ui 0.5.0 → 0.7.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.
@@ -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;
@@ -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 w, 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 = (t) => {
9
+ o(t), document.documentElement.setAttribute("data-theme", t), localStorage.setItem("gx-theme", t);
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, actions: t, mobileActions: i }) {
40
+ const [s, c] = 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
+ t,
58
+ /* @__PURE__ */ e(g, { to: "/about", className: "gx-nav-link", children: "About" }),
59
+ /* @__PURE__ */ l("a", { href: "https://github.com/happykhan", 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: () => c(!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" }) }) })
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" }),
70
+ s && /* @__PURE__ */ l("div", { className: "gx-nav-dropdown", children: [
71
+ i,
72
+ /* @__PURE__ */ e(g, { to: "/about", onClick: () => c(!1), className: "gx-nav-dropdown-link", children: "About" }),
73
73
  /* @__PURE__ */ e("a", { href: "https://github.com/happykhan", 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 t = 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: t.map((i, s) => /* @__PURE__ */ e("li", { children: i }, s)) })
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: t }) {
107
+ const [i, s] = f(!1);
108
+ function c() {
109
+ a ? s(!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: c, 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
128
  onClose: () => s(!1),
129
129
  bugReportEmail: a,
130
- bugReportUrl: l,
131
- bugReportItems: c
130
+ bugReportUrl: o,
131
+ bugReportItems: t
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 = w(null), t = () => {
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: t, 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((s, c) => /* @__PURE__ */ e("div", { className: "gx-console-line", children: s }, c)) })
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: t = ".fasta,.fa,.fna,.fsa,.fasta.gz,.fa.gz,.fna.gz",
187
+ label: i = "Drop files here or click to browse",
188
+ hint: s,
189
+ filterFn: c
190
+ }) {
191
+ const m = x(
192
+ (d) => {
193
+ if (!d.target.files) return;
194
+ let h = Array.from(d.target.files);
195
+ c && (h = h.filter(c)), h.length > 0 && a(h);
196
+ },
197
+ [a, c]
198
+ ), b = x(
199
+ (d) => {
200
+ if (d.preventDefault(), !d.dataTransfer.files) return;
201
+ let h = Array.from(d.dataTransfer.files);
202
+ c && (h = h.filter(c)), h.length > 0 && a(h);
203
+ },
204
+ [a, c]
205
+ );
206
+ return /* @__PURE__ */ e("div", { className: "gx-file-upload", onDrop: b, 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: t,
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
+ s && /* @__PURE__ */ e("div", { className: "gx-file-upload-hint", children: s })
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, t] = 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 (!t.ok) throw new Error(`Failed to fetch ${n}.wasm: ${t.status}`);
276
+ const [i, s] = await Promise.all([
277
+ r.text(),
278
+ t.arrayBuffer()
279
+ ]), m = { factory: new Function("Module", i + "; return Module;")({}), wasmBinary: s };
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), t = [], i = [], s = await o({
284
+ wasmBinary: r.slice(0),
285
+ print: (c) => t.push(c),
286
+ printErr: (c) => i.push(c),
202
287
  noInitialRun: !0
203
288
  });
204
- return s._stdout = c, s._stderr = r, s;
289
+ return s._stdout = t, s._stderr = i, s;
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.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Shared UI components, styles, and WASM loader for GenomicX tools",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -357,17 +357,44 @@ code {
357
357
  }
358
358
  .btn-secondary:hover { border-color: var(--gx-accent); color: var(--gx-accent); }
359
359
 
360
- .input-field {
360
+ .input-field,
361
+ .gx-input,
362
+ .gx-select {
361
363
  background: var(--gx-bg);
362
364
  border: 1px solid var(--gx-border);
363
365
  color: var(--gx-text);
364
366
  border-radius: 6px;
365
367
  padding: 0.5rem 0.75rem;
366
368
  font-size: 0.875rem;
369
+ font-family: inherit;
367
370
  transition: border-color var(--gx-transition);
368
371
  outline: none;
369
372
  }
370
- .input-field:focus { border-color: var(--gx-accent); }
373
+ .input-field:focus,
374
+ .gx-input:focus,
375
+ .gx-select:focus { border-color: var(--gx-accent); box-shadow: 0 0 0 3px var(--gx-accent-dim); }
376
+
377
+ /* ── gx-btn variants ─────────────────────────── */
378
+ .gx-btn {
379
+ display: inline-flex;
380
+ align-items: center;
381
+ justify-content: center;
382
+ gap: 0.375rem;
383
+ font-family: inherit;
384
+ font-weight: 600;
385
+ font-size: 0.875rem;
386
+ padding: 0.6rem 1.25rem;
387
+ border-radius: 6px;
388
+ border: none;
389
+ cursor: pointer;
390
+ transition: all var(--gx-transition);
391
+ }
392
+ .gx-btn:disabled { opacity: 0.5; cursor: not-allowed; }
393
+ .gx-btn-sm { padding: 0.375rem 0.875rem; font-size: 0.8125rem; }
394
+ .gx-btn-primary { background: var(--gx-accent); color: var(--gx-text-inverted); }
395
+ .gx-btn-primary:hover:not(:disabled) { background: var(--gx-accent-hover); }
396
+ .gx-btn-secondary { background: transparent; color: var(--gx-text); border: 1px solid var(--gx-border); }
397
+ .gx-btn-secondary:hover:not(:disabled) { border-color: var(--gx-accent); color: var(--gx-accent); }
371
398
 
372
399
  .label {
373
400
  display: block;
@@ -490,3 +517,76 @@ code {
490
517
  margin: 0;
491
518
  }
492
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
+ }
@@ -16,6 +16,7 @@
16
16
  --gx-success: #0d9488;
17
17
  --gx-warning: #F59E0B;
18
18
  --gx-error: #EF4444;
19
+ --gx-accent-dim: rgba(13, 148, 136, 0.1);
19
20
  --gx-nav-bg: rgba(255, 255, 255, 0.9);
20
21
  --gx-code-bg: #f1f5f9;
21
22
  --gx-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
@@ -23,6 +24,7 @@
23
24
  --gx-max-width: 1280px;
24
25
  --gx-radius: 8px;
25
26
  --gx-radius-lg: 12px;
27
+ --gx-font-mono: 'JetBrains Mono', 'Fira Code', monospace;
26
28
  }
27
29
 
28
30
  [data-theme="dark"] {
@@ -39,6 +41,7 @@
39
41
  --gx-accent: #2dd4bf;
40
42
  --gx-accent-hover: #14b8a6;
41
43
  --gx-indigo: #818cf8;
44
+ --gx-accent-dim: rgba(45, 212, 191, 0.12);
42
45
  --gx-nav-bg: rgba(15, 23, 42, 0.9);
43
46
  --gx-code-bg: #0f172a;
44
47
  --gx-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);