Skip to content

Commit 91f44c1

Browse files
committed
updating to checkboxes from TODO
1 parent 781b407 commit 91f44c1

File tree

2 files changed

+67
-59
lines changed

2 files changed

+67
-59
lines changed

app/components/Compare/FacetSelector.vue

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<script setup lang="ts">
2+
import type { FacetInfoWithLabels } from '~/composables/useFacetSelection'
3+
24
const {
5+
selectedFacets,
36
isFacetSelected,
47
toggleFacet,
58
selectCategory,
@@ -9,6 +12,17 @@ const {
912
getCategoryLabel,
1013
} = useFacetSelection()
1114
15+
/** Native checkbox disabled when coming soon or this is the only selected facet (must keep ≥1). */
16+
function isFacetCheckboxDisabled(facet: FacetInfoWithLabels): boolean {
17+
if (facet.comingSoon) return true
18+
return selectedFacets.value.length === 1 && isFacetSelected(facet.id)
19+
}
20+
21+
function onFacetChange(facet: FacetInfoWithLabels) {
22+
if(isFacetCheckboxDisabled(facet)) return
23+
toggleFacet(facet.id)
24+
}
25+
1226
// Check if all non-comingSoon facets in a category are selected
1327
function isCategoryAllSelected(category: string): boolean {
1428
const facets = facetsByCategory.value[category] ?? []
@@ -58,39 +72,39 @@ function isCategoryNoneSelected(category: string): boolean {
5872
</ButtonBase>
5973
</div>
6074

61-
<!-- Facet buttons -->
62-
<div class="flex items-center gap-1.5 flex-wrap" role="group">
63-
<!-- TODO: These should be checkboxes -->
64-
<ButtonBase
75+
<div
76+
class="flex items-center gap-1.5 flex-wrap"
77+
role="group"
78+
:aria-label="getCategoryLabel(category)"
79+
>
80+
<label
6581
v-for="facet in facetsByCategory[category]"
6682
:key="facet.id"
67-
size="sm"
68-
:title="facet.comingSoon ? $t('compare.facets.coming_soon') : facet.description"
69-
:disabled="facet.comingSoon"
70-
:aria-pressed="isFacetSelected(facet.id)"
71-
:aria-label="facet.label"
72-
class="gap-1 px-1.5 rounded transition-colors focus-visible:outline-accent/70"
83+
class="flex items-center gap-1.5 px-1.5 py-0.5 rounded border text-xs transition-colors focus-within:outline focus-within:outline-2 focus-within:outline-offset-1 focus-within:outline-accent/70"
7384
:class="
7485
facet.comingSoon
7586
? 'text-fg-subtle/50 bg-bg-subtle border-border-subtle cursor-not-allowed'
76-
: isFacetSelected(facet.id)
77-
? 'text-fg-muted bg-bg-muted'
78-
: 'text-fg-subtle bg-bg-subtle border-border-subtle hover:text-fg-muted hover:border-border'
79-
"
80-
@click="!facet.comingSoon && toggleFacet(facet.id)"
81-
:classicon="
82-
facet.comingSoon
83-
? undefined
84-
: isFacetSelected(facet.id)
85-
? 'i-lucide:check'
86-
: 'i-lucide:plus'
87+
: isFacetCheckboxDisabled(facet)
88+
? 'text-fg-muted bg-bg-muted border-border opacity-90 cursor-not-allowed'
89+
: isFacetSelected(facet.id)
90+
? 'text-fg-muted bg-bg-muted border-border cursor-pointer'
91+
: 'text-fg-subtle bg-bg-subtle border-border-subtle hover:text-fg-muted hover:border-border cursor-pointer'
8792
"
8893
>
89-
{{ facet.label }}
94+
<input
95+
type="checkbox"
96+
:data-facet-id="facet.id"
97+
class="size-3.5 shrink-0 accent-accent rounded border-border disabled:opacity-60"
98+
:checked="isFacetSelected(facet.id)"
99+
:disabled="isFacetCheckboxDisabled(facet)"
100+
:title="facet.comingSoon ? $t('compare.facets.coming_soon') : facet.description"
101+
@change="onFacetChange(facet)"
102+
/>
103+
<span>{{ facet.label }}</span>
90104
<span v-if="facet.comingSoon" class="text-4xs"
91105
>({{ $t('compare.facets.coming_soon') }})</span
92106
>
93-
</ButtonBase>
107+
</label>
94108
</div>
95109
</div>
96110
</div>

test/nuxt/components/compare/FacetSelector.spec.ts

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ describe('FacetSelector', () => {
136136
})
137137
})
138138

139-
describe('facet buttons', () => {
139+
describe('facet checkboxes', () => {
140140
it('renders all facets from FACET_INFO', async () => {
141141
const component = await mountSuspended(FacetSelector)
142142

@@ -146,55 +146,61 @@ describe('FacetSelector', () => {
146146
}
147147
})
148148

149-
it('shows checkmark icon for selected facets', async () => {
149+
it('renders a checkbox for each facet', async () => {
150+
const component = await mountSuspended(FacetSelector)
151+
152+
const checkboxes = component.findAll('input[type="checkbox"]')
153+
expect(checkboxes.length).toBe(Object.keys(FACET_INFO).length)
154+
})
155+
156+
it('checks selected facets', async () => {
150157
mockSelectedFacets.value = ['downloads']
151158
mockIsFacetSelected.mockImplementation((f: string) => f === 'downloads')
152159

153160
const component = await mountSuspended(FacetSelector)
154161

155-
expect(component.find('.i-lucide\\:check').exists()).toBe(true)
162+
const downloads = component.find('input[type="checkbox"][data-facet-id="downloads"]')
163+
expect((downloads.element as HTMLInputElement).checked).toBe(true)
156164
})
157165

158-
it('shows add icon for unselected facets', async () => {
166+
it('unchecks unselected facets', async () => {
159167
mockSelectedFacets.value = ['downloads']
160168
mockIsFacetSelected.mockImplementation((f: string) => f === 'downloads')
161169

162170
const component = await mountSuspended(FacetSelector)
163171

164-
expect(component.find('.i-lucide\\:plus').exists()).toBe(true)
172+
const types = component.find('input[type="checkbox"][data-facet-id="types"]')
173+
expect((types.element as HTMLInputElement).checked).toBe(false)
165174
})
166175

167-
it('applies aria-pressed for selected state', async () => {
176+
it('disables the checkbox when it is the only selected facet', async () => {
168177
mockSelectedFacets.value = ['downloads']
169178
mockIsFacetSelected.mockImplementation((f: string) => f === 'downloads')
170179

171180
const component = await mountSuspended(FacetSelector)
172181

173-
const buttons = component.findAll('button[aria-pressed]')
174-
const selectedButton = buttons.find(b => b.attributes('aria-pressed') === 'true')
175-
expect(selectedButton).toBeDefined()
182+
const downloads = component.find('input[type="checkbox"][data-facet-id="downloads"]')
183+
expect(downloads.attributes('disabled')).toBeDefined()
176184
})
177185

178-
it('calls toggleFacet when facet button is clicked', async () => {
186+
it('calls toggleFacet when a facet checkbox is changed', async () => {
179187
const component = await mountSuspended(FacetSelector)
180188

181-
// Find a facet button (not all/none)
182-
const facetButton = component.findAll('button').find(b => b.text().includes('Downloads'))
183-
await facetButton?.trigger('click')
189+
const typesCheckbox = component.find('input[type="checkbox"][data-facet-id="types"]')
190+
await typesCheckbox.trigger('change')
184191

185-
expect(mockToggleFacet).toHaveBeenCalled()
192+
expect(mockToggleFacet).toHaveBeenCalledWith('types')
186193
})
187194
})
188195

189196
describe.runIf(hasComingSoonFacets)('comingSoon facets', () => {
190197
it('disables comingSoon facets', async () => {
191198
const component = await mountSuspended(FacetSelector)
192199

193-
// totalDependencies is marked as comingSoon
194-
const buttons = component.findAll('button')
195-
const comingSoonButton = buttons.find(b => b.text().includes(comingSoonFacetLabel))
196-
197-
expect(comingSoonButton?.attributes('disabled')).toBeDefined()
200+
const comingSoonInput = component.find(
201+
`input[type="checkbox"][data-facet-id="${comingSoonFacetId}"]`,
202+
)
203+
expect(comingSoonInput.attributes('disabled')).toBeDefined()
198204
})
199205

200206
it('shows coming soon text for comingSoon facets', async () => {
@@ -203,26 +209,14 @@ describe('FacetSelector', () => {
203209
expect(component.text().toLowerCase()).toContain('coming soon')
204210
})
205211

206-
it('does not show checkmark/add icon for comingSoon facets', async () => {
212+
it('does not call toggleFacet when comingSoon checkbox change is triggered', async () => {
207213
const component = await mountSuspended(FacetSelector)
208214

209-
// Find the comingSoon button
210-
const buttons = component.findAll('button')
211-
const comingSoonButton = buttons.find(b => b.text().includes(comingSoonFacetLabel))
212-
213-
// Should not have checkmark or add icon
214-
expect(comingSoonButton?.find('.i-lucide\\:check').exists()).toBe(false)
215-
expect(comingSoonButton?.find('.i-lucide\\:plus').exists()).toBe(false)
216-
})
217-
218-
it('does not call toggleFacet when comingSoon facet is clicked', async () => {
219-
const component = await mountSuspended(FacetSelector)
220-
221-
const buttons = component.findAll('button')
222-
const comingSoonButton = buttons.find(b => b.text().includes(comingSoonFacetLabel))
223-
await comingSoonButton?.trigger('click')
215+
const comingSoonInput = component.find(
216+
`input[type="checkbox"][data-facet-id="${comingSoonFacetId}"]`,
217+
)
218+
await comingSoonInput.trigger('change')
224219

225-
// toggleFacet should not have been called with totalDependencies
226220
expect(mockToggleFacet).not.toHaveBeenCalledWith(comingSoonFacetId)
227221
})
228222
})

0 commit comments

Comments
 (0)