@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.101",
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/i18n": "0.1.3",
124
- "@startsimpli/realtime": "0.0.2"
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
- {rows.map((row) => (
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', () => {