feat: github fix and ui improvements (#1685)
* feat: Add reusable UI components and fix GitHub repository display * style: Fix linting issues in UI components * fix: Add close icon to GitHub Connection Required dialog * fix: Add CloseButton component to fix white background issue in dialog close icons * Fix close button styling in dialog components to address ghost white issue in dark mode * fix: update icon color to tertiary for consistency The icon color was changed from `text-bolt-elements-icon-info` to `text-bolt-elements-icon-tertiary` * fix: improve repository selection dialog tab styling for dark mode - Update tab menu styling to prevent white background in dark mode - Use explicit color values for better dark/light mode compatibility - Improve hover and active states for better visual hierarchy - Remove unused Tabs imports --------- Co-authored-by: KevIsDev <zennerd404@gmail.com>
This commit is contained in:
112
app/components/ui/TabsWithSlider.tsx
Normal file
112
app/components/ui/TabsWithSlider.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
|
||||
interface Tab {
|
||||
/** Unique identifier for the tab */
|
||||
id: string;
|
||||
|
||||
/** Content to display in the tab */
|
||||
label: React.ReactNode;
|
||||
|
||||
/** Optional icon to display before the label */
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
interface TabsWithSliderProps {
|
||||
/** Array of tab objects */
|
||||
tabs: Tab[];
|
||||
|
||||
/** ID of the currently active tab */
|
||||
activeTab: string;
|
||||
|
||||
/** Function called when a tab is clicked */
|
||||
onChange: (tabId: string) => void;
|
||||
|
||||
/** Additional class name for the container */
|
||||
className?: string;
|
||||
|
||||
/** Additional class name for inactive tabs */
|
||||
tabClassName?: string;
|
||||
|
||||
/** Additional class name for the active tab */
|
||||
activeTabClassName?: string;
|
||||
|
||||
/** Additional class name for the slider */
|
||||
sliderClassName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* TabsWithSlider component
|
||||
*
|
||||
* A tabs component with an animated slider that moves to the active tab.
|
||||
*/
|
||||
export function TabsWithSlider({
|
||||
tabs,
|
||||
activeTab,
|
||||
onChange,
|
||||
className,
|
||||
tabClassName,
|
||||
activeTabClassName,
|
||||
sliderClassName,
|
||||
}: TabsWithSliderProps) {
|
||||
// State for slider dimensions
|
||||
const [sliderDimensions, setSliderDimensions] = useState({ width: 0, left: 0 });
|
||||
|
||||
// Refs for tab elements
|
||||
const tabsRef = useRef<(HTMLButtonElement | null)[]>([]);
|
||||
|
||||
// Update slider position when active tab changes
|
||||
useEffect(() => {
|
||||
const activeIndex = tabs.findIndex((tab) => tab.id === activeTab);
|
||||
|
||||
if (activeIndex !== -1 && tabsRef.current[activeIndex]) {
|
||||
const activeTabElement = tabsRef.current[activeIndex];
|
||||
|
||||
if (activeTabElement) {
|
||||
setSliderDimensions({
|
||||
width: activeTabElement.offsetWidth,
|
||||
left: activeTabElement.offsetLeft,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [activeTab, tabs]);
|
||||
|
||||
return (
|
||||
<div className={classNames('relative flex gap-2', className)}>
|
||||
{/* Tab buttons */}
|
||||
{tabs.map((tab, index) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
ref={(el) => (tabsRef.current[index] = el)}
|
||||
onClick={() => onChange(tab.id)}
|
||||
className={classNames(
|
||||
'px-4 py-2 h-10 rounded-lg transition-all duration-200 flex items-center gap-2 min-w-[120px] justify-center relative overflow-hidden',
|
||||
tab.id === activeTab
|
||||
? classNames('text-white shadow-sm shadow-purple-500/20', activeTabClassName)
|
||||
: classNames(
|
||||
'bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary-dark hover:bg-bolt-elements-background-depth-3 dark:hover:bg-bolt-elements-background-depth-4 border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor-dark',
|
||||
tabClassName,
|
||||
),
|
||||
)}
|
||||
>
|
||||
<span className={classNames('flex items-center gap-2', tab.id === activeTab ? 'font-medium' : '')}>
|
||||
{tab.icon && <span className={tab.icon} />}
|
||||
{tab.label}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
|
||||
{/* Animated slider */}
|
||||
<motion.div
|
||||
className={classNames('absolute bottom-0 left-0 h-10 rounded-lg bg-purple-500 -z-10', sliderClassName)}
|
||||
initial={false}
|
||||
animate={{
|
||||
width: sliderDimensions.width,
|
||||
x: sliderDimensions.left,
|
||||
}}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user