PagePathHierarchicalLink.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import { type FC, type JSX, memo, useCallback } from 'react';
  2. import Link from 'next/link';
  3. import urljoin from 'url-join';
  4. import type LinkedPagePath from '~/models/linked-page-path';
  5. import styles from './PagePathHierarchicalLink.module.scss';
  6. type PagePathHierarchicalLinkProps = {
  7. linkedPagePath: LinkedPagePath;
  8. linkedPagePathByHtml?: LinkedPagePath;
  9. basePath?: string;
  10. isInTrash?: boolean;
  11. isIconHidden?: boolean;
  12. // !!INTERNAL USE ONLY!!
  13. isInnerElem?: boolean;
  14. };
  15. export const PagePathHierarchicalLink: FC<PagePathHierarchicalLinkProps> = memo(
  16. (props: PagePathHierarchicalLinkProps): JSX.Element => {
  17. const {
  18. linkedPagePath,
  19. linkedPagePathByHtml,
  20. basePath,
  21. isInTrash,
  22. isInnerElem,
  23. } = props;
  24. const isIconHidden = props.isIconHidden ?? false;
  25. // eslint-disable-next-line react/prop-types
  26. const RootElm = useCallback(
  27. ({ children }) => {
  28. return isInnerElem ? (
  29. <>{children}</>
  30. ) : (
  31. <span className="text-break" id="grw-page-path-hierarchical-link">
  32. {children}
  33. </span>
  34. );
  35. },
  36. [isInnerElem],
  37. );
  38. // render root element
  39. if (linkedPagePath.isRoot) {
  40. if (basePath != null || isIconHidden) {
  41. return <></>;
  42. }
  43. return isInTrash ? (
  44. <RootElm>
  45. <span className="path-segment">
  46. <Link href="/trash" prefetch={false}>
  47. <span
  48. className={`material-symbols-outlined ${styles['material-symbols-outlined']}`}
  49. >
  50. delete
  51. </span>
  52. </Link>
  53. </span>
  54. <span className={`separator ${styles.separator}`}>
  55. <a href="/">/</a>
  56. </span>
  57. </RootElm>
  58. ) : (
  59. <RootElm>
  60. <span className="path-segment">
  61. <Link href="/" prefetch={false}>
  62. <span
  63. className={`material-symbols-outlined ${styles['material-symbols-outlined']}`}
  64. >
  65. home
  66. </span>
  67. <span className={`separator ${styles.separator}`}>/</span>
  68. </Link>
  69. </span>
  70. </RootElm>
  71. );
  72. }
  73. const isParentExists = linkedPagePath.parent != null;
  74. const isParentRoot = linkedPagePath.parent?.isRoot;
  75. const isSeparatorRequired = isParentExists && !isParentRoot;
  76. const shouldDangerouslySetInnerHTML = linkedPagePathByHtml != null;
  77. const href = encodeURI(urljoin(basePath || '/', linkedPagePath.href));
  78. return (
  79. <RootElm>
  80. {isParentExists && (
  81. <PagePathHierarchicalLink
  82. linkedPagePath={linkedPagePath.parent}
  83. linkedPagePathByHtml={linkedPagePathByHtml?.parent}
  84. basePath={basePath}
  85. isInTrash={isInTrash || linkedPagePath.isInTrash}
  86. isInnerElem
  87. isIconHidden={isIconHidden}
  88. />
  89. )}
  90. {isSeparatorRequired && (
  91. <span className={`separator ${styles.separator}`}>/</span>
  92. )}
  93. <Link href={href} prefetch={false} legacyBehavior>
  94. {shouldDangerouslySetInnerHTML ? (
  95. // biome-ignore-start lint/a11y/useValidAnchor: ignore
  96. <a
  97. className="page-segment"
  98. // biome-ignore lint/security/noDangerouslySetInnerHtml: ignore
  99. dangerouslySetInnerHTML={{
  100. __html: linkedPagePathByHtml.pathName,
  101. }}
  102. ></a>
  103. ) : (
  104. <a className="page-segment">{linkedPagePath.pathName}</a>
  105. // biome-ignore-end lint/a11y/useValidAnchor: ignore
  106. )}
  107. </Link>
  108. </RootElm>
  109. );
  110. },
  111. );