atomsystem 0.1.20 → 0.1.21

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.
@@ -0,0 +1,30 @@
1
+ export const Badge = ({
2
+ children,
3
+ color,
4
+ type,
5
+ className,
6
+ }: {
7
+ children: React.ReactNode;
8
+ color: "primary" | "secondary" | "warning" | "danger";
9
+ type?: "default" | "tint";
10
+ className?: string;
11
+ }) => {
12
+ switch (type) {
13
+ case "tint":
14
+ return (
15
+ <span
16
+ className={`text-sm px-1.5 rounded ${color === "primary" ? "bg-primary/20 !text-primary" : color === "secondary" ? "bg-secondary/20 !text-secondary" : color === "warning" ? "bg-warning/20 !text-warning" : color === "danger" ? "bg-danger/20 !text-danger" : null} ${className}`}
17
+ >
18
+ {children}
19
+ </span>
20
+ );
21
+ default:
22
+ return (
23
+ <span
24
+ className={`text-sm px-1.5 rounded ${color === "primary" ? "bg-primary !text-white" : color === "secondary" ? "bg-secondary !text-white" : color === "warning" ? "bg-warning !text-white" : color === "danger" ? "bg-danger !text-white" : null} ${className}`}
25
+ >
26
+ {children}
27
+ </span>
28
+ );
29
+ }
30
+ };
@@ -12,8 +12,9 @@ const GlobalNav = ({
12
12
  type?: "sidebar" | "topmenu" | "mobile";
13
13
  navMenu: {
14
14
  name: string;
15
- path: string;
15
+ path?: string;
16
16
  subItems?: { name: string; path: string }[];
17
+ label?: boolean;
17
18
  }[];
18
19
  }) => {
19
20
  const [windowSize, setWindowSize] = useState<number>(); // 초기값 설정
@@ -52,7 +53,7 @@ const GlobalNav = ({
52
53
  <nav
53
54
  className={clsx(
54
55
  `fixed top-0 md:relative w-full bg-[var(--background-card)] text-text-standard z-20 left-0 ${toggleMenu ? "block" : "hidden"}`,
55
- { "h-full max-w-80": type == "sidebar" },
56
+ { "h-full max-w-80 overflow-auto": type == "sidebar" },
56
57
  { "h-auto flex flex-row items-center": type == "topmenu" }
57
58
  )}
58
59
  >
@@ -88,77 +89,95 @@ const GlobalNav = ({
88
89
  "flex flex-row": type == "topmenu",
89
90
  })}
90
91
  >
91
- {navMenu.map((menu) => {
92
- return (
93
- <li className="relative group" key={menu.name}>
94
- <div className={clsx("flex justify-between items-center pr-4")}>
95
- <Link
96
- href={menu.path}
97
- className={clsx(
98
- "px-4 py-2 block text-[var(--text-standard)]",
99
- {
100
- "text-primary":
101
- menu.path == pathname ||
102
- menu.subItems?.some((sub) => sub.path === pathname),
103
- }
104
- )}
92
+ {navMenu.map((menu, idx) => {
93
+ switch (true) {
94
+ case menu.label === true && type == "sidebar":
95
+ return (
96
+ <p
97
+ key={menu.name + idx}
98
+ className="px-4 text-sm text-sub mt-2 mb-0"
105
99
  >
106
100
  {menu.name}
107
- </Link>
108
- {menu.subItems && type == "sidebar" ? (
109
- <button
110
- className="cursor-pointer"
111
- onClick={() => toggleSubMenu(menu.name)}
101
+ </p>
102
+ );
103
+ default:
104
+ if (!menu.path) return null;
105
+ return (
106
+ <li className="relative group" key={menu.name}>
107
+ <div
108
+ className={clsx("flex justify-between items-center pr-4")}
112
109
  >
113
- <span
110
+ <Link
111
+ href={menu.path}
114
112
  className={clsx(
115
- "material-symbols-outlined text-[var(--text-sub)] transition-transform",
113
+ "px-4 py-2 block text-[var(--text-standard)]",
116
114
  {
117
- "rotate-90": openSubMenu[menu.name],
115
+ "text-primary":
116
+ menu.path == pathname ||
117
+ menu.subItems?.some(
118
+ (sub) => sub.path === pathname
119
+ ),
118
120
  }
119
121
  )}
120
- style={{ fontSize: "16px" }}
121
122
  >
122
- keyboard_arrow_right
123
- </span>
124
- </button>
125
- ) : null}
126
- </div>
127
- {menu.subItems ? (
128
- <ul
129
- className={clsx(
130
- "block",
131
- {
132
- "hidden absolute top-[42px] group-hover:block hover:block bg-[var(--background-card)]":
133
- type == "topmenu",
134
- },
135
- {
136
- hidden: !openSubMenu[menu.name] && type == "sidebar",
137
- }
138
- )}
139
- >
140
- {menu.subItems.map((submenu) => {
141
- return (
142
- <li key={submenu.name}>
143
- <Link
123
+ {menu.name}
124
+ </Link>
125
+ {menu.subItems && type == "sidebar" ? (
126
+ <button
127
+ className="cursor-pointer"
128
+ onClick={() => toggleSubMenu(menu.name)}
129
+ >
130
+ <span
144
131
  className={clsx(
145
- "px-4 py-2 block text-[var(--text-standard)]",
132
+ "material-symbols-outlined text-[var(--text-sub)] transition-transform",
146
133
  {
147
- "text-primary": submenu.path == pathname,
148
- },
149
- { "pl-12 ": type == "sidebar" }
134
+ "rotate-90": openSubMenu[menu.name],
135
+ }
150
136
  )}
151
- href={submenu.path}
137
+ style={{ fontSize: "16px" }}
152
138
  >
153
- {submenu.name}
154
- </Link>
155
- </li>
156
- );
157
- })}
158
- </ul>
159
- ) : null}
160
- </li>
161
- );
139
+ keyboard_arrow_right
140
+ </span>
141
+ </button>
142
+ ) : null}
143
+ </div>
144
+ {menu.subItems ? (
145
+ <ul
146
+ className={clsx(
147
+ "block",
148
+ {
149
+ "hidden absolute top-[42px] group-hover:block hover:block bg-[var(--background-card)]":
150
+ type == "topmenu",
151
+ },
152
+ {
153
+ hidden:
154
+ !openSubMenu[menu.name] && type == "sidebar",
155
+ }
156
+ )}
157
+ >
158
+ {menu.subItems.map((submenu) => {
159
+ return (
160
+ <li key={submenu.name}>
161
+ <Link
162
+ className={clsx(
163
+ "px-4 py-2 block text-[var(--text-standard)]",
164
+ {
165
+ "text-primary": submenu.path == pathname,
166
+ },
167
+ { "pl-12 ": type == "sidebar" }
168
+ )}
169
+ href={submenu.path}
170
+ >
171
+ {submenu.name}
172
+ </Link>
173
+ </li>
174
+ );
175
+ })}
176
+ </ul>
177
+ ) : null}
178
+ </li>
179
+ );
180
+ }
162
181
  })}
163
182
  </ul>
164
183
  </nav>
@@ -34,7 +34,7 @@ export const Input = ({
34
34
  ) : null}
35
35
  <input
36
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)] "
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)] w-full"
38
38
  style={{
39
39
  color: "var(--input-text-standard)",
40
40
  paddingLeft: "var(--global-padding-x)",
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import ReactModal from "react-modal";
4
+ import React, { useEffect } from "react";
5
+
6
+ type ModalProps = {
7
+ isOpen: boolean;
8
+ onRequestClose: () => void;
9
+ children: React.ReactNode;
10
+ modalTitle: string;
11
+ modalFooter: React.ReactNode;
12
+ size?: "normal" | "middle" | "large" | "xl" | "full" | "full-screen";
13
+ };
14
+
15
+ export const ModalContent = ({
16
+ isOpen,
17
+ onRequestClose,
18
+ children,
19
+ modalTitle,
20
+ modalFooter,
21
+ size = "normal",
22
+ }: ModalProps) => {
23
+ useEffect(() => {
24
+ ReactModal.setAppElement("#modal-root"); // 접근성 설정
25
+ }, []);
26
+
27
+ return (
28
+ <ReactModal
29
+ isOpen={isOpen}
30
+ onRequestClose={onRequestClose}
31
+ shouldCloseOnEsc={true}
32
+ overlayClassName="fixed inset-0 bg-black/40 flex justify-center items-center z-50"
33
+ className={`bg-[var(--modal-background)] p-0 rounded shadow-md w-full flex flex-col ${size === "normal" ? "max-w-md" : size === "middle" ? "max-w-xl" : size === "large" ? "max-w-5xl" : size === "xl" ? "max-w-6xl" : size === "full" ? "max-w-screen" : size === "full-screen" ? "w-screen h-screen" : null}`}
34
+ >
35
+ <ModalTitle onRequestClose={onRequestClose} title={modalTitle} />
36
+ <div className="p-4 w-full h-full">{children}</div>
37
+ <ModalFooter modalFooter={modalFooter} />
38
+ </ReactModal>
39
+ );
40
+ };
41
+
42
+ export const Modal = () => {
43
+ return <div id="modal-root"></div>;
44
+ };
45
+
46
+ type ModalTitleProps = {
47
+ title: string;
48
+ onRequestClose: () => void;
49
+ };
50
+
51
+ export const ModalTitle = ({ title, onRequestClose }: ModalTitleProps) => {
52
+ return (
53
+ <div className="flex justify-between py-3 px-4">
54
+ <h6>{title}</h6>
55
+ <button
56
+ onClick={onRequestClose}
57
+ className="material-symbols-outlined cursor-pointer"
58
+ >
59
+ close
60
+ </button>
61
+ </div>
62
+ );
63
+ };
64
+
65
+ export const ModalFooter = ({
66
+ modalFooter,
67
+ }: {
68
+ modalFooter: React.ReactNode;
69
+ }) => {
70
+ return <div className="flex justify-end py-3 px-4">{modalFooter}</div>;
71
+ };
@@ -17,15 +17,15 @@ const Pagination = ({
17
17
  /** 페이지네이션을 보여줄 최대 개수정보를 받습니다. */
18
18
  pageRange: number;
19
19
  /** totlaCount로 받아 계산하거나 전체 페이지네이션의 토탈 정보를 받습니다. */
20
- totalCount: number;
20
+ totalCount?: number;
21
21
  /** totlaCount로 받아 계산하거나 전체 페이지네이션의 토탈 정보를 받습니다. */
22
22
  pageTotal?: number;
23
23
  }) => {
24
24
  const totalPages = pageTotal
25
25
  ? pageTotal
26
26
  : totalCount
27
- ? Math.ceil(totalCount / pageViewRange)
28
- : 0;
27
+ ? Math.ceil(totalCount / pageViewRange)
28
+ : 0;
29
29
  const [pageStart, setPageStart] = useState(1);
30
30
  const scrollTop = () => {
31
31
  setTimeout(() => {
@@ -34,7 +34,7 @@ const Pagination = ({
34
34
  };
35
35
 
36
36
  return (
37
- <div className="text-[#999] flex border-[#666] border-b">
37
+ <div className="text-[#999] flex">
38
38
  <button
39
39
  disabled={pageStart === 1}
40
40
  onClick={() => {
@@ -47,7 +47,7 @@ const Pagination = ({
47
47
  >
48
48
  <span className="material-symbols-outlined">keyboard_arrow_left</span>
49
49
  </button>
50
- <ul className="grid grid-cols-5 w-full">
50
+ <ul className="flex w-full">
51
51
  {[...Array(pageRange)].map((_, i) => {
52
52
  const page = pageStart + i;
53
53
 
@@ -74,7 +74,7 @@ const Pagination = ({
74
74
  }
75
75
  scrollTop();
76
76
  }}
77
- className={` w-full text-center py-8 cursor-pointer ${
77
+ className={` w-full text-center py-8 cursor-pointer px-2 ${
78
78
  currentPage === page ? "text-primary" : ""
79
79
  }`}
80
80
  >
@@ -9,7 +9,7 @@ export const ThemeSelector = ({
9
9
  type: "all" | "theme" | "input";
10
10
  }) => {
11
11
  const themes = ["light", "dark"];
12
- const [selectedTheme, setSelectedTheme] = useState<string | null>(null);
12
+ const [selectedTheme, setSelectedTheme] = useState<string | null>("light");
13
13
  // const inputThemes = ["inputStandard", "inputCircle"];
14
14
  // const [selectedInputTheme, setSelectedInputTheme] = useState<string | null>(
15
15
  // null
@@ -19,7 +19,7 @@ let messages: ToastMessage[] = [];
19
19
  let listener: ((messages: ToastMessage[]) => void) | null = null;
20
20
 
21
21
  export const addToast = (msg: Omit<ToastMessage, "id">) => {
22
- const id = crypto.randomUUID();
22
+ const id = Math.random().toString(36).substring(2) + Date.now().toString(36);
23
23
  messages = [...messages, { id, ...msg }];
24
24
  // messages 값 listener에 반영
25
25
  listener?.(messages);
package/cli/add.js CHANGED
@@ -16,6 +16,7 @@ const templateDir = path.join(__dirname, "..", "app", "templates");
16
16
  // 사용할 수 있는 컴포넌트 목록
17
17
  const components = [
18
18
  "Button",
19
+ "Badge",
19
20
  "Card",
20
21
  "CodeBlock",
21
22
  "GlobalNav",
@@ -25,6 +26,7 @@ const components = [
25
26
  "Toast",
26
27
  "Pagination",
27
28
  "Table",
29
+ "Modal",
28
30
  ]; // 여기 추가할 컴포넌트 목록
29
31
 
30
32
  // 명령어에서 컴포넌트 이름 가져오기
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atomsystem",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
5
  "homepage": "https://system.atomground.com",
6
6
  "bin": {
@@ -39,6 +39,7 @@
39
39
  "next": "^15.2.4",
40
40
  "react": "^18.0.0",
41
41
  "react-dom": "^18.0.0",
42
+ "react-modal": "^3.16.3",
42
43
  "sd-tailwindcss-transformer": "^2.0.0",
43
44
  "style-dictionary": "^4.2.0"
44
45
  },
@@ -46,6 +47,7 @@
46
47
  "@types/node": "^20",
47
48
  "@types/react": "^18",
48
49
  "@types/react-dom": "^18",
50
+ "@types/react-modal": "^3.16.3",
49
51
  "@vitest/browser": "^3.1.1",
50
52
  "@vitest/coverage-v8": "^3.1.1",
51
53
  "eslint": "^8",
package/token.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "color": {
3
3
  "primary": { "value": "#2196f3", "type": "color" },
4
+ "secondary": { "value": "#162c4e", "type": "color" },
5
+ "danger": { "value": "#ff4a4a", "type": "color" },
6
+ "warning": { "value": "#ffa300", "type": "color" },
4
7
  "border": {
5
8
  "value": {
6
9
  "light": "#ccc",
@@ -25,7 +28,7 @@
25
28
  },
26
29
  "text": {
27
30
  "h1": {
28
- "value": 32,
31
+ "value": 38,
29
32
  "type": "typography"
30
33
  },
31
34
  "h1-line-height": {
@@ -37,18 +40,42 @@
37
40
  "type": "typography"
38
41
  },
39
42
  "h2": {
40
- "value": 24,
43
+ "value": 32,
41
44
  "type": "typography"
42
45
  },
43
46
  "h2-line-height": {
44
- "value": 36,
47
+ "value": 42,
45
48
  "type": "typography"
46
49
  },
47
50
  "h3": {
48
- "value": 16,
51
+ "value": 30,
49
52
  "type": "typography"
50
53
  },
51
54
  "h3-line-height": {
55
+ "value": 38,
56
+ "type": "typography"
57
+ },
58
+ "h4": {
59
+ "value": 24,
60
+ "type": "typography"
61
+ },
62
+ "h4-line-height": {
63
+ "value": 38,
64
+ "type": "typography"
65
+ },
66
+ "h5": {
67
+ "value": 20,
68
+ "type": "typography"
69
+ },
70
+ "h5-line-height": {
71
+ "value": 32,
72
+ "type": "typography"
73
+ },
74
+ "h6": {
75
+ "value": 16,
76
+ "type": "typography"
77
+ },
78
+ "h6-line-height": {
52
79
  "value": 24,
53
80
  "type": "typography"
54
81
  },
@@ -250,5 +277,13 @@
250
277
  }
251
278
  }
252
279
  }
280
+ },
281
+ "modal": {
282
+ "background": {
283
+ "value": {
284
+ "light": "#fafafa",
285
+ "dark": "#333333"
286
+ }
287
+ }
253
288
  }
254
289
  }