turash/bugulma/frontend/components/ui/Tabs.tsx
Damir Mukimov 6347f42e20
Consolidate repositories: Remove nested frontend .git and merge into main repository
- Remove nested git repository from bugulma/frontend/.git
- Add all frontend files to main repository tracking
- Convert from separate frontend/backend repos to unified monorepo
- Preserve all frontend code and development history as tracked files
- Eliminate nested repository complexity for simpler development workflow

This creates a proper monorepo structure with frontend and backend
coexisting in the same repository for easier development and deployment.
2025-11-25 06:02:57 +01:00

144 lines
3.5 KiB
TypeScript

import React from 'react';
interface TabComponentProps {
activeValue: string;
onValueChange: (value: string) => void;
}
export const Tabs = ({
children,
value,
onValueChange,
className = '',
}: {
children?: React.ReactNode;
value: string;
onValueChange: (value: string) => void;
className?: string;
}) => {
return (
<div className={`tabs-container ${className}`}>
{React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child as React.ReactElement<TabComponentProps>, {
activeValue: value,
onValueChange,
});
}
return child;
})}
</div>
);
};
export const TabsList = ({
children,
className = '',
activeValue,
onValueChange,
}: {
children?: React.ReactNode;
className?: string;
activeValue?: string;
onValueChange?: (value: string) => void;
}) => {
const handleKeyDown = (e: React.KeyboardEvent) => {
const tabs = Array.from(e.currentTarget.querySelectorAll('[role="tab"]')) as HTMLElement[];
const currentIndex = tabs.findIndex((tab) => tab === e.target);
let newIndex = currentIndex;
if (e.key === 'ArrowLeft') {
newIndex = currentIndex > 0 ? currentIndex - 1 : tabs.length - 1;
} else if (e.key === 'ArrowRight') {
newIndex = currentIndex < tabs.length - 1 ? currentIndex + 1 : 0;
} else if (e.key === 'Home') {
newIndex = 0;
} else if (e.key === 'End') {
newIndex = tabs.length - 1;
}
if (newIndex !== currentIndex) {
e.preventDefault();
tabs[newIndex]?.focus();
tabs[newIndex]?.click();
}
};
return (
<div
role="tablist"
aria-label="Content tabs"
className={`flex border-b ${className}`}
onKeyDown={handleKeyDown}
>
{React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
const childProps = child.props as { value: string };
return React.cloneElement(child, {
isActive: childProps.value === activeValue,
onClick: () => onValueChange?.(childProps.value),
});
}
return child;
})}
</div>
);
};
export const TabsTrigger = ({
children,
value,
isActive,
onClick,
className = '',
}: {
value: string;
children?: React.ReactNode;
isActive?: boolean;
onClick?: () => void;
className?: string;
}) => {
return (
<button
type="button"
role="tab"
aria-selected={isActive}
aria-controls={`tabpanel-${value}`}
id={`tab-${value}`}
tabIndex={isActive ? 0 : -1}
onClick={onClick}
className={`px-4 py-2 text-sm font-medium -mb-px focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 ${isActive ? 'border-b-2 border-primary text-primary' : 'text-muted-foreground hover:text-foreground'} ${className}`}
>
{children}
</button>
);
};
TabsTrigger.displayName = 'TabsTrigger';
export const TabsContent = ({
children,
value,
className = '',
activeValue,
}: {
value: string;
children?: React.ReactNode;
className?: string;
activeValue?: string;
}) => {
const isHidden = value !== activeValue;
return (
<div
role="tabpanel"
id={`tabpanel-${value}`}
aria-labelledby={`tab-${value}`}
tabIndex={isHidden ? -1 : 0}
hidden={isHidden}
className={`py-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 ${className}`}
>
{children}
</div>
);
};
TabsContent.displayName = 'TabsContent';