Commit 493832ae authored by Johnny's avatar Johnny

refactor: unify components

parent 50a41a39
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/style.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
\ No newline at end of file
......@@ -15,11 +15,20 @@
"@emotion/styled": "^11.14.0",
"@github/relative-time-element": "^4.4.8",
"@matejmazur/react-katex": "^3.1.3",
"@mui/joy": "5.0.0-beta.52",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-radio-group": "^1.3.7",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tooltip": "^1.2.7",
"@tailwindcss/vite": "^4.1.8",
"@usememos/mui": "0.1.0-20250607013227",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"copy-to-clipboard": "^3.3.3",
"dayjs": "^1.11.13",
"fuse.js": "^7.1.0",
......@@ -70,6 +79,7 @@
"nice-grpc-web": "^3.3.7",
"prettier": "^3.5.3",
"terser": "^5.40.0",
"tw-animate-css": "^1.3.4",
"typescript": "^5.8.3",
"typescript-eslint": "^8.33.0",
"vite": "^6.3.5"
......
This source diff could not be displayed because it is too large. You can view the blob instead.
import { useColorScheme } from "@mui/joy";
import { observer } from "mobx-react-lite";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Outlet } from "react-router-dom";
import { getSystemColorScheme } from "./helpers/utils";
......@@ -10,7 +9,7 @@ import { userStore, workspaceStore } from "./store/v2";
const App = observer(() => {
const { i18n } = useTranslation();
const navigateTo = useNavigateTo();
const { mode, setMode } = useColorScheme();
const [mode, setMode] = useState<"light" | "dark">("light");
const workspaceProfile = workspaceStore.state.profile;
const userSetting = userStore.state.userSetting;
const workspaceGeneralSetting = workspaceStore.state.generalSetting;
......
import { Tooltip } from "@mui/joy";
import dayjs from "dayjs";
import { observer } from "mobx-react-lite";
import { memo, useMemo } from "react";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { workspaceStore } from "@/store/v2";
import type { ActivityCalendarProps, CalendarDay } from "@/types/statistics";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
const getCellOpacity = (ratio: number): string => {
......@@ -59,9 +59,16 @@ const CalendarCell = memo(
}
return (
<Tooltip className="shrink-0" title={tooltipText} placement="top" arrow>
{cellContent}
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="shrink-0">{cellContent}</div>
</TooltipTrigger>
<TooltipContent>
<p>{tooltipText}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
},
);
......
import { Option, Select } from "@mui/joy";
import { SunIcon, MoonIcon, SmileIcon } from "lucide-react";
import { FC } from "react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useTranslate } from "@/utils/i18n";
interface Props {
......@@ -31,21 +31,20 @@ const AppearanceSelect: FC<Props> = (props: Props) => {
};
return (
<Select
className={`min-w-40! w-auto whitespace-nowrap ${className ?? ""}`}
value={value}
onChange={(_, appearance) => {
if (appearance) {
handleSelectChange(appearance);
}
}}
startDecorator={getPrefixIcon(value)}
>
<Select value={value} onValueChange={handleSelectChange}>
<SelectTrigger className={`min-w-40 w-auto whitespace-nowrap ${className ?? ""}`}>
<SelectValue placeholder="Select appearance" />
</SelectTrigger>
<SelectContent>
{appearanceList.map((item) => (
<Option key={item} value={item} className="whitespace-nowrap">
<SelectItem key={item} value={item} className="whitespace-nowrap">
<div className="flex items-center gap-2">
{getPrefixIcon(item)}
{t(`setting.appearance-option.${item}`)}
</Option>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
);
};
......
......@@ -10,8 +10,8 @@ import {
SheetIcon,
} from "lucide-react";
import React from "react";
import { cn } from "@/lib/utils";
import { Attachment } from "@/types/proto/api/v1/attachment_service";
import { cn } from "@/utils";
import { getAttachmentType, getAttachmentUrl } from "@/utils/attachment";
import showPreviewImageDialog from "./PreviewImageDialog";
import SquareDiv from "./kit/SquareDiv";
......
import { observer } from "mobx-react-lite";
import { cn } from "@/lib/utils";
import { workspaceStore } from "@/store/v2";
import { cn } from "@/utils";
import AppearanceSelect from "./AppearanceSelect";
import LocaleSelect from "./LocaleSelect";
......
import { observer } from "mobx-react-lite";
import { cn } from "@/lib/utils";
import { workspaceStore } from "@/store/v2";
import { cn } from "@/utils";
import UserAvatar from "./UserAvatar";
interface Props {
......
import { Button, Input } from "@usememos/mui";
import { XIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { userStore } from "@/store/v2";
import { User } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n";
......@@ -69,7 +70,7 @@ const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
<p>
{t("setting.account-section.change-password")} ({user.displayName})
</p>
<Button variant="plain" onClick={handleCloseBtnClick}>
<Button variant="ghost" onClick={handleCloseBtnClick}>
<XIcon className="w-5 h-auto" />
</Button>
</div>
......@@ -91,7 +92,7 @@ const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
onChange={handleNewPasswordAgainChanged}
/>
<div className="flex flex-row justify-end items-center mt-4 w-full gap-x-2">
<Button variant="plain" onClick={handleCloseBtnClick}>
<Button variant="ghost" onClick={handleCloseBtnClick}>
{t("common.cancel")}
</Button>
<Button color="primary" onClick={handleSaveBtnClick}>
......
import { Radio, RadioGroup } from "@mui/joy";
import { Button, Input } from "@usememos/mui";
import { XIcon } from "lucide-react";
import React, { useState } from "react";
import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import useLoading from "@/hooks/useLoading";
......@@ -56,9 +58,9 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
});
};
const handleRoleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const handleRoleInputChange = (value: string) => {
setPartialState({
expiration: Number(e.target.value),
expiration: Number(value),
});
};
......@@ -89,7 +91,7 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
<div className="flex flex-row justify-between items-center w-full mb-4 gap-2">
<p>{t("setting.access-token-section.create-dialog.create-access-token")}</p>
<Button variant="plain" onClick={() => destroy()}>
<Button variant="ghost" onClick={() => destroy()}>
<XIcon className="w-5 h-auto" />
</Button>
</div>
......@@ -113,18 +115,21 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
{t("setting.access-token-section.create-dialog.expiration")} <span className="text-red-600">*</span>
</span>
<div className="w-full flex flex-row justify-start items-center text-base">
<RadioGroup orientation="horizontal" value={state.expiration} onChange={handleRoleInputChange}>
<RadioGroup value={state.expiration.toString()} onValueChange={handleRoleInputChange} className="flex flex-row gap-4">
{expirationOptions.map((option) => (
<Radio key={option.value} value={option.value} checked={state.expiration === option.value} label={option.label} />
<div key={option.value} className="flex items-center space-x-2">
<RadioGroupItem value={option.value.toString()} id={`expiration-${option.value}`} />
<Label htmlFor={`expiration-${option.value}`}>{option.label}</Label>
</div>
))}
</RadioGroup>
</div>
</div>
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2">
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}>
<Button variant="ghost" disabled={requestState.isLoading} onClick={destroy}>
{t("common.cancel")}
</Button>
<Button color="primary" disabled={requestState.isLoading} onClick={handleSaveBtnClick}>
<Button disabled={requestState.isLoading} onClick={handleSaveBtnClick}>
{t("common.create")}
</Button>
</div>
......
import { Input, Textarea, Button } from "@usememos/mui";
import { XIcon } from "lucide-react";
import React, { useState } from "react";
import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { shortcutServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import useLoading from "@/hooks/useLoading";
......@@ -74,7 +76,7 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
<p className="title-text">{`${isCreating ? t("common.create") : t("common.edit")} ${t("common.shortcuts")}`}</p>
<Button variant="plain" onClick={() => destroy()}>
<Button variant="ghost" onClick={() => destroy()}>
<XIcon className="w-5 h-auto" />
</Button>
</div>
......@@ -84,8 +86,8 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
<Input className="w-full" type="text" placeholder="" value={shortcut.title} onChange={onShortcutTitleChange} />
<span className="text-sm whitespace-nowrap mt-3 mb-1">{t("common.filter")}</span>
<Textarea
className="w-full"
rows={3}
fullWidth
placeholder={t("common.shortcut-filter")}
value={shortcut.filter}
onChange={onShortcutFilterChange}
......@@ -115,7 +117,7 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
</ul>
</div>
<div className="w-full flex flex-row justify-end items-center space-x-2 mt-2">
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}>
<Button variant="ghost" disabled={requestState.isLoading} onClick={destroy}>
{t("common.cancel")}
</Button>
<Button color="primary" disabled={requestState.isLoading} onClick={handleConfirm}>
......
import { Radio, RadioGroup } from "@mui/joy";
import { Button, Input } from "@usememos/mui";
import { XIcon } from "lucide-react";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { userServiceClient } from "@/grpcweb";
import useLoading from "@/hooks/useLoading";
import { User, User_Role } from "@/types/proto/api/v1/user_service";
......@@ -66,7 +68,7 @@ const CreateUserDialog: React.FC<Props> = (props: Props) => {
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
<p className="title-text">{`${isCreating ? t("common.create") : t("common.edit")} ${t("common.user")}`}</p>
<Button variant="plain" onClick={() => destroy()}>
<Button variant="ghost" onClick={() => destroy()}>
<XIcon className="w-5 h-auto" />
</Button>
</div>
......@@ -99,16 +101,22 @@ const CreateUserDialog: React.FC<Props> = (props: Props) => {
/>
<span className="text-sm whitespace-nowrap mt-3 mb-1">{t("common.role")}</span>
<RadioGroup
orientation="horizontal"
defaultValue={user.role}
onChange={(e) => setPartialUser({ role: e.target.value as User_Role })}
value={user.role}
onValueChange={(value) => setPartialUser({ role: value as User_Role })}
className="flex flex-row gap-4"
>
<Radio value={User_Role.USER} label={t("setting.member-section.user")} />
<Radio value={User_Role.ADMIN} label={t("setting.member-section.admin")} />
<div className="flex items-center space-x-2">
<RadioGroupItem value={User_Role.USER} id="user" />
<Label htmlFor="user">{t("setting.member-section.user")}</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value={User_Role.ADMIN} id="admin" />
<Label htmlFor="admin">{t("setting.member-section.admin")}</Label>
</div>
</RadioGroup>
</div>
<div className="w-full flex flex-row justify-end items-center space-x-2 mt-2">
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}>
<Button variant="ghost" disabled={requestState.isLoading} onClick={destroy}>
{t("common.cancel")}
</Button>
<Button color="primary" disabled={requestState.isLoading} onClick={handleConfirm}>
......
import { Button, Input } from "@usememos/mui";
import { XIcon } from "lucide-react";
import React, { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { webhookServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import useLoading from "@/hooks/useLoading";
......@@ -108,7 +109,7 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
<p className="title-text">
{isCreating ? t("setting.webhook-section.create-dialog.create-webhook") : t("setting.webhook-section.create-dialog.edit-webhook")}
</p>
<Button variant="plain" onClick={() => destroy()}>
<Button variant="ghost" onClick={() => destroy()}>
<XIcon className="w-5 h-auto" />
</Button>
</div>
......@@ -142,7 +143,7 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
</div>
</div>
<div className="w-full flex flex-row justify-end items-center mt-2 space-x-2">
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}>
<Button variant="ghost" disabled={requestState.isLoading} onClick={destroy}>
{t("common.cancel")}
</Button>
<Button color="primary" disabled={requestState.isLoading} onClick={handleSaveBtnClick}>
......
import dayjs from "dayjs";
import toast from "react-hot-toast";
import { cn } from "@/utils";
import { cn } from "@/lib/utils";
const DATE_TIME_FORMAT = "M/D/YYYY, H:mm:ss";
......
import { CssVarsProvider } from "@mui/joy";
import { observer } from "mobx-react-lite";
import { useEffect, useRef } from "react";
import { createRoot } from "react-dom/client";
import { cn } from "@/lib/utils";
import dialogStore from "@/store/v2/dialog";
import theme from "@/theme";
import { cn } from "@/utils";
interface DialogConfig {
dialogName: string;
......@@ -90,11 +88,9 @@ export function generateDialog<T extends DialogProps>(
} as T;
const Fragment = observer(() => (
<CssVarsProvider theme={theme}>
<BaseDialog destroy={cbs.destroy} clickSpaceDestroy={true} {...config}>
<DialogComponent {...dialogProps} />
</BaseDialog>
</CssVarsProvider>
));
dialog.render(<Fragment />);
......
......@@ -4,9 +4,9 @@ import { matchPath, useLocation } from "react-router-dom";
import useDebounce from "react-use/lib/useDebounce";
import SearchBar from "@/components/SearchBar";
import useCurrentUser from "@/hooks/useCurrentUser";
import { cn } from "@/lib/utils";
import { Routes } from "@/router";
import { memoStore, userStore } from "@/store/v2";
import { cn } from "@/utils";
import MemoFilters from "../MemoFilters";
import StatisticsView from "../StatisticsView";
import ShortcutsSection from "./ShortcutsSection";
......
import { Drawer } from "@mui/joy";
import { Button } from "@usememos/mui";
import { MenuIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import HomeSidebar from "./HomeSidebar";
const HomeSidebarDrawer = () => {
......@@ -13,25 +13,17 @@ const HomeSidebarDrawer = () => {
setOpen(false);
}, [location.pathname]);
const toggleDrawer = (inOpen: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
if (event.type === "keydown" && ((event as React.KeyboardEvent).key === "Tab" || (event as React.KeyboardEvent).key === "Shift")) {
return;
}
setOpen(inOpen);
};
return (
<>
<Button variant="plain" className="bg-transparent! px-2" onClick={toggleDrawer(true)}>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button variant="ghost" className="bg-transparent! px-2">
<MenuIcon className="w-6 h-auto dark:text-gray-400" />
</Button>
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>
<div className="w-full h-full bg-zinc-100 dark:bg-zinc-900">
</SheetTrigger>
<SheetContent side="right" className="w-full sm:w-80 bg-zinc-100 dark:bg-zinc-900">
<HomeSidebar className="px-4 py-4" />
</div>
</Drawer>
</>
</SheetContent>
</Sheet>
);
};
......
import { Tooltip } from "@mui/joy";
import { Edit3Icon, MoreVerticalIcon, TrashIcon, PlusIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { shortcutServiceClient } from "@/grpcweb";
import useAsyncEffect from "@/hooks/useAsyncEffect";
import { cn } from "@/lib/utils";
import { userStore } from "@/store/v2";
import memoFilterStore from "@/store/v2/memoFilter";
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
import showCreateShortcutDialog from "../CreateShortcutDialog";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
const emojiRegex = /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F)$/u;
......@@ -40,9 +40,16 @@ const ShortcutsSection = observer(() => {
<div className="w-full flex flex-col justify-start items-start mt-3 px-1 h-auto shrink-0 flex-nowrap hide-scrollbar">
<div className="flex flex-row justify-between items-center w-full gap-1 mb-1 text-sm leading-6 text-gray-400 select-none">
<span>{t("common.shortcuts")}</span>
<Tooltip title={t("common.create")} placement="top">
<PlusIcon className="w-4 h-auto" onClick={() => showCreateShortcutDialog({})} />
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<PlusIcon className="w-4 h-auto cursor-pointer" onClick={() => showCreateShortcutDialog({})} />
</TooltipTrigger>
<TooltipContent>
<p>{t("common.create")}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="w-full flex flex-row justify-start items-center relative flex-wrap gap-x-2 gap-y-1">
{shortcuts.map((shortcut) => {
......
import { Switch } from "@usememos/mui";
import { Edit3Icon, HashIcon, MoreVerticalIcon, TagsIcon, TrashIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import toast from "react-hot-toast";
import useLocalStorage from "react-use/lib/useLocalStorage";
import { Switch } from "@/components/ui/switch";
import { memoServiceClient } from "@/grpcweb";
import { cn } from "@/lib/utils";
import { userStore } from "@/store/v2";
import memoFilterStore, { MemoFilter } from "@/store/v2/memoFilter";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
import showRenameTagDialog from "../RenameTagDialog";
import TagTree from "../TagTree";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
interface Props {
readonly?: boolean;
......@@ -58,7 +58,7 @@ const TagsSection = observer((props: Props) => {
<PopoverContent align="end" alignOffset={-12}>
<div className="w-auto flex flex-row justify-between items-center gap-2 p-1">
<span className="text-sm shrink-0 dark:text-zinc-400">{t("common.tree-mode")}</span>
<Switch size="sm" checked={treeMode} onChange={(event) => setTreeMode(event.target.checked)} />
<Switch checked={treeMode} onCheckedChange={(checked) => setTreeMode(checked)} />
</div>
</PopoverContent>
</Popover>
......
import { Tooltip } from "@mui/joy";
import { InboxIcon, LoaderIcon, MessageCircleIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useState } from "react";
import toast from "react-hot-toast";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { activityServiceClient } from "@/grpcweb";
import useAsyncEffect from "@/hooks/useAsyncEffect";
import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { activityNamePrefix } from "@/store/common";
import { memoStore, userStore } from "@/store/v2";
import { Inbox, Inbox_Status } from "@/types/proto/api/v1/inbox_service";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { User } from "@/types/proto/api/v1/user_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
interface Props {
......@@ -79,9 +79,16 @@ const MemoCommentMessage = observer(({ inbox }: Props) => {
: "border-gray-500 text-gray-500 bg-gray-50 dark:bg-zinc-800",
)}
>
<Tooltip title={"Comment"} placement="bottom">
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<MessageCircleIcon className="w-4 sm:w-5 h-auto" />
</TooltipTrigger>
<TooltipContent>
<p>Comment</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div
className={cn(
......@@ -95,12 +102,19 @@ const MemoCommentMessage = observer(({ inbox }: Props) => {
<span className="text-sm text-gray-500">{inbox.createTime?.toLocaleString()}</span>
<div>
{inbox.status === Inbox_Status.UNREAD && (
<Tooltip title={t("common.archive")} placement="top">
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<InboxIcon
className="w-4 h-auto cursor-pointer text-gray-400 hover:text-blue-600"
onClick={() => handleArchiveMessage()}
/>
</TooltipTrigger>
<TooltipContent>
<p>{t("common.archive")}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
</div>
......
import { Tooltip } from "@mui/joy";
import { ExternalLinkIcon } from "lucide-react";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { useTranslate } from "@/utils/i18n";
interface Props {
......@@ -13,11 +13,18 @@ const LearnMore: React.FC<Props> = (props: Props) => {
const t = useTranslate();
return (
<Tooltip title={title ?? t("common.learn-more")} placement="top">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<a className={`text-gray-500 dark:text-gray-400 hover:text-blue-600 ${className}`} href={url} target="_blank">
<ExternalLinkIcon className="w-4 h-auto" />
</a>
</TooltipTrigger>
<TooltipContent>
<p>{title ?? t("common.learn-more")}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
};
......
import { Option, Select } from "@mui/joy";
import { GlobeIcon } from "lucide-react";
import { FC } from "react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { locales } from "@/i18n";
interface Props {
......@@ -17,20 +17,22 @@ const LocaleSelect: FC<Props> = (props: Props) => {
};
return (
<Select
className={`min-w-40! w-auto whitespace-nowrap ${className ?? ""}`}
startDecorator={<GlobeIcon className="w-4 h-auto" />}
value={value}
onChange={(_, value) => handleSelectChange(value as Locale)}
>
<Select value={value} onValueChange={handleSelectChange}>
<SelectTrigger className={`min-w-40 w-auto whitespace-nowrap ${className ?? ""}`}>
<div className="flex items-center gap-2">
<GlobeIcon className="w-4 h-auto" />
<SelectValue placeholder="Select language" />
</div>
</SelectTrigger>
<SelectContent>
{locales.map((locale) => {
try {
const languageName = new Intl.DisplayNames([locale], { type: "language" }).of(locale);
if (languageName) {
return (
<Option key={locale} value={locale}>
<SelectItem key={locale} value={locale}>
{languageName.charAt(0).toUpperCase() + languageName.slice(1)}
</Option>
</SelectItem>
);
}
} catch {
......@@ -38,11 +40,12 @@ const LocaleSelect: FC<Props> = (props: Props) => {
}
return (
<Option key={locale} value={locale}>
<SelectItem key={locale} value={locale}>
{locale}
</Option>
</SelectItem>
);
})}
</SelectContent>
</Select>
);
};
......
import { useCallback, useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
interface Props {
memoList: Memo[];
......
......@@ -15,13 +15,13 @@ import toast from "react-hot-toast";
import { useLocation } from "react-router-dom";
import { markdownServiceClient } from "@/grpcweb";
import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { memoStore, userStore } from "@/store/v2";
import { State } from "@/types/proto/api/v1/common";
import { NodeType } from "@/types/proto/api/v1/markdown_service";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
interface Props {
memo: Memo;
......
import { memo } from "react";
import { cn } from "@/lib/utils";
import { Attachment } from "@/types/proto/api/v1/attachment_service";
import { cn } from "@/utils";
import { getAttachmentType, getAttachmentUrl } from "@/utils/attachment";
import MemoAttachment from "./MemoAttachment";
import showPreviewImageDialog from "./PreviewImageDialog";
......
......@@ -3,7 +3,7 @@ import hljs from "highlight.js";
import { CopyIcon } from "lucide-react";
import { useCallback, useMemo } from "react";
import toast from "react-hot-toast";
import { cn } from "@/utils";
import { cn } from "@/lib/utils";
import MermaidBlock from "./MermaidBlock";
import { BaseProps } from "./types";
......
......@@ -2,8 +2,8 @@ import { observer } from "mobx-react-lite";
import { useEffect } from "react";
import MemoAttachmentListView from "@/components/MemoAttachmentListView";
import useLoading from "@/hooks/useLoading";
import { cn } from "@/lib/utils";
import { attachmentStore } from "@/store/v2";
import { cn } from "@/utils";
import Error from "./Error";
interface Props {
......
......@@ -6,9 +6,9 @@ import toast from "react-hot-toast";
import { Link } from "react-router-dom";
import MemoAttachmentListView from "@/components/MemoAttachmentListView";
import useLoading from "@/hooks/useLoading";
import { cn } from "@/lib/utils";
import { extractMemoIdFromName } from "@/store/common";
import { memoStore } from "@/store/v2";
import { cn } from "@/utils";
import MemoContent from "..";
import { RendererContext } from "../types";
import Error from "./Error";
......
import { Divider } from "@mui/joy";
import { Separator } from "@/components/ui/separator";
import { BaseProps } from "./types";
interface Props extends BaseProps {
......@@ -6,7 +6,7 @@ interface Props extends BaseProps {
}
const HorizontalRule: React.FC<Props> = () => {
return <Divider className="my-3!" />;
return <Separator className="my-3!" />;
};
export default HorizontalRule;
import { Link as MLink, Tooltip } from "@mui/joy";
import { useState } from "react";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { markdownServiceClient } from "@/grpcweb";
import { workspaceStore } from "@/store/v2";
import { LinkMetadata, Node } from "@/types/proto/api/v1/markdown_service";
......@@ -43,11 +43,23 @@ const Link: React.FC<Props> = ({ content, url }: Props) => {
};
return (
<Tooltip
variant="outlined"
title={
linkMetadata && (
<div className="w-full max-w-64 sm:max-w-96 p-1 flex flex-col">
<TooltipProvider>
<Tooltip open={showTooltip}>
<TooltipTrigger asChild>
<a
className="underline text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
target="_blank"
href={url}
rel="noopener noreferrer"
onMouseEnter={handleMouseEnter}
onMouseLeave={() => setShowTooltip(false)}
>
{content ? content.map((child, index) => <Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />) : url}
</a>
</TooltipTrigger>
{linkMetadata && (
<TooltipContent className="w-full max-w-64 sm:max-w-96 p-1">
<div className="w-full flex flex-col">
<div className="w-full flex flex-row justify-start items-center gap-1">
<img className="w-5 h-5 rounded" src={getFaviconWithGoogleS2(url)} alt={linkMetadata?.title} />
<h3 className="text-base truncate dark:opacity-90">{linkMetadata?.title}</h3>
......@@ -59,17 +71,10 @@ const Link: React.FC<Props> = ({ content, url }: Props) => {
<img className="mt-1 w-full h-32 object-cover rounded" src={linkMetadata.image} alt={linkMetadata.title} />
)}
</div>
)
}
open={showTooltip}
arrow
>
<MLink underline="always" target="_blank" href={url} rel="noopener noreferrer">
<span onMouseEnter={handleMouseEnter} onMouseLeave={() => setShowTooltip(false)}>
{content ? content.map((child, index) => <Renderer key={`${child.type}-${index}`} index={String(index)} node={child} />) : url}
</span>
</MLink>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
);
};
......
import { head } from "lodash-es";
import React from "react";
import { cn } from "@/lib/utils";
import { ListNode_Kind, Node, NodeType } from "@/types/proto/api/v1/markdown_service";
import { cn } from "@/utils";
import Renderer from "./Renderer";
interface Props {
......
import TeX from "@matejmazur/react-katex";
import { cn } from "@/utils";
import { cn } from "@/lib/utils";
import "katex/dist/katex.min.css";
interface Props {
......
import { useColorScheme } from "@mui/joy";
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
interface Props {
content: string;
}
const MermaidBlock: React.FC<Props> = ({ content }: Props) => {
const { mode: colorMode } = useColorScheme();
const [colorMode, setColorMode] = useState<"light" | "dark">("light");
const mermaidDockBlock = useRef<null>(null);
// Simple dark mode detection
useEffect(() => {
const updateMode = () => {
const isDark = document.documentElement.classList.contains("dark");
setColorMode(isDark ? "dark" : "light");
};
updateMode();
// Watch for changes to the dark class
const observer = new MutationObserver(updateMode);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
});
return () => observer.disconnect();
}, []);
useEffect(() => {
// Dynamically import mermaid to ensure compatibility with Vite
const initializeMermaid = async () => {
......
import { useState } from "react";
import { cn } from "@/utils";
import { cn } from "@/lib/utils";
interface Props {
content: string;
......
......@@ -2,10 +2,10 @@ import { observer } from "mobx-react-lite";
import { useContext } from "react";
import { useLocation } from "react-router-dom";
import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { Routes } from "@/router";
import { memoFilterStore } from "@/store/v2";
import { stringifyFilters, MemoFilter } from "@/store/v2/memoFilter";
import { cn } from "@/utils";
import { RendererContext } from "./types";
interface Props {
......
import { Checkbox } from "@usememos/mui";
import { observer } from "mobx-react-lite";
import { useContext } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { markdownServiceClient } from "@/grpcweb";
import { cn } from "@/lib/utils";
import { memoStore } from "@/store/v2";
import { Node, TaskListItemNode } from "@/types/proto/api/v1/markdown_service";
import { cn } from "@/utils";
import Renderer from "./Renderer";
import { RendererContext } from "./types";
......@@ -39,7 +39,12 @@ const TaskListItem = observer(({ node, complete, children }: Props) => {
return (
<li className={cn("w-full grid grid-cols-[24px_1fr]")}>
<span className="w-6 h-6 flex justify-start items-center">
<Checkbox size="sm" checked={complete} disabled={context.readonly} onChange={(e) => handleCheckboxChange(e.target.checked)} />
<Checkbox
className="h-4 w-4"
checked={complete}
disabled={context.readonly}
onCheckedChange={(checked) => handleCheckboxChange(checked === true)}
/>
</span>
<p className={cn(complete && "line-through opacity-80")}>
{children.map((child, index) => (
......
import { observer } from "mobx-react-lite";
import { memo, useEffect, useRef, useState } from "react";
import useCurrentUser from "@/hooks/useCurrentUser";
import { cn } from "@/lib/utils";
import { memoStore } from "@/store/v2";
import { Node, NodeType } from "@/types/proto/api/v1/markdown_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
import { isSuperUser } from "@/utils/user";
import Renderer from "./Renderer";
......
import { isEqual } from "lodash-es";
import { CheckCircleIcon, Code2Icon, HashIcon, LinkIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { Memo, MemoRelation_Type, Memo_Property } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
import MemoRelationForceGraph from "../MemoRelationForceGraph";
......
import { Drawer } from "@mui/joy";
import { Button } from "@usememos/mui";
import { GanttChartIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { Memo } from "@/types/proto/api/v1/memo_service";
import MemoDetailSidebar from "./MemoDetailSidebar";
......@@ -19,24 +19,17 @@ const MemoDetailSidebarDrawer = ({ memo, parentPage }: Props) => {
setOpen(false);
}, [location.pathname]);
const toggleDrawer = (inOpen: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
if (event.type === "keydown" && ((event as React.KeyboardEvent).key === "Tab" || (event as React.KeyboardEvent).key === "Shift")) {
return;
}
setOpen(inOpen);
};
return (
<>
<Button variant="plain" className="bg-transparent! px-2" onClick={toggleDrawer(true)}>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button variant="ghost" className="bg-transparent! px-2">
<GanttChartIcon className="w-5 h-auto dark:text-gray-400" />
</Button>
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>
<div className="w-full h-full px-4 bg-zinc-100 dark:bg-zinc-900">
</SheetTrigger>
<SheetContent side="right" className="w-full sm:w-80 px-4 bg-zinc-100 dark:bg-zinc-900">
<MemoDetailSidebar className="py-4" memo={memo} parentPage={parentPage} />
</div>
</Drawer>
</>
</SheetContent>
</Sheet>
);
};
......
import { Option, Select } from "@mui/joy";
import { Settings2Icon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { cn } from "@/lib/utils";
import { viewStore } from "@/store/v2";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
interface Props {
className?: string;
......@@ -26,29 +26,39 @@ const MemoDisplaySettingMenu = observer(({ className }: Props) => {
<div className="w-full flex flex-row justify-between items-center">
<span className="text-sm shrink-0 mr-3 dark:text-zinc-400">{t("memo.direction")}</span>
<Select
value={viewStore.state.orderByTimeAsc}
onChange={(_, value) =>
value={viewStore.state.orderByTimeAsc.toString()}
onValueChange={(value) =>
viewStore.state.setPartial({
orderByTimeAsc: Boolean(value),
orderByTimeAsc: value === "true",
})
}
>
<Option value={false}>{t("memo.direction-desc")}</Option>
<Option value={true}>{t("memo.direction-asc")}</Option>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="false">{t("memo.direction-desc")}</SelectItem>
<SelectItem value="true">{t("memo.direction-asc")}</SelectItem>
</SelectContent>
</Select>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span className="text-sm shrink-0 mr-3 dark:text-zinc-400">{t("common.layout")}</span>
<Select
value={viewStore.state.layout}
onChange={(_, value) =>
onValueChange={(value) =>
viewStore.state.setPartial({
layout: value as "LIST" | "MASONRY",
})
}
>
<Option value={"LIST"}>{t("memo.list")}</Option>
<Option value={"MASONRY"}>{t("memo.masonry")}</Option>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="LIST">{t("memo.list")}</SelectItem>
<SelectItem value="MASONRY">{t("memo.masonry")}</SelectItem>
</SelectContent>
</Select>
</div>
</div>
......
import { Autocomplete, AutocompleteOption, Chip } from "@mui/joy";
import { Button, Checkbox } from "@usememos/mui";
import { uniqBy } from "lodash-es";
import { LinkIcon } from "lucide-react";
import { LinkIcon, X } from "lucide-react";
import React, { useContext, useState } from "react";
import { toast } from "react-hot-toast";
import useDebounce from "react-use/lib/useDebounce";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/Popover";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Command, CommandInput, CommandItem, CommandList, CommandEmpty } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { memoServiceClient } from "@/grpcweb";
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
import useCurrentUser from "@/hooks/useCurrentUser";
......@@ -129,52 +131,69 @@ const AddMemoRelationPopover = (props: Props) => {
return (
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
<PopoverTrigger className="relative">
<Button className="flex items-center justify-center p-0" variant="plain" asChild>
<LinkIcon className="w-5 h-5 mx-auto p-0" />
<PopoverTrigger asChild>
<Button variant="ghost">
<LinkIcon />
</Button>
</PopoverTrigger>
<PopoverContent align="center">
<div className="w-[16rem] p-1 flex flex-col justify-start items-start">
<Autocomplete
className="w-full"
size="md"
clearOnBlur
disableClearable
placeholder={t("reference.search-placeholder")}
noOptionsText={t("reference.no-memos-found")}
options={filteredMemos}
loading={isFetching}
inputValue={searchText}
value={selectedMemos}
multiple
onInputChange={(_, value) => setSearchText(value.trimStart())}
getOptionKey={(memo) => memo.name}
getOptionLabel={(memo) => memo.content}
isOptionEqualToValue={(memo, value) => memo.name === value.name}
renderOption={(props, memo) => (
<AutocompleteOption {...props} key={memo.name}>
<div className="w-full flex flex-col justify-start items-start">
{/* Selected memos display */}
{selectedMemos.length > 0 && (
<div className="w-full mb-2 flex flex-wrap gap-1">
{selectedMemos.map((memo) => (
<Badge key={memo.name} variant="outline" className="max-w-full flex items-center gap-1 p-2">
<div className="flex-1 min-w-0">
<p className="text-xs text-gray-400 select-none">{memo.displayTime?.toLocaleString()}</p>
<p className="mt-0.5 text-sm leading-5 line-clamp-2">{searchText ? getHighlightedContent(memo.content) : memo.snippet}</p>
<span className="text-sm leading-5 truncate block">{memo.content}</span>
</div>
<X
className="w-3 h-3 cursor-pointer hover:text-red-500 flex-shrink-0"
onClick={() => setSelectedMemos((memos) => memos.filter((m) => m.name !== memo.name))}
/>
</Badge>
))}
</div>
</AutocompleteOption>
)}
renderTags={(memos) =>
memos.map((memo) => (
<Chip key={memo.name} className="max-w-full! rounded!" variant="outlined" color="neutral">
{/* Search and selection interface */}
<Command className="w-full">
<CommandInput
placeholder={t("reference.search-placeholder")}
value={searchText}
onValueChange={setSearchText}
className="h-9"
/>
<CommandList className="max-h-[200px]">
<CommandEmpty>{isFetching ? "Loading..." : t("reference.no-memos-found")}</CommandEmpty>
{filteredMemos.map((memo) => (
<CommandItem
key={memo.name}
value={memo.name}
onSelect={() => {
setSelectedMemos((prev) => [...prev, memo]);
}}
className="cursor-pointer"
>
<div className="w-full flex flex-col justify-start items-start">
<p className="text-xs text-gray-400 select-none">{memo.displayTime?.toLocaleString()}</p>
<span className="w-full text-sm leading-5 truncate">{memo.content}</span>
<p className="mt-0.5 text-sm leading-5 line-clamp-2">
{searchText ? getHighlightedContent(memo.content) : memo.snippet}
</p>
</div>
</Chip>
))
}
onChange={(_, value) => setSelectedMemos(value)}
/>
</CommandItem>
))}
</CommandList>
</Command>
<div className="mt-2 w-full flex flex-row justify-end items-center gap-2">
<Checkbox size="sm" label={"Embed"} checked={embedded} onChange={(e) => setEmbedded(e.target.checked)} />
<Button color="primary" onClick={addMemoRelations} disabled={selectedMemos.length === 0}>
<div className="flex items-center space-x-2">
<Checkbox id="embed-checkbox" checked={embedded} onCheckedChange={(checked) => setEmbedded(checked === true)} />
<label htmlFor="embed-checkbox" className="text-sm">
Embed
</label>
</div>
<Button onClick={addMemoRelations} disabled={selectedMemos.length === 0}>
{t("common.add")}
</Button>
</div>
......
import { Button, Input } from "@usememos/mui";
import { LatLng } from "leaflet";
import { MapPinIcon, XIcon } from "lucide-react";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import LeafletMap from "@/components/LeafletMap";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/Popover";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Location } from "@/types/proto/api/v1/memo_service";
import { useTranslate } from "@/utils/i18n";
......@@ -86,6 +87,7 @@ const LocationSelector = (props: Props) => {
};
const removeLocation = (e: React.MouseEvent) => {
console.log("here");
e.preventDefault();
e.stopPropagation();
props.onChange(undefined);
......@@ -94,36 +96,40 @@ const LocationSelector = (props: Props) => {
return (
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
<PopoverTrigger asChild>
<Button className="flex items-center justify-center p-0" size="sm" variant="plain">
<Button variant="ghost" asChild>
<div>
<MapPinIcon className="w-5 h-5 mx-auto shrink-0" />
{props.location && (
<>
<span className="ml-0.5 text-sm text-ellipsis whitespace-nowrap overflow-hidden max-w-32">{props.location.placeholder}</span>
<XIcon className="w-5 h-5 mx-auto shrink-0 hidden group-hover:block opacity-60 hover:opacity-80" onClick={removeLocation} />
<span className="ml-0.5 text-sm text-ellipsis whitespace-nowrap overflow-hidden max-w-32">
{props.location.placeholder}
</span>
<XIcon className="w-5 h-5 mx-auto shrink-0 opacity-60 hover:opacity-80" onClick={removeLocation} />
</>
)}
</div>
</Button>
</PopoverTrigger>
<PopoverContent align="center">
<div className="min-w-80 sm:w-lg p-1 flex flex-col justify-start items-start">
<LeafletMap key={JSON.stringify(state.initilized)} latlng={state.position} onChange={onPositionChanged} />
<div className="mt-2 w-full flex flex-row justify-between items-center gap-2">
<div className="flex flex-row items-center justify-start gap-2">
<div className="flex flex-row items-center justify-start gap-2 w-full">
<div className="relative flex-1">
{state.position && (
<div className="absolute left-2 top-1/2 -translate-y-1/2 text-xs leading-6 opacity-60 z-10">
[{state.position.lat.toFixed(2)}, {state.position.lng.toFixed(2)}]
</div>
)}
<Input
placeholder="Choose a position first."
value={state.placeholder}
size="sm"
startDecorator={
state.position && (
<div className="text-xs leading-6 opacity-60">
[{state.position.lat.toFixed(2)}, {state.position.lng.toFixed(2)}]
</div>
)
}
disabled={!state.position}
className={state.position ? "pl-24" : ""}
onChange={(e) => setState((state) => ({ ...state, placeholder: e.target.value }))}
/>
</div>
</div>
<Button
className="shrink-0"
color="primary"
......
import { Link } from "@mui/joy";
import { Button } from "@usememos/mui";
import { CheckSquareIcon, Code2Icon, SquareSlashIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useTranslate } from "@/utils/i18n";
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/Popover";
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover";
import { EditorRefActions } from "../Editor";
interface Props {
......@@ -10,9 +9,8 @@ interface Props {
}
const MarkdownMenu = (props: Props) => {
const t = useTranslate();
const { editorRef } = props;
const t = useTranslate();
const handleCodeBlockClick = () => {
if (!editorRef.current) {
......@@ -64,8 +62,8 @@ const MarkdownMenu = (props: Props) => {
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="plain" className="p-0">
<SquareSlashIcon className="w-5 h-5" />
<Button variant="ghost">
<SquareSlashIcon />
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="text-sm p-1">
......@@ -85,9 +83,14 @@ const MarkdownMenu = (props: Props) => {
<span>{t("markdown.checkbox")}</span>
</button>
<div className="pl-2">
<Link fontSize={12} href="https://www.usememos.com/docs/getting-started/content-syntax" target="_blank">
<a
className="text-xs text-blue-600 hover:underline"
href="https://www.usememos.com/docs/getting-started/content-syntax"
target="_blank"
rel="noopener noreferrer"
>
{t("markdown.content-syntax")}
</Link>
</a>
</div>
</div>
</PopoverContent>
......
import { Button } from "@usememos/mui";
import { HashIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import OverflowTip from "@/components/kit/OverflowTip";
import { Button } from "@/components/ui/button";
import { userStore } from "@/store/v2";
import { useTranslate } from "@/utils/i18n";
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/Popover";
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover";
import { EditorRefActions } from "../Editor";
interface Props {
......@@ -35,8 +35,8 @@ const TagSelector = observer((props: Props) => {
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="plain" className="p-0">
<HashIcon className="w-5 h-5" />
<Button variant="ghost">
<HashIcon />
</Button>
</PopoverTrigger>
<PopoverContent align="start" sideOffset={2}>
......
import { Button } from "@usememos/mui";
import { LoaderIcon, PaperclipIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useContext, useRef, useState } from "react";
import toast from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { attachmentStore } from "@/store/v2";
import { Attachment } from "@/types/proto/api/v1/attachment_service";
import { MemoEditorContext } from "../types";
......@@ -73,7 +73,7 @@ const UploadAttachmentButton = observer((props: Props) => {
const isUploading = state.uploadingFlag || props.isUploading;
return (
<Button className="relative p-0" variant="plain" disabled={isUploading}>
<Button className="relative" variant="ghost" disabled={isUploading}>
{isUploading ? <LoaderIcon className="w-5 h-5 animate-spin" /> : <PaperclipIcon className="w-5 h-5" />}
<input
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
......
import { ChevronDownIcon } from "lucide-react";
import { useState } from "react";
import VisibilityIcon from "@/components/VisibilityIcon";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/Popover";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import { Visibility } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
interface Props {
......
......@@ -3,8 +3,8 @@ import { observer } from "mobx-react-lite";
import { useEffect, useRef, useState } from "react";
import getCaretCoordinates from "textarea-caret";
import OverflowTip from "@/components/kit/OverflowTip";
import { cn } from "@/lib/utils";
import { userStore } from "@/store/v2";
import { cn } from "@/utils";
import { EditorRefActions } from ".";
type Props = {
......
import { last } from "lodash-es";
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
import { markdownServiceClient } from "@/grpcweb";
import { cn } from "@/lib/utils";
import { Node, NodeType, OrderedListItemNode, TaskListItemNode, UnorderedListItemNode } from "@/types/proto/api/v1/markdown_service";
import { cn } from "@/utils";
import TagSuggestions from "./TagSuggestions";
export interface EditorRefActions {
......
import { Button } from "@usememos/mui";
import copy from "copy-to-clipboard";
import { isEqual } from "lodash-es";
import { LoaderIcon, SendIcon } from "lucide-react";
......@@ -7,17 +6,18 @@ import React, { useEffect, useMemo, useRef, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import useLocalStorage from "react-use/lib/useLocalStorage";
import { Button } from "@/components/ui/button";
import { memoServiceClient } from "@/grpcweb";
import { TAB_SPACE_WIDTH } from "@/helpers/consts";
import { isValidUrl } from "@/helpers/utils";
import useAsyncEffect from "@/hooks/useAsyncEffect";
import useCurrentUser from "@/hooks/useCurrentUser";
import { cn } from "@/lib/utils";
import { extractMemoIdFromName } from "@/store/common";
import { memoStore, attachmentStore, userStore, workspaceStore } from "@/store/v2";
import { Attachment } from "@/types/proto/api/v1/attachment_service";
import { Location, Memo, MemoRelation, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_service";
import { UserSetting } from "@/types/proto/api/v1/user_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
import { convertVisibilityFromString } from "@/utils/memo";
import DateTimeInput from "../DateTimeInput";
......@@ -503,7 +503,7 @@ const MemoEditor = observer((props: Props) => {
<AttachmentListView attachmentList={state.attachmentList} setAttachmentList={handleSetAttachmentList} />
<RelationListView relationList={referenceRelations} setRelationList={handleSetRelationList} />
<div className="relative w-full flex flex-row justify-between items-center py-1" onFocus={(e) => e.stopPropagation()}>
<div className="flex flex-row justify-start items-center opacity-80 dark:opacity-60 space-x-2">
<div className="flex flex-row justify-start items-center opacity-80 dark:opacity-60 -space-x-2">
<TagSelector editorRef={editorRef} />
<MarkdownMenu editorRef={editorRef} />
<UploadAttachmentButton isUploading={state.isUploadingAttachment} />
......@@ -520,7 +520,7 @@ const MemoEditor = observer((props: Props) => {
</div>
<div className="shrink-0 -mr-1 flex flex-row justify-end items-center">
{props.onCancel && (
<Button variant="plain" className="opacity-60" disabled={state.isRequesting} onClick={handleCancelBtnClick}>
<Button variant="ghost" className="opacity-60" disabled={state.isRequesting} onClick={handleCancelBtnClick}>
{t("common.cancel")}
</Button>
)}
......
......@@ -3,7 +3,7 @@ import { MapPinIcon } from "lucide-react";
import { useState } from "react";
import { Location } from "@/types/proto/api/v1/memo_service";
import LeafletMap from "./LeafletMap";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
interface Props {
location: Location;
......
import { useColorScheme } from "@mui/joy";
import { useEffect, useRef, useState } from "react";
import ForceGraph2D, { ForceGraphMethods, LinkObject, NodeObject } from "react-force-graph-2d";
import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { extractMemoIdFromName } from "@/store/common";
import { Memo, MemoRelation_Type } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
import { LinkType, NodeType } from "./types";
import { convertMemoRelationsToGraphData } from "./utils";
......@@ -19,11 +18,30 @@ const DEFAULT_NODE_COLOR = "#a1a1aa";
const MemoRelationForceGraph = ({ className, memo, parentPage }: Props) => {
const navigateTo = useNavigateTo();
const { mode } = useColorScheme();
const [mode, setMode] = useState<"light" | "dark">("light");
const containerRef = useRef<HTMLDivElement>(null);
const graphRef = useRef<ForceGraphMethods<NodeObject<NodeType>, LinkObject<NodeType, LinkType>> | undefined>(undefined);
const [graphSize, setGraphSize] = useState({ width: 0, height: 0 });
// Simple dark mode detection
useEffect(() => {
const updateMode = () => {
const isDark = document.documentElement.classList.contains("dark");
setMode(isDark ? "dark" : "light");
};
updateMode();
// Watch for changes to the dark class
const observer = new MutationObserver(updateMode);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
});
return () => observer.disconnect();
}, []);
useEffect(() => {
if (!containerRef.current) return;
setGraphSize(containerRef.current.getBoundingClientRect());
......
import { LinkIcon, MilestoneIcon } from "lucide-react";
import { memo, useState } from "react";
import { Link } from "react-router-dom";
import { cn } from "@/lib/utils";
import { extractMemoIdFromName } from "@/store/common";
import { Memo, MemoRelation } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
interface Props {
......
import { Tooltip } from "@mui/joy";
import { BookmarkIcon, EyeOffIcon, MessageCircleMoreIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { memo, useCallback, useState } from "react";
import { Link, useLocation } from "react-router-dom";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import useAsyncEffect from "@/hooks/useAsyncEffect";
import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { memoStore, userStore, workspaceStore } from "@/store/v2";
import { State } from "@/types/proto/api/v1/common";
import { Memo, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
import { convertVisibilityToString } from "@/utils/memo";
import { isSuperUser } from "@/utils/user";
......@@ -170,10 +170,13 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
<div className="flex flex-row justify-end items-center select-none shrink-0 gap-2">
<div className="w-auto invisible group-hover:visible flex flex-row justify-between items-center gap-2">
{props.showVisibility && memo.visibility !== Visibility.PRIVATE && (
<Tooltip title={t(`memo.visibility.${convertVisibilityToString(memo.visibility).toLowerCase()}` as any)} placement="top">
<Tooltip>
<TooltipTrigger>
<span className="flex justify-center items-center hover:opacity-70">
<VisibilityIcon visibility={memo.visibility} />
</span>
</TooltipTrigger>
<TooltipContent>{t(`memo.visibility.${convertVisibilityToString(memo.visibility).toLowerCase()}` as any)}</TooltipContent>
</Tooltip>
)}
{currentUser && !isArchived && <ReactionSelector className="border-none w-auto h-auto" memo={memo} />}
......@@ -195,11 +198,18 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
</Link>
)}
{props.showPinned && memo.pinned && (
<Tooltip title={t("common.unpin")} placement="top">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span className="cursor-pointer">
<BookmarkIcon className="w-4 h-auto text-amber-500" onClick={onPinIconClick} />
</span>
</TooltipTrigger>
<TooltipContent>
<p>{t("common.unpin")}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{nsfw && showNSFWContent && (
<span className="cursor-pointer">
......
import useWindowScroll from "react-use/lib/useWindowScroll";
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { cn } from "@/utils";
import { cn } from "@/lib/utils";
import NavigationDrawer from "./NavigationDrawer";
interface Props {
......
import { Tooltip } from "@mui/joy";
import { EarthIcon, LibraryIcon, PaperclipIcon, UserCircleIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useEffect } from "react";
import { NavLink } from "react-router-dom";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import useCurrentUser from "@/hooks/useCurrentUser";
import { cn } from "@/lib/utils";
import { Routes } from "@/router";
import { userStore } from "@/store/v2";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
import BrandBanner from "./BrandBanner";
import UserBanner from "./UserBanner";
......@@ -89,9 +89,16 @@ const Navigation = observer((props: Props) => {
viewTransition
>
{props.collapsed ? (
<Tooltip title={navLink.title} placement="right" arrow>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div>{navLink.icon}</div>
</TooltipTrigger>
<TooltipContent side="right">
<p>{navLink.title}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
navLink.icon
)}
......
import { Drawer } from "@mui/joy";
import { Button } from "@usememos/mui";
import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { workspaceStore } from "@/store/v2";
import Navigation from "./Navigation";
import UserAvatar from "./UserAvatar";
......@@ -18,28 +18,20 @@ const NavigationDrawer = observer(() => {
setOpen(false);
}, [location.pathname]);
const toggleDrawer = (inOpen: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
if (event.type === "keydown" && ((event as React.KeyboardEvent).key === "Tab" || (event as React.KeyboardEvent).key === "Shift")) {
return;
}
setOpen(inOpen);
};
return (
<>
<Button variant="plain" className="px-2" onClick={toggleDrawer(true)}>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button variant="ghost" className="px-2">
<UserAvatar className="shrink-0 w-6 h-6 rounded-md" avatarUrl={avatarUrl} />
<span className="font-bold text-lg leading-10 ml-2 text-ellipsis shrink-0 cursor-pointer overflow-hidden text-gray-700 dark:text-gray-300">
{title}
</span>
</Button>
<Drawer anchor="left" size="sm" open={open} onClose={toggleDrawer(false)}>
<div className="w-full h-full overflow-auto px-2 bg-zinc-100 dark:bg-zinc-900">
</SheetTrigger>
<SheetContent side="left" className="w-full sm:w-80 overflow-auto px-2 bg-zinc-100 dark:bg-zinc-900">
<Navigation />
</div>
</Drawer>
</>
</SheetContent>
</Sheet>
);
});
......
import { Button } from "@usememos/mui";
import { ArrowUpIcon, LoaderIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useCallback, useEffect, useRef, useState } from "react";
import { matchPath } from "react-router-dom";
import PullToRefresh from "react-simple-pull-to-refresh";
import { Button } from "@/components/ui/button";
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { Routes } from "@/router";
......@@ -220,7 +220,7 @@ const BackToTop = () => {
}
return (
<Button variant="plain" onClick={scrollToTop}>
<Button variant="ghost" onClick={scrollToTop}>
{t("router.back-to-top")}
<ArrowUpIcon className="ml-1 w-4 h-auto" />
</Button>
......
import { Button, Input } from "@usememos/mui";
import { LoaderIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { ClientError } from "nice-grpc-web";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { authServiceClient } from "@/grpcweb";
import useLoading from "@/hooks/useLoading";
import useNavigateTo from "@/hooks/useNavigateTo";
......@@ -62,8 +63,7 @@ const PasswordSignInForm = observer(() => {
<div className="w-full flex flex-col justify-start items-start">
<span className="leading-8 text-gray-600">{t("common.username")}</span>
<Input
className="w-full bg-white dark:bg-black"
size="lg"
className="w-full bg-white dark:bg-black h-10"
type="text"
readOnly={actionBtnLoadingState.isLoading}
placeholder={t("common.username")}
......@@ -78,8 +78,7 @@ const PasswordSignInForm = observer(() => {
<div className="w-full flex flex-col justify-start items-start">
<span className="leading-8 text-gray-600">{t("common.password")}</span>
<Input
className="w-full bg-white dark:bg-black"
size="lg"
className="w-full bg-white dark:bg-black h-10"
type="password"
readOnly={actionBtnLoadingState.isLoading}
placeholder={t("common.password")}
......@@ -93,14 +92,7 @@ const PasswordSignInForm = observer(() => {
</div>
</div>
<div className="flex flex-row justify-end items-center w-full mt-6">
<Button
type="submit"
color="primary"
size="lg"
fullWidth
disabled={actionBtnLoadingState.isLoading}
onClick={handleSignInButtonClick}
>
<Button type="submit" className="w-full h-10" disabled={actionBtnLoadingState.isLoading} onClick={handleSignInButtonClick}>
{t("common.sign-in")}
{actionBtnLoadingState.isLoading && <LoaderIcon className="w-5 h-auto ml-2 animate-spin opacity-60" />}
</Button>
......
import { Button } from "@usememos/mui";
import { XIcon } from "lucide-react";
import React, { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { generateDialog } from "./Dialog";
const MIN_SCALE = 0.5;
......
......@@ -4,10 +4,10 @@ import { useRef, useState } from "react";
import useClickAway from "react-use/lib/useClickAway";
import { memoServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import { cn } from "@/lib/utils";
import { memoStore, workspaceStore } from "@/store/v2";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
interface Props {
memo: Memo;
......
import { Tooltip } from "@mui/joy";
import { observer } from "mobx-react-lite";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { memoServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import { cn } from "@/lib/utils";
import { memoStore } from "@/store/v2";
import { State } from "@/types/proto/api/v1/common";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { User } from "@/types/proto/api/v1/user_service";
import { cn } from "@/utils";
interface Props {
memo: Memo;
......@@ -65,7 +65,9 @@ const ReactionView = observer((props: Props) => {
};
return (
<Tooltip title={stringifyUsers(users, reactionType)} placement="top">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div
className={cn(
"h-7 border border-zinc-200 px-2 py-0.5 rounded-full flex flex-row justify-center items-center gap-1 dark:border-zinc-700",
......@@ -78,7 +80,12 @@ const ReactionView = observer((props: Props) => {
<span>{reactionType}</span>
<span className="opacity-60">{users.length}</span>
</div>
</TooltipTrigger>
<TooltipContent>
<p>{stringifyUsers(users, reactionType)}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
});
......
import { List, ListItem } from "@mui/joy";
import { Button, Input } from "@usememos/mui";
import { XIcon } from "lucide-react";
import React, { useState } from "react";
import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { memoServiceClient } from "@/grpcweb";
import useLoading from "@/hooks/useLoading";
import { useTranslate } from "@/utils/i18n";
......@@ -50,7 +50,7 @@ const RenameTagDialog: React.FC<Props> = (props: Props) => {
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
<p className="title-text">{t("tag.rename-tag")}</p>
<Button variant="plain" onClick={() => destroy()}>
<Button variant="ghost" onClick={() => destroy()}>
<XIcon className="w-5 h-auto" />
</Button>
</div>
......@@ -64,17 +64,17 @@ const RenameTagDialog: React.FC<Props> = (props: Props) => {
<span className="w-20 text-sm whitespace-nowrap shrink-0 text-right">{t("tag.new-name")}</span>
<Input className="w-full" type="text" placeholder="A new tag name" value={newName} onChange={handleTagNameInputChange} />
</div>
<List size="sm" marker="disc">
<ListItem>
<ul className="list-disc list-inside text-sm ml-4">
<li>
<p className="leading-5">{t("tag.rename-tip")}</p>
</ListItem>
</List>
</li>
</ul>
</div>
<div className="w-full flex flex-row justify-end items-center space-x-2">
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}>
<Button variant="ghost" disabled={requestState.isLoading} onClick={destroy}>
{t("common.cancel")}
</Button>
<Button color="primary" disabled={requestState.isLoading} onClick={handleConfirm}>
<Button disabled={requestState.isLoading} onClick={handleConfirm}>
{t("common.confirm")}
</Button>
</div>
......
import { SearchIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useState } from "react";
import { cn } from "@/lib/utils";
import { memoFilterStore } from "@/store/v2";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
import MemoDisplaySettingMenu from "./MemoDisplaySettingMenu";
......
import { Button } from "@usememos/mui";
import copy from "copy-to-clipboard";
import { ClipboardIcon, TrashIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import { UserAccessToken } from "@/types/proto/api/v1/user_service";
......@@ -99,7 +99,7 @@ const AccessTokenSection = () => {
<tr key={userAccessToken.accessToken}>
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-900 dark:text-gray-400 flex flex-row justify-start items-center gap-x-1">
<span className="font-mono">{getFormatedAccessToken(userAccessToken.accessToken)}</span>
<Button variant="plain" onClick={() => copyAccessToken(userAccessToken.accessToken)}>
<Button variant="ghost" onClick={() => copyAccessToken(userAccessToken.accessToken)}>
<ClipboardIcon className="w-4 h-auto text-gray-400" />
</Button>
</td>
......@@ -114,7 +114,7 @@ const AccessTokenSection = () => {
</td>
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm">
<Button
variant="plain"
variant="ghost"
onClick={() => {
handleDeleteAccessToken(userAccessToken);
}}
......
import { Radio, RadioGroup } from "@mui/joy";
import { Button, Input } from "@usememos/mui";
import { sortBy } from "lodash-es";
import { MoreVerticalIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import React, { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import { userStore } from "@/store/v2";
......@@ -12,7 +14,7 @@ import { State } from "@/types/proto/api/v1/common";
import { User, User_Role } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n";
import showCreateUserDialog from "../CreateUserDialog";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
interface LocalState {
creatingUser: User;
......@@ -167,15 +169,23 @@ const MemberSection = observer(() => {
</div>
<div className="flex flex-col justify-start items-start gap-1">
<span>{t("common.role")}</span>
<RadioGroup orientation="horizontal" defaultValue={User_Role.USER} onChange={handleUserRoleInputChange}>
<Radio value={User_Role.USER} label={t("setting.member-section.user")} />
<Radio value={User_Role.ADMIN} label={t("setting.member-section.admin")} />
<RadioGroup
defaultValue={User_Role.USER}
onValueChange={(value) => handleUserRoleInputChange({ target: { value } } as React.ChangeEvent<HTMLInputElement>)}
className="flex flex-row gap-4"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value={User_Role.USER} id="user-role" />
<Label htmlFor="user-role">{t("setting.member-section.user")}</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value={User_Role.ADMIN} id="admin-role" />
<Label htmlFor="admin-role">{t("setting.member-section.admin")}</Label>
</div>
</RadioGroup>
</div>
<div className="mt-2">
<Button color="primary" onClick={handleCreateUserBtnClick}>
{t("common.create")}
</Button>
<Button onClick={handleCreateUserBtnClick}>{t("common.create")}</Button>
</div>
</div>
<div className="w-full flex flex-row justify-between items-center mt-6">
......
import { Chip, ChipDelete } from "@mui/joy";
import { Button, Input, Switch } from "@usememos/mui";
import { isEqual, uniq } from "lodash-es";
import { CheckIcon } from "lucide-react";
import { CheckIcon, X } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { workspaceSettingNamePrefix } from "@/store/common";
import { workspaceStore } from "@/store/v2";
import { WorkspaceSettingKey } from "@/store/v2/workspace";
......@@ -70,42 +72,42 @@ const MemoRelatedSettings = observer(() => {
<span>{t("setting.system-section.disable-public-memos")}</span>
<Switch
checked={memoRelatedSetting.disallowPublicVisibility}
onChange={(event) => updatePartialSetting({ disallowPublicVisibility: event.target.checked })}
onCheckedChange={(checked) => updatePartialSetting({ disallowPublicVisibility: checked })}
/>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.system-section.display-with-updated-time")}</span>
<Switch
checked={memoRelatedSetting.displayWithUpdateTime}
onChange={(event) => updatePartialSetting({ displayWithUpdateTime: event.target.checked })}
onCheckedChange={(checked) => updatePartialSetting({ displayWithUpdateTime: checked })}
/>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.memo-related-settings.enable-link-preview")}</span>
<Switch
checked={memoRelatedSetting.enableLinkPreview}
onChange={(event) => updatePartialSetting({ enableLinkPreview: event.target.checked })}
onCheckedChange={(checked) => updatePartialSetting({ enableLinkPreview: checked })}
/>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.memo-related-settings.enable-memo-comments")}</span>
<Switch
checked={memoRelatedSetting.enableComment}
onChange={(event) => updatePartialSetting({ enableComment: event.target.checked })}
onCheckedChange={(checked) => updatePartialSetting({ enableComment: checked })}
/>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.system-section.enable-double-click-to-edit")}</span>
<Switch
checked={memoRelatedSetting.enableDoubleClickEdit}
onChange={(event) => updatePartialSetting({ enableDoubleClickEdit: event.target.checked })}
onCheckedChange={(checked) => updatePartialSetting({ enableDoubleClickEdit: checked })}
/>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.system-section.disable-markdown-shortcuts-in-editor")}</span>
<Switch
checked={memoRelatedSetting.disableMarkdownShortcuts}
onChange={(event) => updatePartialSetting({ disableMarkdownShortcuts: event.target.checked })}
onCheckedChange={(checked) => updatePartialSetting({ disableMarkdownShortcuts: checked })}
/>
</div>
<div className="w-full flex flex-row justify-between items-center">
......@@ -122,33 +124,27 @@ const MemoRelatedSettings = observer(() => {
<div className="mt-2 w-full flex flex-row flex-wrap gap-1">
{memoRelatedSetting.reactions.map((reactionType) => {
return (
<Chip
className="h-8!"
key={reactionType}
variant="outlined"
size="lg"
endDecorator={
<ChipDelete
onDelete={() => updatePartialSetting({ reactions: memoRelatedSetting.reactions.filter((r) => r !== reactionType) })}
/>
}
>
<Badge key={reactionType} variant="outline" className="h-8 flex items-center gap-1">
{reactionType}
</Chip>
<X
className="w-3 h-3 cursor-pointer hover:text-red-500"
onClick={() => updatePartialSetting({ reactions: memoRelatedSetting.reactions.filter((r) => r !== reactionType) })}
/>
</Badge>
);
})}
<div className="flex items-center gap-1">
<Input
className="w-32 rounded-full! pl-1!"
className="w-32"
placeholder={t("common.input")}
value={editingReaction}
onChange={(event) => setEditingReaction(event.target.value.trim())}
endDecorator={
/>
<CheckIcon
className="w-5 h-5 text-gray-500 dark:text-gray-400 cursor-pointer hover:text-teal-600"
onClick={() => upsertReaction()}
/>
}
/>
</div>
</div>
</div>
<div className="w-full">
......@@ -156,43 +152,37 @@ const MemoRelatedSettings = observer(() => {
<span>{t("setting.memo-related-settings.enable-blur-nsfw-content")}</span>
<Switch
checked={memoRelatedSetting.enableBlurNsfwContent}
onChange={(event) => updatePartialSetting({ enableBlurNsfwContent: event.target.checked })}
onCheckedChange={(checked) => updatePartialSetting({ enableBlurNsfwContent: checked })}
/>
</div>
<div className="mt-2 w-full flex flex-row flex-wrap gap-1">
{memoRelatedSetting.nsfwTags.map((nsfwTag) => {
return (
<Chip
className="h-8!"
key={nsfwTag}
variant="outlined"
size="lg"
endDecorator={
<ChipDelete
onDelete={() => updatePartialSetting({ nsfwTags: memoRelatedSetting.nsfwTags.filter((r) => r !== nsfwTag) })}
/>
}
>
<Badge key={nsfwTag} variant="outline" className="h-8 flex items-center gap-1">
{nsfwTag}
</Chip>
<X
className="w-3 h-3 cursor-pointer hover:text-red-500"
onClick={() => updatePartialSetting({ nsfwTags: memoRelatedSetting.nsfwTags.filter((r) => r !== nsfwTag) })}
/>
</Badge>
);
})}
<div className="flex items-center gap-1">
<Input
className="w-32 rounded-full! pl-1!"
className="w-32"
placeholder={t("common.input")}
value={editingNsfwTag}
onChange={(event) => setEditingNsfwTag(event.target.value.trim())}
endDecorator={
/>
<CheckIcon
className="w-5 h-5 text-gray-500 dark:text-gray-400 cursor-pointer hover:text-teal-600"
onClick={() => upsertNsfwTags()}
/>
}
/>
</div>
</div>
</div>
<div className="mt-2 w-full flex justify-end">
<Button color="primary" disabled={isEqual(memoRelatedSetting, originalSetting)} onClick={updateSetting}>
<Button disabled={isEqual(memoRelatedSetting, originalSetting)} onClick={updateSetting}>
{t("common.save")}
</Button>
</div>
......
import { Button } from "@usememos/mui";
import { MoreVerticalIcon, PenLineIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import useCurrentUser from "@/hooks/useCurrentUser";
import { useTranslate } from "@/utils/i18n";
import showChangeMemberPasswordDialog from "../ChangeMemberPasswordDialog";
import showUpdateAccountDialog from "../UpdateAccountDialog";
import UserAvatar from "../UserAvatar";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import AccessTokenSection from "./AccessTokenSection";
import UserSessionsSection from "./UserSessionsSection";
......@@ -27,13 +27,13 @@ const MyAccountSection = () => {
</div>
</div>
<div className="w-full flex flex-row justify-start items-center mt-2 space-x-2">
<Button variant="outlined" onClick={showUpdateAccountDialog}>
<Button variant="outline" onClick={showUpdateAccountDialog}>
<PenLineIcon className="w-4 h-4 mx-auto mr-1" />
{t("common.edit")}
</Button>
<Popover>
<PopoverTrigger asChild>
<Button variant="outlined">
<Button variant="outline">
<MoreVerticalIcon className="w-4 h-4 mx-auto" />
</Button>
</PopoverTrigger>
......
import { Divider, Option, Select } from "@mui/joy";
import { observer } from "mobx-react-lite";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { userStore } from "@/store/v2";
import { Visibility } from "@/types/proto/api/v1/memo_service";
import { UserSetting } from "@/types/proto/api/v1/user_service";
......@@ -44,27 +45,26 @@ const PreferencesSection = observer(() => {
<div className="w-full flex flex-row justify-between items-center">
<span className="truncate">{t("setting.preference-section.default-memo-visibility")}</span>
<Select
className="min-w-fit!"
value={setting.memoVisibility}
startDecorator={<VisibilityIcon visibility={convertVisibilityFromString(setting.memoVisibility)} />}
onChange={(_, visibility) => {
if (visibility) {
handleDefaultMemoVisibilityChanged(visibility);
}
}}
>
<Select value={setting.memoVisibility} onValueChange={handleDefaultMemoVisibilityChanged}>
<SelectTrigger className="min-w-fit">
<div className="flex items-center gap-2">
<VisibilityIcon visibility={convertVisibilityFromString(setting.memoVisibility)} />
<SelectValue />
</div>
</SelectTrigger>
<SelectContent>
{[Visibility.PRIVATE, Visibility.PROTECTED, Visibility.PUBLIC]
.map((v) => convertVisibilityToString(v))
.map((item) => (
<Option key={item} value={item} className="whitespace-nowrap">
<SelectItem key={item} value={item} className="whitespace-nowrap">
{t(`memo.visibility.${item.toLowerCase() as Lowercase<typeof item>}`)}
</Option>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<Divider className="my-3!" />
<Separator className="my-3" />
<WebhookSection />
</div>
......
import { Divider, List, ListItem } from "@mui/joy";
import { Button } from "@usememos/mui";
import { MoreVerticalIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { identityProviderServiceClient } from "@/grpcweb";
import { IdentityProvider } from "@/types/proto/api/v1/idp_service";
import { useTranslate } from "@/utils/i18n";
import showCreateIdentityProviderDialog from "../CreateIdentityProviderDialog";
import LearnMore from "../LearnMore";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
const SSOSection = () => {
const t = useTranslate();
......@@ -48,7 +48,7 @@ const SSOSection = () => {
{t("common.create")}
</Button>
</div>
<Divider />
<Separator />
{identityProviderList.map((identityProvider) => (
<div
key={identityProvider.name}
......@@ -95,8 +95,8 @@ const SSOSection = () => {
<div className="w-full mt-4">
<p className="text-sm">{t("common.learn-more")}:</p>
<List component="ul" marker="disc" size="sm">
<ListItem>
<ul className="list-disc list-inside text-sm ml-4">
<li>
<Link
className="text-sm text-blue-600 hover:underline"
to="https://www.usememos.com/docs/advanced-settings/sso"
......@@ -104,8 +104,8 @@ const SSOSection = () => {
>
{t("setting.sso-section.single-sign-on")}
</Link>
</ListItem>
</List>
</li>
</ul>
</div>
</div>
);
......
import { Divider, List, ListItem, Radio, RadioGroup, Tooltip } from "@mui/joy";
import { Button, Input, Switch } from "@usememos/mui";
import { isEqual } from "lodash-es";
import { HelpCircleIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import React, { useEffect, useMemo, useState } from "react";
import { toast } from "react-hot-toast";
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { workspaceSettingNamePrefix } from "@/store/common";
import { workspaceStore } from "@/store/v2";
import { WorkspaceSettingKey } from "@/store/v2/workspace";
......@@ -131,23 +136,38 @@ const StorageSection = observer(() => {
<div className="w-full flex flex-col gap-2 pt-2 pb-4">
<div className="font-medium text-gray-700 dark:text-gray-500">{t("setting.storage-section.current-storage")}</div>
<RadioGroup
orientation="horizontal"
className="w-full"
value={workspaceStorageSetting.storageType}
onChange={(event) => {
handleStorageTypeChanged(event.target.value as WorkspaceStorageSetting_StorageType);
value={workspaceStorageSetting.storageType.toString()}
onValueChange={(value) => {
handleStorageTypeChanged(parseInt(value) as unknown as WorkspaceStorageSetting_StorageType);
}}
className="flex flex-row gap-4"
>
<Radio value={WorkspaceStorageSetting_StorageType.DATABASE} label={t("setting.storage-section.type-database")} />
<Radio value={WorkspaceStorageSetting_StorageType.LOCAL} label={t("setting.storage-section.type-local")} />
<Radio value={WorkspaceStorageSetting_StorageType.S3} label={"S3"} />
<div className="flex items-center space-x-2">
<RadioGroupItem value={WorkspaceStorageSetting_StorageType.DATABASE.toString()} id="database" />
<Label htmlFor="database">{t("setting.storage-section.type-database")}</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value={WorkspaceStorageSetting_StorageType.LOCAL.toString()} id="local" />
<Label htmlFor="local">{t("setting.storage-section.type-local")}</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value={WorkspaceStorageSetting_StorageType.S3.toString()} id="s3" />
<Label htmlFor="s3">S3</Label>
</div>
</RadioGroup>
<div className="w-full flex flex-row justify-between items-center">
<div className="flex flex-row items-center">
<span className="text-gray-700 dark:text-gray-500 mr-1">{t("setting.system-section.max-upload-size")}</span>
<Tooltip title={t("setting.system-section.max-upload-size-hint")} placement="top">
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<HelpCircleIcon className="w-4 h-auto" />
</TooltipTrigger>
<TooltipContent>
<p>{t("setting.system-section.max-upload-size-hint")}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<Input className="w-16 font-mono" value={workspaceStorageSetting.uploadSizeLimitMb} onChange={handleMaxUploadSizeChanged} />
</div>
......@@ -189,20 +209,23 @@ const StorageSection = observer(() => {
</div>
<div className="w-full flex flex-row justify-between items-center">
<span className="text-gray-700 dark:text-gray-500 mr-1">Use Path Style</span>
<Switch checked={workspaceStorageSetting.s3Config?.usePathStyle} onChange={handleS3ConfigUsePathStyleChanged} />
<Switch
checked={workspaceStorageSetting.s3Config?.usePathStyle}
onCheckedChange={(checked) => handleS3ConfigUsePathStyleChanged({ target: { checked } } as any)}
/>
</div>
</>
)}
<div>
<Button color="primary" disabled={!allowSaveStorageSetting} onClick={saveWorkspaceStorageSetting}>
<Button disabled={!allowSaveStorageSetting} onClick={saveWorkspaceStorageSetting}>
{t("common.save")}
</Button>
</div>
<Divider className="my-2!" />
<Separator className="my-2" />
<div className="w-full mt-4">
<p className="text-sm">{t("common.learn-more")}:</p>
<List component="ul" marker="disc" size="sm">
<ListItem>
<ul className="text-sm list-disc ml-4 space-y-1">
<li>
<Link
className="text-sm text-blue-600 hover:underline"
to="https://www.usememos.com/docs/advanced-settings/local-storage"
......@@ -210,8 +233,8 @@ const StorageSection = observer(() => {
>
Docs - Local storage
</Link>
</ListItem>
<ListItem>
</li>
<li>
<Link
className="text-sm text-blue-600 hover:underline"
to="https://www.usememos.com/blog/choosing-a-storage-for-your-resource"
......@@ -219,8 +242,8 @@ const StorageSection = observer(() => {
>
Choosing a Storage for Your Resource: Database, S3 or Local Storage?
</Link>
</ListItem>
</List>
</li>
</ul>
</div>
</div>
);
......
import { Button } from "@usememos/mui";
import { ClockIcon, MonitorIcon, SmartphoneIcon, TabletIcon, TrashIcon, WifiIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import { UserSession } from "@/types/proto/api/v1/user_service";
......@@ -124,7 +124,7 @@ const UserSessionsSection = () => {
</td>
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm">
<Button
variant="plain"
variant="ghost"
disabled={isCurrentSession(userSession)}
onClick={() => {
handleRevokeSession(userSession);
......
import { Button } from "@usememos/mui";
import { ExternalLinkIcon, TrashIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { webhookServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import { Webhook } from "@/types/proto/api/v1/webhook_service";
......@@ -85,7 +85,7 @@ const WebhookSection = () => {
</td>
<td className="relative whitespace-nowrap px-3 py-2 text-right text-sm">
<Button
variant="plain"
variant="ghost"
onClick={() => {
handleDeleteWebhook(webhook);
}}
......
import { Select, Option, Divider } from "@mui/joy";
import { Button, Textarea, Switch } from "@usememos/mui";
import { isEqual } from "lodash-es";
import { ExternalLinkIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { identityProviderServiceClient } from "@/grpcweb";
import { workspaceSettingNamePrefix } from "@/store/common";
import { workspaceStore } from "@/store/v2";
......@@ -71,19 +74,18 @@ const WorkspaceSection = observer(() => {
{t("setting.system-section.server-name")}:{" "}
<span className="font-mono font-bold">{workspaceGeneralSetting.customProfile?.title || "Memos"}</span>
</div>
<Button variant="outlined" onClick={handleUpdateCustomizedProfileButtonClick}>
<Button variant="outline" onClick={handleUpdateCustomizedProfileButtonClick}>
{t("common.edit")}
</Button>
</div>
<Divider />
<Separator />
<p className="font-medium text-gray-700 dark:text-gray-500">{t("setting.system-section.title")}</p>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.system-section.additional-style")}</span>
</div>
<Textarea
className="font-mono"
className="font-mono w-full"
rows={3}
fullWidth
placeholder={t("setting.system-section.additional-style-placeholder")}
value={workspaceGeneralSetting.additionalStyle}
onChange={(event) => updatePartialSetting({ additionalStyle: event.target.value })}
......@@ -92,9 +94,8 @@ const WorkspaceSection = observer(() => {
<span>{t("setting.system-section.additional-script")}</span>
</div>
<Textarea
className="font-mono"
className="font-mono w-full"
rows={3}
fullWidth
placeholder={t("setting.system-section.additional-script-placeholder")}
value={workspaceGeneralSetting.additionalScript}
onChange={(event) => updatePartialSetting({ additionalScript: event.target.value })}
......@@ -114,7 +115,7 @@ const WorkspaceSection = observer(() => {
<Switch
disabled={workspaceStore.state.profile.mode === "demo"}
checked={workspaceGeneralSetting.disallowUserRegistration}
onChange={(event) => updatePartialSetting({ disallowUserRegistration: event.target.checked })}
onCheckedChange={(checked) => updatePartialSetting({ disallowUserRegistration: checked })}
/>
</div>
<div className="w-full flex flex-row justify-between items-center">
......@@ -125,39 +126,43 @@ const WorkspaceSection = observer(() => {
(identityProviderList.length === 0 && !workspaceGeneralSetting.disallowPasswordAuth)
}
checked={workspaceGeneralSetting.disallowPasswordAuth}
onChange={(event) => updatePartialSetting({ disallowPasswordAuth: event.target.checked })}
onCheckedChange={(checked) => updatePartialSetting({ disallowPasswordAuth: checked })}
/>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.workspace-section.disallow-change-username")}</span>
<Switch
checked={workspaceGeneralSetting.disallowChangeUsername}
onChange={(event) => updatePartialSetting({ disallowChangeUsername: event.target.checked })}
onCheckedChange={(checked) => updatePartialSetting({ disallowChangeUsername: checked })}
/>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.workspace-section.disallow-change-nickname")}</span>
<Switch
checked={workspaceGeneralSetting.disallowChangeNickname}
onChange={(event) => updatePartialSetting({ disallowChangeNickname: event.target.checked })}
onCheckedChange={(checked) => updatePartialSetting({ disallowChangeNickname: checked })}
/>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span className="truncate">{t("setting.workspace-section.week-start-day")}</span>
<Select
className="min-w-fit!"
value={workspaceGeneralSetting.weekStartDayOffset}
onChange={(_, weekStartDayOffset) => {
updatePartialSetting({ weekStartDayOffset: weekStartDayOffset || 0 });
value={workspaceGeneralSetting.weekStartDayOffset.toString()}
onValueChange={(value) => {
updatePartialSetting({ weekStartDayOffset: parseInt(value) || 0 });
}}
>
<Option value={-1}>{t("setting.workspace-section.saturday")}</Option>
<Option value={0}>{t("setting.workspace-section.sunday")}</Option>
<Option value={1}>{t("setting.workspace-section.monday")}</Option>
<SelectTrigger className="min-w-fit">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="-1">{t("setting.workspace-section.saturday")}</SelectItem>
<SelectItem value="0">{t("setting.workspace-section.sunday")}</SelectItem>
<SelectItem value="1">{t("setting.workspace-section.monday")}</SelectItem>
</SelectContent>
</Select>
</div>
<div className="mt-2 w-full flex justify-end">
<Button color="primary" disabled={isEqual(workspaceGeneralSetting, originalSetting)} onClick={handleSaveGeneralSetting}>
<Button disabled={isEqual(workspaceGeneralSetting, originalSetting)} onClick={handleSaveGeneralSetting}>
{t("common.save")}
</Button>
</div>
......
import { Tooltip } from "@mui/joy";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import type { StatCardProps } from "@/types/statistics";
import { cn } from "@/utils";
export const StatCard = ({ icon, label, count, onClick, tooltip, className }: StatCardProps) => {
const content = (
......@@ -22,9 +22,14 @@ export const StatCard = ({ icon, label, count, onClick, tooltip, className }: St
if (tooltip) {
return (
<Tooltip title={tooltip} placement="top" arrow>
{content}
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>{content}</TooltipTrigger>
<TooltipContent>
<p>{tooltip}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
......
import { Button, Input, Textarea } from "@usememos/mui";
import { isEqual } from "lodash-es";
import { XIcon } from "lucide-react";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { convertFileToBase64 } from "@/helpers/utils";
import useCurrentUser from "@/hooks/useCurrentUser";
import { userStore, workspaceStore } from "@/store/v2";
......@@ -142,7 +144,7 @@ const UpdateAccountDialog = ({ destroy }: Props) => {
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
<p className="title-text">{t("setting.account-section.update-information")}</p>
<Button variant="plain" onClick={handleCloseBtnClick}>
<Button variant="ghost" onClick={handleCloseBtnClick}>
<XIcon className="w-5 h-auto" />
</Button>
</div>
......@@ -188,16 +190,14 @@ const UpdateAccountDialog = ({ destroy }: Props) => {
{t("common.email")}
<span className="text-sm text-gray-400 ml-1">({t("setting.account-section.email-note")})</span>
</p>
<Input fullWidth type="email" value={state.email} onChange={handleEmailChanged} />
<Input className="w-full" type="email" value={state.email} onChange={handleEmailChanged} />
<p className="text-sm">{t("common.description")}</p>
<Textarea rows={2} fullWidth value={state.description} onChange={handleDescriptionChanged} />
<Textarea className="w-full" rows={2} value={state.description} onChange={handleDescriptionChanged} />
<div className="w-full flex flex-row justify-end items-center pt-4 space-x-2">
<Button variant="plain" onClick={handleCloseBtnClick}>
<Button variant="ghost" onClick={handleCloseBtnClick}>
{t("common.cancel")}
</Button>
<Button color="primary" onClick={handleSaveBtnClick}>
{t("common.save")}
</Button>
<Button onClick={handleSaveBtnClick}>{t("common.save")}</Button>
</div>
</div>
</div>
......
import { Button, Input, Textarea } from "@usememos/mui";
import { XIcon } from "lucide-react";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { workspaceSettingNamePrefix } from "@/store/common";
import { workspaceStore } from "@/store/v2";
import { WorkspaceSettingKey } from "@/store/v2/workspace";
......@@ -99,7 +101,7 @@ const UpdateCustomizedProfileDialog = ({ destroy }: Props) => {
<div className="max-w-full shadow flex flex-col justify-start items-start bg-white dark:bg-zinc-800 dark:text-gray-300 p-4 rounded-lg">
<div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
<p className="title-text">{t("setting.system-section.customize-server.title")}</p>
<Button variant="plain" onClick={handleCloseButtonClick}>
<Button variant="ghost" onClick={handleCloseButtonClick}>
<XIcon className="w-5 h-auto" />
</Button>
</div>
......@@ -109,19 +111,19 @@ const UpdateCustomizedProfileDialog = ({ destroy }: Props) => {
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.icon-url")}</p>
<Input className="w-full" type="text" value={customProfile.logoUrl} onChange={handleLogoUrlChanged} />
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.description")}</p>
<Textarea rows={3} fullWidth value={customProfile.description} onChange={handleDescriptionChanged} />
<Textarea rows={3} value={customProfile.description} onChange={handleDescriptionChanged} />
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.locale")}</p>
<LocaleSelect className="w-full!" value={customProfile.locale} onChange={handleLocaleSelectChange} />
<p className="text-sm mb-1 mt-2">{t("setting.system-section.customize-server.appearance")}</p>
<AppearanceSelect className="w-full!" value={customProfile.appearance as Appearance} onChange={handleAppearanceSelectChange} />
<div className="mt-4 w-full flex flex-row justify-between items-center space-x-2">
<div className="flex flex-row justify-start items-center">
<Button variant="outlined" onClick={handleRestoreButtonClick}>
<Button variant="outline" onClick={handleRestoreButtonClick}>
{t("common.restore")}
</Button>
</div>
<div className="flex flex-row justify-end items-center gap-2">
<Button variant="plain" onClick={handleCloseButtonClick}>
<Button variant="ghost" onClick={handleCloseButtonClick}>
{t("common.cancel")}
</Button>
<Button color="primary" onClick={handleSaveButtonClick}>
......
import { cn } from "@/utils";
import { cn } from "@/lib/utils";
interface Props {
avatarUrl?: string;
......
......@@ -2,11 +2,11 @@ import { ArchiveIcon, LogOutIcon, User2Icon, SquareUserIcon, SettingsIcon, BellI
import { authServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { Routes } from "@/router";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
import UserAvatar from "./UserAvatar";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
interface Props {
collapsed?: boolean;
......
import { Globe2Icon, LockIcon, UsersIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { Visibility } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
interface Props {
visibility: Visibility;
......
import { Tooltip } from "@mui/joy";
import { useRef, useState, useEffect } from "react";
import { cn } from "@/utils";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
interface Props {
children: React.ReactNode;
......@@ -20,11 +20,20 @@ const OverflowTip = ({ children, className }: Props) => {
}, []);
return (
<Tooltip title={children} placement="top" arrow disableHoverListener={!isOverflowed}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div ref={textElementRef} className={cn("truncate", className)}>
{children}
</div>
</TooltipTrigger>
{isOverflowed && (
<TooltipContent>
<p>{children}</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
);
};
......
import * as PopoverPrimitive from "@radix-ui/react-popover";
import * as React from "react";
import { cn } from "@/utils";
import { cn } from "@/lib/utils";
const Popover = PopoverPrimitive.Root;
function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
}
const PopoverTrigger = PopoverPrimitive.Trigger;
function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
}
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
function PopoverContent({ className, align = "center", sideOffset = 4, ...props }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
data-slot="popover-content"
align={align}
sideOffset={sideOffset}
className={cn(
"z-2000 w-auto rounded-md bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 p-1 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-auto origin-(--radix-popover-content-transform-origin) rounded-md border p-1 shadow-md outline-hidden",
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
);
}
export { Popover, PopoverTrigger, PopoverContent };
function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
}
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
{
variants: {
variant: {
default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
);
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "span";
return <Comp data-slot="badge" className={cn(badgeVariants({ variant }), className)} {...props} />;
}
export { Badge, badgeVariants };
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-8 px-3 py-2 has-[>svg]:px-3",
sm: "h-7 rounded-md gap-1 px-2 has-[>svg]:px-2",
lg: "h-9 rounded-md px-4 has-[>svg]:px-3",
icon: "size-8",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
const Button = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}
>(({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return <Comp ref={ref} data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} />;
});
Button.displayName = "Button";
export { Button, buttonVariants };
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { CheckIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator data-slot="checkbox-indicator" className="flex items-center justify-center text-current transition-none">
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
);
}
export { Checkbox };
import { Command as CommandPrimitive } from "cmdk";
import { SearchIcon } from "lucide-react";
import * as React from "react";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { cn } from "@/lib/utils";
function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {
return (
<CommandPrimitive
data-slot="command"
className={cn("bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md", className)}
{...props}
/>
);
}
function CommandDialog({
title = "Command Palette",
description = "Search for a command to run...",
children,
className,
showCloseButton = true,
...props
}: React.ComponentProps<typeof Dialog> & {
title?: string;
description?: string;
className?: string;
showCloseButton?: boolean;
}) {
return (
<Dialog {...props}>
<DialogHeader className="sr-only">
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogContent className={cn("overflow-hidden p-0", className)} showCloseButton={showCloseButton}>
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
);
}
function CommandInput({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>) {
return (
<div data-slot="command-input-wrapper" className="flex h-9 items-center gap-2 border-b px-3">
<SearchIcon className="size-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
data-slot="command-input"
className={cn(
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
/>
</div>
);
}
function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {
return (
<CommandPrimitive.List
data-slot="command-list"
className={cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className)}
{...props}
/>
);
}
function CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
return <CommandPrimitive.Empty data-slot="command-empty" className="py-6 text-center text-sm" {...props} />;
}
function CommandGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>) {
return (
<CommandPrimitive.Group
data-slot="command-group"
className={cn(
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
className,
)}
{...props}
/>
);
}
function CommandSeparator({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Separator>) {
return <CommandPrimitive.Separator data-slot="command-separator" className={cn("bg-border -mx-1 h-px", className)} {...props} />;
}
function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {
return (
<CommandPrimitive.Item
data-slot="command-item"
className={cn(
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
}
function CommandShortcut({ className, ...props }: React.ComponentProps<"span">) {
return (
<span data-slot="command-shortcut" className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)} {...props} />
);
}
export { Command, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandShortcut, CommandSeparator };
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { XIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
}
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
}
function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className,
)}
{...props}
/>
);
}
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean;
}) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className,
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
);
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="dialog-header" className={cn("flex flex-col gap-2 text-center sm:text-left", className)} {...props} />;
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="dialog-footer" className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)} {...props} />;
}
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
return <DialogPrimitive.Title data-slot="dialog-title" className={cn("text-lg leading-none font-semibold", className)} {...props} />;
}
function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description data-slot="dialog-description" className={cn("text-muted-foreground text-sm", className)} {...props} />
);
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
};
import * as React from "react";
import { cn } from "@/lib/utils";
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
<input
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className,
)}
{...props}
/>
);
}
export { Input };
import * as LabelPrimitive from "@radix-ui/react-label";
import * as React from "react";
import { cn } from "@/lib/utils";
function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className,
)}
{...props}
/>
);
}
export { Label };
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
import { CircleIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
function RadioGroup({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return <RadioGroupPrimitive.Root data-slot="radio-group" className={cn("grid gap-3", className)} {...props} />;
}
function RadioGroupItem({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
return (
<RadioGroupPrimitive.Item
data-slot="radio-group-item"
className={cn(
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
>
<RadioGroupPrimitive.Indicator data-slot="radio-group-indicator" className="relative flex items-center justify-center">
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
}
export { RadioGroup, RadioGroupItem };
import * as SelectPrimitive from "@radix-ui/react-select";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />;
}
function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
}
function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
}
function SelectTrigger({
className,
size = "default",
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default";
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
);
}
function SelectContent({ className, children, position = "popper", ...props }: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className,
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
);
}
function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label data-slot="select-label" className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)} {...props} />
);
}
function SelectItem({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className,
)}
{...props}
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
);
}
function SelectSeparator({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
{...props}
/>
);
}
function SelectScrollUpButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
);
}
function SelectScrollDownButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn("flex cursor-default items-center justify-center py-1", className)}
{...props}
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
);
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
};
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import * as React from "react";
import { cn } from "@/lib/utils";
function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator"
decorative={decorative}
orientation={orientation}
className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className,
)}
{...props}
/>
);
}
export { Separator };
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { XIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
}
function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
}
function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
}
function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
}
function SheetOverlay({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
return (
<SheetPrimitive.Overlay
data-slot="sheet-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className,
)}
{...props}
/>
);
}
function SheetContent({
className,
children,
side = "right",
...props
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: "top" | "right" | "bottom" | "left";
}) {
return (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
data-slot="sheet-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
side === "right" &&
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
side === "left" &&
"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
side === "top" && "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
side === "bottom" &&
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
className,
)}
{...props}
>
{children}
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
<XIcon className="size-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
);
}
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="sheet-header" className={cn("flex flex-col gap-1.5 p-4", className)} {...props} />;
}
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="sheet-footer" className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />;
}
function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
return <SheetPrimitive.Title data-slot="sheet-title" className={cn("text-foreground font-semibold", className)} {...props} />;
}
function SheetDescription({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Description>) {
return <SheetPrimitive.Description data-slot="sheet-description" className={cn("text-muted-foreground text-sm", className)} {...props} />;
}
export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription };
import * as SwitchPrimitive from "@radix-ui/react-switch";
import * as React from "react";
import { cn } from "@/lib/utils";
function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
return (
<SwitchPrimitive.Root
data-slot="switch"
className={cn(
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
>
<SwitchPrimitive.Thumb
data-slot="switch-thumb"
className={cn(
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0",
)}
/>
</SwitchPrimitive.Root>
);
}
export { Switch };
import * as React from "react";
import { cn } from "@/lib/utils";
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
return (
<textarea
data-slot="textarea"
className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className,
)}
{...props}
/>
);
}
export { Textarea };
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import * as React from "react";
import { cn } from "@/lib/utils";
function TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
return <TooltipPrimitive.Provider data-slot="tooltip-provider" delayDuration={delayDuration} {...props} />;
}
function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return (
<TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
</TooltipProvider>
);
}
function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
}
function TooltipContent({ className, sideOffset = 0, children, ...props }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
return (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
data-slot="tooltip-content"
sideOffset={sideOffset}
className={cn(
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
className,
)}
{...props}
>
{children}
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
);
}
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
......@@ -3,7 +3,7 @@ import { Outlet } from "react-router-dom";
import { HomeSidebar, HomeSidebarDrawer } from "@/components/HomeSidebar";
import MobileHeader from "@/components/MobileHeader";
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { cn } from "@/utils";
import { cn } from "@/lib/utils";
const HomeLayout = observer(() => {
const { md, lg } = useResponsiveWidth();
......
......@@ -5,11 +5,11 @@ import usePrevious from "react-use/lib/usePrevious";
import Navigation from "@/components/Navigation";
import useCurrentUser from "@/hooks/useCurrentUser";
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { cn } from "@/lib/utils";
import Loading from "@/pages/Loading";
import { Routes } from "@/router";
import { workspaceStore } from "@/store/v2";
import memoFilterStore from "@/store/v2/memoFilter";
import { cn } from "@/utils";
const RootLayout = observer(() => {
const location = useLocation();
......
import { type ClassValue, clsx } from "clsx";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export const cn = (...inputs: ClassValue[]) => {
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
};
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment