Skip to content

Conversation

@ashwin1111
Copy link
Contributor

@ashwin1111 ashwin1111 commented Jul 27, 2025

Description

Please add PR description here, add screenshots if needed

Clickup

https://app.clickup.com/

Summary by CodeRabbit

  • New Features

    • Introduced comprehensive multi-brand theming with dynamic theme switching for Fyle and Co brands, including brand-specific color palettes, CSS variables, and design token presets.
    • Added a live theme demo component to preview and switch themes in real time, showcasing updated button styles and color swatches.
    • Integrated a centralized ThemeService for managing theme application and brand switching across the app.
  • Documentation

    • Added detailed guides and migration plans covering CSS variable usage, design token mapping, theming architecture, and migration phases for PrimeNG integration.
  • Style

    • Refactored and centralized button and icon styles using CSS custom properties, improving maintainability and consistency across themes.
    • Modularized input, button, toggle, tooltip, radio button, dialog, checkbox, multiselect, dropdown, datepicker, chips, and table styles into separate SCSS files.
    • Reorganized brand-specific icon overrides with explicit CSS layering for better cascade control.
  • Chores

    • Updated dependencies to include theming-related packages for PrimeNG and utility support.
    • Integrated ThemeService into app initialization for automatic theme setup.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 27, 2025

Walkthrough

This update introduces a comprehensive theming infrastructure supporting multi-brand (Fyle and Co) UI customization using CSS custom properties, TypeScript theme presets, and a centralized Angular ThemeService. It adds detailed documentation, new dependencies, CSS architecture improvements, brand-specific style modules, and a demo component to visualize and test dynamic theme switching across the application.

Changes

File(s) / Group Change Summary
CO_CSS_VARIABLES_USAGE.md, CSS_VARIABLES_USAGE.md, CURRENT_THEME_MECHANISM.md, DESIGN_TOKEN_MAPPING.md, PHASE_1_ANALYSIS.md, PHASE_1_COMPLETION_SUMMARY.md, PHASE_2_COMPLETION_SUMMARY.md, PRESET_INTEGRATION_SOLUTION.md, PRIMENG_THEMING_MIGRATION_PLAN.md Added extensive documentation covering CSS variable usage, theming mechanisms, migration plans, design token mapping, and project phase summaries.
package.json Added dependencies: @primeuix/styled, @primeuix/themes, @primeuix/utils.
src/app/app.module.ts Integrated ThemeService into the app module; updated APP_INITIALIZER for theme initialization.
src/app/core/guard/qbo-token.guard.ts Modified canActivate method to always return true, bypassing previous validation logic.
src/app/shared/components/dashboard/dashboard-export-section/dashboard-export-section.component.html Added <app-theme-demo></app-theme-demo> component at the top of the template.
src/app/shared/components/theme-demo/theme-demo.component.ts Introduced ThemeDemoComponent for live theme switching, brand visualization, and CSS variable inspection.
src/app/shared/shared.module.ts Declared and imported ThemeDemoComponent in SharedModule.
src/assets/scss/buttons.scss New SCSS file defining button styles using CSS variables for all button variants and states.
src/assets/scss/inputs.scss New SCSS file defining input styles using CSS variables for various input components and states.
src/assets/scss/tooltips.scss Added tooltip styles using CSS variables for text and arrow positioning.
src/assets/scss/toggles.scss Added toggle switch styles using CSS variables with theme-specific adjustments.
src/assets/scss/radio-buttons.scss Added radio button styles using CSS variables for dimension, border, background, and icon styling.
src/assets/scss/dialogs.scss Added dialog component styles using CSS variables for content, header, and close icon.
src/assets/scss/checkboxes.scss Added checkbox styles using CSS variables for box, icon, hover, and highlight states.
src/assets/scss/dropdowns.scss Added dropdown styles using CSS variables covering container, items, triggers, and states.
src/assets/scss/multiselect.scss Added multiselect styles using CSS variables for container, trigger, panel, items, and states.
src/assets/scss/datepicker.scss Added datepicker styles using CSS variables for layout, navigation, and states.
src/assets/scss/chips.scss Added chips component styles using CSS variables for tokens and container.
src/assets/scss/table.scss Added table styles using CSS variables for headers, body, rows, and status badges.
src/assets/scss/skeleton.scss Added skeleton style using CSS variables.
src/assets/scss/spinner.scss Added spinner styles and animations using CSS variables.
src/assets/scss/toasts.scss Added toast notification styles using CSS variables.
src/core/services/theme.service.ts Added ThemeService to manage brand switching, CSS variable application, and theme state.
src/styles.scss Introduced CSS @layer ordering, grouped brand icon overrides, and imported button, input, tooltip, toggle, radio, dialog, checkbox, multiselect, dropdown, datepicker, chips, and table styles from new SCSS files; removed inline styles.
src/themes/co-css-vars.ts, src/themes/fyle-css-vars.ts Added TypeScript modules exporting structured mappings of brand CSS variables.
src/themes/co-preset.ts, src/themes/fyle-preset.ts Added theme preset objects for Co and Fyle brands, including color palettes and component style overrides.
src/themes/index.ts Centralized exports and utilities for theme presets, CSS variable mappings, and brand utilities.
src/themes/theme.types.ts Added TypeScript interfaces and types for theme configuration and color palettes.
src/app/integrations/netsuite/netsuite-shared/netsuite-import-settings/netsuite-custom-segment-dialog/netsuite-custom-segment-dialog.component.html Removed fixed width and height inline styles from dialog component.
src/app/integrations/qbd-direct/qbd-direct-shared/qbd-direct-download-file/qbd-direct-download-file.component.html Removed padding utility classes from input element.
src/app/integrations/qbd-direct/qbd-direct-shared/qbd-direct-download-file/qbd-direct-download-file.component.scss Added padding utilities to .error-box style.
src/app/integrations/qbd/qbd-shared/qbd-export-setting/qbd-export-setting.component.html Simplified input padding classes by removing explicit padding overrides.
src/app/integrations/qbd/qbd-shared/qbd-export-setting/qbd-export-setting.component.scss Added padding utilities to .error-box and .normal-box styles.
src/app/shared/components/export-log/export-log-table/export-log-table.component.scss Removed hover styling from .clickable-row:hover.
src/app/shared/components/qbd/mapping/qbd-mapping-table/qbd-mapping-table.component.scss Removed styles for th and td elements.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ThemeDemoComponent
    participant ThemeService
    participant Document

    User->>ThemeDemoComponent: Clicks "Switch Theme" or brand button
    ThemeDemoComponent->>ThemeService: switchToBrand(brandId)
    ThemeService->>Document: Set data-theme attribute
    ThemeService->>Document: Apply CSS variables for selected brand
    ThemeDemoComponent->>ThemeService: getCurrentBrand(), getCurrentBrandPreset()
    ThemeService-->>ThemeDemoComponent: Return current brand info
    ThemeDemoComponent->>Document: Render UI with updated styles
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

A rabbit hopped through fields of hue,
With buttons pink and buttons blue.
It switched the theme from Fyle to Co,
And watched the colors change and glow.
With docs and code both neat and clean,
This bunny’s proud—our app’s pristine!
🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch prime-ng-theme

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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai 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

Documentation and Community

  • 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.

@github-actions github-actions bot added the size/XL Extra Large PR label Jul 27, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🔭 Outside diff range comments (2)
src/app/core/guard/qbo-token.guard.ts (1)

31-59: Unreachable security code due to early return.

This entire security validation block is now unreachable due to the early return on line 29. This includes important checks for workspace ID, token health, and error handling with proper user redirects.

src/assets/scss/buttons.scss (1)

1-126: Excellent centralization of button styles with some architectural concerns.

The consolidation of button styles into a dedicated SCSS file using CSS custom properties is a solid architectural decision that supports the dynamic theming system. However, there are some areas for improvement:

  1. Excessive !important usage: Nearly every property uses !important, which could make future customization and debugging more difficult.

  2. Missing fallback values: CSS variables lack fallback values, which could lead to styling issues if variables aren't properly set.

  3. Inconsistent !important application: Some properties like .btn-text-primary cursor and font-size don't use !important while others do.

Consider these improvements:

.p-button {
-  color: var(--preset-btn-primary-color) !important;
+  color: var(--preset-btn-primary-color, #ffffff) !important;
-  background: var(--preset-btn-primary-background) !important;
+  background: var(--preset-btn-primary-background, #007ad9) !important;
}

.btn-text-primary {
-  cursor: var(--preset-btn-text-cursor);
+  cursor: var(--preset-btn-text-cursor) !important;
-  font-size: var(--preset-btn-text-font-size);
+  font-size: var(--preset-btn-text-font-size) !important;
}

The CSS variable naming convention is excellent and the organization by button variants makes the code maintainable.

🧹 Nitpick comments (14)
src/themes/theme.types.ts (1)

1-6: Well-structured brand theme configuration interface.

The BrandThemeConfig interface provides good type safety with constrained brandId values. The preset property uses any type which reduces type safety.

Consider improving the preset type if PrimeNG provides specific preset types:

-  preset: any; // PrimeNG preset type
+  preset: PrimeNGPreset; // Import specific type if available
src/themes/co-css-vars.ts (1)

1-306: Excellent CSS variables mapping structure with room for type safety improvements.

The comprehensive organization by category (backgrounds, text, borders, buttons, etc.) makes this very maintainable. The inline comments with actual color values are helpful for developers.

Consider adding TypeScript type definitions to ensure type safety:

// Add at the top of the file
interface CSSVarsStructure {
  backgrounds: Record<string, string>;
  text: Record<string, string>;
  borders: Record<string, string>;
  buttons: Record<string, string>;
  spacing: Record<string | number, string>;
  // ... other categories
}

export const CoCSSVars: CSSVarsStructure = {
  // existing implementation
};

This would provide compile-time checking and better IntelliSense support.

src/themes/fyle-css-vars.ts (1)

1-228: Good brand-specific CSS variables structure, but consider consistency improvements.

The Fyle-specific implementation follows the same organizational pattern as the Co version, which is excellent for maintainability.

Consider these improvements for better consistency:

  1. Ensure category order matches between brands:
// Both files should follow the same category order:
// backgrounds, text, borders, buttons, gradients (if applicable), spacing, borderRadius, etc.
  1. Add the same TypeScript typing as suggested for CoCSSVars for consistency and type safety.

  2. Document brand-specific categories: The gradients category exists in Fyle but not Co - consider adding a comment explaining this is Fyle-specific, or add an empty gradients object to Co for consistency.

src/app/app.module.ts (1)

94-106: Clear initialization logic with potential for improvement.

The explicit branding initialization followed by implicit theme service initialization is well-documented. However, consider making the theme service initialization more explicit for better maintainability.

Consider this more explicit approach:

useFactory: (brandingService: BrandingService, themeService: ThemeService) => {
  return async () => {
    // Initialize branding first
    await brandingService.init();
    
    // Then explicitly initialize theme service
    await themeService.init();
  };
},

This makes the initialization flow more obvious and allows for proper error handling if either service fails to initialize.

CSS_VARIABLES_USAGE.md (1)

21-21: Fix markdown heading formatting.

The static analysis correctly identifies trailing punctuation in headings which should be removed for consistency.

-### **1. Import the Organized CSS Variables**
+### **1. Import the Organized CSS Variables**

Apply similar fixes to other headings flagged by markdownlint (lines 21, 43, 77, 85).

CO_CSS_VARIABLES_USAGE.md (1)

9-17: Add language specification to fenced code block.

The static analysis correctly identifies that the file structure code block should specify a language for proper syntax highlighting.

-```
+```
src/themes/
├── fyle-css-vars.ts    ← Fyle brand CSS variables
├── co-css-vars.ts      ← 🆕 Co brand CSS variables  
├── fyle-preset.ts      ← Uses FyleCSSVars
├── co-preset.ts        ← ✅ Updated to use CoCSSVars
├── theme.types.ts      ← Type definitions
└── index.ts            ← ✅ Exports both CSS variable systems

+```text


</blockquote></details>
<details>
<summary>src/themes/fyle-preset.ts (1)</summary><blockquote>

`152-164`: **Consider standardizing text button hover states.**

The text button configuration only defines `primary` and `primaryHover` states but lacks disabled and other variants that might be needed for consistency with other button types.



Consider adding a disabled state for completeness:

```diff
  text: {
    primary: {
      color: FyleCSSVars.buttons.tertiaryTextColor,
      fontSize: '14px',
      cursor: 'pointer'
    },
    primaryHover: {
      color: FyleCSSVars.buttons.tertiaryHoverTextColor,
      background: FyleCSSVars.buttons.tertiaryHoverBg,
      borderRadius: FyleCSSVars.borderRadius['2xs']
-   }
+   },
+   disabled: {
+     color: FyleCSSVars.text.disable,
+     cursor: 'not-allowed'
+   }
  }
src/themes/index.ts (1)

49-52: Consider adding error handling for invalid brand IDs.

While TypeScript provides type safety, runtime validation could prevent potential issues if the function is called with invalid data.

Add runtime validation for robustness:

  export function getBrandColors(brandId: SupportedBrand) {
+   if (brandId !== 'fyle' && brandId !== 'co') {
+     console.warn(`Invalid brand ID: ${brandId}, falling back to 'fyle'`);
+     brandId = 'fyle';
+   }
    const preset = getPresetByBrand(brandId);
    return preset.colors;
  }
src/styles.scss (1)

34-127: Good consolidation but inconsistent selector strategy.

The consolidation of brand-specific icon overrides into a dedicated CSS layer is excellent for organization. However, there's an inconsistency in selector approaches that could lead to confusion:

  • Some icons use [data-theme='fyle'] and [data-theme='co'] (modern approach)
  • Others use .fyle and .co class suffixes (legacy approach)

This mixed approach could make maintenance more difficult.

Consider standardizing on the [data-theme] approach for consistency:

-.pi-times.fyle.qbd{
+[data-theme='fyle'] .pi-times.qbd {
    content: url('./assets/icons/cross.svg') !important;
}

-.pi-times.fyle {
+[data-theme='fyle'] .pi-times {
    content: url('./assets/icons/cross-medium.svg') !important;
}

-.pi-times.co {
+[data-theme='co'] .pi-times {
    content: url('./assets/icons/co/grv-close-medium.svg') !important;
}

This would create a more consistent and maintainable theming approach.

src/app/shared/components/theme-demo/theme-demo.component.ts (2)

80-84: Toast buttons lack functionality.

The toast notification buttons are present but have no click handlers or functionality implemented.

Consider adding toast functionality to complete the demo:

-<button class="p-button">Show Success Toast</button>
-<button class="p-button">Show Error Toast</button>
+<button class="p-button" (click)="showSuccessToast()">Show Success Toast</button>
+<button class="p-button" (click)="showErrorToast()">Show Error Toast</button>

Add corresponding methods in the component class to demonstrate themed toast notifications.


112-247: Comprehensive inline styling with good organization.

The inline styles are well-organized and create a clean, functional demo interface. While inline styles work well for a demo component, consider the maintainability implications for larger components.

For better maintainability, consider moving these styles to a separate SCSS file:

@Component({
  selector: 'app-theme-demo',
  templateUrl: './theme-demo.component.html',
  styleUrls: ['./theme-demo.component.scss']
})

This would improve code organization and make the styles easier to maintain.

CURRENT_THEME_MECHANISM.md (1)

14-18: Inconsistent variable naming for the Co brand configuration

featureConfigs maps the brand key co to c1FeatureConfig.
Everywhere else in the docs the Co brand is referred to as co, not c1.
This minor typo will confuse readers and, if copied verbatim, break the example code.

-    co: c1FeatureConfig
+    co: coFeatureConfig
PRIMENG_THEMING_MIGRATION_PLAN.md (1)

167-179: Phase numbering skips “Phase 6”

The document jumps from Phase 5 to Phase 7.
Either rename Phase 7 to Phase 6 or add the missing phase to keep the plan coherent.

PHASE_1_ANALYSIS.md (1)

21-35: Markdown headings flagged by MD026

Several headings end with a colon (:), triggering markdownlint rule MD026.
Drop the trailing punctuation to silence the linter and keep heading style consistent.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 955a1cb and c2d426d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (24)
  • CO_CSS_VARIABLES_USAGE.md (1 hunks)
  • CSS_VARIABLES_USAGE.md (1 hunks)
  • CURRENT_THEME_MECHANISM.md (1 hunks)
  • DESIGN_TOKEN_MAPPING.md (1 hunks)
  • PHASE_1_ANALYSIS.md (1 hunks)
  • PHASE_1_COMPLETION_SUMMARY.md (1 hunks)
  • PHASE_2_COMPLETION_SUMMARY.md (1 hunks)
  • PRESET_INTEGRATION_SOLUTION.md (1 hunks)
  • PRIMENG_THEMING_MIGRATION_PLAN.md (1 hunks)
  • package.json (1 hunks)
  • src/app/app.module.ts (4 hunks)
  • src/app/core/guard/qbo-token.guard.ts (1 hunks)
  • src/app/shared/components/dashboard/dashboard-export-section/dashboard-export-section.component.html (1 hunks)
  • src/app/shared/components/theme-demo/theme-demo.component.ts (1 hunks)
  • src/app/shared/shared.module.ts (2 hunks)
  • src/assets/scss/buttons.scss (1 hunks)
  • src/core/services/theme.service.ts (1 hunks)
  • src/styles.scss (2 hunks)
  • src/themes/co-css-vars.ts (1 hunks)
  • src/themes/co-preset.ts (1 hunks)
  • src/themes/fyle-css-vars.ts (1 hunks)
  • src/themes/fyle-preset.ts (1 hunks)
  • src/themes/index.ts (1 hunks)
  • src/themes/theme.types.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: ashwin1111
PR: fylein/fyle-integrations-app#343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable `targetSystemName` in the subLabel string within the `business-central-export-settings.component.html` file.
Learnt from: ashwin1111
PR: fylein/fyle-integrations-app#343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable `targetSystemName` in the subLabel string within the `business-central-export-settings.component.html` file.
PRESET_INTEGRATION_SOLUTION.md (2)

Learnt from: ashwin1111
PR: #343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable targetSystemName in the subLabel string within the business-central-export-settings.component.html file.

Learnt from: ashwin1111
PR: #343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable targetSystemName in the subLabel string within the business-central-export-settings.component.html file.

🧬 Code Graph Analysis (4)
src/app/shared/components/theme-demo/theme-demo.component.ts (1)
src/themes/theme.types.ts (1)
  • SupportedBrand (8-8)
src/themes/co-preset.ts (3)
src/themes/theme.types.ts (1)
  • ThemeColors (10-30)
src/themes/index.ts (2)
  • CoPreset (3-3)
  • CoCSSVars (7-7)
src/themes/co-css-vars.ts (1)
  • CoCSSVars (11-306)
src/themes/fyle-preset.ts (3)
src/themes/theme.types.ts (1)
  • ThemeColors (10-30)
src/themes/index.ts (2)
  • FylePreset (2-2)
  • FyleCSSVars (6-6)
src/themes/fyle-css-vars.ts (1)
  • FyleCSSVars (11-226)
src/themes/index.ts (6)
src/themes/theme.types.ts (1)
  • SupportedBrand (8-8)
src/themes/fyle-preset.ts (1)
  • FylePreset (41-173)
src/themes/co-preset.ts (1)
  • CoPreset (41-173)
src/core/services/theme.service.ts (1)
  • getBrandColors (128-130)
src/themes/fyle-css-vars.ts (1)
  • FyleCSSVars (11-226)
src/themes/co-css-vars.ts (1)
  • CoCSSVars (11-306)
🪛 markdownlint-cli2 (0.17.2)
CSS_VARIABLES_USAGE.md

21-21: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


43-43: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


77-77: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


85-85: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)

CO_CSS_VARIABLES_USAGE.md

9-9: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

PHASE_2_COMPLETION_SUMMARY.md

43-43: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


51-51: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)

PHASE_1_ANALYSIS.md

21-21: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


43-43: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


77-77: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


85-85: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


141-141: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


148-148: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


155-155: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


160-160: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


165-165: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


173-173: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


179-179: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


187-187: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


192-192: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


197-197: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


204-204: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


210-210: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)

🪛 LanguageTool
DESIGN_TOKEN_MAPPING.md

[uncategorized] ~451-~451: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...s */ } ``` ## Migration Checklist ### High Priority Mappings ✅ - [ ] Primary brand colors (...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[uncategorized] ~459-~459: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...aphy (font family, sizes, weights) ### Medium Priority Mappings - [ ] Dropdown and select comp...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[uncategorized] ~467-~467: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...ng - [ ] Calendar component tokens ### Low Priority Mappings - [ ] Progress spinners - [ ] ...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

🔇 Additional comments (30)
package.json (1)

41-43: Peer-dependency verification succeeded: no conflicts detected

The npm view output for all three packages returned empty peerDependencies, so they impose no strict peer ranges and won’t introduce conflicting transitive dependencies.

• @primeuix/[email protected] — no peerDependencies
• @primeuix/[email protected] — no peerDependencies
• @primeuix/[email protected] — no peerDependencies

PHASE_1_COMPLETION_SUMMARY.md (1)

1-223: Comprehensive documentation providing excellent project context.

This Phase 1 completion summary provides thorough documentation of the theming migration project, including environment assessment, implementation roadmap, and success metrics. The structured approach with clear next steps and risk mitigation strategies demonstrates good project planning.

src/app/shared/shared.module.ts (2)

105-105: Import statement looks correct.

The ThemeDemoComponent import follows the established pattern in the module.


187-188: Component declaration is correct.

The ThemeDemoComponent is properly added to the declarations array. Note that it's not exported, which means it can only be used by components within this shared module. If you need to use it in other modules, consider adding it to the exports array.

src/themes/theme.types.ts (2)

8-8: Clean union type definition.

The SupportedBrand type provides a clear constraint for brand values throughout the theming system.


10-30: Comprehensive color palette interface.

The ThemeColors interface covers a thorough set of color properties including primary variants, grayscale spectrum, and semantic colors. This provides good type safety for theme color management.

src/app/app.module.ts (1)

20-20: ThemeService import path is correct

  • Verified that src/app/app.module.ts imports ThemeService from src/core/services/theme.service.ts via ../core/services/theme.service.
  • No path adjustments needed; DI and initialization setup follow Angular best practices.
CSS_VARIABLES_USAGE.md (1)

1-136: Excellent comprehensive usage guide.

This documentation provides clear, practical guidance for developers adopting the new CSS variables system. The before/after examples and category-based organization make it very accessible.

The guide effectively bridges the gap between the technical implementation and practical developer usage, which will be invaluable for team adoption.

CO_CSS_VARIABLES_USAGE.md (1)

1-224: Excellent Co-specific documentation with clear brand differentiation.

This guide effectively explains the Co brand differences and provides practical examples for dynamic brand switching. The comparison between Fyle and Co features helps developers understand when to use each approach.

The documentation quality matches the comprehensive implementation and will greatly help with team adoption of the multi-brand theming system.

src/themes/fyle-preset.ts (2)

6-32: Well-structured legacy color mapping with proper documentation.

The color palette correctly implements the ThemeColors interface and provides clear CSS variable mapping comments for future migration. The semantic organization (primary, neutral, semantic, brand-specific) makes it easy to understand and maintain.


50-75: Primary button configuration looks comprehensive.

The button variant covers all necessary states (base, hover, disabled) with proper CSS variable references. The structure aligns well with the theming system architecture.

src/core/services/theme.service.ts (2)

103-105: Efficient kebab-case conversion implementation.

The regex-based kebab-case conversion is clean and handles the camelCase to CSS variable naming properly.


147-153: Clean toggle functionality for testing.

The toggle method provides a simple way to switch between brands for testing purposes. The implementation is straightforward and functional.

src/themes/index.ts (2)

24-31: Clean preset retrieval function with proper typing.

The getPresetByBrand function correctly uses the SupportedBrand type and returns the appropriate preset. The implementation is type-safe and straightforward.


68-89: Excellent documentation with clear usage examples.

The comprehensive documentation block provides clear examples for both brands and demonstrates different ways to access the CSS variables. This will be very helpful for developers.

src/themes/co-preset.ts (2)

120-130: Primary outline button uses appropriate Co-specific CSS variables.

Good use of Co-specific outline button variables (outlinePrimaryTextColor, outlinePrimaryBg, etc.) which provides proper brand differentiation.


8-9: No inconsistency in primaryLight mapping for Co
Co’s design system defines both primary and primaryLight using the same shade (#0070A8), and the inline comment (// --bg-brand-primary (same as primary for Co)) correctly documents that. No change is required here.

DESIGN_TOKEN_MAPPING.md (4)

8-41: Comprehensive primitive token mapping with clear brand differentiation.

The color palette definitions for both Fyle and Co brands are well-documented with proper hex values and CSS variable mapping comments. The structure clearly shows the relationship between current Tailwind variables and PrimeNG tokens.


347-382: PrimeNG preset examples may not reflect current implementation.

The documentation shows definePreset and @primeuix/themes imports, but the actual implementation files use a different structure with direct object exports. This could confuse developers.

The documentation describes PrimeNG preset usage that doesn't match the current implementation:

Documentation shows:

import { definePreset } from '@primeuix/themes';
import Aura from '@primeuix/themes/aura';

export const FylePreset = definePreset(Aura, {

But actual implementation in src/themes/fyle-preset.ts uses:

export const FylePreset = {
  name: 'fyle',
  displayName: 'Fyle Theme',
  // ...
};

Consider updating the documentation to reflect the current implementation approach or adding a note about the planned future migration.

Likely an incorrect or invalid review comment.


449-472: Well-organized migration checklist with proper prioritization.

The checklist effectively categorizes mapping tasks by priority and covers all major UI components. This provides a clear roadmap for implementation.


475-489: Solid implementation guidelines with practical considerations.

The token naming strategy, compatibility considerations, and testing strategy provide practical guidance for the migration. The emphasis on semantic naming and backward compatibility is particularly valuable.

src/styles.scss (2)

5-6: Excellent CSS architecture improvement with explicit cascade control.

The introduction of CSS layer ordering is a modern and effective approach to managing complex style precedence. This ensures predictable cascade behavior across PrimeNG, Tailwind, and brand-specific overrides.


137-137: Perfect integration with the new button architecture.

The import of the centralized button styles aligns well with the CSS layer organization and maintains separation of concerns.

PHASE_2_COMPLETION_SUMMARY.md (1)

1-92: Outstanding documentation of the theming implementation.

This documentation provides excellent coverage of the Phase 2 implementation, including clear explanations of the architectural decisions, working features, and testing instructions. The pragmatic approach is well-justified and the technical details are comprehensive.

Minor formatting improvement for markdown compliance:

-## 🎯 **What Works Now**
+## 🎯 What Works Now

-### ✅ Technical Architecture:
+### ✅ Technical Architecture

The content quality and structure make this an invaluable reference for understanding the theming system implementation.

PRESET_INTEGRATION_SOLUTION.md (1)

1-134: Comprehensive solution documentation with excellent technical detail.

This documentation effectively bridges the gap between the theoretical preset system and practical implementation. The step-by-step explanation of the theme switching flow, specific color values, and comprehensive testing instructions make this an excellent technical guide.

Key strengths:

  • Clear problem/solution structure
  • Specific code examples with actual color values
  • Multiple verification approaches (visual, technical, DevTools)
  • Practical testing checklist

The documentation accurately reflects the implementation and provides valuable guidance for testing and understanding the theming system.

src/app/shared/components/theme-demo/theme-demo.component.ts (3)

1-4: Well-structured imports with proper typing.

The imports are clean and properly typed, including the SupportedBrand type from the themes module which ensures type safety for theme operations.


7-111: Comprehensive theme demonstration template with excellent UX.

The template provides an outstanding demonstration of the theming system with multiple interactive sections:

  • Theme switching controls with visual feedback
  • Component showcase displaying various PrimeNG elements
  • Live CSS variable inspection
  • Color preview with hex values

The organization and user experience are excellent for both development and testing purposes.


249-285: Excellent component implementation with robust theme integration.

The component methods provide comprehensive theme management functionality:

  • Type-safe theme switching with SupportedBrand
  • Multiple access methods for current brand information
  • Live CSS variable inspection capabilities
  • Clean integration with the ThemeService

The getComputedCSSVar method is particularly valuable for debugging and verification purposes.

The component serves as an excellent showcase and testing tool for the theming system.

CURRENT_THEME_MECHANISM.md (1)

38-44: Gradient start colour looks like a copy-paste error

--gradient-vibrant-lg-start for the co theme is still #ff3366, which is the Fyle pink.
If the intention is to start the Co gradient with the brand blue, update the value to avoid brand-colour leakage.

PHASE_1_ANALYSIS.md (1)

29-33: Font family for Co brand contradicts earlier docs

Here the Co brand still lists aktiv-grotesk; elsewhere (CURRENT_THEME_MECHANISM.md line 48) it is "Optimist". Align the docs to avoid ambiguity.

Comment on lines +45 to +69
- [ ] Update `src/main.ts` or app configuration to include PrimeNG theming:
```typescript
import { ApplicationConfig } from '@angular/core';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { providePrimeNG } from 'primeng/config';
import Aura from '@primeuix/themes/aura';

export const appConfig: ApplicationConfig = {
providers: [
provideAnimationsAsync(),
providePrimeNG({
theme: {
preset: Aura,
options: {
prefix: 'fyle',
darkModeSelector: '[data-theme="dark"]',
cssLayer: {
name: 'primeng',
order: 'tailwind-base, primeng, tailwind-components, tailwind-utilities'
}
}
}
})
]
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Code snippet uses non-existent Angular utility

provideAnimationsAsync does not exist in current Angular versions (the API is provideAnimations / provideNoopAnimations). Leaving this as-is will mislead consumers following the guide.

-import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
+import { provideAnimations } from '@angular/platform-browser/animations';

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In PRIMENG_THEMING_MIGRATION_PLAN.md between lines 45 and 69, replace the
non-existent Angular utility provideAnimationsAsync with the correct
provideAnimations or provideNoopAnimations from
'@angular/platform-browser/animations'. Update the import statement accordingly
and adjust the providers array to use the valid function to avoid misleading
users following the guide.

state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
this.helperService.setBaseApiURL(AppUrl.QBO);
const workspaceId = this.workspaceService.getWorkspaceId();
return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Guard security checks are bypassed.

The early return true; completely bypasses all security validation including workspace ID checks and QBO token health verification. This disables the guard's primary security functionality.

If this is intentional, please explain why. Otherwise, remove this line to restore proper security checks:

-      return true;

The guard should continue with its original logic to validate workspace ID and token health.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return true;
🤖 Prompt for AI Agents
In src/app/core/guard/qbo-token.guard.ts at line 29, the early return statement
"return true;" bypasses all security checks in the guard, disabling validation
of workspace ID and QBO token health. Remove this line to ensure the guard
executes its full security validation logic as intended.

@@ -1,3 +1,4 @@
<app-theme-demo></app-theme-demo>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider conditional rendering for the theme demo component.

Adding the theme demo directly to the dashboard export section might not be appropriate for production. Consider wrapping it with a condition based on environment or feature flag.

-<app-theme-demo></app-theme-demo>
+<app-theme-demo *ngIf="environment.development || showThemeDemo"></app-theme-demo>

Add the environment import and property to the component:

import { environment } from '../../../../../environment/environment';

// In component class
showThemeDemo = false; // or controlled by feature flag
🤖 Prompt for AI Agents
In
src/app/shared/components/dashboard/dashboard-export-section/dashboard-export-section.component.html
at line 1, the <app-theme-demo> component is rendered unconditionally, which may
not be suitable for production. Modify the component to conditionally render
<app-theme-demo> based on a property like showThemeDemo. In the corresponding
TypeScript file, import the environment configuration and add a showThemeDemo
property initialized to false or controlled by a feature flag. Then update the
template to use *ngIf="showThemeDemo" on the <app-theme-demo> element.

Comment on lines +19 to +23
private initializeTheme(): void {
// Get brand from existing branding config
const brandId = brandingConfig.brandId as SupportedBrand;
this.switchToBrand(brandId);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Potential initialization issue with brandingConfig access.

The service directly accesses brandingConfig.brandId and casts it to SupportedBrand without validation. This could cause runtime errors if the brandId is invalid or undefined.

Add validation to ensure safe brand initialization:

  private initializeTheme(): void {
    // Get brand from existing branding config
-   const brandId = brandingConfig.brandId as SupportedBrand;
-   this.switchToBrand(brandId);
+   const brandId = brandingConfig.brandId;
+   if (brandId === 'fyle' || brandId === 'co') {
+     this.switchToBrand(brandId);
+   } else {
+     console.warn(`Invalid brand ID: ${brandId}, falling back to 'fyle'`);
+     this.switchToBrand('fyle');
+   }
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private initializeTheme(): void {
// Get brand from existing branding config
const brandId = brandingConfig.brandId as SupportedBrand;
this.switchToBrand(brandId);
}
private initializeTheme(): void {
// Get brand from existing branding config
const brandId = brandingConfig.brandId;
if (brandId === 'fyle' || brandId === 'co') {
this.switchToBrand(brandId);
} else {
console.warn(`Invalid brand ID: ${brandId}, falling back to 'fyle'`);
this.switchToBrand('fyle');
}
}
🤖 Prompt for AI Agents
In src/core/services/theme.service.ts around lines 19 to 23, the code directly
accesses brandingConfig.brandId and casts it to SupportedBrand without
validation, risking runtime errors if brandId is invalid or undefined. Add
validation to check if brandingConfig.brandId exists and is a valid
SupportedBrand before calling switchToBrand. If invalid, handle the case
gracefully, such as setting a default brand or logging an error, to ensure safe
theme initialization.

Comment on lines +78 to +96
private applyButtonVariantStyles(root: HTMLElement, variant: string, styles: any): void {
if (styles.base) {
Object.entries(styles.base).forEach(([prop, value]) => {
root.style.setProperty(`--preset-btn-${variant}-${this.kebabCase(prop)}`, value as string);
});
}

if (styles.hover) {
Object.entries(styles.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-btn-${variant}-hover-${this.kebabCase(prop)}`, value as string);
});
}

if (styles.disabled) {
Object.entries(styles.disabled).forEach(([prop, value]) => {
root.style.setProperty(`--preset-btn-${variant}-disabled-${this.kebabCase(prop)}`, value as string);
});
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Button variant styles method lacks type safety.

The applyButtonVariantStyles method uses any type for the styles parameter, which reduces type safety and could lead to runtime errors.

Add proper typing for better type safety:

+ interface ButtonVariantStyles {
+   base?: Record<string, string>;
+   hover?: Record<string, string>;
+   disabled?: Record<string, string>;
+ }

  private applyButtonVariantStyles(root: HTMLElement, variant: string, styles: any): void {
+   private applyButtonVariantStyles(root: HTMLElement, variant: string, styles: ButtonVariantStyles): void {

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/core/services/theme.service.ts between lines 78 and 96, the
applyButtonVariantStyles method uses the any type for the styles parameter,
which reduces type safety. Define a proper interface or type for the styles
parameter that includes optional base, hover, and disabled properties, each
being an object with string keys and string values. Replace the any type with
this new type to ensure better type safety and prevent potential runtime errors.

Comment on lines +68 to +69
boxShadow: '0 2px 4px rgba(0,0,0,0.1)' // Co doesn't have specific shadow var yet
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Inconsistent box-shadow values - hardcoded instead of CSS variables.

The Co preset uses hardcoded '0 2px 4px rgba(0,0,0,0.1)' for box shadows, while the Fyle preset uses FyleCSSVars.shadows.btnCta. This creates inconsistency and makes it harder to maintain centralized shadow values.

Use CSS variables for shadows to maintain consistency:

        hover: {
          color: CoCSSVars.buttons.primaryHoverTextColor,
          background: CoCSSVars.buttons.primaryHoverBg,
-         boxShadow: '0 2px 4px rgba(0,0,0,0.1)' // Co doesn't have specific shadow var yet
+         boxShadow: 'var(--btn-hover-shadow, 0 2px 4px rgba(0,0,0,0.1))'
        },

Consider adding shadow variables to CoCSSVars or create a fallback pattern.

Also applies to: 89-89, 109-109

🤖 Prompt for AI Agents
In src/themes/co-preset.ts at lines 68-69, 89, and 109, the boxShadow values are
hardcoded instead of using CSS variables, causing inconsistency with other
presets like Fyle. To fix this, define appropriate shadow variables in CoCSSVars
or use an existing centralized shadow variable, then replace the hardcoded
boxShadow strings with references to these CSS variables to ensure consistent
and maintainable styling.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/core/services/theme.service.ts (2)

19-23: Potential initialization issue with brandingConfig access.

The service directly accesses brandingConfig.brandId and casts it to SupportedBrand without validation. This could cause runtime errors if the brandId is invalid or undefined.

Add validation to ensure safe brand initialization:

  private initializeTheme(): void {
    // Get brand from existing branding config
-   const brandId = brandingConfig.brandId as SupportedBrand;
-   this.switchToBrand(brandId);
+   const brandId = brandingConfig.brandId;
+   if (brandId === 'fyle' || brandId === 'co') {
+     this.switchToBrand(brandId);
+   } else {
+     console.warn(`Invalid brand ID: ${brandId}, falling back to 'fyle'`);
+     this.switchToBrand('fyle');
+   }
  }

176-194: Button variant styles method lacks type safety.

The applyButtonVariantStyles method uses any type for the styles parameter, which reduces type safety and could lead to runtime errors.

Add proper typing for better type safety:

+interface ButtonVariantStyles {
+  base?: Record<string, string>;
+  hover?: Record<string, string>;
+  disabled?: Record<string, string>;
+}
+
  private applyButtonVariantStyles(root: HTMLElement, variant: string, styles: any): void {
+  private applyButtonVariantStyles(root: HTMLElement, variant: string, styles: ButtonVariantStyles): void {
🧹 Nitpick comments (2)
src/core/services/theme.service.ts (2)

62-69: Consider extracting text button styles to use the same pattern.

The text button styles are applied directly while other button variants use the helper method. For consistency, consider using a similar approach.

-    // Text button styles
-    Object.entries(buttonStyles.text.primary).forEach(([prop, value]) => {
-      root.style.setProperty(`--preset-btn-text-${this.kebabCase(prop)}`, value as string);
-    });
-
-    Object.entries(buttonStyles.text.primaryHover).forEach(([prop, value]) => {
-      root.style.setProperty(`--preset-btn-text-hover-${this.kebabCase(prop)}`, value as string);
-    });
+    // Text button styles
+    this.applyButtonVariantStyles(root, 'text', {
+      base: buttonStyles.text.primary,
+      hover: buttonStyles.text.primaryHover
+    });

209-211: Add explicit return type annotations for better type safety.

Several methods are missing explicit return type annotations, which reduces type safety and IntelliSense support.

-  getCurrentBrandColors() {
+  getCurrentBrandColors(): ReturnType<typeof getBrandColors> {
     return getBrandColors(this.currentBrand);
   }

-  getCurrentBrandPreset() {
+  getCurrentBrandPreset(): ReturnType<typeof getPresetByBrand> {
     return getPresetByBrand(this.currentBrand);
   }

-  getBrandColors(brandId: SupportedBrand) {
+  getBrandColors(brandId: SupportedBrand): ReturnType<typeof getBrandColors> {
     return getBrandColors(brandId);
   }

Also applies to: 217-219, 226-228

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c2d426d and 4ff98fd.

📒 Files selected for processing (5)
  • src/assets/scss/inputs.scss (1 hunks)
  • src/core/services/theme.service.ts (1 hunks)
  • src/styles.scss (2 hunks)
  • src/themes/co-preset.ts (1 hunks)
  • src/themes/fyle-preset.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/assets/scss/inputs.scss
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/themes/fyle-preset.ts
  • src/themes/co-preset.ts
  • src/styles.scss
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: ashwin1111
PR: fylein/fyle-integrations-app#343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable `targetSystemName` in the subLabel string within the `business-central-export-settings.component.html` file.
Learnt from: ashwin1111
PR: fylein/fyle-integrations-app#343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable `targetSystemName` in the subLabel string within the `business-central-export-settings.component.html` file.
src/core/services/theme.service.ts (2)

Learnt from: ashwin1111
PR: #343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable targetSystemName in the subLabel string within the business-central-export-settings.component.html file.

Learnt from: ashwin1111
PR: #343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable targetSystemName in the subLabel string within the business-central-export-settings.component.html file.

⏰ 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). (1)
  • GitHub Check: unit-test

Comment on lines 71 to 168
// Apply input component styles as CSS custom properties
const inputStyles = preset.components.input;

// Basic input styles
if (inputStyles.base) {
Object.entries(inputStyles.base).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-${this.kebabCase(prop)}`, value as string);
});
}

// Input focus visible styles
if (inputStyles.focusVisible) {
Object.entries(inputStyles.focusVisible).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-focus-visible-${this.kebabCase(prop)}`, value as string);
});
}

// Input focus styles
if (inputStyles.focus) {
Object.entries(inputStyles.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Input disabled styles
if (inputStyles.disabled) {
Object.entries(inputStyles.disabled).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-disabled-${this.kebabCase(prop)}`, value as string);
});
}

// Input placeholder styles
if (inputStyles.placeholder) {
Object.entries(inputStyles.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-placeholder-${this.kebabCase(prop)}`, value as string);
});
}

// Calendar input styles
if (inputStyles.calendar) {
Object.entries(inputStyles.calendar).forEach(([prop, value]) => {
if (prop !== 'focus' && prop !== 'placeholder') {
root.style.setProperty(`--preset-calendar-input-${this.kebabCase(prop)}`, value as string);
}
});

// Calendar input focus styles
if (inputStyles.calendar.focus) {
Object.entries(inputStyles.calendar.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-calendar-input-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Calendar input placeholder styles
if (inputStyles.calendar.placeholder) {
Object.entries(inputStyles.calendar.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-calendar-input-placeholder-${this.kebabCase(prop)}`, value as string);
});
}
}

// Dropdown label input styles
if (inputStyles.dropdownLabel) {
Object.entries(inputStyles.dropdownLabel).forEach(([prop, value]) => {
if (prop !== 'input') {
root.style.setProperty(`--preset-dropdown-label-${this.kebabCase(prop)}`, value as string);
}
});

// Dropdown label input specific styles
if (inputStyles.dropdownLabel.input) {
Object.entries(inputStyles.dropdownLabel.input).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-label-input-${this.kebabCase(prop)}`, value as string);
});
}
}

// Chips input styles
if (inputStyles.chips?.focus) {
Object.entries(inputStyles.chips.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-chips-input-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Input container focus styles
if (inputStyles.container?.focus) {
Object.entries(inputStyles.container.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-container-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Dropdown filter styles
if (inputStyles.dropdownFilter) {
Object.entries(inputStyles.dropdownFilter).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-filter-${this.kebabCase(prop)}`, value as string);
});
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider refactoring to reduce code duplication in input styles.

The input styles section contains significant duplication. Each input type follows a similar pattern that could be abstracted into a helper method.

+  private applyInputStyles(
+    root: HTMLElement,
+    styles: Record<string, any>,
+    prefix: string,
+    excludeKeys: string[] = []
+  ): void {
+    Object.entries(styles).forEach(([key, value]) => {
+      if (!excludeKeys.includes(key) && typeof value !== 'object') {
+        root.style.setProperty(`--preset-${prefix}-${this.kebabCase(key)}`, value as string);
+      } else if (typeof value === 'object' && !excludeKeys.includes(key)) {
+        this.applyInputStyles(root, value, `${prefix}-${this.kebabCase(key)}`);
+      }
+    });
+  }

    // Apply input component styles as CSS custom properties
    const inputStyles = preset.components.input;

-    // Basic input styles
-    if (inputStyles.base) {
-      Object.entries(inputStyles.base).forEach(([prop, value]) => {
-        root.style.setProperty(`--preset-input-${this.kebabCase(prop)}`, value as string);
-      });
-    }
-    // ... (remove all the repetitive code)
+    // Apply all input styles using the helper method
+    if (inputStyles.base) this.applyInputStyles(root, inputStyles.base, 'input');
+    if (inputStyles.focusVisible) this.applyInputStyles(root, inputStyles.focusVisible, 'input-focus-visible');
+    if (inputStyles.focus) this.applyInputStyles(root, inputStyles.focus, 'input-focus');
+    if (inputStyles.disabled) this.applyInputStyles(root, inputStyles.disabled, 'input-disabled');
+    if (inputStyles.placeholder) this.applyInputStyles(root, inputStyles.placeholder, 'input-placeholder');
+    if (inputStyles.calendar) this.applyInputStyles(root, inputStyles.calendar, 'calendar-input');
+    if (inputStyles.dropdownLabel) this.applyInputStyles(root, inputStyles.dropdownLabel, 'dropdown-label');
+    if (inputStyles.chips?.focus) this.applyInputStyles(root, inputStyles.chips.focus, 'chips-input-focus');
+    if (inputStyles.container?.focus) this.applyInputStyles(root, inputStyles.container.focus, 'input-container-focus');
+    if (inputStyles.dropdownFilter) this.applyInputStyles(root, inputStyles.dropdownFilter, 'dropdown-filter');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Apply input component styles as CSS custom properties
const inputStyles = preset.components.input;
// Basic input styles
if (inputStyles.base) {
Object.entries(inputStyles.base).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-${this.kebabCase(prop)}`, value as string);
});
}
// Input focus visible styles
if (inputStyles.focusVisible) {
Object.entries(inputStyles.focusVisible).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-focus-visible-${this.kebabCase(prop)}`, value as string);
});
}
// Input focus styles
if (inputStyles.focus) {
Object.entries(inputStyles.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-focus-${this.kebabCase(prop)}`, value as string);
});
}
// Input disabled styles
if (inputStyles.disabled) {
Object.entries(inputStyles.disabled).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-disabled-${this.kebabCase(prop)}`, value as string);
});
}
// Input placeholder styles
if (inputStyles.placeholder) {
Object.entries(inputStyles.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-placeholder-${this.kebabCase(prop)}`, value as string);
});
}
// Calendar input styles
if (inputStyles.calendar) {
Object.entries(inputStyles.calendar).forEach(([prop, value]) => {
if (prop !== 'focus' && prop !== 'placeholder') {
root.style.setProperty(`--preset-calendar-input-${this.kebabCase(prop)}`, value as string);
}
});
// Calendar input focus styles
if (inputStyles.calendar.focus) {
Object.entries(inputStyles.calendar.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-calendar-input-focus-${this.kebabCase(prop)}`, value as string);
});
}
// Calendar input placeholder styles
if (inputStyles.calendar.placeholder) {
Object.entries(inputStyles.calendar.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-calendar-input-placeholder-${this.kebabCase(prop)}`, value as string);
});
}
}
// Dropdown label input styles
if (inputStyles.dropdownLabel) {
Object.entries(inputStyles.dropdownLabel).forEach(([prop, value]) => {
if (prop !== 'input') {
root.style.setProperty(`--preset-dropdown-label-${this.kebabCase(prop)}`, value as string);
}
});
// Dropdown label input specific styles
if (inputStyles.dropdownLabel.input) {
Object.entries(inputStyles.dropdownLabel.input).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-label-input-${this.kebabCase(prop)}`, value as string);
});
}
}
// Chips input styles
if (inputStyles.chips?.focus) {
Object.entries(inputStyles.chips.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-chips-input-focus-${this.kebabCase(prop)}`, value as string);
});
}
// Input container focus styles
if (inputStyles.container?.focus) {
Object.entries(inputStyles.container.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-container-focus-${this.kebabCase(prop)}`, value as string);
});
}
// Dropdown filter styles
if (inputStyles.dropdownFilter) {
Object.entries(inputStyles.dropdownFilter).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-filter-${this.kebabCase(prop)}`, value as string);
});
}
}
// Add this helper method to the class
private applyInputStyles(
root: HTMLElement,
styles: Record<string, any>,
prefix: string,
excludeKeys: string[] = []
): void {
Object.entries(styles).forEach(([key, value]) => {
if (!excludeKeys.includes(key) && typeof value !== 'object') {
root.style.setProperty(`--preset-${prefix}-${this.kebabCase(key)}`, value as string);
} else if (typeof value === 'object' && !excludeKeys.includes(key)) {
this.applyInputStyles(root, value, `${prefix}-${this.kebabCase(key)}`);
}
});
}
// … inside applyPresetStyles, replace the big input‐styles block with:
// Apply input component styles as CSS custom properties
const inputStyles = preset.components.input;
// Apply all input styles using the helper method
if (inputStyles.base) this.applyInputStyles(root, inputStyles.base, 'input');
if (inputStyles.focusVisible) this.applyInputStyles(root, inputStyles.focusVisible, 'input-focus-visible');
if (inputStyles.focus) this.applyInputStyles(root, inputStyles.focus, 'input-focus');
if (inputStyles.disabled) this.applyInputStyles(root, inputStyles.disabled, 'input-disabled');
if (inputStyles.placeholder) this.applyInputStyles(root, inputStyles.placeholder, 'input-placeholder');
if (inputStyles.calendar) this.applyInputStyles(root, inputStyles.calendar, 'calendar-input');
if (inputStyles.dropdownLabel) this.applyInputStyles(root, inputStyles.dropdownLabel, 'dropdown-label');
if (inputStyles.chips?.focus) this.applyInputStyles(root, inputStyles.chips.focus, 'chips-input-focus');
if (inputStyles.container?.focus)this.applyInputStyles(root, inputStyles.container.focus,'input-container-focus');
if (inputStyles.dropdownFilter) this.applyInputStyles(root, inputStyles.dropdownFilter, 'dropdown-filter');
🤖 Prompt for AI Agents
In src/core/services/theme.service.ts between lines 71 and 168, the code for
applying input component styles is highly repetitive, with similar loops for
each input style category. Refactor by creating a helper function that takes the
style object, a prefix string for the CSS variable, and optionally keys to
exclude, then iterates over the entries to set the CSS custom properties.
Replace the repeated code blocks with calls to this helper function to reduce
duplication and improve maintainability.

Comment on lines +245 to +251
/**
* Toggle between brands (useful for testing)
*/
toggleBrand(): void {
const newBrand: SupportedBrand = this.currentBrand === 'fyle' ? 'co' : 'fyle';
this.switchToBrand(newBrand);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider restricting toggleBrand to development environments.

The toggleBrand method is marked as useful for testing. Consider whether this should be available in production builds.


🌐 Web query:

Angular best practices for exposing testing utilities in production builds

💡 Result:

The prevailing best practice is to avoid exposing testing utilities or test-only code in Angular production builds. Testing utilities—such as mocks, helpers, test harnesses, or debug functions—should remain strictly in development and test environments to prevent security risks, code bloat, and performance degradation[1][3][4].

Key practices to prevent unintended exposure in production:

  • Isolate test utilities in .spec.ts files or dedicated testing/ folders not referenced in your app’s main TypeScript files, ensuring the Angular compiler and build tools do not include them in the final bundle[1][5].
  • Do not import test-specific helpers or mocks in production modules. Only include them within your test files[10].
  • Leverage Angular CLI and environment configurations. Use file replacements (in angular.json) to swap environment or API files for testing, so only the intended logic is included in production builds[2].
  • Use strict linting and reviews to enforce separation of test and production code. Accidental exposure often results from code reuse or misconfigured imports[9].

Potential exceptions and alternatives:

  • If diagnostics or debug features are essential in production (such as feature flags or logging), ensure they are controlled via environment variables or build-time replacement and are safe for public access, with no test-only interfaces or internal state manipulation exposed[2].
  • Automated checks in CI/CD pipelines can validate the production bundle, confirming that test utilities are not present[8].

Summary of risks with exposing test utilities in production:

  • Security vulnerabilities: Test utilities often expose internal logic, methods, or state that should not be public.
  • Larger bundle size: Unused test code leads to increased download and parse times for users.
  • Maintenance confusion: Debug-only or test-only APIs in production code reduce maintainability and clarity.

All major Angular testing best practice guides reinforce this separation; test code should be completely “tree-shaken” out by the time your application is shipped to production[1][3][4][10].

Caveat:
If for some exceptional requirement you must share certain utilities across dev and prod (rare for testing), place them in well-scoped shared modules and protect usage with run-time checks or build-time file replacements. However, this pattern is generally discouraged by the Angular community and major enterprise teams[1][10].

Citations:


Restrict toggleBrand to development builds

The toggleBrand method is intended purely for testing and shouldn’t ship in your production bundle (it exposes a debug interface that can bloat the build and surface internal logic).

Please update src/core/services/theme.service.ts to guard or remove this API in production:

  • Wrap its body (or its entire definition) in an environment check:
    import { environment } from 'src/environments/environment';
    
    toggleBrand(): void {
      if (environment.production) {
        return;
      }
      const newBrand: SupportedBrand = this.currentBrand === 'fyle' ? 'co' : 'fyle';
      this.switchToBrand(newBrand);
    }
  • Or move the method into a dev-only subclass/module and use Angular CLI file replacements (angular.json) to omit it from your production build.
  • Verify your CI/CD pipeline confirms no debug/test methods remain in the production bundle.
🤖 Prompt for AI Agents
In src/core/services/theme.service.ts around lines 245 to 251, the toggleBrand
method is intended only for testing and should not be included in production
builds. To fix this, import the environment configuration and wrap the method
body with a check that returns immediately if environment.production is true,
preventing execution in production. Alternatively, move the method to a
development-only module or subclass and configure Angular CLI file replacements
to exclude it from production builds. Ensure the CI/CD pipeline verifies no
debug methods are present in the production bundle.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
src/core/services/theme.service.ts (3)

19-23: Potential initialization issue with brandingConfig access.

The service directly accesses brandingConfig.brandId and casts it to SupportedBrand without validation. This could cause runtime errors if the brandId is invalid or undefined.

Add validation to ensure safe brand initialization:

  private initializeTheme(): void {
    // Get brand from existing branding config
-   const brandId = brandingConfig.brandId as SupportedBrand;
-   this.switchToBrand(brandId);
+   const brandId = brandingConfig.brandId;
+   if (brandId === 'fyle' || brandId === 'co') {
+     this.switchToBrand(brandId);
+   } else {
+     console.warn(`Invalid brand ID: ${brandId}, falling back to 'fyle'`);
+     this.switchToBrand('fyle');
+   }
  }

277-295: Button variant styles method lacks type safety.

The applyButtonVariantStyles method uses any type for the styles parameter, which reduces type safety and could lead to runtime errors.

Add proper typing for better type safety:

+ interface ButtonVariantStyles {
+   base?: Record<string, string>;
+   hover?: Record<string, string>;
+   disabled?: Record<string, string>;
+ }

- private applyButtonVariantStyles(root: HTMLElement, variant: string, styles: any): void {
+ private applyButtonVariantStyles(root: HTMLElement, variant: string, styles: ButtonVariantStyles): void {

349-352: Restrict toggleBrand to development builds

The toggleBrand method is intended purely for testing and shouldn't ship in your production bundle (it exposes a debug interface that can bloat the build and surface internal logic).

Please update src/core/services/theme.service.ts to guard or remove this API in production:

  • Wrap its body (or its entire definition) in an environment check:
    import { environment } from 'src/environments/environment';
    
    toggleBrand(): void {
      if (environment.production) {
        return;
      }
      const newBrand: SupportedBrand = this.currentBrand === 'fyle' ? 'co' : 'fyle';
      this.switchToBrand(newBrand);
    }
  • Or move the method into a dev-only subclass/module and use Angular CLI file replacements (angular.json) to omit it from your production build.
  • Verify your CI/CD pipeline confirms no debug/test methods remain in the production bundle.
src/themes/co-preset.ts (1)

68-69: Inconsistent box-shadow values - hardcoded instead of CSS variables.

The Co preset uses hardcoded '0 2px 4px rgba(0,0,0,0.1)' for box shadows, while the Fyle preset uses FyleCSSVars.shadows.btnCta. This creates inconsistency and makes it harder to maintain centralized shadow values.

Use CSS variables for shadows to maintain consistency:

        hover: {
          color: CoCSSVars.buttons.primaryHoverTextColor,
          background: CoCSSVars.buttons.primaryHoverBg,
-         boxShadow: '0 2px 4px rgba(0,0,0,0.1)' // Co doesn't have specific shadow var yet
+         boxShadow: 'var(--btn-hover-shadow, 0 2px 4px rgba(0,0,0,0.1))'
        },

Consider adding shadow variables to CoCSSVars or create a fallback pattern.

Also applies to: 89-89, 109-109

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ff98fd and 85785c0.

📒 Files selected for processing (8)
  • src/assets/scss/toggles.scss (1 hunks)
  • src/assets/scss/tooltips.scss (1 hunks)
  • src/core/services/theme.service.ts (1 hunks)
  • src/styles.scss (3 hunks)
  • src/themes/co-css-vars.ts (1 hunks)
  • src/themes/co-preset.ts (1 hunks)
  • src/themes/fyle-css-vars.ts (1 hunks)
  • src/themes/fyle-preset.ts (1 hunks)
✅ Files skipped from review due to trivial changes (4)
  • src/assets/scss/tooltips.scss
  • src/assets/scss/toggles.scss
  • src/themes/co-css-vars.ts
  • src/themes/fyle-css-vars.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/styles.scss
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: ashwin1111
PR: fylein/fyle-integrations-app#343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable `targetSystemName` in the subLabel string within the `business-central-export-settings.component.html` file.
Learnt from: ashwin1111
PR: fylein/fyle-integrations-app#343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable `targetSystemName` in the subLabel string within the `business-central-export-settings.component.html` file.
src/core/services/theme.service.ts (4)

Learnt from: ashwin1111
PR: #343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable targetSystemName in the subLabel string within the business-central-export-settings.component.html file.

Learnt from: ashwin1111
PR: #343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable targetSystemName in the subLabel string within the business-central-export-settings.component.html file.

Learnt from: ashwin1111
PR: #298
File: src/app/shared/components/export-log/skipped-export-log-table/skipped-export-log-table.component.html:21-22
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has requested the removal of a condition that was always evaluating to false (*ngIf="false"), which was making an h5 element never visible. This indicates a preference for removing redundant or placeholder code that is not currently in use.

Learnt from: ashwin1111
PR: #298
File: src/app/shared/components/export-log/skipped-export-log-table/skipped-export-log-table.component.html:21-22
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has requested the removal of a condition that was always evaluating to false (*ngIf="false"), which was making an h5 element never visible. This indicates a preference for removing redundant or placeholder code that is not currently in use.

🧬 Code Graph Analysis (2)
src/core/services/theme.service.ts (3)
src/themes/theme.types.ts (1)
  • SupportedBrand (8-8)
src/app/branding/branding-config.ts (1)
  • brandingConfig (11-11)
src/themes/index.ts (2)
  • getPresetByBrand (24-31)
  • getBrandColors (49-52)
src/themes/co-preset.ts (3)
src/themes/theme.types.ts (1)
  • ThemeColors (10-30)
src/themes/index.ts (2)
  • CoPreset (3-3)
  • CoCSSVars (7-7)
src/themes/co-css-vars.ts (1)
  • CoCSSVars (11-323)
🪛 GitHub Check: lint
src/core/services/theme.service.ts

[failure] 166-166:
Expected { after 'if' condition


[failure] 142-142:
Expected { after 'if' condition

🪛 GitHub Actions: TypeScript Lint Check
src/core/services/theme.service.ts

[error] 142-142: Expected { after 'if' condition. Rule: curly

🔇 Additional comments (8)
src/themes/fyle-preset.ts (6)

1-32: LGTM! Well-structured color palette with clear documentation.

The legacy color palette is properly documented with CSS variable references, making the migration path clear. The color mappings are consistent with the design token strategy.


34-48: Well-documented preset structure with clear integration roadmap.

The preset object is properly structured with essential properties and includes helpful documentation about future PrimeNG integration plans.


50-165: Comprehensive button styling with consistent CSS variable usage.

Button variants are well-structured with proper state handling (base, hover, disabled) and consistent use of design tokens from FyleCSSVars.


167-253: Thorough input component styling with specialized variant support.

The input styles comprehensively cover all states and specialized input types (calendar, dropdown, chips) with consistent CSS variable usage.


254-278: Complete tooltip styling with proper positioning support.

Tooltip styles are well-defined with text, arrow, and positioning configurations using appropriate CSS variables.


279-329: Well-structured toggle component with complete state handling.

Toggle styles properly handle all states (checked/unchecked) with appropriate transforms and text content using CSS variables.

src/themes/co-preset.ts (2)

1-32: LGTM! Co brand color palette properly defined.

The Co brand color palette is well-documented with appropriate CSS variable references and brand-specific color values.


284-291: Co brand toggle customizations are appropriately differentiated.

The Co toggle has distinct styling with hidden text labels and different circle positioning, which correctly reflects the brand's design requirements.

Also applies to: 315-329

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (7)
src/core/services/theme.service.ts (6)

19-23: Potential initialization issue with brandingConfig access.

The service directly accesses brandingConfig.brandId and casts it to SupportedBrand without validation. This could cause runtime errors if the brandId is invalid or undefined.


681-699: Button variant styles method lacks type safety.

The applyButtonVariantStyles method uses any type for the styles parameter, which reduces type safety and could lead to runtime errors.


82-144: Consider refactoring to reduce code duplication in input styles.

The input styles section contains significant duplication. Each input type follows a similar pattern that could be abstracted into a helper method.


750-756: Restrict toggleBrand to development builds

The toggleBrand method is intended purely for testing and shouldn't ship in your production bundle (it exposes a debug interface that can bloat the build and surface internal logic).


173-175: Add braces to single-line if statements.

The linter correctly identifies missing braces on guard clauses. This is required by the project's style guide.

-    if (!dropdownLabelStyles) {
-return;
-}
+    if (!dropdownLabelStyles) {
+      return;
+    }

147-149: Add braces to single-line if statements.

The linter correctly identifies missing braces on guard clauses. This is required by the project's style guide.

-    if (!calendarStyles) {
-return;
-}
+    if (!calendarStyles) {
+      return;
+    }
src/themes/co-preset.ts (1)

68-69: Inconsistent box-shadow values - hardcoded instead of CSS variables.

The Co preset uses hardcoded '0 2px 4px rgba(0,0,0,0.1)' for box shadows, while the Fyle preset uses FyleCSSVars.shadows.btnCta. This creates inconsistency and makes it harder to maintain centralized shadow values.

🧹 Nitpick comments (3)
src/core/services/theme.service.ts (1)

706-708: Consider using a utility library for case conversion.

Instead of implementing custom kebab-case conversion, consider using a well-tested utility library like lodash.kebabcase or similar.

+import { kebabCase } from 'lodash-es';

-  private kebabCase(str: string): string {
-    return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
-  }

Then replace all instances of this.kebabCase(...) with kebabCase(...).

src/themes/fyle-preset.ts (1)

537-550: Fix indentation inconsistency.

The panel property has inconsistent indentation compared to other properties at the same level.

-              panel: {
-          items: {
+      panel: {
+        items: {
src/themes/co-preset.ts (1)

539-552: Fix indentation inconsistency.

The panel property has inconsistent indentation compared to other properties at the same level.

-              panel: {
-          items: {
+      panel: {
+        items: {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9c5a15 and 1fe05e3.

📒 Files selected for processing (7)
  • src/assets/scss/checkboxes.scss (1 hunks)
  • src/assets/scss/dropdowns.scss (1 hunks)
  • src/assets/scss/multiselect.scss (1 hunks)
  • src/core/services/theme.service.ts (1 hunks)
  • src/styles.scss (2 hunks)
  • src/themes/co-preset.ts (1 hunks)
  • src/themes/fyle-preset.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • src/assets/scss/dropdowns.scss
  • src/assets/scss/checkboxes.scss
  • src/assets/scss/multiselect.scss
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/styles.scss
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: ashwin1111
PR: fylein/fyle-integrations-app#343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable `targetSystemName` in the subLabel string within the `business-central-export-settings.component.html` file.
Learnt from: ashwin1111
PR: fylein/fyle-integrations-app#343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable `targetSystemName` in the subLabel string within the `business-central-export-settings.component.html` file.
src/core/services/theme.service.ts (4)

Learnt from: ashwin1111
PR: #343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable targetSystemName in the subLabel string within the business-central-export-settings.component.html file.

Learnt from: ashwin1111
PR: #343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable targetSystemName in the subLabel string within the business-central-export-settings.component.html file.

Learnt from: ashwin1111
PR: #298
File: src/app/shared/components/export-log/skipped-export-log-table/skipped-export-log-table.component.html:21-22
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has requested the removal of a condition that was always evaluating to false (*ngIf="false"), which was making an h5 element never visible. This indicates a preference for removing redundant or placeholder code that is not currently in use.

Learnt from: ashwin1111
PR: #298
File: src/app/shared/components/export-log/skipped-export-log-table/skipped-export-log-table.component.html:21-22
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has requested the removal of a condition that was always evaluating to false (*ngIf="false"), which was making an h5 element never visible. This indicates a preference for removing redundant or placeholder code that is not currently in use.

🧬 Code Graph Analysis (1)
src/core/services/theme.service.ts (3)
src/themes/theme.types.ts (1)
  • SupportedBrand (8-8)
src/app/branding/branding-config.ts (1)
  • brandingConfig (11-11)
src/themes/index.ts (2)
  • getPresetByBrand (24-31)
  • getBrandColors (49-52)
🔇 Additional comments (1)
src/themes/fyle-preset.ts (1)

326-327: Use Fyle CSS variables instead of Co variables.

The toggle text content references CoCSSVars but should use FyleCSSVars since this is the Fyle preset.

-        checkedContent: CoCSSVars.toggle.textYes,
-        uncheckedContent: CoCSSVars.toggle.textNo,
+        checkedContent: FyleCSSVars.toggle.textYes,
+        uncheckedContent: FyleCSSVars.toggle.textNo,
⛔ Skipped due to learnings
Learnt from: ashwin1111
PR: fylein/fyle-integrations-app#326
File: src/app/shared/components/configuration/configuration-connector/configuration-connector.component.html:46-46
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user confirmed the correction of a typo from `switchLinkTex` to `switchLinkText`.

Comment on lines +64 to +673
private applyButtonStyles(root: HTMLElement, buttonStyles: any): void {
// Primary button styles
this.applyButtonVariantStyles(root, 'primary', buttonStyles.primary);
this.applyButtonVariantStyles(root, 'secondary', buttonStyles.secondary);
this.applyButtonVariantStyles(root, 'outline', buttonStyles.outline);
this.applyButtonVariantStyles(root, 'primary-outline', buttonStyles.primaryOutline);
this.applyButtonVariantStyles(root, 'danger-outline', buttonStyles.dangerOutline);

// Text button styles
Object.entries(buttonStyles.text.primary).forEach(([prop, value]) => {
root.style.setProperty(`--preset-btn-text-${this.kebabCase(prop)}`, value as string);
});

Object.entries(buttonStyles.text.primaryHover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-btn-text-hover-${this.kebabCase(prop)}`, value as string);
});
}

private applyInputStyles(root: HTMLElement, inputStyles: any): void {
// Basic input styles
if (inputStyles.base) {
Object.entries(inputStyles.base).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-${this.kebabCase(prop)}`, value as string);
});
}

// Input focus visible styles
if (inputStyles.focusVisible) {
Object.entries(inputStyles.focusVisible).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-focus-visible-${this.kebabCase(prop)}`, value as string);
});
}

// Input focus styles
if (inputStyles.focus) {
Object.entries(inputStyles.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Input disabled styles
if (inputStyles.disabled) {
Object.entries(inputStyles.disabled).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-disabled-${this.kebabCase(prop)}`, value as string);
});
}

// Input placeholder styles
if (inputStyles.placeholder) {
Object.entries(inputStyles.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-placeholder-${this.kebabCase(prop)}`, value as string);
});
}

// Calendar input styles
this.applyCalendarInputStyles(root, inputStyles.calendar);

// Dropdown label input styles
this.applyDropdownLabelStyles(root, inputStyles.dropdownLabel);

// Chips input styles
if (inputStyles.chips?.focus) {
Object.entries(inputStyles.chips.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-chips-input-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Input container focus styles
if (inputStyles.container?.focus) {
Object.entries(inputStyles.container.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-container-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Dropdown filter styles
if (inputStyles.dropdownFilter) {
Object.entries(inputStyles.dropdownFilter).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-filter-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyCalendarInputStyles(root: HTMLElement, calendarStyles: any): void {
if (!calendarStyles) {
return;
}

Object.entries(calendarStyles).forEach(([prop, value]) => {
if (prop !== 'focus' && prop !== 'placeholder') {
root.style.setProperty(`--preset-calendar-input-${this.kebabCase(prop)}`, value as string);
}
});

// Calendar input focus styles
if (calendarStyles.focus) {
Object.entries(calendarStyles.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-calendar-input-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Calendar input placeholder styles
if (calendarStyles.placeholder) {
Object.entries(calendarStyles.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-calendar-input-placeholder-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyDropdownLabelStyles(root: HTMLElement, dropdownLabelStyles: any): void {
if (!dropdownLabelStyles) {
return;
}

Object.entries(dropdownLabelStyles).forEach(([prop, value]) => {
if (prop !== 'input') {
root.style.setProperty(`--preset-dropdown-label-${this.kebabCase(prop)}`, value as string);
}
});

// Dropdown label input specific styles
if (dropdownLabelStyles.input) {
Object.entries(dropdownLabelStyles.input).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-label-input-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyTooltipStyles(root: HTMLElement, tooltipStyles: any): void {
// Tooltip text styles
if (tooltipStyles.text) {
Object.entries(tooltipStyles.text).forEach(([prop, value]) => {
root.style.setProperty(`--preset-tooltip-text-${this.kebabCase(prop)}`, value as string);
});
}

// Tooltip arrow styles
if (tooltipStyles.arrow) {
Object.entries(tooltipStyles.arrow).forEach(([prop, value]) => {
root.style.setProperty(`--preset-tooltip-arrow-${this.kebabCase(prop)}`, value as string);
});
}

// Tooltip bottom specific styles
if (tooltipStyles.bottom) {
Object.entries(tooltipStyles.bottom).forEach(([prop, value]) => {
if (prop === 'arrow') {
// Handle nested arrow styles
Object.entries(value as any).forEach(([arrowProp, arrowValue]) => {
root.style.setProperty(`--preset-tooltip-bottom-arrow-${this.kebabCase(arrowProp)}`, arrowValue as string);
});
} else {
root.style.setProperty(`--preset-tooltip-bottom-${this.kebabCase(prop)}`, value as string);
}
});
}
}

private applyToggleStyles(root: HTMLElement, toggleStyles: any): void {
// Base toggle styles
if (toggleStyles.height) {
root.style.setProperty('--preset-toggle-height', toggleStyles.height);
}
if (toggleStyles.width) {
root.style.setProperty('--preset-toggle-width', toggleStyles.width);
}

// Toggle circle styles
if (toggleStyles.circle) {
Object.entries(toggleStyles.circle).forEach(([prop, value]) => {
if (value !== undefined && value !== null) {
root.style.setProperty(`--preset-toggle-circle-${this.kebabCase(prop)}`, value as string);
}
});
}

// Toggle checked state styles
if (toggleStyles.checked) {
// Handle nested circle transform
if (toggleStyles.checked.circle?.transform) {
root.style.setProperty('--preset-toggle-checked-circle-transform', toggleStyles.checked.circle.transform);
}

// Handle other checked properties
if (toggleStyles.checked.background) {
root.style.setProperty('--preset-toggle-checked-background', toggleStyles.checked.background);
}
if (toggleStyles.checked.width) {
root.style.setProperty('--preset-toggle-checked-width', toggleStyles.checked.width);
}
if (toggleStyles.checked.height) {
root.style.setProperty('--preset-toggle-checked-height', toggleStyles.checked.height);
}
}

// Toggle unchecked state styles
if (toggleStyles.unchecked) {
Object.entries(toggleStyles.unchecked).forEach(([prop, value]) => {
root.style.setProperty(`--preset-toggle-unchecked-${this.kebabCase(prop)}`, value as string);
});
}

// Toggle focus styles
if (toggleStyles.focus) {
Object.entries(toggleStyles.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-toggle-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Toggle text styles
if (toggleStyles.text) {
Object.entries(toggleStyles.text).forEach(([prop, value]) => {
root.style.setProperty(`--preset-toggle-text-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyRadioStyles(root: HTMLElement, radioStyles: any): void {
// Base radio styles
if (radioStyles.width) {
root.style.setProperty('--preset-radio-width', radioStyles.width);
}
if (radioStyles.height) {
root.style.setProperty('--preset-radio-height', radioStyles.height);
}

// Radio box styles
if (radioStyles.box) {
Object.entries(radioStyles.box).forEach(([prop, value]) => {
if (prop !== 'hover' && prop !== 'highlight') {
root.style.setProperty(`--preset-radio-box-${this.kebabCase(prop)}`, value as string);
}
});

// Radio box hover styles
if (radioStyles.box.hover) {
Object.entries(radioStyles.box.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-radio-box-hover-${this.kebabCase(prop)}`, value as string);
});
}

// Radio box highlight styles
if (radioStyles.box.highlight) {
Object.entries(radioStyles.box.highlight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-radio-box-highlight-${this.kebabCase(prop)}`, value as string);
});
}
}

// Radio icon styles
if (radioStyles.icon) {
Object.entries(radioStyles.icon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-radio-icon-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyDialogStyles(root: HTMLElement, dialogStyles: any): void {
// Dialog content styles
if (dialogStyles.content) {
Object.entries(dialogStyles.content).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-content-${this.kebabCase(prop)}`, value as string);
});
}

// Dialog header styles
if (dialogStyles.header) {
Object.entries(dialogStyles.header).forEach(([prop, value]) => {
if (prop !== 'title' && prop !== 'button') {
root.style.setProperty(`--preset-dialog-header-${this.kebabCase(prop)}`, value as string);
}
});

// Dialog header title styles
if (dialogStyles.header.title) {
Object.entries(dialogStyles.header.title).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-title-${this.kebabCase(prop)}`, value as string);
});
}

// Dialog header button styles
if (dialogStyles.header.button) {
Object.entries(dialogStyles.header.button).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-header-button-${this.kebabCase(prop)}`, value as string);
});
}
}

// Dialog top-right positioning styles
if (dialogStyles.topRight) {
Object.entries(dialogStyles.topRight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-top-right-${this.kebabCase(prop)}`, value as string);
});
}

// Dialog close icon styles
if (dialogStyles.closeIcon) {
Object.entries(dialogStyles.closeIcon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-close-icon-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyCheckboxStyles(root: HTMLElement, checkboxStyles: any): void {
// Base checkbox styles
if (checkboxStyles.height) {
root.style.setProperty('--preset-checkbox-height', checkboxStyles.height);
}
if (checkboxStyles.width) {
root.style.setProperty('--preset-checkbox-width', checkboxStyles.width);
}

// Checkbox box styles
if (checkboxStyles.box) {
Object.entries(checkboxStyles.box).forEach(([prop, value]) => {
if (prop !== 'hover' && prop !== 'highlight') {
root.style.setProperty(`--preset-checkbox-box-${this.kebabCase(prop)}`, value as string);
}
});

// Checkbox box hover styles
if (checkboxStyles.box.hover) {
Object.entries(checkboxStyles.box.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-box-hover-${this.kebabCase(prop)}`, value as string);
});
}

// Checkbox box highlight styles
if (checkboxStyles.box.highlight) {
Object.entries(checkboxStyles.box.highlight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-box-highlight-${this.kebabCase(prop)}`, value as string);
});
}
}

// Checkbox icon styles
if (checkboxStyles.icon) {
Object.entries(checkboxStyles.icon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-icon-${this.kebabCase(prop)}`, value as string);
});
}

// Checkbox check icon styles (for pi-check)
if (checkboxStyles.checkIcon) {
Object.entries(checkboxStyles.checkIcon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-check-icon-${this.kebabCase(prop)}`, value as string);
});
}

// Multiselect checkbox styles
if (checkboxStyles.multiselect) {
Object.entries(checkboxStyles.multiselect).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-multiselect-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyMultiselectStyles(root: HTMLElement, multiselectStyles: any): void {
// Base multiselect styles
const baseProps = ['height', 'display', 'alignItems', 'width', 'border', 'borderColor', 'borderRadius', 'paddingRight'];
baseProps.forEach(prop => {
if (multiselectStyles[prop]) {
root.style.setProperty(`--preset-multiselect-${this.kebabCase(prop)}`, multiselectStyles[prop]);
}
});

// Trigger styles
if (multiselectStyles.trigger) {
Object.entries(multiselectStyles.trigger).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-trigger-${this.kebabCase(prop)}`, value as string);
});
}

// Label styles
if (multiselectStyles.label) {
Object.entries(multiselectStyles.label).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-label-${this.kebabCase(prop)}`, value as string);
});
}

// Focus styles
if (multiselectStyles.focus) {
Object.entries(multiselectStyles.focus).forEach(([prop, value]) => {
if (prop !== 'enabled') {
root.style.setProperty(`--preset-multiselect-focus-${this.kebabCase(prop)}`, value as string);
}
});

if (multiselectStyles.focus.enabled) {
Object.entries(multiselectStyles.focus.enabled).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-focus-enabled-${this.kebabCase(prop)}`, value as string);
});
}
}

// Panel styles
if (multiselectStyles.panel) {
Object.entries(multiselectStyles.panel).forEach(([prop, value]) => {
if (prop !== 'header') {
root.style.setProperty(`--preset-multiselect-panel-${this.kebabCase(prop)}`, value as string);
}
});

if (multiselectStyles.panel.header) {
Object.entries(multiselectStyles.panel.header).forEach(([prop, value]) => {
if (prop !== 'checkbox') {
root.style.setProperty(`--preset-multiselect-panel-header-${this.kebabCase(prop)}`, value as string);
}
});

if (multiselectStyles.panel.header.checkbox) {
Object.entries(multiselectStyles.panel.header.checkbox).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-header-checkbox-${this.kebabCase(prop)}`, value as string);
});
}
}
}

// Filter container and close styles
['filterContainer', 'close'].forEach(component => {
if (multiselectStyles[component]) {
Object.entries(multiselectStyles[component]).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-${this.kebabCase(component)}-${this.kebabCase(prop)}`, value as string);
});
}
});

// Placeholder styles
if (multiselectStyles.placeholder) {
Object.entries(multiselectStyles.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-placeholder-${this.kebabCase(prop)}`, value as string);
});
}

// Item styles
if (multiselectStyles.item) {
const itemBaseProps = ['fontSize', 'color', 'paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom'];
itemBaseProps.forEach(prop => {
if (multiselectStyles.item[prop]) {
root.style.setProperty(`--preset-multiselect-item-${this.kebabCase(prop)}`, multiselectStyles.item[prop]);
}
});

// Item highlight styles
if (multiselectStyles.item.highlight) {
Object.entries(multiselectStyles.item.highlight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-item-highlight-${this.kebabCase(prop)}`, value as string);
});
}

// Item hover styles
if (multiselectStyles.item.hover) {
Object.entries(multiselectStyles.item.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-item-hover-${this.kebabCase(prop)}`, value as string);
});
}

// Item focus styles
if (multiselectStyles.item.focus) {
Object.entries(multiselectStyles.item.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-item-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Item general hover styles
if (multiselectStyles.item.generalHover) {
Object.entries(multiselectStyles.item.generalHover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-item-general-hover-${this.kebabCase(prop)}`, value as string);
});
}
}

// Empty message styles
if (multiselectStyles.emptyMessage) {
Object.entries(multiselectStyles.emptyMessage).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-empty-message-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyDropdownStyles(root: HTMLElement, dropdownStyles: any): void {
// Base dropdown styles
const baseProps = ['height', 'width', 'transform', 'border', 'borderColor', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', 'borderRadius'];
baseProps.forEach(prop => {
if (dropdownStyles[prop]) {
root.style.setProperty(`--preset-dropdown-${this.kebabCase(prop)}`, dropdownStyles[prop]);
}
});

// Disabled styles
if (dropdownStyles.disabled) {
Object.entries(dropdownStyles.disabled).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-disabled-${this.kebabCase(prop)}`, value as string);
});
}

// Trigger styles
if (dropdownStyles.trigger) {
Object.entries(dropdownStyles.trigger).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-trigger-${this.kebabCase(prop)}`, value as string);
});
}

// Focus styles
if (dropdownStyles.focus) {
Object.entries(dropdownStyles.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Hover styles
if (dropdownStyles.hover) {
Object.entries(dropdownStyles.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-hover-${this.kebabCase(prop)}`, value as string);
});
}

// Placeholder styles
if (dropdownStyles.placeholder) {
Object.entries(dropdownStyles.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-placeholder-${this.kebabCase(prop)}`, value as string);
});
}

// Panel styles
if (dropdownStyles.panel) {
if (dropdownStyles.panel.items) {
Object.entries(dropdownStyles.panel.items).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-panel-items-${this.kebabCase(prop)}`, value as string);
});
}
if (dropdownStyles.panel.header) {
Object.entries(dropdownStyles.panel.header).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-panel-header-${this.kebabCase(prop)}`, value as string);
});
}
}

// Header styles (separate from panel.header)
if (dropdownStyles.header) {
Object.entries(dropdownStyles.header).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-header-${this.kebabCase(prop)}`, value as string);
});
}

// Item styles
if (dropdownStyles.item) {
const itemBaseProps = ['fontSize', 'color', 'borderRadius', 'minHeight', 'display', 'alignItems', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'];
itemBaseProps.forEach(prop => {
if (dropdownStyles.item[prop]) {
root.style.setProperty(`--preset-dropdown-item-${this.kebabCase(prop)}`, dropdownStyles.item[prop]);
}
});

// Item highlight styles
if (dropdownStyles.item.highlight) {
Object.entries(dropdownStyles.item.highlight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-item-highlight-${this.kebabCase(prop)}`, value as string);
});
}

// Item focus styles
if (dropdownStyles.item.focus) {
Object.entries(dropdownStyles.item.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-item-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Item hover styles
if (dropdownStyles.item.hover) {
Object.entries(dropdownStyles.item.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-item-hover-${this.kebabCase(prop)}`, value as string);
});
}

// Item group styles
if (dropdownStyles.item.group) {
Object.entries(dropdownStyles.item.group).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-item-group-${this.kebabCase(prop)}`, value as string);
});
}
}

// Empty message styles
if (dropdownStyles.emptyMessage) {
Object.entries(dropdownStyles.emptyMessage).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-empty-message-${this.kebabCase(prop)}`, value as string);
});
}

// Divider styles
if (dropdownStyles.divider) {
Object.entries(dropdownStyles.divider).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-divider-${this.kebabCase(prop)}`, value as string);
});
}

// Calendar input styles
if (dropdownStyles.calendarInput) {
Object.entries(dropdownStyles.calendarInput).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-calendar-input-${this.kebabCase(prop)}`, value as string);
});
}

// Filter icon styles
if (dropdownStyles.filterIcon) {
Object.entries(dropdownStyles.filterIcon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-filter-icon-${this.kebabCase(prop)}`, value as string);
});
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add type definitions for all style parameters.

All apply*Styles methods use any type for their style parameters, reducing type safety throughout the service. Consider creating proper interfaces for each component's style structure.

+interface ButtonStyles {
+  primary?: ButtonVariantStyles;
+  secondary?: ButtonVariantStyles;
+  outline?: ButtonVariantStyles;
+  primaryOutline?: ButtonVariantStyles;
+  dangerOutline?: ButtonVariantStyles;
+  text?: {
+    primary: Record<string, string>;
+    primaryHover: Record<string, string>;
+  };
+}
+
+interface ButtonVariantStyles {
+  base?: Record<string, string>;
+  hover?: Record<string, string>;
+  disabled?: Record<string, string>;
+}

-  private applyButtonStyles(root: HTMLElement, buttonStyles: any): void {
+  private applyButtonStyles(root: HTMLElement, buttonStyles: ButtonStyles): void {

Similar interfaces should be created for input, tooltip, toggle, radio, dialog, checkbox, multiselect, and dropdown styles.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private applyButtonStyles(root: HTMLElement, buttonStyles: any): void {
// Primary button styles
this.applyButtonVariantStyles(root, 'primary', buttonStyles.primary);
this.applyButtonVariantStyles(root, 'secondary', buttonStyles.secondary);
this.applyButtonVariantStyles(root, 'outline', buttonStyles.outline);
this.applyButtonVariantStyles(root, 'primary-outline', buttonStyles.primaryOutline);
this.applyButtonVariantStyles(root, 'danger-outline', buttonStyles.dangerOutline);
// Text button styles
Object.entries(buttonStyles.text.primary).forEach(([prop, value]) => {
root.style.setProperty(`--preset-btn-text-${this.kebabCase(prop)}`, value as string);
});
Object.entries(buttonStyles.text.primaryHover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-btn-text-hover-${this.kebabCase(prop)}`, value as string);
});
}
private applyInputStyles(root: HTMLElement, inputStyles: any): void {
// Basic input styles
if (inputStyles.base) {
Object.entries(inputStyles.base).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-${this.kebabCase(prop)}`, value as string);
});
}
// Input focus visible styles
if (inputStyles.focusVisible) {
Object.entries(inputStyles.focusVisible).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-focus-visible-${this.kebabCase(prop)}`, value as string);
});
}
// Input focus styles
if (inputStyles.focus) {
Object.entries(inputStyles.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-focus-${this.kebabCase(prop)}`, value as string);
});
}
// Input disabled styles
if (inputStyles.disabled) {
Object.entries(inputStyles.disabled).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-disabled-${this.kebabCase(prop)}`, value as string);
});
}
// Input placeholder styles
if (inputStyles.placeholder) {
Object.entries(inputStyles.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-placeholder-${this.kebabCase(prop)}`, value as string);
});
}
// Calendar input styles
this.applyCalendarInputStyles(root, inputStyles.calendar);
// Dropdown label input styles
this.applyDropdownLabelStyles(root, inputStyles.dropdownLabel);
// Chips input styles
if (inputStyles.chips?.focus) {
Object.entries(inputStyles.chips.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-chips-input-focus-${this.kebabCase(prop)}`, value as string);
});
}
// Input container focus styles
if (inputStyles.container?.focus) {
Object.entries(inputStyles.container.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-container-focus-${this.kebabCase(prop)}`, value as string);
});
}
// Dropdown filter styles
if (inputStyles.dropdownFilter) {
Object.entries(inputStyles.dropdownFilter).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-filter-${this.kebabCase(prop)}`, value as string);
});
}
}
private applyCalendarInputStyles(root: HTMLElement, calendarStyles: any): void {
if (!calendarStyles) {
return;
}
Object.entries(calendarStyles).forEach(([prop, value]) => {
if (prop !== 'focus' && prop !== 'placeholder') {
root.style.setProperty(`--preset-calendar-input-${this.kebabCase(prop)}`, value as string);
}
});
// Calendar input focus styles
if (calendarStyles.focus) {
Object.entries(calendarStyles.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-calendar-input-focus-${this.kebabCase(prop)}`, value as string);
});
}
// Calendar input placeholder styles
if (calendarStyles.placeholder) {
Object.entries(calendarStyles.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-calendar-input-placeholder-${this.kebabCase(prop)}`, value as string);
});
}
}
private applyDropdownLabelStyles(root: HTMLElement, dropdownLabelStyles: any): void {
if (!dropdownLabelStyles) {
return;
}
Object.entries(dropdownLabelStyles).forEach(([prop, value]) => {
if (prop !== 'input') {
root.style.setProperty(`--preset-dropdown-label-${this.kebabCase(prop)}`, value as string);
}
});
// Dropdown label input specific styles
if (dropdownLabelStyles.input) {
Object.entries(dropdownLabelStyles.input).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-label-input-${this.kebabCase(prop)}`, value as string);
});
}
}
private applyTooltipStyles(root: HTMLElement, tooltipStyles: any): void {
// Tooltip text styles
if (tooltipStyles.text) {
Object.entries(tooltipStyles.text).forEach(([prop, value]) => {
root.style.setProperty(`--preset-tooltip-text-${this.kebabCase(prop)}`, value as string);
});
}
// Tooltip arrow styles
if (tooltipStyles.arrow) {
Object.entries(tooltipStyles.arrow).forEach(([prop, value]) => {
root.style.setProperty(`--preset-tooltip-arrow-${this.kebabCase(prop)}`, value as string);
});
}
// Tooltip bottom specific styles
if (tooltipStyles.bottom) {
Object.entries(tooltipStyles.bottom).forEach(([prop, value]) => {
if (prop === 'arrow') {
// Handle nested arrow styles
Object.entries(value as any).forEach(([arrowProp, arrowValue]) => {
root.style.setProperty(`--preset-tooltip-bottom-arrow-${this.kebabCase(arrowProp)}`, arrowValue as string);
});
} else {
root.style.setProperty(`--preset-tooltip-bottom-${this.kebabCase(prop)}`, value as string);
}
});
}
}
private applyToggleStyles(root: HTMLElement, toggleStyles: any): void {
// Base toggle styles
if (toggleStyles.height) {
root.style.setProperty('--preset-toggle-height', toggleStyles.height);
}
if (toggleStyles.width) {
root.style.setProperty('--preset-toggle-width', toggleStyles.width);
}
// Toggle circle styles
if (toggleStyles.circle) {
Object.entries(toggleStyles.circle).forEach(([prop, value]) => {
if (value !== undefined && value !== null) {
root.style.setProperty(`--preset-toggle-circle-${this.kebabCase(prop)}`, value as string);
}
});
}
// Toggle checked state styles
if (toggleStyles.checked) {
// Handle nested circle transform
if (toggleStyles.checked.circle?.transform) {
root.style.setProperty('--preset-toggle-checked-circle-transform', toggleStyles.checked.circle.transform);
}
// Handle other checked properties
if (toggleStyles.checked.background) {
root.style.setProperty('--preset-toggle-checked-background', toggleStyles.checked.background);
}
if (toggleStyles.checked.width) {
root.style.setProperty('--preset-toggle-checked-width', toggleStyles.checked.width);
}
if (toggleStyles.checked.height) {
root.style.setProperty('--preset-toggle-checked-height', toggleStyles.checked.height);
}
}
// Toggle unchecked state styles
if (toggleStyles.unchecked) {
Object.entries(toggleStyles.unchecked).forEach(([prop, value]) => {
root.style.setProperty(`--preset-toggle-unchecked-${this.kebabCase(prop)}`, value as string);
});
}
// Toggle focus styles
if (toggleStyles.focus) {
Object.entries(toggleStyles.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-toggle-focus-${this.kebabCase(prop)}`, value as string);
});
}
// Toggle text styles
if (toggleStyles.text) {
Object.entries(toggleStyles.text).forEach(([prop, value]) => {
root.style.setProperty(`--preset-toggle-text-${this.kebabCase(prop)}`, value as string);
});
}
}
private applyRadioStyles(root: HTMLElement, radioStyles: any): void {
// Base radio styles
if (radioStyles.width) {
root.style.setProperty('--preset-radio-width', radioStyles.width);
}
if (radioStyles.height) {
root.style.setProperty('--preset-radio-height', radioStyles.height);
}
// Radio box styles
if (radioStyles.box) {
Object.entries(radioStyles.box).forEach(([prop, value]) => {
if (prop !== 'hover' && prop !== 'highlight') {
root.style.setProperty(`--preset-radio-box-${this.kebabCase(prop)}`, value as string);
}
});
// Radio box hover styles
if (radioStyles.box.hover) {
Object.entries(radioStyles.box.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-radio-box-hover-${this.kebabCase(prop)}`, value as string);
});
}
// Radio box highlight styles
if (radioStyles.box.highlight) {
Object.entries(radioStyles.box.highlight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-radio-box-highlight-${this.kebabCase(prop)}`, value as string);
});
}
}
// Radio icon styles
if (radioStyles.icon) {
Object.entries(radioStyles.icon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-radio-icon-${this.kebabCase(prop)}`, value as string);
});
}
}
private applyDialogStyles(root: HTMLElement, dialogStyles: any): void {
// Dialog content styles
if (dialogStyles.content) {
Object.entries(dialogStyles.content).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-content-${this.kebabCase(prop)}`, value as string);
});
}
// Dialog header styles
if (dialogStyles.header) {
Object.entries(dialogStyles.header).forEach(([prop, value]) => {
if (prop !== 'title' && prop !== 'button') {
root.style.setProperty(`--preset-dialog-header-${this.kebabCase(prop)}`, value as string);
}
});
// Dialog header title styles
if (dialogStyles.header.title) {
Object.entries(dialogStyles.header.title).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-title-${this.kebabCase(prop)}`, value as string);
});
}
// Dialog header button styles
if (dialogStyles.header.button) {
Object.entries(dialogStyles.header.button).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-header-button-${this.kebabCase(prop)}`, value as string);
});
}
}
// Dialog top-right positioning styles
if (dialogStyles.topRight) {
Object.entries(dialogStyles.topRight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-top-right-${this.kebabCase(prop)}`, value as string);
});
}
// Dialog close icon styles
if (dialogStyles.closeIcon) {
Object.entries(dialogStyles.closeIcon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-close-icon-${this.kebabCase(prop)}`, value as string);
});
}
}
private applyCheckboxStyles(root: HTMLElement, checkboxStyles: any): void {
// Base checkbox styles
if (checkboxStyles.height) {
root.style.setProperty('--preset-checkbox-height', checkboxStyles.height);
}
if (checkboxStyles.width) {
root.style.setProperty('--preset-checkbox-width', checkboxStyles.width);
}
// Checkbox box styles
if (checkboxStyles.box) {
Object.entries(checkboxStyles.box).forEach(([prop, value]) => {
if (prop !== 'hover' && prop !== 'highlight') {
root.style.setProperty(`--preset-checkbox-box-${this.kebabCase(prop)}`, value as string);
}
});
// Checkbox box hover styles
if (checkboxStyles.box.hover) {
Object.entries(checkboxStyles.box.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-box-hover-${this.kebabCase(prop)}`, value as string);
});
}
// Checkbox box highlight styles
if (checkboxStyles.box.highlight) {
Object.entries(checkboxStyles.box.highlight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-box-highlight-${this.kebabCase(prop)}`, value as string);
});
}
}
// Checkbox icon styles
if (checkboxStyles.icon) {
Object.entries(checkboxStyles.icon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-icon-${this.kebabCase(prop)}`, value as string);
});
}
// Checkbox check icon styles (for pi-check)
if (checkboxStyles.checkIcon) {
Object.entries(checkboxStyles.checkIcon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-check-icon-${this.kebabCase(prop)}`, value as string);
});
}
// Multiselect checkbox styles
if (checkboxStyles.multiselect) {
Object.entries(checkboxStyles.multiselect).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-multiselect-${this.kebabCase(prop)}`, value as string);
});
}
}
private applyMultiselectStyles(root: HTMLElement, multiselectStyles: any): void {
// Base multiselect styles
const baseProps = ['height', 'display', 'alignItems', 'width', 'border', 'borderColor', 'borderRadius', 'paddingRight'];
baseProps.forEach(prop => {
if (multiselectStyles[prop]) {
root.style.setProperty(`--preset-multiselect-${this.kebabCase(prop)}`, multiselectStyles[prop]);
}
});
// Trigger styles
if (multiselectStyles.trigger) {
Object.entries(multiselectStyles.trigger).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-trigger-${this.kebabCase(prop)}`, value as string);
});
}
// Label styles
if (multiselectStyles.label) {
Object.entries(multiselectStyles.label).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-label-${this.kebabCase(prop)}`, value as string);
});
}
// Focus styles
if (multiselectStyles.focus) {
Object.entries(multiselectStyles.focus).forEach(([prop, value]) => {
if (prop !== 'enabled') {
root.style.setProperty(`--preset-multiselect-focus-${this.kebabCase(prop)}`, value as string);
}
});
if (multiselectStyles.focus.enabled) {
Object.entries(multiselectStyles.focus.enabled).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-focus-enabled-${this.kebabCase(prop)}`, value as string);
});
}
}
// Panel styles
if (multiselectStyles.panel) {
Object.entries(multiselectStyles.panel).forEach(([prop, value]) => {
if (prop !== 'header') {
root.style.setProperty(`--preset-multiselect-panel-${this.kebabCase(prop)}`, value as string);
}
});
if (multiselectStyles.panel.header) {
Object.entries(multiselectStyles.panel.header).forEach(([prop, value]) => {
if (prop !== 'checkbox') {
root.style.setProperty(`--preset-multiselect-panel-header-${this.kebabCase(prop)}`, value as string);
}
});
if (multiselectStyles.panel.header.checkbox) {
Object.entries(multiselectStyles.panel.header.checkbox).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-header-checkbox-${this.kebabCase(prop)}`, value as string);
});
}
}
}
// Filter container and close styles
['filterContainer', 'close'].forEach(component => {
if (multiselectStyles[component]) {
Object.entries(multiselectStyles[component]).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-${this.kebabCase(component)}-${this.kebabCase(prop)}`, value as string);
});
}
});
// Placeholder styles
if (multiselectStyles.placeholder) {
Object.entries(multiselectStyles.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-placeholder-${this.kebabCase(prop)}`, value as string);
});
}
// Item styles
if (multiselectStyles.item) {
const itemBaseProps = ['fontSize', 'color', 'paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom'];
itemBaseProps.forEach(prop => {
if (multiselectStyles.item[prop]) {
root.style.setProperty(`--preset-multiselect-item-${this.kebabCase(prop)}`, multiselectStyles.item[prop]);
}
});
// Item highlight styles
if (multiselectStyles.item.highlight) {
Object.entries(multiselectStyles.item.highlight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-item-highlight-${this.kebabCase(prop)}`, value as string);
});
}
// Item hover styles
if (multiselectStyles.item.hover) {
Object.entries(multiselectStyles.item.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-item-hover-${this.kebabCase(prop)}`, value as string);
});
}
// Item focus styles
if (multiselectStyles.item.focus) {
Object.entries(multiselectStyles.item.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-item-focus-${this.kebabCase(prop)}`, value as string);
});
}
// Item general hover styles
if (multiselectStyles.item.generalHover) {
Object.entries(multiselectStyles.item.generalHover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-item-general-hover-${this.kebabCase(prop)}`, value as string);
});
}
}
// Empty message styles
if (multiselectStyles.emptyMessage) {
Object.entries(multiselectStyles.emptyMessage).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-empty-message-${this.kebabCase(prop)}`, value as string);
});
}
}
private applyDropdownStyles(root: HTMLElement, dropdownStyles: any): void {
// Base dropdown styles
const baseProps = ['height', 'width', 'transform', 'border', 'borderColor', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', 'borderRadius'];
baseProps.forEach(prop => {
if (dropdownStyles[prop]) {
root.style.setProperty(`--preset-dropdown-${this.kebabCase(prop)}`, dropdownStyles[prop]);
}
});
// Disabled styles
if (dropdownStyles.disabled) {
Object.entries(dropdownStyles.disabled).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-disabled-${this.kebabCase(prop)}`, value as string);
});
}
// Trigger styles
if (dropdownStyles.trigger) {
Object.entries(dropdownStyles.trigger).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-trigger-${this.kebabCase(prop)}`, value as string);
});
}
// Focus styles
if (dropdownStyles.focus) {
Object.entries(dropdownStyles.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-focus-${this.kebabCase(prop)}`, value as string);
});
}
// Hover styles
if (dropdownStyles.hover) {
Object.entries(dropdownStyles.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-hover-${this.kebabCase(prop)}`, value as string);
});
}
// Placeholder styles
if (dropdownStyles.placeholder) {
Object.entries(dropdownStyles.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-placeholder-${this.kebabCase(prop)}`, value as string);
});
}
// Panel styles
if (dropdownStyles.panel) {
if (dropdownStyles.panel.items) {
Object.entries(dropdownStyles.panel.items).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-panel-items-${this.kebabCase(prop)}`, value as string);
});
}
if (dropdownStyles.panel.header) {
Object.entries(dropdownStyles.panel.header).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-panel-header-${this.kebabCase(prop)}`, value as string);
});
}
}
// Header styles (separate from panel.header)
if (dropdownStyles.header) {
Object.entries(dropdownStyles.header).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-header-${this.kebabCase(prop)}`, value as string);
});
}
// Item styles
if (dropdownStyles.item) {
const itemBaseProps = ['fontSize', 'color', 'borderRadius', 'minHeight', 'display', 'alignItems', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'];
itemBaseProps.forEach(prop => {
if (dropdownStyles.item[prop]) {
root.style.setProperty(`--preset-dropdown-item-${this.kebabCase(prop)}`, dropdownStyles.item[prop]);
}
});
// Item highlight styles
if (dropdownStyles.item.highlight) {
Object.entries(dropdownStyles.item.highlight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-item-highlight-${this.kebabCase(prop)}`, value as string);
});
}
// Item focus styles
if (dropdownStyles.item.focus) {
Object.entries(dropdownStyles.item.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-item-focus-${this.kebabCase(prop)}`, value as string);
});
}
// Item hover styles
if (dropdownStyles.item.hover) {
Object.entries(dropdownStyles.item.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-item-hover-${this.kebabCase(prop)}`, value as string);
});
}
// Item group styles
if (dropdownStyles.item.group) {
Object.entries(dropdownStyles.item.group).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-item-group-${this.kebabCase(prop)}`, value as string);
});
}
}
// Empty message styles
if (dropdownStyles.emptyMessage) {
Object.entries(dropdownStyles.emptyMessage).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-empty-message-${this.kebabCase(prop)}`, value as string);
});
}
// Divider styles
if (dropdownStyles.divider) {
Object.entries(dropdownStyles.divider).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-divider-${this.kebabCase(prop)}`, value as string);
});
}
// Calendar input styles
if (dropdownStyles.calendarInput) {
Object.entries(dropdownStyles.calendarInput).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-calendar-input-${this.kebabCase(prop)}`, value as string);
});
}
// Filter icon styles
if (dropdownStyles.filterIcon) {
Object.entries(dropdownStyles.filterIcon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-filter-icon-${this.kebabCase(prop)}`, value as string);
});
}
}
// Add these interfaces at the top of src/core/services/theme.service.ts
interface ButtonVariantStyles {
base?: Record<string, string>;
hover?: Record<string, string>;
disabled?: Record<string, string>;
}
interface ButtonStyles {
primary?: ButtonVariantStyles;
secondary?: ButtonVariantStyles;
outline?: ButtonVariantStyles;
primaryOutline?: ButtonVariantStyles;
dangerOutline?: ButtonVariantStyles;
text?: {
primary: Record<string, string>;
primaryHover: Record<string, string>;
};
}
// … later in your ThemeService class …
- private applyButtonStyles(root: HTMLElement, buttonStyles: any): void {
+ private applyButtonStyles(root: HTMLElement, buttonStyles: ButtonStyles): void {
// Primary button styles
this.applyButtonVariantStyles(root, 'primary', buttonStyles.primary);
this.applyButtonVariantStyles(root, 'secondary', buttonStyles.secondary);
this.applyButtonVariantStyles(root, 'outline', buttonStyles.outline);
this.applyButtonVariantStyles(root, 'primary-outline', buttonStyles.primaryOutline);
this.applyButtonVariantStyles(root, 'danger-outline', buttonStyles.dangerOutline);
// Text button styles
Object.entries(buttonStyles.text.primary).forEach(([prop, value]) => {
root.style.setProperty(`--preset-btn-text-${this.kebabCase(prop)}`, value as string);
});
Object.entries(buttonStyles.text.primaryHover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-btn-text-hover-${this.kebabCase(prop)}`, value as string);
});
}
🤖 Prompt for AI Agents
In src/core/services/theme.service.ts around lines 64 to 673, all apply*Styles
methods currently use the any type for their style parameters, which reduces
type safety. To fix this, define specific TypeScript interfaces representing the
expected structure of each style parameter object for buttons, inputs, tooltips,
toggles, radios, dialogs, checkboxes, multiselects, and dropdowns. Then update
the method signatures to use these interfaces instead of any, ensuring better
type checking and maintainability.

ashwin1111 and others added 2 commits July 28, 2025 00:03
* feat p-chips

* design border fix

* theme datatable

* lint fix

* chips border fix

---------

Co-authored-by: Vignesh <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
src/core/services/theme.service.ts (3)

950-956: Restrict toggleBrand to development environments.

The toggleBrand method is marked as useful for testing. Consider whether this should be available in production builds.


19-23: Potential initialization issue with brandingConfig access.

The service directly accesses brandingConfig.brandId and casts it to SupportedBrand without validation. This could cause runtime errors if the brandId is invalid or undefined.


67-676: Add type definitions for all style parameters.

All apply*Styles methods use any type for their style parameters, reducing type safety throughout the service. Consider creating proper interfaces for each component's style structure.

src/themes/co-preset.ts (1)

68-69: Inconsistent box-shadow values - hardcoded instead of CSS variables.

The Co preset uses hardcoded box shadows while the Fyle preset uses CSS variables. This creates inconsistency and makes it harder to maintain centralized shadow values.

🧹 Nitpick comments (2)
src/themes/fyle-preset.ts (1)

537-550: Fix indentation inconsistency.

The panel property has inconsistent indentation compared to other properties at the same level.

-              panel: {
+      panel: {
src/themes/co-preset.ts (1)

539-552: Fix indentation inconsistency.

The panel property has inconsistent indentation, similar to the issue in fyle-preset.ts.

-              panel: {
+      panel: {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1fe05e3 and a622def.

📒 Files selected for processing (11)
  • src/app/shared/components/export-log/export-log-table/export-log-table.component.scss (0 hunks)
  • src/app/shared/components/qbd/mapping/qbd-mapping-table/qbd-mapping-table.component.scss (0 hunks)
  • src/assets/scss/chips.scss (1 hunks)
  • src/assets/scss/datepicker.scss (1 hunks)
  • src/assets/scss/table.scss (1 hunks)
  • src/core/services/theme.service.ts (1 hunks)
  • src/styles.scss (2 hunks)
  • src/themes/co-css-vars.ts (1 hunks)
  • src/themes/co-preset.ts (1 hunks)
  • src/themes/fyle-css-vars.ts (1 hunks)
  • src/themes/fyle-preset.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • src/app/shared/components/qbd/mapping/qbd-mapping-table/qbd-mapping-table.component.scss
  • src/app/shared/components/export-log/export-log-table/export-log-table.component.scss
✅ Files skipped from review due to trivial changes (4)
  • src/assets/scss/chips.scss
  • src/themes/co-css-vars.ts
  • src/assets/scss/table.scss
  • src/assets/scss/datepicker.scss
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/themes/fyle-css-vars.ts
  • src/styles.scss
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{service,pipe,directive}.ts

📄 CodeRabbit Inference Engine (.cursor/rules/atomics-i18n-key-pattern.mdc)

**/*.{service,pipe,directive}.ts: i18n keys for services, pipes, and directives must use the naming schemas: services.., pipes.., directives..
Convert kebab-case file names to camelCase and remove the suffixes 'Service', 'Pipe', or 'Directive' when generating i18n keys from file names (e.g., 'report-expenses.service.ts' → 'reportExpenses')
Use meaningful, semantic, and context-aware key names for i18n keys; avoid generic names like 'message1', 'text1', or 'error1'

Files:

  • src/core/services/theme.service.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: ashwin1111
PR: fylein/fyle-integrations-app#343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable `targetSystemName` in the subLabel string within the `business-central-export-settings.component.html` file.
Learnt from: ashwin1111
PR: fylein/fyle-integrations-app#343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable `targetSystemName` in the subLabel string within the `business-central-export-settings.component.html` file.
src/themes/co-preset.ts (1)

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/component-i18n-key-naming.mdc:0-0
Timestamp: 2025-07-28T07:37:26.776Z
Learning: Applies to **/*.component.{ts,html} : Do not use prefixes like 'ui', 'feature', or 'component' in the top-level i18n object name; the object name should be the context.

src/core/services/theme.service.ts (12)

Learnt from: ashwin1111
PR: #343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable targetSystemName in the subLabel string within the business-central-export-settings.component.html file.

Learnt from: ashwin1111
PR: #343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable targetSystemName in the subLabel string within the business-central-export-settings.component.html file.

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/component-i18n-key-naming.mdc:0-0
Timestamp: 2025-07-28T07:37:26.776Z
Learning: Applies to **/*.component.{ts,html} : Do not use prefixes like 'ui', 'feature', or 'component' in the top-level i18n object name; the object name should be the context.

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/component-i18n-key-naming.mdc:0-0
Timestamp: 2025-07-28T07:37:26.776Z
Learning: Applies to **/*.component.html : Prefer property binding for user-facing attributes in Angular templates (e.g., [placeholder], [title]) when using translation keys.

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/component-i18n-key-naming.mdc:0-0
Timestamp: 2025-07-28T07:37:26.776Z
Learning: Applies to **/*.component.{ts,html} : Do not translate strings that are already translation keys, tracking/analytics strings, strings with only special characters, comments, non-user-visible text, or variable bindings and expressions.

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/component-i18n-key-naming.mdc:0-0
Timestamp: 2025-07-28T07:37:26.776Z
Learning: Applies to **/*.component.{ts,html} : Keys inside the top-level i18n object must use meaningful, semantic, and context-aware names (e.g., 'title', 'accountDisabledError', 'submitButton'); avoid generic names like 'label1', 'text1', 'message1'.

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/component-i18n-key-naming.mdc:0-0
Timestamp: 2025-07-28T07:37:26.776Z
Learning: Applies to **/*.component.{ts,html} : One i18n object per component or feature; do not combine multiple components/features into a single i18n object.

Learnt from: ashwin1111
PR: #298
File: src/app/shared/components/export-log/skipped-export-log-table/skipped-export-log-table.component.html:21-22
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has requested the removal of a condition that was always evaluating to false (*ngIf="false"), which was making an h5 element never visible. This indicates a preference for removing redundant or placeholder code that is not currently in use.

Learnt from: ashwin1111
PR: #298
File: src/app/shared/components/export-log/skipped-export-log-table/skipped-export-log-table.component.html:21-22
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has requested the removal of a condition that was always evaluating to false (*ngIf="false"), which was making an h5 element never visible. This indicates a preference for removing redundant or placeholder code that is not currently in use.

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/atomics-i18n-key-pattern.mdc:0-0
Timestamp: 2025-07-28T07:37:10.809Z
Learning: Applies to **/*.{service,pipe,directive}.ts : Use meaningful, semantic, and context-aware key names for i18n keys; avoid generic names like 'message1', 'text1', or 'error1'

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/atomics-i18n-key-pattern.mdc:0-0
Timestamp: 2025-07-28T07:37:10.809Z
Learning: Applies to **/*.{service,pipe,directive}.ts : Convert kebab-case file names to camelCase and remove the suffixes 'Service', 'Pipe', or 'Directive' when generating i18n keys from file names (e.g., 'report-expenses.service.ts' → 'reportExpenses')

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/atomics-i18n-key-pattern.mdc:0-0
Timestamp: 2025-07-28T07:37:10.809Z
Learning: Applies to **/*.{service,pipe,directive}.ts : i18n keys for services, pipes, and directives must use the naming schemas: services.., pipes.., directives..

src/themes/fyle-preset.ts (1)

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/component-i18n-key-naming.mdc:0-0
Timestamp: 2025-07-28T07:37:26.776Z
Learning: Applies to **/*.component.{ts,html} : Do not use prefixes like 'ui', 'feature', or 'component' in the top-level i18n object name; the object name should be the context.

🧬 Code Graph Analysis (2)
src/core/services/theme.service.ts (3)
src/themes/theme.types.ts (1)
  • SupportedBrand (8-8)
src/app/branding/branding-config.ts (1)
  • brandingConfig (11-11)
src/themes/index.ts (2)
  • getPresetByBrand (24-31)
  • getBrandColors (49-52)
src/themes/fyle-preset.ts (3)
src/themes/theme.types.ts (1)
  • ThemeColors (10-30)
src/themes/index.ts (2)
  • FylePreset (2-2)
  • FyleCSSVars (6-6)
src/themes/fyle-css-vars.ts (1)
  • FyleCSSVars (11-256)
🔇 Additional comments (2)
src/core/services/theme.service.ts (1)

150-152: Linting issue already addressed.

The missing braces on single-line if statements were already fixed in a previous commit.

src/themes/fyle-preset.ts (1)

1-33: Well-structured legacy color palette with clear migration path.

The legacy color definitions are properly documented with their corresponding CSS variable mappings, making the migration path clear and maintainable.

Comment on lines +85 to +676
private applyInputStyles(root: HTMLElement, inputStyles: any): void {
// Basic input styles
if (inputStyles.base) {
Object.entries(inputStyles.base).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-${this.kebabCase(prop)}`, value as string);
});
}

// Input focus visible styles
if (inputStyles.focusVisible) {
Object.entries(inputStyles.focusVisible).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-focus-visible-${this.kebabCase(prop)}`, value as string);
});
}

// Input focus styles
if (inputStyles.focus) {
Object.entries(inputStyles.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Input disabled styles
if (inputStyles.disabled) {
Object.entries(inputStyles.disabled).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-disabled-${this.kebabCase(prop)}`, value as string);
});
}

// Input placeholder styles
if (inputStyles.placeholder) {
Object.entries(inputStyles.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-placeholder-${this.kebabCase(prop)}`, value as string);
});
}

// Calendar input styles
this.applyCalendarInputStyles(root, inputStyles.calendar);

// Dropdown label input styles
this.applyDropdownLabelStyles(root, inputStyles.dropdownLabel);

// Chips input styles
if (inputStyles.chips?.focus) {
Object.entries(inputStyles.chips.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-chips-input-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Input container focus styles
if (inputStyles.container?.focus) {
Object.entries(inputStyles.container.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-input-container-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Dropdown filter styles
if (inputStyles.dropdownFilter) {
Object.entries(inputStyles.dropdownFilter).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-filter-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyCalendarInputStyles(root: HTMLElement, calendarStyles: any): void {
if (!calendarStyles) {
return;
}

Object.entries(calendarStyles).forEach(([prop, value]) => {
if (prop !== 'focus' && prop !== 'placeholder') {
root.style.setProperty(`--preset-calendar-input-${this.kebabCase(prop)}`, value as string);
}
});

// Calendar input focus styles
if (calendarStyles.focus) {
Object.entries(calendarStyles.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-calendar-input-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Calendar input placeholder styles
if (calendarStyles.placeholder) {
Object.entries(calendarStyles.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-calendar-input-placeholder-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyDropdownLabelStyles(root: HTMLElement, dropdownLabelStyles: any): void {
if (!dropdownLabelStyles) {
return;
}

Object.entries(dropdownLabelStyles).forEach(([prop, value]) => {
if (prop !== 'input') {
root.style.setProperty(`--preset-dropdown-label-${this.kebabCase(prop)}`, value as string);
}
});

// Dropdown label input specific styles
if (dropdownLabelStyles.input) {
Object.entries(dropdownLabelStyles.input).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-label-input-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyTooltipStyles(root: HTMLElement, tooltipStyles: any): void {
// Tooltip text styles
if (tooltipStyles.text) {
Object.entries(tooltipStyles.text).forEach(([prop, value]) => {
root.style.setProperty(`--preset-tooltip-text-${this.kebabCase(prop)}`, value as string);
});
}

// Tooltip arrow styles
if (tooltipStyles.arrow) {
Object.entries(tooltipStyles.arrow).forEach(([prop, value]) => {
root.style.setProperty(`--preset-tooltip-arrow-${this.kebabCase(prop)}`, value as string);
});
}

// Tooltip bottom specific styles
if (tooltipStyles.bottom) {
Object.entries(tooltipStyles.bottom).forEach(([prop, value]) => {
if (prop === 'arrow') {
// Handle nested arrow styles
Object.entries(value as any).forEach(([arrowProp, arrowValue]) => {
root.style.setProperty(`--preset-tooltip-bottom-arrow-${this.kebabCase(arrowProp)}`, arrowValue as string);
});
} else {
root.style.setProperty(`--preset-tooltip-bottom-${this.kebabCase(prop)}`, value as string);
}
});
}
}

private applyToggleStyles(root: HTMLElement, toggleStyles: any): void {
// Base toggle styles
if (toggleStyles.height) {
root.style.setProperty('--preset-toggle-height', toggleStyles.height);
}
if (toggleStyles.width) {
root.style.setProperty('--preset-toggle-width', toggleStyles.width);
}

// Toggle circle styles
if (toggleStyles.circle) {
Object.entries(toggleStyles.circle).forEach(([prop, value]) => {
if (value !== undefined && value !== null) {
root.style.setProperty(`--preset-toggle-circle-${this.kebabCase(prop)}`, value as string);
}
});
}

// Toggle checked state styles
if (toggleStyles.checked) {
// Handle nested circle transform
if (toggleStyles.checked.circle?.transform) {
root.style.setProperty('--preset-toggle-checked-circle-transform', toggleStyles.checked.circle.transform);
}

// Handle other checked properties
if (toggleStyles.checked.background) {
root.style.setProperty('--preset-toggle-checked-background', toggleStyles.checked.background);
}
if (toggleStyles.checked.width) {
root.style.setProperty('--preset-toggle-checked-width', toggleStyles.checked.width);
}
if (toggleStyles.checked.height) {
root.style.setProperty('--preset-toggle-checked-height', toggleStyles.checked.height);
}
}

// Toggle unchecked state styles
if (toggleStyles.unchecked) {
Object.entries(toggleStyles.unchecked).forEach(([prop, value]) => {
root.style.setProperty(`--preset-toggle-unchecked-${this.kebabCase(prop)}`, value as string);
});
}

// Toggle focus styles
if (toggleStyles.focus) {
Object.entries(toggleStyles.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-toggle-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Toggle text styles
if (toggleStyles.text) {
Object.entries(toggleStyles.text).forEach(([prop, value]) => {
root.style.setProperty(`--preset-toggle-text-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyRadioStyles(root: HTMLElement, radioStyles: any): void {
// Base radio styles
if (radioStyles.width) {
root.style.setProperty('--preset-radio-width', radioStyles.width);
}
if (radioStyles.height) {
root.style.setProperty('--preset-radio-height', radioStyles.height);
}

// Radio box styles
if (radioStyles.box) {
Object.entries(radioStyles.box).forEach(([prop, value]) => {
if (prop !== 'hover' && prop !== 'highlight') {
root.style.setProperty(`--preset-radio-box-${this.kebabCase(prop)}`, value as string);
}
});

// Radio box hover styles
if (radioStyles.box.hover) {
Object.entries(radioStyles.box.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-radio-box-hover-${this.kebabCase(prop)}`, value as string);
});
}

// Radio box highlight styles
if (radioStyles.box.highlight) {
Object.entries(radioStyles.box.highlight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-radio-box-highlight-${this.kebabCase(prop)}`, value as string);
});
}
}

// Radio icon styles
if (radioStyles.icon) {
Object.entries(radioStyles.icon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-radio-icon-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyDialogStyles(root: HTMLElement, dialogStyles: any): void {
// Dialog content styles
if (dialogStyles.content) {
Object.entries(dialogStyles.content).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-content-${this.kebabCase(prop)}`, value as string);
});
}

// Dialog header styles
if (dialogStyles.header) {
Object.entries(dialogStyles.header).forEach(([prop, value]) => {
if (prop !== 'title' && prop !== 'button') {
root.style.setProperty(`--preset-dialog-header-${this.kebabCase(prop)}`, value as string);
}
});

// Dialog header title styles
if (dialogStyles.header.title) {
Object.entries(dialogStyles.header.title).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-title-${this.kebabCase(prop)}`, value as string);
});
}

// Dialog header button styles
if (dialogStyles.header.button) {
Object.entries(dialogStyles.header.button).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-header-button-${this.kebabCase(prop)}`, value as string);
});
}
}

// Dialog top-right positioning styles
if (dialogStyles.topRight) {
Object.entries(dialogStyles.topRight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-top-right-${this.kebabCase(prop)}`, value as string);
});
}

// Dialog close icon styles
if (dialogStyles.closeIcon) {
Object.entries(dialogStyles.closeIcon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dialog-close-icon-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyCheckboxStyles(root: HTMLElement, checkboxStyles: any): void {
// Base checkbox styles
if (checkboxStyles.height) {
root.style.setProperty('--preset-checkbox-height', checkboxStyles.height);
}
if (checkboxStyles.width) {
root.style.setProperty('--preset-checkbox-width', checkboxStyles.width);
}

// Checkbox box styles
if (checkboxStyles.box) {
Object.entries(checkboxStyles.box).forEach(([prop, value]) => {
if (prop !== 'hover' && prop !== 'highlight') {
root.style.setProperty(`--preset-checkbox-box-${this.kebabCase(prop)}`, value as string);
}
});

// Checkbox box hover styles
if (checkboxStyles.box.hover) {
Object.entries(checkboxStyles.box.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-box-hover-${this.kebabCase(prop)}`, value as string);
});
}

// Checkbox box highlight styles
if (checkboxStyles.box.highlight) {
Object.entries(checkboxStyles.box.highlight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-box-highlight-${this.kebabCase(prop)}`, value as string);
});
}
}

// Checkbox icon styles
if (checkboxStyles.icon) {
Object.entries(checkboxStyles.icon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-icon-${this.kebabCase(prop)}`, value as string);
});
}

// Checkbox check icon styles (for pi-check)
if (checkboxStyles.checkIcon) {
Object.entries(checkboxStyles.checkIcon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-check-icon-${this.kebabCase(prop)}`, value as string);
});
}

// Multiselect checkbox styles
if (checkboxStyles.multiselect) {
Object.entries(checkboxStyles.multiselect).forEach(([prop, value]) => {
root.style.setProperty(`--preset-checkbox-multiselect-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyMultiselectStyles(root: HTMLElement, multiselectStyles: any): void {
// Base multiselect styles
const baseProps = ['height', 'display', 'alignItems', 'width', 'border', 'borderColor', 'borderRadius', 'paddingRight'];
baseProps.forEach(prop => {
if (multiselectStyles[prop]) {
root.style.setProperty(`--preset-multiselect-${this.kebabCase(prop)}`, multiselectStyles[prop]);
}
});

// Trigger styles
if (multiselectStyles.trigger) {
Object.entries(multiselectStyles.trigger).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-trigger-${this.kebabCase(prop)}`, value as string);
});
}

// Label styles
if (multiselectStyles.label) {
Object.entries(multiselectStyles.label).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-label-${this.kebabCase(prop)}`, value as string);
});
}

// Focus styles
if (multiselectStyles.focus) {
Object.entries(multiselectStyles.focus).forEach(([prop, value]) => {
if (prop !== 'enabled') {
root.style.setProperty(`--preset-multiselect-focus-${this.kebabCase(prop)}`, value as string);
}
});

if (multiselectStyles.focus.enabled) {
Object.entries(multiselectStyles.focus.enabled).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-focus-enabled-${this.kebabCase(prop)}`, value as string);
});
}
}

// Panel styles
if (multiselectStyles.panel) {
Object.entries(multiselectStyles.panel).forEach(([prop, value]) => {
if (prop !== 'header') {
root.style.setProperty(`--preset-multiselect-panel-${this.kebabCase(prop)}`, value as string);
}
});

if (multiselectStyles.panel.header) {
Object.entries(multiselectStyles.panel.header).forEach(([prop, value]) => {
if (prop !== 'checkbox') {
root.style.setProperty(`--preset-multiselect-panel-header-${this.kebabCase(prop)}`, value as string);
}
});

if (multiselectStyles.panel.header.checkbox) {
Object.entries(multiselectStyles.panel.header.checkbox).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-header-checkbox-${this.kebabCase(prop)}`, value as string);
});
}
}
}

// Filter container and close styles
['filterContainer', 'close'].forEach(component => {
if (multiselectStyles[component]) {
Object.entries(multiselectStyles[component]).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-${this.kebabCase(component)}-${this.kebabCase(prop)}`, value as string);
});
}
});

// Placeholder styles
if (multiselectStyles.placeholder) {
Object.entries(multiselectStyles.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-placeholder-${this.kebabCase(prop)}`, value as string);
});
}

// Item styles
if (multiselectStyles.item) {
const itemBaseProps = ['fontSize', 'color', 'paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom'];
itemBaseProps.forEach(prop => {
if (multiselectStyles.item[prop]) {
root.style.setProperty(`--preset-multiselect-item-${this.kebabCase(prop)}`, multiselectStyles.item[prop]);
}
});

// Item highlight styles
if (multiselectStyles.item.highlight) {
Object.entries(multiselectStyles.item.highlight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-item-highlight-${this.kebabCase(prop)}`, value as string);
});
}

// Item hover styles
if (multiselectStyles.item.hover) {
Object.entries(multiselectStyles.item.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-item-hover-${this.kebabCase(prop)}`, value as string);
});
}

// Item focus styles
if (multiselectStyles.item.focus) {
Object.entries(multiselectStyles.item.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-item-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Item general hover styles
if (multiselectStyles.item.generalHover) {
Object.entries(multiselectStyles.item.generalHover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-item-general-hover-${this.kebabCase(prop)}`, value as string);
});
}
}

// Empty message styles
if (multiselectStyles.emptyMessage) {
Object.entries(multiselectStyles.emptyMessage).forEach(([prop, value]) => {
root.style.setProperty(`--preset-multiselect-empty-message-${this.kebabCase(prop)}`, value as string);
});
}
}

private applyDropdownStyles(root: HTMLElement, dropdownStyles: any): void {
// Base dropdown styles
const baseProps = ['height', 'width', 'transform', 'border', 'borderColor', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', 'borderRadius'];
baseProps.forEach(prop => {
if (dropdownStyles[prop]) {
root.style.setProperty(`--preset-dropdown-${this.kebabCase(prop)}`, dropdownStyles[prop]);
}
});

// Disabled styles
if (dropdownStyles.disabled) {
Object.entries(dropdownStyles.disabled).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-disabled-${this.kebabCase(prop)}`, value as string);
});
}

// Trigger styles
if (dropdownStyles.trigger) {
Object.entries(dropdownStyles.trigger).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-trigger-${this.kebabCase(prop)}`, value as string);
});
}

// Focus styles
if (dropdownStyles.focus) {
Object.entries(dropdownStyles.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Hover styles
if (dropdownStyles.hover) {
Object.entries(dropdownStyles.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-hover-${this.kebabCase(prop)}`, value as string);
});
}

// Placeholder styles
if (dropdownStyles.placeholder) {
Object.entries(dropdownStyles.placeholder).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-placeholder-${this.kebabCase(prop)}`, value as string);
});
}

// Panel styles
if (dropdownStyles.panel) {
if (dropdownStyles.panel.items) {
Object.entries(dropdownStyles.panel.items).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-panel-items-${this.kebabCase(prop)}`, value as string);
});
}
if (dropdownStyles.panel.header) {
Object.entries(dropdownStyles.panel.header).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-panel-header-${this.kebabCase(prop)}`, value as string);
});
}
}

// Header styles (separate from panel.header)
if (dropdownStyles.header) {
Object.entries(dropdownStyles.header).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-header-${this.kebabCase(prop)}`, value as string);
});
}

// Item styles
if (dropdownStyles.item) {
const itemBaseProps = ['fontSize', 'color', 'borderRadius', 'minHeight', 'display', 'alignItems', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'];
itemBaseProps.forEach(prop => {
if (dropdownStyles.item[prop]) {
root.style.setProperty(`--preset-dropdown-item-${this.kebabCase(prop)}`, dropdownStyles.item[prop]);
}
});

// Item highlight styles
if (dropdownStyles.item.highlight) {
Object.entries(dropdownStyles.item.highlight).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-item-highlight-${this.kebabCase(prop)}`, value as string);
});
}

// Item focus styles
if (dropdownStyles.item.focus) {
Object.entries(dropdownStyles.item.focus).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-item-focus-${this.kebabCase(prop)}`, value as string);
});
}

// Item hover styles
if (dropdownStyles.item.hover) {
Object.entries(dropdownStyles.item.hover).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-item-hover-${this.kebabCase(prop)}`, value as string);
});
}

// Item group styles
if (dropdownStyles.item.group) {
Object.entries(dropdownStyles.item.group).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-item-group-${this.kebabCase(prop)}`, value as string);
});
}
}

// Empty message styles
if (dropdownStyles.emptyMessage) {
Object.entries(dropdownStyles.emptyMessage).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-empty-message-${this.kebabCase(prop)}`, value as string);
});
}

// Divider styles
if (dropdownStyles.divider) {
Object.entries(dropdownStyles.divider).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-divider-${this.kebabCase(prop)}`, value as string);
});
}

// Calendar input styles
if (dropdownStyles.calendarInput) {
Object.entries(dropdownStyles.calendarInput).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-calendar-input-${this.kebabCase(prop)}`, value as string);
});
}

// Filter icon styles
if (dropdownStyles.filterIcon) {
Object.entries(dropdownStyles.filterIcon).forEach(([prop, value]) => {
root.style.setProperty(`--preset-dropdown-filter-icon-${this.kebabCase(prop)}`, value as string);
});
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extensive code duplication across style application methods.

The service contains significant duplication in how styles are applied. Each component's style application follows a similar pattern of iterating over properties and setting CSS variables. This could be refactored into a generic helper method.

Consider creating a generic style application helper:

+  private applyStyles(
+    root: HTMLElement,
+    styles: Record<string, any>,
+    prefix: string,
+    propertyMapper?: (key: string) => string
+  ): void {
+    Object.entries(styles).forEach(([key, value]) => {
+      if (typeof value === 'object' && value !== null) {
+        // Recursively handle nested objects
+        const nestedPrefix = propertyMapper ? propertyMapper(key) : `${prefix}-${this.kebabCase(key)}`;
+        this.applyStyles(root, value, nestedPrefix);
+      } else {
+        const propertyName = propertyMapper ? propertyMapper(key) : `${prefix}-${this.kebabCase(key)}`;
+        root.style.setProperty(`--preset-${propertyName}`, value as string);
+      }
+    });
+  }

This would allow simplifying methods like:

private applyButtonStyles(root: HTMLElement, buttonStyles: any): void {
  this.applyStyles(root, buttonStyles.primary, 'btn-primary');
  this.applyStyles(root, buttonStyles.secondary, 'btn-secondary');
  // etc.
}
🤖 Prompt for AI Agents
In src/core/services/theme.service.ts around lines 85 to 676, there is extensive
duplication in how styles are applied by iterating over style properties and
setting CSS variables in each method. To fix this, create a generic helper
method that takes the root element, a style object, and a CSS variable prefix,
then iterates over the style properties to set the CSS variables. Refactor
existing style application methods to use this helper, passing the appropriate
style objects and prefixes, thereby reducing code duplication and improving
maintainability.

* feat: theme skeleton

* loader

* toast
@github-actions
Copy link

🔁 Code Duplication Report - Angular

Format Files analyzed Total lines Total tokens Clones found Duplicated lines Duplicated tokens
typescript 730 53270 478236 317 5229 (9.82%) -0.28% 🚀 49097 (10.27%) -0.20% 🚀
scss 78 4574 26909 8 152 (3.32%) -0.12% 🚀 833 (3.10%) -0.31% 🚀
markup 295 12726 134547 158 1631 (12.82%) 14493 (10.77%)
python 4 727 5680 3 32 (4.40%) 368 (6.48%)
yaml 3 155 905 0 0 (0.00%) 0 (0.00%)
javascript 4 193 2175 0 0 (0.00%) 0 (0.00%)
markdown 11 2025 12147 0 0 (0.00%) 0 (0.00%)
bash 2 66 762 0 0 (0.00%) 0 (0.00%)
Total: 1127 73736 661361 486 7044 (9.55%) -0.51% 🚀 64791 (9.80%) -0.38% 🚀

🎉 This PR reduces duplicated code by 0.38%!

@github-actions
Copy link

Unit Test Coverage % values
Statements 35.02% ( 4919 / 14046 )
Branches 26.97% ( 1343 / 4978 )
Functions 28.66% ( 1088 / 3795 )
Lines 35.16% ( 4837 / 13756 )

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/core/services/theme.service.ts (2)

19-23: Potential initialization issue with brandingConfig access.

The service directly accesses brandingConfig.brandId and casts it to SupportedBrand without validation. This could cause runtime errors if the brandId is invalid or undefined.


1064-1070: Consider restricting toggleBrand to development environments.

The toggleBrand method is marked as useful for testing. Consider whether this should be available in production builds.

src/themes/co-preset.ts (1)

68-69: Inconsistent box-shadow values - hardcoded instead of CSS variables.

The Co preset uses hardcoded '0 2px 4px rgba(0,0,0,0.1)' for box shadows, while the Fyle preset uses FyleCSSVars.shadows.btnCta. This creates inconsistency and makes it harder to maintain centralized shadow values.

🧹 Nitpick comments (2)
src/themes/fyle-preset.ts (1)

537-550: Fix indentation inconsistency.

Line 537 has incorrect indentation for the panel property. It should align with other properties at the same level.

       placeholder: {
         color: FyleCSSVars.text.placeholder
       },
-              panel: {
+      panel: {
           items: {
src/themes/co-preset.ts (1)

539-552: Fix indentation inconsistency.

Line 539 has incorrect indentation for the panel property, similar to the issue in fyle-preset.ts. It should align with other properties at the same level.

       placeholder: {
         color: CoCSSVars.text.placeholder
       },
-              panel: {
+      panel: {
           items: {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a622def and abc95b4.

📒 Files selected for processing (7)
  • src/assets/scss/skeleton.scss (1 hunks)
  • src/assets/scss/spinner.scss (1 hunks)
  • src/assets/scss/toasts.scss (1 hunks)
  • src/core/services/theme.service.ts (1 hunks)
  • src/styles.scss (2 hunks)
  • src/themes/co-preset.ts (1 hunks)
  • src/themes/fyle-preset.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • src/assets/scss/skeleton.scss
  • src/assets/scss/spinner.scss
  • src/assets/scss/toasts.scss
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/styles.scss
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{service,pipe,directive}.ts

📄 CodeRabbit Inference Engine (.cursor/rules/atomics-i18n-key-pattern.mdc)

**/*.{service,pipe,directive}.ts: i18n keys for services, pipes, and directives must use the naming schemas: services.., pipes.., directives..
Convert kebab-case file names to camelCase and remove the suffixes 'Service', 'Pipe', or 'Directive' when generating i18n keys from file names (e.g., 'report-expenses.service.ts' → 'reportExpenses')
Use meaningful, semantic, and context-aware key names for i18n keys; avoid generic names like 'message1', 'text1', or 'error1'

Files:

  • src/core/services/theme.service.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: ashwin1111
PR: fylein/fyle-integrations-app#343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable `targetSystemName` in the subLabel string within the `business-central-export-settings.component.html` file.
Learnt from: ashwin1111
PR: fylein/fyle-integrations-app#343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable `targetSystemName` in the subLabel string within the `business-central-export-settings.component.html` file.
src/themes/co-preset.ts (1)

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/component-i18n-key-naming.mdc:0-0
Timestamp: 2025-07-28T07:37:26.776Z
Learning: Applies to **/*.component.{ts,html} : Do not use prefixes like 'ui', 'feature', or 'component' in the top-level i18n object name; the object name should be the context.

src/core/services/theme.service.ts (12)

Learnt from: ashwin1111
PR: #343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable targetSystemName in the subLabel string within the business-central-export-settings.component.html file.

Learnt from: ashwin1111
PR: #343
File: src/app/integrations/business-central/business-central-shared/business-central-export-settings/business-central-export-settings.component.html:0-0
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has confirmed the suggestion to replace the hardcoded reference to 'Sage Intacct' with a dynamic reference to the target system using the variable targetSystemName in the subLabel string within the business-central-export-settings.component.html file.

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/component-i18n-key-naming.mdc:0-0
Timestamp: 2025-07-28T07:37:26.776Z
Learning: Applies to **/*.component.{ts,html} : Do not use prefixes like 'ui', 'feature', or 'component' in the top-level i18n object name; the object name should be the context.

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/component-i18n-key-naming.mdc:0-0
Timestamp: 2025-07-28T07:37:26.776Z
Learning: Applies to **/*.component.html : Prefer property binding for user-facing attributes in Angular templates (e.g., [placeholder], [title]) when using translation keys.

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/component-i18n-key-naming.mdc:0-0
Timestamp: 2025-07-28T07:37:26.776Z
Learning: Applies to **/*.component.{ts,html} : Do not translate strings that are already translation keys, tracking/analytics strings, strings with only special characters, comments, non-user-visible text, or variable bindings and expressions.

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/component-i18n-key-naming.mdc:0-0
Timestamp: 2025-07-28T07:37:26.776Z
Learning: Applies to **/*.component.{ts,html} : Keys inside the top-level i18n object must use meaningful, semantic, and context-aware names (e.g., 'title', 'accountDisabledError', 'submitButton'); avoid generic names like 'label1', 'text1', 'message1'.

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/component-i18n-key-naming.mdc:0-0
Timestamp: 2025-07-28T07:37:26.776Z
Learning: Applies to **/*.component.{ts,html} : One i18n object per component or feature; do not combine multiple components/features into a single i18n object.

Learnt from: ashwin1111
PR: #298
File: src/app/shared/components/export-log/skipped-export-log-table/skipped-export-log-table.component.html:21-22
Timestamp: 2024-06-10T19:13:15.470Z
Learning: The user ashwin1111 has requested the removal of a condition that was always evaluating to false (*ngIf="false"), which was making an h5 element never visible. This indicates a preference for removing redundant or placeholder code that is not currently in use.

Learnt from: ashwin1111
PR: #298
File: src/app/shared/components/export-log/skipped-export-log-table/skipped-export-log-table.component.html:21-22
Timestamp: 2024-10-08T15:51:28.972Z
Learning: The user ashwin1111 has requested the removal of a condition that was always evaluating to false (*ngIf="false"), which was making an h5 element never visible. This indicates a preference for removing redundant or placeholder code that is not currently in use.

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/atomics-i18n-key-pattern.mdc:0-0
Timestamp: 2025-07-28T07:37:10.809Z
Learning: Applies to **/*.{service,pipe,directive}.ts : Use meaningful, semantic, and context-aware key names for i18n keys; avoid generic names like 'message1', 'text1', or 'error1'

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/atomics-i18n-key-pattern.mdc:0-0
Timestamp: 2025-07-28T07:37:10.809Z
Learning: Applies to **/*.{service,pipe,directive}.ts : Convert kebab-case file names to camelCase and remove the suffixes 'Service', 'Pipe', or 'Directive' when generating i18n keys from file names (e.g., 'report-expenses.service.ts' → 'reportExpenses')

Learnt from: CR
PR: fylein/fyle-integrations-app#0
File: .cursor/rules/atomics-i18n-key-pattern.mdc:0-0
Timestamp: 2025-07-28T07:37:10.809Z
Learning: Applies to **/*.{service,pipe,directive}.ts : i18n keys for services, pipes, and directives must use the naming schemas: services.., pipes.., directives..

🧬 Code Graph Analysis (1)
src/themes/fyle-preset.ts (3)
src/themes/theme.types.ts (1)
  • ThemeColors (10-30)
src/themes/index.ts (2)
  • FylePreset (2-2)
  • FyleCSSVars (6-6)
src/themes/fyle-css-vars.ts (1)
  • FyleCSSVars (11-256)
⏰ 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). (1)
  • GitHub Check: unit-test
🔇 Additional comments (2)
src/themes/fyle-preset.ts (1)

41-166: Well-structured theme preset with proper CSS variable usage.

The preset structure and button styles are well-implemented:

  • Properly references CSS variables from FyleCSSVars instead of hardcoding values
  • Comprehensive button variants covering all use cases
  • Clear organization with appropriate comments
src/themes/co-preset.ts (1)

319-332: Brand-specific toggle customization is appropriate.

The Co preset correctly hides toggle text with display: 'none' while the Fyle preset shows YES/NO text. This appears to be an intentional brand differentiation.

Comment on lines +43 to +51
private applyPresetStyles(brandId: SupportedBrand): void {
const preset = getPresetByBrand(brandId);
const root = this.document.documentElement;

// Apply color tokens as CSS custom properties
Object.entries(preset.colors).forEach(([key, value]) => {
root.style.setProperty(`--preset-${key}`, value);
});

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add validation for preset retrieval.

The applyPresetStyles method doesn't validate that getPresetByBrand returns a valid preset. This could cause runtime errors if an invalid brand is passed.

Add validation to ensure the preset exists:

 private applyPresetStyles(brandId: SupportedBrand): void {
   const preset = getPresetByBrand(brandId);
+  if (!preset || !preset.colors || !preset.components) {
+    console.error(`Invalid preset for brand: ${brandId}`);
+    return;
+  }
   const root = this.document.documentElement;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private applyPresetStyles(brandId: SupportedBrand): void {
const preset = getPresetByBrand(brandId);
const root = this.document.documentElement;
// Apply color tokens as CSS custom properties
Object.entries(preset.colors).forEach(([key, value]) => {
root.style.setProperty(`--preset-${key}`, value);
});
private applyPresetStyles(brandId: SupportedBrand): void {
const preset = getPresetByBrand(brandId);
if (!preset || !preset.colors || !preset.components) {
console.error(`Invalid preset for brand: ${brandId}`);
return;
}
const root = this.document.documentElement;
// Apply color tokens as CSS custom properties
Object.entries(preset.colors).forEach(([key, value]) => {
root.style.setProperty(`--preset-${key}`, value);
});
🤖 Prompt for AI Agents
In src/core/services/theme.service.ts between lines 43 and 51, the
applyPresetStyles method calls getPresetByBrand without checking if the returned
preset is valid, which can cause runtime errors if an invalid brand is passed.
Add a validation step after retrieving the preset to check if it is defined; if
not, handle the error appropriately, such as logging an error or throwing an
exception, before proceeding to apply styles.

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

Labels

size/XL Extra Large PR

Development

Successfully merging this pull request may close these issues.

2 participants