feat(a11y): add ARIA attributes, token colors, toast, and tab-switch links

Add aria-required to required inputs, role=alert to error messages,
aria-label to icon-only list nav buttons, and progressbar ARIA to bag
status bars. Replace hardcoded red/green colors with error/success
design tokens across all templates. Add toast notifications for entity
creation and tab-switch links for empty add-form states. Increase scan
upload body limit to 10MB.
This commit is contained in:
Jon Seager 2026-02-09 19:56:11 +00:00
parent aad927085b
commit 3c273feb18
No known key found for this signature in database
26 changed files with 213 additions and 61 deletions

View file

@ -67,8 +67,14 @@ pub(super) fn router() -> axum::Router<AppState> {
.route("/nearby-cafes", get(cafes::nearby_cafes))
.route("/extract-roaster", post(roasters::extract_roaster))
.route("/extract-roast", post(roasts::extract_roast_info))
.route("/extract-bag-scan", post(scan::extract_bag_scan))
.route("/scan", post(scan::submit_scan))
.route(
"/extract-bag-scan",
post(scan::extract_bag_scan).layer(DefaultBodyLimit::max(10 * 1024 * 1024)),
)
.route(
"/scan",
post(scan::submit_scan).layer(DefaultBodyLimit::max(10 * 1024 * 1024)),
)
.route("/check-in", post(checkin::submit_checkin))
.route("/cups", get(cups::list_cups).post(cups::create_cup))
.route("/cups/{id}", get(cups::get_cup).delete(cups::delete_cup))

View file

@ -48,12 +48,33 @@
type="module"
src="https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.6/bundles/datastar.js"
></script>
<script defer src="/static/js/location.js"></script>
<script defer src="/static/js/components/photo-capture.js"></script>
<script defer src="/static/js/components/searchable-select.js"></script>
<script defer src="/static/js/components/chip-scroll.js"></script>
<script defer src="/static/js/components/world-map.js"></script>
<script defer src="/static/js/components/donut-chart.js"></script>
{% block head %}{% endblock %}
<script>
const showToast = (msg, ms = 3000) => {
const el = document.getElementById("toast-message");
if (!el) return;
el.textContent = msg;
el.parentElement.classList.remove("hidden");
clearTimeout(showToast._t);
showToast._t = setTimeout(
() => el.parentElement.classList.add("hidden"),
ms,
);
};
document.addEventListener("DOMContentLoaded", () => {
const t = sessionStorage.getItem("toast");
if (t) {
sessionStorage.removeItem("toast");
showToast(t);
}
});
</script>
<script>
const toggleRow = (event) => {
if (event.target.closest(".card-detail")) return;
@ -184,5 +205,18 @@
</p>
</footer>
</main>
<div
id="toast-container"
class="fixed bottom-4 left-1/2 -translate-x-1/2 z-50"
>
<div
class="hidden rounded-lg border border-success-border bg-surface px-4 py-3 text-sm font-medium text-success-text shadow-lg"
role="status"
aria-live="polite"
>
<span id="toast-message"></span>
</div>
</div>
</body>
</html>

View file

@ -2,16 +2,6 @@
{% import "partials/location_search.html" as location %}
{% block title %}Brewlog · Add{% endblock %}
{% block head %}
<script>
{
{
location::location_search_js();
}
}
</script>
{% endblock %}
{% block content %}
<section
data-signals:_add-submitting="false"
@ -127,7 +117,8 @@
data-show="$_roasterExtractError"
data-text="$_roasterExtractError"
style="display:none"
class="mt-2 text-sm text-red-600"
class="mt-2 text-sm text-error"
role="alert"
></p>
</div>
@ -137,6 +128,7 @@
method="post"
action="/api/v1/roasters"
class="flex flex-col gap-4"
onsubmit="sessionStorage.setItem('toast', 'Roaster added')"
>
<div class="grid gap-4 sm:grid-cols-2">
<label class="flex flex-col gap-1 text-sm">
@ -148,6 +140,7 @@
type="text"
name="name"
required
aria-required="true"
class="input-field"
placeholder="Example Coffee Roasters"
data-bind:_roaster-name
@ -162,6 +155,7 @@
type="text"
name="country"
required
aria-required="true"
class="input-field"
placeholder="United States"
data-bind:_roaster-country
@ -216,7 +210,15 @@
<div class="text-sm text-text-secondary">
<h3 class="text-lg font-semibold text-text">Add a roaster first</h3>
<p class="mt-2">
Roasts need a roaster. Add a roaster above to enable this form.
Roasts need a roaster.
<button
type="button"
class="font-medium text-accent hover:underline"
data-on:click="$_addType = 'roaster'"
>
Add a roaster
</button>
to enable this form.
</p>
</div>
{% else %}
@ -270,7 +272,8 @@
data-show="$_roastExtractError"
data-text="$_roastExtractError"
style="display:none"
class="mt-2 text-sm text-red-600"
class="mt-2 text-sm text-error"
role="alert"
></p>
</div>
@ -280,6 +283,7 @@
method="post"
action="/api/v1/roasts"
class="flex flex-col gap-4"
onsubmit="sessionStorage.setItem('toast', 'Roast added')"
>
<div class="flex flex-col gap-1 text-sm">
<span
@ -314,6 +318,7 @@
type="text"
name="name"
required
aria-required="true"
class="input-field"
placeholder="Ethiopia Yirgacheffe"
data-bind:_roast-name
@ -328,6 +333,7 @@
type="text"
name="origin"
required
aria-required="true"
class="input-field"
placeholder="Ethiopia"
data-bind:_origin
@ -342,6 +348,7 @@
type="text"
name="region"
required
aria-required="true"
class="input-field"
placeholder="Guji"
data-bind:_region
@ -356,6 +363,7 @@
type="text"
name="producer"
required
aria-required="true"
class="input-field"
placeholder="Chelbesa Cooperative"
data-bind:_producer
@ -370,6 +378,7 @@
type="text"
name="process"
required
aria-required="true"
class="input-field"
placeholder="Washed"
data-bind:_process
@ -384,6 +393,7 @@
name="tasting_notes"
rows="2"
required
aria-required="true"
class="input-field"
placeholder="Blueberry, Jasmine"
data-bind:_tasting-notes
@ -413,7 +423,15 @@
<div class="text-sm text-text-secondary">
<h3 class="text-lg font-semibold text-text">Add a roast first</h3>
<p class="mt-2">
Bags need a roast. Add a roaster and roast to enable this form.
Bags need a roast.
<button
type="button"
class="font-medium text-accent hover:underline"
data-on:click="$_addType = 'roast'"
>
Add a roast
</button>
to enable this form.
</p>
</div>
{% else %}
@ -427,6 +445,7 @@
method="post"
action="/api/v1/bags"
class="mt-4 flex flex-col gap-4"
onsubmit="sessionStorage.setItem('toast', 'Bag added')"
>
<div class="flex flex-col gap-1 text-sm">
<span
@ -470,6 +489,7 @@
name="amount"
step="0.1"
required
aria-required="true"
class="input-field"
placeholder="250"
/>
@ -497,14 +517,30 @@
<div class="text-sm text-text-secondary">
<h3 class="text-lg font-semibold text-text">Open a bag first</h3>
<p class="mt-2">
Brews need an open bag of coffee. Add a bag to enable this form.
Brews need an open bag of coffee.
<button
type="button"
class="font-medium text-accent hover:underline"
data-on:click="$_addType = 'bag'"
>
Add a bag
</button>
to enable this form.
</p>
</div>
{% else if grinder_options.is_empty() || brewer_options.is_empty() %}
<div class="text-sm text-text-secondary">
<h3 class="text-lg font-semibold text-text">Add gear first</h3>
<p class="mt-2">
Brews need a grinder and brewer. Add gear to enable this form.
Brews need a grinder and brewer.
<button
type="button"
class="font-medium text-accent hover:underline"
data-on:click="$_addType = 'gear'"
>
Add gear
</button>
to enable this form.
</p>
</div>
{% else %}
@ -516,6 +552,7 @@
method="post"
action="/api/v1/brews"
class="mt-4 flex flex-col gap-6 overflow-hidden pb-16 md:pb-0"
onsubmit="sessionStorage.setItem('toast', 'Brew added')"
>
<!-- Coffee -->
<div>
@ -568,6 +605,7 @@
step="any"
min="1"
required
aria-required="true"
class="input-field flex-1 text-center"
data-bind:_brew-weight
/>
@ -616,6 +654,7 @@
<select
name="grinder_id"
required
aria-required="true"
class="input-field"
data-on:change="$_grinderDisplay = evt.target.options[evt.target.selectedIndex].text"
>
@ -648,6 +687,7 @@
step="any"
min="0"
required
aria-required="true"
class="input-field flex-1 text-center"
data-bind:_brew-grind
/>
@ -707,6 +747,7 @@
<select
name="brewer_id"
required
aria-required="true"
class="input-field"
data-on:change="$_brewerDisplay = evt.target.options[evt.target.selectedIndex].text"
>
@ -768,6 +809,7 @@
step="any"
min="10"
required
aria-required="true"
class="input-field flex-1 text-center"
data-bind:_brew-volume
/>
@ -800,6 +842,7 @@
min="0"
max="100"
required
aria-required="true"
class="input-field flex-1 text-center"
data-bind:_brew-temp
/>
@ -926,6 +969,7 @@
method="post"
action="/api/v1/gear"
class="mt-4 flex flex-col gap-4"
onsubmit="sessionStorage.setItem('toast', 'Gear added')"
>
<div class="grid gap-4 sm:grid-cols-3">
<label class="flex flex-col gap-1 text-sm">
@ -933,7 +977,12 @@
class="text-xs font-semibold text-text-muted uppercase tracking-wide"
>Category*</span
>
<select name="category" required class="input-field">
<select
name="category"
required
aria-required="true"
class="input-field"
>
<option value="">Select a category</option>
<option value="grinder">Grinder</option>
<option value="brewer">Brewer</option>
@ -949,6 +998,7 @@
type="text"
name="make"
required
aria-required="true"
class="input-field"
placeholder="Baratza"
/>
@ -962,6 +1012,7 @@
type="text"
name="model"
required
aria-required="true"
class="input-field"
placeholder="Encore"
/>
@ -998,7 +1049,8 @@
data-show="$_cafeError"
data-text="$_cafeError"
style="display:none"
class="mt-2 text-sm text-red-600"
class="mt-2 text-sm text-error"
role="alert"
></p>
</div>
@ -1036,6 +1088,7 @@
method="post"
action="/api/v1/cafes"
class="mt-4 flex flex-col gap-4"
onsubmit="sessionStorage.setItem('toast', 'Cafe added')"
>
<div class="grid gap-4 sm:grid-cols-2">
<label class="flex flex-col gap-1 text-sm">
@ -1047,6 +1100,7 @@
type="text"
name="name"
required
aria-required="true"
class="input-field"
placeholder="Blue Bottle Coffee"
data-bind:_cafe-name
@ -1061,6 +1115,7 @@
type="text"
name="city"
required
aria-required="true"
class="input-field"
placeholder="San Francisco"
data-bind:_cafe-city
@ -1075,6 +1130,7 @@
type="text"
name="country"
required
aria-required="true"
class="input-field"
placeholder="United States"
data-bind:_cafe-country
@ -1103,6 +1159,7 @@
name="latitude"
step="any"
required
aria-required="true"
class="input-field"
placeholder="37.7749"
data-bind:_cafe-lat
@ -1118,6 +1175,7 @@
name="longitude"
step="any"
required
aria-required="true"
class="input-field"
placeholder="-122.4194"
data-bind:_cafe-lng
@ -1147,8 +1205,27 @@
Add a roast and cafe first
</h3>
<p class="mt-2">
Cups need both a roast and a cafe. Add them above to enable this
form.
Cups need both a roast and a cafe.
{% if roast_options.is_empty() %}
<button
type="button"
class="font-medium text-accent hover:underline"
data-on:click="$_addType = 'roast'"
>
Add a roast
</button>
{% endif %}
{% if roast_options.is_empty() && cafe_options.is_empty() %}and{% endif %}
{% if cafe_options.is_empty() %}
<button
type="button"
class="font-medium text-accent hover:underline"
data-on:click="$_addType = 'cafe'"
>
add a cafe
</button>
{% endif %}
to enable this form.
</p>
</div>
{% else %}
@ -1162,6 +1239,7 @@
method="post"
action="/api/v1/cups"
class="mt-4 flex flex-col gap-4"
onsubmit="sessionStorage.setItem('toast', 'Cup added')"
>
<div class="grid gap-4 sm:grid-cols-2">
<div class="flex flex-col gap-1 text-sm">

View file

@ -73,7 +73,8 @@
>
<div
id="add-passkey-error"
class="mb-3 hidden rounded-md bg-red-100 border border-red-300 p-2 text-sm text-red-800 dark:bg-red-950/40 dark:border-red-800 dark:text-red-300"
class="mb-3 hidden rounded-md bg-error-bg border border-error-border p-2 text-sm text-error-text"
role="alert"
></div>
<div class="flex items-end gap-3">
<label class="flex-1 flex flex-col gap-1 text-sm">
@ -82,6 +83,7 @@
type="text"
id="passkey-name"
required
aria-required="true"
class="input-field"
placeholder="e.g. MacBook Touch ID, iPhone"
/>
@ -188,7 +190,8 @@
data-show="$_tokenError"
data-text="$_tokenError"
style="display: none"
class="mb-3 rounded-md bg-red-100 border border-red-300 p-2 text-sm text-red-800 dark:bg-red-950/40 dark:border-red-800 dark:text-red-300"
class="mb-3 rounded-md bg-error-bg border border-error-border p-2 text-sm text-error-text"
role="alert"
></p>
<form
data-on:submit="$_creatingToken = true; $_tokenError = ''; @post('/api/v1/tokens', {contentType: 'form'})"
@ -204,6 +207,7 @@
name="name"
id="token-name"
required
aria-required="true"
class="input-field"
placeholder="e.g. laptop-cli, ci-server"
/>
@ -230,29 +234,29 @@
<div
data-show="$_tokenCreated"
style="display: none"
class="relative rounded-md border border-green-300 dark:border-green-800 bg-green-50 dark:bg-green-950/40 p-4"
class="relative rounded-md border border-success-border bg-success-bg p-4"
>
<button
type="button"
onclick="window.location.reload()"
class="absolute top-3.5 right-3 inline-flex h-6 w-6 items-center justify-center rounded text-green-800 dark:text-green-300 transition hover:text-green-600 dark:hover:text-green-100"
class="absolute top-3.5 right-3 inline-flex h-6 w-6 items-center justify-center rounded text-success-text transition hover:text-accent-text"
aria-label="Dismiss"
>
{{ icons::x_mark("h-4 w-4") }}
</button>
<p class="pr-6 text-sm font-medium text-green-800 dark:text-green-300">
<p class="pr-6 text-sm font-medium text-success-text">
Token created! Copy it now — it will not be shown again.
</p>
<div class="mt-3 flex items-center gap-2">
<code
id="token-value"
data-text="$_tokenValue"
class="flex-1 rounded bg-surface px-3 py-2 text-sm font-mono text-text border border-green-200 dark:border-green-800 break-all select-all"
class="flex-1 rounded bg-surface px-3 py-2 text-sm font-mono text-text border border-success-border break-all select-all"
></code>
<button
type="button"
onclick="copyToken(this)"
class="shrink-0 inline-flex items-center gap-2 rounded-md bg-green-600 px-3 py-1.5 text-sm font-medium text-green-50 transition hover:bg-green-500"
class="shrink-0 inline-flex items-center gap-2 rounded-md bg-success px-3 py-1.5 text-sm font-medium text-accent-text transition hover:bg-success"
>
{{ icons::clipboard("h-4 w-4") }} Copy
</button>
@ -335,11 +339,12 @@
<div
id="backup-status"
class="hidden rounded-md border border-green-300 bg-green-50 p-4 text-sm text-green-800 dark:bg-green-950/40 dark:border-green-800 dark:text-green-300"
class="hidden rounded-md border border-success-border bg-success-bg p-4 text-sm text-success-text"
></div>
<div
id="backup-error"
class="hidden rounded-md bg-red-100 border border-red-300 p-3 text-sm text-red-800 dark:bg-red-950/40 dark:border-red-800 dark:text-red-300"
class="hidden rounded-md bg-error-bg border border-error-border p-3 text-sm text-error-text"
role="alert"
></div>
</div>
</section>

View file

@ -99,7 +99,7 @@
{% endif %}
<button
type="button"
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-red-600 transition hover:bg-surface-alt"
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-error transition hover:bg-surface-alt"
data-on:click="confirm('Delete this bag? This cannot be undone.') && @delete('/api/v1/bags/{{ bag.id }}')"
>
{{ icons::delete("h-4 w-4") }} Delete

View file

@ -99,7 +99,7 @@
<div class="rounded-lg border bg-surface p-5 flex items-center gap-2">
<button
type="button"
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-red-600 transition hover:bg-surface-alt"
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-error transition hover:bg-surface-alt"
data-on:click="confirm('Delete this brew? This cannot be undone.') && @delete('/api/v1/brews/{{ brew.id }}')"
>
{{ icons::delete("h-4 w-4") }} Delete

View file

@ -59,7 +59,7 @@
<div class="rounded-lg border bg-surface p-5 flex items-center gap-2">
<button
type="button"
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-red-600 transition hover:bg-surface-alt"
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-error transition hover:bg-surface-alt"
data-on:click="confirm('Delete this cafe? This cannot be undone.') && @delete('/api/v1/cafes/{{ cafe.id }}')"
>
{{ icons::delete("h-4 w-4") }} Delete

View file

@ -7,15 +7,6 @@
{% block title %}
Brewlog · Check In
{% endblock %}
{% block head %}
<script>
{
{
location::location_search_js();
}
}
</script>
{% endblock %}
{% block content %}
<section
id="checkin-root"
@ -51,7 +42,11 @@
</header>
<!-- Step progress -->
<div class="relative mx-auto mt-6" style="width: 80%">
<nav
aria-label="Check-in progress"
class="relative mx-auto mt-6"
style="width: 80%"
>
<!-- Background track (continuous line through dot centers) -->
<div
class="absolute bg-border"
@ -79,6 +74,7 @@
<span
class="mt-2 text-xs font-medium transition-colors"
data-class="{'text-accent': $_step === 1, 'text-text': $_step > 1}"
data-attr:aria-current="$_step === 1 ? 'step' : null"
>Cafe</span
>
</div>
@ -90,6 +86,7 @@
<span
class="mt-2 text-xs font-medium transition-colors"
data-class="{'text-accent': $_step === 2, 'text-text': $_step > 2, 'text-text-muted': $_step < 2}"
data-attr:aria-current="$_step === 2 ? 'step' : null"
>Coffee</span
>
</div>
@ -101,11 +98,12 @@
<span
class="mt-2 text-xs font-medium transition-colors"
data-class="{'text-accent': $_step === 3, 'text-text-muted': $_step < 3}"
data-attr:aria-current="$_step === 3 ? 'step' : null"
>Submit</span
>
</div>
</div>
</div>
</nav>
<!-- Selected cafe summary (shown after selection) -->
<div
@ -161,10 +159,11 @@
<!-- Error display -->
<p
class="mt-3 text-sm text-red-600"
class="mt-3 text-sm text-error"
data-show="$_error"
style="display: none"
data-text="$_error"
role="alert"
></p>
<!-- Step 1: Cafe selection -->
@ -189,6 +188,7 @@
type="text"
data-bind:_cafe-name
required
aria-required="true"
class="input-field"
/>
</label>
@ -283,7 +283,7 @@
</div>
<div
class="mb-4 rounded-md bg-green-50 border border-green-200 px-3 py-2 text-sm text-green-800"
class="mb-4 rounded-md bg-success-bg border border-success-border px-3 py-2 text-sm text-success-text"
data-show="$_scanSuccess"
style="display: none"
>
@ -345,7 +345,7 @@
<form
data-on:submit="$_submitting = true; $_error = ''; @post('/api/v1/check-in', {contentType: 'form'})"
data-on:datastar-fetch="if (!$_submitting) return; if (evt.detail.type === 'error') { $_submitting = false; $_error = 'Check-in failed. Please try again.' }"
data-on:datastar-fetch="if (!$_submitting) return; if (evt.detail.type === 'finished') { sessionStorage.setItem('toast', 'Checked in') } else if (evt.detail.type === 'error') { $_submitting = false; $_error = 'Check-in failed. Please try again.' }"
>
<input type="hidden" name="cafe_id" data-attr:value="$_cafeId" />
<input type="hidden" name="cafe_name" data-attr:value="$_cafeName" />

View file

@ -14,12 +14,12 @@
</div>
{% else if error.is_some() %}
<div class="flex flex-col items-center text-center gap-3">
{{ icons::x_circle("h-10 w-10 text-red-600 dark:text-red-400") }}
{{ icons::x_circle("h-10 w-10 text-error") }}
<h1 class="text-2xl font-semibold text-accent">
Authentication Failed
</h1>
<div
class="w-full rounded-md bg-red-100 border border-red-300 p-3 text-sm text-red-800 dark:bg-red-950/40 dark:border-red-800 dark:text-red-300"
class="w-full rounded-md bg-error-bg border border-error-border p-3 text-sm text-error-text"
>
{{ error.as_ref().unwrap() }}
</div>

View file

@ -74,7 +74,7 @@
<div class="rounded-lg border bg-surface p-5 flex items-center gap-2">
<button
type="button"
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-red-600 transition hover:bg-surface-alt"
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-error transition hover:bg-surface-alt"
data-on:click="confirm('Delete this cup? This cannot be undone.') && @delete('/api/v1/cups/{{ cup.id }}')"
>
{{ icons::delete("h-4 w-4") }} Delete

View file

@ -42,7 +42,7 @@
<div class="rounded-lg border bg-surface p-5 flex items-center gap-2">
<button
type="button"
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-red-600 transition hover:bg-surface-alt"
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-error transition hover:bg-surface-alt"
data-on:click="confirm('Delete this gear? This cannot be undone.') && @delete('/api/v1/gear/{{ gear.id }}')"
>
{{ icons::delete("h-4 w-4") }} Delete

View file

@ -86,7 +86,8 @@
data-show="$_extractError"
data-text="$_extractError"
style="display:none"
class="mt-2 text-sm text-red-600 text-center"
class="mt-2 text-sm text-error text-center"
role="alert"
></p>
</div>
@ -95,7 +96,7 @@
<form
class="rounded-lg border bg-surface p-5 flex flex-col gap-6"
data-on:submit="$_scanSubmitting = true; $_scanError = ''; @post('/api/v1/scan', {contentType: 'form'})"
data-on:datastar-fetch="if (!$_scanSubmitting) return; if (evt.detail.type === 'finished') { window.location.reload() } else if (evt.detail.type === 'error') { $_scanSubmitting = false; $_scanError = 'Save failed. Please try again.' }"
data-on:datastar-fetch="if (!$_scanSubmitting) return; if (evt.detail.type === 'finished') { sessionStorage.setItem('toast', 'Roast saved'); window.location.reload() } else if (evt.detail.type === 'error') { $_scanSubmitting = false; $_scanError = 'Save failed. Please try again.' }"
>
{% include "partials/forms/scan_result_form.html" %}
</form>

View file

@ -13,7 +13,8 @@
<div
id="login-error"
class="mt-4 hidden rounded-md bg-red-100 border border-red-300 p-3 text-sm text-red-800"
class="mt-4 hidden rounded-md bg-error-bg border border-error-border p-3 text-sm text-error-text"
role="alert"
></div>
<div

View file

@ -14,7 +14,8 @@
<div
id="register-error"
class="mt-4 hidden rounded-md bg-red-100 border border-red-300 p-3 text-sm text-red-800"
class="mt-4 hidden rounded-md bg-error-bg border border-error-border p-3 text-sm text-error-text"
role="alert"
></div>
<div
@ -32,6 +33,7 @@
type="text"
id="display-name"
required
aria-required="true"
autofocus
class="input-field"
placeholder="Display name"
@ -44,6 +46,7 @@
type="text"
id="passkey-name"
required
aria-required="true"
class="input-field"
placeholder="e.g. MacBook Touch ID, iPhone"
/>

View file

@ -34,7 +34,7 @@
<div class="rounded-lg border bg-surface p-5 flex items-center gap-2">
<button
type="button"
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-red-600 transition hover:bg-surface-alt"
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-error transition hover:bg-surface-alt"
data-on:click="confirm('Delete this roast? This cannot be undone.') && @delete('/api/v1/roasts/{{ roast.id }}')"
>
{{ icons::delete("h-4 w-4") }} Delete

View file

@ -63,7 +63,7 @@
<div class="rounded-lg border bg-surface p-5 flex items-center gap-2">
<button
type="button"
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-red-600 transition hover:bg-surface-alt"
class="inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium text-error transition hover:bg-surface-alt"
data-on:click="confirm('Delete this roaster? This cannot be undone.') && @delete('/api/v1/roasters/{{ roaster.id }}')"
>
{{ icons::delete("h-4 w-4") }} Delete

View file

@ -52,7 +52,12 @@
>
No more events.
</p>
<p id="timeline-error" class="text-sm text-red-600" hidden></p>
<p
id="timeline-error"
class="text-sm text-error"
role="alert"
hidden
></p>
</div>
<div id="timeline-sentinel" class="h-1"></div>
</section>

View file

@ -215,7 +215,8 @@
data-show="$_scanError"
data-text="$_scanError"
style="display:none"
class="text-sm text-red-600"
class="text-sm text-error"
role="alert"
></p>
<div
data-show="$_scanSubmitting"

View file

@ -79,6 +79,11 @@
<div>
<div
class="h-2.5 rounded-full bg-surface-alt overflow-hidden"
role="progressbar"
aria-valuenow="{{ bag.used_percent }}"
aria-valuemin="0"
aria-valuemax="100"
aria-label="Coffee used"
>
<div
class="h-full rounded-full bg-accent"
@ -95,6 +100,11 @@
<td data-label="" class="card-progress md:hidden">
<div
class="h-1.5 rounded-full bg-surface-alt overflow-hidden"
role="progressbar"
aria-valuenow="{{ bag.used_percent }}"
aria-valuemin="0"
aria-valuemax="100"
aria-label="Coffee used"
>
<div
class="h-full rounded-full bg-accent"
@ -129,6 +139,7 @@
</span>
<a
href="/bags/{{ bag.id }}"
aria-label="View {{ bag.roast_name }}"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt"
>
{{ icons::chevron_right("h-5 w-5") }}

View file

@ -136,6 +136,7 @@
<td data-label="" class="card-actions px-4 py-3 text-right">
<a
href="/brews/{{ brew.id }}"
aria-label="View {{ brew.roast_name }}"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt"
>
{{ icons::chevron_right("h-5 w-5") }}

View file

@ -94,6 +94,7 @@
<td data-label="" class="card-actions px-4 py-3 text-right">
<a
href="{{ cafe.detail_path }}"
aria-label="View {{ cafe.name }}"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt"
>
{{ icons::chevron_right("h-5 w-5") }}

View file

@ -100,6 +100,7 @@
<td data-label="" class="card-actions px-4 py-3 text-right">
<a
href="/cups/{{ cup.id }}"
aria-label="View {{ cup.roast_name }}"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt"
>
{{ icons::chevron_right("h-5 w-5") }}

View file

@ -81,6 +81,7 @@
<td data-label="" class="card-actions px-4 py-3 text-right">
<a
href="/gear/{{ item.id }}"
aria-label="View {{ item.full_name }}"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt"
>
{{ icons::chevron_right("h-5 w-5") }}

View file

@ -108,6 +108,7 @@
<td data-label="" class="card-actions px-4 py-3 text-right">
<a
href="{{ roast.detail_path }}"
aria-label="View {{ roast.name }}"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt"
>
{{ icons::chevron_right("h-5 w-5") }}

View file

@ -96,6 +96,7 @@
<td data-label="" class="card-actions px-4 py-3 text-right">
<a
href="{{ roaster.detail_path }}"
aria-label="View {{ roaster.name }}"
class="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-muted transition hover:text-accent hover:bg-surface-alt"
>
{{ icons::chevron_right("h-5 w-5") }}

View file

@ -31,6 +31,7 @@
data-show="{{ error_signal }}"
data-text="{{ error_signal }}"
style="display:none"
class="mt-2 text-sm text-red-600"
class="mt-2 text-sm text-error"
role="alert"
></p>
{% endmacro %}