mirror of
https://github.com/gethomepage/homepage.git
synced 2026-09-30 07:51:17 -07:00
Accessibility: improve sr labeling of search ui
This commit is contained in:
@@ -39,7 +39,9 @@
|
|||||||
"wait": "Please wait"
|
"wait": "Please wait"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"placeholder": "Search…"
|
"placeholder": "Search…",
|
||||||
|
"provider": "Search engine: {{name}}",
|
||||||
|
"select_provider": "Select search engine"
|
||||||
},
|
},
|
||||||
"resources": {
|
"resources": {
|
||||||
"cpu": "CPU",
|
"cpu": "CPU",
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ export default function Search({ options }) {
|
|||||||
focus:border-theme-500 dark:focus:border-white/50
|
focus:border-theme-500 dark:focus:border-white/50
|
||||||
border border-theme-300 dark:border-theme-200/50"
|
border border-theme-300 dark:border-theme-200/50"
|
||||||
placeholder={t("search.placeholder")}
|
placeholder={t("search.placeholder")}
|
||||||
|
aria-label={t("search.placeholder")}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setQuery(event.target.value);
|
setQuery(event.target.value);
|
||||||
}}
|
}}
|
||||||
@@ -208,8 +209,8 @@ export default function Search({ options }) {
|
|||||||
bg-theme-600/40 dark:bg-white/10
|
bg-theme-600/40 dark:bg-white/10
|
||||||
focus:ring-theme-500 dark:focus:ring-white/50"
|
focus:ring-theme-500 dark:focus:ring-white/50"
|
||||||
>
|
>
|
||||||
<selectedProvider.icon className="text-white w-3 h-3" />
|
<selectedProvider.icon className="text-white w-3 h-3" aria-hidden="true" />
|
||||||
<span className="sr-only">{t("search.search")}</span>
|
<span className="sr-only">{t("search.provider", { name: selectedProvider.name })}</span>
|
||||||
</ListboxButton>
|
</ListboxButton>
|
||||||
</div>
|
</div>
|
||||||
<Transition
|
<Transition
|
||||||
@@ -222,6 +223,7 @@ export default function Search({ options }) {
|
|||||||
leaveTo="transform opacity-0 scale-95"
|
leaveTo="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<ListboxOptions
|
<ListboxOptions
|
||||||
|
aria-label={t("search.select_provider")}
|
||||||
className="absolute right-0 z-10 mt-1 origin-top-right rounded-md
|
className="absolute right-0 z-10 mt-1 origin-top-right rounded-md
|
||||||
bg-theme-100 dark:bg-theme-600 shadow-lg
|
bg-theme-100 dark:bg-theme-600 shadow-lg
|
||||||
ring-1 ring-black ring-opacity-5 focus:outline-hidden"
|
ring-1 ring-black ring-opacity-5 focus:outline-hidden"
|
||||||
@@ -238,7 +240,8 @@ export default function Search({ options }) {
|
|||||||
active ? "bg-theme-600/10 dark:bg-white/10 dark:text-gray-900" : "dark:text-gray-100",
|
active ? "bg-theme-600/10 dark:bg-white/10 dark:text-gray-900" : "dark:text-gray-100",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<p.icon className="h-4 w-4 mx-4 my-2" />
|
<p.icon className="h-4 w-4 mx-4 my-2" aria-hidden="true" />
|
||||||
|
<span className="sr-only">{p.name}</span>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
</ListboxOption>
|
</ListboxOption>
|
||||||
@@ -252,7 +255,9 @@ export default function Search({ options }) {
|
|||||||
{searchSuggestions[1]?.length > 0 && (
|
{searchSuggestions[1]?.length > 0 && (
|
||||||
<ComboboxOptions className="mt-1 rounded-md bg-theme-50 dark:bg-theme-800 border border-theme-300 dark:border-theme-200/30 cursor-pointer shadow-lg">
|
<ComboboxOptions className="mt-1 rounded-md bg-theme-50 dark:bg-theme-800 border border-theme-300 dark:border-theme-200/30 cursor-pointer shadow-lg">
|
||||||
<div className="p-1 bg-white/50 dark:bg-white/10 text-theme-900/90 dark:text-white/90 text-xs">
|
<div className="p-1 bg-white/50 dark:bg-white/10 text-theme-900/90 dark:text-white/90 text-xs">
|
||||||
<ComboboxOption key={query} value={query} />
|
<ComboboxOption key={query} value={query}>
|
||||||
|
<span className="sr-only">{query}</span>
|
||||||
|
</ComboboxOption>
|
||||||
{searchSuggestions[1].map((suggestion) => (
|
{searchSuggestions[1].map((suggestion) => (
|
||||||
<ComboboxOption key={suggestion} value={suggestion} className="flex w-full">
|
<ComboboxOption key={suggestion} value={suggestion} className="flex w-full">
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
|
|||||||
@@ -178,6 +178,28 @@ describe("components/widgets/search", () => {
|
|||||||
openSpy.mockRestore();
|
openSpy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("gives every provider option an accessible name", () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<Search options={{ provider: ["google", "duckduckgo", "brave"], showSearchSuggestions: false }} />,
|
||||||
|
{ settings: {} },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getAllByRole("option").map((option) => option.textContent)).toEqual([
|
||||||
|
"Google",
|
||||||
|
"DuckDuckGo",
|
||||||
|
"Brave",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("labels the search input and the provider button", () => {
|
||||||
|
renderWithProviders(<Search options={{ provider: ["google", "duckduckgo"], showSearchSuggestions: false }} />, {
|
||||||
|
settings: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByRole("textbox")).toHaveAccessibleName("search.placeholder");
|
||||||
|
expect(screen.getByRole("button")).toHaveAccessibleName(/search\.provider/);
|
||||||
|
});
|
||||||
|
|
||||||
it("fetches search suggestions and triggers a search when a suggestion is selected", async () => {
|
it("fetches search suggestions and triggers a search when a suggestion is selected", async () => {
|
||||||
const openSpy = vi.spyOn(window, "open").mockImplementation(() => null);
|
const openSpy = vi.spyOn(window, "open").mockImplementation(() => null);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user