Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | <script lang="ts"> import Icon from './Icon.svelte'; import type { HTMLButtonAttributes } from 'svelte/elements'; // Coastal v2 structured-choice control. // Spec: design/coastal-v2/project/directions/coastal-v2.jsx // Post-workout review screen (lines 662-681). // // The house pattern for "closed questions with predefined response options" // (per the product's "structured questions, not open-ended conversation" // principle) is a large tappable card, NOT a native radio. Selected state is // a soft primary wash + primary border + check, sized for a calm thumb. // // Rendered as a <button> so it is keyboard- and AT-operable. Callers wire // selection via `selected` + `onclick`; group semantics (role="radio") can // be layered by the consuming fieldset where needed. interface Props extends HTMLButtonAttributes { /** Visual selected state. */ selected?: boolean; /** Optional leading glyph — an emoji or single character. */ glyph?: string; /** Primary option text. */ label: string; /** Optional supporting line beneath the label. */ description?: string; } let { selected = false, glyph, label, description, class: className = '', ...rest }: Props = $props(); </script> <button type="button" class="option {className}" class:option--selected={selected} aria-pressed={selected} {...rest} > {#if Iglyph} <span class="option__glyph" aria-hidden="true">{glyph}</span> {/if} <span class="option__body"> <span class="option__label">{label}</span> {#if Idescription} <span class="option__description">{description}</span> {/if} </span> <span class="option__check" aria-hidden="true"> {#if Iselected} <Icon name="check" size={18} /> {/if} </span> </button> <style> .option { display: flex; align-items: center; gap: var(--space-3); width: 100%; text-align: left; padding: var(--space-3) var(--space-4); border-radius: var(--radius-md); background: var(--surface); border: 1.5px solid var(--line); color: var(--ink); font-family: var(--font-sans); font-size: 15px; min-height: 56px; } .option:not(.option--selected):hover { border-color: var(--ink-muted); background: var(--surface-alt); } .option--selected { background: var(--primary-soft); border-color: var(--primary); color: var(--on-primary-soft); } .option__glyph { font-size: 20px; line-height: 1; flex-shrink: 0; } .option__body { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; } .option__label { font-weight: 500; letter-spacing: -0.005em; } .option__description { font-size: 13px; color: var(--ink-muted); } .option--selected .option__description { color: inherit; opacity: 0.8; } .option__check { flex-shrink: 0; width: 18px; height: 18px; color: var(--primary); display: flex; align-items: center; justify-content: center; } </style> |