new changes implemented ui

This commit is contained in:
Anas Qureshi
2026-07-22 17:06:24 +05:30
parent 7c4e34e478
commit 7f8ac71558
18 changed files with 24348 additions and 1286 deletions
@@ -6,7 +6,7 @@
[pageIndex]="queryState.pageIndex()"
[pageSize]="queryState.pageSize()"
[pageSizeOptions]="[5, 10, 20, 50]"
tableTitle="Currencies"
tableTitle="Currency Management"
buttonTitle="Add"
[showSearch]="true"
[showAddButton]="true"
@@ -49,6 +49,7 @@
/>
<modal
class="modern-modal"
[open]="showCurrencyModal()"
[title]="currencyModalTitle()"
size="md"
@@ -59,105 +60,287 @@
(closed)="closeCurrencyModal()"
(submitted)="saveCurrency()"
>
<form [formGroup]="currencyForm" (ngSubmit)="saveCurrency()" autocomplete="off">
<div class="grid grid-cols-12 gap-x-5 gap-y-5">
<div class="col-span-12 md:col-span-6">
<app-form-input
<form [formGroup]="currencyForm" (ngSubmit)="saveCurrency()" autocomplete="off">
<div class="grid grid-cols-12 gap-x-5 gap-y-5">
<!-- Currency Name -->
<div class="col-span-12 md:col-span-4">
<div class="custom-floating-field"
[class.has-error]="currencyForm.get('name')?.invalid && currencySubmitAttempted()"
[class.is-empty]="isEmptyField('name')">
<input
id="currency-name"
formControlName="name"
inputId="currency-name"
label="Currency Name"
placeholder="Name"
type="text"
autocomplete="off"
[required]="true"
[maxLength]="100"
[validationMessages]="{
required: 'Currency Name is required.',
maxlength: 'Currency Name cannot exceed 100 characters.'
}"
[submitAttempted]="currencySubmitAttempted()"
maxlength="100"
required
placeholder=" "
class="custom-floating-input"
[class.is-invalid]="currencyForm.get('name')?.invalid && currencySubmitAttempted()"
[attr.aria-invalid]="currencyForm.get('name')?.invalid && currencySubmitAttempted() ? true : null"
/>
</div>
<div class="col-span-12 md:col-span-6">
<app-form-input
formControlName="code"
inputId="currency-code"
label="Currency Code"
placeholder="e.g.: AED"
autocomplete="off"
[required]="true"
[minLength]="3"
[maxLength]="3"
[validationMessages]="{
required: 'Currency Code is required.',
minlength: 'Currency Code must contain exactly 3 letters.',
maxlength: 'Currency Code must contain exactly 3 letters.',
pattern: 'Currency Code can contain letters only.'
}"
[submitAttempted]="currencySubmitAttempted()"
/>
</div>
<label for="currency-name" class="custom-floating-label">
Currency Name
<span class="ms-0.5 text-danger" aria-hidden="true">*</span>
<span class="sr-only">Required</span>
</label>
<div class="col-span-12 md:col-span-6">
<app-form-input
formControlName="symbol"
inputId="currency-symbol"
label="Currency Symbol"
placeholder="e.g.: د.إ"
autocomplete="off"
[required]="true"
[maxLength]="8"
[validationMessages]="{
required: 'Currency Symbol is required.',
maxlength: 'Currency Symbol cannot exceed 8 characters.'
}"
[submitAttempted]="currencySubmitAttempted()"
/>
</div>
@if (getCurrencyFieldError('name', {
required: 'Currency Name is required.',
maxlength: 'Currency Name cannot exceed 100 characters.'
})) {
<span class="custom-floating-error">
{{ getCurrencyFieldError('name', {
required: 'Currency Name is required.',
maxlength: 'Currency Name cannot exceed 100 characters.'
}) }}
</span>
}
<div class="col-span-12 md:col-span-6">
<app-form-input
formControlName="numericCode"
inputId="currency-numeric-code"
label="Numeric Code"
type="number"
inputMode="numeric"
placeholder="e.g.: 784"
autocomplete="off"
[required]="false"
[min]="1"
[max]="999"
[step]="1"
[validationMessages]="{
required: 'Numeric Code is required.',
min: 'Numeric Code must be at least 1.',
max: 'Numeric Code cannot exceed 999.'
}"
[submitAttempted]="currencySubmitAttempted()"
/>
</div>
<div class="col-span-12 md:col-span-6">
<app-form-input
formControlName="decimalDigits"
inputId="currency-decimal-digits"
label="Decimal Digits"
type="number"
inputMode="numeric"
placeholder="e.g.: 2"
autocomplete="off"
[required]="true"
[min]="0"
[max]="4"
[step]="1"
[validationMessages]="{
required: 'Decimal Digits is required.',
min: 'Decimal Digits cannot be less than 0.',
max: 'Decimal Digits cannot exceed 4.'
}"
[submitAttempted]="currencySubmitAttempted()"
/>
</div>
</div>
</form>
<!-- Currency Code -->
<div class="col-span-12 md:col-span-4">
<div class="custom-floating-field"
[class.has-error]="currencyForm.get('code')?.invalid && currencySubmitAttempted()"
[class.is-empty]="isEmptyField('code')">
<input
id="currency-code"
formControlName="code"
type="text"
autocomplete="off"
minlength="3"
maxlength="3"
pattern="[A-Za-z]{3}"
required
placeholder=" "
class="custom-floating-input"
[class.is-invalid]="currencyForm.get('code')?.invalid && currencySubmitAttempted()"
[attr.aria-invalid]="currencyForm.get('code')?.invalid && currencySubmitAttempted() ? true : null"
/>
<label for="currency-code" class="custom-floating-label">
Currency Code
<span class="ms-0.5 text-danger" aria-hidden="true">*</span>
<span class="sr-only">Required</span>
</label>
@if (getCurrencyFieldError('code', {
required: 'Currency Code is required.',
minlength: 'Currency Code must contain exactly 3 letters.',
maxlength: 'Currency Code must contain exactly 3 letters.',
pattern: 'Currency Code can contain letters only.'
})) {
<span class="custom-floating-error">
{{ getCurrencyFieldError('code', {
required: 'Currency Code is required.',
minlength: 'Currency Code must contain exactly 3 letters.',
maxlength: 'Currency Code must contain exactly 3 letters.',
pattern: 'Currency Code can contain letters only.'
}) }}
</span>
}
</div>
</div>
<!-- Currency Symbol -->
<div class="col-span-12 md:col-span-4">
<div class="custom-floating-field"
[class.has-error]="currencyForm.get('symbol')?.invalid && currencySubmitAttempted()"
[class.is-empty]="isEmptyField('symbol')">
<input
id="currency-symbol"
formControlName="symbol"
type="text"
autocomplete="off"
maxlength="8"
required
placeholder=" "
class="custom-floating-input"
[class.is-invalid]="currencyForm.get('symbol')?.invalid && currencySubmitAttempted()"
[attr.aria-invalid]="currencyForm.get('symbol')?.invalid && currencySubmitAttempted() ? true : null"
/>
<label for="currency-symbol" class="custom-floating-label">
Currency Symbol
<span class="ms-0.5 text-danger" aria-hidden="true">*</span>
<span class="sr-only">
Required
</span>
</label>
@if (getCurrencyFieldError('symbol', {
required: 'Currency Symbol is required.',
maxlength: 'Currency Symbol cannot exceed 8 characters.'
})) {
<span class="custom-floating-error">
{{ getCurrencyFieldError('symbol', {
required: 'Currency Symbol is required.',
maxlength: 'Currency Symbol cannot exceed 8 characters.'
}) }}
</span>
}
</div>
</div>
<!-- Numeric Code -->
<div class="col-span-12 md:col-span-4">
<div class="custom-floating-field"
[class.has-error]="currencyForm.get('numericCode')?.invalid && currencySubmitAttempted()"
[class.is-empty]="isEmptyField('numericCode')">
<input
id="currency-numeric-code"
formControlName="numericCode"
type="number"
inputmode="numeric"
autocomplete="off"
min="1"
max="999"
step="1"
placeholder=" "
class="custom-floating-input"
[class.is-invalid]="currencyForm.get('numericCode')?.invalid && currencySubmitAttempted()"
[attr.aria-invalid]="currencyForm.get('numericCode')?.invalid && currencySubmitAttempted() ? true : null"
/>
<label for="currency-numeric-code" class="custom-floating-label">
Numeric Code
</label>
@if (getCurrencyFieldError('numericCode', {
required:'Numeric Code is required.',
min:'Numeric Code must be at least 1.',
max:'Numeric Code cannot exceed 999.'
})) {
<span class="custom-floating-error">
{{ getCurrencyFieldError('numericCode', {
required:'Numeric Code is required.',
min:'Numeric Code must be at least 1.',
max:'Numeric Code cannot exceed 999.'
}) }}
</span>
}
</div>
</div>
<!-- Decimal Digits -->
<div class="col-span-12 md:col-span-4">
<div class="custom-floating-field"
[class.has-error]="currencyForm.get('decimalDigits')?.invalid && currencySubmitAttempted()"
[class.is-empty]="isEmptyField('decimalDigits')">
<input
id="currency-decimal-digits"
formControlName="decimalDigits"
type="number"
inputmode="numeric"
autocomplete="off"
min="0"
max="4"
step="1"
required
placeholder=" "
class="custom-floating-input"
[class.is-invalid]="currencyForm.get('decimalDigits')?.invalid && currencySubmitAttempted()"
[attr.aria-invalid]="currencyForm.get('decimalDigits')?.invalid && currencySubmitAttempted() ? true : null"
/>
<label for="currency-decimal-digits" class="custom-floating-label">
Decimal Digits
<span class="ms-0.5 text-danger" aria-hidden="true">*</span>
<span class="sr-only">
Required
</span>
</label>
@if (getCurrencyFieldError('decimalDigits', {
required:'Decimal Digits is required.',
min:'Decimal Digits cannot be less than 0.',
max:'Decimal Digits cannot exceed 4.'
})) {
<span class="custom-floating-error">
{{ getCurrencyFieldError('decimalDigits', {
required:'Decimal Digits is required.',
min:'Decimal Digits cannot be less than 0.',
max:'Decimal Digits cannot exceed 4.'
}) }}
</span>
}
</div>
</div>
</div>
</form>
</modal>
@@ -0,0 +1,117 @@
:host-context(.dark) .custom-floating-input {
background-color: var(--color-bodybg) !important;
border-color: color-mix(in srgb, #fff 10%, transparent) !important;
}
:host-context(.dark) .custom-floating-label {
background-color: var(--color-bodybg) !important;
}
:host-context(.dark) .custom-floating-input:focus {
border-color: var(--color-blue-400) !important;
box-shadow: none !important;
}
:host-context(.dark) .custom-floating-input:not(:placeholder-shown) {
border-color: var(--color-primary) !important;
box-shadow: none !important;
}
.custom-floating-field {
position: relative;
display: block;
}
.custom-floating-input {
display: block;
width: 100%;
min-height: 2.5rem;
padding: 0.75rem 0.75rem 0.5rem;
border: 1px solid var(--color-inputborder);
border-radius: 0.5rem;
background-color: var(--color-white);
color: var(--color-defaulttextcolor);
font-size: 0.8125rem;
line-height: 1.25;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.custom-floating-input:focus {
border-color: var(--color-primary) !important;
outline: none;
box-shadow: none;
}
.custom-floating-input:not(:placeholder-shown) {
border-color: var(--color-primary);
box-shadow: none;
}
.custom-floating-input::placeholder {
color: transparent;
}
.custom-floating-label {
position: absolute;
inset-inline-start: 0.75rem;
top: 50%;
transform: translateY(-50%);
transform-origin: left top;
z-index: 10;
padding-inline: 0.25rem;
background-color: var(--color-white);
color: #8c9097;
font-size: 0.75rem;
line-height: 1;
pointer-events: none;
transition: all 0.2s ease-out;
max-width: calc(100% - 1.75rem);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.custom-floating-input:focus~.custom-floating-label,
.custom-floating-input:not(:placeholder-shown)~.custom-floating-label {
top: 0;
transform: translateY(-50%) scale(0.9);
color: var(--color-primary);
}
/* Center label on invalid empty fields (validation state) */
.custom-floating-field.has-error.is-empty .custom-floating-input:not(:focus)~.custom-floating-label {
top: 35% !important;
transform: translateY(-50%) !important;
}
.custom-floating-field.has-error .custom-floating-input,
.custom-floating-field.has-error .custom-floating-input:focus,
.custom-floating-field.has-error .custom-floating-input:not(:placeholder-shown) {
border-color: var(--color-danger) !important;
}
.custom-floating-field.has-error .custom-floating-label,
.custom-floating-field.has-error .custom-floating-input:focus~.custom-floating-label,
.custom-floating-field.has-error .custom-floating-input:not(:placeholder-shown)~.custom-floating-label,
.custom-floating-field.has-error.is-empty .custom-floating-input~.custom-floating-label {
color: var(--color-danger) !important;
}
.custom-floating-error {
display: block;
margin-top: 0.25rem;
color: var(--color-danger);
font-size: 0.75rem;
line-height: 1.25;
}
/* Remove number spinner buttons for cleaner look */
.custom-floating-input[type='number']::-webkit-inner-spin-button,
.custom-floating-input[type='number']::-webkit-outer-spin-button {
appearance: none;
margin: 0;
}
.custom-floating-input[type='number'] {
-moz-appearance: textfield;
}
@@ -27,7 +27,6 @@ import {
} from '../../../../../shared/components/data-table/data-table.types';
import { DataTable } from '../../../../../shared/components/data-table/data-table';
import { DataTableCellDirective } from '../../../../../shared/directives/data-table-cell.directive';
import { FormInput } from '../../../../../shared/components/form/form-input/form-input';
import { Modal } from '../../../../../shared/components/modal/modal';
import { ConfirmDialog } from '../../../../../shared/components/confirm-dialog/confirm-dialog';
@@ -48,7 +47,7 @@ interface CurrencyTableRow extends DataTableRecord {
@Component({
selector: 'currency-list',
standalone: true,
imports: [DataTable, DataTableCellDirective, Modal, ReactiveFormsModule, FormInput, ConfirmDialog],
imports: [DataTable, DataTableCellDirective, Modal, ReactiveFormsModule, ConfirmDialog],
templateUrl: './currency-list.html',
styleUrl: './currency-list.scss',
})
@@ -390,7 +389,7 @@ export class CurrencyList {
name: currencyDetails.name ?? '',
code: currencyDetails.code ?? '',
symbol: currencyDetails.symbol ?? '',
numericCode: currencyDetails.numericCode ?? 0,
numericCode: currencyDetails.numericCode ?? '0',
decimalDigits: currencyDetails.decimalDigits ?? 2
});
this.resetCurrencyFormState();
@@ -482,7 +481,7 @@ export class CurrencyList {
queueMicrotask(() => {
const firstInvalidControl =
this.elementRef.nativeElement.querySelector<HTMLElement>(
'modal .form-control.is-invalid, modal [aria-invalid="true"]'
'modal .custom-floating-input.is-invalid, modal [aria-invalid="true"]'
);
firstInvalidControl?.focus();
@@ -515,6 +514,31 @@ export class CurrencyList {
? `https://flagcdn.com/24x18/${code}.png`
: '';
}
isEmptyField(fieldName: string): boolean {
const value = this.currencyForm.get(fieldName)?.value;
return value === '' || value === null || value === undefined;
}
getCurrencyFieldError(fieldName: string, messages: Record<string, string>): string | null {
const control = this.currencyForm.get(fieldName);
if (!control || control.valid || !this.currencySubmitAttempted()) {
return null;
}
const errors = control.errors ?? {};
for (const key of Object.keys(errors)) {
const message = messages[key];
if (message) {
return message;
}
}
return null;
}
onFlagError(event: Event): void {
const image = event.target as HTMLImageElement;
image.style.display = 'none';
@@ -1,16 +1,16 @@
<!-- Start::row-1 -->
<div class="grid grid-cols-12">
<div class="grid grid-cols-12 data-table-modern">
<div class="xl:col-span-12 col-span-12">
<div class="box overflow-visible">
<div class="box-header justify-between">
<div class="box-title">
<div class="box-header justify-between items-center gap-3">
<h5 class="box-title">
{{ tableTitle () }}
</div>
<div class="flex flex-wrap gap-2">
</h5>
<div class="flex flex-wrap items-center gap-3">
@if(showAddButton()){
<div>
<app-button action="add" [label]="buttonTitle()" size="sm" iconClass="!text-[1rem]"
(buttonClicked)="onAddClick($event)" />
className="!rounded-full shadow-sm" (buttonClicked)="onAddClick($event)" />
</div>
}
@@ -20,17 +20,18 @@
</div>
} -->
@if (showSearch()) {
<div>
<input #searchInput class="form-control form-control-sm" type="text" [placeholder]="searchPlaceholder()"
aria-label="table search" (input)="onSearch(searchInput.value)">
<div class="search-wrapper">
<i class="ri-search-line search-icon"></i>
<input #searchInput class="form-control form-control-sm brdr-clr" type="text"
[placeholder]="searchPlaceholder()" aria-label="table search" (input)="onSearch(searchInput.value)">
</div>
}
</div>
</div>
<div class="box-body !p-0">
<div class="table-responsive overflow-x-auto overflow-y-visible">
<div class="box-body !p-0 modern-table-body">
<div class="table-responsive overflow-x-auto overflow-y-visible modern-table-wrapper">
<table [class]="tableClass()">
<thead>
<tr [class]="trHeadClass()">
@@ -67,7 +68,8 @@
<tr class="border-b border-defaultborder bg-light/30 dark:bg-black/10">
<td [attr.colspan]="totalVisibleColumns()" class="p-0">
<div class="flex min-h-[140px] flex-col items-center justify-center px-4 py-8 text-center">
<div class="mb-3 inline-flex h-11 w-11 items-center justify-center rounded-full bg-primary/10 text-primary dark:bg-primary/15">
<div
class="mb-3 inline-flex h-11 w-11 items-center justify-center rounded-full bg-primary/10 text-primary dark:bg-primary/15">
<i class="ti ti-database-off text-[1.25rem]" aria-hidden="true"></i>
</div>
@@ -109,10 +111,10 @@
<button type="button" cdkOverlayOrigin #actionMenuOrigin="cdkOverlayOrigin" data-action-menu-trigger
class="
ti-btn ti-btn-icon ti-btn-sm ti-btn-primary
!m-0 !h-8 !w-8 !p-0
!m-0 !h-6 !w-6 !p-0
" aria-label="Open row actions" aria-haspopup="menu" [attr.aria-controls]="getActionMenuId(row)"
[attr.aria-expanded]="isActionMenuOpen(row)" (click)="toggleActionMenu($event, row)">
<i class="ri-more-2-fill text-lg"></i>
<i class="ri-more-2-fill text-sm"></i>
</button>
<ng-template cdkConnectedOverlay [cdkConnectedOverlayOrigin]="actionMenuOrigin"
@@ -167,7 +169,7 @@
</div>
</div>
@if (showPaginator()) {
<div class="box-footer border-t-0">
<div class="box-footer border-t-0 modern-table-footer">
<div class="flex items-center justify-between flex-wrap gap-3">
<!-- Record details and page-size selection -->
@@ -1,59 +1,238 @@
// :host {
// display: block;
// }
// :host .table tbody td,
// :host .table tbody th {
// line-height: 1.25;
// vertical-align: middle;
// padding: 0.1rem !important;
// }
// .table-responsive {
// width: 100%;
// overflow-x: auto;
// }
// .table th,
// .table td {
// vertical-align: middle;
// }
:host {
display: block;
}
:host .table {
thead {
background-color: color-mix(in srgb, var(--color-primary) 8%, transparent);
}
thead th {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
line-height: 1.2;
vertical-align: middle;
color: var(--color-defaulttextcolor);
font-weight: 600;
}
tbody td,
tbody th {
padding-top: 0.2rem;
padding-bottom: 0.2rem;
line-height: 1.2;
vertical-align: middle;
}
tbody tr {
line-height: 1.1;
}
/* Modern card wrapper */
.data-table-modern .box {
border-radius: 1rem;
border: 1px solid var(--color-defaultborder);
background-color: var(--color-white);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.04);
overflow: hidden;
}
[data-theme-mode='dark'] :host .table thead {
background-color: color-mix(in srgb, var(--color-primary) 14%, transparent);
:host-context(.dark) .data-table-modern .box {
border-color: color-mix(in srgb, #fff 10%, transparent);
background-color: var(--color-bodybg);
}
.data-table-modern .box-header {
// padding: 1rem 1.25rem;
padding: 7px 20px;
border-bottom: 1px solid var(--color-defaultborder);
}
:host-context(.dark) .data-table-modern .box-header {
border-color: color-mix(in srgb, #fff 10%, transparent);
}
.data-table-modern .box-title {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1rem;
font-weight: 600;
color: var(--color-defaulttextcolor);
}
/* Search input */
.search-wrapper {
position: relative;
display: inline-flex;
align-items: center;
}
.brdr-clr {
border: 1.5px solid #7c3deb !important;
}
.search-icon {
position: absolute;
left: 0.75rem;
top: 50%;
transform: translateY(-50%);
color: var(--color-textmuted, #8c9097);
font-size: 0.8125rem;
pointer-events: none;
}
.data-table-modern .search-wrapper .form-control-sm {
min-width: 180px;
padding-left: 2.25rem !important;
border-radius: 9999px;
border-color: var(--color-defaultborder);
background-color: var(--color-white);
font-size: 0.8125rem;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.data-table-modern .search-wrapper .form-control-sm:focus {
border-color: var(--color-primary);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 15%, transparent);
outline: none;
}
:host-context(.dark) .data-table-modern .search-wrapper .form-control-sm {
background-color: var(--color-bodybg);
border-color: color-mix(in srgb, #fff 10%, transparent);
color: var(--color-defaulttextcolor);
}
/* Table body / wrapper */
.modern-table-body {
padding: 0 !important;
}
.modern-table-wrapper {
border-radius: 0 0 0.75rem 0.75rem;
}
/* Table */
.data-table-modern .table {
width: 100%;
margin-bottom: 0;
border-collapse: separate;
border-spacing: 0;
font-size: 0.8125rem;
}
.data-table-modern .table.table-bordered,
.data-table-modern .table.table-bordered thead th,
.data-table-modern .table.table-bordered tbody td {
border: none;
}
.data-table-modern .table thead {
// background: linear-gradient(
// 135deg,
// #020617 0%,
// #172554 40%,
// #4338CA 75%,
// #7C3AED 100%
// );
background: #7C3AED;
color: var(--color-white);
}
.data-table-modern .table thead th {
padding: 0.5rem 0.75rem;
font-weight: 600;
font-size: 0.6875rem;
letter-spacing: 0.04em;
text-transform: uppercase;
white-space: nowrap;
vertical-align: middle;
color: var(--color-white);
background: transparent;
border: none;
}
.data-table-modern .table tbody td {
padding: 0.2rem 0.75rem;
vertical-align: middle;
color: var(--color-defaulttextcolor);
border: none;
font-size: 0.8125rem;
}
.data-table-modern .table tbody tr:hover {
// background-color: var(--color-light);
background-color: #e1d0ff;
}
:host-context(.dark) .data-table-modern .table tbody tr:hover {
background-color: color-mix(in srgb, #fff 6%, transparent);
}
/* Inactive row action buttons border */
.data-table-modern .inactive-actions button {
border: 1.5px solid #7c3deb !important;
}
/* Footer */
.data-table-modern .box-footer {
padding: 0.625rem 1rem;
border-top: 1px solid var(--color-defaultborder);
background-color: color-mix(in srgb, var(--color-primary) 3%, transparent);
}
:host-context(.dark) .data-table-modern .box-footer {
border-color: color-mix(in srgb, #fff 10%, transparent);
background-color: color-mix(in srgb, var(--color-primary) 6%, transparent);
}
/* Pagination */
.data-table-modern .ti-pagination {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0;
margin: 0;
}
.data-table-modern .ti-pagination li.page-item {
display: inline-flex;
border: 1px solid var(--color-primary);
border-radius: 9999px;
}
.data-table-modern .ti-pagination li.page-item.disabled {
opacity: 0.4;
}
.data-table-modern .ti-pagination li .page-link {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 1.5rem;
height: 1.5rem;
padding: 0 0.5rem;
border-radius: 9999px;
border: none;
background-color: transparent;
color: var(--color-defaulttextcolor);
font-size: 0.75rem;
font-weight: 500;
line-height: 1;
transition: all 0.15s ease;
}
.data-table-modern .ti-pagination li .page-link:hover:not(:disabled) {
background-color: color-mix(in srgb, var(--color-primary) 10%, transparent);
color: var(--color-primary);
}
.data-table-modern .ti-pagination li.active .page-link,
.data-table-modern .ti-pagination li .page-link.active {
background-color: var(--color-primary);
color: var(--color-white);
box-shadow: 0 2px 6px color-mix(in srgb, var(--color-primary) 30%, transparent);
}
.data-table-modern .ti-pagination li.disabled .page-link {
cursor: not-allowed;
background-color: transparent;
}
:host-context(.dark) .data-table-modern .ti-pagination li .page-link {
background-color: transparent;
color: color-mix(in srgb, var(--color-defaulttextcolor) 80%, #fff);
}
:host-context(.dark) .data-table-modern .ti-pagination li .page-link:hover:not(:disabled) {
background-color: color-mix(in srgb, var(--color-primary) 15%, transparent);
color: var(--color-primary);
}
:host-context(.dark) .data-table-modern .ti-pagination li.active .page-link {
color: var(--color-white);
background-color: var(--color-primary);
}
/* Responsive scroll */
.table-responsive {
width: 100%;
overflow-x: auto;
@@ -64,3 +243,14 @@
.table td {
vertical-align: middle;
}
/* Remove number spinner buttons for cleaner look (kept from original) */
.data-table-modern .custom-floating-input[type='number']::-webkit-inner-spin-button,
.data-table-modern .custom-floating-input[type='number']::-webkit-outer-spin-button {
appearance: none;
margin: 0;
}
.data-table-modern .custom-floating-input[type='number'] {
-moz-appearance: textfield;
}
@@ -1,5 +1,5 @@
<div [class]="resolvedWrapperClass()">
@if (showLabel()) {
@if (showLabel() && !floating()) {
<label [for]="inputId()" [class]="resolvedLabelClass()">
{{ label() }}
@if (required()) {
@@ -32,7 +32,25 @@
</p>
}
@if (floating()) {
<div class="floating-label" [class.floating-label--float]="hasValue()" [class.floating-label--invalid]="hasVisibleError()">
<ng-content />
<label [for]="inputId()" [class]="resolvedLabelClass()">
{{ label() }}
@if (required()) {
<span [class]="requiredClass()" aria-hidden="true">
*
</span>
<span class="sr-only">
Required
</span>
}
</label>
</div>
} @else {
<ng-content />
}
@if (showHint()) {
<p [id]="hintId()" [class]="hintClass()">
@@ -46,4 +64,4 @@
[submitAttempted]="submitAttempted()" />
}
</div>
</div>
</div>
@@ -0,0 +1,65 @@
:host {
display: block;
}
.floating-label {
position: relative;
display: block;
::ng-deep .form-control {
padding-block: 1.125rem 0.375rem;
min-height: 3.25rem;
border-radius: 0.5rem;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
&::placeholder {
color: transparent;
}
}
::ng-deep .form-control:focus {
border-color: var(--color-primary);
box-shadow: 0 0 0 0.2rem color-mix(in srgb, var(--color-primary) 20%, transparent);
}
> label {
position: absolute;
inset-inline-start: 0.75rem;
top: 50%;
transform: translateY(-50%);
transform-origin: left top;
z-index: 10;
padding-inline: 0.25rem;
background: var(--color-white) !important;
color: #8c9097 !important;
font-size: 0.875rem !important;
line-height: 1;
transition: all 0.2s ease-out;
pointer-events: none;
max-width: calc(100% - 1.75rem);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&:focus-within > label,
&.floating-label--float > label {
top: 0;
transform: translateY(-50%) scale(0.85);
color: var(--color-primary) !important;
}
&.floating-label--invalid > label {
color: var(--color-danger) !important;
}
}
:host-context(.dark) .floating-label > label,
:host-context(.dark) .floating-label:focus-within > label,
:host-context(.dark) .floating-label.floating-label--float > label {
background: var(--color-bodybg) !important;
}
:host-context(.dark) .floating-label ::ng-deep .form-control:focus {
box-shadow: 0 0 0 0.2rem color-mix(in srgb, var(--color-primary) 25%, transparent);
}
@@ -21,6 +21,7 @@ export type FormLabelPosition = 'top' | 'left' | 'hidden';
standalone: true,
imports: [FormValidationMessage, TooltipDirective],
templateUrl: './form-field.html',
styleUrl: './form-field.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FormField {
@@ -51,6 +52,8 @@ export class FormField {
readonly hideLabel = input(false);
readonly floating = input(false);
readonly hideValidation = input(false);
readonly showValidationWhenDirty = input(false);
@@ -135,6 +138,20 @@ export class FormField {
);
});
readonly hasValue = computed(() => {
this.controlStateVersion();
const control = this.control();
if (!control) {
return false;
}
const value = control.value;
return value !== null && value !== undefined && value !== '';
});
readonly resolvedWrapperClass = computed(() => {
return [
this.labelPosition() === 'left'
@@ -1,4 +1,4 @@
<app-form-field
<!-- <app-form-field
[label]="label()"
[inputId]="inputId()"
[control]="control()"
@@ -11,6 +11,7 @@
[showValidationWhenDirty]="showValidationWhenDirty()"
[submitAttempted]="submitAttempted()"
[validationMessages]="validationMessages()"
[floating]="floating()"
[wrapperClass]="wrapperClass()"
[labelClass]="labelClass()"
[contentClass]="fieldContentClass()"
@@ -114,4 +115,144 @@
</span>
}
</div>
</app-form-field>
</app-form-field> -->
<app-form-field
[label]="label()"
[inputId]="inputId()"
[control]="control()"
[required]="required()"
[disabled]="isDisabled()"
[description]="description()"
[hint]="hint()"
[help]="help()"
[hideValidation]="hideValidation()"
[showValidationWhenDirty]="showValidationWhenDirty()"
[submitAttempted]="submitAttempted()"
[validationMessages]="validationMessages()"
[floating]="floating()"
[wrapperClass]="wrapperClass()"
[labelClass]="labelClass()"
[contentClass]="fieldContentClass()"
>
<div class="relative custom-floating-field">
<div class="floating-input-wrapper">
@if (prefixIcon()) {
<span
class="pointer-events-none absolute inset-y-0 start-0 z-[1] flex w-10 items-center justify-center text-textmuted floating-prefix-icon"
aria-hidden="true"
>
<i
[class]="
prefixIcon() +
' text-[1rem] leading-none ' +
prefixIconClass()
"
></i>
</span>
}
<input
[id]="inputId()"
[name]="name()"
[type]="resolvedType()"
[class]="resolvedInputClass() + ' custom-floating-input'"
[value]="value() ?? ''"
[placeholder]="resolvedPlaceholder()"
[required]="required()"
[readOnly]="readonly()"
[disabled]="isDisabled()"
[attr.autocomplete]="autocomplete()"
[attr.inputmode]="inputMode()"
[attr.spellcheck]="spellcheck()"
[attr.minlength]="minLength()"
[attr.maxlength]="maxLength()"
[attr.pattern]="pattern()"
[attr.min]="min()"
[attr.max]="max()"
[attr.step]="step()"
[attr.aria-label]="ariaLabel() || label()"
[attr.aria-description]="ariaDescription()"
[attr.aria-describedby]="describedBy()"
[attr.aria-invalid]="control()?.invalid ? true : null"
[attr.aria-required]="required() ? true : null"
[attr.aria-busy]="loading() ? true : null"
(input)="onInput($event)"
(blur)="onBlur()"
/>
@if (loading()) {
<span
class="pointer-events-none absolute inset-y-0 end-0 flex w-10 items-center justify-center floating-loader"
aria-hidden="true"
>
<span
class="inline-block size-4 animate-spin rounded-full border-2 border-current border-t-transparent text-primary"
></span>
</span>
} @else if (
type() === 'password' &&
showPasswordToggle()
) {
<button
type="button"
class="absolute inset-y-0 end-0 flex w-10 cursor-pointer items-center justify-center text-textmuted transition-colors hover:text-primary disabled:cursor-not-allowed disabled:opacity-50 floating-action"
[disabled]="isDisabled()"
[attr.aria-label]="
passwordVisible()
? 'Hide ' + label()
: 'Show ' + label()
"
(click)="togglePasswordVisibility()"
>
<i
[class]="
passwordVisible()
? 'ri-eye-off-line'
: 'ri-eye-line'
"
class="text-[1rem] leading-none"
aria-hidden="true"
></i>
</button>
} @else if (suffixIcon()) {
<span
class="pointer-events-none absolute inset-y-0 end-0 flex w-10 items-center justify-center text-textmuted floating-suffix-icon"
aria-hidden="true"
>
<i
[class]="
suffixIcon() +
' text-[1rem] leading-none ' +
suffixIconClass()
"
></i>
</span>
}
</div>
</div>
</app-form-field>
@@ -0,0 +1,272 @@
/* =========================================
Floating Input
========================================= */
:host ::ng-deep .custom-floating-field {
position: relative;
width: 100%;
}
:host ::ng-deep .floating-input-wrapper {
position: relative;
width: 100%;
}
/* =========================================
Input
========================================= */
:host ::ng-deep .custom-floating-input {
width: 100%;
height: 56px;
padding: 0 16px;
border-radius: 12px;
border: 1px solid #d1d5db;
background: #ffffff;
font-size: 16px;
color: #111827;
outline: none;
transition:
border-color .25s ease,
box-shadow .25s ease;
box-sizing: border-box;
}
/* =========================================
Focus
========================================= */
:host ::ng-deep .custom-floating-input:focus {
border-color: #2563eb;
box-shadow:
0 0 0 3px rgba(37,99,235,.12);
}
/* =========================================
Floating Label
========================================= */
:host ::ng-deep .floating-label {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
background: #ffffff;
padding: 0 6px;
color: #64748b;
font-size: 16px;
pointer-events: none;
transition: .25s ease;
z-index: 1;
}
/* =========================================
Floating Animation
========================================= */
:host ::ng-deep .custom-floating-input:focus + .floating-label,
:host ::ng-deep .custom-floating-input:not(:placeholder-shown) + .floating-label {
top: 0;
transform: translateY(-50%);
font-size: 13px;
color: #2563eb;
}
/* =========================================
Prefix Icon
========================================= */
:host ::ng-deep .floating-prefix-icon {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
z-index: 2;
color: #94a3b8;
}
:host ::ng-deep .floating-prefix-icon ~ .custom-floating-input {
padding-left: 42px;
}
/* =========================================
Suffix / Action / Loader
========================================= */
:host ::ng-deep .floating-suffix-icon,
:host ::ng-deep .floating-action,
:host ::ng-deep .floating-loader {
position: absolute;
right: 14px;
top: 50%;
transform: translateY(-50%);
}
:host ::ng-deep .floating-action {
border: none;
background: transparent;
cursor: pointer;
color: #64748b;
}
/* =========================================
Disabled
========================================= */
:host ::ng-deep .custom-floating-input:disabled {
background: #f8fafc;
cursor: not-allowed;
opacity: .8;
}
/* =========================================
Validation States
========================================= */
:host ::ng-deep .custom-floating-input.ng-invalid.ng-touched {
border-color:#ef4444;
}
:host ::ng-deep .custom-floating-input.ng-valid.ng-touched {
border-color:#22c55e;
}
/* =========================================
Dark Mode
========================================= */
:host ::ng-deep .dark .custom-floating-input {
background:#1f2937;
border-color:#475569;
color:#ffffff;
}
:host ::ng-deep .dark .floating-label {
background:#1f2937;
color:#94a3b8;
}
:host ::ng-deep .dark .custom-floating-input:focus {
border-color:#60a5fa;
}
:host ::ng-deep .dark
.custom-floating-input:focus + .floating-label,
:host ::ng-deep .dark
.custom-floating-input:not(:placeholder-shown) + .floating-label {
color:#60a5fa;
}
@@ -79,6 +79,7 @@ export class FormInput implements ControlValueAccessor {
readonly showPasswordToggle = input(true);
readonly loading = input(false);
readonly floating = input(false);
readonly wrapperClass = input('');
readonly fieldContentClass = input('');
@@ -151,10 +152,14 @@ export class FormInput implements ControlValueAccessor {
const configuredPlaceholder = this.placeholder().trim();
if (configuredPlaceholder) {
if (configuredPlaceholder && !this.floating()) {
return configuredPlaceholder;
}
if (this.floating()) {
return '\u00A0';
}
return this.useLabelAsPlaceholder()
? this.label()
: null;
+124 -2
View File
@@ -1,4 +1,4 @@
@if (open()) {
<!-- @if (open()) {
<div
class="hs-overlay open hs-overlay-backdrop-open:!bg-[#32325180] dark:hs-overlay-backdrop-open:!bg-[#323251cc] ti-modal pointer-events-none"
(mousedown)="onBackdropClick($event)">
@@ -51,4 +51,126 @@
</div>
</div>
</div>
}
} -->
@if (open()) {
<div
class="hs-overlay modal-overlay"
(mousedown)="onBackdropClick($event)">
<!-- Backdrop -->
<div
class="modal-backdrop"
aria-hidden="true"
(mousedown)="onBackdropClick($event)">
</div>
<!-- Modal -->
<div
[class]="modalBoxClass() + ' modern-modal-wrapper'"
role="dialog"
aria-modal="true"
[attr.aria-label]="title()"
tabindex="-1"
(mousedown)="$event.stopPropagation()">
<div class="modern-modal">
@if (showHeader()) {
<div class="modern-modal-header">
<!-- Decorative Background -->
<div class="modal-header-bg"></div>
<div class="modal-header-content">
<div class="modal-header-left">
<!-- Header Icon -->
<!-- <div class="modal-icon">
<div class="modal-icon-inner">
<i class="ri-layout-grid-fill"></i>
</div>
</div> -->
<!-- Title -->
<div class="modal-title-wrapper">
<h6 class="modern-modal-title">
{{ title() }}
</h6>
@if (subtitle()) {
<p class="modern-modal-subtitle">
{{ subtitle() }}
</p>
}
</div>
</div>
@if (showCloseButton()) {
<button
type="button"
class="modern-close-btn"
aria-label="Close modal"
[disabled]="loading()"
(click)="close()">
<i class="ri-close-line"></i>
</button>
}
</div>
</div>
}
<!-- Body -->
<div class="modern-modal-body">
<div class="modal-content-wrapper">
<ng-content />
</div>
</div>
<!-- Footer -->
@if (showFooter()) {
<div class="modern-modal-footer">
@if (showCancelButton()) {
<app-button
action="cancel"
[label]="cancelLabel()"
[showIcon]="true"
[disabled]="loading()"
(buttonClicked)="close()" />
}
@if (showSubmitButton()) {
<app-button
[action]="submitAction()"
[label]="submitLabel()"
[loadingLabel]="loadingLabel()"
[showIcon]="true"
[loading]="loading()"
[disabled]="submitDisabled()"
(buttonClicked)="submit()" />
}
</div>
}
</div>
</div>
</div>
}
+749 -2
View File
@@ -1,3 +1,750 @@
:host {
display: contents;
// :host {
// display: contents;
// }
// :host(.modern-modal) .ti-modal-content {
// border-radius: 1rem !important;
// box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25) !important;
// border-color: var(--color-defaultborder) !important;
// &:where(.dark, .dark *) {
// box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5) !important;
// }
// }
// :host(.modern-modal) .ti-modal-header {
// padding-inline: 1.5rem !important;
// padding-block: 1.25rem !important;
// border-color: var(--color-defaultborder) !important;
// }
// :host(.modern-modal) .ti-modal-title {
// font-size: 1.125rem !important;
// font-weight: 600 !important;
// color: var(--color-gray-800) !important;
// &:where(.dark, .dark *) {
// color: var(--color-white) !important;
// }
// }
// :host(.modern-modal) .ti-modal-close-btn {
// border-radius: 0.5rem !important;
// color: var(--color-gray-500) !important;
// transition: all 0.2s ease !important;
// &:hover {
// color: var(--color-primary) !important;
// background-color: color-mix(in srgb, var(--color-primary) 10%, transparent) !important;
// }
// }
// :host(.modern-modal) .ti-modal-body {
// padding: 1.5rem !important;
// }
// :host(.modern-modal) .ti-modal-footer {
// padding-inline: 1.5rem !important;
// padding-block: 1rem !important;
// gap: 0.75rem !important;
// border-color: var(--color-defaultborder) !important;
// }
/* ==========================================================
Overlay
========================================================== */
.modal-overlay {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
padding: 1.5rem;
animation: modalFade .22s ease;
}
/* ==========================================================
Backdrop
========================================================== */
.modal-backdrop {
position: absolute;
inset: 0;
background: rgba(15, 23, 42, .42);
backdrop-filter: blur(1px);
-webkit-backdrop-filter: blur(1px);
}
/* ==========================================================
Wrapper
========================================================== */
.modern-modal-wrapper {
position: relative;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
pointer-events: auto;
animation: modalSlide .28s ease;
}
/* ==========================================================
Modal
========================================================== */
.modern-modal {
position: relative;
display: flex;
flex-direction: column;
width: 100%;
max-height: 90vh;
overflow: hidden;
border-radius: 20px;
background: #ffffff;
// border: 1px solid #edf2f7;
box-shadow:
0 15px 40px rgba(15, 23, 42, .10),
0 30px 80px rgba(15, 23, 42, .14);
}
/* ==========================================================
Header
========================================================== */
.modern-modal-header {
position: relative;
overflow: hidden;
border-bottom: 1px solid rgba(255, 255, 255, .08);
// background: linear-gradient(135deg,
// #020617 0%,
// #172554 40%,
// #4338CA 75%,
// #7C3AED 100%);
// background: linear-gradient(135deg,
// #111C43 0%,
// #16285c 55%,
// #1d3578 100%);
background-color: #7c3deb;
isolation: isolate;
}
/* Decorative Background */
.modal-header-bg {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
}
/* Left Glow */
.modal-header-bg::before {
content: "";
position: absolute;
width: 280px;
height: 280px;
left: -120px;
top: -120px;
border-radius: 50%;
background:
radial-gradient(circle,
rgba(255, 255, 255, .14),
transparent 72%);
filter: blur(8px);
}
/* Right Glow */
.modal-header-bg::after {
content: "";
position: absolute;
width: 240px;
height: 240px;
right: -80px;
top: -90px;
border-radius: 50%;
background:
radial-gradient(circle,
rgba(91, 140, 255, .25),
transparent 72%);
filter: blur(10px);
}
.modern-modal-header::after {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
background:
linear-gradient(90deg,
transparent,
rgba(255, 255, 255, .18),
transparent);
}
/* Header Layout */
.modal-header-content {
position: relative;
z-index: 2;
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
padding: 10px 2rem;
}
.modal-header-left {
display: flex;
align-items: center;
gap: 18px;
min-width: 0;
flex: 1;
}
/* ==========================================================
Icon
========================================================== */
.modal-icon {
flex-shrink: 0;
}
.modal-icon-inner {
width: 40px;
height: 40px;
border-radius: 12px;
display: flex;
justify-content: center;
align-items: center;
color: #ffffff;
background:
linear-gradient(145deg,
rgba(255, 255, 255, .18),
rgba(255, 255, 255, .06));
border: 1px solid rgba(255, 255, 255, .18);
backdrop-filter: blur(12px);
box-shadow:
inset 0 1px 1px rgba(255, 255, 255, .15),
0 10px 30px rgba(0, 0, 0, .25);
}
.modal-icon-inner i {
font-size: 17px;
}
/* ==========================================================
Title
========================================================== */
.modal-title-wrapper {
min-width: 0;
}
.modern-modal-title {
margin: 0;
color: #ffffff;
font-size: 17px;
font-weight: 700;
letter-spacing: -.4px;
line-height: 1.3;
}
.modern-modal-subtitle {
margin-top: .45rem;
color: rgba(255, 255, 255, .72);
font-size: .9rem;
line-height: 1.6;
}
/* ==========================================================
Close
========================================================== */
.modern-close-btn {
width: 25px;
height: 25px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #ffffff;
background: rgba(255, 255, 255, .08);
// border: 1px solid rgba(255, 255, 255, .12);
backdrop-filter: blur(8px);
transition: .28s;
}
.modern-close-btn:hover {
background: rgba(255, 255, 255, .18);
transform: rotate(90deg) scale(1.05);
border-color: rgba(255, 255, 255, .25);
}
.modern-close-btn i {
font-size: 16px;
}
/* ==========================================================
Body
========================================================== */
.modern-modal-body {
flex: 1;
overflow: auto;
padding: 2rem;
}
.modal-content-wrapper {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
/* Scroll */
.modern-modal-body::-webkit-scrollbar {
width: 7px;
}
.modern-modal-body::-webkit-scrollbar-thumb {
background: #d4dbe6;
border-radius: 20px;
}
.modern-modal-body::-webkit-scrollbar-thumb:hover {
background: #b8c3d3;
}
/* ==========================================================
Footer
========================================================== */
/* Footer */
.modern-modal-footer {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
padding: 5px 2rem;
border-top: 1px solid #edf2f7;
background: #ffffff;
}
/* ============================
Cancel Button
============================ */
::ng-deep .modern-modal-footer app-button:first-child button {
height: 36px;
padding: 0 16px;
border-radius: 10px;
border: 1px solid #dbe3ef;
background: #ffffff;
color: #475569;
font-weight: 600;
font-size: 13px;
transition: .3s ease;
}
::ng-deep .modern-modal-footer app-button:first-child button:hover {
background: #f8fafc;
border-color: #cbd5e1;
transform: translateY(-2px);
}
/* ============================
Save Button
============================ */
::ng-deep .modern-modal-footer app-button:last-child button {
position: relative;
overflow: hidden;
height: 36px;
padding: 0 16px;
border-radius: 10px;
border: none;
color: #ffffff;
// background:
// linear-gradient(135deg,
// #111C43,
// #2563eb);
background: #7c3deb;
box-shadow:
0 8px 20px rgba(125, 61, 235, 0.468);
font-weight: 600;
font-size: 13px;
transition: .35s ease;
}
::ng-deep .modern-modal-footer app-button:last-child button:hover {
transform: translateY(-3px);
box-shadow:
0 12px 28px rgba(37, 99, 235, .45);
}
/* ============================
Shine Animation
============================ */
::ng-deep .modern-modal-footer app-button:last-child button::before {
content: "";
position: absolute;
top: 0;
left: -120%;
width: 70%;
height: 100%;
background:
linear-gradient(120deg,
transparent,
rgba(255, 255, 255, .55),
transparent);
transform: skewX(-25deg);
animation: saveButtonShine 2s infinite;
}
@keyframes saveButtonShine {
0% {
left: -120%;
}
40% {
left: 130%;
}
100% {
left: 130%;
}
}
/* Disabled */
::ng-deep .modern-modal-footer button:disabled {
opacity: .55;
cursor: not-allowed;
transform: none !important;
box-shadow: none !important;
}
::ng-deep .modern-modal-footer app-button button {
height: 36px;
padding: 0 16px;
border-radius: 10px;
font-size: 13px;
font-weight: 600;
transition: .3s ease;
}
/* ==========================================================
Mobile
========================================================== */
@media (max-width:768px) {
.modal-overlay {
padding: 1rem;
}
.modern-modal {
border-radius: 18px;
max-height: 95vh;
}
.modal-header-content {
padding: 1em;
}
.modal-icon-inner {
width: 48px;
height: 48px;
border-radius: 14px;
}
.modern-modal-title {
font-size: 12px;
}
.modern-modal-body {
padding: 1.2rem;
}
.modern-modal-footer {
padding: 1rem 1.2rem;
flex-wrap: wrap;
}
}
/* ==========================================================
Animation
========================================================== */
@keyframes modalFade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes modalSlide {
from {
opacity: 0;
transform: translateY(20px) scale(.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@@ -1,8 +1,8 @@
@if(childTitle){
<div class="block justify-between page-header md:flex">
<div>
<h3 class="!text-defaulttextcolor dark:!text-defaulttextcolor/70 dark:text-white dark:hover:text-white text-[1.125rem]! font-semibold">
{{childTitle}}</h3>
<!-- <h3 class="!text-defaulttextcolor dark:!text-defaulttextcolor/70 dark:text-white dark:hover:text-white text-[1.125rem]! font-semibold">
{{childTitle}}</h3> -->
</div>
<!-- <ol class="flex items-center whitespace-nowrap min-w-0">
@if(parentTitle){
+36
View File
@@ -13,6 +13,28 @@
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@500;600&display=swap');
/* Etihad Altis Text (local font) */
@font-face {
font-family: 'EtihadAltis-Text';
src: url('/assets/fonts/EtihadAltis-Text_V3.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
}
body {
font-family: 'EtihadAltis-Text', 'Inter', 'Noto Sans', sans-serif !important;
}
.app-sidebar,
.app-header {
font-family: 'EtihadAltis-Text', 'Inter', 'Noto Sans', sans-serif !important;
}
.main-header-container .header-element {
align-items: center !important;
}
.total-mail-recepients::-webkit-scrollbar {
@apply h-0!;
}
@@ -42,3 +64,17 @@
font-size: 1rem;
line-height: 1.25;
}
/* Center currency modal floating label on empty invalid fields */
currency-list .custom-floating-field.has-error.is-empty .custom-floating-input:not(:focus) ~ .custom-floating-label {
top: 35% !important;
transform: translateY(-50%) !important;
}
/* Remove unwanted bottom margin from primary-full buttons */
.ti-btn-primary-full {
margin-bottom: 0 !important;
}
.ti-form-select{
border: 1px solid #7c3deb !important;
}