refactor: centralize UI config and inject CSS variables dynamically
This commit is contained in:
12
src/App.vue
12
src/App.vue
@@ -6,6 +6,7 @@ import Footer from './components/Footer.vue'
|
|||||||
import Sidebar from './components/Sidebar.vue'
|
import Sidebar from './components/Sidebar.vue'
|
||||||
import InstallPrompt from './components/InstallPrompt.vue'
|
import InstallPrompt from './components/InstallPrompt.vue'
|
||||||
import ReloadPrompt from './components/ReloadPrompt.vue'
|
import ReloadPrompt from './components/ReloadPrompt.vue'
|
||||||
|
import { UI_CONFIG } from './config/ui'
|
||||||
|
|
||||||
const isSidebarOpen = ref(window.innerWidth >= 768)
|
const isSidebarOpen = ref(window.innerWidth >= 768)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -31,6 +32,11 @@ const handleResize = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// Set global CSS variables from config
|
||||||
|
document.documentElement.style.setProperty('--header-height', `${UI_CONFIG.headerHeight}px`)
|
||||||
|
document.documentElement.style.setProperty('--footer-height', `${UI_CONFIG.footerHeight}px`)
|
||||||
|
document.documentElement.style.setProperty('--page-padding', `${UI_CONFIG.pagePadding}px`)
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize)
|
window.addEventListener('resize', handleResize)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -70,14 +76,14 @@ onUnmounted(() => {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
/* Space for fixed footer on mobile + extra margin (match top padding 2rem + footer height ~40px) */
|
/* Space for fixed footer on mobile + extra margin */
|
||||||
padding-bottom: calc(1rem + 40px + env(safe-area-inset-bottom));
|
padding-bottom: calc(1rem + var(--footer-height) + env(safe-area-inset-bottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.main-content {
|
.main-content {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
padding-bottom: calc(0.5rem + 40px + env(safe-area-inset-bottom));
|
padding-bottom: calc(0.5rem + var(--footer-height) + env(safe-area-inset-bottom));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ const version = __APP_VERSION__;
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.app-footer {
|
.app-footer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.5rem;
|
height: var(--footer-height);
|
||||||
|
padding: 0 0.5rem;
|
||||||
/* Background handled by glass-panel */
|
/* Background handled by glass-panel */
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
@@ -24,12 +25,9 @@ const version = __APP_VERSION__;
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
/* Remove fixed height to allow content to dictate size */
|
|
||||||
/* height: 30px; */
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
padding-bottom: max(0.5rem, env(safe-area-inset-bottom));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
|
|||||||
@@ -65,7 +65,10 @@ onMounted(() => {
|
|||||||
/* Remove hardcoded colors and use theme variables */
|
/* Remove hardcoded colors and use theme variables */
|
||||||
background: var(--header-bg);
|
background: var(--header-bg);
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
padding: 1rem;
|
height: var(--header-height);
|
||||||
|
padding: 0 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
/* box-shadow handled by glass-panel class */
|
/* box-shadow handled by glass-panel class */
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -77,7 +80,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header-content {
|
.header-content {
|
||||||
max-width: 100%;
|
width: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const {
|
|||||||
stopListening
|
stopListening
|
||||||
} = useExtension()
|
} = useExtension()
|
||||||
|
|
||||||
const { height: textareaHeight } = useFillHeight(textareaRef, 120)
|
const { height: textareaHeight } = useFillHeight(textareaRef, 40)
|
||||||
|
|
||||||
// Watch for clipboard updates from extension
|
// Watch for clipboard updates from extension
|
||||||
watch(lastClipboardText, (newText) => {
|
watch(lastClipboardText, (newText) => {
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ const generatePasswords = () => {
|
|||||||
class="tool-textarea"
|
class="tool-textarea"
|
||||||
v-model="result"
|
v-model="result"
|
||||||
placeholder="Generated passwords will appear here..."
|
placeholder="Generated passwords will appear here..."
|
||||||
readonly
|
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const format = useLocalStorage('format', 'png', 'qr-code')
|
|||||||
const svgContent = ref('')
|
const svgContent = ref('')
|
||||||
const previewRef = ref(null)
|
const previewRef = ref(null)
|
||||||
|
|
||||||
const { height: previewHeight } = useFillHeight(previewRef, 40) // 40px margin bottom
|
const { height: previewHeight } = useFillHeight(previewRef, 40) // 40px extra margin
|
||||||
|
|
||||||
const generateQR = async () => {
|
const generateQR = async () => {
|
||||||
if (!text.value) {
|
if (!text.value) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { onMounted, onUnmounted, ref, nextTick } from 'vue'
|
import { onMounted, onUnmounted, ref, nextTick } from 'vue'
|
||||||
|
import { UI_CONFIG } from '../config/ui'
|
||||||
|
|
||||||
export function useFillHeight(elementRef, marginBottom = 20) {
|
export function useFillHeight(elementRef, extraMargin = 0) {
|
||||||
const height = ref('auto')
|
const height = ref('auto')
|
||||||
|
|
||||||
const updateHeight = () => {
|
const updateHeight = () => {
|
||||||
@@ -8,16 +9,10 @@ export function useFillHeight(elementRef, marginBottom = 20) {
|
|||||||
|
|
||||||
const rect = elementRef.value.getBoundingClientRect()
|
const rect = elementRef.value.getBoundingClientRect()
|
||||||
const windowHeight = window.innerHeight
|
const windowHeight = window.innerHeight
|
||||||
// Calculate available space: window height - element top position - margin bottom
|
|
||||||
// We also need to account for the footer height if it's fixed or layout related
|
|
||||||
// The user mentioned "margin bottom from footer".
|
|
||||||
// If footer is in the flow, we might just want to fill the parent container?
|
|
||||||
// But user asked for JS resizing.
|
|
||||||
|
|
||||||
// Let's assume we want to fill down to (windowHeight - marginBottom).
|
// Calculate available space: window height - element top position - footer height - padding - extra margin
|
||||||
// This assumes the element should stretch to the bottom of the viewport.
|
const bottomOffset = UI_CONFIG.footerHeight + UI_CONFIG.pagePadding + extraMargin
|
||||||
|
const availableHeight = windowHeight - rect.top - bottomOffset
|
||||||
const availableHeight = windowHeight - rect.top - marginBottom
|
|
||||||
|
|
||||||
// Ensure minimum height
|
// Ensure minimum height
|
||||||
if (availableHeight > 100) {
|
if (availableHeight > 100) {
|
||||||
|
|||||||
6
src/config/ui.js
Normal file
6
src/config/ui.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
export const UI_CONFIG = {
|
||||||
|
headerHeight: 64,
|
||||||
|
footerHeight: 50,
|
||||||
|
pagePadding: 16 // Single side padding (1rem)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user