feat: extract MainMenuSummary into component

This is the top part of the main menu which includes the Avatar overlay.
I'm not quite sure how to label each part.

Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de>
This commit is contained in:
André Jaenisch 2024-07-09 12:18:08 +02:00
parent 179db76b5a
commit 0ca5d963f3
No known key found for this signature in database
GPG key ID: 5A668E771F1ED854
11 changed files with 296 additions and 160 deletions

View file

@ -10,6 +10,8 @@
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. * You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { computePosition, autoUpdate, offset, shift, flip, arrow } from '@floating-ui/dom';
import { initializeStores, storePopup } from '@skeletonlabs/skeleton';
import type { Preview } from '@storybook/svelte'; import type { Preview } from '@storybook/svelte';
import { init, locale, register } from 'svelte-i18n'; import { init, locale, register } from 'svelte-i18n';
@ -39,6 +41,17 @@ function withI18n(storyFn, context) {
return storyFn(); return storyFn();
} }
function withStores(storyFn) {
// Globally initialize stores for Skeleton.dev contexts
initializeStores();
return storyFn();
}
function withPopup(storyFn) {
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
return storyFn();
}
const preview: Preview = { const preview: Preview = {
globalTypes: { globalTypes: {
locale: { locale: {
@ -82,6 +95,6 @@ const preview: Preview = {
} }
}; };
export const decorators = [withI18n]; export const decorators = [withI18n, withPopup, withStores];
export default preview; export default preview;

160
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "anvil", "name": "anvil",
"version": "0.0.7", "version": "0.0.8",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "anvil", "name": "anvil",
"version": "0.0.7", "version": "0.0.8",
"dependencies": { "dependencies": {
"axios": "1.7.2", "axios": "1.7.2",
"cheerio": "1.0.0-rc.12" "cheerio": "1.0.0-rc.12"
@ -4815,86 +4815,6 @@
"@tauri-apps/cli-win32-x64-msvc": "1.6.0" "@tauri-apps/cli-win32-x64-msvc": "1.6.0"
} }
}, },
"node_modules/@tauri-apps/cli-darwin-arm64": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.6.0.tgz",
"integrity": "sha512-SNRwUD9nqGxY47mbY1CGTt/jqyQOU7Ps7Mx/mpgahL0FVUDiCEY/5L9QfEPPhEgccgcelEVn7i6aQHIkHyUtCA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-darwin-x64": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.6.0.tgz",
"integrity": "sha512-g2/uDR/eeH2arvuawA4WwaEOqv/7jDO/ZLNI3JlBjP5Pk8GGb3Kdy0ro1xQzF94mtk2mOnOXa4dMgAet4sUJ1A==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.6.0.tgz",
"integrity": "sha512-EVwf4oRkQyG8BpSrk0gqO7oA0sDM2MdNDtJpMfleYFEgCxLIOGZKNqaOW3M7U+0Y4qikmG3TtRK+ngc8Ymtrjg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.6.0.tgz",
"integrity": "sha512-YdpY17cAySrhK9dX4BUVEmhAxE2o+6skIEFg8iN/xrDwRxhaNPI9I80YXPatUTX54Kx55T5++25VJG9+3iw83A==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.6.0.tgz",
"integrity": "sha512-4U628tuf2U8pMr4tIBJhEkrFwt+46dwhXrDlpdyWSZtnop5RJAVKHODm0KbWns4xGKfTW1F3r6sSv+2ZxLcISA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-linux-x64-gnu": { "node_modules/@tauri-apps/cli-linux-x64-gnu": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.6.0.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.6.0.tgz",
@ -4927,54 +4847,6 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.6.0.tgz",
"integrity": "sha512-QwWpWk4ubcwJ1rljsRAmINgB2AwkyzZhpYbalA+MmzyYMREcdXWGkyixWbRZgqc6fEWEBmq5UG73qz5eBJiIKg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.6.0.tgz",
"integrity": "sha512-Vtw0yxO9+aEFuhuxQ57ALG43tjECopRimRuKGbtZYDCriB/ty5TrT3QWMdy0dxBkpDTu3Rqsz30sbDzw6tlP3Q==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.6.0.tgz",
"integrity": "sha512-h54FHOvGi7+LIfRchzgZYSCHB1HDlP599vWXQQJ/XnwJY+6Rwr2E5bOe/EhqoG8rbGkfK0xX3KPAvXPbUlmggg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@testing-library/dom": { "node_modules/@testing-library/dom": {
"version": "10.3.0", "version": "10.3.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.3.0.tgz", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.3.0.tgz",
@ -8878,20 +8750,6 @@
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true "dev": true
}, },
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@ -14566,20 +14424,6 @@
"@esbuild/win32-x64": "0.21.5" "@esbuild/win32-x64": "0.21.5"
} }
}, },
"node_modules/vite/node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/vitefu": { "node_modules/vitefu": {
"version": "0.2.5", "version": "0.2.5",
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz",

View file

@ -1,6 +1,6 @@
{ {
"name": "anvil", "name": "anvil",
"version": "0.0.7", "version": "0.0.8",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",

View file

@ -0,0 +1,85 @@
<!--
Main menu summary atom.
Copyright (C) 2024 André Jaenisch
SPDX-FileCopyrightText: 2024 André Jaenisch
SPDX-License-Identifier: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<script>
import { popup } from '@skeletonlabs/skeleton';
import { _ } from 'svelte-i18n';
import { Bell24, GitPullRequest24, IssueOpened24, Person24 } from 'svelte-octicons';
import OverlayAvatar from './OverlayAvatar.svelte';
const popupClick = {
event: 'click',
target: 'overlayAvatar',
placement: 'bottom'
};
</script>
<div class="flex items-center content-stretch justify-start gap-4 ps-1">
<button
type="button"
class="btn btn-icon text-white bg-surface-400 rounded-full"
use:popup={popupClick}
>
<Person24 fill="currentColor" />
<span class="sr-only">{$_('page.profile.menu.buttons.avatar')}</span>
</button>
<div class="flex flex-1 justify-around content-stretch bg-white">
<div class="flex items-center content-stretch gap-1 bg-white">
<button class="btn btn-icon rounded-full">
<IssueOpened24 fill="currentColor" />
<span>
{@html $_('page.profile.menu.buttons.issues', {
values: {
number: 3,
blockElementOpen: '<span class="sr-only">',
blockElementClose: '</span>'
}
})}
</span>
</button>
</div>
<div class="flex items-center content-stretch gap-1 bg-white">
<button class="btn btn-icon rounded-full">
<GitPullRequest24 fill="currentColor" />
<span>
{@html $_('page.profile.menu.buttons.prs', {
values: {
number: 1,
blockElementOpen: '<span class="sr-only">',
blockElementClose: '</span>'
}
})}
</span>
</button>
</div>
<div class="flex items-center content-stretch gap-1 bg-white">
<button class="btn btn-icon rounded-full">
<Bell24 fill="currentColor" />
<span>
{@html $_('page.profile.menu.buttons.notifications', {
values: {
number: 2,
blockElementOpen: '<span class="sr-only">',
blockElementClose: '</span>'
}
})}
</span>
</button>
</div>
</div>
</div>
<div class="card p-4 bg-transparent" data-popup="overlayAvatar">
<OverlayAvatar />
</div>

View file

@ -90,6 +90,14 @@
}, },
"heading": "" "heading": ""
}, },
"menu": {
"buttons": {
"avatar": "",
"issues": "",
"notifications": "",
"prs": ""
}
},
"projects": { "projects": {
"actions": { "actions": {
"fork": "", "fork": "",

View file

@ -90,6 +90,14 @@
}, },
"heading": "Aktivitäten" "heading": "Aktivitäten"
}, },
"menu": {
"buttons": {
"avatar": "",
"issues": "",
"notifications": "",
"prs": ""
}
},
"projects": { "projects": {
"actions": { "actions": {
"fork": "Fork", "fork": "Fork",

View file

@ -90,6 +90,14 @@
}, },
"heading": "Activities" "heading": "Activities"
}, },
"menu": {
"buttons": {
"avatar": "Menu",
"issues": "{number}{blockElementOpen}{number, plural, one{ issue} other{ issues}} open{blockElementClose}",
"notifications": "{number}{blockElementOpen} unread {number, plural, one{ notification} other{ notifications}}{blockElementClose}",
"prs": "{number}{blockElementOpen}{number, plural, one{ pull request} other{ pull requests}} open{blockElementClose}"
}
},
"projects": { "projects": {
"actions": { "actions": {
"fork": "Fork", "fork": "Fork",

View file

@ -90,6 +90,14 @@
}, },
"heading": "פעילות" "heading": "פעילות"
}, },
"menu": {
"buttons": {
"avatar": "",
"issues": "",
"notifications": "",
"prs": ""
}
},
"projects": { "projects": {
"actions": { "actions": {
"fork": "", "fork": "",

View file

@ -90,6 +90,14 @@
}, },
"heading": "Aktywność" "heading": "Aktywność"
}, },
"menu": {
"buttons": {
"avatar": "",
"issues": "",
"notifications": "",
"prs": ""
}
},
"projects": { "projects": {
"actions": { "actions": {
"fork": "", "fork": "",

View file

@ -0,0 +1,26 @@
/* Stories for MainMenuSummary atom.
* Copyright (C) 2024 André Jaenisch
* SPDX-FileCopyrightText: 2024 André Jaenisch
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import type { Meta, StoryObj } from '@storybook/svelte';
import MainMenuSummary from '$lib/components/atoms/MainMenuSummary.svelte';
const meta = {
title: 'Atoms/MainMenuSummary',
component: MainMenuSummary,
tags: ['autodocs']
} satisfies Meta<MainMenuSummary>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Plain: Story = {};

View file

@ -0,0 +1,128 @@
/* Component test for MainMenuSummary atom.
* Copyright (C) 2024 André Jaenisch
* SPDX-FileCopyrightText: 2024 André Jaenisch
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { computePosition, autoUpdate, offset, shift, flip, arrow } from '@floating-ui/dom';
import { storePopup } from '@skeletonlabs/skeleton';
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/svelte';
import { get, readable } from 'svelte/store';
import { init, locale, register } from 'svelte-i18n';
import MainMenuSummary from '../../../src/lib/components/atoms/MainMenuSummary.svelte';
import enMessages from '../../../src/lib/i18n/locales/en.json';
describe('MainMenuSummary.svelte', () => {
beforeEach(() => {
register('en', () => import('../../../src/lib/i18n/locales/en.json'));
init({ fallbackLocale: 'en', initialLocale: 'en' });
locale.set('en');
});
it('should mount', () => {
// Arrange
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
// Act
const { container } = render(MainMenuSummary, {
context: new Map([
['modalStore', modalStore],
['storePopup', storePopup]
])
});
// Assert
expect(container).toBeTruthy();
});
it('should have an avatar', () => {
// Arrange
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
// Act
render(MainMenuSummary, {
context: new Map([
['modalStore', modalStore],
['storePopup', storePopup]
])
});
// Assert
expect(
screen.getByRole('button', {
name: enMessages.page.profile.menu.buttons.avatar
})
).toBeInTheDocument();
});
it('should have an indicator for open issues', () => {
// Arrange
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
// Act
render(MainMenuSummary, {
context: new Map([
['modalStore', modalStore],
['storePopup', storePopup]
])
});
// Assert
expect(screen.getByText('issues open')).toBeInTheDocument();
});
it('should have an indicator for open issues', () => {
// Arrange
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
// Act
render(MainMenuSummary, {
context: new Map([
['modalStore', modalStore],
['storePopup', storePopup]
])
});
// Assert
expect(screen.getByText('pull request open')).toBeInTheDocument();
});
it('should have an indicator for unread notifications', () => {
// Arrange
// See https://testing-library.com/docs/svelte-testing-library/example#contexts
const modalStore = readable([]);
// See https://www.skeleton.dev/utilities/popups
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
// Act
render(MainMenuSummary, {
context: new Map([
['modalStore', modalStore],
['storePopup', storePopup]
])
});
// Assert
expect(screen.getByText('unread notifications')).toBeInTheDocument();
});
});