@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,6 +17,12 @@ export interface SpecialtySearchAutocompleteProps {
17
17
  title?: string;
18
18
  label?: string;
19
19
  getSelectedCountText?: (count: number) => React.ReactNode;
20
+ /**
21
+ * MED-1499: accessible label / tooltip of the button that removes a
22
+ * selected specialty from its chip. Defaults to the English
23
+ * "Remove specialty".
24
+ */
25
+ removeSpecialtyLabel?: string;
20
26
  }
21
27
  export declare const SpecialtySearchAutocomplete: React.FC<SpecialtySearchAutocompleteProps>;
22
28
  //# sourceMappingURL=SpecialtySearchAutocomplete.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpecialtySearchAutocomplete.d.ts","sourceRoot":"","sources":["../../../src/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.tsx"],"names":[],"mappings":"AACA,OAAO,KAA2C,MAAM,OAAO,CAAC;AAIhE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;CACtD;AAED,MAAM,WAAW,gCAAgC;IAC/C,mBAAmB,EAAE,SAAS,EAAE,CAAC;IACjC,mBAAmB,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,KAAK,IAAI,CAAC;IACxD,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CAC3D;AAED,eAAO,MAAM,2BAA2B,EAAE,KAAK,CAAC,EAAE,CAChD,gCAAgC,CA+JjC,CAAC"}
1
+ {"version":3,"file":"SpecialtySearchAutocomplete.d.ts","sourceRoot":"","sources":["../../../src/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.tsx"],"names":[],"mappings":"AACA,OAAO,KAA2C,MAAM,OAAO,CAAC;AAIhE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;CACtD;AAED,MAAM,WAAW,gCAAgC;IAC/C,mBAAmB,EAAE,SAAS,EAAE,CAAC;IACjC,mBAAmB,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,KAAK,IAAI,CAAC;IACxD,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IAC1D;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,eAAO,MAAM,2BAA2B,EAAE,KAAK,CAAC,EAAE,CAChD,gCAAgC,CAuLjC,CAAC"}
@@ -25,6 +25,11 @@ export interface EnvConfig {
25
25
  * For local development, sensible defaults are provided but can be overridden
26
26
  * (e.g. to point BFF URL at a mock server).
27
27
  *
28
+ * When env is empty (build without VITE_ENV), the environment is derived from
29
+ * the current host instead of always assuming local, so a deployed build never
30
+ * links to http://local.*.medecine360local.com. A console warning names the
31
+ * derived environment so the misconfigured build stays visible.
32
+ *
28
33
  * @param env - The environment name (VITE_ENV value)
29
34
  * @param overrides - Optional URL overrides (e.g. for local mock servers)
30
35
  */
@@ -1 +1 @@
1
- {"version":3,"file":"env-config.d.ts","sourceRoot":"","sources":["../../src/utils/env-config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,KAAK,GAAG,IAAI,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,WAAW,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAuCD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,kBAAkB,GAC7B,SAAS,CASX"}
1
+ {"version":3,"file":"env-config.d.ts","sourceRoot":"","sources":["../../src/utils/env-config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,KAAK,GAAG,IAAI,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,WAAW,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AA6DD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,kBAAkB,GAC7B,SAAS,CAeX"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ews-admin/global-design-system",
3
- "version": "1.27.0",
3
+ "version": "1.29.0",
4
4
  "description": "EWS Global Design System - Reusable components for EWS applications",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -41,6 +41,7 @@
41
41
  "@storybook/react": "^7.5.0",
42
42
  "@storybook/react-vite": "^7.5.0",
43
43
  "@storybook/testing-library": "^0.2.2",
44
+ "@testing-library/react": "14",
44
45
  "@types/react": "^18.2.37",
45
46
  "@types/react-dom": "^18.2.15",
46
47
  "@typescript-eslint/eslint-plugin": "^8.44.0",
@@ -51,6 +52,7 @@
51
52
  "eslint-plugin-react": "^7.37.5",
52
53
  "eslint-plugin-react-hooks": "^5.2.0",
53
54
  "jest": "29",
55
+ "jest-environment-jsdom": "29",
54
56
  "postcss": "^8.5.6",
55
57
  "rollup": "^4.6.1",
56
58
  "rollup-plugin-dts": "^6.1.0",
@@ -22,6 +22,13 @@ interface SearchAutocompleteProps<T extends SearchableEntity> {
22
22
  minSearchLength?: number;
23
23
  debounceTime?: number;
24
24
  error?: string;
25
+ /**
26
+ * MED-1501: builds the hint appended to the placeholder while the search
27
+ * term is still too short. Return `null` (or an empty string) to suppress
28
+ * the hint entirely. Defaults to the English "(min. N characters)", so
29
+ * callers that do not pass it keep today's placeholder.
30
+ */
31
+ getMinCharactersHint?: (minSearchLength: number) => string | null;
25
32
  }
26
33
 
27
34
  export function SearchAutocomplete<T extends SearchableEntity>({
@@ -37,6 +44,7 @@ export function SearchAutocomplete<T extends SearchableEntity>({
37
44
  minSearchLength = 2,
38
45
  debounceTime = 300,
39
46
  error,
47
+ getMinCharactersHint = (min) => `(min. ${min} characters)`,
40
48
  }: SearchAutocompleteProps<T>) {
41
49
  const [searchTerm, setSearchTerm] = useState("");
42
50
  const [entities, setEntities] = useState<T[]>([]);
@@ -171,12 +179,17 @@ export function SearchAutocomplete<T extends SearchableEntity>({
171
179
  onSelect("");
172
180
  };
173
181
 
182
+ const minCharactersHint = getMinCharactersHint(minSearchLength);
183
+ const inputPlaceholder = minCharactersHint
184
+ ? `${placeholder} ${minCharactersHint}`
185
+ : placeholder;
186
+
174
187
  return (
175
188
  <div className="relative" ref={dropdownRef}>
176
189
  <div className="relative">
177
190
  <Input
178
191
  type="text"
179
- placeholder={`${placeholder} (min. ${minSearchLength} characters)`}
192
+ placeholder={inputPlaceholder}
180
193
  value={selectedEntity ? "" : searchTerm}
181
194
  onChange={handleSearchChange}
182
195
  onFocus={() =>
@@ -107,6 +107,21 @@ export interface SelectProps<T = unknown> {
107
107
  * avatar menu).
108
108
  */
109
109
  renderTrigger?: () => React.ReactNode;
110
+ /**
111
+ * MED-1501: placeholder of the in-dropdown search input, so the caller can
112
+ * translate it. Defaults to the English "Search options...".
113
+ */
114
+ searchPlaceholder?: string;
115
+ /**
116
+ * MED-1501: text shown when a search matches no option, so the caller can
117
+ * translate it. Defaults to the English "No options found".
118
+ */
119
+ noOptionsFoundText?: string;
120
+ /**
121
+ * MED-1501: text shown when there is no option at all, so the caller can
122
+ * translate it. Defaults to the English "No options available".
123
+ */
124
+ noOptionsAvailableText?: string;
110
125
  }
111
126
 
112
127
  const Select = forwardRef<HTMLDivElement, SelectProps>(
@@ -133,6 +148,9 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(
133
148
  renderOption,
134
149
  renderValue,
135
150
  renderTrigger,
151
+ searchPlaceholder = "Search options...",
152
+ noOptionsFoundText = "No options found",
153
+ noOptionsAvailableText = "No options available",
136
154
  ...props
137
155
  },
138
156
  ref
@@ -495,7 +513,7 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(
495
513
  type="text"
496
514
  value={searchTerm}
497
515
  onChange={(e) => setSearchTerm(e.target.value)}
498
- placeholder="Search options..."
516
+ placeholder={searchPlaceholder}
499
517
  leftIcon={
500
518
  <Search className="w-4 h-4 text-ews-gray-400" />
501
519
  }
@@ -511,7 +529,7 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(
511
529
  >
512
530
  {filteredOptions.length === 0 ? (
513
531
  <div className="px-3 py-2 text-sm text-ews-gray-500">
514
- {searchTerm ? "No options found" : "No options available"}
532
+ {searchTerm ? noOptionsFoundText : noOptionsAvailableText}
515
533
  </div>
516
534
  ) : (
517
535
  filteredOptions.map((option, index) => {
@@ -1,4 +1,4 @@
1
- import { Stethoscope } from "lucide-react";
1
+ import { Stethoscope, X } from "lucide-react";
2
2
  import React, { useCallback, useEffect, useState } from "react";
3
3
  import { MultiSearchAutocomplete } from "../../components/MultiSearchAutocomplete";
4
4
  import { cn } from "../../utils";
@@ -22,6 +22,12 @@ export interface SpecialtySearchAutocompleteProps {
22
22
  title?: string;
23
23
  label?: string;
24
24
  getSelectedCountText?: (count: number) => React.ReactNode;
25
+ /**
26
+ * MED-1499: accessible label / tooltip of the button that removes a
27
+ * selected specialty from its chip. Defaults to the English
28
+ * "Remove specialty".
29
+ */
30
+ removeSpecialtyLabel?: string;
25
31
  }
26
32
 
27
33
  export const SpecialtySearchAutocomplete: React.FC<
@@ -38,6 +44,7 @@ export const SpecialtySearchAutocomplete: React.FC<
38
44
  title = "Medical Specialties",
39
45
  label = "Select Specialties",
40
46
  getSelectedCountText,
47
+ removeSpecialtyLabel = "Remove specialty",
41
48
  }) => {
42
49
  const [filteredSpecialties, setFilteredSpecialties] = useState<Specialty[]>(
43
50
  []
@@ -83,6 +90,19 @@ export const SpecialtySearchAutocomplete: React.FC<
83
90
  [onSpecialtiesChange, maxSelections]
84
91
  );
85
92
 
93
+ // MED-1499: a selected specialty must be removable from its chip, not only
94
+ // by toggling it off again in the dropdown list.
95
+ const handleRemoveSpecialty = useCallback(
96
+ (specialtyToRemove: Specialty) => {
97
+ onSpecialtiesChange(
98
+ selectedSpecialties.filter(
99
+ (specialty) => specialty.id !== specialtyToRemove.id
100
+ )
101
+ );
102
+ },
103
+ [onSpecialtiesChange, selectedSpecialties]
104
+ );
105
+
86
106
  const getEntityById = useCallback(
87
107
  async (id: string): Promise<Specialty | undefined> => {
88
108
  return availableSpecialties.find((specialty) => specialty.id === id);
@@ -127,6 +147,16 @@ export const SpecialtySearchAutocomplete: React.FC<
127
147
  renderSelectedItem={(specialty) => (
128
148
  <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">
129
149
  {specialty.label}
150
+ <button
151
+ type="button"
152
+ onClick={() => handleRemoveSpecialty(specialty)}
153
+ className="ml-2 text-ews-primary/60 hover:text-ews-primary"
154
+ disabled={disabled}
155
+ title={removeSpecialtyLabel}
156
+ aria-label={removeSpecialtyLabel}
157
+ >
158
+ <X className="w-3 h-3" />
159
+ </button>
130
160
  </span>
131
161
  )}
132
162
  renderListItem={(specialty, isSelected) => (
@@ -56,6 +56,28 @@ function buildUrls(environment: Environment): Omit<EnvConfig, "env"> {
56
56
  };
57
57
  }
58
58
 
59
+ /**
60
+ * Derives the environment from the host the app is served from, for builds
61
+ * that did not set VITE_ENV. Hosts actually served (Traefik rules in
62
+ * infrastructure/medecine360-hetzner/deployment/docker-stack.*.yml):
63
+ * - local: localhost, 127.0.0.1, *.medecine360local.com (local.app., api., ...)
64
+ * - dev: dev.{app,login,dashboard,admin,api}.medecine360.com
65
+ * - qa: qa.{app,login,dashboard,admin,api}.medecine360.com
66
+ * - prod: medecine360.com, {login,dashboard,admin,api}.medecine360.com
67
+ * Any other host (or no window, e.g. SSR or tests) falls back to local.
68
+ */
69
+ function environmentFromHost(): Environment {
70
+ const hostname =
71
+ typeof window !== "undefined" ? window.location?.hostname?.toLowerCase() : undefined;
72
+ if (!hostname) return "local";
73
+ if (hostname === "medecine360.com" || hostname.endsWith(".medecine360.com")) {
74
+ if (hostname.startsWith("dev.")) return "dev";
75
+ if (hostname.startsWith("qa.")) return "qa";
76
+ return "prod";
77
+ }
78
+ return "local";
79
+ }
80
+
59
81
  /**
60
82
  * Creates an environment configuration with all derived URLs.
61
83
  *
@@ -65,6 +87,11 @@ function buildUrls(environment: Environment): Omit<EnvConfig, "env"> {
65
87
  * For local development, sensible defaults are provided but can be overridden
66
88
  * (e.g. to point BFF URL at a mock server).
67
89
  *
90
+ * When env is empty (build without VITE_ENV), the environment is derived from
91
+ * the current host instead of always assuming local, so a deployed build never
92
+ * links to http://local.*.medecine360local.com. A console warning names the
93
+ * derived environment so the misconfigured build stays visible.
94
+ *
68
95
  * @param env - The environment name (VITE_ENV value)
69
96
  * @param overrides - Optional URL overrides (e.g. for local mock servers)
70
97
  */
@@ -72,7 +99,13 @@ export function createEnvConfig(
72
99
  env: string,
73
100
  overrides?: EnvConfigOverrides,
74
101
  ): EnvConfig {
75
- const environment = (env || "local") as Environment;
102
+ let environment = env as Environment;
103
+ if (!environment) {
104
+ environment = environmentFromHost();
105
+ console.warn(
106
+ `[env-config] VITE_ENV is not set; using "${environment}" derived from the current host.`,
107
+ );
108
+ }
76
109
  const derived = buildUrls(environment);
77
110
 
78
111
  return {