Glide Data Grid
  • 👋Welcome to Glide Data Grid
  • 🚀Extended QuickStart Guide
    • ✏️Editing Data
    • 🖱️Working with selections
    • 🔨Grid Columns
    • 📎Copy and paste support
  • 📒FAQ
  • 📚API
    • DataEditor
      • Required Props
      • Important Props
      • Row Markers
      • Editing
      • Input Interaction
      • Selection Handling
      • Custom Cells
      • Drag and Drop
      • Search
      • Styling
    • DataEditorCore
    • Cells
      • BaseGridCell
      • TextCell
      • BooleanCell
      • NumberCell
      • UriCell
      • ProtectedCell
      • RowIDCell
      • LoadingCell
      • ImageCell
      • MarkdownCell
      • BubbleCell
      • DrilldownCell
    • Common Types
    • DataEditorRef
  • Guides
    • Implementing Custom Cells
Powered by GitBook
On this page

Was this helpful?

  1. Guides

Implementing Custom Cells

Coming soon. For now enjoy this custom cell which kind of explains the process.

import {
    type CustomCell,
    measureTextCached,
    type CustomRenderer,
    getMiddleCenterBias,
    GridCellKind,
} from "@glideapps/glide-data-grid";
import * as React from "react";
import { roundedRect } from "../draw-fns.js";

interface RangeCellProps {
    readonly kind: "range-cell";
    readonly value: number;
    readonly min: number;
    readonly max: number;
    readonly step: number;
    readonly label?: string;
    readonly measureLabel?: string;
    readonly readonly?: boolean;
}

export type RangeCell = CustomCell<RangeCellProps>;

const RANGE_HEIGHT = 6;

const inputStyle: React.CSSProperties = {
    marginRight: 8,
};

const wrapperStyle: React.CSSProperties = {
    display: "flex",
    alignItems: "center",
    flexGrow: 1,
};

const renderer: CustomRenderer<RangeCell> = {
    kind: GridCellKind.Custom,
    isMatch: (c): c is RangeCell => (c.data as any).kind === "range-cell",
    draw: (args, cell) => {
        const { ctx, theme, rect } = args;
        const { min, max, value, label, measureLabel } = cell.data;

        const x = rect.x + theme.cellHorizontalPadding;
        const yMid = rect.y + rect.height / 2;

        const rangeSize = max - min;
        const fillRatio = (value - min) / rangeSize;

        ctx.save();
        let labelWidth = 0;
        if (label !== undefined) {
            ctx.font = `12px ${theme.fontFamily}`; // fixme this is slow
            labelWidth =
                measureTextCached(measureLabel ?? label, ctx, `12px ${theme.fontFamily}`).width +
                theme.cellHorizontalPadding;
        }

        const rangeWidth = rect.width - theme.cellHorizontalPadding * 2 - labelWidth;

        if (rangeWidth >= RANGE_HEIGHT) {
            const gradient = ctx.createLinearGradient(x, yMid, x + rangeWidth, yMid);

            gradient.addColorStop(0, theme.accentColor);
            gradient.addColorStop(fillRatio, theme.accentColor);
            gradient.addColorStop(fillRatio, theme.bgBubble);
            gradient.addColorStop(1, theme.bgBubble);

            ctx.beginPath();
            ctx.fillStyle = gradient;
            roundedRect(ctx, x, yMid - RANGE_HEIGHT / 2, rangeWidth, RANGE_HEIGHT, RANGE_HEIGHT / 2);
            ctx.fill();

            ctx.beginPath();
            roundedRect(
                ctx,
                x + 0.5,
                yMid - RANGE_HEIGHT / 2 + 0.5,
                rangeWidth - 1,
                RANGE_HEIGHT - 1,
                (RANGE_HEIGHT - 1) / 2
            );
            ctx.strokeStyle = theme.accentLight;
            ctx.lineWidth = 1;
            ctx.stroke();
        }

        if (label !== undefined) {
            ctx.textAlign = "right";
            ctx.fillStyle = theme.textDark;
            ctx.fillText(
                label,
                rect.x + rect.width - theme.cellHorizontalPadding,
                yMid + getMiddleCenterBias(ctx, `12px ${theme.fontFamily}`)
            );
        }

        ctx.restore();

        return true;
    },
    provideEditor: () => {
        // eslint-disable-next-line react/display-name
        return p => {
            const { data } = p.value;

            const strValue = data.value.toString();
            const strMin = data.min.toString();
            const strMax = data.max.toString();
            const strStep = data.step.toString();

            const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
                p.onChange({
                    ...p.value,
                    data: {
                        ...data,
                        value: Number(e.target.value),
                    },
                });
            };

            return (
                <label style={wrapperStyle}>
                    <input
                        style={inputStyle}
                        type="range"
                        value={strValue}
                        min={strMin}
                        max={strMax}
                        step={strStep}
                        onChange={onChange}
                    />
                    {strValue}
                </label>
            );
        };
    },
    onPaste: (v, d) => {
        let num = Number.parseFloat(v);
        num = Number.isNaN(num) ? d.value : Math.max(d.min, Math.min(d.max, num));
        return {
            ...d,
            value: num,
        };
    },
};

export default renderer;
PreviousDataEditorRef

Last updated 1 year ago

Was this helpful?