@ews-admin/global-design-system 1.1.12 → 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/README.md +3 -3
- package/dist/components/Button/Button.d.ts +1 -1
- package/dist/components/Button/Button.d.ts.map +1 -1
- package/dist/components/DropdownMultiSelect/DropdownMultiSelect.d.ts +22 -0
- package/dist/components/DropdownMultiSelect/DropdownMultiSelect.d.ts.map +1 -0
- package/dist/components/DropdownMultiSelect/index.d.ts +3 -0
- package/dist/components/DropdownMultiSelect/index.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +25 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +140 -12
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +140 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Button/Button.tsx +22 -17
- package/src/components/DropdownMultiSelect/DropdownMultiSelect.tsx +271 -0
- package/src/components/DropdownMultiSelect/index.ts +2 -0
- package/src/components/Modal/Modal.tsx +2 -2
- package/src/index.ts +3 -0
- package/src/utils/index.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -81,19 +81,20 @@ const formatNumeric = (value) => {
|
|
|
81
81
|
*/
|
|
82
82
|
function isValidPhoneNumber(value) {
|
|
83
83
|
const trimmedValue = value.trim();
|
|
84
|
-
const phoneRegex = /^[0-9]\d{1,
|
|
84
|
+
const phoneRegex = /^[0-9]\d{1,17}$/;
|
|
85
85
|
return phoneRegex.test(trimmedValue);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
const Button = React.forwardRef(({ className, variant = "primary", size = "md", loading = false, fullWidth = false, leftIcon, rightIcon, children, disabled, ...props }, ref) => {
|
|
89
|
-
const baseStyles = "inline-flex items-center justify-center font-medium rounded-md transition-colors focus:outline-none
|
|
88
|
+
const Button = React.forwardRef(({ className, variant = "ews-primary", size = "md", loading = false, fullWidth = false, leftIcon, rightIcon, children, disabled, ...props }, ref) => {
|
|
89
|
+
const baseStyles = "inline-flex items-center justify-center font-medium rounded-md transition-colors focus:outline-none disabled:opacity-50 disabled:pointer-events-none";
|
|
90
90
|
const variants = {
|
|
91
|
-
primary: "bg-ews-primary text-white hover:bg-ews-primary-hover
|
|
92
|
-
secondary: "bg-ews-secondary text-white hover:bg-ews-secondary-hover
|
|
93
|
-
success: "bg-ews-success text-white hover:bg-ews-success-hover
|
|
94
|
-
warning: "bg-ews-warning text-white hover:bg-ews-warning-hover
|
|
95
|
-
error: "bg-ews-error text-white hover:bg-ews-error-hover
|
|
96
|
-
|
|
91
|
+
"ews-primary": "bg-ews-primary text-white hover:bg-ews-primary-hover",
|
|
92
|
+
"ews-secondary": "bg-ews-secondary text-white hover:bg-ews-secondary-hover",
|
|
93
|
+
success: "bg-ews-success text-white hover:bg-ews-success-hover",
|
|
94
|
+
warning: "bg-ews-warning text-white hover:bg-ews-warning-hover",
|
|
95
|
+
error: "bg-ews-error text-white hover:bg-ews-error-hover",
|
|
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",
|
|
@@ -105,7 +106,7 @@ const Button = React.forwardRef(({ className, variant = "primary", size = "md",
|
|
|
105
106
|
md: "h-5 w-5",
|
|
106
107
|
lg: "h-6 w-6",
|
|
107
108
|
};
|
|
108
|
-
return (jsxRuntime.jsxs("button", { className: cn(baseStyles, variants[variant], sizes[size], fullWidth && "w-full", className), ref: ref, disabled: disabled || loading, ...props, children: [loading && (jsxRuntime.jsxs("svg", { className: "
|
|
109
|
+
return (jsxRuntime.jsxs("button", { className: cn(baseStyles, variants[variant], sizes[size], fullWidth && "w-full", className), ref: ref, disabled: disabled || loading, ...props, children: [loading && (jsxRuntime.jsxs("svg", { className: "mr-2 -ml-1 w-4 h-4 animate-spin", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [jsxRuntime.jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), jsxRuntime.jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] })), !loading && leftIcon && (jsxRuntime.jsx("span", { className: cn("flex items-center mr-2", iconSizes[size]), children: leftIcon })), children, !loading && rightIcon && (jsxRuntime.jsx("span", { className: cn("flex items-center ml-2", iconSizes[size]), children: rightIcon }))] }));
|
|
109
110
|
});
|
|
110
111
|
Button.displayName = "Button";
|
|
111
112
|
|
|
@@ -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,
|
|
@@ -1590,7 +1635,90 @@ const Modal = ({ isOpen, onClose, title, children, variant = "info", primaryActi
|
|
|
1590
1635
|
onClose();
|
|
1591
1636
|
}
|
|
1592
1637
|
};
|
|
1593
|
-
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: "
|
|
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 }))] }))] })] }));
|
|
1639
|
+
};
|
|
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 })] }));
|
|
1594
1722
|
};
|
|
1595
1723
|
|
|
1596
1724
|
const Logo = ({ size = "md", showTagline: _showTagline = true, iconOnly = false, variant = "normal", className, onClick, }) => {
|
|
@@ -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;
|