Skip to content

Commit

Permalink
Merge pull request #384 from pixiv/toshusai/fix-dropdown-in-modal
Browse files Browse the repository at this point in the history
fix: Modal内のDropdownSelectorの挙動修正
  • Loading branch information
toshusai authored Oct 23, 2023
2 parents fd86546 + f8bc858 commit 4ceb37c
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export function DropdownPopover({ children, ...props }: DropdownPopoverProps) {
const selectedElement = document.querySelector(
`[data-key="${props.value.toString()}"]`
) as HTMLElement | undefined
selectedElement?.scrollIntoView({ block: 'center' })
selectedElement?.focus()
window.scrollTo(windowScrollX, windowScrollY)
}
Expand Down
19 changes: 17 additions & 2 deletions packages/react/src/components/DropdownSelector/Popover/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { RefObject, useRef } from 'react'
import { RefObject, useContext, useRef } from 'react'
import { ReactNode } from 'react'
import { DismissButton, Overlay, usePopover } from '@react-aria/overlays'
import styled from 'styled-components'
import { theme } from '../../../styled'
import { ModalBackgroundContext } from '../../Modal/ModalBackgroundContext'
import { usePreventScroll } from './usePreventScroll'

export type PopoverProps = {
isOpen: boolean
Expand Down Expand Up @@ -40,11 +42,24 @@ export default function Popover(props: PopoverProps) {
}
)

const modalBackground = useContext(ModalBackgroundContext)
usePreventScroll(modalBackground, props.isOpen)

if (!props.isOpen) return null

return (
<Overlay portalContainer={document.body}>
<div {...underlayProps} style={{ position: 'fixed', inset: 0 }} />
<div
{...underlayProps}
style={{
position: 'fixed',
zIndex:
typeof popoverProps.style?.zIndex === 'number'
? popoverProps.style.zIndex - 1
: 99999,
inset: 0,
}}
/>
<DropdownPopoverDiv {...popoverProps} ref={finalPopoverRef}>
<DismissButton onDismiss={() => props.onClose()} />
{props.children}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useEffect } from 'react'

export function usePreventScroll(element: HTMLElement | null, isOpen: boolean) {
useEffect(() => {
if (isOpen && element) {
const defaultPaddingRight = element.style.paddingRight
const defaultOverflow = element.style.overflow
element.style.paddingRight = `${
window.innerWidth - element.clientWidth
}px`
element.style.overflow = 'hidden'
return () => {
element.style.paddingRight = defaultPaddingRight
element.style.overflow = defaultOverflow
}
}
}, [element, isOpen])
}
82 changes: 69 additions & 13 deletions packages/react/src/components/DropdownSelector/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { Story } from '../../_lib/compat'
import { Divider } from './Divider'
import MenuItemGroup from './MenuItemGroup'
import DropdownMenuItem from './DropdownMenuItem'
import Modal from '../Modal'
import { ModalBody, ModalHeader } from '../Modal/ModalPlumbing'
import styled from 'styled-components'
import Button from '../Button'

export default {
title: 'DropdownSelector',
Expand All @@ -26,6 +30,7 @@ export const Playground: Story<DropdownSelectorProps> = (
const [selected, setSelected] = useState('50')
return (
<div style={{ width: 288 }}>
<Button></Button>
<DropdownSelector
{...props}
onChange={(value) => {
Expand All @@ -51,27 +56,78 @@ Playground.args = { ...baseProps }
export const Basic: Story<DropdownSelectorProps> = (
props: DropdownSelectorProps
) => {
const [selected, setSelected] = useState('')
return (
<div style={{ width: 288 }}>
<DropdownSelector
{...props}
onChange={(value) => {
setSelected(value)
}}
value={selected}
label="label"
>
<DropdownMenuItem value={'10'}>Apple</DropdownMenuItem>
<DropdownMenuItem value={'20'}>Banana</DropdownMenuItem>
<DropdownMenuItem value={'30'}>Orange</DropdownMenuItem>
</DropdownSelector>
<PlaygroundDropdownSelector {...props} />
</div>
)
}

Basic.args = { ...baseProps }

function PlaygroundDropdownSelector(props: Partial<DropdownSelectorProps>) {
const [selected, setSelected] = useState('10')
return (
<DropdownSelector
{...props}
onChange={(value) => {
setSelected(value)
}}
value={selected}
label="label"
>
<DropdownMenuItem value={'10'}>Apple</DropdownMenuItem>
<DropdownMenuItem value={'20'}>Banana</DropdownMenuItem>
<DropdownMenuItem value={'30'}>Orange</DropdownMenuItem>
</DropdownSelector>
)
}

const DummyBox = styled.div`
border: solid 1px ${({ theme }) => theme.border.default.color};
display: flex;
justify-content: center;
align-items: center;
height: 256px;
`

export const InModal: Story<DropdownSelectorProps> = () => {
const [open, setOpen] = useState(true)
return (
<div
style={{
height: '10px',
}}
>
<button onClick={() => setOpen(true)}>open</button>
<Modal
bottomSheet="full"
title="modal"
isOpen={open}
isDismissable
onClose={() => {
setOpen(false)
}}
>
<ModalHeader />
<ModalBody>
<div
style={{
padding: '16px',
}}
>
<DummyBox>256px</DummyBox>
<PlaygroundDropdownSelector />
<DummyBox>256px</DummyBox>
<PlaygroundDropdownSelector />
<DummyBox>256px</DummyBox>
</div>
</ModalBody>
</Modal>
</div>
)
}

export const InFormTag: Story<DropdownSelectorProps> = (
props: DropdownSelectorProps
) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as React from 'react'

/**
* ModalBackgroundのElementが入ったコンテキスト
*/
export const ModalBackgroundContext = React.createContext<HTMLElement | null>(
null
)
15 changes: 15 additions & 0 deletions packages/react/src/components/Modal/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
import styled from 'styled-components'
import { theme } from '../../styled'
import TextField from '../TextField'
import DropdownSelector from '../DropdownSelector'
import DropdownMenuItem from '../DropdownSelector/DropdownMenuItem'

export default {
title: 'Modal',
Expand Down Expand Up @@ -75,6 +77,19 @@ const M = (props: ModalProps) => {
placeholder="Tokyo"
></TextField>
</ModalAlign>
<ModalAlign>
<DropdownSelector
onChange={() => null}
value="10"
showLabel
label="Fruits"
placeholder="Apple"
>
<DropdownMenuItem value={'10'}>Apple</DropdownMenuItem>
<DropdownMenuItem value={'20'}>Banana</DropdownMenuItem>
<DropdownMenuItem value={'30'}>Orange</DropdownMenuItem>
</DropdownSelector>
</ModalAlign>
</ModalVStack>
<ModalButtons>
<Button variant="Primary" onClick={() => props.onClose()} fullWidth>
Expand Down
74 changes: 40 additions & 34 deletions packages/react/src/components/Modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { animated, useTransition, easings } from 'react-spring'
import Button, { ButtonProps } from '../Button'
import IconButton from '../IconButton'
import { useObjectRef } from '@react-aria/utils'
import { ModalBackgroundContext } from './ModalBackgroundContext'

export type BottomSheet = boolean | 'full'
type Size = 'S' | 'M' | 'L'
Expand Down Expand Up @@ -131,50 +132,54 @@ const Modal = forwardRef<HTMLDivElement, ModalProps>(function ModalInner(
: { duration: 0 },
})

return transition(({ backgroundColor, transform, overflow }, item) => {
return (
const bgRef = React.useRef<HTMLElement>(null)

return transition(
({ backgroundColor, overflow, transform }, item) =>
item && (
<Overlay portalContainer={portalContainer}>
<ModalBackground
ref={bgRef}
zIndex={zIndex}
{...underlayProps}
style={transitionEnabled ? { backgroundColor, overflow } : {}}
$bottomSheet={bottomSheet}
>
<ModalDialog
ref={ref}
{...modalProps}
style={transitionEnabled ? { transform } : {}}
size={size}
bottomSheet={bottomSheet}
className={className}
>
<Dialog>
<ModalContext.Provider
value={{
titleProps: {},
title,
close: onClose,
showDismiss,
bottomSheet,
}}
>
{children}
{isDismissable === true && (
<ModalCrossButton
size="S"
icon="24/Close"
onClick={onClose}
/>
)}
</ModalContext.Provider>
</Dialog>
</ModalDialog>
<ModalBackgroundContext.Provider value={bgRef.current}>
<ModalDialog
ref={ref}
{...modalProps}
style={transitionEnabled ? { transform } : {}}
size={size}
bottomSheet={bottomSheet}
className={className}
>
<Dialog>
<ModalContext.Provider
value={{
titleProps: {},
title,
close: onClose,
showDismiss,
bottomSheet,
}}
>
{children}
{isDismissable === true && (
<ModalCrossButton
size="S"
icon="24/Close"
onClick={onClose}
/>
)}
</ModalContext.Provider>
</Dialog>
</ModalDialog>
</ModalBackgroundContext.Provider>
</ModalBackground>
</Overlay>
)
)
})
)
})

export default memo(Modal)
Expand Down Expand Up @@ -217,7 +222,8 @@ const ModalBackground = animated(styled.div<{
position: fixed;
top: 0;
left: 0;
width: 100%;
width: -webkit-fill-available;
width: -moz-available;
height: 100%;
justify-content: center;
padding: 40px 0;
Expand Down

0 comments on commit 4ceb37c

Please sign in to comment.