aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-07-06 08:09:57 -0700
committerAdam Soltys <[email protected]>2026-07-06 08:09:57 -0700
commit45a4a9d5cec8eafc7e98ebd7ca2a9790c0ee94b4 (patch)
treef50350fc32126bb93a42a733b24da32fc131f205 /src
parent60f267c5c8b240dbb80f6d567cf2801579b85585 (diff)
downloadcoinosv3-45a4a9d5cec8eafc7e98ebd7ca2a9790c0ee94b4.tar.gz
coinosv3-45a4a9d5cec8eafc7e98ebd7ca2a9790c0ee94b4.zip
Icons in the receive selector and on silent-payment history rows
The receive address-type picker was a native <select>, which can't render SVG in <option>s. Replace it with a lightweight custom dropdown (icon-select) that shows a glyph per rail — ₿ Bitcoin, ⚡ Lightning, the Second mark for Ark, 🕶️ Silent payment — closing on select or an outside tap. Receive modes gained an icon field; the address option is built in app.js. Silent-payment receipts (which merge into history as on-chain-looking rows) now get a rail glyph + SP tag via a decorateTxRow so they're distinguishable, matching how ark/gift rows already decorate. Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/app.js39
-rw-r--r--src/features/ark.js2
-rw-r--r--src/features/sp.js9
-rw-r--r--src/features/swaps.js2
-rw-r--r--src/style.css21
5 files changed, 67 insertions, 6 deletions
diff --git a/src/app.js b/src/app.js
index d4cf928..74fcb82 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1985,6 +1985,37 @@ function hasRecentIncoming() {
}
// ---------------------------------------------------------------- Receive
+// A dropdown that shows an icon beside each option (a native <select> can't
+// render SVG). opts: [{id,label,icon}] where icon is an emoji or an <svg>
+// string. Closes on select or on a tap outside (transparent backdrop).
+function iconGlyph(ic) {
+ if (!ic) return null;
+ return String(ic).trim().startsWith('<')
+ ? h('span', { class: 'sel-ico', html: ic })
+ : h('span', { class: 'sel-ico' }, ic);
+}
+function iconSelect(opts, current, onSelect) {
+ const cur = opts.find((o) => o.id === current) || opts[0];
+ return h('div', { class: 'icon-select', style: 'position:relative;width:100%' },
+ h('button', {
+ type: 'button', class: 'icon-select-btn',
+ onClick: () => { ui.selectOpen = ui.selectOpen === 'receive' ? null : 'receive'; render(); },
+ },
+ iconGlyph(cur.icon),
+ h('span', { class: 'grow', style: 'text-align:left' }, cur.label),
+ h('span', { class: 'muted', style: 'font-size:12px' }, '▾')),
+ ui.selectOpen === 'receive'
+ ? h('div', {},
+ h('div', { style: 'position:fixed;inset:0;z-index:20', onClick: () => { ui.selectOpen = null; render(); } }),
+ h('div', { class: 'icon-select-menu', style: 'position:absolute;left:0;right:0;top:calc(100% + 4px);z-index:21' },
+ opts.map((o) => h('button', {
+ type: 'button', class: 'icon-select-item' + (o.id === current ? ' active' : ''),
+ onClick: () => { ui.selectOpen = null; onSelect(o.id); },
+ }, iconGlyph(o.icon), h('span', { class: 'grow', style: 'text-align:left' }, o.label)))))
+ : null
+ );
+}
+
function receiveTab() {
// Feature takeovers: boarding success, Ark receive celebration, etc.
const takeover = featureHook('receiveTakeover');
@@ -2025,10 +2056,12 @@ function receiveTab() {
const featModes = featureAll('receiveModes');
let mode = ui.receiveType || 'address';
if (mode !== 'address' && !featModes.some((m) => m.id === mode)) mode = 'address';
- const opts = [['address', t('receiveAddressTab')], ...featModes.map((m) => [m.id, m.label])];
+ const opts = [
+ { id: 'address', label: t('receiveAddressTab'), icon: '₿' },
+ ...featModes.map((m) => ({ id: m.id, label: m.label, icon: m.icon })),
+ ];
const seg = opts.length > 1
- ? h('select', { style: 'width:100%', onChange: (e) => { ui.receiveType = e.target.value; render(); } },
- opts.map(([id, label]) => h('option', { value: id, selected: mode === id }, label)))
+ ? iconSelect(opts, mode, (id) => { ui.receiveType = id; render(); })
: null;
const fm = featModes.find((m) => m.id === mode);
if (fm) return fm.render(seg);
diff --git a/src/features/ark.js b/src/features/ark.js
index 6cafe35..5f64b21 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -979,7 +979,7 @@ export function arkFeature(ctx) {
stop() { stopArk(); },
receiveModes() {
if (!arkAvailable()) return [];
- return [{ id: 'ark', label: t('receiveArkTab'), render: (seg) => arkReceivePane(seg) }];
+ return [{ id: 'ark', label: t('receiveArkTab'), icon: ARK_MARK(18), render: (seg) => arkReceivePane(seg) }];
},
receiveTakeover() {
const offboarded = arkOffboardedScreen();
diff --git a/src/features/sp.js b/src/features/sp.js
index 5c55457..92f091f 100644
--- a/src/features/sp.js
+++ b/src/features/sp.js
@@ -42,7 +42,7 @@ export function spFeature(ctx) {
const spAddr = wallet.silentPaymentsAvailable && wallet.silentPaymentsAvailable() ? wallet.silentPaymentAddress() : null;
if (!spAddr) return [];
return [{
- id: 'sp', label: t('receiveSpTab'),
+ id: 'sp', label: t('receiveSpTab'), icon: '🕶️',
render: (seg) => h(
'div',
{ class: 'card col', style: 'align-items:center;gap:14px' },
@@ -54,6 +54,13 @@ export function spFeature(ctx) {
}];
},
isSendDest(a) { return isSilentPaymentAddress(a); },
+ // Silent-payment receipts merge into the main history as on-chain-looking
+ // rows (tx.sp) — give them a rail glyph + tag so they're recognizable.
+ decorateTxRow(tx) {
+ return tx && tx.sp
+ ? { icon: '🕶️', label: h('span', {}, t('received'), ' ', h('span', { class: 'tag' }, t('receiveSpTab'))) }
+ : null;
+ },
settingsCards() { return [spIndexerCard()]; },
};
}
diff --git a/src/features/swaps.js b/src/features/swaps.js
index b41b87a..2ff43ed 100644
--- a/src/features/swaps.js
+++ b/src/features/swaps.js
@@ -186,7 +186,7 @@ export function swapsFeature(ctx) {
// a reverse swap needs our keys to claim on-chain
if (wallet.watchOnly) return [];
return [{
- id: 'ln', label: t('receiveLnTab'),
+ id: 'ln', label: t('receiveLnTab'), icon: '⚡',
render: (seg) => h('div', { class: 'card col', style: 'align-items:center;gap:14px' }, seg, ...lnReceiveContent()),
}];
},
diff --git a/src/style.css b/src/style.css
index 545891c..4fe0394 100644
--- a/src/style.css
+++ b/src/style.css
@@ -289,3 +289,24 @@ hr.divider { margin: 16px 0; }
.words { grid-template-columns: repeat(2, 1fr); }
.balance .amt { font-size: 28px; }
}
+
+/* Icon dropdown (receive mode) — a <select> can't render SVG in options. */
+.icon-select-btn {
+ width: 100%; display: flex; align-items: center; gap: 9px;
+ font: inherit; color: var(--ink); background: var(--paper);
+ border: 1px solid var(--line-strong); border-radius: 11px; padding: 11px 13px; cursor: pointer;
+}
+.icon-select-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
+.icon-select-menu {
+ background: var(--paper); border: 1px solid var(--line-strong); border-radius: 11px;
+ padding: 5px; box-shadow: 0 10px 28px rgba(0,0,0,.18); display: flex; flex-direction: column; gap: 2px;
+}
+.icon-select-item {
+ display: flex; align-items: center; gap: 9px; width: 100%;
+ font: inherit; color: var(--ink); background: transparent;
+ border: 0; border-radius: 8px; padding: 10px 10px; cursor: pointer; text-align: left;
+}
+.icon-select-item:hover { background: var(--hover, rgba(128,128,128,.12)); }
+.icon-select-item.active { background: var(--accent-soft, rgba(247,147,26,.14)); }
+.sel-ico { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; flex-shrink: 0; font-size: 16px; line-height: 1; }
+.sel-ico svg { display: block; }