feat: add theme customization with accent colors and light mode
This commit is contained in:
@@ -91,6 +91,44 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-border-subtle mt-5 pt-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Theme</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Light or dark appearance</p>
|
||||
</div>
|
||||
<div class="w-40">
|
||||
<AppSelect
|
||||
v-model="themeMode"
|
||||
:options="themeModes"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
placeholder="Dark"
|
||||
:placeholder-value="'dark'"
|
||||
@update:model-value="saveThemeSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between mt-5">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Accent Color</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Primary interface color</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
v-for="ac in accentColors"
|
||||
:key="ac.value"
|
||||
@click="accentColor = ac.value; saveThemeSettings()"
|
||||
class="w-6 h-6 rounded-full border-2 transition-all hover:scale-110 cursor-pointer"
|
||||
:class="accentColor === ac.value ? 'border-text-primary scale-110' : 'border-transparent'"
|
||||
:style="{ backgroundColor: ac.color }"
|
||||
:title="ac.label"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Timer -->
|
||||
@@ -120,22 +158,76 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Reminder Interval — progressive disclosure -->
|
||||
<div
|
||||
v-if="idleDetection"
|
||||
class="flex items-center justify-between pl-4 border-l-2 border-border-subtle ml-1"
|
||||
>
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Reminder Interval</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Minutes between reminders while running</p>
|
||||
<!-- Idle sub-settings — progressive disclosure -->
|
||||
<div v-if="idleDetection" class="space-y-5 pl-4 border-l-2 border-border-subtle ml-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Idle Timeout</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Minutes before idle pause triggers</p>
|
||||
</div>
|
||||
<AppNumberInput
|
||||
v-model="idleTimeout"
|
||||
:min="1"
|
||||
:max="60"
|
||||
:step="1"
|
||||
:precision="0"
|
||||
suffix="min"
|
||||
@update:model-value="saveSettings"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
v-model.number="reminderInterval"
|
||||
type="number"
|
||||
min="0"
|
||||
max="120"
|
||||
class="w-20 px-3 py-1.5 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary text-right font-mono focus:outline-none focus:border-border-visible"
|
||||
@change="saveSettings"
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Reminder Interval</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">Minutes between reminders while running</p>
|
||||
</div>
|
||||
<AppNumberInput
|
||||
v-model="reminderInterval"
|
||||
:min="0"
|
||||
:max="120"
|
||||
:step="5"
|
||||
:precision="0"
|
||||
suffix="min"
|
||||
@update:model-value="saveSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="border-t border-border-subtle" />
|
||||
|
||||
<!-- App Tracking Mode -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">App Tracking Mode</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">What happens when a tracked app leaves focus</p>
|
||||
</div>
|
||||
<div class="w-48">
|
||||
<AppSelect
|
||||
v-model="appTrackingMode"
|
||||
:options="appTrackingModes"
|
||||
label-key="label"
|
||||
value-key="value"
|
||||
placeholder="Auto-pause"
|
||||
:placeholder-value="'auto'"
|
||||
@update:model-value="saveSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Check Interval -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-[0.8125rem] text-text-primary">Check Interval</p>
|
||||
<p class="text-[0.6875rem] text-text-tertiary mt-0.5">How often to check idle & app visibility</p>
|
||||
</div>
|
||||
<AppNumberInput
|
||||
v-model="appCheckInterval"
|
||||
:min="1"
|
||||
:max="60"
|
||||
:step="1"
|
||||
:precision="0"
|
||||
suffix="sec"
|
||||
@update:model-value="saveSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -259,11 +351,38 @@ const activeTab = ref('general')
|
||||
// Settings state
|
||||
const hourlyRate = ref(0)
|
||||
const idleDetection = ref(true)
|
||||
const idleTimeout = ref(5)
|
||||
const reminderInterval = ref(15)
|
||||
const appTrackingMode = ref('auto')
|
||||
const appCheckInterval = ref(5)
|
||||
const zoomLevel = ref(100)
|
||||
const locale = ref('system')
|
||||
const currency = ref('USD')
|
||||
const currencyOptions = getCurrencies()
|
||||
const themeMode = ref('dark')
|
||||
const accentColor = ref('amber')
|
||||
|
||||
const themeModes = [
|
||||
{ value: 'dark', label: 'Dark' },
|
||||
{ value: 'light', label: 'Light' },
|
||||
{ value: 'system', label: 'System' },
|
||||
]
|
||||
|
||||
const accentColors = [
|
||||
{ value: 'amber', label: 'Amber', color: '#D97706' },
|
||||
{ value: 'blue', label: 'Blue', color: '#3B82F6' },
|
||||
{ value: 'purple', label: 'Purple', color: '#8B5CF6' },
|
||||
{ value: 'green', label: 'Green', color: '#10B981' },
|
||||
{ value: 'red', label: 'Red', color: '#EF4444' },
|
||||
{ value: 'pink', label: 'Pink', color: '#EC4899' },
|
||||
{ value: 'cyan', label: 'Cyan', color: '#06B6D4' },
|
||||
]
|
||||
|
||||
const appTrackingModes = [
|
||||
{ value: 'auto', label: 'Auto-pause/resume' },
|
||||
{ value: 'notify', label: 'Notify + auto-resume' },
|
||||
{ value: 'prompt', label: 'Prompt user' },
|
||||
]
|
||||
|
||||
// Dialog state
|
||||
const showClearDataDialog = ref(false)
|
||||
@@ -312,7 +431,10 @@ async function saveSettings() {
|
||||
try {
|
||||
await settingsStore.updateSetting('hourly_rate', hourlyRate.value.toString())
|
||||
await settingsStore.updateSetting('idle_detection', idleDetection.value.toString())
|
||||
await settingsStore.updateSetting('idle_timeout', idleTimeout.value.toString())
|
||||
await settingsStore.updateSetting('reminder_interval', reminderInterval.value.toString())
|
||||
await settingsStore.updateSetting('app_tracking_mode', appTrackingMode.value)
|
||||
await settingsStore.updateSetting('app_check_interval', appCheckInterval.value.toString())
|
||||
} catch (error) {
|
||||
console.error('Failed to save settings:', error)
|
||||
toastStore.error('Failed to save settings')
|
||||
@@ -325,6 +447,12 @@ async function saveLocaleSettings() {
|
||||
await settingsStore.updateSetting('currency', currency.value)
|
||||
}
|
||||
|
||||
// Save theme settings
|
||||
async function saveThemeSettings() {
|
||||
await settingsStore.updateSetting('theme_mode', themeMode.value)
|
||||
await settingsStore.updateSetting('accent_color', accentColor.value)
|
||||
}
|
||||
|
||||
// Export all data
|
||||
async function exportData() {
|
||||
try {
|
||||
@@ -362,9 +490,14 @@ onMounted(async () => {
|
||||
|
||||
hourlyRate.value = parseFloat(settingsStore.settings.hourly_rate) || 0
|
||||
idleDetection.value = settingsStore.settings.idle_detection !== 'false'
|
||||
idleTimeout.value = parseInt(settingsStore.settings.idle_timeout) || 5
|
||||
reminderInterval.value = parseInt(settingsStore.settings.reminder_interval) || 15
|
||||
appTrackingMode.value = settingsStore.settings.app_tracking_mode || 'auto'
|
||||
appCheckInterval.value = parseInt(settingsStore.settings.app_check_interval) || 5
|
||||
zoomLevel.value = parseInt(settingsStore.settings.ui_zoom) || 100
|
||||
locale.value = settingsStore.settings.locale || 'system'
|
||||
currency.value = settingsStore.settings.currency || 'USD'
|
||||
themeMode.value = settingsStore.settings.theme_mode || 'dark'
|
||||
accentColor.value = settingsStore.settings.accent_color || 'amber'
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user