Skip to content

Commit

Permalink
feat(alert): add expand and collapse behaviour to inline alert (#114)
Browse files Browse the repository at this point in the history
* refactor(expandable): extract transition animation from Expandable

Create AnimatedExpansion component that handles expand-collapse animation in Expandable, making it
possible to easily add this behaviour to other components.

* feat(alert): add expansion and collapsion behaviour to inline alert

Wrap Alert in AnimatedExpansion component to make it appear and disappear smoothly

* feat(alert): remove neutral variant of inline alert
  • Loading branch information
BalbinaK authored Jun 2, 2022
1 parent 59f7bc9 commit 075f314
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 121 deletions.
51 changes: 51 additions & 0 deletions packages/_helpers/animated-expansion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { PropsWithChildren, useEffect, useRef, useState } from 'react';
import { collapse, expand } from 'element-collapse';
import { classNames } from '@chbphone55/classnames';

export function AnimatedExpansion({
show,
children,
}: PropsWithChildren<{ show?: Boolean }>) {
const [isExpanded, setIsExpanded] = useState(show);
const expandableRef = useRef(null);
const isMounted = useRef(false);

async function collapseElement() {
await new Promise((resolve) => {
collapse(expandableRef.current, resolve);
});
setIsExpanded(false);
}

function expandElement() {
expand(expandableRef.current);
setIsExpanded(true);
}

useEffect(() => {
// Don't do anything at first render
if (!isMounted.current) {
isMounted.current = true;
return;
}

if (show) {
expandElement();
} else {
collapseElement();
}
}, [show]);

return (
<div
ref={expandableRef}
className={classNames({
'overflow-hidden': true,
'h-0 invisible': !isExpanded,
})}
aria-hidden={!isExpanded}
>
{children}
</div>
);
}
1 change: 1 addition & 0 deletions packages/_helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { Clickable } from './clickable';
export { DeadToggle } from './dead-toggle';
export { Affix } from './affix';
export { AnimatedExpansion } from './animated-expansion';
75 changes: 32 additions & 43 deletions packages/alert/docs/Alert.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,77 +13,66 @@ import { Alert } from '@fabric-ds/react';

## Visual Options

### Negative
### Expandable behaviour

```jsx example
<Alert type="negative">
This is the message text that can be short or a little bit long
</Alert>
function ExpandableAlert() {
const [show, setShow] = React.useState(true);

return (
<>
<Alert type="info" show={show}>
<p className="font-bold">Title Caption-Strong</p>
<p>This is the message text that can be short or a little bit long</p>
<a>Link to more information</a>
<div className="mt-8 space-x-8">
<Button small>Primary CTA</Button>
<Button small secondary quiet>
Secondary
</Button>
</div>
</Alert>

<Button className="mt-16" small primary onClick={() => setShow(!show)}>
{show ? 'Hide alert' : 'Show alert'}
</Button>
</>
);
}
```

### Positive
### Negative

```jsx example
<Alert type="positive">
<Alert type="negative" show>
This is the message text that can be short or a little bit long
</Alert>
```

### Warning
### Positive

```jsx example
<Alert type="warning">
<Alert type="positive" show>
This is the message text that can be short or a little bit long
</Alert>
```

### Info
### Warning

```jsx example
<Alert type="info">
<Alert type="warning" show>
This is the message text that can be short or a little bit long
</Alert>
```

### Neutral
### Info

```jsx example
<Alert type="neutral">
<Alert type="info" show>
This is the message text that can be short or a little bit long
</Alert>
```

### Other examples

```jsx example
<Alert type="info">
<p className="font-bold">Title Caption-Strong</p>
<p>This is the message text that can be short or a little bit long</p>
</Alert>
```

```jsx example
<Alert type="info">
<p className="font-bold">Title Caption-Strong</p>
<p>This is the message text that can be short or a little bit long</p>
<a>Link to more information</a>
</Alert>
```

```jsx example
<Alert type="info">
<p className="font-bold">Title Caption-Strong</p>
<p>This is the message text that can be short or a little bit long</p>
<a>Link to more information</a>
<div className="mt-8 space-x-8">
<Button small>Primary CTA</Button>
<Button small secondary quiet>
Secondary
</Button>
</div>
</Alert>
```

## Props

```props packages/alert/src/component.tsx
Expand Down
59 changes: 22 additions & 37 deletions packages/alert/src/component.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import { classNames } from '@chbphone55/classnames';
import React, { PropsWithChildren, ReactElement } from 'react';
import { AlertProps } from '.';
import { AnimatedExpansion } from '../../_helpers';

export function Alert({
show,
type,
children,
...props
}: PropsWithChildren<AlertProps>) {
const { color, icon } = styleMap[type];

return (
<div
className={classNames(
props.className,
`flex p-16 border rounded-4 border-l-4 bg-${color}-50 border-${color}-300`,
)}
style={{ borderLeftColor: `var(--f-${color}-600)`, ...props.style }}
>
<div className={`mr-8 text-${color}-600`}>{icon}</div>
<div className="last-child:mb-0 text-14">{children}</div>
</div>
<AnimatedExpansion show={show}>
<div
className={classNames(
props.className,
`flex p-16 border rounded-4 border-l-4 bg-${color}-50 border-${color}-300`,
)}
style={{ borderLeftColor: `var(--f-${color}-600)`, ...props.style }}
>
<div className={`mr-8 text-${color}-600`}>{icon}</div>
<div className="last-child:mb-0 text-14">{children}</div>
</div>
</AnimatedExpansion>
);
}

Expand All @@ -40,8 +44,8 @@ const styleMap: {
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M8 3.25a.75.75 0 0 1 .75.75v5a.75.75 0 0 1-1.5 0V4A.75.75 0 0 1 8 3.25Z"
fill="#fff"
/>
Expand All @@ -66,8 +70,8 @@ const styleMap: {
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M11.5 4.94c.3.27.34.75.06 1.06l-4 4.5a.75.75 0 0 1-1.09.03l-2-2a.75.75 0 0 1 1.06-1.06l1.44 1.44L10.44 5a.75.75 0 0 1 1.06-.07Z"
fill="#fff"
/>
Expand All @@ -88,8 +92,8 @@ const styleMap: {
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M8 3.25c.41 0 .75.34.75.75v5a.75.75 0 0 1-1.5 0V4c0-.41.34-.75.75-.75Z"
fill="#fff"
/>
Expand All @@ -108,27 +112,8 @@ const styleMap: {
>
<circle cx="8" cy="8" r="8" fill="currentColor" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.25 12a.75.75 0 0 0 1.5 0V8a.75.75 0 0 0-1.5 0v4ZM8 4a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z"
fill="#fff"
/>
</svg>
),
},
neutral: {
color: 'bluegray',
icon: (
<svg
width="16"
height="16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="8" cy="8" r="8" fill="currentColor" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M7.25 12a.75.75 0 0 0 1.5 0V8a.75.75 0 0 0-1.5 0v4ZM8 4a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z"
fill="#fff"
/>
Expand Down
6 changes: 5 additions & 1 deletion packages/alert/src/props.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
export type AlertProps = {
/**
* Determines whether the alert should be visible
*/
show?: Boolean;
/**
* Type of alert
*/
type: 'negative' | 'positive' | 'warning' | 'info' | 'neutral';
type: 'negative' | 'positive' | 'warning' | 'info';
/**
* Additional classes to include
*/
Expand Down
39 changes: 18 additions & 21 deletions packages/alert/stories/Alert.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,37 @@ const metadata = { title: 'FeedbackIndicators/Alert' };
export default metadata;

export const Default = () => {
const [show, setShow] = React.useState(true);

return (
<div className="flex flex-col gap-y-16">
<div>
<h3>Negative</h3>
<Alert type="negative">
<Alert type="negative" show={show}>
This is a message that you've done something really wrong.
</Alert>
<Button className="mt-16" small primary onClick={() => setShow(!show)}>
{show ? 'Hide negative alert' : 'Show negative alert'}
</Button>
</div>
<div>
<h3>Positive</h3>
<Alert type="positive">
<Alert type="positive" show>
This is a message that gives you positive feedback.
</Alert>
</div>
<div>
<h3>Warning</h3>
<Alert type="warning">
<Alert type="warning" show>
This is a message that shows a warning, might be nothing serious.
</Alert>
</div>
<div>
<h3>Info</h3>
<Alert type="info">
<Alert type="info" show>
This is a message that enlightens you with some new cool information.
</Alert>
</div>
<div>
<h3>Neutral</h3>
<Alert type="neutral">
This is the most neutral of neutral messages. Could be used for
whatever really.
</Alert>
</div>
</div>
);
};
Expand All @@ -58,35 +56,34 @@ const InteractiveContent = () => (
);

export const WithInteractiveContent = () => {
const [show, setShow] = React.useState(true);

return (
<div className="flex flex-col gap-y-16">
<div>
<h3>Negative</h3>
<Alert type="negative">
<Alert type="negative" show={show}>
<InteractiveContent />
</Alert>
<Button className="mt-16" small primary onClick={() => setShow(!show)}>
{show ? 'Hide negative alert' : 'Show negative alert'}
</Button>
</div>
<div>
<h3>Positive</h3>
<Alert type="positive">
<Alert type="positive" show>
<InteractiveContent />
</Alert>
</div>
<div>
<h3>Warning</h3>
<Alert type="warning">
<Alert type="warning" show>
<InteractiveContent />
</Alert>
</div>
<div>
<h3>Info</h3>
<Alert type="info">
<InteractiveContent />
</Alert>
</div>
<div>
<h3>Neutral</h3>
<Alert type="neutral">
<Alert type="info" show>
<InteractiveContent />
</Alert>
</div>
Expand Down
Loading

0 comments on commit 075f314

Please sign in to comment.