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.

TextAccordion.tsx 3.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  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 styled from '@emotion/styled';
  21. import classNames from 'classnames';
  22. import { uniqueId } from 'lodash';
  23. import React, { ReactNode } from 'react';
  24. import tw from 'twin.macro';
  25. import { themeColor } from '../helpers';
  26. import { BareButton } from '../sonar-aligned/components/buttons';
  27. import { Note } from './Text';
  28. import { OpenCloseIndicator } from './icons';
  29. interface Props {
  30. ariaLabel: string;
  31. children: ReactNode;
  32. className?: string;
  33. data?: string;
  34. onClick: (data?: string) => void;
  35. open: boolean;
  36. renderHeader?: () => ReactNode;
  37. title: ReactNode;
  38. }
  39. export function TextAccordion(props: Readonly<Props>) {
  40. const [hoveringInner, setHoveringInner] = React.useState(false);
  41. const { className, open, renderHeader, title, ariaLabel } = props;
  42. const id = React.useMemo(() => uniqueId('accordion-'), []);
  43. function handleClick() {
  44. props.onClick(props.data);
  45. }
  46. function onDetailEnter() {
  47. setHoveringInner(true);
  48. }
  49. function onDetailLeave() {
  50. setHoveringInner(false);
  51. }
  52. return (
  53. <StyledAccordion
  54. className={classNames('it__text-accordion', className, {
  55. 'no-hover': hoveringInner,
  56. })}
  57. >
  58. <Note as="h3">
  59. <BareButton
  60. aria-controls={`${id}-panel`}
  61. aria-expanded={open}
  62. aria-label={ariaLabel}
  63. className="sw-flex sw-items-center sw-px-2 sw-py-2 sw-box-border sw-w-full"
  64. id={`${id}-header`}
  65. onClick={handleClick}
  66. >
  67. <AccordionTitle>
  68. <OpenCloseIndicator className="sw-mr-1" open={open} />
  69. {title}
  70. </AccordionTitle>
  71. {renderHeader?.()}
  72. </BareButton>
  73. </Note>
  74. {open && (
  75. <AccordionContent onMouseEnter={onDetailEnter} onMouseLeave={onDetailLeave} role="region">
  76. {props.children}
  77. </AccordionContent>
  78. )}
  79. </StyledAccordion>
  80. );
  81. }
  82. const StyledAccordion = styled.div`
  83. transition: border-color 0.3s ease;
  84. `;
  85. const AccordionTitle = styled.span`
  86. cursor: pointer;
  87. position: relative;
  88. display: inline-flex;
  89. align-items: center;
  90. font-weight: bold;
  91. vertical-align: middle;
  92. transition: color 0.3s ease;
  93. ${tw`sw-select-none`}
  94. ${tw`sw-pt-4 sw-px-page sw-pb-2`}
  95. &:hover {
  96. color: ${themeColor('linkDefault')};
  97. }
  98. `;
  99. const AccordionContent = styled.div`
  100. ${tw`sw-pl-10`}
  101. `;