diff --git a/package.json b/package.json
index 95bba04..7aeae72 100644
--- a/package.json
+++ b/package.json
@@ -13,8 +13,8 @@
}
],
"peerDependencies": {
- "@consta/icons": "^1.1.1",
- "@consta/uikit": "^5.26.0",
+ "@consta/icons": "^1.6.0",
+ "@consta/uikit": "^5.30.0",
"@reatom/core": "3.10.1",
"@reatom/npm-react": "3.10.6"
},
diff --git a/src/components/Collapse/Collapse.css b/src/components/Collapse/Collapse.css
index 875adb2..9ba78f1 100644
--- a/src/components/Collapse/Collapse.css
+++ b/src/components/Collapse/Collapse.css
@@ -1,22 +1,25 @@
.ct--Collapse {
&-Content {
display: grid;
- grid-template-rows: 0fr;
+ grid-template-rows: 0;
grid-template-columns: 100%;
-
transition: grid-template-rows 0.2s ease-in-out;
+
&_expanded {
grid-template-rows: 1fr;
}
}
+
&-ChildrenWrapper {
overflow: hidden;
}
+
&-Title {
display: flex;
align-items: center;
height: var(--control-height-s);
}
+
&-Toolbar.ct--Toolbar {
background: var(--color-bg-secondary);
}
diff --git a/src/components/Collapse/CollapseFullscreen/CollapseFullscreen.css b/src/components/Collapse/CollapseFullscreen/CollapseFullscreen.css
index b87d737..805e26e 100644
--- a/src/components/Collapse/CollapseFullscreen/CollapseFullscreen.css
+++ b/src/components/Collapse/CollapseFullscreen/CollapseFullscreen.css
@@ -1,8 +1,8 @@
.ct--CollapseFullscreen {
position: fixed;
top: 0;
- left: 0;
right: 0;
+ left: 0;
bottom: 0;
background-color: var(--color-bg-default);
@@ -13,9 +13,11 @@
&-ToolbarWrapper {
height: var(--collapse-toolbar-height);
}
+
&-ChildrenWrapper {
height: calc(100% - var(--collapse-toolbar-height));
}
+
&-Toolbar.ct--Toolbar {
background: var(--color-bg-secondary);
}
diff --git a/src/components/Collapse/__stand__/examples/CollapseExampleFullscreenContainer/CollapseExampleFullscreenContainer.css b/src/components/Collapse/__stand__/examples/CollapseExampleFullscreenContainer/CollapseExampleFullscreenContainer.css
index 3c131d4..47d945c 100644
--- a/src/components/Collapse/__stand__/examples/CollapseExampleFullscreenContainer/CollapseExampleFullscreenContainer.css
+++ b/src/components/Collapse/__stand__/examples/CollapseExampleFullscreenContainer/CollapseExampleFullscreenContainer.css
@@ -6,14 +6,16 @@
&-Item {
height: 360px;
+
.ct--Collapse-Content {
- height: calc(
- 100% -
+ height:
+ calc(
+ 100% -
calc(
var(--control-height-s) + var(--control-border-width) +
- var(--space-xs) * 2
+ var(--space-xs) * 2
)
- );
+ );
}
}
diff --git a/src/components/DataCell/DataCell.css b/src/components/DataCell/DataCell.css
index 770a960..0970ff6 100644
--- a/src/components/DataCell/DataCell.css
+++ b/src/components/DataCell/DataCell.css
@@ -1,12 +1,15 @@
/* --table-data-cell-level - задается в компоненте */
.ct--DataCell {
- padding-right: var(--space-s);
- padding-left: calc(
+ --table-data-cell-padding-right: var(--space-s);
+ --table-data-cell-padding-left: calc(
var(--space-s) + (var(--table-data-cell-slot-width) + var(--space-2xs)) *
var(--table-data-cell-level, 0) +
var(--table-data-cell-additional-space, calc(0 * 1px))
);
+ padding-right: var(--table-data-cell-padding-right);
+ padding-left: var(--table-data-cell-padding-left);
+
&_alignmentIndent {
--table-data-cell-additional-space: calc(
var(--table-data-cell-gap) - var(--space-2xs)
@@ -55,10 +58,24 @@
&-ContentSlot {
min-height: var(--table-data-cell-slot-height);
+
&_truncate {
overflow: hidden;
}
}
+
+ &-Text {
+ white-space: pre-wrap;
+ word-break: break-word;
+
+ &_lineClamp {
+ display: -webkit-box;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ -webkit-line-clamp: var(--table-data-cell-line-clamp);
+ -webkit-box-orient: vertical;
+ }
+ }
}
.MixFlex.ct--DataCell {
diff --git a/src/components/DataCell/DataCell.tsx b/src/components/DataCell/DataCell.tsx
index a9f9da4..eb1676e 100644
--- a/src/components/DataCell/DataCell.tsx
+++ b/src/components/DataCell/DataCell.tsx
@@ -21,6 +21,7 @@ export type DataCellProps = {
size?: 'm' | 's';
indicator?: 'alert' | 'warning';
truncate?: boolean;
+ lineClamp?: number;
} & JSX.IntrinsicElements['div'];
const renderContentSlot = (
@@ -47,16 +48,20 @@ const renderChildren = (
view: DataCellProps['view'],
size: 'm' | 's',
truncate: boolean,
+ lineClamp?: number,
) => {
if (isString(children) || isNumber(children)) {
return renderContentSlot(
{children}
,
@@ -79,6 +84,7 @@ export const DataCell = forwardRef(
indicator,
style,
truncate = false,
+ lineClamp,
...otherProps
} = props;
const level = levelProp < 0 ? 0 : levelProp;
@@ -98,8 +104,10 @@ export const DataCell = forwardRef(
const childrenSlots = children
? [
...(Array.isArray(children)
- ? children.map((item) => renderChildren(item, view, size, truncate))
- : [renderChildren(children, view, size, truncate)]),
+ ? children.map((item) =>
+ renderChildren(item, view, size, truncate, lineClamp),
+ )
+ : [renderChildren(children, view, size, truncate, lineClamp)]),
]
: [];
@@ -122,6 +130,7 @@ export const DataCell = forwardRef(
['--table-data-cell-indicator-color' as string]: indicator
? `var(--color-bg-${indicator})`
: undefined,
+ ['--table-data-cell-line-clamp' as string]: lineClamp || undefined,
}}
ref={ref}
>
diff --git a/src/components/DataCell/__stand__/DataCell.dev.stand.mdx b/src/components/DataCell/__stand__/DataCell.dev.stand.mdx
index 6d69045..65575fe 100644
--- a/src/components/DataCell/__stand__/DataCell.dev.stand.mdx
+++ b/src/components/DataCell/__stand__/DataCell.dev.stand.mdx
@@ -37,6 +37,7 @@ import { DataCell } from '@consta/table/DataCell';
| [`view?`](#вид) | `'primary'` | `'alert'` | `'success'` | `'warning'` | - | Слот для контролов |
| [`level?`](#уровень-вложенности) | `number` | `0` | Уровень вложенности |
| `truncate?` | `boolean` | - | Заменяет переполненный текст на многоточие |
+| `lineClamp?` | `number` | - | Обрезка текста по строкам |
| `className?` | `string` | - | Дополнительный CSS-класс |
| `ref?` | `React.Ref` | - | Ссылка на корневой DOM-элемент |
diff --git a/src/components/DataCell/__stand__/DataCell.variants.tsx b/src/components/DataCell/__stand__/DataCell.variants.tsx
index 8dfd4fd..32c9a3b 100644
--- a/src/components/DataCell/__stand__/DataCell.variants.tsx
+++ b/src/components/DataCell/__stand__/DataCell.variants.tsx
@@ -23,6 +23,7 @@ const Variants = () => {
const text = useText('text', 'Значение ячейки');
const size = useSelect('size', ['m', 's'], 'm');
+ const lineClamp = useNumber('lineClamp', 0);
const view = useSelect(
'view',
['primary', 'alert', 'success', 'warning'],
@@ -82,6 +83,7 @@ const Variants = () => {
icon={widthIcon ? IconPhone : undefined}
control={controlMap[control || 'without control']}
indicator={indicator}
+ lineClamp={lineClamp}
>
{children}
diff --git a/src/components/Table/TableCell/TableCell.css b/src/components/Table/TableCell/TableCell.css
index c0fe6e3..d9ba2aa 100644
--- a/src/components/Table/TableCell/TableCell.css
+++ b/src/components/Table/TableCell/TableCell.css
@@ -1,4 +1,5 @@
.ct--TableCell {
+ --table-cell-edit-mode-border-color: var(--color-control-bg-border-focus);
--table-cell-border-top: 0;
--table-cell-border-bottom: 0;
--table-cell-border-left: 0;
@@ -48,4 +49,17 @@
&_up {
z-index: 1;
}
+
+ &:has([data-cell-edit-mode='true']) {
+ box-shadow: 1px 1px 0 0 var(--table-cell-edit-mode-border-color) inset,
+ -1px -1px 0 0 var(--table-cell-edit-mode-border-color) inset;
+ }
+
+ &:has([data-cell-status='alert']) {
+ --table-cell-edit-mode-border-color: var(--color-bg-alert);
+ }
+
+ &:has([data-cell-status='warning']) {
+ --table-cell-edit-mode-border-color: var(--color-bg-warning);
+ }
}
diff --git a/src/components/Table/TableData/TableData.css b/src/components/Table/TableData/TableData.css
index a480eb3..9b89389 100644
--- a/src/components/Table/TableData/TableData.css
+++ b/src/components/Table/TableData/TableData.css
@@ -1,6 +1,6 @@
.ct--TableData {
- display: contents;
--table-data-cell-bg: var(--color-bg-default);
+ display: contents;
&-Cell {
grid-column: span var(--table-cell-col-span, 1);
@@ -16,41 +16,45 @@
display: contents;
&_zebraStriped {
- --table-data-cell-bg: color-mix(
- in srgb,
- rgb(from var(--color-bg-stripe) r g b / 1) 5%,
- var(--color-bg-default)
- );
+ --table-data-cell-bg:
+ color-mix(
+ in srgb,
+ rgb(from var(--color-bg-stripe) r g b / 1) 5%,
+ var(--color-bg-default)
+ );
}
&:has(> * > [data-row-active='true']) {
- --table-data-cell-bg: color-mix(
- in srgb,
- rgb(from var(--color-control-bg-primary) r g b / 1) 15%,
- var(--color-bg-default)
- );
+ --table-data-cell-bg:
+ color-mix(
+ in srgb,
+ rgb(from var(--color-control-bg-primary) r g b / 1) 15%,
+ var(--color-bg-default)
+ );
}
&:has(> * > [data-row-hover='true']),
.ct--TableData_rowHoverEffect &:hover {
- --table-data-cell-bg: color-mix(
- in srgb,
- rgb(from var(--color-control-typo-ghost-hover) r g b / 1) 9%,
- var(--color-bg-default)
- );
+ --table-data-cell-bg:
+ color-mix(
+ in srgb,
+ rgb(from var(--color-control-typo-ghost-hover) r g b / 1) 9%,
+ var(--color-bg-default)
+ );
}
&:has(> * > [data-row-hover='true'][data-row-active='true']),
.ct--TableData_rowHoverEffect &:hover:has(> * > [data-row-active='true']) {
- --table-data-cell-bg: color-mix(
- in srgb,
- rgb(from var(--color-control-typo-ghost-hover) r g b / 1) 3%,
+ --table-data-cell-bg:
color-mix(
in srgb,
- rgb(from var(--color-control-bg-primary) r g b / 1) 15%,
- var(--color-bg-default)
- )
- );
+ rgb(from var(--color-control-typo-ghost-hover) r g b / 1) 3%,
+ color-mix(
+ in srgb,
+ rgb(from var(--color-control-bg-primary) r g b / 1) 15%,
+ var(--color-bg-default)
+ )
+ );
}
}
}
diff --git a/src/components/Table/TableResizers/TableResizers.css b/src/components/Table/TableResizers/TableResizers.css
index c76b313..9704cc8 100644
--- a/src/components/Table/TableResizers/TableResizers.css
+++ b/src/components/Table/TableResizers/TableResizers.css
@@ -11,17 +11,19 @@
--inner-line-width: 1px;
--fast-transition: 0.15s ease-out;
--resizer-width: 4px;
- --resizer-top: calc(
- (var(--table-header-height) - var(--table-resizer-top-offset)) * -1
- );
+ --resizer-top:
+ calc(
+ (var(--table-header-height) - var(--table-resizer-top-offset)) * -1
+ );
position: absolute;
top: var(--resizer-top);
right: 0;
width: var(--resizer-width);
- height: calc(
- var(--table-body-height) - var(--table-resizer-top-offset) -
+ height:
+ calc(
+ var(--table-body-height) - var(--table-resizer-top-offset) -
var(--table-body-horizontal-scroll-height) - 2px
- );
+ );
background-color: var(--color-bg-ghost);
opacity: 0;
cursor: col-resize;
@@ -45,10 +47,11 @@
opacity: 1;
}
}
+
&-VirtualScrollHelper {
position: absolute;
top: calc(var(--table-header-height) * -1);
- pointer-events: none;
width: 100%;
+ pointer-events: none;
}
}
diff --git a/src/components/Table/TableRow/TableRow.css b/src/components/Table/TableRow/TableRow.css
index 60c349a..080861c 100644
--- a/src/components/Table/TableRow/TableRow.css
+++ b/src/components/Table/TableRow/TableRow.css
@@ -6,6 +6,7 @@
&_left {
grid-column: span var(--table-row-offset, 1);
}
+
&_right {
grid-column: span var(--table-row-offset, 1);
}
@@ -13,41 +14,45 @@
}
&_zebraStriped {
- --table-data-cell-bg: color-mix(
- in srgb,
- rgb(from var(--color-bg-stripe) r g b / 1) 5%,
- var(--color-bg-default)
- );
+ --table-data-cell-bg:
+ color-mix(
+ in srgb,
+ rgb(from var(--color-bg-stripe) r g b / 1) 5%,
+ var(--color-bg-default)
+ );
}
&:has(> * > [data-row-active='true']) {
- --table-data-cell-bg: color-mix(
- in srgb,
- rgb(from var(--color-control-bg-primary) r g b / 1) 15%,
- var(--color-bg-default)
- );
+ --table-data-cell-bg:
+ color-mix(
+ in srgb,
+ rgb(from var(--color-control-bg-primary) r g b / 1) 15%,
+ var(--color-bg-default)
+ );
}
&:has(> * > [data-row-hover='true']),
.ct--TableData_rowHoverEffect &:hover {
- --table-data-cell-bg: color-mix(
- in srgb,
- rgb(from var(--color-control-typo-ghost-hover) r g b / 1) 9%,
- var(--color-bg-default)
- );
+ --table-data-cell-bg:
+ color-mix(
+ in srgb,
+ rgb(from var(--color-control-typo-ghost-hover) r g b / 1) 9%,
+ var(--color-bg-default)
+ );
}
&:has(> * > [data-row-hover='true'][data-row-active='true']),
.ct--TableData_rowHoverEffect &:hover:has(> * > [data-row-active='true']) {
- --table-data-cell-bg: color-mix(
- in srgb,
- rgb(from var(--color-control-typo-ghost-hover) r g b / 1) 3%,
+ --table-data-cell-bg:
color-mix(
in srgb,
- rgb(from var(--color-control-bg-primary) r g b / 1) 15%,
- var(--color-bg-default)
- )
- );
+ rgb(from var(--color-control-typo-ghost-hover) r g b / 1) 3%,
+ color-mix(
+ in srgb,
+ rgb(from var(--color-control-bg-primary) r g b / 1) 15%,
+ var(--color-bg-default)
+ )
+ );
}
&-Cell {
diff --git a/src/components/Table/TableRowCell/TableRowCell.tsx b/src/components/Table/TableRowCell/TableRowCell.tsx
index 4ce2423..629fcf7 100644
--- a/src/components/Table/TableRowCell/TableRowCell.tsx
+++ b/src/components/Table/TableRowCell/TableRowCell.tsx
@@ -102,6 +102,8 @@ const TableRowCellRender = (
? `var(--table-column-sticky-right-offset-${index})`
: undefined,
gridColumn: `${index + 1} / span ${miss > 0 ? miss + 1 : 1}`,
+ ['--table-cell-grid-column-index' as string]: index,
+ ['--table-cell-grid-row-index' as string]: rowIndex,
}}
>
{isNotNil(RenderCell) ? (
diff --git a/src/components/Table/__stand__/examples/TableExampleSimple/TableExampleSimple.tsx b/src/components/Table/__stand__/examples/TableExampleSimple/TableExampleSimple.tsx
index b91ea8f..4db2856 100644
--- a/src/components/Table/__stand__/examples/TableExampleSimple/TableExampleSimple.tsx
+++ b/src/components/Table/__stand__/examples/TableExampleSimple/TableExampleSimple.tsx
@@ -16,6 +16,16 @@ const rows: Row[] = [
profession: 'Отвечает на вопросы, хотя его не спросили',
status: 'на связи',
},
+ {
+ name: 'Василий',
+ profession: 'Отвечает на вопросы, хотя его не спросили',
+ status: 'на связи',
+ },
+ {
+ name: 'Василий',
+ profession: 'Отвечает на вопросы, хотя его не спросили',
+ status: 'на связи',
+ },
];
const columns: TableColumn[] = [
diff --git a/src/components/TextFieldCell/TextFieldCell.css b/src/components/TextFieldCell/TextFieldCell.css
new file mode 100644
index 0000000..775f04b
--- /dev/null
+++ b/src/components/TextFieldCell/TextFieldCell.css
@@ -0,0 +1,72 @@
+.ct--TextFieldCell {
+ width: 100%;
+ height: 100%;
+ min-height: var(--text-filed-cell-min-height);
+ padding-left: var(
+ --text-filed-cell-padding-left,
+ var(--table-data-cell-padding-left)
+ );
+ padding-right: var(
+ --text-filed-cell-padding-right,
+ var(--table-data-cell-padding-right)
+ );
+
+ &_size {
+ &_s {
+ --text-filed-cell-min-height: var(--space-3xl);
+ }
+
+ &_m {
+ --text-filed-cell-min-height: var(--space-4xl);
+ }
+ }
+
+ &[data-cell-edit-mode='true'] {
+ &.ct--DataCell {
+ --text-filed-cell-padding-left: calc(
+ var(--table-data-cell-padding-right) - var(--space-s)
+ );
+ --text-filed-cell-padding-right: calc(
+ var(--table-data-cell-padding-right) - var(--space-s)
+ );
+ --field-control-layout-additional-padding-left: var(--space-s);
+ --field-control-layout-additional-padding-right: var(--space-s);
+ }
+
+ & .ct--DataCell-Slots,
+ & .ct--DataCell-ContentSlot {
+ overflow: hidden;
+ flex: 1;
+ }
+ }
+
+ &-Field {
+ flex: 1;
+ align-items: stretch;
+ width: 100%;
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+
+ & .TextFieldTypeTextArea-TextArea {
+ line-height: var(--line-height-text-m);
+ }
+
+ & .FieldControlLayout-Children {
+ width: var(--field-control-layout-children-width);
+ }
+
+ & .ct--DataCell-Slots:has(.TextFieldTypeTextArea_resize),
+ & .ct--DataCell-ContentSlot:has(.TextFieldTypeTextArea_resize),
+ & .TextFieldTypeTextArea_resize,
+ & .TextFieldTypeTextArea_resize .FieldControlLayout-Container,
+ & .TextFieldTypeTextArea_resize .FieldControlLayout-Children,
+ & .TextFieldTypeTextArea_resize .TextFieldTypeTextArea-TextArea {
+ min-height: 100%;
+ height: 100%;
+ }
+
+ & .TextAreaAutoSize {
+ display: initial;
+ }
+}
diff --git a/src/components/TextFieldCell/TextFieldCell.tsx b/src/components/TextFieldCell/TextFieldCell.tsx
new file mode 100644
index 0000000..a4c5913
--- /dev/null
+++ b/src/components/TextFieldCell/TextFieldCell.tsx
@@ -0,0 +1,211 @@
+import './TextFieldCell.css';
+
+import { useClickOutside } from '@consta/uikit/__internal__/src/hooks/useClickOutside';
+import {
+ TextField,
+ TextFieldProps,
+ TextFieldPropValue,
+} from '@consta/uikit/TextFieldCanary';
+import { useFlag } from '@consta/uikit/useFlag';
+import { useForkRef } from '@consta/uikit/useForkRef';
+import { useKeysRef } from '@consta/uikit/useKeysRef';
+import React, { forwardRef, useEffect, useRef } from 'react';
+
+import { DataCell } from '##/components/DataCell';
+import { cn } from '##/utils/bem';
+
+const cnTextFieldCell = cn('TextFieldCell');
+
+export type TextFieldCellProps = Omit<
+ TextFieldProps,
+ 'view' | 'size' | 'form' | 'status'
+> & {
+ size?: 's' | 'm';
+ lineClamp?: number;
+ readModeRender?: (value?: TextFieldPropValue) => React.ReactNode;
+ level?: number;
+ truncate?: boolean;
+ status?: 'alert' | 'warning';
+ indicator?: 'alert' | 'warning';
+ readonly?: boolean;
+};
+
+type SplitProps = TextFieldCellProps<'textarea'> &
+ TextFieldCellProps<'text'> &
+ TextFieldCellProps<'textarray'> &
+ TextFieldCellProps<'password'> &
+ TextFieldCellProps<'number'>;
+
+const readModeRenderDefault = (
+ value?: TextFieldPropValue,
+) => value;
+
+export type TextFieldCellComponent = (
+ props: TextFieldCellProps,
+) => React.ReactNode | null;
+
+const TextFieldCellRender = (
+ props: SplitProps,
+ ref: React.Ref,
+) => {
+ const {
+ className,
+ size = 'm',
+ lineClamp,
+ type,
+ value,
+ readModeRender = readModeRenderDefault,
+ level,
+ truncate,
+ defaultValue,
+ onChange,
+ id,
+ name,
+ disabled,
+ maxLength,
+ minLength,
+ onFocus,
+ onBlur,
+ placeholder,
+ leftSide,
+ rightSide,
+ clearButton,
+ iconClear,
+ autoComplete,
+ readOnly,
+ tabIndex,
+ ariaLabel,
+ iconSize,
+ onClear,
+ inputRef: inputRefProp,
+ onKeyUp,
+ onKeyUpCapture,
+ onKeyDown,
+ onKeyDownCapture,
+ onCopy,
+ onCopyCapture,
+ onCut,
+ onCutCapture,
+ onPaste,
+ onPasteCapture,
+ onWheel,
+ max,
+ min,
+ step,
+ incrementButtons,
+ iconShowPassword,
+ iconHidePassword,
+ resize,
+ minRows,
+ maxRows,
+ rows,
+ renderValueItem,
+ inputValue,
+ onInputChange,
+ status,
+ indicator,
+ readonly,
+ ...restProps
+ } = props;
+ const refRoot = useRef(null);
+ const inputRef = useRef(null);
+ const inputRefForked = useForkRef([inputRef, inputRefProp]);
+
+ const [editMode, setEditMode] = useFlag();
+
+ useClickOutside({
+ isActive: editMode,
+ ignoreClicksInsideRefs: [refRoot],
+ handler: setEditMode.off,
+ });
+
+ useKeysRef({
+ isActive: editMode,
+ ref: refRoot,
+ keys: {
+ Escape: setEditMode.off,
+ },
+ });
+
+ useEffect(() => {
+ if (editMode) {
+ inputRef.current?.focus();
+ }
+ }, [editMode]);
+
+ const textFiledProps = {
+ className: cnTextFieldCell('Field'),
+ view: 'clear',
+ onDoubleClick: setEditMode.on,
+ value,
+ type,
+ inputRef: inputRefForked,
+ defaultValue,
+ onChange,
+ id,
+ name,
+ disabled,
+ maxLength,
+ minLength,
+ onFocus,
+ onBlur,
+ placeholder,
+ leftSide,
+ rightSide,
+ clearButton,
+ iconClear,
+ autoComplete,
+ readOnly,
+ tabIndex,
+ ariaLabel,
+ iconSize,
+ onClear,
+ onKeyUp,
+ onKeyUpCapture,
+ onKeyDown,
+ onKeyDownCapture,
+ max,
+ min,
+ step,
+ incrementButtons,
+ iconShowPassword,
+ iconHidePassword,
+ resize,
+ minRows,
+ maxRows,
+ rows,
+ renderValueItem,
+ inputValue,
+ onInputChange,
+ onCopy,
+ onCopyCapture,
+ onCut,
+ onCutCapture,
+ onPaste,
+ onPasteCapture,
+ onWheel,
+ size,
+ } as const;
+
+ return (
+
+ {editMode ? : readModeRender(value)}
+
+ );
+};
+
+export const TextFieldCell = forwardRef(
+ TextFieldCellRender,
+) as TextFieldCellComponent;
diff --git a/src/components/TextFieldCell/__stand__/TextFieldCell.dev.stand.mdx b/src/components/TextFieldCell/__stand__/TextFieldCell.dev.stand.mdx
new file mode 100644
index 0000000..0effdf7
--- /dev/null
+++ b/src/components/TextFieldCell/__stand__/TextFieldCell.dev.stand.mdx
@@ -0,0 +1,367 @@
+import { TextFieldCellExampleTypes } from './examples/TextFieldCellExampleTypes/TextFieldCellExampleTypes';
+import { TextFieldCellExampleAlertMessage } from './examples/TextFieldCellExampleAlertMessage/TextFieldCellExampleAlertMessage';
+import { MdxTabs } from '@consta/stand';
+
+# Обзор
+
+`TextFieldCell` позволяет вводить текст, числа или массивы строк. Компонент базируется на [`TextField`](##LIBS.LIB.STAND/lib:uikit/stand:components-textfield-canary) и подготовлен для использования в таблицах. Основные свойства компонента идентичны [`TextField`](##LIBS.LIB.STAND/lib:uikit/stand:components-textfield-canary).
+
+# Тип
+
+Доступные типы компонента:
+
+- `text` - текст
+- `textarea` - многострочный текст
+- `number` - число
+- `textarray` - массив строк
+
+
+
+
+
+```tsx
+import React, { useCallback, useState } from 'react';
+
+import { Table, TableColumn, TableRenderCell } from '@consta/table/Table';
+import { TextFieldCell } from '@consta/table/TextFieldCell';
+
+type Row = {
+ text: string;
+ textarea: string;
+ textareaAutosize: string;
+ number: string;
+ textArray: string[];
+};
+
+const rows: Row[] = [
+ {
+ text: 'value1',
+ textarea: 'value2',
+ textareaAutosize: 'value3',
+ number: 'value4',
+ textArray: ['value5', 'value6'],
+ },
+];
+
+const CellTypeText: TableRenderCell = (row) => {
+ const [value, setValue] = useState(row.row.text);
+
+ return (
+
+ );
+};
+
+const CellTypeTextArea: TableRenderCell = (row) => {
+ const [value, setValue] = useState(row.row.textarea);
+
+ return (
+
+ );
+};
+
+const CellTypeTextAreaAutosize: TableRenderCell = (row) => {
+ const [value, setValue] = useState(row.row.textareaAutosize);
+
+ return (
+
+ );
+};
+
+const CellTypeNumber: TableRenderCell = (row) => {
+ const [value, setValue] = useState(row.row.number);
+
+ return (
+
+ );
+};
+
+const CellTypeTextArray: TableRenderCell = (row) => {
+ const [value, setValue] = useState(row.row.textArray);
+
+ const [inputValue, setInputValue] = useState(null);
+ const onChangeValueArray = useCallback((value: string[] | null) => {
+ setValue(value);
+ setInputValue(null);
+ }, []);
+
+ return (
+ value?.join(', ')}
+ size="m"
+ />
+ );
+};
+
+const columns: TableColumn[] = [
+ {
+ title: 'TextArray',
+ accessor: 'textArray',
+ renderCell: CellTypeTextArray,
+ minWidth: 200,
+ },
+ {
+ title: 'Text',
+ accessor: 'text',
+ renderCell: CellTypeText,
+ minWidth: 200,
+ },
+
+ {
+ title: 'Textarea',
+ accessor: 'textarea',
+ renderCell: CellTypeTextArea,
+ minWidth: 200,
+ },
+ {
+ title: 'Textarea autosize',
+ accessor: 'textareaAutosize',
+ renderCell: CellTypeTextAreaAutosize,
+ minWidth: 200,
+ },
+
+ {
+ title: 'Number',
+ accessor: 'number',
+ renderCell: CellTypeNumber,
+ minWidth: 200,
+ },
+];
+
+export const TextFieldCellExampleTypes = () => (
+
+);
+```
+
+
+
+# Обработка ошибок
+
+В компоненте есть 2 свойства для отображения ошибок.
+
+`indicator` - отображает уголок в правом верхнем углу ячейки, в режиме чтения.
+`status` - отображает обводку поля в режиме редактирования.
+
+Для вывода ошибки можете использовать [`Popover`](##LIBS.LIB.STAND/lib:uikit/stand:components-popover-stable) и [`Informer`](##LIBS.LIB.STAND/lib:uikit/stand:components-informer-stable)
+
+В примере снизу ошибка возникает если количество элементов в массиве больше 10.
+
+
+
+
+
+```tsx
+import { Example } from '@consta/stand';
+import { Informer } from '@consta/uikit/Informer';
+import {
+ animateTimeout,
+ cnMixPopoverAnimate,
+} from '@consta/uikit/MixPopoverAnimate';
+import { Popover } from '@consta/uikit/Popover';
+import { useClickOutside } from '@consta/uikit/useClickOutside';
+import { useFlag } from '@consta/uikit/useFlag';
+import { useHover } from '@consta/uikit/useHover';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { Transition } from 'react-transition-group';
+
+import { Table, TableColumn, TableRenderCell } from '@consta/table/Table';
+import { TextFieldCell } from '@consta/table/TextFieldCell';
+
+type Row = {
+ col: string;
+ col2: string;
+ textArray: string[];
+};
+
+const rows: Row[] = [
+ {
+ col: 'value1',
+ textArray: Array.from({ length: 11 }, (_, i) => `элемент-${i + 1}`),
+ col2: 'value2',
+ },
+];
+
+const CellTypeTextArray: TableRenderCell = (row) => {
+ const popoverRef = useRef(null);
+ const cellRef = useRef(null);
+ const inputRef = useRef(null);
+
+ const [value, setValue] = useState(row.row.textArray);
+ const [open, setOpen] = useFlag();
+
+ const onChangeValue = useCallback((value: string[] | null) => {
+ setValue(value);
+ if (inputRef.current) {
+ inputRef.current.value = '';
+ }
+ }, []);
+
+ const error = !!(value && value.length > 10);
+
+ useEffect(() => setOpen.set(error), [error]);
+
+ useHover({
+ isActive: error,
+ refs: [popoverRef, cellRef],
+ onHover: setOpen.on,
+ onBlur: setOpen.off,
+ hoverDelay: 500,
+ blurDelay: 300,
+ });
+
+ useClickOutside({
+ isActive: open,
+ handler: setOpen.off,
+ ignoreClicksInsideRefs: [popoverRef, cellRef],
+ });
+
+ return (
+ <>
+ value?.join(', ')}
+ size="m"
+ status={error ? 'alert' : undefined}
+ indicator={error ? 'alert' : undefined}
+ ref={cellRef}
+ inputRef={inputRef}
+ />
+
+
+ {(animate) => {
+ return (
+
+
+
+ );
+ }}
+
+ >
+ );
+};
+
+const columns: TableColumn[] = [
+ {
+ title: 'Колонка',
+ accessor: 'col',
+ minWidth: 150,
+ },
+ {
+ title: 'Редактируемый массив',
+ accessor: 'textArray',
+ renderCell: CellTypeTextArray,
+ minWidth: 200,
+ },
+ {
+ title: 'Колонка',
+ accessor: 'col2',
+ minWidth: 150,
+ },
+];
+
+export const TextFieldCellExampleAlertMessage = () => (
+
+
+
+);
+```
+
+
+
+# Свойства
+
+| Свойство | Тип | По умолчанию | Описание |
+| ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | ------------ | ------------------------------------------------ |
+| [`size?`](##LIBS.LIB.STAND.TAB/lib:uikit/stand:components-textfield-canary/tab:dev/hash:размер) | `'s' \| 'm'` | `'m'` | Размер ячейки |
+| `lineClamp?` | `number` | - | Ограничение количества строк текста |
+| `readModeRender?` | `(value?: TextFieldPropValue) => React.ReactNode` | - | Функция для отображения значения в режиме чтения |
+| [`level?`](##LIBS.LIB.STAND.TAB/lib:table/stand:components-datacell-stable/tab:dev/hash:уровень-вложенности) | `number` | - | Уровень вложенности ячейки |
+| [`indicator?`](##LIBS.LIB.STAND.TAB/lib:table/stand:components-datacell-stable/tab:dev/hash:индикатор) | `'alert' \| 'warning'` | - | Индикатор состояния |
+| `truncate?` | `boolean` | - | Обрезка текста многоточием |
+| [`value?`](##LIBS.LIB.STAND.TAB/lib:uikit/stand:components-textfield-canary/tab:dev/hash:значение) | `TextFieldPropValue` | - | Значение поля ввода |
+| `onChange?` | `TextFieldPropOnChange` | - | Обработчик изменения значения |
+| `defaultValue?` | `TextFieldPropValue` | - | Значение по умолчанию |
+| `id?` | `string` | - | Идентификатор поля |
+| `name?` | `string` | - | Имя поля |
+| `disabled?` | `boolean` | - | Флаг отключения поля |
+| `maxLength?` | `number` | - | Максимальная длина текста |
+| `minLength?` | `number` | - | Минимальная длина текста |
+| [`placeholder?`](##LIBS.LIB.STAND.TAB/lib:uikit/stand:components-textfield-canary/tab:dev/hash:подсказка) | `string` | - | Подсказка для ввода |
+| [`leftSide?`](##LIBS.LIB.STAND.TAB/lib:uikit/stand:components-textfield-canary/tab:dev/hash:дополнительная-информация) | `React.ReactNode` | - | Элемент слева внутри поля |
+| [`rightSide?`](##LIBS.LIB.STAND.TAB/lib:uikit/stand:components-textfield-canary/tab:dev/hash:дополнительная-информация) | `React.ReactNode` | - | Элемент справа внутри поля |
+| [`clearButton?`](##LIBS.LIB.STAND.TAB/lib:uikit/stand:components-textfield-canary/tab:dev/hash:кнопка-для-очистки-поля) | `boolean` | - | Флаг отображения кнопки очистки |
+| `iconClear?` | `IconComponent` | - | Иконка для кнопки очистки |
+| `iconSize?` | `'s' \| 'm'` | - | Размер иконок |
+| `onClear?` | `React.MouseEventHandler` | - | Обработчик очистки поля |
+| `inputRef?` | `React.Ref` | - | Ссылка на DOM-элемент input |
+| [`type?`](#тип) | `'text' \| 'textarea' \| 'textarray' \| 'password' \| 'number'` | `'text'` | Тип поля ввода |
+| `max?` | `number` | - | Максимальное значение |
+| `min?` | `number` | - | Минимальное значение |
+| [`step?`](##LIBS.LIB.STAND.TAB/lib:uikit/stand:components-textfield-canary/tab:dev/hash:шаг--значения-и-диапазон) | `number` | - | Шаг изменения значения |
+| `incrementButtons?` | `boolean` | - | Флаг отображения кнопок инкремента |
+| `iconShowPassword?` | `IconComponent` | - | Иконка показа пароля |
+| `iconHidePassword?` | `IconComponent` | - | Иконка скрытия пароля |
+| [`resize?`](##LIBS.LIB.STAND.TAB/lib:uikit/stand:components-textfield-canary/tab:dev/hash:изменение-высоты) | `boolean \| 'auto'` | - | Возможность изменения размера |
+| [`minRows?`](##LIBS.LIB.STAND.TAB/lib:uikit/stand:components-textfield-canary/tab:dev/hash:автоматическая-высота) | `number` | - | Минимальное количество строк |
+| [`maxRows?`](##LIBS.LIB.STAND.TAB/lib:uikit/stand:components-textfield-canary/tab:dev/hash:автоматическая-высота) | `number` | - | Максимальное количество строк |
+| [`rows?`](##LIBS.LIB.STAND.TAB/lib:uikit/stand:components-textfield-canary/tab:dev/hash:поле-в-несколько-строк) | `number` | - | Количество строк |
+| `renderValueItem?` | `(item: string) => React.ReactNode` | - | Функция отображения элемента массива |
+| `inputValue?` | `string \| null` | - | Значение поля ввода для массива |
+| `onInputChange?` | `((value: string \| null) => void)` | - | Обработчик изменения значения ввода |
+| `className?` | `string` | - | Дополнительный CSS-класс. |
diff --git a/src/components/TextFieldCell/__stand__/TextFieldCell.stand.mdx b/src/components/TextFieldCell/__stand__/TextFieldCell.stand.mdx
new file mode 100644
index 0000000..cb60a27
--- /dev/null
+++ b/src/components/TextFieldCell/__stand__/TextFieldCell.stand.mdx
@@ -0,0 +1,3 @@
+```tsx
+import { TextFieldCell } from '@consta/table/TextFieldCell';
+```
diff --git a/src/components/TextFieldCell/__stand__/TextFieldCell.stand.tsx b/src/components/TextFieldCell/__stand__/TextFieldCell.stand.tsx
new file mode 100644
index 0000000..7aafb49
--- /dev/null
+++ b/src/components/TextFieldCell/__stand__/TextFieldCell.stand.tsx
@@ -0,0 +1,12 @@
+import { createStand } from '##/stand/standConfig';
+
+export default createStand({
+ title: 'TextFieldCell',
+ id: 'TextFieldCell',
+ group: 'components',
+ description: 'Компонент текстового поля для таблицы',
+ version: '0.8.0',
+ status: 'stable',
+ alias: ['ячейка', 'input', 'текстовое поле'],
+ order: 10,
+});
diff --git a/src/components/TextFieldCell/__stand__/TextFieldCell.variants.tsx b/src/components/TextFieldCell/__stand__/TextFieldCell.variants.tsx
new file mode 100644
index 0000000..d2df6e8
--- /dev/null
+++ b/src/components/TextFieldCell/__stand__/TextFieldCell.variants.tsx
@@ -0,0 +1,194 @@
+import { IconPhoto } from '@consta/icons/IconPhoto';
+import { useBoolean, useNumber, useSelect, useText } from '@consta/stand';
+import React, { useCallback, useState } from 'react';
+
+import { Table, TableColumn, TableRenderCell } from '##/components/Table';
+
+import { TextFieldCell } from '..';
+
+type Row = { data: string };
+
+const rows: Row[] = [{ data: 'Двойной клик' }];
+
+const resizeMap = {
+ true: true,
+ false: false,
+ auto: 'auto',
+} as const;
+
+const getStep = (
+ type: string | undefined,
+ withStepArray: boolean,
+ step: number | undefined,
+) => {
+ if (type !== 'number') {
+ return undefined;
+ }
+
+ if (withStepArray) {
+ return [10, 50, 100];
+ }
+
+ return step;
+};
+
+const RenderCell: TableRenderCell = (row) => {
+ const [value, setValue] = useState(row.row.data);
+ const [valueArray, setValueArray] = useState([row.row.data]);
+ const [inputValue, setInputValue] = useState(null);
+ const onChangeValueArray = useCallback((value: string[] | null) => {
+ setValueArray(value);
+ setInputValue(null);
+ }, []);
+
+ const size = useSelect('size', ['m', 's'], 'm');
+
+ const type = useSelect(
+ 'type',
+ ['text', 'number', 'password', 'textarea', 'textarray'],
+ 'text',
+ );
+ const resize =
+ useSelect(
+ 'resize',
+ ['false', 'true', 'auto'],
+ 'false',
+ type === 'textarea',
+ ) || 'false';
+
+ const placeholder = useText('placeholder', 'placeholder');
+ const rows = useNumber('rows', 3, type === 'textarea' && resize !== 'auto');
+ const lineClamp = useNumber('lineClamp', 0);
+ const minRows = useNumber(
+ 'minRows',
+ 1,
+ type === 'textarea' && resize === 'auto',
+ );
+ const maxRows = useNumber(
+ 'maxRows',
+ 5,
+ type === 'textarea' && resize === 'auto',
+ );
+
+ const step = useNumber('step', 1, type === 'number');
+ const withStepArray = useBoolean('withStepArray', false, type === 'number');
+ const incrementButtons = useBoolean(
+ 'incrementButtons',
+ true,
+ type === 'number',
+ );
+ const min = useNumber('min', 0, type === 'number');
+ const max = useNumber('max', 150, type === 'number');
+
+ const disabled = useBoolean('disabled', false);
+ const clearButton = useBoolean('clearButton', true);
+ const maxLength = useNumber('maxLength', 1000, type !== 'number');
+
+ const leftSideType = useSelect('leftSideType', ['icon', 'text']);
+ const leftSideText = useText('leftSideText', 'from');
+ const rightSideType = useSelect('rightSideType', ['icon', 'text']);
+ const rightSideText = useText('rightSideText', 'm²');
+ const level = useNumber('level', 0);
+ const status = useSelect('status', ['alert', 'warning']);
+
+ const leftSideSelect = {
+ text: leftSideText,
+ icon: IconPhoto,
+ };
+
+ const rightSideSelect = {
+ text: rightSideText,
+ icon: IconPhoto,
+ };
+
+ const leftSide = leftSideType && leftSideSelect[leftSideType];
+ const rightSide = rightSideType && rightSideSelect[rightSideType];
+
+ const indicator = useSelect('indicator', ['alert', 'warning']);
+
+ const props = {
+ size,
+ placeholder,
+ type,
+ disabled,
+ clearButton,
+ maxLength,
+ leftSide,
+ rightSide,
+ level,
+ lineClamp,
+ status,
+ indicator,
+ };
+
+ if (type === 'textarray') {
+ return (
+ value?.join(', ')}
+ onInputChange={setInputValue}
+ inputValue={inputValue}
+ />
+ );
+ }
+
+ if (type === 'textarea') {
+ return (
+
+ );
+ }
+
+ if (type === 'number') {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+const Variants = () => {
+ const columns: TableColumn[] = [
+ { title: 'Изменяемые данные', accessor: 'data', renderCell: RenderCell },
+ ];
+
+ return (
+
+ );
+};
+
+export default Variants;
diff --git a/src/components/TextFieldCell/__stand__/examples/TextFieldCellExampleAlertMessage/TextFieldCellExampleAlertMessage.tsx b/src/components/TextFieldCell/__stand__/examples/TextFieldCellExampleAlertMessage/TextFieldCellExampleAlertMessage.tsx
new file mode 100644
index 0000000..7f24628
--- /dev/null
+++ b/src/components/TextFieldCell/__stand__/examples/TextFieldCellExampleAlertMessage/TextFieldCellExampleAlertMessage.tsx
@@ -0,0 +1,139 @@
+import { Example } from '@consta/stand';
+import { Informer } from '@consta/uikit/Informer';
+import {
+ animateTimeout,
+ cnMixPopoverAnimate,
+} from '@consta/uikit/MixPopoverAnimate';
+import { Popover } from '@consta/uikit/Popover';
+import { useClickOutside } from '@consta/uikit/useClickOutside';
+import { useFlag } from '@consta/uikit/useFlag';
+import { useHover } from '@consta/uikit/useHover';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { Transition } from 'react-transition-group';
+
+import { Table, TableColumn, TableRenderCell } from '##/components/Table';
+import { TextFieldCell } from '##/components/TextFieldCell';
+
+type Row = {
+ col: string;
+ col2: string;
+ textArray: string[];
+};
+
+const rows: Row[] = [
+ {
+ col: 'value1',
+ textArray: Array.from({ length: 11 }, (_, i) => `элемент-${i + 1}`),
+ col2: 'value2',
+ },
+];
+
+const CellTypeTextArray: TableRenderCell = (row) => {
+ const popoverRef = useRef(null);
+ const cellRef = useRef(null);
+ const inputRef = useRef(null);
+
+ const [value, setValue] = useState(row.row.textArray);
+ const [open, setOpen] = useFlag();
+
+ const onChangeValue = useCallback((value: string[] | null) => {
+ setValue(value);
+ if (inputRef.current) {
+ inputRef.current.value = '';
+ }
+ }, []);
+
+ const error = !!(value && value.length > 10);
+
+ useEffect(() => setOpen.set(error), [error]);
+
+ useHover({
+ isActive: error,
+ refs: [popoverRef, cellRef],
+ onHover: setOpen.on,
+ onBlur: setOpen.off,
+ hoverDelay: 500,
+ blurDelay: 300,
+ });
+
+ useClickOutside({
+ isActive: open,
+ handler: setOpen.off,
+ ignoreClicksInsideRefs: [popoverRef, cellRef],
+ });
+
+ return (
+ <>
+ value?.join(', ')}
+ size="m"
+ status={error ? 'alert' : undefined}
+ indicator={error ? 'alert' : undefined}
+ ref={cellRef}
+ inputRef={inputRef}
+ />
+
+
+ {(animate) => {
+ return (
+
+
+
+ );
+ }}
+
+ >
+ );
+};
+
+const columns: TableColumn[] = [
+ {
+ title: 'Колонка',
+ accessor: 'col',
+ minWidth: 150,
+ },
+ {
+ title: 'Редактируемый массив',
+ accessor: 'textArray',
+ renderCell: CellTypeTextArray,
+ minWidth: 200,
+ },
+ {
+ title: 'Колонка',
+ accessor: 'col2',
+ minWidth: 150,
+ },
+];
+
+export const TextFieldCellExampleAlertMessage = () => (
+
+
+
+);
diff --git a/src/components/TextFieldCell/__stand__/examples/TextFieldCellExampleTypes/TextFieldCellExampleTypes.tsx b/src/components/TextFieldCell/__stand__/examples/TextFieldCellExampleTypes/TextFieldCellExampleTypes.tsx
new file mode 100644
index 0000000..8a205ee
--- /dev/null
+++ b/src/components/TextFieldCell/__stand__/examples/TextFieldCellExampleTypes/TextFieldCellExampleTypes.tsx
@@ -0,0 +1,144 @@
+import { Example } from '@consta/stand';
+import React, { useCallback, useState } from 'react';
+
+import { Table, TableColumn, TableRenderCell } from '##/components/Table';
+import { TextFieldCell } from '##/components/TextFieldCell';
+
+type Row = {
+ text: string;
+ textarea: string;
+ textareaAutosize: string;
+ number: string;
+ textArray: string[];
+};
+
+const rows: Row[] = [
+ {
+ text: 'value1',
+ textarea: 'value2',
+ textareaAutosize: 'value3',
+ number: 'value4',
+ textArray: ['value5', 'value6'],
+ },
+];
+
+const CellTypeText: TableRenderCell = (row) => {
+ const [value, setValue] = useState(row.row.text);
+
+ return (
+
+ );
+};
+
+const CellTypeTextArea: TableRenderCell = (row) => {
+ const [value, setValue] = useState(row.row.textarea);
+
+ return (
+
+ );
+};
+
+const CellTypeTextAreaAutosize: TableRenderCell = (row) => {
+ const [value, setValue] = useState(row.row.textareaAutosize);
+
+ return (
+
+ );
+};
+
+const CellTypeNumber: TableRenderCell = (row) => {
+ const [value, setValue] = useState(row.row.number);
+
+ return (
+
+ );
+};
+
+const CellTypeTextArray: TableRenderCell = (row) => {
+ const [value, setValue] = useState(row.row.textArray);
+
+ const [inputValue, setInputValue] = useState(null);
+ const onChangeValueArray = useCallback((value: string[] | null) => {
+ setValue(value);
+ setInputValue(null);
+ }, []);
+
+ return (
+ value?.join(', ')}
+ size="m"
+ />
+ );
+};
+
+const columns: TableColumn[] = [
+ {
+ title: 'TextArray',
+ accessor: 'textArray',
+ renderCell: CellTypeTextArray,
+ minWidth: 200,
+ },
+ {
+ title: 'Text',
+ accessor: 'text',
+ renderCell: CellTypeText,
+ minWidth: 200,
+ },
+
+ {
+ title: 'Textarea',
+ accessor: 'textarea',
+ renderCell: CellTypeTextArea,
+ minWidth: 200,
+ },
+ {
+ title: 'Textarea autosize',
+ accessor: 'textareaAutosize',
+ renderCell: CellTypeTextAreaAutosize,
+ minWidth: 200,
+ },
+
+ {
+ title: 'Number',
+ accessor: 'number',
+ renderCell: CellTypeNumber,
+ minWidth: 200,
+ },
+];
+
+export const TextFieldCellExampleTypes = () => (
+
+
+
+);
diff --git a/src/components/TextFieldCell/index.ts b/src/components/TextFieldCell/index.ts
new file mode 100644
index 0000000..1bc66df
--- /dev/null
+++ b/src/components/TextFieldCell/index.ts
@@ -0,0 +1 @@
+export * from './TextFieldCell';
diff --git a/src/components/Toolbar/Toolbar.css b/src/components/Toolbar/Toolbar.css
index bf5a2bb..22ec818 100644
--- a/src/components/Toolbar/Toolbar.css
+++ b/src/components/Toolbar/Toolbar.css
@@ -1,20 +1,23 @@
.ct--Toolbar {
--bar-default-border-radius: calc(var(--control-radius) * 2);
--bar-default-border: var(--control-border-width) solid var(--color-bg-border);
-
background: var(--color-bg-stripe);
- /* border: var(--control-border-width) solid var(--color-bg-border); */
+ /* border: var(--control-border-width) solid var(--color-bg-border); */
&_form {
&_default {
border-radius: var(--bar-default-border-radius);
}
+
&_defaultBrick {
- border-radius: var(--bar-default-border-radius)
+ border-radius:
+ var(--bar-default-border-radius)
var(--bar-default-border-radius) 0 0;
}
+
&_brickDefault {
- border-radius: 0 0 var(--bar-default-border-radius)
+ border-radius:
+ 0 0 var(--bar-default-border-radius)
var(--bar-default-border-radius);
}
}
@@ -23,9 +26,11 @@
&_all {
border: var(--bar-default-border);
}
+
&_top {
border-top: var(--bar-default-border);
}
+
&_bottom {
border-bottom: var(--bar-default-border);
}
diff --git a/src/components/Toolbar/ToolbarDivider/ToolbarDivider.css b/src/components/Toolbar/ToolbarDivider/ToolbarDivider.css
index bfb2f4a..801bf0a 100644
--- a/src/components/Toolbar/ToolbarDivider/ToolbarDivider.css
+++ b/src/components/Toolbar/ToolbarDivider/ToolbarDivider.css
@@ -1,5 +1,5 @@
.ct--ToolbarDivider {
- background-color: var(--color-bg-border);
width: calc(var(--control-border-width) * 2);
height: var(--toolbar-divider-height);
+ background-color: var(--color-bg-border);
}
diff --git a/src/utils/object/clearUndefined.ts b/src/utils/object/clearUndefined.ts
new file mode 100644
index 0000000..006f474
--- /dev/null
+++ b/src/utils/object/clearUndefined.ts
@@ -0,0 +1,11 @@
+export const clearUndefined = >(
+ obj: T,
+): T => {
+ const result = { ...obj };
+ for (const key in result) {
+ if (result[key] === undefined) {
+ delete result[key];
+ }
+ }
+ return result as T;
+};
diff --git a/src/utils/object/index.ts b/src/utils/object/index.ts
index 4fc8966..24653bc 100644
--- a/src/utils/object/index.ts
+++ b/src/utils/object/index.ts
@@ -1,2 +1,3 @@
export * from './isEmpty';
export * from './isEq';
+export * from './clearUndefined';