fix(a11y): add aria-labels to chat message components (#21708)
Add aria-labels, aria-expanded, and semantic improvements to: - RateComment: close button, rating scale, feedback textarea - Citations: toggle button with count, source item buttons - Source/SourceToken: contextual aria-labels for citation buttons - StatusHistory: toggle button with expanded state - WebSearchResults: descriptive favicon alt text - FollowUps: convert div to button element - RegenerateMenu: submit suggestion button - FloatingButtons: action buttons, input field, submit button - CitationModal: close button WCAG: 4.1.2 (Name, Role, Value), 2.1.1 (Keyboard), 1.1.1 (Non-text Content)
This commit is contained in:
@@ -254,6 +254,7 @@
|
|||||||
>
|
>
|
||||||
{#each actions as action}
|
{#each actions as action}
|
||||||
<button
|
<button
|
||||||
|
aria-label={action.label}
|
||||||
class="px-1.5 py-[1px] hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl flex items-center gap-1 min-w-fit transition"
|
class="px-1.5 py-[1px] hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl flex items-center gap-1 min-w-fit transition"
|
||||||
on:click={async () => {
|
on:click={async () => {
|
||||||
selectedText = window.getSelection().toString();
|
selectedText = window.getSelection().toString();
|
||||||
@@ -291,6 +292,7 @@
|
|||||||
id="floating-message-input"
|
id="floating-message-input"
|
||||||
class="ml-5 bg-transparent outline-hidden w-full flex-1 text-sm"
|
class="ml-5 bg-transparent outline-hidden w-full flex-1 text-sm"
|
||||||
placeholder={$i18n.t('Ask a question')}
|
placeholder={$i18n.t('Ask a question')}
|
||||||
|
aria-label={$i18n.t('Ask a question')}
|
||||||
bind:value={floatingInputValue}
|
bind:value={floatingInputValue}
|
||||||
on:keydown={(e) => {
|
on:keydown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
@@ -301,6 +303,7 @@
|
|||||||
|
|
||||||
<div class="ml-1 mr-1">
|
<div class="ml-1 mr-1">
|
||||||
<button
|
<button
|
||||||
|
aria-label={$i18n.t('Submit question')}
|
||||||
class="{floatingInputValue !== ''
|
class="{floatingInputValue !== ''
|
||||||
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
|
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
|
||||||
: 'text-white bg-gray-200 dark:text-gray-900 dark:bg-gray-700 disabled'} transition rounded-full p-1.5 m-0.5 self-center"
|
: 'text-white bg-gray-200 dark:text-gray-900 dark:bg-gray-700 disabled'} transition rounded-full p-1.5 m-0.5 self-center"
|
||||||
|
|||||||
@@ -163,6 +163,8 @@
|
|||||||
<div class=" py-1 -mx-0.5 w-full flex gap-1 items-center flex-wrap">
|
<div class=" py-1 -mx-0.5 w-full flex gap-1 items-center flex-wrap">
|
||||||
<button
|
<button
|
||||||
class="text-xs font-medium text-gray-600 dark:text-gray-300 px-3.5 h-8 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-1 border border-gray-50 dark:border-gray-850/30"
|
class="text-xs font-medium text-gray-600 dark:text-gray-300 px-3.5 h-8 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-1 border border-gray-50 dark:border-gray-850/30"
|
||||||
|
aria-label={citations.length === 1 ? $i18n.t('Toggle 1 source') : $i18n.t('Toggle {{COUNT}} sources', { COUNT: citations.length })}
|
||||||
|
aria-expanded={showCitations}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
showCitations = !showCitations;
|
showCitations = !showCitations;
|
||||||
}}
|
}}
|
||||||
@@ -197,6 +199,7 @@
|
|||||||
{#each citations as citation, idx}
|
{#each citations as citation, idx}
|
||||||
<button
|
<button
|
||||||
id={`source-${id}-${idx + 1}`}
|
id={`source-${id}-${idx + 1}`}
|
||||||
|
aria-label={$i18n.t('View source: {{name}}', { name: decodeString(citation.source.name) })}
|
||||||
class="no-toggle outline-hidden flex dark:text-gray-300 bg-transparent text-gray-600 rounded-xl gap-1.5 items-center"
|
class="no-toggle outline-hidden flex dark:text-gray-300 bg-transparent text-gray-600 rounded-xl gap-1.5 items-center"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
showCitationModal = true;
|
showCitationModal = true;
|
||||||
|
|||||||
@@ -130,6 +130,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="self-center"
|
class="self-center"
|
||||||
|
aria-label={$i18n.t('Close citation modal')}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
show = false;
|
show = false;
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { getContext } from 'svelte';
|
||||||
import { decodeString } from '$lib/utils';
|
import { decodeString } from '$lib/utils';
|
||||||
|
|
||||||
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
export let id;
|
export let id;
|
||||||
|
|
||||||
export let title: string = 'N/A';
|
export let title: string = 'N/A';
|
||||||
@@ -37,6 +40,7 @@
|
|||||||
|
|
||||||
{#if title !== 'N/A'}
|
{#if title !== 'N/A'}
|
||||||
<button
|
<button
|
||||||
|
aria-label={$i18n.t('View source: {{title}}', { title: formattedTitle(decodeString(title)) })}
|
||||||
class="text-[10px] w-fit translate-y-[2px] px-2 py-0.5 dark:bg-white/5 dark:text-white/80 dark:hover:text-white bg-gray-50 text-black/80 hover:text-black transition rounded-xl"
|
class="text-[10px] w-fit translate-y-[2px] px-2 py-0.5 dark:bg-white/5 dark:text-white/80 dark:hover:text-white bg-gray-50 text-black/80 hover:text-black transition rounded-xl"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
onClick(id);
|
onClick(id);
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
<LinkPreview.Root openDelay={0} bind:open={openPreview}>
|
<LinkPreview.Root openDelay={0} bind:open={openPreview}>
|
||||||
<LinkPreview.Trigger>
|
<LinkPreview.Trigger>
|
||||||
<button
|
<button
|
||||||
|
aria-label={`${getDisplayTitle(formattedTitle(decodeString(sourceIds[token.ids[0] - 1])))} +${(token?.ids ?? []).length - 1} more sources`}
|
||||||
class="text-[10px] w-fit translate-y-[2px] px-2 py-0.5 dark:bg-white/5 dark:text-white/80 dark:hover:text-white bg-gray-50 text-black/80 hover:text-black transition rounded-xl"
|
class="text-[10px] w-fit translate-y-[2px] px-2 py-0.5 dark:bg-white/5 dark:text-white/80 dark:hover:text-white bg-gray-50 text-black/80 hover:text-black transition rounded-xl"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
openPreview = !openPreview;
|
openPreview = !openPreview;
|
||||||
|
|||||||
@@ -121,6 +121,7 @@
|
|||||||
<!-- <div class=" text-sm">{$i18n.t('Tell us more:')}</div> -->
|
<!-- <div class=" text-sm">{$i18n.t('Tell us more:')}</div> -->
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
aria-label={$i18n.t('Close feedback')}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
show = false;
|
show = false;
|
||||||
}}
|
}}
|
||||||
@@ -135,6 +136,7 @@
|
|||||||
<!-- 1-10 scale -->
|
<!-- 1-10 scale -->
|
||||||
{#each Array.from({ length: 10 }).map((_, i) => i + 1) as rating}
|
{#each Array.from({ length: 10 }).map((_, i) => i + 1) as rating}
|
||||||
<button
|
<button
|
||||||
|
aria-label={$i18n.t('Rate {{rating}} out of 10', { rating })}
|
||||||
class="size-7 text-sm border border-gray-100/30 dark:border-gray-850/30 hover:bg-gray-50 dark:hover:bg-gray-850 {detailedRating ===
|
class="size-7 text-sm border border-gray-100/30 dark:border-gray-850/30 hover:bg-gray-50 dark:hover:bg-gray-850 {detailedRating ===
|
||||||
rating
|
rating
|
||||||
? 'bg-gray-100 dark:bg-gray-800'
|
? 'bg-gray-100 dark:bg-gray-800'
|
||||||
@@ -218,6 +220,7 @@
|
|||||||
bind:value={comment}
|
bind:value={comment}
|
||||||
class="w-full text-sm px-1 py-2 bg-transparent outline-hidden resize-none rounded-xl"
|
class="w-full text-sm px-1 py-2 bg-transparent outline-hidden resize-none rounded-xl"
|
||||||
placeholder={$i18n.t('Feel free to add specific details')}
|
placeholder={$i18n.t('Feel free to add specific details')}
|
||||||
|
aria-label={$i18n.t('Additional feedback comments')}
|
||||||
rows="3"
|
rows="3"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,18 +15,16 @@
|
|||||||
|
|
||||||
<div class="flex flex-col text-left gap-1 mt-1.5">
|
<div class="flex flex-col text-left gap-1 mt-1.5">
|
||||||
{#each followUps as followUp, idx (idx)}
|
{#each followUps as followUp, idx (idx)}
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<Tooltip content={followUp} placement="top-start" className="line-clamp-1">
|
<Tooltip content={followUp} placement="top-start" className="line-clamp-1">
|
||||||
<div
|
<button
|
||||||
class=" py-1.5 bg-transparent text-left text-sm flex items-center gap-2 text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white transition cursor-pointer"
|
class=" py-1.5 bg-transparent text-left text-sm flex items-center gap-2 text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white transition cursor-pointer w-full"
|
||||||
on:click={() => onClick(followUp)}
|
on:click={() => onClick(followUp)}
|
||||||
aria-label={followUp}
|
aria-label={$i18n.t('Follow up: {{question}}', { question: followUp })}
|
||||||
>
|
>
|
||||||
<div class="line-clamp-1">
|
<div class="line-clamp-1">
|
||||||
{followUp}
|
{followUp}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{#if idx < followUps.length - 1}
|
{#if idx < followUps.length - 1}
|
||||||
|
|||||||
@@ -53,13 +53,14 @@
|
|||||||
|
|
||||||
<div class="ml-2 self-center flex items-center">
|
<div class="ml-2 self-center flex items-center">
|
||||||
<button
|
<button
|
||||||
class="{inputValue !== ''
|
aria-label={$i18n.t('Submit suggestion')}
|
||||||
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
|
class="{inputValue !== ''
|
||||||
: 'text-white bg-gray-200 dark:text-gray-900 dark:bg-gray-700 disabled'} transition rounded-full p-1 self-center"
|
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
|
||||||
on:click={() => {
|
: 'text-white bg-gray-200 dark:text-gray-900 dark:bg-gray-700 disabled'} transition rounded-full p-1 self-center"
|
||||||
onRegenerate(inputValue);
|
on:click={() => {
|
||||||
show = false;
|
onRegenerate(inputValue);
|
||||||
}}
|
show = false;
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
<div class="text-sm flex flex-col w-full">
|
<div class="text-sm flex flex-col w-full">
|
||||||
<button
|
<button
|
||||||
class="w-full"
|
class="w-full"
|
||||||
|
aria-label={$i18n.t('Toggle status history')}
|
||||||
|
aria-expanded={showHistory}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
showHistory = !showHistory;
|
showHistory = !showHistory;
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
<div class="w-fit">
|
<div class="w-fit">
|
||||||
<img
|
<img
|
||||||
src="https://www.google.com/s2/favicons?sz=32&domain={item.link}"
|
src="https://www.google.com/s2/favicons?sz=32&domain={item.link}"
|
||||||
alt="favicon"
|
alt="{item?.title ?? item.link} favicon"
|
||||||
class="size-3.5"
|
class="size-3.5"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
<div class="w-fit">
|
<div class="w-fit">
|
||||||
<img
|
<img
|
||||||
src="https://www.google.com/s2/favicons?sz=32&domain={url}"
|
src="https://www.google.com/s2/favicons?sz=32&domain={url}"
|
||||||
alt="favicon"
|
alt="{url} favicon"
|
||||||
class="size-3.5"
|
class="size-3.5"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user