@capyx/components-library 0.0.1 → 0.0.2

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.
Files changed (68) hide show
  1. package/README.md +20 -5
  2. package/dist/addons/CharacterCountInput.d.ts +73 -0
  3. package/dist/addons/CharacterCountInput.d.ts.map +1 -0
  4. package/dist/addons/CharacterCountInput.js +130 -0
  5. package/{lib/addons/index.ts → dist/addons/index.d.ts} +1 -0
  6. package/dist/addons/index.d.ts.map +1 -0
  7. package/dist/addons/index.js +1 -0
  8. package/dist/components/CheckInput.d.ts +49 -0
  9. package/dist/components/CheckInput.d.ts.map +1 -0
  10. package/dist/components/CheckInput.js +58 -0
  11. package/dist/components/DateInput.d.ts +63 -0
  12. package/dist/components/DateInput.d.ts.map +1 -0
  13. package/dist/components/DateInput.js +86 -0
  14. package/dist/components/FileInput.d.ts +102 -0
  15. package/dist/components/FileInput.d.ts.map +1 -0
  16. package/dist/components/FileInput.js +164 -0
  17. package/dist/components/RichTextInput.d.ts +34 -0
  18. package/dist/components/RichTextInput.d.ts.map +1 -0
  19. package/dist/components/RichTextInput.js +57 -0
  20. package/dist/components/SelectInput.d.ts +54 -0
  21. package/dist/components/SelectInput.d.ts.map +1 -0
  22. package/dist/components/SelectInput.js +64 -0
  23. package/dist/components/SwitchInput.d.ts +46 -0
  24. package/dist/components/SwitchInput.d.ts.map +1 -0
  25. package/dist/components/SwitchInput.js +53 -0
  26. package/dist/components/TagsInput.d.ts +35 -0
  27. package/dist/components/TagsInput.d.ts.map +1 -0
  28. package/dist/components/TagsInput.js +67 -0
  29. package/dist/components/TextAreaInput.d.ts +71 -0
  30. package/dist/components/TextAreaInput.d.ts.map +1 -0
  31. package/dist/components/TextAreaInput.js +113 -0
  32. package/dist/components/TextInput.d.ts +89 -0
  33. package/dist/components/TextInput.d.ts.map +1 -0
  34. package/dist/components/TextInput.js +177 -0
  35. package/dist/components/index.d.ts +10 -0
  36. package/dist/components/index.d.ts.map +1 -0
  37. package/dist/index.cjs +18 -0
  38. package/dist/index.d.ts +3 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/package.json +85 -70
  41. package/.storybook/main.ts +0 -33
  42. package/.storybook/preview.ts +0 -36
  43. package/.storybook/vitest.setup.ts +0 -7
  44. package/biome.json +0 -37
  45. package/lib/addons/CharacterCountInput.tsx +0 -204
  46. package/lib/components/CheckInput.tsx +0 -126
  47. package/lib/components/DateInput.tsx +0 -179
  48. package/lib/components/FileInput.tsx +0 -353
  49. package/lib/components/RichTextInput.tsx +0 -112
  50. package/lib/components/SelectInput.tsx +0 -144
  51. package/lib/components/SwitchInput.tsx +0 -116
  52. package/lib/components/TagsInput.tsx +0 -118
  53. package/lib/components/TextAreaInput.tsx +0 -211
  54. package/lib/components/TextInput.tsx +0 -381
  55. package/stories/CharacterCountInput.stories.tsx +0 -104
  56. package/stories/CheckInput.stories.tsx +0 -80
  57. package/stories/DateInput.stories.tsx +0 -137
  58. package/stories/FileInput.stories.tsx +0 -125
  59. package/stories/RichTextInput.stories.tsx +0 -77
  60. package/stories/SelectInput.stories.tsx +0 -131
  61. package/stories/SwitchInput.stories.tsx +0 -80
  62. package/stories/TagsInput.stories.tsx +0 -69
  63. package/stories/TextAreaInput.stories.tsx +0 -117
  64. package/stories/TextInput.stories.tsx +0 -167
  65. package/vitest.config.ts +0 -37
  66. package/vitest.shims.d.ts +0 -1
  67. /package/{lib/components/index.ts → dist/components/index.js} +0 -0
  68. /package/{lib/index.ts → dist/index.js} +0 -0
@@ -1,7 +0,0 @@
1
- import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
2
- import { setProjectAnnotations } from '@storybook/react-vite';
3
- import * as projectAnnotations from './preview';
4
-
5
- // This is an important step to apply the right configuration when testing your stories.
6
- // More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
7
- setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
package/biome.json DELETED
@@ -1,37 +0,0 @@
1
- {
2
- "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
3
- "vcs": {
4
- "enabled": false,
5
- "clientKind": "git",
6
- "useIgnoreFile": false
7
- },
8
- "files": {
9
- "ignoreUnknown": false
10
- },
11
- "formatter": {
12
- "enabled": true,
13
- "indentStyle": "tab"
14
- },
15
- "linter": {
16
- "enabled": true,
17
- "rules": {
18
- "recommended": true,
19
- "suspicious": {
20
- "noExplicitAny": "warn"
21
- }
22
- }
23
- },
24
- "javascript": {
25
- "formatter": {
26
- "quoteStyle": "single"
27
- }
28
- },
29
- "assist": {
30
- "enabled": true,
31
- "actions": {
32
- "source": {
33
- "organizeImports": "on"
34
- }
35
- }
36
- }
37
- }
@@ -1,204 +0,0 @@
1
- import React, {
2
- type FC,
3
- type PropsWithChildren,
4
- type ReactElement,
5
- type ReactNode,
6
- useEffect,
7
- useRef,
8
- useState,
9
- } from 'react';
10
- import { Form } from 'react-bootstrap';
11
-
12
- const DEFAULT_VALID_HTML_ELEMENTS = [
13
- 'text',
14
- 'email',
15
- 'password',
16
- 'search',
17
- 'tel',
18
- 'url',
19
- 'textarea',
20
- ];
21
-
22
- /**
23
- * Props for the CharacterCountInput component
24
- */
25
- export type CharacterCountInputProps = PropsWithChildren<{
26
- /** Array of valid HTML element types to monitor (e.g., ["text", "email", "textarea"]) */
27
- validHTMLElements?: string[];
28
- /** CSS class for the container wrapper */
29
- containerClassName?: string;
30
- /** CSS class for the character counter element */
31
- counterClassName?: string;
32
- /** CSS class applied when character count is normal */
33
- normalClassName?: string;
34
- /** CSS class applied when character count is near the limit */
35
- warningClassName?: string;
36
- /** CSS class applied when character count reaches the limit */
37
- dangerClassName?: string;
38
- /** Number of characters remaining before warning state (default: 10) */
39
- warningThreshold?: number;
40
- /** Custom function to format the counter display */
41
- formatCounter?: (current: number, max: number) => ReactNode;
42
- /** Whether to show the character counter (default: true) */
43
- showCounter?: boolean;
44
- /** Whether to show counter even when input is empty (default: false) */
45
- showWhenEmpty?: boolean;
46
- }>;
47
-
48
- /**
49
- * A wrapper component that adds a character counter to text inputs and textareas.
50
- * Automatically detects the maxLength property from the child input and displays
51
- * the current character count with visual feedback.
52
- *
53
- * Features:
54
- * - Automatic character counting from input/textarea children
55
- * - Visual feedback with customizable warning and danger states
56
- * - Configurable warning threshold
57
- * - Custom counter formatting
58
- * - Support for multiple input types (text, email, password, textarea, etc.)
59
- * - Show/hide counter toggle
60
- *
61
- * @example
62
- * // Basic usage with TextInput
63
- * <CharacterCountInput>
64
- * <TextInput
65
- * name="bio"
66
- * type="text"
67
- * maxLength={100}
68
- * placeholder="Enter your bio"
69
- * />
70
- * </CharacterCountInput>
71
- *
72
- * @example
73
- * // With custom styling and warning threshold
74
- * <CharacterCountInput
75
- * warningThreshold={20}
76
- * normalClassName="text-muted"
77
- * warningClassName="text-warning"
78
- * dangerClassName="text-danger"
79
- * >
80
- * <TextAreaInput name="message" maxLength={200} />
81
- * </CharacterCountInput>
82
- *
83
- * @example
84
- * // With custom counter format
85
- * <CharacterCountInput
86
- * formatCounter={(current, max) => (
87
- * <span>{max - current} characters remaining</span>
88
- * )}
89
- * >
90
- * <TextInput name="title" maxLength={50} />
91
- * </CharacterCountInput>
92
- */
93
- export const CharacterCountInput: FC<CharacterCountInputProps> = ({
94
- validHTMLElements,
95
- children,
96
- containerClassName = 'text-end',
97
- counterClassName = '',
98
- normalClassName = 'text-muted',
99
- warningClassName = 'text-warning',
100
- dangerClassName = 'text-danger',
101
- warningThreshold = 10,
102
- formatCounter,
103
- showCounter = true,
104
- showWhenEmpty = false,
105
- }) => {
106
- const containerRef = useRef<HTMLDivElement>(null);
107
- const [characterCount, setCharacterCount] = useState<number>(0);
108
- const [maxLength, setMaxLength] = useState<number | null>(null);
109
-
110
- useEffect(() => {
111
- if (!containerRef.current) return;
112
-
113
- // Extract maxLength from child props
114
- const childProps = (
115
- children as ReactElement<{
116
- type?: string;
117
- as?: string;
118
- maxLength?: number;
119
- }>
120
- )?.props;
121
-
122
- if (childProps?.maxLength) {
123
- setMaxLength(childProps.maxLength);
124
- }
125
-
126
- // Use MutationObserver to wait for input element to be rendered
127
- const findAndAttachInput = () => {
128
- const input = containerRef.current?.querySelector('input, textarea') as
129
- | HTMLInputElement
130
- | HTMLTextAreaElement
131
- | null;
132
-
133
- if (!input) return null;
134
-
135
- const inputReader = () => {
136
- setCharacterCount(input.value?.length ?? 0);
137
- };
138
-
139
- input.addEventListener('input', inputReader);
140
- input.addEventListener('keyup', inputReader);
141
- inputReader(); // Initial read
142
-
143
- return () => {
144
- input.removeEventListener('input', inputReader);
145
- input.removeEventListener('keyup', inputReader);
146
- };
147
- };
148
-
149
- // Try to find input immediately
150
- let cleanup = findAndAttachInput();
151
-
152
- // If not found, wait for it to be rendered
153
- if (!cleanup && containerRef.current) {
154
- const observer = new MutationObserver(() => {
155
- if (!cleanup) {
156
- cleanup = findAndAttachInput();
157
- if (cleanup) {
158
- observer.disconnect();
159
- }
160
- }
161
- });
162
-
163
- observer.observe(containerRef.current, {
164
- childList: true,
165
- subtree: true,
166
- });
167
-
168
- return () => {
169
- observer.disconnect();
170
- cleanup?.();
171
- };
172
- }
173
-
174
- return cleanup || undefined;
175
- }, [children, validHTMLElements]);
176
-
177
- const getCounterClassName = (): string => {
178
- if (maxLength === null) return normalClassName;
179
-
180
- if (characterCount === maxLength) return dangerClassName;
181
- if (characterCount >= maxLength - warningThreshold) return warningClassName;
182
-
183
- return normalClassName;
184
- };
185
-
186
- const renderCounter = (): ReactNode => {
187
- if (!showCounter || maxLength === null) return null;
188
- if (!showWhenEmpty && characterCount === 0) return null;
189
-
190
- const className = `${counterClassName} ${getCounterClassName()}`.trim();
191
- const content = formatCounter
192
- ? formatCounter(characterCount, maxLength)
193
- : `${characterCount} / ${maxLength}`;
194
-
195
- return <Form.Text className={className}>{content}</Form.Text>;
196
- };
197
-
198
- return (
199
- <div ref={containerRef} className={containerClassName}>
200
- {children}
201
- {renderCounter()}
202
- </div>
203
- );
204
- };
@@ -1,126 +0,0 @@
1
- import React, { type FC } from 'react';
2
- import { Form } from 'react-bootstrap';
3
- import { Controller, useFormContext } from 'react-hook-form';
4
-
5
- /**
6
- * Props for the CheckInput component
7
- */
8
- export type CheckInputProps = {
9
- /** The name of the checkbox input field */
10
- name: string;
11
- /** Label text displayed next to the checkbox */
12
- label?: string;
13
- /** Whether the checkbox is required */
14
- required?: boolean;
15
- /** Current checked state (standalone mode) */
16
- value?: boolean;
17
- /** Callback function called when the checked state changes */
18
- onChange?: (checked: boolean) => void;
19
- /** Callback function called when the checkbox loses focus */
20
- onBlur?: () => void;
21
- /** Whether the checkbox is disabled */
22
- disabled?: boolean;
23
- /** Custom HTML id for the checkbox element */
24
- id?: string;
25
- };
26
-
27
- /**
28
- * CheckInput - A checkbox input component with react-hook-form integration
29
- *
30
- * Provides a checkbox input that works both standalone and with react-hook-form.
31
- * Automatically integrates with FormProvider when available, providing validation
32
- * and error handling.
33
- *
34
- * @example
35
- * ```tsx
36
- * // With react-hook-form
37
- * <CheckInput
38
- * name="agreeToTerms"
39
- * label="I agree to the terms"
40
- * required
41
- * />
42
- *
43
- * // Standalone mode
44
- * <CheckInput
45
- * name="newsletter"
46
- * label="Subscribe to newsletter"
47
- * value={subscribed}
48
- * onChange={setSubscribed}
49
- * />
50
- * ```
51
- */
52
- export const CheckInput: FC<CheckInputProps> = ({
53
- name,
54
- label,
55
- required = false,
56
- value,
57
- onChange,
58
- onBlur,
59
- disabled = false,
60
- id,
61
- }) => {
62
- const formContext = useFormContext();
63
-
64
- // Helper to safely get nested error
65
- const getFieldError = (fieldName: string) => {
66
- try {
67
- const error = formContext?.formState?.errors?.[fieldName];
68
- return error?.message as string | undefined;
69
- } catch {
70
- return undefined;
71
- }
72
- };
73
-
74
- const errorMessage = getFieldError(name);
75
- const inputId = id || `check-input-${name}`;
76
-
77
- // Integrated with react-hook-form
78
- if (formContext) {
79
- return (
80
- <Controller
81
- name={name}
82
- control={formContext.control}
83
- rules={{
84
- required: required ? `${label || 'This field'} is required` : false,
85
- }}
86
- render={({ field }) => (
87
- <Form.Check
88
- {...field}
89
- id={inputId}
90
- type="checkbox"
91
- label={label}
92
- checked={field.value ?? false}
93
- onChange={(e) => {
94
- const checked = e.target.checked;
95
- field.onChange(checked);
96
- onChange?.(checked);
97
- }}
98
- onBlur={() => {
99
- field.onBlur();
100
- onBlur?.();
101
- }}
102
- disabled={disabled}
103
- required={required}
104
- isInvalid={!!errorMessage}
105
- feedback={errorMessage}
106
- feedbackType="invalid"
107
- />
108
- )}
109
- />
110
- );
111
- }
112
-
113
- // Standalone mode (no form context)
114
- return (
115
- <Form.Check
116
- id={inputId}
117
- type="checkbox"
118
- label={label}
119
- checked={value ?? false}
120
- onChange={(e) => onChange?.(e.target.checked)}
121
- onBlur={onBlur}
122
- disabled={disabled}
123
- required={required}
124
- />
125
- );
126
- };
@@ -1,179 +0,0 @@
1
- import { DatePicker } from '@mui/x-date-pickers';
2
- import type { Dayjs } from 'dayjs';
3
- import dayjs from 'dayjs';
4
- import React, { type FC } from 'react';
5
- import { Form } from 'react-bootstrap';
6
- import { Controller, useFormContext } from 'react-hook-form';
7
-
8
- /**
9
- * Props for the DateInput component
10
- */
11
- export type DateInputProps = {
12
- /** The name of the date input field */
13
- name: string;
14
- /** Label text displayed for the date picker */
15
- label?: string;
16
- /** Current date value (standalone mode) */
17
- value?: Date | null;
18
- /** Callback function called when the date changes */
19
- onChange?: (date: Date | null) => void;
20
- /** Callback function called when the input loses focus */
21
- onBlur?: () => void;
22
- /** Whether the date field is required */
23
- required?: boolean;
24
- /** Whether the date picker is disabled */
25
- disabled?: boolean;
26
- /** Minimum selectable date */
27
- minDate?: Date;
28
- /** Maximum selectable date */
29
- maxDate?: Date;
30
- /** Date format string (default: "DD/MM/YYYY") */
31
- dateFormat?: string;
32
- /** Size of the text field (default: "small") */
33
- textFieldSize?: 'small' | 'medium';
34
- /** Whether to disable future dates */
35
- disableFuture?: boolean;
36
- /** Whether to disable past dates */
37
- disablePast?: boolean;
38
- /** Which calendar views to display */
39
- views?: Array<'year' | 'month' | 'day'>;
40
- };
41
-
42
- /**
43
- * DateInput - A date picker component using Material-UI with react-hook-form integration
44
- *
45
- * Provides a date picker with calendar interface that works both standalone and with
46
- * react-hook-form. Uses MUI DatePicker internally with customizable date ranges,
47
- * formats, and validation.
48
- *
49
- * @example
50
- * ```tsx
51
- * // With react-hook-form
52
- * <DateInput
53
- * name="birthdate"
54
- * label="Date of Birth"
55
- * required
56
- * disableFuture
57
- * />
58
- *
59
- * // Standalone mode
60
- * <DateInput
61
- * name="appointmentDate"
62
- * label="Select Date"
63
- * value={selectedDate}
64
- * onChange={setSelectedDate}
65
- * minDate={new Date()}
66
- * />
67
- * ```
68
- */
69
- export const DateInput: FC<DateInputProps> = ({
70
- name,
71
- label,
72
- value,
73
- onChange,
74
- onBlur,
75
- required = false,
76
- disabled = false,
77
- minDate,
78
- maxDate,
79
- dateFormat = 'DD/MM/YYYY',
80
- textFieldSize = 'small',
81
- disableFuture = false,
82
- disablePast = false,
83
- views,
84
- }) => {
85
- const formContext = useFormContext();
86
-
87
- // Helper to safely get nested error
88
- const getFieldError = (fieldName: string) => {
89
- try {
90
- const error = formContext?.formState?.errors?.[fieldName];
91
- return error?.message as string | undefined;
92
- } catch {
93
- return undefined;
94
- }
95
- };
96
-
97
- const errorMessage = getFieldError(name);
98
-
99
- // Convert Date to Dayjs
100
- const convertToDateValue = (date: Date | null | undefined): Dayjs | undefined => {
101
- if (!date) return undefined;
102
- return dayjs(date);
103
- };
104
-
105
- // Integrated with react-hook-form
106
- if (formContext) {
107
- return (
108
- <Controller
109
- name={name}
110
- control={formContext.control}
111
- rules={{
112
- required: required ? `${label || 'This field'} is required` : false,
113
- }}
114
- render={({ field }) => (
115
- <Form.Group>
116
- <DatePicker
117
- label={label}
118
- value={convertToDateValue(field.value)}
119
- onChange={(newValue: Dayjs | null) => {
120
- const dateValue = newValue?.isValid()
121
- ? newValue.toDate()
122
- : null;
123
- field.onChange(dateValue);
124
- onChange?.(dateValue);
125
- }}
126
- disabled={disabled}
127
- format={dateFormat}
128
- minDate={convertToDateValue(minDate)}
129
- maxDate={convertToDateValue(maxDate)}
130
- disableFuture={disableFuture}
131
- disablePast={disablePast}
132
- views={views}
133
- slotProps={{
134
- textField: {
135
- size: textFieldSize,
136
- required: required,
137
- error: !!errorMessage,
138
- helperText: errorMessage,
139
- onBlur: () => {
140
- field.onBlur();
141
- onBlur?.();
142
- },
143
- },
144
- }}
145
- />
146
- </Form.Group>
147
- )}
148
- />
149
- );
150
- }
151
-
152
- // Standalone mode (no form context)
153
- return (
154
- <Form.Group>
155
- <DatePicker
156
- label={label}
157
- value={convertToDateValue(value)}
158
- onChange={(newValue: Dayjs | null) => {
159
- const dateValue = newValue?.isValid() ? newValue.toDate() : null;
160
- onChange?.(dateValue);
161
- }}
162
- disabled={disabled}
163
- format={dateFormat}
164
- minDate={convertToDateValue(minDate)}
165
- maxDate={convertToDateValue(maxDate)}
166
- disableFuture={disableFuture}
167
- disablePast={disablePast}
168
- views={views}
169
- slotProps={{
170
- textField: {
171
- size: textFieldSize,
172
- required: required,
173
- onBlur: onBlur,
174
- },
175
- }}
176
- />
177
- </Form.Group>
178
- );
179
- };