Collapse.astro 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. ---
  2. interface Props {
  3. title: string;
  4. }
  5. const { title } = Astro.props as Props;
  6. ---
  7. <div class="accordion group relative rounded-md border border-neutral-200 my-3">
  8. <button
  9. class="accordion__button flex w-full flex-1 items-center justify-between gap-2 p-3 text-left font-medium transition sm:px-4 text-xl"
  10. type="button"
  11. id={`${title} accordion menu button`}
  12. aria-expanded="false"
  13. aria-controls={`${title} accordion menu content`}
  14. >
  15. {title}
  16. <svg
  17. class="accordion__chevron h-7 w-7 shrink-0 transition-transform text-lime-900"
  18. aria-hidden="true"
  19. width="24"
  20. height="24"
  21. viewBox="0 0 24 24"
  22. ><path
  23. fill="none"
  24. stroke="currentColor"
  25. stroke-linecap="round"
  26. stroke-linejoin="round"
  27. stroke-width="2"
  28. d="m6 9l6 6l6-6"></path></svg
  29. >
  30. </button>
  31. <div
  32. id={`${title} accordion menu content`}
  33. aria-labelledby={`${title} accordion menu button`}
  34. class="accordion__content hidden max-h-0 overflow-hidden transition-all duration-300 ease-in-out sm:px-4"
  35. >
  36. <slot />
  37. </div>
  38. </div>
  39. <script>
  40. function accordionSetup() {
  41. const accordionMenus = document.querySelectorAll(
  42. ".accordion"
  43. ) as NodeListOf<HTMLElement>;
  44. accordionMenus.forEach((accordionMenu) => {
  45. const accordionButton = accordionMenu.querySelector(
  46. ".accordion__button"
  47. ) as HTMLElement;
  48. const accordionChevron = accordionMenu.querySelector(
  49. ".accordion__chevron"
  50. ) as HTMLElement;
  51. const accordionContent = accordionMenu.querySelector(
  52. ".accordion__content"
  53. ) as HTMLElement;
  54. if (accordionButton && accordionContent && accordionChevron) {
  55. accordionButton.addEventListener("click", (event) => {
  56. if (!accordionMenu.classList.contains("active")) {
  57. // if accordion is currently closed, so open it
  58. accordionMenu.classList.add("active");
  59. accordionButton.setAttribute("aria-expanded", "true");
  60. // set max-height to the height of the accordion content
  61. // this makes it animate properly
  62. accordionContent.classList.remove("hidden");
  63. accordionContent.style.maxHeight =
  64. accordionContent.scrollHeight + "px";
  65. accordionChevron.classList.add("rotate-180");
  66. } else {
  67. // accordion is currently open, so close it
  68. accordionMenu.classList.remove("active");
  69. accordionButton.setAttribute("aria-expanded", "false");
  70. // set max-height to the height of the accordion content
  71. // this makes it animate properly
  72. accordionContent.style.maxHeight = "0px";
  73. accordionChevron.classList.remove("rotate-180");
  74. // delay to allow close animation
  75. setTimeout(() => {
  76. accordionContent.classList.add("hidden");
  77. }, 300);
  78. }
  79. event.preventDefault();
  80. return false;
  81. });
  82. }
  83. });
  84. }
  85. // runs on initial page load
  86. accordionSetup();
  87. // runs on view transitions navigation
  88. document.addEventListener("astro:after-swap", accordionSetup);
  89. </script>