You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/routes/test/time-field/+page.svelte

295 lines
8.0 KiB

<script lang="ts">
import { TestLayout, StateItem, PropsTable, DemoSection } from '../_components';
import { Terra, TimeField } from '$uix/terra';
import { createTerraTranslator, createTerraDateTimeFormatter } from '$uix/terra/system';
import { Time, type TimeValue, type HourCycle } from '$lib/util/dias';
const LOCALES = [
{ id: 'es-ES', label: 'es-ES' },
{ id: 'en-US', label: 'en-US' },
{ id: 'en-GB', label: 'en-GB' },
{ id: 'de-DE', label: 'de-DE' },
{ id: 'ja-JP', label: 'ja-JP' }
];
let locale = $state('es-ES');
const translator = createTerraTranslator({
locale: () => locale
});
let timeFormat = $state<HourCycle>(24);
const dateTimeFormatter = createTerraDateTimeFormatter({
timeFormat: () => timeFormat
});
let value1 = $state<Time | undefined>(new Time(9, 30, 0));
let value2 = $state<Time | undefined>(new Time(14, 45, 20));
let value3 = $state<Time | undefined>(new Time(8, 0, 0));
let value4 = $state<Time | undefined>(new Time(18, 15, 0));
function fmt(value: Time | undefined) {
if (!value) return '—';
return [value.hour, value.minute, value.second]
.map((part) => String(part).padStart(2, '0'))
.join(':');
}
const timeFieldProps = [
{
name: 'locale',
type: 'string',
default: '"es-ES"',
description: 'Locale para formato de hora'
},
{
name: 'timeFormat',
type: 'HourCycle',
default: '24',
description: 'Formato de hora: 12 o 24'
},
{
name: 'granularity',
type: '"hour" | "minute" | "second"',
default: '"minute"',
description: 'Nivel de precisión'
},
{
name: 'readonlySegments',
type: 'Segment[]',
default: '[]',
description: 'Segmentos en solo lectura'
},
{ name: 'hourCycle', type: 'HourCycle', default: '24', description: 'Ciclo de hora (12h/24h)' }
];
const breadcrumbs = [{ label: 'Test', href: '/test' }, { label: 'TimeField' }];
</script>
<TestLayout
title="TimeField"
description="Campo de entrada de hora con segmentos editables. Soporta locale, formato 12/24h y diferentes niveles de precisión."
badges={['Terra', 'v1.0']}
{breadcrumbs}
>
{#snippet demos()}
<!-- Controls -->
<div class="locale-bar">
<span class="locale-label">Locale:</span>
{#each LOCALES as option}
<button
class="locale-btn {locale === option.id ? 'locale-btn--active' : ''}"
onclick={() => (locale = option.id)}
>
{option.label}
</button>
{/each}
</div>
<div class="locale-bar">
<span class="locale-label">Time format:</span>
<button
class="locale-btn {timeFormat === 24 ? 'locale-btn--active' : ''}"
onclick={() => (timeFormat = 24)}>24h</button
>
<button
class="locale-btn {timeFormat === 12 ? 'locale-btn--active' : ''}"
onclick={() => (timeFormat = 12)}>12h</button
>
</div>
<!-- Demo 1: Básico -->
<DemoSection title="Básico" description="Campo simple con segmentos autoformateados.">
<TimeField.Provider bind:value={value1} {locale} {timeFormat}>
<TimeField.Label class="tf-label">Hora de inicio</TimeField.Label>
<TimeField.Input class="tf-input" name="start-time">
{#snippet children({ segments })}
{#each segments as { part, value }}
<TimeField.Segment {part} class="tf-seg tf-seg--{part}">{value}</TimeField.Segment>
{/each}
{/snippet}
</TimeField.Input>
</TimeField.Provider>
<div class="val-display">{fmt(value1)}</div>
</DemoSection>
<!-- Demo 2: Granularity second -->
<DemoSection
title="Granularity second"
description="Incluye segundos. Ideal para validar cómo cambia AM/PM."
variant="bordered"
>
<TimeField.Provider bind:value={value2} {locale} {timeFormat} granularity="second">
<TimeField.Label class="tf-label">Hora exacta</TimeField.Label>
<TimeField.Input class="tf-input">
{#snippet children({ segments })}
{#each segments as { part, value }}
<TimeField.Segment {part} class="tf-seg tf-seg--{part}">{value}</TimeField.Segment>
{/each}
{/snippet}
</TimeField.Input>
</TimeField.Provider>
<div class="val-display">{fmt(value2)}</div>
</DemoSection>
<!-- Demo 3: Readonly parcial -->
<DemoSection
title="Readonly parcial"
description="Deja minutos editables mientras bloquea segundos."
>
<TimeField.Provider
bind:value={value3}
{locale}
{timeFormat}
granularity="second"
readonlySegments={['second']}
>
<TimeField.Label class="tf-label">Hora parcial</TimeField.Label>
<TimeField.Input class="tf-input">
{#snippet children({ segments })}
{#each segments as { part, value }}
<TimeField.Segment {part} class="tf-seg tf-seg--{part}">{value}</TimeField.Segment>
{/each}
{/snippet}
</TimeField.Input>
</TimeField.Provider>
<div class="val-display">{fmt(value3)}</div>
</DemoSection>
<!-- Demo 4: Terra -->
<DemoSection
title="Vía TerraConfig"
description="Este ejemplo hereda locale y timeFormat desde TerraConfig."
variant="bordered"
>
<Terra {translator} presentation={{ dateTimeFormatter }}>
<TimeField.Provider bind:value={value4} granularity="minute">
<TimeField.Label class="tf-label">Hora heredada</TimeField.Label>
<TimeField.Input class="tf-input">
{#snippet children({ segments })}
{#each segments as { part, value }}
<TimeField.Segment {part} class="tf-seg tf-seg--{part}">{value}</TimeField.Segment>
{/each}
{/snippet}
</TimeField.Input>
</TimeField.Provider>
</Terra>
<div class="val-display">{fmt(value4)}</div>
</DemoSection>
{/snippet}
{#snippet state()}
<StateItem label="Locale" value={locale} type="string" icon="🌐" />
<StateItem label="Time format" value={timeFormat + 'h'} type="string" icon="🕐" />
<StateItem label="Hora 1" value={fmt(value1)} type="string" icon="⏰" />
<StateItem label="Hora 2" value={fmt(value2)} type="string" icon="⏱️" />
{/snippet}
{#snippet props()}
<PropsTable title="TimeField.Provider" props={timeFieldProps} />
{/snippet}
</TestLayout>
<style>
/* Locale bar */
.locale-bar {
display: flex;
align-items: center;
gap: var(--spacing-3);
padding: var(--spacing-3) var(--spacing-4);
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
margin-bottom: var(--spacing-4);
flex-wrap: wrap;
}
.locale-label {
font-size: var(--text-xs);
color: var(--color-text-muted);
font-weight: 600;
}
.locale-btn {
font-family: inherit;
font-size: var(--text-xs);
padding: var(--spacing-1) var(--spacing-3);
border-radius: var(--radius-sm);
cursor: pointer;
background: var(--color-background);
border: 1px solid var(--color-border);
color: var(--color-text-muted);
transition: all var(--transition-fast);
}
.locale-btn:hover {
border-color: var(--color-primary);
color: var(--color-text);
}
.locale-btn--active {
border-color: var(--color-primary);
background: var(--color-primary-bg);
color: var(--color-primary);
}
/* TimeField */
:global(.tf-label) {
display: block;
font-size: var(--text-sm);
color: var(--color-text-muted);
margin-bottom: var(--spacing-2);
font-weight: 500;
}
:global(.tf-input) {
display: inline-flex;
align-items: center;
gap: var(--spacing-1);
padding: var(--spacing-3) var(--spacing-4);
border-radius: var(--radius-md);
border: 1px solid var(--color-border);
background: var(--color-surface-elevated);
min-height: 44px;
width: 100%;
max-width: 280px;
}
:global(.tf-seg) {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 1.7em;
padding: var(--spacing-1) var(--spacing-2);
border-radius: var(--radius-sm);
outline: none;
cursor: pointer;
}
:global(.tf-seg[data-type='literal']) {
min-width: auto;
padding: 0;
color: var(--color-text-dim);
}
:global(.tf-seg:focus),
:global(.tf-seg[data-focused]) {
background: var(--color-primary);
color: var(--color-background);
}
:global(.tf-seg[data-readonly]) {
opacity: 0.55;
}
/* Value display */
.val-display {
font-family: var(--font-mono);
font-size: var(--text-sm);
color: var(--color-primary);
margin-top: var(--spacing-3);
}
</style>

Powered by TurnKey Linux.