Skip to content

Commit

Permalink
feat(core): add dropdown to select a different year
Browse files Browse the repository at this point in the history
add new state version
add separate utils module for state management
  • Loading branch information
simonwep committed Aug 25, 2022
1 parent 5d8876e commit 6982dad
Show file tree
Hide file tree
Showing 18 changed files with 284 additions and 58 deletions.
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"gen:icons": "node scripts/icons.js"
},
"dependencies": {
"@popperjs/core": "^2.11.5",
"echarts": "^5.3.3",
"papaparse": "^5.3.2",
"vue": "^3.2.37",
Expand Down
107 changes: 107 additions & 0 deletions src/app/components/base/context-menu/ContextMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<template>
<div :class="$style.contextMenu">
<button ref="reference" :class="$style.reference" @click="open = !open">
<slot />
</button>
<div ref="popper" :class="$style.popper">
<ul :class="[$style.list, { [$style.visible]: open }]">
<li :class="$style.item" v-for="option of options" :key="option.id">
<button :class="$style.btn" @click="select(option)">
{{ option.label ?? option.id }}
</button>
</li>
</ul>
</div>
</div>
</template>

<script lang="ts" setup>
import { ref, watch } from 'vue';
import { createPopper, Instance } from '@popperjs/core';
import { ContextMenuOption } from '.';
const emit = defineEmits<{
(e: 'select', option: ContextMenuOption): void;
}>();
defineProps<{
options: ContextMenuOption[];
}>();
const reference = ref<HTMLButtonElement>();
const popper = ref<HTMLDivElement>();
const open = ref(false);
let instance: Instance | undefined;
watch([reference, popper], () => {
instance?.destroy();
if (reference.value && popper.value) {
instance = createPopper(reference.value, popper.value, {
placement: 'right-end',
modifiers: [{ name: 'offset', options: { offset: [10, 10] } }]
});
}
});
const select = (option: ContextMenuOption): void => {
emit('select', option);
open.value = false;
};
</script>

<style lang="scss" module>
.contextMenu {
display: flex;
}
.reference {
all: unset;
display: flex;
}
.popper {
display: flex;
position: absolute;
z-index: 1;
}
.list {
list-style: none outside none;
display: flex;
flex-direction: column;
align-items: flex-end;
backdrop-filter: var(--context-menu-backdrop);
box-shadow: var(--context-menu-shadow);
border-radius: var(--border-radius-m);
padding: 5px 2px;
max-height: 130px;
overflow: auto;
visibility: hidden;
opacity: 0;
transform: translateX(-10px);
transition: all var(--transition-m);
&.visible {
visibility: visible;
opacity: 1;
transform: none;
}
.item .btn {
all: unset;
display: flex;
align-items: center;
cursor: pointer;
font-size: var(--font-size-xs);
border-radius: 10px;
padding: 6px 8px;
color: var(--context-menu-item-color);
position: relative;
&:hover {
color: var(--context-menu-item-color-hover);
}
}
}
</style>
4 changes: 4 additions & 0 deletions src/app/components/base/context-menu/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface ContextMenuOption {
id: number;
label?: string;
}
34 changes: 9 additions & 25 deletions src/app/components/base/dialog/Dialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
</template>

<script lang="ts" setup>
import { computed, onUnmounted, ref, useCssModule, watch, watchEffect } from 'vue';
import { computed, ref, useCssModule, watch } from 'vue';
import { useOutOfElementClick } from '@composables';
const emit = defineEmits<{
(e: 'close'): void;
Expand All @@ -17,18 +18,15 @@ const props = defineProps<{
open: boolean;
}>();
const visible = ref(false);
const content = ref<HTMLDivElement>();
const dialog = ref<HTMLDialogElement>();
const visible = ref(false);
const styles = useCssModule();
const classes = computed(() => [styles.dialog, { [styles.open]: visible.value }]);
const classes = computed(() => [styles.dialog, { [styles.open]: props.open }]);
const detectOutOfBoundsClick = (e: MouseEvent) => {
if (props.open && e.isTrusted && !e.composedPath().includes(content.value as HTMLDivElement)) {
emit('close');
}
};
// Todo: somewhat triggered while modal is made visible
useOutOfElementClick(content, () => visible.value && emit('close'));
const transitionEnd = () => {
if (!props.open) {
Expand All @@ -38,29 +36,15 @@ const transitionEnd = () => {
watch(
() => props.open,
(value) => {
if (value) {
(open) => {
if (open) {
dialog.value?.showModal();
visible.value = true;
requestAnimationFrame(() => (visible.value = true));
} else {
visible.value = false;
}
}
);
onUnmounted(() => window.removeEventListener('click', detectOutOfBoundsClick));
watchEffect(() => {
void props.open;
requestAnimationFrame(() => {
if (props.open) {
window.addEventListener('click', detectOutOfBoundsClick);
} else {
window.removeEventListener('click', detectOutOfBoundsClick);
}
});
});
</script>

<style lang="scss" module>
Expand Down
1 change: 1 addition & 0 deletions src/app/components/base/icon/Icon.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type AppIcon =
| 'arrow-down-s-line'
| 'arrow-left-s-line'
| 'arrow-right-s-line'
| 'calendar-line'
| 'check'
| 'close-circle'
| 'cloud-line'
Expand Down
2 changes: 2 additions & 0 deletions src/app/pages/Frame.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

<ImportButton :class="[$style.top, $style.btn]" />
<ExportButton :class="$style.btn" />
<SelectYearButton :class="$style.btn" />
<div :class="$style.divider" />
<CloudButton :class="$style.btn" />
</div>
Expand All @@ -34,6 +35,7 @@ import { useRouter } from 'vue-router';
import CloudButton from './navigation/CloudButton.vue';
import ExportButton from './navigation/export/ExportButton.vue';
import ImportButton from './navigation/import/ImportButton.vue';
import SelectYearButton from './navigation/year/SelectYearButton.vue';
import ThemeButton from './navigation/ThemeButton.vue';
const menu = ref<HTMLDivElement>();
Expand Down
27 changes: 27 additions & 0 deletions src/app/pages/navigation/year/SelectYearButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<template>
<ContextMenu :options="options" @select="changeYear($event.id)">
<Button icon="calendar-line" textual color="dimmed" />
</ContextMenu>
</template>

<script lang="ts" setup>
import { computed } from 'vue';
import Button from '@components/base/button/Button.vue';
import ContextMenu from '@components/base/context-menu/ContextMenu.vue';
import { ContextMenuOption } from '@components/base/context-menu';
import { useDataStore } from '@store/state';
const { changeYear, state } = useDataStore();
const options = computed((): ContextMenuOption[] => {
const yearsStored = state.years.map((v) => v.year);
const offset = Math.min(new Date().getFullYear(), ...yearsStored);
const list: ContextMenuOption[] = [];
for (let i = 0; i < 3; i++) {
list.push({ id: offset + i });
}
return list;
});
</script>
1 change: 1 addition & 0 deletions src/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './useIntristicSize';
export * from './useMediaQuery';
export * from './useMonthNames';
export * from './useMutationObserver';
export * from './useOutOfElementClick';
export * from './useResizeObserver';
export * from './useScrollShadow';
export * from './useStateHistory';
Expand Down
12 changes: 12 additions & 0 deletions src/composables/useOutOfElementClick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { onMounted, onUnmounted, Ref } from 'vue';

export const useOutOfElementClick = (el: Ref<HTMLElement | undefined>, cb: (evt: MouseEvent) => void) => {
const click = (evt: MouseEvent) => {
if (el.value && !evt.composedPath().includes(el.value)) {
cb(evt);
}
};

onMounted(() => window.addEventListener('click', click));
onUnmounted(() => window.removeEventListener('click', click));
};
1 change: 1 addition & 0 deletions src/icons/calendar-line.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/storage/google-drive-storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const createGoogleDriveStorage = (auth: GoogleDriveAuth): AppStorage => {
}
};

const sync = <T extends StorageData>(config: StorageSync<T>) => {
const sync = <T extends StorageData, P extends StorageData = T>(config: StorageSync<T, P>) => {
const change = debounce(async (value: string) => {
syncsActive++;
await upsert(config.name, value);
Expand Down
Loading

0 comments on commit 6982dad

Please sign in to comment.