@cloud-ru/ds-markdown 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/LICENSE +201 -0
- package/README.md +273 -0
- package/dist/cjs/components/Markdown/CodeBlock.d.ts +9 -0
- package/dist/cjs/components/Markdown/CodeBlock.js +58 -0
- package/dist/cjs/components/Markdown/Markdown.d.ts +2 -0
- package/dist/cjs/components/Markdown/Markdown.js +25 -0
- package/dist/cjs/components/Markdown/index.d.ts +1 -0
- package/dist/cjs/components/Markdown/index.js +17 -0
- package/dist/cjs/components/Markdown/styles.module.css +205 -0
- package/dist/cjs/components/MarkdownEditor/MarkdownEditor.d.ts +2 -0
- package/dist/cjs/components/MarkdownEditor/MarkdownEditor.js +176 -0
- package/dist/cjs/components/MarkdownEditor/index.d.ts +1 -0
- package/dist/cjs/components/MarkdownEditor/index.js +17 -0
- package/dist/cjs/components/MarkdownEditor/styles.module.css +273 -0
- package/dist/cjs/components/Toolbar/Toolbar.d.ts +9 -0
- package/dist/cjs/components/Toolbar/Toolbar.js +173 -0
- package/dist/cjs/components/Toolbar/constants.d.ts +13 -0
- package/dist/cjs/components/Toolbar/constants.js +40 -0
- package/dist/cjs/components/Toolbar/hooks/index.d.ts +2 -0
- package/dist/cjs/components/Toolbar/hooks/index.js +18 -0
- package/dist/cjs/components/Toolbar/hooks/useToolbarHotkeys.d.ts +21 -0
- package/dist/cjs/components/Toolbar/hooks/useToolbarHotkeys.js +59 -0
- package/dist/cjs/components/Toolbar/hooks/useToolbarOverflow.d.ts +14 -0
- package/dist/cjs/components/Toolbar/hooks/useToolbarOverflow.js +87 -0
- package/dist/cjs/components/Toolbar/index.d.ts +2 -0
- package/dist/cjs/components/Toolbar/index.js +20 -0
- package/dist/cjs/components/Toolbar/styles.module.css +17 -0
- package/dist/cjs/components/index.d.ts +2 -0
- package/dist/cjs/components/index.js +20 -0
- package/dist/cjs/constants.d.ts +65 -0
- package/dist/cjs/constants.js +67 -0
- package/dist/cjs/helperComponents/ButtonHeading/ButtonHeading.d.ts +6 -0
- package/dist/cjs/helperComponents/ButtonHeading/ButtonHeading.js +61 -0
- package/dist/cjs/helperComponents/ButtonHeading/index.d.ts +1 -0
- package/dist/cjs/helperComponents/ButtonHeading/index.js +17 -0
- package/dist/cjs/helperComponents/ButtonHeading/styles.module.css +3 -0
- package/dist/cjs/helperComponents/Buttons/Buttons.d.ts +10 -0
- package/dist/cjs/helperComponents/Buttons/Buttons.js +21 -0
- package/dist/cjs/helperComponents/Buttons/index.d.ts +1 -0
- package/dist/cjs/helperComponents/Buttons/index.js +17 -0
- package/dist/cjs/helperComponents/Buttons/styles.module.css +17 -0
- package/dist/cjs/helperComponents/Modals/CustomizeTableModal.d.ts +8 -0
- package/dist/cjs/helperComponents/Modals/CustomizeTableModal.js +39 -0
- package/dist/cjs/helperComponents/Modals/ImageModal.d.ts +7 -0
- package/dist/cjs/helperComponents/Modals/ImageModal.js +44 -0
- package/dist/cjs/helperComponents/Modals/LinkModal.d.ts +13 -0
- package/dist/cjs/helperComponents/Modals/LinkModal.js +42 -0
- package/dist/cjs/helperComponents/Modals/index.d.ts +3 -0
- package/dist/cjs/helperComponents/Modals/index.js +19 -0
- package/dist/cjs/helperComponents/Modals/styles.module.css +17 -0
- package/dist/cjs/helperComponents/PrivateButton/PrivateButton.d.ts +17 -0
- package/dist/cjs/helperComponents/PrivateButton/PrivateButton.js +27 -0
- package/dist/cjs/helperComponents/PrivateButton/index.d.ts +1 -0
- package/dist/cjs/helperComponents/PrivateButton/index.js +17 -0
- package/dist/cjs/helperComponents/PrivateButton/styles.module.css +140 -0
- package/dist/cjs/helperComponents/TableSelectItem/TableSelectItem.d.ts +11 -0
- package/dist/cjs/helperComponents/TableSelectItem/TableSelectItem.js +12 -0
- package/dist/cjs/helperComponents/TableSelectItem/index.d.ts +1 -0
- package/dist/cjs/helperComponents/TableSelectItem/index.js +17 -0
- package/dist/cjs/helperComponents/TableSelectItem/styles.module.css +101 -0
- package/dist/cjs/helperComponents/TableSelectSettingsGrid/TableSelectSettingsGrid.d.ts +4 -0
- package/dist/cjs/helperComponents/TableSelectSettingsGrid/TableSelectSettingsGrid.js +27 -0
- package/dist/cjs/helperComponents/TableSelectSettingsGrid/index.d.ts +1 -0
- package/dist/cjs/helperComponents/TableSelectSettingsGrid/index.js +17 -0
- package/dist/cjs/helperComponents/TableSelectSettingsGrid/styles.module.css +54 -0
- package/dist/cjs/helperComponents/index.d.ts +6 -0
- package/dist/cjs/helperComponents/index.js +22 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +20 -0
- package/dist/cjs/locale/index.d.ts +199 -0
- package/dist/cjs/locale/index.js +104 -0
- package/dist/cjs/lowlight.d.ts +14 -0
- package/dist/cjs/lowlight.js +35 -0
- package/dist/cjs/mixins.css +0 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/rawMarkdownCommands.d.ts +16 -0
- package/dist/cjs/rawMarkdownCommands.js +145 -0
- package/dist/cjs/style.css +875 -0
- package/dist/cjs/styles/syntax.module.css +47 -0
- package/dist/cjs/toolbarApi.d.ts +33 -0
- package/dist/cjs/toolbarApi.js +100 -0
- package/dist/cjs/types.d.ts +52 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/components/Markdown/CodeBlock.d.ts +9 -0
- package/dist/esm/components/Markdown/CodeBlock.js +52 -0
- package/dist/esm/components/Markdown/Markdown.d.ts +2 -0
- package/dist/esm/components/Markdown/Markdown.js +19 -0
- package/dist/esm/components/Markdown/index.d.ts +1 -0
- package/dist/esm/components/Markdown/index.js +1 -0
- package/dist/esm/components/Markdown/styles.module.css +205 -0
- package/dist/esm/components/MarkdownEditor/MarkdownEditor.d.ts +2 -0
- package/dist/esm/components/MarkdownEditor/MarkdownEditor.js +170 -0
- package/dist/esm/components/MarkdownEditor/index.d.ts +1 -0
- package/dist/esm/components/MarkdownEditor/index.js +1 -0
- package/dist/esm/components/MarkdownEditor/styles.module.css +273 -0
- package/dist/esm/components/Toolbar/Toolbar.d.ts +9 -0
- package/dist/esm/components/Toolbar/Toolbar.js +167 -0
- package/dist/esm/components/Toolbar/constants.d.ts +13 -0
- package/dist/esm/components/Toolbar/constants.js +37 -0
- package/dist/esm/components/Toolbar/hooks/index.d.ts +2 -0
- package/dist/esm/components/Toolbar/hooks/index.js +2 -0
- package/dist/esm/components/Toolbar/hooks/useToolbarHotkeys.d.ts +21 -0
- package/dist/esm/components/Toolbar/hooks/useToolbarHotkeys.js +56 -0
- package/dist/esm/components/Toolbar/hooks/useToolbarOverflow.d.ts +14 -0
- package/dist/esm/components/Toolbar/hooks/useToolbarOverflow.js +84 -0
- package/dist/esm/components/Toolbar/index.d.ts +2 -0
- package/dist/esm/components/Toolbar/index.js +2 -0
- package/dist/esm/components/Toolbar/styles.module.css +17 -0
- package/dist/esm/components/index.d.ts +2 -0
- package/dist/esm/components/index.js +4 -0
- package/dist/esm/constants.d.ts +65 -0
- package/dist/esm/constants.js +61 -0
- package/dist/esm/helperComponents/ButtonHeading/ButtonHeading.d.ts +6 -0
- package/dist/esm/helperComponents/ButtonHeading/ButtonHeading.js +55 -0
- package/dist/esm/helperComponents/ButtonHeading/index.d.ts +1 -0
- package/dist/esm/helperComponents/ButtonHeading/index.js +1 -0
- package/dist/esm/helperComponents/ButtonHeading/styles.module.css +3 -0
- package/dist/esm/helperComponents/Buttons/Buttons.d.ts +10 -0
- package/dist/esm/helperComponents/Buttons/Buttons.js +13 -0
- package/dist/esm/helperComponents/Buttons/index.d.ts +1 -0
- package/dist/esm/helperComponents/Buttons/index.js +1 -0
- package/dist/esm/helperComponents/Buttons/styles.module.css +17 -0
- package/dist/esm/helperComponents/Modals/CustomizeTableModal.d.ts +8 -0
- package/dist/esm/helperComponents/Modals/CustomizeTableModal.js +33 -0
- package/dist/esm/helperComponents/Modals/ImageModal.d.ts +7 -0
- package/dist/esm/helperComponents/Modals/ImageModal.js +38 -0
- package/dist/esm/helperComponents/Modals/LinkModal.d.ts +13 -0
- package/dist/esm/helperComponents/Modals/LinkModal.js +36 -0
- package/dist/esm/helperComponents/Modals/index.d.ts +3 -0
- package/dist/esm/helperComponents/Modals/index.js +3 -0
- package/dist/esm/helperComponents/Modals/styles.module.css +17 -0
- package/dist/esm/helperComponents/PrivateButton/PrivateButton.d.ts +17 -0
- package/dist/esm/helperComponents/PrivateButton/PrivateButton.js +21 -0
- package/dist/esm/helperComponents/PrivateButton/index.d.ts +1 -0
- package/dist/esm/helperComponents/PrivateButton/index.js +1 -0
- package/dist/esm/helperComponents/PrivateButton/styles.module.css +140 -0
- package/dist/esm/helperComponents/TableSelectItem/TableSelectItem.d.ts +11 -0
- package/dist/esm/helperComponents/TableSelectItem/TableSelectItem.js +6 -0
- package/dist/esm/helperComponents/TableSelectItem/index.d.ts +1 -0
- package/dist/esm/helperComponents/TableSelectItem/index.js +1 -0
- package/dist/esm/helperComponents/TableSelectItem/styles.module.css +101 -0
- package/dist/esm/helperComponents/TableSelectSettingsGrid/TableSelectSettingsGrid.d.ts +4 -0
- package/dist/esm/helperComponents/TableSelectSettingsGrid/TableSelectSettingsGrid.js +21 -0
- package/dist/esm/helperComponents/TableSelectSettingsGrid/index.d.ts +1 -0
- package/dist/esm/helperComponents/TableSelectSettingsGrid/index.js +1 -0
- package/dist/esm/helperComponents/TableSelectSettingsGrid/styles.module.css +54 -0
- package/dist/esm/helperComponents/index.d.ts +6 -0
- package/dist/esm/helperComponents/index.js +6 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/locale/index.d.ts +199 -0
- package/dist/esm/locale/index.js +101 -0
- package/dist/esm/lowlight.d.ts +14 -0
- package/dist/esm/lowlight.js +29 -0
- package/dist/esm/mixins.css +0 -0
- package/dist/esm/rawMarkdownCommands.d.ts +16 -0
- package/dist/esm/rawMarkdownCommands.js +135 -0
- package/dist/esm/style.css +875 -0
- package/dist/esm/styles/syntax.module.css +47 -0
- package/dist/esm/toolbarApi.d.ts +33 -0
- package/dist/esm/toolbarApi.js +96 -0
- package/dist/esm/types.d.ts +52 -0
- package/dist/esm/types.js +1 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/package.json +84 -0
- package/src/components/Markdown/CodeBlock.tsx +96 -0
- package/src/components/Markdown/Markdown.tsx +47 -0
- package/src/components/Markdown/index.ts +1 -0
- package/src/components/Markdown/styles.module.scss +105 -0
- package/src/components/MarkdownEditor/MarkdownEditor.tsx +322 -0
- package/src/components/MarkdownEditor/index.ts +1 -0
- package/src/components/MarkdownEditor/styles.module.scss +201 -0
- package/src/components/Toolbar/Toolbar.tsx +292 -0
- package/src/components/Toolbar/constants.tsx +63 -0
- package/src/components/Toolbar/hooks/index.ts +2 -0
- package/src/components/Toolbar/hooks/useToolbarHotkeys.ts +74 -0
- package/src/components/Toolbar/hooks/useToolbarOverflow.ts +102 -0
- package/src/components/Toolbar/index.ts +2 -0
- package/src/components/Toolbar/styles.module.scss +29 -0
- package/src/components/index.ts +4 -0
- package/src/constants.ts +67 -0
- package/src/helperComponents/ButtonHeading/ButtonHeading.tsx +100 -0
- package/src/helperComponents/ButtonHeading/index.ts +1 -0
- package/src/helperComponents/ButtonHeading/styles.module.scss +5 -0
- package/src/helperComponents/Buttons/Buttons.tsx +30 -0
- package/src/helperComponents/Buttons/index.ts +1 -0
- package/src/helperComponents/Buttons/styles.module.scss +26 -0
- package/src/helperComponents/Modals/CustomizeTableModal.tsx +82 -0
- package/src/helperComponents/Modals/ImageModal.tsx +84 -0
- package/src/helperComponents/Modals/LinkModal.tsx +92 -0
- package/src/helperComponents/Modals/index.ts +3 -0
- package/src/helperComponents/Modals/styles.module.scss +21 -0
- package/src/helperComponents/PrivateButton/PrivateButton.tsx +88 -0
- package/src/helperComponents/PrivateButton/index.ts +1 -0
- package/src/helperComponents/PrivateButton/styles.module.scss +77 -0
- package/src/helperComponents/TableSelectItem/TableSelectItem.tsx +43 -0
- package/src/helperComponents/TableSelectItem/index.ts +1 -0
- package/src/helperComponents/TableSelectItem/styles.module.scss +26 -0
- package/src/helperComponents/TableSelectSettingsGrid/TableSelectSettingsGrid.tsx +48 -0
- package/src/helperComponents/TableSelectSettingsGrid/index.ts +1 -0
- package/src/helperComponents/TableSelectSettingsGrid/styles.module.scss +65 -0
- package/src/helperComponents/index.ts +6 -0
- package/src/index.ts +4 -0
- package/src/locale/index.ts +105 -0
- package/src/lowlight.ts +30 -0
- package/src/mixins.scss +123 -0
- package/src/rawMarkdownCommands.ts +179 -0
- package/src/styles/syntax.module.scss +54 -0
- package/src/toolbarApi.ts +152 -0
- package/src/types.ts +56 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useLayoutEffect } from '@cloud-ru/ds-utils';
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { ToolbarItemId } from '../../../types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Считает, сколько кнопок тулбара помещается по ширине, остальные — в overflow («Ещё»).
|
|
8
|
+
* Возвращает `rootRef` (корень тулбара, под ResizeObserver), `moreButtonRef` (для измерения
|
|
9
|
+
* «веса» overflow-группы) и `visibleCount` (число видимых кнопок).
|
|
10
|
+
*
|
|
11
|
+
* Края слотов кэшируются в полной раскладке (все кнопки, без More) и переиспользуются при
|
|
12
|
+
* пересчёте — поэтому не нужно арифметически приближать гэпы/дивайдеры.
|
|
13
|
+
*/
|
|
14
|
+
export function useToolbarOverflow(items: ToolbarItemId[]) {
|
|
15
|
+
const rootRef = useRef<HTMLDivElement | null>(null);
|
|
16
|
+
const moreButtonRef = useRef<HTMLElement>(null);
|
|
17
|
+
// Правый край каждого слота (отн. левого края тулбара), измеренный в ПОЛНОЙ раскладке.
|
|
18
|
+
const slotEdgesRef = useRef<Map<string, number>>(new Map());
|
|
19
|
+
// Реальная ширина группы «дивайдер + More» — измеряется, когда More присутствует в DOM.
|
|
20
|
+
const moreReserveRef = useRef(48);
|
|
21
|
+
const [visibleCount, setVisibleCount] = useState(items.length);
|
|
22
|
+
|
|
23
|
+
useLayoutEffect(() => {
|
|
24
|
+
const root = rootRef.current;
|
|
25
|
+
|
|
26
|
+
if (!root) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const edges = slotEdgesRef.current;
|
|
31
|
+
edges.clear();
|
|
32
|
+
|
|
33
|
+
let rafId = 0;
|
|
34
|
+
|
|
35
|
+
const measure = () => {
|
|
36
|
+
const rootRect = root.getBoundingClientRect();
|
|
37
|
+
const padRight = parseFloat(getComputedStyle(root).paddingRight) || 0;
|
|
38
|
+
// Правая граница контентной области тулбара (отн. левого края root).
|
|
39
|
+
const limit = root.clientWidth - padRight;
|
|
40
|
+
const slots = Array.from(root.querySelectorAll<HTMLElement>('[data-toolbar-slot]'));
|
|
41
|
+
|
|
42
|
+
// Кэшируем края только в полной раскладке (все items, без More) — иначе край
|
|
43
|
+
// зависел бы от текущего visibleCount/набора дивайдеров и был бы неверным для пересчёта.
|
|
44
|
+
if (slots.length === items.length) {
|
|
45
|
+
for (const slot of slots) {
|
|
46
|
+
const id = slot.dataset.toolbarItem;
|
|
47
|
+
if (id) {
|
|
48
|
+
edges.set(id, slot.getBoundingClientRect().right - rootRect.left);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Реальный «вес» More-группы (дивайдер + гэпы + кнопка): от правого края последнего
|
|
54
|
+
// видимого слота до правого края More-кнопки. Уточняется, как только More появляется.
|
|
55
|
+
const moreEl = moreButtonRef.current;
|
|
56
|
+
if (moreEl && slots.length > 0) {
|
|
57
|
+
const lastRight =
|
|
58
|
+
slots[slots.length - 1].getBoundingClientRect().right - rootRect.left - moreReserveRef.current;
|
|
59
|
+
moreReserveRef.current = moreEl.getBoundingClientRect().right - rootRect.left - lastRight;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Края ещё не собраны (нет полного рендера, напр. items только что изменились) —
|
|
63
|
+
// показываем всё и перезамеряем на следующем кадре, когда все слоты окажутся в DOM.
|
|
64
|
+
if (!items.every(id => edges.has(id))) {
|
|
65
|
+
setVisibleCount(items.length);
|
|
66
|
+
rafId = requestAnimationFrame(measure);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Всё помещается без overflow — показываем все кнопки, без More.
|
|
71
|
+
const fullRight = edges.get(items[items.length - 1]) ?? 0;
|
|
72
|
+
if (fullRight <= limit) {
|
|
73
|
+
setVisibleCount(items.length);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Иначе — максимум k кнопок, после которых ещё влезает группа «│ More».
|
|
78
|
+
const reserve = moreReserveRef.current;
|
|
79
|
+
let fits = 0;
|
|
80
|
+
for (const id of items) {
|
|
81
|
+
if ((edges.get(id) ?? 0) + reserve > limit) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fits += 1;
|
|
86
|
+
}
|
|
87
|
+
setVisibleCount(Math.max(fits, 1));
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
measure();
|
|
91
|
+
|
|
92
|
+
const observer = new ResizeObserver(measure);
|
|
93
|
+
observer.observe(root);
|
|
94
|
+
|
|
95
|
+
return () => {
|
|
96
|
+
observer.disconnect();
|
|
97
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
98
|
+
};
|
|
99
|
+
}, [items]);
|
|
100
|
+
|
|
101
|
+
return { rootRef, moreButtonRef, visibleCount };
|
|
102
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
@use '@cloud-ru/ds-figma-variables/build/scss/components/markdown.module' as md;
|
|
2
|
+
@use '@cloud-ru/ds-figma-variables/build/scss/components/field.module' as field;
|
|
3
|
+
|
|
4
|
+
// Wrapper: размещает `Buttons` + опциональный `More`-кнопку справа.
|
|
5
|
+
// Gap и дивайдеры строки кнопок живут в helperComponents/Buttons.
|
|
6
|
+
.root {
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-shrink: 0;
|
|
10
|
+
align-items: center;
|
|
11
|
+
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
|
|
14
|
+
// Padding строки — на обёртке, а не на helper
|
|
15
|
+
// `Buttons`, чтобы More-кнопка справа не прилипала к краю fieldWrapper'а.
|
|
16
|
+
padding: md.$sn-markdown-anatomy-size-buttons-paddingTop md.$sn-markdown-anatomy-size-buttons-paddingHorizontal
|
|
17
|
+
md.$sn-markdown-anatomy-size-buttons-paddingBottom;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Trigger-обёртка Tooltip — inline span; без inline-flex кнопки в строке
|
|
21
|
+
// выравниваются по baseline текста и «прыгают» по вертикали.
|
|
22
|
+
.tooltipTrigger {
|
|
23
|
+
display: inline-flex;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.tableCustomizeOption {
|
|
27
|
+
align-items: flex-start;
|
|
28
|
+
align-self: center;
|
|
29
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export const TOOLBAR_ITEM = {
|
|
2
|
+
Heading: 'heading',
|
|
3
|
+
Bold: 'bold',
|
|
4
|
+
Italic: 'italic',
|
|
5
|
+
Strikethrough: 'strikethrough',
|
|
6
|
+
Link: 'link',
|
|
7
|
+
InlineCode: 'inline-code',
|
|
8
|
+
BulletList: 'bullet-list',
|
|
9
|
+
OrderedList: 'ordered-list',
|
|
10
|
+
BlockQuote: 'block-quote',
|
|
11
|
+
BlockCode: 'block-code',
|
|
12
|
+
Table: 'table',
|
|
13
|
+
Image: 'image',
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export const HEADING_LEVEL = { H1: 1, H2: 2, H3: 3, H4: 4, H5: 5 } as const;
|
|
17
|
+
|
|
18
|
+
export const ON_CHANGE_DEBOUNCE_MS = 150;
|
|
19
|
+
|
|
20
|
+
export const TEST_IDS = {
|
|
21
|
+
viewer: 'markdown',
|
|
22
|
+
viewerCodeCopy: 'markdown__code-copy',
|
|
23
|
+
editor: 'markdown-editor',
|
|
24
|
+
editorHeader: 'markdown-editor__header',
|
|
25
|
+
editorPreviewToggle: 'markdown-editor__preview-toggle',
|
|
26
|
+
editorLabel: 'markdown-editor__label',
|
|
27
|
+
editorClear: 'markdown-editor__clear',
|
|
28
|
+
editorContent: 'markdown-editor__content',
|
|
29
|
+
// Оболочка поля (FieldShell из @cloud-ru/ds-fields) — общая для raw и preview.
|
|
30
|
+
editorField: 'markdown-editor__field',
|
|
31
|
+
// Нативная textarea в raw-режиме.
|
|
32
|
+
editorRawInput: 'markdown-editor__raw-input',
|
|
33
|
+
toolbar: 'markdown-editor__toolbar',
|
|
34
|
+
toolbarButton: 'markdown-editor__toolbar-button',
|
|
35
|
+
toolbarMore: 'markdown-editor__toolbar-more',
|
|
36
|
+
toolbarMoreList: 'markdown-editor__toolbar-more-list',
|
|
37
|
+
toolbarHeading: 'markdown-editor__toolbar-heading',
|
|
38
|
+
headingDropdown: 'markdown-editor__heading-dropdown',
|
|
39
|
+
headingOption: 'markdown-editor__heading-option',
|
|
40
|
+
tableGridPicker: 'markdown-editor__table-picker',
|
|
41
|
+
tablePickerCell: 'markdown-editor__table-cell',
|
|
42
|
+
tableCustomize: 'markdown-editor__table-customize',
|
|
43
|
+
customizeModal: 'markdown-editor__customize-table',
|
|
44
|
+
customizeColumns: 'markdown-editor__customize-table-columns',
|
|
45
|
+
customizeRows: 'markdown-editor__customize-table-rows',
|
|
46
|
+
customizeCancel: 'markdown-editor__customize-table-cancel',
|
|
47
|
+
customizeAdd: 'markdown-editor__customize-table-add',
|
|
48
|
+
linkModal: 'markdown-editor__link-modal',
|
|
49
|
+
linkModalUrl: 'markdown-editor__link-modal-url',
|
|
50
|
+
linkModalTitle: 'markdown-editor__link-modal-title',
|
|
51
|
+
linkModalCancel: 'markdown-editor__link-modal-cancel',
|
|
52
|
+
linkModalAdd: 'markdown-editor__link-modal-add',
|
|
53
|
+
imageModal: 'markdown-editor__image-modal',
|
|
54
|
+
imageModalUrl: 'markdown-editor__image-modal-url',
|
|
55
|
+
imageModalAlt: 'markdown-editor__image-modal-alt',
|
|
56
|
+
imageModalCancel: 'markdown-editor__image-modal-cancel',
|
|
57
|
+
imageModalAdd: 'markdown-editor__image-modal-add',
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
/** Стабильный data-test-id конкретной кнопки тулбара (`markdown-editor__toolbar-button--bold`). */
|
|
61
|
+
export const toolbarButtonTestId = (item: string) => `${TEST_IDS.toolbarButton}--${item}`;
|
|
62
|
+
|
|
63
|
+
/** data-test-id пункта heading-дропдауна (`…__heading-option--paragraph` | `…--2`). */
|
|
64
|
+
export const headingOptionTestId = (level: number | 'paragraph') => `${TEST_IDS.headingOption}--${level}`;
|
|
65
|
+
|
|
66
|
+
/** data-test-id ячейки table-picker'а (`…__table-cell--2x3`). */
|
|
67
|
+
export const tableCellTestId = (rows: number, cols: number) => `${TEST_IDS.tablePickerCell}--${rows}x${cols}`;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Heading1SVG,
|
|
3
|
+
Heading2SVG,
|
|
4
|
+
Heading3SVG,
|
|
5
|
+
Heading4SVG,
|
|
6
|
+
Heading5SVG,
|
|
7
|
+
HeadingSVG,
|
|
8
|
+
} from '@cloud-ru/ds-icons/interface/system';
|
|
9
|
+
import { BaseItemProps, Droplist } from '@cloud-ru/ds-list';
|
|
10
|
+
import { usePortalContext } from '@cloud-ru/ds-portal-context';
|
|
11
|
+
import { ComponentType, useState } from 'react';
|
|
12
|
+
|
|
13
|
+
import { headingOptionTestId, TEST_IDS, TOOLBAR_ITEM } from '../../constants';
|
|
14
|
+
import { markdownLocale } from '../../locale';
|
|
15
|
+
import { ToolbarApi } from '../../toolbarApi';
|
|
16
|
+
import { HeadingLevel } from '../../types';
|
|
17
|
+
import { PrivateButton } from '../PrivateButton';
|
|
18
|
+
import styles from './styles.module.scss';
|
|
19
|
+
|
|
20
|
+
// Кнопка-триггер выбора уровня заголовка: иконка текущего уровня. Открывает Droplist со списком уровней.
|
|
21
|
+
|
|
22
|
+
const HEADING_LEVELS: HeadingLevel[] = [1, 2, 3, 4, 5];
|
|
23
|
+
|
|
24
|
+
const ICON_FOR: Record<HeadingLevel, ComponentType> = {
|
|
25
|
+
1: Heading1SVG,
|
|
26
|
+
2: Heading2SVG,
|
|
27
|
+
3: Heading3SVG,
|
|
28
|
+
4: Heading4SVG,
|
|
29
|
+
5: Heading5SVG,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type ButtonHeadingProps = {
|
|
33
|
+
/** Бэкенд команд тулбара (WYSIWYG либо raw markdown). */
|
|
34
|
+
api: ToolbarApi;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function ButtonHeading({ api }: ButtonHeadingProps) {
|
|
38
|
+
const portalContext = usePortalContext();
|
|
39
|
+
const { t } = markdownLocale.useTranslations();
|
|
40
|
+
const [open, setOpen] = useState(false);
|
|
41
|
+
const activeLevel = HEADING_LEVELS.find(lvl => api.isHeadingActive(lvl));
|
|
42
|
+
const TriggerIcon = activeLevel != null ? ICON_FOR[activeLevel] : HeadingSVG;
|
|
43
|
+
|
|
44
|
+
const selectLevel = (lvl: HeadingLevel | null) => {
|
|
45
|
+
if (lvl == null) {
|
|
46
|
+
api.setParagraph();
|
|
47
|
+
} else {
|
|
48
|
+
api.toggleHeading(lvl);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setOpen(false);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const items: BaseItemProps[] = [
|
|
55
|
+
{
|
|
56
|
+
id: 'paragraph',
|
|
57
|
+
content: { label: 'Paragraph' },
|
|
58
|
+
checked: activeLevel == null,
|
|
59
|
+
onClick: () => selectLevel(null),
|
|
60
|
+
'data-test-id': headingOptionTestId('paragraph'),
|
|
61
|
+
},
|
|
62
|
+
...HEADING_LEVELS.map((lvl): BaseItemProps => {
|
|
63
|
+
const Icon = ICON_FOR[lvl];
|
|
64
|
+
return {
|
|
65
|
+
id: `heading-${lvl}`,
|
|
66
|
+
beforeContent: <Icon />,
|
|
67
|
+
content: { label: `Heading ${lvl}` },
|
|
68
|
+
checked: activeLevel === lvl,
|
|
69
|
+
onClick: () => selectLevel(lvl),
|
|
70
|
+
'data-test-id': headingOptionTestId(lvl),
|
|
71
|
+
};
|
|
72
|
+
}),
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<Droplist
|
|
77
|
+
size='m'
|
|
78
|
+
selection={{ mode: 'single', value: activeLevel }}
|
|
79
|
+
container={portalContext}
|
|
80
|
+
open={open}
|
|
81
|
+
onOpenChange={setOpen}
|
|
82
|
+
placement='bottom-start'
|
|
83
|
+
items={items}
|
|
84
|
+
triggerClassName={styles.trigger}
|
|
85
|
+
data-test-id={TEST_IDS.headingDropdown}
|
|
86
|
+
>
|
|
87
|
+
<PrivateButton
|
|
88
|
+
icon={<TriggerIcon />}
|
|
89
|
+
tip={{ title: t('toolbar.heading') }}
|
|
90
|
+
withChevron
|
|
91
|
+
checked={activeLevel != null}
|
|
92
|
+
aria-label={t('toolbar.heading')}
|
|
93
|
+
data-toolbar-slot
|
|
94
|
+
data-toolbar-item={TOOLBAR_ITEM.Heading}
|
|
95
|
+
data-test-id={TEST_IDS.toolbarHeading}
|
|
96
|
+
aria-expanded={open}
|
|
97
|
+
/>
|
|
98
|
+
</Droplist>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ButtonHeading';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Divider } from '@cloud-ru/ds-divider';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import { TEST_IDS } from '../../constants';
|
|
5
|
+
import styles from './styles.module.scss';
|
|
6
|
+
|
|
7
|
+
// Toolbar-строка: группы кнопок с дивайдерами.
|
|
8
|
+
export type ButtonsProps = {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function Buttons({ children }: ButtonsProps) {
|
|
13
|
+
return (
|
|
14
|
+
<div className={styles.root} data-test-id={TEST_IDS.toolbar} role='toolbar'>
|
|
15
|
+
{children}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ButtonsGroupProps = {
|
|
21
|
+
children: ReactNode;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function ButtonsGroup({ children }: ButtonsGroupProps) {
|
|
25
|
+
return <div className={styles.group}>{children}</div>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function ButtonsDivider() {
|
|
29
|
+
return <Divider orientation='vertical' className={styles.divider} />;
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Buttons';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
@use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
|
|
2
|
+
@use '@cloud-ru/ds-figma-variables/build/scss/components/markdown.module' as md;
|
|
3
|
+
@use '@cloud-ru/ds-figma-variables/build/scss/components/divider.module' as divider;
|
|
4
|
+
|
|
5
|
+
// Padding всей строки живёт на Toolbar-обёртке,
|
|
6
|
+
// чтобы покрывать и More-кнопку, которая рендерится рядом с этой группой.
|
|
7
|
+
.root {
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
display: flex;
|
|
10
|
+
flex: 0 0 auto;
|
|
11
|
+
gap: md.$sn-markdown-anatomy-size-buttons-gap;
|
|
12
|
+
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.group {
|
|
17
|
+
display: flex;
|
|
18
|
+
gap: base.$sn-primitive-dimension-4;
|
|
19
|
+
align-items: center;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.divider {
|
|
23
|
+
&[data-orientation='vertical'] {
|
|
24
|
+
height: auto;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ButtonGroup } from '@cloud-ru/ds-button';
|
|
2
|
+
import { FieldStepper } from '@cloud-ru/ds-fields';
|
|
3
|
+
import { Modal } from '@cloud-ru/ds-modal';
|
|
4
|
+
import { usePortalContext } from '@cloud-ru/ds-portal-context';
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { TEST_IDS } from '../../constants';
|
|
8
|
+
import { markdownLocale } from '../../locale';
|
|
9
|
+
import styles from './styles.module.scss';
|
|
10
|
+
|
|
11
|
+
type CustomizeTableModalProps = {
|
|
12
|
+
open: boolean;
|
|
13
|
+
onClose(): void;
|
|
14
|
+
/** Вставить таблицу заданного размера. */
|
|
15
|
+
onSubmit(rows: number, cols: number): void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const DEFAULT_SIZE = 1;
|
|
19
|
+
|
|
20
|
+
export function CustomizeTableModal({ open, onClose, onSubmit }: CustomizeTableModalProps) {
|
|
21
|
+
const { t } = markdownLocale.useTranslations();
|
|
22
|
+
const portalContext = usePortalContext();
|
|
23
|
+
const [columns, setColumns] = useState(DEFAULT_SIZE);
|
|
24
|
+
const [rows, setRows] = useState(DEFAULT_SIZE);
|
|
25
|
+
|
|
26
|
+
// Сбрасываем значения при каждом открытии, чтобы модалка не помнила прошлый ввод.
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (open) {
|
|
29
|
+
setColumns(DEFAULT_SIZE);
|
|
30
|
+
setRows(DEFAULT_SIZE);
|
|
31
|
+
}
|
|
32
|
+
}, [open]);
|
|
33
|
+
|
|
34
|
+
const handleAdd = () => onSubmit(rows, columns);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
// TODO: заменить на адаптив
|
|
38
|
+
<Modal
|
|
39
|
+
open={open}
|
|
40
|
+
onClose={onClose}
|
|
41
|
+
container={portalContext.current ?? undefined}
|
|
42
|
+
title={t('table.customize')}
|
|
43
|
+
data-test-id={TEST_IDS.customizeModal}
|
|
44
|
+
content={
|
|
45
|
+
<div className={styles.fields}>
|
|
46
|
+
<FieldStepper
|
|
47
|
+
size='m'
|
|
48
|
+
className={styles.field}
|
|
49
|
+
label={t('table.columns')}
|
|
50
|
+
min={DEFAULT_SIZE}
|
|
51
|
+
value={columns}
|
|
52
|
+
onChange={setColumns}
|
|
53
|
+
data-test-id={TEST_IDS.customizeColumns}
|
|
54
|
+
/>
|
|
55
|
+
<FieldStepper
|
|
56
|
+
size='m'
|
|
57
|
+
className={styles.field}
|
|
58
|
+
label={t('table.rows')}
|
|
59
|
+
min={DEFAULT_SIZE}
|
|
60
|
+
value={rows}
|
|
61
|
+
onChange={setRows}
|
|
62
|
+
data-test-id={TEST_IDS.customizeRows}
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
}
|
|
66
|
+
footer={
|
|
67
|
+
<ButtonGroup
|
|
68
|
+
size='m'
|
|
69
|
+
primaryAction={{ label: t('add'), onClick: handleAdd, 'data-test-id': TEST_IDS.customizeAdd }}
|
|
70
|
+
secondaryAction={{
|
|
71
|
+
appearance: 'neutral',
|
|
72
|
+
view: 'outline',
|
|
73
|
+
label: t('cancel'),
|
|
74
|
+
onClick: onClose,
|
|
75
|
+
'data-test-id': TEST_IDS.customizeCancel,
|
|
76
|
+
}}
|
|
77
|
+
className={styles.buttons}
|
|
78
|
+
/>
|
|
79
|
+
}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ButtonGroup } from '@cloud-ru/ds-button';
|
|
2
|
+
import { FieldCombo } from '@cloud-ru/ds-fields';
|
|
3
|
+
import { Modal } from '@cloud-ru/ds-modal';
|
|
4
|
+
import { usePortalContext } from '@cloud-ru/ds-portal-context';
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { TEST_IDS } from '../../constants';
|
|
8
|
+
import { markdownLocale } from '../../locale';
|
|
9
|
+
import styles from './styles.module.scss';
|
|
10
|
+
|
|
11
|
+
// Модалка вставки изображения (вместо window.prompt): поля URL и Alt + кнопки Отмена/Добавить.
|
|
12
|
+
type ImageModalProps = {
|
|
13
|
+
open: boolean;
|
|
14
|
+
onClose(): void;
|
|
15
|
+
onSubmit(url: string, alt: string): void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function ImageModal({ open, onClose, onSubmit }: ImageModalProps) {
|
|
19
|
+
const { t } = markdownLocale.useTranslations();
|
|
20
|
+
const portalContext = usePortalContext();
|
|
21
|
+
const [url, setUrl] = useState('');
|
|
22
|
+
const [alt, setAlt] = useState('');
|
|
23
|
+
|
|
24
|
+
// Сбрасываем поля при каждом открытии.
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (open) {
|
|
27
|
+
setUrl('');
|
|
28
|
+
setAlt('');
|
|
29
|
+
}
|
|
30
|
+
}, [open]);
|
|
31
|
+
|
|
32
|
+
const trimmedUrl = url.trim();
|
|
33
|
+
const handleAdd = () => onSubmit(trimmedUrl, alt.trim() || 'image');
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
// TODO: заменить на адаптив
|
|
37
|
+
<Modal
|
|
38
|
+
open={open}
|
|
39
|
+
onClose={onClose}
|
|
40
|
+
container={portalContext.current ?? undefined}
|
|
41
|
+
title={t('image.title')}
|
|
42
|
+
data-test-id={TEST_IDS.imageModal}
|
|
43
|
+
content={
|
|
44
|
+
<div className={styles.fields} data-column={true}>
|
|
45
|
+
<FieldCombo
|
|
46
|
+
size='m'
|
|
47
|
+
label={t('image.url')}
|
|
48
|
+
placeholder={t('image.placeholder')}
|
|
49
|
+
value={url}
|
|
50
|
+
onChange={setUrl}
|
|
51
|
+
autoFocus
|
|
52
|
+
data-test-id={TEST_IDS.imageModalUrl}
|
|
53
|
+
/>
|
|
54
|
+
<FieldCombo
|
|
55
|
+
size='m'
|
|
56
|
+
label={t('image.alt')}
|
|
57
|
+
value={alt}
|
|
58
|
+
onChange={setAlt}
|
|
59
|
+
data-test-id={TEST_IDS.imageModalAlt}
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
}
|
|
63
|
+
footer={
|
|
64
|
+
<ButtonGroup
|
|
65
|
+
size='m'
|
|
66
|
+
primaryAction={{
|
|
67
|
+
label: t('add'),
|
|
68
|
+
onClick: handleAdd,
|
|
69
|
+
disabled: trimmedUrl === '',
|
|
70
|
+
'data-test-id': TEST_IDS.imageModalAdd,
|
|
71
|
+
}}
|
|
72
|
+
secondaryAction={{
|
|
73
|
+
appearance: 'neutral',
|
|
74
|
+
view: 'outline',
|
|
75
|
+
label: t('cancel'),
|
|
76
|
+
onClick: onClose,
|
|
77
|
+
'data-test-id': TEST_IDS.imageModalCancel,
|
|
78
|
+
}}
|
|
79
|
+
className={styles.buttons}
|
|
80
|
+
/>
|
|
81
|
+
}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ButtonGroup } from '@cloud-ru/ds-button';
|
|
2
|
+
import { FieldCombo } from '@cloud-ru/ds-fields';
|
|
3
|
+
import { Modal } from '@cloud-ru/ds-modal';
|
|
4
|
+
import { usePortalContext } from '@cloud-ru/ds-portal-context';
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { TEST_IDS } from '../../constants';
|
|
8
|
+
import { markdownLocale } from '../../locale';
|
|
9
|
+
import styles from './styles.module.scss';
|
|
10
|
+
|
|
11
|
+
export type LinkProps = {
|
|
12
|
+
href?: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Модалка добавления ссылки (вместо window.prompt): поле URL + кнопки Отмена/Добавить.
|
|
17
|
+
type LinkModalProps = {
|
|
18
|
+
open: boolean;
|
|
19
|
+
/** Текущий href (для редактирования существующей ссылки). */
|
|
20
|
+
initial?: LinkProps;
|
|
21
|
+
onClose(): void;
|
|
22
|
+
onSubmit(props: LinkProps): void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function LinkModal({ open, initial, onClose, onSubmit }: LinkModalProps) {
|
|
26
|
+
const { t } = markdownLocale.useTranslations();
|
|
27
|
+
const portalContext = usePortalContext();
|
|
28
|
+
const [linkProps, setLinkProps] = useState<LinkProps | undefined>(initial);
|
|
29
|
+
|
|
30
|
+
// Префилл значением при каждом открытии (текущий href либо пусто).
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (open) {
|
|
33
|
+
setLinkProps({ href: initial?.href, title: initial?.title });
|
|
34
|
+
}
|
|
35
|
+
}, [open, initial?.href, initial?.title]);
|
|
36
|
+
|
|
37
|
+
const trimmedUrl = linkProps?.href?.trim() ?? '';
|
|
38
|
+
const handleAdd = () => onSubmit({ href: trimmedUrl, title: linkProps?.title });
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
// TODO: заменить на адаптив
|
|
42
|
+
<Modal
|
|
43
|
+
open={open}
|
|
44
|
+
onClose={onClose}
|
|
45
|
+
container={portalContext.current ?? undefined}
|
|
46
|
+
title={t('link.title')}
|
|
47
|
+
data-test-id={TEST_IDS.linkModal}
|
|
48
|
+
content={
|
|
49
|
+
<div className={styles.fields} data-column={true}>
|
|
50
|
+
<FieldCombo
|
|
51
|
+
size='m'
|
|
52
|
+
label={t('link.titleField.label')}
|
|
53
|
+
placeholder={t('link.titleField.placeholder')}
|
|
54
|
+
value={linkProps?.title ?? ''}
|
|
55
|
+
onChange={title => setLinkProps(prev => ({ ...prev, title }))}
|
|
56
|
+
autoFocus={!initial?.title}
|
|
57
|
+
data-test-id={TEST_IDS.linkModalTitle}
|
|
58
|
+
/>
|
|
59
|
+
|
|
60
|
+
<FieldCombo
|
|
61
|
+
size='m'
|
|
62
|
+
label={t('link.urlField.label')}
|
|
63
|
+
placeholder={t('link.urlField.placeholder')}
|
|
64
|
+
value={linkProps?.href ?? ''}
|
|
65
|
+
autoFocus={Boolean(initial?.title)}
|
|
66
|
+
onChange={href => setLinkProps(prev => ({ ...prev, href }))}
|
|
67
|
+
data-test-id={TEST_IDS.linkModalUrl}
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
}
|
|
71
|
+
footer={
|
|
72
|
+
<ButtonGroup
|
|
73
|
+
size='m'
|
|
74
|
+
primaryAction={{
|
|
75
|
+
label: t('add'),
|
|
76
|
+
onClick: handleAdd,
|
|
77
|
+
disabled: trimmedUrl === '',
|
|
78
|
+
'data-test-id': TEST_IDS.linkModalAdd,
|
|
79
|
+
}}
|
|
80
|
+
secondaryAction={{
|
|
81
|
+
appearance: 'neutral',
|
|
82
|
+
view: 'outline',
|
|
83
|
+
label: t('cancel'),
|
|
84
|
+
onClick: onClose,
|
|
85
|
+
'data-test-id': TEST_IDS.linkModalCancel,
|
|
86
|
+
}}
|
|
87
|
+
className={styles.buttons}
|
|
88
|
+
/>
|
|
89
|
+
}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
@use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
|
|
2
|
+
|
|
3
|
+
// Два FieldStepper (столбцы/строки) в ряд, gap 16.
|
|
4
|
+
.fields {
|
|
5
|
+
display: flex;
|
|
6
|
+
gap: base.$sn-primitive-dimension-16;
|
|
7
|
+
|
|
8
|
+
&[data-column] {
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.field {
|
|
14
|
+
flex: 1 1 0;
|
|
15
|
+
min-width: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.buttons {
|
|
19
|
+
justify-content: flex-end;
|
|
20
|
+
width: 100%;
|
|
21
|
+
}
|