-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Primeng theme #1393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat: Primeng theme #1393
Conversation
WalkthroughThis 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 Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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:
Excessive !important usage: Nearly every property uses
!important, which could make future customization and debugging more difficult.Missing fallback values: CSS variables lack fallback values, which could lead to styling issues if variables aren't properly set.
Inconsistent !important application: Some properties like
.btn-text-primarycursor and font-size don't use!importantwhile 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
anytype 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 availablesrc/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:
- Ensure category order matches between brands:
// Both files should follow the same category order: // backgrounds, text, borders, buttons, gradients (if applicable), spacing, borderRadius, etc.
Add the same TypeScript typing as suggested for CoCSSVars for consistency and type safety.
Document brand-specific categories: The
gradientscategory 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
.fyleand.coclass 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
featureConfigsmaps the brand keycotoc1FeatureConfig.
Everywhere else in the docs the Co brand is referred to asco, notc1.
This minor typo will confuse readers and, if copied verbatim, break the example code.- co: c1FeatureConfig + co: coFeatureConfigPRIMENG_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 MD026Several 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
⛔ Files ignored due to path filters (1)
package-lock.jsonis 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 detectedThe
npm viewoutput 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 peerDependenciesPHASE_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.tsimportsThemeServicefromsrc/core/services/theme.service.tsvia../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
ThemeColorsinterface 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
getPresetByBrandfunction correctly uses theSupportedBrandtype 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 bothprimaryandprimaryLightusing 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
definePresetand@primeuix/themesimports, 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.tsuses: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 ArchitectureThe 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
SupportedBrandtype 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
ThemeServiceThe
getComputedCSSVarmethod 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-startfor thecotheme 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 docsHere the Co brand still lists
aktiv-grotesk; elsewhere (CURRENT_THEME_MECHANISM.mdline 48) it is"Optimist". Align the docs to avoid ambiguity.
| - [ ] 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' | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| ] | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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> | |||
There was a problem hiding this comment.
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.
| private initializeTheme(): void { | ||
| // Get brand from existing branding config | ||
| const brandId = brandingConfig.brandId as SupportedBrand; | ||
| this.switchToBrand(brandId); | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| 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); | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| boxShadow: '0 2px 4px rgba(0,0,0,0.1)' // Co doesn't have specific shadow var yet | ||
| }, |
There was a problem hiding this comment.
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.
There was a problem hiding this 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.brandIdand casts it toSupportedBrandwithout 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
applyButtonVariantStylesmethod usesanytype 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
📒 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
src/core/services/theme.service.ts
Outdated
| // 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); | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| // 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.
| /** | ||
| * Toggle between brands (useful for testing) | ||
| */ | ||
| toggleBrand(): void { | ||
| const newBrand: SupportedBrand = this.currentBrand === 'fyle' ? 'co' : 'fyle'; | ||
| this.switchToBrand(newBrand); | ||
| } |
There was a problem hiding this comment.
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:
- 1: https://empiricaledge.com/blog/testing-angular-applications-tools-best-practices-and-strategies/
- 2: https://ionicframework.com/docs/angular/testing
- 3: https://moldstud.com/articles/p-best-practices-for-testing-angular-services-tips-and-tricks-you-need-to-know
- 4: https://javascript.plainenglish.io/mastering-angular-unit-testing-best-practices-and-tools-7591753681cb
- 5: https://blog.logrocket.com/angular-unit-testing-tutorial/
- 6: https://www.telerik.com/blogs/10-best-practices-pro-tips-using-angular-cli
- 7: https://gorillalogic.com/blog/best-practices-for-angular-unit-testing
- 8: https://dev.to/soumayaerradi/testing-in-angular-1c6l
- 9: https://developer.mescius.com/blogs/the-top-50-tips-for-better-angular-development
- 10: https://infinum.com/handbook/frontend/angular/angular-guidelines-and-best-practices/testing
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.
There was a problem hiding this 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.brandIdand casts it toSupportedBrandwithout 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
applyButtonVariantStylesmethod usesanytype 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 buildsThe 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 usesFyleCSSVars.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
CoCSSVarsor create a fallback pattern.Also applies to: 89-89, 109-109
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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
There was a problem hiding this 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.brandIdand casts it toSupportedBrandwithout validation. This could cause runtime errors if the brandId is invalid or undefined.
681-699: Button variant styles method lacks type safety.The
applyButtonVariantStylesmethod usesanytype 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 buildsThe 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 usesFyleCSSVars.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.kebabcaseor 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(...)withkebabCase(...).src/themes/fyle-preset.ts (1)
537-550: Fix indentation inconsistency.The
panelproperty 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
panelproperty 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
📒 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
CoCSSVarsbut should useFyleCSSVarssince 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`.
| 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); | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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.
* feat p-chips * design border fix * theme datatable * lint fix * chips border fix --------- Co-authored-by: Vignesh <[email protected]>
There was a problem hiding this 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
toggleBrandmethod 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.brandIdand casts it toSupportedBrandwithout validation. This could cause runtime errors if the brandId is invalid or undefined.
67-676: Add type definitions for all style parameters.All
apply*Stylesmethods useanytype 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
panelproperty 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
panelproperty has inconsistent indentation, similar to the issue in fyle-preset.ts.- panel: { + panel: {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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.
| 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); | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
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
🔁 Code Duplication Report - Angular
🎉 This PR reduces duplicated code by 0.38%! |
|
There was a problem hiding this 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.brandIdand casts it toSupportedBrandwithout validation. This could cause runtime errors if the brandId is invalid or undefined.
1064-1070: Consider restricting toggleBrand to development environments.The
toggleBrandmethod 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 usesFyleCSSVars.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
panelproperty. 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
panelproperty, similar to the issue infyle-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
📒 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
FyleCSSVarsinstead 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.
| 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); | ||
| }); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
Description
Please add PR description here, add screenshots if needed
Clickup
https://app.clickup.com/
Summary by CodeRabbit
New Features
Documentation
Style
Chores