Skip to content

Commit

Permalink
feat(s2): update accordion api to allow sibling elements in disclosur…
Browse files Browse the repository at this point in the history
…e header (#7179)

* update s2 accordion api

* fix lint

* rename to disclosure title, context for action button

* fix lint

* remove console log

* fix typscript

* allow disclosure title to wrap header

* add chromatic stories

* make code more concise

* fix lint

* fix chromatic stories

---------

Co-authored-by: Robert Snow <[email protected]>
  • Loading branch information
yihuiliao and snowystinger authored Oct 17, 2024
1 parent e1b72a7 commit b958085
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 49 deletions.
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

1 comment on commit b958085

@rspbot
Copy link

@rspbot rspbot commented on b958085 Oct 17, 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.