index.tsx 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. "use client";
  2. import clsx from "clsx";
  3. import type { ComponentProps, ElementType, ReactNode } from "react";
  4. import { use } from "react";
  5. import { OverlayTriggerStateContext } from "react-aria-components";
  6. import styles from "./index.module.scss";
  7. import { Button } from "../unstyled/Button/index.js";
  8. import { Link } from "../unstyled/Link/index.js";
  9. export const VARIANTS = ["primary", "secondary", "tertiary"] as const;
  10. type OwnProps = {
  11. variant?: (typeof VARIANTS)[number] | undefined;
  12. icon?: ReactNode | undefined;
  13. title?: ReactNode | undefined;
  14. toolbar?: ReactNode | ReactNode[] | undefined;
  15. footer?: ReactNode | undefined;
  16. nonInteractive?: boolean | undefined;
  17. toolbarClassName?: string | undefined;
  18. toolbarAlwaysOnTop?: boolean | undefined;
  19. };
  20. export type Props<T extends ElementType> = Omit<
  21. ComponentProps<T>,
  22. keyof OwnProps
  23. > &
  24. OwnProps;
  25. export const Card = (
  26. props:
  27. | (Props<"div"> & { nonInteractive?: true })
  28. | Props<typeof Link>
  29. | Props<typeof Button>,
  30. ) => {
  31. const overlayState = use(OverlayTriggerStateContext);
  32. if (props.nonInteractive) {
  33. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  34. const { nonInteractive, ...otherProps } = props;
  35. return <div {...cardProps(otherProps)} />;
  36. } else if ("href" in props) {
  37. return <Link {...cardProps(props)} />;
  38. } else if (overlayState !== null || "onPress" in props) {
  39. return <Button {...cardProps(props)} />;
  40. } else {
  41. return <div {...cardProps(props)} />;
  42. }
  43. };
  44. const cardProps = <T extends ElementType>({
  45. className,
  46. variant = "secondary",
  47. children,
  48. icon,
  49. title,
  50. toolbar,
  51. footer,
  52. toolbarClassName,
  53. toolbarAlwaysOnTop,
  54. ...props
  55. }: Props<T>) => ({
  56. ...props,
  57. "data-variant": variant,
  58. className: clsx(styles.card, className),
  59. children: (
  60. <>
  61. <div className={styles.cardHoverBackground} />
  62. {(Boolean(icon) || Boolean(title) || Boolean(toolbar)) && (
  63. <div className={styles.header}>
  64. <h2 className={styles.title}>
  65. {icon && <div className={styles.icon}>{icon}</div>}
  66. {title}
  67. </h2>
  68. {toolbar && (
  69. <div
  70. className={clsx(styles.toolbar, toolbarClassName)}
  71. data-always-on-top={toolbarAlwaysOnTop ? "" : undefined}
  72. >
  73. {toolbar}
  74. </div>
  75. )}
  76. </div>
  77. )}
  78. {children}
  79. {footer && <div className={styles.footer}>{footer}</div>}
  80. </>
  81. ),
  82. });