Skip to content

Commit 4ab3e8d

Browse files
Merge pull request #26 from AlbertArakelyan/25-set-up-the-design-system
feat(25): created design system in tailwind config
2 parents ead642d + eabba2d commit 4ab3e8d

File tree

8 files changed

+264
-30
lines changed

8 files changed

+264
-30
lines changed

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Editor from './components/Editor/Editor.tsx';
22

33
const App = () => (
4-
<div>
4+
<div className="bg-surface text-text-color">
55
<Editor />
66
</div>
77
);

src/assets/css/base.css

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,14 @@
3333
--ghost-button-active-primary-bg: color-mix(in srgb, var(--primary) 20%, transparent);
3434
--ghost-button-hover-secondary-bg: color-mix(in srgb, var(--secondary) 10%, transparent);
3535
--ghost-button-active-secondary-bg: color-mix(in srgb, var(--secondary) 20%, transparent);
36-
/*--ghost-button-hover-gray-bg: color-mix(in srgb, var(--muted-light) 10%, transparent);*/
37-
/*--ghost-button-active-gray-bg: color-mix(in srgb, var(--muted-light) 20%, transparent);*/
38-
39-
/*Cubic's suggestion, switch to these colors instead of var(--muted-light) in the future*/
4036
--ghost-button-hover-gray-bg: color-mix(in srgb, var(--muted-text) 10%, transparent);
4137
--ghost-button-active-gray-bg: color-mix(in srgb, var(--muted-text) 20%, transparent);
4238

4339
/* semantic */
44-
--success: #18b47b;
45-
--warning: #f5a524;
46-
--error: #e5484d;
47-
--info: #3aa1ff;
40+
--success: #18b47b; /* TODO: add shades for these colors like primary/secondary */
41+
--warning: #f5a524; /* TODO: add shades for these colors like primary/secondary */
42+
--danger: #e5484d; /* TODO: add shades for these colors like primary/secondary */
43+
--info: #3aa1ff; /* TODO: add shades for these colors like primary/secondary */
4844

4945
/* neutrals */
5046
--text-light: #0f172a;
@@ -56,8 +52,8 @@
5652
--muted-light: #6b7280;
5753
--muted-dark: #9ca3af;
5854

59-
--border-light: #e5e7eb;
60-
--border-dark: #1f2937;
55+
--border-light: #d1d5db; /* ex color: #e5e7eb */
56+
--border-dark: #374151; /* ex color: #1f2937 */
6157
}
6258

6359
/* ===== Light ===== */
@@ -73,13 +69,17 @@
7369
--secondary-hover: var(--secondary-700);
7470
--secondary-active: var(--secondary-800);
7571

76-
--error-bg: color-mix(in srgb, var(--error) 12%, white);
72+
/* TODO: add hover and active colors for danger, success, warning, info (same for dark theme) */
73+
74+
--danger-bg: color-mix(in srgb, var(--danger) 12%, white);
7775
--success-bg: color-mix(in srgb, var(--success) 12%, white);
7876
--warning-bg: color-mix(in srgb, var(--warning) 12%, white);
77+
--info-bg: color-mix(in srgb, var(--info) 12%, white);
7978
}
8079

80+
/* TODO: support in the future */
8181
/* ===== Dark ===== */
82-
@media (prefers-color-scheme: dark) {
82+
/* @media (prefers-color-scheme: dark) {
8383
:root {
8484
--text-color: var(--text-dark);
8585
--surface: var(--surface-dark);
@@ -92,8 +92,9 @@
9292
--secondary-hover: var(--secondary-400);
9393
--secondary-active: var(--secondary-300);
9494
95-
--error-bg: color-mix(in srgb, var(--error) 20%, black);
95+
--danger-bg: color-mix(in srgb, var(--danger) 20%, black);
9696
--success-bg: color-mix(in srgb, var(--success) 20%, black);
9797
--warning-bg: color-mix(in srgb, var(--warning) 20%, black);
98+
--info-bg: color-mix(in srgb, var(--info) 20%, black);
9899
}
99-
}
100+
} */

src/assets/css/index.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@import "tailwindcss";
22

33
@import "./lib/github-markdown.css";
4+
@import "./base.css";
45

56
@config "../../../tailwind.config.js";
67

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { FC, useMemo } from 'react';
2+
import { ButtonRoundedType, ButtonSizeType, ButtonVariantType, IButtonProps } from './types';
3+
4+
const Button: FC<IButtonProps> = ({
5+
children,
6+
size = 'md',
7+
variant = 'primary',
8+
rounded = 'default',
9+
icon,
10+
iconPosition = 'left',
11+
isLoading,
12+
buttonContainerClassName = '',
13+
buttonContentClassName = '',
14+
className = '',
15+
disabled,
16+
...rest
17+
}) => {
18+
const buttonSize = useMemo(() => {
19+
const sizeMapping: Record<ButtonSizeType, string> = {
20+
xs: 'px-2 py-0.5 text-sm',
21+
sm: 'px-3 py-1 text-base',
22+
md: 'px-4 py-1.5 text-lg',
23+
lg: 'px-5 py-2 text-xl',
24+
xl: 'px-6 py-3 text-2xl',
25+
};
26+
27+
return sizeMapping[size] || sizeMapping.md;
28+
}, [size]);
29+
30+
const buttonColor = useMemo(() => {
31+
const variantMapping: Record<ButtonVariantType, string> = {
32+
primary: 'bg-primary hover:bg-primary-hover active:bg-primary-active text-white',
33+
secondary: 'bg-secondary hover:bg-secondary-hover active:bg-secondary-active text-white',
34+
ghost: 'bg-transparent hover:bg-ghost-button-hover-gray-bg active:bg-ghost-button-active-gray-bg text-text-color', // add hover and active colors to base.css and tailwind.config.js
35+
danger: 'bg-danger hover:bg-danger-hover active:bg-danger-active text-white', // TODO: add hover and active colors to base.css and tailwind.config.js
36+
success: 'bg-success hover:bg-success-hover active:bg-success-active text-white', // TODO: add hover and active colors to base.css and tailwind.config.js
37+
warning: 'bg-warning hover:bg-warning-hover active:bg-warning-active text-white', // TODO: add hover and active colors to base.css and tailwind.config.js
38+
info: 'bg-info hover:bg-info-hover active:bg-info-active text-white', // TODO: add hover and active colors to base.css and tailwind.config.js
39+
};
40+
41+
return variantMapping[variant] || variantMapping.primary;
42+
}, [variant]);
43+
44+
const buttonRounded = useMemo(() => {
45+
const roundedMapping: Record<ButtonRoundedType, string> = {
46+
default: 'rounded-md',
47+
rounded: 'rounded-lg',
48+
circle: 'rounded-full',
49+
};
50+
51+
return roundedMapping[rounded] || roundedMapping.default;
52+
}, [rounded]);
53+
54+
return (
55+
<button
56+
className={`outline-none hover:cursor-pointer transition ${buttonSize} ${buttonColor} ${buttonRounded} ${disabled || isLoading ? '!cursor-not-allowed opacity-50' : ''} ${className}`}
57+
disabled={disabled || isLoading}
58+
{...rest}
59+
>
60+
<div className={`flex items-center justify-center ${buttonContainerClassName}`}>
61+
{icon ? (
62+
<>
63+
{iconPosition === 'left' && icon}
64+
<div className={`${iconPosition === 'right' ? 'mr-2' : 'ml-2'} ${buttonContentClassName}`}>
65+
{children}
66+
</div>
67+
{iconPosition === 'right' && icon}
68+
</>
69+
) : (
70+
children
71+
)}
72+
</div>
73+
</button>
74+
);
75+
};
76+
77+
export default Button;

src/components/UI/Button/types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { PropsWithChildren, ButtonHTMLAttributes, ReactNode } from 'react';
2+
3+
export type ButtonSizeType = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
4+
export type ButtonVariantType = 'primary' | 'secondary' | 'danger' | 'success' | 'warning' | 'info' | 'ghost';
5+
export type ButtonRoundedType = 'default' | 'rounded' | 'circle';
6+
export type ButtonIconPositionType = 'left' | 'right';
7+
8+
export interface IButtonProps extends PropsWithChildren, ButtonHTMLAttributes<HTMLButtonElement> {
9+
size?: ButtonSizeType;
10+
variant?: ButtonVariantType;
11+
rounded?: ButtonRoundedType;
12+
icon?: ReactNode;
13+
iconPosition?: ButtonIconPositionType;
14+
isLoading?: boolean;
15+
buttonContainerClassName?: string;
16+
buttonContentClassName?: string;
17+
}

src/components/UI/Input/Input.tsx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { FC, useMemo } from 'react';
2+
import { IInputProps, InputRoundedType, InputSizeType } from './types';
3+
4+
const Input: FC<IInputProps> = ({
5+
label,
6+
wrapperClassName = '',
7+
labelClassName = '',
8+
size = 'sm',
9+
rounded = 'default',
10+
icon,
11+
iconPosition = 'right',
12+
error,
13+
isDirty,
14+
className = '',
15+
...props
16+
}) => {
17+
const inputSize = useMemo(() => {
18+
if (icon) {
19+
const iconSizeMapping: Record<InputSizeType, string> = {
20+
xs: `${iconPosition === 'left' ? 'pl-8 pr-2' : 'pl-2 pr-8'} py-0.5 text-xs`,
21+
sm: `${iconPosition === 'left' ? 'pl-8 pr-3' : 'pl-3 pr-8'} py-1 text-sm`,
22+
md: `${iconPosition === 'left' ? 'pl-8 pr-4' : 'pl-4 pr-8'} py-1.5 text-base`,
23+
lg: `${iconPosition === 'left' ? 'pl-8 pr-5' : 'pl-5 pr-8'} py-2 text-lg`,
24+
xl: `${iconPosition === 'left' ? 'pl-8 pr-6' : 'pl-6 pr-8'} py-3 text-xl`,
25+
};
26+
27+
return iconSizeMapping[size] || iconSizeMapping.md;
28+
}
29+
30+
const sizeMapping: Record<InputSizeType, string> = {
31+
xs: 'px-2 py-0.5 text-xs', // not stable, need to adjust padding to accommodate icon
32+
sm: 'px-3 py-1 text-sm',
33+
md: 'px-4 py-1.5 text-base',
34+
lg: 'px-5 py-2 text-lg',
35+
xl: 'px-6 py-3 text-xl', // not stable, need to adjust padding to accommodate icon
36+
};
37+
38+
return sizeMapping[size] || sizeMapping.md;
39+
}, [size]);
40+
41+
const inputRounded = useMemo(() => {
42+
const roundedMapping: Record<InputRoundedType, string> = {
43+
default: 'rounded-md',
44+
rounded: 'rounded-lg',
45+
};
46+
47+
return roundedMapping[rounded] || roundedMapping.default;
48+
}, [rounded]);
49+
50+
const inputRingAndBorderColor = useMemo(() => {
51+
if (error) {
52+
return 'focus:ring-danger/20 border-danger hover:ring-danger/20';
53+
}
54+
55+
// if (isDirty) {
56+
// return 'focus:ring-success/20 focus:ring-success focus:border-success hover:ring-success/20';
57+
// }
58+
59+
return 'focus:ring-primary/20 focus:border-primary hover:ring-primary/20';
60+
}, [error]);
61+
62+
return (
63+
<div className={`w-full flex items-center relative ${wrapperClassName}`}>
64+
{/* TODO: Support label */}
65+
<input
66+
className={`outline-none border border-border-color hover:ring-3 focus:ring-3 transition w-full ${inputRingAndBorderColor} ${inputSize} ${inputRounded} ${className}`}
67+
{...props}
68+
/>
69+
{icon && (
70+
<div className={`absolute top-1/2 transform -translate-y-1/2 ${iconPosition === 'left' ? 'left-2' : 'right-2'}`}>
71+
{icon}
72+
</div>
73+
)}
74+
{error && (
75+
<p className="text-danger text-sm absolute top-full mt-1">{error}</p>
76+
)}
77+
</div>
78+
);
79+
};
80+
81+
export default Input;

src/components/UI/Input/types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { InputHTMLAttributes, ReactNode } from 'react';
2+
3+
export type InputSizeType = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
4+
export type InputRoundedType = 'default' | 'rounded';
5+
export type InputIconPositionType = 'left' | 'right';
6+
7+
export interface IInputProps extends InputHTMLAttributes<HTMLInputElement> {
8+
wrapperClassName?: string;
9+
labelClassName?: string;
10+
size?: InputSizeType;
11+
rounded?: InputRoundedType;
12+
icon?: ReactNode;
13+
iconPosition?: InputIconPositionType;
14+
label?: string;
15+
error?: string;
16+
isDirty?: boolean;
17+
}

tailwind.config.js

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,61 @@ module.exports = {
44
],
55
theme: {
66
extend: {
7-
// typography: {
8-
// DEFAULT: {
9-
// css: {
10-
// p: {
11-
// margin: '0.25rem 0', // my-1
12-
// },
13-
// li: {
14-
// margin: '0.25rem 0', // my-1
15-
// },
16-
// h1: { margin: '1rem 0 0.5rem 0' },
17-
// h2: { margin: '0.75rem 0 0.5rem 0' },
18-
// h3: { margin: '0.5rem 0 0.25rem 0' },
19-
// }
20-
// }
21-
// }
7+
colors: {
8+
primary: {
9+
DEFAULT: 'var(--primary)',
10+
hover: 'var(--primary-hover)',
11+
active: 'var(--primary-active)',
12+
50: 'var(--primary-50)',
13+
100: 'var(--primary-100)',
14+
200: 'var(--primary-200)',
15+
300: 'var(--primary-300)',
16+
400: 'var(--primary-400)',
17+
500: 'var(--primary-500)',
18+
600: 'var(--primary-600)',
19+
700: 'var(--primary-700)',
20+
800: 'var(--primary-800)',
21+
900: 'var(--primary-900)',
22+
},
23+
secondary: {
24+
DEFAULT: 'var(--secondary)',
25+
hover: 'var(--secondary-hover)',
26+
active: 'var(--secondary-active)',
27+
50: 'var(--secondary-50)',
28+
100: 'var(--secondary-100)',
29+
200: 'var(--secondary-200)',
30+
300: 'var(--secondary-300)',
31+
400: 'var(--secondary-400)',
32+
500: 'var(--secondary-500)',
33+
600: 'var(--secondary-600)',
34+
700: 'var(--secondary-700)',
35+
800: 'var(--secondary-800)',
36+
900: 'var(--secondary-900)',
37+
},
38+
'ghost-button': {
39+
'hover-primary-bg': 'var(--ghost-button-hover-primary-bg)',
40+
'active-primary-bg': 'var(--ghost-button-active-primary-bg)',
41+
'hover-secondary-bg': 'var(--ghost-button-hover-secondary-bg)',
42+
'active-secondary-bg': 'var(--ghost-button-active-secondary-bg)',
43+
'hover-gray-bg': 'var(--ghost-button-hover-gray-bg)',
44+
'active-gray-bg': 'var(--ghost-button-active-gray-bg)',
45+
},
46+
47+
'text-color': 'var(--text-color)',
48+
surface: 'var(--surface)',
49+
'muted-text': 'var(--muted-text)',
50+
'border-color': 'var(--border-color)',
51+
52+
'danger-bg': 'var(--danger-bg)',
53+
'success-bg': 'var(--success-bg)',
54+
'warning-bg': 'var(--warning-bg)',
55+
'info-bg': 'var(--info-bg)',
56+
57+
success: 'var(--success)',
58+
danger: 'var(--danger)',
59+
warning: 'var(--warning)',
60+
info: 'var(--info)',
61+
},
2262
},
2363
},
2464
plugins: [

0 commit comments

Comments
 (0)