@startsimpli/ui 0.4.101 → 0.4.102
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.102",
|
|
4
4
|
"description": "Shared UI components package for StartSimpli applications",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
45
45
|
"react": "^18.0.0 || ^19.0.0",
|
|
46
46
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
47
|
-
"@startsimpli/api": "^0.5.56",
|
|
48
47
|
"@startsimpli/auth": "^0.4.35",
|
|
49
|
-
"@startsimpli/i18n": "^0.1.3"
|
|
48
|
+
"@startsimpli/i18n": "^0.1.3",
|
|
49
|
+
"@startsimpli/api": "^0.5.56"
|
|
50
50
|
},
|
|
51
51
|
"peerDependenciesMeta": {
|
|
52
52
|
"@startsimpli/api": {
|
|
@@ -120,8 +120,8 @@
|
|
|
120
120
|
"tailwindcss": "^3.4.19",
|
|
121
121
|
"tsup": "^8.5.1",
|
|
122
122
|
"typescript": "^6.0.3",
|
|
123
|
-
"@startsimpli/
|
|
124
|
-
"@startsimpli/
|
|
123
|
+
"@startsimpli/realtime": "0.0.2",
|
|
124
|
+
"@startsimpli/i18n": "0.1.3"
|
|
125
125
|
},
|
|
126
126
|
"keywords": [
|
|
127
127
|
"ui",
|
|
@@ -20,6 +20,18 @@ export interface SourceFreshnessProps {
|
|
|
20
20
|
staleAfterHours?: number
|
|
21
21
|
/** Compare against this instant (injectable for tests); defaults to Date.now(). */
|
|
22
22
|
now?: number
|
|
23
|
+
/**
|
|
24
|
+
* Cap how many rows are LISTED. The roll-up (tone, "N stale") still counts every
|
|
25
|
+
* source, so capping hides rows, never facts.
|
|
26
|
+
*
|
|
27
|
+
* This card is the one health block that used to have no cap, and it got handed
|
|
28
|
+
* the one unbounded list: a tenant with 55 declared sources rendered 55 rows,
|
|
29
|
+
* and in a `DashboardGrid` (CSS grid, `align-items: stretch` by default) that one
|
|
30
|
+
* very tall card silently set the row height for every card beside it — the
|
|
31
|
+
* short neighbour then padded the difference with blank space. Capping the list
|
|
32
|
+
* is the cheap half of that fix; a grid that does not stretch is the other half.
|
|
33
|
+
*/
|
|
34
|
+
limit?: number
|
|
23
35
|
emptyMessage?: string
|
|
24
36
|
className?: string
|
|
25
37
|
}
|
|
@@ -46,6 +58,7 @@ export function SourceFreshness({
|
|
|
46
58
|
sources,
|
|
47
59
|
staleAfterHours = 48,
|
|
48
60
|
now,
|
|
61
|
+
limit,
|
|
49
62
|
emptyMessage = 'No sources configured.',
|
|
50
63
|
className,
|
|
51
64
|
}: SourceFreshnessProps) {
|
|
@@ -54,6 +67,10 @@ export function SourceFreshness({
|
|
|
54
67
|
const staleCount = rows.filter((r) => r.resolved === 'warn' || r.resolved === 'critical').length
|
|
55
68
|
const status = worstStatus(rows.map((r) => r.resolved))
|
|
56
69
|
const statusLabel = sources.length === 0 ? undefined : staleCount > 0 ? `${staleCount} stale` : 'All fresh'
|
|
70
|
+
// Cap the LIST only — staleCount/status above are computed over every source, so
|
|
71
|
+
// a stale feed below the cut still pulls the card's tone and its badge count.
|
|
72
|
+
const listed = limit ? rows.slice(0, limit) : rows
|
|
73
|
+
const hidden = rows.length - listed.length
|
|
57
74
|
|
|
58
75
|
return (
|
|
59
76
|
<HealthCard
|
|
@@ -66,7 +83,7 @@ export function SourceFreshness({
|
|
|
66
83
|
className={className}
|
|
67
84
|
>
|
|
68
85
|
<ul className="space-y-2" aria-label={`${title} sources`}>
|
|
69
|
-
{
|
|
86
|
+
{listed.map((row) => (
|
|
70
87
|
<li key={row.name} className="flex items-center justify-between gap-3 text-sm">
|
|
71
88
|
<span className="flex items-center gap-2 min-w-0">
|
|
72
89
|
<HealthDot status={row.resolved} />
|
|
@@ -83,6 +100,11 @@ export function SourceFreshness({
|
|
|
83
100
|
</li>
|
|
84
101
|
))}
|
|
85
102
|
</ul>
|
|
103
|
+
{hidden > 0 && (
|
|
104
|
+
<p className="text-xs text-gray-500">
|
|
105
|
+
+{hidden} more source{hidden === 1 ? '' : 's'} not shown
|
|
106
|
+
</p>
|
|
107
|
+
)}
|
|
86
108
|
</HealthCard>
|
|
87
109
|
)
|
|
88
110
|
}
|
|
@@ -96,6 +96,68 @@ describe('SourceFreshness', () => {
|
|
|
96
96
|
expect(screen.getByText('2 stale')).toBeInTheDocument()
|
|
97
97
|
expect(screen.getByText('never')).toBeInTheDocument()
|
|
98
98
|
})
|
|
99
|
+
it('caps the listed rows at `limit` and says how many are hidden', () => {
|
|
100
|
+
render(
|
|
101
|
+
<SourceFreshness
|
|
102
|
+
now={NOW}
|
|
103
|
+
limit={2}
|
|
104
|
+
sources={[
|
|
105
|
+
{ name: 'RSS A', lastUpdated: hoursAgo(1) },
|
|
106
|
+
{ name: 'RSS B', lastUpdated: hoursAgo(2) },
|
|
107
|
+
{ name: 'RSS C', lastUpdated: hoursAgo(3) },
|
|
108
|
+
{ name: 'RSS D', lastUpdated: hoursAgo(4) },
|
|
109
|
+
]}
|
|
110
|
+
/>,
|
|
111
|
+
)
|
|
112
|
+
expect(screen.getByText('RSS A')).toBeInTheDocument()
|
|
113
|
+
expect(screen.getByText('RSS B')).toBeInTheDocument()
|
|
114
|
+
expect(screen.queryByText('RSS C')).not.toBeInTheDocument()
|
|
115
|
+
expect(screen.getByText('+2 more sources not shown')).toBeInTheDocument()
|
|
116
|
+
})
|
|
117
|
+
it('rolls up tone and the stale count over EVERY source, not just the listed ones', () => {
|
|
118
|
+
render(
|
|
119
|
+
<SourceFreshness
|
|
120
|
+
now={NOW}
|
|
121
|
+
staleAfterHours={48}
|
|
122
|
+
limit={1}
|
|
123
|
+
sources={[
|
|
124
|
+
{ name: 'RSS A', lastUpdated: hoursAgo(1) },
|
|
125
|
+
{ name: 'RSS B', lastUpdated: hoursAgo(200) },
|
|
126
|
+
{ name: 'RSS C', lastUpdated: null },
|
|
127
|
+
]}
|
|
128
|
+
/>,
|
|
129
|
+
)
|
|
130
|
+
// B and C are below the cut; hiding a row must never hide the fact.
|
|
131
|
+
expect(screen.getByText('2 stale')).toBeInTheDocument()
|
|
132
|
+
expect(screen.queryByText('RSS B')).not.toBeInTheDocument()
|
|
133
|
+
expect(screen.getByText('+2 more sources not shown')).toBeInTheDocument()
|
|
134
|
+
})
|
|
135
|
+
it('lists everything, with no hidden-count line, when no limit is given', () => {
|
|
136
|
+
render(
|
|
137
|
+
<SourceFreshness
|
|
138
|
+
now={NOW}
|
|
139
|
+
sources={[
|
|
140
|
+
{ name: 'RSS A', lastUpdated: hoursAgo(1) },
|
|
141
|
+
{ name: 'RSS B', lastUpdated: hoursAgo(2) },
|
|
142
|
+
]}
|
|
143
|
+
/>,
|
|
144
|
+
)
|
|
145
|
+
expect(screen.getByText('RSS B')).toBeInTheDocument()
|
|
146
|
+
expect(screen.queryByText(/more sources? not shown/)).not.toBeInTheDocument()
|
|
147
|
+
})
|
|
148
|
+
it('says "source" (singular) when exactly one row is hidden', () => {
|
|
149
|
+
render(
|
|
150
|
+
<SourceFreshness
|
|
151
|
+
now={NOW}
|
|
152
|
+
limit={1}
|
|
153
|
+
sources={[
|
|
154
|
+
{ name: 'RSS A', lastUpdated: hoursAgo(1) },
|
|
155
|
+
{ name: 'RSS B', lastUpdated: hoursAgo(2) },
|
|
156
|
+
]}
|
|
157
|
+
/>,
|
|
158
|
+
)
|
|
159
|
+
expect(screen.getByText('+1 more source not shown')).toBeInTheDocument()
|
|
160
|
+
})
|
|
99
161
|
})
|
|
100
162
|
|
|
101
163
|
describe('DeliveryHealth', () => {
|