Skip to content

Commit

Permalink
Add custom feature for highlighting cell
Browse files Browse the repository at this point in the history
  • Loading branch information
nahooni0511 committed Jan 18, 2025
1 parent 8e5f151 commit f71d117
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 3 deletions.
11 changes: 9 additions & 2 deletions src/Cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const Cell: React.FC<Types.CellComponentProps> = ({
column,
DataViewer,
selected,
highlighted,
active,
dragging,
mode,
Expand Down Expand Up @@ -59,13 +60,13 @@ export const Cell: React.FC<Types.CellComponentProps> = ({

React.useEffect(() => {
const root = rootRef.current;
if (selected && root) {
if ((selected || highlighted) && root) {
setCellDimensions(point, getOffsetRect(root));
}
if (root && active && mode === "view") {
root.focus();
}
}, [setCellDimensions, selected, active, mode, point, data]);
}, [setCellDimensions, selected, highlighted, active, mode, point, data]);

if (data && data.DataViewer) {
// @ts-ignore
Expand Down Expand Up @@ -99,6 +100,7 @@ export const enhance = (
Omit<
Types.CellComponentProps,
| "selected"
| "highlighted"
| "active"
| "copied"
| "dragging"
Expand Down Expand Up @@ -146,13 +148,18 @@ export const enhance = (
const selected = useSelector((state) =>
state.selected.has(state.model.data, point)
);
const highlights = useSelector((state) => state.highlights);
const highlighted = highlights.some((highlight) =>
Point.isEqual(highlight.point, point)
);
const dragging = useSelector((state) => state.dragging);
const copied = useSelector((state) => state.copied?.has(point) || false);

return (
<CellComponent
{...props}
selected={selected}
highlighted={highlighted}
active={active}
copied={copied}
dragging={dragging}
Expand Down
54 changes: 54 additions & 0 deletions src/HighlightCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as React from "react";
import classnames from "classnames";
import useSelector from "./use-selector";
import { getCellDimensions } from "./util";
import {Highlight} from "./highlight";

/**
* A component that highlights a cell by taking a specific cell coordinate (`point`) and `color` value
* Like ActiveCell, it captures the position and size (cell bounding) to display the highlight.
*/
type HighlightCellComponentProps ={
highLight: Highlight;
}
const HighlightCell: React.FC<HighlightCellComponentProps> = ({ highLight }) => {
const { point, color } = highLight;
const rootRef = React.useRef<HTMLDivElement>(null);

const dimensions = useSelector((state) =>
getCellDimensions(point, state.rowDimensions, state.columnDimensions)
);

const hidden = !dimensions;
if (hidden) {
return null;
}

return (
<div
ref={rootRef}
className={classnames(
"Spreadsheet__highlight-cell"
)}
style={{
...dimensions,
borderColor: color,
}}
tabIndex={0}
/>
);
};

const HighlightCellContainer: React.FC = () => {
const highlights = useSelector((state) => state.highlights);

return (
<>
{highlights.map((highlight, index) => (
<HighlightCell key={index} highLight={highlight} />
))}
</>
);
}

export default HighlightCellContainer;
7 changes: 7 additions & 0 deletions src/Spreadsheet.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@
box-shadow: var(--elevation);
}

.Spreadsheet__highlight-cell {
position: absolute;
border: 2px solid;
box-sizing: border-box;
pointer-events: none;
}

.Spreadsheet__table {
border-collapse: collapse;
table-layout: fixed;
Expand Down
20 changes: 19 additions & 1 deletion src/Spreadsheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as Types from "./types";
import * as Actions from "./actions";
import * as Matrix from "./matrix";
import * as Point from "./point";
import * as Highlight from "./highlight";
import { Selection } from "./selection";
import reducer, { INITIAL_STATE, hasKeyDownHandler } from "./reducer";
import context from "./context";
Expand Down Expand Up @@ -34,6 +35,7 @@ import { Cell as DefaultCell, enhance as enhanceCell } from "./Cell";
import DefaultDataViewer from "./DataViewer";
import DefaultDataEditor from "./DataEditor";
import ActiveCell from "./ActiveCell";
import HighlightCellContainer from "./HighlightCell";
import Selected from "./Selected";
import Copied from "./Copied";

Expand Down Expand Up @@ -82,6 +84,8 @@ export type Props<CellType extends Types.CellBase> = {
hideColumnIndicators?: boolean;
/** The selected cells in the worksheet. */
selected?: Selection;
/** Highlights to apply to the spreadsheet */
highlights?: Highlight.Highlight[];
// Custom Components
/** Component rendered above each column. */
ColumnIndicator?: Types.ColumnIndicatorComponent;
Expand Down Expand Up @@ -160,8 +164,9 @@ const Spreadsheet = <CellType extends Types.CellBase>(
...INITIAL_STATE,
model,
selected: props.selected || INITIAL_STATE.selected,
highlights: props.highlights || INITIAL_STATE.highlights,
} as State;
}, [props.createFormulaParser, props.data, props.selected]);
}, [props.createFormulaParser, props.data, props.selected, props.highlights]);

const reducerElements = React.useReducer(
reducer as unknown as React.Reducer<State, Actions.Action>,
Expand Down Expand Up @@ -195,6 +200,7 @@ const Spreadsheet = <CellType extends Types.CellBase>(
const onDragStart = useAction(Actions.dragStart);
const onDragEnd = useAction(Actions.dragEnd);
const setData = useAction(Actions.setData);
const setHighlights = useAction(Actions.setHighlights);
const setCreateFormulaParser = useAction(Actions.setCreateFormulaParser);
const blur = useAction(Actions.blur);
const setSelection = useAction(Actions.setSelection);
Expand Down Expand Up @@ -302,6 +308,17 @@ const Spreadsheet = <CellType extends Types.CellBase>(
prevDataPropRef.current = props.data;
}, [props.data, setData]);

// Update highlights when props.highlights changes
const prevHighlightsPropRef = React.useRef<Highlight.Highlight[] | undefined>(
props.highlights
);
React.useEffect(() => {
if (props.highlights !== prevHighlightsPropRef.current) {
setHighlights(props.highlights || []);
}
prevHighlightsPropRef.current = props.highlights;
}, [props.highlights, setHighlights]);

// Update createFormulaParser when props.createFormulaParser changes
const prevCreateFormulaParserPropRef = React.useRef<
Types.CreateFormulaParser | undefined
Expand Down Expand Up @@ -535,6 +552,7 @@ const Spreadsheet = <CellType extends Types.CellBase>(
onBlur={handleBlur}
>
{tableNode}
<HighlightCellContainer />
{activeCellNode}
<Selected />
<Copied />
Expand Down
16 changes: 16 additions & 0 deletions src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import {
CreateFormulaParser,
} from "./types";
import { Selection } from "./selection";
import {Highlight} from "./highlight";

export const SET_DATA = "SET_DATA";
export const SET_CREATE_FORMULA_PARSER = "SET_CREATE_FORMULA_PARSER";
export const SELECT_ENTIRE_ROW = "SELECT_ENTIRE_ROW";
export const SELECT_ENTIRE_COLUMN = "SELECT_ENTIRE_COLUMN";
export const SELECT_ENTIRE_WORKSHEET = "SELECT_ENTIRE_WORKSHEET";
export const SET_SELECTION = "SET_SELECTION";
export const SET_HIGHLIGHTS = "SET_HIGHLIGHTS";
export const SELECT = "SELECT";
export const ACTIVATE = "ACTIVATE";
export const SET_CELL_DATA = "SET_CELL_DATA";
Expand Down Expand Up @@ -132,6 +134,19 @@ export function select(point: Point): SelectAction {
};
}

export type SetHighlightsAction = BaseAction<typeof SET_HIGHLIGHTS> & {
payload: {
highlights: Highlight[];
};
};

export function setHighlights(highlights: Highlight[]): SetHighlightsAction {
return {
type: SET_HIGHLIGHTS,
payload: { highlights },
};
}

export type ActivateAction = BaseAction<typeof ACTIVATE> & {
payload: {
point: Point;
Expand Down Expand Up @@ -283,6 +298,7 @@ export type Action =
| SelectEntireColumnAction
| SelectEntireWorksheetAction
| SetSelectionAction
| SetHighlightsAction
| SelectAction
| ActivateAction
| SetCellDataAction
Expand Down
7 changes: 7 additions & 0 deletions src/highlight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {Point} from "./point";

/** A highlight in the spreadsheet */
export type Highlight = {
point: Point;
color: string;
};
7 changes: 7 additions & 0 deletions src/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const INITIAL_STATE: Types.StoreState = {
selected: new EmptySelection(),
copied: null,
lastCommit: null,
highlights: [],
};

export default function reducer(
Expand Down Expand Up @@ -103,6 +104,12 @@ export default function reducer(
mode: "view",
};
}
case Actions.SET_HIGHLIGHTS: {
return {
...state,
highlights: action.payload.highlights,
};
}
case Actions.SELECT: {
const { point } = action.payload;
if (state.active && !isActive(state.active, point)) {
Expand Down
23 changes: 23 additions & 0 deletions src/stories/Spreadsheet.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import CustomCell from "./CustomCell";
import { RangeEdit, RangeView } from "./RangeDataComponents";
import { SelectEdit, SelectView } from "./SelectDataComponents";
import { CustomCornerIndicator } from "./CustomCornerIndicator";
import {Highlight} from "../highlight";

type StringCell = CellBase<string | undefined>;
type NumberCell = CellBase<number | undefined>;
Expand Down Expand Up @@ -305,3 +306,25 @@ export const ControlledSelection: StoryFn<Props<StringCell>> = (props) => {
</div>
);
};

export const ControlledHighlights: StoryFn<Props<StringCell>> = (props) => {
const [highlights, setHighlights] = React.useState<Highlight[]>([{ point: { row: 0, column: 0 }, color: "#FF0000" }]);

const handleHighlight = React.useCallback(() => {
setHighlights((highlights) => {
if (highlights.length === 0) {
return [{ point: { row: 0, column: 0 }, color: "#FF0000" }];
}
return [];
});
}, []);

return (
<div>
<div>
<button onClick={handleHighlight}>toggle highlight</button>
</div>
<Spreadsheet {...props} highlights={highlights} />;
</div>
);
};
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Selection } from "./selection";
import { Model } from "./engine";
import { PointRange } from "./point-range";
import { Matrix } from "./matrix";
import {Highlight} from "./highlight";

/** The base type of cell data in Spreadsheet */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -52,6 +53,7 @@ export type StoreState<Cell extends CellBase = CellBase> = {
hasPasted: boolean;
cut: boolean;
active: Point | null;
highlights: Highlight[];
mode: Mode;
rowDimensions: Record<number, Pick<Dimensions, "height" | "top"> | undefined>;
columnDimensions: Record<
Expand All @@ -78,6 +80,8 @@ export type CellComponentProps<Cell extends CellBase = CellBase> = {
DataViewer: DataViewerComponent<Cell>;
/** Whether the cell is selected */
selected: boolean;
/** Whether the cell is highlighted */
highlighted: boolean;
/** Whether the cell is active */
active: boolean;
/** Whether the cell is copied */
Expand Down

0 comments on commit f71d117

Please sign in to comment.