@bitrise/bitkit 10.20.1 → 10.21.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bitrise/bitkit",
3
3
  "description": "Bitrise React component library",
4
- "version": "10.20.1",
4
+ "version": "10.21.0",
5
5
  "repository": "git@github.com:bitrise-io/bitkit.git",
6
6
  "main": "src/index.ts",
7
7
  "license": "UNLICENSED",
@@ -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?: string;
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<T> = {
136
- ref: React.Ref<DropdownInstance<T>>;
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<T>) {
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<DropdownInstance<string | null>, DropdownProps<string | null>>(
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<DropdownInstance<T>>>,
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
- const useFloatingDropdown = ({ enabled, optionsRef }: { enabled: boolean; optionsRef: RefObject<HTMLDivElement> }) => {
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 { context, reference, floating, strategy, x, y } = useFloating({
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
- return getReferenceProps({ ...props, ref: reference });
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,