You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

NavBarTabs.tsx 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import styled from '@emotion/styled';
  21. import classNames from 'classnames';
  22. import React from 'react';
  23. import tw, { theme } from 'twin.macro';
  24. import { themeBorder, themeColor, themeContrast } from '../helpers/theme';
  25. import { isDefined } from '../helpers/types';
  26. import NavLink, { NavLinkProps } from './NavLink';
  27. import { Tooltip } from './Tooltip';
  28. import { ChevronDownIcon } from './icons/ChevronDownIcon';
  29. interface Props extends React.HTMLAttributes<HTMLUListElement> {
  30. children?: React.ReactNode;
  31. className?: string;
  32. }
  33. export function NavBarTabs({ children, className, ...other }: Props) {
  34. return (
  35. <ul className={`sw-flex sw-items-end sw-gap-8 ${className ?? ''}`} {...other}>
  36. {children}
  37. </ul>
  38. );
  39. }
  40. interface NavBarTabLinkProps extends Omit<NavLinkProps, 'children'> {
  41. active?: boolean;
  42. children?: React.ReactNode;
  43. className?: string;
  44. text: string;
  45. withChevron?: boolean;
  46. }
  47. export function NavBarTabLink(props: NavBarTabLinkProps) {
  48. const { active, children, className, text, withChevron = false, ...linkProps } = props;
  49. return (
  50. <NavBarTabLinkWrapper>
  51. <NavLink
  52. className={({ isActive }) =>
  53. classNames(
  54. 'sw-flex sw-items-center',
  55. { active: isDefined(active) ? active : isActive },
  56. className,
  57. )
  58. }
  59. {...linkProps}
  60. >
  61. <span className="sw-inline-block sw-text-center" data-text={text}>
  62. {text}
  63. </span>
  64. {children}
  65. {withChevron && <ChevronDownIcon className="sw-ml-1" />}
  66. </NavLink>
  67. </NavBarTabLinkWrapper>
  68. );
  69. }
  70. export function DisabledTabLink(props: { label: string; overlay: React.ReactNode }) {
  71. return (
  72. <NavBarTabLinkWrapper>
  73. <Tooltip overlay={props.overlay}>
  74. <a aria-disabled="true" className="disabled-link" role="link">
  75. {props.label}
  76. </a>
  77. </Tooltip>
  78. </NavBarTabLinkWrapper>
  79. );
  80. }
  81. // Styling for <NavLink> due to its special className function, it conflicts when styled with Emotion.
  82. const NavBarTabLinkWrapper = styled.li`
  83. ${tw`sw-body-md`};
  84. & > a {
  85. ${tw`sw-pb-3`};
  86. ${tw`sw-block`};
  87. ${tw`sw-box-border`};
  88. ${tw`sw-transition-none`};
  89. color: ${themeContrast('buttonSecondary')};
  90. text-decoration: none;
  91. border-bottom: ${themeBorder('xsActive', 'transparent')};
  92. padding-bottom: calc(${theme('spacing.3')} + 1px); // 12px spacing + 3px border + 1px = 16px
  93. }
  94. & > a.active,
  95. & > a:active,
  96. & > a:hover,
  97. & > a:focus {
  98. border-bottom-color: ${themeColor('tabBorder')};
  99. }
  100. & > a.active > span[data-text],
  101. & > a:active > span {
  102. ${tw`sw-body-md-highlight`};
  103. }
  104. // This is a hack to have a link take the space of the bold font, so when active other ones are not moving
  105. & > a > span[data-text]::before {
  106. ${tw`sw-block`};
  107. ${tw`sw-body-md-highlight`};
  108. ${tw`sw-h-0`};
  109. ${tw`sw-overflow-hidden`};
  110. ${tw`sw-invisible`};
  111. content: attr(data-text);
  112. }
  113. &:has(a.disabled-link) > a,
  114. &:has(a.disabled-link) > a:hover,
  115. &:has(a.disabled-link) > a.hover,
  116. &:has(a.disabled-link)[aria-expanded='true'] {
  117. ${tw`sw-cursor-default`};
  118. border-bottom: ${themeBorder('xsActive', 'transparent', 1)};
  119. color: ${themeContrast('subnavigationDisabled')};
  120. }
  121. `;