From 51c49edc55c0d61f7124865e8a80632fbca93fbc Mon Sep 17 00:00:00 2001 From: dgreif Date: Fri, 21 May 2021 14:45:06 -0700 Subject: [PATCH 1/8] fix(SelectPanel): auto scroll when list overflows --- src/FilteredActionList/FilteredActionList.tsx | 15 +++++++++++---- src/stories/SelectPanel.stories.tsx | 7 ++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/FilteredActionList/FilteredActionList.tsx b/src/FilteredActionList/FilteredActionList.tsx index dc6cf6a66ea..4b548f35779 100644 --- a/src/FilteredActionList/FilteredActionList.tsx +++ b/src/FilteredActionList/FilteredActionList.tsx @@ -1,7 +1,8 @@ -import React, {KeyboardEventHandler, useCallback, useMemo, useRef} from 'react' +import React, {KeyboardEventHandler, useCallback, useEffect, useMemo, useRef} from 'react' import {GroupedListProps, ListPropsBase} from '../ActionList/List' import TextInput, {TextInputProps} from '../TextInput' import Box from '../Box' +import Flex from '../Flex' import {ActionList} from '../ActionList' import Spinner from '../Spinner' import {useFocusZone} from '../hooks/useFocusZone' @@ -69,12 +70,18 @@ export function FilteredActionList({ if (current) { current.classList.add(itemActiveDescendantClass) + current.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'center'}) } } }) + useEffect(() => { + // if items changed, we want to instantly move active descendant into view + activeDescendantRef.current?.scrollIntoView({block: 'center', inline: 'center'}) + }, [items]) + return ( - + - + {loading ? ( @@ -96,7 +103,7 @@ export function FilteredActionList({ )} - + ) } diff --git a/src/stories/SelectPanel.stories.tsx b/src/stories/SelectPanel.stories.tsx index 2728692b71a..6e1bd444153 100644 --- a/src/stories/SelectPanel.stories.tsx +++ b/src/stories/SelectPanel.stories.tsx @@ -39,7 +39,10 @@ const items = [ {leadingVisual: getColorCircle('#a2eeef'), text: 'enhancement', id: 1}, {leadingVisual: getColorCircle('#d73a4a'), text: 'bug', id: 2}, {leadingVisual: getColorCircle('#0cf478'), text: 'good first issue', id: 3}, - {leadingVisual: getColorCircle('#8dc6fc'), text: 'design', id: 4} + {leadingVisual: getColorCircle('#ffd78e'), text: 'design', id: 4}, + {leadingVisual: getColorCircle('#ff0000'), text: 'blocker', id: 5}, + {leadingVisual: getColorCircle('#a4f287'), text: 'backend', id: 6}, + {leadingVisual: getColorCircle('#8dc6fc'), text: 'frontend', id: 7} ] export function MultiSelectStory(): JSX.Element { @@ -66,6 +69,7 @@ export function MultiSelectStory(): JSX.Element { onSelectedChange={setSelected} onFilterChange={setFilter} showItemDividers={true} + overlayProps={{width: 'small', height: 'xsmall'}} /> ) @@ -96,6 +100,7 @@ export function SingleSelectStory(): JSX.Element { onSelectedChange={setSelected} onFilterChange={setFilter} showItemDividers={true} + overlayProps={{width: 'small', height: 'xsmall'}} /> ) From 240cee5092b651170b105fa50428ae345f64c196 Mon Sep 17 00:00:00 2001 From: dgreif Date: Fri, 21 May 2021 14:56:00 -0700 Subject: [PATCH 2/8] docs(SelectPanel): add example --- docs/content/SelectPanel.mdx | 46 +++++++++++++++++++ .../gatsby-theme-doctocat/live-code-scope.js | 4 +- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/docs/content/SelectPanel.mdx b/docs/content/SelectPanel.mdx index 549a762d7cf..c633707df49 100644 --- a/docs/content/SelectPanel.mdx +++ b/docs/content/SelectPanel.mdx @@ -7,4 +7,50 @@ A `SelectPanel` provides an anchor that will open an overlay with a list of sele ## Example +```javascript live noinline +function getColorCircle(color) { + return function () { + return + } +} + +const items = [ + {leadingVisual: getColorCircle('#a2eeef'), text: 'enhancement', id: 1}, + {leadingVisual: getColorCircle('#d73a4a'), text: 'bug', id: 2}, + {leadingVisual: getColorCircle('#0cf478'), text: 'good first issue', id: 3}, + {leadingVisual: getColorCircle('#ffd78e'), text: 'design', id: 4}, + {leadingVisual: getColorCircle('#ff0000'), text: 'blocker', id: 5}, + {leadingVisual: getColorCircle('#a4f287'), text: 'backend', id: 6}, + {leadingVisual: getColorCircle('#8dc6fc'), text: 'frontend', id: 7} +] + +function DemoComponent() { +const [selected, setSelected] = React.useState([items[0], items[1]]) + const [filter, setFilter] = React.useState('') + const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) + const [open, setOpen] = React.useState(false) + + return ( + ( + + {children || 'Select Labels'} + + )} + placeholderText="Filter Labels" + open={open} + onOpenChange={setOpen} + items={filteredItems} + selected={selected} + onSelectedChange={setSelected} + onFilterChange={setFilter} + showItemDividers={true} + overlayProps={{width: 'small', height: 'xsmall'}} + /> + ) +} + +render() +``` + ## Component props diff --git a/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js b/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js index e80b9fa4b32..0c928bffe25 100644 --- a/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js +++ b/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js @@ -23,6 +23,7 @@ import State from '../../../components/State' import {Dialog as Dialog2} from '../../../../src/Dialog/Dialog' import {AnchoredOverlay} from '../../../../src/AnchoredOverlay' import {ConfirmationDialog, useConfirm} from '../../../../src/Dialog/ConfirmationDialog' +import {SelectPanel} from '../../../../src/SelectPanel/SelectPanel' export default { ...doctocatComponents, @@ -48,5 +49,6 @@ export default { Dialog2, ConfirmationDialog, useConfirm, - AnchoredOverlay + AnchoredOverlay, + SelectPanel } From ae8abd4c4e4cf950ea92a9c44009745d0cee7c00 Mon Sep 17 00:00:00 2001 From: dgreif Date: Mon, 24 May 2021 09:10:08 -0700 Subject: [PATCH 3/8] fix: use `nearest` scroll block to avoid full page scrolling --- src/FilteredActionList/FilteredActionList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FilteredActionList/FilteredActionList.tsx b/src/FilteredActionList/FilteredActionList.tsx index 4b548f35779..4e8dbf6d273 100644 --- a/src/FilteredActionList/FilteredActionList.tsx +++ b/src/FilteredActionList/FilteredActionList.tsx @@ -70,14 +70,14 @@ export function FilteredActionList({ if (current) { current.classList.add(itemActiveDescendantClass) - current.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'center'}) + current.scrollIntoView({behavior: 'smooth', block: 'nearest', inline: 'nearest'}) } } }) useEffect(() => { // if items changed, we want to instantly move active descendant into view - activeDescendantRef.current?.scrollIntoView({block: 'center', inline: 'center'}) + activeDescendantRef.current?.scrollIntoView({block: 'nearest', inline: 'nearest'}) }, [items]) return ( From 8533b578821655285459c16c6be05712c62f9988 Mon Sep 17 00:00:00 2001 From: dgreif Date: Mon, 24 May 2021 14:53:58 -0700 Subject: [PATCH 4/8] fix(SelectPanel): scroll into view with buffer --- src/FilteredActionList/FilteredActionList.tsx | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/FilteredActionList/FilteredActionList.tsx b/src/FilteredActionList/FilteredActionList.tsx index 4e8dbf6d273..ad787892c01 100644 --- a/src/FilteredActionList/FilteredActionList.tsx +++ b/src/FilteredActionList/FilteredActionList.tsx @@ -16,6 +16,29 @@ export interface FilteredActionListProps extends Partial> } +function scrollIntoViewingArea( + child: HTMLElement, + container: HTMLElement, + margin = 8, + behavior: ScrollBehavior = 'smooth' +) { + const {top: childTop, bottom: childBottom} = child.getBoundingClientRect() + const {top: containerTop, bottom: containerBottom} = container.getBoundingClientRect() + + const isChildTopAboveViewingArea = childTop < containerTop + margin + const isChildBottomBelowViewingArea = childBottom > containerBottom - margin + + if (isChildTopAboveViewingArea) { + const scrollHeightToChildTop = childTop - containerTop + container.scrollTop + container.scrollTo({behavior, top: scrollHeightToChildTop - margin}) + } else if (isChildBottomBelowViewingArea) { + const scrollHeightToChildBottom = childBottom - containerBottom + container.scrollTop + container.scrollTo({behavior, top: scrollHeightToChildBottom + margin}) + } + + // either completely in view or outside viewing area on both ends, don't scroll +} + export function FilteredActionList({ loading = false, placeholderText, @@ -33,6 +56,7 @@ export function FilteredActionList({ ) const containerRef = useRef(null) + const scrollContainerRef = useRef(null) const inputRef = useRef(null) const activeDescendantRef = useRef() const listId = useMemo(uniqueId, []) @@ -70,14 +94,19 @@ export function FilteredActionList({ if (current) { current.classList.add(itemActiveDescendantClass) - current.scrollIntoView({behavior: 'smooth', block: 'nearest', inline: 'nearest'}) + + if (scrollContainerRef.current) { + scrollIntoViewingArea(current, scrollContainerRef.current) + } } } }) useEffect(() => { // if items changed, we want to instantly move active descendant into view - activeDescendantRef.current?.scrollIntoView({block: 'nearest', inline: 'nearest'}) + if (activeDescendantRef.current && scrollContainerRef.current) { + scrollIntoViewingArea(activeDescendantRef.current, scrollContainerRef.current, undefined, 'auto') + } }, [items]) return ( @@ -94,7 +123,7 @@ export function FilteredActionList({ aria-controls={listId} {...textInputProps} /> - + {loading ? ( From 7e2c16957654a67902fd7365ac99b69b915995fd Mon Sep 17 00:00:00 2001 From: dgreif Date: Mon, 24 May 2021 15:04:35 -0700 Subject: [PATCH 5/8] fix(SelectPanel): add box shadow below filter input --- src/FilteredActionList/FilteredActionList.tsx | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/FilteredActionList/FilteredActionList.tsx b/src/FilteredActionList/FilteredActionList.tsx index ad787892c01..b0ab8d8fef5 100644 --- a/src/FilteredActionList/FilteredActionList.tsx +++ b/src/FilteredActionList/FilteredActionList.tsx @@ -8,6 +8,8 @@ import Spinner from '../Spinner' import {useFocusZone} from '../hooks/useFocusZone' import {uniqueId} from '../utils/uniqueId' import {itemActiveDescendantClass} from '../ActionList/Item' +import styled from 'styled-components' +import {get} from '../constants' export interface FilteredActionListProps extends Partial>, ListPropsBase { loading?: boolean @@ -39,6 +41,11 @@ function scrollIntoViewingArea( // either completely in view or outside viewing area on both ends, don't scroll } +const StyledHeader = styled.div` + box-shadow: 0 1px 0 ${get('colors.border.primary')}; + z-index: 1; +` + export function FilteredActionList({ loading = false, placeholderText, @@ -111,18 +118,20 @@ export function FilteredActionList({ return ( - + + + {loading ? ( From c155adbb76c0eca5a6678bd2af9d29a6d77cf140 Mon Sep 17 00:00:00 2001 From: dgreif Date: Mon, 24 May 2021 20:42:31 -0700 Subject: [PATCH 6/8] Smaller dots in SelectPanel examples --- docs/content/SelectPanel.mdx | 2 +- src/stories/SelectPanel.stories.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/SelectPanel.mdx b/docs/content/SelectPanel.mdx index c633707df49..06216bb4f85 100644 --- a/docs/content/SelectPanel.mdx +++ b/docs/content/SelectPanel.mdx @@ -10,7 +10,7 @@ A `SelectPanel` provides an anchor that will open an overlay with a list of sele ```javascript live noinline function getColorCircle(color) { return function () { - return + return } } diff --git a/src/stories/SelectPanel.stories.tsx b/src/stories/SelectPanel.stories.tsx index 6e1bd444153..2f1d16c5d53 100644 --- a/src/stories/SelectPanel.stories.tsx +++ b/src/stories/SelectPanel.stories.tsx @@ -31,7 +31,7 @@ export default meta function getColorCircle(color: string) { return function () { - return + return } } From 27fec3fccd975cba9eebea1b4488dddb9c635b5e Mon Sep 17 00:00:00 2001 From: Dusty Greif Date: Mon, 24 May 2021 21:22:52 -0700 Subject: [PATCH 7/8] Create soft-schools-nail.md --- .changeset/soft-schools-nail.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/soft-schools-nail.md diff --git a/.changeset/soft-schools-nail.md b/.changeset/soft-schools-nail.md new file mode 100644 index 00000000000..8b88d3729fb --- /dev/null +++ b/.changeset/soft-schools-nail.md @@ -0,0 +1,5 @@ +--- +"@primer/components": patch +--- + +Handle overflow and active-decendant scrolling within `SelectPanel` From 14e04c84400f328ce5a1a358f71886ef890097d9 Mon Sep 17 00:00:00 2001 From: Dusty Greif Date: Tue, 25 May 2021 12:24:42 -0700 Subject: [PATCH 8/8] Update soft-schools-nail.md --- .changeset/soft-schools-nail.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/soft-schools-nail.md b/.changeset/soft-schools-nail.md index 8b88d3729fb..f1238040154 100644 --- a/.changeset/soft-schools-nail.md +++ b/.changeset/soft-schools-nail.md @@ -2,4 +2,4 @@ "@primer/components": patch --- -Handle overflow and active-decendant scrolling within `SelectPanel` +Handle overflow and active-descendant scrolling within `SelectPanel`