@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,73 @@
|
|
|
1
|
+
import { type FC, type PropsWithChildren, type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for the CharacterCountInput component
|
|
4
|
+
*/
|
|
5
|
+
export type CharacterCountInputProps = PropsWithChildren<{
|
|
6
|
+
/** Array of valid HTML element types to monitor (e.g., ["text", "email", "textarea"]) */
|
|
7
|
+
validHTMLElements?: string[];
|
|
8
|
+
/** CSS class for the container wrapper */
|
|
9
|
+
containerClassName?: string;
|
|
10
|
+
/** CSS class for the character counter element */
|
|
11
|
+
counterClassName?: string;
|
|
12
|
+
/** CSS class applied when character count is normal */
|
|
13
|
+
normalClassName?: string;
|
|
14
|
+
/** CSS class applied when character count is near the limit */
|
|
15
|
+
warningClassName?: string;
|
|
16
|
+
/** CSS class applied when character count reaches the limit */
|
|
17
|
+
dangerClassName?: string;
|
|
18
|
+
/** Number of characters remaining before warning state (default: 10) */
|
|
19
|
+
warningThreshold?: number;
|
|
20
|
+
/** Custom function to format the counter display */
|
|
21
|
+
formatCounter?: (current: number, max: number) => ReactNode;
|
|
22
|
+
/** Whether to show the character counter (default: true) */
|
|
23
|
+
showCounter?: boolean;
|
|
24
|
+
/** Whether to show counter even when input is empty (default: false) */
|
|
25
|
+
showWhenEmpty?: boolean;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* A wrapper component that adds a character counter to text inputs and textareas.
|
|
29
|
+
* Automatically detects the maxLength property from the child input and displays
|
|
30
|
+
* the current character count with visual feedback.
|
|
31
|
+
*
|
|
32
|
+
* Features:
|
|
33
|
+
* - Automatic character counting from input/textarea children
|
|
34
|
+
* - Visual feedback with customizable warning and danger states
|
|
35
|
+
* - Configurable warning threshold
|
|
36
|
+
* - Custom counter formatting
|
|
37
|
+
* - Support for multiple input types (text, email, password, textarea, etc.)
|
|
38
|
+
* - Show/hide counter toggle
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* // Basic usage with TextInput
|
|
42
|
+
* <CharacterCountInput>
|
|
43
|
+
* <TextInput
|
|
44
|
+
* name="bio"
|
|
45
|
+
* type="text"
|
|
46
|
+
* maxLength={100}
|
|
47
|
+
* placeholder="Enter your bio"
|
|
48
|
+
* />
|
|
49
|
+
* </CharacterCountInput>
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // With custom styling and warning threshold
|
|
53
|
+
* <CharacterCountInput
|
|
54
|
+
* warningThreshold={20}
|
|
55
|
+
* normalClassName="text-muted"
|
|
56
|
+
* warningClassName="text-warning"
|
|
57
|
+
* dangerClassName="text-danger"
|
|
58
|
+
* >
|
|
59
|
+
* <TextAreaInput name="message" maxLength={200} />
|
|
60
|
+
* </CharacterCountInput>
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // With custom counter format
|
|
64
|
+
* <CharacterCountInput
|
|
65
|
+
* formatCounter={(current, max) => (
|
|
66
|
+
* <span>{max - current} characters remaining</span>
|
|
67
|
+
* )}
|
|
68
|
+
* >
|
|
69
|
+
* <TextInput name="title" maxLength={50} />
|
|
70
|
+
* </CharacterCountInput>
|
|
71
|
+
*/
|
|
72
|
+
export declare const CharacterCountInput: FC<CharacterCountInputProps>;
|
|
73
|
+
//# sourceMappingURL=CharacterCountInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CharacterCountInput.d.ts","sourceRoot":"","sources":["../../lib/addons/CharacterCountInput.tsx"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,EAAE,EACP,KAAK,iBAAiB,EAEtB,KAAK,SAAS,EAId,MAAM,OAAO,CAAC;AAaf;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,iBAAiB,CAAC;IACxD,yFAAyF;IACzF,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,0CAA0C;IAC1C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,SAAS,CAAC;IAC5D,4DAA4D;IAC5D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wEAAwE;IACxE,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,eAAO,MAAM,mBAAmB,EAAE,EAAE,CAAC,wBAAwB,CA+G5D,CAAC"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState, } from 'react';
|
|
3
|
+
import { Form } from 'react-bootstrap';
|
|
4
|
+
const _DEFAULT_VALID_HTML_ELEMENTS = [
|
|
5
|
+
'text',
|
|
6
|
+
'email',
|
|
7
|
+
'password',
|
|
8
|
+
'search',
|
|
9
|
+
'tel',
|
|
10
|
+
'url',
|
|
11
|
+
'textarea',
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* A wrapper component that adds a character counter to text inputs and textareas.
|
|
15
|
+
* Automatically detects the maxLength property from the child input and displays
|
|
16
|
+
* the current character count with visual feedback.
|
|
17
|
+
*
|
|
18
|
+
* Features:
|
|
19
|
+
* - Automatic character counting from input/textarea children
|
|
20
|
+
* - Visual feedback with customizable warning and danger states
|
|
21
|
+
* - Configurable warning threshold
|
|
22
|
+
* - Custom counter formatting
|
|
23
|
+
* - Support for multiple input types (text, email, password, textarea, etc.)
|
|
24
|
+
* - Show/hide counter toggle
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Basic usage with TextInput
|
|
28
|
+
* <CharacterCountInput>
|
|
29
|
+
* <TextInput
|
|
30
|
+
* name="bio"
|
|
31
|
+
* type="text"
|
|
32
|
+
* maxLength={100}
|
|
33
|
+
* placeholder="Enter your bio"
|
|
34
|
+
* />
|
|
35
|
+
* </CharacterCountInput>
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // With custom styling and warning threshold
|
|
39
|
+
* <CharacterCountInput
|
|
40
|
+
* warningThreshold={20}
|
|
41
|
+
* normalClassName="text-muted"
|
|
42
|
+
* warningClassName="text-warning"
|
|
43
|
+
* dangerClassName="text-danger"
|
|
44
|
+
* >
|
|
45
|
+
* <TextAreaInput name="message" maxLength={200} />
|
|
46
|
+
* </CharacterCountInput>
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* // With custom counter format
|
|
50
|
+
* <CharacterCountInput
|
|
51
|
+
* formatCounter={(current, max) => (
|
|
52
|
+
* <span>{max - current} characters remaining</span>
|
|
53
|
+
* )}
|
|
54
|
+
* >
|
|
55
|
+
* <TextInput name="title" maxLength={50} />
|
|
56
|
+
* </CharacterCountInput>
|
|
57
|
+
*/
|
|
58
|
+
export const CharacterCountInput = ({ validHTMLElements: _validHTMLElements, children, containerClassName = 'text-end', counterClassName = '', normalClassName = 'text-muted', warningClassName = 'text-warning', dangerClassName = 'text-danger', warningThreshold = 10, formatCounter, showCounter = true, showWhenEmpty = false, }) => {
|
|
59
|
+
const containerRef = useRef(null);
|
|
60
|
+
const [characterCount, setCharacterCount] = useState(0);
|
|
61
|
+
const [maxLength, setMaxLength] = useState(null);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!containerRef.current)
|
|
64
|
+
return;
|
|
65
|
+
// Extract maxLength from child props
|
|
66
|
+
const childProps = children?.props;
|
|
67
|
+
if (childProps?.maxLength) {
|
|
68
|
+
setMaxLength(childProps.maxLength);
|
|
69
|
+
}
|
|
70
|
+
// Use MutationObserver to wait for input element to be rendered
|
|
71
|
+
const findAndAttachInput = () => {
|
|
72
|
+
const input = containerRef.current?.querySelector('input, textarea');
|
|
73
|
+
if (!input)
|
|
74
|
+
return null;
|
|
75
|
+
const inputReader = () => {
|
|
76
|
+
setCharacterCount(input.value?.length ?? 0);
|
|
77
|
+
};
|
|
78
|
+
input.addEventListener('input', inputReader);
|
|
79
|
+
input.addEventListener('keyup', inputReader);
|
|
80
|
+
inputReader(); // Initial read
|
|
81
|
+
return () => {
|
|
82
|
+
input.removeEventListener('input', inputReader);
|
|
83
|
+
input.removeEventListener('keyup', inputReader);
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
// Try to find input immediately
|
|
87
|
+
let cleanup = findAndAttachInput();
|
|
88
|
+
// If not found, wait for it to be rendered
|
|
89
|
+
if (!cleanup && containerRef.current) {
|
|
90
|
+
const observer = new MutationObserver(() => {
|
|
91
|
+
if (!cleanup) {
|
|
92
|
+
cleanup = findAndAttachInput();
|
|
93
|
+
if (cleanup) {
|
|
94
|
+
observer.disconnect();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
observer.observe(containerRef.current, {
|
|
99
|
+
childList: true,
|
|
100
|
+
subtree: true,
|
|
101
|
+
});
|
|
102
|
+
return () => {
|
|
103
|
+
observer.disconnect();
|
|
104
|
+
cleanup?.();
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return cleanup || undefined;
|
|
108
|
+
}, [children]);
|
|
109
|
+
const getCounterClassName = () => {
|
|
110
|
+
if (maxLength === null)
|
|
111
|
+
return normalClassName;
|
|
112
|
+
if (characterCount === maxLength)
|
|
113
|
+
return dangerClassName;
|
|
114
|
+
if (characterCount >= maxLength - warningThreshold)
|
|
115
|
+
return warningClassName;
|
|
116
|
+
return normalClassName;
|
|
117
|
+
};
|
|
118
|
+
const renderCounter = () => {
|
|
119
|
+
if (!showCounter || maxLength === null)
|
|
120
|
+
return null;
|
|
121
|
+
if (!showWhenEmpty && characterCount === 0)
|
|
122
|
+
return null;
|
|
123
|
+
const className = `${counterClassName} ${getCounterClassName()}`.trim();
|
|
124
|
+
const content = formatCounter
|
|
125
|
+
? formatCounter(characterCount, maxLength)
|
|
126
|
+
: `${characterCount} / ${maxLength}`;
|
|
127
|
+
return _jsx(Form.Text, { className: className, children: content });
|
|
128
|
+
};
|
|
129
|
+
return (_jsxs("div", { ref: containerRef, className: containerClassName, children: [children, renderCounter()] }));
|
|
130
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { AutocompleteInputProps } from './AutocompleteInput';
|
|
2
|
+
export { AutocompleteInput } from './AutocompleteInput';
|
|
3
|
+
export type { CharacterCountInputProps } from './CharacterCountInput';
|
|
4
|
+
export { CharacterCountInput } from './CharacterCountInput';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/addons/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for the CheckInput component
|
|
4
|
+
*/
|
|
5
|
+
export type CheckInputProps = {
|
|
6
|
+
/** The name of the checkbox input field */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Label text displayed next to the checkbox */
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Whether the checkbox is required */
|
|
11
|
+
required?: boolean;
|
|
12
|
+
/** Current checked state (standalone mode) */
|
|
13
|
+
value?: boolean;
|
|
14
|
+
/** Callback function called when the checked state changes */
|
|
15
|
+
onChange?: (checked: boolean) => void;
|
|
16
|
+
/** Callback function called when the checkbox loses focus */
|
|
17
|
+
onBlur?: () => void;
|
|
18
|
+
/** Whether the checkbox is disabled */
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
/** Custom HTML id for the checkbox element */
|
|
21
|
+
id?: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* CheckInput - A checkbox input component with react-hook-form integration
|
|
25
|
+
*
|
|
26
|
+
* Provides a checkbox input that works both standalone and with react-hook-form.
|
|
27
|
+
* Automatically integrates with FormProvider when available, providing validation
|
|
28
|
+
* and error handling.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* // With react-hook-form
|
|
33
|
+
* <CheckInput
|
|
34
|
+
* name="agreeToTerms"
|
|
35
|
+
* label="I agree to the terms"
|
|
36
|
+
* required
|
|
37
|
+
* />
|
|
38
|
+
*
|
|
39
|
+
* // Standalone mode
|
|
40
|
+
* <CheckInput
|
|
41
|
+
* name="newsletter"
|
|
42
|
+
* label="Subscribe to newsletter"
|
|
43
|
+
* value={subscribed}
|
|
44
|
+
* onChange={setSubscribed}
|
|
45
|
+
* />
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare const CheckInput: FC<CheckInputProps>;
|
|
49
|
+
//# sourceMappingURL=CheckInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CheckInput.d.ts","sourceRoot":"","sources":["../../lib/components/CheckInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAIhC;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC7B,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8CAA8C;IAC9C,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CA0E1C,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
* CheckInput - A checkbox input component with react-hook-form integration
|
|
6
|
+
*
|
|
7
|
+
* Provides a checkbox 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
|
+
* <CheckInput
|
|
15
|
+
* name="agreeToTerms"
|
|
16
|
+
* label="I agree to the terms"
|
|
17
|
+
* required
|
|
18
|
+
* />
|
|
19
|
+
*
|
|
20
|
+
* // Standalone mode
|
|
21
|
+
* <CheckInput
|
|
22
|
+
* name="newsletter"
|
|
23
|
+
* label="Subscribe to newsletter"
|
|
24
|
+
* value={subscribed}
|
|
25
|
+
* onChange={setSubscribed}
|
|
26
|
+
* />
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const CheckInput = ({ name, label, required = false, value, onChange, onBlur, disabled = false, id, }) => {
|
|
30
|
+
const formContext = useFormContext();
|
|
31
|
+
// Helper to safely get nested error
|
|
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 inputId = id || `check-input-${name}`;
|
|
43
|
+
// Integrated with react-hook-form
|
|
44
|
+
if (formContext) {
|
|
45
|
+
return (_jsx(Controller, { name: name, control: formContext.control, rules: {
|
|
46
|
+
required: required ? `${label || 'This field'} is required` : false,
|
|
47
|
+
}, render: ({ field }) => (_jsx(Form.Check, { ...field, id: inputId, type: "checkbox", label: label, checked: field.value ?? false, onChange: (e) => {
|
|
48
|
+
const checked = e.target.checked;
|
|
49
|
+
field.onChange(checked);
|
|
50
|
+
onChange?.(checked);
|
|
51
|
+
}, onBlur: () => {
|
|
52
|
+
field.onBlur();
|
|
53
|
+
onBlur?.();
|
|
54
|
+
}, disabled: disabled, required: required, isInvalid: !!errorMessage, feedback: errorMessage, feedbackType: "invalid" })) }));
|
|
55
|
+
}
|
|
56
|
+
// Standalone mode (no form context)
|
|
57
|
+
return (_jsx(Form.Check, { id: inputId, type: "checkbox", label: label, checked: value ?? false, onChange: (e) => onChange?.(e.target.checked), onBlur: onBlur, disabled: disabled, required: required }));
|
|
58
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for the DateInput component
|
|
4
|
+
*/
|
|
5
|
+
export type DateInputProps = {
|
|
6
|
+
/** The name of the date input field */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Label text displayed for the date picker */
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Current date value (standalone mode) */
|
|
11
|
+
value?: Date | null;
|
|
12
|
+
/** Callback function called when the date changes */
|
|
13
|
+
onChange?: (date: Date | null) => void;
|
|
14
|
+
/** Callback function called when the input loses focus */
|
|
15
|
+
onBlur?: () => void;
|
|
16
|
+
/** Whether the date field is required */
|
|
17
|
+
required?: boolean;
|
|
18
|
+
/** Whether the date picker is disabled */
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
/** Minimum selectable date */
|
|
21
|
+
minDate?: Date;
|
|
22
|
+
/** Maximum selectable date */
|
|
23
|
+
maxDate?: Date;
|
|
24
|
+
/** Date format string (default: "DD/MM/YYYY") */
|
|
25
|
+
dateFormat?: string;
|
|
26
|
+
/** Size of the text field (default: "small") */
|
|
27
|
+
textFieldSize?: 'small' | 'medium';
|
|
28
|
+
/** Whether to disable future dates */
|
|
29
|
+
disableFuture?: boolean;
|
|
30
|
+
/** Whether to disable past dates */
|
|
31
|
+
disablePast?: boolean;
|
|
32
|
+
/** Which calendar views to display */
|
|
33
|
+
views?: Array<'year' | 'month' | 'day'>;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* DateInput - A date picker component using Material-UI with react-hook-form integration
|
|
37
|
+
*
|
|
38
|
+
* Provides a date picker with calendar interface that works both standalone and with
|
|
39
|
+
* react-hook-form. Uses MUI DatePicker internally with customizable date ranges,
|
|
40
|
+
* formats, and validation.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```tsx
|
|
44
|
+
* // With react-hook-form
|
|
45
|
+
* <DateInput
|
|
46
|
+
* name="birthdate"
|
|
47
|
+
* label="Date of Birth"
|
|
48
|
+
* required
|
|
49
|
+
* disableFuture
|
|
50
|
+
* />
|
|
51
|
+
*
|
|
52
|
+
* // Standalone mode
|
|
53
|
+
* <DateInput
|
|
54
|
+
* name="appointmentDate"
|
|
55
|
+
* label="Select Date"
|
|
56
|
+
* value={selectedDate}
|
|
57
|
+
* onChange={setSelectedDate}
|
|
58
|
+
* minDate={new Date()}
|
|
59
|
+
* />
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare const DateInput: FC<DateInputProps>;
|
|
63
|
+
//# sourceMappingURL=DateInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DateInput.d.ts","sourceRoot":"","sources":["../../lib/components/DateInput.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAIhC;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC5B,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACpB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IACvC,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,8BAA8B;IAC9B,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,aAAa,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACnC,sCAAsC;IACtC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oCAAoC;IACpC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sCAAsC;IACtC,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC,CAAC;CACxC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC,cAAc,CAgHxC,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { DatePicker } from '@mui/x-date-pickers';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import { Form } from 'react-bootstrap';
|
|
5
|
+
import { Controller, useFormContext } from 'react-hook-form';
|
|
6
|
+
/**
|
|
7
|
+
* DateInput - A date picker component using Material-UI with react-hook-form integration
|
|
8
|
+
*
|
|
9
|
+
* Provides a date picker with calendar interface that works both standalone and with
|
|
10
|
+
* react-hook-form. Uses MUI DatePicker internally with customizable date ranges,
|
|
11
|
+
* formats, and validation.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* // With react-hook-form
|
|
16
|
+
* <DateInput
|
|
17
|
+
* name="birthdate"
|
|
18
|
+
* label="Date of Birth"
|
|
19
|
+
* required
|
|
20
|
+
* disableFuture
|
|
21
|
+
* />
|
|
22
|
+
*
|
|
23
|
+
* // Standalone mode
|
|
24
|
+
* <DateInput
|
|
25
|
+
* name="appointmentDate"
|
|
26
|
+
* label="Select Date"
|
|
27
|
+
* value={selectedDate}
|
|
28
|
+
* onChange={setSelectedDate}
|
|
29
|
+
* minDate={new Date()}
|
|
30
|
+
* />
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export const DateInput = ({ name, label, value, onChange, onBlur, required = false, disabled = false, minDate, maxDate, dateFormat = 'DD/MM/YYYY', textFieldSize = 'small', disableFuture = false, disablePast = false, views, }) => {
|
|
34
|
+
const formContext = useFormContext();
|
|
35
|
+
// Helper to safely get nested error
|
|
36
|
+
const getFieldError = (fieldName) => {
|
|
37
|
+
try {
|
|
38
|
+
const error = formContext?.formState?.errors?.[fieldName];
|
|
39
|
+
return error?.message;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const errorMessage = getFieldError(name);
|
|
46
|
+
// Convert Date to Dayjs
|
|
47
|
+
const convertToDateValue = (date) => {
|
|
48
|
+
if (!date)
|
|
49
|
+
return undefined;
|
|
50
|
+
return dayjs(date);
|
|
51
|
+
};
|
|
52
|
+
// Integrated with react-hook-form
|
|
53
|
+
if (formContext) {
|
|
54
|
+
return (_jsx(Controller, { name: name, control: formContext.control, rules: {
|
|
55
|
+
required: required ? `${label || 'This field'} is required` : false,
|
|
56
|
+
}, render: ({ field }) => (_jsx(Form.Group, { children: _jsx(DatePicker, { label: label, value: convertToDateValue(field.value), onChange: (newValue) => {
|
|
57
|
+
const dateValue = newValue?.isValid()
|
|
58
|
+
? newValue.toDate()
|
|
59
|
+
: null;
|
|
60
|
+
field.onChange(dateValue);
|
|
61
|
+
onChange?.(dateValue);
|
|
62
|
+
}, disabled: disabled, format: dateFormat, minDate: convertToDateValue(minDate), maxDate: convertToDateValue(maxDate), disableFuture: disableFuture, disablePast: disablePast, views: views, slotProps: {
|
|
63
|
+
textField: {
|
|
64
|
+
size: textFieldSize,
|
|
65
|
+
required: required,
|
|
66
|
+
error: !!errorMessage,
|
|
67
|
+
helperText: errorMessage,
|
|
68
|
+
onBlur: () => {
|
|
69
|
+
field.onBlur();
|
|
70
|
+
onBlur?.();
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
} }) })) }));
|
|
74
|
+
}
|
|
75
|
+
// Standalone mode (no form context)
|
|
76
|
+
return (_jsx(Form.Group, { children: _jsx(DatePicker, { label: label, value: convertToDateValue(value), onChange: (newValue) => {
|
|
77
|
+
const dateValue = newValue?.isValid() ? newValue.toDate() : null;
|
|
78
|
+
onChange?.(dateValue);
|
|
79
|
+
}, disabled: disabled, format: dateFormat, minDate: convertToDateValue(minDate), maxDate: convertToDateValue(maxDate), disableFuture: disableFuture, disablePast: disablePast, views: views, slotProps: {
|
|
80
|
+
textField: {
|
|
81
|
+
size: textFieldSize,
|
|
82
|
+
required: required,
|
|
83
|
+
onBlur: onBlur,
|
|
84
|
+
},
|
|
85
|
+
} }) }));
|
|
86
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type FC, type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for the FileInput component
|
|
4
|
+
*/
|
|
5
|
+
export type FileInputProps = {
|
|
6
|
+
/** The name of the file input field for form integration */
|
|
7
|
+
name?: string;
|
|
8
|
+
/** File types to accept (e.g., "image/*", ".pdf,.doc") */
|
|
9
|
+
accept?: string;
|
|
10
|
+
/** Whether to allow selecting multiple files */
|
|
11
|
+
multiple?: boolean;
|
|
12
|
+
/** Callback fired when file selection changes */
|
|
13
|
+
onChange?: (files: File | File[] | null) => void;
|
|
14
|
+
/** Callback fired when upload process starts */
|
|
15
|
+
onUploadStart?: () => void;
|
|
16
|
+
/** Callback fired when upload completes successfully */
|
|
17
|
+
onUploadComplete?: () => void;
|
|
18
|
+
/** Callback fired when upload encounters an error */
|
|
19
|
+
onUploadError?: (error: Error) => void;
|
|
20
|
+
/** Whether the file input is disabled */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
/** Maximum file size in bytes */
|
|
23
|
+
maxSize?: number;
|
|
24
|
+
/** CSS class for the container element */
|
|
25
|
+
containerClassName?: string;
|
|
26
|
+
/** CSS class for the label element */
|
|
27
|
+
labelClassName?: string;
|
|
28
|
+
/** CSS class for the button element */
|
|
29
|
+
buttonClassName?: string;
|
|
30
|
+
/** CSS class for the file info text */
|
|
31
|
+
fileInfoClassName?: string;
|
|
32
|
+
/** CSS class for error messages */
|
|
33
|
+
errorClassName?: string;
|
|
34
|
+
/** Label text or element displayed above the input */
|
|
35
|
+
label?: ReactNode;
|
|
36
|
+
/** Text or element displayed on the button */
|
|
37
|
+
buttonText?: ReactNode;
|
|
38
|
+
/** Text shown when no file is selected */
|
|
39
|
+
noFileText?: ReactNode;
|
|
40
|
+
/** Text shown during file upload */
|
|
41
|
+
uploadingText?: ReactNode;
|
|
42
|
+
/** Custom function to format the file info display */
|
|
43
|
+
formatFileInfo?: (files: File | File[]) => ReactNode;
|
|
44
|
+
/** Custom validation function for individual files */
|
|
45
|
+
validateFile?: (file: File) => {
|
|
46
|
+
valid: boolean;
|
|
47
|
+
error?: string;
|
|
48
|
+
};
|
|
49
|
+
/** Whether file selection is required */
|
|
50
|
+
required?: boolean;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* A customizable file input component with validation, upload lifecycle hooks,
|
|
54
|
+
* and react-hook-form integration.
|
|
55
|
+
*
|
|
56
|
+
* Features:
|
|
57
|
+
* - Single or multiple file selection
|
|
58
|
+
* - File type filtering via accept attribute
|
|
59
|
+
* - File size validation with configurable max size
|
|
60
|
+
* - Custom file validation logic
|
|
61
|
+
* - Upload lifecycle callbacks (start, complete, error)
|
|
62
|
+
* - Fully customizable UI with className props
|
|
63
|
+
* - Integration with react-hook-form for form validation
|
|
64
|
+
* - Custom formatting for file information display
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* // Basic file upload with react-hook-form
|
|
68
|
+
* <FileInput
|
|
69
|
+
* name="avatar"
|
|
70
|
+
* label="Profile Picture"
|
|
71
|
+
* accept="image/*"
|
|
72
|
+
* required
|
|
73
|
+
* maxSize={5 * 1024 * 1024} // 5MB
|
|
74
|
+
* />
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // Multiple files with custom validation
|
|
78
|
+
* <FileInput
|
|
79
|
+
* name="documents"
|
|
80
|
+
* label="Upload Documents"
|
|
81
|
+
* multiple
|
|
82
|
+
* accept=".pdf,.doc,.docx"
|
|
83
|
+
* validateFile={(file) => {
|
|
84
|
+
* if (file.name.length > 100) {
|
|
85
|
+
* return { valid: false, error: "Filename too long" };
|
|
86
|
+
* }
|
|
87
|
+
* return { valid: true };
|
|
88
|
+
* }}
|
|
89
|
+
* onChange={(files) => console.log(files)}
|
|
90
|
+
* />
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* // With upload lifecycle hooks
|
|
94
|
+
* <FileInput
|
|
95
|
+
* name="report"
|
|
96
|
+
* onUploadStart={() => console.log("Starting upload...")}
|
|
97
|
+
* onUploadComplete={() => console.log("Upload complete!")}
|
|
98
|
+
* onUploadError={(error) => console.error(error)}
|
|
99
|
+
* />
|
|
100
|
+
*/
|
|
101
|
+
export declare const FileInput: FC<FileInputProps>;
|
|
102
|
+
//# sourceMappingURL=FileInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileInput.d.ts","sourceRoot":"","sources":["../../lib/components/FileInput.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,EAAE,EACP,KAAK,SAAS,EAGd,MAAM,OAAO,CAAC;AAIf;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC5B,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,KAAK,IAAI,CAAC;IACjD,gDAAgD;IAChD,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,wDAAwD;IACxD,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,qDAAqD;IACrD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACvC,yCAAyC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sCAAsC;IACtC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uCAAuC;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uCAAuC;IACvC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,oCAAoC;IACpC,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B,sDAAsD;IACtD,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,EAAE,KAAK,SAAS,CAAC;IACrD,sDAAsD;IACtD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,yCAAyC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC,cAAc,CAqPxC,CAAC"}
|