-
Notifications
You must be signed in to change notification settings - Fork 6.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(scroll btn): Add SelectScrollButton for scrolling dropdown, visual overflow indication #7579
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,130 @@ | ||||||
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; | ||||||
import { useEffect, useRef, useState } from 'react'; | ||||||
import { type FC, type RefObject } from 'react'; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
import styles from '@node-core/ui-components/Common/Select/index.module.css'; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Maybe use dedicate css-module to simplify code |
||||||
|
||||||
type SelectScrollButtonProps = { | ||||||
direction: 'up' | 'down'; | ||||||
selectContentRef?: RefObject<HTMLDivElement | null>; | ||||||
scrollAmount?: number; | ||||||
scrollInterval?: number; | ||||||
}; | ||||||
|
||||||
const SelectScrollButton: FC<SelectScrollButtonProps> = ({ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Primitive radix select already has ScrollUpButton and ScrollDownButton Instead of adding these features, you can use what radix provides directly. Here is an example of primitive select anatomy; https://www.radix-ui.com/primitives/docs/components/select#anatomy |
||||||
direction, | ||||||
selectContentRef, | ||||||
scrollAmount = 35, | ||||||
scrollInterval = 50, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
for moment it's to quick |
||||||
}) => { | ||||||
const DirectionComponent = | ||||||
direction === 'down' ? ChevronDownIcon : ChevronUpIcon; | ||||||
const [isVisible, setIsVisible] = useState(false); | ||||||
const [hasOverflow, setOverflow] = useState(false); | ||||||
const intervalRef = useRef<number | null>(null); | ||||||
const isScrollingRef = useRef(false); | ||||||
|
||||||
const clearScrollInterval = () => { | ||||||
if (intervalRef.current !== null) { | ||||||
window.clearInterval(intervalRef.current); | ||||||
intervalRef.current = null; | ||||||
} | ||||||
}; | ||||||
|
||||||
const startScrolling = () => { | ||||||
if (!selectContentRef?.current || !isVisible || !hasOverflow) return; | ||||||
|
||||||
clearScrollInterval(); | ||||||
|
||||||
intervalRef.current = window.setInterval(() => { | ||||||
if (!selectContentRef.current || !isScrollingRef.current) return; | ||||||
|
||||||
const container = selectContentRef.current; | ||||||
|
||||||
if (direction === 'down') { | ||||||
container.scrollBy({ top: scrollAmount, behavior: 'smooth' }); | ||||||
|
||||||
if ( | ||||||
container.scrollTop >= | ||||||
container.scrollHeight - container.clientHeight | ||||||
) { | ||||||
clearScrollInterval(); | ||||||
setIsVisible(false); | ||||||
} | ||||||
} else { | ||||||
container.scrollBy({ | ||||||
top: -Math.abs(scrollAmount), | ||||||
behavior: 'smooth', | ||||||
}); | ||||||
|
||||||
if (container.scrollTop <= 0) { | ||||||
clearScrollInterval(); | ||||||
setIsVisible(false); | ||||||
} | ||||||
} | ||||||
Comment on lines
+44
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an extremely extremely minor nitpick, but this could probably be simplified into a helper function? (But this is such a tiny nitpick that you really don't have to do anything) |
||||||
}, scrollInterval); | ||||||
}; | ||||||
|
||||||
useEffect(() => { | ||||||
if (!selectContentRef?.current) return; | ||||||
|
||||||
const container = selectContentRef.current; | ||||||
setOverflow(container.scrollHeight > container.clientHeight); | ||||||
|
||||||
const updateButtonVisibility = () => { | ||||||
if (!container) return; | ||||||
|
||||||
if (direction === 'down') { | ||||||
setIsVisible( | ||||||
container.scrollTop < container.scrollHeight - container.clientHeight | ||||||
); | ||||||
} else { | ||||||
setIsVisible(container.scrollTop > 0); | ||||||
} | ||||||
}; | ||||||
|
||||||
updateButtonVisibility(); | ||||||
|
||||||
const handleScroll = () => { | ||||||
updateButtonVisibility(); | ||||||
|
||||||
if (!isScrollingRef.current && intervalRef.current !== null) { | ||||||
clearScrollInterval(); | ||||||
} | ||||||
}; | ||||||
|
||||||
container.addEventListener('scroll', handleScroll); | ||||||
window.addEventListener('resize', updateButtonVisibility); | ||||||
|
||||||
return () => { | ||||||
container.removeEventListener('scroll', handleScroll); | ||||||
window.removeEventListener('resize', updateButtonVisibility); | ||||||
clearScrollInterval(); | ||||||
}; | ||||||
}, [direction, selectContentRef]); | ||||||
|
||||||
const handleMouseEnter = () => { | ||||||
isScrollingRef.current = true; | ||||||
startScrolling(); | ||||||
}; | ||||||
|
||||||
const handleMouseLeave = () => { | ||||||
isScrollingRef.current = false; | ||||||
clearScrollInterval(); | ||||||
}; | ||||||
|
||||||
if (!isVisible) return null; | ||||||
|
||||||
return ( | ||||||
<div | ||||||
className={styles.scrollBtn} | ||||||
data-direction={direction} | ||||||
onMouseEnter={handleMouseEnter} | ||||||
onMouseLeave={handleMouseLeave} | ||||||
> | ||||||
<DirectionComponent className={styles.scrollBtnIcon} aria-hidden="true" /> | ||||||
</div> | ||||||
); | ||||||
}; | ||||||
|
||||||
export default SelectScrollButton; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -149,3 +149,32 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
rounded; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.scrollBtn { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@apply sticky | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
z-10 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
flex | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
w-full | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
cursor-pointer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
justify-center | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
bg-white | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
p-1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
transition-colors | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
hover:bg-neutral-100 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dark:bg-neutral-950 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dark:hover:bg-neutral-900; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.scrollBtn[data-direction='down'] { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
bottom: 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
canerakdas marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.scrollBtn[data-direction='up'] { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
top: 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
canerakdas marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.scrollBtnIcon { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@apply size-5 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
text-neutral-600 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
dark:text-neutral-400; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+153
to
+180
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
you can use nested css to make it easier to maintain |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add stories for this component ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Keep in mind, that, ideally, the stories will show this component working within a Select component, and not on its own)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we do it for pagination