From f9b5d3bcba16ec7262732c2a7e69ce0c6c221354 Mon Sep 17 00:00:00 2001 From: ohprettyhak Date: Wed, 13 Aug 2025 13:13:05 +0900 Subject: [PATCH 01/21] feat(ui): add Button component and update Badge variant to 'primary' --- apps/manager/src/api/queries/index.ts | 0 packages/ui/package.json | 3 +- packages/ui/src/badge/Badge.tsx | 6 +- packages/ui/src/button/Button.module.css | 17 ++++ packages/ui/src/button/Button.tsx | 100 +++++++++++++++++++++++ packages/ui/src/button/index.ts | 1 + packages/ui/src/token/color.ts | 20 +++-- pnpm-lock.yaml | 8 ++ 8 files changed, 146 insertions(+), 9 deletions(-) delete mode 100644 apps/manager/src/api/queries/index.ts create mode 100644 packages/ui/src/button/Button.module.css create mode 100644 packages/ui/src/button/Button.tsx create mode 100644 packages/ui/src/button/index.ts diff --git a/apps/manager/src/api/queries/index.ts b/apps/manager/src/api/queries/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/ui/package.json b/packages/ui/package.json index 189a605c..1b71c627 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -46,6 +46,7 @@ }, "dependencies": { "@radix-ui/react-slot": "^1.2.3", - "clsx": "^2.1.1" + "clsx": "^2.1.1", + "ts-pattern": "^5.8.0" } } diff --git a/packages/ui/src/badge/Badge.tsx b/packages/ui/src/badge/Badge.tsx index fa2c0484..b63de313 100644 --- a/packages/ui/src/badge/Badge.tsx +++ b/packages/ui/src/badge/Badge.tsx @@ -6,7 +6,7 @@ import styles from './Badge.module.css'; type BadgeSize = 'small' | 'medium' | 'large'; -type BadgeVariant = 'default' | 'danger' | 'success'; +type BadgeVariant = 'default' | 'danger' | 'primary'; export interface BadgeProps extends ComponentProps<'div'> { size?: BadgeSize; @@ -66,7 +66,7 @@ const getFontColor = (variant: BadgeVariant) => { return color.neutral600; case 'danger': return color.white; - case 'success': + case 'primary': return color.primary600; } }; @@ -77,7 +77,7 @@ const getBackgroundColor = (variant: BadgeVariant) => { return color.neutral100; case 'danger': return color.danger600; - case 'success': + case 'primary': return color.primary100; } }; diff --git a/packages/ui/src/button/Button.module.css b/packages/ui/src/button/Button.module.css new file mode 100644 index 00000000..f8ad82bf --- /dev/null +++ b/packages/ui/src/button/Button.module.css @@ -0,0 +1,17 @@ +.button { + color: var(--hcc-button-color); + border: var(--hcc-button-border); + border-radius: var(--hcc-button-border-radius); + background-color: var(--hcc-button-bg-color); + + --hcc-button-color: inherit; + --hcc-button-border: inherit; + --hcc-button-border-radius: inherit; + --hcc-button-bg-color: inherit; +} + +.disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; +} \ No newline at end of file diff --git a/packages/ui/src/button/Button.tsx b/packages/ui/src/button/Button.tsx new file mode 100644 index 00000000..4a4aeb9b --- /dev/null +++ b/packages/ui/src/button/Button.tsx @@ -0,0 +1,100 @@ +import { Slot } from '@radix-ui/react-slot'; +import { clsx } from 'clsx'; +import { type ComponentProps, type CSSProperties, forwardRef } from 'react'; +import { match } from 'ts-pattern'; +import { color as colorToken } from '../token'; +import styles from './Button.module.css'; + +type ButtonSize = 'small' | 'medium' | 'large'; + +type ButtonColor = 'black' | 'white' | 'primary' | 'danger'; + +type ButtonVariant = 'solid' | 'subtle' | 'outline' | 'ghost'; + +export interface ButtonProps extends ComponentProps<'button'> { + asChild?: boolean; + size?: ButtonSize; + color?: ButtonColor; + variant?: ButtonVariant; + disabled?: boolean; +} + +export const Button = forwardRef( + ( + { + asChild, + className, + children, + disabled, + size = 'medium', + color = 'primary', + variant = 'solid', + style: _style, + ...props + }, + ref, + ) => { + const Comp = asChild ? Slot : 'button'; + + const style = { + ..._style, + ...getButtonStyle({ color, variant }), + } as CSSProperties; + + return ( + + {children} + + ); + }, +); + +const getButtonStyle = ({ color, variant }: { color: ButtonColor; variant: ButtonVariant }) => { + const backgroundColor = match([color, variant]) + .with(['black', 'solid'], () => colorToken.neutral900) + .with(['black', 'subtle'], () => colorToken.neutral50) + .with(['white', 'solid'], () => colorToken.white) + .with(['white', 'subtle'], () => colorToken.white) + .with(['primary', 'solid'], () => colorToken.primary600) + .with(['primary', 'subtle'], () => colorToken.primary100) + .with(['danger', 'solid'], () => colorToken.danger600) + .with(['danger', 'subtle'], () => colorToken.danger100) + .otherwise(() => colorToken.transparent); + + const borderColor = match([color, variant]) + .with(['black', 'outline'], () => `1px solid ${colorToken.neutral900}`) + .with(['white', 'outline'], () => `1px solid ${colorToken.white}`) + .with(['primary', 'outline'], () => `1px solid ${colorToken.primary600}`) + .with(['danger', 'outline'], () => `1px solid ${colorToken.danger600}`) + .otherwise(() => 'none'); + + const fontColor = match([color, variant]) + .with(['black', 'solid'], () => colorToken.white) + .with(['black', 'subtle'], () => colorToken.neutral900) + .with(['black', 'outline'], () => colorToken.neutral900) + .with(['black', 'ghost'], () => colorToken.neutral900) + .with(['white', 'solid'], () => colorToken.neutral900) + .with(['white', 'subtle'], () => colorToken.neutral900) + .with(['white', 'outline'], () => colorToken.neutral900) + .with(['white', 'ghost'], () => colorToken.neutral900) + .with(['primary', 'solid'], () => colorToken.white) + .with(['primary', 'subtle'], () => colorToken.primary600) + .with(['primary', 'outline'], () => colorToken.primary600) + .with(['primary', 'ghost'], () => colorToken.primary600) + .with(['danger', 'solid'], () => colorToken.white) + .with(['danger', 'subtle'], () => colorToken.danger600) + .with(['danger', 'outline'], () => colorToken.danger600) + .with(['danger', 'ghost'], () => colorToken.danger600) + .exhaustive(); + + return { + '--hcc-button-background-color': backgroundColor, + '--hcc-button-border-color': borderColor, + '--hcc-button-font-color': fontColor, + }; +}; diff --git a/packages/ui/src/button/index.ts b/packages/ui/src/button/index.ts new file mode 100644 index 00000000..8b166a86 --- /dev/null +++ b/packages/ui/src/button/index.ts @@ -0,0 +1 @@ +export * from './Button'; diff --git a/packages/ui/src/token/color.ts b/packages/ui/src/token/color.ts index 4dea5d7c..1a746d37 100644 --- a/packages/ui/src/token/color.ts +++ b/packages/ui/src/token/color.ts @@ -1,12 +1,22 @@ export const color = { - white: '#FFFFFF', - black: '#000000', + white: 'oklch(100% 0 0)', + black: 'oklch(0% 0 0)', + transparent: 'transparent', primary100: '#F2F8FF', primary600: '#007AFF', + danger100: '#FFF2F1', danger600: '#FC5555', - neutral100: '#F5F5F5', - neutral600: '#525252', -}; + neutral50: 'oklch(98.5% 0 0)', + neutral100: 'oklch(97% 0 0)', + neutral200: 'oklch(92.2% 0 0)', + neutral300: 'oklch(87% 0 0)', + neutral400: 'oklch(70.8% 0 0)', + neutral500: 'oklch(55.6% 0 0)', + neutral600: 'oklch(43.9% 0 0)', + neutral700: 'oklch(37.1% 0 0)', + neutral800: 'oklch(26.9% 0 0)', + neutral900: 'oklch(20.5% 0 0)', +} as const; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9b567d1..39a77036 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -206,6 +206,9 @@ importers: react-dom: specifier: ^19.1.1 version: 19.1.1(react@19.1.1) + ts-pattern: + specifier: ^5.8.0 + version: 5.8.0 devDependencies: '@hcc/typescript-config': specifier: workspace:* @@ -1541,6 +1544,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + ts-pattern@5.8.0: + resolution: {integrity: sha512-kIjN2qmWiHnhgr5DAkAafF9fwb0T5OhMVSWrm8XEdTFnX6+wfXwYOFjeF86UZ54vduqiR7BfqScFmXSzSaH8oA==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -2737,6 +2743,8 @@ snapshots: dependencies: is-number: 7.0.0 + ts-pattern@5.8.0: {} + tslib@2.8.1: {} turbo-darwin-64@2.5.5: From e79481170ace57687eee3fcc8264388c4423e5a6 Mon Sep 17 00:00:00 2001 From: ohprettyhak Date: Wed, 13 Aug 2025 16:38:50 +0900 Subject: [PATCH 02/21] feat(ui): update Badge and Button components with new size options and styles --- apps/manager/src/app/auth/login/page.tsx | 8 +- packages/ui/src/badge/Badge.tsx | 16 ++-- packages/ui/src/button/Button.module.css | 27 +++++- packages/ui/src/button/Button.tsx | 110 ++++++++++++++--------- packages/ui/src/index.ts | 1 + packages/ui/src/token/color.ts | 18 +++- 6 files changed, 122 insertions(+), 58 deletions(-) diff --git a/apps/manager/src/app/auth/login/page.tsx b/apps/manager/src/app/auth/login/page.tsx index 2ffb4d78..bec9c944 100644 --- a/apps/manager/src/app/auth/login/page.tsx +++ b/apps/manager/src/app/auth/login/page.tsx @@ -1,4 +1,4 @@ -import { Badge } from '@hcc/ui'; +import { Badge, Button } from '@hcc/ui'; const Page = () => { return ( @@ -9,11 +9,13 @@ const Page = () => {
manager - + 매니저 용 -
+
+ +
); }; diff --git a/packages/ui/src/badge/Badge.tsx b/packages/ui/src/badge/Badge.tsx index b63de313..ad8ab02a 100644 --- a/packages/ui/src/badge/Badge.tsx +++ b/packages/ui/src/badge/Badge.tsx @@ -4,7 +4,7 @@ import { color } from '../token'; import { Typography } from '../typography'; import styles from './Badge.module.css'; -type BadgeSize = 'small' | 'medium' | 'large'; +type BadgeSize = 'sm' | 'md' | 'lg'; type BadgeVariant = 'default' | 'danger' | 'primary'; @@ -14,7 +14,7 @@ export interface BadgeProps extends ComponentProps<'div'> { } export const Badge = forwardRef( - ({ className, children, size = 'medium', variant = 'default', style: _style, ...props }, ref) => { + ({ className, children, size = 'md', variant = 'default', style: _style, ...props }, ref) => { const backgroundColor = getBackgroundColor(variant); const padding = getPadding(size); const style = { @@ -40,22 +40,22 @@ export const Badge = forwardRef( const getPadding = (size: BadgeSize) => { switch (size) { - case 'small': + case 'sm': return '6px 8px'; - case 'medium': + case 'md': return '8px 12px'; - case 'large': + case 'lg': return '10px 16px'; } }; const getFontSize = (size: BadgeSize) => { switch (size) { - case 'small': + case 'sm': return 12; - case 'medium': + case 'md': return 14; - case 'large': + case 'lg': return 18; } }; diff --git a/packages/ui/src/button/Button.module.css b/packages/ui/src/button/Button.module.css index f8ad82bf..c8087bcf 100644 --- a/packages/ui/src/button/Button.module.css +++ b/packages/ui/src/button/Button.module.css @@ -1,17 +1,38 @@ .button { - color: var(--hcc-button-color); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: background-color 0.15s ease-in-out; + + height: var(--hcc-button-height); + color: var(--hcc-button-font-color); + font-size: var(--hcc-button-font-size); border: var(--hcc-button-border); border-radius: var(--hcc-button-border-radius); background-color: var(--hcc-button-bg-color); - --hcc-button-color: inherit; + --hcc-button-height: inherit; + --hcc-button-font-color: inherit; + --hcc-button-font-size: inherit; --hcc-button-border: inherit; --hcc-button-border-radius: inherit; --hcc-button-bg-color: inherit; } +.button:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; +} + .disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; -} \ No newline at end of file +} + +.button:hover { + background-color: var(--hcc-button-bg-hover-color); + + --hcc-button-bg-hover-color: inherit; +} diff --git a/packages/ui/src/button/Button.tsx b/packages/ui/src/button/Button.tsx index 4a4aeb9b..e04d7875 100644 --- a/packages/ui/src/button/Button.tsx +++ b/packages/ui/src/button/Button.tsx @@ -2,14 +2,14 @@ import { Slot } from '@radix-ui/react-slot'; import { clsx } from 'clsx'; import { type ComponentProps, type CSSProperties, forwardRef } from 'react'; import { match } from 'ts-pattern'; -import { color as colorToken } from '../token'; +import { color as token } from '../token'; import styles from './Button.module.css'; -type ButtonSize = 'small' | 'medium' | 'large'; +type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; -type ButtonColor = 'black' | 'white' | 'primary' | 'danger'; +type ButtonColor = 'black' | 'primary' | 'danger'; -type ButtonVariant = 'solid' | 'subtle' | 'outline' | 'ghost'; +type ButtonVariant = 'solid' | 'subtle' | 'ghost'; export interface ButtonProps extends ComponentProps<'button'> { asChild?: boolean; @@ -26,7 +26,7 @@ export const Button = forwardRef( className, children, disabled, - size = 'medium', + size = 'md', color = 'primary', variant = 'solid', style: _style, @@ -38,7 +38,10 @@ export const Button = forwardRef( const style = { ..._style, - ...getButtonStyle({ color, variant }), + ...getColorStyle(color, variant), + '--hcc-button-height': `${getHeight(size)}px`, + '--hcc-button-font-size': `${getFontSize(size)}px`, + '--hcc-button-border-radius': '8px', } as CSSProperties; return ( @@ -54,47 +57,68 @@ export const Button = forwardRef( }, ); -const getButtonStyle = ({ color, variant }: { color: ButtonColor; variant: ButtonVariant }) => { - const backgroundColor = match([color, variant]) - .with(['black', 'solid'], () => colorToken.neutral900) - .with(['black', 'subtle'], () => colorToken.neutral50) - .with(['white', 'solid'], () => colorToken.white) - .with(['white', 'subtle'], () => colorToken.white) - .with(['primary', 'solid'], () => colorToken.primary600) - .with(['primary', 'subtle'], () => colorToken.primary100) - .with(['danger', 'solid'], () => colorToken.danger600) - .with(['danger', 'subtle'], () => colorToken.danger100) - .otherwise(() => colorToken.transparent); - - const borderColor = match([color, variant]) - .with(['black', 'outline'], () => `1px solid ${colorToken.neutral900}`) - .with(['white', 'outline'], () => `1px solid ${colorToken.white}`) - .with(['primary', 'outline'], () => `1px solid ${colorToken.primary600}`) - .with(['danger', 'outline'], () => `1px solid ${colorToken.danger600}`) - .otherwise(() => 'none'); - +const getColorStyle = (color: ButtonColor, variant: ButtonVariant) => { const fontColor = match([color, variant]) - .with(['black', 'solid'], () => colorToken.white) - .with(['black', 'subtle'], () => colorToken.neutral900) - .with(['black', 'outline'], () => colorToken.neutral900) - .with(['black', 'ghost'], () => colorToken.neutral900) - .with(['white', 'solid'], () => colorToken.neutral900) - .with(['white', 'subtle'], () => colorToken.neutral900) - .with(['white', 'outline'], () => colorToken.neutral900) - .with(['white', 'ghost'], () => colorToken.neutral900) - .with(['primary', 'solid'], () => colorToken.white) - .with(['primary', 'subtle'], () => colorToken.primary600) - .with(['primary', 'outline'], () => colorToken.primary600) - .with(['primary', 'ghost'], () => colorToken.primary600) - .with(['danger', 'solid'], () => colorToken.white) - .with(['danger', 'subtle'], () => colorToken.danger600) - .with(['danger', 'outline'], () => colorToken.danger600) - .with(['danger', 'ghost'], () => colorToken.danger600) + .with(['black', 'solid'], ['primary', 'solid'], ['danger', 'solid'], () => token.white) + .with(['black', 'subtle'], ['black', 'ghost'], () => token.neutral900) + .with(['primary', 'subtle'], ['primary', 'ghost'], () => token.primary600) + .with(['danger', 'subtle'], ['danger', 'ghost'], () => token.danger600) .exhaustive(); + const backgroundColor = match([color, variant]) + .with(['black', 'solid'], () => token.neutral900) + .with(['black', 'subtle'], () => token.neutral50) + .with(['primary', 'solid'], () => token.primary600) + .with(['primary', 'subtle'], () => token.primary100) + .with(['danger', 'solid'], () => token.danger600) + .with(['danger', 'subtle'], () => token.danger100) + .otherwise(() => token.transparent); + + const backgroundHoverColor = match([color, variant]) + .with(['black', 'solid'], () => token.neutral700) + .with(['black', 'subtle'], () => token.neutral100) + .with(['black', 'ghost'], () => token.neutral50) + .with(['primary', 'solid'], () => token.primary700) + .with(['primary', 'subtle'], () => token.primary200) + .with(['primary', 'ghost'], () => token.primary50) + .with(['danger', 'solid'], () => token.danger700) + .with(['danger', 'subtle'], () => token.danger200) + .with(['danger', 'ghost'], () => token.danger50) + .otherwise(() => token.transparent); + return { - '--hcc-button-background-color': backgroundColor, - '--hcc-button-border-color': borderColor, '--hcc-button-font-color': fontColor, + '--hcc-button-bg-color': backgroundColor, + '--hcc-button-bg-hover-color': backgroundHoverColor, }; }; + +export const getFontSize = (size: ButtonSize) => { + switch (size) { + case 'xs': + return 12; + case 'sm': + return 14; + case 'md': + return 14; + case 'lg': + return 16; + case 'xl': + return 18; + } +}; + +export const getHeight = (size: ButtonSize) => { + switch (size) { + case 'xs': + return 28; + case 'sm': + return 36; + case 'md': + return 44; + case 'lg': + return 52; + case 'xl': + return 60; + } +}; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index f3a90f60..4a3e77fc 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,3 +1,4 @@ export * from './badge'; +export * from './button'; export * from './token'; export * from './typography'; diff --git a/packages/ui/src/token/color.ts b/packages/ui/src/token/color.ts index 1a746d37..393bf9b5 100644 --- a/packages/ui/src/token/color.ts +++ b/packages/ui/src/token/color.ts @@ -3,11 +3,27 @@ export const color = { black: 'oklch(0% 0 0)', transparent: 'transparent', + primary50: '#F8FBFF', primary100: '#F2F8FF', + primary200: '#E0EDFF', + primary300: '#C7DBFF', + primary400: '#A3C4FF', + primary500: '#4D9FFF', primary600: '#007AFF', + primary700: '#0056CC', + primary800: '#003D99', + primary900: '#002466', - danger100: '#FFF2F1', + danger50: '#FFF8F7', + danger100: '#FFF0EF', + danger200: '#FFE0DE', + danger300: '#FFC7C4', + danger400: '#FFA3A0', + danger500: '#FE7A7A', danger600: '#FC5555', + danger700: '#E63939', + danger800: '#CC2626', + danger900: '#991A1A', neutral50: 'oklch(98.5% 0 0)', neutral100: 'oklch(97% 0 0)', From 269b2078d6e9f914557aeda6c57bc4dd7d88585e Mon Sep 17 00:00:00 2001 From: ohprettyhak Date: Wed, 13 Aug 2025 16:44:04 +0900 Subject: [PATCH 03/21] feat(ui): enhance Button component with font weight options and update usage in page --- apps/manager/src/app/auth/login/page.tsx | 6 ++++-- packages/ui/src/button/Button.module.css | 2 ++ packages/ui/src/button/Button.tsx | 18 +++++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/manager/src/app/auth/login/page.tsx b/apps/manager/src/app/auth/login/page.tsx index bec9c944..0e41242e 100644 --- a/apps/manager/src/app/auth/login/page.tsx +++ b/apps/manager/src/app/auth/login/page.tsx @@ -9,12 +9,14 @@ const Page = () => {
manager - + 매니저 용
- +
); diff --git a/packages/ui/src/button/Button.module.css b/packages/ui/src/button/Button.module.css index c8087bcf..d86116f7 100644 --- a/packages/ui/src/button/Button.module.css +++ b/packages/ui/src/button/Button.module.css @@ -8,6 +8,7 @@ height: var(--hcc-button-height); color: var(--hcc-button-font-color); font-size: var(--hcc-button-font-size); + font-weight: var(--hcc-button-font-weight); border: var(--hcc-button-border); border-radius: var(--hcc-button-border-radius); background-color: var(--hcc-button-bg-color); @@ -15,6 +16,7 @@ --hcc-button-height: inherit; --hcc-button-font-color: inherit; --hcc-button-font-size: inherit; + --hcc-button-font-weight: inherit; --hcc-button-border: inherit; --hcc-button-border-radius: inherit; --hcc-button-bg-color: inherit; diff --git a/packages/ui/src/button/Button.tsx b/packages/ui/src/button/Button.tsx index e04d7875..c21bdb00 100644 --- a/packages/ui/src/button/Button.tsx +++ b/packages/ui/src/button/Button.tsx @@ -2,7 +2,7 @@ import { Slot } from '@radix-ui/react-slot'; import { clsx } from 'clsx'; import { type ComponentProps, type CSSProperties, forwardRef } from 'react'; import { match } from 'ts-pattern'; -import { color as token } from '../token'; +import { fontWeight as fontWeightToken, color as token } from '../token'; import styles from './Button.module.css'; type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; @@ -41,6 +41,7 @@ export const Button = forwardRef( ...getColorStyle(color, variant), '--hcc-button-height': `${getHeight(size)}px`, '--hcc-button-font-size': `${getFontSize(size)}px`, + '--hcc-button-font-weight': fontWeightToken[getFontWeight(size)], '--hcc-button-border-radius': '8px', } as CSSProperties; @@ -108,6 +109,21 @@ export const getFontSize = (size: ButtonSize) => { } }; +export const getFontWeight = (size: ButtonSize) => { + switch (size) { + case 'xs': + return 'medium'; + case 'sm': + return 'medium'; + case 'md': + return 'semiBold'; + case 'lg': + return 'semiBold'; + case 'xl': + return 'semiBold'; + } +}; + export const getHeight = (size: ButtonSize) => { switch (size) { case 'xs': From 0473f14bc274aaac690a0d441715eaf46c9cf42f Mon Sep 17 00:00:00 2001 From: ohprettyhak Date: Wed, 13 Aug 2025 16:45:13 +0900 Subject: [PATCH 04/21] feat(ui): change Button component display to inline-flex for better layout --- packages/ui/src/button/Button.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/button/Button.module.css b/packages/ui/src/button/Button.module.css index d86116f7..2d090840 100644 --- a/packages/ui/src/button/Button.module.css +++ b/packages/ui/src/button/Button.module.css @@ -1,5 +1,5 @@ .button { - display: flex; + display: inline-flex; align-items: center; justify-content: center; cursor: pointer; From c2bb9f0b7fb8a9998b6457ab1c138fc35401f6cf Mon Sep 17 00:00:00 2001 From: ohprettyhak Date: Wed, 13 Aug 2025 17:20:29 +0900 Subject: [PATCH 05/21] feat(ui): add height, font size, and font weight utilities for Button component --- packages/ui/src/button/Button.tsx | 90 +++++++++++++++---------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/packages/ui/src/button/Button.tsx b/packages/ui/src/button/Button.tsx index c21bdb00..7e51f081 100644 --- a/packages/ui/src/button/Button.tsx +++ b/packages/ui/src/button/Button.tsx @@ -58,6 +58,51 @@ export const Button = forwardRef( }, ); +export const getHeight = (size: ButtonSize) => { + switch (size) { + case 'xs': + return 28; + case 'sm': + return 36; + case 'md': + return 44; + case 'lg': + return 52; + case 'xl': + return 60; + } +}; + +export const getFontSize = (size: ButtonSize) => { + switch (size) { + case 'xs': + return 12; + case 'sm': + return 14; + case 'md': + return 14; + case 'lg': + return 16; + case 'xl': + return 18; + } +}; + +export const getFontWeight = (size: ButtonSize) => { + switch (size) { + case 'xs': + return 'medium'; + case 'sm': + return 'medium'; + case 'md': + return 'semiBold'; + case 'lg': + return 'semiBold'; + case 'xl': + return 'semiBold'; + } +}; + const getColorStyle = (color: ButtonColor, variant: ButtonVariant) => { const fontColor = match([color, variant]) .with(['black', 'solid'], ['primary', 'solid'], ['danger', 'solid'], () => token.white) @@ -93,48 +138,3 @@ const getColorStyle = (color: ButtonColor, variant: ButtonVariant) => { '--hcc-button-bg-hover-color': backgroundHoverColor, }; }; - -export const getFontSize = (size: ButtonSize) => { - switch (size) { - case 'xs': - return 12; - case 'sm': - return 14; - case 'md': - return 14; - case 'lg': - return 16; - case 'xl': - return 18; - } -}; - -export const getFontWeight = (size: ButtonSize) => { - switch (size) { - case 'xs': - return 'medium'; - case 'sm': - return 'medium'; - case 'md': - return 'semiBold'; - case 'lg': - return 'semiBold'; - case 'xl': - return 'semiBold'; - } -}; - -export const getHeight = (size: ButtonSize) => { - switch (size) { - case 'xs': - return 28; - case 'sm': - return 36; - case 'md': - return 44; - case 'lg': - return 52; - case 'xl': - return 60; - } -}; From a90789adcf26ee4ccfdc61805458b828444e61c0 Mon Sep 17 00:00:00 2001 From: ohprettyhak Date: Wed, 13 Aug 2025 18:23:41 +0900 Subject: [PATCH 06/21] feat(ui): refactor color imports to use colors object in Badge and Button components --- packages/ui/src/badge/Badge.tsx | 14 +++++----- packages/ui/src/button/Button.tsx | 44 +++++++++++++++---------------- packages/ui/src/token/color.ts | 2 +- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/ui/src/badge/Badge.tsx b/packages/ui/src/badge/Badge.tsx index ad8ab02a..9580e85c 100644 --- a/packages/ui/src/badge/Badge.tsx +++ b/packages/ui/src/badge/Badge.tsx @@ -1,6 +1,6 @@ import { clsx } from 'clsx'; import { type ComponentProps, type CSSProperties, forwardRef } from 'react'; -import { color } from '../token'; +import { colors } from '../token'; import { Typography } from '../typography'; import styles from './Badge.module.css'; @@ -63,21 +63,21 @@ const getFontSize = (size: BadgeSize) => { const getFontColor = (variant: BadgeVariant) => { switch (variant) { case 'default': - return color.neutral600; + return colors.neutral600; case 'danger': - return color.white; + return colors.white; case 'primary': - return color.primary600; + return colors.primary600; } }; const getBackgroundColor = (variant: BadgeVariant) => { switch (variant) { case 'default': - return color.neutral100; + return colors.neutral100; case 'danger': - return color.danger600; + return colors.danger600; case 'primary': - return color.primary100; + return colors.primary100; } }; diff --git a/packages/ui/src/button/Button.tsx b/packages/ui/src/button/Button.tsx index 7e51f081..49252420 100644 --- a/packages/ui/src/button/Button.tsx +++ b/packages/ui/src/button/Button.tsx @@ -2,7 +2,7 @@ import { Slot } from '@radix-ui/react-slot'; import { clsx } from 'clsx'; import { type ComponentProps, type CSSProperties, forwardRef } from 'react'; import { match } from 'ts-pattern'; -import { fontWeight as fontWeightToken, color as token } from '../token'; +import { colors, fontWeight as fontWeightToken } from '../token'; import styles from './Button.module.css'; type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; @@ -105,32 +105,32 @@ export const getFontWeight = (size: ButtonSize) => { const getColorStyle = (color: ButtonColor, variant: ButtonVariant) => { const fontColor = match([color, variant]) - .with(['black', 'solid'], ['primary', 'solid'], ['danger', 'solid'], () => token.white) - .with(['black', 'subtle'], ['black', 'ghost'], () => token.neutral900) - .with(['primary', 'subtle'], ['primary', 'ghost'], () => token.primary600) - .with(['danger', 'subtle'], ['danger', 'ghost'], () => token.danger600) + .with(['black', 'solid'], ['primary', 'solid'], ['danger', 'solid'], () => colors.white) + .with(['black', 'subtle'], ['black', 'ghost'], () => colors.neutral900) + .with(['primary', 'subtle'], ['primary', 'ghost'], () => colors.primary600) + .with(['danger', 'subtle'], ['danger', 'ghost'], () => colors.danger600) .exhaustive(); const backgroundColor = match([color, variant]) - .with(['black', 'solid'], () => token.neutral900) - .with(['black', 'subtle'], () => token.neutral50) - .with(['primary', 'solid'], () => token.primary600) - .with(['primary', 'subtle'], () => token.primary100) - .with(['danger', 'solid'], () => token.danger600) - .with(['danger', 'subtle'], () => token.danger100) - .otherwise(() => token.transparent); + .with(['black', 'solid'], () => colors.neutral900) + .with(['black', 'subtle'], () => colors.neutral50) + .with(['primary', 'solid'], () => colors.primary600) + .with(['primary', 'subtle'], () => colors.primary100) + .with(['danger', 'solid'], () => colors.danger600) + .with(['danger', 'subtle'], () => colors.danger100) + .otherwise(() => colors.transparent); const backgroundHoverColor = match([color, variant]) - .with(['black', 'solid'], () => token.neutral700) - .with(['black', 'subtle'], () => token.neutral100) - .with(['black', 'ghost'], () => token.neutral50) - .with(['primary', 'solid'], () => token.primary700) - .with(['primary', 'subtle'], () => token.primary200) - .with(['primary', 'ghost'], () => token.primary50) - .with(['danger', 'solid'], () => token.danger700) - .with(['danger', 'subtle'], () => token.danger200) - .with(['danger', 'ghost'], () => token.danger50) - .otherwise(() => token.transparent); + .with(['black', 'solid'], () => colors.neutral700) + .with(['black', 'subtle'], () => colors.neutral100) + .with(['black', 'ghost'], () => colors.neutral50) + .with(['primary', 'solid'], () => colors.primary700) + .with(['primary', 'subtle'], () => colors.primary200) + .with(['primary', 'ghost'], () => colors.primary50) + .with(['danger', 'solid'], () => colors.danger700) + .with(['danger', 'subtle'], () => colors.danger200) + .with(['danger', 'ghost'], () => colors.danger50) + .otherwise(() => colors.transparent); return { '--hcc-button-font-color': fontColor, diff --git a/packages/ui/src/token/color.ts b/packages/ui/src/token/color.ts index 393bf9b5..e902eb9e 100644 --- a/packages/ui/src/token/color.ts +++ b/packages/ui/src/token/color.ts @@ -1,4 +1,4 @@ -export const color = { +export const colors = { white: 'oklch(100% 0 0)', black: 'oklch(0% 0 0)', transparent: 'transparent', From e7a4c118c47cfb24bc7bf399c2dc53929086d51a Mon Sep 17 00:00:00 2001 From: ohprettyhak Date: Thu, 14 Aug 2025 02:56:38 +0900 Subject: [PATCH 07/21] feat(ui): add Input component with responsive font size and weight options --- packages/ui/src/badge/Badge.tsx | 8 ++- packages/ui/src/index.ts | 1 + packages/ui/src/input/Input.module.css | 5 ++ packages/ui/src/input/Input.tsx | 52 +++++++++++++++++++ packages/ui/src/input/index.ts | 1 + packages/ui/src/token/typography.ts | 27 ++++++++++ .../ui/src/typography/Typography.module.css | 2 +- packages/ui/src/typography/Typography.tsx | 42 ++++----------- 8 files changed, 105 insertions(+), 33 deletions(-) create mode 100644 packages/ui/src/input/Input.module.css create mode 100644 packages/ui/src/input/Input.tsx create mode 100644 packages/ui/src/input/index.ts diff --git a/packages/ui/src/badge/Badge.tsx b/packages/ui/src/badge/Badge.tsx index 9580e85c..46b031fd 100644 --- a/packages/ui/src/badge/Badge.tsx +++ b/packages/ui/src/badge/Badge.tsx @@ -30,7 +30,13 @@ export const Badge = forwardRef( return (
- + {children}
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 4a3e77fc..d42e9570 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,4 +1,5 @@ export * from './badge'; export * from './button'; +export * from './input'; export * from './token'; export * from './typography'; diff --git a/packages/ui/src/input/Input.module.css b/packages/ui/src/input/Input.module.css new file mode 100644 index 00000000..41824cbb --- /dev/null +++ b/packages/ui/src/input/Input.module.css @@ -0,0 +1,5 @@ +.wrapper {} + +.input { + +} \ No newline at end of file diff --git a/packages/ui/src/input/Input.tsx b/packages/ui/src/input/Input.tsx new file mode 100644 index 00000000..efb05fe6 --- /dev/null +++ b/packages/ui/src/input/Input.tsx @@ -0,0 +1,52 @@ +import { clsx as cn } from 'clsx'; +import { type ComponentProps, type CSSProperties, forwardRef } from 'react'; +import { + type FontWeight, + fontSize as fontSizeToken, + type LineHeight, + parseResponsiveFontSize, + type ResponsiveFontSize, +} from '../token'; +import styles from './Input.module.css'; + +export interface InputProps extends ComponentProps<'input'> { + fontSize?: ResponsiveFontSize; + weight?: FontWeight; + lineHeight?: LineHeight; +} + +const Input = forwardRef( + ( + { + className, + children, + fontSize = 16, + weight = 'regular', + lineHeight = 'normal', + style: _style, + ...props + }, + ref, + ) => { + const { base, tablet, pc } = parseResponsiveFontSize(fontSize); + + const style = { + ..._style, + '--hcc-input-font-size': `${fontSizeToken[base]}px`, + ...(tablet !== undefined && { + '--hcc-tablet-input-font-size': `${fontSizeToken[tablet]}px`, + }), + ...(pc !== undefined && { + '--hcc-pc-input-font-size': `${fontSizeToken[pc]}px`, + }), + } as CSSProperties; + + return ( +
+ + {children} +
+ ); + }, +); +Input.displayName = 'Input'; diff --git a/packages/ui/src/input/index.ts b/packages/ui/src/input/index.ts new file mode 100644 index 00000000..ba9fe7eb --- /dev/null +++ b/packages/ui/src/input/index.ts @@ -0,0 +1 @@ +export * from './Input'; diff --git a/packages/ui/src/token/typography.ts b/packages/ui/src/token/typography.ts index 422ddb21..5ca05abf 100644 --- a/packages/ui/src/token/typography.ts +++ b/packages/ui/src/token/typography.ts @@ -29,3 +29,30 @@ export const lineHeight = { relaxed: 1.625, loose: 2, } as const; + +export type FontSize = keyof typeof fontSize; + +export type ResponsiveFontSize = + | FontSize + | [FontSize, FontSize?, FontSize?] + | { base: FontSize; tablet?: FontSize; pc?: FontSize }; + +export type FontWeight = keyof typeof fontWeight; + +export type LineHeight = keyof typeof lineHeight; + +export const parseResponsiveFontSize = (fontSize: ResponsiveFontSize) => { + let base: FontSize | undefined; + let tablet: FontSize | undefined; + let pc: FontSize | undefined; + + if (Array.isArray(fontSize)) { + [base, tablet, pc] = fontSize; + } else if (typeof fontSize === 'object' && fontSize !== null) { + ({ base, tablet, pc } = fontSize); + } else { + base = fontSize; + } + + return { base, tablet, pc }; +}; diff --git a/packages/ui/src/typography/Typography.module.css b/packages/ui/src/typography/Typography.module.css index e25900bc..1ed20fa7 100644 --- a/packages/ui/src/typography/Typography.module.css +++ b/packages/ui/src/typography/Typography.module.css @@ -18,6 +18,6 @@ @media (min-width: 1024px) { .typography { - font-size: var(--hcc-desktop-typography-font-size, var(--hcc-tablet-typography-font-size, var(--hcc-typography-font-size))); + font-size: var(--hcc-pc-typography-font-size, var(--hcc-tablet-typography-font-size, var(--hcc-typography-font-size))); } } diff --git a/packages/ui/src/typography/Typography.tsx b/packages/ui/src/typography/Typography.tsx index 6db18c31..b6c2a178 100644 --- a/packages/ui/src/typography/Typography.tsx +++ b/packages/ui/src/typography/Typography.tsx @@ -1,27 +1,20 @@ import { Slot } from '@radix-ui/react-slot'; -import { clsx } from 'clsx'; +import { clsx as cn } from 'clsx'; import { type ComponentProps, type CSSProperties, forwardRef } from 'react'; import { + type FontWeight, fontSize as fontSizeToken, fontWeight as fontWeightToken, + type LineHeight, lineHeight as lineHeightToken, + parseResponsiveFontSize, + type ResponsiveFontSize, } from '../token'; import styles from './Typography.module.css'; -export type FontSize = keyof typeof fontSizeToken; - -export type ResponsiveFontSize = - | FontSize - | [FontSize, FontSize?, FontSize?] - | { base: FontSize; tablet?: FontSize; desktop?: FontSize }; - -export type FontWeight = keyof typeof fontWeightToken; - -export type LineHeight = keyof typeof lineHeightToken; - export interface TypographyProps extends ComponentProps<'p'> { asChild?: boolean; - size?: ResponsiveFontSize; + fontSize?: ResponsiveFontSize; weight?: FontWeight; lineHeight?: LineHeight; } @@ -32,7 +25,7 @@ export const Typography = forwardRef( asChild, className, color, - size = 16, + fontSize = 16, weight = 'regular', lineHeight = 'normal', style: _style, @@ -41,18 +34,7 @@ export const Typography = forwardRef( ref, ) => { const Comp = asChild ? Slot : 'p'; - - let base: FontSize | undefined; - let tablet: FontSize | undefined; - let desktop: FontSize | undefined; - - if (Array.isArray(size)) { - [base, tablet, desktop] = size; - } else if (typeof size === 'object') { - ({ base, tablet, desktop } = size); - } else { - base = size; - } + const { base, tablet, pc } = parseResponsiveFontSize(fontSize); const style = { ..._style, @@ -61,15 +43,13 @@ export const Typography = forwardRef( ...(tablet !== undefined && { '--hcc-tablet-typography-font-size': `${fontSizeToken[tablet]}px`, }), - ...(desktop !== undefined && { - '--hcc-desktop-typography-font-size': `${fontSizeToken[desktop]}px`, + ...(pc !== undefined && { + '--hcc-pc-typography-font-size': `${fontSizeToken[pc]}px`, }), '--hcc-typography-font-weight': fontWeightToken[weight], '--hcc-typography-line-height': lineHeightToken[lineHeight], } as CSSProperties; - return ( - - ); + return ; }, ); From 99b6530eb23172985bf91450fbde7ce55b245e76 Mon Sep 17 00:00:00 2001 From: ohprettyhak Date: Thu, 14 Aug 2025 04:14:39 +0900 Subject: [PATCH 08/21] feat(ui): add Input component with responsive sizing and integrate into page --- apps/manager/src/app/auth/login/page.tsx | 18 ++++++-- packages/ui/src/button/Button.tsx | 6 +-- packages/ui/src/input/Input.module.css | 34 +++++++++++++++- packages/ui/src/input/Input.tsx | 52 ++++++++++++++++++++++-- 4 files changed, 99 insertions(+), 11 deletions(-) diff --git a/apps/manager/src/app/auth/login/page.tsx b/apps/manager/src/app/auth/login/page.tsx index 0e41242e..9a6f361c 100644 --- a/apps/manager/src/app/auth/login/page.tsx +++ b/apps/manager/src/app/auth/login/page.tsx @@ -1,4 +1,4 @@ -import { Badge, Button } from '@hcc/ui'; +import { Badge, Button, Input } from '@hcc/ui'; const Page = () => { return ( @@ -13,11 +13,21 @@ const Page = () => { 매니저 용
-
- -
+ ); }; diff --git a/packages/ui/src/button/Button.tsx b/packages/ui/src/button/Button.tsx index 49252420..29de386e 100644 --- a/packages/ui/src/button/Button.tsx +++ b/packages/ui/src/button/Button.tsx @@ -58,7 +58,7 @@ export const Button = forwardRef( }, ); -export const getHeight = (size: ButtonSize) => { +const getHeight = (size: ButtonSize) => { switch (size) { case 'xs': return 28; @@ -73,7 +73,7 @@ export const getHeight = (size: ButtonSize) => { } }; -export const getFontSize = (size: ButtonSize) => { +const getFontSize = (size: ButtonSize) => { switch (size) { case 'xs': return 12; @@ -88,7 +88,7 @@ export const getFontSize = (size: ButtonSize) => { } }; -export const getFontWeight = (size: ButtonSize) => { +const getFontWeight = (size: ButtonSize) => { switch (size) { case 'xs': return 'medium'; diff --git a/packages/ui/src/input/Input.module.css b/packages/ui/src/input/Input.module.css index 41824cbb..133ab332 100644 --- a/packages/ui/src/input/Input.module.css +++ b/packages/ui/src/input/Input.module.css @@ -1,5 +1,37 @@ -.wrapper {} +.wrapper { + display: flex; + align-items: center; + text-align: start; + + height: var(--hcc-input-height); + padding-inline: var(--hcc-input-padding-inline); + border-radius: var(--hcc-input-border-radius); + outline: var(--hcc-input-outline); + + --hcc-input-height: inherit; + --hcc-input-padding-inline: inherit; + --hcc-input-font-size: inherit; + --hcc-input-font-weight: inherit; + --hcc-input-line-height: inherit; + --hcc-input-border-radius: inherit; + --hcc-input-outline: inherit; +} .input { + width: 100%; + display: flex; + align-items: center; + text-align: inherit; + + color: inherit; + outline: 1px solid transparent; + border: none; + + font-size: var(--hcc-input-font-size); + font-weight: var(--hcc-input-font-weight); + line-height: var(--hcc-input-line-height); +} + +.label { } \ No newline at end of file diff --git a/packages/ui/src/input/Input.tsx b/packages/ui/src/input/Input.tsx index efb05fe6..5ef2c45a 100644 --- a/packages/ui/src/input/Input.tsx +++ b/packages/ui/src/input/Input.tsx @@ -1,27 +1,35 @@ import { clsx as cn } from 'clsx'; import { type ComponentProps, type CSSProperties, forwardRef } from 'react'; import { + colors, type FontWeight, fontSize as fontSizeToken, + fontWeight as fontWeightToken, type LineHeight, + lineHeight as lineHeightToken, parseResponsiveFontSize, type ResponsiveFontSize, } from '../token'; import styles from './Input.module.css'; -export interface InputProps extends ComponentProps<'input'> { +type InputSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; + +export interface InputProps extends Omit, 'size'> { + size?: InputSize; fontSize?: ResponsiveFontSize; weight?: FontWeight; lineHeight?: LineHeight; } -const Input = forwardRef( +export const Input = forwardRef( ( { className, children, + placeholder, + size = 'md', fontSize = 16, - weight = 'regular', + weight = 'medium', lineHeight = 'normal', style: _style, ...props @@ -32,6 +40,8 @@ const Input = forwardRef( const style = { ..._style, + '--hcc-input-height': `${getHeight(size)}px`, + '--hcc-input-padding-inline': `${getPadding(size)}px`, '--hcc-input-font-size': `${fontSizeToken[base]}px`, ...(tablet !== undefined && { '--hcc-tablet-input-font-size': `${fontSizeToken[tablet]}px`, @@ -39,14 +49,50 @@ const Input = forwardRef( ...(pc !== undefined && { '--hcc-pc-input-font-size': `${fontSizeToken[pc]}px`, }), + '--hcc-input-font-weight': fontWeightToken[weight], + '--hcc-input-line-height': lineHeightToken[lineHeight], + '--hcc-input-border-radius': '8px', + '--hcc-input-outline': `1px solid ${colors.neutral100}`, } as CSSProperties; return (
+ {children}
); }, ); + Input.displayName = 'Input'; + +const getHeight = (size: InputSize) => { + switch (size) { + case 'xs': + return 28; + case 'sm': + return 36; + case 'md': + return 44; + case 'lg': + return 52; + case 'xl': + return 60; + } +}; + +const getPadding = (size: InputSize) => { + switch (size) { + case 'xs': + return 8; + case 'sm': + return 10; + case 'md': + return 14; + case 'lg': + return 18; + case 'xl': + return 22; + } +}; From bd32083a7da405424c99f9c3812f061ddfee7a82 Mon Sep 17 00:00:00 2001 From: ohprettyhak Date: Thu, 14 Aug 2025 05:35:37 +0900 Subject: [PATCH 09/21] feat(ui): enhance Input component with label positioning and placeholder styling --- apps/manager/src/app/auth/login/page.tsx | 2 +- packages/ui/src/input/Input.module.css | 43 +++++++++++++++++++++--- packages/ui/src/input/Input.tsx | 42 +++++++++++++++++++++-- 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/apps/manager/src/app/auth/login/page.tsx b/apps/manager/src/app/auth/login/page.tsx index 9a6f361c..97951354 100644 --- a/apps/manager/src/app/auth/login/page.tsx +++ b/apps/manager/src/app/auth/login/page.tsx @@ -19,7 +19,7 @@ const Page = () => { , 'size'> { export const Input = forwardRef( ( { + id, className, children, placeholder, @@ -37,11 +38,13 @@ export const Input = forwardRef( ref, ) => { const { base, tablet, pc } = parseResponsiveFontSize(fontSize); + const isLabelVisible = size === 'lg' || size === 'xl'; const style = { ..._style, '--hcc-input-height': `${getHeight(size)}px`, '--hcc-input-padding-inline': `${getPadding(size)}px`, + '--hcc-input-placeholder-color': colors.neutral400, '--hcc-input-font-size': `${fontSizeToken[base]}px`, ...(tablet !== undefined && { '--hcc-tablet-input-font-size': `${fontSizeToken[tablet]}px`, @@ -53,12 +56,23 @@ export const Input = forwardRef( '--hcc-input-line-height': lineHeightToken[lineHeight], '--hcc-input-border-radius': '8px', '--hcc-input-outline': `1px solid ${colors.neutral100}`, + '--hcc-input-position': isLabelVisible ? getInputPosition(size) : '50%', + '--hcc-label-position': isLabelVisible ? getLabelPosition(size) : '50%', } as CSSProperties; return ( -
- - +
+ + {isLabelVisible && ( + + )} {children}
); @@ -96,3 +110,25 @@ const getPadding = (size: InputSize) => { return 22; } }; + +const getInputPosition = (size: InputSize) => { + switch (size) { + case 'lg': + return '12.5%'; + case 'xl': + return '15%'; + default: + return '50%'; + } +}; + +const getLabelPosition = (size: InputSize) => { + switch (size) { + case 'lg': + return '12.5%'; + case 'xl': + return '15%'; + default: + return '50%'; + } +}; From f2354931ea72e1333e24fb733a7069f5c973c829 Mon Sep 17 00:00:00 2001 From: ohprettyhak Date: Thu, 14 Aug 2025 05:42:04 +0900 Subject: [PATCH 10/21] feat(ui): update Input component for improved label positioning and padding --- apps/manager/src/app/auth/login/page.tsx | 2 +- packages/ui/src/input/Input.module.css | 16 +++++++++++----- packages/ui/src/input/Input.tsx | 14 +++++++------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/apps/manager/src/app/auth/login/page.tsx b/apps/manager/src/app/auth/login/page.tsx index 97951354..9a6f361c 100644 --- a/apps/manager/src/app/auth/login/page.tsx +++ b/apps/manager/src/app/auth/login/page.tsx @@ -19,7 +19,7 @@ const Page = () => { ( '--hcc-input-line-height': lineHeightToken[lineHeight], '--hcc-input-border-radius': '8px', '--hcc-input-outline': `1px solid ${colors.neutral100}`, - '--hcc-input-position': isLabelVisible ? getInputPosition(size) : '50%', + '--hcc-input-padding-top': isLabelVisible ? getInputPaddingTop(size) : '0', '--hcc-label-position': isLabelVisible ? getLabelPosition(size) : '50%', } as CSSProperties; @@ -111,23 +111,23 @@ const getPadding = (size: InputSize) => { } }; -const getInputPosition = (size: InputSize) => { +const getInputPaddingTop = (size: InputSize) => { switch (size) { case 'lg': - return '12.5%'; + return '10px'; case 'xl': - return '15%'; + return '18px'; default: - return '50%'; + return '0'; } }; const getLabelPosition = (size: InputSize) => { switch (size) { case 'lg': - return '12.5%'; + return '5px'; case 'xl': - return '15%'; + return '9px'; default: return '50%'; } From 8fda811f681dfb5425af9b9821b3b6ea3817f265 Mon Sep 17 00:00:00 2001 From: ohprettyhak Date: Thu, 14 Aug 2025 06:12:35 +0900 Subject: [PATCH 11/21] feat(ui): refine Input component styles and improve label positioning --- apps/manager/src/app/auth/login/page.tsx | 2 +- packages/ui/src/input/Input.module.css | 18 ++------ packages/ui/src/input/Input.tsx | 52 ++++++++++++------------ 3 files changed, 30 insertions(+), 42 deletions(-) diff --git a/apps/manager/src/app/auth/login/page.tsx b/apps/manager/src/app/auth/login/page.tsx index 9a6f361c..97951354 100644 --- a/apps/manager/src/app/auth/login/page.tsx +++ b/apps/manager/src/app/auth/login/page.tsx @@ -19,7 +19,7 @@ const Page = () => { , 'size'> { size?: InputSize; - fontSize?: ResponsiveFontSize; weight?: FontWeight; lineHeight?: LineHeight; } @@ -29,7 +25,6 @@ export const Input = forwardRef( children, placeholder, size = 'md', - fontSize = 16, weight = 'medium', lineHeight = 'normal', style: _style, @@ -37,7 +32,6 @@ export const Input = forwardRef( }, ref, ) => { - const { base, tablet, pc } = parseResponsiveFontSize(fontSize); const isLabelVisible = size === 'lg' || size === 'xl'; const style = { @@ -45,28 +39,17 @@ export const Input = forwardRef( '--hcc-input-height': `${getHeight(size)}px`, '--hcc-input-padding-inline': `${getPadding(size)}px`, '--hcc-input-placeholder-color': colors.neutral400, - '--hcc-input-font-size': `${fontSizeToken[base]}px`, - ...(tablet !== undefined && { - '--hcc-tablet-input-font-size': `${fontSizeToken[tablet]}px`, - }), - ...(pc !== undefined && { - '--hcc-pc-input-font-size': `${fontSizeToken[pc]}px`, - }), + '--hcc-input-font-size': `${getFontSize(size)}px`, '--hcc-input-font-weight': fontWeightToken[weight], '--hcc-input-line-height': lineHeightToken[lineHeight], '--hcc-input-border-radius': '8px', '--hcc-input-outline': `1px solid ${colors.neutral100}`, - '--hcc-input-padding-top': isLabelVisible ? getInputPaddingTop(size) : '0', - '--hcc-label-position': isLabelVisible ? getLabelPosition(size) : '50%', + '--hcc-input-padding-top': isLabelVisible ? `${getInputPaddingTop(size)}px` : '0px', + '--hcc-label-position': isLabelVisible ? `${getLabelPosition(size)}px` : '0px', } as CSSProperties; return ( -
+
{isLabelVisible && (
-
- - - -
+
); }; From c4482d85b371973cc74c7a5a9217cfd37d8c953d Mon Sep 17 00:00:00 2001 From: ohprettyhak Date: Thu, 14 Aug 2025 07:32:12 +0900 Subject: [PATCH 16/21] feat(ui): standardize font size for Input and Select components --- packages/ui/src/input/Input.tsx | 2 +- packages/ui/src/select/Select.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/input/Input.tsx b/packages/ui/src/input/Input.tsx index 86eb310a..e8a44c34 100644 --- a/packages/ui/src/input/Input.tsx +++ b/packages/ui/src/input/Input.tsx @@ -90,7 +90,7 @@ const getPadding = (size: InputSize) => { case 'lg': return 18; case 'xl': - return 22; + return 18; } }; diff --git a/packages/ui/src/select/Select.tsx b/packages/ui/src/select/Select.tsx index 39a387b2..920da10d 100644 --- a/packages/ui/src/select/Select.tsx +++ b/packages/ui/src/select/Select.tsx @@ -106,7 +106,7 @@ const getPadding = (size: SelectSize) => { case 'lg': return 18; case 'xl': - return 22; + return 18; } }; From be50e10ecf062b837a8378ca8da188c765712232 Mon Sep 17 00:00:00 2001 From: ohprettyhak Date: Thu, 14 Aug 2025 08:44:42 +0900 Subject: [PATCH 17/21] feat(ui): add name attributes to Input fields in LoginForm component --- apps/manager/src/app/auth/login/login-form.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/manager/src/app/auth/login/login-form.tsx b/apps/manager/src/app/auth/login/login-form.tsx index 61ca1169..0a75e850 100644 --- a/apps/manager/src/app/auth/login/login-form.tsx +++ b/apps/manager/src/app/auth/login/login-form.tsx @@ -32,9 +32,17 @@ export const LoginForm = () => { return (
- + Date: Thu, 14 Aug 2025 10:00:51 +0900 Subject: [PATCH 18/21] feat(ui): refactor Badge, Button, Input, and Select components to use ts-pattern for size and variant handling --- .../manager/src/app/auth/login/login-form.tsx | 6 +- apps/manager/src/app/layout.tsx | 7 +- packages/ui/package.json | 1 + packages/ui/src/badge/Badge.tsx | 71 +++++-------- packages/ui/src/button/Button.tsx | 63 ++++------- packages/ui/src/index.ts | 1 + packages/ui/src/input/Input.tsx | 100 +++++++----------- packages/ui/src/select/Select.tsx | 100 +++++++----------- packages/ui/src/toast/Toast.module.css | 3 + packages/ui/src/toast/Toast.tsx | 8 ++ packages/ui/src/toast/index.ts | 1 + packages/ui/src/token/typography.ts | 4 +- pnpm-lock.yaml | 14 +++ 13 files changed, 159 insertions(+), 220 deletions(-) create mode 100644 packages/ui/src/toast/Toast.module.css create mode 100644 packages/ui/src/toast/Toast.tsx create mode 100644 packages/ui/src/toast/index.ts diff --git a/apps/manager/src/app/auth/login/login-form.tsx b/apps/manager/src/app/auth/login/login-form.tsx index 0a75e850..44f2e954 100644 --- a/apps/manager/src/app/auth/login/login-form.tsx +++ b/apps/manager/src/app/auth/login/login-form.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Button, Input } from '@hcc/ui'; +import { Button, Input, toast } from '@hcc/ui'; import type { FormEvent } from 'react'; import { useLogin } from '~/api'; @@ -21,9 +21,9 @@ export const LoginForm = () => { }, onError: error => { if (error instanceof Error) { - alert(error.message); + toast.error(error.message); } else { - alert('로그인에 실패했습니다. 다시 시도해주세요.'); + toast.error('로그인에 실패했습니다. 다시 시도해주세요.'); } }, }, diff --git a/apps/manager/src/app/layout.tsx b/apps/manager/src/app/layout.tsx index 8e912676..762464fa 100644 --- a/apps/manager/src/app/layout.tsx +++ b/apps/manager/src/app/layout.tsx @@ -1,11 +1,13 @@ +import '@hcc/ui/styles.css'; +import '~/styles/globals.css'; + +import { Toaster } from '@hcc/ui'; import { Analytics } from '@vercel/analytics/next'; import type { Metadata } from 'next'; import type { PropsWithChildren } from 'react'; import { Layout } from '~/components/layout'; import { Pretendard } from './_fonts'; import { Provider } from './provider'; -import '~/styles/globals.css'; -import '@hcc/ui/styles.css'; export const metadata: Metadata = { title: '훕치치 매니저', @@ -20,6 +22,7 @@ const RootLayout = ({ children }: PropsWithChildren) => { {children} + ); diff --git a/packages/ui/package.json b/packages/ui/package.json index 1b71c627..7e2f5561 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -47,6 +47,7 @@ "dependencies": { "@radix-ui/react-slot": "^1.2.3", "clsx": "^2.1.1", + "sonner": "^2.0.7", "ts-pattern": "^5.8.0" } } diff --git a/packages/ui/src/badge/Badge.tsx b/packages/ui/src/badge/Badge.tsx index 46b031fd..e3ef61d6 100644 --- a/packages/ui/src/badge/Badge.tsx +++ b/packages/ui/src/badge/Badge.tsx @@ -1,6 +1,7 @@ import { clsx } from 'clsx'; import { type ComponentProps, type CSSProperties, forwardRef } from 'react'; -import { colors } from '../token'; +import { match } from 'ts-pattern'; +import { colors, type ResponsiveFontSize } from '../token'; import { Typography } from '../typography'; import styles from './Badge.module.css'; @@ -32,8 +33,8 @@ export const Badge = forwardRef(
@@ -44,46 +45,30 @@ export const Badge = forwardRef( }, ); -const getPadding = (size: BadgeSize) => { - switch (size) { - case 'sm': - return '6px 8px'; - case 'md': - return '8px 12px'; - case 'lg': - return '10px 16px'; - } -}; +const getPadding = (size: BadgeSize) => + match(size) + .with('sm', () => '4px 8px') + .with('md', () => '6px 12px') + .with('lg', () => '8px 16px') + .exhaustive(); -const getFontSize = (size: BadgeSize) => { - switch (size) { - case 'sm': - return 12; - case 'md': - return 14; - case 'lg': - return 18; - } -}; +const getFontSize = (size: BadgeSize) => + match(size) + .with('sm', () => 12) + .with('md', () => 14) + .with('lg', () => 16) + .exhaustive(); -const getFontColor = (variant: BadgeVariant) => { - switch (variant) { - case 'default': - return colors.neutral600; - case 'danger': - return colors.white; - case 'primary': - return colors.primary600; - } -}; +const getFontColor = (variant: BadgeVariant) => + match(variant) + .with('default', () => colors.neutral600) + .with('danger', () => colors.white) + .with('primary', () => colors.primary600) + .exhaustive(); -const getBackgroundColor = (variant: BadgeVariant) => { - switch (variant) { - case 'default': - return colors.neutral100; - case 'danger': - return colors.danger600; - case 'primary': - return colors.primary100; - } -}; +const getBackgroundColor = (variant: BadgeVariant) => + match(variant) + .with('default', () => colors.neutral100) + .with('danger', () => colors.danger600) + .with('primary', () => colors.primary100) + .exhaustive(); diff --git a/packages/ui/src/button/Button.tsx b/packages/ui/src/button/Button.tsx index 29de386e..51be1900 100644 --- a/packages/ui/src/button/Button.tsx +++ b/packages/ui/src/button/Button.tsx @@ -58,50 +58,29 @@ export const Button = forwardRef( }, ); -const getHeight = (size: ButtonSize) => { - switch (size) { - case 'xs': - return 28; - case 'sm': - return 36; - case 'md': - return 44; - case 'lg': - return 52; - case 'xl': - return 60; - } -}; +const getHeight = (size: ButtonSize) => + match(size) + .with('xs', () => 28) + .with('sm', () => 36) + .with('md', () => 44) + .with('lg', () => 52) + .with('xl', () => 60) + .exhaustive(); -const getFontSize = (size: ButtonSize) => { - switch (size) { - case 'xs': - return 12; - case 'sm': - return 14; - case 'md': - return 14; - case 'lg': - return 16; - case 'xl': - return 18; - } -}; +const getFontSize = (size: ButtonSize) => + match(size) + .with('xs', () => 12) + .with('sm', () => 14) + .with('md', () => 14) + .with('lg', () => 16) + .with('xl', () => 18) + .exhaustive(); -const getFontWeight = (size: ButtonSize) => { - switch (size) { - case 'xs': - return 'medium'; - case 'sm': - return 'medium'; - case 'md': - return 'semiBold'; - case 'lg': - return 'semiBold'; - case 'xl': - return 'semiBold'; - } -}; +const getFontWeight = (size: ButtonSize): keyof typeof fontWeightToken => + match(size) + .with('xs', 'sm', () => 'medium' as const) + .with('md', 'lg', 'xl', () => 'semibold' as const) + .exhaustive(); const getColorStyle = (color: ButtonColor, variant: ButtonVariant) => { const fontColor = match([color, variant]) diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 5f8bbc5b..342abfe2 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -2,5 +2,6 @@ export * from './badge'; export * from './button'; export * from './input'; export * from './select'; +export * from './toast'; export * from './token'; export * from './typography'; diff --git a/packages/ui/src/input/Input.tsx b/packages/ui/src/input/Input.tsx index e8a44c34..cfccabe0 100644 --- a/packages/ui/src/input/Input.tsx +++ b/packages/ui/src/input/Input.tsx @@ -1,5 +1,6 @@ -import { clsx as cn } from 'clsx'; +import { clsx } from 'clsx'; import { type ComponentProps, type CSSProperties, forwardRef } from 'react'; +import { match } from 'ts-pattern'; import { colors, type FontWeight, @@ -49,7 +50,7 @@ export const Input = forwardRef( } as CSSProperties; return ( -
+
{isLabelVisible && (