diff --git a/docs/spec.md b/docs/spec.md index 48fc2404..cc7b9a92 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -26,9 +26,11 @@ type Spec = ArraySpec | BooleanSpec | NumberSpec | ObjectSpec | StringSpec; | viewSpec.layoutDescription | `string` | | Additional description/hint for [Layout](./config.md#layouts) | | viewSpec.layoutOpen | `boolean` | | Expand [Layout](./config.md#layouts) at the first rendering | | viewSpec.itemLabel | `string` | | Text for the button that adds an array element | +| viewSpec.itemPrefix | `string` | | Additional text for an element in the array | | viewSpec.table | `{label: string; property: string;}[]` | | An array whose elements are used to establish column names and their order, if `type === "table"` | | viewSpec.link | `any` | | A field containing information for forming a [link](#link) for a value | | viewSpec.placeholder | `string` | | A short hint displayed in the field before the user enters the value | +| viewSpec.addButtonPosition | `"down"/"right"` | | The location of the button adding a new element to the array. Default value "down". | ### BooleanSpec diff --git a/src/lib/core/types/specs.ts b/src/lib/core/types/specs.ts index 580733c6..5a1c416b 100644 --- a/src/lib/core/types/specs.ts +++ b/src/lib/core/types/specs.ts @@ -22,12 +22,14 @@ export interface ArraySpec { layoutDescription?: string; layoutOpen?: boolean; itemLabel?: string; + itemPrefix?: string; table?: { label: string; property: string; }[]; link?: LinkType; placeholder?: string; + addButtonPosition?: 'down' | 'right'; }; } diff --git a/src/lib/kit/components/Inputs/ArrayBase/ArrayBase.scss b/src/lib/kit/components/Inputs/ArrayBase/ArrayBase.scss new file mode 100644 index 00000000..c1c2e417 --- /dev/null +++ b/src/lib/kit/components/Inputs/ArrayBase/ArrayBase.scss @@ -0,0 +1,25 @@ +@import '../../../styles/variables'; + +.#{$ns}array-base { + &_add-button-right { + display: flex; + align-items: flex-end; + } + + &__items-wrapper { + &_add-button-down { + margin-bottom: 15px; + } + } + + &__item-prefix { + margin-top: -7px; + margin-bottom: 8px; + } + + &__add-button { + &_right { + margin-left: 4px; + } + } +} diff --git a/src/lib/kit/components/Inputs/ArrayBase/ArrayBase.tsx b/src/lib/kit/components/Inputs/ArrayBase/ArrayBase.tsx index 9439b4e9..4a9e75dc 100644 --- a/src/lib/kit/components/Inputs/ArrayBase/ArrayBase.tsx +++ b/src/lib/kit/components/Inputs/ArrayBase/ArrayBase.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {Plus} from '@gravity-ui/icons'; -import {Button, Icon} from '@gravity-ui/uikit'; +import {Button, Icon, Label} from '@gravity-ui/uikit'; import _ from 'lodash'; import { @@ -19,6 +19,11 @@ import { isObjectSpec, transformArrIn, } from '../../../../core'; +import {block} from '../../../utils'; + +import './ArrayBase.scss'; + +const b = block('array-base'); export const ArrayBase: ArrayInput = ({spec, name, arrayInput, input}) => { const keys = React.useMemo( @@ -32,20 +37,6 @@ export const ArrayBase: ArrayInput = ({spec, name, arrayInput, input}) => { const itemSpecCorrect = React.useMemo(() => isCorrectSpec(spec.items), [spec.items]); - const onItemAdd = React.useCallback(() => { - let item; - - if (!spec.items?.required) { - if (isArraySpec(spec.items)) { - item = {[OBJECT_ARRAY_FLAG]: true, [OBJECT_ARRAY_CNT]: 0}; - } else if (isObjectSpec(spec.items)) { - item = {}; - } - } - - arrayInput.onItemAdd(item); - }, [arrayInput.onItemAdd, spec.items]); - const getItemSpec = React.useCallback( (idx: number): typeof spec.items | null => { if (!itemSpecCorrect) { @@ -80,6 +71,55 @@ export const ArrayBase: ArrayInput = ({spec, name, arrayInput, input}) => { [input.onChange, input.name], ); + const AddButton: React.FC = React.useCallback(() => { + let onClick = () => { + let item; + + if (!spec.items?.required) { + if (isArraySpec(spec.items)) { + item = {[OBJECT_ARRAY_FLAG]: true, [OBJECT_ARRAY_CNT]: 0}; + } else if (isObjectSpec(spec.items)) { + item = {}; + } + } + + arrayInput.onItemAdd(item); + }; + + let qa = `${name}-add-item`; + let title = spec.viewSpec.itemLabel; + + if (!arrayInput.value && spec.defaultValue) { + onClick = () => { + input.onChange(transformArrIn(spec.defaultValue!)); + }; + + qa = `${name}-init-arr`; + title = spec.viewSpec.layoutTitle; + } + + return ( + + ); + }, [ + arrayInput, + input, + name, + spec.defaultValue, + spec.items, + spec.viewSpec.disabled, + spec.viewSpec.itemLabel, + spec.viewSpec.layoutTitle, + ]); + const items = React.useMemo( () => keys.map((key, idx) => { @@ -89,18 +129,34 @@ export const ArrayBase: ArrayInput = ({spec, name, arrayInput, input}) => { return null; } + const showItemPrefix = idx !== 0 && spec.viewSpec.itemPrefix; + return ( - `]} - parentOnChange={parentOnChange} - parentOnUnmount={input.parentOnUnmount} - spec={itemSpec} - name={`${name}.<${key}>`} - key={`${name}.<${key}>`} - /> + `}> + {showItemPrefix ? ( + + ) : null} + `]} + parentOnChange={parentOnChange} + parentOnUnmount={input.parentOnUnmount} + spec={itemSpec} + name={`${name}.<${key}>`} + /> + ); }), - [keys.join(''), name, getItemSpec, parentOnChange, input.parentOnUnmount, input.value], + [ + keys.join(''), + name, + getItemSpec, + parentOnChange, + input.parentOnUnmount, + input.value, + spec.viewSpec.itemPrefix, + ], ); if (!itemSpecCorrect) { @@ -108,31 +164,16 @@ export const ArrayBase: ArrayInput = ({spec, name, arrayInput, input}) => { } return ( - - {items} - {!arrayInput.value && spec.defaultValue ? ( - - ) : ( - - )} - +
+
0, + })} + > + {items} +
+ +
); }; diff --git a/src/lib/kit/components/Inputs/TableArrayInput/TableArrayInput.tsx b/src/lib/kit/components/Inputs/TableArrayInput/TableArrayInput.tsx index c2d96459..1ec5d1ed 100644 --- a/src/lib/kit/components/Inputs/TableArrayInput/TableArrayInput.tsx +++ b/src/lib/kit/components/Inputs/TableArrayInput/TableArrayInput.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {Plus, Xmark} from '@gravity-ui/icons'; +import {Plus, TrashBin} from '@gravity-ui/icons'; import {Button, Icon, Table} from '@gravity-ui/uikit'; import _ from 'lodash'; @@ -93,12 +93,12 @@ export const TableArrayInput: ArrayInput = ({spec, name, arrayInput, input}) => sticky: 'right', template: ({key}: {key: string}) => ( ), }; diff --git a/src/lib/kit/components/Layouts/Row/Row.tsx b/src/lib/kit/components/Layouts/Row/Row.tsx index 1d70991c..0b91ef74 100644 --- a/src/lib/kit/components/Layouts/Row/Row.tsx +++ b/src/lib/kit/components/Layouts/Row/Row.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {HelpPopover} from '@gravity-ui/components'; -import {Xmark} from '@gravity-ui/icons'; +import {TrashBin} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; import { @@ -63,12 +63,12 @@ const RowBase = ({ {arrayItem ? ( ) : null} diff --git a/src/lib/kit/components/Layouts/Row2/Row2.tsx b/src/lib/kit/components/Layouts/Row2/Row2.tsx index 438fa4e1..0975e95d 100644 --- a/src/lib/kit/components/Layouts/Row2/Row2.tsx +++ b/src/lib/kit/components/Layouts/Row2/Row2.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {Xmark} from '@gravity-ui/icons'; +import {TrashBin} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; import { @@ -44,12 +44,12 @@ export const Row2 = ({ {isArrayItem(name) ? ( ) : null} diff --git a/src/lib/kit/components/Layouts/Transparent/Transparent.tsx b/src/lib/kit/components/Layouts/Transparent/Transparent.tsx index 7ad78a95..e4634fab 100644 --- a/src/lib/kit/components/Layouts/Transparent/Transparent.tsx +++ b/src/lib/kit/components/Layouts/Transparent/Transparent.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {Xmark} from '@gravity-ui/icons'; +import {TrashBin} from '@gravity-ui/icons'; import {Button, Icon} from '@gravity-ui/uikit'; import { @@ -32,12 +32,12 @@ export const Transparent = ({ if (arrayItem) { return ( ); } diff --git a/src/lib/kit/components/Views/ArrayBaseView/ArrayBaseView.scss b/src/lib/kit/components/Views/ArrayBaseView/ArrayBaseView.scss new file mode 100644 index 00000000..cddcb994 --- /dev/null +++ b/src/lib/kit/components/Views/ArrayBaseView/ArrayBaseView.scss @@ -0,0 +1,8 @@ +@import '../../../styles/variables'; + +.#{$ns}array-base-view { + &__item-prefix { + margin-top: -10px; + margin-bottom: 15px; + } +} diff --git a/src/lib/kit/components/Views/ArrayBaseView.tsx b/src/lib/kit/components/Views/ArrayBaseView/ArrayBaseView.tsx similarity index 60% rename from src/lib/kit/components/Views/ArrayBaseView.tsx rename to src/lib/kit/components/Views/ArrayBaseView/ArrayBaseView.tsx index 9353463a..74721f4c 100644 --- a/src/lib/kit/components/Views/ArrayBaseView.tsx +++ b/src/lib/kit/components/Views/ArrayBaseView/ArrayBaseView.tsx @@ -1,8 +1,14 @@ import React from 'react'; +import {Label} from '@gravity-ui/uikit'; import _ from 'lodash'; -import {ArrayView, Spec, ViewController, isCorrectSpec} from '../../../core'; +import {ArrayView, Spec, ViewController, isCorrectSpec} from '../../../../core'; +import {block} from '../../../utils'; + +import './ArrayBaseView.scss'; + +const b = block('array-base-view'); export const ArrayBaseView: ArrayView = ({spec, name, value = []}) => { const itemSpecCorrect = React.useMemo(() => isCorrectSpec(spec.items), [spec.items]); @@ -36,20 +42,25 @@ export const ArrayBaseView: ArrayView = ({spec, name, value = []}) => { return null; } + const showItemPrefix = idx !== 0 && spec.viewSpec.itemPrefix; + return ( - + + {showItemPrefix ? ( + + ) : null} + + ); }), - [value.length, name, getItemSpec], + [value.length, name, getItemSpec, spec.viewSpec.itemPrefix], ); if (!itemSpecCorrect) { return null; } - return <>{items}; + return {items}; }; diff --git a/src/lib/kit/components/Views/ArrayBaseView/index.ts b/src/lib/kit/components/Views/ArrayBaseView/index.ts new file mode 100644 index 00000000..79b63335 --- /dev/null +++ b/src/lib/kit/components/Views/ArrayBaseView/index.ts @@ -0,0 +1 @@ +export * from './ArrayBaseView'; diff --git a/src/stories/ArraySelect.stories.tsx b/src/stories/ArraySelect.stories.tsx index 5f775bcb..a6e06781 100644 --- a/src/stories/ArraySelect.stories.tsx +++ b/src/stories/ArraySelect.stories.tsx @@ -28,7 +28,14 @@ const spec: ArraySpec = { }, }; -const excludeOptions = ['items', 'viewSpec.type', 'viewSpec.itemLabel', 'viewSpec.table']; +const excludeOptions = [ + 'items', + 'viewSpec.type', + 'viewSpec.itemLabel', + 'viewSpec.table', + 'viewSpec.itemPrefix', + 'viewSpec.addButtonPosition', +]; const template = () => { const Template: StoryFn = (__, {viewMode}) => ( diff --git a/src/stories/ArrayTable.stories.tsx b/src/stories/ArrayTable.stories.tsx index 29b783f4..a643c787 100644 --- a/src/stories/ArrayTable.stories.tsx +++ b/src/stories/ArrayTable.stories.tsx @@ -54,7 +54,14 @@ const spec: ArraySpec = { }, }; -const excludeOptions = ['enum', 'description', 'viewSpec.type', 'viewSpec.placeholder']; +const excludeOptions = [ + 'enum', + 'description', + 'viewSpec.type', + 'viewSpec.placeholder', + 'viewSpec.itemPrefix', + 'viewSpec.addButtonPosition', +]; const value = [ {name: 'Foo', age: 13, license: false}, diff --git a/src/stories/components/InputPreview/constants.ts b/src/stories/components/InputPreview/constants.ts index 27cc43e3..b79526c1 100644 --- a/src/stories/components/InputPreview/constants.ts +++ b/src/stories/components/InputPreview/constants.ts @@ -142,7 +142,7 @@ const disabled: BooleanSpec = { const getViewType = (map: Record): StringSpec => ({ type: SpecTypes.String, enum: ['―', ...Object.keys(map)], - viewSpec: {type: 'select', layout: 'row', layoutTitle: 'View type'}, + viewSpec: {type: 'select', layout: 'row', layoutTitle: 'Type'}, }); const getLayoutSpec = (map: Record): StringSpec => ({ @@ -171,6 +171,17 @@ const itemLabel: StringSpec = { viewSpec: {type: 'base', layout: 'row', layoutTitle: 'Item label'}, }; +const itemPrefix: StringSpec = { + type: SpecTypes.String, + viewSpec: {type: 'base', layout: 'row', layoutTitle: 'Item Prefix'}, +}; + +const addButtonPosition: StringSpec = { + type: SpecTypes.String, + enum: ['―', 'down', 'right'], + viewSpec: {type: 'select', layout: 'row', layoutTitle: 'Add Button Position'}, +}; + const table: ArraySpec = { type: SpecTypes.Array, items: { @@ -190,7 +201,7 @@ const table: ArraySpec = { viewSpec: { type: 'table', layout: 'accordeon', - layoutTitle: 'Enum description', + layoutTitle: 'Table', table: [ {label: 'Property', property: 'property'}, {label: 'Label', property: 'label'}, @@ -357,8 +368,10 @@ export const getArrayOptions = (): ObjectSpec => ({ layoutDescription, layoutOpen, itemLabel, + itemPrefix, table, placeholder, + addButtonPosition, }, [ 'disabled', @@ -368,8 +381,10 @@ export const getArrayOptions = (): ObjectSpec => ({ 'layoutDescription', 'layoutOpen', 'itemLabel', + 'itemPrefix', 'table', 'placeholder', + 'addButtonPosition', ], ), },