fix(ui): avoid icon spacing for plain inputs
This commit is contained in:
1 parent
6dea64b84c
commit
b1064d442a
4 files changed
+75
-6
No files matched your search
@@ -78,6 +78,7 @@ component Input {
|
||||
data-inline="{inline}"
|
||||
data-floating="{variant === 'floating'}"
|
||||
data-invalid="{error || validationError ? 'true' : 'false'}"
|
||||
data-input-type="{type}"
|
||||
>
|
||||
<div
|
||||
class="wrn-next__field-heading"
|
||||
@@ -99,10 +100,11 @@ component Input {
|
||||
<div
|
||||
class="wrn-next__field-control"
|
||||
data-icon-position="{iconPosition}"
|
||||
data-has-icon="{icon || type === 'date' || type === 'datetime-local' || type === 'time' || type === 'month' || type === 'week' ? 'true' : 'false'}"
|
||||
>
|
||||
{#if icon}
|
||||
{#if icon || type === "date" || type === "datetime-local" || type === "time" || type === "month" || type === "week"}
|
||||
<span
|
||||
class="{icon} wrn-next__field-icon"
|
||||
class="{icon || (type === 'time' ? 'icon-[lucide--clock-3]' : type === 'datetime-local' ? 'icon-[lucide--calendar-clock]' : 'icon-[lucide--calendar-days]')} wrn-next__field-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</span>
|
||||
@@ -154,6 +156,7 @@ component Input {
|
||||
id="{(id || name) + '-error'}"
|
||||
class="wrn-next__field-error"
|
||||
data-error="{name}"
|
||||
data-empty="{error || validationError ? 'false' : 'true'}"
|
||||
aria-live="polite"
|
||||
>
|
||||
{error || validationError}
|
||||
@@ -308,6 +311,12 @@ component Input {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
/* Input is a block-level control. Shared field sizing must not cap it when
|
||||
it participates in a grid or flex layout owned by the consumer. */
|
||||
.wrn-next--field.wrn-next--input {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
.wrn-next--field.wrn-next--size-sm,
|
||||
.wrn-next--choice-field.wrn-next--size-sm {
|
||||
font-size: 0.8125rem;
|
||||
@@ -346,10 +355,29 @@ component Input {
|
||||
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--input[data-input-type="date"] .wrn-next__field-control,
|
||||
.wrn-next--input[data-input-type="datetime-local"] .wrn-next__field-control,
|
||||
.wrn-next--input[data-input-type="time"] .wrn-next__field-control,
|
||||
.wrn-next--input[data-input-type="month"] .wrn-next__field-control,
|
||||
.wrn-next--input[data-input-type="week"] .wrn-next__field-control {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
.wrn-next--input :is(input[type="date"], input[type="datetime-local"], input[type="time"], input[type="month"], input[type="week"]) {
|
||||
min-width: 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.wrn-next--input :is(input[type="date"], input[type="datetime-local"], input[type="time"], input[type="month"], input[type="week"])::-webkit-calendar-picker-indicator {
|
||||
width: 1.1rem;
|
||||
height: 1.1rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
opacity: 0.72;
|
||||
}
|
||||
.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 {
|
||||
@@ -433,6 +461,10 @@ component Input {
|
||||
.wrn-next__field-error:empty {
|
||||
display: none;
|
||||
}
|
||||
.wrn-next__field-error[data-empty="true"] {
|
||||
display: none;
|
||||
min-height: 0;
|
||||
}
|
||||
.wrn-next--field[data-invalid="true"] :is(input, textarea, select),
|
||||
.wrn-next--choice-field[data-invalid="true"] input {
|
||||
border-color: var(--wrn-color-danger);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.8.66",
|
||||
"version": "0.8.70",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
|
||||
@@ -1294,6 +1294,10 @@ test("basic form fields expose shared labels, variants, states, hints, values, a
|
||||
expect(validation.error).toBe("Enter a valid email address.");
|
||||
|
||||
const source = readFileSync(uiComponentPath("Input"), "utf8");
|
||||
const sharedFieldRule = source.lastIndexOf(".wrn-next--field,");
|
||||
const inputWidthRule = source.lastIndexOf(".wrn-next--field.wrn-next--input");
|
||||
expect(inputWidthRule).toBeGreaterThan(sharedFieldRule);
|
||||
expect(source.slice(inputWidthRule, inputWidthRule + 140)).toContain("max-width: none");
|
||||
const dom = mountHtml(
|
||||
await renderComponent(source, {
|
||||
id: "account-email",
|
||||
@@ -1317,6 +1321,7 @@ test("basic form fields expose shared labels, variants, states, hints, values, a
|
||||
expect(root.dataset.variant).toBe("floating");
|
||||
expect(root.dataset.inline).toBe("true");
|
||||
expect(root.dataset.invalid).toBe("true");
|
||||
expect(dom.querySelector(".wrn-next__field-error")?.getAttribute("data-empty")).toBe("false");
|
||||
expect(input.id).toBe("account-email");
|
||||
expect(input.name).toBe("email");
|
||||
expect(input.value).toBe("not-an-email");
|
||||
@@ -1344,6 +1349,38 @@ test("basic form fields expose shared labels, variants, states, hints, values, a
|
||||
expect(emitted).toEqual(["input", "change", "focus", "blur", "keydown", "keyup"]);
|
||||
});
|
||||
|
||||
test("Input does not reserve validation space while its error is empty", async () => {
|
||||
const source = readFileSync(uiComponentPath("Input"), "utf8");
|
||||
const dom = mountHtml(await renderComponent(source, { name: "search", hiddenLabel: true }));
|
||||
expect(dom.querySelector(".wrn-next__field-error")?.getAttribute("data-empty")).toBe("true");
|
||||
expect(source).toContain('.wrn-next__field-error[data-empty="true"]');
|
||||
});
|
||||
|
||||
test("Input only reserves icon space when it renders an icon", async () => {
|
||||
const source = readFileSync(uiComponentPath("Input"), "utf8");
|
||||
const plain = mountHtml(
|
||||
await renderComponent(source, { name: "limit", label: "Limit", placeholder: "Enter limit" }),
|
||||
);
|
||||
const decorated = mountHtml(
|
||||
await renderComponent(source, {
|
||||
name: "email",
|
||||
label: "Email",
|
||||
icon: "icon-[lucide--mail]",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(plain.querySelector(".wrn-next__field-control")?.getAttribute("data-has-icon")).toBe(
|
||||
"false",
|
||||
);
|
||||
expect(plain.querySelector(".wrn-next__field-icon")).toBeNull();
|
||||
expect(decorated.querySelector(".wrn-next__field-control")?.getAttribute("data-has-icon")).toBe(
|
||||
"true",
|
||||
);
|
||||
expect(decorated.querySelector(".wrn-next__field-icon")).not.toBeNull();
|
||||
expect(source).toContain('[data-has-icon="true"][data-icon-position="start"]');
|
||||
expect(source).toContain('[data-has-icon="true"][data-icon-position="end"]');
|
||||
});
|
||||
|
||||
test("basic fields suppress native popups and render package constraint messages", async () => {
|
||||
const inputSource = readFileSync(uiComponentPath("Input"), "utf8");
|
||||
const dom = mountHtml(
|
||||
|
||||
Reference in new issue
Block a user