Shadis/src/DashboardList/views/DashboardListCell.tsx

233 lines
6.5 KiB
TypeScript
Executable File

import manageJss, { ComponentStyles } from "@microsoft/fast-jss-manager-react";
import {
DashboardListCellProps,
DashboardListCellClassNameContract,
} from "./DashboardListCell.props";
import React, { useRef } from "react";
import {
neutralLayerL2,
DesignSystem,
backgroundColor,
accentFillSelected,
neutralForegroundRest,
applyElevation,
ElevationMultiplier,
applyElevatedCornerRadius,
} from "@microsoft/fast-components-styles-msft";
import { useTranslation } from "react-i18next";
import {
Label,
Checkbox,
CheckboxClassNameContract,
} from "@microsoft/fast-components-react-msft";
import { parseColorHexRGBA } from "@microsoft/fast-colors";
import { classNames } from "@microsoft/fast-web-utilities";
import { motion } from "framer-motion";
import { Link } from "react-router-dom";
const styles: ComponentStyles<DashboardListCellClassNameContract, DesignSystem> = {
dashboardListCell: {
background: neutralLayerL2,
overflow: "hidden",
...applyElevatedCornerRadius(),
...applyElevation(ElevationMultiplier.e4),
cursor: "pointer",
"&:hover": {
"& $dashboardListCell_metadata": {
transform: "translateY(0%)",
transition: "transform .2s .1s cubic-bezier(0.1, 0.9, 0.2, 1)",
},
"& $dashboardListCell_checkbox": {
opacity: "1",
},
},
"&$dashboardListCell__checked $dashboardListCell_metadata": {
transform: "translateY(0%)",
},
"& > a": {
display: "block",
},
},
dashboardListCell_image: {
width: "200px",
objectFit: "contain",
background: neutralLayerL2,
userDrag: "none",
userSelect: "none",
opacity: "0",
transition: "opacity .1s",
},
dashboardListCell_metadata: {
position: "absolute",
bottom: "0px",
left: "0px",
width: "100%",
padding: "10px",
boxSizing: "border-box",
textAlign: "center",
background: des => parseColorHexRGBA(backgroundColor(des) + "cc").toStringWebRGBA(),
transform: "translateY(100%)",
transition: "transform .2s cubic-bezier(0.9, 0.1, 1, 0.2)",
outline: "none",
cursor: "default",
"& > label": {
width: "100%",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
outline: "none",
},
},
dashboardListCell_checkbox: {
position: "absolute",
top: "10px",
left: "10px",
zIndex: "2",
opacity: "0",
transition: "opacity 0.08s .1s",
".selecting &": {
opacity: "1",
},
},
dashboardListCell_overlay: {
display: "none",
position: "absolute",
top: "0",
left: "0",
width: "100%",
height: "100%",
boxSizing: "border-box",
borderWidth: "2px",
borderStyle: "solid",
borderColor: neutralForegroundRest,
zIndex: "1",
outline: "none",
...applyElevatedCornerRadius(),
background: (des: DesignSystem) =>
parseColorHexRGBA(accentFillSelected(des) + "aa").toStringWebRGBA(),
},
dashboardListCell__checked: {
"& $dashboardListCell_overlay": {
display: "block",
},
},
};
const checkboxStyle: ComponentStyles<CheckboxClassNameContract, DesignSystem> = {
checkbox_input: {
borderRadius: "50%",
},
checkbox_stateIndicator: {},
checkbox__checked: {
"& $checkbox_stateIndicator": {
"&::before": {
backgroundSize: "17px",
backgroundPosition: "1px 2px",
},
},
},
};
const onImageLoaded: React.ReactEventHandler<HTMLImageElement> = ({ currentTarget }) => {
currentTarget.style.opacity = "1";
currentTarget.style.pointerEvents = "none";
};
const onImageError: React.ReactEventHandler<HTMLImageElement> = ({ currentTarget }) => {
currentTarget.style.display = "none";
currentTarget.style.pointerEvents = "none";
};
/**
* Renders a cell and sets its height in the CellMeasurementCache
*/
const CellRenderer: React.FC<DashboardListCellProps> = props => {
let { id, title, thumb_height } = props.data;
const { t } = useTranslation("dashboard");
const cellRef = useRef<HTMLDivElement>(null);
// i18n-ize the title; `untitled` will call a translation string
title = title === "" || title === "untitled" || !title ? t("untitled") : title;
// We already know the thumbnail size, so we take over the work of <CellMeasurer />
if (!props.cache.has(props.index, 0)) {
props.cache.set(props.index, 0, 200, thumb_height);
if (
props.parent &&
typeof props.parent.invalidateCellSizeAfterRender === "function"
) {
props.parent.invalidateCellSizeAfterRender({
columnIndex: 0,
rowIndex: props.index,
});
}
}
const onCheckmarkChange = (
e: React.ChangeEvent<HTMLInputElement> & React.MouseEvent
) => {
if (typeof e.currentTarget.checked !== "undefined" || props.selectMode) {
props.onSelect(!props.selected);
}
};
const onAnimationStart = () => {
if (cellRef.current) cellRef.current.style.zIndex = "400";
};
const onAnimationEnd = () => {
if (cellRef.current) cellRef.current.style.zIndex = "auto";
};
const shouldExecuteclick: React.MouseEventHandler<HTMLAnchorElement> = e => {
props.selectMode && e.preventDefault();
};
return (
<motion.div
key={props.key}
layoutId={`card-image-container-${id}`}
className={classNames(props.managedClasses.dashboardListCell, [
props.managedClasses.dashboardListCell__checked,
props.selected,
])}
onAnimationStart={onAnimationStart}
onAnimationComplete={onAnimationEnd}
onClick={onCheckmarkChange}
ref={cellRef}
style={{
...props.style,
height: thumb_height,
}}
>
<Link to={`/${id}/`} onClick={shouldExecuteclick}>
<img
className={props.managedClasses.dashboardListCell_image}
src={`${window.location.origin}/${id}.thumb.jpg`}
alt={title}
onError={onImageError}
onLoad={onImageLoaded}
/>
</Link>
<Checkbox
inputId={id}
className={props.managedClasses.dashboardListCell_checkbox}
onChange={onCheckmarkChange as any}
jssStyleSheet={checkboxStyle}
checked={props.selected}
/>
<div className={props.managedClasses.dashboardListCell_overlay} />
<footer
className={props.managedClasses.dashboardListCell_metadata}
tabIndex={0}
// Make sure that small images are still selectable
style={thumb_height < 45 ? { display: "none" } : {}}
>
<Label tabIndex={-1}>{title}</Label>
</footer>
</motion.div>
);
};
export default manageJss(styles)(CellRenderer);