Skip to content

Commit

Permalink
feat: add support for keyboard shortcuts
Browse files Browse the repository at this point in the history
- Implemented most of the keyboard shortcuts specified in stdlib-js#26
- Moved shortcuts prop and related methods to client.jsx
- Made sure to not to overlap with the webaim keyboard shortcuts for web accessibility
  • Loading branch information
lovelindhoni committed Mar 19, 2024
1 parent 5652946 commit c637e13
Show file tree
Hide file tree
Showing 24 changed files with 730 additions and 107 deletions.
18 changes: 18 additions & 0 deletions public/css/docs/layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -1143,10 +1143,28 @@ h2 {
display: flex;
}

.readme.help h2 {
border-bottom: 0px;
}

.readme.help h1 span {
flex-grow: 1;
}

.readme.help .keyboard-shortcut {
display: flex;
margin-bottom: 2em;
flex-direction: row;
align-items: baseline;
justify-content: space-between;
border-bottom: 2px solid var(--heading-border-bottom-color);

}

.readme.help .keyboard-shortcut-section {
margin-bottom: 5em;
}

/*
* Error decoder.
*/
Expand Down
12 changes: 9 additions & 3 deletions public/css/docs/typography.css
Original file line number Diff line number Diff line change
Expand Up @@ -184,14 +184,12 @@ a:active {
*/

.readme kbd {
font-size: 0.6875em;
font-size: 1em;
font-family: var(--code-font-family);

color: #555555; /* charcoal */

line-height: 1em;

vertical-align: middle;
}

/*
Expand Down Expand Up @@ -338,6 +336,14 @@ a:active {
color: var(--feedback-error-text-color);
}

/*
* Help page.
*/

.help h1 button.icon-button .icon {
fill: var(--theme-text-color);
}

/*
* Search results.
*/
Expand Down
85 changes: 26 additions & 59 deletions src/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import routes from './routes.js';
// VARIABLES //

var RE_INTERNAL_URL = new RegExp( '^'+config.mount );
var RE_SEARCH_URL = /\/search\/?/;
var RE_SEARCH_OR_HELP_URL = /(\/search|\/help)\/?/;
var RE_FORWARD_SLASH = /\//g;
var RE_PLOT_PKG = /\/plot/;

Expand Down Expand Up @@ -161,14 +161,11 @@ class App extends React.Component {
// Boolean indicating whether to show the side menu:
'sideMenu': false,

// Boolean indicating whether keyboard shortcuts are active:
'shortcuts': true,

// Boolean indicating whether a notification is currently displayed:
'notification': props.location.search.indexOf( 'notification' ) >= 0
};

// Previous (non-search) location (e.g., used for navigating to previous page after closing search results):
// Previous (non-search/help) location (e.g., used for navigating to previous page after closing search results):
this._prevLocation = config.mount; // default is API docs landing page

// Create a `ref` to point to a DOM element for resetting focus on page change:
Expand Down Expand Up @@ -278,9 +275,9 @@ class App extends React.Component {
if ( this.state.query === '' ) {
return;
}
// If we are coming from a non-search page, cache the current location...
// If we are coming from a non-search/help page, cache the current location...
path = this.props.location.pathname;
if ( RE_SEARCH_URL.test( path ) === false ) {
if ( RE_SEARCH_OR_HELP_URL.test( path ) === false ) {
this._prevLocation = path;
}
// Resolve a search URL based on the search query:
Expand All @@ -291,65 +288,30 @@ class App extends React.Component {
}

/**
* Callback invoked upon closing search results.
*
* @private
*/
_onSearchClose = () => {
// Manually update the history to trigger navigation to a previous (non-search) page:
this.props.history.push( this._prevLocation );

// Update the component state:
this.setState({
'query': '' // reset the search input element
});
}

/**
* Callback invoked when the search input element receives focus.
* Callback invoked upon submitting a search query.
*
* @private
* @returns {void}
*/
_onSearchFocus = () => {
// Whenever the search input element receives focus, we want to disable keyboard shortcuts:
this.setState({
'shortcuts': false
});
}
_onHelpOpen = () => {
var path = config.mount + this.props.version + '/help';

/**
* Callback invoked when the search input element loses focus.
*
* @private
*/
_onSearchBlur = () => {
// Whenever the search input element loses focus, we can enable keyboard shortcuts:
this.setState({
'shortcuts': true
});
// Manually update the history to trigger navigation to the help page:
this.props.history.push( path );
}

/**
* Callback invoked when the side menu filter receives focus.
* Callback invoked upon closing search results.
*
* @private
*/
_onFilterFocus = () => {
// Whenever the side menu filter receives focus, we want to disable keyboard shortcuts:
this.setState({
'shortcuts': false
});
}
_onSearchClose = () => {
// Manually update the history to trigger navigation to a previous (non-search/help) page:
this.props.history.push( this._prevLocation );

/**
* Callback invoked when the side menu filter loses focus.
*
* @private
*/
_onFilterBlur = () => {
// Whenever the side menu filter loses focus, we can enable keyboard shortcuts:
// Update the component state:
this.setState({
'shortcuts': true
'query': '' // reset the search input element
});
}

Expand Down Expand Up @@ -464,22 +426,24 @@ class App extends React.Component {

onSearchChange={ this._onSearchChange }
onSearchSubmit={ this._onSearchSubmit }
onSearchFocus={ this._onSearchFocus }
onSearchBlur={ this._onSearchBlur }
onSearchFocus={ this.props.onSearchFocus }
onSearchBlur={ this.props.onSearchBlur }

onFilterFocus={ this._onFilterFocus }
onFilterBlur={ this._onFilterBlur }
onFilterFocus={ this.props.onFilterFocus }
onFilterBlur={ this.props.onFilterBlur }

onVersionChange={ this.props.onVersionChange }

onHelpOpen={ this._onHelpOpen }

onAllowSettingsCookiesChange={ this.props.onAllowSettingsCookiesChange }
onThemeChange={ this.props.onThemeChange }
onModeChange={ this.props.onModeChange }
onExampleSyntaxChange={ this.props.onExampleSyntaxChange }
onPrevNextNavChange={ this.props.onPrevNextNavChange }

sideMenu={ this.state.sideMenu }

shortcuts = { this.props.shortcuts }
allowSettingsCookies={ this.props.allowSettingsCookies }
theme={ this.props.theme }
mode={ this.props.mode }
Expand Down Expand Up @@ -565,6 +529,7 @@ class App extends React.Component {
prev={ prev }
next={ next }
url={ match.url }
shortcuts={ this.props.shortcuts }
content={ this.props.content }
onClick={ this._onReadmeClick }
/>
Expand Down Expand Up @@ -748,6 +713,7 @@ class App extends React.Component {
version={ match.params.version }
query={ query }
onClose={ this._onSearchClose }
shortcuts={ this.props.shortcuts }
/>
</Fragment>
);
Expand All @@ -771,6 +737,7 @@ class App extends React.Component {
/>
<Help
onClose={ this._onHelpClose }
shortcuts={ this.props.shortcuts }
/>
</Fragment>
);
Expand Down
97 changes: 96 additions & 1 deletion src/client.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ var COOKIES = [
'prevnextnavigation'
];

var THEMES = [
'light',
'dark'
// more to come... hopefully...
]

// MAIN //

/**
Expand Down Expand Up @@ -131,7 +137,10 @@ class ClientApp extends React.Component {
'exampleSyntax': cookies.examplesyntax || config.exampleSyntax,

// Previous/next package navigation:
'prevNextNavigation': cookies.prevnextnavigation || config.prevNextNavigation
'prevNextNavigation': cookies.prevnextnavigation || config.prevNextNavigation,

// Boolean indicating whether keyboard shortcuts are active:
'shortcuts': true
};
}

Expand Down Expand Up @@ -269,6 +278,75 @@ class ClientApp extends React.Component {
});
}

/**
* Callback invoked when the search input element receives focus.
*
* @private
*/
_onSearchFocus = () => {
// Whenever the search input element receives focus, we want to disable keyboard shortcuts:
this.setState({
'shortcuts': false
});
}

/**
* Callback invoked when the search input element loses focus.
*
* @private
*/
_onSearchBlur = () => {
// Whenever the search input element loses focus, we can enable keyboard shortcuts:
this.setState({
'shortcuts': true
});
}

/**
* Callback invoked when the side menu filter receives focus.
*
* @private
*/
_onFilterFocus = () => {
// Whenever the side menu filter receives focus, we want to disable keyboard shortcuts:
this.setState({
'shortcuts': false
});
}

/**
* Callback invoked when the side menu filter loses focus.
*
* @private
*/
_onFilterBlur = () => {
// Whenever the side menu filter loses focus, we can enable keyboard shortcuts:
this.setState({
'shortcuts': true
});
}

/**
* Callback invoked upon a user press down a key to cycle through available themes
*
* @private
* @param {Object} event - event object
* @returns {void}
*/
_changeTheme = ( event ) => {
if ( event.shiftKey && event.key === "A" && this.state.shortcuts ) {
var changedTheme;
var currentThemeIndex = THEMES.indexOf( this.state.theme )
if( currentThemeIndex === THEMES.length - 1 ){
changedTheme = THEMES[0]
}
else{
changedTheme = THEMES[currentThemeIndex + 1]
}
this._onThemeChange( changedTheme );
}
};

/**
* Callback invoked immediately after mounting a component (i.e., is inserted into a tree).
*
Expand Down Expand Up @@ -304,6 +382,17 @@ class ClientApp extends React.Component {
'data': data
});
}
document.addEventListener( "keyup", this._changeTheme );
}

/**
* Callback invoked immediately after unmounting a component (i.e., is removed from a tree).
*
* @private
*/
componentWillUnmount() {
// Clean up event listener
document.removeEventListener( "keyup", this._changeTheme );
}

/**
Expand Down Expand Up @@ -337,6 +426,12 @@ class ClientApp extends React.Component {
onModeChange={ this._onModeChange }
onExampleSyntaxChange={ this._onExampleSyntaxChange }
onPrevNextNavChange={ this._onPrevNextNavChange }

shortcuts={ this.state.shortcuts }
onSearchFocus={ this._onSearchFocus }
onSearchBlur={ this._onSearchBlur }
onFilterFocus={ this._onFilterFocus }
onFilterBlur={ this._onFilterBlur }
/>
</HelmetProvider>
</BrowserRouter>
Expand Down
Loading

0 comments on commit c637e13

Please sign in to comment.