- |
+ |
{{ prop.name }}
|
diff --git a/projects/docs/src/assets/docs-content.json b/projects/docs/src/assets/docs-content.json
index 7fc400d..24475ab 100644
--- a/projects/docs/src/assets/docs-content.json
+++ b/projects/docs/src/assets/docs-content.json
@@ -1220,8 +1220,8 @@
"name": "GSSetupComponent",
"examples": {
"appConfigCode": "import { ApplicationConfig } from '@angular/core';\nimport { provideTolleConfig } from '@tolle_/tolle-ui';\n\nexport const appConfig: ApplicationConfig = {\n providers: [\n provideTolleConfig({\n primaryColor: '#8b5cf6', // Indigo\n radius: '0.5rem',\n darkByDefault: false\n })\n ]\n};",
- "tailwindConfigCode": "const { tolleUi } = require('@tolle_/tolle-ui/tailwind');\n\nmodule.exports = {\n content: [\n \"./src/**/*.{html,ts}\",\n \"./node_modules/@tolle_/tolle-ui/**/*.{html,ts,mjs}\"\n ],\n plugins: [tolleUi()],\n};",
- "globalStyles": "{\n \"styles\": [\n \"src/styles.css\",\n \"node_modules/@tolle_/tolle-ui/styles/theme.css\"\n ]\n}"
+ "tailwindConfigCode": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n darkMode: 'class',\n presets: [\n require('@tolle_/tolle-ui/preset') // Point to your library preset\n ],\n content: [\n \"./src/**/*.{html,ts}\",\n \"./node_modules/@tolle_/tolle-ui/**/*.{html,ts,mjs,html}\"\n ],\n plugins: [],\n};",
+ "globalStyles": "{\n \"styles\": [\n \"src/styles.css\",\n \"node_modules/@tolle_/tolle-ui/theme.css\"\n ]\n}"
},
"props": {},
"selector": "app-gs-setup"
@@ -2472,8 +2472,8 @@
{
"name": "ThemingIntegrationComponent",
"examples": {
- "globalStyles": "{\n \"styles\": [\n \"src/styles.css\",\n \"node_modules/@tolle_/tolle-ui/styles/theme.css\" \n ]\n}",
- "tailwindConfigCode": "const { tolleUi } = require('@tolle_/tolle-ui/tailwind');\n\n/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n content: [\n \"./src/**/*.{html,ts}\",\n // Include Tolle UI sources\n \"./node_modules/@tolle_/tolle-ui/**/*.{html,ts,mjs}\"\n ],\n theme: {\n extend: {},\n },\n plugins: [\n tolleUi({\n // Optional: override prefix\n // prefix: 'tolle-'\n })\n ],\n}"
+ "globalStyles": "{\n \"styles\": [\n \"src/styles.css\",\n \"node_modules/@tolle_/tolle-ui/styles/theme.css\"\n ]\n}",
+ "tailwindConfigCode": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n darkMode: 'class',\n presets: [\n require('@tolle_/tolle-ui/preset') // Point to your library preset\n ],\n content: [\n \"./src/**/*.{html,ts}\",\n \"./node_modules/@tolle_/tolle-ui/**/*.{html,ts,mjs,html}\"\n ],\n plugins: [],\n};"
},
"props": {},
"selector": "app-theming-integration"
diff --git a/projects/tolle/package.json b/projects/tolle/package.json
index 5666713..56cdc03 100644
--- a/projects/tolle/package.json
+++ b/projects/tolle/package.json
@@ -1,6 +1,6 @@
{
"name": "@tolle_/tolle-ui",
- "version": "18.2.2",
+ "version": "18.2.7",
"publishConfig": {
"access": "public"
},
diff --git a/projects/tolle/src/lib/alert-dialog-dynamic.component.ts b/projects/tolle/src/lib/alert-dialog-dynamic.component.ts
index 5bf5320..361ab5f 100644
--- a/projects/tolle/src/lib/alert-dialog-dynamic.component.ts
+++ b/projects/tolle/src/lib/alert-dialog-dynamic.component.ts
@@ -1,6 +1,23 @@
import { Component, Inject, inject, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
+ AlertDialogComponent,
+ AlertDialogContentComponent,
+ AlertDialogHeaderComponent,
+ AlertDialogTitleComponent,
+ AlertDialogDescriptionComponent,
+ AlertDialogFooterComponent,
+ AlertDialogCancelComponent,
+ AlertDialogActionComponent
+} from './alert-dialog.component';
+import { ButtonComponent } from './button.component';
+import { AlertDialogConfig, AlertDialogRef } from './alert-dialog.types';
+
+@Component({
+ selector: 'tolle-alert-dialog-dynamic',
+ standalone: true,
+ imports: [
+ CommonModule,
AlertDialogComponent,
AlertDialogContentComponent,
AlertDialogHeaderComponent,
@@ -8,29 +25,12 @@ import {
AlertDialogDescriptionComponent,
AlertDialogFooterComponent,
AlertDialogCancelComponent,
- AlertDialogActionComponent
-} from './alert-dialog.component';
-import { ButtonComponent } from './button.component';
-import { AlertDialogConfig, AlertDialogRef } from './alert-dialog.types';
-
-@Component({
- selector: 'tolle-alert-dialog-dynamic',
- standalone: true,
- imports: [
- CommonModule,
- AlertDialogComponent,
- AlertDialogContentComponent,
- AlertDialogHeaderComponent,
- AlertDialogTitleComponent,
- AlertDialogDescriptionComponent,
- AlertDialogFooterComponent,
- AlertDialogCancelComponent,
- AlertDialogActionComponent,
- ButtonComponent
- ],
- template: `
+ AlertDialogActionComponent,
+ ButtonComponent
+ ],
+ template: `
-
+
{{ config.title }}
@@ -52,16 +52,16 @@ import { AlertDialogConfig, AlertDialogRef } from './alert-dialog.types';
`
})
export class AlertDialogDynamicComponent {
- config!: AlertDialogConfig;
- dialogRef!: AlertDialogRef;
+ config!: AlertDialogConfig;
+ dialogRef!: AlertDialogRef;
- onOpenChange(open: boolean) {
- if (!open) {
- this.close(false);
- }
+ onOpenChange(open: boolean) {
+ if (!open) {
+ this.close(false);
}
+ }
- close(result: boolean) {
- this.dialogRef.close(result);
- }
+ close(result: boolean) {
+ this.dialogRef.close(result);
+ }
}
diff --git a/projects/tolle/src/lib/alert-dialog.component.ts b/projects/tolle/src/lib/alert-dialog.component.ts
index 6e20fdd..ae9a6e8 100644
--- a/projects/tolle/src/lib/alert-dialog.component.ts
+++ b/projects/tolle/src/lib/alert-dialog.component.ts
@@ -4,6 +4,7 @@ import { Overlay, OverlayRef, OverlayConfig } from '@angular/cdk/overlay';
import { TemplatePortal, ComponentPortal } from '@angular/cdk/portal';
import { cn } from './utils/cn';
import { BehaviorSubject } from 'rxjs';
+import { AlertDialogSize } from './alert-dialog.types';
@Injectable()
class AlertDialogInternalService {
@@ -130,6 +131,7 @@ export class AlertDialogPortalComponent implements OnInit, OnDestroy {
})
export class AlertDialogContentComponent {
@Input() class: string = '';
+ @Input() size: AlertDialogSize = 'lg';
private alertDialogService = inject(AlertDialogInternalService);
isOpen = false;
@@ -142,7 +144,17 @@ export class AlertDialogContentComponent {
get computedClass() {
return cn(
- "fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-50 grid w-full max-w-lg gap-4 border bg-background p-6 shadow-lg data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] rounded-lg",
+ "fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-50 grid w-full gap-4 border border-input bg-background p-6 shadow-lg data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] rounded-lg",
+ {
+ 'max-w-xs': this.size === 'xs',
+ 'max-w-sm': this.size === 'sm',
+ 'max-w-md': this.size === 'md',
+ 'max-w-lg': this.size === 'lg',
+ 'max-w-xl': this.size === 'xl',
+ 'max-w-2xl': this.size === '2xl',
+ 'max-w-full': this.size === 'full',
+ 'max-w-fit': this.size === 'fit',
+ },
this.class
);
}
@@ -181,7 +193,7 @@ export class AlertDialogFooterComponent {
})
export class AlertDialogTitleComponent {
@Input() class: string = '';
- get computedClass() { return cn("text-lg font-semibold", this.class); }
+ get computedClass() { return cn("text-lg font-semibold text-foreground", this.class); }
}
@Component({
diff --git a/projects/tolle/src/lib/alert-dialog.service.spec.ts b/projects/tolle/src/lib/alert-dialog.service.spec.ts
new file mode 100644
index 0000000..57dac8e
--- /dev/null
+++ b/projects/tolle/src/lib/alert-dialog.service.spec.ts
@@ -0,0 +1,58 @@
+import { TestBed } from '@angular/core/testing';
+import { AlertDialogService } from './alert-dialog.service';
+import { AlertDialogConfig } from './alert-dialog.types';
+import { OverlayContainer } from '@angular/cdk/overlay';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+
+describe('AlertDialogService Sizing', () => {
+ let service: AlertDialogService;
+ let overlayContainer: OverlayContainer;
+ let overlayContainerElement: HTMLElement;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [NoopAnimationsModule],
+ providers: [AlertDialogService]
+ });
+
+ service = TestBed.inject(AlertDialogService);
+ overlayContainer = TestBed.inject(OverlayContainer);
+ overlayContainerElement = overlayContainer.getContainerElement();
+ });
+
+ afterEach(() => {
+ overlayContainer.ngOnDestroy();
+ });
+
+ it('should apply max-w-lg by default', () => {
+ service.open({
+ title: 'Test',
+ description: 'Test description'
+ });
+
+ const dialogContent = overlayContainerElement.querySelector('tolle-alert-dialog-content div');
+ expect(dialogContent?.classList.contains('max-w-lg')).toBeTrue();
+ });
+
+ it('should apply max-w-xs when size is xs', () => {
+ service.open({
+ title: 'Test',
+ description: 'Test description',
+ size: 'xs'
+ });
+
+ const dialogContent = overlayContainerElement.querySelector('tolle-alert-dialog-content div');
+ expect(dialogContent?.classList.contains('max-w-xs')).toBeTrue();
+ });
+
+ it('should apply max-w-full when size is full', () => {
+ service.open({
+ title: 'Test',
+ description: 'Test description',
+ size: 'full'
+ });
+
+ const dialogContent = overlayContainerElement.querySelector('tolle-alert-dialog-content div');
+ expect(dialogContent?.classList.contains('max-w-full')).toBeTrue();
+ });
+});
diff --git a/projects/tolle/src/lib/alert-dialog.types.ts b/projects/tolle/src/lib/alert-dialog.types.ts
index 8a8cf18..39968a4 100644
--- a/projects/tolle/src/lib/alert-dialog.types.ts
+++ b/projects/tolle/src/lib/alert-dialog.types.ts
@@ -1,11 +1,14 @@
import { Subject, Observable } from 'rxjs';
+export type AlertDialogSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full' | 'fit';
+
export interface AlertDialogConfig {
title: string;
description: string;
cancelText?: string;
actionText?: string;
variant?: 'default' | 'destructive';
+ size?: AlertDialogSize;
}
export class AlertDialogRef {
diff --git a/projects/tolle/src/lib/button.component.ts b/projects/tolle/src/lib/button.component.ts
index bc79143..38a704d 100644
--- a/projects/tolle/src/lib/button.component.ts
+++ b/projects/tolle/src/lib/button.component.ts
@@ -10,7 +10,7 @@ const buttonVariants = cva(
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
- outline: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
+ outline: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground text-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary-foreground underline-offset-4 hover:underline",
diff --git a/projects/tolle/src/lib/data-table.component.ts b/projects/tolle/src/lib/data-table.component.ts
index 076925d..957417e 100644
--- a/projects/tolle/src/lib/data-table.component.ts
+++ b/projects/tolle/src/lib/data-table.component.ts
@@ -79,15 +79,15 @@ export interface TableColumn {
-
+
- |
+ |
+ [class]="cn('text-left font-medium text-foreground',headerPaddingClass,fontSizeClass, col.class, stickyHeader ? 'sticky top-0 z-10 bg-background shadow-sm' : '')">
(false);
- isDark$ = this.isDarkSubject.asObservable();
+ private _isDark = new BehaviorSubject(false);
+ public isDark$ = this._isDark.asObservable();
+
private styleId = 'tolle-dynamic-theme';
constructor(
@Inject(DOCUMENT) private document: Document,
- @Inject(PLATFORM_ID) private platformId: Object,
+ @Inject(PLATFORM_ID) private platformId: object,
@Optional() @Inject(TOLLE_CONFIG) private config: TolleConfig,
rendererFactory: RendererFactory2
) {
@@ -29,120 +30,124 @@ export class ThemeService {
const savedRadius = localStorage.getItem('tolle-radius');
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
- console.log('Theme initialization:', {
- savedTheme,
- savedPrimary,
- savedRadius,
- config: this.config,
- systemPrefersDark
- });
-
- // 1. Determine Dark/Light Mode
- // Priority: Saved Theme > Config Default > System Preference
- let shouldBeDark = systemPrefersDark; // Start with system
+ // Determine Dark/Light Mode
+ let shouldBeDark = systemPrefersDark;
if (savedTheme) {
- // User has saved preference
shouldBeDark = savedTheme === 'dark';
} else if (this.config?.darkByDefault !== undefined) {
- // Use config default if no user preference
shouldBeDark = this.config.darkByDefault;
}
// Apply theme mode
if (shouldBeDark) {
- this.enableDarkMode(false); // Don't save, we'll save after checking all preferences
+ this.enableDarkMode(false);
} else {
this.disableDarkMode(false);
}
- // 2. Apply Primary Color
- // Priority: Saved Color > Config Color
+ // Apply Primary Color
if (savedPrimary) {
- // User has saved color preference
- this.setPrimaryColor(savedPrimary, false); // Don't save again
+ this.setPrimaryColor(savedPrimary, false);
} else if (this.config?.primaryColor) {
- // Use config default if no user preference
- this.setPrimaryColor(this.config.primaryColor, false); // Save this as user preference
+ this.setPrimaryColor(this.config.primaryColor, false);
}
- // 3. Apply Radius
- // Priority: Saved Radius > Config Radius
+ // Apply Radius
if (savedRadius) {
- // User has saved radius preference
this.setRadius(savedRadius, false);
} else if (this.config?.radius) {
- // Use config default if no user preference
this.setRadius(this.config.radius, false);
}
- // Save theme mode preference if it came from config or system
if (!savedTheme) {
localStorage.setItem('tolle-theme', shouldBeDark ? 'dark' : 'light');
}
}
- /**
- * Sets the border radius for all components
- */
+
setRadius(radius: string, persist = true) {
if (!isPlatformBrowser(this.platformId)) return;
const root = this.document.documentElement;
-
- // Set the CSS variable
this.renderer.setStyle(root, '--radius', radius);
-
- // Also update the dynamic styles to include radius calculations
this.updateRadiusInDynamicStyles(radius);
+ this.updateAllRadiusVariables(radius);
- // Persist if needed
if (persist) {
localStorage.setItem('tolle-radius', radius);
}
}
- /**
- * Updates the radius calculations in dynamic styles
- */
+ private updateAllRadiusVariables(baseRadius: string) {
+ const root = this.document.documentElement;
+
+ // Parse the radius to extract numeric value and unit
+ const radiusMatch = baseRadius.match(/^(\d*\.?\d+)([a-z]+|%)$/);
+ if (!radiusMatch) {
+ console.warn(`Invalid radius format: ${baseRadius}. Using default values.`);
+ return;
+ }
+
+ const numericValue = parseFloat(radiusMatch[1]);
+ const unit = radiusMatch[2];
+
+ // Set all radius variables according to your CSS classes
+ this.renderer.setStyle(root, '--radius-sm', `${numericValue * 0.5}${unit}`);
+ this.renderer.setStyle(root, '--radius-md', `${numericValue}${unit}`);
+ this.renderer.setStyle(root, '--radius-lg', `${numericValue * 1.5}${unit}`);
+ this.renderer.setStyle(root, '--radius-xl', `${numericValue * 2}${unit}`);
+ this.renderer.setStyle(root, '--radius-2xl', `${numericValue * 3}${unit}`);
+ this.renderer.setStyle(root, '--radius-3xl', `${numericValue * 4}${unit}`);
+ this.renderer.setStyle(root, '--radius-full', '9999px');
+ }
+
private updateRadiusInDynamicStyles(radius: string) {
const existingStyle = this.document.getElementById(this.styleId);
if (existingStyle) {
let css = existingStyle.textContent || '';
- // Update or add radius calculations
- if (css.includes('--radius:')) {
- // Replace existing radius declarations
- css = css.replace(/--radius:[^;]+;/g, `--radius: ${radius};`);
- } else {
- // Add radius to the beginning of :root
- css = css.replace(/:root\s*{/, `:root {\n --radius: ${radius};`);
- }
-
- // Update the calculated radius values in the CSS
- const radiusCalcRegex = /calc\(var\(--radius[^)]+\)/g;
- css = css.replace(radiusCalcRegex, (match) => {
- if (match.includes('- 2px')) {
- return `calc(${radius} - 2px)`;
- } else if (match.includes('- 4px')) {
- return `calc(${radius} - 4px)`;
+ // Parse the radius to calculate derived values
+ const radiusMatch = radius.match(/^(\d*\.?\d+)([a-z]+|%)$/);
+ if (radiusMatch) {
+ const numericValue = parseFloat(radiusMatch[1]);
+ const unit = radiusMatch[2];
+
+ const derivedRadii = {
+ '--radius-sm': `${numericValue * 0.5}${unit}`,
+ '--radius-md': `${numericValue}${unit}`,
+ '--radius-lg': `${numericValue * 1.5}${unit}`,
+ '--radius-xl': `${numericValue * 2}${unit}`,
+ '--radius-2xl': `${numericValue * 3}${unit}`,
+ '--radius-3xl': `${numericValue * 4}${unit}`,
+ '--radius-full': '9999px'
+ };
+
+ // Update base radius
+ if (css.includes('--radius:')) {
+ css = css.replace(/--radius:[^;]+;/g, `--radius: ${radius};`);
}
- return match;
- });
+
+ // Update all derived radius variables
+ Object.entries(derivedRadii).forEach(([varName, value]) => {
+ if (css.includes(varName)) {
+ const regex = new RegExp(`${varName}:[^;]+;`, 'g');
+ css = css.replace(regex, `${varName}: ${value};`);
+ } else {
+ // Add the variable if it doesn't exist
+ css = css.replace(/:root\s*{/, `:root {\n ${varName}: ${value};`);
+ }
+ });
+ }
existingStyle.textContent = css;
}
}
- /**
- * Generates full primary color palette (50-900) based on base color
- */
private generatePrimaryShades(baseColor: string) {
- // Convert hex to RGB
const rgb = this.hexToRgb(baseColor);
const rgbString = rgb ? `${rgb.r} ${rgb.g} ${rgb.b}` : '37 99 235';
- // Create lighter ring colors in RGB
const ringLight = this.lightenColor(baseColor, 40);
const ringLightRgb = this.hexToRgb(ringLight);
const ringLightRgbString = ringLightRgb ? `${ringLightRgb.r} ${ringLightRgb.g} ${ringLightRgb.b}` : '96 165 250';
@@ -151,21 +156,37 @@ export class ThemeService {
const ringDarkRgb = this.hexToRgb(ringDark);
const ringDarkRgbString = ringDarkRgb ? `${ringDarkRgb.r} ${ringDarkRgb.g} ${ringDarkRgb.b}` : '147 197 253';
- // Get current radius or use default
+ // Get current radius or default
const root = this.document.documentElement;
const currentRadius = getComputedStyle(root).getPropertyValue('--radius').trim() || '0.5rem';
+ // Calculate all derived radius values
+ const radiusMatch = currentRadius.match(/^(\d*\.?\d+)([a-z]+|%)$/);
+ let radiusVariables = '';
+
+ if (radiusMatch) {
+ const numericValue = parseFloat(radiusMatch[1]);
+ const unit = radiusMatch[2];
+
+ radiusVariables = `
+ --radius: ${currentRadius};
+ --radius-sm: ${numericValue * 0.5}${unit};
+ --radius-md: ${numericValue}${unit};
+ --radius-lg: ${numericValue * 1.5}${unit};
+ --radius-xl: ${numericValue * 2}${unit};
+ --radius-2xl: ${numericValue * 3}${unit};
+ --radius-3xl: ${numericValue * 4}${unit};
+ --radius-full: 9999px;
+ `;
+ } else {
+ radiusVariables = `--radius: ${currentRadius};`;
+ }
+
const css = `
- /* Override primary colors - this needs to come AFTER your main CSS */
:root {
- /* Primary in RGB format for Tailwind opacity support */
--primary: ${rgbString};
--primary-foreground: ${this.getContrastColorRgb(baseColor)};
-
- /* Radius */
- --radius: ${currentRadius};
-
- /* Primary shades for light mode */
+ ${radiusVariables}
--primary-50: ${this.hexToRgbString(this.lightenColor(baseColor, 90))};
--primary-100: ${this.hexToRgbString(this.lightenColor(baseColor, 80))};
--primary-200: ${this.hexToRgbString(this.lightenColor(baseColor, 60))};
@@ -176,17 +197,13 @@ export class ThemeService {
--primary-700: ${this.hexToRgbString(this.darkenColor(baseColor, 40))};
--primary-800: ${this.hexToRgbString(this.darkenColor(baseColor, 60))};
--primary-900: ${this.hexToRgbString(this.darkenColor(baseColor, 80))};
-
- /* Update ring color to be lighter */
--ring: ${ringLightRgbString};
}
.dark {
- /* For dark mode, we keep the primary color but adjust shades */
--primary: ${rgbString};
--primary-foreground: ${this.getContrastColorRgb(baseColor)};
-
- /* Dark mode shades */
+ ${radiusVariables}
--primary-50: ${this.hexToRgbString(this.darkenColor(baseColor, 85))};
--primary-100: ${this.hexToRgbString(this.darkenColor(baseColor, 75))};
--primary-200: ${this.hexToRgbString(this.darkenColor(baseColor, 65))};
@@ -197,8 +214,6 @@ export class ThemeService {
--primary-700: ${this.hexToRgbString(this.lightenColor(baseColor, 35))};
--primary-800: ${this.hexToRgbString(this.lightenColor(baseColor, 50))};
--primary-900: ${this.hexToRgbString(this.lightenColor(baseColor, 65))};
-
- /* Update ring color for dark mode - lighter variant */
--ring: ${ringDarkRgbString};
}
`;
@@ -206,13 +221,8 @@ export class ThemeService {
this.injectDynamicStyles(css);
}
- /**
- * Convert hex color to RGB object
- */
private hexToRgb(hex: string): { r: number, g: number, b: number } | null {
- // Remove # if present
const cleanedHex = hex.replace('#', '');
-
let r = 0, g = 0, b = 0;
if (cleanedHex.length === 3) {
@@ -232,42 +242,28 @@ export class ThemeService {
return null;
}
- /**
- * Convert hex to RGB string format for CSS (space-separated)
- */
private hexToRgbString(hexColor: string): string {
const rgb = this.hexToRgb(hexColor);
return rgb ? `${rgb.r} ${rgb.g} ${rgb.b}` : '37 99 235';
}
- /**
- * Get contrast color in RGB format
- */
private getContrastColorRgb(hexColor: string): string {
const contrast = this.getContrastColor(hexColor);
const rgb = this.hexToRgb(contrast);
return rgb ? `${rgb.r} ${rgb.g} ${rgb.b}` : '255 255 255';
}
- /**
- * Lighten a hex color by a percentage
- */
private lightenColor(color: string, percent: number): string {
const rgb = this.hexToRgb(color);
if (!rgb) return color;
- // Lighten by percentage
const r = Math.min(255, Math.floor(rgb.r + (255 - rgb.r) * (percent / 100)));
const g = Math.min(255, Math.floor(rgb.g + (255 - rgb.g) * (percent / 100)));
const b = Math.min(255, Math.floor(rgb.b + (255 - rgb.b) * (percent / 100)));
- // Convert back to hex
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
}
- /**
- * Darken a hex color by a percentage
- */
private darkenColor(color: string, percent: number): string {
const rgb = this.hexToRgb(color);
if (!rgb) return color;
@@ -280,17 +276,11 @@ export class ThemeService {
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
}
- /**
- * Calculate contrast color (black or white) based on background color
- */
private getContrastColor(hexColor: string): string {
const rgb = this.hexToRgb(hexColor);
if (!rgb) return '#ffffff';
- // Calculate relative luminance
const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
-
- // Return black for light colors, white for dark colors
return luminance > 0.5 ? '#000000' : '#ffffff';
}
@@ -318,7 +308,7 @@ export class ThemeService {
if (saveToStorage) {
localStorage.setItem('tolle-theme', 'dark');
}
- this.isDarkSubject.next(true);
+ this._isDark.next(true);
}
private disableDarkMode(saveToStorage = true) {
@@ -326,14 +316,14 @@ export class ThemeService {
if (saveToStorage) {
localStorage.setItem('tolle-theme', 'light');
}
- this.isDarkSubject.next(false);
+ this._isDark.next(false);
}
+
setPrimaryColor(color: string, persist = true) {
if (!isPlatformBrowser(this.platformId)) return;
this.generatePrimaryShades(color);
- // Also set inline for immediate update
const rgb = this.hexToRgb(color);
if (rgb) {
this.renderer.setStyle(
@@ -349,7 +339,11 @@ export class ThemeService {
}
get currentTheme(): 'dark' | 'light' {
- return this.isDarkSubject.value ? 'dark' : 'light';
+ return this._isDark.getValue() ? 'dark' : 'light';
+ }
+
+ get isDark(): boolean {
+ return this._isDark.getValue();
}
get primaryColor(): string | null {
@@ -359,7 +353,6 @@ export class ThemeService {
const cssValue = getComputedStyle(root).getPropertyValue('--primary').trim();
if (cssValue && cssValue !== '') {
- // Convert RGB string back to hex for external use
const rgbParts = cssValue.split(' ').map(Number);
if (rgbParts.length === 3) {
return `#${rgbParts[0].toString(16).padStart(2, '0')}${rgbParts[1].toString(16).padStart(2, '0')}${rgbParts[2].toString(16).padStart(2, '0')}`;
@@ -369,24 +362,16 @@ export class ThemeService {
return localStorage.getItem('tolle-primary-color');
}
- /**
- * Reset to config defaults (clears user preferences)
- */
resetToConfigDefaults() {
if (!isPlatformBrowser(this.platformId)) return;
- // Clear user preferences
localStorage.removeItem('tolle-theme');
localStorage.removeItem('tolle-primary-color');
localStorage.removeItem('tolle-radius');
- // Re-initialize with config defaults
this.initializeTheme();
}
- /**
- * Get current user preferences
- */
getUserPreferences() {
if (!isPlatformBrowser(this.platformId)) return null;
@@ -397,9 +382,6 @@ export class ThemeService {
};
}
- /**
- * Clear all user preferences
- */
clearUserPreferences() {
if (!isPlatformBrowser(this.platformId)) return;
@@ -407,7 +389,6 @@ export class ThemeService {
localStorage.removeItem('tolle-primary-color');
localStorage.removeItem('tolle-radius');
- // Reset to system defaults (not config defaults)
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (systemPrefersDark) {
this.enableDarkMode(false);
@@ -415,12 +396,16 @@ export class ThemeService {
this.disableDarkMode(false);
}
- // Remove CSS variables
const root = this.document.documentElement;
this.renderer.removeStyle(root, '--primary');
this.renderer.removeStyle(root, '--radius');
- // Clear dynamic styles
+ // Also remove all derived radius variables
+ ['--radius-sm', '--radius-md', '--radius-lg', '--radius-xl', '--radius-2xl', '--radius-3xl', '--radius-full']
+ .forEach(varName => {
+ this.renderer.removeStyle(root, varName);
+ });
+
const existingStyle = this.document.getElementById(this.styleId);
if (existingStyle) {
existingStyle.remove();
diff --git a/projects/tolle/src/lib/tooltip.directive.spec.ts b/projects/tolle/src/lib/tooltip.directive.spec.ts
index 3bfca07..982c591 100644
--- a/projects/tolle/src/lib/tooltip.directive.spec.ts
+++ b/projects/tolle/src/lib/tooltip.directive.spec.ts
@@ -1,8 +1,10 @@
+import { ElementRef } from '@angular/core';
import { TooltipDirective } from './tooltip.directive';
describe('TooltipDirective', () => {
it('should create an instance', () => {
- const directive = new TooltipDirective();
+ const mockElementRef = { nativeElement: document.createElement('div') } as ElementRef;
+ const directive = new TooltipDirective(mockElementRef);
expect(directive).toBeTruthy();
});
});
diff --git a/projects/tolle/theme.css b/projects/tolle/theme.css
index 015aa36..5d50fef 100644
--- a/projects/tolle/theme.css
+++ b/projects/tolle/theme.css
@@ -122,8 +122,7 @@ a:hover {
}
/* Remix Icons */
-i[class^="ri-"],
-i[class*=" ri-"] {
+:where(i[class^="ri-"], i[class*=" ri-"]) {
display: inline-flex;
align-items: center;
justify-content: center;
@@ -411,4 +410,37 @@ tolle-input:focus-within {
/* Error state ring color */
.ring-destructive {
--tw-ring-color: var(--destructive) !important;
-}
\ No newline at end of file
+}
+
+/* In your global styles.css or tailwind.config.js if using Tailwind */
+.rounded-sm {
+ border-radius: var(--radius-sm, 0.125rem);
+}
+
+.rounded {
+ border-radius: var(--radius, 0.25rem);
+}
+
+.rounded-md {
+ border-radius: var(--radius-md, 0.375rem);
+}
+
+.rounded-lg {
+ border-radius: var(--radius-lg, 0.5rem);
+}
+
+.rounded-xl {
+ border-radius: var(--radius-xl, 0.75rem);
+}
+
+.rounded-2xl {
+ border-radius: var(--radius-2xl, 1rem);
+}
+
+.rounded-3xl {
+ border-radius: var(--radius-3xl, 1.5rem);
+}
+
+.rounded-full {
+ border-radius: var(--radius-full, 9999px);
+}
| |