refactor: improve accessibility and i18n for search components

• Replace label with ariaLabel prop
• Add searchPlaceholder support
• Use i18n keys for messages
• Improve aria-label attributes
• Standardize noResultsMessage fallback
This commit is contained in:
yangdx 2025-09-24 19:59:56 +08:00
parent ed5911f656
commit ad247f2fc1
4 changed files with 20 additions and 12 deletions

View file

@ -185,9 +185,11 @@ const GraphLabels = () => {
renderOption={(item) => <div style={{ whiteSpace: 'pre' }}>{item}</div>}
getOptionValue={(item) => item}
getDisplayValue={(item) => <div style={{ whiteSpace: 'pre' }}>{item}</div>}
notFound={<div className="py-6 text-center text-sm">No labels found</div>}
label={t('graphPanel.graphLabels.label')}
notFound={<div className="py-6 text-center text-sm">{t('graphPanel.graphLabels.noLabels')}</div>}
ariaLabel={t('graphPanel.graphLabels.label')}
placeholder={t('graphPanel.graphLabels.placeholder')}
searchPlaceholder={t('graphPanel.graphLabels.placeholder')}
noResultsMessage={t('graphPanel.graphLabels.noLabels')}
value={label !== null ? label : '*'}
onChange={(newLabel) => {
const currentLabel = useSettingsStore.getState().queryLabel;

View file

@ -215,8 +215,9 @@ export const GraphSearchInput = ({
onFocus={(id) => {
if (id !== messageId && onFocus) onFocus(id ? { id, type: 'nodes' } : null)
}}
label={'item'}
ariaLabel={t('graphPanel.search.placeholder')}
placeholder={t('graphPanel.search.placeholder')}
noResultsMessage={t('graphPanel.search.placeholder')}
/>
)
}

View file

@ -41,8 +41,8 @@ export interface AsyncSearchProps<T> {
onChange: (value: string) => void
/** Callback when focus changes */
onFocus: (value: string) => void
/** Label for the select field */
label: string
/** Accessibility label for the search field */
ariaLabel?: string
/** Placeholder text when no selection */
placeholder?: string
/** Disable the entire select */
@ -67,7 +67,7 @@ export function AsyncSearch<T>({
getOptionValue,
notFound,
loadingSkeleton,
label,
ariaLabel,
placeholder = 'Select...',
value,
onChange,
@ -179,6 +179,7 @@ export function AsyncSearch<T>({
placeholder={placeholder}
value={searchTerm}
className="max-h-8"
aria-label={ariaLabel}
onFocus={handleFocus}
onValueChange={(value) => {
setSearchTerm(value)
@ -198,7 +199,7 @@ export function AsyncSearch<T>({
!error &&
options.length === 0 &&
(notFound || (
<CommandEmpty>{noResultsMessage ?? `No ${label.toLowerCase()} found.`}</CommandEmpty>
<CommandEmpty>{noResultsMessage || 'No results found.'}</CommandEmpty>
))}
<CommandGroup>
{options.map((option, idx) => (

View file

@ -43,10 +43,12 @@ export interface AsyncSelectProps<T> {
value: string
/** Callback when selection changes */
onChange: (value: string) => void
/** Label for the select field */
label: string
/** Accessibility label for the select field */
ariaLabel?: string
/** Placeholder text when no selection */
placeholder?: string
/** Display text for search placeholder */
searchPlaceholder?: string
/** Disable the entire select */
disabled?: boolean
/** Custom width for the popover *
@ -76,8 +78,9 @@ export function AsyncSelect<T>({
getDisplayValue,
notFound,
loadingSkeleton,
label,
ariaLabel,
placeholder = 'Select...',
searchPlaceholder,
value,
onChange,
disabled = false,
@ -200,6 +203,7 @@ export function AsyncSelect<T>({
variant="outline"
role="combobox"
aria-expanded={open}
aria-label={ariaLabel}
className={cn(
'justify-between',
disabled && 'cursor-not-allowed opacity-50',
@ -223,7 +227,7 @@ export function AsyncSelect<T>({
<Command shouldFilter={false}>
<div className="relative w-full border-b">
<CommandInput
placeholder={`Search ${label.toLowerCase()}...`}
placeholder={searchPlaceholder || 'Search...'}
value={searchTerm}
onValueChange={(value) => {
setSearchTerm(value)
@ -244,7 +248,7 @@ export function AsyncSelect<T>({
options.length === 0 &&
(notFound || (
<CommandEmpty>
{noResultsMessage ?? `No ${label.toLowerCase()} found.`}
{noResultsMessage || 'No results found.'}
</CommandEmpty>
))}
<CommandGroup>