Skip to content

Commit

Permalink
Accordion (#6931)
Browse files Browse the repository at this point in the history
* initialize accordion item

* update yarn.lock

* better hidden=until-found support

* allow passing in panel ref

* comments

* use RAC in v3 Accordion

* use disclosure hooks

* initialize S2 Accordion

* fix lint

* add exports

* lint

* fix chevron color in dark mode

* fix version of @react-stately/accordion

* keep aria-controls even when closed

* add isFocusVisibleWithin to AccordionPanel

* fix panel height

* fix versions

* add to ts strict config

* change open/close to expand/collapse

* move to disclosure package

* fix chevron shrinking on wrapping header text

* support for disabled in S2

* clear ButtonContext in panel

* update colors

* lint

* fix v3 chevron not rotating

* don't open via onbeforematch if disabled

* add disableTapHighlight

* open disclosure onKeyDown

* add getAllowedOverrides

* remove outer header component

* support level in S2 header

* support level in v3 header

* switch divider to use border

* simplify focus ring and padding styles

* update AccordionItem children types to enforce two React elements

* remove Header from RAC example

* scale font size

* update yarn.lock

* fix keydown interaction + types

* support size, density, isQuiet, and isDisabled on individual items, but use group prop if available

* enforce minWidth at item level

* add isFocusVisibleWithin to item

* fix chevron in RTL

* revert changes to @react-aria/accordion

* fix v3 chevron in RTL

* fix packages/imports

* use 'group' as default role

* deprecate useAccordion and useAccordionItem

* update prop JSDocs

* update yarn.lock

* fix v3 refs

* add v3 tests back

* update imports

* remove @ts-ignore

* Revert "remove @ts-ignore"

This reverts commit 88c1604.

* fix stories

* fix context for individual accordion item

* fix defaults for individual item

* add story for individual item

* make paddingTop and paddingBottom equal

* fix story

* change triggerProps to buttonProps

* add optional ref to useDisclosure

* add SSR check

* use useEvent to add beforematch listener

* rename AccordionGroup to Accordion

* rename Disclosure to AccordionItem

* rename AccordionPanel to DisclosurePanel

* more renaming

* rename RAC Accordion file to Disclosure

* comment updates

* package.json updates

* yarn.lock

* update render props

* add DEFAULT_SLOT

* use fontRelative for border radius

* style macro updates

* lint

* update codemod

* fix tests

* use control for borderRadius

* use ref instead of contentRef prop

* use values on grid

* lint

* yarn.lock

* fix refs

* lint

* revert renames in useAccordion

* move Disclosure  to its own file

* reanme RAC Accordion stories to Disclosure

* fix styles prop type

* fix imports

* update styles type on Disclosure

* add dedicated story for S2 Disclosure

* Slight fixes

* Center baseline and simplify padding by changing the minHeight instead

It uses more normal numbers

* fix import

* update lockfile

---------

Co-authored-by: Devon Govett <devongovett@gmail.com>
  • Loading branch information
reidbarber and devongovett authored Sep 20, 2024
1 parent 6193c40 commit 9421c14
Show file tree
Hide file tree
Showing 34 changed files with 1,328 additions and 212 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ governing permissions and limitations under the License.
.spectrum-Accordion-itemIndicator {
display: block;

padding-inline-start: var(--spectrum-accordion-icon-spacing);
padding-inline-start: var(--spectrum-accordion-icon-gap);
padding-inline-end: var(--spectrum-accordion-icon-gap);

transition: transform ease var(--spectrum-global-animation-duration-100);
Expand Down Expand Up @@ -66,7 +66,7 @@ governing permissions and limitations under the License.
box-sizing: border-box;
/* left padding takes into account the icon's size as well as the focus state's left border */
padding-block: var(--spectrum-accordion-item-title-padding-y);
padding-inline-start: 2px;
padding-inline-start: var(--spectrum-accordion-icon-gap);
padding-inline-end: var(--spectrum-accordion-item-padding);
margin: 0;

Expand Down Expand Up @@ -111,7 +111,7 @@ governing permissions and limitations under the License.
}

.spectrum-Accordion-item {
&.is-open {
&.is-expanded {
> .spectrum-Accordion-itemHeading {
> .spectrum-Accordion-itemHeader {
> .spectrum-Accordion-itemIndicator {
Expand All @@ -126,7 +126,7 @@ governing permissions and limitations under the License.
}

> .spectrum-Accordion-itemHeader::after {
/* No bottom border when open, so be less tall */
/* No bottom border when expanded, so be less tall */
height: var(--spectrum-accordion-item-height-actual);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ governing permissions and limitations under the License.
}

.spectrum-Accordion-item {
&.is-open {
&.is-expanded {
.spectrum-Accordion-itemHeader {
&:hover {
background-color: transparent;
Expand Down
6 changes: 6 additions & 0 deletions packages/@react-aria/accordion/src/useAccordion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export interface AccordionItemAria {
regionProps: DOMAttributes
}

/**
* @deprecated Use useDisclosure from `@react-aria/disclosure` instead.
*/
export function useAccordionItem<T>(props: AccordionItemAriaProps<T>, state: TreeState<T>, ref: RefObject<HTMLButtonElement | null>): AccordionItemAria {
let {item} = props;
let buttonId = useId();
Expand Down Expand Up @@ -65,6 +68,9 @@ export function useAccordionItem<T>(props: AccordionItemAriaProps<T>, state: Tre
};
}

/**
* @deprecated Use useDisclosure from `@react-aria/disclosure` instead.
*/
export function useAccordion<T>(props: AriaAccordionProps<T>, state: TreeState<T>, ref: RefObject<HTMLDivElement | null>): AccordionAria {
let {listProps} = useSelectableList({
...props,
Expand Down
3 changes: 3 additions & 0 deletions packages/@react-aria/disclosure/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @react-aria/disclosure

This package is part of [react-spectrum](https://github.com/adobe/react-spectrum). See the repo for more details.
13 changes: 13 additions & 0 deletions packages/@react-aria/disclosure/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

export * from './src';
43 changes: 43 additions & 0 deletions packages/@react-aria/disclosure/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@react-aria/disclosure",
"version": "3.0.0-alpha.0",
"description": "Spectrum UI components in React",
"license": "Apache-2.0",
"main": "dist/main.js",
"module": "dist/module.js",
"exports": {
"types": "./dist/types.d.ts",
"import": "./dist/import.mjs",
"require": "./dist/main.js"
},
"types": "dist/types.d.ts",
"source": "src/index.ts",
"files": [
"dist",
"src"
],
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/adobe/react-spectrum"
},
"dependencies": {
"@react-aria/button": "^3.9.8",
"@react-aria/selection": "^3.19.3",
"@react-aria/ssr": "^3.9.5",
"@react-aria/utils": "^3.25.2",
"@react-stately/disclosure": "3.0.0-alpha.0",
"@react-stately/toggle": "^3.7.7",
"@react-stately/tree": "^3.8.4",
"@react-types/button": "^3.9.6",
"@react-types/shared": "^3.24.1",
"@swc/helpers": "^0.5.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"
},
"publishConfig": {
"access": "public"
}
}
13 changes: 13 additions & 0 deletions packages/@react-aria/disclosure/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright 2020 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
export {useDisclosure} from './useDisclosure';
export type {DisclosureAria, AriaDisclosureProps} from './useDisclosure';
93 changes: 93 additions & 0 deletions packages/@react-aria/disclosure/src/useDisclosure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import {AriaButtonProps} from '@react-types/button';
import {DisclosureState} from '@react-stately/disclosure';
import {HTMLAttributes, RefObject, useEffect} from 'react';
import {useEvent, useId} from '@react-aria/utils';
import {useIsSSR} from '@react-aria/ssr';

export interface AriaDisclosureProps {
/** Whether the disclosure is disabled. */
isDisabled?: boolean,
/** Handler that is called when the disclosure's expanded state changes. */
onExpandedChange?: (isExpanded: boolean) => void,
/** Whether the disclosure is expanded (controlled). */
isExpanded?: boolean,
/** Whether the disclosure is expanded by default (uncontrolled). */
defaultExpanded?: boolean
}

export interface DisclosureAria {
/** Props for the disclosure button. */
buttonProps: AriaButtonProps,
/** Props for the content element. */
contentProps: HTMLAttributes<HTMLElement>
}

/**
* Provides the behavior and accessibility implementation for a disclosure component.
* @param props - Props for the disclosure.
* @param state - State for the disclosure, as returned by `useDisclosureState`.
* @param ref - A ref for the disclosure content.
*/
export function useDisclosure(props: AriaDisclosureProps, state: DisclosureState, ref?: RefObject<Element | null>): DisclosureAria {
let {
isDisabled
} = props;
let triggerId = useId();
let contentId = useId();
let isControlled = props.isExpanded !== undefined;
let isSSR = useIsSSR();
let supportsBeforeMatch = !isSSR && 'onbeforematch' in document.body;

// @ts-ignore https://github.com/facebook/react/pull/24741
useEvent(ref, 'beforematch', supportsBeforeMatch ? () => state.expand() : null);

useEffect(() => {
// Until React supports hidden="until-found": https://github.com/facebook/react/pull/24741
if (supportsBeforeMatch && ref?.current && !isControlled && !isDisabled) {
if (state.isExpanded) {
// @ts-ignore
ref.current.hidden = undefined;
} else {
// @ts-ignore
ref.current.hidden = 'until-found';
}
}
}, [isControlled, ref, props.isExpanded, state, supportsBeforeMatch, isDisabled]);

return {
buttonProps: {
id: triggerId,
'aria-expanded': state.isExpanded,
'aria-controls': contentId,
onPress: (e) => {
if (e.pointerType !== 'keyboard') {
state.toggle();
}
},
isDisabled,
onKeyDown(e) {
if (!isDisabled && (e.key === 'Enter' || e.key === ' ')) {
e.preventDefault();
state.toggle();
}
}
},
contentProps: {
id: contentId,
'aria-labelledby': triggerId,
hidden: (!supportsBeforeMatch || isControlled) ? !state.isExpanded : true
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,15 @@
*/

import {Meta} from '@storybook/react';
import {SpectrumAccordionProps} from '@react-types/accordion';
import {SpectrumAccordionProps} from '../src/Accordion';
import {Template} from '../chromatic/Accordion.stories';

const meta: Meta<SpectrumAccordionProps<object>> = {
const meta: Meta<SpectrumAccordionProps> = {
title: 'Accordion'
};

export default meta;

export const Default = {
render: Template,
args: {defaultExpandedKeys: ['shared'], disabledKeys: ['last']}
render: Template
};
50 changes: 28 additions & 22 deletions packages/@react-spectrum/accordion/chromatic/Accordion.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,49 @@
* governing permissions and limitations under the License.
*/

import {Accordion, Item} from '../';
import {Accordion, Disclosure, DisclosureHeader, DisclosurePanel} from '../';
import {Meta} from '@storybook/react';
import React from 'react';
import {SpectrumAccordionProps} from '@react-types/accordion';

const meta: Meta<SpectrumAccordionProps<object>> = {
const meta: Meta = {
title: 'Accordion',
component: Accordion,
component: Disclosure,
excludeStories: ['Template']
};

export default meta;

export const Template = (args) => (
<Accordion {...args}>
<Item key="files" title="Your files">
files
</Item>
<Item key="shared" title="Shared with you">
shared
</Item>
<Item key="last" title="Last item">
last
</Item>
<Disclosure key="files">
<DisclosureHeader>
Your files
</DisclosureHeader>
<DisclosurePanel>
files
</DisclosurePanel>
</Disclosure>
<Disclosure key="shared">
<DisclosureHeader>
Shared with you
</DisclosureHeader>
<DisclosurePanel>
shared
</DisclosurePanel>
</Disclosure>
<Disclosure key="last">
<DisclosureHeader>
Last item
</DisclosureHeader>
<DisclosurePanel>
last
</DisclosurePanel>
</Disclosure>
</Accordion>
);

export const Default = {
render: Template
};

export const ExpandedKeys = {
render: Template,
args: {defaultExpandedKeys: ['shared']}
};

export const DisabledKeys = {
render: Template,
args: {disabledKeys: ['shared']}
};
// TODO: more stories
4 changes: 2 additions & 2 deletions packages/@react-spectrum/accordion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@
"@react-spectrum/utils": "^3.11.9",
"@react-stately/collections": "^3.10.9",
"@react-stately/tree": "^3.8.3",
"@react-types/accordion": "3.0.0-alpha.23",
"@react-types/shared": "^3.24.1",
"@spectrum-icons/ui": "^3.6.9",
"@swc/helpers": "^0.5.0"
"@swc/helpers": "^0.5.0",
"react-aria-components": "^1.3.3"
},
"devDependencies": {
"@adobe/spectrum-css-temp": "3.0.0-alpha.1"
Expand Down
Loading

1 comment on commit 9421c14

@rspbot
Copy link

@rspbot rspbot commented on 9421c14 Sep 20, 2024

Choose a reason for hiding this comment

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

Please sign in to comment.