@soul-refiner/design-system 0.1.26 → 0.1.27

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.
@@ -1,24 +1,16 @@
1
1
  import React from 'react';
2
2
  import styled from 'styled-components';
3
3
  import {
4
- Alignment,
5
- AlignToolbarButton,
6
- focusEditor,
7
4
  getPluginType,
8
- // getPreventDefaultHandler,
9
- ImageToolbarButton,
10
5
  insertImage,
11
6
  insertNodes,
12
- isCollapsed,
13
- LinkToolbarButton,
14
- MarkToolbarButton,
15
- PlateEditor,
16
- setMarks,
17
- setAlign,
18
- someNode,
19
- toggleIndentList,
20
7
  toggleNodeType,
21
- ToolbarButton,
8
+ useLinkToolbarButton,
9
+ useLinkToolbarButtonState,
10
+ useListToolbarButton,
11
+ useListToolbarButtonState,
12
+ useMarkToolbarButton,
13
+ useMarkToolbarButtonState,
22
14
  usePlateEditorRef,
23
15
  usePlateEditorState,
24
16
  } from '@udecode/plate';
@@ -26,8 +18,9 @@ import {
26
18
  import { Tooltip } from 'components';
27
19
  import {
28
20
  Bold,
29
- Dollar,
30
21
  FileCode,
22
+ Headphones,
23
+ Image,
31
24
  Italic,
32
25
  Link,
33
26
  ListOrdered,
@@ -37,38 +30,80 @@ import {
37
30
  Strikethrough,
38
31
  Underline,
39
32
  Undo,
33
+ Video,
40
34
  } from 'icons';
41
35
  import {
42
- BUTTON_SUBSCRIBE,
43
36
  ELEMENT_BLOCKQUOTE,
44
37
  ELEMENT_BUTTON,
45
- ELEMENT_DEFAULT,
46
- ELEMENT_H2,
47
- ELEMENT_PARAGRAPH,
48
- ELEMENT_PAYWALL,
49
- ELEMENT_QA,
38
+ ELEMENT_OL,
39
+ ELEMENT_UL,
50
40
  MARK_BOLD,
51
41
  MARK_CODE,
52
42
  MARK_ITALIC,
53
- MARK_HIGHLIGHT,
54
43
  MARK_STRIKETHROUGH,
55
- MARK_SUBSCRIPT,
56
- MARK_SUPERSCRIPT,
57
44
  MARK_UNDERLINE,
58
45
  } from '../constants';
59
46
  import { AlignmentDropdown } from './alignment_dropdown';
47
+ import { ButtonModal } from './button_modal';
48
+ import { StyleDropdown } from './style_dropdown';
60
49
 
61
50
  export interface EditorBarProps {
62
- communityName: string;
51
+ handleUploadImage: (dataURL: File) => Promise<string>;
63
52
  }
64
53
 
65
- export const EditorBar = ({ communityName }: EditorBarProps) => {
54
+ export const EditorBar = ({ handleUploadImage }: EditorBarProps) => {
66
55
  const editorRef = usePlateEditorRef('main');
67
56
  const stateEditor = usePlateEditorState();
68
57
  const audioRef = React.useRef<HTMLInputElement>(null);
69
58
  const imageRef = React.useRef<HTMLInputElement>(null);
70
59
  const videoRef = React.useRef<HTMLInputElement>(null);
71
60
 
61
+ const boldState = useMarkToolbarButtonState({ nodeType: MARK_BOLD });
62
+ const {
63
+ props: { pressed: currBold, onClick: handleBold },
64
+ } = useMarkToolbarButton(boldState);
65
+
66
+ const italicState = useMarkToolbarButtonState({ nodeType: MARK_ITALIC });
67
+ const {
68
+ props: { pressed: currItalic, onClick: handleItalic },
69
+ } = useMarkToolbarButton(italicState);
70
+
71
+ const strikethroughState = useMarkToolbarButtonState({
72
+ nodeType: MARK_STRIKETHROUGH,
73
+ });
74
+ const {
75
+ props: { pressed: currStrikethrough, onClick: handleStrikethrough },
76
+ } = useMarkToolbarButton(strikethroughState);
77
+
78
+ const underlineState = useMarkToolbarButtonState({
79
+ nodeType: MARK_UNDERLINE,
80
+ });
81
+ const {
82
+ props: { pressed: currUnderline, onClick: handleUnderline },
83
+ } = useMarkToolbarButton(underlineState);
84
+
85
+ const inlineCodeState = useMarkToolbarButtonState({ nodeType: MARK_CODE });
86
+ const {
87
+ props: { pressed: currInlineCode, onClick: handleInlineCode },
88
+ } = useMarkToolbarButton(inlineCodeState);
89
+
90
+ const linkState = useLinkToolbarButtonState();
91
+ const {
92
+ props: { pressed: currLink, onClick: handleLink },
93
+ } = useLinkToolbarButton(linkState);
94
+
95
+ const orderedListState = useListToolbarButtonState({ nodeType: ELEMENT_OL });
96
+ const {
97
+ props: { pressed: currOrderedList, onClick: handleOrderedList },
98
+ } = useListToolbarButton(orderedListState);
99
+
100
+ const unorderedListState = useListToolbarButtonState({
101
+ nodeType: ELEMENT_UL,
102
+ });
103
+ const {
104
+ props: { pressed: currUnorderedList, onClick: handleUnorderedList },
105
+ } = useListToolbarButton(unorderedListState);
106
+
72
107
  const handleAudioChange = async (event: React.FormEvent) => {
73
108
  const file = (event.target as HTMLInputElement)?.files?.[0];
74
109
  if (!file) return;
@@ -77,110 +112,37 @@ export const EditorBar = ({ communityName }: EditorBarProps) => {
77
112
  */
78
113
  };
79
114
 
80
- const handleBlockquote = (event: React.MouseEvent) => {
115
+ const handleBlockquote = () => {
81
116
  toggleNodeType(stateEditor, { activeType: ELEMENT_BLOCKQUOTE });
82
117
  };
83
118
 
84
- const handleImageChange = async (event: React.FormEvent) => {
85
- const file = (event.target as HTMLInputElement)?.files?.[0];
86
- if (!file) return;
87
- // const imageurl = await handleUploadImage(file);
88
- // if (imageurl) {
89
- // insertImage(editorRef, imageurl);
90
- // }
91
- };
92
-
93
- const handleFont = (value: string) => {
94
- const type = getPluginType(editorRef, 'fontSize');
95
- focusEditor(editorRef);
96
- setMarks(stateEditor, { [type]: `${value}px` });
97
- };
98
-
99
- const handleOrderedList = (event: any) => {
100
- event.preventDefault();
101
- toggleIndentList(editorRef, {
102
- listStyleType: 'decimals',
103
- });
104
- };
105
-
106
- const handlePaywall = () => {
119
+ const handleButton = (type: string) => {
107
120
  insertNodes(editorRef, [
108
121
  {
109
- type: ELEMENT_PAYWALL,
122
+ type: getPluginType(editorRef, ELEMENT_BUTTON),
123
+ buttonType: type,
110
124
  children: [
111
125
  {
112
- type: ELEMENT_H2,
113
- children: [
114
- {
115
- text: 'Keep reading with a paid subscription',
116
- },
117
- ],
118
- },
119
- {
120
- type: ELEMENT_PARAGRAPH,
121
- children: [
122
- {
123
- text: 'Subscribe to ',
124
- },
125
- {
126
- text: communityName,
127
- italic: true,
128
- },
129
- {
130
- text: ' to keep reading this content.',
131
- },
132
- ],
133
- },
134
- {
135
- type: ELEMENT_BUTTON,
136
- buttonType: BUTTON_SUBSCRIBE,
137
- children: [
138
- {
139
- text: 'Get Access',
140
- },
141
- ],
142
- },
143
- ],
144
- },
145
- {
146
- type: ELEMENT_DEFAULT,
147
- children: [
148
- {
149
- text: '',
126
+ text: 'Subscribe',
150
127
  },
151
128
  ],
152
129
  },
153
130
  ]);
154
- focusEditor(editorRef);
155
131
  };
156
132
 
157
- const handleQA = () => {
158
- insertNodes(editorRef, {
159
- type: ELEMENT_QA,
160
- children: [
161
- {
162
- type: ELEMENT_PARAGRAPH,
163
- children: [
164
- {
165
- text: 'Ask your question here...',
166
- },
167
- ],
168
- },
169
- ],
170
- });
133
+ const handleImageChange = async (event: React.FormEvent) => {
134
+ const file = (event.target as HTMLInputElement)?.files?.[0];
135
+ if (!file) return;
136
+ const imageurl = await handleUploadImage(file);
137
+ if (imageurl) {
138
+ insertImage(editorRef, imageurl);
139
+ }
171
140
  };
172
141
 
173
142
  const handleRedo = () => editorRef.redo();
174
143
 
175
144
  const handleUndo = () => editorRef.undo();
176
145
 
177
- const handleUnorderedList = (event: any) => {
178
- event.preventDefault();
179
- toggleIndentList(editorRef, {
180
- listStyleType: 'disc',
181
- });
182
- };
183
-
184
146
  const handleVideoChange = async (event: React.FormEvent) => {
185
147
  const file = (event.target as HTMLInputElement)?.files?.[0];
186
148
  if (!file) return;
@@ -205,57 +167,38 @@ export const EditorBar = ({ communityName }: EditorBarProps) => {
205
167
  </Tooltip>
206
168
  </MenuSection>
207
169
  <Divider />
170
+ <MenuSection>
171
+ <StyleDropdown editorRef={editorRef} />
172
+ </MenuSection>
173
+ <Divider />
208
174
  <MenuSection>
209
175
  <Tooltip content="Bold" place="top">
210
- <MarkToolbarButton
211
- as="div"
212
- type={getPluginType(editorRef, MARK_BOLD)}
213
- icon={
214
- <MenuButton $active={true}>
215
- <Bold size={24} color="grey-5" />
216
- </MenuButton>
217
- }
218
- />
176
+ <MenuButton $active={currBold} onClick={handleBold}>
177
+ <Bold size={24} color="grey-5" />
178
+ </MenuButton>
219
179
  </Tooltip>
220
180
  <Tooltip content="Italic" place="top">
221
- <MarkToolbarButton
222
- as="div"
223
- type={getPluginType(editorRef, MARK_ITALIC)}
224
- icon={
225
- <MenuButton $active={false}>
226
- <Italic size={24} color="grey-5" />
227
- </MenuButton>
228
- }
229
- />
181
+ <MenuButton $active={currItalic} onClick={handleItalic}>
182
+ <Italic size={24} color="grey-5" />
183
+ </MenuButton>
230
184
  </Tooltip>
231
185
  <Tooltip content="Underline" place="top">
232
- <MarkToolbarButton
233
- as="div"
234
- type={getPluginType(editorRef, MARK_UNDERLINE)}
235
- icon={<Underline size={24} color="grey-5" />}
236
- />
186
+ <MenuButton $active={currUnderline} onClick={handleUnderline}>
187
+ <Underline size={24} color="grey-5" />
188
+ </MenuButton>
237
189
  </Tooltip>
238
190
  <Tooltip content="Strikethrough" place="top">
239
- <MarkToolbarButton
240
- as="div"
241
- type={getPluginType(editorRef, MARK_STRIKETHROUGH)}
242
- icon={
243
- <MenuButton $active={false}>
244
- <Strikethrough size={24} color="grey-5" />
245
- </MenuButton>
246
- }
247
- />
191
+ <MenuButton
192
+ $active={currStrikethrough}
193
+ onClick={handleStrikethrough}
194
+ >
195
+ <Strikethrough size={24} color="grey-5" />
196
+ </MenuButton>
248
197
  </Tooltip>
249
198
  <Tooltip content="Inline code" place="top">
250
- <MarkToolbarButton
251
- as="div"
252
- type={getPluginType(editorRef, MARK_CODE)}
253
- icon={
254
- <MenuButton $active={false}>
255
- <FileCode size={24} color="grey-5" />
256
- </MenuButton>
257
- }
258
- />
199
+ <MenuButton $active={currInlineCode} onClick={handleInlineCode}>
200
+ <FileCode size={24} color="grey-5" />
201
+ </MenuButton>
259
202
  </Tooltip>
260
203
  </MenuSection>
261
204
  <Divider />
@@ -264,6 +207,77 @@ export const EditorBar = ({ communityName }: EditorBarProps) => {
264
207
  <AlignmentDropdown editorRef={editorRef} />
265
208
  </MenuButton>
266
209
  </MenuSection>
210
+ <Divider />
211
+ <MenuSection>
212
+ <Tooltip content="Link" place="top">
213
+ <MenuButton $active={currLink} onClick={handleLink}>
214
+ <Link size={24} color="grey-5" />
215
+ </MenuButton>
216
+ </Tooltip>
217
+ <Tooltip content="Image" place="top">
218
+ <MenuButton $active={false} as="label" onChange={handleImageChange}>
219
+ <div onClick={() => imageRef.current?.click()}>
220
+ <input
221
+ accept="image/*"
222
+ ref={imageRef}
223
+ style={{ display: 'none' }}
224
+ type="file"
225
+ />
226
+ <Image size={24} color="grey-5" />
227
+ </div>
228
+ </MenuButton>
229
+ </Tooltip>
230
+ <Tooltip content="Video" place="top">
231
+ <MenuButton $active={false} as="label" onChange={handleVideoChange}>
232
+ <div onClick={() => videoRef.current?.click()}>
233
+ <input
234
+ accept="video/*"
235
+ ref={videoRef}
236
+ style={{ display: 'none' }}
237
+ type="file"
238
+ />
239
+ <Video size={24} color="grey-5" />
240
+ </div>
241
+ </MenuButton>
242
+ </Tooltip>
243
+ <Tooltip content="Audio" place="top">
244
+ <MenuButton $active={false} as="label" onChange={handleAudioChange}>
245
+ <div onClick={() => audioRef.current?.click()}>
246
+ <input
247
+ accept="audio/*"
248
+ ref={audioRef}
249
+ style={{ display: 'none' }}
250
+ type="file"
251
+ />
252
+ <Headphones size={24} color="grey-5" />
253
+ </div>
254
+ </MenuButton>
255
+ </Tooltip>
256
+ <Tooltip content="Blockquote" place="top">
257
+ <MenuButton $active={false} onClick={handleBlockquote}>
258
+ <Quote size={24} color="grey-5" />
259
+ </MenuButton>
260
+ </Tooltip>
261
+ <Tooltip content="Add a button" place="top">
262
+ <ButtonModal onButtonInsert={handleButton} />
263
+ </Tooltip>
264
+ </MenuSection>
265
+ <Divider />
266
+ <MenuSection>
267
+ <Tooltip content="Unordered List" place="top">
268
+ <MenuButton
269
+ $active={currUnorderedList}
270
+ onClick={handleUnorderedList}
271
+ >
272
+ <ListUnordered size={24} color="grey-5" />
273
+ </MenuButton>
274
+ </Tooltip>
275
+ <Tooltip content="Ordered List" place="top">
276
+ <MenuButton $active={currOrderedList} onClick={handleOrderedList}>
277
+ <ListOrdered size={24} color="grey-5" />
278
+ </MenuButton>
279
+ </Tooltip>
280
+ </MenuSection>
267
281
  </Menu>
268
282
  </Wrapper>
269
283
  );
@@ -326,7 +340,7 @@ export const MenuButton = styled.button<{
326
340
  }
327
341
 
328
342
  background-color: ${({ $active }) =>
329
- $active ? 'background-color: var(--color-grey-9)' : 'transparent'};
343
+ $active ? 'var(--color-grey-9)' : 'transparent'};
330
344
  `;
331
345
 
332
346
  const Divider = styled.div`
@@ -0,0 +1,107 @@
1
+ import React from 'react';
2
+ import { Button, Dropdown } from 'components';
3
+ import {
4
+ findNode,
5
+ isBlock,
6
+ isCollapsed,
7
+ PlateEditor,
8
+ toggleNodeType,
9
+ TElement,
10
+ } from '@udecode/plate';
11
+ import {
12
+ ELEMENT_H1,
13
+ ELEMENT_H2,
14
+ ELEMENT_H3,
15
+ ELEMENT_H4,
16
+ ELEMENT_H5,
17
+ ELEMENT_H6,
18
+ ELEMENT_PARAGRAPH,
19
+ } from '../constants';
20
+
21
+ const items = [
22
+ {
23
+ value: ELEMENT_PARAGRAPH,
24
+ label: 'Paragraph',
25
+ description: 'Paragraph',
26
+ },
27
+ {
28
+ value: ELEMENT_H1,
29
+ label: 'Heading 1',
30
+ description: 'Heading 1',
31
+ },
32
+ {
33
+ value: ELEMENT_H2,
34
+ label: 'Heading 2',
35
+ description: 'Heading 2',
36
+ },
37
+ {
38
+ value: ELEMENT_H3,
39
+ label: 'Heading 3',
40
+ description: 'Heading 3',
41
+ },
42
+ {
43
+ value: ELEMENT_H4,
44
+ label: 'Heading 4',
45
+ description: 'Heading 4',
46
+ },
47
+ {
48
+ value: ELEMENT_H5,
49
+ label: 'Heading 5',
50
+ description: 'Heading 5',
51
+ },
52
+ {
53
+ value: ELEMENT_H6,
54
+ label: 'Heading 6',
55
+ description: 'Heading 6',
56
+ },
57
+ ];
58
+
59
+ // eslint-disable-next-line
60
+ const defaultItem = items.find((item) => item.value === ELEMENT_PARAGRAPH)!;
61
+
62
+ export interface StyleDropdown {
63
+ editorRef: PlateEditor;
64
+ }
65
+
66
+ export const StyleDropdown = ({ editorRef }: StyleDropdown) => {
67
+ let value: string = ELEMENT_PARAGRAPH;
68
+
69
+ const handleChange = (event: React.FormEvent) => {
70
+ const value = (event.target as any).getAttribute('data-value');
71
+ if (!value) return;
72
+ toggleNodeType(editorRef, { activeType: value });
73
+ };
74
+
75
+ if (isCollapsed(editorRef?.selection)) {
76
+ const entry = findNode<TElement>(editorRef, {
77
+ match: (n) => isBlock(editorRef, n),
78
+ });
79
+ if (entry) {
80
+ value =
81
+ items.find((item) => item.value === entry[0].type)?.value ??
82
+ ELEMENT_PARAGRAPH;
83
+ }
84
+ }
85
+
86
+ const selectedItem =
87
+ items.find((item) => item.value === value) ?? defaultItem;
88
+
89
+ const { label: selectedItemLabel } = selectedItem;
90
+
91
+ return (
92
+ <Dropdown
93
+ maintainState={false}
94
+ onChange={handleChange}
95
+ options={items}
96
+ trigger={
97
+ <Button
98
+ type="special"
99
+ size="small"
100
+ sx="padding: 1px 4px; & > * { color: var(--color-grey-5); }"
101
+ >
102
+ {selectedItemLabel}
103
+ </Button>
104
+ }
105
+ />
106
+ );
107
+ };
@@ -1,17 +1,37 @@
1
+ import { withProps } from '@udecode/plate';
2
+ import { BlockquoteElement } from './plugins/blockquote';
3
+ import { Button } from './plugins/buttons';
4
+ import { ImageElement } from './plugins/image';
5
+ import { LinkElement } from './plugins/link';
6
+ import { ListElement } from './plugins/list';
1
7
  import {
2
- CodeBlockElement,
3
- BlockquoteElement,
4
- CodeLineElement,
5
- CodeSyntaxLeaf,
6
- ImageElement,
7
- createPlateUI,
8
- } from '@udecode/plate';
8
+ ParagraphElement,
9
+ HOneElement,
10
+ HTwoElement,
11
+ HThreeElement,
12
+ HFourElement,
13
+ HFiveElement,
14
+ HSixElement,
15
+ } from './plugins/text';
16
+
9
17
  import * as Elements from './constants';
10
18
 
11
- export const plateUI = createPlateUI({
19
+ export const plateUI = {
20
+ [Elements.ELEMENT_PARAGRAPH]: ParagraphElement,
21
+ [Elements.ELEMENT_H1]: HOneElement,
22
+ [Elements.ELEMENT_H2]: HTwoElement,
23
+ [Elements.ELEMENT_H3]: HThreeElement,
24
+ [Elements.ELEMENT_H4]: HFourElement,
25
+ [Elements.ELEMENT_H5]: HFiveElement,
26
+ [Elements.ELEMENT_H6]: HSixElement,
12
27
  [Elements.ELEMENT_BLOCKQUOTE]: BlockquoteElement,
13
- [Elements.ELEMENT_CODE_BLOCK]: CodeBlockElement,
14
- [Elements.ELEMENT_CODE_LINE]: CodeLineElement,
15
- [Elements.ELEMENT_CODE_SYNTAX]: CodeSyntaxLeaf,
28
+ [Elements.ELEMENT_LINK]: LinkElement,
29
+ [Elements.ELEMENT_OL]: withProps(ListElement, {
30
+ variant: Elements.ELEMENT_OL,
31
+ }),
32
+ [Elements.ELEMENT_UL]: withProps(ListElement, {
33
+ variant: Elements.ELEMENT_UL,
34
+ }),
16
35
  [Elements.ELEMENT_IMAGE]: ImageElement,
17
- });
36
+ [Elements.ELEMENT_BUTTON]: Button,
37
+ };
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import {
3
+ PlateElement,
4
+ PlateElementProps,
5
+ PlateRenderElementProps,
6
+ } from '@udecode/plate';
7
+ import { P } from 'components';
8
+
9
+ export const BlockquoteElement = React.forwardRef<
10
+ React.ElementRef<typeof PlateElement>,
11
+ PlateElementProps
12
+ >(({ children, ...props }: PlateRenderElementProps, ref) => {
13
+ return (
14
+ <PlateElement asChild ref={ref} {...props}>
15
+ <>
16
+ <P
17
+ as="blockquote"
18
+ sx="padding-left: 16px; border-left: 1px solid var(--color-grey-8);"
19
+ >
20
+ {children}
21
+ </P>
22
+ </>
23
+ </PlateElement>
24
+ );
25
+ });