docs(32): UI design contract

This commit is contained in:
2026-04-13 16:59:25 +02:00
parent 49c59fded9
commit cb0c1e8c9a

View File

@@ -0,0 +1,225 @@
---
phase: 32
slug: setup-sharing-system
status: approved
shadcn_initialized: false
preset: none
created: 2026-04-13
---
# Phase 32 — UI Design Contract
> Visual and interaction contract for the Setup Sharing System. Covers share button, share modal, visibility picker, and shared setup viewer.
---
## Design System
| Property | Value |
|----------|-------|
| Tool | none |
| Preset | not applicable |
| Component library | none (custom Tailwind components) |
| Icon library | Lucide via `LucideIcon` component from `lib/iconData` |
| Font | System font stack (inherited from existing app) |
---
## Spacing Scale
Declared values (must be multiples of 4):
| Token | Value | Usage |
|-------|-------|-------|
| xs | 4px | Icon gaps, inline padding |
| sm | 8px | Compact element spacing, gap-2 |
| md | 16px | Default element spacing, gap-4, p-4 |
| lg | 24px | Section padding, p-6 |
| xl | 32px | Layout gaps |
| 2xl | 48px | Major section breaks |
| 3xl | 64px | Page-level spacing |
Exceptions: none
---
## Typography
| Role | Size | Weight | Line Height |
|------|------|--------|-------------|
| Body | 14px (text-sm) | 400 | 1.5 |
| Label | 14px (text-sm) | 500 (font-medium) | 1.5 |
| Heading | 16px (text-base) | 600 (font-semibold) | 1.5 |
| Display | 20px (text-xl) | 600 (font-semibold) | 1.5 |
---
## Color
| Role | Value | Usage |
|------|-------|-------|
| Dominant (60%) | gray-50 (#f9fafb) | Page background, surfaces |
| Secondary (30%) | white (#ffffff) | Cards, modals, panels |
| Accent (10%) | gray-700 (#374151) | Primary action buttons (Add Items, share CTA) |
| Destructive | red-600 (#dc2626) | Revoke link, delete actions |
Accent reserved for: primary action buttons only (Add Items, Create Link)
### Visibility State Colors
| State | Icon | Text Color | Background |
|-------|------|------------|------------|
| Private | `lock` | gray-500 | gray-50 |
| Link | `link` | blue-600 | blue-50 |
| Public | `globe` | green-700 | green-50 |
---
## Component Specifications
### Share Button (replaces globe toggle)
**Desktop variant:**
- Position: same location as current globe toggle button in setup detail header bar
- Layout: `inline-flex items-center gap-1.5 px-3 py-2`
- Text: "Share" (always visible regardless of visibility state)
- Icon: varies by visibility state (see color table above), size 16px
- Background/text color: matches visibility state from color table
- Rounded: `rounded-lg`
- Hover: lighten background one shade
**Mobile variant:**
- Layout: `inline-flex items-center justify-center min-w-[44px] min-h-[44px] p-2`
- Icon only (no text), same icon/color logic as desktop
- `aria-label`: "Share settings"
- Rounded: `rounded-lg`
### Share Modal
**Overlay:** `fixed inset-0 z-50 bg-black/50 flex items-center justify-center`
**Modal container:**
- Desktop: `bg-white rounded-xl shadow-lg p-6 max-w-md mx-4 w-full`
- Max height: `max-h-[80vh] overflow-y-auto`
- Matches existing modal pattern (see ConfirmDialog.tsx, CreateThreadModal.tsx)
**Header:**
- Title: "Share Setup" (`text-lg font-semibold text-gray-900`)
- Close button: top-right, `LucideIcon name="x" size={20}`, `text-gray-400 hover:text-gray-600`
- Divider: `border-b border-gray-100 pb-4 mb-4`
**Visibility Picker Section:**
- Label: "Visibility" (`text-sm font-medium text-gray-700 mb-2`)
- Three radio-style buttons in a vertical stack, `gap-2`
- Each option: `flex items-center gap-3 p-3 rounded-lg border cursor-pointer transition-colors`
- Unselected: `border-gray-200 hover:border-gray-300`
- Selected: `border-{state-color}-200 bg-{state-color}-50`
- Option layout:
- Icon (size 20, state color)
- Label (`text-sm font-medium text-gray-900`): "Private" / "Link sharing" / "Public"
- Description (`text-xs text-gray-500`): "Only you can access" / "Anyone with the link" / "Visible on your profile"
**Create Link Section (visible when visibility is `link` or `public`):**
- Divider: `border-t border-gray-100 pt-4 mt-4`
- Section label: "Share Links" (`text-sm font-medium text-gray-700 mb-3`)
- Create row: `flex items-center gap-2`
- Expiration dropdown: `select` element styled with `px-3 py-2 text-sm border border-gray-200 rounded-lg bg-white`
- Options: "7 days", "14 days" (default), "30 days", "No expiration"
- Create button: `px-4 py-2 bg-gray-700 hover:bg-gray-800 text-white text-sm font-medium rounded-lg`
- Text: "Create Link"
**Active Links List:**
- Each link: `flex items-center gap-2 p-3 bg-gray-50 rounded-lg mb-2`
- URL display: `text-sm text-gray-600 truncate flex-1` showing `/s/{token-prefix}...`
- Expiration badge: `text-xs text-gray-400` showing "Expires {date}" or "No expiration"
- Copy button: `p-1.5 text-gray-400 hover:text-gray-600 rounded` with `LucideIcon name="copy" size={16}`
- After copy: icon changes to `check` with `text-green-500` for 2 seconds
- Revoke button: `p-1.5 text-gray-400 hover:text-red-500 rounded` with `LucideIcon name="x" size={16}`
**Empty state (no links yet):**
- Text: "No share links yet" (`text-sm text-gray-400 text-center py-4`)
**Deactivation warning (when switching to private with active links):**
- Inline warning below visibility picker: `flex items-start gap-2 p-3 bg-amber-50 border border-amber-200 rounded-lg mt-2`
- Icon: `LucideIcon name="alert-triangle" size={16}` in `text-amber-500`
- Text: "Switching to private will deactivate all share links. They can be reactivated by switching back." (`text-sm text-amber-700`)
### Shared Setup Viewer
**Route:** `/setups/:setupId?share={token}`
**Shared banner:**
- Position: top of setup detail page, before header
- Layout: `flex items-center gap-2 px-4 py-2 bg-blue-50 border-b border-blue-100`
- Icon: `LucideIcon name="link" size={16}` in `text-blue-500`
- Text: "Shared setup" (`text-sm text-blue-700`)
- Appears only when viewing via share token
**Content:** Identical to public setup view (read-only item list with weight summary, no action buttons)
---
## Copywriting Contract
| Element | Copy |
|---------|------|
| Modal title | Share Setup |
| Visibility: Private label | Private |
| Visibility: Private description | Only you can access |
| Visibility: Link label | Link sharing |
| Visibility: Link description | Anyone with the link |
| Visibility: Public label | Public |
| Visibility: Public description | Visible on your profile |
| Create link CTA | Create Link |
| Empty links state | No share links yet |
| Deactivation warning | Switching to private will deactivate all share links. They can be reactivated by switching back. |
| Copy success toast | Link copied |
| Revoke confirmation | Revoke this share link? |
| Shared banner text | Shared setup |
| Expired link error | This share link has expired |
| Invalid link error | This share link is no longer valid |
---
## Registry Safety
| Registry | Blocks Used | Safety Gate |
|----------|-------------|-------------|
| No external registries | N/A | N/A |
All components are custom Tailwind — no shadcn or third-party UI registry blocks.
---
## Responsive Behavior
| Breakpoint | Share Button | Share Modal |
|------------|-------------|-------------|
| Mobile (<768px) | Icon only, 44x44px touch target | Full width with mx-4 margin |
| Desktop (>=768px) | Icon + "Share" text | max-w-md centered |
---
## Interaction States
| Interaction | Behavior |
|-------------|----------|
| Open modal | Click share button, modal appears with current visibility pre-selected |
| Change visibility | Immediate API call on selection, optimistic update |
| Create link | API call, new link appears in list, auto-copy to clipboard |
| Copy link | Copy full URL to clipboard, show check icon for 2s |
| Revoke link | Confirmation prompt (reuse ConfirmDialog pattern), then remove from list |
| Close modal | Click X, click overlay, or press Escape |
---
## Checker Sign-Off
- [x] Dimension 1 Copywriting: PASS
- [x] Dimension 2 Visuals: PASS
- [x] Dimension 3 Color: PASS
- [x] Dimension 4 Typography: PASS
- [x] Dimension 5 Spacing: PASS
- [x] Dimension 6 Registry Safety: PASS
**Approval:** approved 2026-04-13