refactor to common MoreOptionsDropdown component for shared ui

This commit is contained in:
Gregor Vostrak
2024-07-11 13:20:23 +02:00
parent cbdbcef9eb
commit 10d8540e6c
10 changed files with 204 additions and 324 deletions

View File

@@ -307,9 +307,11 @@ test('test that deleting a time entry from the overview works', async ({
const timeEntryCount = await timeEntryRows.count(); const timeEntryCount = await timeEntryRows.count();
const newTimeEntry = timeEntryRows.first(); const newTimeEntry = timeEntryRows.first();
const actionsDropdown = newTimeEntry.getByTestId('time_entry_actions'); const actionsDropdown = newTimeEntry
.getByRole('button', { name: 'Actions for the time entry' })
.first();
await actionsDropdown.click(); await actionsDropdown.click();
const deleteButton = page.getByTestId('time_entry_delete'); const deleteButton = page.getByText('Delete');
await deleteButton.click(); await deleteButton.click();
await expect(timeEntryRows).toHaveCount(timeEntryCount - 1); await expect(timeEntryRows).toHaveCount(timeEntryCount - 1);
}); });

View File

@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import Dropdown from '@/Components/Dropdown.vue';
import { PencilSquareIcon, TrashIcon } from '@heroicons/vue/20/solid'; import { PencilSquareIcon, TrashIcon } from '@heroicons/vue/20/solid';
import type { Client } from '@/utils/api'; import type { Client } from '@/utils/api';
import { canDeleteClients, canUpdateClients } from '@/utils/permissions'; import { canDeleteClients, canUpdateClients } from '@/utils/permissions';
import MoreOptionsDropdown from '@/Components/MoreOptionsDropdown.vue';
const emit = defineEmits<{ const emit = defineEmits<{
delete: []; delete: [];
@@ -14,47 +14,29 @@ const props = defineProps<{
</script> </script>
<template> <template>
<Dropdown> <MoreOptionsDropdown :label="'Actions for Client ' + props.client.name">
<template #trigger> <div class="min-w-[150px]">
<svg <button
data-testid="client_actions" v-if="canUpdateClients()"
:aria-label="'Actions for Client ' + props.client.name" @click="emit('edit')"
class="h-10 w-10 p-2 rounded-full hover:bg-card-background opacity-20 group-hover:opacity-100 transition" :aria-label="'Edit Client ' + props.client.name"
viewBox="0 0 24 24" data-testid="client_edit"
xmlns="http://www.w3.org/2000/svg"> class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<path <PencilSquareIcon
fill="none" class="w-5 text-icon-active"></PencilSquareIcon>
stroke="currentColor" <span>Edit</span>
stroke-linecap="round" </button>
stroke-linejoin="round" <button
stroke-width="1.5" v-if="canDeleteClients()"
d="M12 5.92A.96.96 0 1 0 12 4a.96.96 0 0 0 0 1.92m0 7.04a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92M12 20a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92" /> @click="emit('delete')"
</svg> :aria-label="'Delete Client ' + props.client.name"
</template> data-testid="client_delete"
<template #content> class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<div class="min-w-[150px]"> <TrashIcon class="w-5 text-icon-active"></TrashIcon>
<button <span>Delete</span>
v-if="canUpdateClients()" </button>
@click="emit('edit')" </div>
:aria-label="'Edit Client ' + props.client.name" </MoreOptionsDropdown>
data-testid="client_edit"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<PencilSquareIcon
class="w-5 text-icon-active"></PencilSquareIcon>
<span>Edit</span>
</button>
<button
v-if="canDeleteClients()"
@click="emit('delete')"
:aria-label="'Delete Client ' + props.client.name"
data-testid="client_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>
</div>
</template>
</Dropdown>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import Dropdown from '@/Components/Dropdown.vue';
import { TrashIcon, ArrowPathIcon } from '@heroicons/vue/20/solid'; import { TrashIcon, ArrowPathIcon } from '@heroicons/vue/20/solid';
import MoreOptionsDropdown from '@/Components/MoreOptionsDropdown.vue';
const emit = defineEmits<{ const emit = defineEmits<{
delete: []; delete: [];
resend: []; resend: [];
@@ -8,39 +8,22 @@ const emit = defineEmits<{
</script> </script>
<template> <template>
<Dropdown align="bottom-end"> <MoreOptionsDropdown label="Actions for the invitation">
<template #trigger> <button
<svg @click="emit('resend')"
data-testid="invitation_actions" data-testid="invitation_delete"
class="h-10 w-10 p-2 rounded-full hover:bg-card-background opacity-20 group-hover:opacity-100 transition" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
viewBox="0 0 24 24" <ArrowPathIcon class="w-5 text-icon-active"></ArrowPathIcon>
xmlns="http://www.w3.org/2000/svg"> <span>Resend Invitation</span>
<path </button>
fill="none" <button
stroke="currentColor" @click="emit('delete')"
stroke-linecap="round" data-testid="invitation_delete"
stroke-linejoin="round" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
stroke-width="1.5" <TrashIcon class="w-5 text-icon-active"></TrashIcon>
d="M12 5.92A.96.96 0 1 0 12 4a.96.96 0 0 0 0 1.92m0 7.04a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92M12 20a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92" /> <span>Delete</span>
</svg> </button>
</template> </MoreOptionsDropdown>
<template #content>
<button
@click="emit('resend')"
data-testid="invitation_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<ArrowPathIcon class="w-5 text-icon-active"></ArrowPathIcon>
<span>Resend Invitation</span>
</button>
<button
@click="emit('delete')"
data-testid="invitation_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>
</template>
</Dropdown>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import Dropdown from '@/Components/Dropdown.vue';
import { TrashIcon, PencilSquareIcon } from '@heroicons/vue/20/solid'; import { TrashIcon, PencilSquareIcon } from '@heroicons/vue/20/solid';
import type { Member } from '@/utils/api'; import type { Member } from '@/utils/api';
import { canDeleteMembers, canUpdateMembers } from '@/utils/permissions'; import { canDeleteMembers, canUpdateMembers } from '@/utils/permissions';
import MoreOptionsDropdown from '@/Components/MoreOptionsDropdown.vue';
const emit = defineEmits<{ const emit = defineEmits<{
delete: []; delete: [];
@@ -14,50 +14,30 @@ const props = defineProps<{
</script> </script>
<template> <template>
<Dropdown <MoreOptionsDropdown
v-if="canUpdateMembers() || canDeleteMembers()" v-if="canUpdateMembers() || canDeleteMembers()"
align="bottom-end"> :label="'Actions for Member ' + props.member.name">
<template #trigger> <div class="min-w-[150px]">
<button <button
class="focus-visible:outline-none focus-visible:bg-card-background rounded-full focus-visible:ring-1 focus-visible:ring-input-border-active focus-visible:opacity-100 hover:bg-card-background group-hover:opacity-100 opacity-20 transition-opacity" v-if="canUpdateMembers()"
:aria-label="'Actions for Member ' + props.member.name"> @click="emit('edit')"
<svg :aria-label="'Edit Member ' + props.member.name"
class="h-10 w-10 p-2 rounded-full" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
viewBox="0 0 24 24" <PencilSquareIcon
xmlns="http://www.w3.org/2000/svg"> class="w-5 text-icon-active"></PencilSquareIcon>
<path <span>Edit</span>
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M12 5.92A.96.96 0 1 0 12 4a.96.96 0 0 0 0 1.92m0 7.04a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92M12 20a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92" />
</svg>
</button> </button>
</template> <button
<template #content> v-if="canDeleteMembers()"
<div class="min-w-[150px]"> @click="emit('delete')"
<button :aria-label="'Delete Member ' + props.member.name"
v-if="canUpdateMembers()" data-testid="member_delete"
@click="emit('edit')" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
:aria-label="'Edit Member ' + props.member.name" <TrashIcon class="w-5 text-icon-active"></TrashIcon>
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"> <span>Delete</span>
<PencilSquareIcon </button>
class="w-5 text-icon-active"></PencilSquareIcon> </div>
<span>Edit</span> </MoreOptionsDropdown>
</button>
<button
v-if="canDeleteMembers()"
@click="emit('delete')"
:aria-label="'Delete Member ' + props.member.name"
data-testid="member_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>
</div>
</template>
</Dropdown>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import Dropdown from '@/Components/Dropdown.vue';
import { import {
TrashIcon, TrashIcon,
PencilSquareIcon, PencilSquareIcon,
@@ -7,6 +6,7 @@ import {
} from '@heroicons/vue/20/solid'; } from '@heroicons/vue/20/solid';
import type { Project } from '@/utils/api'; import type { Project } from '@/utils/api';
import { canDeleteProjects, canUpdateProjects } from '@/utils/permissions'; import { canDeleteProjects, canUpdateProjects } from '@/utils/permissions';
import MoreOptionsDropdown from '@/Components/MoreOptionsDropdown.vue';
const emit = defineEmits<{ const emit = defineEmits<{
delete: []; delete: [];
edit: []; edit: [];
@@ -18,61 +18,37 @@ const props = defineProps<{
</script> </script>
<template> <template>
<Dropdown> <MoreOptionsDropdown :label="'Actions for Project ' + props.project.name">
<template #trigger> <div class="min-w-[150px]">
<button <button
class="focus-visible:outline-none focus-visible:bg-card-background rounded-full focus-visible:ring-1 focus-visible:ring-input-border-active focus-visible:opacity-100 hover:bg-card-background group-hover:opacity-100 opacity-20 transition-opacity" @click.prevent="emit('edit')"
data-testid="project_actions" v-if="canUpdateProjects()"
:aria-label="'Actions for Project ' + props.project.name"> :aria-label="'Edit Project ' + props.project.name"
<svg data-testid="project_edit"
class="h-10 w-10 p-2 rounded-full" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
viewBox="0 0 24 24" <PencilSquareIcon
xmlns="http://www.w3.org/2000/svg"> class="w-5 text-icon-active"></PencilSquareIcon>
<path <span>Edit</span>
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M12 5.92A.96.96 0 1 0 12 4a.96.96 0 0 0 0 1.92m0 7.04a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92M12 20a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92" />
</svg>
</button> </button>
</template> <button
<template #content> @click.prevent="emit('archive')"
<div class="min-w-[150px]"> v-if="canUpdateProjects()"
<button :aria-label="'Archive Project ' + props.project.name"
@click.prevent="emit('edit')" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
v-if="canUpdateProjects()" <ArchiveBoxIcon class="w-5 text-icon-active"></ArchiveBoxIcon>
:aria-label="'Edit Project ' + props.project.name" <span>{{ project.is_archived ? 'Unarchive' : 'Archive' }}</span>
data-testid="project_edit" </button>
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"> <button
<PencilSquareIcon @click.prevent="emit('delete')"
class="w-5 text-icon-active"></PencilSquareIcon> :aria-label="'Delete Project ' + props.project.name"
<span>Edit</span> data-testid="project_delete"
</button> v-if="canDeleteProjects()"
<button class="border-b border-card-background-separator flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
@click.prevent="emit('archive')" <TrashIcon class="w-5 text-icon-active"></TrashIcon>
v-if="canUpdateProjects()" <span>Delete</span>
:aria-label="'Archive Project ' + props.project.name" </button>
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"> </div>
<ArchiveBoxIcon </MoreOptionsDropdown>
class="w-5 text-icon-active"></ArchiveBoxIcon>
<span>{{
project.is_archived ? 'Unarchive' : 'Archive'
}}</span>
</button>
<button
@click.prevent="emit('delete')"
:aria-label="'Delete Project ' + props.project.name"
data-testid="project_delete"
v-if="canDeleteProjects()"
class="border-b border-card-background-separator flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>
</div>
</template>
</Dropdown>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import Dropdown from '@/Components/Dropdown.vue';
import { TrashIcon, PencilSquareIcon } from '@heroicons/vue/20/solid'; import { TrashIcon, PencilSquareIcon } from '@heroicons/vue/20/solid';
import type { ProjectMember } from '@/utils/api'; import type { ProjectMember } from '@/utils/api';
import { useMembersStore } from '@/utils/useMembers'; import { useMembersStore } from '@/utils/useMembers';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { computed } from 'vue'; import { computed } from 'vue';
import MoreOptionsDropdown from '@/Components/MoreOptionsDropdown.vue';
const emit = defineEmits<{ const emit = defineEmits<{
delete: []; delete: [];
@@ -24,46 +24,24 @@ const currentMember = computed(() => {
</script> </script>
<template> <template>
<Dropdown> <MoreOptionsDropdown
<template #trigger> :label="'Actions for Project Member ' + currentMember?.name">
<button <button
class="focus-visible:outline-none focus-visible:bg-card-background rounded-full focus-visible:ring-1 focus-visible:ring-input-border-active focus-visible:opacity-100 hover:bg-card-background group-hover:opacity-100 opacity-20 transition-opacity" @click.prevent="emit('edit')"
:aria-label=" :aria-label="'Edit Project Member ' + currentMember?.name"
'Actions for Project Member ' + currentMember?.name class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
"> <PencilSquareIcon class="w-5 text-icon-active"></PencilSquareIcon>
<svg <span>Edit</span>
class="h-10 w-10 p-2 rounded-full" </button>
viewBox="0 0 24 24" <button
xmlns="http://www.w3.org/2000/svg"> @click.prevent="emit('delete')"
<path :aria-label="'Delete Project Member ' + currentMember?.name"
fill="none" data-testid="project_delete"
stroke="currentColor" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
stroke-linecap="round" <TrashIcon class="w-5 text-icon-active"></TrashIcon>
stroke-linejoin="round" <span>Remove from Team</span>
stroke-width="1.5" </button>
d="M12 5.92A.96.96 0 1 0 12 4a.96.96 0 0 0 0 1.92m0 7.04a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92M12 20a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92" /> </MoreOptionsDropdown>
</svg>
</button>
</template>
<template #content>
<button
@click.prevent="emit('edit')"
:aria-label="'Edit Project Member ' + currentMember?.name"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<PencilSquareIcon
class="w-5 text-icon-active"></PencilSquareIcon>
<span>Edit</span>
</button>
<button
@click.prevent="emit('delete')"
:aria-label="'Delete Project Member ' + currentMember?.name"
data-testid="project_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Remove from Team</span>
</button>
</template>
</Dropdown>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import Dropdown from '@/Components/Dropdown.vue';
import { TrashIcon } from '@heroicons/vue/20/solid'; import { TrashIcon } from '@heroicons/vue/20/solid';
import type { Tag } from '@/utils/api'; import type { Tag } from '@/utils/api';
import MoreOptionsDropdown from '@/Components/MoreOptionsDropdown.vue';
const emit = defineEmits<{ const emit = defineEmits<{
delete: []; delete: [];
@@ -12,34 +12,16 @@ const props = defineProps<{
</script> </script>
<template> <template>
<Dropdown> <MoreOptionsDropdown :label="'Actions for Tag ' + props.tag.name">
<template #trigger> <button
<svg @click="emit('delete')"
data-testid="tag_actions" :aria-label="'Delete Tag ' + props.tag.name"
:aria-label="'Actions for Tag ' + props.tag.name" data-testid="tag_delete"
class="h-10 w-10 p-2 rounded-full hover:bg-card-background opacity-20 group-hover:opacity-100 transition" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
viewBox="0 0 24 24" <TrashIcon class="w-5 text-icon-active"></TrashIcon>
xmlns="http://www.w3.org/2000/svg"> <span>Delete</span>
<path </button>
fill="none" </MoreOptionsDropdown>
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M12 5.92A.96.96 0 1 0 12 4a.96.96 0 0 0 0 1.92m0 7.04a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92M12 20a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92" />
</svg>
</template>
<template #content>
<button
@click="emit('delete')"
:aria-label="'Delete Tag ' + props.tag.name"
data-testid="tag_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>
</template>
</Dropdown>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import Dropdown from '@/Components/Dropdown.vue';
import { import {
TrashIcon, TrashIcon,
PencilSquareIcon, PencilSquareIcon,
@@ -7,6 +6,7 @@ import {
} from '@heroicons/vue/20/solid'; } from '@heroicons/vue/20/solid';
import type { Task } from '@/utils/api'; import type { Task } from '@/utils/api';
import { canDeleteTasks, canUpdateTasks } from '@/utils/permissions'; import { canDeleteTasks, canUpdateTasks } from '@/utils/permissions';
import MoreOptionsDropdown from '@/Components/MoreOptionsDropdown.vue';
const emit = defineEmits<{ const emit = defineEmits<{
delete: []; delete: [];
edit: []; edit: [];
@@ -18,60 +18,38 @@ const props = defineProps<{
</script> </script>
<template> <template>
<Dropdown align="bottom-end"> <MoreOptionsDropdown :label="'Actions for Task ' + props.task.name">
<template #trigger> <div class="min-w-[150px]">
<button <button
class="focus-visible:outline-none focus-visible:bg-card-background rounded-full focus-visible:ring-1 focus-visible:ring-input-border-active focus-visible:opacity-100 hover:bg-card-background group-hover:opacity-100 opacity-20 transition-opacity" @click="emit('edit')"
data-testid="task_actions" v-if="canUpdateTasks()"
:aria-label="'Actions for Task ' + props.task.name"> :aria-label="'Edit Task ' + props.task.name"
<svg data-testid="task_edit"
class="h-10 w-10 p-2 rounded-full" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
viewBox="0 0 24 24" <PencilSquareIcon
xmlns="http://www.w3.org/2000/svg"> class="w-5 text-icon-active"></PencilSquareIcon>
<path <span>Edit</span>
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M12 5.92A.96.96 0 1 0 12 4a.96.96 0 0 0 0 1.92m0 7.04a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92M12 20a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92" />
</svg>
</button> </button>
</template> <button
<template #content> @click="emit('done')"
<div class="min-w-[150px]"> v-if="canUpdateTasks()"
<button :aria-label="'Mark Task ' + props.task.name + ' as done'"
@click="emit('edit')" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
v-if="canUpdateTasks()" <CheckCircleIcon class="w-5 text-icon-active"></CheckCircleIcon>
:aria-label="'Edit Task ' + props.task.name" <span v-if="props.task.is_done">Mark as active</span>
data-testid="task_edit" <span v-else>Mark as done</span>
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"> </button>
<PencilSquareIcon <button
class="w-5 text-icon-active"></PencilSquareIcon> @click="emit('delete')"
<span>Edit</span> :aria-label="'Delete Task ' + props.task.name"
</button> v-if="canDeleteTasks()"
<button data-testid="task_delete"
@click="emit('done')" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
v-if="canUpdateTasks()" <TrashIcon class="w-5 text-icon-active"></TrashIcon>
:aria-label="'Mark Task ' + props.task.name + ' as done'" <span>Delete</span>
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"> </button>
<CheckCircleIcon </div>
class="w-5 text-icon-active"></CheckCircleIcon> </MoreOptionsDropdown>
<span v-if="props.task.is_done">Mark as active</span>
<span v-else>Mark as done</span>
</button>
<button
@click="emit('delete')"
:aria-label="'Delete Task ' + props.task.name"
v-if="canDeleteTasks()"
data-testid="task_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>
</div>
</template>
</Dropdown>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -1,38 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import Dropdown from '@/Components/Dropdown.vue';
import { TrashIcon } from '@heroicons/vue/20/solid'; import { TrashIcon } from '@heroicons/vue/20/solid';
import MoreOptionsDropdown from '@/Components/MoreOptionsDropdown.vue';
const emit = defineEmits<{ const emit = defineEmits<{
delete: []; delete: [];
}>(); }>();
</script> </script>
<template> <template>
<Dropdown> <MoreOptionsDropdown label="Actions for the time entry">
<template #trigger> <button
<svg @click="emit('delete')"
data-testid="time_entry_actions" data-testid="time_entry_delete"
class="h-10 w-10 p-2 rounded-full hover:bg-card-background opacity-20 group-hover:opacity-100 transition" class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
viewBox="0 0 24 24" <TrashIcon class="w-5 text-icon-active"></TrashIcon>
xmlns="http://www.w3.org/2000/svg"> <span>Delete</span>
<path </button>
fill="none" </MoreOptionsDropdown>
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M12 5.92A.96.96 0 1 0 12 4a.96.96 0 0 0 0 1.92m0 7.04a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92M12 20a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92" />
</svg>
</template>
<template #content>
<button
@click="emit('delete')"
data-testid="time_entry_delete"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out">
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>
</template>
</Dropdown>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
import Dropdown from '@/Components/Dropdown.vue';
defineProps<{
label: string;
}>();
</script>
<template>
<Dropdown align="bottom-end">
<template #trigger>
<button
class="focus-visible:outline-none focus-visible:bg-card-background rounded-full focus-visible:ring-1 focus-visible:ring-input-border-active focus-visible:opacity-100 hover:bg-card-background group-hover:opacity-100 opacity-20 transition-opacity"
:aria-label="label">
<svg
class="h-10 w-10 p-2 rounded-full"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M12 5.92A.96.96 0 1 0 12 4a.96.96 0 0 0 0 1.92m0 7.04a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92M12 20a.96.96 0 1 0 0-1.92a.96.96 0 0 0 0 1.92" />
</svg>
</button>
</template>
<template #content>
<slot></slot>
</template>
</Dropdown>
</template>
<style scoped></style>