Skip to content

Commit

Permalink
chore: update
Browse files Browse the repository at this point in the history
  • Loading branch information
segunadebayo committed Jan 27, 2025
1 parent 5b3db91 commit 9570b8d
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 51 deletions.
2 changes: 1 addition & 1 deletion packages/react/.storybook/styles/tooltip.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
}

[data-scope='tooltip'][data-part='content'][data-state='closed'] {
animation: fadeOut 0.5s ease-in;
animation: fadeOut 0.2s ease-in;
}

[data-scope='tooltip'][data-part='arrow'] {
Expand Down
14 changes: 8 additions & 6 deletions packages/react/src/components/presence/use-presence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ export function usePresence(props: UsePresenceProps = {}): UsePresenceReturn {

// State
const state = useStateValue<PresenceState>(props.present ? 'mounted' : 'unmounted')
const [initial, setInitial] = React.useState(false)

// Context
const ctx = useStateValue({ initial: false })

// Actions
const setInitialFn = useEvent(() => setInitial(true))
const clearInitialFn = useEvent(() => setInitial(false))
const setInitial = useEvent(() => ctx.set({ initial: true }))
const clearInitial = useEvent(() => ctx.set({ initial: false }))
const cleanupNode = useEvent(() => {
refs.set({ node: null, styles: null })
})
Expand Down Expand Up @@ -158,15 +160,15 @@ export function usePresence(props: UsePresenceProps = {}): UsePresenceReturn {
useStateEffect(state, 'unmounted', clearPrevAnimationName)

// Context watchers
useUpdateEffect(callAll(setInitialFn, syncPresence), [props.present])
useUpdateEffect(callAll(setInitial, syncPresence), [props.present])

// Exit effects
useUnmount(callAll(clearInitialFn, cleanupNode))
useUnmount(callAll(clearInitial, cleanupNode))

const present = state.matches('mounted', 'unmountSuspended')

const api: PresenceApi = {
skip: !initial && present,
skip: !ctx.get('initial') && present,
present,
setNode(node: HTMLElement | null) {
if (!node) return
Expand Down
152 changes: 116 additions & 36 deletions packages/react/src/components/tooltip/use-tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,14 @@ import { addDomEvent, dataAttr, getOverflowAncestors, isComposingEvent } from '@
import { isFocusVisible, trackFocusVisible } from '@zag-js/focus-visible'
import type { Placement, PositioningOptions } from '@zag-js/popper'
import { getPlacement, getPlacementStyles } from '@zag-js/popper'
import { normalizeProps as normalize } from '@zag-js/react'
import { type PropTypes, normalizeProps as normalize } from '@zag-js/react'
import { callAll } from '../../utils/call-all'
import { useEvent } from '../../utils/use-event'
import { useSafeLayoutEffect } from '../../utils/use-safe-layout-effect'
import { useElementScope, useStateEffect, useStateValue } from '../../utils/use-state-value'
import { useUpdateEffect } from '../../utils/use-update-effect'
import { tooltipAnatomy } from './tooltip.anatomy'

export interface UseTooltipProps {
id?: string
openDelay?: number
closeDelay?: number
closeOnPointerDown?: boolean
closeOnEscape?: boolean
closeOnScroll?: boolean
closeOnClick?: boolean
interactive?: boolean
onOpenChange?(details: { open: boolean }): void
'aria-label'?: string
positioning?: PositioningOptions
disabled?: boolean
open?: boolean
defaultOpen?: boolean
ids?: {
trigger?: string
content?: string
arrow?: string
positioner?: string
}
}

type TooltipState = 'opening' | 'open' | 'closing' | 'closed'
type TooltipRefs = {
currentPlacement?: Placement
hasPointerMoveOpened: boolean
}
type TooltipEvent =
| { type: 'CONTROLLED.OPEN' | 'CONTROLLED.CLOSE'; previousEvent?: TooltipEvent | null }
| { type: 'POINTER_MOVE' | 'POINTER_LEAVE' | 'CONTENT.POINTER_MOVE' | 'CONTENT.POINTER_LEAVE' }
| { type: 'OPEN' | 'CLOSE'; src?: string }
| { type: 'POSITIONING.SET'; options?: Partial<PositioningOptions> }

const store = {
id: null as string | null,
prevId: null as string | null,
Expand Down Expand Up @@ -537,4 +503,118 @@ export function useTooltip(props: UseTooltipProps = {}) {
}
}

export type UseTooltipReturn = ReturnType<typeof useTooltip>
type TooltipState = 'opening' | 'open' | 'closing' | 'closed'
type TooltipRefs = {
currentPlacement?: Placement
hasPointerMoveOpened: boolean
}
type TooltipEvent =
| { type: 'CONTROLLED.OPEN' | 'CONTROLLED.CLOSE'; previousEvent?: TooltipEvent | null }
| { type: 'POINTER_MOVE' | 'POINTER_LEAVE' | 'CONTENT.POINTER_MOVE' | 'CONTENT.POINTER_LEAVE' }
| { type: 'OPEN' | 'CLOSE'; src?: string }
| { type: 'POSITIONING.SET'; options?: Partial<PositioningOptions> }

export interface OpenChangeDetails {
open: boolean
}

export type ElementIds = Partial<{
trigger: string
content: string
arrow: string
positioner: string
}>

export interface UseTooltipProps {
/**
* The `id` of the tooltip.
*/
id?: string
/**
* The open delay of the tooltip.
* @default 1000
*/
openDelay?: number
/**
* The close delay of the tooltip.
* @default 500
*/
closeDelay?: number
/**
* Whether to close the tooltip on pointerdown.
* @default true
*/
closeOnPointerDown?: boolean
/**
* Whether to close the tooltip when the Escape key is pressed.
* @default true
*/
closeOnEscape?: boolean
/**
* Whether the tooltip should close on scroll
* @default true
*/
closeOnScroll?: boolean
/**
* Whether the tooltip should close on click
* @default true
*/
closeOnClick?: boolean
/**
* Whether the tooltip's content is interactive.
* In this mode, the tooltip will remain open when user hovers over the content.
* @see https://www.w3.org/TR/WCAG21/#content-on-hover-or-focus
*
* @default false
*/
interactive?: boolean
/**
* Function called when the tooltip is opened.
*/
onOpenChange?(details: { open: boolean }): void
/**
* Custom label for the tooltip.
*/
'aria-label'?: string
/**
* The user provided options used to position the popover content
*/
positioning?: PositioningOptions
/**
* Whether the tooltip is disabled
*/
disabled?: boolean
/**
* Whether the tooltip is open
*/
open?: boolean
/**
* Whether the tooltip is open by default
*/
defaultOpen?: boolean
/**
* The ids of the elements in the tooltip. Useful for composition.
*/
ids?: ElementIds
}

export interface UseTooltipReturn {
/**
* Whether the tooltip is open.
*/
open: boolean
/**
* Function to open the tooltip.
*/
setOpen(open: boolean): void
/**
* Function to reposition the popover
*/
reposition(options?: Partial<PositioningOptions>): void

getTriggerProps(): PropTypes['button']
getArrowProps(): PropTypes['element']
getArrowTipProps(): PropTypes['element']
getPositionerProps(): PropTypes['element']
getContentProps(): PropTypes['element']
}
13 changes: 5 additions & 8 deletions packages/react/src/utils/use-state-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { getActiveElement, getDocument, getWindow } from '@zag-js/dom-query'
import { isFunction } from '@zag-js/utils'
import * as React from 'react'
import { useEnvironmentContext, useLocaleContext } from '../providers'
import { flush } from './flush'
import { useEvent } from './use-event'
import { useSafeLayoutEffect } from './use-safe-layout-effect'
import { useUpdateEffect } from './use-update-effect'
Expand All @@ -28,13 +27,11 @@ export function useStateValue<T>(value: T) {
},
set(value: Partial<T> | ((prev: T) => Partial<T>)) {
previousState.current = state
flush(() => {
setState((curr) => {
if (typeof value === 'object') {
return { ...curr, ...(isFunction(value) ? value(curr) : value) }
}
return typeof value === 'function' ? value(curr) : value
})
setState((curr) => {
if (typeof value === 'object') {
return { ...curr, ...(isFunction(value) ? value(curr) : value) }
}
return typeof value === 'function' ? value(curr) : value
})
},
matches(...values: T[]) {
Expand Down

0 comments on commit 9570b8d

Please sign in to comment.