@omnitend/dashboard-for-laravel 0.21.0 → 0.22.0

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.
@@ -28,3 +28,16 @@ export interface NavigationGroup {
28
28
  visible?: boolean;
29
29
  }
30
30
  export type Navigation = NavigationGroup[];
31
+ /**
32
+ * Horizontal alignment of the navbar search slot content within its region:
33
+ * `"start"` = flush left (after the title from `md` up), `"center"` = centred.
34
+ * Shared by DXDashboard (which forwards it) and DXDashboardNavbar.
35
+ */
36
+ export type NavbarSearchAlign = "start" | "center";
37
+ /**
38
+ * What the navbar actions slot does below the `md` breakpoint: `"wrap"` moves
39
+ * it to its own full-width row (the bar grows), `"hide"` removes it entirely
40
+ * (for apps that relocate page actions into the page on phones).
41
+ * Shared by DXDashboard (which forwards it) and DXDashboardNavbar.
42
+ */
43
+ export type NavbarActionsOnMobile = "wrap" | "hide";
@@ -1,8 +1,8 @@
1
1
  {
2
- "generated": "2026-07-10T09:19:45.286Z",
2
+ "generated": "2026-07-11T04:13:01.837Z",
3
3
  "package": {
4
4
  "name": "@omnitend/dashboard-for-laravel",
5
- "version": "0.19.2"
5
+ "version": "0.21.0"
6
6
  },
7
7
  "components": {
8
8
  "base": [
@@ -1394,7 +1394,14 @@
1394
1394
  "name": "fluid",
1395
1395
  "type": "boolean",
1396
1396
  "required": false,
1397
- "description": "Render the page content full-width and left-aligned instead of the default\ncentred, reading-width (`col-xl-10`) column. Use for data-heavy admin pages\n(wide tables)."
1397
+ "description": "Render the page content full-width and left-aligned instead of the default\ncentred, reading-width column. Use for data-heavy admin pages (wide tables)."
1398
+ },
1399
+ {
1400
+ "name": "contentMaxWidth",
1401
+ "type": "string",
1402
+ "required": false,
1403
+ "default": "'1140px'",
1404
+ "description": "Max width of the centred content column (any CSS length). A genuine cap, so\nforms and text-heavy pages don't stretch to ~2000px on a wide display.\nIgnored when `fluid`. Set a large value (or use `fluid`) for full width."
1398
1405
  },
1399
1406
  {
1400
1407
  "name": "contentClass",
@@ -1410,11 +1417,18 @@
1410
1417
  "description": "User object for navbar dropdown"
1411
1418
  },
1412
1419
  {
1413
- "name": "logoutUrl",
1414
- "type": "string",
1420
+ "name": "searchAlign",
1421
+ "type": "NavbarSearchAlign",
1415
1422
  "required": false,
1416
- "default": "'/logout'",
1417
- "description": "Logout URL for navbar dropdown"
1423
+ "default": "'start'",
1424
+ "description": "Horizontal alignment of the navbar search slot content (`\"start\"` = flush\nleft, `\"center\"` = centred), forwarded to DXDashboardNavbar."
1425
+ },
1426
+ {
1427
+ "name": "actionsOnMobile",
1428
+ "type": "NavbarActionsOnMobile",
1429
+ "required": false,
1430
+ "default": "'wrap'",
1431
+ "description": "What the navbar actions slot does below `md`: `\"wrap\"` to its own\nfull-width row, `\"hide\"` to remove it (relocate actions into the page on\nphones). Forwarded to DXDashboardNavbar."
1418
1432
  },
1419
1433
  {
1420
1434
  "name": "collapsibleGroups",
@@ -1469,7 +1483,7 @@
1469
1483
  "name": "DXDashboardNavbar",
1470
1484
  "category": "extended",
1471
1485
  "filePath": "resources/js/components/extended/DXDashboardNavbar.vue",
1472
- "description": "Top navigation bar for the dashboard shell: menu toggle, page title, centred search, page-level actions, and the user menu. Usually rendered by `DXDashboard`, which forwards these slots with a `navbar-` prefix.",
1486
+ "description": "Top navigation bar for the dashboard shell: menu toggle, page title, search (aligned per `searchAlign`, left by default), page-level actions, and the user menu. Usually rendered by `DXDashboard`, which forwards these slots with a `navbar-` prefix.",
1473
1487
  "props": [
1474
1488
  {
1475
1489
  "name": "user",
@@ -1484,6 +1498,20 @@
1484
1498
  "required": false,
1485
1499
  "default": "\"\"",
1486
1500
  "description": "Page title shown at the left of the navbar (hidden below the `md` breakpoint)."
1501
+ },
1502
+ {
1503
+ "name": "searchAlign",
1504
+ "type": "NavbarSearchAlign",
1505
+ "required": false,
1506
+ "default": "\"start\"",
1507
+ "description": "Horizontal alignment of the search slot content within its region\n(`\"start\"` = flush left, `\"center\"` = centred). Applies at every size:\nthe region sits inline after the title from `md` up, and is its own\nfull-width row below."
1508
+ },
1509
+ {
1510
+ "name": "actionsOnMobile",
1511
+ "type": "NavbarActionsOnMobile",
1512
+ "required": false,
1513
+ "default": "\"wrap\"",
1514
+ "description": "What the actions slot does below the `md` breakpoint: `\"wrap\"` moves it\nto its own full-width row (the bar grows), `\"hide\"` removes it entirely\n(for apps that relocate page actions into the page on phones)."
1487
1515
  }
1488
1516
  ],
1489
1517
  "events": [
@@ -1501,12 +1529,12 @@
1501
1529
  },
1502
1530
  {
1503
1531
  "name": "search",
1504
- "description": "Search input or component. Centred in the bar on wider screens; drops to its own full-width row below the `md` breakpoint.",
1532
+ "description": "Search input or component. Aligned per `searchAlign` within its region (inline in the bar from `md` up; its own full-width row below).",
1505
1533
  "bindings": []
1506
1534
  },
1507
1535
  {
1508
1536
  "name": "actions",
1509
- "description": "Page-level primary actions (e.g. a Create button), right-aligned next to the user menu.",
1537
+ "description": "Page-level primary actions (e.g. a Create button). Right-aligned next to the user menu from `md` up; below, wraps to its own full-width row or is hidden entirely per `actionsOnMobile`.",
1510
1538
  "bindings": [
1511
1539
  {
1512
1540
  "title": "binding",
@@ -1767,7 +1795,7 @@
1767
1795
  "name": "DXField",
1768
1796
  "category": "extended",
1769
1797
  "filePath": "resources/js/components/extended/DXField.vue",
1770
- "description": "Renders a single `FieldDefinition` of any type (text, select, autocomplete, checkbox, switch, repeater, currency, percentage, file/image, component, etc.) with its label, validation error, hint and help. Used by `DXForm` and `DXTable`'s edit modal; exposes `value`, `span`, `info`, `hint` and `repeater-row` slots for per-field customisation.",
1798
+ "description": "Renders a single `FieldDefinition` of any type (text, select, autocomplete, checkbox, switch, repeater, currency, percentage, file/image, component, etc.) with its label, validation error, hint and help. Used by `DXForm` and `DXTable`'s edit modal; exposes `value`, `span`, `info`, `info-popover`, `hint` and `repeater-row` slots for per-field customisation.",
1771
1799
  "props": [
1772
1800
  {
1773
1801
  "name": "field",
@@ -1968,7 +1996,13 @@
1968
1996
  }
1969
1997
  ],
1970
1998
  "events": [],
1971
- "slots": [],
1999
+ "slots": [
2000
+ {
2001
+ "name": "popover",
2002
+ "description": "Rich popover body. Overrides the plain `info` text — use",
2003
+ "bindings": []
2004
+ }
2005
+ ],
1972
2006
  "methods": []
1973
2007
  },
1974
2008
  {
@@ -1,7 +1,7 @@
1
1
  # Documentation Map
2
2
 
3
3
  > Auto-generated hierarchical overview of all documentation
4
- > Last updated: 2026-07-10T09:19:45.350Z
4
+ > Last updated: 2026-07-11T04:13:01.900Z
5
5
 
6
6
  This file provides a complete map of all available documentation for AI agents and developers.
7
7
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnitend/dashboard-for-laravel",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "Vue 3 dashboard components for Laravel with Bootstrap Vue Next",
5
5
  "type": "module",
6
6
  "main": "./dist/dashboard-for-laravel.umd.cjs",
@@ -31,8 +31,11 @@
31
31
  "dev": "vite build --watch",
32
32
  "build": "vite build && vue-tsc --declaration --emitDeclarationOnly --outDir dist",
33
33
  "typecheck": "vue-tsc --noEmit",
34
+ "pretest": "vite build",
34
35
  "test": "vitest",
36
+ "pretest:ui": "vite build",
35
37
  "test:ui": "vitest --ui",
38
+ "pretest:headless": "vite build",
36
39
  "test:headless": "vitest --run --browser.headless",
37
40
  "docs:dev": "astro dev",
38
41
  "docs:build": "npm run docs:generate:ai && astro build",
@@ -287,11 +287,26 @@ $bootstrap-icons-font-src: url("bootstrap-icons/font/fonts/bootstrap-icons.woff2
287
287
  }
288
288
 
289
289
  .dashboard-navbar {
290
- height: $dashboard-navbar-height;
290
+ // Floor, not a fixed height: below `md` the search (and actions) drop to
291
+ // their own rows and the bar must grow to contain them (#93). Single-row
292
+ // content is shorter than this, so desktop stays exactly this height,
293
+ // aligned with the sidebar header.
294
+ min-height: $dashboard-navbar-height;
291
295
  // A light grey top bar over the white content panel (higher contrast than a
292
296
  // grey-everywhere layout). The navbar markup drops its `bg-white` class so
293
297
  // this wins without `!important`.
294
- background-color: #f8f9fa;
298
+ background-color: var(--bs-light);
299
+ }
300
+
301
+ // The avatar dropdown trigger (DXDashboardNavbar sets this via toggle-class,
302
+ // so it lands on the BVN-rendered button — global rule, scoped CSS can't
303
+ // reach it). The themed $btn-padding-y (10px) would make it ~54px tall and
304
+ // blow the navbar's single-row content budget: 64px header minus the bar's
305
+ // 16px padding = 48px. 6px keeps a ~46px tap target (≥ the 44px WCAG
306
+ // guideline) while still fitting the budget.
307
+ .dashboard-navbar__user-menu-toggle {
308
+ padding-top: 0.375rem;
309
+ padding-bottom: 0.375rem;
295
310
  }
296
311
 
297
312
  // ----------------------------------------------------------------------------
@@ -36,7 +36,8 @@
36
36
  <DXDashboardNavbar
37
37
  :page-title="pageTitle"
38
38
  :user="user"
39
- :logout-url="logoutUrl"
39
+ :search-align="searchAlign"
40
+ :actions-on-mobile="actionsOnMobile"
40
41
  @toggle-sidebar="toggleSidebar"
41
42
  >
42
43
  <!-- Dynamically forward all navbar-* slots by stripping the prefix -->
@@ -80,7 +81,7 @@ import DXDashboardNavbar from './DXDashboardNavbar.vue';
80
81
  import DContainer from '../base/DContainer.vue';
81
82
  import DRow from '../base/DRow.vue';
82
83
  import DCol from '../base/DCol.vue';
83
- import type { Navigation } from '../../types/navigation';
84
+ import type { Navigation, NavbarActionsOnMobile, NavbarSearchAlign } from '../../types/navigation';
84
85
 
85
86
  const slots = useSlots();
86
87
 
@@ -116,8 +117,18 @@ interface Props {
116
117
  /** User object for navbar dropdown */
117
118
  user?: { name: string; email: string } | null;
118
119
 
119
- /** Logout URL for navbar dropdown */
120
- logoutUrl?: string;
120
+ /**
121
+ * Horizontal alignment of the navbar search slot content (`"start"` = flush
122
+ * left, `"center"` = centred), forwarded to DXDashboardNavbar.
123
+ */
124
+ searchAlign?: NavbarSearchAlign;
125
+
126
+ /**
127
+ * What the navbar actions slot does below `md`: `"wrap"` to its own
128
+ * full-width row, `"hide"` to remove it (relocate actions into the page on
129
+ * phones). Forwarded to DXDashboardNavbar.
130
+ */
131
+ actionsOnMobile?: NavbarActionsOnMobile;
121
132
 
122
133
  /**
123
134
  * Turn sidebar group headers into accordion toggles that collapse/expand
@@ -147,12 +158,13 @@ const props = withDefaults(defineProps<Props>(), {
147
158
  title: 'Dashboard',
148
159
  pageTitle: '',
149
160
  user: null,
150
- logoutUrl: '/logout',
151
161
  collapsibleGroups: false,
152
162
  autoCollapseInactiveGroups: true,
153
163
  storageKey: 'dashboard-sidebar-hidden',
154
164
  dashboardId: '',
155
165
  contentMaxWidth: '1140px',
166
+ searchAlign: 'start',
167
+ actionsOnMobile: 'wrap',
156
168
  });
157
169
 
158
170
  const collapsed = ref(false);
@@ -245,7 +257,7 @@ const toggleSidebar = () => {
245
257
  <style scoped>
246
258
  .dashboard-layout {
247
259
  min-height: 100vh;
248
- background-color: #f8f9fa;
260
+ background-color: var(--bs-light);
249
261
  }
250
262
 
251
263
  .dashboard-content {
@@ -259,6 +271,6 @@ const toggleSidebar = () => {
259
271
  max-width: 100%;
260
272
  /* A white content panel under the light grey top bar / layout, so forms and
261
273
  content sit on white (higher contrast than a grey-everywhere dashboard). */
262
- background-color: #fff;
274
+ background-color: var(--bs-white);
263
275
  }
264
276
  </style>
@@ -1,8 +1,9 @@
1
1
  <!--
2
2
  @component
3
- Top navigation bar for the dashboard shell: menu toggle, page title, centred
4
- search, page-level actions, and the user menu. Usually rendered by
5
- `DXDashboard`, which forwards these slots with a `navbar-` prefix.
3
+ Top navigation bar for the dashboard shell: menu toggle, page title, search
4
+ (aligned per `searchAlign`, left by default), page-level actions, and the
5
+ user menu. Usually rendered by `DXDashboard`, which forwards these slots
6
+ with a `navbar-` prefix.
6
7
  -->
7
8
  <template>
8
9
  <header class="dashboard-navbar border-bottom">
@@ -39,25 +40,35 @@
39
40
 
40
41
  <div
41
42
  v-if="$slots.search"
42
- class="dashboard-navbar__search d-flex justify-content-center"
43
+ class="dashboard-navbar__search d-flex"
44
+ :class="searchAlignClass"
43
45
  >
44
- <!-- @slot Search input or component. Centred in the bar on wider screens; drops to its own full-width row below the `md` breakpoint. -->
46
+ <!-- @slot Search input or component. Aligned per `searchAlign` within its region (inline in the bar from `md` up; its own full-width row below). -->
45
47
  <slot name="search" />
46
48
  </div>
47
49
 
48
- <div class="dashboard-navbar__end d-flex align-items-center gap-3">
49
- <!-- Page-level primary actions, right-aligned next to the user menu -->
50
- <div
51
- v-if="$slots.actions"
52
- class="dashboard-navbar__actions d-flex align-items-center gap-2"
53
- >
54
- <!--
55
- @slot Page-level primary actions (e.g. a Create button), right-aligned next to the user menu.
56
- @binding {string} pageTitle The current page title, for context.
57
- -->
58
- <slot name="actions" :pageTitle="pageTitle" />
59
- </div>
50
+ <!-- Page-level primary actions: right-aligned next to the user menu
51
+ from `md` up; their own wrappable full-width row below (#93),
52
+ or hidden below `md` when `actionsOnMobile` is "hide". -->
53
+ <div
54
+ v-if="$slots.actions"
55
+ class="dashboard-navbar__actions align-items-center gap-2"
56
+ :class="actionsDisplayClass"
57
+ >
58
+ <!--
59
+ @slot Page-level primary actions (e.g. a Create button). Right-aligned next to the user menu from `md` up; below, wraps to its own full-width row or is hidden entirely per `actionsOnMobile`.
60
+ @binding {string} pageTitle The current page title, for context.
61
+ -->
62
+ <slot name="actions" :pageTitle="pageTitle" />
63
+ </div>
60
64
 
65
+ <!-- Only render the user-menu cluster when it has content: an empty
66
+ flex item would still cost a bar gap, pushing actions off the
67
+ right edge in guest (user-less) layouts. -->
68
+ <div
69
+ v-if="$slots['user-menu'] || user"
70
+ class="dashboard-navbar__end d-flex align-items-center gap-3"
71
+ >
61
72
  <!--
62
73
  @slot Replaces the entire user menu (the default avatar dropdown).
63
74
  @binding {object} user The signed-in user.
@@ -68,6 +79,7 @@
68
79
  variant="link"
69
80
  class="text-dark"
70
81
  menu-class="dropdown-menu-end"
82
+ toggle-class="dashboard-navbar__user-menu-toggle"
71
83
  no-caret
72
84
  >
73
85
  <template #button-content>
@@ -96,11 +108,13 @@
96
108
  </template>
97
109
 
98
110
  <script setup lang="ts">
111
+ import { computed } from "vue";
99
112
  import DContainer from "../base/DContainer.vue";
100
113
  import DButton from "../base/DButton.vue";
101
114
  import DDropdown from "../base/DDropdown.vue";
115
+ import type { NavbarActionsOnMobile, NavbarSearchAlign } from "../../types/navigation";
102
116
 
103
- withDefaults(
117
+ const props = withDefaults(
104
118
  defineProps<{
105
119
  /** The signed-in user shown in the avatar dropdown. `null` hides the menu. */
106
120
  user?: {
@@ -110,13 +124,53 @@ withDefaults(
110
124
  } | null;
111
125
  /** Page title shown at the left of the navbar (hidden below the `md` breakpoint). */
112
126
  pageTitle?: string;
127
+ /**
128
+ * Horizontal alignment of the search slot content within its region
129
+ * (`"start"` = flush left, `"center"` = centred). Applies at every size:
130
+ * the region sits inline after the title from `md` up, and is its own
131
+ * full-width row below.
132
+ */
133
+ searchAlign?: NavbarSearchAlign;
134
+ /**
135
+ * What the actions slot does below the `md` breakpoint: `"wrap"` moves it
136
+ * to its own full-width row (the bar grows), `"hide"` removes it entirely
137
+ * (for apps that relocate page actions into the page on phones).
138
+ */
139
+ actionsOnMobile?: NavbarActionsOnMobile;
113
140
  }>(),
114
141
  {
115
142
  user: null,
116
143
  pageTitle: "",
144
+ searchAlign: "start",
145
+ actionsOnMobile: "wrap",
117
146
  },
118
147
  );
119
148
 
149
+ // Explicit map (not string interpolation) so an out-of-union value at runtime
150
+ // falls back to a valid class instead of a silent Bootstrap no-op, and the
151
+ // full class names stay greppable.
152
+ const SEARCH_ALIGN_CLASSES: Record<NavbarSearchAlign, string> = {
153
+ start: "justify-content-start",
154
+ center: "justify-content-center",
155
+ };
156
+
157
+ const searchAlignClass = computed(
158
+ () => SEARCH_ALIGN_CLASSES[props.searchAlign] ?? SEARCH_ALIGN_CLASSES.start,
159
+ );
160
+
161
+ // "hide" swaps d-flex for d-none/d-md-flex on the WRAPPER — hiding only the
162
+ // slot content would leave a zero-height full-width row still costing a flex
163
+ // row-gap on phones. Explicit map + fallback, same rationale as
164
+ // SEARCH_ALIGN_CLASSES above.
165
+ const ACTIONS_DISPLAY_CLASSES: Record<NavbarActionsOnMobile, string> = {
166
+ wrap: "d-flex",
167
+ hide: "d-none d-md-flex",
168
+ };
169
+
170
+ const actionsDisplayClass = computed(
171
+ () => ACTIONS_DISPLAY_CLASSES[props.actionsOnMobile] ?? ACTIONS_DISPLAY_CLASSES.wrap,
172
+ );
173
+
120
174
  defineEmits<{
121
175
  /** Emitted when the hamburger menu button is clicked. */
122
176
  toggleSidebar: [];
@@ -140,32 +194,56 @@ const getUserInitial = (user: { name: string } | null) => {
140
194
  padding: 0.5rem 0;
141
195
  }
142
196
 
143
- /* Push the user-menu / actions cluster to the right. */
197
+ /* Push the user-menu cluster to the right. (The actions region carries its
198
+ own auto margin from `md` up — see the sibling rule below.) */
144
199
  .dashboard-navbar__end {
145
200
  margin-left: auto;
146
201
  }
147
202
 
148
203
  /*
149
- * Mobile-first: the search sits on its own full-width row below the toggle /
150
- * title / user-menu row (order after both, flex-basis 100% forces the wrap).
151
- * From `md` up it moves inline between the title and the user menu and grows to
152
- * fill the middle, centring its content.
204
+ * Mobile-first: the actions and the search each sit on their own full-width
205
+ * row below the toggle / title / user-menu row (flex-basis 100% forces the
206
+ * wrap; the bar and the sticky header grow to contain them — #93). The
207
+ * actions row itself wraps so a wide button group stacks instead of
208
+ * overflowing the viewport.
209
+ * From `md` up everything moves inline: search between the title and the
210
+ * actions (grows to fill the middle; `searchAlign` controls where its content
211
+ * sits), actions right-aligned next to the user menu. The actions' order 2
212
+ * holds at every size — only search and end change order across the
213
+ * breakpoint.
153
214
  */
215
+ .dashboard-navbar__actions {
216
+ order: 2;
217
+ flex: 0 0 100%;
218
+ flex-wrap: wrap;
219
+ }
220
+
154
221
  .dashboard-navbar__search {
155
222
  order: 3;
156
223
  flex: 0 0 100%;
157
- width: 100%;
158
224
  }
159
225
 
160
226
  @media (min-width: 768px) {
161
227
  .dashboard-navbar__search {
162
228
  order: 1;
163
229
  flex: 1 1 auto;
164
- width: auto;
230
+ }
231
+
232
+ .dashboard-navbar__actions {
233
+ flex: 0 1 auto;
234
+ flex-wrap: nowrap;
235
+ margin-left: auto;
165
236
  }
166
237
 
167
238
  .dashboard-navbar__end {
168
- order: 2;
239
+ order: 3;
240
+ }
241
+
242
+ /* When the actions region is present it carries the push-right auto margin;
243
+ zero the user-menu cluster's so the two sit adjacent instead of having
244
+ the free space split between them. */
245
+ .dashboard-navbar__actions ~ .dashboard-navbar__end {
246
+ margin-left: 0;
169
247
  }
170
248
  }
171
249
 
@@ -139,4 +139,6 @@ export type {
139
139
  NavigationItem,
140
140
  NavigationGroup,
141
141
  Navigation,
142
+ NavbarSearchAlign,
143
+ NavbarActionsOnMobile,
142
144
  } from "./types/navigation";
@@ -30,3 +30,18 @@ export interface NavigationGroup {
30
30
  }
31
31
 
32
32
  export type Navigation = NavigationGroup[];
33
+
34
+ /**
35
+ * Horizontal alignment of the navbar search slot content within its region:
36
+ * `"start"` = flush left (after the title from `md` up), `"center"` = centred.
37
+ * Shared by DXDashboard (which forwards it) and DXDashboardNavbar.
38
+ */
39
+ export type NavbarSearchAlign = "start" | "center";
40
+
41
+ /**
42
+ * What the navbar actions slot does below the `md` breakpoint: `"wrap"` moves
43
+ * it to its own full-width row (the bar grows), `"hide"` removes it entirely
44
+ * (for apps that relocate page actions into the page on phones).
45
+ * Shared by DXDashboard (which forwards it) and DXDashboardNavbar.
46
+ */
47
+ export type NavbarActionsOnMobile = "wrap" | "hide";