view port update

This commit is contained in:
Nate Kelley 2025-09-27 23:09:24 -06:00
parent 4ac600e4bb
commit bb0adc8cb8
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
4 changed files with 66 additions and 25 deletions

View File

@ -1,5 +1,12 @@
import type { SuggestionProps } from '@tiptap/suggestion';
import React, { useEffect, useImperativeHandle, useState } from 'react';
import React, {
createContext,
useContext,
useEffect,
useImperativeHandle,
useRef,
useState,
} from 'react';
import { cn } from '@/lib/utils';
import type {
MentionInputTriggerItem,
@ -26,6 +33,7 @@ function MentionListInner<T = string>(
{ trigger, emptyState, items, command, className }: MentionListProps<T>,
ref: React.ForwardedRef<MentionListImperativeHandle>
) {
const listRef = useRef<HTMLDivElement>(null);
const [selectedItem, setSelectedItem] = useState<T | undefined>(undefined);
const selectItem = (value: T) => {
@ -98,30 +106,55 @@ function MentionListInner<T = string>(
}));
return (
<div
className={cn(
'flex flex-col p-1 bg-background rounded border w-full min-w-[200px] max-w-[280px] overflow-x-auto',
'max-h-[300px] overflow-y-auto',
className
)}
>
{items.length ? (
items.map((item, index: number) => (
<MentionListSelector<T>
key={index}
{...item}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
onSelectItem={selectItem}
/>
))
) : (
<div className="text-gray-light">{emptyState || 'No results'}</div>
)}
</div>
<MentionListProvider listRef={listRef}>
<div
ref={listRef}
data-testid="mention-list"
className={cn(
'flex flex-col p-1 bg-background rounded border w-full min-w-[200px] max-w-[280px] overflow-x-auto',
'max-h-[300px] overflow-y-auto',
className
)}
>
{items.length ? (
items.map((item, index: number) => (
<MentionListSelector<T>
key={index}
{...item}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
onSelectItem={selectItem}
/>
))
) : (
<div className="text-gray-light">{emptyState || 'No results'}</div>
)}
</div>
</MentionListProvider>
);
}
export const MentionList = React.forwardRef(MentionListInner) as <T = string>(
props: MentionListProps<T> & { ref?: React.ForwardedRef<MentionListImperativeHandle> }
) => ReturnType<typeof MentionListInner> & { displayName?: string };
const MentionListContext = createContext<{
listRef: React.RefObject<HTMLDivElement | null> | null;
}>({
listRef: null,
});
const MentionListProvider = ({
children,
listRef,
}: {
children: React.ReactNode;
listRef: React.RefObject<HTMLDivElement | null>;
}) => {
return <MentionListContext.Provider value={{ listRef }}>{children}</MentionListContext.Provider>;
};
export const useMentionListRef = () => {
const { listRef } = useContext(MentionListContext);
return listRef;
};

View File

@ -1,5 +1,8 @@
import { useEffect, useRef } from 'react';
import { Text } from '@/components/ui/typography/Text';
import { useInViewport } from '@/hooks/useInViewport';
import { cn } from '@/lib/utils';
import { useMentionListRef } from './MentionList';
import type { MentionTriggerItemExtended } from './MentionListSelector';
export function MentionListItem<T = string>({
@ -13,10 +16,14 @@ export function MentionListItem<T = string>({
onSelectItem,
secondaryContent,
}: MentionTriggerItemExtended<T>) {
const containerRef = useRef<HTMLDivElement>(null);
return (
<div
ref={containerRef}
onClick={() => onSelectItem(value)}
onMouseEnter={() => setSelectedItem(value)}
data-testid={`mention-list-item-${value}`}
data-disabled={disabled}
data-loading={loading}
data-selected={isSelected}

View File

@ -13,7 +13,6 @@ export const MentionPill = <T extends string>({
editor,
}: ReactNodeViewProps<MentionTriggerItem<T>>) => {
const { trigger, label, value, pillLabel } = node.attrs as MentionPillAttributes;
console.log('node', node);
const pillStyling = editor.storage.mention.pillStylingByTrigger.get(trigger);
const pillClassName =
typeof pillStyling?.className === 'function'

View File

@ -1,11 +1,12 @@
import type React from 'react';
import { useEffect, useRef, useState } from 'react';
import { type RefObject, useEffect, useRef, useState } from 'react';
interface UseInViewportOptions {
/** The percentage of the element that needs to be visible (0 to 1) */
threshold?: number;
/** Margin around the root element (viewport) */
rootMargin?: string;
root?: Element;
}
/**
@ -36,6 +37,7 @@ export const useInViewport = (
{
threshold: options.threshold ?? 0,
rootMargin: options.rootMargin ?? '0px',
root: options.root,
}
);
@ -49,7 +51,7 @@ export const useInViewport = (
observer.unobserve(currentRef);
}
};
}, [ref, options.threshold, options.rootMargin]);
}, [ref, options.threshold, options.rootMargin, options.root]);
return [inViewport] as const;
};