560 lines
12 KiB
Markdown
560 lines
12 KiB
Markdown
# inou Flutter Styleguide Gap Analysis
|
|
|
|
**Generated:** 2025-01-28
|
|
**Purpose:** Document gaps between the original web styleguide and the current Flutter implementation to achieve closer visual parity.
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
The Flutter app has a good foundation with `InouTheme` properly implementing the design tokens. However, there are several gaps in how those tokens are applied consistently across the app. The main issues are:
|
|
|
|
1. **Typography inconsistencies** - font weights and sizes don't always match the web
|
|
2. **Missing CSS-specific styling** - dashed borders, hover states, transitions
|
|
3. **Component styling gaps** - buttons, inputs, and badges not pixel-perfect
|
|
4. **Layout structure** - page layout and spacing inconsistencies
|
|
|
|
---
|
|
|
|
## 1. Typography Gaps
|
|
|
|
### Current vs Original
|
|
|
|
| Style | Web CSS | Flutter Implementation | Gap |
|
|
|-------|---------|------------------------|-----|
|
|
| Page Title (h1Large) | 2.5rem (37.5px), 700 | 37.5px, w700 | ✅ Matches |
|
|
| h1 | 2.25rem (33.75px), 300, -0.03em | 33.75px, w300, -0.03em | ✅ Matches |
|
|
| h2 Section Title | 1.4rem (21px), 600 | 21px, w600 | ✅ Matches |
|
|
| h3 Subsection | 1.1rem (16.5px), 600 | 16.5px, w600 | ✅ Matches |
|
|
| Intro text | 1.15rem (17.25px), 300, line-height 1.8 | 17.25px, w300, height 1.8 | ✅ Matches |
|
|
| Body light | 1rem (15px), 300, line-height 1.8 | Missing `bodyLight` in some places | ⚠️ Inconsistent use |
|
|
| Body regular | 1rem (15px), 400 | 15px, w400 | ✅ Matches |
|
|
| Label/Category | 0.75rem (11.25px), 600, uppercase, 0.1em | 11.25px, w600, 0.1em spacing | ⚠️ `toUpperCase()` not always called |
|
|
| Mono | SF Mono, 0.85rem (12.75px) | SF Mono, 12.75px | ⚠️ Fallback fonts missing |
|
|
|
|
### Fixes Needed
|
|
|
|
```dart
|
|
// In InouTheme, add missing style variants:
|
|
|
|
static TextStyle get bodyLight => GoogleFonts.sora(
|
|
fontSize: 15.0,
|
|
fontWeight: FontWeight.w300, // Light, for long-form content
|
|
color: textMuted,
|
|
height: 1.8,
|
|
);
|
|
|
|
// Mono font needs fallbacks
|
|
static TextStyle get mono => TextStyle(
|
|
fontFamily: 'SF Mono',
|
|
fontFamilyFallback: const ['Monaco', 'Consolas', 'monospace'],
|
|
fontSize: 12.75,
|
|
fontWeight: FontWeight.w400,
|
|
color: text,
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Color Gaps
|
|
|
|
### Theme Colors ✅
|
|
All colors match the design tokens exactly:
|
|
- `bg: #F8F7F6` ✅
|
|
- `bgCard: #FFFFFF` ✅
|
|
- `border: #E5E2DE` ✅
|
|
- `text: #1C1917` ✅
|
|
- `textMuted: #78716C` ✅
|
|
- `textSubtle: #A8A29E` ✅
|
|
- `accent: #B45309` ✅
|
|
- All indicator colors ✅
|
|
|
|
### Missing Usage Patterns
|
|
|
|
**Issue:** The web uses `opacity: 0.6` for "coming soon" and disabled states. Flutter uses hardcoded opacity.
|
|
|
|
```dart
|
|
// Web CSS:
|
|
.data-card.coming-soon { opacity: 0.6; }
|
|
.btn-disabled { opacity: 0.6; }
|
|
|
|
// Flutter should use consistent disabled opacity:
|
|
static const double disabledOpacity = 0.6;
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Spacing Gaps
|
|
|
|
### Spacing Scale ✅
|
|
All spacing tokens match:
|
|
- xs: 4px ✅
|
|
- sm: 8px ✅
|
|
- md: 12px ✅
|
|
- lg: 16px ✅
|
|
- xl: 24px ✅
|
|
- xxl: 32px ✅
|
|
- xxxl: 48px ✅
|
|
|
|
### Usage Inconsistencies
|
|
|
|
**Issue:** The dossier page uses hardcoded padding values instead of theme constants.
|
|
|
|
```dart
|
|
// Current (in dossier_page.dart):
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 48),
|
|
|
|
// Should be:
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: InouTheme.spaceXl,
|
|
vertical: InouTheme.spaceXxxl,
|
|
),
|
|
```
|
|
|
|
**Issue:** Card margins not consistent with web.
|
|
|
|
```dart
|
|
// Web CSS:
|
|
.data-card { margin-bottom: 16px; }
|
|
|
|
// Flutter InouCard uses spaceLg (16px) ✅ but some pages override
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Border Radius Gaps
|
|
|
|
### Radius Values ✅
|
|
All match the design tokens.
|
|
|
|
### Missing Application
|
|
|
|
**Issue:** Some components don't use the theme radius:
|
|
|
|
```dart
|
|
// Current (in various places):
|
|
borderRadius: BorderRadius.circular(4),
|
|
|
|
// Should use:
|
|
borderRadius: InouTheme.borderRadiusSm,
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Component Gaps
|
|
|
|
### 5.1 Buttons
|
|
|
|
**Web CSS:**
|
|
```css
|
|
.btn {
|
|
padding: 10px 18px;
|
|
font-size: 1rem;
|
|
font-weight: 500;
|
|
border-radius: 6px;
|
|
transition: all 0.15s;
|
|
}
|
|
.btn-small {
|
|
padding: 6px 12px;
|
|
font-size: 1rem; /* Same font size as regular */
|
|
}
|
|
```
|
|
|
|
**Flutter Current:**
|
|
```dart
|
|
final padding = isSmall
|
|
? const EdgeInsets.symmetric(horizontal: 12, vertical: 6)
|
|
: const EdgeInsets.symmetric(horizontal: 18, vertical: 10);
|
|
// Font size differs: small uses 14px, regular uses 15px
|
|
```
|
|
|
|
**Fix:**
|
|
```dart
|
|
// Both sizes should use 15px (1rem) font
|
|
textStyle: InouTheme.labelLarge.copyWith(
|
|
fontSize: 15, // Always 15px, not 14 for small
|
|
),
|
|
```
|
|
|
|
### 5.2 Badges
|
|
|
|
**Web CSS:**
|
|
```css
|
|
.badge {
|
|
padding: 2px 8px;
|
|
font-size: 1rem; /* 15px */
|
|
font-weight: 500;
|
|
border-radius: 4px;
|
|
background: var(--accent-light);
|
|
color: var(--accent);
|
|
}
|
|
.badge-care {
|
|
background: var(--success-light);
|
|
color: var(--success);
|
|
}
|
|
```
|
|
|
|
**Flutter Current:** (inou_badge.dart)
|
|
- Padding: 4h, 8v (should be 2v, 8h) ❌
|
|
- Font size: uses bodySmall (12.75px) instead of 15px ❌
|
|
|
|
**Fix:**
|
|
```dart
|
|
// InouBadge needs updating:
|
|
static TextStyle get badgeText => GoogleFonts.sora(
|
|
fontSize: 15.0,
|
|
fontWeight: FontWeight.w500,
|
|
color: text,
|
|
);
|
|
|
|
// Padding should be:
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
|
```
|
|
|
|
### 5.3 Data Cards
|
|
|
|
**Web CSS:**
|
|
```css
|
|
.data-card-header {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 16px;
|
|
gap: 12px;
|
|
}
|
|
.data-card-indicator {
|
|
width: 4px;
|
|
height: 32px;
|
|
border-radius: 2px;
|
|
}
|
|
.section-heading {
|
|
font-size: 0.75rem; /* 11.25px */
|
|
font-weight: 600;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
}
|
|
```
|
|
|
|
**Flutter Current:**
|
|
The `_DataCard` in dossier_page.dart mostly matches, but:
|
|
- Letter spacing is 0.8 instead of 0.08em (0.9px) ❌
|
|
- Not using `InouTheme.labelSmall` which has correct specs
|
|
|
|
**Fix:**
|
|
```dart
|
|
// In _DataCard header:
|
|
Text(
|
|
title, // Don't call toUpperCase() here
|
|
style: InouTheme.labelSmall, // Already has correct specs + uppercase handling
|
|
),
|
|
```
|
|
|
|
### 5.4 Data Rows
|
|
|
|
**Web CSS:**
|
|
```css
|
|
.data-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 16px;
|
|
border-bottom: 1px dashed var(--border); /* DASHED! */
|
|
}
|
|
.data-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
.data-row.child {
|
|
padding-left: 48px;
|
|
}
|
|
```
|
|
|
|
**Flutter Current:**
|
|
- Uses solid borders instead of dashed ❌
|
|
- Child row padding is 32px instead of 48px ❌
|
|
|
|
**Fix:**
|
|
```dart
|
|
// Flutter can't do dashed borders easily, but can simulate with DashedLine widget
|
|
// or use a dotted pattern via CustomPaint
|
|
|
|
// For child padding:
|
|
padding: const EdgeInsets.only(left: 48, right: 16, top: 12, bottom: 12),
|
|
```
|
|
|
|
### 5.5 Form Inputs
|
|
|
|
**Web CSS:**
|
|
```css
|
|
.form-group input {
|
|
width: 100%;
|
|
padding: 10px 12px;
|
|
font-size: 1rem;
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
transition: border-color 0.15s, box-shadow 0.15s;
|
|
}
|
|
.form-group input:focus {
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 3px var(--accent-light);
|
|
}
|
|
```
|
|
|
|
**Flutter Current:**
|
|
The theme's `InputDecorationTheme` uses 2px border on focus, not 1px + shadow.
|
|
|
|
**Fix:**
|
|
```dart
|
|
// In InputDecorationTheme:
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: borderRadiusMd,
|
|
borderSide: BorderSide(color: accent, width: 1), // 1px, not 2px
|
|
),
|
|
// Add focus shadow via Container wrapper or InputDecoration
|
|
```
|
|
|
|
### 5.6 Messages (Error/Info/Success)
|
|
|
|
**Web CSS:**
|
|
```css
|
|
.error {
|
|
background: var(--danger-light);
|
|
border: 1px solid #FECACA;
|
|
color: var(--danger);
|
|
padding: 10px 14px;
|
|
border-radius: 6px;
|
|
}
|
|
.info {
|
|
background: var(--accent-light);
|
|
border: 1px solid #FDE68A;
|
|
color: var(--accent);
|
|
}
|
|
.success {
|
|
background: var(--success-light);
|
|
border: 1px solid #A7F3D0;
|
|
color: var(--success);
|
|
}
|
|
```
|
|
|
|
**Flutter Current:**
|
|
`InouMessage` widget exists but check border colors.
|
|
|
|
**Add specific border colors:**
|
|
```dart
|
|
static const Color errorBorder = Color(0xFFFECACA);
|
|
static const Color infoBorder = Color(0xFFFDE68A);
|
|
static const Color successBorder = Color(0xFFA7F3D0);
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Layout Gaps
|
|
|
|
### 6.1 Page Container
|
|
|
|
**Web CSS:**
|
|
```css
|
|
.container {
|
|
max-width: 800px; /* maxWidthNarrow */
|
|
margin: 0 auto;
|
|
padding: 40px 20px;
|
|
}
|
|
.container-narrow {
|
|
max-width: 360px; /* maxWidthForm */
|
|
padding: 60px 20px 40px;
|
|
}
|
|
```
|
|
|
|
**Flutter:** Uses `InouPage` and `InouAuthFlowPage` which are close but:
|
|
- Auth pages should use `maxWidthForm` (360px) ✅
|
|
- Dossier uses `maxWidth` (1200px) but should probably use `maxWidthNarrow` (800px) ❌
|
|
|
|
### 6.2 Nav Bar
|
|
|
|
**Web CSS:**
|
|
```css
|
|
.nav {
|
|
padding: 12px 24px;
|
|
max-width: 1200px;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.logo {
|
|
font-size: 1.75rem;
|
|
font-weight: 700;
|
|
letter-spacing: -0.02em;
|
|
text-transform: lowercase;
|
|
}
|
|
```
|
|
|
|
**Flutter Current:**
|
|
- Logo font size uses `h3` style (~18px) instead of 1.75rem (26.25px) ❌
|
|
- Missing letter-spacing: -0.02em ❌
|
|
|
|
**Fix:**
|
|
```dart
|
|
// In InouHeader _buildLogo():
|
|
Text(
|
|
'inou',
|
|
style: GoogleFonts.sora(
|
|
fontSize: 26.25, // 1.75rem
|
|
fontWeight: FontWeight.w700,
|
|
color: InouTheme.accent,
|
|
letterSpacing: -0.02 * 26.25, // -0.02em
|
|
),
|
|
),
|
|
```
|
|
|
|
### 6.3 Footer
|
|
|
|
**Web CSS:**
|
|
```css
|
|
.footer {
|
|
margin-top: 40px;
|
|
padding-top: 12px;
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
```
|
|
|
|
**Flutter:** Check `InouFooter` for consistency.
|
|
|
|
---
|
|
|
|
## 7. Missing Features
|
|
|
|
### 7.1 Dashed Borders
|
|
The web uses `border-bottom: 1px dashed var(--border)` for data rows. Flutter doesn't support dashed borders natively.
|
|
|
|
**Solutions:**
|
|
1. Use `dotted_border` package
|
|
2. Custom `CustomPainter` for dashed lines
|
|
3. Accept solid borders (simpler but less accurate)
|
|
|
|
### 7.2 Hover States
|
|
Web has hover effects everywhere. Flutter web supports hover but mobile doesn't.
|
|
|
|
**Solutions:**
|
|
1. Use `InkWell` with splash colors
|
|
2. On web, implement `MouseRegion` for hover states
|
|
3. Accept that mobile won't have hover
|
|
|
|
### 7.3 Transitions
|
|
Web CSS has `transition: all 0.15s` on interactive elements.
|
|
|
|
**Solution:**
|
|
Add `AnimatedContainer` or `AnimatedDefaultTextStyle` where appropriate.
|
|
|
|
---
|
|
|
|
## 8. Priority Fixes
|
|
|
|
### High Priority (Visual Impact)
|
|
1. **Badge font size and padding** - very noticeable
|
|
2. **Logo size in header** - brand consistency
|
|
3. **Button small font size** - should match regular
|
|
4. **Data card title letter-spacing** - subtle but important
|
|
|
|
### Medium Priority
|
|
5. **Child row padding** (32px → 48px)
|
|
6. **Dossier page max-width** (1200px → 800px)
|
|
7. **Input focus style** (2px → 1px + shadow)
|
|
8. **Message border colors** - semantic colors
|
|
|
|
### Low Priority (Polish)
|
|
9. Dashed borders (requires package or custom paint)
|
|
10. Hover states (platform-specific)
|
|
11. Transitions (nice to have)
|
|
|
|
---
|
|
|
|
## 9. Recommended Actions
|
|
|
|
### Immediate (Theme File Updates)
|
|
|
|
```dart
|
|
// Add to InouTheme:
|
|
|
|
// 1. Badge text style
|
|
static TextStyle get badgeText => GoogleFonts.sora(
|
|
fontSize: 15.0,
|
|
fontWeight: FontWeight.w500,
|
|
);
|
|
|
|
// 2. Logo style
|
|
static TextStyle get logo => GoogleFonts.sora(
|
|
fontSize: 26.25,
|
|
fontWeight: FontWeight.w700,
|
|
letterSpacing: -0.02 * 26.25,
|
|
);
|
|
|
|
// 3. Message border colors
|
|
static const Color errorBorder = Color(0xFFFECACA);
|
|
static const Color infoBorder = Color(0xFFFDE68A);
|
|
static const Color successBorder = Color(0xFFA7F3D0);
|
|
|
|
// 4. Consistent disabled opacity
|
|
static const double disabledOpacity = 0.6;
|
|
```
|
|
|
|
### Widget Updates
|
|
|
|
1. **InouBadge** - Fix padding (2v, 8h) and font size (15px)
|
|
2. **InouButton** - Use 15px for small buttons too
|
|
3. **InouHeader** - Use new logo style
|
|
4. **InouCard** - Ensure labelSmall is used correctly
|
|
|
|
### Page Updates
|
|
|
|
1. **DossierPage** - Change max-width to 800px
|
|
2. **All pages** - Use InouTheme spacing constants instead of hardcoded values
|
|
|
|
---
|
|
|
|
## 10. Testing Checklist
|
|
|
|
After applying fixes, verify:
|
|
|
|
- [ ] Landing page hero text matches web
|
|
- [ ] Login form looks identical to web
|
|
- [ ] Dashboard profile cards match
|
|
- [ ] Dossier data cards and rows match
|
|
- [ ] Badges render at correct size
|
|
- [ ] Buttons (both sizes) match
|
|
- [ ] Header logo size and spacing correct
|
|
- [ ] Footer styling matches
|
|
- [ ] Messages (error/info/success) match
|
|
- [ ] Form inputs match (esp. focus state)
|
|
|
|
---
|
|
|
|
## Appendix: Quick Reference
|
|
|
|
### Font Sizes (Base 15px)
|
|
| Name | rem | px |
|
|
|------|-----|-----|
|
|
| h1Large | 2.5 | 37.5 |
|
|
| h1 | 2.25 | 33.75 |
|
|
| h2 | 1.4 | 21 |
|
|
| h3 | 1.1 | 16.5 |
|
|
| intro | 1.15 | 17.25 |
|
|
| body | 1.0 | 15 |
|
|
| small | 0.85 | 12.75 |
|
|
| label | 0.75 | 11.25 |
|
|
| logo | 1.75 | 26.25 |
|
|
|
|
### Key Spacing
|
|
| Use Case | Value |
|
|
|----------|-------|
|
|
| Card margin-bottom | 16px |
|
|
| Card padding | 16px |
|
|
| Header padding | 12px 24px |
|
|
| Container padding | 40px 20px |
|
|
| Button padding | 10px 18px |
|
|
| Button small padding | 6px 12px |
|
|
| Badge padding | 2px 8px |
|
|
|
|
### Border Radius
|
|
| Use Case | Value |
|
|
|----------|-------|
|
|
| Buttons | 6px (radiusMd) |
|
|
| Cards | 8px (radiusLg) |
|
|
| Badges | 4px (radiusSm) |
|
|
| Inputs | 6px (radiusMd) |
|