Menu.module.css 5.6 KB

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