Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions src/components/Button/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -411,3 +411,200 @@ export const WithStates: Story = {
</div>
),
}

export const RainbowButtonZIndexFix: Story = {
render: () => (
<div className="relative z-50 space-y-4">
<Button variant="brand">Normal</Button>
</div>
),
}

export const BrandButtonInStackingContexts: Story = {
name: 'Brand Button in Various Stacking Contexts',
render: () => (
<div className="space-y-8 p-4">
<div>
<h3 className="mb-4 text-sm font-medium">
✅ Brand Button Working in All Contexts
</h3>
<p className="text-muted mb-6 text-sm">
The brand button now works correctly in all stacking contexts thanks
to built-in fixes.
</p>
</div>

<div className="space-y-6">
<div>
<h4 className="mb-3 text-sm font-medium">Flex Containers</h4>
<div className="space-y-3">
<div className="flex items-center gap-3 rounded border bg-white p-3">
<span>Navigation:</span>
<Button variant="brand" size="sm">
<Button.LeftIcon>
<PlusIcon />
</Button.LeftIcon>
<Button.Text>Add</Button.Text>
</Button>
</div>

<div className="flex min-h-8 max-w-full items-center gap-1.5 truncate rounded border bg-white p-2">
<a className="text-muted-foreground hover:text-foreground cursor-pointer rounded px-1.5">
Home
</a>
<span className="text-muted-foreground">/</span>
<a className="text-muted-foreground hover:text-foreground cursor-pointer rounded px-1.5">
Projects
</a>
<div className="ml-auto">
<Button variant="brand" size="sm">
<Button.LeftIcon>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="4,17 10,11 4,5"></polyline>
<line x1="12" x2="20" y1="19" y2="19"></line>
</svg>
</Button.LeftIcon>
<Button.Text>Generate</Button.Text>
</Button>
</div>
</div>
</div>
</div>

<div>
<h4 className="mb-3 text-sm font-medium">Grid Layouts</h4>
<div className="grid grid-cols-[1fr_auto] items-center gap-4 rounded border bg-white p-3">
<div>
<h5 className="font-medium">Project Settings</h5>
<p className="text-muted text-sm">Configure your project</p>
</div>
<Button variant="brand">
<Button.Text>Save Changes</Button.Text>
</Button>
</div>
</div>

<div>
<h4 className="mb-3 text-sm font-medium">High Z-Index Contexts</h4>
<div className="relative z-[9999] rounded border bg-white p-3">
<div className="flex items-center justify-between">
<span>Modal Header</span>
<Button variant="brand" size="sm">
<Button.Text>Action</Button.Text>
</Button>
</div>
</div>
</div>

<div>
<h4 className="mb-3 text-sm font-medium">Transform Contexts</h4>
<div className="scale-100 transform rounded border bg-white p-3">
<div className="flex items-center gap-3">
<span>Transformed container:</span>
<Button variant="brand" size="sm">
<Button.Text>Works!</Button.Text>
</Button>
</div>
</div>
</div>

<div>
<h4 className="mb-3 text-sm font-medium">
Overflow Hidden Containers
</h4>
<div className="overflow-hidden rounded border bg-white p-3">
<div className="flex items-center gap-3">
<span>Clipped container:</span>
<Button variant="brand" size="sm">
<Button.Text>Still works!</Button.Text>
</Button>
</div>
</div>
</div>

<div>
<h4 className="mb-3 text-sm font-medium">Complex Nested Layouts</h4>
<div className="bg-surface-secondary rounded-lg p-4">
<div className="bg-surface-primary rounded border">
<div className="flex items-center justify-between border-b p-4">
<div className="flex items-center gap-2">
<span className="font-medium">Complex Layout</span>
</div>
<Button variant="brand" size="sm">
<Button.LeftIcon>
<PlusIcon />
</Button.LeftIcon>
<Button.Text>Create</Button.Text>
</Button>
</div>
<div className="p-4 pb-6">
<div className="flex min-h-8 max-w-full items-center gap-1.5">
<a className="text-muted-foreground hover:text-foreground cursor-pointer rounded px-1.5">
Workspace
</a>
<span className="text-muted-foreground">/</span>
<a className="text-muted-foreground hover:text-foreground cursor-pointer rounded px-1.5">
Project
</a>
<span className="text-muted-foreground">/</span>
<span className="text-foreground">Current</span>
<div className="ml-auto">
<Button variant="brand" size="sm">
<Button.LeftIcon>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="4,17 10,11 4,5"></polyline>
<line x1="12" x2="20" y1="19" y2="19"></line>
</svg>
</Button.LeftIcon>
<Button.Text>Generate</Button.Text>
</Button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
),
parameters: {
docs: {
description: {
story: `
This story demonstrates the brand button working correctly in various stacking contexts that previously caused issues with the rainbow outline.

**Fixed Issues:**
- ✅ Flex containers with \`items-center\`
- ✅ Containers with \`overflow: hidden\`
- ✅ High z-index contexts
- ✅ Transform contexts
- ✅ Complex nested layouts
- ✅ Grid layouts

**Technical Solution:**
The component now uses \`transform: translateZ(0)\` to create an isolated stacking context and a separate background span element to ensure proper layering of the rainbow outline pseudo-elements.
`,
},
},
},
}
37 changes: 35 additions & 2 deletions src/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ const buttonVariants = cva(
},
variant: {
brand:
'relative bg-btn-brand hover:bg-btn-brand-hover text-btn-brand hover:text-btn-brand-hover disabled:bg-btn-brand-disabled disabled:text-btn-brand-disabled before:absolute before:content-[""] before:-z-10 before:pointer-events-none [--gradient-rotation:220deg] before:bg-[conic-gradient(from_var(--gradient-rotation),hsl(334,54%,13%),hsl(4,67%,47%),hsl(23,96%,62%),hsl(68,52%,72%),hsl(108,24%,41%),hsl(154,100%,7%),hsl(220,100%,12%),hsl(214,69%,50%),hsl(216,100%,80%),hsl(334,54%,13%))] after:absolute after:content-[""] after:-z-20 after:pointer-events-none after:opacity-0 after:transition-opacity after:duration-300 hover:after:opacity-100 after:bg-[conic-gradient(from_var(--gradient-rotation),hsl(334,54%,13%),hsl(4,67%,47%),hsl(23,96%,62%),hsl(68,52%,72%),hsl(108,24%,41%),hsl(154,100%,7%),hsl(220,100%,12%),hsl(214,69%,50%),hsl(216,100%,80%),hsl(334,54%,13%))] after:blur-[2px] focus-visible:ring-2 focus-visible:ring-offset-3 focus-visible:ring-[var(--border-focus)] focus-visible:ring-offset-[var(--bg-surface-primary-default)]',
'relative text-btn-brand hover:text-btn-brand-hover disabled:text-btn-brand-disabled [transform:translateZ(0)] before:absolute before:content-[""] before:-z-10 before:pointer-events-none [--gradient-rotation:220deg] before:bg-[conic-gradient(from_var(--gradient-rotation),hsl(334,54%,13%),hsl(4,67%,47%),hsl(23,96%,62%),hsl(68,52%,72%),hsl(108,24%,41%),hsl(154,100%,7%),hsl(220,100%,12%),hsl(214,69%,50%),hsl(216,100%,80%),hsl(334,54%,13%))] after:absolute after:content-[""] after:-z-20 after:pointer-events-none after:opacity-0 after:transition-opacity after:duration-300 hover:after:opacity-100 after:bg-[conic-gradient(from_var(--gradient-rotation),hsl(334,54%,13%),hsl(4,67%,47%),hsl(23,96%,62%),hsl(68,52%,72%),hsl(108,24%,41%),hsl(154,100%,7%),hsl(220,100%,12%),hsl(214,69%,50%),hsl(216,100%,80%),hsl(334,54%,13%))] after:blur-[2px] focus-visible:ring-2 focus-visible:ring-offset-3 focus-visible:ring-[var(--border-focus)] focus-visible:ring-offset-[var(--bg-surface-primary-default)]',
primary:
'bg-btn-primary text-btn-primary shadow-[0px_2px_1px_0px_rgba(255,255,255,0.1)_inset,0px_-2px_1px_0px_rgba(0,0,0,0.2)_inset] hover:bg-btn-primary-hover hover:text-btn-primary-hover hover:shadow-[0px_2px_1px_0px_rgba(255,255,255,0.08)_inset,0px_-2px_1px_0px_rgba(0,0,0,0.25)_inset] active:bg-btn-primary-active active:text-btn-primary-active active:shadow-none disabled:bg-btn-primary-disabled disabled:text-btn-primary-disabled',
secondary:
Expand Down Expand Up @@ -249,6 +249,22 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(

const isBrandVariant = variant === 'brand'

// Get gap class for the current size
const getGapClass = (size: ButtonSize) => {
switch (size) {
case 'xs':
return 'gap-1'
case 'sm':
return 'gap-1.5'
case 'md':
return 'gap-2'
case 'lg':
return 'gap-2.5'
default:
return 'gap-2'
}
}

// Only run animation frame when brand variant is active
useAnimationFrame(
React.useCallback(
Expand Down Expand Up @@ -430,7 +446,24 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
onMouseUp={handleMouseUp}
{...props}
>
{processedChildren}
{asChild ? (
props.children
) : (
<>
{isBrandVariant && (
<span className="bg-btn-brand hover:bg-btn-brand-hover disabled:bg-btn-brand-disabled pointer-events-none absolute inset-0 z-10 rounded-[inherit]" />
)}
<span
className={cn(
'relative flex items-center justify-center',
getGapClass(size),
isBrandVariant ? `z-20` : ''
)}
>
{processedChildren}
</span>
</>
)}
</Comp>
)
}
Expand Down
Loading