Add mobile menu/layout

This commit is contained in:
Daniel J. Summers 2021-09-16 21:53:34 -04:00
parent 98659f6cfc
commit bc0531a9fc
10 changed files with 208 additions and 92 deletions

View File

@ -11,6 +11,7 @@
"@mdi/js": "^5.9.55", "@mdi/js": "^5.9.55",
"@vuelidate/core": "^2.0.0-alpha.24", "@vuelidate/core": "^2.0.0-alpha.24",
"@vuelidate/validators": "^2.0.0-alpha.21", "@vuelidate/validators": "^2.0.0-alpha.21",
"@vueuse/core": "^6.3.3",
"bootstrap": "^5.1.0", "bootstrap": "^5.1.0",
"core-js": "^3.16.3", "core-js": "^3.16.3",
"date-fns": "^2.23.0", "date-fns": "^2.23.0",
@ -3765,6 +3766,53 @@
"vue-demi": "^0.11.3" "vue-demi": "^0.11.3"
} }
}, },
"node_modules/@vueuse/core": {
"version": "6.3.3",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-6.3.3.tgz",
"integrity": "sha512-qa/0WYqcvqFKQmlkgsLGlXBrYcQeUi3fzHMIaxsD/lO/zm0IWBSN8CTFu91LwER5qNYs4DGhU5pu7jOdrTzAIQ==",
"dependencies": {
"@vueuse/shared": "6.3.3",
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.1.0",
"vue": "^2.6.0 || ^3.2.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"vue": {
"optional": true
}
}
},
"node_modules/@vueuse/core/node_modules/@vueuse/shared": {
"version": "6.3.3",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-6.3.3.tgz",
"integrity": "sha512-2+YPRhFNUXEhhvKNTWBtNU6hGkft9+mfYSVjI4hZu2U8KDbNNKF/215lBPzMYI2twScDtPsAssQ+vu5t9PBy0g==",
"dependencies": {
"vue-demi": "*"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.1.0",
"vue": "^2.6.0 || ^3.2.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"vue": {
"optional": true
}
}
},
"node_modules/@webassemblyjs/ast": { "node_modules/@webassemblyjs/ast": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@ -21238,6 +21286,25 @@
"vue-demi": "^0.11.3" "vue-demi": "^0.11.3"
} }
}, },
"@vueuse/core": {
"version": "6.3.3",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-6.3.3.tgz",
"integrity": "sha512-qa/0WYqcvqFKQmlkgsLGlXBrYcQeUi3fzHMIaxsD/lO/zm0IWBSN8CTFu91LwER5qNYs4DGhU5pu7jOdrTzAIQ==",
"requires": {
"@vueuse/shared": "6.3.3",
"vue-demi": "*"
},
"dependencies": {
"@vueuse/shared": {
"version": "6.3.3",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-6.3.3.tgz",
"integrity": "sha512-2+YPRhFNUXEhhvKNTWBtNU6hGkft9+mfYSVjI4hZu2U8KDbNNKF/215lBPzMYI2twScDtPsAssQ+vu5t9PBy0g==",
"requires": {
"vue-demi": "*"
}
}
}
},
"@webassemblyjs/ast": { "@webassemblyjs/ast": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",

View File

@ -13,6 +13,7 @@
"@mdi/js": "^5.9.55", "@mdi/js": "^5.9.55",
"@vuelidate/core": "^2.0.0-alpha.24", "@vuelidate/core": "^2.0.0-alpha.24",
"@vuelidate/validators": "^2.0.0-alpha.21", "@vuelidate/validators": "^2.0.0-alpha.21",
"@vueuse/core": "^6.3.3",
"bootstrap": "^5.1.0", "bootstrap": "^5.1.0",
"core-js": "^3.16.3", "core-js": "^3.16.3",
"date-fns": "^2.23.0", "date-fns": "^2.23.0",

View File

@ -1,15 +1,15 @@
<template lang="pug"> <template lang="pug">
form.container form.container
.row .row
.col.col-xs-12.col-sm-6.col-md-4.col-lg-3 .col-xs-12.col-sm-6.col-md-4.col-lg-3
continent-list(v-model="criteria.continentId" topLabel="Any" @update:modelValue="updateContinent") continent-list(v-model="criteria.continentId" topLabel="Any" @update:modelValue="updateContinent")
.col.col-xs-12.col-sm-6.col-lg-3 .col-xs-12.col-sm-6.col-lg-3
.form-floating .form-floating
input.form-control(type="text" id="regionSearch" placeholder="(free-form text)" :value="criteria.region" input.form-control(type="text" id="regionSearch" placeholder="(free-form text)" :value="criteria.region"
@input="updateValue('region', $event.target.value)") @input="updateValue('region', $event.target.value)")
label(for="regionSearch") Region label(for="regionSearch") Region
.form-text (free-form text) .form-text (free-form text)
.col.col-xs-12.col-sm-6.col-offset-md-2.col-lg-3.col-offset-lg-0 .col-xs-12.col-sm-6.col-offset-md-2.col-lg-3.col-offset-lg-0
label.jjj-label Remote Work Opportunity? label.jjj-label Remote Work Opportunity?
br br
.form-check.form-check-inline .form-check.form-check-inline
@ -24,13 +24,13 @@ form.container
input.form-check-input(type="radio" id="remoteNo" name="remoteWork" :checked="criteria.remoteWork === 'no'" input.form-check-input(type="radio" id="remoteNo" name="remoteWork" :checked="criteria.remoteWork === 'no'"
@click="updateValue('remoteWork', 'no')") @click="updateValue('remoteWork', 'no')")
label.form-check-label(for="remoteNo") No label.form-check-label(for="remoteNo") No
.col.col-xs-12.col-sm-6.col-lg-3 .col-xs-12.col-sm-6.col-lg-3
.form-floating .form-floating
input.form-control(type="text" id="textSearch" placeholder="(free-form text)" :value="criteria.text" input.form-control(type="text" id="textSearch" placeholder="(free-form text)" :value="criteria.text"
@input="updateValue('text', $event.target.value)") @input="updateValue('text', $event.target.value)")
label(for="textSearch") Job Listing Text label(for="textSearch") Job Listing Text
.form-text (free-form text) .form-text (free-form text)
.row: .col.col-xs-12 .row: .col
br br
button.btn.btn-outline-primary(type="submit" @click.prevent="$emit('search')") Search button.btn.btn-outline-primary(type="submit" @click.prevent="$emit('search')") Search
</template> </template>

View File

@ -0,0 +1,82 @@
<template lang="pug">
nav
template(v-if="isLoggedOn")
router-link(to="/citizen/dashboard" @click="hide") #[icon(:icon="mdiViewDashboardVariant")]&nbsp; Dashboard
router-link(to="/help-wanted" @click="hide") #[icon(:icon="mdiNewspaperVariantMultipleOutline")]&nbsp; Help Wanted!
router-link(to="/profile/search" @click="hide") #[icon(:icon="mdiViewListOutline")]&nbsp; Employment Profiles
router-link(to="/success-story/list" @click="hide") #[icon(:icon="mdiThumbUp")]&nbsp; Success Stories
.separator
router-link(to="/listings/mine" @click="hide") #[icon(:icon="mdiSignText")]&nbsp; My Job Listings
router-link(to="/citizen/profile" @click="hide") #[icon(:icon="mdiPencil")]&nbsp; My Employment Profile
.separator
router-link(to="/citizen/log-off" @click="hide") #[icon(:icon="mdiLogoutVariant")]&nbsp; Log Off
template(v-else)
router-link(to="/" @click="hide") #[icon(:icon="mdiHome")]&nbsp; Home
router-link(to="/profile/seeking" @click="hide") #[icon(:icon="mdiViewListOutline")]&nbsp; Job Seekers
router-link(to="/citizen/log-on" @click="hide") #[icon(:icon="mdiLoginVariant")]&nbsp; Log On
router-link(to="/how-it-works" @click="hide") #[icon(:icon="mdiHelpCircleOutline")]&nbsp; How It Works
</template>
<script setup lang="ts">
import { computed } from "vue"
import { useRouter } from "vue-router"
import { Offcanvas } from "bootstrap"
import { useStore } from "@/store"
import {
mdiHelpCircleOutline,
mdiHome,
mdiLoginVariant,
mdiLogoutVariant,
mdiNewspaperVariantMultipleOutline,
mdiPencil,
mdiSignText,
mdiThumbUp,
mdiViewDashboardVariant,
mdiViewListOutline
} from "@mdi/js"
const store = useStore()
const router = useRouter()
/** Whether a user is logged in or not */
const isLoggedOn = computed(() => store.state.user !== undefined)
/** The current mobile menu */
const menu = computed(() => {
const elt = document.getElementById("mobileMenu")
return elt ? Offcanvas.getOrCreateInstance(elt) : undefined
})
/** Hide the offcanvas menu (if it exists) when a link is clicked */
const hide = () => { if (menu.value) menu.value.hide() }
</script>
<style lang="sass" scoped>
path
fill: white
path:hover
fill: black
a:link, a:visited
text-decoration: none
color: white
// font-weight: 500
nav > a
display: block
width: 100%
border-radius: .25rem
padding: .5rem
margin: .5rem 0
font-size: 1rem
> i
vertical-align: top
margin-right: 1rem
&.router-link-exact-active
background-color: rgba(255, 255, 255, .2)
&:hover
background-color: rgba(255, 255, 255, .5)
color: black
text-decoration: none
nav > div.separator
border-bottom: solid 1px rgba(255, 255, 255, .75)
height: 1px
</style>

View File

@ -1,88 +1,41 @@
<template lang="pug"> <template lang="pug">
aside.collapse.show.p-3 #mobileMenu.offcanvas.offcanvas-end(v-if="showMobileMenu" tabindex="-1" aria-labelledby="mobileMenuLabel")
.offcanvas-header
h5#mobileMenuLabel Menu
button.btn-close.text-reset(type="button" data-bs-dismiss="offcanvas" aria-label="Close")
.offcanvas-body: app-links
aside.collapse.show.p-3(v-else)
p.home-link.pb-3: router-link(to="/") Jobs, Jobs, Jobs p.home-link.pb-3: router-link(to="/") Jobs, Jobs, Jobs
p &nbsp; p &nbsp;
nav app-links
template(v-if="isLoggedOn")
router-link(to="/citizen/dashboard") #[icon(:icon="mdiViewDashboardVariant")]&nbsp; Dashboard
router-link(to="/help-wanted") #[icon(:icon="mdiNewspaperVariantMultipleOutline")]&nbsp; Help Wanted!
router-link(to="/profile/search") #[icon(:icon="mdiViewListOutline")]&nbsp; Employment Profiles
router-link(to="/success-story/list") #[icon(:icon="mdiThumbUp")]&nbsp; Success Stories
.separator
router-link(to="/listings/mine") #[icon(:icon="mdiSignText")]&nbsp; My Job Listings
router-link(to="/citizen/profile") #[icon(:icon="mdiPencil")]&nbsp; My Employment Profile
.separator
router-link(to="/citizen/log-off") #[icon(:icon="mdiLogoutVariant")]&nbsp; Log Off
template(v-else)
router-link(to="/") #[icon(:icon="mdiHome")]&nbsp; Home
router-link(to="/profile/seeking") #[icon(:icon="mdiViewListOutline")]&nbsp; Job Seekers
router-link(to="/citizen/log-on") #[icon(:icon="mdiLoginVariant")]&nbsp; Log On
router-link(to="/how-it-works") #[icon(:icon="mdiHelpCircleOutline")]&nbsp; How It Works
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue" import { useBreakpoints, breakpointsBootstrapV5 } from "@vueuse/core"
import { useStore } from "@/store" import AppLinks from "./AppLinks.vue"
import {
mdiHelpCircleOutline,
mdiHome,
mdiLoginVariant,
mdiLogoutVariant,
mdiNewspaperVariantMultipleOutline,
mdiPencil,
mdiSignText,
mdiThumbUp,
mdiViewDashboardVariant,
mdiViewListOutline
} from "@mdi/js"
const store = useStore() const breakpoints = useBreakpoints(breakpointsBootstrapV5)
/** Whether a user is logged in or not */ /** Whether the mobile menu or the desktop menu should be shown */
const isLoggedOn = computed(() => store.state.user !== undefined) const showMobileMenu = breakpoints.smaller("md")
</script> </script>
<style lang="sass" scoped> <style lang="sass" scoped>
aside aside,
#mobileMenu
background-image: linear-gradient(180deg, darkgreen 0%, green 70%) background-image: linear-gradient(180deg, darkgreen 0%, green 70%)
color: white color: white
font-size: 1.2rem font-size: 1.2rem
aside
min-height: 100vh min-height: 100vh
width: 250px width: 250px
min-width: 250px min-width: 250px
position: sticky position: sticky
top: 0 top: 0
path
fill: white
path:hover
fill: black
a:link, a:visited
text-decoration: none
color: white
// font-weight: 500
.home-link .home-link
font-size: 1.2rem font-size: 1.2rem
text-align: center text-align: center
background-color: rgba(0, 0, 0, .4) background-color: rgba(0, 0, 0, .4)
margin: -1rem margin: -1rem
padding: 1rem padding: 1rem
nav > a
display: block
width: 100%
border-radius: .25rem
padding: .5rem
margin: .5rem 0
font-size: 1rem
> i
vertical-align: top
margin-right: 1rem
&.router-link-exact-active
background-color: rgba(255, 255, 255, .2)
&:hover
background-color: rgba(255, 255, 255, .5)
color: black
text-decoration: none
nav > div.separator
border-bottom: solid 1px rgba(255, 255, 255, .75)
height: 1px
</style> </style>

View File

@ -1,9 +1,9 @@
<template lang="pug"> <template lang="pug">
nav.navbar.navbar-dark.mobile nav.navbar.navbar-dark(v-if="showMobileHeader")
span.navbar-text: router-link(to="/") Jobs, Jobs, Jobs span.navbar-text: router-link(to="/") Jobs, Jobs, Jobs
button.btn(data-bs-toggle="offcanvas" data-bs-target="#mobileMenu" aria-controls="mobileMenu") button.btn(data-bs-toggle="offcanvas" data-bs-target="#mobileMenu" aria-controls="mobileMenu")
icon(:icon="mdiMenu") icon(:icon="mdiMenu")
nav.navbar.navbar-light.bg-light.wide nav.navbar.navbar-light.bg-light(v-else)
span &nbsp; span &nbsp;
span.navbar-text. span.navbar-text.
(&hellip;and Jobs &ndash; #[audio-clip(clip="pelosi-jobs") Let&rsquo;s Vote for Jobs!]) (&hellip;and Jobs &ndash; #[audio-clip(clip="pelosi-jobs") Let&rsquo;s Vote for Jobs!])
@ -11,24 +11,28 @@ nav.navbar.navbar-light.bg-light.wide
<script setup lang="ts"> <script setup lang="ts">
import { mdiMenu } from "@mdi/js" import { mdiMenu } from "@mdi/js"
import { useBreakpoints, breakpointsBootstrapV5 } from "@vueuse/core"
import AudioClip from "@/components/AudioClip.vue" import AudioClip from "@/components/AudioClip.vue"
const breakpoints = useBreakpoints(breakpointsBootstrapV5)
/** Whether to show the mobile or desktop header */
const showMobileHeader = breakpoints.smaller("md")
</script> </script>
<style lang="sass" scoped> <style lang="sass" scoped>
.mobile .navbar-dark
background-image: linear-gradient(0deg, green 0%, darkgreen 70%) background-image: linear-gradient(0deg, green 0%, darkgreen 70%)
padding-left: 1rem
padding-right: 1rem
button button
padding: 0 1rem 0 0 padding: 0
.navbar-text .navbar-text
font-weight: bold font-weight: bold
color: white color: white
.wide .navbar-light
display: none
@media (min-width: 768px)
.wide
display: flex
.mobile
display: none
.navbar-text .navbar-text
font-style: italic font-style: italic
padding: 0 1rem 0 0
</style> </style>

View File

@ -1,15 +1,15 @@
<template lang="pug"> <template lang="pug">
form.container form.container
.row .row
.col.col-xs-12.col-sm-6.col-md-4.col-lg-3 .col-xs-12.col-sm-6.col-md-4.col-lg-3
continent-list(v-model="criteria.continentId" topLabel="Any" @update:modelValue="updateContinent") continent-list(v-model="criteria.continentId" topLabel="Any" @update:modelValue="updateContinent")
.col.col-xs-12.col-sm-6.col-md-4.col-lg-3 .col-xs-12.col-sm-6.col-md-4.col-lg-3
.form-floating .form-floating
input.form-control.form-control-sm(type="text" id="region" placeholder="(free-form text)" input.form-control.form-control-sm(type="text" id="region" placeholder="(free-form text)"
:value="criteria.region" @input="updateValue('region', $event.target.value)") :value="criteria.region" @input="updateValue('region', $event.target.value)")
label(for="region") Region label(for="region") Region
.form-text (free-form text) .form-text (free-form text)
.col.col-xs-12.col-sm-6.col-offset-md-2.col-lg-3.col-offset-lg-0 .col-xs-12.col-sm-6.col-offset-md-2.col-lg-3.col-offset-lg-0
label.jjj-label Seeking Remote Work? label.jjj-label Seeking Remote Work?
br br
.form-check.form-check-inline .form-check.form-check-inline
@ -24,13 +24,13 @@ form.container
input.form-check-input(type="radio" id="remoteNo" name="remoteWork" :checked="criteria.remoteWork === 'no'" input.form-check-input(type="radio" id="remoteNo" name="remoteWork" :checked="criteria.remoteWork === 'no'"
@click="updateValue('remoteWork', 'no')") @click="updateValue('remoteWork', 'no')")
label.form-check-label(for="remoteNo") No label.form-check-label(for="remoteNo") No
.col.col-xs-12.col-sm-6.col-lg-3 .col-xs-12.col-sm-6.col-lg-3
.form-floating .form-floating
input.form-control.form-control-sm(type="text" id="skillSearch" placeholder="(free-form text)" input.form-control.form-control-sm(type="text" id="skillSearch" placeholder="(free-form text)"
:value="criteria.skill" @input="updateValue('skill', $event.target.value)") :value="criteria.skill" @input="updateValue('skill', $event.target.value)")
label(for="skillSearch") Skill label(for="skillSearch") Skill
.form-text (free-form text) .form-text (free-form text)
.row: .col.col-xs-12 .row: .col
br br
button.btn.btn-outline-primary(type="submit" @click.prevent="$emit('search')") Search button.btn.btn-outline-primary(type="submit" @click.prevent="$emit('search')") Search
</template> </template>

View File

@ -1,9 +1,9 @@
<template lang="pug"> <template lang="pug">
form.container form.container
.row .row
.col.col-xs-12.col-sm-6.col-md-4.col-lg-3 .col-xs-12.col-sm-6.col-md-4.col-lg-3
continent-list(v-model="criteria.continentId" topLabel="Any" @update:modelValue="updateContinent") continent-list(v-model="criteria.continentId" topLabel="Any" @update:modelValue="updateContinent")
.col.col-xs-12.col-sm-6.col-offset-md-2.col-lg-3.col-offset-lg-0 .col-xs-12.col-sm-6.col-offset-md-2.col-lg-3.col-offset-lg-0
label.jjj-label Seeking Remote Work? label.jjj-label Seeking Remote Work?
br br
.form-check.form-check-inline .form-check.form-check-inline
@ -18,19 +18,19 @@ form.container
input.form-check-input(type="radio" id="remoteNo" name="remoteWork" :checked="criteria.remoteWork === 'no'" input.form-check-input(type="radio" id="remoteNo" name="remoteWork" :checked="criteria.remoteWork === 'no'"
@click="updateValue('remoteWork', 'no')") @click="updateValue('remoteWork', 'no')")
label.form-check-label(for="remoteNo") No label.form-check-label(for="remoteNo") No
.col.col-xs-12.col-sm-6.col-lg-3 .col-xs-12.col-sm-6.col-lg-3
.form-floating .form-floating
input.form-control(type="text" id="skillSearch" placeholder="(free-form text)" :value="criteria.skill" input.form-control(type="text" id="skillSearch" placeholder="(free-form text)" :value="criteria.skill"
@input="updateValue('skill', $event.target.value)") @input="updateValue('skill', $event.target.value)")
label(for="skillSearch") Skill label(for="skillSearch") Skill
.form-text (free-form text) .form-text (free-form text)
.col.col-xs-12.col-sm-6.col-lg-3 .col-xs-12.col-sm-6.col-lg-3
.form-floating .form-floating
input.form-control(type="text" id="bioSearch" placeholder="(free-form text)" :value="criteria.bioExperience" input.form-control(type="text" id="bioSearch" placeholder="(free-form text)" :value="criteria.bioExperience"
@input="updateValue('bioExperience', $event.target.value)") @input="updateValue('bioExperience', $event.target.value)")
label(for="bioSearch") Bio / Experience label(for="bioSearch") Bio / Experience
.form-text (free-form text) .form-text (free-form text)
.row: .col.col-xs-12 .row: .col
br br
button.btn.btn-outline-primary(type="submit" @click.prevent="$emit('search')") Search button.btn.btn-outline-primary(type="submit" @click.prevent="$emit('search')") Search
</template> </template>

View File

@ -1,15 +1,15 @@
<template lang="pug"> <template lang="pug">
.row.pb-3 .row.pb-3
.col.col-xs-2.col-md-1.align-self-center .col-xs-2.col-md-1.align-self-center
button.btn.btn-sm.btn-outline-danger.rounded-pill(title="Delete" @click.prevent="$emit('remove')") &nbsp;&minus;&nbsp; button.btn.btn-sm.btn-outline-danger.rounded-pill(title="Delete" @click.prevent="$emit('remove')") &nbsp;&minus;&nbsp;
.col.col-xs-10.col-md-6 .col-xs-10.col-md-6
.form-floating .form-floating
input.form-control(type="text" :id="`skillDesc${skill.id}`" maxlength="100" input.form-control(type="text" :id="`skillDesc${skill.id}`" maxlength="100"
placeholder="A skill (language, design technique, process, etc.)" :value="skill.description" placeholder="A skill (language, design technique, process, etc.)" :value="skill.description"
@input="updateValue('description', $event.target.value)") @input="updateValue('description', $event.target.value)")
label.jjj-label(:for="`skillDesc${skill.id}`") Skill label.jjj-label(:for="`skillDesc${skill.id}`") Skill
.form-text A skill (language, design technique, process, etc.) .form-text A skill (language, design technique, process, etc.)
.col.col-xs-12.col-md-5 .col-xs-12.col-md-5
.form-floating .form-floating
input.form-control(type="text" :id="`skillNotes${skill.id}`" maxlength="100" input.form-control(type="text" :id="`skillNotes${skill.id}`" maxlength="100"
placeholder="A further description of the skill (100 characters max)" :value="skill.notes" placeholder="A further description of the skill (100 characters max)" :value="skill.notes"

View File

@ -2,5 +2,14 @@ module.exports = {
transpileDependencies: [ transpileDependencies: [
'vuetify' 'vuetify'
], ],
outputDir: '../Api/wwwroot' outputDir: '../Api/wwwroot',
configureWebpack: {
module: {
rules: [{
test: /\.mjs$/,
include: /node_modules/,
type: "javascript/auto"
}]
}
}
} }