@bitrise/bitkit 10.20.1 → 10.22.1
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/package.json +1 -1
- package/src/Components/Dropdown/Dropdown.test.tsx +7 -0
- package/src/Components/Dropdown/Dropdown.tsx +16 -20
- package/src/Components/Dropdown/hooks/useFloatingDropdown.ts +28 -6
- package/src/Components/Form/Textarea/Textarea.test.tsx +35 -0
- package/src/Components/Form/Textarea/Textarea.tsx +10 -4
- package/src/Components/Table/Table.stories.tsx +8 -1
- package/src/Components/Table/Table.theme.ts +1 -3
- package/src/Components/Table/Tr.tsx +27 -8
- package/src/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -31,6 +31,13 @@ const TestComponent = forwardRef<
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
describe('Dropdown', () => {
|
|
34
|
+
it('sets ref to the button', async () => {
|
|
35
|
+
const handler = jest.fn();
|
|
36
|
+
render(<Dropdown ref={handler} />);
|
|
37
|
+
const button = await screen.findByRole('combobox');
|
|
38
|
+
expect(handler).toHaveBeenCalledWith(button);
|
|
39
|
+
});
|
|
40
|
+
|
|
34
41
|
it('shows option on button', async () => {
|
|
35
42
|
render(<TestComponent submit={() => {}} />);
|
|
36
43
|
const button = await screen.findByRole('combobox', { name: 'Test' });
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
cloneElement,
|
|
3
|
-
forwardRef,
|
|
4
|
-
ReactNode,
|
|
5
|
-
useCallback,
|
|
6
|
-
useEffect,
|
|
7
|
-
useImperativeHandle,
|
|
8
|
-
useMemo,
|
|
9
|
-
useRef,
|
|
10
|
-
useState,
|
|
11
|
-
} from 'react';
|
|
1
|
+
import React, { cloneElement, forwardRef, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
12
2
|
import {
|
|
13
3
|
chakra,
|
|
14
4
|
ChakraProps,
|
|
@@ -18,7 +8,7 @@ import {
|
|
|
18
8
|
useControllableState,
|
|
19
9
|
useMultiStyleConfig,
|
|
20
10
|
} from '@chakra-ui/react';
|
|
21
|
-
import { FloatingFocusManager } from '@floating-ui/react-dom-interactions';
|
|
11
|
+
import { FloatingFocusManager, UseFloatingProps } from '@floating-ui/react-dom-interactions';
|
|
22
12
|
import Icon, { TypeIconName } from '../Icon/Icon';
|
|
23
13
|
import { DropdownEventArgs, DropdownProvider, useDropdownContext, useDropdownStyles } from './Dropdown.context';
|
|
24
14
|
import { DropdownOption, DropdownGroup, DropdownDetailedOption, DropdownOptionProps } from './DropdownOption';
|
|
@@ -95,6 +85,8 @@ export interface DropdownProps<T> extends ChakraProps {
|
|
|
95
85
|
size?: 'small' | 'medium';
|
|
96
86
|
dropdownMaxHeight?: ChakraProps['maxH'];
|
|
97
87
|
dropdownMinHeight?: ChakraProps['minH'];
|
|
88
|
+
dropdownWidth?: ChakraProps['width'] | 'match';
|
|
89
|
+
placement?: UseFloatingProps['placement'];
|
|
98
90
|
readOnly?: boolean;
|
|
99
91
|
disabled?: boolean;
|
|
100
92
|
placeholder?: string;
|
|
@@ -102,7 +94,7 @@ export interface DropdownProps<T> extends ChakraProps {
|
|
|
102
94
|
search?: ReactNode;
|
|
103
95
|
children?: ReactNode;
|
|
104
96
|
iconName?: TypeIconName;
|
|
105
|
-
formLabel?:
|
|
97
|
+
formLabel?: ReactNode;
|
|
106
98
|
}
|
|
107
99
|
|
|
108
100
|
function useOptionListWithIndexes({ children }: { children: ReactNode }) {
|
|
@@ -132,8 +124,8 @@ function useOptionListWithIndexes({ children }: { children: ReactNode }) {
|
|
|
132
124
|
}, [children]);
|
|
133
125
|
}
|
|
134
126
|
|
|
135
|
-
type UseDropdownProps
|
|
136
|
-
ref: React.Ref<
|
|
127
|
+
type UseDropdownProps = {
|
|
128
|
+
ref: React.Ref<Element>;
|
|
137
129
|
optionsRef: React.RefObject<HTMLDivElement>;
|
|
138
130
|
};
|
|
139
131
|
|
|
@@ -162,11 +154,13 @@ function useDropdown<T>({
|
|
|
162
154
|
value,
|
|
163
155
|
optionsRef,
|
|
164
156
|
defaultValue,
|
|
157
|
+
dropdownWidth = 'match',
|
|
158
|
+
placement,
|
|
165
159
|
ref,
|
|
166
160
|
children,
|
|
167
161
|
readOnly,
|
|
168
162
|
...rest
|
|
169
|
-
}: DropdownProps<T> & UseDropdownProps
|
|
163
|
+
}: DropdownProps<T> & UseDropdownProps) {
|
|
170
164
|
const searchRef = useRef(null);
|
|
171
165
|
const {
|
|
172
166
|
close,
|
|
@@ -179,7 +173,7 @@ function useDropdown<T>({
|
|
|
179
173
|
setActiveIndex,
|
|
180
174
|
getItemProps,
|
|
181
175
|
listRef,
|
|
182
|
-
} = useFloatingDropdown({ enabled: !readOnly, optionsRef });
|
|
176
|
+
} = useFloatingDropdown({ enabled: !readOnly, optionsRef, dropdownWidth, placement });
|
|
183
177
|
const [formValue, setFormValue] = useControllableState<T>({
|
|
184
178
|
onChange: (newValue) => onChange?.({ target: { value: newValue, name } }),
|
|
185
179
|
defaultValue,
|
|
@@ -187,7 +181,6 @@ function useDropdown<T>({
|
|
|
187
181
|
});
|
|
188
182
|
|
|
189
183
|
const [formLabel, setFormLabel] = useState<ReactNode>();
|
|
190
|
-
useImperativeHandle(ref, () => ({ value: formValue, name }), [formValue, name]);
|
|
191
184
|
const refdChildren = useOptionListWithIndexes({ children });
|
|
192
185
|
|
|
193
186
|
const searchOnSubmit = useCallback(() => {
|
|
@@ -251,6 +244,7 @@ function useDropdown<T>({
|
|
|
251
244
|
return {
|
|
252
245
|
isOpen,
|
|
253
246
|
referenceProps: getReferenceProps({
|
|
247
|
+
ref,
|
|
254
248
|
onKeyDown: referenceKeyDown,
|
|
255
249
|
}),
|
|
256
250
|
floatingProps,
|
|
@@ -265,11 +259,12 @@ function useDropdown<T>({
|
|
|
265
259
|
};
|
|
266
260
|
}
|
|
267
261
|
|
|
268
|
-
const Dropdown = forwardRef<
|
|
262
|
+
const Dropdown = forwardRef<Element, DropdownProps<string | null>>(
|
|
269
263
|
(
|
|
270
264
|
{
|
|
271
265
|
dropdownMaxHeight,
|
|
272
266
|
dropdownMinHeight,
|
|
267
|
+
dropdownWidth,
|
|
273
268
|
readOnly,
|
|
274
269
|
onBlur,
|
|
275
270
|
placeholder,
|
|
@@ -297,6 +292,7 @@ const Dropdown = forwardRef<DropdownInstance<string | null>, DropdownProps<strin
|
|
|
297
292
|
name,
|
|
298
293
|
readOnly,
|
|
299
294
|
optionsRef,
|
|
295
|
+
dropdownWidth,
|
|
300
296
|
ref,
|
|
301
297
|
...props,
|
|
302
298
|
});
|
|
@@ -350,7 +346,7 @@ const Dropdown = forwardRef<DropdownInstance<string | null>, DropdownProps<strin
|
|
|
350
346
|
|
|
351
347
|
export function typedDropdown<T>() {
|
|
352
348
|
return {
|
|
353
|
-
Dropdown: Dropdown as React.ForwardRefExoticComponent<DropdownProps<T> & React.RefAttributes<
|
|
349
|
+
Dropdown: Dropdown as React.ForwardRefExoticComponent<DropdownProps<T> & React.RefAttributes<Element>>,
|
|
354
350
|
DropdownOption: DropdownOption as (p: DropdownOptionProps<T>) => JSX.Element,
|
|
355
351
|
};
|
|
356
352
|
}
|
|
@@ -9,10 +9,28 @@ import {
|
|
|
9
9
|
useRole,
|
|
10
10
|
useDismiss,
|
|
11
11
|
flip,
|
|
12
|
+
UseFloatingProps,
|
|
13
|
+
MiddlewareArguments,
|
|
12
14
|
} from '@floating-ui/react-dom-interactions';
|
|
15
|
+
import { ChakraProps } from '@chakra-ui/react';
|
|
16
|
+
import { mergeRefs } from '@chakra-ui/react-utils';
|
|
13
17
|
import useAutoScroll from './useAutoScroll';
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
type ApplyFnArgs = MiddlewareArguments & {
|
|
20
|
+
availableWidth: number;
|
|
21
|
+
availableHeight: number;
|
|
22
|
+
};
|
|
23
|
+
const useFloatingDropdown = ({
|
|
24
|
+
enabled,
|
|
25
|
+
optionsRef,
|
|
26
|
+
dropdownWidth,
|
|
27
|
+
placement,
|
|
28
|
+
}: {
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
optionsRef: RefObject<HTMLDivElement>;
|
|
31
|
+
dropdownWidth: ChakraProps['width'] | 'match';
|
|
32
|
+
placement: UseFloatingProps['placement'] | undefined;
|
|
33
|
+
}) => {
|
|
16
34
|
const listRef = useRef<HTMLElement[]>([]);
|
|
17
35
|
const [isOpen, setOpen] = useState(false);
|
|
18
36
|
const [activeIndex, setActiveIndex] = useState<number | null>(null);
|
|
@@ -22,18 +40,21 @@ const useFloatingDropdown = ({ enabled, optionsRef }: { enabled: boolean; option
|
|
|
22
40
|
setKeyboardControlled(true);
|
|
23
41
|
}, [isOpen]);
|
|
24
42
|
|
|
25
|
-
const
|
|
43
|
+
const widthRef = useRef(dropdownWidth);
|
|
44
|
+
widthRef.current = dropdownWidth;
|
|
45
|
+
const { context, reference, floating, strategy, x, y } = useFloating<Element>({
|
|
26
46
|
open: enabled && isOpen,
|
|
27
47
|
onOpenChange: setOpen,
|
|
48
|
+
placement,
|
|
28
49
|
whileElementsMounted(aRef, aFloat, update) {
|
|
29
50
|
return autoUpdate(aRef, aFloat, update, { elementResize: false });
|
|
30
51
|
},
|
|
31
52
|
middleware: [
|
|
32
53
|
size({
|
|
33
54
|
padding: 5,
|
|
34
|
-
apply({ elements, availableHeight, rects }) {
|
|
55
|
+
apply: ({ elements, availableHeight, rects }: ApplyFnArgs) => {
|
|
35
56
|
Object.assign(elements.floating.style, {
|
|
36
|
-
width: `${rects.reference.width}px
|
|
57
|
+
width: widthRef.current === 'match' ? `${rects.reference.width}px` : widthRef.current,
|
|
37
58
|
});
|
|
38
59
|
elements.floating.style.setProperty('--floating-available-height', `${availableHeight}px`);
|
|
39
60
|
},
|
|
@@ -91,8 +112,9 @@ const useFloatingDropdown = ({ enabled, optionsRef }: { enabled: boolean; option
|
|
|
91
112
|
activeIndex,
|
|
92
113
|
setActiveIndex,
|
|
93
114
|
setSelectedIndex,
|
|
94
|
-
getReferenceProps(props: React.HTMLProps<Element>) {
|
|
95
|
-
|
|
115
|
+
getReferenceProps({ ref, ...props }: React.HTMLProps<Element>) {
|
|
116
|
+
const merged = mergeRefs(ref as any, reference);
|
|
117
|
+
return getReferenceProps({ ...props, ref: merged });
|
|
96
118
|
},
|
|
97
119
|
floatingProps: getFloatingProps({
|
|
98
120
|
ref: floating,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { useForm } from 'react-hook-form';
|
|
4
|
+
import Textarea from './Textarea';
|
|
5
|
+
|
|
6
|
+
describe('Textarea', () => {
|
|
7
|
+
it('forwards data-testid', async () => {
|
|
8
|
+
render(<Textarea data-testid="testid" />);
|
|
9
|
+
const elem = await screen.findByTestId('testid');
|
|
10
|
+
expect(elem.tagName).toBe('TEXTAREA');
|
|
11
|
+
expect(elem).toBeInTheDocument();
|
|
12
|
+
});
|
|
13
|
+
describe('react-hook-form support', () => {
|
|
14
|
+
it('sends data when submitted', async () => {
|
|
15
|
+
const handler = jest.fn();
|
|
16
|
+
const TestComponent = () => {
|
|
17
|
+
const { register, handleSubmit } = useForm();
|
|
18
|
+
return (
|
|
19
|
+
<form onSubmit={handleSubmit((data) => handler(data))}>
|
|
20
|
+
<Textarea {...register('textarea')} />
|
|
21
|
+
<button type="submit">Send</button>
|
|
22
|
+
</form>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
render(<TestComponent />);
|
|
26
|
+
const submit = await screen.findByRole('button', { name: 'Send' });
|
|
27
|
+
const textarea = await screen.findByRole('textbox');
|
|
28
|
+
await userEvent.type(textarea, 'test');
|
|
29
|
+
await userEvent.click(submit);
|
|
30
|
+
|
|
31
|
+
expect(handler).toHaveBeenCalledWith({ textarea: 'test' });
|
|
32
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -10,13 +10,15 @@ import {
|
|
|
10
10
|
forwardRef,
|
|
11
11
|
} from '@chakra-ui/react';
|
|
12
12
|
|
|
13
|
-
export interface TextareaProps extends Omit<FormControlProps, 'label' | 'onChange'> {
|
|
14
|
-
|
|
13
|
+
export interface TextareaProps extends Omit<FormControlProps, 'label' | 'onChange' | 'onBlur'> {
|
|
14
|
+
'data-testid'?: string;
|
|
15
15
|
errorText?: string;
|
|
16
16
|
isLoading?: boolean;
|
|
17
17
|
helpherText?: string;
|
|
18
18
|
label?: ReactNode;
|
|
19
|
+
name?: string;
|
|
19
20
|
onChange?: ChakraTextareaProps['onChange'];
|
|
21
|
+
onBlur?: ChakraTextareaProps['onBlur'];
|
|
20
22
|
value?: ChakraTextareaProps['value'];
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -25,7 +27,7 @@ export interface TextareaProps extends Omit<FormControlProps, 'label' | 'onChang
|
|
|
25
27
|
*/
|
|
26
28
|
const Textarea = forwardRef<TextareaProps, 'div'>((props, ref) => {
|
|
27
29
|
const {
|
|
28
|
-
dataTestid,
|
|
30
|
+
'data-testid': dataTestid,
|
|
29
31
|
errorText,
|
|
30
32
|
helpherText,
|
|
31
33
|
isDisabled,
|
|
@@ -34,12 +36,16 @@ const Textarea = forwardRef<TextareaProps, 'div'>((props, ref) => {
|
|
|
34
36
|
label,
|
|
35
37
|
placeholder,
|
|
36
38
|
onChange,
|
|
39
|
+
onBlur,
|
|
40
|
+
name,
|
|
37
41
|
value,
|
|
38
42
|
...rest
|
|
39
43
|
} = props;
|
|
40
44
|
const textareaProps = {
|
|
41
|
-
dataTestid,
|
|
45
|
+
'data-testid': dataTestid,
|
|
42
46
|
onChange,
|
|
47
|
+
onBlur,
|
|
48
|
+
name,
|
|
43
49
|
placeholder,
|
|
44
50
|
value,
|
|
45
51
|
};
|
|
@@ -132,13 +132,14 @@ ExpandableRows.args = {
|
|
|
132
132
|
<TableCaption description="Click on a row">Expandable rows</TableCaption>
|
|
133
133
|
<Thead>
|
|
134
134
|
<Tr>
|
|
135
|
+
<Th />
|
|
135
136
|
<Th colSpan={2}>ID</Th>
|
|
136
137
|
<Th>Status</Th>
|
|
137
138
|
<Th>Time</Th>
|
|
138
139
|
</Tr>
|
|
139
140
|
</Thead>
|
|
140
141
|
<Tbody>
|
|
141
|
-
<Tr expandableContent={<Box paddingY="12">😎</Box>}>
|
|
142
|
+
<Tr expandableContent={<Box paddingY="12">😎</Box>} defaultIsExpanded>
|
|
142
143
|
<Td>f6963af857c8f2fe</Td>
|
|
143
144
|
<Td>Success</Td>
|
|
144
145
|
<Td>May 29, 2022 10:11:32am</Td>
|
|
@@ -148,6 +149,12 @@ ExpandableRows.args = {
|
|
|
148
149
|
<Td>Aborted</Td>
|
|
149
150
|
<Td>May 29, 2022 9:12:50am</Td>
|
|
150
151
|
</Tr>
|
|
152
|
+
<Tr>
|
|
153
|
+
<Td />
|
|
154
|
+
<Td>f6963af857c8f2fe</Td>
|
|
155
|
+
<Td>Failed</Td>
|
|
156
|
+
<Td>May 29, 2022 10:11:32am</Td>
|
|
157
|
+
</Tr>
|
|
151
158
|
</Tbody>
|
|
152
159
|
</>
|
|
153
160
|
),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Children, ReactNode } from 'react';
|
|
1
|
+
import { Children, ReactNode, useCallback } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Collapse,
|
|
4
4
|
Tr as ChakraTr,
|
|
@@ -10,19 +10,33 @@ import {
|
|
|
10
10
|
import IconButton from '../IconButton/IconButton';
|
|
11
11
|
import Td from './Td';
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
expandableContent?: ReactNode | undefined;
|
|
13
|
+
type RowProps = ChakraTableRowProps & {
|
|
15
14
|
/**
|
|
16
15
|
* @deprecated - Please use it only when really necessary, we are working on a new table-like components with clickable items
|
|
17
16
|
*/
|
|
18
17
|
onClick?: ChakraTableRowProps['onClick'];
|
|
19
|
-
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type NonExpandableProps = ChakraTableRowProps & {
|
|
21
|
+
expandableContent?: never;
|
|
22
|
+
defaultIsExpanded?: never;
|
|
23
|
+
onExpandedChanged?: never;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type ExpandableProps = ChakraTableRowProps & {
|
|
27
|
+
expandableContent: ReactNode | undefined;
|
|
28
|
+
defaultIsExpanded?: boolean;
|
|
29
|
+
onExpandedChanged?(isExpanded: boolean): void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type TableRowProps = RowProps & (NonExpandableProps | ExpandableProps);
|
|
20
33
|
|
|
21
34
|
const Tr = forwardRef<TableRowProps, 'tr'>((props, ref) => {
|
|
22
|
-
const {
|
|
35
|
+
const { children, defaultIsExpanded, onExpandedChanged, expandableContent, onClick, ...rest } = props;
|
|
36
|
+
|
|
37
|
+
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: defaultIsExpanded });
|
|
23
38
|
const css = useTableStyles();
|
|
24
39
|
|
|
25
|
-
const { children, expandableContent, onClick, ...rest } = props;
|
|
26
40
|
const properties: ChakraTableRowProps = {
|
|
27
41
|
onClick,
|
|
28
42
|
...rest,
|
|
@@ -32,7 +46,12 @@ const Tr = forwardRef<TableRowProps, 'tr'>((props, ref) => {
|
|
|
32
46
|
properties.sx = css.clickableTr;
|
|
33
47
|
}
|
|
34
48
|
|
|
35
|
-
|
|
49
|
+
const onToggleClick = useCallback(() => {
|
|
50
|
+
const nextOpen = !isOpen;
|
|
51
|
+
onToggle();
|
|
52
|
+
onExpandedChanged?.(nextOpen);
|
|
53
|
+
}, [isOpen, onToggle, onExpandedChanged]);
|
|
54
|
+
|
|
36
55
|
if (expandableContent) {
|
|
37
56
|
const colSpan = Children.count(children) + 1;
|
|
38
57
|
return (
|
|
@@ -42,7 +61,7 @@ const Tr = forwardRef<TableRowProps, 'tr'>((props, ref) => {
|
|
|
42
61
|
<IconButton
|
|
43
62
|
iconName="ChevronDown"
|
|
44
63
|
aria-label="Toggle current row"
|
|
45
|
-
onClick={
|
|
64
|
+
onClick={onToggleClick}
|
|
46
65
|
size="small"
|
|
47
66
|
variant="tertiary"
|
|
48
67
|
sx={{
|