Merge pull request #8454 from lukzerom/select-changes-1
Select and Autocomplete select readonly when one option available
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
In @backstage/plugin-scaffolder - When user will have one option available in hostUrl or owner - autoselect and select component should be readonly.
|
||||
|
||||
in @backstage/core-components - Select component has extended API with few more props: native : boolean, disabled: boolean. native - if set to true - Select component will use native browser select picker (not rendered by Material UI lib ).
|
||||
disabled - if set to true - action on component will not be possible.
|
||||
@@ -12,6 +12,7 @@ Atlassian
|
||||
automations
|
||||
autoscaling
|
||||
Autoscaling
|
||||
autoselect
|
||||
Avro
|
||||
backrub
|
||||
Bigtable
|
||||
|
||||
@@ -814,13 +814,10 @@ export type ResponseErrorPanelClassKey = 'text' | 'divider';
|
||||
export function RoutedTabs(props: { routes: SubRoute_2[] }): JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "SelectProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "SelectComponent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function Select(props: SelectProps): JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SelectClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type SelectClassKey =
|
||||
| 'formControl'
|
||||
@@ -830,11 +827,18 @@ export type SelectClassKey =
|
||||
| 'checkbox'
|
||||
| 'root';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SelectInputBaseClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type SelectedItems = string | string[] | number | number[];
|
||||
|
||||
// @public (undocumented)
|
||||
export type SelectInputBaseClassKey = 'root' | 'input';
|
||||
|
||||
// @public (undocumented)
|
||||
export type SelectItem = {
|
||||
label: string;
|
||||
value: string | number;
|
||||
};
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "Sidebar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
|
||||
@@ -21,17 +21,18 @@ import FormControl from '@material-ui/core/FormControl';
|
||||
import InputBase from '@material-ui/core/InputBase';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import {
|
||||
createStyles,
|
||||
makeStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
} from '@material-ui/core/styles';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ClosedDropdown from './static/ClosedDropdown';
|
||||
import OpenedDropdown from './static/OpenedDropdown';
|
||||
|
||||
/** @public */
|
||||
export type SelectInputBaseClassKey = 'root' | 'input';
|
||||
|
||||
const BootstrapInput = withStyles(
|
||||
@@ -60,6 +61,7 @@ const BootstrapInput = withStyles(
|
||||
{ name: 'BackstageSelectInputBase' },
|
||||
)(InputBase);
|
||||
|
||||
/** @public */
|
||||
export type SelectClassKey =
|
||||
| 'formControl'
|
||||
| 'label'
|
||||
@@ -93,6 +95,7 @@ const useStyles = makeStyles(
|
||||
margin: 2,
|
||||
},
|
||||
checkbox: {},
|
||||
|
||||
root: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -101,23 +104,28 @@ const useStyles = makeStyles(
|
||||
{ name: 'BackstageSelect' },
|
||||
);
|
||||
|
||||
type Item = {
|
||||
/** @public */
|
||||
export type SelectItem = {
|
||||
label: string;
|
||||
value: string | number;
|
||||
};
|
||||
|
||||
type Selection = string | string[] | number | number[];
|
||||
/** @public */
|
||||
export type SelectedItems = string | string[] | number | number[];
|
||||
|
||||
export type SelectProps = {
|
||||
multiple?: boolean;
|
||||
items: Item[];
|
||||
items: SelectItem[];
|
||||
label: string;
|
||||
placeholder?: string;
|
||||
selected?: Selection;
|
||||
onChange: (arg: Selection) => void;
|
||||
selected?: SelectedItems;
|
||||
onChange: (arg: SelectedItems) => void;
|
||||
triggerReset?: boolean;
|
||||
native?: boolean;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export function SelectComponent(props: SelectProps) {
|
||||
const {
|
||||
multiple,
|
||||
@@ -127,9 +135,11 @@ export function SelectComponent(props: SelectProps) {
|
||||
selected,
|
||||
onChange,
|
||||
triggerReset,
|
||||
native = false,
|
||||
disabled = false,
|
||||
} = props;
|
||||
const classes = useStyles();
|
||||
const [value, setValue] = useState<Selection>(
|
||||
const [value, setValue] = useState<SelectedItems>(
|
||||
selected || (multiple ? [] : ''),
|
||||
);
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
@@ -145,11 +155,15 @@ export function SelectComponent(props: SelectProps) {
|
||||
}, [selected]);
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
|
||||
setValue(event.target.value as Selection);
|
||||
onChange(event.target.value as Selection);
|
||||
setValue(event.target.value as SelectedItems);
|
||||
onChange(event.target.value as SelectedItems);
|
||||
};
|
||||
|
||||
const handleClick = (event: React.ChangeEvent<any>) => {
|
||||
if (disabled) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
setOpen(previous => {
|
||||
if (multiple && !(event.target instanceof HTMLElement)) {
|
||||
return true;
|
||||
@@ -175,6 +189,8 @@ export function SelectComponent(props: SelectProps) {
|
||||
<FormControl className={classes.formControl}>
|
||||
<Select
|
||||
value={value}
|
||||
native={native}
|
||||
disabled={disabled}
|
||||
data-testid="select"
|
||||
displayEmpty
|
||||
multiple={multiple}
|
||||
@@ -223,19 +239,26 @@ export function SelectComponent(props: SelectProps) {
|
||||
{placeholder && !multiple && (
|
||||
<MenuItem value={[]}>{placeholder}</MenuItem>
|
||||
)}
|
||||
{items &&
|
||||
items.map(item => (
|
||||
<MenuItem key={item.value} value={item.value}>
|
||||
{multiple && (
|
||||
<Checkbox
|
||||
color="primary"
|
||||
checked={(value as any[]).includes(item.value) || false}
|
||||
className={classes.checkbox}
|
||||
/>
|
||||
)}
|
||||
{item.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
{native
|
||||
? items &&
|
||||
items.map(item => (
|
||||
<option value={item.value} key={item.value}>
|
||||
{item.label}
|
||||
</option>
|
||||
))
|
||||
: items &&
|
||||
items.map(item => (
|
||||
<MenuItem key={item.value} value={item.value}>
|
||||
{multiple && (
|
||||
<Checkbox
|
||||
color="primary"
|
||||
checked={(value as any[]).includes(item.value) || false}
|
||||
className={classes.checkbox}
|
||||
/>
|
||||
)}
|
||||
{item.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</ClickAwayListener>
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
*/
|
||||
|
||||
export { SelectComponent as Select } from './Select';
|
||||
export type { SelectClassKey, SelectInputBaseClassKey } from './Select';
|
||||
export type {
|
||||
SelectClassKey,
|
||||
SelectedItems,
|
||||
SelectInputBaseClassKey,
|
||||
SelectItem,
|
||||
} from './Select';
|
||||
export type { ClosedDropdownClassKey } from './static/ClosedDropdown';
|
||||
export type { OpenedDropdownClassKey } from './static/OpenedDropdown';
|
||||
|
||||
@@ -22,7 +22,7 @@ import { TextField } from '@material-ui/core';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
import { FieldProps } from '@rjsf/core';
|
||||
import React from 'react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
export const EntityPicker = ({
|
||||
@@ -48,9 +48,18 @@ export const EntityPicker = ({
|
||||
formatEntityRefTitle(e, { defaultKind }),
|
||||
);
|
||||
|
||||
const onSelect = (_: any, value: string | null) => {
|
||||
onChange(value || '');
|
||||
};
|
||||
const onSelect = useCallback(
|
||||
(_: any, value: string | null) => {
|
||||
onChange(value || '');
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (entityRefs?.length === 1) {
|
||||
onChange(entityRefs[0]);
|
||||
}
|
||||
}, [entityRefs, onChange]);
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
@@ -59,6 +68,7 @@ export const EntityPicker = ({
|
||||
error={rawErrors?.length > 0 && !formData}
|
||||
>
|
||||
<Autocomplete
|
||||
disabled={entityRefs?.length === 1}
|
||||
id={idSchema?.$id}
|
||||
value={(formData as string) || ''}
|
||||
loading={loading}
|
||||
|
||||
@@ -13,19 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { FieldProps } from '@rjsf/core';
|
||||
import { scaffolderApiRef } from '../../../api';
|
||||
import {
|
||||
Progress,
|
||||
Select,
|
||||
SelectedItems,
|
||||
SelectItem,
|
||||
} from '@backstage/core-components';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
import { useAsync } from 'react-use';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import InputLabel from '@material-ui/core/InputLabel';
|
||||
import Input from '@material-ui/core/Input';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { Progress } from '@backstage/core-components';
|
||||
import Input from '@material-ui/core/Input';
|
||||
import InputLabel from '@material-ui/core/InputLabel';
|
||||
import { FieldProps } from '@rjsf/core';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { scaffolderApiRef } from '../../../api';
|
||||
|
||||
function splitFormData(url: string | undefined, allowedOwners?: string[]) {
|
||||
let host = undefined;
|
||||
@@ -106,10 +109,10 @@ export const RepoUrlPicker = ({
|
||||
allowedOwners,
|
||||
);
|
||||
const updateHost = useCallback(
|
||||
(evt: React.ChangeEvent<{ name?: string; value: unknown }>) => {
|
||||
(value: SelectedItems) => {
|
||||
onChange(
|
||||
serializeFormData({
|
||||
host: evt.target.value as string,
|
||||
host: value as string,
|
||||
owner,
|
||||
repo,
|
||||
organization,
|
||||
@@ -121,6 +124,21 @@ export const RepoUrlPicker = ({
|
||||
[onChange, owner, repo, organization, workspace, project],
|
||||
);
|
||||
|
||||
const updateOwnerSelect = useCallback(
|
||||
(value: SelectedItems) =>
|
||||
onChange(
|
||||
serializeFormData({
|
||||
host,
|
||||
owner: value as string,
|
||||
repo,
|
||||
organization,
|
||||
workspace,
|
||||
project,
|
||||
}),
|
||||
),
|
||||
[onChange, host, repo, organization, workspace, project],
|
||||
);
|
||||
|
||||
const updateOwner = useCallback(
|
||||
(evt: React.ChangeEvent<{ name?: string; value: unknown }>) =>
|
||||
onChange(
|
||||
@@ -224,6 +242,16 @@ export const RepoUrlPicker = ({
|
||||
return <Progress />;
|
||||
}
|
||||
|
||||
const hostsOptions: SelectItem[] = integrations
|
||||
? integrations
|
||||
.filter(i => allowedHosts?.includes(i.host))
|
||||
.map(i => ({ label: i.title, value: i.host }))
|
||||
: [{ label: 'Loading...', value: 'loading' }];
|
||||
|
||||
const ownersOptions: SelectItem[] = allowedOwners
|
||||
? allowedOwners.map(i => ({ label: i, value: i }))
|
||||
: [{ label: 'Loading...', value: 'loading' }];
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormControl
|
||||
@@ -231,20 +259,15 @@ export const RepoUrlPicker = ({
|
||||
required
|
||||
error={rawErrors?.length > 0 && !host}
|
||||
>
|
||||
<InputLabel htmlFor="hostInput">Host</InputLabel>
|
||||
<Select native id="hostInput" onChange={updateHost} value={host}>
|
||||
{integrations ? (
|
||||
integrations
|
||||
.filter(i => allowedHosts?.includes(i.host))
|
||||
.map(i => (
|
||||
<option key={i.host} value={i.host}>
|
||||
{i.title}
|
||||
</option>
|
||||
))
|
||||
) : (
|
||||
<p>loading</p>
|
||||
)}
|
||||
</Select>
|
||||
<Select
|
||||
native
|
||||
disabled={hostsOptions.length === 1}
|
||||
label="Host"
|
||||
onChange={updateHost}
|
||||
selected={host}
|
||||
items={hostsOptions}
|
||||
/>
|
||||
|
||||
<FormHelperText>
|
||||
The host where the repository will be created
|
||||
</FormHelperText>
|
||||
@@ -330,24 +353,15 @@ export const RepoUrlPicker = ({
|
||||
required
|
||||
error={rawErrors?.length > 0 && !owner}
|
||||
>
|
||||
<InputLabel htmlFor="ownerInput">Owner Available</InputLabel>
|
||||
<Select
|
||||
native
|
||||
id="ownerInput"
|
||||
onChange={updateOwner}
|
||||
value={owner}
|
||||
>
|
||||
{allowedOwners ? (
|
||||
allowedOwners.map(i => (
|
||||
<option key={i} value={i}>
|
||||
{i}
|
||||
</option>
|
||||
))
|
||||
) : (
|
||||
<p>loading</p>
|
||||
)}
|
||||
;
|
||||
</Select>
|
||||
label="Owner Available"
|
||||
onChange={updateOwnerSelect}
|
||||
disabled={ownersOptions.length === 1}
|
||||
selected={owner}
|
||||
items={ownersOptions}
|
||||
/>
|
||||
|
||||
<FormHelperText>
|
||||
The organization, user or project that this repo will belong to
|
||||
</FormHelperText>
|
||||
|
||||
Reference in New Issue
Block a user