@vllnt/ui 0.1.11-canary.6589a21 → 0.1.11-canary.db315bc

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 (100) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +106 -1
  3. package/dist/components/activity-heatmap/activity-heatmap.js +168 -0
  4. package/dist/components/activity-heatmap/index.js +6 -0
  5. package/dist/components/activity-log/activity-log.js +256 -0
  6. package/dist/components/activity-log/index.js +6 -0
  7. package/dist/components/ai-chat-input/ai-chat-input.js +107 -0
  8. package/dist/components/ai-chat-input/index.js +4 -0
  9. package/dist/components/ai-message-bubble/ai-message-bubble.js +119 -0
  10. package/dist/components/ai-message-bubble/index.js +6 -0
  11. package/dist/components/ai-source-citation/ai-source-citation.js +39 -0
  12. package/dist/components/ai-source-citation/index.js +6 -0
  13. package/dist/components/ai-streaming-text/ai-streaming-text.js +41 -0
  14. package/dist/components/ai-streaming-text/index.js +6 -0
  15. package/dist/components/ai-tool-call-display/ai-tool-call-display.js +93 -0
  16. package/dist/components/ai-tool-call-display/index.js +6 -0
  17. package/dist/components/animated-text/animated-text.js +328 -0
  18. package/dist/components/animated-text/index.js +4 -0
  19. package/dist/components/annotation/annotation.js +49 -0
  20. package/dist/components/annotation/index.js +8 -0
  21. package/dist/components/avatar-group/avatar-group.js +82 -0
  22. package/dist/components/avatar-group/index.js +10 -0
  23. package/dist/components/border-beam/border-beam.js +51 -0
  24. package/dist/components/border-beam/index.js +4 -0
  25. package/dist/components/candlestick-chart/candlestick-chart.js +215 -0
  26. package/dist/components/candlestick-chart/index.js +6 -0
  27. package/dist/components/combobox/combobox.js +130 -0
  28. package/dist/components/combobox/index.js +4 -0
  29. package/dist/components/countdown-timer/countdown-timer.js +184 -0
  30. package/dist/components/countdown-timer/index.js +4 -0
  31. package/dist/components/credit-badge/credit-badge.js +59 -0
  32. package/dist/components/credit-badge/index.js +6 -0
  33. package/dist/components/data-list/data-list.js +99 -0
  34. package/dist/components/data-list/index.js +16 -0
  35. package/dist/components/data-table/data-table.js +242 -0
  36. package/dist/components/data-table/index.js +6 -0
  37. package/dist/components/date-picker/date-picker.js +74 -0
  38. package/dist/components/date-picker/index.js +4 -0
  39. package/dist/components/file-upload/file-upload.js +227 -0
  40. package/dist/components/file-upload/index.js +4 -0
  41. package/dist/components/flashcard/flashcard.js +66 -0
  42. package/dist/components/flashcard/index.js +4 -0
  43. package/dist/components/index.js +172 -1
  44. package/dist/components/live-feed/index.js +4 -0
  45. package/dist/components/live-feed/live-feed.js +168 -0
  46. package/dist/components/market-treemap/index.js +6 -0
  47. package/dist/components/market-treemap/market-treemap.js +100 -0
  48. package/dist/components/marquee/index.js +4 -0
  49. package/dist/components/marquee/marquee.js +98 -0
  50. package/dist/components/metric-gauge/index.js +6 -0
  51. package/dist/components/metric-gauge/metric-gauge.js +213 -0
  52. package/dist/components/model-selector/model-selector.js +11 -2
  53. package/dist/components/number-input/index.js +4 -0
  54. package/dist/components/number-input/number-input.js +167 -0
  55. package/dist/components/number-ticker/index.js +4 -0
  56. package/dist/components/number-ticker/number-ticker.js +63 -0
  57. package/dist/components/order-book/index.js +6 -0
  58. package/dist/components/order-book/order-book.js +128 -0
  59. package/dist/components/password-input/index.js +4 -0
  60. package/dist/components/password-input/password-input.js +45 -0
  61. package/dist/components/plan-badge/index.js +6 -0
  62. package/dist/components/plan-badge/plan-badge.js +67 -0
  63. package/dist/components/rating/index.js +4 -0
  64. package/dist/components/rating/rating.js +121 -0
  65. package/dist/components/role-badge/index.js +6 -0
  66. package/dist/components/role-badge/role-badge.js +50 -0
  67. package/dist/components/scope-selector/index.js +6 -0
  68. package/dist/components/scope-selector/scope-selector.js +336 -0
  69. package/dist/components/severity-badge/index.js +8 -0
  70. package/dist/components/severity-badge/severity-badge.js +163 -0
  71. package/dist/components/sparkline-grid/index.js +6 -0
  72. package/dist/components/sparkline-grid/sparkline-grid.js +92 -0
  73. package/dist/components/spinner/index.js +5 -1
  74. package/dist/components/spinner/unicode-spinner.js +708 -0
  75. package/dist/components/stat-card/index.js +5 -0
  76. package/dist/components/stat-card/stat-card.js +102 -0
  77. package/dist/components/status-board/index.js +6 -0
  78. package/dist/components/status-board/status-board.js +138 -0
  79. package/dist/components/status-indicator/index.js +10 -0
  80. package/dist/components/status-indicator/status-indicator.js +175 -0
  81. package/dist/components/stepper/index.js +4 -0
  82. package/dist/components/stepper/stepper.js +117 -0
  83. package/dist/components/subscription-card/index.js +6 -0
  84. package/dist/components/subscription-card/subscription-card.js +161 -0
  85. package/dist/components/ticker-tape/index.js +6 -0
  86. package/dist/components/ticker-tape/ticker-tape.js +106 -0
  87. package/dist/components/tour/index.js +4 -0
  88. package/dist/components/tour/tour.js +157 -0
  89. package/dist/components/usage-breakdown/index.js +6 -0
  90. package/dist/components/usage-breakdown/usage-breakdown.js +140 -0
  91. package/dist/components/wallet-card/index.js +4 -0
  92. package/dist/components/wallet-card/wallet-card.js +115 -0
  93. package/dist/components/watchlist/index.js +6 -0
  94. package/dist/components/watchlist/watchlist.js +110 -0
  95. package/dist/components/world-clock-bar/index.js +6 -0
  96. package/dist/components/world-clock-bar/world-clock-bar.js +101 -0
  97. package/dist/index.d.ts +1173 -7
  98. package/dist/test-setup.js +19 -0
  99. package/package.json +27 -6
  100. package/styles.css +55 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,104 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@vllnt/ui` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.2.0] - 2026-04-21
8
+
9
+ ### Added
10
+
11
+ - **AI family** — `AiChatInput`, `AiMessageBubble`, `AiSourceCitation`, `AiStreamingText`, `AiToolCallDisplay`, `ThinkingBlock`, `ModelSelector`.
12
+ - **Financial family** — `CandlestickChart`, `MarketTreemap`, `OrderBook`, `TickerTape`, `SparklineGrid`, `WalletCard`, `Watchlist`.
13
+ - **Ops / Status family** — `StatusBoard`, `StatusIndicator`, `LiveFeed`, `WorldClockBar`, `SeverityBadge`, `RoleBadge`, `ScopeSelector`.
14
+ - **Billing & Plans family** — `SubscriptionCard`, `PlanBadge`, `CreditBadge`, `UsageBreakdown`.
15
+ - **Animation family** — `AnimatedText`, `BorderBeam`, `Marquee`, `NumberTicker`, and a standalone spinner library exported via `Spinner`.
16
+ - **Educational family** — `Flashcard`, `Stepper`, `Tour`, `Annotation`, `CompletionDialog`, `TruncatedText`, `TableOfContentsPanel`.
17
+ - **Form additions** — `DatePicker`, `FileUpload`, `NumberInput`, `PasswordInput`, `InlineInput`, `Combobox`, `Rating`.
18
+ - **Data / metric additions** — `DataTable`, `DataList`, `StatCard`, `MetricGauge`, `ActivityHeatmap`, `ActivityLog`.
19
+ - **App shell additions** — `CategoryFilter`, `FilterBar`, `CookieConsent`, `Slideshow`, `CountdownTimer`, `AvatarGroup`, `FloatingActionButton`, `SocialFab`, `KeyboardShortcutsHelp`, `ShareDialog`, `HorizontalScrollRow`, `ViewSwitcher`.
20
+ - Total component count: **144** (up from 93).
21
+ - Full Storybook integration with E2E smoke tests in CI.
22
+ - OG image generation for every registry page.
23
+ - Signed provenance attestations on every published artifact (sigstore).
24
+
25
+ ### Changed
26
+
27
+ - Documentation domain moved to `ui.vllnt.ai` / `storybook.vllnt.ai`.
28
+ - Default package registry is the public npm registry (no longer GitHub Packages).
29
+ - README component tables expanded to cover all 144 components organized by family.
30
+
31
+ ### Fixed
32
+
33
+ - Publish workflow now uses `npx --yes npm@latest publish` so OIDC trusted publishing survives GitHub runners that ship pre-11.5.1 npm.
34
+
35
+ ## [0.1.11] - 2026-04 *(last canary-era release)*
36
+
37
+ ### Fixed
38
+
39
+ - Re-extract tarball path from `pnpm pack` output for publish step.
40
+
41
+ ## [0.1.10]
42
+
43
+ ### Changed
44
+
45
+ - Switched to `pnpm pack` + `npm publish` flow to support `publishConfig` with OIDC auth.
46
+
47
+ ## [0.1.9]
48
+
49
+ ### Fixed
50
+
51
+ - Use `pnpm publish` and repair release creation step in CI.
52
+
53
+ ## [0.1.8]
54
+
55
+ ### Fixed
56
+
57
+ - Annotated tags + `--notes-file` for release creation.
58
+
59
+ ## [0.1.7]
60
+
61
+ ### Added
62
+
63
+ - `HorizontalScrollRow`, `ViewSwitcher`, and `useHorizontalScroll` hook.
64
+
65
+ ### Fixed
66
+
67
+ - `Comparison`: defensive prop validation to prevent runtime crash on malformed input.
68
+
69
+ ## [0.1.6]
70
+
71
+ ### Fixed
72
+
73
+ - Bundle-free build to preserve per-file `"use client"` directives in published chunks.
74
+
75
+ ## [0.1.5]
76
+
77
+ ### Fixed
78
+
79
+ - Add `"use client"` to all dist JS including `index.js`.
80
+
81
+ ## [0.1.4]
82
+
83
+ ### Fixed
84
+
85
+ - Exclude `index.js` from `"use client"` banner.
86
+
87
+ ## [0.1.3]
88
+
89
+ ### Fixed
90
+
91
+ - Add `"use client"` directive to published dist chunks.
92
+
93
+ ## [0.1.2]
94
+
95
+ ### Added
96
+
97
+ - Initial public publish to the public npm registry.
98
+
99
+ [0.2.0]: https://github.com/vllnt/ui/releases/tag/v0.2.0
100
+ [0.1.11]: https://github.com/vllnt/ui/releases/tag/v0.1.11
101
+ [0.1.10]: https://github.com/vllnt/ui/releases/tag/v0.1.10
102
+ [0.1.9]: https://github.com/vllnt/ui/releases/tag/v0.1.9
103
+ [0.1.8]: https://github.com/vllnt/ui/releases/tag/v0.1.8
104
+ [0.1.2]: https://github.com/vllnt/ui/releases/tag/v0.1.2
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @vllnt/ui
2
2
 
3
- React component library — 93 components built on Radix UI primitives, Tailwind CSS, and CVA.
3
+ React component library — 144 components built on Radix UI primitives, Tailwind CSS, and CVA.
4
+
5
+ See [CHANGELOG.md](./CHANGELOG.md) for release history.
4
6
 
5
7
  ## Install
6
8
 
@@ -271,6 +273,109 @@ import {
271
273
  | `CodePlayground` | `{ CodePlayground, FileTree }` | Code editor with file tree |
272
274
  | `Terminal` | `{ Terminal, SimpleTerminal }` | Terminal emulator UI |
273
275
  | `VideoEmbed` | `{ VideoEmbed }` | Responsive video embed |
276
+ | `Flashcard` | `{ Flashcard }` | Flip-card for spaced repetition |
277
+ | `Stepper` | `{ Stepper }` | Linear progress stepper |
278
+ | `Tour` | `{ Tour }` | Guided product tour |
279
+ | `Annotation` | `{ Annotation }` | Inline annotation / highlight |
280
+ | `CompletionDialog` | `{ CompletionDialog }` | End-of-flow celebration dialog |
281
+ | `TruncatedText` | `{ TruncatedText }` | Expand-on-overflow text block |
282
+ | `TableOfContentsPanel` | `{ TableOfContentsPanel }` | Sidebar TOC panel |
283
+ | `ThinkingBlock` | `{ ThinkingBlock }` | Collapsible reasoning block |
284
+
285
+ ### AI
286
+
287
+ | Component | Import | Notes |
288
+ |-----------|--------|-------|
289
+ | `AiChatInput` | `{ AiChatInput }` | Chat composer with submit + attachments |
290
+ | `AiMessageBubble` | `{ AiMessageBubble }` | User/assistant message bubble |
291
+ | `AiStreamingText` | `{ AiStreamingText }` | Token-stream renderer |
292
+ | `AiToolCallDisplay` | `{ AiToolCallDisplay }` | Tool-use invocation render |
293
+ | `AiSourceCitation` | `{ AiSourceCitation }` | Inline citation reference |
294
+ | `ModelSelector` | `{ ModelSelector }` | Model picker (provider-agnostic) |
295
+
296
+ ### Financial
297
+
298
+ | Component | Import | Notes |
299
+ |-----------|--------|-------|
300
+ | `CandlestickChart` | `{ CandlestickChart }` | OHLC candlestick chart |
301
+ | `MarketTreemap` | `{ MarketTreemap }` | Market-cap treemap |
302
+ | `OrderBook` | `{ OrderBook }` | Bid/ask depth book |
303
+ | `TickerTape` | `{ TickerTape }` | Horizontal ticker marquee |
304
+ | `SparklineGrid` | `{ SparklineGrid }` | Grid of inline sparklines |
305
+ | `WalletCard` | `{ WalletCard }` | Balance / portfolio card |
306
+ | `Watchlist` | `{ Watchlist }` | Tracked-symbol list |
307
+
308
+ ### Ops / Status
309
+
310
+ | Component | Import | Notes |
311
+ |-----------|--------|-------|
312
+ | `StatusBoard` | `{ StatusBoard }` | Grid of service statuses |
313
+ | `StatusIndicator` | `{ StatusIndicator }` | Inline dot + label |
314
+ | `LiveFeed` | `{ LiveFeed }` | Append-only event feed |
315
+ | `WorldClockBar` | `{ WorldClockBar }` | Multi-timezone clock bar |
316
+ | `SeverityBadge` | `{ SeverityBadge }` | Severity-level badge |
317
+ | `RoleBadge` | `{ RoleBadge }` | User-role badge |
318
+ | `ScopeSelector` | `{ ScopeSelector }` | Scope / environment picker |
319
+
320
+ ### Billing & Plans
321
+
322
+ | Component | Import | Notes |
323
+ |-----------|--------|-------|
324
+ | `SubscriptionCard` | `{ SubscriptionCard }` | Plan pricing / feature card |
325
+ | `PlanBadge` | `{ PlanBadge }` | Tier indicator |
326
+ | `CreditBadge` | `{ CreditBadge }` | Remaining-credit pill |
327
+ | `UsageBreakdown` | `{ UsageBreakdown }` | Usage-by-category breakdown |
328
+
329
+ ### Animation
330
+
331
+ | Component | Import | Notes |
332
+ |-----------|--------|-------|
333
+ | `AnimatedText` | `{ AnimatedText }` | Text enter-animations |
334
+ | `BorderBeam` | `{ BorderBeam }` | Animated gradient border |
335
+ | `Marquee` | `{ Marquee }` | Infinite scroll marquee |
336
+ | `NumberTicker` | `{ NumberTicker }` | Animated number counter |
337
+ | `Spinner` | `{ Spinner }` | Rich loading-spinner library |
338
+
339
+ ### Form Additions
340
+
341
+ | Component | Import | Notes |
342
+ |-----------|--------|-------|
343
+ | `DatePicker` | `{ DatePicker }` | Composed `Calendar` + `Popover` |
344
+ | `FileUpload` | `{ FileUpload }` | Drag-drop file input |
345
+ | `NumberInput` | `{ NumberInput }` | Numeric input w/ steppers |
346
+ | `PasswordInput` | `{ PasswordInput }` | Input w/ show-password toggle |
347
+ | `InlineInput` | `{ InlineInput }` | Click-to-edit inline input |
348
+ | `Combobox` | `{ Combobox }` | Typeahead select |
349
+ | `Rating` | `{ Rating }` | Star rating input |
350
+
351
+ ### Data & Metrics
352
+
353
+ | Component | Import | Notes |
354
+ |-----------|--------|-------|
355
+ | `DataTable` | `{ DataTable }` | High-level table w/ sort/filter (TanStack) |
356
+ | `DataList` | `{ DataList }` | Label/value list |
357
+ | `StatCard` | `{ StatCard }` | KPI tile |
358
+ | `MetricGauge` | `{ MetricGauge }` | Gauge chart |
359
+ | `ActivityHeatmap` | `{ ActivityHeatmap }` | GitHub-style contribution grid |
360
+ | `ActivityLog` | `{ ActivityLog }` | Audit-trail feed |
361
+ | `TableOfContents` | `{ TableOfContents }` | Inline TOC |
362
+
363
+ ### App Shell (extended)
364
+
365
+ | Component | Import | Notes |
366
+ |-----------|--------|-------|
367
+ | `CategoryFilter` | `{ CategoryFilter }` | Chip-based category filter |
368
+ | `FilterBar` | `{ FilterBar }` | Multi-facet filter row |
369
+ | `CookieConsent` | `{ CookieConsent }` | Cookie-consent banner |
370
+ | `Slideshow` | `{ Slideshow }` | Hero slideshow |
371
+ | `CountdownTimer` | `{ CountdownTimer }` | Event countdown |
372
+ | `AvatarGroup` | `{ AvatarGroup }` | Stacked avatar group |
373
+ | `FloatingActionButton` | `{ FloatingActionButton }` | Anchored FAB |
374
+ | `SocialFab` | `{ SocialFab }` | Social-links FAB |
375
+ | `KeyboardShortcutsHelp` | `{ KeyboardShortcutsHelp }` | Cmd-? shortcut dialog |
376
+ | `ShareDialog` | `{ ShareDialog }` | Shareable-link dialog |
377
+ | `HorizontalScrollRow` | `{ HorizontalScrollRow }` | Scroll-snap row |
378
+ | `ViewSwitcher` | `{ ViewSwitcher }` | Grid/list toggle |
274
379
 
275
380
  ## Utilities
276
381
 
@@ -0,0 +1,168 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../../lib/utils";
4
+ const WEEKDAY_LABELS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
5
+ const VISIBLE_DAY_LABELS = /* @__PURE__ */ new Set(["Mon", "Wed", "Fri"]);
6
+ const LEVEL_CLASS_NAMES = [
7
+ "bg-muted",
8
+ "bg-emerald-500/25",
9
+ "bg-emerald-500/45",
10
+ "bg-emerald-500/65",
11
+ "bg-emerald-500"
12
+ ];
13
+ function normalizeDate(input) {
14
+ if (input instanceof Date) {
15
+ return new Date(input.getTime());
16
+ }
17
+ return new Date(input);
18
+ }
19
+ function toUtcDate(date) {
20
+ return new Date(
21
+ Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())
22
+ );
23
+ }
24
+ function addUtcDays(date, days) {
25
+ return new Date(
26
+ Date.UTC(
27
+ date.getUTCFullYear(),
28
+ date.getUTCMonth(),
29
+ date.getUTCDate() + days
30
+ )
31
+ );
32
+ }
33
+ function formatDayKey(date) {
34
+ return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
35
+ }
36
+ function getIntensityLevel(count, maxCount) {
37
+ if (count <= 0 || maxCount <= 0) {
38
+ return 0;
39
+ }
40
+ const ratio = count / maxCount;
41
+ if (ratio < 0.25) {
42
+ return 1;
43
+ }
44
+ if (ratio < 0.5) {
45
+ return 2;
46
+ }
47
+ if (ratio < 0.75) {
48
+ return 3;
49
+ }
50
+ return 4;
51
+ }
52
+ function getGridData(data, endDate, weeks) {
53
+ const normalizedEnd = toUtcDate(endDate);
54
+ const startDate = addUtcDays(normalizedEnd, -(weeks * 7 - 1));
55
+ const countsByDate = /* @__PURE__ */ new Map();
56
+ data.forEach((item) => {
57
+ const normalizedDate = toUtcDate(normalizeDate(item.date));
58
+ countsByDate.set(formatDayKey(normalizedDate), item.count);
59
+ });
60
+ const maxCount = Math.max(...data.map((item) => item.count), 0);
61
+ return Array.from({ length: weeks }, (_, weekIndex) => {
62
+ return Array.from({ length: 7 }, (_2, dayIndex) => {
63
+ const date = addUtcDays(startDate, weekIndex * 7 + dayIndex);
64
+ const key = formatDayKey(date);
65
+ const count = countsByDate.get(key) ?? 0;
66
+ return {
67
+ count,
68
+ date,
69
+ key,
70
+ level: getIntensityLevel(count, maxCount)
71
+ };
72
+ });
73
+ });
74
+ }
75
+ function formatMonthLabel(date) {
76
+ return new Intl.DateTimeFormat("en-US", {
77
+ month: "short",
78
+ timeZone: "UTC"
79
+ }).format(date);
80
+ }
81
+ function formatTooltip(date, count) {
82
+ const formattedDate = new Intl.DateTimeFormat("en-US", {
83
+ day: "numeric",
84
+ month: "short",
85
+ timeZone: "UTC",
86
+ year: "numeric"
87
+ }).format(date);
88
+ return `${count} activity ${count === 1 ? "event" : "events"} on ${formattedDate}`;
89
+ }
90
+ function HeatmapGrid({
91
+ gridData,
92
+ weeks
93
+ }) {
94
+ return /* @__PURE__ */ jsxs("div", { className: "min-w-[640px] space-y-3", children: [
95
+ /* @__PURE__ */ jsxs(
96
+ "div",
97
+ {
98
+ className: "grid gap-2 text-xs text-muted-foreground",
99
+ style: { gridTemplateColumns: `40px repeat(${weeks}, minmax(0, 1fr))` },
100
+ children: [
101
+ /* @__PURE__ */ jsx("span", {}),
102
+ gridData.map((week) => /* @__PURE__ */ jsx("span", { className: "text-center", children: week[0] && week[0].date.getUTCDate() <= 7 ? formatMonthLabel(week[0].date) : "" }, `month-${week[0]?.key}`))
103
+ ]
104
+ }
105
+ ),
106
+ /* @__PURE__ */ jsxs(
107
+ "div",
108
+ {
109
+ className: "grid gap-2",
110
+ style: { gridTemplateColumns: `40px repeat(${weeks}, minmax(0, 1fr))` },
111
+ children: [
112
+ /* @__PURE__ */ jsx("div", { className: "grid grid-rows-7 gap-2 pt-1 text-xs text-muted-foreground", children: WEEKDAY_LABELS.map((label) => /* @__PURE__ */ jsx("span", { className: "h-4 leading-4", children: VISIBLE_DAY_LABELS.has(label) ? label : "" }, label)) }),
113
+ gridData.map((week) => /* @__PURE__ */ jsx("div", { className: "grid grid-rows-7 gap-2", children: week.map((day) => /* @__PURE__ */ jsx(
114
+ "div",
115
+ {
116
+ className: cn(
117
+ "h-4 rounded-sm border border-border/40 transition-colors",
118
+ LEVEL_CLASS_NAMES[day.level]
119
+ ),
120
+ role: "img",
121
+ title: formatTooltip(day.date, day.count)
122
+ },
123
+ day.key
124
+ )) }, `week-${week[0]?.key}`))
125
+ ]
126
+ }
127
+ ),
128
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 text-xs text-muted-foreground", children: [
129
+ /* @__PURE__ */ jsx("span", { children: "Less" }),
130
+ LEVEL_CLASS_NAMES.map((className, index) => /* @__PURE__ */ jsx(
131
+ "span",
132
+ {
133
+ className: cn(
134
+ "h-3 w-3 rounded-[3px] border border-border/40",
135
+ className
136
+ )
137
+ },
138
+ `legend-${index}`
139
+ )),
140
+ /* @__PURE__ */ jsx("span", { children: "More" })
141
+ ] })
142
+ ] });
143
+ }
144
+ const ActivityHeatmap = React.forwardRef(
145
+ ({
146
+ className,
147
+ data,
148
+ description,
149
+ endDate = /* @__PURE__ */ new Date(),
150
+ title = "Activity heatmap",
151
+ weeks = 12,
152
+ ...props
153
+ }, ref) => {
154
+ const normalizedEndDate = normalizeDate(endDate);
155
+ const gridData = getGridData(data, normalizedEndDate, weeks);
156
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), ref, ...props, children: [
157
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
158
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold tracking-tight", children: title }),
159
+ description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description }) : null
160
+ ] }),
161
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-lg border bg-card p-4 shadow-sm", children: /* @__PURE__ */ jsx(HeatmapGrid, { gridData, weeks }) })
162
+ ] });
163
+ }
164
+ );
165
+ ActivityHeatmap.displayName = "ActivityHeatmap";
166
+ export {
167
+ ActivityHeatmap
168
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ ActivityHeatmap
3
+ } from "./activity-heatmap";
4
+ export {
5
+ ActivityHeatmap
6
+ };
@@ -0,0 +1,256 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useMemo, useState } from "react";
3
+ import { ArrowRight, ChevronLeft, ChevronRight } from "lucide-react";
4
+ import { cn } from "../../lib/utils";
5
+ import { Avatar, AvatarFallback } from "../avatar";
6
+ import { Badge } from "../badge";
7
+ import { Button } from "../button";
8
+ import {
9
+ Card,
10
+ CardContent,
11
+ CardDescription,
12
+ CardHeader,
13
+ CardTitle
14
+ } from "../card";
15
+ import { ScrollArea } from "../scroll-area";
16
+ import { Separator } from "../separator";
17
+ const toneConfig = {
18
+ danger: {
19
+ badgeClassName: "border-destructive/20 bg-destructive/10 text-destructive dark:text-destructive",
20
+ markerClassName: "bg-destructive"
21
+ },
22
+ default: {
23
+ badgeClassName: "border-border bg-muted text-muted-foreground",
24
+ markerClassName: "bg-primary"
25
+ },
26
+ success: {
27
+ badgeClassName: "border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
28
+ markerClassName: "bg-emerald-500"
29
+ },
30
+ warning: {
31
+ badgeClassName: "border-amber-500/20 bg-amber-500/10 text-amber-700 dark:text-amber-300",
32
+ markerClassName: "bg-amber-500"
33
+ }
34
+ };
35
+ function getInitials(name) {
36
+ return name.split(" ").map((segment) => segment[0]).join("").slice(0, 2).toUpperCase();
37
+ }
38
+ function buildPageNumbers(currentPage, totalPages) {
39
+ if (totalPages <= 1) return [1];
40
+ const start = Math.max(1, currentPage - 1);
41
+ const end = Math.min(totalPages, start + 2);
42
+ const normalizedStart = Math.max(1, end - 2);
43
+ return Array.from(
44
+ { length: end - normalizedStart + 1 },
45
+ (_, index) => normalizedStart + index
46
+ );
47
+ }
48
+ function ActivityLogHeader({
49
+ currentPage,
50
+ description,
51
+ title,
52
+ totalPages
53
+ }) {
54
+ return /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", children: [
55
+ /* @__PURE__ */ jsxs("div", { children: [
56
+ /* @__PURE__ */ jsx(CardTitle, { children: title }),
57
+ description ? /* @__PURE__ */ jsx(CardDescription, { children: description }) : null
58
+ ] }),
59
+ /* @__PURE__ */ jsxs(Badge, { className: "w-fit", variant: "outline", children: [
60
+ "Page ",
61
+ currentPage,
62
+ " of ",
63
+ totalPages
64
+ ] })
65
+ ] }) });
66
+ }
67
+ function PaginationControls({
68
+ currentPage,
69
+ onPageChange,
70
+ pageNumbers,
71
+ totalPages
72
+ }) {
73
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
74
+ /* @__PURE__ */ jsxs(
75
+ Button,
76
+ {
77
+ disabled: currentPage === 1,
78
+ onClick: () => {
79
+ onPageChange(currentPage - 1);
80
+ },
81
+ size: "sm",
82
+ variant: "outline",
83
+ children: [
84
+ /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }),
85
+ "Previous"
86
+ ]
87
+ }
88
+ ),
89
+ pageNumbers.map((pageNumber) => /* @__PURE__ */ jsx(
90
+ Button,
91
+ {
92
+ "aria-label": `Go to page ${pageNumber}`,
93
+ onClick: () => {
94
+ onPageChange(pageNumber);
95
+ },
96
+ size: "sm",
97
+ variant: pageNumber === currentPage ? "default" : "outline",
98
+ children: pageNumber
99
+ },
100
+ pageNumber
101
+ )),
102
+ /* @__PURE__ */ jsxs(
103
+ Button,
104
+ {
105
+ disabled: currentPage === totalPages,
106
+ onClick: () => {
107
+ onPageChange(currentPage + 1);
108
+ },
109
+ size: "sm",
110
+ variant: "outline",
111
+ children: [
112
+ "Next",
113
+ /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
114
+ ]
115
+ }
116
+ )
117
+ ] });
118
+ }
119
+ function ActivityRow({ item }) {
120
+ const palette = toneConfig[item.tone ?? "default"];
121
+ return /* @__PURE__ */ jsxs("li", { className: "relative pl-12", children: [
122
+ /* @__PURE__ */ jsx(
123
+ "span",
124
+ {
125
+ "aria-hidden": "true",
126
+ className: "absolute bottom-[-1.5rem] left-[18px] top-11 w-px bg-border last:hidden"
127
+ }
128
+ ),
129
+ /* @__PURE__ */ jsx(
130
+ "span",
131
+ {
132
+ "aria-hidden": "true",
133
+ className: cn(
134
+ "absolute left-4 top-3 h-3 w-3 rounded-full ring-4 ring-background",
135
+ palette.markerClassName
136
+ )
137
+ }
138
+ ),
139
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-background/70 p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", children: [
140
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-3", children: [
141
+ /* @__PURE__ */ jsx(Avatar, { className: "h-9 w-9 border bg-muted", children: /* @__PURE__ */ jsx(AvatarFallback, { children: getInitials(item.actor) }) }),
142
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
143
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
144
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: item.actor }),
145
+ /* @__PURE__ */ jsx(ArrowRight, { className: "h-3.5 w-3.5 text-muted-foreground" }),
146
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: item.action }),
147
+ item.target ? /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: item.target }) : null
148
+ ] }),
149
+ item.description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: item.description }) : null
150
+ ] })
151
+ ] }),
152
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-2 sm:justify-end", children: [
153
+ item.scope ? /* @__PURE__ */ jsx(Badge, { className: palette.badgeClassName, variant: "outline", children: item.scope }) : null,
154
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: item.timestamp })
155
+ ] })
156
+ ] }) })
157
+ ] });
158
+ }
159
+ function ActivityLogBody({
160
+ currentPage,
161
+ emptyMessage,
162
+ items,
163
+ onPageChange,
164
+ pageNumbers,
165
+ pageSize,
166
+ totalPages
167
+ }) {
168
+ const visibleItems = useMemo(() => {
169
+ const start = (currentPage - 1) * pageSize;
170
+ return items.slice(start, start + pageSize);
171
+ }, [currentPage, items, pageSize]);
172
+ if (items.length === 0) {
173
+ return /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground", children: emptyMessage });
174
+ }
175
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
176
+ /* @__PURE__ */ jsx(ScrollArea, { className: "max-h-[26rem] pr-4", children: /* @__PURE__ */ jsx("ol", { className: "space-y-4 pb-2", children: visibleItems.map((item) => /* @__PURE__ */ jsx(ActivityRow, { item }, item.id)) }) }),
177
+ /* @__PURE__ */ jsx(Separator, {}),
178
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [
179
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
180
+ "Showing ",
181
+ (currentPage - 1) * pageSize + 1,
182
+ " - ",
183
+ (currentPage - 1) * pageSize + visibleItems.length,
184
+ " of ",
185
+ items.length
186
+ ] }),
187
+ /* @__PURE__ */ jsx(
188
+ PaginationControls,
189
+ {
190
+ currentPage,
191
+ onPageChange,
192
+ pageNumbers,
193
+ totalPages
194
+ }
195
+ )
196
+ ] })
197
+ ] });
198
+ }
199
+ const ActivityLog = forwardRef(
200
+ ({
201
+ className,
202
+ defaultPage = 1,
203
+ description,
204
+ emptyMessage = "No activity recorded yet.",
205
+ items,
206
+ onPageChange,
207
+ page,
208
+ pageSize = 5,
209
+ title = "Activity log",
210
+ ...props
211
+ }, ref) => {
212
+ const totalPages = Math.max(1, Math.ceil(items.length / pageSize));
213
+ const [uncontrolledPage, setUncontrolledPage] = useState(defaultPage);
214
+ const currentPage = Math.min(
215
+ Math.max(page ?? uncontrolledPage, 1),
216
+ totalPages
217
+ );
218
+ const pageNumbers = useMemo(
219
+ () => buildPageNumbers(currentPage, totalPages),
220
+ [currentPage, totalPages]
221
+ );
222
+ function handlePageChange(nextPage) {
223
+ if (page === void 0) {
224
+ setUncontrolledPage(nextPage);
225
+ }
226
+ onPageChange?.(nextPage);
227
+ }
228
+ return /* @__PURE__ */ jsxs(Card, { className: cn("w-full", className), ref, ...props, children: [
229
+ /* @__PURE__ */ jsx(
230
+ ActivityLogHeader,
231
+ {
232
+ currentPage,
233
+ description,
234
+ title,
235
+ totalPages
236
+ }
237
+ ),
238
+ /* @__PURE__ */ jsx(CardContent, { className: "space-y-4", children: /* @__PURE__ */ jsx(
239
+ ActivityLogBody,
240
+ {
241
+ currentPage,
242
+ emptyMessage,
243
+ items,
244
+ onPageChange: handlePageChange,
245
+ pageNumbers,
246
+ pageSize,
247
+ totalPages
248
+ }
249
+ ) })
250
+ ] });
251
+ }
252
+ );
253
+ ActivityLog.displayName = "ActivityLog";
254
+ export {
255
+ ActivityLog
256
+ };
@@ -0,0 +1,6 @@
1
+ import {
2
+ ActivityLog
3
+ } from "./activity-log";
4
+ export {
5
+ ActivityLog
6
+ };