feat: add snackbar alert with refresh button (#1193)
* feat: add snackbar alert with refresh button fixes #77 * fixup * change refresh to reload
This commit is contained in:
		
							parent
							
								
									c56d561e9d
								
							
						
					
					
						commit
						0887196db4
					
				
					 7 changed files with 173 additions and 2 deletions
				
			
		| 
						 | 
					@ -45,6 +45,9 @@
 | 
				
			||||||
  <!-- Toast.html gets rendered here -->
 | 
					  <!-- Toast.html gets rendered here -->
 | 
				
			||||||
  <div id="theToast"></div>
 | 
					  <div id="theToast"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <!-- Snackbar.html gets rendered here -->
 | 
				
			||||||
 | 
					  <div id="theSnackbar"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- LoadingMask.html gets rendered here -->
 | 
					  <!-- LoadingMask.html gets rendered here -->
 | 
				
			||||||
  <div id="loading-mask" aria-hidden="true"></div>
 | 
					  <div id="loading-mask" aria-hidden="true"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										137
									
								
								src/routes/_components/snackbar/Snackbar.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								src/routes/_components/snackbar/Snackbar.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,137 @@
 | 
				
			||||||
 | 
					<section class="snackbar-modal {shown ? 'shown' : ''}"
 | 
				
			||||||
 | 
					     aria-live="assertive"
 | 
				
			||||||
 | 
					     aria-atomic="true"
 | 
				
			||||||
 | 
					     aria-hidden={!shown}
 | 
				
			||||||
 | 
					     aria-label="Alert"
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
					  <div class="snackbar-container">
 | 
				
			||||||
 | 
					    <span class="text">
 | 
				
			||||||
 | 
					      {text}
 | 
				
			||||||
 | 
					    </span>
 | 
				
			||||||
 | 
					    <div class="button-wrapper">
 | 
				
			||||||
 | 
					      <button class="button" on:click="onClick(event)">
 | 
				
			||||||
 | 
					        {buttonText}
 | 
				
			||||||
 | 
					      </button>
 | 
				
			||||||
 | 
					      <button class="button" aria-label="Close" on:click="close(event)">
 | 
				
			||||||
 | 
					        <SvgIcon className="close-snackbar-button" href="#fa-times" />
 | 
				
			||||||
 | 
					      </button>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</section>
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					  .snackbar-modal {
 | 
				
			||||||
 | 
					    position: fixed;
 | 
				
			||||||
 | 
					    bottom: 0;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    right: 0;
 | 
				
			||||||
 | 
					    transition: transform 333ms ease-in-out;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    z-index: 99000;
 | 
				
			||||||
 | 
					    transform: translateY(100%);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .snackbar-container {
 | 
				
			||||||
 | 
					    width: 562px; /* same as .main, minus 20px padding */
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    background: var(--toast-bg);
 | 
				
			||||||
 | 
					    padding: 10px 20px;
 | 
				
			||||||
 | 
					    font-size: 1.3em;
 | 
				
			||||||
 | 
					    color: var(--toast-text);
 | 
				
			||||||
 | 
					    border-radius: 4px 4px 0 0;
 | 
				
			||||||
 | 
					    border: 1px solid var(--toast-border);
 | 
				
			||||||
 | 
					    border-bottom: none;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .button-wrapper {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .text {
 | 
				
			||||||
 | 
					    flex: 1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  button {
 | 
				
			||||||
 | 
					    font-size: 1em;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					    color: var(--toast-anchor-color);
 | 
				
			||||||
 | 
					    text-transform: uppercase;
 | 
				
			||||||
 | 
					    font-weight: 500;
 | 
				
			||||||
 | 
					    background: var(--toast-bg);
 | 
				
			||||||
 | 
					    margin-left: 5px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  button:active {
 | 
				
			||||||
 | 
					    background: var(--toast-button-active);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  button:hover {
 | 
				
			||||||
 | 
					    background: var(--toast-button-hover);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .snackbar-modal.shown {
 | 
				
			||||||
 | 
					    transform: translateY(0);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  :global(.close-snackbar-button) {
 | 
				
			||||||
 | 
					    margin-top: 3px;
 | 
				
			||||||
 | 
					    width: 18px;
 | 
				
			||||||
 | 
					    height: 18px;
 | 
				
			||||||
 | 
					    fill: var(--toast-text);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @media (max-width: 767px) {
 | 
				
			||||||
 | 
					    .snackbar-container {
 | 
				
			||||||
 | 
					      width: 100%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					  import { doubleRAF } from '../../_utils/doubleRAF'
 | 
				
			||||||
 | 
					  import SvgIcon from '../SvgIcon.html'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export default {
 | 
				
			||||||
 | 
					    data: () => ({
 | 
				
			||||||
 | 
					      shown: false,
 | 
				
			||||||
 | 
					      text: '',
 | 
				
			||||||
 | 
					      buttonText: '',
 | 
				
			||||||
 | 
					      buttonAction: null
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    methods: {
 | 
				
			||||||
 | 
					      announce (text, buttonText, buttonAction) {
 | 
				
			||||||
 | 
					        this.set({ text, buttonText, buttonAction })
 | 
				
			||||||
 | 
					        doubleRAF(() => {
 | 
				
			||||||
 | 
					          this.set({ shown: true })
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      onClick (e) {
 | 
				
			||||||
 | 
					        e.preventDefault()
 | 
				
			||||||
 | 
					        e.stopPropagation()
 | 
				
			||||||
 | 
					        let { buttonAction } = this.get()
 | 
				
			||||||
 | 
					        if (buttonAction) {
 | 
				
			||||||
 | 
					          buttonAction()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.close()
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      close (e) {
 | 
				
			||||||
 | 
					        if (e) {
 | 
				
			||||||
 | 
					          e.preventDefault()
 | 
				
			||||||
 | 
					          e.stopPropagation()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        requestAnimationFrame(() => {
 | 
				
			||||||
 | 
					          this.set({
 | 
				
			||||||
 | 
					            buttonAction: null, // avoid memory leaks from the closure
 | 
				
			||||||
 | 
					            shown: false
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    components: {
 | 
				
			||||||
 | 
					      SvgIcon
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/routes/_components/snackbar/snackbar.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/routes/_components/snackbar/snackbar.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					import { importSnackbar } from '../../_utils/asyncModules'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let snackbar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const lazySnackbar = {
 | 
				
			||||||
 | 
					  async announce (text, buttonText, buttonAction) {
 | 
				
			||||||
 | 
					    if (!snackbar) {
 | 
				
			||||||
 | 
					      let Snackbar = await importSnackbar()
 | 
				
			||||||
 | 
					      if (!snackbar) {
 | 
				
			||||||
 | 
					        snackbar = new Snackbar({
 | 
				
			||||||
 | 
					          target: document.querySelector('#theSnackbar')
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        if (process.env.NODE_ENV !== 'production') {
 | 
				
			||||||
 | 
					          window.snackbar = snackbar // for debugging
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    snackbar.announce(text, buttonText, buttonAction)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { lazySnackbar as snackbar }
 | 
				
			||||||
| 
						 | 
					@ -47,3 +47,7 @@ export const importEmojiMart = () => import(
 | 
				
			||||||
export const importToast = () => import(
 | 
					export const importToast = () => import(
 | 
				
			||||||
  /* webpackChunkName: 'Toast.html' */ '../_components/toast/Toast.html'
 | 
					  /* webpackChunkName: 'Toast.html' */ '../_components/toast/Toast.html'
 | 
				
			||||||
  ).then(getDefault)
 | 
					  ).then(getDefault)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const importSnackbar = () => import(
 | 
				
			||||||
 | 
					  /* webpackChunkName: 'Snackbar.html' */ '../_components/snackbar/Snackbar.html'
 | 
				
			||||||
 | 
					  ).then(getDefault)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,11 @@
 | 
				
			||||||
import { toast } from '../_components/toast/toast'
 | 
					import { snackbar } from '../_components/snackbar/snackbar'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function onUpdateFound (registration) {
 | 
					function onUpdateFound (registration) {
 | 
				
			||||||
  const newWorker = registration.installing
 | 
					  const newWorker = registration.installing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  newWorker.addEventListener('statechange', async () => {
 | 
					  newWorker.addEventListener('statechange', async () => {
 | 
				
			||||||
    if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
 | 
					    if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
 | 
				
			||||||
      toast.say('App update available. Reload to update.')
 | 
					      snackbar.announce('App update available.', 'Reload', () => document.location.reload(true))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -66,6 +66,9 @@
 | 
				
			||||||
  --toast-bg: #{$toast-bg};
 | 
					  --toast-bg: #{$toast-bg};
 | 
				
			||||||
  --toast-border: #{$toast-border};
 | 
					  --toast-border: #{$toast-border};
 | 
				
			||||||
  --toast-text: #{$secondary-text-color};
 | 
					  --toast-text: #{$secondary-text-color};
 | 
				
			||||||
 | 
					  --toast-button-hover: #{lighten($toast-bg, 5%)};
 | 
				
			||||||
 | 
					  --toast-button-active: #{lighten($toast-bg, 10%)};
 | 
				
			||||||
 | 
					  --toast-anchor-color: #{lighten($anchor-color, 20%)};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  --mask-bg: #{$toast-bg};
 | 
					  --mask-bg: #{$toast-bg};
 | 
				
			||||||
  --mask-svg-fill: #{$secondary-text-color};
 | 
					  --mask-svg-fill: #{$secondary-text-color};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,4 +44,6 @@
 | 
				
			||||||
  --tab-bg-active: #{lighten($main-bg-color, 15%)};
 | 
					  --tab-bg-active: #{lighten($main-bg-color, 15%)};
 | 
				
			||||||
  --tab-bg-hover: #{lighten($main-bg-color, 1%)};
 | 
					  --tab-bg-hover: #{lighten($main-bg-color, 1%)};
 | 
				
			||||||
  --tab-bg-hover-non-selected: #{darken($main-bg-color, 1%)};
 | 
					  --tab-bg-hover-non-selected: #{darken($main-bg-color, 1%)};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  --toast-anchor-color: #{$anchor-color};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue