Menu.module.css 5.3 KB

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