PluginProbe
Parse.ly / 3.24.3
Parse.ly v3.24.3
3.24.3 3.24.2 3.24.1 3.24.0 3.23.7 3.23.6 3.23.5 3.23.4 3.23.3 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.17.0 3.18.0 3.18.1 3.19.0 3.19.1 3.19.2 3.19.3 3.2.0 3.2.1 3.20.0 3.20.1 All 107 releases
wp-parsely / src / content-helper / common / components / tone-selector / component.tsx

component.tsx in Parse.ly 3.24.3, at src/content-helper/common/components/tone-selector/component.tsx

254 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * WordPress dependencies
3 */
4 import {
5 Disabled,
6 DropdownMenu,
7 MenuGroup,
8 MenuItem,
9 TextControl,
10 } from '@wordpress/components';
11 import { useDebounce, useInstanceId } from '@wordpress/compose';
12 import { useState } from '@wordpress/element';
13 import { __ } from '@wordpress/i18n';
14 import { Icon, pencil } from '@wordpress/icons';
15
16 /**
17 * Internal dependencies
18 */
19 import { MoreArrow } from '../../icons/more-arrow';
20 import { MAX_CUSTOM_VALUE_LENGTH } from '../../utils/constants';
21 import { toMetadata, VocabularyEntry } from '../../utils/vocabulary';
22
23 /**
24 * Represents a single tone in the PARSELY_TONES list.
25 *
26 * @since 3.14.0
27 */
28 type ToneMetadata = VocabularyEntry;
29
30 /**
31 * List of the available tones.
32 * Each tone has a label and an optional icon.
33 *
34 * The predefined tones come from PHP, which is their single source, so that
35 * the plugin's settings page can offer the same choices. The custom tone is
36 * defined here, as it is a UI affordance rather than part of the vocabulary,
37 * and carries an icon that PHP cannot express.
38 *
39 * @since 3.13.0
40 * @since 3.24.0 The predefined tones are supplied by PHP.
41 */
42 export const PARSELY_TONES: Record<string, ToneMetadata> = {
43 ...toMetadata( window.wpParselyContentHelperTones ),
44 custom: {
45 label: __( 'Custom Tone', 'wp-parsely' ),
46 icon: pencil,
47 },
48 };
49
50 export type ToneProp = keyof typeof PARSELY_TONES | string;
51 type FixedToneProp = keyof typeof PARSELY_TONES;
52
53 const TONE_LIST = Object.keys( PARSELY_TONES ) as ToneProp[];
54
55 /**
56 * Returns the value for a given tone.
57 *
58 * @since 3.13.0
59 *
60 * @param {ToneProp} tone The tone to get the label for.
61 *
62 * @return {string} The value for the given tone.
63 */
64 export const getLabel = ( tone: ToneProp ): string => {
65 if ( tone === 'custom' || tone === '' ) {
66 return PARSELY_TONES.custom.label;
67 }
68
69 if ( isCustomTone( tone ) ) {
70 return tone;
71 }
72
73 return PARSELY_TONES[ tone as FixedToneProp ].label;
74 };
75
76 /**
77 * Returns whether a given tone is a custom tone.
78 *
79 * @since 3.13.0
80 *
81 * @param {ToneProp} tone
82 *
83 * @return {boolean} Whether the given tone is a custom tone.
84 */
85 export const isCustomTone = ( tone: ToneProp ): boolean => {
86 return ! TONE_LIST.includes( tone ) || tone === 'custom';
87 };
88
89 /**
90 * Properties for the CustomTone component.
91 *
92 * @since 3.13.0
93 */
94 type CustomToneProps = {
95 value: string,
96 onChange: ( tone: string ) => void
97 }
98
99 /**
100 * Custom Tone component.
101 *
102 * Allows the user to enter a custom tone.
103 *
104 * @since 3.13.0
105 *
106 * @param {CustomToneProps} props The properties for the CustomTone component.
107 */
108 const CustomTone = (
109 { value, onChange }: Readonly<CustomToneProps>
110 ): React.JSX.Element => {
111 const [ customTone, setCustomTone ] = useState<string>( '' );
112 const debouncedOnChange = useDebounce( onChange, 500 );
113
114 return (
115 <div className="parsely-tone-selector-custom">
116 <TextControl
117 value={ customTone || value }
118 placeholder={ __( 'Enter a custom tone', 'wp-parsely' ) }
119 onChange={ ( newTone ) => {
120 // If the tone is empty, set it to an empty string, and avoid debouncing.
121 if ( '' === newTone ) {
122 onChange( '' );
123 setCustomTone( '' );
124 return;
125 }
126 if ( newTone.length > MAX_CUSTOM_VALUE_LENGTH ) {
127 newTone = newTone.slice( 0, MAX_CUSTOM_VALUE_LENGTH );
128 }
129 debouncedOnChange( newTone );
130 setCustomTone( newTone );
131 } }
132 />
133 </div>
134 );
135 };
136
137 /**
138 * Properties for the ToneSelector component.
139 *
140 * @since 3.13.0
141 */
142 type ToneSelectorProps = {
143 tone: ToneProp | string;
144 onChange: ( tone: ToneProp | string ) => void;
145 onDropdownChange?: ( tone: ToneProp ) => void;
146 disabled?: boolean;
147 value?: string;
148 label?: string;
149 allowCustom?: boolean;
150 };
151
152 /**
153 * Tone Selector dropdown menu.
154 *
155 * Allows the user to select a tone for their AI generated content.
156 *
157 * @since 3.13.0
158 *
159 * @param {ToneSelectorProps} props The properties for the ToneSelector component.
160 */
161 export const ToneSelector = ( {
162 tone,
163 value = __( 'Select a tone', 'wp-parsely' ),
164 label = __( 'Tone', 'wp-parsely' ),
165 onChange,
166 onDropdownChange,
167 disabled = false,
168 allowCustom = false,
169 }: Readonly<ToneSelectorProps> ): React.JSX.Element => {
170 const togglePropsId = `parsely-tone-selector-${ useInstanceId( ToneSelector ) }`;
171
172 return (
173 <Disabled isDisabled={ disabled }>
174 <label htmlFor={ togglePropsId } className="wp-parsely-editor-sidebar-label">
175 { label }
176 </label>
177 <DropdownMenu
178 label={ __( 'Tone', 'wp-parsely' ) }
179 className={ 'parsely-tone-selector-dropdown' + ( disabled ? ' is-disabled' : '' ) }
180 popoverProps={ {
181 className: 'wp-parsely-popover',
182 } }
183 toggleProps={ {
184 id: togglePropsId,
185 children: (
186 <>
187 <div className="parsely-tone-selector-label">
188 { isCustomTone( tone ) ? PARSELY_TONES.custom.label : value }
189 </div>
190 <MoreArrow />
191 </>
192 ),
193 } }
194 >
195 { ( { onClose } ) => (
196 <MenuGroup label={ __( 'Select a tone', 'wp-parsely' ) }>
197 <>
198 { TONE_LIST.map( ( singleTone ) => {
199 if ( ! allowCustom && singleTone === 'custom' ) {
200 return null;
201 }
202
203 const toneData = PARSELY_TONES[ singleTone as FixedToneProp ];
204 const isSelected = singleTone === tone || ( isCustomTone( tone ) && singleTone === 'custom' );
205 return (
206 <MenuItem
207 key={ singleTone }
208 isSelected={ isSelected }
209 className={ isSelected ? 'is-selected' : '' }
210 role="menuitemradio"
211 onClick={ () => {
212 onDropdownChange?.( singleTone as FixedToneProp );
213 onChange( singleTone );
214 onClose();
215 // Focus the input when the custom tone is selected.
216 if ( singleTone === 'custom' ) {
217 // Wait for the input to be rendered.
218 setTimeout( () => {
219 const inputElement = document.querySelector( `.parsely-tone-selector-custom input` ) as HTMLInputElement;
220 if ( inputElement ) {
221 inputElement.focus();
222 }
223 }, 0 );
224 }
225 } }
226 >
227 { toneData.icon && <Icon icon={ toneData.icon } /> }
228 { toneData.label }
229 </MenuItem>
230 );
231 } ) }
232 </>
233 </MenuGroup>
234 ) }
235 </DropdownMenu>
236 {
237 allowCustom && isCustomTone( tone ) && (
238 <CustomTone
239 onChange={ ( currentTone ) => {
240 if ( '' === currentTone ) {
241 onChange( 'custom' );
242 return;
243 }
244
245 onChange( currentTone );
246 } }
247 value={ tone === 'custom' ? '' : tone }
248 />
249 )
250 }
251 </Disabled>
252 );
253 };
254