diff --git a/about/callout-media-banner-showcase.page.tsx b/about/callout-media-banner-showcase.page.tsx index 70177bba43..5ca868aeba 100644 --- a/about/callout-media-banner-showcase.page.tsx +++ b/about/callout-media-banner-showcase.page.tsx @@ -14,8 +14,11 @@ export default function CalloutMediaBannerShowcase() { console.log(`CalloutMediaBanner button clicked: ${message}`); }; - // Sample background image (placeholder) - const sampleBackgroundImage = "data:image/svg+xml,%3Csvg width='1280' height='400' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3ClinearGradient id='grad' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%2321E46B;stop-opacity:1' /%3E%3Cstop offset='100%25' style='stop-color:%230DAA3E;stop-opacity:1' /%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1280' height='400' fill='url(%23grad)' /%3E%3C/svg%3E"; + // Sample background images (placeholders) + // To load an image from the `public` folder in Next.js (or Create React App), use the path relative to the `public` directory, starting with a slash. + // For example, if you have `/public/backgrounds/Callout.jpg`, use: + const sampleBackgroundImage = "/img/backgrounds/Callout.jpg"; + const sampleLightBackgroundImage = "data:image/svg+xml,%3Csvg width='1280' height='400' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3ClinearGradient id='grad' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%2370EE97;stop-opacity:1' /%3E%3Cstop offset='100%25' style='stop-color:%23C0A7FF;stop-opacity:1' /%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1280' height='400' fill='url(%23grad)' /%3E%3C/svg%3E"; return (
@@ -218,6 +221,7 @@ export default function CalloutMediaBannerShowcase() {

When backgroundImage is provided, it overrides the variant prop. The component automatically adds a gradient overlay to ensure text remains readable. + You can also specify textColor to fix the text color across both light and dark modes.

@@ -226,26 +230,63 @@ export default function CalloutMediaBannerShowcase() {
  • If backgroundImage is provided: Image is used, variant color is ignored
  • If only variant is provided: Solid color background is applied
  • If neither: Defaults to white background (default variant)
  • -
  • Text color: Always white when using background image for readability
  • +
  • Text color: Defaults to white, or set to black via textColor="black"
  • +
  • Fixed text color: Text color remains consistent across light and dark modes
  • +
  • Overlay gradient: Dark overlay for white text, light overlay for black text
  • - handleClick('image-primary') }} - tertiaryButton={{ label: "Explore Features", onClick: () => handleClick('image-tertiary') }} - /> + {/* White Text Example */} +
    + + + +
    + White Text (Default) - textColor="white" +
    + Best for dark or colorful images. Includes dark overlay gradient. +
    +
    +
    +
    + handleClick('image-white-primary') }} + /> +
    + + {/* Black Text Example */} +
    + + + +
    + Black Text - textColor="black" +
    + Best for light or bright images. Includes light overlay gradient. Text remains black in both light and dark modes. +
    +
    +
    +
    + handleClick('image-black-primary') }} + tertiaryButton={{ label: "Explore Features", onClick: () => handleClick('image-black-tertiary') }} + /> +

    - Note: The image variant includes an automatic gradient overlay (dark to light) to maintain text legibility. + Note: The image variant includes an automatic gradient overlay. White text gets a dark overlay, black text gets a light overlay. Text colors remain fixed across both light and dark modes.

    @@ -499,6 +540,14 @@ export default function CalloutMediaBannerShowcase() {
    Background image URL - overrides variant color
    + {/* textColor */} +
    +
    textColor
    +
    'white' | 'black'
    +
    'white'
    +
    Text color for image variant - fixed across light/dark modes (only used when backgroundImage is provided)
    +
    + {/* heading */}
    heading
    diff --git a/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.scss b/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.scss index 4ee8bb8aae..c43f0ebc45 100644 --- a/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.scss +++ b/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.scss @@ -8,7 +8,8 @@ // .bds-callout-media-banner--lilac - Lilac variant // .bds-callout-media-banner--green - Green variant // .bds-callout-media-banner--gray - Gray variant -// .bds-callout-media-banner--image - Background image variant +// .bds-callout-media-banner--image - Background image variant (white text by default) +// .bds-callout-media-banner--image-text-black - Image variant with black text (fixed across light/dark) // .bds-callout-media-banner--centered - Centered content modifier (no buttons or no heading) // .bds-callout-media-banner__content - Content wrapper // .bds-callout-media-banner__text - Text content container @@ -83,7 +84,7 @@ $bds-cmb-image-overlay: rgba(0, 0, 0, 0.3); } // Desktop breakpoint - @include media-breakpoint-up(xl) { + @include media-breakpoint-up(lg) { padding: $bds-cmb-padding-desktop; min-height: 360px; } @@ -152,7 +153,7 @@ $bds-cmb-image-overlay: rgba(0, 0, 0, 0.3); // Typography - Heading MD token (Tobias Light) @include type(heading-md); margin: 0; - color: inherit; + color: inherit !important; } .bds-callout-media-banner__subheading { @@ -164,14 +165,20 @@ $bds-cmb-image-overlay: rgba(0, 0, 0, 0.3); // ============================================================================= // Action Buttons -// ============================================================================= +// ==================================================================== c========= .bds-callout-media-banner__actions { display: flex; - flex-direction: row; - align-items: center; - gap: $bds-cmb-button-gap; + flex-direction: column; + align-items: flex-start; flex-wrap: wrap; + gap: 8px; + // Tablet breakpoint + @include media-breakpoint-up(md) { + flex-direction: row; + align-items: center; + gap: 0px; + } } // ============================================================================= @@ -212,7 +219,7 @@ $bds-cmb-image-overlay: rgba(0, 0, 0, 0.3); // No background-color (image takes precedence) background-color: transparent; - // Add overlay gradient for text readability + // Add overlay gradient for text readability (default: dark overlay for white text) &::before { content: ''; position: absolute; @@ -229,10 +236,24 @@ $bds-cmb-image-overlay: rgba(0, 0, 0, 0.3); z-index: 0; pointer-events: none; } +} + +// Image variant with black text modifier +// Fixed black text across both light and dark modes +.bds-callout-media-banner--image-text-black { + // Light overlay for black text readability + &::before { + background: linear-gradient( + 135deg, + rgba(255, 255, 255, 0.4) 0%, + rgba(255, 255, 255, 0.2) 50%, + rgba(255, 255, 255, 0.1) 100% + ); + } - // Ensure text is readable over image + // Force black text in both light and dark modes .bds-callout-media-banner__text { - color: $white; + color: $black !important; } } @@ -286,12 +307,19 @@ html.dark { } } - // Image variant remains the same (overlay already provides contrast) + // Image variant with white text remains white .bds-callout-media-banner--image { .bds-callout-media-banner__text { color: $white; } } + + // Image variant with black text remains black (fixed across modes) + .bds-callout-media-banner--image-text-black { + .bds-callout-media-banner__text { + color: $black !important; + } + } } // ============================================================================= @@ -344,11 +372,18 @@ html.light { } } - // Image variant + // Image variant with white text remains white .bds-callout-media-banner--image { .bds-callout-media-banner__text { color: $white; } } + + // Image variant with black text remains black (fixed across modes) + .bds-callout-media-banner--image-text-black { + .bds-callout-media-banner__text { + color: $black !important; + } + } } diff --git a/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.tsx b/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.tsx index cdfb3c5b76..0f0de9b41d 100644 --- a/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.tsx +++ b/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.tsx @@ -8,6 +8,8 @@ export interface CalloutMediaBannerProps { variant?: 'default' | 'light-gray' | 'lilac' | 'green' | 'gray'; /** Background image URL - overrides variant color when provided */ backgroundImage?: string; + /** Text color for image variant - fixes text color across light/dark modes (only applicable when backgroundImage is provided) */ + textColor?: 'white' | 'black'; /** Main heading text */ heading?: string; /** Subheading/description text */ @@ -45,17 +47,28 @@ export interface CalloutMediaBannerProps { * /> * * @example - * // With background image + * // With background image (white text - default) * + * + * @example + * // With background image and black text (fixed across light/dark modes) + * */ export const CalloutMediaBanner: React.FC = ({ variant = 'default', backgroundImage, + textColor = 'white', heading, subheading, primaryButton, @@ -79,6 +92,8 @@ export const CalloutMediaBanner: React.FC = ({ !backgroundImage && `bds-callout-media-banner--${variant}`, // Add image class when backgroundImage is provided backgroundImage && 'bds-callout-media-banner--image', + // Add text color modifier for image variant + backgroundImage && textColor === 'black' && 'bds-callout-media-banner--image-text-black', // Add centered class when content should be centered shouldCenter && 'bds-callout-media-banner--centered', className diff --git a/shared/patterns/CalloutMediaBanner/README.md b/shared/patterns/CalloutMediaBanner/README.md index a83354b3cb..b92157e659 100644 --- a/shared/patterns/CalloutMediaBanner/README.md +++ b/shared/patterns/CalloutMediaBanner/README.md @@ -34,7 +34,8 @@ The component automatically adapts its spacing and typography based on viewport | `lilac` | Lilac 300 (#C0A7FF) | Black (#141414) | Black | | `green` | Green 200 (#70EE97) | Black (#141414) | Black | | `gray` | Gray 300 (#CAD4DF) | Black (#141414) | Black | -| `image` | Background Image | White (#FFFFFF) | Green | +| `image` (textColor='white') | Background Image | White (#FFFFFF) | Green | +| `image` (textColor='black') | Background Image | Black (#141414) | Green | ### Dark Mode @@ -45,7 +46,8 @@ The component automatically adapts its spacing and typography based on viewport | `lilac` | Lilac 400 (#7649E3) | White (#FFFFFF) | Black | | `green` | Green 300 (#21E46B) | Black (#141414) | Black | | `gray` | Gray 600 (#454549) | White (#FFFFFF) | Black | -| `image` | Background Image | White (#FFFFFF) | Green | +| `image` (textColor='white') | Background Image | White (#FFFFFF) | Green | +| `image` (textColor='black') | Background Image | Black (#141414) | Green | ## Props API @@ -55,6 +57,8 @@ interface CalloutMediaBannerProps { variant?: 'default' | 'light-gray' | 'lilac' | 'green' | 'gray'; /** Background image URL - overrides variant color when provided */ backgroundImage?: string; + /** Text color for image variant - fixes text color across light/dark modes (only applicable when backgroundImage is provided) */ + textColor?: 'white' | 'black'; /** Main heading text */ heading?: string; /** Subheading/description text */ @@ -80,6 +84,7 @@ interface CalloutMediaBannerProps { - `variant`: `'default'` - `backgroundImage`: `undefined` +- `textColor`: `'white'` (only used when `backgroundImage` is provided) - `primaryButton`: `undefined` - `tertiaryButton`: `undefined` - `className`: `''` @@ -100,7 +105,7 @@ import { CalloutMediaBanner } from 'shared/patterns/CalloutMediaBanner'; /> ``` -### With Background Image +### With Background Image (White Text - Default) ```tsx ``` +### With Background Image (Black Text) + +```tsx + +``` + ### Single Button ```tsx @@ -154,8 +171,9 @@ import { CalloutMediaBanner } from 'shared/patterns/CalloutMediaBanner'; When `backgroundImage` is provided, it **overrides** the `variant` prop: - ✅ Image is used as background - ✅ No solid color background is applied -- ✅ Text color is always white for readability -- ✅ Gradient overlay is automatically added +- ✅ Text color defaults to white (can be set to black via `textColor` prop) +- ✅ Text color remains fixed across both light and dark modes +- ✅ Gradient overlay is automatically added (dark overlay for white text, light overlay for black text) ### Button Color Logic @@ -194,19 +212,20 @@ The component **already wraps content in PageGrid structure**. Do not nest it in ### BEM Class Structure ```scss -.bds-callout-media-banner // Base banner -.bds-callout-media-banner--default // White background variant -.bds-callout-media-banner--light-gray // Light gray variant -.bds-callout-media-banner--lilac // Lilac variant -.bds-callout-media-banner--green // Green variant -.bds-callout-media-banner--gray // Gray variant -.bds-callout-media-banner--image // Background image variant -.bds-callout-media-banner--no-actions // No buttons modifier -.bds-callout-media-banner__content // Content wrapper -.bds-callout-media-banner__text // Text container -.bds-callout-media-banner__heading // Heading element -.bds-callout-media-banner__subheading // Subheading element -.bds-callout-media-banner__actions // Button container +.bds-callout-media-banner // Base banner +.bds-callout-media-banner--default // White background variant +.bds-callout-media-banner--light-gray // Light gray variant +.bds-callout-media-banner--lilac // Lilac variant +.bds-callout-media-banner--green // Green variant +.bds-callout-media-banner--gray // Gray variant +.bds-callout-media-banner--image // Background image variant (white text) +.bds-callout-media-banner--image-text-black // Background image with black text (fixed across modes) +.bds-callout-media-banner--centered // Centered content modifier +.bds-callout-media-banner__content // Content wrapper +.bds-callout-media-banner__text // Text container +.bds-callout-media-banner__heading // Heading element +.bds-callout-media-banner__subheading // Subheading element +.bds-callout-media-banner__actions // Button container ``` ### Typography Tokens @@ -271,8 +290,12 @@ $gray-600 // Gray (dark) - Minimum recommended resolution: 1920x600px for sharp display - Use images with clear focal points on the left/center -- Ensure sufficient contrast for white text readability -- Test with the automatic gradient overlay +- **Text Color Selection**: + - Use `textColor="white"` (default) for dark or colorful images + - Use `textColor="black"` for light or bright images + - Text color remains fixed across both light and dark modes +- Ensure sufficient contrast between image and text +- Test with the automatic gradient overlay (dark for white text, light for black text) ## Files diff --git a/static/css/devportal2024-v1.css b/static/css/devportal2024-v1.css index 4a8b36f9c3..d0fd8fea50 100644 --- a/static/css/devportal2024-v1.css +++ b/static/css/devportal2024-v1.css @@ -16903,7 +16903,7 @@ html.light .bds-hero-split-media--accent .bds-hero-split-media__subtitle { padding: 24px; } } -@media (min-width: 1280px) { +@media (min-width: 992px) { .bds-callout-media-banner { padding: 40px 32px; min-height: 360px; @@ -16978,7 +16978,7 @@ html.light .bds-hero-split-media--accent .bds-hero-split-media__subtitle { } .bds-callout-media-banner__heading { margin: 0; - color: inherit; + color: inherit !important; } .bds-callout-media-banner__subheading { @@ -17012,10 +17012,17 @@ html.light .bds-hero-split-media--accent .bds-hero-split-media__subtitle { .bds-callout-media-banner__actions { display: flex; - flex-direction: row; - align-items: center; - gap: 24px; + flex-direction: column; + align-items: flex-start; flex-wrap: wrap; + gap: 8px; +} +@media (min-width: 576px) { + .bds-callout-media-banner__actions { + flex-direction: row; + align-items: center; + gap: 0px; + } } .bds-callout-media-banner--default { @@ -17052,8 +17059,12 @@ html.light .bds-hero-split-media--accent .bds-hero-split-media__subtitle { z-index: 0; pointer-events: none; } -.bds-callout-media-banner--image .bds-callout-media-banner__text { - color: #FFFFFF; + +.bds-callout-media-banner--image-text-black::before { + background: linear-gradient(135deg, rgba(255, 255, 255, 0.4) 0%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.1) 100%); +} +.bds-callout-media-banner--image-text-black .bds-callout-media-banner__text { + color: #141414 !important; } html.dark .bds-callout-media-banner--default { @@ -17089,6 +17100,9 @@ html.dark .bds-callout-media-banner--gray .bds-callout-media-banner__text { html.dark .bds-callout-media-banner--image .bds-callout-media-banner__text { color: #FFFFFF; } +html.dark .bds-callout-media-banner--image-text-black .bds-callout-media-banner__text { + color: #141414 !important; +} html.light .bds-callout-media-banner--default { background-color: #FFFFFF; @@ -17123,6 +17137,9 @@ html.light .bds-callout-media-banner--gray .bds-callout-media-banner__text { html.light .bds-callout-media-banner--image .bds-callout-media-banner__text { color: #FFFFFF; } +html.light .bds-callout-media-banner--image-text-black .bds-callout-media-banner__text { + color: #141414 !important; +} pre { color: #FFFFFF; diff --git a/static/img/backgrounds/Callout.jpg b/static/img/backgrounds/Callout.jpg new file mode 100644 index 0000000000..9649c492a0 Binary files /dev/null and b/static/img/backgrounds/Callout.jpg differ