@apify/ui-library 1.98.3 → 1.99.1
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/dist/src/components/button.d.ts +6 -2
- package/dist/src/components/button.d.ts.map +1 -1
- package/dist/src/components/button.js +35 -8
- package/dist/src/components/button.js.map +1 -1
- package/dist/src/components/button.stories.d.ts +6 -0
- package/dist/src/components/button.stories.d.ts.map +1 -0
- package/dist/src/components/button.stories.js +46 -0
- package/dist/src/components/button.stories.js.map +1 -0
- package/dist/src/components/code/one_light_theme.d.ts +3 -0
- package/dist/src/components/code/one_light_theme.d.ts.map +1 -0
- package/dist/src/components/code/one_light_theme.js +108 -0
- package/dist/src/components/code/one_light_theme.js.map +1 -0
- package/dist/src/components/code/prism_highlighter.d.ts.map +1 -1
- package/dist/src/components/code/prism_highlighter.js +4 -3
- package/dist/src/components/code/prism_highlighter.js.map +1 -1
- package/dist/src/components/code/prism_python.d.ts +1 -0
- package/dist/src/components/code/prism_python.d.ts.map +1 -0
- package/dist/src/components/code/prism_python.js +76 -0
- package/dist/src/components/code/prism_python.js.map +1 -0
- package/dist/src/components/icon_button.d.ts +55 -0
- package/dist/src/components/icon_button.d.ts.map +1 -0
- package/dist/src/components/icon_button.js +134 -0
- package/dist/src/components/icon_button.js.map +1 -0
- package/dist/src/components/icon_button.stories.d.ts +36 -0
- package/dist/src/components/icon_button.stories.d.ts.map +1 -0
- package/dist/src/components/icon_button.stories.js +59 -0
- package/dist/src/components/icon_button.stories.js.map +1 -0
- package/dist/src/components/index.d.ts +2 -0
- package/dist/src/components/index.d.ts.map +1 -1
- package/dist/src/components/index.js +2 -0
- package/dist/src/components/index.js.map +1 -1
- package/dist/src/components/spinner.d.ts +32 -0
- package/dist/src/components/spinner.d.ts.map +1 -0
- package/dist/src/components/spinner.js +84 -0
- package/dist/src/components/spinner.js.map +1 -0
- package/dist/src/components/spinner.stories.d.ts +8 -0
- package/dist/src/components/spinner.stories.d.ts.map +1 -0
- package/dist/src/components/spinner.stories.js +24 -0
- package/dist/src/components/spinner.stories.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/components/{button.stories.jsx → button.stories.tsx} +26 -11
- package/src/components/button.tsx +43 -11
- package/src/components/code/one_light_theme.ts +110 -0
- package/src/components/code/prism_highlighter.tsx +4 -3
- package/src/components/code/prism_python.ts +76 -0
- package/src/components/icon_button.stories.tsx +251 -0
- package/src/components/icon_button.tsx +211 -0
- package/src/components/index.ts +2 -0
- package/src/components/spinner.stories.tsx +37 -0
- package/src/components/spinner.tsx +116 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import styled, { css } from 'styled-components';
|
|
3
|
+
|
|
4
|
+
import type { IconComponent, IconSize } from '@apify/ui-icons';
|
|
5
|
+
|
|
6
|
+
import type { ValueOf } from '@apify-packages/types';
|
|
7
|
+
|
|
8
|
+
import { theme } from '../design_system/theme.js';
|
|
9
|
+
import type { RegularBoxProps } from './box.js';
|
|
10
|
+
import { Button, BUTTON_SIZE_VARIANTS_CSS, type ButtonSize, type RegularButtonProps, type TransientButtonProps } from './button.js';
|
|
11
|
+
import { Tooltip } from './floating/tooltip.js';
|
|
12
|
+
import { Link, type RegularLinkProps } from './link.js';
|
|
13
|
+
import { InlineSpinner, spinnerClassNames } from './spinner.js';
|
|
14
|
+
|
|
15
|
+
export const ICON_BUTTON_VARIANTS = {
|
|
16
|
+
DEFAULT: 'DEFAULT',
|
|
17
|
+
BORDERED: 'BORDERED',
|
|
18
|
+
DANGER: 'DANGER',
|
|
19
|
+
DANGER_BORDERED: 'DANGER_BORDERED',
|
|
20
|
+
} as const;
|
|
21
|
+
export type ICON_BUTTON_VARIANTS = ValueOf<typeof ICON_BUTTON_VARIANTS>;
|
|
22
|
+
|
|
23
|
+
const iconButtonVariantStyle = {
|
|
24
|
+
[ICON_BUTTON_VARIANTS.DEFAULT]: {
|
|
25
|
+
backgroundColor: 'transparent',
|
|
26
|
+
backgroundHoverColor: theme.color.neutral.hover,
|
|
27
|
+
backgroundDisabledColor: 'transparent',
|
|
28
|
+
borderColor: 'transparent',
|
|
29
|
+
borderHoverColor: 'transparent',
|
|
30
|
+
borderDisabledColor: 'transparent',
|
|
31
|
+
iconColor: theme.color.neutral.text,
|
|
32
|
+
},
|
|
33
|
+
[ICON_BUTTON_VARIANTS.BORDERED]: {
|
|
34
|
+
backgroundColor: theme.color.neutral.backgroundMuted,
|
|
35
|
+
backgroundHoverColor: theme.color.neutral.hover,
|
|
36
|
+
backgroundDisabledColor: theme.color.neutral.disabled,
|
|
37
|
+
borderColor: theme.color.neutral.border,
|
|
38
|
+
borderHoverColor: theme.color.neutral.border,
|
|
39
|
+
borderDisabledColor: theme.color.neutral.border,
|
|
40
|
+
iconColor: theme.color.neutral.text,
|
|
41
|
+
},
|
|
42
|
+
[ICON_BUTTON_VARIANTS.DANGER_BORDERED]: {
|
|
43
|
+
backgroundColor: theme.color.neutral.backgroundMuted,
|
|
44
|
+
backgroundHoverColor: theme.color.danger.backgroundHover,
|
|
45
|
+
backgroundDisabledColor: theme.color.neutral.backgroundSubtle,
|
|
46
|
+
borderColor: theme.color.neutral.border,
|
|
47
|
+
borderHoverColor: theme.color.danger.borderSubtle,
|
|
48
|
+
borderDisabledColor: theme.color.neutral.border,
|
|
49
|
+
iconColor: theme.color.danger.text,
|
|
50
|
+
},
|
|
51
|
+
[ICON_BUTTON_VARIANTS.DANGER]: {
|
|
52
|
+
backgroundColor: 'transparent',
|
|
53
|
+
backgroundHoverColor: theme.color.danger.backgroundHover,
|
|
54
|
+
backgroundDisabledColor: theme.color.neutral.backgroundSubtle,
|
|
55
|
+
borderColor: 'transparent',
|
|
56
|
+
borderHoverColor: 'transparent',
|
|
57
|
+
borderDisabledColor: 'transparent',
|
|
58
|
+
iconColor: theme.color.danger.text,
|
|
59
|
+
},
|
|
60
|
+
} satisfies Record<ICON_BUTTON_VARIANTS, unknown>;
|
|
61
|
+
|
|
62
|
+
type IconButtonSizeConfig = {
|
|
63
|
+
iconSize: IconSize;
|
|
64
|
+
spinnerSize: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const iconButtonSizeConfig: Record<ButtonSize, IconButtonSizeConfig> = {
|
|
68
|
+
extraLarge: {
|
|
69
|
+
iconSize: '24',
|
|
70
|
+
spinnerSize: '2.4rem',
|
|
71
|
+
},
|
|
72
|
+
large: {
|
|
73
|
+
iconSize: '20',
|
|
74
|
+
spinnerSize: '2rem',
|
|
75
|
+
},
|
|
76
|
+
medium: {
|
|
77
|
+
iconSize: '16',
|
|
78
|
+
spinnerSize: '1.6rem',
|
|
79
|
+
},
|
|
80
|
+
small: {
|
|
81
|
+
iconSize: '16',
|
|
82
|
+
spinnerSize: '1.6rem',
|
|
83
|
+
},
|
|
84
|
+
extraSmall: {
|
|
85
|
+
iconSize: '12',
|
|
86
|
+
spinnerSize: '1.2rem',
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const getIconButtonColorStyles = (variant: ICON_BUTTON_VARIANTS) => {
|
|
91
|
+
const {
|
|
92
|
+
backgroundColor,
|
|
93
|
+
backgroundHoverColor,
|
|
94
|
+
backgroundDisabledColor,
|
|
95
|
+
borderDisabledColor,
|
|
96
|
+
borderColor,
|
|
97
|
+
borderHoverColor,
|
|
98
|
+
iconColor,
|
|
99
|
+
} = iconButtonVariantStyle[variant];
|
|
100
|
+
|
|
101
|
+
return css`
|
|
102
|
+
color: ${iconColor};
|
|
103
|
+
background-color: ${backgroundColor};
|
|
104
|
+
border-color: ${borderColor};
|
|
105
|
+
|
|
106
|
+
&:hover {
|
|
107
|
+
background-color: ${backgroundHoverColor};
|
|
108
|
+
border-color: ${borderHoverColor || borderColor};
|
|
109
|
+
color: ${iconColor};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
&:focus {
|
|
113
|
+
border-color: ${theme.color.primary.fieldBorderActive};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
&:active {
|
|
117
|
+
background-color: ${theme.color.neutral.actionSecondaryActive};
|
|
118
|
+
border-color: ${theme.color.neutral.actionSecondaryActive};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
&:disabled {
|
|
122
|
+
color: ${theme.color.neutral.iconDisabled};
|
|
123
|
+
background-color: ${backgroundDisabledColor};
|
|
124
|
+
border-color: ${borderDisabledColor};
|
|
125
|
+
|
|
126
|
+
cursor: default;
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const StyledButton = styled(Button) <{ $variant: ICON_BUTTON_VARIANTS, $size: ButtonSize }>`
|
|
132
|
+
${({ $variant }) => getIconButtonColorStyles($variant)}
|
|
133
|
+
|
|
134
|
+
/* Override default button styles */
|
|
135
|
+
box-sizing: border-box;
|
|
136
|
+
width: ${({ $size }) => (BUTTON_SIZE_VARIANTS_CSS[$size].size)}px;
|
|
137
|
+
padding: 0;
|
|
138
|
+
|
|
139
|
+
.${spinnerClassNames.SPINNER} .path {
|
|
140
|
+
stroke: ${theme.color.neutral.icon};
|
|
141
|
+
}
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
type IconButtonNodeType = Extract<React.ElementType, 'a' | 'button'>;
|
|
145
|
+
type IconButtonNodePropsMap = {
|
|
146
|
+
a: {
|
|
147
|
+
element: HTMLAnchorElement;
|
|
148
|
+
props: RegularLinkProps;
|
|
149
|
+
};
|
|
150
|
+
button: {
|
|
151
|
+
element: HTMLButtonElement;
|
|
152
|
+
props: RegularButtonProps;
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export type IconButtonProps<T extends IconButtonNodeType> = RegularBoxProps & Pick<TransientButtonProps, 'size'> & {
|
|
157
|
+
as?: T
|
|
158
|
+
variant?: ICON_BUTTON_VARIANTS
|
|
159
|
+
Icon: IconComponent
|
|
160
|
+
disabled?: boolean
|
|
161
|
+
isLoading?: boolean
|
|
162
|
+
title?: string
|
|
163
|
+
tooltipProps?: unknown
|
|
164
|
+
} & Omit<IconButtonNodePropsMap[T]['props'], 'size' | 'variant'>;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Simplified button component that displays an icon.
|
|
168
|
+
* Has a tooltip and a loading state.
|
|
169
|
+
*/
|
|
170
|
+
export const IconButton = forwardRef(<T extends IconButtonNodeType>(
|
|
171
|
+
{
|
|
172
|
+
variant = ICON_BUTTON_VARIANTS.DEFAULT,
|
|
173
|
+
size = 'medium',
|
|
174
|
+
as = 'button' as T,
|
|
175
|
+
title,
|
|
176
|
+
disabled = false,
|
|
177
|
+
isLoading = false,
|
|
178
|
+
Icon,
|
|
179
|
+
tooltipProps,
|
|
180
|
+
...passThroughProps
|
|
181
|
+
}: IconButtonProps<T>, ref: React.Ref<IconButtonNodePropsMap[T]['element']>) => {
|
|
182
|
+
const otherProps = { ...passThroughProps, size };
|
|
183
|
+
|
|
184
|
+
const component: React.ElementType = as === 'a' ? Link : as;
|
|
185
|
+
const { iconSize, spinnerSize } = iconButtonSizeConfig[size];
|
|
186
|
+
|
|
187
|
+
const button = (
|
|
188
|
+
<StyledButton
|
|
189
|
+
forwardedAs={component}
|
|
190
|
+
ref={ref}
|
|
191
|
+
disabled={disabled || isLoading}
|
|
192
|
+
type='button'
|
|
193
|
+
aria-label={title}
|
|
194
|
+
LeftIcon={isLoading ? () => <InlineSpinner size={spinnerSize} /> : () => <Icon size={iconSize} />}
|
|
195
|
+
// We apply our own styles to the button, so we need to override the default variant
|
|
196
|
+
variant="tertiary"
|
|
197
|
+
$size={size}
|
|
198
|
+
$variant={variant}
|
|
199
|
+
{...otherProps}
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
return title ? (
|
|
204
|
+
// @ts-expect-error tooltip is not migrated to TS yet
|
|
205
|
+
<Tooltip content={title} {...tooltipProps}>
|
|
206
|
+
{button}
|
|
207
|
+
</Tooltip>
|
|
208
|
+
) : button;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
IconButton.displayName = 'IconButton';
|
package/src/components/index.ts
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Meta } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { InlineSpinner, Spinner, type SpinnerProps } from './spinner.js';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'UI-Library/Spinner',
|
|
7
|
+
parameters: { layout: 'padded' },
|
|
8
|
+
component: Spinner,
|
|
9
|
+
args: {},
|
|
10
|
+
} as Meta<typeof Spinner>;
|
|
11
|
+
|
|
12
|
+
const Template = (args: SpinnerProps) => <Spinner {...args} />;
|
|
13
|
+
|
|
14
|
+
export const Standalone = Template.bind({});
|
|
15
|
+
|
|
16
|
+
export const WithinContainer = (args: SpinnerProps) => {
|
|
17
|
+
return (
|
|
18
|
+
<div style={{
|
|
19
|
+
minWidth: '100px',
|
|
20
|
+
minHeight: '200px',
|
|
21
|
+
border: 'solid 1px lightgray',
|
|
22
|
+
borderRadius: '1rem',
|
|
23
|
+
position: 'relative',
|
|
24
|
+
}}>
|
|
25
|
+
<Spinner {...args} />
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const Inline = () => {
|
|
31
|
+
const elements: React.ElementType[] = ['h1', 'h2', 'h3', 'p'];
|
|
32
|
+
return (
|
|
33
|
+
<div style={{ width: '100%' }}>
|
|
34
|
+
{elements.map((Element) => <Element key={Element}>{Element}: There was a <InlineSpinner /> in New Orleans.</Element>)}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import styled, { keyframes } from 'styled-components';
|
|
2
|
+
|
|
3
|
+
import { theme } from '../design_system/theme.js';
|
|
4
|
+
import type { WithTransientProps } from '../type_utils.js';
|
|
5
|
+
|
|
6
|
+
export const spinnerClassNames = {
|
|
7
|
+
SPINNER: 'spinner',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const rotate = keyframes`
|
|
11
|
+
100% {
|
|
12
|
+
transform: rotate(360deg);
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const dash = keyframes`
|
|
17
|
+
0% {
|
|
18
|
+
stroke-dasharray: 1, 150;
|
|
19
|
+
stroke-dashoffset: 0;
|
|
20
|
+
}
|
|
21
|
+
50% {
|
|
22
|
+
stroke-dasharray: 90, 150;
|
|
23
|
+
stroke-dashoffset: -35;
|
|
24
|
+
}
|
|
25
|
+
100% {
|
|
26
|
+
stroke-dasharray: 90, 150;
|
|
27
|
+
stroke-dashoffset: -124;
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
export type SpinnerProps = {
|
|
32
|
+
id?: string;
|
|
33
|
+
as?: React.ElementType;
|
|
34
|
+
className?: string;
|
|
35
|
+
style?: React.CSSProperties;
|
|
36
|
+
small?: boolean,
|
|
37
|
+
loadingReason?: string,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type SpinnerWrapperProps = WithTransientProps<Pick<SpinnerProps, 'small'>>;
|
|
41
|
+
|
|
42
|
+
// styled-components used in order to make 'as' prop work by default
|
|
43
|
+
// span is intentional instead of div, so it can be validly nested in a <p>
|
|
44
|
+
// display: block is used because it was div before and we don't wanna break anything
|
|
45
|
+
const SpinnerWrapper = styled.span<SpinnerWrapperProps>`
|
|
46
|
+
display: block;
|
|
47
|
+
|
|
48
|
+
.${spinnerClassNames.SPINNER} {
|
|
49
|
+
position: absolute;
|
|
50
|
+
top: 50%;
|
|
51
|
+
left: 50%;
|
|
52
|
+
transform: translate(-50%, -50%);
|
|
53
|
+
z-index: 2;
|
|
54
|
+
|
|
55
|
+
width: ${({ $small }) => ($small ? '3rem' : '5rem')};
|
|
56
|
+
height: ${({ $small }) => ($small ? '3rem' : '5rem')};
|
|
57
|
+
|
|
58
|
+
& .path {
|
|
59
|
+
transform-origin: center;
|
|
60
|
+
stroke: ${theme.color.primary.text};
|
|
61
|
+
animation: ${dash} 1.5s ease-in-out infinite, ${rotate} 2s linear infinite;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Visualize loading state of a component.
|
|
68
|
+
*
|
|
69
|
+
* Use this spinner when a page or section is waiting for asynchronous data.
|
|
70
|
+
* Do not use it for buttons and small components, apply only for bigger sections.
|
|
71
|
+
*
|
|
72
|
+
* Keep the spinner centered (eg. by setting position to parent container).
|
|
73
|
+
*/
|
|
74
|
+
export const Spinner = ({ loadingReason, small, ...props }: SpinnerProps) => {
|
|
75
|
+
return (
|
|
76
|
+
<SpinnerWrapper {...props} title={loadingReason} $small={small}>
|
|
77
|
+
<svg className={spinnerClassNames.SPINNER} viewBox="0 0 50 50">
|
|
78
|
+
<circle
|
|
79
|
+
className="path"
|
|
80
|
+
cx="25"
|
|
81
|
+
cy="25"
|
|
82
|
+
r="20"
|
|
83
|
+
fill="none"
|
|
84
|
+
strokeWidth="5"
|
|
85
|
+
strokeLinecap="round"
|
|
86
|
+
></circle>
|
|
87
|
+
</svg>
|
|
88
|
+
</SpinnerWrapper>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Spinner with basic block styling
|
|
94
|
+
* This styles should probably be in default spinner, but it's dangerous to modify it as it's used on so many places
|
|
95
|
+
*/
|
|
96
|
+
export const BlockSpinner = styled(Spinner)`
|
|
97
|
+
position: relative; /* "hold" absolutely positioned spinner */
|
|
98
|
+
width: 100%;
|
|
99
|
+
min-height: 8rem;
|
|
100
|
+
`;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Use the inline variant within buttons, texts etc. The color of the spinner is set to current color of text.
|
|
104
|
+
*/
|
|
105
|
+
export const InlineSpinner = styled(Spinner)<{size?: string}>`
|
|
106
|
+
display: inline-block;
|
|
107
|
+
position: relative;
|
|
108
|
+
height: ${({ size }) => size || theme.space.space12};
|
|
109
|
+
width: ${({ size }) => size || theme.space.space12};
|
|
110
|
+
vertical-align: middle;
|
|
111
|
+
|
|
112
|
+
.${spinnerClassNames.SPINNER} {
|
|
113
|
+
width: 100%;
|
|
114
|
+
height: 100%;
|
|
115
|
+
}
|
|
116
|
+
`;
|