Files
syscom-master-admin-ui/src/app/shared/components/button/button.ts
T

357 lines
7.4 KiB
TypeScript

import {
ChangeDetectionStrategy,
Component,
computed,
input,
output
} from '@angular/core';
export type ButtonType = 'button' | 'submit' | 'reset';
export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg';
export type IconPosition = 'left' | 'right';
export type ButtonVariant =
| 'primary'
| 'primary-full'
| 'outline-primary'
| 'secondary'
| 'secondary-full'
| 'success'
| 'success-full'
| 'danger'
| 'danger-full'
| 'warning'
| 'warning-full'
| 'info'
| 'info-full'
| 'light'
| 'dark'
| 'transparent'
| 'custom';
export type ButtonAction =
| 'add'
| 'save'
| 'submit'
| 'update'
| 'edit'
| 'delete'
| 'cancel'
| 'close'
| 'upload'
| 'download'
| 'import'
| 'export'
| 'view'
| 'search'
| 'refresh'
| 'reset'
| 'approve'
| 'reject'
| 'login'
| 'next'
| 'previous'
| 'custom';
interface ButtonActionConfig {
label: string;
icon: string | null;
variant: ButtonVariant;
type: ButtonType;
}
const BUTTON_ACTION_CONFIG: Record<ButtonAction, ButtonActionConfig> = {
add: {
label: 'Add',
icon: 'ri-add-line',
variant: 'primary-full',
type: 'button'
},
save: {
label: 'Save',
icon: 'ri-save-line',
variant: 'primary-full',
type: 'submit'
},
submit: {
label: 'Submit',
icon: 'ri-send-plane-line',
variant: 'primary-full',
type: 'submit'
},
update: {
label: 'Update',
icon: 'ri-save-3-line',
variant: 'primary-full',
type: 'submit'
},
edit: {
label: 'Edit',
icon: 'ri-edit-line',
variant: 'primary-full',
type: 'button'
},
delete: {
label: 'Delete',
icon: 'ri-delete-bin-line',
variant: 'danger-full',
type: 'button'
},
cancel: {
label: 'Cancel',
icon: 'ri-close-line',
variant: 'light',
type: 'button'
},
close: {
label: 'Close',
icon: 'ri-close-line',
variant: 'light',
type: 'button'
},
upload: {
label: 'Upload',
icon: 'ri-upload-cloud-2-line',
variant: 'primary-full',
type: 'button'
},
download: {
label: 'Download',
icon: 'ri-download-cloud-2-line',
variant: 'success-full',
type: 'button'
},
import: {
label: 'Import',
icon: 'ri-file-upload-line',
variant: 'info-full',
type: 'button'
},
export: {
label: 'Export',
icon: 'ri-file-download-line',
variant: 'success-full',
type: 'button'
},
view: {
label: 'View',
icon: 'ri-eye-line',
variant: 'info-full',
type: 'button'
},
search: {
label: 'Search',
icon: 'ri-search-line',
variant: 'primary-full',
type: 'button'
},
refresh: {
label: 'Refresh',
icon: 'ri-refresh-line',
variant: 'light',
type: 'button'
},
reset: {
label: 'Reset',
icon: 'ri-restart-line',
variant: 'light',
type: 'reset'
},
approve: {
label: 'Approve',
icon: 'ri-check-line',
variant: 'success-full',
type: 'button'
},
reject: {
label: 'Reject',
icon: 'ri-close-circle-line',
variant: 'danger-full',
type: 'button'
},
login: {
label: 'Login',
icon: 'ri-login-box-line',
variant: 'primary-full',
type: 'submit'
},
next: {
label: 'Next',
icon: 'ri-arrow-right-line',
variant: 'primary-full',
type: 'button'
},
previous: {
label: 'Previous',
icon: 'ri-arrow-left-line',
variant: 'light',
type: 'button'
},
custom: {
label: '',
icon: null,
variant: 'custom',
type: 'button'
}
};
@Component({
selector: 'app-button',
standalone: true,
templateUrl: './button.html',
styleUrl: './button.scss',
host: {
'[class.w-full]': 'fullWidth()'
},
changeDetection: ChangeDetectionStrategy.OnPush
})
export class Button {
/*
* Action provides sensible defaults.
* Explicit inputs below override action configuration.
*/
readonly action = input<ButtonAction | null>(null);
readonly label = input<string | null>(null);
readonly icon = input<string | null>(null);
readonly type = input<ButtonType | null>(null);
readonly variant = input<ButtonVariant | null>(null);
readonly size = input<ButtonSize>('md');
readonly iconPosition = input<IconPosition>('left');
readonly showIcon = input(true);
readonly iconOnly = input(false);
readonly loading = input(false);
readonly loadingLabel = input<string | null>(null);
readonly disabled = input(false);
readonly fullWidth = input(false);
readonly rounded = input(false);
readonly className = input('');
readonly iconClass = input('');
readonly ariaLabel = input<string | null>(null);
readonly ariaExpanded = input<boolean | null>(null);
readonly ariaControls = input<string | null>(null);
readonly title = input<string | null>(null);
readonly buttonClicked = output<MouseEvent>();
private readonly actionConfig = computed<ButtonActionConfig>(() => {
const action = this.action();
return action
? BUTTON_ACTION_CONFIG[action]
: BUTTON_ACTION_CONFIG.custom;
});
readonly resolvedLabel = computed(() => {
return this.label() ?? this.actionConfig().label;
});
readonly resolvedIcon = computed(() => {
// Explicit icon overrides the action icon.
return this.icon() ?? this.actionConfig().icon;
});
readonly resolvedType = computed<ButtonType>(() => {
return this.type() ?? this.actionConfig().type ?? 'button';
});
readonly resolvedVariant = computed<ButtonVariant>(() => {
const explicitVariant = this.variant();
if (explicitVariant) {
return explicitVariant;
}
const action = this.action();
if (action) {
return BUTTON_ACTION_CONFIG[action].variant;
}
return 'primary-full';
});
readonly isDisabled = computed(() => {
return this.disabled() || this.loading();
});
readonly resolvedIconClass = computed(() => {
return [
this.resolvedIcon(),
'align-middle leading-none',
this.iconClass()
]
.filter(Boolean)
.join(' ');
});
readonly buttonClass = computed(() => {
const variants: Record<ButtonVariant, string> = {
primary: 'ti-btn-primary',
'primary-full': 'ti-btn-primary-full',
'outline-primary': 'ti-btn-outline-primary',
secondary: 'ti-btn-secondary',
'secondary-full': 'ti-btn-secondary-full',
success: 'ti-btn-success',
'success-full': 'ti-btn-success-full',
danger: 'ti-btn-danger',
'danger-full': 'ti-btn-danger-full',
warning: 'ti-btn-warning',
'warning-full': 'ti-btn-warning-full',
info: 'ti-btn-info',
'info-full': 'ti-btn-info-full',
light: 'ti-btn-light',
dark: 'ti-btn-dark',
transparent: 'bg-transparent',
custom: ''
};
const sizes: Record<ButtonSize, string> = {
xs: '!px-2 !py-1 !text-[0.6875rem]',
sm: '!px-3 !py-1.5 !text-[0.75rem]',
md: '',
lg: 'ti-btn-lg'
};
return [
'ti-btn',
'inline-flex items-center justify-center',
'cursor-pointer',
variants[this.resolvedVariant()],
sizes[this.size()],
this.fullWidth() ? 'w-full' : '',
this.iconOnly() ? '!p-0' : '',
this.rounded() ? '!rounded-full' : '',
this.isDisabled()
? 'cursor-not-allowed opacity-60'
: 'cursor-pointer',
this.className()
]
.filter(Boolean)
.join(' ');
});
onClick(event: MouseEvent): void {
if (this.isDisabled()) {
event.preventDefault();
event.stopPropagation();
return;
}
this.buttonClicked.emit(event);
}
}