Skip to content

feat(s2): update accordion api to allow sibling elements in disclosure header #7179

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

Merged
merged 12 commits into from
Oct 17, 2024
63 changes: 48 additions & 15 deletions packages/@react-spectrum/s2/chromatic/Accordion.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
* governing permissions and limitations under the License.
*/

import {Accordion, Disclosure, DisclosureHeader, DisclosurePanel, TextField} from '../src';
import {Accordion, ActionButton, Disclosure, DisclosureHeader, DisclosurePanel, DisclosureTitle, TextField} from '../src';
import type {Meta, StoryObj} from '@storybook/react';
import NewIcon from '../s2wf-icons/S2_Icon_New_20_N.svg';
import React from 'react';
import {style} from '../style/spectrum-theme' with { type: 'macro' };

Expand All @@ -32,17 +33,17 @@ export const Example: Story = {
<div className={style({minHeight: 240})}>
<Accordion {...args}>
<Disclosure id="files">
<DisclosureHeader>
<DisclosureTitle>
Files
</DisclosureHeader>
</DisclosureTitle>
<DisclosurePanel>
Files content
</DisclosurePanel>
</Disclosure>
<Disclosure id="people">
<DisclosureHeader>
<DisclosureTitle>
People
</DisclosureHeader>
</DisclosureTitle>
<DisclosurePanel>
<TextField label="Name" styles={style({maxWidth: 176})} />
</DisclosurePanel>
Expand All @@ -59,25 +60,25 @@ export const WithLongTitle: Story = {
<div className={style({minHeight: 224})}>
<Accordion styles={style({maxWidth: 224})} {...args}>
<Disclosure>
<DisclosureHeader>
<DisclosureTitle>
Files
</DisclosureHeader>
</DisclosureTitle>
<DisclosurePanel>
Files content
</DisclosurePanel>
</Disclosure>
<Disclosure>
<DisclosureHeader>
<DisclosureTitle>
People
</DisclosureHeader>
</DisclosureTitle>
<DisclosurePanel>
People content
</DisclosurePanel>
</Disclosure>
<Disclosure>
<DisclosureHeader>
<DisclosureTitle>
Very very very very very long title that wraps
</DisclosureHeader>
</DisclosureTitle>
<DisclosurePanel>
Accordion content
</DisclosurePanel>
Expand All @@ -94,17 +95,17 @@ export const WithDisabledDisclosure: Story = {
<div className={style({minHeight: 240})}>
<Accordion {...args}>
<Disclosure>
<DisclosureHeader>
<DisclosureTitle>
Files
</DisclosureHeader>
</DisclosureTitle>
<DisclosurePanel>
Files content
</DisclosurePanel>
</Disclosure>
<Disclosure isDisabled>
<DisclosureHeader>
<DisclosureTitle>
People
</DisclosureHeader>
</DisclosureTitle>
<DisclosurePanel>
<TextField label="Name" />
</DisclosurePanel>
Expand All @@ -127,3 +128,35 @@ WithDisabledDisclosure.parameters = {
}
};

export const WithActionButton: Story = {
render: (args) => {
return (
<div className={style({minHeight: 240})}>
<Accordion {...args}>
<Disclosure id="files">
<DisclosureHeader>
<DisclosureTitle>
Files
</DisclosureTitle>
<ActionButton><NewIcon aria-label="new icon" /></ActionButton>
</DisclosureHeader>
<DisclosurePanel>
Files content
</DisclosurePanel>
</Disclosure>
<Disclosure id="people">
<DisclosureHeader>
<DisclosureTitle>
People
</DisclosureTitle>
<ActionButton><NewIcon aria-label="new icon" /></ActionButton>
</DisclosureHeader>
<DisclosurePanel>
<TextField label="Name" styles={style({maxWidth: 176})} />
</DisclosurePanel>
</Disclosure>
</Accordion>
</div>
);
}
};
31 changes: 26 additions & 5 deletions packages/@react-spectrum/s2/chromatic/Disclosure.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
* governing permissions and limitations under the License.
*/

import {Disclosure, DisclosureHeader, DisclosurePanel} from '../src';
import {ActionButton, Disclosure, DisclosureHeader, DisclosurePanel, DisclosureTitle} from '../src';
import type {Meta, StoryObj} from '@storybook/react';
import NewIcon from '../s2wf-icons/S2_Icon_New_20_N.svg';
import React from 'react';
import {style} from '../style/spectrum-theme' with { type: 'macro' };

Expand All @@ -31,9 +32,9 @@ export const Example: Story = {
return (
<div className={style({minHeight: 240})}>
<Disclosure {...args}>
<DisclosureHeader>
<DisclosureTitle>
Files
</DisclosureHeader>
</DisclosureTitle>
<DisclosurePanel>
Files content
</DisclosurePanel>
Expand All @@ -48,9 +49,9 @@ export const WithLongTitle: Story = {
return (
<div className={style({minHeight: 240})}>
<Disclosure styles={style({maxWidth: 224})} {...args}>
<DisclosureHeader>
<DisclosureTitle>
Very very very very very long title that wraps
</DisclosureHeader>
</DisclosureTitle>
<DisclosurePanel>
Content
</DisclosurePanel>
Expand All @@ -66,3 +67,23 @@ WithLongTitle.parameters = {
disable: true
}
};

export const WithActionButton: Story = {
render: (args) => {
return (
<div className={style({minHeight: 240})}>
<Disclosure {...args}>
<DisclosureHeader>
<DisclosureTitle>
Files
</DisclosureTitle>
<ActionButton><NewIcon aria-label="new icon " /></ActionButton>
</DisclosureHeader>
<DisclosurePanel>
Files content
</DisclosurePanel>
</Disclosure>
</div>
);
}
};
78 changes: 70 additions & 8 deletions packages/@react-spectrum/s2/src/Disclosure.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
* governing permissions and limitations under the License.
*/

import {AriaLabelingProps, DOMProps, DOMRef, DOMRefValue} from '@react-types/shared';
import {ActionButtonContext} from './ActionButton';
import {AriaLabelingProps, DOMProps, DOMRef, DOMRefValue, forwardRefType} from '@react-types/shared';
import {Button, ContextValue, DisclosureStateContext, Heading, Provider, UNSTABLE_Disclosure as RACDisclosure, UNSTABLE_DisclosurePanel as RACDisclosurePanel, DisclosurePanelProps as RACDisclosurePanelProps, DisclosureProps as RACDisclosureProps, useLocale, useSlottedContext} from 'react-aria-components';
import {CenterBaseline} from './CenterBaseline';
import {centerPadding, getAllowedOverrides, StyleProps, UnsafeStyles} from './style-utils' with { type: 'macro' };
Expand Down Expand Up @@ -93,7 +94,7 @@ function Disclosure(props: DisclosureProps, ref: DOMRef<HTMLDivElement>) {
let _Disclosure = forwardRef(Disclosure);
export {_Disclosure as Disclosure};

export interface DisclosureHeaderProps extends UnsafeStyles, DOMProps {
export interface DisclosureTitleProps extends UnsafeStyles, DOMProps {
/** The heading level of the disclosure header.
*
* @default 3
Expand All @@ -103,8 +104,13 @@ export interface DisclosureHeaderProps extends UnsafeStyles, DOMProps {
children: React.ReactNode
}

interface DisclosureHeaderProps extends UnsafeStyles, DOMProps {
children: React.ReactNode
}

const headingStyle = style({
margin: 0
margin: 0,
flexGrow: 1
});

const buttonStyles = style({
Expand Down Expand Up @@ -195,7 +201,52 @@ const chevronStyles = style({
flexShrink: 0
});

function DisclosureHeader(props: DisclosureHeaderProps, ref: DOMRef<HTMLDivElement>) {
const InternalDisclosureHeader = createContext<{} | null>(null);

function DisclosureHeaderWithForwardRef(props: DisclosureHeaderProps, ref: DOMRef<HTMLDivElement>) {
let {
UNSAFE_className,
UNSAFE_style,
children
} = props;
let domRef = useDOMRef(ref);
let {size, isQuiet, density} = useSlottedContext(DisclosureContext)!;

let mapSize = {
S: 'XS',
M: 'S',
L: 'M',
XL: 'L'
};

// maps to one size smaller in the compact density to ensure there is space between the top and bottom of the action button and container
let newSize : 'XS' | 'S' | 'M' | 'L' | 'XL' | undefined = size;
if (density === 'compact') {
newSize = mapSize[size ?? 'M'] as 'XS' | 'S' | 'M' | 'L';
}

return (
<Provider
values={[
[ActionButtonContext, {size: newSize, isQuiet}],
[InternalDisclosureHeader, {}]
]}>
<div
style={UNSAFE_style}
className={(UNSAFE_className ?? '') + style({display: 'flex', alignItems: 'center', gap: 4})}
ref={domRef}>
{children}
</div>
</Provider>
);
}

/**
* A wrapper element for the disclosure title that can contain other elements not part of the trigger.
*/
export const DisclosureHeader = /*#__PURE__*/ (forwardRef as forwardRefType)(DisclosureHeaderWithForwardRef);

function DisclosureTitle(props: DisclosureTitleProps, ref: DOMRef<HTMLDivElement>) {
let {
level = 3,
UNSAFE_style,
Expand All @@ -208,7 +259,8 @@ function DisclosureHeader(props: DisclosureHeaderProps, ref: DOMRef<HTMLDivEleme
let {isExpanded} = useContext(DisclosureStateContext)!;
let {size, density, isQuiet} = useSlottedContext(DisclosureContext)!;
let isRTL = direction === 'rtl';
return (

let buttonTrigger = (
<Heading
{...domProps}
level={level}
Expand All @@ -223,13 +275,23 @@ function DisclosureHeader(props: DisclosureHeaderProps, ref: DOMRef<HTMLDivEleme
</Button>
</Heading>
);
let ctx = useContext(InternalDisclosureHeader);
if (ctx) {
return buttonTrigger;
}

return (
<DisclosureHeader>
{buttonTrigger}
</DisclosureHeader>
);
}

/**
* A header for a disclosure. Contains a heading and a trigger button to expand/collapse the panel.
* A disclosure title consisting of a heading and a trigger button to expand/collapse the panel.
*/
let _DisclosureHeader = forwardRef(DisclosureHeader);
export {_DisclosureHeader as DisclosureHeader};
let _DisclosureTitle = forwardRef(DisclosureTitle);
export {_DisclosureTitle as DisclosureTitle};

export interface DisclosurePanelProps extends Omit<RACDisclosurePanelProps, 'className' | 'style' | 'children'>, UnsafeStyles, DOMProps, AriaLabelingProps {
children: React.ReactNode
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-spectrum/s2/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export {ColorSwatchPicker, ColorSwatchPickerContext} from './ColorSwatchPicker';
export {ColorWheel, ColorWheelContext} from './ColorWheel';
export {ComboBox, ComboBoxItem, ComboBoxSection, ComboBoxContext} from './ComboBox';
export {ContextualHelp, ContextualHelpContext} from './ContextualHelp';
export {DisclosureHeader, Disclosure, DisclosurePanel, DisclosureContext} from './Disclosure';
export {DisclosureHeader, Disclosure, DisclosurePanel, DisclosureContext, DisclosureTitle} from './Disclosure';
export {Heading, HeadingContext, Header, HeaderContext, Content, ContentContext, Footer, FooterContext, Text, TextContext, Keyboard, KeyboardContext} from './Content';
export {Dialog} from './Dialog';
export {DialogTrigger} from './DialogTrigger';
Expand Down
Loading