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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "vendor/colors-helper-tools"]
path = vendor/colors-helper-tools
url = https://github.com/citron03/colors-helper-tools.git
43 changes: 42 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
- `web`: 메인 Next.js 애플리케이션입니다.
- `packages`: 여러 앱에서 사용할 수 있는 공유 라이브러리, 컴포넌트 또는 유틸리티를 포함합니다.
- (이 디렉토리에 새 패키지를 추가할 수 있습니다.)
- `vendor`: Git Submodule 형태의 외부 저장소를 포함합니다.
- `colors-helper-tools`: 색상 관련 유틸 함수 저장소(서브모듈)입니다.

## 주요 기술

Expand All @@ -25,6 +27,19 @@
pnpm install
```

서브모듈까지 포함해서 처음 클론할 때는 아래 명령을 권장합니다.

```bash
git clone --recurse-submodules <repo-url>
```

이미 클론한 레포라면 다음을 1회 실행하세요.

```bash
git submodule init
git submodule update
```

### 2. 개발

`web` 애플리케이션의 개발 서버를 시작하려면 루트 디렉토리에서 다음 명령을 실행합니다.
Expand Down Expand Up @@ -73,6 +88,32 @@ pnpm test

5. 루트에서 `pnpm install`을 실행하여 새 패키지를 연결합니다.

## Git Submodule 운영 가이드

현재 이 레포에는 `vendor/colors-helper-tools` 서브모듈이 연결되어 있습니다.

```bash
git submodule status
```

서브모듈 최신 커밋 반영 방법:

```bash
cd vendor/colors-helper-tools
git pull origin main
cd ../..
git add vendor/colors-helper-tools
git commit -m "Update colors-helper-tools submodule"
```

현재 `.gitmodules` URL은 `https://github.com/citron03/colors-helper-tools.git` 입니다.
다른 저장소를 사용하려면 아래 명령으로 변경하세요.

```bash
git submodule set-url vendor/colors-helper-tools <your-remote-repo-url>
git submodule sync --recursive
```

## Blog / 블로그

이 레포에서 배운 내용들을 제 블로그(https://citron031.tistory.com/) 에 정리하고 있습니다. 관심 있으시면 놀러와서 읽어주시고 피드백 남겨주세요.
이 레포에서 배운 내용들을 제 블로그(https://citron031.tistory.com/) 에 정리하고 있습니다. 관심 있으시면 놀러와서 읽어주시고 피드백 남겨주세요.
37 changes: 37 additions & 0 deletions apps/web/app/api/submodule-colors/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { NextResponse } from 'next/server';

import {
complementaryColorHex,
darkenHex,
lightenHex,
pasteltoneHex,
} from '../../../../../vendor/colors-helper-tools/packages/colors-helper-tools/src';

type Action = 'pastel' | 'complementary' | 'lighten' | 'darken';

function isHexColor(value: string) {
return /^#[0-9a-fA-F]{6}$/.test(value);
}

export function GET(req: Request) {
const { searchParams } = new URL(req.url);
const action = searchParams.get('action') as Action | null;
const color = searchParams.get('color') ?? '#3b82f6';

if (!action) {
return NextResponse.json({ error: 'action is required' }, { status: 400 });
}

if (action !== 'pastel' && !isHexColor(color)) {
return NextResponse.json({ error: 'color must be #RRGGBB format' }, { status: 400 });
}

let nextColor = color;

if (action === 'pastel') nextColor = pasteltoneHex();
if (action === 'complementary') nextColor = complementaryColorHex(color);
if (action === 'lighten') nextColor = lightenHex(color, 0.1);
if (action === 'darken') nextColor = darkenHex(color, 0.1);

return NextResponse.json({ color: nextColor });
}
1 change: 1 addition & 0 deletions apps/web/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default function Home() {
<Link href="/index-db-test">IndexDB Page</Link>
<Link href="/events-demo">React Event Wrapper Page</Link>
<Link href="/activity-demo">React Activity Test Page</Link>
<Link href="/submodule-colors">Submodule Colors Demo</Link>
<div>
<InstallButton />
</div>
Expand Down
133 changes: 133 additions & 0 deletions apps/web/app/submodule-colors/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
'use client';

import { useState } from 'react';

const DEFAULT_COLOR = '#3b82f6';

async function requestColor(
action: 'pastel' | 'complementary' | 'lighten' | 'darken',
color: string,
) {
const query = new URLSearchParams({ action, color });
const res = await fetch(`/api/submodule-colors?${query.toString()}`);

if (!res.ok) {
throw new Error(`Failed to get color: ${res.status}`);
}

const data = (await res.json()) as { color: string };
return data.color;
}

export default function SubmoduleColorsPage() {
const [baseColor, setBaseColor] = useState(DEFAULT_COLOR);
const [previousColor, setPreviousColor] = useState(DEFAULT_COLOR);
const [lastAction, setLastAction] = useState<string>('init');
const [loading, setLoading] = useState(false);

const runAction = async (action: 'pastel' | 'complementary' | 'lighten' | 'darken') => {
try {
setLoading(true);
setPreviousColor(baseColor);
const nextColor = await requestColor(action, baseColor);
setBaseColor(nextColor);
setLastAction(action);
} finally {
setLoading(false);
}
};

return (
<main style={{ padding: 24 }}>
<h1>Submodule Colors Demo</h1>
<p>
`colors-helper-tools`를 Git Submodule로 연동한 뒤, 라이브러리 함수를 실제 버튼 액션에 연결한
예제입니다.
</p>

<section
style={{
alignItems: 'center',
display: 'flex',
flexWrap: 'wrap',
gap: 8,
marginTop: 12,
}}
>
<input
aria-label="base color"
disabled={loading}
onChange={(e) => setBaseColor(e.target.value)}
type="color"
value={baseColor}
/>
<button disabled={loading} onClick={() => runAction('pastel')} type="button">
랜덤 파스텔
</button>
<button disabled={loading} onClick={() => runAction('complementary')} type="button">
보색 적용
</button>
<button disabled={loading} onClick={() => runAction('lighten')} type="button">
밝게
</button>
<button disabled={loading} onClick={() => runAction('darken')} type="button">
어둡게
</button>
<button disabled={loading} onClick={() => setBaseColor(DEFAULT_COLOR)} type="button">
리셋
</button>
</section>

<p style={{ marginTop: 10 }}>
현재 색상: <code>{baseColor}</code> / 이전 색상: <code>{previousColor}</code> / 마지막 액션:{' '}
<code>{lastAction}</code> {loading ? '(계산 중)' : ''}
</p>

<div
style={{
display: 'grid',
gap: 12,
gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))',
marginTop: 12,
}}
>
<section
style={{
border: '1px solid #e5e7eb',
borderRadius: 10,
padding: 12,
}}
>
<div
style={{
backgroundColor: previousColor,
borderRadius: 8,
height: 80,
width: '100%',
}}
/>
<p style={{ margin: '10px 0 0 0', fontWeight: 600 }}>이전 색상</p>
<code>{previousColor}</code>
</section>
<section
style={{
border: '1px solid #e5e7eb',
borderRadius: 10,
padding: 12,
}}
>
<div
style={{
backgroundColor: baseColor,
borderRadius: 8,
height: 80,
width: '100%',
}}
/>
<p style={{ margin: '10px 0 0 0', fontWeight: 600 }}>현재 색상</p>
<code>{baseColor}</code>
</section>
</div>
</main>
);
}
7 changes: 4 additions & 3 deletions apps/web/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import withMdxCreate from '@next/mdx';
import { createVanillaExtractPlugin } from '@vanilla-extract/next-plugin';
import type { NextConfig } from 'next';

const withMDX = withMdxCreate({
// Optionally provide remark and rehype plugins
Expand All @@ -16,10 +15,12 @@ const withMDX = withMdxCreate({

const withVanillaExtract = createVanillaExtractPlugin();

const nextConfig: NextConfig = {
const nextConfig = {
/* config options here */
reactCompiler: true,
experimental: {},
experimental: {
externalDir: true,
},
// useEffect 두 번 실행 방지
reactStrictMode: false,
};
Expand Down
15 changes: 10 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
"catalog:check": "node ./scripts/convert-to-catalog.mjs --dry",
"catalog:apply": "node ./scripts/convert-to-catalog.mjs",

"submodule:status": "git submodule status",
"submodule:sync": "git submodule sync --recursive",
"submodule:init": "git submodule update --init --recursive",
"submodule:update": "git submodule update --init --recursive --remote",

"commit": "pnpm --filter scripts commit",
"ready": "pnpm --filter scripts ready",
"prepare": "husky",
Expand All @@ -56,7 +61,7 @@
"@loadable/component": "*",
"@mdx-js/loader": "*",
"@mdx-js/react": "*",
"@next/mdx": "*",
"@next/mdx": "catalog:mdx",
"@storybook/addon-essentials": "*",
"@storybook/addon-interactions": "*",
"@storybook/builder-vite": "*",
Expand All @@ -82,7 +87,7 @@
"babel-plugin-react-compiler": "*",
"chalk": "*",
"eslint": "^9.39.1",
"eslint-config-next": "^16.0.3",
"eslint-config-next": "catalog:eslint",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.4",
Expand All @@ -96,12 +101,12 @@
"js-yaml": "^4.1.1",
"jsdom": "*",
"lint-staged": "^16.2.6",
"next": "*",
"next": "catalog:framework",
"patch-package": "^8.0.1",
"prettier": "^3.6.2",
"react": "^19.2.0",
"react": "catalog:framework",
"react-color": "*",
"react-dom": "^19.2.0",
"react-dom": "catalog:framework",
"storybook": "*",
"stylelint": "^16.25.0",
"stylelint-config-prettier": "^9.0.5",
Expand Down
Loading