@redocly/theme 0.63.0-next.3 → 0.63.0-next.5

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 (54) hide show
  1. package/lib/components/Buttons/CopyButton.d.ts +1 -1
  2. package/lib/components/Buttons/CopyButton.js +1 -1
  3. package/lib/components/Buttons/DownloadButton.d.ts +6 -0
  4. package/lib/components/Buttons/DownloadButton.js +20 -0
  5. package/lib/components/Buttons/EmailButton.js +6 -1
  6. package/lib/components/Buttons/NewTabButton.js +6 -1
  7. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.js +2 -2
  8. package/lib/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityVersionItem.js +6 -13
  9. package/lib/components/CodeBlock/CodeBlockControls.js +3 -3
  10. package/lib/components/Menu/MenuItem.js +1 -1
  11. package/lib/components/PageActions/PageActions.js +1 -1
  12. package/lib/components/Search/SearchDialog.js +1 -1
  13. package/lib/components/SidebarActions/SidebarActions.js +1 -1
  14. package/lib/components/SidebarActions/styled.d.ts +1 -1
  15. package/lib/components/Tooltip/AnchorTooltip.d.ts +7 -0
  16. package/lib/components/Tooltip/AnchorTooltip.js +233 -0
  17. package/lib/components/Tooltip/JsTooltip.d.ts +3 -0
  18. package/lib/components/Tooltip/JsTooltip.js +277 -0
  19. package/lib/components/Tooltip/Tooltip.d.ts +2 -13
  20. package/lib/components/Tooltip/Tooltip.js +14 -190
  21. package/lib/core/hooks/use-page-actions.js +9 -5
  22. package/lib/core/hooks/use-theme-hooks.js +1 -0
  23. package/lib/core/types/hooks.d.ts +4 -0
  24. package/lib/core/types/index.d.ts +1 -0
  25. package/lib/core/types/l10n.d.ts +1 -1
  26. package/lib/core/types/tooltip.d.ts +14 -0
  27. package/lib/core/types/tooltip.js +3 -0
  28. package/lib/core/utils/transform-revisions-to-version-history.js +13 -20
  29. package/lib/index.d.ts +1 -0
  30. package/lib/index.js +1 -0
  31. package/package.json +3 -3
  32. package/src/components/Buttons/CopyButton.tsx +2 -1
  33. package/src/components/Buttons/DownloadButton.tsx +41 -0
  34. package/src/components/Buttons/EmailButton.tsx +18 -8
  35. package/src/components/Buttons/NewTabButton.tsx +19 -8
  36. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityHistorySidebar.tsx +2 -2
  37. package/src/components/Catalog/CatalogEntity/CatalogEntityHistory/CatalogEntityVersionItem.tsx +5 -21
  38. package/src/components/CodeBlock/CodeBlockControls.tsx +3 -0
  39. package/src/components/Menu/MenuItem.tsx +1 -0
  40. package/src/components/PageActions/PageActions.tsx +6 -1
  41. package/src/components/Search/SearchDialog.tsx +1 -1
  42. package/src/components/SidebarActions/SidebarActions.tsx +1 -0
  43. package/src/components/Tooltip/AnchorTooltip.tsx +259 -0
  44. package/src/components/Tooltip/JsTooltip.tsx +296 -0
  45. package/src/components/Tooltip/Tooltip.tsx +18 -257
  46. package/src/core/hooks/__mocks__/use-theme-hooks.ts +3 -0
  47. package/src/core/hooks/use-page-actions.ts +5 -0
  48. package/src/core/hooks/use-theme-hooks.ts +1 -0
  49. package/src/core/types/hooks.ts +2 -1
  50. package/src/core/types/index.ts +1 -0
  51. package/src/core/types/l10n.ts +5 -0
  52. package/src/core/types/tooltip.ts +15 -0
  53. package/src/core/utils/transform-revisions-to-version-history.ts +13 -21
  54. package/src/index.ts +1 -0
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from 'react';
2
+ export type TooltipProps = {
3
+ children?: ReactNode;
4
+ tip: string | ReactNode;
5
+ isOpen?: boolean;
6
+ withArrow?: boolean;
7
+ placement?: 'top' | 'bottom' | 'left' | 'right';
8
+ className?: string;
9
+ width?: string;
10
+ dataTestId?: string;
11
+ disabled?: boolean;
12
+ arrowPosition?: 'top' | 'bottom' | 'left' | 'right' | 'center';
13
+ onClick?: React.MouseEventHandler<HTMLDivElement>;
14
+ };
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=tooltip.js.map
@@ -31,26 +31,19 @@ function transformRevisionsToVersionHistory({ revisions, currentRevisionDate, cu
31
31
  }
32
32
  // Check if any revision in this version group is the default version
33
33
  const isDefaultVersion = versionRevisions.some((rev) => rev.isDefaultVersion === true);
34
- const revisions = versionRevisions.length > 1
35
- ? versionRevisions.map((rev, index) => {
36
- var _a;
37
- const revisionMatches = currentRevisionDate
38
- ? rev.revision === currentRevisionDate
39
- : false;
40
- const isActiveRevision = revisionMatches && versionMatches;
41
- const isCurrentByDefault = !currentRevisionDate &&
42
- normalizedCurrentVersion === undefined &&
43
- index === 0 &&
44
- isCurrent;
45
- return {
46
- name: `r.${versionRevisions.length - index}`,
47
- date: (0, date_1.toLocalizedShortDateTime)(rev.revision, locale),
48
- revisionDate: rev.revision,
49
- isActive: isActiveRevision || isCurrentByDefault,
50
- isCurrent: (_a = rev.isCurrent) !== null && _a !== void 0 ? _a : false,
51
- };
52
- })
53
- : undefined;
34
+ const revisions = versionRevisions.map((rev, index) => {
35
+ var _a;
36
+ const revisionMatches = currentRevisionDate ? rev.revision === currentRevisionDate : false;
37
+ const isActiveRevision = revisionMatches && versionMatches;
38
+ const isCurrentByDefault = !currentRevisionDate && normalizedCurrentVersion === undefined && index === 0 && isCurrent;
39
+ return {
40
+ name: `r.${versionRevisions.length - index}`,
41
+ date: (0, date_1.toLocalizedShortDateTime)(rev.revision, locale),
42
+ revisionDate: rev.revision,
43
+ isActive: isActiveRevision || isCurrentByDefault,
44
+ isCurrent: (_a = rev.isCurrent) !== null && _a !== void 0 ? _a : false,
45
+ };
46
+ });
54
47
  return {
55
48
  version,
56
49
  date: (0, date_1.toLocalizedShortDate)((latestRevision === null || latestRevision === void 0 ? void 0 : latestRevision.revision) || null, locale),
package/lib/index.d.ts CHANGED
@@ -30,6 +30,7 @@ export * from './components/Buttons/CopyButton';
30
30
  export * from './components/Buttons/EditPageButton';
31
31
  export * from './components/Buttons/EmailButton';
32
32
  export * from './components/Buttons/NewTabButton';
33
+ export * from './components/Buttons/DownloadButton';
33
34
  export * from './components/Buttons/AIAssistantButton';
34
35
  export * from './components/Markdown/Markdown';
35
36
  export * from './components/Markdown/styles/base-table';
package/lib/index.js CHANGED
@@ -70,6 +70,7 @@ __exportStar(require("./components/Buttons/CopyButton"), exports);
70
70
  __exportStar(require("./components/Buttons/EditPageButton"), exports);
71
71
  __exportStar(require("./components/Buttons/EmailButton"), exports);
72
72
  __exportStar(require("./components/Buttons/NewTabButton"), exports);
73
+ __exportStar(require("./components/Buttons/DownloadButton"), exports);
73
74
  __exportStar(require("./components/Buttons/AIAssistantButton"), exports);
74
75
  /* Markdown */
75
76
  __exportStar(require("./components/Markdown/Markdown"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/theme",
3
- "version": "0.63.0-next.3",
3
+ "version": "0.63.0-next.5",
4
4
  "description": "Shared UI components lib",
5
5
  "keywords": [
6
6
  "theme",
@@ -63,7 +63,7 @@
63
63
  "vitest": "4.0.10",
64
64
  "vitest-when": "0.6.2",
65
65
  "webpack": "5.105.2",
66
- "@redocly/realm-asyncapi-sdk": "0.9.0-next.1"
66
+ "@redocly/realm-asyncapi-sdk": "0.9.0-next.4"
67
67
  },
68
68
  "dependencies": {
69
69
  "@tanstack/react-query": "5.62.3",
@@ -81,7 +81,7 @@
81
81
  "openapi-sampler": "1.7.0",
82
82
  "react-calendar": "5.1.0",
83
83
  "react-date-picker": "11.0.0",
84
- "@redocly/config": "0.43.0"
84
+ "@redocly/config": "0.44.0"
85
85
  },
86
86
  "scripts": {
87
87
  "watch": "tsc -p tsconfig.build.json && (concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\")",
@@ -1,7 +1,7 @@
1
1
  import React, { memo } from 'react';
2
2
 
3
3
  import type { JSX } from 'react';
4
- import type { TooltipProps } from '@redocly/theme/components/Tooltip/Tooltip';
4
+ import type { TooltipProps } from '@redocly/theme/core/types';
5
5
  import type { ButtonProps } from '@redocly/theme/components/Button/Button';
6
6
 
7
7
  import { ClipboardService } from '@redocly/theme/core/utils';
@@ -88,6 +88,7 @@ function CopyButtonComponent({
88
88
  tone={tone}
89
89
  extraClass={extraClass}
90
90
  iconPosition={iconPosition}
91
+ aria-label="Copy"
91
92
  >
92
93
  {(type === 'text' || type === 'compound') &&
93
94
  (buttonText || translate('codeSnippet.copy.toasterText', 'Copy'))}
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+
3
+ import type { JSX } from 'react';
4
+
5
+ import { Button } from '@redocly/theme/components/Button/Button';
6
+ import { DownloadIcon } from '@redocly/theme/icons/DownloadIcon/DownloadIcon';
7
+ import { Tooltip } from '@redocly/theme/components/Tooltip/Tooltip';
8
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
9
+
10
+ export type DownloadButtonProps = {
11
+ data: string;
12
+ dataTestId?: string;
13
+ };
14
+
15
+ export function DownloadButton({
16
+ data,
17
+ dataTestId = 'download-button',
18
+ }: DownloadButtonProps): JSX.Element {
19
+ const { useTranslate } = useThemeHooks();
20
+ const { translate } = useTranslate();
21
+
22
+ return (
23
+ <div data-component-name="Buttons/DownloadButton">
24
+ <Tooltip
25
+ tip={translate('button.download.tooltipText', 'Download description')}
26
+ placement="top"
27
+ arrowPosition="right"
28
+ >
29
+ <a href={data} target="_blank" download rel="noreferrer">
30
+ <Button
31
+ variant="text"
32
+ size="small"
33
+ aria-label="Download"
34
+ icon={<DownloadIcon />}
35
+ data-testid={dataTestId}
36
+ />
37
+ </a>
38
+ </Tooltip>
39
+ </div>
40
+ );
41
+ }
@@ -4,6 +4,8 @@ import type { JSX } from 'react';
4
4
 
5
5
  import { Button } from '@redocly/theme/components/Button/Button';
6
6
  import { EmailIcon } from '@redocly/theme/icons/EmailIcon/EmailIcon';
7
+ import { Tooltip } from '@redocly/theme/components/Tooltip/Tooltip';
8
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
7
9
 
8
10
  export type EmailButtonProps = {
9
11
  data: string;
@@ -11,20 +13,28 @@ export type EmailButtonProps = {
11
13
  };
12
14
 
13
15
  export function EmailButton({ data, dataTestId = 'email-button' }: EmailButtonProps): JSX.Element {
16
+ const { useTranslate } = useThemeHooks();
17
+ const { translate } = useTranslate();
14
18
  const onClick = () => {
15
19
  window.location.href = `mailto:${data}`;
16
20
  };
17
21
 
18
22
  return (
19
23
  <div data-component-name="Buttons/EmailButton">
20
- <Button
21
- onClick={onClick}
22
- icon={<EmailIcon />}
23
- size="small"
24
- variant="text"
25
- data-testid={dataTestId}
26
- aria-label="Email"
27
- />
24
+ <Tooltip
25
+ tip={translate('button.email.tooltipText', 'Send email')}
26
+ placement="top"
27
+ arrowPosition="right"
28
+ >
29
+ <Button
30
+ onClick={onClick}
31
+ icon={<EmailIcon />}
32
+ size="small"
33
+ variant="text"
34
+ data-testid={dataTestId}
35
+ aria-label="Email"
36
+ />
37
+ </Tooltip>
28
38
  </div>
29
39
  );
30
40
  }
@@ -4,6 +4,8 @@ import type { JSX } from 'react';
4
4
 
5
5
  import { Button } from '@redocly/theme/components/Button/Button';
6
6
  import { ArrowUpRightIcon } from '@redocly/theme/icons/ArrowUpRightIcon/ArrowUpRightIcon';
7
+ import { Tooltip } from '@redocly/theme/components/Tooltip/Tooltip';
8
+ import { useThemeHooks } from '@redocly/theme/core/hooks';
7
9
 
8
10
  export type NewTabButtonProps = {
9
11
  data: string;
@@ -14,20 +16,29 @@ export function NewTabButton({
14
16
  data,
15
17
  dataTestId = 'new-tab-button',
16
18
  }: NewTabButtonProps): JSX.Element {
19
+ const { useTranslate } = useThemeHooks();
20
+ const { translate } = useTranslate();
21
+
17
22
  const onClick = () => {
18
23
  window.open(data, '_blank');
19
24
  };
20
25
 
21
26
  return (
22
27
  <div data-component-name="Buttons/NewTabButton">
23
- <Button
24
- onClick={onClick}
25
- icon={<ArrowUpRightIcon />}
26
- data-testid={dataTestId}
27
- aria-label="Open in new tab"
28
- size="small"
29
- variant="text"
30
- />
28
+ <Tooltip
29
+ tip={translate('button.externalLink.tooltipText', 'Open in new tab')}
30
+ placement="top"
31
+ arrowPosition="right"
32
+ >
33
+ <Button
34
+ onClick={onClick}
35
+ icon={<ArrowUpRightIcon />}
36
+ data-testid={dataTestId}
37
+ aria-label="Open in new tab"
38
+ size="small"
39
+ variant="text"
40
+ />
41
+ </Tooltip>
31
42
  </div>
32
43
  );
33
44
  }
@@ -128,10 +128,10 @@ export function CatalogEntityHistorySidebar({
128
128
 
129
129
  const SidebarOverlay = styled.div`
130
130
  position: fixed;
131
- top: var(--navbar-height);
131
+ top: calc(var(--navbar-height) + var(--banner-height, 0));
132
132
  left: 0;
133
133
  width: var(--sidebar-width);
134
- height: calc(100vh - var(--navbar-height));
134
+ height: calc(100vh - var(--navbar-height) - var(--banner-height, 0));
135
135
  background-color: var(--catalog-history-sidebar-bg-color);
136
136
  border-right: 1px solid var(--catalog-history-sidebar-border-color);
137
137
  display: flex;
@@ -53,9 +53,6 @@ export function CatalogEntityVersionItem({
53
53
 
54
54
  const singleRevisionLink = buildRevisionUrl(basePath, group.singleRevisionDate, group.version);
55
55
 
56
- const isClickable = hasRevisions || Boolean(singleRevisionLink);
57
- const isSingleRevisionActive = Boolean(singleRevisionLink && group.isCurrent);
58
-
59
56
  const versionTitle = isNotSpecifiedVersion
60
57
  ? `${translate('catalog.history.version.label', 'Version')}: ${translate('catalog.history.version.notSpecified', 'not specified')}`
61
58
  : `${translate('catalog.history.version.label', 'Version')}: ${group.version}`;
@@ -64,9 +61,7 @@ export function CatalogEntityVersionItem({
64
61
  <VersionButton
65
62
  type="button"
66
63
  onClick={() => hasRevisions && onToggle(group.version)}
67
- $isClickable={isClickable}
68
64
  $isCurrent={group.isCurrent}
69
- $isActive={isSingleRevisionActive}
70
65
  >
71
66
  <VersionIcon $isCurrent={group.isCurrent}>
72
67
  {group.isCurrent ? (
@@ -155,34 +150,23 @@ export function CatalogEntityVersionItem({
155
150
  }
156
151
 
157
152
  const VersionButton = styled.button<{
158
- $isClickable?: boolean;
159
153
  $isCurrent?: boolean;
160
- $isActive?: boolean;
161
154
  }>`
162
155
  all: unset;
163
156
  display: flex;
164
157
  align-items: center;
165
158
  width: calc(100% - var(--catalog-history-sidebar-version-icon-margin-right));
166
159
  padding: var(--catalog-history-sidebar-version-header-padding);
167
- cursor: ${({ $isClickable }) => ($isClickable ? 'pointer' : 'default')};
160
+ cursor: pointer;
168
161
  border-radius: var(--catalog-history-sidebar-version-header-border-radius);
169
162
  transition: 0.2s ease;
170
163
  text-decoration: none;
171
164
  color: inherit;
172
- background-color: ${({ $isActive }) =>
173
- $isActive ? 'var(--catalog-history-sidebar-revision-item-bg-color-active)' : 'transparent'};
165
+ background-color: 'transparent';
174
166
 
175
- ${({ $isClickable, $isActive }) =>
176
- $isClickable &&
177
- `
178
- &:hover {
179
- background-color: ${
180
- $isActive
181
- ? 'var(--catalog-history-sidebar-revision-item-bg-color-active)'
182
- : 'var(--catalog-history-sidebar-version-header-bg-color-hover)'
183
- };
184
- }
185
- `}
167
+ &:hover {
168
+ background-color: var(--catalog-history-sidebar-version-header-bg-color-hover);
169
+ }
186
170
 
187
171
  ${({ $isCurrent }) =>
188
172
  !$isCurrent &&
@@ -96,6 +96,7 @@ export function CodeBlockControls({
96
96
  size="small"
97
97
  data-testid="report-button"
98
98
  icon={controlsType === 'icon' ? <WarningSquareIcon size="18px" /> : undefined}
99
+ aria-label="Report a problem"
99
100
  {...report.props}
100
101
  >
101
102
  {controlsType != 'icon' && (report.props?.buttonText || 'Report')}
@@ -114,6 +115,7 @@ export function CodeBlockControls({
114
115
  size="small"
115
116
  data-testid="expand-all"
116
117
  icon={controlsType === 'icon' ? <MaximizeIcon /> : undefined}
118
+ aria-label="Expand all"
117
119
  onClick={expand?.onClick}
118
120
  >
119
121
  {controlsType !== 'icon' && (expand?.label || 'Expand all')}
@@ -133,6 +135,7 @@ export function CodeBlockControls({
133
135
  data-testid="collapse-all"
134
136
  icon={controlsType === 'icon' ? <MinimizeIcon /> : undefined}
135
137
  onClick={collapse?.onClick}
138
+ aria-label="Collapse all"
136
139
  >
137
140
  {controlsType !== 'icon' && (expand?.label || 'Collapse all')}
138
141
  </ControlButton>
@@ -142,6 +142,7 @@ export function MenuItem(props: React.PropsWithChildren<MenuItemProps>): JSX.Ele
142
142
  <MenuItemWrapper
143
143
  data-component-name="Menu/MenuItem"
144
144
  className={generateClassName({ type, item, className })}
145
+ role="listitem"
145
146
  >
146
147
  {item.link ? (
147
148
  <MenuItemLink
@@ -83,7 +83,12 @@ export function PageActions(props: PageActionProps): JSX.Element | null {
83
83
  {buttonAction.buttonText}
84
84
  </Button>
85
85
  {actions.length > 1 ? (
86
- <StyledDropdown withArrow trigger={<Button />} placement="bottom" alignment="end">
86
+ <StyledDropdown
87
+ withArrow
88
+ trigger={<Button aria-label="More actions" />}
89
+ placement="bottom"
90
+ alignment="end"
91
+ >
87
92
  <StyledDropdownMenu>{menuItems}</StyledDropdownMenu>
88
93
  </StyledDropdown>
89
94
  ) : null}
@@ -75,7 +75,7 @@ export function SearchDialog({
75
75
  onQuickFilterReset,
76
76
  } = useSearchFilter(filter, setFilter);
77
77
  const { addSearchHistoryItem } = useRecentSearches();
78
- const aiSearch = useAiSearch({ filter });
78
+ const aiSearch = useAiSearch({ filter: filter, product: product?.name });
79
79
  useModalScrollLock(true);
80
80
  const searchInputRef = useRef<HTMLInputElement>(null);
81
81
  const modalRef = useRef<HTMLDivElement>(null);
@@ -64,6 +64,7 @@ export const SidebarActions = ({
64
64
  }}
65
65
  size="small"
66
66
  variant="outlined"
67
+ aria-label={collapsedSidebar ? 'Show sidebar' : 'Hide sidebar'}
67
68
  icon={collapsedSidebar ? <SidePanelOpenIcon /> : <SidePanelCloseIcon />}
68
69
  />
69
70
  </Tooltip>
@@ -0,0 +1,259 @@
1
+ import React, { memo, useEffect, useRef, useId } from 'react';
2
+ import styled, { css } from 'styled-components';
3
+
4
+ import type { JSX, PropsWithChildren } from 'react';
5
+ import type { TooltipProps } from '@redocly/theme/core/types';
6
+
7
+ import { useControl, useOutsideClick } from '@redocly/theme/core/hooks';
8
+ import { Portal } from '@redocly/theme/components/Portal/Portal';
9
+
10
+ type Props = Exclude<TooltipProps, 'arrowPosition'> & {
11
+ arrowPosition?: 'left' | 'right' | 'center';
12
+ };
13
+
14
+ function TooltipComponent({
15
+ children,
16
+ isOpen,
17
+ tip,
18
+ withArrow = true,
19
+ placement = 'top',
20
+ className = 'default',
21
+ width,
22
+ dataTestId,
23
+ disabled = false,
24
+ arrowPosition = 'center',
25
+ onClick,
26
+ }: PropsWithChildren<Props>): JSX.Element {
27
+ const tooltipWrapperRef = useRef<HTMLDivElement | null>(null);
28
+ const tooltipBodyRef = useRef<HTMLDivElement | null>(null);
29
+ const { isOpened, handleOpen, handleClose } = useControl(isOpen);
30
+ const anchorName = `--tooltip${useId().replace(/:/g, '')}`;
31
+
32
+ useOutsideClick(isOpened ? [tooltipWrapperRef, tooltipBodyRef] : tooltipWrapperRef, handleClose);
33
+
34
+ const isControlled = isOpen !== undefined;
35
+
36
+ useEffect(() => {
37
+ if (!isControlled) return;
38
+
39
+ if (isOpen && !disabled) {
40
+ handleOpen();
41
+ } else {
42
+ handleClose();
43
+ }
44
+ }, [isOpen, disabled, isControlled, handleOpen, handleClose]);
45
+
46
+ const controllers =
47
+ !isControlled && !disabled
48
+ ? {
49
+ onMouseEnter: handleOpen,
50
+ onMouseLeave: handleClose,
51
+ onClick: (e: React.MouseEvent<HTMLDivElement>) => {
52
+ onClick?.(e);
53
+ handleClose();
54
+ },
55
+ onFocus: handleOpen,
56
+ onBlur: handleClose,
57
+ }
58
+ : {};
59
+
60
+ return (
61
+ <TooltipWrapper
62
+ ref={tooltipWrapperRef}
63
+ {...controllers}
64
+ className={`tooltip-${className}`}
65
+ data-component-name="Tooltip/Tooltip"
66
+ anchorName={anchorName}
67
+ >
68
+ {children}
69
+ {isOpened && !disabled && (
70
+ <Portal>
71
+ <TooltipBody
72
+ ref={tooltipBodyRef}
73
+ data-testid={dataTestId || (typeof tip === 'string' ? tip : '')}
74
+ placement={placement}
75
+ width={width}
76
+ withArrow={withArrow}
77
+ arrowPosition={arrowPosition}
78
+ anchorName={anchorName}
79
+ >
80
+ {tip}
81
+ </TooltipBody>
82
+ </Portal>
83
+ )}
84
+ </TooltipWrapper>
85
+ );
86
+ }
87
+
88
+ export const Tooltip = memo<PropsWithChildren<Props>>(TooltipComponent);
89
+
90
+ const PLACEMENTS = {
91
+ top: css<Pick<Props, 'withArrow' | 'arrowPosition'>>`
92
+ bottom: anchor(top);
93
+ ${({ withArrow, arrowPosition }) =>
94
+ withArrow && arrowPosition === 'left'
95
+ ? css`
96
+ transform: translate(-32px, -6px);
97
+ left: anchor(center);
98
+ `
99
+ : arrowPosition === 'right'
100
+ ? css`
101
+ transform: translate(32px, -6px);
102
+ right: anchor(center);
103
+ `
104
+ : css`
105
+ transform: translate(-50%, -6px);
106
+ left: anchor(center);
107
+ `}
108
+
109
+ ${({ withArrow, arrowPosition }) =>
110
+ withArrow &&
111
+ css`
112
+ &::after {
113
+ border-left: 14px solid transparent;
114
+ border-right: 14px solid transparent;
115
+ border-top-width: 8px;
116
+ border-top-style: solid;
117
+ border-radius: 2px;
118
+ bottom: 0;
119
+ ${arrowPosition === 'left' && 'left: 16px; transform: translateY(100%);'}
120
+ ${arrowPosition === 'center' && 'left: 50%; transform: translate(-50%, 100%);'}
121
+ ${arrowPosition === 'right' && 'right: 16px; transform: translateY(100%);'}
122
+ }
123
+ `}
124
+ `,
125
+ bottom: css<Pick<Props, 'withArrow' | 'arrowPosition'>>`
126
+ top: anchor(bottom);
127
+ ${({ withArrow, arrowPosition }) =>
128
+ withArrow && arrowPosition === 'left'
129
+ ? css`
130
+ transform: translate(-32px, 6px);
131
+ left: anchor(center);
132
+ `
133
+ : arrowPosition === 'right'
134
+ ? css`
135
+ transform: translate(32px, 6px);
136
+ right: anchor(center);
137
+ `
138
+ : css`
139
+ transform: translate(-50%, 6px);
140
+ left: anchor(center);
141
+ `}
142
+
143
+ ${({ withArrow, arrowPosition }) =>
144
+ withArrow &&
145
+ css`
146
+ &::after {
147
+ border-left: 14px solid transparent;
148
+ border-right: 14px solid transparent;
149
+ border-bottom-width: 8px;
150
+ border-bottom-style: solid;
151
+ border-radius: 0 0 2px 2px;
152
+ top: 0;
153
+ ${arrowPosition === 'left' && 'left: 16px; transform: translateY(-100%);'}
154
+ ${arrowPosition === 'center' && 'left: 50%; transform: translate(-50%, -100%);'}
155
+ ${arrowPosition === 'right' && 'right: 16px; transform: translateY(-100%);'}
156
+ }
157
+ `}
158
+ `,
159
+ left: css<Pick<Props, 'withArrow' | 'arrowPosition'>>`
160
+ transform: translate(-100%, -50%);
161
+ margin-left: -7px;
162
+ top: anchor(center);
163
+ left: anchor(left);
164
+
165
+ ${({ withArrow }) =>
166
+ withArrow &&
167
+ css`
168
+ &::after {
169
+ border-top: 14px solid transparent;
170
+ border-bottom: 14px solid transparent;
171
+ border-left-width: 8px;
172
+ border-left-style: solid;
173
+ border-radius: 2px 0 0 2px;
174
+ right: -9px;
175
+ top: 50%;
176
+ transform: translateY(-50%);
177
+ }
178
+ `}
179
+ `,
180
+ right: css<Pick<Props, 'withArrow' | 'arrowPosition'>>`
181
+ transform: translate(0, -50%);
182
+ margin-left: 7px;
183
+ top: anchor(center);
184
+ left: anchor(right);
185
+
186
+ ${({ withArrow }) =>
187
+ withArrow &&
188
+ css`
189
+ &::after {
190
+ border-top: 14px solid transparent;
191
+ border-bottom: 14px solid transparent;
192
+ border-right-width: 8px;
193
+ border-right-style: solid;
194
+ border-radius: 0 2px 2px 0;
195
+ left: -9px;
196
+ top: 50%;
197
+ transform: translateY(-50%);
198
+ }
199
+ `}
200
+ `,
201
+ };
202
+
203
+ const TooltipWrapper = styled.div.attrs<{ anchorName: string }>(({ anchorName }) => ({
204
+ style: {
205
+ anchorName: anchorName,
206
+ } as React.CSSProperties,
207
+ }))<{ anchorName: string }>`
208
+ display: flex;
209
+ `;
210
+
211
+ const TooltipBody = styled.span.attrs<{ anchorName: string }>(({ anchorName }) => ({
212
+ style: {
213
+ positionAnchor: anchorName,
214
+ } as React.CSSProperties,
215
+ }))<
216
+ Pick<Required<Props>, 'placement' | 'withArrow' | 'arrowPosition'> & {
217
+ width?: string;
218
+ anchorName: string;
219
+ }
220
+ >`
221
+ position: fixed;
222
+ min-width: 64px;
223
+ padding: var(--tooltip-padding);
224
+ max-width: var(--tooltip-max-width);
225
+ white-space: normal;
226
+ word-break: normal;
227
+ overflow-wrap: break-word;
228
+ text-align: left;
229
+
230
+ border-radius: var(--border-radius-md);
231
+ transition: opacity 0.3s ease-out;
232
+
233
+ font-size: var(--font-size-base);
234
+ line-height: var(--line-height-base);
235
+
236
+ z-index: var(--z-index-overlay);
237
+
238
+ &::after {
239
+ position: absolute;
240
+
241
+ content: ' ';
242
+ display: inline-block;
243
+ width: 0;
244
+ height: 0;
245
+ border-color: var(--tooltip-arrow-color, var(--tooltip-bg-color));
246
+ }
247
+
248
+ background: var(--tooltip-bg-color);
249
+ color: var(--tooltip-text-color);
250
+ border: var(--tooltip-border-width, 0) var(--tooltip-border-style, solid)
251
+ var(--tooltip-border-color, transparent);
252
+ box-shadow: var(--bg-raised-shadow);
253
+
254
+ width: ${({ width }) => width || 'max-content'};
255
+
256
+ ${({ placement }) => css`
257
+ ${PLACEMENTS[placement]};
258
+ `}
259
+ `;