@apify/ui-library 0.63.1 → 0.63.2-featcodeblockwithtabs-5a4360.32

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.63.1",
3
+ "version": "0.63.2-featcodeblockwithtabs-5a4360.32+1e905b277d8",
4
4
  "description": "React UI library used by apify.com",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -66,5 +66,5 @@
66
66
  "typescript": "^5.1.6",
67
67
  "typescript-eslint": "^8.24.0"
68
68
  },
69
- "gitHead": "578d4492a2df2afbc48bb0fe0a2bffd11ba8a06f"
69
+ "gitHead": "1e905b277d80c7b4e02e1498fc1bd893de67a91e"
70
70
  }
@@ -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,209 @@
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 { Link, type RegularLinkProps } from '../../link.js';
7
+ import { HeadingShared } from '../../text/heading_shared.js';
8
+ import { CodeBlock, type CodeBlockProps } from './code_block.js';
9
+
10
+ export type CodeBlockTabKey = 'cli' | 'http' | 'javascript' | 'mcp' | 'openapi' | 'python' | 'typescript';
11
+ type CodeBlockTabConfig = {
12
+ label: string;
13
+ language: string;
14
+ src: string;
15
+ };
16
+
17
+ const CODE_BLOCK_TAB_CATALOG: Record<CodeBlockTabKey, CodeBlockTabConfig> = {
18
+ cli: {
19
+ label: 'CLI',
20
+ language: 'bash',
21
+ src: 'https://apify.com/img/icons/code.svg',
22
+ },
23
+ http: {
24
+ label: 'HTTP',
25
+ language: 'bash',
26
+ src: 'https://apify.com/img/icons/http.svg',
27
+ },
28
+ javascript: {
29
+ label: 'JavaScript',
30
+ language: 'javascript',
31
+ // TODO: duplicate icon from 'template-icons' to 'icons' folder on the web
32
+ src: 'https://apify.com/img/template-icons/javascript.svg',
33
+ },
34
+ mcp: {
35
+ label: 'MCP',
36
+ language: 'bash',
37
+ src: 'https://apify.com/img/icons/mcp.svg',
38
+ },
39
+ openapi: {
40
+ label: 'OpenAPI',
41
+ language: 'json',
42
+ src: 'https://apify.com/img/icons/openapi.svg',
43
+ },
44
+ python: {
45
+ label: 'Python',
46
+ language: 'python',
47
+ // TODO: duplicate icon from 'template-icons' to 'icons' folder on the web
48
+ src: 'https://apify.com/img/template-icons/python.svg',
49
+ },
50
+ typescript: {
51
+ label: 'TypeScript',
52
+ language: 'typescript',
53
+ // TODO: duplicate icon from 'template-icons' to 'icons' folder on the web
54
+ src: 'https://apify.com/img/template-icons/typescript.svg',
55
+ },
56
+ };
57
+
58
+ type SharedCodeBlockProps = Omit<CodeBlockProps, 'content' | 'language' | 'defaultTabKey' | 'onTabChange'>;
59
+ type SharedLinkProps = Pick<RegularLinkProps, 'to' | 'rel' | 'target'>;
60
+
61
+ export type CodeBlockTabProps = {
62
+ key: CodeBlockTabKey;
63
+ content: string;
64
+ onClick?: (e: React.MouseEvent) => void;
65
+ } & Partial<SharedLinkProps>;
66
+
67
+ type CodeBlockWithTabsProps = SharedCodeBlockProps & {
68
+ currentTabKey: CodeBlockTabKey;
69
+ tabs: CodeBlockTabProps[];
70
+ };
71
+
72
+ export const CODE_BLOCK_WITH_TABS_CLASSNAMES = {
73
+ WRAPPER: 'CodeBlockWithTabsWrapper',
74
+ TABS: 'CodeBlockWithTabsTabs',
75
+ TAB: 'CodeBlockWithTabsTab',
76
+ CONTENT: 'CodeBlockWithTabsContent',
77
+ };
78
+
79
+ const CodeBlockWithTabsWrapper = styled.div`
80
+ .${CODE_BLOCK_WITH_TABS_CLASSNAMES.TABS} {
81
+ min-width: 266px;
82
+ height: 72px;
83
+ padding-inline: ${theme.space.space8};
84
+ display: flex;
85
+ gap: ${theme.space.space4};
86
+ overflow-x: auto;
87
+ background-color: ${theme.color.neutral.backgroundSubtle};
88
+ border: 1px solid ${theme.color.neutral.border};
89
+ border-bottom: none;
90
+ border-top-right-radius: ${theme.radius.radius12};
91
+ border-top-left-radius: ${theme.radius.radius12};
92
+
93
+ @media ${theme.device.tablet} {
94
+ height: 88px;
95
+ justify-content: center;
96
+ gap: ${theme.space.space24};
97
+ padding-inline: ${theme.space.space24};
98
+ }
99
+ }
100
+
101
+ .${CODE_BLOCK_WITH_TABS_CLASSNAMES.TAB} {
102
+ min-width: 64px;
103
+ position: relative;
104
+ padding: ${theme.space.space12} ${theme.space.space8};
105
+ color: ${theme.color.neutral.textMuted};
106
+ display: flex;
107
+ flex-direction: column;
108
+ flex-shrink: 0;
109
+ align-items: center;
110
+ justify-content: flex-end;
111
+ gap: ${theme.space.space4};
112
+ cursor: pointer;
113
+
114
+ img {
115
+ width: 20px;
116
+ height: 20px;
117
+ }
118
+
119
+ [role="tabpanel"] {
120
+ bottom: 0;
121
+ width: 100%;
122
+ height: 2px;
123
+ color: ${theme.color.neutral.text};
124
+ background-color: ${theme.color.primaryBlack.action};
125
+ border-radius: ${theme.radius.radiusFull};
126
+ display: none;
127
+ position: absolute;
128
+ z-index: 2;
129
+ }
130
+
131
+ &.selected {
132
+ [role="tabpanel"] {
133
+ display: block;
134
+ }
135
+ }
136
+
137
+ &:hover {
138
+ color: ${theme.color.neutral.text};
139
+ text-decoration: none;
140
+ }
141
+ }
142
+
143
+ .${CODE_BLOCK_WITH_TABS_CLASSNAMES.CONTENT} {
144
+ max-width: initial;
145
+ border-top-left-radius: 0;
146
+ border-top-right-radius: 0;
147
+ }
148
+ `;
149
+
150
+ export const CodeBlockWithTabs = ({ currentTabKey, tabs, className, hideBashHeader = true, ...props }: CodeBlockWithTabsProps) => {
151
+ // Math.Max(0, ) ensures that the index is not negative, in case the currentTabKey is not found in the tabs array
152
+ const currentTabIndex = Math.max(0, tabs.findIndex((tab) => tab.key === currentTabKey));
153
+ const currentTab = tabs[currentTabIndex];
154
+
155
+ return (
156
+ <CodeBlockWithTabsWrapper className={clsx(CODE_BLOCK_WITH_TABS_CLASSNAMES.WRAPPER, className)}>
157
+ <div className={CODE_BLOCK_WITH_TABS_CLASSNAMES.TABS}>
158
+ {tabs.map((tab) => {
159
+ const { label, src } = CODE_BLOCK_TAB_CATALOG[tab.key];
160
+ const selected = tab.key === currentTab?.key;
161
+
162
+ const children = (
163
+ <>
164
+ <img src={src} alt={label} />
165
+ <HeadingShared type="titleS" as="p">
166
+ {label}
167
+ </HeadingShared>
168
+ <div role="tabpanel" />
169
+ </>
170
+ );
171
+
172
+ // if the tab has a 'to' prop, render a Link component
173
+ if (tab.to) {
174
+ return (
175
+ <Link
176
+ key={tab.key}
177
+ className={clsx(CODE_BLOCK_WITH_TABS_CLASSNAMES.TAB, { selected })}
178
+ to={tab.to}
179
+ rel={tab.rel}
180
+ target={tab.target}
181
+ onClick={tab.onClick}
182
+ >
183
+ {children}
184
+ </Link>
185
+ );
186
+ }
187
+
188
+ return (
189
+ <div
190
+ key={tab.key}
191
+ className={clsx(CODE_BLOCK_WITH_TABS_CLASSNAMES.TAB, { selected })}
192
+ role="button"
193
+ onClick={tab.onClick}
194
+ >
195
+ {children}
196
+ </div>
197
+ );
198
+ })}
199
+ </div>
200
+ <CodeBlock
201
+ content={currentTab?.content ?? ''}
202
+ language={CODE_BLOCK_TAB_CATALOG[currentTab?.key]?.language}
203
+ className={CODE_BLOCK_WITH_TABS_CLASSNAMES.CONTENT}
204
+ hideBashHeader={hideBashHeader}
205
+ {...props}
206
+ />
207
+ </CodeBlockWithTabsWrapper>
208
+ );
209
+ };
@@ -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';