WEB-492: Material Design 3 implementation: Login
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Material Design 3 UI Components
|
||||
*
|
||||
* This module exports all Material Design 3 components for easy importing.
|
||||
*/
|
||||
|
||||
export { M3ButtonComponent, M3ButtonVariant } from './m3-button/m3-button.component';
|
||||
export { M3IconComponent, M3IconStyle } from './m3-icon/m3-icon.component';
|
||||
@@ -0,0 +1,31 @@
|
||||
<div [class]="containerClasses">
|
||||
@if (variant === 'filled') {
|
||||
<md-filled-button [type]="type" [disabled]="disabled" (click)="onClick($event)">
|
||||
{{ label }}<ng-content></ng-content>
|
||||
</md-filled-button>
|
||||
}
|
||||
|
||||
@if (variant === 'outlined') {
|
||||
<md-outlined-button [type]="type" [disabled]="disabled" (click)="onClick($event)">
|
||||
{{ label }}<ng-content></ng-content>
|
||||
</md-outlined-button>
|
||||
}
|
||||
|
||||
@if (variant === 'elevated') {
|
||||
<md-elevated-button [type]="type" [disabled]="disabled" (click)="onClick($event)">
|
||||
{{ label }}<ng-content></ng-content>
|
||||
</md-elevated-button>
|
||||
}
|
||||
|
||||
@if (variant === 'text') {
|
||||
<md-text-button [type]="type" [disabled]="disabled" (click)="onClick($event)">
|
||||
{{ label }}<ng-content></ng-content>
|
||||
</md-text-button>
|
||||
}
|
||||
|
||||
@if (variant === 'tonal') {
|
||||
<md-filled-tonal-button [type]="type" [disabled]="disabled" (click)="onClick($event)">
|
||||
{{ label }}<ng-content></ng-content>
|
||||
</md-filled-tonal-button>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
// Material Web Button Component Styles
|
||||
// Base styles are applied globally in src/theme/_material-web.scss
|
||||
|
||||
.m3-button-container {
|
||||
display: inline-block;
|
||||
|
||||
&--full-width {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
||||
md-filled-button,
|
||||
md-outlined-button,
|
||||
md-elevated-button,
|
||||
md-text-button,
|
||||
md-filled-tonal-button {
|
||||
color: var(--md-filled-button-disabled-label-text-color, #44474e);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import { Component, Input, Output, EventEmitter, CUSTOM_ELEMENTS_SCHEMA, ViewEncapsulation } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
// Import Material Web Components
|
||||
import '@material/web/button/filled-button.js';
|
||||
import '@material/web/button/outlined-button.js';
|
||||
import '@material/web/button/elevated-button.js';
|
||||
import '@material/web/button/text-button.js';
|
||||
import '@material/web/button/filled-tonal-button.js';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
export type M3ButtonVariant = 'filled' | 'outlined' | 'elevated' | 'text' | 'tonal';
|
||||
|
||||
/**
|
||||
* Material Design 3 Button Component
|
||||
*
|
||||
* Wrapper for @material/web button components following Material Design 3 specifications.
|
||||
* Uses native Material Design 3 web components with full theming support.
|
||||
*
|
||||
* Features:
|
||||
* - Five button variants: filled, outlined, elevated, text, and tonal
|
||||
* - Automatic light/dark theme support
|
||||
* - Proper disabled states with gray colors
|
||||
* - Hover, focus, and pressed states with ripple effects
|
||||
* - Full width support
|
||||
* - Icon support (leading and trailing)
|
||||
*
|
||||
* @example
|
||||
* // Basic usage with label input
|
||||
* <mifosx-m3-button
|
||||
* variant="filled"
|
||||
* [label]="'Submit'"
|
||||
* (clicked)="onSubmit()">
|
||||
* </mifosx-m3-button>
|
||||
*
|
||||
* @example
|
||||
* // With content projection
|
||||
* <mifosx-m3-button variant="outlined" (clicked)="onCancel()">
|
||||
* Cancel
|
||||
* </mifosx-m3-button>
|
||||
*
|
||||
* @example
|
||||
* // Full width disabled button
|
||||
* <mifosx-m3-button
|
||||
* variant="filled"
|
||||
* [label]="'Loading...'"
|
||||
* [disabled]="true"
|
||||
* [fullWidth]="true">
|
||||
* </mifosx-m3-button>
|
||||
*
|
||||
* @example
|
||||
* // With translated label
|
||||
* <mifosx-m3-button
|
||||
* variant="tonal"
|
||||
* type="submit"
|
||||
* [label]="'labels.buttons.Save' | translate"
|
||||
* (clicked)="onSave()">
|
||||
* </mifosx-m3-button>
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-m3-button',
|
||||
templateUrl: './m3-button.component.html',
|
||||
styleUrls: ['./m3-button.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
STANDALONE_SHARED_IMPORTS,
|
||||
CommonModule
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class M3ButtonComponent {
|
||||
/**
|
||||
* Button variant
|
||||
* - filled: Solid background (default, highest emphasis)
|
||||
* - outlined: Bordered button (medium emphasis)
|
||||
* - elevated: Raised button with shadow (medium emphasis)
|
||||
* - text: Text-only button (low emphasis)
|
||||
* - tonal: Subtle background button (medium emphasis)
|
||||
*/
|
||||
@Input() variant: M3ButtonVariant = 'filled';
|
||||
|
||||
/**
|
||||
* Button type attribute
|
||||
*/
|
||||
@Input() type: 'button' | 'submit' | 'reset' = 'button';
|
||||
|
||||
/**
|
||||
* Disabled state
|
||||
*/
|
||||
@Input() disabled: boolean = false;
|
||||
|
||||
/**
|
||||
* Button label text
|
||||
*/
|
||||
@Input() label?: string;
|
||||
|
||||
/**
|
||||
* Icon to display before text
|
||||
*/
|
||||
@Input() icon?: string;
|
||||
|
||||
/**
|
||||
* Icon to display after text
|
||||
*/
|
||||
@Input() trailingIcon?: string;
|
||||
|
||||
/**
|
||||
* Whether the button should take full width
|
||||
*/
|
||||
@Input() fullWidth: boolean = false;
|
||||
|
||||
/**
|
||||
* Click event emitter
|
||||
*/
|
||||
@Output() clicked = new EventEmitter<Event>();
|
||||
|
||||
/**
|
||||
* Handle button click
|
||||
*/
|
||||
onClick(event: Event): void {
|
||||
if (!this.disabled) {
|
||||
this.clicked.emit(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Material Web Component tag name based on variant
|
||||
*/
|
||||
get componentTag(): string {
|
||||
switch (this.variant) {
|
||||
case 'filled':
|
||||
return 'md-filled-button';
|
||||
case 'outlined':
|
||||
return 'md-outlined-button';
|
||||
case 'elevated':
|
||||
return 'md-elevated-button';
|
||||
case 'text':
|
||||
return 'md-text-button';
|
||||
case 'tonal':
|
||||
return 'md-filled-tonal-button';
|
||||
default:
|
||||
return 'md-filled-button';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get container classes
|
||||
*/
|
||||
get containerClasses(): string {
|
||||
return this.fullWidth ? 'm3-button-container--full-width' : 'm3-button-container';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<span [class]="iconClass" [ngStyle]="iconStyles" class="m3-icon">{{ name }}</span>
|
||||
@@ -0,0 +1,71 @@
|
||||
// Material Symbols Font Faces
|
||||
// These will be loaded from Google Fonts or local files
|
||||
@use 'theme/material-web' as *;
|
||||
|
||||
.m3-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
vertical-align: middle;
|
||||
|
||||
// Smooth rendering
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizelegibility;
|
||||
}
|
||||
|
||||
// Material Symbols base styles
|
||||
.material-symbols-outlined,
|
||||
.material-symbols-filled,
|
||||
.material-symbols-rounded,
|
||||
.material-symbols-sharp {
|
||||
font-family: 'Material Symbols Outlined', sans-serif;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
color: var(--md-icon-prefix-label-text-color, #44474e);
|
||||
}
|
||||
|
||||
// Filled variant
|
||||
.material-symbols-filled {
|
||||
font-variation-settings:
|
||||
'FILL' 1,
|
||||
'wght' 400,
|
||||
'GRAD' 0,
|
||||
'opsz' 24;
|
||||
}
|
||||
|
||||
// Outlined variant (default)
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings:
|
||||
'FILL' 0,
|
||||
'wght' 400,
|
||||
'GRAD' 0,
|
||||
'opsz' 24;
|
||||
}
|
||||
|
||||
// Rounded variant
|
||||
.material-symbols-rounded {
|
||||
font-family: 'Material Symbols Rounded', sans-serif;
|
||||
font-variation-settings:
|
||||
'FILL' 0,
|
||||
'wght' 400,
|
||||
'GRAD' 0,
|
||||
'opsz' 24;
|
||||
}
|
||||
|
||||
// Sharp variant
|
||||
.material-symbols-sharp {
|
||||
font-family: 'Material Symbols Sharp', sans-serif;
|
||||
font-variation-settings:
|
||||
'FILL' 0,
|
||||
'wght' 400,
|
||||
'GRAD' 0,
|
||||
'opsz' 24;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Component, Input, ViewEncapsulation } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
export type M3IconStyle = 'outlined' | 'filled' | 'rounded' | 'sharp';
|
||||
|
||||
/**
|
||||
* Material Design 3 Icon Component
|
||||
*
|
||||
* Wrapper for Material Symbols icons following Material Design 3 specifications.
|
||||
* Uses Google's Material Symbols font for consistent iconography.
|
||||
*
|
||||
* Features:
|
||||
* - Multiple icon styles: outlined (default), filled, rounded, and sharp
|
||||
* - Automatic theme support
|
||||
* - Size control
|
||||
* - Color customization via CSS
|
||||
*
|
||||
* @example
|
||||
* // Basic usage
|
||||
* <mifosx-m3-icon name="account_circle"></mifosx-m3-icon>
|
||||
*
|
||||
* @example
|
||||
* // With filled style
|
||||
* <mifosx-m3-icon name="lock" style="filled"></mifosx-m3-icon>
|
||||
*
|
||||
* @example
|
||||
* // With custom size
|
||||
* <mifosx-m3-icon name="visibility" [size]="24"></mifosx-m3-icon>
|
||||
*
|
||||
* Common icon mappings from Font Awesome:
|
||||
* - user-circle → account_circle
|
||||
* - lock → lock
|
||||
* - eye → visibility
|
||||
* - eye-slash → visibility_off
|
||||
* - home → home
|
||||
* - search → search
|
||||
* - settings → settings
|
||||
* - menu → menu
|
||||
* - close → close
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-m3-icon',
|
||||
templateUrl: './m3-icon.component.html',
|
||||
styleUrls: ['./m3-icon.component.scss'],
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class M3IconComponent {
|
||||
/**
|
||||
* Icon name from Material Symbols
|
||||
* See: https://fonts.google.com/icons
|
||||
*/
|
||||
@Input() name!: string;
|
||||
|
||||
/**
|
||||
* Icon style variant
|
||||
* - outlined: Default style with strokes (default)
|
||||
* - filled: Solid filled icons
|
||||
* - rounded: Rounded corner icons
|
||||
* - sharp: Sharp corner icons
|
||||
*/
|
||||
@Input() style: M3IconStyle = 'outlined';
|
||||
|
||||
/**
|
||||
* Icon size in pixels
|
||||
* Default is 24px (Material Design standard)
|
||||
*/
|
||||
@Input() size: number = 24;
|
||||
|
||||
/**
|
||||
* Whether the icon should be filled (alternative to style="filled")
|
||||
* This is a convenience property for backwards compatibility
|
||||
*/
|
||||
@Input() filled: boolean = false;
|
||||
|
||||
/**
|
||||
* Get the CSS class for the icon style
|
||||
*/
|
||||
get iconClass(): string {
|
||||
const styleToUse = this.filled ? 'filled' : this.style;
|
||||
return `material-symbols-${styleToUse}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the inline styles for the icon
|
||||
*/
|
||||
get iconStyles(): { [key: string]: string } {
|
||||
return {
|
||||
'font-size': `${this.size}px`,
|
||||
width: `${this.size}px`,
|
||||
height: `${this.size}px`
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user