mgv-backoffice 1.0.13 → 1.2.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.
Files changed (83) hide show
  1. package/README.md +735 -70
  2. package/dist/index.d.ts +1 -27
  3. package/dist/{components → src/components}/BaseAlert.vue.d.ts +2 -2
  4. package/dist/src/components/BaseAppLayout.vue.d.ts +31 -0
  5. package/dist/{components → src/components}/BaseButton.vue.d.ts +5 -2
  6. package/dist/src/components/BaseCollapsibleSection.vue.d.ts +35 -0
  7. package/dist/src/components/BaseConfirmModal.vue.d.ts +23 -0
  8. package/dist/src/components/BaseEntityPickerModal.vue.d.ts +58 -0
  9. package/dist/{components → src/components}/BaseLogo.vue.d.ts +3 -3
  10. package/dist/{components → src/components}/BaseModal.vue.d.ts +4 -3
  11. package/dist/src/components/BaseModalShell.vue.d.ts +43 -0
  12. package/dist/src/components/BaseNotFoundPage.vue.d.ts +17 -0
  13. package/dist/src/components/BaseSidebar.vue.d.ts +46 -0
  14. package/dist/src/components/BaseTextInputModal.vue.d.ts +46 -0
  15. package/dist/{components → src/components}/BaseToast.vue.d.ts +14 -2
  16. package/dist/{components → src/components}/Pagination.vue.d.ts +0 -3
  17. package/dist/src/composables/useDebounce.d.ts +10 -0
  18. package/dist/src/composables/useEscapeKey.d.ts +6 -0
  19. package/dist/src/composables/useMobileSidebar.d.ts +6 -0
  20. package/dist/src/composables/useTheme.d.ts +19 -0
  21. package/dist/src/composables/useThemeClasses.d.ts +62 -0
  22. package/dist/src/composables/useToast.d.ts +19 -0
  23. package/dist/{enums → src/enums}/AlertEnum.d.ts +1 -1
  24. package/dist/{enums → src/enums}/BaseButtonSizeEnum.d.ts +1 -1
  25. package/dist/src/enums/BaseLogoEnum.d.ts +7 -0
  26. package/dist/src/index.d.ts +46 -0
  27. package/dist/src/types/entityPicker.d.ts +11 -0
  28. package/dist/src/types/sidebar.d.ts +22 -0
  29. package/dist/src/utils/httpColors.d.ts +19 -0
  30. package/dist/ui-lib.css +3 -0
  31. package/dist/ui-lib.js +1624 -1011
  32. package/dist/ui-lib.umd.cjs +1 -1
  33. package/package.json +14 -14
  34. package/src/components/BaseAlert.vue +2 -2
  35. package/src/components/BaseAppLayout.vue +65 -0
  36. package/src/components/BaseBreadcrumb.vue +2 -1
  37. package/src/components/BaseButton.vue +152 -132
  38. package/src/components/BaseCollapsibleSection.vue +107 -0
  39. package/src/components/BaseConfirmModal.vue +110 -0
  40. package/src/components/BaseEntityPickerModal.vue +330 -0
  41. package/src/components/BaseLogo.vue +5 -5
  42. package/src/components/BaseModal.vue +95 -75
  43. package/src/components/BaseModalShell.vue +115 -0
  44. package/src/components/BaseNotFoundPage.vue +51 -0
  45. package/src/components/BaseSidebar.vue +223 -0
  46. package/src/components/BaseTextInputModal.vue +144 -0
  47. package/src/components/BaseToast.vue +41 -20
  48. package/src/components/ColoredSquares.vue +2 -4
  49. package/src/components/Pagination.vue +137 -136
  50. package/src/components/TrendArrow.vue +1 -1
  51. package/src/composables/useDebounce.ts +32 -0
  52. package/src/composables/useEscapeKey.ts +15 -0
  53. package/src/composables/useMobileSidebar.ts +23 -0
  54. package/src/composables/useTheme.ts +69 -0
  55. package/src/composables/useThemeClasses.ts +133 -0
  56. package/src/composables/useToast.ts +56 -0
  57. package/src/enums/AlertEnum.ts +1 -1
  58. package/src/enums/BaseButtonSizeEnum.ts +1 -1
  59. package/src/enums/BaseLogoEnum.ts +4 -1
  60. package/src/index.ts +64 -36
  61. package/src/types/entityPicker.ts +11 -0
  62. package/src/types/sidebar.ts +24 -0
  63. package/src/utils/httpColors.ts +68 -0
  64. package/src/utils/util.ts +61 -53
  65. package/dist/enums/BaseLogoEnum.d.ts +0 -5
  66. package/dist/style.css +0 -1
  67. /package/dist/{components → src/components}/BaseBadge.vue.d.ts +0 -0
  68. /package/dist/{components → src/components}/BaseBreadcrumb.vue.d.ts +0 -0
  69. /package/dist/{components → src/components}/BaseLine.vue.d.ts +0 -0
  70. /package/dist/{components → src/components}/BaseRow.vue.d.ts +0 -0
  71. /package/dist/{components → src/components}/BaseSpinner.vue.d.ts +0 -0
  72. /package/dist/{components → src/components}/ColoredSquares.vue.d.ts +0 -0
  73. /package/dist/{components → src/components}/EarningsCard.vue.d.ts +0 -0
  74. /package/dist/{components → src/components}/EuroAmount.vue.d.ts +0 -0
  75. /package/dist/{components → src/components}/TrendArrow.vue.d.ts +0 -0
  76. /package/dist/{enums → src/enums}/BaseBadgeEnum.d.ts +0 -0
  77. /package/dist/{enums → src/enums}/BaseButtonEnum.d.ts +0 -0
  78. /package/dist/{enums → src/enums}/BaseModalEnum.d.ts +0 -0
  79. /package/dist/{enums → src/enums}/BaseToastEnum.d.ts +0 -0
  80. /package/dist/{enums → src/enums}/ColorsEnums.d.ts +0 -0
  81. /package/dist/{enums → src/enums}/LineEnum.d.ts +0 -0
  82. /package/dist/{enums → src/enums}/PositioningEnum.d.ts +0 -0
  83. /package/dist/{utils → src/utils}/util.d.ts +0 -0
@@ -1,136 +1,137 @@
1
- <template>
2
- <div>
3
- <ul>
4
- <li v-for="cell in cells" :key="cell">
5
- <a v-if="cell!==0" @click="changePage(cell)" :class="{ active: cell === currentPage }">{{ cell }}</a>
6
- <span v-if="cell===0" style="cursor: not-allowed;">...</span>
7
- </li>
8
- </ul>
9
- </div>
10
- </template>
11
-
12
- <script lang="ts" setup>
13
- import { ref, watch } from "vue";
14
-
15
- const props = defineProps({
16
- totalItems: {
17
- type: Number,
18
- required: true,
19
- default: 10
20
- },
21
- itemsPerPage: {
22
- type: Number,
23
- default: 20
24
- }
25
- })
26
-
27
- const currentPage = ref(1);
28
- const totalPages = ref(0);
29
- const cells = ref([] as number[]);
30
- const emit = defineEmits(['page-changed']);
31
-
32
- function changePage(pageNumber: number) {
33
- if (pageNumber !== currentPage.value && pageNumber >= 1 && pageNumber <= totalPages.value) {
34
- currentPage.value = pageNumber;
35
- }
36
-
37
- if (totalPages.value > 5) {
38
- cells.value = [];
39
-
40
- let isAwayFromStartPoint = (pageNumber - 3) > 0;
41
- if (isAwayFromStartPoint) {
42
- cells.value.push(1);
43
- cells.value.push(0);
44
- }
45
-
46
- for (let i = 2; i >= 1; i--) {
47
- let previousCell = pageNumber - i;
48
- if (previousCell > 0) {
49
- cells.value.push(previousCell);
50
- }
51
- }
52
-
53
- cells.value.push(currentPage.value);
54
-
55
- let nextPages = totalPages.value - pageNumber;
56
- let hasNextPages = nextPages > 2;
57
- let nextPagesSize = 2;
58
-
59
- if (hasNextPages) {
60
- for (let i = 1; i <= nextPagesSize; i++) {
61
- cells.value.push(pageNumber + i);
62
- }
63
- cells.value.push(0);
64
- cells.value.push(totalPages.value);
65
- } else {
66
- for (let i = 1; i <= nextPages; i++) {
67
- cells.value.push(pageNumber + i);
68
- }
69
- }
70
- }
71
-
72
- emit('page-changed', pageNumber);
73
- }
74
-
75
- watch(() => props.totalItems, () => {
76
- totalPages.value = Math.ceil(props.totalItems / props.itemsPerPage);
77
-
78
- cells.value = [];
79
- if (totalPages.value > 5) {
80
- for (let i = 1; i < 5; i++) {
81
- cells.value.push(i);
82
- }
83
- cells.value.push(0);
84
- cells.value.push(totalPages.value);
85
- } else {
86
- for (let i = 1; i <= totalPages.value; i++) {
87
- cells.value.push(i);
88
- }
89
- }
90
- })
91
- </script>
92
-
93
- <style lang="scss" scoped>
94
- span {
95
- border: 1px solid #d1d5db;
96
- border-radius: 10px;
97
- padding: 0.75rem;
98
- text-decoration: none;
99
- color: black;
100
- }
101
-
102
- ul {
103
- display: flex;
104
- list-style: none;
105
- padding: 0;
106
- }
107
-
108
- li {
109
- margin-right: 3px;
110
- }
111
-
112
- a {
113
- border: 1px solid #d1d5db;
114
- border-radius: 10px;
115
- padding: 0.75rem;
116
- text-decoration: none;
117
- color: black;
118
- }
119
-
120
- a:hover {
121
- background-color: rgb(195 197 201);
122
- color: #000000;
123
- cursor: pointer;
124
- }
125
-
126
- a.active:hover {
127
- background-color: rgb(195 197 201);
128
- }
129
-
130
- a.active {
131
- font-weight: 700;
132
- border-radius: 10px;
133
- background-color: #e5e7eb;
134
- color: #000000;
135
- }
136
- </style>
1
+ <template>
2
+ <div>
3
+ <ul>
4
+ <li v-for="(cell, index) in cells" :key="index">
5
+ <a v-if="cell!==0" role="button" tabindex="0" @click="changePage(cell)" @keydown.enter="changePage(cell)" :class="{ active: cell === currentPage }">{{ cell }}</a>
6
+ <span v-if="cell===0" style="cursor: not-allowed;">...</span>
7
+ </li>
8
+ </ul>
9
+ </div>
10
+ </template>
11
+
12
+ <script lang="ts" setup>
13
+ import { ref, watch } from "vue";
14
+
15
+ const props = defineProps({
16
+ totalItems: {
17
+ type: Number,
18
+ required: true
19
+ },
20
+ itemsPerPage: {
21
+ type: Number,
22
+ default: 20
23
+ }
24
+ })
25
+
26
+ const currentPage = ref(1);
27
+ const totalPages = ref(0);
28
+ const cells = ref([] as number[]);
29
+ const emit = defineEmits(['page-changed']);
30
+
31
+ function changePage(pageNumber: number) {
32
+ if (pageNumber !== currentPage.value && pageNumber >= 1 && pageNumber <= totalPages.value) {
33
+ currentPage.value = pageNumber;
34
+ }
35
+
36
+ if (totalPages.value > 5) {
37
+ cells.value = [];
38
+
39
+ let isAwayFromStartPoint = (pageNumber - 3) > 0;
40
+ if (isAwayFromStartPoint) {
41
+ cells.value.push(1);
42
+ cells.value.push(0);
43
+ }
44
+
45
+ for (let i = 1; i <= 2; i++) {
46
+ let previousCell = pageNumber - i;
47
+ if (previousCell > 0) {
48
+ cells.value.push(previousCell);
49
+ }
50
+ }
51
+
52
+ // Sort previous pages in ascending order
53
+ const currentIdx = cells.value.length;
54
+ cells.value.push(currentPage.value);
55
+
56
+ let nextPages = totalPages.value - pageNumber;
57
+ let hasNextPages = nextPages > 2;
58
+ let nextPagesSize = 2;
59
+
60
+ if (hasNextPages) {
61
+ for (let i = 1; i <= nextPagesSize; i++) {
62
+ cells.value.push(pageNumber + i);
63
+ }
64
+ cells.value.push(0);
65
+ cells.value.push(totalPages.value);
66
+ } else {
67
+ for (let i = 1; i <= nextPages; i++) {
68
+ cells.value.push(pageNumber + i);
69
+ }
70
+ }
71
+ }
72
+
73
+ emit('page-changed', pageNumber);
74
+ }
75
+
76
+ watch(() => props.totalItems, () => {
77
+ totalPages.value = Math.ceil(props.totalItems / props.itemsPerPage);
78
+
79
+ cells.value = [];
80
+ if (totalPages.value > 5) {
81
+ for (let i = 1; i < 5; i++) {
82
+ cells.value.push(i);
83
+ }
84
+ cells.value.push(0);
85
+ cells.value.push(totalPages.value);
86
+ } else {
87
+ for (let i = 1; i <= totalPages.value; i++) {
88
+ cells.value.push(i);
89
+ }
90
+ }
91
+ }, { immediate: true })
92
+ </script>
93
+
94
+ <style lang="scss" scoped>
95
+ span {
96
+ border: 1px solid #d1d5db;
97
+ border-radius: 10px;
98
+ padding: 0.75rem;
99
+ text-decoration: none;
100
+ color: black;
101
+ }
102
+
103
+ ul {
104
+ display: flex;
105
+ list-style: none;
106
+ padding: 0;
107
+ }
108
+
109
+ li {
110
+ margin-right: 3px;
111
+ }
112
+
113
+ a {
114
+ border: 1px solid #d1d5db;
115
+ border-radius: 10px;
116
+ padding: 0.75rem;
117
+ text-decoration: none;
118
+ color: black;
119
+ }
120
+
121
+ a:hover {
122
+ background-color: rgb(195 197 201);
123
+ color: #000000;
124
+ cursor: pointer;
125
+ }
126
+
127
+ a.active:hover {
128
+ background-color: rgb(195 197 201);
129
+ }
130
+
131
+ a.active {
132
+ font-weight: 700;
133
+ border-radius: 10px;
134
+ background-color: #e5e7eb;
135
+ color: #000000;
136
+ }
137
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div v-if="number">
2
+ <div v-if="number !== null && number !== undefined">
3
3
  <span v-if="isNegative" class="flex items-center space-x-2">
4
4
  <BaseBadge :color="ColorsEnums.RED">{{ number }}{{ props.icon }}</BaseBadge>
5
5
  <ArrowDownIcon class="h-5 w-5 text-red-500" />
@@ -0,0 +1,32 @@
1
+ import { ref, watch, onScopeDispose, type Ref } from 'vue'
2
+
3
+ /**
4
+ * Creates a debounced ref that updates after a delay.
5
+ * Useful for search inputs where you want instant UI feedback
6
+ * but debounced side-effects (URL sync, API calls, etc.).
7
+ *
8
+ * Must be called within a component setup or active effect scope — the
9
+ * pending timer is cleared automatically when the scope is disposed.
10
+ */
11
+ export function useDebouncedRef<T>(source: Ref<T>, delay = 300): Ref<T> {
12
+ const debounced = ref(source.value) as Ref<T>
13
+ let timeout: ReturnType<typeof setTimeout> | null = null
14
+
15
+ watch(source, (val) => {
16
+ if (timeout) clearTimeout(timeout)
17
+ timeout = setTimeout(() => {
18
+ debounced.value = val
19
+ timeout = null
20
+ }, delay)
21
+ })
22
+
23
+ // Clear any pending timer if the consuming scope is torn down before it fires.
24
+ onScopeDispose(() => {
25
+ if (timeout) {
26
+ clearTimeout(timeout)
27
+ timeout = null
28
+ }
29
+ })
30
+
31
+ return debounced
32
+ }
@@ -0,0 +1,15 @@
1
+ import { onBeforeUnmount, onMounted } from 'vue'
2
+
3
+ /**
4
+ * Wire up a callback to fire on Escape key press for the lifetime of the
5
+ * calling component. Listener is attached on mount and detached on unmount,
6
+ * so it's safe to use in modals that may mount/unmount many times.
7
+ */
8
+ export function useEscapeKey(handler: () => void): void {
9
+ function onKeydown(e: KeyboardEvent) {
10
+ if (e.key === 'Escape') handler()
11
+ }
12
+
13
+ onMounted(() => document.addEventListener('keydown', onKeydown))
14
+ onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown))
15
+ }
@@ -0,0 +1,23 @@
1
+ import { ref } from 'vue'
2
+
3
+ /**
4
+ * Shared state for the responsive sidebar's open/closed status.
5
+ *
6
+ * Module-scoped singleton so the sidebar component and the page layout
7
+ * (which typically marks `<main>` as `inert` while the menu is open)
8
+ * stay in sync. There's only ever one sidebar at a time.
9
+ */
10
+ const mobileOpen = ref(false)
11
+
12
+ export function useMobileSidebar() {
13
+ function open() {
14
+ mobileOpen.value = true
15
+ }
16
+ function close() {
17
+ mobileOpen.value = false
18
+ }
19
+ function toggle() {
20
+ mobileOpen.value = !mobileOpen.value
21
+ }
22
+ return { mobileOpen, open, close, toggle }
23
+ }
@@ -0,0 +1,69 @@
1
+ import { ref, watch, effectScope } from 'vue'
2
+
3
+ /**
4
+ * Shared dark/light theme controller.
5
+ *
6
+ * Singleton ref + `<html class="dark">` sync + localStorage persistence.
7
+ * The first consumer can pass an options object to override the
8
+ * localStorage key (each consuming app should pick a unique key so two
9
+ * backoffices on the same origin don't fight). Defaults to `'mgv-theme'`.
10
+ *
11
+ * Subsequent callers get the same singleton; the options are only
12
+ * consulted on the first call.
13
+ */
14
+ export interface UseThemeOptions {
15
+ /** localStorage key used to persist the preference. */
16
+ storageKey?: string
17
+ }
18
+
19
+ const isDark = ref(false)
20
+
21
+ // Idempotent flag: initialization and the DOM-sync watcher should run once,
22
+ // and only when the composable is first invoked in a browser environment.
23
+ let initialized = false
24
+ let configuredStorageKey = 'mgv-theme'
25
+
26
+ function initialize(storageKey: string) {
27
+ if (initialized) return
28
+ initialized = true
29
+ configuredStorageKey = storageKey
30
+
31
+ // Read saved preference / system preference.
32
+ if (typeof window !== 'undefined') {
33
+ const saved = window.localStorage?.getItem(configuredStorageKey)
34
+ if (saved) {
35
+ isDark.value = saved === 'dark'
36
+ } else if (typeof window.matchMedia === 'function') {
37
+ isDark.value = window.matchMedia('(prefers-color-scheme: dark)').matches
38
+ }
39
+ }
40
+
41
+ // Use a detached effect scope so the watcher lives for the app's lifetime
42
+ // and is NOT torn down when the first component that called useTheme() unmounts.
43
+ if (typeof document !== 'undefined') {
44
+ const scope = effectScope(true)
45
+ scope.run(() => {
46
+ watch(
47
+ isDark,
48
+ (value) => {
49
+ document.documentElement.classList.toggle('dark', value)
50
+ window.localStorage?.setItem(configuredStorageKey, value ? 'dark' : 'light')
51
+ },
52
+ { immediate: true },
53
+ )
54
+ })
55
+ }
56
+ }
57
+
58
+ export function useTheme(options?: UseThemeOptions) {
59
+ initialize(options?.storageKey ?? configuredStorageKey)
60
+
61
+ function toggleTheme() {
62
+ isDark.value = !isDark.value
63
+ }
64
+
65
+ return {
66
+ isDark,
67
+ toggleTheme,
68
+ }
69
+ }
@@ -0,0 +1,133 @@
1
+ import { computed, type ComputedRef } from 'vue'
2
+ import { useTheme } from './useTheme'
3
+
4
+ /**
5
+ * Centralized dark/light Tailwind class strings.
6
+ *
7
+ * Named "roles" that map to dark/light class strings, so consumers don't
8
+ * spell out `isDark ? '...' : '...'` ternaries across every view.
9
+ *
10
+ * Usage:
11
+ *
12
+ * const t = useThemeClasses()
13
+ * <div :class="t.card">
14
+ * <p :class="t.mutedText">...</p>
15
+ * </div>
16
+ *
17
+ * Vue auto-unwraps the computed refs inside template `:class` bindings,
18
+ * so callers do not write `.value`.
19
+ */
20
+ export interface ThemeClasses {
21
+ // -- Surfaces --
22
+ /** Card surface: bg-gray-800 / bg-white, with border. */
23
+ card: ComputedRef<string>
24
+ /** Slightly darker card surface: bg-gray-900 / bg-white, with border. */
25
+ cardAlt: ComputedRef<string>
26
+ /** Page background. */
27
+ pageBg: ComputedRef<string>
28
+
29
+ // -- Borders --
30
+ /** Standard border between cards/sections. */
31
+ border: ComputedRef<string>
32
+ /** Subtle divider (lighter than border). */
33
+ divider: ComputedRef<string>
34
+
35
+ // -- Text hierarchy --
36
+ /** Page headings: white / gray-900. */
37
+ primaryText: ComputedRef<string>
38
+ /** Softer headings: gray-100 / gray-800. */
39
+ primaryTextSoft: ComputedRef<string>
40
+ /** Body copy on cards: gray-200 / gray-700. */
41
+ bodyText: ComputedRef<string>
42
+ /** Form labels: gray-300 / gray-700. */
43
+ label: ComputedRef<string>
44
+ /** Secondary text: gray-400 / gray-600. */
45
+ mutedText: ComputedRef<string>
46
+ /** Tertiary text: gray-500 / gray-600. */
47
+ subtleText: ComputedRef<string>
48
+ /** Dimmest text: gray-500 in dark / gray-400 in light. */
49
+ dimText: ComputedRef<string>
50
+ /** Slightly more contrast than dimText: gray-400 / gray-500. */
51
+ dimTextAlt: ComputedRef<string>
52
+ /** Big illustration / empty-state icon: gray-600 / gray-300. */
53
+ illustration: ComputedRef<string>
54
+
55
+ // -- Inputs --
56
+ /** Standard text input. */
57
+ input: ComputedRef<string>
58
+ /** Input + placeholder colour. */
59
+ inputWithPlaceholder: ComputedRef<string>
60
+
61
+ // -- Buttons --
62
+ /** Ghost / Cancel button. */
63
+ ghostButton: ComputedRef<string>
64
+
65
+ // -- Accent text colours --
66
+ /** Emerald accent text: emerald-400 / emerald-600. */
67
+ emeraldText: ComputedRef<string>
68
+ /** Amber accent text: amber-400 / amber-600. */
69
+ amberText: ComputedRef<string>
70
+ /** Amber heading-weight: amber-300 / amber-700. */
71
+ amberTextStrong: ComputedRef<string>
72
+ /** Red accent text: red-400 / red-500. */
73
+ redText: ComputedRef<string>
74
+ }
75
+
76
+ export function useThemeClasses(): ThemeClasses {
77
+ const { isDark } = useTheme()
78
+
79
+ return {
80
+ card: computed(() =>
81
+ isDark.value
82
+ ? 'bg-gray-800 border-gray-700'
83
+ : 'bg-white border-gray-200',
84
+ ),
85
+ cardAlt: computed(() =>
86
+ isDark.value
87
+ ? 'bg-gray-900 border-gray-700'
88
+ : 'bg-white border-gray-200',
89
+ ),
90
+ pageBg: computed(() => (isDark.value ? 'bg-gray-900' : 'bg-gray-50')),
91
+
92
+ border: computed(() => (isDark.value ? 'border-gray-700' : 'border-gray-200')),
93
+ divider: computed(() => (isDark.value ? 'border-gray-800' : 'border-gray-100')),
94
+
95
+ primaryText: computed(() => (isDark.value ? 'text-white' : 'text-gray-900')),
96
+ primaryTextSoft: computed(() => (isDark.value ? 'text-gray-100' : 'text-gray-800')),
97
+ bodyText: computed(() => (isDark.value ? 'text-gray-200' : 'text-gray-700')),
98
+ label: computed(() => (isDark.value ? 'text-gray-300' : 'text-gray-700')),
99
+ mutedText: computed(() => (isDark.value ? 'text-gray-400' : 'text-gray-600')),
100
+ subtleText: computed(() => (isDark.value ? 'text-gray-500' : 'text-gray-600')),
101
+ dimText: computed(() => (isDark.value ? 'text-gray-500' : 'text-gray-400')),
102
+ dimTextAlt: computed(() => (isDark.value ? 'text-gray-400' : 'text-gray-500')),
103
+ illustration: computed(() => (isDark.value ? 'text-gray-600' : 'text-gray-300')),
104
+
105
+ input: computed(() =>
106
+ isDark.value
107
+ ? 'bg-gray-700 border-gray-600 text-white'
108
+ : 'bg-gray-50 border-gray-300 text-gray-900',
109
+ ),
110
+ inputWithPlaceholder: computed(() =>
111
+ isDark.value
112
+ ? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400'
113
+ : 'bg-gray-50 border-gray-300 text-gray-900 placeholder-gray-500',
114
+ ),
115
+
116
+ ghostButton: computed(() =>
117
+ isDark.value
118
+ ? 'bg-gray-800 text-gray-200 border-gray-600 hover:bg-gray-700'
119
+ : 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50',
120
+ ),
121
+
122
+ emeraldText: computed(() =>
123
+ isDark.value ? 'text-emerald-400' : 'text-emerald-600',
124
+ ),
125
+ amberText: computed(() =>
126
+ isDark.value ? 'text-amber-400' : 'text-amber-600',
127
+ ),
128
+ amberTextStrong: computed(() =>
129
+ isDark.value ? 'text-amber-300' : 'text-amber-700',
130
+ ),
131
+ redText: computed(() => (isDark.value ? 'text-red-400' : 'text-red-500')),
132
+ }
133
+ }
@@ -0,0 +1,56 @@
1
+ import { ref, onScopeDispose } from 'vue'
2
+ import { BaseToastEnum } from '../enums/BaseToastEnum'
3
+
4
+ /**
5
+ * Per-component toast controller.
6
+ *
7
+ * Each component gets its own state refs (not a global singleton) so
8
+ * multiple mounted views don't fight over the same toast slot. The pending
9
+ * auto-hide timer is cleared on unmount so it cannot fire after the
10
+ * component is gone.
11
+ *
12
+ * Usage:
13
+ * const { showToast, toastMessage, toastType, showToastMessage } = useToast()
14
+ * showToastMessage('Saved', BaseToastEnum.SUCCESS)
15
+ */
16
+ export function useToast(defaultDurationMs = 3000) {
17
+ const showToast = ref(false)
18
+ const toastMessage = ref('')
19
+ const toastType = ref<BaseToastEnum>(BaseToastEnum.SUCCESS)
20
+
21
+ let timer: ReturnType<typeof setTimeout> | null = null
22
+
23
+ function clearTimer() {
24
+ if (timer) {
25
+ clearTimeout(timer)
26
+ timer = null
27
+ }
28
+ }
29
+
30
+ function showToastMessage(
31
+ message: string,
32
+ type: BaseToastEnum = BaseToastEnum.SUCCESS,
33
+ durationMs: number = defaultDurationMs,
34
+ ) {
35
+ toastMessage.value = message
36
+ toastType.value = type
37
+ showToast.value = true
38
+
39
+ clearTimer()
40
+ timer = setTimeout(() => {
41
+ showToast.value = false
42
+ timer = null
43
+ }, durationMs)
44
+ }
45
+
46
+ onScopeDispose(() => {
47
+ clearTimer()
48
+ })
49
+
50
+ return {
51
+ showToast,
52
+ toastMessage,
53
+ toastType,
54
+ showToastMessage,
55
+ }
56
+ }
@@ -2,5 +2,5 @@ export enum AlertEnum {
2
2
  WARNING = "WARNING",
3
3
  ERROR = "ERROR",
4
4
  SUCCESS = "SUCCESS",
5
- INFROM = "INFROM"
5
+ INFO = "INFO"
6
6
  }
@@ -1,5 +1,5 @@
1
1
  export enum BaseButtonSizeEnum {
2
- EXTRA_SMALL = "EXTRA SMALL",
2
+ EXTRA_SMALL = "EXTRA_SMALL",
3
3
  SMALL = "SMALL",
4
4
  BASE = "BASE",
5
5
  LARGE = "LARGE",
@@ -1,5 +1,8 @@
1
- export enum BaseLoginEnum {
1
+ export enum BaseLogoEnum {
2
2
  SMALL = "SMALL",
3
3
  MEDIUM = "MEDIUM",
4
4
  LARGE = "LARGE"
5
5
  }
6
+
7
+ /** @deprecated Use BaseLogoEnum instead */
8
+ export const BaseLoginEnum = BaseLogoEnum;