@tide-tds/components 0.2.0 → 0.3.0
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/dist/Alert.d.ts +32 -0
- package/dist/Alert.js +47 -0
- package/dist/Avatar.d.ts +27 -0
- package/dist/Avatar.js +44 -0
- package/dist/Badge.d.ts +26 -0
- package/dist/Badge.js +27 -0
- package/dist/Breadcrumb.d.ts +26 -0
- package/dist/Breadcrumb.js +53 -0
- package/dist/Card.d.ts +13 -0
- package/dist/Card.js +18 -0
- package/dist/Pagination.d.ts +46 -0
- package/dist/Pagination.js +79 -0
- package/dist/Progress.d.ts +32 -0
- package/dist/Progress.js +40 -0
- package/dist/Separator.d.ts +18 -0
- package/dist/Separator.js +24 -0
- package/dist/Skeleton.d.ts +24 -0
- package/dist/Skeleton.js +29 -0
- package/dist/Table.d.ts +31 -0
- package/dist/Table.js +53 -0
- package/dist/alert-style.js +40 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +11 -1
- package/package.json +3 -3
package/dist/Alert.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Role } from "./types.js";
|
|
2
|
+
import { ComponentProps, ReactElement, ReactNode } from "react";
|
|
3
|
+
export interface AlertProps extends Omit<ComponentProps<"div">, "color" | "title" | "role" | "onClose"> {
|
|
4
|
+
/**
|
|
5
|
+
* Role. Tide Role 6개와 앱이 `RoleRegistry`에 더한 Role을 받는다. 기본값은 `info`다.
|
|
6
|
+
* 상태 알림은 `info`·`success`·`warning`·`danger`를 쓰고, 상태마다 모양이 다른 아이콘이 붙는다.
|
|
7
|
+
* `neutral`·`primary`·앱 Role은 일반 알림이라 `info`와 같은 아이콘이다.
|
|
8
|
+
* 색 계열 이름(`blue` 등)은 받지 않는다. 알림의 뜻을 색으로 전하기 때문이다(ADR 0016).
|
|
9
|
+
* 루트에 `data-color`로 붙고 바깥 요소의 색을 물려받지 않는다.
|
|
10
|
+
*/
|
|
11
|
+
color?: Role;
|
|
12
|
+
/** 제목. 굵은 글자로 본문 위에 놓인다. HTML `title` 속성(툴팁)은 받지 않는다. */
|
|
13
|
+
title?: ReactNode;
|
|
14
|
+
/** 본문. 문장, 목록, 같은 Role의 `Link`를 넣는다. */
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* live region 역할. 기본값은 없음(정적 표시)이다.
|
|
18
|
+
* 처음부터 화면에 있는 Alert는 스크린 리더가 알리지 않으므로 역할을 붙이지 않는다.
|
|
19
|
+
* 사용자 동작 뒤에 새로 나타나 바로 알려야 하면 `"alert"`(급하고 중요함, 읽던 것을 끊음),
|
|
20
|
+
* 끊지 않고 알리면 되면 `"status"`(정중하게 알림)를 준다.
|
|
21
|
+
*/
|
|
22
|
+
role?: "alert" | "status";
|
|
23
|
+
/** 닫기 버튼을 눌렀을 때 부른다. 주면 끝쪽에 닫기 버튼(IconButton)이 생긴다. Alert를 빼는 일은 앱이 한다. */
|
|
24
|
+
onClose?: () => void;
|
|
25
|
+
/** 닫기 버튼 이름. 기본값은 `"닫기"`다. 같은 화면에 Alert가 여럿이면 `"결제 알림 닫기"`처럼 구분한다. */
|
|
26
|
+
closeLabel?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 화면 안에서 상태(info·success·warning·danger)나 알릴 내용을 보여 주는 표면.
|
|
30
|
+
* 상태 아이콘(장식용), 제목, 본문, 닫기 버튼으로 이뤄진다. 서버 컴포넌트로 쓸 수 있다.
|
|
31
|
+
*/
|
|
32
|
+
export declare function Alert({ color, title, children, onClose, closeLabel, className, ...props }: AlertProps): ReactElement;
|
package/dist/Alert.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { alertCloseClass, alertContentClass, alertDescriptionClass, alertIconClass, alertLayoutClass, alertStatusIcon, alertSurfaceClass, alertTitleClass } from "./alert-style.js";
|
|
2
|
+
import { cn } from "./cn.js";
|
|
3
|
+
import { IconButton } from "./IconButton.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { XIcon } from "@tide-tds/icons";
|
|
6
|
+
|
|
7
|
+
// src/Alert.tsx
|
|
8
|
+
/**
|
|
9
|
+
* 화면 안에서 상태(info·success·warning·danger)나 알릴 내용을 보여 주는 표면.
|
|
10
|
+
* 상태 아이콘(장식용), 제목, 본문, 닫기 버튼으로 이뤄진다. 서버 컴포넌트로 쓸 수 있다.
|
|
11
|
+
*/
|
|
12
|
+
function Alert({ color = "info", title, children, onClose, closeLabel = "닫기", className, ...props }) {
|
|
13
|
+
const StatusIcon = alertStatusIcon(color);
|
|
14
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
15
|
+
...props,
|
|
16
|
+
"data-color": color,
|
|
17
|
+
className: cn(alertLayoutClass, alertSurfaceClass, className),
|
|
18
|
+
children: [
|
|
19
|
+
/* @__PURE__ */ jsx(StatusIcon, {
|
|
20
|
+
size: 20,
|
|
21
|
+
"aria-hidden": "true",
|
|
22
|
+
className: alertIconClass
|
|
23
|
+
}),
|
|
24
|
+
/* @__PURE__ */ jsxs("div", {
|
|
25
|
+
className: alertContentClass,
|
|
26
|
+
children: [title !== void 0 && /* @__PURE__ */ jsx("div", {
|
|
27
|
+
className: "text-body-sm font-semibold",
|
|
28
|
+
children: title
|
|
29
|
+
}), children !== void 0 && /* @__PURE__ */ jsx("div", {
|
|
30
|
+
className: "text-body-sm",
|
|
31
|
+
children
|
|
32
|
+
})]
|
|
33
|
+
}),
|
|
34
|
+
onClose !== void 0 && /* @__PURE__ */ jsx(IconButton, {
|
|
35
|
+
"aria-label": closeLabel,
|
|
36
|
+
variant: "ghost",
|
|
37
|
+
color,
|
|
38
|
+
size: "xs",
|
|
39
|
+
onClick: onClose,
|
|
40
|
+
className: "-my-0.5 -me-1",
|
|
41
|
+
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
42
|
+
})
|
|
43
|
+
]
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { Alert };
|
package/dist/Avatar.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ColorFamilyName, Size } from "./types.js";
|
|
2
|
+
import { ComponentProps, ReactElement } from "react";
|
|
3
|
+
/** Avatar의 size. 공통 size 4개를 모두 받는다. 높이는 spec size 표와 같다(24·32·40·48px). */
|
|
4
|
+
type AvatarSize = Extract<Size, "xs" | "sm" | "md" | "lg">;
|
|
5
|
+
export interface AvatarProps extends Omit<ComponentProps<"span">, "color" | "children" | "role" | "aria-label"> {
|
|
6
|
+
/** 사용자 이미지 주소. 없거나 빈 문자열이거나 불러오지 못하면 fallback 아이콘이 보인다. */
|
|
7
|
+
src?: string;
|
|
8
|
+
/**
|
|
9
|
+
* 대체 텍스트(보통 사람 이름). 필수다. 주면 루트가 `role="img"`가 되고 이 텍스트가 이름이 된다.
|
|
10
|
+
* 옆에 이름이 이미 적혀 있는 장식용이면 `""`를 주어 읽히지 않게 한다. 이미지가 없어도 같다.
|
|
11
|
+
*/
|
|
12
|
+
alt: string;
|
|
13
|
+
/** `xs` 24px, `sm` 32px, `md` 40px, `lg` 48px. 기본값은 `md`다. */
|
|
14
|
+
size?: AvatarSize;
|
|
15
|
+
/**
|
|
16
|
+
* fallback 배경과 아이콘의 색 계열(Tide 색 계열 6개와 앱이 `ColorFamilyRegistry`에 더한 계열).
|
|
17
|
+
* 뜻이 없는 장식 색이라 Role은 받지 않는다(ADR 0016·0017 후속). 기본값은 `gray`다.
|
|
18
|
+
* 루트에 `data-color`로 붙고 바깥 요소의 색을 물려받지 않는다.
|
|
19
|
+
*/
|
|
20
|
+
color?: ColorFamilyName;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 사용자 이미지를 원 안에 보여 준다. 이미지가 없거나 불러오지 못하면 fallback 아이콘이 보인다.
|
|
24
|
+
* 누르는 동작이 없다. 클릭이 필요하면 Button이나 IconButton 안에 넣는다. 서버 컴포넌트로 쓸 수 있다.
|
|
25
|
+
*/
|
|
26
|
+
export declare function Avatar({ src, alt, size, color, className, ...props }: AvatarProps): ReactElement;
|
|
27
|
+
export {};
|
package/dist/Avatar.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { cn } from "./cn.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { UserIcon } from "@tide-tds/icons";
|
|
4
|
+
|
|
5
|
+
// src/Avatar.tsx
|
|
6
|
+
/** 원 크기와 fallback 아이콘 크기(원의 약 60%). */
|
|
7
|
+
const sizeClass = {
|
|
8
|
+
xs: "size-6 [&_svg]:size-4",
|
|
9
|
+
sm: "size-8 [&_svg]:size-5",
|
|
10
|
+
md: "size-10 [&_svg]:size-6",
|
|
11
|
+
lg: "size-12 [&_svg]:size-7"
|
|
12
|
+
};
|
|
13
|
+
const baseClass = "relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-accent-subtle text-accent align-middle select-none [&_svg]:shrink-0";
|
|
14
|
+
/** 이미지 층. 아이콘 위에 원 전체를 덮고, `object-fit: cover`처럼 가운데를 채운다. */
|
|
15
|
+
const imageClass = "absolute inset-0 bg-cover bg-center bg-no-repeat";
|
|
16
|
+
/**
|
|
17
|
+
* CSS `url("…")` 값을 만든다. 따옴표·역슬래시·줄바꿈을 CSS escape(`\HH `)로 바꿔 문자열 밖으로 나가지 못하게 한다.
|
|
18
|
+
*/
|
|
19
|
+
function cssUrl(src) {
|
|
20
|
+
return `url("${src.replace(/["\\\n\r\f]/g, (char) => `\\${char.charCodeAt(0).toString(16)} `)}")`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 사용자 이미지를 원 안에 보여 준다. 이미지가 없거나 불러오지 못하면 fallback 아이콘이 보인다.
|
|
24
|
+
* 누르는 동작이 없다. 클릭이 필요하면 Button이나 IconButton 안에 넣는다. 서버 컴포넌트로 쓸 수 있다.
|
|
25
|
+
*/
|
|
26
|
+
function Avatar({ src, alt, size = "md", color = "gray", className, ...props }) {
|
|
27
|
+
const named = alt !== "";
|
|
28
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
29
|
+
...props,
|
|
30
|
+
...named ? {
|
|
31
|
+
role: "img",
|
|
32
|
+
"aria-label": alt
|
|
33
|
+
} : {},
|
|
34
|
+
"data-color": color,
|
|
35
|
+
className: cn(baseClass, sizeClass[size], className),
|
|
36
|
+
children: [/* @__PURE__ */ jsx(UserIcon, { "aria-hidden": "true" }), src ? /* @__PURE__ */ jsx("span", {
|
|
37
|
+
"aria-hidden": "true",
|
|
38
|
+
className: imageClass,
|
|
39
|
+
style: { backgroundImage: cssUrl(src) }
|
|
40
|
+
}) : null]
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { Avatar };
|
package/dist/Badge.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Color, FillVariant, Size } from "./types.js";
|
|
2
|
+
import { ComponentProps, ReactElement, ReactNode } from "react";
|
|
3
|
+
/** Badge의 채움 방식. 상호작용이 없어 테두리·투명 채움(`outline`·`ghost`)은 받지 않는다. */
|
|
4
|
+
type BadgeVariant = Extract<FillVariant, "subtle" | "solid">;
|
|
5
|
+
/** Badge의 size. 조작 요소가 아니라 높이가 size 표보다 한 단계 작다(`sm` 20px, `md` 24px). */
|
|
6
|
+
type BadgeSize = Extract<Size, "sm" | "md">;
|
|
7
|
+
export interface BadgeProps extends Omit<ComponentProps<"span">, "color" | "children"> {
|
|
8
|
+
/** 상태를 말하는 텍스트. 필수다. 색만으로 상태를 전하지 않기 위해서다(WCAG 1.4.1). */
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
/** `subtle`(옅은 배경, 기본값)과 `solid`(진한 배경). 같은 화면 안에서는 한 가지로 맞춘다. */
|
|
11
|
+
variant?: BadgeVariant;
|
|
12
|
+
/** `sm` 높이 20px·`label-sm`, `md` 높이 24px·`label-md`. 기본값은 `md`다. */
|
|
13
|
+
size?: BadgeSize;
|
|
14
|
+
/**
|
|
15
|
+
* 색 이름. 상태는 Role(Tide Role 6개와 앱이 `RoleRegistry`에 더한 Role), 뜻 없는 분류는 색 계열 이름
|
|
16
|
+
* (Tide 색 계열 6개와 앱이 `ColorFamilyRegistry`에 더한 계열)을 쓴다(ADR 0016·0017). 기본값은 `neutral`이다.
|
|
17
|
+
* 루트에 `data-color`로 붙고 바깥 요소의 색을 물려받지 않는다.
|
|
18
|
+
*/
|
|
19
|
+
color?: Color;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 상태나 분류를 짧은 텍스트로 보여 주는 라벨. 누르거나 고르는 동작이 없다.
|
|
23
|
+
* 상태는 Role, 분류는 색 계열 이름으로 색을 고르고, 뜻은 반드시 텍스트로 적는다. 서버 컴포넌트로 쓸 수 있다.
|
|
24
|
+
*/
|
|
25
|
+
export declare function Badge({ variant, size, color, className, ...props }: BadgeProps): ReactElement;
|
|
26
|
+
export {};
|
package/dist/Badge.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cn } from "./cn.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
// src/Badge.tsx
|
|
5
|
+
const variantClass = {
|
|
6
|
+
subtle: "bg-accent-subtle text-accent",
|
|
7
|
+
solid: "bg-accent-solid text-accent-on-solid"
|
|
8
|
+
};
|
|
9
|
+
/** 높이는 border 1px를 포함한다. 글자 줄 높이(label-sm 16px, label-md 20px) + border 2px + 세로 여백이다. */
|
|
10
|
+
const sizeClass = {
|
|
11
|
+
sm: "h-5 gap-1 px-1.5 text-label-sm",
|
|
12
|
+
md: "h-6 gap-1 px-2 text-label-md"
|
|
13
|
+
};
|
|
14
|
+
const baseClass = "inline-flex max-w-full shrink-0 items-center rounded-sm border border-transparent align-middle whitespace-nowrap [&_svg]:size-3 [&_svg]:shrink-0";
|
|
15
|
+
/**
|
|
16
|
+
* 상태나 분류를 짧은 텍스트로 보여 주는 라벨. 누르거나 고르는 동작이 없다.
|
|
17
|
+
* 상태는 Role, 분류는 색 계열 이름으로 색을 고르고, 뜻은 반드시 텍스트로 적는다. 서버 컴포넌트로 쓸 수 있다.
|
|
18
|
+
*/
|
|
19
|
+
function Badge({ variant = "subtle", size = "md", color = "neutral", className, ...props }) {
|
|
20
|
+
return /* @__PURE__ */ jsx("span", {
|
|
21
|
+
...props,
|
|
22
|
+
"data-color": color,
|
|
23
|
+
className: cn(baseClass, variantClass[variant], sizeClass[size], className)
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { Badge };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ComponentProps, ReactElement } from "react";
|
|
2
|
+
export interface BreadcrumbProps extends ComponentProps<"nav"> {
|
|
3
|
+
/** 탐색 영역 이름. 기본값은 `"이동 경로"`다. 한 화면에 같은 이름의 탐색 영역이 겹치지 않게 한다. */
|
|
4
|
+
"aria-label"?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface BreadcrumbItemProps extends ComponentProps<"li"> {}
|
|
7
|
+
export interface BreadcrumbLinkProps extends Omit<ComponentProps<"a">, "color"> {
|
|
8
|
+
/**
|
|
9
|
+
* 다른 요소로 렌더한다. 예: `render={<NextLink href="/docs" />}`.
|
|
10
|
+
* props, ref, className을 그 요소에 병합한다.
|
|
11
|
+
*/
|
|
12
|
+
render?: ReactElement;
|
|
13
|
+
}
|
|
14
|
+
export interface BreadcrumbCurrentProps extends ComponentProps<"span"> {}
|
|
15
|
+
/**
|
|
16
|
+
* 현재 페이지까지의 경로를 보여 주는 탐색 영역. `<nav>` 안의 `<ol>`이다.
|
|
17
|
+
* 자식은 `BreadcrumbItem`이고, 그 안에 `BreadcrumbLink`(상위 페이지)나 `BreadcrumbCurrent`(현재 페이지)를 둔다.
|
|
18
|
+
* `ref`·`className`은 `<nav>`에 붙는다.
|
|
19
|
+
*/
|
|
20
|
+
export declare function Breadcrumb({ "aria-label": ariaLabel, children, ...props }: BreadcrumbProps): ReactElement;
|
|
21
|
+
/** 경로의 한 항목 `<li>`. 앞에 장식용 구분자(›)를 붙이고, 목록의 첫 항목에서는 숨긴다. */
|
|
22
|
+
export declare function BreadcrumbItem({ className, children, ...props }: BreadcrumbItemProps): ReactElement;
|
|
23
|
+
/** 상위 페이지 링크 `<a>`. 밑줄이 있고 hover에 짙어진다. Next.js에서는 `render={<NextLink href="/x" />}`를 쓴다. */
|
|
24
|
+
export declare function BreadcrumbLink({ render, ...props }: BreadcrumbLinkProps): ReactElement;
|
|
25
|
+
/** 현재 페이지 `<span aria-current="page">`. 링크가 아니다. 경로의 마지막 항목에 둔다. */
|
|
26
|
+
export declare function BreadcrumbCurrent({ className, ...props }: BreadcrumbCurrentProps): ReactElement;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { cn } from "./cn.js";
|
|
2
|
+
import { renderElement } from "./render.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { ChevronRightIcon } from "@tide-tds/icons";
|
|
5
|
+
|
|
6
|
+
// src/Breadcrumb.tsx
|
|
7
|
+
const listClass = "flex flex-wrap items-center gap-1.5 text-body-sm text-muted";
|
|
8
|
+
const itemClass = "inline-flex items-center gap-1.5";
|
|
9
|
+
const separatorClass = "size-4 shrink-0 [li:first-child>&]:hidden";
|
|
10
|
+
const linkClass = "cursor-pointer rounded-sm underline decoration-1 underline-offset-2 hover:text-default hover:decoration-2 focus-visible:focus-ring";
|
|
11
|
+
const currentClass = "text-label-md text-default";
|
|
12
|
+
/**
|
|
13
|
+
* 현재 페이지까지의 경로를 보여 주는 탐색 영역. `<nav>` 안의 `<ol>`이다.
|
|
14
|
+
* 자식은 `BreadcrumbItem`이고, 그 안에 `BreadcrumbLink`(상위 페이지)나 `BreadcrumbCurrent`(현재 페이지)를 둔다.
|
|
15
|
+
* `ref`·`className`은 `<nav>`에 붙는다.
|
|
16
|
+
*/
|
|
17
|
+
function Breadcrumb({ "aria-label": ariaLabel = "이동 경로", children, ...props }) {
|
|
18
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
19
|
+
...props,
|
|
20
|
+
"aria-label": ariaLabel,
|
|
21
|
+
children: /* @__PURE__ */ jsx("ol", {
|
|
22
|
+
className: listClass,
|
|
23
|
+
children
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** 경로의 한 항목 `<li>`. 앞에 장식용 구분자(›)를 붙이고, 목록의 첫 항목에서는 숨긴다. */
|
|
28
|
+
function BreadcrumbItem({ className, children, ...props }) {
|
|
29
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
30
|
+
...props,
|
|
31
|
+
className: cn(itemClass, className),
|
|
32
|
+
children: [/* @__PURE__ */ jsx(ChevronRightIcon, { className: separatorClass }), children]
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** 상위 페이지 링크 `<a>`. 밑줄이 있고 hover에 짙어진다. Next.js에서는 `render={<NextLink href="/x" />}`를 쓴다. */
|
|
36
|
+
function BreadcrumbLink({ render, ...props }) {
|
|
37
|
+
return renderElement({
|
|
38
|
+
defaultElement: "a",
|
|
39
|
+
render,
|
|
40
|
+
baseClassName: linkClass,
|
|
41
|
+
props
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/** 현재 페이지 `<span aria-current="page">`. 링크가 아니다. 경로의 마지막 항목에 둔다. */
|
|
45
|
+
function BreadcrumbCurrent({ className, ...props }) {
|
|
46
|
+
return /* @__PURE__ */ jsx("span", {
|
|
47
|
+
"aria-current": "page",
|
|
48
|
+
...props,
|
|
49
|
+
className: cn(currentClass, className)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { Breadcrumb, BreadcrumbCurrent, BreadcrumbItem, BreadcrumbLink };
|
package/dist/Card.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ComponentProps, ReactElement } from "react";
|
|
2
|
+
export interface CardProps extends ComponentProps<"div"> {
|
|
3
|
+
/**
|
|
4
|
+
* 다른 요소로 렌더한다. 예: `render={<article />}`, `render={<li />}`.
|
|
5
|
+
* 묶음의 뜻이 있으면 그 요소를 고른다. props, ref, className을 그 요소에 병합한다.
|
|
6
|
+
*/
|
|
7
|
+
render?: ReactElement;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 관련 내용을 한 덩어리로 묶는 surface 표면 컨테이너. 경계만 있고 그림자는 없다.
|
|
11
|
+
* 기본 요소는 `div`이고, `render`로 `article`·`section`·`li` 등을 쓴다. 서버 컴포넌트로 쓸 수 있다.
|
|
12
|
+
*/
|
|
13
|
+
export declare function Card({ render, ...props }: CardProps): ReactElement;
|
package/dist/Card.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { renderElement } from "./render.js";
|
|
2
|
+
|
|
3
|
+
// src/Card.tsx
|
|
4
|
+
const baseClass = "rounded-lg border border-default bg-surface p-4 text-default";
|
|
5
|
+
/**
|
|
6
|
+
* 관련 내용을 한 덩어리로 묶는 surface 표면 컨테이너. 경계만 있고 그림자는 없다.
|
|
7
|
+
* 기본 요소는 `div`이고, `render`로 `article`·`section`·`li` 등을 쓴다. 서버 컴포넌트로 쓸 수 있다.
|
|
8
|
+
*/
|
|
9
|
+
function Card({ render, ...props }) {
|
|
10
|
+
return renderElement({
|
|
11
|
+
defaultElement: "div",
|
|
12
|
+
render,
|
|
13
|
+
baseClassName: baseClass,
|
|
14
|
+
props
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { Card };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { IconButtonProps } from "./IconButton.js";
|
|
2
|
+
import { ComponentProps, ReactElement, ReactNode } from "react";
|
|
3
|
+
export interface PaginationProps extends ComponentProps<"nav"> {
|
|
4
|
+
/** 탐색 영역 이름. 기본값은 `"페이지 이동"`이다. 한 화면에 목록이 여럿이면 `"댓글 페이지"`처럼 구분한다. */
|
|
5
|
+
"aria-label"?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PaginationItemProps extends ComponentProps<"li"> {}
|
|
8
|
+
export interface PaginationLinkProps extends Omit<ComponentProps<"a">, "color"> {
|
|
9
|
+
/** 현재 페이지인지. `aria-current="page"`를 붙이고 subtle 채움으로 보인다. 기본값은 `false`다. */
|
|
10
|
+
current?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* 다른 요소로 렌더한다. 예: `render={<NextLink href="?page=2" />}`.
|
|
13
|
+
* 상태로 페이지를 바꾸면 `render={<button type="button" onClick={...} />}`를 쓴다. props, ref, className을 병합한다.
|
|
14
|
+
*/
|
|
15
|
+
render?: ReactElement;
|
|
16
|
+
}
|
|
17
|
+
/** 이전/다음 항목 props. IconButton의 props에서 모양(variant·color·size)과 아이콘·로딩을 고정했다. */
|
|
18
|
+
export interface PaginationArrowProps extends Omit<IconButtonProps, "aria-label" | "children" | "variant" | "color" | "size" | "loading" | "loadingLabel"> {
|
|
19
|
+
/** 버튼 이름. 기본값은 이전 `"이전 페이지"`, 다음 `"다음 페이지"`다. */
|
|
20
|
+
"aria-label"?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface PaginationPreviousProps extends PaginationArrowProps {}
|
|
23
|
+
export interface PaginationNextProps extends PaginationArrowProps {}
|
|
24
|
+
export interface PaginationEllipsisProps extends Omit<ComponentProps<"span">, "children"> {
|
|
25
|
+
/** 스크린리더가 읽는 텍스트. 화면에는 「…」 아이콘만 보인다. 기본값은 `"페이지 생략"`이다. */
|
|
26
|
+
children?: ReactNode;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 페이지 이동 탐색 영역. `<nav>` 안의 `<ul>`이다.
|
|
30
|
+
* 자식은 `PaginationItem`이고, 그 안에 `PaginationPrevious`·`PaginationLink`·`PaginationEllipsis`·`PaginationNext`를 둔다.
|
|
31
|
+
* `ref`·`className`은 `<nav>`에 붙는다.
|
|
32
|
+
*/
|
|
33
|
+
export declare function Pagination({ "aria-label": ariaLabel, children, ...props }: PaginationProps): ReactElement;
|
|
34
|
+
/** 목록의 한 항목 `<li>`. 기본 class가 없다. */
|
|
35
|
+
export declare function PaginationItem(props: PaginationItemProps): ReactElement;
|
|
36
|
+
/**
|
|
37
|
+
* 페이지 번호 링크. 기본은 `<a>`이고 Button(ghost, 32px) 모양이다. 현재 페이지는 `current`를 준다.
|
|
38
|
+
* Next.js에서는 `render={<NextLink href="?page=2" />}`를 쓴다.
|
|
39
|
+
*/
|
|
40
|
+
export declare function PaginationLink({ current, render, className, ...props }: PaginationLinkProps): ReactElement;
|
|
41
|
+
/** 이전 페이지로 가는 IconButton(ghost, 32px, ‹). 첫 페이지에서는 `disabled`를 준다. */
|
|
42
|
+
export declare function PaginationPrevious({ "aria-label": ariaLabel, ...props }: PaginationPreviousProps): ReactElement;
|
|
43
|
+
/** 다음 페이지로 가는 IconButton(ghost, 32px, ›). 마지막 페이지에서는 `disabled`를 준다. */
|
|
44
|
+
export declare function PaginationNext({ "aria-label": ariaLabel, ...props }: PaginationNextProps): ReactElement;
|
|
45
|
+
/** 생략된 번호 자리 `<span>`. 화면에는 「…」 아이콘, 스크린리더에는 텍스트(기본 `"페이지 생략"`)를 보인다. */
|
|
46
|
+
export declare function PaginationEllipsis({ className, children, ...props }: PaginationEllipsisProps): ReactElement;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Button } from "./Button.js";
|
|
2
|
+
import { cn } from "./cn.js";
|
|
3
|
+
import { IconButton } from "./IconButton.js";
|
|
4
|
+
import { VisuallyHidden } from "./VisuallyHidden.js";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { ChevronLeftIcon, ChevronRightIcon, EllipsisIcon } from "@tide-tds/icons";
|
|
7
|
+
|
|
8
|
+
// src/Pagination.tsx
|
|
9
|
+
/** 페이지 번호·이전/다음 항목의 고정 size. 둘 다 32×32px다(24×24 이상). */
|
|
10
|
+
const itemSize = "sm";
|
|
11
|
+
const listClass = "flex flex-wrap items-center gap-1";
|
|
12
|
+
const linkClass = "min-w-8 px-2 tabular-nums";
|
|
13
|
+
const ellipsisClass = "inline-flex size-8 items-center justify-center text-muted";
|
|
14
|
+
/**
|
|
15
|
+
* 페이지 이동 탐색 영역. `<nav>` 안의 `<ul>`이다.
|
|
16
|
+
* 자식은 `PaginationItem`이고, 그 안에 `PaginationPrevious`·`PaginationLink`·`PaginationEllipsis`·`PaginationNext`를 둔다.
|
|
17
|
+
* `ref`·`className`은 `<nav>`에 붙는다.
|
|
18
|
+
*/
|
|
19
|
+
function Pagination({ "aria-label": ariaLabel = "페이지 이동", children, ...props }) {
|
|
20
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
21
|
+
...props,
|
|
22
|
+
"aria-label": ariaLabel,
|
|
23
|
+
children: /* @__PURE__ */ jsx("ul", {
|
|
24
|
+
className: listClass,
|
|
25
|
+
children
|
|
26
|
+
})
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** 목록의 한 항목 `<li>`. 기본 class가 없다. */
|
|
30
|
+
function PaginationItem(props) {
|
|
31
|
+
return /* @__PURE__ */ jsx("li", { ...props });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 페이지 번호 링크. 기본은 `<a>`이고 Button(ghost, 32px) 모양이다. 현재 페이지는 `current`를 준다.
|
|
35
|
+
* Next.js에서는 `render={<NextLink href="?page=2" />}`를 쓴다.
|
|
36
|
+
*/
|
|
37
|
+
function PaginationLink({ current = false, render, className, ...props }) {
|
|
38
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
39
|
+
...props,
|
|
40
|
+
render: render ?? /* @__PURE__ */ jsx("a", {}),
|
|
41
|
+
variant: current ? "subtle" : "ghost",
|
|
42
|
+
color: current ? "primary" : "neutral",
|
|
43
|
+
size: itemSize,
|
|
44
|
+
"aria-current": current ? "page" : props["aria-current"],
|
|
45
|
+
className: cn(linkClass, className)
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/** 이전 페이지로 가는 IconButton(ghost, 32px, ‹). 첫 페이지에서는 `disabled`를 준다. */
|
|
49
|
+
function PaginationPrevious({ "aria-label": ariaLabel = "이전 페이지", ...props }) {
|
|
50
|
+
return /* @__PURE__ */ jsx(IconButton, {
|
|
51
|
+
...props,
|
|
52
|
+
"aria-label": ariaLabel,
|
|
53
|
+
variant: "ghost",
|
|
54
|
+
color: "neutral",
|
|
55
|
+
size: itemSize,
|
|
56
|
+
children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** 다음 페이지로 가는 IconButton(ghost, 32px, ›). 마지막 페이지에서는 `disabled`를 준다. */
|
|
60
|
+
function PaginationNext({ "aria-label": ariaLabel = "다음 페이지", ...props }) {
|
|
61
|
+
return /* @__PURE__ */ jsx(IconButton, {
|
|
62
|
+
...props,
|
|
63
|
+
"aria-label": ariaLabel,
|
|
64
|
+
variant: "ghost",
|
|
65
|
+
color: "neutral",
|
|
66
|
+
size: itemSize,
|
|
67
|
+
children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/** 생략된 번호 자리 `<span>`. 화면에는 「…」 아이콘, 스크린리더에는 텍스트(기본 `"페이지 생략"`)를 보인다. */
|
|
71
|
+
function PaginationEllipsis({ className, children = "페이지 생략", ...props }) {
|
|
72
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
73
|
+
...props,
|
|
74
|
+
className: cn(ellipsisClass, className),
|
|
75
|
+
children: [/* @__PURE__ */ jsx(EllipsisIcon, {}), /* @__PURE__ */ jsx(VisuallyHidden, { children })]
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { Pagination, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Role, Size } from "./types.js";
|
|
2
|
+
import { ComponentProps, ReactElement } from "react";
|
|
3
|
+
/** Progress의 size. 막대 굵기다. */
|
|
4
|
+
type ProgressSize = Extract<Size, "sm" | "md">;
|
|
5
|
+
/** 접근 가능한 이름. `aria-label`이나 `aria-labelledby` 중 하나는 꼭 준다(WAI-ARIA progressbar는 이름이 필요하다). */
|
|
6
|
+
type ProgressName = {
|
|
7
|
+
"aria-label": string;
|
|
8
|
+
"aria-labelledby"?: string;
|
|
9
|
+
} | {
|
|
10
|
+
"aria-label"?: string;
|
|
11
|
+
"aria-labelledby": string;
|
|
12
|
+
};
|
|
13
|
+
interface ProgressOwnProps extends Omit<ComponentProps<"div">, "color" | "children" | "role" | "aria-label" | "aria-labelledby" | "aria-valuenow" | "aria-valuemin" | "aria-valuemax"> {
|
|
14
|
+
/** 현재 값. 0 이상 `max` 이하로 잘라서 쓴다. 값을 모르는 진행은 Progress가 아니라 Spinner로 보여 준다. */
|
|
15
|
+
value: number;
|
|
16
|
+
/** 끝 값. 기본값은 `100`이다. 시작 값은 항상 0이다. */
|
|
17
|
+
max?: number;
|
|
18
|
+
/** 막대 굵기. `sm` 4px, `md` 8px. 기본값은 `md`다. */
|
|
19
|
+
size?: ProgressSize;
|
|
20
|
+
/**
|
|
21
|
+
* 막대 색. 진행 상태는 뜻이 있는 표시라 Role만 받는다(Tide Role 6개와 앱이 `RoleRegistry`에 더한 Role, ADR 0016).
|
|
22
|
+
* 기본값은 `primary`다. 루트에 `data-color`로 붙고 바깥 요소의 색을 물려받지 않는다.
|
|
23
|
+
*/
|
|
24
|
+
color?: Role;
|
|
25
|
+
}
|
|
26
|
+
export type ProgressProps = ProgressOwnProps & ProgressName;
|
|
27
|
+
/**
|
|
28
|
+
* 작업이 얼마나 진행됐는지 막대로 보여 준다. 역할은 `progressbar`이고 값은 `aria-valuenow`로 나간다.
|
|
29
|
+
* 움직임 줄이기에서도 진행 표시는 그대로다. 서버 컴포넌트로 쓸 수 있다.
|
|
30
|
+
*/
|
|
31
|
+
export declare function Progress({ value, max, size, color, className, ...props }: ProgressProps): ReactElement;
|
|
32
|
+
export {};
|
package/dist/Progress.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { cn } from "./cn.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
// src/Progress.tsx
|
|
5
|
+
const sizeClass = {
|
|
6
|
+
sm: "h-1",
|
|
7
|
+
md: "h-2"
|
|
8
|
+
};
|
|
9
|
+
const trackClass = "relative block w-full overflow-hidden rounded-full bg-accent-subtle forced-colors:forced-color-adjust-none forced-colors:border forced-colors:border-[CanvasText] forced-colors:bg-[Canvas]";
|
|
10
|
+
const barClass = "block h-full rounded-full bg-current text-accent motion-safe:transition-[width] forced-colors:text-[Highlight]";
|
|
11
|
+
/** 0 이상 max 이하로 자른다. max가 0 이하이거나 숫자가 아니면 0으로 본다. */
|
|
12
|
+
function clamp(value, max) {
|
|
13
|
+
if (!Number.isFinite(value) || !Number.isFinite(max) || max <= 0) return 0;
|
|
14
|
+
return Math.min(Math.max(value, 0), max);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 작업이 얼마나 진행됐는지 막대로 보여 준다. 역할은 `progressbar`이고 값은 `aria-valuenow`로 나간다.
|
|
18
|
+
* 움직임 줄이기에서도 진행 표시는 그대로다. 서버 컴포넌트로 쓸 수 있다.
|
|
19
|
+
*/
|
|
20
|
+
function Progress({ value, max = 100, size = "md", color = "primary", className, ...props }) {
|
|
21
|
+
const safeMax = Number.isFinite(max) && max > 0 ? max : 100;
|
|
22
|
+
const now = clamp(value, safeMax);
|
|
23
|
+
const percent = now / safeMax * 100;
|
|
24
|
+
return /* @__PURE__ */ jsx("div", {
|
|
25
|
+
...props,
|
|
26
|
+
role: "progressbar",
|
|
27
|
+
"aria-valuenow": now,
|
|
28
|
+
"aria-valuemin": 0,
|
|
29
|
+
"aria-valuemax": safeMax,
|
|
30
|
+
"data-color": color,
|
|
31
|
+
className: cn(trackClass, sizeClass[size], className),
|
|
32
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
33
|
+
"aria-hidden": "true",
|
|
34
|
+
className: barClass,
|
|
35
|
+
style: { width: `${percent}%` }
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { Progress };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ComponentProps, ReactElement } from "react";
|
|
2
|
+
/** Separator의 방향 */
|
|
3
|
+
type SeparatorOrientation = "horizontal" | "vertical";
|
|
4
|
+
export interface SeparatorProps extends Omit<ComponentProps<"hr">, "children" | "role"> {
|
|
5
|
+
/** `horizontal`(기본값)은 위아래 내용을, `vertical`은 한 줄 안의 양옆 내용을 나눈다. */
|
|
6
|
+
orientation?: SeparatorOrientation;
|
|
7
|
+
/**
|
|
8
|
+
* 보기만 나누는 선이면 `true`. `role="none"`이 붙어 보조 기술에서 빠진다. 기본값은 `false`(separator 역할)다.
|
|
9
|
+
* 내용의 주제가 바뀌는 곳이면 `false`로 둔다.
|
|
10
|
+
*/
|
|
11
|
+
decorative?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 내용 묶음 사이를 선으로 나눈다. 기본은 의미 구분(separator 역할)이고, `decorative`면 보조 기술에서 빠진다.
|
|
15
|
+
* 서버 컴포넌트로 쓸 수 있다.
|
|
16
|
+
*/
|
|
17
|
+
export declare function Separator({ orientation, decorative, className, ...props }: SeparatorProps): ReactElement;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { cn } from "./cn.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
// src/Separator.tsx
|
|
5
|
+
const orientationClass = {
|
|
6
|
+
horizontal: "h-0 w-full border-t",
|
|
7
|
+
vertical: "h-auto w-0 self-stretch border-s"
|
|
8
|
+
};
|
|
9
|
+
const baseClass = "m-0 shrink-0 border-0 border-default";
|
|
10
|
+
/**
|
|
11
|
+
* 내용 묶음 사이를 선으로 나눈다. 기본은 의미 구분(separator 역할)이고, `decorative`면 보조 기술에서 빠진다.
|
|
12
|
+
* 서버 컴포넌트로 쓸 수 있다.
|
|
13
|
+
*/
|
|
14
|
+
function Separator({ orientation = "horizontal", decorative = false, className, ...props }) {
|
|
15
|
+
return /* @__PURE__ */ jsx("hr", {
|
|
16
|
+
...props,
|
|
17
|
+
role: decorative ? "none" : void 0,
|
|
18
|
+
"aria-orientation": !decorative && orientation === "vertical" ? "vertical" : void 0,
|
|
19
|
+
"data-orientation": orientation,
|
|
20
|
+
className: cn(baseClass, orientationClass[orientation], className)
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { Separator };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Size } from "./types.js";
|
|
2
|
+
import { ComponentProps, ReactElement } from "react";
|
|
3
|
+
/** 원의 size. Avatar size와 같은 24·32·40·48px다(Avatar 자리를 그대로 채운다). */
|
|
4
|
+
type SkeletonCircleSize = Extract<Size, "xs" | "sm" | "md" | "lg">;
|
|
5
|
+
interface SkeletonBaseProps extends Omit<ComponentProps<"div">, "children" | "aria-hidden"> {}
|
|
6
|
+
/** `variant`에 따라 받는 props가 다르다. `size`는 원(`circle`)에만 있다. */
|
|
7
|
+
export type SkeletonProps = SkeletonBaseProps & ({
|
|
8
|
+
/**
|
|
9
|
+
* 모양. `text`는 글 한 줄(높이 1em, 너비 100%), `rect`는 사각형 영역(너비 100%, 높이는 `className`으로 준다),
|
|
10
|
+
* `circle`은 원(크기는 `size`). 기본값은 `text`다.
|
|
11
|
+
*/
|
|
12
|
+
variant?: "text" | "rect";
|
|
13
|
+
size?: never;
|
|
14
|
+
} | {
|
|
15
|
+
variant: "circle";
|
|
16
|
+
/** 원 크기. `xs` 24px, `sm` 32px, `md` 40px, `lg` 48px(Avatar와 같다). 기본값은 `md`다. */
|
|
17
|
+
size?: SkeletonCircleSize;
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* 내용이 오기 전 자리를 표시한다. 스크린 리더에는 읽히지 않는다(`aria-hidden`).
|
|
21
|
+
* 불러오는 중이라는 사실은 앱이 영역의 `aria-busy`나 보이는 문구로 알린다. 서버 컴포넌트로 쓸 수 있다.
|
|
22
|
+
*/
|
|
23
|
+
export declare function Skeleton({ variant, size, className, ...props }: SkeletonProps): ReactElement;
|
|
24
|
+
export {};
|
package/dist/Skeleton.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { cn } from "./cn.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
// src/Skeleton.tsx
|
|
5
|
+
const variantClass = {
|
|
6
|
+
text: "h-[1em] w-full rounded-sm",
|
|
7
|
+
rect: "w-full rounded-md",
|
|
8
|
+
circle: "shrink-0 rounded-full"
|
|
9
|
+
};
|
|
10
|
+
const circleSizeClass = {
|
|
11
|
+
xs: "size-6",
|
|
12
|
+
sm: "size-8",
|
|
13
|
+
md: "size-10",
|
|
14
|
+
lg: "size-12"
|
|
15
|
+
};
|
|
16
|
+
const baseClass = "block border border-transparent bg-neutral-subtle-active motion-safe:animate-pulse";
|
|
17
|
+
/**
|
|
18
|
+
* 내용이 오기 전 자리를 표시한다. 스크린 리더에는 읽히지 않는다(`aria-hidden`).
|
|
19
|
+
* 불러오는 중이라는 사실은 앱이 영역의 `aria-busy`나 보이는 문구로 알린다. 서버 컴포넌트로 쓸 수 있다.
|
|
20
|
+
*/
|
|
21
|
+
function Skeleton({ variant = "text", size, className, ...props }) {
|
|
22
|
+
return /* @__PURE__ */ jsx("div", {
|
|
23
|
+
...props,
|
|
24
|
+
"aria-hidden": "true",
|
|
25
|
+
className: cn(baseClass, variantClass[variant], variant === "circle" ? circleSizeClass[size ?? "md"] : void 0, className)
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { Skeleton };
|
package/dist/Table.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ComponentProps, ReactElement } from "react";
|
|
2
|
+
export interface TableProps extends ComponentProps<"table"> {}
|
|
3
|
+
export interface TableHeadProps extends ComponentProps<"thead"> {}
|
|
4
|
+
export interface TableBodyProps extends ComponentProps<"tbody"> {}
|
|
5
|
+
export interface TableRowProps extends ComponentProps<"tr"> {}
|
|
6
|
+
export interface TableHeaderCellProps extends ComponentProps<"th"> {
|
|
7
|
+
/**
|
|
8
|
+
* 이 머리 칸이 맡는 범위. 기본값은 `"col"`(아래 열)이다. 본문 행의 첫 칸처럼 행을 맡으면 `"row"`를 준다.
|
|
9
|
+
* 열 그룹·행 그룹 머리는 `"colgroup"`·`"rowgroup"`이다.
|
|
10
|
+
*/
|
|
11
|
+
scope?: "col" | "row" | "colgroup" | "rowgroup";
|
|
12
|
+
}
|
|
13
|
+
export interface TableCellProps extends ComponentProps<"td"> {}
|
|
14
|
+
/**
|
|
15
|
+
* 시맨틱 `<table>`. 행·열로 된 데이터를 보여 준다. 정렬·행 선택·가상화는 없다. 서버 컴포넌트로 쓸 수 있다.
|
|
16
|
+
* 표의 이름은 `<caption>`이나 `aria-label`·`aria-labelledby`로 준다.
|
|
17
|
+
*/
|
|
18
|
+
export declare function Table({ className, ...props }: TableProps): ReactElement;
|
|
19
|
+
/** 머리 행 묶음 `<thead>`. 안의 행은 hover로 바뀌지 않는다. */
|
|
20
|
+
export declare function TableHead(props: TableHeadProps): ReactElement;
|
|
21
|
+
/** 본문 행 묶음 `<tbody>`. 바로 아래 `TableRow`에 마우스를 올리면 배경이 바로 바뀐다. */
|
|
22
|
+
export declare function TableBody(props: TableBodyProps): ReactElement;
|
|
23
|
+
/** 행 `<tr>`. 아래 경계가 있고, `TableBody` 안에서는 hover 배경이 transition 없이 바로 바뀐다. */
|
|
24
|
+
export declare function TableRow({ className, ...props }: TableRowProps): ReactElement;
|
|
25
|
+
/**
|
|
26
|
+
* 머리 칸 `<th>`. 기본은 `scope="col"`(열 머리, columnheader)이다. 본문 행의 행 머리는 `scope="row"`(rowheader)를 준다.
|
|
27
|
+
* 복잡한 표(여러 줄 머리)는 `id`와 칸의 `headers`로 직접 잇는다.
|
|
28
|
+
*/
|
|
29
|
+
export declare function TableHeaderCell({ scope, className, ...props }: TableHeaderCellProps): ReactElement;
|
|
30
|
+
/** 데이터 칸 `<td>`. 숫자 열은 `className="tabular-nums text-end"`로 자릿수를 맞춘다. */
|
|
31
|
+
export declare function TableCell({ className, ...props }: TableCellProps): ReactElement;
|
package/dist/Table.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { cn } from "./cn.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
// src/Table.tsx
|
|
5
|
+
const tableClass = "w-full border-collapse text-body-sm text-default";
|
|
6
|
+
const rowClass = "border-b border-default [:where(tbody>&)]:hover:bg-neutral-subtle-hover";
|
|
7
|
+
const headerCellClass = "px-3 py-2.5 text-start align-middle text-label-md";
|
|
8
|
+
const cellClass = "px-3 py-2.5 text-start align-middle";
|
|
9
|
+
/**
|
|
10
|
+
* 시맨틱 `<table>`. 행·열로 된 데이터를 보여 준다. 정렬·행 선택·가상화는 없다. 서버 컴포넌트로 쓸 수 있다.
|
|
11
|
+
* 표의 이름은 `<caption>`이나 `aria-label`·`aria-labelledby`로 준다.
|
|
12
|
+
*/
|
|
13
|
+
function Table({ className, ...props }) {
|
|
14
|
+
return /* @__PURE__ */ jsx("table", {
|
|
15
|
+
...props,
|
|
16
|
+
className: cn(tableClass, className)
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/** 머리 행 묶음 `<thead>`. 안의 행은 hover로 바뀌지 않는다. */
|
|
20
|
+
function TableHead(props) {
|
|
21
|
+
return /* @__PURE__ */ jsx("thead", { ...props });
|
|
22
|
+
}
|
|
23
|
+
/** 본문 행 묶음 `<tbody>`. 바로 아래 `TableRow`에 마우스를 올리면 배경이 바로 바뀐다. */
|
|
24
|
+
function TableBody(props) {
|
|
25
|
+
return /* @__PURE__ */ jsx("tbody", { ...props });
|
|
26
|
+
}
|
|
27
|
+
/** 행 `<tr>`. 아래 경계가 있고, `TableBody` 안에서는 hover 배경이 transition 없이 바로 바뀐다. */
|
|
28
|
+
function TableRow({ className, ...props }) {
|
|
29
|
+
return /* @__PURE__ */ jsx("tr", {
|
|
30
|
+
...props,
|
|
31
|
+
className: cn(rowClass, className)
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 머리 칸 `<th>`. 기본은 `scope="col"`(열 머리, columnheader)이다. 본문 행의 행 머리는 `scope="row"`(rowheader)를 준다.
|
|
36
|
+
* 복잡한 표(여러 줄 머리)는 `id`와 칸의 `headers`로 직접 잇는다.
|
|
37
|
+
*/
|
|
38
|
+
function TableHeaderCell({ scope = "col", className, ...props }) {
|
|
39
|
+
return /* @__PURE__ */ jsx("th", {
|
|
40
|
+
...props,
|
|
41
|
+
scope,
|
|
42
|
+
className: cn(headerCellClass, className)
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** 데이터 칸 `<td>`. 숫자 열은 `className="tabular-nums text-end"`로 자릿수를 맞춘다. */
|
|
46
|
+
function TableCell({ className, ...props }) {
|
|
47
|
+
return /* @__PURE__ */ jsx("td", {
|
|
48
|
+
...props,
|
|
49
|
+
className: cn(cellClass, className)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CircleCheckIcon, InfoIcon, OctagonExclamationPointIcon, TriangleExclamationPointIcon } from "@tide-tds/icons";
|
|
2
|
+
|
|
3
|
+
// src/alert-style.ts
|
|
4
|
+
/**
|
|
5
|
+
* 배치. 아이콘·내용·닫기 버튼을 한 줄로 놓고 위에 맞춘다. 여백은 16px, radius는 담는 요소의 lg다(spec radius 「쓰는 곳」).
|
|
6
|
+
* border 1px는 항상 둔다. forced-colors에서 브라우저가 경계를 CanvasText로 덮어 배경 없이도 영역이 남는다.
|
|
7
|
+
*/
|
|
8
|
+
const alertLayoutClass = "flex items-start gap-3 rounded-lg border p-4";
|
|
9
|
+
/**
|
|
10
|
+
* Alert의 subtle 표면. 모두 등록된 대비 쌍이다.
|
|
11
|
+
* - 글자·아이콘: text-accent × bg-accent-subtle 4.5:1(Role `text-<role>` × `bg-<role>-subtle`, neutral은 text-default ×
|
|
12
|
+
* bg-neutral-subtle, 앱 Role은 색 이름 규칙 text × subtle)
|
|
13
|
+
* - 경계: border-accent × 바깥 표면 층 3:1(`border-<role>` × 표면 층, neutral은 border-strong)과
|
|
14
|
+
* × 안쪽 bg-accent-subtle 3:1(`border-<role>` × `bg-<role>-subtle`, neutral은 border-strong × bg-neutral-subtle)
|
|
15
|
+
*/
|
|
16
|
+
const alertSurfaceClass = "border-accent bg-accent-subtle text-accent";
|
|
17
|
+
/** 상태 아이콘 자리. 글자 첫 줄 높이(body-sm 20px)와 같은 20px라 첫 줄 가운데에 맞는다. */
|
|
18
|
+
const alertIconClass = "size-5 shrink-0";
|
|
19
|
+
/** 제목과 본문을 세로로 쌓는다. 긴 단어가 닫기 버튼을 밀어내지 않게 min-w-0을 둔다. */
|
|
20
|
+
const alertContentClass = "flex min-w-0 flex-1 flex-col gap-1";
|
|
21
|
+
/** 제목. 본문과 같은 크기에 굵기만 올린다. 제목 수준(h1~h6)은 화면마다 다르므로 heading 요소를 쓰지 않는다. */
|
|
22
|
+
const alertTitleClass = "text-body-sm font-semibold";
|
|
23
|
+
/** 본문 */
|
|
24
|
+
const alertDescriptionClass = "text-body-sm";
|
|
25
|
+
/** 닫기 버튼(IconButton xs, 24px). 위아래 2px를 겹쳐 첫 줄 높이(20px)를 늘리지 않고, 끝쪽 4px를 여백에 겹친다. */
|
|
26
|
+
const alertCloseClass = "-my-0.5 -me-1";
|
|
27
|
+
const statusIcons = {
|
|
28
|
+
neutral: InfoIcon,
|
|
29
|
+
primary: InfoIcon,
|
|
30
|
+
info: InfoIcon,
|
|
31
|
+
success: CircleCheckIcon,
|
|
32
|
+
warning: TriangleExclamationPointIcon,
|
|
33
|
+
danger: OctagonExclamationPointIcon
|
|
34
|
+
};
|
|
35
|
+
/** Role의 상태 아이콘. 앱이 더한 Role(예: `brand`)은 일반 알림으로 보고 i 아이콘을 쓴다. */
|
|
36
|
+
function alertStatusIcon(color) {
|
|
37
|
+
return Object.hasOwn(statusIcons, color) ? statusIcons[color] : InfoIcon;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { alertCloseClass, alertContentClass, alertDescriptionClass, alertIconClass, alertLayoutClass, alertStatusIcon, alertSurfaceClass, alertTitleClass };
|
package/dist/index.d.ts
CHANGED
|
@@ -13,5 +13,15 @@ import { CheckboxGroup, CheckboxGroupProps, ChoiceOrientation } from "./Checkbox
|
|
|
13
13
|
import { Radio, RadioProps } from "./Radio.js";
|
|
14
14
|
import { RadioGroup, RadioGroupProps } from "./RadioGroup.js";
|
|
15
15
|
import { Switch, SwitchProps } from "./Switch.js";
|
|
16
|
+
import { Alert, AlertProps } from "./Alert.js";
|
|
17
|
+
import { Avatar, AvatarProps } from "./Avatar.js";
|
|
18
|
+
import { Badge, BadgeProps } from "./Badge.js";
|
|
19
|
+
import { Card, CardProps } from "./Card.js";
|
|
20
|
+
import { Separator, SeparatorProps } from "./Separator.js";
|
|
21
|
+
import { Skeleton, SkeletonProps } from "./Skeleton.js";
|
|
22
|
+
import { Progress, ProgressProps } from "./Progress.js";
|
|
23
|
+
import { Table, TableBody, TableBodyProps, TableCell, TableCellProps, TableHead, TableHeadProps, TableHeaderCell, TableHeaderCellProps, TableProps, TableRow, TableRowProps } from "./Table.js";
|
|
24
|
+
import { Breadcrumb, BreadcrumbCurrent, BreadcrumbCurrentProps, BreadcrumbItem, BreadcrumbItemProps, BreadcrumbLink, BreadcrumbLinkProps, BreadcrumbProps } from "./Breadcrumb.js";
|
|
25
|
+
import { Pagination, PaginationEllipsis, PaginationEllipsisProps, PaginationItem, PaginationItemProps, PaginationLink, PaginationLinkProps, PaginationNext, PaginationNextProps, PaginationPrevious, PaginationPreviousProps, PaginationProps } from "./Pagination.js";
|
|
16
26
|
import { Color, ColorFamilyName, ColorFamilyRegistry, FillVariant, Role, RoleRegistry, Size } from "./types.js";
|
|
17
|
-
export { Button, type ButtonProps, Checkbox, CheckboxGroup, type CheckboxGroupProps, type CheckboxProps, type ChoiceOrientation, type Color, type ColorFamilyName, type ColorFamilyRegistry, Field, type FieldProps, type FillVariant, IconButton, type IconButtonProps, Input, type InputProps, Link, type LinkProps, Radio, RadioGroup, type RadioGroupProps, type RadioProps, type Role, type RoleRegistry, type Size, Spinner, type SpinnerProps, Switch, type SwitchProps, Textarea, type TextareaProps, Typography, type TypographyProps, VisuallyHidden, type VisuallyHiddenProps, cn };
|
|
27
|
+
export { Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, Breadcrumb, BreadcrumbCurrent, type BreadcrumbCurrentProps, BreadcrumbItem, type BreadcrumbItemProps, BreadcrumbLink, type BreadcrumbLinkProps, type BreadcrumbProps, Button, type ButtonProps, Card, type CardProps, Checkbox, CheckboxGroup, type CheckboxGroupProps, type CheckboxProps, type ChoiceOrientation, type Color, type ColorFamilyName, type ColorFamilyRegistry, Field, type FieldProps, type FillVariant, IconButton, type IconButtonProps, Input, type InputProps, Link, type LinkProps, Pagination, PaginationEllipsis, type PaginationEllipsisProps, PaginationItem, type PaginationItemProps, PaginationLink, type PaginationLinkProps, PaginationNext, type PaginationNextProps, PaginationPrevious, type PaginationPreviousProps, type PaginationProps, Progress, type ProgressProps, Radio, RadioGroup, type RadioGroupProps, type RadioProps, type Role, type RoleRegistry, Separator, type SeparatorProps, type Size, Skeleton, type SkeletonProps, Spinner, type SpinnerProps, Switch, type SwitchProps, Table, TableBody, type TableBodyProps, TableCell, type TableCellProps, TableHead, type TableHeadProps, TableHeaderCell, type TableHeaderCellProps, type TableProps, TableRow, type TableRowProps, Textarea, type TextareaProps, Typography, type TypographyProps, VisuallyHidden, type VisuallyHiddenProps, cn };
|
package/dist/index.js
CHANGED
|
@@ -13,5 +13,15 @@ import { CheckboxGroup } from "./CheckboxGroup.js";
|
|
|
13
13
|
import { Radio } from "./Radio.js";
|
|
14
14
|
import { RadioGroup } from "./RadioGroup.js";
|
|
15
15
|
import { Switch } from "./Switch.js";
|
|
16
|
+
import { Alert } from "./Alert.js";
|
|
17
|
+
import { Avatar } from "./Avatar.js";
|
|
18
|
+
import { Badge } from "./Badge.js";
|
|
19
|
+
import { Card } from "./Card.js";
|
|
20
|
+
import { Separator } from "./Separator.js";
|
|
21
|
+
import { Skeleton } from "./Skeleton.js";
|
|
22
|
+
import { Progress } from "./Progress.js";
|
|
23
|
+
import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "./Table.js";
|
|
24
|
+
import { Breadcrumb, BreadcrumbCurrent, BreadcrumbItem, BreadcrumbLink } from "./Breadcrumb.js";
|
|
25
|
+
import { Pagination, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from "./Pagination.js";
|
|
16
26
|
|
|
17
|
-
export { Button, Checkbox, CheckboxGroup, Field, IconButton, Input, Link, Radio, RadioGroup, Spinner, Switch, Textarea, Typography, VisuallyHidden, cn };
|
|
27
|
+
export { Alert, Avatar, Badge, Breadcrumb, BreadcrumbCurrent, BreadcrumbItem, BreadcrumbLink, Button, Card, Checkbox, CheckboxGroup, Field, IconButton, Input, Link, Pagination, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, Progress, Radio, RadioGroup, Separator, Skeleton, Spinner, Switch, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, Textarea, Typography, VisuallyHidden, cn };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tide-tds/components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Tide 디자인 시스템의 React Component",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"./styles.css": "./dist/styles.css"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@tide-tds/foundation": "0.
|
|
27
|
-
"@tide-tds/icons": "0.
|
|
26
|
+
"@tide-tds/foundation": "0.3.0",
|
|
27
|
+
"@tide-tds/icons": "0.3.0",
|
|
28
28
|
"clsx": "^2.1.1",
|
|
29
29
|
"react-aria": "^3.53.1",
|
|
30
30
|
"react-stately": "^3.51.0",
|