@instructure/ui-editable 11.7.2-snapshot-23 → 11.7.2-snapshot-24
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 +12 -2
- package/es/InPlaceEdit/v2/index.js +173 -0
- package/es/InPlaceEdit/v2/props.js +26 -0
- package/es/InPlaceEdit/v2/styles.js +55 -0
- package/es/exports/b.js +25 -0
- package/lib/InPlaceEdit/v2/index.js +178 -0
- package/lib/InPlaceEdit/v2/props.js +31 -0
- package/lib/InPlaceEdit/v2/styles.js +61 -0
- package/lib/exports/b.js +19 -0
- package/package.json +24 -24
- package/src/Editable/v1/README.md +5 -3
- package/src/InPlaceEdit/v2/README.md +338 -0
- package/src/InPlaceEdit/v2/index.tsx +205 -0
- package/src/InPlaceEdit/v2/props.ts +146 -0
- package/src/InPlaceEdit/v2/styles.ts +58 -0
- package/src/exports/b.ts +29 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/InPlaceEdit/v2/index.d.ts +61 -0
- package/types/InPlaceEdit/v2/index.d.ts.map +1 -0
- package/types/InPlaceEdit/v2/props.d.ts +103 -0
- package/types/InPlaceEdit/v2/props.d.ts.map +1 -0
- package/types/InPlaceEdit/v2/styles.d.ts +14 -0
- package/types/InPlaceEdit/v2/styles.d.ts.map +1 -0
- package/types/exports/b.d.ts +5 -0
- package/types/exports/b.d.ts.map +1 -0
|
@@ -56,7 +56,7 @@ const Example = (props) => {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
const renderViewer = () => {
|
|
59
|
-
return <
|
|
59
|
+
return <Text>{value}</Text>
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const handleValueChange = (event) => {
|
|
@@ -69,8 +69,10 @@ const Example = (props) => {
|
|
|
69
69
|
|
|
70
70
|
const renderEditor = ({ onBlur, editorRef }) => {
|
|
71
71
|
return (
|
|
72
|
-
<
|
|
73
|
-
|
|
72
|
+
<TextInput
|
|
73
|
+
renderLabel=""
|
|
74
|
+
width="10rem"
|
|
75
|
+
inputRef={editorRef}
|
|
74
76
|
onBlur={onBlur}
|
|
75
77
|
value={value}
|
|
76
78
|
onChange={handleValueChange}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
---
|
|
2
|
+
describes: InPlaceEdit
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
`InPlaceEdit` provides the DOM structure to organize the components that participate
|
|
6
|
+
in in-place editing by calling the `renderViewer` and `renderEditor` _render props_
|
|
7
|
+
in the context of the containing DOM sub-tree. It also takes care of wiring up the
|
|
8
|
+
event handlers required by [`Editable`](Editable) to make the user interactions work.
|
|
9
|
+
|
|
10
|
+
As a controlled component, it is the responsibility of the consumer to maintain some data to make this work.
|
|
11
|
+
Each time `InPlaceEdit` is rendered, you must provide as props:
|
|
12
|
+
|
|
13
|
+
- the current `mode` prop, which defaults to `'view'`. The app will be notified of
|
|
14
|
+
changes in mode via the `onChangeMode` function property.
|
|
15
|
+
- the current `value` prop, which primarily serves to trigger a re-render when the value changes
|
|
16
|
+
|
|
17
|
+
The view component, as returned by the `renderViewer` property can be anything you wish, but
|
|
18
|
+
will typically render some formatted text. It is up to the consumer to provide this component with
|
|
19
|
+
the current value.
|
|
20
|
+
|
|
21
|
+
The edit component, as returned by the `renderEditor` property is used to collect the user's
|
|
22
|
+
input. It is up to the consumer to provide the necessary properties to get the user provided value
|
|
23
|
+
(typically `onChange`), and to provide the current value.
|
|
24
|
+
|
|
25
|
+
Some components, like [Heading](Heading) and [Text](Text) can be made editable by setting their `as` property
|
|
26
|
+
to `input` (and optionally the `type` property). The value of this approach is that the editable version
|
|
27
|
+
will render in the same style as the view, making for a nice user experience. Other transitions,
|
|
28
|
+
`Text` to `Select` or `DateTimeInput` for example, result in a wholesale change in the component's UI, and will alter
|
|
29
|
+
the space it consumes in the app.
|
|
30
|
+
|
|
31
|
+
Use `InPlaceEdit` to edit `Text` using `Text as="input"`. Also demonstrates how you might wish to handle
|
|
32
|
+
the case when the text is empty. Use the checkbox to switch between inline and block layout.
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
---
|
|
36
|
+
type: example
|
|
37
|
+
---
|
|
38
|
+
const Example = (props) => {
|
|
39
|
+
const [mode, setMode] = useState(props.mode || 'view')
|
|
40
|
+
const [value, setValue] = useState('This is some text')
|
|
41
|
+
const [inline, setInline] = useState(true)
|
|
42
|
+
|
|
43
|
+
// You must provide this to Editable to be
|
|
44
|
+
// notified of mode changes
|
|
45
|
+
const handleChangeMode = (mode) => {
|
|
46
|
+
setMode(mode)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// You attach an event handler to your edit component
|
|
50
|
+
// to be notified of value changes from user interactions
|
|
51
|
+
const handleChange = (event) => {
|
|
52
|
+
setValue(event.target.value)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Renders the view component
|
|
56
|
+
// Be sure to give it the current value
|
|
57
|
+
const renderView = () => (
|
|
58
|
+
<Text
|
|
59
|
+
color={value ? 'primary' : 'secondary'}
|
|
60
|
+
weight={value ? 'normal' : 'light'}
|
|
61
|
+
size="large"
|
|
62
|
+
>
|
|
63
|
+
{value || 'Enter some text'}
|
|
64
|
+
</Text>
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
// Renders the edit component.
|
|
68
|
+
// You have to forward the props on, which
|
|
69
|
+
// includes an onBlur property to help manage
|
|
70
|
+
// the mode changes.
|
|
71
|
+
// Be sure to give it the current value
|
|
72
|
+
const renderEdit = ({ onBlur, editorRef }) => (
|
|
73
|
+
<Text
|
|
74
|
+
color="primary"
|
|
75
|
+
size="large"
|
|
76
|
+
as="input"
|
|
77
|
+
type="text"
|
|
78
|
+
value={value}
|
|
79
|
+
onChange={handleChange}
|
|
80
|
+
aria-label="The title"
|
|
81
|
+
onBlur={onBlur}
|
|
82
|
+
elementRef={editorRef}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
// Renders the edit button.
|
|
87
|
+
// Leverage the default implementation provided by InPlaceEdit
|
|
88
|
+
const renderEditButton = (props) => {
|
|
89
|
+
props.label = `Edit title "${value}"`
|
|
90
|
+
return InPlaceEdit.renderDefaultEditButton(props)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const onChangeLayout = (event) => {
|
|
94
|
+
setInline(event.target.checked)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<View as="div">
|
|
99
|
+
<InPlaceEdit
|
|
100
|
+
renderViewer={renderView}
|
|
101
|
+
renderEditor={renderEdit}
|
|
102
|
+
renderEditButton={renderEditButton}
|
|
103
|
+
onChangeMode={handleChangeMode}
|
|
104
|
+
mode={mode}
|
|
105
|
+
value={value}
|
|
106
|
+
inline={inline}
|
|
107
|
+
/>
|
|
108
|
+
<View as="div" margin="small 0">
|
|
109
|
+
<Checkbox label="inline" checked={inline} onChange={onChangeLayout} />
|
|
110
|
+
</View>
|
|
111
|
+
</View>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
render(<Example />)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
A readOnly `InPlaceEdit`
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
---
|
|
122
|
+
type: example
|
|
123
|
+
---
|
|
124
|
+
const Example = (props) => {
|
|
125
|
+
const [mode, setMode] = useState(props.mode || 'view')
|
|
126
|
+
const [value, setValue] = useState('This is some text')
|
|
127
|
+
|
|
128
|
+
// You must provide this to Editable to be
|
|
129
|
+
// notified of mode changes
|
|
130
|
+
const handleChangeMode = (mode) => {
|
|
131
|
+
setMode(mode)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// You attach an event handler to your edit component
|
|
135
|
+
// to be notified of value changes from user interactions
|
|
136
|
+
const handleChange = (event) => {
|
|
137
|
+
setValue(event.target.value)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Renders the view component
|
|
141
|
+
// Be sure to give it the current value
|
|
142
|
+
const renderView = () => <Text size="large">{value}</Text>
|
|
143
|
+
|
|
144
|
+
// Renders the edit component.
|
|
145
|
+
// You have to forward the props on, which
|
|
146
|
+
// includes an onBlur property to help manage
|
|
147
|
+
// the mode changes.
|
|
148
|
+
// Be sure to give it the current value
|
|
149
|
+
const renderEdit = ({ onBlur, editorRef }) => (
|
|
150
|
+
<Text
|
|
151
|
+
size="large"
|
|
152
|
+
as="input"
|
|
153
|
+
type="text"
|
|
154
|
+
value={value}
|
|
155
|
+
onChange={handleChange}
|
|
156
|
+
aria-label="The title"
|
|
157
|
+
onBlur={onBlur}
|
|
158
|
+
elementRef={editorRef}
|
|
159
|
+
/>
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
// Renders the edit button.
|
|
163
|
+
// Leverage the default implementation provided by InPlaceEdit
|
|
164
|
+
const renderEditButton = (props) => {
|
|
165
|
+
props.label = `Edit title "${value}"`
|
|
166
|
+
return InPlaceEdit.renderDefaultEditButton(props)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<InPlaceEdit
|
|
171
|
+
readOnly
|
|
172
|
+
renderViewer={renderView}
|
|
173
|
+
renderEditor={renderEdit}
|
|
174
|
+
renderEditButton={renderEditButton}
|
|
175
|
+
onChangeMode={handleChangeMode}
|
|
176
|
+
mode={mode}
|
|
177
|
+
value={value}
|
|
178
|
+
/>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
render(<Example />)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
To edit end-justified text, wrap `<InPlaceEdit />` in a
|
|
186
|
+
`<View>` component, as follows:
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
---
|
|
190
|
+
type: example
|
|
191
|
+
---
|
|
192
|
+
const Example = (props) => {
|
|
193
|
+
const [mode, setMode] = useState(props.mode || 'view')
|
|
194
|
+
const [value, setValue] = useState('This is some text')
|
|
195
|
+
|
|
196
|
+
// You must provide this to Editable to be
|
|
197
|
+
// notified of mode changes
|
|
198
|
+
const handleChangeMode = (mode) => {
|
|
199
|
+
setMode(mode)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// You attach an event handler to your edit component
|
|
203
|
+
// to be notified of value changes from user interactions
|
|
204
|
+
const handleChange = (event) => {
|
|
205
|
+
setValue(event.target.value)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Renders the view component
|
|
209
|
+
// Be sure to give it the current value
|
|
210
|
+
const renderView = () => <Text size="large">{value}</Text>
|
|
211
|
+
|
|
212
|
+
// Renders the edit component.
|
|
213
|
+
// You have to forward the props on, which
|
|
214
|
+
// includes an onBlur property to help manage
|
|
215
|
+
// the mode changes.
|
|
216
|
+
// Be sure to give it the current value
|
|
217
|
+
const renderEdit = ({ onBlur, editorRef }) => (
|
|
218
|
+
<Text
|
|
219
|
+
size="large"
|
|
220
|
+
as="input"
|
|
221
|
+
type="text"
|
|
222
|
+
value={value}
|
|
223
|
+
onChange={handleChange}
|
|
224
|
+
aria-label="The title"
|
|
225
|
+
onBlur={onBlur}
|
|
226
|
+
elementRef={editorRef}
|
|
227
|
+
/>
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
// Renders the edit button.
|
|
231
|
+
// Leverage the default implementation provided by InPlaceEdit
|
|
232
|
+
const renderEditButton = (props) => {
|
|
233
|
+
props.label = `Edit title "${value}"`
|
|
234
|
+
return InPlaceEdit.renderDefaultEditButton(props)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<View as="div" textAlign="end">
|
|
239
|
+
<InPlaceEdit
|
|
240
|
+
renderViewer={renderView}
|
|
241
|
+
renderEditor={renderEdit}
|
|
242
|
+
renderEditButton={renderEditButton}
|
|
243
|
+
onChangeMode={handleChangeMode}
|
|
244
|
+
mode={mode}
|
|
245
|
+
value={value}
|
|
246
|
+
editButtonPlacement="start"
|
|
247
|
+
/>
|
|
248
|
+
</View>
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
render(<Example />)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Same as the first example, but notifies `Editable`'s `onChange`
|
|
256
|
+
when the user has finished editing and the value has changed.
|
|
257
|
+
|
|
258
|
+
```js
|
|
259
|
+
---
|
|
260
|
+
type: example
|
|
261
|
+
---
|
|
262
|
+
const Example = (props) => {
|
|
263
|
+
const [mode, setMode] = useState(props.mode || 'view')
|
|
264
|
+
const [value, setValue] = useState('Edit me')
|
|
265
|
+
const [onChangeValue, setOnChangeValue] = useState(undefined)
|
|
266
|
+
|
|
267
|
+
// typically provided by the application so it can
|
|
268
|
+
// be notified of value changes when the user is
|
|
269
|
+
// finished editing
|
|
270
|
+
const onChange = (newValue) => {
|
|
271
|
+
setOnChangeValue(newValue)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// You must provide this to Editable to be
|
|
275
|
+
// notified of mode changes
|
|
276
|
+
const handleChangeMode = (mode) => {
|
|
277
|
+
setMode(mode)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// You attach an event handler to your edit component
|
|
281
|
+
// to be notified of value changes from user interactions
|
|
282
|
+
const handleChange = (event) => {
|
|
283
|
+
setValue(event.target.value)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Renders the view component
|
|
287
|
+
// Be sure to give it the current value
|
|
288
|
+
const renderView = () => <Text size="large">{value}</Text>
|
|
289
|
+
|
|
290
|
+
// Renders the edit component.
|
|
291
|
+
// You have to forward the props on, which
|
|
292
|
+
// includes an onBlur property to help manage
|
|
293
|
+
// the mode changes.
|
|
294
|
+
// Be sure to give it the current value
|
|
295
|
+
const renderEdit = ({ onBlur, editorRef }) => (
|
|
296
|
+
<Text
|
|
297
|
+
size="large"
|
|
298
|
+
as="input"
|
|
299
|
+
type="text"
|
|
300
|
+
value={value}
|
|
301
|
+
onChange={handleChange}
|
|
302
|
+
aria-label="The title"
|
|
303
|
+
onBlur={onBlur}
|
|
304
|
+
elementRef={editorRef}
|
|
305
|
+
/>
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
// Renders the edit button.
|
|
309
|
+
// Leverage the default implementation provided by InPlaceEdit
|
|
310
|
+
const renderEditButton = (props) => {
|
|
311
|
+
props.label = `Edit title "${value}"`
|
|
312
|
+
return InPlaceEdit.renderDefaultEditButton(props)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return (
|
|
316
|
+
<View as="div" background="primary">
|
|
317
|
+
<InPlaceEdit
|
|
318
|
+
renderViewer={renderView}
|
|
319
|
+
renderEditor={renderEdit}
|
|
320
|
+
renderEditButton={renderEditButton}
|
|
321
|
+
onChangeMode={handleChangeMode}
|
|
322
|
+
mode={mode}
|
|
323
|
+
value={value}
|
|
324
|
+
onChange={onChange}
|
|
325
|
+
/>
|
|
326
|
+
<div>
|
|
327
|
+
<Text fontStyle="italic">
|
|
328
|
+
{onChangeValue !== undefined
|
|
329
|
+
? `onChange said: ${onChangeValue}`
|
|
330
|
+
: `You haven't edited me yet!`}
|
|
331
|
+
</Text>
|
|
332
|
+
</div>
|
|
333
|
+
</View>
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
render(<Example />)
|
|
338
|
+
```
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The MIT License (MIT)
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2018 - present Instructure, Inc.
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { Component } from 'react'
|
|
26
|
+
|
|
27
|
+
import { Flex } from '@instructure/ui-flex/latest'
|
|
28
|
+
import { IconButton } from '@instructure/ui-buttons/latest'
|
|
29
|
+
import type { IconButtonProps } from '@instructure/ui-buttons/latest'
|
|
30
|
+
import { PencilInstUIIcon } from '@instructure/ui-icons'
|
|
31
|
+
import { logWarn as warn } from '@instructure/console'
|
|
32
|
+
import { createChainedFunction } from '@instructure/ui-utils'
|
|
33
|
+
import { withStyle } from '@instructure/emotion'
|
|
34
|
+
import { View } from '@instructure/ui-view/latest'
|
|
35
|
+
|
|
36
|
+
import { Editable } from '../../Editable/v1'
|
|
37
|
+
import generateStyle from './styles'
|
|
38
|
+
|
|
39
|
+
import { allowedProps } from './props'
|
|
40
|
+
import type { InPlaceEditProps } from './props'
|
|
41
|
+
import type { EditableRenderProps } from '../../Editable/v1/props'
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
---
|
|
45
|
+
category: components
|
|
46
|
+
---
|
|
47
|
+
**/
|
|
48
|
+
@withStyle(generateStyle)
|
|
49
|
+
class InPlaceEdit extends Component<InPlaceEditProps> {
|
|
50
|
+
static readonly componentId = 'InPlaceEdit'
|
|
51
|
+
static allowedProps = allowedProps
|
|
52
|
+
static defaultProps = {
|
|
53
|
+
readOnly: false,
|
|
54
|
+
showFocusRing: true,
|
|
55
|
+
inline: true,
|
|
56
|
+
editButtonPlacement: 'end'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
ref: Element | null = null
|
|
60
|
+
_editButtonRef: HTMLButtonElement | null = null
|
|
61
|
+
|
|
62
|
+
handleRef = (el: Element | null) => {
|
|
63
|
+
this.ref = el
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
constructor(props: InPlaceEditProps) {
|
|
67
|
+
super(props)
|
|
68
|
+
|
|
69
|
+
warn(
|
|
70
|
+
props.readOnly ? props.mode === 'view' : true,
|
|
71
|
+
'[InPlaceEdit] When readOnly is true, mode is forced to "view"'
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
componentDidMount() {
|
|
76
|
+
this.props.makeStyles?.()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
componentDidUpdate() {
|
|
80
|
+
this.props.makeStyles?.()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
handleEditButtonRef = (el: HTMLButtonElement) => {
|
|
84
|
+
this._editButtonRef = el
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
renderEditor({
|
|
88
|
+
mode,
|
|
89
|
+
onBlur,
|
|
90
|
+
editorRef,
|
|
91
|
+
readOnly
|
|
92
|
+
}: ReturnType<EditableRenderProps['getEditorProps']>) {
|
|
93
|
+
const { showFocusRing, renderEditor } = this.props
|
|
94
|
+
const isEditMode = !readOnly && mode === 'edit'
|
|
95
|
+
|
|
96
|
+
return isEditMode ? (
|
|
97
|
+
<View
|
|
98
|
+
as="span"
|
|
99
|
+
display="block"
|
|
100
|
+
withFocusOutline={showFocusRing}
|
|
101
|
+
position="relative"
|
|
102
|
+
css={this.props.styles?.inPlaceEdit}
|
|
103
|
+
borderRadius="medium"
|
|
104
|
+
margin="auto"
|
|
105
|
+
>
|
|
106
|
+
{renderEditor({ onBlur, editorRef })}
|
|
107
|
+
</View>
|
|
108
|
+
) : null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
renderViewer({
|
|
112
|
+
readOnly,
|
|
113
|
+
mode
|
|
114
|
+
}: ReturnType<EditableRenderProps['getViewerProps']>) {
|
|
115
|
+
return readOnly || mode === 'view' ? this.props.renderViewer() : null
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
renderEditButton({
|
|
119
|
+
buttonRef,
|
|
120
|
+
...rest
|
|
121
|
+
}: ReturnType<EditableRenderProps['getEditButtonProps']>) {
|
|
122
|
+
return this.props.renderEditButton({
|
|
123
|
+
elementRef: createChainedFunction(this.handleEditButtonRef, buttonRef),
|
|
124
|
+
...rest
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Render a default edit button, an icon button with the edit icon
|
|
129
|
+
// the margin makes room for the focus ring
|
|
130
|
+
static renderDefaultEditButton = ({
|
|
131
|
+
isVisible,
|
|
132
|
+
readOnly,
|
|
133
|
+
label,
|
|
134
|
+
...buttonProps
|
|
135
|
+
}: {
|
|
136
|
+
isVisible: boolean
|
|
137
|
+
readOnly?: boolean
|
|
138
|
+
label: string
|
|
139
|
+
} & Partial<IconButtonProps>) => {
|
|
140
|
+
if (readOnly) {
|
|
141
|
+
return null
|
|
142
|
+
}
|
|
143
|
+
return (
|
|
144
|
+
<IconButton
|
|
145
|
+
size="small"
|
|
146
|
+
screenReaderLabel={label}
|
|
147
|
+
withBackground={false}
|
|
148
|
+
withBorder={false}
|
|
149
|
+
{...buttonProps}
|
|
150
|
+
>
|
|
151
|
+
{isVisible ? PencilInstUIIcon : null}
|
|
152
|
+
</IconButton>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
renderAll = ({
|
|
157
|
+
getContainerProps,
|
|
158
|
+
getViewerProps,
|
|
159
|
+
getEditorProps,
|
|
160
|
+
getEditButtonProps
|
|
161
|
+
}: EditableRenderProps) => {
|
|
162
|
+
const flexDir =
|
|
163
|
+
this.props.editButtonPlacement === 'start' ? 'row-reverse' : 'row'
|
|
164
|
+
const justifyItems = flexDir === 'row-reverse' ? 'end' : 'start'
|
|
165
|
+
const buttonMargin =
|
|
166
|
+
this.props.editButtonPlacement === 'start'
|
|
167
|
+
? '0 xx-small 0 0'
|
|
168
|
+
: '0 0 0 xx-small'
|
|
169
|
+
return (
|
|
170
|
+
<Flex
|
|
171
|
+
display={this.props.inline ? 'inline-flex' : 'flex'}
|
|
172
|
+
direction={flexDir}
|
|
173
|
+
justifyItems={justifyItems}
|
|
174
|
+
{...getContainerProps()}
|
|
175
|
+
>
|
|
176
|
+
<Flex.Item shouldGrow shouldShrink>
|
|
177
|
+
{this.renderEditor(getEditorProps())}
|
|
178
|
+
{this.renderViewer(getViewerProps())}
|
|
179
|
+
</Flex.Item>
|
|
180
|
+
<Flex.Item margin={buttonMargin}>
|
|
181
|
+
{this.renderEditButton(getEditButtonProps())}
|
|
182
|
+
</Flex.Item>
|
|
183
|
+
</Flex>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
render() {
|
|
188
|
+
const { mode, value, onChange, onChangeMode, readOnly } = this.props
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<Editable
|
|
192
|
+
mode={mode}
|
|
193
|
+
onChangeMode={onChangeMode}
|
|
194
|
+
render={this.renderAll}
|
|
195
|
+
value={value}
|
|
196
|
+
onChange={onChange}
|
|
197
|
+
readOnly={readOnly}
|
|
198
|
+
elementRef={this.handleRef}
|
|
199
|
+
/>
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export default InPlaceEdit
|
|
205
|
+
export { InPlaceEdit }
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The MIT License (MIT)
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2015 - present Instructure, Inc.
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
* copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
* SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { WithStyleProps, ComponentStyle } from '@instructure/emotion'
|
|
26
|
+
import type { EditableProps } from '../../Editable/v1/props'
|
|
27
|
+
import React from 'react'
|
|
28
|
+
|
|
29
|
+
type ExtendedRenderEditButton = {
|
|
30
|
+
elementRef?: (el: HTMLButtonElement) => void
|
|
31
|
+
onClick: () => void
|
|
32
|
+
onFocus: () => void
|
|
33
|
+
onBlur: () => void
|
|
34
|
+
isVisible: boolean
|
|
35
|
+
readOnly?: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type InPlaceEditOwnProps = {
|
|
39
|
+
/**
|
|
40
|
+
* Function to render the view mode component.
|
|
41
|
+
* It is the consumer's responsibility to provide the
|
|
42
|
+
* current value or children.
|
|
43
|
+
*
|
|
44
|
+
* Return value:
|
|
45
|
+
* - element: the viewer DOM sub-tree.
|
|
46
|
+
*/
|
|
47
|
+
renderViewer: () => React.ReactNode
|
|
48
|
+
/**
|
|
49
|
+
* Function to render the edit mode component
|
|
50
|
+
* It is the consumer's responsibility to provide the
|
|
51
|
+
* current value, and to attach the appropriate onChange
|
|
52
|
+
* event handler needed to capture the updated value. This
|
|
53
|
+
* new value must then be forwarded to the view mode component.
|
|
54
|
+
*
|
|
55
|
+
* Return value:
|
|
56
|
+
* - element: the editor DOM sub-tree.
|
|
57
|
+
*/
|
|
58
|
+
renderEditor: (data: {
|
|
59
|
+
onBlur: () => void
|
|
60
|
+
editorRef: (el: HTMLElement | null) => void
|
|
61
|
+
}) => React.ReactNode
|
|
62
|
+
/**
|
|
63
|
+
* Function to render the edit button.
|
|
64
|
+
*
|
|
65
|
+
* Parameters:
|
|
66
|
+
* - object: { isVisible, onClick, onFocus, onBlur, buttonRef }
|
|
67
|
+
*
|
|
68
|
+
* Return value:
|
|
69
|
+
*
|
|
70
|
+
* - element: the edit button DOM sub-tree
|
|
71
|
+
*
|
|
72
|
+
* If you choose to use the default edit button, add `label` to the
|
|
73
|
+
* incoming `props` parameter and call `InPlaceEdit.renderDefaultEditButton(props)`
|
|
74
|
+
*
|
|
75
|
+
* If you choose to render a custom button, attach the on* event handlers
|
|
76
|
+
* and set `buttonRef` as a `ref` type property on the `button` element.
|
|
77
|
+
*
|
|
78
|
+
* `isVisible` is a hint as to whether the button is _typically_ shown,
|
|
79
|
+
* but you're free to ignore it for your use-case.
|
|
80
|
+
*/
|
|
81
|
+
renderEditButton: (props: ExtendedRenderEditButton) => React.ReactNode | null
|
|
82
|
+
/**
|
|
83
|
+
* If `'view'`: the view component is rendered,
|
|
84
|
+
* if `'edit'`: the edit component is rendered
|
|
85
|
+
*/
|
|
86
|
+
mode: 'view' | 'edit'
|
|
87
|
+
/**
|
|
88
|
+
* Called when the component's mode changes
|
|
89
|
+
* Parameter:
|
|
90
|
+
* - newMode: string
|
|
91
|
+
*/
|
|
92
|
+
onChangeMode: EditableProps['onChangeMode']
|
|
93
|
+
/**
|
|
94
|
+
* The current value.
|
|
95
|
+
* The value is managed by the consuming app, but we need to tell InPlaceEdit
|
|
96
|
+
* it's changed or it won't re-render
|
|
97
|
+
*/
|
|
98
|
+
value?: any
|
|
99
|
+
/**
|
|
100
|
+
* Called when Editable switches from edit to view mode and the value has changed.
|
|
101
|
+
* Parameter:
|
|
102
|
+
* - value: any
|
|
103
|
+
*/
|
|
104
|
+
onChange?: EditableProps['onChange']
|
|
105
|
+
/**
|
|
106
|
+
* The mode is fixed as 'view'
|
|
107
|
+
*/
|
|
108
|
+
readOnly?: boolean
|
|
109
|
+
/**
|
|
110
|
+
* Show a focus outline when the input is focused
|
|
111
|
+
*/
|
|
112
|
+
showFocusRing?: boolean
|
|
113
|
+
/**
|
|
114
|
+
* Put the edit button before or after the view
|
|
115
|
+
*/
|
|
116
|
+
editButtonPlacement?: 'start' | 'end'
|
|
117
|
+
/**
|
|
118
|
+
* Render outermost element inline v. block
|
|
119
|
+
*/
|
|
120
|
+
inline?: boolean
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type PropKeys = keyof InPlaceEditOwnProps
|
|
124
|
+
|
|
125
|
+
type AllowedPropKeys = Readonly<Array<PropKeys>>
|
|
126
|
+
|
|
127
|
+
type InPlaceEditProps = InPlaceEditOwnProps &
|
|
128
|
+
WithStyleProps<null, InPlaceEditStyle>
|
|
129
|
+
|
|
130
|
+
type InPlaceEditStyle = ComponentStyle<'inPlaceEdit'>
|
|
131
|
+
const allowedProps: AllowedPropKeys = [
|
|
132
|
+
'renderViewer',
|
|
133
|
+
'renderEditor',
|
|
134
|
+
'renderEditButton',
|
|
135
|
+
'mode',
|
|
136
|
+
'onChangeMode',
|
|
137
|
+
'value',
|
|
138
|
+
'onChange',
|
|
139
|
+
'readOnly',
|
|
140
|
+
'showFocusRing',
|
|
141
|
+
'editButtonPlacement',
|
|
142
|
+
'inline'
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
export type { InPlaceEditProps, InPlaceEditStyle }
|
|
146
|
+
export { allowedProps }
|