-
Notifications
You must be signed in to change notification settings - Fork 4
Feat/radio input #176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/radio input #176
Changes from 3 commits
250dba1
f8720aa
dcdd7d4
4249ed2
504be58
eb442b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,54 +1,62 @@ | ||
import { Input } from '..'; | ||
import { Input } from ".."; | ||
|
||
export default { | ||
title: 'UI/Input', | ||
component: Input, | ||
tags: ['autodocs'], | ||
render: (args: { type: string; placeholder: string }) => ({ | ||
Component: Input, | ||
props: args | ||
}) | ||
title: "UI/Input", | ||
component: Input, | ||
tags: ["autodocs"], | ||
render: (args: { type: string; placeholder: string }) => ({ | ||
Component: Input, | ||
props: args, | ||
}), | ||
}; | ||
|
||
export const Text = { | ||
args: { | ||
type: 'text', | ||
placeholder: 'Joe Biden' | ||
} | ||
args: { | ||
type: "text", | ||
placeholder: "Joe Biden", | ||
}, | ||
}; | ||
|
||
export const Tel = { | ||
args: { | ||
type: 'tel', | ||
placeholder: '987654321' | ||
} | ||
args: { | ||
type: "tel", | ||
placeholder: "987654321", | ||
}, | ||
}; | ||
|
||
export const NumberInput = { | ||
args: { | ||
type: 'number', | ||
placeholder: 'Enter something' | ||
} | ||
args: { | ||
type: "number", | ||
placeholder: "Enter something", | ||
}, | ||
}; | ||
|
||
export const Email = { | ||
args: { | ||
type: 'email', | ||
placeholder: '[email protected]' | ||
} | ||
args: { | ||
type: "email", | ||
placeholder: "[email protected]", | ||
}, | ||
}; | ||
|
||
export const Invalid = { | ||
args: { | ||
type: 'email', | ||
placeholder: 'Invalid email', | ||
value: 'not-an-email' | ||
} | ||
args: { | ||
type: "email", | ||
placeholder: "Invalid email", | ||
value: "not-an-email", | ||
}, | ||
}; | ||
|
||
export const Password = { | ||
args: { | ||
type: 'password', | ||
placeholder: 'Please enter password' | ||
} | ||
args: { | ||
type: "password", | ||
placeholder: "Please enter password", | ||
}, | ||
}; | ||
|
||
export const Radio = { | ||
args: { | ||
type: "radio", | ||
value: "option1", | ||
name: "option-1", | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -4,6 +4,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||
interface IInputProps extends HTMLInputAttributes { | ||||||||||||||||||||||||||||||||||||||||||||||
type: HTMLInputTypeAttribute; | ||||||||||||||||||||||||||||||||||||||||||||||
selected?: string; | ||||||||||||||||||||||||||||||||||||||||||||||
name?: string; | ||||||||||||||||||||||||||||||||||||||||||||||
input?: HTMLInputElement; | ||||||||||||||||||||||||||||||||||||||||||||||
value: string | number | any; | ||||||||||||||||||||||||||||||||||||||||||||||
placeholder?: string; | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -13,21 +15,63 @@ | |||||||||||||||||||||||||||||||||||||||||||||
type = 'text', | ||||||||||||||||||||||||||||||||||||||||||||||
input = $bindable(), | ||||||||||||||||||||||||||||||||||||||||||||||
value = $bindable(), | ||||||||||||||||||||||||||||||||||||||||||||||
selected = $bindable(), | ||||||||||||||||||||||||||||||||||||||||||||||
name = '', | ||||||||||||||||||||||||||||||||||||||||||||||
placeholder = '', | ||||||||||||||||||||||||||||||||||||||||||||||
...restProps | ||||||||||||||||||||||||||||||||||||||||||||||
}: IInputProps = $props(); | ||||||||||||||||||||||||||||||||||||||||||||||
const cbase = $derived( | ||||||||||||||||||||||||||||||||||||||||||||||
'w-full bg-grey py-3.5 px-6 text-[15px] text-black-800 font-geist font-normal placeholder:text-black-600 rounded-4xl outline-0 border border-transparent invalid:border-red invalid:text-red focus:invalid:text-black-800 focus:invalid:border-transparent' | ||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||
let radioElement: HTMLInputElement | null = $state(null); | ||||||||||||||||||||||||||||||||||||||||||||||
const typeClasses: Record<string, string> = { | ||||||||||||||||||||||||||||||||||||||||||||||
radio: 'opacity-100' | ||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||
const cbase = $derived({ | ||||||||||||||||||||||||||||||||||||||||||||||
common: 'w-full bg-grey py-3.5 px-6 text-[15px] text-black-800 font-geist font-normal placeholder:text-black-600 rounded-4xl outline-0 border border-transparent invalid:border-red invalid:text-red focus:invalid:text-black-800 focus:invalid:border-transparent', | ||||||||||||||||||||||||||||||||||||||||||||||
type: typeClasses[type] | ||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||
const radioCustomStyles = $derived({ | ||||||||||||||||||||||||||||||||||||||||||||||
common: "before:h-4.5 before:w-4.5 before:border-brand-burnt-orange before:-left-0.75 before:-bottom-0.25 relative before:absolute before:rounded-full before:border-2 before:bg-white before:content-['']", | ||||||||||||||||||||||||||||||||||||||||||||||
selected: | ||||||||||||||||||||||||||||||||||||||||||||||
'after:h-2.5 after:w-2.5 after:bg-brand-burnt-orange after:absolute after:bottom-0.75 after:left-0.25 after:rounded-full' | ||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||
</script> | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
<input | ||||||||||||||||||||||||||||||||||||||||||||||
{...restProps} | ||||||||||||||||||||||||||||||||||||||||||||||
{type} | ||||||||||||||||||||||||||||||||||||||||||||||
{placeholder} | ||||||||||||||||||||||||||||||||||||||||||||||
bind:value | ||||||||||||||||||||||||||||||||||||||||||||||
bind:this={input} | ||||||||||||||||||||||||||||||||||||||||||||||
class={cn([cbase, restProps.class].join(' '))} | ||||||||||||||||||||||||||||||||||||||||||||||
tabindex="0" | ||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||
{#if type === 'radio'} | ||||||||||||||||||||||||||||||||||||||||||||||
<div | ||||||||||||||||||||||||||||||||||||||||||||||
class={cn( | ||||||||||||||||||||||||||||||||||||||||||||||
[radioCustomStyles.common, selected === value ? radioCustomStyles.selected : ''].join( | ||||||||||||||||||||||||||||||||||||||||||||||
' ' | ||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||
)} | ||||||||||||||||||||||||||||||||||||||||||||||
aria-checked={selected === value} | ||||||||||||||||||||||||||||||||||||||||||||||
role="radio" | ||||||||||||||||||||||||||||||||||||||||||||||
tabindex="0" | ||||||||||||||||||||||||||||||||||||||||||||||
onclick={() => radioElement?.click()} | ||||||||||||||||||||||||||||||||||||||||||||||
onkeypress={() => radioElement?.click()} | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||||||||||||||||||
<input | ||||||||||||||||||||||||||||||||||||||||||||||
{...restProps} | ||||||||||||||||||||||||||||||||||||||||||||||
type="radio" | ||||||||||||||||||||||||||||||||||||||||||||||
{value} | ||||||||||||||||||||||||||||||||||||||||||||||
bind:group={selected} | ||||||||||||||||||||||||||||||||||||||||||||||
bind:this={radioElement} | ||||||||||||||||||||||||||||||||||||||||||||||
{name} | ||||||||||||||||||||||||||||||||||||||||||||||
checked={selected === value} | ||||||||||||||||||||||||||||||||||||||||||||||
class={cn([cbase.common, cbase.type, restProps.class].join(' '))} | ||||||||||||||||||||||||||||||||||||||||||||||
tabindex="0" | ||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||
|
<input | |
{...restProps} | |
type="radio" | |
{value} | |
bind:group={selected} | |
bind:this={radioElement} | |
{name} | |
checked={selected === value} | |
class={cn([cbase.common, cbase.type, restProps.class].join(' '))} | |
tabindex="0" | |
/> | |
<input | |
{...restProps} | |
type="radio" | |
{value} | |
bind:group={selected} | |
bind:this={hiddenRadioRef} | |
{name} | |
class={cn([cbase.common, cbase.type, restProps.class].join(' '))} | |
tabindex="-1" | |
style="position: absolute; opacity: 0; pointer-events: none;" | |
/> |
🤖 Prompt for AI Agents
In platforms/metagram/src/lib/ui/Input/Input.svelte around lines 51 to 61, fix
the radio input accessibility by removing the redundant checked attribute since
bind:group already manages selection, remove tabindex="0" from the radio input
to avoid duplicate focusable elements, and ensure the hidden input element is
not focusable by removing or setting tabindex to -1. This will correct tab order
confusion and improve accessibility.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider adding ARIA labelledby or label association.
The custom radio implementation lacks proper label association, which is crucial for screen readers. Consider adding support for labels or ARIA attributes.
Add label support to the interface and implementation:
interface IInputProps extends HTMLInputAttributes {
type: HTMLInputTypeAttribute;
selected?: string;
name?: string;
+ label?: string;
+ id?: string;
}
Then in the radio rendering:
<div
class={cn(
[radioCustomStyles.common, selected === value ? radioCustomStyles.selected : ''].join(' ')
)}
aria-checked={selected === value}
role="radio"
+ aria-labelledby={id ? `${id}-label` : undefined}
tabindex="0"
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In platforms/metagram/src/lib/ui/Input/Input.svelte between lines 38 and 62, the
custom radio input lacks proper label association for accessibility. To fix
this, add a label prop to the component interface and associate the radio input
with a label element or use ARIA attributes like aria-labelledby or aria-label.
Update the radio rendering to wrap the input in a label or link it via
aria-labelledby to ensure screen readers can correctly identify the radio
button's label.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the way i mentioned it, this stays like before, rest goes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uh oh!
There was an error while loading. Please reload this page.