From a3cd230fc7ecd7fa5269bf21320064a0879c9c82 Mon Sep 17 00:00:00 2001 From: synuns Date: Tue, 7 Jan 2025 00:46:22 +0900 Subject: [PATCH 1/7] feat(flex): add Flex component - Introduced a new Flex component for the Sipe Design System - Created accompanying Storybook stories for visual testing and documentation - Added unit tests using Vitest to ensure component functionality --- packages/flex/.storybook/main.ts | 15 ++ packages/flex/.storybook/preview.ts | 5 + packages/flex/package.json | 67 ++++++++ packages/flex/src/Flex.stories.tsx | 134 ++++++++++++++++ packages/flex/src/Flex.test.tsx | 237 ++++++++++++++++++++++++++++ packages/flex/src/Flex.tsx | 57 +++++++ packages/flex/src/index.ts | 1 + packages/flex/tsconfig.json | 3 + packages/flex/tsup.config.ts | 8 + packages/flex/vitest.config.ts | 11 ++ packages/flex/vitest.setup.ts | 1 + pnpm-lock.yaml | 70 +++++++- 12 files changed, 603 insertions(+), 6 deletions(-) create mode 100644 packages/flex/.storybook/main.ts create mode 100644 packages/flex/.storybook/preview.ts create mode 100644 packages/flex/package.json create mode 100644 packages/flex/src/Flex.stories.tsx create mode 100644 packages/flex/src/Flex.test.tsx create mode 100644 packages/flex/src/Flex.tsx create mode 100644 packages/flex/src/index.ts create mode 100644 packages/flex/tsconfig.json create mode 100644 packages/flex/tsup.config.ts create mode 100644 packages/flex/vitest.config.ts create mode 100644 packages/flex/vitest.setup.ts diff --git a/packages/flex/.storybook/main.ts b/packages/flex/.storybook/main.ts new file mode 100644 index 0000000..2f0a252 --- /dev/null +++ b/packages/flex/.storybook/main.ts @@ -0,0 +1,15 @@ +import type { StorybookConfig } from '@storybook/react-vite'; + +export default { + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + addons: [ + '@storybook/addon-onboarding', + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + ], + framework: { + name: '@storybook/react-vite', + options: {}, + }, +} satisfies StorybookConfig; diff --git a/packages/flex/.storybook/preview.ts b/packages/flex/.storybook/preview.ts new file mode 100644 index 0000000..82ec7ed --- /dev/null +++ b/packages/flex/.storybook/preview.ts @@ -0,0 +1,5 @@ +import type { Preview } from '@storybook/react'; + +export default { + tags: ['autodocs'], +} satisfies Preview; diff --git a/packages/flex/package.json b/packages/flex/package.json new file mode 100644 index 0000000..afa6132 --- /dev/null +++ b/packages/flex/package.json @@ -0,0 +1,67 @@ +{ + "name": "@sipe-team/flex", + "description": "Flex for Sipe Design System", + "version": "0.0.0", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/sipe-team/3-1_sds" + }, + "type": "module", + "exports": "./src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsup", + "build:storybook": "storybook build", + "dev:storybook": "storybook dev -p 6006", + "lint:biome": "pnpm exec biome lint", + "lint:eslint": "pnpm exec eslint --flag unstable_ts_config", + "test": "vitest", + "typecheck": "tsc", + "prepack": "pnpm run build" + }, + "dependencies": { + "@sipe-team/card": "workspace:^" + }, + "devDependencies": { + "@faker-js/faker": "^9.2.0", + "@storybook/addon-essentials": "catalog:", + "@storybook/addon-interactions": "catalog:", + "@storybook/addon-links": "catalog:", + "@storybook/blocks": "catalog:", + "@storybook/react": "catalog:", + "@storybook/react-vite": "catalog:", + "@storybook/test": "catalog:", + "@testing-library/jest-dom": "catalog:", + "@testing-library/react": "catalog:", + "@types/react": "^18.3.12", + "happy-dom": "catalog:", + "react": "^18.3.1", + "storybook": "catalog:", + "tsup": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + }, + "peerDependencies": { + "react": ">= 18" + }, + "publishConfig": { + "access": "public", + "registry": "https://npm.pkg.github.com", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + } + }, + "sideEffects": false +} diff --git a/packages/flex/src/Flex.stories.tsx b/packages/flex/src/Flex.stories.tsx new file mode 100644 index 0000000..b3116b0 --- /dev/null +++ b/packages/flex/src/Flex.stories.tsx @@ -0,0 +1,134 @@ +import { Card } from '@sipe-team/card'; +import type { Meta, StoryObj } from '@storybook/react'; +import { Flex } from './Flex'; + +const meta = { + title: 'Flex', + component: Flex, + tags: ['autodocs'], + argTypes: { + direction: { + control: 'select', + options: ['row', 'column', 'row-reverse', 'column-reverse'], + description: 'Flex direction', + }, + align: { + control: 'select', + options: ['flex-start', 'flex-end', 'center', 'stretch', 'baseline'], + description: 'Align items', + }, + justify: { + control: 'select', + options: [ + 'flex-start', + 'flex-end', + 'center', + 'space-between', + 'space-around', + ], + description: 'Justify content', + }, + wrap: { + control: 'select', + options: ['nowrap', 'wrap', 'wrap-reverse'], + description: 'Flex wrap', + }, + gap: { + control: 'text', + description: 'Gap between items', + }, + inline: { + control: 'boolean', + description: 'Display as inline-flex', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = { + args: { + gap: '1rem', + children: [ + , + , + , + ], + }, +}; + +export const Direction: Story = { + args: { + direction: 'column', + gap: '1rem', + css: { width: '100%' }, + children: [ + , + , + , + ], + }, +}; + +export const Align: Story = { + args: { + align: 'center', + gap: '1rem', + css: { width: '100%' }, + children: [ + , + , + , + ], + }, +}; + +export const Justify: Story = { + render: () => ( + + + + flex-start + + + + + center + + + + + flex-end + + + + + space-between + + + + ), +}; + +export const Wrap: Story = { + args: { + wrap: 'wrap', + gap: '1rem', + css: { maxWidth: '400px' }, + children: [ + , + , + , + ], + }, +}; diff --git a/packages/flex/src/Flex.test.tsx b/packages/flex/src/Flex.test.tsx new file mode 100644 index 0000000..102831e --- /dev/null +++ b/packages/flex/src/Flex.test.tsx @@ -0,0 +1,237 @@ +import { faker } from '@faker-js/faker'; +import { render, screen } from '@testing-library/react'; +import type { CSSProperties } from 'react'; +import { describe, expect, it } from 'vitest'; +import { Flex } from './Flex'; + +describe('Flex', () => { + it('flex 컴포넌트는 기본적으로 flex 속성을 가지고 있다.', () => { + render( + +
item 1
+
item 2
+
, + ); + + const flexContainer = screen.getByTestId('flex-container'); + expect(flexContainer).toBeInTheDocument(); + expect(flexContainer).toHaveStyle({ display: 'flex' }); + }); + + it('flex 컴포넌트에 className을 주입하면 추가로 전달한다.', () => { + const customClassName = faker.word.noun(); + render(); + expect(screen.getByTestId('flex-container')).toHaveClass(customClassName); + }); + + describe('flex 속성', () => { + describe('justify', () => { + it.each([ + { justifyContent: 'flex-start' }, + { justifyContent: 'flex-end' }, + { justifyContent: 'center' }, + { justifyContent: 'space-between' }, + { justifyContent: 'space-around' }, + { justifyContent: 'space-evenly' }, + ] satisfies Array<{ justifyContent: CSSProperties['justifyContent'] }>)( + 'flex의 justify prop에 $justifyContent 속성을 주입하면 해당 속성을 적용한다.', + ({ justifyContent }) => { + render( + +
item 1
+
item 2
+
, + ); + + const flexContainer = screen.getByTestId('flex-container'); + expect(flexContainer).toHaveStyle({ justifyContent }); + }, + ); + }); + + describe('align', () => { + it.each([ + { alignItems: 'flex-start' }, + { alignItems: 'flex-end' }, + { alignItems: 'center' }, + { alignItems: 'baseline' }, + { alignItems: 'stretch' }, + ] satisfies Array<{ alignItems: CSSProperties['alignItems'] }>)( + 'flex의 align prop에 $alignItems 속성을 주입하면 해당 속성을 적용한다.', + ({ alignItems }) => { + render( + +
item 1
+
item 2
+
, + ); + + const flexContainer = screen.getByTestId('flex-container'); + expect(flexContainer).toHaveStyle({ alignItems }); + }, + ); + }); + + describe('wrap', () => { + it.each([ + { wrap: 'wrap' }, + { wrap: 'nowrap' }, + { wrap: 'wrap-reverse' }, + ] satisfies Array<{ wrap: CSSProperties['flexWrap'] }>)( + 'flex의 wrap prop에 $wrap 속성을 주입하면 해당 속성을 적용한다.', + ({ wrap }) => { + render( + +
item 1
+
item 2
+
, + ); + + const flexContainer = screen.getByTestId('flex-container'); + expect(flexContainer).toHaveStyle({ flexWrap: wrap }); + }, + ); + }); + + describe('direction', () => { + it.each([ + { direction: 'row' }, + { direction: 'column' }, + { direction: 'row-reverse' }, + { direction: 'column-reverse' }, + { direction: 'column-reverse' }, + ] satisfies Array<{ direction: CSSProperties['flexDirection'] }>)( + 'flex의 direction prop에 $direction 속성을 주입하면 해당 속성을 적용한다.', + ({ direction }) => { + render( + +
item 1
+
item 2
+
, + ); + + const flexContainer = screen.getByTestId('flex-container'); + expect(flexContainer).toHaveStyle({ flexDirection: direction }); + }, + ); + + describe('basis', () => { + it.each([ + { basis: '100px' }, + { basis: '100%' }, + { basis: 'auto' }, + { basis: '10rem' }, + { basis: 'content' }, + ] satisfies Array<{ + basis: CSSProperties['flexBasis']; + }>)( + 'flex의 basis prop에 $basis 속성을 주입하면 해당 속성을 적용한다.', + ({ basis }) => { + render( + +
item 1
+
item 2
+
, + ); + + const flexContainer = screen.getByTestId('flex-container'); + expect(flexContainer).toHaveStyle({ flexBasis: basis }); + }, + ); + }); + + describe('grow', () => { + it.each([{ grow: 0 }, { grow: 1 }, { grow: 2 }] satisfies Array<{ + grow: CSSProperties['flexGrow']; + }>)( + 'flex의 grow prop에 $grow 속성을 주입하면 해당 속성을 적용한다.', + ({ grow }) => { + render( + +
item 1
+
item 2
+
, + ); + + const flexContainer = screen.getByTestId('flex-container'); + expect(flexContainer).toHaveStyle({ flexGrow: grow }); + }, + ); + }); + + describe('shrink', () => { + it.each([{ shrink: 0 }, { shrink: 1 }, { shrink: 2 }] satisfies Array<{ + shrink: CSSProperties['flexShrink']; + }>)( + 'flex의 shrink prop에 $shrink 속성을 주입하면 해당 속성을 적용한다.', + ({ shrink }) => { + render( + +
item 1
+
item 2
+
, + ); + + const flexContainer = screen.getByTestId('flex-container'); + expect(flexContainer).toHaveStyle({ flexShrink: shrink }); + }, + ); + }); + + describe('inline', () => { + it('flex의 inline prop에 true 속성을 주입하면 해당 속성을 적용한다.', () => { + render( + +
item 1
+
item 2
+
, + ); + + const flexContainer = screen.getByTestId('flex-container'); + expect(flexContainer).toHaveStyle({ display: 'inline-flex' }); + }); + }); + + describe('gap', () => { + it.each([{ gap: '10px' }, { gap: '1rem' }] satisfies Array<{ + gap: CSSProperties['gap']; + }>)( + 'flex의 gap prop에 $gap 속성을 주입하면 해당 속성을 적용한다.', + ({ gap }) => { + render( + +
item 1
+
item 2
+
, + ); + }, + ); + }); + }); + }); + + describe('css', () => { + it.each([ + { css: { justifyContent: 'flex-start' } }, + { css: { alignItems: 'center' } }, + { css: { flexWrap: 'wrap' } }, + { css: { flexDirection: 'column' } }, + { css: { flexBasis: '100px' } }, + { css: { flexGrow: 1 } }, + { css: { flexShrink: 1 } }, + ] satisfies Array<{ css: CSSProperties }>)( + 'flex의 css prop에 $css 속성을 주입하면 해당 속성을 적용한다.', + ({ css }) => { + render( + +
item 1
+
item 2
+
, + ); + + const flexContainer = screen.getByTestId('flex-container'); + expect(flexContainer).toHaveStyle(css); + }, + ); + }); +}); diff --git a/packages/flex/src/Flex.tsx b/packages/flex/src/Flex.tsx new file mode 100644 index 0000000..8c6f7d3 --- /dev/null +++ b/packages/flex/src/Flex.tsx @@ -0,0 +1,57 @@ +import { type CSSProperties, type ForwardedRef, forwardRef } from 'react'; + +export interface FlexProps { + align?: CSSProperties['alignItems']; + justify?: CSSProperties['justifyContent']; + wrap?: CSSProperties['flexWrap']; + direction?: CSSProperties['flexDirection']; + basis?: CSSProperties['flexBasis']; + grow?: CSSProperties['flexGrow']; + shrink?: CSSProperties['flexShrink']; + inline?: boolean; + gap?: CSSProperties['gap']; + className?: string; + css?: CSSProperties; + children?: React.ReactNode; +} + +export const Flex = forwardRef(function Flex( + { + align, + justify, + wrap, + direction, + basis, + grow, + shrink, + inline, + gap, + className, + css, + children, + ...rest + }: FlexProps, + ref: ForwardedRef, +) { + return ( +
+ {children} +
+ ); +}); diff --git a/packages/flex/src/index.ts b/packages/flex/src/index.ts new file mode 100644 index 0000000..7cf460b --- /dev/null +++ b/packages/flex/src/index.ts @@ -0,0 +1 @@ +export * from './Flex'; diff --git a/packages/flex/tsconfig.json b/packages/flex/tsconfig.json new file mode 100644 index 0000000..4082f16 --- /dev/null +++ b/packages/flex/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/flex/tsup.config.ts b/packages/flex/tsup.config.ts new file mode 100644 index 0000000..c533199 --- /dev/null +++ b/packages/flex/tsup.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts'], + clean: true, + dts: true, + format: ['esm', 'cjs'], +}); diff --git a/packages/flex/vitest.config.ts b/packages/flex/vitest.config.ts new file mode 100644 index 0000000..a917827 --- /dev/null +++ b/packages/flex/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineProject, mergeConfig } from 'vitest/config'; +import defaultConfig from '../../vitest.config'; + +export default mergeConfig( + defaultConfig, + defineProject({ + test: { + setupFiles: './vitest.setup.ts', + }, + }), +); diff --git a/packages/flex/vitest.setup.ts b/packages/flex/vitest.setup.ts new file mode 100644 index 0000000..7b0828b --- /dev/null +++ b/packages/flex/vitest.setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f10850..8c26770 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -448,6 +448,64 @@ importers: specifier: 'catalog:' version: 2.1.8(@types/node@22.10.1)(happy-dom@15.11.7) + packages/flex: + dependencies: + '@sipe-team/card': + specifier: workspace:^ + version: link:../card + devDependencies: + '@faker-js/faker': + specifier: ^9.2.0 + version: 9.2.0 + '@storybook/addon-essentials': + specifier: 'catalog:' + version: 8.4.6(@types/react@18.3.13)(storybook@8.4.6(prettier@2.8.8)) + '@storybook/addon-interactions': + specifier: 'catalog:' + version: 8.4.6(storybook@8.4.6(prettier@2.8.8)) + '@storybook/addon-links': + specifier: 'catalog:' + version: 8.4.6(react@18.3.1)(storybook@8.4.6(prettier@2.8.8)) + '@storybook/blocks': + specifier: 'catalog:' + version: 8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(prettier@2.8.8)) + '@storybook/react': + specifier: 'catalog:' + version: 8.4.6(@storybook/test@8.4.6(storybook@8.4.6(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(prettier@2.8.8))(typescript@5.7.2) + '@storybook/react-vite': + specifier: 'catalog:' + version: 8.4.6(@storybook/test@8.4.6(storybook@8.4.6(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.28.0)(storybook@8.4.6(prettier@2.8.8))(typescript@5.7.2)(vite@5.4.11(@types/node@22.10.1)) + '@storybook/test': + specifier: 'catalog:' + version: 8.4.6(storybook@8.4.6(prettier@2.8.8)) + '@testing-library/jest-dom': + specifier: 'catalog:' + version: 6.6.3 + '@testing-library/react': + specifier: 'catalog:' + version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.1)(@types/react@18.3.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/react': + specifier: ^18.3.12 + version: 18.3.13 + happy-dom: + specifier: 'catalog:' + version: 15.11.7 + react: + specifier: ^18.3.1 + version: 18.3.1 + storybook: + specifier: 'catalog:' + version: 8.4.6(prettier@2.8.8) + tsup: + specifier: 'catalog:' + version: 8.3.5(jiti@2.4.1)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.6.1) + typescript: + specifier: 'catalog:' + version: 5.7.2 + vitest: + specifier: 'catalog:' + version: 2.1.8(@types/node@22.10.1)(happy-dom@15.11.7) + packages/radio-group: devDependencies: '@storybook/addon-essentials': @@ -4185,7 +4243,7 @@ snapshots: '@babel/traverse': 7.26.3 '@babel/types': 7.26.3 convert-source-map: 2.0.0 - debug: 4.3.7 + debug: 4.4.0 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -4256,7 +4314,7 @@ snapshots: '@babel/parser': 7.26.3 '@babel/template': 7.25.9 '@babel/types': 7.26.3 - debug: 4.3.7 + debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -5877,7 +5935,7 @@ snapshots: esbuild-register@3.6.0(esbuild@0.24.0): dependencies: - debug: 4.3.7 + debug: 4.4.0 esbuild: 0.24.0 transitivePeerDependencies: - supports-color @@ -7235,7 +7293,7 @@ snapshots: cac: 6.7.14 chokidar: 4.0.1 consola: 3.2.3 - debug: 4.3.7 + debug: 4.4.0 esbuild: 0.24.0 joycon: 3.1.1 picocolors: 1.1.1 @@ -7313,7 +7371,7 @@ snapshots: vite-node@2.1.8(@types/node@22.10.1): dependencies: cac: 6.7.14 - debug: 4.3.7 + debug: 4.4.0 es-module-lexer: 1.5.4 pathe: 1.1.2 vite: 5.4.11(@types/node@22.10.1) @@ -7347,7 +7405,7 @@ snapshots: '@vitest/spy': 2.1.8 '@vitest/utils': 2.1.8 chai: 5.1.2 - debug: 4.3.7 + debug: 4.4.0 expect-type: 1.1.0 magic-string: 0.30.14 pathe: 1.1.2 From 0bc01d627a7f3771854cc84e7db296b1ea1b0b71 Mon Sep 17 00:00:00 2001 From: synuns Date: Tue, 7 Jan 2025 00:47:36 +0900 Subject: [PATCH 2/7] feat(flex): integrate Flex component into side package - Exported Flex component in src/index.ts for accessibility within the side package --- packages/side/package.json | 7 +++---- packages/side/src/index.ts | 1 + pnpm-lock.yaml | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/side/package.json b/packages/side/package.json index 4339379..876dbae 100644 --- a/packages/side/package.json +++ b/packages/side/package.json @@ -9,9 +9,7 @@ }, "type": "module", "exports": "./src/index.ts", - "files": [ - "dist" - ], + "files": ["dist"], "scripts": { "build": "tsup", "prepack": "pnpm run build" @@ -26,7 +24,8 @@ "@sipe-team/switch": "workspace:*", "@sipe-team/tokens": "workspace:*", "@sipe-team/tooltip": "workspace:*", - "@sipe-team/typography": "workspace:*" + "@sipe-team/typography": "workspace:*", + "@sipe-team/flex": "workspace:*" }, "devDependencies": { "tsup": "catalog:", diff --git a/packages/side/src/index.ts b/packages/side/src/index.ts index fee0dee..0df0ae7 100644 --- a/packages/side/src/index.ts +++ b/packages/side/src/index.ts @@ -7,3 +7,4 @@ export * from '@sipe-team/skeleton'; export * from '@sipe-team/switch'; export * from '@sipe-team/tooltip'; export * from '@sipe-team/typography'; +export * from '@sipe-team/flex'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8c26770..5305591 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -574,6 +574,9 @@ importers: '@sipe-team/divider': specifier: workspace:* version: link:../divider + '@sipe-team/flex': + specifier: workspace:* + version: link:../flex '@sipe-team/input': specifier: workspace:* version: link:../Input From 39ab4a1ad7f3a0a6c204000fc5c56b70a286ee77 Mon Sep 17 00:00:00 2001 From: synuns Date: Wed, 8 Jan 2025 00:30:51 +0900 Subject: [PATCH 3/7] feat(flex): update Flex component with styling enhancements - Added '@radix-ui/react-slot' and 'clsx' as dependencies in package.json. - Updated Flex component to use 'style' prop instead of 'css' for styling. - Enhanced Flex component stories to include new 'space-evenly' justification option. - Improved unit tests to reflect changes in props and styling. - Refactored Flex component to support polymorphic rendering with 'asChild' prop. --- packages/flex/package.json | 4 +- packages/flex/src/Flex.module.css | 11 ++++ packages/flex/src/Flex.stories.tsx | 82 ++++++++++++++++++++++-------- packages/flex/src/Flex.test.tsx | 45 +++++++++++----- packages/flex/src/Flex.tsx | 45 +++++++++------- packages/flex/src/global.d.ts | 1 + pnpm-lock.yaml | 12 +++-- 7 files changed, 142 insertions(+), 58 deletions(-) create mode 100644 packages/flex/src/Flex.module.css create mode 100644 packages/flex/src/global.d.ts diff --git a/packages/flex/package.json b/packages/flex/package.json index afa6132..0d74162 100644 --- a/packages/flex/package.json +++ b/packages/flex/package.json @@ -23,10 +23,12 @@ "prepack": "pnpm run build" }, "dependencies": { - "@sipe-team/card": "workspace:^" + "@radix-ui/react-slot": "^1.1.0", + "clsx": "^2.1.1" }, "devDependencies": { "@faker-js/faker": "^9.2.0", + "@sipe-team/card": "workspace:^", "@storybook/addon-essentials": "catalog:", "@storybook/addon-interactions": "catalog:", "@storybook/addon-links": "catalog:", diff --git a/packages/flex/src/Flex.module.css b/packages/flex/src/Flex.module.css new file mode 100644 index 0000000..5e4e2ea --- /dev/null +++ b/packages/flex/src/Flex.module.css @@ -0,0 +1,11 @@ +.flex { + display: var(--flex-display, flex); + flex-direction: var(--flex-direction); + align-items: var(--flex-align); + justify-content: var(--flex-justify); + flex-wrap: var(--flex-wrap); + gap: var(--flex-gap); + flex-basis: var(--flex-basis); + flex-grow: var(--flex-grow); + flex-shrink: var(--flex-shrink); +} diff --git a/packages/flex/src/Flex.stories.tsx b/packages/flex/src/Flex.stories.tsx index b3116b0..8195b96 100644 --- a/packages/flex/src/Flex.stories.tsx +++ b/packages/flex/src/Flex.stories.tsx @@ -25,6 +25,7 @@ const meta = { 'center', 'space-between', 'space-around', + 'space-evenly', ], description: 'Justify content', }, @@ -62,11 +63,38 @@ export const Direction: Story = { args: { direction: 'column', gap: '1rem', - css: { width: '100%' }, + style: { width: '100%' }, children: [ - , - , - , + + 1 + , + + 2 + , + + 3 + , ], }, }; @@ -75,19 +103,19 @@ export const Align: Story = { args: { align: 'center', gap: '1rem', - css: { width: '100%' }, + style: { width: '100%' }, children: [ , , , ], }, @@ -95,26 +123,36 @@ export const Align: Story = { export const Justify: Story = { render: () => ( - + - - flex-start - + + flex-start + - - center - + + center + - - flex-end - + + flex-end + - - space-between - + + space-between + + + + + space-around + + + + + space-evenly + ), @@ -124,7 +162,7 @@ export const Wrap: Story = { args: { wrap: 'wrap', gap: '1rem', - css: { maxWidth: '400px' }, + style: { maxWidth: '400px' }, children: [ , , diff --git a/packages/flex/src/Flex.test.tsx b/packages/flex/src/Flex.test.tsx index 102831e..2554e53 100644 --- a/packages/flex/src/Flex.test.tsx +++ b/packages/flex/src/Flex.test.tsx @@ -1,6 +1,6 @@ import { faker } from '@faker-js/faker'; import { render, screen } from '@testing-library/react'; -import type { CSSProperties } from 'react'; +import { type CSSProperties, createElement } from 'react'; import { describe, expect, it } from 'vitest'; import { Flex } from './Flex'; @@ -204,33 +204,50 @@ describe('Flex', () => {
item 2
, ); + + const flexContainer = screen.getByTestId('flex-container'); + expect(flexContainer).toHaveStyle({ gap }); }, ); }); }); }); - describe('css', () => { + describe('style', () => { it.each([ - { css: { justifyContent: 'flex-start' } }, - { css: { alignItems: 'center' } }, - { css: { flexWrap: 'wrap' } }, - { css: { flexDirection: 'column' } }, - { css: { flexBasis: '100px' } }, - { css: { flexGrow: 1 } }, - { css: { flexShrink: 1 } }, - ] satisfies Array<{ css: CSSProperties }>)( - 'flex의 css prop에 $css 속성을 주입하면 해당 속성을 적용한다.', - ({ css }) => { + { style: { justifyContent: 'flex-start' } }, + { style: { alignItems: 'center' } }, + { style: { flexWrap: 'wrap' } }, + { style: { flexDirection: 'column' } }, + ] satisfies Array<{ style: CSSProperties }>)( + 'flex의 style prop에 $style 속성을 주입하면 해당 속성을 적용한다.', + ({ style }) => { render( - +
item 1
item 2
, ); const flexContainer = screen.getByTestId('flex-container'); - expect(flexContainer).toHaveStyle(css); + expect(flexContainer).toHaveStyle(style); + }, + ); + }); + + describe('polymorphic', () => { + it.each(['span', 'nav', 'button', 'input', 'label', 'div'])( + 'flex의 asChild prop에 true 속성을 주입하면 자식으로 %s 엘리먼트가 전달되면 해당 엘리먼트의 태그로 렌더링된다', + (element) => { + render( + + {createElement(element)} + , + ); + + const flexContainer = screen.getByTestId('flex-container'); + expect(flexContainer).toBeInTheDocument(); + expect(flexContainer.tagName.toLowerCase()).toBe(element); }, ); }); diff --git a/packages/flex/src/Flex.tsx b/packages/flex/src/Flex.tsx index 8c6f7d3..6f2bc03 100644 --- a/packages/flex/src/Flex.tsx +++ b/packages/flex/src/Flex.tsx @@ -1,4 +1,7 @@ +import { Slot } from '@radix-ui/react-slot'; +import { clsx as cx } from 'clsx'; import { type CSSProperties, type ForwardedRef, forwardRef } from 'react'; +import styles from './Flex.module.css'; export interface FlexProps { align?: CSSProperties['alignItems']; @@ -11,8 +14,9 @@ export interface FlexProps { inline?: boolean; gap?: CSSProperties['gap']; className?: string; - css?: CSSProperties; + style?: CSSProperties; children?: React.ReactNode; + asChild?: boolean; } export const Flex = forwardRef(function Flex( @@ -27,31 +31,36 @@ export const Flex = forwardRef(function Flex( inline, gap, className, - css, + style, children, + asChild, ...rest }: FlexProps, - ref: ForwardedRef, + ref: ForwardedRef, ) { + const Component = asChild ? Slot : 'div'; + + const flexStyle = { + '--flex-display': inline ? 'inline-flex' : 'flex', + '--flex-direction': direction, + '--flex-align': align, + '--flex-justify': justify, + '--flex-wrap': wrap, + '--flex-gap': gap, + '--flex-basis': basis, + '--flex-grow': grow, + '--flex-shrink': shrink, + ...style, + } as React.CSSProperties; + return ( -
{children} -
+ ); }); diff --git a/packages/flex/src/global.d.ts b/packages/flex/src/global.d.ts new file mode 100644 index 0000000..60260a3 --- /dev/null +++ b/packages/flex/src/global.d.ts @@ -0,0 +1 @@ +declare module '*.module.css'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5305591..f67bb58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -450,13 +450,19 @@ importers: packages/flex: dependencies: - '@sipe-team/card': - specifier: workspace:^ - version: link:../card + '@radix-ui/react-slot': + specifier: ^1.1.0 + version: 1.1.0(@types/react@18.3.13)(react@18.3.1) + clsx: + specifier: ^2.1.1 + version: 2.1.1 devDependencies: '@faker-js/faker': specifier: ^9.2.0 version: 9.2.0 + '@sipe-team/card': + specifier: workspace:^ + version: link:../card '@storybook/addon-essentials': specifier: 'catalog:' version: 8.4.6(@types/react@18.3.13)(storybook@8.4.6(prettier@2.8.8)) From 1ec944a4fc7052fa59f4dbb72ab06ad83dd02d14 Mon Sep 17 00:00:00 2001 From: synuns Date: Wed, 8 Jan 2025 00:51:27 +0900 Subject: [PATCH 4/7] feat(flex): extend interface to support div attributes - Updated FlexProps to extend ComponentProps<'div'>, allowing for additional div attributes. - Refactored imports in Flex.tsx for improved readability. --- packages/flex/src/Flex.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/flex/src/Flex.tsx b/packages/flex/src/Flex.tsx index 6f2bc03..7cb58e2 100644 --- a/packages/flex/src/Flex.tsx +++ b/packages/flex/src/Flex.tsx @@ -1,9 +1,14 @@ import { Slot } from '@radix-ui/react-slot'; import { clsx as cx } from 'clsx'; -import { type CSSProperties, type ForwardedRef, forwardRef } from 'react'; +import { + type CSSProperties, + type ComponentProps, + type ForwardedRef, + forwardRef, +} from 'react'; import styles from './Flex.module.css'; -export interface FlexProps { +export interface FlexProps extends ComponentProps<'div'> { align?: CSSProperties['alignItems']; justify?: CSSProperties['justifyContent']; wrap?: CSSProperties['flexWrap']; From c32806e995334b834e4a6debf63745993702e5b4 Mon Sep 17 00:00:00 2001 From: synuns Date: Wed, 8 Jan 2025 10:41:04 +0900 Subject: [PATCH 5/7] refactor(flex): update package configuration and clean up code - Changed repository URL in package.json to reflect new project structure. - Simplified tsup configuration by importing default settings. - Modified Flex.module.css to remove unnecessary variable for display property. - Cleaned up FlexProps interface in Flex.tsx by removing unused props. - Moved unused global.d.ts file to package root path --- packages/flex/{src => }/global.d.ts | 0 packages/flex/package.json | 9 ++++----- packages/flex/src/Flex.module.css | 2 +- packages/flex/src/Flex.tsx | 3 --- packages/flex/tsup.config.ts | 9 ++------- 5 files changed, 7 insertions(+), 16 deletions(-) rename packages/flex/{src => }/global.d.ts (100%) diff --git a/packages/flex/src/global.d.ts b/packages/flex/global.d.ts similarity index 100% rename from packages/flex/src/global.d.ts rename to packages/flex/global.d.ts diff --git a/packages/flex/package.json b/packages/flex/package.json index 0d74162..64f2e31 100644 --- a/packages/flex/package.json +++ b/packages/flex/package.json @@ -5,13 +5,11 @@ "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/sipe-team/3-1_sds" + "url": "https://github.com/sipe-team/3-2_side" }, "type": "module", "exports": "./src/index.ts", - "files": [ - "dist" - ], + "files": ["dist"], "scripts": { "build": "tsup", "build:storybook": "storybook build", @@ -62,7 +60,8 @@ "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } - } + }, + "./styles.css": "./dist/styles.css" } }, "sideEffects": false diff --git a/packages/flex/src/Flex.module.css b/packages/flex/src/Flex.module.css index 5e4e2ea..22d5bed 100644 --- a/packages/flex/src/Flex.module.css +++ b/packages/flex/src/Flex.module.css @@ -1,5 +1,5 @@ .flex { - display: var(--flex-display, flex); + display: var(--flex-display); flex-direction: var(--flex-direction); align-items: var(--flex-align); justify-content: var(--flex-justify); diff --git a/packages/flex/src/Flex.tsx b/packages/flex/src/Flex.tsx index 7cb58e2..3a6604f 100644 --- a/packages/flex/src/Flex.tsx +++ b/packages/flex/src/Flex.tsx @@ -18,9 +18,6 @@ export interface FlexProps extends ComponentProps<'div'> { shrink?: CSSProperties['flexShrink']; inline?: boolean; gap?: CSSProperties['gap']; - className?: string; - style?: CSSProperties; - children?: React.ReactNode; asChild?: boolean; } diff --git a/packages/flex/tsup.config.ts b/packages/flex/tsup.config.ts index c533199..ee4b117 100644 --- a/packages/flex/tsup.config.ts +++ b/packages/flex/tsup.config.ts @@ -1,8 +1,3 @@ -import { defineConfig } from 'tsup'; +import defaultConfig from '../../tsup.config'; -export default defineConfig({ - entry: ['src/index.ts'], - clean: true, - dts: true, - format: ['esm', 'cjs'], -}); +export default defaultConfig; From 1f7affe9374a4efa0fee6227c7b2363f5af8e61c Mon Sep 17 00:00:00 2001 From: y09n Date: Wed, 8 Jan 2025 10:44:28 +0900 Subject: [PATCH 6/7] Create ninety-icons-invite.md --- .changeset/ninety-icons-invite.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/ninety-icons-invite.md diff --git a/.changeset/ninety-icons-invite.md b/.changeset/ninety-icons-invite.md new file mode 100644 index 0000000..65b3c64 --- /dev/null +++ b/.changeset/ninety-icons-invite.md @@ -0,0 +1,6 @@ +--- +"@sipe-team/flex": minor +"@sipe-team/side": minor +--- + +feat(flex): add flex component From 032f84b2df5439398569254dd58aeac638f8ca60 Mon Sep 17 00:00:00 2001 From: Euihyun Yang Date: Wed, 8 Jan 2025 10:54:01 +0900 Subject: [PATCH 7/7] chore: update lockfile --- pnpm-lock.yaml | 52 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 297c710..2b26c55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -530,6 +530,54 @@ importers: '@sipe-team/card': specifier: workspace:^ version: link:../card + '@storybook/addon-essentials': + specifier: 'catalog:' + version: 8.4.6(@types/react@18.3.13)(storybook@8.4.6(prettier@2.8.8)) + '@storybook/addon-interactions': + specifier: 'catalog:' + version: 8.4.6(storybook@8.4.6(prettier@2.8.8)) + '@storybook/addon-links': + specifier: 'catalog:' + version: 8.4.6(react@18.3.1)(storybook@8.4.6(prettier@2.8.8)) + '@storybook/blocks': + specifier: 'catalog:' + version: 8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(prettier@2.8.8)) + '@storybook/react': + specifier: 'catalog:' + version: 8.4.6(@storybook/test@8.4.6(storybook@8.4.6(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(prettier@2.8.8))(typescript@5.7.2) + '@storybook/react-vite': + specifier: 'catalog:' + version: 8.4.6(@storybook/test@8.4.6(storybook@8.4.6(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.28.0)(storybook@8.4.6(prettier@2.8.8))(typescript@5.7.2)(vite@5.4.11(@types/node@22.10.1)) + '@storybook/test': + specifier: 'catalog:' + version: 8.4.6(storybook@8.4.6(prettier@2.8.8)) + '@testing-library/jest-dom': + specifier: 'catalog:' + version: 6.6.3 + '@testing-library/react': + specifier: 'catalog:' + version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.1)(@types/react@18.3.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/react': + specifier: ^18.3.12 + version: 18.3.13 + happy-dom: + specifier: 'catalog:' + version: 15.11.7 + react: + specifier: ^18.3.1 + version: 18.3.1 + storybook: + specifier: 'catalog:' + version: 8.4.6(prettier@2.8.8) + tsup: + specifier: 'catalog:' + version: 8.3.5(jiti@2.4.1)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.6.1) + typescript: + specifier: 'catalog:' + version: 5.7.2 + vitest: + specifier: 'catalog:' + version: 2.1.8(@types/node@22.10.1)(happy-dom@15.11.7) packages/icon: devDependencies: @@ -5006,7 +5054,7 @@ snapshots: '@eslint/config-array@0.19.1': dependencies: '@eslint/object-schema': 2.1.5 - debug: 4.3.7 + debug: 4.4.0 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -5018,7 +5066,7 @@ snapshots: '@eslint/eslintrc@3.2.0': dependencies: ajv: 6.12.6 - debug: 4.3.7 + debug: 4.4.0 espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2