Browse Source

refactored grid with flex, displayed footer tabs, diplay results, toggle results display

Bachir Soussi Chiadmi 4 years ago
parent
commit
63f3c6409f

+ 146 - 12
assets/css/app.scss

@@ -1,7 +1,7 @@
 @import './base/reset';
 @import './base/variables';
 @import './base/colors';
-@import './base/grid';
+@import './base/grid-flex';
 @import './base/layout';
 @import './base/fonts';
 
@@ -10,6 +10,9 @@ body{
   color: #1a1a1a;
 }
 
+#root{
+}
+
 header[role="banner"]{
   div.wrapper{
     display: grid;
@@ -53,14 +56,67 @@ section[role="main-content"]{
 }
 footer[role="tools"]{
   #history{
-    // color: #1a1a1a;
     background-color: $or;
+    padding:1.2em $side-padding;
   }
   #results{
-    // color: #1a1a1a;
     background-color: $gris;
+    max-height: 50vh;
+    padding:1.2em $side-padding;
+    section.col-1{
+      .results-count{
+        font-size: 0.756em;
+      }
+    }
+    .results-list{
+      overflow-x: hidden;
+      .wrapper{
+        height:100%;
+        overflow-y: auto;
+        width:calc(100% + 1em);
+        padding-right: 1em;
+        >ul{
+          padding:0;
+          display: flex;
+          flex-direction: row;
+          flex-wrap: wrap;
+        }
+      }
+      li.result{
+        flex-basis: percentage(2/$default_sum);
+        height: 5.3em;
+        overflow: hidden;
+        margin-bottom: 1em;
+        padding-right: 0.7em;
+        box-sizing: border-box;
+      }
+      article.result.item{
+        header{
+          h1{
+            font-size: 0.882em;
+            font-weight: normal;
+            margin:0 0 0.5em 0;
+          }
+        }
+        .extract{
+          p{
+            font-size: 0.882em;
+            margin:0;
+          }
+          code{
+            background-color: lighten(desaturate($rouge,20%), 20%);
+          }
+        }
+      }
+    }
   }
   #footer-bottom{
+    padding:0 $side-padding;
+    background-color: $bleuroi;
+    &>*{
+      // disable grid gap
+      padding-right: 0;
+    }
     #footer-tabs{
       ul{
         padding:0; margin:0;
@@ -68,21 +124,25 @@ footer[role="tools"]{
         flex-direction: column;
         li{
           flex: 1 1 auto;
-          box-sizing: border-box;
-          padding:0.3em;
-          margin:0;
-          line-height: 0.6em;
-          height:2em;
-          &.history{
+          .wrapper{
+            box-sizing: border-box;
+            line-height: 0.6em;
+            height:2em;
+            width: calc(100% + $side-padding);
+            margin-left:-$side-padding;
+            padding:0.3em 0.5em 0.3em $side-padding;
+          }
+          &.history .wrapper{
             background-color: $or;
           }
-          &.results{
+          &.results .wrapper{
             background-color: $gris;
           }
           span{
             font-size: 0.693em;
             font-weight: 400;
             text-transform: uppercase;
+            cursor: pointer;
           }
         }
       }
@@ -90,8 +150,38 @@ footer[role="tools"]{
     #search{
       color: #fff;
       background-color: $bleuroi;
-      label[for="keys"]{
-        display: none;
+      form{
+        padding: 0.7em;
+        label[for="keys"]{
+          display: none;
+        }
+        input[type="text"]{
+          padding:0.3em;
+          font-size: 0.756em;
+          line-height: 1;
+          height:1em;
+          border:none;
+          border-radius: 2px;
+        }
+        // input[type="submit"]{
+        //   #submit-search{
+        //     border:none;
+        //
+        //   }
+        // }
+        span.mdi{
+          display: inline-block;
+          font-size: 1.2em;
+          line-height:1.1;
+          vertical-align:middle;
+          width:1.2em; height:1.2em;
+          border-radius: 0.6em;
+          background-color: #fff;
+          color: $bleuroi;
+          text-align: center;
+          font-weight: 700;
+          cursor: pointer;
+        }
       }
     }
   }
@@ -100,5 +190,49 @@ footer[role="tools"]{
     font-size: 0.756em;
     font-weight: 400;
     text-transform: uppercase;
+    padding:0;
   }
 }
+
+
+//  ___
+// |_ _|__ ___ _ _  ___
+//  | |/ _/ _ \ ' \(_-<
+// |___\__\___/_||_/__/
+
+span.mdi-close{
+  cursor: pointer;
+}
+
+@keyframes spin {
+    from {
+        transform:rotate(0deg);
+    }
+    to {
+        transform:rotate(360deg);
+    }
+}
+
+span.mdi-loading{
+  animation-name: spin;
+  animation-duration: 2000ms;
+  animation-iteration-count: infinite;
+  animation-timing-function: linear;
+}
+
+
+//  _                    _ _   _
+// | |_ _ _ __ _ _ _  __(_) |_(_)___ _ _  ___
+// |  _| '_/ _` | ' \(_-< |  _| / _ \ ' \(_-<
+//  \__|_| \__,_|_||_/__/_|\__|_\___/_||_/__/
+
+.fade-roll-enter-active{
+  transition: all .3s ease-in-out;
+}
+.fade-roll-leave-active{
+  transition: all .8s ease;
+}
+.fade-roll-enter, .fade-roll-leave-to{
+  opacity:0;
+  // height:1px;
+}

+ 86 - 0
assets/css/base/_grid-flex.scss

@@ -0,0 +1,86 @@
+// http://www.thesassway.com/intermediate/simple-grid-mixins
+
+
+@mixin row() {
+  display:flex;
+  flex-direction: row;
+  flex-wrap: nowrap;
+  align-items: stretch;
+  // &:after{
+  //   content:"";
+  //   clear:both;
+  //   display: block;
+  // }
+}
+
+.row{
+  @include row;
+}
+.row-rl{
+  @include row;
+}
+
+%col-reset {
+    box-sizing: border-box;
+}
+
+@mixin col($col, $offset: 0, $sum: $default_sum, $gap: $default_gap, $align: top) {
+  @extend %col-reset;
+  padding-left: $gap*$offset;
+  @if $col == $default_sum {
+    // if last col, then no gap
+    padding-right: 0;
+  }@else{
+    padding-right: $gap;
+  }
+  &:last-child{padding-right: 0;}
+
+  // no offset with flex ??
+  // margin-left: percentage(($col/$sum)*$offset);
+
+  // col width
+  flex-basis: percentage($col/$sum);
+}
+
+@for $c from 1 through $default_sum {
+  .col-#{$c} {
+      @include col($c);
+  }
+
+  // small
+  .small-col-#{$c} {
+    @media only screen and (max-width: $small-bp) {
+      @include col($c);
+    }
+  }
+
+  // medium
+  .med-col-#{$c} {
+    @media only screen and (min-width: $small-bp+1) and (max-width: $med-bp) {
+      @include col($c);
+    }
+  }
+
+  // large
+  .large-col-#{$c} {
+    @media only screen and (min-width: $med-bp+1) {
+      @include col($c);
+    }
+  }
+
+}
+
+@for $c from 1 through $default_sum - 1 {
+  @for $o from 1 through $default_sum - $c {
+    .col-#{$c}-offset-#{$o} {
+      @include col($c, $o);
+    }
+  }
+}
+
+// TODO: replace with align-self:flex-start or flex-end
+// .col.float-right{
+//   float: right;
+//   padding-right: 0;
+//   padding-left: $default_gap;
+// }

+ 4 - 2
assets/css/base/_layout.scss

@@ -1,3 +1,5 @@
+$side-padding:3em;
+
 body, html{
   position: relative;
   width: 100%;
@@ -24,12 +26,12 @@ body{
   header[role="banner"]{
     flex: 0 0 auto;
     @extend %layout-element;
-    padding:1em 1em 0 1em;
+    padding:1em $side-padding 0 $side-padding;
   }
   section[role="main-content"]{
     flex:1 1 auto;
     @extend %layout-element;
-    padding:0 1em 0 1em;
+    padding:0 $side-padding 0 $side-padding;
     overflow-y: auto;
     overflow-x: hidden;
   }

+ 2 - 2
assets/css/base/_variables.scss

@@ -1,8 +1,8 @@
 $base_font_size:16px;
 
 // grid
-$default_gap: 0;
-$default_sum: 12;
+$default_gap: 1em;
+$default_sum: 12; // total number of columns
 
 $small-bp:768px;
 $med-bp:1080px;

+ 7 - 2
src/App.vue

@@ -16,7 +16,7 @@
     </section>
     <footer role="tools">
       <History />
-      <Results />
+      <Results v-if="resultsOpened" />
       <div id="footer-bottom" class="row">
         <FooterTabs />
         <Search />
@@ -31,7 +31,7 @@ import History from './components/nav/History'
 import Results from './components/nav/Results'
 import Search from './components/nav/Search'
 import FooterTabs from './components/nav/FooterTabs'
-// import { mapState, mapActions } from 'vuex'
+import { mapState } from 'vuex'
 
 export default {
   metaInfo: {
@@ -46,6 +46,11 @@ export default {
     Results,
     Search,
     FooterTabs
+  },
+  computed: {
+    ...mapState({
+      resultsOpened: state => state.Search.opened
+    })
   }
 }
 </script>

+ 30 - 5
src/components/nav/FooterTabs.vue

@@ -2,21 +2,46 @@
   <div id="footer-tabs" class="col-1">
     <ul>
       <li class="history">
-        <span>Historique de consultation</span>
+        <div class="wrapper">
+          <span>Historique de consultation</span>
+        </div>
       </li>
       <li class="results">
-        <span>Resultas</span>
+        <div class="wrapper">
+          <span
+            v-if="resultsItems.length && !resultsOpened"
+            title="Ouvrir les resultats"
+            @click.prevent="openResults"
+            @keydown.enter.prevent="openResults"
+          >
+            Resultas
+          </span>
+        </div>
       </li>
     </ul>
   </div>
 </template>
 
 <script>
+
+import { mapState } from 'vuex'
+
 export default {
   name: 'FooterTabs',
-  data: () => ({
-
-  })
+  computed: {
+    resultsOpened: {
+      get () { return this.$store.state.Search.opened },
+      set (value) { this.$store.commit('Search/setOpened', value) }
+    },
+    ...mapState({
+      resultsItems: state => state.Search.results
+    })
+  },
+  methods: {
+    openResults () {
+      this.resultsOpened = true
+    }
+  }
 }
 </script>
 

+ 38 - 14
src/components/nav/Results.vue

@@ -1,16 +1,33 @@
 <template>
-  <div id="results">
-    <h2>Resultats</h2>
-    <h3 v-if="keys">{{ keys }}</h3>
-    <div class="results-list">
-      <ul v-if="results.length">
-        <li v-for="result in results" :key="result.uuid">
-          <ResultItem :result="result" />
-        </li>
-      </ul>
+  <transition name="fade-roll">
+    <div
+      v-if="opened"
+      id="results"
+      class="row"
+    >
+      <section class="col-1">
+        <h2>Resultats</h2>
+        <span class="results-count">{{ results.length }} resultat(s)</span>
+      </section>
+      <section class="col-10 results-list">
+        <div class="wrapper">
+          <ul v-if="results.length">
+            <li v-for="result in results" :key="result.uuid" class="result">
+              <ResultItem :result="result" />
+            </li>
+          </ul>
+        </div>
+      </section>
+      <section class="col-1 tools">
+        <span
+          class="mdi mdi-close"
+          title="close"
+          @click.prevent="close"
+          @keydown.enter.prevent="close"
+        />
+      </section>
     </div>
-
-  </div>
+  </transition>
 </template>
 
 <script>
@@ -23,14 +40,21 @@ export default {
   components: {
     ResultItem
   },
-  data: () => ({
-
-  }),
   computed: {
+    opened: {
+      get () { return this.$store.state.Search.opened },
+      set (value) { this.$store.commit('Search/setOpened', value) }
+    },
     ...mapState({
       keys: state => state.Search.keys,
       results: state => state.Search.results
     })
+  },
+  methods: {
+    close () {
+      console.log('clicked on close results')
+      this.opened = false
+    }
   }
 }
 </script>

+ 24 - 7
src/components/nav/Search.vue

@@ -1,28 +1,42 @@
 <template>
   <div id="search" class="col-11">
-    <form class="" action="index.html" method="post">
-      <label htmlFor="keys">Search</label>
+    <form class="search-form">
+      <label for="keys">Search</label>
       <input
         id="keys"
         v-model="keys"
         type="text"
         placeholder="search"
+        @keydown.enter.prevent="submit"
       >
-      <input
-        id="search"
+      <span
+        v-if="!isloading"
+        class="mdi mdi-magnify"
+        title="rechercher"
+        @click.prevent="submit"
+        @keydown.enter.prevent="submit"
+      />
+      <span
+        v-else
+        class="mdi mdi-loading"
+        title="chargement"
+      />
+      <!-- <input
+        id="submit-search"
         type="submit"
         name="search"
         value="Search"
+        class="mdi mdi-magnify"
         @click.prevent="submit"
         @keyup.enter="submit"
-      >
+      > -->
     </form>
   </div>
 </template>
 
 <script>
 
-import { mapActions } from 'vuex'
+import { mapActions, mapState } from 'vuex'
 
 export default {
   name: 'Search',
@@ -33,7 +47,10 @@ export default {
     keys: {
       get () { return this.$store.state.Search.keys },
       set (value) { this.$store.commit('Search/setKeys', value) }
-    }
+    },
+    ...mapState({
+      isloading: state => state.Search.isloading
+    })
   },
   methods: {
     ...mapActions({

+ 13 - 1
src/store/modules/search.js

@@ -7,7 +7,9 @@ export default {
   // initial state
   state: {
     keys: '',
-    results: []
+    results: [],
+    isloading: false,
+    opened: false
   },
 
   // getters
@@ -20,6 +22,12 @@ export default {
     },
     setResults (state, content) {
       state.results = content
+    },
+    setIsloading (state, isloading) {
+      state.isloading = isloading
+    },
+    setOpened (state, opened) {
+      state.opened = opened
     }
   },
 
@@ -27,6 +35,7 @@ export default {
   actions: {
     getResults ({ dispatch, commit, state }) {
       console.log('getResults', state.keys)
+      commit('setIsloading', true)
       let params = {
         search: state.keys
       }
@@ -35,10 +44,13 @@ export default {
       return REST.get(`/search?` + q)
         .then(({ data }) => {
           console.log('search REST: data', data)
+          commit('setIsloading', false)
+          commit('setOpened', true)
           commit('setResults', data.content)
         })
         .catch((error) => {
           console.warn('Issue with search', error)
+          commit('setIsloading', false)
           Promise.reject(error)
         })
     }