Files
duty-teller/docs/miniapp-design.md
Nikolay Tatarinov fa22976e75 feat: enhance Mini App design guidelines and refactor layout components
- Updated Mini App design guidelines to include detailed instructions on UI changes, accessibility rules, and verification processes.
- Refactored multiple components to utilize `MiniAppScreen` and `MiniAppScreenContent` for consistent layout structure across the application.
- Improved error handling in `GlobalError` and `NotFound` components by integrating new layout components for better user experience.
- Introduced new hooks for admin functionality, streamlining access checks and data loading processes.
- Enhanced documentation to reflect changes in design policies and component usage, ensuring clarity for future development.
2026-03-06 17:51:33 +03:00

264 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Mini App Design Guideline
This document defines the design rules for the Duty Teller Mini App (Next.js frontend in `webapp-next/`). It aligns with [Telegrams official Mini App design guidelines](https://core.telegram.org/bots/webapps#designing-mini-apps) (Color Schemes and Design Guidelines) and codifies the current implementation (calendar, duty list, admin) as the reference for new screens and components.
---
## 1. Telegram design principles
Telegrams guidelines state:
- **Mobile-first:** All elements must be responsive and designed for mobile first.
- **Consistency:** Interactive elements should match the style, behaviour, and intent of existing Telegram UI components.
- **Performance:** Animations should be smooth, ideally 60fps.
- **Accessibility:** Inputs and images must have labels.
- **Theme:** The app must use the dynamic theme-based colors provided by the API (Day/Night and custom themes).
- **Safe areas:** The interface must respect the **safe area** and **content safe area** so content does not overlap system or Telegram UI, especially in fullscreen.
- **Android:** On Android, use the extra User-Agent data (e.g. performance class) and reduce animations and effects on low-performance devices for smooth operation.
**Color schemes:** Mini Apps receive the users current **theme** in real time. Use the `ThemeParams` object and the CSS variables Telegram exposes (e.g. `--tg-theme-bg-color`, `--tg-theme-text-color`) so the UI adapts when the user switches Day/Night or custom themes.
---
## 2. Theme and colors
### 2.1 Sources
Theme is resolved in this order:
1. Hash parameters: `tgWebAppColorScheme`, `tgWebAppThemeParams` (parsed in the shared inline script from `webapp-next/src/lib/theme-bootstrap-script.ts`, used in layout and global-error).
2. At runtime: `Telegram.WebApp.colorScheme` and `Telegram.WebApp.themeParams` via **TelegramProvider** (theme sync is provider-owned in `ThemeSync` / `useTelegramTheme`), so every route (/, /admin, not-found, error) receives live theme updates.
The inline script maps all Telegram theme keys to `--tg-theme-*` CSS variables on the document root. The provider sets `data-theme` (`light` / `dark`) and applies Mini App background/header colors.
### 2.2 Mapping (ThemeParams → app tokens)
In `webapp-next/src/app/globals.css`, `:root` and `[data-theme="light"]` / `[data-theme="dark"]` map Telegrams ThemeParams to internal design tokens:
| Telegram ThemeParam (CSS var) | App token / usage |
|--------------------------------------|-------------------|
| `--tg-theme-bg-color` | `--bg` (background) |
| `--tg-theme-secondary-bg-color` | `--surface` (cards, panels) |
| `--tg-theme-text-color` | `--text` (primary text) |
| `--tg-theme-hint-color`, `--tg-theme-subtitle-text-color` | `--muted` (secondary text) |
| `--tg-theme-link-color` | `--accent` (links, secondary actions) |
| `--tg-theme-header-bg-color` | `--header-bg` |
| `--tg-theme-section-bg-color` | `--card` (sections) |
| `--tg-theme-section-header-text-color` | `--section-header` |
| `--tg-theme-section-separator-color` | `--border` |
| `--tg-theme-button-color` | `--primary` |
| `--tg-theme-button-text-color` | `--primary-foreground` |
| `--tg-theme-destructive-text-color` | `--error` |
| `--tg-theme-accent-text-color` | `--accent-text` |
Tailwind/shadcn semantic tokens are wired to these: `--background``--bg`, `--foreground``--text`, `--primary`, `--secondary``--surface`, etc.
### 2.3 Domain colors
Duty-specific semantics (fixed per theme, not from ThemeParams):
- `--duty` — duty shift (e.g. green).
- `--unavailable` — unavailable (e.g. amber).
- `--vacation` — vacation (e.g. blue).
- `--today` — “today” highlight; tied to `--tg-theme-accent-text-color` / `--tg-theme-link-color`.
Use Tailwind classes such as `bg-duty`, `border-l-unavailable`, `text-today`, `bg-today`, etc.
### 2.4 Derived tokens (color-mix)
Prefer these instead of ad-hoc color mixes:
- `--surface-hover`, `--surface-hover-10` — hover states for surfaces.
- `--surface-today-tint`, `--surface-muted-tint` — subtle tints.
- `--today-hover`, `--today-border`, `--today-border-selected`, `--today-gradient-end` — today state.
- `--muted-fade`, `--shadow-card`, etc.
Defined in `globals.css`; use via `var(--surface-hover)` in classes (e.g. `hover:bg-[var(--surface-hover)]`).
### 2.5 Rule for new work
Use **only** these tokens and Tailwind/shadcn aliases (`bg-background`, `text-muted`, `bg-surface`, `text-accent`, `border-l-duty`, `bg-today`, etc.). Do not hardcode hex or RGB in new components or screens.
---
## 3. Layout and safe areas
### 3.1 Width
- **Token:** `--max-width-app: 420px` (in `@theme` in `globals.css`).
- **Usage:** Page wrapper uses `max-w-[var(--max-width-app)]` (e.g. in `page.tsx`, `CalendarPage.tsx`, `admin/page.tsx`). Content is centred with `mx-auto`.
### 3.2 Height
- **Viewport:** Prefer `min-h-[var(--tg-viewport-stable-height,100vh)]` for the main content area so the Mini App fills the visible height correctly when expanded/collapsed. Fallback `100vh` when not in Telegram.
- **Body:** In `globals.css`, `body` already has `min-height: var(--tg-viewport-stable-height, 100vh)` and `background: var(--bg)`.
### 3.3 Safe area and content safe area
- **CSS custom properties** (in `globals.css`): `--app-safe-top`, `--app-safe-bottom`, `--app-safe-left`, `--app-safe-right` use Telegram viewport content-safe-area insets with `env(safe-area-inset-*)` fallbacks. Use these for sticky positioning and padding so layout works on notched and landscape devices.
- **Class `.content-safe`**: Applies padding on all four sides using the above tokens so content does not sit under Telegram header, bottom bar, or side chrome (Bot API 8.0+). Use `.content-safe` on the **root container of each page** and on full-screen fallback screens (not-found, error, access denied).
- **Sticky headers:** Use `top-[var(--app-safe-top)]` (not `top-0`) for sticky elements (e.g. calendar header, admin header) so they sit below the Telegram UI instead of overlapping it.
- Lists that extend to the bottom should also account for bottom inset (e.g. `padding-bottom: var(--tg-viewport-content-safe-area-inset-bottom, 12px)` in `.container-app`).
Official terminology (Telegram docs) uses `safeAreaInset` and `contentSafeAreaInset`
plus events `safeAreaChanged` and `contentSafeAreaChanged`. In our code, these values
are exposed through SDK CSS bindings and consumed via app aliases (`--app-safe-*`).
When updating safe-area code, preserve this mapping and avoid mixing raw `env(...)`
and Telegram content-safe insets within the same component.
### 3.4 Sheets and modals
Bottom sheets and modals that sit at the bottom of the screen must add safe area to their padding, e.g.:
`pb-[calc(24px+env(safe-area-inset-bottom,0px))]`
See `webapp-next/src/components/day-detail/DayDetail.tsx` for the Sheet content.
---
## 4. Typography and spacing
### 4.1 Font
- **Family:** `system-ui, -apple-system, sans-serif` (set in `globals.css` and Tailwind theme).
### 4.2 Patterns from the calendar and duty list
| Element | Classes / tokens |
|--------|-------------------|
| Month title | `text-[1.1rem]` / `sm:text-[1.25rem]`, `font-semibold` |
| Year (above month) | `text-xs`, `text-muted` |
| Nav buttons (prev/next month) | `size-10`, `rounded-[10px]` |
| Calendar day cell | `text-[0.85rem]`, `rounded-lg`, `p-1` |
| Duty timeline card | `px-2.5 py-2`, `rounded-lg` |
### 4.3 Page and block spacing
- Page container: `px-3 pb-6` in addition to `.content-safe`.
- Between sections: `mb-3`, `mb-4` as appropriate.
- Grids: `gap-1` for tight layouts (e.g. calendar grid), larger gaps where needed.
---
## 5. Component patterns
### 5.1 Buttons
- **Primary:** Use the default Button variant: `bg-primary text-primary-foreground` (from `webapp-next/src/components/ui/button.tsx`).
- **Secondary icon buttons** (e.g. calendar nav):
`bg-surface text-accent hover:bg-[var(--surface-hover)] focus-visible:outline-accent active:scale-95`
with `size-10` and `rounded-[10px]`.
- Keep focus visible (e.g. `focus-visible:outline-accent` or ring); do not remove outline without a visible replacement.
### 5.2 Cards
- **Background:** `bg-surface` or `bg-card` (both resolve to theme tokens).
- **Borders:** `border`, `--border` (section separator color).
- **Emphasis:** `var(--shadow-card)` for highlighted cards (e.g. current duty).
- **Left stripe by type:** `border-l-[3px]` with:
- `border-l-duty`, `border-l-unavailable`, `border-l-vacation` for event types;
- `border-l-today` for “current duty” (see `.border-l-today` in `globals.css`).
### 5.3 Calendar grid
- **Structure:** 7 columns × 6 rows; use `role="grid"` on the container and `role="gridcell"` on each cell.
- **Layout:** `min-h-[var(--calendar-grid-min-height)]`, cells `aspect-square` with `min-h-8`, `rounded-lg`, `gap-1`.
- **Today:** `bg-today text-[var(--bg)]`; hover `hover:bg-[var(--today-hover)]`.
- **Other month:** `opacity-40`, `pointer-events-none`, `bg-[var(--surface-muted-tint)]`.
### 5.4 Timeline list (duties)
- **Dates:** Horizontal line and vertical tick from shared CSS in `globals.css`:
`.duty-timeline-date`, `.duty-timeline-date--today` (with `::before` / `::after`).
- **Cards:** Same card rules as above; `border-l-[3px]` + type class; optional flip card for contacts (see `DutyTimelineCard.tsx`).
---
## 6. Motion and performance
### 6.1 Timing
- **Tokens:** `--transition-fast: 0.15s`, `--transition-normal: 0.25s`, `--ease-out: cubic-bezier(0.32, 0.72, 0, 1)`.
- Use these for transitions and short animations so behaviour is consistent and predictable.
### 6.2 Reduced motion
- **Rule:** `@media (prefers-reduced-motion: reduce)` in `globals.css` shortens animation and transition durations globally. New animations should remain optional or short so they degrade gracefully when reduced.
### 6.3 Android low-performance devices
- **Detection:** `webapp-next/src/lib/telegram-android-perf.ts` reads Telegrams User-Agent and sets `data-perf="low"` on the document root when the device performance class is LOW.
- **CSS:** `[data-perf="low"] *` in `globals.css` minimizes animation/transition duration. Avoid adding heavy or long animations without considering this; prefer simple or no animation on low-end devices.
---
## 7. Accessibility
- **Focus:** Use `focus-visible:outline-accent` (or equivalent ring) on interactive elements; do not remove focus outline without a visible alternative.
- **Calendar:** Use `role="grid"` and `role="gridcell"`, `aria-label` on nav buttons (e.g. “Previous month”), and a composite `aria-label` on each day cell (date + event types). See `webapp-next/src/components/calendar/CalendarDay.tsx`.
- **Images and inputs:** Always provide labels (per Telegrams guidelines and WCAG).
---
## 8. Telegram integration
- **Ready gate:** `callMiniAppReadyOnce()` (in `lib/telegram-ready.ts`) is invoked by the layouts `ReadyGate` when `appContentReady` becomes true. Any route (/, /admin, not-found, in-app error) that sets `appContentReady` will trigger it so Telegram hides its loader; no route-specific logic is required.
- **Header and background:** On init (layout script and providers theme sync), call:
- `setBackgroundColor('bg_color')`
- `setHeaderColor('bg_color')`
- `setBottomBarColor('bottom_bar_bg_color')` when available (Bot API 7.10+).
- **Surface contrast:** When `--surface` equals `--bg` (e.g. some iOS OLED themes), `fixSurfaceContrast()` in `use-telegram-theme.ts` adjusts `--surface` using ThemeParams or a light color-mix so cards and panels remain visible.
### 8.1 Native control policy
- Use platform wrappers in `src/hooks/telegram/` rather than direct SDK calls in
feature components.
- **BackButton:** preferred for route-level back navigation in Telegram context.
- **SettingsButton:** use for route actions like opening `/admin` from calendar.
- **Main/Secondary button:** optional; use only if action must align with Telegram
bottom action affordance (do not duplicate with conflicting in-app primary CTA).
- **Haptics:** trigger only on meaningful user actions (submit, confirm, close).
### 8.2 Swipe and closing policy
- Keep vertical swipes enabled by default (`enableVerticalSwipes` behavior).
- Disable vertical swipes only on screens with explicit gesture conflict and document
the reason in code review.
- Enable closing confirmation only for stateful flows where accidental close can
lose user intent (e.g. reassignment flow in admin sheet).
### 8.3 Fullscreen/newer APIs policy
- Fullscreen APIs (`requestFullscreen`, `exitFullscreen`) are currently optional and
out of scope unless a feature explicitly requires immersive mode.
- If fullscreen is introduced, review safe area/content safe area and verify
`safeAreaChanged`, `contentSafeAreaChanged`, and `fullscreenChanged` handling.
---
## 9. Checklist for new screens and components
Use this for review when adding or changing UI:
- [ ] Use only design tokens from `globals.css` and Tailwind/shadcn aliases; no hardcoded colours.
- [ ] Page wrapper has `.content-safe`, `max-w-[var(--max-width-app)]`, and appropriate min-height (viewport-stable-height or `min-h-screen` with fallback).
- [ ] Buttons and cards follow the patterns above (variants, surfaces, border-l by type).
- [ ] Safe area is respected for bottom padding and for sheets/modals.
- [ ] Interactive elements and grids/lists have appropriate `aria-label`s and roles.
- [ ] New animations respect `prefers-reduced-motion` and `data-perf="low"` (short or minimal on low-end Android).
- [ ] User-facing strings and `aria-label`/`sr-only` text are localized via i18n (no hardcoded English in shared UI).
- [ ] Telegram controls are connected through platform hooks (`src/hooks/telegram/*`) instead of direct SDK calls.
- [ ] Vertical swipe and closing confirmation behavior follows the policy above.
## 10. Verification matrix (Mini App)
At minimum verify:
- Telegram light + dark themes.
- iOS and Android safe area/content-safe-area behavior (portrait + landscape).
- Android low-performance behavior (`data-perf="low"`).
- Deep link current duty (`startParam=duty`).
- Direct `/admin` open and reassignment flow.
- Access denied, not-found, and error boundary screens.
- Calendar swipe navigation with sticky header and native Telegram controls.