Merge pull request #233 from php-flasher/themes

add google theme
This commit is contained in:
Younes ENNAJI
2025-03-08 11:42:40 +00:00
committed by GitHub
3 changed files with 480 additions and 0 deletions
@@ -0,0 +1,341 @@
/**
* @file PHPFlasher Google Theme Styles
* @description CSS styling for Material Design-inspired notifications
* @author yoeunes
*/
/**
* PHPFlasher - Google Theme
*
* Google's Material Design-inspired notifications.
* One of the most recognized design systems worldwide.
*/
.fl-google {
/* 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 Google 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 Google Material Design theme styling
*/
.fl-google {
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 icon and text content
*/
.fl-content {
padding: 16px; /* Standard Material padding */
display: flex;
align-items: flex-start; /* Align items to top */
}
/**
* Icon container
* Holds the Material Design SVG icon
*/
.fl-icon-wrapper {
margin-right: 16px; /* Space between icon and text */
color: var(--md-text-secondary-light); /* Default icon color */
flex-shrink: 0; /* Prevent icon from shrinking */
}
/**
* Text content container
* Holds title and message
*/
.fl-text-content {
flex: 1; /* Take available space */
}
/**
* Title styling
* Following Material Design typography
*/
.fl-title {
font-size: 1rem; /* 16px - Material body 1 */
font-weight: 500; /* Medium weight per Material guidelines */
margin-bottom: 4px;
}
/**
* 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-google,
html.fl-dark .fl-google,
.fl-google.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,111 @@
/**
* @file PHPFlasher Google Theme Implementation
* @description Material Design-inspired notification theme
* @author yoeunes
*/
import './google.scss'
import type { Envelope } from '../../types'
/**
* Google Material Design-inspired notification theme for PHPFlasher.
*
* This theme replicates Google's Material Design aesthetics with:
* - Elevated card component with proper shadow depth
* - Material Design iconography
* - Google's typography system with Roboto font
* - Material "ink ripple" effect on buttons
* - UPPERCASE action buttons following Material guidelines
* - Smooth animation with Material Design's motion patterns
*
* The theme follows Material Design specifications for components like
* cards, buttons, typography and iconography, creating a familiar experience
* for users of Google products.
*
* @example
* ```typescript
* import flasher from '@flasher/flasher';
* import { googleTheme } from '@flasher/flasher/themes';
*
* // Register the theme (if not already registered)
* flasher.addTheme('google', googleTheme);
*
* // Use the theme
* flasher.use('theme.google').success('Operation completed successfully');
* ```
*/
export const googleTheme = {
/**
* Renders a notification envelope as HTML.
*
* Creates a Google Material Design-style notification with icon, title (optional),
* message, dismissal 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, title } = 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'
// Action button text in Material Design style (uppercase)
const actionText = 'DISMISS'
/**
* Gets the appropriate Material Design icon based on notification type.
* Each icon follows Google's Material Design iconography guidelines.
*
* @returns SVG markup for the notification icon
*/
const getIcon = () => {
switch (type) {
case 'success':
return `<svg class="fl-icon-svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
</svg>`
case 'error':
return `<svg class="fl-icon-svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="currentColor" d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"/>
</svg>`
case 'warning':
return `<svg class="fl-icon-svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="currentColor" d="M12 5.99L19.53 19H4.47L12 5.99M12 2L1 21h22L12 2zm1 14h-2v2h2v-2zm0-6h-2v4h2v-4z"/>
</svg>`
case 'info':
return `<svg class="fl-icon-svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
</svg>`
}
return ''
}
// Generate title section if title is provided
const titleSection = title ? `<div class="fl-title">${title}</div>` : ''
return `
<div class="fl-google fl-${type}" role="${role}" aria-live="${ariaLive}" aria-atomic="true">
<div class="fl-md-card">
<div class="fl-content">
<div class="fl-icon-wrapper">
${getIcon()}
</div>
<div class="fl-text-content">
${titleSection}
<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>`
},
}
@@ -0,0 +1,28 @@
/**
* @file PHPFlasher Google Theme Registration
* @description Registers the Google theme with PHPFlasher
* @author yoeunes
*/
import flasher from '../../index'
import { googleTheme } from './google'
/**
* Register the Google theme.
*
* This theme provides notifications styled after Google's Material Design system.
* The registration makes the theme available under the name 'google'.
*
* The Google theme can be used by calling:
* ```typescript
* flasher.use('theme.google').success('Operation completed successfully');
* ```
*
* Key features:
* - Material Design card with proper elevation
* - Google's typography system with Roboto font
* - Material Design icons and color palette
* - Ripple effect on interaction
* - UPPERCASE action button text
* - Proper implementation of Material Design light and dark themes
*/
flasher.addTheme('google', googleTheme)