Skip to content

Commit

Permalink
Merge branch 'master' into use-snackbar
Browse files Browse the repository at this point in the history
  • Loading branch information
ecwyne authored Mar 27, 2019
2 parents 018c8db + e189548 commit 07adf24
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 105 deletions.
25 changes: 23 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
## `[email protected].0`
## `[email protected].2`
###### to be published

Thanks to all contributers who improved notistack by opening an issue/PR.

**@martinmckenna**
**@Lukas-Kullmann**
* Add `displayName` to components exported by `withSnackbar` HOC [#100](https://github.com/iamhosseindhv/notistack/issues/100)


## `[email protected]`
###### Mar 15, 2019

Thanks to all contributers who improved notistack by opening an issue/PR.

**@amakhrov**
* Fix typing for `iconVariant` props [#91](https://github.com/iamhosseindhv/notistack/issues/91)



## `[email protected]`
###### Mar 5, 2019

Thanks to all contributers who improved notistack by opening an issue/PR.

**@cwbuecheler @mpash @khhan1993 @Fs00 @martinmckenna**
* Rename `InjectedSnackbarProps` to `withSnackbarProps` in type definitions [#59](https://github.com/iamhosseindhv/notistack/issues/59)
* Add new prop `dense` to allow dense margins for snackbars (suitable for mobiles) [#58](https://github.com/iamhosseindhv/notistack/issues/58)
* Improve performance and prevent unnecessary child re-rendering [#39](https://github.com/iamhosseindhv/notistack/issues/39)


Expand Down
43 changes: 10 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ Table of Contents
- [Documentation](#documentation)
- [`SnackbarProvider`](#snackbarprovider)
- [`withSnackbar`](#withsnackbar)
- [Add actions to snackbar](#add-actions-to-snackbar)
- [Redux example](#redux-example)
- [Redux support](#redux-support)
- [Contribution](#contribution)
- [Notes](#notes)
- [Author - Contact me](#author---contact)
Expand Down Expand Up @@ -82,20 +81,23 @@ Or see the code for a minimal working example: [codesandbox](https://codesandbox
All material-ui Snackbar props will get passed down to a Snackbar component. See Material-ui [docs](https://material-ui.com/api/snackbar/) for more info.
```javascript
// Maximum number of snackbars that can be stacked on top of eachother.
maxSnack type: number required: true default: 3
maxSnack type: number required: false default: 3

// The little icon that is displayed in a snackbar
iconVariant type: any required: false default: Material design icons

// hide or display icon variant of a snackbar
// Hide or display icon variant of a snackbar
hideIconVariant type: boolean required: false default: false

// event fired when user clicks on action button (if any)
onClickAction type: func required: false default: dismiss the snackbar
// Event fired when user clicks on action button (if any)
onClickAction type: func required: false default: Dismiss the snackbar

// Do not allow snackbars with the same message to be displayed multiple times
preventDuplicate type: boolean required: false default: false

// Denser margins for snackbars. Recommended to be used on mobile devices
dense type: boolean required: false default: false

// Example of a Mui-Snackbar prop
transitionDuration={{ exit: 380, enter: 400 }}
```
Expand Down Expand Up @@ -153,33 +155,8 @@ this.props.closeSnackbar(key)
key type: string|number required: true
```


### Add actions to snackbar:
You can add actions to snackbars in the same manner specified in material-ui [docs](https://material-ui.com/demos/snackbars):
```javascript
<SnackbarProvider
maxSnack={3}
action={[
<Button color="secondary" size="small">My Action</Button>
]}
onClickAction={() => alert('Clicked on my action button.')}
>
<App />
</SnackbarProvider>
```

However, notice that by passing `action` to `SnackbarProvider`, you’ll be adding action to all of the snackbars. To specify action for a single snackbar, use `options` argument of `enqueueSnackbar` method instead:
```javascript
this.props.enqueueSnackbar('Item moved to recently deleted folder.', {
variant: 'default',
action: <Button color="secondary" size="small">Undo</Button>,
onClickAction={() => alert('Clicked on my action button.')}
})
```
Use `onClickAction` prop to handle onClick event on snackbar action. The default behaviour of `onClickAction` is to dismiss the snackbar. Also, note that multiple actions for a snackbar is not supported by notistack.

### Redux example:
You can use notistack to send snackbars from reducers. This has lots of applications but particularly useful when a network request fails. For more information check out notistack's [minimal redux example](https://codesandbox.io/s/github/iamhosseindhv/notistack/tree/master/examples/redux-example).
### Redux support:
You can use notistack to send snackbars from reducers. See notistack [documentation](https://iamhosseindhv.com/notistack#redux-example) for more info.

## Contribution
Open an issue and your problem will be solved.
Expand Down
29 changes: 13 additions & 16 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,46 @@
import * as React from 'react';
import { SvgIconProps } from '@material-ui/core/SvgIcon';
import { SnackbarProps, SnackbarClassKey } from '@material-ui/core/Snackbar';

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type ClassNameMap<ClassKey extends string = string> = Record<ClassKey, string>;

export type VariantType = 'default' | 'error' | 'success' | 'warning' | 'info';

export interface OptionsObject extends Omit<SnackbarProps, 'open' | 'message' | 'classes'> {
key?: string | number;
variant?: VariantType;
persist?: boolean;
persist?: boolean;
onClickAction?: Function;
preventDuplicate?: boolean;
}

type NotistackClassKey = 'variantSuccess'
| 'variantError'
| 'variantInfo'
| 'variantWarning';
export type NotistackClassKey = 'variantSuccess' | 'variantError' | 'variantInfo' | 'variantWarning';

// class keys for both MUI and notistack
export type CombinedClassKey = NotistackClassKey | SnackbarClassKey;
type CombinedClassKey = NotistackClassKey | SnackbarClassKey;

export interface InjectedNotistackProps {
export interface withSnackbarProps {
onPresentSnackbar: (variant: VariantType, message: string) => void;
enqueueSnackbar: (message: string | React.ReactNode, options?: OptionsObject) => string | number | null;
closeSnackbar: (key: string | number) => void
}

export function withSnackbar<P extends InjectedNotistackProps>(component: React.ComponentType<P>):
React.ComponentClass<Omit<P, keyof InjectedNotistackProps>> & { WrappedComponent: React.ComponentType<P> };
export function withSnackbar<P extends withSnackbarProps>(component: React.ComponentType<P>):
React.ComponentClass<Omit<P, keyof withSnackbarProps>> & { WrappedComponent: React.ComponentType<P> };


export function useSnackbar(): InjectedNotistackProps;

export type ClassNameMap<ClassKey extends string = string> = Record<ClassKey, string>;

/** all MUI props, including class keys for notistack and MUI with additional notistack props */
export interface SnackbarProviderProps
extends Omit<SnackbarProps, 'open' | 'message' | 'classes'> {
// all material-ui props, including class keys for notistack and material-ui with additional notistack props
export interface SnackbarProviderProps extends Omit<SnackbarProps, 'open' | 'message' | 'classes'> {
classes?: Partial<ClassNameMap<CombinedClassKey>>;
maxSnack: number;
iconVariant?: React.ComponentType<SvgIconProps>;
maxSnack?: number;
iconVariant?: Partial<Record<VariantType, React.ReactNode>>;
hideIconVariant?: boolean;
onClickAction?: Function;
preventDuplicate?: boolean;
dense?: boolean;
}

export const SnackbarProvider: React.ComponentType<SnackbarProviderProps>;
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "notistack",
"version": "0.4.3",
"description": "Highly customisable notification snackbars (Highly customizable notification snackbars (toasts) that can be stacked on top of each other) that can be stacked on top of each other",
"version": "0.5.1",
"description": "Highly customizable notification snackbars (toasts) that can be stacked on top of each other",
"main": "build/index",
"license": "MIT",
"author": {
Expand Down Expand Up @@ -62,7 +62,6 @@
"react",
"javascript",
"material-ui",
"material ui",
"toast",
"redux",
"snackbar",
Expand Down
12 changes: 10 additions & 2 deletions src/SnackbarItem/SnackbarItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,15 @@ class SnackbarItem extends Component {
} = this.props;

const { action: contentAction, className, ...otherContentProps } = ContentProps;
const { key, variant = 'default', persist, ...singleSnackProps } = snack;

const {
key,
persist,
variant = 'default',
onClickAction: singleOnClickAction,
...singleSnackProps
} = snack;

const icon = iconVariant[variant];

const contentProps = {
Expand All @@ -68,7 +76,7 @@ class SnackbarItem extends Component {
action: snack.action || contentAction || action,
};

let onClickHandler = snack.action ? snack.onClickAction : onClickAction;
let onClickHandler = snack.action ? singleOnClickAction : onClickAction;
onClickHandler = onClickHandler || this.handleClose(key);

const anchOrigin = singleSnackProps.anchorOrigin || anchorOrigin;
Expand Down
2 changes: 1 addition & 1 deletion src/SnackbarItem/SnackbarItem.styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const styles = theme => ({
fontFamily: theme.typography.fontFamily,
},
lessPadding: {
paddingLeft: theme.spacing.unit * 2.5,
paddingLeft: 8 * 2.5,
},
variantSuccess: {
backgroundColor: green[600],
Expand Down
69 changes: 38 additions & 31 deletions src/SnackbarProvider.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component, Fragment } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { SnackbarContext, SnackbarContextNext } from './SnackbarContext';
import { TRANSITION_DELAY, TRANSITION_DOWN_DURATION, MESSAGES } from './utils/constants';
Expand All @@ -7,19 +7,30 @@ import warning from './utils/warning';


class SnackbarProvider extends Component {
state = {
snacks: [],
};
constructor(props) {
super(props);
this.state = {
snacks: [],
contextValue: {
handleEnqueueSnackbar: this.handleEnqueueSnackbar,
handleCloseSnackbar: this.handleDismissSnack,
},
};
}

queue = [];

get offsets() {
const { snacks } = this.state;
return snacks.map((item, i) => {
let index = i;
let offset = 20;
const { view: viewOffset, snackbar: snackbarOffset } = this.props.dense
? { view: 0, snackbar: 4 }
: { view: 20, snackbar: 12 };
let offset = viewOffset;
while (snacks[index - 1]) {
offset += snacks[index - 1].height + 16;
const snackHeight = snacks[index - 1].height || 48;
offset += snackHeight + snackbarOffset;
index -= 1;
}
return offset;
Expand Down Expand Up @@ -206,52 +217,48 @@ class SnackbarProvider extends Component {
};

render() {
const { children, maxSnack, ...props } = this.props;
const { snacks } = this.state;
const { children, maxSnack, dense, ...props } = this.props;
const { contextValue, snacks } = this.state;

return (
<SnackbarContext.Provider value={this.handlePresentSnackbar}>
<SnackbarContextNext.Provider value={{
handleEnqueueSnackbar: this.handleEnqueueSnackbar,
handleCloseSnackbar: this.handleDismissSnack,
}}>
<Fragment>
{children}
{snacks.map((snack, index) => (
<SnackbarItem
{...props}
key={snack.key}
snack={snack}
offset={this.offsets[index]}
onClose={this.handleCloseSnack}
onExited={this.handleExitedSnack}
onSetHeight={this.handleSetHeight}
/>
))}
</Fragment>
<SnackbarContextNext.Provider value={contextValue}>
{children}
{snacks.map((snack, index) => (
<SnackbarItem
{...props}
key={snack.key}
snack={snack}
offset={this.offsets[index]}
onClose={this.handleCloseSnack}
onExited={this.handleExitedSnack}
onSetHeight={this.handleSetHeight}
/>
))}
</SnackbarContextNext.Provider>
</SnackbarContext.Provider>
);
}
}

SnackbarProvider.propTypes = {
children: PropTypes.element.isRequired,
children: PropTypes.node.isRequired,
/**
* Maximum snackbars that can be stacked
* on top of one another
* Maximum snackbars that can be stacked on top of one another.
*/
dense: PropTypes.bool,
maxSnack: PropTypes.number,
preventDuplicate: PropTypes.bool,
onClose: PropTypes.func,
onExited: PropTypes.func,
preventDuplicate: PropTypes.bool,
};

SnackbarProvider.defaultProps = {
maxSnack: 3,
dense: false,
preventDuplicate: false,
onClose: undefined,
onExited: undefined,
preventDuplicate: false,
};

export default SnackbarProvider;
38 changes: 22 additions & 16 deletions src/withSnackbar.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import React from 'react';
import { SnackbarContext, SnackbarContextNext } from './SnackbarContext';

const withSnackbar = Component => props => (
<SnackbarContext.Consumer>
{handlePresentSnackbar => (
<SnackbarContextNext.Consumer>
{context => (
<Component
{...props}
onPresentSnackbar={handlePresentSnackbar}
enqueueSnackbar={context.handleEnqueueSnackbar}
closeSnackbar={context.handleCloseSnackbar}
/>
)}
</SnackbarContextNext.Consumer>
)}
</SnackbarContext.Consumer>
);
const withSnackbar = (Component) => {
const WrappedComponent = props => (
<SnackbarContext.Consumer>
{handlePresentSnackbar => (
<SnackbarContextNext.Consumer>
{context => (
<Component
{...props}
onPresentSnackbar={handlePresentSnackbar}
enqueueSnackbar={context.handleEnqueueSnackbar}
closeSnackbar={context.handleCloseSnackbar}
/>
)}
</SnackbarContextNext.Consumer>
)}
</SnackbarContext.Consumer>
);

WrappedComponent.displayName = `withSnackbar(${Component.displayName || Component.name || 'Component'})`;

return WrappedComponent;
};

export default withSnackbar;

0 comments on commit 07adf24

Please sign in to comment.