From 19daa03de22063bb9235d4f65ff3e6ddb5e5b582 Mon Sep 17 00:00:00 2001 From: Armagan Ersoz Date: Thu, 19 Sep 2024 14:13:47 +1000 Subject: [PATCH 1/3] Explore SelectPanel.Messsage API and managing the visibility in the component --- .../src/FeatureFlags/DefaultFeatureFlags.ts | 2 +- .../FilteredActionListEntry.tsx | 5 +- ...eredActionListWithDeprecatedActionList.tsx | 2 + ...FilteredActionListWithModernActionList.tsx | 8 +- .../SelectPanel.features.stories.tsx | 70 +++++++++++++++++ .../react/src/SelectPanel/SelectPanel.tsx | 76 ++++++++++++++++--- 6 files changed, 149 insertions(+), 14 deletions(-) diff --git a/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts b/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts index 3ab756ea60f..daeb9cee255 100644 --- a/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts +++ b/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts @@ -5,5 +5,5 @@ export const DefaultFeatureFlags = FeatureFlagScope.create({ primer_react_css_modules_staff: false, primer_react_css_modules_ga: false, primer_react_action_list_item_as_button: false, - primer_react_select_panel_with_modern_action_list: false, + primer_react_select_panel_with_modern_action_list: true, }) diff --git a/packages/react/src/FilteredActionList/FilteredActionListEntry.tsx b/packages/react/src/FilteredActionList/FilteredActionListEntry.tsx index 450608c6b0a..d8f736514cd 100644 --- a/packages/react/src/FilteredActionList/FilteredActionListEntry.tsx +++ b/packages/react/src/FilteredActionList/FilteredActionListEntry.tsx @@ -6,9 +6,8 @@ import {useFeatureFlag} from '../FeatureFlags' export function FilteredActionList(props: FilteredActionListProps): JSX.Element { const enabled = useFeatureFlag('primer_react_select_panel_with_modern_action_list') - - if (enabled) return - else return + return + // else return } FilteredActionList.displayName = 'FilteredActionList' diff --git a/packages/react/src/FilteredActionList/FilteredActionListWithDeprecatedActionList.tsx b/packages/react/src/FilteredActionList/FilteredActionListWithDeprecatedActionList.tsx index 3de9c215a72..77c4e7c8139 100644 --- a/packages/react/src/FilteredActionList/FilteredActionListWithDeprecatedActionList.tsx +++ b/packages/react/src/FilteredActionList/FilteredActionListWithDeprecatedActionList.tsx @@ -35,6 +35,8 @@ export interface FilteredActionListProps onFilterChange: (value: string, e: React.ChangeEvent | null) => void textInputProps?: Partial> inputRef?: React.RefObject + emptyState?: boolean + children?: React.ReactNode } const StyledHeader = styled.div` diff --git a/packages/react/src/FilteredActionList/FilteredActionListWithModernActionList.tsx b/packages/react/src/FilteredActionList/FilteredActionListWithModernActionList.tsx index 82e961070dc..162803dbf2b 100644 --- a/packages/react/src/FilteredActionList/FilteredActionListWithModernActionList.tsx +++ b/packages/react/src/FilteredActionList/FilteredActionListWithModernActionList.tsx @@ -18,9 +18,11 @@ import {VisuallyHidden} from '../internal/components/VisuallyHidden' import type {SxProp} from '../sx' import type {FilteredActionListLoadingType} from './FilteredActionListLoaders' import {FilteredActionListLoadingTypes, FilteredActionListBodyLoader} from './FilteredActionListLoaders' +import Text from '../Text' import {isValidElementType} from 'react-is' import type {RenderItemFn} from '../deprecated/ActionList/List' +import {SelectPanelMessage} from '../SelectPanel/SelectPanel' const menuScrollMargins: ScrollIntoViewOptions = {startMargin: 0, endMargin: 8} @@ -35,6 +37,7 @@ export interface FilteredActionListProps onFilterChange: (value: string, e: React.ChangeEvent) => void textInputProps?: Partial> inputRef?: React.RefObject + emptyState?: boolean } const StyledHeader = styled.div` @@ -54,6 +57,8 @@ export function FilteredActionList({ sx, groupMetadata, showItemDividers, + emptyState, + message, ...listProps }: FilteredActionListProps): JSX.Element { const [filterValue, setInternalFilterValue] = useProvidedStateOrCreate(externalFilterValue, undefined, '') @@ -150,7 +155,7 @@ export function FilteredActionList({ /> Items will be filtered as you type - + {loading && loadingType.appearsInBody ? ( ) : ( @@ -173,6 +178,7 @@ export function FilteredActionList({ })} )} + {message} ) diff --git a/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx index d8053503244..f6d779af44c 100644 --- a/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx @@ -15,6 +15,8 @@ import { VersionsIcon, } from '@primer/octicons-react' import useSafeTimeout from '../hooks/useSafeTimeout' +import Link from '../Link' +import Text from '../Text' const meta = { title: 'Components/SelectPanel/Features', @@ -422,3 +424,71 @@ export const AsyncFetch: StoryObj = { }, }, } + +export const NoItems = () => { + const [selected, setSelected] = React.useState([]) + const [filteredItems, setFilteredItems] = React.useState([]) + const [open, setOpen] = useState(true) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const onFilterChange = (value: string = '') => { + setTimeout(() => { + // fetch the items + setFilteredItems([]) + }, 0) + } + return ( + ( + + )} + open={open} + onOpenChange={setOpen} + items={filteredItems} + selected={selected} + onSelectedChange={setSelected} + onFilterChange={onFilterChange} + overlayProps={{width: 'medium', height: 'large'}} + > + + Start your first project to organise your issues. + + + Adjust your search term to find other languages + + + ) +} +export const NoMatches = () => { + const [selected, setSelected] = React.useState([]) + const [filter, setFilter] = React.useState('') + const [open, setOpen] = useState(true) + + const filteredItems = items.filter(item => item.text.toLowerCase().startsWith(filter.toLowerCase())) + return ( + ( + + )} + open={open} + onOpenChange={setOpen} + items={filteredItems} + selected={selected} + onSelectedChange={setSelected} + onFilterChange={setFilter} + overlayProps={{width: 'medium', height: 'small'}} + > + + Start your first project to organise your issues. + + + Adjust your search term to find other languages + + + ) +} diff --git a/packages/react/src/SelectPanel/SelectPanel.tsx b/packages/react/src/SelectPanel/SelectPanel.tsx index 9497c7e9199..6934eaa2ab5 100644 --- a/packages/react/src/SelectPanel/SelectPanel.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.tsx @@ -4,6 +4,7 @@ import type {AnchoredOverlayProps} from '../AnchoredOverlay' import {AnchoredOverlay} from '../AnchoredOverlay' import type {AnchoredOverlayWrapperAnchorProps} from '../AnchoredOverlay/AnchoredOverlay' import Box from '../Box' +import Text from '../Text' import type {FilteredActionListProps} from '../FilteredActionList' import {FilteredActionList} from '../FilteredActionList' import Heading from '../Heading' @@ -49,11 +50,13 @@ interface SelectPanelBaseProps { initialLoadingType?: InitialLoadingType } -export type SelectPanelProps = SelectPanelBaseProps & - Omit & - Pick & - AnchoredOverlayWrapperAnchorProps & - (SelectPanelSingleSelection | SelectPanelMultiSelection) +export type SelectPanelProps = React.PropsWithChildren< + SelectPanelBaseProps & + Omit & + Pick & + AnchoredOverlayWrapperAnchorProps & + (SelectPanelSingleSelection | SelectPanelMultiSelection) +> function isMultiSelectVariant( selected: SelectPanelSingleSelection['selected'] | SelectPanelMultiSelection['selected'], @@ -66,7 +69,40 @@ const focusZoneSettings: Partial = { disabled: true, } -export function SelectPanel({ +export type SelectPanelMessageProps = { + children: React.ReactNode + title: string + variant: 'noitems' | 'nomatches' +} +export const SelectPanelMessage: React.FC = ({variant = 'noitems', title, children}) => { + return ( + + {title} + + {children} + + + ) +} + +function Panel({ open, onOpenChange, renderAnchor = props => { @@ -94,6 +130,7 @@ export function SelectPanel({ sx, loading, initialLoadingType = 'spinner', + children, ...listProps }: SelectPanelProps): JSX.Element { const inputRef = React.useRef(null) @@ -241,6 +278,19 @@ export function SelectPanel({ } } + const isNoItemsState = items.length === 0 && dataLoadedOnce.current && !loading + const isNoMatchState = items.length === 0 && filterValue !== '' && dataLoadedOnce.current && !loading + + const deconstructChildren = (children: React.ReactNode) => { + return React.Children.toArray(children).find(child => { + if (isNoMatchState) return child.props.variant === 'nomatches' && React.isValidElement(child) + else if (isNoItemsState) return child.props.variant === 'noitems' && React.isValidElement(child) + else return [] + }) + } + + const message = deconstructChildren(children) + return ( ) : null} + - {footer && ( + + {footer && !isNoItemsState ? ( {footer} - )} + ) : null} ) } -SelectPanel.displayName = 'SelectPanel' +Panel.displayName = 'SelectPanel' + +export const SelectPanel = Object.assign(Panel, { + Message: SelectPanelMessage, +}) From 38ca715049e73e1f817ac36474976064cc74f457 Mon Sep 17 00:00:00 2001 From: Armagan Ersoz Date: Thu, 19 Sep 2024 14:33:35 +1000 Subject: [PATCH 2/3] fix linting and staff --- packages/react/src/FeatureFlags/DefaultFeatureFlags.ts | 2 +- .../react/src/FilteredActionList/FilteredActionListEntry.tsx | 4 ++-- .../FilteredActionListWithDeprecatedActionList.tsx | 2 -- .../FilteredActionListWithModernActionList.tsx | 4 ---- .../react/src/SelectPanel/SelectPanel.features.stories.tsx | 1 - packages/react/src/SelectPanel/SelectPanel.tsx | 3 ++- 6 files changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts b/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts index daeb9cee255..3ab756ea60f 100644 --- a/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts +++ b/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts @@ -5,5 +5,5 @@ export const DefaultFeatureFlags = FeatureFlagScope.create({ primer_react_css_modules_staff: false, primer_react_css_modules_ga: false, primer_react_action_list_item_as_button: false, - primer_react_select_panel_with_modern_action_list: true, + primer_react_select_panel_with_modern_action_list: false, }) diff --git a/packages/react/src/FilteredActionList/FilteredActionListEntry.tsx b/packages/react/src/FilteredActionList/FilteredActionListEntry.tsx index d8f736514cd..e185ecedfc0 100644 --- a/packages/react/src/FilteredActionList/FilteredActionListEntry.tsx +++ b/packages/react/src/FilteredActionList/FilteredActionListEntry.tsx @@ -6,8 +6,8 @@ import {useFeatureFlag} from '../FeatureFlags' export function FilteredActionList(props: FilteredActionListProps): JSX.Element { const enabled = useFeatureFlag('primer_react_select_panel_with_modern_action_list') - return - // else return + if (enabled) return + else return } FilteredActionList.displayName = 'FilteredActionList' diff --git a/packages/react/src/FilteredActionList/FilteredActionListWithDeprecatedActionList.tsx b/packages/react/src/FilteredActionList/FilteredActionListWithDeprecatedActionList.tsx index 77c4e7c8139..3de9c215a72 100644 --- a/packages/react/src/FilteredActionList/FilteredActionListWithDeprecatedActionList.tsx +++ b/packages/react/src/FilteredActionList/FilteredActionListWithDeprecatedActionList.tsx @@ -35,8 +35,6 @@ export interface FilteredActionListProps onFilterChange: (value: string, e: React.ChangeEvent | null) => void textInputProps?: Partial> inputRef?: React.RefObject - emptyState?: boolean - children?: React.ReactNode } const StyledHeader = styled.div` diff --git a/packages/react/src/FilteredActionList/FilteredActionListWithModernActionList.tsx b/packages/react/src/FilteredActionList/FilteredActionListWithModernActionList.tsx index 162803dbf2b..2ccc9ac9f2c 100644 --- a/packages/react/src/FilteredActionList/FilteredActionListWithModernActionList.tsx +++ b/packages/react/src/FilteredActionList/FilteredActionListWithModernActionList.tsx @@ -18,11 +18,9 @@ import {VisuallyHidden} from '../internal/components/VisuallyHidden' import type {SxProp} from '../sx' import type {FilteredActionListLoadingType} from './FilteredActionListLoaders' import {FilteredActionListLoadingTypes, FilteredActionListBodyLoader} from './FilteredActionListLoaders' -import Text from '../Text' import {isValidElementType} from 'react-is' import type {RenderItemFn} from '../deprecated/ActionList/List' -import {SelectPanelMessage} from '../SelectPanel/SelectPanel' const menuScrollMargins: ScrollIntoViewOptions = {startMargin: 0, endMargin: 8} @@ -37,7 +35,6 @@ export interface FilteredActionListProps onFilterChange: (value: string, e: React.ChangeEvent) => void textInputProps?: Partial> inputRef?: React.RefObject - emptyState?: boolean } const StyledHeader = styled.div` @@ -57,7 +54,6 @@ export function FilteredActionList({ sx, groupMetadata, showItemDividers, - emptyState, message, ...listProps }: FilteredActionListProps): JSX.Element { diff --git a/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx b/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx index f6d779af44c..f2fb3cf5f0b 100644 --- a/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.features.stories.tsx @@ -16,7 +16,6 @@ import { } from '@primer/octicons-react' import useSafeTimeout from '../hooks/useSafeTimeout' import Link from '../Link' -import Text from '../Text' const meta = { title: 'Components/SelectPanel/Features', diff --git a/packages/react/src/SelectPanel/SelectPanel.tsx b/packages/react/src/SelectPanel/SelectPanel.tsx index 6934eaa2ab5..eafda3d77a7 100644 --- a/packages/react/src/SelectPanel/SelectPanel.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.tsx @@ -74,6 +74,8 @@ export type SelectPanelMessageProps = { title: string variant: 'noitems' | 'nomatches' } +// we will have more variants in the future like error / warning etc +// eslint-disable-next-line @typescript-eslint/no-unused-vars export const SelectPanelMessage: React.FC = ({variant = 'noitems', title, children}) => { return ( Date: Thu, 19 Sep 2024 14:35:30 +1000 Subject: [PATCH 3/3] footer not depend on empty state --- packages/react/src/SelectPanel/SelectPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/SelectPanel/SelectPanel.tsx b/packages/react/src/SelectPanel/SelectPanel.tsx index eafda3d77a7..59c458c5f5c 100644 --- a/packages/react/src/SelectPanel/SelectPanel.tsx +++ b/packages/react/src/SelectPanel/SelectPanel.tsx @@ -354,7 +354,7 @@ function Panel({ sx={{...sx, height: 'inherit', maxHeight: 'inherit'}} /> - {footer && !isNoItemsState ? ( + {footer ? (