--- 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