@pareto-engineering/design-system 4.0.0-alpha.39 → 4.0.0-alpha.41
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/dist/cjs/a/People/common/Person/Person.js +4 -4
- package/dist/cjs/a/People/styles.scss +10 -4
- package/dist/cjs/b/Card/common/Group/Group.js +1 -1
- package/dist/cjs/f/fields/EditorInput/EditorInput.js +191 -0
- package/dist/cjs/f/fields/EditorInput/common/Toolbar.js +193 -0
- package/dist/cjs/f/fields/EditorInput/common/TreeViewPlugin.js +19 -0
- package/dist/cjs/f/fields/EditorInput/common/index.js +20 -0
- package/dist/cjs/f/fields/EditorInput/index.js +13 -0
- package/dist/cjs/f/fields/EditorInput/styles.scss +119 -0
- package/dist/cjs/f/fields/QueryCombobox/common/Combobox/Combobox.js +1 -1
- package/dist/cjs/f/fields/QueryCombobox/styles.scss +4 -1
- package/dist/cjs/f/fields/index.js +8 -1
- package/dist/es/a/People/common/Person/Person.js +4 -4
- package/dist/es/a/People/styles.scss +10 -4
- package/dist/es/b/Card/common/Group/Group.js +1 -1
- package/dist/es/f/fields/EditorInput/EditorInput.js +186 -0
- package/dist/es/f/fields/EditorInput/common/Toolbar.js +182 -0
- package/dist/es/f/fields/EditorInput/common/TreeViewPlugin.js +11 -0
- package/dist/es/f/fields/EditorInput/common/index.js +2 -0
- package/dist/es/f/fields/EditorInput/index.js +2 -0
- package/dist/es/f/fields/EditorInput/styles.scss +119 -0
- package/dist/es/f/fields/QueryCombobox/common/Combobox/Combobox.js +1 -1
- package/dist/es/f/fields/QueryCombobox/styles.scss +4 -1
- package/dist/es/f/fields/index.js +2 -1
- package/package.json +5 -3
- package/src/stories/a/People.stories.jsx +20 -0
- package/src/stories/f/EditorInput.stories.jsx +88 -0
- package/src/ui/a/People/common/Person/Person.jsx +4 -4
- package/src/ui/a/People/styles.scss +10 -4
- package/src/ui/b/Card/common/Group/Group.jsx +1 -1
- package/src/ui/f/fields/EditorInput/EditorInput.jsx +237 -0
- package/src/ui/f/fields/EditorInput/common/Toolbar.jsx +254 -0
- package/src/ui/f/fields/EditorInput/common/TreeViewPlugin.jsx +16 -0
- package/src/ui/f/fields/EditorInput/common/index.jsx +2 -0
- package/src/ui/f/fields/EditorInput/index.js +2 -0
- package/src/ui/f/fields/EditorInput/styles.scss +119 -0
- package/src/ui/f/fields/QueryCombobox/common/Combobox/Combobox.jsx +1 -0
- package/src/ui/f/fields/QueryCombobox/styles.scss +4 -1
- package/src/ui/f/fields/index.js +1 -0
- package/tests/__snapshots__/Storyshots.test.js.snap +703 -4
|
@@ -70,3 +70,23 @@ export const OnePerson = () => {
|
|
|
70
70
|
</div>
|
|
71
71
|
)
|
|
72
72
|
}
|
|
73
|
+
|
|
74
|
+
export const WithoutRole = () => {
|
|
75
|
+
const peopleMap = [
|
|
76
|
+
{
|
|
77
|
+
image:'https://pareto-v1.s3.us-west-2.amazonaws.com/profile-pictures/Camille-Arigo.jpg',
|
|
78
|
+
name :'Camillie Arigo',
|
|
79
|
+
color:'background4',
|
|
80
|
+
},
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div className="x-background-far b-x v1 p-v">
|
|
85
|
+
<People>
|
|
86
|
+
{peopleMap.map((person) => (
|
|
87
|
+
<People.Person key={person.name} {...person} />
|
|
88
|
+
))}
|
|
89
|
+
</People>
|
|
90
|
+
</div>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* @pareto-engineering/generator-front 1.0.12 */
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
|
|
4
|
+
import { Formik, Form } from 'formik'
|
|
5
|
+
|
|
6
|
+
import { EditorInput, FormDebugger } from 'ui'
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
title :'f/fields/EditorInput',
|
|
10
|
+
component :EditorInput,
|
|
11
|
+
subcomponents:{
|
|
12
|
+
// Item:TextareaInput.Item
|
|
13
|
+
},
|
|
14
|
+
decorators:[
|
|
15
|
+
(storyfn) => (
|
|
16
|
+
<Formik
|
|
17
|
+
initialValues={{ feedback: '' }}
|
|
18
|
+
>
|
|
19
|
+
<Form>
|
|
20
|
+
{ storyfn() }
|
|
21
|
+
</Form>
|
|
22
|
+
</Formik>
|
|
23
|
+
),
|
|
24
|
+
],
|
|
25
|
+
argTypes:{
|
|
26
|
+
name :{ control: 'text' },
|
|
27
|
+
label :{ control: 'text' },
|
|
28
|
+
labelColor :{ control: 'text' },
|
|
29
|
+
disabled :{ control: 'boolean' },
|
|
30
|
+
labelAsDescription:{ control: 'boolean' },
|
|
31
|
+
optional :{ control: 'boolean' },
|
|
32
|
+
labelSpan :{ control: 'number' },
|
|
33
|
+
inputSpan :{ control: 'number' },
|
|
34
|
+
desktopLabelSpan :{ control: 'number' },
|
|
35
|
+
desktopInputSpan :{ control: 'number' },
|
|
36
|
+
showDebugger :{ control: 'boolean' },
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const Template = (args) => (
|
|
41
|
+
<div className="grid">
|
|
42
|
+
<EditorInput {...args} />
|
|
43
|
+
<FormDebugger />
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
export const Base = Template.bind({})
|
|
48
|
+
Base.args = {
|
|
49
|
+
name :'instructions',
|
|
50
|
+
label:'Project Instructions',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const Optional = Template.bind({})
|
|
54
|
+
Optional.args = {
|
|
55
|
+
...Base.args,
|
|
56
|
+
optional:true,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// const validate = (value) => {
|
|
60
|
+
// let errorMessage = ''
|
|
61
|
+
// const isValid = value.length > 3
|
|
62
|
+
|
|
63
|
+
// if (!isValid) {
|
|
64
|
+
// errorMessage = 'Value should be greater than 3 characters'
|
|
65
|
+
// }
|
|
66
|
+
// return errorMessage
|
|
67
|
+
// }
|
|
68
|
+
|
|
69
|
+
// export const Validation = Template.bind({})
|
|
70
|
+
// Validation.args = {
|
|
71
|
+
// ...Base.args,
|
|
72
|
+
// validate,
|
|
73
|
+
// }
|
|
74
|
+
|
|
75
|
+
export const Disabled = Template.bind({})
|
|
76
|
+
Disabled.args = {
|
|
77
|
+
...Base.args,
|
|
78
|
+
disabled:true,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const OnSingleRow = Template.bind({})
|
|
82
|
+
OnSingleRow.args = {
|
|
83
|
+
...Base.args,
|
|
84
|
+
labelSpan :4,
|
|
85
|
+
inputSpan :4,
|
|
86
|
+
desktopLabelSpan:4,
|
|
87
|
+
desktopInputSpan:10,
|
|
88
|
+
}
|
|
@@ -40,8 +40,8 @@ const Person = ({
|
|
|
40
40
|
>
|
|
41
41
|
<img className="image v50 mr-v" src={image} alt={`${name}, ${role}`} />
|
|
42
42
|
<div className="details">
|
|
43
|
-
<p className="name">{name}</p>
|
|
44
|
-
<p className="role">{role}</p>
|
|
43
|
+
{name && <p className="name">{name}</p>}
|
|
44
|
+
{ role && <p className="role">{role}</p>}
|
|
45
45
|
</div>
|
|
46
46
|
</div>
|
|
47
47
|
)
|
|
@@ -69,11 +69,11 @@ Person.propTypes = {
|
|
|
69
69
|
/**
|
|
70
70
|
* The person's name
|
|
71
71
|
*/
|
|
72
|
-
name :PropTypes.string
|
|
72
|
+
name :PropTypes.string,
|
|
73
73
|
/**
|
|
74
74
|
* The person's role
|
|
75
75
|
*/
|
|
76
|
-
role :PropTypes.string
|
|
76
|
+
role :PropTypes.string,
|
|
77
77
|
/**
|
|
78
78
|
* The base color of the person's component
|
|
79
79
|
*/
|
|
@@ -11,7 +11,9 @@ $default-padding:var(--u);
|
|
|
11
11
|
$default-horizontal-margin: .5em;
|
|
12
12
|
|
|
13
13
|
$default-grid-gap: 1em;
|
|
14
|
-
$default-
|
|
14
|
+
$default-margin: 1.5rem;
|
|
15
|
+
$default-image-margin: var(--default-image-margin, #{$default-margin});
|
|
16
|
+
$default-color: var(--default-color, var(--grey));
|
|
15
17
|
|
|
16
18
|
.#{bem.$base}.people {
|
|
17
19
|
display: grid;
|
|
@@ -29,18 +31,22 @@ $default-image-padding: 1.5rem;
|
|
|
29
31
|
.image {
|
|
30
32
|
border-radius: $default-border-radius;
|
|
31
33
|
height: var(--image-size);
|
|
32
|
-
margin-right: $default-image-
|
|
34
|
+
margin-right: $default-image-margin;
|
|
33
35
|
width: var(--image-size);
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
.details {
|
|
37
39
|
align-items: flex-start;
|
|
38
|
-
color:
|
|
40
|
+
color: $default-color;
|
|
39
41
|
display: flex;
|
|
40
42
|
flex-direction: column;
|
|
41
43
|
|
|
44
|
+
p:only-child {
|
|
45
|
+
margin: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
.name {
|
|
43
|
-
color:
|
|
49
|
+
color: $default-color;
|
|
44
50
|
margin-bottom: .1em;
|
|
45
51
|
}
|
|
46
52
|
|
|
@@ -28,7 +28,7 @@ const Group = ({
|
|
|
28
28
|
import('./styles.scss')
|
|
29
29
|
}, [])
|
|
30
30
|
const Wrapper = type === 'snap-scroller' ? SnapScroller : 'div'
|
|
31
|
-
const wrapperProps = type === 'snap-scroller' ? { noScrollOnDesktop: true } : {}
|
|
31
|
+
const wrapperProps = (type === 'snap-scroller' && desktopType !== 'snap-scroller') ? { noScrollOnDesktop: true } : {}
|
|
32
32
|
|
|
33
33
|
return (
|
|
34
34
|
<Wrapper
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/* @pareto-engineering/generator-front 1.0.12 */
|
|
2
|
+
/* eslint-disable import/no-extraneous-dependencies -- required here */
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { useInsertionEffect, memo } from 'react'
|
|
5
|
+
import { useFormikContext } from 'formik'
|
|
6
|
+
import { LexicalComposer } from '@lexical/react/LexicalComposer'
|
|
7
|
+
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
|
|
8
|
+
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
|
|
9
|
+
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'
|
|
10
|
+
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'
|
|
11
|
+
import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin'
|
|
12
|
+
import { ListPlugin } from '@lexical/react/LexicalListPlugin'
|
|
13
|
+
import { TabIndentationPlugin } from '@lexical/react/LexicalTabIndentationPlugin'
|
|
14
|
+
import { AutoLinkNode, LinkNode } from '@lexical/link'
|
|
15
|
+
import { ListItemNode, ListNode } from '@lexical/list'
|
|
16
|
+
|
|
17
|
+
import PropTypes from 'prop-types'
|
|
18
|
+
import styleNames from '@pareto-engineering/bem/exports'
|
|
19
|
+
|
|
20
|
+
// Local Definitions
|
|
21
|
+
|
|
22
|
+
import { FormLabel, FormDescription, InputWrapper } from '../../common'
|
|
23
|
+
import { Toolbar, TreeViewPlugin } from './common'
|
|
24
|
+
|
|
25
|
+
const baseClassName = styleNames.base
|
|
26
|
+
const componentClassName = 'editor-input'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* This is the component description.
|
|
30
|
+
*/
|
|
31
|
+
const EditorInput = ({
|
|
32
|
+
id,
|
|
33
|
+
className:userClassName,
|
|
34
|
+
style,
|
|
35
|
+
name,
|
|
36
|
+
label,
|
|
37
|
+
// validate,
|
|
38
|
+
resize,
|
|
39
|
+
color,
|
|
40
|
+
rows,
|
|
41
|
+
optional,
|
|
42
|
+
labelColor,
|
|
43
|
+
description,
|
|
44
|
+
disabled,
|
|
45
|
+
labelSpan,
|
|
46
|
+
desktopLabelSpan,
|
|
47
|
+
inputSpan,
|
|
48
|
+
desktopInputSpan,
|
|
49
|
+
showDebugger,
|
|
50
|
+
// ...otherProps
|
|
51
|
+
}) => {
|
|
52
|
+
useInsertionEffect(() => {
|
|
53
|
+
import('./styles.scss')
|
|
54
|
+
}, [])
|
|
55
|
+
|
|
56
|
+
const formik = useFormikContext()
|
|
57
|
+
|
|
58
|
+
const setInitialValue = () => {
|
|
59
|
+
const value = formik.values[name]
|
|
60
|
+
return value || undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const onChange = (state) => {
|
|
64
|
+
formik.setValues({
|
|
65
|
+
...formik.values,
|
|
66
|
+
[name]:JSON.stringify(state),
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const initialConfig = {
|
|
71
|
+
nameSpace :name,
|
|
72
|
+
editable :!disabled,
|
|
73
|
+
editorState:setInitialValue(),
|
|
74
|
+
theme :{
|
|
75
|
+
text:{
|
|
76
|
+
italic :'italic',
|
|
77
|
+
strikethrough:'strikethrough',
|
|
78
|
+
underline :'underlined',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
nodes:[
|
|
82
|
+
AutoLinkNode,
|
|
83
|
+
LinkNode,
|
|
84
|
+
ListNode,
|
|
85
|
+
ListItemNode,
|
|
86
|
+
],
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<LexicalComposer
|
|
91
|
+
initialConfig={initialConfig}
|
|
92
|
+
>
|
|
93
|
+
<FormLabel
|
|
94
|
+
name={name}
|
|
95
|
+
color={labelColor}
|
|
96
|
+
optional={optional}
|
|
97
|
+
columnSpan={labelSpan}
|
|
98
|
+
desktopColumnSpan={desktopLabelSpan}
|
|
99
|
+
// {...otherProps}
|
|
100
|
+
>
|
|
101
|
+
{label}
|
|
102
|
+
</FormLabel>
|
|
103
|
+
<InputWrapper
|
|
104
|
+
id={id}
|
|
105
|
+
className={[
|
|
106
|
+
baseClassName,
|
|
107
|
+
componentClassName,
|
|
108
|
+
userClassName,
|
|
109
|
+
`y-${color}`,
|
|
110
|
+
disabled && 'disabled',
|
|
111
|
+
]
|
|
112
|
+
.filter((e) => e)
|
|
113
|
+
.join(' ')}
|
|
114
|
+
style={{
|
|
115
|
+
...style,
|
|
116
|
+
'--resize':resize,
|
|
117
|
+
'--rows' :`${rows}em`,
|
|
118
|
+
}}
|
|
119
|
+
columnSpan={inputSpan}
|
|
120
|
+
desktopColumnSpan={desktopInputSpan}
|
|
121
|
+
>
|
|
122
|
+
{ !disabled && <Toolbar /> }
|
|
123
|
+
<RichTextPlugin
|
|
124
|
+
contentEditable={(
|
|
125
|
+
<ContentEditable
|
|
126
|
+
id={name}
|
|
127
|
+
className="content-editable"
|
|
128
|
+
/>
|
|
129
|
+
)}
|
|
130
|
+
/>
|
|
131
|
+
<OnChangePlugin onChange={onChange} />
|
|
132
|
+
<LinkPlugin />
|
|
133
|
+
<ListPlugin />
|
|
134
|
+
<TabIndentationPlugin />
|
|
135
|
+
<HistoryPlugin />
|
|
136
|
+
<FormDescription className="s-1" description={description} name={name} />
|
|
137
|
+
{ showDebugger && <TreeViewPlugin />}
|
|
138
|
+
</InputWrapper>
|
|
139
|
+
</LexicalComposer>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
EditorInput.propTypes = {
|
|
144
|
+
/**
|
|
145
|
+
* The HTML id for this element
|
|
146
|
+
*/
|
|
147
|
+
id:PropTypes.string,
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The HTML class names for this element
|
|
151
|
+
*/
|
|
152
|
+
className:PropTypes.string,
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The React-written, css properties for this element.
|
|
156
|
+
*/
|
|
157
|
+
style:PropTypes.objectOf(PropTypes.string),
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The input name (html - and Formik state)
|
|
161
|
+
*/
|
|
162
|
+
name:PropTypes.string.isRequired,
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The input label
|
|
166
|
+
*/
|
|
167
|
+
label:PropTypes.string.isRequired,
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The input value validator function
|
|
171
|
+
*/
|
|
172
|
+
// validate:PropTypes.func,
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The number of rows int the text area
|
|
176
|
+
*/
|
|
177
|
+
rows:PropTypes.number,
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Color of the text
|
|
181
|
+
*/
|
|
182
|
+
color:PropTypes.string,
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Label base color
|
|
186
|
+
*/
|
|
187
|
+
labelColor:PropTypes.string,
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Text area description
|
|
191
|
+
*/
|
|
192
|
+
description:PropTypes.string,
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Whether the text area should be disabled
|
|
196
|
+
*/
|
|
197
|
+
disabled:PropTypes.bool,
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Whether the input is optional or not
|
|
201
|
+
*/
|
|
202
|
+
optional:PropTypes.bool,
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The number of columns the label should span
|
|
206
|
+
*/
|
|
207
|
+
labelSpan:PropTypes.number,
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The number of columns the input should span
|
|
211
|
+
*/
|
|
212
|
+
inputSpan:PropTypes.number,
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The number of columns the label should span on desktop
|
|
216
|
+
*/
|
|
217
|
+
desktopLabelSpan:PropTypes.number,
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The number of columns the input should span on desktop
|
|
221
|
+
*/
|
|
222
|
+
desktopInputSpan:PropTypes.number,
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* The resize property of the text area
|
|
226
|
+
*/
|
|
227
|
+
resize:PropTypes.oneOf(['none', 'both', 'horizontal', 'vertical']),
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
EditorInput.defaultProps = {
|
|
231
|
+
rows :10,
|
|
232
|
+
disabled:false,
|
|
233
|
+
color :'paragraph',
|
|
234
|
+
resize :'vertical',
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export default memo(EditorInput)
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/* eslint-disable import/no-extraneous-dependencies -- required here */
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
|
|
4
|
+
import { useEffect, useState, useCallback } from 'react'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
$getSelection,
|
|
8
|
+
$isRangeSelection,
|
|
9
|
+
FORMAT_TEXT_COMMAND,
|
|
10
|
+
FORMAT_ELEMENT_COMMAND,
|
|
11
|
+
UNDO_COMMAND,
|
|
12
|
+
REDO_COMMAND,
|
|
13
|
+
} from 'lexical'
|
|
14
|
+
import {
|
|
15
|
+
INSERT_ORDERED_LIST_COMMAND,
|
|
16
|
+
INSERT_UNORDERED_LIST_COMMAND,
|
|
17
|
+
REMOVE_LIST_COMMAND,
|
|
18
|
+
$isListNode,
|
|
19
|
+
ListNode,
|
|
20
|
+
} from '@lexical/list'
|
|
21
|
+
import {
|
|
22
|
+
$isAtNodeEnd,
|
|
23
|
+
} from '@lexical/selection'
|
|
24
|
+
import {
|
|
25
|
+
$isLinkNode,
|
|
26
|
+
TOGGLE_LINK_COMMAND,
|
|
27
|
+
} from '@lexical/link'
|
|
28
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
|
29
|
+
import { mergeRegister, $getNearestNodeOfType } from '@lexical/utils'
|
|
30
|
+
import styleNames from '@pareto-engineering/bem/exports'
|
|
31
|
+
|
|
32
|
+
const baseClassName = styleNames.base
|
|
33
|
+
|
|
34
|
+
const componentClassName = 'toolbar'
|
|
35
|
+
|
|
36
|
+
const getSelectedNode = (selection) => {
|
|
37
|
+
const { anchor, focus } = selection
|
|
38
|
+
const anchorNode = selection.anchor.getNode()
|
|
39
|
+
const focusNode = selection.focus.getNode()
|
|
40
|
+
if (anchorNode === focusNode) {
|
|
41
|
+
return anchorNode
|
|
42
|
+
}
|
|
43
|
+
const isBackward = selection.isBackward()
|
|
44
|
+
if (isBackward) {
|
|
45
|
+
return $isAtNodeEnd(focus) ? anchorNode : focusNode
|
|
46
|
+
}
|
|
47
|
+
return $isAtNodeEnd(anchor) ? focusNode : anchorNode
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const Toolbar = () => {
|
|
51
|
+
const [editor] = useLexicalComposerContext()
|
|
52
|
+
const [isBold, setIsBold] = useState(false)
|
|
53
|
+
const [isItalic, setIsItalic] = useState(false)
|
|
54
|
+
const [isStrikethrough, setIsStrikethrough] = useState(false)
|
|
55
|
+
const [blockType, setBlockType] = useState('paragraph')
|
|
56
|
+
const [isLink, setIsLink] = useState(false)
|
|
57
|
+
const [isUnderline, setIsUnderline] = useState(false)
|
|
58
|
+
|
|
59
|
+
const formatBulletList = () => {
|
|
60
|
+
if (blockType !== 'ul') {
|
|
61
|
+
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND)
|
|
62
|
+
} else {
|
|
63
|
+
editor.dispatchCommand(REMOVE_LIST_COMMAND)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const formatNumberedList = () => {
|
|
68
|
+
if (blockType !== 'ol') {
|
|
69
|
+
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND)
|
|
70
|
+
} else {
|
|
71
|
+
editor.dispatchCommand(REMOVE_LIST_COMMAND)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const formatLink = useCallback(() => {
|
|
76
|
+
if (!isLink) {
|
|
77
|
+
// eslint-disable-next-line no-alert
|
|
78
|
+
const path = prompt('Enter the full URL. Ex: https://www.example.com')
|
|
79
|
+
editor.dispatchCommand(TOGGLE_LINK_COMMAND, path)
|
|
80
|
+
} else {
|
|
81
|
+
editor.dispatchCommand(TOGGLE_LINK_COMMAND, null)
|
|
82
|
+
}
|
|
83
|
+
}, [editor, isLink])
|
|
84
|
+
|
|
85
|
+
const updateToolbar = useCallback(() => {
|
|
86
|
+
const selection = $getSelection()
|
|
87
|
+
|
|
88
|
+
// Check list selection
|
|
89
|
+
if ($isRangeSelection(selection)) {
|
|
90
|
+
const anchorNode = selection.anchor.getNode()
|
|
91
|
+
const element = anchorNode.getKey() === 'root'
|
|
92
|
+
? anchorNode
|
|
93
|
+
: anchorNode.getTopLevelElementOrThrow()
|
|
94
|
+
if ($isListNode(element)) {
|
|
95
|
+
const parentList = $getNearestNodeOfType(anchorNode, ListNode)
|
|
96
|
+
const type = parentList ? parentList.getTag() : element.getTag()
|
|
97
|
+
setBlockType(type)
|
|
98
|
+
} else {
|
|
99
|
+
setBlockType(element)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check selection text styles
|
|
103
|
+
setIsBold(selection.hasFormat('bold'))
|
|
104
|
+
setIsItalic(selection.hasFormat('italic'))
|
|
105
|
+
setIsStrikethrough(selection.hasFormat('strikethrough'))
|
|
106
|
+
setIsUnderline(selection.hasFormat('underline'))
|
|
107
|
+
setIsLink(selection.hasFormat('link'))
|
|
108
|
+
|
|
109
|
+
// Check links
|
|
110
|
+
const node = getSelectedNode(selection)
|
|
111
|
+
const parent = node.getParent()
|
|
112
|
+
if ($isLinkNode(parent) || $isLinkNode(node)) {
|
|
113
|
+
setIsLink(true)
|
|
114
|
+
} else {
|
|
115
|
+
setIsLink(false)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}, [editor])
|
|
119
|
+
|
|
120
|
+
useEffect(() => mergeRegister(
|
|
121
|
+
editor.registerUpdateListener(({ editorState }) => {
|
|
122
|
+
editorState.read(() => {
|
|
123
|
+
updateToolbar()
|
|
124
|
+
})
|
|
125
|
+
}),
|
|
126
|
+
), [updateToolbar, editor])
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div className={`${baseClassName} ${componentClassName}`}>
|
|
130
|
+
<div className="group">
|
|
131
|
+
<button
|
|
132
|
+
type="button"
|
|
133
|
+
className={isBold ? 'active' : undefined}
|
|
134
|
+
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold')}
|
|
135
|
+
>
|
|
136
|
+
<span className="icon">
|
|
137
|
+
|
|
|
138
|
+
</span>
|
|
139
|
+
</button>
|
|
140
|
+
<button
|
|
141
|
+
type="button"
|
|
142
|
+
className={isItalic ? 'active' : undefined}
|
|
143
|
+
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic')}
|
|
144
|
+
>
|
|
145
|
+
<span className="icon">
|
|
146
|
+
}
|
|
147
|
+
</span>
|
|
148
|
+
</button>
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
className={isUnderline ? 'active' : undefined}
|
|
152
|
+
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline')}
|
|
153
|
+
>
|
|
154
|
+
<span className="icon">
|
|
155
|
+
~
|
|
156
|
+
</span>
|
|
157
|
+
</button>
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
className={isStrikethrough ? 'active' : undefined}
|
|
161
|
+
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough')}
|
|
162
|
+
>
|
|
163
|
+
<span className="icon">
|
|
164
|
+
?
|
|
165
|
+
</span>
|
|
166
|
+
</button>
|
|
167
|
+
<button
|
|
168
|
+
type="button"
|
|
169
|
+
className={isLink ? 'active' : undefined}
|
|
170
|
+
onClick={() => formatLink()}
|
|
171
|
+
>
|
|
172
|
+
<span className="icon">
|
|
173
|
+
]
|
|
174
|
+
</span>
|
|
175
|
+
</button>
|
|
176
|
+
<button
|
|
177
|
+
type="button"
|
|
178
|
+
className={blockType === 'ul' ? 'active' : undefined}
|
|
179
|
+
onClick={() => formatBulletList()}
|
|
180
|
+
>
|
|
181
|
+
<span className="icon">
|
|
182
|
+
.
|
|
183
|
+
</span>
|
|
184
|
+
</button>
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
className={blockType === 'ol' ? 'active' : undefined}
|
|
188
|
+
onClick={() => formatNumberedList()}
|
|
189
|
+
>
|
|
190
|
+
<span className="icon">
|
|
191
|
+
-
|
|
192
|
+
</span>
|
|
193
|
+
</button>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<div className="group">
|
|
197
|
+
<button
|
|
198
|
+
type="button"
|
|
199
|
+
onClick={() => editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'left')}
|
|
200
|
+
>
|
|
201
|
+
<span className="icon">
|
|
202
|
+
^
|
|
203
|
+
</span>
|
|
204
|
+
</button>
|
|
205
|
+
<button
|
|
206
|
+
type="button"
|
|
207
|
+
onClick={() => editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'center')}
|
|
208
|
+
>
|
|
209
|
+
<span className="icon">
|
|
210
|
+
_
|
|
211
|
+
</span>
|
|
212
|
+
</button>
|
|
213
|
+
<button
|
|
214
|
+
type="button"
|
|
215
|
+
onClick={() => editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'right')}
|
|
216
|
+
>
|
|
217
|
+
<span className="icon">
|
|
218
|
+
`
|
|
219
|
+
</span>
|
|
220
|
+
</button>
|
|
221
|
+
<button
|
|
222
|
+
type="button"
|
|
223
|
+
onClick={() => editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'justify')}
|
|
224
|
+
>
|
|
225
|
+
<span className="icon">
|
|
226
|
+
{
|
|
227
|
+
</span>
|
|
228
|
+
</button>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<div className="group">
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
onClick={() => editor.dispatchCommand(UNDO_COMMAND)}
|
|
235
|
+
>
|
|
236
|
+
<span className="icon">
|
|
237
|
+
\
|
|
238
|
+
</span>
|
|
239
|
+
</button>
|
|
240
|
+
<button
|
|
241
|
+
type="button"
|
|
242
|
+
className="flip"
|
|
243
|
+
onClick={() => editor.dispatchCommand(REDO_COMMAND)}
|
|
244
|
+
>
|
|
245
|
+
<span className="icon">
|
|
246
|
+
\
|
|
247
|
+
</span>
|
|
248
|
+
</button>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export default Toolbar
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
|
3
|
+
import { TreeView } from '@lexical/react/LexicalTreeView'
|
|
4
|
+
|
|
5
|
+
const TreeViewPlugin = () => {
|
|
6
|
+
const [editor] = useLexicalComposerContext()
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<TreeView
|
|
10
|
+
viewClassName="tree-view-output"
|
|
11
|
+
editor={editor}
|
|
12
|
+
/>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default TreeViewPlugin
|