diff --git a/apps/admin/app/root.tsx b/apps/admin/app/root.tsx index cbae375398..6ac6531a37 100644 --- a/apps/admin/app/root.tsx +++ b/apps/admin/app/root.tsx @@ -10,6 +10,11 @@ import { LogoSpinner } from "@/components/common/logo-spinner"; import globalStyles from "@/styles/globals.css?url"; import type { Route } from "./+types/root"; import { AppProviders } from "./providers"; +// fonts +import "@fontsource-variable/inter"; +import interVariableWoff2 from "@fontsource-variable/inter/files/inter-latin-wght-normal.woff2?url"; +import "@fontsource/material-symbols-rounded"; +import "@fontsource/ibm-plex-mono"; const APP_TITLE = "Plane | Simple, extensible, open-source project management tool."; const APP_DESCRIPTION = @@ -22,6 +27,13 @@ export const links: LinksFunction = () => [ { rel: "shortcut icon", href: faviconIco }, { rel: "manifest", href: `/site.webmanifest.json` }, { rel: "stylesheet", href: globalStyles }, + { + rel: "preload", + href: interVariableWoff2, + as: "font", + type: "font/woff2", + crossOrigin: "anonymous", + }, ]; export function Layout({ children }: { children: ReactNode }) { diff --git a/apps/admin/package.json b/apps/admin/package.json index 6f6f768824..25f0076f63 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -19,6 +19,9 @@ }, "dependencies": { "@bprogress/core": "catalog:", + "@fontsource-variable/inter": "5.2.8", + "@fontsource/ibm-plex-mono": "5.2.7", + "@fontsource/material-symbols-rounded": "5.2.30", "@headlessui/react": "^1.7.19", "@plane/constants": "workspace:*", "@plane/hooks": "workspace:*", diff --git a/apps/admin/styles/globals.css b/apps/admin/styles/globals.css index be8c75a0a8..7f7f2483a5 100644 --- a/apps/admin/styles/globals.css +++ b/apps/admin/styles/globals.css @@ -1,5 +1,4 @@ @import "@plane/tailwind-config/index.css"; -@import "@plane/propel/styles/fonts"; .shadow-custom { box-shadow: 2px 2px 8px 2px rgba(234, 231, 250, 0.3); /* Convert #EAE7FA4D to rgba */ diff --git a/apps/space/app/root.tsx b/apps/space/app/root.tsx index ad5e8fff22..ca0651042e 100644 --- a/apps/space/app/root.tsx +++ b/apps/space/app/root.tsx @@ -13,6 +13,11 @@ import type { Route } from "./+types/root"; // local imports import ErrorPage from "./error"; import { AppProviders } from "./providers"; +// fonts +import "@fontsource-variable/inter"; +import interVariableWoff2 from "@fontsource-variable/inter/files/inter-latin-wght-normal.woff2?url"; +import "@fontsource/material-symbols-rounded"; +import "@fontsource/ibm-plex-mono"; const APP_TITLE = "Plane Publish | Make your Plane boards public with one-click"; const APP_DESCRIPTION = "Plane Publish is a customer feedback management tool built on top of plane.so"; @@ -24,6 +29,13 @@ export const links: Route.LinksFunction = () => [ { rel: "shortcut icon", href: faviconIco }, { rel: "manifest", href: siteWebmanifest }, { rel: "stylesheet", href: globalStyles }, + { + rel: "preload", + href: interVariableWoff2, + as: "font", + type: "font/woff2", + crossOrigin: "anonymous", + }, ]; export const headers: Route.HeadersFunction = () => ({ diff --git a/apps/space/package.json b/apps/space/package.json index 9a8e09b27d..d0dd16d0fe 100644 --- a/apps/space/package.json +++ b/apps/space/package.json @@ -20,6 +20,9 @@ "@atlaskit/pragmatic-drag-and-drop": "catalog:", "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "catalog:", "@bprogress/core": "catalog:", + "@fontsource-variable/inter": "5.2.8", + "@fontsource/ibm-plex-mono": "5.2.7", + "@fontsource/material-symbols-rounded": "5.2.30", "@headlessui/react": "^1.7.19", "@plane/constants": "workspace:*", "@plane/editor": "workspace:*", diff --git a/apps/space/styles/globals.css b/apps/space/styles/globals.css index e77a5c236c..ba7f052d19 100644 --- a/apps/space/styles/globals.css +++ b/apps/space/styles/globals.css @@ -1,6 +1,5 @@ @import "@plane/tailwind-config/index.css"; @import "@plane/editor/styles"; -@import "@plane/propel/styles/fonts"; @import "@plane/propel/styles/react-day-picker.css"; @plugin "@tailwindcss/typography"; diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 5daaf7d598..e36af34bdd 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,8 +1,6 @@ import Script from "next/script"; // styles import "@/styles/globals.css"; -import "@/styles/power-k.css"; -import "@/styles/emoji.css"; // plane imports import { SITE_DESCRIPTION, SITE_NAME } from "@plane/constants"; diff --git a/apps/web/app/root.tsx b/apps/web/app/root.tsx index 685505afb6..ea4acd7419 100644 --- a/apps/web/app/root.tsx +++ b/apps/web/app/root.tsx @@ -21,6 +21,11 @@ import type { Route } from "./+types/root"; // local import { CustomErrorComponent } from "./error"; import { AppProvider } from "./provider"; +// fonts +import "@fontsource-variable/inter"; +import interVariableWoff2 from "@fontsource-variable/inter/files/inter-latin-wght-normal.woff2?url"; +import "@fontsource/material-symbols-rounded"; +import "@fontsource/ibm-plex-mono"; const APP_TITLE = "Plane | Simple, extensible, open-source project management tool."; @@ -34,6 +39,13 @@ export const links: LinksFunction = () => [ { rel: "apple-touch-icon", sizes: "512x512", href: icon512 }, { rel: "manifest", href: "/manifest.json" }, { rel: "stylesheet", href: globalStyles }, + { + rel: "preload", + href: interVariableWoff2, + as: "font", + type: "font/woff2", + crossOrigin: "anonymous", + }, ]; export function Layout({ children }: { children: ReactNode }) { diff --git a/apps/web/core/components/comments/card/root.tsx b/apps/web/core/components/comments/card/root.tsx index 4f1f0342cf..2f86df1350 100644 --- a/apps/web/core/components/comments/card/root.tsx +++ b/apps/web/core/components/comments/card/root.tsx @@ -1,22 +1,23 @@ -import { useRef, useState } from "react"; +import { useCallback, useRef, useState } from "react"; import { observer } from "mobx-react"; // plane imports +import { EmojiReactionButton, EmojiReactionPicker } from "@plane/propel/emoji-reaction"; import type { EditorRefApi } from "@plane/editor"; import type { TIssueComment, TCommentsOperations } from "@plane/types"; // plane web imports -import { CommentBlock, CommentCardDisplay } from "@/plane-web/components/comments"; +import { CommentBlock } from "@/plane-web/components/comments"; // local imports import { CommentQuickActions } from "../quick-actions"; +import { CommentCardDisplay } from "./display"; +import { CommentCardEditForm } from "./edit-form"; type TCommentCard = { workspaceSlug: string; - entityId: string; comment: TIssueComment | undefined; activityOperations: TCommentsOperations; ends: "top" | "bottom" | undefined; showAccessSpecifier: boolean; showCopyLinkOption: boolean; - enableReplies: boolean; disabled?: boolean; projectId?: string; }; @@ -24,50 +25,85 @@ type TCommentCard = { export const CommentCard = observer(function CommentCard(props: TCommentCard) { const { workspaceSlug, - entityId, comment, activityOperations, ends, showAccessSpecifier, showCopyLinkOption, - enableReplies = false, disabled = false, projectId, } = props; // states const [isEditing, setIsEditing] = useState(false); + const [isPickerOpen, setIsPickerOpen] = useState(false); // refs const readOnlyEditorRef = useRef(null); // derived values const workspaceId = comment?.workspace; + const userReactions = comment?.id ? activityOperations.userReactions(comment.id) : undefined; + + const handleEmojiSelect = useCallback( + (emoji: string) => { + if (!userReactions || !comment?.id) return; + // emoji is already in decimal string format from EmojiReactionPicker + void activityOperations.react(comment.id, emoji, userReactions); + }, + [activityOperations, comment?.id, userReactions] + ); + if (!comment || !workspaceId) return null; return ( - - ( - setIsEditing(true)} - showAccessSpecifier={showAccessSpecifier} - showCopyLinkOption={showCopyLinkOption} - /> - )} - enableReplies={enableReplies} - /> + + setIsPickerOpen(true)} />} + placement="bottom-start" + /> + + setIsEditing(true)} + showAccessSpecifier={showAccessSpecifier} + showCopyLinkOption={showCopyLinkOption} + /> + + ) + } + ends={ends} + > + {isEditing ? ( + + ) : ( + + )} ); }); diff --git a/apps/web/core/components/comments/quick-actions.tsx b/apps/web/core/components/comments/quick-actions.tsx index 27d249d2ba..60af778a8f 100644 --- a/apps/web/core/components/comments/quick-actions.tsx +++ b/apps/web/core/components/comments/quick-actions.tsx @@ -1,14 +1,15 @@ +import { useMemo } from "react"; import { observer } from "mobx-react"; -import { MoreHorizontal } from "lucide-react"; +import { Globe2, Link, Lock, Pencil, Trash2, MoreHorizontal } from "lucide-react"; // plane imports import { EIssueCommentAccessSpecifier } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { IconButton } from "@plane/propel/icon-button"; import type { TIssueComment, TCommentsOperations } from "@plane/types"; import type { TContextMenuItem } from "@plane/ui"; import { CustomMenu } from "@plane/ui"; import { cn } from "@plane/utils"; // hooks -import { useCommentMenuItems } from "@/components/common/quick-actions-helper"; import { useUser } from "@/hooks/store/user"; type TCommentCard = { @@ -25,29 +26,55 @@ export const CommentQuickActions = observer(function CommentQuickActions(props: const { data: currentUser } = useUser(); // derived values const isAuthor = currentUser?.id === comment.actor; + const canEdit = isAuthor; + const canDelete = isAuthor; + // translation + const { t } = useTranslation(); - const MENU_ITEMS: TContextMenuItem[] = useCommentMenuItems({ - comment: { - id: comment.id, - actor: comment.actor, - access: comment.access, + const MENU_ITEMS = useMemo( + function MENU_ITEMS(): TContextMenuItem[] { + return [ + { + key: "edit", + action: setEditMode, + title: t("common.actions.edit"), + icon: Pencil, + shouldRender: canEdit, + }, + { + key: "copy_link", + action: () => activityOperations.copyCommentLink(comment.id), + title: t("common.actions.copy_link"), + icon: Link, + shouldRender: showCopyLinkOption, + }, + { + key: "access_specifier", + action: () => + activityOperations.updateComment(comment.id, { + access: + comment.access === EIssueCommentAccessSpecifier.INTERNAL + ? EIssueCommentAccessSpecifier.EXTERNAL + : EIssueCommentAccessSpecifier.INTERNAL, + }), + title: + comment.access === EIssueCommentAccessSpecifier.INTERNAL + ? t("issue.comments.switch.public") + : t("issue.comments.switch.private"), + icon: comment.access === EIssueCommentAccessSpecifier.INTERNAL ? Globe2 : Lock, + shouldRender: showAccessSpecifier, + }, + { + key: "delete", + action: () => activityOperations.removeComment(comment.id), + title: t("common.actions.delete"), + icon: Trash2, + shouldRender: canDelete, + }, + ]; }, - isAuthor, - showAccessSpecifier, - showCopyLinkOption, - handleEdit: setEditMode, - handleCopyLink: () => activityOperations.copyCommentLink(comment.id), - handleToggleAccess: () => - activityOperations.updateComment(comment.id, { - access: - comment.access === EIssueCommentAccessSpecifier.INTERNAL - ? EIssueCommentAccessSpecifier.EXTERNAL - : EIssueCommentAccessSpecifier.INTERNAL, - }), - handleDelete: () => activityOperations.removeComment(comment.id), - }); - - if (MENU_ITEMS.length === 0) return null; + [t, setEditMode, canEdit, showCopyLinkOption, activityOperations, comment, showAccessSpecifier, canDelete] + ); return ( } closeOnSelect> diff --git a/apps/web/core/components/cycles/modal.tsx b/apps/web/core/components/cycles/modal.tsx index 9bad7b6519..bda4957fbd 100644 --- a/apps/web/core/components/cycles/modal.tsx +++ b/apps/web/core/components/cycles/modal.tsx @@ -140,14 +140,20 @@ export const CycleCreateUpdateModal = observer(function CycleCreateUpdateModal(p if (payload.start_date && payload.end_date) { if (data?.id) { - // Update existing cycle - always include cycle_id for validation - isDateValid = await dateChecker(projectId, { - start_date: payload.start_date, - end_date: payload.end_date, - cycle_id: data.id, - }); + // Update existing cycle - only check dates if they've changed + const originalStartDate = renderFormattedPayloadDate(data.start_date) ?? null; + const originalEndDate = renderFormattedPayloadDate(data.end_date) ?? null; + const hasDateChanged = payload.start_date !== originalStartDate || payload.end_date !== originalEndDate; + + if (hasDateChanged) { + isDateValid = await dateChecker(projectId, { + start_date: payload.start_date, + end_date: payload.end_date, + cycle_id: data.id, + }); + } } else { - // Create new cycle - no cycle_id needed + // Create new cycle - always check dates isDateValid = await dateChecker(projectId, { start_date: payload.start_date, end_date: payload.end_date, diff --git a/apps/web/package.json b/apps/web/package.json index b11b587aed..fa45612330 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -26,6 +26,9 @@ "@flatfile/hooks": "^1.6.0", "@flatfile/react": "^7.13.4", "@bprogress/core": "catalog:", + "@fontsource-variable/inter": "5.2.8", + "@fontsource/ibm-plex-mono": "5.2.7", + "@fontsource/material-symbols-rounded": "5.2.30", "@headlessui/react": "^1.7.19", "@intercom/messenger-js-sdk": "^0.0.12", "@plane/constants": "workspace:*", diff --git a/apps/web/styles/globals.css b/apps/web/styles/globals.css index adae051910..eb4a4b1d77 100644 --- a/apps/web/styles/globals.css +++ b/apps/web/styles/globals.css @@ -1,6 +1,5 @@ @import "@plane/tailwind-config/index.css"; @import "@plane/editor/styles"; -@import "@plane/propel/styles/fonts"; @import "@plane/propel/styles/react-day-picker.css"; @import "./animations.css"; @import "./power-k.css"; diff --git a/packages/propel/.storybook/tailwind.css b/packages/propel/.storybook/tailwind.css index baa553f51c..aca4ed08ea 100644 --- a/packages/propel/.storybook/tailwind.css +++ b/packages/propel/.storybook/tailwind.css @@ -1,3 +1,2 @@ @import "@plane/tailwind-config/index.css"; -@import "../src/styles/fonts/index.css"; @import "../src/styles/react-day-picker.css"; diff --git a/packages/propel/package.json b/packages/propel/package.json index 17be78c9bb..3d761b3b08 100644 --- a/packages/propel/package.json +++ b/packages/propel/package.json @@ -61,8 +61,6 @@ "./utils": "./dist/utils/index.js", "./video-player": "./dist/video-player/index.js", "./package.json": "./package.json", - "./styles/fonts.css": "./dist/styles/fonts/index.css", - "./styles/fonts": "./dist/styles/fonts/index.css", "./styles/react-day-picker.css": "./dist/styles/react-day-picker.css", "./styles/react-day-picker": "./dist/styles/react-day-picker.css" }, diff --git a/packages/propel/src/dialog/root.tsx b/packages/propel/src/dialog/root.tsx index 6360685843..4dfb2d7e99 100644 --- a/packages/propel/src/dialog/root.tsx +++ b/packages/propel/src/dialog/root.tsx @@ -35,12 +35,12 @@ export interface DialogTitleProps extends React.ComponentProps - cn("isolate fixed z-modal", { + cn("isolate fixed z-100", { "top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2": position === "center", "top-8 left-1/2 -translate-x-1/2": position === "top", }); diff --git a/packages/propel/src/emoji-icon-picker/logo.tsx b/packages/propel/src/emoji-icon-picker/logo.tsx index dbed85f58b..117fbec724 100644 --- a/packages/propel/src/emoji-icon-picker/logo.tsx +++ b/packages/propel/src/emoji-icon-picker/logo.tsx @@ -34,7 +34,6 @@ export function Logo({ logo, size = 16, type = "material" }: Props) { const value = in_use === "emoji" ? emoji?.value : icon?.name; if (!value) return loadingSkeleton; - if (!isMaterialSymbolsFontLoaded && type === "material") return loadingSkeleton; // Emoji rendering if (in_use === "emoji") { @@ -66,6 +65,8 @@ export function Logo({ logo, size = 16, type = "material" }: Props) { return ; } + if (!isMaterialSymbolsFontLoaded) return loadingSkeleton; + // Material icon return ( ({ ...exports, - "./styles/fonts.css": "./dist/styles/fonts/index.css", - "./styles/fonts": "./dist/styles/fonts/index.css", "./styles/react-day-picker.css": "./dist/styles/react-day-picker.css", "./styles/react-day-picker": "./dist/styles/react-day-picker.css", }), diff --git a/packages/tailwind-config/variables.css b/packages/tailwind-config/variables.css index fc7c75b83e..b3e87a2890 100644 --- a/packages/tailwind-config/variables.css +++ b/packages/tailwind-config/variables.css @@ -830,10 +830,10 @@ /* ---------- Font family ---------- */ --font-*: initial; --font-heading: - "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", + "Inter Variable", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --font-body: - "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", + "Inter Variable", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --font-code: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; @@ -1134,11 +1134,27 @@ @utility scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; - &::-webkit-scrollbar: { + &::-webkit-scrollbar { display: none; } } +@utility material-symbols-rounded { + font-family: "Material Symbols Rounded"; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + font-feature-settings: "liga"; + -webkit-font-smoothing: antialiased; +} + /* Tailwind typography plugin overwrites */ @utility prose { --tw-prose-body: var(--text-color-primary); diff --git a/packages/ui/styles/globals.css b/packages/ui/styles/globals.css index 64fd6ebdff..434ca7f910 100644 --- a/packages/ui/styles/globals.css +++ b/packages/ui/styles/globals.css @@ -1,2 +1 @@ @import "@plane/tailwind-config/index.css"; -@import "@plane/propel/styles/fonts"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8a69c4fa6..501be581bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,6 +195,15 @@ importers: '@bprogress/core': specifier: 'catalog:' version: 1.3.4 + '@fontsource-variable/inter': + specifier: 5.2.8 + version: 5.2.8 + '@fontsource/ibm-plex-mono': + specifier: 5.2.7 + version: 5.2.7 + '@fontsource/material-symbols-rounded': + specifier: 5.2.30 + version: 5.2.30 '@headlessui/react': specifier: ^1.7.19 version: 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -663,6 +672,15 @@ importers: '@bprogress/core': specifier: 'catalog:' version: 1.3.4 + '@fontsource-variable/inter': + specifier: 5.2.8 + version: 5.2.8 + '@fontsource/ibm-plex-mono': + specifier: 5.2.7 + version: 5.2.7 + '@fontsource/material-symbols-rounded': + specifier: 5.2.30 + version: 5.2.30 '@headlessui/react': specifier: ^1.7.19 version: 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -829,6 +847,15 @@ importers: '@flatfile/react': specifier: ^7.13.4 version: 7.13.10(@flatfile/hooks@1.6.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@fontsource-variable/inter': + specifier: 5.2.8 + version: 5.2.8 + '@fontsource/ibm-plex-mono': + specifier: 5.2.7 + version: 5.2.7 + '@fontsource/material-symbols-rounded': + specifier: 5.2.30 + version: 5.2.30 '@headlessui/react': specifier: ^1.7.19 version: 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2820,6 +2847,15 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@fontsource-variable/inter@5.2.8': + resolution: {integrity: sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==} + + '@fontsource/ibm-plex-mono@5.2.7': + resolution: {integrity: sha512-MKAb8qV+CaiMQn2B0dIi1OV3565NYzp3WN5b4oT6LTkk+F0jR6j0ZN+5BKJiIhffDC3rtBULsYZE65+0018z9w==} + + '@fontsource/material-symbols-rounded@5.2.30': + resolution: {integrity: sha512-svIEPUzsJGdBMr0PwLH3wKndh7ZNB8IVlyKZPKvbGEX0f36gpGPzE2EZiXppb4UJgDUnyjphWzjJ28StuK6NCg==} + '@formatjs/ecma402-abstract@2.3.4': resolution: {integrity: sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==} @@ -12954,6 +12990,12 @@ snapshots: '@floating-ui/utils@0.2.10': {} + '@fontsource-variable/inter@5.2.8': {} + + '@fontsource/ibm-plex-mono@5.2.7': {} + + '@fontsource/material-symbols-rounded@5.2.30': {} + '@formatjs/ecma402-abstract@2.3.4': dependencies: '@formatjs/fast-memoize': 2.2.7