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,214 @@
1
+ <template>
2
+ <div class="vdp-showcase space-y-8 p-6">
3
+ <h2 class="text-xl font-semibold text-center">
4
+ Vue DatePicker Showcase - based on @vuepic/vue-datepicker
5
+ </h2>
6
+
7
+ <DatePicker
8
+ v-model="date"
9
+ label="Birth Date"
10
+
11
+ />
12
+
13
+ <!-- Basic date -->
14
+ <section>
15
+ <h3 class="mb-2 font-medium">
16
+ Basic Date
17
+ </h3>
18
+ <VueDatePicker
19
+ v-model="date"
20
+ format="yyyy-MM-dd"
21
+ placeholder="Select date"
22
+ :text-input="true"
23
+
24
+ />
25
+ <p class="mt-2 text-sm">
26
+ Selected: {{ date ? date.toString() : 'None' }}
27
+ </p>
28
+ </section>
29
+
30
+ <!-- Range selection -->
31
+ <section>
32
+ <h3 class="mb-2 font-medium">
33
+ Range Selection
34
+ </h3>
35
+ <VueDatePicker
36
+ v-model="range"
37
+ range
38
+ auto-apply
39
+ :multi-calendars="2"
40
+ placeholder="Select date range"
41
+ />
42
+ <p class="mt-2 text-sm">
43
+ Selected: {{ range && range[0] && range[1] ? range[0]?.toDateString() + ' — ' + range[1]?.toDateString() : 'None' }}
44
+ </p>
45
+ </section>
46
+
47
+ <!-- Date & time -->
48
+ <section>
49
+ <h3 class="mb-2 font-medium">
50
+ Date & Time
51
+ </h3>
52
+ <VueDatePicker
53
+ v-model="dateTime"
54
+ enable-time-picker
55
+ format="yyyy-MM-dd HH:mm"
56
+ placeholder="Select date & time"
57
+ text-input
58
+ />
59
+ <p class="mt-2 text-sm">
60
+ Selected: {{ dateTime ? dateTime.toString() : 'None' }}
61
+ </p>
62
+ </section>
63
+
64
+ <!-- Inline calendar -->
65
+ <section>
66
+ <h3 class="mb-2 font-medium">
67
+ Inline Calendar
68
+ </h3>
69
+ <VueDatePicker
70
+ v-model="inlineDate"
71
+ inline
72
+ auto-apply
73
+ text-input
74
+ />
75
+ <p class="mt-2 text-sm">
76
+ Selected: {{ inlineDate ? inlineDate.toDateString() : 'None' }}
77
+ </p>
78
+ </section>
79
+
80
+ <!-- Week / Month / Year pickers -->
81
+ <section>
82
+ <h3 class="mb-2 font-medium">
83
+ Week / Month / Year
84
+ </h3>
85
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
86
+ <div>
87
+ <label class="block mb-1 text-sm">Week Picker</label>
88
+ <VueDatePicker
89
+ v-model="week"
90
+ week-picker
91
+ auto-apply
92
+ text-input
93
+ />
94
+ </div>
95
+ <div>
96
+ <label class="block mb-1 text-sm">Month Picker</label>
97
+ <VueDatePicker
98
+ v-model="month"
99
+ month-picker
100
+ auto-apply
101
+ text-input
102
+ />
103
+ </div>
104
+ <div>
105
+ <label class="block mb-1 text-sm">Year Picker</label>
106
+ <VueDatePicker
107
+ v-model="year"
108
+ year-picker
109
+ auto-apply
110
+ text-input
111
+ />
112
+ </div>
113
+ </div>
114
+ </section>
115
+
116
+ <!-- Min / Max and disabled dates -->
117
+ <section>
118
+ <h3 class="mb-2 font-medium">
119
+ Bounded + Disabled Weekends
120
+ </h3>
121
+ <VueDatePicker
122
+ v-model="bounded"
123
+ :min-date="minDate"
124
+ :max-date="maxDate"
125
+ :disabled-dates="disabledDates"
126
+ format="PPPP"
127
+ placeholder="Pick within bounds"
128
+ text-input
129
+ />
130
+ <p class="mt-2 text-sm">
131
+ Selected: {{ bounded ? bounded.toDateString() : 'None' }}
132
+ </p>
133
+ <p class="text-xs text-gray-500">
134
+ Bounds: {{ minDate.toDateString() }} — {{ maxDate.toDateString() }}
135
+ </p>
136
+ </section>
137
+
138
+ <!-- Multiple dates -->
139
+ <section>
140
+ <h3 class="mb-2 font-medium">
141
+ Multiple Dates
142
+ </h3>
143
+ <VueDatePicker
144
+ v-model="multiDates"
145
+ multi-dates
146
+ :max-dates="5"
147
+ text-input
148
+ />
149
+ <p class="mt-2 text-sm">
150
+ Selected: {{ multiDates && multiDates.length ? multiDates.map(d => d.toDateString()).join(', ') : 'None' }}
151
+ </p>
152
+ </section>
153
+
154
+ <!-- Position and Teleport -->
155
+ <!-- <section>
156
+ <h3 class="mb-2 font-medium">Position & Teleport</h3>
157
+ <VueDatePicker v-model="date" position="right" teleport="body" placeholder="Opens to the right" />
158
+ </section> -->
159
+ </div>
160
+ </template>
161
+
162
+ <script setup lang="ts">
163
+ import { ref } from 'vue'
164
+ import { VueDatePicker } from '@vuepic/vue-datepicker'
165
+ import '@vuepic/vue-datepicker/dist/main.css'
166
+ import DatePicker from '../pgo/inputs/DatePicker.vue'
167
+
168
+ // Basic date
169
+ const date = ref(null)
170
+
171
+ // Range selection
172
+ const range = ref(null)
173
+
174
+ // Date & time
175
+ const dateTime = ref(null)
176
+
177
+ // Inline calendar
178
+ const inlineDate = ref(null)
179
+
180
+ // Week / Month / Year pickers
181
+ const week = ref(null)
182
+ const month = ref(null)
183
+ const year = ref(null)
184
+
185
+ // Min / Max and disabled dates
186
+ const bounded = ref(null)
187
+ const minDate = ref(new Date(new Date().getFullYear(), new Date().getMonth(), 5))
188
+ const maxDate = ref(new Date(new Date().getFullYear(), new Date().getMonth(), 25))
189
+
190
+ function disabledDates(d) {
191
+ // Example: disable weekends
192
+ const day = d.getDay()
193
+ return day === 0 || day === 6
194
+ }
195
+
196
+ // Multiple selections
197
+ const multiDates = ref([])
198
+ </script>
199
+
200
+ <style scoped>
201
+ .vdp-showcase {
202
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif;
203
+ }
204
+
205
+ /* Theme token mapping for @vuepic/vue-datepicker via CSS variables */
206
+ /* DatePicker variables are now set globally in global.css */
207
+
208
+ /* Optional: ensure the datepicker input uses tokenized radius when library falls back */
209
+ :deep(.dp__input_wrap) {
210
+ border-radius: var(--vts-radius-md);
211
+ }
212
+
213
+
214
+ </style>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <div class="p-6 space-y-8">
3
+ <h2 class="text-xl font-semibold">DatePicker Examples</h2>
4
+
5
+ <div class="space-y-4">
6
+ <h3 class="font-medium">Single Date</h3>
7
+ <DatePicker v-model="single" placeholder="Pick a date" />
8
+ <div class="text-sm text-[var(--vts-color-textSecondary)]">Selected: {{ single && single.toDateString() }}</div>
9
+ </div>
10
+
11
+ <div class="space-y-4">
12
+ <h3 class="font-medium">Range</h3>
13
+ <DatePicker v-model="range" :range="true" placeholder="Pick a date range" />
14
+ <div class="text-sm text-[var(--vts-color-textSecondary)]">Selected: {{ range && range[0] && range[0].toDateString() }} — {{ range && range[1] && range[1].toDateString() }}</div>
15
+ </div>
16
+
17
+ <div class="space-y-4">
18
+ <h3 class="font-medium">With Constraints</h3>
19
+ <DatePicker v-model="bounded" :min="min" :max="max" />
20
+ </div>
21
+ </div>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { ref } from 'vue'
26
+ import DatePicker from '../pgo/inputs/DatePicker.vue'
27
+
28
+ const single = ref<Date | null>(null)
29
+ const range = ref<[Date | null, Date | null] | null>(null)
30
+ const bounded = ref<Date | null>(null)
31
+ const min = new Date()
32
+ const max = new Date(new Date().getFullYear(), 11, 31)
33
+ </script>
@@ -0,0 +1,77 @@
1
+ <template>
2
+ <Card
3
+ :title="'Form Example'"
4
+ class="mb-4"
5
+ >
6
+ <Form
7
+ v-model="form"
8
+ :rules="rules"
9
+ :validate-on="'change'"
10
+ submit-text="Submit"
11
+ >
12
+ <template #default="{ values, errors, setField }">
13
+ <div class="space-y-3">
14
+ <InputSearch
15
+ v-model="values.name"
16
+ label="Name"
17
+ :error-messages="errors.name || []"
18
+ :placeholder="'Your name'"
19
+ @input="val => setField('name', val)"
20
+ />
21
+
22
+ <Select
23
+ v-model="values.category"
24
+ label="Category"
25
+ :items="categories"
26
+ size="md"
27
+ item-title="text"
28
+ item-value="value"
29
+ :error-messages="errors.category || []"
30
+ @change="val => setField('category', val)"
31
+ />
32
+
33
+ <FileUpload
34
+ v-model="values.files"
35
+ label="Attachments"
36
+ :error-messages="errors.files || []"
37
+ accept="image/*,.pdf"
38
+ rounded=""
39
+ :max-size-mb="2"
40
+ @change="files => setField('files', files)"
41
+ />
42
+ </div>
43
+ </template>
44
+ </Form>
45
+ </Card>
46
+ </template>
47
+
48
+ <script setup>
49
+ import { ref } from 'vue'
50
+ import Card from '../pgo/Card.vue'
51
+ import Form from '../pgo/forms/Form.vue'
52
+ import InputSearch from '../pgo/InputSearch.vue'
53
+ import Select from '../pgo/inputs/Select.vue'
54
+ import FileUpload from '../pgo/inputs/FileUpload.vue'
55
+ const form = ref({ name: '', category: '', files: [] })
56
+
57
+ const rules = {
58
+ name: [
59
+ (v) => (v && String(v).trim().length >= 2) || 'Name is required (min 2 chars)'
60
+ ],
61
+ category: [
62
+ (v) => !!v || 'Category is required'
63
+ ],
64
+ files: [
65
+ (arr) => (Array.isArray(arr) && arr.length > 0) || 'At least one file'
66
+ ]
67
+ }
68
+
69
+ const categories = [
70
+ { text: 'General', value: 'general' },
71
+ { text: 'Bug', value: 'bug' },
72
+ { text: 'Feature', value: 'feature' },
73
+ ]
74
+ </script>
75
+
76
+ <style scoped>
77
+ </style>
@@ -0,0 +1,25 @@
1
+ import AppBar from "./pgo/AppBar.vue";
2
+ import Button from "./pgo/Button.vue";
3
+ import Card from "./pgo/Card.vue";
4
+ import HeroIcon from "./pgo/HeroIcon.vue";
5
+ import Select from "./pgo/inputs/Select.vue";
6
+ import FileUpload from "./pgo/inputs/FileUpload.vue";
7
+ import Form from "./pgo/forms/Form.vue";
8
+ // import NavDrawer from './pgo/NavDrawer.vue'
9
+ import NavigationDrawer from "./pgo/NavigationDrawer.vue";
10
+ import NavDrawer from "./pgo/NavDrawer.vue";
11
+ import NavDrawerItem from "./pgo/NavDrawerItem.vue";
12
+ import OldAppBar from "./pgo/OldAppBar.vue";
13
+
14
+ export {
15
+ AppBar,
16
+ OldAppBar,
17
+ Button,
18
+ Card,
19
+ HeroIcon,
20
+ Select,
21
+ NavDrawer,
22
+ NavDrawerItem,
23
+ FileUpload,
24
+ Form,
25
+ };
@@ -0,0 +1,347 @@
1
+ <template>
2
+ <header
3
+ ref="el"
4
+ data-component="appbar"
5
+ :dir="rtl ? 'rtl' : 'ltr'"
6
+ :class="[
7
+ 'w-full flex flex-col transition-transform duration-300',
8
+ appBarZClass,
9
+ fixed ? 'fixed top-0 left-0' : floating ? 'absolute top-0 left-1/2 -translate-x-1/2 rounded-xl shadow-lg' : 'relative',
10
+ elevationClass,
11
+ hidden ? 'hidden' : '',
12
+ color,
13
+ border,
14
+ p,
15
+ m,
16
+ customClass,
17
+ densityClass,
18
+ collapseClass
19
+ ]"
20
+ :style="[themeStyles, customStyle, appBarZStyle]"
21
+ >
22
+ <div :class="['max-w-7xl mx-auto w-full flex items-center justify-between', innerDirectionClass, dense ? 'h-12' : 'h-16']">
23
+ <!-- DYNAMIC REGION ORDER -->
24
+ <template v-for="region in orderedRegions" :key="region">
25
+ <!-- PREPEND -->
26
+ <template v-if="region === 'prepend'">
27
+ <div :class="['flex items-center', prependClass]">
28
+ <slot name="prepend" />
29
+
30
+ <!-- drawer/menu button -->
31
+ <button
32
+ v-if="drawer || menu"
33
+ @click="$emit('toggle-drawer')"
34
+ class="md:hidden inline-flex items-center justify-center .vts-pa-2 rounded-md"
35
+ >
36
+ <slot name="drawer-icon">
37
+ <HeroIcon name="bars-3" size="24" color="text-gray-600" />
38
+ </slot>
39
+ </button>
40
+
41
+ <!-- TITLE -->
42
+ <div :class="['shrink-0 flex items-center', titleMarginClass, titleAlignClass]">
43
+ <slot name="title">
44
+ <span :class="['font-semibold', titleClass]">{{ title }}</span>
45
+ </slot>
46
+ </div>
47
+ </div>
48
+ </template>
49
+
50
+ <!-- NAV -->
51
+ <template v-if="region === 'nav'">
52
+ <nav :class="navClass">
53
+ <slot name="nav" />
54
+ </nav>
55
+ </template>
56
+
57
+ <!-- ACTIONS -->
58
+ <template v-if="region === 'actions'">
59
+ <div :class="['flex items-center', actionsSpaceClass]">
60
+ <slot name="actions" />
61
+
62
+ <!-- Overflow actions -->
63
+ <div v-if="overflowActions.length" class="relative">
64
+ <button @click="toggleOverflow" class="p-2 rounded-md hover:bg-black/5 dark:hover:bg-white/10">
65
+ <svg class="h-5 w-5" fill="none" stroke="currentColor">
66
+ <circle cx="12" cy="12" r="2" />
67
+ <circle cx="19" cy="12" r="2" />
68
+ <circle cx="5" cy="12" r="2" />
69
+ </svg>
70
+ </button>
71
+
72
+ <div v-if="isOverflowOpen" :class="['absolute vts-mt-2 w-40 rounded shadow-lg z-50', overflowPositionClass]">
73
+ <ul>
74
+ <li v-for="(action, i) in overflowActions" :key="i">
75
+ <button @click="action.onClick" class="w-full text-left vts-px-4 vts-py-2 hover:bg-black/5 dark:hover:bg-white/10">
76
+ <span v-if="action.icon" class="vts-mr-2">
77
+ <component :is="action.icon" />
78
+ </span>
79
+ {{ action.label }}
80
+ </button>
81
+ </li>
82
+ </ul>
83
+ </div>
84
+ </div>
85
+
86
+ <!-- Language Selector -->
87
+ <div class="relative" ref="langMenuRef">
88
+ <button @click="toggleLangMenu" class="px-2 py-1 rounded-md hover:bg-black/5 dark:hover:bg-white/10">
89
+ {{ currentLangCode }}
90
+ </button>
91
+ <div
92
+ v-if="isLangOpen"
93
+ :class="['absolute vts-mt-2 w-28 rounded shadow-lg z-50', overflowPositionClass]"
94
+ :style="{ backgroundColor: 'var(--vts-color-surfaceElevated)', borderColor: 'var(--vts-color-divider)' }"
95
+ >
96
+ <ul>
97
+ <li v-for="(lang, i) in languagesList" :key="i">
98
+ <button
99
+ @click="selectLanguage(lang)"
100
+ class="w-full text-left vts-px-3 vts-py-2 hover:bg-black/5 dark:hover:bg-white/10"
101
+ >
102
+ {{ getLangLabel(lang) }}
103
+ </button>
104
+ </li>
105
+ </ul>
106
+ </div>
107
+ </div>
108
+
109
+ <slot name="append" />
110
+ </div>
111
+ </template>
112
+ </template>
113
+ </div>
114
+
115
+ <!-- MOBILE NAV -->
116
+ <div
117
+ v-show="isOpen"
118
+ class="md:hidden vts-border-sm"
119
+ :style="{ backgroundColor: 'var(--vts-color-surfaceElevated)', borderColor: 'var(--vts-color-divider)' }"
120
+ >
121
+ <div class="vts-px-2 vts-pt-2 vts-pb-3 vts-space-y-1">
122
+ <slot name="mobile-nav" />
123
+ </div>
124
+ </div>
125
+ </header>
126
+ </template>
127
+
128
+ <script setup lang="ts">
129
+ import { ref, computed, onMounted, onBeforeUnmount, inject, watch } from 'vue'
130
+ import type { PropType } from 'vue'
131
+ import { HeroIcon } from './'
132
+ import { globalRtl } from '../../pgo-components/lib/core/rtl/rtl'
133
+
134
+ const themeStyles = computed(() => ({
135
+ backgroundColor: 'var(--vts-color-surfaceElevated)',
136
+ color: 'var(--vts-color-text)',
137
+ boxShadow: props.elevation ? `var(--vts-elevation-${props.elevation})` : props.shadow ? 'var(--vts-shadow-md)' : undefined,
138
+ borderBottom: '1px solid var(--vts-color-border)'
139
+ }))
140
+
141
+ /* ---------------------------------------------
142
+ * PROPS — merged (old props kept)
143
+ ----------------------------------------------*/
144
+ const props = defineProps({
145
+ id: {
146
+ type: String,
147
+ default: () => `appbar-${Math.random().toString(36).slice(2, 9)}`
148
+ },
149
+
150
+ title: String,
151
+ titleAlign: { type: String, default: 'left' },
152
+ titleClass: String,
153
+
154
+ fixed: Boolean,
155
+ floating: Boolean,
156
+
157
+ elevation: [String, Number],
158
+ shadow: String, // kept for compatibility
159
+
160
+ color: String,
161
+ border: String,
162
+ p: String,
163
+ m: String,
164
+ hidden: Boolean,
165
+ customClass: String,
166
+ customStyle: [String, Object],
167
+
168
+ zIndex: { type: [String, Number], default: undefined },
169
+
170
+ drawer: Boolean,
171
+ menu: Boolean,
172
+ overflowActions: { type: Array as PropType<Array<{ label: string; icon?: any; onClick?: () => void }>>, default: () => [] },
173
+
174
+ // Language selector
175
+ languages: { type: Array as PropType<string[]>, default: () => ['en', 'dv'] },
176
+
177
+ rtl: { type: Boolean, default: globalRtl.value },
178
+
179
+ dense: Boolean,
180
+ collapseOnScroll: Boolean
181
+ })
182
+
183
+ /* ---------------------------------------------
184
+ * layout integration
185
+ ----------------------------------------------*/
186
+ const layout = inject('layout', null)
187
+ const el = ref<HTMLElement | null>(null)
188
+ const height = ref(0)
189
+
190
+ function updateHeight() {
191
+ if (!el.value) return
192
+ height.value = el.value.getBoundingClientRect().height
193
+ if (layout)
194
+ layout.register('appBar', props.id, {
195
+ height: height.value,
196
+ dense: props.dense
197
+ })
198
+ // console.log('AppBar registered with height:', height.value, ' and dense:', props.dense)
199
+ }
200
+
201
+ onMounted(() => {
202
+ updateHeight()
203
+ const ro = new ResizeObserver(updateHeight)
204
+ if (el.value) ro.observe(el.value)
205
+
206
+ if (props.collapseOnScroll) window.addEventListener('scroll', onScroll, { passive: true })
207
+ document.addEventListener('click', onDocumentClick, { capture: true })
208
+
209
+ onBeforeUnmount(() => {
210
+ ro.disconnect()
211
+ if (props.collapseOnScroll) window.removeEventListener('scroll', onScroll)
212
+ document.removeEventListener('click', onDocumentClick, { capture: true })
213
+ if (layout) layout.unregister('appBar', props.id)
214
+ })
215
+ })
216
+
217
+ /* ---------------------------------------------
218
+ * collapse on scroll
219
+ ----------------------------------------------*/
220
+ let lastScroll = 0
221
+ const collapseClass = ref('')
222
+
223
+ function onScroll() {
224
+ const y = window.scrollY
225
+ const delta = y - lastScroll
226
+ lastScroll = y
227
+
228
+ if (y > 0 && delta > 0) collapseClass.value = '-translate-y-12 opacity-95'
229
+ else collapseClass.value = ''
230
+ }
231
+
232
+ /* ---------------------------------------------
233
+ * OLD RTL logic (preserved)
234
+ ----------------------------------------------*/
235
+ const orderedRegions = computed(() => (props.rtl ? ['actions', 'nav', 'prepend'] : ['prepend', 'nav', 'actions']))
236
+
237
+ const innerDirectionClass = computed(() => (props.rtl ? 'flex-row-reverse' : 'flex-row'))
238
+
239
+ const prependClass = computed(() => (props.rtl ? 'mr-2' : ''))
240
+ const titleMarginClass = computed(() => (props.rtl ? 'mr-2' : 'ml-2'))
241
+
242
+ const titleAlignClass = computed(() => {
243
+ const a = props.titleAlign
244
+ const RTL = props.rtl
245
+
246
+ if (RTL)
247
+ return {
248
+ center: 'justify-center text-center w-full',
249
+ left: 'justify-end text-right w-full',
250
+ right: ''
251
+ }[a]
252
+
253
+ return {
254
+ center: 'justify-center text-center w-full',
255
+ right: 'justify-end text-right w-full',
256
+ left: ''
257
+ }[a]
258
+ })
259
+
260
+ const navClass = computed(() =>
261
+ props.rtl ? 'hidden md:flex md:mr-6 md:space-x-4 items-center' : 'hidden md:flex md:ml-6 md:space-x-4 items-center'
262
+ )
263
+
264
+ const actionsSpaceClass = computed(() => (props.rtl ? 'space-x-2 space-x-reverse' : 'space-x-2'))
265
+
266
+ const overflowPositionClass = computed(() => (props.rtl ? 'left-0' : 'right-0'))
267
+
268
+ /* ---------------------------------------------
269
+ * Overflow menu
270
+ ----------------------------------------------*/
271
+ const isOverflowOpen = ref(false)
272
+ const toggleOverflow = () => (isOverflowOpen.value = !isOverflowOpen.value)
273
+
274
+ /* ---------------------------------------------
275
+ * Language selector (i18n)
276
+ ----------------------------------------------*/
277
+ const { setLanguage, language } = inject('i18n') as any
278
+ const isLangOpen = ref(false)
279
+ const toggleLangMenu = () => (isLangOpen.value = !isLangOpen.value)
280
+ const langMenuRef = ref<HTMLElement | null>(null)
281
+ const languagesList = computed<string[]>(() =>
282
+ Array.isArray(props.languages) && props.languages.length ? props.languages : ['en', 'dv']
283
+ )
284
+ const currentLangCode = computed(() => {
285
+ const val = (language && (language.value ?? language)) as string
286
+ const code = (val || '').slice(0, 2)
287
+ if (code === 'dv') return 'ދިވެހި'
288
+ return (code || 'en').toUpperCase()
289
+ })
290
+ function selectLanguage(lang: string) {
291
+ if (typeof setLanguage === 'function') setLanguage(lang)
292
+ window.location.reload()
293
+ isLangOpen.value = false
294
+ }
295
+
296
+ function onDocumentClick(e: MouseEvent) {
297
+ if (!isLangOpen.value) return
298
+ isLangOpen.value = false
299
+ // const root = langMenuRef.value
300
+ // const target = e.target as Node | null
301
+ // if (!root || !target) {
302
+ // isLangOpen.value = false
303
+ // return
304
+ // }
305
+ // if (!root.contains(target)) {
306
+ // isLangOpen.value = false
307
+ // }
308
+ }
309
+
310
+ function getLangLabel(lang: string) {
311
+ const val = (language && (language.value ?? language)) as string
312
+ const isDv = (val || '').slice(0, 2) === 'dv'
313
+ if (isDv) {
314
+ if (lang === 'en') return 'އިނގިރޭސި'
315
+ if (lang === 'dv') return 'ދިވެހި'
316
+ }
317
+ return (lang || '').toUpperCase()
318
+ }
319
+
320
+ /* ---------------------------------------------
321
+ * elevation + zIndex logic (merged)
322
+ ----------------------------------------------*/
323
+ const elevationClass = computed(() => {
324
+ if (props.shadow) return props.shadow
325
+ if (props.elevation) return `shadow-${props.elevation}`
326
+ return ''
327
+ })
328
+
329
+ const appBarZClass = computed(() => {
330
+ if (typeof props.zIndex === 'string' && props.zIndex.startsWith('z-')) return props.zIndex
331
+ return 'z-30'
332
+ })
333
+ const appBarZStyle = computed(() => (typeof props.zIndex === 'number' ? { zIndex: props.zIndex } : undefined))
334
+
335
+ /* ---------------------------------------------
336
+ * density class
337
+ ----------------------------------------------*/
338
+ const densityClass = computed(() => (props.dense ? 'py-1' : ''))
339
+
340
+ /* ---------------------------------------------
341
+ * mobile nav
342
+ ----------------------------------------------*/
343
+ const isOpen = ref(false)
344
+
345
+ /* expose for template */
346
+ </script>
347
+