@bagelink/vue 1.4.163 → 1.4.167
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/analytics/BarChart.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectBtn.vue.d.ts +24 -0
- package/dist/components/form/inputs/SelectBtn.vue.d.ts.map +1 -0
- package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts +8 -2
- package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/index.d.ts +1 -0
- package/dist/components/form/inputs/index.d.ts.map +1 -1
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
- package/dist/index.cjs +20 -20
- package/dist/index.mjs +20 -20
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/analytics/BarChart.vue +23 -27
- package/src/components/form/inputs/SelectBtn.vue +72 -0
- package/src/components/form/inputs/Upload/UploadInput.vue +4 -2
- package/src/components/form/inputs/index.ts +1 -0
- package/src/components/layout/AppSidebar.vue +63 -62
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { Icon, Loading } from '@bagelink/vue'
|
|
2
|
+
import { formatDate, Icon, Loading } from '@bagelink/vue'
|
|
3
3
|
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
|
4
4
|
|
|
5
5
|
interface SecondaryValue {
|
|
@@ -61,11 +61,10 @@ const chartData = computed(() => {
|
|
|
61
61
|
|
|
62
62
|
// Use all data without limiting to maxBars
|
|
63
63
|
const maxValue = Math.max(...props.data.map(d => d.value), 1)
|
|
64
|
-
|
|
65
64
|
return props.data.map(item => ({
|
|
66
65
|
...item,
|
|
67
66
|
height: Math.max((item.value / maxValue) * 100, 2), // Minimum height of 2%
|
|
68
|
-
displayLabel: formatDate(item.date)
|
|
67
|
+
displayLabel: formatDate(item.date, 'MMM')
|
|
69
68
|
}))
|
|
70
69
|
})
|
|
71
70
|
|
|
@@ -87,7 +86,7 @@ const getBarOpacity = computed(() => {
|
|
|
87
86
|
|
|
88
87
|
// Animation functions
|
|
89
88
|
function easeOutCubic(t: number): number {
|
|
90
|
-
return 1 -
|
|
89
|
+
return 1 - (1 - t) ** 3
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
function startAnimation() {
|
|
@@ -122,7 +121,7 @@ function setupIntersectionObserver() {
|
|
|
122
121
|
|
|
123
122
|
observer.value = new IntersectionObserver(
|
|
124
123
|
(entries) => {
|
|
125
|
-
entries.forEach(entry => {
|
|
124
|
+
entries.forEach((entry) => {
|
|
126
125
|
if (entry.isIntersecting && !isInView.value) {
|
|
127
126
|
console.log(`👀 TrendChart: Entered viewport, starting animation in ${props.animationStartDelay}ms`)
|
|
128
127
|
isInView.value = true
|
|
@@ -157,14 +156,6 @@ onUnmounted(() => {
|
|
|
157
156
|
}
|
|
158
157
|
})
|
|
159
158
|
|
|
160
|
-
function formatDate(dateStr: string): string {
|
|
161
|
-
const date = new Date(dateStr)
|
|
162
|
-
return date.toLocaleDateString('he-IL', {
|
|
163
|
-
month: 'short',
|
|
164
|
-
day: 'numeric'
|
|
165
|
-
})
|
|
166
|
-
}
|
|
167
|
-
|
|
168
159
|
function formatValue(value: number, isCurrency: boolean = false): string {
|
|
169
160
|
if (isCurrency) {
|
|
170
161
|
return new Intl.NumberFormat('he-IL', {
|
|
@@ -179,8 +170,7 @@ function formatValue(value: number, isCurrency: boolean = false): string {
|
|
|
179
170
|
function formatTooltip(item: any): string {
|
|
180
171
|
const primaryValue = formatValue(item.value, props.currency)
|
|
181
172
|
const primaryText = `${props.prefix}${primaryValue}${props.suffix}`
|
|
182
|
-
|
|
183
|
-
let tooltipLines = [`${item.displayLabel}`, `<b>${primaryText}</b>`]
|
|
173
|
+
const tooltipLines = [`${item.displayLabel}`, `<b>${primaryText}</b>`]
|
|
184
174
|
|
|
185
175
|
if (item.secondaryValues && Array.isArray(item.secondaryValues)) {
|
|
186
176
|
item.secondaryValues.forEach((secondary: SecondaryValue) => {
|
|
@@ -204,27 +194,29 @@ function formatTooltip(item: any): string {
|
|
|
204
194
|
</p>
|
|
205
195
|
</div>
|
|
206
196
|
<div v-if="percentageChange !== 0" class="flex align-center gap-025">
|
|
207
|
-
<Icon
|
|
197
|
+
<Icon
|
|
198
|
+
:name="percentageChange > 0 ? 'trending_up' : 'trending_down'" size="1"
|
|
199
|
+
:class="percentageChange > 0 ? 'color-success' : 'color-danger'"
|
|
200
|
+
/>
|
|
208
201
|
<span class="txt12 bold" :class="percentageChange > 0 ? 'color-success' : 'color-danger'">
|
|
209
202
|
{{ Math.abs(percentageChange) }}%
|
|
210
203
|
</span>
|
|
211
204
|
</div>
|
|
212
205
|
</div>
|
|
213
|
-
<div
|
|
206
|
+
<div
|
|
207
|
+
class="flex w-100p align-items-end mt-auto gap-075 overflow justify-content-start"
|
|
208
|
+
:class="[rtl ? 'rtl' : 'ltr']"
|
|
209
|
+
>
|
|
214
210
|
<div
|
|
215
|
-
v-for="(bar, index) in chartData"
|
|
216
|
-
:
|
|
217
|
-
v-tooltip="{ content: formatTooltip(bar), html: true }"
|
|
218
|
-
class="flex-grow txt-center hover transition-400 relative barWrap mb-1"
|
|
219
|
-
:style="{
|
|
211
|
+
v-for="(bar, index) in chartData" :key="index" v-tooltip="{ content: formatTooltip(bar), html: true }"
|
|
212
|
+
class="flex-grow txt-center hover transition-400 relative barWrap mb-1" :style="{
|
|
220
213
|
width: `max(2rem, ${100 / chartData.length}%)`,
|
|
221
214
|
opacity: getBarOpacity(index),
|
|
222
|
-
transition: animated ? 'opacity 0.3s ease-out' : 'none'
|
|
215
|
+
transition: animated ? 'opacity 0.3s ease-out' : 'none',
|
|
223
216
|
}"
|
|
224
217
|
>
|
|
225
218
|
<div
|
|
226
|
-
class="bar radius-05 transition-400 "
|
|
227
|
-
:style="{
|
|
219
|
+
class="bar radius-05 transition-400 " :style="{
|
|
228
220
|
height: `${bar.height * 1.8}px`,
|
|
229
221
|
background: `linear-gradient(180deg, ${color}60, ${color}30)`,
|
|
230
222
|
minHeight: '4px',
|
|
@@ -232,7 +224,8 @@ function formatTooltip(item: any): string {
|
|
|
232
224
|
/>
|
|
233
225
|
<span
|
|
234
226
|
v-if="chartData.length <= 15 || index % Math.ceil(chartData.length / 8) === 0"
|
|
235
|
-
class="txt-9 block line-height-1 -bottom-075 white-space color-gray absolute
|
|
227
|
+
class="txt-9 block line-height-1 -bottom-075 white-space color-gray absolute"
|
|
228
|
+
:class="rtl ? 'rtl' : 'ltr'"
|
|
236
229
|
>
|
|
237
230
|
{{ bar.displayLabel }}
|
|
238
231
|
</span>
|
|
@@ -249,16 +242,19 @@ function formatTooltip(item: any): string {
|
|
|
249
242
|
font-weight: 300 !important;
|
|
250
243
|
font-size: 12px;
|
|
251
244
|
}
|
|
245
|
+
|
|
252
246
|
.trendTooltip {
|
|
253
247
|
font-weight: 700 !important;
|
|
254
248
|
}
|
|
249
|
+
|
|
255
250
|
.v-popper--theme-tooltip .v-popper__inner:has(.trendTooltip) {
|
|
256
251
|
background-color: var(--bgl-black) !important;
|
|
257
252
|
color: var(--bgl-white) !important;
|
|
258
253
|
border-radius: 0.5rem !important;
|
|
259
254
|
padding: 0.25rem 0.5rem !important;
|
|
260
255
|
}
|
|
261
|
-
|
|
256
|
+
|
|
257
|
+
.bar {
|
|
262
258
|
min-width: 2rem;
|
|
263
259
|
}
|
|
264
260
|
</style>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Btn, Icon } from '@bagelink/vue'
|
|
3
|
+
|
|
4
|
+
interface Option {
|
|
5
|
+
label: string
|
|
6
|
+
value: string | number
|
|
7
|
+
icon?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
options: Option[]
|
|
12
|
+
thin?: boolean
|
|
13
|
+
outline?: boolean
|
|
14
|
+
multiselect?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<Props>(), { multiselect: false })
|
|
18
|
+
|
|
19
|
+
// When multiselect is true: array, when false: single value or null
|
|
20
|
+
const selected = defineModel<string | number | (string | number)[] | null>('modelValue', { default: null })
|
|
21
|
+
|
|
22
|
+
function toggleOption(value: string | number) {
|
|
23
|
+
if (props.multiselect) {
|
|
24
|
+
// Multiselect mode - work with arrays
|
|
25
|
+
const currentValue = Array.isArray(selected.value) ? selected.value : []
|
|
26
|
+
const index = currentValue.indexOf(value)
|
|
27
|
+
if (index > -1) {
|
|
28
|
+
selected.value = currentValue.filter(v => v !== value)
|
|
29
|
+
} else {
|
|
30
|
+
selected.value = [...currentValue, value]
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
// Single select mode - work with single values
|
|
34
|
+
if (selected.value === value) {
|
|
35
|
+
selected.value = null
|
|
36
|
+
} else {
|
|
37
|
+
selected.value = value
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isSelected(value: string | number) {
|
|
43
|
+
if (Array.isArray(selected.value)) {
|
|
44
|
+
return selected.value.includes(value)
|
|
45
|
+
}
|
|
46
|
+
return selected.value === value
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<template>
|
|
51
|
+
<div class="flex flex-wrap gap-05 SelectBtn">
|
|
52
|
+
<Btn
|
|
53
|
+
v-for="option in options"
|
|
54
|
+
:key="option.value"
|
|
55
|
+
:thin="thin"
|
|
56
|
+
:outline="outline"
|
|
57
|
+
:class="isSelected(option.value) ? 'primary' : 'bg-transparent color-black'"
|
|
58
|
+
type="button"
|
|
59
|
+
class="border px-075 radius-2"
|
|
60
|
+
@click="toggleOption(option.value)"
|
|
61
|
+
>
|
|
62
|
+
<Icon v-if="option.icon" :name="option.icon" weight="300" />
|
|
63
|
+
<p>{{ option.label }}</p>
|
|
64
|
+
</Btn>
|
|
65
|
+
</div>
|
|
66
|
+
</template>
|
|
67
|
+
|
|
68
|
+
<style scoped>
|
|
69
|
+
.SelectBtn {
|
|
70
|
+
--btn-height: 30px;
|
|
71
|
+
}
|
|
72
|
+
</style>
|
|
@@ -5,10 +5,12 @@ import { watch, ref } from 'vue'
|
|
|
5
5
|
|
|
6
6
|
import { useFileUpload } from './useFileUpload'
|
|
7
7
|
|
|
8
|
-
const props = withDefaults(defineProps<UploadInputProps>(), {
|
|
8
|
+
const props = withDefaults(defineProps<UploadInputProps & { showIcon?: boolean; icon?: string }>(), {
|
|
9
9
|
height: '215px',
|
|
10
10
|
theme: 'dropzone',
|
|
11
11
|
accept: '*',
|
|
12
|
+
showIcon: true,
|
|
13
|
+
icon: 'upload_2',
|
|
12
14
|
})
|
|
13
15
|
|
|
14
16
|
const emit = defineEmits(['update:modelValue', 'addFileStart'])
|
|
@@ -160,7 +162,7 @@ function fileName(pathKey: string) {
|
|
|
160
162
|
|
|
161
163
|
<slot v-if="(!pathKeys.length && !fileQueue.length) || multiple" name="placeholder" :files="pathKeys" :fileQueue :browse>
|
|
162
164
|
<p class="p-1 flex column hover fileUploadPlaceHolder justify-content-center mb-05 ">
|
|
163
|
-
<Icon
|
|
165
|
+
<Icon v-if="showIcon" :name="icon" class="user-select-none" />
|
|
164
166
|
<span class=" pretty balance user-select-none ">
|
|
165
167
|
{{ dropPlaceholder || 'Drag and Drop files here or click to upload' }}
|
|
166
168
|
</span>
|
|
@@ -13,6 +13,7 @@ export { default as RadioGroup } from './RadioGroup.vue'
|
|
|
13
13
|
export { default as RadioPillsInput } from './RadioPillsInput.vue'
|
|
14
14
|
export { default as RangeInput } from './RangeInput.vue'
|
|
15
15
|
export { default as RichText } from './RichText/index.vue'
|
|
16
|
+
export { default as SelectBtn } from './SelectBtn.vue'
|
|
16
17
|
export { default as SelectInput } from './SelectInput.vue'
|
|
17
18
|
export { default as SignaturePad } from './SignaturePad.vue'
|
|
18
19
|
export { default as TableField } from './TableField.vue'
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
+
import type { NavLink } from '@bagelink/vue'
|
|
3
|
+
import { Btn, Icon } from '@bagelink/vue'
|
|
2
4
|
import { inject, computed, ref, watch } from 'vue'
|
|
3
5
|
import { useRoute } from 'vue-router'
|
|
4
|
-
import { Btn, Icon } from '@bagelink/vue'
|
|
5
|
-
import type { NavLink } from '@bagelink/vue'
|
|
6
6
|
|
|
7
7
|
// Extended interface for footer links that can have actions
|
|
8
8
|
interface FooterLink extends NavLink {
|
|
@@ -82,47 +82,38 @@ function logout() {
|
|
|
82
82
|
|
|
83
83
|
<template>
|
|
84
84
|
<aside
|
|
85
|
-
class="app-sidebar transition-400 fixed
|
|
86
|
-
:class="{
|
|
85
|
+
class="app-sidebar transition-400 fixed start top bottom h-100vh z-99" :class="{
|
|
87
86
|
'sidebar-mobile-open': menuState.isMobile.value && menuState.isOpen.value,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}"
|
|
94
|
-
:style="sidebarStyles"
|
|
87
|
+
'sidebar-mobile-closed':
|
|
88
|
+
menuState.isMobile.value && !menuState.isOpen.value,
|
|
89
|
+
'transitioning': isTransitioning,
|
|
90
|
+
'p-05': props.card,
|
|
91
|
+
'sidebar-collapsed': !menuState.isMobile.value && !menuState.isOpen.value,
|
|
92
|
+
}" :style="sidebarStyles"
|
|
95
93
|
>
|
|
96
94
|
<div
|
|
97
95
|
:style="{
|
|
98
96
|
backgroundColor: props.bgColor,
|
|
99
97
|
color: props.textColor,
|
|
100
|
-
|
|
101
|
-
}"
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
'ps-05': !menuState.isOpen.value,
|
|
98
|
+
...(props.card && { borderRadius: 'var(--card-border-radius)' }),
|
|
99
|
+
}" :class="{
|
|
100
|
+
'card cardWrapSide': props.card,
|
|
101
|
+
'ps-05': !menuState.isOpen.value,
|
|
105
102
|
'scrollbar-gutter-both': menuState.isOpen.value,
|
|
106
|
-
|
|
107
|
-
}"
|
|
108
|
-
class="overflow-hidden flex column flex-stretch gap-1 w100p pt-1 pb-05 h-100p"
|
|
103
|
+
'aside_frame': props.frame,
|
|
104
|
+
}" class="overflow-hidden flex column flex-stretch gap-1 w100p pt-1 pb-05 h-100p"
|
|
109
105
|
>
|
|
110
106
|
<!-- Logo/Brand -->
|
|
111
107
|
<router-link
|
|
112
|
-
to="/"
|
|
113
|
-
|
|
114
|
-
:class="{
|
|
115
|
-
'gap-05': menuState.isOpen.value,
|
|
108
|
+
to="/" class="decoration-none flex px-05" :class="{
|
|
109
|
+
'gap-05': menuState.isOpen.value,
|
|
116
110
|
'gap-0': !menuState.isOpen.value,
|
|
117
111
|
}"
|
|
118
112
|
>
|
|
119
113
|
<img
|
|
120
|
-
v-if="props.logo"
|
|
121
|
-
:src="props.logo"
|
|
122
|
-
:alt="props.logoAlt"
|
|
123
|
-
class="contain"
|
|
114
|
+
v-if="props.logo" :src="props.logo" :alt="props.logoAlt" class="contain"
|
|
124
115
|
:style="{ height: props.logoHeight }"
|
|
125
|
-
|
|
116
|
+
>
|
|
126
117
|
<span class="nav-text">
|
|
127
118
|
{{ props.name }}
|
|
128
119
|
</span>
|
|
@@ -130,43 +121,52 @@ function logout() {
|
|
|
130
121
|
|
|
131
122
|
<!-- Navigation Links -->
|
|
132
123
|
<nav class="sidebar-nav flex column flex-stretch gap-025 align-items-start scrollbar-gutter-stable">
|
|
133
|
-
<Btn
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
124
|
+
<Btn
|
|
125
|
+
v-for="link in props.navLinks" :key="link.to" :title="!menuState.isOpen.value && !menuState.isMobile.value
|
|
126
|
+
? link.label
|
|
127
|
+
: ''
|
|
128
|
+
" fullWidth alignTxt="start" class="flex-shrink-0 px-1" :class="{ 'nav-btn-active': route.path === link.to }"
|
|
129
|
+
:style="{
|
|
130
|
+
backgroundColor:
|
|
131
|
+
route.path === link.to ? props.activeColor : props.bgColor,
|
|
132
|
+
color: route.path === link.to ? 'white' : props.textColor,
|
|
133
|
+
}" :to="link.to || '/'"
|
|
134
|
+
>
|
|
141
135
|
<Icon :name="link.icon" size="1.2" />
|
|
142
136
|
<span class="nav-text">
|
|
143
137
|
{{ link.label }}
|
|
144
138
|
</span>
|
|
145
139
|
</Btn>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
140
|
+
</nav>
|
|
141
|
+
<!-- Footer -->
|
|
142
|
+
<div
|
|
143
|
+
class="sidebar-footer flex column flex-stretch gap-025 align-items-start mt-auto scrollbar-gutter-stable"
|
|
144
|
+
>
|
|
145
|
+
<!-- Footer Links -->
|
|
146
|
+
<Btn
|
|
147
|
+
v-for="link in props.footerLinks" :key="link.to || link.label" :title="!menuState.isOpen.value && !menuState.isMobile.value
|
|
151
148
|
? link.label
|
|
152
149
|
: ''
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
</
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
150
|
+
" alignTxt="start" fullWidth flat :icon="link.icon" class="flex-shrink-0 px-1" :to="link.to" @click="link.action"
|
|
151
|
+
>
|
|
152
|
+
<span class="nav-text">
|
|
153
|
+
{{ link.label }}
|
|
154
|
+
</span>
|
|
155
|
+
</Btn>
|
|
156
|
+
|
|
157
|
+
<!-- Default Logout Button if no footer links provided -->
|
|
158
|
+
<Btn
|
|
159
|
+
v-if="props.footerLinks.length === 0" :title="!menuState.isOpen.value && !menuState.isMobile.value ? 'Logout' : ''
|
|
160
|
+
" alignTxt="start" fullWidth flat icon="logout" class="flex-shrink-0 px-1" @click="logout"
|
|
161
|
+
>
|
|
162
|
+
<span class="nav-text"> Logout </span>
|
|
163
|
+
</Btn>
|
|
164
|
+
|
|
165
|
+
<!-- Custom Footer Content Slot -->
|
|
166
|
+
<slot name="footer" />
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</aside>
|
|
170
170
|
</template>
|
|
171
171
|
|
|
172
172
|
<style>
|
|
@@ -189,7 +189,6 @@ function logout() {
|
|
|
189
189
|
overflow-y: auto;
|
|
190
190
|
flex: 1;
|
|
191
191
|
}
|
|
192
|
-
|
|
193
192
|
/* הסתרת גלילה בזמן שינוי גודל */
|
|
194
193
|
.app-sidebar.transitioning .sidebar-nav {
|
|
195
194
|
scrollbar-width: none;
|
|
@@ -267,8 +266,10 @@ function logout() {
|
|
|
267
266
|
.sidebar-mobile-closed {
|
|
268
267
|
transform: translateX(-100%);
|
|
269
268
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
|
|
270
|
+
[dir="rtl"] .sidebar-mobile-closed,
|
|
271
|
+
.rtl .sidebar-mobile-closed {
|
|
272
|
+
transform: translateX(100%);
|
|
273
|
+
}
|
|
273
274
|
}
|
|
274
275
|
</style>
|