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

Create OnOffToggle component for preferences. #2323

Open
wants to merge 5 commits into
base: develop
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
74 changes: 74 additions & 0 deletions client/modules/IDE/components/Preferences/OnOffToggle.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import PropTypes from 'prop-types';
import React from 'react';
import { useTranslation } from 'react-i18next';

const OnOffToggle = ({ value, setValue, name, translationKey, children }) => {
const { t } = useTranslation();

return (
<div className="preference__options">
<input
type="radio"
onChange={() => setValue(true)}
aria-label={t(`Preferences.${translationKey}OnARIA`)}
name={name}
id={`${name}-on`}
className="preference__radio-button"
value="On"
checked={value}
/>
<label htmlFor={`${name}-on`} className="preference__option">
{t('Preferences.On')}
</label>
<input
type="radio"
onChange={() => setValue(false)}
aria-label={t(`Preferences.${translationKey}OffARIA`)}
name={name}
id={`${name}-off`}
className="preference__radio-button"
value="Off"
checked={!value}
/>
<label htmlFor={`${name}-off`} className="preference__option">
{t('Preferences.Off')}
</label>
{children}
</div>
);
};

OnOffToggle.propTypes = {
/**
* `true` if turned on, `false` if off.
*/
value: PropTypes.bool.isRequired,
/**
* Function to call when the value is changed.
* Will receive the new `boolean` value.
*/
setValue: PropTypes.func.isRequired,
/**
* Used as the HTML `name` attribute of the form elements,
* and also used to formulate the `id`s.
*/
name: PropTypes.string.isRequired,
/**
* Common prefix for looking up the "On" and "Off" ARIA labels.
* If the ARIA label is t('Preferences.LintWarningOnARIA'),
* then the `translationKey` is `LintWarning`.
*/
translationKey: PropTypes.string,
/**
* Can insert additional elements in the same <div> as the inputs.
* Typically not used.
*/
children: PropTypes.node
};

OnOffToggle.defaultProps = {
translationKey: '',
children: null
};

export default OnOffToggle;
60 changes: 60 additions & 0 deletions client/modules/IDE/components/Preferences/OnOffToggle.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import { render, screen, fireEvent } from '../../../../test-utils';
import OnOffToggle from './OnOffToggle';

describe('OnOffToggle', () => {
const setValue = jest.fn();

const subject = (initialValue = false) => {
const result = render(
<OnOffToggle
value={initialValue}
setValue={setValue}
name="linewrap"
translationKey="LineWrap"
/>
);
return {
...result,
onButton: screen.getByLabelText('On'),
offButton: screen.getByLabelText('Off')
};
};

afterEach(() => {
jest.clearAllMocks();
});

it('highlights "On" when value is true', () => {
const { onButton, offButton } = subject(true);

expect(onButton).toBeChecked();
expect(offButton).not.toBeChecked();
});

it('highlights "Off" when value is false', () => {
const { onButton, offButton } = subject(false);

expect(onButton).not.toBeChecked();
expect(offButton).toBeChecked();
});

it('does nothing when clicking the active value', () => {
const { onButton } = subject(true);

fireEvent.click(onButton);

expect(setValue).not.toHaveBeenCalled();
});

it('calls setValue when clicking the inactive value', () => {
const { offButton } = subject(true);

fireEvent.click(offButton);

expect(setValue).toHaveBeenCalledTimes(1);
expect(setValue).toHaveBeenCalledWith(false);

// Note: the checked state will not change until the `value` prop changes.
});
});
218 changes: 38 additions & 180 deletions client/modules/IDE/components/Preferences/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
setAutocompleteHinter,
setLinewrap
} from '../../actions/preferences';
import OnOffToggle from './OnOffToggle';

export default function Preferences() {
const { t } = useTranslation();
Expand Down Expand Up @@ -196,218 +197,75 @@ export default function Preferences() {
</div>
<div className="preference">
<h4 className="preference__title">{t('Preferences.Autosave')}</h4>
<div className="preference__options">
<input
type="radio"
onChange={() => dispatch(setAutosave(true))}
aria-label={t('Preferences.AutosaveOnARIA')}
name="autosave"
id="autosave-on"
className="preference__radio-button"
value="On"
checked={autosave}
/>
<label htmlFor="autosave-on" className="preference__option">
{t('Preferences.On')}
</label>
<input
type="radio"
onChange={() => dispatch(setAutosave(false))}
aria-label={t('Preferences.AutosaveOffARIA')}
name="autosave"
id="autosave-off"
className="preference__radio-button"
value="Off"
checked={!autosave}
/>
<label htmlFor="autosave-off" className="preference__option">
{t('Preferences.Off')}
</label>
</div>
<OnOffToggle
value={autosave}
setValue={(value) => dispatch(setAutosave(value))}
name="autosave"
translationKey="Autosave"
/>
</div>
<div className="preference">
<h4 className="preference__title">
{t('Preferences.AutocloseBracketsQuotes')}
</h4>
<div className="preference__options">
<input
type="radio"
onChange={() => dispatch(setAutocloseBracketsQuotes(true))}
aria-label={t('Preferences.AutocloseBracketsQuotesOnARIA')}
name="autoclosebracketsquotes"
id="autoclosebracketsquotes-on"
className="preference__radio-button"
value="On"
checked={autocloseBracketsQuotes}
/>
<label
htmlFor="autoclosebracketsquotes-on"
className="preference__option"
>
{t('Preferences.On')}
</label>
<input
type="radio"
onChange={() => dispatch(setAutocloseBracketsQuotes(false))}
aria-label={t('Preferences.AutocloseBracketsQuotesOffARIA')}
name="autoclosebracketsquotes"
id="autoclosebracketsquotes-off"
className="preference__radio-button"
value="Off"
checked={!autocloseBracketsQuotes}
/>
<label
htmlFor="autoclosebracketsquotes-off"
className="preference__option"
>
{t('Preferences.Off')}
</label>
</div>
<OnOffToggle
value={autocloseBracketsQuotes}
setValue={(value) => dispatch(setAutocloseBracketsQuotes(value))}
name="autoclosebracketsquotes"
translationKey="AutocloseBracketsQuotes"
/>
</div>
<div className="preference">
<h4 className="preference__title">
{t('Preferences.AutocompleteHinter')}
</h4>
<div className="preference__options">
<input
type="radio"
onChange={() => dispatch(setAutocompleteHinter(true))}
aria-label={t('Preferences.AutocompleteHinterOnARIA')}
name="autocompletehinter"
id="autocompletehinter-on"
className="preference__radio-button"
value="On"
checked={autocompleteHinter}
/>
<label
htmlFor="autocompletehinter-on"
className="preference__option"
>
{t('Preferences.On')}
</label>
<input
type="radio"
onChange={() => dispatch(setAutocompleteHinter(false))}
aria-label={t('Preferences.AutocompleteHinterOffARIA')}
name="autocompletehinter"
id="autocompletehinter-off"
className="preference__radio-button"
value="Off"
checked={!autocompleteHinter}
/>
<label
htmlFor="autocompletehinter-off"
className="preference__option"
>
{t('Preferences.Off')}
</label>
</div>
<OnOffToggle
value={autocompleteHinter}
setValue={(value) => dispatch(setAutocompleteHinter(value))}
name="autocompletehinter"
translationKey="AutocompleteHinter"
/>
</div>
<div className="preference">
<h4 className="preference__title">{t('Preferences.WordWrap')}</h4>
<div className="preference__options">
<input
type="radio"
onChange={() => dispatch(setLinewrap(true))}
aria-label={t('Preferences.LineWrapOnARIA')}
name="linewrap"
id="linewrap-on"
className="preference__radio-button"
value="On"
checked={linewrap}
/>
<label htmlFor="linewrap-on" className="preference__option">
{t('Preferences.On')}
</label>
<input
type="radio"
onChange={() => dispatch(setLinewrap(false))}
aria-label={t('Preferences.LineWrapOffARIA')}
name="linewrap"
id="linewrap-off"
className="preference__radio-button"
value="Off"
checked={!linewrap}
/>
<label htmlFor="linewrap-off" className="preference__option">
{t('Preferences.Off')}
</label>
</div>
<OnOffToggle
value={linewrap}
setValue={(value) => dispatch(setLinewrap(value))}
name="linewrap"
translationKey="LineWrap"
/>
</div>
</TabPanel>
<TabPanel>
<div className="preference">
<h4 className="preference__title">
{t('Preferences.LineNumbers')}
</h4>
<div className="preference__options">
<input
type="radio"
onChange={() => dispatch(setLineNumbers(true))}
aria-label={t('Preferences.LineNumbersOnARIA')}
name="line numbers"
id="line-numbers-on"
className="preference__radio-button"
value="On"
checked={lineNumbers}
/>
<label htmlFor="line-numbers-on" className="preference__option">
{t('Preferences.On')}
</label>
<input
type="radio"
onChange={() => dispatch(setLineNumbers(false))}
aria-label={t('Preferences.LineNumbersOffARIA')}
name="line numbers"
id="line-numbers-off"
className="preference__radio-button"
value="Off"
checked={!lineNumbers}
/>
<label htmlFor="line-numbers-off" className="preference__option">
{t('Preferences.Off')}
</label>
</div>
<OnOffToggle
value={lineNumbers}
setValue={(value) => dispatch(setLineNumbers(value))}
name="line numbers"
translationKey="LineNumbers"
/>
</div>
<div className="preference">
<h4 className="preference__title">
{t('Preferences.LintWarningSound')}
</h4>
<div className="preference__options">
<input
type="radio"
onChange={() => dispatch(setLintWarning(true))}
aria-label={t('Preferences.LintWarningOnARIA')}
name="lint warning"
id="lint-warning-on"
className="preference__radio-button"
value="On"
checked={lintWarning}
/>
<label htmlFor="lint-warning-on" className="preference__option">
{t('Preferences.On')}
</label>
<input
type="radio"
onChange={() => dispatch(setLintWarning(false))}
aria-label={t('Preferences.LintWarningOffARIA')}
name="lint warning"
id="lint-warning-off"
className="preference__radio-button"
value="Off"
checked={!lintWarning}
/>
<label htmlFor="lint-warning-off" className="preference__option">
{t('Preferences.Off')}
</label>
<OnOffToggle
value={lintWarning}
setValue={(value) => dispatch(setLintWarning(value))}
name="lint warning"
translationKey="LintWarning"
>
<button
className="preference__preview-button"
onClick={() => new Audio(beepUrl).play()}
aria-label={t('Preferences.PreviewSoundARIA')}
>
{t('Preferences.PreviewSound')}
</button>
</div>
</OnOffToggle>
</div>
<div className="preference">
<h4 className="preference__title">
Expand Down
Loading