{"version":3,"file":"InboxNotification.mjs","sources":["../../src/components/InboxNotification.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n  InboxNotificationCustomData,\n  InboxNotificationData,\n  InboxNotificationThreadData,\n} from \"@liveblocks/core\";\nimport { assertNever, console, kInternal } from \"@liveblocks/core\";\nimport { useLiveblocksContextBundle } from \"@liveblocks/react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { TooltipProvider } from \"@radix-ui/react-tooltip\";\nimport type {\n  ComponentProps,\n  ComponentPropsWithoutRef,\n  ComponentType,\n  MouseEvent as ReactMouseEvent,\n  ReactNode,\n  SyntheticEvent,\n} from \"react\";\nimport React, { forwardRef, useCallback, useMemo, useState } from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { useComponents } from \"../components\";\nimport { CheckIcon } from \"../icons/Check\";\nimport { EllipsisIcon } from \"../icons/Ellipsis\";\nimport { MissingIcon } from \"../icons/Missing\";\nimport type {\n  CommentOverrides,\n  GlobalOverrides,\n  InboxNotificationOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport type { SlotProp } from \"../types\";\nimport { classNames } from \"../utils/class-names\";\nimport { generateURL } from \"../utils/url\";\nimport { Avatar, type AvatarProps } from \"./internal/Avatar\";\nimport { Button } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport {\n  generateInboxNotificationThreadContents,\n  INBOX_NOTIFICATION_THREAD_MAX_COMMENTS,\n  InboxNotificationComment,\n} from \"./internal/InboxNotificationThread\";\nimport { List } from \"./internal/List\";\nimport { Room } from \"./internal/Room\";\nimport { Tooltip } from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\ntype Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;\n\ntype ComponentTypeWithRef<\n  T extends keyof JSX.IntrinsicElements,\n  P,\n> = ComponentType<P & Pick<ComponentProps<T>, \"ref\">>;\n\ntype InboxNotificationKinds = Record<\n  `$${string}`,\n  ComponentTypeWithRef<\n    \"a\",\n    Optional<InboxNotificationCustomProps, \"title\" | \"children\">\n  >\n> & {\n  thread: ComponentTypeWithRef<\"a\", InboxNotificationThreadProps>;\n};\n\ninterface InboxNotificationSharedProps {\n  /**\n   * How to show or hide the actions.\n   */\n  showActions?: boolean | \"hover\";\n}\n\nexport interface InboxNotificationProps\n  extends Omit<ComponentPropsWithoutRef<\"a\">, \"title\">,\n    InboxNotificationSharedProps {\n  /**\n   * The inbox notification to display.\n   */\n  inboxNotification: InboxNotificationData;\n\n  /**\n   * Override specific kinds of inbox notifications.\n   */\n  kinds?: Partial<InboxNotificationKinds>;\n\n  /**\n   * Override the component's strings.\n   */\n  overrides?: Partial<\n    GlobalOverrides & InboxNotificationOverrides & CommentOverrides\n  >;\n\n  /**\n   * Override the component's components.\n   */\n  components?: Partial<GlobalComponents>;\n}\n\nexport interface InboxNotificationThreadProps\n  extends Omit<InboxNotificationProps, \"kinds\" | \"children\">,\n    InboxNotificationSharedProps {\n  /**\n   * The inbox notification to display.\n   */\n  inboxNotification: InboxNotificationThreadData;\n\n  /**\n   * Whether to show the room name in the title.\n   */\n  showRoomName?: boolean;\n}\n\nexport interface InboxNotificationCustomProps\n  extends Omit<InboxNotificationProps, \"kinds\">,\n    InboxNotificationSharedProps,\n    SlotProp {\n  /**\n   * The inbox notification to display.\n   */\n  inboxNotification: InboxNotificationCustomData;\n\n  /**\n   * The inbox notification's content.\n   */\n  children: ReactNode;\n\n  /**\n   * The inbox notification's title.\n   */\n  title: ReactNode;\n\n  /**\n   * The inbox notification's aside content.\n   * Can be combined with `InboxNotification.Icon` or `InboxNotification.Avatar` to easily follow default styles.\n   */\n  aside?: ReactNode;\n\n  /**\n   * Whether to mark the inbox notification as read when clicked.\n   */\n  markAsReadOnClick?: boolean;\n}\n\ninterface InboxNotificationLayoutProps\n  extends Omit<ComponentPropsWithoutRef<\"a\">, \"title\">,\n    InboxNotificationSharedProps,\n    SlotProp {\n  inboxNotification: InboxNotificationData;\n  aside: ReactNode;\n  title: ReactNode;\n  date: Date | string | number;\n  unread?: boolean;\n  overrides?: Partial<GlobalOverrides & InboxNotificationOverrides>;\n  components?: Partial<GlobalComponents>;\n  markAsReadOnClick?: boolean;\n}\n\nexport type InboxNotificationIconProps = ComponentProps<\"div\">;\n\nexport type InboxNotificationAvatarProps = AvatarProps;\n\nconst InboxNotificationLayout = forwardRef<\n  HTMLAnchorElement,\n  InboxNotificationLayoutProps\n>(\n  (\n    {\n      inboxNotification,\n      children,\n      aside,\n      title,\n      date,\n      unread,\n      markAsReadOnClick,\n      onClick,\n      href,\n      showActions,\n      overrides,\n      components,\n      className,\n      asChild,\n      ...props\n    },\n    forwardedRef\n  ) => {\n    const $ = useOverrides(overrides);\n    const { Anchor } = useComponents(components);\n    const Component = asChild ? Slot : Anchor;\n    const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n    const { useMarkInboxNotificationAsRead } = useLiveblocksContextBundle();\n    const markInboxNotificationAsRead = useMarkInboxNotificationAsRead();\n\n    const handleClick = useCallback(\n      (event: ReactMouseEvent<HTMLAnchorElement, MouseEvent>) => {\n        onClick?.(event);\n\n        const shouldMarkAsReadOnClick = markAsReadOnClick ?? Boolean(href);\n\n        if (unread && shouldMarkAsReadOnClick) {\n          markInboxNotificationAsRead(inboxNotification.id);\n        }\n      },\n      [\n        href,\n        inboxNotification.id,\n        markAsReadOnClick,\n        markInboxNotificationAsRead,\n        onClick,\n        unread,\n      ]\n    );\n\n    const stopPropagation = useCallback((event: SyntheticEvent) => {\n      event.stopPropagation();\n    }, []);\n\n    const preventDefaultAndStopPropagation = useCallback(\n      (event: SyntheticEvent) => {\n        event.preventDefault();\n        event.stopPropagation();\n      },\n      []\n    );\n\n    const handleMoreClick = useCallback((event: ReactMouseEvent) => {\n      event.preventDefault();\n      event.stopPropagation();\n      setMoreActionOpen((open) => !open);\n    }, []);\n\n    const handleMarkAsRead = useCallback(() => {\n      markInboxNotificationAsRead(inboxNotification.id);\n    }, [inboxNotification.id, markInboxNotificationAsRead]);\n\n    return (\n      <TooltipProvider>\n        <Component\n          className={classNames(\n            \"lb-root lb-inbox-notification\",\n            showActions === \"hover\" &&\n              \"lb-inbox-notification:show-actions-hover\",\n            isMoreActionOpen && \"lb-inbox-notification:action-open\",\n            className\n          )}\n          dir={$.dir}\n          data-unread={unread ? \"\" : undefined}\n          data-kind={inboxNotification.kind}\n          onClick={handleClick}\n          href={href}\n          {...props}\n          ref={forwardedRef}\n        >\n          {aside && <div className=\"lb-inbox-notification-aside\">{aside}</div>}\n          <div className=\"lb-inbox-notification-content\">\n            <div className=\"lb-inbox-notification-header\">\n              <span className=\"lb-inbox-notification-title\">{title}</span>\n              <div className=\"lb-inbox-notification-details\">\n                <span className=\"lb-inbox-notification-details-labels\">\n                  <Timestamp\n                    locale={$.locale}\n                    date={date}\n                    className=\"lb-inbox-notification-date\"\n                  />\n                  {unread && (\n                    <span\n                      className=\"lb-inbox-notification-unread-indicator\"\n                      role=\"presentation\"\n                    />\n                  )}\n                </span>\n              </div>\n              {showActions && (\n                <div className=\"lb-inbox-notification-actions\">\n                  <Dropdown\n                    open={isMoreActionOpen}\n                    onOpenChange={setMoreActionOpen}\n                    align=\"end\"\n                    content={\n                      <>\n                        <DropdownItem\n                          onSelect={handleMarkAsRead}\n                          onClick={stopPropagation}\n                          disabled={!unread}\n                        >\n                          <CheckIcon className=\"lb-dropdown-item-icon\" />\n                          {$.INBOX_NOTIFICATION_MARK_AS_READ}\n                        </DropdownItem>\n                      </>\n                    }\n                  >\n                    <Tooltip content={$.INBOX_NOTIFICATION_MORE}>\n                      <DropdownTrigger asChild>\n                        <Button\n                          className=\"lb-inbox-notification-action\"\n                          onClick={handleMoreClick}\n                          onPointerDown={preventDefaultAndStopPropagation}\n                          onPointerUp={preventDefaultAndStopPropagation}\n                          aria-label={$.INBOX_NOTIFICATION_MORE}\n                        >\n                          <EllipsisIcon className=\"lb-button-icon\" />\n                        </Button>\n                      </DropdownTrigger>\n                    </Tooltip>\n                  </Dropdown>\n                </div>\n              )}\n            </div>\n            <div className=\"lb-inbox-notification-body\">{children}</div>\n          </div>\n        </Component>\n      </TooltipProvider>\n    );\n  }\n);\n\nfunction InboxNotificationIcon({\n  className,\n  ...props\n}: InboxNotificationIconProps) {\n  return (\n    <div\n      className={classNames(\"lb-inbox-notification-icon\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction InboxNotificationAvatar({\n  className,\n  ...props\n}: InboxNotificationAvatarProps) {\n  return (\n    <Avatar\n      className={classNames(\"lb-inbox-notification-avatar\", className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * Displays a thread inbox notification.\n */\nconst InboxNotificationThread = forwardRef<\n  HTMLAnchorElement,\n  InboxNotificationThreadProps\n>(\n  (\n    {\n      inboxNotification,\n      href,\n      showRoomName = true,\n      showActions = \"hover\",\n      overrides,\n      ...props\n    },\n    forwardedRef\n  ) => {\n    const $ = useOverrides(overrides);\n    const {\n      useRoomInfo,\n      useInboxNotificationThread,\n      [kInternal]: { useCurrentUserId },\n    } = useLiveblocksContextBundle();\n    const thread = useInboxNotificationThread(inboxNotification.id);\n    const currentUserId = useCurrentUserId();\n    // TODO: If you provide `href` (or plan to), we shouldn't run this hook. We should find a way to conditionally run it.\n    //       Because of batching and the fact that the same hook will be called within <Room /> in the notification's title,\n    //       it's not a big deal, the only scenario where it would be superfluous would be if the user provides their own\n    //       `href` AND disables room names in the title via `showRoomName={false}`.\n    const { info } = useRoomInfo(inboxNotification.roomId);\n    const { unread, date, aside, title, content, commentId } = useMemo(() => {\n      const contents = generateInboxNotificationThreadContents(\n        inboxNotification,\n        thread,\n        currentUserId ?? \"\"\n      );\n\n      switch (contents.type) {\n        case \"comments\": {\n          const reversedUserIds = [...contents.userIds].reverse();\n          const firstUserId = reversedUserIds[0];\n\n          const aside = <InboxNotificationAvatar userId={firstUserId} />;\n          const title = $.INBOX_NOTIFICATION_THREAD_COMMENTS_LIST(\n            <List\n              values={reversedUserIds.map((userId, index) => (\n                <User\n                  key={userId}\n                  userId={userId}\n                  capitalize={index === 0}\n                  replaceSelf\n                />\n              ))}\n              formatRemaining={$.LIST_REMAINING_USERS}\n              truncate={INBOX_NOTIFICATION_THREAD_MAX_COMMENTS - 1}\n            />,\n            showRoomName ? <Room roomId={thread.roomId} /> : undefined,\n            reversedUserIds.length\n          );\n          const content = (\n            <div className=\"lb-inbox-notification-comments\">\n              {contents.comments.map((comment) => (\n                <InboxNotificationComment\n                  key={comment.id}\n                  comment={comment}\n                  showHeader={contents.comments.length > 1}\n                  overrides={overrides}\n                />\n              ))}\n            </div>\n          );\n\n          return {\n            unread: contents.unread,\n            date: contents.date,\n            aside,\n            title,\n            content,\n            threadId: thread.id,\n            commentId: contents.comments[contents.comments.length - 1].id,\n          };\n        }\n\n        case \"mention\": {\n          const mentionUserId = contents.userIds[0];\n          const mentionComment = contents.comments[0];\n\n          const aside = <InboxNotificationAvatar userId={mentionUserId} />;\n          const title = $.INBOX_NOTIFICATION_THREAD_MENTION(\n            <User key={mentionUserId} userId={mentionUserId} capitalize />,\n            showRoomName ? <Room roomId={thread.roomId} /> : undefined\n          );\n          const content = (\n            <div className=\"lb-inbox-notification-comments\">\n              <InboxNotificationComment\n                key={mentionComment.id}\n                comment={mentionComment}\n                showHeader={false}\n              />\n            </div>\n          );\n\n          return {\n            unread: contents.unread,\n            date: contents.date,\n            aside,\n            title,\n            content,\n            threadId: thread.id,\n            commentId: mentionComment.id,\n          };\n        }\n\n        default:\n          return assertNever(\n            contents,\n            \"Unexpected thread inbox notification type\"\n          );\n      }\n    }, [$, currentUserId, inboxNotification, overrides, showRoomName, thread]);\n    // Add the thread ID and comment ID to the `href`.\n    // And use URL from `resolveRoomsInfo` if `href` isn't set.\n    const resolvedHref = useMemo(() => {\n      const resolvedHref = href ?? info?.url;\n\n      return resolvedHref\n        ? generateURL(resolvedHref, undefined, commentId)\n        : undefined;\n    }, [commentId, href, info?.url]);\n\n    return (\n      <InboxNotificationLayout\n        inboxNotification={inboxNotification}\n        aside={aside}\n        title={title}\n        date={date}\n        unread={unread}\n        overrides={overrides}\n        href={resolvedHref}\n        showActions={showActions}\n        markAsReadOnClick={false}\n        {...props}\n        ref={forwardedRef}\n      >\n        {content}\n      </InboxNotificationLayout>\n    );\n  }\n);\n\n/**\n * Displays a custom notification kind.\n */\nconst InboxNotificationCustom = forwardRef<\n  HTMLAnchorElement,\n  InboxNotificationCustomProps\n>(\n  (\n    {\n      inboxNotification,\n      showActions = \"hover\",\n      title,\n      aside,\n      children,\n      overrides,\n      ...props\n    },\n    forwardedRef\n  ) => {\n    const unread = useMemo(() => {\n      return (\n        !inboxNotification.readAt ||\n        inboxNotification.notifiedAt > inboxNotification.readAt\n      );\n    }, [inboxNotification.notifiedAt, inboxNotification.readAt]);\n\n    return (\n      <InboxNotificationLayout\n        inboxNotification={inboxNotification}\n        aside={aside}\n        title={title}\n        date={inboxNotification.notifiedAt}\n        unread={unread}\n        overrides={overrides}\n        showActions={showActions}\n        {...props}\n        ref={forwardedRef}\n      >\n        {children}\n      </InboxNotificationLayout>\n    );\n  }\n);\n\nconst InboxNotificationCustomMissing = forwardRef<\n  HTMLAnchorElement,\n  Omit<InboxNotificationCustomProps, \"children\" | \"title\" | \"aside\">\n>(({ inboxNotification, ...props }, forwardedRef) => {\n  return (\n    <InboxNotificationCustom\n      inboxNotification={inboxNotification}\n      {...props}\n      title={\n        <>\n          Custom notification kind <code>{inboxNotification.kind}</code> is not\n          handled\n        </>\n      }\n      aside={\n        <InboxNotificationIcon>\n          <MissingIcon />\n        </InboxNotificationIcon>\n      }\n      ref={forwardedRef}\n      data-missing=\"\"\n    >\n      {/* TODO: Add link to the docs */}\n      Notifications of this kind won’t be displayed in production. Use the{\" \"}\n      <code>kinds</code> prop to define how they should be rendered.\n    </InboxNotificationCustom>\n  );\n});\n\n// Keeps track of which inbox notification kinds it has warned about already.\nconst inboxNotificationKindsWarnings: Set<string> = new Set();\n\n/**\n * Displays a single inbox notification.\n *\n * @example\n * <>\n *   {inboxNotifications.map((inboxNotification) => (\n *     <InboxNotification\n *       key={inboxNotification.id}\n *       inboxNotification={inboxNotification}\n *       href={`/rooms/${inboxNotification.roomId}`\n *     />\n *   ))}\n * </>\n */\nexport const InboxNotification = Object.assign(\n  forwardRef<HTMLAnchorElement, InboxNotificationProps>(\n    ({ inboxNotification, kinds, ...props }, forwardedRef) => {\n      switch (inboxNotification.kind) {\n        case \"thread\": {\n          const ResolvedInboxNotificationThread =\n            kinds?.thread ?? InboxNotificationThread;\n\n          return (\n            <ResolvedInboxNotificationThread\n              inboxNotification={inboxNotification}\n              {...props}\n              ref={forwardedRef}\n            />\n          );\n        }\n\n        default: {\n          const ResolvedInboxNotificationCustom =\n            kinds?.[inboxNotification.kind];\n\n          if (!ResolvedInboxNotificationCustom) {\n            if (process.env.NODE_ENV !== \"production\") {\n              if (!inboxNotificationKindsWarnings.has(inboxNotification.kind)) {\n                inboxNotificationKindsWarnings.add(inboxNotification.kind);\n                // TODO: Add link to the docs\n                console.warn(\n                  `Custom notification kind \"${inboxNotification.kind}\" is not handled so notifications of this kind will not be displayed in production. Use the kinds prop to define how they should be rendered.`\n                );\n              }\n\n              return (\n                <InboxNotificationCustomMissing\n                  inboxNotification={inboxNotification}\n                  {...props}\n                  ref={forwardedRef}\n                />\n              );\n            } else {\n              // Don't render anything in production if this inbox notification kind is not defined.\n              return null;\n            }\n          }\n\n          return (\n            <ResolvedInboxNotificationCustom\n              inboxNotification={inboxNotification}\n              {...props}\n              ref={forwardedRef}\n            />\n          );\n        }\n      }\n    }\n  ),\n  {\n    Thread: InboxNotificationThread,\n    Custom: InboxNotificationCustom,\n    Icon: InboxNotificationIcon,\n    Avatar: InboxNotificationAvatar,\n  }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAkKA;AAAgC;AAK5B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AAEA;AAAoB;AAEhB;AAEA;AAEA;AACE;AAAgD;AAClD;AACF;AACA;AACE;AACkB;AAClB;AACA;AACA;AACA;AACF;AAGF;AACE;AAAsB;AAGxB;AAAyC;AAErC;AACA;AAAsB;AACxB;AACC;AAGH;AACE;AACA;AACA;AAAiC;AAGnC;AACE;AAAgD;AAGlD;AAEK;AACY;AACT;AAEE;AACkB;AACpB;AACF;AACO;AACoB;AACE;AACpB;AACT;AACI;AACC;AAEM;AAAc;AACxB;AAAc;AACZ;AAAc;AACZ;AAAe;AACf;AAAc;AACZ;AAAe;AACb;AACW;AACV;AACU;AAGT;AACW;AACL;AAMV;AAAc;AACZ;AACO;AACQ;AACR;AAGD;AACW;AACD;AACE;AAEV;AAAoB;AAGzB;AAGD;AAAmB;AACjB;AAAuB;AACrB;AACW;AACD;AACM;AACF;AACC;AAEb;AAAuB;AAQrC;AAAc;AAGrB;AAGN;AAEA;AAA+B;AAC7B;AAEF;AACE;AACG;AAC8D;AACzD;AAGV;AAEA;AAAiC;AAC/B;AAEF;AACE;AACG;AACgE;AAC3D;AAGV;AAKA;AAAgC;AAK5B;AACE;AACA;AACe;AACD;AACd;AACG;AAIL;AACA;AAAM;AACJ;AACA;AACgC;AAElC;AACA;AAKA;AACA;AACE;AAAiB;AACf;AACA;AACiB;AAGnB;AAAuB;AAEnB;AACA;AAEA;AAAe;AAAgC;AAC/C;AAAgB;AACb;AAEI;AACM;AACL;AACsB;AACX;AAEd;AACkB;AACgC;AACrD;AACgB;AAAoB;AAAa;AACjC;AAElB;AACG;AAAc;AAEV;AACc;AACb;AACuC;AACvC;AAMR;AAAO;AACY;AACF;AACf;AACA;AACA;AACiB;AAC0C;AAC7D;AACF;AAGE;AACA;AAEA;AAAe;AAAgC;AAC/C;AAAgB;AACb;AAAU;AAAuB;AAAyB;AAAC;AAC5C;AAAoB;AAAa;AAEnD;AACG;AAAc;AACZ;AACqB;AACX;AACG;AAKlB;AAAO;AACY;AACF;AACf;AACA;AACA;AACiB;AACS;AAC5B;AACF;AAGE;AAAO;AACL;AACA;AACF;AACJ;AAIF;AACE;AAEA;AAEI;AAGN;AACG;AACC;AACA;AACA;AACA;AACA;AACA;AACM;AACN;AACmB;AACf;AACC;AAGP;AAGN;AAKA;AAAgC;AAK5B;AACE;AACc;AACd;AACA;AACA;AACA;AACG;AAIL;AACE;AAEmD;AAIrD;AACG;AACC;AACA;AACA;AACwB;AACxB;AACA;AACA;AACI;AACC;AAGP;AAGN;AAEA;AAIE;AACG;AACC;AACI;AAKF;AAKA;AAEG;AACQ;AAOnB;AAGA;AAgBO;AAAiC;AACtC;AAEI;AAAgC;AAE5B;AAGA;AACG;AACC;AACI;AACC;AACP;AAEJ;AAGE;AAGA;AACE;AACE;AACE;AAEA;AAAQ;AACyC;AACjD;AAGF;AACG;AACC;AACI;AACC;AACP;AAIF;AAAO;AACT;AAGF;AACG;AACC;AACI;AACC;AACP;AAEJ;AACF;AACF;AACF;AACA;AACU;AACA;AACF;AACE;AAEZ;;"}