@capyx/components-library 0.0.10 → 0.0.11

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.
@@ -7,6 +7,18 @@ export type AutocompleteInputProps = PropsWithChildren<{
7
7
  suggestions: string[];
8
8
  /** Whether to highlight the first suggestion (default: true) */
9
9
  highlightFirstSuggestion?: boolean;
10
+ /** Custom class name for the suggestions dropdown container */
11
+ listClassName?: string;
12
+ /** Custom style for the suggestions dropdown container */
13
+ listStyle?: React.CSSProperties;
14
+ /** Custom class name for individual suggestion items */
15
+ itemClassName?: string;
16
+ /** Custom style for individual suggestion items */
17
+ itemStyle?: React.CSSProperties;
18
+ /** Custom class name for the currently highlighted suggestion item */
19
+ activeItemClassName?: string;
20
+ /** Custom style for the currently highlighted suggestion item */
21
+ activeItemStyle?: React.CSSProperties;
10
22
  }>;
11
23
  /**
12
24
  * AutocompleteInput - A wrapper addon that adds autocomplete/suggestions to text inputs
@@ -1 +1 @@
1
- {"version":3,"file":"AutocompleteInput.d.ts","sourceRoot":"","sources":["../../lib/addons/AutocompleteInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,iBAAiB,EAAgC,MAAM,OAAO,CAAC;AAKjF;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,CAAC;IACtD,kDAAkD;IAClD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,gEAAgE;IAChE,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACnC,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CAgHxD,CAAC"}
1
+ {"version":3,"file":"AutocompleteInput.d.ts","sourceRoot":"","sources":["../../lib/addons/AutocompleteInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAiB,iBAAiB,EAAgB,MAAM,OAAO,CAAC;AAKhF;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,CAAC;IACtD,kDAAkD;IAClD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,gEAAgE;IAChE,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAChC,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mDAAmD;IACnD,SAAS,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAChC,sEAAsE;IACtE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iEAAiE;IACjE,eAAe,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CACtC,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CA6LxD,CAAC"}
@@ -1,6 +1,6 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { cloneElement, useEffect, useRef, useState } from 'react';
3
- import Autosuggest from 'react-autosuggest';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cloneElement, useCallback, useEffect, useRef, useState } from 'react';
3
+ import { ListGroup } from 'react-bootstrap';
4
4
  import { useFormContext } from 'react-hook-form';
5
5
  /**
6
6
  * AutocompleteInput - A wrapper addon that adds autocomplete/suggestions to text inputs
@@ -30,72 +30,116 @@ import { useFormContext } from 'react-hook-form';
30
30
  * </AutocompleteInput>
31
31
  * ```
32
32
  */
33
- export const AutocompleteInput = ({ suggestions: allSuggestions, children, highlightFirstSuggestion = true, }) => {
33
+ export const AutocompleteInput = ({ suggestions: allSuggestions, children, highlightFirstSuggestion = true, listClassName, listStyle, itemClassName, itemStyle, activeItemClassName, activeItemStyle, }) => {
34
34
  const formContext = useFormContext();
35
35
  const [filteredSuggestions, setFilteredSuggestions] = useState([]);
36
- const [inputValue, setInputValue] = useState('');
37
- const nameRef = useRef('');
38
- // Extract child props
36
+ const [showSuggestions, setShowSuggestions] = useState(false);
37
+ const [highlightedIndex, setHighlightedIndex] = useState(-1);
38
+ const containerRef = useRef(null);
39
+ const listRef = useRef(null);
40
+ // Extract child element and its props
39
41
  const childElement = children;
40
42
  const childProps = childElement?.props;
41
43
  const name = childProps?.name || '';
42
- nameRef.current = name;
43
- // Sync input value with form context or child value
44
- useEffect(() => {
45
- if (formContext && name) {
46
- const formValue = formContext.getValues(name);
47
- setInputValue(formValue || '');
44
+ // Filter suggestions based on the current input value
45
+ const filterSuggestions = useCallback((value) => {
46
+ const trimmed = value.trim().toLowerCase();
47
+ if (trimmed.length === 0) {
48
+ setFilteredSuggestions([]);
49
+ setShowSuggestions(false);
50
+ return;
48
51
  }
49
- else if (childProps?.value !== undefined) {
50
- setInputValue(String(childProps.value));
51
- }
52
- }, [formContext, name, childProps?.value]);
53
- // Filter suggestions based on input value
54
- const getSuggestions = (value) => {
55
- const trimmedValue = value.trim().toLowerCase();
56
- if (trimmedValue.length === 0)
57
- return [];
58
- return allSuggestions.filter((suggestion) => suggestion.toLowerCase().includes(trimmedValue));
59
- };
60
- const onSuggestionsFetchRequested = ({ value }) => {
61
- setFilteredSuggestions(getSuggestions(value));
62
- };
63
- const onSuggestionsClearRequested = () => {
64
- setFilteredSuggestions([]);
65
- };
66
- const getSuggestionValue = (suggestion) => suggestion;
67
- const renderSuggestion = (suggestion) => _jsx("span", { children: suggestion });
68
- const handleChange = (_event, { newValue }) => {
69
- setInputValue(newValue);
70
- // Update form context if available
52
+ const filtered = allSuggestions.filter((s) => s.toLowerCase().includes(trimmed));
53
+ setFilteredSuggestions(filtered);
54
+ setShowSuggestions(filtered.length > 0);
55
+ setHighlightedIndex(highlightFirstSuggestion ? 0 : -1);
56
+ }, [allSuggestions, highlightFirstSuggestion]);
57
+ // Apply a selected suggestion to the input
58
+ const selectSuggestion = useCallback((suggestion) => {
71
59
  if (formContext && name) {
72
- formContext.setValue(name, newValue);
60
+ formContext.setValue(name, suggestion, { shouldValidate: true });
73
61
  }
74
- // Call child's onChange if provided
62
+ childProps?.onChange?.(suggestion);
63
+ setShowSuggestions(false);
64
+ setFilteredSuggestions([]);
65
+ }, [formContext, name, childProps?.onChange]);
66
+ // Intercept onChange from the child to filter suggestions
67
+ const handleChange = useCallback((newValue) => {
75
68
  childProps?.onChange?.(newValue);
76
- };
77
- return (_jsx(Autosuggest, { suggestions: filteredSuggestions, onSuggestionsFetchRequested: onSuggestionsFetchRequested, onSuggestionsClearRequested: onSuggestionsClearRequested, getSuggestionValue: getSuggestionValue, renderSuggestion: renderSuggestion, highlightFirstSuggestion: highlightFirstSuggestion, inputProps: {
78
- value: inputValue,
79
- onChange: handleChange,
80
- }, renderInputComponent: (inputProps) => {
81
- // Clone the child element and merge with autosuggest props
82
- const { value, onChange: autosuggestOnChange, ...autosuggestProps } = inputProps;
83
- return cloneElement(childElement, {
84
- ...autosuggestProps,
85
- value,
86
- onChange: (newValue) => {
87
- // Handle both event objects and direct values
88
- const actualValue = typeof newValue === 'string'
89
- ? newValue
90
- : newValue?.target
91
- ?.value || '';
92
- // Create synthetic event for Autosuggest
93
- const syntheticEvent = {};
94
- autosuggestOnChange?.(syntheticEvent, {
95
- newValue: actualValue,
96
- method: 'type',
97
- });
98
- },
99
- });
100
- } }));
69
+ filterSuggestions(newValue);
70
+ }, [childProps?.onChange, filterSuggestions]);
71
+ // Keyboard navigation for the suggestions dropdown
72
+ const handleKeyDown = useCallback((e) => {
73
+ if (!showSuggestions || filteredSuggestions.length === 0)
74
+ return;
75
+ switch (e.key) {
76
+ case 'ArrowDown':
77
+ e.preventDefault();
78
+ setHighlightedIndex((prev) => prev < filteredSuggestions.length - 1 ? prev + 1 : 0);
79
+ break;
80
+ case 'ArrowUp':
81
+ e.preventDefault();
82
+ setHighlightedIndex((prev) => prev > 0 ? prev - 1 : filteredSuggestions.length - 1);
83
+ break;
84
+ case 'Enter':
85
+ if (highlightedIndex >= 0 &&
86
+ highlightedIndex < filteredSuggestions.length) {
87
+ e.preventDefault();
88
+ selectSuggestion(filteredSuggestions[highlightedIndex]);
89
+ }
90
+ break;
91
+ case 'Escape':
92
+ setShowSuggestions(false);
93
+ break;
94
+ }
95
+ }, [showSuggestions, filteredSuggestions, highlightedIndex, selectSuggestion]);
96
+ // Scroll the highlighted item into view when navigating with keyboard
97
+ useEffect(() => {
98
+ if (highlightedIndex < 0 || !listRef.current)
99
+ return;
100
+ const items = listRef.current.querySelectorAll('[role="option"]');
101
+ const item = items[highlightedIndex];
102
+ item?.scrollIntoView({ block: 'nearest' });
103
+ }, [highlightedIndex]);
104
+ // Close suggestions when clicking outside
105
+ useEffect(() => {
106
+ const handleClickOutside = (e) => {
107
+ if (containerRef.current &&
108
+ !containerRef.current.contains(e.target)) {
109
+ setShowSuggestions(false);
110
+ }
111
+ };
112
+ document.addEventListener('mousedown', handleClickOutside);
113
+ return () => document.removeEventListener('mousedown', handleClickOutside);
114
+ }, []);
115
+ // Clone the child element, overriding onChange and onBlur to hook into the autocomplete logic
116
+ const enhancedChild = cloneElement(childElement, {
117
+ onChange: handleChange,
118
+ onBlur: () => {
119
+ // Delay closing so a suggestion click can register first
120
+ setTimeout(() => setShowSuggestions(false), 150);
121
+ childProps?.onBlur?.();
122
+ },
123
+ });
124
+ return (_jsxs("div", { ref: containerRef, style: { position: 'relative' }, onKeyDown: handleKeyDown, children: [enhancedChild, showSuggestions && filteredSuggestions.length > 0 && (_jsx(ListGroup, { ref: listRef, role: "listbox", className: listClassName, style: {
125
+ position: 'absolute',
126
+ top: '100%',
127
+ left: 0,
128
+ right: 0,
129
+ zIndex: 1000,
130
+ maxHeight: '200px',
131
+ overflowY: 'auto',
132
+ boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
133
+ ...listStyle,
134
+ }, children: filteredSuggestions.map((suggestion, index) => {
135
+ const isActive = index === highlightedIndex;
136
+ return (_jsx(ListGroup.Item, { role: "option", action: true, active: isActive, "aria-selected": isActive, className: isActive ? activeItemClassName : itemClassName, onMouseDown: (e) => {
137
+ e.preventDefault();
138
+ selectSuggestion(suggestion);
139
+ }, onMouseEnter: () => setHighlightedIndex(index), style: {
140
+ cursor: 'pointer',
141
+ ...itemStyle,
142
+ ...(isActive ? activeItemStyle : undefined),
143
+ }, children: suggestion }, suggestion));
144
+ }) }))] }));
101
145
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capyx/components-library",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Capyx Components Library for forms across applications",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -61,7 +61,6 @@
61
61
  "@types/lodash.debounce": "^4.0.9",
62
62
  "@types/node": "^25.3.0",
63
63
  "@types/react": "^19.2.14",
64
- "@types/react-autosuggest": "^10.1.11",
65
64
  "@types/react-datepicker": "^7.0.0",
66
65
  "@vitest/browser-playwright": "^4.0.18",
67
66
  "@vitest/coverage-v8": "^4.0.18",
@@ -79,7 +78,6 @@
79
78
  "dayjs": "^1.11.19",
80
79
  "lodash.debounce": "^4.0.8",
81
80
  "react": "^19.2.4",
82
- "react-autosuggest": "^10.1.0",
83
81
  "react-bootstrap": "^2.10.10",
84
82
  "react-hook-form": "^7.71.2",
85
83
  "react-quill-new": "^3.8.3"