@tide-tds/components 0.0.0-stage → 0.2.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.
@@ -0,0 +1,65 @@
1
+ // src/pressable.ts
2
+ /** 기본 색(배경, 글자, 경계). 색 이름마다 Foundation이 같은 대비 규칙으로 검사한다. */
3
+ const colorClass = {
4
+ solid: "border-transparent bg-accent-solid text-accent-on-solid",
5
+ subtle: "border-transparent bg-accent-subtle text-accent",
6
+ outline: "border-accent text-accent",
7
+ ghost: "border-transparent text-accent"
8
+ };
9
+ /** hover·누름 색. 활성 상태일 때만 붙인다(비활성·로딩에는 붙이지 않아 CSS 순서에 기대지 않는다). */
10
+ const interactionClass = {
11
+ solid: "hover:bg-accent-solid-hover active:bg-accent-solid-active",
12
+ subtle: "hover:bg-accent-subtle-hover active:bg-accent-subtle-active",
13
+ outline: "hover:bg-accent-subtle-hover active:bg-accent-subtle-active",
14
+ ghost: "hover:bg-accent-subtle-hover active:bg-accent-subtle-active"
15
+ };
16
+ /** 비활성 색. 색 이름과 관계없이 같다. disabled Token은 대비 검사 대상이 아니다. */
17
+ const disabledClass = {
18
+ solid: "cursor-not-allowed border-transparent bg-disabled text-disabled",
19
+ subtle: "cursor-not-allowed border-transparent bg-disabled text-disabled",
20
+ outline: "cursor-not-allowed border-default text-disabled",
21
+ ghost: "cursor-not-allowed border-transparent text-disabled"
22
+ };
23
+ /** 모양의 공통 부분. radius는 모든 size에서 `md`다. 높이·폭·padding은 Component가 정한다. */
24
+ const pressableBaseClass = "inline-flex shrink-0 items-center justify-center rounded-md border whitespace-nowrap select-none transition-colors focus-visible:focus-ring";
25
+ const pressScaleClass = "motion-safe:transition-[color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,scale] motion-safe:active:scale-96";
26
+ /** `disabled`를 네이티브로 지원하는 요소인지. Button류가 렌더할 만한 요소 중에서는 `button`뿐이다. */
27
+ const supportsNativeDisabled = (render) => render === void 0 || render.type === "button";
28
+ /**
29
+ * 채움 방식·색·상태에서 class와 루트 props를 정한다.
30
+ * - disabled: 기본 요소와 `render={<button />}`는 네이티브 `disabled`, 그 밖의 요소는 `aria-disabled`·`data-disabled`와 클릭 차단
31
+ * - loading: 포커스를 남긴 채 `aria-disabled`·`data-loading`과 클릭 차단. disabled와 함께 오면 disabled가 이긴다
32
+ * - 커서: 활성 pointer, 로딩 wait, 비활성 not-allowed
33
+ * - hover·누름 색과 누름 축소는 활성 상태에서만 붙인다
34
+ */
35
+ function pressableState({ variant, color, disabled, loading, noPressScale, render, type, onClick, ariaDisabled }) {
36
+ const native = supportsNativeDisabled(render);
37
+ const softDisabled = disabled && !native;
38
+ const blocked = softDisabled || loading && !disabled;
39
+ const interactive = !disabled && !loading;
40
+ const showSpinner = loading && !disabled;
41
+ return {
42
+ className: [
43
+ disabled ? disabledClass[variant] : colorClass[variant],
44
+ interactive ? interactionClass[variant] : void 0,
45
+ interactive && !noPressScale ? pressScaleClass : void 0,
46
+ interactive ? "cursor-pointer" : void 0,
47
+ showSpinner ? "cursor-wait" : void 0
48
+ ].filter(Boolean).join(" "),
49
+ showSpinner,
50
+ rootProps: {
51
+ type: render === void 0 ? type ?? "button" : type,
52
+ disabled: native && disabled ? true : void 0,
53
+ "data-color": color,
54
+ "aria-disabled": blocked ? true : ariaDisabled,
55
+ "data-disabled": softDisabled ? "" : void 0,
56
+ "data-loading": showSpinner ? "" : void 0,
57
+ onClick: blocked ? (event) => {
58
+ event.preventDefault();
59
+ event.stopPropagation();
60
+ } : onClick
61
+ }
62
+ };
63
+ }
64
+
65
+ export { pressableBaseClass, pressableState };
package/dist/render.js ADDED
@@ -0,0 +1,55 @@
1
+ import { cn } from "./cn.js";
2
+ import { cloneElement, createElement } from "react";
3
+
4
+ // src/render.ts
5
+ const isHandlerName = (name) => /^on[A-Z]/.test(name);
6
+ /** ref 여러 개를 하나로 합친다. React 19의 cleanup 함수 반환도 지원한다. */
7
+ function mergeRefs(...refs) {
8
+ return (node) => {
9
+ const cleanups = refs.map((ref) => {
10
+ if (typeof ref === "function") return ref(node);
11
+ if (ref) ref.current = node;
12
+ });
13
+ return () => {
14
+ refs.forEach((ref, index) => {
15
+ const cleanup = cleanups[index];
16
+ if (typeof cleanup === "function") cleanup();
17
+ else if (typeof ref === "function") ref(null);
18
+ else if (ref) ref.current = null;
19
+ });
20
+ };
21
+ };
22
+ }
23
+ /** render 요소의 props와 Component props를 위 규칙대로 합친다. */
24
+ function mergeProps(elementProps, props) {
25
+ const merged = { ...elementProps };
26
+ for (const [name, value] of Object.entries(props)) {
27
+ const existing = elementProps[name];
28
+ if (value === void 0) continue;
29
+ if (isHandlerName(name) && typeof existing === "function" && typeof value === "function") merged[name] = (...args) => {
30
+ existing(...args);
31
+ return value(...args);
32
+ };
33
+ else if (name === "style" && existing && typeof existing === "object") merged[name] = {
34
+ ...existing,
35
+ ...value
36
+ };
37
+ else if (name === "ref" && existing) merged[name] = mergeRefs(existing, value);
38
+ else merged[name] = value;
39
+ }
40
+ return merged;
41
+ }
42
+ /** `render`가 있으면 그 요소에 props를 병합하고, 없으면 기본 요소를 렌더한다. */
43
+ function renderElement({ defaultElement, render, baseClassName, props }) {
44
+ const { className, ...rest } = props;
45
+ if (!render) return createElement(defaultElement, {
46
+ ...rest,
47
+ className: cn(baseClassName, className)
48
+ });
49
+ const elementProps = render.props;
50
+ const merged = mergeProps(elementProps, rest);
51
+ merged["className"] = cn(baseClassName, elementProps["className"], className);
52
+ return cloneElement(render, merged);
53
+ }
54
+
55
+ export { renderElement };
@@ -0,0 +1,5 @@
1
+ /* 앱 CSS에는 @import "tailwindcss"와 이 파일, 두 줄만 쓴다. */
2
+ @import "@tide-tds/foundation/tailwind.css";
3
+
4
+ /* 패키지 자신(빌드 결과)을 Tailwind 스캔 대상으로 등록한다. 경로는 이 CSS 파일 위치 기준이다. */
5
+ @source "./";
@@ -0,0 +1,9 @@
1
+ import { Color, ColorFamilyName, ColorFamilyRegistry, Role, RoleRegistry } from "@tide-tds/foundation";
2
+ /** Component size. 높이는 `xs` 24px, `sm` 32px, `md` 40px, `lg` 48px다(spec size 표). */
3
+ export type Size = "xs" | "sm" | "md" | "lg";
4
+ /**
5
+ * Button류의 채움 방식. `solid`·`subtle`은 Token의 강도와 이름이 같고, `outline`·`ghost`는 Component에만 있다.
6
+ * 스타일 축 이름이 `variant`라도 값 집합은 채움 방식이라 `Variant`가 아닌 이 이름을 쓴다(GLOSSARY 「채움 방식」).
7
+ */
8
+ export type FillVariant = "solid" | "subtle" | "outline" | "ghost";
9
+ export type { Color, ColorFamilyName, ColorFamilyRegistry, Role, RoleRegistry };
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "@tide-tds/components",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "Tide 디자인 시스템의 React Component",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/chane81/tide-design-system.git",
9
+ "directory": "packages/components"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "type": "module",
15
+ "sideEffects": [
16
+ "**/*.css"
17
+ ],
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ },
23
+ "./styles.css": "./dist/styles.css"
24
+ },
25
+ "dependencies": {
26
+ "@tide-tds/foundation": "0.2.0",
27
+ "@tide-tds/icons": "0.2.0",
28
+ "clsx": "^2.1.1",
29
+ "react-aria": "^3.53.1",
30
+ "react-stately": "^3.51.0",
31
+ "tailwind-merge": "^3.7.0"
32
+ },
33
+ "devDependencies": {
34
+ "@tailwindcss/vite": "^4.3.5",
35
+ "@types/node": "^26.6.6",
36
+ "@types/react": "^19.3.0",
37
+ "@types/react-dom": "^19.3.0",
38
+ "@vitest/browser-playwright": "^5.1.0",
39
+ "next": "^16.4.5",
40
+ "playwright": "^1.65.0",
41
+ "react": "^19.3.1",
42
+ "react-dom": "^19.3.1",
43
+ "tailwindcss": "^4.3.5",
44
+ "tsdown": "0.23.7",
45
+ "typescript": "^7.0.2",
46
+ "vite": "^8.3.6",
47
+ "vitest": "^5.1.0",
48
+ "vitest-browser-react": "^2.3.0"
49
+ },
50
+ "peerDependencies": {
51
+ "react": "^19",
52
+ "react-dom": "^19",
53
+ "tailwindcss": "^4.3"
54
+ },
55
+ "scripts": {
56
+ "build": "tsdown && cp src/styles.css dist/styles.css",
57
+ "typecheck": "tsc --noEmit",
58
+ "test": "vitest run"
59
+ }
6
60
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.