From 4e1da64c9348d630a4e6cce3b19e62ea148aba6d Mon Sep 17 00:00:00 2001 From: Bernardo Sunderhus Date: Fri, 9 Aug 2024 10:29:06 +0200 Subject: [PATCH] chore: ass tests and noPopover to context --- .../library/etc/react-tag-picker.api.md | 4 +- .../src/components/TagPicker/TagPicker.cy.tsx | 52 ++++++++++++------- .../components/TagPicker/TagPicker.types.ts | 11 +++- .../src/components/TagPicker/useTagPicker.ts | 13 +++-- .../TagPicker/useTagPickerContextValues.ts | 2 + .../TagPickerControl/useTagPickerControl.tsx | 5 +- .../TagPickerInput/useTagPickerInput.tsx | 2 +- .../library/src/contexts/TagPickerContext.ts | 1 + .../TagPicker/TagPickerNoPopover.stories.tsx | 7 ++- 9 files changed, 63 insertions(+), 34 deletions(-) diff --git a/packages/react-components/react-tag-picker/library/etc/react-tag-picker.api.md b/packages/react-components/react-tag-picker/library/etc/react-tag-picker.api.md index 609c8d2392ddf1..ad2eb52a97aab4 100644 --- a/packages/react-components/react-tag-picker/library/etc/react-tag-picker.api.md +++ b/packages/react-components/react-tag-picker/library/etc/react-tag-picker.api.md @@ -220,7 +220,7 @@ export type TagPickerProps = ComponentProps & Pick; onOptionSelect?: EventHandler; - children: [JSX.Element, JSX.Element] | JSX.Element; + children: [JSX.Element, JSX.Element | undefined | false] | JSX.Element; inline?: boolean; }; @@ -231,7 +231,7 @@ export type TagPickerSize = 'medium' | 'large' | 'extra-large'; export type TagPickerSlots = {}; // @public -export type TagPickerState = ComponentState & Pick & Pick & { +export type TagPickerState = ComponentState & Pick & Pick & { trigger: React_2.ReactNode; popover?: React_2.ReactNode; inline: boolean; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPicker/TagPicker.cy.tsx b/packages/react-components/react-tag-picker/library/src/components/TagPicker/TagPicker.cy.tsx index cbf6a728f949c2..26b49ca3a6a1a7 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPicker/TagPicker.cy.tsx +++ b/packages/react-components/react-tag-picker/library/src/components/TagPicker/TagPicker.cy.tsx @@ -13,6 +13,7 @@ import { TagPickerOption } from '../TagPickerOption/TagPickerOption'; import { Avatar } from '@fluentui/react-avatar'; import { Button } from '@fluentui/react-button'; +import 'cypress-real-events'; /** * This error means that ResizeObserver * was not able to deliver all observations within a single animation frame. @@ -40,9 +41,14 @@ const options = [ 'Maria Rossi', ]; -type TagPickerControlledProps = Pick; +type TagPickerControlledProps = Pick; -const TagPickerControlled = ({ open, defaultOpen, defaultSelectedOptions = [] }: TagPickerControlledProps) => { +const TagPickerControlled = ({ + open, + defaultOpen, + defaultSelectedOptions = [], + noPopover = false, +}: TagPickerControlledProps) => { const [selectedOptions, setSelectedOptions] = React.useState(defaultSelectedOptions); const onOptionSelect: TagPickerProps['onOptionSelect'] = (e, data) => { setSelectedOptions(data.selectedOptions); @@ -54,6 +60,7 @@ const TagPickerControlled = ({ open, defaultOpen, defaultSelectedOptions = [] }: return (
- - {options - .filter(option => !selectedOptions.includes(option)) - .map((option, index) => ( - } - value={option} - key={option} - > - {option} - - ))} - + {noPopover ? undefined : ( + + {options + .filter(option => !selectedOptions.includes(option)) + .map((option, index) => ( + } + value={option} + key={option} + > + {option} + + ))} + + )}
); @@ -305,4 +314,11 @@ describe('TagPicker', () => { }); }); }); + it('should not render popover when "noPopover"', () => { + mount(); + cy.get('[data-testid="tag-picker-control"]').should('exist'); + cy.get('[data-testid="tag-picker-list"]').should('not.exist'); + cy.get('[data-testid="tag-picker-input"]').realClick(); + cy.get('[data-testid="tag-picker-list"]').should('not.exist'); + }); }); diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPicker/TagPicker.types.ts b/packages/react-components/react-tag-picker/library/src/components/TagPicker/TagPicker.types.ts index c6996c9cc23c09..00969493e3b4eb 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPicker/TagPicker.types.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPicker/TagPicker.types.ts @@ -46,7 +46,7 @@ export type TagPickerProps = ComponentProps & /** * Can contain two children including a trigger and a popover */ - children: [JSX.Element, JSX.Element] | JSX.Element; + children: [JSX.Element, JSX.Element | undefined | false] | JSX.Element; /** * TagPickers are rendered out of DOM order on `document.body` by default, * use this to render the popover in DOM order @@ -81,7 +81,14 @@ export type TagPickerState = ComponentState & > & Pick< TagPickerContextValue, - 'triggerRef' | 'secondaryActionRef' | 'popoverId' | 'popoverRef' | 'targetRef' | 'tagPickerGroupRef' | 'size' + | 'triggerRef' + | 'secondaryActionRef' + | 'popoverId' + | 'popoverRef' + | 'targetRef' + | 'tagPickerGroupRef' + | 'size' + | 'noPopover' > & { trigger: React.ReactNode; popover?: React.ReactNode; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPicker/useTagPicker.ts b/packages/react-components/react-tag-picker/library/src/components/TagPicker/useTagPicker.ts index 004e2acd5fe242..6c658638188a40 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPicker/useTagPicker.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPicker/useTagPicker.ts @@ -77,6 +77,7 @@ export const useTagPicker_unstable = (props: TagPickerProps): TagPickerState => trigger, popover: comboboxState.open || comboboxState.hasFocus ? popover : undefined, popoverId, + noPopover, disabled: comboboxState.disabled, triggerRef: useMergedRefs(triggerInnerRef, activeParentRef), popoverRef: useMergedRefs(listboxRef, containerRef), @@ -120,17 +121,19 @@ const childrenToTriggerAndPopover = (children: React.ReactNode, noPopover: boole } } + if (noPopover) { + return { trigger: childrenArray[0] }; + } + let trigger: React.ReactElement | undefined = undefined; let popover: React.ReactElement | undefined = undefined; + if (childrenArray.length === 2) { trigger = childrenArray[0]; popover = childrenArray[1]; } else if (childrenArray.length === 1) { - if (noPopover) { - trigger = childrenArray[0]; - } else { - popover = childrenArray[0]; - } + popover = childrenArray[0]; } + return { trigger, popover }; }; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPicker/useTagPickerContextValues.ts b/packages/react-components/react-tag-picker/library/src/components/TagPicker/useTagPickerContextValues.ts index 167e876ffc171e..e4c20f182b9c6b 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPicker/useTagPickerContextValues.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPicker/useTagPickerContextValues.ts @@ -24,6 +24,7 @@ export function useTagPickerContextValues(state: TagPickerState): TagPickerConte open, popoverId, disabled, + noPopover, } = state; return { activeDescendant: React.useMemo( @@ -59,6 +60,7 @@ export function useTagPickerContextValues(state: TagPickerState): TagPickerConte open, popoverId, disabled, + noPopover, }, }; } diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/useTagPickerControl.tsx b/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/useTagPickerControl.tsx index 273c2a09b6c083..46ade32dbad2df 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/useTagPickerControl.tsx +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/useTagPickerControl.tsx @@ -39,6 +39,7 @@ export const useTagPickerControl_unstable = ( const appearance = useTagPickerContext_unstable(ctx => ctx.appearance); const disabled = useTagPickerContext_unstable(ctx => ctx.disabled); const invalid = useFieldContext_unstable()?.validationState === 'error'; + const noPopover = useTagPickerContext_unstable(ctx => ctx.noPopover ?? false); const innerRef = React.useRef(null); const expandIconRef = React.useRef(null); @@ -53,7 +54,7 @@ export const useTagPickerControl_unstable = ( } const expandIcon = slot.optional(props.expandIcon, { - renderByDefault: true, + renderByDefault: !noPopover, defaultProps: { 'aria-expanded': open, children: , @@ -107,7 +108,7 @@ export const useTagPickerControl_unstable = ( root: slot.always( getIntrinsicElementProps('div', { ref: useMergedRefs(ref, targetRef, innerRef), - 'aria-owns': open ? popoverId : undefined, + 'aria-owns': open && !noPopover ? popoverId : undefined, ...props, onMouseDown: handleMouseDown, }), diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/useTagPickerInput.tsx b/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/useTagPickerInput.tsx index 16fe6132f87975..d103572eb8c914 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/useTagPickerInput.tsx +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/useTagPickerInput.tsx @@ -45,7 +45,7 @@ export const useTagPickerInput_unstable = ( const setHasFocus = useTagPickerContext_unstable(ctx => ctx.setHasFocus); const clearSelection = useTagPickerContext_unstable(ctx => ctx.clearSelection); const open = useTagPickerContext_unstable(ctx => ctx.open); - const popoverId = useTagPickerContext_unstable(ctx => ctx.popoverId); + const popoverId = useTagPickerContext_unstable(ctx => (ctx.noPopover ? undefined : ctx.popoverId)); const selectOption = useTagPickerContext_unstable(ctx => ctx.selectOption); const getOptionById = useTagPickerContext_unstable(ctx => ctx.getOptionById); const contextValue = useTagPickerContext_unstable(ctx => ctx.value); diff --git a/packages/react-components/react-tag-picker/library/src/contexts/TagPickerContext.ts b/packages/react-components/react-tag-picker/library/src/contexts/TagPickerContext.ts index efd9c1a15ece79..c4f16ea6cff931 100644 --- a/packages/react-components/react-tag-picker/library/src/contexts/TagPickerContext.ts +++ b/packages/react-components/react-tag-picker/library/src/contexts/TagPickerContext.ts @@ -25,6 +25,7 @@ export interface TagPickerContextValue secondaryActionRef: React.RefObject; tagPickerGroupRef: React.RefObject; size: TagPickerSize; + noPopover?: boolean; } /** diff --git a/packages/react-components/react-tag-picker/stories/src/TagPicker/TagPickerNoPopover.stories.tsx b/packages/react-components/react-tag-picker/stories/src/TagPicker/TagPickerNoPopover.stories.tsx index 271067a445a904..9ec13614763739 100644 --- a/packages/react-components/react-tag-picker/stories/src/TagPicker/TagPickerNoPopover.stories.tsx +++ b/packages/react-components/react-tag-picker/stories/src/TagPicker/TagPickerNoPopover.stories.tsx @@ -10,6 +10,7 @@ import { Tag, Avatar, Field } from '@fluentui/react-components'; export const NoPopover = () => { const [selectedOptions, setSelectedOptions] = React.useState([]); + const [inputValue, setInputValue] = React.useState(''); const onOptionSelect: TagPickerProps['onOptionSelect'] = (_, data) => { setSelectedOptions(data.selectedOptions); @@ -20,16 +21,14 @@ export const NoPopover = () => { const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === 'Enter' && inputValue) { setInputValue(''); - setSelectedOptions(curr => [...curr, inputValue]); + setSelectedOptions(curr => (curr.includes(inputValue) ? curr : [...curr, inputValue])); } }; - const [inputValue, setInputValue] = React.useState(''); - return ( - + {selectedOptions.map((option, index) => (