Skip to content

Conversation

adamhaeger
Copy link
Contributor

@adamhaeger adamhaeger commented Aug 28, 2025

Description

Reintroduces readOnly support for list component.

List without datamodelbindings now shows a table without checkboxes or radiobuttons.

Result:

Screenshot 2025-08-29 at 10 36 31

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

Created task: Altinn/altinn-studio-docs#2333

  • 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

    • Automatic read-only mode when no data bindings are present: selection controls hidden, row clicks disabled, content remains visible across desktop and mobile.
  • Bug Fixes

    • Prevents unintended selection or data-patch actions when bindings are missing.
  • Tests

    • Added coverage for read-only behavior on desktop and mobile, verifying controls are omitted and data remains visible without persisting selection.
  • Chores

    • No public/exported API changes.

Copy link

coderabbitai bot commented Aug 28, 2025

📝 Walkthrough

Walkthrough

Derives a selection mode from item dataModelBindings in ListComponent and uses it to suppress selection controls and row-click behavior when bindings are missing (readonly). Adds tests verifying no radios/checkboxes render and selection does not persist when dataModelBindings are undefined or empty (desktop and mobile).

Changes

Cohort / File(s) Summary
List selection / readonly logic
src/layout/List/ListComponent.tsx
Adds SelectionMode type and getSelectionMode(bindings); computes selectionMode/readOnly/isMultipleSelection from dataModelBindings; gates rendering of Checkbox/Radio and row click handling when readOnly; updates isRowSelected, handleRowClick, selectedRow logic; removes use of enabled from useSaveObjectToGroup; adjusts readonly row styling.
Readonly tests for missing bindings
src/layout/List/ListComponent.test.tsx
Adds test suite asserting that when component.dataModelBindings is undefined or {}: radios/checkboxes are not rendered, selection does not call setMultiLeafValues, and data remains visible on desktop and mobile; minor comment adjustments for row selection lookup.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Assessment against linked issues

Objective Addressed Explanation
Hide list selection controls when component readOnly: true is set (#3646) Change derives readonly from missing dataModelBindings; does not read or honor an explicit readOnly: true property on the component.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Introduce SelectionMode derived from dataModelBindings (src/layout/List/ListComponent.tsx) The linked issue requests honoring an explicit readOnly: true setting; deriving readonly from absent bindings is not the requested change.
Add tests that assert readonly behavior when dataModelBindings are missing (src/layout/List/ListComponent.test.tsx) Tests validate behavior for missing bindings rather than verifying handling of an explicit readOnly: true component property.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c24a58a and f83f5d6.

📒 Files selected for processing (1)
  • src/layout/List/ListComponent.tsx (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/layout/List/ListComponent.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Type-checks, eslint, unit tests and SonarCloud
  • GitHub Check: Install
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/3646-read-only-list

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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: 5

🧹 Nitpick comments (1)
src/layout/List/ListComponent.tsx (1)

335-336: Nit: class condition already excludes readOnly.

[classes.selectedRowCell]: isRowSelected(row) && !readOnly is fine. Just make sure the readOnly visual state is handled by .readOnlyRow (after enabling CSS). No change required.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ff46fdf and 3401000.

📒 Files selected for processing (3)
  • src/layout/List/ListComponent.module.css (1 hunks)
  • src/layout/List/ListComponent.test.tsx (2 hunks)
  • src/layout/List/ListComponent.tsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Avoid using any and unnecessary type casts (as Type) in TypeScript; prefer precise typings and refactor existing casts/anys
For TanStack Query, use objects to manage query keys and functions, and centralize shared options via queryOptions

Files:

  • src/layout/List/ListComponent.test.tsx
  • src/layout/List/ListComponent.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

In tests, use renderWithProviders from src/test/renderWithProviders.tsx to supply required form layout context

Files:

  • src/layout/List/ListComponent.test.tsx
**/*.module.css

📄 CodeRabbit inference engine (CLAUDE.md)

Use CSS Modules for component styling and follow existing patterns in *.module.css files

Files:

  • src/layout/List/ListComponent.module.css
🧬 Code graph analysis (2)
src/layout/List/ListComponent.test.tsx (1)
src/__mocks__/getLayoutSetsMock.ts (1)
  • defaultDataTypeMock (3-3)
src/layout/List/ListComponent.tsx (2)
src/features/language/Lang.tsx (1)
  • Lang (15-23)
src/components/form/RadioButton.tsx (1)
  • RadioButton (24-104)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Install
  • GitHub Check: Type-checks, eslint, unit tests and SonarCloud
🔇 Additional comments (5)
src/layout/List/ListComponent.test.tsx (1)

228-325: LGTM: solid read-only coverage (desktop + mobile).

Good assertions to ensure no controls render and clicks don’t trigger data updates while content remains visible.

src/layout/List/ListComponent.tsx (4)

52-53: LGTM: adding readOnly prop to item config.

Matches the new behavior tested and gates interactions elsewhere.


100-102: LGTM: short-circuit row clicks in readOnly.

Prevents accidental state changes when readOnly.


268-275: LGTM: hide controls header cell in readOnly.

Keeps column alignment consistent when the control column is omitted.


297-299: Missing style: readOnlyRow has no effect unless CSS is enabled.

className={cn({ [classes.readOnlyRow]: readOnly })} relies on .readOnlyRow which is commented out in the CSS module. Enable the CSS (see CSS review) or drop the class reference.

Copy link

@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: 0

♻️ Duplicate comments (4)
src/layout/List/ListComponent.module.css (1)

108-111: Uncomment or remove dead hover rule.

The readOnlyRow:hover rule is still commented, so hover style remains unchanged. Either enable it or delete to avoid confusion.

-/*.readOnlyRow:hover {*/
-/*  background-color: initial;*/
-/*}*/
+.readOnlyRow:hover {
+  background-color: initial;
+}
src/layout/List/ListComponent.test.tsx (1)

214-216: Fix stale comment (now selecting by accessible name).

The code uses an accessible name, but the comment says the label is empty.

-    // Select the second row - find by value since label is empty
+    // Select the second row by accessible name
src/layout/List/ListComponent.tsx (2)

179-194: Mobile (checkbox): a11y + interaction fixed.

Switched to onChange, added a visually-hidden label, and preserved checked/value. Matches prior guidance.


215-229: Mobile (radio): a11y + interaction fixed.

Uses onChange and provides an accessible label. Consistent with checkbox path.

🧹 Nitpick comments (3)
src/layout/List/ListComponent.test.tsx (2)

228-257: Read-only assertions cover core cases. Add a header-cell assertion for completeness.

Great coverage ensuring no radios/checkboxes and no row selection. Consider also asserting that the controls header cell is omitted in readOnly (desktop).

Example (can be placed in this describe block):

it('should not render the controls header cell in readOnly mode', async () => {
  await render({ component: { readOnly: true } });
  await waitFor(() => expect(screen.getByText('Norway')).toBeInTheDocument());
  expect(screen.queryByText(/list_component\.controlsHeader/i)).not.toBeInTheDocument();
});

274-289: Restore spies after mobile tests to avoid leakage.

jest.spyOn(useDeviceWidths, 'useIsMobile').mockReturnValue(true); is not restored. Add a global afterEach(jest.restoreAllMocks) to prevent cross-test interference.

afterEach(() => {
  jest.restoreAllMocks();
});

Also applies to: 291-306

src/layout/List/ListComponent.tsx (1)

297-299: Expose non-interactive state to AT.

Optionally add aria-disabled on rows when readOnly to communicate the non-interactive state.

-            <Table.Row
+            <Table.Row
               key={JSON.stringify(row)}
               onClick={!readOnly ? () => handleRowClick(row) : undefined}
+              aria-disabled={readOnly || undefined}
               className={cn({ [classes.readOnlyRow]: readOnly })}
             >
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3401000 and 7b1fa1a.

📒 Files selected for processing (3)
  • src/layout/List/ListComponent.module.css (1 hunks)
  • src/layout/List/ListComponent.test.tsx (2 hunks)
  • src/layout/List/ListComponent.tsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Avoid using any and unnecessary type casts (as Type) in TypeScript; prefer precise typings and refactor existing casts/anys
For TanStack Query, use objects to manage query keys and functions, and centralize shared options via queryOptions

Files:

  • src/layout/List/ListComponent.test.tsx
  • src/layout/List/ListComponent.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

In tests, use renderWithProviders from src/test/renderWithProviders.tsx to supply required form layout context

Files:

  • src/layout/List/ListComponent.test.tsx
**/*.module.css

📄 CodeRabbit inference engine (CLAUDE.md)

Use CSS Modules for component styling and follow existing patterns in *.module.css files

Files:

  • src/layout/List/ListComponent.module.css
🧬 Code graph analysis (2)
src/layout/List/ListComponent.test.tsx (1)
src/__mocks__/getLayoutSetsMock.ts (1)
  • defaultDataTypeMock (3-3)
src/layout/List/ListComponent.tsx (2)
src/features/language/Lang.tsx (1)
  • Lang (15-23)
src/components/form/RadioButton.tsx (1)
  • RadioButton (24-104)
🔇 Additional comments (8)
src/layout/List/ListComponent.module.css (3)

104-107: Good: non-interactive row cursor for readOnly.

tr.readOnlyRow correctly communicates non-interactive state via cursor.


116-131: Mobile layout helpers look correct.

mobileContent and mobileControl align with TSX usage and improve structure on small screens.


112-114: Remove unused .readOnlyCell rule.

Only occurrence is its definition (no references found). Eliminate this dead CSS:

-.readOnlyCell {
-  background-color: initial;
-}
src/layout/List/ListComponent.tsx (5)

99-103: Good: short-circuit interactions in readOnly.

Early return prevents any mutation paths when readOnly is true.


268-274: Hide control header in readOnly.

Omits the controls column in readOnly; avoids empty column and improves semantics.


301-329: Desktop controls: keyboard/toggle behavior looks correct.

  • Checkbox uses onChange={() => toggle(row)} and stops propagation via onClick.
  • RadioButton uses onChange and stops propagation.
  • Removed unreachable readOnlyCell class usage.

334-335: Selection styling gated by readOnly.

Applying selectedRowCell only when not readOnly avoids misleading emphasis.


52-53: Public API: Add readOnly to the generated config and update docs

  • No config.generated file found under src/layout/List; ensure the component’s generated config includes a typed readOnly prop (e.g. in config.generated.ts) and regenerate.
  • Reflect the new readOnly setting in Studio documentation and examples.

@adamhaeger adamhaeger added kind/bug Something isn't working backport-ignore This PR is a new feature and should not be cherry-picked onto release branches labels Aug 29, 2025
@adamhaeger
Copy link
Contributor Author

/publish

1 similar comment
@adamhaeger
Copy link
Contributor Author

/publish

Copy link
Contributor

github-actions bot commented Aug 29, 2025

PR release:

  • <link rel="stylesheet" type="text/css" href="https://altinncdn.no/toolkits/altinn-app-frontend/4.21.0-pr.2043.3646-read-only-list.7b1fa1a8/altinn-app-frontend.css">
  • <script src="https://altinncdn.no/toolkits/altinn-app-frontend/4.21.0-pr.2043.3646-read-only-list.7b1fa1a8/altinn-app-frontend.js"></script>

⚙️ Building...
✅ Done!

Copy link
Contributor

github-actions bot commented Aug 29, 2025

PR release:

  • <link rel="stylesheet" type="text/css" href="https://altinncdn.no/toolkits/altinn-app-frontend/4.21.0-pr.2044.3646-read-only-list.7b1fa1a8/altinn-app-frontend.css">
  • <script src="https://altinncdn.no/toolkits/altinn-app-frontend/4.21.0-pr.2044.3646-read-only-list.7b1fa1a8/altinn-app-frontend.js"></script>

⚙️ Building...
✅ Done!

Copy link

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/layout/List/ListComponent.tsx (1)

126-134: Sort toggle starts at descending; make cycle none → ascending → descending → none.

Current logic initializes new columns to descending. Adjust to expected UX.

-  const handleSort = (key: string) => {
-    if (sortColumn === key && sortDirection === 'ascending') {
-      setSortColumn(undefined);
-      setSortDirection(undefined);
-    } else {
-      setSortColumn(key);
-      setSortDirection(sortColumn === key && sortDirection === 'descending' ? 'ascending' : 'descending');
-    }
-  };
+  const handleSort = (key: string) => {
+    if (sortColumn !== key) {
+      setSortColumn(key);
+      setSortDirection('ascending');
+      return;
+    }
+    if (sortDirection === 'ascending') {
+      setSortDirection('descending');
+      return;
+    }
+    // descending -> none
+    setSortColumn(undefined);
+    setSortDirection(undefined);
+  };
🧹 Nitpick comments (3)
src/layout/List/ListComponent.module.css (1)

104-106: Also reset hover state for read-only rows.

Avoid hover highlight in read-only mode to prevent an interactive affordance.

 .listTable tr.readOnlyRow {
   cursor: default;
 }
+
+.listTable tr.readOnlyRow:hover {
+  background-color: initial;
+}
src/layout/List/ListComponent.test.tsx (1)

214-216: Fix stale comment (now using accessible name).

The radio has an accessible label; update the comment.

-    // Select the second row - find by value since label is empty
+    // Select the second row by accessible name
src/layout/List/ListComponent.tsx (1)

174-209: Remove redundant readOnly checks in the mobile branch.

This block only renders when isMobile && !readOnly; inner !readOnly guards are dead.

-                {!readOnly && (
-                  <Checkbox
+                <Checkbox
                     className={cn(classes.mobileControl)}
                     {...getCheckboxProps({ value: JSON.stringify(row) })}
                     onChange={() => handleRowClick(row)}
                     value={JSON.stringify(row)}
                     checked={isChecked(row)}
                     label={<span className={utilClasses.visuallyHidden}>{getRowLabel(row)}</span>}
-                  />
-                )}
+                  />
@@
-                {!readOnly && (
-                  <Radio
+                <Radio
                     {...getRadioProps({ value: JSON.stringify(row) })}
                     value={JSON.stringify(row)}
                     className={classes.mobileControl}
                     onChange={() => handleSelectedRadioRow({ selectedValue: row })}
                     label={<span className={utilClasses.visuallyHidden}>{getRowLabel(row)}</span>}
-                  />
-                )}
+                  />

Also applies to: 229-244

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7b1fa1a and 06f12bf.

📒 Files selected for processing (3)
  • src/layout/List/ListComponent.module.css (1 hunks)
  • src/layout/List/ListComponent.test.tsx (2 hunks)
  • src/layout/List/ListComponent.tsx (9 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Avoid using any and unnecessary type casts (as Type) in TypeScript; prefer precise typings and refactor existing casts/anys
For TanStack Query, use objects to manage query keys and functions, and centralize shared options via queryOptions

Files:

  • src/layout/List/ListComponent.test.tsx
  • src/layout/List/ListComponent.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

In tests, use renderWithProviders from src/test/renderWithProviders.tsx to supply required form layout context

Files:

  • src/layout/List/ListComponent.test.tsx
**/*.module.css

📄 CodeRabbit inference engine (CLAUDE.md)

Use CSS Modules for component styling and follow existing patterns in *.module.css files

Files:

  • src/layout/List/ListComponent.module.css
🧬 Code graph analysis (1)
src/layout/List/ListComponent.tsx (5)
src/features/formData/useDataModelBindings.ts (1)
  • useDataModelBindings (42-57)
src/features/saveToGroup/useSaveToGroup.ts (1)
  • useSaveObjectToGroup (108-136)
src/layout/ComponentStructureWrapper.tsx (1)
  • ComponentStructureWrapper (20-48)
src/features/language/Lang.tsx (1)
  • Lang (15-23)
src/components/form/RadioButton.tsx (1)
  • RadioButton (24-104)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Install
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Type-checks, eslint, unit tests and SonarCloud
🔇 Additional comments (4)
src/layout/List/ListComponent.module.css (2)

100-102: LGTM: mobile layout helpers and control sizing.

mobileControl and mobileContent styles align with the TSX structure and improve mobile readability.

Also applies to: 112-117, 119-123


108-110: Delete unused .readOnlyCell definition in src/layout/List/ListComponent.module.css (lines 108–110); it isn’t referenced anywhere.

src/layout/List/ListComponent.test.tsx (1)

228-292: Restore spies after each test to prevent cross-test leakage. jest.clearAllMocks() doesn’t restore mocked implementations; add jest.restoreAllMocks() in an afterEach.

 describe('ListComponent', () => {
   beforeEach(() => {
     jest.clearAllMocks();
     jest.useRealTimers();
   });
+  afterEach(() => {
+    jest.restoreAllMocks();
+  });
src/layout/List/ListComponent.tsx (1)

309-357: Optional: replace JSON.stringify(row) with a stable unique key
Using key={JSON.stringify(row)} can be slow and may break if object property order changes. If each row has a unique identifier (for example an id field), use key={row.id} instead; otherwise, verify that your data model guarantees consistent ordering of properties or consider falling back to the item index.

Copy link

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/layout/List/ListComponent.tsx (3)

92-101: Don’t copy reserved keys into form data; compute selection by value keys.

Current logic includes group/checked in comparisons and setValues, which can break matching and write invalid data.

-  const selectedRow = !isMultipleSelection
-    ? (data?.listItems.find((row) => Object.keys(formData).every((key) => row[key] === formData[key])) ?? '')
-    : '';
+  const valueKeys = getValueBindingKeys(bindings);
+  const selectedRow = !isMultipleSelection
+    ? (data?.listItems.find((row) =>
+        valueKeys.every((key) => row[key] === (formData as Record<string, unknown>)[key]),
+      ) ?? '')
+    : '';
@@
   function handleSelectedRadioRow({ selectedValue }: { selectedValue: Row }) {
     const next: Row = {};
-    for (const binding of Object.keys(bindings)) {
-      next[binding] = selectedValue[binding];
-    }
+    for (const key of valueKeys) {
+      next[key] = selectedValue[key];
+    }
     setValues(next);
   }

104-109: Use value-key equality instead of JSON.stringify for selection.

Stringifying rows is brittle and order-dependent.

-  function isRowSelected(row: Row): boolean {
-    if (isMultipleSelection) {
-      return isChecked(row);
-    }
-    return JSON.stringify(selectedRow) === JSON.stringify(row);
-  }
+  function isRowSelected(row: Row): boolean {
+    if (isMultipleSelection) return isChecked(row);
+    return valueKeys.every((key) => row[key] === (formData as Record<string, unknown>)[key]);
+  }

68-69: Type error: assigning undefined to aria-sort-typed state/vars.

AriaAttributes['aria-sort'] does not include undefined, yet you assign it. Update types to allow undefined.

-const [sortDirection, setSortDirection] = useState<AriaAttributes['aria-sort']>('none');
+const [sortDirection, setSortDirection] = useState<AriaAttributes['aria-sort'] | undefined>('none');
@@
-    if (sortColumn === key && sortDirection === 'ascending') {
+    if (sortColumn === key && sortDirection === 'ascending') {
       setSortColumn(undefined);
       setSortDirection(undefined);
     } else {
       setSortColumn(key);
       setSortDirection(sortColumn === key && sortDirection === 'descending' ? 'ascending' : 'descending');
     }
@@
-              let sort: AriaAttributes['aria-sort'] = undefined;
+              let sort: AriaAttributes['aria-sort'] | undefined = undefined;

Also applies to: 126-133, 280-283

♻️ Duplicate comments (3)
src/layout/List/ListComponent.tsx (3)

174-206: Mobile (checkbox): use onChange, not onClick, for keyboard accessibility.

Same as prior review; Space/Enter won’t toggle reliably via onClick. Switch to onChange.

-                <Checkbox
+                <Checkbox
                   key={JSON.stringify(row)}
                   className={cn(classes.mobile)}
                   {...getCheckboxProps({ value: JSON.stringify(row) })}
-                  onClick={() => handleRowClick(row)}
+                  onChange={() => handleRowClick(row)}
                   value={JSON.stringify(row)}
                   checked={isChecked(row)}
                   label={renderListItems(row, tableHeaders)}
                 />

224-233: Mobile (radio): use onChange for accessibility; avoid onClick.

Matches the checkbox path and DS expectations.

-            {data?.listItems.map((row) => (
-              <Radio
+            {data?.listItems.map((row) => (
+              <Radio
                 key={JSON.stringify(row)}
                 {...getRadioProps({ value: JSON.stringify(row) })}
                 value={JSON.stringify(row)}
                 className={cn(classes.mobile, { [classes.selectedRow]: isRowSelected(row) })}
-                onClick={() => handleSelectedRadioRow({ selectedValue: row })}
+                onChange={() => handleSelectedRadioRow({ selectedValue: row })}
                 label={renderListItems(row, tableHeaders)}
               />
             ))}

37-47: Derive selection mode from value-binding keys only (exclude 'group'/'checked').

Otherwise a component with only group/checked present won’t enter readonly mode as intended.

 type Row = Record<string, string | number | boolean>;
-type SelectionMode = 'readonly' | 'single' | 'multiple';
+type SelectionMode = 'readonly' | 'single' | 'multiple';
+
+function getValueBindingKeys(bindings: IDataModelBindingsForList): string[] {
+  return (Object.keys(bindings) as Array<keyof IDataModelBindingsForList>)
+    .filter((k) => k !== 'group' && k !== 'checked' && Boolean(bindings[k]));
+}

-function getSelectionMode(bindings: IDataModelBindingsForList): SelectionMode {
-  const hasValidBindings = Object.keys(bindings).length > 0 && Object.values(bindings).some((b) => b !== undefined);
+function getSelectionMode(bindings: IDataModelBindingsForList): SelectionMode {
+  const hasValidBindings = getValueBindingKeys(bindings).length > 0;
 
   if (!hasValidBindings) {
     return 'readonly';
   }
 
   return bindings.group ? 'multiple' : 'single';
 }
🧹 Nitpick comments (1)
src/layout/List/ListComponent.tsx (1)

195-197: Nit: JSON.stringify as React key/value can be fragile.

Prefer a stable id from the row (e.g., row.id) if available to avoid collisions and heavy stringification.

Also applies to: 224-229, 299-319

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 06f12bf and c24a58a.

📒 Files selected for processing (1)
  • src/layout/List/ListComponent.tsx (8 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Avoid using any and unnecessary type casts (as Type) in TypeScript; prefer precise typings and refactor existing casts/anys
For TanStack Query, use objects to manage query keys and functions, and centralize shared options via queryOptions

Files:

  • src/layout/List/ListComponent.tsx
🧬 Code graph analysis (1)
src/layout/List/ListComponent.tsx (4)
src/features/formData/useDataModelBindings.ts (1)
  • useDataModelBindings (42-57)
src/features/saveToGroup/useSaveToGroup.ts (1)
  • useSaveObjectToGroup (108-136)
src/layout/ComponentStructureWrapper.tsx (1)
  • ComponentStructureWrapper (20-48)
src/components/form/RadioButton.tsx (1)
  • RadioButton (24-104)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Type-checks, eslint, unit tests and SonarCloud
  • GitHub Check: Install
🔇 Additional comments (7)
src/layout/List/ListComponent.tsx (7)

80-84: LGTM: Clear flags from selection mode.


86-86: LGTM: Using toggle/isChecked only.


114-124: LGTM: Click handler respects readOnly and routes by mode.


271-277: LGTM: Hide controls header in readOnly.


300-302: LGTM: Row click disabled in readOnly and styled distinctly.


303-331: LGTM: Desktop controls use onChange and stop propagation.


337-338: LGTM: Avoid selected styling in readOnly.

Copy link

sonarqubecloud bot commented Sep 3, 2025

Copy link
Contributor

@paal2707 paal2707 left a comment

Choose a reason for hiding this comment

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

ser bra ut

@adamhaeger adamhaeger merged commit 7fe5c99 into main Sep 3, 2025
15 of 16 checks passed
@adamhaeger adamhaeger deleted the fix/3646-read-only-list branch September 3, 2025 12:24
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/bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Readonly not working for list
3 participants