@capyx/components-library 0.0.7 → 0.0.9
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type FC } from 'react';
|
|
2
2
|
/**
|
|
3
3
|
* Props for the TagsInput component
|
|
4
4
|
*/
|
|
@@ -18,9 +18,9 @@ export type TagsInputProps = {
|
|
|
18
18
|
/** Label used in validation error messages (e.g. "Skills is required") */
|
|
19
19
|
label?: string;
|
|
20
20
|
/** Minimum number of tags required */
|
|
21
|
-
|
|
21
|
+
min?: number;
|
|
22
22
|
/** Maximum number of tags allowed */
|
|
23
|
-
|
|
23
|
+
max?: number;
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
26
26
|
* TagsInput - A multi-tag input component using Material-UI Autocomplete
|
|
@@ -31,8 +31,8 @@ export type TagsInputProps = {
|
|
|
31
31
|
*
|
|
32
32
|
* Supports validation via:
|
|
33
33
|
* - **react-hook-form**: When wrapped in a `FormProvider`, the component
|
|
34
|
-
* registers itself with `Controller` and validates `required`, `
|
|
35
|
-
* and `
|
|
34
|
+
* registers itself with `Controller` and validates `required`, `min`,
|
|
35
|
+
* and `max` rules automatically.
|
|
36
36
|
* - **Native HTML5**: A hidden `<input>` element participates in native
|
|
37
37
|
* constraint validation (`required` attribute), making it compatible with
|
|
38
38
|
* any form library that relies on `checkValidity()` / `reportValidity()`.
|
|
@@ -45,8 +45,8 @@ export type TagsInputProps = {
|
|
|
45
45
|
* onChange={setTags}
|
|
46
46
|
* placeholder="Add skills..."
|
|
47
47
|
* required
|
|
48
|
-
*
|
|
49
|
-
*
|
|
48
|
+
* min={1}
|
|
49
|
+
* max={5}
|
|
50
50
|
* />
|
|
51
51
|
* ```
|
|
52
52
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TagsInput.d.ts","sourceRoot":"","sources":["../../lib/components/TagsInput.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"TagsInput.d.ts","sourceRoot":"","sources":["../../lib/components/TagsInput.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,EAAqB,MAAM,OAAO,CAAC;AAGnD;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC5B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,GAAG,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC,cAAc,CA2OxC,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createElement as _createElement } from "react";
|
|
3
|
-
import { Autocomplete, Chip,
|
|
3
|
+
import { Autocomplete, Chip, TextField } from '@mui/material';
|
|
4
|
+
import { useEffect, useRef } from 'react';
|
|
4
5
|
import { Controller, useFormContext } from 'react-hook-form';
|
|
5
6
|
/**
|
|
6
7
|
* TagsInput - A multi-tag input component using Material-UI Autocomplete
|
|
@@ -11,8 +12,8 @@ import { Controller, useFormContext } from 'react-hook-form';
|
|
|
11
12
|
*
|
|
12
13
|
* Supports validation via:
|
|
13
14
|
* - **react-hook-form**: When wrapped in a `FormProvider`, the component
|
|
14
|
-
* registers itself with `Controller` and validates `required`, `
|
|
15
|
-
* and `
|
|
15
|
+
* registers itself with `Controller` and validates `required`, `min`,
|
|
16
|
+
* and `max` rules automatically.
|
|
16
17
|
* - **Native HTML5**: A hidden `<input>` element participates in native
|
|
17
18
|
* constraint validation (`required` attribute), making it compatible with
|
|
18
19
|
* any form library that relies on `checkValidity()` / `reportValidity()`.
|
|
@@ -25,13 +26,35 @@ import { Controller, useFormContext } from 'react-hook-form';
|
|
|
25
26
|
* onChange={setTags}
|
|
26
27
|
* placeholder="Add skills..."
|
|
27
28
|
* required
|
|
28
|
-
*
|
|
29
|
-
*
|
|
29
|
+
* min={1}
|
|
30
|
+
* max={5}
|
|
30
31
|
* />
|
|
31
32
|
* ```
|
|
32
33
|
*/
|
|
33
|
-
export const TagsInput = ({ name, value: valueProp = [], onChange, placeholder = 'Add tags...', disabled = false, required = false, label,
|
|
34
|
+
export const TagsInput = ({ name, value: valueProp = [], onChange, placeholder = 'Add tags...', disabled = false, required = false, label, min, max, }) => {
|
|
34
35
|
const formContext = useFormContext();
|
|
36
|
+
const nativeInputRef = useRef(null);
|
|
37
|
+
const fieldLabel = label || 'This field';
|
|
38
|
+
// Keep the hidden native input's custom validity in sync with the tag count
|
|
39
|
+
// so that form.checkValidity() / form.reportValidity() report the right error.
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const input = nativeInputRef.current;
|
|
42
|
+
if (!input)
|
|
43
|
+
return;
|
|
44
|
+
const count = valueProp.length;
|
|
45
|
+
if (required && count === 0) {
|
|
46
|
+
input.setCustomValidity(`${fieldLabel} is required`);
|
|
47
|
+
}
|
|
48
|
+
else if (min != null && count < min) {
|
|
49
|
+
input.setCustomValidity(`At least ${min} tag${min === 1 ? '' : 's'} required`);
|
|
50
|
+
}
|
|
51
|
+
else if (max != null && count > max) {
|
|
52
|
+
input.setCustomValidity(`No more than ${max} tag${max === 1 ? '' : 's'} allowed`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
input.setCustomValidity('');
|
|
56
|
+
}
|
|
57
|
+
}, [valueProp, required, min, max, fieldLabel]);
|
|
35
58
|
const getFieldError = (fieldName) => {
|
|
36
59
|
try {
|
|
37
60
|
const error = formContext?.formState?.errors?.[fieldName];
|
|
@@ -41,20 +64,19 @@ export const TagsInput = ({ name, value: valueProp = [], onChange, placeholder =
|
|
|
41
64
|
return undefined;
|
|
42
65
|
}
|
|
43
66
|
};
|
|
44
|
-
const fieldLabel = label || 'This field';
|
|
45
67
|
const buildRules = () => ({
|
|
46
68
|
required: required ? `${fieldLabel} is required` : false,
|
|
47
69
|
validate: {
|
|
48
|
-
...(
|
|
70
|
+
...(min != null
|
|
49
71
|
? {
|
|
50
|
-
|
|
51
|
-
`At least ${
|
|
72
|
+
min: (v) => v.length >= min ||
|
|
73
|
+
`At least ${min} tag${min === 1 ? '' : 's'} required`,
|
|
52
74
|
}
|
|
53
75
|
: {}),
|
|
54
|
-
...(
|
|
76
|
+
...(max != null
|
|
55
77
|
? {
|
|
56
|
-
|
|
57
|
-
`No more than ${
|
|
78
|
+
max: (v) => v.length <= max ||
|
|
79
|
+
`No more than ${max} tag${max === 1 ? '' : 's'} allowed`,
|
|
58
80
|
}
|
|
59
81
|
: {}),
|
|
60
82
|
},
|
|
@@ -63,9 +85,9 @@ export const TagsInput = ({ name, value: valueProp = [], onChange, placeholder =
|
|
|
63
85
|
const cleaned = newValue
|
|
64
86
|
.map((v) => (typeof v === 'string' ? v.trim() : v))
|
|
65
87
|
.filter((v) => v.length > 0);
|
|
66
|
-
// Enforce
|
|
67
|
-
if (
|
|
68
|
-
return cleaned.slice(0,
|
|
88
|
+
// Enforce max cap
|
|
89
|
+
if (max != null && cleaned.length > max) {
|
|
90
|
+
return cleaned.slice(0, max);
|
|
69
91
|
}
|
|
70
92
|
return cleaned;
|
|
71
93
|
};
|
|
@@ -115,19 +137,18 @@ export const TagsInput = ({ name, value: valueProp = [], onChange, placeholder =
|
|
|
115
137
|
const hasError = !!errorMessage;
|
|
116
138
|
return (_jsx(Controller, { name: name, control: formContext.control, defaultValue: valueProp, rules: buildRules(), render: ({ field }) => {
|
|
117
139
|
const tags = field.value ?? [];
|
|
118
|
-
return (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
140
|
+
return (_jsx("div", { children: _jsx(Autocomplete, { multiple: true, freeSolo: true, options: [], value: tags, onChange: (_event, newValue) => {
|
|
141
|
+
const cleaned = cleanValues(newValue);
|
|
142
|
+
field.onChange(cleaned);
|
|
143
|
+
onChange?.(cleaned);
|
|
144
|
+
}, disabled: disabled, renderValue: (tagValue, getTagProps) => renderChips(tagValue, getTagProps), renderInput: (params) => (_jsx(TextField, { ...params, name: name, placeholder: tags.length === 0 ? placeholder : undefined, variant: "outlined", size: "small", error: hasError, sx: inputSx(hasError), onBlur: field.onBlur })) }) }));
|
|
123
145
|
} }));
|
|
124
146
|
}
|
|
125
147
|
// ── standalone mode ───────────────────────────────────────────────────
|
|
126
148
|
return (_jsxs("div", { children: [_jsx(Autocomplete, { multiple: true, freeSolo: true, options: [], value: valueProp, onChange: (_event, newValue) => {
|
|
127
149
|
const cleaned = cleanValues(newValue);
|
|
128
150
|
onChange?.(cleaned);
|
|
129
|
-
}, disabled: disabled, renderValue: (tagValue, getTagProps) => renderChips(tagValue, getTagProps), renderInput: (params) => (_jsx(TextField, { ...params, name: name, placeholder: valueProp.length === 0 ? placeholder : undefined, variant: "outlined", size: "small", sx: inputSx(false) })) }), _jsx("input", { type: "text", name: `${name}__native`, value: valueProp.join(','),
|
|
130
|
-
display: 'none',
|
|
151
|
+
}, disabled: disabled, renderValue: (tagValue, getTagProps) => renderChips(tagValue, getTagProps), renderInput: (params) => (_jsx(TextField, { ...params, name: name, placeholder: valueProp.length === 0 ? placeholder : undefined, variant: "outlined", size: "small", sx: inputSx(false) })) }), _jsx("input", { ref: nativeInputRef, type: "text", name: `${name}__native`, value: valueProp.join(','), "aria-hidden": "true", tabIndex: -1, onChange: () => { }, style: {
|
|
131
152
|
position: 'absolute',
|
|
132
153
|
width: 0,
|
|
133
154
|
height: 0,
|