Skip to content

Commit a67db75

Browse files
authored
feat: add account links and responsive layout (#17)
1 parent e4c8ddb commit a67db75

10 files changed

Lines changed: 329 additions & 82 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## 1.4.2 - 2026-07-16
4+
5+
- Moved copy controls out of the minter table and into the account detail view,
6+
with separate name and address copy actions.
7+
- Made account names on My Minting, Blocks, and online-account tables open the
8+
same minter detail view.
9+
- Replaced fixed centered page widths with responsive full-window layouts;
10+
Modern keeps a wider gutter than Classic and Fun.
11+
312
## 1.4.1 - 2026-07-16
413

514
- Reworked the app into My Minting, Minters, Blocks, and Developers workspaces.

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,16 @@ minting-key removal for development; otherwise browser mode is read-only. Set
3939

4040
## QAVS and UI styles
4141

42-
The app is at QAVS `1.4.1`: the `1.4` portion is its minimum Qortium platform
42+
The app is at QAVS `1.4.2`: the `1.4` portion is its minimum Qortium platform
4343
level and the patch number tracks the app release. `vite.config.ts` reads the
4444
package version, injects the visible version badge, and emits
4545
`dist/qortium-app.json` with the name `Minting` during every build.
4646

4747
Minting supports Classic, Modern, and Fun QDN UI styles and follows Home theme,
4848
accent, language, and text-size settings. All fonts are bundled locally; UI
49-
families intentionally preserve the accent selected in Home.
49+
families intentionally preserve the accent selected in Home. The workspace uses
50+
the full available app window with responsive gutters; Modern keeps the roomiest
51+
outer gutter while Classic and Fun stay tighter.
5052

5153
## Development and verification
5254

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "qortium-minting",
3-
"version": "1.4.1",
3+
"version": "1.4.2",
44
"private": true,
55
"license": "0BSD",
66
"description": "A small QDN minting status app for Qortium Home.",

src/App.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,15 @@ export default function App() {
740740
}
741741
}
742742

743+
function openAccount(address: string) {
744+
setSelectedMinter(address);
745+
setTab('minters');
746+
747+
if (!minters.value.length && !minters.loading) {
748+
void loadMinters();
749+
}
750+
}
751+
743752
const writeAvailable = !bridge.value.isUsingPublicNode;
744753
const canJoin = writeAvailable
745754
&& !!account
@@ -823,6 +832,7 @@ export default function App() {
823832
() => joinGroup(MINTING_GROUP_ID, actions),
824833
);
825834
}}
835+
onOpenAccount={openAccount}
826836
onRemove={(item) => {
827837
if (!item.publicKey) return;
828838
void submitWrite(
@@ -874,6 +884,7 @@ export default function App() {
874884
now={now}
875885
onlineAccounts={onlineByHeight}
876886
onlineNow={onlineNow}
887+
onOpenAccount={openAccount}
877888
onShowMore={() => {
878889
const next = Math.min(MAX_BLOCK_COUNT, blockCount + BLOCK_COUNT_STEP);
879890
setBlockCount(next);

src/styles.css

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,3 +782,145 @@ td { overflow-wrap: anywhere; }
782782
width: 100%;
783783
}
784784
}
785+
786+
/* Responsive page width and account navigation. Modern keeps a roomier outer
787+
gutter, while Classic and Fun use more of the available app window. */
788+
html {
789+
scrollbar-gutter: stable;
790+
}
791+
792+
.app-shell {
793+
margin-inline: 0;
794+
max-width: none;
795+
padding: clamp(10px, 1.25vw, 20px);
796+
width: 100%;
797+
}
798+
799+
:root[data-ui='modern'] .app-shell {
800+
max-width: none;
801+
padding-block: clamp(18px, 2.4vw, 36px);
802+
padding-inline: clamp(16px, 3vw, 48px);
803+
}
804+
805+
:root[data-ui='fun'] .app-shell {
806+
max-width: none;
807+
padding: clamp(10px, 1.5vw, 24px);
808+
}
809+
810+
.account-link,
811+
.block-toggle {
812+
background: transparent;
813+
border: 0;
814+
box-shadow: none;
815+
color: var(--qm-text-primary);
816+
min-height: auto;
817+
padding: 0;
818+
}
819+
820+
.account-link {
821+
align-items: center;
822+
border-radius: var(--qm-radius);
823+
display: inline-flex;
824+
gap: calc(8px * var(--qm-text-scale));
825+
justify-content: flex-start;
826+
max-width: 100%;
827+
min-width: 0;
828+
text-align: start;
829+
}
830+
831+
.account-link > span:last-child {
832+
overflow: hidden;
833+
text-overflow: ellipsis;
834+
white-space: nowrap;
835+
}
836+
837+
.account-link:hover:not(:disabled),
838+
.block-toggle:hover:not(:disabled) {
839+
background: var(--qm-accent-soft);
840+
color: var(--qm-text-primary);
841+
}
842+
843+
.account-link:focus-visible,
844+
.block-toggle:focus-visible {
845+
outline: 2px solid var(--qm-accent);
846+
outline-offset: 3px;
847+
}
848+
849+
.table-wrap tbody tr {
850+
cursor: default;
851+
}
852+
853+
.table-wrap tbody tr:has(.account-link):hover {
854+
background: var(--qm-surface-alt);
855+
}
856+
857+
.block-row__summary {
858+
align-items: center;
859+
min-height: 2.45em;
860+
padding: 0.55em 0.8em;
861+
}
862+
863+
.block-toggle {
864+
align-items: center;
865+
display: inline-flex;
866+
gap: 0.4em;
867+
justify-content: flex-start;
868+
}
869+
870+
.block-minter {
871+
width: fit-content;
872+
}
873+
874+
.account-detail {
875+
align-items: center;
876+
display: flex;
877+
gap: clamp(12px, 2vw, 20px);
878+
min-width: 0;
879+
}
880+
881+
.account-detail__text {
882+
display: grid;
883+
gap: 0.5em;
884+
min-width: 0;
885+
}
886+
887+
.account-detail__line {
888+
align-items: center;
889+
display: flex;
890+
flex-wrap: wrap;
891+
gap: 0.6em;
892+
min-width: 0;
893+
}
894+
895+
.account-detail__address p {
896+
overflow-wrap: anywhere;
897+
}
898+
899+
.selected-account {
900+
align-items: flex-start;
901+
flex-direction: column;
902+
}
903+
904+
:root[data-ui='fun'] .account-link,
905+
:root[data-ui='fun'] .block-toggle {
906+
border: 0;
907+
box-shadow: none;
908+
}
909+
910+
:root[data-ui='fun'] .account-link:hover:not(:disabled),
911+
:root[data-ui='fun'] .block-toggle:hover:not(:disabled) {
912+
box-shadow: none;
913+
transform: none;
914+
}
915+
916+
@media (max-width: 560px) {
917+
.account-detail {
918+
align-items: flex-start;
919+
flex-direction: column;
920+
}
921+
922+
.account-detail__line {
923+
align-items: flex-start;
924+
flex-direction: column;
925+
}
926+
}

src/ui.tsx

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,14 @@ export function Avatar({
4444
);
4545
}
4646

47-
export function CopyAddressButton({
48-
address,
49-
label = 'Copy address',
47+
export function CopyTextButton({
48+
label,
49+
text,
50+
textLabel = 'Copy',
5051
}: {
51-
address: string;
52-
label?: string;
52+
label: string;
53+
text: string;
54+
textLabel?: string;
5355
}) {
5456
const [status, setStatus] = useState<'copied' | 'error' | 'idle'>('idle');
5557
const timer = useRef(0);
@@ -59,7 +61,7 @@ export function CopyAddressButton({
5961
event.stopPropagation();
6062
window.clearTimeout(timer.current);
6163

62-
setStatus(await copyTextToClipboard(address) ? 'copied' : 'error');
64+
setStatus(await copyTextToClipboard(text) ? 'copied' : 'error');
6365

6466
timer.current = window.setTimeout(() => setStatus('idle'), 1_800);
6567
}
@@ -72,28 +74,52 @@ export function CopyAddressButton({
7274
type="button"
7375
>
7476
<Copy size={14} />
75-
{status === 'copied' ? 'Copied' : status === 'error' ? 'Copy failed' : 'Copy'}
77+
{status === 'copied' ? 'Copied' : status === 'error' ? 'Copy failed' : textLabel}
7678
</button>
7779
);
7880
}
7981

80-
export function Identity({
82+
export function CopyAddressButton({
83+
address,
84+
label = 'Copy address',
85+
textLabel = 'Copy address',
86+
}: {
87+
address: string;
88+
label?: string;
89+
textLabel?: string;
90+
}) {
91+
return <CopyTextButton label={label} text={address} textLabel={textLabel} />;
92+
}
93+
94+
export function AccountLink({
8195
address,
8296
avatarSrc,
97+
className = '',
8398
name,
99+
onOpen,
84100
}: {
85101
address: string;
86102
avatarSrc: string | null;
103+
className?: string;
87104
name: string | null;
105+
onOpen: (address: string) => void;
88106
}) {
89107
const label = name ?? `${address.slice(0, 8)}${address.slice(-6)}`;
90108

91109
return (
92-
<span className="identity" title={address}>
110+
<button
111+
aria-label={`View ${name ?? address}`}
112+
className={`account-link ${className}`}
113+
onClick={(event) => {
114+
event.stopPropagation();
115+
onOpen(address);
116+
}}
117+
title={address}
118+
type="button"
119+
>
93120
<Avatar className="identity__avatar" name={name} src={avatarSrc} />
94121
<span className={name ? '' : 'mono'}>{label}</span>
95-
<CopyAddressButton address={address} label={`Copy ${label} address`} />
96-
</span>
122+
</button>
97123
);
98124
}
99125

0 commit comments

Comments
 (0)