@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
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
import debounce from 'lodash.debounce';
|
|
2
|
-
import React, {
|
|
3
|
-
type ChangeEvent,
|
|
4
|
-
type FC,
|
|
5
|
-
type ReactElement,
|
|
6
|
-
type SyntheticEvent,
|
|
7
|
-
useEffect,
|
|
8
|
-
useRef,
|
|
9
|
-
useState,
|
|
10
|
-
} from 'react';
|
|
11
|
-
import Autosuggest from 'react-autosuggest';
|
|
12
|
-
import { Form, InputGroup } from 'react-bootstrap';
|
|
13
|
-
import { Controller, useFormContext } from 'react-hook-form';
|
|
14
|
-
|
|
15
|
-
const formatSpaces = (value: string) =>
|
|
16
|
-
value.replace(
|
|
17
|
-
/(?<=[A-Z])(?![0-9]+)(?=[A-Z][a-z])|(?<=[^A-Z])(?![0-9]+)(?=[A-Z])|(?<=[A-Za-z])(?![0-9]+)(?=[^A-Za-z])/g,
|
|
18
|
-
' ',
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Props for the TextInput component
|
|
23
|
-
*/
|
|
24
|
-
export type TextInputProps = {
|
|
25
|
-
/** The name of the text input field */
|
|
26
|
-
name?: string;
|
|
27
|
-
/** Label text displayed for the input */
|
|
28
|
-
label?: string;
|
|
29
|
-
/** HTML input type (default: "text") */
|
|
30
|
-
type?: string;
|
|
31
|
-
/** Minimum value for number/date inputs */
|
|
32
|
-
min?: string | number;
|
|
33
|
-
/** Maximum value for number/date inputs */
|
|
34
|
-
max?: string | number;
|
|
35
|
-
/** Whether the field is required */
|
|
36
|
-
required?: boolean;
|
|
37
|
-
/** Maximum number of characters allowed */
|
|
38
|
-
maxLength?: number;
|
|
39
|
-
/** Size variant of the input control */
|
|
40
|
-
controlSize?: 'sm' | 'lg' | undefined;
|
|
41
|
-
/** Placeholder text shown when input is empty */
|
|
42
|
-
placeholder?: string;
|
|
43
|
-
/** Controlled field value */
|
|
44
|
-
fieldValue?: string | number | Date | boolean | undefined;
|
|
45
|
-
/** Callback to update field value */
|
|
46
|
-
setFieldValue?: (value: string | number | Date | boolean | undefined) => void;
|
|
47
|
-
/** Callback to save/persist field value */
|
|
48
|
-
saveFieldvalue?: (
|
|
49
|
-
value: string | number | Date | boolean | undefined,
|
|
50
|
-
) => void;
|
|
51
|
-
/** Whether the input is disabled */
|
|
52
|
-
disabled?: boolean;
|
|
53
|
-
/** Whether the input is read-only */
|
|
54
|
-
isReadOnly?: boolean;
|
|
55
|
-
/** Whether to render as plain text */
|
|
56
|
-
isPlainText?: boolean;
|
|
57
|
-
/** Regex pattern for validation */
|
|
58
|
-
pattern?: string;
|
|
59
|
-
/** Icon element to display in the input group */
|
|
60
|
-
icon?: ReactElement | null;
|
|
61
|
-
/** Enable autocomplete/suggestions functionality */
|
|
62
|
-
shouldAutoComplete?: boolean;
|
|
63
|
-
/** Array of suggestion values for autocomplete */
|
|
64
|
-
values?: string[];
|
|
65
|
-
/** Debounce delay in milliseconds for value changes */
|
|
66
|
-
debounceMs?: number;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* A versatile text input component with support for react-hook-form integration,
|
|
71
|
-
* autocomplete suggestions, debouncing, and various input types.
|
|
72
|
-
*
|
|
73
|
-
* Features:
|
|
74
|
-
* - Seamless integration with react-hook-form for validation and state management
|
|
75
|
-
* - Autocomplete with suggestion filtering when enabled
|
|
76
|
-
* - Debounced value updates to reduce callback frequency
|
|
77
|
-
* - Support for various input types (text, number, email, tel, etc.)
|
|
78
|
-
* - Built-in validation rules (required, min, max, maxLength, pattern)
|
|
79
|
-
* - Icon support for enhanced UI
|
|
80
|
-
* - Automatic phone number formatting for tel type
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* // Basic usage with react-hook-form
|
|
84
|
-
* <TextInput
|
|
85
|
-
* name="email"
|
|
86
|
-
* label="Email Address"
|
|
87
|
-
* type="email"
|
|
88
|
-
* required
|
|
89
|
-
* placeholder="Enter your email"
|
|
90
|
-
* />
|
|
91
|
-
*
|
|
92
|
-
* @example
|
|
93
|
-
* // With autocomplete suggestions
|
|
94
|
-
* <TextInput
|
|
95
|
-
* name="country"
|
|
96
|
-
* label="Country"
|
|
97
|
-
* shouldAutoComplete
|
|
98
|
-
* values={["USA", "Canada", "Mexico"]}
|
|
99
|
-
* />
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* // With debouncing and custom callbacks
|
|
103
|
-
* <TextInput
|
|
104
|
-
* name="search"
|
|
105
|
-
* placeholder="Search..."
|
|
106
|
-
* debounceMs={300}
|
|
107
|
-
* setFieldValue={handleSearch}
|
|
108
|
-
* />
|
|
109
|
-
*/
|
|
110
|
-
export const TextInput: FC<TextInputProps> = ({
|
|
111
|
-
name,
|
|
112
|
-
label,
|
|
113
|
-
type = 'text',
|
|
114
|
-
min,
|
|
115
|
-
max,
|
|
116
|
-
required,
|
|
117
|
-
maxLength,
|
|
118
|
-
controlSize,
|
|
119
|
-
placeholder,
|
|
120
|
-
fieldValue,
|
|
121
|
-
setFieldValue,
|
|
122
|
-
saveFieldvalue,
|
|
123
|
-
disabled,
|
|
124
|
-
isReadOnly,
|
|
125
|
-
isPlainText,
|
|
126
|
-
pattern,
|
|
127
|
-
icon,
|
|
128
|
-
shouldAutoComplete,
|
|
129
|
-
values = [],
|
|
130
|
-
debounceMs,
|
|
131
|
-
}) => {
|
|
132
|
-
const formContext = useFormContext();
|
|
133
|
-
|
|
134
|
-
// Create refs for debounced functions to clean up on unmount
|
|
135
|
-
const debouncedSetFieldValueRef = useRef<ReturnType<typeof debounce> | null>(
|
|
136
|
-
null,
|
|
137
|
-
);
|
|
138
|
-
const debouncedSaveFieldValueRef = useRef<ReturnType<typeof debounce> | null>(
|
|
139
|
-
null,
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
// Create debounced versions of callbacks if debounceMs is provided
|
|
143
|
-
useEffect(() => {
|
|
144
|
-
if (debounceMs && setFieldValue) {
|
|
145
|
-
debouncedSetFieldValueRef.current = debounce(setFieldValue, debounceMs);
|
|
146
|
-
}
|
|
147
|
-
if (debounceMs && saveFieldvalue) {
|
|
148
|
-
debouncedSaveFieldValueRef.current = debounce(saveFieldvalue, debounceMs);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Cleanup on unmount
|
|
152
|
-
return () => {
|
|
153
|
-
debouncedSetFieldValueRef.current?.cancel();
|
|
154
|
-
debouncedSaveFieldValueRef.current?.cancel();
|
|
155
|
-
};
|
|
156
|
-
}, [debounceMs, setFieldValue, saveFieldvalue]);
|
|
157
|
-
|
|
158
|
-
const fieldError =
|
|
159
|
-
formContext && name ? formContext.formState.errors[name] : null;
|
|
160
|
-
const isInvalid = !!fieldError;
|
|
161
|
-
|
|
162
|
-
const [suggestions, setSuggestions] = useState<string[]>([]);
|
|
163
|
-
|
|
164
|
-
const getSuggestions = (value: string) => {
|
|
165
|
-
const inputValue = value.trim().toLowerCase();
|
|
166
|
-
const inputLength = inputValue.length;
|
|
167
|
-
|
|
168
|
-
return inputLength === 0
|
|
169
|
-
? []
|
|
170
|
-
: values.filter(
|
|
171
|
-
(v: string) => v.trim().toLowerCase().indexOf(inputValue) > -1,
|
|
172
|
-
);
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
const onChange = (
|
|
176
|
-
_event: SyntheticEvent,
|
|
177
|
-
{ newValue }: { newValue: string | number | Date | undefined },
|
|
178
|
-
) => {
|
|
179
|
-
if (debounceMs) {
|
|
180
|
-
if (debouncedSetFieldValueRef.current)
|
|
181
|
-
debouncedSetFieldValueRef.current(newValue);
|
|
182
|
-
if (debouncedSaveFieldValueRef.current)
|
|
183
|
-
debouncedSaveFieldValueRef.current(newValue);
|
|
184
|
-
} else {
|
|
185
|
-
if (setFieldValue) setFieldValue(newValue);
|
|
186
|
-
if (saveFieldvalue) saveFieldvalue(newValue);
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const defaultOnChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
|
191
|
-
if (type === 'tel') {
|
|
192
|
-
const nativeEvent = event.nativeEvent as InputEvent;
|
|
193
|
-
const isPasted = nativeEvent.inputType?.startsWith('insertFromPaste');
|
|
194
|
-
if (isPasted) {
|
|
195
|
-
const pasted: string = event.currentTarget.value;
|
|
196
|
-
const formatted = pasted.replace(/[./]/g, ' ');
|
|
197
|
-
event.currentTarget.value = formatted;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (setFieldValue) setFieldValue(event.currentTarget.value);
|
|
202
|
-
if (saveFieldvalue) saveFieldvalue(event.currentTarget.value);
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
let processedFieldValue = fieldValue;
|
|
206
|
-
if (label && label === 'Profile') {
|
|
207
|
-
processedFieldValue = formatSpaces(fieldValue as string);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (formContext && name) {
|
|
211
|
-
// With react-hook-form Controller
|
|
212
|
-
if (shouldAutoComplete && values?.length > 0) {
|
|
213
|
-
return (
|
|
214
|
-
<Autosuggest
|
|
215
|
-
suggestions={suggestions}
|
|
216
|
-
onSuggestionsFetchRequested={({ value }: { value: string }) => {
|
|
217
|
-
const newSuggestions = getSuggestions(value);
|
|
218
|
-
setSuggestions(newSuggestions);
|
|
219
|
-
}}
|
|
220
|
-
onSuggestionsClearRequested={() => setSuggestions([])}
|
|
221
|
-
getSuggestionValue={(item: string) => item}
|
|
222
|
-
renderSuggestion={(suggestion: string) => (
|
|
223
|
-
<span key={suggestion}>{suggestion}</span>
|
|
224
|
-
)}
|
|
225
|
-
highlightFirstSuggestion={true}
|
|
226
|
-
inputProps={{
|
|
227
|
-
value: String(processedFieldValue || ''),
|
|
228
|
-
onChange: (
|
|
229
|
-
_event: SyntheticEvent,
|
|
230
|
-
{ newValue }: { newValue: string },
|
|
231
|
-
) => {
|
|
232
|
-
if (formContext && name) {
|
|
233
|
-
formContext.setValue(name, newValue);
|
|
234
|
-
}
|
|
235
|
-
if (setFieldValue) setFieldValue(newValue);
|
|
236
|
-
if (saveFieldvalue) saveFieldvalue(newValue);
|
|
237
|
-
},
|
|
238
|
-
}}
|
|
239
|
-
renderInputComponent={({ value, onChange, ...props }) => (
|
|
240
|
-
<InputGroup>
|
|
241
|
-
{icon && <InputGroup.Text>{icon}</InputGroup.Text>}
|
|
242
|
-
<Form.Control
|
|
243
|
-
type={type}
|
|
244
|
-
// biome-ignore lint/suspicious/noExplicitAny: Type mismatch between Props and Form.Control - controlSize needs type assertion
|
|
245
|
-
size={controlSize as any}
|
|
246
|
-
min={min ?? undefined}
|
|
247
|
-
max={max ?? undefined}
|
|
248
|
-
required={required ?? false}
|
|
249
|
-
maxLength={maxLength ?? undefined}
|
|
250
|
-
placeholder={placeholder ?? undefined}
|
|
251
|
-
value={value}
|
|
252
|
-
onChange={onChange}
|
|
253
|
-
disabled={disabled}
|
|
254
|
-
readOnly={isReadOnly}
|
|
255
|
-
plaintext={isPlainText}
|
|
256
|
-
pattern={pattern}
|
|
257
|
-
isInvalid={isInvalid}
|
|
258
|
-
{...props}
|
|
259
|
-
/>
|
|
260
|
-
</InputGroup>
|
|
261
|
-
)}
|
|
262
|
-
/>
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return (
|
|
267
|
-
<Controller
|
|
268
|
-
name={name}
|
|
269
|
-
control={formContext.control}
|
|
270
|
-
rules={{
|
|
271
|
-
required: required ? `${label || 'This field'} is required` : false,
|
|
272
|
-
maxLength: maxLength
|
|
273
|
-
? {
|
|
274
|
-
value: maxLength,
|
|
275
|
-
message: `Maximum ${maxLength} characters allowed`,
|
|
276
|
-
}
|
|
277
|
-
: undefined,
|
|
278
|
-
min: min
|
|
279
|
-
? { value: Number(min), message: `Minimum value is ${min}` }
|
|
280
|
-
: undefined,
|
|
281
|
-
max: max
|
|
282
|
-
? { value: Number(max), message: `Maximum value is ${max}` }
|
|
283
|
-
: undefined,
|
|
284
|
-
pattern: pattern
|
|
285
|
-
? { value: new RegExp(pattern), message: 'Invalid format' }
|
|
286
|
-
: undefined,
|
|
287
|
-
}}
|
|
288
|
-
render={({ field: { value, ...field } }) => (
|
|
289
|
-
<Form.Control
|
|
290
|
-
{...field}
|
|
291
|
-
value={value ?? ''}
|
|
292
|
-
type={type}
|
|
293
|
-
// biome-ignore lint/suspicious/noExplicitAny: Type mismatch between Props and Form.Control - controlSize needs type assertion
|
|
294
|
-
size={controlSize as any}
|
|
295
|
-
min={min ?? undefined}
|
|
296
|
-
max={max ?? undefined}
|
|
297
|
-
required={required ?? false}
|
|
298
|
-
maxLength={maxLength ?? undefined}
|
|
299
|
-
placeholder={placeholder ?? undefined}
|
|
300
|
-
onChange={(e) => {
|
|
301
|
-
field.onChange(e);
|
|
302
|
-
if (setFieldValue) setFieldValue(e.target.value);
|
|
303
|
-
if (saveFieldvalue) saveFieldvalue(e.target.value);
|
|
304
|
-
}}
|
|
305
|
-
disabled={disabled}
|
|
306
|
-
readOnly={isReadOnly}
|
|
307
|
-
plaintext={isPlainText}
|
|
308
|
-
pattern={pattern}
|
|
309
|
-
isInvalid={isInvalid}
|
|
310
|
-
/>
|
|
311
|
-
)}
|
|
312
|
-
/>
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
// Without react-hook-form (fallback)
|
|
316
|
-
if (shouldAutoComplete && values?.length > 0) {
|
|
317
|
-
return (
|
|
318
|
-
<Autosuggest
|
|
319
|
-
suggestions={suggestions}
|
|
320
|
-
onSuggestionsFetchRequested={({ value }: { value: string }) => {
|
|
321
|
-
const newSuggestions = getSuggestions(value);
|
|
322
|
-
setSuggestions(newSuggestions);
|
|
323
|
-
}}
|
|
324
|
-
onSuggestionsClearRequested={() => setSuggestions([])}
|
|
325
|
-
getSuggestionValue={(item: string) => item}
|
|
326
|
-
renderSuggestion={(suggestion: string) => (
|
|
327
|
-
<span key={suggestion}>{suggestion}</span>
|
|
328
|
-
)}
|
|
329
|
-
highlightFirstSuggestion={true}
|
|
330
|
-
inputProps={{
|
|
331
|
-
value: String(processedFieldValue || ''),
|
|
332
|
-
onChange,
|
|
333
|
-
}}
|
|
334
|
-
renderInputComponent={(inputProps) => {
|
|
335
|
-
const { value, onChange, ...props } = inputProps;
|
|
336
|
-
return (
|
|
337
|
-
<InputGroup>
|
|
338
|
-
{icon && <InputGroup.Text>{icon}</InputGroup.Text>}
|
|
339
|
-
<Form.Control
|
|
340
|
-
type={type}
|
|
341
|
-
// biome-ignore lint/suspicious/noExplicitAny: Type mismatch between Props and Form.Control - controlSize needs type assertion
|
|
342
|
-
size={controlSize as any}
|
|
343
|
-
min={min ?? undefined}
|
|
344
|
-
max={max ?? undefined}
|
|
345
|
-
required={required ?? false}
|
|
346
|
-
maxLength={maxLength ?? undefined}
|
|
347
|
-
placeholder={placeholder ?? undefined}
|
|
348
|
-
value={String(value || '')}
|
|
349
|
-
onChange={onChange}
|
|
350
|
-
disabled={disabled}
|
|
351
|
-
readOnly={isReadOnly}
|
|
352
|
-
plaintext={isPlainText}
|
|
353
|
-
pattern={pattern}
|
|
354
|
-
{...props}
|
|
355
|
-
/>
|
|
356
|
-
</InputGroup>
|
|
357
|
-
);
|
|
358
|
-
}}
|
|
359
|
-
/>
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return (
|
|
364
|
-
<Form.Control
|
|
365
|
-
type={type}
|
|
366
|
-
// biome-ignore lint/suspicious/noExplicitAny: Type mismatch between Props and Form.Control - controlSize needs type assertion
|
|
367
|
-
size={controlSize as any}
|
|
368
|
-
min={min ?? undefined}
|
|
369
|
-
max={max ?? undefined}
|
|
370
|
-
required={required ?? false}
|
|
371
|
-
maxLength={maxLength ?? undefined}
|
|
372
|
-
placeholder={placeholder ?? undefined}
|
|
373
|
-
onChange={defaultOnChange}
|
|
374
|
-
value={processedFieldValue as Exclude<string | number | Date, Date>}
|
|
375
|
-
disabled={disabled}
|
|
376
|
-
readOnly={isReadOnly}
|
|
377
|
-
plaintext={isPlainText}
|
|
378
|
-
pattern={pattern}
|
|
379
|
-
/>
|
|
380
|
-
);
|
|
381
|
-
};
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { FormProvider, useForm } from 'react-hook-form';
|
|
4
|
-
import { CharacterCountInput } from '../lib/addons/CharacterCountInput';
|
|
5
|
-
import { TextAreaInput } from '../lib/components/TextAreaInput';
|
|
6
|
-
import { TextInput } from '../lib/components/TextInput';
|
|
7
|
-
|
|
8
|
-
const meta = {
|
|
9
|
-
title: 'Addons/CharacterCountInput',
|
|
10
|
-
component: CharacterCountInput,
|
|
11
|
-
parameters: {
|
|
12
|
-
layout: 'centered',
|
|
13
|
-
},
|
|
14
|
-
tags: ['autodocs'],
|
|
15
|
-
} satisfies Meta<typeof CharacterCountInput>;
|
|
16
|
-
|
|
17
|
-
export default meta;
|
|
18
|
-
type Story = StoryObj<typeof meta>;
|
|
19
|
-
|
|
20
|
-
const FormWrapper = ({ children }: { children: React.ReactNode }) => {
|
|
21
|
-
const methods = useForm({
|
|
22
|
-
defaultValues: {
|
|
23
|
-
username: '',
|
|
24
|
-
bio: '',
|
|
25
|
-
description: '',
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
return <FormProvider {...methods}>{children}</FormProvider>;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export const WithTextInput: Story = {
|
|
33
|
-
render: () => (
|
|
34
|
-
<FormWrapper>
|
|
35
|
-
<CharacterCountInput>
|
|
36
|
-
<TextInput name="username" label="Username" maxLength={50} />
|
|
37
|
-
</CharacterCountInput>
|
|
38
|
-
</FormWrapper>
|
|
39
|
-
),
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export const WithTextAreaInput: Story = {
|
|
43
|
-
render: () => (
|
|
44
|
-
<FormWrapper>
|
|
45
|
-
<CharacterCountInput>
|
|
46
|
-
<TextAreaInput name="bio" label="Biography" maxLength={500} />
|
|
47
|
-
</CharacterCountInput>
|
|
48
|
-
</FormWrapper>
|
|
49
|
-
),
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export const CustomWarningThreshold: Story = {
|
|
53
|
-
render: () => (
|
|
54
|
-
<FormWrapper>
|
|
55
|
-
<CharacterCountInput
|
|
56
|
-
warningThreshold={100}
|
|
57
|
-
warningClassName="text-warning"
|
|
58
|
-
dangerClassName="text-danger"
|
|
59
|
-
>
|
|
60
|
-
<TextAreaInput
|
|
61
|
-
name="description"
|
|
62
|
-
label="Description"
|
|
63
|
-
maxLength={500}
|
|
64
|
-
/>
|
|
65
|
-
</CharacterCountInput>
|
|
66
|
-
</FormWrapper>
|
|
67
|
-
),
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
export const CustomCounterFormat: Story = {
|
|
71
|
-
render: () => (
|
|
72
|
-
<FormWrapper>
|
|
73
|
-
<CharacterCountInput
|
|
74
|
-
formatCounter={(current, max) => (
|
|
75
|
-
<span style={{ fontWeight: 'bold' }}>
|
|
76
|
-
{current} of {max} characters used
|
|
77
|
-
</span>
|
|
78
|
-
)}
|
|
79
|
-
>
|
|
80
|
-
<TextInput name="username" label="Username" maxLength={50} />
|
|
81
|
-
</CharacterCountInput>
|
|
82
|
-
</FormWrapper>
|
|
83
|
-
),
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
export const AlwaysShowCounter: Story = {
|
|
87
|
-
render: () => (
|
|
88
|
-
<FormWrapper>
|
|
89
|
-
<CharacterCountInput showWhenEmpty={true}>
|
|
90
|
-
<TextInput name="username" label="Username" maxLength={50} placeholder="Counter always visible" />
|
|
91
|
-
</CharacterCountInput>
|
|
92
|
-
</FormWrapper>
|
|
93
|
-
),
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
export const HiddenCounter: Story = {
|
|
97
|
-
render: () => (
|
|
98
|
-
<FormWrapper>
|
|
99
|
-
<CharacterCountInput showCounter={false}>
|
|
100
|
-
<TextInput name="username" label="Username" maxLength={50} />
|
|
101
|
-
</CharacterCountInput>
|
|
102
|
-
</FormWrapper>
|
|
103
|
-
),
|
|
104
|
-
};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { FormProvider, useForm } from 'react-hook-form';
|
|
4
|
-
import { CheckInput } from '../lib/components/CheckInput';
|
|
5
|
-
|
|
6
|
-
const meta = {
|
|
7
|
-
title: 'Components/CheckInput',
|
|
8
|
-
component: CheckInput,
|
|
9
|
-
parameters: {
|
|
10
|
-
layout: 'centered',
|
|
11
|
-
},
|
|
12
|
-
tags: ['autodocs'],
|
|
13
|
-
} satisfies Meta<typeof CheckInput>;
|
|
14
|
-
|
|
15
|
-
export default meta;
|
|
16
|
-
type Story = StoryObj<typeof meta>;
|
|
17
|
-
|
|
18
|
-
const FormWrapper = ({ children }: { children: React.ReactNode }) => {
|
|
19
|
-
const methods = useForm({
|
|
20
|
-
defaultValues: {
|
|
21
|
-
agreeToTerms: false,
|
|
22
|
-
newsletter: false,
|
|
23
|
-
remember: false,
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
return <FormProvider {...methods}>{children}</FormProvider>;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export const Basic: Story = {
|
|
31
|
-
args: {
|
|
32
|
-
name: 'newsletter',
|
|
33
|
-
label: 'Subscribe to newsletter',
|
|
34
|
-
},
|
|
35
|
-
render: (args) => (
|
|
36
|
-
<FormWrapper>
|
|
37
|
-
<CheckInput {...args} />
|
|
38
|
-
</FormWrapper>
|
|
39
|
-
),
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export const Required: Story = {
|
|
43
|
-
args: {
|
|
44
|
-
name: 'agreeToTerms',
|
|
45
|
-
label: 'I agree to the terms and conditions',
|
|
46
|
-
required: true,
|
|
47
|
-
},
|
|
48
|
-
render: (args) => (
|
|
49
|
-
<FormWrapper>
|
|
50
|
-
<CheckInput {...args} />
|
|
51
|
-
</FormWrapper>
|
|
52
|
-
),
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export const Disabled: Story = {
|
|
56
|
-
args: {
|
|
57
|
-
name: 'remember',
|
|
58
|
-
label: 'Remember me',
|
|
59
|
-
disabled: true,
|
|
60
|
-
},
|
|
61
|
-
render: (args) => (
|
|
62
|
-
<FormWrapper>
|
|
63
|
-
<CheckInput {...args} />
|
|
64
|
-
</FormWrapper>
|
|
65
|
-
),
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export const DisabledChecked: Story = {
|
|
69
|
-
args: {
|
|
70
|
-
name: 'remember',
|
|
71
|
-
label: 'Remember me (checked & disabled)',
|
|
72
|
-
disabled: true,
|
|
73
|
-
value: true,
|
|
74
|
-
},
|
|
75
|
-
render: (args) => (
|
|
76
|
-
<FormWrapper>
|
|
77
|
-
<CheckInput {...args} />
|
|
78
|
-
</FormWrapper>
|
|
79
|
-
),
|
|
80
|
-
};
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { LocalizationProvider } from '@mui/x-date-pickers';
|
|
4
|
-
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
|
5
|
-
import { FormProvider, useForm } from 'react-hook-form';
|
|
6
|
-
import { DateInput } from '../lib/components/DateInput';
|
|
7
|
-
|
|
8
|
-
const meta = {
|
|
9
|
-
title: 'Components/DateInput',
|
|
10
|
-
component: DateInput,
|
|
11
|
-
parameters: {
|
|
12
|
-
layout: 'centered',
|
|
13
|
-
},
|
|
14
|
-
tags: ['autodocs'],
|
|
15
|
-
argTypes: {
|
|
16
|
-
textFieldSize: {
|
|
17
|
-
control: 'select',
|
|
18
|
-
options: ['small', 'medium'],
|
|
19
|
-
},
|
|
20
|
-
dateFormat: {
|
|
21
|
-
control: 'text',
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
decorators: [
|
|
25
|
-
(Story) => (
|
|
26
|
-
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
|
27
|
-
<Story />
|
|
28
|
-
</LocalizationProvider>
|
|
29
|
-
),
|
|
30
|
-
],
|
|
31
|
-
} satisfies Meta<typeof DateInput>;
|
|
32
|
-
|
|
33
|
-
export default meta;
|
|
34
|
-
type Story = StoryObj<typeof meta>;
|
|
35
|
-
|
|
36
|
-
const FormWrapper = ({ children }: { children: React.ReactNode }) => {
|
|
37
|
-
const methods = useForm({
|
|
38
|
-
defaultValues: {
|
|
39
|
-
birthdate: null,
|
|
40
|
-
appointmentDate: null,
|
|
41
|
-
deadline: null,
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
return <FormProvider {...methods}>{children}</FormProvider>;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export const Basic: Story = {
|
|
49
|
-
args: {
|
|
50
|
-
name: 'birthdate',
|
|
51
|
-
label: 'Date of Birth',
|
|
52
|
-
},
|
|
53
|
-
render: (args) => (
|
|
54
|
-
<FormWrapper>
|
|
55
|
-
<DateInput {...args} />
|
|
56
|
-
</FormWrapper>
|
|
57
|
-
),
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export const Required: Story = {
|
|
61
|
-
args: {
|
|
62
|
-
name: 'appointmentDate',
|
|
63
|
-
label: 'Appointment Date',
|
|
64
|
-
required: true,
|
|
65
|
-
},
|
|
66
|
-
render: (args) => (
|
|
67
|
-
<FormWrapper>
|
|
68
|
-
<DateInput {...args} />
|
|
69
|
-
</FormWrapper>
|
|
70
|
-
),
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export const DisableFuture: Story = {
|
|
74
|
-
args: {
|
|
75
|
-
name: 'birthdate',
|
|
76
|
-
label: 'Date of Birth',
|
|
77
|
-
disableFuture: true,
|
|
78
|
-
},
|
|
79
|
-
render: (args) => (
|
|
80
|
-
<FormWrapper>
|
|
81
|
-
<DateInput {...args} />
|
|
82
|
-
</FormWrapper>
|
|
83
|
-
),
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
export const DisablePast: Story = {
|
|
87
|
-
args: {
|
|
88
|
-
name: 'appointmentDate',
|
|
89
|
-
label: 'Appointment Date',
|
|
90
|
-
disablePast: true,
|
|
91
|
-
},
|
|
92
|
-
render: (args) => (
|
|
93
|
-
<FormWrapper>
|
|
94
|
-
<DateInput {...args} />
|
|
95
|
-
</FormWrapper>
|
|
96
|
-
),
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
export const CustomFormat: Story = {
|
|
100
|
-
args: {
|
|
101
|
-
name: 'deadline',
|
|
102
|
-
label: 'Deadline',
|
|
103
|
-
dateFormat: 'YYYY-MM-DD',
|
|
104
|
-
},
|
|
105
|
-
render: (args) => (
|
|
106
|
-
<FormWrapper>
|
|
107
|
-
<DateInput {...args} />
|
|
108
|
-
</FormWrapper>
|
|
109
|
-
),
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
export const YearMonthOnly: Story = {
|
|
113
|
-
args: {
|
|
114
|
-
name: 'deadline',
|
|
115
|
-
label: 'Select Month',
|
|
116
|
-
views: ['year', 'month'],
|
|
117
|
-
dateFormat: 'MM/YYYY',
|
|
118
|
-
},
|
|
119
|
-
render: (args) => (
|
|
120
|
-
<FormWrapper>
|
|
121
|
-
<DateInput {...args} />
|
|
122
|
-
</FormWrapper>
|
|
123
|
-
),
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
export const Disabled: Story = {
|
|
127
|
-
args: {
|
|
128
|
-
name: 'birthdate',
|
|
129
|
-
label: 'Date of Birth',
|
|
130
|
-
disabled: true,
|
|
131
|
-
},
|
|
132
|
-
render: (args) => (
|
|
133
|
-
<FormWrapper>
|
|
134
|
-
<DateInput {...args} />
|
|
135
|
-
</FormWrapper>
|
|
136
|
-
),
|
|
137
|
-
};
|