pesona-ui 1.0.29 → 1.0.31

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/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useState, useRef, useMemo, forwardRef, useCallback } from 'react';
1
+ import React, { useEffect, useState, useRef, useMemo, forwardRef, useImperativeHandle, useCallback } from 'react';
2
2
  import ReactDOM, { createPortal } from 'react-dom';
3
3
 
4
4
  const Button = ({ type = 'button', className, children, ...rest }) => {
@@ -8187,120 +8187,55 @@ const useDropdownPositionAndScroll = (isOpen, dropdownOptionsRef) => {
8187
8187
  }, [isOpen, dropdownOptionsRef]);
8188
8188
  };
8189
8189
 
8190
- const Select = forwardRef(({ name, label, message, selectLabel, size = 'md', floatingLabel = false, error, options, value = '', onChange, onBlur, required, disabled, }, ref) => {
8190
+ const Select = forwardRef((props, ref) => {
8191
+ const { name, label, message, selectLabel = 'Select an option', size = 'md', floatingLabel = false, error, options, value = '', onChange, onBlur, required, disabled, } = props;
8191
8192
  const [isOpen, setIsOpen] = useState(false);
8192
- const [highlightIndex, setHighlightIndex] = useState(-1);
8193
8193
  const dropdownRef = useRef(null);
8194
8194
  const dropdownOptionsRef = useRef(null);
8195
- const hiddenSelectRef = useRef(null);
8196
- const shouldReturnNumber = useMemo(() => options.every((opt) => typeof opt.value === 'number'), [options]);
8197
- const coerceValue = useCallback((val) => (shouldReturnNumber ? Number(val) : val), [shouldReturnNumber]);
8198
- const createSyntheticEvent = useCallback((val) => ({
8199
- target: { name, value: coerceValue(val), type: 'select-one' },
8200
- currentTarget: { name, value: coerceValue(val), type: 'select-one' },
8201
- }), [name, coerceValue]);
8202
- const handleOptionSelect = useCallback((optionValue) => {
8203
- const selectedOption = options.find((opt) => opt.value === optionValue);
8204
- if (!selectedOption)
8205
- return;
8206
- hiddenSelectRef.current.value = String(selectedOption.value);
8207
- onChange?.(createSyntheticEvent(selectedOption.value));
8208
- setIsOpen(false);
8209
- setTimeout(() => {
8210
- onBlur?.(createSyntheticEvent(selectedOption.value));
8211
- }, 0);
8212
- }, [options, onChange, onBlur, createSyntheticEvent]);
8213
- const handleOutsideClick = useCallback(() => {
8195
+ const selectRef = useRef(null);
8196
+ useImperativeHandle(ref, () => selectRef.current);
8197
+ useEffect(() => setIsOpen(false), []);
8198
+ useOutsideClick([dropdownRef], () => {
8214
8199
  setIsOpen(false);
8215
- if (onBlur && value !== undefined && value !== null && value !== '') {
8216
- setTimeout(() => {
8217
- onBlur(createSyntheticEvent(value));
8218
- }, 0);
8219
- }
8220
- }, [value, onBlur, createSyntheticEvent]);
8221
- useOutsideClick([dropdownRef], handleOutsideClick);
8200
+ selectRef.current?.blur(); // Trigger onBlur RHF
8201
+ });
8222
8202
  useDropdownPositionAndScroll(isOpen, dropdownOptionsRef);
8223
- const normalizedValue = useMemo(() => (value === null || value === undefined || value === '' ? '' : String(value)), [value]);
8224
- const selectedOption = useMemo(() => options.find((opt) => String(opt.value) === normalizedValue), [options, normalizedValue]);
8225
- const displayText = selectedOption?.label || selectLabel || 'Select an option';
8226
- useEffect(() => {
8227
- if (hiddenSelectRef.current)
8228
- hiddenSelectRef.current.value = normalizedValue;
8229
- }, [normalizedValue]);
8230
- useEffect(() => {
8231
- if (isOpen) {
8232
- const index = options.findIndex((opt) => String(opt.value) === normalizedValue);
8233
- setHighlightIndex(index >= 0 ? index : 0);
8234
- }
8235
- }, [isOpen, options, normalizedValue]);
8236
- const handleKeyDown = useCallback((e) => {
8237
- if (disabled)
8238
- return;
8239
- if (e.key === 'Enter' || e.key === ' ') {
8240
- e.preventDefault();
8241
- if (isOpen && highlightIndex >= 0) {
8242
- handleOptionSelect(options[highlightIndex].value);
8243
- }
8244
- else {
8245
- setIsOpen((prev) => !prev);
8246
- }
8247
- }
8248
- else if (e.key === 'Escape' && isOpen) {
8249
- setIsOpen(false);
8250
- }
8251
- else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
8252
- e.preventDefault();
8253
- if (!isOpen) {
8254
- setIsOpen(true);
8255
- }
8256
- else {
8257
- setHighlightIndex((prev) => {
8258
- const next = e.key === 'ArrowDown'
8259
- ? (prev + 1) % options.length
8260
- : (prev - 1 + options.length) % options.length;
8261
- return next;
8262
- });
8263
- }
8264
- }
8265
- }, [disabled, isOpen, highlightIndex, options, handleOptionSelect]);
8266
- useEffect(() => {
8267
- if (isOpen && dropdownOptionsRef.current && highlightIndex >= 0) {
8268
- const optionElements = dropdownOptionsRef.current.querySelectorAll('.option');
8269
- const highlighted = optionElements[highlightIndex];
8270
- highlighted?.scrollIntoView({ block: 'nearest' });
8203
+ const displayText = value
8204
+ ? options.find((opt) => opt.value === value)?.label
8205
+ : selectLabel;
8206
+ const handleOptionClick = (optionValue) => {
8207
+ const syntheticEvent = {
8208
+ target: {
8209
+ name,
8210
+ value: optionValue,
8211
+ },
8212
+ };
8213
+ onChange?.(syntheticEvent);
8214
+ // Change the select value to trigger onChange in RHF
8215
+ if (selectRef.current) {
8216
+ selectRef.current.value = String(optionValue);
8217
+ selectRef.current.dispatchEvent(new Event('change', { bubbles: true }));
8271
8218
  }
8272
- }, [highlightIndex, isOpen]);
8219
+ setIsOpen(false);
8220
+ };
8273
8221
  return (React.createElement(React.Fragment, null,
8274
- React.createElement("select", { ref: (el) => {
8275
- hiddenSelectRef.current = el;
8276
- if (typeof ref === 'function')
8277
- ref(el);
8278
- else if (ref)
8279
- ref.current = el;
8280
- }, name: name, value: normalizedValue, onChange: onChange, onBlur: onBlur, required: required, disabled: disabled, style: {
8281
- position: 'absolute',
8282
- left: '-9999px',
8283
- opacity: 0,
8284
- pointerEvents: 'none',
8285
- width: '1px',
8286
- height: '1px',
8287
- }, tabIndex: -1, "aria-hidden": "true" },
8222
+ React.createElement("select", { ref: selectRef, name: name, value: value, onChange: onChange, onBlur: onBlur, required: required, disabled: disabled, style: { position: 'absolute', left: '-9999px' }, tabIndex: -1, "aria-hidden": "true" },
8288
8223
  React.createElement("option", { value: "" }, "Select an option"),
8289
- options.map((option) => (React.createElement("option", { key: option.value, value: option.value }, option.label)))),
8224
+ options.map((opt) => (React.createElement("option", { key: opt.value, value: opt.value }, opt.label)))),
8290
8225
  label && !floatingLabel && (React.createElement("label", { htmlFor: name },
8291
8226
  label,
8292
8227
  " ",
8293
8228
  required && React.createElement("span", { className: "text-danger" }, "*"))),
8294
8229
  React.createElement("div", { className: `dropdown-select-container ${size}`, ref: dropdownRef },
8295
- React.createElement("div", { className: `dropdown-header ${isOpen ? 'open' : ''} ${disabled ? 'disabled' : ''} ${error ? 'error' : ''}`, onClick: () => !disabled && setIsOpen(!isOpen), onKeyDown: handleKeyDown, "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-invalid": !!error, tabIndex: disabled ? -1 : 0, role: "combobox" },
8230
+ React.createElement("div", { className: `dropdown-header ${isOpen ? 'open' : ''} ${disabled ? 'disabled' : ''}`, onClick: () => !disabled && setIsOpen(!isOpen), "aria-haspopup": "listbox", "aria-expanded": isOpen },
8296
8231
  React.createElement("span", { className: "label" }, displayText),
8297
8232
  React.createElement(FiChevronDown, { className: "arrow" })),
8298
- isOpen && (React.createElement("div", { className: "dropdown-options scrollbar", ref: dropdownOptionsRef, role: "listbox" }, options.map((option, index) => (React.createElement("div", { key: `${option.value}-${index}`, className: `option ${String(value) === String(option.value) ? 'selected' : ''} ${highlightIndex === index ? 'highlight' : ''}`, onClick: () => !disabled && handleOptionSelect(option.value), role: "option", "aria-selected": String(value) === String(option.value), tabIndex: 0 }, option.label)))))),
8233
+ isOpen && (React.createElement("div", { className: "dropdown-options scrollbar", ref: dropdownOptionsRef, role: "listbox" }, options.map((opt) => (React.createElement("div", { key: opt.value, className: `option ${opt.value === value ? 'selected' : ''}`, onClick: () => handleOptionClick(opt.value), role: "option", "aria-selected": opt.value === value }, opt.label)))))),
8299
8234
  label && floatingLabel && (React.createElement("label", { htmlFor: name },
8300
8235
  label,
8301
8236
  " ",
8302
8237
  required && React.createElement("span", { className: "text-danger" }, "*"))),
8303
- error ? (React.createElement("small", { className: "form-message text-danger", role: "alert" }, error)) : (message && React.createElement("small", { className: "form-message text-muted" }, message))));
8238
+ error ? (React.createElement("small", { className: "form-message text-danger" }, error)) : (message && React.createElement("small", { className: "form-message text-muted" }, message))));
8304
8239
  });
8305
8240
  Select.displayName = 'Select';
8306
8241
 
@@ -8697,39 +8632,49 @@ const SelectMultiple = forwardRef(({ name, label, message, selectLabel, floating
8697
8632
  });
8698
8633
  SelectMultiple.displayName = 'SelectMultiple';
8699
8634
 
8700
- const SelectWithSearch = forwardRef(({ name, label, message, selectLabel, size = 'md', floatingLabel = false, error, options = [], value = '', onChange, required, disabled, selectSearch = '', setSelectSearch, isLoading = false, }, ref) => {
8635
+ const SelectWithSearch = forwardRef((props, ref) => {
8636
+ const { name, label, message, selectLabel = 'Select an option', size = 'md', floatingLabel = false, error, options = [], value = '', onChange, required, disabled, selectSearch = '', setSelectSearch, isLoading = false, } = props;
8701
8637
  const [isOpen, setIsOpen] = useState(false);
8702
8638
  const dropdownRef = useRef(null);
8703
8639
  const dropdownOptionsRef = useRef(null);
8640
+ const selectRef = useRef(null);
8641
+ useImperativeHandle(ref, () => selectRef.current);
8642
+ useOutsideClick([dropdownRef], () => setIsOpen(false));
8643
+ useDropdownPositionAndScroll(isOpen, dropdownOptionsRef);
8644
+ const displayText = value
8645
+ ? options.find((opt) => opt.value === value)?.label
8646
+ : selectLabel;
8704
8647
  const handleOptionClick = (optionValue) => {
8705
- if (onChange) {
8706
- onChange({
8707
- target: { name, value: optionValue },
8708
- });
8648
+ const syntheticEvent = {
8649
+ target: {
8650
+ name,
8651
+ value: optionValue,
8652
+ },
8653
+ };
8654
+ onChange?.(syntheticEvent);
8655
+ // Change the select value to trigger onChange in RHF
8656
+ if (selectRef.current) {
8657
+ selectRef.current.value = String(optionValue);
8658
+ selectRef.current.dispatchEvent(new Event('change', { bubbles: true }));
8709
8659
  }
8710
8660
  setIsOpen(false);
8711
8661
  };
8712
- useOutsideClick([dropdownRef], () => setIsOpen(false));
8713
- useDropdownPositionAndScroll(isOpen, dropdownOptionsRef);
8714
- useEffect(() => {
8715
- return () => setIsOpen(false);
8716
- }, []);
8717
- const displayText = value
8718
- ? options.find((option) => option.value === value)?.label
8719
- : selectLabel || 'Select an option';
8720
8662
  return (React.createElement(React.Fragment, null,
8663
+ React.createElement("select", { ref: selectRef, name: name, value: value, onChange: onChange, style: { position: 'absolute', left: '-9999px' }, tabIndex: -1, "aria-hidden": "true" },
8664
+ React.createElement("option", { value: "" }, "Select an option"),
8665
+ options.map((opt) => (React.createElement("option", { key: opt.value, value: opt.value }, opt.label)))),
8721
8666
  label && !floatingLabel && (React.createElement("label", { htmlFor: name },
8722
8667
  label,
8723
8668
  " ",
8724
8669
  required && React.createElement("span", { className: "text-danger" }, "*"))),
8725
8670
  React.createElement("div", { className: `dropdown-select-container ${size}`, ref: dropdownRef },
8726
- React.createElement("div", { className: `dropdown-header ${isOpen ? 'open' : ''} ${disabled ? 'disabled' : ''}`, onClick: () => !disabled && setIsOpen(!isOpen), ref: ref, "aria-haspopup": "listbox", "aria-expanded": isOpen },
8671
+ React.createElement("div", { className: `dropdown-header ${isOpen ? 'open' : ''} ${disabled ? 'disabled' : ''}`, onClick: () => !disabled && setIsOpen(!isOpen), "aria-haspopup": "listbox", "aria-expanded": isOpen },
8727
8672
  React.createElement("span", { className: "label" }, displayText),
8728
8673
  React.createElement(FiChevronDown, { className: "arrow" })),
8729
- isOpen && (React.createElement("div", { className: `dropdown-options scrollbar ${disabled ? 'disabled' : ''}`, ref: dropdownOptionsRef, role: "listbox" },
8674
+ isOpen && (React.createElement("div", { className: "dropdown-options scrollbar", ref: dropdownOptionsRef, role: "listbox" },
8730
8675
  React.createElement("div", { className: "search-input" },
8731
- React.createElement(Input, { name: "search", placeholder: "Ketik untuk mencari..", type: "search", autoComplete: "off", autoCorrect: "off", autoCapitalize: "off", spellCheck: false, role: "textbox", value: selectSearch, autoFocus: true, onChange: (e) => setSelectSearch && setSelectSearch(e.currentTarget.value) })),
8732
- isLoading ? (React.createElement("div", { className: "p-10 text-center" }, "Loading options...")) : options.length === 0 ? (React.createElement("div", { className: "p-10 text-center" }, "Tidak ada hasil ditemukan")) : (options.map((option) => (React.createElement("div", { key: String(option.value), className: `option ${value === option.value ? 'selected' : ''}`, onClick: () => !disabled && handleOptionClick(String(option.value)), role: "option", "aria-selected": value === option.value }, option.label))))))),
8676
+ React.createElement(Input, { name: "search", placeholder: "Ketik untuk mencari..", type: "search", autoFocus: true, value: selectSearch, onChange: (e) => setSelectSearch?.(e.currentTarget.value) })),
8677
+ isLoading ? (React.createElement("div", { className: "p-10 text-center" }, "Loading options...")) : options.length === 0 ? (React.createElement("div", { className: "p-10 text-center" }, "Tidak ada hasil ditemukan")) : (options.map((opt) => (React.createElement("div", { key: opt.value, className: `option ${value === opt.value ? 'selected' : ''}`, onClick: () => handleOptionClick(opt.value), role: "option", "aria-selected": value === opt.value }, opt.label))))))),
8733
8678
  label && floatingLabel && (React.createElement("label", { htmlFor: name },
8734
8679
  label,
8735
8680
  " ",