kwon-ui 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,59 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { titleText } from "./tone.js";
3
+ import { cx } from "./util.js";
4
+ /** 하위 페이지의 맨 바깥. 검은 바탕과 떠 있는 메뉴 자리(위 여백)를 준다. */
5
+ export function Page({ as, className, ...rest }) {
6
+ const Tag = as ?? "div";
7
+ return _jsx(Tag, { className: cx("night n-page", className), ...rest });
8
+ }
9
+ /** 페이지 머리: 제목 한 줄, 받쳐 주는 문단 하나, 버튼 줄. */
10
+ export function PageHead({ title, label, lead, actions, className, children, }) {
11
+ return (_jsxs("header", { className: cx("n-wrap n-page-head", className), children: [_jsxs("div", { className: "n-head", children: [_jsx("h1", { className: "n-title", children: titleText(title, label) }), lead ? _jsx("p", { className: "n-body", children: lead }) : null] }), actions ? _jsx("div", { className: "n-actions", children: actions }) : null, children] }));
12
+ }
13
+ /**
14
+ * 구간. 안쪽은 내용 폭(1064px)으로 감싼다. bare 를 주면 감싸지 않는다(화면 끝까지 쓰는 내용).
15
+ * rule 은 앞 구간과의 사이에 가는 선을 긋고, slim 은 문장 하나와 버튼 하나뿐인 짧은 구간의 여백을 준다.
16
+ */
17
+ export function Section({ as, bare, rule, slim, className, children, ...rest }) {
18
+ const Tag = as ?? "section";
19
+ return (_jsx(Tag, { className: cx("n-sect", slim && "slim", rule && "ruled", className), ...rest, children: bare ? children : _jsx("div", { className: "n-wrap", children: children }) }));
20
+ }
21
+ /** 구간 머리: 왼쪽에 한 줄 제목, 오른쪽에 본문(children). 본문이 없으면 제목이 한 줄 전체를 쓴다. */
22
+ export function SectionHead({ title, label, rule, as, className, children, }) {
23
+ const H = as ?? "h2";
24
+ return (_jsxs("div", { className: cx("n-head", rule && "ruled", className), children: [_jsx(H, { className: "n-title", children: titleText(title, label) }), children ? _jsx("div", { children: typeof children === "string" ? _jsx("p", { className: "n-body", children: children }) : children }) : null] }));
25
+ }
26
+ /** 줄 목록의 바깥. 순서가 있으면 as="ol". */
27
+ export function Rows({ as, className, ...rest }) {
28
+ const Tag = as ?? "div";
29
+ return _jsx(Tag, { className: cx("n-rows", className), ...rest });
30
+ }
31
+ /** 한 줄: 왼쪽 작은 라벨, 오른쪽 본문. 글만 넘기면 본문 문단으로 그린다. */
32
+ export function Row({ as, label, labelAs, wide, className, children, ...rest }) {
33
+ const Tag = as ?? "div";
34
+ const L = labelAs ?? "h3";
35
+ return (_jsxs(Tag, { className: cx("n-row", wide && "wide-label", className), ...rest, children: [_jsx(L, { className: "n-row-label", children: label }), typeof children === "string" ? _jsx("p", { className: "n-body", children: children }) : children] }));
36
+ }
37
+ /** 검은 판. 화면 양옆에서 한 칸 들어온 둥근 면으로 구간 하나를 감싼다. */
38
+ export function Panel({ as, className, children, ...rest }) {
39
+ const Tag = as ?? "section";
40
+ return (_jsx(Tag, { className: cx("n-panel", className), ...rest, children: _jsx("div", { className: "n-wrap", children: children }) }));
41
+ }
42
+ /** 그림과 영상의 틀. 기본 4:3, wide 는 16:9. 안의 img, video 는 틀을 채운다. */
43
+ export function Media({ as, wide, className, ...rest }) {
44
+ const Tag = as ?? "div";
45
+ return _jsx(Tag, { className: cx("n-media", wide && "wide", className), ...rest });
46
+ }
47
+ /**
48
+ * 사진 판. 어둡게 누른 사진 위 왼쪽 아래에 제목과 문단을 둔다. media 에 img 나 video(프레임워크의 이미지 컴포넌트)를 넘긴다.
49
+ * 한 페이지에 하나, 밝은 곳이 필요한 자리에 쓴다.
50
+ */
51
+ export function PhotoPanel({ as, media, className, children, ...rest }) {
52
+ const Tag = as ?? "div";
53
+ return (_jsxs(Tag, { className: cx("n-photo", className), ...rest, children: [media, _jsx("div", { className: "n-photo-in", children: children })] }));
54
+ }
55
+ /** 문의 띠: 왼쪽 한 줄 문장(과 짧은 문단), 오른쪽 버튼 줄. 페이지 끝에 하나 둔다. */
56
+ export function CtaBand({ title, label, body, actions, className, }) {
57
+ const heading = _jsx("h2", { className: "n-title", children: titleText(title, label) });
58
+ return (_jsxs("div", { className: cx("n-cta-band", className), children: [body ? (_jsxs("div", { children: [heading, _jsx("p", { className: "n-body", children: body })] })) : (heading), _jsx("div", { className: "n-actions", children: actions })] }));
59
+ }
@@ -0,0 +1,18 @@
1
+ import { type ReactNode } from "react";
2
+ /**
3
+ * 한 줄 두 톤 제목. 글은 "**진한 낱말** 나머지" 꼴로 쓴다.
4
+ * ** 로 감싼 곳만 진한 글자(제목 기본색)이고, 나머지는 같은 줄의 회색(.lo-inline)이다.
5
+ * 진한 글자는 문장의 핵심 낱말 한두 개에만 준다. 숫자는 숫자가 요점일 때만 진하게 둔다.
6
+ * label 을 주면 화면 낭독기만 읽는 구간 이름을 앞에 붙인다(보이는 문장에 구간 이름이 없을 때).
7
+ */
8
+ export declare function tone(line: string, label?: string): ReactNode;
9
+ /**
10
+ * 본문 안의 진한 낱말. 글은 "**진한 낱말** 나머지" 꼴로 쓴다.
11
+ * ** 로 감싼 곳만 진한 글자(.hi)이고, 나머지는 본문 기본색 그대로다. n-body 나 n-small 안에서 쓴다.
12
+ */
13
+ export declare function hi(line: string): ReactNode;
14
+ /**
15
+ * 제목 글. ** 표시가 있으면 두 톤으로, 없으면 전부 진한 글자로 그린다.
16
+ * (tone 은 표시가 없는 글을 전부 회색으로 그리므로, 표시 없는 제목은 이 함수를 거친다.)
17
+ */
18
+ export declare function titleText(line: string, label?: string): ReactNode;
@@ -0,0 +1,29 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Fragment } from "react";
3
+ /**
4
+ * 한 줄 두 톤 제목. 글은 "**진한 낱말** 나머지" 꼴로 쓴다.
5
+ * ** 로 감싼 곳만 진한 글자(제목 기본색)이고, 나머지는 같은 줄의 회색(.lo-inline)이다.
6
+ * 진한 글자는 문장의 핵심 낱말 한두 개에만 준다. 숫자는 숫자가 요점일 때만 진하게 둔다.
7
+ * label 을 주면 화면 낭독기만 읽는 구간 이름을 앞에 붙인다(보이는 문장에 구간 이름이 없을 때).
8
+ */
9
+ export function tone(line, label) {
10
+ const parts = line.split(/(\*\*[^*]+\*\*)/).filter(Boolean);
11
+ return (_jsxs(_Fragment, { children: [label ? _jsxs("span", { className: "n-sr", children: [label, ". "] }) : null, parts.map((p, i) => p.startsWith("**") && p.endsWith("**") ? (_jsx(Fragment, { children: p.slice(2, -2) }, i)) : (_jsx("span", { className: "lo-inline", children: p }, i)))] }));
12
+ }
13
+ /**
14
+ * 본문 안의 진한 낱말. 글은 "**진한 낱말** 나머지" 꼴로 쓴다.
15
+ * ** 로 감싼 곳만 진한 글자(.hi)이고, 나머지는 본문 기본색 그대로다. n-body 나 n-small 안에서 쓴다.
16
+ */
17
+ export function hi(line) {
18
+ const parts = line.split(/(\*\*[^*]+\*\*)/).filter(Boolean);
19
+ return (_jsx(_Fragment, { children: parts.map((p, i) => p.startsWith("**") && p.endsWith("**") ? (_jsx("span", { className: "hi", children: p.slice(2, -2) }, i)) : (_jsx(Fragment, { children: p }, i))) }));
20
+ }
21
+ /**
22
+ * 제목 글. ** 표시가 있으면 두 톤으로, 없으면 전부 진한 글자로 그린다.
23
+ * (tone 은 표시가 없는 글을 전부 회색으로 그리므로, 표시 없는 제목은 이 함수를 거친다.)
24
+ */
25
+ export function titleText(line, label) {
26
+ if (line.includes("**"))
27
+ return tone(line, label);
28
+ return (_jsxs(_Fragment, { children: [label ? _jsxs("span", { className: "n-sr", children: [label, ". "] }) : null, line] }));
29
+ }
@@ -0,0 +1,29 @@
1
+ import type { ElementType, ReactNode } from "react";
2
+ import { type PolyProps } from "./util.js";
3
+ type LineProps = {
4
+ /** "**진한 낱말** 나머지" 꼴의 한 줄. children 대신 쓴다 */
5
+ line?: string;
6
+ /** 화면 낭독기만 읽는 구간 이름 */
7
+ label?: string;
8
+ className?: string;
9
+ children?: ReactNode;
10
+ };
11
+ /** 첫 화면 제목. 한 페이지에 한 번만 쓴다. */
12
+ export declare function Display<T extends ElementType = "h1">({ as, line, label, className, children, ...rest }: PolyProps<T, LineProps>): import("react").JSX.Element;
13
+ /** 구간 제목. 한 문장 한 줄로 쓰고 진한 글자는 핵심 낱말 한두 개에만 준다. */
14
+ export declare function Title<T extends ElementType = "h2">({ as, line, label, className, children, ...rest }: PolyProps<T, LineProps>): import("react").JSX.Element;
15
+ type TextProps = {
16
+ className?: string;
17
+ children?: ReactNode;
18
+ };
19
+ /** 본문. line 을 주면 "**진한 낱말** 나머지" 꼴에서 ** 로 감싼 곳만 진하게 그린다. */
20
+ export declare function Body<T extends ElementType = "p">({ as, line, className, children, ...rest }: PolyProps<T, TextProps & {
21
+ line?: string;
22
+ }>): import("react").JSX.Element;
23
+ /** 목록이나 카드 안의 작은 제목. 본문과 같은 크기이고 진한 글자다. */
24
+ export declare function CardTitle<T extends ElementType = "h3">({ as, className, ...rest }: PolyProps<T, TextProps>): import("react").JSX.Element;
25
+ /** 작은 글: 캡션, 기준, 라벨. num 을 주면 숫자 폭을 맞춘다. */
26
+ export declare function Small<T extends ElementType = "p">({ as, num, className, ...rest }: PolyProps<T, TextProps & {
27
+ num?: boolean;
28
+ }>): import("react").JSX.Element;
29
+ export {};
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { hi, titleText } from "./tone.js";
3
+ import { cx } from "./util.js";
4
+ /** 첫 화면 제목. 한 페이지에 한 번만 쓴다. */
5
+ export function Display({ as, line, label, className, children, ...rest }) {
6
+ const Tag = as ?? "h1";
7
+ return (_jsx(Tag, { className: cx("n-display", className), ...rest, children: children ?? (line ? titleText(line, label) : null) }));
8
+ }
9
+ /** 구간 제목. 한 문장 한 줄로 쓰고 진한 글자는 핵심 낱말 한두 개에만 준다. */
10
+ export function Title({ as, line, label, className, children, ...rest }) {
11
+ const Tag = as ?? "h2";
12
+ return (_jsx(Tag, { className: cx("n-title", className), ...rest, children: children ?? (line ? titleText(line, label) : null) }));
13
+ }
14
+ /** 본문. line 을 주면 "**진한 낱말** 나머지" 꼴에서 ** 로 감싼 곳만 진하게 그린다. */
15
+ export function Body({ as, line, className, children, ...rest }) {
16
+ const Tag = as ?? "p";
17
+ return (_jsx(Tag, { className: cx("n-body", className), ...rest, children: children ?? (line ? hi(line) : null) }));
18
+ }
19
+ /** 목록이나 카드 안의 작은 제목. 본문과 같은 크기이고 진한 글자다. */
20
+ export function CardTitle({ as, className, ...rest }) {
21
+ const Tag = as ?? "h3";
22
+ return _jsx(Tag, { className: cx("n-card-title", className), ...rest });
23
+ }
24
+ /** 작은 글: 캡션, 기준, 라벨. num 을 주면 숫자 폭을 맞춘다. */
25
+ export function Small({ as, num, className, ...rest }) {
26
+ const Tag = as ?? "p";
27
+ return _jsx(Tag, { className: cx("n-small", num && "n-num", className), ...rest });
28
+ }
@@ -0,0 +1,10 @@
1
+ import type { ComponentPropsWithoutRef, ElementType } from "react";
2
+ /** 클래스 이름을 이어 붙인다. 거짓 값은 뺀다. */
3
+ export declare function cx(...parts: Array<string | false | null | undefined>): string;
4
+ /**
5
+ * as 로 태그나 컴포넌트를 바꿀 수 있는 조각의 속성.
6
+ * 예: <Pill as={Link} href="/contact"> 처럼 프레임워크의 링크 컴포넌트를 넘긴다.
7
+ */
8
+ export type PolyProps<T extends ElementType, Own = object> = Own & {
9
+ as?: T;
10
+ } & Omit<ComponentPropsWithoutRef<T>, keyof Own | "as">;
@@ -0,0 +1,4 @@
1
+ /** 클래스 이름을 이어 붙인다. 거짓 값은 뺀다. */
2
+ export function cx(...parts) {
3
+ return parts.filter(Boolean).join(" ");
4
+ }
package/package.json CHANGED
@@ -1,6 +1,64 @@
1
1
  {
2
2
  "name": "kwon-ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "권앤컴퍼니의 세 제품(홈페이지, 스킬캠퍼스, Projects)이 같이 쓰는 디자인 시스템: 토큰, 글자, 뼈대, 컴포넌트, 검사기",
5
+ "license": "UNLICENSED",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/kihyeonkwon/kwon-ui.git"
9
+ },
10
+ "homepage": "https://github.com/kihyeonkwon/kwon-ui#readme",
11
+ "type": "module",
12
+ "sideEffects": [
13
+ "**/*.css"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/react/index.d.ts",
18
+ "default": "./dist/react/index.js"
19
+ },
20
+ "./kwon-ui.css": "./dist/kwon-ui.css",
21
+ "./classes.json": "./dist/classes.json",
22
+ "./check": "./check/index.mjs",
23
+ "./CATALOG.md": "./CATALOG.md",
24
+ "./package.json": "./package.json"
25
+ },
26
+ "bin": {
27
+ "kwon-ui": "bin/kwon-ui.mjs"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "!dist/catalog",
32
+ "check",
33
+ "bin",
34
+ "claude",
35
+ "CATALOG.md",
36
+ "CHANGELOG.md"
37
+ ],
38
+ "scripts": {
39
+ "build": "node scripts/build.mjs",
40
+ "test": "node --test test/*.test.mjs",
41
+ "verify": "npm run build && npm test",
42
+ "prepublishOnly": "npm run verify"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "peerDependencies": {
48
+ "react": ">=18"
49
+ },
50
+ "dependencies": {
51
+ "postcss": "^8.4.47"
52
+ },
53
+ "devDependencies": {
54
+ "@types/react": "^19",
55
+ "@types/react-dom": "^19",
56
+ "pretendard": "^1.3.9",
57
+ "react": "^19",
58
+ "react-dom": "^19",
59
+ "typescript": "^5"
60
+ },
61
+ "engines": {
62
+ "node": ">=20"
63
+ }
64
+ }