Skip to content

Commit

Permalink
Implement SensitiveTextControl component and use it for API keys so t…
Browse files Browse the repository at this point in the history
…hat the text is not visible by default.
  • Loading branch information
felixarntz committed Oct 5, 2024
1 parent 32bbab6 commit 6222797
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 2 deletions.
96 changes: 96 additions & 0 deletions src/components/SensitiveTextControl/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* External dependencies
*/
import clsx from 'clsx';

/**
* WordPress dependencies
*/
import { BaseControl } from '@wordpress/components';
import { useInstanceId } from '@wordpress/compose';
import { forwardRef, useState } from '@wordpress/element';

/**
* Internal dependencies
*/
import InputVisibleButton from './input-invisible-button.js';
import './style.scss';

/**
* Renders a modified version of TextControl, which shows a button to toggle visibility of the input text.
*
* By default, the input text is hidden, i.e. the input type is forced to 'password'.
*
* The code is almost entirely copied from the original TextControl component implementation.
*
* @since n.e.x.t
*
* @param {Object} props Component props. These are identical to the props of the SensitiveTextControl component, with the
* addition of 'buttonShowLabel' and 'buttonHideLabel'.
* @param {Object} ref Reference to the component.
* @return {Component} The component to be rendered.
*/
function UnforwardedSensitiveTextControl( props, ref ) {
const {
__nextHasNoMarginBottom,
__next40pxDefaultSize = false,
label,
hideLabelFromVision,
value,
help,
id: idProp,
className,
onChange,
type = 'text',
buttonShowLabel,
buttonHideLabel,
...additionalProps
} = props;
const id = useInstanceId(
SensitiveTextControl,
'inspector-text-control',
idProp
);
const onChangeValue = ( event ) => onChange( event.target.value );

const [ visible, setVisible ] = useState( false );

return (
<BaseControl
__nextHasNoMarginBottom={ __nextHasNoMarginBottom }
__associatedWPComponentName="SensitiveTextControl"
label={ label }
hideLabelFromVision={ hideLabelFromVision }
id={ id }
help={ help }
className={ className }
>
<div className="ai-services-sensitive-text-control-container">
<input
className={ clsx( 'components-text-control__input', {
'is-next-40px-default-size': __next40pxDefaultSize,
} ) }
type={ visible ? type : 'password' }
id={ id }
value={ value }
onChange={ onChangeValue }
aria-describedby={ !! help ? id + '__help' : undefined }
ref={ ref }
{ ...additionalProps }
/>
<InputVisibleButton
visible={ visible }
setVisible={ setVisible }
showLabel={ buttonShowLabel }
hideLabel={ buttonHideLabel }
/>
</div>
</BaseControl>
);
}

export const SensitiveTextControl = forwardRef(
UnforwardedSensitiveTextControl
);

export default SensitiveTextControl;
54 changes: 54 additions & 0 deletions src/components/SensitiveTextControl/input-invisible-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';

/**
* WordPress dependencies
*/
import { Button, Dashicon } from '@wordpress/components';
import { _x } from '@wordpress/i18n';

/**
* Renders a wrapper for the actions within the header of the application.
*
* Any children passed to this component will be rendered inside the header actions area.
*
* @since n.e.x.t
*
* @param {Object} props Component props.
* @param {boolean} props.visible Whether the input is visible.
* @param {Function} props.setVisible Function to toggle the input visibility. Must accept a boolean.
* @param {string} props.showLabel Label for the show action.
* @param {string} props.hideLabel Label for the hide action.
* @return {Component} The component to be rendered.
*/
function InputVisibleButton( { visible, setVisible, showLabel, hideLabel } ) {
return (
<Button
variant="secondary"
className="ai-services-input-visible-button"
onClick={ () => setVisible( ! visible ) }
ariaLabel={ visible ? hideLabel : showLabel }
>
<Dashicon
icon={ visible ? 'hidden' : 'visibility' }
aria-hidden="true"
/>
<span className="text">
{ visible
? _x( 'Hide', 'action', 'ai-services' )
: _x( 'Show', 'action', 'ai-services' ) }
</span>
</Button>
);
}

InputVisibleButton.propTypes = {
visible: PropTypes.bool.isRequired,
setVisible: PropTypes.func.isRequired,
showLabel: PropTypes.string,
hideLabel: PropTypes.string,
};

export default InputVisibleButton;
26 changes: 26 additions & 0 deletions src/components/SensitiveTextControl/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.ai-services-sensitive-text-control-container {
display: flex;

.ai-services-input-visible-button {
margin-left: 4px;

.dashicons {
margin-right: 4px;
}
}

.components-text-control__input {
min-height: 36px; // Same as button.
}

@media (max-width: 782px) {

.components-text-control__input {
min-height: 40px; // Same as button.
}

.ai-services-input-visible-button {
min-height: 40px; // Same as input.
}
}
}
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { default as PinnedSidebars } from './PinnedSidebars';
export { default as Sidebar } from './Sidebar';
export { default as KeyboardShortcutsMenuItem } from './KeyboardShortcutsMenuItem';
export { default as DistractionFreePreferenceToggleMenuItem } from './DistractionFreePreferenceToggleMenuItem';
export { default as SensitiveTextControl } from './SensitiveTextControl';
14 changes: 12 additions & 2 deletions src/services-page/components/SettingsCards/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* External dependencies
*/
import { SensitiveTextControl } from '@ai-services/components';
import { store as pluginSettingsStore } from '@ai-services/settings-store';

/**
Expand All @@ -10,7 +11,6 @@ import {
Card,
CardHeader,
CardBody,
TextControl,
ToggleControl,
} from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
Expand Down Expand Up @@ -70,7 +70,7 @@ export default function SettingsCards() {
</CardHeader>
<CardBody>
{ services.map( ( service ) => (
<TextControl
<SensitiveTextControl
key={ service.slug }
label={ service.name }
help={
Expand Down Expand Up @@ -98,6 +98,16 @@ export default function SettingsCards() {
onChange={ ( value ) =>
setApiKey( service.slug, value )
}
buttonShowLabel={ sprintf(
/* translators: %s: service name */
__( 'Show API key for %s.', 'ai-services' ),
service.name
) }
buttonHideLabel={ sprintf(
/* translators: %s: service name */
__( 'Hide API key for %s.', 'ai-services' ),
service.name
) }
/>
) ) }
</CardBody>
Expand Down

0 comments on commit 6222797

Please sign in to comment.