diff --git a/projects/docs/public/llm-ctx.txt b/projects/docs/public/llm-ctx.txt index 2ea1b55..397e6fa 100644 --- a/projects/docs/public/llm-ctx.txt +++ b/projects/docs/public/llm-ctx.txt @@ -37,14 +37,17 @@ export const appConfig: ApplicationConfig = { #### Tailwind Configuration (tailwind.config.js) ```javascript -const { tolleUi } = require('@tolle_/tolle-ui/tailwind'); - +/** @type {import('tailwindcss').Config} */ module.exports = { + darkMode: 'class', + presets: [ + require('@tolle_/tolle-ui/preset') // Point to your library preset + ], content: [ "./src/**/*.{html,ts}", - "./node_modules/@tolle_/tolle-ui/**/*.{html,ts,mjs}" + "./node_modules/@tolle_/tolle-ui/**/*.{html,ts,mjs,html}" ], - plugins: [tolleUi()], + plugins: [], }; ``` @@ -53,7 +56,7 @@ module.exports = { { "styles": [ "src/styles.css", - "node_modules/@tolle_/tolle-ui/styles/theme.css" + "node_modules/@tolle_/tolle-ui/theme.css" ] } ``` @@ -1806,32 +1809,25 @@ export const appConfig: ApplicationConfig = { { "styles": [ "src/styles.css", - "node_modules/@tolle_/tolle-ui/styles/theme.css" + "node_modules/@tolle_/tolle-ui/styles/theme.css" ] } ``` #### tailwindConfig ```html -const { tolleUi } = require('@tolle_/tolle-ui/tailwind'); - /** @type {import('tailwindcss').Config} */ module.exports = { + darkMode: 'class', + presets: [ + require('@tolle_/tolle-ui/preset') // Point to your library preset + ], content: [ "./src/**/*.{html,ts}", - // Include Tolle UI sources - "./node_modules/@tolle_/tolle-ui/**/*.{html,ts,mjs}" - ], - theme: { - extend: {}, - }, - plugins: [ - tolleUi({ - // Optional: override prefix - // prefix: 'tolle-' - }) + "./node_modules/@tolle_/tolle-ui/**/*.{html,ts,mjs,html}" ], -} + plugins: [], +}; ``` diff --git a/projects/docs/src/app/components/alert-dialog-docs/alert-dialog-interactive/alert-dialog-interactive.component.ts b/projects/docs/src/app/components/alert-dialog-docs/alert-dialog-interactive/alert-dialog-interactive.component.ts index bce0d60..d53a0b8 100644 --- a/projects/docs/src/app/components/alert-dialog-docs/alert-dialog-interactive/alert-dialog-interactive.component.ts +++ b/projects/docs/src/app/components/alert-dialog-docs/alert-dialog-interactive/alert-dialog-interactive.component.ts @@ -66,7 +66,15 @@ import { ButtonComponent } from '../../../../../../tolle/src/lib/button.componen - Service-based Trigger +
+ Size: XS + Size: SM + Size: MD + Size: LG + Size: XL + Size: 2XL + Size: Full +
@@ -75,12 +83,13 @@ import { ButtonComponent } from '../../../../../../tolle/src/lib/button.componen export class AlertDialogInteractiveComponent { private alertDialog = inject(AlertDialogService); - openFromService() { + openFromService(size: any = 'lg') { this.alertDialog.open({ title: "Delete Project?", description: "This will permanently delete the selected project. This action cannot be undone.", actionText: "Delete", - variant: "destructive" + variant: "destructive", + size: size }); } diff --git a/projects/docs/src/app/components/data-table-docs/data-table-docs.component.html b/projects/docs/src/app/components/data-table-docs/data-table-docs.component.html index 04f6b90..71905db 100644 --- a/projects/docs/src/app/components/data-table-docs/data-table-docs.component.html +++ b/projects/docs/src/app/components/data-table-docs/data-table-docs.component.html @@ -6,6 +6,7 @@ + @@ -55,6 +56,12 @@ +
  • + + Sticky Header + +
  • @@ -63,4 +70,4 @@
  • - \ No newline at end of file + diff --git a/projects/docs/src/app/components/data-table-docs/data-table-docs.component.ts b/projects/docs/src/app/components/data-table-docs/data-table-docs.component.ts index 7961df1..78c586f 100644 --- a/projects/docs/src/app/components/data-table-docs/data-table-docs.component.ts +++ b/projects/docs/src/app/components/data-table-docs/data-table-docs.component.ts @@ -7,8 +7,9 @@ import { ColumnHidingDocsComponent } from './column-hiding-docs/column-hiding-do import { PaginationDocsComponent } from './pagination-docs/pagination-docs.component'; import { SearchableDocsComponent } from './searchable-docs/searchable-docs.component'; import { DataTableApiComponent } from './data-table-api/data-table-api.component'; -import { DocsWrapperComponent } from '../shared/docs-wrapper/docs-wrapper.component'; +import { DocsWrapperComponent } from '../shared/docs-wrapper/docs-wrapper.component'; +import { StickyDocsComponent } from './sticky-docs/sticky-docs.component'; @Component({ selector: 'app-data-table-docs', @@ -21,7 +22,8 @@ import { DocsWrapperComponent } from '../shared/docs-wrapper/docs-wrapper.compon PaginationDocsComponent, SearchableDocsComponent, DataTableApiComponent, - DocsWrapperComponent + DocsWrapperComponent, + StickyDocsComponent ], templateUrl: './data-table-docs.component.html', diff --git a/projects/docs/src/app/components/data-table-docs/sticky-docs/sticky-docs.component.html b/projects/docs/src/app/components/data-table-docs/sticky-docs/sticky-docs.component.html new file mode 100644 index 0000000..2c9253c --- /dev/null +++ b/projects/docs/src/app/components/data-table-docs/sticky-docs/sticky-docs.component.html @@ -0,0 +1,55 @@ + \ No newline at end of file diff --git a/projects/docs/src/app/components/data-table-docs/sticky-docs/sticky-docs.component.ts b/projects/docs/src/app/components/data-table-docs/sticky-docs/sticky-docs.component.ts new file mode 100644 index 0000000..ada6ece --- /dev/null +++ b/projects/docs/src/app/components/data-table-docs/sticky-docs/sticky-docs.component.ts @@ -0,0 +1,47 @@ +import { Component, inject, OnInit } from '@angular/core'; +import { BaseService } from '../../../shared/base.service'; +import { SourceCodeService } from '../../../shared/source-code.service'; +import { Observable } from 'rxjs'; +import { AsyncPipe, NgIf } from '@angular/common'; +import { BaseEditorComponent } from '../../../shared/base-editor/base-editor.component'; +import { SegmentedComponent } from '../../../../../../tolle/src/lib/segment.component'; +import { FormsModule } from '@angular/forms'; +import { StickyComponent } from '../../../docs-examples/data-table/sticky/sticky.component'; + +@Component({ + selector: 'app-sticky-docs', + standalone: true, + imports: [ + AsyncPipe, + BaseEditorComponent, + NgIf, + SegmentedComponent, + FormsModule, + StickyComponent + ], + templateUrl: './sticky-docs.component.html' +}) +export class StickyDocsComponent implements OnInit { + baseService = inject(BaseService); + sourceService = inject(SourceCodeService); + + htmlCode$!: Observable; + tsCode$!: Observable; + + activeCallback = 'preview'; + viewOptions = [ + { label: 'Preview', value: 'preview' }, + { label: 'Code', value: 'code' } + ]; + + codeType = 'html'; + codeOptions = [ + { label: 'HTML', value: 'html' }, + { label: 'TypeScript', value: 'typescript' } + ]; + + ngOnInit(): void { + this.htmlCode$ = this.sourceService.getFile('data-table/sticky/sticky.component.html'); + this.tsCode$ = this.sourceService.getFile('data-table/sticky/sticky.component.ts'); + } +} diff --git a/projects/docs/src/app/components/getting-started-docs/gs-installation/gs-installation.component.ts b/projects/docs/src/app/components/getting-started-docs/gs-installation/gs-installation.component.ts index f3d5b57..1b889fd 100644 --- a/projects/docs/src/app/components/getting-started-docs/gs-installation/gs-installation.component.ts +++ b/projects/docs/src/app/components/getting-started-docs/gs-installation/gs-installation.component.ts @@ -8,5 +8,5 @@ import { BaseService } from '../../../shared/base.service'; }) export class GSInstallationComponent { baseService = inject(BaseService); - install = 'npm install @tolle_/tolle-ui@18.2.1 date-fns@4.1.0 tailwind-merge@3.4.2 clsx@2.1.1 embla-carousel@8.5.2 @floating-ui/dom@1.7.4 class-variance-authority@0.7.1 remixicon@4.5.0'; + install = 'npm install @tolle_/tolle-ui@18.2.2 date-fns@4.1.0 tailwind-merge@3.4.2 clsx@2.1.1 embla-carousel@8.5.2 @floating-ui/dom@1.7.4 class-variance-authority@0.7.1 remixicon@4.5.0'; } diff --git a/projects/docs/src/app/docs-examples/data-table/column-def.ts b/projects/docs/src/app/docs-examples/data-table/column-def.ts new file mode 100644 index 0000000..98468b6 --- /dev/null +++ b/projects/docs/src/app/docs-examples/data-table/column-def.ts @@ -0,0 +1,8 @@ +import {TableColumn} from '../../../../../tolle/src/lib/data-table.component'; + + +export const columns: TableColumn[] = [ + { key: 'name', label: 'Name', sortable: true }, + { key: 'email', label: 'Email', sortable: true }, + { key: 'actions', label: 'Actions', class: 'text-right w-[100px]' } +]; diff --git a/projects/docs/src/app/docs-examples/data-table/sticky/sticky.component.html b/projects/docs/src/app/docs-examples/data-table/sticky/sticky.component.html new file mode 100644 index 0000000..62e9225 --- /dev/null +++ b/projects/docs/src/app/docs-examples/data-table/sticky/sticky.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/projects/docs/src/app/docs-examples/data-table/sticky/sticky.component.ts b/projects/docs/src/app/docs-examples/data-table/sticky/sticky.component.ts new file mode 100644 index 0000000..3fd7471 --- /dev/null +++ b/projects/docs/src/app/docs-examples/data-table/sticky/sticky.component.ts @@ -0,0 +1,42 @@ +import { Component } from '@angular/core'; +import { DataTableComponent, TableColumn } from '../../../../../../tolle/src/lib/data-table.component'; + +@Component({ + selector: 'app-sticky', + standalone: true, + imports: [ + DataTableComponent + ], + templateUrl: './sticky.component.html' +}) +export class StickyComponent { + columns: TableColumn[] = [ + { key: 'name', label: 'Name', sortable: true }, + { key: 'email', label: 'Email', sortable: true }, + { key: 'role', label: 'Role', sortable: true }, + { key: 'status', label: 'Status' } + ]; + + users = [ + { id: 1, name: 'Alex Rivera', email: 'alex@example.com', role: 'Admin', status: 'Active' }, + { id: 2, name: 'Jordan Smith', email: 'jordan@example.com', role: 'User', status: 'Inactive' }, + { id: 3, name: 'Taylor Johnson', email: 'taylor.johnson@example.com', role: 'Editor', status: 'Active' }, + { id: 4, name: 'Morgan Lee', email: 'morgan.lee@example.com', role: 'User', status: 'Active' }, + { id: 5, name: 'Casey Brown', email: 'casey.brown@example.com', role: 'User', status: 'Pending' }, + { id: 6, name: 'Riley Anderson', email: 'riley.anderson@example.com', role: 'Admin', status: 'Active' }, + { id: 7, name: 'Jamie Clark', email: 'jamie.clark@example.com', role: 'User', status: 'Active' }, + { id: 8, name: 'Avery Martinez', email: 'avery.martinez@example.com', role: 'User', status: 'Inactive' }, + { id: 9, name: 'Quinn Walker', email: 'quinn.walker@example.com', role: 'Editor', status: 'Active' }, + { id: 10, name: 'Drew Thompson', email: 'drew.thompson@example.com', role: 'User', status: 'Suspended' }, + { id: 11, name: 'Parker Lewis', email: 'parker.lewis@example.com', role: 'User', status: 'Active' }, + { id: 12, name: 'Rowan Harris', email: 'rowan.harris@example.com', role: 'Admin', status: 'Active' }, + { id: 13, name: 'Logan White', email: 'logan.white@example.com', role: 'User', status: 'Pending' }, + { id: 14, name: 'Skyler Hall', email: 'skyler.hall@example.com', role: 'Editor', status: 'Active' }, + { id: 15, name: 'Reese Young', email: 'reese.young@example.com', role: 'User', status: 'Active' }, + { id: 16, name: 'Cameron King', email: 'cameron.king@example.com', role: 'User', status: 'Inactive' }, + { id: 17, name: 'Emerson Wright', email: 'emerson.wright@example.com', role: 'Admin', status: 'Active' }, + { id: 18, name: 'Harper Lopez', email: 'harper.lopez@example.com', role: 'User', status: 'Active' }, + { id: 19, name: 'Finley Scott', email: 'finley.scott@example.com', role: 'Editor', status: 'Active' }, + { id: 20, name: 'Dakota Green', email: 'dakota.green@example.com', role: 'User', status: 'Pending' } + ]; +} diff --git a/projects/docs/src/app/layout/doc-layout/doc-layout.component.html b/projects/docs/src/app/layout/doc-layout/doc-layout.component.html index d778715..cceed61 100644 --- a/projects/docs/src/app/layout/doc-layout/doc-layout.component.html +++ b/projects/docs/src/app/layout/doc-layout/doc-layout.component.html @@ -6,8 +6,7 @@
    } -
    @@ -59,4 +58,4 @@
    - \ No newline at end of file + diff --git a/projects/docs/src/app/shared/prop-table/prop-table.component.ts b/projects/docs/src/app/shared/prop-table/prop-table.component.ts index b325150..6d82b00 100644 --- a/projects/docs/src/app/shared/prop-table/prop-table.component.ts +++ b/projects/docs/src/app/shared/prop-table/prop-table.component.ts @@ -20,7 +20,7 @@ import { PropEntry } from '../types'; @for (prop of props; track prop.name) { - + {{ 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); +}