@dfosco/storyboard-react-reshaped 1.1.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.
- package/package.json +24 -0
- package/src/Checkbox.jsx +57 -0
- package/src/Select.jsx +56 -0
- package/src/StoryboardForm.jsx +51 -0
- package/src/TextInput.jsx +54 -0
- package/src/Textarea.jsx +55 -0
- package/src/index.js +12 -0
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dfosco/storyboard-react-reshaped",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/dfosco/storyboard.git",
|
|
9
|
+
"directory": "packages/react-reshaped"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@dfosco/storyboard-react": "*"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"reshaped": ">=3",
|
|
19
|
+
"react": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./src/index.js"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/Checkbox.jsx
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* eslint-disable react/prop-types */
|
|
2
|
+
import { useContext, useState, useEffect } from 'react'
|
|
3
|
+
import { Checkbox as ReshapedCheckbox } from 'reshaped'
|
|
4
|
+
import { FormContext, useOverride } from '@dfosco/storyboard-react'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Wrapped Reshaped Checkbox that integrates with StoryboardForm.
|
|
8
|
+
*
|
|
9
|
+
* Inside a <StoryboardForm>, values are buffered locally and only
|
|
10
|
+
* written to session on form submit.
|
|
11
|
+
*
|
|
12
|
+
* Stores "true" / "false" as string values in the URL hash.
|
|
13
|
+
*/
|
|
14
|
+
export default function Checkbox({ name, onChange, checked: controlledChecked, ...props }) {
|
|
15
|
+
const form = useContext(FormContext)
|
|
16
|
+
const path = form?.prefix && name ? `${form.prefix}.${name}` : name
|
|
17
|
+
const [sessionValue] = useOverride(path || '')
|
|
18
|
+
|
|
19
|
+
const initialChecked = sessionValue === 'true' || sessionValue === true
|
|
20
|
+
const [draft, setDraftState] = useState(initialChecked)
|
|
21
|
+
|
|
22
|
+
const isConnected = !!form && !!name
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!isConnected) return
|
|
26
|
+
return form.subscribe(name, (val) => setDraftState(val === 'true' || val === true))
|
|
27
|
+
}, [isConnected, form, name])
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (isConnected && sessionValue != null) {
|
|
31
|
+
const val = sessionValue === 'true' || sessionValue === true
|
|
32
|
+
setDraftState(val)
|
|
33
|
+
form.setDraft(name, val ? 'true' : 'false')
|
|
34
|
+
}
|
|
35
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
36
|
+
}, [])
|
|
37
|
+
|
|
38
|
+
const handleChange = ({ value }) => {
|
|
39
|
+
const checked = !!value
|
|
40
|
+
if (isConnected) {
|
|
41
|
+
setDraftState(checked)
|
|
42
|
+
form.setDraft(name, checked ? 'true' : 'false')
|
|
43
|
+
}
|
|
44
|
+
if (onChange) onChange({ value })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const resolvedChecked = isConnected ? draft : controlledChecked
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<ReshapedCheckbox
|
|
51
|
+
name={name}
|
|
52
|
+
checked={resolvedChecked}
|
|
53
|
+
onChange={handleChange}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
package/src/Select.jsx
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* eslint-disable react/prop-types */
|
|
2
|
+
import { useContext, useState, useEffect } from 'react'
|
|
3
|
+
import { Select as ReshapedSelect } from 'reshaped'
|
|
4
|
+
import { FormContext, useOverride } from '@dfosco/storyboard-react'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Wrapped Reshaped Select that integrates with StoryboardForm.
|
|
8
|
+
*
|
|
9
|
+
* Inside a <StoryboardForm>, values are buffered locally and only
|
|
10
|
+
* written to session on form submit.
|
|
11
|
+
*
|
|
12
|
+
* Outside a form, behaves as a normal controlled Reshaped Select.
|
|
13
|
+
*/
|
|
14
|
+
export default function Select({ name, onChange, value: controlledValue, children, ...props }) {
|
|
15
|
+
const form = useContext(FormContext)
|
|
16
|
+
const path = form?.prefix && name ? `${form.prefix}.${name}` : name
|
|
17
|
+
const [sessionValue] = useOverride(path || '')
|
|
18
|
+
|
|
19
|
+
const [draft, setDraftState] = useState(sessionValue ?? '')
|
|
20
|
+
|
|
21
|
+
const isConnected = !!form && !!name
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!isConnected) return
|
|
25
|
+
return form.subscribe(name, (val) => setDraftState(val))
|
|
26
|
+
}, [isConnected, form, name])
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (isConnected && sessionValue != null) {
|
|
30
|
+
setDraftState(sessionValue)
|
|
31
|
+
form.setDraft(name, sessionValue)
|
|
32
|
+
}
|
|
33
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
+
}, [])
|
|
35
|
+
|
|
36
|
+
const handleChange = ({ value }) => {
|
|
37
|
+
if (isConnected) {
|
|
38
|
+
setDraftState(value)
|
|
39
|
+
form.setDraft(name, value)
|
|
40
|
+
}
|
|
41
|
+
if (onChange) onChange({ value })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const resolvedValue = isConnected ? draft : controlledValue
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<ReshapedSelect
|
|
48
|
+
name={name}
|
|
49
|
+
value={resolvedValue}
|
|
50
|
+
onChange={handleChange}
|
|
51
|
+
{...props}
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</ReshapedSelect>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* eslint-disable react/prop-types */
|
|
2
|
+
import { useRef, useCallback } from 'react'
|
|
3
|
+
import { FormContext } from '@dfosco/storyboard-react'
|
|
4
|
+
import { setParam } from '@dfosco/storyboard-core'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A form wrapper that buffers input values locally and only
|
|
8
|
+
* persists them to session (URL hash) on submit.
|
|
9
|
+
*
|
|
10
|
+
* The `data` prop sets the root path — child inputs with a `name` prop
|
|
11
|
+
* will read/write to local draft state while typing, then flush to
|
|
12
|
+
* `data.name` in the URL hash on form submission.
|
|
13
|
+
*/
|
|
14
|
+
export default function StoryboardForm({ data, onSubmit, children, ...props }) {
|
|
15
|
+
const prefix = data || null
|
|
16
|
+
const draftsRef = useRef({})
|
|
17
|
+
const listenersRef = useRef({})
|
|
18
|
+
|
|
19
|
+
const getDraft = useCallback((name) => draftsRef.current[name], [])
|
|
20
|
+
|
|
21
|
+
const setDraft = useCallback((name, value) => {
|
|
22
|
+
draftsRef.current[name] = value
|
|
23
|
+
const listener = listenersRef.current[name]
|
|
24
|
+
if (listener) listener(value)
|
|
25
|
+
}, [])
|
|
26
|
+
|
|
27
|
+
const subscribe = useCallback((name, listener) => {
|
|
28
|
+
listenersRef.current[name] = listener
|
|
29
|
+
return () => { delete listenersRef.current[name] }
|
|
30
|
+
}, [])
|
|
31
|
+
|
|
32
|
+
const handleSubmit = (e) => {
|
|
33
|
+
e.preventDefault()
|
|
34
|
+
if (prefix) {
|
|
35
|
+
for (const [name, value] of Object.entries(draftsRef.current)) {
|
|
36
|
+
setParam(`${prefix}.${name}`, value)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (onSubmit) onSubmit(e)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const contextValue = { prefix, getDraft, setDraft, subscribe }
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<FormContext.Provider value={contextValue}>
|
|
46
|
+
<form {...props} onSubmit={handleSubmit}>
|
|
47
|
+
{children}
|
|
48
|
+
</form>
|
|
49
|
+
</FormContext.Provider>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* eslint-disable react/prop-types */
|
|
2
|
+
import { useContext, useState, useEffect } from 'react'
|
|
3
|
+
import { TextField } from 'reshaped'
|
|
4
|
+
import { FormContext, useOverride } from '@dfosco/storyboard-react'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Wrapped Reshaped TextField that integrates with StoryboardForm.
|
|
8
|
+
*
|
|
9
|
+
* Inside a <StoryboardForm>, values are buffered locally and only
|
|
10
|
+
* written to session on form submit.
|
|
11
|
+
*
|
|
12
|
+
* Outside a form, behaves as a normal controlled Reshaped TextField.
|
|
13
|
+
*/
|
|
14
|
+
export default function TextInput({ name, onChange, value: controlledValue, ...props }) {
|
|
15
|
+
const form = useContext(FormContext)
|
|
16
|
+
const path = form?.prefix && name ? `${form.prefix}.${name}` : name
|
|
17
|
+
const [sessionValue] = useOverride(path || '')
|
|
18
|
+
|
|
19
|
+
const [draft, setDraftState] = useState(sessionValue ?? '')
|
|
20
|
+
|
|
21
|
+
const isConnected = !!form && !!name
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!isConnected) return
|
|
25
|
+
return form.subscribe(name, (val) => setDraftState(val))
|
|
26
|
+
}, [isConnected, form, name])
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (isConnected && sessionValue != null) {
|
|
30
|
+
setDraftState(sessionValue)
|
|
31
|
+
form.setDraft(name, sessionValue)
|
|
32
|
+
}
|
|
33
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
+
}, [])
|
|
35
|
+
|
|
36
|
+
const handleChange = ({ value }) => {
|
|
37
|
+
if (isConnected) {
|
|
38
|
+
setDraftState(value)
|
|
39
|
+
form.setDraft(name, value)
|
|
40
|
+
}
|
|
41
|
+
if (onChange) onChange({ value })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const resolvedValue = isConnected ? draft : controlledValue
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<TextField
|
|
48
|
+
name={name}
|
|
49
|
+
value={resolvedValue}
|
|
50
|
+
onChange={handleChange}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
)
|
|
54
|
+
}
|
package/src/Textarea.jsx
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* eslint-disable react/prop-types */
|
|
2
|
+
import { useContext, useState, useEffect } from 'react'
|
|
3
|
+
import { TextField } from 'reshaped'
|
|
4
|
+
import { FormContext, useOverride } from '@dfosco/storyboard-react'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Wrapped Reshaped TextField (multiline) that integrates with StoryboardForm.
|
|
8
|
+
*
|
|
9
|
+
* Inside a <StoryboardForm>, values are buffered locally and only
|
|
10
|
+
* written to session on form submit.
|
|
11
|
+
*
|
|
12
|
+
* Outside a form, behaves as a normal controlled Reshaped TextField.
|
|
13
|
+
*/
|
|
14
|
+
export default function Textarea({ name, onChange, value: controlledValue, ...props }) {
|
|
15
|
+
const form = useContext(FormContext)
|
|
16
|
+
const path = form?.prefix && name ? `${form.prefix}.${name}` : name
|
|
17
|
+
const [sessionValue] = useOverride(path || '')
|
|
18
|
+
|
|
19
|
+
const [draft, setDraftState] = useState(sessionValue ?? '')
|
|
20
|
+
|
|
21
|
+
const isConnected = !!form && !!name
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!isConnected) return
|
|
25
|
+
return form.subscribe(name, (val) => setDraftState(val))
|
|
26
|
+
}, [isConnected, form, name])
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (isConnected && sessionValue != null) {
|
|
30
|
+
setDraftState(sessionValue)
|
|
31
|
+
form.setDraft(name, sessionValue)
|
|
32
|
+
}
|
|
33
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
+
}, [])
|
|
35
|
+
|
|
36
|
+
const handleChange = ({ value }) => {
|
|
37
|
+
if (isConnected) {
|
|
38
|
+
setDraftState(value)
|
|
39
|
+
form.setDraft(name, value)
|
|
40
|
+
}
|
|
41
|
+
if (onChange) onChange({ value })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const resolvedValue = isConnected ? draft : controlledValue
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<TextField
|
|
48
|
+
name={name}
|
|
49
|
+
value={resolvedValue}
|
|
50
|
+
onChange={handleChange}
|
|
51
|
+
inputAttributes={{ rows: 3 }}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @dfosco/storyboard-react-reshaped — Reshaped design system package for Storyboard.
|
|
3
|
+
*
|
|
4
|
+
* Provides storyboard-aware form components backed by Reshaped.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Storyboard form wrappers (Reshaped-backed)
|
|
8
|
+
export { default as TextInput } from './TextInput.jsx'
|
|
9
|
+
export { default as Select } from './Select.jsx'
|
|
10
|
+
export { default as Checkbox } from './Checkbox.jsx'
|
|
11
|
+
export { default as Textarea } from './Textarea.jsx'
|
|
12
|
+
export { default as StoryboardForm } from './StoryboardForm.jsx'
|