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

    • Lists now enter a read-only mode when selection isn’t applicable; data remains visible without selection controls.
    • Automatic selection mode: single, multiple, or read-only based on context.
    • Improved mobile layout with clearer separation of controls and content.
  • Bug Fixes

    • Prevents unintended selection or updates when bindings are missing.
    • Hides selection controls and disables row clicks in read-only mode.
  • Style

    • Updated row styling for read-only state and refined mobile spacing.
  • Tests

    • Added tests covering read-only behavior across desktop and mobile.

Copy link

coderabbitai bot commented Aug 28, 2025

📝 Walkthrough

Walkthrough

Suppresses selection controls and row-click behavior when list item bindings are absent (treated as readOnly), applies readOnly classes/styles, updates mobile layout CSS, and adds tests validating readOnly behavior in desktop and mobile.

Changes

Cohort / File(s) Summary
List implementation
src/layout/List/ListComponent.tsx
Derives selection mode/readOnly from dataModelBindings; suppresses Checkbox/Radio and row click handling when readOnly; updates selected-row logic and applies readOnlyRow/readOnlyCell classes; mobile and desktop render paths gated by readOnly.
List tests
src/layout/List/ListComponent.test.tsx
Adds tests for auto-readonly (no/empty dataModelBindings) verifying no radios/checkboxes rendered, no selection persistence/patching, and data remains visible in desktop and mobile; minor test comment adjustments.
List styles
src/layout/List/ListComponent.module.css
Removes .listTable { width: 100% }; adds .mobileControl, .listTable tr.readOnlyRow, .readOnlyCell, .mobileContent, and .mobileContent div to control readOnly cursor/background and mobile flex layout.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Assessment against linked issues

Objective Addressed Explanation
Ensure lists marked/read-derived as readOnly do not show selection controls (issue #3646)

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ 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 @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit 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:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit 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 @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @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

sonarqubecloud bot commented Sep 1, 2025

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
1 participant