Menu.module.css 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. .root {
  2. position: relative;
  3. display: inline-flex;
  4. }
  5. /* Dropdown card (figma MenuDropdown 122:9481 / 419:16920): menu surface,
  6. * r12, inverted hairline border, shadow-lv3, 4px inset padding. */
  7. .list,
  8. .submenu {
  9. /* min-widths below are the design's outer card widths — include the pad. */
  10. box-sizing: border-box;
  11. padding: 4px;
  12. display: flex;
  13. flex-direction: column;
  14. gap: 0;
  15. border: 1px solid var(--dsw-alias-border-inverted);
  16. border-radius: 12px;
  17. background: var(--dsw-specific-menu);
  18. box-shadow: var(--dsw-shadow-lv3);
  19. }
  20. /* Primary card is 218 wide in the design across both hosts. */
  21. .list {
  22. position: absolute;
  23. top: calc(100% + 4px);
  24. left: 0;
  25. z-index: 100;
  26. min-width: 218px;
  27. max-width: 360px;
  28. }
  29. /* Portal mode: fixed in the viewport, coordinates supplied inline from the
  30. * anchor rect (side/align resolved in JS, the in-place offset rules above
  31. * don't apply). Portaled lists must layer above modal overlays (z 1000) —
  32. * an anchor inside a dialog still expects its menu on top. */
  33. .portal {
  34. position: fixed;
  35. top: auto;
  36. left: auto;
  37. z-index: 1100;
  38. }
  39. /* Open above the anchor (empty-state workspace chip: figma 122:9481). */
  40. .sideTop {
  41. top: auto;
  42. bottom: calc(100% + 4px);
  43. }
  44. .alignEnd {
  45. left: auto;
  46. right: 0;
  47. }
  48. /* Viewport fit: the card stops 12px short of the viewport's top/bottom edges
  49. * (24 = 2 × the portal MARGIN in Menu.tsx) and taller content scrolls inside
  50. * .viewport, so a pinned .footer stays visible. Menus with submenu rows skip
  51. * this class — the overflow clip would crop the side card, so they rely on
  52. * staying short. */
  53. .scrollable {
  54. max-height: calc(100vh - 24px);
  55. }
  56. .viewport {
  57. display: flex;
  58. flex-direction: column;
  59. min-height: 0;
  60. }
  61. .scrollable .viewport {
  62. overflow-y: auto;
  63. }
  64. /* Pinned rows below the scroll region; l2 hairline (l1 is near-invisible on
  65. * the menu surface) mirrors the .separator spacing. */
  66. .footer {
  67. flex: none;
  68. display: flex;
  69. flex-direction: column;
  70. margin-top: 4px;
  71. padding-top: 4px;
  72. border-top: 1px solid var(--dsw-alias-border-l2);
  73. }
  74. .itemWrap {
  75. position: relative;
  76. }
  77. /* Menu cell (figma .Menu_cell): min-h 40, r10, pad 10/8, 14/22 primary,
  78. * gap 8 between leading icon / label / trailing check. */
  79. .item {
  80. display: flex;
  81. align-items: center;
  82. gap: 8px;
  83. width: 100%;
  84. min-height: 40px;
  85. padding: 8px 10px;
  86. border: none;
  87. border-radius: 10px;
  88. background: transparent;
  89. cursor: pointer;
  90. font-size: 14px;
  91. line-height: 22px;
  92. color: var(--dsw-alias-label-primary);
  93. text-align: left;
  94. }
  95. .item:hover:not(:disabled) {
  96. background: var(--dsw-alias-interactive-bg-hover);
  97. }
  98. .item:disabled {
  99. opacity: 0.4;
  100. cursor: not-allowed;
  101. }
  102. .itemIcon {
  103. display: inline-flex;
  104. flex: none;
  105. width: 16px;
  106. height: 16px;
  107. align-items: center;
  108. justify-content: center;
  109. color: var(--dsw-alias-label-tertiary);
  110. }
  111. .itemLabel {
  112. flex: 1;
  113. min-width: 0;
  114. overflow: hidden;
  115. text-overflow: ellipsis;
  116. white-space: nowrap;
  117. }
  118. .check {
  119. flex: none;
  120. color: var(--dsw-alias-label-primary);
  121. }
  122. /* Selected cell keeps the plain fill (marker is the trailing check); the
  123. * class remains as a hook for owner-side emphasis. */
  124. .selected {
  125. background: transparent;
  126. }
  127. /* Destructive row: error text/icon, danger hover fill. */
  128. .danger {
  129. color: var(--dsw-alias-state-error-primary);
  130. }
  131. .danger .itemIcon {
  132. color: var(--dsw-alias-state-error-primary);
  133. }
  134. .danger:hover:not(:disabled) {
  135. background: var(--dsw-alias-interactive-bg-hover-danger);
  136. }
  137. /* Heading row: non-interactive small grey text, padding aligned with items. */
  138. .label {
  139. padding: 8px 10px;
  140. font-size: 12px;
  141. line-height: 16px;
  142. color: var(--dsw-alias-label-tertiary);
  143. }
  144. /* Separator cell (figma 122:9481): py 4 / px 2 around the hairline. */
  145. .separator {
  146. height: 1px;
  147. margin: 4px 2px;
  148. background: var(--dsw-alias-border-l1);
  149. }
  150. /* Nested card to the right of the parent row (figma 419:16920).
  151. * Bottom-aligned with the parent menu card (grows upward): itemWrap sits in
  152. * .list's 4px pad, so bottom: -4px matches the list's outer bottom edge.
  153. * Horizontal: list pad (4px) + 6px card gap = 10px past itemWrap — plain
  154. * `100% + 6px` collapses to ~2px between outer card edges.
  155. * ::before bridges the full gap so the pointer can cross without mouseLeave. */
  156. .submenu {
  157. position: absolute;
  158. top: auto;
  159. bottom: -4px;
  160. left: calc(100% + 10px);
  161. z-index: 101;
  162. min-width: 163px;
  163. }
  164. .submenu::before {
  165. content: '';
  166. position: absolute;
  167. top: 0;
  168. bottom: 0;
  169. left: -10px;
  170. width: 10px;
  171. }