atomsystem 0.1.26 → 0.1.28
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 +53 -5
- package/app/templates/Badge/Badge.tsx +2 -4
- package/app/templates/Button/Button.tsx +2 -24
- package/app/templates/Card/Card.tsx +4 -13
- package/app/templates/CodeBlock/CodeBlock.tsx +18 -2
- package/app/templates/Collapse/Collapse.tsx +10 -10
- package/app/templates/{Input/Input.tsx → Form/Form.tsx} +69 -90
- package/app/templates/Modal/Modal.tsx +7 -7
- package/app/templates/Navigation/Navigation.tsx +44 -52
- package/app/templates/Pagination/Pagination.tsx +6 -6
- package/app/templates/Tab/Tab.tsx +10 -10
- package/app/templates/Table/Table.tsx +9 -18
- package/app/templates/Theme/Theme.tsx +83 -80
- package/app/templates/Toast/Toast.tsx +4 -4
- package/build/build-tokens.js +204 -53
- package/build/css/_variables.css +269 -0
- package/build/typescript/theme.ts +14 -0
- package/cli/add.js +34 -2
- package/cli/init.js +82 -14
- package/css/badge.css +28 -0
- package/css/button.css +75 -0
- package/css/card.css +17 -0
- package/css/collapse.css +33 -0
- package/css/form.css +115 -0
- package/css/modal.css +98 -0
- package/css/navigation.css +69 -0
- package/css/pagination.css +66 -0
- package/css/tab.css +56 -0
- package/css/table.css +58 -0
- package/css/text.css +25 -0
- package/css/toast.css +69 -0
- package/package.json +9 -6
- package/tokens/tokens.json +949 -0
- package/app/templates/NavToggle/NavToggle.tsx +0 -1
- package/token.json +0 -492
package/README.md
CHANGED
|
@@ -1,7 +1,55 @@
|
|
|
1
|
-
|
|
1
|
+
# AtomSystem Design Token
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
with Figma,
|
|
5
|
-
with Storybook,
|
|
3
|
+
AtomSystem은 **디자인 토큰(Design Tokens)**을 중심으로 UI 컴포넌트와 문서를 관리하는 현대적인 디자인 시스템 프레임워크입니다. `tokens.json`을 단일 진실 공급원(Single Source of Truth)으로 하여 CSS 변수와 TypeScript 타입을 자동으로 생성하고, 이를 기반으로 React 컴포넌트와 MDX 가이드를 구축합니다.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
## ✨ 핵심 가치
|
|
6
|
+
|
|
7
|
+
- **Token-Driven Design**: 모든 스타일 값(색상, 간격, 타이포그래피 등)은 토큰으로 관리되어 디자인과 코드 간의 일관성을 유지합니다.
|
|
8
|
+
- **Automated Workflow**: Style Dictionary를 통해 토큰 변경 시 즉시 CSS 및 TS 파일이 업데이트됩니다.
|
|
9
|
+
- **Component-First Documentation**: 컴포넌트 생성과 동시에 MDX 문서가 연동되어 실시간 라이브러리 가이드를 제공합니다.
|
|
10
|
+
- **Multi-Theme Support**: 테마별 토큰 분리 관리를 통해 화이트/다크 모드 및 커스텀 테마를 손쉽게 지원합니다.
|
|
11
|
+
|
|
12
|
+
## 🛠 기술 스택
|
|
13
|
+
|
|
14
|
+
- **Core**: Next.js 15, React 18, TypeScript
|
|
15
|
+
- **Styling**: Tailwind CSS 4, CSS Modules
|
|
16
|
+
- **Design Token**: Style Dictionary 4, Tokens Studio
|
|
17
|
+
- **Animation**: GSAP, @gsap/react
|
|
18
|
+
- **Documentation**: MDX
|
|
19
|
+
|
|
20
|
+
## 📂 프로젝트 구조
|
|
21
|
+
|
|
22
|
+
- `/tokens`: 디자인 토큰의 원천 데이터 (`tokens.json`)
|
|
23
|
+
- `/build`: 빌드된 토큰 결과물 (CSS Variables, TS Objects)
|
|
24
|
+
- `/app/templates`: 디자인 시스템의 핵심 React 컴포넌트
|
|
25
|
+
- `/css`: 각 컴포넌트의 스타일 시트
|
|
26
|
+
- `/markdown`: 컴포넌트 가이드 및 예시를 담은 MDX 파일
|
|
27
|
+
- `/cli`: 디자인 시스템 관리를 위한 CLI 도구
|
|
28
|
+
|
|
29
|
+
## 🚀 시작하기
|
|
30
|
+
|
|
31
|
+
### 1. 의존성 설치
|
|
32
|
+
```bash
|
|
33
|
+
npm install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. 토큰 빌드 및 개발 서버 실행
|
|
37
|
+
`npm run dev`는 토큰을 먼저 빌드한 후 Next.js 개발 서버를 실행합니다.
|
|
38
|
+
```bash
|
|
39
|
+
npm run dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 3. 토큰만 빌드하기
|
|
43
|
+
```bash
|
|
44
|
+
npm run token-build
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 🔄 워크플로우
|
|
48
|
+
|
|
49
|
+
1. **토큰 정의**: `tokens/tokens.json`에서 새로운 스타일 값이나 컴포넌트 토큰을 정의합니다.
|
|
50
|
+
2. **토큰 빌드**: `npm run token-build`를 통해 `build/` 경로의 자산들을 업데이트합니다.
|
|
51
|
+
3. **컴포넌트 구현**: `app/templates/`에서 빌드된 TS 타입을 참조하여 컴포넌트를 작성합니다.
|
|
52
|
+
4. **문서화**: `markdown/`에서 MDX를 작성하여 컴포넌트 사용법을 기록합니다.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
© 2026 [AtomGround](https://system.atomground.com). All rights reserved.
|
|
@@ -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={`
|
|
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={`
|
|
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
|
}: {
|
|
@@ -11,17 +10,9 @@ export const Card = ({
|
|
|
11
10
|
type?: "standard" | "group";
|
|
12
11
|
overflow?: "overflow-auto" | "overflow-hidden" | "overflow-none";
|
|
13
12
|
}) => {
|
|
14
|
-
// CardHeader와 CardContent의 존재 여부 확인
|
|
15
|
-
const hasCardContent = React.Children.toArray(children).some(
|
|
16
|
-
(child) =>
|
|
17
|
-
React.isValidElement(child) &&
|
|
18
|
-
(child.type === CardHeader ||
|
|
19
|
-
child.type === CardContent ||
|
|
20
|
-
child.type === CardFooter)
|
|
21
|
-
);
|
|
22
13
|
return (
|
|
23
14
|
<div
|
|
24
|
-
className={
|
|
15
|
+
className={`card ${className} ${overflow}`}
|
|
25
16
|
>
|
|
26
17
|
{children}
|
|
27
18
|
</div>
|
|
@@ -35,7 +26,7 @@ export const CardHeader = ({
|
|
|
35
26
|
children: React.ReactNode;
|
|
36
27
|
noMargin?: boolean;
|
|
37
28
|
}) => {
|
|
38
|
-
return <div className={
|
|
29
|
+
return <div className={`card-header ${noMargin ? "no-margin" : ""}`}>{children}</div>;
|
|
39
30
|
};
|
|
40
31
|
|
|
41
32
|
export const CardContent = ({
|
|
@@ -45,7 +36,7 @@ export const CardContent = ({
|
|
|
45
36
|
children: React.ReactNode;
|
|
46
37
|
noMargin?: boolean;
|
|
47
38
|
}) => {
|
|
48
|
-
return <div className={
|
|
39
|
+
return <div className={`card-content ${noMargin ? "no-margin" : ""}`}>{children}</div>;
|
|
49
40
|
};
|
|
50
41
|
|
|
51
42
|
export const CardFooter = ({
|
|
@@ -55,5 +46,5 @@ export const CardFooter = ({
|
|
|
55
46
|
children: React.ReactNode;
|
|
56
47
|
noMargin?: boolean;
|
|
57
48
|
}) => {
|
|
58
|
-
return <div className={
|
|
49
|
+
return <div className={`card-footer ${noMargin ? "no-margin" : ""}`}>{children}</div>;
|
|
59
50
|
};
|
|
@@ -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-
|
|
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="
|
|
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
|
|
44
|
-
isActive(idx) ? "
|
|
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="
|
|
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="
|
|
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
|
|
100
|
-
isActive(idx) ? "
|
|
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="
|
|
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,53 +33,27 @@ 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
|
-
required,
|
|
59
54
|
disabled,
|
|
60
55
|
}: {
|
|
61
56
|
options: string[];
|
|
62
|
-
label?: string;
|
|
63
|
-
required?: boolean;
|
|
64
57
|
disabled?: boolean;
|
|
65
58
|
}) => {
|
|
66
59
|
const [filterValue, setFilterValue] = useState("");
|
|
@@ -76,16 +69,41 @@ export const Select = ({
|
|
|
76
69
|
option.toLowerCase().includes(filterValue.toLowerCase())
|
|
77
70
|
);
|
|
78
71
|
|
|
79
|
-
|
|
80
|
-
if (isOpen && inputRef.current) {
|
|
72
|
+
useLayoutEffect(() => {
|
|
73
|
+
if (isOpen && inputRef.current && dropdownRef.current) {
|
|
81
74
|
const rect = inputRef.current.getBoundingClientRect();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
75
|
+
const dropdownRect = dropdownRef.current.getBoundingClientRect();
|
|
76
|
+
|
|
77
|
+
if (Math.abs(menuPosition.width - rect.width) > 0.5) {
|
|
78
|
+
setMenuPosition({
|
|
79
|
+
top: rect.bottom + window.scrollY,
|
|
80
|
+
left: rect.left,
|
|
81
|
+
width: rect.width,
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let top = rect.bottom + window.scrollY;
|
|
87
|
+
|
|
88
|
+
if (rect.bottom + dropdownRect.height > window.innerHeight) {
|
|
89
|
+
top = rect.top + window.scrollY - dropdownRect.height;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
setMenuPosition((prev) => {
|
|
93
|
+
if (
|
|
94
|
+
Math.abs(prev.top - top) < 0.5 &&
|
|
95
|
+
Math.abs(prev.left - rect.left) < 0.5
|
|
96
|
+
) {
|
|
97
|
+
return prev;
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
top,
|
|
101
|
+
left: rect.left,
|
|
102
|
+
width: rect.width,
|
|
103
|
+
};
|
|
86
104
|
});
|
|
87
105
|
}
|
|
88
|
-
}, [isOpen]);
|
|
106
|
+
}, [isOpen, filterValue, menuPosition.width]);
|
|
89
107
|
|
|
90
108
|
useEffect(() => {
|
|
91
109
|
// 마우스 벗어난 영역 클릭 시 드롭다운 닫기
|
|
@@ -105,17 +123,6 @@ export const Select = ({
|
|
|
105
123
|
|
|
106
124
|
return (
|
|
107
125
|
<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
126
|
<label className="relative w-fit hidden md:block">
|
|
120
127
|
<input
|
|
121
128
|
ref={inputRef}
|
|
@@ -126,15 +133,7 @@ export const Select = ({
|
|
|
126
133
|
setFilterValue(e.target.value);
|
|
127
134
|
}}
|
|
128
135
|
onFocus={() => setIsOpen(true)}
|
|
129
|
-
className=
|
|
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
|
-
)}
|
|
136
|
+
className="select hidden sm:block"
|
|
138
137
|
disabled={disabled}
|
|
139
138
|
/>
|
|
140
139
|
<div
|
|
@@ -150,7 +149,7 @@ export const Select = ({
|
|
|
150
149
|
createPortal(
|
|
151
150
|
<ul
|
|
152
151
|
ref={dropdownRef}
|
|
153
|
-
className="absolute w-full
|
|
152
|
+
className="absolute w-full select-menu"
|
|
154
153
|
style={{
|
|
155
154
|
top: menuPosition.top,
|
|
156
155
|
left: menuPosition.left,
|
|
@@ -161,7 +160,7 @@ export const Select = ({
|
|
|
161
160
|
filteredOptions.map((option, index) => (
|
|
162
161
|
<li
|
|
163
162
|
key={index}
|
|
164
|
-
className="
|
|
163
|
+
className="select-menu-item"
|
|
165
164
|
onClick={() => {
|
|
166
165
|
setFilterValue(option);
|
|
167
166
|
setIsOpen(false);
|
|
@@ -171,19 +170,13 @@ export const Select = ({
|
|
|
171
170
|
</li>
|
|
172
171
|
))
|
|
173
172
|
) : (
|
|
174
|
-
<li className="
|
|
173
|
+
<li className="select-menu-item">No results</li>
|
|
175
174
|
)}
|
|
176
175
|
</ul>,
|
|
177
176
|
document.body
|
|
178
177
|
)}
|
|
179
178
|
<select
|
|
180
|
-
className=
|
|
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
|
-
)}
|
|
179
|
+
className="select block sm:hidden"
|
|
187
180
|
>
|
|
188
181
|
{options.map((option, index) => {
|
|
189
182
|
return (
|
|
@@ -204,6 +197,7 @@ export const Radio = ({
|
|
|
204
197
|
className,
|
|
205
198
|
onChange,
|
|
206
199
|
defaultChecked,
|
|
200
|
+
checked,
|
|
207
201
|
}: {
|
|
208
202
|
name: string;
|
|
209
203
|
value: string;
|
|
@@ -211,15 +205,16 @@ export const Radio = ({
|
|
|
211
205
|
className?: string;
|
|
212
206
|
onChange?: () => void;
|
|
213
207
|
defaultChecked?: boolean;
|
|
208
|
+
checked?: boolean;
|
|
214
209
|
}) => {
|
|
215
210
|
return (
|
|
216
211
|
<>
|
|
217
212
|
{label ? <p>{label}</p> : null}
|
|
218
|
-
<label className="flex items-center gap-
|
|
213
|
+
<label className="flex items-center gap-1">
|
|
219
214
|
<input
|
|
220
215
|
type="radio"
|
|
221
216
|
className={clsx(
|
|
222
|
-
"relative appearance-none
|
|
217
|
+
"relative appearance-none radio rounded-full ",
|
|
223
218
|
{
|
|
224
219
|
"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
220
|
true,
|
|
@@ -231,6 +226,7 @@ export const Radio = ({
|
|
|
231
226
|
name={name}
|
|
232
227
|
onChange={onChange}
|
|
233
228
|
defaultChecked={defaultChecked}
|
|
229
|
+
checked={checked}
|
|
234
230
|
/>
|
|
235
231
|
<span>{value}</span>
|
|
236
232
|
</label>
|
|
@@ -240,15 +236,12 @@ export const Radio = ({
|
|
|
240
236
|
|
|
241
237
|
export const Checkbox = ({
|
|
242
238
|
text,
|
|
243
|
-
label,
|
|
244
|
-
className,
|
|
245
239
|
onChange,
|
|
246
240
|
disabled,
|
|
247
241
|
required,
|
|
248
242
|
defaultChecked,
|
|
249
243
|
}: {
|
|
250
244
|
text?: string;
|
|
251
|
-
label?: string;
|
|
252
245
|
className?: string;
|
|
253
246
|
onChange?: () => void;
|
|
254
247
|
disabled?: boolean;
|
|
@@ -257,30 +250,16 @@ export const Checkbox = ({
|
|
|
257
250
|
}) => {
|
|
258
251
|
return (
|
|
259
252
|
<>
|
|
260
|
-
|
|
261
|
-
<label className="relative flex flex-nowrap items-center gap-1">
|
|
253
|
+
<label className="checkbox gap-1.5">
|
|
262
254
|
<input
|
|
263
255
|
type="checkbox"
|
|
264
|
-
className=
|
|
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
|
-
)}
|
|
256
|
+
className="appearance-none hidden"
|
|
278
257
|
onChange={onChange}
|
|
279
258
|
disabled={disabled}
|
|
280
259
|
required={required}
|
|
281
260
|
defaultChecked={defaultChecked}
|
|
282
261
|
/>
|
|
283
|
-
<span
|
|
262
|
+
<span>
|
|
284
263
|
<svg
|
|
285
264
|
xmlns="http://www.w3.org/2000/svg"
|
|
286
265
|
className="h-4 w-4"
|
|
@@ -297,8 +276,8 @@ export const Checkbox = ({
|
|
|
297
276
|
{text ? (
|
|
298
277
|
<span
|
|
299
278
|
className={clsx(
|
|
300
|
-
"text-
|
|
301
|
-
{ "!text-
|
|
279
|
+
"text-text-standard text-nowrap select-none",
|
|
280
|
+
{ "!text-text-disabled": disabled }
|
|
302
281
|
)}
|
|
303
282
|
>
|
|
304
283
|
{text}
|
|
@@ -29,11 +29,11 @@ export const ModalContent = ({
|
|
|
29
29
|
isOpen={isOpen}
|
|
30
30
|
onRequestClose={onRequestClose}
|
|
31
31
|
shouldCloseOnEsc={true}
|
|
32
|
-
overlayClassName="
|
|
33
|
-
className={`
|
|
32
|
+
overlayClassName="modal-overlay"
|
|
33
|
+
className={`modal modal-${size}`}
|
|
34
34
|
>
|
|
35
35
|
<ModalTitle onRequestClose={onRequestClose} title={modalTitle} />
|
|
36
|
-
<div className="
|
|
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="
|
|
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
|
|
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="
|
|
70
|
+
return <div className="modal-footer">{modalFooter}</div>;
|
|
71
71
|
};
|