mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-30 09:03:15 +00:00
feat: agents route replacing chatbots
- Removed API Keys tab from SettingsBar and adjusted tab layout. - Improved styling for tab scrolling buttons and gradient indicators. - Introduced AgentDetailsModal for displaying agent access details. - Updated Analytics component to fetch agent data and handle analytics for selected agent. - Refactored Logs component to accept agentId as a prop for filtering logs. - Enhanced type definitions for InputProps to include textSize. - Cleaned up unused imports and optimized component structure across various files.
This commit is contained in:
@@ -90,7 +90,7 @@ export default function ContextMenu({
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div
|
||||
className="flex w-32 flex-col rounded-xl text-sm shadow-xl md:w-36 dark:bg-charleston-green-2 bg-lotion"
|
||||
className="flex w-32 flex-col rounded-xl bg-lotion text-sm shadow-xl dark:bg-charleston-green-2 md:w-36"
|
||||
style={{ minWidth: '144px' }}
|
||||
>
|
||||
{options.map((option, index) => (
|
||||
@@ -102,26 +102,22 @@ export default function ContextMenu({
|
||||
option.onClick(event);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
className={`
|
||||
flex justify-start items-center gap-4 p-3
|
||||
transition-colors duration-200 ease-in-out
|
||||
${index === 0 ? 'rounded-t-xl' : ''}
|
||||
${index === options.length - 1 ? 'rounded-b-xl' : ''}
|
||||
${
|
||||
option.variant === 'danger'
|
||||
? 'dark:text-red-2000 dark:hover:bg-charcoal-grey text-rosso-corsa hover:bg-bright-gray'
|
||||
: 'dark:text-bright-gray dark:hover:bg-charcoal-grey text-eerie-black hover:bg-bright-gray'
|
||||
}
|
||||
`}
|
||||
className={`flex items-center justify-start gap-4 p-3 transition-colors duration-200 ease-in-out ${index === 0 ? 'rounded-t-xl' : ''} ${index === options.length - 1 ? 'rounded-b-xl' : ''} ${
|
||||
option.variant === 'danger'
|
||||
? 'text-rosso-corsa hover:bg-bright-gray dark:text-red-2000 dark:hover:bg-charcoal-grey'
|
||||
: 'text-eerie-black hover:bg-bright-gray dark:text-bright-gray dark:hover:bg-charcoal-grey'
|
||||
} `}
|
||||
>
|
||||
{option.icon && (
|
||||
<img
|
||||
width={option.iconWidth || 16}
|
||||
height={option.iconHeight || 16}
|
||||
src={option.icon}
|
||||
alt={option.label}
|
||||
className={`cursor-pointer hover:opacity-75 ${option.iconClassName || ''}`}
|
||||
/>
|
||||
<div className="flex w-4 justify-center">
|
||||
<img
|
||||
width={option.iconWidth || 16}
|
||||
height={option.iconHeight || 16}
|
||||
src={option.icon}
|
||||
alt={option.label}
|
||||
className={`cursor-pointer hover:opacity-75 ${option.iconClassName || ''}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<span>{option.label}</span>
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import Arrow2 from '../assets/dropdown-arrow.svg';
|
||||
import Edit from '../assets/edit.svg';
|
||||
import Trash from '../assets/trash.svg';
|
||||
@@ -9,6 +10,10 @@ function Dropdown({
|
||||
onSelect,
|
||||
size = 'w-32',
|
||||
rounded = 'xl',
|
||||
buttonBackgroundColor = 'white',
|
||||
buttonDarkBackgroundColor = 'transparent',
|
||||
optionsBackgroundColor = 'white',
|
||||
optionsDarkBackgroundColor = 'dark-charcoal',
|
||||
border = 'border-2',
|
||||
borderColor = 'silver',
|
||||
darkBorderColor = 'dim-gray',
|
||||
@@ -17,6 +22,8 @@ function Dropdown({
|
||||
showDelete,
|
||||
onDelete,
|
||||
placeholder,
|
||||
placeholderTextColor = 'gray-500',
|
||||
darkPlaceholderTextColor = 'gray-400',
|
||||
contentSize = 'text-base',
|
||||
}: {
|
||||
options:
|
||||
@@ -37,6 +44,10 @@ function Dropdown({
|
||||
| ((value: { value: number; description: string }) => void);
|
||||
size?: string;
|
||||
rounded?: 'xl' | '3xl';
|
||||
buttonBackgroundColor?: string;
|
||||
buttonDarkBackgroundColor?: string;
|
||||
optionsBackgroundColor?: string;
|
||||
optionsDarkBackgroundColor?: string;
|
||||
border?: 'border' | 'border-2';
|
||||
borderColor?: string;
|
||||
darkBorderColor?: string;
|
||||
@@ -45,6 +56,8 @@ function Dropdown({
|
||||
showDelete?: boolean;
|
||||
onDelete?: (value: string) => void;
|
||||
placeholder?: string;
|
||||
placeholderTextColor?: string;
|
||||
darkPlaceholderTextColor?: string;
|
||||
contentSize?: string;
|
||||
}) {
|
||||
const dropdownRef = React.useRef<HTMLDivElement>(null);
|
||||
@@ -71,7 +84,7 @@ function Dropdown({
|
||||
<div
|
||||
className={[
|
||||
typeof selectedValue === 'string'
|
||||
? 'relative mt-2'
|
||||
? 'relative'
|
||||
: 'relative align-middle',
|
||||
size,
|
||||
].join(' ')}
|
||||
@@ -79,7 +92,7 @@ function Dropdown({
|
||||
>
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className={`flex w-full cursor-pointer items-center justify-between ${border} border-${borderColor} bg-white px-5 py-3 dark:border-${darkBorderColor} dark:bg-transparent ${
|
||||
className={`flex w-full cursor-pointer items-center justify-between ${border} border-${borderColor} bg-${buttonBackgroundColor} px-5 py-3 dark:border-${darkBorderColor} dark:bg-${buttonDarkBackgroundColor} ${
|
||||
isOpen ? `${borderTopRadius}` : `${borderRadius}`
|
||||
}`}
|
||||
>
|
||||
@@ -89,8 +102,9 @@ function Dropdown({
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className={`truncate dark:text-bright-gray ${
|
||||
!selectedValue && 'text-silver dark:text-gray-400'
|
||||
className={`truncate ${selectedValue && `dark:text-bright-gray`} ${
|
||||
!selectedValue &&
|
||||
`text-${placeholderTextColor} dark:text-${darkPlaceholderTextColor}`
|
||||
} ${contentSize}`}
|
||||
>
|
||||
{selectedValue && 'label' in selectedValue
|
||||
@@ -116,7 +130,7 @@ function Dropdown({
|
||||
</button>
|
||||
{isOpen && (
|
||||
<div
|
||||
className={`absolute left-0 right-0 z-20 -mt-1 max-h-40 overflow-y-auto rounded-b-xl ${border} border-${borderColor} bg-white shadow-lg dark:border-${darkBorderColor} dark:bg-dark-charcoal`}
|
||||
className={`absolute left-0 right-0 z-20 -mt-1 max-h-40 overflow-y-auto rounded-b-xl ${border} border-${borderColor} bg-${optionsBackgroundColor} shadow-lg dark:border-${darkBorderColor} dark:bg-${optionsDarkBackgroundColor}`}
|
||||
>
|
||||
{options.map((option: any, index) => (
|
||||
<div
|
||||
|
||||
@@ -13,6 +13,7 @@ const Input = ({
|
||||
className = '',
|
||||
colorVariant = 'silver',
|
||||
borderVariant = 'thick',
|
||||
textSize = 'medium',
|
||||
children,
|
||||
labelBgClassName = 'bg-white dark:bg-raisin-black',
|
||||
onChange,
|
||||
@@ -28,6 +29,10 @@ const Input = ({
|
||||
thin: 'border',
|
||||
thick: 'border-2',
|
||||
};
|
||||
const textSizeStyles = {
|
||||
small: 'text-sm',
|
||||
medium: 'text-base',
|
||||
};
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
@@ -35,15 +40,7 @@ const Input = ({
|
||||
<div className={`relative ${className}`}>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={`peer h-[42px] w-full rounded-full px-3 py-1
|
||||
bg-transparent outline-none
|
||||
text-jet dark:text-bright-gray
|
||||
placeholder-transparent
|
||||
${colorStyles[colorVariant]}
|
||||
${borderStyles[borderVariant]}
|
||||
[&:-webkit-autofill]:bg-transparent
|
||||
[&:-webkit-autofill]:appearance-none
|
||||
[&:-webkit-autofill_selected]:bg-transparent`}
|
||||
className={`peer h-[42px] w-full rounded-full bg-transparent px-3 py-1 text-jet placeholder-transparent outline-none dark:text-bright-gray ${colorStyles[colorVariant]} ${borderStyles[borderVariant]} ${textSizeStyles[textSize]} [&:-webkit-autofill]:appearance-none [&:-webkit-autofill]:bg-transparent [&:-webkit-autofill_selected]:bg-transparent`}
|
||||
type={type}
|
||||
id={id}
|
||||
name={name}
|
||||
@@ -61,15 +58,11 @@ const Input = ({
|
||||
{placeholder && (
|
||||
<label
|
||||
htmlFor={id}
|
||||
className={`absolute left-3 -top-2.5 px-2 text-xs transition-all
|
||||
peer-placeholder-shown:top-2.5 peer-placeholder-shown:left-3 peer-placeholder-shown:text-base
|
||||
peer-placeholder-shown:text-gray-4000 peer-focus:-top-2.5 peer-focus:left-3 peer-focus:text-xs
|
||||
peer-focus:text-gray-4000 dark:text-silver dark:peer-placeholder-shown:text-gray-400
|
||||
cursor-none pointer-events-none ${labelBgClassName}`}
|
||||
className={`absolute -top-2.5 left-3 px-2 ${textSizeStyles[textSize]} transition-all peer-placeholder-shown:left-3 peer-placeholder-shown:top-2.5 peer-placeholder-shown:${textSizeStyles[textSize]} pointer-events-none cursor-none peer-placeholder-shown:text-gray-4000 peer-focus:-top-2.5 peer-focus:left-3 peer-focus:text-xs peer-focus:text-gray-4000 dark:text-silver dark:peer-placeholder-shown:text-gray-400 ${labelBgClassName}`}
|
||||
>
|
||||
{placeholder}
|
||||
{required && (
|
||||
<span className="text-[#D30000] dark:text-[#D42626] ml-0.5">*</span>
|
||||
<span className="ml-0.5 text-[#D30000] dark:text-[#D42626]">*</span>
|
||||
)}
|
||||
</label>
|
||||
)}
|
||||
|
||||
278
frontend/src/components/MultiSelectPopup.tsx
Normal file
278
frontend/src/components/MultiSelectPopup.tsx
Normal file
@@ -0,0 +1,278 @@
|
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import CheckmarkIcon from '../assets/checkmark.svg';
|
||||
import NoFilesDarkIcon from '../assets/no-files-dark.svg';
|
||||
import NoFilesIcon from '../assets/no-files.svg';
|
||||
import { useDarkTheme } from '../hooks';
|
||||
import Input from './Input';
|
||||
|
||||
export type OptionType = {
|
||||
id: string | number;
|
||||
label: string;
|
||||
icon?: string | React.ReactNode;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
type MultiSelectPopupProps = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
anchorRef: React.RefObject<HTMLElement>;
|
||||
options: OptionType[];
|
||||
selectedIds: Set<string | number>;
|
||||
onSelectionChange: (newSelectedIds: Set<string | number>) => void;
|
||||
title?: string;
|
||||
searchPlaceholder?: string;
|
||||
noOptionsMessage?: string;
|
||||
loading?: boolean;
|
||||
footerContent?: React.ReactNode;
|
||||
showSearch?: boolean;
|
||||
singleSelect?: boolean;
|
||||
};
|
||||
|
||||
export default function MultiSelectPopup({
|
||||
isOpen,
|
||||
onClose,
|
||||
anchorRef,
|
||||
options,
|
||||
selectedIds,
|
||||
onSelectionChange,
|
||||
title,
|
||||
searchPlaceholder,
|
||||
noOptionsMessage,
|
||||
loading = false,
|
||||
footerContent,
|
||||
showSearch = true,
|
||||
singleSelect = false,
|
||||
}: MultiSelectPopupProps) {
|
||||
const { t } = useTranslation();
|
||||
const [isDarkTheme] = useDarkTheme();
|
||||
|
||||
const popupRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [popupPosition, setPopupPosition] = useState({
|
||||
top: 0,
|
||||
left: 0,
|
||||
maxHeight: 0,
|
||||
showAbove: false,
|
||||
});
|
||||
|
||||
const filteredOptions = options.filter((option) =>
|
||||
option.label.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
);
|
||||
|
||||
const handleOptionClick = (optionId: string | number) => {
|
||||
let newSelectedIds: Set<string | number>;
|
||||
if (singleSelect) newSelectedIds = new Set<string | number>();
|
||||
else newSelectedIds = new Set(selectedIds);
|
||||
if (newSelectedIds.has(optionId)) {
|
||||
newSelectedIds.delete(optionId);
|
||||
} else newSelectedIds.add(optionId);
|
||||
onSelectionChange(newSelectedIds);
|
||||
};
|
||||
|
||||
const renderIcon = (icon: string | React.ReactNode) => {
|
||||
if (typeof icon === 'string') {
|
||||
if (icon.startsWith('/') || icon.startsWith('http')) {
|
||||
return (
|
||||
<img
|
||||
src={icon}
|
||||
alt=""
|
||||
className="mr-3 h-5 w-5 flex-shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="mr-3 h-5 w-5 flex-shrink-0" aria-hidden="true">
|
||||
{icon}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return <span className="mr-3 flex-shrink-0">{icon}</span>;
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!isOpen || !anchorRef.current) return;
|
||||
|
||||
const updatePosition = () => {
|
||||
if (!anchorRef.current) return;
|
||||
|
||||
const rect = anchorRef.current.getBoundingClientRect();
|
||||
const viewportHeight = window.innerHeight;
|
||||
const viewportWidth = window.innerWidth;
|
||||
const popupPadding = 16;
|
||||
const popupMinWidth = 300;
|
||||
const popupMaxWidth = 462;
|
||||
const popupDefaultHeight = 300;
|
||||
|
||||
const spaceAbove = rect.top;
|
||||
const spaceBelow = viewportHeight - rect.bottom;
|
||||
const showAbove =
|
||||
spaceBelow < popupDefaultHeight && spaceAbove >= popupDefaultHeight;
|
||||
|
||||
const maxHeight = Math.max(
|
||||
150,
|
||||
showAbove ? spaceAbove - popupPadding : spaceBelow - popupPadding,
|
||||
);
|
||||
|
||||
const availableWidth = viewportWidth - 20;
|
||||
const calculatedWidth = Math.min(popupMaxWidth, availableWidth);
|
||||
|
||||
let left = rect.left;
|
||||
if (left + calculatedWidth > viewportWidth - 10) {
|
||||
left = viewportWidth - calculatedWidth - 10;
|
||||
}
|
||||
left = Math.max(10, left);
|
||||
|
||||
setPopupPosition({
|
||||
top: showAbove ? rect.top - 8 : rect.bottom + 8,
|
||||
left: left,
|
||||
maxHeight: Math.min(600, maxHeight),
|
||||
showAbove,
|
||||
});
|
||||
};
|
||||
|
||||
updatePosition();
|
||||
window.addEventListener('resize', updatePosition);
|
||||
window.addEventListener('scroll', updatePosition, true);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', updatePosition);
|
||||
window.removeEventListener('scroll', updatePosition, true);
|
||||
};
|
||||
}, [isOpen, anchorRef]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
popupRef.current &&
|
||||
!popupRef.current.contains(event.target as Node) &&
|
||||
anchorRef.current &&
|
||||
!anchorRef.current.contains(event.target as Node)
|
||||
)
|
||||
onClose();
|
||||
};
|
||||
if (isOpen) document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [onClose, anchorRef, isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) setSearchTerm('');
|
||||
}, [isOpen]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
return (
|
||||
<div
|
||||
ref={popupRef}
|
||||
className="fixed z-[9999] flex flex-col rounded-lg border border-light-silver bg-lotion shadow-[0px_9px_46px_8px_#0000001F,0px_24px_38px_3px_#00000024,0px_11px_15px_-7px_#00000033] dark:border-dim-gray dark:bg-charleston-green-2"
|
||||
style={{
|
||||
top: popupPosition.showAbove ? undefined : popupPosition.top,
|
||||
bottom: popupPosition.showAbove
|
||||
? window.innerHeight - popupPosition.top + 8
|
||||
: undefined,
|
||||
left: popupPosition.left,
|
||||
maxWidth: `${Math.min(462, window.innerWidth - 20)}px`,
|
||||
width: '100%',
|
||||
maxHeight: `${popupPosition.maxHeight}px`,
|
||||
}}
|
||||
>
|
||||
{(title || showSearch) && (
|
||||
<div className="flex-shrink-0 p-4">
|
||||
{title && (
|
||||
<h3 className="mb-4 text-lg font-medium text-gray-900 dark:text-white">
|
||||
{title}
|
||||
</h3>
|
||||
)}
|
||||
{showSearch && (
|
||||
<Input
|
||||
id="multi-select-search"
|
||||
name="multi-select-search"
|
||||
type="text"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder={
|
||||
searchPlaceholder ||
|
||||
t('settings.tools.searchPlaceholder', 'Search...')
|
||||
}
|
||||
labelBgClassName="bg-lotion dark:bg-charleston-green-2"
|
||||
borderVariant="thin"
|
||||
className="mb-4"
|
||||
textSize="small"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="mx-4 mb-4 flex-grow overflow-auto rounded-md border border-[#D9D9D9] dark:border-dim-gray">
|
||||
{loading ? (
|
||||
<div className="flex h-full items-center justify-center py-4">
|
||||
<div className="h-6 w-6 animate-spin rounded-full border-b-2 border-gray-900 dark:border-white"></div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-full overflow-y-auto [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-gray-400 dark:[&::-webkit-scrollbar-thumb]:bg-gray-600 [&::-webkit-scrollbar-track]:bg-gray-200 dark:[&::-webkit-scrollbar-track]:bg-[#2C2E3C] [&::-webkit-scrollbar]:w-2">
|
||||
{filteredOptions.length === 0 ? (
|
||||
<div className="flex h-full flex-col items-center justify-center px-4 py-8 text-center">
|
||||
<img
|
||||
src={isDarkTheme ? NoFilesDarkIcon : NoFilesIcon}
|
||||
alt="No options found"
|
||||
className="mx-auto mb-3 h-16 w-16"
|
||||
/>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{searchTerm
|
||||
? 'No results found'
|
||||
: noOptionsMessage || 'No options available'}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
filteredOptions.map((option) => {
|
||||
const isSelected = selectedIds.has(option.id);
|
||||
return (
|
||||
<div
|
||||
key={option.id}
|
||||
onClick={() => handleOptionClick(option.id)}
|
||||
className="flex cursor-pointer items-center justify-between border-b border-[#D9D9D9] p-3 last:border-b-0 hover:bg-gray-100 dark:border-dim-gray dark:hover:bg-charleston-green-3"
|
||||
role="option"
|
||||
aria-selected={isSelected}
|
||||
>
|
||||
<div className="mr-3 flex flex-grow items-center overflow-hidden">
|
||||
{option.icon && renderIcon(option.icon)}
|
||||
<p
|
||||
className="overflow-hidden overflow-ellipsis whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white"
|
||||
title={option.label}
|
||||
>
|
||||
{option.label}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<div
|
||||
className={`flex h-4 w-4 items-center justify-center rounded-sm border border-[#C6C6C6] bg-white dark:border-[#757783] dark:bg-charleston-green-2`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{isSelected && (
|
||||
<img
|
||||
src={CheckmarkIcon}
|
||||
alt="checkmark"
|
||||
width={10}
|
||||
height={10}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{footerContent && (
|
||||
<div className="flex-shrink-0 border-t border-light-silver p-4 dark:border-dim-gray">
|
||||
{footerContent}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ArrowLeft from '../assets/arrow-left.svg';
|
||||
import ArrowRight from '../assets/arrow-right.svg';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type HiddenGradientType = 'left' | 'right' | undefined;
|
||||
|
||||
@@ -10,7 +11,6 @@ const useTabs = () => {
|
||||
const tabs = [
|
||||
t('settings.general.label'),
|
||||
t('settings.documents.label'),
|
||||
t('settings.apiKeys.label'),
|
||||
t('settings.analytics.label'),
|
||||
t('settings.logs.label'),
|
||||
t('settings.tools.label'),
|
||||
@@ -48,18 +48,18 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||
[containerRef.current],
|
||||
);
|
||||
return (
|
||||
<div className="relative mt-6 flex flex-row items-center space-x-1 md:space-x-0 overflow-auto">
|
||||
<div className="relative mt-6 flex flex-row items-center space-x-1 overflow-auto md:space-x-0">
|
||||
<div
|
||||
className={`${hiddenGradient === 'left' ? 'hidden' : ''} md:hidden absolute inset-y-0 left-6 w-14 bg-gradient-to-r from-white dark:from-raisin-black pointer-events-none`}
|
||||
className={`${hiddenGradient === 'left' ? 'hidden' : ''} pointer-events-none absolute inset-y-0 left-6 w-14 bg-gradient-to-r from-white dark:from-raisin-black md:hidden`}
|
||||
></div>
|
||||
<div
|
||||
className={`${hiddenGradient === 'right' ? 'hidden' : ''} md:hidden absolute inset-y-0 right-6 w-14 bg-gradient-to-l from-white dark:from-raisin-black pointer-events-none`}
|
||||
className={`${hiddenGradient === 'right' ? 'hidden' : ''} pointer-events-none absolute inset-y-0 right-6 w-14 bg-gradient-to-l from-white dark:from-raisin-black md:hidden`}
|
||||
></div>
|
||||
|
||||
<div className="md:hidden z-10">
|
||||
<div className="z-10 md:hidden">
|
||||
<button
|
||||
onClick={() => scrollTabs(-1)}
|
||||
className="flex h-6 w-6 items-center rounded-full justify-center transition-all hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-full transition-all hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
aria-label="Scroll tabs left"
|
||||
>
|
||||
<img src={ArrowLeft} alt="left-arrow" className="h-3" />
|
||||
@@ -67,7 +67,7 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||
</div>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="flex flex-nowrap overflow-x-auto no-scrollbar md:space-x-4 scroll-smooth snap-x"
|
||||
className="no-scrollbar flex snap-x flex-nowrap overflow-x-auto scroll-smooth md:space-x-4"
|
||||
role="tablist"
|
||||
aria-label="Settings tabs"
|
||||
>
|
||||
@@ -75,7 +75,7 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
className={`snap-start h-9 rounded-3xl px-4 font-bold transition-colors ${
|
||||
className={`h-9 snap-start rounded-3xl px-4 font-bold transition-colors ${
|
||||
activeTab === tab
|
||||
? 'bg-[#F4F4F5] text-neutral-900 dark:bg-dark-charcoal dark:text-white'
|
||||
: 'text-neutral-700 hover:text-neutral-900 dark:text-neutral-300 dark:hover:text-white'
|
||||
@@ -89,10 +89,10 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="md:hidden z-10">
|
||||
<div className="z-10 md:hidden">
|
||||
<button
|
||||
onClick={() => scrollTabs(1)}
|
||||
className="flex h-6 w-6 rounded-full items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-full hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
aria-label="Scroll tabs right"
|
||||
>
|
||||
<img src={ArrowRight} alt="right-arrow" className="h-3" />
|
||||
|
||||
@@ -3,6 +3,7 @@ export type InputProps = {
|
||||
value: string | string[] | number;
|
||||
colorVariant?: 'silver' | 'jet' | 'gray';
|
||||
borderVariant?: 'thin' | 'thick';
|
||||
textSize?: 'small' | 'medium';
|
||||
isAutoFocused?: boolean;
|
||||
id?: string;
|
||||
maxLength?: number;
|
||||
|
||||
Reference in New Issue
Block a user