@apify/ui-library 0.71.1-featcolortokens-178953.63 → 0.71.1-featcolortokens-178953.67

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 (86) hide show
  1. package/dist/tsconfig.build.tsbuildinfo +1 -1
  2. package/package.json +3 -2
  3. package/src/codemods/generate_typograpy_tokens_files.mjs +137 -0
  4. package/src/components/action_link.tsx +60 -0
  5. package/src/components/actor_template_card.tsx +116 -0
  6. package/src/components/badge.tsx +148 -0
  7. package/src/components/banner.tsx +94 -0
  8. package/src/components/blog_article.tsx +85 -0
  9. package/src/components/box.tsx +127 -0
  10. package/src/components/button.tsx +305 -0
  11. package/src/components/chip.tsx +128 -0
  12. package/src/components/code/action_button.tsx +96 -0
  13. package/src/components/code/code_block/code_block.styled.tsx +180 -0
  14. package/src/components/code/code_block/code_block.tsx +224 -0
  15. package/src/components/code/code_block/code_block_with_tabs.tsx +257 -0
  16. package/src/components/code/code_block/utils.tsx +67 -0
  17. package/src/components/code/index.ts +5 -0
  18. package/src/components/code/inline_code/inline_code.tsx +62 -0
  19. package/src/components/code/one_line_code/one_line_code.tsx +228 -0
  20. package/src/components/code/prism_highlighter.tsx +180 -0
  21. package/src/components/color_wheel_gradient.tsx +31 -0
  22. package/src/components/floating/index.ts +3 -0
  23. package/src/components/floating/menu.tsx +189 -0
  24. package/src/components/floating/menu_common.tsx +31 -0
  25. package/src/components/floating/menu_components.tsx +99 -0
  26. package/src/components/image.tsx +24 -0
  27. package/src/components/index.ts +22 -0
  28. package/src/components/link.tsx +114 -0
  29. package/src/components/message.tsx +153 -0
  30. package/src/components/rating.tsx +106 -0
  31. package/src/components/readme_renderer/index.ts +3 -0
  32. package/src/components/readme_renderer/pythonize_value.ts +76 -0
  33. package/src/components/readme_renderer/table_of_contents.tsx +272 -0
  34. package/src/components/readme_renderer/utils.tsx +46 -0
  35. package/src/components/simple_markdown/index.ts +2 -0
  36. package/src/components/simple_markdown/simple_markdown.tsx +214 -0
  37. package/src/components/simple_markdown/simple_markdown_components.tsx +293 -0
  38. package/src/components/tabs/index.ts +2 -0
  39. package/src/components/tabs/tab.tsx +217 -0
  40. package/src/components/tabs/tabs.tsx +169 -0
  41. package/src/components/tag.tsx +196 -0
  42. package/src/components/text/heading_content.tsx +56 -0
  43. package/src/components/text/heading_marketing.tsx +55 -0
  44. package/src/components/text/heading_shared.tsx +55 -0
  45. package/src/components/text/index.ts +19 -0
  46. package/src/components/text/text_base.tsx +52 -0
  47. package/src/components/text/text_content.tsx +104 -0
  48. package/src/components/text/text_marketing.tsx +152 -0
  49. package/src/components/text/text_shared.tsx +95 -0
  50. package/src/components/tile/horizontal_tile.tsx +77 -0
  51. package/src/components/tile/index.ts +2 -0
  52. package/src/components/tile/shared.ts +27 -0
  53. package/src/components/tile/vertical_tile.tsx +59 -0
  54. package/src/components/to_consolidate/card.tsx +141 -0
  55. package/src/components/to_consolidate/index.ts +4 -0
  56. package/src/components/to_consolidate/markdown.tsx +609 -0
  57. package/src/components/to_consolidate/pagination.tsx +136 -0
  58. package/src/components/to_consolidate/tab_number_chip.tsx +31 -0
  59. package/src/design_system/colors/build_color_tokens.js +183 -0
  60. package/src/design_system/colors/figma_color_tokens.dark.json +886 -0
  61. package/src/design_system/colors/figma_color_tokens.light.json +886 -0
  62. package/src/design_system/colors/generated/colors_theme.dark.ts +110 -0
  63. package/src/design_system/colors/generated/colors_theme.light.ts +110 -0
  64. package/src/design_system/colors/generated/css_variables.dark.ts +147 -0
  65. package/src/design_system/colors/generated/css_variables.light.ts +147 -0
  66. package/src/design_system/colors/generated/css_variables_palette.dark.ts +74 -0
  67. package/src/design_system/colors/generated/css_variables_palette.light.ts +74 -0
  68. package/src/design_system/colors/generated/properties_theme.ts +179 -0
  69. package/src/design_system/colors/index.ts +7 -0
  70. package/src/design_system/supernova_typography_tokens.json +657 -0
  71. package/src/design_system/theme.ts +25 -0
  72. package/src/design_system/tokens/index.ts +5 -0
  73. package/src/design_system/tokens/layouts.ts +29 -0
  74. package/src/design_system/tokens/radiuses.ts +22 -0
  75. package/src/design_system/tokens/shadows.ts +22 -0
  76. package/src/design_system/tokens/spaces.ts +15 -0
  77. package/src/design_system/tokens/transitions.ts +19 -0
  78. package/src/design_system/typography_theme.ts +197 -0
  79. package/src/index.ts +8 -0
  80. package/src/type_utils.ts +7 -0
  81. package/src/ui_dependency_provider.tsx +58 -0
  82. package/src/utils/copy_to_clipboard.ts +24 -0
  83. package/src/utils/image_color.ts +42 -0
  84. package/src/utils/index.ts +4 -0
  85. package/src/utils/resize_observer.ts +18 -0
  86. package/src/utils/sanitization.ts +14 -0
@@ -0,0 +1,189 @@
1
+ import {
2
+ FloatingFocusManager,
3
+ FloatingPortal,
4
+ useClick,
5
+ useDismiss,
6
+ useInteractions,
7
+ useListNavigation,
8
+ useRole,
9
+ useTypeahead,
10
+ } from '@floating-ui/react';
11
+ import clsx from 'clsx';
12
+ import React, {
13
+ useMemo,
14
+ useRef,
15
+ useState,
16
+ } from 'react';
17
+
18
+ import {
19
+ Box,
20
+ type BoxProps,
21
+ } from '../box.js';
22
+ import { Text } from '../text/index.js';
23
+ import { useFloatingMenu } from './menu_common.js';
24
+ import {
25
+ DropdownMenuBaseComponent,
26
+ ListMenuComponent,
27
+ ListMenuItemComponent,
28
+ } from './menu_components.js';
29
+
30
+ export const menuClassNames = {
31
+ WRAPPER: 'menu-wrapper',
32
+ BASE: 'menu-base',
33
+ LIST: 'menu-list',
34
+ ITEM: 'menu-item',
35
+ };
36
+
37
+ type SelectActionType = 'click' | 'enter' | 'space' | 'type';
38
+
39
+ // It might be tempting to define label as ReactNode, but we need to have the string value for keyboard navigation
40
+ // More difficult cases can be handled using renderOption prop
41
+ export interface MenuOption {
42
+ label: string,
43
+ value: string,
44
+ }
45
+
46
+ export interface MenuProps<T = MenuOption> {
47
+ options: T[],
48
+ value?: string,
49
+ onSelect: (newValue: string, selectedBy: SelectActionType) => void,
50
+ defaultLabel?: React.ReactNode,
51
+ closeOnSelect?: boolean,
52
+ renderOption?: (option: T) => React.ReactNode,
53
+ components?: {
54
+ MenuBase?: React.ElementType,
55
+ Menu?: React.ElementType,
56
+ MenuItem?: React.ElementType,
57
+ },
58
+ }
59
+
60
+ const defaultRenderOption = ({ label }: MenuOption) => (<Text as='span'>{label}</Text>);
61
+
62
+ /**
63
+ * This example is a Select taken from https://floating-ui.com/docs/react-examples
64
+ * It tries to mimic API of react-select as closely as possible
65
+ */
66
+ export const Menu = <T extends MenuOption>({
67
+ options,
68
+ value,
69
+ onSelect,
70
+ closeOnSelect = true,
71
+ defaultLabel,
72
+ components,
73
+ renderOption,
74
+ className,
75
+ ...rest
76
+ }: MenuProps<T> & BoxProps) => {
77
+ const [isOpen, setIsOpen] = useState(false);
78
+ const { refs, floatingStyles, context } = useFloatingMenu({
79
+ isOpen,
80
+ setIsOpen,
81
+ });
82
+
83
+ const selectedIndex = useMemo(() => {
84
+ const foundIndex = options.findIndex((option) => option.value === value);
85
+ return foundIndex < 0 ? null : foundIndex;
86
+ }, [options, value]);
87
+
88
+ const [activeIndex, setActiveIndex] = useState<number | null>(null);
89
+ const handleSelect = (newIndex: number, selectedBy: SelectActionType) => {
90
+ onSelect(options[newIndex].value, selectedBy);
91
+ if (closeOnSelect) setIsOpen(false);
92
+ };
93
+
94
+ const listRef = useRef<(HTMLElement | null)[]>([]);
95
+ const isTypingRef = useRef(false);
96
+ const listContentRef = useRef(options.map((option) => option.label));
97
+
98
+ const click = useClick(context, { event: 'mousedown' });
99
+ const dismiss = useDismiss(context);
100
+ const typeahead = useTypeahead(context, {
101
+ listRef: listContentRef,
102
+ activeIndex,
103
+ selectedIndex,
104
+ onMatch: isOpen ? setActiveIndex : (newIndex) => onSelect(options[newIndex].value, 'type'),
105
+ onTypingChange(isTyping) {
106
+ isTypingRef.current = isTyping;
107
+ },
108
+ });
109
+ const role = useRole(context, { role: 'listbox' });
110
+ const listNav = useListNavigation(context, {
111
+ listRef,
112
+ activeIndex,
113
+ selectedIndex,
114
+ onNavigate: setActiveIndex,
115
+ loop: true,
116
+ });
117
+
118
+ const { getFloatingProps, getItemProps, getReferenceProps } = useInteractions([click, dismiss, role, listNav, typeahead]);
119
+
120
+ const MenuBaseComponent = components?.MenuBase || DropdownMenuBaseComponent;
121
+ const MenuComponent = components?.Menu || ListMenuComponent;
122
+ const MenuItemComponent = components?.MenuItem || ListMenuItemComponent;
123
+
124
+ const effectiveRenderOption = renderOption || defaultRenderOption;
125
+
126
+ return (
127
+ <Box
128
+ className={clsx(className, menuClassNames.WRAPPER)}
129
+ {...rest}
130
+ >
131
+ <MenuBaseComponent
132
+ aria-labelledby="select-label"
133
+ aria-autocomplete="none"
134
+ ref={refs.setReference}
135
+ {...getReferenceProps()}
136
+ className={menuClassNames.BASE}
137
+ tabindex="0"
138
+ >
139
+ {selectedIndex !== null ? options[selectedIndex].label : defaultLabel}
140
+ </MenuBaseComponent>
141
+ {isOpen && (
142
+ <FloatingPortal>
143
+ <FloatingFocusManager
144
+ context={context}
145
+ modal={false}
146
+ >
147
+ <MenuComponent
148
+ ref={refs.setFloating}
149
+ style={floatingStyles}
150
+ {...getFloatingProps()}
151
+ className={menuClassNames.LIST}
152
+ >
153
+ {options.map((option, i) => (
154
+ <MenuItemComponent
155
+ key={option.value}
156
+ $isSelected={i === selectedIndex}
157
+ $isActive={i === activeIndex}
158
+ ref={(node: HTMLElement | null) => {
159
+ listRef.current[i] = node;
160
+ }}
161
+ role="option"
162
+ tabIndex={i === activeIndex ? 0 : -1}
163
+ className={menuClassNames.ITEM}
164
+ aria-selected={i === selectedIndex && i === activeIndex}
165
+ {...getItemProps({
166
+ onClick: () => handleSelect(i, 'click'), // Handles mouse click
167
+ onKeyDown: (event) => {
168
+ if (event.key === 'Enter') {
169
+ event.preventDefault();
170
+ handleSelect(i, 'enter'); // Handles enter press
171
+ }
172
+
173
+ if (event.key === ' ' && !isTypingRef.current) {
174
+ event.preventDefault();
175
+ handleSelect(i, 'space');
176
+ }
177
+ },
178
+ })}
179
+ >
180
+ {effectiveRenderOption(option)}
181
+ </MenuItemComponent>
182
+ ))}
183
+ </MenuComponent>
184
+ </FloatingFocusManager>
185
+ </FloatingPortal>
186
+ )}
187
+ </Box>
188
+ );
189
+ };
@@ -0,0 +1,31 @@
1
+ import {
2
+ autoUpdate,
3
+ offset,
4
+ size,
5
+ useFloating,
6
+ } from '@floating-ui/react';
7
+
8
+ export const useFloatingMenu = ({
9
+ isOpen,
10
+ setIsOpen,
11
+ }: {
12
+ isOpen: boolean,
13
+ setIsOpen: (newIsOpen: boolean) => void
14
+ }) => useFloating<Element>({
15
+ placement: 'bottom-start',
16
+ open: isOpen,
17
+ onOpenChange: setIsOpen,
18
+ whileElementsMounted: autoUpdate,
19
+ middleware: [
20
+ offset(5),
21
+ size({
22
+ apply({ elements, availableHeight }) {
23
+ Object.assign(elements.floating.style, {
24
+ maxHeight: `${availableHeight}px`,
25
+ minHeight: '50px',
26
+ });
27
+ },
28
+ padding: 10,
29
+ }),
30
+ ],
31
+ });
@@ -0,0 +1,99 @@
1
+ import {
2
+ type ReferenceType,
3
+ type UseFloatingReturn,
4
+ } from '@floating-ui/react';
5
+ import { forwardRef } from 'react';
6
+ import styled, { css } from 'styled-components';
7
+
8
+ import { ChevronDownIcon } from '@apify/ui-icons';
9
+
10
+ import { theme } from '../../design_system/theme.js';
11
+ import type { BoxProps } from '../box.js';
12
+ import { Box } from '../box.js';
13
+
14
+ export type FloatingContext = Pick<UseFloatingReturn<ReferenceType>, 'context'>['context'];
15
+
16
+ /**
17
+ * Menu wrapper component
18
+ */
19
+ export const ListMenuComponent = styled(Box).attrs({
20
+ p: 'space8',
21
+ })`
22
+ display: flex;
23
+ flex-direction: column;
24
+ max-height: 300px;
25
+ min-width: 160px;
26
+ box-shadow: ${theme.shadow.shadow2};
27
+ background: ${theme.color.neutral.background};
28
+ border: 1px solid ${theme.color.neutral.separatorSubtle};
29
+ border-radius: ${theme.radius.radius8};
30
+ scrollbar-color: ${theme.color.neutral.separatorSubtle} ${theme.color.neutral.background};
31
+ outline: none;
32
+ overflow-y: auto;
33
+ z-index: 10;
34
+ `;
35
+
36
+ interface SelectableListMenuItemProps {
37
+ $isActive?: boolean,
38
+ $isSelected?: boolean,
39
+ }
40
+
41
+ /**
42
+ * Menu item component
43
+ */
44
+ export const ListMenuItemComponent = styled(Box).attrs({
45
+ px: 'space8',
46
+ })<SelectableListMenuItemProps>`
47
+ border-radius: ${theme.radius.radius8};
48
+ outline: none;
49
+ cursor: pointer;
50
+ white-space: nowrap;
51
+
52
+ ${({ $isActive }) => $isActive && css`
53
+ background-color: ${theme.color.neutral.hover};
54
+ `}
55
+
56
+ ${({ $isSelected }) => $isSelected && css`
57
+ background-color: ${theme.color.neutral.backgroundSubtle};
58
+ color: ${theme.color.primary.text};
59
+ `}
60
+ `;
61
+
62
+ /**
63
+ * Menu base component WITHOUT dropdown icon
64
+ */
65
+ export const PlainMenuBaseComponent = styled(Box)`
66
+ display: flex;
67
+ align-items: center;
68
+ gap: ${theme.space.space4};
69
+ padding: ${theme.space.space8};
70
+ border-radius: ${theme.radius.radius8};
71
+ border: 1px solid ${theme.color.neutral.separatorSubtle};
72
+ background-color: ${theme.color.neutral.backgroundMuted};
73
+
74
+ cursor: pointer;
75
+ transition: background-color 0.2s ease-in-out;
76
+
77
+ &:hover {
78
+ background-color: ${theme.color.neutral.backgroundSubtle};
79
+ }
80
+ `;
81
+
82
+ type DropdownMenuBaseComponentProps = {
83
+ children: React.ReactNode;
84
+ } & BoxProps;
85
+
86
+ /**
87
+ * Menu base component WITH dropdown icon
88
+ */
89
+ export const DropdownMenuBaseComponent = forwardRef<HTMLElement, DropdownMenuBaseComponentProps>(({
90
+ children,
91
+ ...props
92
+ }, ref) => {
93
+ return (
94
+ <PlainMenuBaseComponent {...props} ref={ref}>
95
+ {children}
96
+ <ChevronDownIcon size="16" color={theme.color.neutral.icon} />
97
+ </PlainMenuBaseComponent>
98
+ );
99
+ });
@@ -0,0 +1,24 @@
1
+ import { forwardRef } from 'react';
2
+
3
+ import type { ImageProxyOptions } from '../ui_dependency_provider.js';
4
+ import { useSharedUiDependencies } from '../ui_dependency_provider.js';
5
+ import { Box, type BoxProps } from './box.js';
6
+
7
+ type ImageProps = {
8
+ src: string,
9
+ alt?: string,
10
+ width?: number,
11
+ height?: number,
12
+ loading?: 'eager' | 'lazy' | undefined;
13
+ } & ImageProxyOptions
14
+
15
+ export const Image = forwardRef<HTMLImageElement, ImageProps & Omit<BoxProps, 'as'>>((props, ref) => {
16
+ const { InternalImage } = useSharedUiDependencies();
17
+ return (
18
+ <Box
19
+ ref={ref}
20
+ {...props}
21
+ as={InternalImage}
22
+ />
23
+ );
24
+ });
@@ -0,0 +1,22 @@
1
+ export * from './text/index.js';
2
+ export * from './box.js';
3
+ export * from './message.js';
4
+ export * from './floating/index.js';
5
+ export * from './button.js';
6
+ export * from './link.js';
7
+ export * from './action_link.js';
8
+ export * from './simple_markdown/index.js';
9
+ export * from './code/index.js';
10
+ export * from './color_wheel_gradient.js';
11
+ export * from './readme_renderer/index.js';
12
+ export * from './to_consolidate/index.js';
13
+ export * from './tile/index.js';
14
+ export * from './blog_article.js';
15
+ export * from './banner.js';
16
+ export * from './actor_template_card.js';
17
+ export * from './chip.js';
18
+ export * from './image.js';
19
+ export * from './rating.js';
20
+ export * from './badge.js';
21
+ export * from './tag.js';
22
+ export * from './tabs/index.js';
@@ -0,0 +1,114 @@
1
+ import clsx from 'clsx';
2
+ import { createPath, type Path } from 'history';
3
+ import React, { forwardRef } from 'react';
4
+ import styled from 'styled-components';
5
+
6
+ import { ExternalLinkIcon } from '@apify/ui-icons';
7
+
8
+ import { theme } from '../design_system/theme.js';
9
+ import { useSharedUiDependencies } from '../ui_dependency_provider.js';
10
+ import { Box, type BoxProps } from './box.js';
11
+
12
+ export type To = string | Partial<Path>;
13
+ export interface RegularLinkProps {
14
+ to: To,
15
+ hideExternalIcon?: boolean,
16
+ showExternalIcon?: boolean,
17
+ rel?: string,
18
+ target?: string,
19
+ trackingId?: string,
20
+ trackingData?: object,
21
+ }
22
+
23
+ // TODO: This function is copied from tools.client.js. We can remove it from there once this is deployed
24
+ /**
25
+ * Checks if passed url is external
26
+ */
27
+ export const isUrlExternal = (url: To, windowLocationHost: string) => {
28
+ if (!url || typeof (url) !== 'string') return false;
29
+ const domainMatch = url.match(/^https?:\/\/([^/?#]+)(?:[/?#]|$)/i);
30
+ return !!(domainMatch && domainMatch[1] !== windowLocationHost);
31
+ };
32
+
33
+ // TODO: This function is copied from tools.client.js. We can remove it from there once this is deployed
34
+ /**
35
+ * Checks if passed url is email
36
+ */
37
+ export const isUrlEmail = (url: To) => {
38
+ if (!url || typeof (url) !== 'string') return false;
39
+ return url.startsWith('mailto:');
40
+ };
41
+
42
+ const StyledLink = styled(Box)`
43
+ /* Basic positioning */
44
+ display: inline-flex;
45
+ align-items: center;
46
+ gap: ${theme.space.space4};
47
+
48
+ /* Default behavior */
49
+ text-decoration: none;
50
+ transition: color ${theme.transition.fastEaseInOut};
51
+
52
+ &:hover {
53
+ text-decoration: none;
54
+ color: ${theme.color.primary.actionHover};
55
+ }
56
+
57
+ &:active {
58
+ color: ${theme.color.primary.actionActive};
59
+ }
60
+ `;
61
+
62
+ export type LinkProps = RegularLinkProps & BoxProps;
63
+
64
+ export const Link = forwardRef<HTMLElement, LinkProps>(({
65
+ to,
66
+ children,
67
+ rel,
68
+ target,
69
+ hideExternalIcon,
70
+ showExternalIcon,
71
+ onClick,
72
+ trackingId,
73
+ trackingData,
74
+ ...rest
75
+ }, ref) => {
76
+ const {
77
+ windowLocationHost,
78
+ isHrefTrusted,
79
+ trackClick,
80
+ InternalLink,
81
+ } = useSharedUiDependencies();
82
+ const href = typeof (to) === 'string' ? to : createPath(to);
83
+ const isExternal = isUrlExternal(to, windowLocationHost);
84
+ const isEmail = isUrlEmail(to);
85
+ const isTrusted = isHrefTrusted(href);
86
+
87
+ const trackedOnClick = (e: React.MouseEvent) => {
88
+ if (trackingId && trackClick) trackClick(trackingId, trackingData);
89
+ if (onClick) onClick(e);
90
+ };
91
+
92
+ const effectiveRel = clsx(
93
+ rel,
94
+ isExternal && 'external',
95
+ (isExternal || target === '_blank') && 'noopener',
96
+ (isExternal && !isTrusted) && 'nofollow',
97
+ );
98
+
99
+ return (
100
+ <StyledLink
101
+ // We use the InternalLink only for internal navigation
102
+ forwardedAs={(isEmail || isExternal) ? 'a' : InternalLink}
103
+ href={href}
104
+ rel={effectiveRel}
105
+ target={target || (isExternal ? '_blank' : '_self')}
106
+ onClick={trackedOnClick}
107
+ ref={ref}
108
+ {...rest}
109
+ >
110
+ {children}
111
+ {((isExternal && !hideExternalIcon) || showExternalIcon) && <ExternalLinkIcon size="16" />}
112
+ </StyledLink>
113
+ );
114
+ });
@@ -0,0 +1,153 @@
1
+ import clsx from 'clsx';
2
+ import React from 'react';
3
+ import styled from 'styled-components';
4
+
5
+ import { CheckCircleIcon, CrossIcon, InfoIcon, WarningTriangleIcon } from '@apify/ui-icons';
6
+
7
+ import { theme } from '../design_system/theme.js';
8
+ import { Box, type BoxProps } from './box.js';
9
+ import { Button } from './button.js';
10
+ import { Heading } from './text/index.js';
11
+
12
+ export const messageClassNames = {
13
+ main: 'Message',
14
+ icon: 'icon',
15
+ content: 'content',
16
+ caption: 'caption',
17
+ dismiss: 'dismiss',
18
+ };
19
+
20
+ const StyledMessage = styled(Box)`
21
+ display: flex;
22
+ gap: ${theme.space.space8};
23
+
24
+ border-radius: ${theme.radius.radius8};
25
+ box-shadow: ${theme.shadow.shadow1};
26
+
27
+ .${messageClassNames.content} {
28
+ flex: 1;
29
+ margin-top: 1px; /* This is to align icon with text (align-items: center not possible - icon should be on top) */
30
+ }
31
+
32
+ .${messageClassNames.caption} {
33
+ /* TODO: We should not override the default Heading that is rendered for caption but it doesn't look good without these */
34
+ line-height: 20px;
35
+ font-weight: 600;
36
+ margin-bottom: ${theme.space.space4};
37
+ }
38
+
39
+ .${messageClassNames.dismiss} {
40
+ background: transparent;
41
+ height: 20px;
42
+
43
+ svg {
44
+ color: ${theme.color.neutral.icon}
45
+ }
46
+ }
47
+
48
+ &.borderless {
49
+ border: none !important;
50
+ background-color: transparent !important;
51
+ box-shadow: none !important;
52
+ }
53
+
54
+ &.info {
55
+ background-color: ${theme.color.neutral.background};
56
+ border: 1px solid ${theme.color.neutral.border};
57
+
58
+ .${messageClassNames.icon} { color: ${theme.color.primary.icon}; }
59
+ }
60
+
61
+ &.success {
62
+ background-color: ${theme.color.success.backgroundSubtle};
63
+ border: 1px solid ${theme.color.success.borderSubtle};
64
+
65
+ .${messageClassNames.icon} { color: ${theme.color.success.icon}; }
66
+ }
67
+
68
+ &.warning {
69
+ background-color: ${theme.color.warning.backgroundSubtle};
70
+ border: 1px solid ${theme.color.warning.borderSubtle};
71
+
72
+ .${messageClassNames.icon} { color: ${theme.color.warning.icon}; }
73
+ }
74
+
75
+ &.danger {
76
+ background-color: ${theme.color.danger.backgroundSubtle};
77
+ border: 1px solid ${theme.color.danger.borderSubtle};
78
+
79
+ .${messageClassNames.icon} { color: ${theme.color.danger.icon}; }
80
+ }
81
+ `;
82
+
83
+ export type MessageType = 'info' | 'warning' | 'success' | 'danger';
84
+
85
+ const typeToIcon: { [key in MessageType]: React.ElementType } = {
86
+ info: InfoIcon,
87
+ success: CheckCircleIcon,
88
+ warning: WarningTriangleIcon,
89
+ danger: WarningTriangleIcon,
90
+ };
91
+
92
+ type MessageProps = BoxProps & {
93
+ type: MessageType,
94
+ caption?: string,
95
+ icon?: React.ElementType,
96
+ onDismissClick?: () => void,
97
+ borderless?: boolean,
98
+ boxless?: boolean,
99
+ as?: React.ElementType,
100
+ dismissTrackingId?: string,
101
+ }
102
+
103
+ /**
104
+ * Component used to display larger messages that are part of the content of the application.
105
+ */
106
+ export const Message: React.FC<MessageProps> = ({
107
+ className,
108
+ icon,
109
+ caption,
110
+ children,
111
+ onDismissClick,
112
+ type = 'warning',
113
+ borderless = false,
114
+ boxless = false,
115
+ dismissTrackingId,
116
+ as,
117
+ ...rest
118
+ }) => {
119
+ const Icon = icon || typeToIcon[type];
120
+ return (
121
+ <StyledMessage
122
+ className={clsx(className, messageClassNames.main, type, borderless && 'borderless')}
123
+ mb={boxless ? 'space8' : 'space16'} // TODO: Export message without margin
124
+ p={boxless ? 'none' : 'space16'}
125
+ forwardedAs={as}
126
+ {...rest}
127
+ >
128
+ <Icon className={messageClassNames.icon} size="20" />
129
+ <div className={messageClassNames.content}>
130
+ {caption && (<Heading as='div' type='titleM' className={messageClassNames.caption}>{caption}</Heading>)}
131
+ {children}
132
+ </div>
133
+ {onDismissClick && (
134
+ <Button
135
+ size='small'
136
+ variant='text'
137
+ trackingId={dismissTrackingId}
138
+ onClick={onDismissClick}
139
+ className={messageClassNames.dismiss}
140
+ >
141
+ <CrossIcon size="16" />
142
+ </Button>
143
+ )}
144
+ </StyledMessage>
145
+ );
146
+ };
147
+
148
+ type SpecificMessageProps = Omit<MessageProps, 'type'>;
149
+ // Semantic classes for all the message types
150
+ export const InfoMessage = (props: SpecificMessageProps) => (<Message {...props} type='info' />);
151
+ export const WarningMessage = (props: SpecificMessageProps) => (<Message {...props} type='warning' />);
152
+ export const SuccessMessage = (props: SpecificMessageProps) => (<Message {...props} type='success' />);
153
+ export const DangerMessage = (props: SpecificMessageProps) => (<Message {...props} type='danger' />);