@fibery/ui-kit 1.7.3 → 1.7.4

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,11 +1,11 @@
1
1
  {
2
2
  "name": "@fibery/ui-kit",
3
- "version": "1.7.3",
3
+ "version": "1.7.4",
4
4
  "main": "index.ts",
5
5
  "private": false,
6
6
  "files": [
7
7
  "src/antd/styles.ts",
8
- "src/Button.ts",
8
+ "src/Button.tsx",
9
9
  "src/designSystem.ts",
10
10
  "src/theme-styles.ts",
11
11
  "src/theme-settings.ts",
@@ -21,7 +21,8 @@
21
21
  "src/error-alert.tsx",
22
22
  "src/Pallete.ts",
23
23
  "src/tooltip.tsx",
24
- "src/BackButton.tsx"
24
+ "src/BackButton.tsx",
25
+ "src/Button"
25
26
  ],
26
27
  "license": "UNLICENSED",
27
28
  "dependencies": {
@@ -0,0 +1,13 @@
1
+ import {forwardRef} from "react";
2
+ import AddIcon from "../icons/react/Add";
3
+ import {Button, ButtonProps} from "./button";
4
+
5
+ type Props = {
6
+ disableReason?: string;
7
+ } & ButtonProps;
8
+
9
+ export const AddButton = forwardRef<HTMLButtonElement, Props>(
10
+ ({disableReason = "", Icon = AddIcon, ...rest}, forwardedRef) => {
11
+ return <Button ref={forwardedRef} borderless Icon={Icon} title={disableReason} {...rest} />;
12
+ }
13
+ );
@@ -0,0 +1,26 @@
1
+ import {ButtonSize, Button} from "./button";
2
+ import BackIcon from "../icons/react/Back";
3
+
4
+ type Props = {
5
+ disabled?: boolean;
6
+ pending?: boolean;
7
+ onClick: () => unknown;
8
+ size?: ButtonSize;
9
+ color?: string;
10
+ };
11
+
12
+ export function BackButton({disabled, onClick, pending, size, color}: Props) {
13
+ return (
14
+ <Button
15
+ borderless
16
+ Icon={BackIcon}
17
+ disabled={disabled}
18
+ onClick={onClick}
19
+ pending={pending}
20
+ size={size}
21
+ color={color}
22
+ >
23
+ Back
24
+ </Button>
25
+ );
26
+ }
@@ -0,0 +1,190 @@
1
+ import {styled} from "@linaria/react";
2
+ import {ComponentPropsWithoutRef, forwardRef} from "react";
3
+ import {border, colors, fontWeight, getDarkenColor, getOpacities, opacity, ThemeColors} from "../designSystem";
4
+ import {useTheme} from "../ThemeProvider";
5
+
6
+ export const getMainColor = ({color, dangerous, theme}: {color?: string; dangerous?: boolean; theme: ThemeColors}) => {
7
+ if (color) {
8
+ return color;
9
+ }
10
+ if (dangerous) {
11
+ return theme.danger;
12
+ }
13
+ if (!theme) {
14
+ return colors.primary;
15
+ }
16
+ return theme.buttonColor;
17
+ };
18
+
19
+ const getBorderColor = ({
20
+ mainColor,
21
+ borderless,
22
+ primary,
23
+ }: {
24
+ mainColor: string;
25
+ borderless?: boolean;
26
+ primary?: boolean;
27
+ }) => {
28
+ if (borderless) {
29
+ return colors.transparent;
30
+ }
31
+ if (primary) {
32
+ return mainColor;
33
+ }
34
+ return getOpacities(mainColor).opacity15;
35
+ };
36
+
37
+ const getHoverBorderColor = ({
38
+ mainColor,
39
+ borderless,
40
+ primary,
41
+ hoverBgColor,
42
+ }: {
43
+ mainColor: string;
44
+ borderless?: boolean;
45
+ primary?: boolean;
46
+ hoverBgColor: string;
47
+ }) => {
48
+ if (borderless) {
49
+ return colors.transparent;
50
+ }
51
+ if (primary) {
52
+ return hoverBgColor;
53
+ }
54
+ return mainColor;
55
+ };
56
+
57
+ export const getTextColor = ({
58
+ mainColor,
59
+ primary,
60
+ theme,
61
+ }: {
62
+ mainColor: string;
63
+ primary?: boolean;
64
+ theme: ThemeColors;
65
+ }) => {
66
+ if (primary) {
67
+ return theme.buttonPrimaryTextColor;
68
+ }
69
+ return mainColor;
70
+ };
71
+
72
+ const getBgColor = ({mainColor, primary}: {mainColor: string; primary?: boolean}) => {
73
+ if (primary) {
74
+ return mainColor;
75
+ }
76
+ return colors.transparent;
77
+ };
78
+
79
+ const getHoverBgColor = ({
80
+ mainColor,
81
+ borderless,
82
+ primary,
83
+ }: {
84
+ mainColor: string;
85
+ primary?: boolean;
86
+ borderless?: boolean;
87
+ }) => {
88
+ if (primary) {
89
+ return getDarkenColor(mainColor);
90
+ }
91
+ if (borderless) {
92
+ return getOpacities(mainColor).opacity15;
93
+ }
94
+ return colors.transparent;
95
+ };
96
+
97
+ export const buttonBorderWidth = 1;
98
+
99
+ type StyledProps = {
100
+ textColor: string;
101
+ bgColor: string;
102
+ borderColor: string;
103
+ hoverBgColor: string;
104
+ hoverBorderColor: string;
105
+ borderWidth: number;
106
+ };
107
+
108
+ const StyledButtonBase = styled.button<StyledProps>`
109
+ display: block;
110
+ box-sizing: border-box;
111
+ font-weight: ${fontWeight.bold};
112
+ margin: 0;
113
+ border-radius: ${border.radius6}px;
114
+ border: 0;
115
+ border-width: ${({borderWidth}) => `${borderWidth}px`};
116
+ border-style: solid;
117
+ padding: 0;
118
+ cursor: pointer;
119
+ transition: all 150ms ease-out;
120
+ color: ${({textColor}) => textColor};
121
+ background-color: ${({bgColor}) => bgColor};
122
+ border-color: ${({borderColor}) => borderColor};
123
+
124
+ &:disabled {
125
+ cursor: default;
126
+ opacity: ${opacity.opacity40};
127
+ }
128
+
129
+ &:focus {
130
+ outline: none;
131
+ }
132
+
133
+ &:focus-visible {
134
+ box-shadow: ${({borderColor}) => `0 0 0 3px ${getOpacities(borderColor).opacity25}`};
135
+ background-color: ${({hoverBgColor}) => hoverBgColor};
136
+ border-color: ${({hoverBorderColor}) => hoverBorderColor};
137
+ }
138
+
139
+ &:hover:not(:disabled) {
140
+ background-color: ${({hoverBgColor}) => hoverBgColor};
141
+ border-color: ${({hoverBorderColor}) => hoverBorderColor};
142
+ }
143
+ `;
144
+
145
+ export type ButtonBaseProps = {
146
+ dataId?: string;
147
+ color?: string;
148
+ primary?: boolean;
149
+ dangerous?: boolean;
150
+ borderless?: boolean;
151
+ } & ComponentPropsWithoutRef<"button">;
152
+ export const ButtonBase = forwardRef<HTMLButtonElement, ButtonBaseProps>(function ButtonBase(
153
+ {primary, color, borderless, dangerous, className, type = "button", dataId, ...buttonProps},
154
+ ref
155
+ ) {
156
+ const theme = useTheme();
157
+
158
+ const mainColor = getMainColor({color, dangerous, theme});
159
+ const textColor = getTextColor({mainColor, primary, theme});
160
+ const bgColor = getBgColor({mainColor, primary});
161
+ const hoverBgColor = getHoverBgColor({mainColor, borderless, primary});
162
+ const borderColor = getBorderColor({mainColor, borderless, primary});
163
+ const hoverBorderColor = getHoverBorderColor({
164
+ mainColor,
165
+ borderless,
166
+ primary,
167
+ hoverBgColor,
168
+ });
169
+ const borderWidth = borderless ? 0 : buttonBorderWidth;
170
+
171
+ const styledProps: StyledProps = {
172
+ bgColor,
173
+ borderColor,
174
+ hoverBgColor,
175
+ hoverBorderColor,
176
+ textColor,
177
+ borderWidth,
178
+ };
179
+
180
+ return (
181
+ <StyledButtonBase
182
+ ref={ref}
183
+ type={type}
184
+ className={className}
185
+ data-testid={dataId}
186
+ {...styledProps}
187
+ {...buttonProps}
188
+ />
189
+ );
190
+ });
@@ -0,0 +1,76 @@
1
+ import {Children, cloneElement, CSSProperties, ReactElement} from "react";
2
+ import {css, cx} from "@linaria/core";
3
+ import {border} from "../designSystem";
4
+
5
+ const ordersInRowGroup = {
6
+ first: css`
7
+ ${{
8
+ display: "inline-flex",
9
+ borderRadius: `${border.radius6}px 0 0 ${border.radius6}px`,
10
+ marginRight: -1,
11
+ }}
12
+ `,
13
+ middle: css`
14
+ ${{
15
+ display: "inline-flex",
16
+ borderRadius: border.radius0,
17
+ marginRight: -1,
18
+ }}
19
+ `,
20
+ last: css`
21
+ ${{
22
+ display: "inline-flex",
23
+ borderRadius: `0 ${border.radius6}px ${border.radius6}px 0`,
24
+ }}
25
+ `,
26
+ };
27
+
28
+ const ordersInColumnGroup = {
29
+ first: css`
30
+ ${{
31
+ display: "inline-flex",
32
+ borderRadius: `${border.radius6}px ${border.radius6}px 0 0 `,
33
+ marginBottom: -1,
34
+ }}
35
+ `,
36
+ middle: css`
37
+ ${{
38
+ display: "inline-flex",
39
+ borderRadius: border.radius0,
40
+ marginBottom: -1,
41
+ }}
42
+ `,
43
+ last: css`
44
+ ${{
45
+ display: "inline-flex",
46
+ borderRadius: `0 0 ${border.radius6}px ${border.radius6}px`,
47
+ }}
48
+ `,
49
+ };
50
+
51
+ export interface ButtonGroupProps {
52
+ direction: CSSProperties["flexDirection"];
53
+ children: ReactElement[];
54
+ }
55
+
56
+ export function ButtonGroup({direction, children}: ButtonGroupProps) {
57
+ return (
58
+ <div
59
+ style={{
60
+ display: "flex",
61
+ alignItems: "flex-start",
62
+ flexDirection: direction,
63
+ }}
64
+ >
65
+ {Children.map(children, (child, index) => {
66
+ const inGroup = index === 0 ? "first" : index === children.length - 1 ? "last" : "middle";
67
+ return cloneElement(child, {
68
+ className: cx(
69
+ child.props.className,
70
+ direction === "row" ? ordersInRowGroup[inGroup] : ordersInColumnGroup[inGroup]
71
+ ),
72
+ });
73
+ })}
74
+ </div>
75
+ );
76
+ }
@@ -0,0 +1,242 @@
1
+ import {hasNonEmptyChild} from "@fibery/react/src/has-non-empty-child";
2
+ import {css} from "@linaria/core";
3
+ import {styled} from "@linaria/react";
4
+ import cx from "classnames";
5
+ import _ from "lodash";
6
+ import React, {forwardRef} from "react";
7
+ import {ButtonBase, ButtonBaseProps, buttonBorderWidth, getMainColor, getTextColor} from "./button-base";
8
+ import {border, colors, getOpacities, opacity, space, textStyles} from "../designSystem";
9
+ import MoreIcon from "../icons/react/More";
10
+ import MoreCompactIcon from "../icons/react/MoreCompact";
11
+ import {IconBaseProps} from "../icons/types";
12
+ import {Item} from "../Item";
13
+ import {Spinner} from "../loaders";
14
+ import {useTheme} from "../ThemeProvider";
15
+
16
+ type PositionInGroup = "first" | "middle" | "last";
17
+
18
+ export const ButtonSize = {
19
+ superSmall: ":button-size/super-small",
20
+ small: ":button-size/small",
21
+ normal: ":button-size/normal",
22
+ big: ":button-size/big",
23
+ } as const;
24
+ export type ButtonSize = (typeof ButtonSize)[keyof typeof ButtonSize];
25
+
26
+ const FontSizes = {
27
+ ":button-size/super-small": 10,
28
+ ":button-size/small": 12,
29
+ ":button-size/normal": 14,
30
+ ":button-size/big": 16,
31
+ } as const;
32
+
33
+ const HorizontalPaddings = {
34
+ ":button-size/super-small": 4,
35
+ ":button-size/small": 8,
36
+ ":button-size/normal": 12,
37
+ ":button-size/big": 16,
38
+ } as const;
39
+
40
+ const IconSizes = {
41
+ ":button-size/super-small": 16,
42
+ ":button-size/small": 18,
43
+ ":button-size/normal": 20,
44
+ ":button-size/big": 22,
45
+ };
46
+
47
+ type StyledProps = {
48
+ buttonSize: ButtonSize;
49
+ borderless?: boolean;
50
+ buttonWidth?: number | string;
51
+ buttonHeight?: number | string;
52
+ };
53
+
54
+ const getFontSize = ({buttonSize}: StyledProps) => FontSizes[buttonSize];
55
+ const getButtonMinSize = ({buttonSize}: StyledProps) => FontSizes[buttonSize] * 2;
56
+ const getHorizontalPadding = ({buttonSize, borderless}: StyledProps) =>
57
+ borderless ? HorizontalPaddings[buttonSize] : HorizontalPaddings[buttonSize] - buttonBorderWidth;
58
+
59
+ const StyledButton = styled(ButtonBase)<StyledProps>`
60
+ display: flex;
61
+ justify-content: center;
62
+ align-items: center;
63
+ vertical-align: middle;
64
+ position: relative;
65
+
66
+ width: ${({buttonWidth = "auto"}) => (typeof buttonWidth === "string" ? buttonWidth : `${buttonWidth}px`)};
67
+ height: ${({buttonHeight = "auto"}) => (typeof buttonHeight === "string" ? buttonHeight : `${buttonHeight}px`)};
68
+
69
+ font-size: ${getFontSize}px;
70
+ min-height: ${getButtonMinSize}px;
71
+ min-width: ${getButtonMinSize}px;
72
+ padding-left: ${getHorizontalPadding}px;
73
+ padding-right: ${getHorizontalPadding}px;
74
+ `;
75
+ export const buttonClassName = StyledButton;
76
+
77
+ // TODO: should be a different component, e.g. ButtonGroup
78
+ const ordersInGroup = {
79
+ first: css`
80
+ ${{
81
+ display: "inline-flex",
82
+ borderRadius: `${border.radius6}px 0 0 ${border.radius6}px`,
83
+ marginRight: -1,
84
+ }}
85
+ `,
86
+ middle: css`
87
+ ${{
88
+ display: "inline-flex",
89
+ borderRadius: border.radius0,
90
+ marginRight: -1,
91
+ }}
92
+ `,
93
+ last: css`
94
+ ${{
95
+ display: "inline-flex",
96
+ borderRadius: `0 ${border.radius6}px ${border.radius6}px 0`,
97
+ }}
98
+ `,
99
+ };
100
+
101
+ const getSpinnerColor = ({mainColor, primary}: {mainColor: string; primary?: boolean}) => {
102
+ if (primary) {
103
+ return colors.inversedTextColor;
104
+ }
105
+ return mainColor;
106
+ };
107
+
108
+ const getIconMagicConstants = _.memoize((size: ButtonSize) => {
109
+ const iconSize = IconSizes[size];
110
+
111
+ const leftContainerSize = 8;
112
+ const leftContainerMarginRight = iconSize - leftContainerSize;
113
+
114
+ return {iconSize, leftContainerSize, leftContainerMarginRight};
115
+ });
116
+
117
+ const shortcutStyle = css`
118
+ ${{
119
+ ...textStyles.small,
120
+ color: "currentColor",
121
+ borderRadius: border.radius4,
122
+ paddingLeft: space.xs,
123
+ paddingRight: space.xs,
124
+ paddingBottom: space.xxs,
125
+ marginRight: -space.xs,
126
+ marginLeft: space.xxs,
127
+ opacity: opacity.opacity80,
128
+ display: "inline-block",
129
+ }}
130
+ `;
131
+
132
+ const leftContainerMarginRightVar = "--fibery-button-left-container-margin-right";
133
+
134
+ const leftContainerClassName = css`
135
+ margin-right: var(${leftContainerMarginRightVar});
136
+ `;
137
+
138
+ export type ButtonProps = ButtonBaseProps & {
139
+ size?: ButtonSize;
140
+ pending?: boolean;
141
+ Icon?: ((props: IconBaseProps) => JSX.Element) | null;
142
+ inGroup?: PositionInGroup;
143
+ shortcut?: string;
144
+ noOverflow?: boolean;
145
+ width?: string | number;
146
+ height?: string | number;
147
+ };
148
+
149
+ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
150
+ {
151
+ disabled,
152
+ pending,
153
+ Icon,
154
+ inGroup,
155
+ shortcut,
156
+ noOverflow = true,
157
+ width,
158
+ height,
159
+ size = ":button-size/normal",
160
+ className,
161
+ children,
162
+ ...baseProps
163
+ },
164
+ forwardedRef
165
+ ) {
166
+ const theme = useTheme();
167
+ const mainColor = getMainColor({color: baseProps.color, dangerous: baseProps.dangerous, theme});
168
+ const textColor = getTextColor({mainColor, primary: baseProps.primary, theme});
169
+ const spinnerBgColor = getOpacities(textColor).opacity25;
170
+ const spinnerColor = getSpinnerColor({mainColor, primary: baseProps.primary});
171
+
172
+ const {iconSize, leftContainerSize, leftContainerMarginRight} = getIconMagicConstants(size);
173
+
174
+ const leftContainer = pending ? (
175
+ <Spinner backgroundColor={spinnerBgColor} color={spinnerColor} size={iconSize} containerSize={leftContainerSize} />
176
+ ) : Icon ? (
177
+ <Icon color={textColor} iconSize={iconSize} containerSize={leftContainerSize} />
178
+ ) : null;
179
+
180
+ const styleVariable: StyledProps = {
181
+ buttonHeight: height,
182
+ buttonWidth: width,
183
+ buttonSize: size,
184
+ borderless: baseProps.borderless,
185
+ };
186
+
187
+ const isDisabled = disabled || pending;
188
+
189
+ return (
190
+ <StyledButton
191
+ {...styleVariable}
192
+ ref={forwardedRef}
193
+ onClick={!isDisabled && baseProps.onClick ? baseProps.onClick : _.noop}
194
+ disabled={isDisabled}
195
+ className={cx(className, inGroup && ordersInGroup[inGroup])}
196
+ {...baseProps}
197
+ >
198
+ <Item
199
+ leftContainer={leftContainer}
200
+ style={
201
+ {
202
+ [leftContainerMarginRightVar]: `${leftContainerMarginRight}px`,
203
+ } as React.CSSProperties
204
+ }
205
+ containerClassName={cx({[leftContainerClassName]: leftContainer && hasNonEmptyChild(children)})}
206
+ rightContainer={shortcut && <span className={cx(shortcutStyle)}>{shortcut}</span>}
207
+ noOverflow={noOverflow}
208
+ >
209
+ {children}
210
+ </Item>
211
+ </StyledButton>
212
+ );
213
+ });
214
+
215
+ // TODO can be inlined
216
+ export function ActionsButton({
217
+ onClick,
218
+ label = ``,
219
+ disabled,
220
+ }: {
221
+ onClick: ButtonProps["onClick"];
222
+ label?: string;
223
+ disabled?: boolean;
224
+ }) {
225
+ return <Button Icon={MoreIcon} onClick={onClick} borderless aria-label={label} disabled={disabled} />;
226
+ }
227
+
228
+ export const ActionsButtonCompact = forwardRef<HTMLButtonElement, ButtonProps & {inverted?: boolean}>(
229
+ ({inverted, ...rest}, ref) => {
230
+ const theme = useTheme();
231
+ return (
232
+ <Button
233
+ ref={ref}
234
+ size={":button-size/small"}
235
+ Icon={MoreCompactIcon}
236
+ color={inverted ? colors.inversedTextColor : theme.buttonColor}
237
+ borderless
238
+ {...rest}
239
+ />
240
+ );
241
+ }
242
+ );
@@ -0,0 +1,15 @@
1
+ import {forwardRef} from "react";
2
+ import {Tooltip, TooltipProps} from "../tooltip";
3
+ import {IconButton, IconButtonProps} from "./icon-button";
4
+
5
+ type IconButtonTooltipProps = Pick<TooltipProps, "title" | "description" | "content" | "side">;
6
+
7
+ export const IconButtonWithTooltip = forwardRef<HTMLButtonElement, IconButtonTooltipProps & IconButtonProps>(
8
+ function IconButtonWithTooltip({title, description, content, side, ...props}, ref) {
9
+ return (
10
+ <Tooltip title={title} description={description} content={content} side={side}>
11
+ <IconButton ref={ref} {...props} />
12
+ </Tooltip>
13
+ );
14
+ }
15
+ );
@@ -0,0 +1,42 @@
1
+ import {styled} from "@linaria/react";
2
+ import {Children, cloneElement, forwardRef} from "react";
3
+ import {ButtonBase, ButtonBaseProps} from "./button-base";
4
+ import {IconProps} from "../icons/types";
5
+
6
+ export type IconButtonSize = "super-small" | "small" | "normal" | "big";
7
+
8
+ export type IconButtonProps = Omit<ButtonBaseProps, "borderless"> & {
9
+ size?: IconButtonSize;
10
+ };
11
+
12
+ const Paddings = {
13
+ "super-small": 0,
14
+ small: 2,
15
+ normal: 4,
16
+ big: 6,
17
+ };
18
+
19
+ type StyledProps = {
20
+ /** The size of the component. `super-small` is equivalent to the dense button styling */
21
+ size: IconButtonSize;
22
+ };
23
+
24
+ const getPadding = ({size}: StyledProps) => Paddings[size];
25
+
26
+ const StyledIconButton = styled(ButtonBase)<StyledProps>`
27
+ padding: ${getPadding}px;
28
+ `;
29
+
30
+ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(function IconButton(
31
+ {size = "normal", children, className, ...rest},
32
+ ref
33
+ ) {
34
+ const iconNode = Children.only(children) as React.ReactElement<IconProps>;
35
+
36
+ return (
37
+ <StyledIconButton ref={ref} size={size} className={className} borderless {...rest}>
38
+ {/* maybe better to use css variables? */}
39
+ {cloneElement(iconNode, {color: iconNode.props.color || "inherit"})}
40
+ </StyledIconButton>
41
+ );
42
+ });
File without changes