Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"docs:build": "docusaurus build",
"test:type": "tsc --skipLibCheck",
"test": "vitest run",
"test:watch": "vitest watch",
"release:package": "pnpm run build && changeset publish"
},
"peerDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
* @packageDocumentation Monolith에서 구성하는 공통 컴포넌트들이 구성되어있는 영역 입니다.
*/

export { Stack } from './stack/Stack';
export type { StackProps } from './stack/Stack';
export { SwitchCase } from './switch-case/SwitchCase';
38 changes: 38 additions & 0 deletions src/components/stack/Stack.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.flex {
display: flex;
}

/* flex-direction */
.flex-column { flex-direction: column; }
.flex-column-reverse { flex-direction: column-reverse; }
.flex-row { flex-direction: row; }
.flex-row-reverse { flex-direction: row-reverse; }

/* flex-wrap */
.flex-nowrap { flex-wrap: nowrap; }
.flex-wrap { flex-wrap: wrap; }
.flex-wrap-reverse { flex-wrap: wrap-reverse; }

/* align-items */
.align-center { align-items: center; }
.align-start { align-items: start; }
.align-end { align-items: end; }
.align-flex-start { align-items: flex-start; }
.align-flex-end { align-items: flex-end; }
.align-self-start { align-items: self-start; }
.align-self-end { align-items: self-end; }
.align-baseline { align-items: baseline; }
.align-stretch { align-items: stretch; }

/* justify-content */
.justify-center { justify-content: center; }
.justify-start { justify-content: start; }
.justify-end { justify-content: end; }
.justify-flex-start { justify-content: flex-start; }
.justify-flex-end { justify-content: flex-end; }
.justify-left { justify-content: left; }
.justify-right { justify-content: right; }
.justify-space-between { justify-content: space-between; }
.justify-space-around { justify-content: space-around; }
.justify-space-evenly { justify-content: space-evenly; }
.justify-stretch { justify-content: stretch; }
133 changes: 133 additions & 0 deletions src/components/stack/Stack.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { render } from '@testing-library/react';
import { Stack } from './Stack';
import '@testing-library/jest-dom';

describe('Stack', () => {
it('자식 컴포넌트들을 렌더링합니다', () => {
const { getByText } = render(
<Stack>
<div>1번</div>
<div>2번</div>
<div>3번</div>
</Stack>,
);
expect(getByText('1번')).toBeInTheDocument();
expect(getByText('2번')).toBeInTheDocument();
expect(getByText('3번')).toBeInTheDocument();
});

it('direction prop이 정상적으로 적용됩니다', () => {
const { container } = render(
<Stack direction='column'>
<div>1번</div>
</Stack>,
);
expect(container.firstChild).toHaveClass('flex flex-column');
});

it('wrap prop이 정상적으로 적용됩니다', () => {
const { container } = render(
<Stack wrap='wrap'>
<div>1번</div>
</Stack>,
);
expect(container.firstChild).toHaveClass('flex flex-wrap');
});

it('align prop이 정상적으로 적용됩니다', () => {
const { container } = render(
<Stack align='center'>
<div>1번</div>
</Stack>,
);
expect(container.firstChild).toHaveClass('flex align-center');
});

it('justify prop이 정상적으로 적용됩니다', () => {
const { container } = render(
<Stack justify='center'>
<div>1번</div>
</Stack>,
);
expect(container.firstChild).toHaveClass('flex justify-center');
});

it('gap prop이 정상적으로 적용됩니다', () => {
const { container } = render(
<Stack gap={10}>
<div>1번</div>
</Stack>,
);
expect(container.firstChild).toHaveStyle({ gap: '10px' });
});

it('width prop이 정상적으로 적용됩니다', () => {
const { container } = render(
<Stack width={100}>
<div>1번</div>
</Stack>,
);
expect(container.firstChild).toHaveStyle({ width: '100px' });
});

it('height prop이 정상적으로 적용됩니다', () => {
const { container } = render(
<Stack height={100}>
<div>1번</div>
</Stack>,
);
expect(container.firstChild).toHaveStyle({ height: '100px' });
});

it('margin prop이 정상적으로 적용됩니다', () => {
const { container } = render(
<Stack m={24}>
<div>1번</div>
</Stack>,
);
expect(container.firstChild).toHaveStyle({ margin: '24px' });
});

it('padding prop이 정상적으로 적용됩니다', () => {
const { container } = render(
<Stack p={16}>
<div>1번</div>
</Stack>,
);
expect(container.firstChild).toHaveStyle({ padding: '16px' });
});

it('모든 props가 정상적으로 적용됩니다', () => {
const { container } = render(
<Stack
direction='column'
wrap='nowrap'
align='center'
justify='center'
gap={10}
width={100}
height={100}
m={24}
p={16}
mt={16}
ml={24}
mr={24}
mb={16}
pt={16}
pl={24}
pr={24}
pb={16}
>
<div>1번</div>
</Stack>,
);
expect(container.firstChild).toHaveClass('flex flex-column flex-nowrap align-center justify-center');
expect(container.firstChild).toHaveStyle({
gap: '10px',
width: '100px',
height: '100px',
margin: '16px 24px 16px 24px',
padding: '16px 24px 16px 24px',
});
});
});
116 changes: 116 additions & 0 deletions src/components/stack/Stack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { PropsWithChildren } from 'react';
import './Stack.css';
import { buildFlexClassNames } from './utils/buildFlexClassNames';

/** The props type of {@link Stack | 'Stack'}. */
export interface StackProps extends PropsWithChildren {
/** 컴포넌트에 적용할 CSS 클래스명입니다. */
className?: string;
/** flex-direction 속성을 지정합니다. */
direction?: 'row' | 'row-reverse' | 'column' | 'column-reverse';
/** flex-wrap 속성을 지정합니다. */
wrap?: 'wrap' | 'nowrap' | 'wrap-reverse';
/** align-items 속성을 지정합니다. */
align?: 'center' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'baseline' | 'stretch';
/** justify-content 속성을 지정합니다. */
justify?:
| 'center'
| 'start'
| 'end'
| 'flex-start'
| 'flex-end'
| 'left'
| 'right'
| 'space-between'
| 'space-around'
| 'space-evenly'
| 'stretch';
/** gap 속성을 지정합니다. */
gap?: React.CSSProperties['gap'];
/** width 속성을 지정합니다. */
width?: number | string;
/** height 속성을 지정합니다. */
height?: number | string;
/** margin 속성을 지정합니다. */
m?: number | string;
/** margin-top 속성을 지정합니다. */
mt?: number | string;
/** margin-left 속성을 지정합니다. */
ml?: number | string;
/** margin-right 속성을 지정합니다. */
mr?: number | string;
/** margin-bottom 속성을 지정합니다. */
mb?: number | string;
/** padding 속성을 지정합니다. */
p?: number | string;
/** padding-top 속성을 지정합니다. */
pt?: number | string;
/** padding-left 속성을 지정합니다. */
pl?: number | string;
/** padding-right 속성을 지정합니다. */
pr?: number | string;
/** padding-bottom 속성을 지정합니다. */
pb?: number | string;
}

/**
* 자식 컴포넌트를 스택으로 렌더링하는 컴포넌트입니다.
*
* @category Component
* @param props - {@link StackProps}를 참조하세요.
* @returns 해당하는 컴포넌트를 반환합니다.
*
* @example
* ```tsx
* <Stack>
* <div>Hello</div>
* <div>World</div>
* </Stack>
* ```
*/
export const Stack = ({
children,
className,
direction,
wrap,
align,
justify,
gap,
width,
height,
m,
mt,
ml,
mr,
mb,
p,
pt,
pl,
pr,
pb,
}: StackProps) => {
const flexClasses = buildFlexClassNames({ direction, wrap, align, justify });
const combinedClasses = [flexClasses, className].filter(Boolean).join(' ');

const spacing_style: React.CSSProperties = {
...(gap !== undefined && { gap }),
...(width !== undefined && { width }),
...(height !== undefined && { height }),
...(m !== undefined && { margin: m }),
...(mt !== undefined && { marginTop: mt }),
...(ml !== undefined && { marginLeft: ml }),
...(mr !== undefined && { marginRight: mr }),
...(mb !== undefined && { marginBottom: mb }),
...(p !== undefined && { padding: p }),
...(pt !== undefined && { paddingTop: pt }),
...(pl !== undefined && { paddingLeft: pl }),
...(pr !== undefined && { paddingRight: pr }),
...(pb !== undefined && { paddingBottom: pb }),
};

return (
<div className={combinedClasses} style={spacing_style}>
{children}
</div>
);
};
33 changes: 33 additions & 0 deletions src/components/stack/utils/buildFlexClassNames.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { buildFlexClassNames } from './buildFlexClassNames';

describe('buildFlexClassNames', () => {
it('파라미터가 없는 경우 flex만 반환합니다.', () => {
const classNames = buildFlexClassNames({});
expect(classNames).toEqual('flex');
});

it('flex-direction 파라미터가 있는 경우 flex-direction 클래스를 반환합니다.', () => {
const classNames = buildFlexClassNames({ direction: 'row' });
expect(classNames).toEqual('flex flex-row');
});

it('flex-wrap 파라미터가 있는 경우 flex-wrap 클래스를 반환합니다.', () => {
const classNames = buildFlexClassNames({ wrap: 'wrap' });
expect(classNames).toEqual('flex flex-wrap');
});

it('align-items 파라미터가 있는 경우 align-items 클래스를 반환합니다.', () => {
const classNames = buildFlexClassNames({ align: 'center' });
expect(classNames).toEqual('flex align-center');
});

it('justify-content 파라미터가 있는 경우 justify-content 클래스를 반환합니다.', () => {
const classNames = buildFlexClassNames({ justify: 'center' });
expect(classNames).toEqual('flex justify-center');
});

it('모든 파라미터가 있는 경우 모든 클래스를 반환합니다.', () => {
const classNames = buildFlexClassNames({ direction: 'row', wrap: 'wrap', align: 'center', justify: 'center' });
expect(classNames).toEqual('flex flex-row flex-wrap align-center justify-center');
});
});
41 changes: 41 additions & 0 deletions src/components/stack/utils/buildFlexClassNames.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/** The props type of {@link buildFlexClassNames | 'buildFlexClassNames'}. */
export interface FlexClassNamesParams {
/** flex-direction 속성을 지정합니다. */
direction?: React.CSSProperties['flexDirection'];
/** flex-wrap 속성을 지정합니다. */
wrap?: React.CSSProperties['flexWrap'];
/** align-items 속성을 지정합니다. */
align?: React.CSSProperties['alignItems'];
/** justify-content 속성을 지정합니다. */
justify?: React.CSSProperties['justifyContent'];
}

/**
* 컴포넌트에 적용할 Flexbox 관련 CSS 클래스명을 반환합니다.
*
* @param props - {@link FlexClassNamesParams}를 참조하세요.
* @returns 컴포넌트에 적용할 Flexbox 관련 CSS 클래스명을 반환합니다.
*/
export const buildFlexClassNames = ({
direction,
wrap,
align,
justify,
}: FlexClassNamesParams): string => {
const classes: string[] = ['flex'];

if (direction) {
classes.push(`flex-${direction}`);
}
if (wrap) {
classes.push(`flex-${wrap}`);
}
if (align) {
classes.push(`align-${align}`);
}
if (justify) {
classes.push(`justify-${justify}`);
}

return classes.join(' ');
};
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Components
export { SwitchCase } from './components/switch-case/SwitchCase';
export { Stack } from './components/stack/Stack';

// Hooks
export { useMount } from './hooks/use-mount/useMount';
Expand Down