Skip to content
Closed
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
9 changes: 9 additions & 0 deletions frontend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,15 @@ module.exports = {
'no-empty': 'off',
'no-empty-pattern': 'off',
'no-nested-ternary': 'error',
'no-restricted-syntax': [
'error',
{
'message':
'InputGroup no longer renders custom controls. Use SelectField for a labelled Select, or FieldLabel + FieldError for other controls.',
'selector':
"JSXOpeningElement[name.name='InputGroup'] JSXAttribute[name.name='component']",
},
],
'no-unused-vars': 'off',
'no-var': 'error',
'object-curly-spacing': ['error', 'always'],
Expand Down
10 changes: 5 additions & 5 deletions frontend/common/utils/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -789,11 +789,11 @@ const Utils = Object.assign({}, BaseUtils, {
.replace(/[\s_]+/g, '-')
.toLowerCase(),

toSelectedValue: (
value: string,
options: { label: string; value: string }[],
defaultValue?: string,
) => {
toSelectedValue: <T extends { label: string; value?: unknown }>(
value: unknown,
options: T[] | undefined,
defaultValue?: T,
): T | undefined => {
return options?.find((option) => option.value === value) ?? defaultValue
},

Expand Down
74 changes: 33 additions & 41 deletions frontend/web/components/EditPermissions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { FC, forwardRef, useCallback, useEffect, useState } from 'react'
import FieldLabel from './base/forms/FieldLabel'
import { find } from 'lodash'
import { close as closeIcon } from 'ionicons/icons'
import { IonIcon } from '@ionic/react'
Expand Down Expand Up @@ -55,7 +56,6 @@ import {

import MyRoleSelect from './MyRoleSelect'
import Panel from './base/grid/Panel'
import InputGroup from './base/forms/InputGroup'
import classNames from 'classnames'
import OrganisationProvider from 'common/providers/OrganisationProvider'
import { useHasPermission } from 'common/providers/Permission'
Expand Down Expand Up @@ -934,47 +934,39 @@ const _EditPermissionsModal: FC<EditPermissionModalType> = withAdminPermissions(
</div>
{roles && level === 'organisation' && (
<FormGroup className='px-4'>
<InputGroup
component={
<div>
<Row>
<strong style={{ width: 70 }}>Roles: </strong>
{rolesAdded?.map((r) => (
<Row
key={r.id}
onClick={() => removeOwner(r.id)}
className='chip'
style={{ marginBottom: 4, marginTop: 4 }}
>
<span className='font-weight-bold'>{r.name}</span>
<span className='chip-icon ion'>
<IonIcon
icon={closeIcon}
style={{ fontSize: '13px' }}
/>
</span>
</Row>
))}
<Button
theme='text'
onClick={() => setShowRoles(true)}
style={{ width: 70 }}
<div className='form-group full-width'>
<FieldLabel tooltip='Assigns what role the user/group will have'>
Assign roles
</FieldLabel>
<div>
<Row>
<strong style={{ width: 70 }}>Roles: </strong>
{rolesAdded?.map((r) => (
<Row
key={r.id}
onClick={() => removeOwner(r.id)}
className='chip'
style={{ marginBottom: 4, marginTop: 4 }}
>
Add Role
</Button>
</Row>
</div>
}
type='text'
title='Assign roles'
tooltip='Assigns what role the user/group will have'
inputProps={{
className: 'full-width',
style: { minHeight: 80 },
}}
className='full-width'
placeholder='Add an optional description...'
/>
<span className='font-weight-bold'>{r.name}</span>
<span className='chip-icon ion'>
<IonIcon
icon={closeIcon}
style={{ fontSize: '13px' }}
/>
</span>
</Row>
))}
<Button
theme='text'
onClick={() => setShowRoles(true)}
style={{ width: 70 }}
>
Add Role
</Button>
</Row>
</div>
</div>
</FormGroup>
)}
{level !== 'environment' && level !== 'project' && (
Expand Down
95 changes: 44 additions & 51 deletions frontend/web/components/base/forms/InputGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ interface InputGroupProps {
tooltip?: string
tooltipPlace?: TooltipProps['place']
unsaved?: boolean
// Render an arbitrary control instead of the default Input/textarea.
component?: ReactNode
textarea?: boolean
// Legacy: consumers pass truthy/falsy non-booleans (e.g. `name && name.length`);
// coerced to a boolean before it reaches Input.
Expand Down Expand Up @@ -60,7 +58,6 @@ interface InputGroupProps {

const InputGroup: FC<InputGroupProps> = ({
className,
component,
'data-test': dataTest,
defaultValue,
disabled,
Expand Down Expand Up @@ -125,54 +122,50 @@ const InputGroup: FC<InputGroupProps> = ({
)}

<div>
{component ? (
component
) : (
<div>
{textarea ? (
<textarea
ref={(c) => {
inputRef.current = c
}}
{...(restInputProps as React.TextareaHTMLAttributes<HTMLTextAreaElement>)}
disabled={disabled}
value={value}
defaultValue={defaultValue}
data-test={dataTest}
onChange={onChange}
id={id}
aria-invalid={hasError}
aria-describedby={hasError ? errorId : undefined}
placeholder={placeholder}
onBlur={onBlur}
/>
) : (
<Input
ref={(c) => {
inputRef.current = c
}}
{...restInputProps}
isValid={
isValid === null || isValid === undefined
? undefined
: !!isValid
}
disabled={disabled}
defaultValue={defaultValue}
value={value}
data-test={dataTest}
onChange={onChange}
type={type || 'text'}
id={id}
aria-invalid={hasError}
aria-describedby={hasError ? errorId : undefined}
onBlur={onBlur}
placeholder={placeholder}
size={size}
/>
)}
</div>
)}
<div>
{textarea ? (
<textarea
ref={(c) => {
inputRef.current = c
}}
{...(restInputProps as React.TextareaHTMLAttributes<HTMLTextAreaElement>)}
disabled={disabled}
value={value}
defaultValue={defaultValue}
data-test={dataTest}
onChange={onChange}
id={id}
aria-invalid={hasError}
aria-describedby={hasError ? errorId : undefined}
placeholder={placeholder}
onBlur={onBlur}
/>
) : (
<Input
ref={(c) => {
inputRef.current = c
}}
{...restInputProps}
isValid={
isValid === null || isValid === undefined
? undefined
: !!isValid
}
disabled={disabled}
defaultValue={defaultValue}
value={value}
data-test={dataTest}
onChange={onChange}
type={type || 'text'}
id={id}
aria-invalid={hasError}
aria-describedby={hasError ? errorId : undefined}
onBlur={onBlur}
placeholder={placeholder}
size={size}
/>
)}
</div>
</div>
<FieldError id={errorId} error={errorContent} />
</div>
Expand Down
5 changes: 5 additions & 0 deletions frontend/web/components/base/forms/SelectField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,13 @@ function SelectField<Option = unknown, IsMulti extends boolean = false>({
{title}
</FieldLabel>
)}
{/* The legacy select SCSS (.react-select .react-select, .select-* size
blocks) only applies when the wrapper carries these classes, which
call sites historically passed by hand (or forgot to). SelectField
always sets them, so its selects are deterministically styled. */}
<Select
{...selectProps}
className={cn('react-select', selectProps.size)}
inputId={inputId}
data-test={dataTest}
aria-invalid={hasError || undefined}
Expand Down
41 changes: 19 additions & 22 deletions frontend/web/components/metadata/SupportedContentTypesSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { FC, useEffect, useState } from 'react'
import { useGetSupportedContentTypeQuery } from 'common/services/useSupportedContentType'
import { ContentType, MetadataFieldModelField } from 'common/types/responses'
import InputGroup from 'components/base/forms/InputGroup'
import SelectField from 'components/base/forms/SelectField'
import ContentTypesMetadataFieldTable from './ContentTypesMetadataFieldTable'

type SupportedContentTypesSelectType = {
Expand Down Expand Up @@ -64,28 +64,25 @@ const SupportedContentTypesSelect: FC<SupportedContentTypesSelectType> = ({

return (
<>
<InputGroup
<SelectField
title={'Entities'}
component={
<Select
placeholder='Select the Entity'
options={(supportedContentTypes || [])
.filter(
(v) =>
v.model !== 'project' &&
v.model !== 'organisation' &&
!selectedContentTypes.some((x) => x.value === `${v.id}`),
)
.map((v: ContentType) => ({
label: v.model,
value: `${v.id}`,
}))}
onChange={(v: SelectContentTypesType) => {
setSelectedContentTypes((prevState) => [...prevState, v])
}}
className='mb-4 react-select'
/>
}
placeholder='Select the Entity'
options={(supportedContentTypes || [])
.filter(
(v) =>
v.model !== 'project' &&
v.model !== 'organisation' &&
!selectedContentTypes.some((x) => x.value === `${v.id}`),
)
.map((v: ContentType) => ({
label: v.model,
value: `${v.id}`,
}))}
onChange={(v) => {
if (v) {
setSelectedContentTypes((prevState) => [...prevState, v])
}
}}
/>
{!!selectedContentTypes.length && (
<ContentTypesMetadataFieldTable
Expand Down
Loading
Loading