@crystallize/design-system 1.7.0 → 1.8.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/index.css +53 -332
  3. package/dist/index.d.ts +15 -8
  4. package/dist/index.js +1723 -4554
  5. package/dist/index.mjs +1643 -2339
  6. package/package.json +1 -1
  7. package/src/action-menu/action-item-separator.tsx +14 -0
  8. package/src/action-menu/action-item.tsx +2 -2
  9. package/src/action-menu/action-menu.css +8 -0
  10. package/src/action-menu/action-menu.tsx +2 -1
  11. package/src/dropdown-menu/dropdown-menu-root.tsx +3 -1
  12. package/src/dropdown-menu/index.ts +5 -2
  13. package/src/iconography/subscription-contracts.tsx +4 -4
  14. package/src/iconography/subscription-plans.tsx +5 -5
  15. package/src/rich-text-editor/model/crystallize-to-lexical.ts +12 -1
  16. package/src/rich-text-editor/model/lexical-to-crystallize.ts +38 -38
  17. package/src/rich-text-editor/nodes/BaseNodes.ts +0 -7
  18. package/src/rich-text-editor/nodes/TableCellNodes.ts +0 -7
  19. package/src/rich-text-editor/plugins/ActionsPlugin/index.tsx +1 -1
  20. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/CopyButton/index.tsx +3 -2
  21. package/src/rich-text-editor/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx +0 -1
  22. package/src/rich-text-editor/plugins/FloatingTextFormatToolbarPlugin/index.css +17 -17
  23. package/src/rich-text-editor/plugins/FloatingTextFormatToolbarPlugin/index.tsx +1 -1
  24. package/src/rich-text-editor/plugins/MaxLengthPlugin/index.tsx +2 -7
  25. package/src/rich-text-editor/plugins/TableActionMenuPlugin/index.tsx +80 -149
  26. package/src/rich-text-editor/plugins/ToolbarPlugin/index.tsx +2 -2
  27. package/src/rich-text-editor/plugins/ToolbarPlugin/insert-table.tsx +55 -0
  28. package/src/rich-text-editor/rich-text-editor.css +10 -322
  29. package/src/rich-text-editor/rich-text-editor.stories.tsx +35 -5
  30. package/src/rich-text-editor/rich-text-editor.tsx +6 -39
  31. package/src/rich-text-editor/tests/rich-text-editor-code.test.tsx +10 -6
  32. package/src/rich-text-editor/tests/rich-text-editor-model-conversions.test.tsx +19 -7
  33. package/src/rich-text-editor/themes/CrystallizeRTEditorTheme.css +3 -11
  34. package/dist/TableComponent-I2YOOYOU.css +0 -281
  35. package/dist/TableComponent-QINOO453.mjs +0 -1377
  36. package/dist/chevron-down-3FRWSIKS.svg +0 -1
  37. package/dist/chunk-VUXQZRSP.mjs +0 -737
  38. package/dist/markdown-4BGQNLLT.svg +0 -1
  39. package/src/rich-text-editor/nodes/KeywordNode.ts +0 -73
  40. package/src/rich-text-editor/nodes/TableComponent.tsx +0 -1547
  41. package/src/rich-text-editor/nodes/TableNode.tsx +0 -398
  42. package/src/rich-text-editor/plugins/ComponentPickerPlugin/index.tsx +0 -320
  43. package/src/rich-text-editor/plugins/DragDropPastePlugin/index.ts +0 -40
  44. package/src/rich-text-editor/plugins/MarkdownShortcutPlugin/index.tsx +0 -16
  45. package/src/rich-text-editor/plugins/MarkdownTransformers/index.ts +0 -195
  46. package/src/rich-text-editor/plugins/SpeechToTextPlugin/index.ts +0 -113
  47. package/src/rich-text-editor/plugins/TableCellResizer/index.css +0 -12
  48. package/src/rich-text-editor/plugins/TableCellResizer/index.tsx +0 -386
  49. package/src/rich-text-editor/plugins/TablePlugin.tsx +0 -190
  50. package/src/rich-text-editor/plugins/TreeViewPlugin/index.tsx +0 -25
  51. package/src/rich-text-editor/plugins/TypingPerfPlugin/index.ts +0 -117
@@ -6,7 +6,6 @@
6
6
  *
7
7
  */
8
8
 
9
- import * as React from 'react';
10
9
  import { ReactPortal, useCallback, useEffect, useRef, useState } from 'react';
11
10
  import { $getRoot, $getSelection, $isRangeSelection, DEPRECATED_$isGridSelection } from 'lexical';
12
11
  import { createPortal } from 'react-dom';
@@ -22,6 +21,7 @@ import {
22
21
  $insertTableColumn,
23
22
  $insertTableRow,
24
23
  $isTableCellNode,
24
+ $isTableNode,
25
25
  $isTableRowNode,
26
26
  $removeTableRowAtIndex,
27
27
  getTableSelectionFromTableElement,
@@ -30,24 +30,22 @@ import {
30
30
  TableCellNode,
31
31
  } from '@lexical/table';
32
32
 
33
+ import { DropdownMenu } from '../../../dropdown-menu';
33
34
  import { IconButton } from '../../../icon-button';
34
35
  import { Icon } from '../../../iconography';
35
36
 
37
+ type TableStats = {
38
+ rows: number;
39
+ columns: number;
40
+ };
41
+
36
42
  type TableCellActionMenuProps = Readonly<{
37
- contextRef: { current: null | HTMLElement };
38
- onClose: () => void;
39
- setIsMenuOpen: (isOpen: boolean) => void;
40
43
  tableCellNode: TableCellNode;
44
+ tableStats: TableStats;
41
45
  }>;
42
46
 
43
- function TableActionMenu({
44
- onClose,
45
- tableCellNode: _tableCellNode,
46
- setIsMenuOpen,
47
- contextRef,
48
- }: TableCellActionMenuProps) {
47
+ function TableActionMenu({ tableCellNode: _tableCellNode, tableStats }: TableCellActionMenuProps) {
49
48
  const [editor] = useLexicalComposerContext();
50
- const dropDownRef = useRef<HTMLDivElement | null>(null);
51
49
  const [tableCellNode, updateTableCellNode] = useState(_tableCellNode);
52
50
  const [selectionCounts, updateSelectionCounts] = useState({
53
51
  columns: 1,
@@ -81,38 +79,6 @@ function TableActionMenu({
81
79
  });
82
80
  }, [editor]);
83
81
 
84
- useEffect(() => {
85
- const menuButtonElement = contextRef.current;
86
- const dropDownElement = dropDownRef.current;
87
-
88
- if (menuButtonElement != null && dropDownElement != null) {
89
- const menuButtonRect = menuButtonElement.getBoundingClientRect();
90
-
91
- dropDownElement.style.opacity = '1';
92
-
93
- dropDownElement.style.left = `${menuButtonRect.left + menuButtonRect.width + window.pageXOffset + 5}px`;
94
-
95
- dropDownElement.style.top = `${menuButtonRect.top + window.pageYOffset}px`;
96
- }
97
- }, [contextRef, dropDownRef]);
98
-
99
- useEffect(() => {
100
- function handleClickOutside(event: MouseEvent) {
101
- if (
102
- dropDownRef.current != null &&
103
- contextRef.current != null &&
104
- !dropDownRef.current.contains(event.target as Node) &&
105
- !contextRef.current.contains(event.target as Node)
106
- ) {
107
- setIsMenuOpen(false);
108
- }
109
- }
110
-
111
- window.addEventListener('click', handleClickOutside);
112
-
113
- return () => window.removeEventListener('click', handleClickOutside);
114
- }, [setIsMenuOpen, contextRef]);
115
-
116
82
  const clearTableSelection = useCallback(() => {
117
83
  editor.update(() => {
118
84
  if (tableCellNode.isAttached()) {
@@ -158,11 +124,9 @@ function TableActionMenu({
158
124
  $insertTableRow(tableNode, tableRowIndex, shouldInsertAfter, selectionCounts.rows, grid);
159
125
 
160
126
  clearTableSelection();
161
-
162
- onClose();
163
127
  });
164
128
  },
165
- [editor, tableCellNode, selectionCounts.rows, clearTableSelection, onClose],
129
+ [editor, tableCellNode, selectionCounts.rows, clearTableSelection],
166
130
  );
167
131
 
168
132
  const insertTableColumnAtSelection = useCallback(
@@ -186,11 +150,9 @@ function TableActionMenu({
186
150
  $insertTableColumn(tableNode, tableColumnIndex, shouldInsertAfter, selectionCounts.columns, grid);
187
151
 
188
152
  clearTableSelection();
189
-
190
- onClose();
191
153
  });
192
154
  },
193
- [editor, tableCellNode, selectionCounts.columns, clearTableSelection, onClose],
155
+ [editor, tableCellNode, selectionCounts.columns, clearTableSelection],
194
156
  );
195
157
 
196
158
  const deleteTableRowAtSelection = useCallback(() => {
@@ -201,9 +163,8 @@ function TableActionMenu({
201
163
  $removeTableRowAtIndex(tableNode, tableRowIndex);
202
164
 
203
165
  clearTableSelection();
204
- onClose();
205
166
  });
206
- }, [editor, tableCellNode, clearTableSelection, onClose]);
167
+ }, [editor, tableCellNode, clearTableSelection]);
207
168
 
208
169
  const deleteTableAtSelection = useCallback(() => {
209
170
  editor.update(() => {
@@ -211,9 +172,8 @@ function TableActionMenu({
211
172
  tableNode.remove();
212
173
 
213
174
  clearTableSelection();
214
- onClose();
215
175
  });
216
- }, [editor, tableCellNode, clearTableSelection, onClose]);
176
+ }, [editor, tableCellNode, clearTableSelection]);
217
177
 
218
178
  const deleteTableColumnAtSelection = useCallback(() => {
219
179
  editor.update(() => {
@@ -224,9 +184,8 @@ function TableActionMenu({
224
184
  $deleteTableColumn(tableNode, tableColumnIndex);
225
185
 
226
186
  clearTableSelection();
227
- onClose();
228
187
  });
229
- }, [editor, tableCellNode, clearTableSelection, onClose]);
188
+ }, [editor, tableCellNode, clearTableSelection]);
230
189
 
231
190
  const toggleTableRowIsHeader = useCallback(() => {
232
191
  editor.update(() => {
@@ -255,9 +214,8 @@ function TableActionMenu({
255
214
  });
256
215
 
257
216
  clearTableSelection();
258
- onClose();
259
217
  });
260
- }, [editor, tableCellNode, clearTableSelection, onClose]);
218
+ }, [editor, tableCellNode, clearTableSelection]);
261
219
 
262
220
  const toggleTableColumnIsHeader = useCallback(() => {
263
221
  editor.update(() => {
@@ -290,79 +248,59 @@ function TableActionMenu({
290
248
  }
291
249
 
292
250
  clearTableSelection();
293
- onClose();
294
251
  });
295
- }, [editor, tableCellNode, clearTableSelection, onClose]);
296
-
297
- return createPortal(
298
- <div
299
- className="dropdown"
300
- ref={dropDownRef}
301
- onClick={e => {
302
- e.stopPropagation();
303
- }}
304
- >
305
- <button className="item" onClick={() => insertTableRowAtSelection(false)}>
306
- <span className="text-sm">
307
- Insert {selectionCounts.rows === 1 ? 'row' : `${selectionCounts.rows} rows`} above
308
- </span>
309
- </button>
310
- <button className="item" onClick={() => insertTableRowAtSelection(true)}>
311
- <span className="text-sm">
312
- Insert {selectionCounts.rows === 1 ? 'row' : `${selectionCounts.rows} rows`} below
313
- </span>
314
- </button>
315
- <hr className="my-1" />
316
- <button className="item" onClick={() => insertTableColumnAtSelection(false)}>
317
- <span className="text-sm">
318
- Insert {selectionCounts.columns === 1 ? 'column' : `${selectionCounts.columns} columns`} left
319
- </span>
320
- </button>
321
- <button className="item" onClick={() => insertTableColumnAtSelection(true)}>
322
- <span className="text-sm">
323
- Insert {selectionCounts.columns === 1 ? 'column' : `${selectionCounts.columns} columns`} right
324
- </span>
325
- </button>
326
- <hr className="my-1" />
327
- <button className="item" onClick={() => deleteTableColumnAtSelection()}>
328
- <span className="text-sm">Delete column</span>
329
- </button>
330
- <button className="item" onClick={() => deleteTableRowAtSelection()}>
331
- <span className="text-sm">Delete row</span>
332
- </button>
333
- <button className="item" onClick={() => deleteTableAtSelection()}>
334
- <span className="text-sm">Delete table</span>
335
- </button>
336
- <hr className="my-1" />
337
- <button className="item" onClick={() => toggleTableRowIsHeader()}>
338
- <span className="text-sm">
339
- {(tableCellNode.__headerState & TableCellHeaderStates.ROW) === TableCellHeaderStates.ROW ? 'Remove' : 'Add'}{' '}
340
- row header
341
- </span>
342
- </button>
343
- <button className="item" onClick={() => toggleTableColumnIsHeader()}>
344
- <span className="text-sm">
345
- {(tableCellNode.__headerState & TableCellHeaderStates.COLUMN) === TableCellHeaderStates.COLUMN
346
- ? 'Remove'
347
- : 'Add'}{' '}
348
- column header
349
- </span>
350
- </button>
351
- </div>,
352
- document.body,
252
+ }, [editor, tableCellNode, clearTableSelection]);
253
+
254
+ return (
255
+ <>
256
+ <DropdownMenu.Item onSelect={() => insertTableRowAtSelection(false)}>
257
+ Insert {selectionCounts.rows === 1 ? 'row' : `${selectionCounts.rows} rows`} above
258
+ </DropdownMenu.Item>
259
+ <DropdownMenu.Item onSelect={() => insertTableRowAtSelection(true)}>
260
+ Insert {selectionCounts.rows === 1 ? 'row' : `${selectionCounts.rows} rows`} below
261
+ </DropdownMenu.Item>
262
+ <DropdownMenu.Item onSelect={() => insertTableColumnAtSelection(false)}>
263
+ Insert {selectionCounts.columns === 1 ? 'column' : `${selectionCounts.columns} columns`} left
264
+ </DropdownMenu.Item>
265
+ <DropdownMenu.Item onSelect={() => insertTableColumnAtSelection(true)}>
266
+ Insert {selectionCounts.columns === 1 ? 'column' : `${selectionCounts.columns} columns`} right
267
+ </DropdownMenu.Item>
268
+ <DropdownMenu.Item onSelect={() => toggleTableRowIsHeader()}>
269
+ {(tableCellNode.__headerState & TableCellHeaderStates.ROW) === TableCellHeaderStates.ROW ? 'Remove' : 'Add'} row
270
+ header
271
+ </DropdownMenu.Item>
272
+ <DropdownMenu.Item onSelect={() => toggleTableColumnIsHeader()}>
273
+ {(tableCellNode.__headerState & TableCellHeaderStates.COLUMN) === TableCellHeaderStates.COLUMN
274
+ ? 'Remove'
275
+ : 'Add'}{' '}
276
+ column header
277
+ </DropdownMenu.Item>
278
+ <DropdownMenu.Separator />
279
+ {tableStats.columns > 1 && (
280
+ <DropdownMenu.Item onSelect={() => deleteTableColumnAtSelection()}>Delete column</DropdownMenu.Item>
281
+ )}
282
+ {tableStats.rows > 1 && (
283
+ <DropdownMenu.Item onSelect={() => deleteTableRowAtSelection()}>Delete row</DropdownMenu.Item>
284
+ )}
285
+ <DropdownMenu.Item onSelect={() => deleteTableAtSelection()}>Delete table</DropdownMenu.Item>
286
+ </>
353
287
  );
354
288
  }
355
289
 
356
- function TableCellActionMenuContainer({ anchorElem }: { anchorElem: HTMLElement }): JSX.Element {
290
+ function TableCellActionMenuContainer({ anchorElem }: { anchorElem: HTMLElement }) {
357
291
  const [editor] = useLexicalComposerContext();
358
292
 
359
293
  const menuButtonRef = useRef(null);
360
- const menuRootRef = useRef(null);
361
- const [isMenuOpen, setIsMenuOpen] = useState(false);
362
294
 
295
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
363
296
  const [tableCellNode, setTableMenuCellNode] = useState<TableCellNode | null>(null);
297
+ const [tableStats, setTablestats] = useState<TableStats>({ rows: 1, columns: 1 });
364
298
 
365
299
  const moveMenu = useCallback(() => {
300
+ if (isMenuOpen) {
301
+ return;
302
+ }
303
+
366
304
  const menu = menuButtonRef.current;
367
305
  const selection = $getSelection();
368
306
  const nativeSelection = window.getSelection();
@@ -396,10 +334,26 @@ function TableCellActionMenuContainer({ anchorElem }: { anchorElem: HTMLElement
396
334
  }
397
335
 
398
336
  setTableMenuCellNode(tableCellNodeFromSelection);
337
+
338
+ let rows = 1;
339
+ let columns = 1;
340
+ const parentRowNode = tableCellNodeFromSelection.getParent();
341
+ if ($isTableRowNode(parentRowNode)) {
342
+ const parentTableNode = parentRowNode.getParent();
343
+ if ($isTableNode(parentTableNode)) {
344
+ rows = parentTableNode.getChildrenSize();
345
+ columns = parentRowNode.getChildrenSize();
346
+ }
347
+ }
348
+
349
+ setTablestats({
350
+ rows,
351
+ columns,
352
+ });
399
353
  } else if (!activeElement) {
400
354
  setTableMenuCellNode(null);
401
355
  }
402
- }, [editor]);
356
+ }, [editor, isMenuOpen]);
403
357
 
404
358
  useEffect(() => {
405
359
  return editor.registerUpdateListener(() => {
@@ -432,40 +386,17 @@ function TableCellActionMenuContainer({ anchorElem }: { anchorElem: HTMLElement
432
386
  }
433
387
  }, [menuButtonRef, tableCellNode, editor, anchorElem]);
434
388
 
435
- const prevTableCellDOM = useRef(tableCellNode);
436
-
437
- useEffect(() => {
438
- if (prevTableCellDOM.current !== tableCellNode) {
439
- setIsMenuOpen(false);
440
- }
441
-
442
- prevTableCellDOM.current = tableCellNode;
443
- }, [prevTableCellDOM, tableCellNode]);
444
-
445
389
  return (
446
390
  <div className="table-cell-action-button-container" ref={menuButtonRef}>
447
391
  {tableCellNode != null && (
448
- <>
449
- <IconButton
450
- size="xs"
451
- className="table-cell-action-button chevron-down"
452
- onClick={e => {
453
- e.stopPropagation();
454
- setIsMenuOpen(!isMenuOpen);
455
- }}
456
- ref={menuRootRef}
457
- >
392
+ <DropdownMenu.Root
393
+ onOpenChange={isOpen => setIsMenuOpen(isOpen)}
394
+ content={<TableActionMenu tableCellNode={tableCellNode} tableStats={tableStats} />}
395
+ >
396
+ <IconButton size="xs" className="table-cell-action-button">
458
397
  <Icon.Arrow />
459
398
  </IconButton>
460
- {isMenuOpen && (
461
- <TableActionMenu
462
- contextRef={menuRootRef}
463
- setIsMenuOpen={setIsMenuOpen}
464
- onClose={() => setIsMenuOpen(false)}
465
- tableCellNode={tableCellNode}
466
- />
467
- )}
468
- </>
399
+ </DropdownMenu.Root>
469
400
  )}
470
401
  </div>
471
402
  );
@@ -62,8 +62,8 @@ import type { CrystallizeRichTextActionMenuItem } from '../../types/types';
62
62
  import { IS_APPLE } from '../../utils/environment';
63
63
  import { getSelectedNode } from '../../utils/getSelectedNode';
64
64
  import { sanitizeUrl } from '../../utils/url';
65
- import { InsertNewTableDialog } from '../TablePlugin';
66
65
  import ActionsPlugin from './../ActionsPlugin';
66
+ import { InsertTableDialog } from './insert-table';
67
67
 
68
68
  const blockTypeToBlockName = {
69
69
  bullet: 'Bulleted List',
@@ -713,7 +713,7 @@ export default function ToolbarPlugin({
713
713
  Define your starting point of a table, you can add and remove columns and rows after creation.
714
714
  </Dialog.Description>
715
715
  <div className="items-center justify-between">
716
- <InsertNewTableDialog activeEditor={activeEditor} />
716
+ <InsertTableDialog activeEditor={activeEditor} />
717
717
  </div>
718
718
  </Dialog.Content>
719
719
  </div>
@@ -0,0 +1,55 @@
1
+ import { useState } from 'react';
2
+ import type { LexicalEditor } from 'lexical';
3
+ import { INSERT_TABLE_COMMAND } from '@lexical/table';
4
+
5
+ import { Button } from '../../../button';
6
+ import { Dialog } from '../../../dialog';
7
+ import { InputWithLabel } from '../../../input-with-label';
8
+
9
+ export function InsertTableDialog({ activeEditor }: { activeEditor: LexicalEditor }) {
10
+ const [rows, setRows] = useState('5');
11
+ const [columns, setColumns] = useState('5');
12
+
13
+ const onClick = () => {
14
+ if (parseInt(rows) < 1 || parseInt(columns) < 1) {
15
+ return;
16
+ }
17
+ activeEditor.dispatchCommand(INSERT_TABLE_COMMAND, {
18
+ columns,
19
+ rows,
20
+ includeHeaders: {
21
+ columns: false,
22
+ rows: false,
23
+ },
24
+ });
25
+ };
26
+
27
+ return (
28
+ <>
29
+ <div className="grid grid-cols-[1fr_1px_1fr] border border-gray-100-800 border-solid shadow-sm rounded-md ">
30
+ <InputWithLabel
31
+ label="Rows"
32
+ value={rows}
33
+ placeholder="0"
34
+ type="text"
35
+ inputMode="numeric"
36
+ onChange={e => setRows(e.target.value)}
37
+ />
38
+ <span className="h-full bg-gray-100-800" />
39
+ <InputWithLabel
40
+ type="text"
41
+ label="Columns"
42
+ placeholder="0"
43
+ value={columns}
44
+ inputMode="numeric"
45
+ onChange={e => setColumns(e.target.value)}
46
+ />
47
+ </div>
48
+ <div className="flex justify-end mt-3">
49
+ <Button as={Dialog.Close} size="sm" intent="action" aria-label="Confirm" onClick={onClick}>
50
+ Confirm
51
+ </Button>
52
+ </div>
53
+ </>
54
+ );
55
+ }