nimiq-branding-cli 1.1.1 → 1.2.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.
- package/.audit/report.json +71 -0
- package/.audit/report.md +33 -0
- package/AUDIT.md +60 -0
- package/LINT.md +158 -0
- package/README.md +47 -4
- package/align/canonical.json +44 -0
- package/audit/learnings.json +51 -0
- package/bin/nq.js +88 -6
- package/hooks/ci-additions.md +53 -0
- package/hooks/pre-commit +10 -0
- package/hooks/session-start.sh +3 -0
- package/hooks/stack-align.yml +75 -0
- package/package.json +4 -4
- package/registry/SKILL-BLOCK.md +20 -0
- package/registry/components/account-header/meta.json +11 -1
- package/registry/components/account-list/meta.json +9 -1
- package/registry/components/account-ring/meta.json +9 -1
- package/registry/components/address-display/html/address-display.css +17 -13
- package/registry/components/address-display/html/demo.html +8 -10
- package/registry/components/address-display/meta.json +37 -7
- package/registry/components/address-display/vue/AddressDisplay.vue +11 -9
- package/registry/components/address-input/meta.json +53 -10
- package/registry/components/amount/meta.json +60 -11
- package/registry/components/amount-input/meta.json +55 -10
- package/registry/components/amount-with-fee/meta.json +58 -11
- package/registry/components/app-showcase-card/meta.json +17 -4
- package/registry/components/backup-banner/meta.json +29 -5
- package/registry/components/balance-distribution/meta.json +25 -5
- package/registry/components/buttons/meta.json +38 -7
- package/registry/components/card/meta.json +20 -4
- package/registry/components/close-button/meta.json +19 -4
- package/registry/components/consensus-icon/meta.json +26 -5
- package/registry/components/copyable/meta.json +25 -5
- package/registry/components/fiat-amount/meta.json +49 -9
- package/registry/components/flag-hex/html/demo.html +55 -0
- package/registry/components/flag-hex/html/flag-hex.css +21 -0
- package/registry/components/flag-hex/html/flag-hex.html +38 -0
- package/registry/components/flag-hex/meta.json +43 -0
- package/registry/components/flag-hex/vue/FlagHex.vue +98 -0
- package/registry/components/hero-section/meta.json +9 -4
- package/registry/components/honeycomb-band/meta.json +47 -9
- package/registry/components/identicon/meta.json +8 -1
- package/registry/components/label-input/meta.json +49 -9
- package/registry/components/loading-spinner/meta.json +19 -4
- package/registry/components/page-body/meta.json +23 -5
- package/registry/components/page-footer/meta.json +22 -5
- package/registry/components/page-header/meta.json +43 -8
- package/registry/components/payment-info-line/meta.json +10 -1
- package/registry/components/price-chart/meta.json +67 -11
- package/registry/components/qr-code/meta.json +22 -5
- package/registry/components/search-bar/meta.json +25 -5
- package/registry/components/select-bar/meta.json +37 -7
- package/registry/components/slider-toggle/meta.json +45 -8
- package/registry/components/small-page/meta.json +19 -4
- package/registry/components/status-alert/meta.json +37 -7
- package/registry/components/status-screen/meta.json +70 -13
- package/registry/components/swap-balance-bar/meta.json +8 -1
- package/registry/components/timer/meta.json +54 -10
- package/registry/components/toast-notification/meta.json +49 -9
- package/registry/components/tooltip/meta.json +69 -13
- package/registry/components/transaction-list/meta.json +9 -1
- package/registry/index.json +10 -0
- package/schemas/nimiq-stack.v1.json +103 -0
- package/scripts/align.mjs +435 -0
- package/scripts/apply-safe-bump.mjs +47 -0
- package/scripts/audit.mjs +247 -0
- package/scripts/bootstrap-provenance.mjs +126 -0
- package/scripts/hooks.mjs +152 -0
- package/scripts/lint.mjs +333 -0
- package/scripts/scaffold.mjs +391 -0
- package/scripts/sync-skill.mjs +84 -0
- package/test/align.test.mjs +166 -0
- package/test/scaffold.test.mjs +103 -0
- package/upstream-pins.json +35 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# CI additions for nq align / new / hooks
|
|
2
|
+
|
|
3
|
+
The OAuth token used to open this PR lacks the `workflow` scope, so `.github/workflows`
|
|
4
|
+
could not be modified from this branch. A maintainer with `workflow` scope should apply
|
|
5
|
+
the two changes below (they are otherwise ready and locally verified).
|
|
6
|
+
|
|
7
|
+
## 1. `.github/workflows/verify.yml` — extend `cli-smoke` + add a `unit-tests` job
|
|
8
|
+
|
|
9
|
+
Append to the end of the `cli-smoke` job's run step:
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
- name: nq new / align / hooks smoke
|
|
13
|
+
run: |
|
|
14
|
+
cd /tmp
|
|
15
|
+
node "$GITHUB_WORKSPACE/bin/nq.js" new smoke-app
|
|
16
|
+
test -f smoke-app/nimiq-stack.json
|
|
17
|
+
test -f smoke-app/src/server.ts
|
|
18
|
+
test -f smoke-app/Dockerfile
|
|
19
|
+
# a freshly-scaffolded canonical app must align CLEAN (exit 0)
|
|
20
|
+
node "$GITHUB_WORKSPACE/bin/nq.js" align smoke-app
|
|
21
|
+
# the settlement gate must FAIL on a real light-client path (exit 1)
|
|
22
|
+
printf "const c = Client.create(cfg);\n" > smoke-app/src/bad.ts
|
|
23
|
+
if node "$GITHUB_WORKSPACE/bin/nq.js" align smoke-app --fail-on=settlement; then
|
|
24
|
+
echo "ERROR: light-client path did not fail the settlement gate"; exit 1
|
|
25
|
+
fi
|
|
26
|
+
echo "align gate OK"
|
|
27
|
+
rm smoke-app/src/bad.ts
|
|
28
|
+
git -C smoke-app init -q
|
|
29
|
+
node "$GITHUB_WORKSPACE/bin/nq.js" hooks install smoke-app
|
|
30
|
+
test -x smoke-app/.git/hooks/pre-commit
|
|
31
|
+
echo "new/align/hooks smoke OK"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Add a new job:
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
unit-tests:
|
|
38
|
+
name: node --test (align / new / hooks)
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
timeout-minutes: 5
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
- uses: actions/setup-node@v4
|
|
44
|
+
with:
|
|
45
|
+
node-version: 20
|
|
46
|
+
- run: npm test
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 2. `.github/workflows/stack-align.yml` — the weekly fleet-alignment Action
|
|
50
|
+
|
|
51
|
+
Copy `hooks/stack-align.yml` (committed in this PR) to `.github/workflows/stack-align.yml`.
|
|
52
|
+
It mirrors `audit.yml`: `nq align --all` weekly, PR safe manifest fixes (`nq align --fix`)
|
|
53
|
+
on safe-drift, and a rolling `stack-drift` issue on risky-fail.
|
package/hooks/pre-commit
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# nq align pre-commit gate — installed by `nq hooks install`.
|
|
3
|
+
# Blocks a commit that introduces the broken @nimiq/core light-client path
|
|
4
|
+
# (Client.create / @nimiq/core/web / waitForConsensusEstablished) or off-brand styling.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
if command -v nq >/dev/null 2>&1; then
|
|
7
|
+
nq align --fail-on=settlement,styling
|
|
8
|
+
else
|
|
9
|
+
npx -y github:Andjroo111/nimiq-branding-cli align --fail-on=settlement,styling
|
|
10
|
+
fi
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: stack-align
|
|
2
|
+
|
|
3
|
+
# Weekly fleet stack-alignment audit — the sibling of branding-audit.yml. Runs
|
|
4
|
+
# `nq align --all` across the repo, PRs safe manifest fixes (nq align --fix), and
|
|
5
|
+
# files/updates a rolling stack-drift issue on any risky-fail (the broken light-client
|
|
6
|
+
# path, off-brand styling). Reuses the same verdict-driven shape as audit.yml.
|
|
7
|
+
on:
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: '30 9 * * 1' # Mondays 09:30 UTC (after branding-audit at 09:00)
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
pull-requests: write
|
|
15
|
+
issues: write
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: stack-align
|
|
19
|
+
cancel-in-progress: false
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
align:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
timeout-minutes: 15
|
|
25
|
+
env:
|
|
26
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
- uses: actions/setup-node@v4
|
|
30
|
+
with:
|
|
31
|
+
node-version: 20
|
|
32
|
+
|
|
33
|
+
- name: Run nq align
|
|
34
|
+
id: align
|
|
35
|
+
run: |
|
|
36
|
+
npx -y github:Andjroo111/nimiq-branding-cli align --all . --json > align.json || true
|
|
37
|
+
node -e "const r=require('./align.json').results.filter(x=>!x.exempt); const bad=r.filter(x=>x.overall==='risky-fail'); const drift=r.filter(x=>x.overall==='safe-drift'); console.log('risky='+bad.length); console.log('drift='+drift.length); process.stdout.write('');"
|
|
38
|
+
risky=$(node -e "console.log(require('./align.json').results.filter(x=>!x.exempt&&x.overall==='risky-fail').length)")
|
|
39
|
+
drift=$(node -e "console.log(require('./align.json').results.filter(x=>!x.exempt&&x.overall==='safe-drift').length)")
|
|
40
|
+
echo "risky=$risky" >> "$GITHUB_OUTPUT"
|
|
41
|
+
echo "drift=$drift" >> "$GITHUB_OUTPUT"
|
|
42
|
+
|
|
43
|
+
# ---- SAFE: only manifest drift → autofix the manifests + open a PR ----
|
|
44
|
+
- name: PR safe manifest fixes
|
|
45
|
+
if: steps.align.outputs.risky == '0' && steps.align.outputs.drift != '0'
|
|
46
|
+
run: |
|
|
47
|
+
npx -y github:Andjroo111/nimiq-branding-cli align --all . --fix || true
|
|
48
|
+
git config user.name "nq-align-bot"
|
|
49
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
50
|
+
branch="align/auto-manifest-fix"
|
|
51
|
+
git checkout -B "$branch"
|
|
52
|
+
git add -A '*nimiq-stack.json'
|
|
53
|
+
if git diff --cached --quiet; then echo "nothing to fix"; exit 0; fi
|
|
54
|
+
git commit -m "align: safe manifest fixes (nq align --fix)"
|
|
55
|
+
git push -f origin "$branch"
|
|
56
|
+
if [ "$(gh pr list --head "$branch" --state open --json number --jq 'length')" = "0" ]; then
|
|
57
|
+
gh pr create --head "$branch" --base main \
|
|
58
|
+
--title "align: safe manifest fixes" \
|
|
59
|
+
--body "Automated \`nq align --fix\` — manifest-only, no source changes."
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# ---- RISKY: a load-bearing axis failed (light client / off-brand) → rolling issue ----
|
|
63
|
+
- name: Open/update stack-drift issue
|
|
64
|
+
if: steps.align.outputs.risky != '0'
|
|
65
|
+
run: |
|
|
66
|
+
gh label create stack-drift --color B91C1C \
|
|
67
|
+
--description "an app drifted off the canonical Nimiq fleet stack" 2>/dev/null || true
|
|
68
|
+
body="$(npx -y github:Andjroo111/nimiq-branding-cli align --all . 2>&1 || true)"
|
|
69
|
+
existing=$(gh issue list --state open --label stack-drift --json number --jq '.[0].number')
|
|
70
|
+
if [ -n "$existing" ]; then
|
|
71
|
+
printf '%s\n' "$body" | gh issue comment "$existing" --body-file -
|
|
72
|
+
else
|
|
73
|
+
printf '%s\n' "$body" | gh issue create --title "⚙️ Nimiq stack drift needs review" \
|
|
74
|
+
--label stack-drift --body-file -
|
|
75
|
+
fi
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nimiq-branding-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "nq
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "nq — pixel-verified Nimiq UI component registry + CLI. 39 components (Vue 3 + plain HTML) diffed against the real Nimiq apps, plus the team's real asset library. Unofficial community tool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"nq": "./bin/nq.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"verify": "node scripts/verify.mjs",
|
|
11
|
-
"test": "node --test test
|
|
11
|
+
"test": "node --test test/*.test.mjs"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18"
|
|
@@ -37,4 +37,4 @@
|
|
|
37
37
|
"crypto",
|
|
38
38
|
"ui"
|
|
39
39
|
]
|
|
40
|
-
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!-- Generated by nq sync-skill. The nimiq-ui SKILL.md mirrors these two blocks. -->
|
|
2
|
+
|
|
3
|
+
## summary block
|
|
4
|
+
<!-- AUTO-GENERATED by `nq sync-skill` from registry/index.json — do not hand-edit -->
|
|
5
|
+
The **nimiq-branding-cli** at `~/Projects/nimiq/nimiq-branding-cli` ships **40 components** ported
|
|
6
|
+
from Nimiq's own Vue source, each **pixel-diffed against a verbatim upstream render** — **39 pixel-verified**,
|
|
7
|
+
plus 1 original brand composition (`flag-hex`). If the registry has it, copying it in is strictly better than writing it.
|
|
8
|
+
|
|
9
|
+
## registry block
|
|
10
|
+
<!-- AUTO-GENERATED by `nq sync-skill` from registry/index.json — do not hand-edit -->
|
|
11
|
+
Registry (40 components, 39 pixel-verified) ·
|
|
12
|
+
**data-display** balance-distribution, copyable, flag-hex*, price-chart, qr-code, tooltip ·
|
|
13
|
+
**feedback** backup-banner, consensus-icon, loading-spinner, status-alert, status-screen, timer, toast-notification ·
|
|
14
|
+
**form** address-input, amount-input, buttons, close-button, label-input, search-bar, select-bar, slider-toggle, swap-balance-bar ·
|
|
15
|
+
**identity** account-list, account-ring, address-display, identicon ·
|
|
16
|
+
**layout** card, page-body, page-footer, page-header, small-page ·
|
|
17
|
+
**marketing** app-showcase-card, hero-section, honeycomb-band ·
|
|
18
|
+
**payment** amount, amount-with-fee, fiat-amount, payment-info-line, transaction-list ·
|
|
19
|
+
**wallet** account-header.
|
|
20
|
+
(`*` = unverified. `flag-hex` is an original brand composition, not an upstream port — battle-tested.)
|
|
@@ -66,5 +66,15 @@
|
|
|
66
66
|
"notes": "Composition. Sources: upstream/wallet/src/components/layouts/AddressOverview.vue (.active-address + .actions desktop markup and scoped SCSS), upstream/wallet/src/components/SearchBar.vue, upstream/wallet/src/components/staking/StakingButton.vue (.staking-feature-tip green CTA tooltip, ::v-deep expanded), upstream/wallet/src/components/icons/Staking/StakingIcon.vue (gradients=false pill mode), upstream/vue-components Tooltip.vue base (bottom-position subset) and Amount.vue. Wallet theme vars (--body-size:2rem, --h1-size:3rem, --large-button-size:2rem, --text-16, --light-blue-40) from wallet src/scss/themes.scss are defined on the component root. Identicon via the pinned @nimiq/iqons@1.6.0 jsdelivr recipe (registry identicon component). Component chrome: root gets padding-bottom:9rem to reserve space for the CTA tooltip (in the wallet it overlays the transaction list); tooltip-box positioned inline (top:100%; left:50%; translate(-50%,1rem)) replacing the upstream JS positioning. Identicon sprite is the REAL team-shipped iqons.min.svg (84-symbol part catalog, 88KB) vendored at assets/img/iqons.min.svg — byte-identical to the Hub-deployed sprite captured in references/assets/hub/iqons.min.svg and to @nimiq/iqons@1.6.0 dist; the snippet probes the local copy and falls back to the same CDN sprite, while the iqons JS library itself stays pinned on jsdelivr (code, not artwork).",
|
|
67
67
|
"assetFiles": [
|
|
68
68
|
"img/iqons.min.svg"
|
|
69
|
-
]
|
|
69
|
+
],
|
|
70
|
+
"source": {
|
|
71
|
+
"repo": "wallet",
|
|
72
|
+
"ref": "656ed56",
|
|
73
|
+
"files": [
|
|
74
|
+
"wallet:src/components/layouts/AddressOverview.vue",
|
|
75
|
+
"wallet:src/components/SearchBar.vue",
|
|
76
|
+
"wallet:src/components/staking/StakingButton.vue",
|
|
77
|
+
"wallet:src/components/icons/Staking/StakingIcon.vue"
|
|
78
|
+
]
|
|
79
|
+
}
|
|
70
80
|
}
|
|
@@ -80,5 +80,13 @@
|
|
|
80
80
|
"notes": "Sample state: 3 enabled rows (Main Account 12 345.6789 NIM / Savings 2500.00 NIM / Pocket Money 49.92 NIM), minBalance set so balances render, list at 420px (SmallPage width). Addresses are synthetic but checksum-valid (IBAN mod-97). Identicons via @nimiq/iqons@1.6.0 pinned on jsdelivr with Iqons.svgPath pointed at iqons.min.svg (same recipe as the identicon component). Static state: identicon scale(0.9), label/balance opacity 0.7, caret hidden (appears on hover with green balance + row highlight). Amount formatting: U+202F narrow no-break space grouping above 4 integer digits. The row content is upstream's Account.vue (layout 'row'); its markup/styles are inlined in the Vue port since Account has no own registry entry. Tooltip/LabelInput only appear for disabled/editable rows (not in sample). Identicon sprite is the REAL team-shipped iqons.min.svg (84-symbol part catalog, 88KB) vendored at assets/img/iqons.min.svg — byte-identical to the Hub-deployed sprite captured in references/assets/hub/iqons.min.svg and to @nimiq/iqons@1.6.0 dist; the snippet probes the local copy and falls back to the same CDN sprite, while the iqons JS library itself stays pinned on jsdelivr (code, not artwork).",
|
|
81
81
|
"assetFiles": [
|
|
82
82
|
"img/iqons.min.svg"
|
|
83
|
-
]
|
|
83
|
+
],
|
|
84
|
+
"source": {
|
|
85
|
+
"repo": "vue-components",
|
|
86
|
+
"ref": "3c5b972",
|
|
87
|
+
"files": [
|
|
88
|
+
"vue-components:src/components/AccountList.vue",
|
|
89
|
+
"vue-components:src/components/Identicon.vue"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
84
92
|
}
|
|
@@ -44,5 +44,13 @@
|
|
|
44
44
|
"notes": "Wraps the identicon component (same @nimiq/iqons@1.6.0 jsdelivr pin in truth and demo). Sample state: 3 fixed valid addresses (NQ07 0000…, NQ21 1111…, NQ35 2222…) → 3 iqons + 3 faint placeholder hexagons. Default size 11.25rem = 90px under nimiq-style's 8px root. Bottom slot (nth-child 3) intentionally overflows the square container by ~0.45rem upstream; element screenshots crop to the .account-ring box identically in truth and demo. Identicon sprite is the REAL team-shipped iqons.min.svg (84-symbol part catalog, 88KB) vendored at assets/img/iqons.min.svg — byte-identical to the Hub-deployed sprite captured in references/assets/hub/iqons.min.svg and to @nimiq/iqons@1.6.0 dist; the snippet probes the local copy and falls back to the same CDN sprite, while the iqons JS library itself stays pinned on jsdelivr (code, not artwork).",
|
|
45
45
|
"assetFiles": [
|
|
46
46
|
"img/iqons.min.svg"
|
|
47
|
-
]
|
|
47
|
+
],
|
|
48
|
+
"source": {
|
|
49
|
+
"repo": "vue-components",
|
|
50
|
+
"ref": "3c5b972",
|
|
51
|
+
"files": [
|
|
52
|
+
"vue-components:src/components/AccountRing.vue",
|
|
53
|
+
"vue-components:src/components/Identicon.vue"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
48
56
|
}
|
|
@@ -1,23 +1,31 @@
|
|
|
1
|
-
/* AddressDisplay (from @nimiq/vue-components AddressDisplay.vue)
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/* AddressDisplay — hardened grid build (from @nimiq/vue-components AddressDisplay.vue).
|
|
2
|
+
The original used flex `width:33%` + space-between, which drifts when Fira Mono
|
|
3
|
+
isn't loaded or the root font-size differs. This version locks the 3×3 with CSS Grid
|
|
4
|
+
(the column count can never reflow) and self-imports Fira Mono so it can't fall back.
|
|
5
|
+
Assumes the Nimiq style root font-size of 8px (html { font-size: 8px }); on a 16px-root
|
|
6
|
+
modern page, set font-size/max-width in px or double the rem values. */
|
|
7
|
+
@import url('https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;500&display=swap');
|
|
5
8
|
|
|
6
9
|
.address-display {
|
|
10
|
+
display: grid;
|
|
11
|
+
justify-items: center;
|
|
7
12
|
width: 100%;
|
|
8
13
|
box-sizing: content-box;
|
|
9
14
|
color: rgba(31, 35, 72, .5); /* nimiq-blue with .5 opacity */
|
|
10
|
-
display: flex;
|
|
11
|
-
flex-wrap: wrap;
|
|
12
|
-
justify-content: space-between;
|
|
13
15
|
font-size: 3rem;
|
|
16
|
+
font-family: 'Fira Mono', monospace;
|
|
14
17
|
}
|
|
15
18
|
|
|
19
|
+
/* 3 columns at 33% + space-between exactly mirrors the upstream spacing, but grid
|
|
20
|
+
pins the column count so a wide fallback glyph can never wrap or shove a block. */
|
|
16
21
|
.format-nimiq {
|
|
22
|
+
grid-template-columns: repeat(3, 33%);
|
|
23
|
+
justify-content: space-between;
|
|
17
24
|
max-width: 28.25rem;
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
.format-ethereum {
|
|
28
|
+
grid-template-columns: 1fr;
|
|
21
29
|
max-width: 27rem;
|
|
22
30
|
}
|
|
23
31
|
|
|
@@ -28,22 +36,18 @@
|
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
.chunk {
|
|
31
|
-
font-family: 'Fira Mono', monospace;
|
|
32
39
|
margin: 0.875rem 0;
|
|
33
40
|
line-height: 1.11;
|
|
34
41
|
text-align: center;
|
|
35
42
|
box-sizing: border-box;
|
|
43
|
+
white-space: nowrap; /* a 4-char block never breaks */
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
.format-nimiq .chunk {
|
|
39
|
-
width: 33%;
|
|
40
47
|
text-transform: uppercase;
|
|
41
48
|
}
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
width: 100%;
|
|
45
|
-
}
|
|
46
|
-
|
|
50
|
+
/* the renders zero-width so copy/paste keeps "NQ07 0000 …" intact */
|
|
47
51
|
.space {
|
|
48
52
|
font-size: 0;
|
|
49
53
|
}
|
|
@@ -9,22 +9,25 @@
|
|
|
9
9
|
<style>
|
|
10
10
|
body { font-family: 'Mulish', sans-serif; margin: 0; padding: 32px; }
|
|
11
11
|
|
|
12
|
-
/* address-display */
|
|
12
|
+
/* address-display — hardened grid build (locks the 3 columns; can't reflow) */
|
|
13
13
|
.address-display {
|
|
14
|
+
display: grid;
|
|
15
|
+
justify-items: center;
|
|
14
16
|
width: 100%;
|
|
15
17
|
box-sizing: content-box;
|
|
16
18
|
color: rgba(31, 35, 72, .5); /* nimiq-blue with .5 opacity */
|
|
17
|
-
display: flex;
|
|
18
|
-
flex-wrap: wrap;
|
|
19
|
-
justify-content: space-between;
|
|
20
19
|
font-size: 3rem;
|
|
20
|
+
font-family: 'Fira Mono', monospace;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
.format-nimiq {
|
|
24
|
+
grid-template-columns: repeat(3, 33%);
|
|
25
|
+
justify-content: space-between;
|
|
24
26
|
max-width: 28.25rem;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
.format-ethereum {
|
|
30
|
+
grid-template-columns: 1fr;
|
|
28
31
|
max-width: 27rem;
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -35,22 +38,17 @@
|
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
.chunk {
|
|
38
|
-
font-family: 'Fira Mono', monospace;
|
|
39
41
|
margin: 0.875rem 0;
|
|
40
42
|
line-height: 1.11;
|
|
41
43
|
text-align: center;
|
|
42
44
|
box-sizing: border-box;
|
|
45
|
+
white-space: nowrap;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
.format-nimiq .chunk {
|
|
46
|
-
width: 33%;
|
|
47
49
|
text-transform: uppercase;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
.format-ethereum .chunk {
|
|
51
|
-
width: 100%;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
52
|
.space {
|
|
55
53
|
font-size: 0;
|
|
56
54
|
}
|
|
@@ -2,21 +2,51 @@
|
|
|
2
2
|
"name": "address-display",
|
|
3
3
|
"purpose": "Displays a Nimiq (or Ethereum) address as a grid of monospace chunks — 9 four-char blocks in 3 columns for Nimiq format — optionally copyable.",
|
|
4
4
|
"category": "identity",
|
|
5
|
-
"variants": [
|
|
5
|
+
"variants": [
|
|
6
|
+
"vue",
|
|
7
|
+
"html"
|
|
8
|
+
],
|
|
6
9
|
"props": [
|
|
7
|
-
{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
{
|
|
11
|
+
"name": "address",
|
|
12
|
+
"type": "string",
|
|
13
|
+
"required": true,
|
|
14
|
+
"description": "The address to display."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "format",
|
|
18
|
+
"type": "string",
|
|
19
|
+
"default": "nimiq",
|
|
20
|
+
"description": "'nimiq' (9 chunks of 4, uppercase) or 'ethereum' (3 rows of 14 chars)."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "copyable",
|
|
24
|
+
"type": "boolean",
|
|
25
|
+
"default": false,
|
|
26
|
+
"description": "Wraps the display in a Copyable so clicking copies the address."
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"cssFiles": [
|
|
30
|
+
"css/legacy/nimiq-style.min.css"
|
|
10
31
|
],
|
|
11
|
-
"cssFiles": ["css/legacy/nimiq-style.min.css"],
|
|
12
32
|
"dependsOn": [],
|
|
13
33
|
"npmDeps": [],
|
|
14
34
|
"verified": true,
|
|
15
35
|
"verify": {
|
|
16
|
-
"viewport": {
|
|
36
|
+
"viewport": {
|
|
37
|
+
"width": 600,
|
|
38
|
+
"height": 400
|
|
39
|
+
},
|
|
17
40
|
"selector": ".address-display",
|
|
18
41
|
"maxDiffPct": 0.5,
|
|
19
42
|
"settleMs": 250
|
|
20
43
|
},
|
|
21
|
-
"notes": "Sample state: NQ07 0000... burn address, nimiq format, copyable=false.
|
|
44
|
+
"notes": "Sample state: NQ07 0000... burn address, nimiq format, copyable=false. HARDENED 2026-06-20: the 3×3 is now CSS Grid (grid-template-columns: repeat(3,33%) + justify-content:space-between) instead of flex width:33% — grid pins the column count so a wide fallback glyph can never reflow/wrap; the CSS self-imports Fira Mono so a missing font link can't make it drift; .chunk has white-space:nowrap. Pixel-verified 0% diff vs the original flex render, so the look is identical. GOTCHA still live: font-size:3rem + max-width:28.25rem assume the legacy 8px root; on a 16px-root modern page set them in px or 2× the rem. copyable=true depends on the Copyable component (not ported here). ValidationUtils.normalizeAddress from @nimiq/utils is inlined in the Vue port.",
|
|
45
|
+
"source": {
|
|
46
|
+
"repo": "vue-components",
|
|
47
|
+
"ref": "3c5b972",
|
|
48
|
+
"files": [
|
|
49
|
+
"vue-components:src/components/AddressDisplay.vue"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
22
52
|
}
|
|
@@ -62,21 +62,28 @@ defineExpose({ text });
|
|
|
62
62
|
</script>
|
|
63
63
|
|
|
64
64
|
<style scoped>
|
|
65
|
+
/* Hardened grid build: locks the 3 columns (can't reflow) and self-imports
|
|
66
|
+
Fira Mono so a missing font link can't make the 3×3 drift. */
|
|
67
|
+
@import url('https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;500&display=swap');
|
|
68
|
+
|
|
65
69
|
.address-display {
|
|
70
|
+
display: grid;
|
|
71
|
+
justify-items: center;
|
|
66
72
|
width: 100%;
|
|
67
73
|
box-sizing: content-box;
|
|
68
74
|
color: rgba(31, 35, 72, .5); /* nimiq-blue with .5 opacity */
|
|
69
|
-
display: flex;
|
|
70
|
-
flex-wrap: wrap;
|
|
71
|
-
justify-content: space-between;
|
|
72
75
|
font-size: 3rem;
|
|
76
|
+
font-family: 'Fira Mono', monospace;
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
.format-nimiq {
|
|
80
|
+
grid-template-columns: repeat(3, 33%);
|
|
81
|
+
justify-content: space-between;
|
|
76
82
|
max-width: 28.25rem;
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
.format-ethereum {
|
|
86
|
+
grid-template-columns: 1fr;
|
|
80
87
|
max-width: 27rem;
|
|
81
88
|
}
|
|
82
89
|
|
|
@@ -87,22 +94,17 @@ defineExpose({ text });
|
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
.chunk {
|
|
90
|
-
font-family: 'Fira Mono', monospace;
|
|
91
97
|
margin: 0.875rem 0;
|
|
92
98
|
line-height: 1.11;
|
|
93
99
|
text-align: center;
|
|
94
100
|
box-sizing: border-box;
|
|
101
|
+
white-space: nowrap;
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
.format-nimiq .chunk {
|
|
98
|
-
width: 33%;
|
|
99
105
|
text-transform: uppercase;
|
|
100
106
|
}
|
|
101
107
|
|
|
102
|
-
.format-ethereum .chunk {
|
|
103
|
-
width: 100%;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
108
|
.space {
|
|
107
109
|
font-size: 0;
|
|
108
110
|
}
|
|
@@ -2,23 +2,66 @@
|
|
|
2
2
|
"name": "address-input",
|
|
3
3
|
"purpose": "A 3x3 block grid textarea for typing Nimiq addresses (optionally Ethereum addresses and domains), with live formatting into 4-char blocks, validation, and per-block focus coloring.",
|
|
4
4
|
"category": "form",
|
|
5
|
-
"variants": [
|
|
5
|
+
"variants": [
|
|
6
|
+
"vue",
|
|
7
|
+
"html"
|
|
8
|
+
],
|
|
6
9
|
"props": [
|
|
7
|
-
{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
{
|
|
11
|
+
"name": "value",
|
|
12
|
+
"type": "string",
|
|
13
|
+
"default": "",
|
|
14
|
+
"description": "The address value, bindable via v-model."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "autofocus",
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"default": false,
|
|
20
|
+
"description": "Focus the input on mount."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "allowNimAddresses",
|
|
24
|
+
"type": "boolean",
|
|
25
|
+
"default": true,
|
|
26
|
+
"description": "Accept Nimiq NQ... addresses."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "allowEthAddresses",
|
|
30
|
+
"type": "boolean",
|
|
31
|
+
"default": false,
|
|
32
|
+
"description": "Accept Ethereum 0x... addresses (checksummed via js-sha3 keccak_256)."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "allowDomains",
|
|
36
|
+
"type": "boolean",
|
|
37
|
+
"default": false,
|
|
38
|
+
"description": "Accept domain names (collapses to a single-row input)."
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"cssFiles": [
|
|
42
|
+
"css/legacy/nimiq-style.min.css"
|
|
12
43
|
],
|
|
13
|
-
"cssFiles": ["css/legacy/nimiq-style.min.css"],
|
|
14
44
|
"dependsOn": [],
|
|
15
|
-
"npmDeps": [
|
|
45
|
+
"npmDeps": [
|
|
46
|
+
"input-format",
|
|
47
|
+
"js-sha3"
|
|
48
|
+
],
|
|
16
49
|
"verified": true,
|
|
17
50
|
"verify": {
|
|
18
|
-
"viewport": {
|
|
51
|
+
"viewport": {
|
|
52
|
+
"width": 600,
|
|
53
|
+
"height": 400
|
|
54
|
+
},
|
|
19
55
|
"selector": ".address-input",
|
|
20
56
|
"maxDiffPct": 0.5,
|
|
21
57
|
"settleMs": 250
|
|
22
58
|
},
|
|
23
|
-
"notes": "Sample state: filled with valid address 'NQ48 8CKH BA24 2VR3 N249 N8MN J5XX 74DB 5XJ8', unfocused, default props (NIM only) -> 3x3 grid of 4-char blocks (matches the wallet send modal); textarea text is black and each block is colored via a .color-overlay with mix-blend-mode:screen (var(--nimiq-blue) idle, light blue for the selected block in the live component). The live component formats the value as 3 lines ('wwww wwww wwww\\n' template). Empty state (previous sample): no value -> placeholder 'NQ' at 60% opacity in block 1, color overlays rendered but visibility:hidden, grid + vertical block separators unchanged. Fira Mono loaded from Google Fonts (upstream assumes host app provides it). html variant is static visual only (no input-format behavior). Vue port keeps full behavior: input-format and lazy js-sha3 are real npm deps; Clipboard.copy and ValidationUtils.isValidAddress from @nimiq/utils are inlined."
|
|
59
|
+
"notes": "Sample state: filled with valid address 'NQ48 8CKH BA24 2VR3 N249 N8MN J5XX 74DB 5XJ8', unfocused, default props (NIM only) -> 3x3 grid of 4-char blocks (matches the wallet send modal); textarea text is black and each block is colored via a .color-overlay with mix-blend-mode:screen (var(--nimiq-blue) idle, light blue for the selected block in the live component). The live component formats the value as 3 lines ('wwww wwww wwww\\n' template). Empty state (previous sample): no value -> placeholder 'NQ' at 60% opacity in block 1, color overlays rendered but visibility:hidden, grid + vertical block separators unchanged. Fira Mono loaded from Google Fonts (upstream assumes host app provides it). html variant is static visual only (no input-format behavior). Vue port keeps full behavior: input-format and lazy js-sha3 are real npm deps; Clipboard.copy and ValidationUtils.isValidAddress from @nimiq/utils are inlined.",
|
|
60
|
+
"source": {
|
|
61
|
+
"repo": "vue-components",
|
|
62
|
+
"ref": "3c5b972",
|
|
63
|
+
"files": [
|
|
64
|
+
"vue-components:src/components/AddressInput.vue"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
24
67
|
}
|
|
@@ -2,25 +2,74 @@
|
|
|
2
2
|
"name": "amount",
|
|
3
3
|
"purpose": "Formats a crypto amount given in its smallest unit (luna, satoshi, ...) as a human-readable number with thin-space digit grouping, configurable decimals and a currency ticker.",
|
|
4
4
|
"category": "payment",
|
|
5
|
-
"variants": [
|
|
5
|
+
"variants": [
|
|
6
|
+
"vue",
|
|
7
|
+
"html"
|
|
8
|
+
],
|
|
6
9
|
"props": [
|
|
7
|
-
{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
{
|
|
10
|
+
{
|
|
11
|
+
"name": "amount",
|
|
12
|
+
"type": "number | bigint | BigInteger",
|
|
13
|
+
"required": true,
|
|
14
|
+
"description": "Amount in the currency's smallest unit (e.g. luna for NIM)."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "decimals",
|
|
18
|
+
"type": "number",
|
|
19
|
+
"description": "Exact decimals to show; takes precedence over minDecimals/maxDecimals."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "minDecimals",
|
|
23
|
+
"type": "number",
|
|
24
|
+
"default": 2,
|
|
25
|
+
"description": "Minimum decimals to show (pads with zeros)."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "maxDecimals",
|
|
29
|
+
"type": "number",
|
|
30
|
+
"default": 5,
|
|
31
|
+
"description": "Maximum decimals to show (rounds)."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "showApprox",
|
|
35
|
+
"type": "boolean",
|
|
36
|
+
"default": false,
|
|
37
|
+
"description": "Prefix '~ ' when rounding made the displayed value inexact."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "currency",
|
|
41
|
+
"type": "string",
|
|
42
|
+
"default": "nim",
|
|
43
|
+
"description": "Currency code; rendered uppercase as the ticker (special-cased: tnim→tNIM, mbtc→mBTC, tbtc→tBTC, usdc.e→USDC.e)."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "currencyDecimals",
|
|
47
|
+
"type": "number",
|
|
48
|
+
"default": 5,
|
|
49
|
+
"description": "Decimals of the currency's smallest unit (5 for NIM/luna, 8 for BTC/sat)."
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"cssFiles": [
|
|
53
|
+
"css/legacy/nimiq-style.min.css"
|
|
14
54
|
],
|
|
15
|
-
"cssFiles": ["css/legacy/nimiq-style.min.css"],
|
|
16
55
|
"dependsOn": [],
|
|
17
56
|
"npmDeps": [],
|
|
18
57
|
"verified": true,
|
|
19
58
|
"verify": {
|
|
20
|
-
"viewport": {
|
|
59
|
+
"viewport": {
|
|
60
|
+
"width": 600,
|
|
61
|
+
"height": 400
|
|
62
|
+
},
|
|
21
63
|
"selector": ".amount",
|
|
22
64
|
"maxDiffPct": 0.5,
|
|
23
65
|
"settleMs": 250
|
|
24
66
|
},
|
|
25
|
-
"notes": "Sample state: 1234567890 luna = 12,345.6789 NIM, minDecimals 2, maxDecimals 5, currency nim. FormattableNumber from @nimiq/utils is inlined as a faithful mini-port in the Vue variant (string-based, precision-loss-free: moveDecimalSeparator, half-up rounding, U+202F narrow no-break space grouping applied only when there are more than 4 integer digits). No npm deps. The .currency span carries the currency code as a class for host styling; upstream itself styles only .amount (nowrap) and .approx::before."
|
|
67
|
+
"notes": "Sample state: 1234567890 luna = 12,345.6789 NIM, minDecimals 2, maxDecimals 5, currency nim. FormattableNumber from @nimiq/utils is inlined as a faithful mini-port in the Vue variant (string-based, precision-loss-free: moveDecimalSeparator, half-up rounding, U+202F narrow no-break space grouping applied only when there are more than 4 integer digits). No npm deps. The .currency span carries the currency code as a class for host styling; upstream itself styles only .amount (nowrap) and .approx::before.",
|
|
68
|
+
"source": {
|
|
69
|
+
"repo": "vue-components",
|
|
70
|
+
"ref": "3c5b972",
|
|
71
|
+
"files": [
|
|
72
|
+
"vue-components:src/components/Amount.vue"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
26
75
|
}
|