@ews-admin/global-design-system 1.1.13 → 1.1.14

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.js CHANGED
@@ -81,7 +81,7 @@ const formatNumeric = (value) => {
81
81
  */
82
82
  function isValidPhoneNumber(value) {
83
83
  const trimmedValue = value.trim();
84
- const phoneRegex = /^[0-9]\d{1,14}$/;
84
+ const phoneRegex = /^[0-9]\d{1,17}$/;
85
85
  return phoneRegex.test(trimmedValue);
86
86
  }
87
87
 
@@ -94,6 +94,7 @@ const Button = React.forwardRef(({ className, variant = "ews-primary", size = "m
94
94
  warning: "bg-ews-warning text-white hover:bg-ews-warning-hover",
95
95
  error: "bg-ews-error text-white hover:bg-ews-error-hover",
96
96
  outline: "bg-transparent text-sm font-medium text-ews-primary hover:text-ews-primary/80",
97
+ ghost: "border border-ews-primary text-ews-primary hover:bg-ews-primary hover:text-white disabled:border-gray-400 disabled:text-gray-400 focus:ring-2 focus:ring-offset-2 focus:ring-ews-primary",
97
98
  };
98
99
  const sizes = {
99
100
  sm: "px-3 py-1.5 text-sm",
@@ -1257,6 +1258,50 @@ function useController(props) {
1257
1258
  }), [field, formState, fieldState]);
1258
1259
  }
1259
1260
 
1261
+ /**
1262
+ * Component based on `useController` hook to work with controlled component.
1263
+ *
1264
+ * @remarks
1265
+ * [API](https://react-hook-form.com/docs/usecontroller/controller) • [Demo](https://codesandbox.io/s/react-hook-form-v6-controller-ts-jwyzw) • [Video](https://www.youtube.com/watch?v=N2UNk_UCVyA)
1266
+ *
1267
+ * @param props - the path name to the form field value, and validation rules.
1268
+ *
1269
+ * @returns provide field handler functions, field and form state.
1270
+ *
1271
+ * @example
1272
+ * ```tsx
1273
+ * function App() {
1274
+ * const { control } = useForm<FormValues>({
1275
+ * defaultValues: {
1276
+ * test: ""
1277
+ * }
1278
+ * });
1279
+ *
1280
+ * return (
1281
+ * <form>
1282
+ * <Controller
1283
+ * control={control}
1284
+ * name="test"
1285
+ * render={({ field: { onChange, onBlur, value, ref }, formState, fieldState }) => (
1286
+ * <>
1287
+ * <input
1288
+ * onChange={onChange} // send value to hook form
1289
+ * onBlur={onBlur} // notify when input is touched
1290
+ * value={value} // return updated value
1291
+ * ref={ref} // set ref for focus management
1292
+ * />
1293
+ * <p>{formState.isSubmitted ? "submitted" : ""}</p>
1294
+ * <p>{fieldState.isTouched ? "touched" : ""}</p>
1295
+ * </>
1296
+ * )}
1297
+ * />
1298
+ * </form>
1299
+ * );
1300
+ * }
1301
+ * ```
1302
+ */
1303
+ const Controller = (props) => props.render(useController(props));
1304
+
1260
1305
  function useSelectField({ name, control, options: _options, rules, defaultValue, }) {
1261
1306
  const { field, fieldState: { error, invalid }, } = useController({
1262
1307
  name,
@@ -1593,6 +1638,89 @@ const Modal = ({ isOpen, onClose, title, children, variant = "info", primaryActi
1593
1638
  return (jsxRuntime.jsxs("div", { className: "flex fixed inset-0 z-50 justify-center items-center", children: [jsxRuntime.jsx("div", { className: "absolute inset-0 backdrop-blur-sm bg-black/50", onClick: handleOverlayClick }), jsxRuntime.jsxs("div", { className: cn("relative mx-4 w-full max-w-md bg-white rounded-lg shadow-xl transition-all transform", "duration-200 animate-in fade-in-0 zoom-in-95", className), role: "dialog", "aria-modal": "true", "aria-labelledby": "modal-title", children: [jsxRuntime.jsxs("div", { className: cn("flex items-center justify-between p-6 border-b", variantStyles.borderColor), children: [jsxRuntime.jsxs("div", { className: "flex items-center space-x-3", children: [jsxRuntime.jsx("div", { className: cn("p-2 rounded-full", variantStyles.iconBg), children: variantStyles.icon }), jsxRuntime.jsx("h2", { id: "modal-title", className: cn("text-lg font-semibold", variantStyles.titleColor), children: title })] }), jsxRuntime.jsx("button", { onClick: onClose, className: "p-1 text-gray-400 transition-colors hover:text-gray-600", "aria-label": "Close modal", children: jsxRuntime.jsx(X, { className: "w-5 h-5" }) })] }), jsxRuntime.jsx("div", { className: cn("p-6", contentClassName), children: jsxRuntime.jsx("div", { className: "leading-relaxed text-gray-700", children: error && variant === "error" ? (jsxRuntime.jsxs("div", { className: "space-y-3", children: [jsxRuntime.jsx("p", { children: error.message }), error.fields && error.fields.length > 0 && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("p", { className: "font-semibold text-gray-900", children: "Erreurs de champ:" }), jsxRuntime.jsx("ul", { className: "mt-2 space-y-1", children: error.fields.map((field, index) => (jsxRuntime.jsxs("li", { className: "text-ews-error", children: ["\u2022 ", field.path, ": ", field.message] }, index))) })] }))] })) : (children) }) }), (primaryAction || secondaryAction) && (jsxRuntime.jsxs("div", { className: "flex justify-end items-center p-6 pt-0 space-x-3", children: [secondaryAction && (jsxRuntime.jsx(Button, { variant: "outline", onClick: onSecondaryAction || onClose, disabled: isLoading, children: secondaryAction })), primaryAction && (jsxRuntime.jsx(Button, { variant: variant === "error" ? "error" : "ews-primary", onClick: onPrimaryAction, loading: isLoading, children: primaryAction }))] }))] })] }));
1594
1639
  };
1595
1640
 
1641
+ const DropdownMultiSelect = ({ options, name, control, placeholder = "Select options", searchPlaceholder = "Search...", onChange, value: controlledValue, defaultValue, onValidate, disabled = false, error, label, className, }) => {
1642
+ const [isOpen, setIsOpen] = React.useState(false);
1643
+ const [searchTerm, setSearchTerm] = React.useState("");
1644
+ const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);
1645
+ const dropdownRef = React.useRef(null);
1646
+ const handleToggle = () => {
1647
+ if (!disabled) {
1648
+ setIsOpen(!isOpen);
1649
+ }
1650
+ };
1651
+ const handleClickOutside = (event) => {
1652
+ if (dropdownRef.current &&
1653
+ !dropdownRef.current.contains(event.target)) {
1654
+ setIsOpen(false);
1655
+ setSearchTerm("");
1656
+ }
1657
+ };
1658
+ React.useEffect(() => {
1659
+ document.addEventListener("mousedown", handleClickOutside);
1660
+ return () => {
1661
+ document.removeEventListener("mousedown", handleClickOutside);
1662
+ };
1663
+ }, []);
1664
+ const removeAccents = (str) => str
1665
+ .normalize("NFD")
1666
+ .replace(/[\u0300-\u036f]/g, "")
1667
+ .replace(/ç/g, "c")
1668
+ .replace(/é|è|ê|ë/g, "e")
1669
+ .replace(/à|á|â|ã|ä/g, "a")
1670
+ .replace(/î|ï/g, "i")
1671
+ .replace(/ô|ö/g, "o")
1672
+ .replace(/ù|ú|û|ü/g, "u");
1673
+ const getDisplayValue = (value) => {
1674
+ if (typeof value === "string" || typeof value === "number") {
1675
+ return String(value);
1676
+ }
1677
+ return options.find((opt) => opt.value === value)?.label || "";
1678
+ };
1679
+ const filteredOptions = options.filter((option) => removeAccents(option.label.toLowerCase()).includes(removeAccents(searchTerm.toLowerCase())));
1680
+ const renderDropdown = ({ value = [], onChange: fieldOnChange, }) => (jsxRuntime.jsxs("div", { className: cn("relative", className), ref: dropdownRef, children: [jsxRuntime.jsxs("button", { type: "button", onClick: handleToggle, "aria-label": name, disabled: disabled, className: cn("flex w-full items-center justify-between rounded-md border border-ews-gray-300 bg-white px-3 py-2 text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-ews-primary focus:ring-offset-0 disabled:bg-ews-gray-50 disabled:text-ews-gray-500", isOpen ? "rounded-b-none border-b-0" : "rounded-md", error && "border-ews-error focus:ring-ews-error"), children: [jsxRuntime.jsx("span", { className: cn("truncate", !value?.length && "text-ews-gray-500"), children: value?.length > 0
1681
+ ? value.map((v) => getDisplayValue(v)).join(", ")
1682
+ : placeholder }), jsxRuntime.jsx("span", { className: cn("ml-2 w-4 h-4 transition-transform transform", isOpen ? "rotate-180" : "rotate-0"), children: jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) }) })] }), isOpen && (jsxRuntime.jsxs("div", { className: "absolute z-50 w-full bg-white rounded-b-md border border-t-0 shadow-lg border-ews-gray-300", children: [jsxRuntime.jsx("div", { className: "p-2 border-b border-ews-gray-200", children: jsxRuntime.jsx(Input, { type: "text", placeholder: searchPlaceholder, value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), className: "p-0 border-0 shadow-none focus:ring-0", size: "sm" }) }), jsxRuntime.jsx("div", { className: "overflow-y-auto max-h-48", children: filteredOptions.length > 0 ? (filteredOptions.map((option) => (jsxRuntime.jsxs("div", { className: "flex items-center p-2 cursor-pointer hover:bg-ews-gray-100", onClick: () => {
1683
+ const currentValue = value ?? [];
1684
+ const isSelected = currentValue.some((item) => JSON.stringify(item) === JSON.stringify(option.value));
1685
+ const newValue = isSelected
1686
+ ? currentValue.filter((item) => JSON.stringify(item) !==
1687
+ JSON.stringify(option.value))
1688
+ : [...currentValue, option.value];
1689
+ if (onValidate && !onValidate(newValue)) {
1690
+ return;
1691
+ }
1692
+ fieldOnChange(newValue);
1693
+ onChange?.(newValue);
1694
+ }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: (value ?? []).some((item) => JSON.stringify(item) === JSON.stringify(option.value)), onChange: (e) => {
1695
+ e.stopPropagation();
1696
+ const currentValue = value ?? [];
1697
+ const isSelected = currentValue.some((item) => JSON.stringify(item) === JSON.stringify(option.value));
1698
+ const newValue = isSelected
1699
+ ? currentValue.filter((item) => JSON.stringify(item) !==
1700
+ JSON.stringify(option.value))
1701
+ : [...currentValue, option.value];
1702
+ if (onValidate && !onValidate(newValue)) {
1703
+ return;
1704
+ }
1705
+ fieldOnChange(newValue);
1706
+ onChange?.(newValue);
1707
+ }, onClick: (e) => e.stopPropagation(), className: "mr-3 w-4 h-4 rounded border-ews-gray-300 text-ews-primary focus:ring-ews-primary" }), jsxRuntime.jsx("label", { className: "text-sm cursor-pointer text-ews-gray-700", children: option.label })] }, getDisplayValue(option.value))))) : (jsxRuntime.jsx("div", { className: "p-2 text-sm text-ews-gray-500", children: "No options found" })) })] }))] }));
1708
+ // Render controlled version with react-hook-form
1709
+ if (control) {
1710
+ return (jsxRuntime.jsx(Controller, { name: name, control: control, render: ({ field: { value, onChange } }) => (jsxRuntime.jsxs("div", { children: [label && (jsxRuntime.jsx("label", { className: "block mb-1 text-sm font-medium text-ews-gray-700", children: label })), renderDropdown({ value, onChange }), error && jsxRuntime.jsx("p", { className: "mt-1 text-sm text-ews-error", children: error })] })) }));
1711
+ }
1712
+ // Render uncontrolled version
1713
+ return (jsxRuntime.jsxs("div", { children: [label && (jsxRuntime.jsx("label", { className: "block mb-1 text-sm font-medium text-ews-gray-700", children: label })), renderDropdown({
1714
+ value: controlledValue ?? uncontrolledValue,
1715
+ onChange: (newValue) => {
1716
+ if (controlledValue === undefined) {
1717
+ setUncontrolledValue(newValue);
1718
+ }
1719
+ onChange?.(newValue);
1720
+ },
1721
+ }), error && jsxRuntime.jsx("p", { className: "mt-1 text-sm text-ews-error", children: error })] }));
1722
+ };
1723
+
1596
1724
  const Logo = ({ size = "md", showTagline: _showTagline = true, iconOnly = false, variant = "normal", className, onClick, }) => {
1597
1725
  const sizes = {
1598
1726
  sm: "h-8",
@@ -1792,6 +1920,7 @@ exports.ArrowRight = ArrowRight;
1792
1920
  exports.Button = Button;
1793
1921
  exports.Check = Check;
1794
1922
  exports.DoctorIcon = DoctorIcon;
1923
+ exports.DropdownMultiSelect = DropdownMultiSelect;
1795
1924
  exports.Icon = Icon;
1796
1925
  exports.Input = Input;
1797
1926
  exports.Logo = Logo;