Skip to content

Conversation

@odinr
Copy link
Collaborator

@odinr odinr commented Oct 31, 2025

Removes @material-ui/styles dependency from @equinor/fusion-react-styles package to enable React 19 compatibility.

Priority: High
Related to: equinor/fusion-framework#3698
Parent Issue: React 19: Dependency Verification & Preparation

Summary

This PR removes the Material-UI v4 dependency (@material-ui/styles) and replaces it with a custom JSS-based implementation. This resolves the React 19 compatibility blocker identified during dependency verification in fusion-framework.

Root Cause: Material-UI v4 (@material-ui/styles) is incompatible with React 19 and will never receive React 19 support. The @equinor/fusion-react-styles package had a transitive dependency on @material-ui/styles, blocking React 19 upgrades in consuming applications.

Implementation Strategy: Instead of inlining Material-UI code, this PR implements a custom JSS-based solution that maintains API compatibility while removing the Material-UI dependency entirely. This is a temporary solution until the style provider can be eliminated entirely.

Stats: 50 files changed, +5,973 insertions, -224 deletions across 23 commits

Breaking Changes

  • Removed @material-ui/styles dependency - replaced with direct JSS integration
  • Theme API Changes: Theme properties now require getVariable() method:
    • Colors: theme.colors.*.getVariable('color')
    • Spacing: theme.spacing.*.getVariable('padding')
    • Typography: theme.typography.*.style.* (unchanged)
  • Type Changes: makeStyles now returns Record<ClassKey, string> where ClassKey is inferred from style rules when using createStyles, providing compile-time type safety

Major Features

Core Implementation

  • ✅ Custom makeStyles implementation using JSS directly
  • ✅ Custom StylesProvider with enhanced scope isolation via seed prefixes
  • ✅ Custom ThemeProvider with React 19 compatibility
  • ✅ Hot-reload support for development (dev only)
  • ✅ Shared sheet detection to prevent premature detachment

Theme System Enhancements

  • Theme Extension Support: FusionTheme now supports extending with custom properties using generics
  • Custom Base Theme Merging: createTheme now accepts optional baseTheme parameter for nested theme composition
  • Deep Merging Improvements: Properly handles nested theme properties, Record types, and StyleProperty instances
  • ✅ Enhanced class name isolation for micro-frontend scenarios

Testing & Quality

  • ✅ Comprehensive test suite with Vitest (52+ tests, 100% statement coverage)
  • ✅ Test files covering:
    • StyleProvider functionality
    • ThemeProvider functionality
    • makeStyles behavior
    • createStyles helper
    • Class name generation
    • Sheet management
    • Theme utilities

Documentation

  • Complete TSDoc Documentation: All exported functions, types, and interfaces have comprehensive TSDoc comments with examples
  • Updated README: Comprehensive documentation with validated examples and API reference
  • Storybook Documentation: MDX documentation page (styles.mdx) with organized story groups:
    • Basic Usage (basic styles, dynamic styles, theme-based styles)
    • Scope Isolation (micro-frontend style isolation demonstrations)
    • Advanced Features (nested selectors, multiple rules, caching)
    • Theme System (theme composition, useTheme hook, theme extension guide)
    • Utilities (createStyles helper examples)
  • ✅ All Storybook stories use Fusion theme CSS values instead of hardcoded CSS

Infrastructure

  • ✅ Pre-release workflow for next branch (.github/workflows/next.yml)
  • ✅ Changeset configuration for pre-release publishing
  • ✅ NPM authentication setup in GitHub Actions
  • ✅ Dynamic NPM tag support for pre-releases

Technical Details

  • React 19 Compatible: Tested with React ^18 || ^19
  • JSS Integration: Uses JSS v10 with all necessary plugins
  • Backward Compatible: Existing code works without modifications (API surface unchanged except for theme property access)
  • Type Safety: Improved TypeScript inference for class names and theme extensions
  • Scope Isolation: Unique identifier generation for stylesheet isolation in micro-frontend scenarios
  • Production Optimized: Shorter class names using hash function in production builds

Files Changed

New Files

  • packages/styles/src/utils/ - Utility modules (JSS setup, class name generator, sheet manager, contexts)
  • packages/styles/src/__tests__/ - Comprehensive test suite
  • storybook/src/stories/styles/ - Documentation and stories
  • .github/workflows/next.yml - Pre-release workflow
  • .changeset/ - Changeset files

Modified Files

  • packages/styles/src/ - Core implementation files
  • packages/styles/README.md - Updated documentation
  • Package dependencies updated across:
    • @equinor/fusion-react-context-selector
    • @equinor/fusion-react-errorboundary
    • @equinor/fusion-react-filter
    • @equinor/fusion-react-hanging-garden

Migration Notes

  1. Theme Property Access: Update code to use getVariable() method:

    // Before
    theme.colors.ui.background__default.css
    
    // After
    theme.colors.ui.background__default.getVariable('color')
  2. Type Safety: When using createStyles with makeStyles, class names are now type-safe:

    const useStyles = makeStyles((theme) =>
      createStyles({
        root: { color: theme.colors.text.primary.getVariable('color') },
      })
    );
    
    // classes.root is now type-safe
    const classes = useStyles();

Acceptance Criteria

This PR meets all acceptance criteria from equinor/fusion-framework#3698:

  • No @material-ui/styles dependency in fusion-react-styles package
  • React 19 compatibility - package supports React ^18 || ^19
  • Backward compatibility - public API maintained where possible
  • Breaking changes documented - CHANGELOG includes migration guide
  • Comprehensive test coverage - 52+ tests with 100% statement coverage
  • Documentation updated - README, TSDoc, and Storybook stories
  • All styling works correctly - tested with React 18 and React 19
  • No Material-UI-related warnings - dependency completely removed

Future Migration Path

Note: This is a temporary solution. Material-UI moved away from styled-components in v5+, but the styles package has not. This PR provides React 19 compatibility until the style provider can be removed entirely.

Long-term goals:

  1. Phase out styled-component-based theming
  2. Migrate to CSS-in-JS solution compatible with React 19
  3. Remove JSS-based implementation entirely
  4. Consider migrating to native CSS variables

This PR resolves the React 19 blocker identified in fusion-framework dependency verification and enables React 19 upgrades in consuming applications.

@changeset-bot
Copy link

changeset-bot bot commented Oct 31, 2025

🦋 Changeset detected

Latest commit: c1502cf

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@equinor/fusion-react-styles Major
@equinor/fusion-react-components-stories Patch
@equinor/fusion-react-context-selector Patch
@equinor/fusion-react-errorboundary Patch
@equinor/fusion-react-filter Patch
@equinor/fusion-react-hanging-garden Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

odinr added 5 commits November 1, 2025 00:31
- Remove @material-ui/styles dependency and replace with custom JSS implementation
- Add comprehensive test suite with Vitest (52 tests, 100% coverage)
- Add TSDoc documentation for all exported APIs
- Update README with comprehensive documentation
- Add utility modules for JSS setup, class name generation, and sheet management
- Enhance StylesProvider with seed-based scope isolation
- Improve TypeScript types with better inference

BREAKING CHANGE: Removed @material-ui/styles dependency

Signed-off-by: Odin Thomas Rochmann <odin.rochmann@gmail.com>
- Add MDX documentation page for styles package
- Organize stories into logical groups (Basic Usage, Scope Isolation, Advanced Features, Theme System, Utilities)
- Add visual demonstrations of class name isolation for micro-frontends
- Update Storybook port to 3000
- Remove @material-ui/styles dependency from Storybook

ref: equinor/fusion-framework#3698
@odinr odinr force-pushed the styles/react-19-compatibility branch from b9a1f34 to 13f2657 Compare October 31, 2025 23:31
github-actions bot and others added 18 commits November 1, 2025 00:33
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…mposition

- Add support for extending FusionTheme with custom properties using generics
- Enhance createTheme to accept optional baseTheme parameter for nested themes
- Improve deep merging to properly handle StyleProperty instances and Record types
- Export ThemeProviderProps and StylesProviderProps interfaces for better TypeScript support
- Add explicit exports for createTheme, FusionTheme, and StyleDefinition
- Update tests to use createTheme instead of plain objects
- Fix theme merging in nested ThemeProvider scenarios

All changes are backward compatible.

Signed-off-by: Odin Thomas Rochmann <odin.rochmann@gmail.com>
- Add comprehensive TSDoc comments to all exported functions, types, and interfaces in styles package
- Update all Storybook stories to use theme CSS values instead of custom CSS
- Add ThemeExtension story demonstrating how to extend FusionTheme
- Update changesets to reflect documentation improvements
- Clear pre.json changesets array for re-release
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
… tag

- Modify the changeset:publish:pre-release script in package.json to include a dynamic NPM tag using the environment variable.
- Update GitHub Actions workflow to pass the NPM tag as an environment variable for the publish step.

Signed-off-by: Odin Thomas Rochmann <odin.rochmann@gmail.com>
- Add a step to the GitHub Actions workflow to set up npm authentication using the NPM_AUTH_TOKEN secret.
- Ensure the changeset:publish:pre-release script in package.json uses the correct NPM tag format.

Signed-off-by: Odin Thomas Rochmann <odin.rochmann@gmail.com>
- Remove unused changeset reference from pre.json.
- Add TypeScript definitions path to package.json for better type support.

Signed-off-by: Odin Thomas Rochmann <odin.rochmann@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
- Bump version to 2.0.0-preview.3 in package.json.
- Introduce unique identifier generation for makeStyles instances to ensure stylesheet isolation.
- Implement a simple hash function for generating shorter class names in production.
- Update makeStyles to utilize the new unique name generation logic.
- Remove outdated create-styles test file and replace it with a comprehensive test suite in create-styles.test.tsx.

Signed-off-by: Odin Thomas Rochmann <odin.rochmann@gmail.com>
… safety

- Replace incorrect .css and getAttribute() usage with getVariable() API
- Update all storybook stories to use correct theme property access patterns
- Enhance makeStyles type inference to return Record<ClassKey, string>
- Improve createStyles to preserve literal key types for better type safety
- Update README with comprehensive documentation on theme property access
- Update changesets to reflect correct API usage
- Fix test files to match new API signatures

BREAKING CHANGE: Theme properties now require getVariable() method instead of .css property:
  - Colors: theme.colors.*.getVariable('color')
  - Spacing: theme.spacing.*.getVariable('padding')
  - Typography: theme.typography.*.style.* (unchanged)

The return type of makeStyles is now Record<ClassKey, string> where ClassKey
is inferred from style rules when using createStyles, providing compile-time
type safety for class name access.
- Add hot-reload detection using style hashing (dev only)
- Implement shared sheet detection to prevent premature detachment
- Simplify scopeId generation and instance naming
- Add warning when name option is missing from makeStyles
- Improve sheet cleanup logic to handle shared sheets correctly
- Update tsconfig to exclude test files from build
- Bump version to 2.0.0-preview.5
- Add comprehensive TSDoc comments to all source files
- Fix all TSDoc examples to use correct theme API (getVariable)
- Add inline maintainer comments explaining complex logic
- Add Biome ignore comments for all explicit 'any' types with proper reasoning
- Update README with validated examples and comprehensive documentation
- Remove ESLint comments (project uses Biome)
- Improve code comments throughout the package for better maintainability
Changed default Props type from Record<string, unknown> to {} in makeStyles overloads.
This ensures keyof Props evaluates to never when Props is empty, making the
conditional type return (props?: Props) instead of requiring props.

Fixes build errors where useStyles() was called without arguments but TypeScript
required props parameter.
@odinr odinr requested a review from Copilot November 3, 2025 07:29
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR removes the @material-ui/styles dependency from @equinor/fusion-react-styles and replaces it with a custom JSS-based implementation for React 19 compatibility. It also adds comprehensive Storybook documentation, test coverage, and theme extension capabilities.

Key Changes:

  • Complete replacement of Material-UI v4 styles with custom JSS implementation
  • Added comprehensive test suite (52 tests with 100% coverage)
  • Enhanced theme system supporting custom property extensions via generics
  • Added extensive Storybook documentation with interactive examples
  • Improved TypeScript type inference and class key safety

Reviewed Changes

Copilot reviewed 48 out of 50 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/styles/src/make-styles.ts Complete rewrite: custom makeStyles implementation using JSS with improved type inference and caching
packages/styles/src/theme.ts Added theme extension support with FusionTheme<T> generics, createTheme(), and deep merge functionality
packages/styles/src/ThemeProvider.tsx Replaced Material-UI ThemeProvider with custom implementation supporting theme composition
packages/styles/src/StyleProvider.tsx Custom StylesProvider with enhanced seed-based scope isolation for micro-frontends
packages/styles/src/utils/*.ts New utility modules: jss-setup, class-name-generator, sheet-manager, contexts
packages/styles/src/tests/*.test.tsx Comprehensive test suite covering all APIs and functionality
packages/styles/README.md Complete documentation rewrite with examples and migration guide
storybook/src/stories/styles/*.tsx New interactive Storybook stories demonstrating all features
packages/styles/package.json Updated dependencies: removed @material-ui/styles, added JSS packages and testing libraries
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (2)

packages/styles/src/utils/sheet-manager.ts:1

  • The isFirstRender parameter is passed as a standalone boolean true at line 40, but getOrCreateSheet expects 8 parameters with isFirstRender as the 7th parameter. This call is missing the 6th parameter (cacheKey), which will cause true to be passed as cacheKey instead of isFirstRender. Review the function signature and ensure all required parameters are provided in the correct order.
    packages/styles/src/utils/sheet-manager.ts:1
  • The 7th parameter is undefined but the 8th parameter is true. According to the function signature in line 156-164, the 7th parameter is props (defaults to {}), and isFirstRender is the 8th parameter (defaults to false). Passing undefined as props is unusual since props has a default value. Consider removing the undefined parameter and just passing the boolean, or passing {} explicitly if props is required.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Signed-off-by: Odin Thomas Rochmann <odin.rochmann@gmail.com>
@odinr odinr requested a review from eikeland November 3, 2025 07:51
@odinr odinr merged commit 15eacdf into main Nov 3, 2025
5 of 8 checks passed
@odinr odinr deleted the styles/react-19-compatibility branch November 3, 2025 10:25
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.

3 participants