Documentation
Cell Clicking
Handle cell click events to create interactive table experiences. The onCellClick callback provides detailed information about each click, enabling navigation, modals, filtering, and quick actions.
Last Click:Click any cell to see interaction details...
React TSX
Copy
1import { useState, useMemo } from "react";2import { SimpleTable } from "@simple-table/react";3import type {4 Theme,5 ReactHeaderObject,6 CellRendererProps,7 CellClickProps,8} from "@simple-table/react";9import {10 cellClickingHeaders,11 cellClickingData,12 CELL_CLICKING_STATUSES,13} from "./cell-clicking.demo-data";14import type { ProjectTask } from "./cell-clicking.demo-data";15import "@simple-table/react/styles.css";1617const CellClickingDemo = ({ height, theme }: { height?: string | number; theme?: Theme }) => {18 const [clickInfo, setClickInfo] = useState("");19 const [selectedTask, setSelectedTask] = useState<ProjectTask | null>(null);20 const [rows, setRows] = useState<ProjectTask[]>([...cellClickingData]);2122 const headers: ReactHeaderObject[] = useMemo(23 () =>24 cellClickingHeaders.map((h) => {25 if (h.accessor === "priority") {26 return {27 ...h,28 cellRenderer: ({ row }: CellRendererProps) => {29 const p = String(row.priority);30 return (31 <span32 style={{33 color: p === "High" ? "#ef4444" : p === "Medium" ? "#f59e0b" : "#10b981",34 fontWeight: "bold",35 cursor: "pointer",36 }}37 title="Click to filter by priority"38 >39 {p}40 </span>41 );42 },43 };44 }45 if (h.accessor === "status") {46 return {47 ...h,48 cellRenderer: ({ row }: CellRendererProps) => {49 const s = String(row.status);50 const bg =51 s === "Completed" ? "#dcfce7" : s === "In Progress" ? "#fef3c7" : "#fee2e2";52 const color =53 s === "Completed" ? "#166534" : s === "In Progress" ? "#92400e" : "#991b1b";54 return (55 <span56 style={{57 backgroundColor: bg,58 color,59 padding: "4px 8px",60 borderRadius: 4,61 fontSize: 12,62 fontWeight: "bold",63 cursor: "pointer",64 }}65 title="Click to change status"66 >67 {s}68 </span>69 );70 },71 };72 }73 if (h.accessor === "details") {74 return {75 ...h,76 cellRenderer: () => (77 <button78 style={{79 backgroundColor: "#3b82f6",80 color: "white",81 border: "none",82 padding: "6px 12px",83 borderRadius: 4,84 cursor: "pointer",85 fontSize: 12,86 fontWeight: "bold",87 }}88 title="Click to view task details"89 >90 View Details91 </button>92 ),93 };94 }95 return h;96 }),97 [],98 );99100 const handleCellClick = ({ accessor, rowIndex, value, row }: CellClickProps) => {101 const task = row as ProjectTask;102 switch (accessor) {103 case "priority": {104 setClickInfo(`Filtering by ${value} priority`);105 setRows(cellClickingData.filter((t) => t.priority === value));106 break;107 }108 case "status": {109 const idx = CELL_CLICKING_STATUSES.indexOf(String(value));110 const next = CELL_CLICKING_STATUSES[(idx + 1) % CELL_CLICKING_STATUSES.length];111 setRows((prev) => prev.map((t) => (t.id === task.id ? { ...t, status: next } : t)));112 setClickInfo(`Status changed from "${value}" to "${next}"`);113 break;114 }115 case "details":116 setSelectedTask(task);117 setClickInfo(`Opening details for: ${task.task}`);118 break;119 case "estimatedHours": {120 const newVal = Math.min(task.estimatedHours + 2, 40);121 setRows((prev) =>122 prev.map((t) => (t.id === task.id ? { ...t, estimatedHours: newVal } : t)),123 );124 setClickInfo(`Est. hours: ${task.estimatedHours}h → ${newVal}h`);125 break;126 }127 case "completedHours": {128 const newVal = Math.min(task.completedHours + 1, task.estimatedHours);129 setRows((prev) =>130 prev.map((t) => (t.id === task.id ? { ...t, completedHours: newVal } : t)),131 );132 setClickInfo(`Done hours: ${task.completedHours}h → ${newVal}h`);133 break;134 }135 default:136 setClickInfo(`Clicked [${accessor}] = "${value}" (row ${rowIndex})`);137 }138 };139140 const isDark = theme === "modern-dark" || theme === "dark";141142 return (143 <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>144 <div145 style={{146 padding: 12,147 backgroundColor: isDark ? "#374151" : "#f3f4f6",148 borderRadius: 8,149 border: `1px solid ${isDark ? "#4b5563" : "#d1d5db"}`,150 minHeight: 48,151 display: "flex",152 alignItems: "center",153 }}154 >155 <strong style={{ marginRight: 8, color: isDark ? "#f9fafb" : "#1f2937" }}>156 Last Click:157 </strong>158 <span style={{ color: isDark ? "#d1d5db" : "#4b5563" }}>159 {clickInfo || "Click any cell to see interaction details..."}160 </span>161 </div>162163 {selectedTask && (164 <div165 style={{166 position: "fixed",167 inset: 0,168 backgroundColor: "rgba(0,0,0,0.5)",169 display: "flex",170 alignItems: "center",171 justifyContent: "center",172 zIndex: 1000,173 }}174 >175 <div176 style={{177 backgroundColor: isDark ? "#1f2937" : "white",178 padding: 24,179 borderRadius: 8,180 maxWidth: 500,181 width: "90%",182 }}183 >184 <h3 style={{ margin: "0 0 16px", color: isDark ? "#f9fafb" : "#1f2937" }}>185 Task Details186 </h3>187 {(["task", "details", "assignee", "status", "priority"] as const).map((key) => (188 <p key={key} style={{ margin: "8px 0", color: isDark ? "#d1d5db" : "#4b5563" }}>189 <strong>{key.charAt(0).toUpperCase() + key.slice(1)}:</strong> {selectedTask[key]}190 </p>191 ))}192 <button193 onClick={() => setSelectedTask(null)}194 style={{195 marginTop: 16,196 backgroundColor: "#3b82f6",197 color: "white",198 border: "none",199 padding: "8px 16px",200 borderRadius: 4,201 cursor: "pointer",202 fontWeight: "bold",203 }}204 >205 Close206 </button>207 </div>208 </div>209 )}210211 <SimpleTable212 columnResizing213 defaultHeaders={headers}214 height={height ?? "320px"}215 onCellClick={handleCellClick}216 rows={rows}217 theme={theme}218 />219 </div>220 );221};222223export default CellClickingDemo;
Vue SFC
Copy
1<script setup lang="ts">2import { ref, computed, h } from "vue";3import { SimpleTable } from "@simple-table/vue";4import type { Theme, VueHeaderObject, CellClickProps, CellRendererProps } from "@simple-table/vue";5import { cellClickingHeaders, cellClickingData, CELL_CLICKING_STATUSES } from "./cell-clicking.demo-data";6import type { ProjectTask } from "./cell-clicking.demo-data";7import "@simple-table/vue/styles.css";89const props = withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {10 height: "320px",11});1213const clickInfo = ref("");14const selectedTask = ref<ProjectTask | null>(null);15const rows = ref<ProjectTask[]>([...cellClickingData]);1617const headers: VueHeaderObject[] = cellClickingHeaders.map((col) => {18 if (col.accessor === "priority") {19 return {20 ...col,21 cellRenderer: ({ row }: CellRendererProps) => {22 const p = String((row as Record<string, unknown>).priority);23 const color = p === "High" ? "#ef4444" : p === "Medium" ? "#f59e0b" : "#10b981";24 return h(25 "span",26 {27 style: { color, fontWeight: "bold", cursor: "pointer" },28 title: "Click to filter by priority",29 },30 p,31 );32 },33 };34 }35 if (col.accessor === "status") {36 return {37 ...col,38 cellRenderer: ({ row }: CellRendererProps) => {39 const s = String((row as Record<string, unknown>).status);40 const bg = s === "Completed" ? "#dcfce7" : s === "In Progress" ? "#fef3c7" : "#fee2e2";41 const color = s === "Completed" ? "#166534" : s === "In Progress" ? "#92400e" : "#991b1b";42 return h(43 "span",44 {45 style: {46 backgroundColor: bg,47 color,48 padding: "4px 8px",49 borderRadius: "4px",50 fontSize: "12px",51 fontWeight: "bold",52 cursor: "pointer",53 },54 title: "Click to change status",55 },56 s,57 );58 },59 };60 }61 if (col.accessor === "details") {62 return {63 ...col,64 cellRenderer: () =>65 h(66 "button",67 {68 style: {69 background: "#3b82f6",70 color: "white",71 border: "none",72 padding: "6px 12px",73 borderRadius: "4px",74 cursor: "pointer",75 fontSize: "12px",76 fontWeight: "bold",77 },78 title: "Click to view task details",79 },80 "View Details",81 ),82 };83 }84 return { ...col };85});8687const isDark = computed(() => props.theme === "modern-dark" || props.theme === "dark");8889function handleCellClick({ accessor, rowIndex, value, row }: CellClickProps) {90 const task = row as ProjectTask;91 switch (accessor) {92 case "priority":93 clickInfo.value = `Filtering by ${value} priority`;94 rows.value = cellClickingData.filter((t) => t.priority === value);95 break;96 case "status": {97 const idx = CELL_CLICKING_STATUSES.indexOf(String(value));98 const next = CELL_CLICKING_STATUSES[(idx + 1) % CELL_CLICKING_STATUSES.length];99 rows.value = rows.value.map((t) => (t.id === task.id ? { ...t, status: next } : t));100 clickInfo.value = `Status: "${value}" → "${next}"`;101 break;102 }103 case "details":104 selectedTask.value = task;105 clickInfo.value = `Opening details for: ${task.task}`;106 break;107 case "estimatedHours": {108 const n = Math.min(task.estimatedHours + 2, 40);109 rows.value = rows.value.map((t) => (t.id === task.id ? { ...t, estimatedHours: n } : t));110 clickInfo.value = `Est. hours: ${task.estimatedHours}h → ${n}h`;111 break;112 }113 case "completedHours": {114 const n = Math.min(task.completedHours + 1, task.estimatedHours);115 rows.value = rows.value.map((t) => (t.id === task.id ? { ...t, completedHours: n } : t));116 clickInfo.value = `Done hours: ${task.completedHours}h → ${n}h`;117 break;118 }119 default:120 clickInfo.value = `Clicked [${accessor}] = "${value}" (row ${rowIndex})`;121 }122}123</script>124125<template>126 <div style="display: flex; flex-direction: column; gap: 16px">127 <div128 :style="{129 padding: '12px',130 backgroundColor: isDark ? '#374151' : '#f3f4f6',131 borderRadius: '8px',132 border: '1px solid ' + (isDark ? '#4b5563' : '#d1d5db'),133 minHeight: '48px',134 display: 'flex',135 alignItems: 'center',136 }"137 >138 <strong :style="{ marginRight: '8px', color: isDark ? '#f9fafb' : '#1f2937' }">Last Click:</strong>139 <span :style="{ color: isDark ? '#d1d5db' : '#4b5563' }">140 {{ clickInfo || "Click any cell to see interaction details..." }}141 </span>142 </div>143144 <div145 v-if="selectedTask"146 style="position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000"147 >148 <div149 :style="{ background: isDark ? '#1f2937' : 'white', padding: '24px', borderRadius: '8px', maxWidth: '500px', width: '90%' }"150 >151 <h3 :style="{ margin: '0 0 16px', color: isDark ? '#f9fafb' : '#1f2937' }">Task Details</h3>152 <p :style="{ margin: '8px 0', color: isDark ? '#d1d5db' : '#4b5563' }"><strong>Task:</strong> {{ selectedTask.task }}</p>153 <p :style="{ margin: '8px 0', color: isDark ? '#d1d5db' : '#4b5563' }"><strong>Details:</strong> {{ selectedTask.details }}</p>154 <p :style="{ margin: '8px 0', color: isDark ? '#d1d5db' : '#4b5563' }"><strong>Assignee:</strong> {{ selectedTask.assignee }}</p>155 <p :style="{ margin: '8px 0', color: isDark ? '#d1d5db' : '#4b5563' }"><strong>Status:</strong> {{ selectedTask.status }}</p>156 <p :style="{ margin: '8px 0', color: isDark ? '#d1d5db' : '#4b5563' }"><strong>Priority:</strong> {{ selectedTask.priority }}</p>157 <button158 @click="selectedTask = null"159 style="margin-top: 16px; background: #3b82f6; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-weight: bold"160 >161 Close162 </button>163 </div>164 </div>165166 <SimpleTable167 :column-resizing="true"168 :default-headers="headers"169 :height="height"170 :on-cell-click="handleCellClick"171 :rows="rows"172 :theme="theme"173 />174 </div>175</template>
Angularcell-click-details-cell.component.ts
Copy
1import { Component } from "@angular/core";23@Component({4 standalone: true,5 selector: "demo-cell-click-details",6 template: `7 <button8 type="button"9 style="background:#3b82f6;color:white;border:none;padding:6px 12px;border-radius:4px;cursor:pointer;font-size:12px;font-weight:bold;"10 >11 View Details12 </button>13 `,14})15export class CellClickDetailsCellComponent {}161718// cell-click-priority-cell.component.ts19import { Component, Input } from "@angular/core";20import type { Row } from "@simple-table/angular";2122@Component({23 standalone: true,24 selector: "demo-cell-click-priority",25 template: `26 <span [style.color]="color" style="font-weight:bold;cursor:pointer;" title="Click to filter by priority">{{ priority }}</span>27 `,28})29export class CellClickPriorityCellComponent {30 @Input({ required: true }) row!: Row;3132 get priority(): string {33 return String((this.row as Record<string, unknown>)["priority"]);34 }3536 get color(): string {37 const p = this.priority;38 if (p === "High") return "#ef4444";39 if (p === "Medium") return "#f59e0b";40 return "#10b981";41 }42}434445// cell-click-status-cell.component.ts46import { Component, Input } from "@angular/core";47import type { Row } from "@simple-table/angular";4849@Component({50 standalone: true,51 selector: "demo-cell-click-status",52 template: `53 <span54 [style.background]="bg"55 [style.color]="fg"56 style="padding:4px 8px;border-radius:4px;font-size:12px;font-weight:bold;cursor:pointer;"57 title="Click to change status"58 >{{ status }}</span>59 `,60})61export class CellClickStatusCellComponent {62 @Input({ required: true }) row!: Row;6364 get status(): string {65 return String((this.row as Record<string, unknown>)["status"]);66 }6768 get bg(): string {69 const s = this.status;70 if (s === "Completed") return "#dcfce7";71 if (s === "In Progress") return "#fef3c7";72 return "#fee2e2";73 }7475 get fg(): string {76 const s = this.status;77 if (s === "Completed") return "#166534";78 if (s === "In Progress") return "#92400e";79 return "#991b1b";80 }81}828384// cell-clicking-demo.component.ts85import { NgIf } from "@angular/common";86import { Component, Input } from "@angular/core";87import { SimpleTableComponent } from "@simple-table/angular";88import type { AngularHeaderObject, CellClickProps, Theme } from "@simple-table/angular";89import { cellClickingData, cellClickingHeaders, CELL_CLICKING_STATUSES } from "./cell-clicking.demo-data";90import type { ProjectTask } from "./cell-clicking.demo-data";91import { CellClickDetailsCellComponent } from "./cell-click-details-cell.component";92import { CellClickPriorityCellComponent } from "./cell-click-priority-cell.component";93import { CellClickStatusCellComponent } from "./cell-click-status-cell.component";94import "@simple-table/angular/styles.css";9596@Component({97 selector: "cell-clicking-demo",98 standalone: true,99 imports: [SimpleTableComponent, NgIf],100 template: `101 <div style="display: flex; flex-direction: column; gap: 16px">102 <div [style.padding]="'12px'" [style.background-color]="isDark ? '#374151' : '#f3f4f6'" [style.border-radius]="'8px'" [style.border]="'1px solid ' + (isDark ? '#4b5563' : '#d1d5db')" [style.min-height]="'48px'" style="display: flex; align-items: center">103 <strong [style.margin-right]="'8px'" [style.color]="isDark ? '#f9fafb' : '#1f2937'">Last Click:</strong>104 <span [style.color]="isDark ? '#d1d5db' : '#4b5563'">{{ clickInfo || 'Click any cell to see interaction details...' }}</span>105 </div>106107 <div *ngIf="selectedTask" style="position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000">108 <div [style.background]="isDark ? '#1f2937' : 'white'" style="padding: 24px; border-radius: 8px; max-width: 500px; width: 90%">109 <h3 [style.color]="isDark ? '#f9fafb' : '#1f2937'" style="margin: 0 0 16px">Task Details</h3>110 <p [style.color]="isDark ? '#d1d5db' : '#4b5563'" style="margin: 8px 0"><strong>Task:</strong> {{ selectedTask.task }}</p>111 <p [style.color]="isDark ? '#d1d5db' : '#4b5563'" style="margin: 8px 0"><strong>Details:</strong> {{ selectedTask.details }}</p>112 <p [style.color]="isDark ? '#d1d5db' : '#4b5563'" style="margin: 8px 0"><strong>Assignee:</strong> {{ selectedTask.assignee }}</p>113 <p [style.color]="isDark ? '#d1d5db' : '#4b5563'" style="margin: 8px 0"><strong>Status:</strong> {{ selectedTask.status }}</p>114 <p [style.color]="isDark ? '#d1d5db' : '#4b5563'" style="margin: 8px 0"><strong>Priority:</strong> {{ selectedTask.priority }}</p>115 <button (click)="selectedTask = null" style="margin-top: 16px; background: #3b82f6; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-weight: bold">Close</button>116 </div>117 </div>118119 <simple-table120 [columnResizing]="true"121 [defaultHeaders]="headers"122 [height]="height"123 [onCellClick]="handleCellClick"124 [rows]="rows"125 [theme]="theme"126 ></simple-table>127 </div>128 `,129})130export class CellClickingDemoComponent {131 @Input() height: string | number = "320px";132 @Input() theme?: Theme;133134 clickInfo = "";135 selectedTask: ProjectTask | null = null;136 rows: ProjectTask[] = [...cellClickingData];137138 get isDark() {139 return this.theme === "modern-dark" || this.theme === "dark";140 }141142 readonly headers: AngularHeaderObject[] = cellClickingHeaders.map((h) => {143 if (h.accessor === "priority") {144 return { ...h, cellRenderer: CellClickPriorityCellComponent };145 }146 if (h.accessor === "status") {147 return { ...h, cellRenderer: CellClickStatusCellComponent };148 }149 if (h.accessor === "details") {150 return { ...h, cellRenderer: CellClickDetailsCellComponent };151 }152 return { ...h };153 });154155 handleCellClick = ({ accessor, rowIndex, value, row }: CellClickProps) => {156 const task = row as ProjectTask;157 switch (accessor) {158 case "priority":159 this.clickInfo = `Filtering by ${value} priority`;160 this.rows = cellClickingData.filter((t) => t.priority === value);161 break;162 case "status": {163 const idx = CELL_CLICKING_STATUSES.indexOf(String(value));164 const next = CELL_CLICKING_STATUSES[(idx + 1) % CELL_CLICKING_STATUSES.length];165 this.rows = this.rows.map((t) => (t.id === task.id ? { ...t, status: next } : t));166 this.clickInfo = `Status: "${value}" → "${next}"`;167 break;168 }169 case "details":170 this.selectedTask = task;171 this.clickInfo = `Opening details for: ${task.task}`;172 break;173 case "estimatedHours": {174 const n = Math.min(task.estimatedHours + 2, 40);175 this.rows = this.rows.map((t) => (t.id === task.id ? { ...t, estimatedHours: n } : t));176 this.clickInfo = `Est. hours: ${task.estimatedHours}h → ${n}h`;177 break;178 }179 case "completedHours": {180 const n = Math.min(task.completedHours + 1, task.estimatedHours);181 this.rows = this.rows.map((t) => (t.id === task.id ? { ...t, completedHours: n } : t));182 this.clickInfo = `Done hours: ${task.completedHours}h → ${n}h`;183 break;184 }185 default:186 this.clickInfo = `Clicked [${accessor}] = "${value}" (row ${rowIndex})`;187 }188 };189}190191192// cell-clicking.demo-data.ts193// Self-contained demo table setup for this example.194import type { AngularHeaderObject, Row } from "@simple-table/angular";195196197export type ProjectTask = {198 id: number;199 task: string;200 assignee: string;201 priority: string;202 status: string;203 dueDate: string;204 estimatedHours: number;205 completedHours: number;206 details: string;207};208209export const STATUSES = ["Not Started", "In Progress", "Completed"];210211export const cellClickingData: ProjectTask[] = [212 { id: 1001, task: "Design login page mockups", assignee: "Sarah Chen", priority: "High", status: "In Progress", dueDate: "2024-02-15", estimatedHours: 8, completedHours: 5, details: "Create responsive login page designs with modern UI patterns" },213 { id: 1002, task: "Implement user authentication API", assignee: "Marcus Rodriguez", priority: "High", status: "Not Started", dueDate: "2024-02-20", estimatedHours: 16, completedHours: 0, details: "Build secure JWT-based authentication system with OAuth integration" },214 { id: 1003, task: "Write unit tests for payment module", assignee: "Luna Martinez", priority: "Medium", status: "Completed", dueDate: "2024-02-10", estimatedHours: 12, completedHours: 12, details: "Comprehensive test coverage for payment processing functionality" },215 { id: 1004, task: "Update documentation for API endpoints", assignee: "Kai Thompson", priority: "Low", status: "In Progress", dueDate: "2024-02-25", estimatedHours: 6, completedHours: 3, details: "Update Swagger documentation and add usage examples" },216 { id: 1005, task: "Performance optimization for dashboard", assignee: "Zara Kim", priority: "Medium", status: "Not Started", dueDate: "2024-03-01", estimatedHours: 20, completedHours: 0, details: "Optimize rendering performance and implement lazy loading" },217 { id: 1006, task: "Mobile responsiveness testing", assignee: "Tyler Anderson", priority: "High", status: "In Progress", dueDate: "2024-02-18", estimatedHours: 10, completedHours: 7, details: "Test application across various mobile devices and screen sizes" },218 { id: 1007, task: "Setup CI/CD pipeline", assignee: "Phoenix Lee", priority: "Medium", status: "Completed", dueDate: "2024-02-08", estimatedHours: 14, completedHours: 14, details: "Automated testing and deployment pipeline using GitHub Actions" },219 { id: 1008, task: "Database migration scripts", assignee: "River Jackson", priority: "Low", status: "Not Started", dueDate: "2024-02-28", estimatedHours: 8, completedHours: 0, details: "Create migration scripts for database schema updates" },220];221222export const cellClickingHeaders: AngularHeaderObject[] = [223 { accessor: "id", label: "Task ID", width: 80, isSortable: true, type: "number" },224 { accessor: "task", label: "Task Name", minWidth: 150, width: "1fr", isSortable: true, type: "string" },225 { accessor: "assignee", label: "Assignee", width: 120, isSortable: true, type: "string" },226 { accessor: "priority", label: "Priority", width: 100, isSortable: true, type: "string" },227 { accessor: "status", label: "Status", width: 120, isSortable: true, type: "string" },228 { accessor: "dueDate", label: "Due Date", width: 120, isSortable: true, type: "date" },229 { accessor: "estimatedHours", label: "Est. Hours", width: 100, isSortable: true, type: "number" },230 { accessor: "completedHours", label: "Done Hours", width: 100, isSortable: true, type: "number" },231 { accessor: "details", label: "View Details", width: 120, type: "other" },232];233234export const cellClickingConfig = {235 headers: cellClickingHeaders,236 rows: cellClickingData,237} as const;238239export { STATUSES as CELL_CLICKING_STATUSES };240
SvelteCellClickDetailsCell.svelte
Copy
1<button2 type="button"3 style="background:#3b82f6;color:white;border:none;padding:6px 12px;border-radius:4px;cursor:pointer;font-size:12px;font-weight:bold;"4>5 View Details6</button>789// CellClickPriorityCell.svelte10<script lang="ts">11 import type { CellRendererProps } from "@simple-table/svelte";1213 let { row }: CellRendererProps = $props();14 const p = $derived(String((row as Record<string, unknown>).priority));15 const color = $derived(p === "High" ? "#ef4444" : p === "Medium" ? "#f59e0b" : "#10b981");16</script>1718<span style="color:{color};font-weight:bold;cursor:pointer;" title="Click to filter by priority">{p}</span>192021// CellClickStatusCell.svelte22<script lang="ts">23 import type { CellRendererProps } from "@simple-table/svelte";2425 let { row }: CellRendererProps = $props();26 const s = $derived(String((row as Record<string, unknown>).status));27 const bg = $derived(s === "Completed" ? "#dcfce7" : s === "In Progress" ? "#fef3c7" : "#fee2e2");28 const c = $derived(s === "Completed" ? "#166534" : s === "In Progress" ? "#92400e" : "#991b1b");29</script>3031<span32 style="background:{bg};color:{c};padding:4px 8px;border-radius:4px;font-size:12px;font-weight:bold;cursor:pointer;"33 title="Click to change status">{s}</span>343536// CellClickingDemo.svelte37<script lang="ts">38 import { SimpleTable } from "@simple-table/svelte";39 import type { Theme, SvelteHeaderObject, CellClickProps } from "@simple-table/svelte";40 import { cellClickingHeaders, cellClickingData, CELL_CLICKING_STATUSES } from "./cell-clicking.demo-data";41 import type { ProjectTask } from "./cell-clicking.demo-data";42 import CellClickPriorityCell from "./CellClickPriorityCell.svelte";43 import CellClickStatusCell from "./CellClickStatusCell.svelte";44 import CellClickDetailsCell from "./CellClickDetailsCell.svelte";45 import "@simple-table/svelte/styles.css";4647 let { height = "320px", theme }: { height?: string | number; theme?: Theme } = $props();4849 let clickInfo = $state("");50 let selectedTask: ProjectTask | null = $state(null);51 let rows: ProjectTask[] = $state([...cellClickingData]);5253 const headers: SvelteHeaderObject[] = cellClickingHeaders.map((h) => {54 if (h.accessor === "priority") return { ...h, cellRenderer: CellClickPriorityCell };55 if (h.accessor === "status") return { ...h, cellRenderer: CellClickStatusCell };56 if (h.accessor === "details") return { ...h, cellRenderer: CellClickDetailsCell };57 return { ...h };58 });5960 let isDark = $derived(theme === "modern-dark" || theme === "dark");6162 function handleCellClick({ accessor, rowIndex, value, row }: CellClickProps) {63 const task = row as ProjectTask;64 switch (accessor) {65 case "priority":66 clickInfo = `Filtering by ${value} priority`;67 rows = cellClickingData.filter((t) => t.priority === value);68 break;69 case "status": {70 const idx = CELL_CLICKING_STATUSES.indexOf(String(value));71 const next = CELL_CLICKING_STATUSES[(idx + 1) % CELL_CLICKING_STATUSES.length];72 rows = rows.map((t) => (t.id === task.id ? { ...t, status: next } : t));73 clickInfo = `Status: "${value}" → "${next}"`;74 break;75 }76 case "details":77 selectedTask = task;78 clickInfo = `Opening details for: ${task.task}`;79 break;80 case "estimatedHours": {81 const n = Math.min(task.estimatedHours + 2, 40);82 rows = rows.map((t) => (t.id === task.id ? { ...t, estimatedHours: n } : t));83 clickInfo = `Est. hours: ${task.estimatedHours}h → ${n}h`;84 break;85 }86 case "completedHours": {87 const n = Math.min(task.completedHours + 1, task.estimatedHours);88 rows = rows.map((t) => (t.id === task.id ? { ...t, completedHours: n } : t));89 clickInfo = `Done hours: ${task.completedHours}h → ${n}h`;90 break;91 }92 default:93 clickInfo = `Clicked [${accessor}] = "${value}" (row ${rowIndex})`;94 }95 }96</script>9798<div style="display: flex; flex-direction: column; gap: 16px">99 <div style="padding: 12px; background-color: {isDark ? '#374151' : '#f3f4f6'}; border-radius: 8px; border: 1px solid {isDark ? '#4b5563' : '#d1d5db'}; min-height: 48px; display: flex; align-items: center;">100 <strong style="margin-right: 8px; color: {isDark ? '#f9fafb' : '#1f2937'}">Last Click:</strong>101 <span style="color: {isDark ? '#d1d5db' : '#4b5563'}">{clickInfo || "Click any cell to see interaction details..."}</span>102 </div>103104 {#if selectedTask}105 <div style="position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000">106 <div style="background: {isDark ? '#1f2937' : 'white'}; padding: 24px; border-radius: 8px; max-width: 500px; width: 90%;">107 <h3 style="margin: 0 0 16px; color: {isDark ? '#f9fafb' : '#1f2937'}">Task Details</h3>108 <p style="margin: 8px 0; color: {isDark ? '#d1d5db' : '#4b5563'}"><strong>Task:</strong> {selectedTask.task}</p>109 <p style="margin: 8px 0; color: {isDark ? '#d1d5db' : '#4b5563'}"><strong>Details:</strong> {selectedTask.details}</p>110 <p style="margin: 8px 0; color: {isDark ? '#d1d5db' : '#4b5563'}"><strong>Assignee:</strong> {selectedTask.assignee}</p>111 <p style="margin: 8px 0; color: {isDark ? '#d1d5db' : '#4b5563'}"><strong>Status:</strong> {selectedTask.status}</p>112 <p style="margin: 8px 0; color: {isDark ? '#d1d5db' : '#4b5563'}"><strong>Priority:</strong> {selectedTask.priority}</p>113 <button114 onclick={() => selectedTask = null}115 style="margin-top: 16px; background: #3b82f6; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-weight: bold"116 >117 Close118 </button>119 </div>120 </div>121 {/if}122123 <SimpleTable124 columnResizing={true}125 defaultHeaders={headers}126 {height}127 onCellClick={handleCellClick}128 rows={rows}129 {theme}130 />131</div>132
Solid TSX
Copy
1import { createSignal, createMemo, Show, For } from "solid-js";2import {SimpleTable} from "@simple-table/solid";import type { Theme, SolidHeaderObject, CellRendererProps, CellClickProps } from "@simple-table/solid";3import { cellClickingHeaders, cellClickingData, CELL_CLICKING_STATUSES } from "./cell-clicking.demo-data";4import type { ProjectTask } from "./cell-clicking.demo-data";5import "@simple-table/solid/styles.css";67const DETAIL_KEYS = ["task", "details", "assignee", "status", "priority"] as const;89export default function CellClickingDemo(props: { height?: string | number; theme?: Theme }) {10 const [clickInfo, setClickInfo] = createSignal("");11 const [selectedTask, setSelectedTask] = createSignal<ProjectTask | null>(null);12 const [rows, setRows] = createSignal<ProjectTask[]>([...cellClickingData]);1314 const headers: SolidHeaderObject[] = cellClickingHeaders.map((h) => {15 if (h.accessor === "priority") {16 return {17 ...h,18 cellRenderer: (cr: CellRendererProps) => {19 const p = String(cr.row.priority);20 const color = p === "High" ? "#ef4444" : p === "Medium" ? "#f59e0b" : "#10b981";21 return (22 <span style={{ color, "font-weight": "bold", cursor: "pointer" }} title="Click to filter">23 {p}24 </span>25 );26 },27 };28 }29 if (h.accessor === "status") {30 return {31 ...h,32 cellRenderer: (cr: CellRendererProps) => {33 const s = String(cr.row.status);34 const bg = s === "Completed" ? "#dcfce7" : s === "In Progress" ? "#fef3c7" : "#fee2e2";35 const c = s === "Completed" ? "#166534" : s === "In Progress" ? "#92400e" : "#991b1b";36 return (37 <span38 style={{39 background: bg,40 color: c,41 padding: "4px 8px",42 "border-radius": "4px",43 "font-size": "12px",44 "font-weight": "bold",45 cursor: "pointer",46 }}47 title="Click to change"48 >49 {s}50 </span>51 );52 },53 };54 }55 if (h.accessor === "details") {56 return {57 ...h,58 cellRenderer: () => (59 <button60 type="button"61 style={{62 background: "#3b82f6",63 color: "white",64 border: "none",65 padding: "6px 12px",66 "border-radius": "4px",67 cursor: "pointer",68 "font-size": "12px",69 "font-weight": "bold",70 }}71 >72 View Details73 </button>74 ),75 };76 }77 return h;78 });7980 const isDark = createMemo(() => props.theme === "modern-dark" || props.theme === "dark");8182 const handleCellClick = ({ accessor, rowIndex, value, row }: CellClickProps) => {83 const task = row as ProjectTask;84 switch (accessor) {85 case "priority":86 setClickInfo(`Filtering by ${value} priority`);87 setRows(cellClickingData.filter((t) => t.priority === value));88 break;89 case "status": {90 const idx = CELL_CLICKING_STATUSES.indexOf(String(value));91 const next = CELL_CLICKING_STATUSES[(idx + 1) % CELL_CLICKING_STATUSES.length];92 setRows((prev) => prev.map((t) => (t.id === task.id ? { ...t, status: next } : t)));93 setClickInfo(`Status: "${value}" → "${next}"`);94 break;95 }96 case "details":97 setSelectedTask(task);98 setClickInfo(`Opening details for: ${task.task}`);99 break;100 case "estimatedHours": {101 const n = Math.min(task.estimatedHours + 2, 40);102 setRows((prev) => prev.map((t) => (t.id === task.id ? { ...t, estimatedHours: n } : t)));103 setClickInfo(`Est. hours: ${task.estimatedHours}h → ${n}h`);104 break;105 }106 case "completedHours": {107 const n = Math.min(task.completedHours + 1, task.estimatedHours);108 setRows((prev) => prev.map((t) => (t.id === task.id ? { ...t, completedHours: n } : t)));109 setClickInfo(`Done hours: ${task.completedHours}h → ${n}h`);110 break;111 }112 default:113 setClickInfo(`Clicked [${accessor}] = "${value}" (row ${rowIndex})`);114 }115 };116117 return (118 <div style={{ display: "flex", "flex-direction": "column", gap: "16px" }}>119 <div120 style={{121 padding: "12px",122 "background-color": isDark() ? "#374151" : "#f3f4f6",123 "border-radius": "8px",124 border: `1px solid ${isDark() ? "#4b5563" : "#d1d5db"}`,125 "min-height": "48px",126 display: "flex",127 "align-items": "center",128 }}129 >130 <strong style={{ "margin-right": "8px", color: isDark() ? "#f9fafb" : "#1f2937" }}>Last Click:</strong>131 <span style={{ color: isDark() ? "#d1d5db" : "#4b5563" }}>132 {clickInfo() || "Click any cell to see interaction details..."}133 </span>134 </div>135136 <Show when={selectedTask()}>137 {(task) => (138 <div139 style={{140 position: "fixed",141 inset: "0",142 background: "rgba(0,0,0,0.5)",143 display: "flex",144 "align-items": "center",145 "justify-content": "center",146 "z-index": 1000,147 }}148 >149 <div150 style={{151 background: isDark() ? "#1f2937" : "white",152 padding: "24px",153 "border-radius": "8px",154 "max-width": "500px",155 width: "90%",156 }}157 >158 <h3 style={{ margin: "0 0 16px", color: isDark() ? "#f9fafb" : "#1f2937" }}>Task Details</h3>159 <For each={DETAIL_KEYS}>160 {(key) => (161 <p style={{ margin: "8px 0", color: isDark() ? "#d1d5db" : "#4b5563" }}>162 <strong>{key.charAt(0).toUpperCase() + key.slice(1)}:</strong> {String(task()[key])}163 </p>164 )}165 </For>166 <button167 type="button"168 onClick={() => setSelectedTask(null)}169 style={{170 "margin-top": "16px",171 background: "#3b82f6",172 color: "white",173 border: "none",174 padding: "8px 16px",175 "border-radius": "4px",176 cursor: "pointer",177 "font-weight": "bold",178 }}179 >180 Close181 </button>182 </div>183 </div>184 )}185 </Show>186187 <SimpleTable188 columnResizing189 defaultHeaders={headers}190 height={props.height ?? "320px"}191 onCellClick={handleCellClick}192 rows={rows()}193 theme={props.theme}194 />195 </div>196 );197}
TypeScriptCellClickingDemo.ts
Copy
1import { SimpleTableVanilla } from "simple-table-core";2import type { Theme, HeaderObject, CellClickProps } from "simple-table-core";3import {4 cellClickingHeaders,5 cellClickingData,6 CELL_CLICKING_STATUSES,7} from "./cell-clicking.demo-data";8import type { ProjectTask } from "./cell-clicking.demo-data";9import "simple-table-core/styles.css";1011export function renderCellClickingDemo(12 container: HTMLElement,13 options?: { height?: string | number; theme?: Theme },14): SimpleTableVanilla {15 const isDark = options?.theme === "modern-dark" || options?.theme === "dark";1617 const wrapper = document.createElement("div");18 wrapper.style.cssText = "display:flex;flex-direction:column;gap:16px";1920 const banner = document.createElement("div");21 banner.style.cssText = `padding:12px;background:${isDark ? "#374151" : "#f3f4f6"};border-radius:8px;border:1px solid ${isDark ? "#4b5563" : "#d1d5db"};min-height:48px;display:flex;align-items:center`;22 banner.innerHTML = `<strong style="margin-right:8px;color:${isDark ? "#f9fafb" : "#1f2937"}">Last Click:</strong><span style="color:${isDark ? "#d1d5db" : "#4b5563"}">Click any cell to see interaction details...</span>`;23 wrapper.appendChild(banner);2425 const overlay = document.createElement("div");26 overlay.style.cssText =27 "position:fixed;inset:0;background:rgba(0,0,0,0.5);display:none;align-items:center;justify-content:center;z-index:1000";28 wrapper.appendChild(overlay);2930 const tableContainer = document.createElement("div");31 wrapper.appendChild(tableContainer);32 container.appendChild(wrapper);3334 let rows: ProjectTask[] = [...cellClickingData];3536 const headers: HeaderObject[] = cellClickingHeaders.map((h) => {37 if (h.accessor === "priority") {38 return {39 ...h,40 cellRenderer: ({ row }: { row: Record<string, unknown> }) => {41 const p = String(row.priority);42 const color = p === "High" ? "#ef4444" : p === "Medium" ? "#f59e0b" : "#10b981";43 const el = document.createElement("span");44 Object.assign(el.style, { color, fontWeight: "bold", cursor: "pointer" });45 el.title = "Click to filter by priority";46 el.textContent = p;47 return el;48 },49 };50 }51 if (h.accessor === "status") {52 return {53 ...h,54 cellRenderer: ({ row }: { row: Record<string, unknown> }) => {55 const s = String(row.status);56 const bg = s === "Completed" ? "#dcfce7" : s === "In Progress" ? "#fef3c7" : "#fee2e2";57 const c = s === "Completed" ? "#166534" : s === "In Progress" ? "#92400e" : "#991b1b";58 const el = document.createElement("span");59 Object.assign(el.style, {60 background: bg,61 color: c,62 padding: "4px 8px",63 borderRadius: "4px",64 fontSize: "12px",65 fontWeight: "bold",66 cursor: "pointer",67 });68 el.title = "Click to change status";69 el.textContent = s;70 return el;71 },72 };73 }74 if (h.accessor === "details") {75 return {76 ...h,77 cellRenderer: () => {78 const btn = document.createElement("button");79 Object.assign(btn.style, {80 background: "#3b82f6",81 color: "white",82 border: "none",83 padding: "6px 12px",84 borderRadius: "4px",85 cursor: "pointer",86 fontSize: "12px",87 fontWeight: "bold",88 });89 btn.textContent = "View Details";90 btn.addEventListener("mouseover", () => {91 btn.style.backgroundColor = "#2563eb";92 });93 btn.addEventListener("mouseout", () => {94 btn.style.backgroundColor = "#3b82f6";95 });96 return btn;97 },98 };99 }100 return { ...h };101 });102103 function showModal(task: ProjectTask) {104 overlay.innerHTML = `<div style="background:${isDark ? "#1f2937" : "white"};padding:24px;border-radius:8px;max-width:500px;width:90%">105 <h3 style="margin:0 0 16px;color:${isDark ? "#f9fafb" : "#1f2937"}">Task Details</h3>106 <p style="margin:8px 0;color:${isDark ? "#d1d5db" : "#4b5563"}"><strong>Task:</strong> ${task.task}</p>107 <p style="margin:8px 0;color:${isDark ? "#d1d5db" : "#4b5563"}"><strong>Details:</strong> ${task.details}</p>108 <p style="margin:8px 0;color:${isDark ? "#d1d5db" : "#4b5563"}"><strong>Assignee:</strong> ${task.assignee}</p>109 <p style="margin:8px 0;color:${isDark ? "#d1d5db" : "#4b5563"}"><strong>Status:</strong> ${task.status}</p>110 <p style="margin:8px 0;color:${isDark ? "#d1d5db" : "#4b5563"}"><strong>Priority:</strong> ${task.priority}</p>111 <button style="margin-top:16px;background:#3b82f6;color:white;border:none;padding:8px 16px;border-radius:4px;cursor:pointer;font-weight:bold" id="close-modal">Close</button>112 </div>`;113 overlay.style.display = "flex";114 overlay.querySelector("#close-modal")?.addEventListener("click", () => {115 overlay.style.display = "none";116 });117 }118119 function updateBanner(msg: string) {120 const span = banner.querySelector("span");121 if (span) span.textContent = msg;122 }123124 const table = new SimpleTableVanilla(tableContainer, {125 defaultHeaders: headers,126 rows,127 height: options?.height ?? "320px",128 theme: options?.theme,129 columnResizing: true,130 onCellClick: ({ accessor, rowIndex, value, row }: CellClickProps) => {131 const task = row as ProjectTask;132 switch (accessor) {133 case "priority":134 updateBanner(`Filtering by ${value} priority`);135 rows = cellClickingData.filter((t) => t.priority === value);136 table.update({ rows });137 break;138 case "status": {139 const idx = CELL_CLICKING_STATUSES.indexOf(String(value));140 const next = CELL_CLICKING_STATUSES[(idx + 1) % CELL_CLICKING_STATUSES.length];141 rows = rows.map((t) => (t.id === task.id ? { ...t, status: next } : t));142 table.update({ rows });143 updateBanner(`Status: "${value}" → "${next}"`);144 break;145 }146 case "details":147 showModal(task);148 updateBanner(`Opening details for: ${task.task}`);149 break;150 case "estimatedHours": {151 const n = Math.min(task.estimatedHours + 2, 40);152 rows = rows.map((t) => (t.id === task.id ? { ...t, estimatedHours: n } : t));153 table.update({ rows });154 updateBanner(`Est. hours: ${task.estimatedHours}h → ${n}h`);155 break;156 }157 case "completedHours": {158 const n = Math.min(task.completedHours + 1, task.estimatedHours);159 rows = rows.map((t) => (t.id === task.id ? { ...t, completedHours: n } : t));160 table.update({ rows });161 updateBanner(`Done hours: ${task.completedHours}h → ${n}h`);162 break;163 }164 default:165 updateBanner(`Clicked [${accessor}] = "${value}" (row ${rowIndex})`);166 }167 },168 });169170 return table;171}172173174// cell-clicking.demo-data.ts175// Self-contained demo table setup for this example.176import type { HeaderObject, Row } from "simple-table-core";177178179export type ProjectTask = {180 id: number;181 task: string;182 assignee: string;183 priority: string;184 status: string;185 dueDate: string;186 estimatedHours: number;187 completedHours: number;188 details: string;189};190191export const STATUSES = ["Not Started", "In Progress", "Completed"];192193export const cellClickingData: ProjectTask[] = [194 { id: 1001, task: "Design login page mockups", assignee: "Sarah Chen", priority: "High", status: "In Progress", dueDate: "2024-02-15", estimatedHours: 8, completedHours: 5, details: "Create responsive login page designs with modern UI patterns" },195 { id: 1002, task: "Implement user authentication API", assignee: "Marcus Rodriguez", priority: "High", status: "Not Started", dueDate: "2024-02-20", estimatedHours: 16, completedHours: 0, details: "Build secure JWT-based authentication system with OAuth integration" },196 { id: 1003, task: "Write unit tests for payment module", assignee: "Luna Martinez", priority: "Medium", status: "Completed", dueDate: "2024-02-10", estimatedHours: 12, completedHours: 12, details: "Comprehensive test coverage for payment processing functionality" },197 { id: 1004, task: "Update documentation for API endpoints", assignee: "Kai Thompson", priority: "Low", status: "In Progress", dueDate: "2024-02-25", estimatedHours: 6, completedHours: 3, details: "Update Swagger documentation and add usage examples" },198 { id: 1005, task: "Performance optimization for dashboard", assignee: "Zara Kim", priority: "Medium", status: "Not Started", dueDate: "2024-03-01", estimatedHours: 20, completedHours: 0, details: "Optimize rendering performance and implement lazy loading" },199 { id: 1006, task: "Mobile responsiveness testing", assignee: "Tyler Anderson", priority: "High", status: "In Progress", dueDate: "2024-02-18", estimatedHours: 10, completedHours: 7, details: "Test application across various mobile devices and screen sizes" },200 { id: 1007, task: "Setup CI/CD pipeline", assignee: "Phoenix Lee", priority: "Medium", status: "Completed", dueDate: "2024-02-08", estimatedHours: 14, completedHours: 14, details: "Automated testing and deployment pipeline using GitHub Actions" },201 { id: 1008, task: "Database migration scripts", assignee: "River Jackson", priority: "Low", status: "Not Started", dueDate: "2024-02-28", estimatedHours: 8, completedHours: 0, details: "Create migration scripts for database schema updates" },202];203204export const cellClickingHeaders: HeaderObject[] = [205 { accessor: "id", label: "Task ID", width: 80, isSortable: true, type: "number" },206 { accessor: "task", label: "Task Name", minWidth: 150, width: "1fr", isSortable: true, type: "string" },207 { accessor: "assignee", label: "Assignee", width: 120, isSortable: true, type: "string" },208 { accessor: "priority", label: "Priority", width: 100, isSortable: true, type: "string" },209 { accessor: "status", label: "Status", width: 120, isSortable: true, type: "string" },210 { accessor: "dueDate", label: "Due Date", width: 120, isSortable: true, type: "date" },211 { accessor: "estimatedHours", label: "Est. Hours", width: 100, isSortable: true, type: "number" },212 { accessor: "completedHours", label: "Done Hours", width: 100, isSortable: true, type: "number" },213 { accessor: "details", label: "View Details", width: 120, type: "other" },214];215216export const cellClickingConfig = {217 headers: cellClickingHeaders,218 rows: cellClickingData,219} as const;220221export { STATUSES as CELL_CLICKING_STATUSES };222
API Reference
Cell Clicking Props
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
onCellClick | Optional | Callback function triggered when any cell is clicked. Provides comprehensive information about the clicked cell including its position, value, and containing row data. |