@skyhook-io/k8s-ui 1.8.0 → 1.8.1
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 +1 -1
- package/src/components/checks/ChecksView.tsx +2 -2
- package/src/components/gitops/GitOpsTableView.tsx +9 -6
- package/src/components/gitops/detail-helpers.test.ts +1 -1
- package/src/components/resources/resource-utils.ts +4 -0
- package/src/components/timeline/TimelineList.tsx +31 -3
- package/src/components/timeline/TimelineSwimlanes.tsx +27 -5
- package/src/components/ui/SearchPillInput.tsx +45 -4
package/package.json
CHANGED
|
@@ -820,9 +820,9 @@ function FindingLine({
|
|
|
820
820
|
</button>
|
|
821
821
|
) : resourceHref ? (
|
|
822
822
|
// Opens in a new tab to match the ExternalLink glyph in `body`. For the
|
|
823
|
-
// Hub fleet view this href crosses the Hub
|
|
823
|
+
// Hub fleet view this href crosses the Hub to Cluster route-tree boundary (a full
|
|
824
824
|
// document nav); a new tab keeps the Checks queue intact and avoids the
|
|
825
|
-
// cross-tree browser-Back dead-end
|
|
825
|
+
// cross-tree browser-Back dead-end.
|
|
826
826
|
<a href={resourceHref(r)} target="_blank" rel="noreferrer" className={cls}>
|
|
827
827
|
{body}
|
|
828
828
|
</a>
|
|
@@ -102,7 +102,7 @@ export interface FleetClusterStamp {
|
|
|
102
102
|
name: string
|
|
103
103
|
}
|
|
104
104
|
export type FleetDestinationMatch = 'in_cluster' | 'exact' | 'inferred' | 'unmatched'
|
|
105
|
-
// FleetDestinationConfidence is a coarse signal the
|
|
105
|
+
// FleetDestinationConfidence is a coarse signal the frontend uses to style
|
|
106
106
|
// the destination chip. `high` = URL-equality match (either direct or
|
|
107
107
|
// via Argo cluster-secret), `medium` = name-equality match (more
|
|
108
108
|
// fragile, more likely to be a false positive when two clusters share
|
|
@@ -116,7 +116,7 @@ export interface FleetDestinationStamp {
|
|
|
116
116
|
// Confidence + reason are populated for non-unmatched rows. They power
|
|
117
117
|
// the chip's visual treatment (a checkmark on high-confidence matches)
|
|
118
118
|
// and its title= tooltip respectively. Both come from the hub —
|
|
119
|
-
// adding new values shouldn't break the
|
|
119
|
+
// adding new values shouldn't break the frontend (unknown confidence
|
|
120
120
|
// falls back to no special styling).
|
|
121
121
|
confidence?: FleetDestinationConfidence
|
|
122
122
|
reason?: string
|
|
@@ -184,7 +184,7 @@ export interface GitOpsTableViewProps {
|
|
|
184
184
|
onRefresh?: () => void
|
|
185
185
|
// Row click — caller routes to its own detail page. When the host also
|
|
186
186
|
// passes `rowHrefFor`, the callback receives the MouseEvent so it can
|
|
187
|
-
// `preventDefault()` for
|
|
187
|
+
// `preventDefault()` for same-tree nav (e.g. react-router) or skip the
|
|
188
188
|
// preventDefault to let the anchor's default full-page navigation run
|
|
189
189
|
// (required for cross-router-boundary links).
|
|
190
190
|
onRowClick: (row: GitOpsRow, event?: ReactMouseEvent) => void
|
|
@@ -217,8 +217,11 @@ export interface GitOpsTableViewProps {
|
|
|
217
217
|
searchHotkey?: boolean
|
|
218
218
|
// emptyStateTitle / emptyStateBody override the "No GitOps resources
|
|
219
219
|
// detected" copy. Hub passes "No GitOps resources across the fleet".
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
// ReactNode (not just string) so a host can split the body across lines
|
|
221
|
+
// (e.g. a `<br/>` between "what's wrong" and "what to do") or emphasize
|
|
222
|
+
// part of it. Strings still work unchanged.
|
|
223
|
+
emptyStateTitle?: ReactNode
|
|
224
|
+
emptyStateBody?: ReactNode
|
|
222
225
|
/**
|
|
223
226
|
* Which side the filter rail sits on. Default 'left' (OSS Radar, which
|
|
224
227
|
* has no app sidebar). A host with its own left navigation rail (the
|
|
@@ -483,7 +486,7 @@ export function GitOpsTableView({
|
|
|
483
486
|
// be hidden behind a "nothing here" screen.
|
|
484
487
|
if (totalGitOps === 0 && allRowsInput.length === 0 && !loading && !hasGlobalNamespaceFilter) {
|
|
485
488
|
return (
|
|
486
|
-
<div className="flex h-full min-h-0 flex-1 items-
|
|
489
|
+
<div className="flex h-full min-h-0 flex-1 items-start justify-center bg-theme-base px-4 pb-4 pt-[22vh]">
|
|
487
490
|
<div className="rounded-lg border border-theme-border bg-theme-surface p-8 text-center">
|
|
488
491
|
<GitBranch className="mx-auto h-8 w-8 text-theme-text-tertiary" />
|
|
489
492
|
<h2 className="mt-3 text-base font-semibold text-theme-text-primary">
|
|
@@ -80,7 +80,7 @@ describe('getGitOpsTool', () => {
|
|
|
80
80
|
expect(getGitOpsTool('appprojects', 'argoproj.io')).toBe('argo')
|
|
81
81
|
})
|
|
82
82
|
// Argo kinds without an explicit group still route to 'argo'.
|
|
83
|
-
// Defends against a normalizer that drops `group` from the
|
|
83
|
+
// Defends against a normalizer that drops `group` from the frontend payload.
|
|
84
84
|
test('Argo kinds without group still route to argo', () => {
|
|
85
85
|
expect(getGitOpsTool('applications', undefined)).toBe('argo')
|
|
86
86
|
expect(getGitOpsTool('applicationsets', '')).toBe('argo')
|
|
@@ -1127,6 +1127,10 @@ export function cronToHuman(cron: string): string {
|
|
|
1127
1127
|
if (minute === '0' && hour === '0' && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
|
|
1128
1128
|
return 'Daily at midnight'
|
|
1129
1129
|
}
|
|
1130
|
+
if (minute === '0' && hour.startsWith('*/') && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
|
|
1131
|
+
const interval = hour.slice(2)
|
|
1132
|
+
return interval === '1' ? 'Every hour' : `Every ${interval} hours`
|
|
1133
|
+
}
|
|
1130
1134
|
if (minute === '0' && hour !== '*' && dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
|
|
1131
1135
|
return `Daily at ${hour}:00`
|
|
1132
1136
|
}
|
|
@@ -53,6 +53,11 @@ export interface TimelineListProps {
|
|
|
53
53
|
onResourceClick?: NavigateToResource
|
|
54
54
|
initialFilter?: ActivityTypeFilter
|
|
55
55
|
initialTimeRange?: TimeRange
|
|
56
|
+
// Controlled "show deleted" toggle. When omitted the component manages it
|
|
57
|
+
// internally; the host passes it to share one toggle across list + swimlane
|
|
58
|
+
// and to drive server-side delete filtering.
|
|
59
|
+
showDeleted?: boolean
|
|
60
|
+
onShowDeletedChange?: (showDeleted: boolean) => void
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
const TIME_RANGES: { value: TimeRange; label: string }[] = [
|
|
@@ -80,11 +85,14 @@ const RESOURCE_KINDS = [
|
|
|
80
85
|
'StatefulSet',
|
|
81
86
|
]
|
|
82
87
|
|
|
83
|
-
export function TimelineList({ events, isLoading, onRefresh, onQueryChange, hasLimitedAccess, namespaces, onViewChange, currentView = 'list', onResourceClick, initialFilter, initialTimeRange }: TimelineListProps) {
|
|
88
|
+
export function TimelineList({ events, isLoading, onRefresh, onQueryChange, hasLimitedAccess, namespaces, onViewChange, currentView = 'list', onResourceClick, initialFilter, initialTimeRange, showDeleted: showDeletedProp, onShowDeletedChange }: TimelineListProps) {
|
|
84
89
|
const [searchTerm, setSearchTerm] = useState('')
|
|
85
90
|
const [activityTypeFilter, setActivityTypeFilter] = useState<ActivityTypeFilter>(initialFilter ?? 'all')
|
|
86
91
|
const [timeRange, setTimeRange] = useState<TimeRange>(initialTimeRange ?? '1h')
|
|
87
92
|
const [kindFilter, setKindFilter] = useState<string>('')
|
|
93
|
+
const [showDeletedInternal, setShowDeletedInternal] = useState(true)
|
|
94
|
+
const showDeleted = showDeletedProp ?? showDeletedInternal
|
|
95
|
+
const setShowDeleted = onShowDeletedChange ?? setShowDeletedInternal
|
|
88
96
|
const [expandedItem, setExpandedItem] = useState<string | null>(null)
|
|
89
97
|
|
|
90
98
|
useEffect(() => {
|
|
@@ -111,6 +119,7 @@ export function TimelineList({ events, isLoading, onRefresh, onQueryChange, hasL
|
|
|
111
119
|
const isUnhealthyChange = isChangeEvent(item) && (item.healthState === 'unhealthy' || item.healthState === 'degraded')
|
|
112
120
|
if (!isUnhealthyChange) return false
|
|
113
121
|
}
|
|
122
|
+
if (!showDeleted && item.eventType === 'delete') return false
|
|
114
123
|
|
|
115
124
|
// Filter by search term
|
|
116
125
|
if (searchTerm) {
|
|
@@ -129,7 +138,7 @@ export function TimelineList({ events, isLoading, onRefresh, onQueryChange, hasL
|
|
|
129
138
|
|
|
130
139
|
return true
|
|
131
140
|
})
|
|
132
|
-
}, [events, activityTypeFilter, searchTerm])
|
|
141
|
+
}, [events, activityTypeFilter, searchTerm, showDeleted])
|
|
133
142
|
|
|
134
143
|
// Aggregated event group type
|
|
135
144
|
type AggregatedItem = {
|
|
@@ -245,12 +254,13 @@ export function TimelineList({ events, isLoading, onRefresh, onQueryChange, hasL
|
|
|
245
254
|
|
|
246
255
|
// Count stats
|
|
247
256
|
const stats = useMemo(() => {
|
|
248
|
-
if (!events) return { total: 0, changes: 0, warnings: 0, unhealthy: 0 }
|
|
257
|
+
if (!events) return { total: 0, changes: 0, warnings: 0, unhealthy: 0, deleted: 0 }
|
|
249
258
|
return {
|
|
250
259
|
total: events.length,
|
|
251
260
|
changes: events.filter((e) => isChangeEvent(e)).length,
|
|
252
261
|
warnings: events.filter((e) => e.eventType === 'Warning').length,
|
|
253
262
|
unhealthy: events.filter((e) => isChangeEvent(e) && (e.healthState === 'unhealthy' || e.healthState === 'degraded')).length,
|
|
263
|
+
deleted: events.filter((e) => e.eventType === 'delete').length,
|
|
254
264
|
}
|
|
255
265
|
}, [events])
|
|
256
266
|
|
|
@@ -306,6 +316,24 @@ export function TimelineList({ events, isLoading, onRefresh, onQueryChange, hasL
|
|
|
306
316
|
/>
|
|
307
317
|
</div>
|
|
308
318
|
|
|
319
|
+
<button
|
|
320
|
+
type="button"
|
|
321
|
+
onClick={() => setShowDeleted(!showDeleted)}
|
|
322
|
+
title="Show or hide resources that were deleted, including Pods that no longer exist"
|
|
323
|
+
className={clsx(
|
|
324
|
+
'px-3 py-1.5 text-sm rounded-md transition-colors flex items-center gap-2 bg-theme-elevated',
|
|
325
|
+
showDeleted ? 'text-theme-text-primary' : 'text-theme-text-secondary hover:text-theme-text-primary'
|
|
326
|
+
)}
|
|
327
|
+
>
|
|
328
|
+
<Trash2 className="w-3 h-3" />
|
|
329
|
+
Deleted
|
|
330
|
+
{stats.deleted > 0 && (
|
|
331
|
+
<span className="text-xs px-1.5 rounded bg-theme-hover/50">
|
|
332
|
+
{stats.deleted}
|
|
333
|
+
</span>
|
|
334
|
+
)}
|
|
335
|
+
</button>
|
|
336
|
+
|
|
309
337
|
{/* Kind filter */}
|
|
310
338
|
<select
|
|
311
339
|
value={kindFilter}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
Timer,
|
|
22
22
|
RotateCcw,
|
|
23
23
|
Shield,
|
|
24
|
+
Trash2,
|
|
24
25
|
} from 'lucide-react'
|
|
25
26
|
import type { TimelineEvent, Topology } from '../../types'
|
|
26
27
|
import type { NavigateToResource } from '../../utils/navigation'
|
|
@@ -54,9 +55,14 @@ export interface TimelineSwimlanesProps {
|
|
|
54
55
|
// RBAC capability flag (was a radar/web context); host passes it. Default false.
|
|
55
56
|
hasLimitedAccess?: boolean
|
|
56
57
|
// GitOps lane labels deep-link to a controller path; the host decides how to
|
|
57
|
-
// navigate (radar router push, or cross-
|
|
58
|
+
// navigate (radar router push, or cross-route-tree href). When omitted, GitOps lanes
|
|
58
59
|
// fall back to onResourceClick (the resource drawer).
|
|
59
60
|
onNavigatePath?: (path: string) => void
|
|
61
|
+
// Controlled "show deleted" toggle. When omitted the component manages it
|
|
62
|
+
// internally; the host passes it to share one toggle with the list view and
|
|
63
|
+
// to drive server-side delete filtering on the underlying fetch.
|
|
64
|
+
showDeleted?: boolean
|
|
65
|
+
onShowDeletedChange?: (showDeleted: boolean) => void
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
interface ResourceLane extends BaseResourceLane {
|
|
@@ -180,7 +186,7 @@ function calculateInterestingnessWithBreakdown(lane: ResourceLane): ScoreBreakdo
|
|
|
180
186
|
return breakdown
|
|
181
187
|
}
|
|
182
188
|
|
|
183
|
-
export function TimelineSwimlanes({ events, isLoading, onResourceClick, viewMode, onViewModeChange, topology, namespaces, hasLimitedAccess = false, onNavigatePath }: TimelineSwimlanesProps) {
|
|
189
|
+
export function TimelineSwimlanes({ events, isLoading, onResourceClick, viewMode, onViewModeChange, topology, namespaces, hasLimitedAccess = false, onNavigatePath, showDeleted: showDeletedProp, onShowDeletedChange }: TimelineSwimlanesProps) {
|
|
184
190
|
// Timeline lane labels for GitOps CRs (Application/Kustomization/HelmRelease)
|
|
185
191
|
// deep-link to GitOps detail rather than the resource drawer — the lane is
|
|
186
192
|
// already telling the user "this controller had changes/events"; the GitOps
|
|
@@ -203,6 +209,9 @@ export function TimelineSwimlanes({ events, isLoading, onResourceClick, viewMode
|
|
|
203
209
|
const [expandedLanes, setExpandedLanes] = useState<Set<string>>(new Set())
|
|
204
210
|
const [hasAutoZoomed, setHasAutoZoomed] = useState(false)
|
|
205
211
|
const [groupByApp, setGroupByApp] = useState(true) // Group by app.kubernetes.io/name label
|
|
212
|
+
const [showDeletedInternal, setShowDeletedInternal] = useState(true)
|
|
213
|
+
const showDeleted = showDeletedProp ?? showDeletedInternal
|
|
214
|
+
const setShowDeleted = onShowDeletedChange ?? setShowDeletedInternal
|
|
206
215
|
|
|
207
216
|
// Stable lane ordering - use ref to avoid render loop (lanes depends on order, order depends on lanes)
|
|
208
217
|
const laneOrderRef = useRef<Map<string, number>>(new Map())
|
|
@@ -253,17 +262,18 @@ export function TimelineSwimlanes({ events, isLoading, onResourceClick, viewMode
|
|
|
253
262
|
|
|
254
263
|
// Filter events by search term
|
|
255
264
|
const filteredEvents = useMemo(() => {
|
|
256
|
-
|
|
265
|
+
const baseEvents = showDeleted ? events : events.filter(e => e.eventType !== 'delete')
|
|
266
|
+
if (!searchTerm) return baseEvents
|
|
257
267
|
|
|
258
268
|
const term = searchTerm.toLowerCase()
|
|
259
|
-
return
|
|
269
|
+
return baseEvents.filter(e =>
|
|
260
270
|
e.name.toLowerCase().includes(term) ||
|
|
261
271
|
e.kind.toLowerCase().includes(term) ||
|
|
262
272
|
e.namespace?.toLowerCase().includes(term) ||
|
|
263
273
|
e.reason?.toLowerCase().includes(term) ||
|
|
264
274
|
e.message?.toLowerCase().includes(term)
|
|
265
275
|
)
|
|
266
|
-
}, [events, searchTerm])
|
|
276
|
+
}, [events, searchTerm, showDeleted])
|
|
267
277
|
|
|
268
278
|
// Build hierarchical lanes using owner references + topology edges
|
|
269
279
|
// Uses the shared utility from utils/resource-hierarchy.ts
|
|
@@ -514,6 +524,18 @@ export function TimelineSwimlanes({ events, isLoading, onResourceClick, viewMode
|
|
|
514
524
|
<span className="border-b border-dotted border-theme-text-tertiary">Group by app</span>
|
|
515
525
|
</label>
|
|
516
526
|
</Tooltip>
|
|
527
|
+
<Tooltip content="Show resources Radar observed being deleted, including Pods that no longer exist" position="bottom">
|
|
528
|
+
<label className="flex items-center gap-1.5 text-xs text-theme-text-secondary hover:text-theme-text-primary">
|
|
529
|
+
<input
|
|
530
|
+
type="checkbox"
|
|
531
|
+
checked={showDeleted}
|
|
532
|
+
onChange={(e) => setShowDeleted(e.target.checked)}
|
|
533
|
+
className="w-3.5 h-3.5 rounded border-theme-border-light bg-theme-elevated text-accent focus:ring-accent focus:ring-offset-0"
|
|
534
|
+
/>
|
|
535
|
+
<Trash2 className="w-3.5 h-3.5" />
|
|
536
|
+
<span className="border-b border-dotted border-theme-text-tertiary">Deleted</span>
|
|
537
|
+
</label>
|
|
538
|
+
</Tooltip>
|
|
517
539
|
{/* View toggle */}
|
|
518
540
|
{onViewModeChange && (
|
|
519
541
|
<div className="flex items-center gap-1 bg-theme-elevated rounded-lg p-1">
|
|
@@ -38,6 +38,8 @@ export interface SearchPillInputProps {
|
|
|
38
38
|
rightSlot?: React.ReactNode
|
|
39
39
|
/** Applied to the input container (host owns the box chrome: height, bg, border). */
|
|
40
40
|
className?: string
|
|
41
|
+
/** Applied to the `<input>` itself (host owns text size: e.g. a hero variant). */
|
|
42
|
+
inputClassName?: string
|
|
41
43
|
'aria-label'?: string
|
|
42
44
|
/** Fires when the modifier autocomplete opens/closes, so the host can suppress
|
|
43
45
|
its own results dropdown while a modifier is being completed. */
|
|
@@ -101,6 +103,7 @@ export function SearchPillInput({
|
|
|
101
103
|
leftSlot,
|
|
102
104
|
rightSlot,
|
|
103
105
|
className,
|
|
106
|
+
inputClassName,
|
|
104
107
|
onSuggestingChange,
|
|
105
108
|
...rest
|
|
106
109
|
}: SearchPillInputProps) {
|
|
@@ -110,6 +113,20 @@ export function SearchPillInput({
|
|
|
110
113
|
const [sel, setSel] = useState(0)
|
|
111
114
|
const [dismissed, setDismissed] = useState(false)
|
|
112
115
|
const [anchor, setAnchor] = useState<{ left: number; bottom: number } | null>(null)
|
|
116
|
+
// Collapse a long pill list to a few + "+N more" so they don't flood the
|
|
117
|
+
// field (e.g. a broad namespace scope seeded as ns: pills). Expanding wraps
|
|
118
|
+
// them so every pill stays removable.
|
|
119
|
+
const [pillsExpanded, setPillsExpanded] = useState(false)
|
|
120
|
+
const MAX_VISIBLE_PILLS = 3
|
|
121
|
+
// Reset the expand toggle once there's nothing to collapse (pills cleared or
|
|
122
|
+
// trimmed) — otherwise a later seed of many pills would render expanded,
|
|
123
|
+
// re-flooding the field instead of collapsing.
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (pills.length <= MAX_VISIBLE_PILLS + 1) setPillsExpanded(false)
|
|
126
|
+
}, [pills.length])
|
|
127
|
+
const collapsePills = !pillsExpanded && pills.length > MAX_VISIBLE_PILLS + 1
|
|
128
|
+
const shownPills = collapsePills ? pills.slice(0, MAX_VISIBLE_PILLS) : pills
|
|
129
|
+
const hiddenPillCount = pills.length - shownPills.length
|
|
113
130
|
|
|
114
131
|
const mod = useMemo(() => activeModifier(text, aliases), [text, aliases])
|
|
115
132
|
|
|
@@ -181,12 +198,12 @@ export function SearchPillInput({
|
|
|
181
198
|
}, [suggesting, filtered, sel, mod, text, pills, commitPill, onChange, onKeyDown])
|
|
182
199
|
|
|
183
200
|
return (
|
|
184
|
-
<div ref={containerRef} className={clsx('flex items-center gap-1.5', className)} onClick={() => inputRef.current?.focus()}>
|
|
201
|
+
<div ref={containerRef} className={clsx('flex items-center gap-1.5', pillsExpanded && 'flex-wrap', className)} onClick={() => inputRef.current?.focus()}>
|
|
185
202
|
{leftSlot}
|
|
186
|
-
{
|
|
203
|
+
{shownPills.map((p, i) => (
|
|
187
204
|
<span key={`${p.key}:${p.value}:${i}`} className="inline-flex items-center gap-1 shrink-0 rounded-md bg-theme-elevated border border-theme-border-light pl-1.5 pr-1 py-0.5 text-xs whitespace-nowrap">
|
|
188
205
|
<span className="text-theme-text-tertiary">{p.key}:</span>
|
|
189
|
-
<span className="text-theme-text-primary
|
|
206
|
+
<span className="max-w-[16ch] truncate font-medium text-theme-text-primary" title={p.value}>{p.value}</span>
|
|
190
207
|
<button
|
|
191
208
|
type="button"
|
|
192
209
|
tabIndex={-1}
|
|
@@ -198,6 +215,27 @@ export function SearchPillInput({
|
|
|
198
215
|
</button>
|
|
199
216
|
</span>
|
|
200
217
|
))}
|
|
218
|
+
{collapsePills && hiddenPillCount > 0 && (
|
|
219
|
+
<button
|
|
220
|
+
type="button"
|
|
221
|
+
tabIndex={-1}
|
|
222
|
+
onMouseDown={(e) => { e.preventDefault(); setPillsExpanded(true) }}
|
|
223
|
+
className="shrink-0 rounded-md bg-theme-elevated border border-theme-border-light px-1.5 py-0.5 text-xs font-medium text-theme-text-secondary hover:text-theme-text-primary whitespace-nowrap"
|
|
224
|
+
title="Show all filters"
|
|
225
|
+
>
|
|
226
|
+
+{hiddenPillCount} more
|
|
227
|
+
</button>
|
|
228
|
+
)}
|
|
229
|
+
{pillsExpanded && pills.length > MAX_VISIBLE_PILLS + 1 && (
|
|
230
|
+
<button
|
|
231
|
+
type="button"
|
|
232
|
+
tabIndex={-1}
|
|
233
|
+
onMouseDown={(e) => { e.preventDefault(); setPillsExpanded(false) }}
|
|
234
|
+
className="shrink-0 rounded-md px-1.5 py-0.5 text-xs text-theme-text-tertiary hover:text-theme-text-primary whitespace-nowrap"
|
|
235
|
+
>
|
|
236
|
+
less
|
|
237
|
+
</button>
|
|
238
|
+
)}
|
|
201
239
|
<input
|
|
202
240
|
ref={inputRef}
|
|
203
241
|
type="text"
|
|
@@ -207,7 +245,10 @@ export function SearchPillInput({
|
|
|
207
245
|
onFocus={onFocus}
|
|
208
246
|
placeholder={pills.length ? '' : placeholder}
|
|
209
247
|
aria-label={rest['aria-label']}
|
|
210
|
-
className=
|
|
248
|
+
className={clsx(
|
|
249
|
+
'flex-1 min-w-[80px] bg-transparent text-theme-text-primary placeholder-theme-text-tertiary outline-none',
|
|
250
|
+
inputClassName ?? 'text-sm',
|
|
251
|
+
)}
|
|
211
252
|
/>
|
|
212
253
|
{rightSlot}
|
|
213
254
|
{suggesting && anchor && mod && createPortal(
|