@apify/ui-library 0.64.0 → 0.64.1-featcodeblockwithtabs-5a4360.30

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": "@apify/ui-library",
3
- "version": "0.64.0",
3
+ "version": "0.64.1-featcodeblockwithtabs-5a4360.30+e5db735eb87",
4
4
  "description": "React UI library used by apify.com",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -65,5 +65,5 @@
65
65
  "typescript": "^5.1.6",
66
66
  "typescript-eslint": "^8.24.0"
67
67
  },
68
- "gitHead": "7fb7b476bfa81ad05a3a830b91a85abcec7a6a63"
68
+ "gitHead": "e5db735eb871d1296037d5e73adae45604b3c4d8"
69
69
  }
@@ -32,7 +32,7 @@ type HeaderProps = {
32
32
 
33
33
  const LANGUAGES_WITHOUT_LINE_NUMBERS = ['json', 'jsonp', 'jsonp', 'rss', 'yaml', 'xml', 'html', 'bash', 'text', 'dockerfile', 'http'];
34
34
 
35
- function HeaderDots() {
35
+ const HeaderDots = () => {
36
36
  return (
37
37
  <div className="CodeBlock-HederDotsWrapper">
38
38
  <div className="CodeBlock-HeaderDot" />
@@ -40,15 +40,15 @@ function HeaderDots() {
40
40
  <div className="CodeBlock-HeaderDot" />
41
41
  </div>
42
42
  );
43
- }
43
+ };
44
44
 
45
- function Header({
45
+ const Header = ({
46
46
  tabs,
47
47
  currentTab,
48
48
  showBashHeader,
49
49
  onTabChange,
50
50
  title,
51
- }: HeaderProps) {
51
+ }: HeaderProps) => {
52
52
  return (
53
53
  <div className="CodeBlock-Header">
54
54
  {showBashHeader && <HeaderDots />}
@@ -87,9 +87,9 @@ function Header({
87
87
  </div>
88
88
  </div>
89
89
  );
90
- }
90
+ };
91
91
 
92
- type CodeBlockProps = RegularBoxProps & {
92
+ export type CodeBlockProps = RegularBoxProps & {
93
93
  content: string | CodeTab[]; // TODO: Try to make it work with children props
94
94
  size?: SharedTextSize;
95
95
  language?: string | undefined;
@@ -112,7 +112,7 @@ type CodeBlockProps = RegularBoxProps & {
112
112
  hideLineNumbers?: boolean | undefined;
113
113
  }
114
114
 
115
- export function CodeBlock({
115
+ export const CodeBlock = ({
116
116
  content,
117
117
  size,
118
118
  language,
@@ -133,7 +133,7 @@ export function CodeBlock({
133
133
  hideBashPromptPrefixes,
134
134
  onActionButtonClick,
135
135
  ...rest
136
- }: CodeBlockProps) {
136
+ }: CodeBlockProps) => {
137
137
  const isMultiTab = content instanceof Array;
138
138
  const defaultTab = isMultiTab
139
139
  ? content.find((tab) => tab.key === defaultTabKey) ?? content[0]
@@ -232,4 +232,4 @@ export function CodeBlock({
232
232
  )}
233
233
  </CodeBlockWrapper >
234
234
  );
235
- }
235
+ };
@@ -0,0 +1,250 @@
1
+ import clsx from 'clsx';
2
+ import React from 'react';
3
+ import styled from 'styled-components';
4
+
5
+ import { theme } from '../../../design_system/theme.js';
6
+ import { useSharedUiDependencies } from '../../../ui_dependency_provider.js';
7
+ import { Box, type MarginSpacingProps, type RegularBoxProps } from '../../box.js';
8
+ import { Link, type RegularLinkProps } from '../../link.js';
9
+ import { HeadingShared } from '../../text/heading_shared.js';
10
+ import { CodeBlock, type CodeBlockProps } from './code_block.js';
11
+
12
+ export type CodeBlockTabKey = 'cli' | 'http' | 'javascript' | 'mcp' | 'openapi' | 'python' | 'typescript';
13
+ type CodeBlockTabConfig = {
14
+ label: string;
15
+ language: string;
16
+ src: string;
17
+ };
18
+
19
+ export const CODE_BLOCK_TAB_CATALOG: Record<CodeBlockTabKey, CodeBlockTabConfig> = {
20
+ cli: {
21
+ label: 'CLI',
22
+ language: 'bash',
23
+ src: 'https://apify.com/img/icons/code.svg',
24
+ },
25
+ http: {
26
+ label: 'HTTP',
27
+ language: 'bash',
28
+ src: 'https://apify.com/img/icons/http.svg',
29
+ },
30
+ javascript: {
31
+ label: 'JavaScript',
32
+ language: 'javascript',
33
+ // TODO: duplicate icon from 'template-icons' to 'icons' folder on the web
34
+ src: 'https://apify.com/img/template-icons/javascript.svg',
35
+ },
36
+ mcp: {
37
+ label: 'MCP',
38
+ language: 'bash',
39
+ src: 'https://apify.com/img/icons/mcp.svg',
40
+ },
41
+ openapi: {
42
+ label: 'OpenAPI',
43
+ language: 'json',
44
+ src: 'https://apify.com/img/icons/openapi.svg',
45
+ },
46
+ python: {
47
+ label: 'Python',
48
+ language: 'python',
49
+ // TODO: duplicate icon from 'template-icons' to 'icons' folder on the web
50
+ src: 'https://apify.com/img/template-icons/python.svg',
51
+ },
52
+ typescript: {
53
+ label: 'TypeScript',
54
+ language: 'typescript',
55
+ // TODO: duplicate icon from 'template-icons' to 'icons' folder on the web
56
+ src: 'https://apify.com/img/template-icons/typescript.svg',
57
+ },
58
+ };
59
+
60
+ type SharedBoxProps = Omit<RegularBoxProps, 'as' | 'children' | 'onClick'> & MarginSpacingProps;
61
+
62
+ type SharedCodeBlockProps = Omit<CodeBlockProps, 'bashCommandsStart' | 'content' | 'language' | 'defaultTabKey' | 'onTabChange'>;
63
+ type SharedLinkProps = Pick<RegularLinkProps, 'to' | 'rel' | 'target'>;
64
+
65
+ export type CodeBlockTabProps = {
66
+ key: CodeBlockTabKey;
67
+ bashCommandsStart?: number[];
68
+ content: string;
69
+ onClick?: (e: React.MouseEvent) => void;
70
+ } & Partial<SharedLinkProps>;
71
+
72
+ type CodeBlockWithTabsProps = SharedBoxProps & {
73
+ codeBlockProps: SharedCodeBlockProps;
74
+ currentTabKey: CodeBlockTabKey;
75
+ tabs: CodeBlockTabProps[];
76
+ };
77
+
78
+ export const CODE_BLOCK_WITH_TABS_CLASSNAMES = {
79
+ WRAPPER: 'CodeBlockWithTabsWrapper',
80
+ TABS: 'CodeBlockWithTabsTabs',
81
+ TAB: 'CodeBlockWithTabsTab',
82
+ CONTENT: 'CodeBlockWithTabsContent',
83
+ };
84
+
85
+ const CodeBlockWithTabsWrapper = styled(Box)`
86
+ .${CODE_BLOCK_WITH_TABS_CLASSNAMES.TABS} {
87
+ min-width: 266px;
88
+ height: 72px;
89
+ padding-inline: ${theme.space.space8};
90
+ display: flex;
91
+ gap: ${theme.space.space4};
92
+ overflow-x: auto;
93
+ background-color: ${theme.color.neutral.backgroundSubtle};
94
+ border: 1px solid ${theme.color.neutral.border};
95
+ border-bottom: none;
96
+ border-top-right-radius: ${theme.radius.radius12};
97
+ border-top-left-radius: ${theme.radius.radius12};
98
+ position: relative;
99
+
100
+ & > [role="tabpanel"] {
101
+ position: sticky;
102
+ right: 0;
103
+ top: 0;
104
+ height: 100%;
105
+ width: 0;
106
+
107
+ &::after {
108
+ right: -${theme.space.space8};
109
+ height: 100%;
110
+ width: ${theme.space.space16};
111
+ background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, ${theme.color.neutral.backgroundSubtle} 100%);
112
+ content: ' ';
113
+ pointer-events: none;
114
+ position: absolute;
115
+ }
116
+ }
117
+
118
+ @media ${theme.device.tablet} {
119
+ height: 88px;
120
+ justify-content: center;
121
+ gap: ${theme.space.space24};
122
+ padding-inline: ${theme.space.space24};
123
+
124
+ & > [role="tabpanel"]::after {
125
+ right: -${theme.space.space24};
126
+ width: ${theme.space.space32};
127
+ }
128
+ }
129
+ }
130
+
131
+ .${CODE_BLOCK_WITH_TABS_CLASSNAMES.TAB} {
132
+ min-width: 64px;
133
+ position: relative;
134
+ padding: ${theme.space.space12} ${theme.space.space8};
135
+ color: ${theme.color.neutral.textMuted};
136
+ display: flex;
137
+ flex-direction: column;
138
+ flex-shrink: 0;
139
+ align-items: center;
140
+ justify-content: flex-end;
141
+ gap: ${theme.space.space4};
142
+ cursor: pointer;
143
+
144
+ img {
145
+ width: 20px;
146
+ height: 20px;
147
+ }
148
+
149
+ [role="tabpanel"] {
150
+ bottom: 0;
151
+ width: 100%;
152
+ height: 2px;
153
+ color: ${theme.color.neutral.text};
154
+ background-color: ${theme.color.primaryBlack.action};
155
+ border-radius: ${theme.radius.radiusFull};
156
+ display: none;
157
+ position: absolute;
158
+ z-index: 2;
159
+ }
160
+
161
+ &.selected {
162
+ color: ${theme.color.neutral.text};
163
+
164
+ [role="tabpanel"] {
165
+ display: block;
166
+ }
167
+ }
168
+
169
+ &:hover {
170
+ color: ${theme.color.neutral.text};
171
+ text-decoration: none;
172
+ }
173
+ }
174
+
175
+ .${CODE_BLOCK_WITH_TABS_CLASSNAMES.CONTENT} {
176
+ max-width: initial;
177
+ border-top-left-radius: 0;
178
+ border-top-right-radius: 0;
179
+ }
180
+ `;
181
+
182
+ const IMG_RESIZE = {
183
+ width: 20,
184
+ height: 20,
185
+ };
186
+
187
+ export const CodeBlockWithTabs = ({ className, codeBlockProps, currentTabKey, tabs, ...props }: CodeBlockWithTabsProps) => {
188
+ const { generateProxyImageUrl } = useSharedUiDependencies();
189
+ const currentTab = tabs.find((tab) => tab.key === currentTabKey) ?? tabs[0];
190
+
191
+ return (
192
+ <CodeBlockWithTabsWrapper className={clsx(CODE_BLOCK_WITH_TABS_CLASSNAMES.WRAPPER, className)} {...props}>
193
+ <div className={CODE_BLOCK_WITH_TABS_CLASSNAMES.TABS}>
194
+ {tabs.map((tab) => {
195
+ const { label, src } = CODE_BLOCK_TAB_CATALOG[tab.key];
196
+ const selected = tab.key === currentTab?.key;
197
+
198
+ const commonProps = {
199
+ key: tab.key,
200
+ className: clsx(CODE_BLOCK_WITH_TABS_CLASSNAMES.TAB, { selected }),
201
+ 'data-test': `code-block-tab-${tab.key}`,
202
+ onClick: tab.onClick,
203
+ };
204
+ const children = (
205
+ <>
206
+ <img src={generateProxyImageUrl?.(src, { resize: IMG_RESIZE }) ?? src} alt={label} />
207
+ <HeadingShared type="titleS" as="p">
208
+ {label}
209
+ </HeadingShared>
210
+ <div role="tabpanel" />
211
+ </>
212
+ );
213
+
214
+ // if the tab has a 'to' prop, render a Link component
215
+ if (tab.to) {
216
+ return (
217
+ <Link
218
+ {...commonProps}
219
+ to={tab.to}
220
+ rel={tab.rel}
221
+ target={tab.target}
222
+ >
223
+ {children}
224
+ </Link>
225
+ );
226
+ }
227
+
228
+ return (
229
+ <div
230
+ {...commonProps}
231
+ role="button"
232
+ >
233
+ {children}
234
+ </div>
235
+ );
236
+ })}
237
+
238
+ <div role="tabpanel" />
239
+ </div>
240
+ <CodeBlock
241
+ bashCommandsStart={currentTab?.bashCommandsStart}
242
+ content={currentTab?.content ?? ''}
243
+ language={CODE_BLOCK_TAB_CATALOG[currentTab?.key]?.language}
244
+ className={CODE_BLOCK_WITH_TABS_CLASSNAMES.CONTENT}
245
+ hideBashHeader
246
+ {...codeBlockProps}
247
+ />
248
+ </CodeBlockWithTabsWrapper>
249
+ );
250
+ };
@@ -1,3 +1,4 @@
1
1
  export * from './code_block/code_block.js';
2
+ export * from './code_block/code_block_with_tabs.js';
2
3
  export * from './one_line_code/one_line_code.js';
3
4
  export * from './inline_code/inline_code.js';