Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/dev/docs/src/Layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ function Page({children, currentPage, publicUrl, styles, scripts}) {
}
}
)}} />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
</head>
<body>
{children}
Expand All @@ -216,6 +217,7 @@ function Page({children, currentPage, publicUrl, styles, scripts}) {
document.head.appendChild(script);
});
`}} />
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js" />
</body>
</html>
);
Expand Down
134 changes: 130 additions & 4 deletions packages/dev/docs/src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import docsStyle from './docs.css';
import {listen} from 'quicklink';
import React, {useEffect, useRef, useState} from 'react';
import ReactDOM from 'react-dom';
import {SearchField} from '@react-spectrum/searchfield';
import ShowMenu from '@spectrum-icons/workflow/ShowMenu';
import {ThemeSwitcher} from './ThemeSwitcher';
import {watchModals} from '@react-aria/aria-modal-polyfill';
Expand Down Expand Up @@ -74,21 +75,25 @@ function Hamburger() {
let nav = document.querySelector('.' + docsStyle.nav);
let main = document.querySelector('main');
let themeSwitcher = event.target.parentElement.nextElementSibling;

let themeSwitcherButton = themeSwitcher.querySelector('button');
nav.classList.toggle(docsStyle.visible);

if (nav.classList.contains(docsStyle.visible)) {
setIsPressed(true);
main.setAttribute('aria-hidden', 'true');
themeSwitcher.setAttribute('aria-hidden', 'true');
themeSwitcher.querySelector('button').tabIndex = -1;
if (themeSwitcherButton) {
themeSwitcherButton.tabIndex = -1;
}
nav.tabIndex = -1;
nav.focus();
} else {
setIsPressed(false);
main.removeAttribute('aria-hidden');
themeSwitcher.removeAttribute('aria-hidden');
themeSwitcher.querySelector('button').removeAttribute('tabindex');
if (themeSwitcherButton) {
themeSwitcherButton.removeAttribute('tabindex');
}
nav.removeAttribute('tabindex');
}
};
Expand All @@ -110,7 +115,10 @@ function Hamburger() {
nav.classList.remove(docsStyle.visible);
main.removeAttribute('aria-hidden');
themeSwitcher.removeAttribute('aria-hidden');
themeSwitcher.querySelector('button').removeAttribute('tabindex');
let themeSwitcherButton = themeSwitcher.querySelector('button');
if (themeSwitcherButton) {
themeSwitcherButton.removeAttribute('tabindex');
}
nav.removeAttribute('tabindex');
};

Expand Down Expand Up @@ -183,8 +191,126 @@ function Hamburger() {
);
}

function DocSearch() {
useEffect(() => {
// the following comes from docsearch.min.js
// eslint-disable-next-line no-undef
const search = docsearch({
apiKey: '9b5a0967c8bb751b5048ecfc99917979',
indexName: 'react-spectrum',
inputSelector: '#algolia-doc-search',
debug: false // Set debug to true to inspect the dropdown
});

// autocomplete:opened event handler
search.autocomplete.on('autocomplete:opened', event => {
const input = event.target;

// WAI-ARIA 1.2 uses aria-controls rather than aria-owns on combobox.
if (!input.hasAttribute('aria-controls') && input.hasAttribute('aria-owns')) {
input.setAttribute('aria-controls', input.getAttribute('aria-owns'));
}

// Listbox dropdown should have an accessibility name.
const listbox = input.parentElement.querySelector(`#${input.getAttribute('aria-controls')}`);
listbox.setAttribute('aria-label', 'Search results');
});

// autocomplete:updated event handler
search.autocomplete.on('autocomplete:updated', event => {
const input = event.target;
const listbox = input.parentElement.querySelector(`#${input.getAttribute('aria-controls')}`);

// Add aria-hidden to the logo in the footer so that it does not break the listbox accessibility tree structure.
const footer = listbox.querySelector('.algolia-docsearch-footer');
if (footer && !footer.hasAttribute('aria-hidden')) {
footer.setAttribute('aria-hidden', 'true');
footer.querySelector('a[href]').tabIndex = -1;
}

// With no results, the message should be an option in the listbox.
const noResults = listbox.querySelector('.algolia-docsearch-suggestion--no-results');
if (noResults) {
noResults.setAttribute('role', 'option');

// Use aria-live to ensure that the noResults message gets announced.
noResults.querySelector('.algolia-docsearch-suggestion--title').setAttribute('aria-live', 'assertive');
}

// Clean up WAI-ARIA listbox structure by setting role=presentation to non-semantic div and span elements.
[...listbox.querySelectorAll('div:not([role]), span:not([role])')].forEach(element => element.setAttribute('role', 'presentation'));

// Clean up WAI-ARIA listbox structure by correcting improper nesting of interactive controls.
[...listbox.querySelectorAll('.ds-suggestion[role="option"]')].forEach(element => {
const link = element.querySelector('a.algolia-docsearch-suggestion');
if (link) {

// Remove static aria-label="Link to the result" that causes all options to be named the same.
link.removeAttribute('aria-label');

// The interactive element should have role="option", a unique id, and tabIndex.
link.setAttribute('role', 'option');
link.id = `${element.id}-link`;
link.tabIndex = -1;

// containing element should have role="presentation"
element.setAttribute('role', 'presentation');

// Move aria-selected to the link, and update aria-activedescendant on input.
if (element.hasAttribute('aria-selected')) {
link.setAttribute('aria-selected', element.getAttribute('aria-selected'));
element.removeAttribute('aria-selected');
input.setAttribute('aria-activedescendant', link.id);
}

// Fix double voicing of options when subcategory matches suggestion title.
const subcategoryColumn = link.querySelector('.algolia-docsearch-suggestion--subcategory-column');
const suggestionTitle = link.querySelector('.algolia-docsearch-suggestion--title');
if (subcategoryColumn.textContent.trim() === suggestionTitle.textContent.trim()) {
subcategoryColumn.setAttribute('aria-hidden', 'true');
}
}
});
});

// When navigating listbox, move aria-selected to link.
search.autocomplete.on('autocomplete:cursorchanged', event => {
const input = event.target;
const listbox = input.parentElement.querySelector(`#${input.getAttribute('aria-controls')}`);
let element = listbox.querySelector('a.algolia-docsearch-suggestion[aria-selected]');
if (element) {
element.removeAttribute('aria-selected');
}

element = listbox.querySelector('.ds-suggestion.ds-cursor[aria-selected]');
if (element) {
let link = element.querySelector('a.algolia-docsearch-suggestion');

// Move aria-selected to the link, and update aria-activedescendant on input.
if (link) {
link.id = `${element.id}-link`;
link.setAttribute('aria-selected', 'true');
input.setAttribute('aria-activedescendant', link.id);
element.removeAttribute('aria-selected');
}
}
});
}, []);

return (
<div role="search">
<SearchField
aria-label="Search"
UNSAFE_className={docsStyle.docSearchBox}
id="algolia-doc-search"
placeholder="Search" />
</div>
);
}

ReactDOM.render(<>
<Hamburger />
<DocSearch />
<ThemeSwitcher />
</>, document.querySelector('.' + docsStyle.pageHeader));

Expand Down
125 changes: 125 additions & 0 deletions packages/dev/docs/src/docs.css
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,7 @@ h2.sectionHeader {
}

.pageHeader {
gap: var(--spectrum-global-dimension-size-100);
position: fixed;
top: 0;
display: inline-flex;
Expand All @@ -622,6 +623,104 @@ h2.sectionHeader {
[dir='rtl'] & {
left: 0;
}

z-index: 1;

& :global(.algolia-autocomplete .ds-dropdown-menu) {
border-color: var(--spectrum-alias-border-color-dark, var(--spectrum-global-color-gray-400));
border-radius: var(--spectrum-global-dimension-size-65);
border-style: solid;
border-width: var(--spectrum-alias-border-size-thin, var(--spectrum-global-dimension-static-size-10));
box-shadow: unset;
filter: drop-shadow(0 1px 4px var(--spectrum-alias-dropshadow-color));

& [class^='ds-dataset-'] {
background-color: var(--spectrum-global-color-gray-50);
border: unset;
}

&:before {
background-color: var(--spectrum-global-color-gray-50);
border-right-color: var(--spectrum-alias-border-color-dark, var(--spectrum-global-color-gray-400));
border-top-color: var(--spectrum-alias-border-color-dark, var(--spectrum-global-color-gray-400));
z-index: -1;
}

& :global(.algolia-docsearch-suggestion) {
background-color: var(--spectrum-global-color-gray-50);
}

& :global(.algolia-docsearch-suggestion.algolia-docsearch-suggestion__main) {
text-decoration: none;
}

& :global(.algolia-docsearch-suggestion--category-header) {
border-bottom-color: var(--spectrum-global-color-gray-300);
color: var(--spectrum-global-color-gray-900);
font-weight: var(--spectrum-global-font-weight-semi-bold);
text-transform: uppercase;
}

& :global(.algolia-docsearch-suggestion--highlight) {
background: var(--spectrum-alias-text-highlight-color);
color: var(--spectrum-global-color-blue-700);
}

& :global(.algolia-docsearch-suggestion--subcategory-column) {
color: var(--spectrum-global-color-gray-800);
}

& :global(.algolia-docsearch-suggestion--subcategory-column:before) {
background: var(--spectrum-global-color-gray-300);
}

& :global(.ds-suggestion.ds-cursor .algolia-docsearch-suggestion--subcategory-column:before) {
background: var(--spectrum-alias-border-color-focus);
width: var(--spectrum-selectlist-border-size-key-focus);
}

& :global(.ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content:before) {
background: var(--spectrum-alias-border-color-focus);
width: var(--spectrum-selectlist-border-size-key-focus);
left: calc(-1 * var(--spectrum-selectlist-border-size-key-focus));
}

& :global(.ds-suggestion.ds-cursor .algolia-docsearch-suggestion:not(.suggestion-layout-simple) .algolia-docsearch-suggestion--content) {
background-color: var(--spectrum-alias-background-color-hover-overlay);
}

& :global(.algolia-docsearch-suggestion--content:before) {
background: var(--spectrum-global-color-gray-300);
}

& :global(.algolia-docsearch-suggestion--title) {
color: var(--spectrum-global-color-gray-800);
}
}
}

@media (max-width: 768px) {
.pageHeader {
& :global(.algolia-autocomplete) {
& :global(.algolia-docsearch-suggestion .algolia-docsearch-suggestion--subcategory-column) {
color: var(--spectrum-global-color-gray-700);
opacity: 1;
}

& :global(.algolia-docsearch-suggestion .algolia-docsearch-suggestion--subcategory-column:after) {
content: "|\00A0";
}

& :global(.ds-suggestion.ds-cursor .algolia-docsearch-suggestion .algolia-docsearch-suggestion--subcategory-column:after) {
color: var(--spectrum-alias-border-color-focus);
font-weight: bold;
}
}
}
}

.docSearchBox {
margin-inline-start: auto;
}

/* hamburger menu should be hidden so that it doesn't receive focus before the sidenav */
Expand Down Expand Up @@ -704,6 +803,32 @@ h2.sectionHeader {
margin-right: auto;
width: auto
}

@media (min-width: 570px) {
.pageHeader {
& :global(.algolia-autocomplete .ds-dropdown-menu) {
margin-inline-end: var(--spectrum-global-dimension-size-600);
max-width: 500px;
min-width: unset;
position: fixed !important;
top: var(--spectrum-global-dimension-size-500) !important;
}
}
}
}

@media (max-width: 569px) {
.pageHeader {
& :global(.algolia-autocomplete .ds-dropdown-menu) {
min-width: unset;
position: fixed !important;
top: var(--spectrum-global-dimension-size-500) !important;

&:before {
right: var(--spectrum-global-dimension-size-1200) !important;
}
}
}
}

@media (max-width: 1200px) {
Expand Down