-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/rover UI/ever 13326/kite component (#324)
* feat: Adding basic kite component * refactored interface and fixing css styles * refactored to add kite header and fixed styling * used shared size variable for margin and removed un-used childrenWrapper class * fix: left aligned the title text between the icon and close button * added knobs to the overview * added knobs for Kite.Icon props * refactored to make title node more flexible, add context for dismiss and onclose properties * fixed kite content style * fixed styling and refactored component to properly align the kite icons and contents. * updated snapshot test * chore: EVER-13326: Kite README and storybook knob additional, renamed Kite.Content to Kite.Body, to match convention from Modal Co-authored-by: Boima Konuwa <boima.konuwa@cision.com> Co-authored-by: Chris Garcia <pixelbandito@gmail.com>
- Loading branch information
1 parent
8c0b4d1
commit cf49de4
Showing
11 changed files
with
576 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
.Kite { | ||
z-index: var(--rvr-zindex-kite); | ||
visibility: hidden; | ||
box-sizing: border-box; | ||
position: fixed; | ||
top: var(--rvr-space-md); | ||
right: var(--rvr-space-md); | ||
transition: transform .6s ease-in-out; | ||
animation: slide-in-right .7s; | ||
display: flex; | ||
flex-flow: row nowrap; | ||
align-items: flex-start; | ||
background-color: var(--rvr-white); | ||
width: 342px; | ||
box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.2); | ||
border-radius: 4px; | ||
padding: 16px; | ||
font-size: var(--rvr-font-size-md); | ||
color: var(--rvr-color-font-dark); | ||
} | ||
|
||
.Kite > * { | ||
margin-left: 16px; | ||
} | ||
|
||
.Kite > :first-child { | ||
margin-left: 0; | ||
} | ||
|
||
.visible { | ||
visibility: visible; | ||
} | ||
|
||
.icon { | ||
flex: 0 0 auto; | ||
align-self: flex-start; | ||
} | ||
|
||
.content { | ||
flex: 1 1 auto; | ||
min-width: 0; | ||
} | ||
|
||
.dismissButton { | ||
flex: 0 0 auto; | ||
margin-right: -16px; | ||
margin-top: -12px; | ||
margin-bottom: -12px; | ||
align-self: flex-start; | ||
} | ||
|
||
.header { | ||
font-weight: var(--rvr-font-weight-bold); | ||
} | ||
|
||
.header + .body { | ||
margin-top: 8px; | ||
} | ||
|
||
.title { | ||
flex-grow: 2; | ||
} | ||
|
||
.enterDone { | ||
opacity: 1; | ||
transform: translateX(0); | ||
} | ||
|
||
.exit { | ||
opacity: 0; | ||
transform: translateX(-200px); | ||
} | ||
|
||
.top-right { | ||
top: 12px; | ||
right: 12px; | ||
transition: all .6s ease-in-out; | ||
transform: translateX(100%); | ||
} | ||
|
||
|
||
|
||
|
||
@keyframes slide-in-right { | ||
from { | ||
transform: translateX(100%); | ||
} | ||
|
||
to { | ||
transform: translateX(0); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import React from 'react'; | ||
import { render, screen } from '@testing-library/react'; | ||
import '@testing-library/jest-dom'; | ||
|
||
import userEvent from '@testing-library/user-event'; | ||
import Kite from './Kite'; | ||
|
||
const defaultProps = { | ||
visible: true, | ||
onClose: jest.fn(), | ||
ttl: 3000, | ||
canBeDismissed: true, | ||
}; | ||
|
||
const renderKite = (props = defaultProps) => | ||
render( | ||
<Kite | ||
{...props} | ||
icon={<Kite.Icon fill="red" height="20" name="warning" width="20" />} | ||
> | ||
<Kite.Header>Kite Title</Kite.Header> | ||
<Kite.Body> | ||
<p>Kite Content Goes here!</p> | ||
</Kite.Body> | ||
</Kite> | ||
); | ||
describe('Kite', () => { | ||
it('renders correctly', () => { | ||
const { baseElement } = renderKite(); | ||
expect(baseElement).toMatchSnapshot(); | ||
}); | ||
|
||
it("does not render when 'visible' prop is false", () => { | ||
render(<Kite visible={false} data-testid="Kite-Test" />); | ||
expect(screen.queryByTestId('Modal-Test')).not.toBeInTheDocument(); | ||
}); | ||
|
||
describe('Dismiss Button', () => { | ||
test.each` | ||
canBeDismissed | visible | ||
${true} | ${true} | ||
${false} | ${false} | ||
`( | ||
'when canBeDismissed = $canBeDismissed, the close button visible should be: $visible', | ||
({ canBeDismissed }) => { | ||
render( | ||
<Kite {...defaultProps} canBeDismissed={canBeDismissed}> | ||
<Kite.Header>Title</Kite.Header> | ||
</Kite> | ||
); | ||
|
||
const dismissButton = screen.queryByTestId('kite-dismiss-button'); | ||
if (canBeDismissed) { | ||
expect(dismissButton).toBeInTheDocument(); | ||
} else { | ||
expect(dismissButton).not.toBeInTheDocument(); | ||
} | ||
} | ||
); | ||
}); | ||
|
||
describe('onClose callback', () => { | ||
it('calls onClose callback when dismiss button is clicked', async () => { | ||
render( | ||
<Kite {...defaultProps} onClose={defaultProps.onClose}> | ||
<Kite.Header>Title</Kite.Header> | ||
</Kite> | ||
); | ||
const dismissButton = screen.getByTestId('kite-dismiss-button'); | ||
expect(dismissButton).toBeInTheDocument(); | ||
|
||
userEvent.click(dismissButton); | ||
|
||
expect(defaultProps.onClose).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import React, { useEffect, CSSProperties } from 'react'; | ||
import classNames from 'classnames'; | ||
|
||
import ReactDOM from 'react-dom'; | ||
import { CSSTransition } from 'react-transition-group'; | ||
import styles from './Kite.module.css'; | ||
import Button from '../Button'; | ||
import Icon from '../Icon'; | ||
|
||
interface KiteProps { | ||
children?: React.ReactNode; | ||
canBeDismissed?: boolean; | ||
className?: string; | ||
visible?: boolean; | ||
icon?: React.ReactNode; | ||
onClose?: () => void; | ||
style?: CSSProperties; | ||
ttl?: number; | ||
} | ||
|
||
type KiteIconProps = Parameters<typeof Icon>[0]; | ||
type KiteChildProps = React.HTMLAttributes<HTMLDivElement>; | ||
|
||
type KiteType = React.FC<KiteProps> & { | ||
Body: React.FC<KiteChildProps>; | ||
Header: React.FC<KiteChildProps>; | ||
Icon: React.FC<KiteIconProps>; | ||
}; | ||
|
||
const Kite: KiteType = ({ | ||
canBeDismissed = true, | ||
children = undefined, | ||
className: passedClassName = '', | ||
visible = false, | ||
icon = undefined, | ||
onClose = () => {}, | ||
ttl = undefined, | ||
...passedProps | ||
}) => { | ||
useEffect(() => { | ||
const interval = setInterval(() => { | ||
if (onClose && ttl) { | ||
onClose(); | ||
} | ||
}, ttl); | ||
return () => { | ||
clearInterval(interval); | ||
}; | ||
}, [onClose, ttl]); | ||
|
||
return ReactDOM.createPortal( | ||
<CSSTransition | ||
in={visible} | ||
unmountOnExit | ||
timeout={{ enter: 0, exit: 300 }} | ||
classNames={{ | ||
enterDone: styles.enterDone, | ||
exit: styles.exit, | ||
}} | ||
> | ||
<div | ||
className={classNames( | ||
styles.Kite, | ||
{ [styles.visible]: visible }, | ||
passedClassName | ||
)} | ||
{...passedProps} | ||
> | ||
{icon && <div className={styles.icon}>{icon}</div>} | ||
<div className={styles.content}>{children}</div> | ||
{canBeDismissed && ( | ||
<div className={styles.dismissButton}> | ||
<Button | ||
level="text" | ||
onClick={onClose} | ||
data-testid="kite-dismiss-button" | ||
> | ||
<Button.Addon> | ||
<Icon | ||
height="20" | ||
name="close" | ||
style={{ display: 'block' }} | ||
width="20" | ||
/> | ||
</Button.Addon> | ||
</Button> | ||
</div> | ||
)} | ||
</div> | ||
</CSSTransition>, | ||
document.body | ||
); | ||
}; | ||
|
||
const KiteIcon: React.FC<KiteIconProps> = ({ className, ...props }) => { | ||
return <Icon {...props} className={classNames(styles.icon, className)} />; | ||
}; | ||
|
||
const KiteHeader: React.FC<KiteChildProps> = ({ className, ...props }) => ( | ||
<div {...props} className={classNames(styles.header, className)} /> | ||
); | ||
const KiteBody: React.FC<KiteChildProps> = ({ className, ...props }) => { | ||
return <div {...props} className={classNames(styles.body, className)} />; | ||
}; | ||
|
||
Kite.Icon = KiteIcon; | ||
Kite.Header = KiteHeader; | ||
Kite.Body = KiteBody; | ||
|
||
export default Kite; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# \<Kite\> | ||
|
||
**A configurable Kite component that automatically triggers onClose when the time to live interval elapses** | ||
|
||
This component can be used as a toast that slides in from the top left to display information. | ||
|
||
Below is an example structure of the `<Kite>` | ||
|
||
```js | ||
<Kite | ||
canBeDismissed | ||
icon={<Kite.Icon />} | ||
onClose={() => setVisible(false)} | ||
ttl={3000} | ||
visible={visible} | ||
> | ||
<Kite.Header>Bold title</Kite.Header> | ||
<Kite.Body>Optional plain text / React node content</Kite.Body> | ||
</Kite> | ||
``` | ||
|
||
This will render a Kite with an icon, heading, and body content. | ||
|
||
- The parent component controls visibility via the `onClose` callback and `visible` prop. | ||
- If a `ttl` is provided, the component will call `onClose` that many ms after `visible` changes to `true`. | ||
- `icon` and `children` can be anything React can render. Kites expert simple `Kite.Icon`, `Kite.Header`, and `Kite.Body` helpers. `Kite.Icon` uses the same interface as the base `Icon` component, and the heading and body provide some default spacing and font-weight variation. | ||
- If `canBeDismissed` is false, the kite will not include a standard close button. |
Oops, something went wrong.