Skip to content

Commit 6f652f2

Browse files
asizikovCopilot
andcommitted
refactor: migrate App shell and UI components to Tailwind
Replace BEM CSS classes with Tailwind utility classes using Primer design tokens for: - App.tsx (header, sidebar, content layout, footer) - UploadPage (drag-drop zone, progress bar, spinner) - InfoTip (popover positioning and hover states) - SummaryCard (static and clickable variants) - CostBreakdownCard (row variants with semantic colors) - BillingComparisonCard (savings/overspend verdict badges) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 81bfe5b commit 6f652f2

6 files changed

Lines changed: 149 additions & 120 deletions

File tree

src/App.tsx

Lines changed: 61 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useCallback, useRef, useState } from 'react'
22
import type { ChangeEvent, DragEvent, KeyboardEvent, MouseEvent } from 'react'
3-
import './App.css'
3+
44
import { UploadPage } from './components'
55
import { UsersView } from './views/UsersView'
66
import type { SeatOverrides } from './views/UsersView'
@@ -186,8 +186,13 @@ function App() {
186186
users: reportUsers,
187187
})
188188

189+
const sidebarItemBase = 'flex items-center gap-[10px] w-full px-3 py-[10px] border-0 rounded-md bg-transparent text-[13px] font-medium cursor-pointer text-left transition-colors hover:bg-bg-muted hover:text-fg-default disabled:opacity-40 disabled:cursor-default disabled:hover:bg-transparent disabled:hover:text-fg-muted focus-visible:outline-2 focus-visible:outline-app-accent focus-visible:outline-offset-[-2px] max-sm:justify-center max-sm:p-2'
190+
const sidebarActive = 'bg-app-accent-subtle text-app-accent font-semibold hover:bg-app-accent-muted'
191+
const sidebarInactive = 'text-fg-muted'
192+
const viewContentClasses = 'max-w-[var(--width-content-max)] w-full mx-auto px-6 pt-8 pb-12 flex flex-col gap-6 min-[1440px]:-translate-x-[calc(var(--width-sidebar)/2)]'
193+
189194
return (
190-
<div className={`app ${hasReport ? 'app--review' : ''}`}>
195+
<div className={`min-h-screen flex flex-col ${hasReport ? 'bg-bg-muted' : ''}`}>
191196
<input
192197
ref={fileInputRef}
193198
id="file-input"
@@ -198,16 +203,16 @@ function App() {
198203
style={{ display: 'none' }}
199204
/>
200205

201-
<header className="site-header">
202-
<div className="site-header__left">
203-
<svg className="site-header__logo" height="32" viewBox="0 0 16 16" width="32" fill="#fff" aria-hidden="true">
206+
<header className="bg-header-bg py-[14px] px-6 flex justify-between items-center gap-3 flex-wrap max-sm:px-4 max-sm:py-3">
207+
<div className="flex items-center gap-3">
208+
<svg className="block" height="32" viewBox="0 0 16 16" width="32" fill="#fff" aria-hidden="true">
204209
<path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z" />
205210
</svg>
206-
<span className="site-header__title">Billing Preview</span>
211+
<span className="text-lg font-semibold text-white tracking-tight max-sm:text-xs">Billing Preview</span>
207212
</div>
208213
{hasReport && (
209-
<div className="site-header__right">
210-
<button className="site-header__button" onClick={triggerFileDialog}>
214+
<div className="flex items-center max-sm:w-full">
215+
<button className="border border-border-emphasis rounded-md bg-transparent text-white px-4 py-2 text-sm font-medium cursor-pointer transition-colors whitespace-nowrap hover:bg-white/[0.08] hover:border-border-emphasis focus-visible:outline-2 focus-visible:outline-white focus-visible:outline-offset-2 max-sm:w-full max-sm:text-center" onClick={triggerFileDialog}>
211216
Upload another report
212217
</button>
213218
</div>
@@ -216,144 +221,144 @@ function App() {
216221

217222
{hasReport ? (
218223
<>
219-
<nav className="secondary-header">
220-
<div className="secondary-header__info">
221-
<span className="secondary-header__label">File:</span>
222-
<span className="secondary-header__value">{fileName ?? 'Processing…'}</span>
224+
<nav className="bg-bg-default border-b border-border-default px-6 py-3 flex justify-between items-center gap-4 flex-wrap max-sm:px-4 max-sm:flex-col max-sm:items-start max-sm:gap-3">
225+
<div className="flex items-center gap-2 flex-wrap text-sm text-fg-default max-sm:flex-col max-sm:items-start max-sm:gap-1">
226+
<span className="text-fg-muted font-medium">File:</span>
227+
<span className="font-semibold text-fg-default">{fileName ?? 'Processing…'}</span>
223228
{reportContext && (reportContext.startDate || reportContext.endDate) && (
224229
<>
225-
<span className="secondary-header__separator">|</span>
226-
<span className="secondary-header__label">Report window:</span>
227-
<span className="secondary-header__value">
230+
<span className="text-border-default mx-1 max-sm:hidden">|</span>
231+
<span className="text-fg-muted font-medium">Report window:</span>
232+
<span className="font-semibold text-fg-default">
228233
{reportContext.startDate ?? '—'} to {reportContext.endDate ?? '—'}
229234
</span>
230235
</>
231236
)}
232237
{quickStats && (
233238
<>
234-
<span className="secondary-header__separator">|</span>
235-
<span className="secondary-header__label">Total rows:</span>
236-
<span className="secondary-header__value">{quickStats.lineCount.toLocaleString()}</span>
239+
<span className="text-border-default mx-1 max-sm:hidden">|</span>
240+
<span className="text-fg-muted font-medium">Total rows:</span>
241+
<span className="font-semibold text-fg-default">{quickStats.lineCount.toLocaleString()}</span>
237242
</>
238243
)}
239244
</div>
240245
</nav>
241246

242-
<div className="app-layout">
243-
<aside className="sidebar" aria-label="Navigation">
244-
<nav className="sidebar__nav">
247+
<div className="flex flex-1 min-h-0">
248+
<aside className="w-[var(--width-sidebar)] shrink-0 p-4 pr-0 sticky top-0 self-start max-h-screen overflow-y-auto max-sm:w-12 max-sm:pl-1" aria-label="Navigation">
249+
<nav className="bg-bg-default border border-border-default rounded-lg p-[6px] flex flex-col gap-[2px] max-sm:border-0 max-sm:p-[2px]">
245250
<button
246251
type="button"
247-
className={`sidebar__item ${activeView === 'overview' ? 'sidebar__item--active' : ''}`}
252+
className={`${sidebarItemBase} ${activeView === 'overview' ? sidebarActive : sidebarInactive}`}
248253
onClick={() => setActiveView('overview')}
249254
>
250-
<svg className="sidebar__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
255+
<svg className="w-[18px] h-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
251256
<rect x="3" y="3" width="7" height="7" rx="1" />
252257
<rect x="14" y="3" width="7" height="7" rx="1" />
253258
<rect x="3" y="14" width="7" height="7" rx="1" />
254259
<rect x="14" y="14" width="7" height="7" rx="1" />
255260
</svg>
256-
<span className="sidebar__label">Overview</span>
261+
<span className="whitespace-nowrap overflow-hidden text-ellipsis max-sm:hidden">Overview</span>
257262
</button>
258263

259264
<button
260265
type="button"
261-
className={`sidebar__item ${userNavActive ? 'sidebar__item--active' : ''}`}
266+
className={`${sidebarItemBase} ${userNavActive ? sidebarActive : sidebarInactive}`}
262267
disabled={!userUsage}
263268
onClick={openUserView}
264269
>
265-
<svg className="sidebar__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
270+
<svg className="w-[18px] h-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
266271
<circle cx="12" cy="8" r="4" />
267272
<path d="M20 21a8 8 0 1 0-16 0" />
268273
</svg>
269-
<span className="sidebar__label">{isIndividualReport ? 'User' : 'Users'}</span>
274+
<span className="whitespace-nowrap overflow-hidden text-ellipsis max-sm:hidden">{isIndividualReport ? 'User' : 'Users'}</span>
270275
</button>
271276

272277
{modelUsage && modelUsage.models.length > 0 && (
273278
<button
274279
type="button"
275-
className={`sidebar__item ${activeView === 'models' ? 'sidebar__item--active' : ''}`}
280+
className={`${sidebarItemBase} ${activeView === 'models' ? sidebarActive : sidebarInactive}`}
276281
onClick={() => setActiveView('models')}
277282
>
278-
<svg className="sidebar__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
283+
<svg className="w-[18px] h-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
279284
<path d="M12 2L2 7l10 5 10-5-10-5Z" />
280285
<path d="M2 17l10 5 10-5" />
281286
<path d="M2 12l10 5 10-5" />
282287
</svg>
283-
<span className="sidebar__label">Models</span>
288+
<span className="whitespace-nowrap overflow-hidden text-ellipsis max-sm:hidden">Models</span>
284289
</button>
285290
)}
286291

287292
<button
288293
type="button"
289-
className={`sidebar__item ${activeView === 'products' ? 'sidebar__item--active' : ''}`}
294+
className={`${sidebarItemBase} ${activeView === 'products' ? sidebarActive : sidebarInactive}`}
290295
onClick={() => setActiveView('products')}
291296
>
292-
<svg className="sidebar__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
297+
<svg className="w-[18px] h-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
293298
<path d="M3 9h18M3 15h18M9 3v18M15 3v18" />
294299
</svg>
295-
<span className="sidebar__label">Products</span>
300+
<span className="whitespace-nowrap overflow-hidden text-ellipsis max-sm:hidden">Products</span>
296301
</button>
297302

298303
{orgs && orgs.organizations.length > 0 && (
299304
<button
300305
type="button"
301-
className={`sidebar__item ${activeView === 'orgs' ? 'sidebar__item--active' : ''}`}
306+
className={`${sidebarItemBase} ${activeView === 'orgs' ? sidebarActive : sidebarInactive}`}
302307
onClick={() => setActiveView('orgs')}
303308
>
304-
<svg className="sidebar__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
309+
<svg className="w-[18px] h-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
305310
<rect x="4" y="3" width="16" height="18" rx="1" />
306311
<path d="M9 7h2M9 11h2M9 15h2M13 7h2M13 11h2M13 15h2" />
307312
</svg>
308-
<span className="sidebar__label">Organizations</span>
313+
<span className="whitespace-nowrap overflow-hidden text-ellipsis max-sm:hidden">Organizations</span>
309314
</button>
310315
)}
311316

312317
{costCenters && costCenters.costCenters.length > 0 && (
313318
<button
314319
type="button"
315-
className={`sidebar__item ${activeView === 'costCenters' ? 'sidebar__item--active' : ''}`}
320+
className={`${sidebarItemBase} ${activeView === 'costCenters' ? sidebarActive : sidebarInactive}`}
316321
onClick={() => setActiveView('costCenters')}
317322
>
318-
<svg className="sidebar__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
323+
<svg className="w-[18px] h-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
319324
<path d="M12 3C8.5 3 6 5 6 7c0 2 2.5 4 6 4s6-2 6-4c0-2-2.5-4-6-4Z" />
320325
<path d="M6 7v4c0 2 2.5 4 6 4s6-2 6-4V7" />
321326
<path d="M6 11v4c0 2 2.5 4 6 4s6-2 6-4v-4" />
322327
</svg>
323-
<span className="sidebar__label">Cost Centers</span>
328+
<span className="whitespace-nowrap overflow-hidden text-ellipsis max-sm:hidden">Cost Centers</span>
324329
</button>
325330
)}
326331

327-
<hr className="sidebar__divider" />
332+
<hr className="border-0 border-t border-border-default my-[6px]" />
328333

329334
<button
330335
type="button"
331-
className={`sidebar__item ${activeView === 'guide' ? 'sidebar__item--active' : ''}`}
336+
className={`${sidebarItemBase} ${activeView === 'guide' ? sidebarActive : sidebarInactive}`}
332337
onClick={() => setActiveView('guide')}
333338
>
334-
<svg className="sidebar__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
339+
<svg className="w-[18px] h-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
335340
<circle cx="12" cy="12" r="10" />
336341
<path d="M12 16v-4M12 8h.01" />
337342
</svg>
338-
<span className="sidebar__label">Report Format</span>
343+
<span className="whitespace-nowrap overflow-hidden text-ellipsis max-sm:hidden">Report Format</span>
339344
</button>
340345

341346
<button
342347
type="button"
343-
className={`sidebar__item ${activeView === 'faq' ? 'sidebar__item--active' : ''}`}
348+
className={`${sidebarItemBase} ${activeView === 'faq' ? sidebarActive : sidebarInactive}`}
344349
onClick={() => setActiveView('faq')}
345350
>
346-
<svg className="sidebar__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
351+
<svg className="w-[18px] h-[18px] shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" focusable="false">
347352
<circle cx="12" cy="12" r="10" />
348353
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
349354
<circle cx="12" cy="17" r="0.5" fill="currentColor" />
350355
</svg>
351-
<span className="sidebar__label">FAQ</span>
356+
<span className="whitespace-nowrap overflow-hidden text-ellipsis max-sm:hidden">FAQ</span>
352357
</button>
353358
</nav>
354359
</aside>
355360

356-
<main className="report-content">
361+
<main className="flex-1 min-w-0 flex flex-col">
357362
{activeView === 'overview' ? (
358363
<OverviewView
359364
error={error}
@@ -364,7 +369,7 @@ function App() {
364369
/>
365370
) : activeView === 'models' ? (
366371
modelUsage && modelUsage.models.length > 0 ? (
367-
<div className="view-content">
372+
<div className={viewContentClasses}>
368373
<ModelsView
369374
modelUsage={modelUsage}
370375
rangeStart={rangeStart}
@@ -373,7 +378,7 @@ function App() {
373378
</div>
374379
) : null
375380
) : activeView === 'users' && !isIndividualReport ? (
376-
<div className="view-content">
381+
<div className={viewContentClasses}>
377382
<UsersView
378383
users={reportUsers}
379384
costOptimizationOpportunity={costOptimizationOpportunity}
@@ -386,7 +391,7 @@ function App() {
386391
/>
387392
</div>
388393
) : activeView === 'userDetails' || (activeView === 'users' && isIndividualReport) ? (
389-
<div className="view-content">
394+
<div className={viewContentClasses}>
390395
<UserDetailsView
391396
user={selectedUser}
392397
reportPlanScope={reportPlanScope}
@@ -398,23 +403,23 @@ function App() {
398403
/>
399404
</div>
400405
) : activeView === 'costCenters' ? (
401-
<div className="view-content">
406+
<div className={viewContentClasses}>
402407
<CostCentersView data={costCenters ?? { costCenters: [] }} rangeStart={rangeStart} />
403408
</div>
404409
) : activeView === 'products' ? (
405-
<div className="view-content">
410+
<div className={viewContentClasses}>
406411
<ProductsView data={productUsage ?? { products: [] }} />
407412
</div>
408413
) : activeView === 'guide' ? (
409-
<div className="view-content">
414+
<div className={viewContentClasses}>
410415
<ReportGuideView />
411416
</div>
412417
) : activeView === 'faq' ? (
413-
<div className="view-content">
418+
<div className={viewContentClasses}>
414419
<FaqView />
415420
</div>
416421
) : (
417-
<div className="view-content">
422+
<div className={viewContentClasses}>
418423
<OrganizationsView data={orgs ?? { organizations: [] }} rangeStart={rangeStart} />
419424
</div>
420425
)}
@@ -437,7 +442,7 @@ function App() {
437442
)}
438443

439444
{hasReport && (
440-
<footer className="site-footer">
445+
<footer className="text-center text-fg-muted text-xs leading-[1.6] pt-6 px-4 pb-10 max-w-[960px] mx-auto w-full [&_a]:text-fg-muted [&_a]:no-underline [&_a:hover]:underline">
441446
This is a preview based on your uploaded usage data. Actual bills may differ.<br />
442447
Your data never leaves your browser.{' '}
443448
Questions? <a href="https://support.github.com" target="_blank" rel="noopener noreferrer">Contact GitHub Support</a>.

0 commit comments

Comments
 (0)