Skip to content

Commit f05bdb7

Browse files
authored
feat(ui): add select all button to configuration sections (#32)
* feat(ui): add select all button to category headers * feat(core): add SearchContext for global state management * feat(ui): implement search bar in ActionBar with Win98 styling * feat(logic): enable filtering and auto-expansion in catalogs * fix(lint): resolve react-hooks and refresh lint errors * feat(ui): add select all button to configuration sections
1 parent cfa79dd commit f05bdb7

2 files changed

Lines changed: 80 additions & 9 deletions

File tree

src/components/ConfigurationSelector/ConfigSection.jsx

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { useState } from 'react';
22
import ConfigOption from './ConfigOption';
3+
import { useSelection } from '../../context/SelectionContext';
34

45
const ConfigSection = ({ category, configs, isSearching }) => {
56
const [expanded, setExpanded] = useState(true);
67
const [prevIsSearching, setPrevIsSearching] = useState(isSearching);
8+
const { isAllConfigCategorySelected, selectAllConfigsInCategory, deselectAllConfigsInCategory } = useSelection();
79

810
if (isSearching !== prevIsSearching) {
911
setPrevIsSearching(isSearching);
@@ -12,35 +14,75 @@ const ConfigSection = ({ category, configs, isSearching }) => {
1214
}
1315
}
1416

17+
const allSelected = isAllConfigCategorySelected(category.id);
18+
19+
const handleSelectAll = (e) => {
20+
e.stopPropagation();
21+
if (allSelected) {
22+
deselectAllConfigsInCategory(category.id);
23+
} else {
24+
selectAllConfigsInCategory(category.id);
25+
}
26+
};
27+
1528
return (
1629
<section style={{ marginBottom: '12px' }}>
17-
<div className="win98-raised" style={{ padding: '8px', marginBottom: '8px' }}>
30+
<div className="win98-raised" style={{ padding: '6px', marginBottom: '8px', display: 'flex', gap: '6px', alignItems: 'stretch' }}>
1831
<button
1932
onClick={() => setExpanded(!expanded)}
2033
className="win98-button"
2134
style={{
22-
width: '100%',
23-
minHeight: '50px',
35+
flex: 1,
36+
minHeight: '52px',
2437
display: 'flex',
2538
alignItems: 'center',
26-
gap: '8px',
39+
gap: '10px',
2740
justifyContent: 'flex-start',
28-
padding: '8px 12px'
41+
padding: '8px 12px',
42+
textAlign: 'left'
2943
}}
3044
aria-expanded={expanded}
3145
aria-label={`${expanded ? 'Collapse' : 'Expand'} ${category.name} configuration section`}
3246
>
3347
<span style={{ fontSize: '20px', flexShrink: 0 }}>{category.icon}</span>
34-
<div style={{ textAlign: 'left', flex: 1, overflow: 'hidden' }}>
35-
<div style={{ fontSize: '13px', fontWeight: 'bold', marginBottom: '3px', lineHeight: '1.2' }}>
48+
<div style={{ flex: 1, overflow: 'hidden' }}>
49+
<div style={{ fontSize: '13px', fontWeight: 'bold', marginBottom: '2px', lineHeight: '1.2' }}>
3650
{category.name}
51+
<span style={{ fontSize: '10px', marginLeft: '8px', verticalAlign: 'middle', fontWeight: 'normal', color: 'var(--win98-gray-dark)' }}>
52+
{expanded ? '▼' : '▶'}
53+
</span>
3754
</div>
3855
<div className="category-description" style={{ fontSize: '11px', fontWeight: 'normal', color: 'var(--win95-black)', lineHeight: '1.3' }}>
3956
{category.description}
4057
</div>
4158
</div>
42-
<span style={{ fontSize: '10px', marginLeft: 'auto', flexShrink: 0 }}>
43-
{expanded ? '▼' : '▶'}
59+
</button>
60+
61+
<button
62+
onClick={handleSelectAll}
63+
className={`win98-button ${allSelected ? 'win98-button-danger' : ''}`}
64+
style={{
65+
minWidth: '100px',
66+
minHeight: '52px',
67+
display: 'flex',
68+
flexDirection: 'column',
69+
alignItems: 'center',
70+
justifyContent: 'center',
71+
gap: '4px',
72+
padding: '4px 12px',
73+
flexShrink: 0
74+
}}
75+
title={allSelected ? "Deselect all items in this category" : "Select all items in this category"}
76+
>
77+
<input
78+
type="checkbox"
79+
checked={allSelected}
80+
readOnly
81+
className="win98-checkbox"
82+
style={{ pointerEvents: 'none', marginBottom: '2px' }}
83+
/>
84+
<span style={{ fontSize: '10px', fontWeight: 'bold', textTransform: 'uppercase', letterSpacing: '0.5px' }}>
85+
{allSelected ? 'Deselect All' : 'Select All'}
4486
</span>
4587
</button>
4688
</div>

src/context/SelectionContext.jsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react';
22
import { softwareCatalog } from '../data/software-catalog';
3+
import { configurations } from '../data/configurations';
34
import { getCategoryItemIds } from '../utils/catalogHelpers';
45
import { STORAGE_KEYS } from '../constants';
56

@@ -73,9 +74,31 @@ export function SelectionProvider({ children }) {
7374
// Check if all items in category are selected
7475
const isAllCategorySelected = useCallback((categoryId) => {
7576
const categoryItems = getCategoryItemIds(softwareCatalog, categoryId);
77+
if (categoryItems.length === 0) return false;
7678
return categoryItems.every((id) => selectedSoftware.includes(id));
7779
}, [selectedSoftware]);
7880

81+
// Select all configurations in a category
82+
const selectAllConfigsInCategory = useCallback((categoryId) => {
83+
const categoryItems = getCategoryItemIds(configurations, categoryId);
84+
setSelectedConfigs((prev) => [...new Set([...prev, ...categoryItems])]);
85+
}, []);
86+
87+
// Deselect all configurations in a category
88+
const deselectAllConfigsInCategory = useCallback((categoryId) => {
89+
const categoryItems = getCategoryItemIds(configurations, categoryId);
90+
setSelectedConfigs((prev) =>
91+
prev.filter((id) => !categoryItems.includes(id))
92+
);
93+
}, []);
94+
95+
// Check if all configurations in category are selected
96+
const isAllConfigCategorySelected = useCallback((categoryId) => {
97+
const categoryItems = getCategoryItemIds(configurations, categoryId);
98+
if (categoryItems.length === 0) return false;
99+
return categoryItems.every((id) => selectedConfigs.includes(id));
100+
}, [selectedConfigs]);
101+
79102
// Clear all selections
80103
const clearAll = useCallback(() => {
81104
setSelectedSoftware([]);
@@ -100,6 +123,9 @@ export function SelectionProvider({ children }) {
100123
selectAllInCategory,
101124
deselectAllInCategory,
102125
isAllCategorySelected,
126+
selectAllConfigsInCategory,
127+
deselectAllConfigsInCategory,
128+
isAllConfigCategorySelected,
103129
clearAll,
104130
clearSoftware,
105131
clearConfigs,
@@ -111,6 +137,9 @@ export function SelectionProvider({ children }) {
111137
selectAllInCategory,
112138
deselectAllInCategory,
113139
isAllCategorySelected,
140+
selectAllConfigsInCategory,
141+
deselectAllConfigsInCategory,
142+
isAllConfigCategorySelected,
114143
clearAll,
115144
clearSoftware,
116145
clearConfigs,

0 commit comments

Comments
 (0)