@xipkg/button 0.0.2 → 1.0.0

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/Button.tsx CHANGED
@@ -1,152 +1,48 @@
1
- import 'pkg.config.muidts';
2
-
3
- import { Typography, Button as MuiButton, darken, useTheme } from '@mui/material';
4
- import React, { FC, MouseEvent } from 'react';
5
-
6
- import { ButtonProps } from './types';
7
-
8
- import {
9
- buttonVariantsColor,
10
- buttonSizes,
11
- getButtonPadding,
12
- buttonDisabledStyle,
13
- getActionButtonStyle,
14
- typographyVariants,
15
- clickedPadding,
16
- buttonBorderStyle,
17
- } from './styles';
18
- import { ButtonSnackbar } from './ButtonSnackbar';
19
- import { IconContainer } from './IconContainer';
20
- import { Loading } from './Loading';
21
-
22
- declare module '@mui/material/Typography' {
23
- export interface TypographyPropsVariantOverrides {
24
- xl: true;
25
- l: true;
26
- m: true;
27
- s: true;
28
- xs: true;
29
- xxs: true;
30
- }
1
+ import * as React from 'react';
2
+ import { Slot } from '@radix-ui/react-slot';
3
+ import { cva, type VariantProps } from 'class-variance-authority';
4
+
5
+ import { cn } from '@xipkg/utils';
6
+
7
+ export const buttonVariants = cva(
8
+ 'inline-flex items-center w-fit justify-center ring-offset-gray-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'text-gray-0 dark:text-gray-100 bg-brand-80 hover:bg-brand-100 active:bg-brand-100 focus:bg-brand-100',
13
+ secondary: 'bg-gray-0 text-gray-100 border-gray-30 border-2 hover:bg-gray-5 active:bg-gray-5 focus:bg-gray-5',
14
+ ghost: 'text-gray-100 bg-gray-0 border-0 hover:bg-gray-5 active:bg-gray-5 focus:bg-gray-5',
15
+ error: 'bg-red-80 text-gray-0 hover:bg-red-100 active:bg-red-100 focus:bg-red-100',
16
+ success: 'bg-green-80 text-gray-0 hover:bg-green-100 active:bg-green-100 focus:bg-green-100'
17
+ },
18
+ size: {
19
+ l: 'h-14 px-8 rounded-xl text-[20px]',
20
+ m: 'h-12 px-6 rounded-lg text-[16px]',
21
+ s: 'h-8 px-4 rounded-md text-[14px]',
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: 'default',
26
+ size: 'l',
27
+ },
28
+ },
29
+ );
30
+
31
+ export interface ButtonProps
32
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
33
+ VariantProps<typeof buttonVariants> {
34
+ asChild?: boolean;
31
35
  }
32
36
 
33
- export const Button: FC<ButtonProps> = ({
34
- status = 'idle',
35
- size = 'medium',
36
- loadingPosition = 'center',
37
- variant = 'contained',
38
- color = 'primary',
39
- children,
40
- startIcon,
41
- endIcon,
42
- isSnackbar,
43
- snackbarText,
44
- isSnackbarIconStart,
45
- isSnackbarIconEnd,
46
- snackbarLoadingPosition,
47
- snackbarPosition,
48
- ...props
49
- }) => {
50
- const { sx, onClick, ...otherButtonProps } = props;
51
-
52
- const theme = useTheme();
53
-
54
- const actionButtonStyle = getActionButtonStyle(
55
- variant,
56
- color === 'grayscale' ? 'grayscale' : darken(theme.palette[color].dark, 0.2),
57
- );
58
-
59
- const buttonPadding = getButtonPadding(!!children, !!startIcon, !!endIcon);
60
-
61
- const buttonBorder = variant === 'outlined' ? buttonBorderStyle[size] : 'none';
62
- const buttonDisabled = status !== 'completed' ? buttonDisabledStyle[variant] : actionButtonStyle;
63
-
64
- const onButtonClick = (e: MouseEvent<HTMLButtonElement>) => {
65
- if (status === 'completed') return;
66
-
67
- if (onClick) {
68
- onClick(e);
69
- }
70
- };
71
-
72
- return (
73
- <MuiButton
74
- onClick={onButtonClick}
75
- disabled={status === 'pending' || status === 'completed'}
76
- size={size}
77
- disableRipple
78
- disableElevation
79
- sx={{
80
- textTransform: 'none',
81
- position: 'relative',
82
- minWidth: 0,
83
- border: buttonBorder,
84
- '&:hover': {
85
- ...actionButtonStyle,
86
- },
87
- '&:disabled': {
88
- ...buttonDisabled,
89
- },
90
- '&:active': {
91
- ...actionButtonStyle,
92
- pt: clickedPadding[size].pt,
93
- pb: clickedPadding[size].pb,
94
- },
95
- ...buttonSizes[size],
96
- ...buttonVariantsColor[variant][color],
97
- ...buttonPadding[size],
98
- ...sx,
99
- }}
100
- {...otherButtonProps}
101
- >
102
- {startIcon && (
103
- <IconContainer
104
- Icon={startIcon}
105
- size={size}
106
- order={0}
107
- status={status}
108
- isLoadingIcon={loadingPosition !== 'center'}
109
- />
110
- )}
111
- {endIcon && (
112
- <IconContainer
113
- Icon={endIcon}
114
- size={size}
115
- order={2}
116
- status={status}
117
- isLoadingIcon={loadingPosition !== 'center'}
118
- />
119
- )}
120
-
121
- {children && (
122
- <Typography
123
- order={1}
124
- variant={typographyVariants[size]}
125
- sx={{
126
- color: 'inherit',
127
- opacity: loadingPosition === 'center' && status === 'pending' ? 0 : 1,
128
- }}
129
- >
130
- {children}
131
- </Typography>
132
- )}
133
-
134
- {status === 'pending' && loadingPosition === 'center' && (
135
- <Loading top="50%" left="50%" transform="translate(-50%,-50%)" size={size} />
136
- )}
137
-
138
- {isSnackbar && status !== 'idle' && (
139
- <ButtonSnackbar
140
- size={size}
141
- variant={variant}
142
- status={status}
143
- snackbarText={snackbarText}
144
- isSnackbarIconStart={isSnackbarIconStart}
145
- isSnackbarIconEnd={isSnackbarIconEnd}
146
- snackbarLoadingPosition={snackbarLoadingPosition}
147
- snackbarPosition={snackbarPosition}
148
- />
149
- )}
150
- </MuiButton>
151
- );
152
- };
37
+ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
38
+ ({ className, variant, size, asChild = false, children, ...props }, ref) => {
39
+ const Comp = asChild ? Slot : 'button';
40
+ return (
41
+ <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props}>
42
+ {children}
43
+ </Comp>
44
+ );
45
+ },
46
+ );
47
+
48
+ Button.displayName = 'Button';
package/index.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { Button } from './Button';
2
- export type { ButtonProps } from './types';
1
+ export { Button, buttonVariants } from './Button';
2
+ export type { ButtonProps } from './Button';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xipkg/button",
3
- "version": "0.0.2",
3
+ "version": "1.0.0",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "license": "MIT",
@@ -11,24 +11,25 @@
11
11
  "lint": "eslint \"**/*.ts*\""
12
12
  },
13
13
  "dependencies": {
14
- "@emotion/react": "^11.11.1",
15
- "@emotion/styled": "^11.11.0",
16
- "@mui/material": "^5.14.0",
17
- "@xipkg/icons": "^0.4.3",
14
+ "class-variance-authority": "^0.7.0",
15
+ "@xipkg/utils": "*",
16
+ "@radix-ui/react-slot": "^1.0.2",
18
17
  "react": "^18.2.0"
19
18
  },
20
19
  "devDependencies": {
21
20
  "@types/node": "^17.0.12",
22
21
  "@types/react": "^18.2.14",
23
22
  "@types/react-dom": "^18.2.6",
24
- "@xipkg/eslint": "^0.0.2",
23
+ "@xipkg/eslint": "^0.0.3",
25
24
  "@xipkg/typescript": "^0.0.0",
26
25
  "eslint": "^7.32.0",
27
- "typescript": "^5.1.3"
26
+ "typescript": "^5.1.3",
27
+ "tailwindcss": "^3.3.2"
28
28
  },
29
29
  "publishConfig": {
30
30
  "access": "public"
31
31
  },
32
32
  "author": "xi.effect",
33
- "description": ""
33
+ "description": "",
34
+ "gitHead": "0ee562e7c7723699c85daf61b493d6a57b7c1f08"
34
35
  }
@@ -1,104 +0,0 @@
1
- import { Typography, Stack } from '@mui/material';
2
- import React, { FC } from 'react';
3
-
4
- import {
5
- buttonSizes,
6
- typographyVariants,
7
- buttonDisabledStyle,
8
- getButtonPadding,
9
- getSnackbarCurrentPosition,
10
- buttonBorderStyle,
11
- } from './styles';
12
- import { LoadingPosition, Size, SnackbarPosition, Status, Variant } from './types';
13
- import { IconContainer } from './IconContainer';
14
- import { Loading } from './Loading';
15
-
16
- export const ButtonSnackbar: FC<ButtonSnackbarProps> = ({
17
- size,
18
- variant,
19
- status,
20
- snackbarText,
21
- isSnackbarIconStart,
22
- isSnackbarIconEnd,
23
- snackbarLoadingPosition = 'center',
24
- snackbarPosition = 'right',
25
- }) => {
26
- const snackbarRef = React.useRef<HTMLDivElement | null>(null);
27
- const [snackbarSize, setSnackbarSize] = React.useState(0);
28
- const snackbarCurrentPosition = getSnackbarCurrentPosition(snackbarSize, snackbarPosition);
29
-
30
- const buttonBorder = variant === 'outlined' ? buttonBorderStyle[size] : 'none';
31
-
32
- const snackbarPadding = getButtonPadding(
33
- !!snackbarText,
34
- !!isSnackbarIconStart,
35
- !!isSnackbarIconEnd,
36
- );
37
-
38
- React.useLayoutEffect(() => {
39
- if (!snackbarRef?.current) return;
40
- if (snackbarPosition === 'left' || snackbarPosition === 'right') {
41
- setSnackbarSize(snackbarRef.current.clientWidth);
42
- }
43
- if (snackbarPosition === 'top' || snackbarPosition === 'bottom') {
44
- setSnackbarSize(snackbarRef.current.clientHeight);
45
- }
46
- }, [snackbarPosition, size, snackbarText]);
47
-
48
- return (
49
- <Stack
50
- ref={snackbarRef}
51
- direction="row"
52
- alignItems="center"
53
- sx={{
54
- position: 'absolute',
55
- border: buttonBorder,
56
- ...snackbarCurrentPosition,
57
- ...buttonSizes[size],
58
- ...snackbarPadding[size],
59
- ...buttonDisabledStyle[variant],
60
- }}
61
- >
62
- {status === 'pending' && snackbarLoadingPosition === 'center' && (
63
- <Loading top="50%" left="50%" transform="translate(-50%,-50%)" size={size} />
64
- )}
65
- {isSnackbarIconStart && (
66
- <IconContainer
67
- size={size}
68
- order={0}
69
- status={status}
70
- isLoadingIcon={snackbarLoadingPosition !== 'center'}
71
- />
72
- )}
73
- {isSnackbarIconEnd && (
74
- <IconContainer
75
- size={size}
76
- order={2}
77
- status={status}
78
- isLoadingIcon={snackbarLoadingPosition !== 'center'}
79
- />
80
- )}
81
-
82
- {snackbarText && (
83
- <Typography
84
- color="inherit"
85
- sx={{ opacity: snackbarLoadingPosition === 'center' && status === 'pending' ? 0 : 1 }}
86
- variant={typographyVariants[size]}
87
- >
88
- {snackbarText}
89
- </Typography>
90
- )}
91
- </Stack>
92
- );
93
- };
94
-
95
- type ButtonSnackbarProps = {
96
- size: Size;
97
- variant: Variant;
98
- status: Status;
99
- snackbarText?: string;
100
- isSnackbarIconStart?: boolean;
101
- isSnackbarIconEnd?: boolean;
102
- snackbarLoadingPosition?: LoadingPosition;
103
- snackbarPosition?: SnackbarPosition;
104
- };
package/IconContainer.tsx DELETED
@@ -1,52 +0,0 @@
1
- import { Stack } from '@mui/material';
2
- import { Check } from 'pkg.icons';
3
- import { FC } from 'react';
4
- import { Loading } from './Loading';
5
- import { iconSizes } from './styles';
6
- import { Size, Status } from './types';
7
-
8
- export const IconContainer: FC<IconComponentProps> = ({
9
- order,
10
- Icon,
11
- size,
12
- status,
13
- isLoadingIcon,
14
- }) => (
15
- <Stack order={order} sx={{ position: 'relative', ...iconSizes[size] }}>
16
- {!!Icon && (
17
- <Icon
18
- sx={{
19
- opacity: status === 'idle' ? 1 : 0,
20
- color: 'inherit',
21
- ...iconSizes[size],
22
- }}
23
- />
24
- )}
25
- <Check
26
- sx={{
27
- opacity: status === 'completed' ? 1 : 0,
28
- position: 'absolute',
29
- top: '50%',
30
- transform: 'translateY(-50%)',
31
- color: 'inherit',
32
- ...iconSizes[size],
33
- }}
34
- />
35
- {isLoadingIcon && (
36
- <Loading
37
- size={size}
38
- top="50%"
39
- transform="translateY(-50%)"
40
- opacity={status === 'pending' ? 1 : 0}
41
- />
42
- )}
43
- </Stack>
44
- );
45
-
46
- type IconComponentProps = {
47
- Icon?: any;
48
- order: number;
49
- size: Size;
50
- status: Status;
51
- isLoadingIcon: boolean;
52
- };
package/Loading.tsx DELETED
@@ -1,18 +0,0 @@
1
- import { FC } from 'react';
2
- import { CircularProgress, Stack, SxProps } from '@mui/material';
3
- import { iconSizes, spinnerSizes } from './styles';
4
- import { Size } from './types';
5
-
6
- export const Loading: FC<LoadingProps> = ({ size, ...props }) => (
7
- <Stack
8
- sx={{
9
- position: 'absolute',
10
- ...iconSizes[size],
11
- ...props,
12
- }}
13
- >
14
- <CircularProgress size={spinnerSizes[size]} color="inherit" />
15
- </Stack>
16
- );
17
-
18
- type LoadingProps = { size: Size } & SxProps;
package/styles.ts DELETED
@@ -1,191 +0,0 @@
1
- import { SnackbarPosition, Variant } from './types';
2
-
3
- export const buttonSizes = {
4
- large: {
5
- height: '56px',
6
- borderRadius: '12px',
7
- gap: '16px',
8
- },
9
- medium: {
10
- height: '48px',
11
- borderRadius: '8px',
12
- gap: '8px',
13
- },
14
- small: {
15
- height: '32px',
16
- borderRadius: '6px',
17
- gap: '6px',
18
- },
19
- };
20
-
21
- export const buttonVariantsColor = {
22
- contained: {
23
- primary: {
24
- bgcolor: 'brand.80',
25
- color: 'petersburg.0',
26
- },
27
- success: {
28
- bgcolor: 'ekaterinburg.80',
29
- color: 'petersburg.0',
30
- },
31
- error: {
32
- bgcolor: 'moscow.80',
33
- color: 'petersburg.0',
34
- },
35
- grayscale: {
36
- bgcolor: 'petersburg.40',
37
- color: 'petersburg.100',
38
- },
39
- },
40
- outlined: {
41
- primary: {
42
- borderColor: 'brand.80',
43
- color: 'brand.80',
44
- },
45
- success: {
46
- borderColor: 'ekaterinburg.80',
47
- color: 'ekaterinburg.80',
48
- },
49
- error: {
50
- borderColor: 'moscow.80',
51
- color: 'moscow.80',
52
- },
53
- grayscale: {
54
- color: 'petersburg.100',
55
- borderColor: 'petersburg.40',
56
- },
57
- },
58
- text: {
59
- primary: { color: 'brand.80' },
60
- success: { color: 'ekaterinburg.80' },
61
- error: { color: 'moscow.80' },
62
- grayscale: { color: 'petersburg.100' },
63
- },
64
- };
65
-
66
- export const buttonDisabledStyle = {
67
- contained: {
68
- backgroundColor: 'petersburg.10',
69
- color: 'petersburg.40',
70
- },
71
- outlined: {
72
- border: '1px solid',
73
- borderColor: 'petersburg.10',
74
- color: 'petersburg.40',
75
- },
76
- text: {
77
- color: 'petersburg.40',
78
- },
79
- };
80
-
81
- export const buttonBorderStyle = {
82
- large: '2px solid',
83
- medium: '2px solid',
84
- small: '1px solid',
85
- };
86
-
87
- export const clickedPadding = {
88
- large: { pt: '14px', pb: '12px' },
89
- medium: { pt: '13px', pb: '11px' },
90
- small: { pt: '9px', pb: '7px' },
91
- };
92
-
93
- export const typographyVariants = {
94
- large: 'l' as 'l',
95
- medium: 'm' as 'm',
96
- small: 's' as 's',
97
- };
98
-
99
- export const iconSizes = {
100
- large: {
101
- width: '32px',
102
- height: '32px',
103
- },
104
- medium: {
105
- width: '24px',
106
- height: '24px',
107
- },
108
- small: {
109
- width: '16px',
110
- height: '16px',
111
- },
112
- };
113
-
114
- export const spinnerSizes = {
115
- large: 32,
116
- medium: 24,
117
- small: 16,
118
- };
119
-
120
- export const getButtonPadding = (isText: boolean, startIcon: boolean, endIcon: boolean) => {
121
- const isTextIconButton = isText && (startIcon || endIcon);
122
- const isIconButton = !isText && (startIcon || endIcon);
123
-
124
- const style = {
125
- large: {
126
- padding: '13px 32px',
127
- },
128
- medium: {
129
- padding: '12px 24px',
130
- },
131
- small: {
132
- padding: '8px 16px',
133
- },
134
- };
135
-
136
- if (isTextIconButton) {
137
- if (startIcon) {
138
- style.large.padding = '13px 24px 13px 12px';
139
- style.medium.padding = '12px 16px 12px 12px';
140
- style.small.padding = '8px 12px 8px 8px';
141
- return style;
142
- }
143
- if (endIcon) {
144
- style.large.padding = '13px 12px 13px 24px';
145
- style.medium.padding = '12px 12px 12px 16px';
146
- style.small.padding = '8px 8px 8px 12px';
147
- return style;
148
- }
149
- }
150
-
151
- if (isIconButton) {
152
- style.large.padding = '13px 12px';
153
- style.medium.padding = '12px 12px';
154
- style.small.padding = '8px 8px';
155
- return style;
156
- }
157
-
158
- return style;
159
- };
160
-
161
- export const getSnackbarCurrentPosition = (snackbarSize: number, position: SnackbarPosition) => {
162
- switch (position) {
163
- case 'top':
164
- return { top: `-${snackbarSize + 8}px`, right: '50%', transform: 'translateX(50%)' };
165
- case 'bottom':
166
- return { bottom: `-${snackbarSize + 8}px`, right: '50%', transform: 'translateX(50%)' };
167
- case 'left':
168
- return { left: `-${snackbarSize + 16}px`, top: 0 };
169
- default: {
170
- return { right: `-${snackbarSize + 16}px` };
171
- }
172
- }
173
- };
174
-
175
- export const getActionButtonStyle = (variant: Variant, color: string) => {
176
- if (color === 'grayscale') {
177
- return { bgcolor: 'petersburg.5', color: 'petersburg.100' };
178
- }
179
-
180
- switch (variant) {
181
- case 'contained':
182
- return { bgcolor: color, color: 'petersburg.0' };
183
- case 'outlined':
184
- return { bgcolor: 'petersburg.5', borderColor: color, color };
185
- default:
186
- return {
187
- bgcolor: 'petersburg.5',
188
- color,
189
- };
190
- }
191
- };
package/types.ts DELETED
@@ -1,38 +0,0 @@
1
- import { ButtonProps as MuiButtonProps } from '@mui/material';
2
-
3
- export type Status = 'idle' | 'pending' | 'completed';
4
- export type Size = 'small' | 'medium' | 'large';
5
- export type Variant = 'contained' | 'outlined' | 'text';
6
- export type Color = 'primary' | 'success' | 'error' | 'grayscale';
7
- export type LoadingPosition = 'icon' | 'center';
8
- export type SnackbarPosition = 'left' | 'right' | 'top' | 'bottom';
9
-
10
- export type ButtonProps = {
11
- // button status: pending = spinner appears, completed button disabled with active color
12
- // completed and startIcon or endIcon or only icon without text appears check
13
- status?: Status;
14
- size?: Size; // button size
15
- // button spinner position: icon above icon and center
16
- loadingPosition?: LoadingPosition;
17
- // button variant
18
- variant?: Variant;
19
- // button color
20
- color?: Color;
21
- // start or end button icon if icon without text, icon position will be center
22
- startIcon?: any;
23
- endIcon?: any;
24
- // whether to display snackbar
25
- isSnackbar?: boolean;
26
- // snackbar text
27
- snackbarText?: string;
28
- // start or end snackbar icon if icon without text, icon position will be center
29
- isSnackbarIconStart?: boolean;
30
- isSnackbarIconEnd?: boolean;
31
- // snackbar spinner position: icon above icon and center
32
- snackbarLoadingPosition?: LoadingPosition;
33
- // snackbar position: default right
34
- snackbarPosition?: SnackbarPosition;
35
- // styles
36
- sx?: any;
37
- // you can also pass default mui button attributes
38
- } & Omit<MuiButtonProps, 'startIcon' | 'endIcon' | 'color'>;