Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions src/components/ActionBar/ActionBar.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { useSelection } from '../../context/SelectionContext';
import { useScriptGenerator } from '../../hooks/useScriptGenerator';
import { useSearchContext } from '../../context/SearchContext';
import { useState } from 'react';
import WinDialog from '../Common/WinDialog';
import { FaSearch } from '../Common/icons';

const ActionBar = () => {
const { selectedSoftware, selectedConfigs, clearAll } = useSelection();
const { downloadScript } = useScriptGenerator();
const { searchTerm, setSearchTerm } = useSearchContext();
const [showClearDialog, setShowClearDialog] = useState(false);
const [showDownloadDialog, setShowDownloadDialog] = useState(false);

Expand All @@ -30,7 +33,7 @@ const ActionBar = () => {
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '12px' }}>
{/* Left: Selection count */}
<div style={{ fontSize: '11px' }}>
<div style={{ fontSize: '11px', flexShrink: 0 }}>
<span style={{ fontWeight: 'bold' }}>{totalSelected}</span>
<span> items selected </span>
<span style={{ color: 'var(--win98-gray-medium)' }}>• </span>
Expand All @@ -39,8 +42,37 @@ const ActionBar = () => {
</span>
</div>

{/* Center: Search Bar */}
<div style={{ flex: 1, display: 'flex', justifyContent: 'center', minWidth: '200px' }}>
<div className="win98-inset" style={{
display: 'flex',
alignItems: 'center',
backgroundColor: 'var(--win95-white)',
padding: '2px 4px',
width: '100%',
maxWidth: '400px'
}}>
<FaSearch style={{ color: 'var(--win95-black)', marginRight: '6px', fontSize: '12px' }} />
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search catalog..."
style={{
border: 'none',
outline: 'none',
fontSize: '12px',
fontFamily: 'inherit',
width: '100%',
backgroundColor: 'transparent',
color: 'var(--win95-black)'
}}
/>
</div>
</div>

{/* Right: Action buttons */}
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap', flexShrink: 0 }}>
<button
onClick={handleDownload}
disabled={totalSelected === 0}
Expand Down
10 changes: 9 additions & 1 deletion src/components/ConfigurationSelector/ConfigSection.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { useState } from 'react';
import ConfigOption from './ConfigOption';

const ConfigSection = ({ category, configs }) => {
const ConfigSection = ({ category, configs, isSearching }) => {
const [expanded, setExpanded] = useState(true);
const [prevIsSearching, setPrevIsSearching] = useState(isSearching);

if (isSearching !== prevIsSearching) {
setPrevIsSearching(isSearching);
if (isSearching) {
setExpanded(true);
}
}

return (
<section style={{ marginBottom: '12px' }}>
Expand Down
18 changes: 16 additions & 2 deletions src/components/ConfigurationSelector/ConfigurationSelector.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { configCategories } from '../../data/categories';
import { getConfigsByCategory } from '../../data/configurations';
import ConfigSection from './ConfigSection';
import { useSearchContext } from '../../context/SearchContext';

const ConfigurationSelector = () => {
const { searchTerm } = useSearchContext();

return (
<div style={{ marginBottom: '16px' }}>
<div className="win98-inset" style={{ padding: '12px', marginBottom: '12px' }}>
Expand All @@ -16,13 +19,24 @@ const ConfigurationSelector = () => {

{configCategories.map((category) => {
const categoryConfigs = getConfigsByCategory(category.id);
if (categoryConfigs.length === 0) return null;

const filteredConfigs = categoryConfigs.filter((config) => {
if (!searchTerm) return true;
const searchLower = searchTerm.toLowerCase();
return (
config.name.toLowerCase().includes(searchLower) ||
(config.description && config.description.toLowerCase().includes(searchLower))
);
});

if (filteredConfigs.length === 0) return null;

return (
<ConfigSection
key={category.id}
category={category}
configs={categoryConfigs}
configs={filteredConfigs}
isSearching={!!searchTerm}
/>
);
})}
Expand Down
11 changes: 10 additions & 1 deletion src/components/SoftwareSelector/CategorySection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@ import { useState } from 'react';
import SoftwareCard from './SoftwareCard';
import { useSelection } from '../../context/SelectionContext';

const CategorySection = ({ category, software }) => {
const CategorySection = ({ category, software, isSearching }) => {
const [expanded, setExpanded] = useState(true);
const [prevIsSearching, setPrevIsSearching] = useState(isSearching);
const { isAllCategorySelected, selectAllInCategory, deselectAllInCategory } = useSelection();

// Derived state: Automatically expand when search becomes active
if (isSearching !== prevIsSearching) {
setPrevIsSearching(isSearching);
if (isSearching) {
setExpanded(true);
}
}

const allSelected = isAllCategorySelected(category.id);

const handleSelectAll = (e) => {
Expand Down
17 changes: 15 additions & 2 deletions src/components/SoftwareSelector/SoftwareSelector.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { categories } from '../../data/categories';
import { getSoftwareByCategory } from '../../data/software-catalog';
import CategorySection from './CategorySection';
import { useSearchContext } from '../../context/SearchContext';

const SoftwareSelector = () => {
// Sort categories by order
const sortedCategories = [...categories].sort((a, b) => a.order - b.order);
const { searchTerm } = useSearchContext();

return (
<div style={{ marginBottom: '16px' }}>
Expand All @@ -19,13 +21,24 @@ const SoftwareSelector = () => {

{sortedCategories.map((category) => {
const categorySoftware = getSoftwareByCategory(category.id);
if (categorySoftware.length === 0) return null;

const filteredSoftware = categorySoftware.filter((sw) => {
if (!searchTerm) return true;
const searchLower = searchTerm.toLowerCase();
return (
sw.name.toLowerCase().includes(searchLower) ||
(sw.description && sw.description.toLowerCase().includes(searchLower))
);
});

if (filteredSoftware.length === 0) return null;

return (
<CategorySection
key={category.id}
category={category}
software={categorySoftware}
software={filteredSoftware}
isSearching={!!searchTerm}
/>
);
})}
Expand Down
22 changes: 22 additions & 0 deletions src/context/SearchContext.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createContext, useContext, useState } from 'react';

const SearchContext = createContext();

export function SearchProvider({ children }) {
const [searchTerm, setSearchTerm] = useState('');

return (
<SearchContext.Provider value={{ searchTerm, setSearchTerm }}>
{children}
</SearchContext.Provider>
);
}

// eslint-disable-next-line react-refresh/only-export-components
export function useSearchContext() {
const context = useContext(SearchContext);
if (!context) {
throw new Error('useSearchContext must be used within a SearchProvider');
}
return context;
}
5 changes: 4 additions & 1 deletion src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import './index.css'
import App from './App.jsx'
import { SelectionProvider } from './context/SelectionContext.jsx'
import { ThemeProvider } from './context/ThemeContext.jsx'
import { SearchProvider } from './context/SearchContext.jsx'

createRoot(document.getElementById('root')).render(
<StrictMode>
<ThemeProvider>
<SelectionProvider>
<App />
<SearchProvider>
<App />
</SearchProvider>
</SelectionProvider>
</ThemeProvider>
</StrictMode>,
Expand Down