Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DisclosureGroup component to RAC #7063

Merged
merged 1 commit into from
Sep 23, 2024
Merged

Add DisclosureGroup component to RAC #7063

merged 1 commit into from
Sep 23, 2024

Conversation

devongovett
Copy link
Member

@devongovett devongovett commented Sep 21, 2024

This adds a DisclosureGroup (aka Accordion) component to RAC. It allows a single Disclosure to be expanded by default, and an allowsMultipleExpanded prop to allow multiple. You can also control the expandedKeys similar to our other collection components. This brings parity with the existing Accordion alpha in S1.

(We discussed only supporting expandedKey, non-plural, but I thought of a use case for controlling multiple - e.g. if you have a multi-step process that starts all expanded and collapses each step after you completed it. This also matches the previous Accordion API in S1, and is more consistent with our other components. The downside is that it's slightly more annoying for single select usecases.)

Ideally there'd also be a disallowCollapseAll prop to force at least one to always be expanded but this was a bit more challenging to implement so I punted for now.

Adding some issues that should be closed by this and the previous accordion PR:

Fixes #1989, fixes #2801, fixes #3882, fixes #1891

TODO: verify #1726

@rspbot
Copy link

rspbot commented Sep 21, 2024

@rspbot
Copy link

rspbot commented Sep 21, 2024

## API Changes

react-aria-components

/react-aria-components:Disclosure

 Disclosure {
   children?: ReactNode | ((DisclosureRenderProps & {
     defaultChildren: ReactNode | undefined
 })) => ReactNode
   className?: string | ((DisclosureRenderProps & {
     defaultClassName: string | undefined
 })) => string
   defaultExpanded?: boolean
+  id?: Key
   isDisabled?: boolean
   isExpanded?: boolean
   onExpandedChange?: (boolean) => void
   onHoverChange?: (boolean) => void
   onHoverStart?: (HoverEvent) => void
   slot?: string | null
   style?: CSSProperties | ((DisclosureRenderProps & {
     defaultStyle: CSSProperties
 })) => CSSProperties | undefined
 }

/react-aria-components:DisclosureProps

 DisclosureProps {
   children?: ReactNode | ((DisclosureRenderProps & {
     defaultChildren: ReactNode | undefined
 })) => ReactNode
   className?: string | ((DisclosureRenderProps & {
     defaultClassName: string | undefined
 })) => string
   defaultExpanded?: boolean
+  id?: Key
   isDisabled?: boolean
   isExpanded?: boolean
   onExpandedChange?: (boolean) => void
   onHoverChange?: (boolean) => void
   onHoverStart?: (HoverEvent) => void
   slot?: string | null
   style?: CSSProperties | ((DisclosureRenderProps & {
     defaultStyle: CSSProperties
 })) => CSSProperties | undefined
 }

/react-aria-components:DisclosureGroup

+DisclosureGroup {
+  allowsMultipleExpanded?: boolean
+  children?: ReactNode | ((DisclosureGroupRenderProps & {
+    defaultChildren: ReactNode | undefined
+})) => ReactNode
+  className?: string | ((DisclosureGroupRenderProps & {
+    defaultClassName: string | undefined
+})) => string
+  defaultExpandedKeys?: Iterable<Key>
+  expandedKeys?: Iterable<Key>
+  id?: string
+  isDisabled?: boolean
+  onExpandedChange?: (Set<Key>) => any
+  style?: CSSProperties | ((DisclosureGroupRenderProps & {
+    defaultStyle: CSSProperties
+})) => CSSProperties | undefined
+}

/react-aria-components:DisclosureGroupStateContext

+DisclosureGroupStateContext {
+  UNTYPED
+}

/react-aria-components:DisclosureRenderProps

+DisclosureRenderProps {
+  isDisabled: boolean
+  isExpanded: boolean
+  isFocusVisibleWithin: boolean
+  state: DisclosureState
+}

/react-aria-components:DisclosureGroupProps

+DisclosureGroupProps {
+  allowsMultipleExpanded?: boolean
+  children?: ReactNode | ((DisclosureGroupRenderProps & {
+    defaultChildren: ReactNode | undefined
+})) => ReactNode
+  className?: string | ((DisclosureGroupRenderProps & {
+    defaultClassName: string | undefined
+})) => string
+  defaultExpandedKeys?: Iterable<Key>
+  expandedKeys?: Iterable<Key>
+  id?: string
+  isDisabled?: boolean
+  onExpandedChange?: (Set<Key>) => any
+  style?: CSSProperties | ((DisclosureGroupRenderProps & {
+    defaultStyle: CSSProperties
+})) => CSSProperties | undefined
+}

/react-aria-components:DisclosureGroupRenderProps

+DisclosureGroupRenderProps {
+  isDisabled: boolean
+  state: DisclosureGroupState
+}

@react-aria/disclosure

/@react-aria/disclosure:DisclosureAria

 DisclosureAria {
   buttonProps: AriaButtonProps
-  contentProps: HTMLAttributes<HTMLElement>
+  panelProps: HTMLAttributes<HTMLElement>
 }

@react-spectrum/accordion

/@react-spectrum/accordion:Disclosure

 Disclosure {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children: [ReactElement<SpectrumDisclosureHeaderProps>, ReactElement<SpectrumDisclosurePanelProps>]
   className?: string | ((DisclosureRenderProps & {
     defaultClassName: string | undefined
 })) => string
   defaultExpanded?: boolean
-  id?: string
+  id?: Key
   isDisabled?: boolean
   isExpanded?: boolean
   onExpandedChange?: (boolean) => void
   onHoverChange?: (boolean) => void
   onHoverStart?: (HoverEvent) => void
   slot?: string | null
   style?: CSSProperties | ((DisclosureRenderProps & {
     defaultStyle: CSSProperties
 })) => CSSProperties | undefined
 }

/@react-spectrum/accordion:SpectrumDisclosureProps

 SpectrumDisclosureProps {
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   children: [ReactElement<SpectrumDisclosureHeaderProps>, ReactElement<SpectrumDisclosurePanelProps>]
   className?: string | ((DisclosureRenderProps & {
     defaultClassName: string | undefined
 })) => string
   defaultExpanded?: boolean
-  id?: string
+  id?: Key
   isDisabled?: boolean
   isExpanded?: boolean
   onExpandedChange?: (boolean) => void
   onHoverChange?: (boolean) => void
   onHoverStart?: (HoverEvent) => void
   slot?: string | null
   style?: CSSProperties | ((DisclosureRenderProps & {
     defaultStyle: CSSProperties
 })) => CSSProperties | undefined
 }

@react-spectrum/s2

/@react-spectrum/s2:Accordion

 Accordion {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
+  allowsMultipleExpanded?: boolean
   children: React.ReactNode
+  className?: string | ((DisclosureGroupRenderProps & {
+    defaultClassName: string | undefined
+})) => string
+  defaultExpandedKeys?: Iterable<Key>
   density?: 'compact' | 'regular' | 'spacious' = "regular"
+  expandedKeys?: Iterable<Key>
   id?: string
   isDisabled?: boolean
   isQuiet?: boolean
+  onExpandedChange?: (Set<Key>) => any
   size?: 'S' | 'M' | 'L' | 'XL' = "M"
   slot?: string | null
+  style?: CSSProperties | ((DisclosureGroupRenderProps & {
+    defaultStyle: CSSProperties
+})) => CSSProperties | undefined
   styles?: StylesPropWithHeight
 }

/@react-spectrum/s2:Disclosure

 Disclosure {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
-  children: [ReactElement<DisclosureHeaderProps>, ReactElement<DisclosurePanelProps>]
+  children: ReactNode
   className?: string | ((DisclosureRenderProps & {
     defaultClassName: string | undefined
 })) => string
   defaultExpanded?: boolean
   density?: 'compact' | 'regular' | 'spacious' = "regular"
-  id?: string
+  id?: Key
   isDisabled?: boolean
   isExpanded?: boolean
   isQuiet?: boolean
   onExpandedChange?: (boolean) => void
   onHoverEnd?: (HoverEvent) => void
   onHoverStart?: (HoverEvent) => void
   size?: 'S' | 'M' | 'L' | 'XL' = "M"
   slot?: string | null
   style?: CSSProperties | ((DisclosureRenderProps & {
     defaultStyle: CSSProperties
 })) => CSSProperties | undefined
   styles?: StylesProp
 }

/@react-spectrum/s2:AccordionProps

 AccordionProps {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
+  allowsMultipleExpanded?: boolean
   children: React.ReactNode
+  className?: string | ((DisclosureGroupRenderProps & {
+    defaultClassName: string | undefined
+})) => string
+  defaultExpandedKeys?: Iterable<Key>
   density?: 'compact' | 'regular' | 'spacious' = "regular"
+  expandedKeys?: Iterable<Key>
   id?: string
   isDisabled?: boolean
   isQuiet?: boolean
+  onExpandedChange?: (Set<Key>) => any
   size?: 'S' | 'M' | 'L' | 'XL' = "M"
   slot?: string | null
+  style?: CSSProperties | ((DisclosureGroupRenderProps & {
+    defaultStyle: CSSProperties
+})) => CSSProperties | undefined
   styles?: StylesPropWithHeight
 }

/@react-spectrum/s2:DisclosureProps

 DisclosureProps {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
-  children: [ReactElement<DisclosureHeaderProps>, ReactElement<DisclosurePanelProps>]
+  children: ReactNode
   className?: string | ((DisclosureRenderProps & {
     defaultClassName: string | undefined
 })) => string
   defaultExpanded?: boolean
   density?: 'compact' | 'regular' | 'spacious' = "regular"
-  id?: string
+  id?: Key
   isDisabled?: boolean
   isExpanded?: boolean
   isQuiet?: boolean
   onExpandedChange?: (boolean) => void
   onHoverEnd?: (HoverEvent) => void
   onHoverStart?: (HoverEvent) => void
   size?: 'S' | 'M' | 'L' | 'XL' = "M"
   slot?: string | null
   style?: CSSProperties | ((DisclosureRenderProps & {
     defaultStyle: CSSProperties
 })) => CSSProperties | undefined
   styles?: StylesProp
 }

@react-stately/disclosure

/@react-stately/disclosure:useDisclosureGroupState

+useDisclosureGroupState {
+  props: DisclosureGroupProps
+  returnVal: undefined
+}

/@react-stately/disclosure:DisclosureGroupState

+DisclosureGroupState {
+  allowsMultipleExpanded: boolean
+  expandedKeys: Set<Key>
+  isDisabled: boolean
+  setExpandedKeys: (Set<Key>) => void
+  toggleKey: (Key) => void
+}

/@react-stately/disclosure:DisclosureGroupProps

+DisclosureGroupProps {
+  allowsMultipleExpanded?: boolean
+  defaultExpandedKeys?: Iterable<Key>
+  expandedKeys?: Iterable<Key>
+  isDisabled?: boolean
+  onExpandedChange?: (Set<Key>) => any
+}

import React, {createContext, DOMAttributes, ForwardedRef, forwardRef, ReactNode, useContext} from 'react';

export interface DisclosureProps extends Omit<AriaDisclosureProps, 'children'>, HoverEvents, RenderProps<DisclosureRenderProps>, SlotProps {}
export interface DisclosureGroupProps extends StatelyDisclosureGroupProps, RenderProps<DisclosureGroupRenderProps>, DOMProps {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want AriaLabelingProps here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe? DisclosureGroup doesn't have a role though, so not sure if that's allowed.

Copy link
Member

@yihuiliao yihuiliao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approving for testing but looks good

@devongovett devongovett merged commit e87ee28 into main Sep 23, 2024
29 checks passed
@devongovett devongovett deleted the disclosure-group branch September 23, 2024 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants