chore: upgrade tailwindcss to v4

This commit is contained in:
Johnny 2025-06-07 10:15:12 +08:00
parent a50253d311
commit f5c64849d2
58 changed files with 507 additions and 1924 deletions

View File

@ -1,17 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/css/tailwind.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/utils/utils"
}
}

View File

@ -11,7 +11,7 @@
<!-- memos.metadata.head -->
<title>Memos</title>
</head>
<body class="text-base w-full min-h-[100svh] bg-zinc-50 dark:bg-zinc-900">
<body class="text-base w-full min-h-svh bg-zinc-50 dark:bg-zinc-900">
<div id="root" class="relative w-full min-h-full"></div>
<script type="module" src="/src/main.tsx"></script>
<!-- memos.metadata.body -->

View File

@ -15,9 +15,10 @@
"@emotion/styled": "^11.14.0",
"@github/relative-time-element": "^4.4.8",
"@matejmazur/react-katex": "^3.1.3",
"@mui/joy": "5.0.0-beta.51",
"@mui/joy": "5.0.0-beta.52",
"@radix-ui/react-popover": "^1.1.14",
"@usememos/mui": "0.1.0-20250601165716",
"@tailwindcss/vite": "^4.1.8",
"@usememos/mui": "0.1.0-20250607013227",
"clsx": "^2.1.1",
"copy-to-clipboard": "^3.3.3",
"dayjs": "^1.11.13",
@ -41,8 +42,7 @@
"react-simple-pull-to-refresh": "^1.3.3",
"react-use": "^17.6.0",
"tailwind-merge": "^2.6.0",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
"tailwindcss": "^4.1.8",
"textarea-caret": "^3.1.0",
"uuid": "^11.1.0"
},
@ -60,9 +60,7 @@
"@types/react-dom": "^18.3.7",
"@types/textarea-caret": "^3.0.4",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-legacy": "^6.1.1",
"@vitejs/plugin-react": "^4.5.0",
"autoprefixer": "^10.4.21",
"code-inspector-plugin": "^0.18.3",
"eslint": "^9.28.0",
"eslint-config-prettier": "^10.1.5",
@ -70,7 +68,6 @@
"eslint-plugin-react": "^7.37.5",
"long": "^5.3.2",
"nice-grpc-web": "^3.3.7",
"postcss": "^8.5.4",
"prettier": "^3.5.3",
"terser": "^5.40.0",
"typescript": "^5.8.3",

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
/* eslint-disable no-undef */
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@ -9,10 +9,10 @@ import { useTranslate } from "@/utils/i18n";
const getCellOpacity = (ratio: number): string => {
if (ratio === 0) return "";
if (ratio > 0.75) return "bg-primary-darker/90 text-gray-100 dark:bg-primary-lighter/80";
if (ratio > 0.5) return "bg-primary-darker/70 text-gray-100 dark:bg-primary-lighter/60";
if (ratio > 0.25) return "bg-primary/70 text-gray-100 dark:bg-primary-lighter/40";
return "bg-primary/50 text-gray-100 dark:bg-primary-lighter/20";
if (ratio > 0.75) return "bg-green-700/90 text-gray-50 dark:bg-green-400/80";
if (ratio > 0.5) return "bg-green-700/70 text-gray-100 dark:bg-green-400/60";
if (ratio > 0.25) return "bg-green-700/70 text-gray-100 dark:bg-green-400/40";
return "bg-green-700/50 text-gray-100 dark:bg-green-400/20";
};
const CalendarCell = memo(
@ -66,8 +66,6 @@ const CalendarCell = memo(
},
);
CalendarCell.displayName = "CalendarCell";
export const ActivityCalendar = memo(
observer((props: ActivityCalendarProps) => {
const t = useTranslate();

View File

@ -32,7 +32,7 @@ const AppearanceSelect: FC<Props> = (props: Props) => {
return (
<Select
className={`!min-w-[10rem] w-auto whitespace-nowrap ${className ?? ""}`}
className={`min-w-40! w-auto whitespace-nowrap ${className ?? ""}`}
value={value}
onChange={(_, appearance) => {
if (appearance) {

View File

@ -73,7 +73,7 @@ const ChangeMemberPasswordDialog: React.FC<Props> = (props: Props) => {
<XIcon className="w-5 h-auto" />
</Button>
</div>
<div className="flex flex-col justify-start items-start !w-80">
<div className="flex flex-col justify-start items-start w-80!">
<p className="text-sm mb-1">{t("auth.new-password")}</p>
<Input
className="w-full"

View File

@ -91,7 +91,7 @@ const CreateAccessTokenDialog: React.FC<Props> = (props: Props) => {
<XIcon className="w-5 h-auto" />
</Button>
</div>
<div className="flex flex-col justify-start items-start !w-80">
<div className="flex flex-col justify-start items-start w-80!">
<div className="w-full flex flex-col justify-start items-start mb-3">
<span className="mb-2">
{t("setting.access-token-section.create-dialog.description")} <span className="text-red-600">*</span>

View File

@ -252,7 +252,7 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
<div className="flex flex-col justify-start items-start w-80">
{isCreating && (
<>
<Typography className="!mb-1" level="body-md">
<Typography className="mb-1!" level="body-md">
{t("common.type")}
</Typography>
<Select className="w-full mb-4" value={type} onChange={(_, e) => setType(e ?? type)}>
@ -272,10 +272,10 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
</Option>
))}
</Select>
<Divider className="!my-2" />
<Divider className="my-2!" />
</>
)}
<Typography className="!mb-1" level="body-md">
<Typography className="mb-1!" level="body-md">
{t("common.name")}
<span className="text-red-600">*</span>
</Typography>
@ -291,7 +291,7 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
}
fullWidth
/>
<Typography className="!mb-1" level="body-md">
<Typography className="mb-1!" level="body-md">
{t("setting.sso-section.identifier-filter")}
</Typography>
<Input
@ -306,15 +306,15 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
}
fullWidth
/>
<Divider className="!my-2" />
<Divider className="my-2!" />
{type === "OAUTH2" && (
<>
{isCreating && (
<p className="border rounded-md p-2 text-sm w-full mb-2 break-all">
<p className="border border-zinc-100 dark:border-zinc-700 rounded-md p-2 text-sm w-full mb-2 break-all">
{t("setting.sso-section.redirect-url")}: {absolutifyLink("/auth/callback")}
</p>
)}
<Typography className="!mb-1" level="body-md">
<Typography className="mb-1!" level="body-md">
{t("setting.sso-section.client-id")}
<span className="text-red-600">*</span>
</Typography>
@ -325,7 +325,7 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
onChange={(e) => setPartialOAuth2Config({ clientId: e.target.value })}
fullWidth
/>
<Typography className="!mb-1" level="body-md">
<Typography className="mb-1!" level="body-md">
{t("setting.sso-section.client-secret")}
<span className="text-red-600">*</span>
</Typography>
@ -336,7 +336,7 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
onChange={(e) => setPartialOAuth2Config({ clientSecret: e.target.value })}
fullWidth
/>
<Typography className="!mb-1" level="body-md">
<Typography className="mb-1!" level="body-md">
{t("setting.sso-section.authorization-endpoint")}
<span className="text-red-600">*</span>
</Typography>
@ -347,7 +347,7 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
onChange={(e) => setPartialOAuth2Config({ authUrl: e.target.value })}
fullWidth
/>
<Typography className="!mb-1" level="body-md">
<Typography className="mb-1!" level="body-md">
{t("setting.sso-section.token-endpoint")}
<span className="text-red-600">*</span>
</Typography>
@ -358,7 +358,7 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
onChange={(e) => setPartialOAuth2Config({ tokenUrl: e.target.value })}
fullWidth
/>
<Typography className="!mb-1" level="body-md">
<Typography className="mb-1!" level="body-md">
{t("setting.sso-section.user-endpoint")}
<span className="text-red-600">*</span>
</Typography>
@ -369,7 +369,7 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
onChange={(e) => setPartialOAuth2Config({ userInfoUrl: e.target.value })}
fullWidth
/>
<Typography className="!mb-1" level="body-md">
<Typography className="mb-1!" level="body-md">
{t("setting.sso-section.scopes")}
<span className="text-red-600">*</span>
</Typography>
@ -380,8 +380,8 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
onChange={(e) => setOAuth2Scopes(e.target.value)}
fullWidth
/>
<Divider className="!my-2" />
<Typography className="!mb-1" level="body-md">
<Divider className="my-2!" />
<Typography className="mb-1!" level="body-md">
{t("setting.sso-section.identifier")}
<span className="text-red-600">*</span>
</Typography>
@ -394,7 +394,7 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
}
fullWidth
/>
<Typography className="!mb-1" level="body-md">
<Typography className="mb-1!" level="body-md">
{t("setting.sso-section.display-name")}
</Typography>
<Input
@ -406,7 +406,7 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
}
fullWidth
/>
<Typography className="!mb-1" level="body-md">
<Typography className="mb-1!" level="body-md">
{t("common.email")}
</Typography>
<Input
@ -418,7 +418,7 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
}
fullWidth
/>
<Typography className="!mb-1" level="body-md">
<Typography className="mb-1!" level="body-md">
Avatar URL
</Typography>
<Input

View File

@ -102,7 +102,7 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
<XIcon className="w-5 h-auto" />
</Button>
</div>
<div className="flex flex-col justify-start items-start !w-80">
<div className="flex flex-col justify-start items-start w-80!">
<div className="w-full flex flex-col justify-start items-start mb-3">
<span className="mb-2">
{t("setting.webhook-section.create-dialog.title")} <span className="text-red-600">*</span>

View File

@ -54,7 +54,7 @@ const BaseDialog = observer((props: Props) => {
return (
<div
className={cn(
"fixed top-0 left-0 flex flex-col justify-start items-center w-full h-full pt-16 pb-8 px-4 z-1000 overflow-x-hidden overflow-y-scroll bg-transparent transition-all hide-scrollbar bg-black bg-opacity-60",
"fixed top-0 left-0 flex flex-col justify-start items-center w-full h-full pt-16 pb-8 px-4 z-1000 overflow-x-hidden overflow-y-scroll transition-all hide-scrollbar bg-black/60",
className,
)}
onMouseDown={handleSpaceClicked}

View File

@ -23,7 +23,7 @@ const HomeSidebarDrawer = () => {
return (
<>
<Button variant="plain" className="!bg-transparent px-2" onClick={toggleDrawer(true)}>
<Button variant="plain" className="bg-transparent! px-2" onClick={toggleDrawer(true)}>
<MenuIcon className="w-6 h-auto dark:text-gray-400" />
</Button>
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>

View File

@ -58,7 +58,7 @@ const TagsSection = observer((props: Props) => {
</PopoverTrigger>
<PopoverContent align="end" alignOffset={-12}>
<div className="w-auto flex flex-row justify-between items-center gap-2">
<span className="text-sm shrink-0">{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)} />
</div>
</PopoverContent>

View File

@ -18,7 +18,7 @@ const LocaleSelect: FC<Props> = (props: Props) => {
return (
<Select
className={`!min-w-[10rem] w-auto whitespace-nowrap ${className ?? ""}`}
className={`min-w-40! w-auto whitespace-nowrap ${className ?? ""}`}
startDecorator={<GlobeIcon className="w-4 h-auto" />}
value={value}
onChange={(_, value) => handleSelectChange(value as Locale)}

View File

@ -25,7 +25,7 @@ const CodeBlock: React.FC<Props> = ({ language, content }: Props) => {
if (formatedLanguage === SpecialLanguage.HTML) {
return (
<div
className="w-full overflow-auto !my-2"
className="w-full overflow-auto my-2!"
dangerouslySetInnerHTML={{
__html: content,
}}

View File

@ -6,7 +6,7 @@ interface Props extends BaseProps {
}
const HorizontalRule: React.FC<Props> = () => {
return <Divider className="!my-3" />;
return <Divider className="my-3!" />;
};
export default HorizontalRule;

View File

@ -87,7 +87,7 @@ const MemoContent = observer((props: Props) => {
<div
ref={memoContentContainerRef}
className={cn(
"relative w-full max-w-full word-break text-base leading-snug space-y-2 whitespace-pre-wrap",
"relative w-full max-w-full break-words text-base leading-snug space-y-2 whitespace-pre-wrap",
showCompactMode == "ALL" && "line-clamp-6 max-h-60",
contentClassName,
)}
@ -104,7 +104,7 @@ const MemoContent = observer((props: Props) => {
return <Renderer key={`${node.type}-${index}`} index={String(index)} node={node} />;
})}
{showCompactMode == "ALL" && (
<div className="absolute bottom-0 left-0 w-full h-12 bg-gradient-to-b from-transparent dark:to-zinc-800 to-white pointer-events-none"></div>
<div className="absolute bottom-0 left-0 w-full h-12 bg-linear-to-b from-transparent dark:to-zinc-800 to-white pointer-events-none"></div>
)}
</div>
{showCompactMode != undefined && (

View File

@ -28,7 +28,7 @@ const MemoDetailSidebarDrawer = ({ memo, parentPage }: Props) => {
return (
<>
<Button variant="plain" className="!bg-transparent px-2" onClick={toggleDrawer(true)}>
<Button variant="plain" className="bg-transparent! px-2" onClick={toggleDrawer(true)}>
<GanttChartIcon className="w-5 h-auto dark:text-gray-400" />
</Button>
<Drawer anchor="right" size="sm" open={open} onClose={toggleDrawer(false)}>

View File

@ -24,7 +24,7 @@ const MemoDisplaySettingMenu = observer(({ className }: Props) => {
<PopoverContent align="end" alignOffset={-12} sideOffset={14}>
<div className="flex flex-col gap-2">
<div className="w-full flex flex-row justify-between items-center">
<span className="text-sm shrink-0 mr-3">{t("memo.direction")}</span>
<span className="text-sm shrink-0 mr-3 dark:text-zinc-400">{t("memo.direction")}</span>
<Select
value={viewStore.state.orderByTimeAsc}
onChange={(_, value) =>
@ -38,7 +38,7 @@ const MemoDisplaySettingMenu = observer(({ className }: Props) => {
</Select>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span className="text-sm shrink-0 mr-3">{t("common.layout")}</span>
<span className="text-sm shrink-0 mr-3 dark:text-zinc-400">{t("common.layout")}</span>
<Select
value={viewStore.state.layout}
onChange={(_, value) =>

View File

@ -162,7 +162,7 @@ const AddMemoRelationPopover = (props: Props) => {
)}
renderTags={(memos) =>
memos.map((memo) => (
<Chip key={memo.name} className="!max-w-full !rounded" variant="outlined" color="neutral">
<Chip key={memo.name} className="max-w-full! rounded!" variant="outlined" color="neutral">
<div className="w-full flex flex-col justify-start items-start">
<p className="text-xs text-gray-400 select-none">{memo.displayTime?.toLocaleString()}</p>
<span className="w-full text-sm leading-5 truncate">{memo.content}</span>

View File

@ -105,7 +105,7 @@ const LocationSelector = (props: Props) => {
</Button>
</PopoverTrigger>
<PopoverContent align="center">
<div className="min-w-80 sm:w-128 flex flex-col justify-start items-start">
<div className="min-w-80 sm:w-lg flex flex-col justify-start items-start">
<LeafletMap key={JSON.stringify(state.initilized)} latlng={state.position} onChange={onPositionChanged} />
<div className="mt-2 w-full flex flex-row justify-between items-center gap-2">
<div className="flex flex-row items-center justify-start gap-2">

View File

@ -50,7 +50,7 @@ const TagSelector = observer((props: Props) => {
<Menu className="relative" component="div" size="sm" placement="bottom-start">
<div ref={containerRef}>
{tags.length > 0 ? (
<div className="flex flex-row justify-start items-start flex-wrap px-3 py-1 max-w-[12rem] h-auto max-h-48 overflow-y-auto gap-x-2 gap-y-1">
<div className="flex flex-row justify-start items-start flex-wrap px-3 py-1 max-w-48 h-auto max-h-48 overflow-y-auto gap-x-2 gap-y-1">
{tags.map((tag) => {
return (
<div

View File

@ -49,19 +49,20 @@ const VisibilitySelector = (props: Props) => {
type="button"
>
<VisibilityIcon className="w-3 h-3" visibility={value} />
<span>{currentOption?.label}</span>
<ChevronDownIcon className="w-3 h-3 opacity-60" />
<span className="dark:text-zinc-300">{currentOption?.label}</span>
<ChevronDownIcon className="w-3 h-3 opacity-60 dark:text-zinc-300" />
</button>
</PopoverTrigger>
<PopoverContent className="!p-1" align="end" sideOffset={2} alignOffset={-4}>
<PopoverContent className="p-1!" align="end" sideOffset={2} alignOffset={-4}>
<div className="flex flex-col gap-0.5">
{visibilityOptions.map((option) => (
<button
key={option.value}
onClick={() => handleSelect(option.value)}
className={`flex items-center gap-1 px-1 py-1 text-xs text-left dark:text-zinc-300 hover:bg-gray-100 dark:hover:bg-zinc-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1 rounded transition-colors ${
option.value === value ? "bg-gray-50 dark:bg-zinc-800" : ""
}`}
className={cn(
`flex items-center gap-1 px-1 py-1 text-xs text-left dark:text-zinc-300 hover:bg-gray-100 dark:hover:bg-zinc-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1 rounded transition-colors`,
option.value === value ? "bg-gray-50 dark:bg-zinc-800" : "",
)}
>
<VisibilityIcon className="w-3 h-3" visibility={option.value} />
<span>{option.label}</span>

View File

@ -108,7 +108,7 @@ const TagSuggestions = observer(({ editorRef, editorActions }: Props) => {
if (!isVisibleRef.current || !position) return null;
return (
<div
className="z-20 p-1 mt-1 -ml-2 absolute max-w-[12rem] gap-px rounded font-mono flex flex-col justify-start items-start overflow-auto shadow bg-zinc-100 dark:bg-zinc-700"
className="z-20 p-1 mt-1 -ml-2 absolute max-w-48 gap-px rounded font-mono flex flex-col justify-start items-start overflow-auto shadow bg-zinc-100 dark:bg-zinc-700"
style={{ left: position.left, top: position.top + position.height }}
>
{suggestionsRef.current.map((tag, i) => (

View File

@ -217,7 +217,7 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef<
className={cn("flex flex-col justify-start items-start relative w-full h-auto max-h-[50vh] bg-inherit dark:text-gray-300", className)}
>
<textarea
className="w-full h-full my-1 text-base resize-none overflow-x-hidden overflow-y-auto bg-transparent outline-none whitespace-pre-wrap word-break"
className="w-full h-full my-1 text-base resize-none overflow-x-hidden overflow-y-auto bg-transparent outline-none whitespace-pre-wrap break-words"
rows={2}
placeholder={placeholder}
ref={editorRef}

View File

@ -41,8 +41,8 @@ const ResourceListView = (props: Props) => {
className="max-w-full w-auto flex flex-row justify-start items-center flex-nowrap gap-x-1 bg-zinc-100 dark:bg-zinc-900 px-2 py-1 rounded hover:shadow-sm text-gray-500 dark:text-gray-400"
>
<SortableItem id={resource.name} className="flex flex-row justify-start items-center gap-x-1">
<ResourceIcon resource={resource} className="!w-4 !h-4 !opacity-100" />
<span className="text-sm max-w-[8rem] truncate">{resource.filename}</span>
<ResourceIcon resource={resource} className="w-4! h-4! opacity-100!" />
<span className="text-sm max-w-32 truncate">{resource.filename}</span>
</SortableItem>
<button className="shrink-0" onClick={() => handleDeleteResource(resource.name)}>
<XIcon className="w-4 h-auto cursor-pointer opacity-60 hover:opacity-100" />

View File

@ -24,7 +24,7 @@ const MemoLocationView: React.FC<Props> = (props: Props) => {
</p>
</PopoverTrigger>
<PopoverContent align="start">
<div className="min-w-80 sm:w-128 flex flex-col justify-start items-start">
<div className="min-w-80 sm:w-lg flex flex-col justify-start items-start">
<LeafletMap latlng={new LatLng(location.latitude, location.longitude)} readonly={true} />
</div>
</PopoverContent>

View File

@ -21,7 +21,7 @@ const MemoResource: React.FC<Props> = (props: Props) => {
<audio src={resourceUrl} controls></audio>
) : (
<>
<ResourceIcon className="!w-4 !h-4 mr-1" resource={resource} />
<ResourceIcon className="w-4! h-4! mr-1" resource={resource} />
<span className="text-sm max-w-[256px] truncate cursor-pointer" onClick={handlePreviewBtnClick}>
{resource.filename}
</span>

View File

@ -34,7 +34,10 @@ const MemoResourceListView = ({ resources = [] }: { resources: Resource[] }) =>
if (type === "image/*") {
return (
<img
className={cn("cursor-pointer h-full w-auto rounded-lg border dark:border-zinc-800 object-contain hover:opacity-80", className)}
className={cn(
"cursor-pointer h-full w-auto rounded-lg border border-zinc-200 dark:border-zinc-800 object-contain hover:opacity-80",
className,
)}
src={resource.externalLink ? resourceUrl : resourceUrl + "?thumbnail=true"}
onClick={() => handleImageClick(resourceUrl)}
decoding="async"

View File

@ -80,7 +80,7 @@ const Navigation = observer((props: Props) => {
cn(
"px-2 py-2 rounded-2xl border flex flex-row items-center text-lg text-gray-800 dark:text-gray-400 hover:bg-white hover:border-gray-200 dark:hover:border-zinc-700 dark:hover:bg-zinc-800",
collapsed ? "" : "w-full px-4",
isActive ? "bg-white drop-shadow-sm dark:bg-zinc-800 border-gray-200 dark:border-zinc-700" : "border-transparent",
isActive ? "bg-white drop-shadow-sm dark:bg-zinc-900 border-gray-200 dark:border-zinc-700" : "border-transparent",
)
}
key={navLink.id}

View File

@ -191,7 +191,7 @@ const PreviewImageDialog: React.FC<Props> = ({ destroy, imgUrls, initialIndex }:
export default function showPreviewImageDialog(imgUrls: string[] | string, initialIndex?: number): void {
generateDialog(
{
className: "preview-image-dialog p-0 z-[1001]",
className: "preview-image-dialog p-0 z-1001",
dialogName: "preview-image-dialog",
},
PreviewImageDialog,

View File

@ -73,7 +73,7 @@ const ResourceIcon = (props: Props) => {
};
return (
<div onClick={previewResource} className={cn(className, "max-w-[4rem] opacity-50")}>
<div onClick={previewResource} className={cn(className, "max-w-16 opacity-50")}>
{getResourceIcon()}
</div>
);

View File

@ -2,6 +2,7 @@ import { SearchIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useState } from "react";
import { memoFilterStore } from "@/store/v2";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
import MemoDisplaySettingMenu from "./MemoDisplaySettingMenu";
@ -31,15 +32,17 @@ const SearchBar = observer(() => {
return (
<div className="relative w-full h-auto flex flex-row justify-start items-center">
<SearchIcon className="absolute left-2 w-4 h-auto opacity-40" />
<SearchIcon className="absolute left-2 w-4 h-auto opacity-40 dark:text-zinc-300" />
<input
className="w-full text-gray-500 leading-6 dark:text-gray-400 placeholder:opacity-80 bg-zinc-50 dark:bg-zinc-900 border dark:border-zinc-800 text-sm rounded-xl p-1 pl-8 outline-none"
className={cn(
"w-full text-gray-500 leading-6 dark:text-zinc-300 placeholder:opacity-80 bg-zinc-50 dark:bg-zinc-900 border dark:border-zinc-800 border-zinc-200 text-sm rounded-xl p-1 pl-8 outline-0",
)}
placeholder={t("memo.search-placeholder")}
value={queryText}
onChange={onTextChange}
onKeyDown={onKeyDown}
/>
<MemoDisplaySettingMenu className="absolute right-2 top-2" />
<MemoDisplaySettingMenu className="absolute right-2 top-2 dark:text-zinc-300" />
</div>
);
});

View File

@ -123,7 +123,7 @@ const MemoRelatedSettings = observer(() => {
{memoRelatedSetting.reactions.map((reactionType) => {
return (
<Chip
className="!h-8"
className="h-8!"
key={reactionType}
variant="outlined"
size="lg"
@ -138,7 +138,7 @@ const MemoRelatedSettings = observer(() => {
);
})}
<Input
className="w-32 !rounded-full !pl-1"
className="w-32 rounded-full! pl-1!"
placeholder={t("common.input")}
value={editingReaction}
onChange={(event) => setEditingReaction(event.target.value.trim())}
@ -163,7 +163,7 @@ const MemoRelatedSettings = observer(() => {
{memoRelatedSetting.nsfwTags.map((nsfwTag) => {
return (
<Chip
className="!h-8"
className="h-8!"
key={nsfwTag}
variant="outlined"
size="lg"
@ -178,7 +178,7 @@ const MemoRelatedSettings = observer(() => {
);
})}
<Input
className="w-32 !rounded-full !pl-1"
className="w-32 rounded-full! pl-1!"
placeholder={t("common.input")}
value={editingNsfwTag}
onChange={(event) => setEditingNsfwTag(event.target.value.trim())}

View File

@ -45,7 +45,7 @@ const PreferencesSection = observer(() => {
<div className="w-full flex flex-row justify-between items-center">
<span className="truncate">{t("setting.preference-section.default-memo-visibility")}</span>
<Select
className="!min-w-fit"
className="min-w-fit!"
value={setting.memoVisibility}
startDecorator={<VisibilityIcon visibility={convertVisibilityFromString(setting.memoVisibility)} />}
onChange={(_, visibility) => {
@ -64,7 +64,7 @@ const PreferencesSection = observer(() => {
</Select>
</div>
<Divider className="!my-3" />
<Divider className="my-3!" />
<WebhookSection />
</div>

View File

@ -198,7 +198,7 @@ const StorageSection = observer(() => {
{t("common.save")}
</Button>
</div>
<Divider className="!my-2" />
<Divider className="my-2!" />
<div className="w-full mt-4">
<p className="text-sm">{t("common.learn-more")}:</p>
<List component="ul" marker="disc" size="sm">

View File

@ -145,7 +145,7 @@ const WorkspaceSection = observer(() => {
<div className="w-full flex flex-row justify-between items-center">
<span className="truncate">{t("setting.workspace-section.week-start-day")}</span>
<Select
className="!min-w-fit"
className="min-w-fit!"
value={workspaceGeneralSetting.weekStartDayOffset}
onChange={(_, weekStartDayOffset) => {
updatePartialSetting({ weekStartDayOffset: weekStartDayOffset || 0 });

View File

@ -6,7 +6,7 @@ export const StatCard = ({ icon, label, count, onClick, tooltip, className }: St
const content = (
<div
className={cn(
"w-auto border dark:border-zinc-800 pl-1.5 pr-2 py-0.5 rounded-md flex justify-between items-center",
"w-auto border border-zinc-200 dark:border-zinc-800 pl-1.5 pr-2 py-0.5 rounded-md flex justify-between items-center",
"cursor-pointer hover:bg-gray-50 dark:hover:bg-zinc-800/50 transition-colors",
className,
)}

View File

@ -112,7 +112,7 @@ const TagItemContainer = observer((props: TagItemContainerProps) => {
<div className="relative flex flex-row justify-between items-center w-full leading-6 py-0 mt-px rounded-lg text-sm select-none shrink-0">
<div
className={`flex flex-row justify-start items-center truncate shrink leading-5 mr-1 text-gray-600 dark:text-gray-400 ${
isActive && "!text-blue-600"
isActive && "text-blue-600!"
}`}
>
<div className="shrink-0">
@ -136,7 +136,7 @@ const TagItemContainer = observer((props: TagItemContainerProps) => {
{hasSubTags ? (
<div
className={`w-[calc(100%-0.5rem)] flex flex-col justify-start items-start h-auto ml-2 pl-2 border-l-2 border-l-gray-200 dark:border-l-zinc-800 ${
!showSubTags && "!hidden"
!showSubTags && "hidden!"
}`}
>
{tag.subTags.map((st, idx) => (

View File

@ -146,11 +146,11 @@ const UpdateAccountDialog = ({ destroy }: Props) => {
<XIcon className="w-5 h-auto" />
</Button>
</div>
<div className="flex flex-col justify-start items-start !w-64 space-y-2">
<div className="flex flex-col justify-start items-start w-64! space-y-2">
<div className="w-full flex flex-row justify-start items-center">
<span className="text-sm mr-2">{t("common.avatar")}</span>
<label className="relative cursor-pointer hover:opacity-80">
<UserAvatar className="!w-10 !h-10" avatarUrl={state.avatarUrl} />
<UserAvatar className="w-10! h-10!" avatarUrl={state.avatarUrl} />
<input type="file" accept="image/*" className="absolute invisible w-full h-full inset-0" onChange={handleAvatarChanged} />
</label>
{state.avatarUrl && (

View File

@ -111,9 +111,9 @@ const UpdateCustomizedProfileDialog = ({ destroy }: Props) => {
<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} />
<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>
<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="flex flex-row justify-start items-center">
<Button variant="outlined" onClick={handleRestoreButtonClick}>

View File

@ -16,7 +16,7 @@ const PopoverContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
"z-[2000] w-auto rounded-md bg-white dark:bg-zinc-900 border dark:border-zinc-800 p-2 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",
"z-2000 w-auto rounded-md bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 p-2 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",
className,
)}
{...props}

View File

@ -1,59 +0,0 @@
@import url("highlight.js/styles/github.css") (prefers-color-scheme: light);
@import url("highlight.js/styles/github-dark.css") (prefers-color-scheme: dark);
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
.hide-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
/* Chrome, Safari and Opera */
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
.word-break {
overflow-wrap: anywhere;
word-break: normal;
}
/* Animation utilities for smooth transitions */
.animate-fade-in {
animation: fadeIn 0.3s ease-in-out;
}
.animate-scale-in {
animation: scaleIn 0.2s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes scaleIn {
from {
transform: scale(0.95);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
}
html.dark {
color-scheme: dark;
}
html.light {
color-scheme: light;
}

View File

@ -18,7 +18,7 @@ const HomeLayout = observer(() => {
{md && (
<div
className={cn(
"fixed top-0 left-16 shrink-0 h-[100svh] transition-all",
"fixed top-0 left-16 shrink-0 h-svh transition-all",
"border-r border-gray-200 dark:border-zinc-800",
lg ? "w-72" : "w-56",
)}

View File

@ -48,14 +48,14 @@ const RootLayout = observer(() => {
{sm && (
<div
className={cn(
"group flex flex-col justify-start items-start fixed top-0 left-0 select-none border-r dark:border-zinc-800 h-full bg-zinc-100 dark:bg-zinc-800 dark:bg-opacity-40",
"group flex flex-col justify-start items-start fixed top-0 left-0 select-none border-r border-zinc-200 dark:border-zinc-800 h-full bg-zinc-100 dark:bg-zinc-800 dark:bg-opacity-40",
"w-16 px-2",
)}
>
<Navigation collapsed={true} />
</div>
)}
<main className="w-full h-auto flex-grow shrink flex flex-col justify-start items-center">
<main className="w-full h-auto grow shrink flex flex-col justify-start items-center">
<Suspense fallback={<Loading />}>
<Outlet />
</Suspense>

View File

@ -4,11 +4,11 @@ import { observer } from "mobx-react-lite";
import { createRoot } from "react-dom/client";
import { Toaster } from "react-hot-toast";
import { RouterProvider } from "react-router-dom";
import "./css/tailwind.css";
import "./i18n";
import router from "./router";
import { initialUserStore } from "./store/v2/user";
import { initialWorkspaceStore } from "./store/v2/workspace";
import "./style.css";
import theme from "./theme";
import "@usememos/mui/dist/index.css";
import "leaflet/dist/leaflet.css";

View File

@ -7,7 +7,7 @@ const AdminSignIn = observer(() => {
const workspaceGeneralSetting = workspaceStore.state.generalSetting;
return (
<div className="py-4 sm:py-8 w-80 max-w-full min-h-[100svh] mx-auto flex flex-col justify-start items-center">
<div className="py-4 sm:py-8 w-80 max-w-full min-h-svh mx-auto flex flex-col justify-start items-center">
<div className="w-full py-4 grow flex flex-col justify-center items-center">
<div className="w-full flex flex-row justify-center items-center mb-6">
<img className="h-14 w-auto rounded-full shadow" src={workspaceGeneralSetting.customProfile?.logoUrl || "/logo.webp"} alt="" />

View File

@ -118,7 +118,7 @@ const MemoDetail = observer(() => {
<h2 id="comments" className="sr-only">
{t("memo.comment.self")}
</h2>
<div className="relative mx-auto flex-grow w-full min-h-full flex flex-col justify-start items-start gap-y-1">
<div className="relative mx-auto grow w-full min-h-full flex flex-col justify-start items-start gap-y-1">
{comments.length === 0 ? (
showCreateCommentButton && (
<div className="w-full flex flex-row justify-center items-center py-6">

View File

@ -2,7 +2,7 @@ import MobileHeader from "@/components/MobileHeader";
const NotFound = () => {
return (
<section className="@container w-full max-w-5xl min-h-[100svh] flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
<section className="@container w-full max-w-5xl min-h-svh flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
<MobileHeader />
<div className="w-full px-4 grow flex flex-col justify-center items-center sm:px-6">
<p className="font-medium">{"The page you are looking for can't be found."}</p>

View File

@ -2,7 +2,7 @@ import MobileHeader from "@/components/MobileHeader";
const PermissionDenied = () => {
return (
<section className="@container w-full max-w-5xl min-h-[100svh] flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
<section className="@container w-full max-w-5xl min-h-svh flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
<MobileHeader />
<div className="w-full px-4 grow flex flex-col justify-center items-center sm:px-6">
<p className="font-medium">Permission denied</p>

View File

@ -76,7 +76,7 @@ const Resources = observer(() => {
</p>
<div>
<Input
className="max-w-[8rem]"
className="max-w-32"
placeholder={t("common.search")}
startDecorator={<SearchIcon className="w-4 h-auto" />}
value={state.searchQuery}

View File

@ -56,7 +56,7 @@ const SignIn = observer(() => {
};
return (
<div className="py-4 sm:py-8 w-80 max-w-full min-h-[100svh] mx-auto flex flex-col justify-start items-center">
<div className="py-4 sm:py-8 w-80 max-w-full min-h-svh mx-auto flex flex-col justify-start items-center">
<div className="w-full py-4 grow flex flex-col justify-center items-center">
<div className="w-full flex flex-row justify-center items-center mb-6">
<img className="h-14 w-auto rounded-full shadow" src={workspaceGeneralSetting.customProfile?.logoUrl || "/logo.webp"} alt="" />
@ -79,7 +79,7 @@ const SignIn = observer(() => {
)}
{identityProviderList.length > 0 && (
<>
{!workspaceGeneralSetting.disallowPasswordAuth && <Divider className="!my-4">{t("common.or")}</Divider>}
{!workspaceGeneralSetting.disallowPasswordAuth && <Divider className="my-4!">{t("common.or")}</Divider>}
<div className="w-full flex flex-col space-y-2">
{identityProviderList.map((identityProvider) => (
<Button

View File

@ -58,7 +58,7 @@ const SignUp = observer(() => {
};
return (
<div className="py-4 sm:py-8 w-80 max-w-full min-h-[100svh] mx-auto flex flex-col justify-start items-center">
<div className="py-4 sm:py-8 w-80 max-w-full min-h-svh mx-auto flex flex-col justify-start items-center">
<div className="w-full py-4 grow flex flex-col justify-center items-center">
<div className="w-full flex flex-row justify-center items-center mb-6">
<img className="h-14 w-auto rounded-full shadow" src={workspaceGeneralSetting.customProfile?.logoUrl || "/logo.webp"} alt="" />

View File

@ -87,7 +87,7 @@ const UserProfile = observer(() => {
</Button>
</div>
<div className="w-full flex flex-col justify-start items-start pt-4 pb-8 px-3">
<UserAvatar className="!w-16 !h-16 drop-shadow rounded-3xl" avatarUrl={user?.avatarUrl} />
<UserAvatar className="w-16! h-16! drop-shadow rounded-3xl" avatarUrl={user?.avatarUrl} />
<div className="mt-2 w-auto max-w-[calc(100%-6rem)] flex flex-col justify-center items-start">
<p className="w-full text-3xl text-black leading-tight font-medium opacity-80 dark:text-gray-200 truncate">
{user.nickname || user.username}

7
web/src/style.css Normal file
View File

@ -0,0 +1,7 @@
@import "tailwindcss";
@theme {
--default-transition-duration: 150ms;
}
@variant dark (&:is(.dark *));

View File

@ -1,49 +0,0 @@
/* eslint-disable no-undef */
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
prefix: "",
theme: {
extend: {
colors: {
primary: {
DEFAULT: "#0d9488", // Teal 600
dark: "#0f766e", // Teal 700
darker: "#0d5a56", // Teal 800
lighter: "#14b8a6", // Teal 500
},
success: {
DEFAULT: "#16a34a", // Green 600
dark: "#047857", // Green 700
darker: "#03664a", // Green 800
},
danger: {
DEFAULT: "#dc2626", // Red 600
dark: "#b91c1c", // Red 700
darker: "#991b1b", // Red 800
},
warning: {
DEFAULT: "#ca8a04", // Yellow 600
dark: "#b45309", // Yellow 700
darker: "#92400e", // Yellow 800
},
},
spacing: {
128: "32rem",
},
zIndex: {
1: "1",
2: "2",
20: "20",
100: "100",
1000: "1000",
2000: "2000",
},
gridTemplateRows: {
7: "repeat(7, minmax(0, 1fr))",
},
},
},
plugins: [require("tailwindcss-animate")],
};

View File

@ -1,8 +1,8 @@
import legacy from "@vitejs/plugin-legacy";
import react from "@vitejs/plugin-react";
import { codeInspectorPlugin } from "code-inspector-plugin";
import { resolve } from "path";
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";
let devProxyServer = "http://localhost:8081";
if (process.env.DEV_PROXY_SERVER && process.env.DEV_PROXY_SERVER.length > 0) {
@ -14,9 +14,7 @@ if (process.env.DEV_PROXY_SERVER && process.env.DEV_PROXY_SERVER.length > 0) {
export default defineConfig({
plugins: [
react(),
legacy({
targets: ["defaults", "not IE 11"],
}),
tailwindcss(),
codeInspectorPlugin({
bundler: "vite",
}),