mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-19 10:43:43 +08:00
- Upgrade CSS variables with proper glass transparency, fixed gradient backgrounds, cubic-bezier(0.2, 0.8, 0.2, 1) fluid transitions, and enhanced shadow/border values matching the Liquid Glass touchstone - Add full backdrop-filter: blur(25px) saturate(180%) to glass-card, glass-input, glass-popover, and glass-backdrop CSS classes - Add lensing inner-glow hover effects and toast-in animation - Update all UI components (Button, Card, Input, Badge, Switch, SegmentedControl, ConfirmDialog, ModalBackdrop, ModalHeader, BackToTop, Navbar) for strict Liquid Glass compliance - Enforce rounded-2xl / rounded-full consistency across all components - Bump version to 4.6.0, update @vercel/analytics to ^2.0.1 Co-Authored-By: Claude Opus 4.6 <[email protected]>
90 lines
3.0 KiB
TypeScript
90 lines
3.0 KiB
TypeScript
'use client';
|
|
|
|
import { useRef, useEffect, useState } from 'react';
|
|
|
|
/**
|
|
* SegmentedControl - A switch-style tab component following Liquid Glass design
|
|
*/
|
|
|
|
interface SegmentedControlProps<T extends string> {
|
|
options: { label: string; value: T }[];
|
|
value: T;
|
|
onChange: (value: T) => void;
|
|
className?: string;
|
|
}
|
|
|
|
export function SegmentedControl<T extends string>({
|
|
options,
|
|
value,
|
|
onChange,
|
|
className = '',
|
|
}: SegmentedControlProps<T>) {
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, width: 0 });
|
|
|
|
useEffect(() => {
|
|
const updateIndicator = () => {
|
|
if (!containerRef.current) return;
|
|
const activeElement = containerRef.current.querySelector(
|
|
`[data-value="${value}"]`
|
|
) as HTMLElement;
|
|
|
|
if (activeElement) {
|
|
setIndicatorStyle({
|
|
left: activeElement.offsetLeft,
|
|
width: activeElement.offsetWidth,
|
|
});
|
|
}
|
|
};
|
|
|
|
updateIndicator();
|
|
window.addEventListener('resize', updateIndicator);
|
|
return () => window.removeEventListener('resize', updateIndicator);
|
|
}, [value, options]);
|
|
|
|
return (
|
|
<div
|
|
ref={containerRef}
|
|
className={`
|
|
relative flex p-1
|
|
bg-[var(--glass-bg)]
|
|
backdrop-blur-[25px] saturate-[180%]
|
|
[-webkit-backdrop-filter:blur(25px)_saturate(180%)]
|
|
border border-[var(--glass-border)]
|
|
rounded-[var(--radius-2xl)]
|
|
shadow-[var(--shadow-sm)]
|
|
${className}
|
|
`}
|
|
>
|
|
{/* Sliding Indicator */}
|
|
<div
|
|
className="absolute top-1 bottom-1 bg-[var(--accent-color)] rounded-[calc(var(--radius-2xl)-4px)] shadow-[0_2px_8px_color-mix(in_srgb,var(--accent-color)_30%,transparent)] transition-all duration-300 [transition-timing-function:cubic-bezier(0.2,0.8,0.2,1)]"
|
|
style={{
|
|
left: `${indicatorStyle.left}px`,
|
|
width: `${indicatorStyle.width}px`,
|
|
}}
|
|
/>
|
|
|
|
{/* Segment Buttons */}
|
|
{options.map((option) => (
|
|
<button
|
|
key={option.value}
|
|
data-value={option.value}
|
|
onClick={() => onChange(option.value)}
|
|
className={`
|
|
relative z-10 flex-1 py-2 px-4
|
|
text-sm font-semibold
|
|
transition-colors duration-200
|
|
${value === option.value
|
|
? 'text-white'
|
|
: 'text-[var(--text-color-secondary)] hover:text-[var(--text-color)]'
|
|
}
|
|
`}
|
|
>
|
|
{option.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|