@omnitend/dashboard-for-laravel 0.21.0 → 0.23.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.
@@ -231,6 +231,29 @@ export interface FieldDefinition {
231
231
  * applied as `disabled`.
232
232
  */
233
233
  readonly?: MaybeFn<boolean>;
234
+ /**
235
+ * Display the value as static text instead of a control — no border, no
236
+ * input box (Bootstrap's `.form-control-plaintext`). For values a
237
+ * profile/settings page shows but never lets you edit, where a `readonly`
238
+ * input still reads as "you could edit this, but can't".
239
+ *
240
+ * Implies read-only, for every field type. Anything backed by a real input
241
+ * — the text family, textarea, currency, percentage, autocomplete — renders
242
+ * as static text; control types with no native readonly state
243
+ * (select/radio/checkbox/switch/file) are disabled instead, exactly as they
244
+ * are for `readonly`.
245
+ *
246
+ * Enforced over `inputProps`: unlike the field's other bindings, which
247
+ * `inputProps` may override, a plaintext field cannot be made editable
248
+ * again through it.
249
+ */
250
+ plaintext?: MaybeFn<boolean>;
251
+ /**
252
+ * Only for `type: "password"`. Show the reveal (eye) toggle that switches
253
+ * the input between masked and plain text. Defaults to `true` — set
254
+ * `false` to render a bare password input.
255
+ */
256
+ revealable?: boolean;
234
257
  /**
235
258
  * Conditionally show or hide this field. When omitted the field is
236
259
  * always visible. Boolean or a function of the form model; evaluated
@@ -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-11T08:44:15.505Z",
3
3
  "package": {
4
4
  "name": "@omnitend/dashboard-for-laravel",
5
- "version": "0.19.2"
5
+ "version": "0.22.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",
@@ -1454,6 +1468,22 @@
1454
1468
  {
1455
1469
  "name": "name",
1456
1470
  "title": "binding"
1471
+ },
1472
+ {
1473
+ "title": "binding",
1474
+ "type": {
1475
+ "name": "function"
1476
+ },
1477
+ "name": "toggleSidebar",
1478
+ "description": "Shows/hides the sidebar."
1479
+ },
1480
+ {
1481
+ "title": "binding",
1482
+ "type": {
1483
+ "name": "boolean"
1484
+ },
1485
+ "name": "sidebarHidden",
1486
+ "description": "Whether the sidebar is currently hidden."
1457
1487
  }
1458
1488
  ]
1459
1489
  },
@@ -1469,7 +1499,7 @@
1469
1499
  "name": "DXDashboardNavbar",
1470
1500
  "category": "extended",
1471
1501
  "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.",
1502
+ "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
1503
  "props": [
1474
1504
  {
1475
1505
  "name": "user",
@@ -1484,6 +1514,20 @@
1484
1514
  "required": false,
1485
1515
  "default": "\"\"",
1486
1516
  "description": "Page title shown at the left of the navbar (hidden below the `md` breakpoint)."
1517
+ },
1518
+ {
1519
+ "name": "searchAlign",
1520
+ "type": "NavbarSearchAlign",
1521
+ "required": false,
1522
+ "default": "\"start\"",
1523
+ "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."
1524
+ },
1525
+ {
1526
+ "name": "actionsOnMobile",
1527
+ "type": "NavbarActionsOnMobile",
1528
+ "required": false,
1529
+ "default": "\"wrap\"",
1530
+ "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
1531
  }
1488
1532
  ],
1489
1533
  "events": [
@@ -1501,12 +1545,12 @@
1501
1545
  },
1502
1546
  {
1503
1547
  "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.",
1548
+ "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
1549
  "bindings": []
1506
1550
  },
1507
1551
  {
1508
1552
  "name": "actions",
1509
- "description": "Page-level primary actions (e.g. a Create button), right-aligned next to the user menu.",
1553
+ "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
1554
  "bindings": [
1511
1555
  {
1512
1556
  "title": "binding",
@@ -1534,7 +1578,7 @@
1534
1578
  },
1535
1579
  {
1536
1580
  "name": "user-icon",
1537
- "description": "Custom user avatar/icon in the dropdown trigger.",
1581
+ "description": "Custom user avatar/icon in the dropdown trigger. To decorate the default avatar (e.g. add a notification dot) rather than replace it, render `DXUserAvatar` — the same component used here, so the styling comes with it.",
1538
1582
  "bindings": [
1539
1583
  {
1540
1584
  "title": "binding",
@@ -1543,6 +1587,14 @@
1543
1587
  },
1544
1588
  "name": "initial",
1545
1589
  "description": "The first letter of the user's name."
1590
+ },
1591
+ {
1592
+ "title": "binding",
1593
+ "type": {
1594
+ "name": "object"
1595
+ },
1596
+ "name": "user",
1597
+ "description": "The signed-in user."
1546
1598
  }
1547
1599
  ]
1548
1600
  },
@@ -1767,7 +1819,7 @@
1767
1819
  "name": "DXField",
1768
1820
  "category": "extended",
1769
1821
  "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.",
1822
+ "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
1823
  "props": [
1772
1824
  {
1773
1825
  "name": "field",
@@ -1968,7 +2020,13 @@
1968
2020
  }
1969
2021
  ],
1970
2022
  "events": [],
1971
- "slots": [],
2023
+ "slots": [
2024
+ {
2025
+ "name": "popover",
2026
+ "description": "Rich popover body. Overrides the plain `info` text — use",
2027
+ "bindings": []
2028
+ }
2029
+ ],
1972
2030
  "methods": []
1973
2031
  },
1974
2032
  {
@@ -2823,6 +2881,39 @@
2823
2881
  "required": false,
2824
2882
  "description": "API endpoint for creating new items (e.g., \"/api/products\") — enables \"New\" button"
2825
2883
  },
2884
+ {
2885
+ "name": "showCreateButton",
2886
+ "type": "boolean",
2887
+ "required": false,
2888
+ "default": "true",
2889
+ "description": "Render the built-in \"New {item}\" button in the card header (default\n`true` whenever `createUrl` is set). Set `false` to drive the create\nmodal from your own trigger elsewhere — e.g. a button in the dashboard\nnavbar's actions slot calling the exposed `openCreate()`. With no\n`title` and no `header` slot, the header is then dropped entirely\nrather than left empty."
2890
+ },
2891
+ {
2892
+ "name": "footClone",
2893
+ "type": "boolean",
2894
+ "required": false,
2895
+ "default": "false",
2896
+ "description": "Render a `<tfoot>` mirroring the header row, so `#foot(<key>)` slots can\nhold per-column totals that line up under their data (#99). Without it\nthere is no footer to put them in. For a fully custom footer, use the\n`custom-foot` slot instead."
2897
+ },
2898
+ {
2899
+ "name": "showEmpty",
2900
+ "type": "boolean",
2901
+ "required": false,
2902
+ "default": "true",
2903
+ "description": "Show a message row when there are no rows (default `true`). An empty\ntable body is indistinguishable from a broken one, so the message is on\nby default. Override the wording with `emptyText`, or the whole row with\nthe `empty` slot."
2904
+ },
2905
+ {
2906
+ "name": "emptyText",
2907
+ "type": "string",
2908
+ "required": false,
2909
+ "description": "Message shown when there are no rows. Defaults to \"No {items} found\", or\n\"No {items} match your filters\" when a column filter is active."
2910
+ },
2911
+ {
2912
+ "name": "expandedItems",
2913
+ "type": "Array",
2914
+ "required": false,
2915
+ "description": "Rows currently expanded, as a `v-model`. Pair with the `row-expansion`\nslot to render detail content under a row (#112) instead of forcing every\nper-row detail into a modal."
2916
+ },
2826
2917
  {
2827
2918
  "name": "clientSide",
2828
2919
  "type": "boolean",
@@ -2891,6 +2982,11 @@
2891
2982
  "description": "`v-model:sortBy` update, emitted with the normalized single-column sort array when sorting changes.",
2892
2983
  "properties": []
2893
2984
  },
2985
+ {
2986
+ "name": "update:expandedItems",
2987
+ "description": "`v-model:expandedItems` update, emitted with the rows currently expanded.",
2988
+ "properties": []
2989
+ },
2894
2990
  {
2895
2991
  "name": "update:filters",
2896
2992
  "description": "`v-model:filters` update, emitted with the new filter map when a column filter changes.",
@@ -2915,7 +3011,7 @@
2915
3011
  },
2916
3012
  {
2917
3013
  "name": "name",
2918
- "description": "Custom rendering for a column's cell. Name it `cell(<fieldKey>)` to target a field; receives the underlying table cell scope.",
3014
+ "description": "Any slot the underlying table supports, forwarded with its scope: `cell(<fieldKey>)` for a cell, `foot(<fieldKey>)` for a footer cell (needs `footClone`), `empty` for the no-rows message, `row-expansion` for expandable row detail, plus `custom-foot`, `top-row`, `bottom-row`, `thead-sub` and the `table-*` slots.",
2919
3015
  "bindings": [
2920
3016
  {
2921
3017
  "name": "name",
@@ -3116,12 +3212,64 @@
3116
3212
  }
3117
3213
  ],
3118
3214
  "methods": []
3215
+ },
3216
+ {
3217
+ "name": "DXUserAvatar",
3218
+ "category": "extended",
3219
+ "filePath": "resources/js/components/extended/DXUserAvatar.vue",
3220
+ "description": "The circular user avatar shown in the dashboard navbar's user menu: a single initial on a solid disc, with an optional notification dot. It exists as its own component because `DXDashboardNavbar`'s `user-icon` slot is one consumers routinely *decorate* rather than replace — adding an unread badge, wrapping it in a link. The navbar's styles are scoped, so slot content (compiled in the consumer's scope) can't reuse them; without this component every override starts by re-implementing the avatar's CSS, which then drifts from the theme. Rendering it here means an override is one line and stays on theme.",
3221
+ "props": [
3222
+ {
3223
+ "name": "user",
3224
+ "type": "union",
3225
+ "required": false,
3226
+ "default": "null",
3227
+ "description": "The user whose initial is shown. The first letter of `name` is used."
3228
+ },
3229
+ {
3230
+ "name": "initial",
3231
+ "type": "string",
3232
+ "required": false,
3233
+ "default": "\"\"",
3234
+ "description": "Overrides the initial derived from `user.name` (e.g. two-letter initials)."
3235
+ },
3236
+ {
3237
+ "name": "badge",
3238
+ "type": "boolean",
3239
+ "required": false,
3240
+ "default": "false",
3241
+ "description": "Show a notification dot on the avatar (e.g. unread updates)."
3242
+ },
3243
+ {
3244
+ "name": "badgeVariant",
3245
+ "type": "ColorVariant",
3246
+ "required": false,
3247
+ "default": "\"danger\"",
3248
+ "description": "Colour of the notification dot."
3249
+ },
3250
+ {
3251
+ "name": "badgeLabel",
3252
+ "type": "string",
3253
+ "required": false,
3254
+ "default": "\"Unread notifications\"",
3255
+ "description": "Accessible text for the notification dot, announced to screen readers\n(the dot itself is a colour-only signal). Ignored unless `badge` is set."
3256
+ }
3257
+ ],
3258
+ "events": [],
3259
+ "slots": [
3260
+ {
3261
+ "name": "default",
3262
+ "description": "Replaces the initial inside the disc (e.g. a photo or an icon).",
3263
+ "bindings": []
3264
+ }
3265
+ ],
3266
+ "methods": []
3119
3267
  }
3120
3268
  ]
3121
3269
  },
3122
3270
  "stats": {
3123
- "totalComponents": 73,
3271
+ "totalComponents": 74,
3124
3272
  "baseComponents": 57,
3125
- "extendedComponents": 16
3273
+ "extendedComponents": 17
3126
3274
  }
3127
3275
  }
@@ -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-11T08:44:15.559Z
5
5
 
6
6
  This file provides a complete map of all available documentation for AI agents and developers.
7
7
 
@@ -82,6 +82,7 @@ This file provides a complete map of all available documentation for AI agents a
82
82
  - [DXStatCard](/components/extended/DXStatCard): A KPI / stat tile: a big formatted value with a title, optional subtitle, an
83
83
  - [DXSwitch](/components/extended/DXSwitch): A "filled box" toggle switch built on `DFormCheckbox`: a bordered box sized like
84
84
  - [DXTable](/components/extended/DXTable): A comprehensive data table component with built-in pagination, sorting, filtering, and CRUD operatio
85
+ - [DXUserAvatar](/components/extended/DXUserAvatar): The circular user avatar shown in the dashboard navbar's user menu: a single
85
86
 
86
87
  ### Examples
87
88
 
@@ -100,4 +101,4 @@ This file provides a complete map of all available documentation for AI agents a
100
101
 
101
102
  ---
102
103
 
103
- **Total Pages**: 76
104
+ **Total Pages**: 77
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Vue 3 dashboard components for Laravel with Bootstrap Vue Next
4
4
  >
5
- > A dual-package library (NPM + Composer) providing 68 Vue 3 components for building Laravel dashboards with Bootstrap Vue Next.
5
+ > A dual-package library (NPM + Composer) providing 69 Vue 3 components for building Laravel dashboards with Bootstrap Vue Next.
6
6
 
7
7
  ## Installation
8
8
 
@@ -81,7 +81,7 @@ Lightweight type-safe wrappers around Bootstrap Vue Next components providing AP
81
81
  - [DToaster](/components/base/dtoaster): Bootstrap Vue Next Toaster wrapper
82
82
  - [DTooltip](/components/base/dtooltip): Bootstrap Vue Next Tooltip wrapper
83
83
 
84
- ## Extended Components (11 components)
84
+ ## Extended Components (12 components)
85
85
 
86
86
  Custom dashboard components with advanced functionality beyond Bootstrap Vue Next.
87
87
 
@@ -96,6 +96,7 @@ Custom dashboard components with advanced functionality beyond Bootstrap Vue Nex
96
96
  - [DXStatCard](/components/extended/dxstatcard): Extended dashboard component
97
97
  - [DXSwitch](/components/extended/dxswitch): Extended dashboard component
98
98
  - [DXTable](/components/extended/dxtable): Data table with pagination, filtering, and sorting
99
+ - [DXUserAvatar](/components/extended/dxuseravatar): Extended dashboard component
99
100
 
100
101
  ## Composables
101
102
 
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.23.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
  // ----------------------------------------------------------------------------
@@ -25,8 +25,17 @@
25
25
  :key="strippedName"
26
26
  #[strippedName]="slotProps"
27
27
  >
28
- <!-- @slot Forwards any `sidebar-*` slot to DXDashboardSidebar with the `sidebar-` prefix stripped (e.g. `sidebar-brand` becomes the sidebar's `brand` slot). -->
29
- <slot :name="originalName" v-bind="slotProps" />
28
+ <!--
29
+ @slot Forwards any `sidebar-*` slot to DXDashboardSidebar with the `sidebar-` prefix stripped (e.g. `sidebar-brand` becomes the sidebar's `brand` slot).
30
+ @binding {function} toggleSidebar Shows/hides the sidebar — so slot content (e.g. a close affordance in the brand row) can drive it.
31
+ @binding {boolean} sidebarHidden Whether the sidebar is currently hidden.
32
+ -->
33
+ <slot
34
+ :name="originalName"
35
+ v-bind="slotProps"
36
+ :toggleSidebar="toggleSidebar"
37
+ :sidebarHidden="hidden"
38
+ />
30
39
  </template>
31
40
  </DXDashboardSidebar>
32
41
 
@@ -36,7 +45,8 @@
36
45
  <DXDashboardNavbar
37
46
  :page-title="pageTitle"
38
47
  :user="user"
39
- :logout-url="logoutUrl"
48
+ :search-align="searchAlign"
49
+ :actions-on-mobile="actionsOnMobile"
40
50
  @toggle-sidebar="toggleSidebar"
41
51
  >
42
52
  <!-- Dynamically forward all navbar-* slots by stripping the prefix -->
@@ -45,8 +55,17 @@
45
55
  :key="strippedName"
46
56
  #[strippedName]="slotProps"
47
57
  >
48
- <!-- @slot Forwards any `navbar-*` slot to DXDashboardNavbar with the `navbar-` prefix stripped (e.g. `navbar-actions` becomes the navbar's `actions` slot). -->
49
- <slot :name="originalName" v-bind="slotProps" />
58
+ <!--
59
+ @slot Forwards any `navbar-*` slot to DXDashboardNavbar with the `navbar-` prefix stripped (e.g. `navbar-actions` becomes the navbar's `actions` slot).
60
+ @binding {function} toggleSidebar Shows/hides the sidebar.
61
+ @binding {boolean} sidebarHidden Whether the sidebar is currently hidden.
62
+ -->
63
+ <slot
64
+ :name="originalName"
65
+ v-bind="slotProps"
66
+ :toggleSidebar="toggleSidebar"
67
+ :sidebarHidden="hidden"
68
+ />
50
69
  </template>
51
70
  </DXDashboardNavbar>
52
71
 
@@ -80,10 +99,16 @@ import DXDashboardNavbar from './DXDashboardNavbar.vue';
80
99
  import DContainer from '../base/DContainer.vue';
81
100
  import DRow from '../base/DRow.vue';
82
101
  import DCol from '../base/DCol.vue';
83
- import type { Navigation } from '../../types/navigation';
102
+ import type { Navigation, NavbarActionsOnMobile, NavbarSearchAlign } from '../../types/navigation';
84
103
 
85
104
  const slots = useSlots();
86
105
 
106
+ // Slot-binding precedence: the forwarded slots bind the child's own slot props
107
+ // FIRST and `toggleSidebar`/`sidebarHidden` after, so ours win a name clash.
108
+ // That's deliberate — the dashboard guarantees those two bindings on every
109
+ // forwarded slot, and a child that later grew a same-named prop must not be
110
+ // able to silently take the guarantee away. No child uses those names today.
111
+
87
112
  interface Props {
88
113
  /** Navigation structure for sidebar */
89
114
  navigation: Navigation;
@@ -116,8 +141,18 @@ interface Props {
116
141
  /** User object for navbar dropdown */
117
142
  user?: { name: string; email: string } | null;
118
143
 
119
- /** Logout URL for navbar dropdown */
120
- logoutUrl?: string;
144
+ /**
145
+ * Horizontal alignment of the navbar search slot content (`"start"` = flush
146
+ * left, `"center"` = centred), forwarded to DXDashboardNavbar.
147
+ */
148
+ searchAlign?: NavbarSearchAlign;
149
+
150
+ /**
151
+ * What the navbar actions slot does below `md`: `"wrap"` to its own
152
+ * full-width row, `"hide"` to remove it (relocate actions into the page on
153
+ * phones). Forwarded to DXDashboardNavbar.
154
+ */
155
+ actionsOnMobile?: NavbarActionsOnMobile;
121
156
 
122
157
  /**
123
158
  * Turn sidebar group headers into accordion toggles that collapse/expand
@@ -147,12 +182,13 @@ const props = withDefaults(defineProps<Props>(), {
147
182
  title: 'Dashboard',
148
183
  pageTitle: '',
149
184
  user: null,
150
- logoutUrl: '/logout',
151
185
  collapsibleGroups: false,
152
186
  autoCollapseInactiveGroups: true,
153
187
  storageKey: 'dashboard-sidebar-hidden',
154
188
  dashboardId: '',
155
189
  contentMaxWidth: '1140px',
190
+ searchAlign: 'start',
191
+ actionsOnMobile: 'wrap',
156
192
  });
157
193
 
158
194
  const collapsed = ref(false);
@@ -240,12 +276,26 @@ const toggleSidebar = () => {
240
276
  console.error('Error saving sidebar state:', error);
241
277
  }
242
278
  };
279
+
280
+ defineExpose({
281
+ /**
282
+ * Show/hide the sidebar (same as clicking the navbar's hamburger). The
283
+ * dashboard owns the visibility state, so this is the supported way to drive
284
+ * it from outside — page content, a custom brand row, a keyboard shortcut.
285
+ * Also available as a slot binding on every forwarded `sidebar-*`/`navbar-*`
286
+ * slot, which is usually more convenient.
287
+ */
288
+ toggleSidebar,
289
+
290
+ /** Whether the sidebar is currently hidden. */
291
+ sidebarHidden: computed(() => hidden.value),
292
+ });
243
293
  </script>
244
294
 
245
295
  <style scoped>
246
296
  .dashboard-layout {
247
297
  min-height: 100vh;
248
- background-color: #f8f9fa;
298
+ background-color: var(--bs-light);
249
299
  }
250
300
 
251
301
  .dashboard-content {
@@ -259,6 +309,6 @@ const toggleSidebar = () => {
259
309
  max-width: 100%;
260
310
  /* A white content panel under the light grey top bar / layout, so forms and
261
311
  content sit on white (higher contrast than a grey-everywhere dashboard). */
262
- background-color: #fff;
312
+ background-color: var(--bs-white);
263
313
  }
264
314
  </style>