From 3fb75ec3d509168f6607343335fb7b3e4bb1dc5f Mon Sep 17 00:00:00 2001 From: Gregor Vostrak Date: Thu, 15 Jan 2026 18:44:42 +0100 Subject: [PATCH] add outline and secondary variants to TimeTrackerStartStop button to reduce visual complexity --- e2e/time.spec.ts | 4 +- .../js/Components/CurrentSidebarTimer.vue | 1 + .../RecentlyTrackedTasksCardEntry.vue | 4 +- .../ui/src/TimeEntry/TimeEntryRow.vue | 3 +- .../packages/ui/src/TimeTrackerStartStop.vue | 80 +++++++++++++------ resources/js/packages/ui/styles.css | 2 +- 6 files changed, 63 insertions(+), 31 deletions(-) diff --git a/e2e/time.spec.ts b/e2e/time.spec.ts index fbcb500d..8764d0d3 100644 --- a/e2e/time.spec.ts +++ b/e2e/time.spec.ts @@ -53,7 +53,7 @@ test('test that starting and stopping an empty time entry shows a new time entry // Test that description update works async function assertThatTimeEntryRowIsStopped(newTimeEntry: Locator) { - await expect(newTimeEntry.getByTestId('timer_button')).toHaveClass(/bg-accent-300\/70/); + await expect(newTimeEntry.getByTestId('timer_button')).toHaveClass(/bg-tertiary/); } test('test that updating a description of a time entry in the overview works on blur', async ({ @@ -224,7 +224,7 @@ test('test that starting a time entry from the overview works', async ({ page }) const newTimeEntry = timeEntryRows.first(); const startButton = newTimeEntry.getByTestId('timer_button'); - await expect(startButton).toHaveClass(/bg-accent-300\/70/); + await expect(startButton).toHaveClass(/bg-tertiary/); await Promise.all([ page.waitForResponse(async (response) => { diff --git a/resources/js/Components/CurrentSidebarTimer.vue b/resources/js/Components/CurrentSidebarTimer.vue index 3aede042..f5ea33d9 100644 --- a/resources/js/Components/CurrentSidebarTimer.vue +++ b/resources/js/Components/CurrentSidebarTimer.vue @@ -51,6 +51,7 @@ const isRunningInDifferentOrganization = computed(() => { diff --git a/resources/js/Components/Dashboard/RecentlyTrackedTasksCardEntry.vue b/resources/js/Components/Dashboard/RecentlyTrackedTasksCardEntry.vue index f6dca4b4..8473165d 100644 --- a/resources/js/Components/Dashboard/RecentlyTrackedTasksCardEntry.vue +++ b/resources/js/Components/Dashboard/RecentlyTrackedTasksCardEntry.vue @@ -66,7 +66,9 @@ async function startTaskTimer() {
- +
diff --git a/resources/js/packages/ui/src/TimeEntry/TimeEntryRow.vue b/resources/js/packages/ui/src/TimeEntry/TimeEntryRow.vue index 64101616..f85db7a2 100644 --- a/resources/js/packages/ui/src/TimeEntry/TimeEntryRow.vue +++ b/resources/js/packages/ui/src/TimeEntry/TimeEntryRow.vue @@ -163,7 +163,8 @@ async function handleDeleteTimeEntry() { @changed="updateStartEndTime"> -import { twMerge } from 'tailwind-merge'; -import { computed } from 'vue'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from './utils/cn'; + +const timeTrackerVariants = cva( + 'flex items-center justify-center transition focus:outline-0 rounded-full', + { + variants: { + variant: { + primary: + 'text-white ring-accent-200/10 focus-visible:ring-ring focus-visible:ring-2 ring-4 sm:ring-[6px]', + secondary: + 'bg-tertiary text-text-tertiary hover:text-text-primary focus:ring-2 focus:ring-border-tertiary', + outline: + 'border border-border-primary text-text-tertiary hover:text-text-primary hover:bg-tertiary/50', + }, + size: { + small: 'w-6 h-6', + base: 'w-9 h-9 hover:scale-110', + large: 'w-11 h-11 hover:scale-110', + }, + active: { + true: '', + false: '', + }, + }, + compoundVariants: [ + { + variant: 'primary', + active: true, + class: 'bg-red-400/80 hover:bg-red-500/80 focus:bg-red-500/80', + }, + { + variant: 'primary', + active: false, + class: 'bg-accent-300/70 hover:bg-accent-400/70 focus:bg-accent-700', + }, + ], + defaultVariants: { + variant: 'primary', + size: 'base', + active: false, + }, + } +); + +type TimeTrackerVariants = VariantProps; const emit = defineEmits(['changed']); const props = withDefaults( defineProps<{ - size?: 'base' | 'large' | 'small'; + variant?: TimeTrackerVariants['variant']; + size?: TimeTrackerVariants['size']; active?: boolean; }>(), { + variant: 'primary', size: 'base', active: false, } ); -const buttonSizeClasses = { - small: 'w-6 h-6 bg-accent-200/40 hover:bg-accent-300/70', - base: 'w-8 h-8 bg-accent-200/40 hover:scale-110 hover:bg-accent-300/70 ring-accent-200/10 focus-visible:ring-ring ring-4 hover:ring-4', - large: 'w-11 h-11 ring-accent-200/10 focus-visible:ring-ring focus-visible:ring-2 ring-4 sm:ring-[6px] hover:scale-110', -}; + const iconClass = { small: 'w-2.5 h-2.5', - base: 'w-3.5 h-3.5', + base: 'w-3 h-3', large: 'w-4 h-4', }; -const buttonColorClasses = computed(() => { - if (props.active) { - return 'bg-red-400/80 hover:bg-red-500/80 focus:bg-red-500/80'; - } else { - return 'bg-accent-300/70 hover:bg-accent-400/70 focus:bg-accent-700'; - } -}); - function toggleState() { emit('changed', !props.active); } @@ -41,18 +75,12 @@ function toggleState() {