Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for keyboard shortcuts #65

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
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