Update UI to match consistent layout patterns between pages
- Refactor Add Stream page to match Teams page layout with glass panels - Rename "Add Stream" to "Streams" in navigation and page title - Add existing streams display with loading states and empty state - Implement unified design system with modern glass morphism styling - Add Header and Footer components with OBS status monitoring - Update global CSS with comprehensive component styling - Consolidate client components into main page files - Add real-time OBS connection status with 30-second polling 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
1d4b1eefba
commit
c28baa9e44
19 changed files with 2388 additions and 567 deletions
|
@ -19,11 +19,12 @@ export default function Dropdown({
|
|||
isOpen: controlledIsOpen,
|
||||
onToggle,
|
||||
}: DropdownProps) {
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const [isOpen, setIsOpen] = useState(controlledIsOpen ?? false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => { if (!dropdownRef.current || !(event.target instanceof Node)) return;
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (!dropdownRef.current || !(event.target instanceof Node)) return;
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
||||
if (onToggle) onToggle(false);
|
||||
else setIsOpen(false);
|
||||
|
@ -53,19 +54,19 @@ const dropdownRef = useRef<HTMLDivElement>(null);
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="relative inline-block text-left" ref={dropdownRef}>
|
||||
<div className="relative w-full" ref={dropdownRef}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleDropdown}
|
||||
className="inline-flex justify-between items-center w-full px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
className="dropdown-button"
|
||||
>
|
||||
{activeOption ? activeOption.name : label}
|
||||
<span>
|
||||
{activeOption ? activeOption.name : label}
|
||||
</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="ml-2 h-5 w-5"
|
||||
viewBox="0 0 20 20"
|
||||
className={`icon-sm transition-transform duration-200 ${(controlledIsOpen ?? isOpen) ? 'rotate-180' : ''}`}
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
|
@ -76,26 +77,24 @@ const dropdownRef = useRef<HTMLDivElement>(null);
|
|||
</button>
|
||||
|
||||
{(controlledIsOpen ?? isOpen) && (
|
||||
<div
|
||||
className="absolute right-0 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
style={{ zIndex: 50 }} // Set a high z-index value
|
||||
>
|
||||
<div className="py-1">
|
||||
{options.map((option) => (
|
||||
<button
|
||||
<div className="absolute z-50 w-full dropdown-menu">
|
||||
{options.length === 0 ? (
|
||||
<div className="dropdown-item text-center">
|
||||
No streams available
|
||||
</div>
|
||||
) : (
|
||||
options.map((option) => (
|
||||
<div
|
||||
key={option.id}
|
||||
type="button"
|
||||
onClick={() => handleSelect(option)}
|
||||
className={`block w-full px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 ${
|
||||
activeOption?.id === option.id ? 'font-bold text-blue-500' : ''
|
||||
}`}
|
||||
className={`dropdown-item ${activeOption?.id === option.id ? 'active' : ''}`}
|
||||
>
|
||||
{option.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue