mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-26 20:59:13 +01:00
feat: refactor data actions into new DataBar component
This commit is contained in:
@@ -2,27 +2,24 @@ import React, {useState, useEffect} from 'react';
|
||||
import {useNavigate} from 'react-router-dom';
|
||||
import FormatCard from './FormatCard';
|
||||
import FormatModal from './FormatModal';
|
||||
import AddButton from '@ui/AddButton';
|
||||
import {getGitStatus} from '@api/api';
|
||||
import {CustomFormats} from '@api/data';
|
||||
import FilterMenu from '@ui/FilterMenu';
|
||||
import SortMenu from '@ui/SortMenu';
|
||||
import {Loader, CheckSquare} from 'lucide-react';
|
||||
import {Loader} from 'lucide-react';
|
||||
import Alert from '@ui/Alert';
|
||||
import SearchBar from '@ui/SearchBar';
|
||||
import {useFormatModal} from '@hooks/useFormatModal';
|
||||
import {useMassSelection} from '@hooks/useMassSelection';
|
||||
import {useKeyboardShortcut} from '@hooks/useKeyboardShortcut';
|
||||
import MassActionsBar from '@ui/MassActionsBar';
|
||||
import ImportModal from '@ui/ImportModal';
|
||||
import {importFormats} from '@api/import';
|
||||
import DataBar from '@ui/DataBar/DataBar';
|
||||
|
||||
function FormatPage() {
|
||||
// Basic state management
|
||||
const [formats, setFormats] = useState([]);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [selectedFormat, setSelectedFormat] = useState(null);
|
||||
const [sortBy, setSortBy] = useState('title');
|
||||
const [sortBy, setSortBy] = useState('name');
|
||||
const [filterType, setFilterType] = useState('none');
|
||||
const [filterValue, setFilterValue] = useState('');
|
||||
const [allTags, setAllTags] = useState([]);
|
||||
@@ -213,7 +210,6 @@ function FormatPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// Rest of the sorting logic remains the same...
|
||||
return filtered.sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'dateModified':
|
||||
@@ -276,37 +272,21 @@ function FormatPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='mb-4 flex items-center gap-4'>
|
||||
<SearchBar
|
||||
onSearch={setSearchQuery}
|
||||
placeholder='Search by name or tag...'
|
||||
/>
|
||||
<div className='flex-none'>
|
||||
<SortMenu sortBy={sortBy} setSortBy={setSortBy} />
|
||||
</div>
|
||||
<div className='flex-none'>
|
||||
<FilterMenu
|
||||
filterType={filterType}
|
||||
setFilterType={setFilterType}
|
||||
filterValue={filterValue}
|
||||
setFilterValue={setFilterValue}
|
||||
allTags={allTags}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex-none'>
|
||||
<button
|
||||
onClick={toggleSelectionMode}
|
||||
className={`flex items-center gap-2 px-3 py-2 rounded transition-colors ${
|
||||
isSelectionMode
|
||||
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300'
|
||||
: 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
|
||||
}`}
|
||||
title='Toggle selection mode (Ctrl+A)'>
|
||||
<CheckSquare className='w-4 h-4' />
|
||||
<span className='text-sm'>Select</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<DataBar
|
||||
onSearch={setSearchQuery}
|
||||
searchPlaceholder='Search by name or tag...'
|
||||
filterType={filterType}
|
||||
setFilterType={setFilterType}
|
||||
filterValue={filterValue}
|
||||
setFilterValue={setFilterValue}
|
||||
allTags={allTags}
|
||||
sortBy={sortBy}
|
||||
setSortBy={setSortBy}
|
||||
isSelectionMode={isSelectionMode}
|
||||
toggleSelectionMode={toggleSelectionMode}
|
||||
onAdd={() => handleOpenModal()}
|
||||
addButtonLabel='Add New Format'
|
||||
/>
|
||||
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4'>
|
||||
{filteredFormats.map((format, index) => (
|
||||
@@ -330,15 +310,6 @@ function FormatPage() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!isSelectionMode && (
|
||||
<AddButton
|
||||
onClick={() => handleOpenModal()}
|
||||
label='Add New Format'
|
||||
top='5vh'
|
||||
left='75vw'
|
||||
/>
|
||||
)}
|
||||
|
||||
{isSelectionMode && (
|
||||
<MassActionsBar
|
||||
selectedCount={selectedItems.size}
|
||||
|
||||
@@ -2,26 +2,23 @@ import React, {useState, useEffect} from 'react';
|
||||
import {useNavigate} from 'react-router-dom';
|
||||
import ProfileCard from './ProfileCard';
|
||||
import ProfileModal from './ProfileModal';
|
||||
import AddButton from '@ui/AddButton';
|
||||
import {getGitStatus} from '@api/api';
|
||||
import {Profiles, CustomFormats} from '@api/data';
|
||||
import FilterMenu from '@ui/FilterMenu';
|
||||
import SortMenu from '@ui/SortMenu';
|
||||
import {Loader, CheckSquare} from 'lucide-react';
|
||||
import SearchBar from '@ui/SearchBar';
|
||||
import {Loader} from 'lucide-react';
|
||||
import Alert from '@ui/Alert';
|
||||
import {useMassSelection} from '@hooks/useMassSelection';
|
||||
import {useKeyboardShortcut} from '@hooks/useKeyboardShortcut';
|
||||
import MassActionsBar from '@ui/MassActionsBar';
|
||||
import ImportModal from '@ui/ImportModal';
|
||||
import {importProfiles} from '@api/import';
|
||||
import Alert from '@ui/Alert';
|
||||
import DataBar from '@ui/DataBar/DataBar';
|
||||
|
||||
function ProfilePage() {
|
||||
const [profiles, setProfiles] = useState([]);
|
||||
const [formats, setFormats] = useState([]);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [selectedProfile, setSelectedProfile] = useState(null);
|
||||
const [sortBy, setSortBy] = useState('title');
|
||||
const [sortBy, setSortBy] = useState('name');
|
||||
const [filterType, setFilterType] = useState('none');
|
||||
const [filterValue, setFilterValue] = useState('');
|
||||
const [allTags, setAllTags] = useState([]);
|
||||
@@ -107,7 +104,7 @@ function ProfilePage() {
|
||||
try {
|
||||
const response = await CustomFormats.getAll();
|
||||
const formatsData = response.map(item => ({
|
||||
id: item.content.name, // Use name as ID
|
||||
id: item.content.name,
|
||||
name: item.content.name,
|
||||
description: item.content.description || '',
|
||||
tags: item.content.tags || []
|
||||
@@ -175,7 +172,6 @@ function ProfilePage() {
|
||||
|
||||
const handleMassImport = async arr => {
|
||||
try {
|
||||
// Use the identifier to find the correct profile
|
||||
const selectedProfiles = Array.from(selectedItems).map(identifier =>
|
||||
profiles.find(p => p.content.name === identifier)
|
||||
);
|
||||
@@ -193,48 +189,61 @@ function ProfilePage() {
|
||||
}
|
||||
};
|
||||
|
||||
const getFilteredAndSortedProfiles = () => {
|
||||
return profiles
|
||||
.filter(profile => {
|
||||
// Apply search filter
|
||||
if (searchQuery) {
|
||||
return (
|
||||
profile.content.name
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
profile.content.tags?.some(tag =>
|
||||
tag
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Apply existing filters
|
||||
if (filterType === 'tag') {
|
||||
return (
|
||||
profile.content.tags &&
|
||||
profile.content.tags.includes(filterValue)
|
||||
);
|
||||
}
|
||||
if (filterType === 'date') {
|
||||
const profileDate = new Date(profile.modified_date);
|
||||
const filterDate = new Date(filterValue);
|
||||
return (
|
||||
profileDate.toDateString() === filterDate.toDateString()
|
||||
);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'dateModified':
|
||||
return (
|
||||
new Date(b.modified_date) -
|
||||
new Date(a.modified_date)
|
||||
);
|
||||
case 'dateCreated':
|
||||
return (
|
||||
new Date(b.created_date) - new Date(a.created_date)
|
||||
);
|
||||
case 'name':
|
||||
default:
|
||||
return a.content.name.localeCompare(b.content.name);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const formatDate = dateString => {
|
||||
return new Date(dateString).toLocaleString();
|
||||
};
|
||||
|
||||
const sortedAndFilteredProfiles = profiles
|
||||
.filter(profile => {
|
||||
// Apply search filter
|
||||
if (searchQuery) {
|
||||
return (
|
||||
profile.content.name
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase()) ||
|
||||
profile.content.tags?.some(tag =>
|
||||
tag.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Apply existing filters
|
||||
if (filterType === 'tag') {
|
||||
return (
|
||||
profile.content.tags &&
|
||||
profile.content.tags.includes(filterValue)
|
||||
);
|
||||
}
|
||||
if (filterType === 'date') {
|
||||
const profileDate = new Date(profile.modified_date);
|
||||
const filterDate = new Date(filterValue);
|
||||
return profileDate.toDateString() === filterDate.toDateString();
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (sortBy === 'name')
|
||||
return a.content.name.localeCompare(b.content.name);
|
||||
if (sortBy === 'dateCreated')
|
||||
return new Date(b.created_date) - new Date(a.created_date);
|
||||
if (sortBy === 'dateModified')
|
||||
return new Date(b.modified_date) - new Date(a.modified_date);
|
||||
return 0;
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className='flex flex-col items-center justify-center h-screen'>
|
||||
@@ -278,42 +287,28 @@ function ProfilePage() {
|
||||
);
|
||||
}
|
||||
|
||||
const filteredProfiles = getFilteredAndSortedProfiles();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='mb-4 flex items-center gap-4'>
|
||||
<SearchBar
|
||||
onSearch={setSearchQuery}
|
||||
placeholder='Search by name or tag...'
|
||||
/>
|
||||
<div className='flex-none'>
|
||||
<SortMenu sortBy={sortBy} setSortBy={setSortBy} />
|
||||
</div>
|
||||
<div className='flex-none'>
|
||||
<FilterMenu
|
||||
filterType={filterType}
|
||||
setFilterType={setFilterType}
|
||||
filterValue={filterValue}
|
||||
setFilterValue={setFilterValue}
|
||||
allTags={allTags}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex-none'>
|
||||
<button
|
||||
onClick={toggleSelectionMode}
|
||||
className={`flex items-center gap-2 px-3 py-2 rounded transition-colors ${
|
||||
isSelectionMode
|
||||
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300'
|
||||
: 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
|
||||
}`}
|
||||
title='Toggle selection mode (Ctrl+A)'>
|
||||
<CheckSquare className='w-4 h-4' />
|
||||
<span className='text-sm'>Select</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<DataBar
|
||||
onSearch={setSearchQuery}
|
||||
searchPlaceholder='Search by name or tag...'
|
||||
filterType={filterType}
|
||||
setFilterType={setFilterType}
|
||||
filterValue={filterValue}
|
||||
setFilterValue={setFilterValue}
|
||||
allTags={allTags}
|
||||
sortBy={sortBy}
|
||||
setSortBy={setSortBy}
|
||||
isSelectionMode={isSelectionMode}
|
||||
toggleSelectionMode={toggleSelectionMode}
|
||||
onAdd={() => handleOpenModal()}
|
||||
addButtonLabel='Add New Profile'
|
||||
/>
|
||||
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4'>
|
||||
{sortedAndFilteredProfiles.map((profile, index) => (
|
||||
{filteredProfiles.map((profile, index) => (
|
||||
<ProfileCard
|
||||
key={profile.file_name}
|
||||
profile={profile}
|
||||
@@ -328,22 +323,13 @@ function ProfilePage() {
|
||||
profile.content.name,
|
||||
index,
|
||||
e,
|
||||
sortedAndFilteredProfiles
|
||||
filteredProfiles
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!isSelectionMode && (
|
||||
<AddButton
|
||||
onClick={() => handleOpenModal()}
|
||||
label='Add New Profile'
|
||||
top='5vh'
|
||||
left='75vw'
|
||||
/>
|
||||
)}
|
||||
|
||||
{isSelectionMode && (
|
||||
<MassActionsBar
|
||||
selectedCount={selectedItems.size}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, {useState} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Search} from 'lucide-react';
|
||||
import {SortDropdown} from '../ui/SortDropdown';
|
||||
import {SortDropdown} from '../ui/DataBar/SortDropdown';
|
||||
import TabViewer from '../ui/TabViewer';
|
||||
|
||||
const ProfileScoringTab = ({
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import RegexCard from './RegexCard';
|
||||
import RegexModal from './RegexModal';
|
||||
import AddNewCard from '../ui/AddNewCard';
|
||||
import {RegexPatterns} from '@api/data';
|
||||
import FilterMenu from '../ui/FilterMenu';
|
||||
import SortMenu from '../ui/SortMenu';
|
||||
import {Loader, CheckSquare} from 'lucide-react';
|
||||
import {Loader} from 'lucide-react';
|
||||
import Alert from '@ui/Alert';
|
||||
import SearchBar from '@ui/SearchBar';
|
||||
import AddButton from '@ui/AddButton';
|
||||
import {useMassSelection} from '@hooks/useMassSelection';
|
||||
import {useKeyboardShortcut} from '@hooks/useKeyboardShortcut';
|
||||
import MassActionsBar from '@ui/MassActionsBar';
|
||||
import DataBar from '@ui/DataBar/DataBar';
|
||||
|
||||
function RegexPage() {
|
||||
const [patterns, setPatterns] = useState([]);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [selectedPattern, setSelectedPattern] = useState(null);
|
||||
const [sortBy, setSortBy] = useState('title');
|
||||
const [sortBy, setSortBy] = useState('name');
|
||||
const [filterType, setFilterType] = useState('none');
|
||||
const [filterValue, setFilterValue] = useState('');
|
||||
const [allTags, setAllTags] = useState([]);
|
||||
@@ -37,7 +33,6 @@ function RegexPage() {
|
||||
// Setup keyboard shortcut for selection mode (Ctrl+A)
|
||||
useKeyboardShortcut('a', toggleSelectionMode, {ctrl: true});
|
||||
|
||||
// Load initial data
|
||||
useEffect(() => {
|
||||
loadPatterns();
|
||||
}, []);
|
||||
@@ -143,7 +138,6 @@ function RegexPage() {
|
||||
);
|
||||
}
|
||||
|
||||
// Rest of the sorting logic remains the same...
|
||||
return filtered.sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'dateModified':
|
||||
@@ -173,37 +167,21 @@ function RegexPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='mb-4 flex items-center gap-4'>
|
||||
<SearchBar
|
||||
onSearch={setSearchQuery}
|
||||
placeholder='Search by name or tag...'
|
||||
/>
|
||||
<div className='flex-none'>
|
||||
<SortMenu sortBy={sortBy} setSortBy={setSortBy} />
|
||||
</div>
|
||||
<div className='flex-none'>
|
||||
<FilterMenu
|
||||
filterType={filterType}
|
||||
setFilterType={setFilterType}
|
||||
filterValue={filterValue}
|
||||
setFilterValue={setFilterValue}
|
||||
allTags={allTags}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex-none'>
|
||||
<button
|
||||
onClick={toggleSelectionMode}
|
||||
className={`flex items-center gap-2 px-3 py-2 rounded transition-colors ${
|
||||
isSelectionMode
|
||||
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300'
|
||||
: 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
|
||||
}`}
|
||||
title='Toggle selection mode (Ctrl+A)'>
|
||||
<CheckSquare className='w-4 h-4' />
|
||||
<span className='text-sm'>Select</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<DataBar
|
||||
onSearch={setSearchQuery}
|
||||
searchPlaceholder='Search by name or tag...'
|
||||
filterType={filterType}
|
||||
setFilterType={setFilterType}
|
||||
filterValue={filterValue}
|
||||
setFilterValue={setFilterValue}
|
||||
allTags={allTags}
|
||||
sortBy={sortBy}
|
||||
setSortBy={setSortBy}
|
||||
isSelectionMode={isSelectionMode}
|
||||
toggleSelectionMode={toggleSelectionMode}
|
||||
onAdd={() => handleOpenModal()}
|
||||
addButtonLabel='Add New Pattern'
|
||||
/>
|
||||
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-4'>
|
||||
{getFilteredAndSortedPatterns().map((pattern, index) => (
|
||||
@@ -221,14 +199,6 @@ function RegexPage() {
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{!isSelectionMode && (
|
||||
<AddButton
|
||||
onClick={() => handleOpenModal()}
|
||||
label='Add New Pattern'
|
||||
top='5vh'
|
||||
left='75vw'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isSelectionMode && (
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, {useState, useEffect} from 'react';
|
||||
import {Loader} from 'lucide-react';
|
||||
import ArrModal from './ArrModal';
|
||||
import ArrCard from './ArrCard';
|
||||
import AddButton from '@ui/AddButton';
|
||||
import AddButton from '@ui/DataBar/AddButton';
|
||||
import {getArrConfigs} from '@api/arr';
|
||||
|
||||
const ArrContainer = () => {
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
import {Plus} from 'lucide-react';
|
||||
|
||||
const AddButton = ({onClick, label = 'Add New', top, right, bottom, left}) => {
|
||||
const positionStyle = {
|
||||
position: 'fixed',
|
||||
zIndex: 50,
|
||||
...(top !== undefined && {
|
||||
top: typeof top === 'number' ? `${top}px` : top
|
||||
}),
|
||||
...(right !== undefined && {
|
||||
right: typeof right === 'number' ? `${right}px` : right
|
||||
}),
|
||||
...(bottom !== undefined && {
|
||||
bottom: typeof bottom === 'number' ? `${bottom}px` : bottom
|
||||
}),
|
||||
...(left !== undefined && {
|
||||
left: typeof left === 'number' ? `${left}px` : left
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={positionStyle}>
|
||||
<div
|
||||
onClick={onClick}
|
||||
className='add-button-container flex items-center bg-gradient-to-br from-gray-800 to-gray-900
|
||||
rounded-full border border-gray-700 shadow-xl hover:shadow-2xl
|
||||
hover:border-blue-500/50 transition-all duration-300 cursor-pointer overflow-hidden'>
|
||||
<div className='p-4 bg-blue-500/10 hover:bg-blue-500/20 transition-colors rounded-full'>
|
||||
<Plus className='w-6 h-6 text-blue-400' />
|
||||
</div>
|
||||
<div className='add-button-label-wrapper'>
|
||||
<span className='add-button-label font-medium text-gray-100 pl-5 pr-1 text-lg whitespace-nowrap'>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddButton;
|
||||
16
frontend/src/components/ui/DataBar/AddButton.jsx
Normal file
16
frontend/src/components/ui/DataBar/AddButton.jsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import {Plus} from 'lucide-react';
|
||||
|
||||
const AddButton = ({onClick, label = 'Add New'}) => {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className='flex items-center gap-2 px-3 py-2 rounded transition-colors bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
|
||||
title={label}>
|
||||
<Plus className='w-4 h-4' />
|
||||
<span className='text-sm'>{label}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddButton;
|
||||
102
frontend/src/components/ui/DataBar/DataBar.jsx
Normal file
102
frontend/src/components/ui/DataBar/DataBar.jsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import SearchBar from './SearchBar';
|
||||
import FilterMenu from './FilterMenu';
|
||||
import {SortDropdown} from './SortDropdown';
|
||||
import ToggleSelectButton from './ToggleSelectButton';
|
||||
import AddButton from './AddButton';
|
||||
|
||||
const DataBar = ({
|
||||
onSearch,
|
||||
searchPlaceholder = 'Search by name or tag...',
|
||||
filterType,
|
||||
setFilterType,
|
||||
filterValue,
|
||||
setFilterValue,
|
||||
allTags,
|
||||
sortBy,
|
||||
setSortBy,
|
||||
isSelectionMode,
|
||||
toggleSelectionMode,
|
||||
onAdd,
|
||||
addButtonLabel = 'Add New',
|
||||
showAddButton = true
|
||||
}) => {
|
||||
const [isFloating, setIsFloating] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(true);
|
||||
const [lastScrollY, setLastScrollY] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
const currentScrollY = window.scrollY;
|
||||
|
||||
// Show/hide based on scroll direction
|
||||
setIsVisible(currentScrollY <= 0 || currentScrollY < lastScrollY);
|
||||
|
||||
// Start floating once we scroll past the initial position
|
||||
setIsFloating(currentScrollY > 64); // Approximately the height of the bar
|
||||
|
||||
setLastScrollY(currentScrollY);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll, {passive: true});
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, [lastScrollY]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`transition-all duration-300 ${
|
||||
isFloating
|
||||
? 'fixed top-0 left-0 right-0 z-50 px-6 py-4 bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm shadow-md'
|
||||
: 'relative w-full mb-4'
|
||||
} ${isVisible ? 'translate-y-0' : '-translate-y-full'}`}>
|
||||
<div className='flex items-center gap-4'>
|
||||
<SearchBar
|
||||
onSearch={onSearch}
|
||||
placeholder={searchPlaceholder}
|
||||
className='flex-1'
|
||||
/>
|
||||
|
||||
<div className='flex-none'>
|
||||
<SortDropdown
|
||||
options={[
|
||||
{key: 'name', label: 'Sort by Name'},
|
||||
{key: 'dateCreated', label: 'Sort by Date Created'},
|
||||
{
|
||||
key: 'dateModified',
|
||||
label: 'Sort by Date Modified'
|
||||
}
|
||||
]}
|
||||
currentKey={sortBy}
|
||||
currentDirection='desc'
|
||||
onSort={key => setSortBy(key)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex-none'>
|
||||
<FilterMenu
|
||||
filterType={filterType}
|
||||
setFilterType={setFilterType}
|
||||
filterValue={filterValue}
|
||||
setFilterValue={setFilterValue}
|
||||
allTags={allTags}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex-none'>
|
||||
<ToggleSelectButton
|
||||
isSelectionMode={isSelectionMode}
|
||||
onClick={toggleSelectionMode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showAddButton && !isSelectionMode && (
|
||||
<div className='flex-none'>
|
||||
<AddButton onClick={onAdd} label={addButtonLabel} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataBar;
|
||||
24
frontend/src/components/ui/DataBar/ToggleSelectButton.jsx
Normal file
24
frontend/src/components/ui/DataBar/ToggleSelectButton.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import {CheckSquare} from 'lucide-react';
|
||||
|
||||
const ToggleSelectButton = ({
|
||||
isSelectionMode,
|
||||
onClick,
|
||||
shortcutKey = 'A' // Default to 'A' since that's what's used in the pages
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`flex items-center gap-2 px-3 py-2 rounded transition-colors ${
|
||||
isSelectionMode
|
||||
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300'
|
||||
: 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
|
||||
}`}
|
||||
title={`Toggle selection mode (Ctrl+${shortcutKey})`}>
|
||||
<CheckSquare className='w-4 h-4' />
|
||||
<span className='text-sm'>Select</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToggleSelectButton;
|
||||
Reference in New Issue
Block a user