Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/deploy-marketing-heroku.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Deploy marketing to Heroku

on:
push:
branches: [main, master]
paths:
- "apps/marketing/**"
- "packages/core/**"
- "packages/react/**"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "turbo.json"
- "Procfile"
workflow_dispatch:

concurrency:
group: heroku-marketing-${{ github.ref }}
cancel-in-progress: true

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Push to Heroku
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
HEROKU_APP_NAME: ${{ secrets.HEROKU_APP_NAME }}
HEROKU_GIT_BRANCH: ${{ vars.HEROKU_GIT_BRANCH }}
run: |
set -euo pipefail
if [ -z "${HEROKU_API_KEY:-}" ] || [ -z "${HEROKU_APP_NAME:-}" ]; then
echo "::notice title=Heroku::Skipping deploy: set repository secrets HEROKU_API_KEY and HEROKU_APP_NAME."
exit 0
fi
BRANCH="${HEROKU_GIT_BRANCH:-main}"
git remote remove heroku 2>/dev/null || true
git remote add heroku "https://heroku:${HEROKU_API_KEY}@git.heroku.com/${HEROKU_APP_NAME}.git"
git push -f heroku "HEAD:refs/heads/${BRANCH}"
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: cd apps/marketing && ./node_modules/.bin/next start -H 0.0.0.0
57 changes: 37 additions & 20 deletions apps/marketing/src/components/pages/ThemeBuilderContent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useMemo, useRef } from "react";
import { Button, Input } from "antd";
import PageWrapper from "@/components/PageWrapper";
import type { Color } from "antd/es/color-picker";
Expand Down Expand Up @@ -381,9 +381,30 @@ export default function ThemeBuilderContent() {
// Track user changes that apply to all themes
const [userChanges, setUserChanges] = useState<Partial<ThemeConfig>>({});

// Non-CSS variable states (these are props passed directly to the table component)
const [rowHeight, setRowHeight] = useState<number>(ROW_HEIGHT);
const [headerHeight, setHeaderHeight] = useState<number>(HEADER_HEIGHT);
// Non-CSS props: keep raw strings so the user can clear the field while editing; derive
// positive pixel values for the table (last valid number while input is empty/invalid).
const [rowHeightInput, setRowHeightInput] = useState(String(ROW_HEIGHT));
const [headerHeightInput, setHeaderHeightInput] = useState(String(HEADER_HEIGHT));
const lastValidRowHeightRef = useRef(ROW_HEIGHT);
const lastValidHeaderHeightRef = useRef(HEADER_HEIGHT);

const rowHeight = useMemo(() => {
const n = parseInt(rowHeightInput, 10);
if (!isNaN(n) && n > 0) {
lastValidRowHeightRef.current = n;
return n;
}
return lastValidRowHeightRef.current;
}, [rowHeightInput]);

const headerHeight = useMemo(() => {
const n = parseInt(headerHeightInput, 10);
if (!isNaN(n) && n > 0) {
lastValidHeaderHeightRef.current = n;
return n;
}
return lastValidHeaderHeightRef.current;
}, [headerHeightInput]);

// The current theme is a combination of the default theme for the current mode + user changes
const [theme, setTheme] = useState<ThemeConfig>(
Expand Down Expand Up @@ -437,18 +458,12 @@ export default function ThemeBuilderContent() {
if (value !== null) {
// Special handling for non-CSS variable props
if (key === "rowHeight") {
const numValue = typeof value === "number" ? value : parseInt(value, 10);
if (!isNaN(numValue) && numValue > 0) {
setRowHeight(numValue);
}
setRowHeightInput(typeof value === "number" ? String(value) : value);
return;
}

if (key === "headerHeight") {
const numValue = typeof value === "number" ? value : parseInt(value, 10);
if (!isNaN(numValue) && numValue > 0) {
setHeaderHeight(numValue);
}
setHeaderHeightInput(typeof value === "number" ? String(value) : value);
return;
}

Expand All @@ -463,8 +478,10 @@ export default function ThemeBuilderContent() {
// Clear all user changes, search, and reset non-CSS props
setUserChanges({});
setSearchQuery("");
setRowHeight(ROW_HEIGHT);
setHeaderHeight(HEADER_HEIGHT);
setRowHeightInput(String(ROW_HEIGHT));
setHeaderHeightInput(String(HEADER_HEIGHT));
lastValidRowHeightRef.current = ROW_HEIGHT;
lastValidHeaderHeightRef.current = HEADER_HEIGHT;
};

const generateCSS = (): string => {
Expand Down Expand Up @@ -1129,10 +1146,10 @@ export default function ThemeBuilderContent() {
// Check if any fields in this config have been modified
const hasModifications = config.fields.some((field) => {
if (field.key === "rowHeight") {
return rowHeight !== ROW_HEIGHT;
return rowHeightInput !== String(ROW_HEIGHT);
}
if (field.key === "headerHeight") {
return headerHeight !== HEADER_HEIGHT;
return headerHeightInput !== String(HEADER_HEIGHT);
}
return userChanges[field.key as keyof ThemeConfig] !== undefined;
});
Expand All @@ -1155,7 +1172,7 @@ export default function ThemeBuilderContent() {
<ThemeInput
key={index}
label={field.label || shortenLabel(field.key.toString())}
value={rowHeight}
value={rowHeightInput}
onChange={(value) => handleValueChange(field.key)(value)}
tooltip={field.tooltip}
/>
Expand All @@ -1166,7 +1183,7 @@ export default function ThemeBuilderContent() {
<ThemeInput
key={index}
label={field.label || shortenLabel(field.key.toString())}
value={headerHeight}
value={headerHeightInput}
onChange={(value) => handleValueChange(field.key)(value)}
tooltip={field.tooltip}
/>
Expand Down Expand Up @@ -1273,8 +1290,8 @@ export default function ThemeBuilderContent() {
// Count modifications (including non-CSS props if changed)
const modificationCount =
Object.keys(userChanges).length +
(rowHeight !== ROW_HEIGHT ? 1 : 0) +
(headerHeight !== HEADER_HEIGHT ? 1 : 0);
(rowHeightInput !== String(ROW_HEIGHT) ? 1 : 0) +
(headerHeightInput !== String(HEADER_HEIGHT) ? 1 : 0);

// Create footer content
const footerContent = (
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "simple-table-monorepo",
"private": true,
"packageManager": "pnpm@10.32.1",
"engines": {
"node": "22.x"
},
"scripts": {
"install:all": "pnpm install",
"build": "turbo run build --filter=!simple-table-marketing",
Expand All @@ -24,13 +27,14 @@
"dev:examples-solid": "pnpm --filter=examples-solid dev",
"dev:examples-angular": "pnpm --filter=examples-angular dev",
"dev:examples-vanilla": "pnpm --filter=examples-vanilla dev",
"build:examples": "pnpm --filter=examples-react --filter=examples-vue --filter=examples-svelte --filter=examples-solid --filter=examples-angular --filter=examples-vanilla build",
"build:examples": "turbo run build --filter=examples-react --filter=examples-vue --filter=examples-svelte --filter=examples-solid --filter=examples-angular --filter=examples-vanilla",
"new-demo": "node scripts/new-demo.mjs",
"verify-examples": "node scripts/verify-examples.mjs",
"generate-stackblitz": "node scripts/generate-stackblitz.mjs",
"generate-stackblitz:test": "node scripts/generate-stackblitz.mjs --demos quick-start,live-update,crm",
"start:core": "turbo run start --filter=simple-table-core",
"test": "turbo run test"
"test": "turbo run test",
"heroku-postbuild": "pnpm run build:marketing"
},
"devDependencies": {
"turbo": "latest"
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@simple-table/angular",
"version": "3.0.0-beta.25",
"version": "3.0.0",
"main": "dist/cjs/index.js",
"module": "dist/index.es.js",
"types": "dist/types/angular/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "simple-table-core",
"version": "3.0.0-beta.25",
"version": "3.0.0",
"main": "dist/cjs/index.js",
"module": "dist/index.es.js",
"types": "dist/src/index.d.ts",
Expand Down
31 changes: 28 additions & 3 deletions packages/core/src/core/SimpleTableVanilla.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SimpleTableConfig } from "../types/SimpleTableConfig";
import { TableAPI } from "../types/TableAPI";
import HeaderObject, { Accessor } from "../types/HeaderObject";
import Row from "../types/Row";
import { CustomTheme } from "../types/CustomTheme";
import { CustomTheme, areCustomThemesEqual } from "../types/CustomTheme";
import RowState from "../types/RowState";

import { AutoScaleManager } from "../managers/AutoScaleManager";
Expand Down Expand Up @@ -600,9 +600,34 @@ export class SimpleTableVanilla {
}

if (config.customTheme !== undefined) {
const previousTheme = this.customTheme;
this.customTheme = TableInitializer.mergeCustomTheme(this.config);
if (this.selectionManager) {
this.selectionManager.updateConfig({ customTheme: this.customTheme });

if (!areCustomThemesEqual(previousTheme, this.customTheme)) {
if (this.selectionManager) {
this.selectionManager.updateConfig({
customTheme: this.customTheme,
rowHeight: this.customTheme.rowHeight,
});
}

this.dimensionManager?.updateConfig({
headerHeight: this.customTheme.headerHeight,
rowHeight: this.customTheme.rowHeight,
footerHeight:
(this.config.shouldPaginate || this.config.footerRenderer) && !this.config.hideFooter
? this.customTheme.footerHeight
: undefined,
});

if (
this.config.shouldPaginate &&
previousTheme.rowHeight !== this.customTheme.rowHeight
) {
this.currentPage = 1;
}

this.renderOrchestrator.invalidateCache("all");
}
}

Expand Down
16 changes: 16 additions & 0 deletions packages/core/src/managers/DimensionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,22 @@ export class DimensionManager {
needsUpdate = true;
}

// Row / header / footer pixel sizes affect header band height and scroll viewport math.
if (
config.rowHeight !== undefined ||
config.headerHeight !== undefined ||
config.footerHeight !== undefined
) {
const calculatedHeaderHeight = this.calculateHeaderHeight(this.state.maxHeaderDepth);
const contentHeight = this.calculateContentHeight();
this.state = {
...this.state,
calculatedHeaderHeight,
contentHeight,
};
needsUpdate = true;
}

if (needsUpdate) {
this.notifySubscribers();
}
Expand Down
2 changes: 0 additions & 2 deletions packages/core/src/styles/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -485,11 +485,9 @@ input {
}
.st-header-cell.st-header-selected .st-header-icon {
fill: var(--st-header-selected-icon-color);
background-color: var(--st-header-selected-icon-color);
}
.st-header-cell.st-header-selected .st-icon-container * {
fill: var(--st-header-selected-icon-color);
background-color: var(--st-header-selected-icon-color);
}
.st-header-cell.st-header-selected .st-icon-container {
color: var(--st-header-selected-icon-color);
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/types/CustomTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,11 @@ export const DEFAULT_CUSTOM_THEME: CustomTheme = {

selectionColumnWidth: 42,
};

/** Shallow value equality — used to skip layout work when React passes a new `customTheme` object with the same numbers. */
export function areCustomThemesEqual(a: CustomTheme, b: CustomTheme): boolean {
for (const key of Object.keys(DEFAULT_CUSTOM_THEME) as (keyof CustomTheme)[]) {
if (a[key] !== b[key]) return false;
}
return true;
}
2 changes: 1 addition & 1 deletion packages/core/styles.css

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions packages/examples/svelte/src/demos/themes/ThemesDemo.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
import "@simple-table/svelte/styles.css";

let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();
let selectedTheme: Theme = $state(theme ?? "light");
let userThemePick = $state<Theme | undefined>(undefined);
const selectedTheme: Theme = $derived(userThemePick ?? theme ?? "light");
</script>

<div>
<div style="display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px">
{#each AVAILABLE_THEMES as t}
<button
onclick={() => (selectedTheme = t.value)}
onclick={() => (userThemePick = t.value)}
style="padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 13px; border: {selectedTheme === t.value ? '2px solid #3b82f6' : '1px solid #d1d5db'}; background: {selectedTheme === t.value ? '#eff6ff' : '#fff'}; color: {selectedTheme === t.value ? '#1d4ed8' : '#374151'}; font-weight: {selectedTheme === t.value ? 600 : 400}"
>
{t.label}
Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@simple-table/react",
"version": "3.0.0-beta.25",
"version": "3.0.0",
"main": "dist/cjs/index.js",
"module": "dist/index.es.js",
"types": "dist/types/react/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/solid/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@simple-table/solid",
"version": "3.0.0-beta.25",
"version": "3.0.0",
"main": "dist/cjs/index.js",
"module": "dist/index.es.js",
"types": "dist/types/solid/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@simple-table/svelte",
"version": "3.0.0-beta.25",
"version": "3.0.0",
"main": "dist/cjs/index.js",
"module": "dist/index.es.js",
"types": "dist/types/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/vue/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@simple-table/vue",
"version": "3.0.0-beta.25",
"version": "3.0.0",
"main": "dist/cjs/index.js",
"module": "dist/index.es.js",
"types": "dist/types/vue/src/index.d.ts",
Expand Down
Loading