feat(unified-mobile-desktop-ui): Complete US-508 - Selector Temă în MobileDrawerMenu (Dark/Light/Auto)
Implemented by Ralph autonomous loop. Iteration: 8 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -182,8 +182,8 @@
|
|||||||
"npm run build passes",
|
"npm run build passes",
|
||||||
"Verify in browser mobil: tema se schimbă corect din drawer"
|
"Verify in browser mobil: tema se schimbă corect din drawer"
|
||||||
],
|
],
|
||||||
"passes": false,
|
"passes": true,
|
||||||
"notes": ""
|
"notes": "Completed in iteration 8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "US-509",
|
"id": "US-509",
|
||||||
|
|||||||
@@ -112,3 +112,9 @@ Design Reference: src/modules/reports/views/InvoicesView.vue
|
|||||||
[2026-01-12 22:36:40] Working on story: US-507
|
[2026-01-12 22:36:40] Working on story: US-507
|
||||||
[2026-01-12 22:36:40] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_7_US-507.log)
|
[2026-01-12 22:36:40] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_7_US-507.log)
|
||||||
[2026-01-12 22:42:54] SUCCESS: Story US-507 passed!
|
[2026-01-12 22:42:54] SUCCESS: Story US-507 passed!
|
||||||
|
[2026-01-12 22:42:54] Changes committed
|
||||||
|
[2026-01-12 22:42:54] Progress: 7/19 stories completed
|
||||||
|
[2026-01-12 22:42:56] === Iteration 8/100 ===
|
||||||
|
[2026-01-12 22:42:56] Working on story: US-508
|
||||||
|
[2026-01-12 22:42:56] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_8_US-508.log)
|
||||||
|
[2026-01-12 22:45:25] SUCCESS: Story US-508 passed!
|
||||||
|
|||||||
@@ -194,6 +194,33 @@
|
|||||||
<span class="profile-role">Utilizator</span>
|
<span class="profile-role">Utilizator</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Theme Selector -->
|
||||||
|
<div class="theme-selector">
|
||||||
|
<div class="theme-selector-label">
|
||||||
|
<i class="pi pi-palette"></i>
|
||||||
|
<span>Temă</span>
|
||||||
|
</div>
|
||||||
|
<div class="theme-options">
|
||||||
|
<button
|
||||||
|
v-for="option in themeOptions"
|
||||||
|
:key="option.value"
|
||||||
|
class="theme-option"
|
||||||
|
:class="{ active: currentTheme === option.value }"
|
||||||
|
@click="selectTheme(option.value)"
|
||||||
|
:title="option.label"
|
||||||
|
:aria-label="`Selectează tema ${option.label}`"
|
||||||
|
:aria-pressed="currentTheme === option.value"
|
||||||
|
>
|
||||||
|
<i :class="option.icon"></i>
|
||||||
|
<span class="theme-option-label">{{ option.label }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Divider before logout -->
|
||||||
|
<div class="drawer-divider profile-divider"></div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="drawer-link logout-link"
|
class="drawer-link logout-link"
|
||||||
@@ -210,7 +237,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, watch, nextTick } from 'vue'
|
import { computed, ref, watch, nextTick, onMounted } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -457,6 +484,56 @@ const handleLogout = async () => {
|
|||||||
}
|
}
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme Toggle Logic
|
||||||
|
* Supports three modes: auto (system), light, dark
|
||||||
|
* Persists preference in localStorage['user-theme']
|
||||||
|
*/
|
||||||
|
const currentTheme = ref('auto')
|
||||||
|
|
||||||
|
// Theme options with icons and labels
|
||||||
|
const themeOptions = [
|
||||||
|
{ value: 'auto', icon: 'pi pi-desktop', label: 'Auto (sistem)' },
|
||||||
|
{ value: 'light', icon: 'pi pi-sun', label: 'Light' },
|
||||||
|
{ value: 'dark', icon: 'pi pi-moon', label: 'Dark' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// Get current theme option for display
|
||||||
|
const currentThemeOption = computed(() => {
|
||||||
|
return themeOptions.find(opt => opt.value === currentTheme.value) || themeOptions[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply theme to document
|
||||||
|
*/
|
||||||
|
const applyTheme = (theme) => {
|
||||||
|
if (theme === 'auto') {
|
||||||
|
document.documentElement.removeAttribute('data-theme')
|
||||||
|
localStorage.removeItem('user-theme')
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute('data-theme', theme)
|
||||||
|
localStorage.setItem('user-theme', theme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a specific theme
|
||||||
|
*/
|
||||||
|
const selectTheme = (theme) => {
|
||||||
|
currentTheme.value = theme
|
||||||
|
applyTheme(theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize theme from localStorage on mount
|
||||||
|
onMounted(() => {
|
||||||
|
const savedTheme = localStorage.getItem('user-theme')
|
||||||
|
if (savedTheme === 'light' || savedTheme === 'dark') {
|
||||||
|
currentTheme.value = savedTheme
|
||||||
|
} else {
|
||||||
|
currentTheme.value = 'auto'
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -870,6 +947,83 @@ const handleLogout = async () => {
|
|||||||
background: var(--red-50);
|
background: var(--red-50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Profile divider (spacing adjustment) */
|
||||||
|
.profile-divider {
|
||||||
|
margin: var(--space-sm) var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================
|
||||||
|
Theme Selector
|
||||||
|
================================================ */
|
||||||
|
|
||||||
|
.theme-selector {
|
||||||
|
padding: var(--space-sm) var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-selector-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: var(--font-medium);
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-selector-label i {
|
||||||
|
font-size: var(--text-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-options {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
background: var(--surface-ground);
|
||||||
|
padding: var(--space-xs);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-option {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
padding: var(--space-sm);
|
||||||
|
min-height: 48px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-option:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-option:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-option.active {
|
||||||
|
background: var(--surface-card);
|
||||||
|
color: var(--color-primary);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-option i {
|
||||||
|
font-size: var(--text-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-option-label {
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
font-weight: var(--font-medium);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
/* ================================================
|
/* ================================================
|
||||||
Slide Animation (Vue Transition)
|
Slide Animation (Vue Transition)
|
||||||
================================================ */
|
================================================ */
|
||||||
@@ -1080,6 +1234,30 @@ const handleLogout = async () => {
|
|||||||
background: var(--red-900);
|
background: var(--red-900);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dark mode: Theme Selector */
|
||||||
|
[data-theme="dark"] .theme-selector-label {
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .theme-options {
|
||||||
|
background: var(--surface-ground);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .theme-option {
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .theme-option:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .theme-option.active {
|
||||||
|
background: var(--surface-card);
|
||||||
|
color: var(--blue-400);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
/* Auto dark mode (when no manual theme is set) */
|
/* Auto dark mode (when no manual theme is set) */
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root:not([data-theme]) .drawer-overlay {
|
:root:not([data-theme]) .drawer-overlay {
|
||||||
@@ -1250,5 +1428,29 @@ const handleLogout = async () => {
|
|||||||
:root:not([data-theme]) .logout-link:hover {
|
:root:not([data-theme]) .logout-link:hover {
|
||||||
background: var(--red-900);
|
background: var(--red-900);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Auto dark mode: Theme Selector */
|
||||||
|
:root:not([data-theme]) .theme-selector-label {
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root:not([data-theme]) .theme-options {
|
||||||
|
background: var(--surface-ground);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root:not([data-theme]) .theme-option {
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root:not([data-theme]) .theme-option:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root:not([data-theme]) .theme-option.active {
|
||||||
|
background: var(--surface-card);
|
||||||
|
color: var(--blue-400);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user