atomsystem 0.1.16 → 0.1.18

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.
@@ -15,25 +15,25 @@ export const Button = ({
15
15
  return (
16
16
  <button
17
17
  className={clsx(
18
- "px-4 py-2 rounded-common text-button-text transition-all",
18
+ "px-4 py-2 rounded-common text-white transition-all cursor-pointer rounded-[var(--global-rounded)]",
19
19
  {
20
- "bg-button-primary-standard hover:bg-button-primary-hover disabled:bg-button-primary-disabled":
20
+ "bg-[var(--button-main-primary-standard)] hover:bg-[var(--button-main-primary-hover)] active:bg-[var(--button-main-primary-active)] standard:bg-[var(--buttion-main-primary-standard)]/20":
21
21
  type == "primary",
22
22
  },
23
23
  {
24
- "bg-button-secondary-standard hover:bg-button-secondary-hover disabled:bg-button-secondary-disabled ":
24
+ "bg-[var(--button-main-secondary-standard)] hover:bg-[var(--button-main-secondary-hover)] active:bg-[var(--button-main-secondary-active)] standard:bg-[var(--buttion-main-secondary-standard)]/20 ":
25
25
  type == "secondary",
26
26
  },
27
27
  {
28
- "bg-button-success-standard hover:bg-button-success-hover disabled:bg-button-success-disabled":
28
+ "bg-[var(--button-main-success-standard)] hover:bg-[var(--button-main-success-hover)] active:bg-[var(--button-main-success-active)] standard:bg-[var(--buttion-main-success-standard)]/20":
29
29
  type == "success",
30
30
  },
31
31
  {
32
- "bg-button-danger-standard hover:bg-button-danger-hover disabled:bg-button-danger-disabled":
32
+ "bg-[var(--button-main-danger-standard)] hover:bg-[var(--button-main-danger-hover)] active:bg-[var(--button-main-danger-active)] standard:bg-[var(--buttion-main-danger-standard)]/20":
33
33
  type == "danger",
34
34
  },
35
35
  {
36
- "bg-button-gray-standard hover:bg-button-gray-hover disabled:bg-button-gray-disabled":
36
+ "bg-[var(--button-main-gray-standard)] hover:bg-[var(--button-main-gray-hover)] active:bg-[var(--button-main-gray-active)] standard:bg-[var(--buttion-main-gray-standard)]/20":
37
37
  type == "gray",
38
38
  }
39
39
  )}
@@ -19,7 +19,7 @@ export const Card = ({
19
19
  );
20
20
  return (
21
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`}
22
+ className={`${type == "standard" ? "bg-[var(--background-card)] border-[var(--color-border)]" : type == "group" ? "bg-fill-group border-none" : ""} ${!hasCardContent ? "p-4" : ""} rounded border ${className} overflow-auto`}
23
23
  >
24
24
  {children}
25
25
  </div>
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { useEffect, useRef } from "react";
2
+ import { useEffect, useRef, useState } from "react";
3
3
  import hljs from "highlight.js";
4
4
  import "highlight.js/styles/atom-one-dark.css"; // 스타일 파일
5
5
 
@@ -11,6 +11,7 @@ const CodeBlock = ({
11
11
  children: React.ReactNode;
12
12
  }) => {
13
13
  const codeRef = useRef<HTMLElement | null>(null);
14
+ const [copied, setCopied] = useState(false);
14
15
 
15
16
  useEffect(() => {
16
17
  if (codeRef.current) {
@@ -18,12 +19,33 @@ const CodeBlock = ({
18
19
  }
19
20
  }, [children]);
20
21
 
22
+ const handleCopy = async () => {
23
+ if (!codeRef.current) return;
24
+
25
+ try {
26
+ await navigator.clipboard.writeText(codeRef.current.innerText);
27
+ setCopied(true);
28
+ setTimeout(() => setCopied(false), 1500); // 1.5초 후 메시지 제거
29
+ } catch (err) {
30
+ console.error("복사 실패:", err);
31
+ }
32
+ };
33
+
21
34
  return (
22
- <pre>
23
- <code ref={codeRef} className={language}>
24
- {children}
25
- </code>
26
- </pre>
35
+ <div className="relative">
36
+ <pre>
37
+ <code ref={codeRef} className={language}>
38
+ {children}
39
+ </code>
40
+ </pre>
41
+
42
+ <button
43
+ onClick={handleCopy}
44
+ className="absolute top-2 right-2 bg-white text-black text-xs px-2 py-1 rounded hover:bg-gray-200"
45
+ >
46
+ {copied ? "Copied!" : "Copy"}
47
+ </button>
48
+ </div>
27
49
  );
28
50
  };
29
51
 
@@ -2,12 +2,25 @@
2
2
  import Link from "next/link";
3
3
  import Image from "next/image";
4
4
  import { useEffect, useState } from "react";
5
+ import { usePathname } from "next/navigation";
6
+ import clsx from "clsx";
5
7
 
6
8
  const GlobalNav = () => {
7
9
  const [windowSize, setWindowSize] = useState<number>(); // 초기값 설정
8
10
 
9
11
  const [toggleMenu, setToggleMenu] = useState<boolean>(false);
10
12
 
13
+ const pathname = usePathname();
14
+ const navMenu = [
15
+ { name: "시작하기", path: "/" },
16
+ { name: "설치하기", path: "/install" },
17
+ { name: "입력창", path: "/component/input" },
18
+ { name: "선택창", path: "/component/select" },
19
+ { name: "체크박스", path: "/component/checkbox" },
20
+ { name: "라디오", path: "/component/radio" },
21
+ { name: "버튼", path: "/component/button" },
22
+ ];
23
+
11
24
  // 윈도우 사이즈 추적
12
25
  useEffect(() => {
13
26
  const handleResize = () => {
@@ -20,7 +33,6 @@ const GlobalNav = () => {
20
33
  }, [windowSize]);
21
34
 
22
35
  // 초기 토글 값 설정
23
-
24
36
  useEffect(() => {
25
37
  if (window.innerWidth > 768) {
26
38
  setToggleMenu(true);
@@ -36,7 +48,7 @@ const GlobalNav = () => {
36
48
  return (
37
49
  <>
38
50
  <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"}`}
51
+ className={`fixed top-0 md:relative w-full max-w-80 h-full bg-[var(--background-card)] text-text-standard z-20 left-0 ${toggleMenu ? "block" : "hidden"}`}
40
52
  >
41
53
  <div className="brand">
42
54
  <div className="flex items-center justify-between">
@@ -60,46 +72,26 @@ const GlobalNav = () => {
60
72
  </button>
61
73
  </div>
62
74
  </div>
75
+
63
76
  <ul>
64
- <li>
65
- <Link href={"/"} className="px-4 py-2 block">
66
- 시작하기
67
- </Link>
68
- </li>
69
- <li>
70
- <Link href={"/install"} className="px-4 py-2 block">
71
- 설치하기
72
- </Link>
73
- </li>
74
- <li>
75
- <Link href={"/component/input"} className="px-4 py-2 block">
76
- 입력창
77
- </Link>
78
- </li>
79
- <li>
80
- <Link href={"/component/select"} className="px-4 py-2 block">
81
- 선택창
82
- </Link>
83
- </li>
84
- <li>
85
- <Link href={"/component/checkbox"} className="px-4 py-2 block">
86
- 체크박스
87
- </Link>
88
- </li>
89
- <li>
90
- <Link href={"/component/radio"} className="px-4 py-2 block">
91
- 라디오
92
- </Link>
93
- </li>
94
- <li>
95
- <Link href={"/component/button"} className="px-4 py-2 block">
96
- 버튼
97
- </Link>
98
- </li>
77
+ {navMenu.map((menu) => {
78
+ return (
79
+ <li key={menu.name}>
80
+ <Link
81
+ href={menu.path}
82
+ className={clsx("px-4 py-2 block", {
83
+ "text-primary": menu.path == pathname,
84
+ })}
85
+ >
86
+ {menu.name}
87
+ </Link>
88
+ </li>
89
+ );
90
+ })}
99
91
  </ul>
100
92
  </nav>
101
93
  <button
102
- className={`fixed shadow-md rounded-full bg-fill-card w-12 h-12 top-6 left-6 z-10 ${toggleMenu ? "hidden" : "block"} `}
94
+ className={`fixed shadow-md rounded-full bg-[var(--background-card)] w-12 h-12 top-6 left-6 z-10 ${toggleMenu ? "hidden" : "block"} `}
103
95
  >
104
96
  <span
105
97
  className="material-symbols-outlined"
@@ -4,45 +4,21 @@ import { useEffect, useRef, useState } from "react";
4
4
  import { createPortal } from "react-dom";
5
5
 
6
6
  export const Input = ({
7
- className,
8
- defaultValue = "",
7
+ type = "text",
9
8
  placeholder = "값을 입력해주세요",
10
9
  label,
11
10
  readOnly,
12
11
  disabled,
13
- pattern,
14
12
  required,
15
13
  }: {
14
+ type?: string;
16
15
  className?: string;
17
- defaultValue?: string;
18
16
  placeholder?: string;
19
17
  label?: string;
20
18
  readOnly?: boolean;
21
19
  disabled?: boolean;
22
- pattern?: string;
23
20
  required?: boolean;
24
21
  }) => {
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
22
  return (
47
23
  <>
48
24
  {label ? (
@@ -57,43 +33,36 @@ export const Input = ({
57
33
  </p>
58
34
  ) : null}
59
35
  <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 bg-input-background-standard",
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}
36
+ type={type}
37
+ className="border rounded focus:outline-none focus:ring-0 bg-[var(--input-background-standard)] disabled:bg-[var(--input-background-disabled)] disabled:cursor-not-allowed border-[var(--input-border-standard)] focus:border-[var(--input-border-focus)] "
38
+ style={{
39
+ color: "var(--input-text-standard)",
40
+ paddingLeft: "var(--global-padding-x)",
41
+ paddingRight: "var(--global-padding-x)",
42
+ paddingTop: "var(--global-padding-y)",
43
+ paddingBottom: "var(--global-padding-y)",
44
+ borderRadius: "var(--input-rounded)",
45
+ }}
79
46
  placeholder={placeholder}
80
47
  readOnly={readOnly}
81
48
  disabled={disabled}
82
- pattern={pattern}
83
49
  required={required}
84
50
  />
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
51
  </>
93
52
  );
94
53
  };
95
54
 
96
- export const Select = ({ options }: { options: string[] }) => {
55
+ export const Select = ({
56
+ options,
57
+ label,
58
+ required,
59
+ disabled,
60
+ }: {
61
+ options: string[];
62
+ label?: string;
63
+ required?: boolean;
64
+ disabled?: boolean;
65
+ }) => {
97
66
  const [filterValue, setFilterValue] = useState("");
98
67
  const [isOpen, setIsOpen] = useState(false);
99
68
  const [menuPosition, setMenuPosition] = useState({
@@ -136,6 +105,17 @@ export const Select = ({ options }: { options: string[] }) => {
136
105
 
137
106
  return (
138
107
  <div className="relative">
108
+ {label ? (
109
+ <p className={clsx("mb-1 text-text-standard", {})}>
110
+ <span
111
+ className={clsx("", {
112
+ "after:content-['*'] after:ml-1 after:text-danger": required,
113
+ })}
114
+ >
115
+ {label}
116
+ </span>
117
+ </p>
118
+ ) : null}
139
119
  <label className="relative w-fit hidden md:block">
140
120
  <input
141
121
  ref={inputRef}
@@ -147,11 +127,15 @@ export const Select = ({ options }: { options: string[] }) => {
147
127
  }}
148
128
  onFocus={() => setIsOpen(true)}
149
129
  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 }
130
+ "transition-all bg-[var(--input-background-standard)] border px-4 py-2 border-[var(--color-border)] text-[var(--text-standard)] rounded-[var(--input-rounded)]",
131
+ { "placeholder:text[var(--input-text-placeholder)]": true },
132
+ { "focus:border-[var(--input-border-focus)]": true },
133
+ {
134
+ "disabled:bg-[var(--input-background-disabled)] disabled:cursor-not-allowed":
135
+ disabled,
136
+ }
154
137
  )}
138
+ disabled={disabled}
155
139
  />
156
140
  <div
157
141
  className="absolute flex items-center right-0 top-0 h-full px-2"
@@ -166,7 +150,7 @@ export const Select = ({ options }: { options: string[] }) => {
166
150
  createPortal(
167
151
  <ul
168
152
  ref={dropdownRef}
169
- className="absolute w-full border rounded bg-input-background-standard border-input-border-standard shadow-md mt-1"
153
+ className="absolute w-full border rounded bg-[var(--input-background-standard)] border-[var(--input-border-standard)] shadow-md mt-1"
170
154
  style={{
171
155
  top: menuPosition.top,
172
156
  left: menuPosition.left,
@@ -177,7 +161,7 @@ export const Select = ({ options }: { options: string[] }) => {
177
161
  filteredOptions.map((option, index) => (
178
162
  <li
179
163
  key={index}
180
- className="px-4 py-2 text-input-text-value hover:bg-input-background-value cursor-pointer"
164
+ className="px-4 py-2 text-[var(--text-standard)] hover:bg-primary/10 cursor-pointer"
181
165
  onClick={() => {
182
166
  setFilterValue(option);
183
167
  setIsOpen(false);
@@ -194,10 +178,10 @@ export const Select = ({ options }: { options: string[] }) => {
194
178
  )}
195
179
  <select
196
180
  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",
181
+ " block transition-all bg-[(--input-background-standard)] border px-4 py-2 border-[var(--color-border)] text-[var(--text-standard)] rounded-input-rounded",
198
182
  { "placeholder:text-input-text-placeholder": true },
199
- { "focus:border-input-border-focus": true },
200
- { "disabled:bg-input-background-disabled": true },
183
+ { "focus:border-[var(--input-border-focus)]": true },
184
+ { "disabled:bg-[var(--input-background-disabled)]": true },
201
185
  { "md:hidden": true }
202
186
  )}
203
187
  >
@@ -216,40 +200,46 @@ export const Select = ({ options }: { options: string[] }) => {
216
200
  export const Radio = ({
217
201
  name,
218
202
  value,
203
+ label,
219
204
  className,
220
205
  onChange,
221
206
  defaultChecked,
222
207
  }: {
223
208
  name: string;
224
209
  value: string;
210
+ label?: string;
225
211
  className?: string;
226
212
  onChange?: () => void;
227
213
  defaultChecked?: boolean;
228
214
  }) => {
229
215
  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>
216
+ <>
217
+ {label ? <p>{label}</p> : null}
218
+ <label className="flex items-center gap-2">
219
+ <input
220
+ type="radio"
221
+ className={clsx(
222
+ "relative appearance-none peer w-5 h-5 bg-[var(--input-background-standard)] border border-[var(--color-border)] rounded-full",
223
+ {
224
+ "checked:bg-primary after:w-2.5 after:h-2.5 after:bg-[var(--input-background-standard)] after:content-[''] after:absolute after:rounded-full after:left-1 after:top-1":
225
+ true,
226
+ },
227
+ { "focus:border-input-border-focus": true },
228
+ { "disabled:bg-input-background-disabled": true },
229
+ className
230
+ )}
231
+ name={name}
232
+ onChange={onChange}
233
+ defaultChecked={defaultChecked}
234
+ />
235
+ <span>{value}</span>
236
+ </label>
237
+ </>
249
238
  );
250
239
  };
251
240
 
252
241
  export const Checkbox = ({
242
+ text,
253
243
  label,
254
244
  className,
255
245
  onChange,
@@ -257,6 +247,7 @@ export const Checkbox = ({
257
247
  required,
258
248
  defaultChecked,
259
249
  }: {
250
+ text?: string;
260
251
  label?: string;
261
252
  className?: string;
262
253
  onChange?: () => void;
@@ -265,42 +256,55 @@ export const Checkbox = ({
265
256
  defaultChecked?: boolean;
266
257
  }) => {
267
258
  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>
259
+ <>
260
+ {label ? <p>{label}</p> : null}
261
+ <label className="relative flex flex-nowrap items-center gap-1">
262
+ <input
263
+ type="checkbox"
264
+ className={clsx(
265
+ "relative appearance-none peer min-w-5 min-h-5 bg-[var(--input-background-standard)] border border-[var(--color-border)] rounded-common",
266
+ { "checked:bg-primary": true },
267
+ { "focus:border-[var(--input-border-focus)]": true },
268
+ {
269
+ "disabled:bg-[var(--input-background-disabled)] disabled:cursor-not-allowed":
270
+ true,
271
+ },
272
+ {
273
+ "after:content-['*'] after:text-danger after:absolute after:-right-1 after:-top-2":
274
+ required,
275
+ },
276
+ className
277
+ )}
278
+ onChange={onChange}
279
+ disabled={disabled}
280
+ required={required}
281
+ defaultChecked={defaultChecked}
282
+ />
283
+ <span className="absolute left-0.5 top-1 invisible peer-checked:visible fill-[var(--input-background-standard)] ">
284
+ <svg
285
+ xmlns="http://www.w3.org/2000/svg"
286
+ className="h-4 w-4"
287
+ viewBox="0 0 20 20"
288
+ strokeWidth="1"
289
+ >
290
+ <path
291
+ fillRule="evenodd"
292
+ 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"
293
+ clipRule="evenodd"
294
+ ></path>
295
+ </svg>
296
+ </span>
297
+ {text ? (
298
+ <span
299
+ className={clsx(
300
+ "text-[var(--text-standard)] text-nowrap select-none",
301
+ { "!text-[var(--input-text-disabled)]": disabled }
302
+ )}
303
+ >
304
+ {text}
305
+ </span>
306
+ ) : null}
307
+ </label>
308
+ </>
305
309
  );
306
310
  };
@@ -0,0 +1,60 @@
1
+ "use client";
2
+ import { usePathname } from "next/navigation";
3
+ import { useEffect, useState } from "react";
4
+
5
+ type Heading = {
6
+ id: string;
7
+ text: string;
8
+ level: number;
9
+ };
10
+
11
+ export default function TOC() {
12
+ const [headings, setHeadings] = useState<Heading[]>([]);
13
+
14
+ const pathname = usePathname(); // ✅ 페이지 경로 추적
15
+
16
+ useEffect(() => {
17
+ const timer = setTimeout(() => {
18
+ const headingElements = Array.from(
19
+ document.querySelectorAll("h1, h2, h3")
20
+ ) as HTMLHeadingElement[];
21
+
22
+ const generatedHeadings: Heading[] = headingElements.map((heading) => {
23
+ // id가 없으면 자동 생성
24
+ if (!heading.id) {
25
+ heading.id =
26
+ heading.textContent
27
+ ?.toLowerCase()
28
+ .replace(/\s+/g, "-")
29
+ .replace(/[^\w\-]+/g, "") ?? "";
30
+ }
31
+
32
+ return {
33
+ id: heading.id,
34
+ text: heading.textContent || "",
35
+ level: parseInt(heading.tagName.replace("H", "")),
36
+ };
37
+ });
38
+
39
+ setHeadings(generatedHeadings);
40
+ }, 50);
41
+
42
+ return () => clearTimeout(timer);
43
+ }, [pathname]);
44
+
45
+ return (
46
+ <nav className="space-y-2 flex flex-col">
47
+ {headings.map((heading, idx) => {
48
+ return (
49
+ <a
50
+ key={`${heading.id}-${idx}`}
51
+ href={`#${heading.id}`}
52
+ style={{ marginLeft: `${(heading.level - 1) * 8}px` }}
53
+ >
54
+ {heading.text}
55
+ </a>
56
+ );
57
+ })}
58
+ </nav>
59
+ );
60
+ }