-
Notifications
You must be signed in to change notification settings - Fork 149
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: move Dropdown, DropdownOverlay to floating ui
- Loading branch information
1 parent
37653a8
commit 10076fe
Showing
20 changed files
with
942 additions
and
21 deletions.
There are no files selected for viewing
72 changes: 72 additions & 0 deletions
72
packages/blade/src/components/DropdownNew/ActionList/ActionList.web.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { useFloatingTree, useListItem, useMergeRefs } from '@floating-ui/react'; | ||
import React from 'react'; | ||
import type { DropdownItemProps } from '../types'; | ||
import { useDropdown } from '../useDropdown'; | ||
import { Box } from '~components/Box'; | ||
import { BaseMenuItem } from '~components/BaseMenu'; | ||
import { ChevronRightIcon } from '~components/Icons'; | ||
import type { ActionListProps } from '~components/ActionList'; | ||
|
||
const ActionList = ({ children }: ActionListProps): React.ReactElement => { | ||
return <Box>{children}</Box>; | ||
}; | ||
|
||
const ActionListItem = React.forwardRef<HTMLButtonElement, DropdownItemProps>( | ||
( | ||
{ | ||
title, | ||
isDisabled, | ||
description, | ||
leading, | ||
trailing, | ||
_isDropdownTrigger, | ||
_hasFocusInside, | ||
href, | ||
target, | ||
children, | ||
as, | ||
...props | ||
}, | ||
forwardedRef, | ||
) => { | ||
const dropdown = useDropdown(); | ||
const item = useListItem({ label: isDisabled && Boolean(children) ? null : title }); | ||
const tree = useFloatingTree(); | ||
|
||
const isLink = Boolean(href); | ||
|
||
const defaultAs = isLink ? 'a' : 'button'; | ||
|
||
return ( | ||
<BaseMenuItem | ||
title={title} | ||
description={description} | ||
leading={leading} | ||
trailing={ | ||
_isDropdownTrigger ? <ChevronRightIcon color="interactive.icon.gray.muted" /> : trailing | ||
} | ||
as={as ?? defaultAs} | ||
href={href} | ||
ref={useMergeRefs([item.ref, forwardedRef])} | ||
isDisabled={isDisabled} | ||
{...props} | ||
{...(_isDropdownTrigger | ||
? {} | ||
: dropdown.getItemProps({ | ||
onClick(event: React.MouseEvent<HTMLButtonElement>) { | ||
props.onClick?.(event); | ||
tree?.events.emit('click'); | ||
}, | ||
onFocus(event: React.FocusEvent<HTMLButtonElement>) { | ||
props.onFocus?.(event); | ||
dropdown.setHasFocusInside(true); | ||
}, | ||
}))} | ||
> | ||
{children} | ||
</BaseMenuItem> | ||
); | ||
}, | ||
); | ||
|
||
export { ActionList, ActionListItem }; |
14 changes: 14 additions & 0 deletions
14
packages/blade/src/components/DropdownNew/Dropdown.native.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import type { DropdownProps } from './types'; | ||
import { Text } from '~components/Typography'; | ||
import { throwBladeError } from '~utils/logger'; | ||
|
||
const Dropdown = (_props: DropdownProps): React.ReactElement => { | ||
throwBladeError({ | ||
message: 'Dropdown is not yet implemented for native', | ||
moduleName: 'Dropdown', | ||
}); | ||
|
||
return <Text>Dropdown Component is not available for Native mobile apps.</Text>; | ||
}; | ||
|
||
export { Dropdown }; |
112 changes: 112 additions & 0 deletions
112
packages/blade/src/components/DropdownNew/Dropdown.web.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { | ||
FloatingFocusManager, | ||
FloatingList, | ||
FloatingNode, | ||
FloatingPortal, | ||
useMergeRefs, | ||
} from '@floating-ui/react'; | ||
import * as React from 'react'; | ||
import { DropdownContext, useFloatingDropdownSetup, useDropdown } from './useDropdown'; | ||
import type { DropdownProps } from './types'; | ||
|
||
const Dropdown = ({ | ||
children, | ||
openInteraction = 'click', | ||
onOpenChange, | ||
isOpen: isOpenControlled, | ||
}: DropdownProps): React.ReactElement => { | ||
const [hasFocusInside, setHasFocusInside] = React.useState(false); | ||
|
||
const elementsRef = React.useRef<(HTMLButtonElement | null)[]>([]); | ||
const labelsRef = React.useRef<(string | null)[]>([]); | ||
const parent = useDropdown(); | ||
|
||
const { | ||
getReferenceProps, | ||
getFloatingProps, | ||
getItemProps, | ||
item, | ||
refs, | ||
floatingStyles, | ||
isOpen, | ||
nodeId, | ||
isNested, | ||
context, | ||
isMounted, | ||
floatingTransitionStyles, | ||
} = useFloatingDropdownSetup({ | ||
elementsRef, | ||
openInteraction, | ||
onOpenChange, | ||
isOpen: isOpenControlled, | ||
}); | ||
|
||
const referenceProps = { | ||
ref: useMergeRefs([refs.setReference, item.ref]), | ||
...getReferenceProps( | ||
parent.getItemProps({ | ||
onFocus() { | ||
setHasFocusInside(false); | ||
parent.setHasFocusInside(true); | ||
}, | ||
}), | ||
), | ||
}; | ||
|
||
const floatingProps = { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
ref: refs.setFloating as any, | ||
style: floatingStyles, | ||
_transitionStyle: floatingTransitionStyles, | ||
...getFloatingProps(), | ||
}; | ||
|
||
const [dropdownTriggerChild, dropdownOverlayChild] = React.Children.toArray(children) as [ | ||
React.ReactElement, | ||
React.ReactElement, | ||
]; | ||
|
||
const triggerWithReferenceProps = React.cloneElement(dropdownTriggerChild, { | ||
...dropdownTriggerChild.props, | ||
_referenceProps: referenceProps, | ||
_hasFocusInside: hasFocusInside, | ||
_isDropdownTrigger: true, | ||
}); | ||
|
||
const overlayWithFloatingProps = React.cloneElement(dropdownOverlayChild, { | ||
...dropdownOverlayChild.props, | ||
...floatingProps, | ||
}); | ||
|
||
const contextValue = React.useMemo(() => { | ||
return { | ||
getItemProps, | ||
setHasFocusInside, | ||
isOpen, | ||
}; | ||
}, [isOpen]); | ||
|
||
return ( | ||
<FloatingNode id={nodeId}> | ||
<DropdownContext.Provider value={contextValue}> | ||
{triggerWithReferenceProps} | ||
<FloatingList elementsRef={elementsRef} labelsRef={labelsRef}> | ||
{isMounted && ( | ||
<FloatingPortal> | ||
<FloatingFocusManager | ||
context={context} | ||
modal={false} | ||
initialFocus={0} | ||
returnFocus={!isNested} | ||
> | ||
{overlayWithFloatingProps} | ||
</FloatingFocusManager> | ||
</FloatingPortal> | ||
)} | ||
</FloatingList> | ||
</DropdownContext.Provider> | ||
</FloatingNode> | ||
); | ||
}; | ||
|
||
export { Dropdown }; |
50 changes: 50 additions & 0 deletions
50
packages/blade/src/components/DropdownNew/DropdownNew.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import React from 'react'; | ||
import type { Meta } from '@storybook/react'; | ||
import { ActionList, ActionListItem } from './ActionList/ActionList'; | ||
import { Dropdown, DropdownOverlay } from './index'; | ||
|
||
import { SelectInput } from '~components/Input/DropdownInputTriggers'; | ||
import { Box } from '~components/Box'; | ||
import { Button } from '~components/Button'; | ||
import { TextInput } from '~components/Input/TextInput'; | ||
|
||
const DropdownStoryMeta: Meta = { | ||
title: 'Components/DropdownNew', | ||
component: Dropdown, | ||
parameters: { | ||
viewMode: 'story', | ||
options: { | ||
showPanel: false, | ||
}, | ||
previewTabs: { | ||
'storybook/docs/panel': { | ||
hidden: true, | ||
}, | ||
}, | ||
chromatic: { disableSnapshot: true }, | ||
}, | ||
}; | ||
|
||
export const InternalSelect = (): React.ReactElement => { | ||
return ( | ||
<Box | ||
padding="spacing.5" | ||
backgroundColor="surface.background.gray.moderate" | ||
width="100%" | ||
minHeight="100px" | ||
overflow="scroll" | ||
> | ||
<Dropdown selectionType="multiple"> | ||
<SelectInput label="Search" /> | ||
<DropdownOverlay> | ||
<ActionList> | ||
<ActionListItem title="Apples" value="Apples" /> | ||
<ActionListItem title="Appricots" value="Appricots" /> | ||
</ActionList> | ||
</DropdownOverlay> | ||
</Dropdown> | ||
</Box> | ||
); | ||
}; | ||
|
||
export default DropdownStoryMeta; |
14 changes: 14 additions & 0 deletions
14
packages/blade/src/components/DropdownNew/DropdownOverlay.native.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import type { DropdownOverlayProps } from './types'; | ||
import { Text } from '~components/Typography'; | ||
import { throwBladeError } from '~utils/logger'; | ||
|
||
const DropdownOverlay = (_props: DropdownOverlayProps): React.ReactElement => { | ||
throwBladeError({ | ||
message: 'DropdownOverlay is not yet implemented for native', | ||
moduleName: 'DropdownOverlay', | ||
}); | ||
|
||
return <Text>DropdownOverlay Component is not available for Native mobile apps.</Text>; | ||
}; | ||
|
||
export { DropdownOverlay }; |
64 changes: 64 additions & 0 deletions
64
packages/blade/src/components/DropdownNew/DropdownOverlay.web.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import React from 'react'; | ||
import styled from 'styled-components'; | ||
import type { DropdownOverlayProps } from './types'; | ||
import { MENU_MIN_WIDTH, overlayPaddingX, overlayPaddingY } from './tokens'; | ||
import BaseBox from '~components/Box/BaseBox'; | ||
import { componentZIndices } from '~utils/componentZIndices'; | ||
import type { BladeElementRef } from '~utils/types'; | ||
import { metaAttribute, MetaConstants } from '~utils/metaAttribute'; | ||
|
||
const UnfocussableOverlay = styled(BaseBox)((_props) => { | ||
return { | ||
'&:focus-visible': { | ||
outline: 'none', | ||
}, | ||
}; | ||
}); | ||
|
||
const _DropdownOverlay: React.ForwardRefRenderFunction<BladeElementRef, DropdownOverlayProps> = ( | ||
{ | ||
children, | ||
zIndex = componentZIndices.dropdownOverlay, | ||
_transitionStyle, | ||
minWidth, | ||
maxWidth, | ||
width, | ||
testID, | ||
...props | ||
}, | ||
ref, | ||
): React.ReactElement => { | ||
return ( | ||
<UnfocussableOverlay | ||
ref={ref as never} | ||
{...props} | ||
zIndex={zIndex} | ||
{...metaAttribute({ name: MetaConstants.Dropdown, testID })} | ||
minWidth={minWidth ?? MENU_MIN_WIDTH} | ||
width={width} | ||
maxWidth={maxWidth} | ||
> | ||
{/* | ||
Requires another nested div since floatingStyles clash with floatingTransitionStyles | ||
https://floating-ui.com/docs/usetransition#usetransitionstyles | ||
*/} | ||
<BaseBox | ||
backgroundColor="popup.background.subtle" | ||
paddingX={overlayPaddingX} | ||
paddingY={overlayPaddingY} | ||
elevation="midRaised" | ||
borderWidth="thin" | ||
borderColor="surface.border.gray.muted" | ||
borderRadius="medium" | ||
style={_transitionStyle} | ||
> | ||
{children} | ||
</BaseBox> | ||
</UnfocussableOverlay> | ||
); | ||
}; | ||
|
||
const DropdownOverlay = React.forwardRef(_DropdownOverlay); | ||
|
||
export { DropdownOverlay }; |
14 changes: 14 additions & 0 deletions
14
packages/blade/src/components/DropdownNew/VisualSubComponents/MenuDivider.native.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import type { StyledPropsBlade } from '~components/Box/styledProps'; | ||
import { Text } from '~components/Typography'; | ||
import { throwBladeError } from '~utils/logger'; | ||
|
||
const DropdownDivider = (_styledProps: StyledPropsBlade): React.ReactElement => { | ||
throwBladeError({ | ||
message: 'DropdownDivider is not yet implemented for native', | ||
moduleName: 'DropdownDivider', | ||
}); | ||
|
||
return <Text>DropdownDivider Component is not available for Native mobile apps.</Text>; | ||
}; | ||
|
||
export { DropdownDivider }; |
11 changes: 11 additions & 0 deletions
11
packages/blade/src/components/DropdownNew/VisualSubComponents/MenuDivider.web.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { getDividerMarginTokens } from '../tokens'; | ||
import type { StyledPropsBlade } from '~components/Box/styledProps'; | ||
import { Divider } from '~components/Divider'; | ||
import { useTheme } from '~utils'; | ||
|
||
const DropdownDivider = (styledProps: StyledPropsBlade): React.ReactElement => { | ||
const { theme } = useTheme(); | ||
return <Divider {...getDividerMarginTokens(theme)} {...styledProps} />; | ||
}; | ||
|
||
export { DropdownDivider }; |
23 changes: 23 additions & 0 deletions
23
packages/blade/src/components/DropdownNew/VisualSubComponents/MenuHeaderFooter.native.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import type { DropdownHeaderProps, DropdownFooterProps } from '../types'; | ||
import { Text } from '~components/Typography'; | ||
import { throwBladeError } from '~utils/logger'; | ||
|
||
const DropdownHeader = (_props: DropdownHeaderProps): React.ReactElement => { | ||
throwBladeError({ | ||
message: 'DropdownHeader is not yet implemented for native', | ||
moduleName: 'DropdownHeader', | ||
}); | ||
|
||
return <Text>DropdownHeader Component is not available for Native mobile apps.</Text>; | ||
}; | ||
|
||
const DropdownFooter = (_props: DropdownFooterProps): React.ReactElement => { | ||
throwBladeError({ | ||
message: 'DropdownFooter is not yet implemented for native', | ||
moduleName: 'DropdownFooter', | ||
}); | ||
|
||
return <Text>DropdownFooter Component is not available for Native mobile apps.</Text>; | ||
}; | ||
|
||
export { DropdownHeader, DropdownFooter }; |
Oops, something went wrong.