sellmate-design-system-react 3.0.0 → 3.1.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.
Files changed (75) hide show
  1. package/AGENTS.md +661 -0
  2. package/README.md +85 -0
  3. package/dist/components/SActionModal/README.md +41 -0
  4. package/dist/components/SBadge/README.md +25 -0
  5. package/dist/components/SBarcodeInput/README.md +64 -0
  6. package/dist/components/SButton/README.md +49 -0
  7. package/dist/components/SCalendar/README.md +42 -0
  8. package/dist/components/SCallout/README.md +29 -0
  9. package/dist/components/SCard/README.md +12 -0
  10. package/dist/components/SCheckbox/README.md +44 -0
  11. package/dist/components/SChip/README.md +57 -0
  12. package/dist/components/SChipInput/README.md +77 -0
  13. package/dist/components/SCircleProgress/README.md +35 -0
  14. package/dist/components/SConfirmModal/README.md +61 -0
  15. package/dist/components/SDatePicker/README.md +62 -0
  16. package/dist/components/SDateRangePicker/README.md +64 -0
  17. package/dist/components/SDivider/README.md +25 -0
  18. package/dist/components/SDraggableItem/README.md +40 -0
  19. package/dist/components/SDropdownButton/README.md +50 -0
  20. package/dist/components/SExpansionItem/README.md +40 -0
  21. package/dist/components/SField/README.md +91 -0
  22. package/dist/components/SFilePicker/README.md +72 -0
  23. package/dist/components/SForm/README.md +31 -0
  24. package/dist/components/SGhostButton/README.md +76 -0
  25. package/dist/components/SGnb/README.md +56 -0
  26. package/dist/components/SGuide/README.md +34 -0
  27. package/dist/components/SIcon/README.md +91 -0
  28. package/dist/components/SInput/README.md +66 -0
  29. package/dist/components/SKeyValueTable/README.md +59 -0
  30. package/dist/components/SLayout/README.md +42 -0
  31. package/dist/components/SLinearProgress/README.md +17 -0
  32. package/dist/components/SList/README.md +14 -0
  33. package/dist/components/SListItem/README.md +19 -0
  34. package/dist/components/SLoadingContainer/README.md +29 -0
  35. package/dist/components/SLoadingModal/README.md +52 -0
  36. package/dist/components/SModal/README.md +192 -0
  37. package/dist/components/SModalContainer/README.md +49 -0
  38. package/dist/components/SNumberInput/README.md +74 -0
  39. package/dist/components/SPage/README.md +25 -0
  40. package/dist/components/SPagination/README.md +40 -0
  41. package/dist/components/SPopover/README.md +54 -0
  42. package/dist/components/SPopup/README.md +38 -0
  43. package/dist/components/SPortal/README.md +51 -0
  44. package/dist/components/SRadio/README.md +57 -0
  45. package/dist/components/SRadioButton/README.md +24 -0
  46. package/dist/components/SScrollArea/README.md +16 -0
  47. package/dist/components/SSectionHeaderCard/README.md +51 -0
  48. package/dist/components/SSelect/README.md +79 -0
  49. package/dist/components/SStepper/README.md +30 -0
  50. package/dist/components/SSwitch/README.md +24 -0
  51. package/dist/components/STable/README.md +82 -0
  52. package/dist/components/STableBar/README.md +28 -0
  53. package/dist/components/STabs/README.md +37 -0
  54. package/dist/components/STag/README.md +44 -0
  55. package/dist/components/STextLink/README.md +48 -0
  56. package/dist/components/STextarea/README.md +60 -0
  57. package/dist/components/STimePicker/README.md +65 -0
  58. package/dist/components/STimeRangePicker/README.md +63 -0
  59. package/dist/components/SToast/README.md +70 -0
  60. package/dist/components/SToggle/README.md +22 -0
  61. package/dist/components/STooltip/README.md +57 -0
  62. package/dist/llms.txt +3496 -0
  63. package/dist/styles.css +209 -0
  64. package/dist/theme.css +8 -0
  65. package/eslint/index.mjs +66 -0
  66. package/eslint/lib/class-names.mjs +119 -0
  67. package/eslint/lib/table-column.mjs +53 -0
  68. package/eslint/rules/no-arbitrary-class.mjs +91 -0
  69. package/eslint/rules/no-off-scale-spacing.mjs +67 -0
  70. package/eslint/rules/no-raw-html-control.mjs +117 -0
  71. package/eslint/rules/prefer-typo-preset.mjs +73 -0
  72. package/eslint/rules/require-locale-number.mjs +78 -0
  73. package/eslint/rules/table-numeric-align.mjs +93 -0
  74. package/eslint/scale.gen.mjs +16 -0
  75. package/package.json +9 -4
@@ -0,0 +1,91 @@
1
+ /**
2
+ * **디자인 토큰이 있는 속성**에 임의 값을 쓰는 것을 막는다 — `text-[14px]`, `bg-[#eee]` 등.
3
+ *
4
+ * 반대로, 디자인 시스템이 토큰화하지 않는 **앱 고유 레이아웃 치수**(`w-[280px]`,
5
+ * `max-w-[1200px]`, `grid-cols-[200px_1fr]`)는 정당한 사용이므로 막지 않는다.
6
+ * 토큰을 var() 로 참조하는 형태(`h-[var(--sys-size-...)]`)도 허용한다.
7
+ *
8
+ * AGENTS.md §1-2.
9
+ */
10
+ import { collectClassTokens, stripVariants, stripModifiers } from "../lib/class-names.mjs";
11
+
12
+ /**
13
+ * 토큰이 존재하는 속성 접두어 — 여기에 임의 값을 쓰면 디자인 시스템을 우회하는 것이다.
14
+ * 색·타이포·간격·모서리 4계열.
15
+ */
16
+ const TOKEN_BACKED = [
17
+ // 색 (text- 는 색·폰트크기 양쪽 모두 토큰이 있어 어느 쪽이든 대상)
18
+ "text", "bg", "border", "outline", "ring", "shadow", "fill", "stroke",
19
+ "divide", "accent", "caret", "decoration", "from", "via", "to",
20
+ // 타이포
21
+ "font", "leading", "tracking",
22
+ // 간격
23
+ "gap", "gap-x", "gap-y", "space-x", "space-y",
24
+ "p", "px", "py", "pt", "pr", "pb", "pl", "ps", "pe",
25
+ "m", "mx", "my", "mt", "mr", "mb", "ml", "ms", "me",
26
+ // 모서리
27
+ "rounded", "rounded-t", "rounded-r", "rounded-b", "rounded-l",
28
+ "rounded-tl", "rounded-tr", "rounded-br", "rounded-bl",
29
+ ];
30
+
31
+ /** 대괄호 안이 토큰 참조인지 — `var(--x)` 또는 `length:var(--x)` 같은 데이터타입 접두어 허용 */
32
+ function isTokenReference(inner) {
33
+ const body = inner.includes(":") ? inner.slice(inner.indexOf(":") + 1) : inner;
34
+ return /^var\(--[\w-]+\)$/.test(body.trim());
35
+ }
36
+
37
+ /** `bg-[#eee]` → `bg` 처럼 임의 값 앞의 유틸리티 접두어를 뽑는다 */
38
+ function prefixOf(util) {
39
+ const idx = util.indexOf("[");
40
+ return idx <= 0 ? "" : util.slice(0, idx - 1); // 대괄호 바로 앞의 `-` 제외
41
+ }
42
+
43
+ export default {
44
+ meta: {
45
+ type: "problem",
46
+ docs: {
47
+ description: "색·타이포·간격·모서리에 임의 값 금지 — 토큰 유틸리티 또는 var(--token) 참조 사용",
48
+ },
49
+ schema: [
50
+ {
51
+ type: "object",
52
+ properties: {
53
+ /** 예외로 허용할 클래스 (완전 일치) */
54
+ allow: { type: "array", items: { type: "string" }, uniqueItems: true },
55
+ /** 검사 대상에서 뺄 유틸리티 접두어 (예: ["shadow"]) */
56
+ ignorePrefixes: { type: "array", items: { type: "string" }, uniqueItems: true },
57
+ },
58
+ additionalProperties: false,
59
+ },
60
+ ],
61
+ messages: {
62
+ arbitrary:
63
+ "`{{token}}` — {{prefix}} 는 디자인 토큰이 있는 속성이라 임의 값을 쓸 수 없습니다. 토큰 유틸리티를 쓰거나 var(--token) 으로 참조하세요. (AGENTS.md §1-2)",
64
+ },
65
+ },
66
+
67
+ create(context) {
68
+ const allow = new Set(context.options[0]?.allow ?? []);
69
+ const ignored = new Set(context.options[0]?.ignorePrefixes ?? []);
70
+ const targets = TOKEN_BACKED.filter((p) => !ignored.has(p));
71
+
72
+ return {
73
+ JSXAttribute(node) {
74
+ for (const { token, node: valueNode } of collectClassTokens(node)) {
75
+ if (allow.has(token)) continue;
76
+
77
+ const util = stripModifiers(stripVariants(token));
78
+ const match = util.match(/\[([^\]]*)\]/);
79
+ if (!match) continue;
80
+ if (isTokenReference(match[1])) continue;
81
+
82
+ // 토큰이 없는 속성(w·max-w·grid-cols·top …)의 임의 값은 앱 고유 치수이므로 허용
83
+ const prefix = prefixOf(util);
84
+ if (!targets.includes(prefix)) continue;
85
+
86
+ context.report({ node: valueNode, messageId: "arbitrary", data: { token, prefix } });
87
+ }
88
+ },
89
+ };
90
+ },
91
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * 간격 유틸리티가 토큰 스케일 밖의 값을 쓰는 것을 막는다 — `gap-13`, `p-15` 등.
3
+ *
4
+ * Tailwind v4 는 `--spacing-*` 테마 키에 있는 값만 유틸리티로 생성하므로,
5
+ * 스케일 밖 숫자는 **에러 없이 조용히 아무 스타일도 적용되지 않는다.**
6
+ * 눈으로는 "간격이 왜 안 먹지" 로만 보이기 때문에 린트로 잡는다.
7
+ *
8
+ * AGENTS.md §2-2.
9
+ */
10
+ import { collectClassTokens, stripVariants, stripModifiers } from "../lib/class-names.mjs";
11
+ import { SPACING } from "../scale.gen.mjs";
12
+
13
+ /** 값이 순수하게 간격 스케일에서 오는 유틸리티 접두어만 대상으로 한다 */
14
+ const SPACING_PREFIXES = [
15
+ "gap-x", "gap-y", "gap",
16
+ "space-x", "space-y",
17
+ "px", "py", "pt", "pr", "pb", "pl", "ps", "pe", "p",
18
+ "mx", "my", "mt", "mr", "mb", "ml", "ms", "me", "m",
19
+ ];
20
+
21
+ const ALLOWED = new Set([0, ...SPACING]);
22
+
23
+ export default {
24
+ meta: {
25
+ type: "problem",
26
+ docs: {
27
+ description: "간격 유틸리티는 토큰 스케일 값만 사용",
28
+ },
29
+ schema: [],
30
+ messages: {
31
+ offScale:
32
+ "`{{token}}` 의 {{value}} 는 간격 스케일에 없어 스타일이 적용되지 않습니다. 가까운 값: {{nearest}}. (AGENTS.md §2-2)",
33
+ },
34
+ },
35
+
36
+ create(context) {
37
+ return {
38
+ JSXAttribute(node) {
39
+ for (const { token, node: valueNode } of collectClassTokens(node)) {
40
+ const util = stripModifiers(stripVariants(token));
41
+
42
+ const prefix = SPACING_PREFIXES.find((p) => util.startsWith(`${p}-`));
43
+ if (!prefix) continue;
44
+
45
+ const raw = util.slice(prefix.length + 1);
46
+ // 숫자가 아닌 값(auto, px, full, [var(--x)] 등)은 이 규칙 대상이 아니다
47
+ if (!/^\d+$/.test(raw)) continue;
48
+
49
+ const value = Number(raw);
50
+ if (ALLOWED.has(value)) continue;
51
+
52
+ const nearest = [...ALLOWED]
53
+ .sort((a, b) => Math.abs(a - value) - Math.abs(b - value))
54
+ .slice(0, 2)
55
+ .sort((a, b) => a - b)
56
+ .join(" / ");
57
+
58
+ context.report({
59
+ node: valueNode,
60
+ messageId: "offScale",
61
+ data: { token, value: String(value), nearest },
62
+ });
63
+ }
64
+ },
65
+ };
66
+ },
67
+ };
@@ -0,0 +1,117 @@
1
+ /**
2
+ * 디자인 시스템에 대응 컴포넌트가 있는 생 HTML 요소 사용을 막는다.
3
+ * AGENTS.md §1-1 과 같은 목록을 강제한다.
4
+ */
5
+
6
+ /**
7
+ * 기본 대상 — 대응 컴포넌트가 명확해 오탐 여지가 거의 없는 요소.
8
+ */
9
+ const REPLACEMENTS = {
10
+ button: "SButton / SGhostButton / SDropdownButton / STextLink",
11
+ input: "SInput / SNumberInput / SCheckbox / SRadio / SFilePicker",
12
+ select: "SSelect",
13
+ textarea: "STextarea",
14
+ table: "STable / SKeyValueTable",
15
+ form: "SForm",
16
+ dialog: "SModal.create(...) / SModal.confirm(...) / SPopup",
17
+ hr: "SDivider",
18
+ details: "SExpansionItem",
19
+ summary: "SExpansionItem",
20
+ progress: "SLinearProgress / SCircleProgress",
21
+ };
22
+
23
+ /**
24
+ * strict 옵션에서만 검사하는 요소 — 정당한 사용처가 함께 있어 기본에서는 뺀다.
25
+ * - svg : 로고·일러스트·차트
26
+ * - ul/li : 본문 안의 시맨틱 목록, 브레드크럼
27
+ * - label : DS 밖 컨트롤의 접근성 레이블
28
+ */
29
+ const STRICT_REPLACEMENTS = {
30
+ ul: "SList + SListItem",
31
+ ol: "SList + SListItem",
32
+ li: "SListItem",
33
+ svg: "SIcon",
34
+ label: "SField 의 label prop",
35
+ };
36
+
37
+ /** 시각 요소가 없어 대체 컴포넌트가 없는 input 타입 */
38
+ const EXEMPT_INPUT_TYPES = new Set(["hidden"]);
39
+
40
+ /** JSX 속성에서 리터럴 문자열 값을 읽는다 (동적 값이면 undefined) */
41
+ function literalAttr(node, name) {
42
+ const attr = node.attributes.find(
43
+ (a) => a.type === "JSXAttribute" && a.name?.name === name,
44
+ );
45
+ const v = attr?.value;
46
+ if (v?.type === "Literal" && typeof v.value === "string") return v.value;
47
+ return undefined;
48
+ }
49
+
50
+ /** 전역 함수 → 권장 대체 */
51
+ const GLOBAL_CALLS = {
52
+ alert: "SToast / SModal.confirm(...)",
53
+ confirm: "SModal.confirm(...)",
54
+ };
55
+
56
+ export default {
57
+ meta: {
58
+ type: "problem",
59
+ docs: {
60
+ description: "디자인 시스템 컴포넌트가 있는 생 HTML 컨트롤 사용 금지",
61
+ },
62
+ schema: [
63
+ {
64
+ type: "object",
65
+ properties: {
66
+ /** 예외로 허용할 요소명 (예: 로고용 svg) */
67
+ allow: { type: "array", items: { type: "string" }, uniqueItems: true },
68
+ /** ul·ol·li·svg·label 까지 검사 (정당한 사용처가 있어 기본은 off) */
69
+ strict: { type: "boolean" },
70
+ },
71
+ additionalProperties: false,
72
+ },
73
+ ],
74
+ messages: {
75
+ rawElement: "<{{name}}> 대신 {{replacement}} 를 사용하세요. (AGENTS.md §1-1)",
76
+ rawCall: "{{name}}() 대신 {{replacement}} 를 사용하세요. (AGENTS.md §1-1)",
77
+ },
78
+ },
79
+
80
+ create(context) {
81
+ const options = context.options[0] ?? {};
82
+ const allow = new Set(options.allow ?? []);
83
+ const replacements = options.strict
84
+ ? { ...REPLACEMENTS, ...STRICT_REPLACEMENTS }
85
+ : REPLACEMENTS;
86
+
87
+ return {
88
+ JSXOpeningElement(node) {
89
+ if (node.name.type !== "JSXIdentifier") return;
90
+ const name = node.name.name;
91
+ // 컴포넌트(대문자 시작)는 대상이 아니다
92
+ if (name[0] !== name[0].toLowerCase()) return;
93
+ if (allow.has(name)) return;
94
+
95
+ const replacement = replacements[name];
96
+ if (!replacement) return;
97
+
98
+ // <input type="hidden"> 처럼 시각 요소가 없는 경우는 대체 컴포넌트가 없다
99
+ if (name === "input" && EXEMPT_INPUT_TYPES.has(literalAttr(node, "type"))) return;
100
+
101
+ context.report({ node: node.name, messageId: "rawElement", data: { name, replacement } });
102
+ },
103
+
104
+ CallExpression(node) {
105
+ if (node.callee.type !== "Identifier") return;
106
+ const name = node.callee.name;
107
+ const replacement = GLOBAL_CALLS[name];
108
+ if (!replacement) return;
109
+ // 지역 선언(사용자가 만든 confirm 등)은 제외
110
+ if (context.sourceCode.getScope(node).references.some((r) => r.identifier === node.callee && r.resolved)) {
111
+ return;
112
+ }
113
+ context.report({ node, messageId: "rawCall", data: { name, replacement } });
114
+ },
115
+ };
116
+ },
117
+ };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * 폰트 크기·굵기를 낱개 유틸리티로 조립하는 것을 막고 `typo-*` 프리셋 사용을 유도한다.
3
+ * `text-14 font-bold` 대신 `typo-body-md-bold`.
4
+ *
5
+ * AGENTS.md §1-3 / §2-1.
6
+ */
7
+ import { collectClassTokens, stripVariants, stripModifiers } from "../lib/class-names.mjs";
8
+ import { TEXT_SIZES, TYPO_PRESETS } from "../scale.gen.mjs";
9
+
10
+ const FONT_WEIGHTS = new Set([
11
+ "font-thin", "font-extralight", "font-light", "font-normal", "font-medium",
12
+ "font-semibold", "font-bold", "font-extrabold", "font-black",
13
+ ]);
14
+
15
+ const TEXT_SIZE_SET = new Set(TEXT_SIZES.map(String));
16
+
17
+ /** 자주 쓰는 조합에 대한 추천 프리셋 (없으면 일반 안내) */
18
+ function suggest(size) {
19
+ const byRole = {
20
+ 12: "typo-body-sm-*",
21
+ 14: "typo-body-md-* / typo-heading-sm",
22
+ 16: "typo-body-lg-* / typo-heading-md",
23
+ 18: "typo-heading-lg",
24
+ 11: "typo-body-xs-default",
25
+ };
26
+ return byRole[size] ?? "typo-*";
27
+ }
28
+
29
+ export default {
30
+ meta: {
31
+ type: "suggestion",
32
+ docs: {
33
+ description: "폰트 크기·굵기 낱개 조합 대신 typo-* 프리셋 사용",
34
+ },
35
+ schema: [],
36
+ messages: {
37
+ fontSize:
38
+ "`{{token}}` 대신 타이포그래피 프리셋을 쓰세요 — {{suggestion}}. (AGENTS.md §2-1)",
39
+ fontWeight:
40
+ "`{{token}}` 을 단독으로 쓰지 말고 굵기가 포함된 typo-* 프리셋을 쓰세요. (AGENTS.md §2-1)",
41
+ },
42
+ },
43
+
44
+ create(context) {
45
+ return {
46
+ JSXAttribute(node) {
47
+ const tokens = collectClassTokens(node);
48
+ // 이미 프리셋을 쓰고 있으면 굵기 보조 유틸리티는 문제 삼지 않는다
49
+ const hasPreset = tokens.some(({ token }) =>
50
+ TYPO_PRESETS.includes(stripModifiers(stripVariants(token))),
51
+ );
52
+
53
+ for (const { token, node: valueNode } of tokens) {
54
+ const util = stripModifiers(stripVariants(token));
55
+
56
+ const sizeMatch = util.match(/^text-(\d+)$/);
57
+ if (sizeMatch && TEXT_SIZE_SET.has(sizeMatch[1])) {
58
+ context.report({
59
+ node: valueNode,
60
+ messageId: "fontSize",
61
+ data: { token, suggestion: suggest(Number(sizeMatch[1])) },
62
+ });
63
+ continue;
64
+ }
65
+
66
+ if (!hasPreset && FONT_WEIGHTS.has(util)) {
67
+ context.report({ node: valueNode, messageId: "fontWeight", data: { token } });
68
+ }
69
+ }
70
+ },
71
+ };
72
+ },
73
+ };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * 숫자 컬럼은 예외 없이 `toLocaleString()` 으로 세 자리 콤마를 넣도록 강제한다.
3
+ *
4
+ * 전화번호·사업자번호처럼 하이픈이 들어가는 값은 숫자가 아니라 서식이 정해진
5
+ * 문자열이므로 대상에서 뺀다. 그 밖에 예외가 필요하면 `allow` 로 컬럼을 지정한다.
6
+ *
7
+ * AGENTS.md §1-4 / §3-4.
8
+ */
9
+ import {
10
+ NUMERIC_LABEL,
11
+ IDENTIFIER_ONLY,
12
+ prop,
13
+ asColumn,
14
+ } from "../lib/table-column.mjs";
15
+
16
+ export default {
17
+ meta: {
18
+ type: "problem",
19
+ docs: {
20
+ description: "금액·수량 등 숫자 표시에 toLocaleString 사용",
21
+ },
22
+ schema: [
23
+ {
24
+ type: "object",
25
+ properties: {
26
+ /** 검사에서 뺄 컬럼 name */
27
+ allow: { type: "array", items: { type: "string" }, uniqueItems: true },
28
+ },
29
+ additionalProperties: false,
30
+ },
31
+ ],
32
+ messages: {
33
+ missingFormat:
34
+ "숫자 컬럼 `{{label}}` 에 format 이 없습니다. `format: (v) => `${Number(v).toLocaleString()}{{unit}}`` 처럼 세 자리 구분을 넣으세요. (AGENTS.md §1-4)",
35
+ missingLocale:
36
+ "숫자 컬럼 `{{label}}` 의 format 이 toLocaleString 을 쓰지 않습니다. 세 자리 구분 없이 표시됩니다. (AGENTS.md §1-4)",
37
+ },
38
+ },
39
+
40
+ create(context) {
41
+ const allow = new Set(context.options[0]?.allow ?? []);
42
+
43
+ return {
44
+ ObjectExpression(node) {
45
+ const column = asColumn(node);
46
+ if (!column) return;
47
+
48
+ const { labelProp, label, columnName } = column;
49
+ if (columnName && allow.has(columnName)) return;
50
+
51
+ // 숫자 컬럼이면 모두 대상 — 하이픈 서식 문자열(전화번호 등)만 뺀다
52
+ if (!NUMERIC_LABEL.test(label) || IDENTIFIER_ONLY.test(label)) return;
53
+
54
+ // render 로 셀을 직접 그리는 경우는 이 규칙이 판단하지 않는다
55
+ if (prop(node, "render")) return;
56
+
57
+ const unit = /금액|가격|단가|판매가|원가|비용|수수료|배송비|할인|합계|총액|잔액/.test(label)
58
+ ? "원"
59
+ : "";
60
+
61
+ const formatProp = prop(node, "format");
62
+ if (!formatProp) {
63
+ context.report({
64
+ node: labelProp,
65
+ messageId: "missingFormat",
66
+ data: { label, unit },
67
+ });
68
+ return;
69
+ }
70
+
71
+ const src = context.sourceCode.getText(formatProp);
72
+ if (!src.includes("toLocaleString")) {
73
+ context.report({ node: formatProp, messageId: "missingLocale", data: { label } });
74
+ }
75
+ },
76
+ };
77
+ },
78
+ };
@@ -0,0 +1,93 @@
1
+ /**
2
+ * 숫자 컬럼이 오른쪽 정렬(`align: 'right'`)인지 검사한다.
3
+ *
4
+ * 자릿수가 세로로 맞아야 값의 크기를 비교할 수 있으므로,
5
+ * 금액·수량·개수처럼 숫자가 들어가는 컬럼은 예외 없이 오른쪽 정렬한다.
6
+ *
7
+ * 대상은 `name` · `label` · `field` 를 모두 가진 객체 리터럴(= STableColumn 형태)뿐이라
8
+ * 일반 객체를 오탐하지 않는다.
9
+ *
10
+ * AGENTS.md §3-4.
11
+ */
12
+
13
+ import {
14
+ NUMERIC_LABEL,
15
+ IDENTIFIER_ONLY,
16
+ NUMERIC_FORMAT,
17
+ prop,
18
+ literalString,
19
+ asColumn,
20
+ } from "../lib/table-column.mjs";
21
+
22
+ export default {
23
+ meta: {
24
+ type: "problem",
25
+ docs: {
26
+ description: "테이블의 숫자 컬럼은 오른쪽 정렬",
27
+ },
28
+ schema: [
29
+ {
30
+ type: "object",
31
+ properties: {
32
+ /** 검사에서 뺄 컬럼 name */
33
+ allow: { type: "array", items: { type: "string" }, uniqueItems: true },
34
+ },
35
+ additionalProperties: false,
36
+ },
37
+ ],
38
+ messages: {
39
+ needsRight:
40
+ "숫자 컬럼 `{{label}}` 은 `align: 'right'` 로 오른쪽 정렬해야 합니다{{reason}}. (AGENTS.md §3-4)",
41
+ },
42
+ fixable: "code",
43
+ },
44
+
45
+ create(context) {
46
+ const allow = new Set(context.options[0]?.allow ?? []);
47
+
48
+ return {
49
+ ObjectExpression(node) {
50
+ const column = asColumn(node);
51
+ if (!column) return;
52
+
53
+ const { labelProp, label, columnName } = column;
54
+ if (columnName && allow.has(columnName)) return;
55
+
56
+ // 숫자 판정 — label 키워드 또는 format 의 숫자 서식
57
+ let reason = "";
58
+ let numeric = false;
59
+
60
+ if (NUMERIC_LABEL.test(label) && !IDENTIFIER_ONLY.test(label)) {
61
+ numeric = true;
62
+ }
63
+
64
+ const formatProp = prop(node, "format");
65
+ if (formatProp) {
66
+ const src = context.sourceCode.getText(formatProp);
67
+ if (NUMERIC_FORMAT.test(src)) {
68
+ numeric = true;
69
+ reason = " (format 이 숫자를 서식화하고 있습니다)";
70
+ }
71
+ }
72
+
73
+ if (!numeric) return;
74
+
75
+ const alignProp = prop(node, "align");
76
+ if (literalString(alignProp) === "right") return;
77
+
78
+ context.report({
79
+ node: alignProp ?? labelProp,
80
+ messageId: "needsRight",
81
+ data: { label, reason },
82
+ fix(fixer) {
83
+ if (alignProp) {
84
+ return fixer.replaceText(alignProp.value, "'right'");
85
+ }
86
+ // align 이 없으면 label 뒤에 추가한다
87
+ return fixer.insertTextAfter(labelProp, ", align: 'right'");
88
+ },
89
+ });
90
+ },
91
+ };
92
+ },
93
+ };
@@ -0,0 +1,16 @@
1
+ /* 자동 생성: npm run build (scripts/gen-eslint-scale.mjs). 직접 수정 금지. */
2
+
3
+ /** gap-*, p-*, m-* 등 간격 유틸리티가 받을 수 있는 값 (px) */
4
+ export const SPACING = [2,4,6,8,10,12,16,19,20,22,24,28,32,36,40,44,48,56,60,62,80];
5
+
6
+ /** text-* 숫자 유틸리티가 받을 수 있는 값 (px) */
7
+ export const TEXT_SIZES = [10,11,12,13,14,16,18,20,24,28,32];
8
+
9
+ /** rounded-* 가 받을 수 있는 키 */
10
+ export const RADIUS = ["2","4","6","8","full","control-sm","control-md","feedback-xs-square","feedback-sm-square","feedback-pill","field-sm","field-md","navigation","item-bordered"];
11
+
12
+ /** 타이포그래피 프리셋 클래스 */
13
+ export const TYPO_PRESETS = ["typo-body-lg-bold","typo-body-lg-default","typo-body-lg-medium","typo-body-md-bold","typo-body-md-default","typo-body-md-medium","typo-body-sm-bold","typo-body-sm-default","typo-body-sm-medium","typo-body-xs-default","typo-control-lg","typo-control-md-default","typo-control-md-underline","typo-control-sm-default","typo-control-sm-underline","typo-control-xs-bold","typo-control-xs-default","typo-control-xs-medium","typo-control-xs-underline","typo-feedback-md-bold","typo-feedback-md-medium","typo-feedback-md-underline","typo-feedback-sm-bold","typo-feedback-sm-medium","typo-feedback-sm-regular","typo-feedback-sm-underline","typo-feedback-xs","typo-field-lg-bold","typo-field-lg-default","typo-field-lg-medium","typo-field-md-bold","typo-field-md-default","typo-field-md-medium","typo-field-sm-bold","typo-field-sm-default","typo-field-sm-medium","typo-field-xs-default","typo-heading-lg","typo-heading-md","typo-heading-sm","typo-heading-xs","typo-navigation-lg-bold","typo-navigation-lg-medium","typo-navigation-md-bold","typo-navigation-sm-bold","typo-navigation-sm-default","typo-navigation-sm-medium","typo-navigation-xs-bold","typo-navigation-xs-medium","typo-table-accent","typo-table-body","typo-table-header"];
14
+
15
+ /** 색상 유틸리티 키 (text-*, bg-*, border-* 공통) */
16
+ export const COLORS = ["bg-accent","bg-accentBright","bg-accentFaint","bg-accentLight","bg-brand","bg-caution","bg-danger","bg-dangerLight","bg-deep","bg-frame","bg-neutralBright","bg-neutralLight","bg-progress","bg-screen","bg-subtle","bg-success","bg-successLight","bg-tableHeader","bg-warning","black","blue-strong","blue-subtle","border-accent","border-accentLight","border-danger","border-default","border-strong","border-success","brilliantblue-05","brilliantblue-10","brilliantblue-20","brilliantblue-25","brilliantblue-40","brilliantblue-50","brilliantblue-60","brilliantblue-70","brilliantblue-75","brilliantblue-80","brilliantblue-85","brilliantblue-90","brilliantblue-95","brilliantblue-99","control-bg-inverseHover","danger-15","danger-20","danger-30","danger-45","danger-60","danger-70","danger-75","danger-80","danger-85","danger-90","danger-95","danger-99","danger-strong","danger-subtle","darkblue-strong","darkblue-subtle","disabled-bg","disabled-border","disabled-icon","disabled-text","divider-default","fg-accent","fg-accentLight","fg-brand","fg-danger","fg-dangerLight","fg-deep","fg-inverse","fg-primary","fg-quaternary","fg-secondary","fg-subtleCool","fg-subtleWarm","fg-success","fg-tertiary","fg-warning","field-bg-default","field-bg-hover","field-bg-readOnly","field-border-danger","field-border-default","field-border-focus","field-border-hover","field-border-success","field-icon-default","field-text-default","field-text-placeholder","field-text-readOnly","green-15","green-25","green-45","green-55","green-65","green-70","green-75","green-80","green-85","green-90","green-95","green-99","green-strong","green-subtle","grey-05","grey-10","grey-20","grey-25","grey-30","grey-35","grey-45","grey-50","grey-55","grey-60","grey-65","grey-70","grey-80","grey-85","grey-90","grey-95","grey-strong","grey-subtle","indigo-strong","indigo-subtle","lightblue-strong","lightblue-subtle","link-accent","navigation-gnb-bg-dark","oceanblue-15","oceanblue-25","oceanblue-50","oceanblue-60","oceanblue-65","oceanblue-70","oceanblue-75","oceanblue-80","oceanblue-85","oceanblue-90","oceanblue-95","oceanblue-99","olive-05","olive-10","olive-15","olive-20","olive-30","olive-45","olive-55","olive-65","olive-70","olive-80","olive-90","olive-95","orange-10","orange-20","orange-35","orange-45","orange-55","orange-60","orange-65","orange-75","orange-85","orange-90","orange-95","orange-99","orange-strong","orange-subtle","primary-05","primary-10","primary-20","primary-25","primary-40","primary-50","primary-60","primary-70","primary-75","primary-80","primary-85","primary-90","primary-95","primary-99","red-15","red-20","red-30","red-45","red-60","red-70","red-75","red-80","red-85","red-90","red-95","red-99","red-strong","red-subtle","steelblue-10","steelblue-25","steelblue-45","steelblue-60","steelblue-65","steelblue-70","steelblue-75","steelblue-80","steelblue-85","steelblue-90","steelblue-95","steelblue-99","success-15","success-25","success-45","success-55","success-65","success-70","success-75","success-80","success-85","success-90","success-95","success-99","success-strong","success-subtle","warning-10","warning-20","warning-35","warning-45","warning-55","warning-60","warning-65","warning-75","warning-85","warning-90","warning-95","warning-99","warning-strong","warning-subtle","white","yellow-10","yellow-20","yellow-25","yellow-30","yellow-40","yellow-45","yellow-50","yellow-60","yellow-70","yellow-80","yellow-90","yellow-95","yellow-moderate","yellow-strong","yellow-subtle"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sellmate-design-system-react",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Sellmate Design System — React (TypeScript + Tailwind v4) port",
5
5
  "license": "UNLICENSED",
6
6
  "keywords": [
@@ -16,7 +16,10 @@
16
16
  "**/*.css"
17
17
  ],
18
18
  "files": [
19
- "dist"
19
+ "dist",
20
+ "eslint",
21
+ "!eslint/__tests__",
22
+ "AGENTS.md"
20
23
  ],
21
24
  "exports": {
22
25
  ".": {
@@ -25,7 +28,8 @@
25
28
  "require": "./dist/index.cjs"
26
29
  },
27
30
  "./theme.css": "./dist/theme.css",
28
- "./styles.css": "./dist/styles.css"
31
+ "./styles.css": "./dist/styles.css",
32
+ "./eslint": "./eslint/index.mjs"
29
33
  },
30
34
  "main": "./dist/index.cjs",
31
35
  "module": "./dist/index.js",
@@ -33,7 +37,7 @@
33
37
  "scripts": {
34
38
  "prepublishOnly": "node scripts/guard-publish-branch.mjs",
35
39
  "prepack": "npm run build",
36
- "build": "npm run gen:tokens && tsup && node scripts/preserve-directives.mjs && npm run build:dts && npm run build:css",
40
+ "build": "npm run gen:tokens && tsup && node scripts/preserve-directives.mjs && npm run build:dts && npm run build:css && node scripts/copy-docs.mjs && node scripts/gen-eslint-scale.mjs && node scripts/gen-llms.mjs",
37
41
  "build:dts": "tsc -p tsconfig.build.json --emitDeclarationOnly",
38
42
  "gen:tokens": "node scripts/generate-tokens.mjs",
39
43
  "docs:gen": "node scripts/gen-docs.mjs",
@@ -46,6 +50,7 @@
46
50
  "storybook": "storybook dev -p 6006",
47
51
  "build-storybook": "storybook build",
48
52
  "test:spec": "npm run build && node --test ./src/components/**/*.test.mjs",
53
+ "test:eslint": "node --test ./eslint/__tests__/*.test.mjs",
49
54
  "typecheck": "tsc --noEmit",
50
55
  "lint": "eslint \"src/**/*.{ts,tsx}\"",
51
56
  "lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",