@giro-ds/react 12.6.2 → 12.7.0

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.cjs CHANGED
@@ -3927,8 +3927,21 @@ function validatePartialDate(value, locale = 'pt-br') {
3927
3927
  }
3928
3928
  return 'incomplete';
3929
3929
  }
3930
+ function startOfDay(date) {
3931
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
3932
+ }
3933
+ function isDateInRange(date, minDate, maxDate) {
3934
+ const day = startOfDay(date);
3935
+ if (minDate && day < startOfDay(minDate)) {
3936
+ return false;
3937
+ }
3938
+ if (maxDate && day > startOfDay(maxDate)) {
3939
+ return false;
3940
+ }
3941
+ return true;
3942
+ }
3930
3943
 
3931
- const DatePicker = ({ locale = 'pt-br', calendarSide = 'bottom', calendarAlign = 'start', helperText, required = false, label, value, defaultValue, onChange, disabled = false, error: externalError, minDate, maxDate, 'data-testid': testId, scale = 1, id, className, style, ...rest }) => {
3944
+ const DatePicker = ({ locale = 'pt-br', calendarSide = 'bottom', calendarAlign = 'start', helperText, required = false, label, value, defaultValue, onChange, disabled = false, error: externalError, minDate, maxDate, showCalendar = true, 'data-testid': testId, scale = 1, id, className, style, ...rest }) => {
3932
3945
  const fieldId = React.useId();
3933
3946
  const calendarId = `${fieldId}-calendar`;
3934
3947
  const containerStyle = { '--giro-scale': scale };
@@ -3937,31 +3950,40 @@ const DatePicker = ({ locale = 'pt-br', calendarSide = 'bottom', calendarAlign =
3937
3950
  const [tempInputValue, setTempInputValue] = React.useState('');
3938
3951
  const [isEditing, setIsEditing] = React.useState(false);
3939
3952
  const [internalError, setInternalError] = React.useState('');
3940
- const [showCalendar, setShowCalendar] = React.useState(false);
3953
+ const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
3941
3954
  const [currentDate, setCurrentDate] = React.useState(new Date());
3942
3955
  const wrapperRef = React.useRef(null);
3943
3956
  const currentSelectedDate = isControlled ? value : internalDate;
3944
3957
  const hasValidationError = !!(externalError || internalError);
3945
3958
  const displayValue = isEditing ? tempInputValue : (currentSelectedDate ? formatDate(currentSelectedDate, locale) : '');
3946
- const combinedHelperText = externalError && helperText
3947
- ? `${helperText} • ${externalError}`
3948
- : externalError || helperText || '';
3959
+ const invalidDateMessage = locale === 'en-us' ? 'Invalid date' : 'Data inválida';
3960
+ const outOfRangeMessage = locale === 'en-us' ? 'Date out of allowed range' : 'Data fora do intervalo permitido';
3961
+ const internalErrorText = internalError === 'invalid'
3962
+ ? invalidDateMessage
3963
+ : internalError === 'range'
3964
+ ? outOfRangeMessage
3965
+ : '';
3966
+ const combinedHelperText = internalErrorText
3967
+ ? internalErrorText
3968
+ : externalError && helperText
3969
+ ? `${helperText} • ${externalError}`
3970
+ : externalError || helperText || '';
3949
3971
  React.useEffect(() => {
3950
3972
  function handleClickOutside(event) {
3951
3973
  const target = event.target;
3952
3974
  const isInsideWrapper = wrapperRef.current?.contains(target);
3953
3975
  const isInsideCalendar = !!target.closest?.('[data-radix-popper-content-wrapper]');
3954
3976
  if (!isInsideWrapper && !isInsideCalendar) {
3955
- setShowCalendar(false);
3977
+ setIsCalendarOpen(false);
3956
3978
  }
3957
3979
  }
3958
- if (showCalendar) {
3980
+ if (isCalendarOpen) {
3959
3981
  document.addEventListener('mousedown', handleClickOutside);
3960
3982
  }
3961
3983
  return () => {
3962
3984
  document.removeEventListener('mousedown', handleClickOutside);
3963
3985
  };
3964
- }, [showCalendar]);
3986
+ }, [isCalendarOpen]);
3965
3987
  const handleDateChange = React.useCallback((newDate) => {
3966
3988
  if (!isControlled) {
3967
3989
  setInternalDate(newDate);
@@ -3975,36 +3997,43 @@ const DatePicker = ({ locale = 'pt-br', calendarSide = 'bottom', calendarAlign =
3975
3997
  }
3976
3998
  }, [isControlled, onChange]);
3977
3999
  const handleIconClick = () => {
3978
- setShowCalendar((prev) => !prev);
4000
+ if (!showCalendar)
4001
+ return;
4002
+ setIsCalendarOpen((prev) => !prev);
3979
4003
  };
3980
4004
  const handleKeyDown = (event) => {
3981
4005
  const { key } = event;
3982
4006
  if (key === 'Enter') {
3983
- if (showCalendar) {
4007
+ if (isCalendarOpen) {
3984
4008
  if (currentSelectedDate) {
3985
- setShowCalendar(false);
4009
+ setIsCalendarOpen(false);
3986
4010
  event.preventDefault();
3987
4011
  return;
3988
4012
  }
3989
4013
  if (tempInputValue.length === 10 && isValidDateFormat(tempInputValue, locale)) {
3990
4014
  const parsedDate = parseDate(tempInputValue, locale);
3991
4015
  if (parsedDate && !isNaN(parsedDate.getTime())) {
4016
+ if (!isDateInRange(parsedDate, minDate, maxDate)) {
4017
+ setInternalError('range');
4018
+ event.preventDefault();
4019
+ return;
4020
+ }
3992
4021
  handleDateChange(parsedDate);
3993
- setShowCalendar(false);
4022
+ setIsCalendarOpen(false);
3994
4023
  event.preventDefault();
3995
4024
  return;
3996
4025
  }
3997
4026
  }
3998
4027
  }
3999
- else {
4000
- setShowCalendar(true);
4028
+ else if (showCalendar) {
4029
+ setIsCalendarOpen(true);
4001
4030
  event.preventDefault();
4002
4031
  return;
4003
4032
  }
4004
4033
  }
4005
4034
  if (key === 'Escape') {
4006
- if (showCalendar) {
4007
- setShowCalendar(false);
4035
+ if (isCalendarOpen) {
4036
+ setIsCalendarOpen(false);
4008
4037
  event.preventDefault();
4009
4038
  return;
4010
4039
  }
@@ -4020,12 +4049,12 @@ const DatePicker = ({ locale = 'pt-br', calendarSide = 'bottom', calendarAlign =
4020
4049
  }
4021
4050
  };
4022
4051
  const handleDaySelect = (newSelectedDate) => {
4023
- setShowCalendar(false);
4052
+ setIsCalendarOpen(false);
4024
4053
  handleDateChange(newSelectedDate);
4025
4054
  };
4026
4055
  const handleFocus = () => {
4027
- if (!disabled) {
4028
- setShowCalendar(true);
4056
+ if (!disabled && showCalendar) {
4057
+ setIsCalendarOpen(true);
4029
4058
  }
4030
4059
  };
4031
4060
  const handleTextFieldChange = (value) => {
@@ -4052,15 +4081,25 @@ const DatePicker = ({ locale = 'pt-br', calendarSide = 'bottom', calendarAlign =
4052
4081
  if (maskedValue.length === 10) {
4053
4082
  const parsedDate = parseDate(maskedValue, locale);
4054
4083
  if (parsedDate) {
4084
+ if (!isDateInRange(parsedDate, minDate, maxDate)) {
4085
+ setInternalError('range');
4086
+ if (!isControlled)
4087
+ setInternalDate(null);
4088
+ onChange?.(null);
4089
+ return;
4090
+ }
4055
4091
  handleDateChange(parsedDate);
4056
4092
  setCurrentDate(parsedDate);
4057
4093
  }
4058
4094
  }
4059
4095
  }
4060
4096
  };
4061
- return (jsxRuntime.jsx("div", { ref: wrapperRef, id: id, className: clsx(styles$h.datePicker, className), style: { ...containerStyle, ...style }, ...rest, children: jsxRuntime.jsx(Popover, { open: showCalendar, onOpenChange: setShowCalendar, asAnchor: true, trigger: jsxRuntime.jsx(TextField, { className: styles$h.textfieldContainer, type: "tel", icon: jsxRuntime.jsx(reactIcons.Calendar16Regular, { onClick: !disabled ? handleIconClick : undefined, className: clsx(styles$h.datePickerIcon, disabled && styles$h.datePickerIconDisabled) }), onChange: (e) => {
4097
+ return (jsxRuntime.jsx("div", { ref: wrapperRef, id: id, className: clsx(styles$h.datePicker, className), style: { ...containerStyle, ...style }, ...rest, children: jsxRuntime.jsx(Popover, { open: showCalendar && isCalendarOpen, onOpenChange: (open) => {
4098
+ if (showCalendar)
4099
+ setIsCalendarOpen(open);
4100
+ }, asAnchor: true, trigger: jsxRuntime.jsx(TextField, { className: styles$h.textfieldContainer, type: "tel", icon: jsxRuntime.jsx(reactIcons.Calendar16Regular, { onClick: !disabled ? handleIconClick : undefined, className: clsx(styles$h.datePickerIcon, disabled && styles$h.datePickerIconDisabled) }), onChange: (e) => {
4062
4101
  handleTextFieldChange(String(e));
4063
- }, onKeyDown: handleKeyDown, onFocus: handleFocus, autoComplete: "off", value: displayValue, helperText: !externalError ? (helperText || undefined) : undefined, errorMessage: externalError ? combinedHelperText : undefined, error: hasValidationError ? true : undefined, maxLength: 10, required: required, label: label, disabled: disabled, scale: scale, id: fieldId, "data-testid": testId, placeholder: locale === 'en-us' ? 'MM/DD/YYYY' : 'DD/MM/YYYY', "aria-label": "Open calendar", "aria-expanded": showCalendar, "aria-controls": calendarId, "aria-invalid": hasValidationError, "aria-describedby": externalError ? undefined : (helperText ? `${fieldId}-help` : undefined) }), content: jsxRuntime.jsx(Calendar, { selected: currentSelectedDate, currentDate: currentDate, onDateChange: setCurrentDate, onDaySelect: handleDaySelect, locale: locale, format: locale === 'en-us' ? 'mm/dd/yyyy' : 'dd/mm/yyyy', minDate: minDate, maxDate: maxDate, scale: scale, id: calendarId, onClear: () => {
4102
+ }, onKeyDown: handleKeyDown, onFocus: handleFocus, autoComplete: "off", value: displayValue, helperText: !hasValidationError ? (helperText || undefined) : undefined, errorMessage: hasValidationError ? combinedHelperText : undefined, error: hasValidationError ? true : undefined, maxLength: 10, required: required, label: label, disabled: disabled, scale: scale, id: fieldId, "data-testid": testId, placeholder: locale === 'en-us' ? 'MM/DD/YYYY' : 'DD/MM/YYYY', "aria-label": "Open calendar", "aria-expanded": showCalendar && isCalendarOpen, "aria-controls": calendarId, "aria-invalid": hasValidationError, "aria-describedby": hasValidationError ? undefined : (helperText ? `${fieldId}-help` : undefined) }), content: jsxRuntime.jsx(Calendar, { selected: currentSelectedDate, currentDate: currentDate, onDateChange: setCurrentDate, onDaySelect: handleDaySelect, locale: locale, format: locale === 'en-us' ? 'mm/dd/yyyy' : 'dd/mm/yyyy', minDate: minDate, maxDate: maxDate, scale: scale, id: calendarId, onClear: () => {
4064
4103
  handleDateChange(null);
4065
4104
  setCurrentDate(new Date());
4066
4105
  } }), side: calendarSide, align: calendarAlign, sideOffset: 8 }) }));
@@ -4986,11 +5025,12 @@ const Modal = ({ isOpen = false, onClose, title, children, headerContent, closeO
4986
5025
 
4987
5026
  var styles$8 = {"quantity":"Quantity-module__quantity___-vndR","quantityInput":"Quantity-module__quantityInput___xZG-v","disabled":"Quantity-module__disabled___X6p5l"};
4988
5027
 
4989
- const Quantity = ({ value = 0, onChange, disabled = false, decimal = false, decimalPlaces = 2, size = 'lg', id, valueIncrement = 1, scale = 1, className, style, inputSize, minValue = 0, maxValue = 9999, decrementAriaLabel = 'Decrease quantity', incrementAriaLabel = 'Increase quantity', inputAriaLabel = 'Quantity', ...rest }) => {
5028
+ const Quantity = ({ value = 0, onChange, disabled = false, decimal = false, decimalPlaces = 2, decimalSeparator = ',', size = 'lg', id, valueIncrement = 1, scale = 1, className, style, inputSize, minValue = 0, maxValue = 9999, decrementAriaLabel = 'Decrease quantity', incrementAriaLabel = 'Increase quantity', inputAriaLabel = 'Quantity', ...rest }) => {
4990
5029
  const initialValue = Math.min(Math.max(value, minValue), maxValue);
4991
5030
  const [internalValue, setInternalValue] = React.useState(initialValue);
4992
- const [inputValue, setInputValue] = React.useState(decimal ? initialValue.toFixed(decimalPlaces) : String(initialValue));
5031
+ const [inputValue, setInputValue] = React.useState(decimal ? initialValue.toFixed(decimalPlaces).replace('.', decimalSeparator) : String(initialValue));
4993
5032
  const inputRef = React.useRef(null);
5033
+ const formatDecimalValue = React.useCallback((num) => num.toFixed(decimalPlaces).replace('.', decimalSeparator), [decimalPlaces, decimalSeparator]);
4994
5034
  const stepValue = valueIncrement !== undefined ? valueIncrement : decimal ? Math.pow(10, -decimalPlaces) : 1;
4995
5035
  const isMinValue = React.useMemo(() => internalValue <= minValue, [internalValue, minValue]);
4996
5036
  const isMaxValue = React.useMemo(() => internalValue >= maxValue, [internalValue, maxValue]);
@@ -5008,32 +5048,34 @@ const Quantity = ({ value = 0, onChange, disabled = false, decimal = false, deci
5008
5048
  React.useEffect(() => {
5009
5049
  const clamped = Math.min(Math.max(value, minValue), maxValue);
5010
5050
  setInternalValue(clamped);
5011
- setInputValue(decimal ? clamped.toFixed(decimalPlaces) : String(clamped));
5012
- }, [value, minValue, maxValue, decimal, decimalPlaces]);
5051
+ setInputValue(decimal ? formatDecimalValue(clamped) : String(clamped));
5052
+ }, [value, minValue, maxValue, decimal, formatDecimalValue]);
5013
5053
  const increment = React.useCallback(() => {
5014
5054
  if (disabled)
5015
5055
  return;
5016
5056
  const newValue = Math.min(maxValue, internalValue + stepValue);
5017
5057
  setInternalValue(newValue);
5018
- setInputValue(decimal ? newValue.toFixed(decimalPlaces) : String(newValue));
5058
+ setInputValue(decimal ? formatDecimalValue(newValue) : String(newValue));
5019
5059
  onChange?.(newValue);
5020
- }, [disabled, internalValue, stepValue, decimal, decimalPlaces, onChange, maxValue]);
5060
+ }, [disabled, internalValue, stepValue, decimal, formatDecimalValue, onChange, maxValue]);
5021
5061
  const decrement = React.useCallback(() => {
5022
5062
  if (disabled)
5023
5063
  return;
5024
5064
  const newValue = Math.max(minValue, internalValue - stepValue);
5025
5065
  setInternalValue(newValue);
5026
- setInputValue(decimal ? newValue.toFixed(decimalPlaces) : String(newValue));
5066
+ setInputValue(decimal ? formatDecimalValue(newValue) : String(newValue));
5027
5067
  onChange?.(newValue);
5028
- }, [disabled, internalValue, stepValue, decimal, decimalPlaces, onChange, minValue]);
5068
+ }, [disabled, internalValue, stepValue, decimal, formatDecimalValue, onChange, minValue]);
5029
5069
  const filterInput = React.useCallback((inputValue) => {
5030
5070
  if (decimal) {
5031
- let filtered = inputValue.replace(/[^\d.]/g, '');
5032
- const parts = filtered.split('.');
5071
+ const escapedSeparator = decimalSeparator.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
5072
+ const allowedCharsRegex = new RegExp(`[^\\d${escapedSeparator}]`, 'g');
5073
+ let filtered = inputValue.replace(allowedCharsRegex, '');
5074
+ const parts = filtered.split(decimalSeparator);
5033
5075
  if (parts.length > 2) {
5034
- filtered = parts[0] + '.' + parts.slice(1).join('');
5076
+ filtered = parts[0] + decimalSeparator + parts.slice(1).join('');
5035
5077
  }
5036
- if (filtered.length > 1 && filtered.startsWith('0') && !filtered.startsWith('0.')) {
5078
+ if (filtered.length > 1 && filtered.startsWith('0') && !filtered.startsWith(`0${decimalSeparator}`)) {
5037
5079
  filtered = filtered.replace(/^0+/, '');
5038
5080
  }
5039
5081
  return filtered;
@@ -5045,22 +5087,22 @@ const Quantity = ({ value = 0, onChange, disabled = false, decimal = false, deci
5045
5087
  }
5046
5088
  return filtered || '0'; // Se ficar vazio, retorna '0'
5047
5089
  }
5048
- }, [decimal]);
5090
+ }, [decimal, decimalSeparator]);
5049
5091
  const handleInputChange = React.useCallback((event) => {
5050
5092
  if (disabled)
5051
5093
  return;
5052
5094
  const rawValue = event.target.value;
5053
5095
  const filteredValue = filterInput(rawValue);
5054
5096
  setInputValue(filteredValue);
5055
- if (filteredValue === '' || filteredValue === '.') {
5097
+ if (filteredValue === '' || filteredValue === decimalSeparator) {
5056
5098
  onChange?.(0);
5057
5099
  return;
5058
5100
  }
5059
5101
  if (decimal) {
5060
- if (filteredValue.endsWith('.')) {
5102
+ if (filteredValue.endsWith(decimalSeparator)) {
5061
5103
  return;
5062
5104
  }
5063
- const parsedValue = parseFloat(filteredValue);
5105
+ const parsedValue = parseFloat(filteredValue.replace(decimalSeparator, '.'));
5064
5106
  if (!isNaN(parsedValue)) {
5065
5107
  onChange?.(parsedValue);
5066
5108
  }
@@ -5072,36 +5114,36 @@ const Quantity = ({ value = 0, onChange, disabled = false, decimal = false, deci
5072
5114
  onChange?.(validValue);
5073
5115
  }
5074
5116
  }
5075
- }, [disabled, decimal, filterInput, onChange, minValue, maxValue]);
5117
+ }, [disabled, decimal, decimalSeparator, filterInput, onChange, minValue, maxValue]);
5076
5118
  const handleBlur = React.useCallback(() => {
5077
5119
  if (disabled)
5078
5120
  return;
5079
5121
  const currentValue = inputValue.trim();
5080
5122
  if (decimal) {
5081
- if (currentValue === '' || currentValue === '.') {
5082
- setInputValue(internalValue.toFixed(decimalPlaces));
5123
+ if (currentValue === '' || currentValue === decimalSeparator) {
5124
+ setInputValue(formatDecimalValue(internalValue));
5083
5125
  return;
5084
5126
  }
5085
- if (currentValue.endsWith('.')) {
5086
- const baseValue = parseFloat(currentValue.slice(0, -1));
5127
+ if (currentValue.endsWith(decimalSeparator)) {
5128
+ const baseValue = parseFloat(currentValue.slice(0, -1).replace(decimalSeparator, '.'));
5087
5129
  if (!isNaN(baseValue)) {
5088
- const formattedValue = baseValue.toFixed(decimalPlaces);
5130
+ const formattedValue = formatDecimalValue(baseValue);
5089
5131
  setInputValue(formattedValue);
5090
5132
  setInternalValue(baseValue);
5091
5133
  onChange?.(baseValue);
5092
5134
  }
5093
5135
  return;
5094
5136
  }
5095
- const parsedValue = parseFloat(currentValue);
5137
+ const parsedValue = parseFloat(currentValue.replace(decimalSeparator, '.'));
5096
5138
  if (!isNaN(parsedValue)) {
5097
5139
  const normalizedValue = Math.min(Math.max(parsedValue, minValue), maxValue);
5098
- const formattedValue = normalizedValue.toFixed(decimalPlaces);
5140
+ const formattedValue = formatDecimalValue(normalizedValue);
5099
5141
  setInputValue(formattedValue);
5100
5142
  setInternalValue(normalizedValue);
5101
5143
  onChange?.(normalizedValue);
5102
5144
  }
5103
5145
  else {
5104
- setInputValue(internalValue.toFixed(decimalPlaces));
5146
+ setInputValue(formatDecimalValue(internalValue));
5105
5147
  }
5106
5148
  }
5107
5149
  else {
@@ -5120,7 +5162,7 @@ const Quantity = ({ value = 0, onChange, disabled = false, decimal = false, deci
5120
5162
  onChange?.(normalizedValue);
5121
5163
  }
5122
5164
  }
5123
- }, [disabled, decimal, inputValue, decimalPlaces, internalValue, onChange, minValue, maxValue]);
5165
+ }, [disabled, decimal, decimalSeparator, inputValue, formatDecimalValue, internalValue, onChange, minValue, maxValue]);
5124
5166
  const handleInputKeyDown = React.useCallback((e) => {
5125
5167
  if (disabled)
5126
5168
  return;
@@ -5142,14 +5184,14 @@ const Quantity = ({ value = 0, onChange, disabled = false, decimal = false, deci
5142
5184
  case 'Escape':
5143
5185
  e.preventDefault();
5144
5186
  if (inputRef.current) {
5145
- setInputValue(decimal ? internalValue.toFixed(decimalPlaces) : String(internalValue));
5187
+ setInputValue(decimal ? formatDecimalValue(internalValue) : String(internalValue));
5146
5188
  inputRef.current.blur();
5147
5189
  }
5148
5190
  break;
5149
5191
  case 'Home':
5150
5192
  e.preventDefault();
5151
5193
  setInternalValue(minValue);
5152
- setInputValue(decimal ? minValue.toFixed(decimalPlaces) : String(minValue));
5194
+ setInputValue(decimal ? formatDecimalValue(minValue) : String(minValue));
5153
5195
  onChange?.(minValue);
5154
5196
  break;
5155
5197
  case 'End':
@@ -5161,7 +5203,7 @@ const Quantity = ({ value = 0, onChange, disabled = false, decimal = false, deci
5161
5203
  }
5162
5204
  break;
5163
5205
  }
5164
- }, [disabled, increment, decrement, decimal, decimalPlaces, onChange, internalValue, minValue, maxValue]);
5206
+ }, [disabled, increment, decrement, decimal, formatDecimalValue, onChange, internalValue, minValue, maxValue]);
5165
5207
  const inputSizeValue = inputSize !== undefined ? inputSize : Math.max(1, inputValue.length);
5166
5208
  const uniqueId = React.useId();
5167
5209
  const inputId = id || uniqueId;