Initial commit: AeroFetch — yt-dlp/YouTube downloader for Windows
Electron + React (Fluent UI) desktop frontend for yt-dlp: - Download queue with live progress, concurrency cap, cancel/retry - Format/quality picker via yt-dlp probe; audio extraction to MP3 - Settings + history persistence (electron-store / JSON) - Clipboard link auto-detect; persisted light/dark theme - NSIS + portable packaging (binaries bundled at build time, not in git) UI: "Studio" sidebar layout with a toffee-brown theme (light + neutral dark). Dropdowns use a native <select> and tooltips a CSS-only Hint, to avoid a dev-machine GPU overlay flicker/blank issue. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import { makeStyles, mergeClasses, tokens, shorthands } from '@fluentui/react-components'
|
||||
|
||||
// A native <select> styled to match Fluent inputs. We use this instead of
|
||||
// Fluent's <Dropdown> because Fluent's portal/popover menu is a composited
|
||||
// overlay in the WebContents, and on this dev machine's GPU/driver that overlay
|
||||
// paint blanks the whole window (same family as the documented flicker —
|
||||
// hardware acceleration is already disabled; see memory "aerofetch-gpu-flicker").
|
||||
// The native <select> popup is drawn by the OS, so it can't trigger the blank.
|
||||
// The popup's light/dark styling follows the `color-scheme` set on the app root
|
||||
// in App.tsx.
|
||||
const useStyles = makeStyles({
|
||||
select: {
|
||||
width: '100%',
|
||||
height: '32px',
|
||||
padding: '0 8px',
|
||||
fontSize: tokens.fontSizeBase300,
|
||||
fontFamily: tokens.fontFamilyBase,
|
||||
color: tokens.colorNeutralForeground1,
|
||||
backgroundColor: tokens.colorNeutralBackground1,
|
||||
border: `1px solid ${tokens.colorNeutralStroke1}`,
|
||||
...shorthands.borderRadius(tokens.borderRadiusMedium),
|
||||
cursor: 'pointer',
|
||||
':hover': {
|
||||
borderColor: tokens.colorNeutralStroke1Hover
|
||||
},
|
||||
':focus-visible': {
|
||||
outline: 'none',
|
||||
borderColor: tokens.colorCompoundBrandStroke
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export interface SelectOption {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
interface SelectProps {
|
||||
value: string
|
||||
options: SelectOption[]
|
||||
onChange: (value: string) => void
|
||||
className?: string
|
||||
'aria-label'?: string
|
||||
}
|
||||
|
||||
export function Select({
|
||||
value,
|
||||
options,
|
||||
onChange,
|
||||
className,
|
||||
'aria-label': ariaLabel
|
||||
}: SelectProps): React.JSX.Element {
|
||||
const styles = useStyles()
|
||||
return (
|
||||
<select
|
||||
className={mergeClasses(styles.select, className)}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{options.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user