@leavepulse/ui 0.24.1 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/assets/LpNumberFlow-rZkwQwlU.css +116 -0
  2. package/dist/assets/LpRollingText-CR1mb0Vt.css +74 -0
  3. package/dist/assets/LpSkeleton-D7xU8IPV.css +29 -0
  4. package/dist/assets/LpSparkline-0mKTq7pb.css +51 -0
  5. package/dist/assets/LpTooltip-B61SYd4K.css +17 -0
  6. package/dist/assets/LpUptimeBar-B98o9krI.css +18 -0
  7. package/dist/chunks/LpChart-tNXs1ttr.js +671 -0
  8. package/dist/chunks/{LpConfirmDialog-CObRah9c.js → LpConfirmDialog-CjOGcj1e.js} +32 -4
  9. package/dist/chunks/{LpFileTree-H7Ho8uoq.js → LpFileTree-BaV7f0O7.js} +1 -1
  10. package/dist/chunks/{LpFileTreeNode-BEZIvxHi.js → LpFileTreeNode-BzpBa7R7.js} +5 -7
  11. package/dist/chunks/{LpLogViewer-B1aLUVdz.js → LpLogViewer-CcQ3s3Fp.js} +2 -1
  12. package/dist/chunks/{LpMapPicker-DFKrd1Ne.js → LpMapPicker-CXvoTrAe.js} +2 -4
  13. package/dist/chunks/{LpNotificationBell-X1M30e0X.js → LpNotificationBell-Dvu-b6Vn.js} +56 -27
  14. package/dist/chunks/LpNumberFlow-amWYaeuA.js +221 -0
  15. package/dist/chunks/LpPie-BkjWlfSw.js +525 -0
  16. package/dist/chunks/LpProgress-DoIP4jQR.js +198 -0
  17. package/dist/chunks/LpRollingText-CM5gkSeF.js +50 -0
  18. package/dist/chunks/LpSkeleton-t5mMYl3a.js +29 -0
  19. package/dist/chunks/LpSparkline-C6xQ2dwv.js +175 -0
  20. package/dist/chunks/{LpStat-CdbEFOvn.js → LpStat-CDanpzU0.js} +43 -5
  21. package/dist/chunks/{LpThemeSwitcher-BgpJgrnP.js → LpThemeSwitcher-7mAK6bI7.js} +2 -4
  22. package/dist/chunks/LpTooltip-hCLPAqMG.js +131 -0
  23. package/dist/chunks/LpUptimeBar-DcQ8HikG.js +152 -0
  24. package/dist/component-names.d.ts +1 -1
  25. package/dist/components/LpChart.vue.d.ts +88 -0
  26. package/dist/components/LpChart.vue.js +4 -0
  27. package/dist/components/LpConfirmDialog.vue.d.ts +18 -2
  28. package/dist/components/LpConfirmDialog.vue.js +1 -1
  29. package/dist/components/LpFileTree.vue.d.ts +1 -1
  30. package/dist/components/LpFileTree.vue.js +1 -1
  31. package/dist/components/LpFileTreeNode.vue.js +1 -1
  32. package/dist/components/LpLightbox.vue.d.ts +1 -1
  33. package/dist/components/LpLogViewer.vue.d.ts +1 -1
  34. package/dist/components/LpLogViewer.vue.js +1 -1
  35. package/dist/components/LpMapPicker.vue.js +1 -1
  36. package/dist/components/LpNotificationBell.vue.d.ts +12 -0
  37. package/dist/components/LpNotificationBell.vue.js +1 -1
  38. package/dist/components/LpNumberFlow.vue.d.ts +35 -0
  39. package/dist/components/LpNumberFlow.vue.js +4 -0
  40. package/dist/components/LpPie.vue.d.ts +134 -0
  41. package/dist/components/LpPie.vue.js +4 -0
  42. package/dist/components/LpProgress.vue.d.ts +56 -2
  43. package/dist/components/LpProgress.vue.js +1 -1
  44. package/dist/components/LpRollingText.vue.d.ts +33 -0
  45. package/dist/components/LpRollingText.vue.js +4 -0
  46. package/dist/components/LpSkeleton.vue.d.ts +11 -1
  47. package/dist/components/LpSkeleton.vue.js +1 -1
  48. package/dist/components/LpSparkline.vue.d.ts +48 -0
  49. package/dist/components/LpSparkline.vue.js +4 -0
  50. package/dist/components/LpStat.vue.d.ts +25 -2
  51. package/dist/components/LpStat.vue.js +1 -1
  52. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  53. package/dist/components/LpTooltip.vue.d.ts +24 -3
  54. package/dist/components/LpTooltip.vue.js +1 -1
  55. package/dist/components/LpUptimeBar.vue.js +1 -1
  56. package/dist/components/chart.d.ts +180 -0
  57. package/dist/components/chart.js +353 -0
  58. package/dist/composables/easing.d.ts +46 -0
  59. package/dist/composables/easing.js +23 -0
  60. package/dist/composables/usePillTransition.d.ts +13 -1
  61. package/dist/composables/usePillTransition.js +22 -2
  62. package/dist/composables/useReveal.d.ts +26 -0
  63. package/dist/composables/useReveal.js +45 -0
  64. package/dist/composables/useSettled.js +2 -2
  65. package/dist/composables/useShift.js +2 -4
  66. package/dist/index.d.ts +14 -0
  67. package/dist/index.js +125 -102
  68. package/package.json +2 -1
  69. package/src/component-names.ts +5 -0
  70. package/src/components/LpChart.vue +846 -0
  71. package/src/components/LpConfirmDialog.vue +49 -0
  72. package/src/components/LpFileTreeNode.vue +5 -9
  73. package/src/components/LpLogViewer.vue +2 -3
  74. package/src/components/LpMapPicker.vue +2 -5
  75. package/src/components/LpNotificationBell.vue +76 -15
  76. package/src/components/LpNumberFlow.vue +524 -0
  77. package/src/components/LpPie.vue +778 -0
  78. package/src/components/LpProgress.vue +283 -6
  79. package/src/components/LpRollingText.vue +157 -0
  80. package/src/components/LpSkeleton.vue +81 -8
  81. package/src/components/LpSparkline.vue +312 -0
  82. package/src/components/LpStat.vue +52 -4
  83. package/src/components/LpTooltip.vue +185 -3
  84. package/src/components/LpUptimeBar.vue +150 -53
  85. package/src/components/chart.ts +686 -0
  86. package/src/composables/easing.ts +88 -0
  87. package/src/composables/usePillTransition.ts +71 -6
  88. package/src/composables/useReveal.ts +113 -0
  89. package/src/composables/useSettled.ts +2 -4
  90. package/src/composables/useShift.ts +2 -8
  91. package/src/index.ts +19 -0
  92. package/src/theme/useTheme.ts +2 -8
  93. package/src/tokens/tokens.css +36 -0
  94. package/dist/chunks/LpProgress-BhI0IbjC.js +0 -37
  95. package/dist/chunks/LpSkeleton-eSFIo4kD.js +0 -22
  96. package/dist/chunks/LpTooltip-D1hbjQWG.js +0 -63
  97. package/dist/chunks/LpUptimeBar-CB366k0O.js +0 -127
@@ -1,13 +1,8 @@
1
1
  <script setup lang="ts">
2
- import {
3
- TooltipContent,
4
- TooltipPortal,
5
- TooltipProvider,
6
- TooltipRoot,
7
- TooltipTrigger,
8
- } from "reka-ui"
9
- import { computed } from "vue"
10
- import { PANEL_SURFACE, TOOLTIP_ANIM } from "./dropdown"
2
+ import { computed, ref } from "vue"
3
+ import LpNumberFlow from "./LpNumberFlow.vue"
4
+ import LpRollingText from "./LpRollingText.vue"
5
+ import LpTooltip from "./LpTooltip.vue"
11
6
 
12
7
  export type UptimeStatus = "operational" | "degraded" | "down" | "maintenance" | "empty"
13
8
 
@@ -40,14 +35,24 @@ const props = withDefaults(
40
35
  { showUptime: true },
41
36
  )
42
37
 
38
+ /*
39
+ * Green for healthy, and at full strength — the convention every status page
40
+ * shares, so a wall of green reads instantly as "fine" and the eye only has to
41
+ * find the slices that are not green. (Dimming the healthy days was tried and
42
+ * made the whole row look faded rather than calm.)
43
+ */
43
44
  const defaultColor: Record<UptimeStatus, string> = {
44
- operational: "var(--color-brand)",
45
+ operational: "var(--color-action)",
45
46
  degraded: "var(--color-accent)",
46
47
  down: "var(--color-danger)",
47
- maintenance: "var(--color-action)",
48
+ maintenance: "var(--color-brand)",
48
49
  empty: "var(--color-surface-soft)",
49
50
  }
50
51
 
52
+ /* Only "no data yet" is held back; a real reading is always full strength. */
53
+ const opacityFor = (status: UptimeStatus): number =>
54
+ status === "empty" ? 0.4 : 1
55
+
51
56
  const colorFor = (status: UptimeStatus): string =>
52
57
  props.colors?.[status] ?? defaultColor[status]
53
58
 
@@ -67,60 +72,152 @@ const uptimePct = computed(() => {
67
72
  return (up / tracked.length) * 100
68
73
  })
69
74
 
70
- const uptimeText = computed(() => {
75
+ /*
76
+ * Places actually needed, so trailing zeros stay trimmed: 100 rather than
77
+ * 100.00, 99.9 rather than 99.90. The counter needs a fixed count per render —
78
+ * it cannot "trim" mid-roll without changing the row's shape — so it is derived
79
+ * from the value instead of hard-coded at two.
80
+ */
81
+ /*
82
+ * The gap is drawn INSIDE each slice, not between slices.
83
+ *
84
+ * A flex `gap` looked right and measured wrong: the slices divide a fractional
85
+ * remainder, so each one's edges land on a different side of a device pixel and
86
+ * a single 2.59px gap was painted as a mix of 2px and 3px down the row — the
87
+ * bar kept losing its rhythm no matter what the gap was set to, including a
88
+ * whole number of pixels.
89
+ *
90
+ * Giving every slice the same full share and inset-ing its paint by a
91
+ * percentage of that share moves the rounding INTO a mark that is uniform by
92
+ * construction: whatever each slot rounds to, the visible bar and the space
93
+ * beside it round with it, together.
94
+ */
95
+
96
+ /** Slice width as a fraction of its slot; the remainder is the gap. Mirrored by
97
+ * `.lp-uptime__mark` in the stylesheet, which owns the resting state. */
98
+ const MARK_WIDTH = 0.64
99
+
100
+ const hovered = ref<number | null>(null)
101
+
102
+ /* Sticky-ish read of the hovered slice, so the panel has content to show while
103
+ it is closing rather than emptying first. */
104
+ const tip = computed(() => {
105
+ const i = hovered.value
106
+ if (i == null) return null
107
+ const seg = props.segments[i]
108
+ if (!seg) return null
109
+ return { status: seg.status, text: seg.label ?? statusLabel[seg.status] }
110
+ })
111
+
112
+ const uptimeDecimals = computed(() => {
71
113
  const v = uptimePct.value
72
- if (v == null) return "—"
73
- // Trim trailing zeros: 100, 99.98, 99.9.
74
- return `${Number(v.toFixed(2))}%`
114
+ if (v == null) return 0
115
+ const trimmed = Number(v.toFixed(2))
116
+ return Number.isInteger(trimmed) ? 0 : String(trimmed).split(".")[1].length
75
117
  })
76
118
  </script>
77
119
 
78
120
  <template>
79
121
  <div class="flex flex-col gap-1.5">
80
- <div v-if="showUptime || title" class="flex items-baseline justify-between gap-3 text-xs">
81
- <span v-if="title" class="font-medium text-ink">{{ title }}</span>
82
- <span v-else />
83
- <span v-if="showUptime" class="tabular-nums text-muted-strong">
84
- {{ uptimeText }}<span class="text-muted"> uptime</span>
85
- </span>
122
+ <div v-if="title" class="text-xs">
123
+ <span class="font-medium text-ink">{{ title }}</span>
86
124
  </div>
87
125
 
88
- <TooltipProvider :delay-duration="120">
89
- <div class="flex h-7 items-stretch gap-0.5">
90
- <TooltipRoot v-for="(seg, i) in segments" :key="i">
91
- <TooltipTrigger as-child>
92
- <button
93
- type="button"
94
- class="min-w-0 flex-1 rounded-xs outline-none transition-[scale,opacity] duration-150 hover:scale-y-110 focus-visible:ring-2 focus-visible:ring-ring"
95
- :class="seg.status === 'empty' ? 'opacity-50' : ''"
96
- :style="{ backgroundColor: colorFor(seg.status) }"
97
- :aria-label="seg.label ?? statusLabel[seg.status]"
98
- />
99
- </TooltipTrigger>
100
- <TooltipPortal>
101
- <TooltipContent
102
- :side-offset="6"
103
- :class="[PANEL_SURFACE, TOOLTIP_ANIM, 'z-(--z-tooltip) px-2.5 py-1.5 text-xs text-ink']"
104
- >
105
- <span class="flex items-center gap-1.5">
106
- <span
107
- class="size-1.5 shrink-0 rounded-full"
108
- :style="{ backgroundColor: colorFor(seg.status) }"
109
- />
110
- {{ seg.label ?? statusLabel[seg.status] }}
111
- </span>
112
- </TooltipContent>
113
- </TooltipPortal>
114
- </TooltipRoot>
126
+ <!--
127
+ One tooltip for the whole bar, following the pointer, rather than one per
128
+ slice. At sixty slices a few pixels wide, a tip anchored to each would
129
+ jump from box to box as the pointer travelled; following it reads as a
130
+ single readout being scrubbed along the timeline. It also means the kit's
131
+ tooltip is used here rather than a second, hand-wired copy of reka.
132
+ -->
133
+ <LpTooltip :delay="80" follow-cursor side="top" :disabled="hovered === null">
134
+ <template #content>
135
+ <span v-if="tip" class="flex items-center gap-1.5">
136
+ <span
137
+ class="size-1.5 shrink-0 rounded-full"
138
+ :style="{ backgroundColor: colorFor(tip.status) }"
139
+ />
140
+ <LpRollingText :value="tip.text" :duration="180" align="start" />
141
+ </span>
142
+ </template>
143
+
144
+ <!--
145
+ Mark-to-gap is a RATIO, not a pixel count. The reference bar is ~800px
146
+ wide and can spare 3.6px between 5.4px marks; the same 3px inside a
147
+ 384px card left the marks thinner than the spaces between them. The gap
148
+ is therefore derived from how many slices are being drawn, and clamped
149
+ so a year's worth doesn't lose its gaps and a week's doesn't grow
150
+ canyons.
151
+ -->
152
+ <div class="lp-uptime flex h-[34px] items-stretch" @pointerleave="hovered = null">
153
+ <button
154
+ v-for="(seg, i) in segments"
155
+ :key="i"
156
+ type="button"
157
+ class="lp-uptime__slot min-w-0 flex-1 outline-none focus-visible:ring-2 focus-visible:ring-ring"
158
+ :aria-label="seg.label ?? statusLabel[seg.status]"
159
+ @pointerenter="hovered = i"
160
+ >
161
+ <span
162
+ class="lp-uptime__mark block h-full rounded-[1px] transition-[opacity,scale] duration-[var(--duration-fast)] motion-reduce:transition-none"
163
+ :style="{
164
+ backgroundColor: colorFor(seg.status),
165
+ opacity: hovered === i ? 1 : opacityFor(seg.status),
166
+ // Both axes every time: the x factor is what holds the gap open
167
+ // (see the stylesheet), so a hover that set only y would widen
168
+ // the slice into its neighbours' spacing.
169
+ scale: hovered === i ? `${MARK_WIDTH} 1.12` : undefined,
170
+ }"
171
+ />
172
+ </button>
115
173
  </div>
116
- </TooltipProvider>
174
+ </LpTooltip>
117
175
 
176
+ <!--
177
+ The figure sits BETWEEN the two dates rather than above the bar: it is a
178
+ summary of exactly the span they bracket, and the rules on either side say
179
+ so. This is the arrangement status pages settled on, and it also frees the
180
+ top line to be nothing but the component's name.
181
+ -->
118
182
  <div
119
- v-if="startLabel || endLabel"
120
- class="flex items-center justify-between text-xs text-muted"
183
+ v-if="startLabel || endLabel || showUptime"
184
+ class="flex items-center gap-2 text-xs text-muted"
121
185
  >
122
- <span>{{ startLabel }}</span>
123
- <span>{{ endLabel }}</span>
186
+ <span class="shrink-0">{{ startLabel }}</span>
187
+ <span class="h-px flex-1 bg-line" />
188
+ <span v-if="showUptime" class="shrink-0 text-muted-strong">
189
+ <LpNumberFlow
190
+ v-if="uptimePct != null"
191
+ :value="uptimePct"
192
+ :decimals="uptimeDecimals"
193
+ suffix="%"
194
+ />
195
+ <template v-else>—</template>
196
+ <span class="text-muted"> uptime</span>
197
+ </span>
198
+ <span class="h-px flex-1 bg-line" />
199
+ <span class="shrink-0">{{ endLabel }}</span>
124
200
  </div>
125
201
  </div>
126
202
  </template>
203
+
204
+
205
+ <style>
206
+ /*
207
+ * The slot is the full share of the row; the mark inside it is narrowed to a
208
+ * FRACTION of that share, which is what produces the space between days.
209
+ * Because the inset scales with the slot, the mark-to-gap ratio holds at any
210
+ * width and any number of slices, and both round to device pixels together
211
+ * instead of drifting apart.
212
+ *
213
+ * A horizontal scale, not padding. Percentage padding resolves against the
214
+ * PARENT's width, never the element's own — so `padding-inline: 18%` on a slot
215
+ * of a 384px bar asked for 69px of padding on each side of a 3.2px slice. The
216
+ * marks collapsed to zero width and the bar vanished, while flex stretched the
217
+ * slots to fit the padding. `scale` is one of the few things that is genuinely
218
+ * relative to the box itself.
219
+ */
220
+ .lp-uptime__mark {
221
+ scale: 0.64 1;
222
+ }
223
+ </style>