@crystallize/design-system 1.17.0 → 1.17.1
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 +6 -0
- package/dist/index.css +5 -7
- package/dist/index.d.ts +7 -6
- package/dist/index.js +365 -410
- package/dist/index.mjs +312 -357
- package/package.json +1 -1
- package/src/action-menu/action-item.tsx +4 -4
- package/src/rich-text-editor/i18n/translations/en.ts +1 -0
- package/src/rich-text-editor/i18n/types.ts +1 -0
- package/src/rich-text-editor/plugins/ActionsPlugin/index.tsx +34 -38
- package/src/rich-text-editor/plugins/FloatingLinkEditorPlugin/index.tsx +3 -1
- package/src/rich-text-editor/plugins/FloatingTextFormatToolbarPlugin/index.tsx +16 -16
- package/src/rich-text-editor/plugins/ToolbarPlugin/index.css +9 -0
- package/src/rich-text-editor/plugins/ToolbarPlugin/index.tsx +11 -11
- package/src/rich-text-editor/rich-text-editor.css +0 -1
- package/src/rich-text-editor/rich-text-editor.tsx +5 -14
- package/src/rich-text-editor/types/crystallize-rich-text-types/index.ts +1 -1
- package/src/rich-text-editor/plugins/ActionsPlugin/index.css +0 -3
- package/src/rich-text-editor/plugins/DimensionsDetectorPlugin/index.tsx +0 -49
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type { HTMLAttributes } from 'react';
|
|
2
1
|
import { cx } from 'class-variance-authority';
|
|
2
|
+
import type { DropdownMenuItemProps } from '@radix-ui/react-dropdown-menu';
|
|
3
3
|
|
|
4
4
|
import { DropdownMenu } from '../dropdown-menu';
|
|
5
5
|
|
|
6
|
-
type ItemProps =
|
|
6
|
+
type ItemProps = DropdownMenuItemProps & {
|
|
7
7
|
onSelect?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
export function Item({ children, className, onSelect }: ItemProps) {
|
|
10
|
+
export function Item({ children, className, onSelect, ...delegated }: ItemProps) {
|
|
11
11
|
return (
|
|
12
|
-
<DropdownMenu.Item onClick={onSelect} className={cx(className, ['c-action-menu-item'])}>
|
|
12
|
+
<DropdownMenu.Item {...delegated} onClick={onSelect} className={cx(className, ['c-action-menu-item'])}>
|
|
13
13
|
{children}
|
|
14
14
|
</DropdownMenu.Item>
|
|
15
15
|
);
|
|
@@ -18,6 +18,7 @@ const translations: I18N = {
|
|
|
18
18
|
actionFormatWithSubscriptTitle: 'Subscript',
|
|
19
19
|
actionFormatWithSuperscriptLabel: 'Format text as superscript',
|
|
20
20
|
actionFormatWithSuperscriptTitle: 'Superscript',
|
|
21
|
+
actionFormatClear: 'Clear formatting',
|
|
21
22
|
actionInsertCodeBlock: 'Insert code block',
|
|
22
23
|
actionInsertlink: 'Insert link',
|
|
23
24
|
actionCopyJSON: 'Copy JSON',
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
*
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { useRef } from 'react';
|
|
11
10
|
import { CLEAR_EDITOR_COMMAND, LexicalEditor } from 'lexical';
|
|
12
11
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
13
12
|
|
|
@@ -35,44 +34,41 @@ export default function ActionsPlugin({
|
|
|
35
34
|
}): JSX.Element {
|
|
36
35
|
const [editor] = useLexicalComposerContext();
|
|
37
36
|
const tr = useTr();
|
|
38
|
-
const actionMenuAnchorRef = useRef<HTMLDivElement>(null);
|
|
39
37
|
|
|
40
38
|
return (
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
</ActionMenu>
|
|
76
|
-
</div>
|
|
39
|
+
<ActionMenu>
|
|
40
|
+
{!prepend
|
|
41
|
+
? null
|
|
42
|
+
: prepend.map(actionItem => (
|
|
43
|
+
<ActionMenu.Item
|
|
44
|
+
key={actionItem.title}
|
|
45
|
+
onSelect={actionItem.action}
|
|
46
|
+
className={actionItem.type === 'danger' ? 'danger' : ''}
|
|
47
|
+
>
|
|
48
|
+
{actionItem.title}
|
|
49
|
+
</ActionMenu.Item>
|
|
50
|
+
))}
|
|
51
|
+
<ActionMenu.Item onSelect={() => copyJson(editor)}>{tr('actionCopyJSON')}</ActionMenu.Item>
|
|
52
|
+
<ActionMenu.Item
|
|
53
|
+
className="danger"
|
|
54
|
+
onSelect={() => {
|
|
55
|
+
editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);
|
|
56
|
+
editor.focus();
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
{tr('actionClear')}
|
|
60
|
+
</ActionMenu.Item>
|
|
61
|
+
{!append
|
|
62
|
+
? null
|
|
63
|
+
: append.map(actionItem => (
|
|
64
|
+
<ActionMenu.Item
|
|
65
|
+
key={actionItem.title}
|
|
66
|
+
onSelect={actionItem.action}
|
|
67
|
+
className={actionItem.type === 'danger' ? 'danger' : ''}
|
|
68
|
+
>
|
|
69
|
+
{actionItem.title}
|
|
70
|
+
</ActionMenu.Item>
|
|
71
|
+
))}
|
|
72
|
+
</ActionMenu>
|
|
77
73
|
);
|
|
78
74
|
}
|
|
@@ -185,7 +185,7 @@ function FloatingLinkEditor({
|
|
|
185
185
|
}, [isEditMode]);
|
|
186
186
|
|
|
187
187
|
return (
|
|
188
|
-
<div ref={editorRef} className="c-rte-link-editor">
|
|
188
|
+
<div ref={editorRef} className="c-rte-link-editor" data-testid="rich-text-link-editor">
|
|
189
189
|
{isEditMode ? (
|
|
190
190
|
<div>
|
|
191
191
|
<div className="c-rte-link-editor-input-group">
|
|
@@ -215,6 +215,7 @@ function FloatingLinkEditor({
|
|
|
215
215
|
</div>
|
|
216
216
|
<div className="c-rte-link-editor-button-wrap">
|
|
217
217
|
<Button
|
|
218
|
+
data-testid="link-editor-save-button"
|
|
218
219
|
onClick={() => {
|
|
219
220
|
if (lastSelection !== null) {
|
|
220
221
|
if (linkUrl !== '') {
|
|
@@ -253,6 +254,7 @@ function FloatingLinkEditor({
|
|
|
253
254
|
onMouseDown={event => event.preventDefault()}
|
|
254
255
|
onClick={() => setEditMode(true)}
|
|
255
256
|
aria-label={tr('linkEditorEdit')}
|
|
257
|
+
data-testid="link-editor-edit"
|
|
256
258
|
>
|
|
257
259
|
<Icon.Edit />
|
|
258
260
|
</IconButton>
|
|
@@ -179,6 +179,22 @@ function TextFormatFloatingToolbar({
|
|
|
179
179
|
}`}
|
|
180
180
|
/>
|
|
181
181
|
</IconButton>
|
|
182
|
+
<IconButton
|
|
183
|
+
style={{ padding: 0, overflow: 'hidden' }}
|
|
184
|
+
onClick={() => {
|
|
185
|
+
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'code');
|
|
186
|
+
}}
|
|
187
|
+
aria-label={tr('actionInsertCodeBlock')}
|
|
188
|
+
>
|
|
189
|
+
<i
|
|
190
|
+
className={`c-rte-icon-code c-rte-floating-text-format-tb-plugin__format-icon ${isCode ? 'selected' : ''}`}
|
|
191
|
+
/>
|
|
192
|
+
</IconButton>
|
|
193
|
+
<IconButton style={{ padding: 0, overflow: 'hidden' }} onClick={insertLink} aria-label={tr('actionInsertlink')}>
|
|
194
|
+
<i
|
|
195
|
+
className={`c-rte-icon-link c-rte-floating-text-format-tb-plugin__format-icon ${isLink ? 'selected' : ''}`}
|
|
196
|
+
/>
|
|
197
|
+
</IconButton>
|
|
182
198
|
<IconButton
|
|
183
199
|
style={{ padding: 0, overflow: 'hidden' }}
|
|
184
200
|
onClick={() => {
|
|
@@ -221,22 +237,6 @@ function TextFormatFloatingToolbar({
|
|
|
221
237
|
}`}
|
|
222
238
|
/>
|
|
223
239
|
</IconButton>
|
|
224
|
-
<IconButton
|
|
225
|
-
style={{ padding: 0, overflow: 'hidden' }}
|
|
226
|
-
onClick={() => {
|
|
227
|
-
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'code');
|
|
228
|
-
}}
|
|
229
|
-
aria-label={tr('actionInsertCodeBlock')}
|
|
230
|
-
>
|
|
231
|
-
<i
|
|
232
|
-
className={`c-rte-icon-code c-rte-floating-text-format-tb-plugin__format-icon ${isCode ? 'selected' : ''}`}
|
|
233
|
-
/>
|
|
234
|
-
</IconButton>
|
|
235
|
-
<IconButton style={{ padding: 0, overflow: 'hidden' }} onClick={insertLink} aria-label={tr('actionInsertlink')}>
|
|
236
|
-
<i
|
|
237
|
-
className={`c-rte-icon-link c-rte-floating-text-format-tb-plugin__format-icon ${isLink ? 'selected' : ''}`}
|
|
238
|
-
/>
|
|
239
|
-
</IconButton>
|
|
240
240
|
</div>
|
|
241
241
|
);
|
|
242
242
|
}
|
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
padding-left: var(--c-rte-toolbar-pl, 0.5rem);
|
|
7
7
|
padding-right: var(--c-rte-toolbar-pr, 0.5rem);
|
|
8
8
|
|
|
9
|
+
container-type: inline-size;
|
|
10
|
+
container-name: rich-text-toolbar;
|
|
11
|
+
|
|
12
|
+
@container rich-text-toolbar (max-width: 600px) {
|
|
13
|
+
.c-rte-toolbar__icon-btn {
|
|
14
|
+
display: none;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
&__inner {
|
|
10
19
|
@apply flex;
|
|
11
20
|
}
|
|
@@ -456,6 +456,7 @@ export default function ToolbarPlugin({
|
|
|
456
456
|
type="button"
|
|
457
457
|
title={tr(IS_APPLE ? 'actionUndoTitleApple' : 'actionUndoTitle')}
|
|
458
458
|
aria-label={tr('actionUndoLabel')}
|
|
459
|
+
data-testid="rich-text-undo-button"
|
|
459
460
|
>
|
|
460
461
|
<i
|
|
461
462
|
className={`c-rte-icon-undo c-rte-toolbar__icon ${!canUndo ? 'disabled' : ''}
|
|
@@ -563,6 +564,7 @@ export default function ToolbarPlugin({
|
|
|
563
564
|
type="button"
|
|
564
565
|
title={tr('actionInsertCodeBlock')}
|
|
565
566
|
aria-label={tr('actionInsertCodeBlock')}
|
|
567
|
+
data-testid="toggle-format-code"
|
|
566
568
|
>
|
|
567
569
|
<i className={`c-rte-toolbar__icon-btn__icon c-rte-icon-code`} />
|
|
568
570
|
</IconButton>
|
|
@@ -582,9 +584,8 @@ export default function ToolbarPlugin({
|
|
|
582
584
|
content={
|
|
583
585
|
<>
|
|
584
586
|
<DropdownMenu.Item
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}}
|
|
587
|
+
disabled={!isEditable}
|
|
588
|
+
onClick={() => activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough')}
|
|
588
589
|
title={tr('actionFormatWithStrikethroughTitle')}
|
|
589
590
|
aria-label={tr('actionFormatWithStrikethroughLabel')}
|
|
590
591
|
>
|
|
@@ -594,13 +595,12 @@ export default function ToolbarPlugin({
|
|
|
594
595
|
}`}
|
|
595
596
|
/>
|
|
596
597
|
<span className={`c-rte-toolbar__dd-item__text ${isStrikethrough ? 'selected' : ''}`}>
|
|
597
|
-
{tr('
|
|
598
|
+
{tr('actionFormatWithStrikethroughTitle')}
|
|
598
599
|
</span>
|
|
599
600
|
</DropdownMenu.Item>
|
|
600
601
|
<DropdownMenu.Item
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
}}
|
|
602
|
+
disabled={!isEditable}
|
|
603
|
+
onClick={() => activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'subscript')}
|
|
604
604
|
title={tr('actionFormatWithSubscriptTitle')}
|
|
605
605
|
aria-label={tr('actionFormatWithSubscriptLabel')}
|
|
606
606
|
>
|
|
@@ -612,9 +612,8 @@ export default function ToolbarPlugin({
|
|
|
612
612
|
</span>
|
|
613
613
|
</DropdownMenu.Item>
|
|
614
614
|
<DropdownMenu.Item
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
}}
|
|
615
|
+
disabled={!isEditable}
|
|
616
|
+
onClick={() => activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'superscript')}
|
|
618
617
|
title={tr('actionFormatWithSuperscriptTitle')}
|
|
619
618
|
aria-label={tr('actionFormatWithSuperscriptLabel')}
|
|
620
619
|
>
|
|
@@ -628,13 +627,14 @@ export default function ToolbarPlugin({
|
|
|
628
627
|
</span>
|
|
629
628
|
</DropdownMenu.Item>
|
|
630
629
|
<DropdownMenu.Item
|
|
630
|
+
disabled={!isEditable}
|
|
631
631
|
onClick={clearFormatting}
|
|
632
632
|
className="item"
|
|
633
633
|
title={tr('actionClearTextFormatting')}
|
|
634
634
|
aria-label={tr('actionClearTextFormatting')}
|
|
635
635
|
>
|
|
636
636
|
<i className="c-rte-icon-clear c-rte-toolbar__dd-item__icon" />
|
|
637
|
-
<span className="c-rte-toolbar__dd-item__text--clear">
|
|
637
|
+
<span className="c-rte-toolbar__dd-item__text--clear">{tr('actionFormatClear')}</span>
|
|
638
638
|
</DropdownMenu.Item>
|
|
639
639
|
</>
|
|
640
640
|
}
|
|
@@ -26,7 +26,6 @@ import { BaseNodes } from './nodes/BaseNodes';
|
|
|
26
26
|
import AutoLinkPlugin from './plugins/AutoLinkPlugin';
|
|
27
27
|
import CodeActionMenuPlugin from './plugins/CodeActionMenuPlugin';
|
|
28
28
|
import CodeHighlightPlugin from './plugins/CodeHighlightPlugin';
|
|
29
|
-
import { DimensionDetectorPlugin } from './plugins/DimensionsDetectorPlugin';
|
|
30
29
|
import FloatingLinkEditorPlugin from './plugins/FloatingLinkEditorPlugin';
|
|
31
30
|
import FloatingTextFormatToolbarPlugin from './plugins/FloatingTextFormatToolbarPlugin';
|
|
32
31
|
import LinkPlugin from './plugins/LinkPlugin';
|
|
@@ -71,7 +70,7 @@ export function RichTextEditor({
|
|
|
71
70
|
labelTranslations,
|
|
72
71
|
...rest
|
|
73
72
|
}: TRichTextBase & {
|
|
74
|
-
initialData?: CrystallizeRichText;
|
|
73
|
+
initialData?: CrystallizeRichText | null;
|
|
75
74
|
}) {
|
|
76
75
|
return (
|
|
77
76
|
<LexicalComposer
|
|
@@ -79,15 +78,11 @@ export function RichTextEditor({
|
|
|
79
78
|
editable: !rest.disabled,
|
|
80
79
|
namespace: 'crystallize-rich-text-editor',
|
|
81
80
|
nodes: [...BaseNodes],
|
|
81
|
+
theme: CrystallizeRTEditorTheme,
|
|
82
|
+
editorState: initialData ? composeInitialState({ richText: initialData }) : undefined,
|
|
82
83
|
onError: (error: Error) => {
|
|
83
84
|
throw error;
|
|
84
85
|
},
|
|
85
|
-
theme: CrystallizeRTEditorTheme,
|
|
86
|
-
editorState: initialData
|
|
87
|
-
? composeInitialState({
|
|
88
|
-
richText: initialData,
|
|
89
|
-
})
|
|
90
|
-
: undefined,
|
|
91
86
|
}}
|
|
92
87
|
>
|
|
93
88
|
<I18nProvider language={language} labelTranslations={labelTranslations}>
|
|
@@ -119,7 +114,6 @@ function RichTextEditorWithoutContext({
|
|
|
119
114
|
|
|
120
115
|
const [editor] = useLexicalComposerContext();
|
|
121
116
|
const [floatingAnchorElem, setFloatingAnchorElem] = useState<HTMLDivElement | null>(null);
|
|
122
|
-
const [isSmallWidthViewport, setIsSmallWidthViewport] = useState<boolean>(false);
|
|
123
117
|
const firstOnChangeTriggeredRef = useRef(!autoFocus);
|
|
124
118
|
|
|
125
119
|
const onRef = (_floatingAnchorElem: HTMLDivElement) => {
|
|
@@ -146,10 +140,7 @@ function RichTextEditorWithoutContext({
|
|
|
146
140
|
return (
|
|
147
141
|
<>
|
|
148
142
|
<OnChangePlugin onChange={onLocalChange} ignoreSelectionChange />
|
|
149
|
-
<
|
|
150
|
-
{isSmallWidthViewport ? null : (
|
|
151
|
-
<ToolbarPlugin actionsMenuPrepend={actionsMenuPrepend} actionsMenuAppend={actionsMenuAppend} />
|
|
152
|
-
)}
|
|
143
|
+
<ToolbarPlugin actionsMenuPrepend={actionsMenuPrepend} actionsMenuAppend={actionsMenuAppend} />
|
|
153
144
|
{slotPreContent}
|
|
154
145
|
<div className={`c-rte-editor-container ${disabled ? 'disabled' : ''}`}>
|
|
155
146
|
{maxLength != null ? <MaxLengthPlugin maxLength={maxLength} /> : null}
|
|
@@ -178,7 +169,7 @@ function RichTextEditorWithoutContext({
|
|
|
178
169
|
<HorizontalRulePlugin />
|
|
179
170
|
<TabFocusPlugin />
|
|
180
171
|
<TabIndentationPlugin />
|
|
181
|
-
{floatingAnchorElem &&
|
|
172
|
+
{floatingAnchorElem && (
|
|
182
173
|
<>
|
|
183
174
|
{/* <DraggableBlockPlugin anchorElem={floatingAnchorElem} /> */}
|
|
184
175
|
<CodeActionMenuPlugin anchorElem={floatingAnchorElem} />
|
|
@@ -6,7 +6,7 @@ import type { CrystallizeRichTextTableNodes } from './table';
|
|
|
6
6
|
export type { CrystallizeRichTextCodeNodes } from './code';
|
|
7
7
|
export type { CrystallizeRichTextHeadingNodes } from './headings';
|
|
8
8
|
|
|
9
|
-
export type CrystallizeRichText = CrystallizeRichTextNode
|
|
9
|
+
export type CrystallizeRichText = CrystallizeRichTextNode[];
|
|
10
10
|
|
|
11
11
|
type CrystallizeRichTextNodeBase = {
|
|
12
12
|
textContent?: string;
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
type Dimensions = {
|
|
4
|
-
isSmallWidth: boolean;
|
|
5
|
-
width: number;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
type DimensionDetectorPluginProps = {
|
|
9
|
-
onChange: (p: Dimensions) => void;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export function DimensionDetectorPlugin({ onChange }: DimensionDetectorPluginProps) {
|
|
13
|
-
const [dimensions, setDimensions] = useState<Dimensions>();
|
|
14
|
-
const ref = useRef<HTMLDivElement | null>(null);
|
|
15
|
-
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
if (ref.current) {
|
|
18
|
-
const el = ref.current;
|
|
19
|
-
const resizeObserver = new ResizeObserver(entries => {
|
|
20
|
-
const [first] = entries;
|
|
21
|
-
if (first) {
|
|
22
|
-
const [contentBox] = first.contentBoxSize;
|
|
23
|
-
if (contentBox) {
|
|
24
|
-
const width = contentBox.inlineSize;
|
|
25
|
-
setDimensions({
|
|
26
|
-
width,
|
|
27
|
-
/**
|
|
28
|
-
* 600 is (currently) the point before action button crashes with
|
|
29
|
-
* the rest of the toolbar buttons.
|
|
30
|
-
*/
|
|
31
|
-
isSmallWidth: width < 600,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
resizeObserver.observe(el);
|
|
37
|
-
|
|
38
|
-
return () => resizeObserver.disconnect();
|
|
39
|
-
}
|
|
40
|
-
}, []);
|
|
41
|
-
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
if (dimensions) {
|
|
44
|
-
onChange(dimensions);
|
|
45
|
-
}
|
|
46
|
-
}, [dimensions, onChange]);
|
|
47
|
-
|
|
48
|
-
return <div ref={ref} style={{ height: 1, marginTop: -1 }} />;
|
|
49
|
-
}
|