@ews-admin/global-design-system 1.26.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.
- package/dist/components/SearchAutocomplete/SearchAutocomplete.d.ts +8 -1
- package/dist/components/SearchAutocomplete/SearchAutocomplete.d.ts.map +1 -1
- package/dist/components/SecureLoader/SecureLoader.d.ts +18 -0
- package/dist/components/SecureLoader/SecureLoader.d.ts.map +1 -0
- package/dist/components/SecureLoader/index.d.ts +3 -0
- package/dist/components/SecureLoader/index.d.ts.map +1 -0
- package/dist/components/Select/Select.d.ts +15 -0
- package/dist/components/Select/Select.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +47 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +22 -7
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +22 -6
- 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/package.json +3 -1
- package/src/components/SearchAutocomplete/SearchAutocomplete.tsx +14 -1
- package/src/components/SecureLoader/SecureLoader.tsx +37 -0
- package/src/components/SecureLoader/index.ts +2 -0
- package/src/components/Select/Select.tsx +20 -2
- package/src/index.ts +3 -0
- package/src/molecules/SpecialtySearchAutocomplete/SpecialtySearchAutocomplete.tsx +31 -1
|
@@ -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;
|
|
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.
|
|
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={
|
|
192
|
+
placeholder={inputPlaceholder}
|
|
180
193
|
value={selectedEntity ? "" : searchTerm}
|
|
181
194
|
onChange={handleSearchChange}
|
|
182
195
|
onFocus={() =>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Lock } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The loader is purely presentational: the caller decides when to render it
|
|
6
|
+
* (while the session is validated, fetched or terminated) and provides
|
|
7
|
+
* already-translated labels.
|
|
8
|
+
*/
|
|
9
|
+
export interface SecureLoaderProps {
|
|
10
|
+
label: string;
|
|
11
|
+
/** Extra line shown under the label. */
|
|
12
|
+
description?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Full-screen loader (lock icon with a spinner) shown while the user's
|
|
17
|
+
* session is being checked.
|
|
18
|
+
*/
|
|
19
|
+
const SecureLoader: React.FC<SecureLoaderProps> = ({ label, description }) => (
|
|
20
|
+
<div
|
|
21
|
+
data-testid="secure-loader"
|
|
22
|
+
className="flex min-h-screen items-center justify-center bg-gray-100"
|
|
23
|
+
>
|
|
24
|
+
<div className="flex flex-col items-center rounded-lg bg-white p-6 shadow-lg">
|
|
25
|
+
<div className="relative">
|
|
26
|
+
<Lock className="h-16 w-16 text-gray-500" />
|
|
27
|
+
<div className="absolute bottom-2 right-1 h-6 w-6 animate-spin rounded-full border-4 border-gray-300 border-t-green-500"></div>
|
|
28
|
+
</div>
|
|
29
|
+
<p className="mt-4 text-lg text-gray-600">{label}</p>
|
|
30
|
+
{description && (
|
|
31
|
+
<p className="mt-2 text-sm text-gray-500">{description}</p>
|
|
32
|
+
)}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export default SecureLoader;
|
|
@@ -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=
|
|
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 ?
|
|
532
|
+
{searchTerm ? noOptionsFoundText : noOptionsAvailableText}
|
|
515
533
|
</div>
|
|
516
534
|
) : (
|
|
517
535
|
filteredOptions.map((option, index) => {
|
package/src/index.ts
CHANGED
|
@@ -52,6 +52,9 @@ export type { TagListProps } from "./components/TagList";
|
|
|
52
52
|
export { PaymentDueBanner } from "./components/PaymentDueBanner";
|
|
53
53
|
export type { PaymentDueBannerProps } from "./components/PaymentDueBanner";
|
|
54
54
|
|
|
55
|
+
export { SecureLoader } from "./components/SecureLoader";
|
|
56
|
+
export type { SecureLoaderProps } from "./components/SecureLoader";
|
|
57
|
+
|
|
55
58
|
// Molecules
|
|
56
59
|
export { SpecialtySearchAutocomplete } from "./molecules";
|
|
57
60
|
export type { Specialty, SpecialtySearchAutocompleteProps } from "./molecules";
|
|
@@ -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) => (
|