Initial commit: nick docs

This commit is contained in:
moojttaba
2026-05-23 20:35:34 +03:30
commit 0da235ae27
90 changed files with 18268 additions and 0 deletions

View File

@@ -0,0 +1,207 @@
---
title: Colors
tags: [design-system, colors, palette]
created: 2026-05-23
---
# Colors
The palette is built from semantic groups (`primary`, `secondary`, `info`, `success`, `warning`, `error`, plus a 9-step `grey` scale) and exposed via the MUI theme. **Never hard-code hex values in components.**
> [!warning]
> Hardcoded colors break dark mode and any future preset switch. Use `sx={{ color: 'primary.main' }}` or `theme.palette.primary.main`.
---
## 1. Semantic groups
Every color group has the shape:
```ts
{
lighter: '#…', // tint background, alerts
light: '#…', // hover surfaces, badges
main: '#…', // primary use (buttons, links)
dark: '#…', // hover state of `main`
darker: '#…', // pressed state
contrastText: '#…', // WCAG AA on `main`
}
```
### 1.1 Primary
| Token | Light hex (default preset) | Dark hex |
|---|---|---|
| `primary.lighter` | `#D0ECFE` | `#CCF4FE` |
| `primary.light` | `#73BAFB` | `#68CDF9` |
| `primary.main` | `#1877F2` | `#078DEE` |
| `primary.dark` | `#0C44AE` | `#0351AB` |
| `primary.darker` | `#042174` | `#012972` |
| `primary.contrastText` | `#FFFFFF` | `#FFFFFF` |
Used for primary actions, links, focused inputs.
### 1.2 Secondary
Typical: a complementary purple/teal. Confirm by reading `theme/options/palette.ts`.
### 1.3 Info / Success / Warning / Error
Standard semantic colors:
| Group | Hue | Used for |
|---|---|---|
| `info` | cyan/blue | Neutral informative banners |
| `success` | green | Confirmed payments, accepted offers |
| `warning` | amber | Pending state, mild alerts |
| `error` | red | Disputes, validation errors, destructive |
Each follows the same `lighter/light/main/dark/darker/contrastText` shape.
### 1.4 Grey
Nine-step scale (100 → 900). Use for:
- `grey.100``grey.300` → backgrounds, dividers, disabled
- `grey.500` → secondary text, muted icons
- `grey.700``grey.900` → primary text, dark headings
### 1.5 Text & Background
```ts
text: {
primary: grey[800], // body
secondary: grey[600], // muted
disabled: grey[500],
},
background: {
default: '#FFFFFF', // light mode page
paper: '#FFFFFF', // cards, dialogs
neutral: grey[200], // page wash
},
```
In dark mode these invert (`default` becomes near-black, `paper` slightly lighter, `text.primary` near-white).
---
## 2. Action states
```ts
action: {
active: alpha(grey[600], 1),
hover: alpha(grey[500], 0.08),
selected: alpha(grey[500], 0.16),
disabled: alpha(grey[500], 0.8),
disabledBackground: alpha(grey[500], 0.24),
focus: alpha(grey[500], 0.24),
hoverOpacity: 0.08,
disabledOpacity: 0.48,
}
```
Use via `sx={{ bgcolor: 'action.hover' }}` etc.
---
## 3. Color presets
The settings drawer ships several **color presets** that swap only `primary` (and optionally `secondary`) while keeping every other group stable. Typical presets (confirm in `theme/options/presets/`):
| Preset | Primary hue | Vibe |
|---|---|---|
| `default` | Blue | Trust, finance |
| `purple` | Indigo/Purple | Premium |
| `cyan` | Cyan | Tech, fresh |
| `blue` | Vibrant blue | Strong CTA |
| `orange` | Orange | Energetic |
| `red` | Brand red | Bold |
To add a preset:
1. Add a `<name>.ts` file in `theme/options/presets/` exporting `{ lighter, light, main, dark, darker, contrastText }`.
2. Register in the presets index map.
3. Surface in the settings drawer color picker (`src/settings/drawer/`).
---
## 4. Contrast verification
WCAG AA targets:
| Element | Min contrast |
|---|---|
| Body text (≥18 px regular or ≥14 px bold) | **4.5 : 1** |
| Large text (≥24 px regular or ≥18 px bold) | **3 : 1** |
| Icon-only buttons | **3 : 1** for their boundary |
| Form input borders | **3 : 1** |
> [!tip]
> When picking a new `main` color, run it through [Stark](https://stark.co/) or the Chrome DevTools "Inspect color" checker against `background.paper` AND `background.default` for both light and dark modes.
---
## 5. Using colors in `sx`
```tsx
// Token-based — preferred
<Box sx={{ bgcolor: 'primary.main', color: 'primary.contrastText' }} />
// Function form when you need to derive
<Box sx={(theme) => ({ bgcolor: alpha(theme.palette.primary.main, 0.08) })} />
// In styled component
const Item = styled('div')(({ theme }) => ({
background: theme.palette.background.paper,
color: theme.palette.text.primary,
}));
```
---
## 6. Charts & data viz
Use the helper `theme.palette.<group>.main` array for series colors. The `chart/` component family (ApexCharts wrapper) reads these tokens so the chart palette stays in sync with the theme.
For categorical data with >7 series, layer in `secondary.main`, `info.main`, `success.main`, `warning.main`, `error.main`, then darker variants — never invent ad-hoc hex.
---
## 7. Diff visualisation (chat, evidence)
| Concept | Color token |
|---|---|
| Added / accepted | `success.main` with `success.lighter` background |
| Removed / rejected | `error.main` with `error.lighter` background |
| Pending review | `warning.main` |
| Informational | `info.main` |
---
## 8. Avatar backgrounds
`MuiAvatar` override hashes the user's name to one of the semantic `main` colors so the same person always gets the same background. Implementation in `components/user-avatar/`.
---
## 9. Brand vs semantic
| Use case | Token |
|---|---|
| Primary CTA button | `primary.main` |
| Success snackbar | `success.main` |
| Error border on form field | `error.main` |
| Page background | `background.default` |
| Card background | `background.paper` |
| Sidebar background | `background.neutral` |
| Divider line | `divider` |
| Primary text | `text.primary` |
| Muted helper text | `text.secondary` |
---
## 10. Related
- [[Theme Configuration]] · [[Design System Overview]] · [[Typography]]
- [[Components]] — components consuming these tokens
- [[Settings & Theming]] — preset switcher

View File

@@ -0,0 +1,222 @@
---
title: Components
tags: [design-system, components, ui]
created: 2026-05-23
---
# Components
Inventory of reusable components in `frontend/src/components/`. Each component lives in its own folder (`index.ts` + `<name>.tsx` + `classes.ts` + `types.ts`).
> [!info]
> Pattern: components are reusable **and free of business logic**. Page-specific composition lives in `frontend/src/sections/`. Hooks that fetch data live in `frontend/src/hooks/`.
---
## 1. Form components (`components/hook-form/`)
RHF + MUI wrappers. Used inside a `<FormProvider {...methods}>` context. All accept `name`, plus the MUI props of the underlying component.
| Component | Wraps | Notes |
|---|---|---|
| `RHFTextField` | `TextField` | The workhorse text input |
| `RHFSelect` | `Select` + `MenuItem` | Pass `children={<MenuItem value="..."/>...}` |
| `RHFAutocomplete` | `Autocomplete` | Async-loader friendly; pass `options` |
| `RHFCheckbox` | `Checkbox` + `FormControlLabel` | Single boolean |
| `RHFMultiCheckbox` | grid of checkboxes | Returns `string[]` |
| `RHFRadioGroup` | `RadioGroup` | Returns selected value |
| `RHFSwitch` | `Switch` + label | Boolean toggle |
| `RHFEditor` | TipTap wrapper | HTML output |
| `RHFUpload` | `components/upload` Dropzone | Single or multiple |
| `RHFDatePicker` | `@mui/x-date-pickers DatePicker` | Locale-aware |
| `RHFDateTimePicker` | `DateTimePicker` | — |
| `RHFTimePicker` | `TimePicker` | — |
| `RHFPhoneInput` | `react-phone-number-input` | E.164 output |
| `RHFCountrySelect` | `components/country-select` | ISO-2 output |
| `RHFNumberInput` | `components/number-input` | Locale-formatted, accepts min/max/step |
| `RHFRating` | `Rating` | 05 |
| `RHFSlider` | `Slider` | Single or range |
> [!tip]
> Form-level errors render automatically via `formState.errors[name]` — pass `helperText={errors.name?.message}` is **not needed**; the wrapper handles it.
---
## 2. Data display
| Component | Purpose |
|---|---|
| `custom-data-grid/` | MUI DataGrid Pro wrapper — sets density, locale, custom toolbar, RTL support |
| `table/` | Lower-level table utilities (head, pagination, no-data row, skeleton) |
| `chart/` | ApexCharts wrapper with theme-synced palette |
| `markdown/` | `react-markdown` + `remark-gfm` for chat/evidence rendering |
| `lightbox/` | Image gallery (gallery / dispute evidence) |
| `image/` | Next `Image` wrapper with fallback skeleton + error state |
| `file-thumbnail/` | Renders icon by MIME type for non-image files |
| `flag-icon/` | Country flag SVG (used in phone input, locale switcher) |
| `iconify/` | Iconify SVG wrapper — `<Iconify icon="solar:user-bold" />` |
| `svg-color/` | Renders SVG with `color` prop (mask-image trick) |
---
## 3. Navigation
| Component | Purpose |
|---|---|
| `nav-section/` | Sidebar nav with role-filtering, group headers, mini variant |
| `nav-basic/` | Simple horizontal nav for marketing pages |
| `custom-breadcrumbs/` | Page header breadcrumbs + action slot |
| `progress-bar/` | NProgress integration for route transitions |
The sidebar reads from a configuration array — see `src/layouts/dashboard/config-nav-dashboard.ts` (or similar) — where each entry can specify `roles: ['admin']` to hide from other users.
---
## 4. Feedback
| Component | Purpose |
|---|---|
| `snackbar/` | Notistack wrapper — `enqueueSnackbar('Done', { variant: 'success' })` |
| `loading-screen/` | Full-screen splash for boot or guarded routes |
| `empty-content/` | "No results" placeholder with illustration |
| `search-not-found/` | Variant of empty-content for failed searches |
| `email-verification-banner/` | Persistent top banner when email unverified |
---
## 5. Overlays & dialogs
| Component | Purpose |
|---|---|
| `custom-dialog/` | Modal dialog with title/content/actions slots |
| `custom-popover/` | Click-or-hover popover (used for menus, tooltips, info) |
---
## 6. Inputs (raw, non-RHF)
| Component | Purpose |
|---|---|
| `number-input/` | Numeric with +/- buttons + locale format |
| `phone-input/` | Phone with country selector |
| `country-select/` | Country dropdown |
| `incrementer-button/` | Tiny `` / `+` quantity stepper |
| `upload/` | Dropzone with progress, preview, removal |
---
## 7. User & profile
| Component | Purpose |
|---|---|
| `user-avatar/` | Hashes name → color; respects `src` if avatar uploaded |
| `user-profile-card/` | Profile summary card (name, role, level, points) |
| `notifications-drawer/` | Side drawer listing notifications, mark-as-read |
---
## 8. Media
| Component | Purpose |
|---|---|
| `video-player/` | Plyr / video.js wrapper for blog & template videos |
| `map/` | Mapbox + react-map-gl wrapper (delivery addresses) |
| `carousel/` | Embla carousel — multiple variants in `hooks/use-carousel-*` |
---
## 9. Animation
| Component | Purpose |
|---|---|
| `animate/` | Framer-motion presets (fade, slide, scale) for page transitions and lists |
---
## 10. Utility
| Component | Purpose |
|---|---|
| `scrollbar/` | SimpleBar wrapper — themed scrollbar with shadow on overflow |
| `label/` | Tag/badge — variants: filled, outlined, soft |
| `filters-result/` | Active filter chips with × to remove |
| `logo/` | App logo SVG (responsive: full mark + icon-only at small width) |
| `color-utils/` | Color manipulation helpers (alpha, darken, contrast) |
---
## 11. Settings
| Component | Purpose |
|---|---|
| `settings/` | The mounted Settings drawer + provider hooks |
---
## 12. Debug
| Component | Purpose |
|---|---|
| `debug/` | Dev-only diagnostic overlays (only rendered when `NEXT_PUBLIC_ENABLE_DEBUG=true`) |
---
## 13. Editor (`components/editor/`)
TipTap rich-text editor with this extension set:
- `StarterKit` (paragraph, headings, bold/italic, lists, blockquote, history)
- `Link` (URL parsing, target=_blank by default for external)
- `Image` (drag-drop, uploads via `components/upload`)
- `Underline`, `TextAlign`
- `CodeBlock` + `CodeBlockLowlight` (syntax highlighting via `lowlight`)
- `Placeholder`
Output: HTML string. Round-trip safe with `react-markdown` + `turndown` when Markdown storage is preferred.
Used in:
- Blog post editor (`dashboard/post/new`)
- Long-form description fields (request, template)
- Dispute evidence narrative
---
## 14. Payment-specific (`components/payment/`)
| Component | Purpose |
|---|---|
| `<TokenSelector>` | USDT / USDC / BTC / ETH chip selector |
| `<NetworkSelector>` | BSC / TRC20 / Ethereum / Polygon chip selector |
| `<QrInvoice>` | Renders the SHKeeper invoice QR + copy-address button |
| `<PaymentStatus>` | Live status pill that updates from socket events |
(Confirm names against actual exports — these are typical for this stack.)
---
## 15. Adding a new component
1. Create `src/components/<kebab-name>/` with `index.ts`, `<kebab-name>.tsx`, `classes.ts`, `types.ts`.
2. Re-export from `src/components/<kebab-name>/index.ts`: `export * from './<kebab-name>'`.
3. Place ONLY rendering + styling; do NOT call `useQuery` / `useMutation` — pass data via props.
4. Use the `sx` prop, not styled-components, unless the styling is non-trivial.
5. Cover with at least one Jest snapshot test in `__tests__/`.
---
## 16. Component vs Section vs Hook decision
| Question | Where it goes |
|---|---|
| "I want to render a chip with a delete icon." | `components/` |
| "I want a chat sidebar with conversation list + active chat + composer." | `sections/chat/` |
| "I want to subscribe to chat updates and keep the cache in sync." | `hooks/use-chat-socket.ts` |
---
## 17. Related
- [[Design System Overview]] · [[Theme Configuration]] · [[Colors]] · [[Typography]]
- [[Layouts]] · [[Internationalization & RTL]]
- [[Frontend Architecture]] — where these components live in the bigger picture
- [[Coding Standards]] — file layout & import order rules

View File

@@ -0,0 +1,179 @@
---
title: Design System Overview
tags: [design-system, ui, mui]
created: 2026-05-23
---
# Design System Overview
The frontend design system is built on **Material-UI v7** with project-specific tokens, an LTR + RTL-aware emotion cache, and a user-controllable settings drawer (mode, layout, color preset, font, direction).
> [!info]
> All design decisions documented here are enforced by the cursor rules at `backend/.cursor/rules/ui-development-standards.mdc`. When in doubt, that file is the authoritative source.
---
## 1. Philosophy
- **Material first, brand second.** Use MUI components and the `sx` prop with array syntax for responsive overrides. Do NOT subclass primitives unless absolutely necessary.
- **Tokens over magic numbers.** All colors, spacings, radii, and shadows come from the theme — never hard-code hex values or pixel offsets.
- **RTL-first.** Persian and Arabic are first-class. Layouts must mirror correctly without manual `direction` overrides.
- **Accessibility-by-default.** WCAG AA contrast (4.5:1), semantic HTML, keyboard navigation, ARIA labels on icon-only buttons.
- **One component, one folder.** Each component lives in `components/<name>/` with `index.ts` + `component.tsx` + `classes.ts` + `types.ts` so it can be refactored without grep-and-replace pain.
- **Compose, don't inherit.** Sections compose components, components compose primitives, primitives wrap MUI.
---
## 2. Component layering
```mermaid
flowchart TB
P[Pages — src/app/]
S[Sections — src/sections/]
C[Components — src/components/]
L[Layouts — src/layouts/]
H[Hooks — src/hooks/]
T[Theme — src/theme/]
M[MUI v7]
P --> S
P --> L
S --> C
C --> M
L --> C
L --> M
C --> H
S --> H
M --> T
```
- **Pages** are thin — they mount a section and pass URL params.
- **Sections** are page-specific compositions of components + hooks + queries.
- **Components** are reusable, free of business logic.
- **Layouts** wrap pages with sidebar / topbar / auth scaffolding.
---
## 3. Token system
The theme exposes these token groups (see [[Theme Configuration]] for exact values):
| Group | Examples | Used as |
|---|---|---|
| **Palette** | `primary.main`, `error.dark`, `grey.500`, `background.paper` | `sx={{ color: 'primary.main' }}` |
| **Typography** | `h1`-`h6`, `subtitle1/2`, `body1/2`, `caption`, `overline`, `button` | `<Typography variant="h2">` |
| **Spacing** | unit = 8 px; use multiples: `spacing(1)` = 8, `spacing(2)` = 16, … | `sx={{ p: 2, mt: 3 }}` |
| **Shape** | `borderRadius` = 8 (default), 12 for cards | `sx={{ borderRadius: 1 }}` |
| **Shadows** | `shadows[1]``shadows[24]` + custom `customShadows` | `sx={{ boxShadow: 1 }}` |
| **Breakpoints** | `xs`/`sm`/`md`/`lg`/`xl` = 0/600/900/1200/1536 | `sx={{ flexDirection: { xs: 'column', md: 'row' } }}` |
| **Z-index** | named layers: `appBar`, `drawer`, `modal`, `snackbar`, `tooltip` | `sx={{ zIndex: 'modal' }}` |
The unit of spacing is **8 px**. Never use `padding: '14px'` — use `sx={{ p: 1.75 }}` if you really need it (or, better, fix the value to a multiple).
---
## 4. Modes
The app supports three independent user-controllable axes:
| Axis | Values | Persistence |
|---|---|---|
| Mode | `light` · `dark` · `system` | `localStorage.settings.mode` |
| Contrast | `default` · `bold` | `localStorage.settings.contrast` |
| Layout | `vertical` · `mini` · `horizontal` | `localStorage.settings.layout` |
| Direction | `ltr` · `rtl` | `localStorage.settings.direction` (auto-set from locale) |
| Color preset | one of N curated palettes | `localStorage.settings.colorPresets` |
| Font family | per theme defaults or overridden | `localStorage.settings.fontFamily` |
Toggled from the **Settings Drawer** (`src/settings/drawer/`). See [[Settings & Theming]].
---
## 5. Folder structure
```
frontend/src/
├── theme/
│ ├── index.ts # createTheme + provider wiring
│ ├── options/
│ │ ├── palette.ts # Palette tokens (light + dark)
│ │ ├── typography.ts # Type scale & families
│ │ ├── shadows.ts # Elevation shadows
│ │ ├── custom-shadows.ts # Brand-specific glow/elevation
│ │ ├── overrides/ # MUI component overrides
│ │ └── …
│ └── styles/ # Global CSS resets, scrollbar
├── settings/
│ ├── context/ # SettingsContext provider
│ └── drawer/ # The visible drawer UI
├── components/ # ~50 reusable components (see below)
└── layouts/
├── auth-centered/
├── auth-split/
├── dashboard/ # sidebar + topbar
└── main/ # public marketing-style
```
---
## 6. Naming & code conventions (recap from cursor rules)
- File names: kebab-case (`custom-data-grid.tsx`).
- Component names: PascalCase (`CustomDataGrid`).
- Hook names: `use-*.ts` exporting `useXxx`.
- Folder per component with `index.ts` barrel.
- Import order: **Styles → Side-effects → Types → External libs → MUI → Internal**.
- Forbidden: inline hex colors, `!important`, mixing `style={{}}` with `sx`.
- Icons: **Iconify only** (`eva:*`, `solar:*` prefixes preferred).
---
## 7. Accessibility checklist
- All interactive elements reachable with `Tab`.
- Focus visible (don't override default outline without replacement).
- Icon-only buttons have `aria-label`.
- Form fields linked to labels via `htmlFor` or wrapping `<label>`.
- Color contrast verified at WCAG AA (`4.5:1` body text, `3:1` large text).
- Live regions for snackbars (`aria-live="polite"`).
- Skip-to-content link before the navbar.
---
## 8. RTL support
- The MUI emotion cache toggles `stylis-plugin-rtl` when `direction === 'rtl'`.
- DataGrid honors a custom Persian locale at `locales/custom-fa-data-grid-locale.ts`.
- Icon orientation (chevrons, arrows) is mirrored automatically by stylis-plugin-rtl.
- Date pickers use `dayjs` with locale `fa`/`ar` loaded dynamically.
See [[Internationalization & RTL]] for the full setup.
---
## 9. Where to add new visuals
| Need | Add it to |
|---|---|
| New brand color | `theme/options/palette.ts` (light + dark) |
| New text variant | `theme/options/typography.ts` |
| MUI component override (default props, root sx) | `theme/options/overrides/<Component>.ts` |
| Reusable widget | `src/components/<kebab-case-name>/` |
| Page-specific composition | `src/sections/<feature>/<thing>.tsx` |
| Layout variant | `src/layouts/<name>/` |
| New language | `src/locales/langs/<code>/` + register in `locales-config.ts` |
---
## 10. Related
- [[Theme Configuration]] — exact MUI theme structure
- [[Typography]] — type scale & font loading
- [[Colors]] — palette tokens & contrast
- [[Components]] — inventory of reusable components
- [[Layouts]] — layout patterns
- [[Internationalization & RTL]] — i18n + bidi
- [[Iconography]] — Iconify usage rules
- [[Settings & Theming]] — drawer-driven user prefs
- [[Frontend Architecture]] — where this fits in the broader system
- [[Coding Standards]] — full cursor-rules reference

View File

@@ -0,0 +1,183 @@
---
title: Iconography
tags: [design-system, icons, iconify]
created: 2026-05-23
---
# Iconography
Icons come from **Iconify** — a unified SVG icon library aggregating 100k+ icons from dozens of icon sets, loaded on-demand.
> [!important]
> Per cursor rules (`backend/.cursor/rules/ui-development-standards.mdc`), Iconify is the **only** icon system. Do NOT import from `@mui/icons-material`, `react-icons`, or inline SVG files unless you're building a wholly custom illustration.
---
## 1. Library
Installed as `@iconify/react`. Wrapped by `frontend/src/components/iconify/`:
```tsx
import { Iconify } from 'src/components/iconify';
<Iconify icon="solar:user-bold" />
<Iconify icon="eva:close-fill" width={20} />
<Iconify icon="mingcute:check-fill" sx={{ color: 'success.main' }} />
```
Props: `icon` (required), `width` (number or string, default 20), `sx` (any MUI sx), plus standard HTML attributes.
---
## 2. Approved icon sets
Per the cursor rules, **prefer** these prefixes:
| Prefix | Set | Style | Best for |
|---|---|---|---|
| `solar:` | Solar Icons | Bold / Line / Linear / Outline | Default everywhere — most consistent |
| `eva:` | Eva Icons | Fill / Outline | Compact UI icons (close, more, plus) |
| `mingcute:` | MingCute | Fill / Line | Detailed actions |
| `mdi:` | Material Design Icons | Many variants | Use only when above sets lack the metaphor |
| `simple-icons:` | Brand logos | Monochrome | Social platforms, third-party brands |
| `flagpack:` | Country flags | Filled | Language switcher, address country |
> [!tip]
> Pick ONE style per set across the app for visual consistency. The default convention is `solar:*-bold` for filled and `solar:*-line-duotone` for accents.
---
## 3. Sizing
| Size | Use |
|---|---|
| 16 px | Inline with text (caption, body2) |
| 20 px | Inline with body1, navigation items, buttons (default) |
| 24 px | Standalone action buttons, primary nav |
| 32 px | Empty-state illustrations, large CTAs |
| 48 px+ | Hero illustrations (consider Lottie or `react-icons` set built for hero use) |
Set via `width` (icon is always square):
```tsx
<Iconify icon="solar:bell-bold" width={24} />
```
---
## 4. Color
Color comes from the parent's `color` CSS property (Iconify renders with `fill: currentColor`). Use the `sx` prop:
```tsx
<Iconify icon="solar:check-circle-bold" sx={{ color: 'success.main' }} />
<Iconify icon="solar:warning-bold" sx={{ color: 'warning.dark' }} />
```
Never hard-code hex — go through palette tokens.
---
## 5. Common icon mappings
A non-exhaustive convention table:
| Action / Concept | Recommended icon |
|---|---|
| Search | `solar:magnifer-bold` |
| Close | `eva:close-fill` |
| Add / Create | `solar:add-circle-bold` or `mingcute:add-fill` |
| Edit | `solar:pen-bold` |
| Delete | `solar:trash-bin-trash-bold` |
| Settings | `solar:settings-bold` |
| Notifications | `solar:bell-bold` (badge for unread) |
| User / Profile | `solar:user-bold` |
| Avatar group | `solar:users-group-rounded-bold` |
| Shopping cart | `solar:cart-bold` |
| Filter | `solar:filter-bold` |
| Sort | `solar:sort-bold` |
| More menu | `eva:more-vertical-fill` |
| Back | `solar:alt-arrow-left-bold` (auto-flips RTL) |
| Forward | `solar:alt-arrow-right-bold` |
| Up / Down | `solar:alt-arrow-up-bold` / `solar:alt-arrow-down-bold` |
| Success | `solar:check-circle-bold` |
| Warning | `solar:danger-bold` |
| Error | `solar:close-circle-bold` |
| Info | `solar:info-circle-bold` |
| Upload | `solar:upload-bold` |
| Download | `solar:download-bold` |
| Copy | `solar:copy-bold` |
| Eye (show/hide password) | `solar:eye-bold` / `solar:eye-closed-bold` |
| Wallet | `solar:wallet-money-bold` |
| Coin / Token | `solar:dollar-bold`, `mingcute:coin-fill` |
| QR code | `solar:qr-code-bold` |
| Chat | `solar:chat-round-bold` |
| Send (chat) | `solar:plain-bold` |
| Attachment | `eva:attach-2-fill` |
| Calendar | `solar:calendar-bold` |
| Clock | `solar:clock-circle-bold` |
| Location | `solar:map-point-bold` |
| Phone | `solar:phone-bold` |
| Email | `solar:letter-bold` |
| Lock | `solar:lock-password-bold` |
| Unlock | `solar:lock-unlock-bold` |
| Star (rating) | `solar:star-bold` (filled) / `solar:star-linear` (empty) |
| Hamburger | `solar:hamburger-menu-bold` |
| Refresh | `solar:refresh-bold` |
| Logout | `solar:logout-3-bold` |
---
## 6. RTL & direction
Direction-implying icons (chevrons, arrows, swipe) are flipped automatically by `stylis-plugin-rtl` for arrow-class icons. Semantic icons (play, share, send) should NOT flip — wrap them in:
```tsx
<Box sx={{ direction: 'ltr' }}>
<Iconify icon="solar:play-bold" />
</Box>
```
Or use `transform: 'scaleX(-1)'` on RTL if you specifically need to mirror.
---
## 7. Custom SVG (when Iconify is not enough)
Place SVGs in `public/assets/icons/` or `frontend/src/assets/icons/` and use:
```tsx
import { SvgColor } from 'src/components/svg-color';
<SvgColor src="/assets/icons/custom-shape.svg" sx={{ color: 'primary.main' }} />
```
`svg-color` uses the mask-image technique so a single-color SVG inherits the parent color, matching Iconify behavior.
For multi-color illustrations, ship them as separate files in `public/illustrations/` and reference with Next `Image`.
---
## 8. Accessibility
- Icon-only buttons MUST have `aria-label`:
```tsx
<IconButton aria-label="close"><Iconify icon="eva:close-fill" /></IconButton>
```
- Icons next to a label are decorative — no aria-label needed.
- Live status icons (loading, success) inside a `aria-live="polite"` region announce changes.
---
## 9. Performance
- Iconify loads icons on demand from its CDN (or self-hosted if configured).
- Bundle impact is minimal — only the icon component, not the icon data.
- For the most-used set (e.g., `solar:*`), consider pre-loading via `@iconify-icons/solar` to remove the CDN dependency.
---
## 10. Related
- [[Design System Overview]] · [[Components]] · [[Colors]]
- [[Coding Standards]] — Iconify rule cited

View File

@@ -0,0 +1,276 @@
---
title: Internationalization & RTL
tags: [design-system, i18n, rtl, localization]
created: 2026-05-23
---
# Internationalization & RTL
The frontend supports six languages with full LTR/RTL bidi handling. Configuration lives in `frontend/src/locales/`.
| Code | Language | Direction | Native name |
|---|---|---|---|
| `en` | English | LTR | English |
| `fa` | Persian (Farsi) | **RTL** | فارسی |
| `ar` | Arabic | **RTL** | العربية |
| `fr` | French | LTR | Français |
| `cn` | Chinese (Simplified) | LTR | 简体中文 |
| `vi` | Vietnamese | LTR | Tiếng Việt |
---
## 1. Library stack
| Package | Role |
|---|---|
| `i18next` | Core i18n engine |
| `react-i18next` | React bindings (`useTranslation`, `<Trans>`) |
| `i18next-browser-languagedetector` | Detect from `navigator.language` / cookie |
| `stylis-plugin-rtl` | Auto-flip CSS for RTL |
| `dayjs` | Date formatting with locale support |
| `@mui/x-date-pickers/AdapterDayjs` | Picker UI honors locale |
| `Intl.NumberFormat` (built-in) | Numeric formatting |
---
## 2. Setup (`src/locales/locales-config.ts`)
```ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import enCommon from './langs/en/common.json';
import faCommon from './langs/fa/common.json';
import arCommon from './langs/ar/common.json';
// ... fr, cn, vi
export const allLangs = [
{ value: 'en', label: 'English', dir: 'ltr', icon: 'flagpack:gb-ukm' },
{ value: 'fa', label: 'فارسی', dir: 'rtl', icon: 'flagpack:ir' },
{ value: 'ar', label: 'العربية', dir: 'rtl', icon: 'flagpack:sa' },
{ value: 'fr', label: 'Français', dir: 'ltr', icon: 'flagpack:fr' },
{ value: 'cn', label: '简体中文', dir: 'ltr', icon: 'flagpack:cn' },
{ value: 'vi', label: 'Tiếng Việt', dir: 'ltr', icon: 'flagpack:vn' },
];
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: { common: enCommon },
fa: { common: faCommon },
ar: { common: arCommon },
fr: { common: frCommon },
cn: { common: cnCommon },
vi: { common: viCommon },
},
fallbackLng: 'en',
defaultNS: 'common',
interpolation: { escapeValue: false },
});
```
---
## 3. Translation files
```
src/locales/langs/
├── en/common.json
├── fa/common.json
├── ar/common.json
├── fr/common.json
├── cn/common.json
└── vi/common.json
```
Convention: one file per **namespace**. `common.json` is the default; split if any namespace exceeds ~500 keys. Examples (key paths):
```jsonc
{
"actions": { "save": "Save", "cancel": "Cancel", "delete": "Delete" },
"auth": { "signIn": "Sign in", "signOut": "Sign out", "rememberMe": "Remember me" },
"marketplace": {
"request": {
"create": "Create a request",
"openOffers": "{{count}} open offer",
"openOffers_plural": "{{count}} open offers"
}
}
}
```
---
## 4. Usage in components
```tsx
import { useTranslation } from 'react-i18next';
function CreateButton() {
const { t } = useTranslation();
return <Button>{t('marketplace.request.create')}</Button>;
}
```
For sentences with embedded React (link, bold) use `<Trans>`:
```tsx
<Trans i18nKey="legal.terms">
By signing up you agree to our <Link href="/terms">Terms</Link>.
</Trans>
```
---
## 5. Direction (LTR / RTL)
Triggered by the active locale (`dir: 'rtl'` for `fa` / `ar`).
### 5.1 Cache swap
```tsx
const cacheKey = direction === 'rtl' ? 'css-rtl' : 'css';
const stylisPlugins = direction === 'rtl' ? [rtlPlugin] : [];
<AppRouterCacheProvider options={{ key: cacheKey, prepend: true, stylisPlugins }}>
{children}
</AppRouterCacheProvider>
```
> [!warning]
> The cache key **must differ** between modes. Sharing the key causes stale CSS to linger when direction changes at runtime.
### 5.2 Theme
```ts
const theme = createTheme({ direction, ... });
```
### 5.3 `<html dir>`
The root layout sets `<html lang={locale} dir={direction}>`. Some screen readers and form behaviors (e.g., number input flow) rely on this attribute.
---
## 6. RTL CSS rules
| Don't | Do |
|---|---|
| `margin-left: 8px` | `marginInlineStart: 1` (or `ml: 1` — MUI's `sx` translates) |
| `padding-right: 16px` | `paddingInlineEnd: 2` |
| `text-align: left` | `textAlign: 'start'` |
| `float: left` | `float: 'inline-start'` (or refactor) |
| `border-left: 2px` | `borderInlineStart: 2` |
| `direction: ltr` hardcoded | inherit from html unless intentional (e.g., code blocks always LTR) |
Icons that imply direction (chevrons, arrows) are auto-flipped by `stylis-plugin-rtl`. If an icon is **semantic** (e.g., a play button), wrap it in:
```tsx
<Box sx={{ direction: 'ltr' }}>
<Iconify icon="solar:play-bold" />
</Box>
```
---
## 7. Date & time formatting
```tsx
import dayjs from 'dayjs';
import 'dayjs/locale/fa';
dayjs.locale('fa');
dayjs('2026-05-23').format('LL'); // → "۲ خرداد ۱۴۰۵"
```
Use the `Format` helpers from `components/...` for consistent display patterns:
| Helper | Output |
|---|---|
| `fDate(d)` | locale-aware short date |
| `fDateTime(d)` | date + time |
| `fTime(d)` | time only |
| `fToNow(d)` | "3 hours ago" |
| `fIsBetween(start, end)` | boolean window check |
---
## 8. Number & currency formatting
```ts
import { fNumber, fCurrency, fPercent } from '@/locales/utils/number-format-locale';
fNumber(1234567); // "1,234,567" / "۱٬۲۳۴٬۵۶۷"
fCurrency(99.5, 'USD'); // "$99.50" / "۹۹٫۵۰ دلار"
fPercent(0.123); // "12.3%"
```
Underneath, these wrap `Intl.NumberFormat` with the active locale, falling back to `en-US` when the formatter doesn't support a locale (e.g., older browsers + `fa`).
---
## 9. DataGrid Farsi locale
`src/locales/custom-fa-data-grid-locale.ts` exports a Persian translation map for MUI X DataGrid (column menu, pagination labels, filter operators, etc.). Wired in via:
```tsx
<DataGrid
localeText={locale === 'fa' ? customFaDataGrid : undefined}
...
/>
```
For Arabic, the built-in MUI `arSD` locale works.
---
## 10. Language switcher UI
In the topbar, a chip with the flag + native name opens a popover listing all `allLangs`. On click:
1. `i18n.changeLanguage(code)`
2. `settings.onUpdate('direction', dir)` ← persists into settings context
3. Page does NOT reload — the cache + theme + provider all react reactively.
---
## 11. Adding a new language
1. Create `src/locales/langs/<code>/common.json` with the same key set as `en/common.json` (use `i18next-parser` to identify missing keys).
2. Register in `allLangs` array in `locales-config.ts`.
3. Add `dayjs` locale import if it differs from the existing set.
4. Verify number formatter outputs on a sample page.
5. If RTL, add `direction: 'rtl'` and **test bidi** thoroughly.
---
## 12. Translation workflow
- Source of truth: `en/common.json` (English).
- For new strings, add to `en/` first, then translate to other locales (manual or via Crowdin / Lokalise integration).
- Lint check: `i18next-parser` can compare locale files for missing keys.
---
## 13. Common pitfalls
| Pitfall | Fix |
|---|---|
| Hardcoded English in JSX | Move to `common.json` |
| `align="left"` on a Box | Use `textAlign="start"` |
| `marginLeft` instead of `ml` shorthand | Use shorthand (auto-RTL) |
| Forgot to load Persian font | Add to `theme/options/typography.ts` |
| Date showing as English digits in fa | Forgot `dayjs.locale('fa')` |
| Numbers in RTL appearing reversed | Wrap in `<bdi>` |
---
## 14. Related
- [[Design System Overview]] · [[Theme Configuration]] · [[Typography]]
- [[Settings & Theming]] — direction toggle
- [[Frontend Architecture]] — provider tree
- [[Roles & Personas]] — locale defaults per role (admin defaults English, buyers can be Persian)

View File

@@ -0,0 +1,217 @@
---
title: Layouts
tags: [design-system, layout, ui]
created: 2026-05-23
---
# Layouts
Page-template wrappers in `frontend/src/layouts/`. Each layout encapsulates a header, optional sidebar, content slot, and footer — pages mount inside one and never re-implement chrome.
---
## 1. Layout variants
| Layout | Used for | Header | Sidebar | Footer |
|---|---|---|---|---|
| `auth-centered/` | Sign-in, sign-up, verify, reset-password | Logo only | none | Compact links |
| `auth-split/` | Marketing-leaning auth (sign-up with promo panel) | Logo only | Left visual panel | none |
| `dashboard/` | Every `/dashboard/*` route | Topbar with user menu + notifications + language + settings | Vertical / Mini / Horizontal | none |
| `main/` | Public marketing pages (`shop/`, `post/`, landing) | Marketing nav | none | Full footer |
---
## 2. Dashboard layout
`layouts/dashboard/` is the workhorse. It uses MUI's `LayoutSection` pattern — slots for header, sidebar, main, footer with media-query-aware responsive behavior.
### 2.1 Anatomy
```
┌─────────────────────────────────────────────────────┐
│ TopBar ───────────────────────── search bell ▾ │ ← `header-section`
├─────────┬───────────────────────────────────────────┤
│ Nav │ Breadcrumbs │
│ │ ┌─ Page content (mounted route) ──────┐ │
│ ┌───┐ │ │ │ │
│ │ ○ │ │ │ │ │
│ └───┘ │ │ │ │
│ user │ │ │ │
│ profile│ │ │ │
├─────────┴───────────────────────────────────────────┤
│ (optional) Footer slot — unused in dashboard │
└─────────────────────────────────────────────────────┘
```
### 2.2 Sidebar variants
User can switch via the **Settings Drawer**:
| Variant | Width | Behavior |
|---|---|---|
| `vertical` | 280 px | Default; icon + label, collapsible groups |
| `mini` | 88 px | Icons only; labels in tooltip on hover |
| `horizontal` | full-width below header | Nav as a horizontal menu — main content below |
### 2.3 Topbar
Contains, left-to-right:
- Sidebar toggle button (mobile / mini-mode)
- Logo (mini, shown only when sidebar is collapsed)
- Search (Cmd+K palette) — optional
- Language selector (chip with flag)
- Settings drawer toggle (cog icon)
- Notifications drawer toggle (bell with unread count badge)
- Account dropdown (avatar → profile, sign-out)
### 2.4 Nav configuration
Nav items defined as a tree in a config file (e.g., `layouts/dashboard/config-nav-dashboard.ts`):
```ts
const navData = [
{
subheader: 'Overview',
items: [
{ title: 'Dashboard', path: paths.dashboard.root, icon: ICONS.dashboard },
{ title: 'Chat', path: paths.dashboard.chat, icon: ICONS.chat },
],
},
{
subheader: 'Marketplace',
items: [
{ title: 'Requests', path: paths.dashboard.request.root, icon: ICONS.request,
roles: ['buyer', 'admin'] },
{ title: 'Templates', path: paths.dashboard.template.root, icon: ICONS.template,
roles: ['seller', 'admin'] },
// ...
],
},
// ...
];
```
Each `items[i]` may carry `roles: string[]` to scope visibility. The nav renderer filters items based on the current user's role before rendering — entries without `roles` are shown to everyone.
### 2.5 Breadcrumbs
`components/custom-breadcrumbs` renders the active route trail plus an optional action button slot (e.g., "+ New Request"). Convention: set via the page component.
---
## 3. Auth layouts
### 3.1 `auth-centered/`
Single column, centered card. Used by sign-in, password reset, email verification.
```
┌──────────────────────────────┐
│ Logo │
│ │
│ ┌────────────────────┐ │
│ │ Title │ │
│ │ Form fields... │ │
│ │ CTA │ │
│ └────────────────────┘ │
│ │
│ Tiny footer links │
└──────────────────────────────┘
```
### 3.2 `auth-split/`
Two-column on `md+`, single column on small screens. Left = visual / brand panel; right = form.
```
┌──────────────┬───────────────┐
│ │ Logo │
│ Visual │ │
│ panel │ Form │
│ (image, │ │
│ quote, │ CTA │
│ benefit) │ │
│ │ Footer │
└──────────────┴───────────────┘
```
Used for sign-up to balance marketing copy with the form.
---
## 4. Main (public) layout
`layouts/main/` is for unauthenticated visitor pages. Sticky header, then content, then full footer.
Header includes:
- Logo
- Top-level nav (Shop, Blog, About)
- Language switcher
- "Sign in" / "Sign up" buttons (or "Dashboard" if signed in)
Footer includes:
- Brand block
- Sitemap
- Legal links
- Social media
- Newsletter signup (optional)
---
## 5. Responsive behavior
| Breakpoint | xs (<600) | sm (≥600) | md (≥900) | lg (≥1200) | xl (≥1536) |
|---|---|---|---|---|---|
| Dashboard sidebar | drawer overlay | drawer overlay | mini default | full default | full default |
| Auth-split | single col | single col | two col | two col | two col |
| Main nav | hamburger | hamburger | inline | inline | inline |
| Page padding | 16 px | 16 px | 24 px | 32 px | 32 px |
Implementation note: do NOT add `@media` rules in components. Use the responsive `sx` syntax:
```tsx
<Box sx={{ p: { xs: 2, md: 3, lg: 4 } }} />
```
---
## 6. Sticky elements
- TopBar uses `position: sticky; top: 0; z-index: appBar` so it stays on scroll.
- Action bars on long forms can use the same pattern at bottom (`bottom: 0`).
---
## 7. Skeleton & loading
- Each `dashboard/*/page.tsx` can ship a sibling `loading.tsx` that renders skeleton placeholders matching the final layout.
- For long-loading sections, use `<Suspense fallback={<SkeletonX />}>`.
---
## 8. Error boundaries
- App-level error boundary at root layout catches uncaught render errors.
- Each route segment may ship `error.tsx` for scoped recovery (e.g., `dashboard/shops/error.tsx`).
- Snackbars handle non-fatal API errors.
---
## 9. Adding a new layout
1. Create `layouts/<name>/` with `index.tsx` (default export the layout component).
2. Slot pattern: accept `children`; render header + nav + main + (optional) footer.
3. Reuse `components/scrollbar` for the main scroll area to keep custom scrollbar styling.
4. If it's an auth flow, wire it into `app/auth/<flow>/layout.tsx`.
---
## 10. Related
- [[Design System Overview]] · [[Theme Configuration]] · [[Components]]
- [[Frontend Architecture]] — where layouts sit in the tree
- [[Roles & Personas]] — role-driven nav filtering
- [[Settings & Theming]] — layout variant switcher

View File

@@ -0,0 +1,222 @@
---
title: Settings & Theming
tags: [design-system, settings, theming]
created: 2026-05-23
---
# Settings & Theming
A drawer-based UI lets the end user toggle visual preferences. Settings persist in `localStorage` and rebuild the MUI theme on the fly.
> [!info]
> Implementation: `frontend/src/settings/context/` (state) + `frontend/src/settings/drawer/` (UI) + `frontend/src/components/settings/` (helpers).
---
## 1. What's user-controllable
| Axis | Values | Default | Persisted |
|---|---|---|---|
| **Mode** | `light` · `dark` · `system` | `system` | localStorage |
| **Contrast** | `default` · `bold` | `default` | localStorage |
| **Layout** | `vertical` · `mini` · `horizontal` | `vertical` | localStorage |
| **Direction** | `ltr` · `rtl` | derived from locale | localStorage (overrides locale default) |
| **Color preset** | one of `default`, `purple`, `cyan`, `blue`, `orange`, `red` | `default` | localStorage |
| **Font family** | `Public Sans Variable`, `DM Sans Variable`, `Inter Variable`, `Nunito Sans Variable` | `Public Sans Variable` | localStorage |
| **Compact navigation** | boolean | `false` | localStorage |
| **Border radius** | 024 | 8 | localStorage |
| **Stretched container** | boolean | `false` | localStorage |
---
## 2. State shape
```ts
// frontend/src/settings/types.ts (approx)
export interface Settings {
mode: 'light' | 'dark' | 'system';
contrast: 'default' | 'bold';
layout: 'vertical' | 'mini' | 'horizontal';
direction: 'ltr' | 'rtl';
colorPreset: string;
fontFamily: string;
compactLayout: boolean;
primaryColor?: string;
stretch: boolean;
}
```
Stored in a single `localStorage` key (`settings` or `settings-key`). The context provider hydrates on mount, falls back to defaults if absent.
---
## 3. Context API
```ts
// frontend/src/settings/context/use-settings.ts (approx)
export interface SettingsContext {
state: Settings;
canReset: boolean;
onReset(): void;
onUpdate<K extends keyof Settings>(key: K, value: Settings[K]): void;
onUpdateField<K extends keyof Settings>(key: K, value: Settings[K]): void;
openDrawer: boolean;
onToggleDrawer(): void;
}
const { state, onUpdate, onToggleDrawer } = useSettings();
```
Use `onUpdate` to change any value. The context fires a re-render that:
1. Updates localStorage.
2. Triggers a new `buildTheme()` invocation up at `ThemeProvider`.
3. Components re-render with the new theme tokens.
---
## 4. Drawer UI
The drawer (`settings/drawer/SettingsDrawer.tsx`) is a right-side `MuiDrawer` with sections:
```
┌──────────────────────────────┐
│ Settings [reset] │
├──────────────────────────────┤
│ Mode [☀] [🌙] [⌐] │
│ │
│ Contrast ◯ Default ◯ Bold │
│ │
│ Direction ⬅ LTR ➡ RTL │
│ │
│ Layout ▤ Vertical │
│ ▣ Mini │
│ ━ Horizontal │
│ │
│ Color [●●●●●●] │
│ │
│ Font [Public Sans ▼] │
│ │
│ Border radius [—•—] 8 │
└──────────────────────────────┘
```
Each section uses the `BlockOption` helper component for consistent styling.
---
## 5. Wiring at the root
```tsx
// frontend/src/app/layout.tsx (simplified)
<SettingsProvider defaultSettings={defaults}>
<ThemeProviderFromSettings>
{children}
<SettingsDrawer />
</ThemeProviderFromSettings>
</SettingsProvider>
```
`ThemeProviderFromSettings` reads the settings context and rebuilds the theme on every change.
---
## 6. Locale ↔ direction coupling
By default, switching to `fa` / `ar` flips `direction: 'rtl'`. The user CAN override (a Persian-speaker on a desktop might prefer LTR sometimes).
```ts
// In a settings change handler:
onUpdate('direction', i18n.language === 'fa' ? 'rtl' : 'ltr');
```
Or let the user pin direction independently — saved direction wins over locale-derived direction.
---
## 7. Mode = `system`
When `mode === 'system'`:
```ts
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const effectiveMode = prefersDark ? 'dark' : 'light';
```
Subscribe to changes:
```ts
useEffect(() => {
if (settings.mode !== 'system') return;
const mql = window.matchMedia('(prefers-color-scheme: dark)');
const onChange = () => forceUpdate();
mql.addEventListener('change', onChange);
return () => mql.removeEventListener('change', onChange);
}, [settings.mode]);
```
---
## 8. Color presets
Defined in `frontend/src/theme/options/presets/` or similar. Each preset only swaps `primary` (and optionally `secondary`) — `grey`, semantic colors, and background stay constant so layouts feel consistent across presets.
Adding a preset:
1. Add a `<name>.ts` exporting `{ lighter, light, main, dark, darker, contrastText }`.
2. Register in `colorPresets` map.
3. Add a swatch entry in the drawer's color picker block.
---
## 9. Font preset
Switching the font family applies at the theme level:
```ts
typography: { fontFamily: settings.fontFamily, ... }
```
If a font isn't bundled, dynamically `import('@fontsource-variable/<name>')` first to avoid FOUC.
---
## 10. Compact / stretched layout
| Setting | Effect |
|---|---|
| `compactLayout: true` | Reduces top spacing / padding on dashboard pages |
| `stretch: true` | Removes the centered max-width on content (full-bleed) |
---
## 11. Border radius
Slider exposed as 0 → 24. Affects `theme.shape.borderRadius`, cascading to every component using `sx={{ borderRadius: 1 }}` semantics.
---
## 12. Reset to defaults
`onReset()` clears localStorage and re-hydrates with the default object. `canReset` is true when current state differs from defaults — used to enable/disable the Reset button.
---
## 13. Hydration mismatch hazard
Because settings live in `localStorage`, the server render can't know them. The provider implements a 2-pass strategy:
1. First render uses **defaults** (matches what the server emits → no hydration mismatch).
2. After mount, the provider reads localStorage and re-renders with the user's settings.
Brief flash possible. To mitigate, either:
- Suppress the first paint (split layout into client-only).
- Or set the user's settings into a cookie at sign-in so server can pre-render correctly.
---
## 14. Related
- [[Design System Overview]] · [[Theme Configuration]] · [[Colors]] · [[Typography]]
- [[Layouts]] — layout variants drive sidebar
- [[Internationalization & RTL]] — direction coupling
- [[Frontend Architecture]] — provider tree

View File

@@ -0,0 +1,250 @@
---
title: Theme Configuration
tags: [design-system, theme, mui]
created: 2026-05-23
---
# Theme Configuration
The MUI theme is constructed in `frontend/src/theme/index.ts` and composed from option modules in `frontend/src/theme/options/`. The resulting theme is provided at the root layout, wrapped by an RTL-aware emotion cache.
---
## 1. Construction pipeline
```ts
// approximate — read theme/index.ts for the canonical version
import { createTheme } from '@mui/material/styles';
import { palette } from './options/palette';
import { typography } from './options/typography';
import { shadows, customShadows } from './options/shadows';
import { componentsOverrides } from './options/overrides';
export function buildTheme(opts: { mode: 'light' | 'dark'; direction: 'ltr' | 'rtl'; preset: string; }) {
return createTheme({
direction: opts.direction,
palette: palette(opts.mode, opts.preset),
typography,
shape: { borderRadius: 8 },
shadows: shadows(opts.mode),
customShadows: customShadows(opts.mode),
components: componentsOverrides(opts),
breakpoints: {
values: { xs: 0, sm: 600, md: 900, lg: 1200, xl: 1536 },
},
});
}
```
The settings context calls `buildTheme()` whenever any axis changes, then re-mounts `<ThemeProvider>`. Because MUI v7 supports CSS variables, the swap is cheap (no React tree thrash).
---
## 2. Provider wiring (root layout)
```tsx
// frontend/src/app/layout.tsx (simplified)
<AppRouterCacheProvider options={{ key: dir === 'rtl' ? 'css-rtl' : 'css', prepend: true, stylisPlugins: dir === 'rtl' ? [rtlPlugin] : [] }}>
<ThemeProvider theme={theme}>
<CssBaseline />
<LocalizationProvider dateAdapter={AdapterDayjs}>
<I18nProvider>
<QueryClientProvider client={queryClient}>
<SocketProvider>
<SnackbarProvider>{children}</SnackbarProvider>
</SocketProvider>
</QueryClientProvider>
</I18nProvider>
</LocalizationProvider>
</ThemeProvider>
</AppRouterCacheProvider>
```
> [!warning]
> The cache `key` MUST differ between LTR and RTL (`'css'` vs `'css-rtl'`) — otherwise switching direction at runtime keeps the previous direction's CSS in the head and the layout breaks.
---
## 3. Palette
See [[Colors]] for the full table. Structure:
```ts
{
mode: 'light' | 'dark',
primary: { lighter, light, main, dark, darker, contrastText },
secondary: { lighter, light, main, dark, darker, contrastText },
info: { lighter, light, main, dark, darker, contrastText },
success: { lighter, light, main, dark, darker, contrastText },
warning: { lighter, light, main, dark, darker, contrastText },
error: { lighter, light, main, dark, darker, contrastText },
grey: { 100900 },
text: { primary, secondary, disabled },
background:{ default, paper, neutral },
action: { active, hover, selected, focus, disabled, disabledBackground },
}
```
Color presets (selectable in settings) swap the `primary` + `secondary` color groups while leaving `grey`, `error`, etc. untouched.
---
## 4. Typography
```ts
{
fontFamily: '"Public Sans Variable", "Helvetica", "Arial", sans-serif',
fontWeightRegular: 400,
fontWeightMedium: 500,
fontWeightSemiBold: 600,
fontWeightBold: 700,
h1: { fontSize: 64, lineHeight: 80/64, letterSpacing: -1 },
h2: { fontSize: 48, lineHeight: 64/48 },
h3: { fontSize: 32, lineHeight: 48/32 },
h4: { fontSize: 24, lineHeight: 36/24 },
h5: { fontSize: 20, lineHeight: 30/20 },
h6: { fontSize: 18, lineHeight: 28/18 },
subtitle1: { fontSize: 16, fontWeight: 600 },
subtitle2: { fontSize: 14, fontWeight: 600 },
body1: { fontSize: 16 },
body2: { fontSize: 14 },
caption: { fontSize: 12 },
overline: { fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 1.1 },
button: { fontSize: 14, fontWeight: 700, textTransform: 'unset' },
}
```
Variants `h1` and `h2` scale down responsively. Use the `responsiveFontSizes(theme)` helper if you need automatic scaling.
See [[Typography]] for font loading, secondary font (Barlow), and i18n font fallbacks.
---
## 5. Spacing
```ts
spacing: 8 // unit
// theme.spacing(1) === '8px'
// theme.spacing(2) === '16px'
// theme.spacing(0.5) === '4px'
```
Use the shorthand props on the `sx`:
```tsx
<Box sx={{ p: 2, mt: 3, mx: { xs: 1, md: 4 } }} />
```
---
## 6. Shape & radii
```ts
shape: { borderRadius: 8 } // default
// cards typically: borderRadius: 16
// pills: borderRadius: 9999
```
---
## 7. Shadows
MUI's default `shadows` array (25 levels: `none`, then `1``24`) is replaced with a softer custom set. Additionally, `customShadows` defines brand-specific tinted shadows used by the `Card`, `Button`, `Dialog` overrides:
```ts
customShadows: {
z1, z4, z8, z12, z16, z20, z24,
primary: 'rgba(<primary.main>, 0.24) 0 8 16 0',
secondary: '…',
info: '…',
success: '…',
warning: '…',
error: '…',
card: 'rgba(0,0,0,0.04) 0 0 2 0, rgba(0,0,0,0.08) 0 12 24 -4',
dialog: '…',
dropdown: '…',
}
```
Read inside `sx`:
```tsx
sx={{ boxShadow: (theme) => theme.customShadows.z8 }}
```
---
## 8. Component overrides
`theme/options/overrides/` contains one file per overridden component, each exporting `{ defaultProps, styleOverrides }` merged into `components`:
| Component | Notable override |
|---|---|
| `MuiButton` | `disableRipple`, custom `customShadows.primary` on contained variant |
| `MuiCard` | `borderRadius: 16`, `customShadows.card` |
| `MuiPaper` | elevation flattened to `0` by default (use `card` shadow explicitly) |
| `MuiTextField` | `variant: 'outlined'` default, custom focus ring |
| `MuiTooltip` | dark background regardless of mode, smaller padding |
| `MuiAvatar` | letter-based fallback with hashed background color |
| `MuiTableCell` | denser padding, header weight 600 |
| `MuiDialog` | `customShadows.dialog`, max-width responsive |
| `MuiAlert` | tinted background using palette `lighter` |
| `MuiTab` | uppercase off, color from `primary.main` when selected |
| `MuiAutocomplete` | custom popup paper shadow |
Add an override:
1. Create `theme/options/overrides/<Component>.ts` exporting a function `(theme) => ({...})`.
2. Register it in `theme/options/overrides/index.ts`.
3. It will be merged into the next `createTheme` call automatically.
---
## 9. CSS variables (MUI v7)
`createTheme` with `cssVariables: true` (recommended) emits CSS custom properties at the `<html>` root and switches them per mode without re-rendering. The default is on in v7 — verify in `theme/index.ts`.
This means a custom CSS rule can reference:
```css
.my-thing {
background: var(--mui-palette-primary-main);
padding: calc(var(--mui-spacing) * 2);
}
```
Use this sparingly — prefer the `sx` prop.
---
## 10. Per-locale font swap
When the active locale is `fa` or `ar`, the typography option module can layer a Persian/Arabic font (e.g., **Vazirmatn**, **IRANSans**) ahead of `Public Sans Variable`:
```ts
fontFamily: dir === 'rtl'
? '"Vazirmatn", "Public Sans Variable", sans-serif'
: '"Public Sans Variable", sans-serif',
```
Otherwise Latin glyphs still render via the Public Sans fallback. See [[Internationalization & RTL]].
---
## 11. Customisation checklist
- [ ] New brand color → update `palette.ts` (light + dark) and consider a preset.
- [ ] New variant → add to typography options and the `TypographyOptions` interface augmentation.
- [ ] Component override → file in `overrides/`.
- [ ] Verify in both modes (light + dark) AND both directions (ltr + rtl).
- [ ] Verify against WCAG AA contrast.
- [ ] Snapshot test the component if visual stability matters.
---
## Related
- [[Design System Overview]]
- [[Colors]] · [[Typography]] · [[Components]] · [[Layouts]]
- [[Internationalization & RTL]] · [[Settings & Theming]]
- [[Frontend Architecture]]

View File

@@ -0,0 +1,186 @@
---
title: Typography
tags: [design-system, typography, fonts]
created: 2026-05-23
---
# Typography
The system uses **Public Sans Variable** as the primary face with **Barlow** as a secondary (display) face, plus locale-specific Persian/Arabic faces loaded when the active language requires them.
---
## 1. Font stack
Loaded via `@fontsource-variable` (variable fonts streamed at build) plus `@fontsource/barlow`. Confirm in `frontend/package.json`:
```jsonc
"@fontsource-variable/public-sans": "^5.2.5", // Primary
"@fontsource-variable/dm-sans": "^5.2.5", // Optional preset
"@fontsource-variable/inter": "^5.2.5", // Optional preset
"@fontsource-variable/nunito-sans": "^5.2.5", // Optional preset
"@fontsource/barlow": "^5.2.5", // Secondary (display)
```
Imported in `frontend/src/app/layout.tsx` (or a fonts module) so Next can fingerprint and preload them.
Default font-family stack in the theme:
```css
font-family: "Public Sans Variable", "Helvetica", "Arial", sans-serif;
```
Display-only headings (banners, hero) may override with Barlow via the `sx` prop:
```tsx
<Typography variant="h1" sx={{ fontFamily: '"Barlow", serif' }}>Welcome</Typography>
```
---
## 2. Type scale
| Variant | Size (px) | Line height | Weight | Use |
|---|---|---|---|---|
| `h1` | 64 | 80 | 800 | Hero titles only |
| `h2` | 48 | 64 | 800 | Page titles |
| `h3` | 32 | 48 | 700 | Section titles |
| `h4` | 24 | 36 | 700 | Card titles, dialog headers |
| `h5` | 20 | 30 | 700 | Sub-section titles |
| `h6` | 18 | 28 | 700 | Item titles, sidebar headers |
| `subtitle1` | 16 | 24 | 600 | Form section labels |
| `subtitle2` | 14 | 22 | 600 | List item subtitles |
| `body1` | 16 | 24 | 400 | Body copy default |
| `body2` | 14 | 22 | 400 | Secondary copy, table cells |
| `caption` | 12 | 18 | 400 | Helper text, timestamps |
| `overline` | 12 | 18 | 700 | Tags, all-caps category labels |
| `button` | 14 | 24 | 700 | Button text — NOT uppercase |
> [!tip]
> Use `responsiveFontSizes(theme)` once at theme creation to scale `h1`/`h2` on small screens automatically.
---
## 3. Weights
The variable Public Sans face supports 100900. Convention:
| Weight | Token | Use |
|---|---|---|
| 400 | `fontWeightRegular` | Body |
| 500 | `fontWeightMedium` | Mild emphasis |
| 600 | `fontWeightSemiBold` | Subtitles, table headers |
| 700 | `fontWeightBold` | Headings |
| 800 | (raw) | Display |
Never use `font-weight: bolder` — always pick from the table.
---
## 4. Letter-spacing
- Headings (h1h2): `-1` (tight, modern)
- Overline: `+1.1` (open, all-caps)
- Body, subtitle: `0`
---
## 5. Numeric variants
Tabular figures matter for financial UI (payments, prices). Enable per-instance:
```tsx
<Typography variant="body1" sx={{ fontVariantNumeric: 'tabular-nums' }}>
{amount}
</Typography>
```
---
## 6. Multi-script support
The system officially supports Latin, **Persian (Farsi)**, and **Arabic** rendering; French and Vietnamese use Latin glyphs already covered by Public Sans; Chinese (Simplified) falls back to the OS-default CJK font (the current fontsource set does not ship CJK).
Persian/Arabic font swap is layered at theme build:
```ts
fontFamily:
locale === 'fa' || locale === 'ar'
? '"Vazirmatn", "Public Sans Variable", "Tahoma", sans-serif'
: '"Public Sans Variable", "Helvetica", sans-serif'
```
> [!note]
> "Vazirmatn" is the **recommended** Persian face for new installations — it has full Unicode coverage and a variable axis. If not installed via `@fontsource`, ship it as a self-hosted woff2 under `public/fonts/`.
For Chinese (`cn`), consider adding `"PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC"` to the family at the front.
For Vietnamese (`vi`) — Public Sans already covers all needed glyphs.
---
## 7. Line-length
Aim for **4575 characters per line** on body copy. Constrain with `maxWidth`:
```tsx
<Typography variant="body1" sx={{ maxWidth: 720 }}>{longBody}</Typography>
```
---
## 8. Text truncation
Single-line:
```tsx
<Typography noWrap>{text}</Typography>
```
Multi-line (clamp at 2 lines):
```tsx
<Typography
sx={{
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
}}
>{text}</Typography>
```
---
## 9. Bidi & RTL considerations
- Don't hard-code `text-align: left/right` — use `text-align: start/end` so it flips with direction.
- For inline mixed-script (Persian + English), prefer the auto direction inside `<bdi>` or wrap with the Unicode `` (RLE) / `` (PDF) markers.
- Numeric formatting per locale handled by `Intl.NumberFormat`; do NOT translate digits manually.
---
## 10. Loading & FOUC prevention
- All fonts loaded at the route segment that needs them (Next.js best practice).
- `font-display: swap` is the default in fontsource — visible text uses fallback first, then swaps.
- For the FIRST PAINT on auth pages, preload the primary Latin face by adding `<link rel="preload" as="font" />` to the root layout.
---
## 11. Customising
Add a font:
1. `yarn add @fontsource-variable/<name>`
2. `import '@fontsource-variable/<name>'` in `src/app/layout.tsx`.
3. Add to `fontFamily` stack in `theme/options/typography.ts`.
4. Register as a settings option if user-selectable.
---
## Related
- [[Theme Configuration]] · [[Design System Overview]] · [[Colors]]
- [[Internationalization & RTL]]
- [[Settings & Theming]]