atomsystem 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/add.js ADDED
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+
3
+ import inquirer from "inquirer";
4
+ import fs from "fs";
5
+ import path from "path";
6
+ import { copyFileSync } from "fs";
7
+ import { fileURLToPath } from "url";
8
+
9
+ const __dirname = fileURLToPath(new URL(".", import.meta.url));
10
+
11
+ // 컴포넌트 템플릿이 위치한 디렉토리
12
+ const templateDir = path.join(__dirname, "templates");
13
+
14
+ // 사용할 수 있는 컴포넌트 목록
15
+ const components = [
16
+ "Button",
17
+ "Card",
18
+ "CodeBlock",
19
+ "GlobalNav",
20
+ "Input",
21
+ "Theme",
22
+ ]; // 여기 추가할 컴포넌트 목록
23
+
24
+ // 명령어에서 컴포넌트 이름 가져오기
25
+ const args = process.argv.slice(2); // 'npx atomsystem-add Button -> [Button]'
26
+ const component = args[0] || "all"; // '사용자가 입력하지 않으면 기본적으로 'all''
27
+
28
+ function addComponent(comp) {
29
+ const componentPath = path.join(templateDir, `${comp}/${comp}.tsx`);
30
+ const targetDir = path.join(process.cwd(), "app", "components", comp);
31
+ const targetPath = path.join(targetDir, `${comp}.tsx`);
32
+
33
+ if (fs.existsSync(componentPath)) {
34
+ // 대상 디렉토리가 없으면 생성
35
+ if (!fs.existsSync(targetDir)) {
36
+ fs.mkdirSync(targetDir, { recursive: true });
37
+ console.log(`Created directory: ${targetDir}`);
38
+ }
39
+ copyFileSync(componentPath, targetPath);
40
+ console.log(`${comp} component has been added to ${targetPath}`);
41
+ } else {
42
+ console.log(`Component ${comp} does not exist in the atomsystem.`);
43
+ }
44
+ }
45
+
46
+ if (component == "all") {
47
+ console.log("Adding all components...");
48
+
49
+ components.forEach((comp) => {
50
+ addComponent(comp);
51
+ });
52
+ } else {
53
+ // 특정 컴포넌트 추가
54
+ addComponent(component);
55
+ }
56
+
57
+ // "all" 이면 모든 컴포넌트를 추가
58
+
59
+ // 사용자에게 추가할 컴포넌트를 선택하도록 묻습니다.
60
+ // inquirer
61
+ // .prompt([
62
+ // {
63
+ // type: "list",
64
+ // name: "component",
65
+ // message: "Which component would you like to add?",
66
+ // choices: ["all", ...components], // 'all'을 첫 번째로 추가
67
+ // },
68
+ // ])
69
+ // .then((answers) => {
70
+ // const { component } = answers;
71
+
72
+ // // 'all'이 선택되면 모든 컴포넌트를 추가합니다.
73
+ // if (component === "all") {
74
+ // console.log("Adding all components...");
75
+
76
+ // components.forEach((comp) => {
77
+ // const componentPath = path.join(templateDir, `${comp}/${comp}.tsx`);
78
+ // const targetDir = path.join(process.cwd(), "app", "components", comp);
79
+ // const targetPath = path.join(targetDir, `${comp}.tsx`);
80
+
81
+ // if (fs.existsSync(componentPath)) {
82
+ // // 대상 디렉토리가 없으면 생성
83
+ // if (!fs.existsSync(targetDir)) {
84
+ // fs.mkdirSync(targetDir, { recursive: true });
85
+ // console.log(`Created directory: ${targetDir}`);
86
+ // }
87
+
88
+ // copyFileSync(componentPath, targetPath);
89
+ // console.log(`${comp} component has been added to ${targetPath}`);
90
+ // } else {
91
+ // console.log(
92
+ // `Component ${comp} does not exist in the templates directory.`
93
+ // );
94
+ // }
95
+ // });
96
+ // } else {
97
+ // // 개별 컴포넌트 추가
98
+ // const componentPath = path.join(
99
+ // templateDir,
100
+ // `${component}/${component}.tsx`
101
+ // );
102
+ // const targetDir = path.join(
103
+ // process.cwd(),
104
+ // "app",
105
+ // "components",
106
+ // component
107
+ // );
108
+ // const targetPath = path.join(targetDir, `${component}.tsx`);
109
+
110
+ // if (fs.existsSync(componentPath)) {
111
+ // // 대상 디렉토리가 없으면 생성
112
+ // if (!fs.existsSync(targetDir)) {
113
+ // fs.mkdirSync(targetDir, { recursive: true });
114
+ // console.log(`Created directory: ${targetDir}`);
115
+ // }
116
+
117
+ // copyFileSync(componentPath, targetPath);
118
+ // console.log(`${component} component has been added to ${targetPath}`);
119
+ // } else {
120
+ // console.log(
121
+ // `Component ${component} does not exist in the templates directory.`
122
+ // );
123
+ // }
124
+ // }
125
+ // })
126
+ // .catch((error) => {
127
+ // console.error("Error:", error);
128
+ // });
package/cli/init.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import inquirer from "inquirer"; // require -> import
3
- import { exec } from "child_process"; // require -> import
2
+ import inquirer from "inquirer";
3
+ import { exec } from "child_process";
4
+ import fs from "fs"; // 파일 시스템 작업을 위한 모듈
5
+ import path from "path"; // 경로 작업을 위한 모듈
4
6
 
5
7
  // 사용자에게 질문을 던져서 Next.js와 React를 설치할지 물어봅니다.
6
8
  inquirer
@@ -25,6 +27,85 @@ inquirer
25
27
  } else {
26
28
  console.log(stdout);
27
29
  console.log("Installation complete!");
30
+
31
+ // package.json에 scripts 추가
32
+ const packageJsonPath = path.join(process.cwd(), "package.json");
33
+ fs.readFile(packageJsonPath, "utf8", (err, data) => {
34
+ if (err) {
35
+ console.error("Error reading package.json:", err);
36
+ return;
37
+ }
38
+
39
+ const packageJson = JSON.parse(data);
40
+
41
+ // scripts 항목 추가
42
+ packageJson.scripts = packageJson.scripts || {};
43
+ packageJson.scripts.dev = "next dev";
44
+ packageJson.scripts.build = "next build";
45
+ packageJson.scripts.start = "next start";
46
+ packageJson.scripts.lint = "next lint";
47
+
48
+ // 수정된 package.json을 덮어씁니다.
49
+ fs.writeFile(
50
+ packageJsonPath,
51
+ JSON.stringify(packageJson, null, 2),
52
+ (err) => {
53
+ if (err) {
54
+ console.error("Error updating package.json:", err);
55
+ } else {
56
+ console.log("Updated package.json with scripts.");
57
+ }
58
+ }
59
+ );
60
+ });
61
+
62
+ // app 디렉토리 생성 및 layout.tsx, page.tsx 파일 추가
63
+ const appDir = path.join(process.cwd(), "app");
64
+ const layoutPath = path.join(appDir, "layout.tsx");
65
+ const pagePath = path.join(appDir, "page.tsx");
66
+
67
+ // app 디렉토리가 없으면 생성
68
+ if (!fs.existsSync(appDir)) {
69
+ fs.mkdirSync(appDir);
70
+ }
71
+
72
+ // layout.tsx 파일 내용
73
+ const layoutContent = `export default function RootLayout({
74
+ children,
75
+ }: {
76
+ children: React.ReactNode
77
+ }) {
78
+ return (
79
+ <html lang="en">
80
+ <body>{children}</body>
81
+ </html>
82
+ )
83
+ }
84
+ `;
85
+
86
+ // page.tsx 파일 내용
87
+ const pageContent = `export default function Page() {
88
+ return <h1>Hello, Next.js!</h1>
89
+ }
90
+ `;
91
+
92
+ // layout.tsx 파일 생성
93
+ fs.writeFile(layoutPath, layoutContent, (err) => {
94
+ if (err) {
95
+ console.error("Error creating layout.tsx:", err);
96
+ } else {
97
+ console.log("Created layout.tsx.");
98
+ }
99
+ });
100
+
101
+ // page.tsx 파일 생성
102
+ fs.writeFile(pagePath, pageContent, (err) => {
103
+ if (err) {
104
+ console.error("Error creating page.tsx:", err);
105
+ } else {
106
+ console.log("Created page.tsx.");
107
+ }
108
+ });
28
109
  }
29
110
  }
30
111
  );
package/package.json CHANGED
@@ -1,17 +1,15 @@
1
1
  {
2
2
  "name": "atomsystem",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "type": "module",
5
5
  "bin": {
6
- "atomsystem-init": "cli/init.js"
6
+ "atomsystem-init": "cli/init.js",
7
+ "atomsystem-add": "cli/add.js"
7
8
  },
8
9
  "files": [
9
- "bin",
10
- "test",
11
10
  "token.json",
12
11
  "tokens",
13
12
  "build",
14
- "app/component",
15
13
  "tailwind.config.ts"
16
14
  ],
17
15
  "scripts": {
@@ -1,46 +0,0 @@
1
- "use client";
2
- import clsx from "clsx";
3
-
4
- export const Button = ({
5
- children,
6
- type,
7
- style,
8
- disabled,
9
- }: {
10
- children: React.ReactNode;
11
- type: "primary" | "secondary" | "success" | "danger" | "gray";
12
- style?: React.CSSProperties;
13
- disabled?: boolean;
14
- }) => {
15
- return (
16
- <button
17
- className={clsx(
18
- "px-4 py-2 rounded-common text-button-text transition-all",
19
- {
20
- "bg-button-primary-standard hover:bg-button-primary-hover disabled:bg-button-primary-disabled":
21
- type == "primary",
22
- },
23
- {
24
- "bg-button-secondary-standard hover:bg-button-secondary-hover disabled:bg-button-secondary-disabled ":
25
- type == "secondary",
26
- },
27
- {
28
- "bg-button-success-standard hover:bg-button-success-hover disabled:bg-button-success-disabled":
29
- type == "success",
30
- },
31
- {
32
- "bg-button-danger-standard hover:bg-button-danger-hover disabled:bg-button-danger-disabled":
33
- type == "danger",
34
- },
35
- {
36
- "bg-button-gray-standard hover:bg-button-gray-hover disabled:bg-button-gray-disabled":
37
- type == "gray",
38
- }
39
- )}
40
- style={style}
41
- disabled={disabled}
42
- >
43
- {children}
44
- </button>
45
- );
46
- };
@@ -1,57 +0,0 @@
1
- import React from "react";
2
-
3
- export const Card = ({
4
- children,
5
- type = "standard",
6
- className = "",
7
- }: {
8
- children: React.ReactNode;
9
- className?: string;
10
- type?: "standard" | "group";
11
- }) => {
12
- // CardHeader와 CardContent의 존재 여부 확인
13
- const hasCardContent = React.Children.toArray(children).some(
14
- (child) =>
15
- React.isValidElement(child) &&
16
- (child.type === CardHeader ||
17
- child.type === CardContent ||
18
- child.type === CardFooter)
19
- );
20
- return (
21
- <div
22
- className={`${type == "standard" ? "bg-fill-card border-fill-border" : type == "group" ? "bg-fill-group border-none" : ""} ${!hasCardContent ? "p-4" : ""} rounded border ${className} overflow-auto`}
23
- >
24
- {children}
25
- </div>
26
- );
27
- };
28
-
29
- export const CardHeader = ({
30
- children,
31
- noMargin,
32
- }: {
33
- children: React.ReactNode;
34
- noMargin?: boolean;
35
- }) => {
36
- return <div className={`${noMargin ? "p-0" : "p-4"}`}>{children}</div>;
37
- };
38
-
39
- export const CardContent = ({
40
- children,
41
- noMargin,
42
- }: {
43
- children: React.ReactNode;
44
- noMargin?: boolean;
45
- }) => {
46
- return <div className={`${noMargin ? "p-0" : "p-4"}`}>{children}</div>;
47
- };
48
-
49
- export const CardFooter = ({
50
- children,
51
- noMargin,
52
- }: {
53
- children: React.ReactNode;
54
- noMargin?: boolean;
55
- }) => {
56
- return <div className={`${noMargin ? "p-0" : "p-4"}`}>{children}</div>;
57
- };
@@ -1,30 +0,0 @@
1
- "use client";
2
- import { useEffect, useRef } from "react";
3
- import hljs from "highlight.js";
4
- import "highlight.js/styles/atom-one-dark.css"; // 스타일 파일
5
-
6
- const CodeBlock = ({
7
- language,
8
- children,
9
- }: {
10
- language: string;
11
- children: React.ReactNode;
12
- }) => {
13
- const codeRef = useRef<HTMLElement | null>(null);
14
-
15
- useEffect(() => {
16
- if (codeRef.current) {
17
- hljs.highlightElement(codeRef.current); // 코드 블록 하이라이트
18
- }
19
- }, [children]);
20
-
21
- return (
22
- <pre>
23
- <code ref={codeRef} className={language}>
24
- {children}
25
- </code>
26
- </pre>
27
- );
28
- };
29
-
30
- export default CodeBlock;
@@ -1,111 +0,0 @@
1
- "use client";
2
- import Link from "next/link";
3
- import Image from "next/image";
4
- import { useEffect, useState } from "react";
5
-
6
- const GlobalNav = () => {
7
- const [windowSize, setWindowSize] = useState<number>(); // 초기값 설정
8
-
9
- const [toggleMenu, setToggleMenu] = useState<boolean>(false);
10
-
11
- // 윈도우 사이즈 추적
12
- useEffect(() => {
13
- const handleResize = () => {
14
- setWindowSize(window.innerWidth); // 윈도우 사이즈 업데이트
15
- };
16
-
17
- window.addEventListener("resize", handleResize); // 리사이즈 이벤트 리스너 추가
18
-
19
- return () => window.removeEventListener("resize", handleResize); // 컴포넌트 언마운트 시 리스너 제거
20
- }, [windowSize]);
21
-
22
- // 초기 토글 값 설정
23
-
24
- useEffect(() => {
25
- if (window.innerWidth > 768) {
26
- setToggleMenu(true);
27
- } else {
28
- setToggleMenu(false);
29
- }
30
-
31
- // console.log(
32
- // `one Time Work!: ${toggleMenu}, windowSize:${window.innerWidth}`
33
- // );
34
- }, []);
35
-
36
- return (
37
- <>
38
- <nav
39
- className={`fixed w-full max-w-80 h-full bg-fill-card text-text-standard z-20 left-0 top-0 md:relative ${toggleMenu ? "block" : "hidden"}`}
40
- >
41
- <div className="brand">
42
- <div className="flex items-center justify-between">
43
- <Link href={"/"}>
44
- <Image
45
- src={"/images/global/logo.png"}
46
- alt="Logo"
47
- width={180}
48
- height={45}
49
- draggable="false"
50
- ></Image>
51
- </Link>
52
- <button className="p-4">
53
- <span
54
- className="material-symbols-outlined"
55
- onClick={() => setToggleMenu(false)}
56
- style={{ fontSize: "16px" }}
57
- >
58
- close
59
- </span>
60
- </button>
61
- </div>
62
- </div>
63
- <ul>
64
- <li>
65
- <Link href={"/"} className="px-4 py-2 block">
66
- 시작하기
67
- </Link>
68
- </li>
69
- <li>
70
- <Link href={"/component/input"} className="px-4 py-2 block">
71
- 입력창
72
- </Link>
73
- </li>
74
- <li>
75
- <Link href={"/component/select"} className="px-4 py-2 block">
76
- 선택창
77
- </Link>
78
- </li>
79
- <li>
80
- <Link href={"/component/checkbox"} className="px-4 py-2 block">
81
- 체크박스
82
- </Link>
83
- </li>
84
- <li>
85
- <Link href={"/component/radio"} className="px-4 py-2 block">
86
- 라디오
87
- </Link>
88
- </li>
89
- <li>
90
- <Link href={"/component/button"} className="px-4 py-2 block">
91
- 버튼
92
- </Link>
93
- </li>
94
- </ul>
95
- </nav>
96
- <button
97
- className={`fixed shadow-md rounded-full bg-fill-card w-12 h-12 top-6 left-6 z-10 ${toggleMenu ? "hidden" : "block"} `}
98
- >
99
- <span
100
- className="material-symbols-outlined"
101
- onClick={() => setToggleMenu(true)}
102
- style={{ fontSize: "16px" }}
103
- >
104
- menu
105
- </span>
106
- </button>
107
- </>
108
- );
109
- };
110
-
111
- export default GlobalNav;
@@ -1,306 +0,0 @@
1
- "use client";
2
- import clsx from "clsx";
3
- import { useEffect, useRef, useState } from "react";
4
- import { createPortal } from "react-dom";
5
-
6
- export const Input = ({
7
- className,
8
- defaultValue = "",
9
- placeholder = "값을 입력해주세요",
10
- label,
11
- readOnly,
12
- disabled,
13
- pattern,
14
- required,
15
- }: {
16
- className?: string;
17
- defaultValue?: string;
18
- placeholder?: string;
19
- label?: string;
20
- readOnly?: boolean;
21
- disabled?: boolean;
22
- pattern?: string;
23
- required?: boolean;
24
- }) => {
25
- const [value, setValue] = useState(defaultValue);
26
- const [isValue, setIsValue] = useState(false);
27
- const [guide, setGuide] = useState("");
28
-
29
- useEffect(() => {});
30
- const handleValue = (e: React.ChangeEvent<HTMLInputElement>) => {
31
- const inputValue = e.target.value;
32
- if (defaultValue) {
33
- setValue(defaultValue);
34
- }
35
- setValue(inputValue);
36
-
37
- if (e.target.checkValidity()) {
38
- setGuide("유효한 값입니다.");
39
- setIsValue(true);
40
- } else {
41
- setGuide("유효하지 않은 값입니다.");
42
- setIsValue(false);
43
- }
44
- };
45
-
46
- return (
47
- <>
48
- {label ? (
49
- <p className={clsx("mb-1 text-text-standard", {})}>
50
- <span
51
- className={clsx("", {
52
- "after:content-['*'] after:ml-1 after:text-danger": required,
53
- })}
54
- >
55
- {label}
56
- </span>
57
- </p>
58
- ) : null}
59
- <input
60
- type="text"
61
- className={clsx(
62
- "transition-all border px-4 py-2 border-input-border-standard text-input-text-value rounded-input-rounded",
63
- value ? "bg-input-background-value" : "bg-input-background-standard ",
64
- { "placeholder:text-input-text-placeholder": true },
65
- { "focus:border-input-border-focus": true },
66
- { "disabled:bg-input-background-disabled": true },
67
- {
68
- "invalid:border-input-border-error invalid:bg-input-background-error invalid:text-input-text-error":
69
- required && pattern,
70
- },
71
- {
72
- "valid:border-input-border-success valid:bg-input-background-success valid:text-input-text-success":
73
- required && pattern,
74
- },
75
- className
76
- )}
77
- value={value}
78
- onChange={handleValue}
79
- placeholder={placeholder}
80
- readOnly={readOnly}
81
- disabled={disabled}
82
- pattern={pattern}
83
- required={required}
84
- />
85
- {guide && required && pattern ? (
86
- <p
87
- className={`text-sm ${isValue ? "text-input-text-success" : "text-input-text-error"} ml-1 mt-2`}
88
- >
89
- {guide}
90
- </p>
91
- ) : null}
92
- </>
93
- );
94
- };
95
-
96
- export const Select = ({ options }: { options: string[] }) => {
97
- const [filterValue, setFilterValue] = useState("");
98
- const [isOpen, setIsOpen] = useState(false);
99
- const [menuPosition, setMenuPosition] = useState({
100
- top: 0,
101
- left: 0,
102
- width: 0,
103
- });
104
- const inputRef = useRef<HTMLInputElement>(null);
105
- const dropdownRef = useRef<HTMLUListElement>(null);
106
- const filteredOptions = options.filter((option) =>
107
- option.toLowerCase().includes(filterValue.toLowerCase())
108
- );
109
-
110
- useEffect(() => {
111
- if (isOpen && inputRef.current) {
112
- const rect = inputRef.current.getBoundingClientRect();
113
- setMenuPosition({
114
- top: rect.bottom + window.scrollY,
115
- left: rect.left,
116
- width: rect.width,
117
- });
118
- }
119
- }, [isOpen]);
120
-
121
- useEffect(() => {
122
- // 마우스 벗어난 영역 클릭 시 드롭다운 닫기
123
- const handleClickOutside = (event: MouseEvent) => {
124
- if (
125
- dropdownRef.current &&
126
- !dropdownRef.current.contains(event.target as Node) &&
127
- inputRef.current &&
128
- !inputRef.current.contains(event.target as Node)
129
- ) {
130
- setIsOpen(false);
131
- }
132
- };
133
- document.addEventListener("mousedown", handleClickOutside);
134
- return () => document.removeEventListener("mousedown", handleClickOutside);
135
- }, []);
136
-
137
- return (
138
- <div className="relative">
139
- <label className="relative w-fit hidden md:block">
140
- <input
141
- ref={inputRef}
142
- type="text"
143
- value={filterValue}
144
- placeholder="값을 선택해주세요..."
145
- onChange={(e) => {
146
- setFilterValue(e.target.value);
147
- }}
148
- onFocus={() => setIsOpen(true)}
149
- className={clsx(
150
- "transition-all bg-input-background-standard border px-4 py-2 border-input-border-standard text-input-text-value rounded-input-rounded",
151
- { "placeholder:text-input-text-placeholder": true },
152
- { "focus:border-input-border-focus": true },
153
- { "disabled:bg-input-background-disabled": true }
154
- )}
155
- />
156
- <div
157
- className="absolute flex items-center right-0 top-0 h-full px-2"
158
- draggable
159
- >
160
- <span className="material-symbols-outlined">
161
- <span className="text-base input-text-value">arrow_drop_down</span>
162
- </span>
163
- </div>
164
- </label>
165
- {isOpen &&
166
- createPortal(
167
- <ul
168
- ref={dropdownRef}
169
- className="absolute w-full border rounded bg-input-background-standard border-input-border-standard shadow-md mt-1"
170
- style={{
171
- top: menuPosition.top,
172
- left: menuPosition.left,
173
- width: menuPosition.width,
174
- }}
175
- >
176
- {filteredOptions.length > 0 ? (
177
- filteredOptions.map((option, index) => (
178
- <li
179
- key={index}
180
- className="px-4 py-2 text-input-text-value hover:bg-input-background-value cursor-pointer"
181
- onClick={() => {
182
- setFilterValue(option);
183
- setIsOpen(false);
184
- }}
185
- >
186
- {option}
187
- </li>
188
- ))
189
- ) : (
190
- <li className="px-4 py-2 text-gray-500">No results</li>
191
- )}
192
- </ul>,
193
- document.body
194
- )}
195
- <select
196
- className={clsx(
197
- " block transition-all bg-input-background-standard border px-4 py-2 border-input-border-standard text-input-text-value rounded-input-rounded",
198
- { "placeholder:text-input-text-placeholder": true },
199
- { "focus:border-input-border-focus": true },
200
- { "disabled:bg-input-background-disabled": true },
201
- { "md:hidden": true }
202
- )}
203
- >
204
- {options.map((option, index) => {
205
- return (
206
- <option key={index} value={option}>
207
- {option}
208
- </option>
209
- );
210
- })}
211
- </select>
212
- </div>
213
- );
214
- };
215
-
216
- export const Radio = ({
217
- name,
218
- value,
219
- className,
220
- onChange,
221
- defaultChecked,
222
- }: {
223
- name: string;
224
- value: string;
225
- className?: string;
226
- onChange?: () => void;
227
- defaultChecked?: boolean;
228
- }) => {
229
- return (
230
- <label className="flex items-center gap-2">
231
- <input
232
- type="radio"
233
- className={clsx(
234
- "relative appearance-none peer w-5 h-5 bg-input-background-standard border border-fill-border rounded-full",
235
- {
236
- "checked:bg-primary after:w-2.5 after:h-2.5 after:bg-fill-card after:content-[''] after:absolute after:rounded-full after:left-1 after:top-1":
237
- true,
238
- },
239
- { "focus:border-input-border-focus": true },
240
- { "disabled:bg-input-background-disabled": true },
241
- className
242
- )}
243
- name={name}
244
- onChange={onChange}
245
- defaultChecked={defaultChecked}
246
- />
247
- <span>{value}</span>
248
- </label>
249
- );
250
- };
251
-
252
- export const Checkbox = ({
253
- label,
254
- className,
255
- onChange,
256
- disabled,
257
- required,
258
- defaultChecked,
259
- }: {
260
- label?: string;
261
- className?: string;
262
- onChange?: () => void;
263
- disabled?: boolean;
264
- required?: boolean;
265
- defaultChecked?: boolean;
266
- }) => {
267
- return (
268
- <label className="relative flex items-center gap-2">
269
- <input
270
- type="checkbox"
271
- className={clsx(
272
- "relative appearance-none peer w-5 h-5 bg-input-background-standard border border-fill-border rounded-common",
273
- { "checked:bg-primary": true },
274
- { "focus:border-input-border-focus": true },
275
- { "disabled:bg-input-background-disabled": true },
276
- {
277
- "after:content-['*'] after:text-danger after:absolute after:-right-1 after:-top-2":
278
- required,
279
- },
280
- className
281
- )}
282
- onChange={onChange}
283
- disabled={disabled}
284
- required={required}
285
- defaultChecked={defaultChecked}
286
- />
287
- <span className="absolute left-0.5 top-1 invisible peer-checked:visible fill-fill-card ">
288
- <svg
289
- xmlns="http://www.w3.org/2000/svg"
290
- className="h-4 w-4"
291
- viewBox="0 0 20 20"
292
- strokeWidth="1"
293
- >
294
- <path
295
- fillRule="evenodd"
296
- d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
297
- clipRule="evenodd"
298
- ></path>
299
- </svg>
300
- </span>
301
- {label ? (
302
- <span className="text-text-standard select-none">{label}</span>
303
- ) : null}
304
- </label>
305
- );
306
- };
@@ -1,118 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect, useState } from "react";
4
- import { Radio } from "../Input/Input";
5
-
6
- export const ThemeSelector = ({
7
- type,
8
- }: {
9
- type: "all" | "theme" | "input";
10
- }) => {
11
- const themes = ["standard", "dark"];
12
- const [selectedTheme, setSelectedTheme] = useState<string | null>(null);
13
- const inputThemes = ["inputStandard", "inputCircle"];
14
- const [selectedInputTheme, setSelectedInputTheme] = useState<string | null>(
15
- null
16
- );
17
- const [isThemeLoading, setThemeLoading] = useState(false);
18
-
19
- useEffect(() => {
20
- const chageThemeValue = () => {
21
- const storedTheme = localStorage.getItem("theme") || "standard";
22
- const storedInputTheme =
23
- localStorage.getItem("inputTheme") || "inputStandard";
24
-
25
- setSelectedTheme(storedTheme);
26
- setSelectedInputTheme(storedInputTheme);
27
- };
28
-
29
- chageThemeValue();
30
- setThemeLoading(true);
31
- }, []);
32
-
33
- // main theme
34
- const handleThemeChange = (theme: string) => {
35
- if (typeof window !== "undefined") {
36
- localStorage.setItem("theme", theme);
37
- window.dispatchEvent(new Event("storage"));
38
- setSelectedTheme(theme);
39
- }
40
- };
41
- // input theme
42
- const handleInputThemeChange = (theme: string) => {
43
- if (typeof window !== "undefined") {
44
- localStorage.setItem("inputTheme", theme);
45
- window.dispatchEvent(new Event("storage"));
46
- setSelectedInputTheme(theme);
47
- }
48
- };
49
- if (isThemeLoading == false) {
50
- return null;
51
- }
52
-
53
- return (
54
- <div className="flex flex-col gap-4">
55
- <p>theme 선택</p>
56
- <div className="flex flex-col gap-4">
57
- {type == "all" || type == "theme" ? (
58
- <div className="flex gap-4">
59
- {themes.map((theme) => (
60
- <Radio
61
- name="mainTheme"
62
- value={theme}
63
- key={theme}
64
- onChange={() => handleThemeChange(theme)}
65
- defaultChecked={theme == selectedTheme}
66
- />
67
- ))}
68
- </div>
69
- ) : null}
70
- {type == "all" || type == "input" ? (
71
- <div className="flex gap-4">
72
- {inputThemes.map((inputTheme) => (
73
- <Radio
74
- name="inputTheme"
75
- value={inputTheme}
76
- key={inputTheme}
77
- onChange={() => handleInputThemeChange(inputTheme)}
78
- defaultChecked={inputTheme == selectedInputTheme}
79
- />
80
- ))}
81
- </div>
82
- ) : null}
83
- </div>
84
- </div>
85
- );
86
- };
87
-
88
- export const ThemeWrapper = ({
89
- children,
90
- className,
91
- }: {
92
- children: React.ReactNode;
93
- className: string;
94
- }) => {
95
- const [theme, setTheme] = useState<string | null>(null); // 테마 상태 추가
96
- const [inputTheme, setInputTheme] = useState<string | null>(null); // 테마 상태 추가
97
- const [isThemeLoading, setThemeLoading] = useState(false);
98
- // theme setting
99
- useEffect(() => {
100
- // 로컬 스토리지에서 테마 가져오기
101
- const listenStorageChange = () => {
102
- setTheme(localStorage.getItem("theme") || "standard");
103
- setInputTheme(localStorage.getItem("inputTheme") || "inputStandard");
104
- };
105
- listenStorageChange();
106
- window.addEventListener("storage", listenStorageChange);
107
- setThemeLoading(true);
108
- return () => window.removeEventListener("storage", listenStorageChange);
109
- }, [theme]);
110
-
111
- if (isThemeLoading == false) {
112
- return null;
113
- }
114
-
115
- return (
116
- <body className={`${className} ${theme} ${inputTheme}`}>{children}</body>
117
- );
118
- };
@@ -1,42 +0,0 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
4
-
5
- @layer base {
6
- h1 {
7
- @apply text-mobileH1 lg:text-h1 text-text-standard;
8
- }
9
- h2 {
10
- @apply text-mobileH2 lg:text-h2 text-text-standard;
11
- }
12
- h3 {
13
- @apply text-mobileH3 lg:text-h3 text-text-standard;
14
- }
15
- h4 {
16
- @apply text-mobileH4 lg:text-h4 text-text-standard;
17
- }
18
- h5 {
19
- @apply text-mobileH5 lg:text-h5 text-text-standard;
20
- }
21
- h6 {
22
- @apply text-mobileH6 lg:text-h6 text-text-standard;
23
- }
24
-
25
- p {
26
- @apply text-base text-text-standard;
27
- }
28
-
29
- hr {
30
- @apply border-fill-border;
31
- }
32
- *:focus {
33
- @apply outline-none;
34
- }
35
- }
36
-
37
- @layer components {
38
- .required::after {
39
- content: "*";
40
- @apply ml-1 text-danger;
41
- }
42
- }