Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"@eslint/js": "^9.33.0",
"@pinback/eslint-config": "workspace:*",
"@pinback/typescript-config": "workspace:*",
"@pinback/tailwind-config": "workspace:*",
"@pinback/design-system": "workspace:*",
"@tailwindcss/vite": "^4.1.12",
"@types/react": "^19.1.10",
"@types/react-dom": "^19.1.7",
Expand Down
43 changes: 1 addition & 42 deletions apps/client/src/App.css
Original file line number Diff line number Diff line change
@@ -1,42 +1 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}

.card {
padding: 2em;
}

.read-the-docs {
color: #888;
}
@import '@pinback/tailwind-config/shared-styles.css';
2 changes: 2 additions & 0 deletions apps/landing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"@eslint/js": "^9.33.0",
"@pinback/eslint-config": "workspace:*",
"@pinback/typescript-config": "workspace:*",
"@pinback/tailwind-config": "workspace:*",
"@pinback/design-system": "workspace:*",
"@tailwindcss/vite": "^4.1.12",
"@types/react": "^19.1.10",
"@types/react-dom": "^19.1.7",
Expand Down
43 changes: 1 addition & 42 deletions apps/landing/src/App.css
Original file line number Diff line number Diff line change
@@ -1,42 +1 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}

.card {
padding: 2em;
}

.read-the-docs {
color: #888;
}
@import '@pinback/tailwind-config/shared-styles.css';
21 changes: 21 additions & 0 deletions packages/design-system/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "./src/styles.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { config } from "@repo/eslint-config/react-internal";
import { config } from "@pinback/eslint-config/react-internal";

/** @type {import("eslint").Linter.Config} */
export default config;
38 changes: 38 additions & 0 deletions packages/design-system/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@pinback/design-system",
"version": "0.0.0",
"private": true,
"exports": {
"./ui": "./src/components/index.ts",
"./icons": "./src/icons/index.ts",
"./styles": "./styles.css",
"./utils": "./src/lib/index.ts"
},
"scripts": {
"lint": "eslint .",
"build": "vite build",
"check-types": "tsc --noEmit",
"generate:component": "turbo gen react-component"
},
"devDependencies": {
"@pinback/eslint-config": "workspace:*",
"@pinback/tailwind-config": "workspace:*",
"@pinback/typescript-config": "workspace:*",
"@types/node": "^22.15.3",
"@types/react": "19.1.0",
"@types/react-dom": "19.1.1",
"eslint": "^9.33.0",
"tailwindcss": "^4.1.12",
"typescript": "5.9.2"
},
"dependencies": {
"@radix-ui/react-slot": "^1.2.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.540.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"tailwind-merge": "^3.3.1",
"tw-animate-css": "^1.3.7"
}
}
5 changes: 5 additions & 0 deletions packages/design-system/src/components/button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Button = () => {
return <div>Button</div>;
};

export default Button;
Comment on lines +1 to +5
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

div 대신 button 요소 사용 + forwardRef/props 지원으로 접근성·재사용성 확보

디자인 시스템 컴포넌트의 베이스로 div 대신 시맨틱한 button을 사용하고, children/className/…propsforwardRef를 수용하는 형태가 필요합니다. 폼 내 기본 submit 방지를 위해 기본 type="button"도 지정하는 것을 권장합니다.

아래처럼 최소 안전/확장 가능한 형태로 바꾸는 것을 제안드립니다:

-const Button = () => {
-  return <div>Button</div>;
-};
-
-export default Button;
+import * as React from 'react';
+
+export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>;
+
+export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
+  ({ type = 'button', children, ...props }, ref) => {
+    return (
+      <button ref={ref} type={type} {...props}>
+        {children}
+      </button>
+    );
+  }
+);
+
+Button.displayName = 'Button';

1 change: 1 addition & 0 deletions packages/design-system/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Button } from './button/Button';
Empty file.
1 change: 1 addition & 0 deletions packages/design-system/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './utils';
110 changes: 110 additions & 0 deletions packages/design-system/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { type ClassValue, clsx } from 'clsx';
import { extendTailwindMerge } from 'tailwind-merge';

const twMerge = extendTailwindMerge({
extend: {
theme: {
text: [
'head1',
'head2',
'head3',
'head4',
'head5',
'head6',
'sub1-sb',
'sub1-m',
'sub2-b',
'sub2-sb',
'sub3-b',
'sub3-sb',
'sub4-sb',
'sub5-sb',
'body1-m',
'body1-r',
'body2-m',
'body2-r',
'body3-r',
'body4-r',
'caption1-sb',
'caption1-m',
'caption2-sb',
'caption2-m',
],

color: [
// main
'main0',
'main100',
'main200',
'main300',
'main400',
'main500',
'main600',

// gradient
'gradient-start',
'gradient-end',

// secondary
'secondary',

// grayscale
'gray0',
'gray100',
'gray200',
'gray300',
'gray400',
'gray500',
'gray600',
'gray700',
'gray800',
'gray900',

// bg
'white-bg',
'gray-bg',

// font
'font-black-1',
'font-gray-2',
'font-gray-3',
'font-ltgray-4',
'font-ltgray-5',

// state
'error',
'success',

// category text
'category-red-text',
'category-purple-text',
'category-navyblue-text',
'category-skyblue-text',
'category-emerald-text',
'category-navygreen-text',
'category-khaki-text',
'category-orange-text',
'category-amber-text',
'category-maroon-text',

// category bg
'category-red-bg',
'category-purple-bg',
'category-navyblue-bg',
'category-skyblue-bg',
'category-emerald-bg',
'category-navygreen-bg',
'category-khaki-bg',
'category-orange-bg',
'category-amber-bg',
'category-maroon-bg',
],

shadow: ['popup'],
},
},
});
Comment on lines +4 to +106
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

tailwind-merge 확장 키가 기본 스키마와 불일치합니다(colors/fontSize/boxShadow 사용 권장)

tailwind-merge의 theme 키는 Tailwind 테마 키와 매핑됩니다. 현재 text, color, shadow로 지정되어 있어 의도한 병합 규칙이 적용되지 않을 가능성이 큽니다. 일반적으로 다음 키를 사용해야 합니다:

  • 글자 크기 토큰: fontSize
  • 색상 토큰: colors
  • 그림자 토큰: boxShadow

키만 교체해도 text-*, bg-*, shadow-* 계열의 충돌 해소가 기대 동작에 가까워집니다.

 const twMerge = extendTailwindMerge({
   extend: {
     theme: {
-      text: [
+      fontSize: [
         'head1',
         'head2',
         'head3',
         'head4',
         'head5',
         'head6',
         'sub1-sb',
         'sub1-m',
         'sub2-b',
         'sub2-sb',
         'sub3-b',
         'sub3-sb',
         'sub4-sb',
         'sub5-sb',
         'body1-m',
         'body1-r',
         'body2-m',
         'body2-r',
         'body3-r',
         'body4-r',
         'caption1-sb',
         'caption1-m',
         'caption2-sb',
         'caption2-m',
       ],
 
-      color: [
+      colors: [
         // main
         'main0',
         'main100',
         'main200',
         'main300',
         'main400',
         'main500',
         'main600',
 
         // gradient
         'gradient-start',
         'gradient-end',
 
         // secondary
         'secondary',
 
         // grayscale
         'gray0',
         'gray100',
         'gray200',
         'gray300',
         'gray400',
         'gray500',
         'gray600',
         'gray700',
         'gray800',
         'gray900',
 
         // bg
         'white-bg',
         'gray-bg',
 
         // font
         'font-black-1',
         'font-gray-2',
         'font-gray-3',
         'font-ltgray-4',
         'font-ltgray-5',
 
         // state
         'error',
         'success',
 
         // category text
         'category-red-text',
         'category-purple-text',
         'category-navyblue-text',
         'category-skyblue-text',
         'category-emerald-text',
         'category-navygreen-text',
         'category-khaki-text',
         'category-orange-text',
         'category-amber-text',
         'category-maroon-text',
 
         // category bg
         'category-red-bg',
         'category-purple-bg',
         'category-navyblue-bg',
         'category-skyblue-bg',
         'category-emerald-bg',
         'category-navygreen-bg',
         'category-khaki-bg',
         'category-orange-bg',
         'category-amber-bg',
         'category-maroon-bg',
       ],
 
-      shadow: ['popup'],
+      boxShadow: ['popup'],
     },
   },
 });

참고: text-*(font-size)와 text-*(color)가 접두사가 같아 충돌 그룹이 다릅니다. 위 교정으로 fontSize/colors 각각의 그룹에 토큰이 주입되어 올바르게 병합됩니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const twMerge = extendTailwindMerge({
extend: {
theme: {
text: [
'head1',
'head2',
'head3',
'head4',
'head5',
'head6',
'sub1-sb',
'sub1-m',
'sub2-b',
'sub2-sb',
'sub3-b',
'sub3-sb',
'sub4-sb',
'sub5-sb',
'body1-m',
'body1-r',
'body2-m',
'body2-r',
'body3-r',
'body4-r',
'caption1-sb',
'caption1-m',
'caption2-sb',
'caption2-m',
],
color: [
// main
'main0',
'main100',
'main200',
'main300',
'main400',
'main500',
'main600',
// gradient
'gradient-start',
'gradient-end',
// secondary
'secondary',
// grayscale
'gray0',
'gray100',
'gray200',
'gray300',
'gray400',
'gray500',
'gray600',
'gray700',
'gray800',
'gray900',
// bg
'white-bg',
'gray-bg',
// font
'font-black-1',
'font-gray-2',
'font-gray-3',
'font-ltgray-4',
'font-ltgray-5',
// state
'error',
'success',
// category text
'category-red-text',
'category-purple-text',
'category-navyblue-text',
'category-skyblue-text',
'category-emerald-text',
'category-navygreen-text',
'category-khaki-text',
'category-orange-text',
'category-amber-text',
'category-maroon-text',
// category bg
'category-red-bg',
'category-purple-bg',
'category-navyblue-bg',
'category-skyblue-bg',
'category-emerald-bg',
'category-navygreen-bg',
'category-khaki-bg',
'category-orange-bg',
'category-amber-bg',
'category-maroon-bg',
],
shadow: ['popup'],
},
},
});
const twMerge = extendTailwindMerge({
extend: {
theme: {
fontSize: [
'head1',
'head2',
'head3',
'head4',
'head5',
'head6',
'sub1-sb',
'sub1-m',
'sub2-b',
'sub2-sb',
'sub3-b',
'sub3-sb',
'sub4-sb',
'sub5-sb',
'body1-m',
'body1-r',
'body2-m',
'body2-r',
'body3-r',
'body4-r',
'caption1-sb',
'caption1-m',
'caption2-sb',
'caption2-m',
],
colors: [
// main
'main0',
'main100',
'main200',
'main300',
'main400',
'main500',
'main600',
// gradient
'gradient-start',
'gradient-end',
// secondary
'secondary',
// grayscale
'gray0',
'gray100',
'gray200',
'gray300',
'gray400',
'gray500',
'gray600',
'gray700',
'gray800',
'gray900',
// bg
'white-bg',
'gray-bg',
// font
'font-black-1',
'font-gray-2',
'font-gray-3',
'font-ltgray-4',
'font-ltgray-5',
// state
'error',
'success',
// category text
'category-red-text',
'category-purple-text',
'category-navyblue-text',
'category-skyblue-text',
'category-emerald-text',
'category-navygreen-text',
'category-khaki-text',
'category-orange-text',
'category-amber-text',
'category-maroon-text',
// category bg
'category-red-bg',
'category-purple-bg',
'category-navyblue-bg',
'category-skyblue-bg',
'category-emerald-bg',
'category-navygreen-bg',
'category-khaki-bg',
'category-orange-bg',
'category-amber-bg',
'category-maroon-bg',
],
boxShadow: ['popup'],
},
},
});
🤖 Prompt for AI Agents
packages/design-system/src/lib/utils.ts lines 4-106: the extendTailwindMerge
theme keys use nonstandard names (text/color/shadow) so tailwind-merge won’t map
them to Tailwind groups; replace those keys with the correct Tailwind theme
keys—use fontSize for text tokens, colors for color tokens, and boxShadow for
shadow tokens—so that the custom tokens are injected into the proper collision
groups (also ensure token names match the expected prefixes like text- and bg-
where applicable).


export const cn = (...inputs: ClassValue[]) => {
return twMerge(clsx(inputs));
};
Loading