Skip to content

Support merge/col span cells horizontal in grid and hide a column #4050

Open
lassopicasso wants to merge 8 commits intomainfrom
support-merge/colSpan-cells-horizontal-in-grid
Open

Support merge/col span cells horizontal in grid and hide a column #4050
lassopicasso wants to merge 8 commits intomainfrom
support-merge/colSpan-cells-horizontal-in-grid

Conversation

@lassopicasso
Copy link
Contributor

@lassopicasso lassopicasso commented Mar 9, 2026

Description

In this PR we extended GridComponent.tsx to support;

  • - colSpan on both text/label and component cells, so a cell can span multiple columns.
  • - Column-level hidden, so entire columns can be hidden based on expressions.

What is implemented

  1. colSpan support for grid cells
    We added colSpan to IGridColumnProperties in Common.ts
new CG.prop(
        'colSpan',
        new CG.expr(ExprVal.Number)
          .optional()
          .setTitle('Column span')
          .setDescription('Number of columns this cell should span. Defaults to 1 if not set.'),
      ),

This makes it possible to set colSpan directly on the cell: { "text": "Header 1", "colSpan": 2 } or via "gridColumnOptions": { "colSpan": 2 }

colSpan: We merge header columnOptions, per‑cell gridColumnOptions, and any cell‑level colSpan into columnStyleOptions, then evaluate columnStyleOptions.colSpan with useEvalExpression and pass the result as the <th>/<td> colSpan so both text/label and component cells can span multiple columns.

  1. Column-level hidden support
    We added a hidden property to IGridColumnProperties, aligned with ITableColumnProperties.hidden:
new CG.prop(
        'hidden',
        new CG.expr(ExprVal.Boolean)
          .optional()
          .setTitle('Hidden column')
          .setDescription(
            'Expression or boolean indicating whether this column should be hidden. Defaults to false if not set.',
          ),
      ),

This allows header cells to define visibility: "text": "heading2","gridColumnOptions": { "hidden": false }

  • Added also extraction helper in Grid/tools.ts called getGridCellHiddenExpr small helper that It returns the cell’s hidden expression, using gridColumnOptions.hidden if present, otherwise columnOptions.hidden.

NOTE:

  • This will not merge until we add docs (The docs PR will be fixed after review and it will be linked here to merge this PR and the docs one together)

After:

Screen.Recording.2026-03-17.at.12.47.48.mov

Related Issue(s)

Verification/QA

  • Manual functionality testing
    • I have tested these changes manually
    • Creator of the original issue (or service owner) has been contacted for manual testing (or will be contacted when released in alpha)
    • No testing done/necessary
  • Automated tests
    • Unit test(s) have been added/updated
    • Cypress E2E test(s) have been added/updated
    • No automatic tests are needed here (no functional changes/additions)
    • I want someone to help me make some tests
  • UU/WCAG (follow these guidelines until we have our own)
    • I have tested with a screen reader/keyboard navigation/automated wcag validator
    • No testing done/necessary (no DOM/visual changes)
    • I want someone to help me perform accessibility testing
  • User documentation @ altinn-studio-docs
    • Has been added/updated
    • No functionality has been changed/added, so no documentation is needed
    • I will do that later/have created an issue
  • Support in Altinn Studio
    • Issue(s) created for support in Studio
    • This change/feature does not require any changes to Altinn Studio
  • Sprint board
    • The original issue (or this PR itself) has been added to the Team Apps project and to the current sprint board
    • I don't have permissions to do that, please help me out
  • Labels
    • I have added a kind/* and backport* label to this PR for proper release notes grouping
    • I don't have permissions to add labels, please help me out

Summary by CodeRabbit

  • New Features

    • Grid columns now support dynamic hiding through expressions and boolean conditions for conditional visibility.
    • Grid columns now support configurable column spans for flexible multi-column cell layouts.
  • Tests

    • Added comprehensive test coverage for grid column hiding and column span functionality.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 9, 2026

📝 Walkthrough

Walkthrough

Adds a new public type for grid column properties and wires expression-driven column hiding and colSpan into the Grid implementation and tests. Grid components now accept gridColumnOptions instead of extending table column properties; utility and tests support hidden/colSpan evaluation.

Changes

Cohort / File(s) Summary
Type definitions
src/codegen/Common.ts
Adds public IGridColumnProperties (colSpan, hidden) and replaces several Grid-related declarations' inheritance from ITableColumnProperties with an optional gridColumnOptions: IGridColumnProperties.
Grid implementation
src/layout/Grid/GridComponent.tsx
Introduces expression evaluation for column hidden and colSpan, propagates hidden column indices to row renderer, merges gridColumnOptions into cell settings, and applies evaluated colSpan to grid cells.
Grid utilities
src/layout/Grid/tools.ts
Adds getGridCellHiddenExpr(cell: GridCell) which selects gridColumnOptions.hidden (falling back to columnOptions.hidden) and guards invalid inputs.
Tests — Grid behavior
src/layout/Grid/GridComponent.test.tsx, src/layout/Grid/tools.test.ts
Adds tests for column hiding logic (including invalid expressions), precedence of gridColumnOptions.hidden, and colSpan behavior for text and component cells.
Release script tweak
.github/scripts/release.sh
Normalizes APP_FULL to lowercase when deriving from git tag.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive A minor change to the release.sh script that normalizes APP_FULL to lowercase appears unrelated to grid feature development and may represent out-of-scope changes. Clarify the purpose of the APP_FULL normalization change in release.sh and verify it is necessary for this PR or if it should be separated into a different PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main changes: adding colSpan support for grid cells and column-level hidden functionality.
Description check ✅ Passed The PR description provides clear explanations of both features implemented, includes code examples, addresses the linked issue, and follows the repository template structure.
Linked Issues check ✅ Passed The PR successfully implements colSpan and column-level hidden support as required by issue #1187, enabling cells to span multiple columns and columns to be hidden via expressions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch support-merge/colSpan-cells-horizontal-in-grid
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@JamalAlabdullah JamalAlabdullah changed the title Support merge/col span cells horizontal in grid Support merge/col span cells horizontal in grid and hide a column Mar 16, 2026
@JamalAlabdullah JamalAlabdullah added kind/product-feature Pull requests containing new features backport-ignore This PR is a new feature and should not be cherry-picked onto release branches labels Mar 17, 2026
@JamalAlabdullah JamalAlabdullah marked this pull request as ready for review March 17, 2026 11:57
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/layout/Grid/tools.ts (1)

109-117: Consider reducing type casting by leveraging existing type guards.

The function uses as type casting which the coding guidelines recommend avoiding. Since all non-null GridCell variants (GridCellText, GridCellLabelFrom, GridComponentRef) have the same optional columnOptions and gridColumnOptions properties, you could use the existing type guards to narrow the type.

♻️ Suggested refactor using type guards
 export function getGridCellHiddenExpr(cell: GridCell) {
   if (!cell || typeof cell !== 'object') {
     return undefined;
   }
-  const options =
-    'columnOptions' in cell ? (cell as { columnOptions?: { hidden?: unknown } }).columnOptions : undefined;
-  const gridOpts =
-    'gridColumnOptions' in cell ? (cell as { gridColumnOptions?: { hidden?: unknown } }).gridColumnOptions : undefined;
-  return gridOpts?.hidden ?? options?.hidden;
+  if (isGridCellText(cell) || isGridCellLabelFrom(cell) || isGridCellNode(cell)) {
+    return cell.gridColumnOptions?.hidden ?? cell.columnOptions?.hidden;
+  }
+  return undefined;
 }

As per coding guidelines: "Avoid using any or type casting (as type) in TypeScript; instead, improve typing by removing such casts and anys to maintain proper type safety."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/layout/Grid/tools.ts` around lines 109 - 117, getGridCellHiddenExpr
currently uses 'as' casts to access columnOptions/gridColumnOptions; instead use
the existing type guards to narrow GridCell (e.g., isGridCellText,
isGridCellLabelFrom, isGridComponentRef or whatever project's guards are named)
and then read cell.columnOptions and cell.gridColumnOptions directly without
casting, returning gridColumnOptions?.hidden ?? columnOptions?.hidden; remove
the two '(cell as {...})' casts and the initial typeof check since the type
guards handle null/undefined checks.
src/layout/Grid/GridComponent.tsx (2)

255-260: Apply the same optional chaining and type safety improvements here.

This section has the same pattern as the text/label cell handling and should be refactored similarly.

♻️ Suggested refactor
-        const cellColSpan = (cell as { colSpan?: number } | null)?.colSpan;
-        if (cellColSpan !== undefined) {
+        const cellColSpan = cell && 'colSpan' in cell ? cell.colSpan : undefined;
+        if (cellColSpan !== undefined) {
           componentCellSettings = componentCellSettings
             ? { ...componentCellSettings, colSpan: cellColSpan }
             : { colSpan: cellColSpan };
         }

As per coding guidelines: "Avoid using any or type casting (as type) in TypeScript."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/layout/Grid/GridComponent.tsx` around lines 255 - 260, The code uses a
type cast and manual null checks for colSpan; change to use optional chaining
and a proper narrow type or type guard instead of "as". Replace the current
block in GridComponent (where cellColSpan and componentCellSettings are set)
with a check using cell?.colSpan (e.g., if (cell?.colSpan !== undefined) {
componentCellSettings = componentCellSettings ? { ...componentCellSettings,
colSpan: cell.colSpan } : { colSpan: cell.colSpan }; }) or introduce a
CellWithColSpan interface and a small type guard function to ensure type-safe
access to colSpan, avoiding any casts and preserving the existing merge logic
for componentCellSettings.

203-208: Use optional chaining and reduce type casting.

The SonarCloud analysis correctly identifies that optional chaining would be cleaner. Additionally, the as type casting could be avoided by using a more type-safe approach.

♻️ Suggested refactor
-          const cellWithColSpan = cell as { colSpan?: number } | null;
-          if (cellWithColSpan && cellWithColSpan.colSpan !== undefined) {
-            textCellSettings = textCellSettings
-              ? { ...textCellSettings, colSpan: cellWithColSpan.colSpan }
-              : { colSpan: cellWithColSpan.colSpan };
-          }
+          const cellColSpan = 'colSpan' in cell ? cell.colSpan : undefined;
+          if (cellColSpan !== undefined) {
+            textCellSettings = textCellSettings
+              ? { ...textCellSettings, colSpan: cellColSpan }
+              : { colSpan: cellColSpan };
+          }

As per coding guidelines: "Avoid using any or type casting (as type) in TypeScript."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/layout/Grid/GridComponent.tsx` around lines 203 - 208, Replace the ad-hoc
type cast and manual null check with a type-safe user-defined type guard and
optional chaining: add a small type guard function (e.g., hasColSpan(obj:
unknown): obj is { colSpan?: number }) that checks obj is non-null object and
has the 'colSpan' property, then use if (hasColSpan(cell) && cell.colSpan !==
undefined) { textCellSettings = textCellSettings ? { ...textCellSettings,
colSpan: cell.colSpan } : { colSpan: cell.colSpan }; } so you remove the "as"
cast and use optional access via the guard when updating textCellSettings in
GridComponent (reference symbols: hasColSpan, textCellSettings, cell).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/layout/Grid/GridComponent.tsx`:
- Around line 255-260: The code uses a type cast and manual null checks for
colSpan; change to use optional chaining and a proper narrow type or type guard
instead of "as". Replace the current block in GridComponent (where cellColSpan
and componentCellSettings are set) with a check using cell?.colSpan (e.g., if
(cell?.colSpan !== undefined) { componentCellSettings = componentCellSettings ?
{ ...componentCellSettings, colSpan: cell.colSpan } : { colSpan: cell.colSpan };
}) or introduce a CellWithColSpan interface and a small type guard function to
ensure type-safe access to colSpan, avoiding any casts and preserving the
existing merge logic for componentCellSettings.
- Around line 203-208: Replace the ad-hoc type cast and manual null check with a
type-safe user-defined type guard and optional chaining: add a small type guard
function (e.g., hasColSpan(obj: unknown): obj is { colSpan?: number }) that
checks obj is non-null object and has the 'colSpan' property, then use if
(hasColSpan(cell) && cell.colSpan !== undefined) { textCellSettings =
textCellSettings ? { ...textCellSettings, colSpan: cell.colSpan } : { colSpan:
cell.colSpan }; } so you remove the "as" cast and use optional access via the
guard when updating textCellSettings in GridComponent (reference symbols:
hasColSpan, textCellSettings, cell).

In `@src/layout/Grid/tools.ts`:
- Around line 109-117: getGridCellHiddenExpr currently uses 'as' casts to access
columnOptions/gridColumnOptions; instead use the existing type guards to narrow
GridCell (e.g., isGridCellText, isGridCellLabelFrom, isGridComponentRef or
whatever project's guards are named) and then read cell.columnOptions and
cell.gridColumnOptions directly without casting, returning
gridColumnOptions?.hidden ?? columnOptions?.hidden; remove the two '(cell as
{...})' casts and the initial typeof check since the type guards handle
null/undefined checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67925669-d359-425d-a239-078444973770

📥 Commits

Reviewing files that changed from the base of the PR and between 5e931c9 and 912170e.

📒 Files selected for processing (5)
  • src/codegen/Common.ts
  • src/layout/Grid/GridComponent.test.tsx
  • src/layout/Grid/GridComponent.tsx
  • src/layout/Grid/tools.test.ts
  • src/layout/Grid/tools.ts

@JamalAlabdullah JamalAlabdullah added the squad/utforming Issues that belongs to the named squad. label Mar 17, 2026
@JamalAlabdullah JamalAlabdullah moved this to 🔎 In review in Team Altinn Studio Mar 17, 2026
@JamalAlabdullah
Copy link
Contributor

/publish

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

PR release:

  • <link rel="stylesheet" type="text/css" href="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.912170e2/altinn-app-frontend.css">
  • <script src="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.912170e2/altinn-app-frontend.js"></script>

⚙️ Building...
❌ Failed: https://github.com/Altinn/app-frontend-react/actions/runs/23296570841

@github-actions
Copy link
Contributor

PR release:

  • <link rel="stylesheet" type="text/css" href="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.912170e2/altinn-app-frontend.css">
  • <script src="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.912170e2/altinn-app-frontend.js"></script>

⚙️ Building...

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

PR release:

  • <link rel="stylesheet" type="text/css" href="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.912170e2/altinn-app-frontend.css">
  • <script src="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.912170e2/altinn-app-frontend.js"></script>

⚙️ Building...
❌ Failed: https://github.com/Altinn/app-frontend-react/actions/runs/23296570841

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/scripts/release.sh:
- Line 55: The revert script fails validation for pre-release tags because it
doesn't lowercase APP_FULL like release.sh does; update revert.sh to normalize
APP_FULL to lowercase (e.g., APP_FULL=$(echo "$APP_FULL" | tr '[:upper:]'
'[:lower:]')) before the VERSION_REGEX check so pre-release identifiers (like
RC1 -> rc1) match the expected `[a-z0-9.\-]+` pattern and revert logic that uses
APP_FULL will behave identically to release.sh.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3b6945e3-6f8f-4875-9916-cbb56211666f

📥 Commits

Reviewing files that changed from the base of the PR and between 912170e and 8b3b5f3.

📒 Files selected for processing (1)
  • .github/scripts/release.sh

APP_FULL=${CURRENT_VERSION:1}
APP_MAJOR=${CURRENT_VERSION_PARTS[0]:1}
APP_MAJOR_MINOR=${CURRENT_VERSION_PARTS[0]:1}.${CURRENT_VERSION_PARTS[1]}
APP_FULL=$(echo "$APP_FULL" | tr '[:upper:]' '[:lower:]')
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify that revert.sh does not normalize APP_FULL to lowercase
rg -n 'APP_FULL' .github/scripts/revert.sh

Repository: Altinn/app-frontend-react

Length of output: 513


🏁 Script executed:

# Check release.sh around line 55 and VERSION_REGEX definition
sed -n '50,75p' .github/scripts/release.sh | cat -n

Repository: Altinn/app-frontend-react

Length of output: 1091


🏁 Script executed:

# Check revert.sh VERSION_REGEX definition and full context around APP_FULL assignment
sed -n '35,60p' .github/scripts/revert.sh | cat -n

Repository: Altinn/app-frontend-react

Length of output: 1074


🏁 Script executed:

# Check how REVERT_TAG is set in revert.sh
sed -n '1,40p' .github/scripts/revert.sh | cat -n

Repository: Altinn/app-frontend-react

Length of output: 1126


Inconsistency with revert.sh will break revert operations for pre-release tags.

This lowercase normalization ensures APP_FULL passes the VERSION_REGEX validation (which expects [a-z0-9.\-]+ for pre-release identifiers), but revert.sh does not apply the same normalization. When reverting a pre-release like v3.0.0-RC1:

  • release.sh normalizes to 3.0.0-rc1 and publishes to that path
  • revert.sh keeps it as 3.0.0-RC1, which fails the VERSION_REGEX check, causing the script to exit with a validation error

Apply the same normalization in revert.sh line 38:

APP_FULL=${REVERT_TAG:1}
+APP_FULL=$(echo "$APP_FULL" | tr '[:upper:]' '[:lower:]')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/scripts/release.sh at line 55, The revert script fails validation
for pre-release tags because it doesn't lowercase APP_FULL like release.sh does;
update revert.sh to normalize APP_FULL to lowercase (e.g., APP_FULL=$(echo
"$APP_FULL" | tr '[:upper:]' '[:lower:]')) before the VERSION_REGEX check so
pre-release identifiers (like RC1 -> rc1) match the expected `[a-z0-9.\-]+`
pattern and revert logic that uses APP_FULL will behave identically to
release.sh.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i will remove it . this is just temporary

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JamalAlabdullah, understood! No worries — if it's temporary, feel free to remove it when it's no longer needed. 👍

@sonarqubecloud
Copy link

@github-actions
Copy link
Contributor

github-actions bot commented Mar 19, 2026

PR release:

  • <link rel="stylesheet" type="text/css" href="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.8b3b5f36/altinn-app-frontend.css">
  • <script src="https://altinncdn.no/toolkits/altinn-app-frontend/4.25.3-pr.4282.colSpan-cells-horizontal-in-grid.8b3b5f36/altinn-app-frontend.js"></script>

⚙️ Building...
✅ Done!

@JamalAlabdullah
Copy link
Contributor

I send a message to ssb to test it , så we wait until they test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-ignore This PR is a new feature and should not be cherry-picked onto release branches kind/product-feature Pull requests containing new features squad/utforming Issues that belongs to the named squad.

Projects

Status: 🔎 In review

Development

Successfully merging this pull request may close these issues.

Grid/rowsBefore/rowsAfter: Støtte for å slå sammen celler (colSpan/rowSpan)

2 participants