react-use#useEnsuredForwardedRef TypeScript Examples
The following examples show how to use
react-use#useEnsuredForwardedRef.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: FilesystemBrowserTextbox.tsx From flood with GNU General Public License v3.0 | 4 votes |
FilesystemBrowserTextbox = forwardRef<HTMLInputElement, FilesystemBrowserTextboxProps>(
(
{
id,
label,
selectable,
suggested,
showBasePathToggle,
showCompletedToggle,
showSequentialToggle,
onChange,
}: FilesystemBrowserTextboxProps,
ref,
) => {
const [destination, setDestination] = useState<string>(
suggested ||
SettingStore.floodSettings.torrentDestinations?.[''] ||
SettingStore.clientSettings?.directoryDefault ||
'',
);
const [isDirectoryListOpen, setIsDirectoryListOpen] = useState<boolean>(false);
const formRowRef = useRef<HTMLDivElement>(null);
const textboxRef = useEnsuredForwardedRef(ref as MutableRefObject<HTMLInputElement>);
const {i18n} = useLingui();
useEffect(() => {
const closeDirectoryList = (): void => {
setIsDirectoryListOpen(false);
};
const handleDocumentClick = (e: Event): void => {
if (!formRowRef.current?.contains(e.target as unknown as Node)) {
closeDirectoryList();
}
};
document.addEventListener('click', handleDocumentClick);
window.addEventListener('resize', closeDirectoryList);
return () => {
document.removeEventListener('click', handleDocumentClick);
window.removeEventListener('resize', closeDirectoryList);
};
}, []);
const toggles: Array<ReactElement> = [];
if (showBasePathToggle) {
toggles.push(
<Checkbox grow={false} id="isBasePath" key="isBasePath">
<Trans id="torrents.destination.base_path" />
</Checkbox>,
);
}
if (showCompletedToggle) {
toggles.push(
<Checkbox grow={false} id="isCompleted" key="isCompleted">
<Trans id="torrents.destination.completed" />
</Checkbox>,
);
}
if (showSequentialToggle) {
// TODO: this is getting bloated. toggles can be moved to their own elements...
toggles.push(
<Checkbox grow={false} id="isSequential" key="isSequential">
<Trans id="torrents.destination.sequential" />
</Checkbox>,
);
}
return (
<FormRowGroup ref={formRowRef}>
<FormRow>
<Textbox
autoComplete={isDirectoryListOpen ? 'off' : undefined}
addonPlacement="after"
defaultValue={destination}
id={id}
label={label}
onChange={debounce(
() => {
if (textboxRef.current == null) {
return;
}
const newDestination = textboxRef.current.value;
if (onChange) {
onChange(newDestination);
}
setDestination(newDestination);
},
100,
{leading: true},
)}
onClick={(event) => event.nativeEvent.stopImmediatePropagation()}
placeholder={i18n._('torrents.add.destination.placeholder')}
ref={textboxRef}
>
<FormElementAddon
onClick={() => {
if (textboxRef.current != null) {
setDestination(textboxRef.current.value);
}
setIsDirectoryListOpen(!isDirectoryListOpen);
}}
>
<Search />
</FormElementAddon>
<Portal>
<ContextMenu
isIn={isDirectoryListOpen}
onClick={(event) => event.nativeEvent.stopImmediatePropagation()}
overlayProps={{isInteractive: false}}
padding={false}
triggerRef={textboxRef}
>
<FilesystemBrowser
directory={destination}
selectable={selectable}
onItemSelection={(newDestination: string, shouldKeepOpen = true) => {
if (textboxRef.current != null) {
textboxRef.current.value = newDestination;
}
setIsDirectoryListOpen(shouldKeepOpen);
setDestination(newDestination);
}}
onYieldFocus={() => {
textboxRef.current?.focus();
}}
/>
</ContextMenu>
</Portal>
</Textbox>
</FormRow>
{toggles.length > 0 ? <FormRow>{toggles}</FormRow> : null}
</FormRowGroup>
);
},
)
Example #2
Source File: TableHeading.tsx From flood with GNU General Public License v3.0 | 4 votes |
TableHeading = observer(
forwardRef<HTMLDivElement, TableHeadingProps>(
({onCellClick, onCellFocus, onWidthsChange}: TableHeadingProps, ref) => {
const [isPointerDown, setIsPointerDown] = useState<boolean>(false);
const focusedCell = useRef<TorrentListColumn>();
const focusedCellWidth = useRef<number>();
const lastPointerX = useRef<number>();
const tableHeading = useEnsuredForwardedRef<HTMLDivElement>(ref as MutableRefObject<HTMLDivElement>);
const resizeLine = useRef<HTMLDivElement>(null);
const {i18n} = useLingui();
const handlePointerMove = (event: PointerEvent) => {
let widthDelta = 0;
if (lastPointerX.current != null) {
widthDelta = event.clientX - lastPointerX.current;
}
let nextCellWidth = 20;
if (focusedCellWidth.current != null) {
nextCellWidth = focusedCellWidth.current + widthDelta;
}
if (nextCellWidth > 20) {
focusedCellWidth.current = nextCellWidth;
lastPointerX.current = event.clientX;
if (resizeLine.current != null && tableHeading.current != null) {
resizeLine.current.style.transform = `translate(${Math.max(0, event.clientX)}px, ${
tableHeading.current.getBoundingClientRect().top
}px)`;
}
}
};
const handlePointerUp = () => {
UIStore.removeGlobalStyle(pointerDownStyles);
window.removeEventListener('pointerup', handlePointerUp);
window.removeEventListener('pointermove', handlePointerMove);
setIsPointerDown(false);
lastPointerX.current = undefined;
if (resizeLine.current != null) {
resizeLine.current.style.opacity = '0';
}
if (focusedCell.current != null && focusedCellWidth.current != null) {
onWidthsChange(focusedCell.current, focusedCellWidth.current);
}
focusedCell.current = undefined;
focusedCellWidth.current = undefined;
};
return (
<div className="table__row table__row--heading" role="row" ref={tableHeading}>
{SettingStore.floodSettings.torrentListColumns.reduce((accumulator: Array<ReactElement>, {id, visible}) => {
if (!visible) {
return accumulator;
}
const labelID = TorrentListColumns[id];
if (labelID == null) {
return accumulator;
}
let handle = null;
const width = SettingStore.floodSettings.torrentListColumnWidths[id] || 100;
if (!isPointerDown) {
handle = (
<span
className="table__heading__handle"
onPointerDown={(event) => {
if (!isPointerDown && resizeLine.current != null && tableHeading.current != null) {
setIsPointerDown(true);
focusedCell.current = id;
focusedCellWidth.current = width;
lastPointerX.current = event.clientX;
window.addEventListener('pointerup', handlePointerUp);
window.addEventListener('pointermove', handlePointerMove);
UIStore.addGlobalStyle(pointerDownStyles);
resizeLine.current.style.transform = `translate(${Math.max(0, event.clientX)}px, ${
tableHeading.current.getBoundingClientRect().top
}px)`;
resizeLine.current.style.opacity = '1';
}
}}
/>
);
}
const isSortActive = id === SettingStore.floodSettings.sortTorrents.property;
const classes = classnames('table__cell table__heading', {
'table__heading--is-sorted': isSortActive,
[`table__heading--direction--${SettingStore.floodSettings.sortTorrents.direction}`]: isSortActive,
});
accumulator.push(
<button
className={classes}
css={{
textAlign: 'left',
':focus': {
outline: 'none',
WebkitTapHighlightColor: 'transparent',
},
}}
role="columnheader"
aria-sort={
isSortActive
? SettingStore.floodSettings.sortTorrents.direction === 'asc'
? 'ascending'
: 'descending'
: 'none'
}
type="button"
key={id}
onClick={() => onCellClick(id)}
onFocus={() => onCellFocus()}
style={{
width: `${width}px`,
}}
>
<span className="table__heading__label" title={i18n._(labelID)}>
<Trans id={labelID} />
</span>
{handle}
</button>,
);
return accumulator;
}, [])}
<div className="table__cell table__heading table__heading--fill" />
<div className="table__heading__resize-line" ref={resizeLine} />
</div>
);
},
),
)