mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 09:27:29 +00:00
feat(settings): preview each font in its own typeface in font pickers (#33254)
* feat(settings): preview each font in its own typeface in font pickers * perf(settings): skip offscreen font rows so opening the picker loads only visible fonts * fix(settings): keep symbol font names readable in font pickers * fix(settings): size skipped font rows by their content box --------- Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
@@ -7,6 +7,27 @@ import { ScrollArea } from '../ui/scroll-area'
|
||||
import { filterFontSuggestions, getRenderedFontSuggestions } from './settings-form-option-filter'
|
||||
import { useInstalledFontsUnavailable } from './installed-font-list'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { buildAppFontFamily } from '@/lib/app-font-family'
|
||||
|
||||
// Why: these OS-shipped fonts draw pictographs or Greek for Latin letters, so their own name would be unreadable.
|
||||
const SYMBOL_FONT_FAMILIES = new Set([
|
||||
'webdings',
|
||||
'wingdings',
|
||||
'wingdings 2',
|
||||
'wingdings 3',
|
||||
'marlett',
|
||||
'symbol',
|
||||
'mt extra',
|
||||
'bookshelf symbol 7',
|
||||
'd050000l',
|
||||
'standard symbols ps'
|
||||
])
|
||||
|
||||
function fontPreviewStyle(font: string): React.CSSProperties | undefined {
|
||||
return font && !SYMBOL_FONT_FAMILIES.has(font.toLowerCase())
|
||||
? { fontFamily: buildAppFontFamily(font) }
|
||||
: undefined
|
||||
}
|
||||
|
||||
type FontAutocompleteProps = {
|
||||
value: string
|
||||
@@ -354,13 +375,16 @@ export function FontAutocomplete({
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onMouseEnter={() => setHighlightedIndex(sourceIndex)}
|
||||
onClick={() => commitValue(font)}
|
||||
className={`flex w-full items-center justify-between rounded-sm px-3 py-2 text-left text-sm transition-colors ${
|
||||
// Why content-visibility: each row loads its own font, so skip offscreen rows until scrolled to; 20px is the line height inside py-2.
|
||||
className={`flex w-full items-center justify-between rounded-sm px-3 py-2 text-left text-sm transition-colors [contain-intrinsic-size:auto_20px] [content-visibility:auto] ${
|
||||
sourceIndex === highlightedIndex
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'hover:bg-muted/60'
|
||||
}`}
|
||||
>
|
||||
<span className="truncate">{font || defaultOptionLabel}</span>
|
||||
<span className="truncate" style={fontPreviewStyle(font)}>
|
||||
{font || defaultOptionLabel}
|
||||
</span>
|
||||
{font === value ? <Check className="ml-3 size-4 shrink-0" /> : null}
|
||||
</button>
|
||||
))
|
||||
|
||||
@@ -148,6 +148,33 @@ describe('FontAutocomplete', () => {
|
||||
expectOptionsToBePortaled()
|
||||
})
|
||||
|
||||
it('renders each option name in its own typeface and leaves the input in the UI font', async () => {
|
||||
const fonts = ['Arial', 'Fira Code', 'Georgia']
|
||||
|
||||
function Harness(): ReactNode {
|
||||
const [value, setValue] = useState('Arial')
|
||||
return <FontAutocomplete value={value} suggestions={fonts} onChange={setValue} />
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
root.render(<Harness />)
|
||||
})
|
||||
await act(async () => {
|
||||
getInput().focus()
|
||||
})
|
||||
|
||||
const optionFamilies = Array.from(
|
||||
document.querySelectorAll<HTMLElement>('[role="option"]')
|
||||
).map((option) => option.querySelector<HTMLElement>('span')?.style.fontFamily ?? '')
|
||||
|
||||
const primaryFamilies = optionFamilies.map((family) =>
|
||||
family.split(',')[0]?.replaceAll('"', '')
|
||||
)
|
||||
|
||||
expect(primaryFamilies).toEqual(fonts)
|
||||
expect(getInput().style.fontFamily).toBe('')
|
||||
})
|
||||
|
||||
it('bounds the portaled list to the available popover height', async () => {
|
||||
function Harness(): ReactNode {
|
||||
const [value, setValue] = useState('Cascadia Mono')
|
||||
|
||||
Reference in New Issue
Block a user