Skip to content

feat: blocking deletion and addition of array elements #242

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 5 additions & 1 deletion src/lib/core/components/Form/Controller/Controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface ControllerProps<DirtyValue extends FieldValue, SpecType extends
) => void)
| null;
parentOnUnmount: ((childName: string) => void) | null;
additionalContentLayout?: React.ReactNode;
}

export const Controller = <
Expand All @@ -33,6 +34,7 @@ export const Controller = <
value: valueFromParent,
parentOnChange,
parentOnUnmount,
additionalContentLayout,
}: ControllerProps<DirtyValue, SpecType>) => {
const {config, tools, mutatorsStore, __mirror} = useDynamicFormsCtx();

Expand All @@ -47,6 +49,7 @@ export const Controller = <
tools,
parentOnChange,
parentOnUnmount,
additionalContentLayout,
}),
);

Expand Down Expand Up @@ -120,8 +123,9 @@ export const Controller = <
onDrop: methods.onDrop,
},
meta: {...omit(store.state, 'value'), submitFailed: store.tools.submitFailed},
additionalContentLayout: additionalContentLayout,
}),
[methods, store.name, store.state, store.tools.submitFailed],
[methods, store.name, store.state, store.tools.submitFailed, additionalContentLayout],
);

const withSearch = useSearch(store.spec, store.state.value, store.name);
Expand Down
4 changes: 4 additions & 0 deletions src/lib/core/components/Form/Controller/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import {FormValue, Spec} from '../../../types';
import {
DynamicFormConfig,
Expand Down Expand Up @@ -42,6 +43,7 @@ export interface GetRenderParams<
| InputEntity<DirtyValue, undefined, undefined, SpecType>
| IndependentInputEntity<DirtyValue, undefined, undefined, SpecType>;
Layout?: LayoutType<DirtyValue, undefined, undefined, SpecType>;
additionalContentLayout?: React.ReactNode;
}

export interface GetValidateParams<SpecType extends Spec> {
Expand Down Expand Up @@ -110,6 +112,7 @@ export interface InitializeStoreParams<DirtyValue extends FieldValue, SpecType e
) => void)
| null;
parentOnUnmount: ((childName: string) => void) | null;
additionalContentLayout?: React.ReactNode;
}

export interface ControllerStore<
Expand Down Expand Up @@ -148,6 +151,7 @@ export interface ControllerStore<
childErrors: Record<string, ValidateError>;
};
afterStoreUpdateCB?: () => void;
additionalContentLayout?: React.ReactNode;
}

export interface UpdateStoreParams<
Expand Down
12 changes: 10 additions & 2 deletions src/lib/core/components/Form/Controller/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export const getRender = <DirtyValue extends FieldValue, SpecType extends Spec>(
spec,
inputEntity,
Layout,
additionalContentLayout,
}: GetRenderParams<DirtyValue, SpecType>) => {
const render = (props: FieldRenderProps<DirtyValue>) => {
if (inputEntity && isCorrectSpec(spec) && isString(name)) {
Expand Down Expand Up @@ -169,7 +170,13 @@ export const getRender = <DirtyValue extends FieldValue, SpecType extends Spec>(

if (Layout) {
return (
<Layout spec={spec} name={name} layoutProps={layoutProps} {...props}>
<Layout
spec={spec}
name={name}
layoutProps={layoutProps}
additionalContentLayout={additionalContentLayout}
{...props}
>
{input}
</Layout>
);
Expand Down Expand Up @@ -467,10 +474,11 @@ export const initializeStore = <
tools,
parentOnChange,
parentOnUnmount,
additionalContentLayout,
}: InitializeStoreParams<DirtyValue, SpecType>): ControllerStore<DirtyValue, Value, SpecType> => {
const spec = getSpec({name, spec: _spec, mutatorsStore});
const components = getComponents<DirtyValue, Value, SpecType>({spec, config});
const render = getRender({name, spec, ...components});
const render = getRender({name, spec, ...components, additionalContentLayout});
const validate = getValidate({spec, config});
const state = getFieldInitials({
name,
Expand Down
1 change: 1 addition & 0 deletions src/lib/core/components/Form/types/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type LayoutProps<
> = {
children: React.ReactElement;
layoutProps?: LayoutComponentProps;
additionalContentLayout?: React.ReactNode;
} & Omit<InputProps<Value, InputComponentProps, LayoutComponentProps, SpecType>, 'inputProps'>;

export type LayoutType<
Expand Down
1 change: 1 addition & 0 deletions src/lib/core/types/specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface ArraySpec<
placement?: 'horizontal' | 'vertical';
disabled?: Record<string, boolean>;
};
enableLockLength?: boolean;
};
}

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/lib/kit/components/Inputs/ArrayBase/ArrayBase.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@
margin-left: 4px;
}
}

&__remove-button {
margin-left: 4px;
}
}
74 changes: 64 additions & 10 deletions src/lib/kit/components/Inputs/ArrayBase/ArrayBase.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';

import {Plus} from '@gravity-ui/icons';
import {Button, Icon, Label} from '@gravity-ui/uikit';
import {Plus, TrashBin} from '@gravity-ui/icons';
import {Button, Icon, Label, Popover} from '@gravity-ui/uikit';
import set from 'lodash/set';

import {
Expand All @@ -21,6 +21,7 @@ import {
transformArrIn,
} from '../../../../core';
import {block} from '../../../utils';
import i18n from '../../../i18n';

import './ArrayBase.scss';

Expand All @@ -42,6 +43,20 @@ export const ArrayBase: ArrayInput = ({spec, name, arrayInput, input}) => {
return isBooleanSpec(spec.items) || isNumberSpec(spec.items) || isStringSpec(spec.items);
}, [spec.items]);

const disabledButtonLength = React.useMemo(() => {
if (spec.viewSpec.enableLockLength) {
return {
remove: keys.length <= (spec.minLength || 0),
add: keys.length >= (spec.maxLength || Infinity),
};
}

return {
remove: false,
add: false,
};
}, [keys.length, spec.maxLength, spec.minLength, spec.viewSpec.enableLockLength]);

const getItemSpec = React.useCallback(
(idx: number): typeof spec.items | null => {
if (!itemSpecCorrect) {
Expand Down Expand Up @@ -72,6 +87,13 @@ export const ArrayBase: ArrayInput = ({spec, name, arrayInput, input}) => {
[input.onChange, input.name],
);

const removeItem = React.useCallback(
(key: string) => {
arrayInput.onItemRemove(key);
},
[arrayInput],
);

const AddButton: React.FC = React.useCallback(() => {
let onClick = () => arrayInput.onItemAdd(undefined);

Expand All @@ -88,15 +110,24 @@ export const ArrayBase: ArrayInput = ({spec, name, arrayInput, input}) => {
}

return (
<Button
onClick={onClick}
disabled={spec.viewSpec.disabled}
qa={qa}
className={b('add-button', {right: spec.viewSpec.addButtonPosition === 'right'})}
<Popover
content={i18n('label_error-max-length-array', {
count: spec.maxLength,
})}
disabled={!disabledButtonLength.add}
>
<Icon data={Plus} size={14} />
{title || null}
</Button>
<Button
onClick={onClick}
disabled={spec.viewSpec.disabled || disabledButtonLength.add}
qa={qa}
className={b('add-button', {
right: spec.viewSpec.addButtonPosition === 'right',
})}
>
<Icon data={Plus} size={14} />
{title || null}
</Button>
</Popover>
);
}, [
arrayInput,
Expand All @@ -107,6 +138,7 @@ export const ArrayBase: ArrayInput = ({spec, name, arrayInput, input}) => {
spec.viewSpec.itemLabel,
spec.viewSpec.layoutTitle,
spec.viewSpec.addButtonPosition,
disabledButtonLength,
]);

const items = React.useMemo(
Expand All @@ -133,6 +165,25 @@ export const ArrayBase: ArrayInput = ({spec, name, arrayInput, input}) => {
parentOnUnmount={input.parentOnUnmount}
spec={itemSpec}
name={`${name}.<${key}>`}
additionalContentLayout={
<Popover
content={i18n('label_error-min-length-array', {
count: spec.minLength,
})}
disabled={!disabledButtonLength.remove}
>
<Button
view="flat-secondary"
onClick={() => removeItem(key)}
key={`remove-${key}`}
qa={`${name}-item-remove-${key}`}
disabled={disabledButtonLength.remove}
className={b('remove-button')}
>
<Icon data={TrashBin} size={16} />
</Button>
</Popover>
}
/>
</React.Fragment>
);
Expand All @@ -145,6 +196,9 @@ export const ArrayBase: ArrayInput = ({spec, name, arrayInput, input}) => {
input.parentOnUnmount,
input.value,
spec.viewSpec.itemPrefix,
spec.minLength,
disabledButtonLength.remove,
removeItem,
],
);

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 45 additions & 15 deletions src/lib/kit/components/Inputs/TableArrayInput/TableArrayInput.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';

import {Plus, TrashBin} from '@gravity-ui/icons';
import {Button, Flex, Icon, Table} from '@gravity-ui/uikit';
import {Button, Flex, Icon, Popover, Table} from '@gravity-ui/uikit';
import {HelpPopover} from '@gravity-ui/components';
import noop from 'lodash/noop';
import set from 'lodash/set';
Expand All @@ -23,6 +23,7 @@ import {
} from '../../../../core';
import {useSearchContext} from '../../../../core/components/Form/hooks';
import {block} from '../../../utils';
import i18n from '../../../i18n';

import './TableArrayInput.scss';

Expand All @@ -43,6 +44,20 @@ export const TableArrayInput: ArrayInput = ({spec, name, arrayInput, input}) =>
[arrayInput.value],
);

const disabledButtonLength = React.useMemo(() => {
if (spec.viewSpec.enableLockLength) {
return {
remove: keys.length <= (spec.minLength || 0),
add: keys.length >= (spec.maxLength || Infinity),
};
}

return {
remove: false,
add: false,
};
}, [keys.length, spec.maxLength, spec.minLength, spec.viewSpec.enableLockLength]);

const onItemAdd = React.useCallback(() => {
arrayInput.onItemAdd({});
}, [arrayInput.onItemAdd]);
Expand Down Expand Up @@ -90,14 +105,22 @@ export const TableArrayInput: ArrayInput = ({spec, name, arrayInput, input}) =>
name: '',
sticky: 'right',
template: ({key}: {key: string}) => (
<Button
view="flat-secondary"
onClick={() => onItemRemove(key)}
key={`remove-${key}`}
qa={`${name}-item-remove-${key}`}
<Popover
content={i18n('label_error-min-length-array', {
count: spec.minLength,
})}
disabled={!disabledButtonLength.remove}
>
<Icon data={TrashBin} size={16} />
</Button>
<Button
view="flat-secondary"
onClick={() => onItemRemove(key)}
key={`remove-${key}`}
qa={`${name}-item-remove-${key}`}
disabled={disabledButtonLength.remove}
>
<Icon data={TrashBin} size={16} />
</Button>
</Popover>
),
};

Expand Down Expand Up @@ -199,14 +222,21 @@ export const TableArrayInput: ArrayInput = ({spec, name, arrayInput, input}) =>
{spec.viewSpec.layoutTitle || null}
</Button>
) : (
<Button
onClick={onItemAdd}
disabled={spec.viewSpec.disabled}
qa={`${name}-add-item`}
<Popover
content={i18n('label_error-max-length-array', {
count: spec.maxLength,
})}
disabled={!disabledButtonLength.add}
>
<Icon data={Plus} size={14} />
{spec.viewSpec.itemLabel || null}
</Button>
<Button
onClick={onItemAdd}
disabled={spec.viewSpec.disabled || disabledButtonLength.add}
qa={`${name}-add-item`}
>
<Icon data={Plus} size={14} />
{spec.viewSpec.itemLabel || null}
</Button>
</Popover>
)}
</div>
);
Expand Down
4 changes: 0 additions & 4 deletions src/lib/kit/components/Layouts/Row/Row.scss
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,6 @@
word-break: break-word;
}

&__remove-button {
margin-left: 5px;
}

&__required-mark {
color: var(--g-color-text-danger);
}
Expand Down
Loading
Loading