feat(oxibadge): add onboarding flow with i18n and reader preferences
Check, format and test / build (push) Successful in 9m59s
Check, format and test / build (push) Successful in 9m59s
Adds the first real screen: a setup form for the device's host/port, tested against the real device (3 retries) and persisted to preferences.toml before showing a placeholder "done" screen. - Rust: test_connection/save_preferences/load_preferences commands; preferences stored as [[reader]] in TOML so more readers can be added later without a format change. - Svelte: split the monolithic page into reusable components (Screen, Column, Field, Button, ErrorBanner, Hint) under src/lib/components, sharing design tokens from app.css. - Automatic it/en i18n via svelte-i18n, locale detected from navigator.language. Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
let {
|
||||
id,
|
||||
label,
|
||||
value = $bindable(),
|
||||
type = 'text',
|
||||
placeholder = '',
|
||||
oninput,
|
||||
icon
|
||||
}: {
|
||||
id: string;
|
||||
label: string;
|
||||
value: string | number;
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
oninput?: () => void;
|
||||
icon?: Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="field">
|
||||
<label for={id}>{label}</label>
|
||||
{#if icon}{@render icon()}{/if}
|
||||
<input {id} {type} {placeholder} bind:value {oninput} autocomplete="off" spellcheck="false" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.field {
|
||||
position: relative;
|
||||
height: var(--ox-control-h);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 16px;
|
||||
background: var(--ox-bg);
|
||||
border: 1px solid var(--ox-border);
|
||||
border-radius: var(--ox-radius);
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
box-shadow 0.15s;
|
||||
}
|
||||
|
||||
.field:hover {
|
||||
border-color: var(--ox-border-hover);
|
||||
}
|
||||
|
||||
.field:focus-within {
|
||||
border-color: var(--ox-accent);
|
||||
box-shadow: 0 0 0 4px var(--ox-accent-ring);
|
||||
}
|
||||
|
||||
.field label {
|
||||
position: absolute;
|
||||
top: -9px;
|
||||
left: 12px;
|
||||
padding: 0 6px;
|
||||
background: var(--ox-bg);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--ox-label);
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.field:focus-within label {
|
||||
color: var(--ox-accent-soft);
|
||||
}
|
||||
|
||||
.field :global(svg) {
|
||||
color: #6e6e6e;
|
||||
}
|
||||
|
||||
.field input {
|
||||
all: unset;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
font-family: var(--ox-font-mono);
|
||||
font-size: 16px;
|
||||
color: var(--ox-text);
|
||||
caret-color: var(--ox-accent);
|
||||
}
|
||||
|
||||
.field input::placeholder {
|
||||
color: var(--ox-placeholder);
|
||||
}
|
||||
|
||||
.field input[type='number']::-webkit-outer-spin-button,
|
||||
.field input[type='number']::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user