fix: resolve label hover highlight first button issue, use radio inputs

This commit is contained in:
2026-02-12 19:08:11 +05:00
parent caf53e1903
commit 5d6159fe29
6 changed files with 98 additions and 42 deletions
+1
View File
@@ -10,6 +10,7 @@
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"lint:css": "stylelint '**/*.{css,svelte}",
"check:css": "node scripts/css-vars.js",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test": "vitest",
"test:ui": "vitest --ui",
+1 -3
View File
@@ -21,7 +21,6 @@
--text-main: oklch(from var(--brand-main) 0.25 0.02 h);
--text-muted: oklch(from var(--brand-main) 0.45 0.02 h);
--text-disabled: oklch(from var(--brand-main) 0.7 0.01 h);
--text-action: oklch(from var(--brand-main) 0.99 0.02 h);
--border-main: oklch(from var(--brand-main) 0.9 0.04 h);
@@ -48,8 +47,7 @@
--action-secondary-hover: oklch(from var(--action-secondary) calc(l + var(--hover-step)) c h);
--text-main: oklch(from var(--brand-main) 0.94 0.01 h);
--text-muted: oklch(from var(--brand-main) 0.75 0.02 h);
--text-disabled: oklch(from var(--brand-main) 0.5 0.02 h);
--text-muted: oklch(from var(--brand-main) 0.75 0.02 h);
--text-action: oklch(from var(--brand-main) 0.12 0.02 h);
--border-main: oklch(from var(--brand-main) 0.22 0.04 h);
+6 -3
View File
@@ -4,17 +4,20 @@
interface Props {
label: string;
children: Snippet;
noLabel?: boolean;
}
let { label, children }: Props = $props();
let { label, children, noLabel = false }: Props = $props();
let tag = $derived(noLabel ? "div" : "label");
</script>
<label class="setting-row">
<svelte:element this={tag} class="setting-row">
<div class="setting-label">{label}</div>
<div class="setting-control">
{@render children()}
</div>
</label>
</svelte:element>
<style>
.setting-row {
+1 -1
View File
@@ -20,7 +20,7 @@
<SettingsRow label="Цвет">
<ColorPicker bind:value={textConfigState.color} />
</SettingsRow>
<SettingsRow label="Выравнивание">
<SettingsRow label="Выравнивание" noLabel={true}>
<Alignment bind:align={textConfigState.align} />
</SettingsRow>
<SettingsRow label="Отступы">
+86 -32
View File
@@ -3,43 +3,97 @@
import TextAlignCenter from "~icons/lucide/text-align-center";
import TextAlignEnd from "~icons/lucide/text-align-end";
import TextAlignStart from "~icons/lucide/text-align-start";
import Button from "./Button.svelte";
interface Props {
align: TextAlignType;
}
let { align = $bindable(TextAlign.LEFT) }: Props = $props();
function onAlignLeft() {
align = TextAlign.LEFT;
}
function onAlignCenter() {
align = TextAlign.CENTER;
}
function onAlignRight() {
align = TextAlign.RIGHT;
}
</script>
<Button
icon={TextAlignStart}
ariaLabel="Set left"
onclick={onAlignLeft}
type={align === TextAlign.LEFT ? "primary" : "outline"}
extra="grow"
/>
<Button
icon={TextAlignCenter}
ariaLabel="Set center"
onclick={onAlignCenter}
type={align === TextAlign.CENTER ? "primary" : "outline"}
extra="grow"
/>
<Button
icon={TextAlignEnd}
ariaLabel="Set right"
onclick={onAlignRight}
type={align === TextAlign.RIGHT ? "primary" : "outline"}
extra="grow"
/>
<div class="alignment-group">
<label class="alignment-item">
<input
type="radio"
name="alignment"
value={TextAlign.LEFT}
class="sr-only"
bind:group={align}
aria-label={`Set ${TextAlign.LEFT}`}
/>
<div class="alignment-button"><TextAlignStart /></div>
</label>
<label class="alignment-item">
<input
type="radio"
name="alignment"
value={TextAlign.CENTER}
class="sr-only"
bind:group={align}
aria-label={`Set ${TextAlign.CENTER}`}
/>
<div class="alignment-button"><TextAlignCenter /></div>
</label>
<label class="alignment-item">
<input
type="radio"
name="alignment"
value={TextAlign.RIGHT}
class="sr-only"
bind:group={align}
aria-label={`Set ${TextAlign.RIGHT}`}
/>
<div class="alignment-button"><TextAlignEnd /></div>
</label>
</div>
<style>
.alignment-group {
display: flex;
overflow: hidden;
width: 100%;
}
.alignment-button {
flex-grow: 1;
border: 1px solid var(--border-strong);
padding: 6px 12px;
cursor: pointer;
color: var(--text-muted);
transition: all 0.2s ease;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.alignment-button:hover {
color: var(--text-main);
background: var(--surface-subtle);
}
.alignment-item {
flex-grow: 1;
& input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
&:first-child .alignment-button {
border-top-left-radius: var(--radius);
border-bottom-left-radius: var(--radius);
}
&:last-child .alignment-button {
border-top-right-radius: var(--radius);
border-bottom-right-radius: var(--radius);
}
}
.alignment-item input:checked + .alignment-button {
background: var(--action-primary);
color: var(--text-action);
}
</style>
+3 -3
View File
@@ -9,7 +9,7 @@ describe("Alignment.svelte", () => {
const user = userEvent.setup();
render(AlignmentTest);
const buttons = screen.getAllByRole("button");
const buttons = screen.getAllByRole("radio");
const stateDisplay = screen.getByTestId("align-value");
const allButtonsToClick = [...buttons, buttons[0]];
@@ -38,7 +38,7 @@ describe("Alignment.svelte", () => {
await rerender({ align: TextAlign.CENTER });
const centerBtn = screen.getByRole("button", { name: new RegExp(TextAlign.CENTER, "i") });
expect(centerBtn).toHaveClass("active");
const centerBtn = screen.getByRole("radio", { name: new RegExp(TextAlign.CENTER, "i") });
expect(centerBtn).toBeChecked();
});
});