All files / src/lib/components OptionCard.svelte

0% Statements 0/7
0% Branches 0/5
0% Functions 0/1
0% Lines 0/7

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>