@ews-admin/global-design-system 1.27.0 → 1.28.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"}
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.28.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) => (