@ultraviolet/ui 1.46.0 → 1.47.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/index.d.ts CHANGED
@@ -1334,7 +1334,7 @@ type AvatarProps = {
1334
1334
  declare const Avatar: ({ image, size, text, textBgColor, textColor, textSize, lock, className, "data-testid": dataTestId, }: AvatarProps) => _emotion_react_jsx_runtime.JSX.Element;
1335
1335
 
1336
1336
  type IconName$1 = ComponentProps<typeof Icon>['name'];
1337
- declare const SIZES: {
1337
+ declare const SIZES$1: {
1338
1338
  large: number;
1339
1339
  medium: number;
1340
1340
  small: number;
@@ -1345,7 +1345,7 @@ declare const PROMINENCES$3: {
1345
1345
  };
1346
1346
  type BadgeProps = {
1347
1347
  sentiment?: Color;
1348
- size?: keyof typeof SIZES;
1348
+ size?: keyof typeof SIZES$1;
1349
1349
  prominence?: keyof typeof PROMINENCES$3;
1350
1350
  /**
1351
1351
  * Defines icon to display on left side of badge. **Only available on medium and large sizes**.
@@ -2261,7 +2261,7 @@ declare const Popover: react.ForwardRefExoticComponent<{
2261
2261
  onKeyDown?: react.KeyboardEventHandler | undefined;
2262
2262
  'aria-haspopup'?: boolean | "menu" | "dialog" | "grid" | "listbox" | "tree" | "false" | "true" | undefined;
2263
2263
  hideOnClickOutside?: boolean | undefined;
2264
- needDebounce?: boolean | undefined;
2264
+ debounceDelay?: number | undefined;
2265
2265
  maxHeight?: string | number | undefined;
2266
2266
  disableAnimation?: boolean | undefined;
2267
2267
  portalTarget?: HTMLElement | undefined;
@@ -2307,7 +2307,11 @@ type PopupProps = {
2307
2307
  onKeyDown?: KeyboardEventHandler;
2308
2308
  'aria-haspopup'?: HTMLAttributes<HTMLDivElement>['aria-haspopup'];
2309
2309
  hideOnClickOutside?: boolean;
2310
- needDebounce?: boolean;
2310
+ /**
2311
+ * If you set debounceTime to false, the popup will not debounce the hover event and will be displayed instantly.
2312
+ * If set to 0 it will disable debounce.
2313
+ */
2314
+ debounceDelay?: number;
2311
2315
  /**
2312
2316
  * If you set a max height keep in mind that the animation is disabled, or it will not work properly on some browsers.
2313
2317
  */
@@ -2794,7 +2798,8 @@ declare const Tabs: {
2794
2798
  children: ReactNode;
2795
2799
  onClick?: react.MouseEventHandler<HTMLElement> | undefined;
2796
2800
  borderless?: boolean | undefined;
2797
- sentiment?: ("neutral" | "danger") | undefined;
2801
+ sentiment?: ("primary" | "neutral" | "danger") | undefined;
2802
+ active?: boolean | undefined;
2798
2803
  'data-testid'?: string | undefined;
2799
2804
  } & react.RefAttributes<HTMLElement> & {
2800
2805
  theme?: _emotion_react.Theme | undefined;
@@ -3201,7 +3206,7 @@ declare const ToggleGroup: {
3201
3206
  Toggle: ({ disabled, name, value, label, helper, className, "data-testid": dataTestId, }: ToggleGroupToggleProps) => _emotion_react_jsx_runtime.JSX.Element;
3202
3207
  };
3203
3208
 
3204
- type TooltipProps = Pick<ComponentProps<typeof Popup>, 'id' | 'children' | 'maxWidth' | 'placement' | 'text' | 'className' | 'visible' | 'innerRef' | 'role' | 'data-testid' | 'containerFullWidth' | 'portalTarget' | 'tabIndex'>;
3209
+ type TooltipProps = Pick<ComponentProps<typeof Popup>, 'id' | 'children' | 'maxWidth' | 'placement' | 'text' | 'className' | 'visible' | 'innerRef' | 'role' | 'data-testid' | 'containerFullWidth' | 'portalTarget' | 'tabIndex' | 'debounceDelay'>;
3205
3210
  /**
3206
3211
  * Tooltip component is used to display additional information on hover or focus.
3207
3212
  * It is used to explain the purpose of the element it is attached to.
@@ -3265,6 +3270,11 @@ declare const RadioGroup: {
3265
3270
  Radio: ({ onFocus, onBlur, disabled, error, name, value, label, helper, className, autoFocus, onKeyDown, "data-testid": dataTestId, }: RadioGroupRadioProps) => _emotion_react_jsx_runtime.JSX.Element;
3266
3271
  };
3267
3272
 
3273
+ declare const SIZES: {
3274
+ small: string;
3275
+ medium: string;
3276
+ large: string;
3277
+ };
3268
3278
  type DisclosureProps = {
3269
3279
  visible: boolean;
3270
3280
  };
@@ -3285,12 +3295,21 @@ type MenuProps = {
3285
3295
  visible?: boolean;
3286
3296
  'data-testid'?: string;
3287
3297
  maxHeight?: string;
3298
+ /**
3299
+ * @deprecated: use `size` instead
3300
+ */
3288
3301
  maxWidth?: string;
3289
3302
  /**
3290
3303
  * By default, the portal target is children container or document.body if children is a function. You can override this
3291
3304
  * behavior by setting a portalTarget prop.
3292
3305
  */
3293
3306
  portalTarget?: HTMLElement;
3307
+ size?: keyof typeof SIZES;
3308
+ /**
3309
+ * The behavior of the menu when it is opened. If set to `click`, the menu will open when the user clicks on the disclosure.
3310
+ * If set to `hover`, the menu will open when the user hovers over the disclosure.
3311
+ */
3312
+ triggerMethod?: 'click' | 'hover';
3294
3313
  };
3295
3314
  /**
3296
3315
  * A menu is a widget that offers a list of choices to the user, such as a set of actions or functions.
@@ -3307,9 +3326,14 @@ declare const MenuV2: react.ForwardRefExoticComponent<MenuProps & react.RefAttri
3307
3326
  children: ReactNode;
3308
3327
  onClick?: react.MouseEventHandler<HTMLElement> | undefined;
3309
3328
  borderless?: boolean | undefined;
3310
- sentiment?: ("neutral" | "danger") | undefined;
3329
+ sentiment?: ("primary" | "neutral" | "danger") | undefined;
3330
+ active?: boolean | undefined;
3311
3331
  'data-testid'?: string | undefined;
3312
3332
  } & react.RefAttributes<HTMLElement>>;
3333
+ Group: ({ label, children }: {
3334
+ label: string;
3335
+ children: ReactNode;
3336
+ }) => _emotion_react_jsx_runtime.JSX.Element;
3313
3337
  };
3314
3338
 
3315
3339
  type GlobalAlertProps = {
@@ -0,0 +1,25 @@
1
+ import _styled from '@emotion/styled/base';
2
+ import { Text } from '../Text/index.js';
3
+ import { jsxs, Fragment, jsx } from '@emotion/react/jsx-runtime';
4
+
5
+ const Container = /*#__PURE__*/_styled("span", {
6
+ target: "e7u2rl20"
7
+ })("padding:", ({
8
+ theme
9
+ }) => `${theme.space['0.5']} ${theme.space['1.5']}`, ";text-align:left;");
10
+ const Group = ({
11
+ label,
12
+ children
13
+ }) => jsxs(Fragment, {
14
+ children: [jsx(Container, {
15
+ children: jsx(Text, {
16
+ variant: "captionStrong",
17
+ as: "span",
18
+ prominence: "weak",
19
+ sentiment: "neutral",
20
+ children: label
21
+ })
22
+ }), children]
23
+ });
24
+
25
+ export { Group };
@@ -3,22 +3,28 @@ import { forwardRef } from 'react';
3
3
  import { Tooltip } from '../Tooltip/index.js';
4
4
  import { jsx } from '@emotion/react/jsx-runtime';
5
5
 
6
+ const ANIMATION_DURATION = 200; // in ms
7
+
6
8
  const itemCoreStyle = ({
7
9
  theme,
8
- borderless,
9
10
  sentiment,
10
11
  disabled
11
12
  }) => `
12
- display: inline-block;
13
+ display: flex;
14
+ justify-content: start;
15
+ align-items: center;
16
+ min-height: 32px;
17
+ max-height: 44px;
13
18
  font-size: ${theme.typography.bodySmall.fontSize};
14
19
  line-height: ${theme.typography.bodySmall.lineHeight};
15
20
  font-weight: inherit;
16
21
  padding: ${`${theme.space['0.5']} ${theme.space['1']}`};
17
22
  border: none;
18
- ${borderless ? '' : `border-bottom: 1px solid ${theme.colors.neutral.border};`}
19
23
  cursor: pointer;
20
24
  min-width: 110px;
21
25
  width: 100%;
26
+ border-radius: ${theme.radii.default};
27
+ transition: background-color ${ANIMATION_DURATION}ms, color ${ANIMATION_DURATION}ms;
22
28
 
23
29
  color: ${theme.colors[sentiment][disabled ? 'textDisabled' : 'text']};
24
30
  svg {
@@ -29,13 +35,24 @@ const itemCoreStyle = ({
29
35
  cursor: not-allowed;
30
36
  ` : `
31
37
  &:hover,
32
- &:focus {
38
+ &:focus, &[data-active='true'] {
39
+ background-color: ${theme.colors[sentiment].backgroundHover};
33
40
  color: ${theme.colors[sentiment].textHover};
34
41
  svg {
35
42
  fill: ${theme.colors[sentiment].textHover};
36
43
  }
37
44
  }`}
38
45
  `;
46
+ const Container = /*#__PURE__*/_styled('div', {
47
+ shouldForwardProp: prop => !['borderless'].includes(prop),
48
+ target: "ei26g5y2"
49
+ })(({
50
+ theme,
51
+ borderless
52
+ }) => borderless ? '' : `border-bottom: 1px solid ${theme.colors.neutral.border};`, " padding:", ({
53
+ theme,
54
+ borderless
55
+ }) => `${borderless ? theme.space['0.25'] : theme.space['0.5']} ${theme.space['0.5']}`, ";&:last-child{border:none;}");
39
56
  const StyledItem = /*#__PURE__*/_styled('button', {
40
57
  shouldForwardProp: prop => !['borderless', 'sentiment'].includes(prop),
41
58
  target: "ei26g5y1"
@@ -72,14 +89,17 @@ const Item = /*#__PURE__*/forwardRef(({
72
89
  href,
73
90
  children,
74
91
  tooltip,
92
+ active,
75
93
  className,
76
94
  'data-testid': dataTestId
77
95
  }, ref) => {
78
96
  if (href && !disabled) {
79
- return jsx("div", {
97
+ return jsx(Container, {
98
+ borderless: borderless,
80
99
  children: jsx(Tooltip, {
81
100
  text: tooltip,
82
101
  children: jsx(StyledLinkItem, {
102
+ "data-active": active,
83
103
  borderless: true,
84
104
  href: href,
85
105
  ref: ref,
@@ -94,7 +114,8 @@ const Item = /*#__PURE__*/forwardRef(({
94
114
  })
95
115
  });
96
116
  }
97
- return jsx("div", {
117
+ return jsx(Container, {
118
+ borderless: borderless,
98
119
  children: jsx(Tooltip, {
99
120
  text: tooltip,
100
121
  children: jsx(StyledItem, {
@@ -107,6 +128,7 @@ const Item = /*#__PURE__*/forwardRef(({
107
128
  sentiment: sentiment,
108
129
  className: className,
109
130
  "data-testid": dataTestId,
131
+ "data-active": active,
110
132
  children: children
111
133
  })
112
134
  })
@@ -2,10 +2,17 @@ import _styled from '@emotion/styled/base';
2
2
  import { forwardRef, useState, useRef, useId, isValidElement, useImperativeHandle, cloneElement } from 'react';
3
3
  import { Popup } from '../Popup/index.js';
4
4
  import { Stack } from '../Stack/index.js';
5
+ import { Group } from './Group.js';
5
6
  import Item from './Item.js';
6
7
  import { jsx } from '@emotion/react/jsx-runtime';
7
8
 
9
+ const SIZES = {
10
+ small: '180px',
11
+ medium: '280px',
12
+ large: '380px'
13
+ };
8
14
  const StyledPopup = /*#__PURE__*/_styled(Popup, {
15
+ shouldForwardProp: prop => !['size'].includes(prop),
9
16
  target: "e1jn11gg1"
10
17
  })("background-color:", ({
11
18
  theme
@@ -13,10 +20,14 @@ const StyledPopup = /*#__PURE__*/_styled(Popup, {
13
20
  theme
14
21
  }) => theme.shadows.menu, ";padding:0;&[data-has-arrow='true']{&::after{border-color:", ({
15
22
  theme
16
- }) => theme.colors.neutral.background, " transparent transparent transparent;}}");
23
+ }) => theme.colors.neutral.background, " transparent transparent transparent;}}width:", ({
24
+ size
25
+ }) => SIZES[size], ";max-width:none;padding:", ({
26
+ theme
27
+ }) => `${theme.space['0.25']} 0`, ";");
17
28
  const MenuList = /*#__PURE__*/_styled(Stack, {
18
29
  target: "e1jn11gg0"
19
- })("&:after,&:before{border:solid transparent;border-width:9px;content:' ';height:0;width:0;position:absolute;pointer-events:none;}&:after{border-color:transparent;}&:before{border-color:transparent;}background-color:", ({
30
+ })("max-height:480px;overflow-y:auto;overflow-x:hidden;&:after,&:before{border:solid transparent;border-width:9px;content:' ';height:0;width:0;position:absolute;pointer-events:none;}&:after{border-color:transparent;}&:before{border-color:transparent;}background-color:", ({
20
31
  theme
21
32
  }) => theme.colors.neutral.backgroundWeakElevated, ";color:", ({
22
33
  theme
@@ -28,14 +39,16 @@ const FwdMenu = /*#__PURE__*/forwardRef(({
28
39
  ariaLabel = 'Menu',
29
40
  children,
30
41
  disclosure,
31
- hasArrow = true,
42
+ hasArrow = false,
32
43
  placement = 'bottom',
33
44
  visible = false,
34
45
  className,
35
46
  'data-testid': dataTestId,
36
47
  maxHeight,
37
48
  maxWidth,
38
- portalTarget
49
+ portalTarget,
50
+ size = 'small',
51
+ triggerMethod = 'click'
39
52
  }, ref) => {
40
53
  const [isVisible, setIsVisible] = useState(visible);
41
54
  const popupRef = useRef(null);
@@ -60,11 +73,11 @@ const FwdMenu = /*#__PURE__*/forwardRef(({
60
73
  ref: disclosureRef
61
74
  });
62
75
  return jsx(StyledPopup, {
63
- needDebounce: false,
76
+ debounceDelay: triggerMethod === 'hover' ? 350 : 0,
64
77
  hideOnClickOutside: true,
65
78
  "aria-label": ariaLabel,
66
79
  className: className,
67
- visible: isVisible,
80
+ visible: triggerMethod === 'click' ? isVisible : undefined,
68
81
  placement: placement,
69
82
  hasArrow: hasArrow,
70
83
  "data-has-arrow": hasArrow,
@@ -75,6 +88,7 @@ const FwdMenu = /*#__PURE__*/forwardRef(({
75
88
  tabIndex: -1,
76
89
  maxHeight: maxHeight,
77
90
  maxWidth: maxWidth,
91
+ size: size,
78
92
  text: jsx(MenuList, {
79
93
  "data-testid": dataTestId,
80
94
  className: className,
@@ -95,7 +109,8 @@ const FwdMenu = /*#__PURE__*/forwardRef(({
95
109
  * When a user activates a choice in a menu, the menu usually closes unless the choice opened a submenu.
96
110
  */
97
111
  const MenuV2 = /*#__PURE__*/Object.assign(FwdMenu, {
98
- Item
112
+ Item,
113
+ Group
99
114
  });
100
115
 
101
116
  export { MenuV2 };
@@ -116,7 +116,7 @@ const Popover = /*#__PURE__*/forwardRef(({
116
116
  }, [onClose]);
117
117
  return jsx(StyledPopup, {
118
118
  hideOnClickOutside: true,
119
- needDebounce: false,
119
+ debounceDelay: 0,
120
120
  visible: localVisible,
121
121
  placement: placement,
122
122
  text: jsx(ContentWrapper, {
@@ -78,7 +78,7 @@ const Popup = /*#__PURE__*/forwardRef(({
78
78
  onKeyDown,
79
79
  'aria-haspopup': ariaHasPopup,
80
80
  hideOnClickOutside = false,
81
- needDebounce = true,
81
+ debounceDelay = DEFAULT_DEBOUNCE_DURATION,
82
82
  disableAnimation = false,
83
83
  portalTarget
84
84
  }, ref) => {
@@ -88,8 +88,8 @@ const Popup = /*#__PURE__*/forwardRef(({
88
88
  useImperativeHandle(ref, () => innerPopupRef.current);
89
89
  const timer = useRef();
90
90
  const popupPortalTarget = useMemo(() => {
91
+ if (portalTarget) return portalTarget;
91
92
  if (role === 'dialog') {
92
- if (portalTarget) return portalTarget;
93
93
  if (childrenRef.current) return childrenRef.current;
94
94
  if (typeof window !== 'undefined') return document.body;
95
95
  return null;
@@ -158,14 +158,20 @@ const Popup = /*#__PURE__*/forwardRef(({
158
158
  unmountPopupFromDom();
159
159
  onClose?.();
160
160
  }, animationDuration);
161
- }, needDebounce && !disableAnimation ? DEFAULT_DEBOUNCE_DURATION : 0);
162
- }, [animationDuration, disableAnimation, needDebounce, onClose, unmountPopupFromDom]);
161
+ }, debounceDelay && !disableAnimation ? debounceDelay : 0);
162
+ }, [animationDuration, disableAnimation, debounceDelay, onClose, unmountPopupFromDom]);
163
163
 
164
164
  /**
165
165
  * When mouse hover or stop hovering children this function display or hide popup. A timeout is set to allow animation
166
166
  * end, then remove popup from dom.
167
167
  */
168
168
  const onPointerEvent = useCallback(isVisible => () => {
169
+ // If there is a debounceDelay and the popup is not visible, we clear the debounce timer
170
+ if (!visible && debounceDelay > 0 && debounceTimer.current) {
171
+ clearTimeout(debounceTimer.current);
172
+ debounceTimer.current = undefined;
173
+ }
174
+
169
175
  // This condition is for when we want to unmount the popup
170
176
  // There is debounce in order to avoid popup to flicker when we move the mouse from children to popup
171
177
  // Timer is used to follow the animation duration
@@ -186,9 +192,15 @@ const Popup = /*#__PURE__*/forwardRef(({
186
192
  clearTimeout(debounceTimer.current);
187
193
  debounceTimer.current = undefined;
188
194
  }
189
- setVisibleInDom(true);
195
+ if (debounceDelay > 0) {
196
+ debounceTimer.current = setTimeout(() => {
197
+ setVisibleInDom(true);
198
+ }, debounceDelay);
199
+ } else {
200
+ setVisibleInDom(true);
201
+ }
190
202
  }
191
- }, [closePopup, innerPopupRef]);
203
+ }, [closePopup, debounceDelay, visible]);
192
204
 
193
205
  /**
194
206
  * Once popup is visible in the dom we can compute positions, then set it visible on screen and add event to
@@ -338,6 +350,8 @@ const Popup = /*#__PURE__*/forwardRef(({
338
350
  "data-testid": dataTestId,
339
351
  "data-has-arrow": hasArrow,
340
352
  onClick: stopClickPropagation,
353
+ onPointerEnter: !isControlled ? onPointerEvent(true) : noop,
354
+ onPointerLeave: !isControlled ? onPointerEvent(false) : noop,
341
355
  animationDuration: animationDuration,
342
356
  onKeyDown: role === 'dialog' ? handleFocusTrap : undefined,
343
357
  isDialog: role === 'dialog',
@@ -32,6 +32,7 @@ const Tooltip = /*#__PURE__*/forwardRef(({
32
32
  role = 'tooltip',
33
33
  'data-testid': dataTestId,
34
34
  portalTarget,
35
+ debounceDelay,
35
36
  tabIndex
36
37
  }, tooltipRef) => jsx(StyledPopup, {
37
38
  id: id,
@@ -47,6 +48,7 @@ const Tooltip = /*#__PURE__*/forwardRef(({
47
48
  innerRef: innerRef,
48
49
  portalTarget: portalTarget,
49
50
  tabIndex: tabIndex,
51
+ debounceDelay: debounceDelay,
50
52
  children: children
51
53
  }));
52
54
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultraviolet/ui",
3
- "version": "1.46.0",
3
+ "version": "1.47.0",
4
4
  "description": "Ultraviolet UI",
5
5
  "homepage": "https://github.com/scaleway/ultraviolet#readme",
6
6
  "repository": {
@@ -43,9 +43,9 @@
43
43
  "@emotion/babel-plugin": "11.11.0",
44
44
  "@emotion/react": "11.11.4",
45
45
  "@emotion/styled": "11.11.5",
46
- "@types/react": "18.2.74",
46
+ "@types/react": "18.2.78",
47
47
  "@types/react-datepicker": "4.19.6",
48
- "@types/react-dom": "18.2.24",
48
+ "@types/react-dom": "18.2.25",
49
49
  "react": "18.2.0",
50
50
  "react-dom": "18.2.0"
51
51
  },
@@ -65,7 +65,7 @@
65
65
  "react-use-clipboard": "1.0.9",
66
66
  "reakit": "1.3.11",
67
67
  "@ultraviolet/themes": "1.10.0",
68
- "@ultraviolet/icons": "2.12.3"
68
+ "@ultraviolet/icons": "2.12.4"
69
69
  },
70
70
  "scripts": {
71
71
  "build": "rollup -c ../../rollup.config.mjs",