Feat: FileManager (#98) (#703)

* feat: add file manager module

- Complete file manager implementation with UI/UX
- Add drive management functionality
- Add file upload/download with progress tracking
- Add stamp integration and handling
- Add bulk operations and context menus

Co-authored-by: Roland Seres <roland.seres90@gmail.com>
Co-authored-by: nidishk <nidishkrishnan45@gmail.com>
This commit is contained in:
Bálint Ujvári
2025-11-12 11:26:00 +01:00
committed by GitHub
parent 1249c0df71
commit 5bfe2a0331
107 changed files with 21529 additions and 5578 deletions
@@ -0,0 +1,87 @@
.fm-tooltip-wrapper {
position: relative;
display: inline-flex;
align-items: center;
margin-left: 4px;
}
.fm-tooltip-wrapper.no-margin {
margin-left: 0;
}
.fm-tooltip-text {
display: inline-block;
margin-right: 4px;
line-height: 1.2;
}
.fm-tooltip-trigger {
background: transparent;
border: none;
padding: 0;
cursor: default;
display: flex;
align-items: center;
color: rgb(107, 114, 128);
}
.fm-tooltip-wrapper:hover .fm-tooltip-trigger svg {
color: rgb(55, 65, 81);
}
.fm-tooltip-container {
visibility: hidden;
opacity: 0;
width: max-content;
max-width: 360px;
background: #fff;
color: #222;
text-align: left;
border-radius: 6px;
padding: 8px 12px;
position: absolute;
z-index: 30;
top: 50%;
left: calc(100% + 6px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
font-size: 12px;
line-height: 1.4;
font-weight: 400; /* ensure no bold styling */
pointer-events: none;
transition: opacity 0.16s ease-in-out, visibility 0.16s ease-in-out, transform 0.16s ease-in-out;
border: 1px solid rgb(209, 213, 219);
transform: translateY(-50%) translateX(4px);
}
.fm-tooltip-container.bottom {
transform: translateY(-60%) !important;
}
.fm-tooltip-wrapper:hover .fm-tooltip-container {
visibility: visible;
opacity: 1;
pointer-events: auto;
transform: translateY(-50%) translateX(0);
}
/* Left alignment (flip) when wrapper has .left class */
.fm-tooltip-wrapper.left .fm-tooltip-container {
left: auto;
right: calc(100% + 6px);
transform: translateY(-50%) translateX(-4px);
}
.fm-tooltip-wrapper.left:hover .fm-tooltip-container {
transform: translateY(-50%) translateX(0);
}
.fm-inline-label-with-tooltip {
display: flex;
align-items: center;
gap: 4px;
}
.fm-flex-column {
display: flex;
flex-direction: column;
}
@@ -0,0 +1,66 @@
import { ReactElement, useState, useRef, useCallback } from 'react'
import InfoIcon from 'remixicon-react/InformationLineIcon'
import './Tooltip.scss'
interface TooltipProps {
label: string
iconSize?: string
edgeOffsetPx?: number
gapPx?: number
children?: React.ReactNode
disableMargin?: boolean
bottomTooltip?: boolean
}
export function Tooltip({
label,
iconSize = '16px',
edgeOffsetPx = 12,
gapPx = 6,
children,
disableMargin = false,
bottomTooltip = false,
}: TooltipProps): ReactElement {
const [alignLeft, setAlignLeft] = useState(false)
const wrapperRef = useRef<HTMLSpanElement>(null)
const evaluateAlignment = useCallback(() => {
const wrapper = wrapperRef.current
if (!wrapper) return
const container = wrapper.querySelector('.fm-tooltip-container') as HTMLElement | null
if (!container) return
const wrapperRect = wrapper.getBoundingClientRect()
const tooltipWidth = container.offsetWidth || 0
const projectedRight = wrapperRect.right + gapPx + tooltipWidth + edgeOffsetPx
const viewportWidth = window.innerWidth
if (projectedRight > viewportWidth) {
setAlignLeft(true)
} else {
setAlignLeft(false)
}
}, [edgeOffsetPx, gapPx])
return (
<span
ref={wrapperRef}
className={`fm-tooltip-wrapper${alignLeft ? ' left' : ''}${disableMargin ? ' no-margin' : ''}`}
aria-label="info tooltip"
onMouseEnter={evaluateAlignment}
style={{ ['--fm-tooltip-gap' as string]: `${gapPx}px` }}
>
{children && <span className="fm-tooltip-text">{children}</span>}
<span className="fm-tooltip-trigger" role="button" tabIndex={0}>
<InfoIcon size={iconSize} />
</span>
<div
className={`fm-tooltip-container${bottomTooltip ? ' bottom' : ''}`}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: label }}
/>
</span>
)
}