Skip to content

Phase 2.3: Form Elements Migration#2833

Merged
RoyEJohnson merged 18 commits intomainfrom
phase-2.3-form-elements-migration
Apr 14, 2026
Merged

Phase 2.3: Form Elements Migration#2833
RoyEJohnson merged 18 commits intomainfrom
phase-2.3-form-elements-migration

Conversation

@OpenStaxClaude
Copy link
Copy Markdown
Contributor

@OpenStaxClaude OpenStaxClaude commented Mar 27, 2026

CORE-1697

Summary

Migrates form element components from styled-components to plain CSS, following the established patterns from Phase 1.1-2.1.

Components Migrated

  1. TextArea (Note component) - Textarea for highlight notes
  2. SimpleLabel (Note component) - Label for the textarea
  3. AnswerInput (Practice Questions) - Visually hidden radio inputs for answer selection

Changes

  • Created Note.css with styles for TextArea and SimpleLabel
  • Updated Note.tsx to use plain HTML elements with CSS variables
  • Updated Answer/styled.tsx to re-export the standard hiddenButAccessibleClass utility as answerInputClass
  • Updated Answer/index.tsx to use plain <input> element with className
  • Removed styled-components from these form elements

Visible change: There was a dynamic bit of padding in the highlighter note textarea when the text was empty. It was there to make room for when the field label was rendered inside the textarea, which we don't do anymore. It caused the textarea size to shift slightly when going from empty to not or vice-versa. Now it doesn't.

Note: Originally created Answer.css, but during code review it was identified that the codebase already has a standard .hidden-but-accessible utility class. The Answer.css file was removed in favor of using the existing utility for better consistency and screen reader support.

Migration Approach

These components are internal (not exported) and not used in component selectors, so we used a simple in-place migration without the full hybrid approach. No legacy exports needed.

Testing Notes

This is a refactoring-only change with no visual differences. All styling, layout, and behavior should remain identical:

  • Highlight notes textarea should look and behave the same
  • Practice question radio inputs should remain visually hidden but accessible
  • All interactive behaviors (auto-resize, ESC handling, keyboard navigation) should work identically

Test Plan

Manual Testing

  1. Highlights Note Textarea

    • Open a textbook in REX
    • Select text to create a highlight
    • Click in the note textarea
    • Verify: correct width, gray border, blue label, auto-resize, ESC key behavior, character limit
  2. Practice Questions Answer Inputs

    • Open a textbook and click Practice
    • Start a practice session
    • Verify: inputs visually hidden, custom indicator shows selection, keyboard navigation works, screen reader accessible

Automated Testing

CI will run all existing tests to ensure no regressions.

Related

  • Jira ticket: CORE-1697
  • Follows patterns from Phase 1.1-2.1 and learnings from Phase 2.2

🤖 Generated with Claude Code

@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-3-form--nz1or3 March 27, 2026 22:22 Inactive
@RoyEJohnson RoyEJohnson requested a review from Copilot March 28, 2026 12:41

This comment was marked as resolved.

RoyEJohnson

This comment was marked as resolved.

@TomWoodward TomWoodward had a problem deploying to rex-web-phase-2-3-form--cp82cs March 28, 2026 12:53 Failure
@OpenStaxClaude

This comment was marked as resolved.

@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-3-form--cp82cs March 28, 2026 12:58 Inactive
@RoyEJohnson RoyEJohnson force-pushed the phase-2.3-form-elements-migration branch from 58cdf19 to 1882365 Compare March 28, 2026 13:02
@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-3-form--fun5li March 28, 2026 13:02 Inactive
@RoyEJohnson RoyEJohnson force-pushed the phase-2.3-form-elements-migration branch from 1882365 to af81407 Compare March 28, 2026 13:08
@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-3-form--zbsjlm March 28, 2026 13:09 Inactive
@RoyEJohnson RoyEJohnson force-pushed the phase-2.3-form-elements-migration branch from af81407 to abd2f18 Compare March 28, 2026 13:25
@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-3-form--zbsjlm March 28, 2026 13:26 Inactive
RoyEJohnson

This comment was marked as resolved.

OpenStaxClaude added a commit that referenced this pull request Mar 29, 2026
Moved ContentPane.css import to global CSS loader in app/index.tsx
to resolve webpack chunk ordering conflict with Note.css.

The build was failing with:
"Conflicting order. Following module has been added:
 * css ...ContentPane.css
despite it was not able to fulfill desired ordering with these modules:
 * css ...Note.css"

This follows the established pattern of importing component CSS files
in app/index.tsx to ensure consistent chunk ordering across routes.

Fixes: Review comment from RoyEJohnson in PR #2833

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@OpenStaxClaude

This comment was marked as resolved.

This comment was marked as resolved.

RoyEJohnson

This comment was marked as resolved.

@OpenStaxClaude

This comment was marked as resolved.

@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-3-form--zbsjlm March 29, 2026 22:40 Inactive
RoyEJohnson

This comment was marked as resolved.

@OpenStaxClaude

This comment was marked as resolved.

RoyEJohnson

This comment was marked as resolved.

OpenStaxClaude added a commit that referenced this pull request Mar 30, 2026
Updated the global button border reset to use :not([class*="button"])
selector, which preserves borders on styled button components (like
.button-default, .button-primary) while still removing unwanted default
borders from unstyled buttons.

This fixes the issue where the Cancel button in EditCard (which uses
the Button component with variant="default") was losing its border due
to the global reset overriding .button-default styles.

The :not([class*="button"]) approach is automatic and doesn't require
adding special classes to preserve borders - any button with a class
name containing "button" will keep its intended styling.

Addresses PR #2833, Review 7.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@OpenStaxClaude

This comment was marked as resolved.

@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-3-form--zbsjlm March 30, 2026 22:00 Inactive
@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-3-form--zbsjlm March 30, 2026 22:11 Inactive
@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-3-form--zbsjlm March 30, 2026 22:22 Inactive
@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-3-form--zbsjlm March 31, 2026 11:36 Inactive
@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-3-form--zbsjlm March 31, 2026 12:46 Inactive
@RoyEJohnson RoyEJohnson force-pushed the phase-2.3-form-elements-migration branch from 692579c to c6bed21 Compare March 31, 2026 12:50
RoyEJohnson pushed a commit that referenced this pull request Mar 31, 2026
Fixed two issues identified in the latest code review:

1. Button.css selector specificity (Comment 1):
   - Changed `button.plain-button` and `button.button-link` to just
     `.plain-button` and `.button-link`
   - Removes element selector to reduce specificity
   - Allows styled-components wrappers (e.g., NudgeCloseButton) to
     override with their own border declarations
   - Matches the old behavior where component-specific styles could
     override the reset

2. Note.spec.tsx test cleanup (Comment 2):
   - Added empty dependency array `[]` to useLayoutEffect
   - Added cleanup function that removes the event listener
   - Captures div ref in local variable for cleanup closure
   - Prevents multiple listener registrations on re-renders
   - Eliminates potential test flakiness

Both changes improve code quality and follow React best practices.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix CSS loading order conflict with ContentPane.css

Moved ContentPane.css import to global CSS loader in app/index.tsx
to resolve webpack chunk ordering conflict with Note.css.

The build was failing with:
"Conflicting order. Following module has been added:
 * css ...ContentPane.css
despite it was not able to fulfill desired ordering with these modules:
 * css ...Note.css"

This follows the established pattern of importing component CSS files
in app/index.tsx to ensure consistent chunk ordering across routes.

Fixes: Review comment from RoyEJohnson in PR #2833

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix ESC handler bug and update PR description

Addressed latest Copilot review comments:

1. Fixed ESC handler bug in Note.tsx (Comment 1):
   - Changed condition from checking textareaRef.current?.textContent
     to checking the note prop directly
   - For <textarea> elements, user-entered content is in .value property,
     not textContent (which would always be empty)
   - This prevents ESC from incorrectly dispatching hideCardEvent when
     the textarea has content
   - Added note to dependency array of useCallback

2. Updated PR description (Comment 2):
   - Clarified that Answer.css was initially created but then deleted
     during first code review
   - Explained we now use the standard .hidden-but-accessible utility
     instead for better consistency and screen reader support
   - Added note section explaining the change

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix AuthenticationGate button

Refine button border reset to exclude styled buttons

Updated the global button border reset to use :not([class*="button"])
selector, which preserves borders on styled button components (like
.button-default, .button-primary) while still removing unwanted default
borders from unstyled buttons.

This fixes the issue where the Cancel button in EditCard (which uses
the Button component with variant="default") was losing its border due
to the global reset overriding .button-default styles.

The :not([class*="button"]) approach is automatic and doesn't require
adding special classes to preserve borders - any button with a class
name containing "button" will keep its intended styling.

Addresses PR #2833, Review 7.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Address Copilot Review comments on button border handling

Fixed two issues identified in the latest Copilot review:

1. Button.css - Added explicit border reset for PlainButton classes:
   - Added `button.plain-button` and `button.button-link` to explicitly
     reset borders on these base button classes
   - These classes contain "button" in their names, so they're excluded
     from the `button:not([class*="button"])` selector
   - Without this explicit reset, they could inherit browser default borders
   - This ensures consistent border behavior across all button types

2. AuthenticationGate.tsx - Removed undefined basic-button class:
   - Removed `className="basic-button"` which had no corresponding CSS definition
   - This was added mistakenly in an earlier commit to work around border issues
   - The button now correctly matches `button:not([class*="button"])` and
     gets the border reset as intended
   - No visual change since basic-button had no styles anyway

Both changes ensure proper border handling without introducing undefined
classes or leaving gaps in the reset selector coverage.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Update Button.css

Remove box-shadow from disabled button-primary state

Fixed Review 11 comment: Disabled buttons should not have a box-shadow.

The base .button class applies a box-shadow, but disabled buttons should
not display this shadow. Updated both the main disabled state rule and
the hover/active/focus override rules to set box-shadow: none.

This ensures all disabled buttons (including button-primary) are rendered
without shadows, making them visually distinct from enabled buttons.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Note test coverage

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@TomWoodward TomWoodward temporarily deployed to rex-web-phase-2-3-form--zbsjlm March 31, 2026 12:50 Inactive
@RoyEJohnson RoyEJohnson force-pushed the phase-2.3-form-elements-migration branch from c6bed21 to 40afc15 Compare March 31, 2026 12:51
RoyEJohnson pushed a commit that referenced this pull request Mar 31, 2026
Fixed two issues identified in the latest code review:

1. Button.css selector specificity (Comment 1):
   - Changed `button.plain-button` and `button.button-link` to just
     `.plain-button` and `.button-link`
   - Removes element selector to reduce specificity
   - Allows styled-components wrappers (e.g., NudgeCloseButton) to
     override with their own border declarations
   - Matches the old behavior where component-specific styles could
     override the reset

2. Note.spec.tsx test cleanup (Comment 2):
   - Added empty dependency array `[]` to useLayoutEffect
   - Added cleanup function that removes the event listener
   - Captures div ref in local variable for cleanup closure
   - Prevents multiple listener registrations on re-renders
   - Eliminates potential test flakiness

Both changes improve code quality and follow React best practices.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix CSS loading order conflict with ContentPane.css

Moved ContentPane.css import to global CSS loader in app/index.tsx
to resolve webpack chunk ordering conflict with Note.css.

The build was failing with:
"Conflicting order. Following module has been added:
 * css ...ContentPane.css
despite it was not able to fulfill desired ordering with these modules:
 * css ...Note.css"

This follows the established pattern of importing component CSS files
in app/index.tsx to ensure consistent chunk ordering across routes.

Fixes: Review comment from RoyEJohnson in PR #2833

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix ESC handler bug and update PR description

Addressed latest Copilot review comments:

1. Fixed ESC handler bug in Note.tsx (Comment 1):
   - Changed condition from checking textareaRef.current?.textContent
     to checking the note prop directly
   - For <textarea> elements, user-entered content is in .value property,
     not textContent (which would always be empty)
   - This prevents ESC from incorrectly dispatching hideCardEvent when
     the textarea has content
   - Added note to dependency array of useCallback

2. Updated PR description (Comment 2):
   - Clarified that Answer.css was initially created but then deleted
     during first code review
   - Explained we now use the standard .hidden-but-accessible utility
     instead for better consistency and screen reader support
   - Added note section explaining the change

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix AuthenticationGate button

Refine button border reset to exclude styled buttons

Updated the global button border reset to use :not([class*="button"])
selector, which preserves borders on styled button components (like
.button-default, .button-primary) while still removing unwanted default
borders from unstyled buttons.

This fixes the issue where the Cancel button in EditCard (which uses
the Button component with variant="default") was losing its border due
to the global reset overriding .button-default styles.

The :not([class*="button"]) approach is automatic and doesn't require
adding special classes to preserve borders - any button with a class
name containing "button" will keep its intended styling.

Addresses PR #2833, Review 7.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Address Copilot Review comments on button border handling

Fixed two issues identified in the latest Copilot review:

1. Button.css - Added explicit border reset for PlainButton classes:
   - Added `button.plain-button` and `button.button-link` to explicitly
     reset borders on these base button classes
   - These classes contain "button" in their names, so they're excluded
     from the `button:not([class*="button"])` selector
   - Without this explicit reset, they could inherit browser default borders
   - This ensures consistent border behavior across all button types

2. AuthenticationGate.tsx - Removed undefined basic-button class:
   - Removed `className="basic-button"` which had no corresponding CSS definition
   - This was added mistakenly in an earlier commit to work around border issues
   - The button now correctly matches `button:not([class*="button"])` and
     gets the border reset as intended
   - No visual change since basic-button had no styles anyway

Both changes ensure proper border handling without introducing undefined
classes or leaving gaps in the reset selector coverage.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Update Button.css

Remove box-shadow from disabled button-primary state

Fixed Review 11 comment: Disabled buttons should not have a box-shadow.

The base .button class applies a box-shadow, but disabled buttons should
not display this shadow. Updated both the main disabled state rule and
the hover/active/focus override rules to set box-shadow: none.

This ensures all disabled buttons (including button-primary) are rendered
without shadows, making them visually distinct from enabled buttons.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Note test coverage

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
OpenStaxClaude and others added 18 commits April 9, 2026 14:26
Migrated TextArea, SimpleLabel, and AnswerInput components following
the established patterns from Phase 1.1-2.1.

Changes:
- Created Note.css for TextArea and SimpleLabel styles
- Updated Note.tsx to use plain HTML elements with CSS variables
- Created Answer.css for AnswerInput styles
- Updated Answer/index.tsx to use plain input element
- Removed styled-components from these form elements

All styling and behavior should remain identical. This is a
refactoring-only change with no visual differences.

Related to CORE-1697

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixed three issues identified in code review:

1. Note.css duplicate color property (Comments 1-2):
   - Removed redundant --text-color and --label-color declarations
   - Consolidated to single --note-text-color variable for clarity
   - Updated Note.tsx to set only --note-text-color

2. Answer.css unnecessary duplication (Comment 3):
   - Removed Answer.css file entirely
   - Changed answerInputClass to re-export hiddenButAccessibleClass
   - Now uses standard .hidden-but-accessible utility from index.css
   - Provides better screen reader consistency with clip/overflow rules

3. CSS import location (Comment 4):
   - Removed Answer.css import (file deleted, see #2)
   - Added hiddenButAccessibleClass import to styled.tsx

All form elements now follow established patterns and use standard
utilities where appropriate, improving maintainability.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Fixed two issues identified in the latest code review:

1. Button.css selector specificity (Comment 1):
   - Changed `button.plain-button` and `button.button-link` to just
     `.plain-button` and `.button-link`
   - Removes element selector to reduce specificity
   - Allows styled-components wrappers (e.g., NudgeCloseButton) to
     override with their own border declarations
   - Matches the old behavior where component-specific styles could
     override the reset

2. Note.spec.tsx test cleanup (Comment 2):
   - Added empty dependency array `[]` to useLayoutEffect
   - Added cleanup function that removes the event listener
   - Captures div ref in local variable for cleanup closure
   - Prevents multiple listener registrations on re-renders
   - Eliminates potential test flakiness

Both changes improve code quality and follow React best practices.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix CSS loading order conflict with ContentPane.css

Moved ContentPane.css import to global CSS loader in app/index.tsx
to resolve webpack chunk ordering conflict with Note.css.

The build was failing with:
"Conflicting order. Following module has been added:
 * css ...ContentPane.css
despite it was not able to fulfill desired ordering with these modules:
 * css ...Note.css"

This follows the established pattern of importing component CSS files
in app/index.tsx to ensure consistent chunk ordering across routes.

Fixes: Review comment from RoyEJohnson in PR #2833

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix ESC handler bug and update PR description

Addressed latest Copilot review comments:

1. Fixed ESC handler bug in Note.tsx (Comment 1):
   - Changed condition from checking textareaRef.current?.textContent
     to checking the note prop directly
   - For <textarea> elements, user-entered content is in .value property,
     not textContent (which would always be empty)
   - This prevents ESC from incorrectly dispatching hideCardEvent when
     the textarea has content
   - Added note to dependency array of useCallback

2. Updated PR description (Comment 2):
   - Clarified that Answer.css was initially created but then deleted
     during first code review
   - Explained we now use the standard .hidden-but-accessible utility
     instead for better consistency and screen reader support
   - Added note section explaining the change

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Fix AuthenticationGate button

Refine button border reset to exclude styled buttons

Updated the global button border reset to use :not([class*="button"])
selector, which preserves borders on styled button components (like
.button-default, .button-primary) while still removing unwanted default
borders from unstyled buttons.

This fixes the issue where the Cancel button in EditCard (which uses
the Button component with variant="default") was losing its border due
to the global reset overriding .button-default styles.

The :not([class*="button"]) approach is automatic and doesn't require
adding special classes to preserve borders - any button with a class
name containing "button" will keep its intended styling.

Addresses PR #2833, Review 7.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Address Copilot Review comments on button border handling

Fixed two issues identified in the latest Copilot review:

1. Button.css - Added explicit border reset for PlainButton classes:
   - Added `button.plain-button` and `button.button-link` to explicitly
     reset borders on these base button classes
   - These classes contain "button" in their names, so they're excluded
     from the `button:not([class*="button"])` selector
   - Without this explicit reset, they could inherit browser default borders
   - This ensures consistent border behavior across all button types

2. AuthenticationGate.tsx - Removed undefined basic-button class:
   - Removed `className="basic-button"` which had no corresponding CSS definition
   - This was added mistakenly in an earlier commit to work around border issues
   - The button now correctly matches `button:not([class*="button"])` and
     gets the border reset as intended
   - No visual change since basic-button had no styles anyway

Both changes ensure proper border handling without introducing undefined
classes or leaving gaps in the reset selector coverage.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Update Button.css

Remove box-shadow from disabled button-primary state

Fixed Review 11 comment: Disabled buttons should not have a box-shadow.

The base .button class applies a box-shadow, but disabled buttons should
not display this shadow. Updated both the main disabled state rule and
the hover/active/focus override rules to set box-shadow: none.

This ensures all disabled buttons (including button-primary) are rendered
without shadows, making them visually distinct from enabled buttons.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Note test coverage

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The SkipAndSubmitButtons were not receiving the margin-left: 0.1rem
styling specified in the QuestionNavigation Wrapper component selector.

Root Cause:
- QuestionNavigation's Wrapper uses styled-components selector ${Button}
  to apply margin-left to buttons (line 28-30)
- SkipAndSubmitButtons was importing the plain Button component directly
- After Button migration to plain CSS, component selectors only work with
  styled-component wrappers

Fix:
- Exported the styled Button wrapper from QuestionNavigation/index.tsx
- Updated SkipAndSubmitButtons to import the styled Button instead of
  the plain Button component
- Now buttons in SkipAndSubmitButtons match the ${Button} selector and
  receive the margin-left styling

This ensures proper button spacing in the practice questions navigation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixed two issues identified in the latest code review:

1. Fixed :empty CSS selector issue in Note.css (Comment 1):
   - Issue: The :empty pseudo-class doesn't detect textarea value changes
     (it only checks for child nodes, not the value property)
   - Fix: Replaced .note-textarea:empty with data-attribute selector
   - Added data-empty={note === '' ? 'true' : 'false'} to textarea element
   - Now padding-top styling correctly applies based on actual note content

2. Fixed circular dependency in QuestionNavigation (Comment 2):
   - Issue: SkipAndSubmitButtons imported { Button } from './index',
     but index.tsx also imports SkipAndSubmitButtons, creating circular dependency
   - Fix: Extracted styled Button wrapper to separate Button.tsx file
   - Both index.tsx and SkipAndSubmitButtons.tsx now import from Button.tsx
   - Eliminates circular dependency while preserving styled-component selector behavior

Both fixes maintain existing functionality while following React and module best practices.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This was leftover behavior for when the label was embedded in the textarea.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants