Skip to content

Commit

Permalink
feat: collapsing + shaking + refactoring for type-safety and minimalism
Browse files Browse the repository at this point in the history
  • Loading branch information
Destaq committed Nov 19, 2024
1 parent a9cb55b commit 9425129
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 42 deletions.
77 changes: 53 additions & 24 deletions src/components/Course/Course.vue
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,16 @@
:expandedTranslateY="'-35px'"
:width="'calc(102.8% - 10px)'"
:color="cssVars['--bg-color']"
:expand="expandNote"
:initialNote="courseObj.note || ''"
@toggle="handleToggleNote"
@save-note="saveNote"
v-click-outside="handleClickOutside"
ref="note"
v-click-outside="handleClickOutsideNote"
/>
</div>
</template>

<script lang="ts">
import { CSSProperties, PropType, defineComponent } from 'vue';
import { PropType, defineComponent } from 'vue';
import CourseMenu from '@/components/Modals/CourseMenu.vue';
import CourseCaution from '@/components/Course/CourseCaution.vue';
import SaveCourseModal from '@/components/Modals/SaveCourseModal.vue';
Expand All @@ -130,6 +128,14 @@ import trashGrayIcon from '@/assets/images/trash-gray.svg';
import trashRedIcon from '@/assets/images/trash.svg';
import Note from '../Notes/Note.vue';
interface MinimalNoteComponent {
note: string;
isDirty: boolean;
isExpanded: boolean;
collapseNote: () => void;
expandNote: () => void;
}
export default defineComponent({
name: 'Course',
components: { CourseCaution, CourseMenu, EditColor, SaveCourseModal, Note },
Expand Down Expand Up @@ -178,9 +184,8 @@ export default defineComponent({
deletingCourse: false,
trashIcon: trashGrayIcon, // Default icon
courseCode: '',
isExpanded: Boolean(this.courseObj.note), // NOTE: might want different behavior than showing all notes
isExpanded: false,
isNoteVisible: Boolean(this.courseObj.note),
expandNote: false,
isShaking: false,
};
},
Expand All @@ -204,10 +209,10 @@ export default defineComponent({
return `${this.courseObj.credits} credits`;
},
cssVars(): CSSProperties {
cssVars(): Record<string, string> {
return {
'--bg-color': `#${this.courseObj.color}`,
} as CSSProperties;
};
},
},
methods: {
Expand Down Expand Up @@ -278,22 +283,36 @@ export default defineComponent({
unhoverTrashIcon() {
this.trashIcon = trashGrayIcon;
},
handleToggleNote() {
this.expandNote = !this.expandNote;
},
openNoteModal() {
if (!this.isNoteVisible) {
this.isNoteVisible = true;
this.menuOpen = false;
this.expandNote = true;
this.$nextTick(() => {
const noteComponent = this.$refs.note as MinimalNoteComponent | undefined;
if (noteComponent) {
noteComponent.expandNote();
}
});
} else {
// Note already open — trigger a shake and close the modal.
this.triggerCourseCardShake();
this.menuOpen = false;
const noteComponent = this.$refs.note as MinimalNoteComponent | undefined;
if (!noteComponent) {
return;
}
// Note already open — trigger a shake.
if (noteComponent.isExpanded) {
this.triggerCourseCardShake();
} else {
noteComponent.expandNote();
}
}
this.menuOpen = false;
},
closeNote() {
this.isNoteVisible = false;
// NOTE: this function hides the note entirely!
// Grant time for the slide-back-in animation to play out.
setTimeout(() => {
this.isNoteVisible = false;
}, 300);
},
triggerCourseCardShake() {
this.isShaking = true;
Expand All @@ -303,16 +322,26 @@ export default defineComponent({
},
saveNote(note: string) {
this.$emit('save-note', this.courseObj.uniqueID, note);
this.closeNote();
},
handleClickOutside() {
if (
this.$refs.note &&
(this.$refs.note as {
isDirty: boolean;
}).isDirty
) {
handleClickOutsideNote(event: MouseEvent) {
// Don't count a click on the open note or three dots as a click outside.
const target = event.target as HTMLElement;
if (target.closest('.courseMenu') || target.closest('.course-dotRow')) {
return;
}
const noteComponent = this.$refs.note as MinimalNoteComponent | undefined;
if (!noteComponent || !this.isNoteVisible) {
return;
}
if (noteComponent.isDirty) {
this.triggerCourseCardShake();
} else if (noteComponent.note && this.isNoteVisible) {
noteComponent.collapseNote();
} else {
this.closeNote();
}
},
},
Expand Down
34 changes: 16 additions & 18 deletions src/components/Notes/Note.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<template>
<div class="note" :class="{ expanded: isExpanded }" :style="noteStyle" @click="expandNote">
<div v-if="isExpanded" class="note-content">
<div class="note-content" :class="{ visible: isExpanded }">
<input
v-model="noteText"
v-model="note"
placeholder="Add a note..."
class="note-input"
@keyup.enter="saveNote"
Expand All @@ -18,7 +18,7 @@
</div>
</template>

<script>
<script lang="ts">
import { coursesColorSet } from '@/assets/constants/colors';
export default {
Expand All @@ -32,16 +32,11 @@ export default {
},
data() {

Check failure on line 33 in src/components/Notes/Note.vue

View workflow job for this annotation

GitHub Actions / check

'data' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.
return {
isExpanded: Boolean(this.initialNote), // NOTE: might want different behavior than showing all notes
noteText: this.initialNote,
isExpanded: false,
note: this.initialNote,

Check failure on line 36 in src/components/Notes/Note.vue

View workflow job for this annotation

GitHub Actions / check

Property 'initialNote' does not exist on type '{ name: string; props: { initialTranslateY: { type: StringConstructor; default: string; }; expandedTranslateY: { type: StringConstructor; default: string; }; width: { ...; }; color: { ...; }; initialNote: { ...; }; }; data(): any; watch: { ...; }; computed: { ...; }; methods: { ...; }; }'.
isDirty: false,
};
},
mounted() {
if (this.expand) {
this.isExpanded = true;
}
},
watch: {
expand(newVal) {

Check failure on line 41 in src/components/Notes/Note.vue

View workflow job for this annotation

GitHub Actions / check

Parameter 'newVal' implicitly has an 'any' type.
if (newVal) this.isExpanded = true;

Check failure on line 42 in src/components/Notes/Note.vue

View workflow job for this annotation

GitHub Actions / check

Property 'isExpanded' does not exist on type '{ expand(newVal: any): void; }'.
Expand All @@ -62,29 +57,24 @@ export default {
expandNote() {
if (!this.isExpanded) {
this.isExpanded = true;
this.$emit('toggle', this.isExpanded);
}
},
collapseNote() {
if (this.isExpanded) {
this.isExpanded = false;
this.$emit('toggle', this.isExpanded);
}
},
getLighterColor(color) {
getLighterColor(color: string) {
const colorObj = coursesColorSet.find(c => c.hex.toUpperCase() === color.toUpperCase());
return colorObj ? colorObj.lighterHex : color;
},
saveNote() {
this.$emit('save-note', this.noteText);
this.$emit('save-note', this.note);
this.isDirty = false;
this.collapseNote();
},
handleEnter() {
this.saveNote();
},
handleInput() {
this.isDirty = this.noteText !== this.initialNote;
this.isDirty = this.note !== this.initialNote;
},
},
};
Expand Down Expand Up @@ -119,6 +109,14 @@ export default {
width: 100%;
padding-top: 15px;
padding-left: 10px;
opacity: 0;
transform: translateY(100%);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.note-content.visible {
opacity: 1;
transform: translateY(0);
}
.note-input {
Expand Down
3 changes: 3 additions & 0 deletions src/components/Semester/Semester.vue
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,9 @@ export default defineComponent({
}
},
saveNote(uniqueID: number, note: string) {
if (!note) {
return;
}
editSemester(
store.state.currentPlan,
this.year,
Expand Down

0 comments on commit 9425129

Please sign in to comment.