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 ''", "prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"lint:css": "stylelint '**/*.{css,svelte}", "lint:css": "stylelint '**/*.{css,svelte}",
"check:css": "node scripts/css-vars.js",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test": "vitest", "test": "vitest",
"test:ui": "vitest --ui", "test:ui": "vitest --ui",
-2
View File
@@ -21,7 +21,6 @@
--text-main: oklch(from var(--brand-main) 0.25 0.02 h); --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-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); --text-action: oklch(from var(--brand-main) 0.99 0.02 h);
--border-main: oklch(from var(--brand-main) 0.9 0.04 h); --border-main: oklch(from var(--brand-main) 0.9 0.04 h);
@@ -49,7 +48,6 @@
--text-main: oklch(from var(--brand-main) 0.94 0.01 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-muted: oklch(from var(--brand-main) 0.75 0.02 h);
--text-disabled: oklch(from var(--brand-main) 0.5 0.02 h);
--text-action: oklch(from var(--brand-main) 0.12 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); --border-main: oklch(from var(--brand-main) 0.22 0.04 h);
+6 -3
View File
@@ -4,17 +4,20 @@
interface Props { interface Props {
label: string; label: string;
children: Snippet; children: Snippet;
noLabel?: boolean;
} }
let { label, children }: Props = $props(); let { label, children, noLabel = false }: Props = $props();
let tag = $derived(noLabel ? "div" : "label");
</script> </script>
<label class="setting-row"> <svelte:element this={tag} class="setting-row">
<div class="setting-label">{label}</div> <div class="setting-label">{label}</div>
<div class="setting-control"> <div class="setting-control">
{@render children()} {@render children()}
</div> </div>
</label> </svelte:element>
<style> <style>
.setting-row { .setting-row {
+1 -1
View File
@@ -20,7 +20,7 @@
<SettingsRow label="Цвет"> <SettingsRow label="Цвет">
<ColorPicker bind:value={textConfigState.color} /> <ColorPicker bind:value={textConfigState.color} />
</SettingsRow> </SettingsRow>
<SettingsRow label="Выравнивание"> <SettingsRow label="Выравнивание" noLabel={true}>
<Alignment bind:align={textConfigState.align} /> <Alignment bind:align={textConfigState.align} />
</SettingsRow> </SettingsRow>
<SettingsRow label="Отступы"> <SettingsRow label="Отступы">
+83 -29
View File
@@ -3,43 +3,97 @@
import TextAlignCenter from "~icons/lucide/text-align-center"; import TextAlignCenter from "~icons/lucide/text-align-center";
import TextAlignEnd from "~icons/lucide/text-align-end"; import TextAlignEnd from "~icons/lucide/text-align-end";
import TextAlignStart from "~icons/lucide/text-align-start"; import TextAlignStart from "~icons/lucide/text-align-start";
import Button from "./Button.svelte";
interface Props { interface Props {
align: TextAlignType; align: TextAlignType;
} }
let { align = $bindable(TextAlign.LEFT) }: Props = $props(); let { align = $bindable(TextAlign.LEFT) }: Props = $props();
function onAlignLeft() {
align = TextAlign.LEFT;
}
function onAlignCenter() {
align = TextAlign.CENTER;
}
function onAlignRight() {
align = TextAlign.RIGHT;
}
</script> </script>
<Button <div class="alignment-group">
icon={TextAlignStart} <label class="alignment-item">
ariaLabel="Set left" <input
onclick={onAlignLeft} type="radio"
type={align === TextAlign.LEFT ? "primary" : "outline"} name="alignment"
extra="grow" value={TextAlign.LEFT}
class="sr-only"
bind:group={align}
aria-label={`Set ${TextAlign.LEFT}`}
/> />
<Button <div class="alignment-button"><TextAlignStart /></div>
icon={TextAlignCenter} </label>
ariaLabel="Set center"
onclick={onAlignCenter} <label class="alignment-item">
type={align === TextAlign.CENTER ? "primary" : "outline"} <input
extra="grow" type="radio"
name="alignment"
value={TextAlign.CENTER}
class="sr-only"
bind:group={align}
aria-label={`Set ${TextAlign.CENTER}`}
/> />
<Button <div class="alignment-button"><TextAlignCenter /></div>
icon={TextAlignEnd} </label>
ariaLabel="Set right"
onclick={onAlignRight} <label class="alignment-item">
type={align === TextAlign.RIGHT ? "primary" : "outline"} <input
extra="grow" 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(); const user = userEvent.setup();
render(AlignmentTest); render(AlignmentTest);
const buttons = screen.getAllByRole("button"); const buttons = screen.getAllByRole("radio");
const stateDisplay = screen.getByTestId("align-value"); const stateDisplay = screen.getByTestId("align-value");
const allButtonsToClick = [...buttons, buttons[0]]; const allButtonsToClick = [...buttons, buttons[0]];
@@ -38,7 +38,7 @@ describe("Alignment.svelte", () => {
await rerender({ align: TextAlign.CENTER }); await rerender({ align: TextAlign.CENTER });
const centerBtn = screen.getByRole("button", { name: new RegExp(TextAlign.CENTER, "i") }); const centerBtn = screen.getByRole("radio", { name: new RegExp(TextAlign.CENTER, "i") });
expect(centerBtn).toHaveClass("active"); expect(centerBtn).toBeChecked();
}); });
}); });