@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.
Files changed (145) hide show
  1. package/dist/components/AccordionItem.vue.d.ts.map +1 -1
  2. package/dist/components/Avatar.vue.d.ts +6 -1
  3. package/dist/components/Avatar.vue.d.ts.map +1 -1
  4. package/dist/components/Badge.vue.d.ts.map +1 -1
  5. package/dist/components/Card.vue.d.ts +7 -0
  6. package/dist/components/Card.vue.d.ts.map +1 -1
  7. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  8. package/dist/components/EmptyState.vue.d.ts +43 -0
  9. package/dist/components/EmptyState.vue.d.ts.map +1 -0
  10. package/dist/components/Icon/Icon.vue.d.ts +13 -0
  11. package/dist/components/Icon/Icon.vue.d.ts.map +1 -1
  12. package/dist/components/Image.vue.d.ts +26 -1
  13. package/dist/components/Image.vue.d.ts.map +1 -1
  14. package/dist/components/ListItem.vue.d.ts +9 -9
  15. package/dist/components/ListItem.vue.d.ts.map +1 -1
  16. package/dist/components/Menu.vue.d.ts.map +1 -1
  17. package/dist/components/Swiper.vue.d.ts +3 -3
  18. package/dist/components/calendar/CalendarPopover.vue.d.ts +10 -0
  19. package/dist/components/calendar/CalendarPopover.vue.d.ts.map +1 -1
  20. package/dist/components/charts/BarChart.vue.d.ts +34 -0
  21. package/dist/components/charts/BarChart.vue.d.ts.map +1 -0
  22. package/dist/components/charts/ChartTooltip.vue.d.ts +33 -0
  23. package/dist/components/charts/ChartTooltip.vue.d.ts.map +1 -0
  24. package/dist/components/charts/Donut.vue.d.ts +53 -0
  25. package/dist/components/charts/Donut.vue.d.ts.map +1 -0
  26. package/dist/components/charts/Funnel.vue.d.ts +53 -0
  27. package/dist/components/charts/Funnel.vue.d.ts.map +1 -0
  28. package/dist/components/charts/Gauge.vue.d.ts +28 -0
  29. package/dist/components/charts/Gauge.vue.d.ts.map +1 -0
  30. package/dist/components/charts/LineChart.vue.d.ts +37 -0
  31. package/dist/components/charts/LineChart.vue.d.ts.map +1 -0
  32. package/dist/components/charts/RadialBars.vue.d.ts +34 -0
  33. package/dist/components/charts/RadialBars.vue.d.ts.map +1 -0
  34. package/dist/components/charts/RankBars.vue.d.ts +27 -0
  35. package/dist/components/charts/RankBars.vue.d.ts.map +1 -0
  36. package/dist/components/charts/Sparkline.vue.d.ts +25 -0
  37. package/dist/components/charts/Sparkline.vue.d.ts.map +1 -0
  38. package/dist/components/charts/StatCard.vue.d.ts +28 -0
  39. package/dist/components/charts/StatCard.vue.d.ts.map +1 -0
  40. package/dist/components/charts/core/data.d.ts +46 -0
  41. package/dist/components/charts/core/data.d.ts.map +1 -0
  42. package/dist/components/charts/core/format.d.ts +13 -0
  43. package/dist/components/charts/core/format.d.ts.map +1 -0
  44. package/dist/components/charts/core/palette.d.ts +19 -0
  45. package/dist/components/charts/core/palette.d.ts.map +1 -0
  46. package/dist/components/charts/core/uid.d.ts +2 -0
  47. package/dist/components/charts/core/uid.d.ts.map +1 -0
  48. package/dist/components/charts/core/useChartAnim.d.ts +11 -0
  49. package/dist/components/charts/core/useChartAnim.d.ts.map +1 -0
  50. package/dist/components/charts/core/useChartFrame.d.ts +21 -0
  51. package/dist/components/charts/core/useChartFrame.d.ts.map +1 -0
  52. package/dist/components/charts/core/useScale.d.ts +16 -0
  53. package/dist/components/charts/core/useScale.d.ts.map +1 -0
  54. package/dist/components/charts/index.d.ts +12 -0
  55. package/dist/components/charts/index.d.ts.map +1 -0
  56. package/dist/components/form/inputs/RadioGroup.vue.d.ts +1 -0
  57. package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
  58. package/dist/components/form/inputs/RangeInput.vue.d.ts +13 -4
  59. package/dist/components/form/inputs/RangeInput.vue.d.ts.map +1 -1
  60. package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
  61. package/dist/components/index.d.ts +3 -1
  62. package/dist/components/index.d.ts.map +1 -1
  63. package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
  64. package/dist/components/layout/Divider.vue.d.ts.map +1 -1
  65. package/dist/components/layout/Layout.vue.d.ts +1 -1
  66. package/dist/components/layout/Layout.vue.d.ts.map +1 -1
  67. package/dist/components/layout/Panel.vue.d.ts +1 -1
  68. package/dist/components/layout/Panel.vue.d.ts.map +1 -1
  69. package/dist/components/layout/SidebarNavItem.vue.d.ts +3 -1
  70. package/dist/components/layout/SidebarNavItem.vue.d.ts.map +1 -1
  71. package/dist/components/layout/Timeline.types.d.ts +9 -0
  72. package/dist/components/layout/Timeline.types.d.ts.map +1 -0
  73. package/dist/components/layout/Timeline.vue.d.ts +42 -0
  74. package/dist/components/layout/Timeline.vue.d.ts.map +1 -0
  75. package/dist/components/layout/TimelineItem.vue.d.ts +37 -0
  76. package/dist/components/layout/TimelineItem.vue.d.ts.map +1 -0
  77. package/dist/components/layout/index.d.ts +3 -0
  78. package/dist/components/layout/index.d.ts.map +1 -1
  79. package/dist/dialog/Dialog.vue.d.ts +4 -0
  80. package/dist/dialog/Dialog.vue.d.ts.map +1 -1
  81. package/dist/index.cjs +110 -116
  82. package/dist/index.mjs +38104 -37045
  83. package/dist/style.css +1 -1
  84. package/package.json +2 -1
  85. package/src/components/AccordionItem.vue +24 -22
  86. package/src/components/Avatar.vue +49 -11
  87. package/src/components/Badge.vue +4 -7
  88. package/src/components/Card.vue +32 -2
  89. package/src/components/Dropdown.vue +14 -3
  90. package/src/components/EmptyState.vue +91 -0
  91. package/src/components/Icon/Icon.vue +118 -25
  92. package/src/components/Image.vue +70 -3
  93. package/src/components/ListItem.vue +43 -22
  94. package/src/components/Menu.vue +10 -2
  95. package/src/components/charts/BarChart.vue +197 -0
  96. package/src/components/charts/ChartTooltip.vue +74 -0
  97. package/src/components/charts/Donut.vue +219 -0
  98. package/src/components/charts/Funnel.vue +377 -0
  99. package/src/components/charts/Gauge.vue +90 -0
  100. package/src/components/charts/LineChart.vue +255 -0
  101. package/src/components/charts/RadialBars.vue +99 -0
  102. package/src/components/charts/RankBars.vue +72 -0
  103. package/src/components/charts/Sparkline.vue +90 -0
  104. package/src/components/charts/StatCard.vue +84 -0
  105. package/src/components/charts/core/data.ts +95 -0
  106. package/src/components/charts/core/format.ts +64 -0
  107. package/src/components/charts/core/palette.ts +52 -0
  108. package/src/components/charts/core/uid.ts +6 -0
  109. package/src/components/charts/core/useChartAnim.ts +60 -0
  110. package/src/components/charts/core/useChartFrame.ts +49 -0
  111. package/src/components/charts/core/useScale.ts +39 -0
  112. package/src/components/charts/index.ts +12 -0
  113. package/src/components/form/inputs/RadioGroup.vue +2 -1
  114. package/src/components/form/inputs/RangeInput.vue +43 -15
  115. package/src/components/form/inputs/SelectInput.vue +1 -19
  116. package/src/components/index.ts +3 -1
  117. package/src/components/layout/AppSidebar.vue +1 -0
  118. package/src/components/layout/Divider.vue +2 -9
  119. package/src/components/layout/SidebarNavItem.vue +82 -41
  120. package/src/components/layout/Timeline.types.ts +9 -0
  121. package/src/components/layout/Timeline.vue +54 -0
  122. package/src/components/layout/TimelineItem.vue +93 -0
  123. package/src/components/layout/index.ts +3 -0
  124. package/src/dialog/Dialog.vue +29 -1
  125. package/src/styles/bagel.css +1 -0
  126. package/src/styles/dark.css +13 -0
  127. package/src/styles/gradients.css +181 -0
  128. package/src/styles/layout.css +9 -0
  129. package/src/styles/text.css +38 -6
  130. package/src/styles/theme.css +1 -1
  131. package/dist/components/analytics/BarChart.vue.d.ts +0 -47
  132. package/dist/components/analytics/BarChart.vue.d.ts.map +0 -1
  133. package/dist/components/analytics/KpiCard.vue.d.ts +0 -24
  134. package/dist/components/analytics/KpiCard.vue.d.ts.map +0 -1
  135. package/dist/components/analytics/LineChart.vue.d.ts +0 -35
  136. package/dist/components/analytics/LineChart.vue.d.ts.map +0 -1
  137. package/dist/components/analytics/PieChart.vue.d.ts +0 -53
  138. package/dist/components/analytics/PieChart.vue.d.ts.map +0 -1
  139. package/dist/components/analytics/index.d.ts +0 -5
  140. package/dist/components/analytics/index.d.ts.map +0 -1
  141. package/src/components/analytics/BarChart.vue +0 -262
  142. package/src/components/analytics/KpiCard.vue +0 -84
  143. package/src/components/analytics/LineChart.vue +0 -357
  144. package/src/components/analytics/PieChart.vue +0 -544
  145. package/src/components/analytics/index.ts +0 -4
@@ -13,8 +13,10 @@ interface LinkWithAction extends NavLink {
13
13
 
14
14
  const props = withDefaults(defineProps<{
15
15
  link: LinkWithAction
16
- /** sidebar is expanded (true) or collapsed to icons (false) */
16
+ /** sidebar is visually expanded (stays true during the collapse transition) */
17
17
  open: boolean
18
+ /** real open state — flips immediately when the user toggles the sidebar */
19
+ realOpen?: boolean
18
20
  isMobile: boolean
19
21
  bgColor: string
20
22
  textColor: string
@@ -45,6 +47,16 @@ const isActive = computed(() => selfActive.value || (hasChildren.value && childA
45
47
  const expanded = ref(childActive.value)
46
48
  watch(childActive, v => { if (v) expanded.value = true })
47
49
 
50
+ // Collapse the open child group smoothly *before* the layout switches to the
51
+ // icon-only flyout, so parents don't jump up when the sidebar narrows. We watch
52
+ // `realOpen` (which flips immediately on toggle) rather than `open` (which stays
53
+ // true throughout the width transition), so the grid-rows collapse animation runs
54
+ // in sync with the sidebar narrowing instead of snapping at the end.
55
+ watch(() => props.realOpen, (isOpenNow) => {
56
+ if (isOpenNow) expanded.value = childActive.value
57
+ else expanded.value = false
58
+ })
59
+
48
60
  function onParentClick() {
49
61
  if (hasChildren.value) expanded.value = !expanded.value
50
62
  else props.link.action?.()
@@ -71,11 +83,10 @@ function onParentClick() {
71
83
  <!-- PARENT with children, EXPANDED sidebar -->
72
84
  <div v-else-if="open || isMobile" class="sidebar-group w100p">
73
85
  <Btn
74
- fullWidth alignTxt="start" class="flex-shrink-0 px-075 sidebar-group-toggle"
75
- :class="{ 'nav-btn-active': isActive }"
86
+ fullWidth alignTxt="start" flat class="flex-shrink-0 px-075 sidebar-group-toggle txt-start sidebar-parent"
87
+ :class="{ 'sidebar-parent-active bold': isActive }"
76
88
  :style="{
77
- backgroundColor: isActive ? activeColor : bgColor,
78
- color: isActive ? 'white' : textColor,
89
+ color: isActive ? activeColor : textColor,
79
90
  }"
80
91
  @click="onParentClick"
81
92
  >
@@ -84,19 +95,22 @@ function onParentClick() {
84
95
  <Icon name="expand_more" size="1.1" class="nav-text sidebar-chevron" :class="{ 'sidebar-chevron-open': expanded }" />
85
96
  </Btn>
86
97
  <div class="sidebar-children" :class="{ 'sidebar-children-open': expanded }">
87
- <Btn
88
- v-for="child in link.children" :key="child.to || child.label"
89
- fullWidth alignTxt="start" class="flex-shrink-0 ps-2 pe-075 sidebar-child"
90
- :class="{ 'nav-btn-active': linkActive(child) }"
91
- :style="{
92
- backgroundColor: linkActive(child) ? activeColor : 'transparent',
93
- color: linkActive(child) ? 'white' : textColor,
94
- }"
95
- :to="child.to || '/'" @click="child.action"
96
- >
97
- <Icon v-if="child.icon" :name="child.icon" size="1.05" class="opacity-7" />
98
- <span class="nav-text">{{ resolveI18n(child.label) }}</span>
99
- </Btn>
98
+ <div class="sidebar-children-inner ps-1">
99
+ <Btn
100
+ v-for="child in link.children" :key="child.to || child.label"
101
+ fullWidth alignTxt="start" class="flex-shrink-0 ps-075 pe-075 sidebar-child"
102
+ :class="{ 'bold sidebar-child-active': linkActive(child) }"
103
+ :style="{
104
+ color: linkActive(child) ? activeColor : textColor,
105
+ '--sidebar-active-color': activeColor,
106
+ }"
107
+ flat
108
+ :to="child.to || '/'" @click="child.action"
109
+ >
110
+ <Icon v-if="child.icon" :name="child.icon" size="1.05" class="opacity-7" />
111
+ <span class="nav-text">{{ resolveI18n(child.label) }}</span>
112
+ </Btn>
113
+ </div>
100
114
  </div>
101
115
  </div>
102
116
 
@@ -105,6 +119,7 @@ function onParentClick() {
105
119
  v-else
106
120
  placement="right-start"
107
121
  :triggers="['hover']"
122
+ :delay="{ show: 0, hide: 200 }"
108
123
  card
109
124
  class="w100p sidebar-flyout-trigger"
110
125
  >
@@ -122,15 +137,15 @@ function onParentClick() {
122
137
  </Btn>
123
138
  </template>
124
139
  <!-- flyout content -->
125
- <div class="sidebar-flyout p-05">
126
- <p class="sidebar-flyout-label">{{ resolveI18n(link.label) }}</p>
140
+ <div class="sidebar-flyout p-05 min-w-180px display-flex gap-025 column">
141
+ <p class="sidebar-flyout-label m-0 semi opacity-5 uppercase">{{ resolveI18n(link.label) }}</p>
127
142
  <Btn
128
143
  v-for="child in link.children" :key="child.to || child.label"
129
- fullWidth alignTxt="start" thin class="flex-shrink-0 px-075 sidebar-flyout-item"
130
- :class="{ 'nav-btn-active': linkActive(child) }"
144
+ fullWidth alignTxt="start" thin flat class="flex-shrink-0 px-075 radius-1 sidebar-child"
145
+ :class="{ 'bold sidebar-child-active': linkActive(child) }"
131
146
  :style="{
132
- backgroundColor: linkActive(child) ? activeColor : 'transparent',
133
- color: linkActive(child) ? 'white' : 'var(--bgl-text-color)',
147
+ color: linkActive(child) ? activeColor : 'var(--bgl-text-color)',
148
+ '--sidebar-active-color': activeColor,
134
149
  }"
135
150
  :to="child.to || '/'" @click="child.action"
136
151
  >
@@ -148,39 +163,65 @@ function onParentClick() {
148
163
  opacity: 0.6;
149
164
  }
150
165
  .sidebar-chevron-open {
151
- transform: rotate(180deg);
166
+ transform: rotate(180deg)!important;
152
167
  }
153
168
  .sidebar-children {
154
169
  display: grid;
155
170
  grid-template-rows: 0fr;
156
- transition: grid-template-rows 0.25s ease;
171
+ transition: grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1);
157
172
  overflow: hidden;
158
173
  }
159
174
  .sidebar-children-open {
160
175
  grid-template-rows: 1fr;
161
176
  }
162
- .sidebar-children > * {
177
+ .sidebar-children-inner {
163
178
  min-height: 0;
164
- }
165
- .sidebar-child {
166
- font-size: 0.85rem;
167
- }
168
- .sidebar-flyout {
169
- min-width: 180px;
170
179
  display: flex;
171
180
  flex-direction: column;
172
- gap: 2px;
181
+ gap: 0.125rem;
173
182
  }
183
+ .sidebar-children-open .sidebar-children-inner {
184
+ padding-block: 0.25rem;
185
+ }
186
+ /* Parent toggle — flat <Btn> with no color prop picks up a default `pair-black`
187
+ background; neutralize it so the active parent shows only colored text/icon. */
188
+ .sidebar-parent.bgl_btn {
189
+ background-color: transparent !important;
190
+ transition: background-color 0.15s ease, color 0.15s ease;
191
+ }
192
+ .sidebar-parent.bgl_btn:hover {
193
+ background-color: color-mix(in srgb, currentColor 8%, transparent) !important;
194
+ filter: none;
195
+ }
196
+
174
197
  .sidebar-flyout-label {
175
- margin: 0;
176
198
  padding: 0.25rem 0.5rem 0.375rem;
177
199
  font-size: 0.6875rem;
178
- font-weight: 600;
179
- text-transform: uppercase;
180
- letter-spacing: 0.05em;
181
- opacity: 0.45;
182
200
  }
183
- .sidebar-flyout-item {
184
- border-radius: var(--bgl-btn-border-radius);
201
+ </style>
202
+
203
+ <!-- Global (un-scoped) so the same child styling also applies inside the flyout,
204
+ which is teleported to <body> and therefore outside this component's scope. -->
205
+ <style>
206
+ .sidebar-child {
207
+ font-size: 0.85rem;
208
+ position: relative;
209
+ transition: background-color 0.15s ease, color 0.15s ease, font-weight 0.15s ease;
210
+ }
211
+ /* Neutralize the default `pair-black` background a flat <Btn> picks up when no
212
+ color prop is set, so we can control the child background ourselves. */
213
+ .sidebar-child.bgl_btn {
214
+ background-color: transparent !important;
215
+ }
216
+ /* Active child — filled with a subtle tint of the active color. */
217
+ .sidebar-child.bgl_btn.sidebar-child-active,
218
+ .sidebar-child.bgl_btn.sidebar-child-active:hover {
219
+ background-color: color-mix(in srgb, var(--sidebar-active-color) 14%, transparent) !important;
220
+ }
221
+
222
+ /* Hover indication for children (mirrors the parent feel) */
223
+ .sidebar-child.bgl_btn:hover {
224
+ background-color: color-mix(in srgb, currentColor 12%, transparent) !important;
225
+ filter: none;
185
226
  }
186
227
  </style>
@@ -0,0 +1,9 @@
1
+ import type { IconType, ThemeType } from '@bagelink/vue'
2
+
3
+ export interface TimelineEntry {
4
+ title?: string
5
+ meta?: string
6
+ icon?: IconType
7
+ color?: ThemeType
8
+ [key: string]: any
9
+ }
@@ -0,0 +1,54 @@
1
+ <script lang="ts" setup>
2
+ defineOptions({ name: 'BglTimeline' })
3
+ import { useSlots } from 'vue'
4
+ import type { TimelineEntry } from './Timeline.types'
5
+ import TimelineItem from './TimelineItem.vue'
6
+
7
+ /**
8
+ * Vertical activity / history feed. Two ways to use it:
9
+ *
10
+ * 1. Data-driven — pass `items`; each maps to a <TimelineItem>. Use the default
11
+ * scoped slot to render custom body content per entry:
12
+ * <Timeline :items="activity">
13
+ * <template #default="{ item }">{{ item.title }}</template>
14
+ * </Timeline>
15
+ *
16
+ * 2. Composition — drop <TimelineItem> children directly (omit `items`). Mark the
17
+ * last one with `last` to drop its connector, or let data-driven mode do it.
18
+ */
19
+ const { items = [] } = defineProps<{
20
+ items?: TimelineEntry[]
21
+ }>()
22
+
23
+ const slots = useSlots()
24
+ </script>
25
+
26
+ <template>
27
+ <div class="bgl-timeline grid">
28
+ <template v-if="items.length">
29
+ <TimelineItem
30
+ v-for="(item, i) in items"
31
+ :key="i"
32
+ :title="item.title"
33
+ :meta="item.meta"
34
+ :icon="item.icon"
35
+ :color="item.color"
36
+ :last="i === items.length - 1"
37
+ >
38
+ <template v-if="slots.default" #default>
39
+ <slot :item="item" :index="i" />
40
+ </template>
41
+ <template v-if="slots.meta" #meta>
42
+ <slot name="meta" :item="item" :index="i" />
43
+ </template>
44
+ </TimelineItem>
45
+ </template>
46
+ <slot v-else name="items" />
47
+ </div>
48
+ </template>
49
+
50
+ <style scoped>
51
+ .bgl-timeline {
52
+ align-content: start;
53
+ }
54
+ </style>
@@ -0,0 +1,93 @@
1
+ <script lang="ts" setup>
2
+ defineOptions({ name: 'BglTimelineItem' })
3
+ import type { IconType, ThemeType } from '@bagelink/vue'
4
+ import { Icon } from '@bagelink/vue'
5
+ import { useSlots } from 'vue'
6
+
7
+ /**
8
+ * One node in a <Timeline>: a marker (icon dot or plain dot) with a connecting
9
+ * line, a title, optional meta line, and free content via the default slot.
10
+ * - `icon` renders inside the dot; otherwise a small solid dot is shown.
11
+ * - `color` themes the dot (and icon).
12
+ * - `last` removes the connector line (auto-handled by <Timeline>).
13
+ * - Slots: `#marker` (replace the dot), `#meta`, default (body).
14
+ */
15
+ const {
16
+ title = '',
17
+ meta = '',
18
+ icon,
19
+ color = 'primary',
20
+ last = false,
21
+ } = defineProps<{
22
+ title?: string
23
+ meta?: string
24
+ icon?: IconType
25
+ color?: ThemeType
26
+ last?: boolean
27
+ }>()
28
+
29
+ const slots = useSlots()
30
+ </script>
31
+
32
+ <template>
33
+ <div class="bgl-tl-item flex gap-075" :class="{ 'bgl-tl-item--last': last }">
34
+ <div class="bgl-tl-rail flex-column align-items-center flex-shrink-0">
35
+ <span class="bgl-tl-marker flex-center" :style="{ '--tl-color': `var(--bgl-${color})` }">
36
+ <slot name="marker">
37
+ <Icon v-if="icon" :icon="icon" :size="0.85" />
38
+ <span v-else class="bgl-tl-dot" />
39
+ </slot>
40
+ </span>
41
+ <span v-if="!last" class="bgl-tl-line" />
42
+ </div>
43
+ <div class="bgl-tl-body min-w-0">
44
+ <p v-if="title || slots.title" class="bgl-tl-title m-0"><slot name="title">{{ title }}</slot></p>
45
+ <div v-if="slots.default" class="bgl-tl-content"><slot /></div>
46
+ <p v-if="meta || slots.meta" class="bgl-tl-meta m-0"><slot name="meta">{{ meta }}</slot></p>
47
+ </div>
48
+ </div>
49
+ </template>
50
+
51
+ <style scoped>
52
+ .bgl-tl-item {
53
+ min-height: 1.75rem;
54
+ }
55
+ .bgl-tl-marker {
56
+ width: 1.625rem;
57
+ height: 1.625rem;
58
+ border-radius: 50%;
59
+ background: color-mix(in srgb, var(--tl-color) 15%, transparent);
60
+ color: var(--tl-color);
61
+ }
62
+ .bgl-tl-dot {
63
+ width: 0.5rem;
64
+ height: 0.5rem;
65
+ border-radius: 50%;
66
+ background: var(--tl-color);
67
+ }
68
+ .bgl-tl-line {
69
+ width: 2px;
70
+ flex: 1;
71
+ background: var(--bgl-border-color);
72
+ margin: 2px 0;
73
+ }
74
+ .bgl-tl-body {
75
+ padding-bottom: 1rem;
76
+ }
77
+ .bgl-tl-item--last .bgl-tl-body {
78
+ padding-bottom: 0;
79
+ }
80
+ .bgl-tl-title {
81
+ font-size: 0.875rem;
82
+ line-height: 1.3;
83
+ }
84
+ .bgl-tl-meta {
85
+ font-size: 0.6875rem;
86
+ color: var(--bgl-text-soft, var(--bgl-gray));
87
+ margin-top: 0.125rem;
88
+ }
89
+ .bgl-tl-content {
90
+ font-size: 0.8125rem;
91
+ color: var(--bgl-text-soft, var(--bgl-gray));
92
+ }
93
+ </style>
@@ -12,5 +12,8 @@ export { default as SidebarMenu } from './SidebarMenu.vue'
12
12
  export { default as Skeleton } from './Skeleton.vue'
13
13
  export { default as TabbedLayout } from './TabbedLayout.vue'
14
14
  export { default as Tabs } from './Tabs.vue'
15
+ export { default as Timeline } from './Timeline.vue'
16
+ export type { TimelineEntry } from './Timeline.types'
17
+ export { default as TimelineItem } from './TimelineItem.vue'
15
18
  export { default as TabsBody } from './TabsBody.vue'
16
19
  export { default as TabsNav } from './TabsNav.vue'
@@ -19,6 +19,9 @@ const props = withDefaults(defineProps<{
19
19
  centerTitle?: boolean
20
20
  ellipsisTitle?: boolean
21
21
  closePlacement?: 'header-end' | 'header-start' | 'overlay-start' | 'overlay-end' | 'none'
22
+ /** Show a drag-handle grip at the top — the mobile bottom-sheet affordance.
23
+ * Defaults to true when position="bottom". Set false to opt out. */
24
+ grip?: boolean
22
25
  }>(), {
23
26
  width: 'm',
24
27
  position: 'center',
@@ -27,9 +30,13 @@ const props = withDefaults(defineProps<{
27
30
  thin: false,
28
31
  centerTitle: false,
29
32
  ellipsisTitle: false,
30
- closePlacement: 'header-end'
33
+ closePlacement: 'header-end',
34
+ grip: undefined,
31
35
  })
32
36
 
37
+ // Grip is the native bottom-sheet handle; default it on for bottom-positioned dialogs.
38
+ const showGrip = computed(() => props.grip ?? props.position === 'bottom')
39
+
33
40
  const emit = defineEmits<{
34
41
  'update:open': [value: boolean]
35
42
  'close': []
@@ -114,6 +121,8 @@ defineExpose({ close })
114
121
  @cancel="onCancel" @animationend="onAnimationEnd"
115
122
  >
116
123
  <div class="grid grid-dialog max-height-100-2" @click.stop>
124
+ <!-- Bottom-sheet grip handle -->
125
+ <div v-if="showGrip" class="dialog-grip" aria-hidden="true" />
117
126
  <!-- Header -->
118
127
  <header
119
128
  v-if="title || dismissable" class="flex space-between flex-shrink-0 align-items-start gap-05"
@@ -261,6 +270,25 @@ dialog.dialog-bottom.is-closing {
261
270
  animation: slide-out-bottom 0.2s ease-in forwards;
262
271
  }
263
272
 
273
+ /* Bottom sheet: round only the top corners + full-width on mobile. */
274
+ dialog.dialog-bottom {
275
+ border-end-end-radius: 0;
276
+ border-end-start-radius: 0;
277
+ }
278
+ @media (max-width: 910px) {
279
+ dialog.dialog-bottom { width: 100%; max-width: 100%; margin-inline: 0; }
280
+ }
281
+
282
+ /* Drag-handle grip for bottom sheets. */
283
+ .dialog-grip {
284
+ width: 40px;
285
+ height: 4px;
286
+ border-radius: 100px;
287
+ background: var(--bgl-border-color);
288
+ margin: 8px auto 4px;
289
+ flex-shrink: 0;
290
+ }
291
+
264
292
  /* Keyframes - shared and reusable */
265
293
  @keyframes fade-in {
266
294
  from {
@@ -33,6 +33,7 @@
33
33
  @import "colors.css";
34
34
  @import "mobileColors.css";
35
35
  @import "appearance.css";
36
+ @import "gradients.css";
36
37
 
37
38
  /* Icon font-family bindings — mirrored from Icon.vue so icons work even when
38
39
  that component's injected <style> is not yet present (e.g. Vite dev mode). */
@@ -80,6 +80,10 @@
80
80
  --bgl-skeleton-bg: var(--bgl-dm-surface-2);
81
81
  --bgl-skeleton-pulse: var(--bgl-dm-surface);
82
82
  --bgl-gray-light: var(--bgl-dm-surface-2);
83
+ /* --bgl-gray (#b7b7b7 in light) is used directly by color-gray / bg-gray and
84
+ as a neutral mid tone. On dark, keep it a readable mid-gray so color-gray
85
+ text stays legible; bg-gray fills get a dedicated darker treatment below. */
86
+ --bgl-gray: #8b8f96;
83
87
 
84
88
  /* ---- Text ------------------------------------------------------------- */
85
89
  --bgl-text-color: var(--bgl-dm-text);
@@ -269,6 +273,15 @@
269
273
  color: var(--bgl-dm-text);
270
274
  }
271
275
 
276
+ /* bg-gray is a solid light-gray (#b7b7b7) fill in light mode — used as a
277
+ neutral surface. On dark it becomes a bright patch, so remap it to a raised
278
+ dark surface (one step up from the card) like the other neutral fills. */
279
+ .bgl-dark-mode .bg-gray:not(.keep-white) {
280
+ background: var(--bgl-dm-surface-2) !important;
281
+ --alpha-color: var(--bgl-dm-surface-2);
282
+ color: var(--bgl-dm-text);
283
+ }
284
+
272
285
  .bgl-dark-mode .bg-input-white:not(.keep-white) input,
273
286
  .bgl-dark-mode .bg-input-white:not(.keep-white) textarea,
274
287
  .bgl-dark-mode .bg-input-white:not(.keep-white) .selectinput-btn,
@@ -0,0 +1,181 @@
1
+ /* ─────────────────────────────────────────────────────────────────────────────
2
+ * GRADIENTS — a small, memorable system that mirrors the color utilities.
3
+ *
4
+ * <div class="bg-gradient from-purple to-blue">
5
+ * <div class="bg-gradient to-tr from-pink to-orange">
6
+ * <div class="bg-gradient from-primary via-purple to-turquoise">
7
+ * <div class="text-gradient from-blue to-purple">Gorgeous</div>
8
+ * <div class="bg-gradient-brand"> (named presets)
9
+ *
10
+ * How it works:
11
+ * - `from-<tone>` / `via-<tone>` / `to-<tone>` set --bgl-grad-from/via/to.
12
+ * - `bg-gradient` paints them as a linear gradient (default 135°).
13
+ * - direction modifiers (to-t/-b/-l/-r/-tl/-tr/-bl/-br) override the angle.
14
+ * - `gradient-radial` / `gradient-conic` swap the gradient type.
15
+ * - `text-gradient` clips the gradient to text.
16
+ * Tokens resolve through var(--bgl-<tone>) so everything is theme + dark aware.
17
+ * ──────────────────────────────────────────────────────────────────────────── */
18
+
19
+ :root {
20
+ --bgl-grad-angle: 135deg;
21
+ --bgl-grad-from: transparent;
22
+ --bgl-grad-via: ;
23
+ --bgl-grad-to: transparent;
24
+ }
25
+
26
+ /* ── Paint ─────────────────────────────────────────────────────────────────── */
27
+ /* The stop list collapses to 2 stops when --bgl-grad-via is empty, 3 when set. */
28
+ .bg-gradient {
29
+ background-image: linear-gradient(
30
+ var(--bgl-grad-angle),
31
+ var(--bgl-grad-from),
32
+ var(--bgl-grad-via, ) var(--bgl-grad-to)
33
+ ) !important;
34
+ }
35
+ .gradient-radial.bg-gradient,
36
+ .bg-gradient.gradient-radial {
37
+ background-image: radial-gradient(
38
+ circle at var(--bgl-grad-pos, center),
39
+ var(--bgl-grad-from),
40
+ var(--bgl-grad-via, ) var(--bgl-grad-to)
41
+ ) !important;
42
+ }
43
+ .gradient-conic.bg-gradient,
44
+ .bg-gradient.gradient-conic {
45
+ background-image: conic-gradient(
46
+ from var(--bgl-grad-angle),
47
+ var(--bgl-grad-from),
48
+ var(--bgl-grad-via, ) var(--bgl-grad-to)
49
+ ) !important;
50
+ }
51
+
52
+ /* ── Direction modifiers ───────────────────────────────────────────────────── */
53
+ .to-t { --bgl-grad-angle: to top; }
54
+ .to-b { --bgl-grad-angle: to bottom; }
55
+ .to-l { --bgl-grad-angle: to left; }
56
+ .to-r { --bgl-grad-angle: to right; }
57
+ .to-tl { --bgl-grad-angle: to top left; }
58
+ .to-tr { --bgl-grad-angle: to top right; }
59
+ .to-bl { --bgl-grad-angle: to bottom left; }
60
+ .to-br { --bgl-grad-angle: to bottom right; }
61
+
62
+ /* ── Text gradient ─────────────────────────────────────────────────────────── */
63
+ .text-gradient {
64
+ background-image: linear-gradient(
65
+ var(--bgl-grad-angle),
66
+ var(--bgl-grad-from),
67
+ var(--bgl-grad-via, ) var(--bgl-grad-to)
68
+ );
69
+ background-clip: text;
70
+ -webkit-background-clip: text;
71
+ color: transparent;
72
+ -webkit-text-fill-color: transparent;
73
+ }
74
+
75
+ /* ── Stop utilities (generated per tone) ───────────────────────────────────── */
76
+ /* Base tones */
77
+ .from-primary { --bgl-grad-from: var(--bgl-primary); }
78
+ .from-blue { --bgl-grad-from: var(--bgl-blue); }
79
+ .from-green { --bgl-grad-from: var(--bgl-green); }
80
+ .from-red { --bgl-grad-from: var(--bgl-red); }
81
+ .from-yellow { --bgl-grad-from: var(--bgl-yellow); }
82
+ .from-purple { --bgl-grad-from: var(--bgl-purple); }
83
+ .from-brown { --bgl-grad-from: var(--bgl-brown); }
84
+ .from-orange { --bgl-grad-from: var(--bgl-orange); }
85
+ .from-turquoise { --bgl-grad-from: var(--bgl-turquoise); }
86
+ .from-pink { --bgl-grad-from: var(--bgl-pink); }
87
+ .from-gray { --bgl-grad-from: var(--bgl-gray); }
88
+ .from-black { --bgl-grad-from: var(--bgl-black, #000); }
89
+ .from-white { --bgl-grad-from: var(--bgl-white, #fff); }
90
+ .from-bg { --bgl-grad-from: var(--bgl-bg); }
91
+ .from-box { --bgl-grad-from: var(--bgl-box-bg); }
92
+ .from-transparent { --bgl-grad-from: transparent; }
93
+
94
+ .via-primary { --bgl-grad-via: var(--bgl-primary), ; }
95
+ .via-blue { --bgl-grad-via: var(--bgl-blue), ; }
96
+ .via-green { --bgl-grad-via: var(--bgl-green), ; }
97
+ .via-red { --bgl-grad-via: var(--bgl-red), ; }
98
+ .via-yellow { --bgl-grad-via: var(--bgl-yellow), ; }
99
+ .via-purple { --bgl-grad-via: var(--bgl-purple), ; }
100
+ .via-brown { --bgl-grad-via: var(--bgl-brown), ; }
101
+ .via-orange { --bgl-grad-via: var(--bgl-orange), ; }
102
+ .via-turquoise { --bgl-grad-via: var(--bgl-turquoise), ; }
103
+ .via-pink { --bgl-grad-via: var(--bgl-pink), ; }
104
+ .via-gray { --bgl-grad-via: var(--bgl-gray), ; }
105
+ .via-transparent { --bgl-grad-via: transparent, ; }
106
+
107
+ .to-primary { --bgl-grad-to: var(--bgl-primary); }
108
+ .to-blue { --bgl-grad-to: var(--bgl-blue); }
109
+ .to-green { --bgl-grad-to: var(--bgl-green); }
110
+ .to-red { --bgl-grad-to: var(--bgl-red); }
111
+ .to-yellow { --bgl-grad-to: var(--bgl-yellow); }
112
+ .to-purple { --bgl-grad-to: var(--bgl-purple); }
113
+ .to-brown { --bgl-grad-to: var(--bgl-brown); }
114
+ .to-orange { --bgl-grad-to: var(--bgl-orange); }
115
+ .to-turquoise { --bgl-grad-to: var(--bgl-turquoise); }
116
+ .to-pink { --bgl-grad-to: var(--bgl-pink); }
117
+ .to-gray { --bgl-grad-to: var(--bgl-gray); }
118
+ .to-black { --bgl-grad-to: var(--bgl-black, #000); }
119
+ .to-white { --bgl-grad-to: var(--bgl-white, #fff); }
120
+ .to-bg { --bgl-grad-to: var(--bgl-bg); }
121
+ .to-box { --bgl-grad-to: var(--bgl-box-bg); }
122
+ .to-transparent { --bgl-grad-to: transparent; }
123
+
124
+ /* Common tint/light/dark stops (compose with from-/to-) */
125
+ .from-primary-tint { --bgl-grad-from: var(--bgl-primary-tint); }
126
+ .to-primary-tint { --bgl-grad-to: var(--bgl-primary-tint); }
127
+ .from-blue-tint { --bgl-grad-from: var(--bgl-blue-tint); }
128
+ .to-blue-tint { --bgl-grad-to: var(--bgl-blue-tint); }
129
+
130
+ /* ── Named presets — the gorgeous, copy-pasted brand sweeps, tokenized ──────── */
131
+ .bg-gradient-brand {
132
+ background-image: linear-gradient(135deg, var(--bgl-primary), var(--bgl-purple)) !important;
133
+ }
134
+ .bg-gradient-sunset {
135
+ background-image: linear-gradient(135deg, var(--bgl-orange), var(--bgl-pink)) !important;
136
+ }
137
+ .bg-gradient-ocean {
138
+ background-image: linear-gradient(135deg, var(--bgl-blue), var(--bgl-turquoise)) !important;
139
+ }
140
+ .bg-gradient-dusk {
141
+ background-image: linear-gradient(135deg, var(--bgl-purple), var(--bgl-blue)) !important;
142
+ }
143
+ .bg-gradient-citrus {
144
+ background-image: linear-gradient(135deg, var(--bgl-yellow), var(--bgl-orange)) !important;
145
+ }
146
+ /* Subtle surface sweep for cards/panels — dark-mode aware via tokens. */
147
+ .bg-gradient-surface {
148
+ background-image: linear-gradient(160deg,
149
+ color-mix(in srgb, var(--bgl-box-bg) 100%, transparent),
150
+ color-mix(in srgb, var(--bgl-primary) 6%, var(--bgl-box-bg))) !important;
151
+ }
152
+ /* Multi-radial "mesh" hero background built from the brand palette. */
153
+ .bg-gradient-mesh {
154
+ background-color: var(--bgl-box-bg) !important;
155
+ background-image:
156
+ radial-gradient(at 0% 0%, color-mix(in srgb, var(--bgl-primary) 35%, transparent) 0px, transparent 50%),
157
+ radial-gradient(at 100% 0%, color-mix(in srgb, var(--bgl-purple) 30%, transparent) 0px, transparent 50%),
158
+ radial-gradient(at 100% 100%, color-mix(in srgb, var(--bgl-turquoise) 25%, transparent) 0px, transparent 50%),
159
+ radial-gradient(at 0% 100%, color-mix(in srgb, var(--bgl-pink) 25%, transparent) 0px, transparent 50%) !important;
160
+ }
161
+
162
+ /* Soft fade for chart/sparkline fills: tone at top → transparent. Pair with a
163
+ from-<tone> and a direction, e.g. `bg-gradient-fade to-b from-blue`. */
164
+ .bg-gradient-fade {
165
+ --bgl-grad-to: transparent;
166
+ background-image: linear-gradient(var(--bgl-grad-angle),
167
+ color-mix(in srgb, var(--bgl-grad-from) 22%, transparent),
168
+ transparent) !important;
169
+ }
170
+
171
+ @media (prefers-reduced-motion: no-preference) {
172
+ .bg-gradient-animate {
173
+ background-size: 200% 200% !important;
174
+ animation: bgl-grad-pan 8s ease infinite;
175
+ }
176
+ }
177
+ @keyframes bgl-grad-pan {
178
+ 0% { background-position: 0% 50%; }
179
+ 50% { background-position: 100% 50%; }
180
+ 100% { background-position: 0% 50%; }
181
+ }
@@ -3949,6 +3949,15 @@
3949
3949
  flex-shrink: 0;
3950
3950
  }
3951
3951
 
3952
+ .min-w-0 {
3953
+ /* shrink below content size for ellipsis truncation in flex rows */
3954
+ min-width: 0;
3955
+ }
3956
+
3957
+ .min-h-0 {
3958
+ min-height: 0;
3959
+ }
3960
+
3952
3961
  .flex-grow-2 {
3953
3962
  flex-grow: 2;
3954
3963
  }