* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import styled from '@emotion/styled';
+import { ForwardedRef, forwardRef } from 'react';
import tw from 'twin.macro';
import { themeBorder, themeColor, themeContrast, themeShadow } from '../helpers';
import { CheckIcon } from './icons';
return typeof value === 'string' ? value === 'true' : value;
};
-export function Switch(props: Readonly<Props>) {
+function SwitchWithRef(props: Readonly<Props>, ref: ForwardedRef<HTMLButtonElement>) {
const { disabled, onChange, name, labels } = props;
const value = getValue(props.value);
disabled={disabled}
name={name}
onClick={handleClick}
+ ref={ref}
role="switch"
type="button"
>
active ? themeBorder('focus', 'switchActive') : themeBorder('focus', 'switch')};
}
`;
+
+export const Switch = forwardRef(SwitchWithRef);
}
export function TextError({
+ as,
text,
className,
}: Readonly<{
+ as?: React.ElementType;
className?: string;
text: string | React.ReactNode;
}>) {
if (typeof text === 'string') {
return (
- <StyledTextError className={className} title={text}>
+ <StyledTextError as={as} className={className} title={text}>
{text}
</StyledTextError>
);
}
- return <StyledTextError className={className}>{text}</StyledTextError>;
+ return (
+ <StyledTextError as={as} className={className}>
+ {text}
+ </StyledTextError>
+ );
}
export function TextSuccess({ text, className }: Readonly<{ className?: string; text: string }>) {
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import styled from '@emotion/styled';
-import { Children, Fragment, HtmlHTMLAttributes, ReactNode } from 'react';
+import { Children, ElementType, Fragment, HtmlHTMLAttributes, ReactNode } from 'react';
import tw from 'twin.macro';
import { themeBorder, themeColor } from '../../helpers/theme';
import { isDefined } from '../../helpers/types';
interface Props extends HtmlHTMLAttributes<HTMLDivElement> {
+ as?: ElementType;
children: ReactNode;
className?: string;
}
-export function SubnavigationGroup({ className, children, ...htmlProps }: Props) {
+export function SubnavigationGroup({ as, className, children, ...htmlProps }: Readonly<Props>) {
const childrenArray = Children.toArray(children).filter(isDefined);
return (
- <Group className={className} {...htmlProps}>
+ <Group as={as} className={className} {...htmlProps}>
{childrenArray.map((child, index) => (
<Fragment key={index}>
{child}
import withAvailableFeatures, {
WithAvailableFeaturesProps,
} from '../../../app/components/available-features/withAvailableFeatures';
+import { translate } from '../../../helpers/l10n';
import { getGlobalSettingsUrl, getProjectSettingsUrl } from '../../../helpers/urls';
import { Feature } from '../../../types/features';
import { Component } from '../../../types/types';
const sortedCategories = sortBy(categoriesWithName, (category) => category.name.toLowerCase());
return (
- <SubnavigationGroup className="sw-box-border it__subnavigation_menu">
+ <SubnavigationGroup
+ as="nav"
+ aria-label={translate('settings.page')}
+ className="sw-box-border it__subnavigation_menu"
+ >
{sortedCategories.map((c) => {
const category = c.key !== defaultCategory ? c.key.toLowerCase() : undefined;
+ const isActive = c.key.toLowerCase() === selectedCategory.toLowerCase();
return (
<SubnavigationItem
- active={c.key.toLowerCase() === selectedCategory.toLowerCase()}
+ active={isActive}
+ ariaCurrent={isActive}
onClick={() => openCategory(category)}
key={c.key}
>
<div className="sw-col-span-2">
<DocumentationLink to={DocLink.AnalysisScope}>
- {translate('learn_more')}
+ {translate('settings.analysis_scope.learn_more')}
</DocumentationLink>
</div>
</StyledGrid>
import {
combineDefinitionAndSettingValue,
getSettingValue,
+ getUniqueName,
isDefaultOrInherited,
isEmptyValue,
isURLKind,
const [success, setSuccess] = React.useState(false);
const [changedValue, setChangedValue] = React.useState<FieldValue>();
const [validationMessage, setValidationMessage] = React.useState<string>();
+ const ref = React.useRef<HTMLElement>(null);
+ const name = getUniqueName(definition);
const { data: loadedSettingValue, isLoading } = useGetValueQuery({
key: definition.key,
// WARNING: do *not* remove `?? undefined` below, it is required to change `null` to `undefined`!
// (Yes, it's ugly, we really shouldn't use `null` as the fallback value in useGetValueQuery)
+ // prettier-ignore
const settingValue = isLoading ? initialSettingValue : (loadedSettingValue ?? undefined);
const { mutateAsync: resetSettingValue } = useResetSettingsMutation();
setChangedValue(undefined);
setLoading(false);
setSuccess(true);
+ ref.current?.focus();
setValidationMessage(undefined);
timeout.current = window.setTimeout(() => {
const validationMessage = await parseError(e as Response);
setLoading(false);
setValidationMessage(validationMessage);
+ ref.current?.focus();
}
};
} else {
setValidationMessage(translate('settings.state.value_cant_be_empty'));
}
+ ref.current?.focus();
return false;
}
setValidationMessage(
translateWithParameters('settings.state.url_not_valid', value?.toString() ?? ''),
);
+ ref.current?.focus();
return false;
}
JSON.parse(value?.toString() ?? '');
} catch (e) {
setValidationMessage((e as Error).message);
+ ref.current?.focus();
return false;
}
if (isEmptyValue(definition, changedValue)) {
setValidationMessage(translate('settings.state.value_cant_be_empty'));
+ ref.current?.focus();
return;
}
setIsEditing(false);
setLoading(false);
setSuccess(true);
+ ref.current?.focus();
timeout.current = window.setTimeout(() => {
setSuccess(false);
const validationMessage = await parseError(e as Response);
setLoading(false);
setValidationMessage(validationMessage);
+ ref.current?.focus();
}
}
};
return (
<div data-key={definition.key} data-testid={definition.key} className="sw-flex sw-gap-12">
<DefinitionDescription definition={definition} />
-
<div className="sw-flex-1">
<form onSubmit={formNoop}>
<Input
+ ariaDescribedBy={`definition-stats-${name}`}
hasValueChanged={hasValueChanged}
onCancel={handleCancel}
onChange={handleChange}
onSave={handleSave}
onEditing={() => setIsEditing(true)}
+ ref={ref}
isEditing={isEditing}
isInvalid={hasError}
setting={settingDefinitionAndValue}
<div className="sw-mt-2">
{loading && (
- <div className="sw-flex">
- <Spinner />
+ <div id={`definition-stats-${name}`} className="sw-flex">
+ <Spinner aria-busy />
<Note className="sw-ml-2">{translate('settings.state.saving')}</Note>
</div>
)}
{!loading && validationMessage && (
- <div>
+ <div id={`definition-stats-${name}`}>
<TextError
+ as="output"
+ className="sw-whitespace-break-spaces"
text={translateWithParameters(
'settings.state.validation_failed',
validationMessage,
)}
{!loading && !hasError && success && (
- <FlagMessage variant="success">{translate('settings.state.saved')}</FlagMessage>
+ <FlagMessage id={`definition-stats-${name}`} variant="success">
+ {translate('settings.state.saved')}
+ </FlagMessage>
)}
</div>
<DefinitionActions
changedValue={changedValue}
+ definition={definition}
hasError={hasError}
hasValueChanged={hasValueChanged}
isDefault={isDefault}
import { Modal, Note } from 'design-system';
import * as React from 'react';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { Setting } from '../../../types/settings';
+import { ExtendedSettingDefinition, Setting } from '../../../types/settings';
import { getDefaultValue, getPropertyName, isEmptyValue } from '../utils';
type Props = {
changedValue?: string | string[] | boolean;
+ definition: ExtendedSettingDefinition;
hasError: boolean;
hasValueChanged: boolean;
isDefault: boolean;
}
render() {
- const { setting, changedValue, isDefault, isEditing, hasValueChanged, hasError } = this.props;
+ const { definition, setting, changedValue, isDefault, isEditing, hasValueChanged, hasError } =
+ this.props;
const hasBeenChangedToEmptyValue =
changedValue !== undefined && isEmptyValue(setting.definition, changedValue);
const showReset = hasBeenChangedToEmptyValue || (!isDefault && setting.hasValue);
const showCancel = hasValueChanged || isEditing;
+ const propertyName = getPropertyName(definition);
+ const saveButtonLabel = `${translate('save')} ${propertyName}`;
+ const cancelButtonLabel = `${translate('cancel')} ${propertyName}`;
return (
<div className="sw-mt-8">
<ButtonGroup className="sw-mr-3">
{hasValueChanged && (
<Button
+ aria-label={saveButtonLabel}
isDisabled={hasError}
onClick={this.props.onSave}
variety={ButtonVariety.Primary}
</Button>
)}
- {showCancel && <Button onClick={this.props.onCancel}>{translate('cancel')}</Button>}
+ {showCancel && (
+ <Button aria-label={cancelButtonLabel} onClick={this.props.onCancel}>
+ {translate('cancel')}
+ </Button>
+ )}
</ButtonGroup>
{showReset && (
return (
<div className="sw-w-abs-300">
- <SubHeading title={propertyName}>{propertyName}</SubHeading>
+ <SubHeading className="sw-text-ellipsis sw-overflow-hidden" title={propertyName}>
+ {propertyName}
+ </SubHeading>
{description && (
<div
<li className={noPadding ? '' : 'sw-p-6'} key={subCategory.key}>
{displaySubCategoryTitle && (
<SubTitle
- as="h3"
+ as="h2"
data-key={subCategory.key}
ref={this.scrollToSubCategoryOrDefinition}
>
securedInput: byRole('textbox', { name: 'property.sonar.announcement.message.secured.name' }),
multiValuesInput: byRole('textbox', { name: 'property.sonar.javascript.globals.name' }),
urlKindInput: byRole('textbox', { name: /sonar.auth.gitlab.url/ }),
- fieldsInput: (name: string) => byRole('textbox', { name: `property.${name}.name` }),
+ nameInput: byRole('textbox', { name: /property.name.name/ }),
+ valueInput: byRole('textbox', { name: /property.value.name/ }),
savedMsg: byText('settings.state.saved'),
validationMsg: byText(/settings.state.validation_failed/),
jsonFormatStatus: byText('settings.json.format_error'),
toggleButton: byRole('switch'),
selectOption: (name: string) => byRole('option', { name }),
selectInput: byRole('combobox', { name: 'property.test.single.select.list.name' }),
- saveButton: byRole('button', { name: 'save' }),
- cancelButton: byRole('button', { name: 'cancel' }),
+ saveButton: byRole('button', { name: /save/ }),
+ cancelButton: byRole('button', { name: /cancel/ }),
changeButton: byRole('button', { name: 'change_verb' }),
resetButton: (name: string | RegExp = 'reset_verb') => byRole('button', { name }),
deleteValueButton: byRole('button', {
expect(screen.getByRole('columnheader', { name: 'Value' })).toBeInTheDocument();
// Should type new values
- await user.type(ui.fieldsInput('name').get(), 'any name');
- expect(ui.fieldsInput('name').getAll()).toHaveLength(2);
+ await user.type(ui.nameInput.get(), 'any name');
+ expect(ui.nameInput.getAll()).toHaveLength(2);
// Can cancel changes
await user.click(ui.cancelButton.get());
- expect(ui.fieldsInput('name').getAll()).toHaveLength(1);
- expect(ui.fieldsInput('name').get()).toHaveValue('');
+ expect(ui.nameInput.getAll()).toHaveLength(1);
+ expect(ui.nameInput.get()).toHaveValue('');
// Can save new values
- await user.type(ui.fieldsInput('name').get(), 'any name');
- await user.type(ui.fieldsInput('value').getAll()[0], 'any value');
+ await user.type(ui.nameInput.get(), 'any name');
+ await user.type(ui.valueInput.getAll()[0], 'any value');
await user.click(ui.saveButton.get());
expect(ui.savedMsg.get()).toBeInTheDocument();
- expect(ui.fieldsInput('name').getAll()[0]).toHaveValue('any name');
- expect(ui.fieldsInput('value').getAll()[0]).toHaveValue('any value');
+ expect(ui.nameInput.getAll()[0]).toHaveValue('any name');
+ expect(ui.valueInput.getAll()[0]).toHaveValue('any value');
// Deleting previous value show validation message
await user.click(ui.deleteFieldsButton.get());
await user.click(ui.resetButton().get());
expect(ui.savedMsg.get()).toBeInTheDocument();
- expect(ui.fieldsInput('name').get()).toHaveValue('');
- expect(ui.fieldsInput('value').get()).toHaveValue('');
+ expect(ui.nameInput.get()).toHaveValue('');
+ expect(ui.valueInput.get()).toHaveValue('');
});
it('renders secured definition and can do operations', async () => {
jsFileSuffixesHeading: byRole('heading', {
name: 'property.sonar.javascript.file.suffixes.name',
}),
- jsGlobalVariablesInput: byRole('textbox', { name: 'property.sonar.javascript.globals.name' }),
+ jsGlobalVariablesInput: byRole('textbox', { name: /property.sonar.javascript.globals.name -/ }),
jsResetGlobalVariablesButton: byRole('button', {
name: 'settings.definition.reset.property.sonar.javascript.globals.name',
}),
validationMsg: byText('settings.state.validation_failed.A non empty value must be provided'),
- saveButton: byRole('button', { name: 'save' }),
- cancelButton: byRole('button', { name: 'cancel' }),
+ saveButton: byRole('button', { name: 'save property.sonar.javascript.globals.name' }),
+ cancelButton: byRole('button', { name: 'cancel property.sonar.javascript.globals.name' }),
resetButton: byRole('button', { name: 'reset_verb' }),
};
const workspacesDefinition = byTestId('sonar.auth.bitbucket.workspaces');
const ui = {
- save: byRole('button', { name: 'save' }),
- cancel: byRole('button', { name: 'cancel' }),
+ save: byRole('button', { name: /save/ }),
+ cancel: byRole('button', { name: /cancel/ }),
reset: byRole('button', { name: /settings.definition.reset/ }),
confirmReset: byRole('dialog').byRole('button', { name: 'reset_verb' }),
change: byRole('button', { name: 'change_verb' }),
import PrimitiveInput from './PrimitiveInput';
import PropertySetInput from './PropertySetInput';
-export default function Input(props: Readonly<DefaultInputProps>) {
+function Input(props: Readonly<DefaultInputProps>, ref: React.ForwardedRef<HTMLElement>) {
const { setting } = props;
const { definition } = setting;
const name = getUniqueName(definition);
- let Input: React.ComponentType<React.PropsWithChildren<DefaultSpecializedInputProps>> =
- PrimitiveInput;
+ let Input: React.ComponentType<
+ React.PropsWithChildren<DefaultSpecializedInputProps> & React.RefAttributes<HTMLElement>
+ > = PrimitiveInput;
if (isCategoryDefinition(definition) && definition.multiValues) {
Input = MultiValueInput;
}
if (isSecuredDefinition(definition)) {
- return <InputForSecured input={Input} {...props} />;
+ return <InputForSecured input={Input} ref={ref} {...props} />;
}
- return <Input {...props} name={name} isDefault={isDefaultOrInherited(setting)} />;
+ return <Input {...props} name={name} ref={ref} isDefault={isDefaultOrInherited(setting)} />;
}
+
+export default React.forwardRef(Input);
value: string | boolean | undefined;
}
-export default function InputForBoolean({ onChange, name, value, setting }: Props) {
+function InputForBoolean(
+ { onChange, name, value, setting, ...other }: Readonly<Props>,
+ ref: React.ForwardedRef<HTMLButtonElement>,
+) {
const toggleValue = getToggleValue(value != null ? value : false);
const propertyName = getPropertyName(setting.definition);
<Switch
name={name}
onChange={onChange}
+ ref={ref}
value={toggleValue}
labels={{
on: propertyName,
off: propertyName,
}}
+ {...other}
/>
{value == null && <Note className="sw-ml-2">{translate('settings.not_set')}</Note>}
</div>
function getToggleValue(value: string | boolean) {
return typeof value === 'string' ? value === 'true' : value;
}
+
+export default React.forwardRef(InputForBoolean);
import { sanitizeUserInput } from '../../../../helpers/sanitize';
import { DefaultSpecializedInputProps, getPropertyName } from '../../utils';
-export default function InputForFormattedText(props: DefaultSpecializedInputProps) {
+function InputForFormattedText(
+ props: DefaultSpecializedInputProps,
+ ref: React.ForwardedRef<HTMLTextAreaElement>,
+) {
const { isEditing, setting, name, value } = props;
const { values, hasValue } = setting;
const editMode = !hasValue || isEditing;
className="settings-large-input sw-mr-2"
name={name}
onChange={handleInputChange}
+ ref={ref}
rows={5}
value={value || ''}
/>
overflow-wrap: break-word;
line-height: 1.5;
`;
+
+export default React.forwardRef(InputForFormattedText);
const JSON_SPACE_SIZE = 4;
+interface Props extends DefaultSpecializedInputProps {
+ innerRef: React.ForwardedRef<HTMLTextAreaElement>;
+}
+
interface State {
formatError: boolean;
}
-export default class InputForJSON extends React.PureComponent<DefaultSpecializedInputProps, State> {
+class InputForJSON extends React.PureComponent<Props, State> {
state: State = { formatError: false };
handleInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
};
render() {
- const { value, name, setting, isInvalid } = this.props;
+ const { value, name, innerRef, setting, isInvalid } = this.props;
const { formatError } = this.state;
return (
size="large"
name={name}
onChange={this.handleInputChange}
+ ref={innerRef}
rows={5}
value={value || ''}
aria-label={getPropertyName(setting.definition)}
);
}
}
+
+export default React.forwardRef(
+ (props: DefaultSpecializedInputProps, ref: React.ForwardedRef<HTMLTextAreaElement>) => (
+ <InputForJSON innerRef={ref} {...props} />
+ ),
+);
import { DefaultSpecializedInputProps } from '../../utils';
import SimpleInput from './SimpleInput';
-export default function InputForNumber(props: DefaultSpecializedInputProps) {
- return <SimpleInput size="small" type="text" {...props} />;
+function InputForNumber(
+ props: DefaultSpecializedInputProps,
+ ref: React.ForwardedRef<HTMLInputElement>,
+) {
+ return <SimpleInput ref={ref} size="small" type="text" {...props} />;
}
+
+export default React.forwardRef(InputForNumber);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import * as React from 'react';
import { DefaultSpecializedInputProps } from '../../utils';
import SimpleInput from './SimpleInput';
-export default function InputForPassword(props: DefaultSpecializedInputProps) {
- return <SimpleInput {...props} size="large" type="password" autoComplete="off" />;
+function InputForPassword(
+ props: DefaultSpecializedInputProps,
+ ref: React.ForwardedRef<HTMLInputElement>,
+) {
+ return <SimpleInput {...props} ref={ref} size="large" type="password" autoComplete="off" />;
}
+
+export default React.forwardRef(InputForPassword);
}
interface Props extends DefaultInputProps {
- input: React.ComponentType<React.PropsWithChildren<DefaultSpecializedInputProps>>;
+ innerRef: React.ForwardedRef<HTMLElement>;
+ input: React.ComponentType<
+ React.PropsWithChildren<DefaultSpecializedInputProps> & React.RefAttributes<HTMLElement>
+ >;
}
-export default class InputForSecured extends React.PureComponent<Props, State> {
+class InputForSecured extends React.PureComponent<Props, State> {
state: State = {
changing: !this.props.setting.hasValue,
};
};
renderInput() {
- const { input: Input, setting, value } = this.props;
+ const { input: Input, innerRef, setting, value } = this.props;
const name = getUniqueName(setting.definition);
return (
// The input hidden will prevent browser asking for saving login information
aria-label={getPropertyName(setting.definition)}
autoComplete="off"
className="js-setting-input"
+ id={`input-${name}`}
isDefault={isDefaultOrInherited(setting)}
name={name}
onChange={this.handleInputChange}
+ ref={innerRef}
setting={setting}
size="large"
type="password"
);
}
}
+
+export default React.forwardRef(
+ (props: Omit<Props, 'innerRef'>, ref: React.ForwardedRef<HTMLElement>) => (
+ <InputForSecured innerRef={ref} {...props} />
+ ),
+);
type Props = DefaultSpecializedInputProps & Pick<ExtendedSettingDefinition, 'options'>;
-export default function InputForSingleSelectList(props: Readonly<Props>) {
+function InputForSingleSelectList(
+ props: Readonly<Props>,
+ ref: React.ForwardedRef<HTMLInputElement>,
+) {
const { name, options: opts, value, setting } = props;
const options = React.useMemo(
isNotClearable
name={name}
onChange={props.onChange}
+ ref={ref}
size={InputSize.Large}
value={value}
/>
);
}
+
+export default React.forwardRef(InputForSingleSelectList);
import { DefaultSpecializedInputProps } from '../../utils';
import SimpleInput from './SimpleInput';
-export default function InputForString(props: DefaultSpecializedInputProps) {
- return <SimpleInput size="large" type="text" {...props} />;
+function InputForString(
+ props: DefaultSpecializedInputProps,
+ ref: React.ForwardedRef<HTMLInputElement>,
+) {
+ return <SimpleInput ref={ref} size="large" type="text" {...props} />;
}
+
+export default React.forwardRef(InputForString);
import * as React from 'react';
import { DefaultSpecializedInputProps, getPropertyName } from '../../utils';
-export default class InputForText extends React.PureComponent<DefaultSpecializedInputProps> {
+interface Props extends DefaultSpecializedInputProps {
+ innerRef: React.ForwardedRef<HTMLTextAreaElement>;
+}
+
+class InputForText extends React.PureComponent<Props> {
handleInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
this.props.onChange(event.target.value);
};
render() {
- const { setting, name, value } = this.props;
+ const { setting, name, innerRef, value } = this.props;
return (
<InputTextArea
size="large"
name={name}
onChange={this.handleInputChange}
+ ref={innerRef}
rows={5}
value={value || ''}
aria-label={getPropertyName(setting.definition)}
);
}
}
+
+export default React.forwardRef(
+ (props: DefaultSpecializedInputProps, ref: React.ForwardedRef<HTMLTextAreaElement>) => (
+ <InputForText innerRef={ref} {...props} />
+ ),
+);
import { DefaultSpecializedInputProps, getEmptyValue, getPropertyName } from '../../utils';
import PrimitiveInput from './PrimitiveInput';
-export default class MultiValueInput extends React.PureComponent<DefaultSpecializedInputProps> {
+interface Props extends DefaultSpecializedInputProps {
+ innerRef: React.ForwardedRef<HTMLInputElement>;
+}
+
+class MultiValueInput extends React.PureComponent<Props> {
ensureValue = () => {
return this.props.value || [];
};
};
renderInput(value: any, index: number, isLast: boolean) {
- const { setting, isDefault, name } = this.props;
+ const { ariaDescribedBy, setting, isDefault, name, innerRef } = this.props;
return (
<li className="sw-flex sw-items-center sw-mb-2" key={index}>
<PrimitiveInput
+ ariaDescribedBy={ariaDescribedBy}
+ index={index}
isDefault={isDefault}
name={name}
hasValueChanged={this.props.hasValueChanged}
onChange={(value) => this.handleSingleInputChange(index, value)}
+ ref={index === 0 ? innerRef : null}
setting={setting}
value={value}
/>
);
}
}
+
+export default React.forwardRef(
+ (props: DefaultSpecializedInputProps, ref: React.ForwardedRef<HTMLInputElement>) => (
+ <MultiValueInput innerRef={ref} {...props} />
+ ),
+);
import InputForString from './InputForString';
import InputForText from './InputForText';
-function withOptions(
- options: string[],
-): React.ComponentType<React.PropsWithChildren<DefaultSpecializedInputProps>> {
- return function Wrapped(props: DefaultSpecializedInputProps) {
- return <InputForSingleSelectList options={options} {...props} />;
- };
-}
-
-export default function PrimitiveInput(props: DefaultSpecializedInputProps) {
- const { setting, name, isDefault, ...other } = props;
+function PrimitiveInput(
+ props: DefaultSpecializedInputProps,
+ ref: React.ForwardedRef<HTMLInputElement>,
+) {
+ const { ariaDescribedBy, setting, name, isDefault, ...other } = props;
const { definition } = setting;
const typeMapping: {
[type in SettingType]?: React.ComponentType<
- React.PropsWithChildren<DefaultSpecializedInputProps>
+ React.PropsWithChildren<DefaultSpecializedInputProps & { options?: string[] }>
>;
} = React.useMemo(
() => ({
INTEGER: InputForNumber,
LONG: InputForNumber,
FLOAT: InputForNumber,
- SINGLE_SELECT_LIST: withOptions(definition.options),
+ SINGLE_SELECT_LIST: InputForSingleSelectList,
FORMATTED_TEXT: InputForFormattedText,
}),
[definition.options],
);
const InputComponent = (definition.type && typeMapping[definition.type]) || InputForString;
+ let id = `input-${name}`;
+ if (typeof props.index === 'number') {
+ id = `${id}-${props.index}`;
+ }
- return <InputComponent isDefault={isDefault} name={name} setting={setting} {...other} />;
+ return (
+ <InputComponent
+ ariaDescribedBy={ariaDescribedBy}
+ id={id}
+ isDefault={isDefault}
+ name={name}
+ options={definition.type === SettingType.SINGLE_SELECT_LIST ? definition.options : undefined}
+ setting={setting}
+ ref={ref}
+ {...other}
+ />
+ );
}
+
+export default React.forwardRef(PrimitiveInput);
} from '../../utils';
import PrimitiveInput from './PrimitiveInput';
-export default class PropertySetInput extends React.PureComponent<DefaultSpecializedInputProps> {
+interface Props extends DefaultSpecializedInputProps {
+ innerRef: React.ForwardedRef<HTMLInputElement>;
+}
+
+class PropertySetInput extends React.PureComponent<Props> {
ensureValue() {
return this.props.value || [];
}
};
renderFields(fieldValues: any, index: number, isLast: boolean) {
- const { setting, isDefault } = this.props;
+ const { ariaDescribedBy, setting, isDefault, innerRef } = this.props;
const { definition } = setting;
return (
<TableRow key={index}>
{isCategoryDefinition(definition) &&
- definition.fields.map((field) => {
+ definition.fields.map((field, idx) => {
const newSetting = {
...setting,
definition: field,
return (
<ContentCell className="sw-py-2 sw-border-0" key={field.key}>
<PrimitiveInput
+ ariaDescribedBy={ariaDescribedBy}
+ index={index}
isDefault={isDefault}
hasValueChanged={this.props.hasValueChanged}
name={getUniqueName(definition, field.key)}
onChange={(value) => this.handleInputChange(index, field.key, value)}
+ ref={index === 0 && idx === 0 ? innerRef : null}
setting={newSetting}
size="full"
value={fieldValues[field.key]}
);
}
}
+
+export default React.forwardRef(
+ (props: DefaultSpecializedInputProps, ref: React.ForwardedRef<HTMLInputElement>) => (
+ <PropertySetInput innerRef={ref} {...props} />
+ ),
+);
import { DefaultSpecializedInputProps, getPropertyName } from '../../utils';
export interface SimpleInputProps extends DefaultSpecializedInputProps {
+ innerRef: React.ForwardedRef<HTMLInputElement>;
value: string | number;
}
-export default class SimpleInput extends React.PureComponent<SimpleInputProps> {
+class SimpleInput extends React.PureComponent<SimpleInputProps> {
handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.props.onChange(event.currentTarget.value);
};
render() {
const {
+ ariaDescribedBy,
autoComplete,
autoFocus,
className,
+ index,
+ innerRef,
isInvalid,
name,
value = '',
size,
type,
} = this.props;
+
+ let label = getPropertyName(setting.definition);
+ if (typeof index === 'number') {
+ label = label.concat(` - ${index + 1}`);
+ }
+
return (
<InputField
+ aria-describedby={ariaDescribedBy}
+ id={`input-${name}-${index}`}
isInvalid={isInvalid}
autoComplete={autoComplete}
autoFocus={autoFocus}
name={name}
onChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
+ ref={innerRef}
type={type}
value={value}
size={size}
- aria-label={getPropertyName(setting.definition)}
+ aria-label={label}
/>
);
}
}
+
+export default React.forwardRef(
+ (props: Omit<SimpleInputProps, 'innerRef'>, ref: React.ForwardedRef<HTMLInputElement>) => (
+ <SimpleInput innerRef={ref} {...props} />
+ ),
+);
export type DefaultSpecializedInputProps = DefaultInputProps & {
autoComplete?: string;
className?: string;
+ index?: number;
isDefault: boolean;
name: string;
type?: string;
};
export interface DefaultInputProps {
+ ariaDescribedBy?: string;
autoFocus?: boolean;
hasValueChanged?: boolean;
+ id?: string;
isEditing?: boolean;
isInvalid?: boolean;
onCancel?: () => void;
settings.analysis_scope.wildcards.zero_more_char=Match zero or more characters
settings.analysis_scope.wildcards.zero_more_dir=Match zero or more directories
settings.analysis_scope.wildcards.single_char=Match a single character
+settings.analysis_scope.learn_more=Read more about analysis scope
settings.new_code_period.category=New Code
settings.new_code_period.title=New Code