|
| 1 | +/* |
| 2 | + * Copyright 2024 Adobe. All rights reserved. |
| 3 | + * This file is licensed to you under the Apache License, Version 2.0 (the 'License'); |
| 4 | + * you may not use this file except in compliance with the License. You may obtain a copy |
| 5 | + * of the License at http://www.apache.org/licenses/LICENSE-2.0 |
| 6 | + * |
| 7 | + * Unless required by applicable law or agreed to in writing, software distributed under |
| 8 | + * the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS |
| 9 | + * OF ANY KIND, either express or implied. See the License for the specific language |
| 10 | + * governing permissions and limitations under the License. |
| 11 | + */ |
| 12 | + |
| 13 | +import {AriaButtonProps} from '@react-types/button'; |
| 14 | +import {DisclosureState} from '@react-stately/disclosure'; |
| 15 | +import {HTMLAttributes, RefObject, useEffect} from 'react'; |
| 16 | +import {useEvent, useId} from '@react-aria/utils'; |
| 17 | +import {useIsSSR} from '@react-aria/ssr'; |
| 18 | + |
| 19 | +export interface AriaDisclosureProps { |
| 20 | + /** Whether the disclosure is disabled. */ |
| 21 | + isDisabled?: boolean, |
| 22 | + /** Handler that is called when the disclosure's expanded state changes. */ |
| 23 | + onExpandedChange?: (isExpanded: boolean) => void, |
| 24 | + /** Whether the disclosure is expanded (controlled). */ |
| 25 | + isExpanded?: boolean, |
| 26 | + /** Whether the disclosure is expanded by default (uncontrolled). */ |
| 27 | + defaultExpanded?: boolean |
| 28 | +} |
| 29 | + |
| 30 | +export interface DisclosureAria { |
| 31 | + /** Props for the disclosure button. */ |
| 32 | + buttonProps: AriaButtonProps, |
| 33 | + /** Props for the content element. */ |
| 34 | + contentProps: HTMLAttributes<HTMLElement> |
| 35 | +} |
| 36 | + |
| 37 | +/** |
| 38 | + * Provides the behavior and accessibility implementation for a disclosure component. |
| 39 | + * @param props - Props for the disclosure. |
| 40 | + * @param state - State for the disclosure, as returned by `useDisclosureState`. |
| 41 | + * @param ref - A ref for the disclosure content. |
| 42 | + */ |
| 43 | +export function useDisclosure(props: AriaDisclosureProps, state: DisclosureState, ref?: RefObject<Element | null>): DisclosureAria { |
| 44 | + let { |
| 45 | + isDisabled |
| 46 | + } = props; |
| 47 | + let triggerId = useId(); |
| 48 | + let contentId = useId(); |
| 49 | + let isControlled = props.isExpanded !== undefined; |
| 50 | + let isSSR = useIsSSR(); |
| 51 | + let supportsBeforeMatch = !isSSR && 'onbeforematch' in document.body; |
| 52 | + |
| 53 | + // @ts-ignore https://github.com/facebook/react/pull/24741 |
| 54 | + useEvent(ref, 'beforematch', supportsBeforeMatch ? () => state.expand() : null); |
| 55 | + |
| 56 | + useEffect(() => { |
| 57 | + // Until React supports hidden="until-found": https://github.com/facebook/react/pull/24741 |
| 58 | + if (supportsBeforeMatch && ref?.current && !isControlled && !isDisabled) { |
| 59 | + if (state.isExpanded) { |
| 60 | + // @ts-ignore |
| 61 | + ref.current.hidden = undefined; |
| 62 | + } else { |
| 63 | + // @ts-ignore |
| 64 | + ref.current.hidden = 'until-found'; |
| 65 | + } |
| 66 | + } |
| 67 | + }, [isControlled, ref, props.isExpanded, state, supportsBeforeMatch, isDisabled]); |
| 68 | + |
| 69 | + return { |
| 70 | + buttonProps: { |
| 71 | + id: triggerId, |
| 72 | + 'aria-expanded': state.isExpanded, |
| 73 | + 'aria-controls': contentId, |
| 74 | + onPress: (e) => { |
| 75 | + if (e.pointerType !== 'keyboard') { |
| 76 | + state.toggle(); |
| 77 | + } |
| 78 | + }, |
| 79 | + isDisabled, |
| 80 | + onKeyDown(e) { |
| 81 | + if (!isDisabled && (e.key === 'Enter' || e.key === ' ')) { |
| 82 | + e.preventDefault(); |
| 83 | + state.toggle(); |
| 84 | + } |
| 85 | + } |
| 86 | + }, |
| 87 | + contentProps: { |
| 88 | + id: contentId, |
| 89 | + 'aria-labelledby': triggerId, |
| 90 | + hidden: (!supportsBeforeMatch || isControlled) ? !state.isExpanded : true |
| 91 | + } |
| 92 | + }; |
| 93 | +} |
0 commit comments