@axis-ds/mcp 1.4.8 → 1.5.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/README.md
CHANGED
|
@@ -41,7 +41,7 @@ Claude Desktop 또는 기타 MCP 호환 클라이언트에서 설정:
|
|
|
41
41
|
- 이름은 레지스트리 이름 그대로입니다. Agentic UI 는 `agentic-` 접두를 붙입니다(예: `agentic-run-progress`). 의존 항목(`utils` · `agentic-a11y-labels`)은 함께 설치됩니다.
|
|
42
42
|
- `targetDir` 는 이미 있는 프로젝트 루트입니다(상대 경로는 서버의 현재 디렉터리 기준). `axis.config.json` 이 있으면 그 `componentsDir` · `agenticDir` 에, 없으면 `components/ui` · `components/agentic` 에 설치하고 그 사실을 알립니다. 프로젝트 밖으로 나가는 경로는 아무것도 쓰지 않고 실패합니다.
|
|
43
43
|
- 내용이 다른 기존 파일은 덮어쓰지 않고 건너뛰며 응답 첫머리에 건너뛴 수를 적습니다. `overwrite: true` 일 때만 덮어씁니다.
|
|
44
|
-
- 의존성은 설치하지 않고 lockfile 에 맞는 명령(`pnpm add` · `npm install` · `yarn add`)만 안내합니다. lockfile 이 없으면 `package.json` 의 `packageManager` 를 봅니다(1.4.2 부터). `axis.apply_template` 의 「필요한 의존성」 · `axis.list_brands` · `axis.get_brand` 의 설치 안내도 1.4.2 부터 같은 방식으로 `targetDir` 의 매니저를 따릅니다(1.4.1 까지는 `pnpm` 고정).
|
|
44
|
+
- 의존성은 설치하지 않고 lockfile 에 맞는 명령(`pnpm add` · `npm install` · `yarn add`)만 안내합니다. 워크스페이스 루트 `package.json` 에 선언된 의존성은 안내하지 않고, 1.4.8 다음 patch 부터(WI-0095) Yarn Plug'n'Play 에서는 루트에만 선언한 의존성도 앱이 해석하지 못하므로 안내 명령에 넣습니다(CLI `axis add` 와 같음). lockfile 이 없으면 `package.json` 의 `packageManager` 를 봅니다(1.4.2 부터). `axis.apply_template` 의 「필요한 의존성」 · `axis.list_brands` · `axis.get_brand` 의 설치 안내도 1.4.2 부터 같은 방식으로 `targetDir` 의 매니저를 따릅니다(1.4.1 까지는 `pnpm` 고정).
|
|
45
45
|
- `axis.apply_template` 은 `targetDir` 에 이미 있는 `package.json` · `axis.config.json`(템플릿 루트의 것)을 덮어쓰지 않고 CLI `axis template apply` 와 같은 규칙으로 합칩니다(WI-0088). `packageManager` 등 사용자 필드가 남고, 결과의 파일 목록에 「합치기」로 표시합니다. 파일을 쓰는 도중 실패하면 건드린 파일을 되돌리고 오류로 응답합니다. 템플릿 경로가 `targetDir` 밖(절대 경로 · `..` · 밖을 가리키는 심볼릭 링크 경유)이면 `axis.apply_template` · `axis.diff_template` 은 아무 파일도 쓰거나 읽지 않고 오류로 응답합니다.
|
|
46
46
|
|
|
47
47
|
## 토큰 도구
|
package/dist/index.js
CHANGED
|
@@ -58,8 +58,8 @@ function listTokens(category) {
|
|
|
58
58
|
}
|
|
59
59
|
return tokens;
|
|
60
60
|
}
|
|
61
|
-
function getToken(
|
|
62
|
-
const q =
|
|
61
|
+
function getToken(path13) {
|
|
62
|
+
const q = path13.trim().toLowerCase();
|
|
63
63
|
return loadTokens().tokens.find(
|
|
64
64
|
(t) => t.path.toLowerCase() === q || (t.aliases ?? []).some((a) => a.toLowerCase() === q) || t.cssVariable.toLowerCase() === q
|
|
65
65
|
);
|
|
@@ -1476,9 +1476,6 @@ import { basename, dirname as dirname2, extname, isAbsolute, join as join2, rela
|
|
|
1476
1476
|
function declaredRange(baseDir, pkg, name) {
|
|
1477
1477
|
return declaredDependency(baseDir, pkg, name)?.range;
|
|
1478
1478
|
}
|
|
1479
|
-
function declaredRangeForCheck(baseDir, pkg, name) {
|
|
1480
|
-
return declaredDependency(baseDir, pkg, name, void 0, { appOnlyInPnp: true })?.range;
|
|
1481
|
-
}
|
|
1482
1479
|
function isPlainObject2(value) {
|
|
1483
1480
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1484
1481
|
}
|
|
@@ -2321,9 +2318,9 @@ function checkTailwindSetup(baseDirInput) {
|
|
|
2321
2318
|
};
|
|
2322
2319
|
}
|
|
2323
2320
|
for (const file of TAILWIND_CONFIG_FILES) {
|
|
2324
|
-
const
|
|
2325
|
-
if (!existsSync3(
|
|
2326
|
-
const content = readFileSync4(
|
|
2321
|
+
const path13 = join2(baseDir, file);
|
|
2322
|
+
if (!existsSync3(path13)) continue;
|
|
2323
|
+
const content = readFileSync4(path13, "utf8");
|
|
2327
2324
|
if (hasTokensPresetReference(content)) {
|
|
2328
2325
|
const code = stripJsComments(content);
|
|
2329
2326
|
const bareCjs = isCommonJsConfig(file, code) && hasBareCjsPresetRequire(code);
|
|
@@ -2744,6 +2741,33 @@ function componentsUseAnimation(baseDir, componentsDir) {
|
|
|
2744
2741
|
}
|
|
2745
2742
|
return false;
|
|
2746
2743
|
}
|
|
2744
|
+
function animationPluginProblem(baseDir, pkg, name, pnp) {
|
|
2745
|
+
const declared = declaredDependency(baseDir, pkg, name, void 0, { appOnlyInPnp: true });
|
|
2746
|
+
if (declared === void 0) {
|
|
2747
|
+
return pnpRootOnlyDependency(baseDir, pkg, name) !== void 0 ? { name, kind: "pnpRootOnly", why: `${name} \uAC00 ${PNP_ROOT_ONLY_NOTE}` } : { name, kind: "undeclared", why: `${name} \uC120\uC5B8 \uC5C6\uC74C` };
|
|
2748
|
+
}
|
|
2749
|
+
if (!pnp && findInstalledPackage(baseDir, name) === void 0) {
|
|
2750
|
+
return { name, kind: "notInstalled", why: `${name} \uBBF8\uC124\uCE58(\uC120\uC5B8\uB9CC \uC788\uC74C)`, root: declared.source === "root" };
|
|
2751
|
+
}
|
|
2752
|
+
return void 0;
|
|
2753
|
+
}
|
|
2754
|
+
function inspectTailwind4Animation(baseDirInput, pkgInput) {
|
|
2755
|
+
const baseDir = resolveBaseDir(baseDirInput);
|
|
2756
|
+
const pkg = pkgInput ?? readPackageJson(baseDir);
|
|
2757
|
+
const pnp = isPnpProject(baseDir);
|
|
2758
|
+
const selection = selectCheckGlobalCss(baseDir);
|
|
2759
|
+
const note = globalCssSelectionNote(selection);
|
|
2760
|
+
const expanded = selection.path !== void 0 && selection.rel !== void 0 ? expandGlobalCss(baseDir, selection.path, selection.rel, outsideCssDir(selection)) : void 0;
|
|
2761
|
+
const css = expanded !== void 0 ? stripCssComments(expanded.evidence) : "";
|
|
2762
|
+
const twAnimate = new RegExp(`@import\\s+(?:url\\(\\s*)?["']${ANIMATION_PLUGIN_TAILWIND4}["']`);
|
|
2763
|
+
const animatePlugin = new RegExp(`@plugin\\s+["']${ANIMATION_PLUGIN_TAILWIND3}["']`);
|
|
2764
|
+
const lines = [];
|
|
2765
|
+
if (twAnimate.test(css)) lines.push(ANIMATION_PLUGIN_TAILWIND4);
|
|
2766
|
+
if (animatePlugin.test(css)) lines.push(ANIMATION_PLUGIN_TAILWIND3);
|
|
2767
|
+
const via = expanded !== void 0 ? cssImportViaNote(viaFiles([evidenceOrigin(expanded, (f) => twAnimate.test(f)), evidenceOrigin(expanded, (f) => animatePlugin.test(f))])) : "";
|
|
2768
|
+
const problems = lines.map((name) => animationPluginProblem(baseDir, pkg, name, pnp)).filter((p) => p !== void 0);
|
|
2769
|
+
return { selection, file: selection.rel, note, via, lines, problems };
|
|
2770
|
+
}
|
|
2747
2771
|
function checkAnimationPlugin(baseDirInput) {
|
|
2748
2772
|
const baseDir = resolveBaseDir(baseDirInput);
|
|
2749
2773
|
const pkg = readPackageJson(baseDir);
|
|
@@ -2754,25 +2778,8 @@ function checkAnimationPlugin(baseDirInput) {
|
|
|
2754
2778
|
const by = declared ? "@axis-ds/ui-react \uC120\uC5B8" : "componentsDir \uC758 animate-in \uB958 \uD074\uB798\uC2A4";
|
|
2755
2779
|
const tailwind = detectTailwind(baseDir, pkg);
|
|
2756
2780
|
const pnp = isPnpProject(baseDir);
|
|
2757
|
-
const pluginProblem = (name) => {
|
|
2758
|
-
if (declaredRangeForCheck(baseDir, pkg, name) === void 0) {
|
|
2759
|
-
return pnpRootOnlyDependency(baseDir, pkg, name) !== void 0 ? `${name} \uAC00 ${PNP_ROOT_ONLY_NOTE}` : `${name} \uC120\uC5B8 \uC5C6\uC74C`;
|
|
2760
|
-
}
|
|
2761
|
-
if (!pnp && findInstalledPackage(baseDir, name) === void 0) return `${name} \uBBF8\uC124\uCE58(\uC120\uC5B8\uB9CC \uC788\uC74C)`;
|
|
2762
|
-
return void 0;
|
|
2763
|
-
};
|
|
2764
2781
|
if (tailwind.major !== void 0 && tailwind.major >= 4) {
|
|
2765
|
-
const
|
|
2766
|
-
const note = globalCssSelectionNote(selection);
|
|
2767
|
-
const file = selection.rel;
|
|
2768
|
-
const expanded = selection.path !== void 0 && selection.rel !== void 0 ? expandGlobalCss(baseDir, selection.path, selection.rel, outsideCssDir(selection)) : void 0;
|
|
2769
|
-
const css = expanded !== void 0 ? stripCssComments(expanded.evidence) : "";
|
|
2770
|
-
const twAnimate = new RegExp(`@import\\s+(?:url\\(\\s*)?["']${ANIMATION_PLUGIN_TAILWIND4}["']`);
|
|
2771
|
-
const animatePlugin = new RegExp(`@plugin\\s+["']${ANIMATION_PLUGIN_TAILWIND3}["']`);
|
|
2772
|
-
const lines = [];
|
|
2773
|
-
if (twAnimate.test(css)) lines.push(ANIMATION_PLUGIN_TAILWIND4);
|
|
2774
|
-
if (animatePlugin.test(css)) lines.push(ANIMATION_PLUGIN_TAILWIND3);
|
|
2775
|
-
const via = expanded !== void 0 ? cssImportViaNote(viaFiles([evidenceOrigin(expanded, (f) => twAnimate.test(f)), evidenceOrigin(expanded, (f) => animatePlugin.test(f))])) : "";
|
|
2782
|
+
const { file, note, via, lines, problems } = inspectTailwind4Animation(baseDir, pkg);
|
|
2776
2783
|
if (lines.length === 0) {
|
|
2777
2784
|
return {
|
|
2778
2785
|
name: "animation",
|
|
@@ -2780,7 +2787,6 @@ function checkAnimationPlugin(baseDirInput) {
|
|
|
2780
2787
|
message: `\uC560\uB2C8\uBA54\uC774\uC158 \uD50C\uB7EC\uADF8\uC778\uC774 \uC5C6\uC2B5\uB2C8\uB2E4(${by}): Dialog \xB7 Select \uB4F1\uC758 \uC5F4\uACE0 \uB2EB\uB294 \uC560\uB2C8\uBA54\uC774\uC158\uC774 \uB3D9\uC791\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2192 ${ANIMATION_PLUGIN_TAILWIND4} \uC124\uCE58 \uB4A4 @import "${ANIMATION_PLUGIN_TAILWIND4}";${note}`
|
|
2781
2788
|
};
|
|
2782
2789
|
}
|
|
2783
|
-
const problems = lines.map((name) => ({ name, why: pluginProblem(name) })).filter((x) => x.why !== void 0);
|
|
2784
2790
|
if (problems.length === 0) return { name: "animation", severity: "pass", message: `${file}\uC5D0 \uC560\uB2C8\uBA54\uC774\uC158 \uD50C\uB7EC\uADF8\uC778\uC774 \uC788\uC2B5\uB2C8\uB2E4${via}${note}` };
|
|
2785
2791
|
return {
|
|
2786
2792
|
name: "animation",
|
|
@@ -2791,7 +2797,7 @@ function checkAnimationPlugin(baseDirInput) {
|
|
|
2791
2797
|
const configFile = TAILWIND_CONFIG_FILES.find((f) => existsSync3(join2(baseDir, f)));
|
|
2792
2798
|
const source = configFile !== void 0 ? stripJsComments(readFileSync4(join2(baseDir, configFile), "utf8")) : "";
|
|
2793
2799
|
const plugins = configFile !== void 0 ? readAnimationPluginUse(source) : "absent";
|
|
2794
|
-
const pluginReason =
|
|
2800
|
+
const pluginReason = animationPluginProblem(baseDir, pkg, ANIMATION_PLUGIN_TAILWIND3, pnp)?.why;
|
|
2795
2801
|
if (plugins === "found" && pluginReason === void 0) {
|
|
2796
2802
|
return { name: "animation", severity: "pass", message: `${configFile}\uC758 plugins \uC5D0 ${ANIMATION_PLUGIN_TAILWIND3} \uAC00 \uC788\uC2B5\uB2C8\uB2E4` };
|
|
2797
2803
|
}
|
|
@@ -3305,7 +3311,7 @@ function missingDependencies(root, items) {
|
|
|
3305
3311
|
for (const item of items) {
|
|
3306
3312
|
for (const dep of item.dependencies) {
|
|
3307
3313
|
const name = dependencyName(dep);
|
|
3308
|
-
if (declaredDependency(root, pkg, name, rootPkg) !== void 0) continue;
|
|
3314
|
+
if (declaredDependency(root, pkg, name, rootPkg, { appOnlyInPnp: true }) !== void 0) continue;
|
|
3309
3315
|
if (name in specs) continue;
|
|
3310
3316
|
const explicit = dep.length > name.length ? dep.slice(name.length + 1) : void 0;
|
|
3311
3317
|
specs[name] = explicit ?? ranges[name];
|
|
@@ -3757,11 +3763,11 @@ async function handleGetComponent(params, deps = {}) {
|
|
|
3757
3763
|
return { component, source: describeOrigin(client, latest.version), notices };
|
|
3758
3764
|
}
|
|
3759
3765
|
function handleGetToken(params) {
|
|
3760
|
-
const { path:
|
|
3761
|
-
if (!
|
|
3766
|
+
const { path: path13 } = params;
|
|
3767
|
+
if (!path13 || path13.trim() === "") {
|
|
3762
3768
|
throw new Error("\uD1A0\uD070 \uACBD\uB85C(path)\uB294 \uD544\uC218\uC785\uB2C8\uB2E4.");
|
|
3763
3769
|
}
|
|
3764
|
-
const token = getToken(
|
|
3770
|
+
const token = getToken(path13);
|
|
3765
3771
|
if (!token) {
|
|
3766
3772
|
return null;
|
|
3767
3773
|
}
|
|
@@ -3842,9 +3848,9 @@ function cssUsage(token) {
|
|
|
3842
3848
|
const shadcnColor = !token.cssVariable.startsWith("--axis-") && token.value.startsWith("hsl(");
|
|
3843
3849
|
return shadcnColor ? `hsl(var(${token.cssVariable}))` : `var(${token.cssVariable})`;
|
|
3844
3850
|
}
|
|
3845
|
-
function formatTokenNotFound(
|
|
3851
|
+
function formatTokenNotFound(path13) {
|
|
3846
3852
|
return [
|
|
3847
|
-
`\uD1A0\uD070\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: \`${
|
|
3853
|
+
`\uD1A0\uD070\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: \`${path13}\``,
|
|
3848
3854
|
"",
|
|
3849
3855
|
"- \uACBD\uB85C\uB294 `axis.list_tokens` \uB85C \uD655\uC778\uD558\uC138\uC694. \uACBD\uB85C \xB7 tokens \uC774\uB984 \uAE30\uBC18 \uACBD\uB85C(\uC608: `control.h.md`) \xB7 CSS \uBCC0\uC218 \uC774\uB984(\uC608: `--axis-control-h-md`)\uC744 \uBC1B\uC2B5\uB2C8\uB2E4.",
|
|
3850
3856
|
"- \uBE0C\uB79C\uB4DC \uAC12\uC740 `axis.list_brands` \xB7 `axis.get_brand` \uB85C \uC870\uD68C\uD558\uC138\uC694."
|
|
@@ -5542,7 +5548,7 @@ function formatSaveResult(result) {
|
|
|
5542
5548
|
// src/tools/template.ts
|
|
5543
5549
|
import fetch from "node-fetch";
|
|
5544
5550
|
import fs6 from "fs-extra";
|
|
5545
|
-
import
|
|
5551
|
+
import path11 from "path";
|
|
5546
5552
|
|
|
5547
5553
|
// src/tools/brand-core.ts
|
|
5548
5554
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -6309,6 +6315,605 @@ function writeTemplateFiles(plan) {
|
|
|
6309
6315
|
}
|
|
6310
6316
|
}
|
|
6311
6317
|
|
|
6318
|
+
// ../axis-registry-core/src/template/template-checks.ts
|
|
6319
|
+
import { lstatSync as lstatSync4, readdirSync as readdirSync4, readFileSync as readFileSync11, realpathSync as realpathSync5, statSync as statSync7 } from "fs";
|
|
6320
|
+
import path10 from "path";
|
|
6321
|
+
var TEMPLATE_CHECK_DEFINITIONS = /* @__PURE__ */ new Map([
|
|
6322
|
+
["ontology-rules", { id: "ontology-rules", script: "check:ontology" }]
|
|
6323
|
+
]);
|
|
6324
|
+
var TEMPLATE_CHECKS = /* @__PURE__ */ new Map([["ontology-console", ["ontology-rules"]]]);
|
|
6325
|
+
var TEMPLATE_SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
6326
|
+
var ONTOLOGY_RULE_IDS = ["em-dash", "exclamation", "one-side-line", "card-shadow", "palette"];
|
|
6327
|
+
var ONTOLOGY_TARGETS = ["app", "components", "lib"];
|
|
6328
|
+
var ONTOLOGY_MAX_FILE_BYTES = 256 * 1024;
|
|
6329
|
+
var ONTOLOGY_DIR_DEPTH_LIMIT = 32;
|
|
6330
|
+
var ONTOLOGY_MAX_UNIT_CHARS = 8e3;
|
|
6331
|
+
var ONTOLOGY_SCAN_STEPS_PER_CHAR = 16;
|
|
6332
|
+
var ONTOLOGY_SCAN_STEPS_BASE = 1e5;
|
|
6333
|
+
var ONTOLOGY_MAX_FILES = 4e3;
|
|
6334
|
+
var ONTOLOGY_MAX_TOTAL_BYTES = 16 * 1024 * 1024;
|
|
6335
|
+
var ONTOLOGY_MAX_ENTRIES = 5e4;
|
|
6336
|
+
var ONTOLOGY_DEFAULT_BUDGET = Object.freeze({
|
|
6337
|
+
maxFiles: ONTOLOGY_MAX_FILES,
|
|
6338
|
+
maxBytes: ONTOLOGY_MAX_TOTAL_BYTES,
|
|
6339
|
+
maxEntries: ONTOLOGY_MAX_ENTRIES
|
|
6340
|
+
});
|
|
6341
|
+
var MESSAGE_PATH_MAX = 120;
|
|
6342
|
+
function templateChecksFor(baseDirInput) {
|
|
6343
|
+
let config;
|
|
6344
|
+
try {
|
|
6345
|
+
config = JSON.parse(readFileSync11(path10.join(path10.resolve(baseDirInput), "axis.config.json"), "utf8"));
|
|
6346
|
+
} catch {
|
|
6347
|
+
return null;
|
|
6348
|
+
}
|
|
6349
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) return null;
|
|
6350
|
+
if (!Object.prototype.hasOwnProperty.call(config, "template")) return null;
|
|
6351
|
+
const template = config.template;
|
|
6352
|
+
if (typeof template !== "string" || !TEMPLATE_SLUG.test(template)) return null;
|
|
6353
|
+
const ids = TEMPLATE_CHECKS.get(template);
|
|
6354
|
+
if (!ids || ids.length === 0) return null;
|
|
6355
|
+
return { template, ids };
|
|
6356
|
+
}
|
|
6357
|
+
function collectTemplateCheckResults(baseDirInput) {
|
|
6358
|
+
const baseDir = path10.resolve(baseDirInput);
|
|
6359
|
+
const picked = templateChecksFor(baseDir);
|
|
6360
|
+
if (!picked) return [];
|
|
6361
|
+
const out = [];
|
|
6362
|
+
for (const id of picked.ids) {
|
|
6363
|
+
const def = TEMPLATE_CHECK_DEFINITIONS.get(id);
|
|
6364
|
+
if (def?.id === "ontology-rules") out.push(ontologyRulesResult(baseDir, picked.template, def));
|
|
6365
|
+
}
|
|
6366
|
+
return out;
|
|
6367
|
+
}
|
|
6368
|
+
function hasPackageScript(baseDir, script) {
|
|
6369
|
+
try {
|
|
6370
|
+
const pkg = JSON.parse(readFileSync11(path10.join(baseDir, "package.json"), "utf8"));
|
|
6371
|
+
if (typeof pkg !== "object" || pkg === null || Array.isArray(pkg)) return false;
|
|
6372
|
+
const scripts = pkg.scripts;
|
|
6373
|
+
if (typeof scripts !== "object" || scripts === null || Array.isArray(scripts)) return false;
|
|
6374
|
+
return Object.prototype.hasOwnProperty.call(scripts, script) && typeof scripts[script] === "string";
|
|
6375
|
+
} catch {
|
|
6376
|
+
return false;
|
|
6377
|
+
}
|
|
6378
|
+
}
|
|
6379
|
+
var MESSAGE_PATH_KEEP = /^[\p{L}\p{N}\p{P}\p{S} ]$/u;
|
|
6380
|
+
function messageSafePath(p) {
|
|
6381
|
+
let out = "";
|
|
6382
|
+
for (const ch of p) {
|
|
6383
|
+
const cp = ch.codePointAt(0);
|
|
6384
|
+
const piece = MESSAGE_PATH_KEEP.test(ch) && ch !== '"' && ch !== "`" && ch !== "\\" ? ch : cp > 65535 ? `\\u{${cp.toString(16)}}` : `\\u${cp.toString(16).padStart(4, "0")}`;
|
|
6385
|
+
if (out.length + piece.length > MESSAGE_PATH_MAX) return `${out}...`;
|
|
6386
|
+
out += piece;
|
|
6387
|
+
}
|
|
6388
|
+
return out;
|
|
6389
|
+
}
|
|
6390
|
+
function ontologyRulesResult(baseDir, template, def) {
|
|
6391
|
+
const name = `template.${def.id}`;
|
|
6392
|
+
const head = `\uD15C\uD50C\uB9BF ${template} \uADDC\uCE59 \uAC80\uC0AC(axis \uB0B4\uC7A5 \uADDC\uCE59, \uC124\uCE58\uD55C axis \uBC84\uC804 \uAE30\uC900 \xB7 \uD504\uB85C\uC81D\uD2B8 \uC2A4\uD06C\uB9BD\uD2B8\uB294 \uC2E4\uD589\uD558\uC9C0 \uC54A\uC74C)`;
|
|
6393
|
+
let inspection;
|
|
6394
|
+
try {
|
|
6395
|
+
inspection = inspectOntologyRules(baseDir);
|
|
6396
|
+
} catch (e) {
|
|
6397
|
+
const code = typeof e?.code === "string" && /^[A-Z0-9_]{1,32}$/.test(e.code) ? e.code : "\uC624\uB958";
|
|
6398
|
+
return { name, severity: "warn", message: `${head}: \uCE21\uC815 \uC2E4\uD328, \uD30C\uC77C\uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4(${code})` };
|
|
6399
|
+
}
|
|
6400
|
+
const parts = ontologySkippedParts(inspection);
|
|
6401
|
+
const skipped = parts.length > 0 ? `, \uAC74\uB108\uB700: ${parts.join(" \xB7 ")}` : "";
|
|
6402
|
+
if (inspection.checked === 0) {
|
|
6403
|
+
return {
|
|
6404
|
+
name,
|
|
6405
|
+
severity: "warn",
|
|
6406
|
+
message: `${head}: \uCE21\uC815 \uC2E4\uD328, \uAC80\uC0AC\uD560 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4(\uB300\uC0C1: ${ONTOLOGY_TARGETS.join(" \xB7 ")})${skipped}`
|
|
6407
|
+
};
|
|
6408
|
+
}
|
|
6409
|
+
const { violations } = inspection;
|
|
6410
|
+
if (violations.length === 0) {
|
|
6411
|
+
if (isOntologyIncomplete(inspection)) {
|
|
6412
|
+
return { name, severity: "warn", message: `${head}: \uC77C\uBD80 \uCE21\uC815 \uC2E4\uD328, \uD30C\uC77C ${inspection.checked}\uAC1C \uC704\uBC18 0\uAC74${skipped}` };
|
|
6413
|
+
}
|
|
6414
|
+
return { name, severity: "pass", message: `${head}: \uD30C\uC77C ${inspection.checked}\uAC1C, \uC704\uBC18 0\uAC74${skipped}` };
|
|
6415
|
+
}
|
|
6416
|
+
const counts = ONTOLOGY_RULE_IDS.map((rule) => [rule, violations.filter((v) => v.rule === rule).length]).filter(([, n]) => n > 0).map(([rule, n]) => `${rule} ${n}`).join(" \xB7 ");
|
|
6417
|
+
const first = violations[0];
|
|
6418
|
+
const hint = hasPackageScript(baseDir, def.script) ? ` \u2192 \uC804\uCCB4 \uBCF4\uAE30: npm run ${def.script}` : "";
|
|
6419
|
+
return {
|
|
6420
|
+
name,
|
|
6421
|
+
severity: "warn",
|
|
6422
|
+
message: `${head}: \uD30C\uC77C ${inspection.checked}\uAC1C, \uC704\uBC18 ${violations.length}\uAC74(${counts}), \uCCAB \uC704\uCE58 "${messageSafePath(first.file)}":${first.line} ${first.rule}${skipped}${hint}`
|
|
6423
|
+
};
|
|
6424
|
+
}
|
|
6425
|
+
function ontologySkippedParts(found) {
|
|
6426
|
+
const parts = [];
|
|
6427
|
+
if (found.skippedLarge > 0) parts.push(`\uD06C\uAE30 \uC0C1\uD55C(256KB) \uCD08\uACFC ${found.skippedLarge}\uAC1C`);
|
|
6428
|
+
if (found.skippedLinks > 0) parts.push(`\uD504\uB85C\uC81D\uD2B8 \uBC16 \xB7 \uAE68\uC9C4 \uB9C1\uD06C ${found.skippedLinks}\uAC1C`);
|
|
6429
|
+
if (found.skippedDeep > 0) parts.push(`\uAE4A\uC774 ${ONTOLOGY_DIR_DEPTH_LIMIT} \uB2E8\uACC4 \uBC16 \uB514\uB809\uD130\uB9AC ${found.skippedDeep}\uAC1C`);
|
|
6430
|
+
if (found.partialFiles > 0) parts.push(`\uD074\uB798\uC2A4 \uBB36\uC74C \uC791\uC5C5\uB7C9 \uC0C1\uD55C \uCD08\uACFC(\uC77C\uBD80\uB9CC \uD310\uC815) ${found.partialFiles}\uAC1C`);
|
|
6431
|
+
if (found.budgetStop) parts.push(ontologyBudgetStopText(found.budgetStop));
|
|
6432
|
+
return parts;
|
|
6433
|
+
}
|
|
6434
|
+
function formatBytes(n) {
|
|
6435
|
+
if (n % (1024 * 1024) === 0) return `${n / (1024 * 1024)}MB`;
|
|
6436
|
+
if (n % 1024 === 0) return `${n / 1024}KB`;
|
|
6437
|
+
return `${n}B`;
|
|
6438
|
+
}
|
|
6439
|
+
function ontologyBudgetStopText(stop) {
|
|
6440
|
+
if (stop.reason === "files") return `\uD504\uB85C\uC81D\uD2B8 \uC608\uC0B0 \uCD08\uACFC\uB85C \uCE21\uC815 \uC911\uB2E8(\uD30C\uC77C ${stop.limit}\uAC1C \uC0C1\uD55C)`;
|
|
6441
|
+
if (stop.reason === "entries") return `\uD504\uB85C\uC81D\uD2B8 \uC608\uC0B0 \uCD08\uACFC\uB85C \uCE21\uC815 \uC911\uB2E8(\uB514\uB809\uD130\uB9AC \uD56D\uBAA9 ${stop.limit}\uAC1C \uC0C1\uD55C)`;
|
|
6442
|
+
return `\uD504\uB85C\uC81D\uD2B8 \uC608\uC0B0 \uCD08\uACFC\uB85C \uCE21\uC815 \uC911\uB2E8(\uC77D\uAE30 ${formatBytes(stop.limit)} \uC0C1\uD55C)`;
|
|
6443
|
+
}
|
|
6444
|
+
function isOntologyIncomplete(found) {
|
|
6445
|
+
return found.skippedLarge > 0 || found.skippedDeep > 0 || found.partialFiles > 0 || found.budgetStop !== null;
|
|
6446
|
+
}
|
|
6447
|
+
var EXTS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".css"]);
|
|
6448
|
+
var EM_DASH = String.fromCharCode(8212);
|
|
6449
|
+
var FULLWIDTH_BANG = String.fromCharCode(65281);
|
|
6450
|
+
var NEUTRAL_BORDER = /* @__PURE__ */ new Set(["border-border", "border-input", "border-transparent", "border-inherit", "border-current"]);
|
|
6451
|
+
var SIDE_WIDTH = /(?:^|\s|:)border-[lrse](?:-(?:0|2|4|8|\[[^\]]+\]))?(?=\s|$)/;
|
|
6452
|
+
var BORDER_COLOR = /(?:^|\s|:)(border-(?:[lrse]-)?(?!(?:0|2|4|8|x|y|t|b|l|r|s|e|solid|dashed|dotted|double|none|hidden|collapse|separate|spacing)(?:\s|$))[a-z][\w-]*(?:\/\d+)?|border-(?:[lrse]-)?\[(?:#|rgb|hsl)[^\]]*\])(?=\s|$)/g;
|
|
6453
|
+
var BORDER_NOT_COLOR = /^border-(?:(?:[lrsetbxy])(?:-(?:0|2|4|8|\[[^\]]+\]))?|0|2|4|8|\[\d[^\]]*\]|solid|dashed|dotted|double|none|hidden|collapse|separate)$/;
|
|
6454
|
+
var SHADOW = /(?:^|\s|:)shadow(?:-(?!none(?:\s|$))[\w[\]().,%-]+)?(?=\s|$)/;
|
|
6455
|
+
var PALETTE_NAMES = "slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose";
|
|
6456
|
+
var PALETTE_CLASS = new RegExp(
|
|
6457
|
+
`(?:^|[\\s:"'\`])((?:bg|text|border|ring|fill|stroke|from|via|to|outline|divide|decoration|shadow|accent|caret|placeholder)-(?:(?:${PALETTE_NAMES})-\\d{2,3}|white|black)(?:\\/\\d+)?)(?=[\\s"'\`]|$)`,
|
|
6458
|
+
"g"
|
|
6459
|
+
);
|
|
6460
|
+
var HEX = /#[0-9a-fA-F]{3,8}\b/g;
|
|
6461
|
+
var NAMED_COLORS = new Set(
|
|
6462
|
+
"aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato turquoise violet wheat white whitesmoke yellow yellowgreen".split(" ")
|
|
6463
|
+
);
|
|
6464
|
+
var CSS_COLOR_PROP = /(?:^|[;{\s])((?:background|border|outline|text-decoration|column-rule)(?:-(?:top|right|bottom|left|inline-start|inline-end))?(?:-color)?|color|fill|stroke|caret-color|accent-color|box-shadow)\s*:\s*([^;}]+)/g;
|
|
6465
|
+
var STYLE_COLOR_KEY = /\b((?:background|border|outline|textDecoration|columnRule)(?:Top|Right|Bottom|Left|InlineStart|InlineEnd)?(?:Color)?|color|fill|stroke|caretColor|accentColor|boxShadow)\s*:\s*(["'`])([^"'`]*)\2/g;
|
|
6466
|
+
var COLOR_FN = /\b(?:rgba?|hsla?)\(\s*\d/g;
|
|
6467
|
+
var HANGUL = /[가-힣]/;
|
|
6468
|
+
function stripComments2(src) {
|
|
6469
|
+
const blank = (m) => m.replace(/[^\n]/g, " ");
|
|
6470
|
+
let out = "";
|
|
6471
|
+
let last = 0;
|
|
6472
|
+
for (let i = src.indexOf("/*"); i !== -1; i = src.indexOf("/*", last)) {
|
|
6473
|
+
const end = src.indexOf("*/", i + 2);
|
|
6474
|
+
if (end === -1) break;
|
|
6475
|
+
out += src.slice(last, i) + blank(src.slice(i, end + 2));
|
|
6476
|
+
last = end + 2;
|
|
6477
|
+
}
|
|
6478
|
+
out += src.slice(last);
|
|
6479
|
+
return out.replace(/(^|[^:'"`\\])(\/\/[^\n]*)/g, (_, pre, c) => pre + blank(c));
|
|
6480
|
+
}
|
|
6481
|
+
function lineIndex(src) {
|
|
6482
|
+
const breaks = [];
|
|
6483
|
+
for (let i = src.indexOf("\n"); i !== -1; i = src.indexOf("\n", i + 1)) breaks.push(i);
|
|
6484
|
+
return (index) => {
|
|
6485
|
+
let lo = 0;
|
|
6486
|
+
let hi = breaks.length;
|
|
6487
|
+
while (lo < hi) {
|
|
6488
|
+
const mid = lo + hi >> 1;
|
|
6489
|
+
if (breaks[mid] < index) lo = mid + 1;
|
|
6490
|
+
else hi = mid;
|
|
6491
|
+
}
|
|
6492
|
+
return lo + 1;
|
|
6493
|
+
};
|
|
6494
|
+
}
|
|
6495
|
+
var isLineTerminator = (c) => c === "\n" || c === "\r" || c === "\u2028" || c === "\u2029";
|
|
6496
|
+
function literalEnds(code, q) {
|
|
6497
|
+
const n = code.length;
|
|
6498
|
+
const end = new Int32Array(n + 2).fill(-1);
|
|
6499
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
6500
|
+
const c = code[i];
|
|
6501
|
+
if (c === q) end[i] = i;
|
|
6502
|
+
else if (c === "\n") end[i] = -1;
|
|
6503
|
+
else if (c === "\\") end[i] = i + 1 < n && !isLineTerminator(code[i + 1]) ? end[i + 2] : -1;
|
|
6504
|
+
else end[i] = end[i + 1];
|
|
6505
|
+
}
|
|
6506
|
+
return end;
|
|
6507
|
+
}
|
|
6508
|
+
function stringLiterals2(code) {
|
|
6509
|
+
const ends = { '"': literalEnds(code, '"'), "'": literalEnds(code, "'"), "`": literalEnds(code, "`") };
|
|
6510
|
+
const out = [];
|
|
6511
|
+
for (let i = 0; i < code.length; i++) {
|
|
6512
|
+
const c = code[i];
|
|
6513
|
+
if (c !== '"' && c !== "'" && c !== "`") continue;
|
|
6514
|
+
const e = ends[c][i + 1];
|
|
6515
|
+
if (e === -1) continue;
|
|
6516
|
+
out.push({ text: code.slice(i + 1, e), index: i });
|
|
6517
|
+
i = e;
|
|
6518
|
+
}
|
|
6519
|
+
return out;
|
|
6520
|
+
}
|
|
6521
|
+
function textChunks(code, isJsx) {
|
|
6522
|
+
const out = stringLiterals2(code).map((x) => ({ text: x.text, index: x.index, kind: "string" }));
|
|
6523
|
+
let m;
|
|
6524
|
+
if (isJsx) {
|
|
6525
|
+
const jsx = /[>}]([^<>{}]+)(?=[<{])/g;
|
|
6526
|
+
while (m = jsx.exec(code)) {
|
|
6527
|
+
if (/[=;]/.test(m[1])) continue;
|
|
6528
|
+
const text = m[1].replace(/(["'`])(?:(?!\1)[^\n])*\1/g, " ");
|
|
6529
|
+
out.push({ text, index: m.index + 1, kind: "jsx" });
|
|
6530
|
+
}
|
|
6531
|
+
}
|
|
6532
|
+
return out;
|
|
6533
|
+
}
|
|
6534
|
+
var SCAN_BUDGET_EXCEEDED = /* @__PURE__ */ Symbol("scan-budget-exceeded");
|
|
6535
|
+
function spend(budget, steps) {
|
|
6536
|
+
budget.left -= steps;
|
|
6537
|
+
if (budget.left < 0) throw SCAN_BUDGET_EXCEEDED;
|
|
6538
|
+
}
|
|
6539
|
+
function skipEnds(code, q) {
|
|
6540
|
+
const n = code.length;
|
|
6541
|
+
const end = new Int32Array(n + 2).fill(n);
|
|
6542
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
6543
|
+
const c = code[i];
|
|
6544
|
+
end[i] = c === q ? i : c === "\\" ? end[Math.min(i + 2, n)] : end[i + 1];
|
|
6545
|
+
}
|
|
6546
|
+
return end;
|
|
6547
|
+
}
|
|
6548
|
+
function closeMatcher(code, budget) {
|
|
6549
|
+
const memo = /* @__PURE__ */ new Map();
|
|
6550
|
+
const pair = { "(": ")", "{": "}" };
|
|
6551
|
+
const skips = { '"': skipEnds(code, '"'), "'": skipEnds(code, "'"), "`": skipEnds(code, "`") };
|
|
6552
|
+
return (open) => {
|
|
6553
|
+
const known = memo.get(open);
|
|
6554
|
+
if (known !== void 0) return known;
|
|
6555
|
+
const limit = open + ONTOLOGY_MAX_UNIT_CHARS;
|
|
6556
|
+
const stack = [pair[code[open]]];
|
|
6557
|
+
const opens = [open];
|
|
6558
|
+
let steps = 0;
|
|
6559
|
+
for (let i = open + 1; i < code.length; i++) {
|
|
6560
|
+
if (++steps === 1024) {
|
|
6561
|
+
spend(budget, steps);
|
|
6562
|
+
steps = 0;
|
|
6563
|
+
}
|
|
6564
|
+
if (i >= limit) {
|
|
6565
|
+
spend(budget, steps);
|
|
6566
|
+
memo.set(open, -1);
|
|
6567
|
+
return -1;
|
|
6568
|
+
}
|
|
6569
|
+
const c = code[i];
|
|
6570
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
6571
|
+
i = skips[c][i + 1];
|
|
6572
|
+
continue;
|
|
6573
|
+
}
|
|
6574
|
+
if (c === "(" || c === "{") {
|
|
6575
|
+
const inner = memo.get(i);
|
|
6576
|
+
if (inner !== void 0) {
|
|
6577
|
+
if (inner === -1) {
|
|
6578
|
+
spend(budget, steps);
|
|
6579
|
+
for (const p of opens) memo.set(p, -1);
|
|
6580
|
+
return -1;
|
|
6581
|
+
}
|
|
6582
|
+
i = inner;
|
|
6583
|
+
continue;
|
|
6584
|
+
}
|
|
6585
|
+
stack.push(pair[c]);
|
|
6586
|
+
opens.push(i);
|
|
6587
|
+
} else if (c === stack[stack.length - 1]) {
|
|
6588
|
+
stack.pop();
|
|
6589
|
+
memo.set(opens.pop(), i);
|
|
6590
|
+
if (stack.length === 0) {
|
|
6591
|
+
spend(budget, steps);
|
|
6592
|
+
return i;
|
|
6593
|
+
}
|
|
6594
|
+
}
|
|
6595
|
+
}
|
|
6596
|
+
spend(budget, steps);
|
|
6597
|
+
for (const p of opens) memo.set(p, -1);
|
|
6598
|
+
return -1;
|
|
6599
|
+
};
|
|
6600
|
+
}
|
|
6601
|
+
function joinStrings(expr, budget) {
|
|
6602
|
+
spend(budget, expr.length);
|
|
6603
|
+
return stringLiterals2(expr).map((x) => x.text).join(" ");
|
|
6604
|
+
}
|
|
6605
|
+
function isCardTagAt(code, at, end) {
|
|
6606
|
+
if (at < 0 || at + 5 > end || !code.startsWith("<Card", at)) return false;
|
|
6607
|
+
const next = at + 5;
|
|
6608
|
+
return next >= end || !/\w/.test(code[next]);
|
|
6609
|
+
}
|
|
6610
|
+
function lastLtFinder(code) {
|
|
6611
|
+
let pos = 0;
|
|
6612
|
+
let last = -1;
|
|
6613
|
+
return (index) => {
|
|
6614
|
+
for (; pos <= index && pos < code.length; pos++) if (code[pos] === "<") last = pos;
|
|
6615
|
+
return last;
|
|
6616
|
+
};
|
|
6617
|
+
}
|
|
6618
|
+
function cardClassStrings(code) {
|
|
6619
|
+
const out = [];
|
|
6620
|
+
let from = 0;
|
|
6621
|
+
let noQuoteAfter = Infinity;
|
|
6622
|
+
for (let at = code.indexOf("<Card", from); at !== -1; at = code.indexOf("<Card", from)) {
|
|
6623
|
+
from = at + 1;
|
|
6624
|
+
if (!isCardTagAt(code, at, code.length)) continue;
|
|
6625
|
+
for (let k = at + 5; k < code.length; k++) {
|
|
6626
|
+
if (k + 11 <= code.length && code.startsWith('className="', k) && k + 11 < noQuoteAfter) {
|
|
6627
|
+
const q = code.indexOf('"', k + 11);
|
|
6628
|
+
if (q !== -1) {
|
|
6629
|
+
out.push({ text: code.slice(k + 11, q), index: at, card: true });
|
|
6630
|
+
from = q + 1;
|
|
6631
|
+
break;
|
|
6632
|
+
}
|
|
6633
|
+
noQuoteAfter = k + 11;
|
|
6634
|
+
}
|
|
6635
|
+
const c = code[k];
|
|
6636
|
+
if (c === "<") break;
|
|
6637
|
+
if (c === ">" && !(k - 1 >= at + 5 && code[k - 1] === "=")) break;
|
|
6638
|
+
}
|
|
6639
|
+
}
|
|
6640
|
+
return out;
|
|
6641
|
+
}
|
|
6642
|
+
function classUnits(code, budget) {
|
|
6643
|
+
const out = [];
|
|
6644
|
+
const matchClose = closeMatcher(code, budget);
|
|
6645
|
+
const lastLt = lastLtFinder(code);
|
|
6646
|
+
let m;
|
|
6647
|
+
const attr = /className=\{/g;
|
|
6648
|
+
while (m = attr.exec(code)) {
|
|
6649
|
+
const open = m.index + m[0].length - 1;
|
|
6650
|
+
const close = matchClose(open);
|
|
6651
|
+
if (close === -1 || close - open + 1 > ONTOLOGY_MAX_UNIT_CHARS) continue;
|
|
6652
|
+
out.push({ text: joinStrings(code.slice(open, close + 1), budget), index: m.index, card: isCardTagAt(code, lastLt(m.index), m.index) });
|
|
6653
|
+
}
|
|
6654
|
+
const call = /\b(?:cn|clsx|cva|twMerge)\(/g;
|
|
6655
|
+
while (m = call.exec(code)) {
|
|
6656
|
+
const open = m.index + m[0].length - 1;
|
|
6657
|
+
const close = matchClose(open);
|
|
6658
|
+
if (close === -1 || close - open + 1 > ONTOLOGY_MAX_UNIT_CHARS) continue;
|
|
6659
|
+
out.push({ text: joinStrings(code.slice(open, close + 1), budget), index: m.index, card: false });
|
|
6660
|
+
}
|
|
6661
|
+
out.push(...cardClassStrings(code));
|
|
6662
|
+
return out;
|
|
6663
|
+
}
|
|
6664
|
+
function cssRules(code) {
|
|
6665
|
+
const out = [];
|
|
6666
|
+
const nextBrace = (from) => {
|
|
6667
|
+
for (let k = from; k < code.length; k++) if (code[k] === "{" || code[k] === "}") return k;
|
|
6668
|
+
return -1;
|
|
6669
|
+
};
|
|
6670
|
+
let i = 0;
|
|
6671
|
+
while (i < code.length) {
|
|
6672
|
+
if (code[i] === "{" || code[i] === "}") {
|
|
6673
|
+
i++;
|
|
6674
|
+
continue;
|
|
6675
|
+
}
|
|
6676
|
+
const open = nextBrace(i);
|
|
6677
|
+
if (open === -1) break;
|
|
6678
|
+
if (code[open] !== "{") {
|
|
6679
|
+
i = open + 1;
|
|
6680
|
+
continue;
|
|
6681
|
+
}
|
|
6682
|
+
const close = nextBrace(open + 1);
|
|
6683
|
+
if (close === -1) break;
|
|
6684
|
+
if (code[close] !== "}") {
|
|
6685
|
+
i = open + 1;
|
|
6686
|
+
continue;
|
|
6687
|
+
}
|
|
6688
|
+
out.push({ selector: code.slice(i, open), body: code.slice(open + 1, close), open });
|
|
6689
|
+
i = close + 1;
|
|
6690
|
+
}
|
|
6691
|
+
return out;
|
|
6692
|
+
}
|
|
6693
|
+
function oneSideColors(t) {
|
|
6694
|
+
if (!SIDE_WIDTH.test(t)) return [];
|
|
6695
|
+
return [...t.matchAll(BORDER_COLOR)].map((x) => x[1]).filter((c) => !NEUTRAL_BORDER.has(c.replace(/^border-[lrse]-/, "border-")) && !BORDER_NOT_COLOR.test(c));
|
|
6696
|
+
}
|
|
6697
|
+
function isCardLike(t, card) {
|
|
6698
|
+
return card || /(?:^|\s)bg-card(?=\s|$)/.test(t) || /(?:^|\s)rounded(?:-[\w[\]]+)?(?=\s|$)/.test(t) && /(?:^|\s)border(?:-(?:0|2|4|8))?(?=\s|$)/.test(t);
|
|
6699
|
+
}
|
|
6700
|
+
function sideValueColored(value) {
|
|
6701
|
+
const v = value.trim();
|
|
6702
|
+
const neutral = /^(?:0|none|hidden)$/.test(v) || /var\(--border\)|hsl\(var\(--border\)\)|transparent|currentColor/.test(v);
|
|
6703
|
+
const colored = /#|rgb|hsl|var\(--/.test(v) || v.split(/[\s,()]+/).some((w) => NAMED_COLORS.has(w.toLowerCase()));
|
|
6704
|
+
return colored && !neutral;
|
|
6705
|
+
}
|
|
6706
|
+
function byLineThenRule(a, b) {
|
|
6707
|
+
return a.line - b.line || ONTOLOGY_RULE_IDS.indexOf(a.rule) - ONTOLOGY_RULE_IDS.indexOf(b.rule);
|
|
6708
|
+
}
|
|
6709
|
+
function inspectOntologySourceDetailed(raw, ext) {
|
|
6710
|
+
const out = [];
|
|
6711
|
+
let partial = false;
|
|
6712
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6713
|
+
const code = stripComments2(raw);
|
|
6714
|
+
const lineOf = lineIndex(raw);
|
|
6715
|
+
const report = (index, rule) => {
|
|
6716
|
+
const line = lineOf(index);
|
|
6717
|
+
const key = `${line}:${rule}`;
|
|
6718
|
+
if (seen.has(key)) return;
|
|
6719
|
+
seen.add(key);
|
|
6720
|
+
out.push({ line, rule });
|
|
6721
|
+
};
|
|
6722
|
+
for (let i = raw.indexOf(EM_DASH); i !== -1; i = raw.indexOf(EM_DASH, i + 1)) report(i, "em-dash");
|
|
6723
|
+
if (ext === ".css") {
|
|
6724
|
+
const side = /border-(?:left|right|inline-start|inline-end)(?:-color)?\s*:\s*([^;}]+)/g;
|
|
6725
|
+
let m2;
|
|
6726
|
+
while (m2 = side.exec(code)) {
|
|
6727
|
+
if (sideValueColored(m2[1])) report(m2.index, "one-side-line");
|
|
6728
|
+
}
|
|
6729
|
+
for (const r of cssRules(code)) {
|
|
6730
|
+
if (/card/i.test(r.selector) && /box-shadow\s*:(?!\s*(?:none\b|0 0 #0000))/.test(r.body)) report(r.open, "card-shadow");
|
|
6731
|
+
}
|
|
6732
|
+
} else {
|
|
6733
|
+
const isJsx = ext === ".tsx" || ext === ".jsx";
|
|
6734
|
+
for (const chunk of textChunks(code, isJsx)) {
|
|
6735
|
+
const t = chunk.text;
|
|
6736
|
+
if (HANGUL.test(t) && (t.includes("!") || t.includes(FULLWIDTH_BANG))) report(chunk.index, "exclamation");
|
|
6737
|
+
if (chunk.kind !== "string") continue;
|
|
6738
|
+
if (t.length > ONTOLOGY_MAX_UNIT_CHARS) continue;
|
|
6739
|
+
if (oneSideColors(t).length > 0) report(chunk.index, "one-side-line");
|
|
6740
|
+
if (isCardLike(t, false) && SHADOW.test(t)) report(chunk.index, "card-shadow");
|
|
6741
|
+
}
|
|
6742
|
+
let units = [];
|
|
6743
|
+
try {
|
|
6744
|
+
units = classUnits(code, { left: code.length * ONTOLOGY_SCAN_STEPS_PER_CHAR + ONTOLOGY_SCAN_STEPS_BASE });
|
|
6745
|
+
} catch (e) {
|
|
6746
|
+
if (e !== SCAN_BUDGET_EXCEEDED) throw e;
|
|
6747
|
+
partial = true;
|
|
6748
|
+
}
|
|
6749
|
+
for (const unit of units) {
|
|
6750
|
+
const t = unit.text;
|
|
6751
|
+
if (oneSideColors(t).length > 0) report(unit.index, "one-side-line");
|
|
6752
|
+
if (isCardLike(t, unit.card) && SHADOW.test(t)) report(unit.index, "card-shadow");
|
|
6753
|
+
}
|
|
6754
|
+
const lastLt = lastLtFinder(code);
|
|
6755
|
+
let m2;
|
|
6756
|
+
STYLE_COLOR_KEY.lastIndex = 0;
|
|
6757
|
+
while (m2 = STYLE_COLOR_KEY.exec(code)) {
|
|
6758
|
+
const key = m2[1];
|
|
6759
|
+
const value = m2[3];
|
|
6760
|
+
if (/^border(?:Left|Right|InlineStart|InlineEnd)(?:Color)?$/.test(key) && sideValueColored(value)) {
|
|
6761
|
+
report(m2.index, "one-side-line");
|
|
6762
|
+
}
|
|
6763
|
+
if (key === "boxShadow" && !/^(?:none|0 0 #0000)$/.test(value.trim())) {
|
|
6764
|
+
if (isCardTagAt(code, lastLt(m2.index), code.length)) report(m2.index, "card-shadow");
|
|
6765
|
+
}
|
|
6766
|
+
if (value.split(/[\s,()]+/).some((w) => NAMED_COLORS.has(w.toLowerCase()))) report(m2.index, "palette");
|
|
6767
|
+
}
|
|
6768
|
+
}
|
|
6769
|
+
if (ext === ".css") {
|
|
6770
|
+
let m2;
|
|
6771
|
+
CSS_COLOR_PROP.lastIndex = 0;
|
|
6772
|
+
while (m2 = CSS_COLOR_PROP.exec(code)) {
|
|
6773
|
+
if (m2[2].split(/[\s,()]+/).some((w) => NAMED_COLORS.has(w.toLowerCase()))) report(m2.index, "palette");
|
|
6774
|
+
}
|
|
6775
|
+
}
|
|
6776
|
+
let m;
|
|
6777
|
+
HEX.lastIndex = 0;
|
|
6778
|
+
while (m = HEX.exec(code)) {
|
|
6779
|
+
if (code[m.index - 1] === "&") continue;
|
|
6780
|
+
if (/[\w/]/.test(code[m.index - 1] ?? "")) continue;
|
|
6781
|
+
if (/(?:href|to|id)=\{?\s*["'`]$/.test(code.slice(Math.max(0, m.index - 12), m.index))) continue;
|
|
6782
|
+
report(m.index, "palette");
|
|
6783
|
+
}
|
|
6784
|
+
COLOR_FN.lastIndex = 0;
|
|
6785
|
+
while (m = COLOR_FN.exec(code)) report(m.index, "palette");
|
|
6786
|
+
PALETTE_CLASS.lastIndex = 0;
|
|
6787
|
+
while (m = PALETTE_CLASS.exec(code)) report(m.index, "palette");
|
|
6788
|
+
return { violations: out.sort(byLineThenRule), partial };
|
|
6789
|
+
}
|
|
6790
|
+
function collectOntologyFiles(baseDir, targets, maxFiles, maxEntries) {
|
|
6791
|
+
const rootReal = realpathSync5(baseDir);
|
|
6792
|
+
const found = {
|
|
6793
|
+
files: [],
|
|
6794
|
+
skippedLarge: 0,
|
|
6795
|
+
skippedLinks: 0,
|
|
6796
|
+
skippedDeep: 0,
|
|
6797
|
+
partialFiles: 0,
|
|
6798
|
+
budgetStop: null
|
|
6799
|
+
};
|
|
6800
|
+
const visitedDirs = /* @__PURE__ */ new Set();
|
|
6801
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
6802
|
+
let entries = 0;
|
|
6803
|
+
const toRel = (p) => path10.relative(baseDir, p).split(path10.sep).join("/");
|
|
6804
|
+
const resolveEntry = (p, st) => {
|
|
6805
|
+
if (!st.isSymbolicLink()) return { st, real: void 0 };
|
|
6806
|
+
let real;
|
|
6807
|
+
let target;
|
|
6808
|
+
try {
|
|
6809
|
+
real = realpathSync5(p);
|
|
6810
|
+
target = statSync7(real);
|
|
6811
|
+
} catch {
|
|
6812
|
+
found.skippedLinks++;
|
|
6813
|
+
return void 0;
|
|
6814
|
+
}
|
|
6815
|
+
if (!isSameOrInside(rootReal, real)) {
|
|
6816
|
+
found.skippedLinks++;
|
|
6817
|
+
return void 0;
|
|
6818
|
+
}
|
|
6819
|
+
return { st: target, real };
|
|
6820
|
+
};
|
|
6821
|
+
const addFile = (p, real, st, ext) => {
|
|
6822
|
+
if (seenFiles.has(real)) return;
|
|
6823
|
+
seenFiles.add(real);
|
|
6824
|
+
if (st.size > ONTOLOGY_MAX_FILE_BYTES) {
|
|
6825
|
+
found.skippedLarge++;
|
|
6826
|
+
return;
|
|
6827
|
+
}
|
|
6828
|
+
if (found.files.length >= maxFiles) {
|
|
6829
|
+
found.budgetStop = { reason: "files", limit: maxFiles };
|
|
6830
|
+
return;
|
|
6831
|
+
}
|
|
6832
|
+
found.files.push({ rel: toRel(p), real, ext, size: st.size });
|
|
6833
|
+
};
|
|
6834
|
+
const walk = (dir, dirReal, depth) => {
|
|
6835
|
+
const names = readdirSync4(dir).sort();
|
|
6836
|
+
for (const name of names) {
|
|
6837
|
+
if (found.budgetStop) return;
|
|
6838
|
+
if (entries >= maxEntries) {
|
|
6839
|
+
found.budgetStop = { reason: "entries", limit: maxEntries };
|
|
6840
|
+
return;
|
|
6841
|
+
}
|
|
6842
|
+
entries++;
|
|
6843
|
+
if (name === "node_modules" || name.startsWith(".")) continue;
|
|
6844
|
+
const p = path10.join(dir, name);
|
|
6845
|
+
const entry = resolveEntry(p, lstatSync4(p));
|
|
6846
|
+
if (!entry) continue;
|
|
6847
|
+
const real = entry.real ?? path10.join(dirReal, name);
|
|
6848
|
+
if (entry.st.isDirectory()) {
|
|
6849
|
+
if (visitedDirs.has(real)) continue;
|
|
6850
|
+
if (depth >= ONTOLOGY_DIR_DEPTH_LIMIT) {
|
|
6851
|
+
found.skippedDeep++;
|
|
6852
|
+
continue;
|
|
6853
|
+
}
|
|
6854
|
+
visitedDirs.add(real);
|
|
6855
|
+
walk(p, real, depth + 1);
|
|
6856
|
+
} else if (entry.st.isFile() && EXTS.has(path10.extname(name))) {
|
|
6857
|
+
addFile(p, real, entry.st, path10.extname(name));
|
|
6858
|
+
}
|
|
6859
|
+
}
|
|
6860
|
+
};
|
|
6861
|
+
for (const t of targets) {
|
|
6862
|
+
if (found.budgetStop) break;
|
|
6863
|
+
const p = path10.join(baseDir, t);
|
|
6864
|
+
let st;
|
|
6865
|
+
try {
|
|
6866
|
+
st = lstatSync4(p);
|
|
6867
|
+
} catch (e) {
|
|
6868
|
+
if (e?.code === "ENOENT") continue;
|
|
6869
|
+
throw e;
|
|
6870
|
+
}
|
|
6871
|
+
const entry = resolveEntry(p, st);
|
|
6872
|
+
if (!entry) continue;
|
|
6873
|
+
const real = entry.real ?? realpathSync5(p);
|
|
6874
|
+
if (!isSameOrInside(rootReal, real)) {
|
|
6875
|
+
found.skippedLinks++;
|
|
6876
|
+
continue;
|
|
6877
|
+
}
|
|
6878
|
+
if (entry.st.isDirectory()) {
|
|
6879
|
+
if (visitedDirs.has(real)) continue;
|
|
6880
|
+
visitedDirs.add(real);
|
|
6881
|
+
walk(p, real, 1);
|
|
6882
|
+
} else if (entry.st.isFile() && EXTS.has(path10.extname(p))) {
|
|
6883
|
+
addFile(p, real, entry.st, path10.extname(p));
|
|
6884
|
+
}
|
|
6885
|
+
}
|
|
6886
|
+
return found;
|
|
6887
|
+
}
|
|
6888
|
+
function inspectOntologyRules(baseDirInput, targets = ONTOLOGY_TARGETS, budget = ONTOLOGY_DEFAULT_BUDGET) {
|
|
6889
|
+
const baseDir = path10.resolve(baseDirInput);
|
|
6890
|
+
const found = collectOntologyFiles(baseDir, targets, budget.maxFiles, budget.maxEntries);
|
|
6891
|
+
const violations = [];
|
|
6892
|
+
let checked = 0;
|
|
6893
|
+
let bytes = 0;
|
|
6894
|
+
for (const f of found.files) {
|
|
6895
|
+
if (bytes + f.size > budget.maxBytes) {
|
|
6896
|
+
found.budgetStop = { reason: "bytes", limit: budget.maxBytes };
|
|
6897
|
+
break;
|
|
6898
|
+
}
|
|
6899
|
+
bytes += f.size;
|
|
6900
|
+
const raw = readFileSync11(f.real, "utf8");
|
|
6901
|
+
checked++;
|
|
6902
|
+
const result = inspectOntologySourceDetailed(raw, f.ext);
|
|
6903
|
+
if (result.partial) found.partialFiles++;
|
|
6904
|
+
for (const v of result.violations) violations.push({ file: f.rel, line: v.line, rule: v.rule });
|
|
6905
|
+
}
|
|
6906
|
+
return {
|
|
6907
|
+
checked,
|
|
6908
|
+
violations,
|
|
6909
|
+
skippedLarge: found.skippedLarge,
|
|
6910
|
+
skippedLinks: found.skippedLinks,
|
|
6911
|
+
skippedDeep: found.skippedDeep,
|
|
6912
|
+
partialFiles: found.partialFiles,
|
|
6913
|
+
budgetStop: found.budgetStop
|
|
6914
|
+
};
|
|
6915
|
+
}
|
|
6916
|
+
|
|
6312
6917
|
// src/site.ts
|
|
6313
6918
|
var AXIS_SITE_URL2 = "https://axis-design-system.pages.dev";
|
|
6314
6919
|
|
|
@@ -6362,7 +6967,7 @@ async function handleGetTemplate(params) {
|
|
|
6362
6967
|
}
|
|
6363
6968
|
async function handleApplyTemplate(params) {
|
|
6364
6969
|
const detail = await fetchTemplateDetail(params.name);
|
|
6365
|
-
const baseDir =
|
|
6970
|
+
const baseDir = path11.resolve(params.targetDir);
|
|
6366
6971
|
const installCommand = detectInstallCommand(baseDir);
|
|
6367
6972
|
const plan = planTemplateFiles(baseDir, detail.files, (detail.postInstall ?? []).map((p) => p.file));
|
|
6368
6973
|
if (!params.dryRun) writeTemplateFiles(plan);
|
|
@@ -6401,7 +7006,7 @@ async function handleApplyTemplate(params) {
|
|
|
6401
7006
|
}
|
|
6402
7007
|
async function handleDiffTemplate(params) {
|
|
6403
7008
|
const detail = await fetchTemplateDetail(params.name);
|
|
6404
|
-
const baseDir =
|
|
7009
|
+
const baseDir = path11.resolve(params.targetDir || ".");
|
|
6405
7010
|
const entries = [];
|
|
6406
7011
|
const localPaths = detail.files.map((file) => resolveTemplatePath(baseDir, file.path));
|
|
6407
7012
|
for (const [i, file] of detail.files.entries()) {
|
|
@@ -6493,9 +7098,9 @@ function brandActivationResult(check) {
|
|
|
6493
7098
|
};
|
|
6494
7099
|
}
|
|
6495
7100
|
async function handleCheckProject(params) {
|
|
6496
|
-
const baseDir =
|
|
7101
|
+
const baseDir = path11.resolve(params.targetDir || ".");
|
|
6497
7102
|
const results = [];
|
|
6498
|
-
const configExists = await fs6.pathExists(
|
|
7103
|
+
const configExists = await fs6.pathExists(path11.join(baseDir, "axis.config.json"));
|
|
6499
7104
|
results.push({
|
|
6500
7105
|
name: "axis.config.json",
|
|
6501
7106
|
severity: configExists ? "pass" : "fail",
|
|
@@ -6512,7 +7117,7 @@ async function handleCheckProject(params) {
|
|
|
6512
7117
|
];
|
|
6513
7118
|
let utilsFound = false;
|
|
6514
7119
|
for (const candidate of utilsCandidates) {
|
|
6515
|
-
const fullPath =
|
|
7120
|
+
const fullPath = path11.join(baseDir, candidate);
|
|
6516
7121
|
if (await fs6.pathExists(fullPath)) {
|
|
6517
7122
|
const content2 = await fs6.readFile(fullPath, "utf-8");
|
|
6518
7123
|
const hasCn = content2.includes("function cn");
|
|
@@ -6541,6 +7146,7 @@ async function handleCheckProject(params) {
|
|
|
6541
7146
|
if (content) results.push(content);
|
|
6542
7147
|
const animation = checkAnimationPlugin(baseDir);
|
|
6543
7148
|
if (animation) results.push(animation);
|
|
7149
|
+
results.push(...collectTemplateCheckResults(baseDir));
|
|
6544
7150
|
const summary = {
|
|
6545
7151
|
pass: results.filter((r) => r.severity === "pass").length,
|
|
6546
7152
|
warn: results.filter((r) => r.severity === "warn").length,
|
|
@@ -6663,10 +7269,10 @@ function formatCheckProjectResult(result) {
|
|
|
6663
7269
|
}
|
|
6664
7270
|
|
|
6665
7271
|
// src/tools/brand.ts
|
|
6666
|
-
import
|
|
7272
|
+
import path12 from "path";
|
|
6667
7273
|
var TOKENS_INSTALL_COMMAND = addCommandFor("pnpm install", { [TOKENS_PACKAGE]: `^${TOKENS_DEPENDENCY_MIN_VERSION}` });
|
|
6668
7274
|
function baseDirOf(targetDir) {
|
|
6669
|
-
return
|
|
7275
|
+
return path12.resolve(targetDir || process.cwd());
|
|
6670
7276
|
}
|
|
6671
7277
|
function tokensInstallGuideFor(targetDir) {
|
|
6672
7278
|
return dependencyInstallGuide(baseDirOf(targetDir), TOKENS_PACKAGE, `^${TOKENS_DEPENDENCY_MIN_VERSION}`);
|
|
@@ -6954,10 +7560,10 @@ server.tool(
|
|
|
6954
7560
|
{
|
|
6955
7561
|
path: z.string().describe("\uD1A0\uD070 \uACBD\uB85C (\uC608: colors.primary, spacing.4, control.h.md) \uB610\uB294 CSS \uBCC0\uC218 \uC774\uB984 (\uC608: --axis-control-h-md)")
|
|
6956
7562
|
},
|
|
6957
|
-
async ({ path:
|
|
7563
|
+
async ({ path: path13 }) => {
|
|
6958
7564
|
try {
|
|
6959
|
-
const token = handleGetToken({ path:
|
|
6960
|
-
const formatted = formatTokenDetail(token,
|
|
7565
|
+
const token = handleGetToken({ path: path13 });
|
|
7566
|
+
const formatted = formatTokenDetail(token, path13);
|
|
6961
7567
|
return {
|
|
6962
7568
|
content: [
|
|
6963
7569
|
{
|
|
@@ -7331,7 +7937,7 @@ server.tool(
|
|
|
7331
7937
|
);
|
|
7332
7938
|
server.tool(
|
|
7333
7939
|
"axis.check_project",
|
|
7334
|
-
"\uD504\uB85C\uC81D\uD2B8\uC758 AXIS \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C \uC124\uC815 \uC0C1\uD0DC\uB97C \uAC80\uC99D\uD569\uB2C8\uB2E4.",
|
|
7940
|
+
"\uD504\uB85C\uC81D\uD2B8\uC758 AXIS \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C \uC124\uC815 \uC0C1\uD0DC\uB97C \uAC80\uC99D\uD569\uB2C8\uB2E4. \uC801\uC6A9\uD55C \uD15C\uD50C\uB9BF\uC758 \uADDC\uCE59 \uAC80\uC0AC \uD3EC\uD568(\uC77D\uAE30 \uC804\uC6A9, \uC2A4\uD06C\uB9BD\uD2B8 \uC2E4\uD589 \uC548 \uD568).",
|
|
7335
7941
|
{
|
|
7336
7942
|
targetDir: z.string().optional().describe("\uAC80\uC99D \uB300\uC0C1 \uB514\uB809\uD1A0\uB9AC (\uAE30\uBCF8: \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC)")
|
|
7337
7943
|
},
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
{
|
|
14
14
|
"path": "components/agentic/tool-call-card.tsx",
|
|
15
15
|
"type": "registry:ui",
|
|
16
|
-
"content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/registry/axis/lib/utils\";\nimport { STATUS_LABELS } from \"@/registry/axis/lib/agentic-a11y-labels\";\n\nexport type ToolStatus = \"pending\" | \"running\" | \"success\" | \"error\";\n\nexport interface ToolCallCardProps {\n /** 도구 이름 */\n toolName: string;\n /** 도구 설명 */\n description?: string;\n /** 실행 상태 */\n status: ToolStatus;\n /** 입력 파라미터 */\n input?: Record<string, unknown>;\n /** 출력 결과 */\n output?: unknown;\n /** 에러 메시지 */\n error?: string;\n /** 실행 시간 (ms) */\n duration?: number;\n /** 펼침 상태 */\n defaultExpanded?: boolean;\n /** 접근성 레이블 */\n \"aria-label\"?: string;\n /** 추가 클래스 */\n className?: string;\n}\n\nconst statusConfig: Record<ToolStatus, { color: string; icon: React.ReactNode }> = {\n pending: {\n color: \"text-[var(--axis-text-tertiary)]\",\n icon: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" strokeWidth={2} />\n </svg>\n ),\n },\n running: {\n color: \"text-[var(--axis-status-running)]\",\n icon: (\n <svg className=\"w-4 h-4 animate-spin motion-reduce:![animation-duration:3s]\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\n <path fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\" />\n </svg>\n ),\n },\n success: {\n color: \"text-[var(--axis-text-success)]\",\n icon: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M5 13l4 4L19 7\" />\n </svg>\n ),\n },\n error: {\n color: \"text-[var(--axis-text-error)]\",\n icon: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n ),\n },\n};\n\n/**\n * AI 도구 호출 정보를 표시하는 카드 컴포넌트\n */\nexport function ToolCallCard({\n toolName,\n description,\n status,\n input,\n output,\n error,\n duration,\n defaultExpanded = false,\n \"aria-label\": ariaLabel,\n className,\n}: ToolCallCardProps) {\n const [isExpanded, setIsExpanded] = React.useState(defaultExpanded);\n const config = statusConfig[status];\n const contentId = React.useId();\n const statusLabel = STATUS_LABELS[status] || status;\n\n return (\n <div\n className={cn(\n \"rounded-lg border overflow-hidden\",\n \"bg-[var(--axis-surface-default)] border-[var(--axis-border-default)]\",\n className\n )}\n >\n {/* 헤더 */}\n <button\n onClick={() => setIsExpanded(!isExpanded)}\n aria-expanded={isExpanded}\n aria-controls={contentId}\n aria-label={ariaLabel || `${toolName} - ${statusLabel}`}\n className=\"w-full flex items-center justify-between p-3 hover:bg-[var(--axis-surface-secondary)] transition-colors\"\n >\n <div className=\"flex items-center gap-2\">\n <span className={config.color} aria-hidden=\"true\">{config.icon}</span>\n <code className=\"text-sm font-mono text-[var(--axis-text-primary)]\">\n {toolName}\n </code>\n {description && (\n <span className=\"text-xs text-[var(--axis-text-secondary)]\">\n
|
|
16
|
+
"content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/registry/axis/lib/utils\";\nimport { STATUS_LABELS } from \"@/registry/axis/lib/agentic-a11y-labels\";\n\nexport type ToolStatus = \"pending\" | \"running\" | \"success\" | \"error\";\n\nexport interface ToolCallCardProps {\n /** 도구 이름 */\n toolName: string;\n /** 도구 설명 */\n description?: string;\n /** 실행 상태 */\n status: ToolStatus;\n /** 입력 파라미터 */\n input?: Record<string, unknown>;\n /** 출력 결과 */\n output?: unknown;\n /** 에러 메시지 */\n error?: string;\n /** 실행 시간 (ms) */\n duration?: number;\n /** 펼침 상태 */\n defaultExpanded?: boolean;\n /** 접근성 레이블 */\n \"aria-label\"?: string;\n /** 추가 클래스 */\n className?: string;\n}\n\nconst statusConfig: Record<ToolStatus, { color: string; icon: React.ReactNode }> = {\n pending: {\n color: \"text-[var(--axis-text-tertiary)]\",\n icon: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" strokeWidth={2} />\n </svg>\n ),\n },\n running: {\n color: \"text-[var(--axis-status-running)]\",\n icon: (\n <svg className=\"w-4 h-4 animate-spin motion-reduce:![animation-duration:3s]\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\n <path fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\" />\n </svg>\n ),\n },\n success: {\n color: \"text-[var(--axis-text-success)]\",\n icon: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M5 13l4 4L19 7\" />\n </svg>\n ),\n },\n error: {\n color: \"text-[var(--axis-text-error)]\",\n icon: (\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n ),\n },\n};\n\n/**\n * AI 도구 호출 정보를 표시하는 카드 컴포넌트\n */\nexport function ToolCallCard({\n toolName,\n description,\n status,\n input,\n output,\n error,\n duration,\n defaultExpanded = false,\n \"aria-label\": ariaLabel,\n className,\n}: ToolCallCardProps) {\n const [isExpanded, setIsExpanded] = React.useState(defaultExpanded);\n const config = statusConfig[status];\n const contentId = React.useId();\n const statusLabel = STATUS_LABELS[status] || status;\n\n return (\n <div\n className={cn(\n \"rounded-lg border overflow-hidden\",\n \"bg-[var(--axis-surface-default)] border-[var(--axis-border-default)]\",\n className\n )}\n >\n {/* 헤더 */}\n <button\n onClick={() => setIsExpanded(!isExpanded)}\n aria-expanded={isExpanded}\n aria-controls={contentId}\n aria-label={ariaLabel || `${toolName} - ${statusLabel}`}\n className=\"w-full flex items-center justify-between p-3 hover:bg-[var(--axis-surface-secondary)] transition-colors\"\n >\n <div className=\"flex items-center gap-2\">\n <span className={config.color} aria-hidden=\"true\">{config.icon}</span>\n <code className=\"text-sm font-mono text-[var(--axis-text-primary)]\">\n {toolName}\n </code>\n {description && (\n <span className=\"text-xs text-[var(--axis-text-secondary)]\">\n · {description}\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n {duration !== undefined && (\n <span className=\"text-xs text-[var(--axis-text-secondary)]\">\n {duration}ms\n </span>\n )}\n <svg\n className={cn(\n \"w-4 h-4 text-[var(--axis-text-secondary)] transition-transform\",\n isExpanded && \"rotate-180\"\n )}\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 9l-7 7-7-7\" />\n </svg>\n </div>\n </button>\n\n {/* 내용 */}\n {isExpanded && (\n <div id={contentId} className=\"border-t border-[var(--axis-border-default)] p-3 space-y-3\">\n {input && (\n <div>\n <h5 className=\"text-xs font-medium text-[var(--axis-text-secondary)] mb-1\">\n 입력\n </h5>\n <pre className=\"text-xs p-2 rounded bg-[var(--axis-surface-secondary)] overflow-x-auto\">\n <code className=\"text-[var(--axis-text-primary)]\">\n {JSON.stringify(input, null, 2)}\n </code>\n </pre>\n </div>\n )}\n {output !== undefined && (\n <div>\n <h5 className=\"text-xs font-medium text-[var(--axis-text-secondary)] mb-1\">\n 출력\n </h5>\n <pre className=\"text-xs p-2 rounded bg-[var(--axis-surface-secondary)] overflow-x-auto\">\n <code className=\"text-[var(--axis-text-primary)]\">\n {typeof output === \"string\" ? output : JSON.stringify(output, null, 2)}\n </code>\n </pre>\n </div>\n )}\n {error && (\n <div>\n <h5 className=\"text-xs font-medium text-[var(--axis-text-error)] mb-1\">\n 에러\n </h5>\n <pre className=\"text-xs p-2 rounded bg-[var(--axis-surface-error)] overflow-x-auto\">\n <code className=\"text-[var(--axis-text-error)]\">{error}</code>\n </pre>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n"
|
|
17
17
|
}
|
|
18
18
|
]
|
|
19
19
|
}
|
package/dist/registry/index.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "axis",
|
|
3
3
|
"homepage": "https://axis-design-system.pages.dev",
|
|
4
|
-
"version": "1.8.
|
|
4
|
+
"version": "1.8.4",
|
|
5
5
|
"items": [
|
|
6
6
|
{
|
|
7
7
|
"name": "utils",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"type": "registry:ui",
|
|
65
65
|
"title": "Select",
|
|
66
66
|
"description": "AXIS 셀렉트 컴포넌트 - 드롭다운 선택",
|
|
67
|
-
"sha256": "
|
|
67
|
+
"sha256": "cf6a8a5ef905a09580a7420b0ee7d77751599ef9455dfba3382ca79414d02cef"
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"name": "tabs",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"type": "registry:ui",
|
|
79
79
|
"title": "Toast",
|
|
80
80
|
"description": "AXIS 토스트 컴포넌트 - 알림 메시지",
|
|
81
|
-
"sha256": "
|
|
81
|
+
"sha256": "c21626ca0ef04ccb6b88a3a4728bec32867e7bc4430f728f630b7e16fcc6c217"
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
84
|
"name": "agentic-a11y-labels",
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
"type": "registry:ui",
|
|
156
156
|
"title": "Tool Call Card",
|
|
157
157
|
"description": "도구 호출 결과 표시 카드 컴포넌트",
|
|
158
|
-
"sha256": "
|
|
158
|
+
"sha256": "4411f955e7f2ee5199efb171ca218be0373d4ddac46cd7dad8bca3b4a3c19226"
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
161
|
"name": "agentic-message-bubble",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://ui.shadcn.com/schema/registry.json",
|
|
3
3
|
"name": "axis",
|
|
4
4
|
"homepage": "https://axis-design-system.pages.dev",
|
|
5
|
-
"version": "1.8.
|
|
5
|
+
"version": "1.8.4",
|
|
6
6
|
"items": [
|
|
7
7
|
{
|
|
8
8
|
"name": "utils",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"type": "registry:ui",
|
|
66
66
|
"title": "Select",
|
|
67
67
|
"description": "AXIS 셀렉트 컴포넌트 - 드롭다운 선택",
|
|
68
|
-
"sha256": "
|
|
68
|
+
"sha256": "cf6a8a5ef905a09580a7420b0ee7d77751599ef9455dfba3382ca79414d02cef"
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
"name": "tabs",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"type": "registry:ui",
|
|
80
80
|
"title": "Toast",
|
|
81
81
|
"description": "AXIS 토스트 컴포넌트 - 알림 메시지",
|
|
82
|
-
"sha256": "
|
|
82
|
+
"sha256": "c21626ca0ef04ccb6b88a3a4728bec32867e7bc4430f728f630b7e16fcc6c217"
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
"name": "agentic-a11y-labels",
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
"type": "registry:ui",
|
|
157
157
|
"title": "Tool Call Card",
|
|
158
158
|
"description": "도구 호출 결과 표시 카드 컴포넌트",
|
|
159
|
-
"sha256": "
|
|
159
|
+
"sha256": "4411f955e7f2ee5199efb171ca218be0373d4ddac46cd7dad8bca3b4a3c19226"
|
|
160
160
|
},
|
|
161
161
|
{
|
|
162
162
|
"name": "agentic-message-bubble",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
{
|
|
15
15
|
"path": "components/ui/select.tsx",
|
|
16
16
|
"type": "registry:ui",
|
|
17
|
-
"content": "import * as React from \"react\";\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { cn } from \"@/registry/axis/lib/utils\";\n\n/** 셀렉트의 루트 컴포넌트. 열림/닫힘 및 선택 상태를 관리한다. */\nconst Select = SelectPrimitive.Root;\n\n/** 셀렉트 옵션들을 논리적으로 그룹화하는 컴포넌트 */\nconst SelectGroup = SelectPrimitive.Group;\n\n/** 셀렉트 트리거 내에서 현재 선택된 값을 표시하는 컴포넌트 */\nconst SelectValue = SelectPrimitive.Value;\n\n/** 셀렉트 드롭다운을 여는 트리거 버튼 컴포넌트. 선택된 값과 하향 화살표 아이콘을 표시한다. */\nconst SelectTrigger = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n <SelectPrimitive.Trigger\n ref={ref}\n className={cn(\n \"flex h-[var(--axis-field-h,2.5rem)] w-full items-center justify-between rounded-[var(--axis-radius-field,0.375rem)] border px-3 py-2 text-sm\",\n \"bg-[var(--axis-input-bg-default)] border-[var(--axis-input-border-default)]\",\n // placeholder 글자는 SelectValue 의 span 이라 ::placeholder 가 닿지 않는다. 값이 없을 때 Radix 가 붙이는 data-placeholder 로 칠한다(WI-0082)\n // Trigger 가 아니라 그 안의 span 에만 준다. Trigger 에 주면 화살표(currentColor, 50%)도 따라가 대비가 3:1 아래로 떨어진다\n \"[&[data-placeholder]>span]:text-[var(--axis-input-text-placeholder)]\",\n \"hover:border-[var(--axis-input-border-hover)]\",\n \"focus:outline-none focus:ring-2 focus:ring-[var(--axis-input-border-focus)] focus:ring-offset-2\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"[&>span]:line-clamp-1\",\n className\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon asChild>\n <svg\n className=\"h-4 w-4 opacity-50\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n));\nSelectTrigger.displayName = \"SelectTrigger\";\n\n/** 셀렉트 드롭다운의 본문 콘텐츠 컴포넌트. 포털을 통해 렌더링되며 진입/퇴장 애니메이션을 포함한다. */\nconst SelectContent = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = \"popper\", ...props }, ref) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n ref={ref}\n className={cn(\n \"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border shadow-
|
|
17
|
+
"content": "import * as React from \"react\";\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { cn } from \"@/registry/axis/lib/utils\";\n\n/** 셀렉트의 루트 컴포넌트. 열림/닫힘 및 선택 상태를 관리한다. */\nconst Select = SelectPrimitive.Root;\n\n/** 셀렉트 옵션들을 논리적으로 그룹화하는 컴포넌트 */\nconst SelectGroup = SelectPrimitive.Group;\n\n/** 셀렉트 트리거 내에서 현재 선택된 값을 표시하는 컴포넌트 */\nconst SelectValue = SelectPrimitive.Value;\n\n/** 셀렉트 드롭다운을 여는 트리거 버튼 컴포넌트. 선택된 값과 하향 화살표 아이콘을 표시한다. */\nconst SelectTrigger = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n <SelectPrimitive.Trigger\n ref={ref}\n className={cn(\n \"flex h-[var(--axis-field-h,2.5rem)] w-full items-center justify-between rounded-[var(--axis-radius-field,0.375rem)] border px-3 py-2 text-sm\",\n \"bg-[var(--axis-input-bg-default)] border-[var(--axis-input-border-default)]\",\n // placeholder 글자는 SelectValue 의 span 이라 ::placeholder 가 닿지 않는다. 값이 없을 때 Radix 가 붙이는 data-placeholder 로 칠한다(WI-0082)\n // Trigger 가 아니라 그 안의 span 에만 준다. Trigger 에 주면 화살표(currentColor, 50%)도 따라가 대비가 3:1 아래로 떨어진다\n \"[&[data-placeholder]>span]:text-[var(--axis-input-text-placeholder)]\",\n \"hover:border-[var(--axis-input-border-hover)]\",\n \"focus:outline-none focus:ring-2 focus:ring-[var(--axis-input-border-focus)] focus:ring-offset-2\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"[&>span]:line-clamp-1\",\n className\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon asChild>\n <svg\n className=\"h-4 w-4 opacity-50\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n));\nSelectTrigger.displayName = \"SelectTrigger\";\n\n/** 셀렉트 드롭다운의 본문 콘텐츠 컴포넌트. 포털을 통해 렌더링되며 진입/퇴장 애니메이션을 포함한다. */\nconst SelectContent = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = \"popper\", ...props }, ref) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n ref={ref}\n className={cn(\n \"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border shadow-[shadow:var(--axis-shadow-lg)]\",\n \"bg-[var(--axis-surface-default)] border-[var(--axis-border-default)]\",\n \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 motion-reduce:!animate-none\",\n position === \"popper\" &&\n \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n className\n )}\n position={position}\n {...props}\n >\n <SelectPrimitive.Viewport\n className={cn(\n \"p-1\",\n position === \"popper\" &&\n \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]\"\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n));\nSelectContent.displayName = \"SelectContent\";\n\n/** 셀렉트 옵션 그룹의 레이블 컴포넌트. 비선택 텍스트로 그룹 제목을 표시한다. */\nconst SelectLabel = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.Label\n ref={ref}\n className={cn(\n \"py-1.5 pl-8 pr-2 text-sm font-semibold text-[var(--axis-text-secondary)]\",\n className\n )}\n {...props}\n />\n));\nSelectLabel.displayName = \"SelectLabel\";\n\n/** 셀렉트 드롭다운의 개별 선택 항목 컴포넌트. 선택 시 좌측에 체크 아이콘이 표시된다. */\nconst SelectItem = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>\n>(({ className, children, ...props }, ref) => (\n <SelectPrimitive.Item\n ref={ref}\n className={cn(\n \"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none\",\n \"text-[var(--axis-text-primary)]\",\n \"focus:bg-[var(--axis-surface-secondary)] focus:text-[var(--axis-text-primary)]\",\n \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className\n )}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator>\n <svg\n className=\"h-4 w-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n));\nSelectItem.displayName = \"SelectItem\";\n\n/** 셀렉트 옵션 사이의 구분선 컴포넌트 */\nconst SelectSeparator = React.forwardRef<\n React.ElementRef<typeof SelectPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.Separator\n ref={ref}\n className={cn(\"-mx-1 my-1 h-px bg-[var(--axis-border-default)]\", className)}\n {...props}\n />\n));\nSelectSeparator.displayName = \"SelectSeparator\";\n\nexport {\n Select,\n SelectGroup,\n SelectValue,\n SelectTrigger,\n SelectContent,\n SelectLabel,\n SelectItem,\n SelectSeparator,\n};\n"
|
|
18
18
|
}
|
|
19
19
|
]
|
|
20
20
|
}
|
package/dist/registry/toast.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
{
|
|
16
16
|
"path": "components/ui/toast.tsx",
|
|
17
17
|
"type": "registry:ui",
|
|
18
|
-
"content": "import * as React from \"react\";\nimport * as ToastPrimitives from \"@radix-ui/react-toast\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/registry/axis/lib/utils\";\n\n/** 토스트 알림 시스템의 프로바이더 컴포넌트. 앱 루트에 배치하여 토스트 기능을 활성화한다. */\nconst ToastProvider = ToastPrimitives.Provider;\n\n/** 토스트 알림이 표시되는 뷰포트 영역. 화면 하단 우측에 고정 위치로 렌더링된다. */\nconst ToastViewport = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Viewport>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>\n>(({ className, ...props }, ref) => (\n <ToastPrimitives.Viewport\n ref={ref}\n className={cn(\n \"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]\",\n className\n )}\n {...props}\n />\n));\nToastViewport.displayName = ToastPrimitives.Viewport.displayName;\n\nconst toastVariants = cva(\n \"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full motion-reduce:!animate-none\",\n {\n variants: {\n variant: {\n default:\n \"border-[var(--axis-border-default)] bg-[var(--axis-surface-default)] text-[var(--axis-text-primary)]\",\n success:\n \"border-[var(--axis-border-success)] bg-[var(--axis-surface-success)] text-[var(--axis-text-success)]\",\n error:\n \"border-[var(--axis-border-error)] bg-[var(--axis-surface-error)] text-[var(--axis-text-error)]\",\n warning:\n \"border-transparent bg-[var(--axis-surface-warning)] text-[var(--axis-text-warning)]\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n);\n\n/** 토스트 알림의 루트 컴포넌트. default, success, error, warning 변형을 지원하며 스와이프로 닫을 수 있다. */\nconst Toast = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Root>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &\n VariantProps<typeof toastVariants>\n>(({ className, variant, ...props }, ref) => (\n <ToastPrimitives.Root\n ref={ref}\n className={cn(toastVariants({ variant }), className)}\n {...props}\n />\n));\nToast.displayName = ToastPrimitives.Root.displayName;\n\n/** 토스트 내에 표시되는 액션 버튼 컴포넌트. 사용자 상호작용을 위한 CTA를 제공한다. */\nconst ToastAction = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Action>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>\n>(({ className, ...props }, ref) => (\n <ToastPrimitives.Action\n ref={ref}\n className={cn(\n \"inline-flex h-8 shrink-0 items-center justify-center rounded-md border px-3 text-sm font-medium transition-colors\",\n \"border-[var(--axis-border-default)] bg-transparent\",\n \"hover:bg-[var(--axis-surface-secondary)]\",\n \"focus:outline-none focus:ring-2 focus:ring-[var(--axis-border-focus)]\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n className\n )}\n {...props}\n />\n));\nToastAction.displayName = ToastPrimitives.Action.displayName;\n\n/** 토스트를 닫는 닫기 버튼 컴포넌트. 호버 시 표시되며 X 아이콘을 포함한다. */\nconst ToastClose = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Close>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>\n>(({ className, ...props }, ref) => (\n <ToastPrimitives.Close\n ref={ref}\n className={cn(\n \"absolute right-2 top-2 rounded-md p-1 text-[var(--axis-text-secondary)] opacity-0 transition-opacity\",\n \"hover:text-[var(--axis-text-primary)]\",\n \"focus:opacity-100 focus:outline-none focus:ring-2\",\n \"group-hover:opacity-100\",\n className\n )}\n toast-close=\"\"\n {...props}\n >\n <svg className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </ToastPrimitives.Close>\n));\nToastClose.displayName = ToastPrimitives.Close.displayName;\n\n/** 토스트의 제목을 표시하는 컴포넌트 */\nconst ToastTitle = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Title>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>\n>(({ className, ...props }, ref) => (\n <ToastPrimitives.Title\n ref={ref}\n className={cn(\"text-sm font-semibold\", className)}\n {...props}\n />\n));\nToastTitle.displayName = ToastPrimitives.Title.displayName;\n\n/** 토스트의 상세 설명을 표시하는 컴포넌트 */\nconst ToastDescription = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Description>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>\n>(({ className, ...props }, ref) => (\n <ToastPrimitives.Description\n ref={ref}\n className={cn(\"text-sm\", className)}\n {...props}\n />\n));\nToastDescription.displayName = ToastPrimitives.Description.displayName;\n\nexport {\n ToastProvider,\n ToastViewport,\n Toast,\n ToastTitle,\n ToastDescription,\n ToastClose,\n ToastAction,\n};\n"
|
|
18
|
+
"content": "import * as React from \"react\";\nimport * as ToastPrimitives from \"@radix-ui/react-toast\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/registry/axis/lib/utils\";\n\n/** 토스트 알림 시스템의 프로바이더 컴포넌트. 앱 루트에 배치하여 토스트 기능을 활성화한다. */\nconst ToastProvider = ToastPrimitives.Provider;\n\n/** 토스트 알림이 표시되는 뷰포트 영역. 화면 하단 우측에 고정 위치로 렌더링된다. */\nconst ToastViewport = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Viewport>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>\n>(({ className, ...props }, ref) => (\n <ToastPrimitives.Viewport\n ref={ref}\n className={cn(\n \"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]\",\n className\n )}\n {...props}\n />\n));\nToastViewport.displayName = ToastPrimitives.Viewport.displayName;\n\nconst toastVariants = cva(\n \"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-[shadow:var(--axis-shadow-lg)] transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full motion-reduce:!animate-none\",\n {\n variants: {\n variant: {\n default:\n \"border-[var(--axis-border-default)] bg-[var(--axis-surface-default)] text-[var(--axis-text-primary)]\",\n success:\n \"border-[var(--axis-border-success)] bg-[var(--axis-surface-success)] text-[var(--axis-text-success)]\",\n error:\n \"border-[var(--axis-border-error)] bg-[var(--axis-surface-error)] text-[var(--axis-text-error)]\",\n warning:\n \"border-transparent bg-[var(--axis-surface-warning)] text-[var(--axis-text-warning)]\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n);\n\n/** 토스트 알림의 루트 컴포넌트. default, success, error, warning 변형을 지원하며 스와이프로 닫을 수 있다. */\nconst Toast = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Root>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &\n VariantProps<typeof toastVariants>\n>(({ className, variant, ...props }, ref) => (\n <ToastPrimitives.Root\n ref={ref}\n className={cn(toastVariants({ variant }), className)}\n {...props}\n />\n));\nToast.displayName = ToastPrimitives.Root.displayName;\n\n/** 토스트 내에 표시되는 액션 버튼 컴포넌트. 사용자 상호작용을 위한 CTA를 제공한다. */\nconst ToastAction = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Action>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>\n>(({ className, ...props }, ref) => (\n <ToastPrimitives.Action\n ref={ref}\n className={cn(\n \"inline-flex h-8 shrink-0 items-center justify-center rounded-md border px-3 text-sm font-medium transition-colors\",\n \"border-[var(--axis-border-default)] bg-transparent\",\n \"hover:bg-[var(--axis-surface-secondary)]\",\n \"focus:outline-none focus:ring-2 focus:ring-[var(--axis-border-focus)]\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n className\n )}\n {...props}\n />\n));\nToastAction.displayName = ToastPrimitives.Action.displayName;\n\n/** 토스트를 닫는 닫기 버튼 컴포넌트. 호버 시 표시되며 X 아이콘을 포함한다. */\nconst ToastClose = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Close>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>\n>(({ className, ...props }, ref) => (\n <ToastPrimitives.Close\n ref={ref}\n className={cn(\n \"absolute right-2 top-2 rounded-md p-1 text-[var(--axis-text-secondary)] opacity-0 transition-opacity\",\n \"hover:text-[var(--axis-text-primary)]\",\n \"focus:opacity-100 focus:outline-none focus:ring-2\",\n \"group-hover:opacity-100\",\n className\n )}\n toast-close=\"\"\n {...props}\n >\n <svg className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </ToastPrimitives.Close>\n));\nToastClose.displayName = ToastPrimitives.Close.displayName;\n\n/** 토스트의 제목을 표시하는 컴포넌트 */\nconst ToastTitle = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Title>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>\n>(({ className, ...props }, ref) => (\n <ToastPrimitives.Title\n ref={ref}\n className={cn(\"text-sm font-semibold\", className)}\n {...props}\n />\n));\nToastTitle.displayName = ToastPrimitives.Title.displayName;\n\n/** 토스트의 상세 설명을 표시하는 컴포넌트 */\nconst ToastDescription = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Description>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>\n>(({ className, ...props }, ref) => (\n <ToastPrimitives.Description\n ref={ref}\n className={cn(\"text-sm\", className)}\n {...props}\n />\n));\nToastDescription.displayName = ToastPrimitives.Description.displayName;\n\nexport {\n ToastProvider,\n ToastViewport,\n Toast,\n ToastTitle,\n ToastDescription,\n ToastClose,\n ToastAction,\n};\n"
|
|
19
19
|
}
|
|
20
20
|
]
|
|
21
21
|
}
|