Skip to content

Commit

Permalink
Improve internal demo mode (#3143)
Browse files Browse the repository at this point in the history
* improve demo mode for the `Dialog` component

This still disabled `inert` and focus stealing code. But it does allow
outside click.

* improve demo mode for the `Combobox` component

Before this, once you start interacting with the `Combobox`, the options
weren't properly scrolled into view.

* improve demo mode for the `Menu` component

* improve demo mode for the `Popover` component

* add demo mode to the `Listbox` component
  • Loading branch information
RobinMalfait authored Apr 26, 2024
1 parent 26e1644 commit 539c124
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 33 deletions.
34 changes: 22 additions & 12 deletions packages/@headlessui-react/src/components/combobox/combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ interface StateDefinition<T> {
options: { id: string; dataRef: ComboboxOptionDataRef<T> }[]
activeOptionIndex: number | null
activationTrigger: ActivationTrigger

__demoMode: boolean
}

enum ActionTypes {
Expand Down Expand Up @@ -190,7 +192,12 @@ let reducers: {
if (state.dataRef.current?.disabled) return state
if (state.comboboxState === ComboboxState.Closed) return state

return { ...state, activeOptionIndex: null, comboboxState: ComboboxState.Closed }
return {
...state,
activeOptionIndex: null,
comboboxState: ComboboxState.Closed,
__demoMode: false,
}
},
[ActionTypes.OpenCombobox](state) {
if (state.dataRef.current?.disabled) return state
Expand All @@ -204,11 +211,12 @@ let reducers: {
...state,
activeOptionIndex: idx,
comboboxState: ComboboxState.Open,
__demoMode: false,
}
}
}

return { ...state, comboboxState: ComboboxState.Open }
return { ...state, comboboxState: ComboboxState.Open, __demoMode: false }
},
[ActionTypes.GoToOption](state, action) {
if (state.dataRef.current?.disabled) return state
Expand Down Expand Up @@ -249,6 +257,7 @@ let reducers: {
...state,
activeOptionIndex,
activationTrigger,
__demoMode: false,
}
}

Expand Down Expand Up @@ -290,6 +299,7 @@ let reducers: {
...adjustedState,
activeOptionIndex,
activationTrigger,
__demoMode: false,
}
},
[ActionTypes.RegisterOption]: (state, action) => {
Expand Down Expand Up @@ -635,6 +645,7 @@ function ComboboxFn<TValue, TTag extends ElementType = typeof DEFAULT_COMBOBOX_T
: null,
activeOptionIndex: null,
activationTrigger: ActivationTrigger.Other,
__demoMode,
} as StateDefinition<TValue>)

let defaultToFirstOption = useRef(false)
Expand Down Expand Up @@ -1583,7 +1594,10 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
useOnDisappear(data.inputRef, actions.closeCombobox, visible)

// Enable scroll locking when the combobox is visible, and `modal` is enabled
useScrollLock(ownerDocument, modal && data.comboboxState === ComboboxState.Open)
useScrollLock(
ownerDocument,
data.__demoMode ? false : modal && data.comboboxState === ComboboxState.Open
)

// Mark other elements as inert when the combobox is visible, and `modal` is enabled
useInertOthers(
Expand All @@ -1594,7 +1608,7 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
data.optionsRef.current,
]),
},
modal && data.comboboxState === ComboboxState.Open
data.__demoMode ? false : modal && data.comboboxState === ComboboxState.Open
)

useIsoMorphicEffect(() => {
Expand Down Expand Up @@ -1753,25 +1767,21 @@ function OptionFn<

let enableScrollIntoView = useRef(data.virtual || data.__demoMode ? false : true)
useIsoMorphicEffect(() => {
if (!data.virtual) return
if (!data.__demoMode) return
let d = disposables()
d.requestAnimationFrame(() => {
if (data.virtual) return
if (data.__demoMode) return
return disposables().requestAnimationFrame(() => {
enableScrollIntoView.current = true
})
return d.dispose
}, [data.virtual, data.__demoMode])

useIsoMorphicEffect(() => {
if (!enableScrollIntoView.current) return
if (data.comboboxState !== ComboboxState.Open) return
if (!active) return
if (data.activationTrigger === ActivationTrigger.Pointer) return
let d = disposables()
d.requestAnimationFrame(() => {
return disposables().requestAnimationFrame(() => {
internalOptionRef.current?.scrollIntoView?.({ block: 'nearest' })
})
return d.dispose
}, [
internalOptionRef,
active,
Expand Down
10 changes: 7 additions & 3 deletions packages/@headlessui-react/src/components/dialog/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ function DialogFn<TTag extends ElementType = typeof DEFAULT_DIALOG_TAG>(
let setTitleId = useEvent((id: string | null) => dispatch({ type: ActionTypes.SetTitleId, id }))

let ready = useServerHandoffComplete()
let enabled = ready ? (__demoMode ? false : dialogState === DialogStates.Open) : false
let enabled = ready ? dialogState === DialogStates.Open : false
let hasNestedDialogs = nestedDialogCount > 1 // 1 is the current dialog
let hasParentDialog = useContext(DialogContext) !== null
let [portals, PortalWrapper] = useNestedPortals()
Expand Down Expand Up @@ -281,7 +281,7 @@ function DialogFn<TTag extends ElementType = typeof DEFAULT_DIALOG_TAG>(
null,
]),
},
inertEnabled
__demoMode ? false : inertEnabled
)

// Close Dialog on outside click
Expand Down Expand Up @@ -321,7 +321,7 @@ function DialogFn<TTag extends ElementType = typeof DEFAULT_DIALOG_TAG>(
if (hasParentDialog) return false
return true
})()
useScrollLock(ownerDocument, scrollLockEnabled, resolveRootContainers)
useScrollLock(ownerDocument, __demoMode ? false : scrollLockEnabled, resolveRootContainers)

// Ensure we close the dialog as soon as the dialog itself becomes hidden
useOnDisappear(internalDialogRef, close, dialogState === DialogStates.Open)
Expand Down Expand Up @@ -367,6 +367,10 @@ function DialogFn<TTag extends ElementType = typeof DEFAULT_DIALOG_TAG>(
focusTrapFeatures &= ~FocusTrapFeatures.InitialFocus
}

if (__demoMode) {
focusTrapFeatures = FocusTrapFeatures.None
}

return (
<StackProvider
type="Dialog"
Expand Down
27 changes: 22 additions & 5 deletions packages/@headlessui-react/src/components/listbox/listbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ interface StateDefinition<T> {
searchQuery: string
activeOptionIndex: number | null
activationTrigger: ActivationTrigger

__demoMode: boolean
}

enum ActionTypes {
Expand Down Expand Up @@ -172,7 +174,12 @@ let reducers: {
[ActionTypes.CloseListbox](state) {
if (state.dataRef.current.disabled) return state
if (state.listboxState === ListboxStates.Closed) return state
return { ...state, activeOptionIndex: null, listboxState: ListboxStates.Closed }
return {
...state,
activeOptionIndex: null,
listboxState: ListboxStates.Closed,
__demoMode: false,
}
},
[ActionTypes.OpenListbox](state) {
if (state.dataRef.current.disabled) return state
Expand All @@ -187,7 +194,7 @@ let reducers: {
activeOptionIndex = optionIdx
}

return { ...state, listboxState: ListboxStates.Open, activeOptionIndex }
return { ...state, listboxState: ListboxStates.Open, activeOptionIndex, __demoMode: false }
},
[ActionTypes.GoToOption](state, action) {
if (state.dataRef.current.disabled) return state
Expand All @@ -197,6 +204,7 @@ let reducers: {
...state,
searchQuery: '',
activationTrigger: action.trigger ?? ActivationTrigger.Other,
__demoMode: false,
}

// Optimization:
Expand Down Expand Up @@ -460,6 +468,8 @@ export type ListboxProps<
form?: string
name?: string
multiple?: boolean

__demoMode?: boolean
}
>

Expand All @@ -480,6 +490,7 @@ function ListboxFn<
disabled = providedDisabled || false,
horizontal = false,
multiple = false,
__demoMode = false,
...theirProps
} = props
const orientation = horizontal ? 'horizontal' : 'vertical'
Expand All @@ -493,12 +504,13 @@ function ListboxFn<

let [state, dispatch] = useReducer(stateReducer, {
dataRef: createRef(),
listboxState: ListboxStates.Closed,
listboxState: __demoMode ? ListboxStates.Open : ListboxStates.Closed,
options: [],
searchQuery: '',
activeOptionIndex: null,
activationTrigger: ActivationTrigger.Other,
optionsVisible: false,
__demoMode,
} as StateDefinition<TType>)

let optionsPropsRef = useRef<_Data['optionsPropsRef']['current']>({ static: false, hold: false })
Expand Down Expand Up @@ -914,12 +926,15 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
useOnDisappear(data.buttonRef, actions.closeListbox, visible)

// Enable scroll locking when the listbox is visible, and `modal` is enabled
useScrollLock(ownerDocument, modal && data.listboxState === ListboxStates.Open)
useScrollLock(
ownerDocument,
data.__demoMode ? false : modal && data.listboxState === ListboxStates.Open
)

// Mark other elements as inert when the listbox is visible, and `modal` is enabled
useInertOthers(
{ allowed: useEvent(() => [data.buttonRef.current, data.optionsRef.current]) },
modal && data.listboxState === ListboxStates.Open
data.__demoMode ? false : modal && data.listboxState === ListboxStates.Open
)

let initialOption = useRef<number | null>(null)
Expand Down Expand Up @@ -1181,6 +1196,7 @@ function OptionFn<
})

useIsoMorphicEffect(() => {
if (data.__demoMode) return
if (data.listboxState !== ListboxStates.Open) return
if (!active) return
if (data.activationTrigger === ActivationTrigger.Pointer) return
Expand All @@ -1192,6 +1208,7 @@ function OptionFn<
}, [
internalOptionRef,
active,
data.__demoMode,
data.listboxState,
data.activationTrigger,
/* We also want to trigger this when the position of the active item changes so that we can re-trigger the scrollIntoView */ data.activeOptionIndex,
Expand Down
8 changes: 6 additions & 2 deletions packages/@headlessui-react/src/components/menu/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ let reducers: {
...state,
searchQuery: '',
activationTrigger: action.trigger ?? ActivationTrigger.Other,
__demoMode: false,
}

// Optimization:
Expand Down Expand Up @@ -626,12 +627,15 @@ function ItemsFn<TTag extends ElementType = typeof DEFAULT_ITEMS_TAG>(
useOnDisappear(state.buttonRef, () => dispatch({ type: ActionTypes.CloseMenu }), visible)

// Enable scroll locking when the menu is visible, and `modal` is enabled
useScrollLock(ownerDocument, modal && state.menuState === MenuStates.Open)
useScrollLock(
ownerDocument,
state.__demoMode ? false : modal && state.menuState === MenuStates.Open
)

// Mark other elements as inert when the menu is visible, and `modal` is enabled
useInertOthers(
{ allowed: useEvent(() => [state.buttonRef.current, state.itemsRef.current]) },
modal && state.menuState === MenuStates.Open
state.__demoMode ? false : modal && state.menuState === MenuStates.Open
)

// We keep track whether the button moved or not, we only check this when the menu state becomes
Expand Down
17 changes: 6 additions & 11 deletions packages/@headlessui-react/src/components/popover/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ enum PopoverStates {
}

interface StateDefinition {
__demoMode: boolean
popoverState: PopoverStates

buttons: MutableRefObject<Symbol[]>
Expand All @@ -93,6 +92,8 @@ interface StateDefinition {

beforePanelSentinel: MutableRefObject<HTMLButtonElement | null>
afterPanelSentinel: MutableRefObject<HTMLButtonElement | null>

__demoMode: boolean
}

enum ActionTypes {
Expand Down Expand Up @@ -120,24 +121,18 @@ let reducers: {
) => StateDefinition
} = {
[ActionTypes.TogglePopover]: (state) => {
let nextState = {
return {
...state,
popoverState: match(state.popoverState, {
[PopoverStates.Open]: PopoverStates.Closed,
[PopoverStates.Closed]: PopoverStates.Open,
}),
__demoMode: false,
}

/* We can turn off demo mode once we re-open the `Popover` */
if (nextState.popoverState === PopoverStates.Open) {
nextState.__demoMode = false
}

return nextState
},
[ActionTypes.ClosePopover](state) {
if (state.popoverState === PopoverStates.Closed) return state
return { ...state, popoverState: PopoverStates.Closed }
return { ...state, popoverState: PopoverStates.Closed, __demoMode: false }
},
[ActionTypes.SetButton](state, action) {
if (state.button === action.button) return state
Expand Down Expand Up @@ -863,7 +858,7 @@ function PanelFn<TTag extends ElementType = typeof DEFAULT_PANEL_TAG>(
useOnDisappear(state.button, () => dispatch({ type: ActionTypes.ClosePopover }), visible)

// Enable scroll locking when the popover is visible, and `modal` is enabled
useScrollLock(ownerDocument, modal && visible)
useScrollLock(ownerDocument, state.__demoMode ? false : modal && visible)

let handleKeyDown = useEvent((event: ReactKeyboardEvent<HTMLButtonElement>) => {
switch (event.key) {
Expand Down

0 comments on commit 539c124

Please sign in to comment.