mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-02-25 13:54:59 -05:00
Add reusable alert component using context
This commit is contained in:
parent
61cf2b5bcc
commit
5ee1c7c203
9 changed files with 231 additions and 0 deletions
41
src/components/alert/alert-component.jsx
Normal file
41
src/components/alert/alert-component.jsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import Button from '../../components/forms/button.jsx';
|
||||
|
||||
import './alert.scss';
|
||||
|
||||
const AlertComponent = ({className, icon, id, values, onClear}) => (
|
||||
<div className="alert-wrapper">
|
||||
<div
|
||||
className={classNames('alert', className)}
|
||||
>
|
||||
{icon && <img
|
||||
className="alert-icon"
|
||||
src={icon}
|
||||
/>}
|
||||
<div className="alert-msg">
|
||||
<FormattedMessage
|
||||
id={id}
|
||||
values={values}
|
||||
/>
|
||||
</div>
|
||||
{onClear && <Button
|
||||
className="alert-close-button"
|
||||
isCloseType
|
||||
onClick={onClear}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
AlertComponent.propTypes = {
|
||||
className: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
id: PropTypes.string.isRequired,
|
||||
values: PropTypes.shape({}),
|
||||
onClear: PropTypes.func
|
||||
};
|
||||
|
||||
export default AlertComponent;
|
18
src/components/alert/alert-context.js
Normal file
18
src/components/alert/alert-context.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {createContext, useContext} from 'react';
|
||||
import AlertStatus from './alert-status.js';
|
||||
|
||||
const AlertContext = createContext({
|
||||
// Note: defaults here are only used if there is no Provider in the tree
|
||||
status: AlertStatus.NONE,
|
||||
data: {},
|
||||
clearAlert: () => {},
|
||||
successAlert: () => {},
|
||||
errorAlert: () => {}
|
||||
});
|
||||
|
||||
const useAlertContext = () => useContext(AlertContext);
|
||||
|
||||
export {
|
||||
AlertContext as default,
|
||||
useAlertContext
|
||||
};
|
54
src/components/alert/alert-provider.jsx
Normal file
54
src/components/alert/alert-provider.jsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
import React, {useRef, useState} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import AlertStatus from './alert-status.js';
|
||||
import AlertContext from './alert-context.js';
|
||||
|
||||
const AlertProvider = ({children}) => {
|
||||
const defaultState = {
|
||||
status: AlertStatus.NONE,
|
||||
data: {},
|
||||
showClear: false
|
||||
};
|
||||
const [state, setState] = useState(defaultState);
|
||||
const timeoutRef = useRef(null);
|
||||
|
||||
const clearAlert = () => {
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = null;
|
||||
setState(defaultState);
|
||||
};
|
||||
|
||||
const handleAlert = (status, data, timeoutSeconds = 3) => {
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
setState({status, data, showClear: !timeoutSeconds});
|
||||
if (timeoutSeconds) {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
timeoutRef.current = null;
|
||||
setState(defaultState);
|
||||
}, timeoutSeconds * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AlertContext.Provider
|
||||
value={{
|
||||
status: state.status,
|
||||
data: state.data,
|
||||
showClear: state.showClear,
|
||||
clearAlert: clearAlert,
|
||||
successAlert: (newData, timeoutSeconds = 3) =>
|
||||
handleAlert(AlertStatus.SUCCESS, newData, timeoutSeconds),
|
||||
errorAlert: (newData, timeoutSeconds = 3) =>
|
||||
handleAlert(AlertStatus.ERROR, newData, timeoutSeconds)
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AlertContext.Provider>
|
||||
);
|
||||
};
|
||||
AlertProvider.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
export default AlertProvider;
|
5
src/components/alert/alert-status.js
Normal file
5
src/components/alert/alert-status.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export default {
|
||||
NONE: 'NONE',
|
||||
SUCCESS: 'SUCCESS',
|
||||
ERROR: 'ERROR'
|
||||
};
|
33
src/components/alert/alert.jsx
Normal file
33
src/components/alert/alert.jsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import AlertComponent from './alert-component.jsx';
|
||||
import AlertStatus from './alert-status.js';
|
||||
import {useAlertContext} from './alert-context.js';
|
||||
|
||||
import successIcon from './icon-alert-success.svg';
|
||||
import errorIcon from './icon-alert-error.svg';
|
||||
|
||||
const Alert = ({className}) => {
|
||||
const {status, data, showClear, clearAlert} = useAlertContext();
|
||||
if (status === AlertStatus.NONE) return null;
|
||||
return (
|
||||
<AlertComponent
|
||||
className={classNames(className, {
|
||||
'alert-success': status === AlertStatus.SUCCESS,
|
||||
'alert-error': status === AlertStatus.ERROR
|
||||
})}
|
||||
icon={status === AlertStatus.SUCCESS ? successIcon : errorIcon}
|
||||
id={data.id}
|
||||
values={data.values}
|
||||
onClear={showClear && clearAlert}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Alert.propTypes = {
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
export default Alert;
|
37
src/components/alert/alert.scss
Normal file
37
src/components/alert/alert.scss
Normal file
|
@ -0,0 +1,37 @@
|
|||
|
||||
.alert-wrapper {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
z-index: 100;
|
||||
.alert {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
min-height: 60px;
|
||||
&.alert-error {
|
||||
background: #FFF0DF;
|
||||
border: 1px solid #FF8C1A;
|
||||
box-shadow: 0px 0px 0px 2px rgba(255, 140, 26, 0.25)
|
||||
}
|
||||
|
||||
&.alert-success {
|
||||
background: #CEF2E8;
|
||||
border: 1px solid #0EBD8C;
|
||||
box-shadow: 0px 0px 0px rgba(14, 189, 140, 0.25);
|
||||
}
|
||||
.alert-msg {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.alert-close-button {
|
||||
position: unset;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
3
src/components/alert/icon-alert-error.svg
Normal file
3
src/components/alert/icon-alert-error.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="28" height="20" viewBox="-2 -1 15 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.40571 6.50912C1.50472 6.50912 0.775271 7.23857 0.775271 8.13956C0.775271 9.04055 1.50472 9.77 2.40571 9.77C3.3067 9.77 4.03615 9.04055 4.03615 8.13956C4.03615 7.23857 3.3067 6.50912 2.40571 6.50912ZM3.34168 5.02359C2.92699 5.9523 1.88444 5.9523 1.46975 5.02359L0.145744 2.07519C-0.268945 1.15289 0.250665 0 1.08171 0H3.72972C4.56076 0 5.08037 1.15289 4.66568 2.07519L3.34168 5.02359Z" fill="#FF8C1A"/>
|
||||
</svg>
|
After Width: | Height: | Size: 559 B |
9
src/components/alert/icon-alert-success.svg
Normal file
9
src/components/alert/icon-alert-success.svg
Normal file
|
@ -0,0 +1,9 @@
|
|||
<svg width="28" height="20" viewBox="0 0 28 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.86144 15.403C7.43527 15.403 7.0091 15.2398 6.68447 14.9152L3.48818 11.7189C2.83727 11.068 2.83727 10.0159 3.48818 9.36498C4.13909 8.71407 5.19121 8.71407 5.84212 9.36498L7.86144 11.3843L14.1591 5.08828C14.8084 4.43737 15.8622 4.43737 16.5131 5.08828C17.1623 5.73753 17.1623 6.7913 16.5131 7.44222L9.03841 14.9152C8.71378 15.2398 8.28761 15.403 7.86144 15.403Z" fill="#575E75"/>
|
||||
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="3" y="4" width="14" height="12">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.86144 15.403C7.43527 15.403 7.0091 15.2398 6.68447 14.9152L3.48818 11.7189C2.83727 11.068 2.83727 10.0159 3.48818 9.36498C4.13909 8.71407 5.19121 8.71407 5.84212 9.36498L7.86144 11.3843L14.1591 5.08828C14.8084 4.43737 15.8622 4.43737 16.5131 5.08828C17.1623 5.73753 17.1623 6.7913 16.5131 7.44222L9.03841 14.9152C8.71378 15.2398 8.28761 15.403 7.86144 15.403Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0)">
|
||||
<rect width="20" height="20" fill="#0FBD8C"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -14,12 +14,43 @@ const SubNavigation = require('../../components/subnavigation/subnavigation.jsx'
|
|||
const Select = require('../../components/forms/select.jsx');
|
||||
const OverflowMenu = require('../../components/overflow-menu/overflow-menu.jsx').default;
|
||||
const exampleIcon = require('./example-icon.svg');
|
||||
const AlertProvider = require('../../components/alert/alert-provider.jsx').default;
|
||||
const {useAlertContext} = require('../../components/alert/alert-context.js');
|
||||
const Alert = require('../../components/alert/alert.jsx').default;
|
||||
|
||||
require('./components.scss');
|
||||
|
||||
/* eslint-disable react/prop-types, react/jsx-no-bind */
|
||||
/* Demo of how to use the useAlertContext hook */
|
||||
const AlertButton = ({type, timeoutSeconds}) => {
|
||||
const {errorAlert, successAlert} = useAlertContext();
|
||||
const onClick = type === 'success' ?
|
||||
() => successAlert({id: 'success-alert.string.id'}, timeoutSeconds) :
|
||||
() => errorAlert({id: 'error-alert.string.id'}, timeoutSeconds);
|
||||
return (
|
||||
<Button onClick={onClick}>
|
||||
{type}, {timeoutSeconds || 'no '} timeout
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const Components = () => (
|
||||
<div className="components">
|
||||
<div className="inner">
|
||||
<h1>Alert Provider, Display and Hooks</h1>
|
||||
<AlertProvider>
|
||||
<div style={{position: 'relative', minHeight: '200px', border: '1px solid red'}}>
|
||||
<Alert />
|
||||
<div><AlertButton
|
||||
type="success"
|
||||
timeoutSeconds={3}
|
||||
/></div>
|
||||
<div><AlertButton
|
||||
type="error"
|
||||
timeoutSeconds={null}
|
||||
/></div>
|
||||
</div>
|
||||
</AlertProvider>
|
||||
<h1>Overflow Menu</h1>
|
||||
<div className="example-tile">
|
||||
<OverflowMenu>
|
||||
|
|
Loading…
Reference in a new issue