Merge pull request #2004 from fveiraswww/feat/banner-hex-colors

UI: Support `rainbowColors` API in `<Banner />` component
This commit is contained in:
Francisco Veiras
2025-06-23 11:48:28 +08:00
committed by GitHub
parent d21b7dea27
commit e9b1c9cc54
5 changed files with 85 additions and 52 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-ui': patch
---
Support `rainbowColors` API in `<Banner />` component
+2
View File
@@ -45,6 +45,7 @@ import { getMDXComponents } from '@/mdx-components';
import { APIPage } from 'fumadocs-openapi/ui';
import { LLMCopyButton, ViewOptions } from './page.client';
import * as path from 'node:path';
import { Banner } from 'fumadocs-ui/components/banner';
function PreviewRenderer({ preview }: { preview: string }): ReactNode {
if (preview && preview in Preview) {
@@ -131,6 +132,7 @@ export default async function Page(props: {
</HoverCard>
);
},
Banner,
Mermaid,
TypeTable,
AutoTypeTable: (props) => (
+17 -3
View File
@@ -241,16 +241,30 @@ export function banner(): ReactNode {
</Banner>
<Banner
className="z-0"
id="test-rainbow"
className="z-0"
variant="rainbow"
changeLayout={false}
>
Using the <code>rainbow</code> variant
</Banner>
<Banner className="z-0" id="test" changeLayout={false}>
Be careful, this banner can be closed
<Banner
id="test"
className="z-0"
variant="rainbow"
rainbowColors={[
'rgba(255,100,0, 0.5)',
'rgba(255,100,0, 0.5)',
'transparent',
'rgba(255,100,0, 0.5)',
'transparent',
'rgba(255,100,0, 0.5)',
'transparent',
]}
changeLayout={false}
>
customise the <code>rainbow</code> variant
</Banner>
</div>
</Wrapper>
@@ -35,6 +35,22 @@ Change the default variant.
import { Banner } from 'fumadocs-ui/components/banner';
<Banner variant="rainbow">Hello World</Banner>;
// customise colors
<Banner
variant="rainbow"
rainbowColors={[
'rgba(255,100,0, 0.5)',
'rgba(255,100,0, 0.5)',
'transparent',
'rgba(255,100,0, 0.5)',
'transparent',
'rgba(255,100,0, 0.5)',
'transparent',
]}
>
Hello World
</Banner>;
```
### Change Layout
+45 -49
View File
@@ -5,11 +5,19 @@ import { X } from 'lucide-react';
import { cn } from '@/utils/cn';
import { buttonVariants } from '@/components/ui/button';
type BannerVariant = 'rainbow' | 'normal';
export function Banner({
id,
variant = 'normal',
changeLayout = true,
height = '3rem',
rainbowColors = [
'rgba(0,149,255,0.56)',
'rgba(231,77,255,0.77)',
'rgba(255,0,0,0.73)',
'rgba(131,255,166,0.66)',
],
...props
}: HTMLAttributes<HTMLDivElement> & {
/**
@@ -20,7 +28,12 @@ export function Banner({
/**
* @defaultValue 'normal'
*/
variant?: 'rainbow' | 'normal';
variant?: BannerVariant;
/**
* For rainbow variant only, customise the colors
*/
rainbowColors?: string[];
/**
* Change Fumadocs layout styles
@@ -43,7 +56,8 @@ export function Banner({
id={id}
{...props}
className={cn(
'sticky top-0 z-40 flex flex-row items-center justify-center bg-fd-secondary px-4 text-center text-sm font-medium',
'sticky top-0 z-40 flex flex-row items-center justify-center px-4 text-center text-sm font-medium',
variant === 'normal' && 'bg-fd-secondary',
variant === 'rainbow' && 'bg-fd-background',
!open && 'hidden',
props.className,
@@ -70,7 +84,11 @@ export function Banner({
/>
) : null}
{variant === 'rainbow' ? rainbowLayer : null}
{variant === 'rainbow'
? flow({
colors: rainbowColors,
})
: null}
{props.children}
{id ? (
<button
@@ -84,8 +102,8 @@ export function Banner({
buttonVariants({
color: 'ghost',
className:
'absolute end-2 top-1/2 -translate-y-1/2 text-fd-muted-foreground',
size: 'icon',
'absolute end-2 top-1/2 -translate-y-1/2 text-fd-muted-foreground/50',
size: 'icon-sm',
}),
)}
>
@@ -99,50 +117,28 @@ export function Banner({
const maskImage =
'linear-gradient(to bottom,white,transparent), radial-gradient(circle at top center, white, transparent)';
const rainbowLayer = (
<>
<div
className="absolute inset-0 z-[-1]"
style={
{
maskImage,
maskComposite: 'intersect',
animation: 'fd-moving-banner 16s linear infinite',
'--start': 'rgba(0,87,255,0.5)',
'--mid': 'rgba(255,0,166,0.77)',
'--end': 'rgba(255,77,0,0.4)',
'--via': 'rgba(164,255,68,0.4)',
animationDirection: 'reverse',
backgroundImage:
'repeating-linear-gradient(60deg, var(--end), var(--start) 2%, var(--start) 5%, transparent 8%, transparent 14%, var(--via) 18%, var(--via) 22%, var(--mid) 28%, var(--mid) 30%, var(--via) 34%, var(--via) 36%, transparent, var(--end) calc(50% - 12px))',
backgroundSize: '200% 100%',
mixBlendMode: 'difference',
} as object
}
/>
<div
className="absolute inset-0 z-[-1]"
style={
{
maskImage,
maskComposite: 'intersect',
animation: 'fd-moving-banner 20s linear infinite',
'--start': 'rgba(255,120,120,0.5)',
'--mid': 'rgba(36,188,255,0.4)',
'--end': 'rgba(64,0,255,0.51)',
'--via': 'rgba(255,89,0,0.56)',
backgroundImage:
'repeating-linear-gradient(45deg, var(--end), var(--start) 4%, var(--start) 8%, transparent 9%, transparent 14%, var(--mid) 16%, var(--mid) 20%, transparent, var(--via) 36%, var(--via) 40%, transparent 42%, var(--end) 46%, var(--end) calc(50% - 16.8px))',
backgroundSize: '200% 100%',
mixBlendMode: 'color-dodge',
} as object
}
/>
<style>
{`@keyframes fd-moving-banner {
function flow({ colors }: { colors: string[] }) {
return (
<>
<div
className="absolute inset-0 z-[-1]"
style={
{
maskImage,
maskComposite: 'intersect',
animation: 'fd-moving-banner 20s linear infinite',
backgroundImage: `repeating-linear-gradient(70deg, ${[...colors, colors[0]].map((color, i) => `${color} ${(i * 50) / colors.length}%`).join(', ')})`,
backgroundSize: '200% 100%',
filter: 'saturate(2)',
} as object
}
/>
<style>
{`@keyframes fd-moving-banner {
from { background-position: 0% 0; }
to { background-position: 100% 0; }
}`}
</style>
</>
);
</style>
</>
);
}