TerminalBlock.module.css 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. .block {
  2. --dsl-terminal-radius: 12px;
  3. --dsl-terminal-line-height: 22px;
  4. /* Rebindable by consumers (CodeBlock's --dsl-code-block-content-font
  5. pattern): a surface wanting the smaller code size rebinds this together
  6. with --dsl-terminal-line-height on its own container. */
  7. --dsl-terminal-font: var(--dsw-font-markdown-code-block);
  8. /* The card's own left inset, holding the run-state dot in a column of its own
  9. so it never competes with the commands for horizontal space. */
  10. --dsl-terminal-gutter: 30px;
  11. position: relative;
  12. margin: 16px 0;
  13. /* The gutter is the card's OWN padding, not a margin: every consumer rewrites
  14. `margin` wholesale (each render site sets its own indent), which silently
  15. cancelled the reservation and let the dot fall outside the card into a
  16. container that clips it. Owning the reservation here keeps the invariant
  17. with the component that depends on it. */
  18. padding-left: var(--dsl-terminal-gutter);
  19. color: var(--dsw-alias-label-primary);
  20. background: var(--dsw-alias-markdown-code-block);
  21. border-radius: var(--dsl-terminal-radius);
  22. /* Clip the banner to the card's own radius: when a consumer adds a border,
  23. the banner's equal corner radius no longer nests inside it and leaves a
  24. notch at the corner. Nothing inside renders out of the box. */
  25. overflow: hidden;
  26. }
  27. /* The status pill and copy control top-align to the FIRST prompt row (their
  28. heights are capped to the prompt line, so on a multi-line command they sit
  29. with the first command instead of floating mid-banner). */
  30. .header {
  31. display: flex;
  32. align-items: flex-start;
  33. gap: 12px;
  34. /* Pulled back across the card's gutter padding so the banner spans the FULL
  35. surface, then re-inset by the same amount so the prompt text and the dot
  36. keep their positions. The banner shares the card's own surface (no banner
  37. token): the l2 divider below is the section boundary. */
  38. margin-left: calc(-1 * var(--dsl-terminal-gutter));
  39. padding: 9px 14px 9px var(--dsl-terminal-gutter);
  40. border-top-left-radius: var(--dsl-terminal-radius);
  41. border-top-right-radius: var(--dsl-terminal-radius);
  42. /* A long multi-line command scrolls inside the banner (same cap as the
  43. IN/OUT card's sections) instead of pushing the output off screen. */
  44. max-height: 150px;
  45. overflow-y: auto;
  46. }
  47. /* Banner scrollbar floats off the card edge like the output's. */
  48. .header::-webkit-scrollbar-thumb {
  49. border: 2px solid transparent;
  50. background-clip: padding-box;
  51. border-radius: 6px;
  52. }
  53. .header::-webkit-scrollbar-track {
  54. margin: 6px;
  55. }
  56. /* Full-width l2 hairline between the command banner and the body — the same
  57. divider the IN/OUT card draws between its sections. A running card is
  58. banner-only, so it draws none. */
  59. .block:not([data-running]) .header {
  60. border-bottom: 1px solid var(--dsw-alias-border-l2);
  61. }
  62. /* One row per command line. The prompt column is the only element allowed to
  63. shrink; the status pill and the copy control keep their intrinsic width. */
  64. .prompt {
  65. display: flex;
  66. flex-direction: column;
  67. min-width: 0;
  68. flex: 1;
  69. font: var(--dsl-terminal-font);
  70. }
  71. .promptLine {
  72. position: relative;
  73. display: flex;
  74. align-items: baseline;
  75. gap: 8px;
  76. min-width: 0;
  77. line-height: var(--dsl-terminal-line-height);
  78. }
  79. /* Out of flow inside the card's own gutter padding, so the reservation and the
  80. dot move together and no consumer margin can pull them apart; the dot neither
  81. indents its command nor depends on the command's text metrics to line up.
  82. Centered against the row's line box, not the code font's baseline. */
  83. .runState {
  84. position: absolute;
  85. left: calc(-1 * var(--dsl-terminal-gutter) + 8px);
  86. top: 50%;
  87. transform: translateY(-50%);
  88. }
  89. /* The dot is aria-hidden; this is its text label for assistive technology. */
  90. .runStateLabel {
  91. position: absolute;
  92. width: 1px;
  93. height: 1px;
  94. overflow: hidden;
  95. clip-path: inset(50%);
  96. white-space: nowrap;
  97. }
  98. .cwd {
  99. flex: none;
  100. color: var(--dsw-alias-label-tertiary);
  101. }
  102. /* `pre`, not `nowrap`: the prompt row renders the command verbatim, and
  103. `nowrap` collapses the repeated spaces, tabs, and alignment of an indented
  104. continuation. Both hold the single row and the ellipsis. */
  105. .command {
  106. min-width: 0;
  107. color: var(--dsw-alias-label-primary);
  108. overflow: hidden;
  109. text-overflow: ellipsis;
  110. white-space: pre;
  111. }
  112. /* Capped to the prompt's line height (Pill's own 24px height would exceed a
  113. smaller-font prompt row and stretch the banner). Sticky against the
  114. banner's own scroll so the pill and the copy control stay in reach while a
  115. long command scrolls underneath. */
  116. .status {
  117. flex: none;
  118. position: sticky;
  119. top: 0;
  120. height: var(--dsl-terminal-line-height);
  121. color: var(--dsw-alias-state-error-primary);
  122. }
  123. .copyButton {
  124. flex: none;
  125. position: sticky;
  126. top: 0;
  127. /* Card surface, not transparent: the control is sticky over the banner's
  128. own scroll, so scrolled command text must not bleed through it. */
  129. background-color: var(--dsw-alias-markdown-code-block);
  130. border: none;
  131. padding: 0;
  132. margin: 0;
  133. color: var(--dsw-alias-label-secondary);
  134. cursor: pointer;
  135. font: var(--dsw-font-xs-13);
  136. line-height: var(--dsl-terminal-line-height);
  137. }
  138. /* Vertical scrolling lives on the OUTPUT, not the card root: a root scroller
  139. would run its scrollbar over the banner (and the copy control), while here
  140. the banner stays pinned and the bar sits inside the output's right padding.
  141. Unset, the max-height is none and the auto overflow never engages. */
  142. .output {
  143. max-height: var(--dsl-terminal-output-max-height, none);
  144. padding: 12px 14px 12px 0;
  145. font: var(--dsl-terminal-font);
  146. overflow-x: auto;
  147. overflow-y: auto;
  148. }
  149. /* Both output scrollbars (vertical cap, horizontal pre overflow) float 2px
  150. off the card edge: a transparent border clips the thumb inward so it never
  151. hugs the rounded corner. */
  152. .output::-webkit-scrollbar-thumb {
  153. border: 2px solid transparent;
  154. background-clip: padding-box;
  155. border-radius: 6px;
  156. }
  157. /* Track end-margins keep the thumb's travel out of the card's rounded
  158. corners in both directions. */
  159. .output::-webkit-scrollbar-track {
  160. margin: 6px;
  161. }
  162. /* No wrapping, no word-break: alignment is the payload of terminal output. */
  163. .line {
  164. min-height: var(--dsl-terminal-line-height);
  165. white-space: pre;
  166. }
  167. .expand {
  168. display: block;
  169. width: 100%;
  170. padding: 0;
  171. border: none;
  172. background-color: transparent;
  173. color: var(--dsw-alias-label-tertiary);
  174. cursor: pointer;
  175. font: inherit;
  176. text-align: left;
  177. }
  178. .expand:hover {
  179. color: var(--dsw-alias-label-secondary);
  180. }
  181. .empty {
  182. padding: 12px 14px 12px 0;
  183. font: var(--dsl-terminal-font);
  184. color: var(--dsw-alias-label-tertiary);
  185. }