feat: tooltips, two-column timer, font selector, tray behavior, icons, readme
- Custom tooltip directive (WCAG AAA) on every button in the app - Two-column timer layout with sticky hero and recent entries sidebar - Timer font selector with 16 monospace Google Fonts and live preview - UI font selector with 15+ Google Fonts - Close-to-tray and minimize-to-tray settings - New app icons (no-glow variants), platform icon set - Mini timer pop-out window - Favorites strip with drag-reorder and inline actions - Comprehensive README with feature documentation - Remove tracked files that belong in gitignore
This commit is contained in:
137
src/components/RecurringPromptDialog.vue
Normal file
137
src/components/RecurringPromptDialog.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<script setup lang="ts">
|
||||
import { watch, ref, computed } from 'vue'
|
||||
import { Clock, Calendar } from 'lucide-vue-next'
|
||||
import { useFocusTrap } from '../utils/focusTrap'
|
||||
|
||||
interface Props {
|
||||
show: boolean
|
||||
projectName: string
|
||||
projectColor?: string
|
||||
taskName?: string
|
||||
description?: string
|
||||
duration: number
|
||||
timeOfDay: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
projectColor: '#6B7280',
|
||||
taskName: '',
|
||||
description: '',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
confirm: []
|
||||
snooze: []
|
||||
skip: []
|
||||
}>()
|
||||
|
||||
const { activate, deactivate } = useFocusTrap()
|
||||
const dialogRef = ref<HTMLElement | null>(null)
|
||||
const announcement = ref('')
|
||||
|
||||
const formattedDuration = computed(() => {
|
||||
const totalSeconds = props.duration
|
||||
const h = Math.floor(totalSeconds / 3600)
|
||||
const m = Math.floor((totalSeconds % 3600) / 60)
|
||||
if (h === 0) return `${m}m`
|
||||
return `${h}h ${m}m`
|
||||
})
|
||||
|
||||
watch(() => props.show, (val) => {
|
||||
if (val) {
|
||||
announcement.value = `Recurring entry ready: ${props.projectName} - ${props.description || 'Scheduled task'}`
|
||||
setTimeout(() => {
|
||||
if (dialogRef.value) activate(dialogRef.value, { onDeactivate: () => emit('skip') })
|
||||
}, 50)
|
||||
} else {
|
||||
deactivate()
|
||||
announcement.value = ''
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="#app">
|
||||
<Transition name="modal">
|
||||
<div
|
||||
v-if="show"
|
||||
class="fixed inset-0 bg-black/70 backdrop-blur-[4px] flex items-center justify-center p-4 z-[60]"
|
||||
@click.self="$emit('skip')"
|
||||
>
|
||||
<div
|
||||
ref="dialogRef"
|
||||
role="alertdialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="recurring-prompt-title"
|
||||
class="bg-bg-surface border border-border-subtle rounded-lg shadow-[0_1px_3px_rgba(0,0,0,0.3)] w-full max-w-sm p-6"
|
||||
>
|
||||
<h2
|
||||
id="recurring-prompt-title"
|
||||
class="text-[1.125rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-4"
|
||||
>
|
||||
Recurring Entry Ready
|
||||
</h2>
|
||||
|
||||
<div class="space-y-3 mb-6">
|
||||
<!-- Project -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="w-2.5 h-2.5 rounded-full shrink-0"
|
||||
:style="{ backgroundColor: projectColor }"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span class="text-[0.8125rem] font-medium text-text-primary">{{ projectName }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Task name -->
|
||||
<div v-if="taskName" class="text-[0.75rem] text-text-secondary pl-[1.125rem]">
|
||||
{{ taskName }}
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div v-if="description" class="text-[0.75rem] text-text-secondary pl-[1.125rem]">
|
||||
{{ description }}
|
||||
</div>
|
||||
|
||||
<!-- Duration -->
|
||||
<div class="flex items-center gap-2 text-[0.75rem] text-text-secondary">
|
||||
<Clock class="w-3.5 h-3.5 shrink-0" :stroke-width="1.5" aria-hidden="true" />
|
||||
<span>{{ formattedDuration }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Time of day -->
|
||||
<div v-if="timeOfDay" class="flex items-center gap-2 text-[0.75rem] text-text-secondary">
|
||||
<Calendar class="w-3.5 h-3.5 shrink-0" :stroke-width="1.5" aria-hidden="true" />
|
||||
<span>{{ timeOfDay }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<button
|
||||
@click="$emit('skip')"
|
||||
class="px-3 py-1.5 text-[0.75rem] text-text-tertiary hover:text-text-secondary transition-colors"
|
||||
>
|
||||
Skip
|
||||
</button>
|
||||
<button
|
||||
@click="$emit('snooze')"
|
||||
class="px-3 py-1.5 text-[0.75rem] font-medium border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors"
|
||||
>
|
||||
Snooze 30min
|
||||
</button>
|
||||
<button
|
||||
@click="$emit('confirm')"
|
||||
class="px-3 py-1.5 text-[0.75rem] font-medium bg-accent text-bg-base rounded-lg hover:bg-accent-hover transition-colors"
|
||||
>
|
||||
Create Entry
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
|
||||
<!-- aria-live announcement -->
|
||||
<div class="sr-only" aria-live="assertive" aria-atomic="true">{{ announcement }}</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user