|
1 | 1 | 'use client' |
2 | 2 |
|
3 | | -import type { ComponentType } from 'react' |
4 | | -import { Avatar, cn, disclosureChevronClass, formatChartCompactNumber } from '@sim/emcn' |
5 | | -import { ArrowRight, ChevronDown } from '@sim/emcn/icons' |
| 3 | +import { type ComponentType, useId, useState } from 'react' |
| 4 | +import { |
| 5 | + Avatar, |
| 6 | + Button, |
| 7 | + cn, |
| 8 | + disclosureChevronClass, |
| 9 | + FloatingTooltip, |
| 10 | + formatChartCompactNumber, |
| 11 | + OverflowText, |
| 12 | + Popover, |
| 13 | + PopoverContent, |
| 14 | + PopoverTrigger, |
| 15 | + useFloatingTooltip, |
| 16 | +} from '@sim/emcn' |
| 17 | +import { ArrowRight, ChevronDown, CircleInfo } from '@sim/emcn/icons' |
6 | 18 | import { |
7 | 19 | AnthropicIcon, |
8 | 20 | AzureIcon, |
@@ -91,6 +103,7 @@ const PROVIDER_ICONS: Readonly<Record<string, ComponentType<{ className?: string |
91 | 103 |
|
92 | 104 | interface UsageConsumerRowProps { |
93 | 105 | row: OrganizationUsageBreakdownRow |
| 106 | + info?: string |
94 | 107 | /** Member rows lead with the member's avatar where other rows show a provider mark. */ |
95 | 108 | isMember: boolean |
96 | 109 | /** BYOK rows carry no cost, so tokens are the only usage they can show. */ |
@@ -124,12 +137,46 @@ const TRAILING_SLOT_CLASSES = { |
124 | 137 | */ |
125 | 138 | export const USAGE_ROW_CLASSES = 'flex w-full items-center gap-2.5 rounded-lg p-2 text-left' |
126 | 139 |
|
| 140 | +interface UsageInfoProps { |
| 141 | + label: string |
| 142 | + info: string |
| 143 | +} |
| 144 | + |
| 145 | +function UsageInfo({ label, info }: UsageInfoProps) { |
| 146 | + const [open, setOpen] = useState(false) |
| 147 | + const tooltipId = useId() |
| 148 | + const tooltip = useFloatingTooltip(() => !open, { revalidateKey: open }) |
| 149 | + |
| 150 | + return ( |
| 151 | + <Popover open={open} onOpenChange={setOpen}> |
| 152 | + <PopoverTrigger asChild> |
| 153 | + <Button |
| 154 | + variant='quiet' |
| 155 | + size='icon' |
| 156 | + aria-label={label} |
| 157 | + aria-describedby={!open && tooltip.state.visible ? tooltipId : undefined} |
| 158 | + {...tooltip.handlers} |
| 159 | + > |
| 160 | + <CircleInfo className='size-[14px]' /> |
| 161 | + </Button> |
| 162 | + </PopoverTrigger> |
| 163 | + <PopoverContent appearance='tooltip' aria-label={label}> |
| 164 | + {info} |
| 165 | + </PopoverContent> |
| 166 | + {!open && ( |
| 167 | + <FloatingTooltip id={tooltipId} role='tooltip' label={info} state={tooltip.state} /> |
| 168 | + )} |
| 169 | + </Popover> |
| 170 | + ) |
| 171 | +} |
| 172 | + |
127 | 173 | /** |
128 | 174 | * A tabular row, not `SettingsResourceRow` — tabular columns are the sanctioned |
129 | 175 | * exception in `sim-settings-pages.md`, alongside billing invoices and credit usage. |
130 | 176 | */ |
131 | 177 | function UsageConsumerRow({ |
132 | 178 | row, |
| 179 | + info, |
133 | 180 | isMember, |
134 | 181 | showTokensOnly, |
135 | 182 | onSelect, |
@@ -158,7 +205,14 @@ function UsageConsumerRow({ |
158 | 205 | ) : ( |
159 | 206 | ProviderIcon && <ProviderIcon className='size-[14px] shrink-0 text-[var(--text-icon)]' /> |
160 | 207 | )} |
161 | | - <span className='min-w-0 flex-1 truncate text-[var(--text-body)] text-sm'>{row.label}</span> |
| 208 | + <div className='flex min-w-0 flex-1 items-center gap-1'> |
| 209 | + <OverflowText |
| 210 | + label={row.label} |
| 211 | + className='text-[var(--text-body)] text-sm' |
| 212 | + focusTarget={onSelect ? 'nearest-interactive' : undefined} |
| 213 | + /> |
| 214 | + {info && <UsageInfo label={`About ${row.label.toLowerCase()}`} info={info} />} |
| 215 | + </div> |
162 | 216 | <div |
163 | 217 | className='h-[4px] w-[64px] shrink-0 overflow-hidden rounded-full bg-[var(--border)]' |
164 | 218 | aria-hidden='true' |
@@ -243,9 +297,14 @@ export function UsageConsumers({ |
243 | 297 | <UsageConsumerRow |
244 | 298 | key={`${dimension}-${row.id}`} |
245 | 299 | row={row} |
| 300 | + info={ |
| 301 | + dimension === 'workspace' && !row.id |
| 302 | + ? 'Usage without a workspace link, including organization-level activity and permanently deleted workspaces.' |
| 303 | + : undefined |
| 304 | + } |
246 | 305 | isMember={dimension === 'member'} |
247 | 306 | showTokensOnly={showTokensOnly} |
248 | | - {...(onExpandOther && trailingSlot ? { reservedTrailing: trailingSlot } : {})} |
| 307 | + {...(trailingSlot ? { reservedTrailing: trailingSlot } : {})} |
249 | 308 | {...(onSelectRow && row.id ? { onSelect: onSelectRow } : {})} |
250 | 309 | {...(rowActions && row.id ? { actions: rowActions(row) } : {})} |
251 | 310 | /> |
|
0 commit comments