@keycloakify/keycloak-ui-shared 260200.0.0 → 260305.0.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/keycloak-theme/shared/keycloak-ui-shared/context/ErrorPage.tsx +7 -10
- package/keycloak-theme/shared/keycloak-ui-shared/context/KeycloakContext.tsx +3 -9
- package/keycloak-theme/shared/keycloak-ui-shared/controls/FileUploadControl.tsx +85 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/FormLabel.tsx +4 -2
- package/keycloak-theme/shared/keycloak-ui-shared/controls/SwitchControl.tsx +1 -1
- package/keycloak-theme/shared/keycloak-ui-shared/controls/select-control/SelectControl.tsx +1 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/select-control/SingleSelectControl.tsx +4 -2
- package/keycloak-theme/shared/keycloak-ui-shared/controls/select-control/TypeaheadSelectControl.tsx +65 -35
- package/keycloak-theme/shared/keycloak-ui-shared/controls/table/KeycloakDataTable.tsx +16 -15
- package/keycloak-theme/shared/keycloak-ui-shared/controls/table/ListEmptyState.tsx +9 -2
- package/keycloak-theme/shared/keycloak-ui-shared/main.ts +4 -0
- package/keycloak-theme/shared/keycloak-ui-shared/scroll-form/ScrollForm.tsx +9 -2
- package/keycloak-theme/shared/keycloak-ui-shared/user-profile/TextComponent.tsx +1 -1
- package/keycloak-theme/shared/keycloak-ui-shared/user-profile/utils.ts +9 -3
- package/package.json +5 -5
|
@@ -11,18 +11,17 @@ import {
|
|
|
11
11
|
TextContent,
|
|
12
12
|
} from "../../@patternfly/react-core";
|
|
13
13
|
import { useTranslation } from "react-i18next";
|
|
14
|
-
import {
|
|
14
|
+
import { getNetworkErrorMessage } from "../utils/errors";
|
|
15
15
|
|
|
16
16
|
type ErrorPageProps = {
|
|
17
17
|
error?: unknown;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
export const ErrorPage = (props: ErrorPageProps) => {
|
|
21
|
-
const { t } = useTranslation();
|
|
21
|
+
const { t, i18n } = useTranslation();
|
|
22
22
|
const error = props.error;
|
|
23
|
-
const errorMessage =
|
|
24
|
-
|
|
25
|
-
getNetworkErrorDescription(error)?.replace(/\+/g, " ");
|
|
23
|
+
const errorMessage = getErrorMessage(error);
|
|
24
|
+
const networkErrorMessage = getNetworkErrorMessage(error);
|
|
26
25
|
console.error(error);
|
|
27
26
|
|
|
28
27
|
function onRetry() {
|
|
@@ -33,7 +32,7 @@ export const ErrorPage = (props: ErrorPageProps) => {
|
|
|
33
32
|
<Page>
|
|
34
33
|
<Modal
|
|
35
34
|
variant={ModalVariant.small}
|
|
36
|
-
title={
|
|
35
|
+
title={t("somethingWentWrong")}
|
|
37
36
|
titleIconVariant="danger"
|
|
38
37
|
showClose={false}
|
|
39
38
|
isOpen
|
|
@@ -46,6 +45,8 @@ export const ErrorPage = (props: ErrorPageProps) => {
|
|
|
46
45
|
<TextContent>
|
|
47
46
|
{errorMessage ? (
|
|
48
47
|
<Text>{t(errorMessage)}</Text>
|
|
48
|
+
) : networkErrorMessage && i18n.exists(networkErrorMessage) ? (
|
|
49
|
+
<Text>{t(networkErrorMessage)}</Text>
|
|
49
50
|
) : (
|
|
50
51
|
<Text>{t("somethingWentWrongDescription")}</Text>
|
|
51
52
|
)}
|
|
@@ -56,10 +57,6 @@ export const ErrorPage = (props: ErrorPageProps) => {
|
|
|
56
57
|
};
|
|
57
58
|
|
|
58
59
|
function getErrorMessage(error: unknown): string | null {
|
|
59
|
-
if (typeof error === "string") {
|
|
60
|
-
return error;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
60
|
if (error instanceof Error) {
|
|
64
61
|
return error.message;
|
|
65
62
|
}
|
|
@@ -71,7 +71,7 @@ export const KeycloakProvider = <T extends BaseEnvironment>({
|
|
|
71
71
|
|
|
72
72
|
const init = () =>
|
|
73
73
|
keycloak.init({
|
|
74
|
-
onLoad: "
|
|
74
|
+
onLoad: "login-required",
|
|
75
75
|
pkceMethod: "S256",
|
|
76
76
|
responseMode: "query",
|
|
77
77
|
scope: environment.scope,
|
|
@@ -84,14 +84,8 @@ export const KeycloakProvider = <T extends BaseEnvironment>({
|
|
|
84
84
|
calledOnce.current = true;
|
|
85
85
|
}, [keycloak]);
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (error || searchParams.get("error_description")) {
|
|
90
|
-
return (
|
|
91
|
-
<ErrorPage
|
|
92
|
-
error={error ? error : searchParams.get("error_description")}
|
|
93
|
-
/>
|
|
94
|
-
);
|
|
87
|
+
if (error) {
|
|
88
|
+
return <ErrorPage error={error} />;
|
|
95
89
|
}
|
|
96
90
|
|
|
97
91
|
if (!init) {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
// @ts-nocheck
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
FileUpload,
|
|
7
|
+
ValidatedOptions,
|
|
8
|
+
FileUploadProps,
|
|
9
|
+
} from "../../@patternfly/react-core";
|
|
10
|
+
import { ReactNode, useState } from "react";
|
|
11
|
+
import {
|
|
12
|
+
FieldPath,
|
|
13
|
+
FieldValues,
|
|
14
|
+
PathValue,
|
|
15
|
+
UseControllerProps,
|
|
16
|
+
useController,
|
|
17
|
+
} from "react-hook-form";
|
|
18
|
+
import { getRuleValue } from "../utils/getRuleValue";
|
|
19
|
+
import { FormLabel } from "./FormLabel";
|
|
20
|
+
import { useTranslation } from "react-i18next";
|
|
21
|
+
|
|
22
|
+
export type FileUploadControlProps<
|
|
23
|
+
T extends FieldValues,
|
|
24
|
+
P extends FieldPath<T> = FieldPath<T>,
|
|
25
|
+
> = UseControllerProps<T, P> &
|
|
26
|
+
Omit<FileUploadProps, "name" | "isRequired" | "required"> & {
|
|
27
|
+
label: string;
|
|
28
|
+
labelIcon?: string | ReactNode;
|
|
29
|
+
isDisabled?: boolean;
|
|
30
|
+
"data-testid"?: string;
|
|
31
|
+
type?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const FileUploadControl = <
|
|
35
|
+
T extends FieldValues,
|
|
36
|
+
P extends FieldPath<T> = FieldPath<T>,
|
|
37
|
+
>(
|
|
38
|
+
props: FileUploadControlProps<T, P>,
|
|
39
|
+
) => {
|
|
40
|
+
const { labelIcon, ...rest } = props;
|
|
41
|
+
const required = !!getRuleValue(props.rules?.required);
|
|
42
|
+
const defaultValue = props.defaultValue ?? ("" as PathValue<T, P>);
|
|
43
|
+
|
|
44
|
+
const { t } = useTranslation();
|
|
45
|
+
|
|
46
|
+
const [filename, setFilename] = useState<string>("");
|
|
47
|
+
|
|
48
|
+
const { field, fieldState } = useController({
|
|
49
|
+
...props,
|
|
50
|
+
defaultValue,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<FormLabel
|
|
55
|
+
name={props.name}
|
|
56
|
+
label={props.label}
|
|
57
|
+
labelIcon={labelIcon}
|
|
58
|
+
isRequired={required}
|
|
59
|
+
error={fieldState.error}
|
|
60
|
+
>
|
|
61
|
+
<FileUpload
|
|
62
|
+
isRequired={required}
|
|
63
|
+
data-testid={props["data-testid"] || props.name}
|
|
64
|
+
filename={filename}
|
|
65
|
+
browseButtonText={t("browse")}
|
|
66
|
+
validated={
|
|
67
|
+
fieldState.error ? ValidatedOptions.error : ValidatedOptions.default
|
|
68
|
+
}
|
|
69
|
+
hideDefaultPreview
|
|
70
|
+
isDisabled={props.isDisabled}
|
|
71
|
+
type="text"
|
|
72
|
+
onFileInputChange={(_, file) => {
|
|
73
|
+
field.onChange(file);
|
|
74
|
+
setFilename(file.name);
|
|
75
|
+
}}
|
|
76
|
+
onClearClick={() => {
|
|
77
|
+
field.onChange(null);
|
|
78
|
+
setFilename("");
|
|
79
|
+
}}
|
|
80
|
+
{...rest}
|
|
81
|
+
{...field}
|
|
82
|
+
/>
|
|
83
|
+
</FormLabel>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
@@ -9,6 +9,7 @@ import { FormErrorText } from "./FormErrorText";
|
|
|
9
9
|
import { HelpItem } from "./HelpItem";
|
|
10
10
|
|
|
11
11
|
export type FieldProps<T extends FieldValues = FieldValues> = {
|
|
12
|
+
id?: string | undefined;
|
|
12
13
|
label?: string;
|
|
13
14
|
name: string;
|
|
14
15
|
labelIcon?: string | ReactNode;
|
|
@@ -19,6 +20,7 @@ export type FieldProps<T extends FieldValues = FieldValues> = {
|
|
|
19
20
|
type FormLabelProps = FieldProps & Omit<FormGroupProps, "label" | "labelIcon">;
|
|
20
21
|
|
|
21
22
|
export const FormLabel = ({
|
|
23
|
+
id,
|
|
22
24
|
name,
|
|
23
25
|
label,
|
|
24
26
|
labelIcon,
|
|
@@ -28,10 +30,10 @@ export const FormLabel = ({
|
|
|
28
30
|
}: PropsWithChildren<FormLabelProps>) => (
|
|
29
31
|
<FormGroup
|
|
30
32
|
label={label || name}
|
|
31
|
-
fieldId={name}
|
|
33
|
+
fieldId={id || name}
|
|
32
34
|
labelIcon={
|
|
33
35
|
labelIcon ? (
|
|
34
|
-
<HelpItem helpText={labelIcon} fieldLabelId={name} />
|
|
36
|
+
<HelpItem helpText={labelIcon} fieldLabelId={id || name} />
|
|
35
37
|
) : undefined
|
|
36
38
|
}
|
|
37
39
|
{...rest}
|
|
@@ -18,7 +18,7 @@ export type SwitchControlProps<
|
|
|
18
18
|
T extends FieldValues,
|
|
19
19
|
P extends FieldPath<T> = FieldPath<T>,
|
|
20
20
|
> = Omit<SwitchProps, "name" | "defaultValue" | "ref"> &
|
|
21
|
-
UseControllerProps<
|
|
21
|
+
UseControllerProps<any, P> & {
|
|
22
22
|
name: string;
|
|
23
23
|
label?: string;
|
|
24
24
|
labelIcon?: string;
|
package/keycloak-theme/shared/keycloak-ui-shared/controls/select-control/SingleSelectControl.tsx
CHANGED
|
@@ -34,6 +34,7 @@ export const SingleSelectControl = <
|
|
|
34
34
|
name,
|
|
35
35
|
label,
|
|
36
36
|
options,
|
|
37
|
+
selectedOptions = [],
|
|
37
38
|
controller,
|
|
38
39
|
labelIcon,
|
|
39
40
|
isDisabled,
|
|
@@ -49,6 +50,7 @@ export const SingleSelectControl = <
|
|
|
49
50
|
|
|
50
51
|
return (
|
|
51
52
|
<FormLabel
|
|
53
|
+
id={id}
|
|
52
54
|
name={name}
|
|
53
55
|
label={label}
|
|
54
56
|
isRequired={required}
|
|
@@ -78,7 +80,7 @@ export const SingleSelectControl = <
|
|
|
78
80
|
}
|
|
79
81
|
toggle={(ref) => (
|
|
80
82
|
<MenuToggle
|
|
81
|
-
id={id || name
|
|
83
|
+
id={id || name}
|
|
82
84
|
ref={ref}
|
|
83
85
|
onClick={() => setOpen(!open)}
|
|
84
86
|
isExpanded={open}
|
|
@@ -108,7 +110,7 @@ export const SingleSelectControl = <
|
|
|
108
110
|
isOpen={open}
|
|
109
111
|
>
|
|
110
112
|
<SelectList data-testid={`select-${name}`}>
|
|
111
|
-
{options.map((option) => (
|
|
113
|
+
{[...options, ...selectedOptions].map((option) => (
|
|
112
114
|
<SelectOption key={key(option)} value={key(option)}>
|
|
113
115
|
{isString(option) ? option : option.value}
|
|
114
116
|
</SelectOption>
|
package/keycloak-theme/shared/keycloak-ui-shared/controls/select-control/TypeaheadSelectControl.tsx
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
import { getRuleValue } from "../../utils/getRuleValue";
|
|
29
29
|
import { FormLabel } from "../FormLabel";
|
|
30
30
|
import {
|
|
31
|
+
OptionType,
|
|
31
32
|
SelectControlOption,
|
|
32
33
|
SelectControlProps,
|
|
33
34
|
SelectVariant,
|
|
@@ -47,6 +48,7 @@ export const TypeaheadSelectControl = <
|
|
|
47
48
|
name,
|
|
48
49
|
label,
|
|
49
50
|
options,
|
|
51
|
+
selectedOptions = [],
|
|
50
52
|
controller,
|
|
51
53
|
labelIcon,
|
|
52
54
|
placeholderText,
|
|
@@ -61,28 +63,50 @@ export const TypeaheadSelectControl = <
|
|
|
61
63
|
const [open, setOpen] = useState(false);
|
|
62
64
|
const [filterValue, setFilterValue] = useState("");
|
|
63
65
|
const [focusedItemIndex, setFocusedItemIndex] = useState<number>(0);
|
|
66
|
+
const [selectedOptionsState, setSelectedOptions] = useState<
|
|
67
|
+
SelectControlOption[]
|
|
68
|
+
>([]);
|
|
64
69
|
const textInputRef = useRef<HTMLInputElement>();
|
|
65
70
|
const required = getRuleValue(controller.rules?.required) === true;
|
|
66
71
|
const isTypeaheadMulti = variant === SelectVariant.typeaheadMulti;
|
|
67
72
|
|
|
68
|
-
const
|
|
69
|
-
|
|
73
|
+
const combinedOptions = useMemo(
|
|
74
|
+
() =>
|
|
75
|
+
[
|
|
76
|
+
...options.filter(
|
|
77
|
+
(o) => !selectedOptions.map((o) => getValue(o)).includes(getValue(o)),
|
|
78
|
+
),
|
|
79
|
+
...selectedOptions,
|
|
80
|
+
] as OptionType,
|
|
81
|
+
[selectedOptions, options],
|
|
70
82
|
);
|
|
71
83
|
|
|
72
|
-
const
|
|
73
|
-
()
|
|
74
|
-
filteredOptions.map((option, index) => (
|
|
75
|
-
<SelectOption
|
|
76
|
-
key={key(option)}
|
|
77
|
-
value={key(option)}
|
|
78
|
-
isFocused={focusedItemIndex === index}
|
|
79
|
-
>
|
|
80
|
-
{getValue(option)}
|
|
81
|
-
</SelectOption>
|
|
82
|
-
)),
|
|
83
|
-
[focusedItemIndex, filteredOptions],
|
|
84
|
+
const filteredOptions = combinedOptions.filter((option) =>
|
|
85
|
+
getValue(option).toLowerCase().startsWith(filterValue.toLowerCase()),
|
|
84
86
|
);
|
|
85
87
|
|
|
88
|
+
const updateValue = (
|
|
89
|
+
option: string | string[],
|
|
90
|
+
field: ControllerRenderProps<FieldValues, string>,
|
|
91
|
+
) => {
|
|
92
|
+
if (field.value.includes(option)) {
|
|
93
|
+
field.onChange(field.value.filter((item: string) => item !== option));
|
|
94
|
+
if (isSelectBasedOptions(options)) {
|
|
95
|
+
setSelectedOptions(
|
|
96
|
+
selectedOptionsState.filter((item) => item.key !== option),
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
field.onChange([...field.value, option]);
|
|
101
|
+
if (isSelectBasedOptions(combinedOptions)) {
|
|
102
|
+
setSelectedOptions([
|
|
103
|
+
...selectedOptionsState,
|
|
104
|
+
combinedOptions.find((o) => o.key === option)!,
|
|
105
|
+
]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
86
110
|
const onInputKeyDown = (
|
|
87
111
|
event: React.KeyboardEvent<HTMLDivElement>,
|
|
88
112
|
field: ControllerRenderProps<FieldValues, string>,
|
|
@@ -100,11 +124,8 @@ export const TypeaheadSelectControl = <
|
|
|
100
124
|
setFilterValue("");
|
|
101
125
|
}
|
|
102
126
|
|
|
103
|
-
field
|
|
104
|
-
|
|
105
|
-
? [...field.value, key(focusedItem)]
|
|
106
|
-
: key(focusedItem),
|
|
107
|
-
);
|
|
127
|
+
updateValue(key(focusedItem), field);
|
|
128
|
+
|
|
108
129
|
setOpen(false);
|
|
109
130
|
setFocusedItemIndex(0);
|
|
110
131
|
|
|
@@ -152,6 +173,7 @@ export const TypeaheadSelectControl = <
|
|
|
152
173
|
|
|
153
174
|
return (
|
|
154
175
|
<FormLabel
|
|
176
|
+
id={id}
|
|
155
177
|
name={name}
|
|
156
178
|
label={label}
|
|
157
179
|
isRequired={required}
|
|
@@ -167,8 +189,8 @@ export const TypeaheadSelectControl = <
|
|
|
167
189
|
{...rest}
|
|
168
190
|
onOpenChange={() => setOpen(false)}
|
|
169
191
|
selected={
|
|
170
|
-
isSelectBasedOptions(
|
|
171
|
-
?
|
|
192
|
+
isSelectBasedOptions(combinedOptions)
|
|
193
|
+
? combinedOptions
|
|
172
194
|
.filter((o) =>
|
|
173
195
|
Array.isArray(field.value)
|
|
174
196
|
? field.value.includes(o.key)
|
|
@@ -181,7 +203,7 @@ export const TypeaheadSelectControl = <
|
|
|
181
203
|
toggle={(ref) => (
|
|
182
204
|
<MenuToggle
|
|
183
205
|
ref={ref}
|
|
184
|
-
id={id || name
|
|
206
|
+
id={id || name}
|
|
185
207
|
variant="typeahead"
|
|
186
208
|
onClick={() => {
|
|
187
209
|
setOpen(!open);
|
|
@@ -196,8 +218,8 @@ export const TypeaheadSelectControl = <
|
|
|
196
218
|
placeholder={placeholderText}
|
|
197
219
|
value={
|
|
198
220
|
variant === SelectVariant.typeahead && field.value
|
|
199
|
-
? isSelectBasedOptions(
|
|
200
|
-
?
|
|
221
|
+
? isSelectBasedOptions(combinedOptions)
|
|
222
|
+
? combinedOptions.find(
|
|
201
223
|
(o) =>
|
|
202
224
|
o.key ===
|
|
203
225
|
(Array.isArray(field.value)
|
|
@@ -235,9 +257,11 @@ export const TypeaheadSelectControl = <
|
|
|
235
257
|
);
|
|
236
258
|
}}
|
|
237
259
|
>
|
|
238
|
-
{isSelectBasedOptions(
|
|
239
|
-
?
|
|
240
|
-
|
|
260
|
+
{isSelectBasedOptions(combinedOptions)
|
|
261
|
+
? [
|
|
262
|
+
...combinedOptions,
|
|
263
|
+
...selectedOptionsState,
|
|
264
|
+
].find((o) => selection === o.key)?.value
|
|
241
265
|
: getValue(selection)}
|
|
242
266
|
</Chip>
|
|
243
267
|
),
|
|
@@ -267,13 +291,8 @@ export const TypeaheadSelectControl = <
|
|
|
267
291
|
event?.stopPropagation();
|
|
268
292
|
const option = v?.toString();
|
|
269
293
|
if (isTypeaheadMulti && Array.isArray(field.value)) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
field.value.filter((item: string) => item !== option),
|
|
273
|
-
);
|
|
274
|
-
} else {
|
|
275
|
-
field.onChange([...field.value, option]);
|
|
276
|
-
}
|
|
294
|
+
setFilterValue("");
|
|
295
|
+
updateValue(option || "", field);
|
|
277
296
|
} else {
|
|
278
297
|
field.onChange(Array.isArray(field.value) ? [option] : option);
|
|
279
298
|
setOpen(false);
|
|
@@ -281,7 +300,18 @@ export const TypeaheadSelectControl = <
|
|
|
281
300
|
}}
|
|
282
301
|
isOpen={open}
|
|
283
302
|
>
|
|
284
|
-
<SelectList>
|
|
303
|
+
<SelectList>
|
|
304
|
+
{filteredOptions.map((option, index) => (
|
|
305
|
+
<SelectOption
|
|
306
|
+
key={key(option)}
|
|
307
|
+
value={key(option)}
|
|
308
|
+
isFocused={focusedItemIndex === index}
|
|
309
|
+
isActive={field.value.includes(getValue(option))}
|
|
310
|
+
>
|
|
311
|
+
{getValue(option)}
|
|
312
|
+
</SelectOption>
|
|
313
|
+
))}
|
|
314
|
+
</SelectList>
|
|
285
315
|
</Select>
|
|
286
316
|
)}
|
|
287
317
|
/>
|
|
@@ -92,21 +92,21 @@ const CellRenderer = ({
|
|
|
92
92
|
index,
|
|
93
93
|
actions,
|
|
94
94
|
actionResolver,
|
|
95
|
-
}: CellRendererProps) =>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
extraData={{ rowIndex: index }}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
95
|
+
}: CellRendererProps) => {
|
|
96
|
+
const items = actions || actionResolver?.(row, {});
|
|
97
|
+
return (
|
|
98
|
+
<>
|
|
99
|
+
{row.cells!.map((c, i) => (
|
|
100
|
+
<Td key={`cell-${i}`}>{(isRow(c) ? c.title : c) as ReactNode}</Td>
|
|
101
|
+
))}
|
|
102
|
+
{items && items.length > 0 && !row.disableActions && (
|
|
103
|
+
<Td isActionCell>
|
|
104
|
+
<ActionsColumn items={items} extraData={{ rowIndex: index }} />
|
|
105
|
+
</Td>
|
|
106
|
+
)}
|
|
107
|
+
</>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
110
|
|
|
111
111
|
const ExpandableRowRenderer = ({ row }: CellRendererProps) =>
|
|
112
112
|
row.cells!.map((c, i) => (
|
|
@@ -239,6 +239,7 @@ function DataTable<T>({
|
|
|
239
239
|
(v) => get(v, "id") === row.data.id,
|
|
240
240
|
),
|
|
241
241
|
variant: isRadio ? "radio" : "checkbox",
|
|
242
|
+
isDisabled: row.disableSelection,
|
|
242
243
|
}}
|
|
243
244
|
/>
|
|
244
245
|
)}
|
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
// @ts-nocheck
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ComponentClass,
|
|
7
|
+
MouseEventHandler,
|
|
8
|
+
PropsWithChildren,
|
|
9
|
+
ReactNode,
|
|
10
|
+
} from "react";
|
|
6
11
|
import {
|
|
7
12
|
EmptyState,
|
|
8
13
|
EmptyStateIcon,
|
|
@@ -44,7 +49,8 @@ export const ListEmptyState = ({
|
|
|
44
49
|
secondaryActions,
|
|
45
50
|
icon,
|
|
46
51
|
isDisabled = false,
|
|
47
|
-
|
|
52
|
+
children,
|
|
53
|
+
}: PropsWithChildren<ListEmptyStateProps>) => {
|
|
48
54
|
return (
|
|
49
55
|
<EmptyState data-testid="empty-state" variant="lg">
|
|
50
56
|
{hasIcon && isSearchVariant ? (
|
|
@@ -67,6 +73,7 @@ export const ListEmptyState = ({
|
|
|
67
73
|
{primaryActionText}
|
|
68
74
|
</Button>
|
|
69
75
|
)}
|
|
76
|
+
{children}
|
|
70
77
|
{secondaryActions && (
|
|
71
78
|
<EmptyStateActions>
|
|
72
79
|
{secondaryActions.map((action) => (
|
|
@@ -47,6 +47,10 @@ export {
|
|
|
47
47
|
KeycloakTextArea,
|
|
48
48
|
type KeycloakTextAreaProps,
|
|
49
49
|
} from "./controls/keycloak-text-area/KeycloakTextArea";
|
|
50
|
+
export {
|
|
51
|
+
FileUploadControl,
|
|
52
|
+
type FileUploadControlProps,
|
|
53
|
+
} from "./controls/FileUploadControl";
|
|
50
54
|
export { IconMapper } from "./icons/IconMapper";
|
|
51
55
|
export { FormPanel } from "./scroll-form/FormPanel";
|
|
52
56
|
export { ScrollForm, mainPageContentId } from "./scroll-form/ScrollForm";
|
|
@@ -84,10 +84,17 @@ export const ScrollForm = ({
|
|
|
84
84
|
const scrollId = spacesToHyphens(title.toLowerCase());
|
|
85
85
|
|
|
86
86
|
return (
|
|
87
|
-
// note that JumpLinks currently does not work with spaces in the href
|
|
88
87
|
<JumpLinksItem
|
|
89
88
|
key={title}
|
|
90
|
-
|
|
89
|
+
onClick={() => {
|
|
90
|
+
const element = document.getElementById(scrollId);
|
|
91
|
+
if (element) {
|
|
92
|
+
element.scrollIntoView({
|
|
93
|
+
behavior: "smooth",
|
|
94
|
+
block: "start",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}}
|
|
91
98
|
data-testid={`jump-link-${scrollId}`}
|
|
92
99
|
>
|
|
93
100
|
{title}
|
|
@@ -19,7 +19,7 @@ export type UserFormFields = Omit<
|
|
|
19
19
|
type FieldError = {
|
|
20
20
|
field: string;
|
|
21
21
|
errorMessage: string;
|
|
22
|
-
params?:
|
|
22
|
+
params?: unknown[];
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
type ErrorArray = { errors?: FieldError[] };
|
|
@@ -28,7 +28,11 @@ export type UserProfileError = {
|
|
|
28
28
|
responseData: ErrorArray | FieldError;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
const isBundleKey = (displayName
|
|
31
|
+
const isBundleKey = (displayName: unknown) => {
|
|
32
|
+
return displayName && typeof displayName === "string"
|
|
33
|
+
? displayName.includes("${")
|
|
34
|
+
: false;
|
|
35
|
+
};
|
|
32
36
|
const unWrap = (key: string) => key.substring(2, key.length - 1);
|
|
33
37
|
|
|
34
38
|
export const label = (
|
|
@@ -77,7 +81,9 @@ export function setUserProfileServerError<T>(
|
|
|
77
81
|
).forEach((e) => {
|
|
78
82
|
const params = Object.assign(
|
|
79
83
|
{},
|
|
80
|
-
e.params?.map((p) =>
|
|
84
|
+
e.params?.map((p) =>
|
|
85
|
+
isBundleKey(p?.toString()) ? t(unWrap(p as string)) : p,
|
|
86
|
+
),
|
|
81
87
|
);
|
|
82
88
|
setError(fieldName(e.field) as keyof T, {
|
|
83
89
|
message: t(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keycloakify/keycloak-ui-shared",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "260305.0.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git://github.com/keycloakify/keycloak-ui-shared.git"
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"@patternfly/react-icons": "^5.4.2",
|
|
14
14
|
"@patternfly/react-styles": "^5.4.1",
|
|
15
15
|
"@patternfly/react-table": "^5.4.16",
|
|
16
|
-
"i18next": "^
|
|
16
|
+
"i18next": "^25.2.1",
|
|
17
17
|
"keycloak-js": "^26.2.0",
|
|
18
18
|
"lodash-es": "^4.17.21",
|
|
19
19
|
"react": "^18.3.1",
|
|
20
|
-
"react-hook-form": "7.
|
|
21
|
-
"react-i18next": "^15.
|
|
22
|
-
"@keycloak/keycloak-admin-client": "26.
|
|
20
|
+
"react-hook-form": "7.59.0",
|
|
21
|
+
"react-i18next": "^15.5.3",
|
|
22
|
+
"@keycloak/keycloak-admin-client": "26.3.5",
|
|
23
23
|
"@types/lodash-es": "^4.17.12",
|
|
24
24
|
"@types/react": "^18.3.18"
|
|
25
25
|
},
|