fix(web): give the protocol dropdowns the shared chevron
`select.input` caps the control at 240px, and a `<select>` carrying only `.input` keeps the OS arrow, which paints flush inside that shrunk right edge — visibly tighter than every other control on the page. Both protocol dropdowns took `.input` alone; the provider select beside them already added `.selectInput`, which removes the OS arrow, reserves the right pad, and paints the shared 12px chevron. The stylesheet spec now reads the package's own sources and fails any `<select>` that takes `.input` without `.selectInput`, so the next one cannot be added bare.
This commit is contained in:
@@ -236,7 +236,7 @@ export function CustomProviderCard(props: CustomProviderCardProps): ReactNode {
|
||||
<div className={styles['field']}>
|
||||
<span className={styles['fieldLabel']}>{t('customApi')}</span>
|
||||
<select
|
||||
className={styles['input']}
|
||||
className={`${styles['input']} ${styles['selectInput']}`}
|
||||
value={protocol}
|
||||
aria-label={t('customApi')}
|
||||
disabled={profileDisabled}
|
||||
|
||||
@@ -396,7 +396,7 @@ export function ProviderEditor(props: ProviderEditorProps): ReactNode {
|
||||
<div className={styles['field']}>
|
||||
<span className={styles['fieldLabel']}>{t('customApi')}</span>
|
||||
<select
|
||||
className={styles['input']}
|
||||
className={`${styles['input']} ${styles['selectInput']}`}
|
||||
value={probeApi ?? ''}
|
||||
aria-label={t('customApi')}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -61,6 +61,29 @@ describe('ModelsSection theme styles', () => {
|
||||
expect(block('.rowCard')).not.toMatch(/\bbackground\s*:/)
|
||||
})
|
||||
|
||||
it('gives every dropdown the shared chevron instead of the OS arrow', () => {
|
||||
// `select.input` caps the control at 240px, and the OS arrow is painted
|
||||
// flush inside that shrunk right edge — visibly tighter than every other
|
||||
// control on the page. `.selectInput` is what removes it, reserves the
|
||||
// right pad, and paints the shared chevron; a `<select>` that takes
|
||||
// `.input` alone silently keeps the OS one.
|
||||
const sources = readdirSync(fileURLToPath(new URL('../src/client/', import.meta.url)))
|
||||
.filter(name => name.endsWith('.tsx'))
|
||||
.map(name => ({
|
||||
name,
|
||||
text: readFileSync(fileURLToPath(new URL(`../src/client/${name}`, import.meta.url)), 'utf8'),
|
||||
}))
|
||||
const bare = sources.flatMap(({ name, text }) => text
|
||||
.split('<select')
|
||||
.slice(1)
|
||||
// The element's own attributes end at the first `>`; a child `<option>`
|
||||
// carries no className of its own and must not answer for the select.
|
||||
.map(rest => rest.slice(0, rest.indexOf('>')))
|
||||
.filter(attributes => !attributes.includes('selectInput'))
|
||||
.map(() => name))
|
||||
expect(bare).toEqual([])
|
||||
})
|
||||
|
||||
it('never falls back to a literal colour', () => {
|
||||
// A token that resolves is never the problem; an undeclared one takes this
|
||||
// branch, and a literal here is a single colour for both themes.
|
||||
|
||||
Reference in New Issue
Block a user