Skip to content

Commit

Permalink
#74 introduced toolbar-header component allowing to use list-mode-swi…
Browse files Browse the repository at this point in the history
…tch and search button in toolbar
  • Loading branch information
fcamblor committed Aug 30, 2024
1 parent 4894d7f commit 943296d
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 93 deletions.
34 changes: 24 additions & 10 deletions mobile/src/components/ui/ListModeSwitch.vue
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
<template>
<!-- TODO #74 Dev btn and custom option / icons depending on context -->
<ion-segment value="buttons" class="listModesSwitch">
<ion-segment-button class="listModesSwitch-button" value="default" :aria-label="LL.Big_list_mode()">
<ion-icon :icon="albums" aria-hidden="true"></ion-icon>
</ion-segment-button>
<ion-segment-button class="listModesSwitch-button" value="segment" :aria-label="LL.Compact_list_mode()">
<ion-icon :icon="list" aria-hidden="true"></ion-icon>
<ion-segment :value="selectedModeRef" class="listModesSwitch">
<ion-segment-button v-for="mode in modes" :key="mode.id" class="listModesSwitch-button"
:value="mode.id" :aria-label="mode.label"
@click="() => updateSelectedModeTo(mode.id)">
<ion-icon :icon="mode.icon" aria-hidden="true"></ion-icon>
</ion-segment-button>
</ion-segment>
</template>

<script setup lang="ts">
import {albums, list} from "ionicons/icons";
import {IonSegment, IonSegmentButton} from "@ionic/vue";
import {typesafeI18n} from "@/i18n/i18n-vue";
import {PropType, ref} from "vue";
const props = defineProps({
modes: {
required: true,
type: Array as PropType<Array<{id: string, icon: string, label: string, preSelected?: boolean}>>,
},
})
const $emits = defineEmits<{
(e: 'mode-updated', updatedModeId: string, previousModeId: string|undefined): void
}>()
const { LL } = typesafeI18n()
const selectedModeRef = ref<string|undefined>(props.modes?.find(mode => !!mode.preSelected)?.id)
function updateSelectedModeTo(updatedModeId: string) {
const previousModeId = selectedModeRef.value;
selectedModeRef.value = updatedModeId;
$emits('mode-updated', updatedModeId, previousModeId);
}
</script>

<style lang="scss">
Expand Down
104 changes: 104 additions & 0 deletions mobile/src/components/ui/ToolbarHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<template>
<ion-header class="toolbarHeader">
<ion-toolbar>
<ion-title slot="start">{{ title }}</ion-title>

<transition name="searchBar" v-if="searchEnabled">
<div v-if="searchFieldDisplayed" class="search-input">
<ion-input :size="10" ref="$searchInput"
:debounce="300"
:placeholder="`${LL.Search()}...`"
@ionInput="(ev) => $emits('search-terms-updated', ''+ev.target.value)"
/>
<ion-icon class="iconInput" src="/assets/icons/line/search-line.svg"></ion-icon>
<ion-button shape="round" size="small" fill="outline" @click="toggleSearchField()"
:aria-label="LL.Search_close()">
<ion-icon src="/assets/icons/line/close-line.svg"></ion-icon>
</ion-button>
</div>
</transition>

<div class="toolbarHeader-actions" slot="end">
<ListModeSwitch v-if="modes" :modes="modes" @mode-updated="(updatedModeId, previousModeId) => $emits('mode-updated', updatedModeId, previousModeId)"></ListModeSwitch>
<slot />
<ion-button slot="end" shape="round" size="small" @click="toggleSearchField()"
:aria-label="LL.Search()" v-if="searchEnabled">
<ion-icon src="/assets/icons/line/search-line.svg"></ion-icon>
</ion-button>
</div>
</ion-toolbar>
</ion-header>
</template>

<script setup lang="ts">
import {IonButton, IonInput} from "@ionic/vue";
import {typesafeI18n} from "@/i18n/i18n-vue";
import ListModeSwitch from "@/components/ui/ListModeSwitch.vue";
import {nextTick, PropType} from "vue";
import {isRefDefined, managedRef as ref} from "@/views/vue-utils";
const props = defineProps({
title: {
required: true,
type: String,
},
searchEnabled: {
required: false,
type: Boolean,
default: false
},
modes: {
required: false,
type: Array as PropType<Array<{id: string, icon: string, label: string}>>,
},
})
const $emits = defineEmits<{
(e: 'search-terms-updated', value: string): void,
(e: 'mode-updated', updatedModeId: string, previousModeId: string|undefined): void,
}>()
const { LL } = typesafeI18n()
const searchFieldDisplayed = ref(false);
const searchTermsRef = ref<string|undefined>(undefined);
const $searchInput = ref<{ $el: HTMLIonInputElement }|undefined>(undefined);
async function toggleSearchField() {
searchFieldDisplayed.value = !searchFieldDisplayed.value
if(searchFieldDisplayed.value) {
await nextTick(); // Wait for Vue to update the DOM
if(isRefDefined($searchInput)) {
setTimeout(() => $searchInput.value.$el.setFocus(), 100);
}
} else {
searchTermsRef.value = '';
}
}
</script>

<style lang="scss" scoped>
.searchBar-enter-active, .searchBar-leave-active {
transition: width 120ms cubic-bezier(0.250, 0.460, 0.450, 0.940);
}
.searchBar-enter-from, .searchBar-leave-to {
width: 0;
}
.searchBar-enter-to, .searchBar-leave-from {
width: 100%;
}
ion-toolbar {
position: sticky;
top: 0;
}
.toolbarHeader {
&-actions {
display: flex;
align-items: center;
gap: var(--app-gutters);
}
}
</style>
78 changes: 17 additions & 61 deletions mobile/src/views/event/SchedulePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,12 @@
layout="stacked"
@didDismiss="preparingOfflineScheduleToastIsOpenRef = false"
></ion-toast>
<ion-header class="toolbarHeader">
<ion-toolbar>
<ion-title slot="start">{{ LL.Schedule() }}</ion-title>
<transition name="searchBar">
<div v-if="searchFieldDisplayed" class="search-input">
<ion-input :size="10" ref="$searchInput"
:debounce="300"
:placeholder="`${LL.Search()}...`"
@ionInput="(ev) => searchTermsRef = ''+ev.target.value"
/>
<ion-icon class="iconInput" src="/assets/icons/line/search-line.svg"></ion-icon>
<ion-button shape="round" size="small" fill="outline" @click="toggleSearchField()"
:aria-label="LL.Search_close()">
<ion-icon src="/assets/icons/line/close-line.svg"></ion-icon>
</ion-button>
</div>
</transition>

<ion-button class="ion-margin-end" slot="end" shape="round" size="small" fill="outline" @click="openSchedulePreferencesModal()"
v-if="false" :aria-label="LL.Filters()">
<ion-icon src="/assets/icons/solid/settings-cog.svg"></ion-icon>
</ion-button>
<ion-button slot="end" shape="round" size="small" @click="toggleSearchField()"
:aria-label="LL.Search()">
<ion-icon src="/assets/icons/line/search-line.svg"></ion-icon>
</ion-button>
</ion-toolbar>
</ion-header>
<toolbar-header :title="LL.Schedule()" :search-enabled="true" @search-terms-updated="searchTerms => searchTermsRef = searchTerms">
<ion-button class="ion-margin-end" slot="end" shape="round" size="small" fill="outline" @click="openSchedulePreferencesModal()"
v-if="false" :aria-label="LL.Filters()">
<ion-icon src="/assets/icons/solid/settings-cog.svg"></ion-icon>
</ion-button>
</toolbar-header>

<ion-header class="stickyHeader daySelectorContainer">
<day-selector
Expand Down Expand Up @@ -115,7 +93,15 @@
</template>

<script setup lang="ts">
import {IonAccordionGroup, IonFab, IonFabButton, IonFabList, IonInput, IonToast, modalController} from '@ionic/vue';
import {
IonAccordionGroup,
IonButton,
IonFab,
IonFabButton,
IonFabList,
IonToast,
modalController
} from '@ionic/vue';
import {computed, nextTick, Ref, toValue, watch} from "vue";
import {isRefDefined, managedRef as ref} from "@/views/vue-utils";
import {LabelledTimeslotWithFeedback, useSchedule} from "@/state/useSchedule";
Expand Down Expand Up @@ -152,6 +138,8 @@ import ProvideFeedbackTalkButton from "@/components/talk-card/ProvideFeedbackTal
import PoweredVoxxrin from "@/components/ui/PoweredVoxxrin.vue";
import {useRoomsStats} from "@/state/useRoomsStats";
import {getResolvedEventRootPathFromSpacedEventIdRef, useCurrentSpaceEventIdRef} from "@/services/Spaces";
import {list, star} from "ionicons/icons";
import ToolbarHeader from "@/components/ui/ToolbarHeader.vue";
const LOGGER = Logger.named("SchedulePage");
Expand Down Expand Up @@ -205,9 +193,7 @@ const displayedTimeslotsRef = ref<LabelledTimeslotWithFeedback[]>([]) as Ref<Lab
const missingFeedbacksPastTimeslots = ref<MissingFeedbackPastTimeslot[]>([])
const expandedTimeslotIds = ref<string[]>([])
const searchFieldDisplayed = ref(false);
const searchTermsRef = ref<string|undefined>(undefined);
const $searchInput = ref<{ $el: HTMLIonInputElement }|undefined>(undefined);
const autoExpandTimeslotsRequested = ref(true);
watch([confDescriptor, displayedTimeslotsRef ], ([confDescriptor, displayedTimeslots]) => {
Expand Down Expand Up @@ -280,18 +266,6 @@ function toggleExpandedTimeslot(timeslot: VoxxrinScheduleTimeSlot) {
}
}
async function toggleSearchField() {
searchFieldDisplayed.value = !searchFieldDisplayed.value
if(searchFieldDisplayed.value) {
await nextTick(); // Wait for Vue to update the DOM
if(isRefDefined($searchInput)) {
setTimeout(() => $searchInput.value.$el.setFocus(), 100);
}
} else {
searchTermsRef.value = '';
}
}
async function openSchedulePreferencesModal() {
const modal = await modalController.create({
component: SchedulePreferencesModal,
Expand All @@ -305,19 +279,6 @@ async function openSchedulePreferencesModal() {

<style scoped lang="scss">
.searchBar-enter-active, .searchBar-leave-active {
transition: width 120ms cubic-bezier(0.250, 0.460, 0.450, 0.940);
}
.searchBar-enter-from, .searchBar-leave-to {
width: 0;
}
.searchBar-enter-to, .searchBar-leave-from {
width: 100%;
}
.daySelectorContainer {
overflow-y: auto;
}
Expand All @@ -335,11 +296,6 @@ async function openSchedulePreferencesModal() {
margin-bottom: $ion-fab-button-height;
}
ion-toolbar {
position: sticky;
top: 0;
}
.listFeedbackSlot {
&.temporarily-displayed-during-inactive-animation {
display: flex;
Expand Down
36 changes: 14 additions & 22 deletions mobile/src/views/event/SpeakersDirectoryPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,10 @@
<ion-page>
<ion-content :fullscreen="true" v-if="confDescriptor">
<current-event-header :conf-descriptor="confDescriptor" />
<ion-header class="toolbarHeader">
<ion-toolbar>
<ion-title slot="start">{{ LL.Speakers() }}</ion-title>
<div class="toolbarHeader-options" slot="end">
<ListModeSwitch></ListModeSwitch>
<ion-button slot="end" shape="round" size="small" @click="toggleSearchField()"
:aria-label="LL.Search()">
<ion-icon src="/assets/icons/line/search-line.svg"></ion-icon>
</ion-button>
</div>
</ion-toolbar>
</ion-header>
<toolbar-header :title="LL.Speakers()" :modes="MODES" :search-enabled="true"
@search-terms-updated="searchTerms => searchTermsRef = searchTerms"
@mode-updated="(updatedModeId, previousModeId) => console.log(`Mode updated from ${previousModeId} to ${updatedModeId}`)">
</toolbar-header>

<speaker-card @speaker-clicked="openSpeakerDetails($event)"></speaker-card>
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
Expand All @@ -35,12 +27,12 @@
import {typesafeI18n} from "@/i18n/i18n-vue";
import {managedRef as ref} from "@/views/vue-utils";
import {IonFab, IonFabButton} from "@ionic/vue";
import {ticket} from "ionicons/icons";
import {albums, list, ticket} from "ionicons/icons";
import PoweredVoxxrin from "@/components/ui/PoweredVoxxrin.vue";
import SpeakerCard from "@/components/speaker-card/SpeakerCard.vue";
import ListModeSwitch from "@/components/ui/ListModeSwitch.vue";
import {useTabbedPageNav} from "@/state/useTabbedPageNav";
import {VoxxrinSimpleSpeaker} from "@/models/VoxxrinSpeaker";
import ToolbarHeader from "@/components/ui/ToolbarHeader.vue";
const { LL } = typesafeI18n()
const route = useRoute();
Expand All @@ -50,6 +42,14 @@
const baseUrl = import.meta.env.BASE_URL;
const MODES = [
{ id: "detailed", icon: albums, label: LL.value.Big_list_mode(), preSelected: true },
{ id: "compact", icon: list, label: LL.value.Compact_list_mode() },
]
const searchTermsRef = ref<string|undefined>(undefined);
// TODO: take searchTermsRef into consideration when looking for speakers/talks
async function openSpeakerDetails(speaker: VoxxrinSimpleSpeaker) {
if(speaker) {
// TODO: Re-enable this once *tabbed* talk details as feedback viewer routing has been fixed
Expand All @@ -68,14 +68,6 @@
</script>

<style lang="scss" scoped>
.toolbarHeader {
&-options {
display: flex;
align-items: center;
gap: var(--app-gutters);
}
}
.btnGoToTicketing {
--background: var(--voxxrin-event-theme-colors-secondary-hex);
--background-activated: var(--voxxrin-event-theme-colors-secondary-hex);
Expand Down

0 comments on commit 943296d

Please sign in to comment.