@inceptionbg/iui 2.0.20 → 2.0.21
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/icons/index.d.ts +5 -1
- package/dist/icons/index.js +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/iui.css +1 -1
- package/package.json +2 -2
- package/src/assets/icons/index.ts +2 -0
- package/src/assets/icons/light/faArrowLeft.ts +15 -0
- package/src/assets/icons/light/faArrowRight.ts +15 -0
- package/src/components/Accordions/Accordions.tsx +37 -33
- package/src/components/Helper/Collapse.tsx +5 -1
- package/src/components/Inputs/Select2/Select.tsx +258 -0
- package/src/components/Inputs/Select2/select.scss +42 -0
- package/src/components/Tooltip/Tooltip.tsx +81 -14
- package/src/components/Wrappers/FormWrapper.tsx +3 -1
- package/src/components/Wrappers/PageLayout.tsx +14 -6
- package/src/styles/common/helpers/_size.scss +3 -0
- package/src/styles/components/_accordions.scss +3 -0
- package/src/styles/components/_page.scss +45 -12
- package/src/styles/components/_table.scss +1 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import './select.scss';
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useEffect, ChangeEvent, FC, UIEvent } from 'react';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import { InputWrapper } from '../InputWrapper';
|
|
6
|
+
import { createPortal } from 'react-dom';
|
|
7
|
+
import { rootDir } from '../../../utils/rootDir';
|
|
8
|
+
import { useMenuPosition } from '../../Menu/hooks/useMenuPosition';
|
|
9
|
+
import { IMenuPlacement } from '../../../types/IMenu';
|
|
10
|
+
|
|
11
|
+
export interface OptionType {
|
|
12
|
+
label: string;
|
|
13
|
+
value: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// interface LoadOptionsParams {
|
|
17
|
+
// inputValue: string;
|
|
18
|
+
// loadedOptions: OptionType[];
|
|
19
|
+
// page: number;
|
|
20
|
+
// }
|
|
21
|
+
|
|
22
|
+
interface LoadOptionsResponse {
|
|
23
|
+
options: OptionType[];
|
|
24
|
+
hasMore: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface CustomSelectProps {
|
|
28
|
+
label: string;
|
|
29
|
+
value?: any;
|
|
30
|
+
setValue: (option: OptionType | null) => void;
|
|
31
|
+
required?: boolean;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
isMulti?: boolean;
|
|
34
|
+
isAsync?: boolean;
|
|
35
|
+
isCreatable?: boolean;
|
|
36
|
+
options?: OptionType[];
|
|
37
|
+
placeholder?: string;
|
|
38
|
+
loadOptions?: (
|
|
39
|
+
inputValue: string,
|
|
40
|
+
loadedOptions: OptionType[],
|
|
41
|
+
params: { page: number }
|
|
42
|
+
) => Promise<LoadOptionsResponse>;
|
|
43
|
+
onCreateOption?: (inputValue: string) => Promise<OptionType>;
|
|
44
|
+
isClearable: boolean;
|
|
45
|
+
onClearInput?: () => void;
|
|
46
|
+
helperText?: string;
|
|
47
|
+
errorText?: string;
|
|
48
|
+
error?: boolean;
|
|
49
|
+
menuPlacement?: IMenuPlacement;
|
|
50
|
+
className?: string;
|
|
51
|
+
// minWidth?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const Select: FC<CustomSelectProps> = ({
|
|
55
|
+
label,
|
|
56
|
+
value,
|
|
57
|
+
setValue,
|
|
58
|
+
onCreateOption,
|
|
59
|
+
required,
|
|
60
|
+
disabled,
|
|
61
|
+
options = [],
|
|
62
|
+
loadOptions,
|
|
63
|
+
placeholder = 'Select...',
|
|
64
|
+
isMulti,
|
|
65
|
+
isAsync = false,
|
|
66
|
+
isCreatable = false,
|
|
67
|
+
isClearable = true,
|
|
68
|
+
onClearInput,
|
|
69
|
+
helperText,
|
|
70
|
+
errorText,
|
|
71
|
+
error,
|
|
72
|
+
menuPlacement = 'bottom-left',
|
|
73
|
+
className,
|
|
74
|
+
// minWidth,
|
|
75
|
+
}) => {
|
|
76
|
+
const [inputValue, setInputValue] = useState<string | null>(null);
|
|
77
|
+
const [dropdownVisible, setDropdownVisible] = useState(false);
|
|
78
|
+
const [filteredOptions, setFilteredOptions] = useState<OptionType[]>([]);
|
|
79
|
+
const [page, setPage] = useState(0);
|
|
80
|
+
const [index, setIndex] = useState<number | null>(null);
|
|
81
|
+
const [hasMore, setHasMore] = useState(true);
|
|
82
|
+
|
|
83
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
84
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
85
|
+
|
|
86
|
+
const menuStyle = useMenuPosition({
|
|
87
|
+
isOpen: dropdownVisible,
|
|
88
|
+
placement: menuPlacement,
|
|
89
|
+
containerRef,
|
|
90
|
+
menuRef: dropdownRef,
|
|
91
|
+
withMinWidth: true,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (!isAsync) {
|
|
96
|
+
const filtered = inputValue
|
|
97
|
+
? options.filter(opt =>
|
|
98
|
+
opt.label.toLowerCase().includes(inputValue.toLowerCase())
|
|
99
|
+
)
|
|
100
|
+
: options;
|
|
101
|
+
setFilteredOptions(filtered);
|
|
102
|
+
}
|
|
103
|
+
}, [inputValue, options, isAsync]);
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (isAsync && loadOptions) {
|
|
107
|
+
loadOptions(inputValue ?? '', filteredOptions, { page }).then(
|
|
108
|
+
({ options: newOptions, hasMore }) => {
|
|
109
|
+
setFilteredOptions(prev => [...prev, ...newOptions]);
|
|
110
|
+
setHasMore(hasMore);
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
115
|
+
}, [page]);
|
|
116
|
+
|
|
117
|
+
const handleOptionClick = (option: OptionType) => {
|
|
118
|
+
setValue(option);
|
|
119
|
+
setInputValue(null);
|
|
120
|
+
setTimeout(() => {
|
|
121
|
+
setIndex(null);
|
|
122
|
+
// setDropdownVisible(false);
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const handleInput = (e: ChangeEvent<HTMLInputElement>) => {
|
|
127
|
+
const inputValue = e.target.value;
|
|
128
|
+
setInputValue(
|
|
129
|
+
e.target.defaultValue === value?.label
|
|
130
|
+
? // @ts-ignore
|
|
131
|
+
(e.nativeEvent?.data ?? '')
|
|
132
|
+
: inputValue
|
|
133
|
+
);
|
|
134
|
+
// setFilteredOptions([]);
|
|
135
|
+
setPage(0);
|
|
136
|
+
if (isAsync && loadOptions) {
|
|
137
|
+
loadOptions(inputValue, [], { page: 0 }).then(({ options, hasMore }) => {
|
|
138
|
+
setFilteredOptions(options);
|
|
139
|
+
setHasMore(hasMore);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const handleScroll = (e: UIEvent<HTMLDivElement>) => {
|
|
145
|
+
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
|
|
146
|
+
if (scrollHeight - scrollTop === clientHeight && hasMore) {
|
|
147
|
+
setPage(prev => prev + 1);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const handleCreate = async () => {
|
|
152
|
+
if (onCreateOption && inputValue?.trim()) {
|
|
153
|
+
const newOption = await onCreateOption(inputValue);
|
|
154
|
+
setFilteredOptions(prev => [newOption, ...prev]);
|
|
155
|
+
setValue(newOption);
|
|
156
|
+
setInputValue(null);
|
|
157
|
+
setDropdownVisible(false);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div
|
|
163
|
+
className="customSelect"
|
|
164
|
+
onFocus={() => setDropdownVisible(true)}
|
|
165
|
+
onBlur={() => {
|
|
166
|
+
setDropdownVisible(false);
|
|
167
|
+
setInputValue(null);
|
|
168
|
+
}}
|
|
169
|
+
>
|
|
170
|
+
<InputWrapper
|
|
171
|
+
label={label}
|
|
172
|
+
required={required}
|
|
173
|
+
disabled={disabled}
|
|
174
|
+
// endText={endText}
|
|
175
|
+
// endButton={endButton}
|
|
176
|
+
onClearInput={
|
|
177
|
+
isClearable && value
|
|
178
|
+
? () => {
|
|
179
|
+
setValue(null);
|
|
180
|
+
setInputValue('');
|
|
181
|
+
// setSelectedOption(null);
|
|
182
|
+
}
|
|
183
|
+
: undefined
|
|
184
|
+
}
|
|
185
|
+
helperText={helperText}
|
|
186
|
+
errorText={errorText}
|
|
187
|
+
error={error}
|
|
188
|
+
inputFieldRef={containerRef}
|
|
189
|
+
className={className}
|
|
190
|
+
>
|
|
191
|
+
<input
|
|
192
|
+
value={inputValue ?? value?.label ?? ''}
|
|
193
|
+
onChange={handleInput}
|
|
194
|
+
required={required}
|
|
195
|
+
disabled={disabled}
|
|
196
|
+
// onFocus={e => e.currentTarget.select()}
|
|
197
|
+
// autoFocus={autoFocus}
|
|
198
|
+
placeholder={value?.label || placeholder}
|
|
199
|
+
onKeyDown={e => {
|
|
200
|
+
if (e.key === 'ArrowUp') {
|
|
201
|
+
e.preventDefault();
|
|
202
|
+
if (index === null || index === 0) {
|
|
203
|
+
setIndex(filteredOptions.length - 1);
|
|
204
|
+
} else {
|
|
205
|
+
setIndex(prev => prev! - 1);
|
|
206
|
+
}
|
|
207
|
+
} else if (e.key === 'ArrowDown') {
|
|
208
|
+
e.preventDefault();
|
|
209
|
+
if (index === null || index === filteredOptions.length - 1) {
|
|
210
|
+
setIndex(0);
|
|
211
|
+
} else {
|
|
212
|
+
setIndex(prev => prev! + 1);
|
|
213
|
+
dropdownRef.current?.scrollTo({ top: 20 * ((index ?? 0) + 1) });
|
|
214
|
+
}
|
|
215
|
+
} else if (e.key === 'Enter' && index !== null) {
|
|
216
|
+
e.preventDefault();
|
|
217
|
+
e.currentTarget.blur();
|
|
218
|
+
handleOptionClick(filteredOptions[index]);
|
|
219
|
+
} else if (e.key === 'Escape') {
|
|
220
|
+
e.preventDefault();
|
|
221
|
+
e.currentTarget.blur();
|
|
222
|
+
}
|
|
223
|
+
}}
|
|
224
|
+
/>
|
|
225
|
+
</InputWrapper>
|
|
226
|
+
|
|
227
|
+
{dropdownVisible
|
|
228
|
+
? createPortal(
|
|
229
|
+
<div
|
|
230
|
+
ref={dropdownRef}
|
|
231
|
+
className="select-dropdown"
|
|
232
|
+
onScroll={handleScroll}
|
|
233
|
+
style={menuStyle}
|
|
234
|
+
>
|
|
235
|
+
{filteredOptions.map((option, i) => (
|
|
236
|
+
<div
|
|
237
|
+
key={i}
|
|
238
|
+
className={clsx('option', { hover: index === i })}
|
|
239
|
+
onMouseDown={() => handleOptionClick(option)}
|
|
240
|
+
>
|
|
241
|
+
{option.label}
|
|
242
|
+
</div>
|
|
243
|
+
))}
|
|
244
|
+
{!filteredOptions.length && <div className="option">No options</div>}
|
|
245
|
+
{isCreatable &&
|
|
246
|
+
inputValue &&
|
|
247
|
+
!filteredOptions.some(opt => opt.label === inputValue) && (
|
|
248
|
+
<div className="option createOption" onClick={handleCreate}>
|
|
249
|
+
{`Create ${inputValue}`}
|
|
250
|
+
</div>
|
|
251
|
+
)}
|
|
252
|
+
</div>,
|
|
253
|
+
rootDir
|
|
254
|
+
)
|
|
255
|
+
: null}
|
|
256
|
+
</div>
|
|
257
|
+
);
|
|
258
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
.customSelect {
|
|
2
|
+
position: relative;
|
|
3
|
+
width: 300px;
|
|
4
|
+
font-family: sans-serif;
|
|
5
|
+
|
|
6
|
+
.inputWrapper {
|
|
7
|
+
input {
|
|
8
|
+
width: 100%;
|
|
9
|
+
padding: 8px 12px;
|
|
10
|
+
border: 1px solid #ccc;
|
|
11
|
+
border-radius: 4px;
|
|
12
|
+
font-size: 14px;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.select-dropdown {
|
|
18
|
+
position: absolute;
|
|
19
|
+
z-index: 1000;
|
|
20
|
+
// width: 100%;
|
|
21
|
+
min-width: 200px;
|
|
22
|
+
max-height: 200px;
|
|
23
|
+
overflow-y: auto;
|
|
24
|
+
background: #fff;
|
|
25
|
+
border: var(--border);
|
|
26
|
+
border-radius: 8px;
|
|
27
|
+
// border-radius: 0 0 8px 8px;
|
|
28
|
+
margin-top: 2px;
|
|
29
|
+
.option {
|
|
30
|
+
padding: 8px 12px;
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
|
|
33
|
+
&.hover,
|
|
34
|
+
&:hover {
|
|
35
|
+
background-color: #f5f5f5;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
.createOption {
|
|
39
|
+
font-weight: bold;
|
|
40
|
+
color: #007bff;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -103,31 +103,98 @@ const setAbsolutePosition = (
|
|
|
103
103
|
) => {
|
|
104
104
|
const containerRect = containerEl.getBoundingClientRect();
|
|
105
105
|
const tooltipRect = targetEl.getBoundingClientRect();
|
|
106
|
+
const margin = 16; // Distance from container
|
|
107
|
+
const screenPadding = 8; // Minimum distance from screen edges
|
|
106
108
|
|
|
107
109
|
if (position === 'bottom' || position === 'top') {
|
|
108
|
-
const bottom = Math.floor(containerRect.bottom +
|
|
109
|
-
const top = Math.floor(containerRect.top - tooltipRect.height -
|
|
110
|
-
|
|
110
|
+
const bottom = Math.floor(containerRect.bottom + margin);
|
|
111
|
+
const top = Math.floor(containerRect.top - tooltipRect.height - margin);
|
|
112
|
+
let leftCenter = Math.floor(
|
|
111
113
|
containerRect.left + containerRect.width / 2 - tooltipRect.width / 2
|
|
112
114
|
);
|
|
115
|
+
|
|
116
|
+
// Check if tooltip overflows horizontally and adjust
|
|
117
|
+
if (leftCenter < screenPadding) {
|
|
118
|
+
targetEl.style.width = `${tooltipRect.width + leftCenter}px`;
|
|
119
|
+
leftCenter = screenPadding;
|
|
120
|
+
} else if (leftCenter + tooltipRect.width > window.innerWidth - screenPadding) {
|
|
121
|
+
leftCenter = window.innerWidth - tooltipRect.width - screenPadding;
|
|
122
|
+
}
|
|
123
|
+
|
|
113
124
|
const reverse =
|
|
114
125
|
position === 'top' ? top < 0 : bottom + tooltipRect.height > window.innerHeight;
|
|
115
126
|
|
|
116
127
|
setNewPosition(reverse ? (position === 'bottom' ? 'top' : 'bottom') : '');
|
|
117
128
|
|
|
118
|
-
|
|
119
|
-
position === 'bottom' ? (reverse ? top : bottom) : reverse ? bottom : top
|
|
120
|
-
|
|
129
|
+
let finalTop =
|
|
130
|
+
position === 'bottom' ? (reverse ? top : bottom) : reverse ? bottom : top;
|
|
131
|
+
|
|
132
|
+
// Ensure tooltip doesn't go above screen
|
|
133
|
+
if (finalTop < screenPadding) {
|
|
134
|
+
finalTop = screenPadding;
|
|
135
|
+
}
|
|
136
|
+
// Ensure tooltip doesn't go below screen
|
|
137
|
+
if (finalTop + tooltipRect.height > window.innerHeight - screenPadding) {
|
|
138
|
+
finalTop = window.innerHeight - tooltipRect.height - screenPadding;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
targetEl.style.top = `${finalTop}px`;
|
|
121
142
|
targetEl.style.left = `${leftCenter}px`;
|
|
122
143
|
} else if (position === 'right') {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
144
|
+
let finalTop = Math.floor(
|
|
145
|
+
containerRect.top + containerRect.height / 2 - tooltipRect.height / 2
|
|
146
|
+
);
|
|
147
|
+
let finalLeft = Math.floor(containerRect.right + margin);
|
|
148
|
+
|
|
149
|
+
// Check if tooltip overflows vertically and adjust
|
|
150
|
+
if (finalTop < screenPadding) {
|
|
151
|
+
finalTop = screenPadding;
|
|
152
|
+
} else if (finalTop + tooltipRect.height > window.innerHeight - screenPadding) {
|
|
153
|
+
finalTop = window.innerHeight - tooltipRect.height - screenPadding;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check if tooltip overflows to the right and flip to left
|
|
157
|
+
if (finalLeft + tooltipRect.width > window.innerWidth - screenPadding) {
|
|
158
|
+
finalLeft = Math.floor(containerRect.left - tooltipRect.width - margin);
|
|
159
|
+
setNewPosition('left');
|
|
160
|
+
|
|
161
|
+
// If it still overflows on the left, position it at the edge
|
|
162
|
+
if (finalLeft < screenPadding) {
|
|
163
|
+
finalLeft = screenPadding;
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
setNewPosition('');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
targetEl.style.top = `${finalTop}px`;
|
|
170
|
+
targetEl.style.left = `${finalLeft}px`;
|
|
127
171
|
} else if (position === 'left') {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
172
|
+
let finalTop = Math.floor(
|
|
173
|
+
containerRect.top + containerRect.height / 2 - tooltipRect.height / 2
|
|
174
|
+
);
|
|
175
|
+
let finalLeft = Math.floor(containerRect.left - tooltipRect.width - margin);
|
|
176
|
+
|
|
177
|
+
// Check if tooltip overflows vertically and adjust
|
|
178
|
+
if (finalTop < screenPadding) {
|
|
179
|
+
finalTop = screenPadding;
|
|
180
|
+
} else if (finalTop + tooltipRect.height > window.innerHeight - screenPadding) {
|
|
181
|
+
finalTop = window.innerHeight - tooltipRect.height - screenPadding;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Check if tooltip overflows to the left and flip to right
|
|
185
|
+
if (finalLeft < screenPadding) {
|
|
186
|
+
finalLeft = Math.floor(containerRect.right + margin);
|
|
187
|
+
setNewPosition('right');
|
|
188
|
+
|
|
189
|
+
// If it still overflows on the right, position it at the edge
|
|
190
|
+
if (finalLeft + tooltipRect.width > window.innerWidth - screenPadding) {
|
|
191
|
+
finalLeft = window.innerWidth - tooltipRect.width - screenPadding;
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
setNewPosition('');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
targetEl.style.top = `${finalTop}px`;
|
|
198
|
+
targetEl.style.left = `${finalLeft}px`;
|
|
132
199
|
}
|
|
133
200
|
};
|
|
@@ -61,16 +61,18 @@ export const FormWrapper: FC<IFormWrapper> = ({
|
|
|
61
61
|
variant={e.variant}
|
|
62
62
|
color={e.color}
|
|
63
63
|
onClick={e.onClick}
|
|
64
|
+
type="button"
|
|
64
65
|
/>
|
|
65
66
|
)
|
|
66
67
|
)}
|
|
67
68
|
{!noAccess && (
|
|
68
69
|
<Button
|
|
69
70
|
label={submitButton.label ?? t('Save')}
|
|
70
|
-
icon={submitButton.icon}
|
|
71
|
+
// icon={submitButton.icon}
|
|
71
72
|
disabled={isLoading || submitButton.disabled}
|
|
72
73
|
variant={submitButton.variant}
|
|
73
74
|
color={submitButton.color}
|
|
75
|
+
type="submit"
|
|
74
76
|
/>
|
|
75
77
|
)}
|
|
76
78
|
</div>
|
|
@@ -28,6 +28,7 @@ export interface IPageLayoutProps {
|
|
|
28
28
|
noAccess?: boolean;
|
|
29
29
|
isInitiallyLoading?: boolean;
|
|
30
30
|
isLoading?: boolean;
|
|
31
|
+
hideFooter?: boolean;
|
|
31
32
|
children?: ReactNode;
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -40,6 +41,7 @@ export const PageLayout: FC<IPageLayoutProps> = ({
|
|
|
40
41
|
noAccess,
|
|
41
42
|
isLoading,
|
|
42
43
|
isInitiallyLoading,
|
|
44
|
+
hideFooter,
|
|
43
45
|
children,
|
|
44
46
|
}) => {
|
|
45
47
|
const [isMoreActionsOpen, setIsMoreActionsOpen] = useState(false);
|
|
@@ -56,9 +58,11 @@ export const PageLayout: FC<IPageLayoutProps> = ({
|
|
|
56
58
|
return null;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
<Loader isLoading isFullPage
|
|
61
|
-
|
|
61
|
+
if (isInitiallyLoading) {
|
|
62
|
+
return <Loader isLoading isFullPage />;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
62
66
|
<Loader isLoading={!!isLoading}>
|
|
63
67
|
<div className="page-container">
|
|
64
68
|
{breadcrumbs && (
|
|
@@ -138,9 +142,13 @@ export const PageLayout: FC<IPageLayoutProps> = ({
|
|
|
138
142
|
</div>
|
|
139
143
|
)}
|
|
140
144
|
<div className="page-content">{children}</div>
|
|
141
|
-
|
|
142
|
-
<
|
|
143
|
-
|
|
145
|
+
{!hideFooter && (
|
|
146
|
+
<footer className="page-footer">
|
|
147
|
+
<p className="page-footer-text">
|
|
148
|
+
<Trans i18nKey="FooterText" />
|
|
149
|
+
</p>
|
|
150
|
+
</footer>
|
|
151
|
+
)}
|
|
144
152
|
</div>
|
|
145
153
|
</Loader>
|
|
146
154
|
);
|
|
@@ -32,14 +32,6 @@
|
|
|
32
32
|
max-height: calc(100vh - var(--header-height));
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
.page-content {
|
|
36
|
-
display: flex;
|
|
37
|
-
flex-direction: column;
|
|
38
|
-
padding: 1.5rem 1.5rem 1rem 1.5rem;
|
|
39
|
-
flex: 1;
|
|
40
|
-
overflow-y: auto;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
35
|
.page-header {
|
|
44
36
|
display: flex;
|
|
45
37
|
justify-content: space-between;
|
|
@@ -57,9 +49,50 @@
|
|
|
57
49
|
}
|
|
58
50
|
}
|
|
59
51
|
|
|
52
|
+
.page-content {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
padding: 1.5rem 1.5rem 1rem 1.5rem;
|
|
56
|
+
flex: 1;
|
|
57
|
+
overflow-y: auto;
|
|
58
|
+
}
|
|
59
|
+
.scrollable-content {
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
flex: 1;
|
|
63
|
+
overflow-y: auto;
|
|
64
|
+
margin-right: -1.5rem;
|
|
65
|
+
padding-right: 1.5rem;
|
|
66
|
+
padding-bottom: 1rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
.page-footer {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
justify-content: center;
|
|
73
|
+
gap: 16px;
|
|
74
|
+
padding: 16px 24px;
|
|
75
|
+
margin: 0 -1.5rem -1rem;
|
|
76
|
+
&.footer-actions {
|
|
77
|
+
box-shadow: var(--container-shadow);
|
|
78
|
+
z-index: 2;
|
|
79
|
+
}
|
|
80
|
+
.page-footer-text {
|
|
81
|
+
text-align: center;
|
|
82
|
+
font-size: 12px;
|
|
83
|
+
white-space: pre-line;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.page-footer-text {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
justify-content: center;
|
|
91
|
+
gap: 16px;
|
|
92
|
+
padding: 16px 24px;
|
|
93
|
+
.page-footer-text {
|
|
94
|
+
text-align: center;
|
|
95
|
+
font-size: 12px;
|
|
96
|
+
white-space: pre-line;
|
|
97
|
+
}
|
|
65
98
|
}
|