@liveblocks/react-ui 1.12.0-initial1 → 2.0.0-alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Comment.js +8 -17
- package/dist/components/Comment.js.map +1 -1
- package/dist/components/Comment.mjs +1 -10
- package/dist/components/Comment.mjs.map +1 -1
- package/dist/components/Composer.js +4 -6
- package/dist/components/Composer.js.map +1 -1
- package/dist/components/Composer.mjs +1 -3
- package/dist/components/Composer.mjs.map +1 -1
- package/dist/components/InboxNotification.js +3 -5
- package/dist/components/InboxNotification.js.map +1 -1
- package/dist/components/InboxNotification.mjs +1 -3
- package/dist/components/InboxNotification.mjs.map +1 -1
- package/dist/components/InboxNotificationList.js.map +1 -1
- package/dist/components/InboxNotificationList.mjs.map +1 -1
- package/dist/components/Thread.js +2 -3
- package/dist/components/Thread.js.map +1 -1
- package/dist/components/Thread.mjs +1 -2
- package/dist/components/Thread.mjs.map +1 -1
- package/dist/components/internal/Avatar.js +1 -1
- package/dist/components/internal/Avatar.js.map +1 -1
- package/dist/components/internal/Avatar.mjs +1 -1
- package/dist/components/internal/Avatar.mjs.map +1 -1
- package/dist/components/internal/Room.js +1 -2
- package/dist/components/internal/Room.js.map +1 -1
- package/dist/components/internal/Room.mjs +1 -2
- package/dist/components/internal/Room.mjs.map +1 -1
- package/dist/index.d.mts +21 -4
- package/dist/index.d.ts +21 -4
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/dist/primitives/Composer/index.js +4 -9
- package/dist/primitives/Composer/index.js.map +1 -1
- package/dist/primitives/Composer/index.mjs +2 -7
- package/dist/primitives/Composer/index.mjs.map +1 -1
- package/dist/shared.js +79 -6
- package/dist/shared.js.map +1 -1
- package/dist/shared.mjs +81 -9
- package/dist/shared.mjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/version.mjs +1 -1
- package/dist/version.mjs.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InboxNotification.js","sources":["../../src/components/InboxNotification.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n InboxNotificationCustomData,\n InboxNotificationData,\n InboxNotificationThreadData,\n} from \"@liveblocks/core\";\nimport { assertNever, console } 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 { useCurrentUserId } from \"../shared\";\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 { useRoomInfo, useInboxNotificationThread } =\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAmKA;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;AAEA;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;;"}
|
|
1
|
+
{"version":3,"file":"InboxNotification.js","sources":["../../src/components/InboxNotification.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n InboxNotificationCustomData,\n InboxNotificationData,\n InboxNotificationThreadData,\n} from \"@liveblocks/core\";\nimport { assertNever, console } from \"@liveblocks/core\";\nimport {\n useInboxNotificationThread,\n useMarkInboxNotificationAsRead,\n useRoomInfo,\n} 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 { useCurrentUserId } from \"../shared\";\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 ComponentTypeWithRef<\n T extends keyof JSX.IntrinsicElements,\n P,\n> = ComponentType<P & Pick<ComponentProps<T>, \"ref\">>;\n\ntype InboxNotificationKinds = Record<\n `$${string}`,\n ComponentTypeWithRef<\"a\", InboxNotificationCustomKindProps>\n> & {\n thread: ComponentTypeWithRef<\"a\", InboxNotificationThreadKindProps>;\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\nexport type InboxNotificationThreadKindProps = Omit<\n InboxNotificationProps,\n \"kinds\"\n> & {\n inboxNotification: InboxNotificationThreadData;\n};\n\nexport type InboxNotificationCustomKindProps = Omit<\n InboxNotificationProps,\n \"kinds\"\n> & {\n inboxNotification: InboxNotificationCustomData;\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 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 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 * @beta\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAgLA;AAAgC;AAK5B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;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;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;AAkBO;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;;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { assertNever, console } from '@liveblocks/core';
|
|
3
|
-
import {
|
|
3
|
+
import { useMarkInboxNotificationAsRead, useInboxNotificationThread, useRoomInfo } from '@liveblocks/react';
|
|
4
4
|
import { Slot } from '@radix-ui/react-slot';
|
|
5
5
|
import { TooltipProvider } from '@radix-ui/react-tooltip';
|
|
6
6
|
import React__default, { forwardRef, useState, useCallback, useMemo } from 'react';
|
|
@@ -45,7 +45,6 @@ const InboxNotificationLayout = forwardRef(
|
|
|
45
45
|
const { Anchor } = useComponents(components);
|
|
46
46
|
const Component = asChild ? Slot : Anchor;
|
|
47
47
|
const [isMoreActionOpen, setMoreActionOpen] = useState(false);
|
|
48
|
-
const { useMarkInboxNotificationAsRead } = useLiveblocksContextBundle();
|
|
49
48
|
const markInboxNotificationAsRead = useMarkInboxNotificationAsRead();
|
|
50
49
|
const handleClick = useCallback(
|
|
51
50
|
(event) => {
|
|
@@ -173,7 +172,6 @@ const InboxNotificationThread = forwardRef(
|
|
|
173
172
|
...props
|
|
174
173
|
}, forwardedRef) => {
|
|
175
174
|
const $ = useOverrides(overrides);
|
|
176
|
-
const { useRoomInfo, useInboxNotificationThread } = useLiveblocksContextBundle();
|
|
177
175
|
const thread = useInboxNotificationThread(inboxNotification.id);
|
|
178
176
|
const currentUserId = useCurrentUserId();
|
|
179
177
|
const { info } = useRoomInfo(inboxNotification.roomId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"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 } 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 { useCurrentUserId } from \"../shared\";\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 { useRoomInfo, useInboxNotificationThread } =\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":";;;;;;;;;;;;;;;;;;;;;;;;;AAmKA;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;AAEA;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;;"}
|
|
1
|
+
{"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 } from \"@liveblocks/core\";\nimport {\n useInboxNotificationThread,\n useMarkInboxNotificationAsRead,\n useRoomInfo,\n} 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 { useCurrentUserId } from \"../shared\";\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 ComponentTypeWithRef<\n T extends keyof JSX.IntrinsicElements,\n P,\n> = ComponentType<P & Pick<ComponentProps<T>, \"ref\">>;\n\ntype InboxNotificationKinds = Record<\n `$${string}`,\n ComponentTypeWithRef<\"a\", InboxNotificationCustomKindProps>\n> & {\n thread: ComponentTypeWithRef<\"a\", InboxNotificationThreadKindProps>;\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\nexport type InboxNotificationThreadKindProps = Omit<\n InboxNotificationProps,\n \"kinds\"\n> & {\n inboxNotification: InboxNotificationThreadData;\n};\n\nexport type InboxNotificationCustomKindProps = Omit<\n InboxNotificationProps,\n \"kinds\"\n> & {\n inboxNotification: InboxNotificationCustomData;\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 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 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 * @beta\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":";;;;;;;;;;;;;;;;;;;;;;;;;AAgLA;AAAgC;AAK5B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;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;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;AAkBO;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;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InboxNotificationList.js","sources":["../../src/components/InboxNotificationList.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport React, { Children, forwardRef } from \"react\";\n\nimport { classNames } from \"../utils/class-names\";\n\nexport type InboxNotificationListProps = ComponentPropsWithoutRef<\"ol\">;\n\n/**\n * Displays inbox notifications as a list.\n *\n * @example\n * <InboxNotificationList>\n * {inboxNotifications.map((inboxNotification) => (\n * <InboxNotification key={inboxNotification.id} inboxNotification={inboxNotification} />\n * ))}\n * </InboxNotificationList>\n */\nexport const InboxNotificationList = forwardRef<\n HTMLOListElement,\n InboxNotificationListProps\n>(({ children, className, ...props }, forwardedRef) => {\n return (\n <ol\n className={classNames(\"lb-root lb-inbox-notification-list\", className)}\n {...props}\n ref={forwardedRef}\n >\n {Children.map(children, (child, index) => (\n <li key={index} className=\"lb-inbox-notification-list-item\">\n {child}\n </li>\n ))}\n </ol>\n );\n});\n"],"names":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"InboxNotificationList.js","sources":["../../src/components/InboxNotificationList.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport React, { Children, forwardRef } from \"react\";\n\nimport { classNames } from \"../utils/class-names\";\n\nexport type InboxNotificationListProps = ComponentPropsWithoutRef<\"ol\">;\n\n/**\n * @beta\n *\n * Displays inbox notifications as a list.\n *\n * @example\n * <InboxNotificationList>\n * {inboxNotifications.map((inboxNotification) => (\n * <InboxNotification key={inboxNotification.id} inboxNotification={inboxNotification} />\n * ))}\n * </InboxNotificationList>\n */\nexport const InboxNotificationList = forwardRef<\n HTMLOListElement,\n InboxNotificationListProps\n>(({ children, className, ...props }, forwardedRef) => {\n return (\n <ol\n className={classNames(\"lb-root lb-inbox-notification-list\", className)}\n {...props}\n ref={forwardedRef}\n >\n {Children.map(children, (child, index) => (\n <li key={index} className=\"lb-inbox-notification-list-item\">\n {child}\n </li>\n ))}\n </ol>\n );\n});\n"],"names":[],"mappings":";;;;;;AAqBa;AAIX;AACG;AACsE;AACjE;AACC;AAGF;AAAQ;AAAiB;AAMlC;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InboxNotificationList.mjs","sources":["../../src/components/InboxNotificationList.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport React, { Children, forwardRef } from \"react\";\n\nimport { classNames } from \"../utils/class-names\";\n\nexport type InboxNotificationListProps = ComponentPropsWithoutRef<\"ol\">;\n\n/**\n * Displays inbox notifications as a list.\n *\n * @example\n * <InboxNotificationList>\n * {inboxNotifications.map((inboxNotification) => (\n * <InboxNotification key={inboxNotification.id} inboxNotification={inboxNotification} />\n * ))}\n * </InboxNotificationList>\n */\nexport const InboxNotificationList = forwardRef<\n HTMLOListElement,\n InboxNotificationListProps\n>(({ children, className, ...props }, forwardedRef) => {\n return (\n <ol\n className={classNames(\"lb-root lb-inbox-notification-list\", className)}\n {...props}\n ref={forwardedRef}\n >\n {Children.map(children, (child, index) => (\n <li key={index} className=\"lb-inbox-notification-list-item\">\n {child}\n </li>\n ))}\n </ol>\n );\n});\n"],"names":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"InboxNotificationList.mjs","sources":["../../src/components/InboxNotificationList.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport React, { Children, forwardRef } from \"react\";\n\nimport { classNames } from \"../utils/class-names\";\n\nexport type InboxNotificationListProps = ComponentPropsWithoutRef<\"ol\">;\n\n/**\n * @beta\n *\n * Displays inbox notifications as a list.\n *\n * @example\n * <InboxNotificationList>\n * {inboxNotifications.map((inboxNotification) => (\n * <InboxNotification key={inboxNotification.id} inboxNotification={inboxNotification} />\n * ))}\n * </InboxNotificationList>\n */\nexport const InboxNotificationList = forwardRef<\n HTMLOListElement,\n InboxNotificationListProps\n>(({ children, className, ...props }, forwardedRef) => {\n return (\n <ol\n className={classNames(\"lb-root lb-inbox-notification-list\", className)}\n {...props}\n ref={forwardedRef}\n >\n {Children.map(children, (child, index) => (\n <li key={index} className=\"lb-inbox-notification-list-item\">\n {child}\n </li>\n ))}\n </ol>\n );\n});\n"],"names":[],"mappings":";;;;AAqBa;AAIX;AACG;AACsE;AACjE;AACC;AAGF;AAAQ;AAAiB;AAMlC;;"}
|
|
@@ -54,8 +54,7 @@ const Thread = React.forwardRef(
|
|
|
54
54
|
className,
|
|
55
55
|
...props
|
|
56
56
|
}, forwardedRef) => {
|
|
57
|
-
const
|
|
58
|
-
const editThreadMetadata = useEditThreadMetadata();
|
|
57
|
+
const editThreadMetadata = react.useEditThreadMetadata();
|
|
59
58
|
const $ = overrides.useOverrides(overrides$1);
|
|
60
59
|
const firstCommentIndex = React.useMemo(() => {
|
|
61
60
|
return showDeletedComments ? 0 : thread.comments.findIndex((comment) => comment.body);
|
|
@@ -63,7 +62,7 @@ const Thread = React.forwardRef(
|
|
|
63
62
|
const lastCommentIndex = React.useMemo(() => {
|
|
64
63
|
return showDeletedComments ? thread.comments.length - 1 : findLastIndex.findLastIndex(thread.comments, (comment) => comment.body);
|
|
65
64
|
}, [showDeletedComments, thread.comments]);
|
|
66
|
-
const { status: subscriptionStatus, unreadSince } = useThreadSubscription(
|
|
65
|
+
const { status: subscriptionStatus, unreadSince } = react.useThreadSubscription(
|
|
67
66
|
thread.id
|
|
68
67
|
);
|
|
69
68
|
const unreadIndex = React.useMemo(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Thread.js","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport type { BaseMetadata, CommentData, ThreadData } from \"@liveblocks/core\";\nimport { useRoomContextBundle } from \"@liveblocks/react\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentPropsWithoutRef,\n ForwardedRef,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport React, {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { ResolveIcon } from \"../icons/Resolve\";\nimport { ResolvedIcon } from \"../icons/Resolved\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport type { ThreadMetadata } from \"../types\";\nimport { classNames } from \"../utils/class-names\";\nimport { findLastIndex } from \"../utils/find-last-index\";\nimport type { CommentProps } from \"./Comment\";\nimport { Comment } from \"./Comment\";\nimport { Composer } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\nexport interface ThreadProps<M extends BaseMetadata = ThreadMetadata>\n extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<M>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<M>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <M extends BaseMetadata = ThreadMetadata>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n overrides,\n className,\n ...props\n }: ThreadProps<M>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const { useEditThreadMetadata, useThreadSubscription } =\n useRoomContextBundle();\n const editThreadMetadata = useEditThreadMetadata();\n const $ = useOverrides(overrides);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const { status: subscriptionStatus, unreadSince } = useThreadSubscription(\n thread.id\n );\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n editThreadMetadata({ threadId: thread.id, metadata: { resolved } });\n },\n [editThreadMetadata, onResolvedChange, thread.id]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n return (\n <TooltipProvider>\n <div\n className={classNames(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={\n (thread.metadata as ThreadMetadata).resolved ? \"\" : undefined\n }\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\">\n {thread.comments.map((comment, index) => {\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n\n const children = (\n <Comment\n key={comment.id}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n markThreadAsReadWhenVisible={\n index === lastCommentIndex && isUnread\n ? thread.id\n : undefined\n }\n additionalActionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n additionalActions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n (thread.metadata as ThreadMetadata).resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={(thread.metadata as ThreadMetadata).resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n (thread.metadata as ThreadMetadata).resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n {(thread.metadata as ThreadMetadata).resolved ? (\n <ResolvedIcon className=\"lb-button-icon\" />\n ) : (\n <ResolveIcon className=\"lb-button-icon\" />\n )}\n </Button>\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n }}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <M extends BaseMetadata = ThreadMetadata>(\n props: ThreadProps<M> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8HO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AAEA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAE4D;AAE9D;AAAoD;AAC3C;AAET;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AAAkE;AACpE;AACgD;AAGlD;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AAEK;AACY;AACT;AAC2B;AAC3B;AACF;AAEsD;AAER;AACvC;AACH;AACC;AAEJ;AAAc;AAEX;AACA;AAGA;AACG;AACc;AACH;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACiB;AACjB;AACA;AAIM;AAGmC;AAIpC;AAIS;AAGP;AAC8C;AAC5B;AACV;AAEN;AACW;AACD;AAID;AAIL;AAAuB;AAEvB;AAAsB;AAK7B;AAKV;AAGG;AAAsB;AACpB;AACW;AACI;AAEb;AAAe;AACb;AAAwB;AAO/B;AAKH;AACW;AACO;AACuC;AAC7C;AACe;AACP;AACnB;AAIR;AAGN;;"}
|
|
1
|
+
{"version":3,"file":"Thread.js","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport type { BaseMetadata, CommentData, ThreadData } from \"@liveblocks/core\";\nimport {\n useEditThreadMetadata,\n useThreadSubscription,\n} from \"@liveblocks/react\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentPropsWithoutRef,\n ForwardedRef,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport React, {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { ResolveIcon } from \"../icons/Resolve\";\nimport { ResolvedIcon } from \"../icons/Resolved\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport type { ThreadMetadata } from \"../types\";\nimport { classNames } from \"../utils/class-names\";\nimport { findLastIndex } from \"../utils/find-last-index\";\nimport type { CommentProps } from \"./Comment\";\nimport { Comment } from \"./Comment\";\nimport { Composer } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\nexport interface ThreadProps<M extends BaseMetadata = ThreadMetadata>\n extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<M>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<M>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <M extends BaseMetadata = ThreadMetadata>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n overrides,\n className,\n ...props\n }: ThreadProps<M>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const editThreadMetadata = useEditThreadMetadata();\n const $ = useOverrides(overrides);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const { status: subscriptionStatus, unreadSince } = useThreadSubscription(\n thread.id\n );\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n editThreadMetadata({ threadId: thread.id, metadata: { resolved } });\n },\n [editThreadMetadata, onResolvedChange, thread.id]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n return (\n <TooltipProvider>\n <div\n className={classNames(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={\n (thread.metadata as ThreadMetadata).resolved ? \"\" : undefined\n }\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\">\n {thread.comments.map((comment, index) => {\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n\n const children = (\n <Comment\n key={comment.id}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n markThreadAsReadWhenVisible={\n index === lastCommentIndex && isUnread\n ? thread.id\n : undefined\n }\n additionalActionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n additionalActions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n (thread.metadata as ThreadMetadata).resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={(thread.metadata as ThreadMetadata).resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n (thread.metadata as ThreadMetadata).resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n {(thread.metadata as ThreadMetadata).resolved ? (\n <ResolvedIcon className=\"lb-button-icon\" />\n ) : (\n <ResolveIcon className=\"lb-button-icon\" />\n )}\n </Button>\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n }}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <M extends BaseMetadata = ThreadMetadata>(\n props: ThreadProps<M> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiIO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAE4D;AAE9D;AAAoD;AAC3C;AAET;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AAAkE;AACpE;AACgD;AAGlD;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AAEK;AACY;AACT;AAC2B;AAC3B;AACF;AAEsD;AAER;AACvC;AACH;AACC;AAEJ;AAAc;AAEX;AACA;AAGA;AACG;AACc;AACH;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACiB;AACjB;AACA;AAIM;AAGmC;AAIpC;AAIS;AAGP;AAC8C;AAC5B;AACV;AAEN;AACW;AACD;AAID;AAIL;AAAuB;AAEvB;AAAsB;AAK7B;AAKV;AAGG;AAAsB;AACpB;AACW;AACI;AAEb;AAAe;AACb;AAAwB;AAO/B;AAKH;AACW;AACO;AACuC;AAC7C;AACe;AACP;AACnB;AAIR;AAGN;;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
2
|
+
import { useEditThreadMetadata, useThreadSubscription } from '@liveblocks/react';
|
|
3
3
|
import * as TogglePrimitive from '@radix-ui/react-toggle';
|
|
4
4
|
import React__default, { forwardRef, useMemo, useState, useEffect, useCallback, Fragment } from 'react';
|
|
5
5
|
import { ArrowDownIcon } from '../icons/ArrowDown.mjs';
|
|
@@ -33,7 +33,6 @@ const Thread = forwardRef(
|
|
|
33
33
|
className,
|
|
34
34
|
...props
|
|
35
35
|
}, forwardedRef) => {
|
|
36
|
-
const { useEditThreadMetadata, useThreadSubscription } = useRoomContextBundle();
|
|
37
36
|
const editThreadMetadata = useEditThreadMetadata();
|
|
38
37
|
const $ = useOverrides(overrides);
|
|
39
38
|
const firstCommentIndex = useMemo(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Thread.mjs","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport type { BaseMetadata, CommentData, ThreadData } from \"@liveblocks/core\";\nimport { useRoomContextBundle } from \"@liveblocks/react\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentPropsWithoutRef,\n ForwardedRef,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport React, {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { ResolveIcon } from \"../icons/Resolve\";\nimport { ResolvedIcon } from \"../icons/Resolved\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport type { ThreadMetadata } from \"../types\";\nimport { classNames } from \"../utils/class-names\";\nimport { findLastIndex } from \"../utils/find-last-index\";\nimport type { CommentProps } from \"./Comment\";\nimport { Comment } from \"./Comment\";\nimport { Composer } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\nexport interface ThreadProps<M extends BaseMetadata = ThreadMetadata>\n extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<M>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<M>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <M extends BaseMetadata = ThreadMetadata>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n overrides,\n className,\n ...props\n }: ThreadProps<M>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const { useEditThreadMetadata, useThreadSubscription } =\n useRoomContextBundle();\n const editThreadMetadata = useEditThreadMetadata();\n const $ = useOverrides(overrides);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const { status: subscriptionStatus, unreadSince } = useThreadSubscription(\n thread.id\n );\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n editThreadMetadata({ threadId: thread.id, metadata: { resolved } });\n },\n [editThreadMetadata, onResolvedChange, thread.id]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n return (\n <TooltipProvider>\n <div\n className={classNames(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={\n (thread.metadata as ThreadMetadata).resolved ? \"\" : undefined\n }\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\">\n {thread.comments.map((comment, index) => {\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n\n const children = (\n <Comment\n key={comment.id}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n markThreadAsReadWhenVisible={\n index === lastCommentIndex && isUnread\n ? thread.id\n : undefined\n }\n additionalActionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n additionalActions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n (thread.metadata as ThreadMetadata).resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={(thread.metadata as ThreadMetadata).resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n (thread.metadata as ThreadMetadata).resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n {(thread.metadata as ThreadMetadata).resolved ? (\n <ResolvedIcon className=\"lb-button-icon\" />\n ) : (\n <ResolveIcon className=\"lb-button-icon\" />\n )}\n </Button>\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n }}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <M extends BaseMetadata = ThreadMetadata>(\n props: ThreadProps<M> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AA8HO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AAEA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAE4D;AAE9D;AAAoD;AAC3C;AAET;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AAAkE;AACpE;AACgD;AAGlD;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AAEK;AACY;AACT;AAC2B;AAC3B;AACF;AAEsD;AAER;AACvC;AACH;AACC;AAEJ;AAAc;AAEX;AACA;AAGA;AACG;AACc;AACH;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACiB;AACjB;AACA;AAIM;AAGmC;AAIpC;AAIS;AAGP;AAC8C;AAC5B;AACV;AAEN;AACW;AACD;AAID;AAIL;AAAuB;AAEvB;AAAsB;AAK7B;AAKV;AAGG;AAAsB;AACpB;AACW;AACI;AAEb;AAAe;AACb;AAAwB;AAO/B;AAKH;AACW;AACO;AACuC;AAC7C;AACe;AACP;AACnB;AAIR;AAGN;;"}
|
|
1
|
+
{"version":3,"file":"Thread.mjs","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport type { BaseMetadata, CommentData, ThreadData } from \"@liveblocks/core\";\nimport {\n useEditThreadMetadata,\n useThreadSubscription,\n} from \"@liveblocks/react\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentPropsWithoutRef,\n ForwardedRef,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport React, {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { ResolveIcon } from \"../icons/Resolve\";\nimport { ResolvedIcon } from \"../icons/Resolved\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport type { ThreadMetadata } from \"../types\";\nimport { classNames } from \"../utils/class-names\";\nimport { findLastIndex } from \"../utils/find-last-index\";\nimport type { CommentProps } from \"./Comment\";\nimport { Comment } from \"./Comment\";\nimport { Composer } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\nexport interface ThreadProps<M extends BaseMetadata = ThreadMetadata>\n extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<M>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<M>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <M extends BaseMetadata = ThreadMetadata>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n overrides,\n className,\n ...props\n }: ThreadProps<M>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const editThreadMetadata = useEditThreadMetadata();\n const $ = useOverrides(overrides);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const { status: subscriptionStatus, unreadSince } = useThreadSubscription(\n thread.id\n );\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n editThreadMetadata({ threadId: thread.id, metadata: { resolved } });\n },\n [editThreadMetadata, onResolvedChange, thread.id]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n return (\n <TooltipProvider>\n <div\n className={classNames(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={\n (thread.metadata as ThreadMetadata).resolved ? \"\" : undefined\n }\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\">\n {thread.comments.map((comment, index) => {\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n\n const children = (\n <Comment\n key={comment.id}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n markThreadAsReadWhenVisible={\n index === lastCommentIndex && isUnread\n ? thread.id\n : undefined\n }\n additionalActionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n additionalActions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n (thread.metadata as ThreadMetadata).resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={(thread.metadata as ThreadMetadata).resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n (thread.metadata as ThreadMetadata).resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n {(thread.metadata as ThreadMetadata).resolved ? (\n <ResolvedIcon className=\"lb-button-icon\" />\n ) : (\n <ResolveIcon className=\"lb-button-icon\" />\n )}\n </Button>\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n }}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <M extends BaseMetadata = ThreadMetadata>(\n props: ThreadProps<M> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAiIO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAE4D;AAE9D;AAAoD;AAC3C;AAET;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AAAkE;AACpE;AACgD;AAGlD;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AAEK;AACY;AACT;AAC2B;AAC3B;AACF;AAEsD;AAER;AACvC;AACH;AACC;AAEJ;AAAc;AAEX;AACA;AAGA;AACG;AACc;AACH;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACiB;AACjB;AACA;AAIM;AAGmC;AAIpC;AAIS;AAGP;AAC8C;AAC5B;AACV;AAEN;AACW;AACD;AAID;AAIL;AAAuB;AAEvB;AAAsB;AAK7B;AAKV;AAGG;AAAsB;AACpB;AACW;AACI;AAEb;AAAe;AACb;AAAwB;AAO/B;AAKH;AACW;AACO;AACuC;AAC7C;AACe;AACP;AACnB;AAIR;AAGN;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Avatar.js","sources":["../../../src/components/internal/Avatar.tsx"],"sourcesContent":["\"use client\";\n\nimport
|
|
1
|
+
{"version":3,"file":"Avatar.js","sources":["../../../src/components/internal/Avatar.tsx"],"sourcesContent":["\"use client\";\n\nimport { useUser } from \"@liveblocks/react\";\nimport type { ComponentProps } from \"react\";\nimport React, { useMemo } from \"react\";\n\nimport { classNames } from \"../../utils/class-names\";\nimport { getInitials } from \"../../utils/get-initials\";\n\nexport interface AvatarProps extends ComponentProps<\"div\"> {\n /**\n * The user ID to display the avatar for.\n */\n userId: string;\n}\n\nexport function Avatar({ userId, className, ...props }: AvatarProps) {\n const { user, isLoading } = useUser(userId);\n const resolvedUserName = useMemo(() => user?.name, [user]);\n const resolvedUserAvatar = useMemo(() => user?.avatar, [user]);\n const resolvedUserInitials = useMemo(\n () => (resolvedUserName ? getInitials(resolvedUserName) : undefined),\n [resolvedUserName]\n );\n const resolvedUserIdInitials = useMemo(\n () => (!isLoading && !user ? getInitials(userId) : undefined),\n [isLoading, user, userId]\n );\n\n return (\n <div\n className={classNames(\"lb-avatar\", className)}\n data-loading={isLoading ? \"\" : undefined}\n {...props}\n >\n {resolvedUserAvatar && (\n <img\n className=\"lb-avatar-image\"\n src={resolvedUserAvatar}\n alt={resolvedUserName}\n />\n )}\n {resolvedUserInitials ? (\n <span className=\"lb-avatar-fallback\" aria-hidden>\n {resolvedUserInitials}\n </span>\n ) : resolvedUserIdInitials ? (\n <span className=\"lb-avatar-fallback\" aria-label={userId} title={userId}>\n {resolvedUserIdInitials}\n </span>\n ) : null}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;AAgBO;AACL;AACA;AACA;AACA;AAA6B;AAC+B;AACzC;AAEnB;AAA+B;AACsB;AAC3B;AAG1B;AACG;AAC6C;AACb;AAC3B;AAGD;AACW;AACL;AACA;AAIN;AAAe;AAAgC;AAI/C;AAAe;AAAiC;AAAe;AAMxE;;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import React__default, { useMemo } from 'react';
|
|
3
2
|
import { useUser } from '@liveblocks/react';
|
|
3
|
+
import React__default, { useMemo } from 'react';
|
|
4
4
|
import { classNames } from '../../utils/class-names.mjs';
|
|
5
5
|
import { getInitials } from '../../utils/get-initials.mjs';
|
|
6
6
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Avatar.mjs","sources":["../../../src/components/internal/Avatar.tsx"],"sourcesContent":["\"use client\";\n\nimport
|
|
1
|
+
{"version":3,"file":"Avatar.mjs","sources":["../../../src/components/internal/Avatar.tsx"],"sourcesContent":["\"use client\";\n\nimport { useUser } from \"@liveblocks/react\";\nimport type { ComponentProps } from \"react\";\nimport React, { useMemo } from \"react\";\n\nimport { classNames } from \"../../utils/class-names\";\nimport { getInitials } from \"../../utils/get-initials\";\n\nexport interface AvatarProps extends ComponentProps<\"div\"> {\n /**\n * The user ID to display the avatar for.\n */\n userId: string;\n}\n\nexport function Avatar({ userId, className, ...props }: AvatarProps) {\n const { user, isLoading } = useUser(userId);\n const resolvedUserName = useMemo(() => user?.name, [user]);\n const resolvedUserAvatar = useMemo(() => user?.avatar, [user]);\n const resolvedUserInitials = useMemo(\n () => (resolvedUserName ? getInitials(resolvedUserName) : undefined),\n [resolvedUserName]\n );\n const resolvedUserIdInitials = useMemo(\n () => (!isLoading && !user ? getInitials(userId) : undefined),\n [isLoading, user, userId]\n );\n\n return (\n <div\n className={classNames(\"lb-avatar\", className)}\n data-loading={isLoading ? \"\" : undefined}\n {...props}\n >\n {resolvedUserAvatar && (\n <img\n className=\"lb-avatar-image\"\n src={resolvedUserAvatar}\n alt={resolvedUserName}\n />\n )}\n {resolvedUserInitials ? (\n <span className=\"lb-avatar-fallback\" aria-hidden>\n {resolvedUserInitials}\n </span>\n ) : resolvedUserIdInitials ? (\n <span className=\"lb-avatar-fallback\" aria-label={userId} title={userId}>\n {resolvedUserIdInitials}\n </span>\n ) : null}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;AAgBO;AACL;AACA;AACA;AACA;AAA6B;AAC+B;AACzC;AAEnB;AAA+B;AACsB;AAC3B;AAG1B;AACG;AAC6C;AACb;AAC3B;AAGD;AACW;AACL;AACA;AAIN;AAAe;AAAgC;AAI/C;AAAe;AAAiC;AAAe;AAMxE;;"}
|
|
@@ -6,8 +6,7 @@ var React = require('react');
|
|
|
6
6
|
var classNames = require('../../utils/class-names.js');
|
|
7
7
|
|
|
8
8
|
function Room({ roomId, className, ...props }) {
|
|
9
|
-
const {
|
|
10
|
-
const { info, isLoading } = useRoomInfo(roomId);
|
|
9
|
+
const { info, isLoading } = react.useRoomInfo(roomId);
|
|
11
10
|
const resolvedRoomName = React.useMemo(() => {
|
|
12
11
|
return info?.name ?? roomId;
|
|
13
12
|
}, [info?.name, roomId]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Room.js","sources":["../../../src/components/internal/Room.tsx"],"sourcesContent":["\"use client\";\n\nimport {
|
|
1
|
+
{"version":3,"file":"Room.js","sources":["../../../src/components/internal/Room.tsx"],"sourcesContent":["\"use client\";\n\nimport { useRoomInfo } from \"@liveblocks/react\";\nimport type { ComponentProps } from \"react\";\nimport React, { useMemo } from \"react\";\n\nimport { classNames } from \"../../utils/class-names\";\n\nexport interface RoomProps extends ComponentProps<\"span\"> {\n roomId: string;\n}\n\nexport function Room({ roomId, className, ...props }: RoomProps) {\n const { info, isLoading } = useRoomInfo(roomId);\n const resolvedRoomName = useMemo(() => {\n return info?.name ?? roomId;\n }, [info?.name, roomId]);\n\n return (\n <span\n className={classNames(\"lb-name lb-room\", className)}\n data-loading={isLoading ? \"\" : undefined}\n {...props}\n >\n {isLoading ? null : resolvedRoomName}\n </span>\n );\n}\n"],"names":[],"mappings":";;;;;;;AAYO;AACL;AACA;AACE;AAAqB;AAGvB;AACG;AACmD;AACnB;AAC3B;AAKV;;"}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
2
|
+
import { useRoomInfo } from '@liveblocks/react';
|
|
3
3
|
import React__default, { useMemo } from 'react';
|
|
4
4
|
import { classNames } from '../../utils/class-names.mjs';
|
|
5
5
|
|
|
6
6
|
function Room({ roomId, className, ...props }) {
|
|
7
|
-
const { useRoomInfo } = useLiveblocksContextBundle();
|
|
8
7
|
const { info, isLoading } = useRoomInfo(roomId);
|
|
9
8
|
const resolvedRoomName = useMemo(() => {
|
|
10
9
|
return info?.name ?? roomId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Room.mjs","sources":["../../../src/components/internal/Room.tsx"],"sourcesContent":["\"use client\";\n\nimport {
|
|
1
|
+
{"version":3,"file":"Room.mjs","sources":["../../../src/components/internal/Room.tsx"],"sourcesContent":["\"use client\";\n\nimport { useRoomInfo } from \"@liveblocks/react\";\nimport type { ComponentProps } from \"react\";\nimport React, { useMemo } from \"react\";\n\nimport { classNames } from \"../../utils/class-names\";\n\nexport interface RoomProps extends ComponentProps<\"span\"> {\n roomId: string;\n}\n\nexport function Room({ roomId, className, ...props }: RoomProps) {\n const { info, isLoading } = useRoomInfo(roomId);\n const resolvedRoomName = useMemo(() => {\n return info?.name ?? roomId;\n }, [info?.name, roomId]);\n\n return (\n <span\n className={classNames(\"lb-name lb-room\", className)}\n data-loading={isLoading ? \"\" : undefined}\n {...props}\n >\n {isLoading ? null : resolvedRoomName}\n </span>\n );\n}\n"],"names":[],"mappings":";;;;;AAYO;AACL;AACA;AACE;AAAqB;AAGvB;AACG;AACmD;AACnB;AAC3B;AAKV;;"}
|
package/dist/index.d.mts
CHANGED
|
@@ -277,10 +277,9 @@ interface AvatarProps extends ComponentProps<"div"> {
|
|
|
277
277
|
userId: string;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
declare type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
|
281
280
|
declare type ComponentTypeWithRef<T extends keyof JSX.IntrinsicElements, P> = ComponentType<P & Pick<ComponentProps<T>, "ref">>;
|
|
282
|
-
declare type InboxNotificationKinds = Record<`$${string}`, ComponentTypeWithRef<"a",
|
|
283
|
-
thread: ComponentTypeWithRef<"a",
|
|
281
|
+
declare type InboxNotificationKinds = Record<`$${string}`, ComponentTypeWithRef<"a", InboxNotificationCustomKindProps>> & {
|
|
282
|
+
thread: ComponentTypeWithRef<"a", InboxNotificationThreadKindProps>;
|
|
284
283
|
};
|
|
285
284
|
interface InboxNotificationSharedProps {
|
|
286
285
|
/**
|
|
@@ -339,11 +338,19 @@ interface InboxNotificationCustomProps extends Omit<InboxNotificationProps, "kin
|
|
|
339
338
|
*/
|
|
340
339
|
markAsReadOnClick?: boolean;
|
|
341
340
|
}
|
|
341
|
+
declare type InboxNotificationThreadKindProps = Omit<InboxNotificationProps, "kinds"> & {
|
|
342
|
+
inboxNotification: InboxNotificationThreadData;
|
|
343
|
+
};
|
|
344
|
+
declare type InboxNotificationCustomKindProps = Omit<InboxNotificationProps, "kinds"> & {
|
|
345
|
+
inboxNotification: InboxNotificationCustomData;
|
|
346
|
+
};
|
|
342
347
|
declare type InboxNotificationIconProps = ComponentProps<"div">;
|
|
343
348
|
declare type InboxNotificationAvatarProps = AvatarProps;
|
|
344
349
|
declare function InboxNotificationIcon({ className, ...props }: InboxNotificationIconProps): React.JSX.Element;
|
|
345
350
|
declare function InboxNotificationAvatar({ className, ...props }: InboxNotificationAvatarProps): React.JSX.Element;
|
|
346
351
|
/**
|
|
352
|
+
* @beta
|
|
353
|
+
*
|
|
347
354
|
* Displays a single inbox notification.
|
|
348
355
|
*
|
|
349
356
|
* @example
|
|
@@ -366,6 +373,8 @@ declare const InboxNotification: React.ForwardRefExoticComponent<InboxNotificati
|
|
|
366
373
|
|
|
367
374
|
declare type InboxNotificationListProps = ComponentPropsWithoutRef<"ol">;
|
|
368
375
|
/**
|
|
376
|
+
* @beta
|
|
377
|
+
*
|
|
369
378
|
* Displays inbox notifications as a list.
|
|
370
379
|
*
|
|
371
380
|
* @example
|
|
@@ -473,4 +482,12 @@ declare type LiveblocksUIConfigProps = PropsWithChildren<{
|
|
|
473
482
|
*/
|
|
474
483
|
declare function LiveblocksUIConfig({ overrides, components, portalContainer, children, }: LiveblocksUIConfigProps): React.JSX.Element;
|
|
475
484
|
|
|
476
|
-
|
|
485
|
+
/**
|
|
486
|
+
* @private For internal use only. Do not rely on this hook.
|
|
487
|
+
*
|
|
488
|
+
* Simplistic debounced search, we don't need to worry too much about deduping
|
|
489
|
+
* and race conditions as there can only be one search at a time.
|
|
490
|
+
*/
|
|
491
|
+
declare function useMentionSuggestions(search?: string): string[] | undefined;
|
|
492
|
+
|
|
493
|
+
export { Comment, CommentProps, Composer, ComposerProps, ComposerSubmitComment, InboxNotification, InboxNotificationAvatarProps, InboxNotificationCustomKindProps, InboxNotificationCustomProps, InboxNotificationIconProps, InboxNotificationList, InboxNotificationListProps, InboxNotificationProps, InboxNotificationThreadKindProps, InboxNotificationThreadProps, LiveblocksUIConfig, Thread, ThreadProps, useMentionSuggestions };
|