@bagelink/vue 1.15.61 → 1.15.65
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/AccordionItem.vue.d.ts.map +1 -1
- package/dist/components/Avatar.vue.d.ts +6 -1
- package/dist/components/Avatar.vue.d.ts.map +1 -1
- package/dist/components/Badge.vue.d.ts.map +1 -1
- package/dist/components/Card.vue.d.ts +7 -0
- package/dist/components/Card.vue.d.ts.map +1 -1
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/EmptyState.vue.d.ts +43 -0
- package/dist/components/EmptyState.vue.d.ts.map +1 -0
- package/dist/components/Icon/Icon.vue.d.ts +13 -0
- package/dist/components/Icon/Icon.vue.d.ts.map +1 -1
- package/dist/components/Image.vue.d.ts +26 -1
- package/dist/components/Image.vue.d.ts.map +1 -1
- package/dist/components/ListItem.vue.d.ts +9 -9
- package/dist/components/ListItem.vue.d.ts.map +1 -1
- package/dist/components/Menu.vue.d.ts.map +1 -1
- package/dist/components/Swiper.vue.d.ts +3 -3
- package/dist/components/calendar/CalendarPopover.vue.d.ts +10 -0
- package/dist/components/calendar/CalendarPopover.vue.d.ts.map +1 -1
- package/dist/components/charts/BarChart.vue.d.ts +34 -0
- package/dist/components/charts/BarChart.vue.d.ts.map +1 -0
- package/dist/components/charts/ChartTooltip.vue.d.ts +33 -0
- package/dist/components/charts/ChartTooltip.vue.d.ts.map +1 -0
- package/dist/components/charts/Donut.vue.d.ts +53 -0
- package/dist/components/charts/Donut.vue.d.ts.map +1 -0
- package/dist/components/charts/Funnel.vue.d.ts +53 -0
- package/dist/components/charts/Funnel.vue.d.ts.map +1 -0
- package/dist/components/charts/Gauge.vue.d.ts +28 -0
- package/dist/components/charts/Gauge.vue.d.ts.map +1 -0
- package/dist/components/charts/LineChart.vue.d.ts +37 -0
- package/dist/components/charts/LineChart.vue.d.ts.map +1 -0
- package/dist/components/charts/RadialBars.vue.d.ts +34 -0
- package/dist/components/charts/RadialBars.vue.d.ts.map +1 -0
- package/dist/components/charts/RankBars.vue.d.ts +27 -0
- package/dist/components/charts/RankBars.vue.d.ts.map +1 -0
- package/dist/components/charts/Sparkline.vue.d.ts +25 -0
- package/dist/components/charts/Sparkline.vue.d.ts.map +1 -0
- package/dist/components/charts/StatCard.vue.d.ts +28 -0
- package/dist/components/charts/StatCard.vue.d.ts.map +1 -0
- package/dist/components/charts/core/data.d.ts +46 -0
- package/dist/components/charts/core/data.d.ts.map +1 -0
- package/dist/components/charts/core/format.d.ts +13 -0
- package/dist/components/charts/core/format.d.ts.map +1 -0
- package/dist/components/charts/core/palette.d.ts +19 -0
- package/dist/components/charts/core/palette.d.ts.map +1 -0
- package/dist/components/charts/core/uid.d.ts +2 -0
- package/dist/components/charts/core/uid.d.ts.map +1 -0
- package/dist/components/charts/core/useChartAnim.d.ts +11 -0
- package/dist/components/charts/core/useChartAnim.d.ts.map +1 -0
- package/dist/components/charts/core/useChartFrame.d.ts +21 -0
- package/dist/components/charts/core/useChartFrame.d.ts.map +1 -0
- package/dist/components/charts/core/useScale.d.ts +16 -0
- package/dist/components/charts/core/useScale.d.ts.map +1 -0
- package/dist/components/charts/index.d.ts +12 -0
- package/dist/components/charts/index.d.ts.map +1 -0
- package/dist/components/form/inputs/RadioGroup.vue.d.ts +1 -0
- package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RangeInput.vue.d.ts +13 -4
- package/dist/components/form/inputs/RangeInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
- package/dist/components/layout/Divider.vue.d.ts.map +1 -1
- package/dist/components/layout/Layout.vue.d.ts +1 -1
- package/dist/components/layout/Layout.vue.d.ts.map +1 -1
- package/dist/components/layout/Panel.vue.d.ts +1 -1
- package/dist/components/layout/Panel.vue.d.ts.map +1 -1
- package/dist/components/layout/SidebarNavItem.vue.d.ts +3 -1
- package/dist/components/layout/SidebarNavItem.vue.d.ts.map +1 -1
- package/dist/components/layout/Timeline.types.d.ts +9 -0
- package/dist/components/layout/Timeline.types.d.ts.map +1 -0
- package/dist/components/layout/Timeline.vue.d.ts +42 -0
- package/dist/components/layout/Timeline.vue.d.ts.map +1 -0
- package/dist/components/layout/TimelineItem.vue.d.ts +37 -0
- package/dist/components/layout/TimelineItem.vue.d.ts.map +1 -0
- package/dist/components/layout/index.d.ts +3 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/dialog/Dialog.vue.d.ts +4 -0
- package/dist/dialog/Dialog.vue.d.ts.map +1 -1
- package/dist/index.cjs +110 -116
- package/dist/index.mjs +38104 -37045
- package/dist/style.css +1 -1
- package/package.json +2 -1
- package/src/components/AccordionItem.vue +24 -22
- package/src/components/Avatar.vue +49 -11
- package/src/components/Badge.vue +4 -7
- package/src/components/Card.vue +32 -2
- package/src/components/Dropdown.vue +14 -3
- package/src/components/EmptyState.vue +91 -0
- package/src/components/Icon/Icon.vue +118 -25
- package/src/components/Image.vue +70 -3
- package/src/components/ListItem.vue +43 -22
- package/src/components/Menu.vue +10 -2
- package/src/components/charts/BarChart.vue +197 -0
- package/src/components/charts/ChartTooltip.vue +74 -0
- package/src/components/charts/Donut.vue +219 -0
- package/src/components/charts/Funnel.vue +377 -0
- package/src/components/charts/Gauge.vue +90 -0
- package/src/components/charts/LineChart.vue +255 -0
- package/src/components/charts/RadialBars.vue +99 -0
- package/src/components/charts/RankBars.vue +72 -0
- package/src/components/charts/Sparkline.vue +90 -0
- package/src/components/charts/StatCard.vue +84 -0
- package/src/components/charts/core/data.ts +95 -0
- package/src/components/charts/core/format.ts +64 -0
- package/src/components/charts/core/palette.ts +52 -0
- package/src/components/charts/core/uid.ts +6 -0
- package/src/components/charts/core/useChartAnim.ts +60 -0
- package/src/components/charts/core/useChartFrame.ts +49 -0
- package/src/components/charts/core/useScale.ts +39 -0
- package/src/components/charts/index.ts +12 -0
- package/src/components/form/inputs/RadioGroup.vue +2 -1
- package/src/components/form/inputs/RangeInput.vue +43 -15
- package/src/components/form/inputs/SelectInput.vue +1 -19
- package/src/components/index.ts +3 -1
- package/src/components/layout/AppSidebar.vue +1 -0
- package/src/components/layout/Divider.vue +2 -9
- package/src/components/layout/SidebarNavItem.vue +82 -41
- package/src/components/layout/Timeline.types.ts +9 -0
- package/src/components/layout/Timeline.vue +54 -0
- package/src/components/layout/TimelineItem.vue +93 -0
- package/src/components/layout/index.ts +3 -0
- package/src/dialog/Dialog.vue +29 -1
- package/src/styles/bagel.css +1 -0
- package/src/styles/dark.css +13 -0
- package/src/styles/gradients.css +181 -0
- package/src/styles/layout.css +9 -0
- package/src/styles/text.css +38 -6
- package/src/styles/theme.css +1 -1
- package/dist/components/analytics/BarChart.vue.d.ts +0 -47
- package/dist/components/analytics/BarChart.vue.d.ts.map +0 -1
- package/dist/components/analytics/KpiCard.vue.d.ts +0 -24
- package/dist/components/analytics/KpiCard.vue.d.ts.map +0 -1
- package/dist/components/analytics/LineChart.vue.d.ts +0 -35
- package/dist/components/analytics/LineChart.vue.d.ts.map +0 -1
- package/dist/components/analytics/PieChart.vue.d.ts +0 -53
- package/dist/components/analytics/PieChart.vue.d.ts.map +0 -1
- package/dist/components/analytics/index.d.ts +0 -5
- package/dist/components/analytics/index.d.ts.map +0 -1
- package/src/components/analytics/BarChart.vue +0 -262
- package/src/components/analytics/KpiCard.vue +0 -84
- package/src/components/analytics/LineChart.vue +0 -357
- package/src/components/analytics/PieChart.vue +0 -544
- package/src/components/analytics/index.ts +0 -4
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bagelink/vue",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.15.
|
|
4
|
+
"version": "1.15.65",
|
|
5
5
|
"description": "Bagel core sdk packages",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Bagel Studio",
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
"scripts": {
|
|
91
91
|
"dev": "tsx watch src/index.ts",
|
|
92
92
|
"build": "vite build",
|
|
93
|
+
"gen:cheatsheet": "tsx scripts/gen-cheatsheet.ts",
|
|
93
94
|
"start": "tsx src/index.ts"
|
|
94
95
|
}
|
|
95
96
|
}
|
|
@@ -72,7 +72,7 @@ function toggle() {
|
|
|
72
72
|
v-if="iconPosition === 'start'" class="accordion-icon"
|
|
73
73
|
:class="[iconClass, { open: isOpen && iconType === 'expand_more' }]"
|
|
74
74
|
>
|
|
75
|
-
<Icon :icon="computedIcon" />
|
|
75
|
+
<Icon :icon="computedIcon" transition="scale" />
|
|
76
76
|
</span>
|
|
77
77
|
|
|
78
78
|
<slot name="head">
|
|
@@ -85,17 +85,17 @@ function toggle() {
|
|
|
85
85
|
v-if="iconPosition === 'end'" class="accordion-icon"
|
|
86
86
|
:class="[iconClass, { open: isOpen && iconType === 'expand_more' }]"
|
|
87
87
|
>
|
|
88
|
-
<Icon :icon="computedIcon" />
|
|
88
|
+
<Icon :icon="computedIcon" transition="scale" />
|
|
89
89
|
</span>
|
|
90
90
|
</button>
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
>
|
|
91
|
+
<div
|
|
92
|
+
:id="`accordion-body-${id}`" class="accordion-body"
|
|
93
|
+
:class="{ open: isOpen }" :aria-hidden="isOpen ? 'false' : 'true'"
|
|
94
|
+
>
|
|
95
|
+
<div class="accordion-body-inner">
|
|
96
96
|
<slot />
|
|
97
97
|
</div>
|
|
98
|
-
</
|
|
98
|
+
</div>
|
|
99
99
|
</div>
|
|
100
100
|
</template>
|
|
101
101
|
|
|
@@ -149,24 +149,26 @@ border-bottom: none
|
|
|
149
149
|
text-decoration: underline;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
transition
|
|
152
|
+
/* Height animation via grid-template-rows 0fr -> 1fr. Unlike a fixed max-height,
|
|
153
|
+
this animates to the content's natural height with no cap (no clipping on long
|
|
154
|
+
bodies) and no JS measurement. The inner wrapper needs min-height:0 + overflow
|
|
155
|
+
hidden so it can collapse fully. */
|
|
156
|
+
.accordion-body {
|
|
157
|
+
display: grid;
|
|
158
|
+
grid-template-rows: 0fr;
|
|
159
|
+
transition: grid-template-rows 0.25s ease;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
.
|
|
163
|
-
|
|
164
|
-
max-height: 0;
|
|
162
|
+
.accordion-body.open {
|
|
163
|
+
grid-template-rows: 1fr;
|
|
165
164
|
}
|
|
166
165
|
|
|
167
|
-
.
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
.accordion-body-inner {
|
|
167
|
+
min-height: 0;
|
|
168
|
+
overflow: hidden;
|
|
170
169
|
}
|
|
171
170
|
|
|
171
|
+
@media (prefers-reduced-motion: reduce) {
|
|
172
|
+
.accordion-body { transition: none; }
|
|
173
|
+
}
|
|
172
174
|
</style>
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
2
|
+
// Forward consumer class/style/attrs to the inner (rounded, clipped) avatar
|
|
3
|
+
// element rather than the positioning wrapper — otherwise a custom background
|
|
4
|
+
// would render as a square behind the circle.
|
|
5
|
+
defineOptions({ name: 'BglAvatar', inheritAttrs: false })
|
|
3
6
|
import type { IconType, ThemeType } from '@bagelink/vue'
|
|
4
7
|
import { initials, Icon } from '@bagelink/vue'
|
|
5
8
|
import { computed } from 'vue'
|
|
@@ -16,8 +19,17 @@ const props = withDefaults(defineProps<{
|
|
|
16
19
|
square?: boolean | number
|
|
17
20
|
/** Theme color → tinted background + matching icon/text color (e.g. an icon chip). */
|
|
18
21
|
color?: ThemeType
|
|
22
|
+
/** Presence/status dot in the corner. `true` = green "online", or a ThemeType
|
|
23
|
+
(e.g. 'gray' offline, 'orange' away, 'red' busy). */
|
|
24
|
+
status?: boolean | ThemeType
|
|
25
|
+
/** Accessible label / tooltip for the status dot. */
|
|
26
|
+
statusTitle?: string
|
|
19
27
|
}>(), { size: 50 })
|
|
20
28
|
|
|
29
|
+
const statusColor = computed(() => (props.status === true ? 'green' : props.status || null))
|
|
30
|
+
// Dot scales with the avatar but stays within sane bounds.
|
|
31
|
+
const dotSize = computed(() => Math.max(7, Math.min(14, Math.round(props.size * 0.28))))
|
|
32
|
+
|
|
21
33
|
const radius = computed(() => {
|
|
22
34
|
if (!props.square) { return '1000px' } // circle (default)
|
|
23
35
|
if (props.square === true) { return 'var(--bgl-btn-border-radius)' }
|
|
@@ -25,32 +37,58 @@ const radius = computed(() => {
|
|
|
25
37
|
})
|
|
26
38
|
|
|
27
39
|
// When a theme color is given, tint the background and color the icon/initials.
|
|
40
|
+
// Mix straight from the live --bgl-<color> so it always matches the foreground —
|
|
41
|
+
// notably for `primary`, whose pre-baked --bgl-primary-tint token can resolve to a
|
|
42
|
+
// different (default) hue when an app overrides --bgl-primary in a nested scope.
|
|
28
43
|
const themed = computed(() => props.color
|
|
29
|
-
? { background: `
|
|
44
|
+
? { background: `color-mix(in srgb, var(--bgl-${props.color}) 14%, transparent)`, color: `var(--bgl-${props.color})`, border: 'none' }
|
|
30
45
|
: {})
|
|
31
46
|
</script>
|
|
32
47
|
|
|
33
48
|
<template>
|
|
34
|
-
<div
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
<div class="avatar-wrap" :style="{ width: `${size}px`, height: `${size}px` }">
|
|
50
|
+
<div
|
|
51
|
+
v-bind="$attrs"
|
|
52
|
+
class="overflow-hidden txt-center p-0 avatar flex justify-content-center align-items-center"
|
|
53
|
+
:style="{ width: `${size}px`, height: `${size}px`, borderRadius: radius, ...themed }"
|
|
54
|
+
>
|
|
55
|
+
<Image v-if="src" :src="src" :alt="alt || name" />
|
|
56
|
+
<Icon v-else-if="icon" :icon="icon" :size="size / 40" />
|
|
57
|
+
<p v-else :style="{ 'line-height': `${size}px`, 'font-size': `calc(1.5rem * ${size} / 50)` }">
|
|
58
|
+
{{ (fallback || initials(name || '')).toUpperCase() }}
|
|
59
|
+
</p>
|
|
60
|
+
</div>
|
|
61
|
+
<span
|
|
62
|
+
v-if="statusColor" class="avatar-status" :title="statusTitle"
|
|
63
|
+
:style="{ width: `${dotSize}px`, height: `${dotSize}px`, background: `var(--bgl-${statusColor})` }"
|
|
64
|
+
/>
|
|
43
65
|
</div>
|
|
44
66
|
</template>
|
|
45
67
|
|
|
46
68
|
<style scoped>
|
|
47
69
|
|
|
70
|
+
.avatar-wrap {
|
|
71
|
+
position: relative;
|
|
72
|
+
flex-shrink: 0;
|
|
73
|
+
display: inline-flex;
|
|
74
|
+
}
|
|
75
|
+
|
|
48
76
|
.avatar {
|
|
49
77
|
background-color: var(--bgl-gray-tint);
|
|
50
78
|
border: 0.5px solid var(--bgl-border-color);
|
|
51
79
|
flex-shrink: 0;
|
|
52
80
|
}
|
|
53
81
|
|
|
82
|
+
/* Presence dot — sits over the bottom-trailing corner, ringed by the surface
|
|
83
|
+
color so it reads cleanly on any background. */
|
|
84
|
+
.avatar-status {
|
|
85
|
+
position: absolute;
|
|
86
|
+
inset-block-end: 0;
|
|
87
|
+
inset-inline-end: 0;
|
|
88
|
+
border-radius: 50%;
|
|
89
|
+
box-shadow: 0 0 0 2px var(--bgl-box-bg, #fff);
|
|
90
|
+
}
|
|
91
|
+
|
|
54
92
|
.avatar p {
|
|
55
93
|
font-size: 1.5rem;
|
|
56
94
|
line-height: 50px;
|
package/src/components/Badge.vue
CHANGED
|
@@ -55,12 +55,6 @@ const computedPairClass = computed(() => {
|
|
|
55
55
|
return `pair-${theme}`
|
|
56
56
|
})
|
|
57
57
|
|
|
58
|
-
// Dot uses the badge's theme color (strip any -light/-tint suffix for a solid dot).
|
|
59
|
-
const dotColor = computed(() => {
|
|
60
|
-
const base = (computedTheme.value || 'primary').replace(/-(light|tint|10|20|30|40|50|60|70)$/, '')
|
|
61
|
-
return `var(--bgl-${base})`
|
|
62
|
-
})
|
|
63
|
-
|
|
64
58
|
const computedSize = computed(() => {
|
|
65
59
|
if (props.size) { return props.size }
|
|
66
60
|
if (props.sm) { return 'sm' }
|
|
@@ -105,7 +99,7 @@ const computedClasses = computed(() => {
|
|
|
105
99
|
<div v-if="loading" class="loading absolute inset-0 mx-auto" />
|
|
106
100
|
<div class="px-025 flex gap-025 justify-content-center align-items-center" :class="{ 'opacity-0': loading }">
|
|
107
101
|
<Btn v-if="btn" class="bgl_pill-btn -ms-025" icon-size="0.8" round v-bind="btn" />
|
|
108
|
-
<span v-if="dot" class="bgl_pill-dot"
|
|
102
|
+
<span v-if="dot" class="bgl_pill-dot" />
|
|
109
103
|
<Icon v-if="icon" class="line-height-0" :icon="icon" style="font-size: var(--bgl-pill-font-size)" />
|
|
110
104
|
<slot :class="{ uppercase }" />
|
|
111
105
|
<template v-if="!slots.default">
|
|
@@ -138,6 +132,9 @@ width: 6px;
|
|
|
138
132
|
height: 6px;
|
|
139
133
|
border-radius: 50%;
|
|
140
134
|
flex-shrink: 0;
|
|
135
|
+
/* Use the badge's text color so the dot is always legible on both solid
|
|
136
|
+
(white text) and -light/-tint (tone text) variants. */
|
|
137
|
+
background: currentColor;
|
|
141
138
|
}
|
|
142
139
|
.bgl_pill-btn{
|
|
143
140
|
color: var(--bgl-pill-btn-color);
|
package/src/components/Card.vue
CHANGED
|
@@ -4,6 +4,11 @@ import { computed } from 'vue'
|
|
|
4
4
|
|
|
5
5
|
const props = defineProps<{
|
|
6
6
|
label?: string
|
|
7
|
+
/** Card header title. Pairs with the `#header-action` slot (e.g. a "View all"
|
|
8
|
+
button or a more-menu) to render a divided header row — replacing the common
|
|
9
|
+
hand-rolled `<div class="flex space-between border-bottom">…</div>` pattern.
|
|
10
|
+
For full control use the `#header` slot instead. */
|
|
11
|
+
title?: string
|
|
7
12
|
thin?: boolean
|
|
8
13
|
outline?: boolean
|
|
9
14
|
h100?: boolean
|
|
@@ -55,6 +60,13 @@ const is = computed(() => {
|
|
|
55
60
|
<span v-if="label" class="card_label">
|
|
56
61
|
{{ label }}
|
|
57
62
|
</span>
|
|
63
|
+
<!-- Header row: title + optional trailing action. Full override via #header. -->
|
|
64
|
+
<div v-if="$slots.header || title || $slots['header-action']" class="card_header flex space-between align-items-center">
|
|
65
|
+
<slot name="header">
|
|
66
|
+
<span class="card_header_title">{{ title }}</span>
|
|
67
|
+
<span class="card_header_action flex align-items-center gap-05"><slot name="header-action" /></span>
|
|
68
|
+
</slot>
|
|
69
|
+
</div>
|
|
58
70
|
<slot />
|
|
59
71
|
</component>
|
|
60
72
|
</template>
|
|
@@ -74,6 +86,19 @@ border-bottom: 1px solid var(--bgl-border-color);
|
|
|
74
86
|
margin-bottom: 1rem;
|
|
75
87
|
}
|
|
76
88
|
|
|
89
|
+
/* Header row (title + action). Pulls out to the card edges via negative margins
|
|
90
|
+
so its divider spans full-width regardless of the card's own padding, then
|
|
91
|
+
restores breathing room below. On a p-0 card the negative margins resolve to 0. */
|
|
92
|
+
.card_header {
|
|
93
|
+
margin: calc(var(--bgl-card-pad) * -1) calc(var(--bgl-card-pad) * -1) var(--bgl-card-pad);
|
|
94
|
+
padding: 0.85rem var(--bgl-card-pad);
|
|
95
|
+
border-bottom: 1px solid var(--bgl-border-color);
|
|
96
|
+
min-height: 0;
|
|
97
|
+
}
|
|
98
|
+
.card_header_title {
|
|
99
|
+
font-weight: 600;
|
|
100
|
+
}
|
|
101
|
+
|
|
77
102
|
.border .card_label {
|
|
78
103
|
font-size: 0.7rem;
|
|
79
104
|
font-weight: 300;
|
|
@@ -89,9 +114,10 @@ border-bottom: unset;
|
|
|
89
114
|
}
|
|
90
115
|
|
|
91
116
|
.bgl_card {
|
|
117
|
+
--bgl-card-pad: 2rem;
|
|
92
118
|
border-radius: var(--bgl-card-border-radius);
|
|
93
119
|
background: var(--bgl-box-bg);
|
|
94
|
-
padding:
|
|
120
|
+
padding: var(--bgl-card-pad);
|
|
95
121
|
position: relative;
|
|
96
122
|
}
|
|
97
123
|
|
|
@@ -105,8 +131,12 @@ background-color: transparent;
|
|
|
105
131
|
}
|
|
106
132
|
|
|
107
133
|
.bgl_card.thin {
|
|
108
|
-
|
|
134
|
+
--bgl-card-pad: 1rem;
|
|
135
|
+
padding: var(--bgl-card-pad);
|
|
109
136
|
}
|
|
137
|
+
/* When padding is stripped via the p-0 utility, the header supplies its own. */
|
|
138
|
+
.bgl_card.p-0 { --bgl-card-pad: 0rem; }
|
|
139
|
+
.bgl_card.p-0 .card_header { padding-inline: 1rem; padding-block: 0.75rem; margin-bottom: 0; }
|
|
110
140
|
|
|
111
141
|
.bgl_card.BagelTable {
|
|
112
142
|
height: 100%;
|
|
@@ -482,10 +482,21 @@ animation: bgl-dropdown-enter 0.18s ease 0.04s both;
|
|
|
482
482
|
transform-origin: top left;
|
|
483
483
|
}
|
|
484
484
|
|
|
485
|
-
/* Default menu breathing room so
|
|
486
|
-
|
|
485
|
+
/* Default menu breathing room so items don't touch the rounded top/bottom edges.
|
|
486
|
+
Applies to the card content itself so plain menu content (e.g. a list of Btns)
|
|
487
|
+
gets the same padding as content wrapped in a .bgl_card — no consumer py-* needed. */
|
|
487
488
|
.bgl-dropdown__content--card {
|
|
488
|
-
padding-block: 0.
|
|
489
|
+
padding-block: 0.5rem;
|
|
490
|
+
padding-inline: 0.375rem;
|
|
491
|
+
}
|
|
492
|
+
/* When the slot already provides a .bgl_card, that card owns the padding — drop
|
|
493
|
+
the container's own padding so the spacing isn't applied twice. */
|
|
494
|
+
.bgl-dropdown__content--card:has(> .bgl_card) {
|
|
495
|
+
padding: 0;
|
|
496
|
+
}
|
|
497
|
+
.bgl-dropdown__content--card .bgl_card{
|
|
498
|
+
padding-block: 0.5rem;
|
|
499
|
+
padding-inline: 0.375rem;
|
|
489
500
|
}
|
|
490
501
|
|
|
491
502
|
</style>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
defineOptions({ name: 'BglEmptyState' })
|
|
3
|
+
import type { IconType } from '@bagelink/vue'
|
|
4
|
+
import { Icon } from '@bagelink/vue'
|
|
5
|
+
import { useSlots } from 'vue'
|
|
6
|
+
import Btn from './Btn.vue'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Placeholder for empty lists / no-results / zero states.
|
|
10
|
+
* - `icon` + `title` + `description` for the common case.
|
|
11
|
+
* - `actionLabel` (+ `to`/`href` or @action) renders a primary button.
|
|
12
|
+
* - Slots: `#icon`, default (body text), `#action` for full control.
|
|
13
|
+
* - `compact` for tight inline spots (table empties, small cards).
|
|
14
|
+
*/
|
|
15
|
+
const {
|
|
16
|
+
icon = 'inbox',
|
|
17
|
+
title = '',
|
|
18
|
+
description = '',
|
|
19
|
+
actionLabel = '',
|
|
20
|
+
actionIcon,
|
|
21
|
+
to,
|
|
22
|
+
href,
|
|
23
|
+
compact = false,
|
|
24
|
+
class: className = '',
|
|
25
|
+
} = defineProps<{
|
|
26
|
+
icon?: IconType | 'none'
|
|
27
|
+
title?: string
|
|
28
|
+
description?: string
|
|
29
|
+
actionLabel?: string
|
|
30
|
+
actionIcon?: IconType
|
|
31
|
+
to?: string | Record<string, any>
|
|
32
|
+
href?: string
|
|
33
|
+
compact?: boolean
|
|
34
|
+
class?: string
|
|
35
|
+
}>()
|
|
36
|
+
|
|
37
|
+
const emit = defineEmits<{ action: [] }>()
|
|
38
|
+
const slots = useSlots()
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<div class="bgl-empty flex-column align-items-center justify-content-center txt-center" :class="[{ 'bgl-empty--compact': compact }, className]">
|
|
43
|
+
<div v-if="slots.icon || icon !== 'none'" class="bgl-empty-icon flex-center">
|
|
44
|
+
<slot name="icon"><Icon :icon="icon" :size="compact ? 1.6 : 2.2" /></slot>
|
|
45
|
+
</div>
|
|
46
|
+
<p v-if="title" class="bgl-empty-title m-0">{{ title }}</p>
|
|
47
|
+
<p v-if="description || slots.default" class="bgl-empty-desc m-0">
|
|
48
|
+
<slot>{{ description }}</slot>
|
|
49
|
+
</p>
|
|
50
|
+
<div v-if="slots.action || actionLabel" class="bgl-empty-action">
|
|
51
|
+
<slot name="action">
|
|
52
|
+
<Btn :color="'primary'" :thin="compact" :icon="actionIcon" :value="actionLabel" :to="to" :href="href" @click="emit('action')" />
|
|
53
|
+
</slot>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<style scoped>
|
|
59
|
+
.bgl-empty {
|
|
60
|
+
gap: 0.75rem;
|
|
61
|
+
padding: 2.5rem 1.5rem;
|
|
62
|
+
color: var(--bgl-text-soft, var(--bgl-gray));
|
|
63
|
+
}
|
|
64
|
+
.bgl-empty--compact {
|
|
65
|
+
gap: 0.5rem;
|
|
66
|
+
padding: 1.25rem 1rem;
|
|
67
|
+
}
|
|
68
|
+
.bgl-empty-icon {
|
|
69
|
+
width: 3.5rem;
|
|
70
|
+
height: 3.5rem;
|
|
71
|
+
border-radius: 50%;
|
|
72
|
+
background: var(--bgl-gray-tint, color-mix(in srgb, var(--bgl-gray) 14%, transparent));
|
|
73
|
+
color: var(--bgl-gray);
|
|
74
|
+
}
|
|
75
|
+
.bgl-empty--compact .bgl-empty-icon {
|
|
76
|
+
width: 2.5rem;
|
|
77
|
+
height: 2.5rem;
|
|
78
|
+
}
|
|
79
|
+
.bgl-empty-title {
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
font-size: 1rem;
|
|
82
|
+
color: var(--bgl-text-color, inherit);
|
|
83
|
+
}
|
|
84
|
+
.bgl-empty-desc {
|
|
85
|
+
font-size: 0.875rem;
|
|
86
|
+
max-width: 32ch;
|
|
87
|
+
}
|
|
88
|
+
.bgl-empty-action {
|
|
89
|
+
margin-top: 0.25rem;
|
|
90
|
+
}
|
|
91
|
+
</style>
|
|
@@ -4,6 +4,9 @@ import { useDevice } from '@bagelink/vue'
|
|
|
4
4
|
import { computed, ref, onMounted, watchEffect } from 'vue'
|
|
5
5
|
import { FONT_AWESOME_ICONS, MATERIAL_ICONS, FONT_AWESOME_BRANDS_ICONS } from './constants'
|
|
6
6
|
|
|
7
|
+
type AnimateType = 'spin' | 'pulse' | 'bounce' | 'fade-in' | 'slide-up' | 'rotate-in'
|
|
8
|
+
type TransitionType = 'slide-up' | 'rotate' | 'scale' | 'fade'
|
|
9
|
+
|
|
7
10
|
const props = withDefaults(defineProps<{
|
|
8
11
|
icon?: IconType
|
|
9
12
|
name?: IconType
|
|
@@ -14,8 +17,19 @@ const props = withDefaults(defineProps<{
|
|
|
14
17
|
weight?: number | string
|
|
15
18
|
fontAwesome?: boolean
|
|
16
19
|
fill?: boolean
|
|
20
|
+
/** Entrance / looping effect. Pure CSS, opt-in. e.g. `animate="spin"`. */
|
|
21
|
+
animate?: AnimateType
|
|
22
|
+
/** Seconds for one cycle of `animate` (spin/pulse/etc.). */
|
|
23
|
+
animateSpeed?: number
|
|
24
|
+
/** Play the `animate` effect once instead of looping. */
|
|
25
|
+
animateOnce?: boolean
|
|
26
|
+
/** Animate the glyph whenever the icon changes (e.g. a play/pause toggle).
|
|
27
|
+
Defaults ON: pass `transition="rotate"` to pick a style, or
|
|
28
|
+
`:transition="false"` to disable. */
|
|
29
|
+
transition?: boolean | TransitionType
|
|
17
30
|
}>(), {
|
|
18
|
-
size: 1
|
|
31
|
+
size: 1,
|
|
32
|
+
transition: true,
|
|
19
33
|
})
|
|
20
34
|
|
|
21
35
|
const iconRender = computed(() => (props.icon ?? props.name) as IconType)
|
|
@@ -114,33 +128,56 @@ const isCurrentFontReady = computed(() => {
|
|
|
114
128
|
if (iconRenderType.value === 'material') { return isMaterialReady.value }
|
|
115
129
|
return isFaBrand.value ? isFABrandsReady.value : isFAFreeReady.value
|
|
116
130
|
})
|
|
131
|
+
|
|
132
|
+
// Entrance / loop animation class + timing.
|
|
133
|
+
const animateClass = computed(() => (props.animate ? `bgl_icon--${props.animate}` : null))
|
|
134
|
+
const animateStyle = computed(() => {
|
|
135
|
+
if (!props.animate) { return undefined }
|
|
136
|
+
const style: Record<string, string> = {}
|
|
137
|
+
if (props.animateSpeed !== undefined) { style.animationDuration = `${props.animateSpeed}s` }
|
|
138
|
+
if (props.animateOnce) { style.animationIterationCount = '1' }
|
|
139
|
+
return style
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Swap transition: animate whenever the glyph changes. `transition` true uses the
|
|
143
|
+
// default style; a string picks one. The keyed inner span drives Vue's <Transition>.
|
|
144
|
+
const transitionName = computed(() => {
|
|
145
|
+
// `bgl-icon-x-none` has no CSS defined, so the swap is instant when disabled.
|
|
146
|
+
if (props.transition === false) { return 'bgl-icon-x-none' }
|
|
147
|
+
const style = props.transition === true ? 'slide-up' : props.transition
|
|
148
|
+
return `bgl-icon-x-${style}`
|
|
149
|
+
})
|
|
117
150
|
</script>
|
|
118
151
|
|
|
119
152
|
<template>
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
'
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
153
|
+
<Transition :name="transitionName" mode="out-in">
|
|
154
|
+
<span
|
|
155
|
+
v-if="iconRenderType === 'material'" :key="iconRender" class="bgl_icon-font notranslate"
|
|
156
|
+
:class="[animateClass, { 'round flex aspect-ratio-1 justify-content-center': round }]" :style="{
|
|
157
|
+
'fontSize': `${computedSize}rem`,
|
|
158
|
+
color,
|
|
159
|
+
'font-variation-settings': `'FILL' ${fill ? 1 : 0}, 'wght' ${weight || 400}`,
|
|
160
|
+
'width': round ? `calc(${computedSize}rem * 2)` : 'auto',
|
|
161
|
+
'height': round ? `calc(${computedSize}rem * 2)` : 'auto',
|
|
162
|
+
'visibility': isCurrentFontReady ? 'visible' : 'hidden',
|
|
163
|
+
...animateStyle,
|
|
164
|
+
}" translate="no"
|
|
165
|
+
>
|
|
166
|
+
{{ iconRender }}
|
|
167
|
+
</span>
|
|
168
|
+
<span
|
|
169
|
+
v-else-if="iconRenderType === 'font-awesome'" :key="iconRender" class="fa bgl_icon-font notranslate" :class="[
|
|
170
|
+
`fa-${iconRender}`,
|
|
171
|
+
animateClass,
|
|
172
|
+
{
|
|
173
|
+
'fa-brands': isFaBrand,
|
|
174
|
+
'fa-solid': fill,
|
|
175
|
+
'far': !fill && !isFaBrand,
|
|
176
|
+
},
|
|
177
|
+
]" :style="{ 'fontSize': `${computedSize}rem`, color, 'font-variation-settings': `'wght' ${weight || 400}`, 'visibility': isCurrentFontReady ? 'visible' : 'hidden', ...animateStyle }"
|
|
178
|
+
translate="no"
|
|
179
|
+
/>
|
|
180
|
+
</Transition>
|
|
144
181
|
</template>
|
|
145
182
|
|
|
146
183
|
<style>
|
|
@@ -166,6 +203,62 @@ const isCurrentFontReady = computed(() => {
|
|
|
166
203
|
.bgl_icon-font.fa-brands {
|
|
167
204
|
font-family: 'Font Awesome 6 Brands', serif !important;
|
|
168
205
|
}
|
|
206
|
+
|
|
207
|
+
/* ── Entrance / loop animations (opt-in via `animate`) ─────────────────────── */
|
|
208
|
+
.bgl_icon--spin,
|
|
209
|
+
.bgl_icon--pulse,
|
|
210
|
+
.bgl_icon--bounce { display: inline-block; animation-duration: 1s; animation-iteration-count: infinite; animation-timing-function: linear; }
|
|
211
|
+
.bgl_icon--fade-in,
|
|
212
|
+
.bgl_icon--slide-up,
|
|
213
|
+
.bgl_icon--rotate-in { display: inline-block; animation-duration: 0.3s; animation-iteration-count: 1; animation-timing-function: ease; }
|
|
214
|
+
|
|
215
|
+
.bgl_icon--spin { animation-name: bgl-icon-spin; }
|
|
216
|
+
.bgl_icon--pulse { animation-name: bgl-icon-pulse; animation-timing-function: ease-in-out; }
|
|
217
|
+
.bgl_icon--bounce { animation-name: bgl-icon-bounce; animation-timing-function: ease; }
|
|
218
|
+
.bgl_icon--fade-in { animation-name: bgl-icon-fade-in; }
|
|
219
|
+
.bgl_icon--slide-up { animation-name: bgl-icon-slide-up; }
|
|
220
|
+
.bgl_icon--rotate-in { animation-name: bgl-icon-rotate-in; }
|
|
221
|
+
|
|
222
|
+
@keyframes bgl-icon-spin { to { transform: rotate(360deg); } }
|
|
223
|
+
@keyframes bgl-icon-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
|
|
224
|
+
@keyframes bgl-icon-bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-20%); } }
|
|
225
|
+
@keyframes bgl-icon-fade-in { from { opacity: 0; } to { opacity: 1; } }
|
|
226
|
+
@keyframes bgl-icon-slide-up { from { opacity: 0; transform: translateY(0.35em); } to { opacity: 1; transform: translateY(0); } }
|
|
227
|
+
@keyframes bgl-icon-rotate-in { from { opacity: 0; transform: rotate(-90deg) scale(0.6); } to { opacity: 1; transform: rotate(0) scale(1); } }
|
|
228
|
+
|
|
229
|
+
@media (prefers-reduced-motion: reduce) {
|
|
230
|
+
.bgl_icon--spin, .bgl_icon--pulse, .bgl_icon--bounce,
|
|
231
|
+
.bgl_icon--fade-in, .bgl_icon--slide-up, .bgl_icon--rotate-in { animation: none; }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* ── Swap transitions (on `name`/`icon` change; `transition` defaults on) ──── */
|
|
235
|
+
.bgl-icon-x-slide-up-enter-active,
|
|
236
|
+
.bgl-icon-x-slide-up-leave-active,
|
|
237
|
+
.bgl-icon-x-rotate-enter-active,
|
|
238
|
+
.bgl-icon-x-rotate-leave-active,
|
|
239
|
+
.bgl-icon-x-scale-enter-active,
|
|
240
|
+
.bgl-icon-x-scale-leave-active,
|
|
241
|
+
.bgl-icon-x-fade-enter-active,
|
|
242
|
+
.bgl-icon-x-fade-leave-active { transition: opacity 0.16s ease, transform 0.16s ease; }
|
|
243
|
+
|
|
244
|
+
.bgl-icon-x-slide-up-enter-from { opacity: 0; transform: translateY(0.3em); }
|
|
245
|
+
.bgl-icon-x-slide-up-leave-to { opacity: 0; transform: translateY(-0.3em); }
|
|
246
|
+
|
|
247
|
+
.bgl-icon-x-rotate-enter-from { opacity: 0; transform: rotate(-90deg); }
|
|
248
|
+
.bgl-icon-x-rotate-leave-to { opacity: 0; transform: rotate(90deg); }
|
|
249
|
+
|
|
250
|
+
.bgl-icon-x-scale-enter-from { opacity: 0; transform: scale(0.5); }
|
|
251
|
+
.bgl-icon-x-scale-leave-to { opacity: 0; transform: scale(0.5); }
|
|
252
|
+
|
|
253
|
+
.bgl-icon-x-fade-enter-from,
|
|
254
|
+
.bgl-icon-x-fade-leave-to { opacity: 0; }
|
|
255
|
+
|
|
256
|
+
@media (prefers-reduced-motion: reduce) {
|
|
257
|
+
.bgl-icon-x-slide-up-enter-active, .bgl-icon-x-slide-up-leave-active,
|
|
258
|
+
.bgl-icon-x-rotate-enter-active, .bgl-icon-x-rotate-leave-active,
|
|
259
|
+
.bgl-icon-x-scale-enter-active, .bgl-icon-x-scale-leave-active,
|
|
260
|
+
.bgl-icon-x-fade-enter-active, .bgl-icon-x-fade-leave-active { transition: none; }
|
|
261
|
+
}
|
|
169
262
|
</style>
|
|
170
263
|
|
|
171
264
|
<!-- <style src="./font-awesome.css" /> -->
|