You've already forked php-flasher
mirror of
https://github.com/php-flasher/php-flasher.git
synced 2026-04-05 12:32:55 +01:00
add material theme
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @file PHPFlasher Material Design Theme Registration
|
||||
* @description Registers the Material Design theme with PHPFlasher
|
||||
* @author yoeunes
|
||||
*/
|
||||
import flasher from '../../index'
|
||||
import { materialTheme } from './material'
|
||||
|
||||
/**
|
||||
* Register the Material Design theme.
|
||||
*
|
||||
* This theme provides minimalist notifications styled after Google's Material Design system.
|
||||
* The registration makes the theme available under the name 'material'.
|
||||
*
|
||||
* The Material theme can be used by calling:
|
||||
* ```typescript
|
||||
* flasher.use('theme.material').success('Operation completed successfully');
|
||||
* ```
|
||||
*
|
||||
* Key features:
|
||||
* - Clean Material Design card with proper elevation
|
||||
* - Material typography system with Roboto font
|
||||
* - UPPERCASE action button text following Material guidelines
|
||||
* - Ripple effect on button interaction
|
||||
* - Linear progress indicator
|
||||
* - Minimalist design without icons
|
||||
*/
|
||||
flasher.addTheme('material', materialTheme)
|
||||
@@ -0,0 +1,321 @@
|
||||
/**
|
||||
* @file PHPFlasher Material Design Theme Styles
|
||||
* @description CSS styling for minimalist Material Design notifications
|
||||
* @author yoeunes
|
||||
*/
|
||||
|
||||
/**
|
||||
* PHPFlasher - Material Design Theme
|
||||
*
|
||||
* Google's Material Design-inspired notifications with a minimalist approach.
|
||||
* Clean and focused implementation of the widely recognized design system.
|
||||
*/
|
||||
.fl-material {
|
||||
/* Theme variables - Define the visual appearance of Material Design notifications */
|
||||
|
||||
/* Base colors and appearance for light and dark modes */
|
||||
--md-bg-light: #ffffff; /* Card background in light mode */
|
||||
--md-bg-dark: #2d2d2d; /* Card background in dark mode */
|
||||
--md-text-light: rgba(0, 0, 0, 0.87); /* Primary text in light mode (87% black) */
|
||||
--md-text-secondary-light: rgba(0, 0, 0, 0.6); /* Secondary text in light mode (60% black) */
|
||||
--md-text-dark: rgba(255, 255, 255, 0.87); /* Primary text in dark mode (87% white) */
|
||||
--md-text-secondary-dark: rgba(255, 255, 255, 0.6); /* Secondary text in dark mode (60% white) */
|
||||
|
||||
/* Material Design elevation - multi-layered shadows */
|
||||
--md-elevation: 0 3px 5px -1px rgba(0,0,0,0.2), /* Ambient shadow */
|
||||
0 6px 10px 0 rgba(0,0,0,0.14), /* Penumbra shadow */
|
||||
0 1px 18px 0 rgba(0,0,0,0.12); /* Umbra shadow */
|
||||
|
||||
--md-elevation-dark: 0 3px 5px -1px rgba(0,0,0,0.4), /* Darker ambient shadow */
|
||||
0 6px 10px 0 rgba(0,0,0,0.28), /* Darker penumbra shadow */
|
||||
0 1px 18px 0 rgba(0,0,0,0.24); /* Darker umbra shadow */
|
||||
--md-border-radius: 4px; /* Material Design default corner radius */
|
||||
|
||||
/* Material Design color palette for notification types */
|
||||
--md-success: #43a047; /* Green 600 from Material palette */
|
||||
--md-info: #1e88e5; /* Blue 600 from Material palette */
|
||||
--md-warning: #fb8c00; /* Orange 600 from Material palette */
|
||||
--md-error: #e53935; /* Red 600 from Material palette */
|
||||
|
||||
/* Animation timing variables */
|
||||
--md-animation-duration: 0.3s; /* Standard Material motion duration */
|
||||
--md-ripple-duration: 0.6s; /* Ripple effect duration */
|
||||
}
|
||||
|
||||
/**
|
||||
* Entrance animation for Material theme
|
||||
* Material Design-style slide up with easing
|
||||
*/
|
||||
@keyframes mdSlideUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px); /* Start below final position */
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0); /* End at final position */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Material Design ripple effect animation
|
||||
* Creates expanding circle from point of interaction
|
||||
*/
|
||||
@keyframes mdRipple {
|
||||
to {
|
||||
transform: scale(4); /* Expand to 4x original size */
|
||||
opacity: 0; /* Fade out completely */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base Material Design theme styling
|
||||
*/
|
||||
.fl-material {
|
||||
position: relative;
|
||||
margin: 8px 0; /* Spacing between notifications */
|
||||
width: 100%;
|
||||
max-width: 400px; /* Maximum width following Material guidelines */
|
||||
font-family: Roboto, "Segoe UI", Helvetica, Arial, sans-serif; /* Material uses Roboto */
|
||||
animation: mdSlideUp var(--md-animation-duration) cubic-bezier(0.4, 0, 0.2, 1); /* Material standard easing */
|
||||
|
||||
/**
|
||||
* Main card container
|
||||
* Follows Material Design card component styling
|
||||
*/
|
||||
.fl-md-card {
|
||||
background-color: var(--md-bg-light);
|
||||
color: var(--md-text-light);
|
||||
border-radius: var(--md-border-radius);
|
||||
box-shadow: var(--md-elevation); /* Multi-layered shadow for depth */
|
||||
overflow: hidden; /* Contains progress bar */
|
||||
}
|
||||
|
||||
/**
|
||||
* Content section
|
||||
* Contains message text
|
||||
*/
|
||||
.fl-content {
|
||||
padding: 16px; /* Standard Material padding */
|
||||
display: flex;
|
||||
align-items: flex-start; /* Align items to top */
|
||||
}
|
||||
|
||||
/**
|
||||
* Text content container
|
||||
* Holds message
|
||||
*/
|
||||
.fl-text-content {
|
||||
flex: 1; /* Take available space */
|
||||
}
|
||||
|
||||
/**
|
||||
* Message styling
|
||||
* Following Material Design typography
|
||||
*/
|
||||
.fl-message {
|
||||
font-size: 0.875rem; /* 14px - Material body 2 */
|
||||
line-height: 1.43; /* Material line height for body 2 */
|
||||
color: var(--md-text-secondary-light); /* Secondary text color (60% opacity) */
|
||||
}
|
||||
|
||||
/**
|
||||
* Action buttons section
|
||||
* Contains dismiss button
|
||||
*/
|
||||
.fl-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end; /* Align buttons to right */
|
||||
padding: 8px; /* Standard Material padding */
|
||||
}
|
||||
|
||||
/**
|
||||
* Action button styling
|
||||
* Following Material Design "text button" guidelines
|
||||
*/
|
||||
.fl-action-button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: currentColor; /* Inherit color from parent */
|
||||
font-family: inherit;
|
||||
font-weight: 500; /* Medium weight per Material specs */
|
||||
font-size: 0.8125rem; /* 13px - Material button text size */
|
||||
text-transform: uppercase; /* Material buttons use uppercase */
|
||||
letter-spacing: 0.0892857143em; /* Material's letter spacing for buttons */
|
||||
padding: 8px 12px; /* Material button padding */
|
||||
border-radius: 4px; /* Rounded corners per Material */
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s; /* Smooth hover effect */
|
||||
position: relative; /* For ripple positioning */
|
||||
overflow: hidden; /* Contain ripple effect */
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: rgba(0, 0, 0, 0.04); /* Material hover state - 4% opacity */
|
||||
}
|
||||
|
||||
/**
|
||||
* Ripple effect
|
||||
* Material Design's signature ink ripple on interaction
|
||||
*/
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 5px; /* Initial small circle */
|
||||
height: 5px;
|
||||
background: currentColor; /* Use button text color */
|
||||
opacity: 0;
|
||||
border-radius: 50%; /* Perfect circle */
|
||||
transform: scale(1);
|
||||
pointer-events: none; /* Don't interfere with clicks */
|
||||
}
|
||||
|
||||
&:active::after {
|
||||
opacity: 0.3; /* Material ripple opacity */
|
||||
animation: mdRipple var(--md-ripple-duration) linear; /* Expand and fade */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-specific styling
|
||||
* Each notification type has its own color based on Material palette
|
||||
*/
|
||||
&.fl-success {
|
||||
.fl-icon-wrapper {
|
||||
color: var(--md-success); /* Green icon */
|
||||
}
|
||||
|
||||
.fl-action-button {
|
||||
color: var(--md-success); /* Green button */
|
||||
}
|
||||
}
|
||||
|
||||
&.fl-info {
|
||||
.fl-icon-wrapper {
|
||||
color: var(--md-info); /* Blue icon */
|
||||
}
|
||||
|
||||
.fl-action-button {
|
||||
color: var(--md-info); /* Blue button */
|
||||
}
|
||||
}
|
||||
|
||||
&.fl-warning {
|
||||
.fl-icon-wrapper {
|
||||
color: var(--md-warning); /* Orange icon */
|
||||
}
|
||||
|
||||
.fl-action-button {
|
||||
color: var(--md-warning); /* Orange button */
|
||||
}
|
||||
}
|
||||
|
||||
&.fl-error {
|
||||
.fl-icon-wrapper {
|
||||
color: var(--md-error); /* Red icon */
|
||||
}
|
||||
|
||||
.fl-action-button {
|
||||
color: var(--md-error); /* Red button */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress bar
|
||||
* Material Design linear progress indicator
|
||||
*/
|
||||
.fl-progress-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px; /* Material's standard progress height */
|
||||
overflow: hidden;
|
||||
|
||||
.fl-progress {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
transform-origin: left center; /* Animation starts from left */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-specific progress colors
|
||||
* Each notification type has its own progress bar color
|
||||
*/
|
||||
&.fl-success .fl-progress {
|
||||
background-color: var(--md-success); /* Green progress */
|
||||
}
|
||||
|
||||
&.fl-info .fl-progress {
|
||||
background-color: var(--md-info); /* Blue progress */
|
||||
}
|
||||
|
||||
&.fl-warning .fl-progress {
|
||||
background-color: var(--md-warning); /* Orange progress */
|
||||
}
|
||||
|
||||
&.fl-error .fl-progress {
|
||||
background-color: var(--md-error); /* Red progress */
|
||||
}
|
||||
|
||||
/**
|
||||
* RTL Support
|
||||
* Right-to-left language direction support
|
||||
*/
|
||||
&.fl-rtl {
|
||||
direction: rtl;
|
||||
|
||||
.fl-content {
|
||||
flex-direction: row-reverse; /* Reverse flex direction */
|
||||
}
|
||||
|
||||
.fl-icon-wrapper {
|
||||
margin-right: 0;
|
||||
margin-left: 16px; /* Swap margins for RTL */
|
||||
}
|
||||
|
||||
.fl-actions {
|
||||
justify-content: flex-start; /* Align buttons to left in RTL */
|
||||
}
|
||||
|
||||
.fl-progress {
|
||||
transform-origin: right center; /* Animation starts from right */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessibility
|
||||
* Respects reduced motion preferences
|
||||
*/
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
animation: none; /* Disable slide animation */
|
||||
|
||||
.fl-action-button:active::after {
|
||||
animation: none; /* Disable ripple animation */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dark mode support
|
||||
* Material Design dark theme implementation
|
||||
*/
|
||||
body.fl-dark .fl-material,
|
||||
html.fl-dark .fl-material,
|
||||
.fl-material.fl-auto-dark {
|
||||
.fl-md-card {
|
||||
background-color: var(--md-bg-dark); /* Darker background */
|
||||
color: var(--md-text-dark); /* Lighter text */
|
||||
box-shadow: var(--md-elevation-dark); /* Stronger shadows */
|
||||
}
|
||||
|
||||
.fl-message {
|
||||
color: var(--md-text-secondary-dark); /* Lighter secondary text */
|
||||
}
|
||||
|
||||
.fl-action-button {
|
||||
&:hover, &:focus {
|
||||
background-color: rgba(255, 255, 255, 0.08); /* Material dark hover - 8% opacity */
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @file PHPFlasher Material Design Theme Implementation
|
||||
* @description Minimalist Material Design notification theme
|
||||
* @author yoeunes
|
||||
*/
|
||||
import './material.scss'
|
||||
import type { Envelope } from '../../types'
|
||||
|
||||
/**
|
||||
* Material Design notification theme for PHPFlasher.
|
||||
*
|
||||
* This theme implements Google's Material Design principles with a minimalist approach:
|
||||
* - Clean card design with proper elevation shadow
|
||||
* - Material Design typography with Roboto font
|
||||
* - UPPERCASE action button following Material guidelines
|
||||
* - Material "ink ripple" effect on button press
|
||||
* - Material motion patterns for animations
|
||||
* - Linear progress indicator
|
||||
*
|
||||
* Unlike the more comprehensive Google theme, this Material theme
|
||||
* provides a simpler, more streamlined appearance without icons,
|
||||
* focusing purely on the message content.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import flasher from '@flasher/flasher';
|
||||
* import { materialTheme } from '@flasher/flasher/themes';
|
||||
*
|
||||
* // Register the theme (if not already registered)
|
||||
* flasher.addTheme('material', materialTheme);
|
||||
*
|
||||
* // Use the theme
|
||||
* flasher.use('theme.material').success('Operation completed successfully');
|
||||
* ```
|
||||
*/
|
||||
export const materialTheme = {
|
||||
/**
|
||||
* Renders a notification envelope as HTML.
|
||||
*
|
||||
* Creates a minimalist Material Design notification card with
|
||||
* message content, dismiss button, and progress indicator.
|
||||
*
|
||||
* @param envelope - The notification envelope to render
|
||||
* @returns HTML string representation of the notification
|
||||
*/
|
||||
render: (envelope: Envelope): string => {
|
||||
const { type, message } = envelope
|
||||
|
||||
// Set appropriate ARIA roles based on notification type
|
||||
const isAlert = type === 'error' || type === 'warning'
|
||||
const role = isAlert ? 'alert' : 'status'
|
||||
const ariaLive = isAlert ? 'assertive' : 'polite'
|
||||
|
||||
// Material Design uses uppercase text for buttons
|
||||
const actionText = 'DISMISS'
|
||||
|
||||
return `
|
||||
<div class="fl-material fl-${type}" role="${role}" aria-live="${ariaLive}" aria-atomic="true">
|
||||
<div class="fl-md-card">
|
||||
<div class="fl-content">
|
||||
<div class="fl-text-content">
|
||||
<div class="fl-message">${message}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fl-actions">
|
||||
<button class="fl-action-button fl-close" aria-label="Close ${type} message">
|
||||
${actionText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fl-progress-bar">
|
||||
<div class="fl-progress"></div>
|
||||
</div>
|
||||
</div>`
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user