mirror of
https://github.com/makeplane/plane.git
synced 2026-09-02 12:09:14 +02:00
refactor(space): migrate from @plane/propel to @makeplane/propel
- Drop the @plane/propel workspace dependency from apps/space - Buttons/tooltips/toast switched to @makeplane/propel APIs (label-based Button, label/side Tooltip, manager-based toast adapter mirroring admin) - Simple icons renamed to @makeplane/propel/icons equivalents - Domain icons (StateGroup/Priority/CycleGroup) and Plane brand assets vendored into apps/space/components/icons - Emoji reactions reimplemented locally on frimousse + propel popover (apps/space/components/emoji); animated counter dropped - Remove dead react-day-picker stylesheet import from space globals - Clean stale @plane/propel comments in admin
This commit is contained in:
@@ -8,8 +8,7 @@ import React from "react";
|
||||
// helpers
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
// Loading-placeholder skeleton. @makeplane/propel has no skeleton component yet;
|
||||
// this matches the API the workspace @plane/propel skeleton exposed.
|
||||
// Loading-placeholder skeleton. @makeplane/propel has no skeleton component yet, so it is kept local.
|
||||
|
||||
type SkeletonProps = {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -10,8 +10,7 @@ import { IconButton } from "@makeplane/propel/components/icon-button";
|
||||
import { Icon } from "@makeplane/propel/components/icon";
|
||||
import { CloseOutline } from "@makeplane/propel/icons";
|
||||
|
||||
// Keep the call-site API the workspace @plane/propel toast exposed, backed by
|
||||
// @makeplane/propel's toast manager, so consumers only change their import path.
|
||||
// Thin wrapper over @makeplane/propel's toast manager to keep the call-site API stable.
|
||||
export enum TOAST_TYPE {
|
||||
SUCCESS = "success",
|
||||
ERROR = "error",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
// ui
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
|
||||
function ErrorPage() {
|
||||
const handleRetry = () => {
|
||||
@@ -31,12 +31,7 @@ function ErrorPage() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Button variant="primary" size="lg" onClick={handleRetry}>
|
||||
Refresh
|
||||
</Button>
|
||||
{/* <Button variant="secondary" size="lg" onClick={() => {}}>
|
||||
Sign out
|
||||
</Button> */}
|
||||
<Button variant="primary" size="lg" stretch="auto" onClick={handleRetry} label="Refresh" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { Info } from "lucide-react";
|
||||
import { CloseIcon } from "@plane/propel/icons";
|
||||
import { CloseOutline } from "@makeplane/propel/icons";
|
||||
// helpers
|
||||
import type { TAuthErrorInfo } from "@/helpers/authentication.helper";
|
||||
|
||||
@@ -28,7 +28,7 @@ export function AuthBanner(props: TAuthBanner) {
|
||||
className="relative ml-auto flex h-6 w-6 cursor-pointer items-center justify-center rounded-xs text-accent-primary/80 transition-all hover:bg-accent-primary/20"
|
||||
onClick={() => handleBannerData && handleBannerData(undefined)}
|
||||
>
|
||||
<CloseIcon className="h-4 w-4 flex-shrink-0" />
|
||||
<CloseOutline className="h-4 w-4 flex-shrink-0" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -10,10 +10,10 @@ import { observer } from "mobx-react";
|
||||
// icons
|
||||
import { CircleAlert, XCircle } from "lucide-react";
|
||||
// types
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import type { IEmailCheckData } from "@plane/types";
|
||||
// ui
|
||||
import { Input, Spinner } from "@plane/ui";
|
||||
import { Input } from "@plane/ui";
|
||||
// helpers
|
||||
import { cn } from "@plane/utils";
|
||||
import { checkEmailValidity } from "@/helpers/string.helper";
|
||||
@@ -100,9 +100,15 @@ export const AuthEmailForm = observer(function AuthEmailForm(props: TAuthEmailFo
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Button type="submit" variant="primary" className="w-full" size="xl" disabled={isButtonDisabled}>
|
||||
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Continue"}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
stretch="full"
|
||||
disabled={isButtonDisabled}
|
||||
loading={isSubmitting}
|
||||
label="Continue"
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -8,10 +8,10 @@ import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Eye, EyeOff, XCircle } from "lucide-react";
|
||||
// plane imports
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { API_BASE_URL, E_PASSWORD_STRENGTH } from "@plane/constants";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { AuthService } from "@plane/services";
|
||||
import { Input, Spinner, PasswordStrengthIndicator } from "@plane/ui";
|
||||
import { Input, PasswordStrengthIndicator } from "@plane/ui";
|
||||
import { getPasswordStrength } from "@plane/utils";
|
||||
// types
|
||||
import { EAuthModes, EAuthSteps } from "@/types/auth";
|
||||
@@ -215,31 +215,36 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
|
||||
<div className="space-y-2.5">
|
||||
{mode === EAuthModes.SIGN_IN ? (
|
||||
<>
|
||||
<Button type="submit" variant="primary" className="w-full" size="xl" disabled={isButtonDisabled}>
|
||||
{isSubmitting ? (
|
||||
<Spinner height="20px" width="20px" />
|
||||
) : isSMTPConfigured ? (
|
||||
"Continue"
|
||||
) : (
|
||||
"Go to workspace"
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
stretch="full"
|
||||
disabled={isButtonDisabled}
|
||||
loading={isSubmitting}
|
||||
label={isSMTPConfigured ? "Continue" : "Go to workspace"}
|
||||
/>
|
||||
{isSMTPConfigured && (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={redirectToUniqueCodeSignIn}
|
||||
variant="secondary"
|
||||
className="w-full"
|
||||
size="xl"
|
||||
>
|
||||
Sign in with unique code
|
||||
</Button>
|
||||
size="lg"
|
||||
stretch="full"
|
||||
label="Sign in with unique code"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Button type="submit" variant="primary" className="w-full" size="xl" disabled={isButtonDisabled}>
|
||||
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Create account"}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
stretch="full"
|
||||
disabled={isButtonDisabled}
|
||||
loading={isSubmitting}
|
||||
label="Create account"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { CircleCheck, XCircle } from "lucide-react";
|
||||
// plane imports
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { API_BASE_URL } from "@plane/constants";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { AuthService } from "@plane/services";
|
||||
import { Input, Spinner } from "@plane/ui";
|
||||
import { Input } from "@plane/ui";
|
||||
// hooks
|
||||
import useTimer from "@/hooks/use-timer";
|
||||
// types
|
||||
@@ -148,9 +148,15 @@ export function AuthUniqueCodeForm(props: TAuthUniqueCodeForm) {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2.5">
|
||||
<Button type="submit" variant="primary" className="w-full" size="xl" disabled={isButtonDisabled}>
|
||||
{isRequestingNewCode ? "Sending code" : isSubmitting ? <Spinner height="20px" width="20px" /> : "Continue"}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
stretch="full"
|
||||
disabled={isButtonDisabled}
|
||||
loading={isSubmitting}
|
||||
label={isRequestingNewCode ? "Sending code" : "Continue"}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { PlaneLockup } from "@plane/propel/icons";
|
||||
// assets
|
||||
import UserLoggedInImage from "@/app/assets/user-logged-in.svg?url";
|
||||
// components
|
||||
import { PoweredBy } from "@/components/common/powered-by";
|
||||
import { PlaneLockup } from "@/components/icons";
|
||||
import { UserAvatar } from "@/components/issues/navbar/user-avatar";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store/use-user";
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
|
||||
import { WEBSITE_URL } from "@plane/constants";
|
||||
// assets
|
||||
import { PlaneLogo } from "@plane/propel/icons";
|
||||
// components
|
||||
import { PlaneLogo } from "@/components/icons";
|
||||
|
||||
type TPoweredBy = {
|
||||
disabled?: boolean;
|
||||
|
||||
@@ -8,8 +8,8 @@ import React, { useEffect, useState, useCallback } from "react";
|
||||
// plane imports
|
||||
import { TOOLBAR_ITEMS } from "@plane/editor";
|
||||
import type { ToolbarMenuItem, EditorRefApi } from "@plane/editor";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
type Props = {
|
||||
@@ -68,15 +68,7 @@ export function IssueCommentToolbar(props: Props) {
|
||||
const isItemActive = activeStates[item.renderKey];
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={item.renderKey}
|
||||
tooltipContent={
|
||||
<p className="flex flex-col gap-1 text-center text-11">
|
||||
<span className="font-medium">{item.name}</span>
|
||||
{item.shortcut && <kbd className="text-placeholder">{item.shortcut.join(" + ")}</kbd>}
|
||||
</p>
|
||||
}
|
||||
>
|
||||
<Tooltip key={item.renderKey} label={item.name} shortcut={item.shortcut?.join(" + ")}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => executeCommand(item)}
|
||||
@@ -104,13 +96,14 @@ export function IssueCommentToolbar(props: Props) {
|
||||
<div className="sticky right-1">
|
||||
<Button
|
||||
type="button"
|
||||
label="Comment"
|
||||
variant="primary"
|
||||
size="md"
|
||||
stretch="auto"
|
||||
onClick={handleSubmit}
|
||||
disabled={isCommentEmpty}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Comment
|
||||
</Button>
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
141
apps/space/components/emoji/emoji-reaction-picker.tsx
Normal file
141
apps/space/components/emoji/emoji-reaction-picker.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { EmojiPicker } from "frimousse";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@makeplane/propel/components/popover";
|
||||
// plane imports
|
||||
import { cn } from "@plane/utils";
|
||||
// local imports
|
||||
import { emojiToString } from "./helpers";
|
||||
|
||||
type EmojiRootProps = {
|
||||
onChange: (value: string) => void;
|
||||
searchPlaceholder?: string;
|
||||
};
|
||||
|
||||
function EmojiRoot(props: EmojiRootProps) {
|
||||
const { onChange, searchPlaceholder = "Search" } = props;
|
||||
const searchWrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const inputElement = searchWrapperRef.current?.querySelector("input");
|
||||
if (inputElement) {
|
||||
inputElement.removeAttribute("disabled");
|
||||
inputElement.focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<EmojiPicker.Root
|
||||
data-slot="emoji-picker"
|
||||
className="isolate flex h-full w-full flex-col rounded-md border-none p-2"
|
||||
onEmojiSelect={(val) => onChange(val.emoji)}
|
||||
>
|
||||
<div className="sticky top-0 z-10 flex items-center bg-layer-2 px-1.5 py-2">
|
||||
<div ref={searchWrapperRef} data-slot="emoji-picker-search-wrapper" className="flex-grow">
|
||||
<EmojiPicker.Search
|
||||
placeholder={searchPlaceholder}
|
||||
className="block h-full w-full rounded-md border-[0.5px] border-subtle bg-transparent px-3 py-2 text-16 placeholder-(--text-color-placeholder) focus:border-accent-strong focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<EmojiPicker.Viewport data-slot="emoji-picker-content" className="relative flex-1 outline-none">
|
||||
<EmojiPicker.List
|
||||
data-slot="emoji-picker-list"
|
||||
className="pb-2 select-none"
|
||||
components={{
|
||||
CategoryHeader: ({ category, ...props }) => (
|
||||
<div
|
||||
data-slot="emoji-picker-list-category-header"
|
||||
className="bg-layer-2 px-3 pb-1.5 text-11 font-medium text-tertiary"
|
||||
{...props}
|
||||
>
|
||||
{category.label}
|
||||
</div>
|
||||
),
|
||||
Row: ({ children, ...props }) => (
|
||||
<div data-slot="emoji-picker-list-row" className="scroll-my-1.5 px-1.5" {...props}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
Emoji: ({ emoji, ...props }) => (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={emoji?.label ?? emoji?.emoji}
|
||||
data-slot="emoji-picker-list-emoji"
|
||||
className="data-active:bg-accent flex size-8 items-center justify-center rounded-md text-16"
|
||||
{...props}
|
||||
>
|
||||
{emoji.emoji}
|
||||
</button>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EmojiPicker.Viewport>
|
||||
</EmojiPicker.Root>
|
||||
);
|
||||
}
|
||||
|
||||
type TPlacement = "top" | "right" | "bottom" | "left" | `${"top" | "right" | "bottom" | "left"}-${"start" | "end"}`;
|
||||
|
||||
export interface EmojiReactionPickerProps {
|
||||
isOpen: boolean;
|
||||
handleToggle: (value: boolean) => void;
|
||||
buttonClassName?: string;
|
||||
closeOnSelect?: boolean;
|
||||
disabled?: boolean;
|
||||
dropdownClassName?: string;
|
||||
label: React.ReactNode;
|
||||
onChange: (emoji: string) => void;
|
||||
placement?: TPlacement;
|
||||
searchPlaceholder?: string;
|
||||
}
|
||||
|
||||
export function EmojiReactionPicker(props: EmojiReactionPickerProps) {
|
||||
const {
|
||||
isOpen,
|
||||
handleToggle,
|
||||
buttonClassName,
|
||||
closeOnSelect = true,
|
||||
disabled = false,
|
||||
dropdownClassName,
|
||||
label,
|
||||
onChange,
|
||||
placement = "bottom-start",
|
||||
searchPlaceholder = "Search",
|
||||
} = props;
|
||||
|
||||
const { side, align } = useMemo(() => {
|
||||
const [rawSide, rawAlign] = placement.split("-") as ["top" | "right" | "bottom" | "left", ("start" | "end")?];
|
||||
return { side: rawSide, align: rawAlign ?? "start" };
|
||||
}, [placement]);
|
||||
|
||||
const handleEmojiChange = useCallback(
|
||||
(value: string) => {
|
||||
onChange(emojiToString(value));
|
||||
if (closeOnSelect) handleToggle(false);
|
||||
},
|
||||
[onChange, closeOnSelect, handleToggle]
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover open={isOpen} onOpenChange={handleToggle}>
|
||||
<PopoverTrigger
|
||||
render={<button type="button" className={cn("outline-none", buttonClassName)} disabled={disabled} />}
|
||||
>
|
||||
{label}
|
||||
</PopoverTrigger>
|
||||
<PopoverContent side={side} align={align} sideOffset={8}>
|
||||
{/* bleed past the propel popup's default padding so the picker fills the panel */}
|
||||
<div className={cn("-m-4 h-80 w-80 overflow-hidden rounded-[inherit]", dropdownClassName)}>
|
||||
<EmojiRoot onChange={handleEmojiChange} searchPlaceholder={searchPlaceholder} />
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
110
apps/space/components/emoji/emoji-reaction.tsx
Normal file
110
apps/space/components/emoji/emoji-reaction.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import { ReactionOutline } from "@makeplane/propel/icons";
|
||||
// plane imports
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
export interface EmojiReactionType {
|
||||
emoji: string;
|
||||
count: number;
|
||||
reacted?: boolean;
|
||||
users?: string[];
|
||||
}
|
||||
|
||||
interface EmojiReactionProps {
|
||||
emoji: string;
|
||||
count: number;
|
||||
reacted?: boolean;
|
||||
users?: string[];
|
||||
onReactionClick?: (emoji: string) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const MAX_DISPLAY_USERS = 5;
|
||||
|
||||
const EmojiReaction = React.forwardRef(function EmojiReaction(
|
||||
props: EmojiReactionProps,
|
||||
ref: React.ForwardedRef<HTMLButtonElement>
|
||||
) {
|
||||
const { emoji, count, reacted = false, users = [], onReactionClick, className } = props;
|
||||
|
||||
const handleClick = () => {
|
||||
onReactionClick?.(emoji);
|
||||
};
|
||||
|
||||
const button = (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
onClick={handleClick}
|
||||
className={cn(
|
||||
"inline-flex items-center gap-0.5 rounded-full border px-1.5 text-11 transition-all duration-200",
|
||||
reacted
|
||||
? "border-accent-strong bg-accent-primary/10 text-accent-primary"
|
||||
: "border-subtle bg-surface-1 text-tertiary hover:border-strong hover:bg-surface-2",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<span className="leading-unset text-14">{emoji}</span>
|
||||
{count > 0 && <span className="text-11 leading-normal">{count}</span>}
|
||||
</button>
|
||||
);
|
||||
|
||||
if (users.length === 0) return button;
|
||||
|
||||
const displayUsers = users.slice(0, MAX_DISPLAY_USERS);
|
||||
const remainingCount = users.length - displayUsers.length;
|
||||
const tooltipLabel = `${emoji} ${displayUsers.join(", ")}${remainingCount > 0 ? ` and ${remainingCount} more` : ""}`;
|
||||
|
||||
return <Tooltip label={tooltipLabel}>{button}</Tooltip>;
|
||||
});
|
||||
|
||||
export interface EmojiReactionGroupProps {
|
||||
reactions: EmojiReactionType[];
|
||||
onReactionClick?: (emoji: string) => void;
|
||||
onAddReaction?: () => void;
|
||||
showAddButton?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const EmojiReactionGroup = React.forwardRef(function EmojiReactionGroup(
|
||||
props: EmojiReactionGroupProps,
|
||||
ref: React.ForwardedRef<HTMLDivElement>
|
||||
) {
|
||||
const { reactions, onReactionClick, onAddReaction, showAddButton = true, className } = props;
|
||||
|
||||
return (
|
||||
<div ref={ref} className={cn("flex flex-wrap items-center gap-2", className)}>
|
||||
{reactions.map((reaction, index) => (
|
||||
<EmojiReaction
|
||||
key={`${reaction.emoji}-${index}`}
|
||||
emoji={reaction.emoji}
|
||||
count={reaction.count}
|
||||
reacted={reaction.reacted}
|
||||
users={reaction.users}
|
||||
onReactionClick={onReactionClick}
|
||||
/>
|
||||
))}
|
||||
{showAddButton && (
|
||||
<Tooltip label="Add reaction">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onAddReaction}
|
||||
className="grid size-6 place-items-center rounded-full text-tertiary transition-colors outline-none hover:bg-surface-2 hover:text-secondary"
|
||||
>
|
||||
<ReactionOutline className="size-3.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
EmojiReaction.displayName = "EmojiReaction";
|
||||
EmojiReactionGroup.displayName = "EmojiReactionGroup";
|
||||
38
apps/space/components/emoji/helpers.ts
Normal file
38
apps/space/components/emoji/helpers.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts an emoji to a dash-separated string of decimal Unicode code points.
|
||||
* Handles multi-byte characters and emoji sequences (including skin tone modifiers).
|
||||
* @param emoji - The emoji string to convert
|
||||
* @returns Dash-separated string of decimal code points (e.g. "128077-127999")
|
||||
*/
|
||||
export function emojiToString(emoji: string): string {
|
||||
const codePoints: number[] = [];
|
||||
for (const char of Array.from(emoji)) {
|
||||
const codePoint = char.codePointAt(0);
|
||||
if (codePoint !== undefined) codePoints.push(codePoint);
|
||||
}
|
||||
return codePoints.join("-");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a dash-separated string of decimal Unicode code points back to the emoji character.
|
||||
* @param emojiString - Dash-separated string of decimal code points (e.g. "128077-127999")
|
||||
* @returns The reconstructed emoji string, or "" on failure
|
||||
*/
|
||||
export function stringToEmoji(emojiString: string): string {
|
||||
if (!emojiString) return "";
|
||||
const decimals = emojiString
|
||||
.split("-")
|
||||
.map((s) => Number(s.trim()))
|
||||
.filter((n) => Number.isFinite(n) && n >= 0 && n <= 0x10ffff);
|
||||
try {
|
||||
return decimals.map((decimal) => String.fromCodePoint(decimal)).join("");
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
10
apps/space/components/emoji/index.ts
Normal file
10
apps/space/components/emoji/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
export { stringToEmoji, emojiToString } from "./helpers";
|
||||
export { EmojiReactionGroup } from "./emoji-reaction";
|
||||
export type { EmojiReactionType } from "./emoji-reaction";
|
||||
export { EmojiReactionPicker } from "./emoji-reaction-picker";
|
||||
51
apps/space/components/icons/brand/plane-lockup.tsx
Normal file
51
apps/space/components/icons/brand/plane-lockup.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import type { ISvgIcons } from "../type";
|
||||
|
||||
export function PlaneLockup({ width = "253", height = "53", className, color = "currentColor" }: ISvgIcons) {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 253 53"
|
||||
fill={color}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g clipPath="url(#clip0_27_76)">
|
||||
<path
|
||||
d="M217.077 51.4468H210.972L210.98 31.4317C210.717 27.7545 208.786 25.1545 205.03 24.6199C198.587 23.7006 194.663 27.8828 194.21 34.0258L194.218 51.4468H188.041V20.2543H192.158L194.255 26.8846H194.6C195.825 22.8701 198.684 20.4831 202.898 19.5757C210.126 18.0231 216.643 22.2724 217.079 29.8239V51.4468H217.077Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M163.812 19.7315C158.03 20.9566 153.67 25.2354 153.222 31.264H159.395C159.669 28.3484 161.561 25.9969 164.267 24.9553C167.418 23.7401 172.805 23.8881 175.178 26.6005C176.774 28.4253 177.305 31.5816 175.212 33.2249C172.675 35.2173 166.39 34.6117 163.214 34.7636C158.631 34.9826 154.141 36.8429 153.183 41.7727C151.1 52.5042 165.035 54.9267 171.902 49.8963C174.095 48.2885 175.672 45.8443 176.261 43.1792H176.577L178.788 51.4508H182.61V29.964C182.517 20.3214 171.462 18.108 163.81 19.7315H163.812ZM174.847 41.2637C172.484 45.649 166.958 49.0441 162.218 47.3318C158.44 45.9686 158.268 40.4095 161.973 38.9181C165.613 37.4524 173.732 38.9694 176.368 34.949C176.275 37.0855 175.906 39.2989 174.849 41.2637H174.847Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M113.082 35.5652V51.4475H106.611V9.00063H124.863C125.007 8.99274 125.141 9.06178 125.283 9.06967C133.732 9.53129 139.297 13.601 139.104 22.6025C138.928 30.7991 132.663 35.2199 124.934 35.5671H113.082V35.5652ZM113.082 14.4828V30.2941H124.088C129.595 29.8956 133.071 27.1022 132.491 21.265C131.967 15.9781 127.802 14.6544 123.107 14.4788C119.795 14.3546 116.402 14.5795 113.082 14.4808V14.4828Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path d="M148.647 9H142.529V51.4468H148.647V9Z" fill={color} />
|
||||
<path
|
||||
d="M245.872 42.418C244.911 44.4953 243.138 46.1603 240.934 46.8981C238.368 47.7562 234.479 47.5431 232.083 46.2569C228.67 44.4263 227.145 40.6485 227.137 36.8984H251.891C252.194 33.6908 251.902 30.3648 250.496 27.4333C247.083 20.3177 238.859 17.7236 231.513 19.9429C222.868 22.5568 220.165 31.4162 221.462 39.6543C222.86 48.5611 230.197 52.976 238.958 52.3585C244.884 51.9423 250.271 48.1192 251.788 42.4161H245.874L245.872 42.418ZM227.208 32.4381C227.778 26.2457 233.347 23.2018 239.163 24.4269C243.361 25.3107 245.75 28.9523 245.842 33.1068H227.145C227.145 33.1068 227.19 32.6255 227.208 32.4401V32.4381Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M44.3223 2.9264C44.3223 0.754665 46.6083 -0.65811 48.5508 0.313121L80.4551 16.2653C82.9294 17.5024 84.4922 20.0321 84.4922 22.7985V48.2487C84.4922 50.4204 82.2071 51.833 80.2646 50.8619L62.3281 41.8932V22.7975C62.3281 20.0311 60.7653 17.5015 58.291 16.2643L44.3223 9.27992V2.9264ZM0 2.92543C8.01645e-05 0.753753 2.28609 -0.659069 4.22852 0.312144L22.1582 9.27699V28.3766C22.1582 31.1428 23.7213 33.6716 26.1953 34.9088L40.1699 41.8952V48.2487C40.1697 50.4202 37.8847 51.832 35.9424 50.861L4.03711 34.9088C1.56305 33.6716 0 31.1428 0 28.3766V2.92543ZM22.1582 2.92543C22.1583 0.753753 24.4443 -0.659069 26.3867 0.312144L44.3223 9.27992V28.3776C44.3223 31.1439 45.8861 33.6727 48.3604 34.9098L62.3281 41.8932V48.2487C62.3279 50.4202 60.0429 51.832 58.1006 50.861L40.1699 41.8952V22.7975C40.1699 20.0311 38.6071 17.5015 36.1328 16.2643L22.1582 9.27699V2.92543Z"
|
||||
fill={color}
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_27_76">
|
||||
<rect width="252" height="53" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
27
apps/space/components/icons/brand/plane-logo.tsx
Normal file
27
apps/space/components/icons/brand/plane-logo.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import type { ISvgIcons } from "../type";
|
||||
|
||||
export function PlaneLogo({ width = "85", height = "52", className, color = "currentColor" }: ISvgIcons) {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 85 52"
|
||||
fill={color}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M44.3223 2.9264C44.3223 0.754665 46.6083 -0.65811 48.5508 0.313121L80.4551 16.2653C82.9294 17.5024 84.4922 20.0321 84.4922 22.7985V48.2487C84.4922 50.4204 82.2071 51.833 80.2646 50.8619L62.3281 41.8932V22.7975C62.3281 20.0311 60.7653 17.5015 58.291 16.2643L44.3223 9.27992V2.9264ZM0 2.92543C8.01645e-05 0.753753 2.28609 -0.659069 4.22852 0.312144L22.1582 9.27699V28.3766C22.1582 31.1428 23.7213 33.6716 26.1953 34.9088L40.1699 41.8952V48.2487C40.1697 50.4202 37.8847 51.832 35.9424 50.861L4.03711 34.9088C1.56305 33.6716 0 31.1428 0 28.3766V2.92543ZM22.1582 2.92543C22.1583 0.753753 24.4443 -0.659069 26.3867 0.312144L44.3223 9.27992V28.3776C44.3223 31.1439 45.8861 33.6727 48.3604 34.9098L62.3281 41.8932V48.2487C62.3279 50.4202 60.0429 51.832 58.1006 50.861L40.1699 41.8952V22.7975C40.1699 20.0311 38.6071 17.5015 36.1328 16.2643L22.1582 9.27699V2.92543Z"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
24
apps/space/components/icons/cycle/circle-dot-full-icon.tsx
Normal file
24
apps/space/components/icons/cycle/circle-dot-full-icon.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import type { ISvgIcons } from "../type";
|
||||
|
||||
export function CircleDotFullIcon({ className = "text-current", ...rest }: ISvgIcons) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
className={`${className} stroke-1`}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...rest}
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
||||
<circle cx="12" cy="12" r="6.25" fill="currentColor" strokeWidth="0.5" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
39
apps/space/components/icons/cycle/cycle-group-icon.tsx
Normal file
39
apps/space/components/icons/cycle/cycle-group-icon.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { CircleDotDashed, Circle } from "lucide-react";
|
||||
|
||||
import { CycleIcon } from "../project/cycle-icon";
|
||||
import { CircleDotFullIcon } from "./circle-dot-full-icon";
|
||||
import type { ICycleGroupIcon } from "./helper";
|
||||
import { CYCLE_GROUP_COLORS } from "./helper";
|
||||
|
||||
const iconComponents = {
|
||||
current: CycleIcon,
|
||||
upcoming: CircleDotDashed,
|
||||
completed: CircleDotFullIcon,
|
||||
draft: Circle,
|
||||
};
|
||||
|
||||
export function CycleGroupIcon({
|
||||
className = "",
|
||||
color,
|
||||
cycleGroup,
|
||||
height = "12px",
|
||||
width = "12px",
|
||||
}: ICycleGroupIcon) {
|
||||
const CycleIconComponent = iconComponents[cycleGroup] || CycleIcon;
|
||||
|
||||
return (
|
||||
<CycleIconComponent
|
||||
height={height}
|
||||
width={width}
|
||||
color={color ?? CYCLE_GROUP_COLORS[cycleGroup]}
|
||||
className={`flex-shrink-0 ${className}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
33
apps/space/components/icons/cycle/helper.tsx
Normal file
33
apps/space/components/icons/cycle/helper.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
export interface ICycleGroupIcon {
|
||||
className?: string;
|
||||
color?: string;
|
||||
cycleGroup: TCycleGroups;
|
||||
height?: string;
|
||||
width?: string;
|
||||
}
|
||||
|
||||
export type TCycleGroups = "current" | "upcoming" | "completed" | "draft";
|
||||
|
||||
export const CYCLE_GROUP_COLORS: {
|
||||
[key in TCycleGroups]: string;
|
||||
} = {
|
||||
current: "#F59E0B",
|
||||
upcoming: "#3F76FF",
|
||||
completed: "#16A34A",
|
||||
draft: "#525252",
|
||||
};
|
||||
|
||||
export const CYCLE_GROUP_I18N_LABELS: {
|
||||
[key in TCycleGroups]: string;
|
||||
} = {
|
||||
current: "current",
|
||||
upcoming: "common.upcoming",
|
||||
completed: "common.completed",
|
||||
draft: "project_cycles.status.draft",
|
||||
};
|
||||
50
apps/space/components/icons/icon-wrapper.tsx
Normal file
50
apps/space/components/icons/icon-wrapper.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import type { ISvgIcons } from "./type";
|
||||
|
||||
interface IIconWrapper extends ISvgIcons {
|
||||
children: React.ReactNode;
|
||||
clipPathId?: string;
|
||||
viewBox?: string;
|
||||
}
|
||||
|
||||
export function IconWrapper({
|
||||
width = "16",
|
||||
height = "16",
|
||||
className = "text-current",
|
||||
children,
|
||||
clipPathId,
|
||||
viewBox = "0 0 16 16",
|
||||
...rest
|
||||
}: IIconWrapper) {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={viewBox}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
{...rest}
|
||||
>
|
||||
{clipPathId ? (
|
||||
<>
|
||||
<g clipPath={`url(#${clipPathId})`}>{children}</g>
|
||||
<defs>
|
||||
<clipPath id={clipPathId}>
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
15
apps/space/components/icons/index.ts
Normal file
15
apps/space/components/icons/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
// Domain and brand icons vendored from the workspace @plane/propel icon set —
|
||||
// @makeplane/propel has no equivalents for these.
|
||||
export { StateGroupIcon } from "./state/state-group-icon";
|
||||
export { PriorityIcon } from "./priority-icon";
|
||||
export type { TIssuePriorities } from "./priority-icon";
|
||||
export { CycleGroupIcon } from "./cycle/cycle-group-icon";
|
||||
export { PlaneLogo } from "./brand/plane-logo";
|
||||
export { PlaneLockup } from "./brand/plane-lockup";
|
||||
export type { ISvgIcons } from "./type";
|
||||
84
apps/space/components/icons/priority-icon.tsx
Normal file
84
apps/space/components/icons/priority-icon.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { AlertCircle, Ban, SignalHigh, SignalLow, SignalMedium } from "lucide-react";
|
||||
import { cn } from "@plane/utils";
|
||||
|
||||
export type TIssuePriorities = "urgent" | "high" | "medium" | "low" | "none";
|
||||
|
||||
interface IPriorityIcon {
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
priority: TIssuePriorities | undefined | null;
|
||||
size?: number;
|
||||
withContainer?: boolean;
|
||||
}
|
||||
|
||||
export function PriorityIcon(props: IPriorityIcon) {
|
||||
const { priority, className = "", containerClassName = "", size = 14, withContainer = false } = props;
|
||||
|
||||
const priorityClasses = {
|
||||
urgent: "bg-layer-2 text-priority-urgent border-priority-urgent",
|
||||
high: "bg-layer-2 text-priority-high border-priority-high",
|
||||
medium: "bg-layer-2 text-priority-medium border-priority-medium",
|
||||
low: "bg-layer-2 text-priority-low border-priority-low",
|
||||
none: "bg-layer-2 text-priority-none border-priority-none",
|
||||
};
|
||||
|
||||
// get priority icon
|
||||
const icons = {
|
||||
urgent: AlertCircle,
|
||||
high: SignalHigh,
|
||||
medium: SignalMedium,
|
||||
low: SignalLow,
|
||||
none: Ban,
|
||||
};
|
||||
const Icon = icons[priority ?? "none"];
|
||||
|
||||
if (!Icon) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{withContainer ? (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-shrink-0 items-center justify-center rounded-sm border p-0.5",
|
||||
priorityClasses[priority ?? "none"],
|
||||
containerClassName
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
size={size}
|
||||
className={cn(
|
||||
{
|
||||
"translate-x-[0.0625rem]": priority === "high",
|
||||
"translate-x-0.5": priority === "medium",
|
||||
"translate-x-1": priority === "low",
|
||||
},
|
||||
className
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Icon
|
||||
size={size}
|
||||
className={cn(
|
||||
"flex-shrink-0",
|
||||
{
|
||||
"text-priority-urgent": priority === "urgent",
|
||||
"text-priority-high": priority === "high",
|
||||
"text-priority-medium": priority === "medium",
|
||||
"text-priority-low": priority === "low",
|
||||
"text-priority-none": priority === "none",
|
||||
},
|
||||
className
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
27
apps/space/components/icons/project/cycle-icon.tsx
Normal file
27
apps/space/components/icons/project/cycle-icon.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import { IconWrapper } from "../icon-wrapper";
|
||||
import type { ISvgIcons } from "../type";
|
||||
|
||||
export function CycleIcon({ color = "currentColor", ...rest }: ISvgIcons) {
|
||||
const clipPathId = React.useId();
|
||||
|
||||
return (
|
||||
<IconWrapper color={color} clipPathId={clipPathId} {...rest}>
|
||||
<path
|
||||
d="M14.041 8C14.041 4.66339 11.3365 1.95818 8 1.95801C4.66328 1.95801 1.95801 4.66328 1.95801 8C1.95818 11.3365 4.66339 14.041 8 14.041C11.3364 14.0408 14.0408 11.3364 14.041 8ZM15.291 8C15.2908 12.0268 12.0268 15.2908 8 15.291C3.97303 15.291 0.708184 12.0269 0.708008 8C0.708008 3.97292 3.97292 0.708008 8 0.708008C12.0269 0.708184 15.291 3.97303 15.291 8Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M7.99951 12.3337C10.3928 12.3337 12.3328 10.3936 12.3328 8.00033C12.3328 5.60709 10.3928 3.66699 7.99951 3.66699V12.3337Z"
|
||||
fill={color}
|
||||
/>
|
||||
</IconWrapper>
|
||||
);
|
||||
}
|
||||
28
apps/space/components/icons/state/backlog-group-icon.tsx
Normal file
28
apps/space/components/icons/state/backlog-group-icon.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import type { ISvgIcons } from "../type";
|
||||
import { DashedCircle } from "./dashed-circle";
|
||||
|
||||
export function BacklogGroupIcon({ width = "20", height = "20", className, color = "#60646C" }: ISvgIcons) {
|
||||
// SVG parameters
|
||||
const viewBoxSize = 16;
|
||||
const center = viewBoxSize / 2;
|
||||
const radius = 6;
|
||||
return (
|
||||
<svg
|
||||
height={height}
|
||||
width={width}
|
||||
viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}
|
||||
className={className}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<DashedCircle center={center} radius={radius} color={color} percentage={0} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
35
apps/space/components/icons/state/cancelled-group-icon.tsx
Normal file
35
apps/space/components/icons/state/cancelled-group-icon.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import type { ISvgIcons } from "../type";
|
||||
|
||||
export function CancelledGroupIcon({
|
||||
className = "",
|
||||
color = "#9AA4BC",
|
||||
height = "20",
|
||||
width = "20",
|
||||
...rest
|
||||
}: ISvgIcons) {
|
||||
return (
|
||||
<svg
|
||||
height={height}
|
||||
width={width}
|
||||
className={className}
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...rest}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15ZM11.1018 4.89826C11.3947 5.19115 11.3947 5.66603 11.1018 5.95892L9.06068 8.00002L11.1018 10.0411C11.3947 10.334 11.3947 10.8089 11.1018 11.1018C10.8089 11.3947 10.334 11.3947 10.0411 11.1018L8.00002 9.06068L5.95892 11.1018C5.66603 11.3947 5.19115 11.3947 4.89826 11.1018C4.60537 10.8089 4.60537 10.334 4.89826 10.0411L6.93936 8.00002L4.89826 5.95892C4.60537 5.66603 4.60537 5.19115 4.89826 4.89826C5.19115 4.60537 5.66603 4.60537 5.95892 4.89826L8.00002 6.93936L10.0411 4.89826C10.334 4.60537 10.8089 4.60537 11.1018 4.89826Z"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
35
apps/space/components/icons/state/completed-group-icon.tsx
Normal file
35
apps/space/components/icons/state/completed-group-icon.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import type { ISvgIcons } from "../type";
|
||||
|
||||
export function CompletedGroupIcon({
|
||||
className = "",
|
||||
color = "#46A758",
|
||||
height = "20",
|
||||
width = "20",
|
||||
...rest
|
||||
}: ISvgIcons) {
|
||||
return (
|
||||
<svg
|
||||
height={height}
|
||||
width={width}
|
||||
className={className}
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...rest}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15ZM11.3587 6.18828C11.6007 5.85214 11.5244 5.38343 11.1882 5.14141C10.8521 4.89938 10.3834 4.97568 10.1414 5.31183L7.03706 9.62335L5.25956 7.97751C4.95563 7.69609 4.4811 7.71434 4.19968 8.01828C3.91826 8.32221 3.93651 8.79673 4.24045 9.07815L6.64045 11.3004C6.79816 11.4464 7.01095 11.5178 7.22481 11.4963C7.43868 11.4749 7.63307 11.3627 7.75865 11.1883L11.3587 6.18828Z"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
46
apps/space/components/icons/state/dashed-circle.tsx
Normal file
46
apps/space/components/icons/state/dashed-circle.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
interface DashedCircleProps {
|
||||
center: number;
|
||||
radius: number;
|
||||
color: string;
|
||||
percentage: number;
|
||||
totalSegments?: number;
|
||||
}
|
||||
|
||||
export function DashedCircle({ center, color, percentage, totalSegments = 15 }: DashedCircleProps) {
|
||||
// Ensure percentage is between 0 and 100
|
||||
const validPercentage = Math.max(0, Math.min(100, percentage));
|
||||
|
||||
// Generate dashed segments for the circle
|
||||
const generateDashedCircle = () => {
|
||||
const segments = [];
|
||||
const angleIncrement = 360 / totalSegments;
|
||||
|
||||
for (let i = 0; i < totalSegments; i++) {
|
||||
// Calculate the angle for this segment (starting from top/12 o'clock position)
|
||||
const angle = i * angleIncrement - 90; // -90 adjusts to start from top center
|
||||
|
||||
// Calculate if this segment should be hidden based on percentage
|
||||
const segmentStartPercentage = (i / totalSegments) * 100;
|
||||
const isSegmentVisible = segmentStartPercentage >= validPercentage;
|
||||
|
||||
if (isSegmentVisible) {
|
||||
segments.push(
|
||||
<g key={i} transform={`translate(${center} ${center}) rotate(${angle})`}>
|
||||
<line x1={5.75} y1="0" x2={6.5} y2="0" stroke={color} strokeWidth={1.21} strokeLinecap="round" />
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
return segments;
|
||||
};
|
||||
|
||||
return <g>{generateDashedCircle()}</g>;
|
||||
}
|
||||
48
apps/space/components/icons/state/helper.tsx
Normal file
48
apps/space/components/icons/state/helper.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import type { TIntakeStateGroups } from "@plane/types";
|
||||
|
||||
export interface IStateGroupIcon {
|
||||
className?: string;
|
||||
color?: string;
|
||||
stateGroup: TStateGroups;
|
||||
size?: EIconSize;
|
||||
percentage?: number;
|
||||
}
|
||||
|
||||
export interface IIntakeStateGroupIcon {
|
||||
className?: string;
|
||||
color?: string;
|
||||
stateGroup: TIntakeStateGroups;
|
||||
size?: EIconSize;
|
||||
percentage?: number;
|
||||
}
|
||||
|
||||
export type TStateGroups = "backlog" | "unstarted" | "started" | "completed" | "cancelled";
|
||||
|
||||
export const STATE_GROUP_COLORS: {
|
||||
[key in TStateGroups]: string;
|
||||
} = {
|
||||
backlog: "#60646C",
|
||||
unstarted: "#60646C",
|
||||
started: "#F59E0B",
|
||||
completed: "#46A758",
|
||||
cancelled: "#9AA4BC",
|
||||
};
|
||||
|
||||
export const INTAKE_STATE_GROUP_COLORS: { [key in TIntakeStateGroups]: string } = { triage: "#4E5355" };
|
||||
|
||||
export const STATE_GROUP_SIZES: {
|
||||
[key in EIconSize]: string;
|
||||
} = {
|
||||
[EIconSize.XS]: "10px",
|
||||
[EIconSize.SM]: "12px",
|
||||
[EIconSize.MD]: "14px",
|
||||
[EIconSize.LG]: "16px",
|
||||
[EIconSize.XL]: "18px",
|
||||
};
|
||||
33
apps/space/components/icons/state/progress-circle.tsx
Normal file
33
apps/space/components/icons/state/progress-circle.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
interface ProgressCircleProps {
|
||||
center: number;
|
||||
radius: number;
|
||||
color: string;
|
||||
strokeWidth: number;
|
||||
circumference: number;
|
||||
dashOffset: number;
|
||||
}
|
||||
|
||||
export function ProgressCircle({ center, radius, color, strokeWidth, circumference, dashOffset }: ProgressCircleProps) {
|
||||
return (
|
||||
<circle
|
||||
cx={center}
|
||||
cy={center}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={dashOffset}
|
||||
strokeLinecap="round"
|
||||
transform={`rotate(-90 ${center} ${center})`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
71
apps/space/components/icons/state/started-group-icon.tsx
Normal file
71
apps/space/components/icons/state/started-group-icon.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import type { ISvgIcons } from "../type";
|
||||
import { DashedCircle } from "./dashed-circle";
|
||||
import { ProgressCircle } from "./progress-circle";
|
||||
|
||||
// StateIcon component implementation
|
||||
export function StartedGroupIcon({
|
||||
width = "20",
|
||||
height = "20",
|
||||
className,
|
||||
color = "#F59E0B",
|
||||
percentage = 100,
|
||||
}: ISvgIcons) {
|
||||
// Ensure percentage is between 0 and 100
|
||||
const normalized =
|
||||
typeof percentage === "number"
|
||||
? percentage <= 1
|
||||
? percentage * 100 // treat 0-1 as fraction
|
||||
: percentage // already 0-100
|
||||
: 100; // fallback
|
||||
const validPercentage = Math.max(0, Math.min(100, normalized));
|
||||
|
||||
// SVG parameters
|
||||
const viewBoxSize = 16;
|
||||
const center = viewBoxSize / 2;
|
||||
const radius = 6;
|
||||
const strokeWidth = 1.5;
|
||||
|
||||
// Calculate the circumference of the circle
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
const dashOffset = circumference * (1 - validPercentage / 100);
|
||||
const dashOffsetSmall = circumference * (1 - 100 / 100);
|
||||
|
||||
return (
|
||||
<svg width={width} height={height} viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`} className={className}>
|
||||
{/* Dashed background circle with segments that disappear with progress */}
|
||||
<DashedCircle center={center} radius={radius} color={color} percentage={validPercentage} />
|
||||
|
||||
{/* render smaller circle in the middle */}
|
||||
<circle
|
||||
cx={6}
|
||||
cy={6}
|
||||
r={3}
|
||||
strokeWidth={1.5}
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
transform={`rotate(-90 8 6)`}
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={dashOffsetSmall}
|
||||
stroke={color}
|
||||
/>
|
||||
|
||||
{/* Solid progress circle */}
|
||||
<ProgressCircle
|
||||
center={center}
|
||||
radius={radius}
|
||||
color={color}
|
||||
strokeWidth={strokeWidth}
|
||||
circumference={circumference}
|
||||
dashOffset={dashOffset}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
44
apps/space/components/icons/state/state-group-icon.tsx
Normal file
44
apps/space/components/icons/state/state-group-icon.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { BacklogGroupIcon } from "./backlog-group-icon";
|
||||
import { CancelledGroupIcon } from "./cancelled-group-icon";
|
||||
import { CompletedGroupIcon } from "./completed-group-icon";
|
||||
import type { IStateGroupIcon } from "./helper";
|
||||
import { STATE_GROUP_COLORS, STATE_GROUP_SIZES } from "./helper";
|
||||
import { StartedGroupIcon } from "./started-group-icon";
|
||||
import { UnstartedGroupIcon } from "./unstarted-group-icon";
|
||||
|
||||
const iconComponents = {
|
||||
backlog: BacklogGroupIcon,
|
||||
cancelled: CancelledGroupIcon,
|
||||
completed: CompletedGroupIcon,
|
||||
started: StartedGroupIcon,
|
||||
unstarted: UnstartedGroupIcon,
|
||||
};
|
||||
|
||||
export function StateGroupIcon({
|
||||
className = "",
|
||||
color,
|
||||
stateGroup,
|
||||
size = EIconSize.SM,
|
||||
percentage,
|
||||
}: IStateGroupIcon) {
|
||||
const StateIconComponent = iconComponents[stateGroup] || UnstartedGroupIcon;
|
||||
|
||||
return (
|
||||
<StateIconComponent
|
||||
height={STATE_GROUP_SIZES[size]}
|
||||
width={STATE_GROUP_SIZES[size]}
|
||||
color={color ?? STATE_GROUP_COLORS[stateGroup]}
|
||||
className={`flex-shrink-0 ${className}`}
|
||||
percentage={percentage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
57
apps/space/components/icons/state/unstarted-group-icon.tsx
Normal file
57
apps/space/components/icons/state/unstarted-group-icon.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import type { ISvgIcons } from "../type";
|
||||
import { DashedCircle } from "./dashed-circle";
|
||||
import { ProgressCircle } from "./progress-circle";
|
||||
|
||||
// StateIcon component implementation
|
||||
export function UnstartedGroupIcon({
|
||||
width = "20",
|
||||
height = "20",
|
||||
className,
|
||||
color = "#F59E0B",
|
||||
percentage = 100,
|
||||
}: ISvgIcons) {
|
||||
// Ensure percentage is between 0 and 100
|
||||
const normalized =
|
||||
typeof percentage === "number"
|
||||
? percentage <= 1
|
||||
? percentage * 100 // treat 0-1 as fraction
|
||||
: percentage // already 0-100
|
||||
: 100; // fallback
|
||||
const validPercentage = Math.max(0, Math.min(100, normalized));
|
||||
|
||||
// SVG parameters
|
||||
const viewBoxSize = 16;
|
||||
const center = viewBoxSize / 2;
|
||||
const radius = 6;
|
||||
const strokeWidth = 1.5;
|
||||
|
||||
// Calculate the circumference of the circle
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
|
||||
// Calculate the dash offset based on percentage
|
||||
const dashOffset = circumference * (1 - validPercentage / 100);
|
||||
|
||||
return (
|
||||
<svg width={width} height={height} viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`} className={className}>
|
||||
<DashedCircle center={center} radius={radius} color={color} percentage={validPercentage} />
|
||||
|
||||
{/* Solid progress circle */}
|
||||
<ProgressCircle
|
||||
center={center}
|
||||
radius={radius}
|
||||
color={color}
|
||||
strokeWidth={strokeWidth}
|
||||
circumference={circumference}
|
||||
dashOffset={dashOffset}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
10
apps/space/components/icons/type.ts
Normal file
10
apps/space/components/icons/type.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
export interface ISvgIcons extends React.SVGAttributes<SVGElement> {
|
||||
className?: string | undefined;
|
||||
percentage?: number;
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
// assets
|
||||
import InstanceFailureDarkImage from "@/app/assets/instance/instance-failure-dark.svg?url";
|
||||
import InstanceFailureImage from "@/app/assets/instance/instance-failure.svg?url";
|
||||
@@ -31,9 +31,7 @@ export function InstanceFailureView() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Button size="lg" onClick={handleRetry}>
|
||||
Retry
|
||||
</Button>
|
||||
<Button variant="primary" size="lg" stretch="auto" onClick={handleRetry} label="Retry" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { CloseIcon } from "@plane/propel/icons";
|
||||
import { CloseOutline } from "@makeplane/propel/icons";
|
||||
// types
|
||||
import type { TFilters } from "@/types/issue";
|
||||
// components
|
||||
@@ -59,7 +59,7 @@ export const AppliedFiltersList = observer(function AppliedFiltersList(props: Pr
|
||||
className="grid place-items-center text-tertiary hover:text-secondary"
|
||||
onClick={() => handleRemoveFilter(filterKey, null)}
|
||||
>
|
||||
<CloseIcon height={12} width={12} strokeWidth={2} />
|
||||
<CloseOutline height={12} width={12} strokeWidth={2} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,7 +71,7 @@ export const AppliedFiltersList = observer(function AppliedFiltersList(props: Pr
|
||||
className="flex items-center gap-2 rounded-md border border-subtle px-2 py-1 text-11 text-tertiary hover:text-secondary"
|
||||
>
|
||||
{t("common.clear_all")}
|
||||
<CloseIcon height={12} width={12} strokeWidth={2} />
|
||||
<CloseOutline height={12} width={12} strokeWidth={2} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { CloseIcon } from "@plane/propel/icons";
|
||||
import { CloseOutline } from "@makeplane/propel/icons";
|
||||
// types
|
||||
import type { IIssueLabel } from "@/types/issue";
|
||||
|
||||
@@ -38,7 +38,7 @@ export function AppliedLabelsFilters(props: Props) {
|
||||
className="grid place-items-center text-tertiary hover:text-secondary"
|
||||
onClick={() => handleRemove(labelId)}
|
||||
>
|
||||
<CloseIcon height={10} width={10} strokeWidth={2} />
|
||||
<CloseOutline height={10} width={10} strokeWidth={2} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { CloseIcon, PriorityIcon } from "@plane/propel/icons";
|
||||
import type { TIssuePriorities } from "@plane/propel/icons";
|
||||
import { CloseOutline } from "@makeplane/propel/icons";
|
||||
import { PriorityIcon } from "@/components/icons";
|
||||
import type { TIssuePriorities } from "@/components/icons";
|
||||
|
||||
type Props = {
|
||||
handleRemove: (val: string) => void;
|
||||
@@ -26,7 +27,7 @@ export function AppliedPriorityFilters(props: Props) {
|
||||
className="grid place-items-center text-tertiary hover:text-secondary"
|
||||
onClick={() => handleRemove(priority)}
|
||||
>
|
||||
<CloseIcon height={10} width={10} strokeWidth={2} />
|
||||
<CloseOutline height={10} width={10} strokeWidth={2} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { CloseIcon, StateGroupIcon } from "@plane/propel/icons";
|
||||
import { CloseOutline } from "@makeplane/propel/icons";
|
||||
// components
|
||||
import { StateGroupIcon } from "@/components/icons";
|
||||
// hooks
|
||||
import { useStates } from "@/hooks/store/use-state";
|
||||
|
||||
@@ -37,7 +39,7 @@ export const AppliedStateFilters = observer(function AppliedStateFilters(props:
|
||||
className="grid place-items-center text-tertiary hover:text-secondary"
|
||||
onClick={() => handleRemove(stateId)}
|
||||
>
|
||||
<CloseIcon height={10} width={10} strokeWidth={2} />
|
||||
<CloseOutline height={10} width={10} strokeWidth={2} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { Placement } from "@popperjs/core";
|
||||
import { usePopper } from "react-popper";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
@@ -35,11 +35,7 @@ export function FiltersDropdown(props: Props) {
|
||||
return (
|
||||
<>
|
||||
<Popover.Button as={React.Fragment}>
|
||||
<Button ref={setReferenceElement} variant="secondary">
|
||||
<div className={`${open ? "text-primary" : "text-secondary"}`}>
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
</Button>
|
||||
<Button ref={setReferenceElement} variant="secondary" size="md" stretch="auto" label={title} />
|
||||
</Popover.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import React from "react";
|
||||
// icons
|
||||
import { ChevronDownIcon, ChevronUpIcon } from "@plane/propel/icons";
|
||||
import { ChevronDownOutline, ChevronUpOutline } from "@makeplane/propel/icons";
|
||||
|
||||
interface IFilterHeader {
|
||||
title: string;
|
||||
@@ -23,7 +23,7 @@ export function FilterHeader({ title, isPreviewEnabled, handleIsPreviewEnabled }
|
||||
className="grid h-5 w-5 shrink-0 place-items-center rounded-sm hover:bg-layer-transparent-hover"
|
||||
onClick={handleIsPreviewEnabled}
|
||||
>
|
||||
{isPreviewEnabled ? <ChevronUpIcon height={14} width={14} /> : <ChevronDownIcon height={14} width={14} />}
|
||||
{isPreviewEnabled ? <ChevronUpOutline height={14} width={14} /> : <ChevronDownOutline height={14} width={14} />}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import React from "react";
|
||||
// plane imports
|
||||
import { CheckIcon } from "@plane/propel/icons";
|
||||
import { TickOutline } from "@makeplane/propel/icons";
|
||||
|
||||
type Props = {
|
||||
icon?: React.ReactNode;
|
||||
@@ -30,7 +30,7 @@ export function FilterOption(props: Props) {
|
||||
isChecked ? "border-accent-strong bg-accent-primary text-on-color" : "border-strong"
|
||||
} ${multiple ? "rounded-xs" : "rounded-full"}`}
|
||||
>
|
||||
{isChecked && <CheckIcon width={10} height={10} strokeWidth={3} />}
|
||||
{isChecked && <TickOutline width={10} height={10} strokeWidth={3} />}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 truncate">
|
||||
{icon && <div className="grid w-5 shrink-0 place-items-center">{icon}</div>}
|
||||
|
||||
@@ -9,7 +9,8 @@ import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { ISSUE_PRIORITY_FILTERS } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { PriorityIcon } from "@plane/propel/icons";
|
||||
// components
|
||||
import { PriorityIcon } from "@/components/icons";
|
||||
// local imports
|
||||
import { FilterHeader } from "./helpers/filter-header";
|
||||
import { FilterOption } from "./helpers/filter-option";
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { SearchIcon, CloseIcon } from "@plane/propel/icons";
|
||||
import { SearchOutline, CloseOutline } from "@makeplane/propel/icons";
|
||||
// types
|
||||
import type { IIssueFilterOptions, TIssueFilterKeys } from "@/types/issue";
|
||||
// local imports
|
||||
@@ -30,7 +30,7 @@ export const FilterSelection = observer(function FilterSelection(props: Props) {
|
||||
<div className="flex h-full w-full flex-col overflow-hidden">
|
||||
<div className="p-2.5 pb-0">
|
||||
<div className="flex items-center gap-1.5 rounded-sm border-[0.5px] border-subtle bg-surface-2 px-1.5 py-1 text-11">
|
||||
<SearchIcon className="text-placeholder" width={12} height={12} strokeWidth={2} />
|
||||
<SearchOutline className="text-placeholder" width={12} height={12} strokeWidth={2} />
|
||||
<input
|
||||
type="text"
|
||||
className="w-full bg-surface-2 outline-none placeholder:text-placeholder"
|
||||
@@ -41,7 +41,7 @@ export const FilterSelection = observer(function FilterSelection(props: Props) {
|
||||
/>
|
||||
{filtersSearchQuery !== "" && (
|
||||
<button type="button" className="grid place-items-center" onClick={() => setFiltersSearchQuery("")}>
|
||||
<CloseIcon className="text-tertiary" height={12} width={12} strokeWidth={2} />
|
||||
<CloseOutline className="text-tertiary" height={12} width={12} strokeWidth={2} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,9 @@ import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// ui
|
||||
import { EIconSize } from "@plane/constants";
|
||||
import { StateGroupIcon } from "@plane/propel/icons";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { StateGroupIcon } from "@/components/icons";
|
||||
// hooks
|
||||
import { useStates } from "@/hooks/store/use-state";
|
||||
// local imports
|
||||
|
||||
@@ -9,7 +9,7 @@ import { observer } from "mobx-react";
|
||||
import { Link } from "react-router";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
// plane types
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import type { IIssueDisplayProperties } from "@plane/types";
|
||||
// plane ui
|
||||
// plane utils
|
||||
@@ -57,7 +57,7 @@ const KanbanIssueDetailsBlock = observer(function KanbanIssueDetailsBlock(props:
|
||||
</WithDisplayPropertiesHOC>
|
||||
|
||||
<div className="mb-1.5 line-clamp-1 w-full text-13 text-primary">
|
||||
<Tooltip tooltipContent={issue.name}>
|
||||
<Tooltip label={issue.name}>
|
||||
<span>{issue.name}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { Circle } from "lucide-react";
|
||||
import { ChevronDownIcon, ChevronUpIcon } from "@plane/propel/icons";
|
||||
import { ChevronDownOutline, ChevronUpOutline } from "@makeplane/propel/icons";
|
||||
// mobx
|
||||
|
||||
interface IHeaderSubGroupByCard {
|
||||
@@ -25,7 +25,11 @@ export const HeaderSubGroupByCard = observer(function HeaderSubGroupByCard(props
|
||||
onClick={() => toggleExpanded()}
|
||||
>
|
||||
<div className="flex h-[20px] w-[20px] flex-shrink-0 items-center justify-center overflow-hidden rounded-xs transition-all hover:bg-layer-1">
|
||||
{isExpanded ? <ChevronUpIcon width={14} strokeWidth={2} /> : <ChevronDownIcon width={14} strokeWidth={2} />}
|
||||
{isExpanded ? (
|
||||
<ChevronUpOutline width={14} strokeWidth={2} />
|
||||
) : (
|
||||
<ChevronDownOutline width={14} strokeWidth={2} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex h-[20px] w-[20px] flex-shrink-0 items-center justify-center overflow-hidden rounded-xs">
|
||||
|
||||
@@ -9,7 +9,7 @@ import { observer } from "mobx-react";
|
||||
import { Link } from "react-router";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
// plane types
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import type { IIssueDisplayProperties } from "@plane/types";
|
||||
// plane ui
|
||||
// plane utils
|
||||
@@ -79,7 +79,7 @@ export const IssueBlock = observer(function IssueBlock(props: IssueBlockProps) {
|
||||
onClick={handleIssuePeekOverview}
|
||||
className="w-full cursor-pointer truncate text-13 text-primary"
|
||||
>
|
||||
<Tooltip tooltipContent={issue.name} position="top-start">
|
||||
<Tooltip label={issue.name} side="top" align="start">
|
||||
<p className="truncate">{issue.name}</p>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { Paperclip } from "lucide-react";
|
||||
import { LinkIcon, ViewsIcon } from "@plane/propel/icons";
|
||||
import { LinkOutline, ViewsOutline } from "@makeplane/propel/icons";
|
||||
// plane imports
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import type { IIssueDisplayProperties } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
@@ -138,7 +138,7 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
|
||||
displayPropertyKey="sub_issue_count"
|
||||
shouldRenderProperty={(properties) => !!properties.sub_issue_count && !!issue.sub_issues_count}
|
||||
>
|
||||
<Tooltip tooltipHeading="Sub-work items" tooltipContent={`${issue.sub_issues_count}`}>
|
||||
<Tooltip label={`Sub-work items: ${issue.sub_issues_count}`}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded-sm border-[0.5px] border-strong px-2.5 py-1",
|
||||
@@ -147,7 +147,7 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
|
||||
}
|
||||
)}
|
||||
>
|
||||
<ViewsIcon className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
|
||||
<ViewsOutline className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
|
||||
<div className="text-11">{issue.sub_issues_count}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -159,7 +159,7 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
|
||||
displayPropertyKey="attachment_count"
|
||||
shouldRenderProperty={(properties) => !!properties.attachment_count && !!issue.attachment_count}
|
||||
>
|
||||
<Tooltip tooltipHeading="Attachments" tooltipContent={`${issue.attachment_count}`}>
|
||||
<Tooltip label={`Attachments: ${issue.attachment_count}`}>
|
||||
<div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded-sm border-[0.5px] border-strong px-2.5 py-1">
|
||||
<Paperclip className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
|
||||
<div className="text-11">{issue.attachment_count}</div>
|
||||
@@ -173,9 +173,9 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
|
||||
displayPropertyKey="link"
|
||||
shouldRenderProperty={(properties) => !!properties.link && !!issue.link_count}
|
||||
>
|
||||
<Tooltip tooltipHeading="Links" tooltipContent={`${issue.link_count}`}>
|
||||
<Tooltip label={`Links: ${issue.link_count}`}>
|
||||
<div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded-sm border-[0.5px] border-strong px-2.5 py-1">
|
||||
<LinkIcon className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
|
||||
<LinkOutline className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
|
||||
<div className="text-11">{issue.link_count}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// plane ui
|
||||
import { CycleIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { CyclesOutline } from "@makeplane/propel/icons";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
// plane utils
|
||||
import { cn } from "@plane/utils";
|
||||
//hooks
|
||||
@@ -24,7 +24,7 @@ export const IssueBlockCycle = observer(function IssueBlockCycle({ cycleId, shou
|
||||
const cycle = getCycleById(cycleId);
|
||||
|
||||
return (
|
||||
<Tooltip tooltipHeading="Cycle" tooltipContent={cycle?.name ?? "No Cycle"}>
|
||||
<Tooltip label={`Cycle: ${cycle?.name ?? "No Cycle"}`}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-full w-full items-center justify-between gap-1 rounded-sm px-2.5 py-1 text-11 duration-300 focus:outline-none",
|
||||
@@ -32,7 +32,7 @@ export const IssueBlockCycle = observer(function IssueBlockCycle({ cycleId, shou
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full items-center gap-1.5 text-11">
|
||||
<CycleIcon className="h-3 w-3 flex-shrink-0" />
|
||||
<CyclesOutline className="h-3 w-3 flex-shrink-0" />
|
||||
<div className="max-w-40 truncate">{cycle?.name ?? "No Cycle"}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { DueDatePropertyIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { DueDateOutline } from "@makeplane/propel/icons";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import { cn } from "@plane/utils";
|
||||
// helpers
|
||||
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
||||
@@ -30,14 +30,14 @@ export const IssueBlockDate = observer(function IssueBlockDate(props: Props) {
|
||||
const formattedDate = renderFormattedDate(due_date);
|
||||
|
||||
return (
|
||||
<Tooltip tooltipHeading="Due Date" tooltipContent={formattedDate}>
|
||||
<Tooltip label={`Due Date: ${formattedDate ?? "None"}`}>
|
||||
<div
|
||||
className={cn("flex h-full items-center gap-1 rounded-sm px-2.5 py-1 text-11 text-primary", {
|
||||
"text-danger-primary": shouldHighLight && due_date && shouldHighlightIssueDueDate(due_date, state?.group),
|
||||
"border-[0.5px] border-strong": shouldShowBorder,
|
||||
})}
|
||||
>
|
||||
<DueDatePropertyIcon className="size-3 flex-shrink-0" />
|
||||
<DueDateOutline className="size-3 flex-shrink-0" />
|
||||
{formattedDate ? formattedDate : "No Date"}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { LabelPropertyIcon } from "@plane/propel/icons";
|
||||
import { LabelsOutline } from "@makeplane/propel/icons";
|
||||
// plane imports
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
// hooks
|
||||
import { useLabel } from "@/hooks/store/use-label";
|
||||
|
||||
@@ -25,11 +25,11 @@ export const IssueBlockLabels = observer(function IssueBlockLabels({ labelIds, s
|
||||
|
||||
if (labels.length <= 0)
|
||||
return (
|
||||
<Tooltip position="top" tooltipHeading="Labels" tooltipContent="None">
|
||||
<Tooltip label="Labels: None">
|
||||
<div
|
||||
className={`flex h-full items-center justify-center gap-2 rounded-sm border-[0.5px] border-strong px-2.5 py-1 text-11`}
|
||||
>
|
||||
<LabelPropertyIcon className="h-3.5 w-3.5" strokeWidth={2} />
|
||||
<LabelsOutline className="h-3.5 w-3.5" strokeWidth={2} />
|
||||
{shouldShowLabel && <span>No Labels</span>}
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -40,7 +40,7 @@ export const IssueBlockLabels = observer(function IssueBlockLabels({ labelIds, s
|
||||
{labels.length <= 2 ? (
|
||||
<>
|
||||
{labels.map((label) => (
|
||||
<Tooltip key={label.id} position="top" tooltipHeading="Labels" tooltipContent={label?.name ?? ""}>
|
||||
<Tooltip key={label.id} label={`Labels: ${label?.name ?? ""}`}>
|
||||
<div
|
||||
key={label?.id}
|
||||
className={`flex h-full max-w-full flex-shrink-0 items-center overflow-hidden rounded-sm border-[0.5px] border-strong px-2.5 py-1 text-11`}
|
||||
@@ -62,7 +62,7 @@ export const IssueBlockLabels = observer(function IssueBlockLabels({ labelIds, s
|
||||
<div
|
||||
className={`cursor-not-allowed" flex h-full flex-shrink-0 items-center rounded-sm border-[0.5px] border-strong px-2.5 py-1 text-11`}
|
||||
>
|
||||
<Tooltip position="top" tooltipHeading="Labels" tooltipContent={labelsString}>
|
||||
<Tooltip label={`Labels: ${labelsString}`}>
|
||||
<div className="flex h-full items-center gap-1.5 text-secondary">
|
||||
<span className="h-2 w-2 flex-shrink-0 rounded-full bg-accent-primary" />
|
||||
{`${labels.length} Labels`}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { observer } from "mobx-react";
|
||||
// icons
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { MembersPropertyIcon } from "@plane/propel/icons";
|
||||
import { MembersOutline } from "@makeplane/propel/icons";
|
||||
// plane ui
|
||||
import { Avatar, AvatarGroup } from "@plane/ui";
|
||||
// plane utils
|
||||
@@ -56,7 +56,7 @@ export const ButtonAvatars = observer(function ButtonAvatars(props: AvatarProps)
|
||||
return Icon ? (
|
||||
<Icon className="h-3 w-3 flex-shrink-0" />
|
||||
) : (
|
||||
<MembersPropertyIcon className="mx-[4px] h-3 w-3 flex-shrink-0" />
|
||||
<MembersOutline className="mx-[4px] h-3 w-3 flex-shrink-0" />
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// plane ui
|
||||
import { ModuleIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { ModuleOutline } from "@makeplane/propel/icons";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
// plane utils
|
||||
import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
@@ -27,7 +27,7 @@ export const IssueBlockModules = observer(function IssueBlockModules({ moduleIds
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full flex-wrap items-center gap-1">
|
||||
<Tooltip tooltipHeading="Modules" tooltipContent={modulesString}>
|
||||
<Tooltip label={`Modules: ${modulesString}`}>
|
||||
{modules.length <= 1 ? (
|
||||
<div
|
||||
key={modules?.[0]?.id}
|
||||
@@ -36,7 +36,7 @@ export const IssueBlockModules = observer(function IssueBlockModules({ moduleIds
|
||||
})}
|
||||
>
|
||||
<div className="flex items-center gap-1.5 text-secondary">
|
||||
<ModuleIcon className="h-3 w-3 flex-shrink-0" />
|
||||
<ModuleOutline className="h-3 w-3 flex-shrink-0" />
|
||||
<div className="text-11">{modules?.[0]?.name ?? "No Modules"}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
import { SignalHigh } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// types
|
||||
import { PriorityIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import type { TIssuePriorities } from "@plane/types";
|
||||
// constants
|
||||
import { cn, getIssuePriorityFilters } from "@plane/utils";
|
||||
// components
|
||||
import { PriorityIcon } from "@/components/icons";
|
||||
|
||||
export function IssueBlockPriority({
|
||||
priority,
|
||||
@@ -35,7 +36,7 @@ export function IssueBlockPriority({
|
||||
if (priority_detail === null) return <></>;
|
||||
|
||||
return (
|
||||
<Tooltip tooltipHeading="Priority" tooltipContent={t(priority_detail?.titleTranslationKey || "")}>
|
||||
<Tooltip label={`Priority: ${t(priority_detail?.titleTranslationKey || "")}`}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-full items-center gap-1.5 rounded-sm border-[0.5px] px-2 py-0.5 text-11",
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
// plane ui
|
||||
import { StateGroupIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import type { TStateGroups } from "@plane/types";
|
||||
// plane utils
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { StateGroupIcon } from "@/components/icons";
|
||||
//hooks
|
||||
import { useStates } from "@/hooks/store/use-state";
|
||||
|
||||
@@ -37,7 +38,7 @@ export const IssueBlockState = observer(function IssueBlockState(props: Props) {
|
||||
if (!state) return null;
|
||||
|
||||
return (
|
||||
<Tooltip tooltipHeading="State" tooltipContent={state.name}>
|
||||
<Tooltip label={`State: ${state.name}`}>
|
||||
<div
|
||||
className={cn("flex h-full w-full items-center justify-between gap-1 rounded-sm px-2.5 py-1 text-11", {
|
||||
"border-[0.5px] border-strong": shouldShowBorder,
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
import { isNil } from "lodash-es";
|
||||
// types
|
||||
import { EIconSize, ISSUE_PRIORITIES } from "@plane/constants";
|
||||
import { CycleGroupIcon, CycleIcon, ModuleIcon, PriorityIcon, StateGroupIcon } from "@plane/propel/icons";
|
||||
import { CyclesOutline, ModuleOutline } from "@makeplane/propel/icons";
|
||||
import { CycleGroupIcon, PriorityIcon, StateGroupIcon } from "@/components/icons";
|
||||
import type {
|
||||
GroupByColumnTypes,
|
||||
IGroupByColumn,
|
||||
@@ -79,7 +80,7 @@ const getCycleColumns = (cycleStore: ICycleStore): IGroupByColumn[] | undefined
|
||||
cycleGroups.push({
|
||||
id: "None",
|
||||
name: "None",
|
||||
icon: <CycleIcon className="h-3.5 w-3.5" />,
|
||||
icon: <CyclesOutline className="h-3.5 w-3.5" />,
|
||||
payload: { cycle_id: null },
|
||||
});
|
||||
|
||||
@@ -98,14 +99,14 @@ const getModuleColumns = (moduleStore: IIssueModuleStore): IGroupByColumn[] | un
|
||||
moduleGroups.push({
|
||||
id: moduleInfo.id,
|
||||
name: moduleInfo.name,
|
||||
icon: <ModuleIcon className="h-3.5 w-3.5" />,
|
||||
icon: <ModuleOutline className="h-3.5 w-3.5" />,
|
||||
payload: { module_ids: [moduleInfo.id] },
|
||||
});
|
||||
}) as any;
|
||||
moduleGroups.push({
|
||||
id: "None",
|
||||
name: "None",
|
||||
icon: <ModuleIcon className="h-3.5 w-3.5" />,
|
||||
icon: <ModuleOutline className="h-3.5 w-3.5" />,
|
||||
payload: { module_ids: [] },
|
||||
});
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
|
||||
import type { TIssueLayout } from "@plane/constants";
|
||||
import { ListLayoutIcon, BoardLayoutIcon } from "@plane/propel/icons";
|
||||
import type { ISvgIcons } from "@plane/propel/icons";
|
||||
import { ListOutline, BoardOutline } from "@makeplane/propel/icons";
|
||||
import type { ISvgIcons } from "@/components/icons";
|
||||
|
||||
export function IssueLayoutIcon({
|
||||
layout,
|
||||
@@ -20,9 +20,9 @@ export function IssueLayoutIcon({
|
||||
|
||||
switch (layout) {
|
||||
case "list":
|
||||
return <ListLayoutIcon {...iconProps} />;
|
||||
return <ListOutline {...iconProps} />;
|
||||
case "kanban":
|
||||
return <BoardLayoutIcon {...iconProps} />;
|
||||
return <BoardOutline {...iconProps} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { SITES_ISSUE_LAYOUTS } from "@plane/constants";
|
||||
// plane i18n
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
// helpers
|
||||
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
||||
// hooks
|
||||
@@ -53,7 +53,7 @@ export const IssuesLayoutSelection = observer(function IssuesLayoutSelection(pro
|
||||
if (!layoutOptions[layout.key]) return;
|
||||
|
||||
return (
|
||||
<Tooltip key={layout.key} tooltipContent={t(layout.titleTranslationKey)}>
|
||||
<Tooltip key={layout.key} label={t(layout.titleTranslationKey)}>
|
||||
<button
|
||||
type="button"
|
||||
className={`group grid h-[22px] w-7 place-items-center overflow-hidden rounded-sm bg-layer-transparent transition-all hover:bg-layer-transparent-hover ${
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { ProjectIcon } from "@plane/propel/icons";
|
||||
import { ProjectsOutline } from "@makeplane/propel/icons";
|
||||
// components
|
||||
import { ProjectLogo } from "@/components/common/project-logo";
|
||||
// store
|
||||
@@ -32,7 +32,7 @@ export const IssuesNavbarRoot = observer(function IssuesNavbarRoot(props: Props)
|
||||
</span>
|
||||
) : (
|
||||
<span className="grid size-7 shrink-0 place-items-center rounded-sm uppercase">
|
||||
<ProjectIcon className="size-4" />
|
||||
<ProjectsOutline className="size-4" />
|
||||
</span>
|
||||
)}
|
||||
<div className="line-clamp-1 max-w-[300px] overflow-hidden text-16 font-medium">
|
||||
|
||||
@@ -13,7 +13,7 @@ import { LogOut } from "lucide-react";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { API_BASE_URL } from "@plane/constants";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
import { AuthService } from "@plane/services";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { getFileURL } from "@plane/utils";
|
||||
@@ -119,7 +119,7 @@ export const UserAvatar = observer(function UserAvatar() {
|
||||
) : (
|
||||
<div className="flex-shrink-0">
|
||||
<Link to={`/?next_path=${pathName}?${queryParam}`}>
|
||||
<Button variant="secondary">Sign in</Button>
|
||||
<Button variant="secondary" size="md" stretch="auto" label="Sign in" />
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { observer } from "mobx-react";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
// plane imports
|
||||
import type { EditorRefApi } from "@plane/editor";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { TOAST_TYPE, setToast } from "@/providers/toast";
|
||||
import { SitesFileService } from "@plane/services";
|
||||
import type { TIssuePublicComment } from "@plane/types";
|
||||
// editor components
|
||||
|
||||
@@ -11,7 +11,7 @@ import { MessageSquare, MoreVertical } from "lucide-react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
// plane imports
|
||||
import type { EditorRefApi } from "@plane/editor";
|
||||
import { CheckIcon, CloseIcon } from "@plane/propel/icons";
|
||||
import { TickOutline, CloseOutline } from "@makeplane/propel/icons";
|
||||
import type { TIssuePublicComment } from "@plane/types";
|
||||
import { getFileURL } from "@plane/utils";
|
||||
// components
|
||||
@@ -140,14 +140,14 @@ export const CommentCard = observer(function CommentCard(props: Props) {
|
||||
disabled={isSubmitting}
|
||||
className="group shadow-md rounded-sm border border-success-strong bg-success-primary p-2 duration-300 hover:bg-success-primary"
|
||||
>
|
||||
<CheckIcon className="h-3 w-3 text-on-color" strokeWidth={2} />
|
||||
<TickOutline className="h-3 w-3 text-on-color" strokeWidth={2} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="group shadow-md rounded-sm border border-danger-strong bg-danger-primary p-2 duration-300 hover:bg-danger-primary-hover"
|
||||
onClick={() => setIsEditing(false)}
|
||||
>
|
||||
<CloseIcon className="h-3 w-3 text-on-color" strokeWidth={2} />
|
||||
<CloseOutline className="h-3 w-3 text-on-color" strokeWidth={2} />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -8,9 +8,8 @@ import React, { useMemo, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { stringToEmoji } from "@plane/propel/emoji-icon-picker";
|
||||
import { EmojiReactionGroup, EmojiReactionPicker } from "@plane/propel/emoji-reaction";
|
||||
import type { EmojiReactionType } from "@plane/propel/emoji-reaction";
|
||||
import { stringToEmoji, EmojiReactionGroup, EmojiReactionPicker } from "@/components/emoji";
|
||||
import type { EmojiReactionType } from "@/components/emoji";
|
||||
// helpers
|
||||
import { groupReactions } from "@/helpers/emoji.helper";
|
||||
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
||||
|
||||
@@ -9,8 +9,8 @@ import { observer } from "mobx-react";
|
||||
import { MoveRight } from "lucide-react";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { LinkIcon, CenterPanelIcon, FullScreenPanelIcon, SidePanelIcon } from "@plane/propel/icons";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { LinkOutline, ModalPeekOutline, FullScreenPeekOutline, RightSidePaneOutline } from "@makeplane/propel/icons";
|
||||
import { TOAST_TYPE, setToast } from "@/providers/toast";
|
||||
// helpers
|
||||
import { copyTextToClipboard } from "@/helpers/string.helper";
|
||||
// hooks
|
||||
@@ -29,15 +29,15 @@ const PEEK_MODES: {
|
||||
icon: any;
|
||||
label: string;
|
||||
}[] = [
|
||||
{ key: "side", icon: SidePanelIcon, label: "Side Peek" },
|
||||
{ key: "side", icon: RightSidePaneOutline, label: "Side Peek" },
|
||||
{
|
||||
key: "modal",
|
||||
icon: CenterPanelIcon,
|
||||
icon: ModalPeekOutline,
|
||||
label: "Modal",
|
||||
},
|
||||
{
|
||||
key: "full",
|
||||
icon: FullScreenPanelIcon,
|
||||
icon: FullScreenPeekOutline,
|
||||
label: "Full Screen",
|
||||
},
|
||||
];
|
||||
@@ -60,7 +60,7 @@ export const PeekOverviewHeader = observer(function PeekOverviewHeader(props: Pr
|
||||
});
|
||||
};
|
||||
|
||||
const Icon = PEEK_MODES.find((m) => m.key === peekMode)?.icon ?? SidePanelIcon;
|
||||
const Icon = PEEK_MODES.find((m) => m.key === peekMode)?.icon ?? RightSidePaneOutline;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -126,7 +126,7 @@ export const PeekOverviewHeader = observer(function PeekOverviewHeader(props: Pr
|
||||
className="shrink-0 text-tertiary hover:text-secondary focus:outline-none"
|
||||
tabIndex={1}
|
||||
>
|
||||
<LinkIcon className="h-4 w-4 -rotate-45" />
|
||||
<LinkOutline className="h-4 w-4 -rotate-45" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Link } from "react-router";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Lock } from "lucide-react";
|
||||
// plane imports
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { Button } from "@makeplane/propel/components/button";
|
||||
// components
|
||||
import { AddComment } from "@/components/issues/peek-overview/comment/add-comment";
|
||||
import { CommentCard } from "@/components/issues/peek-overview/comment/comment-detail-card";
|
||||
@@ -63,7 +63,7 @@ export const PeekOverviewIssueActivity = observer(function PeekOverviewIssueActi
|
||||
Sign in to add your comment
|
||||
</p>
|
||||
<Link to={`/?next_path=${pathname}`}>
|
||||
<Button variant="primary">Sign in</Button>
|
||||
<Button variant="primary" size="md" stretch="auto" label="Sign in" />
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -9,14 +9,9 @@ import { useParams } from "next/navigation";
|
||||
import { LinkIcon } from "lucide-react";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import {
|
||||
StatePropertyIcon,
|
||||
StateGroupIcon,
|
||||
PriorityPropertyIcon,
|
||||
DueDatePropertyIcon,
|
||||
PriorityIcon,
|
||||
} from "@plane/propel/icons";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import { StateOutline, PriorityOutline, DueDateOutline } from "@makeplane/propel/icons";
|
||||
import { StateGroupIcon, PriorityIcon } from "@/components/icons";
|
||||
import { TOAST_TYPE, setToast } from "@/providers/toast";
|
||||
import { cn, getIssuePriorityFilters } from "@plane/utils";
|
||||
// helpers
|
||||
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
||||
@@ -77,7 +72,7 @@ export const PeekOverviewIssueProperties = observer(function PeekOverviewIssuePr
|
||||
<div className={`space-y-2 ${mode === "full" ? "pt-3" : ""}`}>
|
||||
<div className="flex h-8 items-center gap-3">
|
||||
<div className="flex w-1/4 flex-shrink-0 items-center gap-1 text-13 text-tertiary">
|
||||
<StatePropertyIcon className="size-4 flex-shrink-0" />
|
||||
<StateOutline className="size-4 flex-shrink-0" />
|
||||
<span>State</span>
|
||||
</div>
|
||||
<div className="flex w-3/4 items-center gap-1.5 py-0.5 text-13">
|
||||
@@ -88,7 +83,7 @@ export const PeekOverviewIssueProperties = observer(function PeekOverviewIssuePr
|
||||
|
||||
<div className="flex h-8 items-center gap-3">
|
||||
<div className="flex w-1/4 flex-shrink-0 items-center gap-1 text-13 text-tertiary">
|
||||
<PriorityPropertyIcon className="size-4 flex-shrink-0" />
|
||||
<PriorityOutline className="size-4 flex-shrink-0" />
|
||||
<span>Priority</span>
|
||||
</div>
|
||||
<div className="w-3/4">
|
||||
@@ -113,7 +108,7 @@ export const PeekOverviewIssueProperties = observer(function PeekOverviewIssuePr
|
||||
|
||||
<div className="flex h-8 items-center gap-3">
|
||||
<div className="flex w-1/4 flex-shrink-0 items-center gap-1 text-13 text-tertiary">
|
||||
<DueDatePropertyIcon className="size-4 flex-shrink-0" />
|
||||
<DueDateOutline className="size-4 flex-shrink-0" />
|
||||
<span>Due date</span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -123,7 +118,7 @@ export const PeekOverviewIssueProperties = observer(function PeekOverviewIssuePr
|
||||
"text-danger-primary": shouldHighlightIssueDueDate(issueDetails.target_date, state?.group),
|
||||
})}
|
||||
>
|
||||
<DueDatePropertyIcon className="size-3" />
|
||||
<DueDateOutline className="size-3" />
|
||||
{renderFormattedDate(issueDetails.target_date)}
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -8,9 +8,8 @@ import { useMemo, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
// lib
|
||||
import { stringToEmoji } from "@plane/propel/emoji-icon-picker";
|
||||
import { EmojiReactionGroup, EmojiReactionPicker } from "@plane/propel/emoji-reaction";
|
||||
import type { EmojiReactionType } from "@plane/propel/emoji-reaction";
|
||||
import { stringToEmoji, EmojiReactionGroup, EmojiReactionPicker } from "@/components/emoji";
|
||||
import type { EmojiReactionType } from "@/components/emoji";
|
||||
// helpers
|
||||
import { groupReactions } from "@/helpers/emoji.helper";
|
||||
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ArrowDown, ArrowUp } from "lucide-react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
// plane imports
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { Tooltip } from "@makeplane/propel/components/tooltip";
|
||||
import { cn } from "@plane/utils";
|
||||
// helpers
|
||||
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
||||
@@ -81,20 +81,13 @@ export const IssueVotes = observer(function IssueVotes(props: TIssueVotes) {
|
||||
<div className="flex items-center gap-2">
|
||||
{/* upvote button 👇 */}
|
||||
<Tooltip
|
||||
tooltipContent={
|
||||
<div>
|
||||
{allUpVotes.length > 0 ? (
|
||||
<>
|
||||
{allUpVotes
|
||||
.map((r) => r.actor_details?.display_name)
|
||||
.splice(0, VOTES_LIMIT)
|
||||
.join(", ")}
|
||||
{allUpVotes.length > VOTES_LIMIT && " and " + (allUpVotes.length - VOTES_LIMIT) + " more"}
|
||||
</>
|
||||
) : (
|
||||
"No upvotes yet"
|
||||
)}
|
||||
</div>
|
||||
label={
|
||||
allUpVotes.length > 0
|
||||
? `${allUpVotes
|
||||
.map((r) => r.actor_details?.display_name)
|
||||
.splice(0, VOTES_LIMIT)
|
||||
.join(", ")}${allUpVotes.length > VOTES_LIMIT ? ` and ${allUpVotes.length - VOTES_LIMIT} more` : ""}`
|
||||
: "No upvotes yet"
|
||||
}
|
||||
>
|
||||
<button
|
||||
@@ -122,20 +115,15 @@ export const IssueVotes = observer(function IssueVotes(props: TIssueVotes) {
|
||||
|
||||
{/* downvote button 👇 */}
|
||||
<Tooltip
|
||||
tooltipContent={
|
||||
<div>
|
||||
{allDownVotes.length > 0 ? (
|
||||
<>
|
||||
{allDownVotes
|
||||
.map((r) => r.actor_details.display_name)
|
||||
.splice(0, VOTES_LIMIT)
|
||||
.join(", ")}
|
||||
{allDownVotes.length > VOTES_LIMIT && " and " + (allDownVotes.length - VOTES_LIMIT) + " more"}
|
||||
</>
|
||||
) : (
|
||||
"No downvotes yet"
|
||||
)}
|
||||
</div>
|
||||
label={
|
||||
allDownVotes.length > 0
|
||||
? `${allDownVotes
|
||||
.map((r) => r.actor_details.display_name)
|
||||
.splice(0, VOTES_LIMIT)
|
||||
.join(
|
||||
", "
|
||||
)}${allDownVotes.length > VOTES_LIMIT ? ` and ${allDownVotes.length - VOTES_LIMIT} more` : ""}`
|
||||
: "No downvotes yet"
|
||||
}
|
||||
>
|
||||
<button
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
import React from "react";
|
||||
import { Link } from "react-router";
|
||||
import { PlaneLockup } from "@plane/propel/icons";
|
||||
// components
|
||||
import { PlaneLockup } from "@/components/icons";
|
||||
|
||||
export function AuthHeader() {
|
||||
return (
|
||||
|
||||
@@ -10,12 +10,12 @@ import { useTheme } from "next-themes";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import { SPACE_BASE_PATH } from "@plane/constants";
|
||||
import { PlaneLockup } from "@plane/propel/icons";
|
||||
// assets
|
||||
import PlaneBackgroundPatternDark from "@/app/assets/auth/background-pattern-dark.svg?url";
|
||||
import PlaneBackgroundPattern from "@/app/assets/auth/background-pattern.svg?url";
|
||||
// components
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
import { PlaneLockup } from "@/components/icons";
|
||||
import { InstanceFailureView } from "@/components/instance/instance-failure-view";
|
||||
// hooks
|
||||
import { useInstance } from "@/hooks/store/use-instance";
|
||||
|
||||
@@ -4,18 +4,12 @@
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
// plane imports
|
||||
import { Toast } from "@plane/propel/toast";
|
||||
import { resolveGeneralTheme } from "@plane/utils";
|
||||
import { ToastWithTheme } from "@/providers/toast";
|
||||
|
||||
export function ToastProvider({ children }: { children: React.ReactNode }) {
|
||||
// themes
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toast theme={resolveGeneralTheme(resolvedTheme)} />
|
||||
<ToastWithTheme />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
"@fontsource/ibm-plex-mono": "catalog:",
|
||||
"@fontsource/material-symbols-rounded": "catalog:",
|
||||
"@headlessui/react": "catalog:",
|
||||
"@makeplane/propel": "0.2.0",
|
||||
"@plane/constants": "workspace:*",
|
||||
"@plane/editor": "workspace:*",
|
||||
"@plane/i18n": "workspace:*",
|
||||
"@plane/propel": "workspace:*",
|
||||
"@plane/services": "workspace:*",
|
||||
"@plane/types": "workspace:*",
|
||||
"@plane/ui": "workspace:*",
|
||||
@@ -36,6 +36,7 @@
|
||||
"axios": "catalog:",
|
||||
"clsx": "catalog:",
|
||||
"date-fns": "catalog:",
|
||||
"frimousse": "catalog:",
|
||||
"isbot": "catalog:",
|
||||
"lodash-es": "catalog:",
|
||||
"lucide-react": "catalog:",
|
||||
|
||||
52
apps/space/providers/toast.tsx
Normal file
52
apps/space/providers/toast.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { createToastManager, ToastProvider } from "@makeplane/propel/components/toast";
|
||||
import type { ToastVariant } from "@makeplane/propel/components/toast";
|
||||
import { IconButton } from "@makeplane/propel/components/icon-button";
|
||||
import { Icon } from "@makeplane/propel/components/icon";
|
||||
import { CloseOutline } from "@makeplane/propel/icons";
|
||||
|
||||
// Thin wrapper over @makeplane/propel's toast manager to keep the call-site API stable.
|
||||
export enum TOAST_TYPE {
|
||||
SUCCESS = "success",
|
||||
ERROR = "error",
|
||||
INFO = "info",
|
||||
WARNING = "warning",
|
||||
}
|
||||
|
||||
const TYPE_TO_VARIANT: Record<TOAST_TYPE, ToastVariant> = {
|
||||
[TOAST_TYPE.SUCCESS]: "success",
|
||||
[TOAST_TYPE.ERROR]: "danger",
|
||||
[TOAST_TYPE.INFO]: "info",
|
||||
[TOAST_TYPE.WARNING]: "warning",
|
||||
};
|
||||
|
||||
const toastManager = createToastManager();
|
||||
|
||||
type SetToastProps = {
|
||||
type: TOAST_TYPE;
|
||||
title: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export const setToast = (props: SetToastProps) =>
|
||||
toastManager.add({
|
||||
title: props.title,
|
||||
description: props.message,
|
||||
data: { variant: TYPE_TO_VARIANT[props.type] },
|
||||
});
|
||||
|
||||
export function ToastWithTheme() {
|
||||
return (
|
||||
<ToastProvider
|
||||
toastManager={toastManager}
|
||||
close={
|
||||
<IconButton variant="ghost" size="sm" aria-label="Close notification" icon={<Icon icon={CloseOutline} />} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
@import "@plane/tailwind-config/index.css";
|
||||
@import "@plane/editor/styles";
|
||||
@import "@plane/propel/styles/react-day-picker.css";
|
||||
/* Design tokens already arrive via @plane/tailwind-config. The @makeplane/propel styles barrel
|
||||
re-imports the same token files (identical content) and, crucially, registers propel's dist as a
|
||||
Tailwind @source so the component class names are generated. */
|
||||
@import "@makeplane/propel/styles";
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
/* stickies and editor colors
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -873,6 +873,9 @@ importers:
|
||||
'@headlessui/react':
|
||||
specifier: 'catalog:'
|
||||
version: 2.2.10(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@makeplane/propel':
|
||||
specifier: 0.2.0
|
||||
version: 0.2.0(@date-fns/tz@1.4.1)(@types/react@19.2.17)(date-fns@4.1.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.1.17)
|
||||
'@plane/constants':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/constants
|
||||
@@ -882,9 +885,6 @@ importers:
|
||||
'@plane/i18n':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/i18n
|
||||
'@plane/propel':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/propel
|
||||
'@plane/services':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/services
|
||||
@@ -915,6 +915,9 @@ importers:
|
||||
date-fns:
|
||||
specifier: 'catalog:'
|
||||
version: 4.1.0
|
||||
frimousse:
|
||||
specifier: 'catalog:'
|
||||
version: 0.3.0(react@19.2.8)(typescript@5.8.3)
|
||||
isbot:
|
||||
specifier: 'catalog:'
|
||||
version: 5.1.31
|
||||
|
||||
Reference in New Issue
Block a user