3d4e574916
Shared UI primitives to end the "two ways to do X" drift on surfaces: - EmptyState (ui/EmptyState.tsx): one centered empty block — optional focal icon/badge + Body1 message + optional muted hint, with a `compact` variant for in-content placeholders. Adopted by Downloads, History, Library (L116), and — compact — the Terminal log, Diagnostics, and TemplateManager (L117). - Banner (ui/Banner.tsx) + LinkSuggestion (ui/LinkSuggestion.tsx): one tinted notification row (icon + truncating/wrap content + actions, tone brand/warning) replacing five hand-rolled style blocks — the DownloadBar copied-link suggestion / channel nudge / duplicate warning and the Library copied-link suggestion (UI19). - useTextStyles().truncate: shared single-line-ellipsis utility, adopted in Library rows + the playlist panel + Banner; `title` now also sets minWidth:0 so it shrinks in a flex row (L126). - L102: Settings "Default format" now uses the same SegmentedControl (Video/ Audio) + quality Select as the DownloadBar — one mental model; the combined `kind|quality` string encoding is gone. Resolved by verified convention (documented in code / CODE-AUDIT.md): - L103 busy = in-button icon→Spinner everywhere (done in L134; no adjacent- spinner pattern survives). - L115 list-row actions icon-only+tooltip vs card-toolbar actions labelled. - L120 Fluent <Card> for static content cards; tokenized surface recipe for interactive/list-item cards. - UI20 toggles = solid brand, list-selection = brand-tint (rule noted on SegmentedControl); the cited Library pill is gone via UI18. L128 (build-time DCE of preview seeds) deferred to Batch 14 with PERF8. Verified: typecheck (node+web) + 268 tests + eslint + production build green; touched files prettier-clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
import { Body1, Caption1, makeStyles, mergeClasses, tokens } from '@fluentui/react-components'
|
|
import { SPACE } from './tokens'
|
|
|
|
/**
|
|
* One shared empty-state block (L116/L117). The screens had drifted into three
|
|
* shapes — Downloads (icon + line), History (colored badge + line + sub-hint),
|
|
* Library (icon + line, no sub-hint) — and the Terminal / Diagnostics "empty"
|
|
* text wasn't a centered block at all (inline text). This centers them all on one
|
|
* structure: an optional focal `icon` (a hero glyph or a colored badge), a `Body1`
|
|
* message at normal foreground, and an optional muted `hint` line.
|
|
*
|
|
* `compact` swaps the tall screen-level padding (56px) for a smaller inset (32px),
|
|
* for placeholders that sit inside an already-populated screen (the Terminal log,
|
|
* the Diagnostics list) rather than filling an empty one.
|
|
*/
|
|
const useStyles = makeStyles({
|
|
root: {
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
gap: SPACE.tight,
|
|
padding: '56px 16px',
|
|
// The container is muted, so a hero glyph and the hint line read as secondary;
|
|
// the message overrides back to the normal foreground so it stays legible.
|
|
color: tokens.colorNeutralForeground3,
|
|
textAlign: 'center'
|
|
},
|
|
compact: {
|
|
padding: '32px 16px'
|
|
},
|
|
message: {
|
|
color: tokens.colorNeutralForeground1
|
|
}
|
|
})
|
|
|
|
export function EmptyState({
|
|
icon,
|
|
message,
|
|
hint,
|
|
compact = false,
|
|
className
|
|
}: {
|
|
icon?: React.ReactNode
|
|
message: React.ReactNode
|
|
hint?: React.ReactNode
|
|
compact?: boolean
|
|
className?: string
|
|
}): React.JSX.Element {
|
|
const styles = useStyles()
|
|
return (
|
|
<div className={mergeClasses(styles.root, compact && styles.compact, className)}>
|
|
{icon}
|
|
<Body1 className={styles.message}>{message}</Body1>
|
|
{hint && <Caption1>{hint}</Caption1>}
|
|
</div>
|
|
)
|
|
}
|