@fibery/ui-kit 1.25.0 → 1.25.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fibery/ui-kit",
3
- "version": "1.25.0",
3
+ "version": "1.25.1",
4
4
  "private": false,
5
5
  "files": [
6
6
  "src/antd/styles.ts",
@@ -103,8 +103,8 @@
103
103
  "svgo": "2.8.0",
104
104
  "typescript": "5.1.6",
105
105
  "unist-util-reduce": "0.2.2",
106
- "@fibery/babel-preset": "7.3.0",
107
- "@fibery/eslint-config": "8.5.0"
106
+ "@fibery/eslint-config": "8.5.0",
107
+ "@fibery/babel-preset": "7.3.0"
108
108
  },
109
109
  "jest": {
110
110
  "testEnvironment": "jsdom",
@@ -1,11 +1,12 @@
1
- import {ForwardedRef, forwardRef, HTMLAttributes, PropsWithChildren, ReactNode} from "react";
1
+ import {ForwardedRef, forwardRef, HTMLAttributes, PropsWithChildren, ReactNode, useId} from "react";
2
2
  import {border, colors, layout, lineHeight, space, textStyles, themeVars} from "../design-system";
3
3
  import {css, cx} from "@linaria/core";
4
4
  import {ActionsMenuItemProps} from "../actions-menu/actions-menu-item";
5
5
  import {useActionsMenuContext} from "../actions-menu";
6
6
  import {stopPropagation} from "@fibery/react/src/stop-propagation";
7
- import {useSetActiveDangerousRow} from "../actions-menu/contexts/actions-menu-dangerous-rows";
7
+ import {useActiveDangerousRow, useSetActiveDangerousRow} from "../actions-menu/contexts/actions-menu-dangerous-rows";
8
8
  import _ from "lodash";
9
+ import {useCallbackRef} from "@fibery/react/src/use-callback-ref";
9
10
 
10
11
  interface ListRowSurfaceBase extends HTMLAttributes<HTMLDivElement> {
11
12
  testId?: string;
@@ -19,11 +20,7 @@ export interface ListRowSurfaceProps extends ListRowSurfaceBase {
19
20
  }
20
21
  interface ActionMenuItemSurfaceProps
21
22
  extends Omit<ActionsMenuItemProps, "description">,
22
- Omit<ListRowSurfaceProps, "children" | "darker" | "onSelect"> {
23
- id?: string;
24
- showWarning?: boolean;
25
- onSelect?: (e: Event) => void;
26
- }
23
+ Omit<ListRowSurfaceProps, "children" | "darker" | "onSelect" | "id"> {}
27
24
  export const NATUAL_HOVER_CLASS = "natural-hover";
28
25
  export const NATUAL_FOCUS_CLASS = "natural-focus";
29
26
  export const NATURAL_HOVER = `&.${NATUAL_HOVER_CLASS}:hover`;
@@ -227,27 +224,14 @@ export const ListRowSurface = forwardRef<HTMLDivElement, ListRowSurfaceProps>(
227
224
  }
228
225
  );
229
226
  export const ActionMenuItemSurface = forwardRef<HTMLDivElement, ActionMenuItemSurfaceProps>(
230
- (
231
- {
232
- testId,
233
- className,
234
- loading,
235
- id,
236
- selected,
237
- hovered,
238
- focused,
239
- title,
240
- disabled,
241
- children,
242
- dangerous,
243
- showWarning,
244
- onSelect = _.noop,
245
- },
246
- ref
247
- ) => {
227
+ ({testId, className, loading, selected, hovered, focused, title, disabled, children, dangerous, ...props}, ref) => {
228
+ const id = useId();
229
+ const activeDangerousRow = useActiveDangerousRow();
230
+ const showWarning = activeDangerousRow === id;
231
+ const onSelect = useCallbackRef(props.onSelect || _.noop);
248
232
  const {MenuPrimitive} = useActionsMenuContext();
249
233
  const setActiveDangerousRow = useSetActiveDangerousRow();
250
- const handleSelect = async (e: Event) => {
234
+ const handleSelect = (e: Event) => {
251
235
  e.stopPropagation();
252
236
 
253
237
  if (loading || !id) {
@@ -1,6 +1,6 @@
1
1
  import {GroupHeadingProps, GroupProps, MenuListProps} from "react-select";
2
2
  import {ItemContent, Virtuoso, VirtuosoHandle} from "react-virtuoso";
3
- import {Children, ComponentType, ReactNode, useContext, useEffect, useRef} from "react";
3
+ import {Children, ComponentType, ReactNode, useContext, useEffect, useRef, useState} from "react";
4
4
  import {GroupBase, ReactSelectRefContext} from "../util";
5
5
  import {layout} from "../../../src/design-system";
6
6
  import _, {isFunction, isObject} from "lodash";
@@ -89,11 +89,17 @@ export function MenuListVirtualized<
89
89
  ) : null;
90
90
  };
91
91
  const select = useContext(ReactSelectRefContext);
92
+ const [initialFocusedOption, setInitialFocusedOption] = useState<Option | null>(null);
92
93
 
93
94
  const flattenChildren = flattenGroupChildren(children, renderGroup);
94
95
  //fix for select not having focused option when open. if no focused item present, at least first option must be focused.
95
- if (select.current && flattenChildren.length && !focusedOption) {
96
- let fitstExistingOption: unknown;
96
+ //Unfortunately, we can not rely on select focusedOption to determine option that should be focused initially:
97
+ //to mark selected option react-select uses value field of Option object.
98
+ //to define initially focused option react select uses whole Option object reference equality which is not always the case
99
+ //if select value is not exactly same object as option, we end up having initially focusedOption being just first option.
100
+ //fix below ensures that initial focusedOption is always selectedOption in cost of one more rerender
101
+ if (select.current && flattenChildren.length && !initialFocusedOption) {
102
+ let firstExistingOption: unknown;
97
103
  let selectedOption: unknown;
98
104
  //if an options exists, but there is no focused option, looking for first selected / existing option
99
105
  for (let i = 0; i < flattenChildren.length; i++) {
@@ -113,23 +119,25 @@ export function MenuListVirtualized<
113
119
  selectedOption = child.props.data;
114
120
  break;
115
121
  }
116
- if (!fitstExistingOption) {
117
- fitstExistingOption = child.props.data;
122
+ if (!firstExistingOption) {
123
+ firstExistingOption = child.props.data;
118
124
  }
119
125
  }
120
126
  }
121
127
  }
122
128
  if (selectedOption) {
129
+ setInitialFocusedOption(selectedOption as Option);
123
130
  select.current.setState({focusedOption: selectedOption});
124
- } else if (fitstExistingOption) {
125
- select.current.setState({focusedOption: fitstExistingOption});
131
+ } else if (firstExistingOption) {
132
+ setInitialFocusedOption(firstExistingOption as Option);
133
+ select.current.setState({focusedOption: firstExistingOption});
126
134
  }
127
135
  }
128
136
  const menuHeight = Math.min(flattenChildren.length * layout.menuItemHeight, maxHeight);
129
137
  //scrolling to focused option
130
138
  useEffect(() => {
131
139
  let to: ReturnType<typeof setTimeout> | undefined;
132
- if (focusedOption && virtuoso.current) {
140
+ if (initialFocusedOption && focusedOption && virtuoso.current) {
133
141
  const index = flattenChildren.findIndex((child) => {
134
142
  if (isObject(child) && "props" in child) {
135
143
  if (isObject(child.props) && "data" in child.props) {
@@ -167,7 +175,7 @@ export function MenuListVirtualized<
167
175
  return () => {
168
176
  clearTimeout(to);
169
177
  };
170
- }, [focusedOption, flattenChildren, menuHeight, virtuoso]);
178
+ }, [focusedOption, flattenChildren, menuHeight, initialFocusedOption, virtuoso]);
171
179
  return (
172
180
  // Needed for correct display inside portals and etc.
173
181
  <div>
@@ -10,11 +10,11 @@ const noOptionMessageClass = css`
10
10
  `;
11
11
  const noOptionMessageContentClass = css`
12
12
  padding: 0 ${space.s6}px;
13
- line-height: ${layout.menuItemHeight}px;
14
13
  white-space: nowrap;
15
14
  overflow: hidden;
16
15
  text-overflow: ellipsis;
17
16
  ${{...textStyles.regular}}
17
+ line-height: ${layout.menuItemHeight}px;
18
18
  `;
19
19
 
20
20
  export const NoOptionsMessage = ({children}: {children: ReactNode}) => {
@@ -1,57 +0,0 @@
1
- import {css} from "@linaria/core";
2
- import {layout, border, space, textStyles, themeVars} from "./design-system";
3
- import {Button} from "./button/button";
4
- import CloseIcon from "./icons/react/Close";
5
- import {useTheme} from "./theme-provider";
6
-
7
- const errorAlert = css`
8
- background-color: ${themeVars.errorBgColor};
9
- padding: ${space.s8}px ${space.s12}px;
10
- min-height: ${layout.itemHeight}px;
11
- border-radius: ${border.radius6}px;
12
- display: flex;
13
- gap: ${space.s6}px;
14
- align-items: center;
15
- `;
16
-
17
- const messageStyle = css`
18
- ${textStyles.heading5};
19
- color: ${themeVars.errorTextColor};
20
- `;
21
-
22
- const descriptionStyle = css`
23
- ${textStyles.regular};
24
- color: ${themeVars.errorTextColor};
25
- `;
26
-
27
- export function ErrorAlert({
28
- message,
29
- description,
30
- closable,
31
- onClose,
32
- }: {
33
- message: string;
34
- description?: React.ReactNode;
35
- closable?: boolean;
36
- onClose?: () => void;
37
- }): JSX.Element {
38
- const theme = useTheme();
39
- const buttonColor = theme.errorButtonColor;
40
- return (
41
- <div className={errorAlert}>
42
- <div
43
- className={css`
44
- flex-grow: 2;
45
- `}
46
- >
47
- <div className={messageStyle}>{message}</div>
48
- {description ? <div className={descriptionStyle}>{description}</div> : null}
49
- </div>
50
- {closable && onClose ? (
51
- <div>
52
- <Button borderless onClick={onClose} color={buttonColor} Icon={CloseIcon} width={24} />
53
- </div>
54
- ) : null}
55
- </div>
56
- );
57
- }