qdadm 0.26.1 → 0.26.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qdadm",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.3",
|
|
4
4
|
"description": "Vue 3 framework for admin dashboards with PrimeVue",
|
|
5
5
|
"author": "quazardous",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"./components": "./src/components/index.js",
|
|
25
25
|
"./module": "./src/module/index.js",
|
|
26
26
|
"./utils": "./src/utils/index.js",
|
|
27
|
-
"./styles": "./src/styles/index.
|
|
27
|
+
"./styles": "./src/styles/index.scss",
|
|
28
|
+
"./styles/breakpoints": "./src/styles/_breakpoints.scss"
|
|
28
29
|
},
|
|
29
30
|
"files": [
|
|
30
31
|
"src",
|
|
@@ -35,11 +36,11 @@
|
|
|
35
36
|
"@quazardous/quarkernel": "^2.1.0"
|
|
36
37
|
},
|
|
37
38
|
"peerDependencies": {
|
|
38
|
-
"vue": "^3.3.0",
|
|
39
|
-
"vue-router": "^4.0.0",
|
|
40
|
-
"primevue": "^4.0.0",
|
|
41
39
|
"pinia": "^2.0.0",
|
|
42
|
-
"
|
|
40
|
+
"primevue": "^4.0.0",
|
|
41
|
+
"vanilla-jsoneditor": "^0.23.0",
|
|
42
|
+
"vue": "^3.3.0",
|
|
43
|
+
"vue-router": "^4.0.0"
|
|
43
44
|
},
|
|
44
45
|
"keywords": [
|
|
45
46
|
"vue",
|
|
@@ -55,6 +56,7 @@
|
|
|
55
56
|
"@vitejs/plugin-vue": "^5.2.1",
|
|
56
57
|
"@vue/test-utils": "^2.4.6",
|
|
57
58
|
"jsdom": "^25.0.1",
|
|
59
|
+
"sass": "^1.97.1",
|
|
58
60
|
"vitest": "^2.1.8"
|
|
59
61
|
}
|
|
60
62
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* </AppLayout>
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { ref, watch, onMounted, computed, inject, provide, useSlots } from 'vue'
|
|
15
|
+
import { ref, watch, onMounted, onUnmounted, computed, inject, provide, useSlots } from 'vue'
|
|
16
16
|
import { RouterLink, RouterView, useRouter, useRoute } from 'vue-router'
|
|
17
17
|
import { useNavigation } from '../../composables/useNavigation'
|
|
18
18
|
import { useApp } from '../../composables/useApp'
|
|
@@ -42,6 +42,30 @@ const STORAGE_KEY = computed(() => `${app.shortName.toLowerCase()}_nav_collapsed
|
|
|
42
42
|
// Collapsed sections state (section title -> boolean)
|
|
43
43
|
const collapsedSections = ref({})
|
|
44
44
|
|
|
45
|
+
// Mobile sidebar state
|
|
46
|
+
const sidebarOpen = ref(false)
|
|
47
|
+
const MOBILE_BREAKPOINT = 768
|
|
48
|
+
|
|
49
|
+
function toggleSidebar() {
|
|
50
|
+
sidebarOpen.value = !sidebarOpen.value
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function closeSidebar() {
|
|
54
|
+
sidebarOpen.value = false
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check if we're on mobile
|
|
58
|
+
function isMobile() {
|
|
59
|
+
return window.innerWidth < MOBILE_BREAKPOINT
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Close sidebar on resize to desktop
|
|
63
|
+
function handleResize() {
|
|
64
|
+
if (!isMobile() && sidebarOpen.value) {
|
|
65
|
+
sidebarOpen.value = false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
45
69
|
/**
|
|
46
70
|
* Load collapsed state from localStorage
|
|
47
71
|
*/
|
|
@@ -87,13 +111,21 @@ function isSectionExpanded(section) {
|
|
|
87
111
|
return !collapsedSections.value[section.title]
|
|
88
112
|
}
|
|
89
113
|
|
|
90
|
-
// Load state on mount
|
|
114
|
+
// Load state on mount + setup resize listener
|
|
91
115
|
onMounted(() => {
|
|
92
116
|
loadCollapsedState()
|
|
117
|
+
window.addEventListener('resize', handleResize)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
onUnmounted(() => {
|
|
121
|
+
window.removeEventListener('resize', handleResize)
|
|
93
122
|
})
|
|
94
123
|
|
|
95
|
-
// Auto-expand section when navigating to an item in it
|
|
124
|
+
// Auto-expand section when navigating to an item in it + close mobile sidebar
|
|
96
125
|
watch(() => route.path, () => {
|
|
126
|
+
// Close mobile sidebar on navigation
|
|
127
|
+
closeSidebar()
|
|
128
|
+
|
|
97
129
|
for (const section of navSections.value) {
|
|
98
130
|
if (sectionHasActiveItem(section) && collapsedSections.value[section.title]) {
|
|
99
131
|
// Auto-expand but don't save (user can collapse again if they want)
|
|
@@ -149,8 +181,15 @@ const showBreadcrumb = computed(() => {
|
|
|
149
181
|
|
|
150
182
|
<template>
|
|
151
183
|
<div class="app-layout">
|
|
184
|
+
<!-- Mobile overlay -->
|
|
185
|
+
<div
|
|
186
|
+
class="sidebar-overlay"
|
|
187
|
+
:class="{ 'sidebar-overlay--visible': sidebarOpen }"
|
|
188
|
+
@click="closeSidebar"
|
|
189
|
+
></div>
|
|
190
|
+
|
|
152
191
|
<!-- Sidebar -->
|
|
153
|
-
<aside class="sidebar">
|
|
192
|
+
<aside class="sidebar" :class="{ 'sidebar--open': sidebarOpen }">
|
|
154
193
|
<div class="sidebar-header">
|
|
155
194
|
<div class="sidebar-header-top">
|
|
156
195
|
<img v-if="app.logo" :src="app.logo" :alt="app.name" class="sidebar-logo" />
|
|
@@ -216,6 +255,19 @@ const showBreadcrumb = computed(() => {
|
|
|
216
255
|
|
|
217
256
|
<!-- Main content -->
|
|
218
257
|
<main class="main-content">
|
|
258
|
+
<!-- Mobile header bar -->
|
|
259
|
+
<div class="mobile-header">
|
|
260
|
+
<Button
|
|
261
|
+
icon="pi pi-bars"
|
|
262
|
+
severity="secondary"
|
|
263
|
+
text
|
|
264
|
+
class="hamburger-btn"
|
|
265
|
+
@click="toggleSidebar"
|
|
266
|
+
aria-label="Toggle menu"
|
|
267
|
+
/>
|
|
268
|
+
<span class="mobile-header-title">{{ app.name }}</span>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
219
271
|
<!-- Breadcrumb + Navlinks bar -->
|
|
220
272
|
<div v-if="showBreadcrumb" class="layout-nav-bar">
|
|
221
273
|
<Breadcrumb :model="breadcrumbItems" class="layout-breadcrumb">
|
|
@@ -562,4 +614,98 @@ const showBreadcrumb = computed(() => {
|
|
|
562
614
|
.layout-nav-bar :deep(.p-breadcrumb-separator) {
|
|
563
615
|
color: var(--p-surface-400, #94a3b8);
|
|
564
616
|
}
|
|
617
|
+
|
|
618
|
+
/* ============================================
|
|
619
|
+
Mobile / Responsive Styles
|
|
620
|
+
============================================ */
|
|
621
|
+
|
|
622
|
+
.mobile-header {
|
|
623
|
+
display: none;
|
|
624
|
+
align-items: center;
|
|
625
|
+
gap: 0.75rem;
|
|
626
|
+
padding: 0.75rem 1rem;
|
|
627
|
+
background: var(--p-surface-0, white);
|
|
628
|
+
border-bottom: 1px solid var(--p-surface-200, #e2e8f0);
|
|
629
|
+
position: sticky;
|
|
630
|
+
top: 0;
|
|
631
|
+
z-index: 50;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.mobile-header-title {
|
|
635
|
+
font-weight: 600;
|
|
636
|
+
font-size: 1.125rem;
|
|
637
|
+
color: var(--p-surface-800, #1e293b);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.hamburger-btn {
|
|
641
|
+
display: none !important;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/* Sidebar overlay for mobile */
|
|
645
|
+
.sidebar-overlay {
|
|
646
|
+
display: none;
|
|
647
|
+
position: fixed;
|
|
648
|
+
inset: 0;
|
|
649
|
+
background: rgba(0, 0, 0, 0.5);
|
|
650
|
+
z-index: 99;
|
|
651
|
+
opacity: 0;
|
|
652
|
+
transition: opacity 0.25s ease;
|
|
653
|
+
pointer-events: none;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/* Tablet breakpoint (< 768px) */
|
|
657
|
+
@media (max-width: 767px) {
|
|
658
|
+
.mobile-header {
|
|
659
|
+
display: flex;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
.hamburger-btn {
|
|
663
|
+
display: flex !important;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.sidebar {
|
|
667
|
+
transform: translateX(-100%);
|
|
668
|
+
transition: transform 0.25s ease;
|
|
669
|
+
box-shadow: none;
|
|
670
|
+
width: min(80vw, 280px);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.sidebar.sidebar--open {
|
|
674
|
+
transform: translateX(0);
|
|
675
|
+
box-shadow: 4px 0 20px rgba(0, 0, 0, 0.15);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.sidebar-overlay {
|
|
679
|
+
display: block;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.sidebar-overlay.sidebar-overlay--visible {
|
|
683
|
+
opacity: 1;
|
|
684
|
+
pointer-events: auto;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.main-content {
|
|
688
|
+
margin-left: 0;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
.page-content {
|
|
692
|
+
padding: 1rem;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
.layout-nav-bar {
|
|
696
|
+
padding: 1rem;
|
|
697
|
+
padding-bottom: 0;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/* Large tablet breakpoint (768px - 1023px) */
|
|
702
|
+
@media (min-width: 768px) and (max-width: 1023px) {
|
|
703
|
+
.sidebar {
|
|
704
|
+
width: 12rem;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.main-content {
|
|
708
|
+
margin-left: 12rem;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
565
711
|
</style>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ref, computed, watch, onMounted, inject, provide
|
|
1
|
+
import { ref, computed, watch, onMounted, inject, provide } from 'vue'
|
|
2
2
|
import { useRouter, useRoute } from 'vue-router'
|
|
3
3
|
import { useToast } from 'primevue/usetoast'
|
|
4
4
|
import { useConfirm } from 'primevue/useconfirm'
|
|
@@ -165,12 +165,19 @@ export function useListPageBuilder(config = {}) {
|
|
|
165
165
|
// Get HookRegistry for list:alter hook (optional, may not exist in tests)
|
|
166
166
|
const hooks = useHooks()
|
|
167
167
|
|
|
168
|
+
// ============ SESSION RESTORE ============
|
|
169
|
+
// Load saved filters + search from session storage (used by filterValues and searchQuery)
|
|
170
|
+
const savedSession = persistFilters ? getSessionFilters(filterSessionKey) : null
|
|
171
|
+
const savedSearch = savedSession?._search || ''
|
|
172
|
+
// Clone and remove _search from filters
|
|
173
|
+
const savedFilters = savedSession ? { ...savedSession } : null
|
|
174
|
+
if (savedFilters) delete savedFilters._search
|
|
175
|
+
|
|
168
176
|
// ============ STATE ============
|
|
169
177
|
const items = ref([])
|
|
170
178
|
const loading = ref(false)
|
|
171
179
|
const selected = ref([])
|
|
172
180
|
const deleting = ref(false)
|
|
173
|
-
let isRestoringFilters = false // Flag to skip watch during restore
|
|
174
181
|
|
|
175
182
|
// Pagination (load from cookie if available)
|
|
176
183
|
const page = ref(1)
|
|
@@ -182,8 +189,8 @@ export function useListPageBuilder(config = {}) {
|
|
|
182
189
|
const sortField = ref(defaultSort)
|
|
183
190
|
const sortOrder = ref(defaultSortOrder)
|
|
184
191
|
|
|
185
|
-
// Search
|
|
186
|
-
const searchQuery = ref(
|
|
192
|
+
// Search (initialized from session storage)
|
|
193
|
+
const searchQuery = ref(savedSearch)
|
|
187
194
|
const searchConfig = ref({
|
|
188
195
|
placeholder: 'Search...',
|
|
189
196
|
fields: [],
|
|
@@ -473,8 +480,7 @@ export function useListPageBuilder(config = {}) {
|
|
|
473
480
|
|
|
474
481
|
// ============ FILTERS ============
|
|
475
482
|
const filtersMap = ref(new Map())
|
|
476
|
-
//
|
|
477
|
-
const savedFilters = persistFilters ? getSessionFilters(filterSessionKey) : null
|
|
483
|
+
// filterValues initialized from savedFilters (loaded in SESSION RESTORE section)
|
|
478
484
|
const filterValues = ref(savedFilters || {})
|
|
479
485
|
|
|
480
486
|
function addFilter(name, filterConfig) {
|
|
@@ -897,55 +903,24 @@ export function useListPageBuilder(config = {}) {
|
|
|
897
903
|
}
|
|
898
904
|
|
|
899
905
|
/**
|
|
900
|
-
*
|
|
906
|
+
* Apply URL query params as overrides (session already loaded at init)
|
|
901
907
|
*/
|
|
902
908
|
function restoreFilters() {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
// Priority 1: URL query params
|
|
906
|
-
const urlFilters = {}
|
|
909
|
+
// URL params override session values
|
|
907
910
|
for (const key of filtersMap.value.keys()) {
|
|
908
911
|
if (route.query[key] !== undefined) {
|
|
909
|
-
// Parse value (handle booleans, numbers, etc.)
|
|
910
912
|
let value = route.query[key]
|
|
911
913
|
if (value === 'true') value = true
|
|
912
914
|
else if (value === 'false') value = false
|
|
913
915
|
else if (value === 'null') value = null
|
|
914
916
|
else if (!isNaN(Number(value)) && value !== '') value = Number(value)
|
|
915
|
-
|
|
917
|
+
filterValues.value[key] = value
|
|
916
918
|
}
|
|
917
919
|
}
|
|
918
|
-
//
|
|
920
|
+
// URL search overrides session search
|
|
919
921
|
if (route.query.search) {
|
|
920
922
|
searchQuery.value = route.query.search
|
|
921
923
|
}
|
|
922
|
-
|
|
923
|
-
// Priority 2: Session storage (only for filters/search not in URL)
|
|
924
|
-
const sessionData = persistFilters ? getSessionFilters(filterSessionKey) : null
|
|
925
|
-
|
|
926
|
-
// Extract search from session (stored as _search)
|
|
927
|
-
if (sessionData?._search && !route.query.search) {
|
|
928
|
-
searchQuery.value = sessionData._search
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
// Remove _search from session data before merging with filters
|
|
932
|
-
const sessionFilters = sessionData ? { ...sessionData } : null
|
|
933
|
-
if (sessionFilters) delete sessionFilters._search
|
|
934
|
-
|
|
935
|
-
// Merge: URL takes priority over session
|
|
936
|
-
const restoredFilters = { ...sessionFilters, ...urlFilters }
|
|
937
|
-
|
|
938
|
-
// Apply restored values
|
|
939
|
-
for (const [name, value] of Object.entries(restoredFilters)) {
|
|
940
|
-
if (filtersMap.value.has(name)) {
|
|
941
|
-
filterValues.value[name] = value
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// Reset flag after Vue processes updates
|
|
946
|
-
nextTick(() => {
|
|
947
|
-
isRestoringFilters = false
|
|
948
|
-
})
|
|
949
924
|
}
|
|
950
925
|
|
|
951
926
|
// ============ ACTIONS ============
|
|
@@ -1242,8 +1217,6 @@ export function useListPageBuilder(config = {}) {
|
|
|
1242
1217
|
// ============ WATCHERS ============
|
|
1243
1218
|
let searchTimeout = null
|
|
1244
1219
|
watch(searchQuery, () => {
|
|
1245
|
-
// Skip watch during restore (loadItems will be called after restore)
|
|
1246
|
-
if (isRestoringFilters) return
|
|
1247
1220
|
clearTimeout(searchTimeout)
|
|
1248
1221
|
searchTimeout = setTimeout(() => {
|
|
1249
1222
|
// Use onFiltersChanged to also sync URL params
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qdadm - Responsive Breakpoints
|
|
3
|
+
*
|
|
4
|
+
* Strategy: Desktop-first (PC > tablet > mobile)
|
|
5
|
+
*
|
|
6
|
+
* Usage in components:
|
|
7
|
+
* @use 'qdadm/styles/breakpoints' as *;
|
|
8
|
+
* .sidebar { @include tablet { width: 60px; } }
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Breakpoint values
|
|
12
|
+
$bp-desktop-lg: 1400px; // Large desktop
|
|
13
|
+
$bp-desktop: 1200px; // Standard desktop
|
|
14
|
+
$bp-tablet-lg: 1024px; // Large tablet / small laptop
|
|
15
|
+
$bp-tablet: 768px; // Tablet portrait
|
|
16
|
+
$bp-mobile-lg: 576px; // Large mobile
|
|
17
|
+
$bp-mobile: 480px; // Standard mobile
|
|
18
|
+
|
|
19
|
+
// Sidebar behavior threshold
|
|
20
|
+
$bp-sidebar-collapse: $bp-tablet-lg; // Sidebar collapses below this
|
|
21
|
+
$bp-sidebar-hidden: $bp-tablet; // Sidebar becomes drawer below this
|
|
22
|
+
|
|
23
|
+
// Mixins - Desktop-first (max-width)
|
|
24
|
+
@mixin desktop-lg {
|
|
25
|
+
@media (max-width: #{$bp-desktop-lg - 1px}) { @content; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@mixin desktop {
|
|
29
|
+
@media (max-width: #{$bp-desktop - 1px}) { @content; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@mixin tablet-lg {
|
|
33
|
+
@media (max-width: #{$bp-tablet-lg - 1px}) { @content; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@mixin tablet {
|
|
37
|
+
@media (max-width: #{$bp-tablet - 1px}) { @content; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@mixin mobile-lg {
|
|
41
|
+
@media (max-width: #{$bp-mobile-lg - 1px}) { @content; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@mixin mobile {
|
|
45
|
+
@media (max-width: #{$bp-mobile - 1px}) { @content; }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Utility: hide/show at breakpoints
|
|
49
|
+
@mixin hide-below($bp) {
|
|
50
|
+
@media (max-width: #{$bp - 1px}) { display: none !important; }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@mixin show-below($bp) {
|
|
54
|
+
display: none !important;
|
|
55
|
+
@media (max-width: #{$bp - 1px}) { display: block !important; }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Sidebar-specific mixins
|
|
59
|
+
@mixin sidebar-collapsed {
|
|
60
|
+
@media (max-width: #{$bp-sidebar-collapse - 1px}) { @content; }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@mixin sidebar-hidden {
|
|
64
|
+
@media (max-width: #{$bp-sidebar-hidden - 1px}) { @content; }
|
|
65
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qdadm - Responsive Layout Styles
|
|
3
|
+
*
|
|
4
|
+
* Global responsive rules for layout components.
|
|
5
|
+
* These complement the scoped styles in Vue components.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
@use 'breakpoints' as *;
|
|
9
|
+
|
|
10
|
+
// ============================================
|
|
11
|
+
// CSS Variables for responsive behavior
|
|
12
|
+
// ============================================
|
|
13
|
+
|
|
14
|
+
:root {
|
|
15
|
+
// Sidebar
|
|
16
|
+
--qdadm-sidebar-width: 15rem;
|
|
17
|
+
--qdadm-sidebar-collapsed-width: 4rem;
|
|
18
|
+
--qdadm-sidebar-transition: 0.25s ease;
|
|
19
|
+
|
|
20
|
+
// Content padding
|
|
21
|
+
--qdadm-content-padding: 1.5rem;
|
|
22
|
+
--qdadm-content-padding-mobile: 1rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ============================================
|
|
26
|
+
// App Layout Responsive
|
|
27
|
+
// ============================================
|
|
28
|
+
|
|
29
|
+
.app-layout {
|
|
30
|
+
// Tablet: reduce sidebar width
|
|
31
|
+
@include tablet-lg {
|
|
32
|
+
--qdadm-sidebar-width: 12rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Mobile: sidebar becomes overlay
|
|
36
|
+
@include tablet {
|
|
37
|
+
--qdadm-sidebar-width: 80vw;
|
|
38
|
+
max-width: 280px;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Sidebar responsive behavior
|
|
43
|
+
.sidebar {
|
|
44
|
+
transition: transform var(--qdadm-sidebar-transition),
|
|
45
|
+
width var(--qdadm-sidebar-transition);
|
|
46
|
+
|
|
47
|
+
@include tablet {
|
|
48
|
+
transform: translateX(-100%);
|
|
49
|
+
position: fixed;
|
|
50
|
+
z-index: 1000;
|
|
51
|
+
box-shadow: none;
|
|
52
|
+
|
|
53
|
+
&.sidebar--open {
|
|
54
|
+
transform: translateX(0);
|
|
55
|
+
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Main content responsive
|
|
61
|
+
.main-content {
|
|
62
|
+
transition: margin-left var(--qdadm-sidebar-transition);
|
|
63
|
+
|
|
64
|
+
@include tablet {
|
|
65
|
+
margin-left: 0 !important;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Mobile overlay when sidebar open
|
|
70
|
+
.sidebar-overlay {
|
|
71
|
+
display: none;
|
|
72
|
+
position: fixed;
|
|
73
|
+
inset: 0;
|
|
74
|
+
background: rgba(0, 0, 0, 0.5);
|
|
75
|
+
z-index: 99;
|
|
76
|
+
opacity: 0;
|
|
77
|
+
transition: opacity 0.25s;
|
|
78
|
+
|
|
79
|
+
@include tablet {
|
|
80
|
+
display: block;
|
|
81
|
+
pointer-events: none;
|
|
82
|
+
|
|
83
|
+
&.sidebar-overlay--visible {
|
|
84
|
+
opacity: 1;
|
|
85
|
+
pointer-events: auto;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Hamburger menu button (hidden on desktop)
|
|
91
|
+
.hamburger-btn {
|
|
92
|
+
display: none !important;
|
|
93
|
+
|
|
94
|
+
@include tablet {
|
|
95
|
+
display: flex !important;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ============================================
|
|
100
|
+
// Page Content Responsive
|
|
101
|
+
// ============================================
|
|
102
|
+
|
|
103
|
+
.page-content {
|
|
104
|
+
@include tablet {
|
|
105
|
+
padding: var(--qdadm-content-padding-mobile);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Page header responsive
|
|
110
|
+
.page-header {
|
|
111
|
+
@include tablet {
|
|
112
|
+
flex-direction: column;
|
|
113
|
+
gap: 1rem;
|
|
114
|
+
align-items: flex-start !important;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.page-header-actions {
|
|
118
|
+
@include tablet {
|
|
119
|
+
width: 100%;
|
|
120
|
+
justify-content: flex-start;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ============================================
|
|
126
|
+
// DataTable Responsive
|
|
127
|
+
// ============================================
|
|
128
|
+
|
|
129
|
+
.p-datatable {
|
|
130
|
+
@include tablet {
|
|
131
|
+
// Horizontal scroll for table
|
|
132
|
+
.p-datatable-wrapper {
|
|
133
|
+
overflow-x: auto;
|
|
134
|
+
-webkit-overflow-scrolling: touch;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Minimum width to prevent squishing
|
|
138
|
+
.p-datatable-table {
|
|
139
|
+
min-width: 600px;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@include mobile {
|
|
144
|
+
.p-datatable-table {
|
|
145
|
+
min-width: 500px;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ============================================
|
|
151
|
+
// Filter Bar Responsive
|
|
152
|
+
// ============================================
|
|
153
|
+
|
|
154
|
+
.filter-bar {
|
|
155
|
+
@include tablet {
|
|
156
|
+
flex-wrap: wrap;
|
|
157
|
+
gap: 0.75rem;
|
|
158
|
+
|
|
159
|
+
.filter-bar-search {
|
|
160
|
+
width: 100%;
|
|
161
|
+
order: -1;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.filter-bar-filters {
|
|
165
|
+
flex-wrap: wrap;
|
|
166
|
+
gap: 0.5rem;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.filter-bar-item {
|
|
170
|
+
min-width: calc(50% - 0.25rem);
|
|
171
|
+
flex: 1 1 auto;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@include mobile {
|
|
176
|
+
.filter-bar-item {
|
|
177
|
+
min-width: 100%;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ============================================
|
|
183
|
+
// Form Layout Responsive
|
|
184
|
+
// ============================================
|
|
185
|
+
|
|
186
|
+
.form-grid {
|
|
187
|
+
@include tablet {
|
|
188
|
+
grid-template-columns: 1fr !important;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.form-actions {
|
|
193
|
+
@include mobile {
|
|
194
|
+
flex-direction: column;
|
|
195
|
+
|
|
196
|
+
.p-button {
|
|
197
|
+
width: 100%;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ============================================
|
|
203
|
+
// Cards Grid Responsive
|
|
204
|
+
// ============================================
|
|
205
|
+
|
|
206
|
+
.cards-grid {
|
|
207
|
+
@include tablet {
|
|
208
|
+
grid-template-columns: repeat(2, 1fr);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@include mobile {
|
|
212
|
+
grid-template-columns: 1fr;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ============================================
|
|
217
|
+
// Utility Classes
|
|
218
|
+
// ============================================
|
|
219
|
+
|
|
220
|
+
.hide-mobile {
|
|
221
|
+
@include tablet {
|
|
222
|
+
display: none !important;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.hide-desktop {
|
|
227
|
+
display: none !important;
|
|
228
|
+
@include tablet {
|
|
229
|
+
display: block !important;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.hide-mobile-inline {
|
|
234
|
+
@include tablet {
|
|
235
|
+
display: none !important;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.show-mobile-inline {
|
|
240
|
+
display: none !important;
|
|
241
|
+
@include tablet {
|
|
242
|
+
display: inline !important;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qdadm - Styles entry point
|
|
3
|
+
*
|
|
4
|
+
* Import this file in your main.js:
|
|
5
|
+
* import 'qdadm/styles'
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* Main styles */
|
|
9
|
+
@use './main.css';
|
|
10
|
+
|
|
11
|
+
/* Component-specific styles */
|
|
12
|
+
@use './_alerts.css';
|
|
13
|
+
@use './_code.css';
|
|
14
|
+
@use './_dialogs.css';
|
|
15
|
+
@use './_markdown.css';
|
|
16
|
+
@use './_show-pages.css';
|
|
17
|
+
|
|
18
|
+
/* Responsive styles (must be last for specificity) */
|
|
19
|
+
@use './responsive';
|
package/src/styles/index.css
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* qdadm - Styles entry point
|
|
3
|
-
*
|
|
4
|
-
* Import this file in your main.js:
|
|
5
|
-
* import 'qdadm/styles'
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/* Main styles */
|
|
9
|
-
@import './main.css';
|
|
10
|
-
|
|
11
|
-
/* Component-specific styles */
|
|
12
|
-
@import './_alerts.css';
|
|
13
|
-
@import './_code.css';
|
|
14
|
-
@import './_dialogs.css';
|
|
15
|
-
@import './_markdown.css';
|
|
16
|
-
@import './_show-pages.css';
|