react-better-html 1.1.34 → 1.1.35

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.
@@ -8,6 +8,7 @@ export type ButtonProps<Value> = {
8
8
  href?: string;
9
9
  text?: string;
10
10
  value?: Value;
11
+ download?: string;
11
12
  icon?: IconName | AnyOtherString;
12
13
  /** @default "left" */
13
14
  iconPosition?: "left" | "right";
@@ -104,7 +104,7 @@ const ButtonElement = styled_components_1.default.button.withConfig({
104
104
  ${(props) => props.hoverStyle}
105
105
  }
106
106
  `;
107
- const ButtonComponent = function Button({ href, text, value, icon, iconPosition = "left", iconColor, iconSize, image, imagePosition = "left", imageWidth, imageHeight, loaderName, loaderSize, isLoading, disabled, isSmall, isSubmit, onClick, onClickWithValue, ...props }) {
107
+ const ButtonComponent = function Button({ href, text, value, download, icon, iconPosition = "left", iconColor, iconSize, image, imagePosition = "left", imageWidth, imageHeight, loaderName, loaderSize, isLoading, disabled, isSmall, isSubmit, onClick, onClickWithValue, ...props }) {
108
108
  const theme = (0, BetterHtmlProvider_1.useTheme)();
109
109
  const isLoadingHook = (0, BetterHtmlProvider_1.useLoader)(loaderName);
110
110
  const betterHtmlContext = (0, BetterHtmlProvider_1.useBetterHtmlContext)();
@@ -122,7 +122,7 @@ const ButtonComponent = function Button({ href, text, value, icon, iconPosition
122
122
  }, [onClick, onClickWithValue, value]);
123
123
  const iconComponent = icon ? ((0, jsx_runtime_1.jsx)(Div_1.default.row, { height: 20, alignItems: "center", justifyContent: "center", children: (0, jsx_runtime_1.jsx)(Icon_1.default, { name: icon, color: iconColor ?? props.color ?? theme.colors.base, size: iconSize ?? parseInt(styledComponentStyles.normalStyle.fontSize?.toString() ?? "16") }) })) : undefined;
124
124
  const imageComponent = image ? ((0, jsx_runtime_1.jsx)(Image_1.default, { name: image, color: iconColor ?? props.color ?? theme.colors.base, width: imageWidth ?? parseInt(styledComponentStyles.normalStyle.fontSize?.toString() ?? "16"), height: imageHeight })) : undefined;
125
- return ((0, jsx_runtime_1.jsxs)(ButtonElement, { as: (href ? "a" : "button"), theme: theme, isSmall: isSmall, withText: text !== undefined, isLoading: isLoadingElement, disabled: disabled, href: href, type: isSubmit && !isLoadingElement ? "submit" : "button", onClick: !disabled && !isLoadingElement ? onClickElement : undefined, ...styledComponentStyles, ...dataProps, ...ariaProps, ...restProps, children: [(0, jsx_runtime_1.jsxs)(Div_1.default.row, { alignItems: "center", justifyContent: "center", gap: 10, pointerEvents: "none", opacity: isLoadingElement ? 0 : 1, transition: theme.styles.transition, children: [iconPosition === "left" && iconComponent, imagePosition === "left" && imageComponent, text, iconPosition === "right" && iconComponent, imagePosition === "right" && imageComponent] }), (0, jsx_runtime_1.jsx)(Div_1.default.row, { position: "absolute", width: "100%", height: "100%", top: 0, left: 0, pointerEvents: "none", alignItems: "center", justifyContent: "center", opacity: isLoadingElement ? 1 : 0, transition: theme.styles.transition, children: (0, jsx_runtime_1.jsx)(Loader_1.default, { color: props.color ?? theme.colors.base, size: loaderSize, disabled: !isLoadingElement }) })] }));
125
+ return ((0, jsx_runtime_1.jsxs)(ButtonElement, { as: (href ? "a" : "button"), theme: theme, isSmall: isSmall, withText: text !== undefined, isLoading: isLoadingElement, disabled: disabled, href: href, download: download, type: isSubmit && !isLoadingElement ? "submit" : "button", onClick: !disabled && !isLoadingElement ? onClickElement : undefined, ...styledComponentStyles, ...dataProps, ...ariaProps, ...restProps, children: [(0, jsx_runtime_1.jsxs)(Div_1.default.row, { alignItems: "center", justifyContent: "center", gap: 10, pointerEvents: "none", opacity: isLoadingElement ? 0 : 1, transition: theme.styles.transition, children: [iconPosition === "left" && iconComponent, imagePosition === "left" && imageComponent, text, iconPosition === "right" && iconComponent, imagePosition === "right" && imageComponent] }), (0, jsx_runtime_1.jsx)(Div_1.default.row, { position: "absolute", width: "100%", height: "100%", top: 0, left: 0, pointerEvents: "none", alignItems: "center", justifyContent: "center", opacity: isLoadingElement ? 1 : 0, transition: theme.styles.transition, children: (0, jsx_runtime_1.jsx)(Loader_1.default, { color: props.color ?? theme.colors.base, size: loaderSize, disabled: !isLoadingElement }) })] }));
126
126
  };
127
127
  ButtonComponent.secondary = function Secondary({ className, ...props }) {
128
128
  const theme = (0, BetterHtmlProvider_1.useTheme)();
@@ -3,8 +3,8 @@ import { AnyOtherString, OmitProps } from "../types/app";
3
3
  import { IconName } from "../types/icon";
4
4
  import { DivProps } from "./Div";
5
5
  export type DropdownOption<Value, Data = unknown> = {
6
- label: string;
7
6
  value: Value;
7
+ label: string;
8
8
  disabled?: boolean;
9
9
  searchValues?: string[];
10
10
  data?: Data;
@@ -17,10 +17,13 @@ export type DropdownProps<Value, Data> = {
17
17
  disabled?: boolean;
18
18
  options: DropdownOption<Value, Data>[];
19
19
  value?: Value;
20
+ defaultValue?: Value;
20
21
  placeholder?: string;
21
22
  leftIcon?: IconName | AnyOtherString;
23
+ inputFieldClassName?: string;
22
24
  withSearch?: boolean;
23
25
  withDebounce?: boolean;
26
+ withoutClearButton?: boolean;
24
27
  /** @default 0.5s */
25
28
  debounceDelay?: number;
26
29
  debounceIsLoading?: boolean;
@@ -28,7 +31,7 @@ export type DropdownProps<Value, Data> = {
28
31
  onChange?: (value: Value | undefined) => void;
29
32
  onChangeSearch?: (query: string) => void;
30
33
  renderOption?: (option: DropdownOption<Value, Data>, index: number, isSelected: boolean) => React.ReactNode;
31
- } & OmitProps<DivProps<unknown>, "onChange">;
34
+ } & OmitProps<DivProps<unknown>, "onChange" | "defaultChecked">;
32
35
  type DropdownComponentType = {
33
36
  <Value, Data>(props: ComponentPropWithRef<HTMLDivElement, DropdownProps<Value, Data>>): React.ReactElement;
34
37
  };
@@ -14,7 +14,7 @@ const Icon_1 = __importDefault(require("./Icon"));
14
14
  const Button_1 = __importDefault(require("./Button"));
15
15
  const Loader_1 = __importDefault(require("./Loader"));
16
16
  const BetterHtmlProvider_1 = require("./BetterHtmlProvider");
17
- const DropdownComponent = (0, react_1.forwardRef)(function Dropdown({ label, errorText, infoText, required, disabled, options, value: controlledValue, placeholder = "Select an option", leftIcon, withSearch, withDebounce, debounceDelay = 0.5, debounceIsLoading, debounceMinimumSymbolsRequired, onChange, onChangeSearch, renderOption, ...props }, ref) {
17
+ const DropdownComponent = (0, react_1.forwardRef)(function Dropdown({ label, errorText, infoText, required, disabled, options, value: controlledValue, defaultValue, placeholder = "Select an option", leftIcon, inputFieldClassName, withSearch, withDebounce, withoutClearButton, debounceDelay = 0.5, debounceIsLoading, debounceMinimumSymbolsRequired, onChange, onChangeSearch, renderOption, ...props }, ref) {
18
18
  const theme = (0, BetterHtmlProvider_1.useTheme)();
19
19
  const dropdownHolderRef = (0, react_1.useRef)(null);
20
20
  const inputRef = (0, react_1.useRef)(null);
@@ -24,7 +24,7 @@ const DropdownComponent = (0, react_1.forwardRef)(function Dropdown({ label, err
24
24
  const [searchQuery, setSearchQuery] = (0, react_1.useState)("");
25
25
  const [_, debouncedSearchQuery, setDebouncedSearchQuery, isLoadingDebouncedSearchQuery] = (0, hooks_2.useDebounceState)("", debounceDelay);
26
26
  const [focusedOptionIndex, setFocusedOptionIndex] = (0, react_1.useState)();
27
- const [internalValue, setInternalValue] = (0, react_1.useState)();
27
+ const [internalValue, setInternalValue] = (0, react_1.useState)(defaultValue);
28
28
  const value = controlledValue ?? internalValue;
29
29
  const filteredOptions = (0, react_1.useMemo)(() => {
30
30
  if (!searchQuery)
@@ -141,7 +141,7 @@ const DropdownComponent = (0, react_1.forwardRef)(function Dropdown({ label, err
141
141
  }, [withDebounce, onChangeSearch, debouncedSearchQuery]);
142
142
  const displayValue = withSearch && isFocused ? searchQuery : selectedOption?.label ?? "";
143
143
  const withClearButton = isOpen && selectedOption;
144
- return ((0, jsx_runtime_1.jsx)(Div_1.default.column, { width: "100%", position: "relative", userSelect: "none", ...props, ref: dropdownHolderRef, children: (0, jsx_runtime_1.jsxs)(Div_1.default.row, { position: "relative", width: "100%", children: [(0, jsx_runtime_1.jsx)(InputField_1.default, { label: label, errorText: errorText, infoText: infoText, required: required, disabled: disabled, readOnly: !withSearch, value: displayValue, placeholder: withSearch ? (selectedOption ? selectedOption.label : placeholder) : placeholder, leftIcon: leftIcon, className: `react-better-html-dropdown${isOpen ? " react-better-html-dropdown-open" : ""}`, zIndex: isOpen || isOpenLate ? 1001 : undefined, onClick: !disabled ? setIsOpen.toggle : undefined, onFocus: setIsFocused.setTrue, onBlur: setIsFocused.setFalse, onKeyDown: onKeyDownInputField, onChangeValue: withSearch ? onChangeValue : undefined, insideInputFieldComponent: (0, jsx_runtime_1.jsx)(Div_1.default, { position: "absolute", top: "100%", left: 0, width: "100%", maxHeight: 300, backgroundColor: theme.colors.backgroundContent, border: `1px solid ${isFocused ? theme.colors.primary : theme.colors.border}`, borderTop: "none", borderBottomLeftRadius: theme.styles.borderRadius, borderBottomRightRadius: theme.styles.borderRadius, boxShadow: "0px 10px 20px #00000020", zIndex: 1000, overflowY: "auto", opacity: !isOpen ? 0 : undefined, pointerEvents: !isOpen ? "none" : undefined, transform: `translateY(${!isOpen ? -10 : 0}px)`, transition: theme.styles.transition, role: "listbox", "aria-label": label, children: isLoadingDebouncedSearchQuery || debounceIsLoading ? ((0, jsx_runtime_1.jsx)(Div_1.default, { padding: `${theme.styles.space / 2}px ${theme.styles.space + theme.styles.gap}px`, children: (0, jsx_runtime_1.jsx)(Loader_1.default.text, {}) })) : filteredOptions.length ? (filteredOptions.map((option, index) => {
144
+ return ((0, jsx_runtime_1.jsx)(Div_1.default.column, { width: "100%", position: "relative", userSelect: "none", ...props, ref: dropdownHolderRef, children: (0, jsx_runtime_1.jsxs)(Div_1.default.row, { position: "relative", width: "100%", children: [(0, jsx_runtime_1.jsx)(InputField_1.default, { label: label, errorText: errorText, infoText: infoText, required: required, disabled: disabled, readOnly: !withSearch, value: displayValue, placeholder: withSearch ? (selectedOption ? selectedOption.label : placeholder) : placeholder, leftIcon: leftIcon, className: `react-better-html-dropdown${isOpen ? " react-better-html-dropdown-open" : ""}${inputFieldClassName ? ` ${inputFieldClassName}` : ""}`, zIndex: isOpen || isOpenLate ? 1001 : undefined, onClick: !disabled ? setIsOpen.toggle : undefined, onFocus: setIsFocused.setTrue, onBlur: setIsFocused.setFalse, onKeyDown: onKeyDownInputField, onChangeValue: withSearch ? onChangeValue : undefined, insideInputFieldComponent: (0, jsx_runtime_1.jsx)(Div_1.default, { position: "absolute", top: "100%", left: 0, width: "100%", maxHeight: 300, backgroundColor: theme.colors.backgroundContent, border: `1px solid ${isFocused ? theme.colors.primary : theme.colors.border}`, borderTop: "none", borderBottomLeftRadius: theme.styles.borderRadius, borderBottomRightRadius: theme.styles.borderRadius, boxShadow: "0px 10px 20px #00000020", zIndex: 1000, overflowY: "auto", opacity: !isOpen ? 0 : undefined, pointerEvents: !isOpen ? "none" : undefined, transform: `translateY(${!isOpen ? -10 : 0}px)`, transition: theme.styles.transition, role: "listbox", "aria-label": label, children: isLoadingDebouncedSearchQuery || debounceIsLoading ? ((0, jsx_runtime_1.jsx)(Div_1.default, { padding: `${theme.styles.space / 2}px ${theme.styles.space + theme.styles.gap}px`, children: (0, jsx_runtime_1.jsx)(Loader_1.default.text, {}) })) : filteredOptions.length ? (filteredOptions.map((option, index) => {
145
145
  const isSelected = option.value === value;
146
146
  const isDisabled = option.disabled;
147
147
  const isFocused = index === focusedOptionIndex;
@@ -153,7 +153,7 @@ const DropdownComponent = (0, react_1.forwardRef)(function Dropdown({ label, err
153
153
  })) : ((0, jsx_runtime_1.jsx)(Div_1.default, { padding: `${theme.styles.space / 2}px ${theme.styles.space + theme.styles.gap}px`, children: (0, jsx_runtime_1.jsx)(Text_1.default.unknown, { textAlign: "left", children: debounceMinimumSymbolsRequired !== undefined &&
154
154
  searchQuery.length < debounceMinimumSymbolsRequired
155
155
  ? `Enter at least ${debounceMinimumSymbolsRequired} characters`
156
- : "No options" }) })) }), role: "combobox", "aria-expanded": isOpen, "aria-controls": "dropdown-list", "aria-haspopup": "listbox", "aria-label": label, ref: inputRef }), (0, jsx_runtime_1.jsxs)(Div_1.default.row, { position: "absolute", top: 46 / 2 + (label ? 16 + theme.styles.gap / 2 : 0), right: theme.styles.space + 1, alignItems: "center", gap: theme.styles.gap, transform: "translateY(-50%)", pointerEvents: "none", filter: disabled ? "brightness(0.9)" : undefined, opacity: disabled ? 0.6 : undefined, zIndex: isOpen || isOpenLate ? 1001 : undefined, children: [(0, jsx_runtime_1.jsx)(Button_1.default.icon, { icon: "XMark", position: "relative", size: 10, iconSize: 14, opacity: !withClearButton ? 0 : undefined, pointerEvents: withClearButton ? "all" : undefined, onClick: onClickClearButton, disabled: !withClearButton }), (0, jsx_runtime_1.jsx)(Icon_1.default, { name: "chevronDown", position: "relative", size: 16, color: theme.colors.textSecondary, transform: `rotate(${isOpen ? 180 : 0}deg)`, transition: theme.styles.transition, pointerEvents: "none" })] })] }) }));
156
+ : "No options" }) })) }), role: "combobox", "aria-expanded": isOpen, "aria-controls": "dropdown-list", "aria-haspopup": "listbox", "aria-label": label, ref: inputRef }), (0, jsx_runtime_1.jsxs)(Div_1.default.row, { position: "absolute", top: 46 / 2 + (label ? 16 + theme.styles.gap / 2 : 0), right: theme.styles.space + 1, alignItems: "center", gap: theme.styles.gap, transform: "translateY(-50%)", pointerEvents: "none", filter: disabled ? "brightness(0.9)" : undefined, opacity: disabled ? 0.6 : undefined, zIndex: isOpen || isOpenLate ? 1001 : undefined, children: [!withoutClearButton && ((0, jsx_runtime_1.jsx)(Button_1.default.icon, { icon: "XMark", position: "relative", size: 10, iconSize: 14, opacity: !withClearButton ? 0 : undefined, pointerEvents: withClearButton ? "all" : undefined, onClick: onClickClearButton, disabled: !withClearButton })), (0, jsx_runtime_1.jsx)(Icon_1.default, { name: "chevronDown", position: "relative", size: 16, color: theme.colors.textSecondary, transform: `rotate(${isOpen ? 180 : 0}deg)`, transition: theme.styles.transition, pointerEvents: "none" })] })] }) }));
157
157
  });
158
158
  const Dropdown = (0, react_1.memo)(DropdownComponent);
159
159
  exports.default = Dropdown;
@@ -21,6 +21,7 @@ type InputFieldComponentType = {
21
21
  email: (props: ComponentPropWithRef<HTMLInputElement, InputFieldProps>) => React.ReactElement;
22
22
  password: (props: ComponentPropWithRef<HTMLInputElement, InputFieldProps>) => React.ReactElement;
23
23
  search: (props: ComponentPropWithRef<HTMLInputElement, InputFieldProps>) => React.ReactElement;
24
+ phoneNumber: (props: ComponentPropWithRef<HTMLInputElement, OmitProps<InputFieldProps, "type">>) => React.ReactElement;
24
25
  };
25
26
  declare const InputFieldComponent: InputFieldComponentType;
26
27
  export type TextareaFieldProps = OmitProps<InputFieldProps, "type"> & OmitProps<React.ComponentProps<"textarea">, "style" | "ref">;
@@ -29,5 +30,6 @@ declare const InputField: typeof InputFieldComponent & {
29
30
  email: typeof InputFieldComponent.email;
30
31
  password: typeof InputFieldComponent.password;
31
32
  search: typeof InputFieldComponent.search;
33
+ phoneNumber: typeof InputFieldComponent.phoneNumber;
32
34
  };
33
35
  export default InputField;
@@ -6,12 +6,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  const react_1 = require("react");
8
8
  const styled_components_1 = __importDefault(require("styled-components"));
9
+ const countries_1 = require("../constants/countries");
9
10
  const hooks_1 = require("../utils/hooks");
10
11
  const Text_1 = __importDefault(require("./Text"));
11
12
  const Div_1 = __importDefault(require("./Div"));
12
13
  const Icon_1 = __importDefault(require("./Icon"));
13
14
  const Button_1 = __importDefault(require("./Button"));
14
15
  const Label_1 = __importDefault(require("./Label"));
16
+ const Dropdown_1 = __importDefault(require("./Dropdown"));
17
+ const Image_1 = __importDefault(require("./Image"));
15
18
  const BetterHtmlProvider_1 = require("./BetterHtmlProvider");
16
19
  const InputElement = styled_components_1.default.input.withConfig({
17
20
  shouldForwardProp: (prop) => !["theme", "withLeftIcon", "withRightIcon", "normalStyle", "hoverStyle"].includes(prop),
@@ -44,6 +47,17 @@ const InputElement = styled_components_1.default.input.withConfig({
44
47
  cursor: not-allowed;
45
48
  }
46
49
 
50
+ &.react-better-html-phone-number-holder {
51
+ border-right: none;
52
+ border-top-right-radius: 0px;
53
+ border-bottom-right-radius: 0px;
54
+ }
55
+
56
+ &.react-better-html-phone-number {
57
+ border-top-left-radius: 0px;
58
+ border-bottom-left-radius: 0px;
59
+ }
60
+
47
61
  &.react-better-html-dropdown {
48
62
  padding-right: ${(props) => props.theme.styles.space + 16 + props.theme.styles.space - 1}px;
49
63
 
@@ -139,9 +153,46 @@ InputFieldComponent.password = (0, react_1.forwardRef)(function Email({ ...props
139
153
  InputFieldComponent.search = (0, react_1.forwardRef)(function Email({ ...props }, ref) {
140
154
  return (0, jsx_runtime_1.jsx)(InputFieldComponent, { leftIcon: "magnifyingGlass", placeholder: "Search...", ref: ref, ...props });
141
155
  });
156
+ InputFieldComponent.phoneNumber = (0, react_1.forwardRef)(function Email({ value, onChangeValue, ...props }, ref) {
157
+ const theme = (0, BetterHtmlProvider_1.useTheme)();
158
+ const [dropdownValue, setDropdownValue] = (0, react_1.useState)();
159
+ const [inputFieldValue, setInputFieldValue] = (0, react_1.useState)(value?.toString() ?? "");
160
+ const renderOption = (0, react_1.useCallback)((option, index, isSelected) => ((0, jsx_runtime_1.jsxs)(Div_1.default.row, { alignItems: "center", gap: theme.styles.gap, children: [(0, jsx_runtime_1.jsx)(Image_1.default, { src: `https://flagcdn.com/w80/${option.data?.code.toString().toLowerCase()}.webp`, width: 20 }), (0, jsx_runtime_1.jsx)(Text_1.default, { children: option.label })] })), []);
161
+ const onChangeValueElement = (0, react_1.useCallback)((value) => {
162
+ const readyValue = value.replace(/\D/g, "");
163
+ setInputFieldValue(readyValue);
164
+ onChangeValue?.(dropdownValue ? `+${dropdownValue}${readyValue}` : readyValue);
165
+ }, [onChangeValue, dropdownValue]);
166
+ const options = (0, react_1.useMemo)(() => countries_1.countries.map((country) => ({
167
+ value: country.phoneNumberExtension,
168
+ label: `+${country.phoneNumberExtension}`,
169
+ data: country,
170
+ })), []);
171
+ const defaultValue = (0, react_1.useMemo)(() => {
172
+ const thisTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
173
+ const initialDefaultValue = options.find((option) => option.data?.timeZone === thisTimeZone)?.value ?? "";
174
+ setDropdownValue(initialDefaultValue);
175
+ return initialDefaultValue;
176
+ }, [options]);
177
+ (0, react_1.useEffect)(() => {
178
+ if (value === undefined || value === null)
179
+ return;
180
+ const newValue = value.toString();
181
+ const country = countries_1.countries.find((country) => country.phoneNumberExtension ===
182
+ newValue.slice(newValue.startsWith("+") ? 1 : 0, country.phoneNumberExtension.length + (newValue.startsWith("+") ? 1 : 0)));
183
+ if (!country) {
184
+ setInputFieldValue(newValue);
185
+ return;
186
+ }
187
+ setDropdownValue(country.phoneNumberExtension);
188
+ setInputFieldValue(newValue.slice(country?.phoneNumberExtension.length + 1));
189
+ }, [value]);
190
+ return ((0, jsx_runtime_1.jsxs)(Div_1.default.row, { children: [(0, jsx_runtime_1.jsx)(Dropdown_1.default, { options: options, renderOption: renderOption, width: 130, withSearch: true, placeholder: "+00", inputFieldClassName: "react-better-html-phone-number-holder", defaultValue: defaultValue, value: dropdownValue, onChange: setDropdownValue, withoutClearButton: true }), (0, jsx_runtime_1.jsx)(InputFieldComponent, { placeholder: "Phone number", className: "react-better-html-phone-number", value: inputFieldValue, onChangeValue: onChangeValueElement, ref: ref, ...props })] }));
191
+ });
142
192
  const InputField = (0, react_1.memo)(InputFieldComponent);
143
193
  InputField.multiline = InputFieldComponent.multiline;
144
194
  InputField.email = InputFieldComponent.email;
145
195
  InputField.password = InputFieldComponent.password;
146
196
  InputField.search = InputFieldComponent.search;
197
+ InputField.phoneNumber = InputFieldComponent.phoneNumber;
147
198
  exports.default = InputField;
@@ -0,0 +1,2 @@
1
+ import { Country } from "../types/countries";
2
+ export declare const countries: Country[];