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 @@ ...@@ -15,11 +15,20 @@
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@github/relative-time-element": "^4.4.8", "@github/relative-time-element": "^4.4.8",
"@matejmazur/react-katex": "^3.1.3", "@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-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", "@tailwindcss/vite": "^4.1.8",
"@usememos/mui": "0.1.0-20250607013227", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1",
"copy-to-clipboard": "^3.3.3", "copy-to-clipboard": "^3.3.3",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"fuse.js": "^7.1.0", "fuse.js": "^7.1.0",
...@@ -70,6 +79,7 @@ ...@@ -70,6 +79,7 @@
"nice-grpc-web": "^3.3.7", "nice-grpc-web": "^3.3.7",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"terser": "^5.40.0", "terser": "^5.40.0",
"tw-animate-css": "^1.3.4",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.33.0", "typescript-eslint": "^8.33.0",
"vite": "^6.3.5" "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 { observer } from "mobx-react-lite";
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
import { getSystemColorScheme } from "./helpers/utils"; import { getSystemColorScheme } from "./helpers/utils";
...@@ -10,7 +9,7 @@ import { userStore, workspaceStore } from "./store/v2"; ...@@ -10,7 +9,7 @@ import { userStore, workspaceStore } from "./store/v2";
const App = observer(() => { const App = observer(() => {
const { i18n } = useTranslation(); const { i18n } = useTranslation();
const navigateTo = useNavigateTo(); const navigateTo = useNavigateTo();
const { mode, setMode } = useColorScheme(); const [mode, setMode] = useState<"light" | "dark">("light");
const workspaceProfile = workspaceStore.state.profile; const workspaceProfile = workspaceStore.state.profile;
const userSetting = userStore.state.userSetting; const userSetting = userStore.state.userSetting;
const workspaceGeneralSetting = workspaceStore.state.generalSetting; const workspaceGeneralSetting = workspaceStore.state.generalSetting;
......
import { Tooltip } from "@mui/joy";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { memo, useMemo } from "react"; 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 { workspaceStore } from "@/store/v2";
import type { ActivityCalendarProps, CalendarDay } from "@/types/statistics"; import type { ActivityCalendarProps, CalendarDay } from "@/types/statistics";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
const getCellOpacity = (ratio: number): string => { const getCellOpacity = (ratio: number): string => {
...@@ -59,9 +59,16 @@ const CalendarCell = memo( ...@@ -59,9 +59,16 @@ const CalendarCell = memo(
} }
return ( return (
<Tooltip className="shrink-0" title={tooltipText} placement="top" arrow> <TooltipProvider>
{cellContent} <Tooltip>
<TooltipTrigger asChild>
<div className="shrink-0">{cellContent}</div>
</TooltipTrigger>
<TooltipContent>
<p>{tooltipText}</p>
</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider>
); );
}, },
); );
......
import { Option, Select } from "@mui/joy";
import { SunIcon, MoonIcon, SmileIcon } from "lucide-react"; import { SunIcon, MoonIcon, SmileIcon } from "lucide-react";
import { FC } from "react"; import { FC } from "react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
interface Props { interface Props {
...@@ -31,21 +31,20 @@ const AppearanceSelect: FC<Props> = (props: Props) => { ...@@ -31,21 +31,20 @@ const AppearanceSelect: FC<Props> = (props: Props) => {
}; };
return ( return (
<Select <Select value={value} onValueChange={handleSelectChange}>
className={`min-w-40! w-auto whitespace-nowrap ${className ?? ""}`} <SelectTrigger className={`min-w-40 w-auto whitespace-nowrap ${className ?? ""}`}>
value={value} <SelectValue placeholder="Select appearance" />
onChange={(_, appearance) => { </SelectTrigger>
if (appearance) { <SelectContent>
handleSelectChange(appearance);
}
}}
startDecorator={getPrefixIcon(value)}
>
{appearanceList.map((item) => ( {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}`)} {t(`setting.appearance-option.${item}`)}
</Option> </div>
</SelectItem>
))} ))}
</SelectContent>
</Select> </Select>
); );
}; };
......
...@@ -10,8 +10,8 @@ import { ...@@ -10,8 +10,8 @@ import {
SheetIcon, SheetIcon,
} from "lucide-react"; } from "lucide-react";
import React from "react"; import React from "react";
import { cn } from "@/lib/utils";
import { Attachment } from "@/types/proto/api/v1/attachment_service"; import { Attachment } from "@/types/proto/api/v1/attachment_service";
import { cn } from "@/utils";
import { getAttachmentType, getAttachmentUrl } from "@/utils/attachment"; import { getAttachmentType, getAttachmentUrl } from "@/utils/attachment";
import showPreviewImageDialog from "./PreviewImageDialog"; import showPreviewImageDialog from "./PreviewImageDialog";
import SquareDiv from "./kit/SquareDiv"; import SquareDiv from "./kit/SquareDiv";
......
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { cn } from "@/lib/utils";
import { workspaceStore } from "@/store/v2"; import { workspaceStore } from "@/store/v2";
import { cn } from "@/utils";
import AppearanceSelect from "./AppearanceSelect"; import AppearanceSelect from "./AppearanceSelect";
import LocaleSelect from "./LocaleSelect"; import LocaleSelect from "./LocaleSelect";
......
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { cn } from "@/lib/utils";
import { workspaceStore } from "@/store/v2"; import { workspaceStore } from "@/store/v2";
import { cn } from "@/utils";
import UserAvatar from "./UserAvatar"; import UserAvatar from "./UserAvatar";
interface Props { interface Props {
......
import { Button, Input } from "@usememos/mui";
import { XIcon } from "lucide-react"; import { XIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { userStore } from "@/store/v2"; import { userStore } from "@/store/v2";
import { User } from "@/types/proto/api/v1/user_service"; import { User } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
...@@ -69,7 +70,7 @@ const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => { ...@@ -69,7 +70,7 @@ const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
<p> <p>
{t("setting.account-section.change-password")} ({user.displayName}) {t("setting.account-section.change-password")} ({user.displayName})
</p> </p>
<Button variant="plain" onClick={handleCloseBtnClick}> <Button variant="ghost" onClick={handleCloseBtnClick}>
<XIcon className="w-5 h-auto" /> <XIcon className="w-5 h-auto" />
</Button> </Button>
</div> </div>
...@@ -91,7 +92,7 @@ const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => { ...@@ -91,7 +92,7 @@ const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
onChange={handleNewPasswordAgainChanged} onChange={handleNewPasswordAgainChanged}
/> />
<div className="flex flex-row justify-end items-center mt-4 w-full gap-x-2"> <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")} {t("common.cancel")}
</Button> </Button>
<Button color="primary" onClick={handleSaveBtnClick}> <Button color="primary" onClick={handleSaveBtnClick}>
......
import { Radio, RadioGroup } from "@mui/joy";
import { Button, Input } from "@usememos/mui";
import { XIcon } from "lucide-react"; import { XIcon } from "lucide-react";
import React, { useState } from "react"; import React, { useState } from "react";
import { toast } from "react-hot-toast"; 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 { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
...@@ -56,9 +58,9 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => { ...@@ -56,9 +58,9 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
}); });
}; };
const handleRoleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleRoleInputChange = (value: string) => {
setPartialState({ setPartialState({
expiration: Number(e.target.value), expiration: Number(value),
}); });
}; };
...@@ -89,7 +91,7 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => { ...@@ -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="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"> <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> <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" /> <XIcon className="w-5 h-auto" />
</Button> </Button>
</div> </div>
...@@ -113,18 +115,21 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => { ...@@ -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> {t("setting.access-token-section.create-dialog.expiration")} <span className="text-red-600">*</span>
</span> </span>
<div className="w-full flex flex-row justify-start items-center text-base"> <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) => ( {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> </RadioGroup>
</div> </div>
</div> </div>
<div className="w-full flex flex-row justify-end items-center mt-4 space-x-2"> <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")} {t("common.cancel")}
</Button> </Button>
<Button color="primary" disabled={requestState.isLoading} onClick={handleSaveBtnClick}> <Button disabled={requestState.isLoading} onClick={handleSaveBtnClick}>
{t("common.create")} {t("common.create")}
</Button> </Button>
</div> </div>
......
import { Input, Textarea, Button } from "@usememos/mui";
import { XIcon } from "lucide-react"; import { XIcon } from "lucide-react";
import React, { useState } from "react"; import React, { useState } from "react";
import { toast } from "react-hot-toast"; 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 { shortcutServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
...@@ -74,7 +76,7 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => { ...@@ -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="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"> <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> <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" /> <XIcon className="w-5 h-auto" />
</Button> </Button>
</div> </div>
...@@ -84,8 +86,8 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => { ...@@ -84,8 +86,8 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
<Input className="w-full" type="text" placeholder="" value={shortcut.title} onChange={onShortcutTitleChange} /> <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> <span className="text-sm whitespace-nowrap mt-3 mb-1">{t("common.filter")}</span>
<Textarea <Textarea
className="w-full"
rows={3} rows={3}
fullWidth
placeholder={t("common.shortcut-filter")} placeholder={t("common.shortcut-filter")}
value={shortcut.filter} value={shortcut.filter}
onChange={onShortcutFilterChange} onChange={onShortcutFilterChange}
...@@ -115,7 +117,7 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => { ...@@ -115,7 +117,7 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
</ul> </ul>
</div> </div>
<div className="w-full flex flex-row justify-end items-center space-x-2 mt-2"> <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")} {t("common.cancel")}
</Button> </Button>
<Button color="primary" disabled={requestState.isLoading} onClick={handleConfirm}> <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 { XIcon } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { toast } from "react-hot-toast"; 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 { userServiceClient } from "@/grpcweb";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
import { User, User_Role } from "@/types/proto/api/v1/user_service"; import { User, User_Role } from "@/types/proto/api/v1/user_service";
...@@ -66,7 +68,7 @@ const CreateUserDialog: React.FC<Props> = (props: Props) => { ...@@ -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="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"> <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> <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" /> <XIcon className="w-5 h-auto" />
</Button> </Button>
</div> </div>
...@@ -99,16 +101,22 @@ const CreateUserDialog: React.FC<Props> = (props: Props) => { ...@@ -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> <span className="text-sm whitespace-nowrap mt-3 mb-1">{t("common.role")}</span>
<RadioGroup <RadioGroup
orientation="horizontal" value={user.role}
defaultValue={user.role} onValueChange={(value) => setPartialUser({ role: value as User_Role })}
onChange={(e) => setPartialUser({ role: e.target.value as User_Role })} className="flex flex-row gap-4"
> >
<Radio value={User_Role.USER} label={t("setting.member-section.user")} /> <div className="flex items-center space-x-2">
<Radio value={User_Role.ADMIN} label={t("setting.member-section.admin")} /> <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> </RadioGroup>
</div> </div>
<div className="w-full flex flex-row justify-end items-center space-x-2 mt-2"> <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")} {t("common.cancel")}
</Button> </Button>
<Button color="primary" disabled={requestState.isLoading} onClick={handleConfirm}> <Button color="primary" disabled={requestState.isLoading} onClick={handleConfirm}>
......
import { Button, Input } from "@usememos/mui";
import { XIcon } from "lucide-react"; import { XIcon } from "lucide-react";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { webhookServiceClient } from "@/grpcweb"; import { webhookServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
...@@ -108,7 +109,7 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => { ...@@ -108,7 +109,7 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
<p className="title-text"> <p className="title-text">
{isCreating ? t("setting.webhook-section.create-dialog.create-webhook") : t("setting.webhook-section.create-dialog.edit-webhook")} {isCreating ? t("setting.webhook-section.create-dialog.create-webhook") : t("setting.webhook-section.create-dialog.edit-webhook")}
</p> </p>
<Button variant="plain" onClick={() => destroy()}> <Button variant="ghost" onClick={() => destroy()}>
<XIcon className="w-5 h-auto" /> <XIcon className="w-5 h-auto" />
</Button> </Button>
</div> </div>
...@@ -142,7 +143,7 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => { ...@@ -142,7 +143,7 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
</div> </div>
</div> </div>
<div className="w-full flex flex-row justify-end items-center mt-2 space-x-2"> <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")} {t("common.cancel")}
</Button> </Button>
<Button color="primary" disabled={requestState.isLoading} onClick={handleSaveBtnClick}> <Button color="primary" disabled={requestState.isLoading} onClick={handleSaveBtnClick}>
......
import dayjs from "dayjs"; import dayjs from "dayjs";
import toast from "react-hot-toast"; 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"; const DATE_TIME_FORMAT = "M/D/YYYY, H:mm:ss";
......
import { CssVarsProvider } from "@mui/joy";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { cn } from "@/lib/utils";
import dialogStore from "@/store/v2/dialog"; import dialogStore from "@/store/v2/dialog";
import theme from "@/theme";
import { cn } from "@/utils";
interface DialogConfig { interface DialogConfig {
dialogName: string; dialogName: string;
...@@ -90,11 +88,9 @@ export function generateDialog<T extends DialogProps>( ...@@ -90,11 +88,9 @@ export function generateDialog<T extends DialogProps>(
} as T; } as T;
const Fragment = observer(() => ( const Fragment = observer(() => (
<CssVarsProvider theme={theme}>
<BaseDialog destroy={cbs.destroy} clickSpaceDestroy={true} {...config}> <BaseDialog destroy={cbs.destroy} clickSpaceDestroy={true} {...config}>
<DialogComponent {...dialogProps} /> <DialogComponent {...dialogProps} />
</BaseDialog> </BaseDialog>
</CssVarsProvider>
)); ));
dialog.render(<Fragment />); dialog.render(<Fragment />);
......
...@@ -4,9 +4,9 @@ import { matchPath, useLocation } from "react-router-dom"; ...@@ -4,9 +4,9 @@ import { matchPath, useLocation } from "react-router-dom";
import useDebounce from "react-use/lib/useDebounce"; import useDebounce from "react-use/lib/useDebounce";
import SearchBar from "@/components/SearchBar"; import SearchBar from "@/components/SearchBar";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { cn } from "@/lib/utils";
import { Routes } from "@/router"; import { Routes } from "@/router";
import { memoStore, userStore } from "@/store/v2"; import { memoStore, userStore } from "@/store/v2";
import { cn } from "@/utils";
import MemoFilters from "../MemoFilters"; import MemoFilters from "../MemoFilters";
import StatisticsView from "../StatisticsView"; import StatisticsView from "../StatisticsView";
import ShortcutsSection from "./ShortcutsSection"; import ShortcutsSection from "./ShortcutsSection";
......
import { Drawer } from "@mui/joy";
import { Button } from "@usememos/mui";
import { MenuIcon } from "lucide-react"; import { MenuIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import HomeSidebar from "./HomeSidebar"; import HomeSidebar from "./HomeSidebar";
const HomeSidebarDrawer = () => { const HomeSidebarDrawer = () => {
...@@ -13,25 +13,17 @@ const HomeSidebarDrawer = () => { ...@@ -13,25 +13,17 @@ const HomeSidebarDrawer = () => {
setOpen(false); setOpen(false);
}, [location.pathname]); }, [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 ( return (
<> <Sheet open={open} onOpenChange={setOpen}>
<Button variant="plain" className="bg-transparent! px-2" onClick={toggleDrawer(true)}> <SheetTrigger asChild>
<Button variant="ghost" className="bg-transparent! px-2">
<MenuIcon className="w-6 h-auto dark:text-gray-400" /> <MenuIcon className="w-6 h-auto dark:text-gray-400" />
</Button> </Button>
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}> </SheetTrigger>
<div className="w-full h-full bg-zinc-100 dark:bg-zinc-900"> <SheetContent side="right" className="w-full sm:w-80 bg-zinc-100 dark:bg-zinc-900">
<HomeSidebar className="px-4 py-4" /> <HomeSidebar className="px-4 py-4" />
</div> </SheetContent>
</Drawer> </Sheet>
</>
); );
}; };
......
import { Tooltip } from "@mui/joy";
import { Edit3Icon, MoreVerticalIcon, TrashIcon, PlusIcon } from "lucide-react"; import { Edit3Icon, MoreVerticalIcon, TrashIcon, PlusIcon } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { shortcutServiceClient } from "@/grpcweb"; import { shortcutServiceClient } from "@/grpcweb";
import useAsyncEffect from "@/hooks/useAsyncEffect"; import useAsyncEffect from "@/hooks/useAsyncEffect";
import { cn } from "@/lib/utils";
import { userStore } from "@/store/v2"; import { userStore } from "@/store/v2";
import memoFilterStore from "@/store/v2/memoFilter"; import memoFilterStore from "@/store/v2/memoFilter";
import { Shortcut } from "@/types/proto/api/v1/shortcut_service"; import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import showCreateShortcutDialog from "../CreateShortcutDialog"; 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; const emojiRegex = /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F)$/u;
...@@ -40,9 +40,16 @@ const ShortcutsSection = observer(() => { ...@@ -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="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"> <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> <span>{t("common.shortcuts")}</span>
<Tooltip title={t("common.create")} placement="top"> <TooltipProvider>
<PlusIcon className="w-4 h-auto" onClick={() => showCreateShortcutDialog({})} /> <Tooltip>
<TooltipTrigger asChild>
<PlusIcon className="w-4 h-auto cursor-pointer" onClick={() => showCreateShortcutDialog({})} />
</TooltipTrigger>
<TooltipContent>
<p>{t("common.create")}</p>
</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider>
</div> </div>
<div className="w-full flex flex-row justify-start items-center relative flex-wrap gap-x-2 gap-y-1"> <div className="w-full flex flex-row justify-start items-center relative flex-wrap gap-x-2 gap-y-1">
{shortcuts.map((shortcut) => { {shortcuts.map((shortcut) => {
......
import { Switch } from "@usememos/mui";
import { Edit3Icon, HashIcon, MoreVerticalIcon, TagsIcon, TrashIcon } from "lucide-react"; import { Edit3Icon, HashIcon, MoreVerticalIcon, TagsIcon, TrashIcon } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import useLocalStorage from "react-use/lib/useLocalStorage"; import useLocalStorage from "react-use/lib/useLocalStorage";
import { Switch } from "@/components/ui/switch";
import { memoServiceClient } from "@/grpcweb"; import { memoServiceClient } from "@/grpcweb";
import { cn } from "@/lib/utils";
import { userStore } from "@/store/v2"; import { userStore } from "@/store/v2";
import memoFilterStore, { MemoFilter } from "@/store/v2/memoFilter"; import memoFilterStore, { MemoFilter } from "@/store/v2/memoFilter";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import showRenameTagDialog from "../RenameTagDialog"; import showRenameTagDialog from "../RenameTagDialog";
import TagTree from "../TagTree"; import TagTree from "../TagTree";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
interface Props { interface Props {
readonly?: boolean; readonly?: boolean;
...@@ -58,7 +58,7 @@ const TagsSection = observer((props: Props) => { ...@@ -58,7 +58,7 @@ const TagsSection = observer((props: Props) => {
<PopoverContent align="end" alignOffset={-12}> <PopoverContent align="end" alignOffset={-12}>
<div className="w-auto flex flex-row justify-between items-center gap-2 p-1"> <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> <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> </div>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
......
import { Tooltip } from "@mui/joy";
import { InboxIcon, LoaderIcon, MessageCircleIcon } from "lucide-react"; import { InboxIcon, LoaderIcon, MessageCircleIcon } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useState } from "react"; import { useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { activityServiceClient } from "@/grpcweb"; import { activityServiceClient } from "@/grpcweb";
import useAsyncEffect from "@/hooks/useAsyncEffect"; import useAsyncEffect from "@/hooks/useAsyncEffect";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { activityNamePrefix } from "@/store/common"; import { activityNamePrefix } from "@/store/common";
import { memoStore, userStore } from "@/store/v2"; import { memoStore, userStore } from "@/store/v2";
import { Inbox, Inbox_Status } from "@/types/proto/api/v1/inbox_service"; import { Inbox, Inbox_Status } from "@/types/proto/api/v1/inbox_service";
import { Memo } from "@/types/proto/api/v1/memo_service"; import { Memo } from "@/types/proto/api/v1/memo_service";
import { User } from "@/types/proto/api/v1/user_service"; import { User } from "@/types/proto/api/v1/user_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
interface Props { interface Props {
...@@ -79,9 +79,16 @@ const MemoCommentMessage = observer(({ inbox }: Props) => { ...@@ -79,9 +79,16 @@ const MemoCommentMessage = observer(({ inbox }: Props) => {
: "border-gray-500 text-gray-500 bg-gray-50 dark:bg-zinc-800", : "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" /> <MessageCircleIcon className="w-4 sm:w-5 h-auto" />
</TooltipTrigger>
<TooltipContent>
<p>Comment</p>
</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider>
</div> </div>
<div <div
className={cn( className={cn(
...@@ -95,12 +102,19 @@ const MemoCommentMessage = observer(({ inbox }: Props) => { ...@@ -95,12 +102,19 @@ const MemoCommentMessage = observer(({ inbox }: Props) => {
<span className="text-sm text-gray-500">{inbox.createTime?.toLocaleString()}</span> <span className="text-sm text-gray-500">{inbox.createTime?.toLocaleString()}</span>
<div> <div>
{inbox.status === Inbox_Status.UNREAD && ( {inbox.status === Inbox_Status.UNREAD && (
<Tooltip title={t("common.archive")} placement="top"> <TooltipProvider>
<Tooltip>
<TooltipTrigger>
<InboxIcon <InboxIcon
className="w-4 h-auto cursor-pointer text-gray-400 hover:text-blue-600" className="w-4 h-auto cursor-pointer text-gray-400 hover:text-blue-600"
onClick={() => handleArchiveMessage()} onClick={() => handleArchiveMessage()}
/> />
</TooltipTrigger>
<TooltipContent>
<p>{t("common.archive")}</p>
</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider>
)} )}
</div> </div>
</div> </div>
......
import { Tooltip } from "@mui/joy";
import { ExternalLinkIcon } from "lucide-react"; import { ExternalLinkIcon } from "lucide-react";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
interface Props { interface Props {
...@@ -13,11 +13,18 @@ const LearnMore: React.FC<Props> = (props: Props) => { ...@@ -13,11 +13,18 @@ const LearnMore: React.FC<Props> = (props: Props) => {
const t = useTranslate(); const t = useTranslate();
return ( 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"> <a className={`text-gray-500 dark:text-gray-400 hover:text-blue-600 ${className}`} href={url} target="_blank">
<ExternalLinkIcon className="w-4 h-auto" /> <ExternalLinkIcon className="w-4 h-auto" />
</a> </a>
</TooltipTrigger>
<TooltipContent>
<p>{title ?? t("common.learn-more")}</p>
</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider>
); );
}; };
......
import { Option, Select } from "@mui/joy";
import { GlobeIcon } from "lucide-react"; import { GlobeIcon } from "lucide-react";
import { FC } from "react"; import { FC } from "react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { locales } from "@/i18n"; import { locales } from "@/i18n";
interface Props { interface Props {
...@@ -17,20 +17,22 @@ const LocaleSelect: FC<Props> = (props: Props) => { ...@@ -17,20 +17,22 @@ const LocaleSelect: FC<Props> = (props: Props) => {
}; };
return ( return (
<Select <Select value={value} onValueChange={handleSelectChange}>
className={`min-w-40! w-auto whitespace-nowrap ${className ?? ""}`} <SelectTrigger className={`min-w-40 w-auto whitespace-nowrap ${className ?? ""}`}>
startDecorator={<GlobeIcon className="w-4 h-auto" />} <div className="flex items-center gap-2">
value={value} <GlobeIcon className="w-4 h-auto" />
onChange={(_, value) => handleSelectChange(value as Locale)} <SelectValue placeholder="Select language" />
> </div>
</SelectTrigger>
<SelectContent>
{locales.map((locale) => { {locales.map((locale) => {
try { try {
const languageName = new Intl.DisplayNames([locale], { type: "language" }).of(locale); const languageName = new Intl.DisplayNames([locale], { type: "language" }).of(locale);
if (languageName) { if (languageName) {
return ( return (
<Option key={locale} value={locale}> <SelectItem key={locale} value={locale}>
{languageName.charAt(0).toUpperCase() + languageName.slice(1)} {languageName.charAt(0).toUpperCase() + languageName.slice(1)}
</Option> </SelectItem>
); );
} }
} catch { } catch {
...@@ -38,11 +40,12 @@ const LocaleSelect: FC<Props> = (props: Props) => { ...@@ -38,11 +40,12 @@ const LocaleSelect: FC<Props> = (props: Props) => {
} }
return ( return (
<Option key={locale} value={locale}> <SelectItem key={locale} value={locale}>
{locale} {locale}
</Option> </SelectItem>
); );
})} })}
</SelectContent>
</Select> </Select>
); );
}; };
......
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { Memo } from "@/types/proto/api/v1/memo_service"; import { Memo } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
interface Props { interface Props {
memoList: Memo[]; memoList: Memo[];
......
...@@ -15,13 +15,13 @@ import toast from "react-hot-toast"; ...@@ -15,13 +15,13 @@ import toast from "react-hot-toast";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { markdownServiceClient } from "@/grpcweb"; import { markdownServiceClient } from "@/grpcweb";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { memoStore, userStore } from "@/store/v2"; import { memoStore, userStore } from "@/store/v2";
import { State } from "@/types/proto/api/v1/common"; import { State } from "@/types/proto/api/v1/common";
import { NodeType } from "@/types/proto/api/v1/markdown_service"; import { NodeType } from "@/types/proto/api/v1/markdown_service";
import { Memo } from "@/types/proto/api/v1/memo_service"; import { Memo } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
interface Props { interface Props {
memo: Memo; memo: Memo;
......
import { memo } from "react"; import { memo } from "react";
import { cn } from "@/lib/utils";
import { Attachment } from "@/types/proto/api/v1/attachment_service"; import { Attachment } from "@/types/proto/api/v1/attachment_service";
import { cn } from "@/utils";
import { getAttachmentType, getAttachmentUrl } from "@/utils/attachment"; import { getAttachmentType, getAttachmentUrl } from "@/utils/attachment";
import MemoAttachment from "./MemoAttachment"; import MemoAttachment from "./MemoAttachment";
import showPreviewImageDialog from "./PreviewImageDialog"; import showPreviewImageDialog from "./PreviewImageDialog";
......
...@@ -3,7 +3,7 @@ import hljs from "highlight.js"; ...@@ -3,7 +3,7 @@ import hljs from "highlight.js";
import { CopyIcon } from "lucide-react"; import { CopyIcon } from "lucide-react";
import { useCallback, useMemo } from "react"; import { useCallback, useMemo } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { cn } from "@/utils"; import { cn } from "@/lib/utils";
import MermaidBlock from "./MermaidBlock"; import MermaidBlock from "./MermaidBlock";
import { BaseProps } from "./types"; import { BaseProps } from "./types";
......
...@@ -2,8 +2,8 @@ import { observer } from "mobx-react-lite"; ...@@ -2,8 +2,8 @@ import { observer } from "mobx-react-lite";
import { useEffect } from "react"; import { useEffect } from "react";
import MemoAttachmentListView from "@/components/MemoAttachmentListView"; import MemoAttachmentListView from "@/components/MemoAttachmentListView";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
import { cn } from "@/lib/utils";
import { attachmentStore } from "@/store/v2"; import { attachmentStore } from "@/store/v2";
import { cn } from "@/utils";
import Error from "./Error"; import Error from "./Error";
interface Props { interface Props {
......
...@@ -6,9 +6,9 @@ import toast from "react-hot-toast"; ...@@ -6,9 +6,9 @@ import toast from "react-hot-toast";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import MemoAttachmentListView from "@/components/MemoAttachmentListView"; import MemoAttachmentListView from "@/components/MemoAttachmentListView";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
import { cn } from "@/lib/utils";
import { extractMemoIdFromName } from "@/store/common"; import { extractMemoIdFromName } from "@/store/common";
import { memoStore } from "@/store/v2"; import { memoStore } from "@/store/v2";
import { cn } from "@/utils";
import MemoContent from ".."; import MemoContent from "..";
import { RendererContext } from "../types"; import { RendererContext } from "../types";
import Error from "./Error"; import Error from "./Error";
......
import { Divider } from "@mui/joy"; import { Separator } from "@/components/ui/separator";
import { BaseProps } from "./types"; import { BaseProps } from "./types";
interface Props extends BaseProps { interface Props extends BaseProps {
...@@ -6,7 +6,7 @@ interface Props extends BaseProps { ...@@ -6,7 +6,7 @@ interface Props extends BaseProps {
} }
const HorizontalRule: React.FC<Props> = () => { const HorizontalRule: React.FC<Props> = () => {
return <Divider className="my-3!" />; return <Separator className="my-3!" />;
}; };
export default HorizontalRule; export default HorizontalRule;
import { Link as MLink, Tooltip } from "@mui/joy";
import { useState } from "react"; import { useState } from "react";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { markdownServiceClient } from "@/grpcweb"; import { markdownServiceClient } from "@/grpcweb";
import { workspaceStore } from "@/store/v2"; import { workspaceStore } from "@/store/v2";
import { LinkMetadata, Node } from "@/types/proto/api/v1/markdown_service"; import { LinkMetadata, Node } from "@/types/proto/api/v1/markdown_service";
...@@ -43,11 +43,23 @@ const Link: React.FC<Props> = ({ content, url }: Props) => { ...@@ -43,11 +43,23 @@ const Link: React.FC<Props> = ({ content, url }: Props) => {
}; };
return ( return (
<Tooltip <TooltipProvider>
variant="outlined" <Tooltip open={showTooltip}>
title={ <TooltipTrigger asChild>
linkMetadata && ( <a
<div className="w-full max-w-64 sm:max-w-96 p-1 flex flex-col"> 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"> <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} /> <img className="w-5 h-5 rounded" src={getFaviconWithGoogleS2(url)} alt={linkMetadata?.title} />
<h3 className="text-base truncate dark:opacity-90">{linkMetadata?.title}</h3> <h3 className="text-base truncate dark:opacity-90">{linkMetadata?.title}</h3>
...@@ -59,17 +71,10 @@ const Link: React.FC<Props> = ({ content, url }: Props) => { ...@@ -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} /> <img className="mt-1 w-full h-32 object-cover rounded" src={linkMetadata.image} alt={linkMetadata.title} />
)} )}
</div> </div>
) </TooltipContent>
} )}
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>
</Tooltip> </Tooltip>
</TooltipProvider>
); );
}; };
......
import { head } from "lodash-es"; import { head } from "lodash-es";
import React from "react"; import React from "react";
import { cn } from "@/lib/utils";
import { ListNode_Kind, Node, NodeType } from "@/types/proto/api/v1/markdown_service"; import { ListNode_Kind, Node, NodeType } from "@/types/proto/api/v1/markdown_service";
import { cn } from "@/utils";
import Renderer from "./Renderer"; import Renderer from "./Renderer";
interface Props { interface Props {
......
import TeX from "@matejmazur/react-katex"; import TeX from "@matejmazur/react-katex";
import { cn } from "@/utils"; import { cn } from "@/lib/utils";
import "katex/dist/katex.min.css"; import "katex/dist/katex.min.css";
interface Props { interface Props {
......
import { useColorScheme } from "@mui/joy"; import { useEffect, useRef, useState } from "react";
import { useEffect, useRef } from "react";
interface Props { interface Props {
content: string; content: string;
} }
const MermaidBlock: React.FC<Props> = ({ content }: Props) => { const MermaidBlock: React.FC<Props> = ({ content }: Props) => {
const { mode: colorMode } = useColorScheme(); const [colorMode, setColorMode] = useState<"light" | "dark">("light");
const mermaidDockBlock = useRef<null>(null); 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(() => { useEffect(() => {
// Dynamically import mermaid to ensure compatibility with Vite // Dynamically import mermaid to ensure compatibility with Vite
const initializeMermaid = async () => { const initializeMermaid = async () => {
......
import { useState } from "react"; import { useState } from "react";
import { cn } from "@/utils"; import { cn } from "@/lib/utils";
interface Props { interface Props {
content: string; content: string;
......
...@@ -2,10 +2,10 @@ import { observer } from "mobx-react-lite"; ...@@ -2,10 +2,10 @@ import { observer } from "mobx-react-lite";
import { useContext } from "react"; import { useContext } from "react";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { Routes } from "@/router"; import { Routes } from "@/router";
import { memoFilterStore } from "@/store/v2"; import { memoFilterStore } from "@/store/v2";
import { stringifyFilters, MemoFilter } from "@/store/v2/memoFilter"; import { stringifyFilters, MemoFilter } from "@/store/v2/memoFilter";
import { cn } from "@/utils";
import { RendererContext } from "./types"; import { RendererContext } from "./types";
interface Props { interface Props {
......
import { Checkbox } from "@usememos/mui";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useContext } from "react"; import { useContext } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { markdownServiceClient } from "@/grpcweb"; import { markdownServiceClient } from "@/grpcweb";
import { cn } from "@/lib/utils";
import { memoStore } from "@/store/v2"; import { memoStore } from "@/store/v2";
import { Node, TaskListItemNode } from "@/types/proto/api/v1/markdown_service"; import { Node, TaskListItemNode } from "@/types/proto/api/v1/markdown_service";
import { cn } from "@/utils";
import Renderer from "./Renderer"; import Renderer from "./Renderer";
import { RendererContext } from "./types"; import { RendererContext } from "./types";
...@@ -39,7 +39,12 @@ const TaskListItem = observer(({ node, complete, children }: Props) => { ...@@ -39,7 +39,12 @@ const TaskListItem = observer(({ node, complete, children }: Props) => {
return ( return (
<li className={cn("w-full grid grid-cols-[24px_1fr]")}> <li className={cn("w-full grid grid-cols-[24px_1fr]")}>
<span className="w-6 h-6 flex justify-start items-center"> <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> </span>
<p className={cn(complete && "line-through opacity-80")}> <p className={cn(complete && "line-through opacity-80")}>
{children.map((child, index) => ( {children.map((child, index) => (
......
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { memo, useEffect, useRef, useState } from "react"; import { memo, useEffect, useRef, useState } from "react";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { cn } from "@/lib/utils";
import { memoStore } from "@/store/v2"; import { memoStore } from "@/store/v2";
import { Node, NodeType } from "@/types/proto/api/v1/markdown_service"; import { Node, NodeType } from "@/types/proto/api/v1/markdown_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { isSuperUser } from "@/utils/user"; import { isSuperUser } from "@/utils/user";
import Renderer from "./Renderer"; import Renderer from "./Renderer";
......
import { isEqual } from "lodash-es"; import { isEqual } from "lodash-es";
import { CheckCircleIcon, Code2Icon, HashIcon, LinkIcon } from "lucide-react"; 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 { Memo, MemoRelation_Type, Memo_Property } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import MemoRelationForceGraph from "../MemoRelationForceGraph"; import MemoRelationForceGraph from "../MemoRelationForceGraph";
......
import { Drawer } from "@mui/joy";
import { Button } from "@usememos/mui";
import { GanttChartIcon } from "lucide-react"; import { GanttChartIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom"; 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 { Memo } from "@/types/proto/api/v1/memo_service";
import MemoDetailSidebar from "./MemoDetailSidebar"; import MemoDetailSidebar from "./MemoDetailSidebar";
...@@ -19,24 +19,17 @@ const MemoDetailSidebarDrawer = ({ memo, parentPage }: Props) => { ...@@ -19,24 +19,17 @@ const MemoDetailSidebarDrawer = ({ memo, parentPage }: Props) => {
setOpen(false); setOpen(false);
}, [location.pathname]); }, [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 ( return (
<> <Sheet open={open} onOpenChange={setOpen}>
<Button variant="plain" className="bg-transparent! px-2" onClick={toggleDrawer(true)}> <SheetTrigger asChild>
<Button variant="ghost" className="bg-transparent! px-2">
<GanttChartIcon className="w-5 h-auto dark:text-gray-400" /> <GanttChartIcon className="w-5 h-auto dark:text-gray-400" />
</Button> </Button>
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}> </SheetTrigger>
<div className="w-full h-full px-4 bg-zinc-100 dark:bg-zinc-900"> <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} /> <MemoDetailSidebar className="py-4" memo={memo} parentPage={parentPage} />
</div> </SheetContent>
</Drawer> </Sheet>
</>
); );
}; };
......
import { Option, Select } from "@mui/joy";
import { Settings2Icon } from "lucide-react"; import { Settings2Icon } from "lucide-react";
import { observer } from "mobx-react-lite"; 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 { viewStore } from "@/store/v2";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
interface Props { interface Props {
className?: string; className?: string;
...@@ -26,29 +26,39 @@ const MemoDisplaySettingMenu = observer(({ className }: Props) => { ...@@ -26,29 +26,39 @@ const MemoDisplaySettingMenu = observer(({ className }: Props) => {
<div className="w-full flex flex-row justify-between items-center"> <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> <span className="text-sm shrink-0 mr-3 dark:text-zinc-400">{t("memo.direction")}</span>
<Select <Select
value={viewStore.state.orderByTimeAsc} value={viewStore.state.orderByTimeAsc.toString()}
onChange={(_, value) => onValueChange={(value) =>
viewStore.state.setPartial({ viewStore.state.setPartial({
orderByTimeAsc: Boolean(value), orderByTimeAsc: value === "true",
}) })
} }
> >
<Option value={false}>{t("memo.direction-desc")}</Option> <SelectTrigger className="w-32">
<Option value={true}>{t("memo.direction-asc")}</Option> <SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="false">{t("memo.direction-desc")}</SelectItem>
<SelectItem value="true">{t("memo.direction-asc")}</SelectItem>
</SelectContent>
</Select> </Select>
</div> </div>
<div className="w-full flex flex-row justify-between items-center"> <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> <span className="text-sm shrink-0 mr-3 dark:text-zinc-400">{t("common.layout")}</span>
<Select <Select
value={viewStore.state.layout} value={viewStore.state.layout}
onChange={(_, value) => onValueChange={(value) =>
viewStore.state.setPartial({ viewStore.state.setPartial({
layout: value as "LIST" | "MASONRY", layout: value as "LIST" | "MASONRY",
}) })
} }
> >
<Option value={"LIST"}>{t("memo.list")}</Option> <SelectTrigger className="w-32">
<Option value={"MASONRY"}>{t("memo.masonry")}</Option> <SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="LIST">{t("memo.list")}</SelectItem>
<SelectItem value="MASONRY">{t("memo.masonry")}</SelectItem>
</SelectContent>
</Select> </Select>
</div> </div>
</div> </div>
......
import { Autocomplete, AutocompleteOption, Chip } from "@mui/joy";
import { Button, Checkbox } from "@usememos/mui";
import { uniqBy } from "lodash-es"; import { uniqBy } from "lodash-es";
import { LinkIcon } from "lucide-react"; import { LinkIcon, X } from "lucide-react";
import React, { useContext, useState } from "react"; import React, { useContext, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import useDebounce from "react-use/lib/useDebounce"; 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 { memoServiceClient } from "@/grpcweb";
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts"; import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
...@@ -129,52 +131,69 @@ const AddMemoRelationPopover = (props: Props) => { ...@@ -129,52 +131,69 @@ const AddMemoRelationPopover = (props: Props) => {
return ( return (
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}> <Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
<PopoverTrigger className="relative"> <PopoverTrigger asChild>
<Button className="flex items-center justify-center p-0" variant="plain" asChild> <Button variant="ghost">
<LinkIcon className="w-5 h-5 mx-auto p-0" /> <LinkIcon />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent align="center"> <PopoverContent align="center">
<div className="w-[16rem] p-1 flex flex-col justify-start items-start"> <div className="w-[16rem] p-1 flex flex-col justify-start items-start">
<Autocomplete {/* Selected memos display */}
className="w-full" {selectedMemos.length > 0 && (
size="md" <div className="w-full mb-2 flex flex-wrap gap-1">
clearOnBlur {selectedMemos.map((memo) => (
disableClearable <Badge key={memo.name} variant="outline" className="max-w-full flex items-center gap-1 p-2">
placeholder={t("reference.search-placeholder")} <div className="flex-1 min-w-0">
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">
<p className="text-xs text-gray-400 select-none">{memo.displayTime?.toLocaleString()}</p> <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> </div>
</AutocompleteOption>
)} )}
renderTags={(memos) =>
memos.map((memo) => ( {/* Search and selection interface */}
<Chip key={memo.name} className="max-w-full! rounded!" variant="outlined" color="neutral"> <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"> <div className="w-full flex flex-col justify-start items-start">
<p className="text-xs text-gray-400 select-none">{memo.displayTime?.toLocaleString()}</p> <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> </div>
</Chip> </CommandItem>
)) ))}
} </CommandList>
onChange={(_, value) => setSelectedMemos(value)} </Command>
/>
<div className="mt-2 w-full flex flex-row justify-end items-center gap-2"> <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)} /> <div className="flex items-center space-x-2">
<Button color="primary" onClick={addMemoRelations} disabled={selectedMemos.length === 0}> <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")} {t("common.add")}
</Button> </Button>
</div> </div>
......
import { Button, Input } from "@usememos/mui";
import { LatLng } from "leaflet"; import { LatLng } from "leaflet";
import { MapPinIcon, XIcon } from "lucide-react"; import { MapPinIcon, XIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import LeafletMap from "@/components/LeafletMap"; 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 { Location } from "@/types/proto/api/v1/memo_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
...@@ -86,6 +87,7 @@ const LocationSelector = (props: Props) => { ...@@ -86,6 +87,7 @@ const LocationSelector = (props: Props) => {
}; };
const removeLocation = (e: React.MouseEvent) => { const removeLocation = (e: React.MouseEvent) => {
console.log("here");
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
props.onChange(undefined); props.onChange(undefined);
...@@ -94,36 +96,40 @@ const LocationSelector = (props: Props) => { ...@@ -94,36 +96,40 @@ const LocationSelector = (props: Props) => {
return ( return (
<Popover open={popoverOpen} onOpenChange={setPopoverOpen}> <Popover open={popoverOpen} onOpenChange={setPopoverOpen}>
<PopoverTrigger asChild> <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" /> <MapPinIcon className="w-5 h-5 mx-auto shrink-0" />
{props.location && ( {props.location && (
<> <>
<span className="ml-0.5 text-sm text-ellipsis whitespace-nowrap overflow-hidden max-w-32">{props.location.placeholder}</span> <span className="ml-0.5 text-sm text-ellipsis whitespace-nowrap overflow-hidden max-w-32">
<XIcon className="w-5 h-5 mx-auto shrink-0 hidden group-hover:block opacity-60 hover:opacity-80" onClick={removeLocation} /> {props.location.placeholder}
</span>
<XIcon className="w-5 h-5 mx-auto shrink-0 opacity-60 hover:opacity-80" onClick={removeLocation} />
</> </>
)} )}
</div>
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent align="center"> <PopoverContent align="center">
<div className="min-w-80 sm:w-lg p-1 flex flex-col justify-start items-start"> <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} /> <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="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 <Input
placeholder="Choose a position first." placeholder="Choose a position first."
value={state.placeholder} 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} disabled={!state.position}
className={state.position ? "pl-24" : ""}
onChange={(e) => setState((state) => ({ ...state, placeholder: e.target.value }))} onChange={(e) => setState((state) => ({ ...state, placeholder: e.target.value }))}
/> />
</div> </div>
</div>
<Button <Button
className="shrink-0" className="shrink-0"
color="primary" color="primary"
......
import { Link } from "@mui/joy";
import { Button } from "@usememos/mui";
import { CheckSquareIcon, Code2Icon, SquareSlashIcon } from "lucide-react"; import { CheckSquareIcon, Code2Icon, SquareSlashIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/Popover"; import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover";
import { EditorRefActions } from "../Editor"; import { EditorRefActions } from "../Editor";
interface Props { interface Props {
...@@ -10,9 +9,8 @@ interface Props { ...@@ -10,9 +9,8 @@ interface Props {
} }
const MarkdownMenu = (props: Props) => { const MarkdownMenu = (props: Props) => {
const t = useTranslate();
const { editorRef } = props; const { editorRef } = props;
const t = useTranslate();
const handleCodeBlockClick = () => { const handleCodeBlockClick = () => {
if (!editorRef.current) { if (!editorRef.current) {
...@@ -64,8 +62,8 @@ const MarkdownMenu = (props: Props) => { ...@@ -64,8 +62,8 @@ const MarkdownMenu = (props: Props) => {
return ( return (
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button variant="plain" className="p-0"> <Button variant="ghost">
<SquareSlashIcon className="w-5 h-5" /> <SquareSlashIcon />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent align="start" className="text-sm p-1"> <PopoverContent align="start" className="text-sm p-1">
...@@ -85,9 +83,14 @@ const MarkdownMenu = (props: Props) => { ...@@ -85,9 +83,14 @@ const MarkdownMenu = (props: Props) => {
<span>{t("markdown.checkbox")}</span> <span>{t("markdown.checkbox")}</span>
</button> </button>
<div className="pl-2"> <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")} {t("markdown.content-syntax")}
</Link> </a>
</div> </div>
</div> </div>
</PopoverContent> </PopoverContent>
......
import { Button } from "@usememos/mui";
import { HashIcon } from "lucide-react"; import { HashIcon } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import OverflowTip from "@/components/kit/OverflowTip"; import OverflowTip from "@/components/kit/OverflowTip";
import { Button } from "@/components/ui/button";
import { userStore } from "@/store/v2"; import { userStore } from "@/store/v2";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/Popover"; import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover";
import { EditorRefActions } from "../Editor"; import { EditorRefActions } from "../Editor";
interface Props { interface Props {
...@@ -35,8 +35,8 @@ const TagSelector = observer((props: Props) => { ...@@ -35,8 +35,8 @@ const TagSelector = observer((props: Props) => {
return ( return (
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button variant="plain" className="p-0"> <Button variant="ghost">
<HashIcon className="w-5 h-5" /> <HashIcon />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent align="start" sideOffset={2}> <PopoverContent align="start" sideOffset={2}>
......
import { Button } from "@usememos/mui";
import { LoaderIcon, PaperclipIcon } from "lucide-react"; import { LoaderIcon, PaperclipIcon } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useContext, useRef, useState } from "react"; import { useContext, useRef, useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { attachmentStore } from "@/store/v2"; import { attachmentStore } from "@/store/v2";
import { Attachment } from "@/types/proto/api/v1/attachment_service"; import { Attachment } from "@/types/proto/api/v1/attachment_service";
import { MemoEditorContext } from "../types"; import { MemoEditorContext } from "../types";
...@@ -73,7 +73,7 @@ const UploadAttachmentButton = observer((props: Props) => { ...@@ -73,7 +73,7 @@ const UploadAttachmentButton = observer((props: Props) => {
const isUploading = state.uploadingFlag || props.isUploading; const isUploading = state.uploadingFlag || props.isUploading;
return ( 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" />} {isUploading ? <LoaderIcon className="w-5 h-5 animate-spin" /> : <PaperclipIcon className="w-5 h-5" />}
<input <input
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
......
import { ChevronDownIcon } from "lucide-react"; import { ChevronDownIcon } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import VisibilityIcon from "@/components/VisibilityIcon"; 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 { Visibility } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
interface Props { interface Props {
......
...@@ -3,8 +3,8 @@ import { observer } from "mobx-react-lite"; ...@@ -3,8 +3,8 @@ import { observer } from "mobx-react-lite";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import getCaretCoordinates from "textarea-caret"; import getCaretCoordinates from "textarea-caret";
import OverflowTip from "@/components/kit/OverflowTip"; import OverflowTip from "@/components/kit/OverflowTip";
import { cn } from "@/lib/utils";
import { userStore } from "@/store/v2"; import { userStore } from "@/store/v2";
import { cn } from "@/utils";
import { EditorRefActions } from "."; import { EditorRefActions } from ".";
type Props = { type Props = {
......
import { last } from "lodash-es"; import { last } from "lodash-es";
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react"; import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
import { markdownServiceClient } from "@/grpcweb"; import { markdownServiceClient } from "@/grpcweb";
import { cn } from "@/lib/utils";
import { Node, NodeType, OrderedListItemNode, TaskListItemNode, UnorderedListItemNode } from "@/types/proto/api/v1/markdown_service"; import { Node, NodeType, OrderedListItemNode, TaskListItemNode, UnorderedListItemNode } from "@/types/proto/api/v1/markdown_service";
import { cn } from "@/utils";
import TagSuggestions from "./TagSuggestions"; import TagSuggestions from "./TagSuggestions";
export interface EditorRefActions { export interface EditorRefActions {
......
import { Button } from "@usememos/mui";
import copy from "copy-to-clipboard"; import copy from "copy-to-clipboard";
import { isEqual } from "lodash-es"; import { isEqual } from "lodash-es";
import { LoaderIcon, SendIcon } from "lucide-react"; import { LoaderIcon, SendIcon } from "lucide-react";
...@@ -7,17 +6,18 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; ...@@ -7,17 +6,18 @@ import React, { useEffect, useMemo, useRef, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import useLocalStorage from "react-use/lib/useLocalStorage"; import useLocalStorage from "react-use/lib/useLocalStorage";
import { Button } from "@/components/ui/button";
import { memoServiceClient } from "@/grpcweb"; import { memoServiceClient } from "@/grpcweb";
import { TAB_SPACE_WIDTH } from "@/helpers/consts"; import { TAB_SPACE_WIDTH } from "@/helpers/consts";
import { isValidUrl } from "@/helpers/utils"; import { isValidUrl } from "@/helpers/utils";
import useAsyncEffect from "@/hooks/useAsyncEffect"; import useAsyncEffect from "@/hooks/useAsyncEffect";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { cn } from "@/lib/utils";
import { extractMemoIdFromName } from "@/store/common"; import { extractMemoIdFromName } from "@/store/common";
import { memoStore, attachmentStore, userStore, workspaceStore } from "@/store/v2"; import { memoStore, attachmentStore, userStore, workspaceStore } from "@/store/v2";
import { Attachment } from "@/types/proto/api/v1/attachment_service"; import { Attachment } from "@/types/proto/api/v1/attachment_service";
import { Location, Memo, MemoRelation, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_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 { UserSetting } from "@/types/proto/api/v1/user_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { convertVisibilityFromString } from "@/utils/memo"; import { convertVisibilityFromString } from "@/utils/memo";
import DateTimeInput from "../DateTimeInput"; import DateTimeInput from "../DateTimeInput";
...@@ -503,7 +503,7 @@ const MemoEditor = observer((props: Props) => { ...@@ -503,7 +503,7 @@ const MemoEditor = observer((props: Props) => {
<AttachmentListView attachmentList={state.attachmentList} setAttachmentList={handleSetAttachmentList} /> <AttachmentListView attachmentList={state.attachmentList} setAttachmentList={handleSetAttachmentList} />
<RelationListView relationList={referenceRelations} setRelationList={handleSetRelationList} /> <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="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} /> <TagSelector editorRef={editorRef} />
<MarkdownMenu editorRef={editorRef} /> <MarkdownMenu editorRef={editorRef} />
<UploadAttachmentButton isUploading={state.isUploadingAttachment} /> <UploadAttachmentButton isUploading={state.isUploadingAttachment} />
...@@ -520,7 +520,7 @@ const MemoEditor = observer((props: Props) => { ...@@ -520,7 +520,7 @@ const MemoEditor = observer((props: Props) => {
</div> </div>
<div className="shrink-0 -mr-1 flex flex-row justify-end items-center"> <div className="shrink-0 -mr-1 flex flex-row justify-end items-center">
{props.onCancel && ( {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")} {t("common.cancel")}
</Button> </Button>
)} )}
......
...@@ -3,7 +3,7 @@ import { MapPinIcon } from "lucide-react"; ...@@ -3,7 +3,7 @@ import { MapPinIcon } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { Location } from "@/types/proto/api/v1/memo_service"; import { Location } from "@/types/proto/api/v1/memo_service";
import LeafletMap from "./LeafletMap"; import LeafletMap from "./LeafletMap";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
interface Props { interface Props {
location: Location; location: Location;
......
import { useColorScheme } from "@mui/joy";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import ForceGraph2D, { ForceGraphMethods, LinkObject, NodeObject } from "react-force-graph-2d"; import ForceGraph2D, { ForceGraphMethods, LinkObject, NodeObject } from "react-force-graph-2d";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { extractMemoIdFromName } from "@/store/common"; import { extractMemoIdFromName } from "@/store/common";
import { Memo, MemoRelation_Type } from "@/types/proto/api/v1/memo_service"; import { Memo, MemoRelation_Type } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
import { LinkType, NodeType } from "./types"; import { LinkType, NodeType } from "./types";
import { convertMemoRelationsToGraphData } from "./utils"; import { convertMemoRelationsToGraphData } from "./utils";
...@@ -19,11 +18,30 @@ const DEFAULT_NODE_COLOR = "#a1a1aa"; ...@@ -19,11 +18,30 @@ const DEFAULT_NODE_COLOR = "#a1a1aa";
const MemoRelationForceGraph = ({ className, memo, parentPage }: Props) => { const MemoRelationForceGraph = ({ className, memo, parentPage }: Props) => {
const navigateTo = useNavigateTo(); const navigateTo = useNavigateTo();
const { mode } = useColorScheme(); const [mode, setMode] = useState<"light" | "dark">("light");
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const graphRef = useRef<ForceGraphMethods<NodeObject<NodeType>, LinkObject<NodeType, LinkType>> | undefined>(undefined); const graphRef = useRef<ForceGraphMethods<NodeObject<NodeType>, LinkObject<NodeType, LinkType>> | undefined>(undefined);
const [graphSize, setGraphSize] = useState({ width: 0, height: 0 }); 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(() => { useEffect(() => {
if (!containerRef.current) return; if (!containerRef.current) return;
setGraphSize(containerRef.current.getBoundingClientRect()); setGraphSize(containerRef.current.getBoundingClientRect());
......
import { LinkIcon, MilestoneIcon } from "lucide-react"; import { LinkIcon, MilestoneIcon } from "lucide-react";
import { memo, useState } from "react"; import { memo, useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { cn } from "@/lib/utils";
import { extractMemoIdFromName } from "@/store/common"; import { extractMemoIdFromName } from "@/store/common";
import { Memo, MemoRelation } from "@/types/proto/api/v1/memo_service"; import { Memo, MemoRelation } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
interface Props { interface Props {
......
import { Tooltip } from "@mui/joy";
import { BookmarkIcon, EyeOffIcon, MessageCircleMoreIcon } from "lucide-react"; import { BookmarkIcon, EyeOffIcon, MessageCircleMoreIcon } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { memo, useCallback, useState } from "react"; import { memo, useCallback, useState } from "react";
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import useAsyncEffect from "@/hooks/useAsyncEffect"; import useAsyncEffect from "@/hooks/useAsyncEffect";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { memoStore, userStore, workspaceStore } from "@/store/v2"; import { memoStore, userStore, workspaceStore } from "@/store/v2";
import { State } from "@/types/proto/api/v1/common"; import { State } from "@/types/proto/api/v1/common";
import { Memo, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_service"; import { Memo, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { convertVisibilityToString } from "@/utils/memo"; import { convertVisibilityToString } from "@/utils/memo";
import { isSuperUser } from "@/utils/user"; import { isSuperUser } from "@/utils/user";
...@@ -170,10 +170,13 @@ const MemoView: React.FC<Props> = observer((props: Props) => { ...@@ -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="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"> <div className="w-auto invisible group-hover:visible flex flex-row justify-between items-center gap-2">
{props.showVisibility && memo.visibility !== Visibility.PRIVATE && ( {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"> <span className="flex justify-center items-center hover:opacity-70">
<VisibilityIcon visibility={memo.visibility} /> <VisibilityIcon visibility={memo.visibility} />
</span> </span>
</TooltipTrigger>
<TooltipContent>{t(`memo.visibility.${convertVisibilityToString(memo.visibility).toLowerCase()}` as any)}</TooltipContent>
</Tooltip> </Tooltip>
)} )}
{currentUser && !isArchived && <ReactionSelector className="border-none w-auto h-auto" memo={memo} />} {currentUser && !isArchived && <ReactionSelector className="border-none w-auto h-auto" memo={memo} />}
...@@ -195,11 +198,18 @@ const MemoView: React.FC<Props> = observer((props: Props) => { ...@@ -195,11 +198,18 @@ const MemoView: React.FC<Props> = observer((props: Props) => {
</Link> </Link>
)} )}
{props.showPinned && memo.pinned && ( {props.showPinned && memo.pinned && (
<Tooltip title={t("common.unpin")} placement="top"> <TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span className="cursor-pointer"> <span className="cursor-pointer">
<BookmarkIcon className="w-4 h-auto text-amber-500" onClick={onPinIconClick} /> <BookmarkIcon className="w-4 h-auto text-amber-500" onClick={onPinIconClick} />
</span> </span>
</TooltipTrigger>
<TooltipContent>
<p>{t("common.unpin")}</p>
</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider>
)} )}
{nsfw && showNSFWContent && ( {nsfw && showNSFWContent && (
<span className="cursor-pointer"> <span className="cursor-pointer">
......
import useWindowScroll from "react-use/lib/useWindowScroll"; import useWindowScroll from "react-use/lib/useWindowScroll";
import useResponsiveWidth from "@/hooks/useResponsiveWidth"; import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { cn } from "@/utils"; import { cn } from "@/lib/utils";
import NavigationDrawer from "./NavigationDrawer"; import NavigationDrawer from "./NavigationDrawer";
interface Props { interface Props {
......
import { Tooltip } from "@mui/joy";
import { EarthIcon, LibraryIcon, PaperclipIcon, UserCircleIcon } from "lucide-react"; import { EarthIcon, LibraryIcon, PaperclipIcon, UserCircleIcon } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useEffect } from "react"; import { useEffect } from "react";
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { cn } from "@/lib/utils";
import { Routes } from "@/router"; import { Routes } from "@/router";
import { userStore } from "@/store/v2"; import { userStore } from "@/store/v2";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import BrandBanner from "./BrandBanner"; import BrandBanner from "./BrandBanner";
import UserBanner from "./UserBanner"; import UserBanner from "./UserBanner";
...@@ -89,9 +89,16 @@ const Navigation = observer((props: Props) => { ...@@ -89,9 +89,16 @@ const Navigation = observer((props: Props) => {
viewTransition viewTransition
> >
{props.collapsed ? ( {props.collapsed ? (
<Tooltip title={navLink.title} placement="right" arrow> <TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div>{navLink.icon}</div> <div>{navLink.icon}</div>
</TooltipTrigger>
<TooltipContent side="right">
<p>{navLink.title}</p>
</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider>
) : ( ) : (
navLink.icon navLink.icon
)} )}
......
import { Drawer } from "@mui/joy";
import { Button } from "@usememos/mui";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom"; 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 { workspaceStore } from "@/store/v2";
import Navigation from "./Navigation"; import Navigation from "./Navigation";
import UserAvatar from "./UserAvatar"; import UserAvatar from "./UserAvatar";
...@@ -18,28 +18,20 @@ const NavigationDrawer = observer(() => { ...@@ -18,28 +18,20 @@ const NavigationDrawer = observer(() => {
setOpen(false); setOpen(false);
}, [location.pathname]); }, [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 ( return (
<> <Sheet open={open} onOpenChange={setOpen}>
<Button variant="plain" className="px-2" onClick={toggleDrawer(true)}> <SheetTrigger asChild>
<Button variant="ghost" className="px-2">
<UserAvatar className="shrink-0 w-6 h-6 rounded-md" avatarUrl={avatarUrl} /> <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"> <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} {title}
</span> </span>
</Button> </Button>
<Drawer anchor="left" size="sm" open={open} onClose={toggleDrawer(false)}> </SheetTrigger>
<div className="w-full h-full overflow-auto px-2 bg-zinc-100 dark:bg-zinc-900"> <SheetContent side="left" className="w-full sm:w-80 overflow-auto px-2 bg-zinc-100 dark:bg-zinc-900">
<Navigation /> <Navigation />
</div> </SheetContent>
</Drawer> </Sheet>
</>
); );
}); });
......
import { Button } from "@usememos/mui";
import { ArrowUpIcon, LoaderIcon } from "lucide-react"; import { ArrowUpIcon, LoaderIcon } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { matchPath } from "react-router-dom"; import { matchPath } from "react-router-dom";
import PullToRefresh from "react-simple-pull-to-refresh"; import PullToRefresh from "react-simple-pull-to-refresh";
import { Button } from "@/components/ui/button";
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts"; import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
import useResponsiveWidth from "@/hooks/useResponsiveWidth"; import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { Routes } from "@/router"; import { Routes } from "@/router";
...@@ -220,7 +220,7 @@ const BackToTop = () => { ...@@ -220,7 +220,7 @@ const BackToTop = () => {
} }
return ( return (
<Button variant="plain" onClick={scrollToTop}> <Button variant="ghost" onClick={scrollToTop}>
{t("router.back-to-top")} {t("router.back-to-top")}
<ArrowUpIcon className="ml-1 w-4 h-auto" /> <ArrowUpIcon className="ml-1 w-4 h-auto" />
</Button> </Button>
......
import { Button, Input } from "@usememos/mui";
import { LoaderIcon } from "lucide-react"; import { LoaderIcon } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { ClientError } from "nice-grpc-web"; import { ClientError } from "nice-grpc-web";
import { useState } from "react"; import { useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { authServiceClient } from "@/grpcweb"; import { authServiceClient } from "@/grpcweb";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
...@@ -62,8 +63,7 @@ const PasswordSignInForm = observer(() => { ...@@ -62,8 +63,7 @@ const PasswordSignInForm = observer(() => {
<div className="w-full flex flex-col justify-start items-start"> <div className="w-full flex flex-col justify-start items-start">
<span className="leading-8 text-gray-600">{t("common.username")}</span> <span className="leading-8 text-gray-600">{t("common.username")}</span>
<Input <Input
className="w-full bg-white dark:bg-black" className="w-full bg-white dark:bg-black h-10"
size="lg"
type="text" type="text"
readOnly={actionBtnLoadingState.isLoading} readOnly={actionBtnLoadingState.isLoading}
placeholder={t("common.username")} placeholder={t("common.username")}
...@@ -78,8 +78,7 @@ const PasswordSignInForm = observer(() => { ...@@ -78,8 +78,7 @@ const PasswordSignInForm = observer(() => {
<div className="w-full flex flex-col justify-start items-start"> <div className="w-full flex flex-col justify-start items-start">
<span className="leading-8 text-gray-600">{t("common.password")}</span> <span className="leading-8 text-gray-600">{t("common.password")}</span>
<Input <Input
className="w-full bg-white dark:bg-black" className="w-full bg-white dark:bg-black h-10"
size="lg"
type="password" type="password"
readOnly={actionBtnLoadingState.isLoading} readOnly={actionBtnLoadingState.isLoading}
placeholder={t("common.password")} placeholder={t("common.password")}
...@@ -93,14 +92,7 @@ const PasswordSignInForm = observer(() => { ...@@ -93,14 +92,7 @@ const PasswordSignInForm = observer(() => {
</div> </div>
</div> </div>
<div className="flex flex-row justify-end items-center w-full mt-6"> <div className="flex flex-row justify-end items-center w-full mt-6">
<Button <Button type="submit" className="w-full h-10" disabled={actionBtnLoadingState.isLoading} onClick={handleSignInButtonClick}>
type="submit"
color="primary"
size="lg"
fullWidth
disabled={actionBtnLoadingState.isLoading}
onClick={handleSignInButtonClick}
>
{t("common.sign-in")} {t("common.sign-in")}
{actionBtnLoadingState.isLoading && <LoaderIcon className="w-5 h-auto ml-2 animate-spin opacity-60" />} {actionBtnLoadingState.isLoading && <LoaderIcon className="w-5 h-auto ml-2 animate-spin opacity-60" />}
</Button> </Button>
......
import { Button } from "@usememos/mui";
import { XIcon } from "lucide-react"; import { XIcon } from "lucide-react";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { generateDialog } from "./Dialog"; import { generateDialog } from "./Dialog";
const MIN_SCALE = 0.5; const MIN_SCALE = 0.5;
......
...@@ -4,10 +4,10 @@ import { useRef, useState } from "react"; ...@@ -4,10 +4,10 @@ import { useRef, useState } from "react";
import useClickAway from "react-use/lib/useClickAway"; import useClickAway from "react-use/lib/useClickAway";
import { memoServiceClient } from "@/grpcweb"; import { memoServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { cn } from "@/lib/utils";
import { memoStore, workspaceStore } from "@/store/v2"; import { memoStore, workspaceStore } from "@/store/v2";
import { Memo } from "@/types/proto/api/v1/memo_service"; 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 { interface Props {
memo: Memo; memo: Memo;
......
import { Tooltip } from "@mui/joy";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { memoServiceClient } from "@/grpcweb"; import { memoServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { cn } from "@/lib/utils";
import { memoStore } from "@/store/v2"; import { memoStore } from "@/store/v2";
import { State } from "@/types/proto/api/v1/common"; import { State } from "@/types/proto/api/v1/common";
import { Memo } from "@/types/proto/api/v1/memo_service"; import { Memo } from "@/types/proto/api/v1/memo_service";
import { User } from "@/types/proto/api/v1/user_service"; import { User } from "@/types/proto/api/v1/user_service";
import { cn } from "@/utils";
interface Props { interface Props {
memo: Memo; memo: Memo;
...@@ -65,7 +65,9 @@ const ReactionView = observer((props: Props) => { ...@@ -65,7 +65,9 @@ const ReactionView = observer((props: Props) => {
}; };
return ( return (
<Tooltip title={stringifyUsers(users, reactionType)} placement="top"> <TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div <div
className={cn( 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", "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) => { ...@@ -78,7 +80,12 @@ const ReactionView = observer((props: Props) => {
<span>{reactionType}</span> <span>{reactionType}</span>
<span className="opacity-60">{users.length}</span> <span className="opacity-60">{users.length}</span>
</div> </div>
</TooltipTrigger>
<TooltipContent>
<p>{stringifyUsers(users, reactionType)}</p>
</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider>
); );
}); });
......
import { List, ListItem } from "@mui/joy";
import { Button, Input } from "@usememos/mui";
import { XIcon } from "lucide-react"; import { XIcon } from "lucide-react";
import React, { useState } from "react"; import React, { useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { memoServiceClient } from "@/grpcweb"; import { memoServiceClient } from "@/grpcweb";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
...@@ -50,7 +50,7 @@ const RenameTagDialog: React.FC<Props> = (props: Props) => { ...@@ -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="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"> <div className="flex flex-row justify-between items-center mb-4 gap-2 w-full">
<p className="title-text">{t("tag.rename-tag")}</p> <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" /> <XIcon className="w-5 h-auto" />
</Button> </Button>
</div> </div>
...@@ -64,17 +64,17 @@ const RenameTagDialog: React.FC<Props> = (props: Props) => { ...@@ -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> <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} /> <Input className="w-full" type="text" placeholder="A new tag name" value={newName} onChange={handleTagNameInputChange} />
</div> </div>
<List size="sm" marker="disc"> <ul className="list-disc list-inside text-sm ml-4">
<ListItem> <li>
<p className="leading-5">{t("tag.rename-tip")}</p> <p className="leading-5">{t("tag.rename-tip")}</p>
</ListItem> </li>
</List> </ul>
</div> </div>
<div className="w-full flex flex-row justify-end items-center space-x-2"> <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")} {t("common.cancel")}
</Button> </Button>
<Button color="primary" disabled={requestState.isLoading} onClick={handleConfirm}> <Button disabled={requestState.isLoading} onClick={handleConfirm}>
{t("common.confirm")} {t("common.confirm")}
</Button> </Button>
</div> </div>
......
import { SearchIcon } from "lucide-react"; import { SearchIcon } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useState } from "react"; import { useState } from "react";
import { cn } from "@/lib/utils";
import { memoFilterStore } from "@/store/v2"; import { memoFilterStore } from "@/store/v2";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import MemoDisplaySettingMenu from "./MemoDisplaySettingMenu"; import MemoDisplaySettingMenu from "./MemoDisplaySettingMenu";
......
import { Button } from "@usememos/mui";
import copy from "copy-to-clipboard"; import copy from "copy-to-clipboard";
import { ClipboardIcon, TrashIcon } from "lucide-react"; import { ClipboardIcon, TrashIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { userServiceClient } from "@/grpcweb"; import { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { UserAccessToken } from "@/types/proto/api/v1/user_service"; import { UserAccessToken } from "@/types/proto/api/v1/user_service";
...@@ -99,7 +99,7 @@ const AccessTokenSection = () => { ...@@ -99,7 +99,7 @@ const AccessTokenSection = () => {
<tr key={userAccessToken.accessToken}> <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"> <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> <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" /> <ClipboardIcon className="w-4 h-auto text-gray-400" />
</Button> </Button>
</td> </td>
...@@ -114,7 +114,7 @@ const AccessTokenSection = () => { ...@@ -114,7 +114,7 @@ const AccessTokenSection = () => {
</td> </td>
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm"> <td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm">
<Button <Button
variant="plain" variant="ghost"
onClick={() => { onClick={() => {
handleDeleteAccessToken(userAccessToken); handleDeleteAccessToken(userAccessToken);
}} }}
......
import { Radio, RadioGroup } from "@mui/joy";
import { Button, Input } from "@usememos/mui";
import { sortBy } from "lodash-es"; import { sortBy } from "lodash-es";
import { MoreVerticalIcon } from "lucide-react"; import { MoreVerticalIcon } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; 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 { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { userStore } from "@/store/v2"; import { userStore } from "@/store/v2";
...@@ -12,7 +14,7 @@ import { State } from "@/types/proto/api/v1/common"; ...@@ -12,7 +14,7 @@ import { State } from "@/types/proto/api/v1/common";
import { User, User_Role } from "@/types/proto/api/v1/user_service"; import { User, User_Role } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import showCreateUserDialog from "../CreateUserDialog"; import showCreateUserDialog from "../CreateUserDialog";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
interface LocalState { interface LocalState {
creatingUser: User; creatingUser: User;
...@@ -167,15 +169,23 @@ const MemberSection = observer(() => { ...@@ -167,15 +169,23 @@ const MemberSection = observer(() => {
</div> </div>
<div className="flex flex-col justify-start items-start gap-1"> <div className="flex flex-col justify-start items-start gap-1">
<span>{t("common.role")}</span> <span>{t("common.role")}</span>
<RadioGroup orientation="horizontal" defaultValue={User_Role.USER} onChange={handleUserRoleInputChange}> <RadioGroup
<Radio value={User_Role.USER} label={t("setting.member-section.user")} /> defaultValue={User_Role.USER}
<Radio value={User_Role.ADMIN} label={t("setting.member-section.admin")} /> 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> </RadioGroup>
</div> </div>
<div className="mt-2"> <div className="mt-2">
<Button color="primary" onClick={handleCreateUserBtnClick}> <Button onClick={handleCreateUserBtnClick}>{t("common.create")}</Button>
{t("common.create")}
</Button>
</div> </div>
</div> </div>
<div className="w-full flex flex-row justify-between items-center mt-6"> <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 { isEqual, uniq } from "lodash-es";
import { CheckIcon } from "lucide-react"; import { CheckIcon, X } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useState } from "react"; import { useState } from "react";
import { toast } from "react-hot-toast"; 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 { workspaceSettingNamePrefix } from "@/store/common";
import { workspaceStore } from "@/store/v2"; import { workspaceStore } from "@/store/v2";
import { WorkspaceSettingKey } from "@/store/v2/workspace"; import { WorkspaceSettingKey } from "@/store/v2/workspace";
...@@ -70,42 +72,42 @@ const MemoRelatedSettings = observer(() => { ...@@ -70,42 +72,42 @@ const MemoRelatedSettings = observer(() => {
<span>{t("setting.system-section.disable-public-memos")}</span> <span>{t("setting.system-section.disable-public-memos")}</span>
<Switch <Switch
checked={memoRelatedSetting.disallowPublicVisibility} checked={memoRelatedSetting.disallowPublicVisibility}
onChange={(event) => updatePartialSetting({ disallowPublicVisibility: event.target.checked })} onCheckedChange={(checked) => updatePartialSetting({ disallowPublicVisibility: checked })}
/> />
</div> </div>
<div className="w-full flex flex-row justify-between items-center"> <div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.system-section.display-with-updated-time")}</span> <span>{t("setting.system-section.display-with-updated-time")}</span>
<Switch <Switch
checked={memoRelatedSetting.displayWithUpdateTime} checked={memoRelatedSetting.displayWithUpdateTime}
onChange={(event) => updatePartialSetting({ displayWithUpdateTime: event.target.checked })} onCheckedChange={(checked) => updatePartialSetting({ displayWithUpdateTime: checked })}
/> />
</div> </div>
<div className="w-full flex flex-row justify-between items-center"> <div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.memo-related-settings.enable-link-preview")}</span> <span>{t("setting.memo-related-settings.enable-link-preview")}</span>
<Switch <Switch
checked={memoRelatedSetting.enableLinkPreview} checked={memoRelatedSetting.enableLinkPreview}
onChange={(event) => updatePartialSetting({ enableLinkPreview: event.target.checked })} onCheckedChange={(checked) => updatePartialSetting({ enableLinkPreview: checked })}
/> />
</div> </div>
<div className="w-full flex flex-row justify-between items-center"> <div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.memo-related-settings.enable-memo-comments")}</span> <span>{t("setting.memo-related-settings.enable-memo-comments")}</span>
<Switch <Switch
checked={memoRelatedSetting.enableComment} checked={memoRelatedSetting.enableComment}
onChange={(event) => updatePartialSetting({ enableComment: event.target.checked })} onCheckedChange={(checked) => updatePartialSetting({ enableComment: checked })}
/> />
</div> </div>
<div className="w-full flex flex-row justify-between items-center"> <div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.system-section.enable-double-click-to-edit")}</span> <span>{t("setting.system-section.enable-double-click-to-edit")}</span>
<Switch <Switch
checked={memoRelatedSetting.enableDoubleClickEdit} checked={memoRelatedSetting.enableDoubleClickEdit}
onChange={(event) => updatePartialSetting({ enableDoubleClickEdit: event.target.checked })} onCheckedChange={(checked) => updatePartialSetting({ enableDoubleClickEdit: checked })}
/> />
</div> </div>
<div className="w-full flex flex-row justify-between items-center"> <div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.system-section.disable-markdown-shortcuts-in-editor")}</span> <span>{t("setting.system-section.disable-markdown-shortcuts-in-editor")}</span>
<Switch <Switch
checked={memoRelatedSetting.disableMarkdownShortcuts} checked={memoRelatedSetting.disableMarkdownShortcuts}
onChange={(event) => updatePartialSetting({ disableMarkdownShortcuts: event.target.checked })} onCheckedChange={(checked) => updatePartialSetting({ disableMarkdownShortcuts: checked })}
/> />
</div> </div>
<div className="w-full flex flex-row justify-between items-center"> <div className="w-full flex flex-row justify-between items-center">
...@@ -122,33 +124,27 @@ const MemoRelatedSettings = observer(() => { ...@@ -122,33 +124,27 @@ const MemoRelatedSettings = observer(() => {
<div className="mt-2 w-full flex flex-row flex-wrap gap-1"> <div className="mt-2 w-full flex flex-row flex-wrap gap-1">
{memoRelatedSetting.reactions.map((reactionType) => { {memoRelatedSetting.reactions.map((reactionType) => {
return ( return (
<Chip <Badge key={reactionType} variant="outline" className="h-8 flex items-center gap-1">
className="h-8!"
key={reactionType}
variant="outlined"
size="lg"
endDecorator={
<ChipDelete
onDelete={() => updatePartialSetting({ reactions: memoRelatedSetting.reactions.filter((r) => r !== reactionType) })}
/>
}
>
{reactionType} {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 <Input
className="w-32 rounded-full! pl-1!" className="w-32"
placeholder={t("common.input")} placeholder={t("common.input")}
value={editingReaction} value={editingReaction}
onChange={(event) => setEditingReaction(event.target.value.trim())} onChange={(event) => setEditingReaction(event.target.value.trim())}
endDecorator={ />
<CheckIcon <CheckIcon
className="w-5 h-5 text-gray-500 dark:text-gray-400 cursor-pointer hover:text-teal-600" className="w-5 h-5 text-gray-500 dark:text-gray-400 cursor-pointer hover:text-teal-600"
onClick={() => upsertReaction()} onClick={() => upsertReaction()}
/> />
} </div>
/>
</div> </div>
</div> </div>
<div className="w-full"> <div className="w-full">
...@@ -156,43 +152,37 @@ const MemoRelatedSettings = observer(() => { ...@@ -156,43 +152,37 @@ const MemoRelatedSettings = observer(() => {
<span>{t("setting.memo-related-settings.enable-blur-nsfw-content")}</span> <span>{t("setting.memo-related-settings.enable-blur-nsfw-content")}</span>
<Switch <Switch
checked={memoRelatedSetting.enableBlurNsfwContent} checked={memoRelatedSetting.enableBlurNsfwContent}
onChange={(event) => updatePartialSetting({ enableBlurNsfwContent: event.target.checked })} onCheckedChange={(checked) => updatePartialSetting({ enableBlurNsfwContent: checked })}
/> />
</div> </div>
<div className="mt-2 w-full flex flex-row flex-wrap gap-1"> <div className="mt-2 w-full flex flex-row flex-wrap gap-1">
{memoRelatedSetting.nsfwTags.map((nsfwTag) => { {memoRelatedSetting.nsfwTags.map((nsfwTag) => {
return ( return (
<Chip <Badge key={nsfwTag} variant="outline" className="h-8 flex items-center gap-1">
className="h-8!"
key={nsfwTag}
variant="outlined"
size="lg"
endDecorator={
<ChipDelete
onDelete={() => updatePartialSetting({ nsfwTags: memoRelatedSetting.nsfwTags.filter((r) => r !== nsfwTag) })}
/>
}
>
{nsfwTag} {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 <Input
className="w-32 rounded-full! pl-1!" className="w-32"
placeholder={t("common.input")} placeholder={t("common.input")}
value={editingNsfwTag} value={editingNsfwTag}
onChange={(event) => setEditingNsfwTag(event.target.value.trim())} onChange={(event) => setEditingNsfwTag(event.target.value.trim())}
endDecorator={ />
<CheckIcon <CheckIcon
className="w-5 h-5 text-gray-500 dark:text-gray-400 cursor-pointer hover:text-teal-600" className="w-5 h-5 text-gray-500 dark:text-gray-400 cursor-pointer hover:text-teal-600"
onClick={() => upsertNsfwTags()} onClick={() => upsertNsfwTags()}
/> />
} </div>
/>
</div> </div>
</div> </div>
<div className="mt-2 w-full flex justify-end"> <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")} {t("common.save")}
</Button> </Button>
</div> </div>
......
import { Button } from "@usememos/mui";
import { MoreVerticalIcon, PenLineIcon } from "lucide-react"; import { MoreVerticalIcon, PenLineIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import showChangeMemberPasswordDialog from "../ChangeMemberPasswordDialog"; import showChangeMemberPasswordDialog from "../ChangeMemberPasswordDialog";
import showUpdateAccountDialog from "../UpdateAccountDialog"; import showUpdateAccountDialog from "../UpdateAccountDialog";
import UserAvatar from "../UserAvatar"; import UserAvatar from "../UserAvatar";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import AccessTokenSection from "./AccessTokenSection"; import AccessTokenSection from "./AccessTokenSection";
import UserSessionsSection from "./UserSessionsSection"; import UserSessionsSection from "./UserSessionsSection";
...@@ -27,13 +27,13 @@ const MyAccountSection = () => { ...@@ -27,13 +27,13 @@ const MyAccountSection = () => {
</div> </div>
</div> </div>
<div className="w-full flex flex-row justify-start items-center mt-2 space-x-2"> <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" /> <PenLineIcon className="w-4 h-4 mx-auto mr-1" />
{t("common.edit")} {t("common.edit")}
</Button> </Button>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button variant="outlined"> <Button variant="outline">
<MoreVerticalIcon className="w-4 h-4 mx-auto" /> <MoreVerticalIcon className="w-4 h-4 mx-auto" />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
......
import { Divider, Option, Select } from "@mui/joy";
import { observer } from "mobx-react-lite"; 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 { userStore } from "@/store/v2";
import { Visibility } from "@/types/proto/api/v1/memo_service"; import { Visibility } from "@/types/proto/api/v1/memo_service";
import { UserSetting } from "@/types/proto/api/v1/user_service"; import { UserSetting } from "@/types/proto/api/v1/user_service";
...@@ -44,27 +45,26 @@ const PreferencesSection = observer(() => { ...@@ -44,27 +45,26 @@ const PreferencesSection = observer(() => {
<div className="w-full flex flex-row justify-between items-center"> <div className="w-full flex flex-row justify-between items-center">
<span className="truncate">{t("setting.preference-section.default-memo-visibility")}</span> <span className="truncate">{t("setting.preference-section.default-memo-visibility")}</span>
<Select <Select value={setting.memoVisibility} onValueChange={handleDefaultMemoVisibilityChanged}>
className="min-w-fit!" <SelectTrigger className="min-w-fit">
value={setting.memoVisibility} <div className="flex items-center gap-2">
startDecorator={<VisibilityIcon visibility={convertVisibilityFromString(setting.memoVisibility)} />} <VisibilityIcon visibility={convertVisibilityFromString(setting.memoVisibility)} />
onChange={(_, visibility) => { <SelectValue />
if (visibility) { </div>
handleDefaultMemoVisibilityChanged(visibility); </SelectTrigger>
} <SelectContent>
}}
>
{[Visibility.PRIVATE, Visibility.PROTECTED, Visibility.PUBLIC] {[Visibility.PRIVATE, Visibility.PROTECTED, Visibility.PUBLIC]
.map((v) => convertVisibilityToString(v)) .map((v) => convertVisibilityToString(v))
.map((item) => ( .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>}`)} {t(`memo.visibility.${item.toLowerCase() as Lowercase<typeof item>}`)}
</Option> </SelectItem>
))} ))}
</SelectContent>
</Select> </Select>
</div> </div>
<Divider className="my-3!" /> <Separator className="my-3" />
<WebhookSection /> <WebhookSection />
</div> </div>
......
import { Divider, List, ListItem } from "@mui/joy";
import { Button } from "@usememos/mui";
import { MoreVerticalIcon } from "lucide-react"; import { MoreVerticalIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { identityProviderServiceClient } from "@/grpcweb"; import { identityProviderServiceClient } from "@/grpcweb";
import { IdentityProvider } from "@/types/proto/api/v1/idp_service"; import { IdentityProvider } from "@/types/proto/api/v1/idp_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import showCreateIdentityProviderDialog from "../CreateIdentityProviderDialog"; import showCreateIdentityProviderDialog from "../CreateIdentityProviderDialog";
import LearnMore from "../LearnMore"; import LearnMore from "../LearnMore";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
const SSOSection = () => { const SSOSection = () => {
const t = useTranslate(); const t = useTranslate();
...@@ -48,7 +48,7 @@ const SSOSection = () => { ...@@ -48,7 +48,7 @@ const SSOSection = () => {
{t("common.create")} {t("common.create")}
</Button> </Button>
</div> </div>
<Divider /> <Separator />
{identityProviderList.map((identityProvider) => ( {identityProviderList.map((identityProvider) => (
<div <div
key={identityProvider.name} key={identityProvider.name}
...@@ -95,8 +95,8 @@ const SSOSection = () => { ...@@ -95,8 +95,8 @@ const SSOSection = () => {
<div className="w-full mt-4"> <div className="w-full mt-4">
<p className="text-sm">{t("common.learn-more")}:</p> <p className="text-sm">{t("common.learn-more")}:</p>
<List component="ul" marker="disc" size="sm"> <ul className="list-disc list-inside text-sm ml-4">
<ListItem> <li>
<Link <Link
className="text-sm text-blue-600 hover:underline" className="text-sm text-blue-600 hover:underline"
to="https://www.usememos.com/docs/advanced-settings/sso" to="https://www.usememos.com/docs/advanced-settings/sso"
...@@ -104,8 +104,8 @@ const SSOSection = () => { ...@@ -104,8 +104,8 @@ const SSOSection = () => {
> >
{t("setting.sso-section.single-sign-on")} {t("setting.sso-section.single-sign-on")}
</Link> </Link>
</ListItem> </li>
</List> </ul>
</div> </div>
</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 { isEqual } from "lodash-es";
import { HelpCircleIcon } from "lucide-react"; import { HelpCircleIcon } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { Link } from "react-router-dom"; 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 { workspaceSettingNamePrefix } from "@/store/common";
import { workspaceStore } from "@/store/v2"; import { workspaceStore } from "@/store/v2";
import { WorkspaceSettingKey } from "@/store/v2/workspace"; import { WorkspaceSettingKey } from "@/store/v2/workspace";
...@@ -131,23 +136,38 @@ const StorageSection = observer(() => { ...@@ -131,23 +136,38 @@ const StorageSection = observer(() => {
<div className="w-full flex flex-col gap-2 pt-2 pb-4"> <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> <div className="font-medium text-gray-700 dark:text-gray-500">{t("setting.storage-section.current-storage")}</div>
<RadioGroup <RadioGroup
orientation="horizontal" value={workspaceStorageSetting.storageType.toString()}
className="w-full" onValueChange={(value) => {
value={workspaceStorageSetting.storageType} handleStorageTypeChanged(parseInt(value) as unknown as WorkspaceStorageSetting_StorageType);
onChange={(event) => {
handleStorageTypeChanged(event.target.value as WorkspaceStorageSetting_StorageType);
}} }}
className="flex flex-row gap-4"
> >
<Radio value={WorkspaceStorageSetting_StorageType.DATABASE} label={t("setting.storage-section.type-database")} /> <div className="flex items-center space-x-2">
<Radio value={WorkspaceStorageSetting_StorageType.LOCAL} label={t("setting.storage-section.type-local")} /> <RadioGroupItem value={WorkspaceStorageSetting_StorageType.DATABASE.toString()} id="database" />
<Radio value={WorkspaceStorageSetting_StorageType.S3} label={"S3"} /> <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> </RadioGroup>
<div className="w-full flex flex-row justify-between items-center"> <div className="w-full flex flex-row justify-between items-center">
<div className="flex flex-row 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> <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" /> <HelpCircleIcon className="w-4 h-auto" />
</TooltipTrigger>
<TooltipContent>
<p>{t("setting.system-section.max-upload-size-hint")}</p>
</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider>
</div> </div>
<Input className="w-16 font-mono" value={workspaceStorageSetting.uploadSizeLimitMb} onChange={handleMaxUploadSizeChanged} /> <Input className="w-16 font-mono" value={workspaceStorageSetting.uploadSizeLimitMb} onChange={handleMaxUploadSizeChanged} />
</div> </div>
...@@ -189,20 +209,23 @@ const StorageSection = observer(() => { ...@@ -189,20 +209,23 @@ const StorageSection = observer(() => {
</div> </div>
<div className="w-full flex flex-row justify-between items-center"> <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> <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>
</> </>
)} )}
<div> <div>
<Button color="primary" disabled={!allowSaveStorageSetting} onClick={saveWorkspaceStorageSetting}> <Button disabled={!allowSaveStorageSetting} onClick={saveWorkspaceStorageSetting}>
{t("common.save")} {t("common.save")}
</Button> </Button>
</div> </div>
<Divider className="my-2!" /> <Separator className="my-2" />
<div className="w-full mt-4"> <div className="w-full mt-4">
<p className="text-sm">{t("common.learn-more")}:</p> <p className="text-sm">{t("common.learn-more")}:</p>
<List component="ul" marker="disc" size="sm"> <ul className="text-sm list-disc ml-4 space-y-1">
<ListItem> <li>
<Link <Link
className="text-sm text-blue-600 hover:underline" className="text-sm text-blue-600 hover:underline"
to="https://www.usememos.com/docs/advanced-settings/local-storage" to="https://www.usememos.com/docs/advanced-settings/local-storage"
...@@ -210,8 +233,8 @@ const StorageSection = observer(() => { ...@@ -210,8 +233,8 @@ const StorageSection = observer(() => {
> >
Docs - Local storage Docs - Local storage
</Link> </Link>
</ListItem> </li>
<ListItem> <li>
<Link <Link
className="text-sm text-blue-600 hover:underline" className="text-sm text-blue-600 hover:underline"
to="https://www.usememos.com/blog/choosing-a-storage-for-your-resource" to="https://www.usememos.com/blog/choosing-a-storage-for-your-resource"
...@@ -219,8 +242,8 @@ const StorageSection = observer(() => { ...@@ -219,8 +242,8 @@ const StorageSection = observer(() => {
> >
Choosing a Storage for Your Resource: Database, S3 or Local Storage? Choosing a Storage for Your Resource: Database, S3 or Local Storage?
</Link> </Link>
</ListItem> </li>
</List> </ul>
</div> </div>
</div> </div>
); );
......
import { Button } from "@usememos/mui";
import { ClockIcon, MonitorIcon, SmartphoneIcon, TabletIcon, TrashIcon, WifiIcon } from "lucide-react"; import { ClockIcon, MonitorIcon, SmartphoneIcon, TabletIcon, TrashIcon, WifiIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { Button } from "@/components/ui/button";
import { userServiceClient } from "@/grpcweb"; import { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { UserSession } from "@/types/proto/api/v1/user_service"; import { UserSession } from "@/types/proto/api/v1/user_service";
...@@ -124,7 +124,7 @@ const UserSessionsSection = () => { ...@@ -124,7 +124,7 @@ const UserSessionsSection = () => {
</td> </td>
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm"> <td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm">
<Button <Button
variant="plain" variant="ghost"
disabled={isCurrentSession(userSession)} disabled={isCurrentSession(userSession)}
onClick={() => { onClick={() => {
handleRevokeSession(userSession); handleRevokeSession(userSession);
......
import { Button } from "@usememos/mui";
import { ExternalLinkIcon, TrashIcon } from "lucide-react"; import { ExternalLinkIcon, TrashIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { webhookServiceClient } from "@/grpcweb"; import { webhookServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { Webhook } from "@/types/proto/api/v1/webhook_service"; import { Webhook } from "@/types/proto/api/v1/webhook_service";
...@@ -85,7 +85,7 @@ const WebhookSection = () => { ...@@ -85,7 +85,7 @@ const WebhookSection = () => {
</td> </td>
<td className="relative whitespace-nowrap px-3 py-2 text-right text-sm"> <td className="relative whitespace-nowrap px-3 py-2 text-right text-sm">
<Button <Button
variant="plain" variant="ghost"
onClick={() => { onClick={() => {
handleDeleteWebhook(webhook); handleDeleteWebhook(webhook);
}} }}
......
import { Select, Option, Divider } from "@mui/joy";
import { Button, Textarea, Switch } from "@usememos/mui";
import { isEqual } from "lodash-es"; import { isEqual } from "lodash-es";
import { ExternalLinkIcon } from "lucide-react"; import { ExternalLinkIcon } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { Link } from "react-router-dom"; 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 { identityProviderServiceClient } from "@/grpcweb";
import { workspaceSettingNamePrefix } from "@/store/common"; import { workspaceSettingNamePrefix } from "@/store/common";
import { workspaceStore } from "@/store/v2"; import { workspaceStore } from "@/store/v2";
...@@ -71,19 +74,18 @@ const WorkspaceSection = observer(() => { ...@@ -71,19 +74,18 @@ const WorkspaceSection = observer(() => {
{t("setting.system-section.server-name")}:{" "} {t("setting.system-section.server-name")}:{" "}
<span className="font-mono font-bold">{workspaceGeneralSetting.customProfile?.title || "Memos"}</span> <span className="font-mono font-bold">{workspaceGeneralSetting.customProfile?.title || "Memos"}</span>
</div> </div>
<Button variant="outlined" onClick={handleUpdateCustomizedProfileButtonClick}> <Button variant="outline" onClick={handleUpdateCustomizedProfileButtonClick}>
{t("common.edit")} {t("common.edit")}
</Button> </Button>
</div> </div>
<Divider /> <Separator />
<p className="font-medium text-gray-700 dark:text-gray-500">{t("setting.system-section.title")}</p> <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"> <div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.system-section.additional-style")}</span> <span>{t("setting.system-section.additional-style")}</span>
</div> </div>
<Textarea <Textarea
className="font-mono" className="font-mono w-full"
rows={3} rows={3}
fullWidth
placeholder={t("setting.system-section.additional-style-placeholder")} placeholder={t("setting.system-section.additional-style-placeholder")}
value={workspaceGeneralSetting.additionalStyle} value={workspaceGeneralSetting.additionalStyle}
onChange={(event) => updatePartialSetting({ additionalStyle: event.target.value })} onChange={(event) => updatePartialSetting({ additionalStyle: event.target.value })}
...@@ -92,9 +94,8 @@ const WorkspaceSection = observer(() => { ...@@ -92,9 +94,8 @@ const WorkspaceSection = observer(() => {
<span>{t("setting.system-section.additional-script")}</span> <span>{t("setting.system-section.additional-script")}</span>
</div> </div>
<Textarea <Textarea
className="font-mono" className="font-mono w-full"
rows={3} rows={3}
fullWidth
placeholder={t("setting.system-section.additional-script-placeholder")} placeholder={t("setting.system-section.additional-script-placeholder")}
value={workspaceGeneralSetting.additionalScript} value={workspaceGeneralSetting.additionalScript}
onChange={(event) => updatePartialSetting({ additionalScript: event.target.value })} onChange={(event) => updatePartialSetting({ additionalScript: event.target.value })}
...@@ -114,7 +115,7 @@ const WorkspaceSection = observer(() => { ...@@ -114,7 +115,7 @@ const WorkspaceSection = observer(() => {
<Switch <Switch
disabled={workspaceStore.state.profile.mode === "demo"} disabled={workspaceStore.state.profile.mode === "demo"}
checked={workspaceGeneralSetting.disallowUserRegistration} checked={workspaceGeneralSetting.disallowUserRegistration}
onChange={(event) => updatePartialSetting({ disallowUserRegistration: event.target.checked })} onCheckedChange={(checked) => updatePartialSetting({ disallowUserRegistration: checked })}
/> />
</div> </div>
<div className="w-full flex flex-row justify-between items-center"> <div className="w-full flex flex-row justify-between items-center">
...@@ -125,39 +126,43 @@ const WorkspaceSection = observer(() => { ...@@ -125,39 +126,43 @@ const WorkspaceSection = observer(() => {
(identityProviderList.length === 0 && !workspaceGeneralSetting.disallowPasswordAuth) (identityProviderList.length === 0 && !workspaceGeneralSetting.disallowPasswordAuth)
} }
checked={workspaceGeneralSetting.disallowPasswordAuth} checked={workspaceGeneralSetting.disallowPasswordAuth}
onChange={(event) => updatePartialSetting({ disallowPasswordAuth: event.target.checked })} onCheckedChange={(checked) => updatePartialSetting({ disallowPasswordAuth: checked })}
/> />
</div> </div>
<div className="w-full flex flex-row justify-between items-center"> <div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.workspace-section.disallow-change-username")}</span> <span>{t("setting.workspace-section.disallow-change-username")}</span>
<Switch <Switch
checked={workspaceGeneralSetting.disallowChangeUsername} checked={workspaceGeneralSetting.disallowChangeUsername}
onChange={(event) => updatePartialSetting({ disallowChangeUsername: event.target.checked })} onCheckedChange={(checked) => updatePartialSetting({ disallowChangeUsername: checked })}
/> />
</div> </div>
<div className="w-full flex flex-row justify-between items-center"> <div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.workspace-section.disallow-change-nickname")}</span> <span>{t("setting.workspace-section.disallow-change-nickname")}</span>
<Switch <Switch
checked={workspaceGeneralSetting.disallowChangeNickname} checked={workspaceGeneralSetting.disallowChangeNickname}
onChange={(event) => updatePartialSetting({ disallowChangeNickname: event.target.checked })} onCheckedChange={(checked) => updatePartialSetting({ disallowChangeNickname: checked })}
/> />
</div> </div>
<div className="w-full flex flex-row justify-between items-center"> <div className="w-full flex flex-row justify-between items-center">
<span className="truncate">{t("setting.workspace-section.week-start-day")}</span> <span className="truncate">{t("setting.workspace-section.week-start-day")}</span>
<Select <Select
className="min-w-fit!" value={workspaceGeneralSetting.weekStartDayOffset.toString()}
value={workspaceGeneralSetting.weekStartDayOffset} onValueChange={(value) => {
onChange={(_, weekStartDayOffset) => { updatePartialSetting({ weekStartDayOffset: parseInt(value) || 0 });
updatePartialSetting({ weekStartDayOffset: weekStartDayOffset || 0 });
}} }}
> >
<Option value={-1}>{t("setting.workspace-section.saturday")}</Option> <SelectTrigger className="min-w-fit">
<Option value={0}>{t("setting.workspace-section.sunday")}</Option> <SelectValue />
<Option value={1}>{t("setting.workspace-section.monday")}</Option> </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> </Select>
</div> </div>
<div className="mt-2 w-full flex justify-end"> <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")} {t("common.save")}
</Button> </Button>
</div> </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 type { StatCardProps } from "@/types/statistics";
import { cn } from "@/utils";
export const StatCard = ({ icon, label, count, onClick, tooltip, className }: StatCardProps) => { export const StatCard = ({ icon, label, count, onClick, tooltip, className }: StatCardProps) => {
const content = ( const content = (
...@@ -22,9 +22,14 @@ export const StatCard = ({ icon, label, count, onClick, tooltip, className }: St ...@@ -22,9 +22,14 @@ export const StatCard = ({ icon, label, count, onClick, tooltip, className }: St
if (tooltip) { if (tooltip) {
return ( return (
<Tooltip title={tooltip} placement="top" arrow> <TooltipProvider>
{content} <Tooltip>
<TooltipTrigger asChild>{content}</TooltipTrigger>
<TooltipContent>
<p>{tooltip}</p>
</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider>
); );
} }
......
import { Button, Input, Textarea } from "@usememos/mui";
import { isEqual } from "lodash-es"; import { isEqual } from "lodash-es";
import { XIcon } from "lucide-react"; import { XIcon } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { toast } from "react-hot-toast"; 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 { convertFileToBase64 } from "@/helpers/utils";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { userStore, workspaceStore } from "@/store/v2"; import { userStore, workspaceStore } from "@/store/v2";
...@@ -142,7 +144,7 @@ const UpdateAccountDialog = ({ destroy }: Props) => { ...@@ -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="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"> <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> <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" /> <XIcon className="w-5 h-auto" />
</Button> </Button>
</div> </div>
...@@ -188,16 +190,14 @@ const UpdateAccountDialog = ({ destroy }: Props) => { ...@@ -188,16 +190,14 @@ const UpdateAccountDialog = ({ destroy }: Props) => {
{t("common.email")} {t("common.email")}
<span className="text-sm text-gray-400 ml-1">({t("setting.account-section.email-note")})</span> <span className="text-sm text-gray-400 ml-1">({t("setting.account-section.email-note")})</span>
</p> </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> <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"> <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")} {t("common.cancel")}
</Button> </Button>
<Button color="primary" onClick={handleSaveBtnClick}> <Button onClick={handleSaveBtnClick}>{t("common.save")}</Button>
{t("common.save")}
</Button>
</div> </div>
</div> </div>
</div> </div>
......
import { Button, Input, Textarea } from "@usememos/mui";
import { XIcon } from "lucide-react"; import { XIcon } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { toast } from "react-hot-toast"; 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 { workspaceSettingNamePrefix } from "@/store/common";
import { workspaceStore } from "@/store/v2"; import { workspaceStore } from "@/store/v2";
import { WorkspaceSettingKey } from "@/store/v2/workspace"; import { WorkspaceSettingKey } from "@/store/v2/workspace";
...@@ -99,7 +101,7 @@ const UpdateCustomizedProfileDialog = ({ destroy }: Props) => { ...@@ -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="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"> <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> <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" /> <XIcon className="w-5 h-auto" />
</Button> </Button>
</div> </div>
...@@ -109,19 +111,19 @@ const UpdateCustomizedProfileDialog = ({ destroy }: Props) => { ...@@ -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> <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} /> <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> <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> <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} /> <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> <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} /> <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="mt-4 w-full flex flex-row justify-between items-center space-x-2">
<div className="flex flex-row justify-start items-center"> <div className="flex flex-row justify-start items-center">
<Button variant="outlined" onClick={handleRestoreButtonClick}> <Button variant="outline" onClick={handleRestoreButtonClick}>
{t("common.restore")} {t("common.restore")}
</Button> </Button>
</div> </div>
<div className="flex flex-row justify-end items-center gap-2"> <div className="flex flex-row justify-end items-center gap-2">
<Button variant="plain" onClick={handleCloseButtonClick}> <Button variant="ghost" onClick={handleCloseButtonClick}>
{t("common.cancel")} {t("common.cancel")}
</Button> </Button>
<Button color="primary" onClick={handleSaveButtonClick}> <Button color="primary" onClick={handleSaveButtonClick}>
......
import { cn } from "@/utils"; import { cn } from "@/lib/utils";
interface Props { interface Props {
avatarUrl?: string; avatarUrl?: string;
......
...@@ -2,11 +2,11 @@ import { ArchiveIcon, LogOutIcon, User2Icon, SquareUserIcon, SettingsIcon, BellI ...@@ -2,11 +2,11 @@ import { ArchiveIcon, LogOutIcon, User2Icon, SquareUserIcon, SettingsIcon, BellI
import { authServiceClient } from "@/grpcweb"; import { authServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { Routes } from "@/router"; import { Routes } from "@/router";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import UserAvatar from "./UserAvatar"; import UserAvatar from "./UserAvatar";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/Popover"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
interface Props { interface Props {
collapsed?: boolean; collapsed?: boolean;
......
import { Globe2Icon, LockIcon, UsersIcon } from "lucide-react"; import { Globe2Icon, LockIcon, UsersIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { Visibility } from "@/types/proto/api/v1/memo_service"; import { Visibility } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
interface Props { interface Props {
visibility: Visibility; visibility: Visibility;
......
import { Tooltip } from "@mui/joy";
import { useRef, useState, useEffect } from "react"; 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 { interface Props {
children: React.ReactNode; children: React.ReactNode;
...@@ -20,11 +20,20 @@ const OverflowTip = ({ children, className }: Props) => { ...@@ -20,11 +20,20 @@ const OverflowTip = ({ children, className }: Props) => {
}, []); }, []);
return ( return (
<Tooltip title={children} placement="top" arrow disableHoverListener={!isOverflowed}> <TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div ref={textElementRef} className={cn("truncate", className)}> <div ref={textElementRef} className={cn("truncate", className)}>
{children} {children}
</div> </div>
</TooltipTrigger>
{isOverflowed && (
<TooltipContent>
<p>{children}</p>
</TooltipContent>
)}
</Tooltip> </Tooltip>
</TooltipProvider>
); );
}; };
......
import * as PopoverPrimitive from "@radix-ui/react-popover"; import * as PopoverPrimitive from "@radix-ui/react-popover";
import * as React from "react"; 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< function PopoverContent({ className, align = "center", sideOffset = 4, ...props }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
React.ElementRef<typeof PopoverPrimitive.Content>, return (
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal> <PopoverPrimitive.Portal>
<PopoverPrimitive.Content <PopoverPrimitive.Content
ref={ref} data-slot="popover-content"
align={align} align={align}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( 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, className,
)} )}
{...props} {...props}
/> />
</PopoverPrimitive.Portal> </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"; ...@@ -3,7 +3,7 @@ import { Outlet } from "react-router-dom";
import { HomeSidebar, HomeSidebarDrawer } from "@/components/HomeSidebar"; import { HomeSidebar, HomeSidebarDrawer } from "@/components/HomeSidebar";
import MobileHeader from "@/components/MobileHeader"; import MobileHeader from "@/components/MobileHeader";
import useResponsiveWidth from "@/hooks/useResponsiveWidth"; import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { cn } from "@/utils"; import { cn } from "@/lib/utils";
const HomeLayout = observer(() => { const HomeLayout = observer(() => {
const { md, lg } = useResponsiveWidth(); const { md, lg } = useResponsiveWidth();
......
...@@ -5,11 +5,11 @@ import usePrevious from "react-use/lib/usePrevious"; ...@@ -5,11 +5,11 @@ import usePrevious from "react-use/lib/usePrevious";
import Navigation from "@/components/Navigation"; import Navigation from "@/components/Navigation";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import useResponsiveWidth from "@/hooks/useResponsiveWidth"; import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { cn } from "@/lib/utils";
import Loading from "@/pages/Loading"; import Loading from "@/pages/Loading";
import { Routes } from "@/router"; import { Routes } from "@/router";
import { workspaceStore } from "@/store/v2"; import { workspaceStore } from "@/store/v2";
import memoFilterStore from "@/store/v2/memoFilter"; import memoFilterStore from "@/store/v2/memoFilter";
import { cn } from "@/utils";
const RootLayout = observer(() => { const RootLayout = observer(() => {
const location = useLocation(); const location = useLocation();
......
import { type ClassValue, clsx } from "clsx"; import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
export const cn = (...inputs: ClassValue[]) => { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); 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