@scality/core-ui 0.135.0 → 0.137.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/searchinput/SearchInput.component.d.ts +0 -1
- package/dist/components/searchinput/SearchInput.component.d.ts.map +1 -1
- package/dist/components/searchinput/SearchInput.component.js +1 -1
- package/dist/components/selectv2/Selectv2.component.d.ts +1 -1
- package/dist/components/selectv2/Selectv2.component.d.ts.map +1 -1
- package/dist/components/selectv2/Selectv2.component.js +5 -9
- package/dist/components/tablev2/Search.d.ts +1 -1
- package/dist/components/tablev2/Search.d.ts.map +1 -1
- package/dist/components/tablev2/Search.js +1 -1
- package/dist/components/tablev2/TableCommon.d.ts +1 -1
- package/dist/components/tablev2/TableCommon.js +3 -3
- package/dist/components/tablev2/Tablestyle.d.ts +1 -1
- package/dist/components/tablev2/Tablestyle.d.ts.map +1 -1
- package/dist/components/tablev2/Tablev2.component.d.ts +5 -1
- package/dist/components/tablev2/Tablev2.component.d.ts.map +1 -1
- package/dist/components/tablev2/Tablev2.component.js +6 -0
- package/dist/components/tablev2/useSyncedScroll.d.ts +2 -1
- package/dist/components/tablev2/useSyncedScroll.d.ts.map +1 -1
- package/dist/components/tablev2/useSyncedScroll.js +17 -19
- package/dist/components/tabsv2/StyledTabs.d.ts +1 -1
- package/dist/components/tabsv2/StyledTabs.d.ts.map +1 -1
- package/dist/components/tabsv2/Tabsv2.component.js +5 -1
- package/dist/components/toggle/Toggle.component.d.ts +1 -1
- package/dist/components/toggle/Toggle.component.d.ts.map +1 -1
- package/dist/components/toggle/Toggle.component.js +8 -11
- package/dist/organisms/attachments/AttachmentConfirmationModal.d.ts +4 -3
- package/dist/organisms/attachments/AttachmentConfirmationModal.d.ts.map +1 -1
- package/dist/organisms/attachments/AttachmentConfirmationModal.js +1 -0
- package/dist/organisms/attachments/AttachmentTable.d.ts +10 -10
- package/dist/organisms/attachments/AttachmentTable.d.ts.map +1 -1
- package/dist/organisms/attachments/AttachmentTable.js +2 -2
- package/dist/organisms/attachments/AttachmentTypes.d.ts +4 -3
- package/dist/organisms/attachments/AttachmentTypes.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/lib/components/searchinput/SearchInput.component.tsx +0 -2
- package/src/lib/components/searchinput/SearchInput.test.tsx +88 -0
- package/src/lib/components/selectv2/Selectv2.component.tsx +7 -11
- package/src/lib/components/selectv2/selectv2.test.tsx +190 -200
- package/src/lib/components/tablev2/Search.tsx +1 -2
- package/src/lib/components/tablev2/TableCommon.tsx +5 -5
- package/src/lib/components/tablev2/Tablestyle.tsx +1 -1
- package/src/lib/components/tablev2/Tablev2.component.tsx +14 -0
- package/src/lib/components/tablev2/useSyncedScroll.ts +22 -24
- package/src/lib/components/tabsv2/StyledTabs.ts +1 -1
- package/src/lib/components/tabsv2/Tabsv2.component.tsx +1 -1
- package/src/lib/components/toggle/Toggle.component.tsx +9 -12
- package/src/lib/components/toggle/Toggle.test.tsx +56 -0
- package/src/lib/organisms/attachments/AttachmentConfirmationModal.tsx +11 -5
- package/src/lib/organisms/attachments/AttachmentTable.tsx +53 -31
- package/src/lib/organisms/attachments/AttachmentTypes.ts +10 -3
- package/stories/SearchInput/searchinput.guideline.mdx +20 -0
- package/stories/{searchinput.stories.tsx → SearchInput/searchinput.stories.tsx} +13 -20
- package/stories/Select/selectv2.stories.tsx +23 -5
- package/stories/Toggle/toggle.guideline.mdx +20 -0
- package/stories/{toggle.stories.tsx → Toggle/toggle.stories.tsx} +17 -10
|
@@ -44,14 +44,14 @@ export const VirtualizedRows = <
|
|
|
44
44
|
listRef,
|
|
45
45
|
itemKey,
|
|
46
46
|
}: VirtualizedRowsType<DATA_ROW>) => (
|
|
47
|
-
<AutoSizer>
|
|
48
|
-
{({ height
|
|
47
|
+
<AutoSizer disableWidth>
|
|
48
|
+
{({ height }) => {
|
|
49
49
|
return (
|
|
50
50
|
<List
|
|
51
|
-
height={height}
|
|
51
|
+
height={height - 1}
|
|
52
52
|
itemCount={rows.length} // how many items we are going to render
|
|
53
53
|
itemSize={convertRemToPixels(tableRowHeight[rowHeight])} // height of each row in pixel
|
|
54
|
-
width={
|
|
54
|
+
width={'100%'}
|
|
55
55
|
itemKey={itemKey}
|
|
56
56
|
itemData={rows}
|
|
57
57
|
ref={listRef}
|
|
@@ -81,7 +81,7 @@ export const VirtualizedRows = <
|
|
|
81
81
|
);
|
|
82
82
|
|
|
83
83
|
export const useTableScrollbar = () => {
|
|
84
|
-
const
|
|
84
|
+
const { hasScrollbar, setHasScrollbar } = useTableContext();
|
|
85
85
|
const [scrollBarWidth, setScrollBarWidth] = useState(0);
|
|
86
86
|
|
|
87
87
|
const handleScrollbarWidth = useCallback((node) => {
|
|
@@ -105,6 +105,10 @@ type TableContextType<
|
|
|
105
105
|
en: { singular: string; plural: string };
|
|
106
106
|
fr?: { singular: string; plural: string };
|
|
107
107
|
};
|
|
108
|
+
syncScrollListener: ((event: Event) => void) | null;
|
|
109
|
+
setSyncScrollListener: (listener: (event: Event) => void) => void;
|
|
110
|
+
setHasScrollbar: React.Dispatch<React.SetStateAction<boolean>>;
|
|
111
|
+
hasScrollbar?: boolean;
|
|
108
112
|
};
|
|
109
113
|
const TableContext = React.createContext<TableContextType | null>(null);
|
|
110
114
|
|
|
@@ -215,6 +219,12 @@ function Table<
|
|
|
215
219
|
|
|
216
220
|
const [rowHeight, setRowHeight] = React.useState<TableHeightKeyType>('h40');
|
|
217
221
|
|
|
222
|
+
const [syncScrollListener, setSyncScrollListener] = React.useState<
|
|
223
|
+
((event: Event) => void) | null
|
|
224
|
+
>(null);
|
|
225
|
+
|
|
226
|
+
const [hasScrollbar, setHasScrollbar] = React.useState<boolean>(false);
|
|
227
|
+
|
|
218
228
|
const {
|
|
219
229
|
headerGroups,
|
|
220
230
|
rows,
|
|
@@ -310,6 +320,10 @@ function Table<
|
|
|
310
320
|
toggleAllRowsSelected,
|
|
311
321
|
status,
|
|
312
322
|
entityName,
|
|
323
|
+
syncScrollListener,
|
|
324
|
+
setSyncScrollListener,
|
|
325
|
+
setHasScrollbar,
|
|
326
|
+
hasScrollbar,
|
|
313
327
|
};
|
|
314
328
|
return (
|
|
315
329
|
<TableContext.Provider
|
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import { useEffect, useState, useCallback } from 'react';
|
|
1
|
+
import { useEffect, useState, useCallback, useRef } from 'react';
|
|
2
2
|
import { Row } from 'react-table';
|
|
3
3
|
import { FixedSizeList } from 'react-window';
|
|
4
|
+
import { useTableContext } from './Tablev2.component';
|
|
4
5
|
|
|
5
6
|
export default function useSyncedScroll<
|
|
6
7
|
DATA_ROW extends Record<string, unknown> = Record<string, unknown>,
|
|
7
8
|
>(): {
|
|
8
9
|
headerRef: (element: HTMLDivElement) => void;
|
|
9
|
-
bodyRef:
|
|
10
|
+
bodyRef: React.RefObject<FixedSizeList<Row<DATA_ROW>[]>>;
|
|
10
11
|
} {
|
|
11
|
-
const
|
|
12
|
-
useState<((event: Event) => void) | null>(null);
|
|
13
|
-
const [tableBody, setTableBody] =
|
|
14
|
-
useState<FixedSizeList<Row<DATA_ROW>[]> | null>(null);
|
|
12
|
+
const { syncScrollListener, setSyncScrollListener } = useTableContext();
|
|
15
13
|
|
|
16
14
|
const headerRef = useCallback(
|
|
17
15
|
(element: HTMLDivElement) => {
|
|
@@ -24,41 +22,41 @@ export default function useSyncedScroll<
|
|
|
24
22
|
});
|
|
25
23
|
}
|
|
26
24
|
};
|
|
27
|
-
if (!
|
|
28
|
-
|
|
25
|
+
if (!syncScrollListener) {
|
|
26
|
+
setSyncScrollListener(() => {
|
|
29
27
|
return callback;
|
|
30
28
|
});
|
|
31
29
|
}
|
|
32
30
|
}
|
|
33
31
|
},
|
|
34
|
-
[
|
|
32
|
+
[syncScrollListener],
|
|
35
33
|
);
|
|
36
34
|
|
|
37
|
-
const bodyRef =
|
|
38
|
-
setTableBody(tableBody);
|
|
39
|
-
}, []);
|
|
35
|
+
const bodyRef = useRef<FixedSizeList<Row<DATA_ROW>[]> | null>(null);
|
|
40
36
|
|
|
41
37
|
useEffect(() => {
|
|
42
|
-
if (
|
|
38
|
+
if (bodyRef.current && syncScrollListener) {
|
|
43
39
|
/*
|
|
44
40
|
We intentionally use _outerRef prop here despite the fact that it is
|
|
45
41
|
internal use only and not typed, as it is the only way for us to access to the scrollable element
|
|
46
42
|
*/
|
|
47
43
|
//@ts-expect-error
|
|
48
|
-
(
|
|
44
|
+
(bodyRef.current._outerRef as HTMLDivElement).addEventListener(
|
|
49
45
|
'scroll',
|
|
50
|
-
|
|
46
|
+
syncScrollListener,
|
|
51
47
|
);
|
|
52
|
-
|
|
53
|
-
return () => {
|
|
54
|
-
//@ts-expect-error
|
|
55
|
-
if (tableBody && tableBody._outerRef) {
|
|
56
|
-
//@ts-expect-error
|
|
57
|
-
tableBody._outerRef.removeEventListener('scroll', listener);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
48
|
}
|
|
61
|
-
|
|
49
|
+
return () => {
|
|
50
|
+
//@ts-expect-error
|
|
51
|
+
if (bodyRef.current && bodyRef.current._outerRef) {
|
|
52
|
+
//@ts-expect-error
|
|
53
|
+
bodyRef.current._outerRef.removeEventListener(
|
|
54
|
+
'scroll',
|
|
55
|
+
syncScrollListener,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}, [bodyRef.current, syncScrollListener]);
|
|
62
60
|
|
|
63
61
|
return { headerRef, bodyRef };
|
|
64
62
|
}
|
|
@@ -185,8 +185,8 @@ function Tabs({
|
|
|
185
185
|
});
|
|
186
186
|
return (
|
|
187
187
|
<TabsContext.Provider value={true}>
|
|
188
|
-
{/*@ts-expect-error containerType is not yet a valid prop for react */}
|
|
189
188
|
<TabsContainer
|
|
189
|
+
// @ts-expect-error containerType is not yet a valid prop for react
|
|
190
190
|
style={{ containerType: 'size' }}
|
|
191
191
|
className={['sc-tabs', className].join(' ')}
|
|
192
192
|
tabLineColor={tabLineColor}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { ChangeEvent, InputHTMLAttributes, useRef } from 'react';
|
|
2
2
|
import styled, { css } from 'styled-components';
|
|
3
3
|
|
|
4
|
-
import { spacing } from '../../style/theme';
|
|
5
4
|
import { LABEL_PREFIX, useFieldContext } from '../form/Form.component';
|
|
6
|
-
import { Stack } from '../../spacing';
|
|
5
|
+
import { Stack, spacing } from '../../spacing';
|
|
7
6
|
import { Text } from '../text/Text.component';
|
|
8
7
|
|
|
9
|
-
type Props = InputHTMLAttributes<HTMLInputElement> & {
|
|
8
|
+
export type Props = InputHTMLAttributes<HTMLInputElement> & {
|
|
10
9
|
toggle: boolean;
|
|
11
10
|
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
|
12
11
|
label?: string;
|
|
@@ -20,8 +19,7 @@ const ToggleContainer = styled.span<{ disabled?: boolean }>`
|
|
|
20
19
|
`;
|
|
21
20
|
const Switch = styled.label<{ disabled?: boolean }>`
|
|
22
21
|
position: relative;
|
|
23
|
-
width: ${spacing.
|
|
24
|
-
height: ${spacing.sp14};
|
|
22
|
+
width: ${spacing.r24};
|
|
25
23
|
align-self: center;
|
|
26
24
|
${(props) => {
|
|
27
25
|
return css`
|
|
@@ -39,23 +37,22 @@ const Slider = styled.div<{ toggle?: boolean }>`
|
|
|
39
37
|
width: 100%;
|
|
40
38
|
height: 1rem;
|
|
41
39
|
background-color: ${(props) => props.theme.backgroundLevel1};
|
|
42
|
-
border: ${spacing.
|
|
40
|
+
border: ${spacing.r1} solid
|
|
43
41
|
${(props) => props.theme[props.toggle ? 'selectedActive' : 'infoPrimary']};
|
|
44
|
-
border-radius: ${spacing.
|
|
42
|
+
border-radius: ${spacing.r8};
|
|
45
43
|
transition: 0.4s;
|
|
46
|
-
|
|
44
|
+
|
|
47
45
|
&:before {
|
|
48
46
|
border-radius: 100%;
|
|
49
47
|
position: absolute;
|
|
50
48
|
content: '';
|
|
51
|
-
height: ${spacing.
|
|
52
|
-
width: ${spacing.
|
|
49
|
+
height: ${spacing.r10};
|
|
50
|
+
width: ${spacing.r10};
|
|
53
51
|
left: 3px;
|
|
54
52
|
top: 3.5px;
|
|
55
53
|
background-color: ${(props) =>
|
|
56
54
|
props.theme[props.toggle ? 'textSecondary' : 'textPrimary']};
|
|
57
55
|
transition: 0.4s;
|
|
58
|
-
-moz-transform: rotate(0.02deg);
|
|
59
56
|
}
|
|
60
57
|
`;
|
|
61
58
|
const ToggleInput = styled.input`
|
|
@@ -63,7 +60,7 @@ const ToggleInput = styled.input`
|
|
|
63
60
|
background-color: ${(props) => props.theme.selectedActive};
|
|
64
61
|
}
|
|
65
62
|
&:checked + ${Slider}:before {
|
|
66
|
-
transform: translateX(${spacing.
|
|
63
|
+
transform: translateX(${spacing.r10});
|
|
67
64
|
}
|
|
68
65
|
display: none;
|
|
69
66
|
`;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { Props, Toggle } from './Toggle.component';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
|
|
6
|
+
describe('Toggle', () => {
|
|
7
|
+
const selectors = {
|
|
8
|
+
toggle: () => screen.getByRole('checkbox'),
|
|
9
|
+
label: (text: string | RegExp) => screen.getByText(text),
|
|
10
|
+
};
|
|
11
|
+
const RenderToggle = (props: Omit<Props, 'onChange' | 'toggle'>) => {
|
|
12
|
+
const [toggle, setToggle] = useState<boolean>(false);
|
|
13
|
+
const handleClick = () => {
|
|
14
|
+
setToggle(!toggle);
|
|
15
|
+
};
|
|
16
|
+
return <Toggle {...props} toggle={toggle} onChange={handleClick} />;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
it('should render the Toggle component with label', () => {
|
|
20
|
+
render(<RenderToggle label="Test" />);
|
|
21
|
+
const toggle = selectors.toggle();
|
|
22
|
+
expect(toggle).toBeInTheDocument();
|
|
23
|
+
const label = selectors.label(/Test/);
|
|
24
|
+
expect(label).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should toggle the switch on click on checkbox or label', () => {
|
|
28
|
+
render(<RenderToggle label="Test"></RenderToggle>);
|
|
29
|
+
const toggle = selectors.toggle();
|
|
30
|
+
userEvent.click(toggle);
|
|
31
|
+
expect(toggle).toBeChecked();
|
|
32
|
+
const label = selectors.label('Test');
|
|
33
|
+
userEvent.click(label);
|
|
34
|
+
expect(toggle).not.toBeChecked();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should toggle the switch when pressing the space key or enter key', () => {
|
|
38
|
+
render(<RenderToggle />);
|
|
39
|
+
const toggle = selectors.toggle();
|
|
40
|
+
userEvent.tab();
|
|
41
|
+
userEvent.keyboard('{space}');
|
|
42
|
+
expect(toggle).toBeChecked();
|
|
43
|
+
userEvent.keyboard('{enter}');
|
|
44
|
+
expect(toggle).not.toBeChecked();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should not toggle the switch when disabled', () => {
|
|
48
|
+
render(<RenderToggle disabled={true} />);
|
|
49
|
+
const toggle = selectors.toggle();
|
|
50
|
+
// toBeDisabled is not working for some reason
|
|
51
|
+
userEvent.tab();
|
|
52
|
+
expect(toggle).not.toHaveFocus();
|
|
53
|
+
userEvent.click(toggle);
|
|
54
|
+
expect(toggle).not.toBeChecked();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -10,7 +10,11 @@ import { Icon, LargerText, Modal, SecondaryText, Stack, Wrap } from '../..';
|
|
|
10
10
|
type AttachmentStatus = 'Waiting for confirmation' | 'Error' | 'Success';
|
|
11
11
|
|
|
12
12
|
//The entity is the "thing" you want to attach to the resource, sorry about the naming :(
|
|
13
|
-
export function AttachmentConfirmationModal<
|
|
13
|
+
export function AttachmentConfirmationModal<
|
|
14
|
+
ENTITY_TYPE,
|
|
15
|
+
RESOURCE_TYPE,
|
|
16
|
+
ENTITY extends Record<string, unknown> = Record<string, unknown>,
|
|
17
|
+
>({
|
|
14
18
|
attachmentOperations,
|
|
15
19
|
getAttachmentMutationOptions,
|
|
16
20
|
resourceType,
|
|
@@ -21,7 +25,7 @@ export function AttachmentConfirmationModal<ENTITY_TYPE, RESOURCE_TYPE>({
|
|
|
21
25
|
onCancel,
|
|
22
26
|
onExit,
|
|
23
27
|
}: {
|
|
24
|
-
attachmentOperations: AttachmentOperation<ENTITY_TYPE>[];
|
|
28
|
+
attachmentOperations: AttachmentOperation<ENTITY_TYPE, ENTITY>[];
|
|
25
29
|
getAttachmentMutationOptions: () => UseMutationOptions<
|
|
26
30
|
unknown,
|
|
27
31
|
unknown,
|
|
@@ -30,6 +34,7 @@ export function AttachmentConfirmationModal<ENTITY_TYPE, RESOURCE_TYPE>({
|
|
|
30
34
|
type: ENTITY_TYPE;
|
|
31
35
|
entityName: string;
|
|
32
36
|
id: string;
|
|
37
|
+
completeEntity?: ENTITY;
|
|
33
38
|
}
|
|
34
39
|
>;
|
|
35
40
|
resourceName: string;
|
|
@@ -39,8 +44,8 @@ export function AttachmentConfirmationModal<ENTITY_TYPE, RESOURCE_TYPE>({
|
|
|
39
44
|
cancelButtonDisabled?: boolean;
|
|
40
45
|
onCancel?: () => void;
|
|
41
46
|
onExit?: (
|
|
42
|
-
successfullOperations: AttachmentOperation<ENTITY_TYPE>[],
|
|
43
|
-
failedOperations: AttachmentOperation<ENTITY_TYPE>[],
|
|
47
|
+
successfullOperations: AttachmentOperation<ENTITY_TYPE, ENTITY>[],
|
|
48
|
+
failedOperations: AttachmentOperation<ENTITY_TYPE, ENTITY>[],
|
|
44
49
|
) => void;
|
|
45
50
|
}) {
|
|
46
51
|
const history = useHistory();
|
|
@@ -70,12 +75,13 @@ export function AttachmentConfirmationModal<ENTITY_TYPE, RESOURCE_TYPE>({
|
|
|
70
75
|
entityName: string;
|
|
71
76
|
id: string;
|
|
72
77
|
}[] = attachmentOperations.map(
|
|
73
|
-
(attachmentOperation: AttachmentOperation<ENTITY_TYPE>) => {
|
|
78
|
+
(attachmentOperation: AttachmentOperation<ENTITY_TYPE, ENTITY>) => {
|
|
74
79
|
return {
|
|
75
80
|
action: attachmentOperation.action,
|
|
76
81
|
type: attachmentOperation.entity.type,
|
|
77
82
|
entityName: attachmentOperation.entity.name,
|
|
78
83
|
id: attachmentOperation.entity.id,
|
|
84
|
+
completeEntity: attachmentOperation.entity.completeEntity,
|
|
79
85
|
};
|
|
80
86
|
},
|
|
81
87
|
);
|
|
@@ -38,27 +38,36 @@ type AttachableEntityWithPendingStatus<ENTITY_TYPE> = {
|
|
|
38
38
|
isPending?: boolean;
|
|
39
39
|
} & AttachableEntity<ENTITY_TYPE>;
|
|
40
40
|
|
|
41
|
-
export type AttachmentTableProps<
|
|
42
|
-
|
|
41
|
+
export type AttachmentTableProps<
|
|
42
|
+
ENTITY_TYPE,
|
|
43
|
+
ENTITY extends Record<string, unknown> = Record<string, unknown>,
|
|
44
|
+
> = {
|
|
45
|
+
initiallyAttachedEntities: AttachableEntity<ENTITY_TYPE, ENTITY>[];
|
|
43
46
|
initiallyAttachedEntitiesStatus: 'idle' | 'loading' | 'success' | 'error';
|
|
44
|
-
initialAttachmentOperations: AttachmentOperation<ENTITY_TYPE>[];
|
|
47
|
+
initialAttachmentOperations: AttachmentOperation<ENTITY_TYPE, ENTITY>[];
|
|
45
48
|
entityName: { plural: string; singular: string };
|
|
46
49
|
getNameQuery?: (
|
|
47
|
-
entity: AttachableEntity<ENTITY_TYPE>,
|
|
50
|
+
entity: AttachableEntity<ENTITY_TYPE, ENTITY>,
|
|
48
51
|
) => UseQueryOptions<unknown, unknown, string>;
|
|
49
52
|
searchEntityPlaceholder: string;
|
|
50
53
|
onAttachmentsOperationsChanged: (
|
|
51
|
-
attachmentOperations: AttachmentOperation<ENTITY_TYPE>[],
|
|
54
|
+
attachmentOperations: AttachmentOperation<ENTITY_TYPE, ENTITY>[],
|
|
52
55
|
) => void;
|
|
53
56
|
filteredEntities:
|
|
54
57
|
| { status: 'idle' }
|
|
55
58
|
| {
|
|
56
59
|
status: 'loading' | 'error';
|
|
57
|
-
data?: {
|
|
60
|
+
data?: {
|
|
61
|
+
number: number;
|
|
62
|
+
entities: AttachableEntity<ENTITY_TYPE, ENTITY>[];
|
|
63
|
+
};
|
|
58
64
|
}
|
|
59
65
|
| {
|
|
60
66
|
status: 'success';
|
|
61
|
-
data: {
|
|
67
|
+
data: {
|
|
68
|
+
number: number;
|
|
69
|
+
entities: AttachableEntity<ENTITY_TYPE, ENTITY>[];
|
|
70
|
+
};
|
|
62
71
|
};
|
|
63
72
|
onEntitySearchChange: (search?: string) => void;
|
|
64
73
|
};
|
|
@@ -131,31 +140,34 @@ const PrivateAttachmentContext = createContext<{
|
|
|
131
140
|
setResetAttachementTable: Dispatch<
|
|
132
141
|
SetStateAction<
|
|
133
142
|
(
|
|
134
|
-
initiallyAttachedEntities: AttachableEntity<any>[], //Deliberately using any here because we can't use generics
|
|
135
|
-
initialAttachmentOperations: AttachmentOperation<any>[],
|
|
143
|
+
initiallyAttachedEntities: AttachableEntity<any, any>[], //Deliberately using any here because we can't use generics
|
|
144
|
+
initialAttachmentOperations: AttachmentOperation<any, any>[],
|
|
136
145
|
) => void
|
|
137
146
|
>
|
|
138
147
|
>;
|
|
139
148
|
} | null>(null);
|
|
140
149
|
const AttachmentContext = createContext<{
|
|
141
150
|
resetAttachmentTable: (
|
|
142
|
-
initiallyAttachedEntities: AttachableEntity<any>[], //Deliberately using any here because we can't use generics
|
|
143
|
-
initialAttachmentOperations: AttachmentOperation<any>[],
|
|
151
|
+
initiallyAttachedEntities: AttachableEntity<any, any>[], //Deliberately using any here because we can't use generics
|
|
152
|
+
initialAttachmentOperations: AttachmentOperation<any, any>[],
|
|
144
153
|
) => void;
|
|
145
154
|
} | null>(null);
|
|
146
155
|
|
|
147
|
-
export const AttachmentProvider = <
|
|
156
|
+
export const AttachmentProvider = <
|
|
157
|
+
ENTITY_TYPE extends unknown,
|
|
158
|
+
ENTITY extends Record<string, unknown> = Record<string, unknown>,
|
|
159
|
+
>({
|
|
148
160
|
children,
|
|
149
161
|
}: PropsWithChildren<{}>) => {
|
|
150
162
|
const [resetAttachmentTable, setResetAttachementTable] = useState<
|
|
151
163
|
(
|
|
152
|
-
initiallyAttachedEntities: AttachableEntity<ENTITY_TYPE>[],
|
|
153
|
-
initialAttachmentOperations: AttachmentOperation<ENTITY_TYPE>[],
|
|
164
|
+
initiallyAttachedEntities: AttachableEntity<ENTITY_TYPE, ENTITY>[],
|
|
165
|
+
initialAttachmentOperations: AttachmentOperation<ENTITY_TYPE, ENTITY>[],
|
|
154
166
|
) => void
|
|
155
167
|
>(
|
|
156
168
|
(
|
|
157
|
-
_: AttachableEntity<ENTITY_TYPE>[],
|
|
158
|
-
__: AttachmentOperation<ENTITY_TYPE>[],
|
|
169
|
+
_: AttachableEntity<ENTITY_TYPE, ENTITY>[],
|
|
170
|
+
__: AttachmentOperation<ENTITY_TYPE, ENTITY>[],
|
|
159
171
|
) => {},
|
|
160
172
|
);
|
|
161
173
|
return (
|
|
@@ -177,7 +189,10 @@ export const useAttachmentOperations = () => {
|
|
|
177
189
|
return ctx;
|
|
178
190
|
};
|
|
179
191
|
|
|
180
|
-
export const AttachmentTable = <
|
|
192
|
+
export const AttachmentTable = <
|
|
193
|
+
ENTITY_TYPE,
|
|
194
|
+
ENTITY extends Record<string, unknown> = Record<string, unknown>,
|
|
195
|
+
>({
|
|
181
196
|
initiallyAttachedEntities,
|
|
182
197
|
initiallyAttachedEntitiesStatus,
|
|
183
198
|
initialAttachmentOperations,
|
|
@@ -187,7 +202,7 @@ export const AttachmentTable = <ENTITY_TYPE,>({
|
|
|
187
202
|
getNameQuery,
|
|
188
203
|
filteredEntities,
|
|
189
204
|
onEntitySearchChange,
|
|
190
|
-
}: AttachmentTableProps<ENTITY_TYPE>) => {
|
|
205
|
+
}: AttachmentTableProps<ENTITY_TYPE, ENTITY>) => {
|
|
191
206
|
const privateAttachmentContext = useContext(PrivateAttachmentContext);
|
|
192
207
|
const exposedAttachmentContext = useContext(AttachmentContext);
|
|
193
208
|
|
|
@@ -198,8 +213,11 @@ export const AttachmentTable = <ENTITY_TYPE,>({
|
|
|
198
213
|
//Desired attached entities and onAttachmentsOperationsChanged handling
|
|
199
214
|
const convertInitiallyAttachedEntitiesToDesiredAttachedEntities = useCallback(
|
|
200
215
|
(
|
|
201
|
-
initiallyAttachedEntities: AttachableEntity<ENTITY_TYPE>[],
|
|
202
|
-
operations: AttachmentOperation<
|
|
216
|
+
initiallyAttachedEntities: AttachableEntity<ENTITY_TYPE, ENTITY>[],
|
|
217
|
+
operations: AttachmentOperation<
|
|
218
|
+
ENTITY_TYPE,
|
|
219
|
+
ENTITY
|
|
220
|
+
>[] = initialAttachmentOperations,
|
|
203
221
|
) => {
|
|
204
222
|
return initiallyAttachedEntities
|
|
205
223
|
.filter(
|
|
@@ -216,7 +234,9 @@ export const AttachmentTable = <ENTITY_TYPE,>({
|
|
|
216
234
|
);
|
|
217
235
|
const convertInitiallyAttachementOperationsToDesiredAttachedEntities =
|
|
218
236
|
useCallback(
|
|
219
|
-
(
|
|
237
|
+
(
|
|
238
|
+
initialAttachmentOperations: AttachmentOperation<ENTITY_TYPE, ENTITY>[],
|
|
239
|
+
) => {
|
|
220
240
|
return initialAttachmentOperations
|
|
221
241
|
.filter((op) => op.action !== AttachmentAction.REMOVE)
|
|
222
242
|
.map((op) => ({
|
|
@@ -232,21 +252,21 @@ export const AttachmentTable = <ENTITY_TYPE,>({
|
|
|
232
252
|
(
|
|
233
253
|
state: {
|
|
234
254
|
desiredAttachedEntities: AttachableEntityWithPendingStatus<ENTITY_TYPE>[];
|
|
235
|
-
attachmentsOperations: AttachmentOperation<ENTITY_TYPE>[];
|
|
255
|
+
attachmentsOperations: AttachmentOperation<ENTITY_TYPE, ENTITY>[];
|
|
236
256
|
},
|
|
237
257
|
action:
|
|
238
258
|
| {
|
|
239
259
|
action: AttachmentAction.ADD;
|
|
240
|
-
entity: AttachableEntity<ENTITY_TYPE>;
|
|
260
|
+
entity: AttachableEntity<ENTITY_TYPE, ENTITY>;
|
|
241
261
|
}
|
|
242
262
|
| {
|
|
243
263
|
action: AttachmentAction.REMOVE;
|
|
244
|
-
entity: AttachableEntity<ENTITY_TYPE>;
|
|
264
|
+
entity: AttachableEntity<ENTITY_TYPE, ENTITY>;
|
|
245
265
|
}
|
|
246
266
|
| {
|
|
247
267
|
action: 'RESET_DESIRED_ATTACHED_ENTITIES';
|
|
248
268
|
entities: AttachableEntityWithPendingStatus<ENTITY_TYPE>[];
|
|
249
|
-
operations: AttachmentOperation<ENTITY_TYPE>[];
|
|
269
|
+
operations: AttachmentOperation<ENTITY_TYPE, ENTITY>[];
|
|
250
270
|
},
|
|
251
271
|
) => {
|
|
252
272
|
switch (action.action) {
|
|
@@ -401,8 +421,8 @@ export const AttachmentTable = <ENTITY_TYPE,>({
|
|
|
401
421
|
useEffect(() => {
|
|
402
422
|
privateAttachmentContext.setResetAttachementTable(() => {
|
|
403
423
|
return (
|
|
404
|
-
newlyAttachedEntities: AttachableEntity<ENTITY_TYPE>[],
|
|
405
|
-
newAttachmentOperations: AttachmentOperation<ENTITY_TYPE>[],
|
|
424
|
+
newlyAttachedEntities: AttachableEntity<ENTITY_TYPE, ENTITY>[],
|
|
425
|
+
newAttachmentOperations: AttachmentOperation<ENTITY_TYPE, ENTITY>[],
|
|
406
426
|
) => {
|
|
407
427
|
dispatch({
|
|
408
428
|
action: 'RESET_DESIRED_ATTACHED_ENTITIES',
|
|
@@ -429,7 +449,11 @@ export const AttachmentTable = <ENTITY_TYPE,>({
|
|
|
429
449
|
const searchInputRef = useRef<HTMLInputElement | null>(null);
|
|
430
450
|
|
|
431
451
|
const onSelectedItemChange = useCallback(
|
|
432
|
-
(
|
|
452
|
+
(
|
|
453
|
+
onChangeParams: UseComboboxStateChange<
|
|
454
|
+
AttachableEntity<ENTITY_TYPE, ENTITY>
|
|
455
|
+
>,
|
|
456
|
+
) => {
|
|
433
457
|
if (onChangeParams.selectedItem) {
|
|
434
458
|
dispatch({
|
|
435
459
|
action: AttachmentAction.ADD,
|
|
@@ -478,7 +502,7 @@ export const AttachmentTable = <ENTITY_TYPE,>({
|
|
|
478
502
|
row: { original: entity },
|
|
479
503
|
}: {
|
|
480
504
|
value: string;
|
|
481
|
-
row: { original: AttachableEntity<ENTITY_TYPE> };
|
|
505
|
+
row: { original: AttachableEntity<ENTITY_TYPE, ENTITY> };
|
|
482
506
|
}) => {
|
|
483
507
|
const { data: asyncName, status } = useQuery({
|
|
484
508
|
...(getNameQuery
|
|
@@ -594,7 +618,6 @@ export const AttachmentTable = <ENTITY_TYPE,>({
|
|
|
594
618
|
onBlur={() => {
|
|
595
619
|
setSearchInputIsFocused(false);
|
|
596
620
|
}}
|
|
597
|
-
disableToggle
|
|
598
621
|
disabled={filteredEntities.status === 'error'}
|
|
599
622
|
/>
|
|
600
623
|
<Loader />
|
|
@@ -616,7 +639,6 @@ export const AttachmentTable = <ENTITY_TYPE,>({
|
|
|
616
639
|
onBlur={() => {
|
|
617
640
|
setSearchInputIsFocused(false);
|
|
618
641
|
}}
|
|
619
|
-
disableToggle
|
|
620
642
|
searchInputIsFocused={searchInputIsFocused}
|
|
621
643
|
/>
|
|
622
644
|
)}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
export type AttachableEntity<
|
|
1
|
+
export type AttachableEntity<
|
|
2
|
+
ENTITY_TYPE,
|
|
3
|
+
ENTITY extends Record<string, unknown> = Record<string, unknown>,
|
|
4
|
+
> = {
|
|
2
5
|
name: string;
|
|
3
6
|
id: string;
|
|
4
7
|
type: ENTITY_TYPE;
|
|
5
8
|
disableDetach?: boolean;
|
|
9
|
+
completeEntity?: ENTITY;
|
|
6
10
|
};
|
|
7
11
|
|
|
8
12
|
export enum AttachmentAction {
|
|
@@ -10,7 +14,10 @@ export enum AttachmentAction {
|
|
|
10
14
|
REMOVE,
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
export type AttachmentOperation<
|
|
17
|
+
export type AttachmentOperation<
|
|
18
|
+
ENTITY_TYPE,
|
|
19
|
+
ENTITY extends Record<string, unknown> = Record<string, unknown>,
|
|
20
|
+
> = {
|
|
14
21
|
action: AttachmentAction;
|
|
15
|
-
entity: AttachableEntity<ENTITY_TYPE>;
|
|
22
|
+
entity: AttachableEntity<ENTITY_TYPE, ENTITY>;
|
|
16
23
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Meta,
|
|
3
|
+
Story,
|
|
4
|
+
Canvas,
|
|
5
|
+
Primary,
|
|
6
|
+
Controls,
|
|
7
|
+
Unstyled,
|
|
8
|
+
Source,
|
|
9
|
+
} from '@storybook/blocks';
|
|
10
|
+
import { SearchInput } from '../../src/lib/components/searchinput/SearchInput.component';
|
|
11
|
+
|
|
12
|
+
import * as Stories from './searchinput.stories';
|
|
13
|
+
|
|
14
|
+
<Meta name="Guideline" of={Stories} />
|
|
15
|
+
|
|
16
|
+
# Search Input
|
|
17
|
+
|
|
18
|
+
<Story of={Stories.Debounce} />
|
|
19
|
+
<Controls of={Stories.Debounce} />
|
|
20
|
+
<Source of={Stories.Debounce} />
|