Feat copy caption with ipa & refactor some style (#516)
* refactor sidebar * fix vocabulary style * fix conversation page in dark mode * remove focus-visible:ring * copy caption with ipa
This commit is contained in:
@@ -96,4 +96,7 @@
|
||||
|
||||
body {
|
||||
user-select: none;
|
||||
:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,17 +46,21 @@ const FrontSide = (props: {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<h2 className="py-8 text-4xl font-bold font-serif text-center">{word}</h2>
|
||||
<div className="px-6">
|
||||
<div className="mb-4 italic text-sm">{t("context")}</div>
|
||||
</div>
|
||||
<ScrollArea className="flex-1 px-6 text-lg font-serif">
|
||||
<div ref={ref} className="">
|
||||
{lookups.map((lookup) => (
|
||||
<p key={lookup.id} className="mb-8">
|
||||
{lookup.context}
|
||||
</p>
|
||||
))}
|
||||
<ScrollArea className="flex-1">
|
||||
<h2 className="py-8 text-4xl font-bold font-serif text-center">
|
||||
{word}
|
||||
</h2>
|
||||
<div className="px-6">
|
||||
<div className="mb-4 italic text-sm">{t("context")}</div>
|
||||
</div>
|
||||
<div className="px-6 text-lg font-serif">
|
||||
<div ref={ref} className="">
|
||||
{lookups.map((lookup) => (
|
||||
<p key={lookup.id} className="mb-8">
|
||||
{lookup.context}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
<div className="mt-4 flex items-center justify-center">
|
||||
@@ -102,38 +106,42 @@ const BackSide = (props: { meaning: MeaningType; onFlip: () => void }) => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
<h2 className="py-8 text-4xl font-bold font-serif text-center">{word}</h2>
|
||||
<div className="px-6">
|
||||
<div className="mb-2">
|
||||
{pos && (
|
||||
<span className="italic text-sm text-muted-foreground mr-2">
|
||||
{pos}
|
||||
</span>
|
||||
)}
|
||||
{pronunciation && (
|
||||
<span className="text-sm mr-2">/{pronunciation}/</span>
|
||||
)}
|
||||
{lemma && lemma !== word && (
|
||||
<span className="text-sm">({lemma})</span>
|
||||
)}
|
||||
</div>
|
||||
{translation && <div className="mb-2">{translation}</div>}
|
||||
<div className="mb-2">
|
||||
<span>{definition}</span>
|
||||
<ScrollArea className="flex-1">
|
||||
<h2 className="py-8 text-4xl font-bold font-serif text-center">
|
||||
{word}
|
||||
</h2>
|
||||
<div className="px-6">
|
||||
<div className="mb-2">
|
||||
{pos && (
|
||||
<span className="italic text-sm text-muted-foreground mr-2">
|
||||
{pos}
|
||||
</span>
|
||||
)}
|
||||
{pronunciation && (
|
||||
<span className="text-sm mr-2">/{pronunciation}/</span>
|
||||
)}
|
||||
{lemma && lemma !== word && (
|
||||
<span className="text-sm">({lemma})</span>
|
||||
)}
|
||||
</div>
|
||||
{translation && <div className="mb-2">{translation}</div>}
|
||||
<div className="mb-2">
|
||||
<span>{definition}</span>
|
||||
</div>
|
||||
|
||||
<Separator className="my-6" />
|
||||
<div className="mb-4 italic text-sm">{t("context")}</div>
|
||||
</div>
|
||||
|
||||
<Separator className="my-6" />
|
||||
<div className="mb-4 italic text-sm">{t("context")}</div>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="flex-1 px-6 text-lg font-serif">
|
||||
<div ref={ref} className="">
|
||||
{lookups.map((lookup) => (
|
||||
<div key={lookup.id} className="mb-8">
|
||||
<div className="mb-2">{lookup.context}</div>
|
||||
<div className="text-base">{lookup.contextTranslation}</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="px-6 text-lg font-serif">
|
||||
<div ref={ref} className="">
|
||||
{lookups.map((lookup) => (
|
||||
<div key={lookup.id} className="mb-8">
|
||||
<div className="mb-2">{lookup.context}</div>
|
||||
<div className="text-base">{lookup.contextTranslation}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState, useContext } from "react";
|
||||
import { MediaPlayerProviderContext } from "@renderer/context";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import { Button, toast, ScrollArea } from "@renderer/components/ui";
|
||||
import { Button, toast } from "@renderer/components/ui";
|
||||
import { ConversationShortcuts, MediaCaptionTabs } from "@renderer/components";
|
||||
import { t } from "i18next";
|
||||
import { BotIcon, CopyIcon, CheckIcon, SpeechIcon } from "lucide-react";
|
||||
@@ -397,7 +397,22 @@ export const MediaCaption = () => {
|
||||
data-tooltip-id="media-player-tooltip"
|
||||
data-tooltip-content={t("copyText")}
|
||||
onClick={() => {
|
||||
copyToClipboard(caption.text);
|
||||
if (displayIpa) {
|
||||
const text = caption.timeline
|
||||
.map((word) => {
|
||||
const ipa = word.timeline
|
||||
.map((t) =>
|
||||
t.timeline.map((s) => convertIpaToNormal(s.text)).join("")
|
||||
)
|
||||
.join(" · ");
|
||||
return `${word.text}(${ipa})`;
|
||||
})
|
||||
.join(" ");
|
||||
|
||||
copyToClipboard(text);
|
||||
} else {
|
||||
copyToClipboard(caption.text);
|
||||
}
|
||||
setCopied(true);
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
|
||||
@@ -4,6 +4,16 @@ import {
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
ScrollArea,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuItem,
|
||||
Separator,
|
||||
} from "@renderer/components/ui";
|
||||
import {
|
||||
SettingsIcon,
|
||||
@@ -15,203 +25,112 @@ import {
|
||||
UserIcon,
|
||||
BotIcon,
|
||||
UsersRoundIcon,
|
||||
LucideIcon,
|
||||
HelpCircleIcon,
|
||||
ExternalLinkIcon,
|
||||
} from "lucide-react";
|
||||
import { useLocation, Link } from "react-router-dom";
|
||||
import { t } from "i18next";
|
||||
import { Preferences } from "@renderer/components";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { useContext } from "react";
|
||||
|
||||
export const Sidebar = () => {
|
||||
const location = useLocation();
|
||||
const activeTab = location.pathname;
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="h-[100vh] w-20 xl:w-48 transition-all relative"
|
||||
className="h-[100vh] w-16 xl:w-40 transition-all relative"
|
||||
data-testid="sidebar"
|
||||
>
|
||||
<div className="fixed top-0 left-0 h-full w-20 xl:w-48 bg-muted">
|
||||
<div className="fixed top-0 left-0 h-full w-16 xl:w-40 bg-muted">
|
||||
<ScrollArea className="w-full h-full">
|
||||
<div className="py-4 flex items-center space-x-1 justify-center">
|
||||
<div className="py-4 mb-4 flex items-center space-x-1 justify-center">
|
||||
<img src="./assets/logo-light.svg" className="w-8 h-8" />
|
||||
<span className="hidden xl:block text-xl font-semibold text-[#4797F5]">
|
||||
ENJOY
|
||||
</span>
|
||||
</div>
|
||||
<div className="xl:px-3 py-2">
|
||||
<div className="xl:pl-3">
|
||||
<Link
|
||||
to="/"
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={t("sidebar.home")}
|
||||
data-tooltip-place="right"
|
||||
className="block px-2"
|
||||
>
|
||||
<Button
|
||||
variant={activeTab === "/" ? "default" : "ghost"}
|
||||
className="w-full xl:justify-start"
|
||||
>
|
||||
<HomeIcon className="xl:mr-2 h-5 w-5" />
|
||||
<span className="hidden xl:block">{t("sidebar.home")}</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<div className="grid gap-2">
|
||||
<SidebarItem
|
||||
href="/"
|
||||
label={t("sidebar.home")}
|
||||
tooltip={t("sidebar.home")}
|
||||
active={activeTab === "/"}
|
||||
Icon={HomeIcon}
|
||||
/>
|
||||
|
||||
<Link
|
||||
to="/conversations"
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={t("sidebar.aiAssistant")}
|
||||
data-tooltip-place="right"
|
||||
data-testid="sidebar-conversations"
|
||||
className="block px-2"
|
||||
>
|
||||
<Button
|
||||
variant={
|
||||
activeTab.startsWith("/conversations")
|
||||
? "default"
|
||||
: "ghost"
|
||||
}
|
||||
className="w-full xl:justify-start"
|
||||
>
|
||||
<BotIcon className="xl:mr-2 h-5 w-5" />
|
||||
<span className="hidden xl:block">
|
||||
{t("sidebar.aiAssistant")}
|
||||
</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<Separator className="hidden xl:block" />
|
||||
|
||||
<Link
|
||||
to="/community"
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={t("sidebar.community")}
|
||||
data-tooltip-place="right"
|
||||
className="block px-2"
|
||||
>
|
||||
<Button
|
||||
variant={activeTab === "/community" ? "default" : "ghost"}
|
||||
className="w-full xl:justify-start"
|
||||
>
|
||||
<UsersRoundIcon className="xl:mr-2 h-5 w-5" />
|
||||
<span className="hidden xl:block">
|
||||
{t("sidebar.community")}
|
||||
</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<SidebarItem
|
||||
href="/audios"
|
||||
label={t("sidebar.audios")}
|
||||
tooltip={t("sidebar.audios")}
|
||||
active={activeTab.startsWith("/audios")}
|
||||
Icon={HeadphonesIcon}
|
||||
/>
|
||||
|
||||
<div className="xl:px-3 py-2">
|
||||
<h3 className="hidden xl:block mb-2 px-4 text-lg font-semibold tracking-tight">
|
||||
{t("sidebar.library")}
|
||||
</h3>
|
||||
<div className="xl:pl-3 space-y-2">
|
||||
<Link
|
||||
to="/audios"
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={t("sidebar.audios")}
|
||||
data-tooltip-place="right"
|
||||
className="block px-2"
|
||||
>
|
||||
<Button
|
||||
variant={
|
||||
activeTab.startsWith("/audios") ? "default" : "ghost"
|
||||
}
|
||||
className="w-full xl:justify-start"
|
||||
>
|
||||
<HeadphonesIcon className="xl:mr-2 h-5 w-5" />
|
||||
<span className="hidden xl:block">{t("sidebar.audios")}</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<SidebarItem
|
||||
href="/videos"
|
||||
label={t("sidebar.videos")}
|
||||
tooltip={t("sidebar.videos")}
|
||||
active={activeTab.startsWith("/videos")}
|
||||
Icon={VideoIcon}
|
||||
/>
|
||||
|
||||
<Link
|
||||
to="/videos"
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={t("sidebar.videos")}
|
||||
data-tooltip-place="right"
|
||||
className="block px-2"
|
||||
>
|
||||
<Button
|
||||
variant={
|
||||
activeTab.startsWith("/videos") ? "default" : "ghost"
|
||||
}
|
||||
className="w-full xl:justify-start"
|
||||
>
|
||||
<VideoIcon className="xl:mr-2 h-5 w-5" />
|
||||
<span className="hidden xl:block">{t("sidebar.videos")}</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<SidebarItem
|
||||
href="/stories"
|
||||
label={t("sidebar.stories")}
|
||||
tooltip={t("sidebar.stories")}
|
||||
active={activeTab.startsWith("/stories")}
|
||||
Icon={NewspaperIcon}
|
||||
/>
|
||||
|
||||
<Link
|
||||
to="/stories"
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={t("sidebar.stories")}
|
||||
data-tooltip-place="right"
|
||||
className="block px-2"
|
||||
>
|
||||
<Button
|
||||
variant={
|
||||
activeTab.startsWith("/stories") ? "default" : "ghost"
|
||||
}
|
||||
className="w-full xl:justify-start"
|
||||
>
|
||||
<NewspaperIcon className="xl:mr-2 h-5 w-5" />
|
||||
<span className="hidden xl:block">
|
||||
{t("sidebar.stories")}
|
||||
</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="hidden xl:block" />
|
||||
|
||||
<div className="xl:px-3 py-2">
|
||||
<h3 className="hidden xl:block mb-2 px-4 text-lg font-semibold tracking-tight">
|
||||
{t("sidebar.mine")}
|
||||
</h3>
|
||||
<div className="xl:pl-3 space-y-2">
|
||||
<Link
|
||||
to="/vocabulary"
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={t("sidebar.vocabulary")}
|
||||
data-tooltip-place="right"
|
||||
className="block px-2"
|
||||
>
|
||||
<Button
|
||||
variant={
|
||||
activeTab.startsWith("/vocabulary") ? "default" : "ghost"
|
||||
}
|
||||
className="w-full xl:justify-start"
|
||||
>
|
||||
<BookMarkedIcon className="xl:mr-2 h-5 w-5" />
|
||||
<span className="hidden xl:block">
|
||||
{t("sidebar.vocabulary")}
|
||||
</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<SidebarItem
|
||||
href="/conversations"
|
||||
label={t("sidebar.aiAssistant")}
|
||||
tooltip={t("sidebar.aiAssistant")}
|
||||
active={activeTab.startsWith("/conversations")}
|
||||
Icon={BotIcon}
|
||||
/>
|
||||
|
||||
<Link
|
||||
to="/profile"
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={t("sidebar.profile")}
|
||||
data-tooltip-place="right"
|
||||
className="block px-2"
|
||||
>
|
||||
<Button
|
||||
variant={
|
||||
activeTab.startsWith("/profile") ? "default" : "ghost"
|
||||
}
|
||||
className="w-full xl:justify-start"
|
||||
>
|
||||
<UserIcon className="xl:mr-2 h-5 w-5" />
|
||||
<span className="hidden xl:block">
|
||||
{t("sidebar.profile")}
|
||||
</span>
|
||||
</Button>
|
||||
</Link>
|
||||
<SidebarItem
|
||||
href="/vocabulary"
|
||||
label={t("sidebar.vocabulary")}
|
||||
tooltip={t("sidebar.vocabulary")}
|
||||
active={activeTab.startsWith("/vocabulary")}
|
||||
Icon={BookMarkedIcon}
|
||||
/>
|
||||
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Separator className="hidden xl:block" />
|
||||
|
||||
<SidebarItem
|
||||
href="/community"
|
||||
label={t("sidebar.community")}
|
||||
tooltip={t("sidebar.community")}
|
||||
active={activeTab === "/community"}
|
||||
Icon={UsersRoundIcon}
|
||||
/>
|
||||
|
||||
<SidebarItem
|
||||
href="/profile"
|
||||
label={t("sidebar.profile")}
|
||||
tooltip={t("sidebar.profile")}
|
||||
active={activeTab.startsWith("/profile")}
|
||||
Icon={UserIcon}
|
||||
/>
|
||||
|
||||
<Separator className="hidden xl:block" />
|
||||
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<div className="px-1 xl:px-2">
|
||||
<Button
|
||||
variant={
|
||||
activeTab.startsWith("/settings") ? "default" : "ghost"
|
||||
}
|
||||
variant="ghost"
|
||||
id="preferences-button"
|
||||
className="w-full xl:justify-start"
|
||||
data-tooltip-id="global-tooltip"
|
||||
@@ -223,16 +142,106 @@ export const Sidebar = () => {
|
||||
{t("sidebar.preferences")}
|
||||
</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
</div>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="max-w-screen-md xl:max-w-screen-lg h-5/6 p-0">
|
||||
<Preferences />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
<DialogContent className="max-w-screen-md xl:max-w-screen-lg h-5/6 p-0">
|
||||
<Preferences />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
<div className="w-full absolute bottom-0 px-1 xl:px-4 py-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full xl:justify-start px-2 xl:px-4"
|
||||
>
|
||||
<HelpCircleIcon className="h-5 w-5" />
|
||||
<span className="ml-2 hidden xl:block">{t("help")}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent className="px-4">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
EnjoyApp.shell.openExternal("https://1000h.org/enjoy-app/")
|
||||
}
|
||||
className="flex justify-between space-x-2"
|
||||
>
|
||||
<span>{t("userGuide")}</span>
|
||||
<ExternalLinkIcon className="h-4 w-4" />
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
{t("feedback")}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
EnjoyApp.shell.openExternal(
|
||||
"https://mixin.one/codes/f8ff96b8-54fb-4ad8-a6d4-5a5bdb1df13e"
|
||||
)
|
||||
}
|
||||
className="flex justify-between space-x-2"
|
||||
>
|
||||
<span>Mixin</span>
|
||||
<ExternalLinkIcon className="h-4 w-4" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
EnjoyApp.shell.openExternal(
|
||||
"https://github.com/xiaolai/everyone-can-use-english/discussions"
|
||||
)
|
||||
}
|
||||
className="flex justify-between space-x-2"
|
||||
>
|
||||
<span>Github</span>
|
||||
<ExternalLinkIcon className="h-4 w-4" />
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SidebarItem = (props: {
|
||||
href: string;
|
||||
label: string;
|
||||
tooltip: string;
|
||||
active: boolean;
|
||||
Icon: LucideIcon;
|
||||
}) => {
|
||||
const { href, label, tooltip, active, Icon } = props;
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={href}
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={tooltip}
|
||||
data-tooltip-place="right"
|
||||
className="block px-1 xl:px-2"
|
||||
>
|
||||
<Button
|
||||
variant={active ? "default" : "ghost"}
|
||||
className="w-full xl:justify-start px-2 xl:px-4"
|
||||
>
|
||||
<Icon className="h-5 w-5" />
|
||||
<span className="ml-2 hidden xl:block">{label}</span>
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"capitalize inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 min-w-fit",
|
||||
"capitalize inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 min-w-fit",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
@@ -11,7 +11,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -28,7 +28,7 @@ const RadioGroupItem = React.forwardRef<
|
||||
<RadioGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -20,7 +20,7 @@ const Slider = React.forwardRef<
|
||||
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
|
||||
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50" />
|
||||
</SliderPrimitive.Root>
|
||||
));
|
||||
Slider.displayName = SliderPrimitive.Root.displayName;
|
||||
|
||||
@@ -11,7 +11,7 @@ const Switch = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(
|
||||
"peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||
"peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -29,7 +29,7 @@ const TabsTrigger = React.forwardRef<
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -44,7 +44,7 @@ const TabsContent = React.forwardRef<
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
"mt-2 ring-offset-background focus-visible:outline-none",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -10,7 +10,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "@renderer/lib/utils";
|
||||
|
||||
const toggleVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
@@ -229,10 +229,10 @@ export default () => {
|
||||
return (
|
||||
<div
|
||||
data-testid="conversation-page"
|
||||
className="h-screen px-4 py-6 lg:px-8 bg-muted flex flex-col"
|
||||
className="h-screen px-4 py-6 lg:px-8 flex flex-col"
|
||||
>
|
||||
<div className="h-[calc(100vh-3rem)] relative w-full max-w-screen-md mx-auto flex flex-col">
|
||||
<div className="flex items-center justify-center py-2 bg-gradient-to-b from-muted relative">
|
||||
<div className="flex items-center justify-center py-2 relative">
|
||||
<div className="cursor-pointer h-6 opacity-50 hover:opacity-100">
|
||||
<Link className="flex items-center" to="/conversations">
|
||||
<BotIcon className="h-5 mr-2" />
|
||||
@@ -243,7 +243,7 @@ export default () => {
|
||||
<Sheet open={editting} onOpenChange={(value) => setEditting(value)}>
|
||||
<SheetTrigger>
|
||||
<div className="absolute right-4 top-0 py-3">
|
||||
<SettingsIcon className="w-4 h-4 text-muted-foreground" />
|
||||
<SettingsIcon className="w-5 h-5 text-muted-foreground" />
|
||||
</div>
|
||||
</SheetTrigger>
|
||||
|
||||
@@ -310,7 +310,7 @@ export default () => {
|
||||
</ScrollArea>
|
||||
</MediaPlayerProvider>
|
||||
|
||||
<div className="px-4 absolute w-full bottom-0 left-0 bg-muted z-50">
|
||||
<div className="px-4 absolute w-full bottom-0 left-0 z-50">
|
||||
<div className="focus-within:bg-background pr-4 py-2 flex items-end space-x-4 rounded-lg shadow-lg border scrollbar">
|
||||
<Textarea
|
||||
ref={inputRef}
|
||||
@@ -319,7 +319,7 @@ export default () => {
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
placeholder={t("pressEnterToSend")}
|
||||
data-testid="conversation-page-input"
|
||||
className="text-base px-4 py-0 shadow-none focus-visible:outline-0 focus-visible:ring-0 border-none bg-muted focus:bg-background min-h-[1rem] max-h-[70vh] scrollbar-thin scrollbar-thumb-sky-500 !overflow-x-hidden"
|
||||
className="text-base px-4 py-0 shadow-none focus-visible:outline-0 focus-visible:ring-0 border-none min-h-[1rem] max-h-[70vh] scrollbar-thin scrollbar-thumb-sky-500 !overflow-x-hidden"
|
||||
/>
|
||||
<div className="h-12 py-1">
|
||||
<Button
|
||||
|
||||
@@ -39,51 +39,51 @@ export default () => {
|
||||
|
||||
return (
|
||||
<div className="h-[100vh]">
|
||||
<div className="max-w-screen-md mx-auto px-4 py-6">
|
||||
<div className="flex space-x-1 items-center mb-4">
|
||||
<Button variant="ghost" size="icon" onClick={() => navigate(-1)}>
|
||||
<ChevronLeftIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
<span>{t("sidebar.vocabulary")}</span>
|
||||
</div>
|
||||
|
||||
{meanings.length === 0 ? (
|
||||
<div className=""></div>
|
||||
) : (
|
||||
<div className="h-[calc(100vh-5rem)] flex items-center justify-between space-x-6">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="rounded-full"
|
||||
onClick={() => {
|
||||
if (currentIndex > 0) {
|
||||
setCurrentIndex(currentIndex - 1);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="max-w-screen-md mx-auto p-4">
|
||||
<div className="flex space-x-1 items-center mb-4">
|
||||
<Button variant="ghost" size="icon" onClick={() => navigate(-1)}>
|
||||
<ChevronLeftIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
<div className="bg-background flex-1 h-5/6 border p-6 rounded-xl shadow-xl">
|
||||
<MeaningMemorizingCard meaning={meanings[currentIndex]} />
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="rounded-full"
|
||||
onClick={() => {
|
||||
if (currentIndex < meanings.length - 1) {
|
||||
setCurrentIndex(currentIndex + 1);
|
||||
}
|
||||
if (currentIndex === meanings.length - 2 && nextPage) {
|
||||
fetchMeanings(nextPage);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ChevronRightIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
<span>{t("sidebar.vocabulary")}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{meanings.length === 0 ? (
|
||||
<div className=""></div>
|
||||
) : (
|
||||
<div className="h-[calc(100vh-5.25rem)] flex items-center justify-between space-x-6">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="rounded-full"
|
||||
onClick={() => {
|
||||
if (currentIndex > 0) {
|
||||
setCurrentIndex(currentIndex - 1);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ChevronLeftIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
<div className="bg-background flex-1 h-5/6 border p-6 rounded-xl shadow-xl">
|
||||
<MeaningMemorizingCard meaning={meanings[currentIndex]} />
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
className="rounded-full"
|
||||
onClick={() => {
|
||||
if (currentIndex < meanings.length - 1) {
|
||||
setCurrentIndex(currentIndex + 1);
|
||||
}
|
||||
if (currentIndex === meanings.length - 2 && nextPage) {
|
||||
fetchMeanings(nextPage);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ChevronRightIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user