@omnitend/dashboard-for-laravel 0.4.7

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.
Files changed (149) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +397 -0
  3. package/dist/components/base/DAccordion.vue.d.ts +12 -0
  4. package/dist/components/base/DAccordionItem.vue.d.ts +12 -0
  5. package/dist/components/base/DAlert.vue.d.ts +12 -0
  6. package/dist/components/base/DAvatar.vue.d.ts +12 -0
  7. package/dist/components/base/DBadge.vue.d.ts +12 -0
  8. package/dist/components/base/DBreadcrumb.vue.d.ts +12 -0
  9. package/dist/components/base/DButton.vue.d.ts +29 -0
  10. package/dist/components/base/DButtonGroup.vue.d.ts +12 -0
  11. package/dist/components/base/DButtonToolbar.vue.d.ts +12 -0
  12. package/dist/components/base/DCard.vue.d.ts +12 -0
  13. package/dist/components/base/DCarousel.vue.d.ts +12 -0
  14. package/dist/components/base/DCarouselSlide.vue.d.ts +12 -0
  15. package/dist/components/base/DCol.vue.d.ts +12 -0
  16. package/dist/components/base/DCollapse.vue.d.ts +12 -0
  17. package/dist/components/base/DContainer.vue.d.ts +12 -0
  18. package/dist/components/base/DDropdown.vue.d.ts +12 -0
  19. package/dist/components/base/DDropdownDivider.vue.d.ts +2 -0
  20. package/dist/components/base/DDropdownItem.vue.d.ts +12 -0
  21. package/dist/components/base/DForm.vue.d.ts +12 -0
  22. package/dist/components/base/DFormCheckbox.vue.d.ts +12 -0
  23. package/dist/components/base/DFormGroup.vue.d.ts +12 -0
  24. package/dist/components/base/DFormInput.vue.d.ts +2 -0
  25. package/dist/components/base/DFormInvalidFeedback.vue.d.ts +12 -0
  26. package/dist/components/base/DFormRadio.vue.d.ts +12 -0
  27. package/dist/components/base/DFormSelect.vue.d.ts +12 -0
  28. package/dist/components/base/DFormSpinbutton.vue.d.ts +12 -0
  29. package/dist/components/base/DFormTags.vue.d.ts +12 -0
  30. package/dist/components/base/DFormText.vue.d.ts +12 -0
  31. package/dist/components/base/DFormTextarea.vue.d.ts +2 -0
  32. package/dist/components/base/DImage.vue.d.ts +12 -0
  33. package/dist/components/base/DInputGroup.vue.d.ts +12 -0
  34. package/dist/components/base/DLink.vue.d.ts +12 -0
  35. package/dist/components/base/DListGroup.vue.d.ts +12 -0
  36. package/dist/components/base/DListGroupItem.vue.d.ts +12 -0
  37. package/dist/components/base/DModal.vue.d.ts +12 -0
  38. package/dist/components/base/DNav.vue.d.ts +12 -0
  39. package/dist/components/base/DNavItem.vue.d.ts +12 -0
  40. package/dist/components/base/DNavbar.vue.d.ts +12 -0
  41. package/dist/components/base/DNavbarBrand.vue.d.ts +12 -0
  42. package/dist/components/base/DNavbarNav.vue.d.ts +12 -0
  43. package/dist/components/base/DNavbarToggle.vue.d.ts +12 -0
  44. package/dist/components/base/DOffcanvas.vue.d.ts +12 -0
  45. package/dist/components/base/DOverlay.vue.d.ts +12 -0
  46. package/dist/components/base/DPagination.vue.d.ts +2 -0
  47. package/dist/components/base/DPlaceholder.vue.d.ts +12 -0
  48. package/dist/components/base/DPopover.vue.d.ts +12 -0
  49. package/dist/components/base/DProgress.vue.d.ts +12 -0
  50. package/dist/components/base/DRow.vue.d.ts +12 -0
  51. package/dist/components/base/DSpinner.vue.d.ts +2 -0
  52. package/dist/components/base/DTab.vue.d.ts +12 -0
  53. package/dist/components/base/DTable.vue.d.ts +26 -0
  54. package/dist/components/base/DTabs.vue.d.ts +12 -0
  55. package/dist/components/base/DToast.vue.d.ts +12 -0
  56. package/dist/components/base/DToaster.vue.d.ts +12 -0
  57. package/dist/components/base/DTooltip.vue.d.ts +12 -0
  58. package/dist/components/extended/DXBasicForm.vue.d.ts +39 -0
  59. package/dist/components/extended/DXDashboard.vue.d.ts +52 -0
  60. package/dist/components/extended/DXDashboardNavbar.vue.d.ts +53 -0
  61. package/dist/components/extended/DXDashboardSidebar.vue.d.ts +37 -0
  62. package/dist/components/extended/DXForm.vue.d.ts +31 -0
  63. package/dist/components/extended/DXTable.vue.d.ts +190 -0
  64. package/dist/composables/defineForm.d.ts +35 -0
  65. package/dist/composables/useForm.d.ts +46 -0
  66. package/dist/composables/useToast.d.ts +1 -0
  67. package/dist/dashboard-for-laravel.js +17748 -0
  68. package/dist/dashboard-for-laravel.js.map +1 -0
  69. package/dist/dashboard-for-laravel.umd.cjs +11 -0
  70. package/dist/dashboard-for-laravel.umd.cjs.map +1 -0
  71. package/dist/index.d.ts +73 -0
  72. package/dist/style.css +5 -0
  73. package/dist/types/index.d.ts +37 -0
  74. package/dist/types/navigation.d.ts +17 -0
  75. package/dist/utils/api.d.ts +30 -0
  76. package/docs/public/api-reference.json +1932 -0
  77. package/docs/public/docs-map.md +85 -0
  78. package/docs/public/llms.txt +110 -0
  79. package/package.json +116 -0
  80. package/resources/css/theme.scss +219 -0
  81. package/resources/js/components/base/DAccordion.vue +21 -0
  82. package/resources/js/components/base/DAccordionItem.vue +14 -0
  83. package/resources/js/components/base/DAlert.vue +14 -0
  84. package/resources/js/components/base/DAvatar.vue +21 -0
  85. package/resources/js/components/base/DBadge.vue +14 -0
  86. package/resources/js/components/base/DBreadcrumb.vue +21 -0
  87. package/resources/js/components/base/DButton.vue +58 -0
  88. package/resources/js/components/base/DButtonGroup.vue +21 -0
  89. package/resources/js/components/base/DButtonToolbar.vue +21 -0
  90. package/resources/js/components/base/DCard.vue +35 -0
  91. package/resources/js/components/base/DCarousel.vue +21 -0
  92. package/resources/js/components/base/DCarouselSlide.vue +14 -0
  93. package/resources/js/components/base/DCol.vue +14 -0
  94. package/resources/js/components/base/DCollapse.vue +34 -0
  95. package/resources/js/components/base/DContainer.vue +14 -0
  96. package/resources/js/components/base/DDropdown.vue +16 -0
  97. package/resources/js/components/base/DDropdownDivider.vue +7 -0
  98. package/resources/js/components/base/DDropdownItem.vue +14 -0
  99. package/resources/js/components/base/DForm.vue +21 -0
  100. package/resources/js/components/base/DFormCheckbox.vue +14 -0
  101. package/resources/js/components/base/DFormGroup.vue +11 -0
  102. package/resources/js/components/base/DFormInput.vue +7 -0
  103. package/resources/js/components/base/DFormInvalidFeedback.vue +16 -0
  104. package/resources/js/components/base/DFormRadio.vue +21 -0
  105. package/resources/js/components/base/DFormSelect.vue +14 -0
  106. package/resources/js/components/base/DFormSpinbutton.vue +21 -0
  107. package/resources/js/components/base/DFormTags.vue +21 -0
  108. package/resources/js/components/base/DFormText.vue +16 -0
  109. package/resources/js/components/base/DFormTextarea.vue +7 -0
  110. package/resources/js/components/base/DImage.vue +21 -0
  111. package/resources/js/components/base/DInputGroup.vue +21 -0
  112. package/resources/js/components/base/DLink.vue +21 -0
  113. package/resources/js/components/base/DListGroup.vue +21 -0
  114. package/resources/js/components/base/DListGroupItem.vue +14 -0
  115. package/resources/js/components/base/DModal.vue +11 -0
  116. package/resources/js/components/base/DNav.vue +14 -0
  117. package/resources/js/components/base/DNavItem.vue +14 -0
  118. package/resources/js/components/base/DNavbar.vue +21 -0
  119. package/resources/js/components/base/DNavbarBrand.vue +14 -0
  120. package/resources/js/components/base/DNavbarNav.vue +14 -0
  121. package/resources/js/components/base/DNavbarToggle.vue +14 -0
  122. package/resources/js/components/base/DOffcanvas.vue +11 -0
  123. package/resources/js/components/base/DOverlay.vue +21 -0
  124. package/resources/js/components/base/DPagination.vue +7 -0
  125. package/resources/js/components/base/DPlaceholder.vue +21 -0
  126. package/resources/js/components/base/DPopover.vue +21 -0
  127. package/resources/js/components/base/DProgress.vue +21 -0
  128. package/resources/js/components/base/DRow.vue +14 -0
  129. package/resources/js/components/base/DSpinner.vue +7 -0
  130. package/resources/js/components/base/DTab.vue +14 -0
  131. package/resources/js/components/base/DTable.vue +62 -0
  132. package/resources/js/components/base/DTabs.vue +21 -0
  133. package/resources/js/components/base/DToast.vue +16 -0
  134. package/resources/js/components/base/DToaster.vue +16 -0
  135. package/resources/js/components/base/DTooltip.vue +21 -0
  136. package/resources/js/components/extended/DXBasicForm.vue +177 -0
  137. package/resources/js/components/extended/DXDashboard.vue +208 -0
  138. package/resources/js/components/extended/DXDashboardNavbar.vue +112 -0
  139. package/resources/js/components/extended/DXDashboardSidebar.vue +233 -0
  140. package/resources/js/components/extended/DXForm.vue +44 -0
  141. package/resources/js/components/extended/DXTable.vue +1345 -0
  142. package/resources/js/composables/defineForm.ts +78 -0
  143. package/resources/js/composables/useForm.ts +272 -0
  144. package/resources/js/composables/useToast.ts +1 -0
  145. package/resources/js/index.ts +118 -0
  146. package/resources/js/types/index.ts +61 -0
  147. package/resources/js/types/navigation.ts +19 -0
  148. package/resources/js/utils/api.ts +182 -0
  149. package/scripts/mcp-server.mjs +359 -0
@@ -0,0 +1,208 @@
1
+ <template>
2
+ <div class="dashboard-layout d-flex" :data-dashboard-id="dashboardId">
3
+ <!-- Sidebar -->
4
+ <DXDashboardSidebar
5
+ :navigation="navigation"
6
+ :current-url="currentUrl"
7
+ :title="title"
8
+ :collapsed="collapsed"
9
+ :hidden="hidden"
10
+ @toggle="toggleSidebar"
11
+ >
12
+ <!-- Dynamically forward all sidebar-* slots by stripping the prefix -->
13
+ <template
14
+ v-for="(originalName, strippedName) in sidebarSlots"
15
+ :key="strippedName"
16
+ #[strippedName]="slotProps"
17
+ >
18
+ <slot :name="originalName" v-bind="slotProps" />
19
+ </template>
20
+ </DXDashboardSidebar>
21
+
22
+ <!-- Main Content Area -->
23
+ <div class="dashboard-content flex-grow-1">
24
+ <!-- Top Navbar -->
25
+ <DXDashboardNavbar
26
+ :page-title="pageTitle"
27
+ :user="user"
28
+ :logout-url="logoutUrl"
29
+ @toggle-sidebar="toggleSidebar"
30
+ >
31
+ <!-- Dynamically forward all navbar-* slots by stripping the prefix -->
32
+ <template
33
+ v-for="(originalName, strippedName) in navbarSlots"
34
+ :key="strippedName"
35
+ #[strippedName]="slotProps"
36
+ >
37
+ <slot :name="originalName" v-bind="slotProps" />
38
+ </template>
39
+ </DXDashboardNavbar>
40
+
41
+ <!-- Page Content -->
42
+ <main class="dashboard-main p-4">
43
+ <DContainer fluid>
44
+ <DRow class="justify-content-center">
45
+ <DCol cols="12" xl="10">
46
+ <slot />
47
+ </DCol>
48
+ </DRow>
49
+ </DContainer>
50
+ </main>
51
+ </div>
52
+ </div>
53
+ </template>
54
+
55
+ <script setup lang="ts">
56
+ import { ref, computed, useSlots } from 'vue';
57
+ import DXDashboardSidebar from './DXDashboardSidebar.vue';
58
+ import DXDashboardNavbar from './DXDashboardNavbar.vue';
59
+ import DContainer from '../base/DContainer.vue';
60
+ import DRow from '../base/DRow.vue';
61
+ import DCol from '../base/DCol.vue';
62
+ import type { Navigation } from '../../types/navigation';
63
+
64
+ const slots = useSlots();
65
+
66
+ interface Props {
67
+ /** Navigation structure for sidebar */
68
+ navigation: Navigation;
69
+
70
+ /** Current URL path for active state */
71
+ currentUrl: string;
72
+
73
+ /** Dashboard title shown in sidebar brand */
74
+ title?: string;
75
+
76
+ /** Page title shown in navbar */
77
+ pageTitle?: string;
78
+
79
+ /** User object for navbar dropdown */
80
+ user?: { name: string; email: string } | null;
81
+
82
+ /** Logout URL for navbar dropdown */
83
+ logoutUrl?: string;
84
+
85
+ /** LocalStorage key for sidebar state persistence */
86
+ storageKey?: string;
87
+
88
+ /**
89
+ * Unique ID for this dashboard instance (for nested dashboards)
90
+ * Used to scope visibility state when multiple dashboards exist on same page
91
+ * If not provided, uses global HTML class approach (for SSR compatibility)
92
+ */
93
+ dashboardId?: string;
94
+ }
95
+
96
+ const props = withDefaults(defineProps<Props>(), {
97
+ title: 'Dashboard',
98
+ pageTitle: '',
99
+ user: null,
100
+ logoutUrl: '/logout',
101
+ storageKey: 'dashboard-sidebar-hidden',
102
+ dashboardId: '',
103
+ });
104
+
105
+ const collapsed = ref(false);
106
+
107
+ // Compute sidebar slots (strip 'sidebar-' prefix)
108
+ const sidebarSlots = computed(() => {
109
+ const result: Record<string, string> = {};
110
+ Object.keys(slots).forEach(name => {
111
+ if (name.startsWith('sidebar-')) {
112
+ const strippedName = name.substring(8); // Remove 'sidebar-' prefix
113
+ result[strippedName] = name;
114
+ }
115
+ });
116
+ return result;
117
+ });
118
+
119
+ // Compute navbar slots (strip 'navbar-' prefix)
120
+ const navbarSlots = computed(() => {
121
+ const result: Record<string, string> = {};
122
+ Object.keys(slots).forEach(name => {
123
+ if (name.startsWith('navbar-')) {
124
+ const strippedName = name.substring(7); // Remove 'navbar-' prefix
125
+ result[strippedName] = name;
126
+ }
127
+ });
128
+ return result;
129
+ });
130
+
131
+ // Initialize sidebar visibility from localStorage
132
+ const getInitialHiddenState = (): boolean => {
133
+ // Skip during SSR - no access to localStorage or document
134
+ if (typeof window === 'undefined') {
135
+ return !props.dashboardId; // Default: hidden for global, visible for scoped
136
+ }
137
+
138
+ try {
139
+ const savedHidden = localStorage.getItem(props.storageKey);
140
+ if (savedHidden !== null) {
141
+ const isHidden = JSON.parse(savedHidden);
142
+
143
+ // If no dashboard ID (global instance), update HTML class for SSR compatibility
144
+ if (!props.dashboardId) {
145
+ if (isHidden) {
146
+ document.documentElement.classList.remove('sidebar-visible');
147
+ } else {
148
+ document.documentElement.classList.add('sidebar-visible');
149
+ }
150
+ }
151
+
152
+ return isHidden;
153
+ }
154
+ } catch (error) {
155
+ console.error('Error loading sidebar state:', error);
156
+ }
157
+
158
+ // Default: hidden for global instances (docs), visible for scoped instances (examples)
159
+ if (!props.dashboardId) {
160
+ document.documentElement.classList.remove('sidebar-visible');
161
+ return true;
162
+ }
163
+ return false; // Show sidebar in scoped instances by default
164
+ };
165
+
166
+ const hidden = ref(getInitialHiddenState());
167
+
168
+ const toggleSidebar = () => {
169
+ hidden.value = !hidden.value;
170
+
171
+ // Skip during SSR
172
+ if (typeof window === 'undefined') return;
173
+
174
+ // If no dashboard ID (global instance), update HTML class
175
+ if (!props.dashboardId) {
176
+ if (hidden.value) {
177
+ document.documentElement.classList.remove('sidebar-visible');
178
+ } else {
179
+ document.documentElement.classList.add('sidebar-visible');
180
+ }
181
+ }
182
+
183
+ // Save to localStorage
184
+ try {
185
+ localStorage.setItem(props.storageKey, JSON.stringify(hidden.value));
186
+ } catch (error) {
187
+ console.error('Error saving sidebar state:', error);
188
+ }
189
+ };
190
+ </script>
191
+
192
+ <style scoped>
193
+ .dashboard-layout {
194
+ min-height: 100vh;
195
+ background-color: #f8f9fa;
196
+ }
197
+
198
+ .dashboard-content {
199
+ display: flex;
200
+ flex-direction: column;
201
+ overflow-x: hidden;
202
+ }
203
+
204
+ .dashboard-main {
205
+ flex: 1;
206
+ max-width: 100%;
207
+ }
208
+ </style>
@@ -0,0 +1,112 @@
1
+ <template>
2
+ <header class="dashboard-navbar bg-white border-bottom">
3
+ <DContainer fluid class="h-100">
4
+ <DRow class="h-100 align-items-center">
5
+ <DCol class="d-flex align-items-center gap-3">
6
+ <DButton
7
+ variant="link"
8
+ class="text-dark p-0"
9
+ @click="$emit('toggleSidebar')"
10
+ aria-label="Toggle sidebar"
11
+ >
12
+ <slot name="menu-icon">
13
+ <svg
14
+ xmlns="http://www.w3.org/2000/svg"
15
+ width="24"
16
+ height="24"
17
+ fill="currentColor"
18
+ viewBox="0 0 16 16"
19
+ >
20
+ <path
21
+ fill-rule="evenodd"
22
+ d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5"
23
+ />
24
+ </svg>
25
+ </slot>
26
+ </DButton>
27
+
28
+ <h4 v-if="pageTitle" class="mb-0 fw-semibold d-none d-md-block">{{ pageTitle }}</h4>
29
+
30
+ <div class="flex-grow-1 d-flex justify-content-center">
31
+ <slot name="search" />
32
+ </div>
33
+ </DCol>
34
+
35
+ <DCol cols="auto">
36
+ <slot name="user-menu" :user="user">
37
+ <DDropdown
38
+ v-if="user"
39
+ variant="link"
40
+ class="text-dark"
41
+ menu-class="dropdown-menu-end"
42
+ no-caret
43
+ >
44
+ <template #button-content>
45
+ <slot name="user-icon" :initial="getUserInitial(user)">
46
+ <div class="user-avatar">
47
+ {{ getUserInitial(user) }}
48
+ </div>
49
+ </slot>
50
+ </template>
51
+
52
+ <slot name="user-menu-items" :user="user" />
53
+ </DDropdown>
54
+ </slot>
55
+ </DCol>
56
+ </DRow>
57
+ </DContainer>
58
+ </header>
59
+ </template>
60
+
61
+ <script setup lang="ts">
62
+ import DContainer from "../base/DContainer.vue";
63
+ import DRow from "../base/DRow.vue";
64
+ import DCol from "../base/DCol.vue";
65
+ import DButton from "../base/DButton.vue";
66
+ import DDropdown from "../base/DDropdown.vue";
67
+
68
+ withDefaults(
69
+ defineProps<{
70
+ user?: {
71
+ name: string;
72
+ email: string;
73
+ [key: string]: any;
74
+ } | null;
75
+ pageTitle?: string;
76
+ }>(),
77
+ {
78
+ user: null,
79
+ pageTitle: "",
80
+ },
81
+ );
82
+
83
+ defineEmits<{
84
+ toggleSidebar: [];
85
+ }>();
86
+
87
+ const getUserInitial = (user: { name: string } | null) => {
88
+ if (!user?.name) return "";
89
+ return user.name.charAt(0).toUpperCase();
90
+ };
91
+ </script>
92
+
93
+ <style scoped>
94
+ .dashboard-navbar {
95
+ position: sticky;
96
+ top: 0;
97
+ z-index: 1000;
98
+ }
99
+
100
+ .user-avatar {
101
+ width: 32px;
102
+ height: 32px;
103
+ border-radius: 50%;
104
+ background-color: var(--bs-dark);
105
+ color: var(--bs-white);
106
+ display: flex;
107
+ align-items: center;
108
+ justify-content: center;
109
+ font-weight: 600;
110
+ font-size: 14px;
111
+ }
112
+ </style>
@@ -0,0 +1,233 @@
1
+ <template>
2
+ <aside
3
+ ref="sidebarRef"
4
+ class="dashboard-sidebar text-white"
5
+ :class="{
6
+ 'sidebar-collapsed': collapsed,
7
+ 'sidebar-hidden': hidden
8
+ }"
9
+ >
10
+ <div class="sidebar-header p-3">
11
+ <div class="d-flex align-items-center justify-content-between">
12
+ <slot name="brand" :collapsed="collapsed" :title="title">
13
+ <div class="brand-container" :class="{ 'collapsed': collapsed }">
14
+ <div class="brand-initial">{{ brandInitial }}</div>
15
+ <h5 v-if="!collapsed" class="mb-0 fw-bold ms-2">
16
+ {{ title }}
17
+ </h5>
18
+ </div>
19
+ </slot>
20
+ </div>
21
+ </div>
22
+
23
+ <nav class="sidebar-nav p-3">
24
+ <template v-for="(group, groupIndex) in navigation" :key="groupIndex">
25
+ <div v-if="group.visible !== false" class="nav-group mb-3">
26
+ <div
27
+ v-if="group.label && !collapsed"
28
+ class="nav-group-label text-uppercase small fw-semibold mb-2 px-2"
29
+ >
30
+ {{ group.label }}
31
+ </div>
32
+
33
+ <div v-if="group.label && collapsed" class="nav-group-divider">
34
+ <hr class="my-2 border-secondary" />
35
+ </div>
36
+
37
+ <ul class="nav flex-column gap-1">
38
+ <li v-for="(item, itemIndex) in group.items" :key="itemIndex" class="nav-item">
39
+ <slot
40
+ name="link"
41
+ :item="item"
42
+ :is-active="isActive(item.url)"
43
+ :collapsed="collapsed"
44
+ >
45
+ <a
46
+ :href="item.url"
47
+ class="nav-link d-flex align-items-center gap-2 rounded"
48
+ :class="{
49
+ 'active': isActive(item.url),
50
+ 'justify-content-center': collapsed
51
+ }"
52
+ >
53
+ <component
54
+ v-if="item.icon"
55
+ :is="item.icon"
56
+ class="nav-icon"
57
+ style="width: 20px; height: 20px;"
58
+ />
59
+ <span v-if="!collapsed" class="nav-label">{{ item.label }}</span>
60
+ <span
61
+ v-if="item.badge && !collapsed"
62
+ class="badge ms-auto"
63
+ :class="`bg-${item.badgeColor || 'primary'}`"
64
+ >
65
+ {{ item.badge }}
66
+ </span>
67
+ </a>
68
+ </slot>
69
+ </li>
70
+ </ul>
71
+ </div>
72
+ </template>
73
+ </nav>
74
+ </aside>
75
+ </template>
76
+
77
+ <script setup lang="ts">
78
+ import { computed, ref, onMounted, watch, nextTick } from 'vue';
79
+ import type { Navigation } from '../../types/navigation';
80
+
81
+ const props = withDefaults(defineProps<{
82
+ navigation: Navigation;
83
+ currentUrl: string;
84
+ collapsed?: boolean;
85
+ hidden?: boolean;
86
+ title?: string;
87
+ }>(), {
88
+ collapsed: false,
89
+ hidden: false,
90
+ title: 'Dashboard',
91
+ });
92
+
93
+ defineEmits<{
94
+ toggle: [];
95
+ }>();
96
+
97
+ const sidebarRef = ref<HTMLElement | null>(null);
98
+
99
+ const brandInitial = computed(() => {
100
+ return props.title.charAt(0).toUpperCase();
101
+ });
102
+
103
+ const isActive = (url: string): boolean => {
104
+ // Normalize URLs for comparison (remove trailing slash, lowercase)
105
+ const normalizeUrl = (u: string) => u.toLowerCase().replace(/\/$/, '');
106
+ return normalizeUrl(props.currentUrl) === normalizeUrl(url);
107
+ };
108
+
109
+ const scrollToActiveItem = async (smooth = false) => {
110
+ await nextTick();
111
+ if (!sidebarRef.value) return;
112
+
113
+ const activeLink = sidebarRef.value.querySelector('.nav-link.active') as HTMLElement;
114
+ if (activeLink) {
115
+ const sidebar = sidebarRef.value;
116
+ const linkRect = activeLink.getBoundingClientRect();
117
+ const sidebarRect = sidebar.getBoundingClientRect();
118
+
119
+ // Calculate the position to scroll to (center the active item)
120
+ const scrollTop = activeLink.offsetTop - (sidebarRect.height / 2) + (linkRect.height / 2);
121
+
122
+ sidebar.scrollTo({
123
+ top: scrollTop,
124
+ behavior: smooth ? 'smooth' : 'instant',
125
+ });
126
+ }
127
+ };
128
+
129
+ // Scroll to active item on initial mount (instant, no animation)
130
+ onMounted(() => {
131
+ scrollToActiveItem(false);
132
+ });
133
+
134
+ // Watch for URL changes (client-side routing) and scroll smoothly
135
+ watch(() => props.currentUrl, () => {
136
+ scrollToActiveItem(true);
137
+ });
138
+ </script>
139
+
140
+ <style scoped>
141
+ .dashboard-sidebar {
142
+ min-height: 100vh;
143
+ transition: width 0.3s ease;
144
+ position: sticky;
145
+ top: 0;
146
+ height: 100vh;
147
+ overflow-y: auto;
148
+ overflow-x: hidden;
149
+ flex-shrink: 0;
150
+ }
151
+
152
+ .sidebar-header {
153
+ display: flex;
154
+ align-items: center;
155
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
156
+ }
157
+
158
+ .brand-container {
159
+ display: flex;
160
+ align-items: center;
161
+ justify-content: flex-start;
162
+ transition: all 0.3s ease;
163
+ }
164
+
165
+ .brand-container.collapsed {
166
+ justify-content: center;
167
+ }
168
+
169
+ .brand-initial {
170
+ width: 32px;
171
+ height: 32px;
172
+ flex-shrink: 0;
173
+ display: flex;
174
+ align-items: center;
175
+ justify-content: center;
176
+ font-weight: 700;
177
+ font-size: 1.25rem;
178
+ }
179
+
180
+ .sidebar-nav {
181
+ overflow-x: hidden;
182
+ }
183
+
184
+ .nav-group-label {
185
+ font-size: 0.75rem;
186
+ letter-spacing: 0.5px;
187
+ }
188
+
189
+ :deep(.nav-link) {
190
+ padding: 0.625rem 0.75rem;
191
+ transition: all 0.2s ease;
192
+ text-decoration: none;
193
+ white-space: nowrap;
194
+ }
195
+
196
+ :deep(.nav-link.active) {
197
+ font-weight: 500;
198
+ }
199
+
200
+ :deep(.nav-icon) {
201
+ flex-shrink: 0;
202
+ }
203
+
204
+ :deep(.nav-label) {
205
+ flex: 1;
206
+ }
207
+
208
+ .sidebar-collapsed :deep(.nav-link) {
209
+ padding: 0.625rem;
210
+ }
211
+
212
+ /* Custom scrollbar */
213
+ .dashboard-sidebar::-webkit-scrollbar {
214
+ width: 6px;
215
+ }
216
+
217
+ .dashboard-sidebar::-webkit-scrollbar-track {
218
+ background: rgba(0, 0, 0, 0.1);
219
+ }
220
+
221
+ .dashboard-sidebar::-webkit-scrollbar-thumb {
222
+ background: rgba(255, 255, 255, 0.2);
223
+ border-radius: 3px;
224
+ }
225
+
226
+ .dashboard-sidebar::-webkit-scrollbar-thumb:hover {
227
+ background: rgba(255, 255, 255, 0.3);
228
+ }
229
+
230
+ .sidebar-hidden {
231
+ display: none;
232
+ }
233
+ </style>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <OBasicForm
3
+ :form="form.form"
4
+ :fields="form.fields"
5
+ :submit-text="submitText"
6
+ :submit-loading-text="submitLoadingText"
7
+ :show-submit="showSubmit"
8
+ @submit="emit('submit')"
9
+ >
10
+ <!-- Pass through all slots -->
11
+ <template v-for="(_, name) in $slots" #[name]="slotProps">
12
+ <slot :name="name" v-bind="slotProps" />
13
+ </template>
14
+ </OBasicForm>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import OBasicForm from "./DXBasicForm.vue";
19
+ import type { DefineFormReturn } from "../../composables/defineForm";
20
+
21
+ interface Props {
22
+ /** Form object from defineForm */
23
+ form: DefineFormReturn<any>;
24
+
25
+ /** Submit button text */
26
+ submitText?: string;
27
+
28
+ /** Submit button loading text */
29
+ submitLoadingText?: string;
30
+
31
+ /** Show submit button */
32
+ showSubmit?: boolean;
33
+ }
34
+
35
+ withDefaults(defineProps<Props>(), {
36
+ submitText: "Submit",
37
+ submitLoadingText: "Submitting...",
38
+ showSubmit: true,
39
+ });
40
+
41
+ const emit = defineEmits<{
42
+ submit: [];
43
+ }>();
44
+ </script>