@bagelink/vue 1.14.15 → 1.15.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/dist/components/Alert.vue.d.ts.map +1 -1
- package/dist/components/Badge.vue.d.ts.map +1 -1
- package/dist/components/Btn.vue.d.ts.map +1 -1
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/Image.vue.d.ts.map +1 -1
- package/dist/components/ListItem.vue.d.ts.map +1 -1
- package/dist/components/MapEmbed/Index.vue.d.ts.map +1 -1
- package/dist/components/Pagination.vue.d.ts.map +1 -1
- package/dist/components/Swiper.vue.d.ts.map +1 -1
- package/dist/components/Toast.vue.d.ts.map +1 -1
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
- package/dist/components/layout/AppLayout.vue.d.ts.map +1 -1
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
- package/dist/components/layout/Panel.vue.d.ts.map +1 -1
- package/dist/components/layout/Resizable.vue.d.ts.map +1 -1
- package/dist/components/layout/TabsNav.vue.d.ts.map +1 -1
- package/dist/components/layout/appLayoutContext.d.ts +24 -0
- package/dist/components/layout/appLayoutContext.d.ts.map +1 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
- package/dist/composables/index.d.ts.map +1 -1
- package/dist/composables/useDevice.d.ts.map +1 -1
- package/dist/composables/useEscapeKey.d.ts +12 -0
- package/dist/composables/useEscapeKey.d.ts.map +1 -0
- package/dist/composables/useSchemaField.d.ts.map +1 -1
- package/dist/composables/useTheme.d.ts.map +1 -1
- package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
- package/dist/form-flow/form-flow.d.ts.map +1 -1
- package/dist/index.cjs +203 -207
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +25819 -28870
- package/dist/style.css +1 -1
- package/dist/types/BagelForm.d.ts.map +1 -1
- package/dist/types/BtnOptions.d.ts.map +1 -1
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/package.json +3 -6
- package/src/components/Alert.vue +34 -14
- package/src/components/Badge.vue +145 -22
- package/src/components/Btn.vue +43 -31
- package/src/components/Dropdown.vue +5 -12
- package/src/components/FilterQuery.vue +1 -1
- package/src/components/Image.vue +3 -2
- package/src/components/JSONSchema.vue +2 -2
- package/src/components/JsonBuilder.vue +1 -1
- package/src/components/ListItem.vue +1 -3
- package/src/components/MapEmbed/Index.vue +10 -9
- package/src/components/NavBar.vue +2 -2
- package/src/components/Spreadsheet/Index.vue +1 -1
- package/src/components/Swiper.vue +3 -1
- package/src/components/Toast.vue +23 -8
- package/src/components/calendar/Index.vue +4 -4
- package/src/components/calendar/views/MonthView.vue +3 -3
- package/src/components/form/index.ts +0 -4
- package/src/components/form/inputs/EmailInput.vue +1 -1
- package/src/components/form/inputs/NumberInput.vue +1 -1
- package/src/components/form/inputs/OTP.vue +2 -2
- package/src/components/form/inputs/SelectInput.vue +3 -3
- package/src/components/form/inputs/TelInput.vue +2 -2
- package/src/components/form/inputs/TextInput.vue +1 -1
- package/src/components/form/inputs/Upload/upload.css +2 -2
- package/src/components/index.ts +2 -6
- package/src/components/layout/AppContent.vue +5 -19
- package/src/components/layout/AppLayout.vue +47 -18
- package/src/components/layout/AppSidebar.vue +16 -33
- package/src/components/layout/Resizable.vue +5 -2
- package/src/components/layout/TabsNav.vue +5 -5
- package/src/components/layout/appLayoutContext.ts +44 -0
- package/src/components/layout/index.ts +2 -0
- package/src/components/lightbox/Lightbox.vue +3 -9
- package/src/composables/index.ts +1 -0
- package/src/composables/useDevice.ts +2 -1
- package/src/composables/useEscapeKey.ts +56 -0
- package/src/composables/useSchemaField.ts +2 -17
- package/src/composables/useTheme.ts +23 -19
- package/src/form-flow/FormFlow.vue +2 -0
- package/src/form-flow/form-flow.ts +7 -0
- package/src/index.ts +0 -2
- package/src/styles/inputs.css +1 -1
- package/src/types/BagelForm.ts +46 -151
- package/src/types/BtnOptions.ts +5 -3
- package/src/utils/constants.ts +7 -0
- package/src/utils/index.ts +19 -3
- package/src/utils/sizeParsing.ts +5 -5
- package/vite.config.ts +5 -1
- package/src/components/Carousel.vue +0 -724
- package/src/components/ImportData.vue +0 -1749
- package/src/components/Pill.vue +0 -150
- package/src/components/Slider.vue +0 -1446
- package/src/components/Title.vue +0 -23
- package/src/components/ToolBar.vue +0 -9
- package/src/components/form/BagelForm.vue +0 -219
- package/src/components/form/BglFieldSet.vue +0 -14
- package/src/components/form/BglMultiStepForm.vue +0 -469
- package/src/components/form/FieldArray.vue +0 -422
- package/src/components/form/useBagelFormState.ts +0 -76
- package/src/composables/useFormField.ts +0 -38
- package/src/dialog/DialogOLD.vue +0 -358
- package/src/utils/BagelFormUtils.ts +0 -684
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { LatLngExpression, Map, Marker } from 'leaflet'
|
|
3
|
-
import { appendScript,
|
|
4
|
-
import { onMounted, ref, watch } from 'vue'
|
|
3
|
+
import { appendScript, awaitGlobal } from '@bagelink/vue'
|
|
4
|
+
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
|
5
5
|
import './leaflet.css'
|
|
6
6
|
|
|
7
7
|
type MapMarker = {
|
|
@@ -40,14 +40,9 @@ const id = ref(Math.random().toString(36).slice(2, 10))
|
|
|
40
40
|
const defaultMarkerSVG = '<svg width="28" height="38" viewBox="0 0 28 38" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14.2263 37.7955C17.0897 37.7955 19.4109 37.0138 19.4109 36.0496C19.4109 35.0854 17.0897 34.3037 14.2263 34.3037C11.363 34.3037 9.04175 35.0854 9.04175 36.0496C9.04175 37.0138 11.363 37.7955 14.2263 37.7955Z" fill="black" fill-opacity="0.1"/><path d="M14.2265 0.549591C21.2842 0.549591 27.0131 6.23786 27.0787 13.28V13.4024C27.0787 19.3328 24.4759 24.4306 21.5627 28.2764C18.6511 32.12 15.4577 34.6754 14.3457 35.5097C14.2748 35.5629 14.1778 35.5629 14.1068 35.5097C12.9947 34.675 9.80135 32.1197 6.88984 28.2762C3.97665 24.4304 1.37378 19.3328 1.37378 13.4024C1.37378 6.30387 7.12806 0.549591 14.2265 0.549591Z" fill="#ED1b3E" stroke="#ED6C6F"/><path d="M14.2263 21.6185C18.7639 21.6185 22.4424 17.94 22.4424 13.4024C22.4424 8.86477 18.7639 5.18631 14.2263 5.18631C9.68872 5.18631 6.01025 8.86477 6.01025 13.4024C6.01025 17.94 9.68872 21.6185 14.2263 21.6185Z" fill="white"/></svg>'
|
|
41
41
|
const leafletScriptUrl = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'
|
|
42
42
|
|
|
43
|
-
async function loadGlobalL() {
|
|
44
|
-
while (!window.L) { await sleep(100) }
|
|
45
|
-
return window.L
|
|
46
|
-
}
|
|
47
|
-
|
|
48
43
|
async function initializeMap() {
|
|
49
44
|
await appendScript(leafletScriptUrl)
|
|
50
|
-
L.value = await
|
|
45
|
+
L.value = await awaitGlobal('L')
|
|
51
46
|
if (!map.value) {
|
|
52
47
|
map.value = L.value.map(id.value, {
|
|
53
48
|
center: props.center,
|
|
@@ -96,7 +91,7 @@ function latLangFromMarker(marker: MapMarker): LatLngExpression {
|
|
|
96
91
|
}
|
|
97
92
|
|
|
98
93
|
async function watchMarkers(markers?: MapMarker[]) {
|
|
99
|
-
if (!L.value) { L.value = await
|
|
94
|
+
if (!L.value) { L.value = await awaitGlobal('L') }
|
|
100
95
|
_markers.value.forEach(marker => marker.remove())
|
|
101
96
|
if (!markers) { return }
|
|
102
97
|
|
|
@@ -111,6 +106,12 @@ watch(() => props.markers, watchMarkers, { immediate: true })
|
|
|
111
106
|
watch(() => props.center, center => map.value?.setView(center, props.zoom), { immediate: true })
|
|
112
107
|
|
|
113
108
|
onMounted(initializeMap)
|
|
109
|
+
|
|
110
|
+
onUnmounted(() => {
|
|
111
|
+
_markers.value.forEach(marker => marker.remove())
|
|
112
|
+
map.value?.remove()
|
|
113
|
+
map.value = undefined
|
|
114
|
+
})
|
|
114
115
|
</script>
|
|
115
116
|
|
|
116
117
|
<template>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import type { IconType, NavLink } from '@bagelink/vue'
|
|
3
|
-
import { Icon } from '@bagelink/vue'
|
|
3
|
+
import { Icon, MOBILE_BREAKPOINT } from '@bagelink/vue'
|
|
4
4
|
import { onMounted, ref } from 'vue'
|
|
5
5
|
import { resolveI18n } from '../i18n'
|
|
6
6
|
|
|
@@ -24,7 +24,7 @@ withDefaults(
|
|
|
24
24
|
const isOpen = ref(true)
|
|
25
25
|
|
|
26
26
|
function calcIsOpen() {
|
|
27
|
-
isOpen.value = window.innerWidth
|
|
27
|
+
isOpen.value = window.innerWidth <= MOBILE_BREAKPOINT
|
|
28
28
|
|
|
29
29
|
const storedNavOpenVal = localStorage.getItem('navOpen')
|
|
30
30
|
if (storedNavOpenVal === 'true' || storedNavOpenVal === null) { isOpen.value = true }
|
|
@@ -217,7 +217,7 @@ function handleSpreadsheetKeyDown(event: KeyboardEvent) {
|
|
|
217
217
|
flat thin small value="$t:spreadsheet.selectAll"
|
|
218
218
|
@click="visibleColumns = columnOptions.map(col => col.key)"
|
|
219
219
|
/>
|
|
220
|
-
<Btn flat thin
|
|
220
|
+
<Btn flat thin sm value="$t:spreadsheet.clearAll" @click="visibleColumns = []" />
|
|
221
221
|
</div>
|
|
222
222
|
<CheckInput
|
|
223
223
|
v-for="col in columnOptions" :key="col.key" v-model="visibleColumns"
|
|
@@ -14,6 +14,8 @@ import 'swiper/css/effect-cube'
|
|
|
14
14
|
import 'swiper/css/effect-flip'
|
|
15
15
|
import 'swiper/css/effect-cards'
|
|
16
16
|
|
|
17
|
+
defineOptions({ name: 'BglSwiper' })
|
|
18
|
+
|
|
17
19
|
type SwiperEffect = 'slide' | 'fade' | 'cube' | 'coverflow' | 'flip' | 'cards'
|
|
18
20
|
type SwiperDirection = 'horizontal' | 'vertical'
|
|
19
21
|
type SwiperVariant = 'default' | 'testimonial' | 'gallery' | 'cards' | 'coverflow' | 'hero'
|
|
@@ -505,7 +507,7 @@ defineExpose({
|
|
|
505
507
|
opacity: 0.8;
|
|
506
508
|
}
|
|
507
509
|
|
|
508
|
-
@media screen and (max-width:
|
|
510
|
+
@media screen and (max-width: 910px) {
|
|
509
511
|
.swi-ctrl {
|
|
510
512
|
padding: 0rem 0.5rem;
|
|
511
513
|
}
|
package/src/components/Toast.vue
CHANGED
|
@@ -1,30 +1,45 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { Icon } from '@bagelink/vue'
|
|
3
|
+
import { computed } from 'vue'
|
|
3
4
|
|
|
4
5
|
defineOptions({ name: 'BglToast' })
|
|
5
6
|
|
|
7
|
+
type ToastType = 'success' | 'error' | 'warning' | 'info'
|
|
8
|
+
|
|
6
9
|
interface Props {
|
|
7
10
|
message: string
|
|
8
|
-
type?:
|
|
11
|
+
type?: ToastType
|
|
12
|
+
/** Boolean shorthands: <Toast error message="..." /> */
|
|
13
|
+
info?: boolean
|
|
14
|
+
success?: boolean
|
|
15
|
+
warning?: boolean
|
|
16
|
+
error?: boolean
|
|
9
17
|
showIcon?: boolean
|
|
10
18
|
showCloseButton?: boolean
|
|
11
19
|
closeToast?: () => void
|
|
12
20
|
}
|
|
13
21
|
|
|
14
|
-
withDefaults(defineProps<Props>(), {
|
|
15
|
-
type: 'info',
|
|
22
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
16
23
|
showIcon: true,
|
|
17
24
|
showCloseButton: true,
|
|
18
25
|
})
|
|
26
|
+
|
|
27
|
+
const computedType = computed<ToastType>(() => {
|
|
28
|
+
if (props.type) { return props.type }
|
|
29
|
+
if (props.error) { return 'error' }
|
|
30
|
+
if (props.warning) { return 'warning' }
|
|
31
|
+
if (props.success) { return 'success' }
|
|
32
|
+
return 'info'
|
|
33
|
+
})
|
|
19
34
|
</script>
|
|
20
35
|
|
|
21
36
|
<template>
|
|
22
|
-
<div class="custom-toast" :class="`custom-toast--${
|
|
37
|
+
<div class="custom-toast" :class="`custom-toast--${computedType}`">
|
|
23
38
|
<div v-if="showIcon" class="custom-toast__icon">
|
|
24
|
-
<Icon v-if="
|
|
25
|
-
<Icon v-if="
|
|
26
|
-
<Icon v-if="
|
|
27
|
-
<Icon v-if="
|
|
39
|
+
<Icon v-if="computedType === 'success'" icon="check_circle" />
|
|
40
|
+
<Icon v-if="computedType === 'error'" icon="dangerous" />
|
|
41
|
+
<Icon v-if="computedType === 'warning'" icon="report" />
|
|
42
|
+
<Icon v-if="computedType === 'info'" icon="info" />
|
|
28
43
|
</div>
|
|
29
44
|
<div class="custom-toast__content">
|
|
30
45
|
{{ message }}
|
|
@@ -163,20 +163,20 @@ onMounted(() => {
|
|
|
163
163
|
{{ formatDate(currentDate, 'YYYY') }}
|
|
164
164
|
</h3>
|
|
165
165
|
<div class="ms-auto flex gap-025 m_flex-wrap">
|
|
166
|
-
<TabsNav size="
|
|
166
|
+
<TabsNav size="sm" v-if="viewSwitcher === 'tabs'" :model-value="currentView" :tabs="viewTabs" group="calendar-view" @update:model-value="handleViewChange($event as CalendarView)"
|
|
167
167
|
style="--bgl_tabs-border-radius: calc(var(--bgl-btn-border-radius) / 2)" class="txt12 m_mb-05 m_w-100p " align-txt="center" />
|
|
168
168
|
<Dropdown v-else thin :value="currentView" iconEnd="keyboard_arrow_down" color="gray">
|
|
169
169
|
<ListItem v-for="(_, key) in views" :key="key" thin :title="t(`calendar.views.${key.toLowerCase()}`)" @click="handleViewChange(key)" />
|
|
170
170
|
</Dropdown>
|
|
171
171
|
<div class="flex gap-025">
|
|
172
|
-
<Btn icon="calendar" size="
|
|
172
|
+
<Btn icon="calendar" size="sm" class="txt12" color="gray" :value="t('calendar.today')" @click="handleDateChange(new Date())" />
|
|
173
173
|
<Btn
|
|
174
|
-
icon="chevron_left" color="gray" size="
|
|
174
|
+
icon="chevron_left" color="gray" size="sm" class=""
|
|
175
175
|
:disabled="currentView === 'Agenda'"
|
|
176
176
|
@click="handleDateChange(timeDelta(currentDate, { [viewToDeltaKey[currentView]]: -1 }))"
|
|
177
177
|
/>
|
|
178
178
|
<Btn
|
|
179
|
-
icon="chevron_right" color="gray" size="
|
|
179
|
+
icon="chevron_right" color="gray" size="sm" class=""
|
|
180
180
|
:disabled="currentView === 'Agenda'"
|
|
181
181
|
@click="handleDateChange(timeDelta(currentDate, { [viewToDeltaKey[currentView]]: 1 }))"
|
|
182
182
|
/>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { SetupContext } from 'vue'
|
|
3
3
|
import type { CalendarEvent } from '../CalendarTypes'
|
|
4
|
-
import { formatDate, getI18n } from '@bagelink/vue'
|
|
4
|
+
import { formatDate, getI18n, useDevice } from '@bagelink/vue'
|
|
5
5
|
import { computed, useSlots } from 'vue'
|
|
6
6
|
|
|
7
7
|
interface MonthViewEvent {
|
|
@@ -23,8 +23,8 @@ const emit = defineEmits<{
|
|
|
23
23
|
|
|
24
24
|
const slots: SetupContext['slots'] = useSlots()
|
|
25
25
|
|
|
26
|
-
// Responsive state
|
|
27
|
-
const isMobile =
|
|
26
|
+
// Responsive state (reactive — tracks resize via useDevice)
|
|
27
|
+
const { isMobile } = useDevice()
|
|
28
28
|
|
|
29
29
|
// Calendar data
|
|
30
30
|
const weekDays = computed(() => {
|
|
@@ -117,11 +117,11 @@ function isDigitsFull() {
|
|
|
117
117
|
|
|
118
118
|
<style scoped>
|
|
119
119
|
.otp-error-message {
|
|
120
|
-
color: var(--bgl-red
|
|
120
|
+
color: var(--bgl-red);
|
|
121
121
|
}
|
|
122
122
|
.otp_wrap.has-error input,
|
|
123
123
|
.otp_wrap.has-error textarea {
|
|
124
|
-
outline: 1px solid var(--bgl-red
|
|
124
|
+
outline: 1px solid var(--bgl-red) !important;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
.otp_wrap {
|
|
@@ -457,12 +457,12 @@ onMounted(() => {
|
|
|
457
457
|
}
|
|
458
458
|
|
|
459
459
|
.selectinput.has-error .selectinput-btn {
|
|
460
|
-
border-color: var(--bgl-red
|
|
461
|
-
outline: 1px solid var(--bgl-red
|
|
460
|
+
border-color: var(--bgl-red) !important;
|
|
461
|
+
outline: 1px solid var(--bgl-red) !important;
|
|
462
462
|
}
|
|
463
463
|
|
|
464
464
|
.selectinput.underlined.has-error .selectinput-btn {
|
|
465
|
-
border-color: var(--bgl-red
|
|
465
|
+
border-color: var(--bgl-red) !important;
|
|
466
466
|
}
|
|
467
467
|
|
|
468
468
|
/* Underlined mode styling */
|
|
@@ -399,10 +399,10 @@ onMounted(initializeCountry)
|
|
|
399
399
|
}
|
|
400
400
|
|
|
401
401
|
.bagel-input.has-error .tel-input {
|
|
402
|
-
border: 1px solid var(--bgl-red
|
|
402
|
+
border: 1px solid var(--bgl-red) !important;
|
|
403
403
|
}
|
|
404
404
|
.bagel-input.has-error .tel-input {
|
|
405
|
-
--bgl-border-color: var(--bgl-red
|
|
405
|
+
--bgl-border-color: var(--bgl-red) !important;
|
|
406
406
|
}
|
|
407
407
|
|
|
408
408
|
@keyframes highlight-country {
|
|
@@ -266,7 +266,7 @@
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
.bagel-input.has-error .fileUploadWrap {
|
|
269
|
-
outline-color: var(--bgl-red
|
|
269
|
+
outline-color: var(--bgl-red);
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
/* ─── Variant: frame ─────────────────────────────────────────────────────── */
|
|
@@ -285,5 +285,5 @@
|
|
|
285
285
|
}
|
|
286
286
|
|
|
287
287
|
.bagel-input.has-error :deep(.bgl-card) {
|
|
288
|
-
border-color: var(--bgl-red
|
|
288
|
+
border-color: var(--bgl-red);
|
|
289
289
|
}
|
package/src/components/index.ts
CHANGED
|
@@ -6,11 +6,12 @@ export { default as Alert } from './Alert.vue'
|
|
|
6
6
|
export * from './analytics'
|
|
7
7
|
export { default as Avatar } from './Avatar.vue'
|
|
8
8
|
export { default as Badge } from './Badge.vue'
|
|
9
|
+
/** @deprecated Renamed to Badge. Pill is an alias that will be removed in a future version. */
|
|
10
|
+
export { default as Pill } from './Badge.vue'
|
|
9
11
|
export { default as BglVideo } from './BglVideo.vue'
|
|
10
12
|
export { default as Btn } from './Btn.vue'
|
|
11
13
|
export { default as Calendar } from './calendar/Index.vue'
|
|
12
14
|
export { default as Card } from './Card.vue'
|
|
13
|
-
export { default as Carousel } from './Carousel.vue'
|
|
14
15
|
export { default as DataPreview } from './DataPreview.vue'
|
|
15
16
|
export { default as DataTable } from './dataTable/DataTable.vue'
|
|
16
17
|
/** @deprecated Use DataTable instead. TableSchema is an alias that will be removed in a future version. */
|
|
@@ -27,7 +28,6 @@ export { default as Icon } from './Icon/Icon.vue'
|
|
|
27
28
|
export { FONT_AWESOME_ICONS, FONT_AWESOME_BRANDS_ICONS, MATERIAL_ICONS } from './Icon/constants'
|
|
28
29
|
export { default as IframeVue } from './IframeVue.vue'
|
|
29
30
|
export { default as Image } from './Image.vue'
|
|
30
|
-
export { default as ImportData } from './ImportData.vue'
|
|
31
31
|
export * from './layout'
|
|
32
32
|
export { default as ListItem } from './ListItem.vue'
|
|
33
33
|
export { default as ListView } from './ListView.vue'
|
|
@@ -37,13 +37,9 @@ export { default as Menu } from './Menu.vue'
|
|
|
37
37
|
export { default as NavBar } from './NavBar.vue'
|
|
38
38
|
export { default as PageTitle } from './PageTitle.vue'
|
|
39
39
|
export { default as Pagination } from './Pagination.vue'
|
|
40
|
-
export { default as Pill } from './Pill.vue'
|
|
41
40
|
export { default as Rating } from './Rating.vue'
|
|
42
41
|
export { default as RouterWrapper } from './RouterWrapper.vue'
|
|
43
|
-
export { default as Slider } from './Slider.vue'
|
|
44
42
|
export { default as Spreadsheet } from './Spreadsheet/Index.vue'
|
|
45
43
|
export { default as Swiper } from './Swiper.vue'
|
|
46
|
-
export { default as Title } from './Title.vue'
|
|
47
|
-
export { default as ToolBar } from './ToolBar.vue'
|
|
48
44
|
export { default as TopBar } from './TopBar.vue'
|
|
49
45
|
export { default as Zoomer } from './Zoomer.vue'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import { Btn, PageTitle } from '@bagelink/vue'
|
|
3
|
-
import {
|
|
3
|
+
import { useAppLayout } from './appLayoutContext'
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
title?: string
|
|
@@ -14,28 +14,13 @@ withDefaults(defineProps<Props>(), {
|
|
|
14
14
|
border: true,
|
|
15
15
|
})
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
const menuState = inject('menuState', {
|
|
19
|
-
isOpen: { value: true },
|
|
20
|
-
isMobile: { value: false },
|
|
21
|
-
toggleMenu: () => { },
|
|
22
|
-
}) as {
|
|
23
|
-
isOpen: { value: boolean }
|
|
24
|
-
isMobile: { value: boolean }
|
|
25
|
-
toggleMenu: () => void
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Inject sidebar card style state
|
|
29
|
-
const sidebarCardStyle = inject('sidebarCardStyle', { value: false })
|
|
30
|
-
|
|
31
|
-
// Computed property to check if sidebar has card style
|
|
32
|
-
const hasSidebarCard = computed(() => sidebarCardStyle?.value ?? false)
|
|
17
|
+
const { isOpen, toggleMenu, sidebarCardStyle } = useAppLayout()
|
|
33
18
|
</script>
|
|
34
19
|
|
|
35
20
|
<template>
|
|
36
21
|
<div
|
|
37
22
|
class="app-content h-100p flex column" :class="{
|
|
38
|
-
paddingAppContent:
|
|
23
|
+
paddingAppContent: sidebarCardStyle,
|
|
39
24
|
}"
|
|
40
25
|
>
|
|
41
26
|
<!-- Header -->
|
|
@@ -48,7 +33,8 @@ const hasSidebarCard = computed(() => sidebarCardStyle?.value ?? false)
|
|
|
48
33
|
<!-- Menu Toggle Button -->
|
|
49
34
|
<Btn
|
|
50
35
|
v-if="showMenuButton" flat icon="dock_to_right" class="menuToggleButton"
|
|
51
|
-
|
|
36
|
+
:aria-expanded="isOpen" aria-controls="bgl-app-sidebar" aria-label="Toggle sidebar"
|
|
37
|
+
@click="toggleMenu"
|
|
52
38
|
/>
|
|
53
39
|
|
|
54
40
|
<!-- Back Button -->
|
|
@@ -1,35 +1,69 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import { ref, provide, onMounted, onUnmounted, computed } from 'vue'
|
|
2
|
+
import { ref, provide, onMounted, onUnmounted, computed, watch } from 'vue'
|
|
3
|
+
import { useEscapeKey } from '../../composables/useEscapeKey'
|
|
4
|
+
import { MOBILE_BREAKPOINT } from '../../utils/constants'
|
|
5
|
+
import { AppLayoutKey, SIDEBAR_COLLAPSED_WIDTH, SIDEBAR_COLLAPSED_WIDTH_CARD } from './appLayoutContext'
|
|
3
6
|
|
|
4
7
|
interface Props {
|
|
5
8
|
sidebarWidth?: string
|
|
6
9
|
sidebarCardStyle?: boolean
|
|
7
10
|
defaultOpen?: boolean
|
|
11
|
+
/** v-model:open — controlled sidebar state */
|
|
12
|
+
open?: boolean
|
|
13
|
+
/** Persist sidebar state to localStorage (desktop only). Default true. */
|
|
14
|
+
persist?: boolean
|
|
15
|
+
storageKey?: string
|
|
8
16
|
}
|
|
9
17
|
|
|
10
18
|
const props = withDefaults(defineProps<Props>(), {
|
|
11
19
|
sidebarWidth: '260px',
|
|
12
20
|
sidebarCardStyle: false,
|
|
13
21
|
defaultOpen: true,
|
|
22
|
+
open: undefined,
|
|
23
|
+
persist: true,
|
|
24
|
+
storageKey: 'bgl-sidebar-open',
|
|
14
25
|
})
|
|
15
26
|
|
|
16
|
-
|
|
17
|
-
|
|
27
|
+
const emit = defineEmits<{ 'update:open': [value: boolean] }>()
|
|
28
|
+
|
|
29
|
+
function readStored(): boolean | undefined {
|
|
30
|
+
if (!props.persist || typeof localStorage === 'undefined') { return undefined }
|
|
31
|
+
const raw = localStorage.getItem(props.storageKey)
|
|
32
|
+
return raw === null ? undefined : raw === 'true'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const isOpen = ref(props.open ?? readStored() ?? props.defaultOpen)
|
|
18
36
|
const isMobile = ref(false)
|
|
19
37
|
|
|
20
|
-
|
|
38
|
+
const sidebarCollapsedWidth = props.sidebarCardStyle ? SIDEBAR_COLLAPSED_WIDTH_CARD : SIDEBAR_COLLAPSED_WIDTH
|
|
39
|
+
|
|
40
|
+
// Sync controlled prop → internal state
|
|
41
|
+
watch(() => props.open, (v) => {
|
|
42
|
+
if (v !== undefined) { isOpen.value = v }
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
watch(isOpen, (v) => {
|
|
46
|
+
emit('update:open', v)
|
|
47
|
+
// Persist only explicit desktop state — never the forced mobile collapse
|
|
48
|
+
if (props.persist && !isMobile.value && typeof localStorage !== 'undefined') {
|
|
49
|
+
localStorage.setItem(props.storageKey, String(v))
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
21
53
|
function checkMobile() {
|
|
22
|
-
isMobile.value = window.innerWidth
|
|
54
|
+
isMobile.value = window.innerWidth <= MOBILE_BREAKPOINT
|
|
23
55
|
if (isMobile.value) {
|
|
24
56
|
isOpen.value = false
|
|
25
57
|
}
|
|
26
58
|
}
|
|
27
59
|
|
|
28
|
-
// Toggle menu
|
|
29
60
|
function toggleMenu() {
|
|
30
61
|
isOpen.value = !isOpen.value
|
|
31
62
|
}
|
|
32
63
|
|
|
64
|
+
// Esc closes the sidebar overlay on mobile
|
|
65
|
+
useEscapeKey(() => { isOpen.value = false }, () => isMobile.value && isOpen.value)
|
|
66
|
+
|
|
33
67
|
// Close menu on mobile when clicking outside
|
|
34
68
|
function closeOnMobile() {
|
|
35
69
|
if (isMobile.value) {
|
|
@@ -42,26 +76,21 @@ const mainContentStyles = computed(() => {
|
|
|
42
76
|
if (isMobile.value) {
|
|
43
77
|
return { marginInlineStart: '0' }
|
|
44
78
|
}
|
|
45
|
-
const collapsedWidth = props.sidebarCardStyle ? '82px' : '66px'
|
|
46
79
|
return {
|
|
47
|
-
marginInlineStart: isOpen.value ? props.sidebarWidth :
|
|
80
|
+
marginInlineStart: isOpen.value ? props.sidebarWidth : sidebarCollapsedWidth
|
|
48
81
|
}
|
|
49
82
|
})
|
|
50
83
|
|
|
51
|
-
|
|
52
|
-
provide('menuState', {
|
|
84
|
+
provide(AppLayoutKey, {
|
|
53
85
|
isOpen,
|
|
54
86
|
isMobile,
|
|
55
87
|
toggleMenu,
|
|
56
88
|
closeOnMobile,
|
|
57
89
|
sidebarWidth: props.sidebarWidth,
|
|
58
|
-
sidebarCollapsedWidth
|
|
90
|
+
sidebarCollapsedWidth,
|
|
91
|
+
sidebarCardStyle: props.sidebarCardStyle,
|
|
59
92
|
})
|
|
60
93
|
|
|
61
|
-
// Provide sidebar card style based on prop
|
|
62
|
-
provide('sidebarCardStyle', { value: props.sidebarCardStyle })
|
|
63
|
-
|
|
64
|
-
// Initialize
|
|
65
94
|
onMounted(() => {
|
|
66
95
|
checkMobile()
|
|
67
96
|
window.addEventListener('resize', checkMobile)
|
|
@@ -82,7 +111,7 @@ onUnmounted(() => {
|
|
|
82
111
|
/>
|
|
83
112
|
|
|
84
113
|
<!-- Sidebar Slot -->
|
|
85
|
-
<slot name="sidebar" />
|
|
114
|
+
<slot name="sidebar" v-bind="{ isOpen, isMobile, toggleMenu, closeOnMobile }" />
|
|
86
115
|
|
|
87
116
|
<!-- Main Content Area -->
|
|
88
117
|
<main
|
|
@@ -90,11 +119,11 @@ onUnmounted(() => {
|
|
|
90
119
|
:style="mainContentStyles"
|
|
91
120
|
>
|
|
92
121
|
<!-- Header Slot -->
|
|
93
|
-
<slot name="header" />
|
|
122
|
+
<slot name="header" v-bind="{ isOpen, isMobile, toggleMenu, closeOnMobile }" />
|
|
94
123
|
|
|
95
124
|
<!-- Page Content -->
|
|
96
125
|
<div class="page-content overflow w-100p h-100p">
|
|
97
|
-
<slot />
|
|
126
|
+
<slot v-bind="{ isOpen, isMobile, toggleMenu, closeOnMobile }" />
|
|
98
127
|
</div>
|
|
99
128
|
</main>
|
|
100
129
|
</div>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import type { NavLink } from '@bagelink/vue'
|
|
3
3
|
import { Btn, Icon } from '@bagelink/vue'
|
|
4
|
-
import {
|
|
4
|
+
import { computed, ref, watch } from 'vue'
|
|
5
5
|
import { useRoute } from 'vue-router'
|
|
6
6
|
import { resolveI18n } from '../../i18n'
|
|
7
|
+
import { useAppLayout, SIDEBAR_COLLAPSED_WIDTH, SIDEBAR_COLLAPSED_WIDTH_CARD } from './appLayoutContext'
|
|
7
8
|
|
|
8
9
|
// Extended interface for links with active route tracking
|
|
9
10
|
interface LinkWithAction extends NavLink {
|
|
@@ -24,13 +25,11 @@ interface Props {
|
|
|
24
25
|
frame?: boolean
|
|
25
26
|
activeRoutes?: string[]
|
|
26
27
|
centerlinks?: boolean
|
|
27
|
-
defaultOpen?: boolean
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const props = withDefaults(defineProps<Props>(), {
|
|
31
31
|
card: true,
|
|
32
32
|
centerlinks: false,
|
|
33
|
-
defaultOpen: true,
|
|
34
33
|
bgColor: 'var(--bgl-white)',
|
|
35
34
|
textColor: 'var(--bgl-black)',
|
|
36
35
|
activeColor: 'var(--bgl-black)',
|
|
@@ -43,32 +42,16 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
43
42
|
const route = useRoute()
|
|
44
43
|
const isTransitioning = ref(false)
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
const _fallbackIsOpen = ref(props.defaultOpen)
|
|
48
|
-
const _fallbackIsMobile = ref(false)
|
|
49
|
-
|
|
50
|
-
const menuState = inject('menuState', {
|
|
51
|
-
isOpen: _fallbackIsOpen,
|
|
52
|
-
isMobile: _fallbackIsMobile,
|
|
53
|
-
closeOnMobile: () => void 0,
|
|
54
|
-
sidebarWidth: '260px',
|
|
55
|
-
sidebarCollapsedWidth: '66px',
|
|
56
|
-
}) as {
|
|
57
|
-
isOpen: { value: boolean }
|
|
58
|
-
isMobile: { value: boolean }
|
|
59
|
-
closeOnMobile: () => void
|
|
60
|
-
sidebarWidth: string
|
|
61
|
-
sidebarCollapsedWidth: string
|
|
62
|
-
}
|
|
45
|
+
const { isOpen, isMobile, sidebarWidth, toggleMenu, closeOnMobile } = useAppLayout()
|
|
63
46
|
|
|
64
47
|
// visually "open" during transition — prevents logo/padding from jumping immediately
|
|
65
|
-
const isVisuallyOpen = computed(() =>
|
|
48
|
+
const isVisuallyOpen = computed(() => isOpen.value || isTransitioning.value)
|
|
66
49
|
|
|
67
50
|
// Watch for changes in menu state to handle transitioning
|
|
68
51
|
watch(
|
|
69
|
-
() =>
|
|
52
|
+
() => isOpen.value,
|
|
70
53
|
() => {
|
|
71
|
-
if (!
|
|
54
|
+
if (!isMobile.value) {
|
|
72
55
|
isTransitioning.value = true
|
|
73
56
|
// Reset after transition completes
|
|
74
57
|
setTimeout(() => {
|
|
@@ -134,9 +117,9 @@ function isActiveRoute(link: LinkWithAction): boolean {
|
|
|
134
117
|
const sidebarStyles = computed(() => {
|
|
135
118
|
let width = '280px'
|
|
136
119
|
|
|
137
|
-
if (!
|
|
138
|
-
const collapsedWidth = props.card ?
|
|
139
|
-
width =
|
|
120
|
+
if (!isMobile.value) {
|
|
121
|
+
const collapsedWidth = props.card ? SIDEBAR_COLLAPSED_WIDTH_CARD : SIDEBAR_COLLAPSED_WIDTH
|
|
122
|
+
width = isOpen.value ? sidebarWidth : collapsedWidth
|
|
140
123
|
}
|
|
141
124
|
|
|
142
125
|
return {
|
|
@@ -147,13 +130,13 @@ const sidebarStyles = computed(() => {
|
|
|
147
130
|
|
|
148
131
|
<template>
|
|
149
132
|
<aside
|
|
133
|
+
id="bgl-app-sidebar"
|
|
150
134
|
class="app-sidebar transition-400 fixed start top bottom h-100vh z-99" :class="{
|
|
151
|
-
'sidebar-mobile-open':
|
|
152
|
-
'sidebar-mobile-closed':
|
|
153
|
-
menuState.isMobile.value && !menuState.isOpen.value,
|
|
135
|
+
'sidebar-mobile-open': isMobile && isOpen,
|
|
136
|
+
'sidebar-mobile-closed': isMobile && !isOpen,
|
|
154
137
|
'transitioning': isTransitioning,
|
|
155
138
|
'p-05': props.card,
|
|
156
|
-
'sidebar-collapsed': !
|
|
139
|
+
'sidebar-collapsed': !isMobile && !isOpen,
|
|
157
140
|
}" :style="sidebarStyles"
|
|
158
141
|
>
|
|
159
142
|
<div
|
|
@@ -186,7 +169,7 @@ const sidebarStyles = computed(() => {
|
|
|
186
169
|
<nav class="sidebar-nav flex column flex-stretch gap-025 align-items-start" :class="{ 'justify-content-center': props.centerlinks }">
|
|
187
170
|
<Btn
|
|
188
171
|
v-for="link in props.navLinks" :key="link.to"
|
|
189
|
-
:title="!
|
|
172
|
+
:title="!isOpen && !isMobile ? resolveI18n(link.label) : ''" fullWidth
|
|
190
173
|
alignTxt="start" class="flex-shrink-0 px-075" :class="{ 'nav-btn-active': isActiveRoute(link) }"
|
|
191
174
|
:style="{
|
|
192
175
|
backgroundColor: isActiveRoute(link) ? props.activeColor : props.bgColor,
|
|
@@ -205,7 +188,7 @@ const sidebarStyles = computed(() => {
|
|
|
205
188
|
<!-- Footer Links -->
|
|
206
189
|
<Btn
|
|
207
190
|
v-for="link in props.footerLinks" :key="link.to || link.label"
|
|
208
|
-
:title="!
|
|
191
|
+
:title="!isOpen && !isMobile ? resolveI18n(link.label) : ''"
|
|
209
192
|
:style="{
|
|
210
193
|
backgroundColor: isActiveRoute(link) ? props.activeColor : props.bgColor,
|
|
211
194
|
color: isActiveRoute(link) ? 'white' : props.textColor,
|
|
@@ -219,7 +202,7 @@ const sidebarStyles = computed(() => {
|
|
|
219
202
|
</span>
|
|
220
203
|
</Btn>
|
|
221
204
|
<!-- Custom Footer Content Slot -->
|
|
222
|
-
<slot name="footer" />
|
|
205
|
+
<slot name="footer" v-bind="{ isOpen, isMobile, toggleMenu, closeOnMobile }" />
|
|
223
206
|
</div>
|
|
224
207
|
</div>
|
|
225
208
|
</aside>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import { computed, onMounted, onUnmounted, provide, ref } from 'vue'
|
|
3
3
|
import { RESIZABLE_KEY, useResizableLayoutProvider } from '../../composables/useResizableLayout'
|
|
4
|
+
import { MOBILE_BREAKPOINT } from '../../utils/constants'
|
|
5
|
+
|
|
6
|
+
defineOptions({ name: 'BglResizable' })
|
|
4
7
|
|
|
5
8
|
interface Props {
|
|
6
9
|
/** Lay panels side-by-side. Default: vertical (stacked). */
|
|
@@ -11,14 +14,14 @@ interface Props {
|
|
|
11
14
|
* Set `:breakpoint="0"` to disable responsive switching entirely.
|
|
12
15
|
*/
|
|
13
16
|
mobileVertical?: boolean
|
|
14
|
-
/** Width (px) below which the mobile layout activates. @default
|
|
17
|
+
/** Width (px) below which the mobile layout activates. @default MOBILE_BREAKPOINT (910) */
|
|
15
18
|
breakpoint?: number
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
const props = withDefaults(defineProps<Props>(), {
|
|
19
22
|
horizontal: false,
|
|
20
23
|
mobileVertical: true,
|
|
21
|
-
breakpoint:
|
|
24
|
+
breakpoint: MOBILE_BREAKPOINT,
|
|
22
25
|
})
|
|
23
26
|
|
|
24
27
|
const containerEl = ref<HTMLElement>()
|