Skip to content

Commit

Permalink
Fix: [IGL-100] Disclosure isExpanded issues (#584)
Browse files Browse the repository at this point in the history
* Fix: [IGL-100] Disclosure isExpanded issues

---------

Co-authored-by: Franck Gaudin <franck.gaudin@workleap.com>
  • Loading branch information
vicky-comeau and Franck Gaudin authored Dec 5, 2023
1 parent d814f31 commit c981a8b
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 119 deletions.
12 changes: 12 additions & 0 deletions .changeset/fuzzy-rockets-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@igloo-ui/disclosure": major
---

Separated the controlled and uncontrolled expanded props.

## BREAKING CHANGE

The defaultExpanded prop was added, which means isExpanded may need to be exchanged for this new prop.
If you use isExpanded to make the disclosure expanded by default, rename it to defaultExpanded.
If you use isExpanded to open/close the disclosure programmatically, keep isExpanded.
Rule of thumb: If isExpanded was used without onOpen and onClose, it most likely has to be renamed to defaultExpanded.
3 changes: 2 additions & 1 deletion packages/Disclosure/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"@hopper-ui/tokens": "^3.1.0",
"classnames": "^2.3.2",
"framer-motion": "^6.5.1",
"react-aria": "^3.30.0"
"react-aria": "^3.30.0",
"react-stately": "^3.28.0"
},
"devDependencies": {
"@igloo-ui/button": "^0.7.1"
Expand Down
62 changes: 57 additions & 5 deletions packages/Disclosure/src/Disclosure.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const Expanded = {
<>
<Button onClick={() => setIsExpanded(!isExpanded)}>{isExpanded ? 'Close me' : 'Open me'}</Button>
<br />
<Disclosure {...args} isExpanded={isExpanded} />
<Disclosure {...args} isExpanded={isExpanded} onOpen={() => setIsExpanded(true)} onClose={() => setIsExpanded(false)} />
</>
);
},
Expand All @@ -67,16 +67,14 @@ export const Expanded = {
</div>
),
title: 'Expanded',
icon: <Substract size="large" />,
onOpen: () => console.log('onOpen'),
onClose: () => console.log('onClose'),
icon: <Substract size="large" />
},
};


export const LowContrast = {
args: {
isExpanded: true,
defaultExpanded: true,
isLowContrast: true,
children: (
<div style={{ padding: '0.8rem 0 0' }}>
Expand All @@ -88,3 +86,57 @@ export const LowContrast = {
icon: <Settings size="small" />,
},
};


const data = [
{
id: 1,
title: "Send Good Vibes",
description:
"Celebrate a team member with a thoughtful card that makes their day",
},
{
id: 2,
title: "Send Good Vibes 2",
description:
"Celebrate a team member with a thoughtful card that makes their day",
},
];

export const VerticallyStacked = {
render: () => {
const [selectedIndex, setSelectedIndex] = React.useState<number | null>(null);

const handleDisclosureOpen = (id: number) => {
setSelectedIndex(id);
};

const handleDisclosureClose = () => {
setSelectedIndex(null);
};


return (
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
{data.map((item, index) => {
const isExpanded = selectedIndex === index;

return (
<Disclosure
key={item.id}
title={item.title}
isExpanded={isExpanded}
description={item.description}
onOpen={() => handleDisclosureOpen(index)}
onClose={() => handleDisclosureClose()}
>
<div style={{ padding: "1.6rem" }}>
<Button appearance="secondary">Send Good Vibes</Button>
</div>
</Disclosure>
);
})}
</div>
)
}
}
227 changes: 114 additions & 113 deletions packages/Disclosure/src/Disclosure.tsx
Original file line number Diff line number Diff line change
@@ -1,129 +1,130 @@
import * as React from 'react';
import cx from 'classnames';
import { useButton } from 'react-aria';
import { m, AnimatePresence, LazyMotion, domAnimation } from 'framer-motion';
import * as React from "react";
import cx from "classnames";
import { useToggleButton } from "react-aria";
import { useToggleState } from "react-stately";
import { m, AnimatePresence, LazyMotion, domAnimation } from "framer-motion";

import ChevronDown from '@igloo-ui/icons/dist/ChevronDown';
import ChevronDown from "@igloo-ui/icons/dist/ChevronDown";

import './disclosure.scss';
import "./disclosure.scss";

export interface DisclosureProps extends React.ComponentProps<'div'> {
/** The component content to display when expanding */
children: React.ReactNode;
/** Add a specific class to the disclosure */
className?: string;
/** Add a data-test tag for automated tests */
dataTest?: string;
/** The description of the disclosure */
description?: React.ReactNode;
/** An icon to show as the first element in disclosure */
icon?: React.ReactNode;
/** Whether or not the disclosure is expanded */
isExpanded?: boolean;
/** True for a light appearance (no shadow, icon or border) */
isLowContrast?: boolean;
/** Callback when the disclosure is closed */
onClose?: () => void;
/** Callback when the disclosure is opened */
onOpen?: () => void;
/** The title of the disclosure */
title?: string;
export interface DisclosureProps extends React.ComponentProps<"div"> {
/** The component content to display when expanding */
children: React.ReactNode;
/** Add a specific class to the disclosure */
className?: string;
/** Add a data-test tag for automated tests */
dataTest?: string;
/** Whether or not the disclosure is expanded by default (uncontrolled) */
defaultExpanded?: boolean;
/** The description of the disclosure */
description?: React.ReactNode;
/** An icon to show as the first element in disclosure */
icon?: React.ReactNode;
/** Whether or not the disclosure is expanded (controlled) */
isExpanded?: boolean;
/** True for a light appearance (no shadow, icon or border) */
isLowContrast?: boolean;
/** Callback when the disclosure is closed */
onClose?: () => void;
/** Callback when the disclosure is opened */
onOpen?: () => void;
/** The title of the disclosure */
title?: string;
}

const Disclosure: React.FunctionComponent<DisclosureProps> = ({
children,
className,
dataTest,
description,
icon,
isExpanded = false,
isLowContrast = false,
onClose,
onOpen,
title,
children,
className,
dataTest,
defaultExpanded = false,
description,
icon,
isExpanded,
isLowContrast = false,
onClose,
onOpen,
title
}: DisclosureProps) => {
const btnRef = React.useRef<HTMLButtonElement>(null);
const [expanded, setExpanded] = React.useState(isExpanded);
const btnRef = React.useRef<HTMLButtonElement>(null);

const { buttonProps } = useButton(
{
onPress: () => {
setExpanded(!expanded);
},
},
btnRef,
);

React.useEffect(() => {
setExpanded(isExpanded);
}, [isExpanded]);
const state = useToggleState({
defaultSelected: defaultExpanded,
isSelected: isExpanded,
onChange: isSelected => {
if (isSelected) {
onOpen?.();
} else {
onClose?.();
}
}

React.useEffect(() => {
if (expanded && onOpen) {
onOpen();
}
if (!expanded && onClose) {
onClose();
}
}, [expanded, onOpen, onClose]);
});

const classes = cx('ids-disclosure', className, {
'ids-disclosure--low-contrast': isLowContrast,
});
const { buttonProps } = useToggleButton(
{},
state,
btnRef
);

return (
<div className={classes} data-test={dataTest}>
<button
{...buttonProps}
ref={btnRef}
className="ids-disclosure__header"
aria-expanded={expanded}
>
{icon && <span className="ids-disclosure__header-icon">{icon}</span>}
<span className="ids-disclosure__header-content">
{title && (
<span className="ids-disclosure__header-title">{title}</span>
)}
{description && (
<span className="ids-disclosure__header-desc">{description}</span>
)}
</span>
{!isLowContrast && (
<ChevronDown
size="medium"
className="ids-disclosure__header-chevron"
/>
)}
</button>
<LazyMotion features={domAnimation} strict>
<AnimatePresence initial={false}>
{expanded && (
<m.div
className="ids-disclosure__content"
key="content"
initial="collapsed"
animate="open"
exit="collapsed"
variants={{
open: {
opacity: 1,
height: 'auto',
overflow: 'hidden',
transitionEnd: {
overflow: 'visible',
},
},
collapsed: { opacity: 0, height: 0, overflow: 'hidden' },
}}
transition={{ duration: 0.5, ease: [0.04, 0.62, 0.23, 0.98] }}
const classes = cx("ids-disclosure", className, {
"ids-disclosure--low-contrast": isLowContrast
});

return (
<div className={classes} data-test={dataTest}>
<button
{...buttonProps}
ref={btnRef}
className="ids-disclosure__header"
aria-expanded={state.isSelected}
>
{children}
</m.div>
)}
</AnimatePresence>
</LazyMotion>
</div>
);
{icon && <span className="ids-disclosure__header-icon">{icon}</span>}
<span className="ids-disclosure__header-content">
{title && (
<span className="ids-disclosure__header-title">{title}</span>
)}
{description && (
<span className="ids-disclosure__header-desc">{description}</span>
)}
</span>
{!isLowContrast && (
<ChevronDown
size="medium"
className="ids-disclosure__header-chevron"
/>
)}
</button>
<LazyMotion features={domAnimation} strict>
<AnimatePresence initial={false}>
{state.isSelected && (
<m.div
className="ids-disclosure__content"
key="content"
initial="collapsed"
animate="open"
exit="collapsed"
variants={{
open: {
opacity: 1,
height: "auto",
overflow: "hidden",
transitionEnd: {
overflow: "visible"
}
},
collapsed: { opacity: 0, height: 0, overflow: "hidden" }
}}
transition={{ duration: 0.5, ease: [0.04, 0.62, 0.23, 0.98] }}
>
{children}
</m.div>
)}
</AnimatePresence>
</LazyMotion>
</div>
);
};

export default Disclosure;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ exports[`Disclosure It should render a snapshot 1`] = `
>
<button
aria-expanded="false"
aria-pressed="false"
class="ids-disclosure__header"
type="button"
>
Expand Down

0 comments on commit c981a8b

Please sign in to comment.