diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-27 16:41:28 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-27 16:41:28 +0200 |
| commit | 751af663f38e174203c10438e5ef36277e93cd68 (patch) | |
| tree | 6a5504ef58e43e79d85aca0c9df5257b9914049b | |
| parent | 4f2f6d757380e80f8e2937ffe99f4390865cb7fa (diff) | |
Use radio inputs and collapsible curation on ride detail
Replace dropdowns with vertical radio groups for bike and ride type.
Make curation block collapsible (details/summary) collapsed by default
and expanded when both categories are empty.
| -rw-r--r-- | web/static/style.css | 23 | ||||
| -rw-r--r-- | web/templates.templ | 55 |
2 files changed, 39 insertions, 39 deletions
diff --git a/web/static/style.css b/web/static/style.css index 3c9c05b..755a5cb 100644 --- a/web/static/style.css +++ b/web/static/style.css @@ -157,15 +157,28 @@ select { border: 1px solid var(--color-input-border); border-radius: .55rem; pad .section-heading .eyebrow { margin-bottom: .2rem; } .section-heading h2 { margin: 0; font-size: 1.2rem; } .section-count { display: grid; place-items: center; min-width: 2rem; height: 2rem; border-radius: 999px; background: var(--color-secondary-surface); color: var(--color-forest); font-weight: 800; } -.categories-panel { padding: 1rem 1.25rem; } -.categories-layout { display: flex; align-items: center; justify-content: space-between; gap: 1.25rem; flex-wrap: wrap; } +.categories-panel { padding: 0; overflow: hidden; } +.categories-details { display: block; } +.categories-summary { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 1rem 1.25rem; cursor: pointer; list-style: none; } +.categories-summary::-webkit-details-marker { display: none; } +.categories-summary::marker { display: none; } +.categories-summary:hover { background: var(--color-secondary-surface); } +.categories-summary:focus-visible { outline: .2rem solid var(--color-focus); outline-offset: -.2rem; } .categories-heading { display: flex; align-items: baseline; gap: .75rem; flex-wrap: wrap; } .categories-heading .eyebrow { margin: 0; } .categories-heading h2 { margin: 0; font-size: 1.1rem; } +.categories-chevron { color: var(--color-accent); font-size: 1.5rem; font-weight: 500; line-height: 1; transition: transform .2s; } +.categories-details[open] .categories-chevron { transform: rotate(180deg); } +.categories-details[open] .categories-summary { border-bottom: 1px solid var(--color-border-subtle); } .category-badge { display: inline-flex; align-items: center; border-radius: 999px; padding: .35rem .65rem; background: var(--color-notice-surface); color: var(--color-notice-text); font-size: .75rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; } -.category-form { display: flex; align-items: end; gap: .65rem; flex-wrap: wrap; margin-left: auto; } -.category-form label { min-width: 8.5rem; } -.category-form select { min-width: 8.5rem; } +.category-form { display: flex; align-items: end; gap: 1rem 1.25rem; flex-wrap: wrap; padding: 1rem 1.25rem; } +.category-fieldset { display: grid; gap: .35rem; margin: 0; padding: 0; border: 0; min-width: 0; } +.category-fieldset legend { padding: 0; color: var(--color-muted); font-size: .85rem; font-weight: 650; } +.category-options { display: grid; gap: .4rem; } +.category-option { display: inline-flex; align-items: center; gap: .35rem; border: 1px solid var(--color-input-border); border-radius: 999px; padding: .4rem .65rem; background: var(--color-surface); color: var(--color-foreground); font-size: .85rem; font-weight: 650; cursor: pointer; } +.category-option:has(input:checked) { border-color: var(--color-leaf); background: var(--color-secondary-surface); color: var(--color-forest); } +.category-option input { accent-color: var(--color-accent); } +.category-option:focus-within { outline: .2rem solid var(--color-focus); outline-offset: .1rem; } .category-form .button { align-self: end; padding: .55rem .9rem; } .data-quality-bulk { display: flex; align-items: end; gap: .75rem; flex-wrap: wrap; margin-bottom: 1rem; } .data-quality-bulk label { min-width: 8.5rem; } diff --git a/web/templates.templ b/web/templates.templ index 8fbed0c..10008d2 100644 --- a/web/templates.templ +++ b/web/templates.templ @@ -228,47 +228,34 @@ templ RideDetailContent(data RideDetailView) { </dl> </section> <section class="panel categories-panel"> - <div class="categories-layout"> - <div class="categories-heading"> - <p class="eyebrow">Curation</p> - <h2>Categories</h2> - </div> + <details class="categories-details" open?={ data.Ride.BikeType == "" && data.Ride.RideType == "" }> + <summary class="categories-summary"> + <div class="categories-heading"> + <p class="eyebrow">Curation</p> + <h2>Categories</h2> + </div> + <span class="categories-chevron" aria-hidden="true">⌄</span> + </summary> <form method="post" action={ templ.URL(rideCategoriesUpdateURL(data.Ride.ID)) } class="category-form"> - <label for="ride-bike-type">Bike type - <select id="ride-bike-type" name="bike_type" required> - if data.Ride.BikeType == "" { - <option value="" selected>—</option> - } else { - <option value="">—</option> - } + <fieldset class="category-fieldset"> + <legend>Bike type</legend> + <div class="category-options"> for _, opt := range bikeTypeOptions { - if data.Ride.BikeType == opt { - <option value={ string(opt) } selected>{ bikeTypeLabels[opt] }</option> - } else { - <option value={ string(opt) }>{ bikeTypeLabels[opt] }</option> - } - } - </select> - </label> - <label for="ride-ride-type">Ride type - <select id="ride-ride-type" name="ride_type" required> - if data.Ride.RideType == "" { - <option value="" selected>—</option> - } else { - <option value="">—</option> + <label class="category-option"><input type="radio" name="bike_type" value={ string(opt) } required checked?={ data.Ride.BikeType == opt }/><span>{ bikeTypeLabels[opt] }</span></label> } + </div> + </fieldset> + <fieldset class="category-fieldset"> + <legend>Ride type</legend> + <div class="category-options"> for _, opt := range rideTypeOptions { - if data.Ride.RideType == opt { - <option value={ string(opt) } selected>{ rideTypeLabels[opt] }</option> - } else { - <option value={ string(opt) }>{ rideTypeLabels[opt] }</option> - } + <label class="category-option"><input type="radio" name="ride_type" value={ string(opt) } required checked?={ data.Ride.RideType == opt }/><span>{ rideTypeLabels[opt] }</span></label> } - </select> - </label> + </div> + </fieldset> <button class="button" type="submit">Save</button> </form> - </div> + </details> </section> if data.HasRoute { if len(data.Profile.OfficialClimbs()) > 0 { |
