@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crystallize/design-system",
3
- "version": "1.17.0",
3
+ "version": "1.17.1",
4
4
  "types": "./dist/index.d.ts",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -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 = HTMLAttributes<HTMLLIElement> & {
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',
@@ -16,6 +16,7 @@ export type I18NKeys =
16
16
  | 'actionFormatWithSubscriptTitle'
17
17
  | 'actionFormatWithSuperscriptLabel'
18
18
  | 'actionFormatWithSuperscriptTitle'
19
+ | 'actionFormatClear'
19
20
  | 'actionCopyJSON'
20
21
  | 'actionClear'
21
22
  | 'actionFormatCode'
@@ -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
- <div ref={actionMenuAnchorRef} className="c-rte-actions-plugin">
42
- <ActionMenu container={actionMenuAnchorRef.current}>
43
- {!prepend
44
- ? null
45
- : prepend.map(actionItem => (
46
- <ActionMenu.Item
47
- key={actionItem.title}
48
- onSelect={actionItem.action}
49
- className={actionItem.type === 'danger' ? 'danger' : ''}
50
- >
51
- {actionItem.title}
52
- </ActionMenu.Item>
53
- ))}
54
- <ActionMenu.Item onSelect={() => copyJson(editor)}>{tr('actionCopyJSON')}</ActionMenu.Item>
55
- <ActionMenu.Item
56
- className="danger"
57
- onSelect={() => {
58
- editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);
59
- editor.focus();
60
- }}
61
- >
62
- {tr('actionClear')}
63
- </ActionMenu.Item>
64
- {!append
65
- ? null
66
- : append.map(actionItem => (
67
- <ActionMenu.Item
68
- key={actionItem.title}
69
- onSelect={actionItem.action}
70
- className={actionItem.type === 'danger' ? 'danger' : ''}
71
- >
72
- {actionItem.title}
73
- </ActionMenu.Item>
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
- onClick={() => {
586
- activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough');
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('actionFormatAsStrongTitle')}
598
+ {tr('actionFormatWithStrikethroughTitle')}
598
599
  </span>
599
600
  </DropdownMenu.Item>
600
601
  <DropdownMenu.Item
601
- onClick={() => {
602
- activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'subscript');
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
- onClick={() => {
616
- activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, 'superscript');
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">Clear Formatting</span>
637
+ <span className="c-rte-toolbar__dd-item__text--clear">{tr('actionFormatClear')}</span>
638
638
  </DropdownMenu.Item>
639
639
  </>
640
640
  }
@@ -7,7 +7,6 @@
7
7
  */
8
8
 
9
9
  @import './rich-text-editor-icons.css';
10
- @import './plugins/ActionsPlugin/index.css';
11
10
  @import './plugins/CodeActionMenuPlugin/index.css';
12
11
  @import './plugins/DraggableBlockPlugin/index.css';
13
12
  @import './plugins/TableActionMenuPlugin/index.css';
@@ -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
- <DimensionDetectorPlugin onChange={d => setIsSmallWidthViewport(d.isSmallWidth)} />
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 && !isSmallWidthViewport && (
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 | CrystallizeRichTextNode[];
9
+ export type CrystallizeRichText = CrystallizeRichTextNode[];
10
10
 
11
11
  type CrystallizeRichTextNodeBase = {
12
12
  textContent?: string;
@@ -1,3 +0,0 @@
1
- .c-rte-actions-plugin {
2
- @apply z-50 flex items-center;
3
- }
@@ -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
- }