@conscience-technology/ctds 0.3.4 → 0.4.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 (37) hide show
  1. package/assets/components/Button/Button.module.css +2 -2
  2. package/assets/components/EventListItem/EventListItem.module.css +220 -0
  3. package/assets/components/EventListItem/EventListItem.tsx +205 -0
  4. package/assets/components/EventListItem/index.ts +8 -0
  5. package/assets/components/Icon/Icon.tsx +80 -41
  6. package/assets/components/ListItem/ListItem.module.css +91 -211
  7. package/assets/components/ListItem/ListItem.tsx +130 -154
  8. package/assets/components/ListItem/index.ts +2 -2
  9. package/assets/components/MetricCard/MetricCard.module.css +5 -5
  10. package/assets/components/Sidebar/Sidebar.module.css +81 -4
  11. package/assets/components/Sidebar/Sidebar.tsx +11 -2
  12. package/assets/components/SplitButton/SplitButton.module.css +3 -3
  13. package/assets/components/Stepper/Stepper.module.css +196 -52
  14. package/assets/components/Stepper/Stepper.tsx +68 -13
  15. package/assets/components/Switch/Switch.module.css +1 -9
  16. package/assets/components/Topbar/Topbar.module.css +33 -5
  17. package/assets/components/Topbar/Topbar.tsx +62 -7
  18. package/assets/components/_data.ts +11 -4
  19. package/assets/components/index.ts +2 -0
  20. package/assets/components/tokens.css +23 -10
  21. package/assets/prompts/ASSEMBLY.md +219 -0
  22. package/assets/prompts/PRINCIPLES.md +70 -0
  23. package/assets/prompts/axes/icon.md +7 -1
  24. package/assets/prompts/axes/palette.md +11 -0
  25. package/assets/prompts/components/_variants.md +5 -1
  26. package/assets/prompts/components/callout.md +3 -3
  27. package/assets/prompts/components/event-list-item.md +103 -0
  28. package/assets/prompts/components/generated-image.md +80 -0
  29. package/assets/prompts/components/list-item.md +182 -204
  30. package/assets/prompts/components/metric-card.md +2 -2
  31. package/assets/prompts/components/toast.md +1 -0
  32. package/assets/prompts/components/topbar.md +77 -59
  33. package/assets/prompts/themes/focus.md +72 -0
  34. package/assets/skills/ctds/SKILL.md +1 -0
  35. package/assets/skills/ctds-page/SKILL.md +12 -2
  36. package/assets/skills/ctds-wireframe/SKILL.md +35 -1
  37. package/package.json +2 -4
@@ -3,12 +3,13 @@
3
3
  /**
4
4
  * 의미 기반 Icon 컴포넌트.
5
5
  *
6
- * 설계 원칙 — "family별 독립 세트":
7
- * - 각 family(phosphor/solar/lucide)는 자기만의 아이콘 세트를 가진다.
8
- * - 서로 다른 라이브러리 간의 "의미 매핑"은 하지 않는다.
9
- * (search 하나를 phosphor·solar·lucide 세 군데에서 억지로 연결하지 않음)
10
- * - 활성 family에 해당 이름이 없으면 그 family 전용 placeholder 아이콘을 렌더.
11
- * (레이아웃 공간 유지 + 축 전환 시 "어떤 아이콘이 그 family엔 없다"는 사실이 시각적으로 드러남)
6
+ * 설계 원칙 — "phosphor 기준 전면 매핑":
7
+ * - phosphor를 기준 세트로 삼고, solar/lucide는 가능한 한 의미가 가장 가까운
8
+ * 아이콘으로 1:1 매핑한다. family 축을 바꿔도 "그림이 비는" 자리가 생기지 않도록.
9
+ * - 라이브러리에 적절한 분리 아이콘이 없으면 "수렴 매핑"을 허용한다
10
+ * (예: solar에 자동차 아이콘이 없음 → truck/car가 같은 Truck으로 수렴).
11
+ * 이건 라이브러리 한계라는 사실을 받아들이는 거지, 설계 결함이 아님.
12
+ * - 매핑 자체가 아예 없는 경우에만 family 전용 placeholder("빗금 사각형")를 렌더.
12
13
  *
13
14
  * weight(regular/fill) 반응:
14
15
  * phosphor → weight prop으로 반응
@@ -390,6 +391,33 @@ import {
390
391
  MedalRibbonStarLinear as SoMedalL, MedalRibbonStarBold as SoMedalB,
391
392
  CrownLinear as SoCrownL, CrownBold as SoCrownB,
392
393
  LeafLinear as SoLeafL, LeafBold as SoLeafB,
394
+ // --- 수렴 분리용 추가 ---
395
+ SmileCircleLinear as SoSmileyHappyL, SmileCircleBold as SoSmileyHappyB,
396
+ ConfoundedCircleLinear as SoSmileyMehL, ConfoundedCircleBold as SoSmileyMehB,
397
+ SadCircleLinear as SoSmileySadL, SadCircleBold as SoSmileySadB,
398
+ LockUnlockedLinear as SoLockOpenL, LockUnlockedBold as SoLockOpenB,
399
+ GarageLinear as SoFactoryL, GarageBold as SoFactoryB,
400
+ CompassBigLinear as SoNavigationL, CompassBigBold as SoNavigationB,
401
+ MinimizeSquareLinear as SoCornersInL, MinimizeSquareBold as SoCornersInB,
402
+ MaximizeSquareLinear as SoCornersOutL, MaximizeSquareBold as SoCornersOutB,
403
+ RefreshCircleLinear as SoArrowsClockwiseL, RefreshCircleBold as SoArrowsClockwiseB,
404
+ LinkMinimalisticLinear as SoLinkSimpleL, LinkMinimalisticBold as SoLinkSimpleB,
405
+ RoundAltArrowDownLinear as SoCaretCircleDownL, RoundAltArrowDownBold as SoCaretCircleDownB,
406
+ DoubleAltArrowUpLinear as SoArrowsVerticalL, DoubleAltArrowUpBold as SoArrowsVerticalB,
407
+ CheckSquareLinear as SoCheckSquareL, CheckSquareBold as SoCheckSquareB,
408
+ ShieldCheckLinear as SoSealCheckL, ShieldCheckBold as SoSealCheckB,
409
+ DocumentsLinear as SoCopySimpleL, DocumentsBold as SoCopySimpleB,
410
+ Widget6Linear as SoGridNineL, Widget6Bold as SoGridNineB,
411
+ Widget3Linear as SoTableL, Widget3Bold as SoTableB,
412
+ WidgetAddLinear as SoGridFourL, WidgetAddBold as SoGridFourB,
413
+ ChecklistLinear as SoTabsL, ChecklistBold as SoTabsB,
414
+ MenuDotsCircleLinear as SoDotsThreeOutlineL, MenuDotsCircleBold as SoDotsThreeOutlineB,
415
+ SortFromTopToBottomLinear as SoCaretUpDownAltL, SortFromTopToBottomBold as SoCaretUpDownAltB,
416
+ BanknoteLinear as SoCoinsL, BanknoteBold as SoCoinsB,
417
+ DocumentLinear as SoArticleL, DocumentBold as SoArticleB,
418
+ Planet2Linear as SoAirplaneAltL, Planet2Bold as SoAirplaneAltB,
419
+ HandHeartLinear as SoHandWavingL, HandHeartBold as SoHandWavingB,
420
+ SquareShareLineLinear as SoStepsL, SquareShareLineBold as SoStepsB,
393
421
  } from "solar-icon-set";
394
422
 
395
423
  // Lucide ──────────────────────────────────────────────────────────
@@ -584,6 +612,17 @@ import {
584
612
  Flower as LuFlower,
585
613
  Leaf as LuLeaf,
586
614
  Trees as LuTree,
615
+ // --- 수렴 분리용 추가 (아래 세트에서만 사용) ---
616
+ CalendarRange as LuCalendarBlank,
617
+ CalendarDays as LuCalendarDots,
618
+ MessagesSquare as LuChatCenteredDots,
619
+ Ellipsis as LuDotsThreeOutline,
620
+ Newspaper as LuArticle,
621
+ FolderClosed as LuFolderSimple,
622
+ BadgeCheck as LuSealBadge,
623
+ SquareDashed as LuPlaceholder,
624
+ Minimize as LuCornersIn,
625
+ Maximize as LuCornersOut,
587
626
  } from "lucide-react";
588
627
 
589
628
  /**
@@ -807,8 +846,8 @@ const solarSet: Partial<Record<IconName, SolarPair>> = {
807
846
  warning: { regular: SoWarningL, fill: SoWarningB },
808
847
  "warning-circle": { regular: SoWarningCircleL, fill: SoWarningCircleB },
809
848
  "check-circle": { regular: SoCheckCircleL, fill: SoCheckCircleB },
810
- "check-square": { regular: SoCheckL, fill: SoCheckB },
811
- "seal-check": { regular: SoCheckCircleL, fill: SoCheckCircleB },
849
+ "check-square": { regular: SoCheckSquareL, fill: SoCheckSquareB },
850
+ "seal-check": { regular: SoSealCheckL, fill: SoSealCheckB },
812
851
  star: { regular: SoStarL, fill: SoStarB },
813
852
  heart: { regular: SoHeartL, fill: SoHeartB },
814
853
  user: { regular: SoUserL, fill: SoUserB },
@@ -829,8 +868,8 @@ const solarSet: Partial<Record<IconName, SolarPair>> = {
829
868
  "caret-up-down": { regular: SoCaretUpDownL, fill: SoCaretUpDownB },
830
869
  "caret-double-left": { regular: SoCaretDoubleLeftL, fill: SoCaretDoubleLeftB },
831
870
  "caret-double-right": { regular: SoCaretDoubleRightL, fill: SoCaretDoubleRightB },
832
- "caret-circle-down": { regular: SoCaretDownL, fill: SoCaretDownB },
833
- "arrows-vertical": { regular: SoCaretUpDownL, fill: SoCaretUpDownB },
871
+ "caret-circle-down": { regular: SoCaretCircleDownL, fill: SoCaretCircleDownB },
872
+ "arrows-vertical": { regular: SoArrowsVerticalL, fill: SoArrowsVerticalB },
834
873
  columns: { regular: SoWidget4L, fill: SoWidget4B },
835
874
  layout: { regular: SoWidgetL, fill: SoWidgetB },
836
875
  house: { regular: SoHomeL, fill: SoHomeB },
@@ -854,9 +893,9 @@ const solarSet: Partial<Record<IconName, SolarPair>> = {
854
893
  textbox: { regular: SoTextL, fill: SoTextB },
855
894
  keyboard: { regular: SoKeyboardL, fill: SoKeyboardB },
856
895
  info: { regular: SoInfoCircleL, fill: SoInfoCircleB },
857
- article: { regular: SoDocumentTextL, fill: SoDocumentTextB },
858
- "grid-four": { regular: SoWidget2L, fill: SoWidget2B },
859
- "grid-nine": { regular: SoWidget5L, fill: SoWidget5B },
896
+ article: { regular: SoArticleL, fill: SoArticleB },
897
+ "grid-four": { regular: SoGridFourL, fill: SoGridFourB },
898
+ "grid-nine": { regular: SoGridNineL, fill: SoGridNineB },
860
899
  selection: { regular: SoSelectionL, fill: SoSelectionB },
861
900
  "radio-button": { regular: SoRadioL, fill: SoRadioB },
862
901
  "toggle-right": { regular: SoRadioL, fill: SoRadioB },
@@ -867,17 +906,17 @@ const solarSet: Partial<Record<IconName, SolarPair>> = {
867
906
  clock: { regular: SoClockL, fill: SoClockB },
868
907
  "upload-simple": { regular: SoUploadL, fill: SoUploadB },
869
908
  "download-simple": { regular: SoDownloadL, fill: SoDownloadB },
870
- table: { regular: SoWidget4L, fill: SoWidget4B },
909
+ table: { regular: SoTableL, fill: SoTableB },
871
910
  cards: { regular: SoCardsL, fill: SoCardsB },
872
911
  "chart-line": { regular: SoChartL, fill: SoChartB },
873
912
  "trend-up": { regular: SoTrendUpL, fill: SoTrendUpB },
874
913
  "trend-down": { regular: SoTrendDownL, fill: SoTrendDownB },
875
- tabs: { regular: SoWidget4L, fill: SoWidget4B },
914
+ tabs: { regular: SoTabsL, fill: SoTabsB },
876
915
  path: { regular: SoPathL, fill: SoPathB },
877
916
  sidebar: { regular: SoSidebarL, fill: SoSidebarB },
878
917
  "sidebar-simple": { regular: SoSidebarSimpleL, fill: SoSidebarSimpleB },
879
918
  "dots-three": { regular: SoDotsThreeL, fill: SoDotsThreeB },
880
- "dots-three-outline": { regular: SoDotsThreeL, fill: SoDotsThreeB },
919
+ "dots-three-outline": { regular: SoDotsThreeOutlineL, fill: SoDotsThreeOutlineB },
881
920
  compass: { regular: SoCompassL, fill: SoCompassB },
882
921
  "chat-circle": { regular: SoChatCircleL, fill: SoChatCircleB },
883
922
  "chat-text": { regular: SoChatTextL, fill: SoChatTextB },
@@ -896,7 +935,7 @@ const solarSet: Partial<Record<IconName, SolarPair>> = {
896
935
  "pencil-simple": { regular: SoPenL, fill: SoPenB },
897
936
  "share-network": { regular: SoShareL, fill: SoShareB },
898
937
  copy: { regular: SoCopyL, fill: SoCopyB },
899
- "copy-simple": { regular: SoCopyL, fill: SoCopyB },
938
+ "copy-simple": { regular: SoCopySimpleL, fill: SoCopySimpleB },
900
939
  scissors: { regular: SoScissorsL, fill: SoScissorsB },
901
940
  "clipboard-text": { regular: SoClipboardTextL, fill: SoClipboardTextB },
902
941
  camera: { regular: SoCameraL, fill: SoCameraB },
@@ -905,7 +944,7 @@ const solarSet: Partial<Record<IconName, SolarPair>> = {
905
944
  placeholder: { regular: SoSquareL, fill: SoSquareB },
906
945
  square: { regular: SoSquareL, fill: SoSquareB },
907
946
  "square-half": { regular: SoSquareL, fill: SoSquareB },
908
- steps: { regular: SoPathL, fill: SoPathB },
947
+ steps: { regular: SoStepsL, fill: SoStepsB },
909
948
  "line-segments": { regular: SoLineSegmentsL, fill: SoLineSegmentsB },
910
949
  "paint-bucket": { regular: SoPaintBucketL, fill: SoPaintBucketB },
911
950
  "split-horizontal": { regular: SoSplitHorizontalL, fill: SoSplitHorizontalB },
@@ -916,9 +955,9 @@ const solarSet: Partial<Record<IconName, SolarPair>> = {
916
955
  file: { regular: SoFileL, fill: SoFileB },
917
956
  "file-text": { regular: SoDocumentTextL, fill: SoDocumentTextB },
918
957
  link: { regular: SoLinkL, fill: SoLinkB },
919
- "link-simple": { regular: SoLinkL, fill: SoLinkB },
958
+ "link-simple": { regular: SoLinkSimpleL, fill: SoLinkSimpleB },
920
959
  lock: { regular: SoLockL, fill: SoLockB },
921
- "lock-open": { regular: SoLockL, fill: SoLockB },
960
+ "lock-open": { regular: SoLockOpenL, fill: SoLockOpenB },
922
961
  key: { regular: SoKeyL, fill: SoKeyB },
923
962
  "sign-out": { regular: SoSignOutL, fill: SoSignOutB },
924
963
  "sign-in": { regular: SoSignInL, fill: SoSignInB },
@@ -946,12 +985,12 @@ const solarSet: Partial<Record<IconName, SolarPair>> = {
946
985
  "arrow-down": { regular: SoArrowDownL, fill: SoArrowDownB },
947
986
  "arrow-clockwise": { regular: SoArrowClockwiseL, fill: SoArrowClockwiseB },
948
987
  "arrow-counter-clockwise": { regular: SoArrowCCWL, fill: SoArrowCCWB },
949
- "arrows-clockwise": { regular: SoArrowClockwiseL, fill: SoArrowClockwiseB },
988
+ "arrows-clockwise": { regular: SoArrowsClockwiseL, fill: SoArrowsClockwiseB },
950
989
  "arrows-left-right": { regular: SoArrowsLeftRightL, fill: SoArrowsLeftRightB },
951
990
  "arrows-out": { regular: SoArrowsOutL, fill: SoArrowsOutB },
952
991
  "arrows-in": { regular: SoArrowsInL, fill: SoArrowsInB },
953
- "corners-out": { regular: SoArrowsOutL, fill: SoArrowsOutB },
954
- "corners-in": { regular: SoArrowsInL, fill: SoArrowsInB },
992
+ "corners-out": { regular: SoCornersOutL, fill: SoCornersOutB },
993
+ "corners-in": { regular: SoCornersInL, fill: SoCornersInB },
955
994
  play: { regular: SoPlayL, fill: SoPlayB },
956
995
  pause: { regular: SoPauseL, fill: SoPauseB },
957
996
  stop: { regular: SoStopL, fill: SoStopB },
@@ -976,26 +1015,26 @@ const solarSet: Partial<Record<IconName, SolarPair>> = {
976
1015
  "credit-card": { regular: SoCardL, fill: SoCardB },
977
1016
  wallet: { regular: SoWalletL, fill: SoWalletB },
978
1017
  receipt: { regular: SoBillL, fill: SoBillB },
979
- coins: { regular: SoDollarL, fill: SoDollarB },
1018
+ coins: { regular: SoCoinsL, fill: SoCoinsB },
980
1019
  buildings: { regular: SoBuildingsL, fill: SoBuildingsB },
981
- factory: { regular: SoBuildingsL, fill: SoBuildingsB },
1020
+ factory: { regular: SoFactoryL, fill: SoFactoryB },
982
1021
  storefront: { regular: SoStorefrontL, fill: SoStorefrontB },
983
1022
  truck: { regular: SoTruckL, fill: SoTruckB },
984
- airplane: { regular: SoPaperPlaneL, fill: SoPaperPlaneB },
1023
+ airplane: { regular: SoAirplaneAltL, fill: SoAirplaneAltB },
985
1024
  car: { regular: SoTruckL, fill: SoTruckB },
986
1025
  bicycle: { regular: SoBicycleL, fill: SoBicycleB },
987
1026
  "map-trifold": { regular: SoMapTrifoldL, fill: SoMapTrifoldB },
988
- navigation: { regular: SoCompassL, fill: SoCompassB },
1027
+ navigation: { regular: SoNavigationL, fill: SoNavigationB },
989
1028
  shield: { regular: SoShieldL, fill: SoShieldB },
990
1029
  "shield-check": { regular: SoShieldCheckL, fill: SoShieldCheckB },
991
1030
  lifebuoy: { regular: SoLifebuoyL, fill: SoLifebuoyB },
992
1031
  question: { regular: SoQuestionL, fill: SoQuestionB },
993
- smiley: { regular: SoSmileyL, fill: SoSmileyB },
994
- "smiley-meh": { regular: SoSmileyL, fill: SoSmileyB },
995
- "smiley-sad": { regular: SoSmileyL, fill: SoSmileyB },
1032
+ smiley: { regular: SoSmileyHappyL, fill: SoSmileyHappyB },
1033
+ "smiley-meh": { regular: SoSmileyMehL, fill: SoSmileyMehB },
1034
+ "smiley-sad": { regular: SoSmileySadL, fill: SoSmileySadB },
996
1035
  "thumbs-up": { regular: SoThumbsUpL, fill: SoThumbsUpB },
997
1036
  "thumbs-down": { regular: SoThumbsDownL, fill: SoThumbsDownB },
998
- "hand-waving": { regular: SoHeartL, fill: SoHeartB },
1037
+ "hand-waving": { regular: SoHandWavingL, fill: SoHandWavingB },
999
1038
  trophy: { regular: SoTrophyL, fill: SoTrophyB },
1000
1039
  medal: { regular: SoMedalL, fill: SoMedalB },
1001
1040
  crown: { regular: SoCrownL, fill: SoCrownB },
@@ -1011,7 +1050,7 @@ const lucideSet: Partial<Record<IconName, AnyComp>> = {
1011
1050
  "warning-circle": LuWarningCircle,
1012
1051
  "check-circle": LuCheckCircle,
1013
1052
  "check-square": LuCheckSquare,
1014
- "seal-check": LuSealCheck,
1053
+ "seal-check": LuSealBadge,
1015
1054
  star: LuStar,
1016
1055
  heart: LuHeart,
1017
1056
  user: LuUser,
@@ -1036,7 +1075,7 @@ const lucideSet: Partial<Record<IconName, AnyComp>> = {
1036
1075
  "arrows-vertical": LuArrowsVertical,
1037
1076
  columns: LuColumns,
1038
1077
  square: LuSquare,
1039
- placeholder: LuSquare,
1078
+ placeholder: LuPlaceholder,
1040
1079
  rectangle: LuRectangle,
1041
1080
  layout: LuLayout,
1042
1081
  house: LuHouse,
@@ -1058,8 +1097,8 @@ const lucideSet: Partial<Record<IconName, AnyComp>> = {
1058
1097
  "toggle-right": LuToggleRight,
1059
1098
  "toggle-left": LuToggleLeft,
1060
1099
  calendar: LuCalendar,
1061
- "calendar-blank": LuCalendar,
1062
- "calendar-dots": LuCalendar,
1100
+ "calendar-blank": LuCalendarBlank,
1101
+ "calendar-dots": LuCalendarDots,
1063
1102
  clock: LuClock,
1064
1103
  "upload-simple": LuUpload,
1065
1104
  "download-simple": LuDownload,
@@ -1078,7 +1117,7 @@ const lucideSet: Partial<Record<IconName, AnyComp>> = {
1078
1117
  sidebar: LuSidebar,
1079
1118
  "sidebar-simple": LuSidebarSimple,
1080
1119
  "dots-three": LuDotsThree,
1081
- "dots-three-outline": LuDotsThree,
1120
+ "dots-three-outline": LuDotsThreeOutline,
1082
1121
  compass: LuCompass,
1083
1122
  "chat-circle": LuChatCircle,
1084
1123
  "chat-text": LuChatText,
@@ -1101,7 +1140,7 @@ const lucideSet: Partial<Record<IconName, AnyComp>> = {
1101
1140
  "squares-four": LuGrid3X3,
1102
1141
  list: LuList,
1103
1142
  info: LuInfo,
1104
- article: LuFileText,
1143
+ article: LuArticle,
1105
1144
  "grid-four": LuLayoutGrid,
1106
1145
  "arrow-square-out": LuExternalLink,
1107
1146
  "pencil-simple": LuPencil,
@@ -1115,7 +1154,7 @@ const lucideSet: Partial<Record<IconName, AnyComp>> = {
1115
1154
  "square-half": LuSquareHalf,
1116
1155
  folder: LuFolder,
1117
1156
  "folder-open": LuFolderOpen,
1118
- "folder-simple": LuFolder,
1157
+ "folder-simple": LuFolderSimple,
1119
1158
  file: LuFile,
1120
1159
  "file-text": LuFileText,
1121
1160
  link: LuLink,
@@ -1131,7 +1170,7 @@ const lucideSet: Partial<Record<IconName, AnyComp>> = {
1131
1170
  "map-pin": LuMapPin,
1132
1171
  "push-pin": LuPushPin,
1133
1172
  "paper-plane": LuPaperPlane,
1134
- "chat-centered-dots": LuChatText,
1173
+ "chat-centered-dots": LuChatCenteredDots,
1135
1174
  hash: LuHash,
1136
1175
  at: LuAt,
1137
1176
  bookmark: LuBookmark,
@@ -1153,8 +1192,8 @@ const lucideSet: Partial<Record<IconName, AnyComp>> = {
1153
1192
  "arrows-left-right": LuArrowsLeftRight,
1154
1193
  "arrows-out": LuArrowsOut,
1155
1194
  "arrows-in": LuArrowsIn,
1156
- "corners-out": LuArrowsOut,
1157
- "corners-in": LuArrowsIn,
1195
+ "corners-out": LuCornersOut,
1196
+ "corners-in": LuCornersIn,
1158
1197
  play: LuPlay,
1159
1198
  pause: LuPause,
1160
1199
  stop: LuStop,
@@ -1,59 +1,73 @@
1
1
  /*
2
- * ListItem — row 레벨 수평 anatomy. 수직 리듬은 parent gap/hairline에 위임.
2
+ * ListItem — 3구역 뼈대: [leading] [content] [trailing]
3
3
  *
4
- * padding/border/bg는 자체적으로 걸지 않지만, hover bg는 "chrome"이 아니라
5
- * "상호작용 피드백"이므로 row 레벨에서 담당.
4
+ * 수직 리듬은 CSS 변수로 조절:
5
+ * --li-pad-y 세로 padding (hit area)
6
+ * --li-gap-x leading↔content↔trailing 가로 gap
7
+ * --li-gap-title Title↔Description 간격 (조밀)
8
+ * --li-gap-body Description↔Meta·Note 간격 (한 숨 벌림)
9
+ * --li-gap-header Header 아이템 간 가로 gap
10
+ * --li-gap-meta Meta 라인 아이템 간 가로 gap
6
11
  *
7
- * 색상 훅 (부모 컨테이너에서 오버라이드):
8
- * --list-item-fg body 서술 색 (default: --fg)
9
- * --list-item-from-fg from(누구) 색 (default: --fg-strong)
10
- * --list-item-meta-fg meta/상대시간 색 (default: --fg-muted)
11
- * --list-item-hover-bg hover 시 row bg (default: --hover). transparent로 끄기
12
+ * chrome 없음 — bg·border·radius 자체 공급 X. 리스트 분리는 부모가 담당.
13
+ * 블록 외형이 필요하면 [[Callout]] 사용.
12
14
  */
13
15
 
14
16
  .root {
17
+ --li-pad-y: var(--space-3);
18
+ --li-pad-x: var(--space-2);
19
+ --li-gap-x: var(--space-3);
20
+ --li-gap-title: var(--space-0_5);
21
+ --li-gap-body: var(--space-2);
22
+ --li-gap-header: var(--space-2);
23
+ --li-gap-meta: var(--space-2);
24
+
15
25
  display: flex;
16
- align-items: flex-start;
17
- gap: var(--space-3);
18
- color: var(--list-item-fg, var(--fg));
19
- text-decoration: none;
26
+ gap: var(--li-gap-x);
20
27
  min-width: 0;
21
- /* 좌우 padding 없음 — 가로 anatomy는 flex gap만 담당해서 좌우 비대칭 방지.
22
- 세로 padding만 걸어 hover hit area 확보. 부모 li가 세로 hairline/padding을
23
- 담당하면 음의 margin으로 리듬 유지. 좌우로 hover bg를 넓히고 싶으면
24
- 부모 wrapper에 padding/negative margin을 걸어 조정한다. */
25
- padding-block: var(--space-3);
26
- margin-block: calc(var(--space-3) * -1);
28
+ color: var(--fg);
29
+ text-decoration: none;
30
+ /* hit area를 좌우로 넓혀 hover bg가 한 숨 바깥까지 번지도록 (negative margin으로 정렬 유지) */
31
+ padding: var(--li-pad-y) var(--li-pad-x);
32
+ margin-inline: calc(var(--li-pad-x) * -1);
33
+ border-radius: var(--radius-md);
27
34
  transition: var(--transition-colors);
28
35
  }
29
36
 
30
- .inline {
31
- align-items: center;
37
+ .root:hover {
38
+ background: var(--hover);
32
39
  }
33
40
 
34
- .stacked {
41
+ .align-center {
42
+ align-items: center;
43
+ }
44
+ .align-top {
35
45
  align-items: flex-start;
36
- /* 두 줄짜리는 세로 호흡을 한 단계 더. margin-block은 root 기본값(-space-3) 유지해
37
- 실제 레이아웃 박스가 벌어지도록. */
38
- padding-block: var(--space-4);
39
46
  }
40
47
 
41
- /* row hover — 모든 리스트 항목에서 "지금 이 줄" 신호.
42
- pure 좌표에서 끄려면 --list-item-hover-bg: transparent. */
43
- .root:hover {
44
- background: var(--list-item-hover-bg, var(--hover));
48
+ /* size=md (기본) — 작업/자원 리스트 밀도. 전체 호흡은 넉넉하되
49
+ Title↔Description은 조밀하게 묶어 "제목+부제" 유닛감 유지. */
50
+ .size-md {
51
+ --li-pad-y: var(--space-3);
52
+ --li-gap-title: var(--space-0_5);
53
+ --li-gap-body: var(--space-2);
54
+ }
55
+
56
+ /* size=sm — 활동 로그·추출 완료처럼 타이트하게 쌓이는 리스트. */
57
+ .size-sm {
58
+ --li-pad-y: var(--space-2);
59
+ --li-gap-title: var(--space-0_5);
60
+ --li-gap-body: var(--space-1);
45
61
  }
46
62
 
47
63
  .interactive {
48
64
  cursor: pointer;
49
65
  }
50
-
51
66
  .interactive:focus-visible {
52
67
  outline: none;
53
68
  box-shadow: var(--ring-shadow);
54
69
  }
55
70
 
56
- /* Leading — Avatar / Icon / 커스텀 */
57
71
  .leading {
58
72
  flex-shrink: 0;
59
73
  display: inline-flex;
@@ -62,16 +76,20 @@
62
76
  color: var(--fg-muted);
63
77
  }
64
78
 
65
- /* leading 안쪽에 size=auto인 leadingIcon이 있으면 wrapper를 투명화해서
66
- leadingIcon이 root flex의 직접 자식이 되게 한다 — 그래야 align-self: stretch가
67
- root content 전체 높이를 받고, root gap이 leadingIcon에 직접 적용됨. */
68
- .leading:has(.leadingIcon.auto) {
69
- display: contents;
79
+ /* leading/trailing 안 raw Icon(svg)은 리스트 밀도에 맞춰 16px로 축소.
80
+ LeadingIcon·Avatar·Badge·DateStamp·Button·IconButton 등 wrapped 요소는 자체 크기를 가지므로
81
+ :scope > svg 직접 자식만 겨냥 — 중첩된 아이콘(Badge 안 icon 등)에는 영향 없음. */
82
+ .leading > svg,
83
+ .trailing > svg {
84
+ width: var(--space-4);
85
+ height: var(--space-4);
86
+ }
87
+
88
+ .align-top > .leading {
89
+ align-items: flex-start;
90
+ padding-top: var(--space-0_5);
70
91
  }
71
92
 
72
- /* ListItemLeadingIcon — "배경색 + 아이콘" leading 타입.
73
- Avatar와 같은 사이즈 트랙에 올려 리스트에서 섞여도 정렬이 깨지지 않는다.
74
- bg/fg는 tone 프리셋 또는 CSS 변수로 자유 조절. */
75
93
  .leadingIcon {
76
94
  flex-shrink: 0;
77
95
  display: inline-flex;
@@ -81,28 +99,16 @@
81
99
  color: var(--leading-icon-fg, var(--fg));
82
100
  border-radius: var(--radius-sm);
83
101
  }
84
-
85
- .leadingIcon.xs { width: var(--space-4); height: var(--space-4); border-radius: var(--radius-xs); }
86
- .leadingIcon.sm { width: var(--space-6); height: var(--space-6); border-radius: var(--radius-sm); }
87
- .leadingIcon.md { width: var(--space-8); height: var(--space-8); border-radius: var(--radius-md); }
88
- .leadingIcon.lg { width: var(--space-12); height: var(--space-12); border-radius: var(--radius-lg); }
89
-
90
- /* auto — stacked 두 줄 content 높이에 딱 맞는 정사각 마크.
91
- flex 아이템에서 aspect-ratio + align-self: stretch 조합은 width가 min-content로
92
- 풀려 세로로 길어진다. 그래서 height를 토큰 기반으로 명시해 폭을 그에 묶는다.
93
- 계산: stacked content 두 줄 = from(text-sm × lh-tight) + body(text-sm × lh-sm) + gap(space-1).
94
- 오버라이드가 필요하면 --leading-icon-size로 통째로 교체. */
95
- .leadingIcon.auto {
96
- align-self: center;
97
- height: var(--leading-icon-size,
98
- calc(var(--text-sm) * var(--lh-tight) + var(--lh-sm) + var(--space-1))
99
- );
102
+ .leadingIcon.li-xs { width: var(--space-4); height: var(--space-4); border-radius: var(--radius-xs); }
103
+ .leadingIcon.li-sm { width: var(--space-6); height: var(--space-6); border-radius: var(--radius-sm); }
104
+ .leadingIcon.li-md { width: var(--space-8); height: var(--space-8); border-radius: var(--radius-md); }
105
+ .leadingIcon.li-lg { width: var(--space-12); height: var(--space-12); border-radius: var(--radius-lg); }
106
+ .leadingIcon.li-auto {
107
+ align-self: stretch;
100
108
  aspect-ratio: 1 / 1;
101
- width: auto;
102
109
  border-radius: var(--radius-md);
103
110
  }
104
111
 
105
- /* tone 프리셋 — subtle bg + solid fg 쌍. */
106
112
  .leadingIcon[data-tone="neutral"] {
107
113
  --leading-icon-bg: var(--bg-muted);
108
114
  --leading-icon-fg: var(--fg);
@@ -128,191 +134,65 @@
128
134
  --leading-icon-fg: var(--info);
129
135
  }
130
136
 
131
- /* Read-state 톤 신호 ──────────────────────────────────
132
- dot/badge 없이 텍스트 톤만으로 읽음/안읽음을 표현.
133
- - unread: 전체 톤이 가장 진하게 (body=fg-strong), meta는 primary로 "지금"을 강조
134
- - read: 전체 톤이 한두 단계 뒤로 물러남
135
- - 둘 다 미지정: 중립 — 리드 개념 없는 자리 */
136
-
137
- /* unread — meta primary, body/from 가장 진한 톤 */
138
- .unread .body,
139
- .unread .target,
140
- .unread .narrativeBody {
141
- color: var(--fg-strong, var(--fg));
142
- }
143
- .unread .from {
144
- color: var(--fg-strong, var(--fg));
145
- font-weight: var(--font-weight-medium);
146
- }
147
- .unread .meta {
148
- color: var(--primary);
149
- font-weight: var(--font-weight-regular);
150
- }
151
-
152
- /* read — 전체 한 단계 뒤로 */
153
- .read .body,
154
- .read .target,
155
- .read .narrativeBody {
156
- color: var(--fg-muted);
157
- }
158
- .read .from {
159
- color: var(--fg-muted);
160
- font-weight: var(--font-weight-regular);
161
- }
162
- .read .meta,
163
- .read .verb,
164
- .read .surface {
165
- color: var(--fg-subtle);
166
- }
167
- .read .leading {
168
- opacity: 0.7;
169
- }
170
-
171
- /* Content wrapper. stacked에서 title↔subtitle gap은 xs(space-1). */
172
137
  .content {
173
138
  flex: 1;
174
139
  min-width: 0;
175
140
  display: flex;
176
141
  flex-direction: column;
177
- gap: var(--space-1);
142
+ gap: 0;
178
143
  }
179
144
 
180
- /* 서술 모드 (A): from body (inline). dash 없이 공백으로만 분리. */
181
- .narrativeBody {
182
- display: inline;
145
+ .title {
183
146
  font-size: var(--text-sm);
184
147
  line-height: var(--lh-sm);
185
- color: var(--list-item-fg, var(--fg));
186
- font-weight: var(--font-weight-regular);
148
+ color: var(--fg-strong, var(--fg));
149
+ font-weight: var(--font-weight-medium);
187
150
  min-width: 0;
188
- }
189
-
190
- .narrativeBody .from {
191
- margin-inline-end: var(--space-2);
192
- }
193
-
194
- /* 서술 모드 (B): from · verb · target · surface */
195
- .actionBody {
151
+ /* Badge 등 inline 요소가 Title 앞에 섞여도 수직 중앙 정렬 유지 */
196
152
  display: inline-flex;
197
153
  flex-wrap: wrap;
198
- align-items: baseline;
199
- gap: var(--space-1_5);
200
- font-size: var(--text-sm);
201
- line-height: var(--lh-sm);
202
- min-width: 0;
203
- }
204
-
205
- /* stacked: content가 flex-column이라 from·body가 자동 2줄로. */
206
- .stacked .from {
207
- font-size: var(--text-sm);
208
- line-height: var(--lh-tight);
154
+ align-items: center;
155
+ gap: var(--space-2);
209
156
  }
210
157
 
211
- .stacked .body {
158
+ .description {
159
+ margin: var(--li-gap-title) 0 0;
212
160
  font-size: var(--text-sm);
213
161
  line-height: var(--lh-sm);
214
- color: var(--list-item-fg, var(--fg));
215
- }
216
-
217
- .from {
218
- font-weight: var(--font-weight-medium);
219
- color: var(--list-item-from-fg, var(--fg-strong));
220
- }
221
-
222
- .body {
223
- color: var(--list-item-fg, var(--fg));
224
- font-weight: var(--font-weight-regular);
225
- }
226
-
227
- .verb {
228
- color: var(--list-item-meta-fg, var(--fg-muted));
229
- font-weight: var(--font-weight-regular);
230
- }
231
-
232
- .target {
233
- color: var(--list-item-fg, var(--fg));
162
+ color: var(--fg-muted);
234
163
  font-weight: var(--font-weight-regular);
235
164
  }
236
165
 
237
- /* 작은 글자(surface, meta)는 본문보다 한 단계 더 연하게 — subtle. */
238
- .surface {
239
- font-size: var(--text-xs);
240
- color: var(--list-item-meta-fg, var(--fg-subtle));
241
- margin-inline-start: var(--space-1);
242
- }
243
-
244
- /* Meta — 오른쪽 끝 상대시간. 작은 글자라 subtle 톤. */
245
166
  .meta {
246
- flex-shrink: 0;
167
+ margin-top: var(--li-gap-title);
168
+ display: flex;
169
+ align-items: center;
170
+ gap: var(--li-gap-meta);
247
171
  font-size: var(--text-xs);
248
- color: var(--list-item-meta-fg, var(--fg-subtle));
172
+ line-height: var(--lh-sm);
173
+ color: var(--fg-subtle);
249
174
  font-variant-numeric: tabular-nums;
250
- align-self: center;
251
175
  }
252
-
253
- .stacked .meta {
254
- align-self: flex-start;
255
- margin-top: var(--space-0_5);
176
+ .meta time {
177
+ color: inherit;
256
178
  }
257
179
 
258
- /* Trailing — meta 자리 커스텀 */
259
- .trailing {
180
+ .metaDivider {
181
+ display: inline-block;
182
+ width: var(--hairline);
183
+ height: var(--space-3);
184
+ background: var(--border);
260
185
  flex-shrink: 0;
261
- display: inline-flex;
262
- align-items: center;
263
- gap: var(--space-2);
264
- color: var(--list-item-meta-fg, var(--fg-muted));
265
- font-size: var(--text-xs);
266
186
  }
267
187
 
268
- /* hoverActions — 평상시 숨김, row hover/focus에서 등장 */
269
- .hoverActions {
188
+ .trailing {
189
+ flex-shrink: 0;
270
190
  display: inline-flex;
271
191
  align-items: center;
272
192
  gap: var(--space-1);
273
- flex-shrink: 0;
274
- width: 0;
275
- margin-inline-start: 0;
276
- opacity: 0;
277
- pointer-events: none;
278
- overflow: hidden;
279
- transition:
280
- width var(--duration-fast) var(--ease-out),
281
- margin-inline-start var(--duration-fast) var(--ease-out),
282
- opacity var(--duration-fast) var(--ease-out);
283
- }
284
-
285
- .root:hover .hoverActions,
286
- .root:focus-visible .hoverActions,
287
- .root:focus-within .hoverActions {
288
- width: auto;
289
- margin-inline-start: var(--space-2);
290
- opacity: 1;
291
- pointer-events: auto;
292
- }
293
-
294
- @media (hover: none) {
295
- .hoverActions {
296
- width: auto;
297
- margin-inline-start: var(--space-2);
298
- opacity: 1;
299
- pointer-events: auto;
300
- }
301
- }
302
-
303
- /* Variant별 default — anatomy는 공통. */
304
- .activity {
305
- /* no-op */
306
- }
307
-
308
- .notification {
309
- /* TSX에서 unread default true 처리 */
310
- }
311
-
312
- .mention .body {
313
- font-style: italic;
314
- }
315
-
316
- .system .leading {
317
193
  color: var(--fg-muted);
194
+ align-self: center;
195
+ }
196
+ .align-top > .trailing {
197
+ align-self: flex-start;
318
198
  }