From 24d8140033c9ce60d1a7b0077c74363335ca69ed Mon Sep 17 00:00:00 2001 From: Zaiba Machhaliya Date: Sun, 16 Aug 2026 18:23:56 +0530 Subject: [PATCH 1/2] feat: add configurable visibility props for SupportUsButton sections --- src/components/SupportUsButton.tsx | 816 +++++++++++++++-------------- src/types/index.ts | 18 + src/utils/validateProps.ts | 229 ++++---- 3 files changed, 551 insertions(+), 512 deletions(-) diff --git a/src/components/SupportUsButton.tsx b/src/components/SupportUsButton.tsx index debcbc7..3f2005b 100644 --- a/src/components/SupportUsButton.tsx +++ b/src/components/SupportUsButton.tsx @@ -10,7 +10,9 @@ function sRgbLuminance(c: number): number { function isDarkColor(colorStr?: string): boolean { if (!colorStr) return true; - let r = 0, g = 0, b = 0; + let r = 0, + g = 0, + b = 0; const rgbMatch = colorStr.match(/\d+/g); if (rgbMatch && rgbMatch.length >= 3) { r = parseInt(rgbMatch[0], 10); @@ -33,7 +35,10 @@ function isDarkColor(colorStr?: string): boolean { return true; } // WCAG relative luminance formula: L = 0.2126 * R + 0.7152 * G + 0.0722 * B - const lum = 0.2126 * sRgbLuminance(r) + 0.7152 * sRgbLuminance(g) + 0.0722 * sRgbLuminance(b); + const lum = + 0.2126 * sRgbLuminance(r) + + 0.7152 * sRgbLuminance(g) + + 0.0722 * sRgbLuminance(b); return lum < 0.179; } @@ -62,31 +67,35 @@ function validateUrl(url?: string): string | undefined { } // Main component function that renders the support us button, taking in various props for customization and rendering different sections such as hero, organization information, sponsors, and call-to-action based on the provided data and selected theme and button variant -function SupportUsButton( - props: supportUsButtonProps, -): React.JSX.Element { - const validatedProps = validateProps(props ?? ({} as supportUsButtonProps)); +function SupportUsButton(props: supportUsButtonProps): React.JSX.Element { + const validatedProps = validateProps(props ?? ({} as supportUsButtonProps)); - const { - Theme = "auto", - organizationInformation, - sponsors = [], - ctaSection, - projectInformation, - Logo = true, - className = "", - } = validatedProps ?? ({} as supportUsButtonProps); + const { + Theme = "auto", + organizationInformation, + sponsors = [], + ctaSection, + projectInformation, + Logo = true, + className = "", + showProjectInfo = true, + showOrganizationInfo = true, + showSponsors = true, + showBackgroundLogo = true, + showBorder = true, + showCornerIcons = true, + } = validatedProps ?? ({} as supportUsButtonProps); - const border = validatedProps.border ?? { - TopX1: "-1000", - TopX2: "1000", - BottomX1: "-1000", - BottomX2: "1000", - LeftY1: "-1000", - LeftY2: "1000", - RightY1: "-1000", - RightY2: "1000", - }; + const border = validatedProps.border ?? { + TopX1: "-1000", + TopX2: "1000", + BottomX1: "-1000", + BottomX2: "1000", + LeftY1: "-1000", + LeftY2: "1000", + RightY1: "-1000", + RightY2: "1000", + }; const containerRef = useRef(null); const isAuto = Theme === "auto" || Theme === "inherit"; @@ -105,13 +114,16 @@ function SupportUsButton( isAuto ? { fontFamily: parentStyles.fontFamily || "inherit", - color: parentStyles.color !== "inherit" ? parentStyles.color : "inherit", + color: + parentStyles.color !== "inherit" + ? parentStyles.color + : "inherit", } : undefined } className={`relative overflow-hidden w-full h-full px-12 sm:px-14 md:px-20 py-10 sm:py-10 md:py-14 text-center ${isAuto ? "bg-transparent font-inherit text-inherit" : "font-sans"} ${classAccordingToTheme(Theme)} ${className}`} > - {Logo && ( + {Logo && showBackgroundLogo && (
{/* Border around page - wrapped around content */} - - {/* */} - + {showBorder && ( + + {/* */} + - {/* */} - + {/* */} + - {/* */} - + {/* */} + - {/* */} - - + {/* */} + + + )} - {/* Top left icon */} - - - + {showCornerIcons && ( + <> + {/* Top left icon */} + + + - {/* Top right icon */} - - - + {/* Top right icon */} + + + - {/* Bottom left Icon */} - - - + {/* Bottom left Icon */} + + + - {/* Bottom Right Icon */} - - - + {/* Bottom Right Icon */} + + + + + )}
@@ -284,12 +302,10 @@ function SupportUsButton( {projectInformation?.name}
- {projectInformation?.name && ( + {showProjectInfo && projectInformation?.name && (
-

- A Project Powered by -

+

A Project Powered by

{validatedUrl ? ( -
+
{ctaSection.sponsorLink.map((link, index) => (
- -
- {projectInformation?.name && ( -
-
- {projectInformation?.name} -

- About {projectInformation?.name} -

-
-

- - + {projectInformation?.name && ( +

+
+ {projectInformation?.name} - - {projectInformation?.description} -

-
- )} - -
- - - -
- -
-
- {organizationInformation.name} -

- About {organizationInformation.name} -

-
-

- - - - {organizationInformation.desc} -

-
-
- -
-
- - Supported By Global - - - - - Powerhouses -
- -
- {sponsors?.map((sponsor, index) => ( -
-
+

+ About {projectInformation?.name} +

+
+

- - - - - - - - - - - - + + {projectInformation?.description} +

+
+ )} + {showProjectInfo && + projectInformation?.name && + showOrganizationInfo && ( +
+
- -
- {sponsor.name} - {sponsor.sponsorshipTier && ( - - {sponsor.sponsorshipTier} Sponsor - - )} + )} + {showOrganizationInfo && ( +
+
+ {organizationInformation.name} +

+ About {organizationInformation.name} +

- -
+

- - - - - - - - - - - - - -

+ {organizationInformation.desc} +

- ))} + )}
-
+ )} + + {showSponsors && ( +
+
+ + Supported By Global + + + + + Powerhouses +
+ +
+ {sponsors?.map((sponsor, index) => ( +
+
+ + + + + + + + + + + + + + + +
+ +
+ {sponsor.name} + {sponsor.sponsorshipTier && ( + + {sponsor.sponsorshipTier} Sponsor + + )} +
+ +
+ + + + + + + + + + + + + + + +
+
+ ))} +
+
+ )}
); diff --git a/src/types/index.ts b/src/types/index.ts index 88be9a9..e3d5a43 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -109,4 +109,22 @@ export interface supportUsButtonProps { // These defines the length in X and Y axis of border around page. Only pass it when border is not covering full page (e.g. TopX1: "-10" TopX2: "110"). border?: borderAroundPage; + + /** Show/hide project information section (default: true) */ + showProjectInfo?: boolean; + + /** Show/hide organization information section (default: true) */ + showOrganizationInfo?: boolean; + + /** Show/hide sponsors section (default: true) */ + showSponsors?: boolean; + + /** Show/hide background logo (default: true) */ + showBackgroundLogo?: boolean; + + /** Show/hide decorative border (default: true) */ + showBorder?: boolean; + + /** Show/hide corner icons (default: true) */ + showCornerIcons?: boolean; } diff --git a/src/utils/validateProps.ts b/src/utils/validateProps.ts index 27d8fe2..1225ad5 100644 --- a/src/utils/validateProps.ts +++ b/src/utils/validateProps.ts @@ -2,12 +2,7 @@ import type { supportUsButtonProps } from "../types/index"; const VALID_THEMES = ["auto", "inherit", "light", "dark"] as const; -const VALID_SPONSOR_TIERS = [ - "Platinum", - "Gold", - "Silver", - "Bronze", -] as const; +const VALID_SPONSOR_TIERS = ["Platinum", "Gold", "Silver", "Bronze"] as const; const DEFAULT_BORDER = { TopX1: "-1000", @@ -30,12 +25,13 @@ type Border = { RightY1: string; RightY2: string; }; + declare const process: | { - env?: { - NODE_ENV?: string; - }; - } + env?: { + NODE_ENV?: string; + }; + } | undefined; function warn(message: string): void { @@ -79,13 +75,23 @@ function isValidBorderValue(value: unknown): value is string { return Number.isFinite(Number(value)); } +function validateBoolean(value: unknown, defaultValue: boolean): boolean { + if (value === undefined || value === null) { + return defaultValue; + } + if (typeof value === "boolean") { + return value; + } + warn( + `Expected boolean, got ${typeof value}. Falling back to ${defaultValue}.`, + ); + return defaultValue; +} + export function validateProps( props: supportUsButtonProps, ): supportUsButtonProps { props = props ?? ({} as supportUsButtonProps); - // -------------------------------- - // Theme - // -------------------------------- const Theme = VALID_THEMES.includes( props.Theme as (typeof VALID_THEMES)[number], @@ -101,10 +107,6 @@ export function validateProps( ); } - // -------------------------------- - // organizationInformation - // -------------------------------- - let organizationInformation = props.organizationInformation; if (!isObject(organizationInformation)) { @@ -171,10 +173,6 @@ export function validateProps( }; } - // -------------------------------- - // projectInformation - // -------------------------------- - let projectInformation = props.projectInformation; if (projectInformation !== undefined) { @@ -228,10 +226,6 @@ export function validateProps( } } - // -------------------------------- - // sponsors - // -------------------------------- - let sponsors = props.sponsors; if (sponsors !== undefined) { @@ -242,63 +236,59 @@ export function validateProps( sponsors = []; } else { - sponsors = sponsors.reduce((normalized, sponsor, index) => { - if (!isObject(sponsor)) { - warn( - `sponsors[${index}] must be an object. This sponsor will be ignored.`, + sponsors = sponsors.reduce( + (normalized, sponsor, index) => { + if (!isObject(sponsor)) { + warn( + `sponsors[${index}] must be an object. This sponsor will be ignored.`, + ); + return normalized; + } + + const name = + typeof sponsor.name === "string" ? sponsor.name.trim() : ""; + + if (!name) { + warn( + `sponsors[${index}].name must be a non-empty string. This sponsor will be ignored.`, + ); + return normalized; + } + + let sponsorshipTier = sponsor.sponsorshipTier; + + if ( + sponsorshipTier !== undefined && + !VALID_SPONSOR_TIERS.includes( + sponsorshipTier as (typeof VALID_SPONSOR_TIERS)[number], + ) + ) { + warn( + `sponsors[${index}].sponsorshipTier must be one of: ${VALID_SPONSOR_TIERS.join( + ", ", + )}. The invalid tier will be removed.`, + ); + + sponsorshipTier = undefined; + } + + const { sponsorshipTier: _originalTier, ...sponsorRest } = sponsor; + + normalized.push( + sponsorshipTier === undefined + ? { ...sponsorRest, name } + : { ...sponsorRest, name, sponsorshipTier }, ); - return normalized; - } - const name = - typeof sponsor.name === "string" ? sponsor.name.trim() : ""; - - if (!name) { - warn( - `sponsors[${index}].name must be a non-empty string. This sponsor will be ignored.`, - ); return normalized; - } - - let sponsorshipTier = sponsor.sponsorshipTier; - - if ( - sponsorshipTier !== undefined && - !VALID_SPONSOR_TIERS.includes( - sponsorshipTier as (typeof VALID_SPONSOR_TIERS)[number], - ) - ) { - warn( - `sponsors[${index}].sponsorshipTier must be one of: ${VALID_SPONSOR_TIERS.join( - ", ", - )}. The invalid tier will be removed.`, - ); - - sponsorshipTier = undefined; - } - - // Build a brand-new sponsor object instead of mutating the - // caller-owned one — omit sponsorshipTier from the spread first, - // then add it back only if it's valid. - const { sponsorshipTier: _originalTier, ...sponsorRest } = sponsor; - - normalized.push( - sponsorshipTier === undefined - ? { ...sponsorRest, name } - : { ...sponsorRest, name, sponsorshipTier }, - ); - - return normalized; - }, [] as typeof sponsors); + }, + [] as typeof sponsors, + ); } } else { sponsors = []; } - // -------------------------------- - // ctaSection - // -------------------------------- - let ctaSection = props.ctaSection; if (!isObject(ctaSection)) { @@ -319,41 +309,40 @@ export function validateProps( sponsorLink: [], }; } else { - const sponsorLink = ctaSection.sponsorLink.reduce((normalized, link, index) => { - if (!isObject(link)) { - warn( - `ctaSection.sponsorLink[${index}] must be an object. This link will be ignored.`, - ); - return normalized; - } + const sponsorLink = ctaSection.sponsorLink.reduce( + (normalized, link, index) => { + if (!isObject(link)) { + warn( + `ctaSection.sponsorLink[${index}] must be an object. This link will be ignored.`, + ); + return normalized; + } - const name = typeof link.name === "string" ? link.name.trim() : ""; + const name = typeof link.name === "string" ? link.name.trim() : ""; - if (!name) { - warn( - `ctaSection.sponsorLink[${index}].name must be a non-empty string. This link will be ignored.`, - ); - return normalized; - } + if (!name) { + warn( + `ctaSection.sponsorLink[${index}].name must be a non-empty string. This link will be ignored.`, + ); + return normalized; + } - if (!isValidUrl(link.url)) { - warn( - `ctaSection.sponsorLink[${index}].url must be a valid http(s) URL. This link will be ignored.`, - ); - return normalized; - } + if (!isValidUrl(link.url)) { + warn( + `ctaSection.sponsorLink[${index}].url must be a valid http(s) URL. This link will be ignored.`, + ); + return normalized; + } - // Build a brand-new link object instead of mutating the - // caller-owned one. - normalized.push({ ...link, name }); + normalized.push({ ...link, name }); - return normalized; - }, [] as typeof ctaSection.sponsorLink); + return normalized; + }, + [] as typeof ctaSection.sponsorLink, + ); if (sponsorLink.length === 0) { - warn( - "ctaSection.sponsorLink should contain at least one valid link.", - ); + warn("ctaSection.sponsorLink should contain at least one valid link."); } ctaSection = { @@ -362,10 +351,6 @@ export function validateProps( }; } - // -------------------------------- - // Logo - // -------------------------------- - let Logo = props.Logo; if (Logo !== undefined && typeof Logo !== "boolean") { @@ -373,22 +358,15 @@ export function validateProps( Logo = true; } - // -------------------------------- - // className - // -------------------------------- - let className = props.className; if (className !== undefined && typeof className !== "string") { - warn("className must be a string when provided. Falling back to an empty string."); + warn( + "className must be a string when provided. Falling back to an empty string.", + ); className = ""; } - - // -------------------------------- - // border - // -------------------------------- - let border: Border = { ...DEFAULT_BORDER, }; @@ -424,6 +402,16 @@ export function validateProps( } } + const showProjectInfo = validateBoolean(props.showProjectInfo, true); + const showOrganizationInfo = validateBoolean( + props.showOrganizationInfo, + true, + ); + const showSponsors = validateBoolean(props.showSponsors, true); + const showBackgroundLogo = validateBoolean(props.showBackgroundLogo, true); + const showBorder = validateBoolean(props.showBorder, true); + const showCornerIcons = validateBoolean(props.showCornerIcons, true); + return { ...props, Theme, @@ -434,5 +422,12 @@ export function validateProps( Logo, className, border, + // Visibility props + showProjectInfo, + showOrganizationInfo, + showSponsors, + showBackgroundLogo, + showBorder, + showCornerIcons, }; -} \ No newline at end of file +} From 3492f003878d3bf702770d31e7913a513a481143 Mon Sep 17 00:00:00 2001 From: Zaiba Machhaliya Date: Sun, 16 Aug 2026 18:47:34 +0530 Subject: [PATCH 2/2] fix: apply showProjectInfo to project panel conditional rendering --- src/components/SupportUsButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SupportUsButton.tsx b/src/components/SupportUsButton.tsx index 3f2005b..304dc6b 100644 --- a/src/components/SupportUsButton.tsx +++ b/src/components/SupportUsButton.tsx @@ -362,7 +362,7 @@ function SupportUsButton(props: supportUsButtonProps): React.JSX.Element { className={`mt-20 sm:mt-24 flex flex-col gap-10 lg:gap-8 animate-sub-fade-in ${projectInformation?.name && "lg:flex-row lg:items-start"}`} style={{ animationDelay: "250ms" }} > - {projectInformation?.name && ( + {showProjectInfo && projectInformation?.name && (