_tabs-common.scss 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. @use '../core/style/vendor-prefixes';
  2. @use '../core/tokens/m2/mdc/tab-indicator' as tokens-mdc-tab-indicator;
  3. @use '../core/tokens/m2/mdc/secondary-navigation-tab' as tokens-mdc-secondary-navigation-tab;
  4. @use '../core/tokens/m2/mat/tab-header' as tokens-mat-tab-header;
  5. @use '../core/tokens/m2/mat/tab-header-with-background' as tokens-mat-tab-header-with-background;
  6. @use '../core/tokens/token-utils';
  7. $mat-tab-animation-duration: 500ms !default;
  8. // Combines the various structural styles we need for the tab group and tab nav bar.
  9. @mixin structural-styles {
  10. .mdc-tab {
  11. min-width: 90px;
  12. padding: 0 24px;
  13. display: flex;
  14. flex: 1 0 auto;
  15. justify-content: center;
  16. box-sizing: border-box;
  17. border: none;
  18. outline: none;
  19. text-align: center;
  20. white-space: nowrap;
  21. cursor: pointer;
  22. z-index: 1;
  23. }
  24. .mdc-tab__content {
  25. display: flex;
  26. align-items: center;
  27. justify-content: center;
  28. height: inherit;
  29. pointer-events: none;
  30. }
  31. .mdc-tab__text-label {
  32. transition: 150ms color linear;
  33. display: inline-block;
  34. line-height: 1;
  35. z-index: 2;
  36. }
  37. .mdc-tab--active .mdc-tab__text-label {
  38. transition-delay: 100ms;
  39. }
  40. ._mat-animation-noopable .mdc-tab__text-label {
  41. transition: none;
  42. }
  43. .mdc-tab-indicator {
  44. display: flex;
  45. position: absolute;
  46. top: 0;
  47. left: 0;
  48. justify-content: center;
  49. width: 100%;
  50. height: 100%;
  51. pointer-events: none;
  52. z-index: 1;
  53. }
  54. .mdc-tab-indicator__content {
  55. transition: var(--mat-tab-animation-duration, 250ms) transform cubic-bezier(0.4, 0, 0.2, 1);
  56. transform-origin: left;
  57. opacity: 0;
  58. }
  59. .mdc-tab-indicator__content--underline {
  60. align-self: flex-end;
  61. box-sizing: border-box;
  62. width: 100%;
  63. border-top-style: solid;
  64. }
  65. .mdc-tab-indicator--active .mdc-tab-indicator__content {
  66. opacity: 1;
  67. }
  68. ._mat-animation-noopable, .mdc-tab-indicator--no-transition {
  69. .mdc-tab-indicator__content {
  70. transition: none;
  71. }
  72. }
  73. .mat-mdc-tab-ripple.mat-mdc-tab-ripple {
  74. position: absolute;
  75. top: 0;
  76. left: 0;
  77. bottom: 0;
  78. right: 0;
  79. pointer-events: none;
  80. }
  81. }
  82. @mixin tab {
  83. -webkit-tap-highlight-color: transparent;
  84. -webkit-font-smoothing: antialiased;
  85. -moz-osx-font-smoothing: grayscale;
  86. text-decoration: none;
  87. // Tabs might be `button` elements so we have to reset the user agent styling.
  88. background: none;
  89. @include token-utils.use-tokens(
  90. tokens-mdc-secondary-navigation-tab.$prefix,
  91. tokens-mdc-secondary-navigation-tab.get-token-slots()
  92. ) {
  93. @include token-utils.create-token-slot(height, container-height);
  94. }
  95. @include token-utils.use-tokens(
  96. tokens-mat-tab-header.$prefix,
  97. tokens-mat-tab-header.get-token-slots()
  98. ) {
  99. @include token-utils.create-token-slot(font-family, label-text-font);
  100. @include token-utils.create-token-slot(font-size, label-text-size);
  101. @include token-utils.create-token-slot(letter-spacing, label-text-tracking);
  102. @include token-utils.create-token-slot(line-height, label-text-line-height);
  103. @include token-utils.create-token-slot(font-weight, label-text-weight);
  104. }
  105. &.mdc-tab {
  106. // MDC's tabs stretch to fit the header by default, whereas stretching on our current ones
  107. // is an opt-in behavior. Also technically we don't need to combine the two classes, but
  108. // we need the extra specificity to avoid issues with CSS insertion order.
  109. flex-grow: 0;
  110. }
  111. .mdc-tab-indicator__content--underline {
  112. @include token-utils.use-tokens(
  113. tokens-mdc-tab-indicator.$prefix,
  114. tokens-mdc-tab-indicator.get-token-slots()
  115. ) {
  116. @include token-utils.create-token-slot(border-color, active-indicator-color);
  117. @include token-utils.create-token-slot(border-top-width, active-indicator-height);
  118. @include token-utils.create-token-slot(border-radius, active-indicator-shape);
  119. }
  120. }
  121. @include token-utils.use-tokens(
  122. tokens-mat-tab-header.$prefix,
  123. tokens-mat-tab-header.get-token-slots()
  124. ) {
  125. &:hover .mdc-tab__text-label {
  126. @include token-utils.create-token-slot(color, inactive-hover-label-text-color);
  127. }
  128. &:focus .mdc-tab__text-label {
  129. @include token-utils.create-token-slot(color, inactive-focus-label-text-color);
  130. }
  131. &.mdc-tab--active {
  132. .mdc-tab__text-label {
  133. @include token-utils.create-token-slot(color, active-label-text-color);
  134. }
  135. .mdc-tab__ripple::before,
  136. .mat-ripple-element {
  137. @include token-utils.create-token-slot(background-color, active-ripple-color);
  138. }
  139. &:hover {
  140. .mdc-tab__text-label {
  141. @include token-utils.create-token-slot(color, active-hover-label-text-color);
  142. }
  143. .mdc-tab-indicator__content--underline {
  144. @include token-utils.create-token-slot(border-color, active-hover-indicator-color);
  145. }
  146. }
  147. &:focus {
  148. .mdc-tab__text-label {
  149. @include token-utils.create-token-slot(color, active-focus-label-text-color);
  150. }
  151. .mdc-tab-indicator__content--underline {
  152. @include token-utils.create-token-slot(border-color, active-focus-indicator-color);
  153. }
  154. }
  155. }
  156. }
  157. &.mat-mdc-tab-disabled {
  158. // MDC doesn't support disabled tabs so we need to improvise.
  159. opacity: 0.4;
  160. // We use `pointer-events` to make the element unclickable when it's disabled, rather than
  161. // preventing the default action through JS, because we can't prevent the action reliably
  162. // due to other directives potentially registering their events earlier. This shouldn't cause
  163. // the user to click through, because we always have a header behind the tab. Furthermore, this
  164. // saves us some CSS, because we don't have to add `:not(.mat-mdc-tab-disabled)` to all the
  165. // hover and focus selectors.
  166. pointer-events: none;
  167. // We also need to prevent content from being clickable.
  168. .mdc-tab__content {
  169. pointer-events: none;
  170. }
  171. @include token-utils.use-tokens(
  172. tokens-mat-tab-header.$prefix,
  173. tokens-mat-tab-header.get-token-slots()
  174. ) {
  175. .mdc-tab__ripple::before,
  176. .mat-ripple-element {
  177. @include token-utils.create-token-slot(background-color, disabled-ripple-color);
  178. }
  179. }
  180. }
  181. // Used to render out the background tint when hovered/focused. Usually this is done by
  182. // MDC's ripple styles, however we're using our own ripples due to size concerns.
  183. .mdc-tab__ripple::before {
  184. content: '';
  185. display: block;
  186. position: absolute;
  187. top: 0;
  188. left: 0;
  189. right: 0;
  190. bottom: 0;
  191. opacity: 0;
  192. pointer-events: none;
  193. @include token-utils.use-tokens(
  194. tokens-mat-tab-header.$prefix,
  195. tokens-mat-tab-header.get-token-slots()
  196. ) {
  197. @include token-utils.create-token-slot(background-color, inactive-ripple-color);
  198. }
  199. }
  200. .mdc-tab__text-label {
  201. @include token-utils.use-tokens(
  202. tokens-mat-tab-header.$prefix,
  203. tokens-mat-tab-header.get-token-slots()
  204. ) {
  205. @include token-utils.create-token-slot(color, inactive-label-text-color);
  206. }
  207. // We support projecting icons into the tab. These styles ensure that they're centered.
  208. display: inline-flex;
  209. align-items: center;
  210. }
  211. .mdc-tab__content {
  212. // Required for `fitInkBarToContent` to work. This used to be included with MDC's
  213. // `without-ripple` mixin, but that no longer appears to be the case with `static-styles`.
  214. // Since the latter is ~10kb smaller, we include this one extra style ourselves.
  215. position: relative;
  216. // MDC sets `pointer-events: none` on the content which prevents interactions with the
  217. // nested content. Re-enable it since we allow nesting any content in the tab (see #26195).
  218. pointer-events: auto;
  219. }
  220. // We need to handle the hover and focus indication ourselves, because we don't use MDC's ripple.
  221. &:hover .mdc-tab__ripple::before {
  222. opacity: 0.04;
  223. }
  224. &.cdk-program-focused,
  225. &.cdk-keyboard-focused {
  226. .mdc-tab__ripple::before {
  227. opacity: 0.12;
  228. }
  229. }
  230. .mat-ripple-element {
  231. opacity: 0.12;
  232. @include token-utils.use-tokens(
  233. tokens-mat-tab-header.$prefix,
  234. tokens-mat-tab-header.get-token-slots()
  235. ) {
  236. @include token-utils.create-token-slot(background-color, inactive-ripple-color);
  237. }
  238. }
  239. }
  240. // Structural styles for a tab header. Used by both `mat-tab-header` and `mat-tab-nav-bar`.
  241. // We need this styles on top of MDC's, because MDC doesn't support pagination like ours.
  242. @mixin paginated-tab-header {
  243. .mat-mdc-tab-header {
  244. display: flex;
  245. overflow: hidden;
  246. position: relative;
  247. flex-shrink: 0;
  248. }
  249. .mdc-tab-indicator .mdc-tab-indicator__content {
  250. transition-duration: var(--mat-tab-animation-duration, 250ms);
  251. }
  252. .mat-mdc-tab-header-pagination {
  253. @include vendor-prefixes.user-select(none);
  254. position: relative;
  255. display: none;
  256. justify-content: center;
  257. align-items: center;
  258. min-width: 32px;
  259. cursor: pointer;
  260. z-index: 2;
  261. -webkit-tap-highlight-color: transparent;
  262. touch-action: none;
  263. box-sizing: content-box;
  264. outline: 0;
  265. &::-moz-focus-inner {
  266. border: 0;
  267. }
  268. .mat-ripple-element {
  269. opacity: 0.12;
  270. @include token-utils.use-tokens(
  271. tokens-mat-tab-header.$prefix,
  272. tokens-mat-tab-header.get-token-slots()
  273. ) {
  274. @include token-utils.create-token-slot(background-color, inactive-ripple-color);
  275. }
  276. }
  277. .mat-mdc-tab-header-pagination-controls-enabled & {
  278. display: flex;
  279. }
  280. }
  281. // The pagination control that is displayed on the left side of the tab header.
  282. .mat-mdc-tab-header-pagination-before,
  283. .mat-mdc-tab-header-rtl .mat-mdc-tab-header-pagination-after {
  284. padding-left: 4px;
  285. .mat-mdc-tab-header-pagination-chevron {
  286. transform: rotate(-135deg);
  287. }
  288. }
  289. // The pagination control that is displayed on the right side of the tab header.
  290. .mat-mdc-tab-header-rtl .mat-mdc-tab-header-pagination-before,
  291. .mat-mdc-tab-header-pagination-after {
  292. padding-right: 4px;
  293. .mat-mdc-tab-header-pagination-chevron {
  294. transform: rotate(45deg);
  295. }
  296. }
  297. .mat-mdc-tab-header-pagination-chevron {
  298. border-style: solid;
  299. border-width: 2px 2px 0 0;
  300. height: 8px;
  301. width: 8px;
  302. @include token-utils.use-tokens(
  303. tokens-mat-tab-header.$prefix,
  304. tokens-mat-tab-header.get-token-slots()
  305. ) {
  306. @include token-utils.create-token-slot(border-color, pagination-icon-color);
  307. }
  308. }
  309. .mat-mdc-tab-header-pagination-disabled {
  310. box-shadow: none;
  311. cursor: default;
  312. pointer-events: none;
  313. .mat-mdc-tab-header-pagination-chevron {
  314. opacity: 0.4;
  315. }
  316. }
  317. .mat-mdc-tab-list {
  318. flex-grow: 1;
  319. position: relative;
  320. transition: transform 500ms cubic-bezier(0.35, 0, 0.25, 1);
  321. ._mat-animation-noopable & {
  322. transition: none;
  323. }
  324. }
  325. }
  326. // Structural styles for the element that wraps the paginated header items.
  327. @mixin paginated-tab-header-item-wrapper($parent) {
  328. display: flex;
  329. flex: 1 0 auto;
  330. // We need to set the parent here explicitly, in order to prevent the alignment
  331. // from any parent tab groups from propagating down to the children when nesting.
  332. // Note that these are used as inputs so they shouldn't be changed to `mat-mdc-`.
  333. [mat-align-tabs='center'] > #{$parent} & {
  334. justify-content: center;
  335. }
  336. [mat-align-tabs='end'] > #{$parent} & {
  337. justify-content: flex-end;
  338. }
  339. // Prevent the header from collapsing when it is a drop list. This is useful,
  340. // because its height may become zero once all the tabs are dragged out.
  341. // Note that ideally we would do this by default, rather than only in a drop
  342. // list, but it ended up being hugely breaking internally.
  343. .cdk-drop-list &,
  344. &.cdk-drop-list {
  345. @include token-utils.use-tokens(
  346. tokens-mdc-secondary-navigation-tab.$prefix,
  347. tokens-mdc-secondary-navigation-tab.get-token-slots()
  348. ) {
  349. @include token-utils.create-token-slot(min-height, container-height);
  350. }
  351. }
  352. }
  353. // Structural styles for the element that wraps the paginated container's content.
  354. // Include a selector for an inverted header if the header may be optionally positioned on the
  355. // bottom of the content.
  356. @mixin paginated-tab-header-container($inverted-header-selector: null) {
  357. display: flex;
  358. flex-grow: 1;
  359. overflow: hidden;
  360. z-index: 1;
  361. @include token-utils.use-tokens(
  362. tokens-mat-tab-header.$prefix,
  363. tokens-mat-tab-header.get-token-slots()
  364. ) {
  365. border-bottom-style: solid;
  366. @include token-utils.create-token-slot(border-bottom-width, divider-height);
  367. @include token-utils.create-token-slot(border-bottom-color, divider-color);
  368. @if ($inverted-header-selector) {
  369. #{$inverted-header-selector} & {
  370. border-bottom: none;
  371. border-top-style: solid;
  372. @include token-utils.create-token-slot(border-top-width, divider-height);
  373. @include token-utils.create-token-slot(border-top-color, divider-color);
  374. }
  375. }
  376. }
  377. }
  378. @mixin paginated-tab-header-with-background($header-selector, $tab-selector) {
  379. &.mat-tabs-with-background {
  380. @include token-utils.use-tokens(
  381. tokens-mat-tab-header-with-background.$prefix,
  382. tokens-mat-tab-header-with-background.get-token-slots()
  383. ) {
  384. // Note that these selectors target direct descendants so
  385. // that the styles don't apply to any nested tab groups.
  386. > #{$header-selector}, > .mat-mdc-tab-header-pagination {
  387. // Set background color for the tab group
  388. @include token-utils.create-token-slot(background-color, background-color);
  389. }
  390. // Note: this is only scoped to primary, because the legacy tabs had the incorrect behavior
  391. // where setting both a background and `mat-accent` would add the background, but keep
  392. // accent on the selected tab. There are some internal apps whose design depends on this now
  393. // so we have to replicate it here.
  394. &.mat-primary > #{$header-selector} {
  395. // Set labels to contrast against background
  396. #{$tab-selector} .mdc-tab__text-label {
  397. @include token-utils.create-token-slot(color, foreground-color);
  398. }
  399. .mdc-tab-indicator__content--underline {
  400. @include token-utils.create-token-slot(border-color, foreground-color);
  401. }
  402. }
  403. &:not(.mat-primary) > #{$header-selector} {
  404. #{$tab-selector}:not(.mdc-tab--active) {
  405. .mdc-tab__text-label {
  406. @include token-utils.create-token-slot(color, foreground-color);
  407. }
  408. .mdc-tab-indicator__content--underline {
  409. @include token-utils.create-token-slot(border-color, foreground-color);
  410. }
  411. }
  412. }
  413. > #{$header-selector}, > .mat-mdc-tab-header-pagination {
  414. .mat-mdc-tab-header-pagination-chevron,
  415. .mat-focus-indicator::before {
  416. @include token-utils.create-token-slot(border-color, foreground-color);
  417. }
  418. .mat-ripple-element, .mdc-tab__ripple::before {
  419. @include token-utils.create-token-slot(background-color, foreground-color);
  420. }
  421. .mat-mdc-tab-header-pagination-chevron {
  422. @include token-utils.create-token-slot(color, foreground-color);
  423. }
  424. }
  425. }
  426. }
  427. }