mgv-backoffice 1.19.0 → 1.20.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.
package/README.md CHANGED
@@ -707,6 +707,8 @@ computePnL({ buyPrice: 100, lastPrice: 110, filledQty: 5 })
707
707
  ### BaseAppLayout
708
708
 
709
709
  Root layout: dark/light page background, skip link, `<main>`-with-inert wrapper.
710
+ The `<main>` content offset tracks the sidebar width automatically —
711
+ `lg:ml-60` when expanded, `lg:ml-16` when collapsed (via `useSidebarCollapse()`).
710
712
 
711
713
  **Props:**
712
714
 
@@ -727,8 +729,9 @@ Root layout: dark/light page background, skip link, `<main>`-with-inert wrapper.
727
729
  ### BaseSidebar
728
730
 
729
731
  Responsive sidebar with desktop fixed-positioning and mobile off-canvas
730
- behavior, focus management, optional theme toggle, and configurable nav
731
- sections.
732
+ behavior, focus management, optional theme toggle, a desktop collapse
733
+ toggle (icon-only rail), an optional notifications bell, and configurable
734
+ nav sections.
732
735
 
733
736
  **Props:**
734
737
 
@@ -739,6 +742,18 @@ sections.
739
742
  | `appName` | `String` | `''` | Optional app name in the footer. |
740
743
  | `version` | `String` | `''` | Optional version string in the footer. |
741
744
  | `showThemeToggle` | `Boolean` | `true` | Toggle the dark/light switch in the footer. |
745
+ | `collapsible` | `Boolean` | `true` | Show the desktop collapse toggle that shrinks the sidebar to an icon-only rail. |
746
+ | `showNotifications` | `Boolean` | `false` | Show the notifications bell (with unread badge) that toggles `BaseNotificationPanel`. |
747
+
748
+ > **Collapse state** is shared via `useSidebarCollapse()` (and persisted to
749
+ > localStorage) so `BaseAppLayout` can shrink the content offset from
750
+ > `lg:ml-60` to `lg:ml-16` in step with the rail. Collapsing only affects
751
+ > desktop (`lg+`); on mobile the sidebar stays a full off-canvas panel.
752
+
753
+ > **Notifications:** set `:show-notifications="true"` to render the bell,
754
+ > then drop a [`BaseNotificationPanel`](#basenotificationpanel) in your app.
755
+ > Both share state through `useNotifications()`, so the unread badge and the
756
+ > panel stay in sync.
742
757
 
743
758
  **Slots:**
744
759
 
@@ -776,6 +791,127 @@ interface NavSection {
776
791
 
777
792
  ---
778
793
 
794
+ ### BaseNotificationPanel
795
+
796
+ Left-anchored notification drawer (teleported to `<body>`, slides in from
797
+ the left, backdrop + Escape to close). Open/close state and the list live
798
+ in `useNotifications()`, so the sidebar bell and the panel stay in sync.
799
+
800
+ Enable the bell on the sidebar with `:show-notifications="true"`, drop one
801
+ `<BaseNotificationPanel />` anywhere in your app, and feed it data via the
802
+ composable.
803
+
804
+ **Props:**
805
+
806
+ | Prop | Type | Default | Description |
807
+ | ----------------- | --------- | ------------------------------ | ----------- |
808
+ | `title` | `String` | `'Notifications'` | Panel heading. |
809
+ | `emptyText` | `String` | `'You have no notifications.'` | Shown when the list is empty. |
810
+ | `showMarkAllRead` | `Boolean` | `true` | Render the "Mark all as read" action when there are unread items. |
811
+
812
+ **Emits:** `select` (the clicked notification's `id`; the row is also marked read).
813
+
814
+ ```vue
815
+ <script setup lang="ts">
816
+ import { BaseNotificationPanel, useNotifications } from 'mgv-backoffice'
817
+ const { setNotifications } = useNotifications()
818
+ setNotifications([
819
+ { id: 1, title: 'New comment', message: 'Alice replied to your post', time: '2m ago', type: 'info' },
820
+ { id: 2, title: 'Build passed', time: '1h ago', read: true, type: 'success' },
821
+ ])
822
+ </script>
823
+
824
+ <template>
825
+ <BaseSidebar :sections="navSections" :show-notifications="true" />
826
+ <BaseNotificationPanel @select="(id) => goTo(id)" />
827
+ </template>
828
+ ```
829
+
830
+ ```ts
831
+ import type { NotificationItem } from 'mgv-backoffice'
832
+
833
+ interface NotificationItem {
834
+ id: string | number
835
+ title: string
836
+ message?: string
837
+ time?: string // pre-formatted by you
838
+ read?: boolean
839
+ type?: 'info' | 'success' | 'warning' | 'error' // status dot colour
840
+ }
841
+ ```
842
+
843
+ ---
844
+
845
+ ## Authentication
846
+
847
+ ### BaseGoogleSignInButton
848
+
849
+ Google-branded "Sign in with Google" button (official multi-colour "G",
850
+ dark-mode surface swap). Purely presentational — it runs no OAuth itself;
851
+ listen on `click` and start your own Google Identity / Firebase / backend
852
+ flow there.
853
+
854
+ **Props:**
855
+
856
+ | Prop | Type | Default | Description |
857
+ | ---------- | --------- | -------------------------- | ----------- |
858
+ | `label` | `String` | `'Sign in with Google'` | Button text. |
859
+ | `loading` | `Boolean` | `false` | Disables and shows a spinner. |
860
+ | `disabled` | `Boolean` | `false` | Disables without the spinner. |
861
+ | `block` | `Boolean` | `true` | Full-width layout. |
862
+
863
+ **Emits:** `click` (only when not disabled/loading).
864
+
865
+ ### BaseLoginForm
866
+
867
+ Presentational sign-in card: email + password (with show/hide), an optional
868
+ "Remember me" checkbox, an error banner, the Google button + "or" divider,
869
+ and `logo` / `forgot` / `footer` slots. Owns its input state and emits
870
+ `submit` / `google-sign-in`; the app handles the actual request and feeds
871
+ back `loading` / `error`.
872
+
873
+ **Props:**
874
+
875
+ | Prop | Type | Default | Description |
876
+ | --------------- | --------- | ----------- | ----------- |
877
+ | `title` | `String` | `'Sign in'` | Card heading. |
878
+ | `subtitle` | `String` | `''` | Muted line under the heading. |
879
+ | `submitLabel` | `String` | `'Sign in'` | Submit button text. |
880
+ | `loading` | `Boolean` | `false` | Disables the form, spinner on submit. |
881
+ | `googleLoading` | `Boolean` | `false` | Disables the form, spinner on the Google button. |
882
+ | `error` | `String` | `''` | Error banner above the form. |
883
+ | `showGoogle` | `Boolean` | `true` | Render the Google button + divider. |
884
+ | `showRemember` | `Boolean` | `false` | Render the "Remember me" checkbox. |
885
+
886
+ **Emits:** `submit` (`LoginCredentials`), `google-sign-in`.
887
+
888
+ **Slots:** `logo`, `forgot` (next to the password label), `footer`.
889
+
890
+ ```vue
891
+ <script setup lang="ts">
892
+ import { BaseLoginForm } from 'mgv-backoffice'
893
+ import type { LoginCredentials } from 'mgv-backoffice'
894
+
895
+ async function onSubmit(creds: LoginCredentials) { /* call your API */ }
896
+ function onGoogle() { /* start Google OAuth */ }
897
+ </script>
898
+
899
+ <template>
900
+ <BaseLoginForm
901
+ subtitle="Welcome back"
902
+ :show-remember="true"
903
+ @submit="onSubmit"
904
+ @google-sign-in="onGoogle"
905
+ >
906
+ <template #logo><MyLogo /></template>
907
+ <template #forgot><a href="/forgot" class="text-sm text-emerald-600">Forgot?</a></template>
908
+ <template #footer>No account? <a href="/signup" class="text-emerald-600">Sign up</a></template>
909
+ </BaseLoginForm>
910
+ </template>
911
+ ```
912
+
913
+ ---
914
+
779
915
  ## Modals & sections
780
916
 
781
917
  ### BaseModalShell
@@ -1025,6 +1161,8 @@ import {
1025
1161
  useDebouncedRef,
1026
1162
  useToast,
1027
1163
  useMobileSidebar,
1164
+ useSidebarCollapse,
1165
+ useNotifications,
1028
1166
  } from 'mgv-backoffice'
1029
1167
  ```
1030
1168
 
@@ -1037,6 +1175,8 @@ import {
1037
1175
  | `useDebouncedRef(source, delay?)` | Debounced mirror of a ref. Timer cleared on scope dispose. |
1038
1176
  | `useToast(durationMs?)` | Per-component toast state: `{ showToast, toastMessage, toastType, showToastMessage }`. |
1039
1177
  | `useMobileSidebar()` | Singleton state shared between `BaseSidebar` and `BaseAppLayout` for the off-canvas open/closed flag. |
1178
+ | `useSidebarCollapse({ storageKey? })` | Singleton collapsed/expanded state for the desktop sidebar rail, shared between `BaseSidebar` and `BaseAppLayout` and persisted to localStorage (default key `'mgv-sidebar-collapsed'`). |
1179
+ | `useNotifications()` | Singleton notification state shared by the sidebar bell and `BaseNotificationPanel`: `{ notifications, unreadCount, open, openPanel, closePanel, togglePanel, setNotifications, add, remove, markRead, markAllRead, clear }`. |
1040
1180
 
1041
1181
  ---
1042
1182
 
@@ -0,0 +1,22 @@
1
+ interface Props {
2
+ /** Button label. Defaults to "Sign in with Google". */
3
+ label?: string;
4
+ /** Disables the button and shows a busy spinner. */
5
+ loading?: boolean;
6
+ /** Disables the button without the busy spinner. */
7
+ disabled?: boolean;
8
+ /** Full-width (block) layout. Defaults to true. */
9
+ block?: boolean;
10
+ }
11
+ declare const __VLS_export: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
12
+ click: () => any;
13
+ }, string, import('vue').PublicProps, Readonly<Props> & Readonly<{
14
+ onClick?: (() => any) | undefined;
15
+ }>, {
16
+ label: string;
17
+ disabled: boolean;
18
+ loading: boolean;
19
+ block: boolean;
20
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
21
+ declare const _default: typeof __VLS_export;
22
+ export default _default;
@@ -0,0 +1,51 @@
1
+ import { LoginCredentials } from '../types/auth';
2
+ interface Props {
3
+ /** Card heading. Defaults to "Sign in". */
4
+ title?: string;
5
+ /** Optional muted line under the heading. */
6
+ subtitle?: string;
7
+ /** Submit button label. Defaults to "Sign in". */
8
+ submitLabel?: string;
9
+ /** Disables the form and shows a spinner on the submit button. */
10
+ loading?: boolean;
11
+ /** Disables the form and shows a spinner on the Google button. */
12
+ googleLoading?: boolean;
13
+ /** Error message rendered above the form (e.g. "Invalid credentials"). */
14
+ error?: string;
15
+ /** Render the "Sign in with Google" button + divider. Defaults to true. */
16
+ showGoogle?: boolean;
17
+ /** Render the "Remember me" checkbox. Defaults to false. */
18
+ showRemember?: boolean;
19
+ }
20
+ declare var __VLS_1: {}, __VLS_10: {}, __VLS_12: {};
21
+ type __VLS_Slots = {} & {
22
+ logo?: (props: typeof __VLS_1) => any;
23
+ } & {
24
+ forgot?: (props: typeof __VLS_10) => any;
25
+ } & {
26
+ footer?: (props: typeof __VLS_12) => any;
27
+ };
28
+ declare const __VLS_base: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
29
+ submit: (credentials: LoginCredentials) => any;
30
+ "google-sign-in": () => any;
31
+ }, string, import('vue').PublicProps, Readonly<Props> & Readonly<{
32
+ onSubmit?: ((credentials: LoginCredentials) => any) | undefined;
33
+ "onGoogle-sign-in"?: (() => any) | undefined;
34
+ }>, {
35
+ title: string;
36
+ error: string;
37
+ subtitle: string;
38
+ loading: boolean;
39
+ submitLabel: string;
40
+ googleLoading: boolean;
41
+ showGoogle: boolean;
42
+ showRemember: boolean;
43
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
44
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
45
+ declare const _default: typeof __VLS_export;
46
+ export default _default;
47
+ type __VLS_WithSlots<T, S> = T & {
48
+ new (): {
49
+ $slots: S;
50
+ };
51
+ };
@@ -0,0 +1,19 @@
1
+ interface Props {
2
+ /** Panel heading. Defaults to "Notifications". */
3
+ title?: string;
4
+ /** Shown when there are no notifications. */
5
+ emptyText?: string;
6
+ /** Render the "Mark all read" action when there are unread items. */
7
+ showMarkAllRead?: boolean;
8
+ }
9
+ declare const __VLS_export: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
10
+ select: (id: string | number) => any;
11
+ }, string, import('vue').PublicProps, Readonly<Props> & Readonly<{
12
+ onSelect?: ((id: string | number) => any) | undefined;
13
+ }>, {
14
+ title: string;
15
+ emptyText: string;
16
+ showMarkAllRead: boolean;
17
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
18
+ declare const _default: typeof __VLS_export;
19
+ export default _default;
@@ -10,26 +10,32 @@ interface Props {
10
10
  version?: string;
11
11
  /** Whether to render the dark/light toggle in the footer. */
12
12
  showThemeToggle?: boolean;
13
+ /** Whether to render the desktop collapse toggle (icon-only rail). */
14
+ collapsible?: boolean;
15
+ /** Whether to render the notifications bell button. */
16
+ showNotifications?: boolean;
13
17
  }
14
18
  declare var __VLS_1: {
15
19
  size: number;
16
20
  }, __VLS_13: {
17
21
  size: number;
18
- }, __VLS_25: {}, __VLS_27: {};
22
+ }, __VLS_40: {}, __VLS_42: {};
19
23
  type __VLS_Slots = {} & {
20
24
  logo?: (props: typeof __VLS_1) => any;
21
25
  } & {
22
26
  logo?: (props: typeof __VLS_13) => any;
23
27
  } & {
24
- status?: (props: typeof __VLS_25) => any;
28
+ status?: (props: typeof __VLS_40) => any;
25
29
  } & {
26
- footer?: (props: typeof __VLS_27) => any;
30
+ footer?: (props: typeof __VLS_42) => any;
27
31
  };
28
32
  declare const __VLS_base: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<Props> & Readonly<{}>, {
29
33
  homeRouteName: string;
30
34
  appName: string;
31
35
  version: string;
32
36
  showThemeToggle: boolean;
37
+ collapsible: boolean;
38
+ showNotifications: boolean;
33
39
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
34
40
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
35
41
  declare const _default: typeof __VLS_export;
@@ -0,0 +1,29 @@
1
+ import { NotificationItem } from '../types/notification';
2
+ export declare function useNotifications(): {
3
+ notifications: import('vue').Ref<{
4
+ id: string | number;
5
+ title: string;
6
+ message?: string | undefined;
7
+ time?: string | undefined;
8
+ read?: boolean | undefined;
9
+ type?: "info" | "success" | "warning" | "error" | undefined;
10
+ }[], NotificationItem[] | {
11
+ id: string | number;
12
+ title: string;
13
+ message?: string | undefined;
14
+ time?: string | undefined;
15
+ read?: boolean | undefined;
16
+ type?: "info" | "success" | "warning" | "error" | undefined;
17
+ }[]>;
18
+ unreadCount: import('vue').ComputedRef<number>;
19
+ open: import('vue').Ref<boolean, boolean>;
20
+ openPanel: () => void;
21
+ closePanel: () => void;
22
+ togglePanel: () => void;
23
+ setNotifications: (items: NotificationItem[]) => void;
24
+ add: (item: NotificationItem) => void;
25
+ remove: (id: NotificationItem["id"]) => void;
26
+ markRead: (id: NotificationItem["id"]) => void;
27
+ markAllRead: () => void;
28
+ clear: () => void;
29
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Shared collapsed/expanded state for the desktop sidebar.
3
+ *
4
+ * Module-scoped singleton (mirrors useMobileSidebar) so `BaseSidebar` and
5
+ * the page layout (`BaseAppLayout`, which offsets `<main>` by the sidebar
6
+ * width) stay in sync. When collapsed, the sidebar shrinks to an icon-only
7
+ * rail on desktop.
8
+ *
9
+ * The preference is persisted to localStorage. The first consumer can pass
10
+ * an options object to override the key; subsequent callers reuse the same
11
+ * singleton and the option is only consulted on the first call.
12
+ */
13
+ export interface UseSidebarCollapseOptions {
14
+ /** localStorage key used to persist the preference. */
15
+ storageKey?: string;
16
+ }
17
+ export declare function useSidebarCollapse(options?: UseSidebarCollapseOptions): {
18
+ collapsed: import('vue').Ref<boolean, boolean>;
19
+ collapse: () => void;
20
+ expand: () => void;
21
+ toggle: () => void;
22
+ };
package/dist/index.d.ts CHANGED
@@ -25,6 +25,9 @@ export { default as BasePageHeader } from './components/BasePageHeader.vue';
25
25
  export { default as BaseToolbarButton } from './components/BaseToolbarButton.vue';
26
26
  export { default as BaseActionButton } from './components/BaseActionButton.vue';
27
27
  export { default as BaseCopyButton } from './components/BaseCopyButton.vue';
28
+ export { default as BaseGoogleSignInButton } from './components/BaseGoogleSignInButton.vue';
29
+ export { default as BaseLoginForm } from './components/BaseLoginForm.vue';
30
+ export { default as BaseNotificationPanel } from './components/BaseNotificationPanel.vue';
28
31
  export { useTheme, initTheme } from './composables/useTheme';
29
32
  export type { UseThemeOptions } from './composables/useTheme';
30
33
  export { useThemeClasses } from './composables/useThemeClasses';
@@ -33,6 +36,9 @@ export { useEscapeKey } from './composables/useEscapeKey';
33
36
  export { useDebouncedRef } from './composables/useDebounce';
34
37
  export { useToast } from './composables/useToast';
35
38
  export { useMobileSidebar } from './composables/useMobileSidebar';
39
+ export { useSidebarCollapse } from './composables/useSidebarCollapse';
40
+ export type { UseSidebarCollapseOptions } from './composables/useSidebarCollapse';
41
+ export { useNotifications } from './composables/useNotifications';
36
42
  export { AlertEnum } from './enums/AlertEnum';
37
43
  export { BaseBadgeEnum } from './enums/BaseBadgeEnum';
38
44
  export { BaseButtonEnum } from './enums/BaseButtonEnum';
@@ -46,6 +52,8 @@ export { PositioningEnum } from './enums/PositioningEnum';
46
52
  export type { BreadCrumb } from './components/BaseBreadcrumb.vue';
47
53
  export type { NavItem, NavSection } from './types/sidebar';
48
54
  export type { EntityPickerItem } from './types/entityPicker';
55
+ export type { LoginCredentials } from './types/auth';
56
+ export type { NotificationItem } from './types/notification';
49
57
  export { getBaseColor, getBaseColorOf } from './utils/util';
50
58
  export { methodBadgeSolid, methodBadgeBright, statusBadgeSolid, statusBadgeTinted, } from './utils/httpColors';
51
59
  export { sanitizeHtml, isSafeHref } from './utils/sanitizeHtml';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Credentials emitted by `BaseLoginForm` on submit.
3
+ *
4
+ * `remember` is only present when the form is rendered with the
5
+ * "remember me" checkbox enabled.
6
+ */
7
+ export interface LoginCredentials {
8
+ email: string;
9
+ password: string;
10
+ remember?: boolean;
11
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * A single entry rendered in `BaseNotificationPanel` and counted by the
3
+ * sidebar's unread badge.
4
+ *
5
+ * • `id` — stable key, used for list rendering and mark/remove ops.
6
+ * • `title` — bold headline line.
7
+ * • `message` — optional secondary line.
8
+ * • `time` — optional pre-formatted timestamp (e.g. "2h ago"). The
9
+ * library does not format dates for you.
10
+ * • `read` — unread entries are highlighted and count toward the badge.
11
+ * • `type` — drives the small status dot colour.
12
+ */
13
+ export interface NotificationItem {
14
+ id: string | number;
15
+ title: string;
16
+ message?: string;
17
+ time?: string;
18
+ read?: boolean;
19
+ type?: 'info' | 'success' | 'warning' | 'error';
20
+ }