NGX CSS - Library
About
NGX CSS Framework An angular abstraction for Squid CSS
Table of contents
Exemple
See an exemple of all components here
Usage
Install
- Install
npm install @squidit/css @squidit/ngx-css --save
- Add
cssandtoast jsfiles to yourangular.json
{
// ...,
"assets": [
// This object inside assets Array
{
"glob": "**/*",
"input": "./node_modules/@squidit/css/dist/fonts",
"output": "./assets/fonts" // Output fonts
},
"src/assets" // Default assets
],
"styles": [
"src/styles.scss"
],
"scripts": [
"node_modules/@squidit/css/src/js/components/toast.js" // JS includes (legacy)
]
// ...
}- Add to your
style.scssmain file
$fontsFolderPath: '/assets/fonts'; // Overwrite default font path @import '@squidit/css/src/scss/squid.scss'; // Import all Framework Styles
- Import
SquidCSSModulein your*.module.ts
import { SquidCSSModule } from '@squidit/ngx-css' @NgModule({ // ... imports: [ // ... NgxSquidModule ] // ... })
Use form erros variables
To use the errors handled in form components, you need to follow the steps below
-
Install ngx-translate and follow the initial Setup
-
On you
.jsonfiles from each language follow the same structure (need one for each supported language of your application):
{
// ...
"forms": {
"search": "Search",
"searchSelectEmpty": "There are no options to select",
"fileSize": "File too large",
"required": "Required field",
"minimumRequired": "The minimum number of selected tags must be greater than or equal to {{ minTags }}",
"email": "Invalid email",
"url": "Invalid URL. Attention: URL must start with https://",
"date": "Invalid Date",
"phone": "Invalid phone number",
"minValueAllowed": "Min value allowed is: {{ min }}",
"maxValueAllowed": "Max value allowed is: {{ max }}",
"rangeDate:": "Date outside valid range"
}
// ...
}SqModalService
Service for programmatically opening Modal and Overlay dialogs. Fully testable, Observable-based, with proper memory management.
Basic Usage
import { Component, inject } from '@angular/core'; import { SqModalService } from '@squidit/ngx-css'; @Component({...}) export class MyComponent { private modalService = inject(SqModalService); openSimpleModal() { this.modalService.openModal({ header: 'Confirm Action', body: myBodyTemplate, // TemplateRef or Component size: 'md' }).subscribe(result => { if (result) { console.log('User confirmed!'); } }); } }
Modal Configuration
interface SqModalConfig<T = any> { id?: string; // Unique ID (auto-generated if not provided) header?: string | TemplateRef; // Header content (string = title) body?: TemplateRef | Type<any>; // Body content (template or component) footer?: TemplateRef; // Footer content (template) data?: T; // Data to pass to content size?: 'sm' | 'md' | 'lg' | 'xl'; // Modal size (default: 'md') backdrop?: 'static' | true; // Backdrop behavior (default: 'static') showCloseButton?: boolean; // Show X button (default: true) customClass?: string; // Additional CSS classes cancelText?: string; // Cancel button text (default: 'Cancelar') confirmText?: string; // Confirm button text (default: 'Confirmar') dataTest?: string; // data-test attribute for testing }
Overlay Configuration
interface SqOverlayConfig<T = any> extends SqDialogConfig<T> { direction?: 'left' | 'right' | 'top' | 'bottom'; // Slide direction (default: 'right') width?: string; // Custom width for left/right overlays height?: string; // Custom height for top/bottom overlays borderless?: boolean; }
Opening with Templates
@ViewChild('headerTemplate') headerTemplate!: TemplateRef<any>; @ViewChild('bodyTemplate') bodyTemplate!: TemplateRef<any>; @ViewChild('footerTemplate') footerTemplate!: TemplateRef<any>; openModal() { this.modalService.openModal({ header: this.headerTemplate, body: this.bodyTemplate, footer: this.footerTemplate, data: { items: this.items } }); }
<ng-template #bodyTemplate let-modal let-data="data"> <ul> @for (item of data.items; track item.id) { <li>{{ item.name }}</li> } </ul> </ng-template> <ng-template #footerTemplate let-modal let-data="data"> <button (click)="modal.close()">Cancel</button> <button (click)="modal.close(data)">Save</button> </ng-template>
Opening with Components
// Content component @Component({ selector: 'app-my-content', template: ` <h3>{{ title }}</h3> <button (click)="dialogRef?.close({ saved: true })">Save</button> ` }) export class MyContentComponent { @Input() title!: string; // Receives from data @Input() dialogRef?: SqDialogRef; // Auto-injected // Optional: provide header/footer templates @ViewChild('headerTemplate') headerTemplate?: TemplateRef<any>; @ViewChild('footerTemplate') footerTemplate?: TemplateRef<any>; } // Opening this.modalService.openModal({ body: MyContentComponent, data: { title: 'My Modal Title' } }).subscribe(result => { if (result?.saved) { console.log('Data was saved!'); } });
Opening an Overlay
openSidePanel() { this.modalService.openOverlay({ direction: 'right', width: '500px', body: FilterPanelComponent, data: { filters: this.currentFilters } }).subscribe(result => { if (result) { this.applyFilters(result); } }); }
Confirmation Before Close
Use the confirmBeforeClose operator to prompt user before closing without saving:
import { confirmBeforeClose } from '@squidit/ngx-css'; this.modalService.openModal({ body: MyFormComponent, data: { form: this.form } }).pipe( confirmBeforeClose(() => { // Return Observable<boolean>, Promise<boolean>, or boolean return confirm('You have unsaved changes. Discard?'); }) ).subscribe(result => { // Only reaches here if user confirms or saves });
Programmatic Control
const ref = this.modalService.openModal({...}); // Close with result ref.close({ success: true }); // Close without result (cancel) ref.close(); // Update data in content component ref.updateData({ loading: true }); // Close all modals this.modalService.closeAll(); // Check open count console.log(this.modalService.openCount);
SqToastService
Service for displaying toast notifications. Observable-based, fully testable, with proper memory management and data-test attributes.
Basic Usage
import { Component, inject } from '@angular/core'; import { SqToastService } from '@squidit/ngx-css'; @Component({...}) export class MyComponent { private toastService = inject(SqToastService); showSuccess() { this.toastService.success('Operation completed successfully!'); } showError() { this.toastService.error('Something went wrong.'); } }
Toast Types
// Success (green) this.toastService.success('Item saved!'); // Error (red) this.toastService.error('Failed to save item.'); // Warning (yellow) this.toastService.warning('This action cannot be undone.'); // Info (blue) this.toastService.info('New updates available.'); // Default (gray) this.toastService.default('Notification message.');
Toast Configuration
interface SqToastConfig { type?: 'success' | 'error' | 'warning' | 'info' | 'default'; duration?: number; // ms, 0 = persistent (default: 5000) position?: SqToastPosition; // (default: 'top-right') closeable?: boolean; // Show close button (default: true) showIcon?: boolean; // Show type icon (default: false) icon?: string; // Custom icon class (FontAwesome) dismissOnClick?: boolean; // Click toast to dismiss (default: false) pauseOnHover?: boolean; // Pause timer on hover (default: true) action?: { // Action button label: string; callback?: () => void; }; customClass?: string; dataTest?: string; // data-test attribute data?: any; // Data for template messages }
Available Positions
type SqToastPosition = | 'top-right' // Default | 'top-left' | 'top-center' | 'top-full' // Full-width banner at top | 'bottom-right' | 'bottom-left' | 'bottom-center' | 'bottom-full'; // Full-width banner at bottom
With Action Button
this.toastService.success('Item deleted', { duration: 8000, action: { label: 'Undo', callback: () => this.undoDelete() } });
Persistent Toast (Manual Dismiss)
const ref = this.toastService.warning('Processing...', { duration: 0 // Never auto-dismiss }); // Later, dismiss programmatically ref.dismiss();
With Observable Lifecycle
this.toastService.info('Click me!', { duration: 0, dismissOnClick: true }).afterDismissed().subscribe(reason => { // reason: 'timeout' | 'action' | 'manual' | 'swipe' console.log('Toast dismissed by:', reason); });
Full-Width Banner
// Top banner (like success notification) this.toastService.success('Campaign created successfully!', { position: 'top-full' }); // Bottom banner this.toastService.error('Connection lost', { position: 'bottom-full' });
With Custom Template
@ViewChild('customToast') customToast!: TemplateRef<any>; showCustom() { this.toastService.success(this.customToast, { data: { userName: 'John', count: 5 } }); }
<ng-template #customToast let-ref let-data="data"> <strong>{{ data.userName }}</strong> completed {{ data.count }} tasks! <button (click)="ref.dismiss()">OK</button> </ng-template>
Global Operations
// Dismiss all active toasts this.toastService.dismissAll(); // Get count of active toasts console.log(this.toastService.getActiveCount());
Unit Testing
it('should show success toast', () => { const toastSpy = spyOn(toastService, 'success').and.callThrough(); component.save(); expect(toastSpy).toHaveBeenCalledWith('Data saved!'); });
E2E Testing (Cypress)
// Find toast by data-test cy.get('[data-test="sq-toast-container"]').should('exist'); cy.get('[data-test="sq-toast-1"]').should('contain', 'Success!'); cy.get('[data-test="sq-toast-1-close"]').click();
Development
-
Install npm dependences
npm install -
Run
npm startto watch angular library (srcdirectory) -
In another window run
start:application
This launches an angular pattern that is contained in the application folder. Just use the components inside it, and every change in the files in the src folder will be automatically reflected in the application.
Write Documentation
We use compodoc to write docs with jsDocs
Run start:docs and the compodoc will serve the docs. For each change it is necessary to run the command again
Deploy to NPM
Just draft a new release here on Github and an actions will starts
**Important to use the same tag as package.json
Documentation
See Docs here