@capyx/components-library 0.0.14 → 0.0.15

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 CHANGED
@@ -1,18 +1,21 @@
1
- # Components Library
1
+ # Capyx Components Library
2
2
 
3
- A comprehensive React component library built with TypeScript, React 19, react-hook-form, react-bootstrap, and Material-UI (MUI).
3
+ The shared React component library for all Capyx applications.
4
+
5
+ Built with TypeScript and React 19, the library ships fully-typed ESM + CJS bundles. Components are opinionated about their UI stack (see [Coupled Dependencies](#coupled-dependencies)) so that every Capyx app has a consistent look and feel without having to re-configure the same set of libraries.
4
6
 
5
7
  ## Architecture
6
8
 
7
- This library follows a clear separation between **Components** and **Addons**:
9
+ The library is organised into two layers:
8
10
 
9
- ### Components (Base Input Types)
11
+ ### Components
10
12
 
11
- Components are specific input elements based on HTML input types and specialized use cases:
13
+ Self-contained visual components (currently focused on form inputs, but not restricted to them):
12
14
 
13
15
  - **CheckInput** - Checkbox input for boolean values
14
16
  - **DateInput** - Date picker with formatted string output (using MUI DatePicker)
15
17
  - **FileInput** - File upload with validation and preview
18
+ - **RichTextInput** - Rich text (WYSIWYG) editor using Quill. Grows with content by default; supports height-constrained / scrollable layouts (e.g. inside a modal)
16
19
  - **SelectInput** - Dropdown selection for single/multiple options
17
20
  - **SwitchInput** - Toggle switch for on/off values
18
21
  - **TagsInput** - Tag management with MUI Autocomplete
@@ -21,13 +24,58 @@ Components are specific input elements based on HTML input types and specialized
21
24
 
22
25
  ### Addons (Enhancement Wrappers)
23
26
 
24
- Addons wrap existing components to add functionality:
27
+ Addons wrap existing components to add behaviour without modifying the base component:
25
28
 
26
29
  - **AutocompleteInput** - Adds autocomplete/suggestions dropdown to text inputs
27
30
  - **CharacterCountInput** - Adds character counting to text, textarea, or editor inputs
28
31
  - **EditorAddon** - Wraps TextAreaInput to add rich text editing (ReactQuill)
29
32
  - **Editor** (Legacy) - Standalone rich text editor (deprecated, use EditorAddon instead)
30
33
 
34
+ ## Coupled Dependencies
35
+
36
+ The packages below are **production dependencies bundled with the library**. Every consuming application must have compatible versions installed. They are considered part of the library's core stack and should not be removed or swapped lightly.
37
+
38
+ ### UI foundation
39
+
40
+ | Package | Version | Role |
41
+ |---|---|---|
42
+ | `react` | `^19` | Core framework |
43
+ | `react-bootstrap` | `^2` | Layout, forms, modals, and all general UI primitives |
44
+ | `bootstrap` | `^5` | CSS layer required by `react-bootstrap` |
45
+
46
+ ### Form management
47
+
48
+ | Package | Version | Role |
49
+ |---|---|---|
50
+ | `react-hook-form` | `^7` | Form state, validation, and field registration — deeply integrated into most input components |
51
+
52
+ ### MUI (advanced components)
53
+
54
+ | Package | Version | Role |
55
+ |---|---|---|
56
+ | `@mui/material` | `^7` | MUI component primitives (used by `TagsInput`, `SelectInput`, etc.) |
57
+ | `@emotion/styled` | `^11` | Mandatory styling engine for MUI |
58
+ | `@mui/x-date-pickers` | `^8` | Date picker used by `DateInput` |
59
+
60
+ ### Date & time
61
+
62
+ | Package | Version | Role |
63
+ |---|---|---|
64
+ | `dayjs` | `^1` | Date adapter required by `@mui/x-date-pickers` |
65
+ | `dateformat` | `^5` | Output formatting for `DateInput` |
66
+
67
+ ### Rich text
68
+
69
+ | Package | Version | Role |
70
+ |---|---|---|
71
+ | `react-quill-new` | `^3` | Quill WYSIWYG editor used by `RichTextInput` |
72
+
73
+ ### Utilities
74
+
75
+ | Package | Version | Role |
76
+ |---|---|---|
77
+ | `lodash.debounce` | `^4` | Debouncing used in `AutocompleteInput` |
78
+
31
79
  ## Features
32
80
 
33
81
  - ✅ **React 19** compatible
@@ -102,6 +150,83 @@ function MyForm() {
102
150
  }
103
151
  ```
104
152
 
153
+ ### RichTextInput
154
+
155
+ `RichTextInput` is a Quill-based WYSIWYG editor that **grows with its content** by default.
156
+
157
+ #### Basic usage
158
+
159
+ ```tsx
160
+ import { RichTextInput } from '@your-package/components-library';
161
+
162
+ function MyForm() {
163
+ const [content, setContent] = useState('');
164
+
165
+ return (
166
+ <RichTextInput
167
+ value={content}
168
+ onChange={setContent}
169
+ maxLength={5000}
170
+ />
171
+ );
172
+ }
173
+ ```
174
+
175
+ #### Height-constrained layout (e.g. inside a modal)
176
+
177
+ By default the editor has no maximum height. In a constrained context — such as a Bootstrap `Modal` — you want the editor to grow until the modal fills the viewport, then scroll inside the editor instead of overflowing the page.
178
+
179
+ Three things are required:
180
+
181
+ 1. **`<Modal scrollable>`** — Bootstrap caps the modal body at the available viewport height.
182
+ 2. **`d-flex flex-column overflow-hidden` on `<Modal.Body>`** — turns the body into a flex column that constrains its children to the available height.
183
+ 3. **`style={{ flex: 1, minHeight: 0 }}` on `<RichTextInput>`** — `flex: 1` fills the remaining space; `minHeight: 0` is required in a flex context so the wrapper can shrink below its natural size and pass the overflow constraint down to Quill.
184
+
185
+ ```tsx
186
+ import { RichTextInput } from '@your-package/components-library';
187
+ import { Modal, Button } from 'react-bootstrap';
188
+
189
+ function MyModal({ show, onHide }) {
190
+ const [content, setContent] = useState('');
191
+
192
+ return (
193
+ <Modal show={show} onHide={onHide} scrollable>
194
+ <Modal.Header closeButton>
195
+ <Modal.Title>Edit content</Modal.Title>
196
+ </Modal.Header>
197
+
198
+ {/* flex column + overflow-hidden constrains children to available height */}
199
+ <Modal.Body className="d-flex flex-column overflow-hidden">
200
+ <RichTextInput
201
+ value={content}
202
+ onChange={setContent}
203
+ {/* flex:1 fills remaining space; minHeight:0 enables overflow */}
204
+ style={{ flex: 1, minHeight: 0 }}
205
+ />
206
+ </Modal.Body>
207
+
208
+ <Modal.Footer>
209
+ <Button variant="secondary" onClick={onHide}>Cancel</Button>
210
+ <Button variant="primary" onClick={onHide}>Save</Button>
211
+ </Modal.Footer>
212
+ </Modal>
213
+ );
214
+ }
215
+ ```
216
+
217
+ #### Props
218
+
219
+ | Prop | Type | Default | Description |
220
+ |---|---|---|---|
221
+ | `value` | `string` | `''` | Current editor content as an HTML string |
222
+ | `onChange` | `(value: string) => void` | — | Fired on every content change |
223
+ | `readonly` | `boolean` | `false` | Hides the toolbar and disables editing |
224
+ | `maxLength` | `number` | — | Hard character limit (measured against the raw HTML string). Shows a counter below the editor |
225
+ | `isInvalid` | `boolean` | `false` | Applies Bootstrap `is-invalid` border styling |
226
+ | `formats` | `FormatType[]` | `['header','bold','italic','underline','list']` | Allowed Quill formats |
227
+ | `style` | `CSSProperties` | — | Inline styles on the outer wrapper `div`. Use to constrain height in a flex context |
228
+ | `wrapperClassName` | `string` | — | Extra CSS class names on the outer wrapper `div` |
229
+
105
230
  ### Using Addons
106
231
 
107
232
  #### Autocomplete Addon
@@ -1,37 +1,89 @@
1
- import { type FC } from 'react';
1
+ import { type CSSProperties, type FC } from 'react';
2
2
  import 'react-quill-new/dist/quill.snow.css';
3
+ import './RichTextInput.css';
3
4
  declare const formats: readonly ["header", "bold", "color", "italic", "link", "strike", "script", "underline", "list", "code", "blockquote", "code-block"];
4
5
  type FormatType = typeof formats[number];
5
6
  /**
6
7
  * Props for the RichTextInput component
7
8
  */
8
9
  export type RichTextInputProps = {
9
- /** Whether the editor is in read-only mode */
10
+ /** Whether the editor is in read-only mode. Hides the toolbar when `true`. */
10
11
  readonly?: boolean;
11
- /** Maximum number of characters allowed in the editor */
12
+ /**
13
+ * Maximum number of characters allowed (measured against the raw HTML string).
14
+ * A character counter is shown below the editor when this prop is set.
15
+ * Typing is blocked once the limit is reached (Backspace is still allowed).
16
+ */
12
17
  maxLength?: number;
13
- /** Current value of the editor (HTML string) */
18
+ /** Current value of the editor as an HTML string. */
14
19
  value?: string;
15
- /** Callback function called when the content changes */
20
+ /** Callback fired on every content change, receiving the updated HTML string. */
16
21
  onChange?: (value: string) => void;
17
- /** Whether the input should be styled as invalid */
22
+ /** When `true` the editor border is styled as invalid (Bootstrap `is-invalid`). */
18
23
  isInvalid?: boolean;
19
- /** Supported formats for the editor */
24
+ /**
25
+ * Quill formats to enable. Defaults to `['header', 'bold', 'italic', 'underline', 'list']`.
26
+ * Only formats listed here can be applied even if the toolbar offers more.
27
+ */
20
28
  formats?: FormatType[];
29
+ /**
30
+ * Inline styles applied to the outer wrapper `div`.
31
+ *
32
+ * **Height / scroll behaviour**
33
+ *
34
+ * By default the editor grows with its content. Use this prop together with a
35
+ * flex context on the parent to cap the height and activate scrolling inside
36
+ * the editor once that cap is reached:
37
+ *
38
+ * ```tsx
39
+ * // Inside a Bootstrap Modal with the `scrollable` prop:
40
+ * <Modal.Body className="d-flex flex-column overflow-hidden">
41
+ * <RichTextInput
42
+ * value={content}
43
+ * onChange={setContent}
44
+ * style={{ flex: 1, minHeight: 0 }}
45
+ * />
46
+ * </Modal.Body>
47
+ * ```
48
+ *
49
+ * - `flex: 1` fills the remaining space in the flex parent.
50
+ * - `minHeight: 0` is required in a flex context so the wrapper can shrink
51
+ * below its natural size and hand the overflow constraint down to Quill.
52
+ */
53
+ style?: CSSProperties;
54
+ /** Extra CSS class names added to the outer wrapper `div`. */
55
+ wrapperClassName?: string;
21
56
  };
22
57
  /**
23
58
  * RichTextInput - A rich text editor component using ReactQuill
24
59
  *
25
- * Provides a WYSIWYG editor with support for headers, bold, italic, underline, and lists.
26
- * Includes optional character counting and length validation.
60
+ * Renders a Quill "snow" WYSIWYG editor. The editor **grows with its content**
61
+ * by default (no fixed height). To cap the height and enable scrolling — for
62
+ * example when the component is used inside a modal — pass `style` and set up
63
+ * a flex context on the parent (see `style` prop docs).
27
64
  *
28
- * @example
65
+ * @example Basic usage
29
66
  * ```tsx
30
- * <RichTextInput
31
- * value={content}
32
- * onChange={setContent}
33
- * maxLength={1000}
34
- * />
67
+ * <RichTextInput value={content} onChange={setContent} />
68
+ * ```
69
+ *
70
+ * @example With character limit
71
+ * ```tsx
72
+ * <RichTextInput value={content} onChange={setContent} maxLength={1000} />
73
+ * ```
74
+ *
75
+ * @example Inside a Bootstrap Modal (expand-then-scroll)
76
+ * ```tsx
77
+ * <Modal show={show} onHide={close} scrollable>
78
+ * <Modal.Header closeButton>Title</Modal.Header>
79
+ * <Modal.Body className="d-flex flex-column overflow-hidden">
80
+ * <RichTextInput
81
+ * value={content}
82
+ * onChange={setContent}
83
+ * style={{ flex: 1, minHeight: 0 }}
84
+ * />
85
+ * </Modal.Body>
86
+ * </Modal>
35
87
  * ```
36
88
  */
37
89
  export declare const RichTextInput: FC<RichTextInputProps>;
@@ -1 +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,QAAA,MAAM,OAAO,qIAAsI,CAAC;AACpJ,KAAK,UAAU,GAAG,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;AAEzC;;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;IACpB,uCAAuC;IACvC,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CA4EhD,CAAC"}
1
+ {"version":3,"file":"RichTextInput.d.ts","sourceRoot":"","sources":["../../lib/components/RichTextInput.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,EAAE,EAAwC,MAAM,OAAO,CAAC;AAG1F,OAAO,qCAAqC,CAAC;AAC7C,OAAO,qBAAqB,CAAC;AAE7B,QAAA,MAAM,OAAO,qIAAsI,CAAC;AACpJ,KAAK,UAAU,GAAG,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;AAEzC;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAChC,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,mFAAmF;IACnF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CAiFhD,CAAC"}
@@ -1,25 +1,43 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRef, useState } from 'react';
3
3
  import { Form } from 'react-bootstrap';
4
4
  import ReactQuill from 'react-quill-new';
5
5
  import 'react-quill-new/dist/quill.snow.css';
6
+ import './RichTextInput.css';
6
7
  const formats = ['header', 'bold', 'color', 'italic', 'link', 'strike', 'script', 'underline', 'list', 'code', 'blockquote', 'code-block'];
7
8
  /**
8
9
  * RichTextInput - A rich text editor component using ReactQuill
9
10
  *
10
- * Provides a WYSIWYG editor with support for headers, bold, italic, underline, and lists.
11
- * Includes optional character counting and length validation.
11
+ * Renders a Quill "snow" WYSIWYG editor. The editor **grows with its content**
12
+ * by default (no fixed height). To cap the height and enable scrolling — for
13
+ * example when the component is used inside a modal — pass `style` and set up
14
+ * a flex context on the parent (see `style` prop docs).
12
15
  *
13
- * @example
16
+ * @example Basic usage
14
17
  * ```tsx
15
- * <RichTextInput
16
- * value={content}
17
- * onChange={setContent}
18
- * maxLength={1000}
19
- * />
18
+ * <RichTextInput value={content} onChange={setContent} />
19
+ * ```
20
+ *
21
+ * @example With character limit
22
+ * ```tsx
23
+ * <RichTextInput value={content} onChange={setContent} maxLength={1000} />
24
+ * ```
25
+ *
26
+ * @example Inside a Bootstrap Modal (expand-then-scroll)
27
+ * ```tsx
28
+ * <Modal show={show} onHide={close} scrollable>
29
+ * <Modal.Header closeButton>Title</Modal.Header>
30
+ * <Modal.Body className="d-flex flex-column overflow-hidden">
31
+ * <RichTextInput
32
+ * value={content}
33
+ * onChange={setContent}
34
+ * style={{ flex: 1, minHeight: 0 }}
35
+ * />
36
+ * </Modal.Body>
37
+ * </Modal>
20
38
  * ```
21
39
  */
22
- export const RichTextInput = ({ readonly = false, maxLength, value = '', onChange, isInvalid = false, formats = ['header', 'bold', 'italic', 'underline', 'list'] }) => {
40
+ export const RichTextInput = ({ readonly = false, maxLength, value = '', onChange, isInvalid = false, formats = ['header', 'bold', 'italic', 'underline', 'list'], style, wrapperClassName, }) => {
23
41
  const reactQuillRef = useRef(null);
24
42
  const [count, setCount] = useState(value.length);
25
43
  const checkCharacterCount = (event) => {
@@ -54,5 +72,5 @@ export const RichTextInput = ({ readonly = false, maxLength, value = '', onChang
54
72
  ],
55
73
  };
56
74
  // const formats = ['header', 'bold', 'color', 'italic', 'link', 'strike', 'script', 'underline', 'list', 'code', 'blockquote', 'code-block'];
57
- 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"] }))] }));
75
+ return (_jsxs("div", { className: `ql-rich-text-wrapper${wrapperClassName ? ` ${wrapperClassName}` : ''}`, style: style, 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"] }))] }));
58
76
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capyx/components-library",
3
- "version": "0.0.14",
4
- "description": "Capyx Components Library for forms across applications",
3
+ "version": "0.0.15",
4
+ "description": "Shared React component library for Capyx applications",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -50,13 +50,13 @@
50
50
  "build-storybook": "storybook build"
51
51
  },
52
52
  "devDependencies": {
53
- "@biomejs/biome": "^2.4.4",
53
+ "@biomejs/biome": "^2.4.5",
54
54
  "@chromatic-com/storybook": "^5.0.1",
55
- "@storybook/addon-a11y": "^10.2.13",
56
- "@storybook/addon-docs": "^10.2.13",
57
- "@storybook/addon-onboarding": "^10.2.13",
58
- "@storybook/addon-vitest": "^10.2.13",
59
- "@storybook/react-vite": "^10.2.13",
55
+ "@storybook/addon-a11y": "^10.2.15",
56
+ "@storybook/addon-docs": "^10.2.15",
57
+ "@storybook/addon-onboarding": "^10.2.15",
58
+ "@storybook/addon-vitest": "^10.2.15",
59
+ "@storybook/react-vite": "^10.2.15",
60
60
  "@types/dateformat": "^5.0.3",
61
61
  "@types/lodash.debounce": "^4.0.9",
62
62
  "@types/node": "^25.3.3",
@@ -65,7 +65,7 @@
65
65
  "@vitest/browser-playwright": "^4.0.18",
66
66
  "@vitest/coverage-v8": "^4.0.18",
67
67
  "playwright": "^1.58.2",
68
- "storybook": "^10.2.13",
68
+ "storybook": "^10.2.15",
69
69
  "typescript": "^5.9.3",
70
70
  "vitest": "^4.0.18"
71
71
  },