forked from cybrespace/pinafore
		
	add animation for navigation bar indicator (#257)
This commit is contained in:
		
							parent
							
								
									8f5a4aadca
								
							
						
					
					
						commit
						b7c90a4206
					
				
					 8 changed files with 200 additions and 53 deletions
				
			
		| 
						 | 
				
			
			@ -29,6 +29,8 @@
 | 
			
		|||
        firstTime = false
 | 
			
		||||
        this.refs.container.focus()
 | 
			
		||||
      }
 | 
			
		||||
      let { page } = this.get()
 | 
			
		||||
      this.store.set({currentPage: page})
 | 
			
		||||
    },
 | 
			
		||||
    store: () => store
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,37 +1,16 @@
 | 
			
		|||
<nav class="main-nav">
 | 
			
		||||
	<ul class="main-nav-ul">
 | 
			
		||||
		<li class="main-nav-li">
 | 
			
		||||
      <NavItem {page} name="home" href="/" svg="#pinafore-logo" label="Home" />
 | 
			
		||||
		</li>
 | 
			
		||||
		<li class="main-nav-li">
 | 
			
		||||
      <NavItem {page} name="notifications" href="/notifications" svg="#fa-bell" label="Notifications" />
 | 
			
		||||
		</li>
 | 
			
		||||
    {#if $pinnedPage === '/local'}
 | 
			
		||||
    {#each $navPages as navPage (navPage.name)}
 | 
			
		||||
      <li class="main-nav-li">
 | 
			
		||||
        <NavItem {page} name="local" href="/local" svg="#fa-users" label="Local" />
 | 
			
		||||
        <NavItem
 | 
			
		||||
          {page}
 | 
			
		||||
          name={navPage.name}
 | 
			
		||||
          href={navPage.href}
 | 
			
		||||
          svg={navPage.svg}
 | 
			
		||||
          label={navPage.label}
 | 
			
		||||
        />
 | 
			
		||||
      </li>
 | 
			
		||||
    {:elseif $pinnedPage === '/federated'}
 | 
			
		||||
      <li class="main-nav-li">
 | 
			
		||||
        <NavItem {page} name="federated" href="/federated" svg="#fa-globe" label="Federated" />
 | 
			
		||||
      </li>
 | 
			
		||||
    {:elseif $pinnedPage === '/favorites'}
 | 
			
		||||
      <li class="main-nav-li">
 | 
			
		||||
        <NavItem {page} name="favorites" href="/favorites" svg="#fa-star" label="Favorites" />
 | 
			
		||||
      </li>
 | 
			
		||||
    {:elseif $pinnedPage.startsWith('/lists/')}
 | 
			
		||||
      <li class="main-nav-li">
 | 
			
		||||
        <NavItem {page} name="lists" href={$pinnedPage} svg="#fa-bars" label={$pinnedListTitle} />
 | 
			
		||||
      </li>
 | 
			
		||||
    {/if}
 | 
			
		||||
		<li class="main-nav-li">
 | 
			
		||||
      <NavItem {page} name="community" href="/community" svg="#fa-comments" label="Community" />
 | 
			
		||||
		</li>
 | 
			
		||||
    <li class="main-nav-li">
 | 
			
		||||
      <NavItem {page} name="search" href="/search" svg="#fa-search" label="Search" />
 | 
			
		||||
    </li>
 | 
			
		||||
	  <li class="main-nav-li">
 | 
			
		||||
      <NavItem {page} name="settings" href="/settings" svg="#fa-gear" label="Settings" />
 | 
			
		||||
		</li>
 | 
			
		||||
    {/each}
 | 
			
		||||
	</ul>
 | 
			
		||||
</nav>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,28 +3,41 @@
 | 
			
		|||
   aria-current={selected}
 | 
			
		||||
   on:click="onClick(event)"
 | 
			
		||||
   {href} >
 | 
			
		||||
  {#if name === 'notifications'}
 | 
			
		||||
  <div class="nav-icon-and-label">
 | 
			
		||||
    {#if name === 'notifications'}
 | 
			
		||||
    <div class="nav-link-svg-wrapper">
 | 
			
		||||
      <svg class="nav-link-svg">
 | 
			
		||||
        <use xlink:href={svg} />
 | 
			
		||||
      </svg>
 | 
			
		||||
      {#if $hasNotifications}
 | 
			
		||||
        <span class="nav-link-notifications" aria-hidden="true">
 | 
			
		||||
      <span class="nav-link-notifications" aria-hidden="true">
 | 
			
		||||
          {$numberOfNotifications}
 | 
			
		||||
        </span>
 | 
			
		||||
      {/if}
 | 
			
		||||
    </div>
 | 
			
		||||
  {:else}
 | 
			
		||||
    {:else}
 | 
			
		||||
    <svg class="nav-link-svg">
 | 
			
		||||
      <use xlink:href={svg} />
 | 
			
		||||
    </svg>
 | 
			
		||||
  {/if}
 | 
			
		||||
  <span class="nav-link-label">{label}</span>
 | 
			
		||||
    {/if}
 | 
			
		||||
    <span class="nav-link-label">{label}</span>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="nav-indicator"
 | 
			
		||||
       nav-indicator-key={name}
 | 
			
		||||
       ref:indicator
 | 
			
		||||
  ></div>
 | 
			
		||||
</a>
 | 
			
		||||
<style>
 | 
			
		||||
  .main-nav-link {
 | 
			
		||||
    border-bottom: 1px solid var(--nav-a-border);
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .nav-icon-and-label {
 | 
			
		||||
    padding: 15px 20px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
| 
						 | 
				
			
			@ -62,18 +75,38 @@
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  .main-nav-link.selected {
 | 
			
		||||
    border-bottom: 1px solid var(--nav-a-selected-border);
 | 
			
		||||
    background: var(--nav-a-selected-bg);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .main-nav-link.selected:hover {
 | 
			
		||||
    border-bottom: 1px solid var(--nav-a-selected-border-hover);
 | 
			
		||||
    background: var(--nav-a-selected-bg-hover);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .nav-indicator {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 1px;
 | 
			
		||||
    background: var(--nav-a-border);
 | 
			
		||||
    transform-origin: left;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .nav-indicator.animate {
 | 
			
		||||
    transition: transform 333ms ease-in-out;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .main-nav-link:hover .nav-indicator {
 | 
			
		||||
    background: var(--nav-a-border-hover);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .main-nav-link.selected .nav-indicator {
 | 
			
		||||
    background: var(--nav-a-selected-border);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .main-nav-link.selected:hover .nav-indicator {
 | 
			
		||||
    background: var(--nav-a-selected-border-hover);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .main-nav-link:hover {
 | 
			
		||||
    background-color: var(--nav-a-bg-hover);
 | 
			
		||||
    border-bottom: 1px solid var(--nav-a-border-hover);
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +139,7 @@
 | 
			
		|||
      width: 25px;
 | 
			
		||||
      height: 25px;
 | 
			
		||||
    }
 | 
			
		||||
    .main-nav-link {
 | 
			
		||||
    .nav-icon-and-label {
 | 
			
		||||
      padding: 20px 0;
 | 
			
		||||
    }
 | 
			
		||||
    .nav-link-notifications {
 | 
			
		||||
| 
						 | 
				
			
			@ -118,8 +151,44 @@
 | 
			
		|||
<script>
 | 
			
		||||
  import { store } from '../_store/store'
 | 
			
		||||
  import { smoothScrollToTop } from '../_utils/smoothScrollToTop'
 | 
			
		||||
  import { on, emit } from '../_utils/eventBus'
 | 
			
		||||
  import { mark, stop } from '../_utils/marks'
 | 
			
		||||
  import { doubleRAF } from '../_utils/doubleRAF'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    oncreate () {
 | 
			
		||||
      let { name } = this.get()
 | 
			
		||||
      let indicator = this.refs.indicator
 | 
			
		||||
      on('animateNavPart1', this, ({fromPage, toPage}) => {
 | 
			
		||||
        if (fromPage !== name) {
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
        mark('animateNavPart1 gBCR')
 | 
			
		||||
        let fromRect = indicator.getBoundingClientRect()
 | 
			
		||||
        stop('animateNavPart1 gBCR')
 | 
			
		||||
        emit('animateNavPart2', {fromRect, fromPage, toPage})
 | 
			
		||||
      })
 | 
			
		||||
      on('animateNavPart2', this, ({fromPage, fromRect, toPage}) => {
 | 
			
		||||
        if (toPage !== name) {
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
        mark('animateNavPart2 gBCR')
 | 
			
		||||
        let toRect = indicator.getBoundingClientRect()
 | 
			
		||||
        stop('animateNavPart2 gBCR')
 | 
			
		||||
        let translateX = fromRect.left - toRect.left
 | 
			
		||||
        let scaleX = fromRect.width / toRect.width
 | 
			
		||||
        indicator.style.transform = `translateX(${translateX}px) scaleX(${scaleX})`
 | 
			
		||||
        let onTransitionEnd = () => {
 | 
			
		||||
          indicator.removeEventListener('transitionend', onTransitionEnd)
 | 
			
		||||
          indicator.classList.remove('animate')
 | 
			
		||||
        }
 | 
			
		||||
        indicator.addEventListener('transitionend', onTransitionEnd)
 | 
			
		||||
        doubleRAF(() => {
 | 
			
		||||
          indicator.classList.add('animate')
 | 
			
		||||
          indicator.style.transform = ''
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    store: () => store,
 | 
			
		||||
    computed: {
 | 
			
		||||
      selected: ({ page, name }) => page === name,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,9 @@
 | 
			
		|||
import { instanceComputations } from './instanceComputations'
 | 
			
		||||
import { timelineComputations } from './timelineComputations'
 | 
			
		||||
import { navComputations } from './navComputations'
 | 
			
		||||
 | 
			
		||||
export function computations (store) {
 | 
			
		||||
  instanceComputations(store)
 | 
			
		||||
  timelineComputations(store)
 | 
			
		||||
  navComputations(store)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,17 +47,4 @@ export function instanceComputations (store) {
 | 
			
		|||
    ['currentInstanceData'],
 | 
			
		||||
    (currentInstanceData) => currentInstanceData && currentInstanceData.access_token
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  store.compute(
 | 
			
		||||
    'pinnedListTitle',
 | 
			
		||||
    ['lists', 'pinnedPage'],
 | 
			
		||||
    (lists, pinnedPage) => {
 | 
			
		||||
      if (!pinnedPage.startsWith('/lists')) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      let listId = pinnedPage.split('/').slice(-1)[0]
 | 
			
		||||
      let list = lists.find(_ => _.id === listId)
 | 
			
		||||
      return list ? list.title : ''
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										85
									
								
								routes/_store/computations/navComputations.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								routes/_store/computations/navComputations.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,85 @@
 | 
			
		|||
export function navComputations (store) {
 | 
			
		||||
  store.compute(
 | 
			
		||||
    'pinnedListTitle',
 | 
			
		||||
    ['lists', 'pinnedPage'],
 | 
			
		||||
    (lists, pinnedPage) => {
 | 
			
		||||
      if (!pinnedPage.startsWith('/lists')) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      let listId = pinnedPage.split('/').slice(-1)[0]
 | 
			
		||||
      let list = lists.find(_ => _.id === listId)
 | 
			
		||||
      return list ? list.title : ''
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  store.compute(
 | 
			
		||||
    'navPages',
 | 
			
		||||
    ['pinnedPage', 'pinnedListTitle'],
 | 
			
		||||
    (pinnedPage, pinnedListTitle) => {
 | 
			
		||||
      let pinnedPageObject
 | 
			
		||||
      if (pinnedPage === '/federated') {
 | 
			
		||||
        pinnedPageObject = {
 | 
			
		||||
          name: 'federated',
 | 
			
		||||
          href: '/federated',
 | 
			
		||||
          svg: '#fa-globe',
 | 
			
		||||
          label: 'Federated'
 | 
			
		||||
        }
 | 
			
		||||
      } else if (pinnedPage === '/favorites') {
 | 
			
		||||
        pinnedPageObject = {
 | 
			
		||||
          name: 'favorites',
 | 
			
		||||
          href: '/favorites',
 | 
			
		||||
          svg: '#fa-star',
 | 
			
		||||
          label: 'Favorites'
 | 
			
		||||
        }
 | 
			
		||||
      } else if (pinnedPage.startsWith('/lists/')) {
 | 
			
		||||
        pinnedPageObject = {
 | 
			
		||||
          name: 'lists',
 | 
			
		||||
          href: pinnedPage,
 | 
			
		||||
          svg: '#fa-bars',
 | 
			
		||||
          label: pinnedListTitle
 | 
			
		||||
        }
 | 
			
		||||
      } else { // local
 | 
			
		||||
        pinnedPageObject = {
 | 
			
		||||
          name: 'local',
 | 
			
		||||
          href: '/local',
 | 
			
		||||
          svg: '#fa-users',
 | 
			
		||||
          label: 'Local'
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return [
 | 
			
		||||
        {
 | 
			
		||||
          name: 'home',
 | 
			
		||||
          href: '/',
 | 
			
		||||
          svg: '#pinafore-logo',
 | 
			
		||||
          label: 'Home'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: 'notifications',
 | 
			
		||||
          href: '/notifications',
 | 
			
		||||
          svg: '#fa-bell',
 | 
			
		||||
          label: 'Notifications'
 | 
			
		||||
        },
 | 
			
		||||
        pinnedPageObject,
 | 
			
		||||
        {
 | 
			
		||||
          name: 'community',
 | 
			
		||||
          href: '/community',
 | 
			
		||||
          svg: '#fa-comments',
 | 
			
		||||
          label: 'Community'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: 'search',
 | 
			
		||||
          href: '/search',
 | 
			
		||||
          svg: '#fa-search',
 | 
			
		||||
          label: 'Search'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: 'settings',
 | 
			
		||||
          href: '/settings',
 | 
			
		||||
          svg: '#fa-gear',
 | 
			
		||||
          label: 'Settings'
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								routes/_store/observers/navObservers.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								routes/_store/observers/navObservers.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
import { emit } from '../../_utils/eventBus'
 | 
			
		||||
 | 
			
		||||
export function navObservers (store) {
 | 
			
		||||
  function pageIsInNav (store, page) {
 | 
			
		||||
    let { navPages } = store.get()
 | 
			
		||||
    return !!navPages.find(_ => _.name === page)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  store.observe('currentPage', (currentPage, previousPage) => {
 | 
			
		||||
    if (currentPage && previousPage &&
 | 
			
		||||
        pageIsInNav(store, currentPage) &&
 | 
			
		||||
        pageIsInNav(store, previousPage)) {
 | 
			
		||||
      emit('animateNavPart1', {
 | 
			
		||||
        fromPage: previousPage,
 | 
			
		||||
        toPage: currentPage
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }, {
 | 
			
		||||
    init: false
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,10 +2,12 @@ import { instanceObservers } from './instanceObservers'
 | 
			
		|||
import { timelineObservers } from './timelineObservers'
 | 
			
		||||
import { notificationObservers } from './notificationObservers'
 | 
			
		||||
import { onlineObservers } from './onlineObservers'
 | 
			
		||||
import { navObservers } from './navObservers'
 | 
			
		||||
 | 
			
		||||
export function observers (store) {
 | 
			
		||||
  instanceObservers(store)
 | 
			
		||||
  timelineObservers(store)
 | 
			
		||||
  notificationObservers(store)
 | 
			
		||||
  onlineObservers(store)
 | 
			
		||||
  navObservers(store)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue