Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { cva, VariantProps } from 'class-variance-authority';
import * as React from 'react';
import { cn } from '../utils';

const variants = cva('btn', {
const ButtonVariants = cva('btn', {
variants: {
variant: {
default: '',
Expand All @@ -15,8 +15,9 @@ const variants = cva('btn', {
default: '',
small: 'btn-sm',
icon: 'w-8 h-8 p-0',
'icon-rounded': 'w-8 h-8 p-0 rounded-full',
'icon-small': 'btn-sm w-4 h-4 p-0 rounded-full',
'icon-sm': 'btn-sm w-4 h-4 p-0 rounded-full',
'icon-md': 'btn-sm w-5 h-5 p-0 rounded-full',
'icon-xl': 'w-8 h-8 p-0 rounded-full',
},
},
defaultVariants: {
Expand All @@ -26,13 +27,13 @@ const variants = cva('btn', {
});

export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
VariantProps<typeof variants>;
VariantProps<typeof ButtonVariants>;

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => (
<button
type="button"
className={cn(variants({ variant, size, className }))}
className={cn(ButtonVariants({ variant, size, className }))}
ref={ref}
dir="auto"
{...props}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export default function Header() {
{/* new conversation button */}
<Button
variant="ghost"
size="icon-rounded"
size="icon-xl"
onClick={() => navigate('/')}
title={t('header.buttons.newConv')}
aria-label={t('header.ariaLabels.newConv')}
Expand Down Expand Up @@ -122,7 +122,7 @@ export default function Header() {
<div className="flex items-center">
<Button
variant="ghost"
size="icon-rounded"
size="icon-xl"
className="max-xl:hidden"
title={t('header.buttons.settings')}
aria-label={t('header.ariaLabels.settings')}
Expand Down
1 change: 1 addition & 0 deletions src/components/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const iconVariants = cva('', {
},
defaultVariants: {
library: 'lucide',
size: 'sm',
},
});

Expand Down
12 changes: 6 additions & 6 deletions src/components/Label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@ import { cva, VariantProps } from 'class-variance-authority';
import * as React from 'react';
import { cn } from '../utils';

const variants = cva('', {
const LabelVariants = cva('', {
variants: {
variant: {
default: '',
'group-title': 'block font-bold text-base-content text-start',
'fake-btn': 'text-center cursor-pointer',
btn: 'btn',
'btn-ghost': 'btn btn-ghost',
'form-control': 'form-control flex flex-col justify-center mb-3',
'form-control': 'form-control flex flex-col justify-center',
'input-bordered':
'input input-bordered join-item grow flex items-center gap-2 mb-1',
'input input-bordered join-item grow flex items-center gap-2 focus-within:outline-1 focus-within:outline-offset-0',
},
size: {
default: '',
xs: 'text-xs',
icon: 'w-8 h-8 p-0',
'icon-rounded': 'w-8 h-8 p-0 rounded-full',
'icon-xl': 'w-8 h-8 p-0 rounded-full',
},
},
defaultVariants: {
Expand All @@ -28,12 +28,12 @@ const variants = cva('', {
});

export type LabelProps = React.LabelHTMLAttributes<HTMLLabelElement> &
VariantProps<typeof variants>;
VariantProps<typeof LabelVariants>;

const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
({ className, variant, size, ...props }, ref) => (
<label
className={cn(variants({ variant, size, className }))}
className={cn(LabelVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
Expand Down
82 changes: 69 additions & 13 deletions src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import toast from 'react-hot-toast';
import { Trans, useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router';
import IndexedDB from '../database/indexedDB';
import useFilter from '../hooks/useFilter';
import { useChatContext } from '../store/chat';
import { useModals } from '../store/modal';
import { Conversation } from '../types';
import { classNames } from '../utils';
import { downloadAsFile } from '../utils/downloadAsFile';
import { Button } from './Button';
import { Icon } from './Icon';
import { Input } from './Input';
import { Label } from './Label';

export default function Sidebar() {
Expand All @@ -19,6 +21,14 @@ export default function Sidebar() {

const [conversations, setConversations] = useState<Conversation[]>([]);

const {
filteredData: filteredConversations,
setFilter,
resetFilter,
searchTerm,
isFiltered,
} = useFilter(conversations);

useEffect(() => {
const handleConversationChange = async () => {
setConversations(await IndexedDB.getAllConversations());
Expand All @@ -44,7 +54,7 @@ export default function Sidebar() {

return (
<>
<input
<Input
id="toggle-drawer"
type="checkbox"
className="drawer-toggle"
Expand All @@ -58,14 +68,14 @@ export default function Sidebar() {
aria-label="Sidebar"
tabIndex={0}
>
<div className="flex flex-col bg-base-300 h-full min-h-0 max-w-full xl:w-72 pb-4 px-4 xl:pl-2 xl:pr-0">
<div className="flex flex-col bg-base-300 h-full min-h-0 max-w-full w-96 xl:w-72 pb-4 px-4 xl:pl-2 xl:pr-0 shadow-xl/50">
<div className="flex flex-row items-center justify-between xl:py-2">
{/* close sidebar button */}
<Label size="icon" className="max-xl:hidden" />
<Label
className="xl:hidden"
variant="btn-ghost"
size="icon-rounded"
size="icon-xl"
htmlFor="toggle-drawer"
role="button"
title={t('sidebar.buttons.closeSideBar')}
Expand All @@ -88,7 +98,7 @@ export default function Sidebar() {
{/* new conversation button */}
<Button
variant="ghost"
size="icon-rounded"
size="icon-xl"
onClick={() => navigate('/')}
title={t('header.buttons.newConv')}
aria-label={t('header.ariaLabels.newConv')}
Expand All @@ -97,15 +107,59 @@ export default function Sidebar() {
</Button>
</div>

{/* search conversation */}
<div className="flex max-xl:mt-2 xl:px-2">
<Label className="px-1.5" variant="input-bordered">
<Icon icon="LuSearch" size="md" />
<Input
className="input-sm grow"
name="Search"
placeholder={t('sidebar.searchPlaceHolder')}
value={searchTerm}
onChange={(e) => setFilter(e.target.value)}
onKeyDown={(e) => {
if (e.nativeEvent.isComposing || e.keyCode === 229) return;
if (e.key === 'Escape' && !e.shiftKey) {
e.preventDefault();
resetFilter();
}
}}
autoFocus
/>
{isFiltered && (
<Button
variant="ghost"
size="icon-md"
onClick={resetFilter}
title={t('header.buttons.clear')}
aria-label={t('header.ariaLabels.clear')}
>
<Icon icon="LuX" size="md" />
</Button>
)}
</Label>
</div>

{/* scrollable conversation list */}
<div className="flex-1 overflow-y-auto overflow-x-hidden min-h-0">
{groupedConv.map((group) => (
<ConversationGroup
key={group.title}
group={group}
onItemSelect={handleSelect}
/>
))}
{!isFiltered &&
groupedConv.map((group, idx) => (
<ConversationGroup
className={idx > 0 ? 'mt-6' : 'mt-3'}
key={group.title}
group={group}
onItemSelect={handleSelect}
/>
))}

{isFiltered &&
filteredConversations.map((conv) => (
<ConversationItem
key={conv.id}
conv={conv}
onSelect={handleSelect}
/>
))}
</div>

{/* Footer always at the bottom */}
Expand All @@ -120,20 +174,22 @@ export default function Sidebar() {

const ConversationGroup = memo(
({
className,
group,
onItemSelect,
}: {
className?: string;
group: GroupedConversations;
onItemSelect: () => void;
}) => {
const { t } = useTranslation();

return (
<div role="group">
<div role="group" className={className}>
{/* group name (by date) */}
{/* we use btn class here to make sure that the padding/margin are aligned with the other items */}
<Label
className="px-2 mb-0 mt-6 opacity-75"
className="px-2 opacity-75"
variant="group-title"
size="xs"
role="note"
Expand Down
Loading