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.

Sidebar.tsx 4.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 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 { withTheme } from '@emotion/react';
  21. import styled from '@emotion/styled';
  22. import {
  23. BareButton,
  24. LAYOUT_FOOTER_HEIGHT,
  25. LAYOUT_GLOBAL_NAV_HEIGHT,
  26. LAYOUT_PROJECT_NAV_HEIGHT,
  27. SubnavigationGroup,
  28. SubnavigationItem,
  29. themeBorder,
  30. themeColor,
  31. } from 'design-system';
  32. import * as React from 'react';
  33. import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
  34. import { translate } from '../../../helpers/l10n';
  35. import useFollowScroll from '../../../hooks/useFollowScroll';
  36. import { MeasureEnhanced } from '../../../types/types';
  37. import { PROJECT_OVERVEW, Query, isProjectOverview, populateDomainsFromMeasures } from '../utils';
  38. import DomainSubnavigation from './DomainSubnavigation';
  39. import { Domain } from '../../../types/measures';
  40. interface Props {
  41. measures: MeasureEnhanced[];
  42. selectedMetric: string;
  43. showFullMeasures: boolean;
  44. updateQuery: (query: Partial<Query>) => void;
  45. }
  46. export default function Sidebar(props: Readonly<Props>) {
  47. const { showFullMeasures, updateQuery, selectedMetric, measures } = props;
  48. const { top: topScroll, scrolledOnce } = useFollowScroll();
  49. const domains = populateDomainsFromMeasures(measures);
  50. const handleChangeMetric = React.useCallback(
  51. (metric: string) => {
  52. updateQuery({ metric });
  53. },
  54. [updateQuery],
  55. );
  56. const handleProjectOverviewClick = () => {
  57. handleChangeMetric(PROJECT_OVERVEW);
  58. };
  59. const distanceFromBottom = topScroll + window.innerHeight - document.body.scrollHeight;
  60. const footerVisibleHeight =
  61. (scrolledOnce &&
  62. (distanceFromBottom > -LAYOUT_FOOTER_HEIGHT
  63. ? LAYOUT_FOOTER_HEIGHT + distanceFromBottom
  64. : 0)) ||
  65. 0;
  66. return (
  67. <StyledSidebar
  68. className="sw-col-span-3"
  69. style={{
  70. top: `${LAYOUT_GLOBAL_NAV_HEIGHT + LAYOUT_PROJECT_NAV_HEIGHT}px`,
  71. height: `calc(
  72. 100vh - ${LAYOUT_GLOBAL_NAV_HEIGHT + LAYOUT_PROJECT_NAV_HEIGHT + footerVisibleHeight}px
  73. )`,
  74. }}
  75. >
  76. <section
  77. className="sw-flex sw-flex-col sw-gap-4 sw-p-4"
  78. aria-label={translate('component_measures.navigation')}
  79. >
  80. <A11ySkipTarget
  81. anchor="measures_filters"
  82. label={translate('component_measures.skip_to_navigation')}
  83. weight={10}
  84. />
  85. <SubnavigationGroup>
  86. <SubnavigationItem
  87. active={isProjectOverview(selectedMetric)}
  88. onClick={handleProjectOverviewClick}
  89. >
  90. <BareButton aria-current={isProjectOverview(selectedMetric)}>
  91. {translate('component_measures.overview', PROJECT_OVERVEW, 'subnavigation')}
  92. </BareButton>
  93. </SubnavigationItem>
  94. </SubnavigationGroup>
  95. {domains.map((domain: Domain) => (
  96. <DomainSubnavigation
  97. domain={domain}
  98. key={domain.name}
  99. onChange={handleChangeMetric}
  100. open={isDomainSelected(selectedMetric, domain)}
  101. selected={selectedMetric}
  102. showFullMeasures={showFullMeasures}
  103. />
  104. ))}
  105. </section>
  106. </StyledSidebar>
  107. );
  108. }
  109. function isDomainSelected(selectedMetric: string, domain: Domain) {
  110. return (
  111. selectedMetric === domain.name ||
  112. domain.measures.some((measure) => measure.metric.key === selectedMetric)
  113. );
  114. }
  115. const StyledSidebar = withTheme(styled.div`
  116. box-sizing: border-box;
  117. margin-top: -2rem;
  118. background-color: ${themeColor('filterbar')};
  119. border-right: ${themeBorder('default', 'filterbarBorder')};
  120. position: sticky;
  121. overflow-x: hidden;
  122. `);