fix(a11y): label associations for AdminNewsCreate fields

- htmlFor/id pairs for title, slug, read-time, excerpt and featured-image inputs
- add an ariaLabel prop to ColoredItemCombobox (applied to the trigger button) and
  pass the category/tag labels so the comboboxes have an accessible name (WCAG 1.3.1/4.1.2)
This commit is contained in:
c0mrade
2026-05-26 00:20:30 +03:00
parent 1b40c3856d
commit fefc52fb20
2 changed files with 25 additions and 5 deletions

View File

@@ -27,6 +27,7 @@ interface ColoredItemComboboxProps {
onCreateNew: (name: string, color: string) => Promise<ColoredItem>;
onDelete?: (item: ColoredItem) => Promise<void>;
placeholder?: string;
ariaLabel?: string;
isLoading?: boolean;
colors?: string[];
className?: string;
@@ -39,6 +40,7 @@ export function ColoredItemCombobox({
onCreateNew,
onDelete,
placeholder,
ariaLabel,
isLoading = false,
colors = DEFAULT_COLORS,
className,
@@ -190,6 +192,7 @@ export function ColoredItemCombobox({
isOpen ? 'border-accent-500/50' : 'border-dark-700 hover:border-dark-600',
isLoading && 'animate-pulse',
)}
aria-label={ariaLabel}
aria-expanded={isOpen}
aria-haspopup="listbox"
>

View File

@@ -652,8 +652,11 @@ export default function AdminNewsCreate() {
<div className="space-y-5">
{/* Title */}
<div>
<label className="label">{t('news.admin.titleLabel')}</label>
<label htmlFor="news-title" className="label">
{t('news.admin.titleLabel')}
</label>
<input
id="news-title"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
@@ -664,8 +667,11 @@ export default function AdminNewsCreate() {
{/* Slug */}
<div>
<label className="label">{t('news.admin.slugLabel')}</label>
<label htmlFor="news-slug" className="label">
{t('news.admin.slugLabel')}
</label>
<input
id="news-slug"
type="text"
value={slug}
onChange={(e) => {
@@ -688,6 +694,7 @@ export default function AdminNewsCreate() {
onCreateNew={handleCreateCategory}
onDelete={handleDeleteCategory}
placeholder={t('news.admin.combobox.selectCategory')}
ariaLabel={t('news.admin.categoryLabel')}
/>
</div>
<div>
@@ -699,14 +706,18 @@ export default function AdminNewsCreate() {
onCreateNew={handleCreateTag}
onDelete={handleDeleteTag}
placeholder={t('news.admin.combobox.selectTag')}
ariaLabel={t('news.admin.tagLabel')}
/>
</div>
</div>
{/* Read time */}
<div>
<label className="label">{t('news.admin.readTimeLabel')}</label>
<label htmlFor="news-readtime" className="label">
{t('news.admin.readTimeLabel')}
</label>
<input
id="news-readtime"
type="number"
value={readTimeMinutes}
onChange={(e) => setReadTimeMinutes(Number(e.target.value) || 1)}
@@ -718,8 +729,11 @@ export default function AdminNewsCreate() {
{/* Excerpt */}
<div>
<label className="label">{t('news.admin.excerptLabel')}</label>
<label htmlFor="news-excerpt" className="label">
{t('news.admin.excerptLabel')}
</label>
<textarea
id="news-excerpt"
value={excerpt}
onChange={(e) => setExcerpt(e.target.value)}
className="input min-h-[80px] resize-y"
@@ -729,9 +743,12 @@ export default function AdminNewsCreate() {
{/* Featured Image URL */}
<div>
<label className="label">{t('news.admin.imageLabel')}</label>
<label htmlFor="news-image" className="label">
{t('news.admin.imageLabel')}
</label>
<div className="flex items-center gap-2">
<input
id="news-image"
type="text"
value={featuredImageUrl}
onChange={(e) => setFeaturedImageUrl(e.target.value)}