Refresh UI components and showcase; release updated framework packages
This commit is contained in:
1 parent
d800228585
commit
c77f5e1cec
293 files changed
+22544
-35778
No files matched your search
@@ -1,3 +1,5 @@
|
||||
import TimePicker from "./TimePicker.wrn"
|
||||
|
||||
component DatePicker {
|
||||
outputs {
|
||||
input(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])
|
||||
@@ -20,9 +22,9 @@ size: string = "default"
|
||||
type: string = "date"
|
||||
locale: string = "en-US"
|
||||
firstDayOfWeek: number = 0
|
||||
months = [{ label: "Jan", value: "01" }, { label: "Feb", value: "02" }, { label: "Mar", value: "03" }, { label: "Apr", value: "04" }, { label: "May", value: "05" }, { label: "Jun", value: "06" }, { label: "Jul", value: "07" }, { label: "Aug", value: "08" }, { label: "Sep", value: "09" }, { label: "Oct", value: "10" }, { label: "Nov", value: "11" }, { label: "Dec", value: "12" }]
|
||||
days = [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]
|
||||
years = [2024, 2025, 2026, 2027, 2028, 2029, 2030]
|
||||
months: { label: string; value: string }[] = [{ label: "Jan", value: "01" }, { label: "Feb", value: "02" }, { label: "Mar", value: "03" }, { label: "Apr", value: "04" }, { label: "May", value: "05" }, { label: "Jun", value: "06" }, { label: "Jul", value: "07" }, { label: "Aug", value: "08" }, { label: "Sep", value: "09" }, { label: "Oct", value: "10" }, { label: "Nov", value: "11" }, { label: "Dec", value: "12" }]
|
||||
days: number[] = [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]
|
||||
years: number[] = [2024, 2025, 2026, 2027, 2028, 2029, 2030]
|
||||
min: string = ""
|
||||
max: string = ""
|
||||
step: string = ""
|
||||
@@ -37,30 +39,117 @@ size: string = "default"
|
||||
class: string = ""
|
||||
}
|
||||
state currentValue = value
|
||||
state selectedYear = value ? value.split("-")[0] : ""
|
||||
state selectedMonth = value ? value.split("-")[1] : ""
|
||||
state selectedDay = value ? value.split("-")[2] : ""
|
||||
state selectedYear = value ? value.split("-")[0] : String(new Date().getFullYear())
|
||||
state selectedMonth = value ? value.split("-")[1] : String(new Date().getMonth() + 1).padStart(2, "0")
|
||||
state selectedDay = value ? value.slice(8, 10) : ""
|
||||
state selectedTime = value.includes("T") ? value.split("T")[1].slice(0, 5) : "09:00"
|
||||
state expanded: boolean = false
|
||||
functions {
|
||||
client function openCalendar(sourceEvent) { expanded = true; output.open({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
|
||||
client function updateDate(part, nextValue, sourceEvent, nextDate, detail) { if (part === "year") { selectedYear = String(nextValue) } if (part === "month") { selectedMonth = String(nextValue).padStart(2, "0") } if (part === "day") { selectedDay = String(nextValue).padStart(2, "0") } if (!selectedYear || !selectedMonth || !selectedDay) { return } nextDate = selectedYear + "-" + selectedMonth + "-" + selectedDay; if ((min && nextDate < min) || (max && nextDate > max)) { return } currentValue = nextDate; detail = { value: currentValue, year: selectedYear, month: selectedMonth, day: selectedDay, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) }
|
||||
client function finishDate(sourceEvent) { if (!currentValue) { return } expanded = false; output.close({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
|
||||
client function selectDay(sourceEvent, root, input, nextDate, trigger, detail) { root = sourceEvent.currentTarget.closest(".wrn-next--date-picker"); input = root.querySelector(".wrn-next__picker-value"); nextDate = input.getAttribute("value").slice(0, 5) + root.querySelector("select").getAttribute("value") + "-" + sourceEvent.currentTarget.dataset.value; currentValue = nextDate; input.setAttribute("value", nextDate); trigger = root.querySelector(".wrn-next__date-trigger span"); if (trigger) { trigger.replaceChildren(nextDate) } sourceEvent.currentTarget.setAttribute("aria-pressed", "true"); detail = { value: nextDate, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) }
|
||||
client function clearDate(sourceEvent, detail) { if (disabled || readonly) { return } currentValue = ""; detail = { value: currentValue, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) }
|
||||
client function updateTime(sourceEvent) {
|
||||
selectedTime = sourceEvent.currentTarget.value
|
||||
currentValue = (currentValue ? currentValue.slice(0, 10) : selectedYear + "-" + selectedMonth + "-01") + "T" + selectedTime
|
||||
output.input({ value: currentValue, name: name, sourceEvent: sourceEvent })
|
||||
output.change({ value: currentValue, name: name, sourceEvent: sourceEvent })
|
||||
}
|
||||
|
||||
shared function calendarYears() {
|
||||
if (years.includes(Number(selectedYear))) { return years }
|
||||
return years.concat(Number(selectedYear))
|
||||
}
|
||||
shared function weekdayName(index, first) {
|
||||
first = ((firstDayOfWeek % 7) + 7) % 7
|
||||
return Intl.DateTimeFormat(locale, { weekday: "short", timeZone: "UTC" }).format(Date.UTC(2024, 0, 7 + first + index))
|
||||
}
|
||||
shared function weekdays() { return [0, 1, 2, 3, 4, 5, 6].map(weekdayName) }
|
||||
shared function validDay(day, year, leap, count) {
|
||||
year = Number(selectedYear)
|
||||
leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
|
||||
count = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][Number(selectedMonth) - 1]
|
||||
return Number(day) >= 1 && Number(day) <= count
|
||||
}
|
||||
shared function dayView(day, year, month, first, offset, date, today) {
|
||||
year = Number(selectedYear)
|
||||
month = Number(selectedMonth)
|
||||
first = ((firstDayOfWeek % 7) + 7) % 7
|
||||
offset = ((Math.floor(Date.UTC(year, month - 1, 1) / 86400000) + 4) % 7 - first + 7) % 7
|
||||
date = selectedYear + "-" + selectedMonth + "-" + String(day).padStart(2, "0")
|
||||
today = Intl.DateTimeFormat("en-CA", { year: "numeric", month: "2-digit", day: "2-digit" }).format(Date.now())
|
||||
return { day: day, value: date, column: ((offset + Number(day) - 1) % 7) + 1, disabled: disabled || readonly || !!(min && date < min.slice(0, 10)) || !!(max && date > max.slice(0, 10)), today: date === today, label: Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone: "UTC" }).format(Date.UTC(year, month - 1, Number(day))) }
|
||||
}
|
||||
shared function calendarDays() { return days.filter(validDay).map(dayView) }
|
||||
client function openCalendar(sourceEvent) {
|
||||
if (disabled || readonly) { return }
|
||||
expanded = !expanded
|
||||
syncCalendarSelectors(sourceEvent)
|
||||
if (expanded) { output.open({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
|
||||
else { output.close({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
|
||||
}
|
||||
client function updateDate(part, nextValue, sourceEvent) {
|
||||
if (disabled || readonly) { return }
|
||||
if (part === "year") { selectedYear = String(nextValue) }
|
||||
if (part === "month") { selectedMonth = String(nextValue).padStart(2, "0") }
|
||||
syncCalendarSelectors(sourceEvent)
|
||||
}
|
||||
client function syncCalendarSelectors(sourceEvent) {
|
||||
var root = sourceEvent.currentTarget.closest(".wrn-next--date-picker")
|
||||
setTimeout(applyCalendarSelectors, 0, root)
|
||||
}
|
||||
client function applyCalendarSelectors(root) {
|
||||
var selectors = root.querySelectorAll(".wrn-next__date-selectors select")
|
||||
selectors[0].value = selectedMonth
|
||||
selectors[1].value = selectedYear
|
||||
}
|
||||
client function finishDate(sourceEvent) {
|
||||
var root = sourceEvent.currentTarget.closest(".wrn-next--date-picker")
|
||||
root.querySelector(".wrn-next__date-trigger").focus()
|
||||
expanded = false
|
||||
output.close({ value: currentValue, name: name, sourceEvent: sourceEvent })
|
||||
}
|
||||
client function selectDay(sourceEvent) {
|
||||
if (disabled || readonly || sourceEvent.currentTarget.disabled) { return }
|
||||
var nextDate = sourceEvent.currentTarget.dataset.value
|
||||
if ((min && nextDate < min.slice(0, 10)) || (max && nextDate > max.slice(0, 10))) { return }
|
||||
currentValue = nextDate + (type === "datetime-local" ? "T" + selectedTime : "")
|
||||
selectedDay = nextDate.slice(8)
|
||||
syncCalendarSelectors(sourceEvent)
|
||||
var detail = { value: currentValue, year: selectedYear, month: selectedMonth, day: selectedDay, name: name, sourceEvent: sourceEvent }
|
||||
output.input(detail)
|
||||
output.change(detail)
|
||||
}
|
||||
client function calendarKeydown(sourceEvent) {
|
||||
if (sourceEvent.key === "Escape" && expanded) { sourceEvent.preventDefault(); finishDate(sourceEvent); return }
|
||||
var keys = { ArrowRight: 1, ArrowLeft: -1, ArrowDown: 7, ArrowUp: -7 }
|
||||
if (!keys[sourceEvent.key] || !sourceEvent.target.closest(".wrn-next__calendar-grid")) { return }
|
||||
sourceEvent.preventDefault()
|
||||
var root = sourceEvent.currentTarget
|
||||
var buttons = Array.from(root.querySelectorAll(".wrn-next__calendar-grid button"))
|
||||
var index = buttons.indexOf(sourceEvent.target) + keys[sourceEvent.key]
|
||||
while (index >= 0 && index < buttons.length && buttons[index].disabled) { index += keys[sourceEvent.key] > 0 ? 1 : -1 }
|
||||
if (buttons[index]) { buttons[index].focus() }
|
||||
}
|
||||
client function clearDate(sourceEvent) {
|
||||
if (disabled || readonly) { return }
|
||||
currentValue = ""
|
||||
selectedDay = ""
|
||||
var detail = { value: currentValue, name: name, sourceEvent: sourceEvent }
|
||||
output.input(detail)
|
||||
output.change(detail)
|
||||
}
|
||||
client function handleFocus(sourceEvent) { output.focus({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
|
||||
client function handleBlur(sourceEvent) { output.blur({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
|
||||
client function handleInvalid(sourceEvent) { output.invalid({ name: name, message: sourceEvent.currentTarget.validationMessage, sourceEvent: sourceEvent }) }
|
||||
}
|
||||
view {
|
||||
<div {...attrs} class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--field wrn-next--date-picker {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}" data-expanded="{expanded}">
|
||||
<div {...attrs} class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--field wrn-next--date-picker {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}" data-expanded="{expanded}" @keydown="calendarKeydown(event)">
|
||||
<div class="wrn-next__field-heading"><label for="{id || name}">{label}</label>{#if cornerHint}<span class="wrn-next__field-hint">{cornerHint}</span>{/if}</div>
|
||||
<div class="wrn-next__picker-control"><input id="{id || name}" class="wrn-next__sr-only wrn-next__picker-value" type="text" name="{name}" value="{currentValue}" required="{required}" readonly aria-hidden="true" tabindex="-1" @invalid="handleInvalid(event)" /><button type="button" class="wrn-next__date-trigger" disabled="{disabled || readonly}" aria-haspopup="dialog" aria-expanded="{expanded}" @click="openCalendar(event)" @focus="handleFocus(event)" @blur="handleBlur(event)"><span>{currentValue || placeholder || "Select date"}</span><i class="icon-[lucide--calendar-days]" aria-hidden="true"></i></button>{#if currentValue && !readonly && !disabled}<button type="button" class="wrn-next__picker-clear" aria-label="Clear date" @click="clearDate(event)">×</button>{/if}</div>
|
||||
<div class="wrn-next__calendar" role="dialog" aria-label="{label}"><div class="wrn-next__date-selectors"><label>Month<select value="{selectedMonth}" @change="updateDate('month', event.currentTarget.value, event)">{#each months as month}<option value="{month.value}" selected="{month.value === selectedMonth}">{month.label}</option>{/each}</select></label><label>Year<select value="{selectedYear}" @change="updateDate('year', event.currentTarget.value, event)">{#each years as year}<option value="{year}" selected="{String(year) === selectedYear}">{year}</option>{/each}</select></label></div><div class="wrn-next__calendar-grid">{#each days as day}<button type="button" data-value="{String(day).padStart(2, '0')}" aria-pressed="{String(day).padStart(2, '0') === selectedDay}" @click="selectDay(event)">{day}</button>{/each}</div><button type="button" class="wrn-next__date-done" disabled="{!currentValue}" @click="finishDate(event)">Done</button></div>
|
||||
<div class="wrn-next__picker-control"><input id="{id || name}" class="wrn-next__sr-only wrn-next__picker-value" type="text" name="{name}" value="{currentValue}" required="{required}" readonly aria-hidden="true" tabindex="-1" @invalid="handleInvalid(event)" /><button type="button" class="wrn-next__date-trigger" aria-label="{label}" disabled="{disabled || readonly}" aria-haspopup="dialog" aria-expanded="{expanded}" @click="openCalendar(event)" @focus="handleFocus(event)" @blur="handleBlur(event)"><span>{currentValue || placeholder || "Select date"}</span><i class="icon-[lucide--calendar-days]" aria-hidden="true"></i></button>{#if currentValue && !readonly && !disabled}<button type="button" class="wrn-next__picker-clear" aria-label="Clear date" @click="clearDate(event)">×</button>{/if}</div>
|
||||
<button type="button" class="wrn-picker-dismiss" aria-label="Close calendar" data-show="expanded" @click="finishDate(event)"></button><div data-show="expanded" class="wrn-next__calendar" role="dialog" aria-label="{label}"><div class="wrn-next__date-selectors"><label>Month<select value="{selectedMonth}" @change="updateDate('month', event.currentTarget.value, event)">{#each months as month}<option value="{month.value}" selected="{month.value === selectedMonth}">{month.label}</option>{/each}</select></label><label>Year<select value="{selectedYear}" @change="updateDate('year', event.currentTarget.value, event)">{#each calendarYears() as calendarYear}<option value="{calendarYear}" selected="{String(calendarYear) === selectedYear}" data-text="calendarYear"></option>{/each}</select></label></div><div class="wrn-next__calendar-weekdays" aria-hidden="true">{#each weekdays() as weekday}<span>{weekday}</span>{/each}</div><div class="wrn-next__calendar-grid">{#each calendarDays() as day}<button type="button" data-value="{day.value}" data-today="{day.today}" style="grid-column: {day.column}" disabled="{day.disabled}" aria-label="{day.label}" aria-pressed="{day.value === currentValue.slice(0, 10)}" @click="selectDay(event)">{day.day}</button>{/each}</div>{#if type === "datetime-local"}<TimePicker label="Time" value="{selectedTime}" @change="selectedTime = payload.value; currentValue = (currentValue ? currentValue.slice(0, 10) : selectedYear + '-' + selectedMonth + '-01') + 'T' + selectedTime; output.change({ value: currentValue, name: name })" />{/if}<button type="button" class="wrn-next__date-done" disabled="{!currentValue}" @click="finishDate(event)">Done</button></div>
|
||||
{#if helperText}<small class="wrn-next__field-help">{helperText}</small>{/if}<small class="wrn-next__field-error" data-error="{name}">{error}</small>
|
||||
</div>
|
||||
}
|
||||
|
||||
style {
|
||||
.wrn-picker-dismiss { position:fixed; inset:0; z-index:39; opacity:0; cursor:default; border:0; }
|
||||
.wrn-next__picker-clear {
|
||||
position: absolute;
|
||||
right: 2.5rem;
|
||||
@@ -116,6 +205,10 @@ size: string = "default"
|
||||
box-shadow: inset 0 0 0 1px var(--wrn-field-color);
|
||||
}
|
||||
|
||||
.wrn-next__calendar-grid button:disabled { opacity: 0.35; cursor: not-allowed; }
|
||||
.wrn-next__calendar-grid button:not(:disabled):focus-visible { outline: 2px solid var(--wrn-field-color); outline-offset: 1px; }
|
||||
.wrn-next__calendar-weekdays { padding-block: 0.375rem; }
|
||||
|
||||
/* Shared component foundation. */
|
||||
|
||||
.wrn-component {
|
||||
@@ -222,10 +315,10 @@ size: string = "default"
|
||||
}
|
||||
.wrn-next--field :where(input, textarea, select) {
|
||||
width: 100%;
|
||||
min-height: 2.65rem;
|
||||
padding: 0.6rem 0.7rem;
|
||||
min-height: var(--wrn-control-height, 2.5rem);
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid var(--wrn-color-border);
|
||||
border-radius: 0.6rem;
|
||||
border-radius: var(--wrn-radius-sm, 6px);
|
||||
outline: 0;
|
||||
color: var(--wrn-color-text);
|
||||
background: var(--wrn-color-bg);
|
||||
@@ -239,9 +332,9 @@ size: string = "default"
|
||||
}
|
||||
.wrn-auth-form__submit > .wrn-btn {
|
||||
width: 100%;
|
||||
min-height: 3rem;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 0.8rem 1.8rem color-mix(in srgb, var(--wrn-btn-color) 20%, transparent);
|
||||
min-height: var(--wrn-control-height, 2.5rem);
|
||||
border-radius: var(--wrn-radius-sm, 6px);
|
||||
box-shadow: none;
|
||||
}
|
||||
.wrn-next--field :where(input, textarea, select):focus-visible {
|
||||
border-color: var(--wrn-color-primary);
|
||||
@@ -283,7 +376,7 @@ size: string = "default"
|
||||
.wrn-next__field-hint,
|
||||
.wrn-next__field-help {
|
||||
color: var(--wrn-color-muted);
|
||||
font-size: 0.82em;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.wrn-next__field-control {
|
||||
@@ -297,10 +390,10 @@ size: string = "default"
|
||||
background: var(--wrn-color-surface);
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
.wrn-next__field-control[data-icon-position="start"] > :is(input, textarea, select) {
|
||||
.wrn-next__field-control[data-has-icon="true"][data-icon-position="start"] > :is(input, textarea, select) {
|
||||
padding-inline-start: 2.5rem;
|
||||
}
|
||||
.wrn-next__field-control[data-icon-position="end"] > :is(input, textarea, select) {
|
||||
.wrn-next__field-control[data-has-icon="true"][data-icon-position="end"] > :is(input, textarea, select) {
|
||||
padding-inline-end: 2.5rem;
|
||||
}
|
||||
.wrn-next__field-icon {
|
||||
@@ -408,7 +501,7 @@ size: string = "default"
|
||||
}
|
||||
.wrn-next--field[data-inline="true"] .wrn-next__field-heading,
|
||||
.wrn-next--choice-field[data-inline="true"] .wrn-next__field-heading {
|
||||
min-height: 2.65rem;
|
||||
min-height: var(--wrn-control-height, 2.5rem);
|
||||
align-items: center;
|
||||
}
|
||||
.wrn-next--field[data-inline="true"] .wrn-next__field-heading {
|
||||
@@ -417,7 +510,7 @@ size: string = "default"
|
||||
.wrn-next--field[data-inline="true"] .wrn-next__field-help,
|
||||
.wrn-next--choice-field[data-inline="true"] .wrn-next__field-help {
|
||||
display: flex;
|
||||
min-height: 2.65rem;
|
||||
min-height: var(--wrn-control-height, 2.5rem);
|
||||
align-items: center;
|
||||
}
|
||||
.wrn-next--choice-field[data-inline="true"] {
|
||||
@@ -566,7 +659,7 @@ size: string = "default"
|
||||
.wrn-next__time-trigger {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 2.65rem;
|
||||
min-height: var(--wrn-control-height, 2.5rem);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
@@ -578,9 +671,13 @@ size: string = "default"
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
.wrn-next--date-picker, .wrn-next--time-picker { position: relative; }
|
||||
.wrn-next__calendar,
|
||||
.wrn-next__time-panel {
|
||||
z-index: 20;
|
||||
position: absolute;
|
||||
inset-block-start: calc(100% + .4rem);
|
||||
inset-inline-start: 0;
|
||||
z-index: 40;
|
||||
display: grid;
|
||||
width: min(100%, 22rem);
|
||||
gap: 0.75rem;
|
||||
@@ -588,7 +685,7 @@ size: string = "default"
|
||||
border: 1px solid var(--wrn-color-border);
|
||||
border-radius: var(--wrn-radius-md);
|
||||
background: var(--wrn-color-surface);
|
||||
box-shadow: var(--wrn-shadow-3);
|
||||
box-shadow: var(--wrn-shadow-2);
|
||||
}
|
||||
.wrn-next--date-picker[data-expanded="false"] .wrn-next__calendar,
|
||||
.wrn-next--time-picker[data-expanded="false"] .wrn-next__time-panel {
|
||||
|
||||
Reference in new issue
Block a user