@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 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
+ }
@@ -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
+ }
@@ -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'