- 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.
264 lines
14 KiB
Markdown
264 lines
14 KiB
Markdown
# 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 [Telegram’s 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
|
||
|
||
Telegram’s 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 user’s 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 Telegram’s 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 Telegram’s 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 Telegram’s guidelines and WCAG).
|
||
|
||
---
|
||
|
||
## 8. Telegram integration
|
||
|
||
- **Ready gate:** `callMiniAppReadyOnce()` (in `lib/telegram-ready.ts`) is invoked by the layout’s `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 provider’s 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.
|