Merge pull request #74610 from NousResearch/bb/checkbox-indeterminate

fix(desktop): stop the checkbox painting the check and dash at once
This commit is contained in:
brooklyn! 2026-07-30 01:06:49 -05:00 committed by GitHub
commit 382282d5a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 66 additions and 2 deletions

View file

@ -0,0 +1,62 @@
import { cleanup, render } from '@testing-library/react'
import { afterEach, beforeAll, describe, expect, it } from 'vitest'
import { Checkbox } from './checkbox'
/**
* The indicator stacks both glyphs and hides one with a utility class, so the
* cascade not the markup decides what the user sees. codicon.css sets
* `display: inline-block` on `.codicon[class*='codicon-']`, which outranks a
* single-class utility and paints the check and the dash at the same time.
* That specificity race is the real contract, so the test carries both
* stylesheets rather than asserting on class strings. Tailwind's nested output
* is flattened to the equivalent descendant selector because jsdom does not
* implement CSS nesting.
*/
const CODICON_CSS = `.codicon[class*='codicon-'] { display: inline-block; }`
const TAILWIND_CSS = `
.hidden\\! { display: none !important; }
:where(.group)[data-state="checked"] .group-data-\\[state\\=checked\\]\\:block\\! {
display: block !important;
}
:where(.group)[data-state="indeterminate"] .group-data-\\[state\\=indeterminate\\]\\:block\\! {
display: block !important;
}
`
function shownGlyphs(container: HTMLElement) {
return [...container.querySelectorAll<HTMLElement>('.codicon')]
.filter(glyph => getComputedStyle(glyph).display !== 'none')
.map(glyph => (glyph.classList.contains('codicon-check') ? 'check' : 'dash'))
}
beforeAll(() => {
// eslint-disable-next-line no-restricted-globals -- the cascade is the assertion; it needs a real stylesheet
const style = document.createElement('style')
style.textContent = `${CODICON_CSS}\n${TAILWIND_CSS}`
// eslint-disable-next-line no-restricted-globals -- see above
document.head.append(style)
})
afterEach(cleanup)
describe('Checkbox', () => {
it('paints the check alone when checked', () => {
const { container } = render(<Checkbox checked />)
expect(shownGlyphs(container)).toEqual(['check'])
})
it('paints the dash alone when indeterminate', () => {
const { container } = render(<Checkbox checked="indeterminate" />)
expect(shownGlyphs(container)).toEqual(['dash'])
})
it('paints no glyph when unchecked', () => {
const { container } = render(<Checkbox checked={false} />)
expect(shownGlyphs(container)).toEqual([])
})
})

View file

@ -18,8 +18,10 @@ function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxP
className="flex items-center justify-center text-current"
data-slot="checkbox-indicator"
>
<Codicon className="hidden group-data-[state=checked]:block" name="check" size="0.875rem" />
<Codicon className="hidden group-data-[state=indeterminate]:block" name="dash" size="0.875rem" />
{/* codicon.css sets `display: inline-block` at higher specificity than a bare
`hidden`, so both glyphs paint at once without the important modifier. */}
<Codicon className="hidden! group-data-[state=checked]:block!" name="check" size="0.875rem" />
<Codicon className="hidden! group-data-[state=indeterminate]:block!" name="dash" size="0.875rem" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)