sellmate-design-system-react 8.1.0 → 9.0.0-beta.10

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 (76) hide show
  1. package/AGENTS.md +279 -124
  2. package/README.md +101 -8
  3. package/dist/components/SChipFilter/README.md +70 -0
  4. package/dist/components/SChipFilter/SChipFilter.d.ts +167 -0
  5. package/dist/components/SChipFilter/index.d.ts +1 -0
  6. package/dist/components/SCircleProgress/README.md +3 -0
  7. package/dist/components/SCircleProgress/SCircleProgress.d.ts +7 -1
  8. package/dist/components/SDatePicker/README.md +43 -3
  9. package/dist/components/SDatePicker/SDatePicker.d.ts +36 -3
  10. package/dist/components/SDatePicker/index.d.ts +1 -1
  11. package/dist/components/SDateRangePicker/README.md +21 -0
  12. package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +16 -0
  13. package/dist/components/SDateRangePicker/index.d.ts +1 -1
  14. package/dist/components/SField/README.md +3 -0
  15. package/dist/components/SField/SField.d.ts +12 -0
  16. package/dist/components/SGhostButton/README.md +6 -0
  17. package/dist/components/SGnb/gnb.config.d.ts +2 -0
  18. package/dist/components/SGuide/README.md +1 -1
  19. package/dist/components/SGuide/SGuide.d.ts +1 -1
  20. package/dist/components/SIcon/README.md +6 -0
  21. package/dist/components/SIcon/icons.gen.d.ts +1 -0
  22. package/dist/components/SImage/README.md +44 -0
  23. package/dist/components/SImage/SImage.d.ts +41 -0
  24. package/dist/components/SImage/index.d.ts +1 -0
  25. package/dist/components/SKeyValueTable/README.md +1 -0
  26. package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +8 -1
  27. package/dist/components/SListItem/README.md +2 -2
  28. package/dist/components/SListItem/SListItem.d.ts +2 -2
  29. package/dist/components/SPage/README.md +23 -0
  30. package/dist/components/SPage/SPage.d.ts +27 -1
  31. package/dist/components/SPage/SPageHeader.d.ts +32 -0
  32. package/dist/components/SPage/index.d.ts +2 -1
  33. package/dist/components/SPage/page.config.d.ts +8 -0
  34. package/dist/components/SPortal/README.md +1 -1
  35. package/dist/components/SPortal/SPortal.d.ts +7 -1
  36. package/dist/components/SRadio/README.md +2 -0
  37. package/dist/components/SRadioButton/README.md +13 -0
  38. package/dist/components/SSearchInput/README.md +48 -0
  39. package/dist/components/SSearchInput/SSearchInput.d.ts +46 -0
  40. package/dist/components/SSearchInput/index.d.ts +1 -0
  41. package/dist/components/SSectionHeaderCard/README.md +10 -23
  42. package/dist/components/SSectionHeaderCard/SSectionHeaderCard.d.ts +21 -14
  43. package/dist/components/SSectionHeaderCard/index.d.ts +1 -1
  44. package/dist/components/SSelect/README.md +4 -2
  45. package/dist/components/SSelect/SSelect.d.ts +2 -2
  46. package/dist/components/STable/README.md +1 -0
  47. package/dist/components/STable/STable.d.ts +20 -0
  48. package/dist/components/STabs/README.md +0 -1
  49. package/dist/components/STabs/STabs.d.ts +2 -4
  50. package/dist/components/STabs/index.d.ts +1 -1
  51. package/dist/components/STabs/tabs.config.d.ts +3 -4
  52. package/dist/components/STag/README.md +2 -0
  53. package/dist/components/STextLink/README.md +2 -0
  54. package/dist/components/STimePicker/timepicker.config.d.ts +5 -0
  55. package/dist/components/SToggle/README.md +1 -0
  56. package/dist/components/SToggle/SToggle.d.ts +4 -1
  57. package/dist/components/STooltip/README.md +2 -0
  58. package/dist/components/STree/README.md +3 -0
  59. package/dist/components/STree/STree.d.ts +8 -0
  60. package/dist/components/STree/index.d.ts +1 -1
  61. package/dist/index.cjs +2988 -393
  62. package/dist/index.cjs.map +1 -1
  63. package/dist/index.d.ts +3 -0
  64. package/dist/index.js +2981 -394
  65. package/dist/index.js.map +1 -1
  66. package/dist/lib/floating-width.d.ts +22 -0
  67. package/dist/lib/truncated-value-tooltip.d.ts +18 -0
  68. package/dist/llms-full.txt +545 -152
  69. package/dist/llms.txt +283 -127
  70. package/dist/styles.css +397 -36
  71. package/dist/theme.css +34 -11
  72. package/eslint/index.mjs +5 -0
  73. package/eslint/rules/field-width-grade.d.mts +41 -0
  74. package/eslint/rules/field-width-grade.mjs +294 -0
  75. package/eslint/scale.gen.mjs +1 -1
  76. package/package.json +7 -4
package/dist/theme.css CHANGED
@@ -154,6 +154,7 @@
154
154
  --color-fg-subtleCool: #025497;
155
155
  --color-fg-deep: #004290;
156
156
  --color-fg-success: #00973C;
157
+ --color-fg-successLight: #12B553;
157
158
  --color-fg-dangerLight: #FB4444;
158
159
  --color-fg-accent: #0075FF;
159
160
  --color-fg-accentLight: #64ABFF;
@@ -453,6 +454,7 @@
453
454
  --sys-color-fg-subtleCool: #025497;
454
455
  --sys-color-fg-deep: #004290;
455
456
  --sys-color-fg-success: #00973C;
457
+ --sys-color-fg-successLight: #12B553;
456
458
  --sys-color-fg-dangerLight: #FB4444;
457
459
  --sys-color-fg-accent: #0075FF;
458
460
  --sys-color-fg-accentLight: #64ABFF;
@@ -608,8 +610,10 @@
608
610
  --cmp-switch-bg-disabled-on: #BBDAFF;
609
611
  --cmp-switch-bg-disabled-off: #E1E1E1;
610
612
  --cmp-switch-gap: 6px;
611
- --cmp-toggle-height: 28px;
612
- --cmp-toggle-paddingX: 12px;
613
+ --cmp-toggle-xs-height: 24px;
614
+ --cmp-toggle-xs-paddingX: 8px;
615
+ --cmp-toggle-sm-height: 28px;
616
+ --cmp-toggle-sm-paddingX: 12px;
613
617
  --cmp-toggle-radius: 9999px;
614
618
  --cmp-toggle-border-width: 1px;
615
619
  --cmp-toggle-border-default: #CCCCCC;
@@ -742,6 +746,11 @@
742
746
  --cmp-field-errorMessage-color: #E30000;
743
747
  --cmp-field-contents-gap: 4px;
744
748
  --cmp-field-message-paddingX: 8px;
749
+ --cmp-field-width-xs: 80px;
750
+ --cmp-field-width-sm: 120px;
751
+ --cmp-field-width-md: 160px;
752
+ --cmp-field-width-lg: 240px;
753
+ --cmp-field-width-xl: 480px;
745
754
  --cmp-textinput-sm-height: 28px;
746
755
  --cmp-textinput-sm-paddingX: 12px;
747
756
  --cmp-textinput-sm-paddingY: 4px;
@@ -889,6 +898,20 @@
889
898
  --cmp-datepicker-calendar-time-gap: 12px;
890
899
  --cmp-datepicker-calendar-time-section-gap: 12px;
891
900
  --cmp-datepicker-calendar-time-unit-gap: 2px;
901
+ --cmp-datepicker-mode-bg: #FFFFFF;
902
+ --cmp-datepicker-mode-radius: 4px;
903
+ --cmp-datepicker-mode-year-width: 120px;
904
+ --cmp-datepicker-mode-yearMonth-column-width: 64px;
905
+ --cmp-datepicker-mode-yearMonth-gap: 4px;
906
+ --cmp-datepicker-mode-item-paddingY: 4px;
907
+ --cmp-datepicker-mode-item-paddingX: 12px;
908
+ --cmp-datepicker-mode-item-gap: 8px;
909
+ --cmp-datepicker-mode-item-bg-default: #FFFFFF;
910
+ --cmp-datepicker-mode-item-bg-hover: #0075FF;
911
+ --cmp-datepicker-mode-item-content-default: #222222;
912
+ --cmp-datepicker-mode-item-content-hover: #FFFFFF;
913
+ --cmp-datepicker-mode-item-content-disabled: #888888;
914
+ --cmp-datepicker-mode-item-content-selected: #0075FF;
892
915
  --cmp-chip-height: 24px;
893
916
  --cmp-chip-paddingX: 8px;
894
917
  --cmp-chip-gap: 4px;
@@ -1139,9 +1162,10 @@
1139
1162
  --cmp-progress-circular-gap: 8px;
1140
1163
  --cmp-progress-color-track: #EEEEEE;
1141
1164
  --cmp-progress-color-label: #555555;
1142
- --cmp-progress-active-color: #2D8DFF;
1143
- --cmp-progress-success-color: #12B553;
1144
- --cmp-progress-error-color: #FB4444;
1165
+ --cmp-progress-color-active: #2D8DFF;
1166
+ --cmp-progress-color-success: #12B553;
1167
+ --cmp-progress-color-error: #FB4444;
1168
+ --cmp-progress-color-neutral: #D8D8D8;
1145
1169
  --cmp-progress-bar-text: #FFFFFF;
1146
1170
  --cmp-progress-inverse-color: #FFFFFF;
1147
1171
  --cmp-progress-inverse-opacity: 40%;
@@ -1219,10 +1243,8 @@
1219
1243
  --cmp-stepper-vertical-title-paddingTop: 12px;
1220
1244
  --cmp-stepper-vertical-title-paddingBottom: 6px;
1221
1245
  --cmp-stepper-vertical-title-text: #004290;
1222
- --cmp-tab-main-lg-height: 44px;
1223
- --cmp-tab-main-lg-paddingX: 32px;
1224
- --cmp-tab-main-md-height: 36px;
1225
- --cmp-tab-main-md-paddingX: 24px;
1246
+ --cmp-tab-main-height: 36px;
1247
+ --cmp-tab-main-paddingX: 24px;
1226
1248
  --cmp-tab-main-gap: 8px;
1227
1249
  --cmp-tab-main-container-gap: 4px;
1228
1250
  --cmp-tab-main-radius: 6px;
@@ -1378,9 +1400,8 @@
1378
1400
  --cmp-tree-item-rowGap: 8px;
1379
1401
  --cmp-tree-item-contentGap: 12px;
1380
1402
  --cmp-tree-item-text-default: #222222;
1381
- --cmp-tree-item-text-selected: #0075FF;
1382
1403
  --cmp-tree-item-arrow-icon: 16px;
1383
- --cmp-tree-connector-line-color: #AAAAAA;
1404
+ --cmp-tree-connector-line-color: #E1E1E1;
1384
1405
  --cmp-pageHeader-paddingX: 16px;
1385
1406
  --cmp-pageHeader-gap: 12px;
1386
1407
  --cmp-pageHeader-title-paddingLeft: 4px;
@@ -1450,6 +1471,8 @@
1450
1471
  --cmp-splitter-border-width: 4px;
1451
1472
  --cmp-splitter-border-padding: 4px;
1452
1473
  --cmp-splitter-border-color: #64ABFF;
1474
+ --cmp-image-bg: #F9F9F9;
1475
+ --cmp-image-icon: #D8D8D8;
1453
1476
  }
1454
1477
 
1455
1478
  /* 전역 스크롤바 — overflow(auto/scroll)로 스크롤이 생기는 모든 요소에 SScrollArea 기본 스크롤바를 자동 적용.
package/eslint/index.mjs CHANGED
@@ -11,6 +11,7 @@ import preferTypoPreset from "./rules/prefer-typo-preset.mjs";
11
11
  import tableNumericAlign from "./rules/table-numeric-align.mjs";
12
12
  import requireLocaleNumber from "./rules/require-locale-number.mjs";
13
13
  import componentGroupGap from "./rules/component-group-gap.mjs";
14
+ import fieldWidthGrade from "./rules/field-width-grade.mjs";
14
15
 
15
16
  const PLUGIN_NAME = "sellmate";
16
17
 
@@ -24,6 +25,7 @@ const plugin = {
24
25
  "table-numeric-align": tableNumericAlign,
25
26
  "require-locale-number": requireLocaleNumber,
26
27
  "component-group-gap": componentGroupGap,
28
+ "field-width-grade": fieldWidthGrade,
27
29
  },
28
30
  };
29
31
 
@@ -54,6 +56,8 @@ const recommended = [
54
56
  [`${PLUGIN_NAME}/component-group-gap`]: "warn",
55
57
  [`${PLUGIN_NAME}/table-numeric-align`]: "warn",
56
58
  [`${PLUGIN_NAME}/require-locale-number`]: "warn",
59
+ // 폭 등급 환산이 아직 잠정치라 권고에 둔다 (디자인 확정표가 오면 error 승격 검토)
60
+ [`${PLUGIN_NAME}/field-width-grade`]: "warn",
57
61
 
58
62
  // 앱 고유 치수(w-[280px])·일회성 값에는 임의 값이 정당하다. 필요한 팀만 켠다.
59
63
  [`${PLUGIN_NAME}/no-arbitrary-class`]: "off",
@@ -79,6 +83,7 @@ const strict = [
79
83
  [`${PLUGIN_NAME}/component-group-gap`]: "error",
80
84
  [`${PLUGIN_NAME}/table-numeric-align`]: "error",
81
85
  [`${PLUGIN_NAME}/require-locale-number`]: "error",
86
+ [`${PLUGIN_NAME}/field-width-grade`]: "error",
82
87
  },
83
88
  },
84
89
  ];
@@ -0,0 +1,41 @@
1
+ /**
2
+ * playground 의 실측 페이지(FieldWidthGradePage)가 규칙과 같은 계산을 쓰기 위한 타입 선언.
3
+ * 규칙 본체는 소비 앱이 그대로 실행하는 plain ESM 이라 .mjs 로 두고, 타입만 여기에 붙인다.
4
+ */
5
+
6
+ export type FieldGrade = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
7
+ export type FieldSize = 'sm' | 'md';
8
+
9
+ export interface FieldComponentSpec {
10
+ /** 스키마 상한을 담는 prop — 숫자 필드는 maxLength 가 아니라 max 다 */
11
+ lengthProp: 'maxLength' | 'max';
12
+ font: Record<FieldSize, number>;
13
+ /** 글자가 쓸 수 없는 폭의 합 (좌우 패딩 · 테두리 · 스테퍼) */
14
+ chrome: Record<FieldSize, number>;
15
+ /** 한글이 들어갈 수 있는가 */
16
+ korean: boolean;
17
+ grades: FieldGrade[];
18
+ }
19
+
20
+ export declare const GRADES: Record<FieldGrade, number>;
21
+ export declare const CHAR_RATIO: { narrow: number; wide: number };
22
+ export declare const COMPONENTS: Record<string, FieldComponentSpec>;
23
+
24
+ export declare function capacity(
25
+ spec: FieldComponentSpec,
26
+ size: FieldSize,
27
+ grade: FieldGrade,
28
+ ratio: number,
29
+ grades?: Record<FieldGrade, number>,
30
+ ): number;
31
+
32
+ export declare function smallestGradeFor(
33
+ spec: FieldComponentSpec,
34
+ size: FieldSize,
35
+ len: number,
36
+ ratio: number,
37
+ grades?: Record<FieldGrade, number>,
38
+ ): FieldGrade | undefined;
39
+
40
+ declare const rule: unknown;
41
+ export default rule;
@@ -0,0 +1,294 @@
1
+ /**
2
+ * 필드 폭을 `maxLength`(= 스키마 상한) 기준으로 고정했는지 검사한다.
3
+ *
4
+ * 확정 규칙 (design-feedback-final.md §1-2):
5
+ * 필드 너비는 maxLength 로 정한다.
6
+ * - 상한이 등급 안에 들어오면 → 그 등급
7
+ * - 상한이 등급 상한(xl)을 넘거나 상한이 없으면 → 행 전체 + 100%
8
+ * - 셀렉트는 예외 (maxLength 개념이 없어 목록 최장값 기준) → 대상 아님
9
+ *
10
+ * 디자인팀이 요구한 "검수: maxLength ↔ 폭 등급 적합성 필수 확인" 을 자동화한 것이다.
11
+ * 사람이 하는 검수는 스키마가 나중에 확정되는 화면에서 빠지기 쉽다 —
12
+ * 시안 단계의 잠정 등급이 그대로 남아도 화면은 멀쩡해 보이기 때문이다.
13
+ *
14
+ * 대상이 아닌 것과 그 이유:
15
+ * STextarea 여러 줄로 접히므로 maxLength 가 폭을 정하지 않는다
16
+ * SSelect 계열 maxLength 개념이 없다 (§1-2 예외 — 목록 최장값 기준)
17
+ * SDatePicker 계열 컴포넌트가 자체 상한을 갖는다 (B-14)
18
+ *
19
+ * ⚠️ 글자수 ↔ 등급 환산은 아직 디자인 확정본이 없어 토큰 값에서 계산한 잠정치다.
20
+ * (폭 등급 자체도 B-8 토큰 도착 전이라 하드코딩이다 — 도착하면 GRADES 를 교체한다)
21
+ * 확정표가 오면 GRADES · CHAR_RATIO 를 그 값으로 바꾼다. 두 상수 모두 옵션으로 덮을 수 있다.
22
+ */
23
+
24
+ /**
25
+ * 폭 등급 (px). design-feedback-final.md §2-10.
26
+ * B-8 토큰(`--cmp-field-width-*`)이 소스에 추가되면 scale.gen.mjs 를 거쳐 여기로 들어온다.
27
+ */
28
+ export const GRADES = { xs: 80, sm: 120, md: 160, lg: 240, xl: 480 };
29
+
30
+ /**
31
+ * 글자 하나가 차지하는 폭 (font-size 대비 비율).
32
+ * narrow — 영문·숫자. "이 등급에 안 담긴다" 를 판정할 때 쓴다(가장 좁게 잡아도 넘치면 확실히 위반)
33
+ * wide — 한글(전각). "한 등급 작아도 된다" 를 판정할 때 쓴다(가장 넓게 잡아도 남으면 확실히 과하다)
34
+ * 두 판정 사이에는 아무것도 보고하지 않는 구간이 남는다 — 오탐보다 미탐을 택한 것이다.
35
+ */
36
+ export const CHAR_RATIO = { narrow: 0.55, wide: 1 };
37
+
38
+ /**
39
+ * 컴포넌트별 기하. chrome 은 글자가 쓸 수 없는 폭(좌우 패딩 · 테두리 · 스테퍼)의 합이고
40
+ * 전부 theme.css 토큰 값에서 계산했다.
41
+ *
42
+ * SInput/SBarcodeInput paddingX(12·16) × 2 + border 1 × 2
43
+ * SNumberInput 스테퍼가 없으면 패딩이 두 배가 된다(SNumberInput.tsx:388) —
44
+ * 없을 때 paddingX(6·8) × 2 × 2 + border,
45
+ * 있을 때 paddingX × 2 + stepper(20·24) × 2 + gap(= paddingX) × 2 + border
46
+ *
47
+ * lengthProp — 숫자 필드의 스키마 상한은 `maxLength` 가 아니라 `max` 다.
48
+ * korean — 한글이 들어갈 수 있는가. false 면 두 판정 모두 narrow 로 계산한다.
49
+ * chromeStepper — `useButton` 일 때의 chrome. 기본값이 false 라 chrome 쪽이 기본이다.
50
+ */
51
+ export const COMPONENTS = {
52
+ SInput: {
53
+ lengthProp: "maxLength",
54
+ font: { sm: 12, md: 14 },
55
+ chrome: { sm: 26, md: 34 },
56
+ korean: true,
57
+ grades: ["sm", "md", "lg", "xl"],
58
+ },
59
+ SBarcodeInput: {
60
+ lengthProp: "maxLength",
61
+ font: { sm: 12, md: 14 },
62
+ chrome: { sm: 26, md: 34 },
63
+ korean: false, // 바코드는 영숫자다
64
+ grades: ["sm", "md", "lg", "xl"],
65
+ },
66
+ SNumberInput: {
67
+ lengthProp: "max",
68
+ font: { sm: 12, md: 14 },
69
+ chrome: { sm: 26, md: 34 },
70
+ chromeStepper: { sm: 66, md: 82 },
71
+ korean: false,
72
+ grades: ["xs", "sm", "md", "lg", "xl"], // xs 는 숫자 필드 전용 등급이다
73
+ },
74
+ };
75
+
76
+ /**
77
+ * 등급이 담을 수 있는 글자수. playground 의 실측 페이지가 같은 식을 쓰도록 내보낸다
78
+ * (숫자를 두 곳에 적으면 한쪽만 고쳐져 검수가 무의미해진다).
79
+ */
80
+ export function capacity(spec, size, grade, ratio, grades = GRADES) {
81
+ const usable = Math.max(0, grades[grade] - spec.chrome[size]);
82
+ return Math.floor(usable / (spec.font[size] * ratio));
83
+ }
84
+
85
+ /** 그 비율로 len 글자를 담을 수 있는 가장 작은 등급 (없으면 undefined) */
86
+ export function smallestGradeFor(spec, size, len, ratio, grades = GRADES) {
87
+ return spec.grades.find(g => capacity(spec, size, g, ratio, grades) >= len);
88
+ }
89
+
90
+ /** JSX 속성 노드 */
91
+ function attr(node, name) {
92
+ return node.attributes.find(a => a.type === "JSXAttribute" && a.name?.name === name);
93
+ }
94
+
95
+ /**
96
+ * 속성에서 리터럴 값을 꺼낸다.
97
+ * 없음 → undefined
98
+ * 리터럴 → 값
99
+ * 그 밖(변수) → null (판단 불가 — 조용히 넘긴다)
100
+ */
101
+ function literal(node, name) {
102
+ const a = attr(node, name);
103
+ if (!a) return undefined;
104
+ const v = a.value;
105
+ if (!v) return true; // <SNumberInput useButton /> 같은 boolean shorthand 는 true 다
106
+ if (v.type === "Literal") return v.value;
107
+ if (v.type === "JSXExpressionContainer" && v.expression.type === "Literal") {
108
+ return v.expression.value;
109
+ }
110
+ return null;
111
+ }
112
+
113
+ /** `max={1000}` → 자릿수 4. 천단위 콤마도 폭을 먹으므로 함께 센다 */
114
+ function digitsOf(max) {
115
+ const digits = String(Math.abs(Math.trunc(max))).length;
116
+ return digits + Math.floor((digits - 1) / 3);
117
+ }
118
+
119
+ export default {
120
+ meta: {
121
+ type: "problem",
122
+ docs: {
123
+ description: "필드 폭을 maxLength(스키마 상한) 기준 등급으로 고정",
124
+ },
125
+ schema: [
126
+ {
127
+ type: "object",
128
+ properties: {
129
+ /** 폭 등급 px 재정의 — 디자인 확정본이 오면 여기로 덮는다 */
130
+ grades: { type: "object", additionalProperties: { type: "number" } },
131
+ /** 글자폭 비율 재정의 */
132
+ charRatio: {
133
+ type: "object",
134
+ properties: { narrow: { type: "number" }, wide: { type: "number" } },
135
+ additionalProperties: false,
136
+ },
137
+ },
138
+ additionalProperties: false,
139
+ },
140
+ ],
141
+ messages: {
142
+ noGrade:
143
+ "`{{lengthProp}}={{len}}` 로 상한이 정해진 필드인데 `width` 가 없어 행 전체를 차지합니다. `width={{{suggestPx}}}`({{suggest}} 등급)으로 고정하세요. (§1-2)",
144
+ offGrade:
145
+ "`width={{{width}}}` 는 폭 등급이 아닙니다. {{gradeList}} 중 하나이거나, 상한이 없으면 `width=\"100%\"` 여야 합니다. (§1-2)",
146
+ noBound:
147
+ "`width` 로 폭을 고정했는데 `{{lengthProp}}` 이 없습니다. 폭의 근거가 스키마 상한이므로, 상한이 없으면 행 전체(`width=\"100%\"` 또는 생략)로 둡니다. (§1-2)",
148
+ tooNarrow:
149
+ "`{{lengthProp}}={{len}}` 이 {{grade}} 등급({{gradePx}}px)에 담기지 않습니다. `width={{{suggestPx}}}`({{suggest}} 등급) 이상이 필요합니다. (§1-2)",
150
+ tooWide:
151
+ "`{{lengthProp}}={{len}}` 은 {{suggest}} 등급({{suggestPx}}px)으로 충분한데 {{grade}} 등급({{gradePx}}px)을 주었습니다. 폭은 상한에 맞춥니다. (§1-2)",
152
+ overMax:
153
+ "`{{lengthProp}}={{len}}` 은 최대 등급 xl({{xl}}px)로도 담기지 않습니다. 행 전체(`width=\"100%\"`)로 둡니다. (§1-2)",
154
+ fullWidthBounded:
155
+ "`{{lengthProp}}={{len}}` 로 상한이 정해져 있으므로 `width=\"100%\"` 가 아니라 `width={{{suggestPx}}}`({{suggest}} 등급)로 고정합니다. 행 전체는 상한이 없거나 xl 을 넘을 때만 씁니다. (§1-2)",
156
+ },
157
+ },
158
+
159
+ create(context) {
160
+ const grades = { ...GRADES, ...(context.options[0]?.grades ?? {}) };
161
+ const ratio = { ...CHAR_RATIO, ...(context.options[0]?.charRatio ?? {}) };
162
+
163
+ return {
164
+ JSXOpeningElement(node) {
165
+ if (node.name.type !== "JSXIdentifier") return;
166
+ const base = COMPONENTS[node.name.name];
167
+ if (!base) return;
168
+
169
+ // spread 가 있으면 width·maxLength 가 그 안에 있을 수 있어 판단하지 않는다
170
+ if (node.attributes.some(a => a.type === "JSXSpreadAttribute")) return;
171
+
172
+ const size = literal(node, "size") === "md" ? "md" : "sm";
173
+ // 스테퍼가 붙으면 글자가 쓸 수 있는 폭이 크게 줄어든다 (useButton 기본값은 false)
174
+ const spec = literal(node, "useButton") === true && base.chromeStepper
175
+ ? { ...base, chrome: base.chromeStepper }
176
+ : base;
177
+
178
+ const capacityAt = (grade, r) => capacity(spec, size, grade, r, grades);
179
+ const smallestFor = (len, r) => smallestGradeFor(spec, size, len, r, grades);
180
+
181
+ const narrow = ratio.narrow;
182
+ // 한글이 들어갈 수 없는 필드는 두 판정 모두 좁은 글자폭으로 계산한다
183
+ const wide = spec.korean ? ratio.wide : ratio.narrow;
184
+
185
+ const rawLen = literal(node, spec.lengthProp);
186
+ if (rawLen === null) return; // 변수로 넘긴 상한 — 판단 불가
187
+ const len =
188
+ rawLen === undefined
189
+ ? undefined
190
+ : spec.lengthProp === "max"
191
+ ? digitsOf(rawLen)
192
+ : rawLen;
193
+ if (len !== undefined && (!Number.isFinite(len) || len <= 0)) return;
194
+
195
+ const rawWidth = literal(node, "width");
196
+ if (rawWidth === null) return; // 변수로 넘긴 폭 — 판단 불가
197
+
198
+ const reportNode = attr(node, "width") ?? attr(node, spec.lengthProp) ?? node.name;
199
+ const gradeList = spec.grades.map(g => `${g}(${grades[g]})`).join(" · ");
200
+
201
+ /* ── width 가 없다 ── */
202
+ if (rawWidth === undefined) {
203
+ if (len === undefined) return; // 둘 다 없음 = 상한 없는 필드 → 행 전체가 맞다
204
+ const suggest = smallestFor(len, wide);
205
+ if (!suggest) return; // xl 로도 안 담긴다 → 행 전체가 맞다
206
+ context.report({
207
+ node: reportNode,
208
+ messageId: "noGrade",
209
+ data: { lengthProp: spec.lengthProp, len: rawLen, suggest, suggestPx: grades[suggest] },
210
+ });
211
+ return;
212
+ }
213
+
214
+ /* ── width="100%" ── */
215
+ if (rawWidth === "100%") {
216
+ if (len === undefined) return; // 상한 없음 + 행 전체 = 규칙대로다
217
+ const suggest = smallestFor(len, wide);
218
+ if (!suggest) return; // xl 초과 → 행 전체가 맞다
219
+ context.report({
220
+ node: reportNode,
221
+ messageId: "fullWidthBounded",
222
+ data: { lengthProp: spec.lengthProp, len: rawLen, suggest, suggestPx: grades[suggest] },
223
+ });
224
+ return;
225
+ }
226
+
227
+ /* ── width 가 등급 값인가 ── */
228
+ const px = typeof rawWidth === "number" ? rawWidth : /^(\d+)px$/.exec(String(rawWidth))?.[1];
229
+ const grade = px != null && spec.grades.find(g => grades[g] === Number(px));
230
+ if (!grade) {
231
+ context.report({
232
+ node: reportNode,
233
+ messageId: "offGrade",
234
+ data: { width: String(rawWidth), gradeList },
235
+ });
236
+ return;
237
+ }
238
+
239
+ /* ── 등급은 맞다. 상한과 맞는가 ── */
240
+ if (len === undefined) {
241
+ context.report({
242
+ node: reportNode,
243
+ messageId: "noBound",
244
+ data: { lengthProp: spec.lengthProp },
245
+ });
246
+ return;
247
+ }
248
+
249
+ // 영문·숫자로 채워도 넘치면 등급이 확실히 좁다
250
+ if (capacityAt(grade, narrow) < len) {
251
+ const suggest = smallestFor(len, narrow);
252
+ if (!suggest) {
253
+ context.report({
254
+ node: reportNode,
255
+ messageId: "overMax",
256
+ data: { lengthProp: spec.lengthProp, len: rawLen, xl: grades.xl },
257
+ });
258
+ return;
259
+ }
260
+ context.report({
261
+ node: reportNode,
262
+ messageId: "tooNarrow",
263
+ data: {
264
+ lengthProp: spec.lengthProp,
265
+ len: rawLen,
266
+ grade,
267
+ gradePx: grades[grade],
268
+ suggest,
269
+ suggestPx: grades[suggest],
270
+ },
271
+ });
272
+ return;
273
+ }
274
+
275
+ // 한글로 채워도 더 작은 등급에 들어가면 등급이 확실히 과하다
276
+ const enough = smallestFor(len, wide);
277
+ if (enough && grades[enough] < grades[grade]) {
278
+ context.report({
279
+ node: reportNode,
280
+ messageId: "tooWide",
281
+ data: {
282
+ lengthProp: spec.lengthProp,
283
+ len: rawLen,
284
+ grade,
285
+ gradePx: grades[grade],
286
+ suggest: enough,
287
+ suggestPx: grades[enough],
288
+ },
289
+ });
290
+ }
291
+ },
292
+ };
293
+ },
294
+ };
@@ -13,4 +13,4 @@ export const RADIUS = ["2","4","6","8","full","control-sm","control-md","feedbac
13
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-heading-xxs","typo-item-md-default","typo-item-md-selected","typo-item-sm-default","typo-item-sm-selected","typo-navigation-lg-medium","typo-navigation-lg-semibold","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
14
 
15
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-bold","border-danger","border-default","border-strong","border-success","border-warning","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"];
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-bold","border-danger","border-default","border-strong","border-success","border-warning","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-successLight","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": "8.1.0",
3
+ "version": "9.0.0-beta.10",
4
4
  "description": "Sellmate Design System — React (TypeScript + Tailwind v4) port",
5
5
  "license": "UNLICENSED",
6
6
  "keywords": [
@@ -70,8 +70,10 @@
70
70
  "check:docs": "node scripts/check-docs-coverage.mjs",
71
71
  "check:docs-refs": "node scripts/check-docs-refs.mjs",
72
72
  "check:routing": "node scripts/check-routing.mjs",
73
- "version": "node scripts/version.mjs",
74
- "version:dry": "node scripts/version.mjs --dry-run",
73
+ "version": "node scripts/version.mjs --prerelease beta --tag",
74
+ "version:dry": "node scripts/version.mjs --prerelease beta --dry-run",
75
+ "version:stable": "node scripts/version.mjs --tag",
76
+ "version:stable:dry": "node scripts/version.mjs --dry-run",
75
77
  "clean": "rm -rf dist storybook-static"
76
78
  },
77
79
  "peerDependencies": {
@@ -117,6 +119,7 @@
117
119
  "vite": "^8.0.0"
118
120
  },
119
121
  "publishConfig": {
120
- "access": "public"
122
+ "access": "public",
123
+ "tag": "beta"
121
124
  }
122
125
  }