@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.
@@ -17,7 +17,14 @@ interface SearchAutocompleteProps<T extends SearchableEntity> {
17
17
  minSearchLength?: number;
18
18
  debounceTime?: number;
19
19
  error?: string;
20
+ /**
21
+ * MED-1501: builds the hint appended to the placeholder while the search
22
+ * term is still too short. Return `null` (or an empty string) to suppress
23
+ * the hint entirely. Defaults to the English "(min. N characters)", so
24
+ * callers that do not pass it keep today's placeholder.
25
+ */
26
+ getMinCharactersHint?: (minSearchLength: number) => string | null;
20
27
  }
21
- export declare function SearchAutocomplete<T extends SearchableEntity>({ onSelect, selectedId, searchFunction, getEntityById, getDisplayValue, getSecondaryText, placeholder, icon, disabled, minSearchLength, debounceTime, error, }: SearchAutocompleteProps<T>): React.JSX.Element;
28
+ export declare function SearchAutocomplete<T extends SearchableEntity>({ onSelect, selectedId, searchFunction, getEntityById, getDisplayValue, getSecondaryText, placeholder, icon, disabled, minSearchLength, debounceTime, error, getMinCharactersHint, }: SearchAutocompleteProps<T>): React.JSX.Element;
22
29
  export {};
23
30
  //# sourceMappingURL=SearchAutocomplete.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SearchAutocomplete.d.ts","sourceRoot":"","sources":["../../../src/components/SearchAutocomplete/SearchAutocomplete.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,cAAc,CAAC;AACrD,OAAO,KAAsC,MAAM,OAAO,CAAC;AAK3D,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,UAAU,uBAAuB,CAAC,CAAC,SAAS,gBAAgB;IAC1D,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAClD,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACvD,eAAe,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACvC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,CAAC;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,gBAAgB,EAAE,EAC7D,QAAQ,EACR,UAAU,EACV,cAAc,EACd,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,IAAa,EACb,QAAgB,EAChB,eAAmB,EACnB,YAAkB,EAClB,KAAK,GACN,EAAE,uBAAuB,CAAC,CAAC,CAAC,qBAiR5B"}
1
+ {"version":3,"file":"SearchAutocomplete.d.ts","sourceRoot":"","sources":["../../../src/components/SearchAutocomplete/SearchAutocomplete.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,cAAc,CAAC;AACrD,OAAO,KAAsC,MAAM,OAAO,CAAC;AAK3D,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,UAAU,uBAAuB,CAAC,CAAC,SAAS,gBAAgB;IAC1D,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAClD,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACvD,eAAe,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACvC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,CAAC;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACnE;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,gBAAgB,EAAE,EAC7D,QAAQ,EACR,UAAU,EACV,cAAc,EACd,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,IAAa,EACb,QAAgB,EAChB,eAAmB,EACnB,YAAkB,EAClB,KAAK,EACL,oBAA0D,GAC3D,EAAE,uBAAuB,CAAC,CAAC,CAAC,qBAsR5B"}
@@ -92,6 +92,21 @@ export interface SelectProps<T = unknown> {
92
92
  * avatar menu).
93
93
  */
94
94
  renderTrigger?: () => React.ReactNode;
95
+ /**
96
+ * MED-1501: placeholder of the in-dropdown search input, so the caller can
97
+ * translate it. Defaults to the English "Search options...".
98
+ */
99
+ searchPlaceholder?: string;
100
+ /**
101
+ * MED-1501: text shown when a search matches no option, so the caller can
102
+ * translate it. Defaults to the English "No options found".
103
+ */
104
+ noOptionsFoundText?: string;
105
+ /**
106
+ * MED-1501: text shown when there is no option at all, so the caller can
107
+ * translate it. Defaults to the English "No options available".
108
+ */
109
+ noOptionsAvailableText?: string;
95
110
  }
96
111
  declare const Select: React.ForwardRefExoticComponent<SelectProps<unknown> & React.RefAttributes<HTMLDivElement>>;
97
112
  export { Select };
@@ -1 +1 @@
1
- {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../../src/components/Select/Select.tsx"],"names":[],"mappings":"AACA,OAAO,KAON,MAAM,OAAO,CAAC;AAIf,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC;;OAEG;IACH,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3B;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,CAAC;IACV;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IACvD;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;OAEG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,YAAY,CAAC,EAAE,CACb,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EACvB,UAAU,EAAE,OAAO,KAChB,KAAK,CAAC,SAAS,CAAC;IACrB;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,KAAK,CAAC,SAAS,CAAC;IAClE;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;CACvC;AAED,QAAA,MAAM,MAAM,6FAmcX,CAAC;AAIF,OAAO,EAAE,MAAM,EAAE,CAAC"}
1
+ {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../../src/components/Select/Select.tsx"],"names":[],"mappings":"AACA,OAAO,KAON,MAAM,OAAO,CAAC;AAIf,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,KAAK,EAAE,CAAC,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC;;OAEG;IACH,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3B;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,CAAC;IACV;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IACvD;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;OAEG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,YAAY,CAAC,EAAE,CACb,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EACvB,UAAU,EAAE,OAAO,KAChB,KAAK,CAAC,SAAS,CAAC;IACrB;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,KAAK,CAAC,SAAS,CAAC;IAClE;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACtC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,QAAA,MAAM,MAAM,6FAscX,CAAC;AAIF,OAAO,EAAE,MAAM,EAAE,CAAC"}
package/dist/index.d.ts CHANGED
@@ -201,6 +201,21 @@ interface SelectProps<T = unknown> {
201
201
  * avatar menu).
202
202
  */
203
203
  renderTrigger?: () => React__default.ReactNode;
204
+ /**
205
+ * MED-1501: placeholder of the in-dropdown search input, so the caller can
206
+ * translate it. Defaults to the English "Search options...".
207
+ */
208
+ searchPlaceholder?: string;
209
+ /**
210
+ * MED-1501: text shown when a search matches no option, so the caller can
211
+ * translate it. Defaults to the English "No options found".
212
+ */
213
+ noOptionsFoundText?: string;
214
+ /**
215
+ * MED-1501: text shown when there is no option at all, so the caller can
216
+ * translate it. Defaults to the English "No options available".
217
+ */
218
+ noOptionsAvailableText?: string;
204
219
  }
205
220
  declare const Select: React__default.ForwardRefExoticComponent<SelectProps<unknown> & React__default.RefAttributes<HTMLDivElement>>;
206
221
 
@@ -221,8 +236,15 @@ interface SearchAutocompleteProps<T extends SearchableEntity> {
221
236
  minSearchLength?: number;
222
237
  debounceTime?: number;
223
238
  error?: string;
239
+ /**
240
+ * MED-1501: builds the hint appended to the placeholder while the search
241
+ * term is still too short. Return `null` (or an empty string) to suppress
242
+ * the hint entirely. Defaults to the English "(min. N characters)", so
243
+ * callers that do not pass it keep today's placeholder.
244
+ */
245
+ getMinCharactersHint?: (minSearchLength: number) => string | null;
224
246
  }
225
- declare function SearchAutocomplete<T extends SearchableEntity>({ onSelect, selectedId, searchFunction, getEntityById, getDisplayValue, getSecondaryText, placeholder, icon, disabled, minSearchLength, debounceTime, error, }: SearchAutocompleteProps<T>): React__default.JSX.Element;
247
+ declare function SearchAutocomplete<T extends SearchableEntity>({ onSelect, selectedId, searchFunction, getEntityById, getDisplayValue, getSecondaryText, placeholder, icon, disabled, minSearchLength, debounceTime, error, getMinCharactersHint, }: SearchAutocompleteProps<T>): React__default.JSX.Element;
226
248
 
227
249
  interface MultiSearchAutocompleteProps<T extends SearchableEntity> {
228
250
  items: T[];
@@ -585,6 +607,12 @@ interface SpecialtySearchAutocompleteProps {
585
607
  title?: string;
586
608
  label?: string;
587
609
  getSelectedCountText?: (count: number) => React__default.ReactNode;
610
+ /**
611
+ * MED-1499: accessible label / tooltip of the button that removes a
612
+ * selected specialty from its chip. Defaults to the English
613
+ * "Remove specialty".
614
+ */
615
+ removeSpecialtyLabel?: string;
588
616
  }
589
617
  declare const SpecialtySearchAutocomplete: React__default.FC<SpecialtySearchAutocompleteProps>;
590
618
 
@@ -651,6 +679,11 @@ interface EnvConfig {
651
679
  * For local development, sensible defaults are provided but can be overridden
652
680
  * (e.g. to point BFF URL at a mock server).
653
681
  *
682
+ * When env is empty (build without VITE_ENV), the environment is derived from
683
+ * the current host instead of always assuming local, so a deployed build never
684
+ * links to http://local.*.medecine360local.com. A console warning names the
685
+ * derived environment so the misconfigured build stays visible.
686
+ *
654
687
  * @param env - The environment name (VITE_ENV value)
655
688
  * @param overrides - Optional URL overrides (e.g. for local mock servers)
656
689
  */
package/dist/index.esm.js CHANGED
@@ -38,6 +38,29 @@ function buildUrls(environment) {
38
38
  adminUrl: `https://${prefix}admin.${domain}`,
39
39
  };
40
40
  }
41
+ /**
42
+ * Derives the environment from the host the app is served from, for builds
43
+ * that did not set VITE_ENV. Hosts actually served (Traefik rules in
44
+ * infrastructure/medecine360-hetzner/deployment/docker-stack.*.yml):
45
+ * - local: localhost, 127.0.0.1, *.medecine360local.com (local.app., api., ...)
46
+ * - dev: dev.{app,login,dashboard,admin,api}.medecine360.com
47
+ * - qa: qa.{app,login,dashboard,admin,api}.medecine360.com
48
+ * - prod: medecine360.com, {login,dashboard,admin,api}.medecine360.com
49
+ * Any other host (or no window, e.g. SSR or tests) falls back to local.
50
+ */
51
+ function environmentFromHost() {
52
+ const hostname = typeof window !== "undefined" ? window.location?.hostname?.toLowerCase() : undefined;
53
+ if (!hostname)
54
+ return "local";
55
+ if (hostname === "medecine360.com" || hostname.endsWith(".medecine360.com")) {
56
+ if (hostname.startsWith("dev."))
57
+ return "dev";
58
+ if (hostname.startsWith("qa."))
59
+ return "qa";
60
+ return "prod";
61
+ }
62
+ return "local";
63
+ }
41
64
  /**
42
65
  * Creates an environment configuration with all derived URLs.
43
66
  *
@@ -47,11 +70,20 @@ function buildUrls(environment) {
47
70
  * For local development, sensible defaults are provided but can be overridden
48
71
  * (e.g. to point BFF URL at a mock server).
49
72
  *
73
+ * When env is empty (build without VITE_ENV), the environment is derived from
74
+ * the current host instead of always assuming local, so a deployed build never
75
+ * links to http://local.*.medecine360local.com. A console warning names the
76
+ * derived environment so the misconfigured build stays visible.
77
+ *
50
78
  * @param env - The environment name (VITE_ENV value)
51
79
  * @param overrides - Optional URL overrides (e.g. for local mock servers)
52
80
  */
53
81
  function createEnvConfig(env, overrides) {
54
- const environment = (env || "local");
82
+ let environment = env;
83
+ if (!environment) {
84
+ environment = environmentFromHost();
85
+ console.warn(`[env-config] VITE_ENV is not set; using "${environment}" derived from the current host.`);
86
+ }
55
87
  const derived = buildUrls(environment);
56
88
  return {
57
89
  env: environment,
@@ -32766,7 +32798,7 @@ const Input = React__default.forwardRef(({ className, variant = "default", size
32766
32798
  });
32767
32799
  Input.displayName = "Input";
32768
32800
 
32769
- const Select = 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) => {
32801
+ const Select = 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) => {
32770
32802
  const generatedId = useId();
32771
32803
  const selectId = `select-${generatedId}`;
32772
32804
  const hasError = isError || !!error;
@@ -32975,7 +33007,7 @@ const Select = forwardRef(({ options = [], value, onChange, placeholder = "Selec
32975
33007
  // Padding for icons
32976
33008
  "pr-10", selectClassName), ...props, children: jsx("div", { className: "flex justify-between items-center", children: renderTrigger ? (renderTrigger()) : (jsxs(Fragment, { children: [jsx("div", { className: "flex-1 min-w-0", children: selectedOption ? (renderValue ? (renderValue(selectedOption)) : (jsx("span", { className: "truncate", children: selectedOption.label }))) : (jsx("span", { className: "text-ews-gray-500", children: placeholder })) }), jsxs("div", { className: "flex items-center ml-2 space-x-1", children: [clearable && selectedOption && !disabled && (jsx("button", { type: "button", onClick: handleClear, className: "p-1 rounded hover:bg-ews-gray-100", children: jsx(X, { className: cn(iconSizeClasses[size], "text-ews-gray-400") }) })), 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 && (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"
32977
33009
  ? "bottom-full mb-1"
32978
- : "top-full mt-1", dropdownClassName), style: { maxHeight: `${maxHeight}px` }, children: [searchable && (jsx("div", { className: "p-2 border-b border-ews-gray-200", children: jsxs("div", { className: "relative", children: [jsx(Search, { className: "absolute left-3 top-1/2 w-4 h-4 transform -translate-y-1/2 text-ews-gray-400" }), jsx(Input, { ref: inputRef, type: "text", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), placeholder: "Search options...", leftIcon: jsx(Search, { className: "w-4 h-4 text-ews-gray-400" }) })] }) })), jsx("div", { className: "overflow-auto py-1", style: { maxHeight: `${maxHeight - (searchable ? 60 : 0)}px` }, children: filteredOptions.length === 0 ? (jsx("div", { className: "px-3 py-2 text-sm text-ews-gray-500", children: searchTerm ? "No options found" : "No options available" })) : (filteredOptions.map((option, index) => {
33010
+ : "top-full mt-1", dropdownClassName), style: { maxHeight: `${maxHeight}px` }, children: [searchable && (jsx("div", { className: "p-2 border-b border-ews-gray-200", children: jsxs("div", { className: "relative", children: [jsx(Search, { className: "absolute left-3 top-1/2 w-4 h-4 transform -translate-y-1/2 text-ews-gray-400" }), jsx(Input, { ref: inputRef, type: "text", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), placeholder: searchPlaceholder, leftIcon: jsx(Search, { className: "w-4 h-4 text-ews-gray-400" }) })] }) })), jsx("div", { className: "overflow-auto py-1", style: { maxHeight: `${maxHeight - (searchable ? 60 : 0)}px` }, children: filteredOptions.length === 0 ? (jsx("div", { className: "px-3 py-2 text-sm text-ews-gray-500", children: searchTerm ? noOptionsFoundText : noOptionsAvailableText })) : (filteredOptions.map((option, index) => {
32979
33011
  const isSelected = option.value === value;
32980
33012
  const isFocused = index === focusedIndex;
32981
33013
  return (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)) : (jsx("span", { className: "truncate", children: option.label })) }, `${String(option.value)}-${index}`));
@@ -33052,7 +33084,7 @@ function useSelectField({ name, control, options: _options, rules, defaultValue,
33052
33084
  };
33053
33085
  }
33054
33086
 
33055
- function SearchAutocomplete({ onSelect, selectedId, searchFunction, getEntityById, getDisplayValue, getSecondaryText, placeholder, icon = Search, disabled = false, minSearchLength = 2, debounceTime = 300, error, }) {
33087
+ function SearchAutocomplete({ onSelect, selectedId, searchFunction, getEntityById, getDisplayValue, getSecondaryText, placeholder, icon = Search, disabled = false, minSearchLength = 2, debounceTime = 300, error, getMinCharactersHint = (min) => `(min. ${min} characters)`, }) {
33056
33088
  const [searchTerm, setSearchTerm] = useState("");
33057
33089
  const [entities, setEntities] = useState([]);
33058
33090
  const [isLoading, setIsLoading] = useState(false);
@@ -33165,7 +33197,11 @@ function SearchAutocomplete({ onSelect, selectedId, searchFunction, getEntityByI
33165
33197
  setSearchTerm("");
33166
33198
  onSelect("");
33167
33199
  };
33168
- return (jsxs("div", { className: "relative", ref: dropdownRef, children: [jsxs("div", { className: "relative", children: [jsx(Input, { type: "text", placeholder: `${placeholder} (min. ${minSearchLength} characters)`, value: selectedEntity ? "" : searchTerm, onChange: handleSearchChange, onFocus: () => searchTerm.length >= minSearchLength &&
33200
+ const minCharactersHint = getMinCharactersHint(minSearchLength);
33201
+ const inputPlaceholder = minCharactersHint
33202
+ ? `${placeholder} ${minCharactersHint}`
33203
+ : placeholder;
33204
+ return (jsxs("div", { className: "relative", ref: dropdownRef, children: [jsxs("div", { className: "relative", children: [jsx(Input, { type: "text", placeholder: inputPlaceholder, value: selectedEntity ? "" : searchTerm, onChange: handleSearchChange, onFocus: () => searchTerm.length >= minSearchLength &&
33169
33205
  isUserSearch.current &&
33170
33206
  setShowDropdown(true), disabled: disabled, autoComplete: "off", name: "search-autocomplete", leftIcon: React__default.createElement(icon, { size: 16 }) }), selectedEntity && (jsx("button", { type: "button", className: "flex absolute inset-y-0 right-0 items-center pr-3", onClick: handleClearSelection, disabled: disabled, children: jsx(X, { className: "w-5 h-5 text-gray-400 hover:text-gray-500" }) }))] }), selectedEntity && (jsx("div", { className: "p-4 mt-3 rounded-lg border bg-neutral-50 border-neutral-200", children: jsxs("div", { className: "flex justify-between items-center", children: [jsxs("div", { className: "flex items-center", children: [selectedEntity.photoUrl ? (jsx("img", { src: selectedEntity.photoUrl, alt: getDisplayValue(selectedEntity), className: "object-cover mr-3 w-10 h-10 rounded-full" })) : (React__default.createElement(icon, {
33171
33207
  className: "w-10 h-10 text-neutral-400 mr-3",
@@ -35236,7 +35272,7 @@ const PaymentDueBanner = ({ title, subtitle, warning, ctaLabel, onCtaClick, onDi
35236
35272
  */
35237
35273
  const SecureLoader = ({ label, description }) => (jsx("div", { "data-testid": "secure-loader", className: "flex min-h-screen items-center justify-center bg-gray-100", children: jsxs("div", { className: "flex flex-col items-center rounded-lg bg-white p-6 shadow-lg", children: [jsxs("div", { className: "relative", children: [jsx(Lock, { className: "h-16 w-16 text-gray-500" }), 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" })] }), jsx("p", { className: "mt-4 text-lg text-gray-600", children: label }), description && (jsx("p", { className: "mt-2 text-sm text-gray-500", children: description }))] }) }));
35238
35274
 
35239
- 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, }) => {
35275
+ 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", }) => {
35240
35276
  const [filteredSpecialties, setFilteredSpecialties] = useState([]);
35241
35277
  const [isLoading, setIsLoading] = useState(false);
35242
35278
  // Filter specialties based on search term
@@ -35267,10 +35303,15 @@ const SpecialtySearchAutocomplete = ({ selectedSpecialties = [], onSpecialtiesCh
35267
35303
  }
35268
35304
  onSpecialtiesChange(items);
35269
35305
  }, [onSpecialtiesChange, maxSelections]);
35306
+ // MED-1499: a selected specialty must be removable from its chip, not only
35307
+ // by toggling it off again in the dropdown list.
35308
+ const handleRemoveSpecialty = useCallback((specialtyToRemove) => {
35309
+ onSpecialtiesChange(selectedSpecialties.filter((specialty) => specialty.id !== specialtyToRemove.id));
35310
+ }, [onSpecialtiesChange, selectedSpecialties]);
35270
35311
  const getEntityById = useCallback(async (id) => {
35271
35312
  return availableSpecialties.find((specialty) => specialty.id === id);
35272
35313
  }, [availableSpecialties]);
35273
- return (jsxs("div", { className: cn("space-y-3", className), children: [jsxs("div", { className: "flex items-center space-x-2", children: [jsx("div", { className: "flex justify-center items-center w-8 h-8 rounded-lg bg-secondary-100", children: jsx(Stethoscope, { className: "w-4 h-4 text-secondary-600" }) }), jsx("h3", { className: "text-lg font-semibold text-gray-900", children: title || "Medical Specialties" })] }), jsx("div", { children: jsx("label", { className: "block mb-2 text-sm font-medium text-gray-700", children: label || "Select Specialties" }) }), 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) => (jsx("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 })), renderListItem: (specialty, isSelected) => (jsxs("div", { className: "flex items-center space-x-3", children: [jsx("div", { className: cn("flex justify-center items-center w-5 h-5 rounded border-2", isSelected
35314
+ return (jsxs("div", { className: cn("space-y-3", className), children: [jsxs("div", { className: "flex items-center space-x-2", children: [jsx("div", { className: "flex justify-center items-center w-8 h-8 rounded-lg bg-secondary-100", children: jsx(Stethoscope, { className: "w-4 h-4 text-secondary-600" }) }), jsx("h3", { className: "text-lg font-semibold text-gray-900", children: title || "Medical Specialties" })] }), jsx("div", { children: jsx("label", { className: "block mb-2 text-sm font-medium text-gray-700", children: label || "Select Specialties" }) }), 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) => (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, 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: jsx(X, { className: "w-3 h-3" }) })] })), renderListItem: (specialty, isSelected) => (jsxs("div", { className: "flex items-center space-x-3", children: [jsx("div", { className: cn("flex justify-center items-center w-5 h-5 rounded border-2", isSelected
35274
35315
  ? "bg-ews-primary border-ews-primary"
35275
35316
  : "border-ews-gray-300"), children: isSelected && (jsx("svg", { className: "w-3 h-3 text-white", fill: "currentColor", viewBox: "0 0 20 20", children: 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" }) })) }), jsx("span", { className: cn("font-medium", isSelected ? "text-ews-primary" : "text-gray-900"), children: specialty.label })] })) }), showSelectedCount && selectedSpecialties.length > 0 && (jsxs("div", { className: "flex justify-between items-center text-sm text-gray-600", children: [jsx("span", { children: getSelectedCountText
35276
35317
  ? getSelectedCountText(selectedSpecialties.length)