@capyx/components-library 0.0.1 → 0.0.3
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/README.md +250 -210
- package/dist/addons/AutocompleteInput.d.ts +40 -0
- package/dist/addons/AutocompleteInput.d.ts.map +1 -0
- package/dist/addons/AutocompleteInput.js +101 -0
- package/dist/addons/CharacterCountInput.d.ts +73 -0
- package/dist/addons/CharacterCountInput.d.ts.map +1 -0
- package/dist/addons/CharacterCountInput.js +130 -0
- package/dist/addons/index.d.ts +5 -0
- package/dist/addons/index.d.ts.map +1 -0
- package/dist/addons/index.js +2 -0
- package/dist/components/CheckInput.d.ts +49 -0
- package/dist/components/CheckInput.d.ts.map +1 -0
- package/dist/components/CheckInput.js +58 -0
- package/dist/components/DateInput.d.ts +63 -0
- package/dist/components/DateInput.d.ts.map +1 -0
- package/dist/components/DateInput.js +86 -0
- package/dist/components/FileInput.d.ts +102 -0
- package/dist/components/FileInput.d.ts.map +1 -0
- package/dist/components/FileInput.js +164 -0
- package/dist/components/RichTextInput.d.ts +34 -0
- package/dist/components/RichTextInput.d.ts.map +1 -0
- package/dist/components/RichTextInput.js +57 -0
- package/dist/components/SelectInput.d.ts +54 -0
- package/dist/components/SelectInput.d.ts.map +1 -0
- package/dist/components/SelectInput.js +64 -0
- package/dist/components/SwitchInput.d.ts +46 -0
- package/dist/components/SwitchInput.d.ts.map +1 -0
- package/dist/components/SwitchInput.js +53 -0
- package/dist/components/TagsInput.d.ts +35 -0
- package/dist/components/TagsInput.d.ts.map +1 -0
- package/dist/components/TagsInput.js +67 -0
- package/dist/components/TextAreaInput.d.ts +71 -0
- package/dist/components/TextAreaInput.d.ts.map +1 -0
- package/dist/components/TextAreaInput.js +113 -0
- package/dist/components/TextInput.d.ts +68 -0
- package/dist/components/TextInput.d.ts.map +1 -0
- package/dist/components/TextInput.js +77 -0
- package/dist/components/index.d.ts +10 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/index.cjs +18 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/package.json +87 -72
- package/.storybook/main.ts +0 -33
- package/.storybook/preview.ts +0 -36
- package/.storybook/vitest.setup.ts +0 -7
- package/biome.json +0 -37
- package/lib/addons/CharacterCountInput.tsx +0 -204
- package/lib/addons/index.ts +0 -2
- package/lib/components/CheckInput.tsx +0 -126
- package/lib/components/DateInput.tsx +0 -179
- package/lib/components/FileInput.tsx +0 -353
- package/lib/components/RichTextInput.tsx +0 -112
- package/lib/components/SelectInput.tsx +0 -144
- package/lib/components/SwitchInput.tsx +0 -116
- package/lib/components/TagsInput.tsx +0 -118
- package/lib/components/TextAreaInput.tsx +0 -211
- package/lib/components/TextInput.tsx +0 -381
- package/stories/CharacterCountInput.stories.tsx +0 -104
- package/stories/CheckInput.stories.tsx +0 -80
- package/stories/DateInput.stories.tsx +0 -137
- package/stories/FileInput.stories.tsx +0 -125
- package/stories/RichTextInput.stories.tsx +0 -77
- package/stories/SelectInput.stories.tsx +0 -131
- package/stories/SwitchInput.stories.tsx +0 -80
- package/stories/TagsInput.stories.tsx +0 -69
- package/stories/TextAreaInput.stories.tsx +0 -117
- package/stories/TextInput.stories.tsx +0 -167
- package/vitest.config.ts +0 -37
- package/vitest.shims.d.ts +0 -1
- /package/{lib/components/index.ts → dist/components/index.js} +0 -0
- /package/{lib/index.ts → dist/index.js} +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState, } from 'react';
|
|
3
|
+
import { Button, Form } from 'react-bootstrap';
|
|
4
|
+
import { Controller, useFormContext } from 'react-hook-form';
|
|
5
|
+
/**
|
|
6
|
+
* A customizable file input component with validation, upload lifecycle hooks,
|
|
7
|
+
* and react-hook-form integration.
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Single or multiple file selection
|
|
11
|
+
* - File type filtering via accept attribute
|
|
12
|
+
* - File size validation with configurable max size
|
|
13
|
+
* - Custom file validation logic
|
|
14
|
+
* - Upload lifecycle callbacks (start, complete, error)
|
|
15
|
+
* - Fully customizable UI with className props
|
|
16
|
+
* - Integration with react-hook-form for form validation
|
|
17
|
+
* - Custom formatting for file information display
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Basic file upload with react-hook-form
|
|
21
|
+
* <FileInput
|
|
22
|
+
* name="avatar"
|
|
23
|
+
* label="Profile Picture"
|
|
24
|
+
* accept="image/*"
|
|
25
|
+
* required
|
|
26
|
+
* maxSize={5 * 1024 * 1024} // 5MB
|
|
27
|
+
* />
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* // Multiple files with custom validation
|
|
31
|
+
* <FileInput
|
|
32
|
+
* name="documents"
|
|
33
|
+
* label="Upload Documents"
|
|
34
|
+
* multiple
|
|
35
|
+
* accept=".pdf,.doc,.docx"
|
|
36
|
+
* validateFile={(file) => {
|
|
37
|
+
* if (file.name.length > 100) {
|
|
38
|
+
* return { valid: false, error: "Filename too long" };
|
|
39
|
+
* }
|
|
40
|
+
* return { valid: true };
|
|
41
|
+
* }}
|
|
42
|
+
* onChange={(files) => console.log(files)}
|
|
43
|
+
* />
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* // With upload lifecycle hooks
|
|
47
|
+
* <FileInput
|
|
48
|
+
* name="report"
|
|
49
|
+
* onUploadStart={() => console.log("Starting upload...")}
|
|
50
|
+
* onUploadComplete={() => console.log("Upload complete!")}
|
|
51
|
+
* onUploadError={(error) => console.error(error)}
|
|
52
|
+
* />
|
|
53
|
+
*/
|
|
54
|
+
export const FileInput = ({ name, accept, multiple = false, onChange, onUploadStart, onUploadComplete, onUploadError, disabled = false, maxSize, containerClassName = '', labelClassName = '', buttonClassName = '', fileInfoClassName = '', errorClassName = '', label = 'Upload File', buttonText = 'Choose File', noFileText = 'No file selected', uploadingText = 'Uploading...', formatFileInfo, validateFile, required = false, }) => {
|
|
55
|
+
const fileInputRef = useRef(null);
|
|
56
|
+
const [selectedFiles, setSelectedFiles] = useState(null);
|
|
57
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
58
|
+
const [error, setError] = useState(null);
|
|
59
|
+
const formContext = useFormContext();
|
|
60
|
+
// Helper to safely get nested error
|
|
61
|
+
const getFieldError = (fieldName) => {
|
|
62
|
+
try {
|
|
63
|
+
const error = formContext?.formState?.errors?.[fieldName];
|
|
64
|
+
return error?.message;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const formErrorMessage = name ? getFieldError(name) : undefined;
|
|
71
|
+
const handleFileUploadClick = () => {
|
|
72
|
+
if (isUploading || disabled)
|
|
73
|
+
return;
|
|
74
|
+
fileInputRef.current?.click();
|
|
75
|
+
};
|
|
76
|
+
const validateFiles = (files) => {
|
|
77
|
+
for (const file of files) {
|
|
78
|
+
// Check file size
|
|
79
|
+
if (maxSize && file.size > maxSize) {
|
|
80
|
+
return {
|
|
81
|
+
valid: false,
|
|
82
|
+
error: `File size exceeds maximum allowed size of ${(maxSize / 1024 / 1024).toFixed(2)}MB`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// Custom validation
|
|
86
|
+
if (validateFile) {
|
|
87
|
+
const result = validateFile(file);
|
|
88
|
+
if (!result.valid) {
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { valid: true };
|
|
94
|
+
};
|
|
95
|
+
const handleFileChange = (event, fieldOnChange) => {
|
|
96
|
+
setError(null);
|
|
97
|
+
const files = event.target.files;
|
|
98
|
+
if (!files || files.length === 0) {
|
|
99
|
+
setSelectedFiles(null);
|
|
100
|
+
onChange?.(null);
|
|
101
|
+
fieldOnChange?.(null);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const fileArray = Array.from(files);
|
|
105
|
+
const validation = validateFiles(fileArray);
|
|
106
|
+
if (!validation.valid) {
|
|
107
|
+
setError(validation.error ?? 'Invalid file');
|
|
108
|
+
if (fileInputRef.current) {
|
|
109
|
+
fileInputRef.current.value = '';
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const result = multiple ? fileArray : fileArray[0];
|
|
114
|
+
setSelectedFiles(result);
|
|
115
|
+
// Reset input value to allow re-selecting the same file
|
|
116
|
+
if (fileInputRef.current) {
|
|
117
|
+
fileInputRef.current.value = '';
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
onUploadStart?.();
|
|
121
|
+
setIsUploading(true);
|
|
122
|
+
onChange?.(result);
|
|
123
|
+
fieldOnChange?.(result);
|
|
124
|
+
onUploadComplete?.();
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
const error = err instanceof Error ? err : new Error('Upload failed');
|
|
128
|
+
setError(error.message);
|
|
129
|
+
onUploadError?.(error);
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
setIsUploading(false);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const renderFileInfo = () => {
|
|
136
|
+
if (isUploading) {
|
|
137
|
+
return (_jsx(Form.Text, { className: fileInfoClassName, children: uploadingText }));
|
|
138
|
+
}
|
|
139
|
+
if (!selectedFiles) {
|
|
140
|
+
return _jsx(Form.Text, { className: fileInfoClassName, children: noFileText });
|
|
141
|
+
}
|
|
142
|
+
if (formatFileInfo) {
|
|
143
|
+
return (_jsx(Form.Text, { className: fileInfoClassName, children: formatFileInfo(selectedFiles) }));
|
|
144
|
+
}
|
|
145
|
+
if (Array.isArray(selectedFiles)) {
|
|
146
|
+
return (_jsxs(Form.Text, { className: fileInfoClassName, children: [selectedFiles.length, " file", selectedFiles.length !== 1 ? 's' : '', ' ', "selected"] }));
|
|
147
|
+
}
|
|
148
|
+
return (_jsx(Form.Text, { className: fileInfoClassName, children: selectedFiles.name }));
|
|
149
|
+
};
|
|
150
|
+
const displayError = error || formErrorMessage;
|
|
151
|
+
// Render with form context integration
|
|
152
|
+
if (formContext && name) {
|
|
153
|
+
return (_jsx(Controller, { name: name, control: formContext.control, rules: {
|
|
154
|
+
required: required
|
|
155
|
+
? `${typeof label === 'string' ? label : 'File'} is required`
|
|
156
|
+
: false,
|
|
157
|
+
}, render: ({ field }) => (_jsxs(Form.Group, { className: containerClassName, children: [_jsx("input", { type: "file", ref: (e) => {
|
|
158
|
+
fileInputRef.current = e;
|
|
159
|
+
field.ref(e);
|
|
160
|
+
}, style: { display: 'none' }, accept: accept, multiple: multiple, onChange: (e) => handleFileChange(e, field.onChange), onBlur: field.onBlur, disabled: disabled || isUploading, "aria-invalid": !!displayError, "aria-describedby": displayError ? `${name}-error` : undefined }), label && (_jsx(Form.Label, { className: labelClassName, children: label })), _jsxs("div", { children: [_jsx(Button, { variant: "secondary", className: buttonClassName, onClick: handleFileUploadClick, disabled: disabled || isUploading, children: buttonText }), ' ', renderFileInfo()] }), displayError && (_jsx(Form.Control.Feedback, { type: "invalid", className: errorClassName, style: { display: 'block' }, children: displayError }))] })) }));
|
|
161
|
+
}
|
|
162
|
+
// Standalone mode (no form context)
|
|
163
|
+
return (_jsxs(Form.Group, { className: containerClassName, children: [_jsx("input", { type: "file", ref: fileInputRef, style: { display: 'none' }, accept: accept, multiple: multiple, onChange: (e) => handleFileChange(e), disabled: disabled || isUploading }), label && _jsx(Form.Label, { className: labelClassName, children: label }), _jsxs("div", { children: [_jsx(Button, { variant: "secondary", className: buttonClassName, onClick: handleFileUploadClick, disabled: disabled || isUploading, children: buttonText }), ' ', renderFileInfo()] }), displayError && (_jsx(Form.Control.Feedback, { type: "invalid", className: errorClassName, style: { display: 'block' }, children: displayError }))] }));
|
|
164
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
import 'react-quill-new/dist/quill.snow.css';
|
|
3
|
+
/**
|
|
4
|
+
* Props for the RichTextInput component
|
|
5
|
+
*/
|
|
6
|
+
export type RichTextInputProps = {
|
|
7
|
+
/** Whether the editor is in read-only mode */
|
|
8
|
+
readonly?: boolean;
|
|
9
|
+
/** Maximum number of characters allowed in the editor */
|
|
10
|
+
maxLength?: number;
|
|
11
|
+
/** Current value of the editor (HTML string) */
|
|
12
|
+
value?: string;
|
|
13
|
+
/** Callback function called when the content changes */
|
|
14
|
+
onChange?: (value: string) => void;
|
|
15
|
+
/** Whether the input should be styled as invalid */
|
|
16
|
+
isInvalid?: boolean;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* RichTextInput - A rich text editor component using ReactQuill
|
|
20
|
+
*
|
|
21
|
+
* Provides a WYSIWYG editor with support for headers, bold, italic, underline, and lists.
|
|
22
|
+
* Includes optional character counting and length validation.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* <RichTextInput
|
|
27
|
+
* value={content}
|
|
28
|
+
* onChange={setContent}
|
|
29
|
+
* maxLength={1000}
|
|
30
|
+
* />
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare const RichTextInput: FC<RichTextInputProps>;
|
|
34
|
+
//# sourceMappingURL=RichTextInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RichTextInput.d.ts","sourceRoot":"","sources":["../../lib/components/RichTextInput.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,EAAwC,MAAM,OAAO,CAAC;AAGtE,OAAO,qCAAqC,CAAC;AAE7C;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAChC,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CA2EhD,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { Form } from 'react-bootstrap';
|
|
4
|
+
import ReactQuill from 'react-quill-new';
|
|
5
|
+
import 'react-quill-new/dist/quill.snow.css';
|
|
6
|
+
/**
|
|
7
|
+
* RichTextInput - A rich text editor component using ReactQuill
|
|
8
|
+
*
|
|
9
|
+
* Provides a WYSIWYG editor with support for headers, bold, italic, underline, and lists.
|
|
10
|
+
* Includes optional character counting and length validation.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <RichTextInput
|
|
15
|
+
* value={content}
|
|
16
|
+
* onChange={setContent}
|
|
17
|
+
* maxLength={1000}
|
|
18
|
+
* />
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export const RichTextInput = ({ readonly = false, maxLength, value = '', onChange, isInvalid = false, }) => {
|
|
22
|
+
const reactQuillRef = useRef(null);
|
|
23
|
+
const [count, setCount] = useState(value.length);
|
|
24
|
+
const checkCharacterCount = (event) => {
|
|
25
|
+
const currentRef = reactQuillRef.current;
|
|
26
|
+
if (currentRef != null && maxLength != null) {
|
|
27
|
+
const unprivilegedEditor = currentRef.unprivilegedEditor;
|
|
28
|
+
if (unprivilegedEditor) {
|
|
29
|
+
const length = unprivilegedEditor.getHTML().length;
|
|
30
|
+
if (length >= maxLength && event.key !== 'Backspace') {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const setContentLength = () => {
|
|
37
|
+
const currentRef = reactQuillRef.current;
|
|
38
|
+
if (currentRef != null && maxLength != null) {
|
|
39
|
+
const unprivilegedEditor = currentRef.unprivilegedEditor;
|
|
40
|
+
if (unprivilegedEditor) {
|
|
41
|
+
const length = unprivilegedEditor.getHTML().length;
|
|
42
|
+
setCount(length);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const modules = {
|
|
47
|
+
toolbar: readonly
|
|
48
|
+
? false
|
|
49
|
+
: [
|
|
50
|
+
[{ header: [1, 2, 3, 4, 5, 6, false] }],
|
|
51
|
+
['bold', 'italic', 'underline'],
|
|
52
|
+
[{ list: 'ordered' }, { list: 'bullet' }],
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
const formats = ['header', 'bold', 'italic', 'underline', 'list', 'bullet'];
|
|
56
|
+
return (_jsxs(_Fragment, { children: [_jsx(ReactQuill, { ref: reactQuillRef, theme: "snow", onKeyDown: checkCharacterCount, onKeyUp: setContentLength, formats: formats, modules: modules, value: value, onChange: onChange, readOnly: readonly, className: isInvalid ? 'is-invalid' : '' }), maxLength && (_jsxs(Form.Text, { className: count > maxLength ? 'text-danger' : 'text-muted', children: [count, "/", maxLength, " characters"] }))] }));
|
|
57
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for the SelectInput component
|
|
4
|
+
*/
|
|
5
|
+
export type SelectInputProps = {
|
|
6
|
+
/** The name of the select input field */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Label text displayed for the select */
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Array of option strings to display in the dropdown */
|
|
11
|
+
options: string[];
|
|
12
|
+
/** Placeholder text shown when no option is selected (default: "Select...") */
|
|
13
|
+
helpText?: string;
|
|
14
|
+
/** Size of the select control */
|
|
15
|
+
controlSize?: 'sm' | 'lg';
|
|
16
|
+
/** Current selected value (standalone mode) */
|
|
17
|
+
value?: string;
|
|
18
|
+
/** Callback function called when the selection changes */
|
|
19
|
+
onChange?: (value: string) => void;
|
|
20
|
+
/** Whether the select is disabled */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
/** Whether the select is required */
|
|
23
|
+
required?: boolean;
|
|
24
|
+
/** Array of option values to display in bold */
|
|
25
|
+
highlightValues?: string[];
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* SelectInput - A dropdown select component with react-hook-form integration
|
|
29
|
+
*
|
|
30
|
+
* Provides a select dropdown that works both standalone and with react-hook-form.
|
|
31
|
+
* Supports option highlighting, custom placeholder text, and automatic validation.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```tsx
|
|
35
|
+
* // With react-hook-form
|
|
36
|
+
* <SelectInput
|
|
37
|
+
* name="country"
|
|
38
|
+
* label="Country"
|
|
39
|
+
* options={["USA", "Canada", "UK"]}
|
|
40
|
+
* required
|
|
41
|
+
* />
|
|
42
|
+
*
|
|
43
|
+
* // Standalone mode
|
|
44
|
+
* <SelectInput
|
|
45
|
+
* name="role"
|
|
46
|
+
* options={["Admin", "User", "Guest"]}
|
|
47
|
+
* value={selectedRole}
|
|
48
|
+
* onChange={setSelectedRole}
|
|
49
|
+
* highlightValues={["Admin"]}
|
|
50
|
+
* />
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare const SelectInput: FC<SelectInputProps>;
|
|
54
|
+
//# sourceMappingURL=SelectInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectInput.d.ts","sourceRoot":"","sources":["../../lib/components/SelectInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,EAAE,EAAE,MAAM,OAAO,CAAC;AAI7C;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC9B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,WAAW,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAuF5C,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Form } from 'react-bootstrap';
|
|
3
|
+
import { Controller, useFormContext } from 'react-hook-form';
|
|
4
|
+
/**
|
|
5
|
+
* SelectInput - A dropdown select component with react-hook-form integration
|
|
6
|
+
*
|
|
7
|
+
* Provides a select dropdown that works both standalone and with react-hook-form.
|
|
8
|
+
* Supports option highlighting, custom placeholder text, and automatic validation.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* // With react-hook-form
|
|
13
|
+
* <SelectInput
|
|
14
|
+
* name="country"
|
|
15
|
+
* label="Country"
|
|
16
|
+
* options={["USA", "Canada", "UK"]}
|
|
17
|
+
* required
|
|
18
|
+
* />
|
|
19
|
+
*
|
|
20
|
+
* // Standalone mode
|
|
21
|
+
* <SelectInput
|
|
22
|
+
* name="role"
|
|
23
|
+
* options={["Admin", "User", "Guest"]}
|
|
24
|
+
* value={selectedRole}
|
|
25
|
+
* onChange={setSelectedRole}
|
|
26
|
+
* highlightValues={["Admin"]}
|
|
27
|
+
* />
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export const SelectInput = ({ name, label, options = [], helpText = 'Select...', controlSize, value, onChange, disabled = false, required = false, highlightValues = [], }) => {
|
|
31
|
+
const formContext = useFormContext();
|
|
32
|
+
const getFieldError = (fieldName) => {
|
|
33
|
+
try {
|
|
34
|
+
const error = formContext?.formState?.errors?.[fieldName];
|
|
35
|
+
return error?.message;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const errorMessage = getFieldError(name);
|
|
42
|
+
const isInvalid = !!errorMessage;
|
|
43
|
+
const handleSelectChange = (event) => {
|
|
44
|
+
onChange?.(event.currentTarget.value);
|
|
45
|
+
};
|
|
46
|
+
const renderOptions = () => (_jsxs(_Fragment, { children: [_jsx("option", { value: "", children: helpText.trim() }), options.map((option) => {
|
|
47
|
+
const style = highlightValues.includes(option)
|
|
48
|
+
? { fontWeight: 'bold' }
|
|
49
|
+
: {};
|
|
50
|
+
const key = option.replace(/ /g, '');
|
|
51
|
+
return (_jsx("option", { style: style, value: key, children: option.trim() }, key));
|
|
52
|
+
})] }));
|
|
53
|
+
// Integrated with react-hook-form
|
|
54
|
+
if (formContext) {
|
|
55
|
+
return (_jsx(Controller, { name: name, control: formContext.control, rules: {
|
|
56
|
+
required: required ? `${label || 'This field'} is required` : false,
|
|
57
|
+
}, render: ({ field }) => (_jsx(Form.Select, { ...field, onChange: (e) => {
|
|
58
|
+
field.onChange(e);
|
|
59
|
+
onChange?.(e.target.value);
|
|
60
|
+
}, required: required, size: controlSize, disabled: disabled, isInvalid: isInvalid, children: renderOptions() })) }));
|
|
61
|
+
}
|
|
62
|
+
// Standalone mode
|
|
63
|
+
return (_jsx(Form.Select, { required: required, size: controlSize, value: value || '', onChange: handleSelectChange, disabled: disabled, children: renderOptions() }));
|
|
64
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for the SwitchInput component
|
|
4
|
+
*/
|
|
5
|
+
export type SwitchInputProps = {
|
|
6
|
+
/** The name of the switch input field */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Label text displayed next to the switch */
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Whether the switch is required */
|
|
11
|
+
required?: boolean;
|
|
12
|
+
/** Current on/off state (standalone mode) */
|
|
13
|
+
value?: boolean;
|
|
14
|
+
/** Callback function called when the switch state changes */
|
|
15
|
+
onChange?: (checked: boolean) => void;
|
|
16
|
+
/** Whether the switch is disabled */
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
/** Custom HTML id for the switch element */
|
|
19
|
+
id?: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* SwitchInput - A toggle switch component with react-hook-form integration
|
|
23
|
+
*
|
|
24
|
+
* Provides a toggle switch input that works both standalone and with react-hook-form.
|
|
25
|
+
* Automatically integrates with FormProvider when available, providing validation
|
|
26
|
+
* and error handling.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* // With react-hook-form
|
|
31
|
+
* <SwitchInput
|
|
32
|
+
* name="notifications"
|
|
33
|
+
* label="Enable notifications"
|
|
34
|
+
* />
|
|
35
|
+
*
|
|
36
|
+
* // Standalone mode
|
|
37
|
+
* <SwitchInput
|
|
38
|
+
* name="darkMode"
|
|
39
|
+
* label="Dark mode"
|
|
40
|
+
* value={isDarkMode}
|
|
41
|
+
* onChange={setIsDarkMode}
|
|
42
|
+
* />
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare const SwitchInput: FC<SwitchInputProps>;
|
|
46
|
+
//# sourceMappingURL=SwitchInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SwitchInput.d.ts","sourceRoot":"","sources":["../../lib/components/SwitchInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAIhC;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC9B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4CAA4C;IAC5C,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAmE5C,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Form } from 'react-bootstrap';
|
|
3
|
+
import { Controller, useFormContext } from 'react-hook-form';
|
|
4
|
+
/**
|
|
5
|
+
* SwitchInput - A toggle switch component with react-hook-form integration
|
|
6
|
+
*
|
|
7
|
+
* Provides a toggle switch input that works both standalone and with react-hook-form.
|
|
8
|
+
* Automatically integrates with FormProvider when available, providing validation
|
|
9
|
+
* and error handling.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* // With react-hook-form
|
|
14
|
+
* <SwitchInput
|
|
15
|
+
* name="notifications"
|
|
16
|
+
* label="Enable notifications"
|
|
17
|
+
* />
|
|
18
|
+
*
|
|
19
|
+
* // Standalone mode
|
|
20
|
+
* <SwitchInput
|
|
21
|
+
* name="darkMode"
|
|
22
|
+
* label="Dark mode"
|
|
23
|
+
* value={isDarkMode}
|
|
24
|
+
* onChange={setIsDarkMode}
|
|
25
|
+
* />
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const SwitchInput = ({ name, label, required = false, value, onChange, disabled = false, id, }) => {
|
|
29
|
+
const formContext = useFormContext();
|
|
30
|
+
const getFieldError = (fieldName) => {
|
|
31
|
+
try {
|
|
32
|
+
const error = formContext?.formState?.errors?.[fieldName];
|
|
33
|
+
return error?.message;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const errorMessage = getFieldError(name);
|
|
40
|
+
const inputId = id || `switch-input-${name}`;
|
|
41
|
+
// Integrated with react-hook-form
|
|
42
|
+
if (formContext) {
|
|
43
|
+
return (_jsx(Controller, { name: name, control: formContext.control, rules: {
|
|
44
|
+
required: required ? `${label || 'This field'} is required` : false,
|
|
45
|
+
}, render: ({ field }) => (_jsx(Form.Check, { ...field, id: inputId, type: "switch", label: label, checked: field.value ?? false, onChange: (e) => {
|
|
46
|
+
const checked = e.target.checked;
|
|
47
|
+
field.onChange(checked);
|
|
48
|
+
onChange?.(checked);
|
|
49
|
+
}, disabled: disabled, required: required, isInvalid: !!errorMessage, feedback: errorMessage, feedbackType: "invalid" })) }));
|
|
50
|
+
}
|
|
51
|
+
// Standalone mode
|
|
52
|
+
return (_jsx(Form.Check, { id: inputId, type: "switch", label: label, checked: value ?? false, onChange: (e) => onChange?.(e.target.checked), disabled: disabled, required: required }));
|
|
53
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for the TagsInput component
|
|
4
|
+
*/
|
|
5
|
+
export type TagsInputProps = {
|
|
6
|
+
/** Array of current tag values */
|
|
7
|
+
value: string[];
|
|
8
|
+
/** Callback function called when tags change */
|
|
9
|
+
onChange: (value: string[]) => void;
|
|
10
|
+
/** Placeholder text shown when no tags are entered (default: "Add tags...") */
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
/** Whether the input is disabled */
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
/** Name attribute for the input field */
|
|
15
|
+
name?: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* TagsInput - A multi-tag input component using Material-UI Autocomplete
|
|
19
|
+
*
|
|
20
|
+
* Provides a tag input interface where users can add/remove multiple tags.
|
|
21
|
+
* Automatically trims whitespace and filters empty values. Tags are displayed
|
|
22
|
+
* as chips with delete functionality.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* <TagsInput
|
|
27
|
+
* name="skills"
|
|
28
|
+
* value={tags}
|
|
29
|
+
* onChange={setTags}
|
|
30
|
+
* placeholder="Add skills..."
|
|
31
|
+
* />
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare const TagsInput: FC<TagsInputProps>;
|
|
35
|
+
//# sourceMappingURL=TagsInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TagsInput.d.ts","sourceRoot":"","sources":["../../lib/components/TagsInput.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAEhC;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC5B,kCAAkC;IAClC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,gDAAgD;IAChD,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACpC,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC,cAAc,CAiFxC,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createElement as _createElement } from "react";
|
|
3
|
+
import { Autocomplete, Chip, TextField } from '@mui/material';
|
|
4
|
+
/**
|
|
5
|
+
* TagsInput - A multi-tag input component using Material-UI Autocomplete
|
|
6
|
+
*
|
|
7
|
+
* Provides a tag input interface where users can add/remove multiple tags.
|
|
8
|
+
* Automatically trims whitespace and filters empty values. Tags are displayed
|
|
9
|
+
* as chips with delete functionality.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <TagsInput
|
|
14
|
+
* name="skills"
|
|
15
|
+
* value={tags}
|
|
16
|
+
* onChange={setTags}
|
|
17
|
+
* placeholder="Add skills..."
|
|
18
|
+
* />
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export const TagsInput = ({ value = [], onChange, placeholder = 'Add tags...', disabled = false, name, }) => {
|
|
22
|
+
return (_jsx(Autocomplete, { multiple: true, freeSolo: true, options: [], value: value, onChange: (_event, newValue) => {
|
|
23
|
+
// Filter out empty strings and trim whitespace
|
|
24
|
+
const cleanedValues = newValue
|
|
25
|
+
.map((v) => (typeof v === 'string' ? v.trim() : v))
|
|
26
|
+
.filter((v) => v.length > 0);
|
|
27
|
+
onChange(cleanedValues);
|
|
28
|
+
}, disabled: disabled, renderValue: (tagValue, getTagProps) => tagValue.map((option, index) => (_createElement(Chip, { ...getTagProps({ index }), key: option, label: option, sx: {
|
|
29
|
+
backgroundColor: '#212529',
|
|
30
|
+
color: '#ffffff',
|
|
31
|
+
'& .MuiChip-deleteIcon': {
|
|
32
|
+
color: 'rgba(255, 255, 255, 0.7)',
|
|
33
|
+
userSelect: 'none',
|
|
34
|
+
WebkitUserSelect: 'none',
|
|
35
|
+
MozUserSelect: 'none',
|
|
36
|
+
msUserSelect: 'none',
|
|
37
|
+
pointerEvents: 'auto',
|
|
38
|
+
'&::selection': {
|
|
39
|
+
backgroundColor: 'transparent',
|
|
40
|
+
color: 'transparent',
|
|
41
|
+
},
|
|
42
|
+
'& svg': {
|
|
43
|
+
userSelect: 'none',
|
|
44
|
+
WebkitUserSelect: 'none',
|
|
45
|
+
pointerEvents: 'none',
|
|
46
|
+
},
|
|
47
|
+
'&:hover': {
|
|
48
|
+
color: '#dc3545',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
} }))), renderInput: (params) => (_jsx(TextField, { ...params, name: name, placeholder: value.length === 0 ? placeholder : undefined, variant: "outlined", size: "small", sx: {
|
|
52
|
+
'& .MuiOutlinedInput-root': {
|
|
53
|
+
padding: '4px',
|
|
54
|
+
minHeight: '38px',
|
|
55
|
+
'& fieldset': {
|
|
56
|
+
borderColor: '#ced4da',
|
|
57
|
+
},
|
|
58
|
+
'&:hover fieldset': {
|
|
59
|
+
borderColor: '#86b7fe',
|
|
60
|
+
},
|
|
61
|
+
'&.Mui-focused fieldset': {
|
|
62
|
+
borderColor: '#86b7fe',
|
|
63
|
+
borderWidth: '1px',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
} })) }));
|
|
67
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for the TextAreaInput component
|
|
4
|
+
*/
|
|
5
|
+
export type TextAreaInputProps = {
|
|
6
|
+
/** The name of the textarea field */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Label text displayed for the textarea */
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Whether the field is required */
|
|
11
|
+
required?: boolean;
|
|
12
|
+
/** Maximum number of characters allowed */
|
|
13
|
+
maxLength?: number;
|
|
14
|
+
/** Size variant of the textarea control */
|
|
15
|
+
controlSize?: 'sm' | 'lg';
|
|
16
|
+
/** Placeholder text shown when textarea is empty */
|
|
17
|
+
placeholder?: string;
|
|
18
|
+
/** Controlled value of the textarea */
|
|
19
|
+
value?: string;
|
|
20
|
+
/** Callback fired when the value changes */
|
|
21
|
+
onChange?: (value: string) => void;
|
|
22
|
+
/** Whether the textarea is disabled */
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
/** Whether the textarea is read-only */
|
|
25
|
+
isReadOnly?: boolean;
|
|
26
|
+
/** Whether to render as plain text */
|
|
27
|
+
isPlainText?: boolean;
|
|
28
|
+
/** Debounce delay in milliseconds for value changes */
|
|
29
|
+
debounceMs?: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A flexible textarea input component with automatic height adjustment,
|
|
33
|
+
* react-hook-form integration, and optional debouncing.
|
|
34
|
+
*
|
|
35
|
+
* Features:
|
|
36
|
+
* - Auto-expands height based on content
|
|
37
|
+
* - Seamless integration with react-hook-form for validation
|
|
38
|
+
* - Debounced onChange callback to reduce update frequency
|
|
39
|
+
* - Built-in validation rules (required, maxLength)
|
|
40
|
+
* - Works in both controlled and standalone modes
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // Basic usage with react-hook-form
|
|
44
|
+
* <TextAreaInput
|
|
45
|
+
* name="description"
|
|
46
|
+
* label="Description"
|
|
47
|
+
* required
|
|
48
|
+
* maxLength={500}
|
|
49
|
+
* placeholder="Enter description..."
|
|
50
|
+
* />
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // With custom onChange and debouncing
|
|
54
|
+
* <TextAreaInput
|
|
55
|
+
* name="notes"
|
|
56
|
+
* label="Notes"
|
|
57
|
+
* debounceMs={300}
|
|
58
|
+
* onChange={(value) => console.log(value)}
|
|
59
|
+
* />
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // Standalone mode without form context
|
|
63
|
+
* <TextAreaInput
|
|
64
|
+
* name="comment"
|
|
65
|
+
* value={commentText}
|
|
66
|
+
* onChange={setCommentText}
|
|
67
|
+
* placeholder="Add your comment..."
|
|
68
|
+
* />
|
|
69
|
+
*/
|
|
70
|
+
export declare const TextAreaInput: FC<TextAreaInputProps>;
|
|
71
|
+
//# sourceMappingURL=TextAreaInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TextAreaInput.d.ts","sourceRoot":"","sources":["../../lib/components/TextAreaInput.tsx"],"names":[],"mappings":"AACA,OAAO,EAEN,KAAK,EAAE,EAKP,MAAM,OAAO,CAAC;AAIf;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAChC,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sCAAsC;IACtC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAIF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CA+HhD,CAAC"}
|