@ews-admin/global-design-system 1.27.0 → 1.29.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/components/SearchAutocomplete/SearchAutocomplete.d.ts +8 -1
- package/dist/components/SearchAutocomplete/SearchAutocomplete.d.ts.map +1 -1
- package/dist/components/Select/Select.d.ts +15 -0
- package/dist/components/Select/Select.d.ts.map +1 -1
- package/dist/index.d.ts +34 -1
- package/dist/index.esm.js +48 -7
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +48 -7
- package/dist/index.js.map +1 -1
- package/dist/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.d.ts +6 -0
- package/dist/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.d.ts.map +1 -1
- package/dist/utils/env-config.d.ts +5 -0
- package/dist/utils/env-config.d.ts.map +1 -1
- package/package.json +3 -1
- package/src/components/SearchAutocomplete/SearchAutocomplete.tsx +14 -1
- package/src/components/Select/Select.tsx +20 -2
- package/src/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.tsx +31 -1
- package/src/utils/env-config.ts +34 -1
package/dist/index.js
CHANGED
|
@@ -58,6 +58,29 @@ function buildUrls(environment) {
|
|
|
58
58
|
adminUrl: `https://${prefix}admin.${domain}`,
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Derives the environment from the host the app is served from, for builds
|
|
63
|
+
* that did not set VITE_ENV. Hosts actually served (Traefik rules in
|
|
64
|
+
* infrastructure/medecine360-hetzner/deployment/docker-stack.*.yml):
|
|
65
|
+
* - local: localhost, 127.0.0.1, *.medecine360local.com (local.app., api., ...)
|
|
66
|
+
* - dev: dev.{app,login,dashboard,admin,api}.medecine360.com
|
|
67
|
+
* - qa: qa.{app,login,dashboard,admin,api}.medecine360.com
|
|
68
|
+
* - prod: medecine360.com, {login,dashboard,admin,api}.medecine360.com
|
|
69
|
+
* Any other host (or no window, e.g. SSR or tests) falls back to local.
|
|
70
|
+
*/
|
|
71
|
+
function environmentFromHost() {
|
|
72
|
+
const hostname = typeof window !== "undefined" ? window.location?.hostname?.toLowerCase() : undefined;
|
|
73
|
+
if (!hostname)
|
|
74
|
+
return "local";
|
|
75
|
+
if (hostname === "medecine360.com" || hostname.endsWith(".medecine360.com")) {
|
|
76
|
+
if (hostname.startsWith("dev."))
|
|
77
|
+
return "dev";
|
|
78
|
+
if (hostname.startsWith("qa."))
|
|
79
|
+
return "qa";
|
|
80
|
+
return "prod";
|
|
81
|
+
}
|
|
82
|
+
return "local";
|
|
83
|
+
}
|
|
61
84
|
/**
|
|
62
85
|
* Creates an environment configuration with all derived URLs.
|
|
63
86
|
*
|
|
@@ -67,11 +90,20 @@ function buildUrls(environment) {
|
|
|
67
90
|
* For local development, sensible defaults are provided but can be overridden
|
|
68
91
|
* (e.g. to point BFF URL at a mock server).
|
|
69
92
|
*
|
|
93
|
+
* When env is empty (build without VITE_ENV), the environment is derived from
|
|
94
|
+
* the current host instead of always assuming local, so a deployed build never
|
|
95
|
+
* links to http://local.*.medecine360local.com. A console warning names the
|
|
96
|
+
* derived environment so the misconfigured build stays visible.
|
|
97
|
+
*
|
|
70
98
|
* @param env - The environment name (VITE_ENV value)
|
|
71
99
|
* @param overrides - Optional URL overrides (e.g. for local mock servers)
|
|
72
100
|
*/
|
|
73
101
|
function createEnvConfig(env, overrides) {
|
|
74
|
-
|
|
102
|
+
let environment = env;
|
|
103
|
+
if (!environment) {
|
|
104
|
+
environment = environmentFromHost();
|
|
105
|
+
console.warn(`[env-config] VITE_ENV is not set; using "${environment}" derived from the current host.`);
|
|
106
|
+
}
|
|
75
107
|
const derived = buildUrls(environment);
|
|
76
108
|
return {
|
|
77
109
|
env: environment,
|
|
@@ -32786,7 +32818,7 @@ const Input = React.forwardRef(({ className, variant = "default", size = "md", l
|
|
|
32786
32818
|
});
|
|
32787
32819
|
Input.displayName = "Input";
|
|
32788
32820
|
|
|
32789
|
-
const Select = React.forwardRef(({ options = [], value, onChange, placeholder = "Select an option...", label, helperText, error, isError = false, size = "md", disabled = false, required = false, searchable = false, multiple: _multiple = false, selectClassName, containerClassName, dropdownClassName, maxHeight = 200, clearable = false, renderOption, renderValue, renderTrigger, ...props }, ref) => {
|
|
32821
|
+
const Select = React.forwardRef(({ options = [], value, onChange, placeholder = "Select an option...", label, helperText, error, isError = false, size = "md", disabled = false, required = false, searchable = false, multiple: _multiple = false, selectClassName, containerClassName, dropdownClassName, maxHeight = 200, clearable = false, renderOption, renderValue, renderTrigger, searchPlaceholder = "Search options...", noOptionsFoundText = "No options found", noOptionsAvailableText = "No options available", ...props }, ref) => {
|
|
32790
32822
|
const generatedId = React.useId();
|
|
32791
32823
|
const selectId = `select-${generatedId}`;
|
|
32792
32824
|
const hasError = isError || !!error;
|
|
@@ -32995,7 +33027,7 @@ const Select = React.forwardRef(({ options = [], value, onChange, placeholder =
|
|
|
32995
33027
|
// Padding for icons
|
|
32996
33028
|
"pr-10", selectClassName), ...props, children: jsxRuntime.jsx("div", { className: "flex justify-between items-center", children: renderTrigger ? (renderTrigger()) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: selectedOption ? (renderValue ? (renderValue(selectedOption)) : (jsxRuntime.jsx("span", { className: "truncate", children: selectedOption.label }))) : (jsxRuntime.jsx("span", { className: "text-ews-gray-500", children: placeholder })) }), jsxRuntime.jsxs("div", { className: "flex items-center ml-2 space-x-1", children: [clearable && selectedOption && !disabled && (jsxRuntime.jsx("button", { type: "button", onClick: handleClear, className: "p-1 rounded hover:bg-ews-gray-100", children: jsxRuntime.jsx(X, { className: cn(iconSizeClasses[size], "text-ews-gray-400") }) })), jsxRuntime.jsx(ChevronDown, { className: cn(iconSizeClasses[size], hasError ? "text-ews-error" : "text-ews-gray-400", disabled && "text-ews-gray-300", isOpen && "rotate-180 transition-transform") })] })] })) }) }), isOpen && (jsxRuntime.jsxs("div", { ref: dropdownRef, role: "listbox", className: cn("absolute z-50 w-full bg-white rounded-md border shadow-lg border-ews-gray-300", "focus:outline-none", dropdownPosition === "top"
|
|
32997
33029
|
? "bottom-full mb-1"
|
|
32998
|
-
: "top-full mt-1", dropdownClassName), style: { maxHeight: `${maxHeight}px` }, children: [searchable && (jsxRuntime.jsx("div", { className: "p-2 border-b border-ews-gray-200", children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(Search, { className: "absolute left-3 top-1/2 w-4 h-4 transform -translate-y-1/2 text-ews-gray-400" }), jsxRuntime.jsx(Input, { ref: inputRef, type: "text", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), placeholder:
|
|
33030
|
+
: "top-full mt-1", dropdownClassName), style: { maxHeight: `${maxHeight}px` }, children: [searchable && (jsxRuntime.jsx("div", { className: "p-2 border-b border-ews-gray-200", children: jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(Search, { className: "absolute left-3 top-1/2 w-4 h-4 transform -translate-y-1/2 text-ews-gray-400" }), jsxRuntime.jsx(Input, { ref: inputRef, type: "text", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), placeholder: searchPlaceholder, leftIcon: jsxRuntime.jsx(Search, { className: "w-4 h-4 text-ews-gray-400" }) })] }) })), jsxRuntime.jsx("div", { className: "overflow-auto py-1", style: { maxHeight: `${maxHeight - (searchable ? 60 : 0)}px` }, children: filteredOptions.length === 0 ? (jsxRuntime.jsx("div", { className: "px-3 py-2 text-sm text-ews-gray-500", children: searchTerm ? noOptionsFoundText : noOptionsAvailableText })) : (filteredOptions.map((option, index) => {
|
|
32999
33031
|
const isSelected = option.value === value;
|
|
33000
33032
|
const isFocused = index === focusedIndex;
|
|
33001
33033
|
return (jsxRuntime.jsx("div", { ref: (el) => (optionRefs.current[index] = el), role: "option", "aria-selected": isSelected, onClick: () => handleSelect(option), className: cn("px-3 py-2 text-sm cursor-pointer transition-colors", isSelected && "bg-ews-primary text-white", !isSelected && isFocused && "bg-ews-gray-100", !isSelected && !isFocused && "hover:bg-ews-gray-50", option.disabled && "opacity-50 cursor-not-allowed"), children: renderOption ? (renderOption(option, isSelected)) : (jsxRuntime.jsx("span", { className: "truncate", children: option.label })) }, `${String(option.value)}-${index}`));
|
|
@@ -33072,7 +33104,7 @@ function useSelectField({ name, control, options: _options, rules, defaultValue,
|
|
|
33072
33104
|
};
|
|
33073
33105
|
}
|
|
33074
33106
|
|
|
33075
|
-
function SearchAutocomplete({ onSelect, selectedId, searchFunction, getEntityById, getDisplayValue, getSecondaryText, placeholder, icon = Search, disabled = false, minSearchLength = 2, debounceTime = 300, error, }) {
|
|
33107
|
+
function SearchAutocomplete({ onSelect, selectedId, searchFunction, getEntityById, getDisplayValue, getSecondaryText, placeholder, icon = Search, disabled = false, minSearchLength = 2, debounceTime = 300, error, getMinCharactersHint = (min) => `(min. ${min} characters)`, }) {
|
|
33076
33108
|
const [searchTerm, setSearchTerm] = React.useState("");
|
|
33077
33109
|
const [entities, setEntities] = React.useState([]);
|
|
33078
33110
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
@@ -33185,7 +33217,11 @@ function SearchAutocomplete({ onSelect, selectedId, searchFunction, getEntityByI
|
|
|
33185
33217
|
setSearchTerm("");
|
|
33186
33218
|
onSelect("");
|
|
33187
33219
|
};
|
|
33188
|
-
|
|
33220
|
+
const minCharactersHint = getMinCharactersHint(minSearchLength);
|
|
33221
|
+
const inputPlaceholder = minCharactersHint
|
|
33222
|
+
? `${placeholder} ${minCharactersHint}`
|
|
33223
|
+
: placeholder;
|
|
33224
|
+
return (jsxRuntime.jsxs("div", { className: "relative", ref: dropdownRef, children: [jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(Input, { type: "text", placeholder: inputPlaceholder, value: selectedEntity ? "" : searchTerm, onChange: handleSearchChange, onFocus: () => searchTerm.length >= minSearchLength &&
|
|
33189
33225
|
isUserSearch.current &&
|
|
33190
33226
|
setShowDropdown(true), disabled: disabled, autoComplete: "off", name: "search-autocomplete", leftIcon: React.createElement(icon, { size: 16 }) }), selectedEntity && (jsxRuntime.jsx("button", { type: "button", className: "flex absolute inset-y-0 right-0 items-center pr-3", onClick: handleClearSelection, disabled: disabled, children: jsxRuntime.jsx(X, { className: "w-5 h-5 text-gray-400 hover:text-gray-500" }) }))] }), selectedEntity && (jsxRuntime.jsx("div", { className: "p-4 mt-3 rounded-lg border bg-neutral-50 border-neutral-200", children: jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [jsxRuntime.jsxs("div", { className: "flex items-center", children: [selectedEntity.photoUrl ? (jsxRuntime.jsx("img", { src: selectedEntity.photoUrl, alt: getDisplayValue(selectedEntity), className: "object-cover mr-3 w-10 h-10 rounded-full" })) : (React.createElement(icon, {
|
|
33191
33227
|
className: "w-10 h-10 text-neutral-400 mr-3",
|
|
@@ -35256,7 +35292,7 @@ const PaymentDueBanner = ({ title, subtitle, warning, ctaLabel, onCtaClick, onDi
|
|
|
35256
35292
|
*/
|
|
35257
35293
|
const SecureLoader = ({ label, description }) => (jsxRuntime.jsx("div", { "data-testid": "secure-loader", className: "flex min-h-screen items-center justify-center bg-gray-100", children: jsxRuntime.jsxs("div", { className: "flex flex-col items-center rounded-lg bg-white p-6 shadow-lg", children: [jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(Lock, { className: "h-16 w-16 text-gray-500" }), jsxRuntime.jsx("div", { className: "absolute bottom-2 right-1 h-6 w-6 animate-spin rounded-full border-4 border-gray-300 border-t-green-500" })] }), jsxRuntime.jsx("p", { className: "mt-4 text-lg text-gray-600", children: label }), description && (jsxRuntime.jsx("p", { className: "mt-2 text-sm text-gray-500", children: description }))] }) }));
|
|
35258
35294
|
|
|
35259
|
-
const SpecialtySearchAutocomplete = ({ selectedSpecialties = [], onSpecialtiesChange, specialties: availableSpecialties, placeholder = "Search and select medical specialties...", className = "", disabled = false, maxSelections, showSelectedCount = true, title = "Medical Specialties", label = "Select Specialties", getSelectedCountText, }) => {
|
|
35295
|
+
const SpecialtySearchAutocomplete = ({ selectedSpecialties = [], onSpecialtiesChange, specialties: availableSpecialties, placeholder = "Search and select medical specialties...", className = "", disabled = false, maxSelections, showSelectedCount = true, title = "Medical Specialties", label = "Select Specialties", getSelectedCountText, removeSpecialtyLabel = "Remove specialty", }) => {
|
|
35260
35296
|
const [filteredSpecialties, setFilteredSpecialties] = React.useState([]);
|
|
35261
35297
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
35262
35298
|
// Filter specialties based on search term
|
|
@@ -35287,10 +35323,15 @@ const SpecialtySearchAutocomplete = ({ selectedSpecialties = [], onSpecialtiesCh
|
|
|
35287
35323
|
}
|
|
35288
35324
|
onSpecialtiesChange(items);
|
|
35289
35325
|
}, [onSpecialtiesChange, maxSelections]);
|
|
35326
|
+
// MED-1499: a selected specialty must be removable from its chip, not only
|
|
35327
|
+
// by toggling it off again in the dropdown list.
|
|
35328
|
+
const handleRemoveSpecialty = React.useCallback((specialtyToRemove) => {
|
|
35329
|
+
onSpecialtiesChange(selectedSpecialties.filter((specialty) => specialty.id !== specialtyToRemove.id));
|
|
35330
|
+
}, [onSpecialtiesChange, selectedSpecialties]);
|
|
35290
35331
|
const getEntityById = React.useCallback(async (id) => {
|
|
35291
35332
|
return availableSpecialties.find((specialty) => specialty.id === id);
|
|
35292
35333
|
}, [availableSpecialties]);
|
|
35293
|
-
return (jsxRuntime.jsxs("div", { className: cn("space-y-3", className), children: [jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [jsxRuntime.jsx("div", { className: "flex justify-center items-center w-8 h-8 rounded-lg bg-secondary-100", children: jsxRuntime.jsx(Stethoscope, { className: "w-4 h-4 text-secondary-600" }) }), jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900", children: title || "Medical Specialties" })] }), jsxRuntime.jsx("div", { children: jsxRuntime.jsx("label", { className: "block mb-2 text-sm font-medium text-gray-700", children: label || "Select Specialties" }) }), jsxRuntime.jsx(MultiSearchAutocomplete, { items: filteredSpecialties, selectedItems: selectedSpecialties, onSelectionChange: handleSelectionChange, onSearch: fetchSpecialties, getEntityById: getEntityById, getPrimaryText: (specialty) => specialty.label, getSecondaryText: (specialty) => specialty.code, placeholder: placeholder, disabled: disabled, loading: isLoading, multiple: true, keepOpenOnSelect: true, className: "w-full", renderSelectedItem: (specialty) => (jsxRuntime.
|
|
35334
|
+
return (jsxRuntime.jsxs("div", { className: cn("space-y-3", className), children: [jsxRuntime.jsxs("div", { className: "flex items-center space-x-2", children: [jsxRuntime.jsx("div", { className: "flex justify-center items-center w-8 h-8 rounded-lg bg-secondary-100", children: jsxRuntime.jsx(Stethoscope, { className: "w-4 h-4 text-secondary-600" }) }), jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900", children: title || "Medical Specialties" })] }), jsxRuntime.jsx("div", { children: jsxRuntime.jsx("label", { className: "block mb-2 text-sm font-medium text-gray-700", children: label || "Select Specialties" }) }), jsxRuntime.jsx(MultiSearchAutocomplete, { items: filteredSpecialties, selectedItems: selectedSpecialties, onSelectionChange: handleSelectionChange, onSearch: fetchSpecialties, getEntityById: getEntityById, getPrimaryText: (specialty) => specialty.label, getSecondaryText: (specialty) => specialty.code, placeholder: placeholder, disabled: disabled, loading: isLoading, multiple: true, keepOpenOnSelect: true, className: "w-full", renderSelectedItem: (specialty) => (jsxRuntime.jsxs("span", { className: "inline-flex items-center px-3 py-1 text-sm font-medium rounded-full border bg-ews-primary/10 text-ews-primary border-ews-primary/20", children: [specialty.label, jsxRuntime.jsx("button", { type: "button", onClick: () => handleRemoveSpecialty(specialty), className: "ml-2 text-ews-primary/60 hover:text-ews-primary", disabled: disabled, title: removeSpecialtyLabel, "aria-label": removeSpecialtyLabel, children: jsxRuntime.jsx(X, { className: "w-3 h-3" }) })] })), renderListItem: (specialty, isSelected) => (jsxRuntime.jsxs("div", { className: "flex items-center space-x-3", children: [jsxRuntime.jsx("div", { className: cn("flex justify-center items-center w-5 h-5 rounded border-2", isSelected
|
|
35294
35335
|
? "bg-ews-primary border-ews-primary"
|
|
35295
35336
|
: "border-ews-gray-300"), children: isSelected && (jsxRuntime.jsx("svg", { className: "w-3 h-3 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) })) }), jsxRuntime.jsx("span", { className: cn("font-medium", isSelected ? "text-ews-primary" : "text-gray-900"), children: specialty.label })] })) }), showSelectedCount && selectedSpecialties.length > 0 && (jsxRuntime.jsxs("div", { className: "flex justify-between items-center text-sm text-gray-600", children: [jsxRuntime.jsx("span", { children: getSelectedCountText
|
|
35296
35337
|
? getSelectedCountText(selectedSpecialties.length)
|