@apify/ui-library 1.127.5 → 1.127.7

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": "1.127.5",
3
+ "version": "1.127.7",
4
4
  "description": "React UI library used by apify.com",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -27,9 +27,10 @@
27
27
  "It's not nice, but helps us to get around the problem of multiple react instances."
28
28
  ],
29
29
  "dependencies": {
30
- "@apify/ui-icons": "^1.29.1",
30
+ "@apify/ui-icons": "^1.29.2",
31
31
  "@floating-ui/react": "^0.26.2",
32
32
  "@radix-ui/react-checkbox": "^1.3.3",
33
+ "@radix-ui/react-collapsible": "^1.0.0",
33
34
  "@react-hook/resize-observer": "^2.0.2",
34
35
  "clsx": "^2.0.0",
35
36
  "dayjs": "1.11.9",
@@ -67,5 +68,5 @@
67
68
  "src",
68
69
  "style"
69
70
  ],
70
- "gitHead": "ed1183fd2d33f91e7429ae07b7e2fa6197a051e4"
71
+ "gitHead": "f5c8dc90016b381b6621529320ad6af263b2f246"
71
72
  }
@@ -3,6 +3,8 @@ import _ from 'lodash';
3
3
  import type React from 'react';
4
4
  import styled from 'styled-components';
5
5
 
6
+ import { CodeIcon, HttpIcon, type IconComponent, McpIcon } from '@apify/ui-icons';
7
+
6
8
  import { theme } from '../../../design_system/theme.js';
7
9
  import { useSharedUiDependencies } from '../../../ui_dependency_provider.js';
8
10
  import { Box, type MarginSpacingProps, type RegularBoxProps } from '../../box.js';
@@ -14,19 +16,21 @@ export type CodeBlockTabKey = 'cli' | 'http' | 'javascript' | 'mcp' | 'openapi'
14
16
  type CodeBlockTabConfig = {
15
17
  label: string;
16
18
  language: string;
17
- src: string;
19
+ Icon?: IconComponent;
20
+ /** Fallback image URL for tabs that don't have an Icon component yet */
21
+ src?: string;
18
22
  };
19
23
 
20
24
  export const CODE_BLOCK_TAB_CATALOG: Record<CodeBlockTabKey, CodeBlockTabConfig> = {
21
25
  cli: {
22
26
  label: 'CLI',
23
27
  language: 'bash',
24
- src: 'https://apify.com/img/icons/code.svg',
28
+ Icon: CodeIcon,
25
29
  },
26
30
  http: {
27
31
  label: 'HTTP',
28
32
  language: 'bash',
29
- src: 'https://apify.com/img/icons/http.svg',
33
+ Icon: HttpIcon,
30
34
  },
31
35
  javascript: {
32
36
  label: 'JavaScript',
@@ -37,7 +41,7 @@ export const CODE_BLOCK_TAB_CATALOG: Record<CodeBlockTabKey, CodeBlockTabConfig>
37
41
  mcp: {
38
42
  label: 'MCP',
39
43
  language: 'bash',
40
- src: 'https://apify.com/img/icons/mcp.svg',
44
+ Icon: McpIcon,
41
45
  },
42
46
  openapi: {
43
47
  label: 'OpenAPI',
@@ -201,7 +205,7 @@ export const CodeBlockWithTabs = ({ className, codeBlockProps, currentTabKey, ta
201
205
  <CodeBlockWithTabsWrapper className={clsx(CODE_BLOCK_WITH_TABS_CLASSNAMES.WRAPPER, className)} {...props}>
202
206
  <div className={CODE_BLOCK_WITH_TABS_CLASSNAMES.TABS}>
203
207
  {tabs.map((tab) => {
204
- const { label, src } = CODE_BLOCK_TAB_CATALOG[tab.key];
208
+ const { label, src, Icon } = CODE_BLOCK_TAB_CATALOG[tab.key];
205
209
  const selected = (optimisticCurrentTabKey ?? currentTabKey) === tab.key;
206
210
 
207
211
  const commonProps = {
@@ -211,7 +215,10 @@ export const CodeBlockWithTabs = ({ className, codeBlockProps, currentTabKey, ta
211
215
  };
212
216
  const children = (
213
217
  <>
214
- <img src={generateProxyImageUrl?.(src, { resize: IMG_RESIZE }) ?? src} alt={label} />
218
+ {Icon
219
+ ? <Icon size="20" />
220
+ : <img src={src && (generateProxyImageUrl?.(src, { resize: IMG_RESIZE }) ?? src)} alt={label} />
221
+ }
215
222
  <HeadingShared type="titleS" as="p">
216
223
  {label}
217
224
  </HeadingShared>
@@ -0,0 +1,132 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import { CollapsibleCard, type CollapsibleCardProps } from './collapsible_card.js';
4
+
5
+ export default {
6
+ title: 'UI-Library/Cards/CollapsibleCard',
7
+ component: CollapsibleCard,
8
+ argTypes: {
9
+ header: {
10
+ control: 'text',
11
+ description: 'Content rendered in the clickable header area',
12
+ },
13
+ children: {
14
+ control: 'text',
15
+ description: 'Main content revealed when the card is expanded',
16
+ },
17
+ isExpanded: {
18
+ control: 'boolean',
19
+ description: 'Controlled expanded state',
20
+ },
21
+ onIsExpandedChanged: {
22
+ control: false,
23
+ description: 'Callback when expanded state changes (controlled mode)',
24
+ },
25
+ noChevron: {
26
+ control: 'boolean',
27
+ description: 'Whether to hide the chevron icon',
28
+ },
29
+ topSection: {
30
+ control: 'text',
31
+ description: 'Content rendered above the header',
32
+ },
33
+ isClosedHeaderGrey: {
34
+ control: 'boolean',
35
+ description: 'Whether the header has a muted background when collapsed',
36
+ },
37
+ isHeaderGreyOnHover: {
38
+ control: 'boolean',
39
+ description: 'Whether the header turns grey on hover',
40
+ },
41
+ hideOuterBorder: {
42
+ control: 'boolean',
43
+ description: 'Whether to hide the outer border and border radius',
44
+ },
45
+ hasShadow: {
46
+ control: 'boolean',
47
+ description: 'Whether the card has a box shadow',
48
+ },
49
+ as: {
50
+ control: 'text',
51
+ description: 'The HTML element type to render as',
52
+ },
53
+ },
54
+ } as Meta<CollapsibleCardProps>;
55
+
56
+ type Story = StoryObj<CollapsibleCardProps>;
57
+
58
+ /**
59
+ * An uncontrolled collapsible card that manages its own expanded state.
60
+ */
61
+ export const Uncontrolled: Story = {
62
+ args: {
63
+ header: 'Click to expand',
64
+ children: 'This is the collapsible content. It starts collapsed by default.',
65
+ },
66
+ };
67
+
68
+ /**
69
+ * A controlled collapsible card that starts expanded.
70
+ */
71
+ export const Controlled: Story = {
72
+ args: {
73
+ header: 'Controlled card (expanded by default)',
74
+ children: 'This card is controlled externally via isExpanded prop.',
75
+ isExpanded: true,
76
+ },
77
+ };
78
+
79
+ /**
80
+ * A collapsible card with a top section above the header.
81
+ */
82
+ export const WithTopSection: Story = {
83
+ args: {
84
+ header: 'Card with top section',
85
+ topSection: 'Top section content',
86
+ children: 'Content below the collapsible header.',
87
+ },
88
+ };
89
+
90
+ /**
91
+ * A collapsible card without the chevron indicator.
92
+ */
93
+ export const NoChevron: Story = {
94
+ args: {
95
+ header: 'No chevron icon',
96
+ noChevron: true,
97
+ children: 'This card has no chevron indicator.',
98
+ },
99
+ };
100
+
101
+ /**
102
+ * A collapsible card with a muted header background when collapsed.
103
+ */
104
+ export const GreyHeaderWhenClosed: Story = {
105
+ args: {
106
+ header: 'Muted background when closed, more prominent on hover',
107
+ isClosedHeaderGrey: true,
108
+ children: 'The header has a muted background when the card is collapsed, which becomes more prominent on hover.',
109
+ },
110
+ };
111
+
112
+ /**
113
+ * A collapsible card with a grey header on hover.
114
+ */
115
+ export const GreyHeaderOnHover: Story = {
116
+ args: {
117
+ header: 'Grey on hover',
118
+ isHeaderGreyOnHover: true,
119
+ children: 'The header turns grey when hovered.',
120
+ },
121
+ };
122
+
123
+ /**
124
+ * A collapsible card without the outer border.
125
+ */
126
+ export const HiddenOuterBorder: Story = {
127
+ args: {
128
+ header: 'No outer border',
129
+ hideOuterBorder: true,
130
+ children: 'This card has no outer border or border radius.',
131
+ },
132
+ };
@@ -0,0 +1,159 @@
1
+ import * as Collapsible from '@radix-ui/react-collapsible';
2
+ import clsx from 'clsx';
3
+ import { type FC, type ReactNode, useState } from 'react';
4
+ import styled, { css } from 'styled-components';
5
+
6
+ import { ChevronDownIcon } from '@apify/ui-icons';
7
+
8
+ import { theme } from '../../design_system/theme.js';
9
+ import type { WithTransientProps } from '../../type_utils.js';
10
+ import { Box } from '../box.js';
11
+
12
+ interface TransientWrapperProps {
13
+ hideOuterBorder?: boolean;
14
+ hasShadow?: boolean;
15
+ }
16
+
17
+ interface TransientHeaderProps {
18
+ isHeaderGrey?: boolean;
19
+ isHeaderGreyOnHover?: boolean;
20
+ hideOuterBorder?: boolean;
21
+ }
22
+
23
+ type StyledWrapperProps = WithTransientProps<TransientWrapperProps>;
24
+ type StyledHeaderProps = WithTransientProps<TransientHeaderProps>;
25
+
26
+ export type CollapsibleCardProps = {
27
+ header: ReactNode;
28
+ children: ReactNode;
29
+ isExpanded?: boolean;
30
+ onIsExpandedChanged?: (expanded: boolean) => void;
31
+ noChevron?: boolean;
32
+ topSection?: ReactNode;
33
+ isClosedHeaderGrey?: boolean;
34
+ isHeaderGreyOnHover?: boolean;
35
+ className?: string;
36
+ style?: React.CSSProperties;
37
+ as?: React.ElementType;
38
+ id?: string;
39
+ } & TransientWrapperProps;
40
+
41
+ export const collapsibleCardClassNames = {
42
+ TOP_SECTION: 'CollapsibleCard-TopSection',
43
+ COLLAPSIBLE_CONTENT_WRAPPER: 'CollapsibleCard-Content',
44
+ CONTENT: 'Card-content',
45
+ };
46
+
47
+ const StyledCardWrapper = styled(Box) <StyledWrapperProps>`
48
+ /* consoleStyle.partialStyle.card — TODO: reuse from Card once moved out of to_consolidate */
49
+ background-color: ${theme.color.neutral.cardBackground};
50
+ border: 1px solid ${theme.color.neutral.border};
51
+ box-shadow: var(--shadow-1);
52
+ border-radius: ${theme.radius.radius8};
53
+ box-sizing: border-box;
54
+
55
+ ${({ $hideOuterBorder }) => $hideOuterBorder && css`
56
+ border: none;
57
+ border-radius: unset;
58
+ `};
59
+ /* Reset padding, it is dealt with within header and content styles */
60
+ padding: 0;
61
+ ${({ $hasShadow }) => ($hasShadow ? '' : 'box-shadow: initial;')}
62
+ /* TODO: check if it this is needed, ie in publication tab it is reset back to overflow: visible */
63
+ overflow: hidden;
64
+ `;
65
+
66
+ const StyledTopSection = styled.div`
67
+ display: flex;
68
+ align-items: center;
69
+ padding: ${theme.space.space4} ${theme.space.space8};
70
+ background: ${theme.color.neutral.backgroundMuted};
71
+ border-bottom: solid 1px ${theme.color.neutral.border};
72
+ gap: ${theme.space.space8};
73
+ `;
74
+
75
+ const StyledHeader = styled.header<StyledHeaderProps>`
76
+ display: flex;
77
+ gap: ${theme.space.space4};
78
+ align-items: center;
79
+ ${({ onClick }) => (onClick ? 'cursor: pointer;' : '')};
80
+ padding: ${theme.space.space16};
81
+ background-color: ${({ $isHeaderGrey }) => ($isHeaderGrey ? theme.color.neutral.backgroundMuted : 'initial')};
82
+ /* We want to radiuses if there is no outer border */
83
+ ${({ $hideOuterBorder }) => ($hideOuterBorder ? '' : `border-radius: ${theme.radius.radius8};`)}
84
+
85
+ &:hover {
86
+ background-color: ${({ $isHeaderGrey, $isHeaderGreyOnHover }) => (($isHeaderGrey || $isHeaderGreyOnHover) ? theme.color.neutral.hover : 'initial')};
87
+ }
88
+
89
+ .chevron {
90
+ transition: transform 100ms ease-in-out;
91
+ }
92
+
93
+ .chevron-open {
94
+ transform: rotate(0deg);
95
+ }
96
+
97
+ .chevron-closed {
98
+ transform: rotate(-90deg);
99
+ }
100
+ `;
101
+
102
+ const StyledCardContent = styled.div`
103
+ padding: ${theme.space.space16};
104
+ overflow: auto;
105
+ `;
106
+
107
+ const CollapsibleContent = styled(Collapsible.Content)`
108
+ border-top: 1px solid ${theme.color.neutral.border};
109
+ `;
110
+
111
+ export const CollapsibleCard: FC<CollapsibleCardProps> = ({
112
+ header,
113
+ children,
114
+ isExpanded,
115
+ onIsExpandedChanged,
116
+ noChevron,
117
+ topSection,
118
+ ...rest
119
+ }) => {
120
+ const isUncontrolled = isExpanded === undefined;
121
+ const [isOpen, setOpen] = useState(false);
122
+
123
+ let onHeaderClick;
124
+ if (isUncontrolled) onHeaderClick = () => setOpen((prevIsOpen) => !prevIsOpen);
125
+ else if (onIsExpandedChanged) onHeaderClick = () => onIsExpandedChanged(!isExpanded);
126
+
127
+ return (
128
+ <StyledCardWrapper
129
+ forwardedAs={rest.as || 'section'}
130
+ $hideOuterBorder={rest.hideOuterBorder}
131
+ $hasShadow={rest.hasShadow}
132
+ className={rest.className}
133
+ style={rest.style}
134
+ id={rest.id}
135
+ >
136
+ {topSection && <StyledTopSection className={collapsibleCardClassNames.TOP_SECTION} data-test='collapsible-card-top-section'>
137
+ {topSection}
138
+ </StyledTopSection>}
139
+ <StyledHeader
140
+ $hideOuterBorder={rest.hideOuterBorder}
141
+ $isHeaderGrey={rest.isClosedHeaderGrey && !isOpen && !isExpanded}
142
+ $isHeaderGreyOnHover={rest.isHeaderGreyOnHover}
143
+ onClick={onHeaderClick}
144
+ >
145
+ {!noChevron && <ChevronDownIcon size="16" className={clsx('chevron', (isExpanded ?? isOpen) ? 'chevron-open' : 'chevron-closed')} />}
146
+ {header}
147
+ </StyledHeader>
148
+ <Collapsible.Root
149
+ open={isUncontrolled ? isOpen : isExpanded}
150
+ >
151
+ <CollapsibleContent className={collapsibleCardClassNames.COLLAPSIBLE_CONTENT_WRAPPER}>
152
+ <StyledCardContent className={collapsibleCardClassNames.CONTENT} data-test="card-content">
153
+ {children}
154
+ </StyledCardContent>
155
+ </CollapsibleContent>
156
+ </Collapsible.Root>
157
+ </StyledCardWrapper>
158
+ );
159
+ };
@@ -0,0 +1 @@
1
+ export * from './collapsible_card.js';
@@ -27,3 +27,4 @@ export * from './icon_button.js';
27
27
  export * from './spinner.js';
28
28
  export * from './store/index.js';
29
29
  export * from './checkbox/index.js';
30
+ export * from './collapsible_card/index.js';