@judo/components 0.1.1-alpha.3
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/LICENSE +277 -0
- package/README.md +1 -0
- package/dist/cjs/components.cjs +2 -0
- package/dist/cjs/components.cjs.map +1 -0
- package/dist/components.d.cts +111 -0
- package/dist/components.d.mts +111 -0
- package/dist/esm/components.mjs +2 -0
- package/dist/esm/components.mjs.map +1 -0
- package/package.json +54 -0
- package/src/AggregationInput.tsx +156 -0
- package/src/CustomBreadcrumb.tsx +144 -0
- package/src/CustomLink.tsx +35 -0
- package/src/CustomTablePagination.tsx +41 -0
- package/src/DropdownButton.tsx +124 -0
- package/src/PageHeader.tsx +35 -0
- package/src/TrinaryLogicCombobox.tsx +64 -0
- package/src/dialog/ConfirmationDialog.tsx +50 -0
- package/src/dialog/DialogContext.tsx +206 -0
- package/src/dialog/FilterDialog.tsx +424 -0
- package/src/dialog/PageDialog.tsx +40 -0
- package/src/dialog/RangeDialog.tsx +324 -0
- package/src/dialog/index.tsx +5 -0
- package/src/index.tsx +9 -0
- package/src/table/index.tsx +1 -0
- package/src/table/table-row-actions.tsx +86 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { Delete, LinkOff, NoteAdd, Visibility } from '@mui/icons-material';
|
|
2
|
+
import { ButtonBase, Grid, IconButton, InputAdornment, TextField } from '@mui/material';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
5
|
+
import { exists } from '@judo/utilities';
|
|
6
|
+
|
|
7
|
+
interface AggregationInputProps {
|
|
8
|
+
name?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
label?: string;
|
|
11
|
+
value: any | undefined | null;
|
|
12
|
+
error?: boolean | undefined;
|
|
13
|
+
helperText?: string | undefined;
|
|
14
|
+
disabled?: boolean | undefined;
|
|
15
|
+
readonly?: boolean | undefined;
|
|
16
|
+
labelList: string[];
|
|
17
|
+
icon?: ReactNode;
|
|
18
|
+
onSet?: () => Promise<void> | undefined;
|
|
19
|
+
onView?: () => void | undefined;
|
|
20
|
+
onCreate?: () => void | undefined;
|
|
21
|
+
onDelete?: () => Promise<void> | undefined;
|
|
22
|
+
onRemove?: () => void | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ViewIconsProps {
|
|
26
|
+
value: any | undefined | null;
|
|
27
|
+
disabled: boolean;
|
|
28
|
+
onView?: () => void | undefined;
|
|
29
|
+
onCreate?: () => void | undefined;
|
|
30
|
+
onDelete?: () => Promise<void> | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface EditIconsProps {
|
|
34
|
+
value: any | undefined | null;
|
|
35
|
+
disabled: boolean;
|
|
36
|
+
onRemove?: () => void | undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const AggregationInput = ({
|
|
40
|
+
name,
|
|
41
|
+
id,
|
|
42
|
+
label,
|
|
43
|
+
value,
|
|
44
|
+
error = false,
|
|
45
|
+
helperText,
|
|
46
|
+
disabled = false,
|
|
47
|
+
readonly = true,
|
|
48
|
+
labelList,
|
|
49
|
+
icon,
|
|
50
|
+
onSet,
|
|
51
|
+
onView,
|
|
52
|
+
onCreate,
|
|
53
|
+
onRemove,
|
|
54
|
+
onDelete,
|
|
55
|
+
}: AggregationInputProps) => {
|
|
56
|
+
const [focused, setFocused] = useState(false);
|
|
57
|
+
|
|
58
|
+
let icons: ReactNode;
|
|
59
|
+
|
|
60
|
+
if (readonly) {
|
|
61
|
+
icons = <ViewIcons value={value} disabled={disabled} onCreate={onCreate} onView={onView} onDelete={onDelete} />;
|
|
62
|
+
} else {
|
|
63
|
+
icons = <EditIcons value={value} disabled={disabled} onRemove={onRemove} />;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Grid container item direction="row" justifyContent="stretch" alignContent="stretch">
|
|
68
|
+
<ButtonBase
|
|
69
|
+
sx={{ padding: 0 }}
|
|
70
|
+
disabled={disabled || readonly}
|
|
71
|
+
onFocusCapture={() => setFocused(true)}
|
|
72
|
+
onBlur={() => setFocused(false)}
|
|
73
|
+
onClick={onSet}
|
|
74
|
+
>
|
|
75
|
+
<TextField
|
|
76
|
+
disabled={!onSet || disabled}
|
|
77
|
+
name={name}
|
|
78
|
+
id={id}
|
|
79
|
+
label={label}
|
|
80
|
+
error={error}
|
|
81
|
+
helperText={helperText}
|
|
82
|
+
focused={focused}
|
|
83
|
+
fullWidth
|
|
84
|
+
value={labelList.join(' - ')}
|
|
85
|
+
className={!readonly ? undefined : 'Mui-readOnly'}
|
|
86
|
+
sx={{
|
|
87
|
+
':hover': {
|
|
88
|
+
cursor: 'pointer',
|
|
89
|
+
},
|
|
90
|
+
'.MuiFilledInput-input:hover': {
|
|
91
|
+
cursor: 'pointer',
|
|
92
|
+
},
|
|
93
|
+
}}
|
|
94
|
+
InputProps={{
|
|
95
|
+
readOnly: true,
|
|
96
|
+
startAdornment: <InputAdornment position="start">{icon}</InputAdornment>,
|
|
97
|
+
}}
|
|
98
|
+
/>
|
|
99
|
+
</ButtonBase>
|
|
100
|
+
{icons}
|
|
101
|
+
</Grid>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const ViewIcons = ({ value, disabled, onCreate, onDelete, onView }: ViewIconsProps) => {
|
|
106
|
+
let icons: ReactNode;
|
|
107
|
+
|
|
108
|
+
if (exists(value)) {
|
|
109
|
+
icons = (
|
|
110
|
+
<>
|
|
111
|
+
{onView && (
|
|
112
|
+
<IconButton disabled={disabled} onClick={onView}>
|
|
113
|
+
<Visibility />
|
|
114
|
+
</IconButton>
|
|
115
|
+
)}
|
|
116
|
+
{onDelete && (
|
|
117
|
+
<IconButton disabled={disabled} onClick={onDelete}>
|
|
118
|
+
<Delete />
|
|
119
|
+
</IconButton>
|
|
120
|
+
)}
|
|
121
|
+
</>
|
|
122
|
+
);
|
|
123
|
+
} else {
|
|
124
|
+
icons = (
|
|
125
|
+
<>
|
|
126
|
+
{onCreate && (
|
|
127
|
+
<IconButton disabled={disabled} onClick={onCreate}>
|
|
128
|
+
<NoteAdd />
|
|
129
|
+
</IconButton>
|
|
130
|
+
)}
|
|
131
|
+
</>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return <>{icons}</>;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const EditIcons = ({ value, disabled, onRemove }: EditIconsProps) => {
|
|
139
|
+
let icons: ReactNode;
|
|
140
|
+
|
|
141
|
+
if (exists(value)) {
|
|
142
|
+
icons = (
|
|
143
|
+
<>
|
|
144
|
+
{onRemove && (
|
|
145
|
+
<IconButton disabled={disabled} onClick={onRemove}>
|
|
146
|
+
<LinkOff />
|
|
147
|
+
</IconButton>
|
|
148
|
+
)}
|
|
149
|
+
</>
|
|
150
|
+
);
|
|
151
|
+
} else {
|
|
152
|
+
icons = <></>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return <>{icons}</>;
|
|
156
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Home } from '@mui/icons-material';
|
|
2
|
+
import { Breadcrumbs, Typography } from '@mui/material';
|
|
3
|
+
import { useState, useContext, createContext, useMemo, useEffect } from 'react';
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
5
|
+
import { useLocation, useNavigate } from 'react-router-dom';
|
|
6
|
+
import type { To } from 'react-router-dom';
|
|
7
|
+
|
|
8
|
+
interface BreadcrumbProviderProps {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type JudoNavigationSetTitle = (pageTitle: string) => void;
|
|
13
|
+
|
|
14
|
+
interface JudoNavigationProviderContext {
|
|
15
|
+
clearNavigate: (to: To) => void;
|
|
16
|
+
navigate: (to: To) => void;
|
|
17
|
+
back: () => void;
|
|
18
|
+
isBackDisabled: boolean;
|
|
19
|
+
setTitle: JudoNavigationSetTitle;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface BreadcrumbItem {
|
|
23
|
+
key: string;
|
|
24
|
+
path: To;
|
|
25
|
+
label: string | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
const JudoNavigationContextState = createContext<JudoNavigationProviderContext>();
|
|
30
|
+
|
|
31
|
+
const BreadcrumbContextState = createContext<BreadcrumbItem[]>([]);
|
|
32
|
+
|
|
33
|
+
export const useJudoNavigation = () => {
|
|
34
|
+
const context = useContext(JudoNavigationContextState);
|
|
35
|
+
|
|
36
|
+
if (context === undefined) {
|
|
37
|
+
throw new Error('useJudoNavigation was used outside of its Provider');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return context;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const BreadcrumbProvider = ({ children }: BreadcrumbProviderProps) => {
|
|
44
|
+
const navigate = useNavigate();
|
|
45
|
+
const location = useLocation();
|
|
46
|
+
|
|
47
|
+
const [breadcrumbItems, setBreadcrumbItems] = useState<BreadcrumbItem[]>([]);
|
|
48
|
+
const [nextBreadcrumbItem, setNextBreadcrumbItem] = useState<BreadcrumbItem>({} as any);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
setNextBreadcrumbItem((prevNextBreadcrumbItem) => {
|
|
52
|
+
return {
|
|
53
|
+
...prevNextBreadcrumbItem,
|
|
54
|
+
key: '0.' + location.pathname,
|
|
55
|
+
path: location.pathname,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}, []);
|
|
59
|
+
|
|
60
|
+
const isBackDisabled = useMemo(() => {
|
|
61
|
+
return breadcrumbItems.length === 0;
|
|
62
|
+
}, [breadcrumbItems]);
|
|
63
|
+
|
|
64
|
+
const judoNavigationContext = {
|
|
65
|
+
clearNavigate: (to: To) => {
|
|
66
|
+
setBreadcrumbItems([]);
|
|
67
|
+
setNextBreadcrumbItem({
|
|
68
|
+
key: '0.' + to.toString(),
|
|
69
|
+
label: null,
|
|
70
|
+
path: to,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
navigate(to);
|
|
74
|
+
},
|
|
75
|
+
navigate: (to: To) => {
|
|
76
|
+
if (nextBreadcrumbItem.label === null) {
|
|
77
|
+
throw Error('Page title has not been set!');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setBreadcrumbItems((prevBreadCrumbItems) => {
|
|
81
|
+
return [...prevBreadCrumbItems, nextBreadcrumbItem];
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
setNextBreadcrumbItem({
|
|
85
|
+
key: breadcrumbItems.length + '.' + to.toString(),
|
|
86
|
+
label: null,
|
|
87
|
+
path: to,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
navigate(to);
|
|
91
|
+
},
|
|
92
|
+
back: () => {
|
|
93
|
+
if (breadcrumbItems.length !== 0) {
|
|
94
|
+
const lastItem = breadcrumbItems[breadcrumbItems.length - 1];
|
|
95
|
+
setNextBreadcrumbItem(lastItem);
|
|
96
|
+
setBreadcrumbItems((prevBreadcrumbItems) => {
|
|
97
|
+
return [...prevBreadcrumbItems.slice(0, prevBreadcrumbItems.length - 1)];
|
|
98
|
+
});
|
|
99
|
+
navigate(lastItem.path);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
setBreadcrumbItems([]);
|
|
104
|
+
setNextBreadcrumbItem({
|
|
105
|
+
key: '0.' + '/'.toString(),
|
|
106
|
+
label: null,
|
|
107
|
+
path: '/',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
navigate('/');
|
|
111
|
+
},
|
|
112
|
+
isBackDisabled: isBackDisabled,
|
|
113
|
+
setTitle: (pageTitle: string) => {
|
|
114
|
+
setNextBreadcrumbItem((prevNextBreadcrumbItem) => {
|
|
115
|
+
return { ...prevNextBreadcrumbItem, label: pageTitle };
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<BreadcrumbContextState.Provider value={breadcrumbItems}>
|
|
122
|
+
<JudoNavigationContextState.Provider value={judoNavigationContext}>
|
|
123
|
+
{children}
|
|
124
|
+
</JudoNavigationContextState.Provider>
|
|
125
|
+
</BreadcrumbContextState.Provider>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export const CustomBreadcrumb = () => {
|
|
130
|
+
const breadcrumbItems = useContext(BreadcrumbContextState);
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<Breadcrumbs maxItems={2} separator=">">
|
|
134
|
+
<Home />
|
|
135
|
+
{breadcrumbItems.map(({ label, key }, index) => {
|
|
136
|
+
return (
|
|
137
|
+
<Typography color="text.primary" key={key}>
|
|
138
|
+
{label}
|
|
139
|
+
</Typography>
|
|
140
|
+
);
|
|
141
|
+
})}
|
|
142
|
+
</Breadcrumbs>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useMatch, useResolvedPath } from 'react-router-dom';
|
|
2
|
+
import type { LinkProps } from 'react-router-dom';
|
|
3
|
+
import { theme } from '@judo/theme';
|
|
4
|
+
import { ListItem, ListItemButton } from '@mui/material';
|
|
5
|
+
import { useJudoNavigation } from './CustomBreadcrumb';
|
|
6
|
+
|
|
7
|
+
const customLinkItemStyle = {
|
|
8
|
+
py: '2px',
|
|
9
|
+
px: 3,
|
|
10
|
+
color: theme.palette.text.secondary,
|
|
11
|
+
'&:hover, &:focus': {
|
|
12
|
+
color: theme.palette.secondary.main,
|
|
13
|
+
},
|
|
14
|
+
'&.Mui-selected': {
|
|
15
|
+
color: theme.palette.secondary.main,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function CustomLink({ children, to, ...props }: LinkProps) {
|
|
20
|
+
const resolved = useResolvedPath(to);
|
|
21
|
+
const match = useMatch({ path: resolved.pathname, end: true });
|
|
22
|
+
const { clearNavigate } = useJudoNavigation();
|
|
23
|
+
|
|
24
|
+
const onClickHandler = () => {
|
|
25
|
+
clearNavigate(to);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<ListItem disablePadding style={{ textDecoration: 'none' }}>
|
|
30
|
+
<ListItemButton selected={!!match} sx={customLinkItemStyle} onClick={onClickHandler}>
|
|
31
|
+
{children}
|
|
32
|
+
</ListItemButton>
|
|
33
|
+
</ListItem>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { TablePagination } from '@mui/material';
|
|
2
|
+
import type { MouseEvent, Dispatch, SetStateAction } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface CustomTablePaginationProps {
|
|
5
|
+
pageChange: (isNext: boolean) => Promise<void>;
|
|
6
|
+
isNextButtonEnabled: boolean;
|
|
7
|
+
page: number;
|
|
8
|
+
rowPerPage: number;
|
|
9
|
+
setPage: Dispatch<SetStateAction<number>>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const CustomTablePagination = (props: CustomTablePaginationProps) => {
|
|
13
|
+
const handleChangePage = async (event: MouseEvent<HTMLButtonElement> | null, newPage: number) => {
|
|
14
|
+
let isNext = true;
|
|
15
|
+
if (newPage < props.page) {
|
|
16
|
+
isNext = false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
props.setPage(newPage);
|
|
20
|
+
|
|
21
|
+
await props.pageChange(isNext);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<TablePagination
|
|
26
|
+
component="div"
|
|
27
|
+
count={-1}
|
|
28
|
+
page={props.page}
|
|
29
|
+
onPageChange={handleChangePage}
|
|
30
|
+
rowsPerPage={props.rowPerPage}
|
|
31
|
+
rowsPerPageOptions={[props.rowPerPage]}
|
|
32
|
+
labelDisplayedRows={({ from, to }) => `${from}–${to}`}
|
|
33
|
+
nextIconButtonProps={{
|
|
34
|
+
disabled: !props.isNextButtonEnabled,
|
|
35
|
+
}}
|
|
36
|
+
backIconButtonProps={{
|
|
37
|
+
disabled: props.page === 0,
|
|
38
|
+
}}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Button, ClickAwayListener, Grow, MenuItem, MenuList, Paper, Popper } from '@mui/material';
|
|
2
|
+
import { useState, useRef, useEffect } from 'react';
|
|
3
|
+
import type { ReactNode, KeyboardEvent, SyntheticEvent } from 'react';
|
|
4
|
+
import { KeyboardArrowDown } from '@mui/icons-material';
|
|
5
|
+
|
|
6
|
+
interface DropdownMenuItem {
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
visible?: boolean;
|
|
9
|
+
label?: string;
|
|
10
|
+
onClick: () => void;
|
|
11
|
+
startIcon?: ReactNode;
|
|
12
|
+
endIcon?: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface DropdownButtonProps {
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
id?: string | undefined;
|
|
18
|
+
menuItems: DropdownMenuItem[];
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
showDropdownIcon?: boolean;
|
|
21
|
+
fullWidth?: boolean;
|
|
22
|
+
variant?: 'text' | 'outlined' | 'contained' | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function DropdownButton({
|
|
26
|
+
children,
|
|
27
|
+
id,
|
|
28
|
+
menuItems,
|
|
29
|
+
disabled = false,
|
|
30
|
+
showDropdownIcon = true,
|
|
31
|
+
fullWidth = false,
|
|
32
|
+
variant = 'contained',
|
|
33
|
+
}: DropdownButtonProps) {
|
|
34
|
+
const [open, setOpen] = useState(false);
|
|
35
|
+
const anchorRef = useRef<HTMLButtonElement>(null);
|
|
36
|
+
|
|
37
|
+
const handleToggle = () => {
|
|
38
|
+
setOpen((prevOpen) => !prevOpen);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const handleClose = (event: Event | SyntheticEvent) => {
|
|
42
|
+
if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setOpen(false);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function handleListKeyDown(event: KeyboardEvent) {
|
|
50
|
+
if (event.key === 'Tab') {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
setOpen(false);
|
|
53
|
+
} else if (event.key === 'Escape') {
|
|
54
|
+
setOpen(false);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// return focus to the button when we transitioned from !open -> open
|
|
59
|
+
const prevOpen = useRef(open);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (prevOpen.current && !open) {
|
|
62
|
+
anchorRef.current!.focus();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
prevOpen.current = open;
|
|
66
|
+
}, [open]);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<>
|
|
70
|
+
<Button
|
|
71
|
+
ref={anchorRef}
|
|
72
|
+
id={id}
|
|
73
|
+
onClick={handleToggle}
|
|
74
|
+
endIcon={showDropdownIcon && <KeyboardArrowDown />}
|
|
75
|
+
disabled={disabled}
|
|
76
|
+
fullWidth={fullWidth}
|
|
77
|
+
variant={variant}
|
|
78
|
+
>
|
|
79
|
+
{children}
|
|
80
|
+
</Button>
|
|
81
|
+
<Popper
|
|
82
|
+
open={open}
|
|
83
|
+
anchorEl={anchorRef.current}
|
|
84
|
+
placement="bottom"
|
|
85
|
+
transition
|
|
86
|
+
style={{ zIndex: 1400, minWidth: anchorRef.current?.scrollWidth }}
|
|
87
|
+
>
|
|
88
|
+
{({ TransitionProps, placement }) => (
|
|
89
|
+
<Grow
|
|
90
|
+
{...TransitionProps}
|
|
91
|
+
style={{
|
|
92
|
+
transformOrigin: placement === 'bottom-start' ? 'left top' : 'left bottom',
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
<Paper>
|
|
96
|
+
<ClickAwayListener onClickAway={handleClose}>
|
|
97
|
+
<MenuList autoFocusItem={open} onKeyDown={handleListKeyDown}>
|
|
98
|
+
{menuItems
|
|
99
|
+
.filter((menuItem) => menuItem.visible ?? true)
|
|
100
|
+
.map((menuItem, index) => {
|
|
101
|
+
return (
|
|
102
|
+
<MenuItem
|
|
103
|
+
id={menuItem.label ?? '' + index}
|
|
104
|
+
disabled={menuItem.disabled ?? false}
|
|
105
|
+
onClick={(event) => {
|
|
106
|
+
handleClose(event);
|
|
107
|
+
menuItem.onClick();
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
{menuItem.startIcon}
|
|
111
|
+
{menuItem.label}
|
|
112
|
+
{menuItem.endIcon}
|
|
113
|
+
</MenuItem>
|
|
114
|
+
);
|
|
115
|
+
})}
|
|
116
|
+
</MenuList>
|
|
117
|
+
</ClickAwayListener>
|
|
118
|
+
</Paper>
|
|
119
|
+
</Grow>
|
|
120
|
+
)}
|
|
121
|
+
</Popper>
|
|
122
|
+
</>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { AppBar, Toolbar, Grid, Typography, Divider } from '@mui/material';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
import { useJudoNavigation } from './CustomBreadcrumb';
|
|
5
|
+
|
|
6
|
+
interface PageHeaderProps {
|
|
7
|
+
title: string;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const PageHeader = ({ title, children }: PageHeaderProps) => {
|
|
12
|
+
const { setTitle } = useJudoNavigation();
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
setTitle(title);
|
|
16
|
+
}, [title]);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<AppBar component="div" position="static" elevation={0} sx={{ zIndex: 0 }}>
|
|
21
|
+
<Toolbar>
|
|
22
|
+
<Grid container alignItems="center" spacing={1}>
|
|
23
|
+
<Grid item xs>
|
|
24
|
+
<Typography component="span" color="text.primary" variant="h5">
|
|
25
|
+
{title}
|
|
26
|
+
</Typography>
|
|
27
|
+
</Grid>
|
|
28
|
+
{children}
|
|
29
|
+
</Grid>
|
|
30
|
+
</Toolbar>
|
|
31
|
+
</AppBar>
|
|
32
|
+
<Divider />
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CheckBoxOutlined } from '@mui/icons-material';
|
|
2
|
+
import { TextField, InputAdornment, MenuItem } from '@mui/material';
|
|
3
|
+
import type { ChangeEvent } from 'react';
|
|
4
|
+
import { TRINARY_LOGIC } from '@judo/components-api';
|
|
5
|
+
|
|
6
|
+
interface TrinaryLogicProps {
|
|
7
|
+
readOnly?: boolean;
|
|
8
|
+
value?: boolean | null;
|
|
9
|
+
id?: string;
|
|
10
|
+
label: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
error?: boolean | undefined;
|
|
13
|
+
helperText?: string | undefined;
|
|
14
|
+
onChange?: (value: boolean | null) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const TrinaryLogicCombobox = ({
|
|
18
|
+
readOnly = false,
|
|
19
|
+
value = null,
|
|
20
|
+
id,
|
|
21
|
+
label,
|
|
22
|
+
name,
|
|
23
|
+
error,
|
|
24
|
+
helperText,
|
|
25
|
+
onChange,
|
|
26
|
+
}: TrinaryLogicProps) => {
|
|
27
|
+
const onChangeHandler = onChange
|
|
28
|
+
? (event: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
|
|
29
|
+
const index = Array.from(TRINARY_LOGIC.values()).indexOf(event.target.value);
|
|
30
|
+
const keysArray = Array.from(TRINARY_LOGIC.keys());
|
|
31
|
+
onChange(keysArray[index]);
|
|
32
|
+
}
|
|
33
|
+
: undefined;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<TextField
|
|
37
|
+
name={name}
|
|
38
|
+
id={id}
|
|
39
|
+
label={label}
|
|
40
|
+
select
|
|
41
|
+
value={TRINARY_LOGIC.get(value)}
|
|
42
|
+
onChange={onChangeHandler}
|
|
43
|
+
className={readOnly ? 'Mui-readOnly' : undefined}
|
|
44
|
+
error={error}
|
|
45
|
+
helperText={helperText}
|
|
46
|
+
InputProps={{
|
|
47
|
+
readOnly: readOnly,
|
|
48
|
+
startAdornment: (
|
|
49
|
+
<InputAdornment position="start">
|
|
50
|
+
<CheckBoxOutlined />
|
|
51
|
+
</InputAdornment>
|
|
52
|
+
),
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
{Array.from(TRINARY_LOGIC.keys()).map((key) => (
|
|
56
|
+
<MenuItem key={TRINARY_LOGIC.get(key)} value={TRINARY_LOGIC.get(key)}>
|
|
57
|
+
{TRINARY_LOGIC.get(key)}
|
|
58
|
+
</MenuItem>
|
|
59
|
+
))}
|
|
60
|
+
</TextField>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default TrinaryLogicCombobox;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Button } from '@mui/material';
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import type { ConfirmationDialogProps } from '@judo/components-api';
|
|
4
|
+
|
|
5
|
+
export const ConfirmationDialog = ({
|
|
6
|
+
confirmationMessage,
|
|
7
|
+
title,
|
|
8
|
+
resolve,
|
|
9
|
+
open,
|
|
10
|
+
handleClose,
|
|
11
|
+
}: ConfirmationDialogProps) => {
|
|
12
|
+
const descriptionElementRef = useRef<HTMLElement>(null);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (open) {
|
|
15
|
+
const { current: descriptionElement } = descriptionElementRef;
|
|
16
|
+
if (descriptionElement !== null) {
|
|
17
|
+
descriptionElement.focus();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}, [open]);
|
|
21
|
+
|
|
22
|
+
const cancel = () => {
|
|
23
|
+
handleClose();
|
|
24
|
+
resolve(false);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const ok = () => {
|
|
28
|
+
handleClose();
|
|
29
|
+
resolve(true);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Dialog open={open} onClose={handleClose} scroll="paper" fullWidth={true} maxWidth={'xs'}>
|
|
34
|
+
{title && <DialogTitle id="scroll-dialog-title">{title}</DialogTitle>}
|
|
35
|
+
<DialogContent dividers={!!title}>
|
|
36
|
+
<DialogContentText id="scroll-dialog-description" ref={descriptionElementRef} tabIndex={-1}>
|
|
37
|
+
{confirmationMessage}
|
|
38
|
+
</DialogContentText>
|
|
39
|
+
</DialogContent>
|
|
40
|
+
<DialogActions>
|
|
41
|
+
<Button variant="text" onClick={cancel}>
|
|
42
|
+
No
|
|
43
|
+
</Button>
|
|
44
|
+
<Button variant="text" onClick={ok}>
|
|
45
|
+
Yes
|
|
46
|
+
</Button>
|
|
47
|
+
</DialogActions>
|
|
48
|
+
</Dialog>
|
|
49
|
+
);
|
|
50
|
+
};
|