fix(design): mobile receipt form layout + confidence badges + DESIGN.md
Fix mobile layout issues on /data-entry/create after OCR upload: - CompactUploadZone action bar forced horizontal on mobile (was stacking vertically due to global .action-bar column rule in containers.css) - TOTAL input stretches full width on mobile (was fixed 140px) - Card/Cash/Alte payment inputs equal width via flex: 1 1 0% - Extra-small (≤360px) fallback stacks payments vertically - OCR confidence badges: fixed width (icon-only), hidden percentage text, hover opacity effect — prevents input misalignment - OCRConfidenceIndicator: replace hardcoded colors with design tokens (green-100/800, yellow-100/800, red-100/800), add three-tier dark mode - Smaller size-small badge variant (border-radius-sm, line-height: 1) Create docs/DESIGN.md as design source of truth for /design-review, /plan-design-review, and /frontend-design skills. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
431
docs/DESIGN.md
Normal file
431
docs/DESIGN.md
Normal file
@@ -0,0 +1,431 @@
|
||||
# ROA2WEB Design System
|
||||
|
||||
**Version:** 1.0.0 | **Updated:** 2026-03-23 | **Status:** Source of Truth
|
||||
|
||||
---
|
||||
|
||||
## 1. Design Philosophy
|
||||
|
||||
ROA2WEB is an ERP application for accounting and financial data management. The design prioritizes:
|
||||
|
||||
- **Data density over whitespace** — Users process hundreds of records daily; every pixel earns its space
|
||||
- **Scan-friendly hierarchy** — Bold totals, muted labels, color-coded sections guide the eye
|
||||
- **Compact, not cramped** — 32px input height, 8px gaps, systematic spacing scale
|
||||
- **Professional restraint** — No decorative elements, no hero sections, no gratuitous animation
|
||||
- **Trust at the pixel level** — Financial data demands precision; misaligned inputs erode confidence
|
||||
- **System fonts for speed** — No custom font loading; native OS typography feels fast and familiar
|
||||
- **Progressive disclosure** — Show summary first, reveal detail on demand (collapsible sections, tooltips, bottom sheets)
|
||||
|
||||
---
|
||||
|
||||
## 2. Visual Identity
|
||||
|
||||
### Brand Colors
|
||||
|
||||
| Role | Token | Hex (Light) | Usage |
|
||||
|------|-------|-------------|-------|
|
||||
| Primary | `--color-primary` | #2563eb | Actions, links, focus rings, active states |
|
||||
| Primary Dark | `--color-primary-dark` | #1d4ed8 | Hover states, pressed buttons |
|
||||
| Success | `--color-success` | #059669 | Approvals, positive values, confirmations |
|
||||
| Warning | `--color-warning` | #d97706 | Pending states, caution alerts, mismatches |
|
||||
| Error | `--color-error` | #dc2626 | Rejections, validation errors, negative values |
|
||||
| Info | `--color-info` | #0891b2 | Informational badges, OCR hints |
|
||||
|
||||
### Surface Hierarchy
|
||||
|
||||
```
|
||||
Ground (#f8fafc) → Section → Card (#ffffff) → Overlay
|
||||
↓ darkest ↓ lightest ↓ modal/dropdown
|
||||
```
|
||||
|
||||
Each level uses progressively elevated surface tokens. In dark mode, the scale inverts (darker = lower, lighter = higher).
|
||||
|
||||
| Token | Light | Dark | Purpose |
|
||||
|-------|-------|------|---------|
|
||||
| `--surface-ground` | #f8fafc | #0f172a | Page background |
|
||||
| `--surface-card` | #ffffff | #1e293b | Card/container backgrounds |
|
||||
| `--surface-hover` | #f1f5f9 | #334155 | Hover states, chips |
|
||||
| `--surface-border` | #e2e8f0 | #475569 | Borders, dividers |
|
||||
| `--text-color` | #111827 | #f9fafb | Primary text |
|
||||
| `--text-color-secondary` | #6b7280 | #d1d5db | Labels, hints, metadata |
|
||||
|
||||
> **Full palette reference**: [DESIGN_TOKENS.md](./DESIGN_TOKENS.md)
|
||||
|
||||
---
|
||||
|
||||
## 3. Color System
|
||||
|
||||
### Semantic Color Roles
|
||||
|
||||
Always use semantic tokens, never raw hex values:
|
||||
|
||||
```css
|
||||
/* Backgrounds */
|
||||
background: var(--surface-card); /* Cards, modals */
|
||||
background: var(--surface-ground); /* Page background */
|
||||
background: var(--surface-hover); /* Hover states, chips */
|
||||
|
||||
/* Text */
|
||||
color: var(--text-color); /* Primary text */
|
||||
color: var(--text-color-secondary); /* Labels, hints */
|
||||
|
||||
/* Borders */
|
||||
border-color: var(--surface-border); /* Standard borders */
|
||||
```
|
||||
|
||||
### Status Color Scales
|
||||
|
||||
Each status has a 50-900 scale. Use **100** for backgrounds, **800** for text in light mode:
|
||||
|
||||
| Status | Background | Text | Border | Use For |
|
||||
|--------|-----------|------|--------|---------|
|
||||
| Success | `--green-100` | `--green-800` | `--green-300` | Approvals, positive values, OCR high confidence |
|
||||
| Warning | `--yellow-100` | `--yellow-800` | `--yellow-300` | Pending, caution, OCR medium confidence |
|
||||
| Error | `--red-100` | `--red-800` | `--red-300` | Rejections, errors, OCR low confidence |
|
||||
| Info | `--blue-100` | `--blue-800` | `--blue-300` | Informational, OCR hints, categorization |
|
||||
|
||||
### OCR Confidence Colors
|
||||
|
||||
| Level | Threshold | Background | Text | Icon |
|
||||
|-------|-----------|-----------|------|------|
|
||||
| High | >= 85% | `--green-100` | `--green-800` | `pi-check-circle` |
|
||||
| Medium | >= 60% | `--yellow-100` | `--yellow-800` | `pi-exclamation-circle` |
|
||||
| Low | < 60% | `--red-100` | `--red-800` | `pi-question-circle` |
|
||||
|
||||
### Color-Coded Form Sections
|
||||
|
||||
- **Green background** (`--green-50`): Financial values (TOTAL, payments, TVA)
|
||||
- **Blue background** (`--blue-50`): Categorization (expense type, description)
|
||||
- **Cyan background** (`--cyan-50`): Client/B2B information
|
||||
- **No background**: Standard form fields (supplier, document)
|
||||
|
||||
---
|
||||
|
||||
## 4. Typography
|
||||
|
||||
### Font Stack
|
||||
|
||||
```css
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
font-family: var(--font-mono); /* For financial values, code */
|
||||
```
|
||||
|
||||
### Scale
|
||||
|
||||
| Token | Size | Usage |
|
||||
|-------|------|-------|
|
||||
| `--text-xs` | 12px | Labels, badges, metadata, timestamps, hints |
|
||||
| `--text-sm` | 14px | Table cells, form inputs, secondary text, buttons |
|
||||
| `--text-base` | 16px | Body text, primary content (default) |
|
||||
| `--text-lg` | 18px | Subheadings |
|
||||
| `--text-xl` | 20px | Card titles, section headings |
|
||||
| `--text-2xl` | 24px | Page titles, metric values, KPI numbers |
|
||||
| `--text-3xl` | 32px | Dashboard hero numbers |
|
||||
| `--text-4xl` | 40px | Reserved for exceptional emphasis |
|
||||
|
||||
### Weight Rules
|
||||
|
||||
| Token | Value | Usage |
|
||||
|-------|-------|-------|
|
||||
| `--font-normal` | 400 | Body text, table cells, descriptions |
|
||||
| `--font-medium` | 500 | Labels, buttons, badges, emphasis |
|
||||
| `--font-semibold` | 600 | Headings, financial totals, card titles |
|
||||
| `--font-bold` | 700 | TOTAL labels, strong emphasis, alerts |
|
||||
|
||||
### Financial Numbers
|
||||
|
||||
All financial values use `font-variant-numeric: tabular-nums` for column alignment.
|
||||
|
||||
---
|
||||
|
||||
## 5. Spacing & Layout
|
||||
|
||||
### Spacing Scale
|
||||
|
||||
| Token | Value | Primary Use |
|
||||
|-------|-------|------------|
|
||||
| `--space-xs` | 4px | Icon gaps, badge padding, tight internal spacing |
|
||||
| `--space-sm` | 8px | Related item gaps, compact form spacing |
|
||||
| `--space-md` | 16px | Standard component padding, section gaps |
|
||||
| `--space-lg` | 24px | Card padding, section separation |
|
||||
| `--space-xl` | 32px | Page margins, major separations |
|
||||
| `--space-2xl` | 48px | Layout sections |
|
||||
| `--space-3xl` | 64px | Page-level spacing |
|
||||
|
||||
### Form Layout Rules
|
||||
|
||||
- **Input height**: 32px for compact forms, 40px for standalone forms
|
||||
- **Input font**: `--text-sm` (14px) for form inputs
|
||||
- **Numeric inputs**: `text-align: right` always
|
||||
- **Labels**: `--text-xs` (12px), `--font-semibold`, `--text-color-secondary`
|
||||
- **Layout**: Inline labels next to inputs on desktop, stacked on mobile
|
||||
- **Row gaps**: `--space-sm` (8px) between form rows
|
||||
- **Row borders**: `1px solid var(--surface-border)` between rows
|
||||
|
||||
### Breakpoints
|
||||
|
||||
| Name | Width | Layout Behavior |
|
||||
|------|-------|----------------|
|
||||
| Extra-small | ≤360px | Payment inputs stack vertically, minimal padding |
|
||||
| Mobile | ≤768px | Single column, stacked forms, bottom sheets for filters |
|
||||
| Tablet | 769-1023px | Hybrid layout, wider cards |
|
||||
| Desktop | ≥1024px | Full multi-column, inline forms, side-by-side panels |
|
||||
| Wide | ≥1400px | Max content width, centered layout |
|
||||
|
||||
---
|
||||
|
||||
## 6. Component Patterns
|
||||
|
||||
### Cards
|
||||
|
||||
```css
|
||||
/* Standard card */
|
||||
background: var(--surface-card);
|
||||
border-radius: var(--radius-md); /* 8px */
|
||||
padding: var(--space-lg); /* 24px */
|
||||
box-shadow: var(--shadow-sm);
|
||||
|
||||
/* Compact card (data-dense views) */
|
||||
padding: var(--space-md); /* 16px */
|
||||
|
||||
/* Metric/KPI card */
|
||||
/* Icon + --text-2xl value + --text-xs label + trend indicator */
|
||||
```
|
||||
|
||||
### Buttons
|
||||
|
||||
- Primary: `--color-primary` background, white text
|
||||
- Secondary: Outlined or text-only (`severity="secondary"`)
|
||||
- Minimum touch target: 44x44px on mobile
|
||||
- Icon buttons: 32x32px on desktop, 44x44px on mobile
|
||||
|
||||
### Tables (PrimeVue DataTable)
|
||||
|
||||
- Separate Debit/Credit columns (never stacked in one cell)
|
||||
- Filter buttons on dedicated row below filters
|
||||
- Export exports ALL data, not just current page
|
||||
- All overrides in `vendor/primevue-overrides.css` — never use `:deep()` in components
|
||||
|
||||
### Dropdowns in Compact Forms
|
||||
|
||||
Use `dropdown-borderless` class for inline dropdowns:
|
||||
```html
|
||||
<Dropdown ... class="my-select dropdown-borderless" />
|
||||
```
|
||||
|
||||
> **Full pattern reference**: [CSS_PATTERNS.md](./CSS_PATTERNS.md)
|
||||
|
||||
---
|
||||
|
||||
## 7. Form Design
|
||||
|
||||
### Receipt Form Layout (UnifiedReceiptForm)
|
||||
|
||||
Ultra-compact flat layout with color-coded sections:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Furnizor [AutoComplete____] CUI [___] 🟢 ↻ │ ← white bg
|
||||
├─────────────────────────────────────────────┤
|
||||
│ OCR: Lidl Discount SRL (22891860) │ ← blue-50 hint
|
||||
├─────────────────────────────────────────────┤
|
||||
│ [Bon fiscal ▾] Nr. [___] Data [___] 🟢 │ ← white bg
|
||||
├─────────────────────────────────────────────┤
|
||||
│ TOTAL [_140px_] 🟢 Card [_120px_] │ ← green-50 bg
|
||||
│ Cash [_120px_] │
|
||||
│ Alte [_120px_] 🟢 │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ TVA: [19% ▾][___] [9% ▾][___] 🟢 │ ← white bg
|
||||
├─────────────────────────────────────────────┤
|
||||
│ [Cheltuiala... ▾] │ ← blue-50 bg
|
||||
│ [Descriere...________________________] │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ Atasamente: [📄 bon.pdf ✕] [➕] │ ← white bg
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### OCR Confidence Badges
|
||||
|
||||
- **Placement**: Inline, next to the input they describe
|
||||
- **Display**: Icon-only in compact form (percentage hidden, available via tooltip)
|
||||
- **Sizing**: Fixed reserved width (20px) to prevent input misalignment
|
||||
- **Hover**: `opacity: 0.85 → 1` transition for discoverability
|
||||
- **Interaction**: `cursor: help`, tooltip shows full percentage on hover/tap
|
||||
|
||||
### Input Conventions
|
||||
|
||||
```css
|
||||
/* All compact form inputs */
|
||||
height: 32px;
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
font-size: var(--text-sm);
|
||||
border: 1px solid var(--surface-border);
|
||||
border-radius: var(--radius-sm);
|
||||
|
||||
/* Financial inputs additionally */
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Mobile Strategy
|
||||
|
||||
### Philosophy
|
||||
|
||||
MD3-inspired (Material Design 3) touch-first design. Mobile is not "desktop shrunk" — it gets intentional layout.
|
||||
|
||||
### Required Components (ALL mobile views)
|
||||
|
||||
| Component | Location | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| MobileTopBar | `@shared/components/mobile/MobileTopBar.vue` | Fixed header (56px), title, actions |
|
||||
| MobileBottomNav | `@shared/components/mobile/MobileBottomNav.vue` | Fixed footer (56px), tab navigation |
|
||||
| BottomSheet | `@shared/components/mobile/BottomSheet.vue` | Filters, extended options (slides up) |
|
||||
| MobileSelectionFooter | `@shared/components/mobile/MobileSelectionFooter.vue` | Batch actions when items selected |
|
||||
|
||||
### Content Padding
|
||||
|
||||
```css
|
||||
.mobile-content {
|
||||
padding-top: 56px; /* MobileTopBar height */
|
||||
padding-bottom: 56px; /* MobileBottomNav height */
|
||||
}
|
||||
```
|
||||
|
||||
### Mobile Form Rules
|
||||
|
||||
- **Full-width inputs**: Stacked vertically, `width: 100%`
|
||||
- **Payment inputs**: Equal-width side-by-side (`flex: 1 1 0%`), stack on extra-small (≤360px)
|
||||
- **Confidence badges**: Icon-only, no percentage text
|
||||
- **Filters**: Always in BottomSheet, never inline
|
||||
- **Selection actions**: Always in MobileSelectionFooter, never in header
|
||||
- **Upload zone**: Reduced height (24px min) after file selection
|
||||
|
||||
### Touch Targets
|
||||
|
||||
- **Minimum**: 44x44px for all interactive elements (buttons, links, checkboxes)
|
||||
- **Input font**: 16px minimum to prevent iOS zoom
|
||||
- **Active feedback**: `scale(0.98)` on touch for responsive feel
|
||||
|
||||
> **Full mobile guide**: [MOBILE_PATTERNS.md](./MOBILE_PATTERNS.md)
|
||||
|
||||
---
|
||||
|
||||
## 9. Dark Mode
|
||||
|
||||
### Three-Tier System
|
||||
|
||||
| Mode | Trigger | CSS Selector |
|
||||
|------|---------|-------------|
|
||||
| Auto (default) | OS preference | `@media (prefers-color-scheme: dark)` + `:root:not([data-theme])` |
|
||||
| Light | User toggle | `[data-theme="light"]` on `<html>` |
|
||||
| Dark | User toggle | `[data-theme="dark"]` on `<html>` |
|
||||
|
||||
### CSS Priority
|
||||
|
||||
```
|
||||
[data-theme="dark/light"] > @media (prefers-color-scheme) > :root (light default)
|
||||
```
|
||||
|
||||
### Implementation Pattern
|
||||
|
||||
```css
|
||||
/* Light mode (default) */
|
||||
.component { background: var(--green-100); color: var(--green-800); }
|
||||
|
||||
/* Dark mode — explicit toggle */
|
||||
[data-theme="dark"] .component {
|
||||
background: rgba(34, 197, 94, 0.2);
|
||||
color: var(--green-400);
|
||||
}
|
||||
|
||||
/* Dark mode — auto (prefers-color-scheme) */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) .component {
|
||||
background: rgba(34, 197, 94, 0.2);
|
||||
color: var(--green-400);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Key Rules
|
||||
|
||||
- **Always** use semantic tokens (`--surface-card`, `--text-color`) — they auto-adapt
|
||||
- **Status colors**: Use `rgba()` overlays in dark mode, not the inverted scale tokens
|
||||
- **Persistence**: `localStorage['user-theme']` stores user choice
|
||||
- **Toggle**: Theme button in `AppHeader.vue` cycles auto → light → dark
|
||||
|
||||
### Testing Checklist
|
||||
|
||||
1. Click theme button in header (cycles auto → light → dark)
|
||||
2. DevTools → Rendering → Emulate CSS media → `prefers-color-scheme: dark`
|
||||
3. Verify all text remains readable
|
||||
4. Verify colored sections (green, blue) are visible but not harsh
|
||||
|
||||
---
|
||||
|
||||
## 10. Accessibility
|
||||
|
||||
### Targets
|
||||
|
||||
- **WCAG 2.1 AA** compliance
|
||||
- **Color contrast**: `--color-text` provides 16.9:1 ratio, `--color-text-secondary` provides 4.6:1
|
||||
|
||||
### Focus States
|
||||
|
||||
```css
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
```
|
||||
|
||||
### Touch Targets
|
||||
|
||||
- All interactive elements: minimum 44x44px on mobile
|
||||
- Added via `min-height: 44px; min-width: 44px` on touch devices
|
||||
|
||||
### Heading Hierarchy
|
||||
|
||||
- Sequential: h1 > h2 > h3 (no skipping levels)
|
||||
- One `<h1>` per page (page title)
|
||||
- `<h2>` for sections, `<h3>` for subsections
|
||||
|
||||
### Screen Reader Support
|
||||
|
||||
- `.sr-only` class for visually hidden but accessible text
|
||||
- ARIA landmarks on major regions
|
||||
- Meaningful `alt` text on all images
|
||||
|
||||
### Reduced Motion
|
||||
|
||||
```css
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Reference Index
|
||||
|
||||
| Document | Purpose | Read When |
|
||||
|----------|---------|-----------|
|
||||
| [DESIGN_TOKENS.md](./DESIGN_TOKENS.md) | Complete token reference with all variables | Looking up specific token values |
|
||||
| [CSS_PATTERNS.md](./CSS_PATTERNS.md) | Reusable component CSS patterns | Building or styling components |
|
||||
| [ONBOARDING_CSS.md](./ONBOARDING_CSS.md) | CSS system quick start (5 min) | New to the project's frontend |
|
||||
| [MOBILE_PATTERNS.md](./MOBILE_PATTERNS.md) | Mobile component library and patterns | Building mobile views |
|
||||
| [ARCHITECTURE-DECISIONS.md](./ARCHITECTURE-DECISIONS.md) | Technical architecture decisions | Understanding "why" behind choices |
|
||||
|
||||
### CSS File Structure
|
||||
|
||||
```
|
||||
src/assets/css/
|
||||
├── core/ # Foundation: variables, tokens, md3-tokens, reset, typography
|
||||
├── layout/ # Grid, containers, navigation
|
||||
├── components/ # Cards, buttons, tables, forms, stats
|
||||
├── patterns/ # Interactive, dashboard, animations
|
||||
├── utilities/ # Spacing, display, text, flex, colors
|
||||
├── vendor/ # PrimeVue overrides (centralized, never :deep())
|
||||
├── mobile.css # Mobile optimizations (imported last)
|
||||
└── main.css # Entry point with import cascade
|
||||
```
|
||||
@@ -189,6 +189,12 @@
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* Keep compact upload action bar horizontal on mobile */
|
||||
.compact-upload-zone .action-bar {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
|
||||
@@ -73,17 +73,19 @@ const tooltipText = computed(() => {
|
||||
.confidence-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
gap: var(--space-xs);
|
||||
padding: 2px var(--space-sm);
|
||||
border-radius: var(--radius-lg);
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
/* Sizes */
|
||||
.size-small {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.1rem 0.35rem;
|
||||
padding: 1px var(--space-xs);
|
||||
border-radius: var(--radius-sm);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.size-small i {
|
||||
@@ -96,7 +98,7 @@ const tooltipText = computed(() => {
|
||||
|
||||
.size-large {
|
||||
font-size: 0.85rem;
|
||||
padding: 0.2rem 0.6rem;
|
||||
padding: 2px var(--space-sm);
|
||||
}
|
||||
|
||||
.size-large i {
|
||||
@@ -105,21 +107,55 @@ const tooltipText = computed(() => {
|
||||
|
||||
/* Confidence levels */
|
||||
.high {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
background: var(--green-100);
|
||||
color: var(--green-800);
|
||||
}
|
||||
|
||||
.medium {
|
||||
background: #fef9c3;
|
||||
color: #854d0e;
|
||||
background: var(--yellow-100);
|
||||
color: var(--yellow-800);
|
||||
}
|
||||
|
||||
.low {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
background: var(--red-100);
|
||||
color: var(--red-800);
|
||||
}
|
||||
|
||||
.percentage {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* Dark mode — explicit toggle */
|
||||
[data-theme="dark"] .high {
|
||||
background: rgba(34, 197, 94, 0.2);
|
||||
color: var(--green-400);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .medium {
|
||||
background: rgba(234, 179, 8, 0.2);
|
||||
color: var(--yellow-400);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .low {
|
||||
background: rgba(220, 38, 38, 0.2);
|
||||
color: var(--red-400);
|
||||
}
|
||||
|
||||
/* Dark mode — auto (prefers-color-scheme) */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) .high {
|
||||
background: rgba(34, 197, 94, 0.2);
|
||||
color: var(--green-400);
|
||||
}
|
||||
|
||||
:root:not([data-theme="light"]) .medium {
|
||||
background: rgba(234, 179, 8, 0.2);
|
||||
color: var(--yellow-400);
|
||||
}
|
||||
|
||||
:root:not([data-theme="light"]) .low {
|
||||
background: rgba(220, 38, 38, 0.2);
|
||||
color: var(--red-400);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -425,12 +425,17 @@ defineExpose({ reset, processOCR })
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
/* Action bar - single line */
|
||||
/* Action bar - single line (override global .action-bar from containers.css) */
|
||||
.action-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
gap: var(--space-sm);
|
||||
margin-top: 2px; /* minimal margin */
|
||||
margin-top: 2px;
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -490,7 +495,78 @@ defineExpose({ reset, processOCR })
|
||||
font-size: var(--text-xs); /* 12px - uniform */
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
/* Mobile: compact upload strip */
|
||||
@media (max-width: 768px) {
|
||||
.compact-upload-zone {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.upload-strip {
|
||||
min-height: 24px;
|
||||
padding: 1px var(--space-sm);
|
||||
}
|
||||
|
||||
.upload-strip.has-file {
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.empty-state span {
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
.file-name {
|
||||
max-width: 180px;
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
.file-size {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
/* Action bar: compact row on mobile
|
||||
Global override is in containers.css (.compact-upload-zone .action-bar) */
|
||||
.action-bar {
|
||||
margin-top: 1px;
|
||||
gap: 2px;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-bar :deep(.p-button) {
|
||||
height: 24px;
|
||||
min-width: 24px;
|
||||
padding: 2px var(--space-xs);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
/* Hide refresh button label, keep icon only */
|
||||
.action-bar :deep(.p-button .p-button-label) {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.engine-selector {
|
||||
min-width: 70px;
|
||||
max-width: 110px;
|
||||
}
|
||||
|
||||
.engine-selector :deep(.p-dropdown) {
|
||||
min-height: 24px;
|
||||
height: 24px;
|
||||
font-size: 0.7rem;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.engine-selector :deep(.p-dropdown-label) {
|
||||
padding: 2px var(--space-xs);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.engine-selector :deep(.p-dropdown-trigger) {
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra-small: hide long text */
|
||||
@media (max-width: 480px) {
|
||||
.strip-content span:not(.file-name) {
|
||||
display: none;
|
||||
|
||||
@@ -108,28 +108,28 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Row 4: TOTAL + Payments (merged on same row) -->
|
||||
<div class="form-row total-row">
|
||||
<span class="total-label">TOTAL</span>
|
||||
<InputNumber
|
||||
v-model="form.amount"
|
||||
:minFractionDigits="2"
|
||||
:maxFractionDigits="2"
|
||||
:disabled="readonly"
|
||||
placeholder="0.00"
|
||||
class="total-input"
|
||||
/>
|
||||
<OCRConfidenceIndicator
|
||||
v-if="ocrData?.confidence_amount"
|
||||
:confidence="ocrData.confidence_amount"
|
||||
size="small"
|
||||
/>
|
||||
<!-- Row 4: TOTAL + Payments (two-tier layout) -->
|
||||
<div class="form-row values-row">
|
||||
<!-- Left: TOTAL -->
|
||||
<div class="values-total">
|
||||
<span class="total-label">TOTAL</span>
|
||||
<InputNumber
|
||||
v-model="form.amount"
|
||||
:minFractionDigits="2"
|
||||
:maxFractionDigits="2"
|
||||
:disabled="readonly"
|
||||
placeholder="0.00"
|
||||
class="total-input"
|
||||
/>
|
||||
<OCRConfidenceIndicator
|
||||
v-if="ocrData?.confidence_amount"
|
||||
:confidence="ocrData.confidence_amount"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Separator vizual -->
|
||||
<span class="pay-separator">│</span>
|
||||
|
||||
<!-- Payment methods inline -->
|
||||
<div class="pay-inline">
|
||||
<!-- Right: Payment breakdown -->
|
||||
<div class="values-payments">
|
||||
<div class="pay-item">
|
||||
<span class="pay-label">Card</span>
|
||||
<InputNumber
|
||||
@@ -163,7 +163,6 @@
|
||||
class="pay-input"
|
||||
/>
|
||||
</div>
|
||||
<!-- Payment confidence -->
|
||||
<OCRConfidenceIndicator
|
||||
v-if="ocrData?.confidence_payment && hasPaymentFromOCR"
|
||||
:confidence="ocrData.confidence_payment"
|
||||
@@ -448,6 +447,31 @@ defineExpose({
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* === CONFIDENCE BADGES: fixed width to prevent input misalignment === */
|
||||
.form-row :deep(.confidence-indicator),
|
||||
.values-total :deep(.confidence-indicator),
|
||||
.values-payments > :deep(.confidence-indicator) {
|
||||
flex-shrink: 0;
|
||||
min-width: 20px;
|
||||
justify-content: center;
|
||||
opacity: 0.85;
|
||||
transition: opacity var(--transition-fast, 150ms);
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.form-row :deep(.confidence-indicator:hover),
|
||||
.values-total :deep(.confidence-indicator:hover),
|
||||
.values-payments > :deep(.confidence-indicator:hover) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Hide percentage text in compact form — info available via tooltip */
|
||||
.form-row :deep(.confidence-indicator .percentage),
|
||||
.values-total :deep(.confidence-indicator .percentage),
|
||||
.values-payments > :deep(.confidence-indicator .percentage) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* === SUPPLIER ROW === */
|
||||
.supplier-input {
|
||||
flex: 1;
|
||||
@@ -595,35 +619,41 @@ defineExpose({
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
/* === TOTAL ROW (with payments inline) === */
|
||||
.total-row {
|
||||
/* === VALUES ROW (TOTAL + Payments) === */
|
||||
.values-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap; /* FORCE single line */
|
||||
align-items: center;
|
||||
gap: var(--space-xs, 0.25rem); /* smaller gap */
|
||||
align-items: stretch;
|
||||
background: var(--green-50);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
padding: var(--space-xs, 0.25rem) var(--space-sm, 0.5rem) !important;
|
||||
margin: var(--space-xs, 0.25rem) 0;
|
||||
overflow-x: auto; /* scroll if needed on very small screens */
|
||||
border-radius: var(--radius-sm);
|
||||
padding: var(--space-sm) var(--space-md) !important;
|
||||
margin: var(--space-xs) 0;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
/* Left: TOTAL section */
|
||||
.values-total {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.total-label {
|
||||
font-weight: 700;
|
||||
font-size: var(--text-sm, 0.875rem);
|
||||
font-weight: var(--font-bold);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.total-input {
|
||||
width: 105px;
|
||||
width: 140px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.total-input :deep(.p-inputnumber-input) {
|
||||
width: 100%;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold); /* 600, not 700 */
|
||||
font-weight: var(--font-semibold);
|
||||
text-align: right;
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
height: 32px;
|
||||
@@ -633,44 +663,39 @@ defineExpose({
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
/* Payment separator */
|
||||
.pay-separator {
|
||||
color: var(--text-color-secondary);
|
||||
font-weight: 300;
|
||||
margin: 0 var(--space-xs, 0.25rem);
|
||||
}
|
||||
|
||||
/* Payment methods inline container */
|
||||
.pay-inline {
|
||||
/* Right: Payment breakdown */
|
||||
.values-payments {
|
||||
display: flex;
|
||||
flex-wrap: nowrap; /* stay on same line */
|
||||
gap: var(--space-sm, 0.5rem); /* smaller gap */
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: var(--space-xs);
|
||||
justify-content: center;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.pay-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs, 0.25rem);
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.pay-label {
|
||||
font-size: var(--text-xs); /* 12px - labels */
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--text-color-secondary);
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
.pay-input {
|
||||
width: 70px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.pay-input :deep(.p-inputnumber-input) {
|
||||
width: 100%;
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
font-size: var(--text-sm); /* 14px - uniform for inputs */
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-normal);
|
||||
text-align: right;
|
||||
height: 32px; /* uniform height */
|
||||
height: 32px;
|
||||
background: var(--surface-card);
|
||||
border: 1px solid var(--surface-border);
|
||||
border-radius: var(--radius-sm);
|
||||
@@ -849,7 +874,7 @@ defineExpose({
|
||||
}
|
||||
|
||||
/* === MOBILE STACKING === */
|
||||
@media (max-width: 480px) {
|
||||
@media (max-width: 768px) {
|
||||
.form-row {
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-xs);
|
||||
@@ -914,30 +939,47 @@ defineExpose({
|
||||
}
|
||||
|
||||
/* Payment methods stack on mobile */
|
||||
.total-row {
|
||||
flex-wrap: wrap;
|
||||
.values-row {
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.pay-separator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pay-inline {
|
||||
.values-total {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
margin-top: var(--space-xs);
|
||||
justify-content: flex-start;
|
||||
padding-bottom: var(--space-sm);
|
||||
border-bottom: 1px dashed var(--green-200);
|
||||
}
|
||||
|
||||
.total-input {
|
||||
flex: 1;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.values-payments {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.pay-item {
|
||||
flex: 1 1 auto;
|
||||
min-width: 80px;
|
||||
flex: 1 1 0%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pay-input {
|
||||
flex: 1;
|
||||
width: auto;
|
||||
min-width: 60px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra-small phones: stack payments vertically */
|
||||
@media (max-width: 360px) {
|
||||
.values-payments {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pay-item {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -968,7 +1010,7 @@ defineExpose({
|
||||
color: var(--cyan-300);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .total-row {
|
||||
[data-theme="dark"] .values-row {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user