@vllnt/ui 0.1.11 → 0.2.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.
- package/CHANGELOG.md +104 -0
- package/README.md +106 -1
- package/dist/components/activity-heatmap/activity-heatmap.js +168 -0
- package/dist/components/activity-heatmap/index.js +6 -0
- package/dist/components/activity-log/activity-log.js +256 -0
- package/dist/components/activity-log/index.js +6 -0
- package/dist/components/ai-chat-input/ai-chat-input.js +107 -0
- package/dist/components/ai-chat-input/index.js +4 -0
- package/dist/components/ai-message-bubble/ai-message-bubble.js +119 -0
- package/dist/components/ai-message-bubble/index.js +6 -0
- package/dist/components/ai-source-citation/ai-source-citation.js +39 -0
- package/dist/components/ai-source-citation/index.js +6 -0
- package/dist/components/ai-streaming-text/ai-streaming-text.js +41 -0
- package/dist/components/ai-streaming-text/index.js +6 -0
- package/dist/components/ai-tool-call-display/ai-tool-call-display.js +93 -0
- package/dist/components/ai-tool-call-display/index.js +6 -0
- package/dist/components/animated-text/animated-text.js +328 -0
- package/dist/components/animated-text/index.js +4 -0
- package/dist/components/annotation/annotation.js +49 -0
- package/dist/components/annotation/index.js +8 -0
- package/dist/components/avatar-group/avatar-group.js +82 -0
- package/dist/components/avatar-group/index.js +10 -0
- package/dist/components/border-beam/border-beam.js +51 -0
- package/dist/components/border-beam/index.js +4 -0
- package/dist/components/candlestick-chart/candlestick-chart.js +215 -0
- package/dist/components/candlestick-chart/index.js +6 -0
- package/dist/components/combobox/combobox.js +130 -0
- package/dist/components/combobox/index.js +4 -0
- package/dist/components/countdown-timer/countdown-timer.js +184 -0
- package/dist/components/countdown-timer/index.js +4 -0
- package/dist/components/credit-badge/credit-badge.js +59 -0
- package/dist/components/credit-badge/index.js +6 -0
- package/dist/components/data-list/data-list.js +99 -0
- package/dist/components/data-list/index.js +16 -0
- package/dist/components/data-table/data-table.js +242 -0
- package/dist/components/data-table/index.js +6 -0
- package/dist/components/date-picker/date-picker.js +74 -0
- package/dist/components/date-picker/index.js +4 -0
- package/dist/components/file-upload/file-upload.js +227 -0
- package/dist/components/file-upload/index.js +4 -0
- package/dist/components/flashcard/flashcard.js +66 -0
- package/dist/components/flashcard/index.js +4 -0
- package/dist/components/index.js +172 -1
- package/dist/components/live-feed/index.js +4 -0
- package/dist/components/live-feed/live-feed.js +168 -0
- package/dist/components/market-treemap/index.js +6 -0
- package/dist/components/market-treemap/market-treemap.js +100 -0
- package/dist/components/marquee/index.js +4 -0
- package/dist/components/marquee/marquee.js +98 -0
- package/dist/components/metric-gauge/index.js +6 -0
- package/dist/components/metric-gauge/metric-gauge.js +213 -0
- package/dist/components/model-selector/model-selector.js +11 -2
- package/dist/components/number-input/index.js +4 -0
- package/dist/components/number-input/number-input.js +167 -0
- package/dist/components/number-ticker/index.js +4 -0
- package/dist/components/number-ticker/number-ticker.js +63 -0
- package/dist/components/order-book/index.js +6 -0
- package/dist/components/order-book/order-book.js +128 -0
- package/dist/components/password-input/index.js +4 -0
- package/dist/components/password-input/password-input.js +45 -0
- package/dist/components/plan-badge/index.js +6 -0
- package/dist/components/plan-badge/plan-badge.js +67 -0
- package/dist/components/rating/index.js +4 -0
- package/dist/components/rating/rating.js +121 -0
- package/dist/components/role-badge/index.js +6 -0
- package/dist/components/role-badge/role-badge.js +50 -0
- package/dist/components/scope-selector/index.js +6 -0
- package/dist/components/scope-selector/scope-selector.js +336 -0
- package/dist/components/severity-badge/index.js +8 -0
- package/dist/components/severity-badge/severity-badge.js +163 -0
- package/dist/components/sparkline-grid/index.js +6 -0
- package/dist/components/sparkline-grid/sparkline-grid.js +92 -0
- package/dist/components/spinner/index.js +5 -1
- package/dist/components/spinner/unicode-spinner.js +708 -0
- package/dist/components/stat-card/index.js +5 -0
- package/dist/components/stat-card/stat-card.js +102 -0
- package/dist/components/status-board/index.js +6 -0
- package/dist/components/status-board/status-board.js +138 -0
- package/dist/components/status-indicator/index.js +10 -0
- package/dist/components/status-indicator/status-indicator.js +175 -0
- package/dist/components/stepper/index.js +4 -0
- package/dist/components/stepper/stepper.js +117 -0
- package/dist/components/subscription-card/index.js +6 -0
- package/dist/components/subscription-card/subscription-card.js +161 -0
- package/dist/components/ticker-tape/index.js +6 -0
- package/dist/components/ticker-tape/ticker-tape.js +106 -0
- package/dist/components/tour/index.js +4 -0
- package/dist/components/tour/tour.js +157 -0
- package/dist/components/usage-breakdown/index.js +6 -0
- package/dist/components/usage-breakdown/usage-breakdown.js +140 -0
- package/dist/components/wallet-card/index.js +4 -0
- package/dist/components/wallet-card/wallet-card.js +115 -0
- package/dist/components/watchlist/index.js +6 -0
- package/dist/components/watchlist/watchlist.js +110 -0
- package/dist/components/world-clock-bar/index.js +6 -0
- package/dist/components/world-clock-bar/world-clock-bar.js +101 -0
- package/dist/index.d.ts +1173 -7
- package/dist/test-setup.js +19 -0
- package/package.json +27 -6
- 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 —
|
|
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,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
|
+
};
|