-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui): add slider to sync multiples layer at a time
- Loading branch information
1 parent
49b6d3b
commit db1772b
Showing
7 changed files
with
370 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import React, { useState, useEffect, useRef } from 'react'; | ||
import Checkbox from '@/components/core/Checkbox'; | ||
import Button from '@/components/core/Button'; | ||
import Tag from '@/components/widgets/Tag'; | ||
import { Layer } from '@/clients/layers/types'; | ||
|
||
// Define the props for the LayerChecklist component | ||
interface LayerChecklistProps { | ||
layers: Layer[]; | ||
variant?: 'light' | 'dark'; // Optional: To pass variant to Checkbox | ||
onSelectionChange?: (selectedLayers: { name: string; namespace: string }[]) => void; // Updated callback prop | ||
} | ||
|
||
const LayerChecklist: React.FC<LayerChecklistProps> = ({ | ||
layers, | ||
variant = 'light', | ||
onSelectionChange, | ||
}) => { | ||
// State to keep track of selected layers using unique keys | ||
const [selectedLayers, setSelectedLayers] = useState<{ name: string; namespace: string }[]>([]); | ||
const selectAllRef = useRef<HTMLInputElement>(null); | ||
|
||
// Function to generate a unique key for each layer | ||
const getLayerKey = (layer: Layer): string => `${layer.namespace}-${layer.name}`; | ||
|
||
// Update the indeterminate state based on selection | ||
useEffect(() => { | ||
if (selectAllRef.current) { | ||
const isIndeterminate = | ||
selectedLayers.length > 0 && selectedLayers.length < layers.length; | ||
selectAllRef.current.indeterminate = isIndeterminate; | ||
} | ||
}, [selectedLayers, layers.length]); | ||
|
||
// Handler for individual layer checkbox toggle | ||
const handleToggle = (layer: Layer) => { | ||
setSelectedLayers((prevSelected) => | ||
prevSelected.some((selectedLayer) => selectedLayer.name === layer.name && selectedLayer.namespace === layer.namespace) | ||
? prevSelected.filter((selectedLayer) => selectedLayer.name !== layer.name || selectedLayer.namespace !== layer.namespace) | ||
: [...prevSelected, { name: layer.name, namespace: layer.namespace }] | ||
); | ||
}; | ||
|
||
// Handler to select all layers | ||
const handleSelectAll = () => { | ||
setSelectedLayers(layers.map(layer => ({ name: layer.name, namespace: layer.namespace }))); | ||
}; | ||
|
||
// Handler to unselect all layers | ||
const handleUnselectAll = () => { | ||
setSelectedLayers([]); | ||
}; | ||
|
||
useEffect(() => { | ||
if (onSelectionChange) { | ||
onSelectionChange(selectedLayers); | ||
} | ||
}, [selectedLayers, onSelectionChange]); | ||
|
||
return ( | ||
<div className="max-w-md mx-auto p-4 bg-white shadow-md rounded-md"> | ||
<div className="flex justify-start items-center mb-4 space-x-2"> | ||
<Button | ||
variant={"tertiary"} | ||
className='text-sm px-0' | ||
onClick={handleSelectAll}> | ||
Select All | ||
</Button> | ||
<Button | ||
variant={"tertiary"} | ||
className='text-sm' | ||
onClick={handleUnselectAll}> | ||
Unselect All | ||
</Button> | ||
</div> | ||
<ul className="space-y-2"> | ||
{layers.map((layer) => { | ||
const key = getLayerKey(layer); | ||
return ( | ||
<li key={key}> | ||
<div className="flex justify-between"> | ||
<Checkbox | ||
label={`${layer.namespace}/${layer.name}`} | ||
checked={selectedLayers.some(selectedLayer => selectedLayer.name === layer.name && selectedLayer.namespace === layer.namespace)} | ||
onChange={() => handleToggle(layer)} | ||
variant={variant} | ||
/> | ||
<Tag variant={layer.state}/> | ||
</div> | ||
</li> | ||
); | ||
})} | ||
</ul> | ||
</div> | ||
); | ||
}; | ||
|
||
export default LayerChecklist; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import React from 'react'; | ||
|
||
interface ProgressBarProps { | ||
/** | ||
* Progress value between 0 and 100 | ||
*/ | ||
value: number; | ||
label?: string; | ||
color?: string; | ||
className?: string; | ||
} | ||
|
||
const ProgressBar: React.FC<ProgressBarProps> = ({ | ||
value, | ||
label, | ||
color = 'bg-blue-500', | ||
className = '', | ||
}) => { | ||
// Ensure the value is between 0 and 100 | ||
const normalizedValue = Math.min(Math.max(value, 0), 100); | ||
|
||
return ( | ||
<div className={`w-full bg-gray-200 rounded-full h-4 ${className}`} aria-label="Progress Bar"> | ||
<div | ||
className={`${color} h-4 rounded-full transition-width duration-300 ease-in-out`} | ||
style={{ width: `${normalizedValue}%` }} | ||
role="progressbar" | ||
aria-valuenow={normalizedValue} | ||
aria-valuemin={0} | ||
aria-valuemax={100} | ||
> | ||
{label && ( | ||
<span className="sr-only"> | ||
{label} | ||
</span> | ||
)} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ProgressBar; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import React, { useEffect } from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import FocusLock from 'react-focus-lock'; | ||
|
||
interface SlidingPaneProps { | ||
isOpen: boolean; | ||
onClose: () => void; | ||
children?: React.ReactNode; | ||
width?: string; | ||
} | ||
|
||
const SlidingPane: React.FC<SlidingPaneProps> = ({ | ||
isOpen, | ||
onClose, | ||
children, | ||
width = 'w-1/3', | ||
}) => { | ||
// Handle Escape key to close the pane | ||
useEffect(() => { | ||
const handleKeyDown = (e: KeyboardEvent) => { | ||
if (e.key === 'Escape' && isOpen) { | ||
onClose(); | ||
} | ||
}; | ||
|
||
document.addEventListener('keydown', handleKeyDown); | ||
return () => document.removeEventListener('keydown', handleKeyDown); | ||
}, [isOpen, onClose]); | ||
|
||
// Prevent background scrolling when pane is open | ||
useEffect(() => { | ||
if (isOpen) { | ||
document.body.classList.add('overflow-hidden'); | ||
} else { | ||
document.body.classList.remove('overflow-hidden'); | ||
} | ||
|
||
return () => { | ||
document.body.classList.remove('overflow-hidden'); | ||
}; | ||
}, [isOpen]); | ||
|
||
|
||
return ReactDOM.createPortal( | ||
<> | ||
{/* Background */} | ||
<div | ||
className={`fixed inset-0 flex bg-nuances-400 bg-opacity-50 z-9 duration-300 ease-in-out ${ | ||
isOpen ? 'opacity-100 visible' : 'opacity-0 invisible' | ||
}`} | ||
onClick={onClose} | ||
aria-hidden={!isOpen} | ||
></div> | ||
|
||
{/* Sliding Pane */} | ||
<FocusLock disabled={!isOpen}> | ||
<div | ||
className={`fixed top-0 right-0 h-screen bg-primary-100 z-10 shadow-lg transform transition-transform duration-300 ease-in-out ${ | ||
isOpen ? 'translate-x-0' : 'translate-x-full' | ||
} ${width}`} | ||
> | ||
{/* Close Button */} | ||
<button | ||
aria-label="Close" | ||
className="absolute top-4 right-8 text-2xl text-gray-600 focus:outline-none" | ||
onClick={onClose} | ||
> | ||
× | ||
</button> | ||
{/* Content */} | ||
<div className="p-8 pt-12 overflow-y-auto h-full">{children}</div> | ||
</div> | ||
</FocusLock> | ||
</>, | ||
document.body | ||
); | ||
}; | ||
|
||
export default SlidingPane; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.