Skip to content

Commit

Permalink
Merge pull request #53 from depromeet/chore/button
Browse files Browse the repository at this point in the history
chore: ๊ณต์šฉ button ์ปดํฌ๋„ŒํŠธ ์ž‘์„ฑ ๋ฐ ์Šคํ† ๋ฆฌ๋ถ ์„ค์ •์„ ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค.
  • Loading branch information
summermong authored Jul 28, 2024
2 parents acaad6b + 7428958 commit e66e2c5
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/deploy-storybook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ jobs:
if: steps.cache.outputs.cache-hit != 'true'
run: pnpm install

- name: Generate CSS
run: pnpm panda cssgen

- name: Publish to chromatic
id: chromatic
uses: chromaui/action@v1
Expand Down
11 changes: 11 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { StorybookConfig } from '@storybook/nextjs';
import path from 'path';

const config: StorybookConfig = {
stories: ['../**/*.mdx', '../**/*.stories.@(js|jsx|mjs|ts|tsx)'],
Expand All @@ -13,5 +14,15 @@ const config: StorybookConfig = {
name: '@storybook/nextjs',
options: {},
},
webpackFinal: async (config) => {
if (config.resolve) {
config.resolve.alias = {
...config.resolve.alias,
'@': path.resolve(__dirname, '..'),
};
}
return config;
},
};

export default config;
1 change: 1 addition & 0 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Preview } from '@storybook/react';
import '../styled-system/styles.css';

const preview: Preview = {
parameters: {
Expand Down
62 changes: 58 additions & 4 deletions components/atoms/button/button.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,78 @@
import type { Meta, StoryObj } from '@storybook/react';

import { css } from '@/styled-system/css';

import { Button } from './button';

const meta: Meta<typeof Button> = {
title: 'components/atoms/button',
title: 'Example/Button',
component: Button,
tags: ['autodocs'],
decorators: [
(Story) => (
<div className={css({ margin: 10 })}>
<Story />
</div>
),
],
argTypes: {
size: {
control: { type: 'select', options: ['small', 'medium', 'large'] },
},
disabled: {
control: 'boolean',
},
onClick: { action: 'clicked' },
variant: {
control: { type: 'select', options: ['solid', 'outlined', 'text'] },
},
type: {
control: {
type: 'select',
options: ['primary', 'secondary', 'assistive'],
},
},
interaction: {
control: {
type: 'select',
options: ['normal', 'hovered', 'focused', 'pressed'],
},
},
label: {
control: 'text',
},
leftIconSrc: {
control: 'text',
},
rightIconSrc: {
control: 'text',
},
},
};

export default meta;

type Story = StoryObj<typeof Button>;

export const Basic: Story = {
export const Default: Story = {
args: {
label: 'Label',
size: 'large',
variant: 'solid',
type: 'primary',
interaction: 'normal',
},
};

export const outlined: Story = {
args: {
...Default.args,
variant: 'outlined',
},
};

export const text: Story = {
args: {
children: 'Button',
...Default.args,
variant: 'text',
},
};
187 changes: 181 additions & 6 deletions components/atoms/button/button.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,184 @@
import { ButtonHTMLAttributes, forwardRef } from 'react';
import Image from 'next/image';

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {}
import { css, cx } from '@/styled-system/css';
import { flex } from '@/styled-system/patterns';

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ ...props }, ref) => <button ref={ref} {...props} />,
);
import { ButtonProps } from './type';

Button.displayName = 'Button';
export const Button = ({
size = 'medium',
disabled = false,
leftIconSrc,
rightIconSrc,
label,
variant = 'solid',
type = 'primary',
interaction = 'normal',
}: ButtonProps) => {
const baseStyles = flex({
alignItems: 'center',
justifyContent: leftIconSrc && rightIconSrc ? 'space-between' : 'center',
position: 'relative',
cursor: disabled ? 'not-allowed' : 'pointer',
});

const sizeStylesMap = new Map([
[
'large',
css({
height: '48px',
padding: '12px 28px',
borderRadius: '10px',
textStyle: 'body1.normal',
gap: '6px',
}),
],
[
'medium',
css({
height: '40px',
padding: '9px 20px',
borderRadius: '8px',
textStyle: 'body2.normal',
gap: '5px',
}),
],
[
'small',
css({
height: '32px',
padding: '7px 14px',
borderRadius: '6px',
textStyle: 'label2',
gap: '4px',
}),
],
]);

const variantStylesMap = new Map([
[
'solid',
css({
backgroundColor: disabled ? 'fill.disable' : 'blue.60',
border: 'none',
}),
],
[
'outlined',
css({
backgroundColor: 'white',
border: '1px solid',
borderColor: disabled
? 'line.normal'
: type === 'primary'
? '#3B87F4'
: '#70737C38',
}),
],
[
'text',
css({ backgroundColor: 'white', border: 'none', padding: '4px 0px' }),
],
]);

const typeStylesMap = new Map([
[
'primary',
css({
color: disabled
? 'text.placeHolder'
: variant === 'solid'
? 'white'
: 'blue.60',
}),
],
[
'secondary',
css({
color: disabled
? 'text.placeHolder'
: variant === 'solid'
? 'white'
: '#3B87F4',
}),
],
[
'assistive',
css({
color: disabled
? 'text.placeHolder'
: variant === 'solid'
? 'white'
: variant === 'outlined'
? 'text.normal'
: 'text.alternative',
}),
],
]);

const interactionStylesMap = new Map([
['hovered', css({ '&:hover::after': { opacity: 0.075 } })],
['focused', css({ '&:focus::after': { opacity: 0.12 } })],
['pressed', css({ '&:active::after': { opacity: 0.18 } })],
]);

const buttonStyles = cx(
baseStyles,
sizeStylesMap.get(size),
variantStylesMap.get(variant),
typeStylesMap.get(type),
interactionStylesMap.get(interaction),
css({
fontWeight: '600',
'&::after': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
borderRadius: 'inherit',
backgroundColor:
type === 'primary' && (variant === 'outlined' || variant === 'text')
? 'blue.60'
: 'text.normal',
opacity: 0,
},
}),
);

const iconSize = size === 'large' ? 20 : size === 'medium' ? 18 : 16;

const iconWrapperStyles = flex({
width: `${iconSize}px`,
height: `${iconSize}px`,
alignItems: 'center',
justifyContent: 'center',
});

return (
<button className={buttonStyles}>
{leftIconSrc && (
<div className={iconWrapperStyles}>
<Image
src={leftIconSrc}
alt="left icon"
width={iconSize}
height={iconSize}
/>
</div>
)}
{label}
{rightIconSrc && (
<div className={iconWrapperStyles}>
<Image
src={rightIconSrc}
alt="right icon"
width={iconSize}
height={iconSize}
/>
</div>
)}
</button>
);
};
11 changes: 11 additions & 0 deletions components/atoms/button/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface ButtonProps {
size: 'large' | 'medium' | 'small';
disabled?: boolean;
label: string;
interaction: 'normal' | 'hovered' | 'focused' | 'pressed';
variant?: 'solid' | 'outlined' | 'text';
type?: 'primary' | 'secondary' | 'assistive';
leftIconSrc?: string;
rightIconSrc?: string;
onClick?: () => void;
}
1 change: 1 addition & 0 deletions panda.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default defineConfig({
'./app/**/*.{js,jsx,ts,tsx}',
'./components/**/*.{js,jsx,ts,tsx}',
'./features/**/*.{js,jsx,ts,tsx}',
'./stories/**/*.{js,jsx,ts,tsx}',
],

// Files to exclude
Expand Down

0 comments on commit e66e2c5

Please sign in to comment.