@scality/core-ui 0.116.0 → 0.117.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/.storybook/preview.js +32 -18
- package/README.md +1 -1
- package/dist/components/buttonv2/Buttonv2.component.d.ts +1 -0
- package/dist/components/buttonv2/Buttonv2.component.d.ts.map +1 -1
- package/dist/components/buttonv2/Buttonv2.component.js +9 -8
- package/dist/components/card/Card.component.d.ts.map +1 -1
- package/dist/components/card/Card.component.js +11 -11
- package/dist/components/checkbox/Checkbox.component.d.ts.map +1 -1
- package/dist/components/checkbox/Checkbox.component.js +2 -2
- package/dist/components/dropdown/Dropdown.component.d.ts.map +1 -1
- package/dist/components/dropdown/Dropdown.component.js +41 -57
- package/dist/components/inlineinput/InlineInput.d.ts +17 -0
- package/dist/components/inlineinput/InlineInput.d.ts.map +1 -0
- package/dist/components/inlineinput/InlineInput.js +85 -0
- package/dist/components/inputv2/inputv2.d.ts +8 -0
- package/dist/components/inputv2/inputv2.d.ts.map +1 -1
- package/dist/components/modal/Modal.component.d.ts.map +1 -1
- package/dist/components/modal/Modal.component.js +18 -1
- package/dist/components/navbar/Navbar.component.d.ts +1 -0
- package/dist/components/navbar/Navbar.component.d.ts.map +1 -1
- package/dist/components/navbar/Navbar.component.js +23 -6
- package/dist/components/sidebar/Sidebar.component.d.ts.map +1 -1
- package/dist/components/sidebar/Sidebar.component.js +18 -1
- package/dist/components/tablev2/MultiSelectableContent.d.ts.map +1 -1
- package/dist/components/tablev2/MultiSelectableContent.js +10 -1
- package/dist/components/tablev2/SingleSelectableContent.d.ts.map +1 -1
- package/dist/components/tablev2/SingleSelectableContent.js +20 -1
- package/dist/components/tablev2/Tablestyle.d.ts +1 -0
- package/dist/components/tablev2/Tablestyle.d.ts.map +1 -1
- package/dist/components/tablev2/Tablestyle.js +10 -1
- package/dist/components/text/Text.component.d.ts.map +1 -1
- package/dist/components/text/Text.component.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/spacing.js +1 -1
- package/package.json +5 -5
- package/src/lib/components/buttonv2/Buttonv2.component.tsx +11 -8
- package/src/lib/components/card/Card.component.tsx +11 -11
- package/src/lib/components/checkbox/Checkbox.component.tsx +2 -2
- package/src/lib/components/dropdown/Dropdown.component.tsx +65 -81
- package/src/lib/components/inlineinput/InlineInput.test.tsx +211 -0
- package/src/lib/components/inlineinput/InlineInput.tsx +179 -0
- package/src/lib/components/inputv2/inputv2.tsx +2 -2
- package/src/lib/components/modal/Modal.component.tsx +19 -1
- package/src/lib/components/navbar/Navbar.component.tsx +27 -19
- package/src/lib/components/sidebar/Sidebar.component.tsx +21 -0
- package/src/lib/components/tablev2/MultiSelectableContent.tsx +17 -1
- package/src/lib/components/tablev2/SingleSelectableContent.tsx +29 -1
- package/src/lib/components/tablev2/Tablestyle.tsx +15 -2
- package/src/lib/components/text/Text.component.tsx +2 -2
- package/src/lib/index.ts +1 -0
- package/src/lib/spacing.tsx +1 -1
- package/stories/InlineInput/InlineInput.stories.tsx +46 -0
- package/stories/navbar.stories.tsx +18 -0
|
@@ -12,6 +12,8 @@ import { spacing } from '../../spacing';
|
|
|
12
12
|
import { fontSize } from '../../style/theme';
|
|
13
13
|
import { getThemePropSelector } from '../../utils';
|
|
14
14
|
import { Icon } from '../icon/Icon.component';
|
|
15
|
+
import { useSelect } from 'downshift';
|
|
16
|
+
import { FocusVisibleStyle } from '../buttonv2/Buttonv2.component';
|
|
15
17
|
export type Item = {
|
|
16
18
|
label: string;
|
|
17
19
|
name?: string;
|
|
@@ -41,38 +43,14 @@ const DropdownMenuStyled = styled.ul`
|
|
|
41
43
|
position: absolute;
|
|
42
44
|
margin: 0;
|
|
43
45
|
padding: 0;
|
|
46
|
+
top: 50px;
|
|
44
47
|
border: 1px solid ${getThemePropSelector('backgroundLevel1')};
|
|
45
48
|
z-index: ${zIndex.dropdown};
|
|
46
49
|
max-height: 200px;
|
|
47
50
|
min-width: 100%;
|
|
48
51
|
overflow: auto;
|
|
49
52
|
border-bottom: 0.3px solid ${getThemePropSelector('border')};
|
|
50
|
-
${(props) =>
|
|
51
|
-
if (
|
|
52
|
-
props.size &&
|
|
53
|
-
props.triggerSize &&
|
|
54
|
-
props.triggerSize.x + props.size.width > window.innerWidth
|
|
55
|
-
) {
|
|
56
|
-
return css`
|
|
57
|
-
right: 0;
|
|
58
|
-
top: 100%;
|
|
59
|
-
`;
|
|
60
|
-
} else if (
|
|
61
|
-
props.size &&
|
|
62
|
-
props.triggerSize &&
|
|
63
|
-
props.triggerSize.y + props.size.height > window.innerHeight
|
|
64
|
-
) {
|
|
65
|
-
return css`
|
|
66
|
-
left: 0;
|
|
67
|
-
bottom: ${props.triggerSize.height + 'px'};
|
|
68
|
-
`;
|
|
69
|
-
} else {
|
|
70
|
-
return css`
|
|
71
|
-
left: 0;
|
|
72
|
-
top: 100%;
|
|
73
|
-
`;
|
|
74
|
-
}
|
|
75
|
-
}};
|
|
53
|
+
display: ${(props) => (props.isOpen ? 'auto' : 'none')};
|
|
76
54
|
`;
|
|
77
55
|
const DropdownMenuItemStyled = styled.li`
|
|
78
56
|
display: flex;
|
|
@@ -81,25 +59,36 @@ const DropdownMenuItemStyled = styled.li`
|
|
|
81
59
|
white-space: nowrap;
|
|
82
60
|
cursor: pointer;
|
|
83
61
|
font-size: ${fontSize.base};
|
|
62
|
+
${(props) => {
|
|
63
|
+
console.log(props.isSelected);
|
|
64
|
+
return props.isSelected
|
|
65
|
+
? `background-color: ${props.theme.highlight};`
|
|
66
|
+
: `background-color: ${props.theme.backgroundLevel1};`;
|
|
67
|
+
}}
|
|
68
|
+
|
|
69
|
+
color: ${getThemePropSelector('textPrimary')};
|
|
70
|
+
border-top: 0.3px solid ${getThemePropSelector('border')};
|
|
71
|
+
border-left: 0.3px solid ${getThemePropSelector('border')};
|
|
72
|
+
border-right: 0.3px solid ${getThemePropSelector('border')};
|
|
84
73
|
|
|
85
|
-
|
|
86
|
-
background-color: ${getThemePropSelector('
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
&:hover {
|
|
92
|
-
background-color: ${getThemePropSelector('highlight')};
|
|
93
|
-
}
|
|
94
|
-
&:active {
|
|
95
|
-
background-color: ${getThemePropSelector('highlight')};
|
|
96
|
-
}
|
|
97
|
-
`};
|
|
74
|
+
&:hover {
|
|
75
|
+
background-color: ${getThemePropSelector('highlight')};
|
|
76
|
+
}
|
|
77
|
+
&:active {
|
|
78
|
+
background-color: ${getThemePropSelector('highlight')};
|
|
79
|
+
}
|
|
98
80
|
`;
|
|
99
81
|
const Caret = styled.span`
|
|
100
82
|
margin-left: ${spacing.r16};
|
|
101
83
|
`;
|
|
102
|
-
const
|
|
84
|
+
const Trigger = ButtonStyled.withComponent('div');
|
|
85
|
+
const TriggerStyled = styled(Trigger)`
|
|
86
|
+
// :focus-visible is the keyboard-only version of :focus
|
|
87
|
+
&:focus-visible {
|
|
88
|
+
${FocusVisibleStyle}
|
|
89
|
+
color: ${(props) => props.theme.textPrimary};
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
103
92
|
|
|
104
93
|
function Dropdown({
|
|
105
94
|
items,
|
|
@@ -111,19 +100,16 @@ function Dropdown({
|
|
|
111
100
|
caret = true,
|
|
112
101
|
...rest
|
|
113
102
|
}: Props) {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
setTriggerSize(node.getBoundingClientRect());
|
|
125
|
-
}
|
|
126
|
-
}, []);
|
|
103
|
+
const {
|
|
104
|
+
isOpen,
|
|
105
|
+
getToggleButtonProps,
|
|
106
|
+
getMenuProps,
|
|
107
|
+
getItemProps,
|
|
108
|
+
highlightedIndex,
|
|
109
|
+
} = useSelect({
|
|
110
|
+
items,
|
|
111
|
+
itemToString: (item) => item?.label || '',
|
|
112
|
+
});
|
|
127
113
|
return (
|
|
128
114
|
<DropdownStyled
|
|
129
115
|
active={open}
|
|
@@ -135,12 +121,8 @@ function Dropdown({
|
|
|
135
121
|
variant={variant}
|
|
136
122
|
size={size}
|
|
137
123
|
className="trigger"
|
|
138
|
-
onBlur={() => setOpen(!open)}
|
|
139
|
-
onFocus={() => setOpen(!open)}
|
|
140
|
-
onClick={(event) => event.stopPropagation()}
|
|
141
|
-
tabIndex="0"
|
|
142
124
|
title={title}
|
|
143
|
-
|
|
125
|
+
{...getToggleButtonProps()}
|
|
144
126
|
>
|
|
145
127
|
{icon && (
|
|
146
128
|
<ButtonIcon text={text} size={size}>
|
|
@@ -153,29 +135,31 @@ function Dropdown({
|
|
|
153
135
|
<Icon name="Dropdown-down" />
|
|
154
136
|
</Caret>
|
|
155
137
|
)}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
138
|
+
|
|
139
|
+
<DropdownMenuStyled
|
|
140
|
+
className="menu-item"
|
|
141
|
+
isOpen={isOpen}
|
|
142
|
+
{...getMenuProps()}
|
|
143
|
+
>
|
|
144
|
+
{items.map((item, index) => {
|
|
145
|
+
return (
|
|
146
|
+
<DropdownMenuItemStyled
|
|
147
|
+
className="menu-item-label"
|
|
148
|
+
key={item.label}
|
|
149
|
+
variant={item.variant}
|
|
150
|
+
{...item}
|
|
151
|
+
{...getItemProps({
|
|
152
|
+
item,
|
|
153
|
+
index,
|
|
154
|
+
onClick: item.onClick,
|
|
155
|
+
})}
|
|
156
|
+
isSelected={index === highlightedIndex}
|
|
157
|
+
>
|
|
158
|
+
{item.label}
|
|
159
|
+
</DropdownMenuItemStyled>
|
|
160
|
+
);
|
|
161
|
+
})}
|
|
162
|
+
</DropdownMenuStyled>
|
|
179
163
|
</TriggerStyled>
|
|
180
164
|
</DropdownStyled>
|
|
181
165
|
);
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import React, { PropsWithChildren } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
QueryClient,
|
|
4
|
+
QueryClientProvider,
|
|
5
|
+
useMutation,
|
|
6
|
+
UseMutationResult,
|
|
7
|
+
} from 'react-query';
|
|
8
|
+
import { ToastProvider } from '../toast/ToastProvider';
|
|
9
|
+
import {
|
|
10
|
+
render,
|
|
11
|
+
screen,
|
|
12
|
+
waitFor,
|
|
13
|
+
waitForElementToBeRemoved,
|
|
14
|
+
within,
|
|
15
|
+
} from '@testing-library/react';
|
|
16
|
+
import userEvent from '@testing-library/user-event';
|
|
17
|
+
import { InlineInput } from './InlineInput';
|
|
18
|
+
|
|
19
|
+
const queryClient = new QueryClient();
|
|
20
|
+
const Wrapper = ({ children }: PropsWithChildren<{}>) => {
|
|
21
|
+
return (
|
|
22
|
+
<ToastProvider>
|
|
23
|
+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
24
|
+
</ToastProvider>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const ChangeMutationProvider = ({
|
|
29
|
+
onChange,
|
|
30
|
+
children,
|
|
31
|
+
}: {
|
|
32
|
+
onChange: (value: string) => void;
|
|
33
|
+
children: ({
|
|
34
|
+
changeMutation,
|
|
35
|
+
}: {
|
|
36
|
+
changeMutation: UseMutationResult<unknown, unknown, { value: string }>;
|
|
37
|
+
}) => JSX.Element;
|
|
38
|
+
}) => {
|
|
39
|
+
const changeMutation = useMutation<unknown, unknown, { value: string }>({
|
|
40
|
+
mutationFn: ({ value }) => {
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
onChange(value);
|
|
43
|
+
resolve(null);
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
return <>{children({ changeMutation })}</>;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const selectors = {
|
|
51
|
+
confirmationModal: () => screen.getByRole('dialog', { name: /Confirm/i }),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
describe('InlineInput', () => {
|
|
55
|
+
describe('when the user clicks accepts the edit', () => {
|
|
56
|
+
test('without confirmation modal', async () => {
|
|
57
|
+
//S
|
|
58
|
+
const mock = jest.fn();
|
|
59
|
+
render(
|
|
60
|
+
<ChangeMutationProvider onChange={mock}>
|
|
61
|
+
{({ changeMutation }) => (
|
|
62
|
+
<InlineInput
|
|
63
|
+
id="test"
|
|
64
|
+
defaultValue={'test'}
|
|
65
|
+
changeMutation={changeMutation}
|
|
66
|
+
/>
|
|
67
|
+
)}
|
|
68
|
+
</ChangeMutationProvider>,
|
|
69
|
+
{ wrapper: Wrapper },
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
//E
|
|
73
|
+
/// First focus the edit button
|
|
74
|
+
await userEvent.tab();
|
|
75
|
+
/// Then press enter to edit the input
|
|
76
|
+
await userEvent.keyboard('{enter}');
|
|
77
|
+
/// Then type a new value
|
|
78
|
+
await userEvent.type(document.activeElement, 'new value');
|
|
79
|
+
/// Then press enter to confirm the new value
|
|
80
|
+
await userEvent.keyboard('{enter}');
|
|
81
|
+
await waitForElementToBeRemoved(() => screen.getByRole('textbox'));
|
|
82
|
+
|
|
83
|
+
//V
|
|
84
|
+
expect(mock).toHaveBeenCalledWith('testnew value');
|
|
85
|
+
expect(mock).toHaveBeenCalledTimes(1);
|
|
86
|
+
expect(screen.getByText('testnew value')).toBeInTheDocument();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('with confirmation modal', async () => {
|
|
90
|
+
//S
|
|
91
|
+
const mock = jest.fn();
|
|
92
|
+
render(
|
|
93
|
+
<ChangeMutationProvider onChange={mock}>
|
|
94
|
+
{({ changeMutation }) => (
|
|
95
|
+
<InlineInput
|
|
96
|
+
id="test"
|
|
97
|
+
defaultValue={'test'}
|
|
98
|
+
changeMutation={changeMutation}
|
|
99
|
+
confirmationModal={{
|
|
100
|
+
title: <div>Confirm</div>,
|
|
101
|
+
body: <div>Are you sure?</div>,
|
|
102
|
+
}}
|
|
103
|
+
/>
|
|
104
|
+
)}
|
|
105
|
+
</ChangeMutationProvider>,
|
|
106
|
+
{ wrapper: Wrapper },
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
//E
|
|
110
|
+
/// First focus the edit button
|
|
111
|
+
await userEvent.tab();
|
|
112
|
+
/// Then press enter to edit the input
|
|
113
|
+
await userEvent.keyboard('{enter}');
|
|
114
|
+
/// Then type a new value
|
|
115
|
+
await userEvent.type(document.activeElement, 'new value');
|
|
116
|
+
/// Then press enter to confirm the new value
|
|
117
|
+
await userEvent.keyboard('{enter}');
|
|
118
|
+
/// Expect the confirmation modal to be opened
|
|
119
|
+
await waitFor(() =>
|
|
120
|
+
expect(selectors.confirmationModal()).toBeInTheDocument(),
|
|
121
|
+
);
|
|
122
|
+
/// Click the confirm button
|
|
123
|
+
await userEvent.click(screen.getByRole('button', { name: /confirm/i }));
|
|
124
|
+
/// Wait for modal to be closed
|
|
125
|
+
await waitForElementToBeRemoved(() => selectors.confirmationModal());
|
|
126
|
+
|
|
127
|
+
//V
|
|
128
|
+
expect(mock).toHaveBeenCalledWith('testnew value');
|
|
129
|
+
expect(mock).toHaveBeenCalledTimes(1);
|
|
130
|
+
expect(screen.getByText('testnew value')).toBeInTheDocument();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('when the user clicks reset the edit', () => {
|
|
135
|
+
test('without confirmation modal', async () => {
|
|
136
|
+
//S
|
|
137
|
+
const mock = jest.fn();
|
|
138
|
+
render(
|
|
139
|
+
<ChangeMutationProvider onChange={mock}>
|
|
140
|
+
{({ changeMutation }) => (
|
|
141
|
+
<InlineInput
|
|
142
|
+
id="test"
|
|
143
|
+
defaultValue={'test'}
|
|
144
|
+
changeMutation={changeMutation}
|
|
145
|
+
/>
|
|
146
|
+
)}
|
|
147
|
+
</ChangeMutationProvider>,
|
|
148
|
+
{ wrapper: Wrapper },
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
//E
|
|
152
|
+
/// First focus the edit button
|
|
153
|
+
await userEvent.tab();
|
|
154
|
+
/// Then press enter to edit the input
|
|
155
|
+
await userEvent.keyboard('{enter}');
|
|
156
|
+
/// Then type a new value
|
|
157
|
+
await userEvent.type(document.activeElement, 'new value');
|
|
158
|
+
/// Then press escape to cancel the new value
|
|
159
|
+
await userEvent.keyboard('{esc}');
|
|
160
|
+
|
|
161
|
+
//V
|
|
162
|
+
expect(mock).not.toHaveBeenCalled();
|
|
163
|
+
expect(screen.getByText('test')).toBeInTheDocument();
|
|
164
|
+
});
|
|
165
|
+
test('with confirmation modal', async () => {
|
|
166
|
+
//S
|
|
167
|
+
const mock = jest.fn();
|
|
168
|
+
render(
|
|
169
|
+
<ChangeMutationProvider onChange={mock}>
|
|
170
|
+
{({ changeMutation }) => (
|
|
171
|
+
<InlineInput
|
|
172
|
+
id="test"
|
|
173
|
+
defaultValue={'test'}
|
|
174
|
+
changeMutation={changeMutation}
|
|
175
|
+
confirmationModal={{
|
|
176
|
+
title: <div>Confirm</div>,
|
|
177
|
+
body: <div>Are you sure?</div>,
|
|
178
|
+
}}
|
|
179
|
+
/>
|
|
180
|
+
)}
|
|
181
|
+
</ChangeMutationProvider>,
|
|
182
|
+
{ wrapper: Wrapper },
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
//E
|
|
186
|
+
/// First focus the edit button
|
|
187
|
+
await userEvent.tab();
|
|
188
|
+
/// Then press enter to edit the input
|
|
189
|
+
await userEvent.keyboard('{enter}');
|
|
190
|
+
/// Then type a new value
|
|
191
|
+
await userEvent.type(document.activeElement, 'new value');
|
|
192
|
+
/// Then press enter to confirm the new value
|
|
193
|
+
await userEvent.keyboard('{enter}');
|
|
194
|
+
/// Expect the confirmation modal to be opened
|
|
195
|
+
await waitFor(() =>
|
|
196
|
+
expect(selectors.confirmationModal()).toBeInTheDocument(),
|
|
197
|
+
);
|
|
198
|
+
/// Click the cancel button
|
|
199
|
+
await userEvent.click(
|
|
200
|
+
within(selectors.confirmationModal()).getByRole('button', {
|
|
201
|
+
name: /Cancel/i,
|
|
202
|
+
}),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
//V
|
|
206
|
+
expect(mock).not.toHaveBeenCalled();
|
|
207
|
+
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
208
|
+
expect(screen.getByRole('textbox').value).toBe('testnew value');
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import { Button } from '../buttonv2/Buttonv2.component';
|
|
3
|
+
import { Icon } from '../icon/Icon.component';
|
|
4
|
+
import { Input, InputProps } from '../inputv2/inputv2';
|
|
5
|
+
import { Modal } from '../modal/Modal.component';
|
|
6
|
+
import { useToast } from '../toast/ToastProvider';
|
|
7
|
+
import { useForm } from 'react-hook-form';
|
|
8
|
+
import { UseMutationResult } from 'react-query';
|
|
9
|
+
import { Text } from '../text/Text.component';
|
|
10
|
+
import { useState } from 'react';
|
|
11
|
+
import { Stack, Wrap } from '../../spacing';
|
|
12
|
+
|
|
13
|
+
const UnderlinedText = styled(Text)`
|
|
14
|
+
text-decoration-line: underline;
|
|
15
|
+
text-decoration-style: dashed;
|
|
16
|
+
cursor: text;
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
type InlineInputForm = {
|
|
20
|
+
value: string;
|
|
21
|
+
};
|
|
22
|
+
type InlineInputProps = {
|
|
23
|
+
defaultValue?: string;
|
|
24
|
+
confirmationModal?: {
|
|
25
|
+
title: JSX.Element;
|
|
26
|
+
body: JSX.Element;
|
|
27
|
+
};
|
|
28
|
+
changeMutation: UseMutationResult<unknown, unknown, InlineInputForm, unknown>;
|
|
29
|
+
} & InputProps;
|
|
30
|
+
|
|
31
|
+
export const InlineInput = ({
|
|
32
|
+
defaultValue,
|
|
33
|
+
confirmationModal,
|
|
34
|
+
changeMutation,
|
|
35
|
+
...props
|
|
36
|
+
}: InlineInputProps) => {
|
|
37
|
+
const { register, handleSubmit, watch, reset } = useForm<InlineInputForm>({
|
|
38
|
+
defaultValues: {
|
|
39
|
+
value: defaultValue,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
const [isConfirmationModalOpened, setIsConfirmationModalOpened] =
|
|
43
|
+
useState(false);
|
|
44
|
+
const handleSuccess = () => {
|
|
45
|
+
setIsConfirmationModalOpened(false);
|
|
46
|
+
setIsEditing(false);
|
|
47
|
+
setIsHover(false);
|
|
48
|
+
};
|
|
49
|
+
const onSubmit = (data: InlineInputForm) => {
|
|
50
|
+
if (confirmationModal) {
|
|
51
|
+
setIsConfirmationModalOpened(true);
|
|
52
|
+
} else {
|
|
53
|
+
changeMutation.mutate(data, {
|
|
54
|
+
onSuccess: () => {
|
|
55
|
+
handleSuccess();
|
|
56
|
+
},
|
|
57
|
+
onError: () => {
|
|
58
|
+
showToast({
|
|
59
|
+
open: true,
|
|
60
|
+
status: 'error',
|
|
61
|
+
message: 'An error occurred while updating the value',
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const { showToast } = useToast();
|
|
68
|
+
const [isHover, setIsHover] = useState(false);
|
|
69
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
70
|
+
|
|
71
|
+
const handleReset = () => {
|
|
72
|
+
reset();
|
|
73
|
+
setIsEditing(false);
|
|
74
|
+
setIsHover(false);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
//handle esc key to cancel editing
|
|
78
|
+
const handleKeyDown = (event: React.KeyboardEvent) => {
|
|
79
|
+
if (event.key === 'Escape') {
|
|
80
|
+
handleReset();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (isEditing) {
|
|
85
|
+
return (
|
|
86
|
+
<>
|
|
87
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
88
|
+
<Stack>
|
|
89
|
+
<Input
|
|
90
|
+
{...register('value')}
|
|
91
|
+
size="1/3"
|
|
92
|
+
autoFocus
|
|
93
|
+
onKeyDown={handleKeyDown}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
<Button
|
|
97
|
+
icon={<Icon name="Close" />}
|
|
98
|
+
tooltip={{
|
|
99
|
+
overlay: 'Cancel',
|
|
100
|
+
}}
|
|
101
|
+
type="reset"
|
|
102
|
+
variant="outline"
|
|
103
|
+
onClick={handleReset}
|
|
104
|
+
/>
|
|
105
|
+
<Button
|
|
106
|
+
icon={<Icon name="Check" />}
|
|
107
|
+
tooltip={{
|
|
108
|
+
overlay: 'Save',
|
|
109
|
+
}}
|
|
110
|
+
variant="primary"
|
|
111
|
+
type="submit"
|
|
112
|
+
isLoading={changeMutation.isLoading}
|
|
113
|
+
/>
|
|
114
|
+
</Stack>
|
|
115
|
+
</form>
|
|
116
|
+
{confirmationModal && (
|
|
117
|
+
<Modal
|
|
118
|
+
isOpen={isConfirmationModalOpened}
|
|
119
|
+
title={confirmationModal.title}
|
|
120
|
+
footer={
|
|
121
|
+
<Wrap>
|
|
122
|
+
<p></p>
|
|
123
|
+
<Stack>
|
|
124
|
+
<Button
|
|
125
|
+
label="Cancel"
|
|
126
|
+
variant="outline"
|
|
127
|
+
onClick={() => setIsConfirmationModalOpened(false)}
|
|
128
|
+
/>
|
|
129
|
+
<Button
|
|
130
|
+
label="Confirm"
|
|
131
|
+
variant="primary"
|
|
132
|
+
isLoading={changeMutation.isLoading}
|
|
133
|
+
onClick={() => {
|
|
134
|
+
changeMutation.mutate(watch(), {
|
|
135
|
+
onSuccess: () => {
|
|
136
|
+
handleSuccess();
|
|
137
|
+
},
|
|
138
|
+
onError: () => {
|
|
139
|
+
showToast({
|
|
140
|
+
open: true,
|
|
141
|
+
status: 'error',
|
|
142
|
+
message:
|
|
143
|
+
'An error occurred while updating the value',
|
|
144
|
+
});
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}}
|
|
148
|
+
/>
|
|
149
|
+
</Stack>
|
|
150
|
+
</Wrap>
|
|
151
|
+
}
|
|
152
|
+
>
|
|
153
|
+
{confirmationModal.body}
|
|
154
|
+
</Modal>
|
|
155
|
+
)}
|
|
156
|
+
</>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<Stack
|
|
162
|
+
onMouseEnter={() => setIsHover(true)}
|
|
163
|
+
onMouseLeave={() => setIsHover(false)}
|
|
164
|
+
onFocus={() => setIsHover(true)}
|
|
165
|
+
onBlur={() => setIsHover(false)}
|
|
166
|
+
>
|
|
167
|
+
<UnderlinedText>{watch('value')}</UnderlinedText>
|
|
168
|
+
<Button
|
|
169
|
+
icon={<Icon name="Pencil" />}
|
|
170
|
+
tooltip={{
|
|
171
|
+
overlay: 'Edit',
|
|
172
|
+
}}
|
|
173
|
+
variant="primary"
|
|
174
|
+
onClick={() => setIsEditing(true)}
|
|
175
|
+
style={{ opacity: !isHover ? '0' : '1' }}
|
|
176
|
+
/>
|
|
177
|
+
</Stack>
|
|
178
|
+
);
|
|
179
|
+
};
|
|
@@ -89,7 +89,7 @@ const SelfCenterredIcon = styled(Icon)`
|
|
|
89
89
|
|
|
90
90
|
type InputSize = '1' | '2/3' | '1/2' | '1/3';
|
|
91
91
|
|
|
92
|
-
type
|
|
92
|
+
export type InputProps = {
|
|
93
93
|
error?: string;
|
|
94
94
|
id: string;
|
|
95
95
|
leftIcon?: IconName;
|
|
@@ -97,7 +97,7 @@ type Props = {
|
|
|
97
97
|
size?: InputSize;
|
|
98
98
|
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'size'>;
|
|
99
99
|
|
|
100
|
-
export const Input = forwardRef<HTMLInputElement,
|
|
100
|
+
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
101
101
|
(
|
|
102
102
|
{
|
|
103
103
|
error,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReactNode, useLayoutEffect, useRef } from 'react';
|
|
1
|
+
import { ReactNode, useEffect, useLayoutEffect, useRef } from 'react';
|
|
2
2
|
import ReactDom from 'react-dom';
|
|
3
3
|
import styled from 'styled-components';
|
|
4
4
|
import { Wrap, spacing } from '../../spacing';
|
|
@@ -76,6 +76,24 @@ const Modal = ({
|
|
|
76
76
|
document.body && document.body.removeChild(modalContainer.current);
|
|
77
77
|
};
|
|
78
78
|
}, [modalContainer]);
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (isOpen) {
|
|
82
|
+
//Auto focus the modal when it opens
|
|
83
|
+
modalContainer.current.setAttribute('tabindex', '0');
|
|
84
|
+
modalContainer.current.focus();
|
|
85
|
+
//Listen to esc key to close the modal
|
|
86
|
+
const handleEsc = (event: KeyboardEvent) => {
|
|
87
|
+
if (event.key === 'Escape') {
|
|
88
|
+
close && close();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
document.addEventListener('keydown', handleEsc);
|
|
92
|
+
return () => {
|
|
93
|
+
document.removeEventListener('keydown', handleEsc);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}, [isOpen]);
|
|
79
97
|
return isOpen
|
|
80
98
|
? ReactDom.createPortal(
|
|
81
99
|
<ModalContainer
|