Skip to content

Commit

Permalink
Merge pull request #124 from bcgov/user-profile-dark-mode
Browse files Browse the repository at this point in the history
Jasper 176: User Profile Settings/Dark Mode
  • Loading branch information
WadeBarnes authored Jan 6, 2025
2 parents d87e163 + f4e9e50 commit ff92125
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 20 deletions.
18 changes: 18 additions & 0 deletions web/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 web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"vuetify": "^3.7.5"
},
"devDependencies": {
"@faker-js/faker": "^9.3.0",
"@mdi/font": "^7.4.47",
"@mdi/js": "^7.4.47",
"@typescript-eslint/eslint-plugin": "^8.17.0",
Expand Down
57 changes: 37 additions & 20 deletions web/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,27 +1,44 @@
<template>
<v-app>
<v-app-bar app>
<v-app-bar-title>
<router-link to="/"> JASPER </router-link>
</v-app-bar-title>
<v-tabs align-tabs="start">
<v-tab to="/dashboard">Dashboard</v-tab>
<v-tab to="/court-list">Court list</v-tab>
<v-tab to="/court-file-search">Court file search</v-tab>
</v-tabs>
</v-app-bar>
<v-main>
<router-view />
</v-main>
</v-app>
<v-theme-provider :theme="theme">
<v-app>
<profile-off-canvas v-model="profile" @close="profile = false" />
<v-app-bar app>
<v-app-bar-title>
<router-link to="/"> JASPER </router-link>
</v-app-bar-title>
<v-tabs align-tabs="start">
<v-tab to="/dashboard">Dashboard</v-tab>
<v-tab to="/court-list">Court list</v-tab>
<v-tab to="/court-file-search">Court file search</v-tab>
<v-spacer></v-spacer>
<div class="d-flex align-center">
<v-btn
class="ma-2"
@click.stop="profile = true"
:icon="mdiAccountCircle"
size="x-large"
style="font-size: 1.5rem"
/>
</div>
</v-tabs>
</v-app-bar>

<v-main>
<router-view />
</v-main>
</v-app>
</v-theme-provider>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
<script setup lang="ts">
import { mdiAccountCircle } from '@mdi/js';
import { ref } from 'vue';
import ProfileOffCanvas from './components/shared/ProfileOffCanvas.vue';
import { useThemeStore } from './stores/ThemeStore';
export default defineComponent({
name: 'App',
});
const themeStore = useThemeStore();
const theme = ref(themeStore.state);
const profile = ref(false);
</script>

<style>
Expand Down
45 changes: 45 additions & 0 deletions web/src/components/shared/ProfileOffCanvas.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<template>
<v-navigation-drawer location="right" temporary>
<v-list-item subtitle="JSmith" color="primary" rounded="shaped">
<template v-slot:prepend>
<v-icon :icon="mdiAccountCircle" size="45" />
</template>
<v-list-item-title>John Smith</v-list-item-title>
<template v-slot:append>
<v-btn :icon="mdiCloseCircle" @click="$emit('close')" />
</template>
</v-list-item>

<v-divider></v-divider>

<v-list-item color="primary" rounded="shaped">
<template v-slot:prepend>
<v-icon :icon="mdiWeatherNight"></v-icon>
</template>
<v-list-item-title>Dark mode</v-list-item-title>
<template v-slot:append>
<v-switch v-model="isDark" hide-details @click="toggleDark" />
</template>
</v-list-item>
</v-navigation-drawer>
</template>

<script setup lang="ts">
import { useThemeStore } from '@/stores/ThemeStore';
import { mdiAccountCircle, mdiCloseCircle, mdiWeatherNight } from '@mdi/js';
import { ref } from 'vue';
const themeStore = useThemeStore();
const theme = ref(themeStore.state);
const isDark = ref(theme.value === 'dark');
function toggleDark() {
themeStore.changeState(theme.value === 'dark' ? 'light' : 'dark');
}
</script>

<style>
div.v-list-item__spacer {
width: 15px !important;
}
</style>
14 changes: 14 additions & 0 deletions web/src/stores/ThemeStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ref } from 'vue';

// We want the default experience to be light mode
const theme = localStorage.getItem('theme') ?? 'light';
const state = ref(theme);

const changeState = (newTheme: string) => {
localStorage.setItem('theme', newTheme);
state.value = newTheme;
};

export const useThemeStore = () => {
return { state, changeState };
};
39 changes: 39 additions & 0 deletions web/tests/components/shared/ProfileOffCanvas.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { mount } from '@vue/test-utils';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import ProfileOffCanvas from 'CMP/shared/ProfileOffCanvas.vue';

vi.mock('SRC/stores/ThemeStore', () => ({
useThemeStore: () => ({
theme: 'light',
setTheme: vi.fn(),
changeState: vi.fn(),
}),
}));

describe('ProfileOffCanvas.vue', () => {
let wrapper;

beforeEach(() => {
wrapper = mount(ProfileOffCanvas);
});

it('renders the component', () => {
expect(wrapper.exists()).toBe(true);
});

// Unable to dive deeper into slotted append/prepend components
// todo: find a way to test the slotted components
// it('calls close when close button clicked', async () => {
// await wrapper.find('v-button').trigger('click');

// expect(wrapper.emitted()).toHaveProperty('close');
// });

// it('calls set theme to dark when toggle button is clicked', async () => {
// await wrapper.find('v-switch').trigger('click');

// expect(themeStore.changeState).toHaveBeenCalledWith('dark');
// });
});


30 changes: 30 additions & 0 deletions web/tests/stores/ThemeStore.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { useThemeStore } from '@/stores/ThemeStore';

describe('ThemeStore', () => {
let themeStore: ReturnType<typeof useThemeStore>;

beforeEach(() => {
themeStore = useThemeStore();
});

afterEach(() => {
localStorage.clear();
});

it('should initialize with light theme by default', () => {
expect(themeStore.state.value).toBe('light');
});

it('should change theme and update localStorage', () => {
themeStore.changeState('dark');
expect(themeStore.state.value).toBe('dark');
expect(localStorage.getItem('theme')).toBe('dark');
});

it('should persist theme from localStorage', () => {
localStorage.setItem('theme', 'dark');
themeStore = useThemeStore();
expect(themeStore.state.value).toBe('dark');
});
});
4 changes: 4 additions & 0 deletions web/vitest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ export default defineConfig({
},
test: {
alias: [
{ find: '@', replacement: resolve(basePath, './src') },
{ find: 'SRC', replacement: resolve(basePath, './src') },
{ find: 'CMP', replacement: resolve(basePath, './src/components') }
],
deps: {
inline: ['vuetify']
},
css: true,
environment: 'happy-dom',
globals: true,
Expand Down

0 comments on commit ff92125

Please sign in to comment.