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
package/dist/styles.css CHANGED
@@ -31,6 +31,9 @@
31
31
  --text-base--line-height: calc(1.5 / 1);
32
32
  --text-xl: 1.25rem;
33
33
  --text-xl--line-height: calc(1.75 / 1.25);
34
+ --font-weight-thin: 100;
35
+ --font-weight-extralight: 200;
36
+ --font-weight-light: 300;
34
37
  --font-weight-normal: 400;
35
38
  --font-weight-medium: 500;
36
39
  --font-weight-semibold: 600;
@@ -669,6 +672,9 @@
669
672
  .top-\[16px\] {
670
673
  top: 16px;
671
674
  }
675
+ .top-\[64px\] {
676
+ top: 64px;
677
+ }
672
678
  .top-\[var\(--cmp-table-header-height\)\] {
673
679
  top: var(--cmp-table-header-height);
674
680
  }
@@ -747,6 +753,9 @@
747
753
  .z-\[31\] {
748
754
  z-index: 31;
749
755
  }
756
+ .z-\[60\] {
757
+ z-index: 60;
758
+ }
750
759
  .z-\[1000\] {
751
760
  z-index: 1000;
752
761
  }
@@ -789,6 +798,9 @@
789
798
  .mx-3 {
790
799
  margin-inline: calc(var(--spacing) * 3);
791
800
  }
801
+ .-mt-8 {
802
+ margin-top: calc(var(--spacing-8) * -1);
803
+ }
792
804
  .-mt-px {
793
805
  margin-top: -1px;
794
806
  }
@@ -801,6 +813,9 @@
801
813
  .mt-8 {
802
814
  margin-top: var(--spacing-8);
803
815
  }
816
+ .mt-9 {
817
+ margin-top: calc(var(--spacing) * 9);
818
+ }
804
819
  .mt-12 {
805
820
  margin-top: var(--spacing-12);
806
821
  }
@@ -973,6 +988,12 @@
973
988
  .h-\[var\(--cmp-textinput-sm-height\)\] {
974
989
  height: var(--cmp-textinput-sm-height);
975
990
  }
991
+ .h-\[var\(--sys-size-\.\.\.\)\] {
992
+ height: var(--sys-size-...);
993
+ }
994
+ .h-\[var\(--sys-size-control-md-height\)\] {
995
+ height: var(--sys-size-control-md-height);
996
+ }
976
997
  .h-auto {
977
998
  height: auto;
978
999
  }
@@ -1108,6 +1129,9 @@
1108
1129
  .w-\[var\(--cmp-pagination-height\)\] {
1109
1130
  width: var(--cmp-pagination-height);
1110
1131
  }
1132
+ .w-\[var\(--sys-size-control-md-height\)\] {
1133
+ width: var(--sys-size-control-md-height);
1134
+ }
1111
1135
  .w-fit {
1112
1136
  width: fit-content;
1113
1137
  }
@@ -1126,6 +1150,9 @@
1126
1150
  .max-w-\[420px\] {
1127
1151
  max-width: 420px;
1128
1152
  }
1153
+ .max-w-\[1200px\] {
1154
+ max-width: 1200px;
1155
+ }
1129
1156
  .max-w-full {
1130
1157
  max-width: 100%;
1131
1158
  }
@@ -1266,6 +1293,9 @@
1266
1293
  .grid-cols-7 {
1267
1294
  grid-template-columns: repeat(7, minmax(0, 1fr));
1268
1295
  }
1296
+ .grid-cols-\[200px_1fr\] {
1297
+ grid-template-columns: 200px 1fr;
1298
+ }
1269
1299
  .grid-cols-\[repeat\(7\,var\(--cmp-datepicker-calendar-day-width\)\)\] {
1270
1300
  grid-template-columns: repeat(7,var(--cmp-datepicker-calendar-day-width));
1271
1301
  }
@@ -1311,6 +1341,9 @@
1311
1341
  .justify-start {
1312
1342
  justify-content: flex-start;
1313
1343
  }
1344
+ .\!gap-12 {
1345
+ gap: var(--spacing-12) !important;
1346
+ }
1314
1347
  .gap-\(--cmp-chipInput-chip-gap\) {
1315
1348
  gap: var(--cmp-chipInput-chip-gap);
1316
1349
  }
@@ -1338,12 +1371,18 @@
1338
1371
  .gap-4 {
1339
1372
  gap: var(--spacing-4);
1340
1373
  }
1374
+ .gap-7 {
1375
+ gap: calc(var(--spacing) * 7);
1376
+ }
1341
1377
  .gap-8 {
1342
1378
  gap: var(--spacing-8);
1343
1379
  }
1344
1380
  .gap-12 {
1345
1381
  gap: var(--spacing-12);
1346
1382
  }
1383
+ .gap-13 {
1384
+ gap: calc(var(--spacing) * 13);
1385
+ }
1347
1386
  .gap-16 {
1348
1387
  gap: var(--spacing-16);
1349
1388
  }
@@ -1365,6 +1404,9 @@
1365
1404
  .gap-\[12px\] {
1366
1405
  gap: 12px;
1367
1406
  }
1407
+ .gap-\[13px\] {
1408
+ gap: 13px;
1409
+ }
1368
1410
  .gap-\[16px\] {
1369
1411
  gap: 16px;
1370
1412
  }
@@ -1548,6 +1590,9 @@
1548
1590
  .rounded-\[4px\] {
1549
1591
  border-radius: 4px;
1550
1592
  }
1593
+ .rounded-\[5px\] {
1594
+ border-radius: 5px;
1595
+ }
1551
1596
  .rounded-\[6px\] {
1552
1597
  border-radius: 6px;
1553
1598
  }
@@ -1611,10 +1656,18 @@
1611
1656
  .rounded-none {
1612
1657
  border-radius: 0;
1613
1658
  }
1659
+ .rounded-t {
1660
+ border-top-left-radius: 0.25rem;
1661
+ border-top-right-radius: 0.25rem;
1662
+ }
1614
1663
  .rounded-t-none {
1615
1664
  border-top-left-radius: 0;
1616
1665
  border-top-right-radius: 0;
1617
1666
  }
1667
+ .rounded-l {
1668
+ border-top-left-radius: 0.25rem;
1669
+ border-bottom-left-radius: 0.25rem;
1670
+ }
1618
1671
  .rounded-l-\[4px\] {
1619
1672
  border-top-left-radius: 4px;
1620
1673
  border-bottom-left-radius: 4px;
@@ -1627,6 +1680,13 @@
1627
1680
  border-top-left-radius: var(--cmp-select-sm-radius);
1628
1681
  border-bottom-left-radius: var(--cmp-select-sm-radius);
1629
1682
  }
1683
+ .rounded-tl {
1684
+ border-top-left-radius: 0.25rem;
1685
+ }
1686
+ .rounded-r {
1687
+ border-top-right-radius: 0.25rem;
1688
+ border-bottom-right-radius: 0.25rem;
1689
+ }
1630
1690
  .rounded-r-\[4px\] {
1631
1691
  border-top-right-radius: 4px;
1632
1692
  border-bottom-right-radius: 4px;
@@ -1639,6 +1699,13 @@
1639
1699
  border-top-right-radius: var(--cmp-select-sm-radius);
1640
1700
  border-bottom-right-radius: var(--cmp-select-sm-radius);
1641
1701
  }
1702
+ .rounded-tr {
1703
+ border-top-right-radius: 0.25rem;
1704
+ }
1705
+ .rounded-b {
1706
+ border-bottom-right-radius: 0.25rem;
1707
+ border-bottom-left-radius: 0.25rem;
1708
+ }
1642
1709
  .rounded-b-\[8px\] {
1643
1710
  border-bottom-right-radius: 8px;
1644
1711
  border-bottom-left-radius: 8px;
@@ -1647,6 +1714,12 @@
1647
1714
  border-bottom-right-radius: var(--cmp-table-radius);
1648
1715
  border-bottom-left-radius: var(--cmp-table-radius);
1649
1716
  }
1717
+ .rounded-br {
1718
+ border-bottom-right-radius: 0.25rem;
1719
+ }
1720
+ .rounded-bl {
1721
+ border-bottom-left-radius: 0.25rem;
1722
+ }
1650
1723
  .border {
1651
1724
  border-style: var(--tw-border-style);
1652
1725
  border-width: 1px;
@@ -1799,6 +1872,18 @@
1799
1872
  .border-\[var\(--sys-color-border-default\)\] {
1800
1873
  border-color: var(--sys-color-border-default);
1801
1874
  }
1875
+ .border-border-default {
1876
+ border-color: var(--color-border-default);
1877
+ }
1878
+ .border-border-strong {
1879
+ border-color: var(--color-border-strong);
1880
+ }
1881
+ .border-disabled-border {
1882
+ border-color: var(--color-disabled-border);
1883
+ }
1884
+ .border-divider-default {
1885
+ border-color: var(--color-divider-default);
1886
+ }
1802
1887
  .border-gray-200 {
1803
1888
  border-color: var(--color-gray-200);
1804
1889
  }
@@ -1862,9 +1947,15 @@
1862
1947
  .bg-\(--sys-color-bg-neutralLight\) {
1863
1948
  background-color: var(--sys-color-bg-neutralLight);
1864
1949
  }
1950
+ .bg-\[\#\.\.\.\] {
1951
+ background-color: #...;
1952
+ }
1865
1953
  .bg-\[\#F2F2F2\] {
1866
1954
  background-color: #F2F2F2;
1867
1955
  }
1956
+ .bg-\[\#eee\] {
1957
+ background-color: #eee;
1958
+ }
1868
1959
  .bg-\[var\(--btn-bg\)\] {
1869
1960
  background-color: var(--btn-bg);
1870
1961
  }
@@ -1964,6 +2055,18 @@
1964
2055
  .bg-\[var\(--sys-color-divider-default\)\] {
1965
2056
  background-color: var(--sys-color-divider-default);
1966
2057
  }
2058
+ .bg-bg-frame {
2059
+ background-color: var(--color-bg-frame);
2060
+ }
2061
+ .bg-bg-neutralBright {
2062
+ background-color: var(--color-bg-neutralBright);
2063
+ }
2064
+ .bg-bg-neutralLight {
2065
+ background-color: var(--color-bg-neutralLight);
2066
+ }
2067
+ .bg-bg-screen {
2068
+ background-color: var(--color-bg-screen);
2069
+ }
1967
2070
  .bg-black\/5 {
1968
2071
  background-color: color-mix(in srgb, #000000 5%, transparent);
1969
2072
  @supports (color: color-mix(in lab, red, red)) {
@@ -1976,6 +2079,12 @@
1976
2079
  background-color: color-mix(in oklab, var(--color-black) 40%, transparent);
1977
2080
  }
1978
2081
  }
2082
+ .bg-blue-subtle {
2083
+ background-color: var(--color-blue-subtle);
2084
+ }
2085
+ .bg-disabled-bg {
2086
+ background-color: var(--color-disabled-bg);
2087
+ }
1979
2088
  .bg-gray-200 {
1980
2089
  background-color: var(--color-gray-200);
1981
2090
  }
@@ -2024,6 +2133,12 @@
2024
2133
  .p-12 {
2025
2134
  padding: var(--spacing-12);
2026
2135
  }
2136
+ .p-15 {
2137
+ padding: calc(var(--spacing) * 15);
2138
+ }
2139
+ .p-16 {
2140
+ padding: var(--spacing-16);
2141
+ }
2027
2142
  .p-20 {
2028
2143
  padding: var(--spacing-20);
2029
2144
  }
@@ -2036,6 +2151,9 @@
2036
2151
  .p-\[8px\] {
2037
2152
  padding: 8px;
2038
2153
  }
2154
+ .p-\[10px\] {
2155
+ padding: 10px;
2156
+ }
2039
2157
  .p-\[12px\] {
2040
2158
  padding: 12px;
2041
2159
  }
@@ -2063,6 +2181,9 @@
2063
2181
  .px-8 {
2064
2182
  padding-inline: var(--spacing-8);
2065
2183
  }
2184
+ .px-11 {
2185
+ padding-inline: calc(var(--spacing) * 11);
2186
+ }
2066
2187
  .px-12 {
2067
2188
  padding-inline: var(--spacing-12);
2068
2189
  }
@@ -2304,6 +2425,9 @@
2304
2425
  .text-16 {
2305
2426
  font-size: var(--text-16);
2306
2427
  }
2428
+ .text-18 {
2429
+ font-size: var(--text-18);
2430
+ }
2307
2431
  .text-20 {
2308
2432
  font-size: var(--text-20);
2309
2433
  }
@@ -2383,10 +2507,30 @@
2383
2507
  --tw-leading: 1;
2384
2508
  line-height: 1;
2385
2509
  }
2510
+ .font-\[600\] {
2511
+ --tw-font-weight: 600;
2512
+ font-weight: 600;
2513
+ }
2514
+ .font-black {
2515
+ --tw-font-weight: var(--font-weight-black);
2516
+ font-weight: var(--font-weight-black);
2517
+ }
2386
2518
  .font-bold {
2387
2519
  --tw-font-weight: var(--font-weight-bold);
2388
2520
  font-weight: var(--font-weight-bold);
2389
2521
  }
2522
+ .font-extrabold {
2523
+ --tw-font-weight: var(--font-weight-extrabold);
2524
+ font-weight: var(--font-weight-extrabold);
2525
+ }
2526
+ .font-extralight {
2527
+ --tw-font-weight: var(--font-weight-extralight);
2528
+ font-weight: var(--font-weight-extralight);
2529
+ }
2530
+ .font-light {
2531
+ --tw-font-weight: var(--font-weight-light);
2532
+ font-weight: var(--font-weight-light);
2533
+ }
2390
2534
  .font-medium {
2391
2535
  --tw-font-weight: var(--font-weight-medium);
2392
2536
  font-weight: var(--font-weight-medium);
@@ -2399,6 +2543,10 @@
2399
2543
  --tw-font-weight: var(--font-weight-semibold);
2400
2544
  font-weight: var(--font-weight-semibold);
2401
2545
  }
2546
+ .font-thin {
2547
+ --tw-font-weight: var(--font-weight-thin);
2548
+ font-weight: var(--font-weight-thin);
2549
+ }
2402
2550
  .tracking-wider {
2403
2551
  --tw-tracking: var(--tracking-wider);
2404
2552
  letter-spacing: var(--tracking-wider);
@@ -2487,6 +2635,9 @@
2487
2635
  .text-\[\#0A0A0A\] {
2488
2636
  color: #0A0A0A;
2489
2637
  }
2638
+ .text-\[\#333\] {
2639
+ color: #333;
2640
+ }
2490
2641
  .text-\[\#666\] {
2491
2642
  color: #666;
2492
2643
  }
@@ -2610,6 +2761,30 @@
2610
2761
  .text-blue-strong {
2611
2762
  color: var(--color-blue-strong);
2612
2763
  }
2764
+ .text-fg-accent {
2765
+ color: var(--color-fg-accent);
2766
+ }
2767
+ .text-fg-danger {
2768
+ color: var(--color-fg-danger);
2769
+ }
2770
+ .text-fg-inverse {
2771
+ color: var(--color-fg-inverse);
2772
+ }
2773
+ .text-fg-primary {
2774
+ color: var(--color-fg-primary);
2775
+ }
2776
+ .text-fg-secondary {
2777
+ color: var(--color-fg-secondary);
2778
+ }
2779
+ .text-fg-success {
2780
+ color: var(--color-fg-success);
2781
+ }
2782
+ .text-fg-tertiary {
2783
+ color: var(--color-fg-tertiary);
2784
+ }
2785
+ .text-fg-warning {
2786
+ color: var(--color-fg-warning);
2787
+ }
2613
2788
  .text-gray-400 {
2614
2789
  color: var(--color-gray-400);
2615
2790
  }
@@ -2643,6 +2818,9 @@
2643
2818
  .text-inherit {
2644
2819
  color: inherit;
2645
2820
  }
2821
+ .text-link-accent {
2822
+ color: var(--color-link-accent);
2823
+ }
2646
2824
  .text-red-75 {
2647
2825
  color: var(--color-red-75);
2648
2826
  }
@@ -2711,6 +2889,10 @@
2711
2889
  --tw-shadow: 0px 0px 24px -6px var(--tw-shadow-color, rgba(34,34,34,0.12)), 4px 8px 16px -4px var(--tw-shadow-color, rgba(34,34,34,0.1));
2712
2890
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2713
2891
  }
2892
+ .ring {
2893
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
2894
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2895
+ }
2714
2896
  .outline {
2715
2897
  outline-style: var(--tw-outline-style);
2716
2898
  outline-width: 1px;
@@ -2803,6 +2985,9 @@
2803
2985
  .\[font\:inherit\] {
2804
2986
  font: inherit;
2805
2987
  }
2988
+ .\[length\:var\(--x\)\] {
2989
+ length: var(--x);
2990
+ }
2806
2991
  .group-hover\/filepicker\:text-\[color\:var\(--cmp-filepicker-text-default\)\] {
2807
2992
  &:is(:where(.group\/filepicker):hover *) {
2808
2993
  @media (hover: hover) {
@@ -3051,6 +3236,13 @@
3051
3236
  }
3052
3237
  }
3053
3238
  }
3239
+ .hover\:gap-16 {
3240
+ &:hover {
3241
+ @media (hover: hover) {
3242
+ gap: var(--spacing-16);
3243
+ }
3244
+ }
3245
+ }
3054
3246
  .hover\:\!border-\[color\:var\(--cmp-radioButton-bg-hover\)\] {
3055
3247
  &:hover {
3056
3248
  @media (hover: hover) {
@@ -3551,6 +3743,20 @@
3551
3743
  transition-property: none;
3552
3744
  }
3553
3745
  }
3746
+ .md\:px-24 {
3747
+ @media (width >= 48rem) {
3748
+ padding-inline: var(--spacing-24);
3749
+ }
3750
+ }
3751
+ .hover\:md\:gap-12 {
3752
+ &:hover {
3753
+ @media (hover: hover) {
3754
+ @media (width >= 48rem) {
3755
+ gap: var(--spacing-12);
3756
+ }
3757
+ }
3758
+ }
3759
+ }
3554
3760
  .\[\&\:\:-webkit-scrollbar\]\:hidden {
3555
3761
  &::-webkit-scrollbar {
3556
3762
  display: none;
@@ -4713,6 +4919,9 @@
4713
4919
  --cmp-tree-connector-line-color: #AAAAAA;
4714
4920
  }
4715
4921
  @layer base {
4922
+ body {
4923
+ color: var(--color-fg-primary);
4924
+ }
4716
4925
  *::-webkit-scrollbar {
4717
4926
  width: 12px;
4718
4927
  height: 12px;
package/dist/theme.css CHANGED
@@ -1439,6 +1439,14 @@
1439
1439
  Chrome 121+/WebKit 는 scrollbar-width·scrollbar-color 계산값이 auto 가 아니면 ::-webkit-scrollbar-* 를 통째로
1440
1440
  무시하므로, 표준 속성은 webkit 유사요소가 없는 브라우저(Firefox)에서만 켠다. */
1441
1441
  @layer base {
1442
+ /* 전역 기본 텍스트 색 — 본문은 grey_95(= fg.primary). 요소마다 색을 지정하지 않아도
1443
+ 브라우저 기본 검정(#000)이 아니라 디자인 시스템 색으로 렌더된다.
1444
+ 보조·비활성 텍스트만 text-fg-tertiary(grey_65)로 내려 쓴다.
1445
+ base 레이어라 소비 앱의 text-* 유틸리티(utilities)가 언제든 덮어쓴다. */
1446
+ body {
1447
+ color: var(--color-fg-primary);
1448
+ }
1449
+
1442
1450
  *::-webkit-scrollbar {
1443
1451
  width: 12px;
1444
1452
  height: 12px;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * sellmate-design-system-react — 소비 앱용 ESLint 플러그인.
3
+ *
4
+ * AGENTS.md 의 규칙 중 정적으로 검출 가능한 것을 린트로 강제한다.
5
+ * 사용법은 README 의 "ESLint 설정" 절 참조.
6
+ */
7
+ import noRawHtmlControl from "./rules/no-raw-html-control.mjs";
8
+ import noArbitraryClass from "./rules/no-arbitrary-class.mjs";
9
+ import noOffScaleSpacing from "./rules/no-off-scale-spacing.mjs";
10
+ import preferTypoPreset from "./rules/prefer-typo-preset.mjs";
11
+ import tableNumericAlign from "./rules/table-numeric-align.mjs";
12
+ import requireLocaleNumber from "./rules/require-locale-number.mjs";
13
+
14
+ const PLUGIN_NAME = "sellmate";
15
+
16
+ const plugin = {
17
+ meta: { name: "eslint-plugin-sellmate" },
18
+ rules: {
19
+ "no-raw-html-control": noRawHtmlControl,
20
+ "no-arbitrary-class": noArbitraryClass,
21
+ "no-off-scale-spacing": noOffScaleSpacing,
22
+ "prefer-typo-preset": preferTypoPreset,
23
+ "table-numeric-align": tableNumericAlign,
24
+ "require-locale-number": requireLocaleNumber,
25
+ },
26
+ };
27
+
28
+ /** 기본 프리셋 — JSX/TSX 파일에 적용 */
29
+ const recommended = [
30
+ {
31
+ // 컬럼 정의를 .ts 파일로 분리하는 경우가 있어 tsx 뿐 아니라 ts 도 포함한다
32
+ files: ["**/*.{js,jsx,ts,tsx}"],
33
+ plugins: { [PLUGIN_NAME]: plugin },
34
+ rules: {
35
+ // 스타일이 조용히 사라지거나 디자인 시스템을 우회하는 경우 → error
36
+ [`${PLUGIN_NAME}/no-raw-html-control`]: "error",
37
+ [`${PLUGIN_NAME}/no-arbitrary-class`]: "error",
38
+ [`${PLUGIN_NAME}/no-off-scale-spacing`]: "error",
39
+ [`${PLUGIN_NAME}/table-numeric-align`]: "error",
40
+ [`${PLUGIN_NAME}/require-locale-number`]: "error",
41
+ // 대체 프리셋 판단에 여지가 있어 warn
42
+ [`${PLUGIN_NAME}/prefer-typo-preset`]: "warn",
43
+ },
44
+ },
45
+ ];
46
+
47
+ /**
48
+ * 전 규칙 error + ul·ol·li·svg·label 까지 검사.
49
+ * 정당한 사용처(로고 SVG, 시맨틱 목록 등)가 있는 요소도 잡히므로
50
+ * 예외는 `allow` 로 열어 쓰는 것을 전제로 한다. 신규 프로젝트용.
51
+ */
52
+ const strict = [
53
+ {
54
+ ...recommended[0],
55
+ rules: {
56
+ ...recommended[0].rules,
57
+ [`${PLUGIN_NAME}/no-raw-html-control`]: ["error", { strict: true }],
58
+ [`${PLUGIN_NAME}/prefer-typo-preset`]: "error",
59
+ },
60
+ },
61
+ ];
62
+
63
+ plugin.configs = { recommended, strict };
64
+
65
+ export default plugin;
66
+ export const { rules, configs } = plugin;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * className / class 속성에서 클래스 토큰을 추출하는 공용 헬퍼.
3
+ *
4
+ * 지원 형태:
5
+ * className="a b" → Literal
6
+ * className={`a ${x} b`} → TemplateLiteral (정적 quasi 부분만)
7
+ * className={cn('a', cond && 'b')} → clsx/cn/classnames 호출의 문자열 인자
8
+ * className={'a b'} → JSXExpressionContainer > Literal
9
+ *
10
+ * 동적으로 조립되는 부분(변수·삼항의 식별자 등)은 정적 분석 대상이 아니므로 건너뛴다.
11
+ */
12
+
13
+ const CLASS_ATTRS = new Set(["className", "class"]);
14
+ const CLASS_HELPERS = new Set(["cn", "clsx", "classNames", "classnames", "cva", "twMerge"]);
15
+
16
+ /** 문자열을 공백 기준 클래스 토큰으로 나눈다 (variant 접두어는 유지) */
17
+ export function tokenize(value) {
18
+ return String(value)
19
+ .split(/\s+/)
20
+ .filter(Boolean);
21
+ }
22
+
23
+ /**
24
+ * `hover:md:gap-12` → `gap-12` — variant 접두어를 벗겨 유틸리티 본체만 남긴다.
25
+ * 임의 값 안의 `:`(예: `[length:var(--x)]`)는 벗기지 않는다.
26
+ */
27
+ export function stripVariants(token) {
28
+ let rest = token;
29
+ for (;;) {
30
+ const idx = indexOfTopLevelColon(rest);
31
+ if (idx === -1) return rest;
32
+ rest = rest.slice(idx + 1);
33
+ }
34
+ }
35
+
36
+ /** 대괄호 밖에 있는 첫 `:` 위치 (없으면 -1) */
37
+ function indexOfTopLevelColon(s) {
38
+ let depth = 0;
39
+ for (let i = 0; i < s.length; i++) {
40
+ const c = s[i];
41
+ if (c === "[") depth++;
42
+ else if (c === "]") depth--;
43
+ else if (c === ":" && depth === 0) return i;
44
+ }
45
+ return -1;
46
+ }
47
+
48
+ /** `!gap-12` / `-mt-8` 같은 접두 기호 제거 */
49
+ export function stripModifiers(token) {
50
+ return token.replace(/^!/, "").replace(/^-/, "");
51
+ }
52
+
53
+ /**
54
+ * JSXAttribute 노드에서 정적으로 알 수 있는 클래스 토큰을 모두 수집한다.
55
+ * @returns {Array<{ token: string, node: import('estree').Node }>}
56
+ */
57
+ export function collectClassTokens(attrNode) {
58
+ if (attrNode.type !== "JSXAttribute") return [];
59
+ const name = attrNode.name?.name;
60
+ if (!CLASS_ATTRS.has(name)) return [];
61
+
62
+ const out = [];
63
+ const push = (raw, node) => {
64
+ for (const token of tokenize(raw)) out.push({ token, node });
65
+ };
66
+
67
+ const visit = (node) => {
68
+ if (!node) return;
69
+ switch (node.type) {
70
+ case "Literal":
71
+ if (typeof node.value === "string") push(node.value, node);
72
+ break;
73
+ case "TemplateLiteral":
74
+ // 정적 부분만 본다. 보간(${}) 안쪽은 아래 표현식 방문으로 따로 처리.
75
+ for (const q of node.quasis) push(q.value.cooked ?? "", node);
76
+ for (const e of node.expressions) visit(e);
77
+ break;
78
+ case "JSXExpressionContainer":
79
+ visit(node.expression);
80
+ break;
81
+ case "CallExpression": {
82
+ const callee = node.callee;
83
+ const fnName = callee?.type === "Identifier" ? callee.name : callee?.property?.name;
84
+ if (fnName && CLASS_HELPERS.has(fnName)) {
85
+ for (const arg of node.arguments) visit(arg);
86
+ }
87
+ break;
88
+ }
89
+ case "ConditionalExpression":
90
+ visit(node.consequent);
91
+ visit(node.alternate);
92
+ break;
93
+ case "LogicalExpression":
94
+ visit(node.left);
95
+ visit(node.right);
96
+ break;
97
+ case "ArrayExpression":
98
+ for (const el of node.elements) visit(el);
99
+ break;
100
+ case "ObjectExpression":
101
+ // clsx({ 'gap-12': cond }) 형태 — 키가 클래스다
102
+ for (const prop of node.properties) {
103
+ if (prop.type === "Property" && prop.key) {
104
+ if (prop.key.type === "Literal" && typeof prop.key.value === "string") {
105
+ push(prop.key.value, prop.key);
106
+ } else if (prop.key.type === "Identifier" && !prop.computed) {
107
+ push(prop.key.name, prop.key);
108
+ }
109
+ }
110
+ }
111
+ break;
112
+ default:
113
+ break;
114
+ }
115
+ };
116
+
117
+ visit(attrNode.value);
118
+ return out;
119
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * STableColumn 형태 판별과 "숫자 컬럼" 판정을 공유하는 헬퍼.
3
+ * table-numeric-align · require-locale-number 두 규칙이 함께 쓴다.
4
+ */
5
+
6
+ /**
7
+ * 오른쪽 정렬 대상 — 숫자가 들어가는 모든 컬럼.
8
+ * 식별자(주문번호·상품코드)도 숫자라 자릿수를 맞춰야 하므로 포함한다.
9
+ */
10
+ export const NUMERIC_LABEL =
11
+ /금액|가격|단가|판매가|원가|비용|수수료|배송비|할인|합계|총액|잔액|포인트|적립|수량|재고|개수|건수|횟수|비율|퍼센트|평점|점수|무게|용량|길이|면적|번호|코드|순번|순위|나이|연차/;
12
+
13
+ /**
14
+ * 하이픈 등 서식이 들어간 문자열 — 숫자가 아니므로 정렬·서식 규칙 모두 대상이 아니다.
15
+ * (전화번호 010-1234-5678, 사업자번호 123-45-67890)
16
+ */
17
+ export const IDENTIFIER_ONLY = /전화|휴대폰|연락처|사업자|주민|계좌|우편/;
18
+
19
+ /** format 본문에 숫자 서식이 있으면 숫자 컬럼으로 본다 */
20
+ export const NUMERIC_FORMAT = /toLocaleString|toFixed|Number\s*\(|parseInt|parseFloat/;
21
+
22
+ /** 객체 리터럴에서 프로퍼티 노드를 이름으로 찾는다 */
23
+ export function prop(objectNode, name) {
24
+ return objectNode.properties.find(
25
+ (p) =>
26
+ p.type === "Property" &&
27
+ !p.computed &&
28
+ ((p.key.type === "Identifier" && p.key.name === name) ||
29
+ (p.key.type === "Literal" && p.key.value === name)),
30
+ );
31
+ }
32
+
33
+ /** 문자열 리터럴 값 (아니면 undefined) */
34
+ export function literalString(node) {
35
+ return node?.value?.type === "Literal" && typeof node.value.value === "string"
36
+ ? node.value.value
37
+ : undefined;
38
+ }
39
+
40
+ /**
41
+ * STableColumn 형태인지 — name·label·field 셋을 모두 가진 객체만 컬럼으로 본다.
42
+ * @returns {{ nameProp, labelProp, label: string } | null}
43
+ */
44
+ export function asColumn(node) {
45
+ const nameProp = prop(node, "name");
46
+ const labelProp = prop(node, "label");
47
+ if (!nameProp || !labelProp || !prop(node, "field")) return null;
48
+
49
+ const label = literalString(labelProp);
50
+ if (label === undefined) return null; // 동적 label 은 판단하지 않는다
51
+
52
+ return { nameProp, labelProp, label, columnName: literalString(nameProp) };
53
+ }