@apify/ui-library 1.167.0 → 1.168.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.
Files changed (48) hide show
  1. package/dist/components/avatar/actor_avatar.d.ts +2 -0
  2. package/dist/components/avatar/actor_avatar.d.ts.map +1 -1
  3. package/dist/components/avatar/actor_avatar.js +6 -3
  4. package/dist/components/floating/tooltip.d.ts +1 -0
  5. package/dist/components/floating/tooltip.d.ts.map +1 -1
  6. package/dist/components/floating/tooltip.js +1 -0
  7. package/dist/components/icon_button/icon_button.d.ts +3 -2
  8. package/dist/components/icon_button/icon_button.d.ts.map +1 -1
  9. package/dist/components/readme_renderer/pythonize_value.d.ts.map +1 -1
  10. package/dist/components/readme_renderer/pythonize_value.js +1 -1
  11. package/dist/components/split_button/split_button.d.ts +1 -0
  12. package/dist/components/split_button/split_button.d.ts.map +1 -1
  13. package/dist/components/split_button/split_button.js +35 -33
  14. package/dist/components/store/store_actor_header.d.ts.map +1 -1
  15. package/dist/components/store/store_actor_header.js +1 -2
  16. package/dist/files_hash_at_build.txt +1 -1
  17. package/dist/index.js +2 -2
  18. package/dist/src/components/avatar/actor_avatar.d.ts +2 -0
  19. package/dist/src/components/avatar/actor_avatar.d.ts.map +1 -1
  20. package/dist/src/components/avatar/actor_avatar.js +5 -3
  21. package/dist/src/components/avatar/actor_avatar.js.map +1 -1
  22. package/dist/src/components/floating/tooltip.d.ts +1 -0
  23. package/dist/src/components/floating/tooltip.d.ts.map +1 -1
  24. package/dist/src/components/floating/tooltip.js +1 -0
  25. package/dist/src/components/floating/tooltip.js.map +1 -1
  26. package/dist/src/components/icon_button/icon_button.d.ts +3 -2
  27. package/dist/src/components/icon_button/icon_button.d.ts.map +1 -1
  28. package/dist/src/components/icon_button/icon_button.js.map +1 -1
  29. package/dist/src/components/readme_renderer/pythonize_value.d.ts.map +1 -1
  30. package/dist/src/components/readme_renderer/pythonize_value.js +2 -1
  31. package/dist/src/components/readme_renderer/pythonize_value.js.map +1 -1
  32. package/dist/src/components/split_button/split_button.d.ts +1 -0
  33. package/dist/src/components/split_button/split_button.d.ts.map +1 -1
  34. package/dist/src/components/split_button/split_button.js +6 -7
  35. package/dist/src/components/split_button/split_button.js.map +1 -1
  36. package/dist/src/components/store/store_actor_header.d.ts.map +1 -1
  37. package/dist/src/components/store/store_actor_header.js +1 -1
  38. package/dist/src/components/store/store_actor_header.js.map +1 -1
  39. package/dist/tsconfig.build.tsbuildinfo +1 -1
  40. package/package.json +3 -3
  41. package/src/components/avatar/actor_avatar.tsx +7 -3
  42. package/src/components/button/button.stories.tsx +43 -1
  43. package/src/components/floating/tooltip.tsx +1 -0
  44. package/src/components/icon_button/icon_button.tsx +2 -3
  45. package/src/components/readme_renderer/pythonize_value.ts +2 -1
  46. package/src/components/split_button/split_button.stories.tsx +16 -5
  47. package/src/components/split_button/split_button.tsx +35 -34
  48. package/src/components/store/store_actor_header.tsx +1 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apify/ui-library",
3
- "version": "1.167.0",
3
+ "version": "1.168.0",
4
4
  "description": "React UI library used by apify.com",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -75,7 +75,7 @@
75
75
  "styled-components": "^6.1.19"
76
76
  },
77
77
  "devDependencies": {
78
- "@apify-packages/types": "^3.453.2",
78
+ "@apify-packages/types": "^3.454.0",
79
79
  "@storybook/react-vite": "^10.3.5",
80
80
  "@testing-library/react": "^16.3.2",
81
81
  "@types/hast": "^3.0.4",
@@ -95,5 +95,5 @@
95
95
  "src",
96
96
  "style"
97
97
  ],
98
- "gitHead": "6295bb684477aec1dd61d5c1f0917162197f0de8"
98
+ "gitHead": "6a2d3eedb716e9e766ea4328f4ef3c5554fc6426"
99
99
  }
@@ -34,10 +34,13 @@ const StyledBoringAvatarMarble = styled(BoringAvatarMarble)<{ $borderRadius: str
34
34
  }
35
35
  `;
36
36
 
37
- const StyledImg = styled.img`
38
- border-radius: ${theme.radius.radius8};
37
+ const StyledImg = styled.img<{ $borderRadius: string | number }>`
38
+ border-radius: ${({ $borderRadius }) => $borderRadius};
39
39
  `;
40
40
 
41
+ /** Corner radius in px for an Actor avatar of the given size, shared with bordered avatars. */
42
+ export const getActorAvatarBorderRadius = (size: number) => size / 4 + 2;
43
+
41
44
  interface ActorAvatarProps extends React.ImgHTMLAttributes<HTMLImageElement> {
42
45
  name?: string;
43
46
  className?: string;
@@ -53,7 +56,7 @@ export const ActorAvatarInner: FC<ActorAvatarProps> = ({
53
56
  url,
54
57
  size,
55
58
  alt,
56
- borderRadius = theme.radius.radius8,
59
+ borderRadius = `${getActorAvatarBorderRadius(size)}px`,
57
60
  ...rest
58
61
  }) => {
59
62
  const [hasImageError, setHasImageError] = useState(false);
@@ -110,6 +113,7 @@ export const ActorAvatarInner: FC<ActorAvatarProps> = ({
110
113
  height={size}
111
114
  alt={altText}
112
115
  onError={onError}
116
+ $borderRadius={borderRadius}
113
117
  {...rest}
114
118
  />
115
119
  );
@@ -1,11 +1,13 @@
1
- import type { Meta } from '@storybook/react-vite';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  // eslint-disable-next-line import-x/extensions
3
3
  import { action } from 'storybook/actions';
4
+ import { expect, userEvent, waitFor, within } from 'storybook/test';
4
5
  import styled from 'styled-components';
5
6
 
6
7
  import { CrossIcon } from '@apify/ui-icons';
7
8
 
8
9
  import { theme } from '../../design_system/theme';
10
+ import { Tooltip } from '../floating';
9
11
  import { Button, type ButtonProps } from './button';
10
12
 
11
13
  export default {
@@ -219,3 +221,43 @@ export const Animated = () => {
219
221
  </Wrapper>
220
222
  );
221
223
  };
224
+
225
+ // Tooltip renders only its children when `content` is empty, so a conditional tooltip needs no branching.
226
+ export const WithTooltip: StoryObj = {
227
+ render: () => (
228
+ <Wrapper>
229
+ <ButtonGrid>
230
+ <Tooltip content="Saves the current draft" delayShow={0}>
231
+ <Button onClick={action('onClick')}>Save</Button>
232
+ </Tooltip>
233
+ <Tooltip content="Fill in all required fields first" delayShow={0}>
234
+ <Button onClick={action('onClick')} disabled>
235
+ Disabled with reason
236
+ </Button>
237
+ </Tooltip>
238
+ <Tooltip content={undefined}>
239
+ <Button onClick={action('onClick')}>No tooltip</Button>
240
+ </Tooltip>
241
+ </ButtonGrid>
242
+ </Wrapper>
243
+ ),
244
+ play: async ({ canvasElement }) => {
245
+ const canvas = within(canvasElement);
246
+
247
+ await userEvent.hover(canvas.getByRole('button', { name: 'Save' }));
248
+ await expect(await canvas.findByText('Saves the current draft')).toBeInTheDocument();
249
+ await userEvent.unhover(canvas.getByRole('button', { name: 'Save' }));
250
+
251
+ // A disabled button fires no pointer events, the tooltip's wrapping span does.
252
+ const disabledButton = canvas.getByRole('button', { name: 'Disabled with reason' });
253
+ await userEvent.hover(disabledButton.parentElement as HTMLElement);
254
+ await expect(await canvas.findByText('Fill in all required fields first')).toBeInTheDocument();
255
+ await userEvent.unhover(disabledButton.parentElement as HTMLElement);
256
+ await waitFor(async () =>
257
+ expect(canvas.queryByText('Fill in all required fields first')).not.toBeInTheDocument(),
258
+ );
259
+
260
+ const plainButton = canvas.getByRole('button', { name: 'No tooltip' });
261
+ await expect(plainButton.parentElement?.tagName).not.toBe('SPAN');
262
+ },
263
+ };
@@ -152,6 +152,7 @@ export const Tooltip = ({
152
152
  );
153
153
  };
154
154
 
155
+ /** @deprecated Wrap the component in `Tooltip` instead. */
155
156
  export function withTooltip<TProps extends Record<string, unknown> = Record<string, unknown>>(
156
157
  Component: ComponentType<TProps>,
157
158
  ) {
@@ -14,7 +14,7 @@ import {
14
14
  type RegularButtonProps,
15
15
  type TransientButtonProps,
16
16
  } from '../button';
17
- import { Tooltip } from '../floating';
17
+ import { Tooltip, type TooltipProps } from '../floating';
18
18
  import { Link, type RegularLinkProps } from '../link';
19
19
  import { InlineSpinner, spinnerClassNames } from '../spinner';
20
20
 
@@ -212,7 +212,7 @@ export type IconButtonProps<T extends IconButtonNodeType> = RegularBoxProps &
212
212
  disabled?: boolean;
213
213
  isLoading?: boolean;
214
214
  title?: string;
215
- tooltipProps?: unknown;
215
+ tooltipProps?: Omit<TooltipProps, 'children'>;
216
216
  } & Omit<IconButtonNodePropsMap[T]['props'], 'size' | 'variant'>;
217
217
 
218
218
  /**
@@ -261,7 +261,6 @@ export const IconButton = forwardRef(
261
261
  );
262
262
 
263
263
  return title ? (
264
- // @ts-expect-error tooltip is not migrated to TS yet
265
264
  <Tooltip content={title} {...tooltipProps}>
266
265
  {button}
267
266
  </Tooltip>
@@ -66,7 +66,8 @@ export const pythonizeValue = <T>(value: T, depth = 0): string | T => {
66
66
 
67
67
  // Object values
68
68
  if (typeof value === 'object') {
69
- const entries = Object.entries(value);
69
+ // Keys with `undefined` values are left out, like in `JSON.stringify`
70
+ const entries = Object.entries(value).filter(([, v]) => v !== undefined);
70
71
  if (entries.length === 0) return '{}';
71
72
  if (entries.length === 1)
72
73
  return '{ ' + pythonQuoteString(entries[0][0]) + ': ' + pythonizeValue(entries[0][1], depth + 1) + ' }';
@@ -1,10 +1,11 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite';
2
2
  import { action } from 'storybook/actions';
3
- import { expect, userEvent, within } from 'storybook/test';
3
+ import { expect, userEvent, waitFor, within } from 'storybook/test';
4
4
  import styled from 'styled-components';
5
5
 
6
6
  import { BuildIcon, PlayIcon } from '@apify/ui-icons';
7
7
 
8
+ import { Tooltip } from '../floating';
8
9
  import { SplitButton, type SplitButtonAction } from './';
9
10
 
10
11
  const defaultActions: SplitButtonAction[] = [
@@ -204,10 +205,20 @@ export const AllActionsDisabled: Story = {
204
205
  };
205
206
 
206
207
  export const WithTooltip: Story = {
207
- args: {
208
- tooltipProps: {
209
- content: 'Run this Actor with the current input',
210
- },
208
+ render: (args) => (
209
+ <Tooltip content="Run this Actor with the current input" delayShow={0}>
210
+ <SplitButton {...args} />
211
+ </Tooltip>
212
+ ),
213
+ play: async ({ canvasElement }) => {
214
+ const canvas = within(canvasElement);
215
+
216
+ await userEvent.hover(canvas.getAllByRole('button')[0]);
217
+ await expect(await canvas.findByText('Run this Actor with the current input')).toBeInTheDocument();
218
+ await userEvent.unhover(canvas.getAllByRole('button')[0]);
219
+ await waitFor(async () =>
220
+ expect(canvas.queryByText('Run this Actor with the current input')).not.toBeInTheDocument(),
221
+ );
211
222
  },
212
223
  };
213
224
 
@@ -7,7 +7,7 @@ import styled from 'styled-components';
7
7
  import type { MarginSpacingProps } from '../box';
8
8
  import { Button, type ButtonColor, type ButtonSize, type ButtonVariant } from '../button';
9
9
  import { Dropdown, DropdownShell } from '../dropdown';
10
- import { type TooltipProps, withTooltip } from '../floating';
10
+ import { Tooltip, type TooltipProps } from '../floating';
11
11
  import { Text } from '../text';
12
12
  import { splitButtonClassNames, StyledDropdownChevron, StyledSplitButtonWrapper } from './split_button.styled';
13
13
  import { splitButtonMainActionTestId, splitButtonTriggerTestId } from './split_button.test_ids';
@@ -36,6 +36,7 @@ export interface SplitButtonProps
36
36
  align?: PopperContentProps['align'];
37
37
  width?: string;
38
38
  showMainIcon?: boolean;
39
+ /** @deprecated Wrap `SplitButton` in `Tooltip` instead. */
39
40
  tooltipProps?: TooltipProps;
40
41
  /** Forwarded to the dropdown's DropdownShell - override when a consumer renders inside a modal/overlay stack. */
41
42
  zIndex?: number;
@@ -48,8 +49,6 @@ const ButtonWrapper = styled.div`
48
49
  align-items: center;
49
50
  `;
50
51
 
51
- const ButtonWrapperWithTooltip = withTooltip(ButtonWrapper);
52
-
53
52
  export const SplitButton = ({
54
53
  className,
55
54
  actions,
@@ -84,40 +83,42 @@ export const SplitButton = ({
84
83
  );
85
84
  const buttonMainLabel = label || mainAction.label;
86
85
 
87
- const Wrapper = tooltipProps ? ButtonWrapperWithTooltip : ButtonWrapper;
86
+ const buttons = (
87
+ <ButtonWrapper ref={triggerRef}>
88
+ <Button
89
+ data-test={splitButtonMainActionTestId(mainAction.trackingId)}
90
+ {...passedProps}
91
+ className={mainActionClassNames}
92
+ disabled={mainAction.disabled}
93
+ mr="none"
94
+ onClick={() => {
95
+ mainAction.onClick();
96
+ setActionsVisible(false);
97
+ }}
98
+ trackingId={mainAction.trackingId}
99
+ >
100
+ {mainAction.icon && showMainIcon ? <mainAction.icon /> : null}
101
+ {buttonMainLabel}
102
+ </Button>
103
+ <Button
104
+ {...passedProps}
105
+ className={triggerClassNames}
106
+ ml="none"
107
+ onClick={() => setActionsVisible((prevActionsVisible) => !prevActionsVisible)}
108
+ trackingId={mainAction.trackingId ? `${mainAction.trackingId}-OpenDropdown` : undefined}
109
+ data-test={splitButtonTriggerTestId(mainAction.trackingId)}
110
+ aria-haspopup="menu"
111
+ aria-expanded={actionsVisible}
112
+ aria-label={triggerAriaLabel}
113
+ >
114
+ <StyledDropdownChevron $isOpen={actionsVisible} size="16" />
115
+ </Button>
116
+ </ButtonWrapper>
117
+ );
88
118
 
89
119
  return (
90
120
  <StyledSplitButtonWrapper className={wrapperClassNames}>
91
- <Wrapper ref={triggerRef} tooltipProps={tooltipProps}>
92
- <Button
93
- data-test={splitButtonMainActionTestId(mainAction.trackingId)}
94
- {...passedProps}
95
- className={mainActionClassNames}
96
- disabled={mainAction.disabled}
97
- mr="none"
98
- onClick={() => {
99
- mainAction.onClick();
100
- setActionsVisible(false);
101
- }}
102
- trackingId={mainAction.trackingId}
103
- >
104
- {mainAction.icon && showMainIcon ? <mainAction.icon /> : null}
105
- {buttonMainLabel}
106
- </Button>
107
- <Button
108
- {...passedProps}
109
- className={triggerClassNames}
110
- ml="none"
111
- onClick={() => setActionsVisible((prevActionsVisible) => !prevActionsVisible)}
112
- trackingId={mainAction.trackingId ? `${mainAction.trackingId}-OpenDropdown` : undefined}
113
- data-test={splitButtonTriggerTestId(mainAction.trackingId)}
114
- aria-haspopup="menu"
115
- aria-expanded={actionsVisible}
116
- aria-label={triggerAriaLabel}
117
- >
118
- <StyledDropdownChevron $isOpen={actionsVisible} size="16" />
119
- </Button>
120
- </Wrapper>
121
+ {tooltipProps ? <Tooltip {...tooltipProps}>{buttons}</Tooltip> : buttons}
121
122
 
122
123
  <DropdownShell
123
124
  isOpen={actionsVisible}
@@ -96,12 +96,7 @@ export const StoreActorHeader: React.FC<StoreActorHeaderProps & BoxProps> = ({
96
96
  return (
97
97
  <StyledStoreActorHeader data-test={storeActorHeaderTestIds.HEADER} $compact={compact} {...rest}>
98
98
  <ActorAvatarWrapper>
99
- <ActorAvatar
100
- size={compact ? 20 : avatarSize}
101
- url={pictureUrl}
102
- name={title}
103
- borderRadius={compact ? theme.radius.radius4 : theme.radius.radius8}
104
- />
99
+ <ActorAvatar size={compact ? 20 : avatarSize} url={pictureUrl} name={title} />
105
100
  {!compact && hasRisingStarBadge && (
106
101
  <StyledCompactBadge
107
102
  variant="primary_blue"