Refresh UI components and showcase; release updated framework packages

This commit is contained in:
Clintchiz committed 2026-09-20 18:13:24 +05:30
1 parent d800228585
commit c77f5e1cec
293 files changed
+22544 -35778

No files matched your search

+11 -4
View File
@@ -77,7 +77,7 @@ component Accordion {
wasOpen = isOpen(value)
if (wasOpen) {
openValues = openValues.filter((entry) => entry !== value)
openValues = openValues.slice(0, openValues.indexOf(value)).concat(openValues.slice(openValues.indexOf(value) + 1))
} else if (allowsMultiple()) {
openValues = openValues.concat([value])
} else {
@@ -102,6 +102,7 @@ component Accordion {
data-wrn-accordion
data-variant="{variant}"
data-indicator="{indicator}"
data-indicator-position="{showIndicator ? indicatorPosition : 'none'}"
data-multiple="{allowsMultiple() ? 'true' : 'false'}"
class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--accordion wrn-next--accordion-{variant} {bordered ? 'wrn-next--accordion-bordered' : ''} {separated ? 'wrn-next--accordion-separated' : ''} {flush ? 'wrn-next--accordion-flush' : ''} {disabled ? 'wrn-next--disabled' : ''} {class}"
>
@@ -208,7 +209,7 @@ component Accordion {
style {
.wrn-next--accordion {
--wrn-accordion-accent: var(--wrn-component-color);
--wrn-accordion-accent: var(--wrn-color-primary-text);
display: grid;
width: 100%;
color: var(--wrn-color-text);
@@ -234,7 +235,7 @@ component Accordion {
color: var(--wrn-color-text);
font: inherit;
font-size: 0.88rem;
font-weight: 700;
font-weight: 600;
text-align: left;
cursor: pointer;
}
@@ -319,12 +320,16 @@ component Accordion {
}
.wrn-next__accordion-content {
padding: 0 0 1rem 1.75rem;
padding: 0 0 1rem;
color: var(--wrn-color-muted);
font-size: 0.86rem;
line-height: 1.65;
}
.wrn-next--accordion[data-indicator-position="start"] .wrn-next__accordion-content {
padding-inline-start: 1.75rem;
}
.wrn-next__accordion-content > p {
color: inherit;
}
@@ -369,6 +374,8 @@ component Accordion {
.wrn-next--accordion-separated {
gap: 0.5rem;
border: 0;
background: transparent;
}
.wrn-next--accordion-separated > .wrn-next__accordion-item {
+7 -5
View File
@@ -73,8 +73,9 @@ component AdvancedSelect {
state selectedValues = values
functions {
shared function groupOptions(group) { return group.options || [] }
shared function allOptions() {
return [...groups.flatMap((group) => group.options || []), ...options]
return groups.flatMap(groupOptions).concat(options)
}
shared function searchableText(option) {
@@ -95,7 +96,7 @@ component AdvancedSelect {
}
shared function matchingOptions(list) {
return list.filter((option) => matches(option))
return list.filter(matches)
}
shared function visibleOptions(list) {
@@ -118,7 +119,7 @@ component AdvancedSelect {
}
shared function selectedOptions() {
return allOptions().filter((option) => isSelected(option))
return allOptions().filter(isSelected)
}
shared function selectedCount() {
@@ -475,13 +476,14 @@ component AdvancedSelect {
.wrn-next__select-search .wrn-next__search-icon {
inset-inline-start: 0.8em;
left: auto;
width: 1em;
height: 1em;
pointer-events: none;
}
.wrn-next__select-search input {
.wrn-next--advanced-select .wrn-next__select-search input {
min-height: var(--wrn-control-height, 2.5rem);
font-size: 0.875rem;
padding-inline-start: 2.4em;
padding-inline-end: 0.75em;
}
+2 -2
View File
@@ -37,7 +37,7 @@ badge: string = ""
state dismissed: boolean = false
view {
<aside
<div
{...attrs}
data-ui-component="AnnouncementBar"
data-size='{size}'
@@ -149,7 +149,7 @@ badge: string = ""
{/if}
</div>
</div>
</aside>
</div>
}
style {
+2 -2
View File
@@ -1,6 +1,6 @@
import Button from "./button.wrn"
import Checkbox from "./Checkbox.wrn"
import Input from "./Input.wrn"
import Checkbox from "./checkbox.wrn"
import Input from "./input.wrn"
import PinInput from "./PinInput.wrn"
component AuthForm {
+2 -2
View File
@@ -160,7 +160,7 @@ component AuthSplitLayout {
.wrn-auth-split__message h1 {
max-width: 14ch;
margin: 0.65rem 0 0;
font-size: clamp(2rem, 4vw, 3.75rem);
font-size: clamp(1.75rem, 3vw, 2.5rem);
font-weight: 650;
letter-spacing: -0.045em;
line-height: 1.08;
@@ -221,7 +221,7 @@ component AuthSplitLayout {
}
.wrn-auth-split__form-inner {
width: min(100%, 30rem);
width: min(100%, 24rem);
}
@media (max-width: 800px) {
+6 -1
View File
@@ -63,7 +63,7 @@ component AvatarGroup {
data-color="{item.color || color}"
data-variant="{item.variant || variant}"
tabindex="{showTooltips && (item.tooltip || item.name) ? '0' : '-1'}"
aria-label="{item.name || item.alt || item.initials || 'Group member'}"
aria-label="{item.tooltip || item.name || item.alt || item.initials || 'Group member'}"
>
<span
class="wrn-next__avatar-group-avatar"
@@ -241,6 +241,8 @@ component AvatarGroup {
.wrn-next__avatar-group-avatar,
.wrn-next__avatar-group-overflow-button {
white-space: nowrap;
padding: 0;
display: grid;
width: 100%;
height: 100%;
@@ -264,6 +266,9 @@ component AvatarGroup {
}
.wrn-next__avatar-group-overflow-button {
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--wrn-color-text);
background: var(--wrn-color-surface-2);
+1
View File
@@ -136,6 +136,7 @@ label: string = "Breadcrumb"
--breadcrumb-text: var(--wrn-color-primary-text);
min-width: 0;
max-width: 100%;
color: var(--wrn-color-text-muted);
}
+24 -4
View File
@@ -28,7 +28,7 @@ items: unknown[] = []
return item.value || item.label || String(index)
}
client function selectItem(item, index) {
client function selectItem(item, index, previousValue, nextValue) {
previousValue = selectedValue
nextValue = itemValue(item, index)
@@ -99,6 +99,26 @@ items: unknown[] = []
isolation: isolate;
}
.wrn-next--button-group > .wrn-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.45rem;
min-height: var(--wrn-control-height, 2.5rem);
padding: 0 0.85rem;
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-sm);
background: var(--wrn-color-surface);
color: var(--wrn-color-text);
font: inherit;
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
}
.wrn-next--button-group > .wrn-btn:hover { background: var(--wrn-color-surface-2); }
.wrn-next--button-group > .wrn-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.wrn-next--button-group > .wrn-btn .wrn-btn__icon { width: 1rem; height: 1rem; }
.wrn-next--button-group[data-size="sm"] > .wrn-btn { min-height: 2rem; font-size: 0.8rem; }
.wrn-next--button-group[data-orientation="vertical"] {
flex-direction: column;
}
@@ -182,9 +202,9 @@ items: unknown[] = []
}
.wrn-next--button-group[data-selectable="true"] > .wrn-btn[data-selected="true"] {
z-index: 1;
border-color: var(--wrn-component-color);
border-color: var(--wrn-color-primary-text);
background: color-mix(in srgb, var(--wrn-component-color) 16%, var(--wrn-color-surface));
color: var(--wrn-component-color);
color: var(--wrn-color-primary-text);
}
@media (max-width: 480px) {
.wrn-next--button-group[data-responsive="true"] > :is(.wrn-btn, .wrn-action) {
@@ -292,7 +312,7 @@ items: unknown[] = []
}
.wrn-component :is(a, button, input, select, textarea):focus-visible {
outline-color: var(--wrn-component-color);
outline-color: var(--wrn-color-primary-text);
}
.wrn-component p {
+24 -12
View File
@@ -16,6 +16,8 @@ component Card {
subtitleKey: string = ""
description: string = ""
descriptionKey: string = ""
showHeader: boolean = true
showFooter: boolean = true
header: string = ""
footer: string = ""
imageSrc: string = ""
@@ -170,8 +172,8 @@ component Card {
{/if}
<div class="wrn-next__card-content">
{#if header || title || subtitle || headerActions.length > 0 || dismissible}
<header class="wrn-next__card-header">
{#if showHeader}
<header class="wrn-next__card-header"><slot name="header" />
<div class="wrn-next__card-heading">
{#if header}
<p class="wrn-next__card-eyebrow">{header}</p>
@@ -284,8 +286,8 @@ component Card {
{/if}
</div>
{#if footer || actionLabel}
<footer class="wrn-next__card-footer">
{#if showFooter}
<footer class="wrn-next__card-footer"><div class="wrn-next__card-custom-footer"><slot name="footer" /></div>
{#if footer}
<span>{footer}</span>
{/if}
@@ -321,6 +323,8 @@ component Card {
}
style {
.wrn-next__card-custom-footer:empty { display:none; }
.wrn-next__card-header:not(:has(strong,h1,h2,h3,h4,p,button,[data-slot])), .wrn-next__card-footer:not(:has(> span, > a, > .wrn-next__card-custom-footer:not(:empty))) { display:none; }
.wrn-next__card-media--overlay {
aspect-ratio: 16 / 9;
min-height: 18rem;
@@ -340,7 +344,7 @@ component Card {
.wrn-next--card {
--wrn-card-color: var(--wrn-component-color, var(--wrn-color-primary));
--wrn-card-padding: 1.25rem;
--wrn-card-padding: var(--wrn-space-panel, 1.25rem);
display: block;
width: 100%;
color: var(--wrn-color-text);
@@ -354,10 +358,15 @@ component Card {
.wrn-next--card[data-shadow="none"] > .wrn-next__card-panel { box-shadow: none; }
.wrn-next--card[data-equal-height="true"], .wrn-next--card[data-equal-height="true"] > .wrn-next__card-panel { height: 100%; }
.wrn-next--card[data-layout="horizontal"] .wrn-next__card-body { display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 0.8rem; }
.wrn-next__card-icon { display: grid; width: 2.35rem; height: 2.35rem; place-items: center; border-radius: 0.7rem; color: var(--wrn-color-primary); background: color-mix(in srgb, var(--wrn-color-primary) 12%, transparent); font-size: 1.15rem; }
.wrn-next__card-icon { display: inline-block; width: 2rem; height: 2rem; color: var(--wrn-card-icon-color, var(--wrn-color-primary-text)); background-color: currentColor; }
.wrn-next--card.wrn-component--color-secondary { --wrn-card-icon-color: var(--wrn-color-secondary-text); }
.wrn-next--card.wrn-component--color-success { --wrn-card-icon-color: var(--wrn-color-success-text); }
.wrn-next--card.wrn-component--color-warning { --wrn-card-icon-color: var(--wrn-color-warning-text); }
.wrn-next--card.wrn-component--color-danger { --wrn-card-icon-color: var(--wrn-color-danger-text); }
.wrn-next--card.wrn-component--color-info { --wrn-card-icon-color: var(--wrn-color-info-text); }
.wrn-next--card[data-size="lg"] {
--wrn-card-padding: 1.75rem;
--wrn-card-padding: 1.5rem;
}
.wrn-next__card-panel {
@@ -370,7 +379,7 @@ component Card {
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-md);
background: var(--wrn-color-surface);
box-shadow: var(--wrn-shadow-1);
box-shadow: none;
transition:
transform var(--wrn-motion-base) var(--wrn-ease-emphasized),
border-color var(--wrn-motion-base) ease,
@@ -384,8 +393,8 @@ component Card {
.wrn-next--card[data-hover="raise"] > .wrn-next__card-panel:hover {
border-color: color-mix(in srgb, var(--wrn-card-color) 38%, var(--wrn-color-border));
box-shadow: var(--wrn-shadow-3);
transform: translateY(-4px);
box-shadow: var(--wrn-shadow-2);
transform: translateY(-2px);
}
.wrn-next__card-header,
@@ -531,7 +540,7 @@ component Card {
.wrn-next__card-subtitle {
color: var(--wrn-color-muted);
font-size: 0.72rem;
font-weight: 700;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
}
@@ -626,9 +635,12 @@ component Card {
min-height: 100%;
}
.wrn-next__card-group .wrn-next__card-content { padding: var(--wrn-card-padding, 1.25rem); display: grid; gap: 0.65rem; }
.wrn-next__card-content h3 { margin: 0; font-size: 1rem; font-weight: 600; }
.wrn-next__card-group {
display: grid;
grid-template-columns: repeat(var(--wrn-card-columns, 3), minmax(0, 1fr));
grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
width: 100%;
}
.wrn-next__card-group > .wrn-next__card-panel {
+24
View File
@@ -34,6 +34,7 @@ component Carousel {
showCounter: boolean = false
thumbnails: string = "none"
ariaLabel: string = "Content carousel"
framed: boolean = false
variant: string = "default"
class: string = ""
}
@@ -316,6 +317,7 @@ component Carousel {
view {
<section
{...attrs}
data-framed="{framed}"
class="wrn-component wrn-next--carousel wrn-component--color-{color} wrn-component--size-{size} wrn-next--variant-{variant} {class}"
data-rtl="{isRTL}"
data-centered="{isCentered}"
@@ -477,6 +479,22 @@ component Carousel {
box-shadow: var(--wrn-shadow-1);
}
.wrn-next--carousel[data-framed="false"] {
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.wrn-next__carousel-slide:has(> img)::after {
content: "";
position: absolute;
inset: 0;
background: rgb(0 0 0 / 0.4);
pointer-events: none;
}
.wrn-next__carousel-slide:has(> img) .wrn-next__carousel-slide-content,
.wrn-next__carousel-slide:has(> img) .wrn-next__carousel-slide-content p { color: white; }
.wrn-next__carousel-header h3,
.wrn-next__carousel-header p {
margin: 0;
@@ -515,6 +533,12 @@ component Carousel {
outline: none;
}
.wrn-next--carousel[data-framed="false"] .wrn-next__carousel-viewport {
border: 0;
border-radius: 0;
background: transparent;
}
.wrn-next__carousel-viewport:focus-visible {
box-shadow: 0 0 0 3px color-mix(in srgb, var(--wrn-carousel-color) 30%, transparent);
}
+32 -23
View File
@@ -9,6 +9,8 @@ component Chart {
title: string = "Chart"
description: string = ""
items: unknown[] = []
data: unknown[] = []
type: string = ""
valueKey: string = "value"
labelKey: string = "label"
height: number = 240
@@ -23,15 +25,17 @@ component Chart {
state hiddenIndexes = []
functions {
shared function chartData() { return data.length ? data : items }
shared function linePoints() { return chartData().map(function (item, index) { return (barX(index) + barWidth() / 2) + "," + barY(item) }).join(" ") }
shared function numericValue(item) { return Math.max(0, Number(item[valueKey]) || 0) }
shared function maximumValue() {
return Math.max(1, ...items.map(function (item) { return numericValue(item) }))
return Math.max(1, ...chartData().map(function (item) { return numericValue(item) }))
}
shared function isHidden(index) { return hiddenIndexes.includes(index) }
shared function barWidth() { return items.length ? Math.max(4, 80 / items.length) : 80 }
shared function barX(index) { return 10 + index * (80 / Math.max(1, items.length)) }
shared function barHeight(item) { return numericValue(item) / maximumValue() * 72 }
shared function barY(item) { return 84 - barHeight(item) }
shared function barWidth() { return chartData().length ? Math.max(4, 400 / chartData().length) : 400 }
shared function barX(index) { return 50 + index * (500 / Math.max(1, chartData().length)) }
shared function barHeight(item) { return numericValue(item) / maximumValue() * 160 }
shared function barY(item) { return 200 - barHeight(item) }
client function selectPoint(item, index, sourceEvent) {
output.dataPointClick({ item: item, index: index, sourceEvent: sourceEvent })
@@ -58,25 +62,21 @@ component Chart {
{#if description}<span>{description}</span>{/if}
</figcaption>
{/if}
<svg class="wrn-chart__plot" viewBox="0 0 100 100" role="img" aria-label='{title}' style='height:{height}px'>
<line x1="8" y1="84" x2="96" y2="84" class="wrn-chart__axis" />
{#each items as item, index}
<g data-hidden='{isHidden(index) ? "true" : "false"}'>
<rect
x='{barX(index)}' y='{barY(item)}' width='{barWidth()}' height='{barHeight(item)}'
rx="1.5" class="wrn-chart__bar" role="button" tabindex="0"
aria-label='{item[labelKey] + ": " + numericValue(item)}'
@click='selectPoint(item, index, event)'
@keydown='if (event.key === "Enter" || event.key === " ") { event.preventDefault(); selectPoint(item, index, event) }'
/>
{#if showValues}<text x='{barX(index) + barWidth() / 2}' y='{barY(item) - 3}' class="wrn-chart__value">{numericValue(item)}</text>{/if}
<text x='{barX(index) + barWidth() / 2}' y="94" class="wrn-chart__label">{item[labelKey]}</text>
</g>
{/each}
</svg>
{#if (type || variant) === "line"}
<svg class="wrn-chart__line" viewBox="0 0 600 240" role="img" aria-label="{title}" style="height:{height}px"><polyline points="{linePoints()}" fill="none" stroke="var(--wrn-color-primary)" stroke-width="3" /></svg>
<div class="wrn-chart__point-labels">{#each chartData() as item, index}<button type="button" @click="selectPoint(item, index, event)">{item[labelKey]} · {numericValue(item)}</button>{/each}</div>
{:else}
<div class="wrn-chart__columns" style="height:{height}px" role="group" aria-label="{title}">
{#each chartData() as item, index}<button type="button" class="wrn-chart__column" data-hidden="{hiddenIndexes.includes(index)}" aria-label="{item[labelKey] + ': ' + numericValue(item)}" @click="selectPoint(item, index, event)">
{#if showValues}<span>{numericValue(item)}</span>{/if}
<svg viewBox="0 0 60 160" preserveAspectRatio="none" aria-hidden="true" style="height:{numericValue(item) / maximumValue() * Math.max(60, height - 60)}px"><rect x="0" y="0" width="60" height="160" rx="3" fill="var(--wrn-color-primary)" /></svg>
<small>{item[labelKey]}</small>
</button>{/each}
</div>
{/if}
{#if showLegend}
<div class="wrn-chart__legend" aria-label="Chart legend">
{#each items as item, index}
{#each chartData() as item, index}
<button type="button" aria-pressed='{isHidden(index) ? "false" : "true"}' @click='toggleLegend(item, index, event)'>
<span aria-hidden="true"></span>{item[labelKey]}
</button>
@@ -88,6 +88,15 @@ component Chart {
}
style {
.wrn-chart__columns { display:flex; align-items:flex-end; gap:clamp(.5rem,2vw,1.5rem); padding:.5rem; border-bottom:1px solid var(--wrn-color-border); }
.wrn-chart__column { display:flex; flex:1; min-width:0; flex-direction:column; align-items:center; gap:.6rem; padding:0; border:0; background:transparent; color:var(--wrn-color-text); font:inherit; font-size:.8rem; cursor:pointer; }
.wrn-chart__column svg { width:100%; max-width:5rem; min-height:1px; }
.wrn-chart__column small { max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--wrn-color-muted); }
.wrn-chart__column[data-hidden="true"] { opacity:.2; }
.wrn-chart__line { width:100%; display:block; }
.wrn-chart__point-labels { display:flex; justify-content:space-around; gap:.5rem; flex-wrap:wrap; }
.wrn-chart__point-labels button { border:0; background:transparent; color:var(--wrn-color-muted); font:inherit; font-size:.8rem; cursor:pointer; }
.wrn-chart { display: grid; gap: 1rem; min-width: 0; margin: 0; padding: 1rem; border: 1px solid var(--wrn-color-border); border-radius: var(--wrn-radius); color: var(--wrn-color-text); background: var(--wrn-color-surface); box-shadow: var(--wrn-shadow-1); }
.wrn-chart__header { display: grid; gap: 0.2rem; }
.wrn-chart__header > span { color: var(--wrn-color-muted); font-size: 0.8rem; }
@@ -97,7 +106,7 @@ component Chart {
.wrn-chart__bar:hover { opacity: 0.82; transform: scaleY(1.02); }
.wrn-chart__bar:focus-visible { outline: 1.5px solid var(--wrn-color-focus); outline-offset: 1px; }
.wrn-chart__plot g[data-hidden="true"] { opacity: 0.18; pointer-events: none; }
.wrn-chart__value, .wrn-chart__label { fill: var(--wrn-color-text); font-size: 3.5px; text-anchor: middle; }
.wrn-chart__value, .wrn-chart__label { fill: var(--wrn-color-text); font-size: 11px; text-anchor: middle; }
.wrn-chart__label { fill: var(--wrn-color-muted); }
.wrn-chart__legend { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.wrn-chart__legend button { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.3rem 0.5rem; border: 1px solid var(--wrn-color-border); border-radius: 999px; color: var(--wrn-color-text); background: transparent; font: inherit; font-size: 0.75rem; cursor: pointer; }
+2 -1
View File
@@ -12,6 +12,7 @@ component Clipboard {
errorLabel: string = "Copy failed"
title: string = "Clipboard"
description: string = ""
iconOnly: boolean = false
code: boolean = true
size: string = "default"
color: string = "primary"
@@ -73,7 +74,7 @@ component Clipboard {
>
<span class="icon-[lucide--copy] wrn-clipboard__icon" data-show='status !== "success"' aria-hidden="true"></span>
<span class="icon-[lucide--check] wrn-clipboard__icon" data-show='status === "success"' aria-hidden="true"></span>
<span>{status === "success" ? copiedLabel : status === "error" ? errorLabel : label}</span>
{#if !iconOnly}<span>{status === "success" ? copiedLabel : status === "error" ? errorLabel : label}</span>{/if}
</button>
</div>
<slot />
+12 -12
View File
@@ -151,10 +151,10 @@ component ColorPicker {
}
.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);
@@ -168,9 +168,9 @@ component ColorPicker {
}
.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);
@@ -212,7 +212,7 @@ component ColorPicker {
.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 {
@@ -226,10 +226,10 @@ component ColorPicker {
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 {
@@ -337,7 +337,7 @@ component ColorPicker {
}
.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 {
@@ -346,7 +346,7 @@ component ColorPicker {
.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"] {
@@ -495,7 +495,7 @@ component ColorPicker {
.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;
+5 -6
View File
@@ -62,8 +62,9 @@ component ComboBox {
state activeIndex: number = -1
functions {
shared function groupOptions(group) { return group.options || [] }
shared function allOptions() {
return [...groups.flatMap((group) => group.options || []), ...options]
return groups.flatMap(groupOptions).concat(options)
}
shared function searchableText(option) {
@@ -84,7 +85,7 @@ component ComboBox {
}
shared function matchingOptions(list) {
return list.filter((option) => matches(option))
return list.filter(matches)
}
shared function visibleOptions(list) {
@@ -103,7 +104,7 @@ component ComboBox {
}
shared function selectedOptions() {
return allOptions().filter((option) => isSelected(option))
return allOptions().filter(isSelected)
}
shared function selectedText() {
@@ -312,8 +313,7 @@ component ComboBox {
role="listbox"
>
{#each groups as group}
{#if visibleOptions(group.options || []).length}
<div class="wrn-next__option-group" role="group" aria-label="{group.label}">
<div class="wrn-next__option-group" role="group" aria-label="{group.label}" data-show="visibleOptions(group.options || []).length > 0">
<div class="wrn-next__group-label">{group.label}</div>
{#each group.options || [] as option}
<button
@@ -336,7 +336,6 @@ component ComboBox {
</button>
{/each}
</div>
{/if}
{/each}
{#each options as option}
+50 -9
View File
@@ -7,7 +7,7 @@ component Confetti {
props {
label: string = "Celebrate"
duration: number = 1200
count: number = 24
count: number = 80
autoStart: boolean = false
disabled: boolean = false
size: string = "default"
@@ -18,9 +18,45 @@ component Confetti {
state running = false
functions {
shared function pieces() { return Array.from({ length: Math.max(1, Math.min(60, count)) }) }
shared function pieces() { return Array.from({ length: Math.max(1, Math.min(200, count)) }) }
shared function pieceStyle(index) {
return "--wrn-confetti-index:" + index + ";--wrn-confetti-x:" + ((index * 37) % 100) + ";--wrn-confetti-delay:" + ((index * 29) % 240) + "ms;"
return "--wrn-confetti-index:" + index + ";--wrn-confetti-x:" + ((index * 37) % 100) + ";--wrn-confetti-delay:" + 0 + "ms;"
}
client function animateFrame(canvas, particles, started) {
var context = canvas.getContext("2d")
var progress = (Date.now() - started) / Math.max(1, duration)
context.clearRect(0, 0, 720, 420)
if (progress >= 1 || !canvas.isConnected) { return }
particles.forEach(function (piece) {
piece.vx = piece.vx * 0.99
piece.vy = piece.vy + 0.19
piece.x = piece.x + piece.vx
piece.y = piece.y + piece.vy
piece.rotation = piece.rotation + piece.spin
context.save()
context.translate(piece.x, piece.y)
context.rotate(piece.rotation)
context.globalAlpha = Math.min(1, (1 - progress) * 4)
context.fillStyle = piece.color
context.fillRect(-3, -4, 6, 8 * Math.cos(piece.rotation))
context.restore()
})
setTimeout(animateFrame, 16, canvas, particles, started)
}
client function animateBurst(sourceEvent) {
var root = sourceEvent.currentTarget.closest(".wrn-confetti")
var canvas = root.querySelector("canvas")
if (!canvas || window.matchMedia("(prefers-reduced-motion: reduce)").matches) { return }
var context = canvas.getContext("2d")
if (!context) { return }
canvas.width = 720
canvas.height = 420
var particles = Array.from({length:Math.min(200, Math.max(1, count))}, function (_, index) {
var angle = (-155 + Math.random() * 130) * Math.PI / 180
var speed = 5 + Math.random() * 9
return {x:360,y:273,vx:Math.cos(angle) * speed,vy:Math.sin(angle) * speed,rotation:Math.random() * 6,spin:(Math.random() - 0.5) * 0.3,color:["#8b5cf6","#14b8a6","#f59e0b","#f472b6","#60a5fa"][index % 5]}
})
animateFrame(canvas, particles, Date.now())
}
client function finish(sourceEvent) {
running = false
@@ -31,6 +67,7 @@ component Confetti {
return
}
running = true
if (sourceEvent) { animateBurst(sourceEvent) }
output.start({ sourceEvent: sourceEvent, duration: duration })
setTimeout(finish, duration, sourceEvent)
}
@@ -41,11 +78,11 @@ component Confetti {
}
view {
<div {...attrs} data-ui-component="Confetti" data-running='{running ? "true" : "false"}' class='wrn-confetti {class}' style='--wrn-confetti-duration:{duration}ms'>
<div {...attrs} data-ui-component="Confetti" data-autostart="{autoStart}" data-running='{running ? "true" : "false"}' class='wrn-confetti {class}' style='--wrn-confetti-duration:{duration}ms'>
<button type="button" class="wrn-confetti__trigger" disabled='{disabled}' @click='start(event)'>
<span class="icon-[lucide--party-popper]" aria-hidden="true"></span><span>{label}</span>
</button>
<div class="wrn-confetti__burst" aria-hidden="true">
<canvas class="wrn-confetti__canvas" aria-hidden="true"></canvas><div class="wrn-confetti__burst" aria-hidden="true">
{#each pieces() as piece, index}<i style='{pieceStyle(index)}'></i>{/each}
</div>
<slot />
@@ -53,17 +90,21 @@ component Confetti {
}
style {
.wrn-confetti__canvas { position:absolute; width:720px; height:420px; left:50%; top:50%; transform:translate(-50%,-65%); pointer-events:none; z-index:5; }
.wrn-confetti__burst { display:none; }
.wrn-confetti[data-autostart="true"] .wrn-confetti__burst { display:block; }
.wrn-confetti { position: relative; display: inline-flex; }
.wrn-confetti__trigger { display: inline-flex; min-height: 2.5rem; align-items: center; gap: 0.5rem; padding: 0.55rem 0.85rem; border: 1px solid var(--wrn-color-primary); border-radius: var(--wrn-radius); color: var(--wrn-color-on-primary); background: var(--wrn-color-primary); font: inherit; font-weight: 700; cursor: pointer; }
.wrn-confetti__trigger > span:first-child { width: 1rem; height: 1rem; }
.wrn-confetti__trigger:focus-visible { outline: 2px solid var(--wrn-color-focus); outline-offset: 2px; }
.wrn-confetti__trigger:disabled { cursor: not-allowed; opacity: 0.55; }
.wrn-confetti__burst { position: absolute; inset: 50% 0 auto; height: 0; pointer-events: none; }
.wrn-confetti__burst > i { position: absolute; left: calc(var(--wrn-confetti-x) * 1%); width: 0.45rem; height: 0.7rem; border-radius: 0.1rem; opacity: 0; background: hsl(calc(var(--wrn-confetti-index) * 47deg) 80% 55%); transform: translate(-50%, 0) rotate(calc(var(--wrn-confetti-index) * 23deg)); }
.wrn-confetti[data-running="true"] .wrn-confetti__burst > i { animation: wrn-confetti-burst var(--wrn-confetti-duration) cubic-bezier(0.2, 0.7, 0.2, 1) var(--wrn-confetti-delay) both; }
.wrn-confetti__burst { position: absolute; inset: 50% 0 auto; z-index:10; height: 0; pointer-events: none; }
.wrn-confetti__burst > i { position: absolute; left:50%; width: 0.45rem; height: 0.7rem; border-radius: 0.1rem; opacity: 0; background: hsl(calc(var(--wrn-confetti-index) * 47deg) 80% 55%); transform: translate(-50%, 0) rotate(calc(var(--wrn-confetti-index) * 23deg)); }
.wrn-confetti[data-running="true"] .wrn-confetti__burst > i { animation: wrn-confetti-burst var(--wrn-confetti-duration) linear var(--wrn-confetti-delay) both; }
@keyframes wrn-confetti-burst {
0% { opacity: 1; transform: translate(-50%, 0) rotate(0); }
100% { opacity: 0; transform: translate(calc(-50% + (var(--wrn-confetti-x) - 50) * 0.9px), 9rem) rotate(540deg); }
30% { opacity: 1; transform: translate(calc(-50% + (var(--wrn-confetti-x) - 50) * 6px), calc(-5rem - var(--wrn-confetti-x) * 1.2px)) rotate(180deg); }
100% { opacity: 0; transform: translate(calc(-50% + (var(--wrn-confetti-x) - 50) * 9px), 14rem) rotate(540deg); }
}
@media (prefers-reduced-motion: reduce) {
.wrn-confetti[data-running="true"] .wrn-confetti__burst > i { animation-duration: 1ms; animation-delay: 0ms; }
+10 -4
View File
@@ -37,6 +37,12 @@ component ContextMenu {
return open || visible
}
client function handleContext(sourceEvent) {
if (trigger === "contextmenu" || trigger === "both") { sourceEvent.preventDefault(); showMenu(sourceEvent) }
}
client function handleClick(sourceEvent) {
if (trigger === "click" || trigger === "both") { toggleMenu(sourceEvent) }
}
client function showMenu(sourceEvent) {
if (disabled) {
return
@@ -146,8 +152,8 @@ component ContextMenu {
tabindex='{disabled ? "-1" : "0"}'
aria-haspopup="menu"
aria-expanded='{open || visible ? "true" : "false"}'
@contextmenu='if (trigger === "contextmenu" || trigger === "both") { showMenu(event) }'
@click='if (trigger === "click" || trigger === "both") { toggleMenu(event) }'
@contextmenu='handleContext(event)'
@click='handleClick(event)'
@keydown='if (event.key === "Enter" || event.key === " ") { event.preventDefault(); toggleMenu(event) }'
>
<slot name="trigger"></slot>
@@ -296,7 +302,7 @@ component ContextMenu {
}
.wrn-context-menu__trigger:focus-visible {
border-radius: 0.6rem;
border-radius: var(--wrn-radius-sm, 6px);
outline: 2px solid var(--wrn-color-focus);
outline-offset: 3px;
}
@@ -325,7 +331,7 @@ component ContextMenu {
linear-gradient(145deg, color-mix(in srgb, var(--context-accent) 5%, transparent), transparent 58%),
color-mix(in srgb, var(--wrn-color-surface-raised) 96%, transparent);
border: 1px solid color-mix(in srgb, var(--context-accent) 18%, var(--wrn-color-border));
border-radius: 1rem;
border-radius: var(--wrn-radius-md);
box-shadow:
0 1px 0 color-mix(in srgb, white 5%, transparent) inset,
0 24px 64px color-mix(in srgb, black 24%, transparent);
+13 -87
View File
@@ -13,96 +13,22 @@ component DataMap {
variant: string = "default"
class: string = ""
}
state selectedLabel: string = ""
functions {
client function choose(item) { selectedLabel = item.label; output.select({ value: item.value, item: item }); output.change({ value: item.value, item: item }) }
shared function shade(item) { return Math.max(6, Math.min(35, (Number(item.value) || 0) / 3)) + "%" }
}
view {
<section class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--data-map wrn-next--variant-{variant} {class}">
{#if title}<strong>{title}</strong>{/if}
{#if description}<p>{description}</p>{/if}
{#if items}<div class="wrn-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
<slot />
</section>
<section {...attrs} class="wrn-data-map {class}">{#if title}<strong>{title}</strong>{/if}{#if description}<p>{description}</p>{/if}<div class="wrn-data-map__grid" role="group" aria-label="{title}">{#each items as item}<button type="button" aria-pressed="{selectedLabel === item.label}" style="--heat:{shade(item)}" @click="choose(item)"><strong>{item.label}</strong><span>{item.value}</span></button>{/each}</div><small>Lower activity <span class="wrn-data-map__legend"></span> Higher activity</small><slot /></section>
}
style {
.wrn-next--data-map {
display: grid;
gap: 0.75rem;
padding: 1rem;
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius);
color: var(--wrn-color-text);
background: var(--wrn-color-surface);
}
.wrn-next--data-map > p { margin: 0; color: var(--wrn-color-muted); }
.wrn-next--data-map > .wrn-next__items { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.wrn-next--data-map > .wrn-next__items > span { padding: 0.35rem 0.6rem; border-radius: var(--wrn-radius-sm); background: var(--wrn-color-surface-2); }
/* Shared component foundation. */
.wrn-component {
--wrn-component-color: var(--wrn-color-primary);
--wrn-component-scale: 1;
margin: 0;
color: var(--wrn-color-text);
font-family: var(--wrn-font-sans, inherit);
}
.wrn-component--color-primary {
--wrn-component-color: var(--wrn-color-primary);
}
.wrn-component--color-secondary {
--wrn-component-color: var(--wrn-color-secondary);
}
.wrn-component--color-success {
--wrn-component-color: var(--wrn-color-success);
}
.wrn-component--color-warning {
--wrn-component-color: var(--wrn-color-warning);
}
.wrn-component--color-danger {
--wrn-component-color: var(--wrn-color-danger);
}
.wrn-component--color-info {
--wrn-component-color: var(--wrn-color-info);
}
.wrn-component--size-xs {
--wrn-component-scale: 0.78;
}
.wrn-component--size-sm {
--wrn-component-scale: 0.88;
}
.wrn-component--size-default,
.wrn-component--size-md {
--wrn-component-scale: 1;
}
.wrn-component--size-lg {
--wrn-component-scale: 1.14;
}
.wrn-component--size-xl {
--wrn-component-scale: 1.28;
}
.wrn-component:not(.wrn-btn) {
font-size: calc(1em * var(--wrn-component-scale));
}
.wrn-component :is(a, button, input, select, textarea):focus-visible {
outline-color: var(--wrn-component-color);
}
.wrn-component p {
margin: 0;
color: var(--wrn-color-muted);
line-height: 1.6;
}
.wrn-data-map { display:grid; gap:.85rem; color:var(--wrn-color-text); }
.wrn-data-map p { margin:0; color:var(--wrn-color-muted); font-size:.875rem; }
.wrn-data-map__grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(6rem,1fr)); gap:.4rem; }
.wrn-data-map button { display:grid; gap:.35rem; padding:1rem .65rem; border:1px solid var(--wrn-color-border); border-radius:6px; background:color-mix(in srgb,var(--wrn-color-primary) var(--heat),var(--wrn-color-surface)); color:var(--wrn-color-text); font:inherit; font-size:.8rem; cursor:pointer; }
.wrn-data-map button[aria-pressed="true"] { outline:2px solid var(--wrn-color-primary-text); outline-offset:2px; }
.wrn-data-map small { display:flex; align-items:center; gap:.5rem; color:var(--wrn-color-muted); }
.wrn-data-map__legend { display:inline-block; width:4rem; height:.5rem; background:linear-gradient(90deg,var(--wrn-color-primary-soft),var(--wrn-color-primary)); border-radius:4px; }
}
}
+4 -3
View File
@@ -964,6 +964,7 @@ component DataTable {
size="sm"
placement="top-end"
label=""
value='{perPage}'
items='{pageSizeItems()}'
@select='pickPageSize(payload)'
>
@@ -1053,7 +1054,7 @@ component DataTable {
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-md);
background: var(--wrn-color-surface);
box-shadow: var(--wrn-shadow-1);
box-shadow: none;
}
.wrn-table {
@@ -1174,7 +1175,7 @@ component DataTable {
color: var(--wrn-color-text-muted);
background: var(--wrn-color-surface-soft);
border: 1px solid var(--wrn-color-border);
border-radius: 0.7rem;
border-radius: var(--wrn-radius-sm);
}
.wrn-table__search input {
@@ -1209,7 +1210,7 @@ component DataTable {
max-width: 100%;
overflow-x: auto;
border: 1px solid var(--wrn-color-border);
border-radius: 0.9rem;
border-radius: var(--wrn-radius-sm);
}
.wrn-table[data-bordered="false"] .wrn-table__scroll {
+125 -28
View File
@@ -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 {
+1
View File
@@ -53,6 +53,7 @@ component Divider {
style {
.wrn-divider {
border: 0;
--divider-tone: var(--wrn-color-border);
--divider-thickness: 1px;
display: flex;
+23 -87
View File
@@ -17,96 +17,32 @@ component DragAndDrop {
variant: string = "default"
class: string = ""
}
state orderedItems = items
state draggedIndex: number = -1
functions {
client function startDrag(index, sourceEvent) { draggedIndex = index; sourceEvent.dataTransfer.setData("text/plain", String(index)); output.dragStart({ index: index }) }
client function enter(sourceEvent) { sourceEvent.preventDefault(); output.dragEnter({ sourceEvent: sourceEvent }) }
client function leave(sourceEvent) { output.dragLeave({ sourceEvent: sourceEvent }) }
client function finish(sourceEvent) { draggedIndex = -1; output.dragEnd({ sourceEvent: sourceEvent }) }
client function move(from, to, sourceEvent, next, moved) {
if (from < 0 || to < 0 || to >= orderedItems.length || from === to) { return }
next = orderedItems.slice(); moved = next.splice(from, 1)[0]; next.splice(to, 0, moved); orderedItems = next
output.change({ value: orderedItems, sourceEvent: sourceEvent })
}
client function dropAt(index, sourceEvent) { sourceEvent.preventDefault(); move(draggedIndex, index, sourceEvent); draggedIndex = -1; output.drop({ index: index, items: orderedItems }) }
}
view {
<section class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--drag-and-drop wrn-next--variant-{variant} {class}">
{#if title}<strong>{title}</strong>{/if}
{#if description}<p>{description}</p>{/if}
{#if items}<div class="wrn-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
<slot />
</section>
<section {...attrs} class="wrn-sortable {class}">{#if title}<strong>{title}</strong>{/if}{#if description}<p>{description}</p>{/if}<ol>{#each orderedItems as item, index}<li draggable="true" @dragstart="startDrag(index, event)" @dragover="enter(event)" @dragleave="leave(event)" @dragend="finish(event)" @drop="dropAt(index, event)"><span class="icon-[lucide--grip-vertical]" aria-hidden="true"></span><span>{item.label}</span><button type="button" aria-label="Move {item.label} up" disabled="{index === 0}" @click="move(index, index - 1, event)">↑</button><button type="button" aria-label="Move {item.label} down" disabled="{index === orderedItems.length - 1}" @click="move(index, index + 1, event)">↓</button></li>{/each}</ol><slot /></section>
}
style {
.wrn-next--drag-and-drop {
display: grid;
gap: 0.75rem;
padding: 1rem;
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius);
color: var(--wrn-color-text);
background: var(--wrn-color-surface);
}
.wrn-next--drag-and-drop > p { margin: 0; color: var(--wrn-color-muted); }
.wrn-next--drag-and-drop > .wrn-next__items { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.wrn-next--drag-and-drop > .wrn-next__items > span { padding: 0.35rem 0.6rem; border-radius: var(--wrn-radius-sm); background: var(--wrn-color-surface-2); }
/* Shared component foundation. */
.wrn-component {
--wrn-component-color: var(--wrn-color-primary);
--wrn-component-scale: 1;
margin: 0;
color: var(--wrn-color-text);
font-family: var(--wrn-font-sans, inherit);
}
.wrn-component--color-primary {
--wrn-component-color: var(--wrn-color-primary);
}
.wrn-component--color-secondary {
--wrn-component-color: var(--wrn-color-secondary);
}
.wrn-component--color-success {
--wrn-component-color: var(--wrn-color-success);
}
.wrn-component--color-warning {
--wrn-component-color: var(--wrn-color-warning);
}
.wrn-component--color-danger {
--wrn-component-color: var(--wrn-color-danger);
}
.wrn-component--color-info {
--wrn-component-color: var(--wrn-color-info);
}
.wrn-component--size-xs {
--wrn-component-scale: 0.78;
}
.wrn-component--size-sm {
--wrn-component-scale: 0.88;
}
.wrn-component--size-default,
.wrn-component--size-md {
--wrn-component-scale: 1;
}
.wrn-component--size-lg {
--wrn-component-scale: 1.14;
}
.wrn-component--size-xl {
--wrn-component-scale: 1.28;
}
.wrn-component:not(.wrn-btn) {
font-size: calc(1em * var(--wrn-component-scale));
}
.wrn-component :is(a, button, input, select, textarea):focus-visible {
outline-color: var(--wrn-component-color);
}
.wrn-component p {
margin: 0;
color: var(--wrn-color-muted);
line-height: 1.6;
}
.wrn-sortable { display:grid; gap:.75rem; color:var(--wrn-color-text); }
.wrn-sortable p { margin:0; color:var(--wrn-color-muted); font-size:.875rem; }
.wrn-sortable ol { display:grid; gap:.5rem; list-style:none; margin:0; padding:0; }
.wrn-sortable li { display:flex; align-items:center; gap:.6rem; padding:.65rem; background:var(--wrn-color-surface); border:1px solid var(--wrn-color-border); border-radius:var(--wrn-radius-sm); font-size:.875rem; cursor:grab; }
.wrn-sortable li > span:nth-child(2) { flex:1; }
.wrn-sortable li > span:first-child { width:1rem; height:1rem; color:var(--wrn-color-muted); }
.wrn-sortable button { border:1px solid var(--wrn-color-border); background:transparent; color:inherit; border-radius:4px; width:1.75rem; height:1.75rem; cursor:pointer; }
.wrn-sortable button:disabled { opacity:.3; }
}
}
+2 -2
View File
@@ -209,12 +209,12 @@ open: boolean = false
.wrn-drawer__trigger {
appearance: none;
min-height: 2.55rem;
min-height: var(--wrn-control-height, 2.5rem);
padding: 0.65rem 1rem;
color: var(--wrn-color-primary-contrast);
background: var(--drawer-accent);
border: 0;
border-radius: 0.8rem;
border-radius: var(--wrn-radius-sm);
font: inherit;
font-size: 0.85rem;
font-weight: 650;
+10 -9
View File
@@ -8,6 +8,7 @@ component Dropdown {
}
props {
value: string | number = ""
items: unknown[] = []
open: boolean = false
defaultOpen: boolean = false
@@ -215,7 +216,7 @@ items: unknown[] = []
role="menuitem"
data-dropdown-item
data-danger='{item.danger ? "true" : "false"}'
data-selected='{item.selected || item.checked ? "true" : "false"}'
data-selected='{(value !== "" ? String(item.value) === String(value) : item.selected || item.checked) ? "true" : "false"}'
aria-disabled='{item.disabled ? "true" : "false"}'
tabindex='{item.disabled ? "-1" : "0"}'
class="wrn-dropdown__item"
@@ -234,7 +235,7 @@ items: unknown[] = []
<span class="wrn-dropdown__badge">{item.badge}</span>
{:else if item.shortcut}
<kbd>{item.shortcut}</kbd>
{:else if item.checked || item.selected}
{:else if value !== "" ? String(item.value) === String(value) : item.checked || item.selected}
<span class="icon-[lucide--check] wrn-dropdown__status" aria-hidden="true"></span>
{:else if item.external}
<span class="icon-[lucide--arrow-up-right] wrn-dropdown__status" aria-hidden="true"></span>
@@ -246,7 +247,7 @@ items: unknown[] = []
role="menuitem"
data-dropdown-item
data-danger='{item.danger ? "true" : "false"}'
data-selected='{item.selected || item.checked ? "true" : "false"}'
data-selected='{(value !== "" ? String(item.value) === String(value) : item.selected || item.checked) ? "true" : "false"}'
disabled='{item.disabled}'
class="wrn-dropdown__item"
@click='chooseItem(item, itemIndex, event)'
@@ -264,7 +265,7 @@ items: unknown[] = []
<span class="wrn-dropdown__badge">{item.badge}</span>
{:else if item.shortcut}
<kbd>{item.shortcut}</kbd>
{:else if item.checked || item.selected}
{:else if value !== "" ? String(item.value) === String(value) : item.checked || item.selected}
<span class="icon-[lucide--check] wrn-dropdown__status" aria-hidden="true"></span>
{/if}
</button>
@@ -320,12 +321,12 @@ items: unknown[] = []
align-items: center;
justify-content: center;
gap: 0.55rem;
min-height: 2.55rem;
min-height: var(--wrn-control-height, 2.5rem);
padding: 0.65rem 0.9rem;
color: var(--wrn-color-text);
background: var(--wrn-color-surface-raised);
border: 1px solid var(--wrn-color-border);
border-radius: 0.78rem;
border-radius: var(--wrn-radius-sm);
font: inherit;
font-size: 0.84rem;
font-weight: 600;
@@ -362,13 +363,13 @@ items: unknown[] = []
}
.wrn-dropdown[data-size="sm"] .wrn-dropdown__trigger {
min-height: 2.2rem;
min-height: var(--wrn-control-height-sm, 2rem);
padding: 0.5rem 0.72rem;
font-size: 0.78rem;
}
.wrn-dropdown[data-size="lg"] .wrn-dropdown__trigger {
min-height: 2.9rem;
min-height: var(--wrn-control-height-lg, 2.75rem);
padding: 0.78rem 1.05rem;
font-size: 0.9rem;
}
@@ -397,7 +398,7 @@ items: unknown[] = []
linear-gradient(145deg, color-mix(in srgb, var(--dropdown-accent) 5%, transparent), transparent 60%),
color-mix(in srgb, var(--wrn-color-surface-raised) 97%, transparent);
border: 1px solid color-mix(in srgb, var(--dropdown-accent) 18%, var(--wrn-color-border));
border-radius: 1rem;
border-radius: var(--wrn-radius-md);
box-shadow:
0 1px 0 color-mix(in srgb, white 5%, transparent) inset,
0 24px 64px color-mix(in srgb, black 22%, transparent);
-1
View File
@@ -49,7 +49,6 @@ component FeatureCard {
data-disabled='{disabled ? "true" : "false"}'
data-stretched-link='{stretchedLink ? "true" : "false"}'
class='wrn-feature-card {class}'
role="listitem"
aria-disabled='{disabled ? "true" : "false"}'
>
{#if image}
+1 -1
View File
@@ -38,7 +38,7 @@ component FeatureGrid {
style='--wrn-feature-grid-columns: {columns}; --wrn-feature-grid-tablet-columns: {tabletColumns}; --wrn-feature-grid-mobile-columns: {mobileColumns}; --wrn-feature-grid-min-item-width: {minItemWidth || "17rem"};'
>
<div class="wrn-feature-grid__inner">
<div class="wrn-feature-grid__grid" role="list">
<div class="wrn-feature-grid__grid">
{#if items.length > 0}
{#each items as item, itemIndex}
<FeatureCard
+23 -19
View File
@@ -36,10 +36,10 @@ size: string = "default"
functions {
client function handleChange(sourceEvent) {
sourceEvent.stopPropagation()
selectedName = sourceEvent.currentTarget.files && sourceEvent.currentTarget.files.length ? sourceEvent.currentTarget.files[0].name : ""
output.input({ files: sourceEvent.currentTarget.files, name: name, sourceEvent: sourceEvent })
output.change({ files: sourceEvent.currentTarget.files, name: name, sourceEvent: sourceEvent })
if (selectedName) output.select({ files: sourceEvent.currentTarget.files, name: name, sourceEvent: sourceEvent })
selectedName = sourceEvent.currentTarget.files && sourceEvent.currentTarget.files.length ? Array.from(sourceEvent.currentTarget.files).map(function (file) { return file.name }).join(", ") : ""
output.input({ files: Array.from(sourceEvent.currentTarget.files || []), name: name, sourceEvent: sourceEvent })
output.change({ files: Array.from(sourceEvent.currentTarget.files || []), name: name, sourceEvent: sourceEvent })
if (selectedName) output.select({ files: Array.from(sourceEvent.currentTarget.files || []), name: name, sourceEvent: sourceEvent })
else output.clear({ name: name, sourceEvent: sourceEvent })
}
client function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); output[nameEvent]({ name: name, sourceEvent: sourceEvent }) }
@@ -48,15 +48,19 @@ size: string = "default"
view {
<div {...attrs} class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--field wrn-next--file-input {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}">
<div class="wrn-next__field-heading"><label class="{hiddenLabel ? 'wrn-next__sr-only' : ''}" for="{id || name}">{label}</label>{#if cornerHint}<span class="wrn-next__field-hint">{cornerHint}</span>{/if}</div>
<div class="wrn-next__file-control" data-icon-position="{iconPosition}">{#if icon}<span class="{icon}" aria-hidden="true"></span>{/if}<span>{selectedName || value || placeholder}</span><input id="{id || name}" type="file" name="{name}" accept="{accept}" multiple="{multiple}" disabled="{disabled}" required="{required}" aria-readonly="{readonly}" aria-invalid="{error ? 'true' : 'false'}" @click="preventReadonly(event)" @input="handleChange(event)" @change="handleChange(event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" @invalid="emitField('invalid', event)" /></div>
<div class="wrn-next__file-control" data-icon-position="{iconPosition}">{#if icon}<span class="{icon}" aria-hidden="true"></span>{/if}<span>{selectedName || value || placeholder}</span><input id="{id || name}" type="file" name="{name}" accept="{accept}" multiple="{multiple}" disabled="{disabled}" required="{required}" aria-readonly="{readonly}" aria-invalid="{error ? 'true' : 'false'}" @click="preventReadonly(event)" @change="handleChange(event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" @invalid="emitField('invalid', event)" /></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-next__file-control > span:last-of-type { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.wrn-next__file-control > span:first-child:not(:last-of-type) { flex:none; width:1rem; height:1rem; }
.wrn-next__file-control:focus-within { outline:2px solid var(--wrn-color-focus); outline-offset:2px; }
.wrn-next__file-control {
position: relative;
min-height: 2.75rem;
display:flex; align-items:center; gap:.6rem; min-width:0;
min-height: 2.5rem;
padding: 0.65rem 0.85rem;
border: 1px dashed var(--wrn-color-border);
border-radius: var(--wrn-radius-sm);
@@ -67,7 +71,7 @@ size: string = "default"
position: absolute;
cursor: pointer;
opacity: 0;
inset: 0;
inset: 0; width:100%; height:100%;
}
/* Shared component foundation. */
@@ -176,10 +180,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);
@@ -193,9 +197,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);
@@ -237,7 +241,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 {
@@ -251,10 +255,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 {
@@ -362,7 +366,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 {
@@ -371,7 +375,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"] {
@@ -520,7 +524,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;
+14 -13
View File
@@ -18,6 +18,7 @@ size: string = "default"
status: string = "uploading"
cancelLabel: string = "Cancel upload"
retryLabel: string = "Retry upload"
showCompleteAction: boolean = false
class: string = ""
}
state currentValue = value
@@ -34,7 +35,7 @@ size: string = "default"
<div class="wrn-next__row"><span>{#if fileName}<strong>{fileName}</strong><small>{#if uploadedSize || fileSize}{uploadedSize}{#if uploadedSize && fileSize} of {/if}{fileSize}{:else}{label}{/if}</small>{:else}<strong>{label}</strong>{/if}</span>{#if showValue}<strong>{percent()}%</strong>{/if}</div>
<progress value="{currentValue}" max="{max}" aria-label="{label}"></progress>
<div class="wrn-next__upload-status"><span class="wrn-next__upload-status-icon" aria-hidden="true"></span><span>{#if currentStatus === "uploading"}Uploading securely…{:else if currentStatus === "complete"}Upload complete{:else if currentStatus === "error"}Upload failed{:else}Upload cancelled{/if}</span></div>
<div class="wrn-next__upload-actions">{#if currentStatus === "uploading"}<button type="button" @click="cancelUpload(event)">{cancelLabel}</button><button type="button" @click="completeUpload(event)">Complete</button>{/if}{#if currentStatus === "error" || currentStatus === "cancelled"}<button type="button" @click="retryUpload(event)">{retryLabel}</button>{/if}</div>
<div class="wrn-next__upload-actions">{#if currentStatus === "uploading"}<button type="button" @click="cancelUpload(event)">{cancelLabel}</button>{#if showCompleteAction}<button type="button" @click="completeUpload(event)">Complete</button>{/if}{/if}{#if currentStatus === "error" || currentStatus === "cancelled"}<button type="button" @click="retryUpload(event)">{retryLabel}</button>{/if}</div>
</div>
}
@@ -190,10 +191,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);
@@ -207,9 +208,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);
@@ -251,7 +252,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 {
@@ -265,10 +266,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 {
@@ -376,7 +377,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 {
@@ -385,7 +386,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"] {
@@ -534,7 +535,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;
+1 -1
View File
@@ -1,4 +1,4 @@
import Badge from "./Badge.wrn"
import Badge from "./badge.wrn"
component Hero {
props {
+14 -12
View File
@@ -53,6 +53,8 @@ component InputGroup {
}
style {
.wrn-next__input-group-control > .wrn-next__field-icon { position:static; transform:none; flex:0 0 1rem; width:1rem; height:1rem; margin-inline:.75rem 0; }
.wrn-next__input-group-control > .wrn-next__field-icon[data-position="end"] { order:2; margin-inline:0 .75rem; }
.wrn-next__input-group-control {
overflow: hidden;
border: 1px solid var(--wrn-color-border);
@@ -189,10 +191,10 @@ component InputGroup {
}
.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);
@@ -206,9 +208,9 @@ component InputGroup {
}
.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);
@@ -250,7 +252,7 @@ component InputGroup {
.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 {
@@ -264,10 +266,10 @@ component InputGroup {
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 {
@@ -375,7 +377,7 @@ component InputGroup {
}
.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 {
@@ -384,7 +386,7 @@ component InputGroup {
.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"] {
@@ -533,7 +535,7 @@ component InputGroup {
.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;
+18 -5
View File
@@ -13,16 +13,29 @@ component LegendIndicator {
variant: string = "default"
class: string = ""
}
state hiddenItems = []
functions {
client function toggleItem(item, index, sourceEvent) {
hiddenItems = hiddenItems.includes(index) ? hiddenItems.filter(function (value) { return value !== index }) : hiddenItems.concat(index)
output.toggle({ item: item, index: index, hidden: hiddenItems.includes(index), sourceEvent: sourceEvent })
output.select({ value: item.value || item.label, sourceEvent: sourceEvent })
}
}
view {
<section class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--legend-indicator wrn-next--variant-{variant} {class}">
<div class="wrn-legend {class}">
{#if title}<strong>{title}</strong>{/if}
{#if description}<p>{description}</p>{/if}
{#if items}<div class="wrn-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
<slot />
</section>
<div class="wrn-legend__items">{#each items as item, index}<button type="button" aria-pressed="{!hiddenItems.includes(index)}" @click="toggleItem(item, index, event)"><i style="background:{item.color || 'var(--wrn-color-primary)'}" aria-hidden="true"></i><span>{item.label}</span>{#if item.value}<small>{item.value}</small>{/if}</button>{/each}</div><slot />
</div>
}
style {
.wrn-legend { display:grid; gap:.65rem; }
.wrn-legend p { margin:0; color:var(--wrn-color-muted); }
.wrn-legend__items { display:flex; flex-wrap:wrap; gap:.75rem; }
.wrn-legend button { display:flex; align-items:center; gap:.4rem; border:0; background:transparent; color:var(--wrn-color-text); font:inherit; font-size:.875rem; cursor:pointer; }
.wrn-legend i { width:.65rem; height:.65rem; border-radius:50%; }
.wrn-legend button[aria-pressed="false"] { opacity:.45; text-decoration:line-through; }
.wrn-next--legend-indicator {
display: grid;
gap: 0.75rem;
+23 -86
View File
@@ -11,98 +11,35 @@ component ListGroup {
description: string = ""
items: unknown[] = []
variant: string = "default"
value: string = ""
class: string = ""
}
state selectedValue = value
functions {
client function choose(item, index) {
if (item.disabled) { return }
selectedValue = String(item.value || index)
output.select({ value: selectedValue, item: item, index: index })
output.change({ value: selectedValue, item: item, index: index })
}
}
view {
<section class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--list-group wrn-next--variant-{variant} {class}">
{#if title}<strong>{title}</strong>{/if}
{#if description}<p>{description}</p>{/if}
{#if items}<div class="wrn-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
<slot />
<section {...attrs} class="wrn-list-group {class}">
{#if title}<strong>{title}</strong>{/if}{#if description}<p>{description}</p>{/if}
<div class="wrn-list-group__items" role="group" aria-label="{title}">{#each items as item, index}<button type="button" disabled="{item.disabled}" aria-pressed="{selectedValue === String(item.value || index)}" @click="choose(item, index)">{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}<span><strong>{item.label}</strong>{#if item.description}<small>{item.description}</small>{/if}</span>{#if item.badge}<span class="wrn-list-group__badge">{item.badge}</span>{/if}</button>{/each}</div><slot />
</section>
}
style {
.wrn-next--list-group {
display: grid;
gap: 0.75rem;
padding: 1rem;
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius);
color: var(--wrn-color-text);
background: var(--wrn-color-surface);
}
.wrn-next--list-group > p { margin: 0; color: var(--wrn-color-muted); }
.wrn-next--list-group > .wrn-next__items { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.wrn-next--list-group > .wrn-next__items > span { padding: 0.35rem 0.6rem; border-radius: var(--wrn-radius-sm); background: var(--wrn-color-surface-2); }
/* Shared component foundation. */
.wrn-component {
--wrn-component-color: var(--wrn-color-primary);
--wrn-component-scale: 1;
margin: 0;
color: var(--wrn-color-text);
font-family: var(--wrn-font-sans, inherit);
}
.wrn-component--color-primary {
--wrn-component-color: var(--wrn-color-primary);
}
.wrn-component--color-secondary {
--wrn-component-color: var(--wrn-color-secondary);
}
.wrn-component--color-success {
--wrn-component-color: var(--wrn-color-success);
}
.wrn-component--color-warning {
--wrn-component-color: var(--wrn-color-warning);
}
.wrn-component--color-danger {
--wrn-component-color: var(--wrn-color-danger);
}
.wrn-component--color-info {
--wrn-component-color: var(--wrn-color-info);
}
.wrn-component--size-xs {
--wrn-component-scale: 0.78;
}
.wrn-component--size-sm {
--wrn-component-scale: 0.88;
}
.wrn-component--size-default,
.wrn-component--size-md {
--wrn-component-scale: 1;
}
.wrn-component--size-lg {
--wrn-component-scale: 1.14;
}
.wrn-component--size-xl {
--wrn-component-scale: 1.28;
}
.wrn-component:not(.wrn-btn) {
font-size: calc(1em * var(--wrn-component-scale));
}
.wrn-component :is(a, button, input, select, textarea):focus-visible {
outline-color: var(--wrn-component-color);
}
.wrn-component p {
margin: 0;
color: var(--wrn-color-muted);
line-height: 1.6;
}
.wrn-list-group { display:grid; gap:.75rem; color:var(--wrn-color-text); }
.wrn-list-group > p { margin:0; font-size:.875rem; color:var(--wrn-color-muted); }
.wrn-list-group__items { border:1px solid var(--wrn-color-border); border-radius:var(--wrn-radius-md); overflow:hidden; }
.wrn-list-group button { display:flex; align-items:center; gap:.75rem; width:100%; padding:.85rem 1rem; text-align:start; border:0; color:inherit; background:var(--wrn-color-surface); font:inherit; font-size:.875rem; cursor:pointer; }
.wrn-list-group button + button { border-top:1px solid var(--wrn-color-border); }
.wrn-list-group button[aria-pressed="true"] { background:var(--wrn-color-primary-soft); color:var(--wrn-color-primary-text); }
.wrn-list-group button > [class*="icon-"] { width:1.1rem; height:1.1rem; flex:none; }
.wrn-list-group small { display:block; margin-top:.2rem; color:var(--wrn-color-muted); font-size:.8rem; }
.wrn-list-group__badge { margin-inline-start:auto; font-size:.75rem; }
.wrn-list-group button:disabled { opacity:.5; cursor:not-allowed; }
}
}
+37 -4
View File
@@ -14,9 +14,30 @@ component Map {
description: string = ""
items: unknown[] = []
variant: string = "default"
provider: string = "openstreetmap"
apiKey: string = ""
query: string = ""
latitude: number = 12.9716
longitude: number = 77.5946
zoomLevel: number = 13
mapType: string = "roadmap"
imageSrc: string = ""
imageAlt: string = "Map background"
class: string = ""
}
state imageZoom: number = 1
state selectedLabel: string = ""
functions {
shared function mapUrl() {
if (provider === "google") { return "https://www.google.com/maps/embed/v1/place?key=" + encodeURIComponent(apiKey) + "&q=" + encodeURIComponent(query || latitude + "," + longitude) + "&zoom=" + Math.max(1, Math.min(21, zoomLevel)) + "&maptype=" + (mapType === "satellite" ? "satellite" : "roadmap") }
var span = 360 / Math.pow(2, Math.max(1, Math.min(18, zoomLevel)))
return "https://www.openstreetmap.org/export/embed.html?bbox=" + (longitude - span) + "," + (latitude - span / 2) + "," + (longitude + span) + "," + (latitude + span / 2) + "&layer=mapnik&marker=" + latitude + "," + longitude
}
client function changeZoom(direction) { imageZoom = Math.max(1, Math.min(3, imageZoom + direction * .25)); output.zoom({ level: imageZoom, direction: direction > 0 ? "in" : "out" }) }
client function chooseMarker(item) { selectedLabel = item.label || item.title || "Location"; output.markerClick(item); output.select(item) }
}
view {
<section
data-ui-component="Map"
@@ -42,6 +63,10 @@ component Map {
class="wrn-map__viewport"
data-size='{size}'
>
{#if !imageSrc}
{#if provider === "google" && !apiKey}<p class="wrn-map__selection">Add your Google Maps Embed API key to display this map.</p>{:else}<iframe title="{title || 'Location map'}" src="{mapUrl()}" loading="lazy" referrerpolicy="no-referrer-when-downgrade" allowfullscreen style="width:100%;height:100%;min-height:320px;border:0"></iframe>{/if}
{:else}<div class="wrn-map__canvas" style="transform:scale({imageZoom})">
{#if imageSrc}<img class="wrn-map__image" src="{imageSrc}" alt="{imageAlt}" />{/if}
<div
class="wrn-map__grid"
style="background-image: linear-gradient(var(--wrn-color-border) 1px, transparent 1px), linear-gradient(90deg, var(--wrn-color-border) 1px, transparent 1px); background-size: 32px 32px;"
@@ -59,19 +84,21 @@ component Map {
aria-label='{item.label || item.title || "Map marker"}'
class="wrn-map__marker"
style='left: {item.x || (20 + index * 12)}%; top: {item.y || (30 + (index % 3) * 18)}%;'
@click='output.markerClick(item); output.select(item)'
@click='chooseMarker(item)'
>
<span class='{item.icon || "icon-[lucide--map-pin]"}' aria-hidden="true"></span>
</button>
{/each}
{/if}
</div>
{#if selectedLabel}<output class="wrn-map__selection">{selectedLabel}</output>{/if}
<div class="wrn-map__controls">
<button
type="button"
aria-label="Zoom in"
class="wrn-map__control"
@click='output.zoom({ direction: "in" })'
@click='changeZoom(1)'
>
<span class="icon-[lucide--plus] wrn-map__control-icon" aria-hidden="true"></span>
</button>
@@ -79,16 +106,22 @@ component Map {
type="button"
aria-label="Zoom out"
class="wrn-map__control"
@click='output.zoom({ direction: "out" })'
@click='changeZoom(-1)'
>
<span class="icon-[lucide--minus] wrn-map__control-icon" aria-hidden="true"></span>
</button>
</div>
</div>{/if}
</div>
</section>
}
style {
.wrn-map__canvas { position:absolute; inset:0; transition:transform 160ms ease; }
.wrn-map__image { width:100%; height:100%; object-fit:cover; }
.wrn-map__canvas:has(.wrn-map__image) .wrn-map__grid { display:none; }
.wrn-map__selection { position:absolute; inset-inline-start:1rem; bottom:1rem; padding:.5rem .75rem; border-radius:6px; background:var(--wrn-color-surface); color:var(--wrn-color-text); font-size:.875rem; }
.wrn-map__marker > span { width:1.1rem; height:1.1rem; }
.wrn-map { overflow: hidden; border: 1px solid var(--wrn-color-border); border-radius: calc(var(--wrn-radius) * 1.5); background: var(--wrn-color-surface-raised); box-shadow: var(--wrn-shadow-1); }
.wrn-map__header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; padding: 1.25rem; border-bottom: 1px solid var(--wrn-color-border); }
.wrn-map__title { color: var(--wrn-color-text); font-size: 1.125rem; font-weight: 700; }
+2 -2
View File
@@ -193,12 +193,12 @@ component MegaMenu {
class="wrn-mega__trigger"
aria-haspopup="true"
aria-expanded='{visible}'
aria-label='{label}'
aria-label='{label || "Menu"}'
@click='activatePanel(event)'
@mouseenter='hoverPanel(event)'
>
<span class='wrn-mega__trigger-icon {icon}' data-show="icon" aria-hidden="true"></span>
<span class="wrn-mega__trigger-label" data-label='{label}' aria-hidden="true"></span>
<span class="wrn-mega__trigger-label" data-label='{label || "Menu"}' aria-hidden="true"></span>
<span class="wrn-mega__arrow" aria-hidden="true">&#8250;</span>
</button>
+14 -22
View File
@@ -31,7 +31,7 @@ component MetricCard {
size: string = "default"
color: string = "primary"
variant: string = "default"
hover: string = "lift"
hover: string = "none"
align: string = "left"
class: string = ""
}
@@ -211,7 +211,7 @@ component MetricCard {
--metric-accent-soft: var(--wrn-color-primary-soft);
--metric-accent-muted: var(--wrn-color-primary-muted);
--metric-contrast: var(--wrn-color-primary-contrast);
--metric-border: color-mix(in srgb, var(--metric-accent) 16%, var(--wrn-color-border));
--metric-border: var(--wrn-color-border);
position: relative;
isolation: isolate;
@@ -220,20 +220,12 @@ component MetricCard {
width: 100%;
min-width: 0;
height: 100%;
padding: 1.5rem;
padding: var(--wrn-space-panel, 1.25rem);
color: var(--wrn-color-text);
background:
radial-gradient(
circle at 100% 0%,
color-mix(in srgb, var(--metric-accent) 8%, transparent),
transparent 36%
),
var(--wrn-color-surface-raised);
background: var(--wrn-color-surface);
border: 1px solid var(--metric-border);
border-radius: 1.25rem;
box-shadow:
0 1px 0 color-mix(in srgb, white 4%, transparent) inset,
0 14px 36px color-mix(in srgb, black 10%, transparent);
border-radius: var(--wrn-radius-md);
box-shadow: none;
overflow: hidden;
transition:
transform 180ms ease,
@@ -288,8 +280,8 @@ component MetricCard {
}
.wrn-metric-card[data-size="lg"] {
padding: 1.9rem;
border-radius: 1.5rem;
padding: 1.5rem;
border-radius: var(--wrn-radius-lg);
}
.wrn-metric-card[data-variant="soft"] {
@@ -464,7 +456,7 @@ component MetricCard {
align-items: baseline;
gap: 0.2rem;
width: 100%;
margin-top: 1.15rem;
margin-top: 0.75rem;
}
.wrn-metric-card[data-align="center"] .wrn-metric-card__value-row {
@@ -477,7 +469,7 @@ component MetricCard {
.wrn-metric-card__value {
color: var(--wrn-color-text);
font-size: clamp(2rem, 5vw, 2.8rem);
font-size: clamp(1.75rem, 3vw, 2rem);
font-weight: 700;
line-height: 0.98;
letter-spacing: -0.045em;
@@ -486,11 +478,11 @@ component MetricCard {
}
.wrn-metric-card[data-size="sm"] .wrn-metric-card__value {
font-size: clamp(1.65rem, 4vw, 2.15rem);
font-size: 1.5rem;
}
.wrn-metric-card[data-size="lg"] .wrn-metric-card__value {
font-size: clamp(2.45rem, 6vw, 3.5rem);
font-size: clamp(2rem, 4vw, 2.5rem);
}
.wrn-metric-card[data-variant="solid"] .wrn-metric-card__value {
@@ -513,8 +505,8 @@ component MetricCard {
max-width: 38rem;
margin: 0.8rem 0 0;
color: var(--wrn-color-text-muted);
font-size: 0.86rem;
line-height: 1.7;
font-size: 0.8125rem;
line-height: 1.5;
}
.wrn-metric-card__progress-block {
+18 -24
View File
@@ -18,6 +18,7 @@ component Modal {
placement: string = "center"
color: string = "primary"
variant: string = "default"
showHeader: boolean = true
showClose: boolean = true
closeLabel: string = "Close modal"
closeOnBackdrop: boolean = true
@@ -98,7 +99,7 @@ component Modal {
<div
{...attrs}
data-ui-component="Modal"
data-open='{isOpen() ? "true" : "false"}'
data-open='{(open || visible) ? "true" : "false"}'
data-size='{size}'
data-placement='{placement}'
data-color='{color}'
@@ -107,6 +108,7 @@ component Modal {
data-scroll='{scrollBehavior}'
data-destructive='{destructive ? "true" : "false"}'
class='wrn-modal {class}'
@click="if (event.target.closest('[data-modal-close]')) { hideModal('custom-button', event) }"
@wrnexus:modal:close='hideModal("api", event)'
>
{#if triggerLabel}
@@ -114,7 +116,7 @@ component Modal {
type="button"
class="wrn-modal__trigger"
aria-haspopup="dialog"
aria-expanded='{isOpen() ? "true" : "false"}'
aria-expanded='{(open || visible) ? "true" : "false"}'
@click='showModal(event)'
>
{#if triggerIcon}
@@ -140,7 +142,7 @@ component Modal {
<div
class="wrn-modal__layer"
data-show="isOpen()"
data-show="(open || visible)"
role="presentation"
@keydown='handleKeydown(event)'
>
@@ -159,7 +161,7 @@ component Modal {
aria-label='{label || title}'
tabindex="-1"
>
<header
{#if showHeader}<header
class="wrn-modal__header"
>
<div
@@ -212,7 +214,7 @@ component Modal {
</svg>
</button>
{/if}
</header>
</header>{/if}
<div
class="wrn-modal__body"
@@ -343,12 +345,12 @@ component Modal {
.wrn-modal__trigger {
appearance: none;
min-height: 2.55rem;
min-height: var(--wrn-control-height);
padding: 0.65rem 1rem;
color: var(--modal-contrast);
background: var(--modal-accent);
border: 0;
border-radius: 0.8rem;
border-radius: var(--wrn-radius-sm);
font: inherit;
font-size: 0.85rem;
font-weight: 650;
@@ -431,18 +433,10 @@ component Modal {
width: min(32rem, calc(100vw - 2rem));
max-height: min(88vh, 52rem);
color: var(--wrn-color-text);
background:
radial-gradient(
circle at 100% 0%,
color-mix(in srgb, var(--modal-accent) 9%, transparent),
transparent 34%
),
var(--wrn-color-surface-raised);
border: 1px solid color-mix(in srgb, var(--modal-accent) 18%, var(--wrn-color-border));
border-radius: 1.35rem;
box-shadow:
0 1px 0 color-mix(in srgb, white 5%, transparent) inset,
0 40px 110px color-mix(in srgb, black 38%, transparent);
background: var(--wrn-color-surface-raised);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-lg);
box-shadow: var(--wrn-shadow-lg);
overflow: hidden;
opacity: 0;
transform: scale(0.96) translateY(10px);
@@ -502,7 +496,7 @@ component Modal {
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
padding: 1.4rem 1.4rem 1.15rem;
padding: 1.25rem;
border-bottom: 1px solid var(--wrn-color-border);
}
@@ -631,7 +625,7 @@ component Modal {
.wrn-modal__body {
flex: 1 1 auto;
min-height: 0;
padding: 1.4rem;
padding: 1.25rem;
color: var(--wrn-color-text);
}
@@ -659,7 +653,7 @@ component Modal {
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 1rem 1.4rem 1.4rem;
padding: 1rem 1.25rem;
border-top: 1px solid var(--wrn-color-border);
}
@@ -681,9 +675,9 @@ component Modal {
align-items: center;
justify-content: center;
gap: 0.5rem;
min-height: 2.55rem;
min-height: var(--wrn-control-height, 2.5rem);
padding: 0.65rem 1rem;
border-radius: 0.78rem;
border-radius: var(--wrn-radius-sm);
font: inherit;
font-size: 0.83rem;
font-weight: 650;
+1 -1
View File
@@ -639,7 +639,7 @@ size: string = "default"
padding: 0.65rem;
align-items: flex-start;
gap: 0.65rem;
border-radius: 0.6rem;
border-radius: var(--wrn-radius-sm, 6px);
font-size: 0.8rem;
font-weight: 450;
}
+15 -14
View File
@@ -387,6 +387,7 @@ current: true
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.35), transparent 78%);
}
.wrn-page-header[data-variant="default"] .wrn-page-header__decoration,
.wrn-page-header[data-variant="minimal"] .wrn-page-header__decoration,
.wrn-page-header[data-compact="true"] .wrn-page-header__grid-pattern {
display: none;
@@ -404,7 +405,7 @@ current: true
width: 100%;
max-width: 80rem;
margin-inline: auto;
padding: 1.25rem 1rem 2.75rem;
padding: 1.25rem 1rem;
}
.wrn-page-header[data-max-width="compact"] .wrn-page-header__inner,
@@ -423,11 +424,11 @@ current: true
.wrn-page-header[data-compact="true"] .wrn-page-header__inner {
padding-top: 1rem;
padding-bottom: 1.75rem;
padding-bottom: 1rem;
}
.wrn-page-header[data-size="lg"] .wrn-page-header__inner {
padding-bottom: 3.5rem;
padding-bottom: 2rem;
}
.wrn-page-header__breadcrumb {
@@ -518,9 +519,9 @@ current: true
margin: 0;
color: var(--page-header-accent-text);
font-size: 0.72rem;
font-weight: 800;
font-weight: 600;
line-height: 1.3;
letter-spacing: 0.16em;
letter-spacing: 0.06em;
text-transform: uppercase;
}
@@ -538,27 +539,27 @@ current: true
max-width: 54rem;
margin: 0;
color: var(--wrn-color-text);
font-size: clamp(2rem, 4vw, 3.75rem);
font-weight: 800;
line-height: 1.08;
font-size: clamp(1.5rem, 2.2vw, 1.75rem);
font-weight: 600;
line-height: 1.25;
letter-spacing: -0.035em;
text-wrap: balance;
}
.wrn-page-header[data-compact="true"] .wrn-page-header__title {
font-size: clamp(1.75rem, 3vw, 2.65rem);
font-size: 1.375rem;
}
.wrn-page-header[data-size="lg"] .wrn-page-header__title {
font-size: clamp(2.35rem, 5vw, 4.5rem);
font-size: clamp(1.75rem, 3vw, 2.25rem);
}
.wrn-page-header__description {
max-width: 48rem;
margin: 0.9rem 0 0;
margin: 0.5rem 0 0;
color: var(--wrn-color-text-muted);
font-size: 1rem;
line-height: 1.75;
font-size: 0.875rem;
line-height: 1.6;
text-wrap: pretty;
}
@@ -663,7 +664,7 @@ current: true
.wrn-page-header__layout {
grid-template-columns: minmax(0, 1fr) auto;
align-items: start;
gap: 2.5rem;
gap: 1.5rem;
}
.wrn-page-header[data-align="center"] .wrn-page-header__layout,
+3
View File
@@ -262,6 +262,9 @@ component Pagination {
.wrn-pagination__pages {
display: flex;
flex-wrap: wrap;
min-width: 0;
max-width: 100%;
align-items: center;
gap: 0.25rem;
margin: 0;
+12 -9
View File
@@ -165,6 +165,9 @@ component PinInput {
.wrn-next__pin-cells {
display: flex;
box-sizing: border-box;
width: 100%;
min-width: 0;
max-width: 100%;
align-items: center;
gap: clamp(0.35rem, 1.5vw, 0.65rem);
@@ -174,18 +177,18 @@ component PinInput {
}
.wrn-next--pin-input .wrn-next__pin-cells input {
flex: 0 0 clamp(2.4rem, 8vw, 3.25rem);
width: clamp(2.4rem, 8vw, 3.25rem);
height: clamp(2.75rem, 9vw, 3.5rem);
min-width: clamp(2.4rem, 8vw, 3.25rem);
flex: 0 1 2.5rem;
width: 2.5rem;
height: var(--wrn-control-height, 2.5rem);
min-width: 0;
padding: 0;
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-sm);
color: var(--wrn-color-text);
background: var(--wrn-color-bg);
font: inherit;
font-size: clamp(1rem, 3vw, 1.25rem);
font-weight: 750;
font-size: 1rem;
font-weight: 600;
text-align: center;
caret-color: var(--wrn-component-color);
transition:
@@ -229,9 +232,9 @@ component PinInput {
}
.wrn-next--pin-input .wrn-next__pin-cells input {
flex-basis: min(2.7rem, 12vw);
width: min(2.7rem, 12vw);
min-width: min(2.7rem, 12vw);
height: min(3rem, 14vw);
width: min(2.5rem, 10vw);
min-width: 0;
height: var(--wrn-control-height, 2.5rem);
}
}
+5 -5
View File
@@ -248,12 +248,12 @@ component Popover {
align-items: center;
justify-content: center;
gap: 0.55rem;
min-height: 2.55rem;
min-height: var(--wrn-control-height, 2.5rem);
padding: 0.65rem 0.9rem;
color: var(--wrn-color-text);
background: var(--wrn-color-surface-raised);
border: 1px solid var(--wrn-color-border);
border-radius: 0.78rem;
border-radius: var(--wrn-radius-sm);
font: inherit;
font-size: 0.84rem;
font-weight: 600;
@@ -276,13 +276,13 @@ component Popover {
}
.wrn-popover[data-size="sm"] .wrn-popover__trigger {
min-height: 2.2rem;
min-height: var(--wrn-control-height-sm, 2rem);
padding: 0.5rem 0.72rem;
font-size: 0.78rem;
}
.wrn-popover[data-size="lg"] .wrn-popover__trigger {
min-height: 2.9rem;
min-height: var(--wrn-control-height-lg, 2.75rem);
padding: 0.78rem 1.05rem;
font-size: 0.9rem;
}
@@ -309,7 +309,7 @@ component Popover {
linear-gradient(145deg, color-mix(in srgb, var(--popover-accent) 5%, transparent), transparent 62%),
color-mix(in srgb, var(--wrn-color-surface-raised) 97%, transparent);
border: 1px solid color-mix(in srgb, var(--popover-accent) 18%, var(--wrn-color-border));
border-radius: 1rem;
border-radius: var(--wrn-radius-md);
box-shadow:
0 1px 0 color-mix(in srgb, white 5%, transparent) inset,
0 24px 64px color-mix(in srgb, black 22%, transparent);
+10 -15
View File
@@ -121,20 +121,15 @@ component PortalDashboard {
.wrn-portal-dashboard__hero {
display: flex;
padding: clamp(1.25rem, 3vw, 2rem);
padding: 0 0 1.25rem;
align-items: flex-end;
justify-content: space-between;
gap: 1rem;
border: 1px solid var(--wrn-color-border);
border-radius: 1rem;
background:
radial-gradient(
circle at 85% 0%,
color-mix(in srgb, var(--wrn-color-primary) 20%, transparent),
transparent 34%
),
var(--wrn-color-surface);
box-shadow: var(--wrn-shadow-sm);
border: 0;
border-bottom: 1px solid var(--wrn-color-border);
border-radius: 0;
background: transparent;
box-shadow: none;
}
.wrn-portal-dashboard__eyebrow,
@@ -148,7 +143,7 @@ component PortalDashboard {
.wrn-portal-dashboard__hero h1 {
margin: 0.35rem 0 0;
font-size: clamp(1.4rem, 3vw, 2.25rem);
font-size: clamp(1.375rem, 2vw, 1.75rem);
font-weight: 650;
letter-spacing: -0.035em;
}
@@ -177,7 +172,7 @@ component PortalDashboard {
border: 1px solid var(--wrn-color-border);
border-radius: 0.85rem;
background: var(--wrn-color-surface);
box-shadow: var(--wrn-shadow-sm);
box-shadow: none;
}
.wrn-portal-dashboard__metric-icon {
@@ -221,9 +216,9 @@ component PortalDashboard {
min-width: 0;
padding: 1.1rem;
border: 1px solid var(--wrn-color-border);
border-radius: 0.9rem;
border-radius: var(--wrn-radius-md);
background: var(--wrn-color-surface);
box-shadow: var(--wrn-shadow-sm);
box-shadow: none;
}
.wrn-portal-dashboard__panel--actions {
@@ -113,7 +113,7 @@ size: string = "default"
justify-content: center;
gap: 0.4rem;
border: 1px solid transparent;
border-radius: 0.6rem;
border-radius: var(--wrn-radius-sm, 6px);
color: var(--wrn-color-muted);
cursor: pointer;
list-style: none;
+56 -21
View File
@@ -36,20 +36,52 @@ component RangeSlider {
}
state currentValue = value
functions {
shared function snapValue(raw) {
var increment = Math.max(0.0001, Number(step))
var snapped = Number(min) + Math.round((Number(raw) - Number(min)) / increment) * increment
var bounded = Math.min(Number(max), Math.max(Number(min), snapped))
return Math.round(bounded * 1000000) / 1000000
}
shared function percent() { if (Number(max) === Number(min)) { return 0 } return (Number(currentValue) - Number(min)) / (Number(max) - Number(min)) * 100 }
client function setValue(nextValue, sourceEvent, commit, detail, root, slider, input, outputElement, progressValue) { if (readonly || disabled) { return } currentValue = Number(nextValue); root = sourceEvent.currentTarget.closest(".wrn-next--range-slider"); if (root) { slider = root.querySelector("[role=slider]"); input = root.querySelector("input[name='" + name + "']"); outputElement = root.querySelector("output"); progressValue = (currentValue - Number(min)) / (Number(max) - Number(min)) * 100; if (slider) { slider.setAttribute("aria-valuenow", String(currentValue)); slider.style.setProperty("--wrn-range-progress", progressValue + "%") } if (input) { input.value = String(currentValue) } if (outputElement) { outputElement.textContent = String(currentValue) } } detail = { value: currentValue, min: min, max: max, step: step, name: name, sourceEvent: sourceEvent }; output.input(detail); if (commit) { output.change(detail) } }
client function setValue(nextValue, sourceEvent, commit, detail, root, slider, input, outputElement, progressValue) { if (sourceEvent.currentTarget.closest(".wrn-next--range-slider").dataset.readonly === "true" || sourceEvent.currentTarget.closest(".wrn-next--range-slider").dataset.disabled === "true") { return } currentValue = Number(nextValue); root = sourceEvent.currentTarget.closest(".wrn-next--range-slider"); if (root) { slider = root.querySelector("[role=slider]"); input = root.querySelector("input[name='" + name + "']"); outputElement = root.querySelector("output"); progressValue = (currentValue - Number(min)) / (Number(max) - Number(min)) * 100; if (slider) { slider.setAttribute("aria-valuenow", String(currentValue)); slider.style.setProperty("--wrn-range-progress", progressValue + "%") } if (input) { input.value = String(currentValue) } if (outputElement) { outputElement.textContent = String(currentValue) } } detail = { value: currentValue, min: min, max: max, step: step, name: name, sourceEvent: sourceEvent }; output.input(detail); if (commit) { output.change(detail) } }
client function valueFromPointer(sourceEvent, rect, ratio) { rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); return Number(min) + ratio * (Number(max) - Number(min)) }
client function beginSlide(sourceEvent, rect, ratio, rawValue) { if (readonly || disabled) { return } sourceEvent.currentTarget.setPointerCapture(sourceEvent.pointerId); rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = Number(min) + Math.round((rawValue - Number(min)) / Number(step)) * Number(step); output.input({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
client function moveSlide(sourceEvent, rect, ratio, rawValue) { if (!sourceEvent.currentTarget.hasPointerCapture(sourceEvent.pointerId)) { return } rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = Number(min) + Math.round((rawValue - Number(min)) / Number(step)) * Number(step); output.input({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
client function endSlide(sourceEvent, rect, ratio, rawValue) { rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = Number(min) + Math.round((rawValue - Number(min)) / Number(step)) * Number(step); if (sourceEvent.currentTarget.hasPointerCapture(sourceEvent.pointerId)) { sourceEvent.currentTarget.releasePointerCapture(sourceEvent.pointerId) } output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
client function selectMark(sourceEvent, root, slider, input, outputElement, nextValue, progressValue, detail) { if (readonly || disabled) { return } root = sourceEvent.currentTarget.closest(".wrn-next--range-slider"); slider = root.querySelector(".wrn-next__custom-slider"); input = root.querySelector(".wrn-next__range-value"); outputElement = root.querySelector("output"); nextValue = Number(sourceEvent.currentTarget.dataset.value); currentValue = nextValue; progressValue = (nextValue - Number(min)) / (Number(max) - Number(min)) * 100; slider.setAttribute("aria-valuenow", String(nextValue)); slider.style.setProperty("--wrn-range-progress", progressValue + "%"); input.setAttribute("value", String(nextValue)); outputElement.replaceChildren(String(nextValue)); detail = { value: nextValue, min: min, max: max, step: step, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) }
client function handleKey(sourceEvent) { if (sourceEvent.key === "ArrowRight") { sourceEvent.preventDefault(); currentValue = Math.min(Number(max), Number(currentValue) + Number(step)); output.input({ value: currentValue, name: name, sourceEvent: sourceEvent }); output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) } if (sourceEvent.key === "ArrowLeft") { sourceEvent.preventDefault(); currentValue = Math.max(Number(min), Number(currentValue) - Number(step)); output.input({ value: currentValue, name: name, sourceEvent: sourceEvent }); output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) } if (sourceEvent.key === "Home") { sourceEvent.preventDefault(); currentValue = Number(min); output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) } if (sourceEvent.key === "End") { sourceEvent.preventDefault(); currentValue = Number(max); output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) } }
client function beginSlide(sourceEvent, rect, ratio, rawValue) { if (sourceEvent.currentTarget.closest(".wrn-next--range-slider").dataset.readonly === "true" || sourceEvent.currentTarget.closest(".wrn-next--range-slider").dataset.disabled === "true") { return } sourceEvent.currentTarget.setPointerCapture(sourceEvent.pointerId); rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = snapValue(rawValue); output.input({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
client function moveSlide(sourceEvent, rect, ratio, rawValue) { if (!sourceEvent.currentTarget.hasPointerCapture(sourceEvent.pointerId)) { return } rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = snapValue(rawValue); output.input({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
client function endSlide(sourceEvent, rect, ratio, rawValue) { rect = sourceEvent.currentTarget.getBoundingClientRect(); ratio = Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)); rawValue = Number(min) + ratio * (Number(max) - Number(min)); currentValue = snapValue(rawValue); if (sourceEvent.currentTarget.hasPointerCapture(sourceEvent.pointerId)) { sourceEvent.currentTarget.releasePointerCapture(sourceEvent.pointerId) } output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
client function selectMark(sourceEvent, root, slider, input, outputElement, nextValue, progressValue, detail) { if (sourceEvent.currentTarget.closest(".wrn-next--range-slider").dataset.readonly === "true" || sourceEvent.currentTarget.closest(".wrn-next--range-slider").dataset.disabled === "true") { return } root = sourceEvent.currentTarget.closest(".wrn-next--range-slider"); slider = root.querySelector(".wrn-next__custom-slider"); input = root.querySelector(".wrn-next__range-value"); outputElement = root.querySelector("output"); nextValue = Number(sourceEvent.currentTarget.dataset.value); currentValue = nextValue; progressValue = (nextValue - Number(min)) / (Number(max) - Number(min)) * 100; slider.setAttribute("aria-valuenow", String(nextValue)); slider.style.setProperty("--wrn-range-progress", progressValue + "%"); input.setAttribute("value", String(nextValue)); outputElement.replaceChildren(String(nextValue)); detail = { value: nextValue, min: min, max: max, step: step, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) }
client function handleKey(sourceEvent) {
if (readonly || disabled) { return }
var next = Number(currentValue)
if (sourceEvent.key === "ArrowRight" || sourceEvent.key === "ArrowUp") { next = next + Number(step) }
else if (sourceEvent.key === "ArrowLeft" || sourceEvent.key === "ArrowDown") { next = next - Number(step) }
else if (sourceEvent.key === "Home") { next = Number(min) }
else if (sourceEvent.key === "End") { next = Number(max) }
else { return }
sourceEvent.preventDefault()
currentValue = snapValue(next)
output.input({ value: currentValue, name: name, sourceEvent: sourceEvent })
output.change({ value: currentValue, name: name, sourceEvent: sourceEvent })
}
client function emitFocus(nameEvent, sourceEvent) { output[nameEvent]({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
client function decreaseValue(sourceEvent) { currentValue = Math.max(Number(min), Number(currentValue) - Number(step)); output.input({ value: currentValue, name: name, sourceEvent: sourceEvent }); output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
client function increaseValue(sourceEvent) { currentValue = Math.min(Number(max), Number(currentValue) + Number(step)); output.input({ value: currentValue, name: name, sourceEvent: sourceEvent }); output.change({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
client function decreaseValue(sourceEvent) {
if (readonly || disabled) { return }
var next = Math.max(Number(min), Number(currentValue) - Number(step))
currentValue = Math.round(next * 1000000) / 1000000
output.input({ value: currentValue, name: name, sourceEvent: sourceEvent })
output.change({ value: currentValue, name: name, sourceEvent: sourceEvent })
}
client function increaseValue(sourceEvent) {
if (readonly || disabled) { return }
var next = Math.min(Number(max), Number(currentValue) + Number(step))
currentValue = Math.round(next * 1000000) / 1000000
output.input({ value: currentValue, name: name, sourceEvent: sourceEvent })
output.change({ value: currentValue, name: name, sourceEvent: sourceEvent })
}
}
view {
<div {...attrs} class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--field wrn-next--range-slider {class}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}">
<div {...attrs} class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--field wrn-next--range-slider {class}" data-readonly="{readonly}" data-disabled="{disabled}" data-variant="{variant}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}">
<div class="wrn-next__field-heading"><label class="{hiddenLabel ? 'wrn-next__sr-only' : ''}" for="{id || name}">{label}</label>{#if cornerHint}<span class="wrn-next__field-hint">{cornerHint}</span>{/if}</div>
<div class="wrn-next__range-control" data-variant="{variant}" data-icon-position="{iconPosition}" data-show-steps="{showSteps}">
{#if icon}<span class="{icon}" aria-hidden="true"></span>{/if}
@@ -70,7 +102,10 @@ component RangeSlider {
}
style {
.wrn-next__range-control { align-items:center; padding-bottom:1.5rem; }
.wrn-next__range-bounds { position:absolute; top:calc(100% + .25rem); left:0; right:0; }
.wrn-next__range-track {
position:relative;
display: grid;
flex: 1;
min-width: 0;
@@ -311,10 +346,10 @@ component RangeSlider {
}
.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);
@@ -328,9 +363,9 @@ component RangeSlider {
}
.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);
@@ -372,7 +407,7 @@ component RangeSlider {
.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 {
@@ -386,10 +421,10 @@ component RangeSlider {
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 {
@@ -497,7 +532,7 @@ component RangeSlider {
}
.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 {
@@ -506,7 +541,7 @@ component RangeSlider {
.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"] {
@@ -655,7 +690,7 @@ component RangeSlider {
.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;
+37 -85
View File
@@ -11,98 +11,50 @@ component Rating {
description: string = ""
items: unknown[] = []
variant: string = "default"
value: number = 0
max: number = 5
step: number = 1
readonly: boolean = false
disabled: boolean = false
class: string = ""
}
state selectedValue = value
functions {
shared function stars() { return Array.from({ length: Math.max(1, Math.min(10, max)) }) }
client function choose(index, sourceEvent) {
if (readonly || disabled) { return }
var rect = sourceEvent.currentTarget.getBoundingClientRect()
var fraction = sourceEvent.detail > 0 && rect.width ? Math.max(0, Math.min(1, (sourceEvent.clientX - rect.left) / rect.width)) : 1
var increment = Math.max(0.01, Number(step))
var next = Math.ceil((index + fraction) / increment) * increment
selectedValue = Number(Math.min(max, Math.max(0, next)).toFixed(2))
output.input({ value: selectedValue, sourceEvent: sourceEvent })
output.change({ value: selectedValue, sourceEvent: sourceEvent })
}
}
view {
<section class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--rating wrn-next--variant-{variant} {class}">
<div {...attrs} class="wrn-rating {class}">
{#if title}<strong>{title}</strong>{/if}
{#if description}<p>{description}</p>{/if}
{#if items}<div class="wrn-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
<slot />
</section>
<div role="group" aria-label="{title || 'Rating'}" class="wrn-rating__stars">
{#each stars() as star, index}<button type="button" aria-label="Rate {index + 1} of {max}" aria-pressed="{selectedValue === index + 1}" disabled="{disabled || readonly}" data-filled="{index < selectedValue}" style="--fill:{Math.max(0, Math.min(1, selectedValue - index)) * 100}%" @click="choose(index, event)"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m12 3 2.8 5.7 6.2.9-4.5 4.4 1.1 6.2-5.6-3-5.6 3 1.1-6.2L3 9.6l6.2-.9Z" /></svg><svg class="wrn-rating__fill" viewBox="0 0 24 24" aria-hidden="true"><path d="m12 3 2.8 5.7 6.2.9-4.5 4.4 1.1 6.2-5.6-3-5.6 3 1.1-6.2L3 9.6l6.2-.9Z" /></svg></button>{/each}
<input class="wrn-rating__precise" type="range" aria-label="Precise rating" min="0" max="{max}" step="{step}" value="{selectedValue}" disabled="{disabled || readonly}" @input="selectedValue = Number(event.currentTarget.value); output.input({ value: selectedValue })" @change="output.change({ value: selectedValue })" /><output aria-live="polite">{selectedValue} / {max}</output>
</div><slot />
</div>
}
style {
.wrn-next--rating {
display: grid;
gap: 0.75rem;
padding: 1rem;
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius);
color: var(--wrn-color-text);
background: var(--wrn-color-surface);
}
.wrn-next--rating > p { margin: 0; color: var(--wrn-color-muted); }
.wrn-next--rating > .wrn-next__items { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.wrn-next--rating > .wrn-next__items > span { padding: 0.35rem 0.6rem; border-radius: var(--wrn-radius-sm); background: var(--wrn-color-surface-2); }
/* Shared component foundation. */
.wrn-component {
--wrn-component-color: var(--wrn-color-primary);
--wrn-component-scale: 1;
margin: 0;
color: var(--wrn-color-text);
font-family: var(--wrn-font-sans, inherit);
}
.wrn-component--color-primary {
--wrn-component-color: var(--wrn-color-primary);
}
.wrn-component--color-secondary {
--wrn-component-color: var(--wrn-color-secondary);
}
.wrn-component--color-success {
--wrn-component-color: var(--wrn-color-success);
}
.wrn-component--color-warning {
--wrn-component-color: var(--wrn-color-warning);
}
.wrn-component--color-danger {
--wrn-component-color: var(--wrn-color-danger);
}
.wrn-component--color-info {
--wrn-component-color: var(--wrn-color-info);
}
.wrn-component--size-xs {
--wrn-component-scale: 0.78;
}
.wrn-component--size-sm {
--wrn-component-scale: 0.88;
}
.wrn-component--size-default,
.wrn-component--size-md {
--wrn-component-scale: 1;
}
.wrn-component--size-lg {
--wrn-component-scale: 1.14;
}
.wrn-component--size-xl {
--wrn-component-scale: 1.28;
}
.wrn-component:not(.wrn-btn) {
font-size: calc(1em * var(--wrn-component-scale));
}
.wrn-component :is(a, button, input, select, textarea):focus-visible {
outline-color: var(--wrn-component-color);
}
.wrn-component p {
margin: 0;
color: var(--wrn-color-muted);
line-height: 1.6;
}
.wrn-rating__precise { position:absolute; opacity:0; width:1px; height:1px; }
.wrn-rating__precise:focus-visible { position:static; opacity:1; width:5rem; height:auto; accent-color:var(--wrn-color-primary); }
.wrn-rating { display:grid; gap:.6rem; color:var(--wrn-color-text); }
.wrn-rating p { margin:0; color:var(--wrn-color-muted); font-size:.875rem; }
.wrn-rating__stars { display:flex; align-items:center; gap:.25rem; }
.wrn-rating button { display:grid; place-items:center; width:2rem; height:2rem; padding:.2rem; border:0; background:transparent; color:var(--wrn-color-muted); cursor:pointer; }
.wrn-rating svg { width:1.5rem; height:1.5rem; fill:none; stroke:currentColor; stroke-width:1.5; }
.wrn-rating button[data-filled="true"] { color:var(--wrn-color-warning-text); }
.wrn-rating svg { grid-area:1 / 1; }
.wrn-rating .wrn-rating__fill { fill:currentColor; clip-path:inset(0 calc(100% - var(--fill)) 0 0); }
.wrn-rating output { margin-inline-start:.5rem; font-size:.8rem; color:var(--wrn-color-muted); }
.wrn-rating button:focus-visible { outline:2px solid var(--wrn-color-focus); outline-offset:2px; border-radius:4px; }
}
}
+1
View File
@@ -82,6 +82,7 @@ component SearchBox {
}
style {
input[type="search"]::-webkit-search-cancel-button { -webkit-appearance:none; appearance:none; }
.wrn-search-box { width: 100%; }
.wrn-search-box__label {
display: block;
+9 -9
View File
@@ -50,7 +50,7 @@ component SectionHeader {
style {
.wrn-section-header {
--section-header-accent: var(--wrn-color-primary);
--section-header-title: 1.875rem;
--section-header-title: 1.25rem;
display: flex;
flex-direction: column;
gap: 1.25rem;
@@ -131,7 +131,7 @@ component SectionHeader {
color: var(--section-header-accent);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.18em;
letter-spacing: 0.06em;
text-transform: uppercase;
}
@@ -149,9 +149,9 @@ component SectionHeader {
}
.wrn-section-header__description {
margin: 1rem 0 0;
margin: 0.5rem 0 0;
color: var(--wrn-color-text-muted);
font-size: 1rem;
font-size: 0.875rem;
line-height: 1.7;
}
@@ -185,24 +185,24 @@ component SectionHeader {
* split only becomes a row once there is width for two columns.
*/
.wrn-section-header[data-size="sm"] {
--section-header-title: 1.5rem;
--section-header-title: 1.125rem;
}
.wrn-section-header[data-size="lg"] {
--section-header-title: 2.25rem;
--section-header-title: 1.5rem;
}
@media (min-width: 640px) {
.wrn-section-header {
--section-header-title: 2.25rem;
--section-header-title: 1.25rem;
}
.wrn-section-header[data-size="sm"] {
--section-header-title: 1.75rem;
--section-header-title: 1.125rem;
}
.wrn-section-header[data-size="lg"] {
--section-header-title: 3rem;
--section-header-title: 1.75rem;
}
}
+8 -1
View File
@@ -25,6 +25,7 @@ component Stepper {
size: string = "default"
steps: unknown[] = []
active: number = 0
controlled: boolean = false
orientation: string = "horizontal"
clickable: boolean = false
label: string = "Progress"
@@ -44,6 +45,8 @@ component Stepper {
class: string = ""
}
state currentStep = active
functions {
shared function stepList() {
return Array.isArray(steps) ? steps : []
@@ -54,7 +57,7 @@ component Stepper {
if (count < 1) {
return 0
}
var value = Number(active)
var value = Number(controlled ? active : currentStep)
if (!value || value < 0) {
return 0
}
@@ -100,6 +103,7 @@ component Stepper {
client function goBack() {
var target = Math.max(0, activeIndex() - 1)
output.back({ index: target, step: stepList()[target] || {} })
currentStep = target
output.change({ index: target, step: stepList()[target] || {} })
}
@@ -113,12 +117,14 @@ component Stepper {
}
var target = Math.min(lastIndex(), activeIndex() + 1)
output.next({ index: target, step: stepList()[target] || {} })
currentStep = target
output.change({ index: target, step: stepList()[target] || {} })
}
client function goSkip() {
var target = Math.min(lastIndex(), activeIndex() + 1)
output.skip({ index: target, step: stepList()[target] || {} })
currentStep = target
output.change({ index: target, step: stepList()[target] || {} })
}
@@ -126,6 +132,7 @@ component Stepper {
if (!clickable) {
return
}
currentStep = index
output.change({ index: index, step: step })
}
}
+18 -15
View File
@@ -408,11 +408,14 @@ component StrongPassword {
.wrn-next__strong-password-requirements .wrn-next__strong-password-special {
grid-column: 1 / -1;
white-space: nowrap;
white-space: normal;
flex-wrap: wrap;
}
.wrn-next__strong-password-special [data-text="specialCharactersSet"] {
white-space: nowrap;
white-space: normal;
overflow-wrap: anywhere;
min-width: 0;
}
.wrn-next__strong-password-requirements li > span[aria-hidden="true"] {
@@ -462,7 +465,7 @@ component StrongPassword {
top: calc(100% + 0.7rem);
left: 0;
z-index: 30;
width: min(28rem, calc(100vw - 2rem));
width: min(28rem, 100%);
padding: 1rem;
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius);
@@ -607,10 +610,10 @@ component StrongPassword {
}
.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);
@@ -624,9 +627,9 @@ component StrongPassword {
}
.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);
@@ -668,7 +671,7 @@ component StrongPassword {
.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 {
@@ -682,10 +685,10 @@ component StrongPassword {
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 {
@@ -793,7 +796,7 @@ component StrongPassword {
}
.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 {
@@ -802,7 +805,7 @@ component StrongPassword {
.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"] {
@@ -951,7 +954,7 @@ component StrongPassword {
.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;
+12 -87
View File
@@ -6,98 +6,23 @@ component StyledIcon {
description: string = ""
items: unknown[] = []
variant: string = "default"
icon: string = "icon-[lucide--sparkles]"
label: string = ""
class: string = ""
}
view {
<section class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--styled-icon wrn-next--variant-{variant} {class}">
{#if title}<strong>{title}</strong>{/if}
{#if description}<p>{description}</p>{/if}
{#if items}<div class="wrn-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
<slot />
</section>
<span {...attrs} class="wrn-styled-icon {class}" data-size="{size}" data-color="{color}" data-variant="{variant}" role="{label ? 'img' : 'presentation'}" aria-label="{label}" aria-hidden="{label ? 'false' : 'true'}"><span class="{icon}"></span></span>
}
style {
.wrn-next--styled-icon {
display: grid;
gap: 0.75rem;
padding: 1rem;
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius);
color: var(--wrn-color-text);
background: var(--wrn-color-surface);
}
.wrn-next--styled-icon > p { margin: 0; color: var(--wrn-color-muted); }
.wrn-next--styled-icon > .wrn-next__items { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.wrn-next--styled-icon > .wrn-next__items > span { padding: 0.35rem 0.6rem; border-radius: var(--wrn-radius-sm); background: var(--wrn-color-surface-2); }
/* Shared component foundation. */
.wrn-component {
--wrn-component-color: var(--wrn-color-primary);
--wrn-component-scale: 1;
margin: 0;
color: var(--wrn-color-text);
font-family: var(--wrn-font-sans, inherit);
}
.wrn-component--color-primary {
--wrn-component-color: var(--wrn-color-primary);
}
.wrn-component--color-secondary {
--wrn-component-color: var(--wrn-color-secondary);
}
.wrn-component--color-success {
--wrn-component-color: var(--wrn-color-success);
}
.wrn-component--color-warning {
--wrn-component-color: var(--wrn-color-warning);
}
.wrn-component--color-danger {
--wrn-component-color: var(--wrn-color-danger);
}
.wrn-component--color-info {
--wrn-component-color: var(--wrn-color-info);
}
.wrn-component--size-xs {
--wrn-component-scale: 0.78;
}
.wrn-component--size-sm {
--wrn-component-scale: 0.88;
}
.wrn-component--size-default,
.wrn-component--size-md {
--wrn-component-scale: 1;
}
.wrn-component--size-lg {
--wrn-component-scale: 1.14;
}
.wrn-component--size-xl {
--wrn-component-scale: 1.28;
}
.wrn-component:not(.wrn-btn) {
font-size: calc(1em * var(--wrn-component-scale));
}
.wrn-component :is(a, button, input, select, textarea):focus-visible {
outline-color: var(--wrn-component-color);
}
.wrn-component p {
margin: 0;
color: var(--wrn-color-muted);
line-height: 1.6;
}
.wrn-styled-icon { --icon-color:var(--wrn-color-primary-text); display:inline-grid; place-items:center; width:2.5rem; height:2.5rem; border-radius:var(--wrn-radius-sm); color:var(--icon-color); background:color-mix(in srgb, var(--icon-color) 12%, transparent); }
.wrn-styled-icon > span { width:1.25rem; height:1.25rem; }
.wrn-styled-icon[data-color="success"] { --icon-color:var(--wrn-color-success-text); }
.wrn-styled-icon[data-color="warning"] { --icon-color:var(--wrn-color-warning-text); }
.wrn-styled-icon[data-size="sm"] { width:2rem; height:2rem; }
.wrn-styled-icon[data-size="lg"] { width:3rem; height:3rem; }
.wrn-styled-icon[data-variant="outline"] { border:1px solid currentColor; background:transparent; }
.wrn-styled-icon[data-variant="plain"] { background:transparent; }
}
}
+21 -16
View File
@@ -44,27 +44,28 @@ size: string = "default"
functions {
client function commit(part, nextValue, sourceEvent, next, detail) { if (part === "hour") { selectedHour = String(nextValue).padStart(2, "0") } if (part === "minute") { selectedMinute = String(nextValue).padStart(2, "0") } next = selectedHour + ":" + selectedMinute; if ((min && next < min) || (max && next > max)) { return } currentValue = next; detail = { value: currentValue, hour: selectedHour, minute: selectedMinute, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) }
client function selectTimePart(sourceEvent, root, input, parts, hourValue, minuteValue, next, trigger, detail) { root = sourceEvent.currentTarget.closest(".wrn-next--time-picker"); input = root.querySelector(".wrn-next__time-value"); parts = String(input.value || value || "00:00").split(":"); hourValue = sourceEvent.currentTarget.dataset.part === "hour" ? sourceEvent.currentTarget.dataset.value : parts[0]; minuteValue = sourceEvent.currentTarget.dataset.part === "minute" ? sourceEvent.currentTarget.dataset.value : parts[1]; next = hourValue + ":" + minuteValue; if ((min && next < min) || (max && next > max)) { return } currentValue = next; input.setAttribute("value", next); trigger = root.querySelector(".wrn-next__time-trigger span"); if (trigger) { trigger.replaceChildren(next) } sourceEvent.currentTarget.setAttribute("aria-pressed", "true"); detail = { value: next, hour: hourValue, minute: minuteValue, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) }
client function openPicker(sourceEvent) { expanded = true; output.open({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
client function openPicker(sourceEvent) { expanded = !expanded; output.open({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
client function closePicker(sourceEvent) { expanded = false; output.close({ value: currentValue, name: name, sourceEvent: sourceEvent }) }
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--time-picker {class}" data-variant="{variant}" data-inline="{inline}" data-floating="{variant === 'floating'}" 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--time-picker {class}" data-variant="{variant}" data-inline="{inline}" data-floating="{variant === 'floating'}" data-invalid="{error ? 'true' : 'false'}" data-expanded="{expanded}" @keydown="if (event.key === 'Escape') { closePicker(event) }">
<div class="wrn-next__field-heading"><label class="{hiddenLabel ? 'wrn-next__sr-only' : ''}" for="{id || name}">{label}</label>{#if cornerHint}<span class="wrn-next__field-hint">{cornerHint}</span>{/if}</div>
<div class="wrn-next__field-control" data-icon-position="{iconPosition}">
{#if icon}<span class="{icon} wrn-next__field-icon" aria-hidden="true"></span>{/if}
<input id="{id || name}" class="wrn-next__sr-only wrn-next__time-value" type="text" name="{name}" value="{currentValue}" required="{required}" readonly aria-hidden="true" tabindex="-1" @invalid="handleInvalid(event)" />
<button type="button" class="wrn-next__time-trigger" disabled="{disabled || readonly}" aria-haspopup="dialog" aria-expanded="{expanded}" @click="openPicker(event)" @focus="handleFocus(event)" @blur="handleBlur(event)"><span>{currentValue || placeholder || "Select time"}</span><i class="{icon}" aria-hidden="true"></i></button>
</div>
<div class="wrn-next__time-panel" role="dialog" aria-label="{label}"><div><strong>Hour</strong><div class="wrn-next__time-options">{#each hours as hour}<button type="button" data-part="hour" data-value="{hour}" aria-pressed="{String(hour) === selectedHour}" @click="selectTimePart(event)">{hour}</button>{/each}</div></div><div><strong>Minute</strong><div class="wrn-next__time-options">{#each minutes as minute}<button type="button" data-part="minute" data-value="{minute}" aria-pressed="{String(minute) === selectedMinute}" @click="selectTimePart(event)">{minute}</button>{/each}</div></div><button type="button" class="wrn-next__time-done" @click="closePicker(event)">Done</button></div>
<button type="button" class="wrn-time-dismiss" aria-label="Close time picker" data-show="expanded" @click="closePicker(event)"></button><div data-show="expanded" class="wrn-next__time-panel" role="dialog" aria-label="{label}"><div><strong>Hour</strong><div class="wrn-next__time-options">{#each hours as hour}<button type="button" data-part="hour" data-value="{hour}" aria-pressed="{String(hour) === selectedHour}" @click="selectTimePart(event)">{hour}</button>{/each}</div></div><div><strong>Minute</strong><div class="wrn-next__time-options">{#each minutes as minute}<button type="button" data-part="minute" data-value="{minute}" aria-pressed="{String(minute) === selectedMinute}" @click="selectTimePart(event)">{minute}</button>{/each}</div></div><button type="button" class="wrn-next__time-done" @click="closePicker(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-time-dismiss { position:fixed; inset:0; z-index:39; opacity:0; border:0; }
.wrn-next__time-panel {
grid-template-columns: 1fr 1fr;
}
@@ -195,10 +196,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);
@@ -212,9 +213,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);
@@ -256,7 +257,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 {
@@ -270,10 +271,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 {
@@ -381,7 +382,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 {
@@ -390,7 +391,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"] {
@@ -539,7 +540,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;
@@ -551,9 +552,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;
+1 -1
View File
@@ -659,7 +659,7 @@ component Toaster {
}
.wrn-toaster[data-size="sm"] .wrn-toast {
padding: 0.6rem 0.7rem;
padding: 0.5rem 0.75rem;
font-size: 0.78rem;
}
+1 -1
View File
@@ -34,7 +34,7 @@ component ToggleCount {
}
state selectedValue = value
state selectedValue = value === secondValue ? secondValue : firstValue
functions {
shared function isFirstSelected() {
+18 -18
View File
@@ -9,6 +9,7 @@ component TogglePassword {
size: string = "default"
color: string = "primary"
label: string = "Password"
icon: string = ""
name: string = "password"
value: string = ""
placeholder: string = "Enter your password"
@@ -116,7 +117,7 @@ component TogglePassword {
{/if}
</div>
<div class="wrn-next__password-control">
<span class="wrn-next__password-leading-icon" aria-hidden="true"></span>
{#if icon}<span class="wrn-next__password-leading-icon {icon}" aria-hidden="true"></span>{/if}
<input
{...attrs}
id="{name}-password"
@@ -203,7 +204,7 @@ component TogglePassword {
font-weight: 600;
}
.wrn-next__password-heading :is(a, span) {
.wrn-next__password-heading > :is(a, span) {
color: var(--wrn-component-color);
font-size: 0.75rem;
font-weight: 500;
@@ -241,9 +242,8 @@ component TogglePassword {
.wrn-next__password-control > input {
width: 100%;
min-width: 0;
min-height: 2.75em;
min-height: 3rem;
padding: 0.75rem 3rem 0.75rem 2.75rem;
min-height: var(--wrn-control-height);
padding: 0.55rem 3rem 0.55rem 0.85rem;
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-sm);
outline: 0;
@@ -265,8 +265,8 @@ component TogglePassword {
color: var(--wrn-color-muted);
background: currentColor;
pointer-events: none;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect width='18' height='11' x='3' y='11' rx='2' ry='2'/%3E%3Cpath d='M7 11V7a5 5 0 0 1 10 0v4'/%3E%3C/svg%3E") center / contain no-repeat;
}
.wrn-next__password-control:has(> .wrn-next__password-leading-icon) > input { padding-inline-start: 2.5rem; }
.wrn-next__password-control > input:focus-visible {
border-color: var(--wrn-component-color);
@@ -450,10 +450,10 @@ component TogglePassword {
}
.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);
@@ -467,9 +467,9 @@ component TogglePassword {
}
.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);
@@ -511,7 +511,7 @@ component TogglePassword {
.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 {
@@ -525,10 +525,10 @@ component TogglePassword {
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 {
@@ -636,7 +636,7 @@ component TogglePassword {
}
.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 {
@@ -645,7 +645,7 @@ component TogglePassword {
.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"] {
@@ -794,7 +794,7 @@ component TogglePassword {
.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;
+37 -94
View File
@@ -1,11 +1,4 @@
component WysiwygEditor {
// A chrome shell around a slotted editor, not an editor itself: it renders a
// heading, a description, an optional item strip and a slot. It therefore
// emits none of these outputs on its own -- they forward whatever the slotted
// control raises. Slot a textarea or input and a parent @change or @input
// hears it, because those events bubble to this root; @focus and @blur arrive
// too, since the runtime binds the non-bubbling pair in the capture phase.
// Slot nothing interactive and there is nothing to hear.
outputs {
input(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
change(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
@@ -20,98 +13,48 @@ component WysiwygEditor {
description: string = ""
items: unknown[] = []
variant: string = "default"
value: string = ""
readonly: boolean = false
placeholder: string = "Write something…"
class: string = ""
}
functions {
client function format(command, sourceEvent, root, editor) {
sourceEvent.preventDefault()
root = sourceEvent.currentTarget.closest(".wrn-editor")
editor = root.querySelector("[contenteditable]")
editor.focus()
document.execCommand(command, false, null)
output.input({ value: editor.innerHTML, text: editor.textContent })
output.change({ value: editor.innerHTML, text: editor.textContent })
}
client function edit(sourceEvent) { output.input({ value: sourceEvent.currentTarget.innerHTML, text: sourceEvent.currentTarget.textContent }) }
client function finish(sourceEvent) { output.change({ value: sourceEvent.currentTarget.innerHTML, text: sourceEvent.currentTarget.textContent }); output.blur({ sourceEvent: sourceEvent }) }
}
view {
<section class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--wysiwyg-editor wrn-next--variant-{variant} {class}">
{#if title}<strong>{title}</strong>{/if}
{#if description}<p>{description}</p>{/if}
{#if items}<div class="wrn-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
<slot />
<section {...attrs} class="wrn-editor {class}">
{#if title}<strong>{title}</strong>{/if}{#if description}<p>{description}</p>{/if}
<div class="wrn-editor__surface">
<div role="toolbar" aria-label="Text formatting" class="wrn-editor__toolbar">
<button type="button" disabled="{readonly}" aria-label="Bold" @mousedown="event.preventDefault()" @click="format('bold', event)"><strong>B</strong></button>
<button type="button" disabled="{readonly}" aria-label="Italic" @mousedown="event.preventDefault()" @click="format('italic', event)"><em>I</em></button>
<button type="button" disabled="{readonly}" aria-label="Underline" @mousedown="event.preventDefault()" @click="format('underline', event)"><u>U</u></button>
<button type="button" disabled="{readonly}" aria-label="Bulleted list" @mousedown="event.preventDefault()" @click="format('insertUnorderedList', event)"><span class="icon-[lucide--list]" aria-hidden="true"></span></button>
<button type="button" disabled="{readonly}" aria-label="Undo" @mousedown="event.preventDefault()" @click="format('undo', event)"><span class="icon-[lucide--undo-2]" aria-hidden="true"></span></button>
<button type="button" disabled="{readonly}" aria-label="Strike through" @mousedown="event.preventDefault()" @click="format('strikeThrough', event)">S̶</button><button type="button" disabled="{readonly}" aria-label="Numbered list" @mousedown="event.preventDefault()" @click="format('insertOrderedList', event)">1.</button><button type="button" disabled="{readonly}" aria-label="Align left" @mousedown="event.preventDefault()" @click="format('justifyLeft', event)">⇤</button><button type="button" disabled="{readonly}" aria-label="Align center" @mousedown="event.preventDefault()" @click="format('justifyCenter', event)">↔</button><button type="button" disabled="{readonly}" aria-label="Align right" @mousedown="event.preventDefault()" @click="format('justifyRight', event)">⇥</button><button type="button" disabled="{readonly}" aria-label="Indent" @mousedown="event.preventDefault()" @click="format('indent', event)">→</button><button type="button" disabled="{readonly}" aria-label="Outdent" @mousedown="event.preventDefault()" @click="format('outdent', event)">←</button><button type="button" disabled="{readonly}" aria-label="Remove formatting" @mousedown="event.preventDefault()" @click="format('removeFormat', event)">Tx</button><button type="button" disabled="{readonly}" aria-label="Redo" @mousedown="event.preventDefault()" @click="format('redo', event)">↻</button> </div>
<div contenteditable="{readonly ? 'false' : 'true'}" role="textbox" aria-label="{title || 'Rich text editor'}" aria-multiline="true" aria-readonly="{readonly}" data-placeholder="{placeholder}" class="wrn-editor__content" @input="edit(event)" @blur="finish(event)" @focus="output.focus({ sourceEvent: event })">{value}</div>
</div><slot />
</section>
}
style {
.wrn-next--wysiwyg-editor {
display: grid;
gap: 0.75rem;
padding: 1rem;
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius);
color: var(--wrn-color-text);
background: var(--wrn-color-surface);
}
.wrn-next--wysiwyg-editor > p { margin: 0; color: var(--wrn-color-muted); }
.wrn-next--wysiwyg-editor > .wrn-next__items { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.wrn-next--wysiwyg-editor > .wrn-next__items > span { padding: 0.35rem 0.6rem; border-radius: var(--wrn-radius-sm); background: var(--wrn-color-surface-2); }
/* Shared component foundation. */
.wrn-component {
--wrn-component-color: var(--wrn-color-primary);
--wrn-component-scale: 1;
margin: 0;
color: var(--wrn-color-text);
font-family: var(--wrn-font-sans, inherit);
}
.wrn-component--color-primary {
--wrn-component-color: var(--wrn-color-primary);
}
.wrn-component--color-secondary {
--wrn-component-color: var(--wrn-color-secondary);
}
.wrn-component--color-success {
--wrn-component-color: var(--wrn-color-success);
}
.wrn-component--color-warning {
--wrn-component-color: var(--wrn-color-warning);
}
.wrn-component--color-danger {
--wrn-component-color: var(--wrn-color-danger);
}
.wrn-component--color-info {
--wrn-component-color: var(--wrn-color-info);
}
.wrn-component--size-xs {
--wrn-component-scale: 0.78;
}
.wrn-component--size-sm {
--wrn-component-scale: 0.88;
}
.wrn-component--size-default,
.wrn-component--size-md {
--wrn-component-scale: 1;
}
.wrn-component--size-lg {
--wrn-component-scale: 1.14;
}
.wrn-component--size-xl {
--wrn-component-scale: 1.28;
}
.wrn-component:not(.wrn-btn) {
font-size: calc(1em * var(--wrn-component-scale));
}
.wrn-component :is(a, button, input, select, textarea):focus-visible {
outline-color: var(--wrn-component-color);
}
.wrn-component p {
margin: 0;
color: var(--wrn-color-muted);
line-height: 1.6;
}
.wrn-editor { display:grid; gap:.65rem; color:var(--wrn-color-text); }
.wrn-editor > p { margin:0; color:var(--wrn-color-muted); font-size:.875rem; }
.wrn-editor__surface { border:1px solid var(--wrn-color-border); border-radius:var(--wrn-radius-md); overflow:hidden; background:var(--wrn-color-surface); }
.wrn-editor__toolbar { display:flex; flex-wrap:wrap; gap:.25rem; padding:.4rem; border-bottom:1px solid var(--wrn-color-border); }
.wrn-editor button { display:grid; place-items:center; width:2rem; height:2rem; border:0; border-radius:4px; background:transparent; color:inherit; cursor:pointer; }
.wrn-editor button:hover { background:var(--wrn-color-surface-2); }
.wrn-editor button span { width:1rem; height:1rem; }
.wrn-editor__content { min-height:10rem; padding:1rem; outline-offset:-2px; font-size:.875rem; line-height:1.65; }
.wrn-editor__content:empty::before { content:attr(data-placeholder); color:var(--wrn-color-muted); pointer-events:none; }
}
}
+15 -11
View File
@@ -152,10 +152,10 @@ component Alert {
.wrn-next--alert-soft .wrn-next__alert-title,
.wrn-next--alert-soft .wrn-next__alert-icon,
.wrn-next--alert-soft .wrn-next__alert-actions a {
color: var(--wrn-alert-color);
color: var(--wrn-alert-ink, var(--wrn-color-primary-text));
}
.wrn-next--alert-solid {
border-color: var(--wrn-alert-color);
border-color: var(--wrn-alert-ink, var(--wrn-color-primary-text));
background: var(--wrn-alert-color);
color: white;
}
@@ -176,13 +176,13 @@ component Alert {
color: #18181b;
}
.wrn-next--alert-bordered {
border-color: var(--wrn-alert-color);
border-color: var(--wrn-alert-ink, var(--wrn-color-primary-text));
background: var(--wrn-color-surface);
}
.wrn-next--alert-bordered .wrn-next__alert-title,
.wrn-next--alert-bordered .wrn-next__alert-icon,
.wrn-next--alert-bordered .wrn-next__alert-actions a {
color: var(--wrn-alert-color);
color: var(--wrn-alert-ink, var(--wrn-color-primary-text));
}
.wrn-next--alert-accent {
border-color: color-mix(in srgb, var(--wrn-alert-color) 35%, var(--wrn-color-border));
@@ -192,7 +192,7 @@ component Alert {
.wrn-next--alert-accent .wrn-next__alert-title,
.wrn-next--alert-accent .wrn-next__alert-icon,
.wrn-next--alert-accent .wrn-next__alert-actions a {
color: var(--wrn-alert-color);
color: var(--wrn-alert-ink, var(--wrn-color-primary-text));
}
.wrn-next--alert {
@@ -249,19 +249,23 @@ component Alert {
}
.wrn-next--alert[data-color="success"] {
--wrn-alert-color: #0f9f8f;
--wrn-alert-color: var(--wrn-color-success);
--wrn-alert-ink: var(--wrn-color-success-text);
}
.wrn-next--alert[data-color="danger"] {
--wrn-alert-color: #dc3545;
--wrn-alert-color: var(--wrn-color-danger);
--wrn-alert-ink: var(--wrn-color-danger-text);
}
.wrn-next--alert[data-color="warning"] {
--wrn-alert-color: #eab308;
--wrn-alert-color: var(--wrn-color-warning);
--wrn-alert-ink: var(--wrn-color-warning-text);
}
.wrn-next--alert[data-color="info"] {
--wrn-alert-color: #2563eb;
--wrn-alert-color: var(--wrn-color-info);
--wrn-alert-ink: var(--wrn-color-info-text);
}
.wrn-next--alert[data-color="dark"] {
@@ -298,7 +302,7 @@ component Alert {
height: 1.25rem;
place-items: center;
margin-top: 0.05rem;
color: var(--wrn-alert-color);
color: var(--wrn-alert-ink, var(--wrn-color-primary-text));
}
.wrn-next__alert-icon > span {
@@ -339,7 +343,7 @@ component Alert {
}
.wrn-next__alert-actions a {
color: var(--wrn-alert-color);
color: var(--wrn-alert-ink, var(--wrn-color-primary-text));
font-size: 0.78rem;
font-weight: 700;
text-decoration: none;
+15 -9
View File
@@ -122,6 +122,7 @@ component Badge {
.wrn-next__badge {
--wrn-badge-color: var(--wrn-color-primary);
--wrn-badge-ink: var(--wrn-color-primary-text);
position: relative;
isolation: isolate;
display: inline-flex;
@@ -136,9 +137,9 @@ component Badge {
border: 1px solid var(--wrn-badge-color);
border-radius: 999px;
background: var(--wrn-badge-color);
box-shadow: 0 4px 12px color-mix(in srgb, var(--wrn-badge-color) 18%, transparent);
box-shadow: none;
font-size: 0.75rem;
font-weight: 750;
font-weight: 600;
line-height: 1;
white-space: nowrap;
}
@@ -166,23 +167,28 @@ component Badge {
}
.wrn-next__badge[data-color="secondary"] {
--wrn-badge-color: #737373;
--wrn-badge-color: var(--wrn-color-secondary);
--wrn-badge-ink: var(--wrn-color-secondary-text);
}
.wrn-next__badge[data-color="success"] {
--wrn-badge-color: #0f9f8f;
--wrn-badge-color: var(--wrn-color-success);
--wrn-badge-ink: var(--wrn-color-success-text);
}
.wrn-next__badge[data-color="info"] {
--wrn-badge-color: #2563eb;
--wrn-badge-color: var(--wrn-color-info);
--wrn-badge-ink: var(--wrn-color-info-text);
}
.wrn-next__badge[data-color="danger"] {
--wrn-badge-color: #ef3340;
--wrn-badge-color: var(--wrn-color-danger);
--wrn-badge-ink: var(--wrn-color-danger-text);
}
.wrn-next__badge[data-color="warning"] {
--wrn-badge-color: #eab308;
--wrn-badge-color: var(--wrn-color-warning);
--wrn-badge-ink: var(--wrn-color-warning-text);
}
.wrn-next__badge[data-color="dark"] {
@@ -199,14 +205,14 @@ component Badge {
}
.wrn-next__badge[data-variant="soft"] {
color: var(--wrn-badge-color);
color: var(--wrn-badge-ink, var(--wrn-badge-color));
border-color: transparent;
background: color-mix(in srgb, var(--wrn-badge-color) 15%, var(--wrn-color-surface));
box-shadow: none;
}
.wrn-next__badge[data-variant="outline"] {
color: var(--wrn-badge-color);
color: var(--wrn-badge-ink, var(--wrn-badge-color));
background: transparent;
box-shadow: none;
}
+7 -12
View File
@@ -210,7 +210,7 @@ label: string = "Button"
padding: 0 0.75rem;
cursor: pointer;
text-decoration: none;
min-height: 2rem;
min-height: var(--wrn-control-height, 2.5rem);
transition:
color var(--wrn-motion-base) var(--wrn-ease-standard),
border-color var(--wrn-motion-base) var(--wrn-ease-standard),
@@ -256,9 +256,7 @@ label: string = "Button"
.wrn-btn--primary {
color: var(--wrn-btn-contrast);
background: var(--wrn-btn-color);
box-shadow:
0 1px 2px color-mix(in srgb, black 20%, transparent),
0 0 0 1px color-mix(in srgb, var(--wrn-btn-color) 72%, transparent);
box-shadow: var(--wrn-shadow-sm);
}
.wrn-btn--variant-default:hover,
.wrn-btn--variant-solid:hover,
@@ -266,10 +264,7 @@ label: string = "Button"
.wrn-btn--solid:hover,
.wrn-btn--primary:hover {
background: color-mix(in srgb, var(--wrn-btn-color) 84%, black);
transform: translateY(-1px);
box-shadow:
0 0.35rem 0.9rem color-mix(in srgb, var(--wrn-btn-color) 20%, transparent),
0 0 0 1px color-mix(in srgb, var(--wrn-btn-color) 82%, transparent);
box-shadow: var(--wrn-shadow-sm);
}
.wrn-btn--variant-outline,
.wrn-btn--outline {
@@ -319,7 +314,7 @@ label: string = "Button"
color: var(--wrn-btn-color);
border-color: color-mix(in srgb, var(--wrn-btn-color) 55%, var(--wrn-color-border));
background: color-mix(in srgb, var(--wrn-btn-color) 15%, var(--wrn-color-surface));
transform: translateY(-2px);
transform: none;
}
.wrn-btn--variant-link,
.wrn-btn--link {
@@ -351,20 +346,20 @@ label: string = "Button"
}
.wrn-btn--size-sm,
.wrn-btn--sm {
min-height: 2.125rem;
min-height: var(--wrn-control-height-sm, 2rem);
padding-inline: 0.625rem;
gap: 0.25rem;
font-size: 0.8rem;
}
.wrn-btn--size-default,
.wrn-btn--md {
min-height: 2.5rem;
min-height: var(--wrn-control-height, 2.5rem);
padding-inline: 0.75rem;
font-size: 0.875rem;
}
.wrn-btn--size-lg,
.wrn-btn--lg {
min-height: 2.75rem;
min-height: var(--wrn-control-height-lg, 2.75rem);
padding-inline: 0.875rem;
font-size: 0.925rem;
}
+12 -12
View File
@@ -306,10 +306,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);
@@ -323,9 +323,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);
@@ -367,7 +367,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 {
@@ -381,10 +381,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 {
@@ -492,7 +492,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 {
@@ -501,7 +501,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"] {
@@ -650,7 +650,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;
+10 -10
View File
@@ -274,10 +274,10 @@ component Input {
}
.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);
@@ -291,9 +291,9 @@ component Input {
}
.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);
@@ -341,7 +341,7 @@ component Input {
.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 {
@@ -489,7 +489,7 @@ component Input {
}
.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 {
@@ -498,7 +498,7 @@ component Input {
.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"] {
@@ -647,7 +647,7 @@ component Input {
.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;
+12 -12
View File
@@ -167,10 +167,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);
@@ -184,9 +184,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);
@@ -228,7 +228,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 {
@@ -242,10 +242,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 {
@@ -353,7 +353,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 {
@@ -362,7 +362,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"] {
@@ -511,7 +511,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;
+14 -14
View File
@@ -51,7 +51,7 @@ size: string = "default"
view {
<div {...attrs} class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--field wrn-next--select {class}" data-variant="{variant}" data-inline="{inline}" data-floating="{variant === 'floating'}" data-invalid="{error || validationError ? 'true' : 'false'}" data-readonly="{readonly}">
<div class="wrn-next__field-heading"><label class="{hiddenLabel ? 'wrn-next__sr-only' : ''}" for="{id || name}">{label}</label>{#if cornerHint}<span class="wrn-next__field-hint">{cornerHint}</span>{/if}</div>
<div class="wrn-next__field-control" data-icon-position="{iconPosition}">
<div class="wrn-next__field-control" data-icon-position="{iconPosition}" data-has-icon="{icon ? 'true' : 'false'}">
{#if icon}<span class="{icon} wrn-next__field-icon" aria-hidden="true"></span>{/if}
<select id="{id || name}" name="{name}" multiple="{multiple}" disabled="{disabled || readonly}" required="{required}" aria-readonly="{readonly}" aria-invalid="{error || validationError ? 'true' : 'false'}" aria-describedby="{error || validationError ? (id || name) + '-error' : (helperText ? (id || name) + '-help' : '')}" @input="emitField('input', event)" @change="emitField('change', event)" @focus="handleFocus(event)" @blur="handleBlur(event)" @invalid="handleInvalid(event)">
{#if !multiple}<option value="" selected="{value === ''}" disabled="{required}">{placeholder}</option>{/if}
@@ -69,7 +69,7 @@ size: string = "default"
.wrn-next--select select {
appearance: none;
width: 100%;
min-height: 2.65rem;
min-height: var(--wrn-control-height, 2.5rem);
margin: 0;
padding-inline-end: 2.5rem;
line-height: 1.35;
@@ -194,10 +194,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);
@@ -211,9 +211,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);
@@ -255,7 +255,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 {
@@ -269,10 +269,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 {
@@ -380,7 +380,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 {
@@ -389,7 +389,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"] {
@@ -538,7 +538,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;
+16 -15
View File
@@ -28,14 +28,15 @@ component Switch {
required: boolean = false
class: string = ""
}
state currentChecked = checked
functions {
client function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); output[nameEvent]({ checked: sourceEvent.currentTarget.checked, value: value, name: name, sourceEvent: sourceEvent }) }
client function emitField(nameEvent, sourceEvent) { sourceEvent.stopPropagation(); currentChecked = sourceEvent.currentTarget.checked; output[nameEvent]({ checked: sourceEvent.currentTarget.checked, value: value, name: name, sourceEvent: sourceEvent }) }
client function preventReadonly(sourceEvent) { if (readonly) sourceEvent.preventDefault() }
}
view {
<div {...attrs} class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--choice-field {class}" data-inline="{inline}" data-invalid="{error ? 'true' : 'false'}">
<div class="wrn-next__field-heading">{#if cornerHint}<span class="{hiddenLabel ? 'wrn-next__sr-only' : ''}">{label}</span><span class="wrn-next__field-hint">{cornerHint}</span>{/if}</div>
<label class="wrn-next__choice wrn-next--switch" for="{id || name}" data-variant="{variant}" data-icon-position="{iconPosition}"><input id="{id || name}" type="checkbox" role="switch" name="{name}" value="{value}" checked="{checked}" disabled="{disabled}" required="{required}" readonly="{readonly}" aria-checked="{checked ? 'true' : 'false'}" aria-invalid="{error ? 'true' : 'false'}" @click="preventReadonly(event)" @input="emitField('input', event)" @change="emitField('change', event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" />{#if icon}<span class="{icon}" aria-hidden="true"></span>{/if}<span class="{hiddenLabel || cornerHint ? 'wrn-next__sr-only' : ''}">{label}</span></label>
<label class="wrn-next__choice wrn-next--switch" data-variant="{variant}" data-icon-position="{iconPosition}"><input id="{id || name}" type="checkbox" role="switch" name="{name}" value="{value}" checked="{currentChecked}" disabled="{disabled}" required="{required}" readonly="{readonly}" aria-checked="{currentChecked ? 'true' : 'false'}" aria-invalid="{error ? 'true' : 'false'}" @click="preventReadonly(event)" @input="emitField('input', event)" @change="emitField('change', event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" />{#if icon}<span class="{icon}" aria-hidden="true"></span>{/if}<span class="{hiddenLabel || cornerHint ? 'wrn-next__sr-only' : ''}">{label}</span></label>
{#if helperText}<small class="wrn-next__field-help">{helperText}</small>{/if}<small class="wrn-next__field-error" data-error="{name}">{error}</small>
</div>
}
@@ -53,7 +54,7 @@ component Switch {
}
.wrn-next--switch {
min-height: 2.65rem;
min-height: var(--wrn-control-height, 2.5rem);
justify-content: flex-start;
}
@@ -189,10 +190,10 @@ component Switch {
}
.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);
@@ -206,9 +207,9 @@ component Switch {
}
.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);
@@ -250,7 +251,7 @@ component Switch {
.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 {
@@ -264,10 +265,10 @@ component Switch {
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 {
@@ -375,7 +376,7 @@ component Switch {
}
.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 {
@@ -384,7 +385,7 @@ component Switch {
.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"] {
@@ -533,7 +534,7 @@ component Switch {
.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;
+13 -13
View File
@@ -51,7 +51,7 @@ size: string = "default"
view {
<div {...attrs} class="wrn-component wrn-component--color-{color} wrn-component--size-{size} wrn-next--field wrn-next--textarea {class}" data-variant="{variant}" data-inline="{inline}" data-floating="{variant === 'floating'}" data-invalid="{error || validationError ? 'true' : 'false'}">
<div class="wrn-next__field-heading"><label class="{hiddenLabel ? 'wrn-next__sr-only' : ''}" for="{id || name}">{label}</label>{#if cornerHint}<span class="wrn-next__field-hint">{cornerHint}</span>{/if}</div>
<div class="wrn-next__field-control" data-icon-position="{iconPosition}">
<div class="wrn-next__field-control" data-icon-position="{iconPosition}" data-has-icon="{icon ? 'true' : 'false'}">
{#if icon}<span class="{icon} wrn-next__field-icon" aria-hidden="true"></span>{/if}
<textarea id="{id || name}" name="{name}" value="{value}" placeholder="{variant === 'floating' ? ' ' : placeholder}" rows="{rows}" style="resize: {resize}" readonly="{readonly}" disabled="{disabled}" required="{required}" minlength="{minlength}" maxlength="{maxlength}" aria-invalid="{error || validationError ? 'true' : 'false'}" aria-describedby="{error || validationError ? (id || name) + '-error' : (helperText ? (id || name) + '-help' : '')}" @input="emitField('input', event)" @change="emitField('change', event)" @focus="emitField('focus', event)" @blur="emitField('blur', event)" @invalid="handleInvalid(event)"></textarea>
{#if variant === "floating"}<span class="wrn-next__field-floating-label">{label}</span>{/if}
@@ -171,10 +171,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);
@@ -188,9 +188,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);
@@ -232,7 +232,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 {
@@ -246,10 +246,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 {
@@ -357,7 +357,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 {
@@ -366,7 +366,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"] {
@@ -515,7 +515,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;