Decouple faded scroll component from behavior
This commit is contained in:
parent
fa9b62f34b
commit
29c3c7f167
@ -1,20 +1,15 @@
|
|||||||
import clsx from 'clsx/lite';
|
import clsx from 'clsx/lite';
|
||||||
import {
|
import { HTMLAttributes, RefObject } from 'react';
|
||||||
HTMLAttributes,
|
import useFadedScroll from './useFadedScroll';
|
||||||
RefObject,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
export default function FadedScroll({
|
export default function FadedScroll({
|
||||||
ref: containerRef,
|
ref,
|
||||||
direction = 'vertical',
|
direction = 'vertical',
|
||||||
fadeHeight = 24,
|
fadeHeight = 24,
|
||||||
hideScrollbar,
|
hideScrollbar,
|
||||||
className,
|
className,
|
||||||
classNameContent,
|
classNameContent,
|
||||||
|
style,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: HTMLAttributes<HTMLDivElement> & {
|
}: HTMLAttributes<HTMLDivElement> & {
|
||||||
@ -24,52 +19,22 @@ export default function FadedScroll({
|
|||||||
classNameContent?: string
|
classNameContent?: string
|
||||||
hideScrollbar?: boolean
|
hideScrollbar?: boolean
|
||||||
}) {
|
}) {
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const { maskImage } = useFadedScroll(ref, direction, fadeHeight);
|
||||||
|
|
||||||
const [position, setPosition] = useState<'start' | 'middle' | 'end'>('start');
|
|
||||||
|
|
||||||
const isVertical = direction === 'vertical';
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const ref = contentRef.current;
|
|
||||||
if (ref) {
|
|
||||||
const handleScroll = () => {
|
|
||||||
const isStart = isVertical
|
|
||||||
? ref.scrollTop === 0
|
|
||||||
: ref.scrollLeft === 0;
|
|
||||||
const isEnd = isVertical
|
|
||||||
? ref.scrollHeight - ref.scrollTop === ref.clientHeight
|
|
||||||
: ref.scrollWidth - ref.scrollLeft === ref.clientWidth;
|
|
||||||
setPosition(isStart ? 'start' : isEnd ? 'end' : 'middle');
|
|
||||||
};
|
|
||||||
ref.addEventListener('scroll', handleScroll);
|
|
||||||
return () => ref.removeEventListener('scroll', handleScroll);
|
|
||||||
}
|
|
||||||
}, [isVertical]);
|
|
||||||
|
|
||||||
const maskImage = useMemo(() => {
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
let mask = `linear-gradient(to ${isVertical ? 'bottom' : 'right'}, transparent, black `;
|
|
||||||
mask += `${position !== 'start' ? fadeHeight : 0}px, black calc(100% - `;
|
|
||||||
mask += `${position !== 'end' ? fadeHeight : 0}px), transparent)`;
|
|
||||||
return mask;
|
|
||||||
}, [fadeHeight, isVertical, position]);
|
|
||||||
|
|
||||||
return <div
|
return <div
|
||||||
{...props}
|
{...props}
|
||||||
ref={containerRef}
|
ref={ref}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
isVertical
|
direction === 'vertical'
|
||||||
? 'overflow-y-hidden'
|
? 'overflow-y-hidden'
|
||||||
: 'overflow-x-hidden',
|
: 'overflow-x-hidden',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
style={{ maskImage }}
|
style={{ maskImage, ...style }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref={contentRef}
|
|
||||||
className={clsx(
|
className={clsx(
|
||||||
isVertical
|
direction === 'vertical'
|
||||||
? 'max-h-full overflow-y-auto'
|
? 'max-h-full overflow-y-auto'
|
||||||
: 'max-w-full overflow-x-auto',
|
: 'max-w-full overflow-x-auto',
|
||||||
hideScrollbar && '[scrollbar-width:none]',
|
hideScrollbar && '[scrollbar-width:none]',
|
||||||
|
|||||||
@ -155,7 +155,7 @@ export default function CommandKClient({
|
|||||||
|
|
||||||
const ref = useRef<HTMLInputElement>(null);
|
const ref = useRef<HTMLInputElement>(null);
|
||||||
const mobileViewportHeight = useVisualViewportHeight();
|
const mobileViewportHeight = useVisualViewportHeight();
|
||||||
const heightMinimum = '20rem';
|
const heightMinimum = '18rem';
|
||||||
const maxHeight = useMemo(() => {
|
const maxHeight = useMemo(() => {
|
||||||
const positionY = ref.current?.getBoundingClientRect().y;
|
const positionY = ref.current?.getBoundingClientRect().y;
|
||||||
return mobileViewportHeight && positionY
|
return mobileViewportHeight && positionY
|
||||||
@ -573,14 +573,16 @@ export default function CommandKClient({
|
|||||||
</span>}
|
</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Command.List className={clsx(
|
<Command.List
|
||||||
'relative overflow-y-auto',
|
className={clsx(
|
||||||
'mx-3 pt-3',
|
'overflow-y-auto',
|
||||||
)} style={{
|
'mx-3 pt-3',
|
||||||
maxHeight,
|
)} style={{
|
||||||
// eslint-disable-next-line max-len
|
maxHeight,
|
||||||
maskImage: 'linear-gradient(to bottom, transparent, black 20px, black calc(100% - 20px), transparent)',
|
// eslint-disable-next-line max-len
|
||||||
}}>
|
maskImage: 'linear-gradient(to bottom, transparent, black 20px, black calc(100% - 20px), transparent)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="pb-1 md:pb-2">
|
<div className="pb-1 md:pb-2">
|
||||||
<Command.Empty className="mt-1 pl-3 text-dim pb-4">
|
<Command.Empty className="mt-1 pl-3 text-dim pb-4">
|
||||||
{isLoading ? 'Searching ...' : 'No results found'}
|
{isLoading ? 'Searching ...' : 'No results found'}
|
||||||
|
|||||||
38
src/components/useFadedScroll.ts
Normal file
38
src/components/useFadedScroll.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { RefObject, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
export default function useFadedScroll(
|
||||||
|
containerRef?: RefObject<HTMLDivElement | null>,
|
||||||
|
direction: 'vertical' | 'horizontal' = 'vertical',
|
||||||
|
fadeHeight = 24,
|
||||||
|
) {
|
||||||
|
const [position, setPosition] = useState<'start' | 'middle' | 'end'>('start');
|
||||||
|
|
||||||
|
const isVertical = direction === 'vertical';
|
||||||
|
|
||||||
|
const ref = containerRef?.current?.children[0] as HTMLElement;
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref) {
|
||||||
|
const handleScroll = () => {
|
||||||
|
const isStart = isVertical
|
||||||
|
? ref.scrollTop === 0
|
||||||
|
: ref.scrollLeft === 0;
|
||||||
|
const isEnd = isVertical
|
||||||
|
? ref.scrollHeight - ref.scrollTop === ref.clientHeight
|
||||||
|
: ref.scrollWidth - ref.scrollLeft === ref.clientWidth;
|
||||||
|
setPosition(isStart ? 'start' : isEnd ? 'end' : 'middle');
|
||||||
|
};
|
||||||
|
ref.addEventListener('scroll', handleScroll);
|
||||||
|
return () => ref.removeEventListener('scroll', handleScroll);
|
||||||
|
}
|
||||||
|
}, [ref, isVertical]);
|
||||||
|
|
||||||
|
const maskImage = useMemo(() => {
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
let mask = `linear-gradient(to ${isVertical ? 'bottom' : 'right'}, transparent, black `;
|
||||||
|
mask += `${position !== 'start' ? fadeHeight : 0}px, black calc(100% - `;
|
||||||
|
mask += `${position !== 'end' ? fadeHeight : 0}px), transparent)`;
|
||||||
|
return mask;
|
||||||
|
}, [fadeHeight, isVertical, position]);
|
||||||
|
|
||||||
|
return { maskImage };
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user