pgo-uiux2 1.0.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 (180) hide show
  1. package/.env +1 -0
  2. package/.env.production +1 -0
  3. package/.prettierrc +13 -0
  4. package/.vscode/extensions.json +3 -0
  5. package/BUTTON_GUIDE.md +257 -0
  6. package/README.md +49 -0
  7. package/THEME_REFERENCE.md +310 -0
  8. package/eslint.config.ts +27 -0
  9. package/index.html +13 -0
  10. package/package.json +85 -0
  11. package/public/favicon.ico +0 -0
  12. package/src/App.vue +368 -0
  13. package/src/assets/fonts/Faruma.ttf +0 -0
  14. package/src/components/examples/AppBarExample.vue +101 -0
  15. package/src/components/examples/AvatarExample.vue +47 -0
  16. package/src/components/examples/BannerExample.vue +287 -0
  17. package/src/components/examples/BaseInputExample.vue +25 -0
  18. package/src/components/examples/BreadcrumbExample.vue +53 -0
  19. package/src/components/examples/CardExample.vue +77 -0
  20. package/src/components/examples/ChipExample.vue +225 -0
  21. package/src/components/examples/DatePickerExample.vue +31 -0
  22. package/src/components/examples/DropdownExample.vue +84 -0
  23. package/src/components/examples/EditorExample.vue +200 -0
  24. package/src/components/examples/ExpansionPanelExample.vue +42 -0
  25. package/src/components/examples/FileUploadExample.vue +40 -0
  26. package/src/components/examples/FormExample.vue +121 -0
  27. package/src/components/examples/HugeTest.vue +8 -0
  28. package/src/components/examples/LayoutContainerExample.vue +80 -0
  29. package/src/components/examples/ModalExample.vue +82 -0
  30. package/src/components/examples/NavDrawerExample.vue +170 -0
  31. package/src/components/examples/NumberFieldExample.vue +145 -0
  32. package/src/components/examples/RadioButtonExample.vue +161 -0
  33. package/src/components/examples/SearchExample.vue +322 -0
  34. package/src/components/examples/SelectExample.vue +121 -0
  35. package/src/components/examples/StackedTableViewExample.vue +53 -0
  36. package/src/components/examples/TabExample.vue +336 -0
  37. package/src/components/examples/TableExample.vue +228 -0
  38. package/src/components/examples/TextFieldExample.vue +181 -0
  39. package/src/components/examples/TextareaExample.vue +173 -0
  40. package/src/components/examples/ThemeToggle.vue +50 -0
  41. package/src/components/examples/TimelineExample.vue +66 -0
  42. package/src/components/examples/TipTapEditorExample.vue +20 -0
  43. package/src/components/examples/TooltipExample.vue +53 -0
  44. package/src/components/examples/VueDatePickerShowcase.vue +214 -0
  45. package/src/components/examples/_DatePickerExample.vue +33 -0
  46. package/src/components/examples/__FormExample.vue +77 -0
  47. package/src/components/index.ts +25 -0
  48. package/src/components/pgo/AppBar.vue +347 -0
  49. package/src/components/pgo/Avatar.vue +139 -0
  50. package/src/components/pgo/Banner.vue +300 -0
  51. package/src/components/pgo/Breadcrumb.vue +101 -0
  52. package/src/components/pgo/Button.vue +171 -0
  53. package/src/components/pgo/Card.vue +178 -0
  54. package/src/components/pgo/ConfirmationModel.vue +32 -0
  55. package/src/components/pgo/DataTable.vue +845 -0
  56. package/src/components/pgo/DatePicker/CalendarPanel.vue +43 -0
  57. package/src/components/pgo/DatePicker/__DatePicker.vue +122 -0
  58. package/src/components/pgo/DatePicker/types.ts +11 -0
  59. package/src/components/pgo/DatePicker/useCalendar.ts +39 -0
  60. package/src/components/pgo/DatePicker/useDatePicker.ts +31 -0
  61. package/src/components/pgo/Deprecated/ToastContainer.vue +51 -0
  62. package/src/components/pgo/Deprecated/ToastItem.vue +55 -0
  63. package/src/components/pgo/Dropdown.vue +296 -0
  64. package/src/components/pgo/DropdownItem.vue +40 -0
  65. package/src/components/pgo/Editor.vue +511 -0
  66. package/src/components/pgo/ExpansionPanel.vue +185 -0
  67. package/src/components/pgo/Footer.vue +39 -0
  68. package/src/components/pgo/HeroIcon.vue +124 -0
  69. package/src/components/pgo/InputSearch.vue +194 -0
  70. package/src/components/pgo/LayoutContainer.vue +104 -0
  71. package/src/components/pgo/Main.vue +37 -0
  72. package/src/components/pgo/Modal.vue +273 -0
  73. package/src/components/pgo/NavDrawer.vue +127 -0
  74. package/src/components/pgo/NavDrawerItem.vue +161 -0
  75. package/src/components/pgo/NavigationDrawer.vue +849 -0
  76. package/src/components/pgo/OLDNavDrawer.vue +661 -0
  77. package/src/components/pgo/OldAppBar.vue +223 -0
  78. package/src/components/pgo/PApp.vue +102 -0
  79. package/src/components/pgo/Pagination.vue +242 -0
  80. package/src/components/pgo/Search copy.vue +310 -0
  81. package/src/components/pgo/Search.vue +411 -0
  82. package/src/components/pgo/StackedTableView.vue +167 -0
  83. package/src/components/pgo/Tab.vue +617 -0
  84. package/src/components/pgo/TestInput.vue +395 -0
  85. package/src/components/pgo/Timeline.vue +367 -0
  86. package/src/components/pgo/TimelineItem.vue +80 -0
  87. package/src/components/pgo/TipTapEditor.vue +315 -0
  88. package/src/components/pgo/Tooltip.NOTES.md +12 -0
  89. package/src/components/pgo/Tooltip.PROPS.md +21 -0
  90. package/src/components/pgo/Tooltip.vue +281 -0
  91. package/src/components/pgo/base/Base.vue +444 -0
  92. package/src/components/pgo/buttons/Chip.vue +324 -0
  93. package/src/components/pgo/buttons/ChipGroup.vue +224 -0
  94. package/src/components/pgo/buttons/Radio.vue +424 -0
  95. package/src/components/pgo/filters/FilterSection.vue +188 -0
  96. package/src/components/pgo/filters/Searchbar.vue +216 -0
  97. package/src/components/pgo/forms/DynamicForm.vue +45 -0
  98. package/src/components/pgo/forms/Form.vue +132 -0
  99. package/src/components/pgo/index.ts +15 -0
  100. package/src/components/pgo/inputs/Checkbox.vue +320 -0
  101. package/src/components/pgo/inputs/DatePicker.vue +395 -0
  102. package/src/components/pgo/inputs/FileUpload.vue +326 -0
  103. package/src/components/pgo/inputs/NumberField.vue +243 -0
  104. package/src/components/pgo/inputs/Radio.vue +162 -0
  105. package/src/components/pgo/inputs/RadioGroup.vue +188 -0
  106. package/src/components/pgo/inputs/Select.vue +535 -0
  107. package/src/components/pgo/inputs/TextField.vue +194 -0
  108. package/src/components/pgo/inputs/Textarea.vue +181 -0
  109. package/src/main.js +12 -0
  110. package/src/pgo-components/_index.js +31 -0
  111. package/src/pgo-components/assets/fonts/Faruma.ttf +0 -0
  112. package/src/pgo-components/assets/fonts/logo.png +0 -0
  113. package/src/pgo-components/composables/useTheme.js +10 -0
  114. package/src/pgo-components/directives/tooltip-directive.ts +393 -0
  115. package/src/pgo-components/index.js +96 -0
  116. package/src/pgo-components/lib/componentConfig.js +147 -0
  117. package/src/pgo-components/lib/core/composables/_useCalendar.ts +127 -0
  118. package/src/pgo-components/lib/core/composables/useDefaults.ts +15 -0
  119. package/src/pgo-components/lib/core/composables/useLanguageSelect.js +0 -0
  120. package/src/pgo-components/lib/core/composables/useRtl.ts +12 -0
  121. package/src/pgo-components/lib/core/defaults/createDefaults.ts +5 -0
  122. package/src/pgo-components/lib/core/defaults/defaults.ts +7 -0
  123. package/src/pgo-components/lib/core/rtl/rtl.ts +3 -0
  124. package/src/pgo-components/lib/core/rtl/setRtl.ts +19 -0
  125. package/src/pgo-components/lib/drawerState.ts +3 -0
  126. package/src/pgo-components/lib/i18n/defaultLables.js +71 -0
  127. package/src/pgo-components/lib/i18n/i18nPlugin.js +52 -0
  128. package/src/pgo-components/lib/i18n/useI18n.js +35 -0
  129. package/src/pgo-components/lib/index.ts +38 -0
  130. package/src/pgo-components/pages/Component.vue +7 -0
  131. package/src/pgo-components/pages/ComponentRenderer.vue +85 -0
  132. package/src/pgo-components/pages/Home.vue +130 -0
  133. package/src/pgo-components/pages/ListView.vue +370 -0
  134. package/src/pgo-components/pages/Page1.vue +296 -0
  135. package/src/pgo-components/pages/_Page1.vue +180 -0
  136. package/src/pgo-components/plugins/SnackBar.vue +251 -0
  137. package/src/pgo-components/plugins/SnackBarContainer.vue +53 -0
  138. package/src/pgo-components/plugins/SnackBarPlugin.ts +136 -0
  139. package/src/pgo-components/plugins/theme-plugin.js +114 -0
  140. package/src/pgo-components/plugins/types.ts +46 -0
  141. package/src/pgo-components/plugins/useSnackBar.js +11 -0
  142. package/src/pgo-components/plugins/useSnackBar.ts +21 -0
  143. package/src/pgo-components/plugins/validation-plugin.js +11 -0
  144. package/src/pgo-components/services/Entry.json +813 -0
  145. package/src/pgo-components/services/axios.js +54 -0
  146. package/src/pgo-components/services/data.json +90 -0
  147. package/src/pgo-components/services/person.json +260 -0
  148. package/src/pgo-components/services/toast.ts +44 -0
  149. package/src/pgo-components/styles/global.css +234 -0
  150. package/src/pgo-components/styles/reset.css +96 -0
  151. package/src/pgo-components/styles/tokens.css +18 -0
  152. package/src/pgo-components/styles/utilities/border-radius.css +57 -0
  153. package/src/pgo-components/styles/utilities/borders.css +85 -0
  154. package/src/pgo-components/styles/utilities/colors.css +38 -0
  155. package/src/pgo-components/styles/utilities/cursor.css +19 -0
  156. package/src/pgo-components/styles/utilities/display.css +78 -0
  157. package/src/pgo-components/styles/utilities/elevation.css +33 -0
  158. package/src/pgo-components/styles/utilities/flex.css +403 -0
  159. package/src/pgo-components/styles/utilities/float.css +41 -0
  160. package/src/pgo-components/styles/utilities/hover.css +9 -0
  161. package/src/pgo-components/styles/utilities/index.css +18 -0
  162. package/src/pgo-components/styles/utilities/opacity.css +27 -0
  163. package/src/pgo-components/styles/utilities/overflow.css +26 -0
  164. package/src/pgo-components/styles/utilities/palette.css +515 -0
  165. package/src/pgo-components/styles/utilities/position.css +14 -0
  166. package/src/pgo-components/styles/utilities/sizing.css +70 -0
  167. package/src/pgo-components/styles/utilities/spacing.css +578 -0
  168. package/src/pgo-components/styles/utilities/transitions.css +58 -0
  169. package/src/pgo-components/styles/utilities/typography.css +91 -0
  170. package/src/pgo-components/styles/utilities/z-index.css +11 -0
  171. package/src/pgo-components/tokens/index.js +337 -0
  172. package/src/router/index.js +88 -0
  173. package/src/shims-vue.d.ts +14 -0
  174. package/src/validations/validationRules.js +50 -0
  175. package/tailwind.config.js +73 -0
  176. package/test.php +5 -0
  177. package/tsconfig.json +25 -0
  178. package/ui +31 -0
  179. package/ui.pgo.mv.conf +18 -0
  180. package/vite.config.js +42 -0
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <footer ref="el" :class="['w-full', densityClass]" :style="style">
3
+ <slot />
4
+ </footer>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { ref, onMounted, onBeforeUnmount, computed, inject } from "vue";
9
+
10
+ const props = defineProps({
11
+ id: {
12
+ type: String,
13
+ default: () => `footer-${Math.random().toString(36).slice(2, 9)}`,
14
+ },
15
+ dense: { type: Boolean, default: false },
16
+ });
17
+ const layout = inject<any>("layout", null);
18
+ const el = ref<HTMLElement | null>(null);
19
+ const height = ref(0);
20
+
21
+ function update() {
22
+ if (!el.value) return;
23
+ height.value = el.value.getBoundingClientRect().height;
24
+ if (layout) layout.register("footer", props.id, { height: height.value });
25
+ }
26
+
27
+ onMounted(() => {
28
+ update();
29
+ const ro = new ResizeObserver(update);
30
+ if (el.value) ro.observe(el.value);
31
+ onBeforeUnmount(() => {
32
+ ro.disconnect();
33
+ if (layout) layout.unregister("footer", props.id);
34
+ });
35
+ });
36
+
37
+ const densityClass = computed(() => (props.dense ? "h-10" : "h-14"));
38
+ const style = computed(() => ({}));
39
+ </script>
@@ -0,0 +1,124 @@
1
+ <template>
2
+ <component
3
+ :is="IconAsyncComponent"
4
+ v-bind="attrs"
5
+ :class="computedClass"
6
+ :style="computedStyle"
7
+ />
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import type { Component } from "vue";
12
+ import { defineProps, withDefaults, computed, useAttrs } from "vue";
13
+ import { defineAsyncComponent } from "vue";
14
+
15
+ // Props
16
+ interface Props {
17
+ name: string;
18
+ type?: "solid" | "outline" | "mini" | "micro";
19
+ size?: number | string;
20
+ color?: string;
21
+ }
22
+
23
+ const props = withDefaults(defineProps<Props>(), {
24
+ type: "outline",
25
+ size: 24,
26
+ color: "currentColor",
27
+ });
28
+
29
+ const attrs = useAttrs();
30
+
31
+ // Convert icon names like "academic-cap" to "AcademicCapIcon"
32
+ const normalizeName = (str: string) => {
33
+ return (
34
+ str
35
+ .replace(/-([a-z])/g, (_, c) => c.toUpperCase()) // kebab → camel
36
+ .replace(/(?:^|_|\s|-)(\w)/g, (_, c) => c.toUpperCase()) // capitalize
37
+ .replace(/Icon$/, "") + "Icon"
38
+ );
39
+ };
40
+
41
+ const iconName = computed(() => normalizeName(props.name));
42
+
43
+ // Async loader
44
+ const IconAsyncComponent = computed(() =>
45
+ defineAsyncComponent({
46
+ loader: async () => {
47
+ let pack;
48
+
49
+ if (props.type === "solid") {
50
+ pack = await import("@heroicons/vue/24/solid");
51
+ } else if (props.type === "outline") {
52
+ pack = await import("@heroicons/vue/24/outline");
53
+ } else if (props.type === "mini") {
54
+ pack = await import("@heroicons/vue/20/solid");
55
+ } else if (props.type === "micro") {
56
+ pack = await import("@heroicons/vue/16/solid");
57
+ } else {
58
+ pack = await import("@heroicons/vue/24/outline");
59
+ }
60
+
61
+ const icon = (pack as any)[iconName.value];
62
+
63
+ if (!icon) {
64
+ throw new Error(
65
+ `Icon "${iconName.value}" not found in ${props.type} icons`
66
+ );
67
+ }
68
+
69
+ return icon as Component;
70
+ },
71
+
72
+ // Loading icon
73
+ loadingComponent: defineAsyncComponent(
74
+ async () => (await import("@heroicons/vue/24/outline")).ArrowPathIcon
75
+ ),
76
+
77
+ // Error fallback icon
78
+ errorComponent: defineAsyncComponent(
79
+ async () =>
80
+ (await import("@heroicons/vue/24/outline")).ExclamationTriangleIcon
81
+ ),
82
+
83
+ delay: 200,
84
+ timeout: 5000,
85
+ })
86
+ );
87
+
88
+ // Tailwind color detection
89
+ const isTailwindColorClass = (value?: string) =>
90
+ value
91
+ ? /(?:^|\s)(?:text-|fill-|stroke-|bg-|ring-|border-)/.test(value)
92
+ : false;
93
+
94
+ const tailwindColorClass = computed(() =>
95
+ isTailwindColorClass(props.color) ? String(props.color) : ""
96
+ );
97
+
98
+ const computedClass = computed(() => {
99
+ const classes = ["hero-icon"];
100
+
101
+ if (attrs.class) classes.push(String(attrs.class));
102
+ if (tailwindColorClass.value) classes.push(tailwindColorClass.value);
103
+
104
+ return classes.filter(Boolean).join(" ");
105
+ });
106
+
107
+ const computedStyle = computed(() => {
108
+ const styleObj: Record<string, any> =
109
+ typeof attrs.style === "object" ? { ...attrs.style } : {};
110
+
111
+ const sizeCss =
112
+ typeof props.size === "number" ? `${props.size}px` : props.size;
113
+
114
+ styleObj.width = sizeCss;
115
+ styleObj.height = sizeCss;
116
+
117
+ // Inline CSS color if not a Tailwind class
118
+ if (!isTailwindColorClass(props.color)) {
119
+ styleObj.color = props.color;
120
+ }
121
+
122
+ return styleObj;
123
+ });
124
+ </script>
@@ -0,0 +1,194 @@
1
+ <template>
2
+ <div
3
+ ref="containerRef"
4
+ :class="['relative', containerClass, width]"
5
+ >
6
+ <Base
7
+ :model-value="modelValue"
8
+ :label="label"
9
+ :hint="hint"
10
+ :persistent-hint="!!hint"
11
+ :disabled="disabled"
12
+ :readonly="readonly"
13
+ :required="required"
14
+ :error="!!error || errorMessages.length > 0"
15
+ :error-messages="errorMessages"
16
+ :clearable="clearable && !loading"
17
+ :size="size"
18
+ :id="inputId"
19
+ :prepend="prepend"
20
+ :append="append"
21
+ :is-open="isFocused"
22
+ @click:clear="clear"
23
+ :bg="bg"
24
+ :border="border"
25
+ :text-color="textColor"
26
+ :rounded="rounded"
27
+ :dir="computedDir"
28
+ :lang="computedLang"
29
+ :width="width"
30
+ :rules="rules"
31
+ >
32
+ <template #control="{ attrs, events }">
33
+ <input
34
+ ref="inputRef"
35
+ v-bind="attrs"
36
+ v-on="events"
37
+ type="search"
38
+ :value="modelValue"
39
+ :placeholder="(isFocused && !modelValue) || (!label && !modelValue) ? placeholder : ''"
40
+ :class="inputClasses"
41
+ @input="handleInput"
42
+ @keydown.enter="handleEnter"
43
+ @focus="handleFocus"
44
+ @blur="handleBlur"
45
+ />
46
+ </template>
47
+
48
+ <!-- Loading spinner in append slot -->
49
+ <template v-if="loading" #append>
50
+ <svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
51
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
52
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 714 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
53
+ </svg>
54
+ </template>
55
+ </Base>
56
+ </div>
57
+ </template>
58
+
59
+ <script setup>
60
+ import { ref, computed, inject, onBeforeUnmount } from 'vue'
61
+ import Base from '../pgo/base/Base.vue'
62
+ import { roundedMap, sizes, iconSizes } from '../../pgo-components/lib/componentConfig'
63
+
64
+ const props = defineProps({
65
+ modelValue: { type: [String, Number], default: '' },
66
+ label: { type: [String, Object], default: '' },
67
+ placeholder: { type: String, default: 'Search...' },
68
+ hint: { type: String, default: '' },
69
+ error: { type: String, default: '' },
70
+ errorMessages: { type: Array, default: () => [] },
71
+ disabled: { type: Boolean, default: false },
72
+ readonly: { type: Boolean, default: false },
73
+ required: { type: Boolean, default: false },
74
+ clearable: { type: Boolean, default: true },
75
+ loading: { type: Boolean, default: false },
76
+ prepend: { type: String, default: '' },
77
+ append: { type: String, default: '' },
78
+ rules: { type: Array, default: () => [] }, // ADD THIS
79
+
80
+ // Search behavior
81
+ debounce: { type: Number, default: 300 },
82
+
83
+ // Appearance
84
+ size: { type: String },
85
+ rounded: { type: String },
86
+ border: { type: String},
87
+ textColor: { type: String },
88
+ bg: { type: String },
89
+ containerClass: { type: String, default: '' },
90
+ id: { type: String, default: '' },
91
+ width: { type: String, default: 'w-full' },
92
+
93
+ // RTL/Lang support
94
+ dir: { type: String, default: '' },
95
+ lang: { type: String, default: '' },
96
+
97
+ // Props that might be passed but not used (to avoid warnings)
98
+ items: { type: Array, default: () => [] },
99
+ itemText: { type: String, default: 'text' },
100
+ itemValue: { type: String, default: 'value' },
101
+ })
102
+
103
+ // Inject dir from parent Card (if exists)
104
+ const cardDir = inject('parentDir', '')
105
+ const cardLang = inject('parentLang', '')
106
+
107
+ // Use component's dir if provided, otherwise use card's dir
108
+ const computedDir = computed(() => props.dir || cardDir)
109
+ const computedLang = computed(() => props.lang || cardLang)
110
+
111
+ const emit = defineEmits([
112
+ 'update:modelValue',
113
+ 'input',
114
+ 'change',
115
+ 'focus',
116
+ 'blur',
117
+ 'clear',
118
+ 'search',
119
+ 'enter'
120
+ ])
121
+
122
+ const inputRef = ref(null)
123
+ const containerRef = ref(null)
124
+ const isFocused = ref(false)
125
+ let debounceTimeout = null
126
+
127
+ // Generate unique ID
128
+ const inputId = computed(() => props.id || `search-${Math.random().toString(36).substr(2, 9)}`)
129
+
130
+ // Input classes
131
+ const inputClasses = computed(() => [
132
+ 'w-full bg-transparent outline-none border-none',
133
+ 'placeholder:text-gray-400',
134
+ 'focus:outline-none'
135
+ ])
136
+
137
+ // Handlers
138
+ const handleInput = (e) => {
139
+ const value = e.target.value
140
+
141
+ // Update immediately for label animation
142
+ emit('update:modelValue', value)
143
+
144
+ clearTimeout(debounceTimeout)
145
+ debounceTimeout = setTimeout(() => {
146
+ emit('input', value)
147
+ emit('search', value)
148
+ }, props.debounce)
149
+ }
150
+
151
+ const handleFocus = (event) => {
152
+ isFocused.value = true
153
+ emit('focus', event)
154
+ }
155
+
156
+ const handleBlur = (event) => {
157
+ isFocused.value = false
158
+ emit('blur', event)
159
+ }
160
+
161
+ const handleEnter = (event) => {
162
+ emit('enter', props.modelValue)
163
+ emit('search', props.modelValue)
164
+ }
165
+
166
+ const clear = () => {
167
+ emit('update:modelValue', '')
168
+ emit('clear')
169
+ inputRef.value?.focus()
170
+ }
171
+
172
+ // Focus method
173
+ const focus = () => {
174
+ inputRef.value?.focus()
175
+ }
176
+
177
+ // Lifecycle
178
+ onBeforeUnmount(() => {
179
+ clearTimeout(debounceTimeout)
180
+ })
181
+
182
+ // Expose methods
183
+ defineExpose({ focus, clear })
184
+ </script>
185
+
186
+ <style scoped>
187
+ /* Remove default search input styling */
188
+ input[type="search"]::-webkit-search-decoration,
189
+ input[type="search"]::-webkit-search-cancel-button,
190
+ input[type="search"]::-webkit-search-results-button,
191
+ input[type="search"]::-webkit-search-results-decoration {
192
+ display: none;
193
+ }
194
+ </style>
@@ -0,0 +1,104 @@
1
+ <template>
2
+ <div class="flex h-screen overflow-hidden">
3
+ <!-- Drawer -->
4
+ <NavDrawer
5
+
6
+ :collapsed="collapsed"
7
+ :class="[
8
+ 'flex-shrink-0 bg-white shadow-lg z-30',
9
+ isMobile ? 'fixed inset-y-0 left-0 transform transition-transform duration-300 ease-in-out' : '',
10
+ drawerWidthClass,
11
+ drawerOpenClass
12
+ ]"
13
+ @close="closeDrawer"
14
+ @toggleCollapse="toggleCollapse"
15
+ />
16
+
17
+ <!-- Backdrop for mobile drawer -->
18
+
19
+ <div
20
+ v-if="isMobile && showDrawer"
21
+ class="fixed inset-0 bg-black bg-opacity-40 z-20"
22
+ @click="closeDrawer"
23
+ ></div>
24
+
25
+ <!-- Main content area -->
26
+ <div class="flex-1 flex flex-col overflow-hidden">
27
+ <!-- AppBar -->
28
+ <AppBar
29
+ title="This is APP"
30
+ :class="['flex-shrink-0']"
31
+ @toggleDrawer="toggleDrawer"
32
+ />
33
+
34
+ <!-- Content -->
35
+ <main class="flex-1 overflow-auto bg-gray-50 vts-p-4">
36
+ <slot>
37
+ sadf
38
+ </slot>
39
+ </main>
40
+
41
+ <!-- Optional Footer -->
42
+ <footer v-if="$slots.footer" class="flex-shrink-0 vts-p-4 bg-white shadow-inner">
43
+ <slot name="footer" />
44
+ </footer>
45
+ </div>
46
+ </div>
47
+ </template>
48
+
49
+ <script setup lang="ts">
50
+ import { ref, computed, onMounted, onUnmounted } from 'vue';
51
+ import AppBar from '../pgo/AppBar.vue';
52
+ import NavDrawer from '../pgo/NavDrawer.vue';
53
+
54
+ const collapsed = ref(false);
55
+ const showDrawer = ref(false);
56
+ const windowWidth = ref(window.innerWidth);
57
+
58
+ const MOBILE_BREAKPOINT = 768; // Tailwind md
59
+
60
+ // Detect mobile screen
61
+ const isMobile = computed(() => windowWidth.value < MOBILE_BREAKPOINT);
62
+
63
+ // Drawer width
64
+ const drawerWidthClass = computed(() => (collapsed.value ? 'w-16' : 'w-64'));
65
+
66
+ // Mobile drawer open/close transform
67
+ const drawerOpenClass = computed(() => {
68
+ if (!isMobile.value) return '';
69
+ return showDrawer.value ? 'translate-x-0' : '-translate-x-full';
70
+ });
71
+
72
+ // Toggle collapse
73
+ function toggleCollapse() {
74
+ collapsed.value = !collapsed.value;
75
+ }
76
+
77
+ // Toggle drawer visibility (for mobile)
78
+ function toggleDrawer() {
79
+ showDrawer.value = !showDrawer.value;
80
+ }
81
+
82
+ function closeDrawer() {
83
+ showDrawer.value = false;
84
+ }
85
+
86
+ // Update window width
87
+ function updateWidth() {
88
+ windowWidth.value = window.innerWidth;
89
+ }
90
+
91
+ onMounted(() => {
92
+ window.addEventListener('resize', updateWidth);
93
+ });
94
+ onUnmounted(() => {
95
+ window.removeEventListener('resize', updateWidth);
96
+ });
97
+ </script>
98
+
99
+ <style scoped>
100
+ /* Optional: smooth transition for main content margin on desktop when collapsing drawer */
101
+ .flex-1 {
102
+ transition: margin-left 0.3s ease-in-out;
103
+ }
104
+ </style>
@@ -0,0 +1,37 @@
1
+ <template>
2
+ <main
3
+ :class="['flex-1 w-full transition-padding duration-200', contentClass]"
4
+ ref="el"
5
+ :style="style"
6
+ >
7
+ <slot />
8
+ </main>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { ref, computed, inject, watch, onMounted } from "vue";
13
+
14
+ const layout = inject<any>("layout", null);
15
+ const el = ref<HTMLElement | null>(null);
16
+
17
+ const contentClass = computed(() => "");
18
+
19
+ const style = computed(() => {
20
+ // console.log("Layout in Main.vue horizontal:", layout?.horizontalOffset?.value);
21
+ const top = layout?.topOffset?.value || 0;
22
+ const bottom = layout?.bottomOffset?.value || 0;
23
+ const { left, right } = layout?.horizontalOffset?.value || {
24
+ left: 0,
25
+ right: 0,
26
+ };
27
+ return {
28
+ paddingTop: `${top}px`,
29
+ paddingBottom: `${bottom}px`,
30
+ paddingLeft: left ? `${left}px` : undefined,
31
+ paddingRight: right ? `${right}px` : undefined,
32
+ minHeight: `calc(100vh - ${bottom}px)`,
33
+ };
34
+ });
35
+
36
+ onMounted(() => {});
37
+ </script>