innoboxrr-react-form-elements 3.4.0 → 3.5.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 +2 -2
- package/src/CheckboxInputComponent.jsx +2 -1
- package/src/CodeInputComponent.jsx +4 -2
- package/src/CountrySelectInputComponent.jsx +1 -1
- package/src/DynamicGroupInputComponent.jsx +56 -50
- package/src/FileDropInputComponent.jsx +12 -2
- package/src/FileInputComponent.jsx +2 -2
- package/src/FqsInputComponent.jsx +1 -1
- package/src/RadioInputComponent.jsx +2 -1
- package/src/SingleCheckboxInputComponent.jsx +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "innoboxrr-react-form-elements",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "Gemelo React de innoboxrr-form-elements: los mismos componentes, los mismos nombres y el mismo contrato, con diálogos, drawers, menús, avisos y paleta de comandos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@tinymce/tinymce-react": "^6.3.0",
|
|
54
54
|
"@uiw/react-codemirror": "^4.25.11",
|
|
55
55
|
"@yaireo/tagify": "^4.38.0",
|
|
56
|
-
"innoboxrr-form-core": "^2.
|
|
56
|
+
"innoboxrr-form-core": "^2.7.0",
|
|
57
57
|
"innoboxrr-maskjs": "^2.0.0",
|
|
58
58
|
"react-colorful": "^5.8.1",
|
|
59
59
|
"react-phone-number-input": "^3.4.18",
|
|
@@ -21,13 +21,14 @@ export default function CheckboxInputComponent({
|
|
|
21
21
|
}) {
|
|
22
22
|
const [current, set] = useControlled(value, onChange, '')
|
|
23
23
|
const boxClass = useThemeClass('checkbox', customClass)
|
|
24
|
+
const labelClass = useThemeClass('label')
|
|
24
25
|
|
|
25
26
|
const isGroup = Array.isArray(current)
|
|
26
27
|
const checked = isGroup ? current.includes(val) : Boolean(current)
|
|
27
28
|
|
|
28
29
|
return (
|
|
29
30
|
<div className="fe-mb">
|
|
30
|
-
<label className=
|
|
31
|
+
<label className={labelClass}>
|
|
31
32
|
<input
|
|
32
33
|
className={boxClass}
|
|
33
34
|
type="checkbox"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useRef, useState } from 'react'
|
|
2
2
|
import useControlled from './internal/useControlled.js'
|
|
3
|
+
import useTheme, { joinClasses } from './internal/useTheme.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Gemelo de CodeInputComponent.vue: las casillas de un código de un solo uso.
|
|
@@ -20,6 +21,7 @@ export default function CodeInputComponent({
|
|
|
20
21
|
onChange,
|
|
21
22
|
onComplete,
|
|
22
23
|
}) {
|
|
24
|
+
const theme = useTheme()
|
|
23
25
|
const [code, setCode] = useControlled(value, onChange, '')
|
|
24
26
|
const inputs = useRef([])
|
|
25
27
|
const [chars, setChars] = useState(() => Array.from({ length: fields }, (_, i) => (code ?? '')[i] ?? ''))
|
|
@@ -45,12 +47,12 @@ export default function CodeInputComponent({
|
|
|
45
47
|
<div className={['code-input-container', className].filter(Boolean).join(' ')}>
|
|
46
48
|
{title ? <p className="title">{title}</p> : null}
|
|
47
49
|
|
|
48
|
-
<div className=
|
|
50
|
+
<div className={joinClasses('code-input', theme.codeInput)}>
|
|
49
51
|
{chars.map((char, index) => (
|
|
50
52
|
<input
|
|
51
53
|
key={index}
|
|
52
54
|
ref={(element) => { inputs.current[index] = element }}
|
|
53
|
-
className=
|
|
55
|
+
className={theme.codeCell}
|
|
54
56
|
type="text"
|
|
55
57
|
inputMode="numeric"
|
|
56
58
|
pattern="[0-9]*"
|
|
@@ -21,6 +21,7 @@ import SelectInputComponent from './SelectInputComponent.jsx'
|
|
|
21
21
|
import TextInputComponent from './TextInputComponent.jsx'
|
|
22
22
|
import TextareaInputComponent from './TextareaInputComponent.jsx'
|
|
23
23
|
import useControlled from './internal/useControlled.js'
|
|
24
|
+
import useTheme, { joinClasses } from './internal/useTheme.js'
|
|
24
25
|
|
|
25
26
|
const COMPONENTS = {
|
|
26
27
|
text: TextInputComponent,
|
|
@@ -38,15 +39,20 @@ const COMPONENTS = {
|
|
|
38
39
|
* por su cuenta y trae ordenación **por teclado**, que ni SortableJS ni la API
|
|
39
40
|
* nativa de arrastre dan. Aquí no es un detalle: un formulario que solo se
|
|
40
41
|
* puede reordenar con el ratón no es accesible.
|
|
42
|
+
*
|
|
43
|
+
* El aspecto sale del tema, con el mismo marcado que la rama Vue: cada grupo es
|
|
44
|
+
* una superficie con su barra. Antes eran clases de Tailwind y colores escritos
|
|
45
|
+
* para el modo oscuro que el paquete no declara.
|
|
41
46
|
*/
|
|
42
47
|
function SortableGroup({ id, children }) {
|
|
48
|
+
const theme = useTheme()
|
|
43
49
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id })
|
|
44
50
|
|
|
45
51
|
return (
|
|
46
52
|
<div
|
|
47
53
|
ref={setNodeRef}
|
|
48
54
|
style={{ transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1 }}
|
|
49
|
-
className=
|
|
55
|
+
className={theme.surface}>
|
|
50
56
|
{children({ attributes, listeners })}
|
|
51
57
|
</div>
|
|
52
58
|
)
|
|
@@ -61,6 +67,7 @@ export default function DynamicGroupInputComponent({
|
|
|
61
67
|
removeButtonLabel = 'Eliminar',
|
|
62
68
|
itemLabel = 'Item',
|
|
63
69
|
}) {
|
|
70
|
+
const theme = useTheme()
|
|
64
71
|
const [current, set] = useControlled(value, onChange, [])
|
|
65
72
|
const nextKey = useRef(0)
|
|
66
73
|
|
|
@@ -113,67 +120,66 @@ export default function DynamicGroupInputComponent({
|
|
|
113
120
|
return (
|
|
114
121
|
<div>
|
|
115
122
|
{label ? (
|
|
116
|
-
<label className=
|
|
123
|
+
<label className={theme.label}>
|
|
117
124
|
{label}
|
|
118
125
|
</label>
|
|
119
126
|
) : null}
|
|
120
127
|
|
|
121
128
|
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
|
|
122
129
|
<SortableContext items={keyed.map((group) => group.__key)} strategy={verticalListSortingStrategy}>
|
|
123
|
-
<div className=
|
|
130
|
+
<div className={theme.group}>
|
|
124
131
|
{keyed.map((group, index) => (
|
|
125
132
|
<SortableGroup key={group.__key} id={group.__key}>
|
|
126
133
|
{({ attributes, listeners }) => (
|
|
127
134
|
<>
|
|
128
|
-
<div className=
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
</div>
|
|
135
|
+
<div className={theme.toolbar}>
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
aria-label={`Mover ${itemLabel} ${index + 1}`}
|
|
139
|
+
className={joinClasses(theme.iconButton, theme.dragHandle, 'drag-handle')}
|
|
140
|
+
{...attributes}
|
|
141
|
+
{...listeners}>
|
|
142
|
+
<IconComponent name="drag" />
|
|
143
|
+
</button>
|
|
144
|
+
|
|
145
|
+
<h4 className={theme.groupTitle}>
|
|
146
|
+
{itemLabel} #{index + 1}
|
|
147
|
+
</h4>
|
|
148
|
+
|
|
149
|
+
<span className={theme.toolbarSpacer} />
|
|
150
|
+
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
aria-label={`Duplicar ${itemLabel} ${index + 1}`}
|
|
154
|
+
className={theme.iconButton}
|
|
155
|
+
onClick={() => set([
|
|
156
|
+
...keyed.slice(0, index + 1),
|
|
157
|
+
{ ...group, __key: `grupo-${nextKey.current++}` },
|
|
158
|
+
...keyed.slice(index + 1),
|
|
159
|
+
])}>
|
|
160
|
+
<IconComponent name="copy" />
|
|
161
|
+
</button>
|
|
162
|
+
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
aria-label={`${removeButtonLabel} ${index + 1}`}
|
|
166
|
+
className={joinClasses(theme.iconButton, theme.iconButtonDanger)}
|
|
167
|
+
onClick={() => set(keyed.filter((_, position) => position !== index))}>
|
|
168
|
+
<IconComponent name="delete" />
|
|
169
|
+
</button>
|
|
170
|
+
|
|
171
|
+
<button
|
|
172
|
+
type="button"
|
|
173
|
+
aria-label={`Expandir ${itemLabel} ${index + 1}`}
|
|
174
|
+
aria-expanded={! group._collapsed}
|
|
175
|
+
className={theme.iconButton}
|
|
176
|
+
onClick={() => updateAt(index, '_collapsed', ! group._collapsed)}>
|
|
177
|
+
<IconComponent name={group._collapsed ? 'up' : 'down'} />
|
|
178
|
+
</button>
|
|
173
179
|
</div>
|
|
174
180
|
|
|
175
181
|
{! group._collapsed ? (
|
|
176
|
-
<div className="
|
|
182
|
+
<div className="fe-card-body">
|
|
177
183
|
{inputsConfig.map((field) => {
|
|
178
184
|
const Component = COMPONENTS[field.type] ?? TextInputComponent
|
|
179
185
|
|
|
@@ -208,7 +214,7 @@ export default function DynamicGroupInputComponent({
|
|
|
208
214
|
|
|
209
215
|
<button
|
|
210
216
|
type="button"
|
|
211
|
-
className=
|
|
217
|
+
className={joinClasses(theme.button, 'fe-mt')}
|
|
212
218
|
onClick={() => set([...keyed, emptyGroup()])}>
|
|
213
219
|
{addButtonLabel}
|
|
214
220
|
</button>
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { useRef, useState } from 'react'
|
|
2
|
+
import IconComponent from './IconComponent.jsx'
|
|
3
|
+
import useTheme from './internal/useTheme.js'
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Gemelo de FileDropInputComponent.vue. `onFilesChange` recibe un array de
|
|
5
7
|
* File, venga de arrastrar o del diálogo.
|
|
8
|
+
*
|
|
9
|
+
* `fe-file-drop` no lo definía ninguna hoja, así que la zona salía sin borde ni
|
|
10
|
+
* resalte. Ahora sale del tema, con el mismo icono y el mismo texto secundario
|
|
11
|
+
* que la rama Vue.
|
|
6
12
|
*/
|
|
7
13
|
export default function FileDropInputComponent({
|
|
8
14
|
multiple = false,
|
|
@@ -11,18 +17,20 @@ export default function FileDropInputComponent({
|
|
|
11
17
|
subText = 'o haz clic para seleccionar los archivos.',
|
|
12
18
|
onFilesChange,
|
|
13
19
|
}) {
|
|
20
|
+
const theme = useTheme()
|
|
14
21
|
const input = useRef(null)
|
|
15
22
|
const [dragging, setDragging] = useState(false)
|
|
16
23
|
|
|
17
24
|
return (
|
|
18
25
|
<div
|
|
19
|
-
className=
|
|
26
|
+
className={theme.fileDrop}
|
|
20
27
|
data-dragging={dragging}
|
|
21
28
|
role="button"
|
|
22
29
|
tabIndex={0}
|
|
23
30
|
onClick={() => input.current?.click()}
|
|
24
31
|
onKeyDown={(event) => {
|
|
25
32
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
33
|
+
event.preventDefault()
|
|
26
34
|
input.current?.click()
|
|
27
35
|
}
|
|
28
36
|
}}
|
|
@@ -36,8 +44,10 @@ export default function FileDropInputComponent({
|
|
|
36
44
|
setDragging(false)
|
|
37
45
|
onFilesChange?.(Array.from(event.dataTransfer.files))
|
|
38
46
|
}}>
|
|
47
|
+
<IconComponent name="media" size={32} />
|
|
48
|
+
|
|
39
49
|
<p>{mainText}</p>
|
|
40
|
-
<p>{subText}</p>
|
|
50
|
+
<p className={theme.fileDropHint}>{subText}</p>
|
|
41
51
|
|
|
42
52
|
<input
|
|
43
53
|
ref={input}
|
|
@@ -167,14 +167,14 @@ export default function FileInputComponent({
|
|
|
167
167
|
</ul>
|
|
168
168
|
|
|
169
169
|
{errors.length ? (
|
|
170
|
-
<ul className="fe-list
|
|
170
|
+
<ul className="fe-list fe-error">
|
|
171
171
|
{errors.map((entry, index) => (
|
|
172
172
|
<li key={`${entry.name}-error-${index}`}>{entry.name}: {entry.errors?.join(', ')}</li>
|
|
173
173
|
))}
|
|
174
174
|
</ul>
|
|
175
175
|
) : null}
|
|
176
176
|
|
|
177
|
-
{overTotal ? <p className="
|
|
177
|
+
{overTotal ? <p className="fe-error">El tamaño total supera el máximo permitido.</p> : null}
|
|
178
178
|
|
|
179
179
|
{! autoUpload && files.length ? (
|
|
180
180
|
<button type="button" className="fe-button" disabled={uploading || overTotal} onClick={upload}>
|
|
@@ -34,7 +34,7 @@ export default function FqsInputComponent({
|
|
|
34
34
|
|
|
35
35
|
return (
|
|
36
36
|
<div>
|
|
37
|
-
<h4>{text.title}</h4>
|
|
37
|
+
<h4 className="fe-group-title fe-mb-sm">{text.title}</h4>
|
|
38
38
|
|
|
39
39
|
{items.map((item, index) => (
|
|
40
40
|
<div key={index} className="fe-card fe-card-sm fe-card-body fe-mb">
|
|
@@ -17,10 +17,11 @@ export default function RadioInputComponent({
|
|
|
17
17
|
}) {
|
|
18
18
|
const [current, set] = useControlled(value, onChange, '')
|
|
19
19
|
const radioClass = useThemeClass('radio', customClass)
|
|
20
|
+
const labelClass = useThemeClass('label')
|
|
20
21
|
|
|
21
22
|
return (
|
|
22
23
|
<div className="fe-mb">
|
|
23
|
-
<label className=
|
|
24
|
+
<label className={labelClass}>
|
|
24
25
|
<input
|
|
25
26
|
className={radioClass}
|
|
26
27
|
type="radio"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import useThemeClass from './internal/useThemeClass.js'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Gemelo de SingleCheckboxInputComponent.vue.
|
|
3
5
|
*
|
|
@@ -13,10 +15,11 @@ export default function SingleCheckboxInputComponent({
|
|
|
13
15
|
...rest
|
|
14
16
|
}) {
|
|
15
17
|
const htmlId = `${id}_${label}`
|
|
18
|
+
const labelClass = useThemeClass('label')
|
|
16
19
|
|
|
17
20
|
return (
|
|
18
21
|
<div className="fe-mb">
|
|
19
|
-
<label htmlFor={htmlId} className=
|
|
22
|
+
<label htmlFor={htmlId} className={labelClass}>
|
|
20
23
|
<input
|
|
21
24
|
id={htmlId}
|
|
22
25
|
className="fe-checkbox"
|