atomsystem 0.1.25 → 0.1.27

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/README.md CHANGED
@@ -1,7 +1,15 @@
1
- This is Atom Design System
1
+ # Basic Style Dictionary
2
2
 
3
- with tokenStudio,
4
- with Figma,
5
- with Storybook,
3
+ This example code is bare-bones to show you what this framework can do. If you have the style-dictionary module installed globally, you can `cd` into this directory and run:
6
4
 
7
- https://system.atomground.com
5
+ ```bash
6
+ npx style-dictionary build
7
+ ```
8
+
9
+ Otherwise, install Style Dictionary locally for this project first, `cd` into this directory and run:
10
+
11
+ ```bash
12
+ npm init -y && npm install style-dictionary
13
+ ```
14
+
15
+ and then run the above command.
@@ -2,18 +2,16 @@ export const Badge = ({
2
2
  children,
3
3
  color,
4
4
  type,
5
- className,
6
5
  }: {
7
6
  children: React.ReactNode;
8
7
  color: "primary" | "secondary" | "warning" | "danger";
9
8
  type?: "default" | "tint";
10
- className?: string;
11
9
  }) => {
12
10
  switch (type) {
13
11
  case "tint":
14
12
  return (
15
13
  <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}`}
14
+ className={`badge-tint ${color}`}
17
15
  >
18
16
  {children}
19
17
  </span>
@@ -21,7 +19,7 @@ export const Badge = ({
21
19
  default:
22
20
  return (
23
21
  <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}`}
22
+ className={`badge ${color}`}
25
23
  >
26
24
  {children}
27
25
  </span>
@@ -9,36 +9,14 @@ export const Button = ({
9
9
  onClick,
10
10
  }: {
11
11
  children: React.ReactNode;
12
- type: "primary" | "secondary" | "success" | "danger" | "gray";
12
+ type: "primary" | "secondary" | "success" | "warning" | "danger" | "gray";
13
13
  style?: React.CSSProperties;
14
14
  disabled?: boolean;
15
15
  onClick?: () => void;
16
16
  }) => {
17
17
  return (
18
18
  <button
19
- className={clsx(
20
- "px-4 py-2 rounded-common text-white transition-all cursor-pointer rounded-[var(--global-rounded)]",
21
- {
22
- "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":
23
- type == "primary",
24
- },
25
- {
26
- "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 ":
27
- type == "secondary",
28
- },
29
- {
30
- "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":
31
- type == "success",
32
- },
33
- {
34
- "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":
35
- type == "danger",
36
- },
37
- {
38
- "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":
39
- type == "gray",
40
- }
41
- )}
19
+ className={clsx("btn", type)}
42
20
  style={style}
43
21
  disabled={disabled}
44
22
  onClick={onClick}
@@ -2,7 +2,6 @@ import React from "react";
2
2
 
3
3
  export const Card = ({
4
4
  children,
5
- type = "standard",
6
5
  className = "",
7
6
  overflow = "overflow-auto",
8
7
  }: {
@@ -21,7 +20,7 @@ export const Card = ({
21
20
  );
22
21
  return (
23
22
  <div
24
- className={`${type == "standard" ? "bg-[var(--background-card)] border-[var(--color-border)]" : type == "group" ? "bg-[var(--background-wrapper)] border-none" : ""} ${!hasCardContent ? "p-4" : ""} rounded border ${className} ${overflow === "overflow-auto" ? `overflow-auto` : overflow === "overflow-hidden" ? "overflow-hidden" : overflow === "overflow-none" ? "" : ""}`}
23
+ className={`card ${className} ${overflow}`}
25
24
  >
26
25
  {children}
27
26
  </div>
@@ -35,7 +34,7 @@ export const CardHeader = ({
35
34
  children: React.ReactNode;
36
35
  noMargin?: boolean;
37
36
  }) => {
38
- return <div className={`${noMargin ? "p-0" : "p-4"}`}>{children}</div>;
37
+ return <div className={`card-header ${noMargin ? "no-margin" : ""}`}>{children}</div>;
39
38
  };
40
39
 
41
40
  export const CardContent = ({
@@ -45,7 +44,7 @@ export const CardContent = ({
45
44
  children: React.ReactNode;
46
45
  noMargin?: boolean;
47
46
  }) => {
48
- return <div className={`${noMargin ? "p-0" : "p-4"}`}>{children}</div>;
47
+ return <div className={`card-content ${noMargin ? "no-margin" : ""}`}>{children}</div>;
49
48
  };
50
49
 
51
50
  export const CardFooter = ({
@@ -55,5 +54,5 @@ export const CardFooter = ({
55
54
  children: React.ReactNode;
56
55
  noMargin?: boolean;
57
56
  }) => {
58
- return <div className={`${noMargin ? "p-0" : "p-4"}`}>{children}</div>;
57
+ return <div className={`card-footer ${noMargin ? "no-margin" : ""}`}>{children}</div>;
59
58
  };
@@ -12,10 +12,16 @@ const CodeBlock = ({
12
12
  }) => {
13
13
  const codeRef = useRef<HTMLElement | null>(null);
14
14
  const [copied, setCopied] = useState(false);
15
+ const [isHeightCheck, setHeightCheck] = useState(false);
16
+ const [isOpen, setIsOpen] = useState(false);
15
17
 
16
18
  useEffect(() => {
17
19
  if (codeRef.current) {
18
20
  hljs.highlightElement(codeRef.current); // 코드 블록 하이라이트
21
+
22
+ if (codeRef.current.offsetHeight >= 300) {
23
+ setHeightCheck(true);
24
+ }
19
25
  }
20
26
  }, [children]);
21
27
 
@@ -33,7 +39,7 @@ const CodeBlock = ({
33
39
 
34
40
  return (
35
41
  <div className="relative">
36
- <pre>
42
+ <pre className={`${isOpen ? "max-h-fit" : "max-h-60"} overflow-hidden`}>
37
43
  <code ref={codeRef} className={language}>
38
44
  {children}
39
45
  </code>
@@ -41,10 +47,20 @@ const CodeBlock = ({
41
47
 
42
48
  <button
43
49
  onClick={handleCopy}
44
- className="absolute top-2 right-2 bg-white text-black text-xs px-2 py-1 rounded hover:bg-gray-200"
50
+ className="absolute top-2 right-2 bg-[var(--color-bg-card)] hover:bg-[var(--color-bg-wrapper)] text-[var(--text-standard)] text-xs px-2 py-1 rounded cursor-pointer "
45
51
  >
46
52
  {copied ? "Copied!" : "Copy"}
47
53
  </button>
54
+
55
+ {isHeightCheck ? (
56
+ <button
57
+ onClick={() => setIsOpen(!isOpen)}
58
+ className="
59
+ w-full cursor-pointer hover:bg-[var(--color-bg-wrapper)] text-center py-2 px-8"
60
+ >
61
+ {isOpen ? "Close" : "Open"}
62
+ </button>
63
+ ) : null}
48
64
  </div>
49
65
  );
50
66
  };
@@ -35,20 +35,20 @@ export const Collapse = ({
35
35
  return (
36
36
  <>
37
37
  {collapses.map((collapse, idx) => (
38
- <div key={idx}>
39
- <label className="flex justify-between cursor-pointer p-4 border-t border-[var(--color-border)]">
38
+ <div key={idx} className="collapse-item">
39
+ <label className="collapse-header">
40
40
  {collapse.title}
41
41
  <button
42
42
  onClick={() => toggleContent(idx)}
43
- className={`material-symbols-outlined cursor-pointer transition-all duration-300 ${
44
- isActive(idx) ? "rotate-90" : "rotate-0"
43
+ className={`material-symbols-outlined collapse-icon ${
44
+ isActive(idx) ? "open" : "closed"
45
45
  }`}
46
46
  >
47
47
  keyboard_arrow_right
48
48
  </button>
49
49
  </label>
50
50
  {isActive(idx) && (
51
- <div className="p-4 bg-[var(--background-wrapper)] border-rounded">
51
+ <div className="collapse-content">
52
52
  {collapse.content}
53
53
  </div>
54
54
  )}
@@ -91,20 +91,20 @@ export const NodeCollapse = ({
91
91
  return (
92
92
  <>
93
93
  {collapses.map((collapse, idx) => (
94
- <div key={idx}>
95
- <label className="flex justify-between cursor-pointer p-4 border-t border-[var(--color-border)]">
94
+ <div key={idx} className="collapse-item">
95
+ <label className="collapse-header">
96
96
  {collapse.title}
97
97
  <button
98
98
  onClick={() => toggleContent(idx)}
99
- className={`material-symbols-outlined cursor-pointer transition-all duration-300 ${
100
- isActive(idx) ? "rotate-90" : "rotate-0"
99
+ className={`material-symbols-outlined collapse-icon ${
100
+ isActive(idx) ? "open" : "closed"
101
101
  }`}
102
102
  >
103
103
  keyboard_arrow_right
104
104
  </button>
105
105
  </label>
106
106
  {isActive(idx) && (
107
- <div className="p-4 bg-[var(--background-wrapper)] border-rounded">
107
+ <div className="collapse-content">
108
108
  {collapse.content}
109
109
  </div>
110
110
  )}
@@ -1,12 +1,31 @@
1
1
  "use client";
2
2
  import clsx from "clsx";
3
- import { useEffect, useRef, useState } from "react";
3
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
4
4
  import { createPortal } from "react-dom";
5
5
 
6
+ export const Label = ({
7
+ label,
8
+ required,
9
+ }: {
10
+ label: string;
11
+ required?: boolean;
12
+ }) => {
13
+ return (
14
+ <p className={"mb-1 text-text-standard"}>
15
+ <span
16
+ className={clsx("", {
17
+ "after:content-['*'] after:ml-1 after:text-danger": required,
18
+ })}
19
+ >
20
+ {label}
21
+ </span>
22
+ </p>
23
+ );
24
+ };
25
+
6
26
  export const Input = ({
7
27
  type = "text",
8
28
  placeholder = "값을 입력해주세요",
9
- label,
10
29
  readOnly,
11
30
  disabled,
12
31
  required,
@@ -14,52 +33,28 @@ export const Input = ({
14
33
  type?: string;
15
34
  className?: string;
16
35
  placeholder?: string;
17
- label?: string;
18
36
  readOnly?: boolean;
19
37
  disabled?: boolean;
20
38
  required?: boolean;
21
39
  }) => {
22
40
  return (
23
- <>
24
- {label ? (
25
- <p className={clsx("mb-1 text-text-standard", {})}>
26
- <span
27
- className={clsx("", {
28
- "after:content-['*'] after:ml-1 after:text-danger": required,
29
- })}
30
- >
31
- {label}
32
- </span>
33
- </p>
34
- ) : null}
35
41
  <input
42
+ className="input"
36
43
  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)] w-full"
38
- style={{
39
- color: "var(--input-text-standard)",
40
- paddingLeft: "var(--spacing-global-x)",
41
- paddingRight: "var(--spacing-global-x)",
42
- paddingTop: "var(--spacing-global-y)",
43
- paddingBottom: "var(--spacing-global-y)",
44
- borderRadius: "var(--input-rounded)",
45
- }}
46
44
  placeholder={placeholder}
47
45
  readOnly={readOnly}
48
46
  disabled={disabled}
49
47
  required={required}
50
48
  />
51
- </>
52
49
  );
53
50
  };
54
51
 
55
52
  export const Select = ({
56
53
  options,
57
- label,
58
54
  required,
59
55
  disabled,
60
56
  }: {
61
57
  options: string[];
62
- label?: string;
63
58
  required?: boolean;
64
59
  disabled?: boolean;
65
60
  }) => {
@@ -76,16 +71,41 @@ export const Select = ({
76
71
  option.toLowerCase().includes(filterValue.toLowerCase())
77
72
  );
78
73
 
79
- useEffect(() => {
80
- if (isOpen && inputRef.current) {
74
+ useLayoutEffect(() => {
75
+ if (isOpen && inputRef.current && dropdownRef.current) {
81
76
  const rect = inputRef.current.getBoundingClientRect();
82
- setMenuPosition({
83
- top: rect.bottom + window.scrollY,
84
- left: rect.left,
85
- width: rect.width,
77
+ const dropdownRect = dropdownRef.current.getBoundingClientRect();
78
+
79
+ if (Math.abs(menuPosition.width - rect.width) > 0.5) {
80
+ setMenuPosition({
81
+ top: rect.bottom + window.scrollY,
82
+ left: rect.left,
83
+ width: rect.width,
84
+ });
85
+ return;
86
+ }
87
+
88
+ let top = rect.bottom + window.scrollY;
89
+
90
+ if (rect.bottom + dropdownRect.height > window.innerHeight) {
91
+ top = rect.top + window.scrollY - dropdownRect.height;
92
+ }
93
+
94
+ setMenuPosition((prev) => {
95
+ if (
96
+ Math.abs(prev.top - top) < 0.5 &&
97
+ Math.abs(prev.left - rect.left) < 0.5
98
+ ) {
99
+ return prev;
100
+ }
101
+ return {
102
+ top,
103
+ left: rect.left,
104
+ width: rect.width,
105
+ };
86
106
  });
87
107
  }
88
- }, [isOpen]);
108
+ }, [isOpen, filterValue, menuPosition.width]);
89
109
 
90
110
  useEffect(() => {
91
111
  // 마우스 벗어난 영역 클릭 시 드롭다운 닫기
@@ -105,17 +125,6 @@ export const Select = ({
105
125
 
106
126
  return (
107
127
  <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}
119
128
  <label className="relative w-fit hidden md:block">
120
129
  <input
121
130
  ref={inputRef}
@@ -126,15 +135,7 @@ export const Select = ({
126
135
  setFilterValue(e.target.value);
127
136
  }}
128
137
  onFocus={() => setIsOpen(true)}
129
- className={clsx(
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
- }
137
- )}
138
+ className="select hidden sm:block"
138
139
  disabled={disabled}
139
140
  />
140
141
  <div
@@ -150,7 +151,7 @@ export const Select = ({
150
151
  createPortal(
151
152
  <ul
152
153
  ref={dropdownRef}
153
- className="absolute w-full border rounded bg-[var(--input-background-standard)] border-[var(--input-border-standard)] shadow-md mt-1"
154
+ className="absolute w-full select-menu"
154
155
  style={{
155
156
  top: menuPosition.top,
156
157
  left: menuPosition.left,
@@ -161,7 +162,7 @@ export const Select = ({
161
162
  filteredOptions.map((option, index) => (
162
163
  <li
163
164
  key={index}
164
- className="px-4 py-2 text-[var(--text-standard)] hover:bg-primary/10 cursor-pointer"
165
+ className="select-menu-item"
165
166
  onClick={() => {
166
167
  setFilterValue(option);
167
168
  setIsOpen(false);
@@ -171,19 +172,13 @@ export const Select = ({
171
172
  </li>
172
173
  ))
173
174
  ) : (
174
- <li className="px-4 py-2 text-gray-500">No results</li>
175
+ <li className="select-menu-item">No results</li>
175
176
  )}
176
177
  </ul>,
177
178
  document.body
178
179
  )}
179
180
  <select
180
- className={clsx(
181
- " block transition-all bg-[(--input-background-standard)] border px-4 py-2 border-[var(--color-border)] text-[var(--text-standard)] rounded-input-rounded",
182
- { "placeholder:text-input-text-placeholder": true },
183
- { "focus:border-[var(--input-border-focus)]": true },
184
- { "disabled:bg-[var(--input-background-disabled)]": true },
185
- { "md:hidden": true }
186
- )}
181
+ className="select block sm:hidden"
187
182
  >
188
183
  {options.map((option, index) => {
189
184
  return (
@@ -204,6 +199,7 @@ export const Radio = ({
204
199
  className,
205
200
  onChange,
206
201
  defaultChecked,
202
+ checked,
207
203
  }: {
208
204
  name: string;
209
205
  value: string;
@@ -211,15 +207,16 @@ export const Radio = ({
211
207
  className?: string;
212
208
  onChange?: () => void;
213
209
  defaultChecked?: boolean;
210
+ checked?: boolean;
214
211
  }) => {
215
212
  return (
216
213
  <>
217
214
  {label ? <p>{label}</p> : null}
218
- <label className="flex items-center gap-2">
215
+ <label className="flex items-center gap-1">
219
216
  <input
220
217
  type="radio"
221
218
  className={clsx(
222
- "relative appearance-none peer w-5 h-5 bg-[var(--input-background-standard)] border border-[var(--color-border)] rounded-full",
219
+ "relative appearance-none radio rounded-full ",
223
220
  {
224
221
  "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
222
  true,
@@ -231,6 +228,7 @@ export const Radio = ({
231
228
  name={name}
232
229
  onChange={onChange}
233
230
  defaultChecked={defaultChecked}
231
+ checked={checked}
234
232
  />
235
233
  <span>{value}</span>
236
234
  </label>
@@ -240,15 +238,12 @@ export const Radio = ({
240
238
 
241
239
  export const Checkbox = ({
242
240
  text,
243
- label,
244
- className,
245
241
  onChange,
246
242
  disabled,
247
243
  required,
248
244
  defaultChecked,
249
245
  }: {
250
246
  text?: string;
251
- label?: string;
252
247
  className?: string;
253
248
  onChange?: () => void;
254
249
  disabled?: boolean;
@@ -257,30 +252,16 @@ export const Checkbox = ({
257
252
  }) => {
258
253
  return (
259
254
  <>
260
- {label ? <p>{label}</p> : null}
261
- <label className="relative flex flex-nowrap items-center gap-1">
255
+ <label className="checkbox gap-1.5">
262
256
  <input
263
257
  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
- )}
258
+ className="appearance-none hidden"
278
259
  onChange={onChange}
279
260
  disabled={disabled}
280
261
  required={required}
281
262
  defaultChecked={defaultChecked}
282
263
  />
283
- <span className="absolute left-0.5 top-1 invisible peer-checked:visible fill-[var(--input-background-standard)] ">
264
+ <span>
284
265
  <svg
285
266
  xmlns="http://www.w3.org/2000/svg"
286
267
  className="h-4 w-4"
@@ -297,8 +278,8 @@ export const Checkbox = ({
297
278
  {text ? (
298
279
  <span
299
280
  className={clsx(
300
- "text-[var(--text-standard)] text-nowrap select-none",
301
- { "!text-[var(--input-text-disabled)]": disabled }
281
+ "text-text-standard text-nowrap select-none",
282
+ { "!text-text-disabled": disabled }
302
283
  )}
303
284
  >
304
285
  {text}
@@ -29,11 +29,11 @@ export const ModalContent = ({
29
29
  isOpen={isOpen}
30
30
  onRequestClose={onRequestClose}
31
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}`}
32
+ overlayClassName="modal-overlay"
33
+ className={`modal modal-${size}`}
34
34
  >
35
35
  <ModalTitle onRequestClose={onRequestClose} title={modalTitle} />
36
- <div className="p-4 w-full h-full">{children}</div>
36
+ <div className="modal-content">{children}</div>
37
37
  <ModalFooter modalFooter={modalFooter} />
38
38
  </ReactModal>
39
39
  );
@@ -50,11 +50,11 @@ type ModalTitleProps = {
50
50
 
51
51
  export const ModalTitle = ({ title, onRequestClose }: ModalTitleProps) => {
52
52
  return (
53
- <div className="flex justify-between py-3 px-4">
54
- <h6>{title}</h6>
53
+ <div className="modal-header">
54
+ <h6 className="modal-title">{title}</h6>
55
55
  <button
56
56
  onClick={onRequestClose}
57
- className="material-symbols-outlined cursor-pointer"
57
+ className="material-symbols-outlined modal-close"
58
58
  >
59
59
  close
60
60
  </button>
@@ -67,5 +67,5 @@ export const ModalFooter = ({
67
67
  }: {
68
68
  modalFooter: React.ReactNode;
69
69
  }) => {
70
- return <div className="flex justify-end py-3 px-4">{modalFooter}</div>;
70
+ return <div className="modal-footer">{modalFooter}</div>;
71
71
  };