NGX CSS Framework An angular abstraction for Squid CSS
See an exemple of all components here
- 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
]
// ...
})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"
}
// ...
}Service for programmatically opening Modal and Overlay dialogs. Fully testable, Observable-based, with proper memory management.
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!');
}
});
}
}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
}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;
}@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>// 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!');
}
});openSidePanel() {
this.modalService.openOverlay({
direction: 'right',
width: '500px',
body: FilterPanelComponent,
data: { filters: this.currentFilters }
}).subscribe(result => {
if (result) {
this.applyFilters(result);
}
});
}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
});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);Service for displaying toast notifications. Observable-based, fully testable, with proper memory management and data-test attributes.
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.');
}
}// 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.');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
}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 bottomthis.toastService.success('Item deleted', {
duration: 8000,
action: {
label: 'Undo',
callback: () => this.undoDelete()
}
});const ref = this.toastService.warning('Processing...', {
duration: 0 // Never auto-dismiss
});
// Later, dismiss programmatically
ref.dismiss();this.toastService.info('Click me!', {
duration: 0,
dismissOnClick: true
}).afterDismissed().subscribe(reason => {
// reason: 'timeout' | 'action' | 'manual' | 'swipe'
console.log('Toast dismissed by:', reason);
});// Top banner (like success notification)
this.toastService.success('Campaign created successfully!', {
position: 'top-full'
});
// Bottom banner
this.toastService.error('Connection lost', {
position: 'bottom-full'
});@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>// Dismiss all active toasts
this.toastService.dismissAll();
// Get count of active toasts
console.log(this.toastService.getActiveCount());it('should show success toast', () => {
const toastSpy = spyOn(toastService, 'success').and.callThrough();
component.save();
expect(toastSpy).toHaveBeenCalledWith('Data saved!');
});// 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();-
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.
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
Just draft a new release here on Github and an actions will starts
**Important to use the same tag as package.json
See Docs here