diff --git a/README.md b/README.md index fba0596..fc743c9 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,10 @@ The app runs at http://localhost:5173 by default. - **Transfers** — list of your transfers with status badges (pending, completed, failed), search/status/date-range filters synced to the URL, plus loading, error and empty states. +- **Print support** — a dedicated print stylesheet (`src/print.css`) + optimises the Transfers dashboard and other pages for printing: hides + navigation chrome, interactive elements, and applies a light background + with readable text and preserved chart/status colours. - **Mock wallet** — connect a demo Stellar wallet (no network calls). - Robust error handling for rejected connections - Connection timeout protection (30 seconds) diff --git a/src/App.jsx b/src/App.jsx index 8cc9ab4..22339ba 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -9,6 +9,7 @@ import SendMoney from './pages/SendMoney.jsx' import Transfers from './pages/Transfers.jsx' import NotFound from './pages/NotFound.jsx' import './App.css' +import './print.css' /** * Root application component: wires up the context, router and layout. diff --git a/src/components/Chart.css b/src/components/Chart.css index 78320e2..9f70ec8 100644 --- a/src/components/Chart.css +++ b/src/components/Chart.css @@ -68,3 +68,18 @@ .download-button { margin-top: 1rem; } + +@media print { + .chart-container { + border-color: #ccc !important; + break-inside: avoid; + } + + .chart-tooltip { + display: none !important; + } + + .download-button { + display: none !important; + } +} diff --git a/src/pages/Transfers.css b/src/pages/Transfers.css index f920d22..5dc1258 100644 --- a/src/pages/Transfers.css +++ b/src/pages/Transfers.css @@ -45,3 +45,17 @@ gap: 0.75rem; margin-top: 0.75rem; } + +@media print { + .transfers-filters { + display: none !important; + } + + .transfers-header .btn { + display: none !important; + } + + .transfers-list { + margin-top: 0; + } +} diff --git a/src/print.css b/src/print.css new file mode 100644 index 0000000..bf624ff --- /dev/null +++ b/src/print.css @@ -0,0 +1,94 @@ +@media print { + /* ── Reset to light background for readability ─────────────── */ + body { + background: #fff !important; + color: #000 !important; + } + + /* ── Hide chrome (navigation, footer, skip-link, scroll-to-top) ── */ + .navbar, + .footer, + .skip-link, + .scroll-to-top { + display: none !important; + } + + /* ── Hide interactive / button elements ─────────────────── */ + .btn { + display: none !important; + } + + /* ── Main content: full width, remove padding ──────────── */ + .app-main { + max-width: none; + padding: 0 1rem; + margin: 0; + } + + /* ── Surface elements: white bg, visible borders ────────── */ + .transfer-row, + .feature-card, + .corridor-chip, + .chart-container, + .quote-card, + .modal-panel { + background: #fff !important; + border-color: #ccc !important; + break-inside: avoid; + } + + /* ── Status badges: preserve color on white ─────────────── */ + .status-badge { + print-color-adjust: exact; + -webkit-print-color-adjust: exact; + } + + .status-pending { + background: #fef3cd !important; + color: #856404 !important; + } + + .status-completed { + background: #d4edda !important; + color: #155724 !important; + } + + .status-failed { + background: #f8d7da !important; + color: #721c24 !important; + } + + /* ── Chart: preserve bar colors, hide tooltip ────────────── */ + .chart-bar { + print-color-adjust: exact; + -webkit-print-color-adjust: exact; + } + + .chart-tooltip { + display: none !important; + } + + .download-button { + display: none !important; + } + + /* ── Page text color overrides ───────────────────────────── */ + .page-title, + .chart-title, + .transfer-cell, + .feature-title, + .feature-text, + .hero-title, + .hero-subtitle { + color: #000 !important; + } + + .transfer-label { + color: #555 !important; + } + + /* ── Page margins ────────────────────────────────────────── */ + @page { + margin: 1.5cm; + } +} diff --git a/test/print-styles.test.js b/test/print-styles.test.js new file mode 100644 index 0000000..b4b86ca --- /dev/null +++ b/test/print-styles.test.js @@ -0,0 +1,91 @@ +import fs from 'node:fs' +import path from 'node:path' +import { describe, expect, it } from 'vitest' + +function readCSS(filename) { + return fs.readFileSync( + path.resolve(process.cwd(), 'src', filename), + 'utf8', + ) +} + +describe('print styles', () => { + describe('print.css (global)', () => { + const css = readCSS('print.css') + + it('defines @media print rules', () => { + expect(css).toMatch(/@media\s+print/) + }) + + it('hides chrome (navbar, footer, skip-link, scroll-to-top) in a group selector', () => { + expect(css).toMatch(/\.navbar,\s*\.footer,\s*\.skip-link,\s*\.scroll-to-top\s*\{[^}]*display:\s*none/) + }) + + it('hides all .btn elements', () => { + expect(css).toMatch(/\.btn\s*\{[^}]*display:\s*none/) + }) + + it('resets body background to white', () => { + expect(css).toMatch(/body\s*\{[^}]*background:\s*#[fF]+/) + }) + + it('resets body text color to black', () => { + expect(css).toMatch(/body\s*\{[^}]*color:\s*#[0]+/) + }) + + it('sets @page margin', () => { + expect(css).toMatch(/@page\s*\{[^}]*margin:\s*1\.5cm/) + }) + + it('defines print-color-adjust for status badges', () => { + expect(css).toMatch(/print-color-adjust:\s*exact/) + }) + + it('overrides status badge colors for print', () => { + expect(css).toMatch(/\.status-pending\s*\{[^}]*background:\s*#fef3cd/) + expect(css).toMatch(/\.status-completed\s*\{[^}]*background:\s*#d4edda/) + expect(css).toMatch(/\.status-failed\s*\{[^}]*background:\s*#f8d7da/) + }) + }) + + describe('Transfers.css (page-specific)', () => { + const css = readCSS(path.join('pages', 'Transfers.css')) + + it('includes @media print rules', () => { + expect(css).toMatch(/@media\s+print/) + }) + + it('hides filters on print', () => { + const match = css.match(/@media\s+print\s*\{([^}]*)\}/s) + expect(match).not.toBeNull() + expect(css).toMatch(/\.transfers-filters\s*\{[^}]*display:\s*none/) + }) + }) + + describe('Chart.css (component-specific)', () => { + const css = readCSS(path.join('components', 'Chart.css')) + + it('includes @media print rules', () => { + expect(css).toMatch(/@media\s+print/) + }) + + it('hides chart tooltip on print', () => { + expect(css).toMatch(/\.chart-tooltip\s*\{[^}]*display:\s*none/) + }) + + it('hides download button on print', () => { + expect(css).toMatch(/\.download-button\s*\{[^}]*display:\s*none/) + }) + }) + + describe('App.jsx imports print.css', () => { + const app = fs.readFileSync( + path.resolve(process.cwd(), 'src', 'App.jsx'), + 'utf8', + ) + + it('imports print.css', () => { + expect(app).toMatch(/import\s+['"].\/print\.css['"]/) + }) + }) +})