From 3a088981922236dd1398de77b8867dee95017c0a Mon Sep 17 00:00:00 2001 From: Evan Hu Date: Sat, 19 Sep 2026 00:02:05 +0900 Subject: [PATCH 1/9] refactor(ui): drop the duplicate Card and point its call sites at @pace/propel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @pace/propel is the library the repo standardises on, and Card is the one place where the two copies were a byte-level duplicate rather than a redesign: packages/propel/src/card/helper.tsx and packages/ui/src/card/helper.tsx were identical, and the two card.tsx files differed on exactly one line — whether `cn` comes from `../utils/classname` or `../utils`. Both are clsx + tailwind-merge, so there is no behaviour to preserve and nothing to decide. Two of the six consumers (profile/overview/workload.tsx and state-distribution.tsx) imported nothing else from @pace/ui and now import no @pace/ui at all. The rest keep the symbols that have no propel twin, so the specifier is split rather than the whole statement moved. packages/ui survives as the app-composites layer; only the components that have a propel equivalent leave it. --- .../components/modules/module-card-item.tsx | 3 +- .../components/profile/overview/activity.tsx | 3 +- .../overview/priority-distribution.tsx | 3 +- .../profile/overview/state-distribution.tsx | 2 +- .../components/profile/overview/stats.tsx | 3 +- .../components/profile/overview/workload.tsx | 2 +- packages/ui/src/card/card.tsx | 40 ------------------ packages/ui/src/card/helper.tsx | 41 ------------------- packages/ui/src/card/index.ts | 7 ---- packages/ui/src/index.ts | 1 - 10 files changed, 10 insertions(+), 95 deletions(-) delete mode 100644 packages/ui/src/card/card.tsx delete mode 100644 packages/ui/src/card/helper.tsx delete mode 100644 packages/ui/src/card/index.ts diff --git a/apps/web/core/components/modules/module-card-item.tsx b/apps/web/core/components/modules/module-card-item.tsx index b90b27be..87baa8e5 100644 --- a/apps/web/core/components/modules/module-card-item.tsx +++ b/apps/web/core/components/modules/module-card-item.tsx @@ -15,10 +15,11 @@ import { InfoOutline, UserAltOutline, WorkItemsOutline } from "@makeplane/propel import { MODULE_STATUS, EUserPermissions, EUserPermissionsLevel, IS_FAVORITE_MENU_OPEN } from "@pace/constants"; import { useLocalStorage } from "@pace/hooks"; import { LinearProgress } from "@makeplane/propel/components/linear-progress"; +import { Card } from "@pace/propel/card"; import { TOAST_TYPE, setPromiseToast, setToast } from "@pace/propel/toast"; import { Tooltip } from "@makeplane/propel/components/tooltip"; import type { IModule } from "@pace/types"; -import { Card, FavoriteStar } from "@pace/ui"; +import { FavoriteStar } from "@pace/ui"; import { getDate, renderFormattedPayloadDate, generateQueryParams } from "@pace/utils"; // components import { DateRangeDropdown } from "@/components/dropdowns/date-range"; diff --git a/apps/web/core/components/profile/overview/activity.tsx b/apps/web/core/components/profile/overview/activity.tsx index de804cc4..72516a81 100644 --- a/apps/web/core/components/profile/overview/activity.tsx +++ b/apps/web/core/components/profile/overview/activity.tsx @@ -10,8 +10,9 @@ import useSWR from "swr"; // ui import { useTranslation } from "@pace/i18n"; import { Avatar } from "@pace/propel/avatar"; +import { Card } from "@pace/propel/card"; import { EmptyStateCompact } from "@pace/propel/empty-state"; -import { Loader, Card } from "@pace/ui"; +import { Loader } from "@pace/ui"; import { calculateTimeAgo, getFileURL } from "@pace/utils"; // components import { ActivityMessage, IssueLink } from "@/components/core/activity"; diff --git a/apps/web/core/components/profile/overview/priority-distribution.tsx b/apps/web/core/components/profile/overview/priority-distribution.tsx index ec418b8d..4273a294 100644 --- a/apps/web/core/components/profile/overview/priority-distribution.tsx +++ b/apps/web/core/components/profile/overview/priority-distribution.tsx @@ -6,10 +6,11 @@ // pace imports import { useTranslation } from "@pace/i18n"; +import { Card } from "@pace/propel/card"; import { BarChart } from "@pace/propel/charts/bar-chart"; import { EmptyStateCompact } from "@pace/propel/empty-state"; import type { IUserProfileData } from "@pace/types"; -import { Loader, Card } from "@pace/ui"; +import { Loader } from "@pace/ui"; import { capitalizeFirstLetter } from "@pace/utils"; type Props = { diff --git a/apps/web/core/components/profile/overview/state-distribution.tsx b/apps/web/core/components/profile/overview/state-distribution.tsx index ddf1770d..ac3d5bd0 100644 --- a/apps/web/core/components/profile/overview/state-distribution.tsx +++ b/apps/web/core/components/profile/overview/state-distribution.tsx @@ -7,10 +7,10 @@ // pace imports import { STATE_GROUPS } from "@pace/constants"; import { useTranslation } from "@pace/i18n"; +import { Card } from "@pace/propel/card"; import { PieChart } from "@pace/propel/charts/pie-chart"; import { EmptyStateCompact } from "@pace/propel/empty-state"; import type { IUserProfileData, IUserStateDistribution } from "@pace/types"; -import { Card } from "@pace/ui"; import { capitalizeFirstLetter } from "@pace/utils"; type Props = { diff --git a/apps/web/core/components/profile/overview/stats.tsx b/apps/web/core/components/profile/overview/stats.tsx index 39703e7a..1af9340b 100644 --- a/apps/web/core/components/profile/overview/stats.tsx +++ b/apps/web/core/components/profile/overview/stats.tsx @@ -9,10 +9,11 @@ import { useParams } from "@/app/hooks/navigation"; // ui import { useTranslation } from "@pace/i18n"; +import { Card, ECardDirection, ECardSpacing } from "@pace/propel/card"; import { LayerStackIcon } from "@pace/propel/icons"; import { AddOutline, UserOutline } from "@makeplane/propel/icons"; import type { IUserProfileData } from "@pace/types"; -import { Loader, Card, ECardSpacing, ECardDirection } from "@pace/ui"; +import { Loader } from "@pace/ui"; // types type Props = { diff --git a/apps/web/core/components/profile/overview/workload.tsx b/apps/web/core/components/profile/overview/workload.tsx index 89a8d7e0..8c48fda3 100644 --- a/apps/web/core/components/profile/overview/workload.tsx +++ b/apps/web/core/components/profile/overview/workload.tsx @@ -8,8 +8,8 @@ import { STATE_GROUPS } from "@pace/constants"; // types import { useTranslation } from "@pace/i18n"; +import { Card, ECardDirection, ECardSpacing } from "@pace/propel/card"; import type { IUserStateDistribution } from "@pace/types"; -import { Card, ECardDirection, ECardSpacing } from "@pace/ui"; // constants type Props = { diff --git a/packages/ui/src/card/card.tsx b/packages/ui/src/card/card.tsx deleted file mode 100644 index 4ee7f7e7..00000000 --- a/packages/ui/src/card/card.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/** - * 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 { cn } from "../utils"; -import type { TCardDirection, TCardSpacing, TCardVariant } from "./helper"; -import { ECardDirection, ECardSpacing, ECardVariant, getCardStyle } from "./helper"; - -export interface CardProps extends React.HTMLAttributes { - variant?: TCardVariant; - spacing?: TCardSpacing; - direction?: TCardDirection; - className?: string; - children: React.ReactNode; -} - -const Card = React.forwardRef(function Card(props: CardProps, ref: React.ForwardedRef) { - const { - variant = ECardVariant.WITH_SHADOW, - direction = ECardDirection.COLUMN, - className = "", - spacing = ECardSpacing.LG, - children, - ...rest - } = props; - - const style = getCardStyle(variant, spacing, direction); - return ( -
- {children} -
- ); -}); - -Card.displayName = "plane-ui-card"; - -export { Card, ECardVariant, ECardSpacing, ECardDirection }; diff --git a/packages/ui/src/card/helper.tsx b/packages/ui/src/card/helper.tsx deleted file mode 100644 index cc914c5d..00000000 --- a/packages/ui/src/card/helper.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2023-present Plane Software, Inc. and contributors - * SPDX-License-Identifier: AGPL-3.0-only - * See the LICENSE file for details. - */ - -export enum ECardVariant { - WITHOUT_SHADOW = "without-shadow", - WITH_SHADOW = "with-shadow", -} -export enum ECardDirection { - ROW = "row", - COLUMN = "column", -} -export enum ECardSpacing { - SM = "sm", - LG = "lg", -} -export type TCardVariant = ECardVariant.WITHOUT_SHADOW | ECardVariant.WITH_SHADOW; -export type TCardDirection = ECardDirection.ROW | ECardDirection.COLUMN; -export type TCardSpacing = ECardSpacing.SM | ECardSpacing.LG; - -export interface ICardProperties { - [key: string]: string; -} - -const DEFAULT_STYLE = "bg-surface-1 rounded-lg border-[0.5px] border-subtle w-full flex flex-col"; -export const containerStyle: ICardProperties = { - [ECardVariant.WITHOUT_SHADOW]: "", - [ECardVariant.WITH_SHADOW]: "hover:shadow-raised-200 duration-300", -}; -export const spacings = { - [ECardSpacing.SM]: "p-4", - [ECardSpacing.LG]: "p-6", -}; -export const directions = { - [ECardDirection.ROW]: "flex-row space-x-3", - [ECardDirection.COLUMN]: "flex-col space-y-3", -}; -export const getCardStyle = (variant: TCardVariant, spacing: TCardSpacing, direction: TCardDirection) => - DEFAULT_STYLE + " " + directions[direction] + " " + containerStyle[variant] + " " + spacings[spacing]; diff --git a/packages/ui/src/card/index.ts b/packages/ui/src/card/index.ts deleted file mode 100644 index 4260f850..00000000 --- a/packages/ui/src/card/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) 2023-present Plane Software, Inc. and contributors - * SPDX-License-Identifier: AGPL-3.0-only - * See the LICENSE file for details. - */ - -export * from "./card"; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index e00a1604..c13ced21 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -7,7 +7,6 @@ export * from "./app-progress-bar"; export * from "./avatar"; export * from "./breadcrumbs"; -export * from "./card"; export * from "./collapsible"; export * from "./content-wrapper"; export * from "./control-link"; From d7bffc84de03d35a0b92192ed241d9ca7f4e285e Mon Sep 17 00:00:00 2001 From: Evan Hu Date: Sat, 19 Sep 2026 00:09:00 +0900 Subject: [PATCH 2/9] chore(codemods): add the @pace/ui Loader to @pace/propel Skeleton rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sweep this drives touches 80 files, so the reviewable artefact is the transform and its tests rather than the hunks: the transform has to be provably incapable of changing anything but the import lines and the identifier at its use sites. It keys off the import source and never off the name. `Loader` is a name app code also defines for itself, and @pace/propel's own emoji picker re-exports a lucide `Loader`; a transform matching `` in JSX would rewrite first-party components in files that import nothing from @pace/ui. The headlessui-v2-default-tags transform already makes that guard and its comment explains why. It uses the AST only to locate character offsets and splices the replacement into the original text instead of returning `root.toSource()`. An earlier sweep in this effort did go through recast, and recast rewrote unrelated JSX in 7 files — it reprints a whole subtree whenever it cannot reconcile comment attachment, and several of these files carry `// oxlint-disable-next-line` comments inside their JSX. The spec pins that by asserting full output equality on exactly that shape. Two things were easy to get wrong and are pinned by tests. JSXIdentifier is declared as a subtype of Identifier in ast-types, so searching both types splices every JSX element name twice. And the scope hangs off Program, not off the File node the root path points at — `root.get().scope` is null, so comparing a lookup against it matches every unresolved identifier rather than none, which is the difference between leaving a function-scoped `const Loader` alone and renaming it. --- packages/codemods/package.json | 1 + .../ui-loader-to-propel-skeleton.spec.ts | 183 ++++++++++++++++++ .../codemods/ui-loader-to-propel-skeleton.ts | 152 +++++++++++++++ 3 files changed, 336 insertions(+) create mode 100644 packages/codemods/tests/ui-loader-to-propel-skeleton.spec.ts create mode 100644 packages/codemods/ui-loader-to-propel-skeleton.ts diff --git a/packages/codemods/package.json b/packages/codemods/package.json index e0a2083a..bab2be12 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -10,6 +10,7 @@ "fix:lint": "oxlint --fix .", "function-declaration": "jscodeshift -t ./function-declaration.ts --extensions=tsx --parser=tsx ../../apps/*/app ../../apps/*/core ../../apps/*/helpers ../../packages/*/src --ignore-pattern='**/node_modules/**' --ignore-pattern='**/dist/**' --ignore-pattern='**/build/**' --ignore-pattern='**/*.d.ts'", "headlessui-v2-default-tags": "jscodeshift -t ./headlessui-v2-default-tags.ts --extensions=tsx --parser=tsx ../../apps/*/app ../../apps/*/core ../../apps/*/helpers ../../packages/*/src --ignore-pattern='**/node_modules/**' --ignore-pattern='**/dist/**' --ignore-pattern='**/build/**' --ignore-pattern='**/*.d.ts'", + "ui-loader-to-propel-skeleton": "jscodeshift -t ./ui-loader-to-propel-skeleton.ts --extensions=tsx --parser=tsx ../../apps/space/core ../../apps/web/app ../../apps/web/core ../../packages/editor/src --ignore-pattern='**/node_modules/**' --ignore-pattern='**/dist/**' --ignore-pattern='**/build/**' --ignore-pattern='**/*.d.ts'", "remove-directives": "jscodeshift -t ./remove-directives.ts --extensions=ts,tsx --parser=tsx ../../apps/*/app ../../apps/*/core ../../apps/*/helpers ../../packages/*/src --ignore-pattern='**/node_modules/**' --ignore-pattern='**/dist/**' --ignore-pattern='**/build/**' --ignore-pattern='**/*.d.ts'", "test": "vitest run" }, diff --git a/packages/codemods/tests/ui-loader-to-propel-skeleton.spec.ts b/packages/codemods/tests/ui-loader-to-propel-skeleton.spec.ts new file mode 100644 index 00000000..619e8985 --- /dev/null +++ b/packages/codemods/tests/ui-loader-to-propel-skeleton.spec.ts @@ -0,0 +1,183 @@ +/** + * Copyright (c) 2023-present Plane Software, Inc. and contributors + * SPDX-License-Identifier: AGPL-3.0-only + * See the LICENSE file for details. + */ + +import { describe, it, expect } from "vitest"; +import { applyTransform } from "@hypermod/utils"; +import * as transformer from "../ui-loader-to-propel-skeleton"; + +const apply = (source: string) => + applyTransform(transformer, source, { parser: "tsx" }); + +describe("ui-loader-to-propel-skeleton", () => { + it("moves a sole-specifier import and renames the element and its .Item", async () => { + const result = await apply(` + import { Loader } from "@pace/ui"; + + export const Placeholder = () => ( + + + + ); + `); + + expect(result).toContain( + `import { Skeleton } from "@pace/propel/skeleton";` + ); + expect(result).not.toContain("@pace/ui"); + expect(result).toContain(``); + expect(result).toContain(``); + expect(result).toContain(""); + }); + + it("splits Loader out and leaves the other @pace/ui specifiers in place", async () => { + const result = await apply(` + import { Loader, CustomMenu, Row } from "@pace/ui"; + + export const Placeholder = () => ; + `); + + expect(result).toContain( + `import { Skeleton } from "@pace/propel/skeleton";` + ); + expect(result).toContain(`import { CustomMenu, Row } from "@pace/ui";`); + }); + + it("removes a trailing Loader specifier without leaving a dangling comma", async () => { + const result = await apply(` + import { CustomMenu, Loader } from "@pace/ui"; + + export const Placeholder = () => ; + `); + + expect(result).toContain(`import { CustomMenu } from "@pace/ui";`); + expect(result).not.toMatch(/,\s*\}/); + }); + + it("keeps the leading comment above both statements", async () => { + const result = await apply(` + // ui + import { Loader, Row } from "@pace/ui"; + + export const Placeholder = () => ; + `); + + expect(result).toMatch( + /\/\/ ui\n\s*import \{ Skeleton \} from "@pace\/propel\/skeleton";\n\s*import \{ Row \} from "@pace\/ui";/ + ); + }); + + it("handles a multi-line specifier list", async () => { + const result = await apply(` + import { + CustomMenu, + Loader, + Row, + } from "@pace/ui"; + + export const Placeholder = () => ; + `); + + expect(result).toContain("CustomMenu,"); + expect(result).toContain("Row,"); + expect(result).not.toMatch(/\bLoader\b/); + }); + + it("leaves a same-named local component alone when nothing comes from @pace/ui", async () => { + const result = await apply(` + import { Row } from "@pace/ui"; + + function Loader() { + return
; + } + + export const Placeholder = () => ; + `); + + expect(result).not.toContain("@pace/propel/skeleton"); + expect(result).toContain("function Loader()"); + expect(result).toContain(""); + }); + + it("leaves a file that imports no UI library untouched", async () => { + const result = await apply(` + import { Loader } from "@/components/loader"; + + export const Placeholder = () => ; + `); + + expect(result).not.toContain("@pace/propel/skeleton"); + expect(result).toContain(`import { Loader } from "@/components/loader";`); + }); + + it("does not rename a reference shadowed inside a function", async () => { + const result = await apply(` + import { Loader } from "@pace/ui"; + + export function render() { + const Loader = () => ; + return ; + } + `); + + expect(result).toContain( + `import { Skeleton } from "@pace/propel/skeleton";` + ); + expect(result).toContain("const Loader = () => ;"); + expect(result).toContain("return ;"); + }); + + it("keeps an aliased local name and renames nothing", async () => { + const result = await apply(` + import { Loader as UiLoader } from "@pace/ui"; + + export const Placeholder = () => ; + `); + + expect(result).toContain( + `import { Skeleton as UiLoader } from "@pace/propel/skeleton";` + ); + expect(result).toContain(""); + }); + + it("preserves the type-only modifier", async () => { + const result = await apply(` + import type { Loader } from "@pace/ui"; + + export type Props = { loader: typeof Loader }; + `); + + expect(result).toContain( + `import type { Skeleton } from "@pace/propel/skeleton";` + ); + expect(result).toContain("typeof Skeleton"); + }); + + it("reprints nothing but the lines it edits, JSX comments included", async () => { + // This is the shape recast mangled on an earlier sweep: an oxlint directive inside the JSX, a + // self-closing element next to a text node, and blank lines between siblings. + const source = `import { Loader } from "@pace/ui"; + +export const Placeholder = () => ( + + {/* oxlint-disable-next-line jsx-a11y/no-static-element-interactions */} +
+ label +
+ + +
+); +`; + + // applyTransform trims what it returns, hence the trim on the expectation rather than on the input. + expect(await apply(source)).toBe( + source + .replaceAll("Loader", "Skeleton") + .replace(`from "@pace/ui"`, `from "@pace/propel/skeleton"`) + .trim() + ); + }); +}); diff --git a/packages/codemods/ui-loader-to-propel-skeleton.ts b/packages/codemods/ui-loader-to-propel-skeleton.ts new file mode 100644 index 00000000..ccc903fa --- /dev/null +++ b/packages/codemods/ui-loader-to-propel-skeleton.ts @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2023-present Plane Software, Inc. and contributors + * SPDX-License-Identifier: AGPL-3.0-only + * See the LICENSE file for details. + */ + +import type { + API, + ASTPath, + FileInfo, + ImportDeclaration, + ImportSpecifier, +} from "jscodeshift"; + +/** + * `Loader` from `@pace/ui` -> `Skeleton` from `@pace/propel/skeleton`. + * + * The two are the same component under two names: both render `animate-pulse` + `role="status"` around their children and expose an `.Item` taking `height`/`width`/`className` with an `"auto"` default, and the class expressions are identical. Skeleton additionally sets `data-slot` and an `aria-label`, which is additive — no selector in the repo targets the old markup (`plane-ui-loader` appears only on the deleted component's own `displayName`). + * + * Everything keys off the import source, never off the name. `Loader` is a name several app files define for themselves, and one of them (`packages/editor/src/components/editors/document/loader.tsx`) both defines a `DocumentContentLoader` and imports the @pace/ui `Loader`. A transform that matched `` in JSX would rewrite first-party components in the files that never imported anything. The headlessui-v2-default-tags transform makes the same guard for the same reason. + * + * The AST is used only to locate character offsets; the replacement is spliced into the original text and the source string is returned instead of `root.toSource()`. Reprinting a mutated AST through recast is not byte-preserving — on a 531-file sweep in this repo it rewrote unrelated JSX in 7 files, because recast reprints a whole subtree when it cannot reconcile comment attachment and several files carry `// oxlint-disable-next-line` comments inside their JSX. Splicing keeps every hunk in the 80 files to the import lines and the bare identifier renames. + * + * Deliberately not done: + * + * - No merging into an existing `@pace/propel/skeleton` import, and no reordering. A file keeping other @pace/ui symbols gains a second import statement above the one it had; that is what the icons sweep does too, and both oxlint and oxfmt accept it. + * - An aliased import (`Loader as X`) keeps its local name — the new import becomes `Skeleton as X` and no reference is touched. There is none in the repo today, but renaming references would otherwise have to prove the alias is free at every use site. + * - A reference whose scope resolves to something other than the module-level import is left alone, which is what makes a shadowing `const Loader` inside a function safe. + */ +const FROM_SOURCE = "@pace/ui"; +const TO_SOURCE = "@pace/propel/skeleton"; +const FROM_NAME = "Loader"; +const TO_NAME = "Skeleton"; + +type Splice = { start: number; end: number; text: string }; + +/** Babel records these; ast-types does not declare them on the node types. */ +type Ranged = { start?: number; end?: number }; + +const rangeOf = (node: unknown): [number, number] | undefined => { + const { start, end } = (node ?? {}) as Ranged; + return typeof start === "number" && typeof end === "number" + ? [start, end] + : undefined; +}; + +export default function transform(file: FileInfo, api: API) { + const j = api.jscodeshift; + const root = j(file.source); + + const splices: Splice[] = []; + /** Local names bound to @pace/ui's `Loader` that are safe to rename at their use sites. */ + const renamedLocals = new Set(); + + root + .find(j.ImportDeclaration, { source: { value: FROM_SOURCE } }) + .forEach((path: ASTPath) => { + const specifiers = path.node.specifiers ?? []; + const index = specifiers.findIndex( + (specifier) => + specifier.type === "ImportSpecifier" && + // ast-types types both names as `string | IdentifierKind`; only the string form can be compared. + typeof specifier.imported?.name === "string" && + specifier.imported.name === FROM_NAME + ); + if (index === -1) return; + + const specifier = specifiers[index] as ImportSpecifier; + const specifierRange = rangeOf(specifier); + const declarationRange = rangeOf(path.node); + if (!specifierRange || !declarationRange) return; + + const localName = + typeof specifier.local?.name === "string" + ? specifier.local.name + : FROM_NAME; + // An alias only has to survive when it differs from both names; `Loader as Skeleton` already binds the target name. + const binding = + localName === FROM_NAME || localName === TO_NAME + ? TO_NAME + : `${TO_NAME} as ${localName}`; + // `import type { Loader }` binds no runtime value; the replacement has to keep the modifier or the emitted import stops type-checking. + const keyword = + path.node.importKind === "type" ? "import type" : "import"; + const declaration = `${keyword} { ${binding} } from "${TO_SOURCE}";`; + + if (specifiers.length === 1) { + // Nothing else came from @pace/ui, so the statement itself moves and the file loses the dependency. + splices.push({ + start: declarationRange[0], + end: declarationRange[1], + text: declaration, + }); + } else { + // Cutting to the next specifier's start (or back from the previous one's end when this is the last) takes the separating comma and whitespace with it, whatever the formatting. + const neighbour = + index + 1 < specifiers.length + ? rangeOf(specifiers[index + 1]) + : rangeOf(specifiers[index - 1]); + if (!neighbour) return; + const [cutStart, cutEnd] = + index + 1 < specifiers.length + ? [specifierRange[0], neighbour[0]] + : [neighbour[1], specifierRange[1]]; + splices.push({ start: cutStart, end: cutEnd, text: "" }); + // Inserted at the `import` keyword, so a leading `// ui` style comment stays above both statements. + splices.push({ + start: declarationRange[0], + end: declarationRange[0], + text: `${declaration}\n`, + }); + } + + if (localName === FROM_NAME) renamedLocals.add(localName); + }); + + if (splices.length === 0) return file.source; + + // The scope hangs off Program, not off the File node the root path points at — `root.get().scope` is null, and comparing against null would have matched every unresolved identifier instead of none. + const moduleScope = root.find(j.Program).paths()[0]?.scope; + + if (renamedLocals.size > 0 && moduleScope) { + const rename = (path: ASTPath<{ name?: unknown }>) => { + const name = path.node.name; + if (typeof name !== "string" || !renamedLocals.has(name)) return; + // The import specifier's own identifiers sit inside a range already being replaced. + if (path.parent?.node?.type === "ImportSpecifier") return; + // A JSX member expression's property (`Loader.Item` -> the `Item`) must not be touched. + if ( + path.parent?.node?.type === "JSXMemberExpression" && + path.parent.node.property === path.node + ) + return; + if (path.scope?.lookup(name) !== moduleScope) return; + const range = rangeOf(path.node); + if (range) + splices.push({ start: range[0], end: range[1], text: TO_NAME }); + }; + + // One pass, not two: ast-types declares JSXIdentifier as a subtype of Identifier, so this collection already contains the JSX element names. Searching both types would splice every one of them twice. + root.find(j.Identifier).forEach(rename); + } + + // Back to front, so an earlier splice never invalidates a later offset. + let output = file.source; + for (const splice of splices.toSorted((a, b) => b.start - a.start)) { + output = + output.slice(0, splice.start) + splice.text + output.slice(splice.end); + } + + return output; +} From 7d7de97dd246c612c1afce677462a5b03ed3c035 Mon Sep 17 00:00:00 2001 From: Evan Hu Date: Sat, 19 Sep 2026 00:09:05 +0900 Subject: [PATCH 3/9] refactor(ui): retire Loader in favour of @pace/propel's Skeleton across 80 files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Loader and Skeleton are the same component under two names: both wrap their children in `animate-pulse` with `role="status"`, both expose an `.Item` taking `height`/`width`/`className` defaulting to `"auto"`, and the class expressions are identical. Skeleton is the one that lives in the library everything standardises on, and it had zero consumers until now. Skeleton additionally emits `data-slot` attributes and an `aria-label="Loading content"`, so the rendered markup gains an accessible name it did not have. Nothing styles the old markup — `plane-ui-loader` appeared only on the deleted component's own `displayName`, and the call sites pass only `height`, `width` and `className`, all of which Skeleton supports. 63 of the 80 files imported nothing else from @pace/ui and now import no @pace/ui at all. The rest keep the composites that have no propel twin, so the specifier is split out rather than the statement moved. Produced by `pnpm --filter @pace/codemods run ui-loader-to-propel-skeleton`; every hunk is an import line or a bare identifier. --- .../core/components/issues/filters/labels.tsx | 12 +-- .../core/components/issues/filters/state.tsx | 12 +-- .../peek-overview/full-screen-peek-view.tsx | 26 +++--- .../issues/peek-overview/side-peek-view.tsx | 14 ++-- .../(projects)/browse/[workItem]/page.tsx | 22 ++--- .../(detail)/[archivedIssueId]/page.tsx | 22 ++--- .../components/analytics/insight-card.tsx | 4 +- .../analytics/insight-table/loader.tsx | 4 +- .../web/core/components/analytics/loaders.tsx | 26 +++--- .../analytics/overview/active-projects.tsx | 4 +- .../automation/auto-archive-automation.tsx | 9 ++- .../automation/auto-close-automation.tsx | 9 ++- .../components/base-layouts/gantt/sidebar.tsx | 15 ++-- .../components/common/filters/created-by.tsx | 12 +-- .../core/description-versions/modal.tsx | 19 ++--- .../components/core/image-picker-popover.tsx | 22 ++--- .../core/modals/bulk-delete-issues-modal.tsx | 15 ++-- .../modals/existing-issues-list-modal.tsx | 15 ++-- .../cycles/active-cycle/cycle-stats.tsx | 18 ++--- .../cycles/active-cycle/productivity.tsx | 8 +- .../cycles/active-cycle/progress.tsx | 8 +- .../cycles/analytics-sidebar/root.tsx | 16 ++-- .../analytics-sidebar/sidebar-chart.tsx | 8 +- .../rich-text/description-input/loader.tsx | 24 +++--- .../components/estimates/loader-screen.tsx | 14 ++-- .../gantt-chart/sidebar/issues/sidebar.tsx | 14 ++-- .../gantt-chart/sidebar/modules/sidebar.tsx | 14 ++-- .../global/product-updates/changelog.tsx | 8 +- .../home/widgets/loaders/home-loader.tsx | 10 +-- .../home/widgets/loaders/quick-links.tsx | 8 +- .../home/widgets/loaders/recent-activity.tsx | 10 +-- .../inbox/inbox-filter/filters/labels.tsx | 12 +-- .../inbox/inbox-filter/filters/members.tsx | 12 +-- .../inbox/inbox-filter/filters/state.tsx | 12 +-- .../modals/create-modal/issue-description.tsx | 8 +- .../inbox/modals/select-duplicate.tsx | 15 ++-- .../core/components/inbox/sidebar/root.tsx | 11 +-- .../integration/single-integration-card.tsx | 8 +- .../integration/slack/select-channel.tsx | 8 +- .../issue-detail/issue-activity/loader.tsx | 30 +++---- .../issues/issue-detail/subscription.tsx | 8 +- .../filters/header/filters/assignee.tsx | 12 +-- .../filters/header/filters/created-by.tsx | 12 +-- .../filters/header/filters/cycle.tsx | 12 +-- .../filters/header/filters/labels.tsx | 12 +-- .../filters/header/filters/mentions.tsx | 12 +-- .../filters/header/filters/module.tsx | 12 +-- .../filters/header/filters/project.tsx | 12 +-- .../filters/header/filters/state.tsx | 12 +-- .../components/description-editor.tsx | 24 +++--- .../issues/parent-issues-list-modal.tsx | 15 ++-- .../issues/peek-overview/loader.tsx | 72 ++++++++--------- .../labels/project-setting-label-list.tsx | 14 ++-- .../license/modal/card/checkout-button.tsx | 14 ++-- .../license/modal/card/talk-to-sales.tsx | 14 ++-- .../modules/analytics-sidebar/root.tsx | 17 ++-- .../modules/dropdowns/filters/lead.tsx | 12 +-- .../modules/dropdowns/filters/members.tsx | 12 +-- .../pages/loaders/page-content-loader.tsx | 80 +++++++++---------- .../components/pages/loaders/page-loader.tsx | 26 +++--- .../core/components/pages/version/editor.tsx | 38 ++++----- .../components/profile/overview/activity.tsx | 16 ++-- .../overview/priority-distribution.tsx | 16 ++-- .../components/profile/overview/stats.tsx | 12 +-- apps/web/core/components/profile/sidebar.tsx | 18 ++--- .../core/components/project-states/loader.tsx | 14 ++-- .../project/dropdowns/filters/lead.tsx | 12 +-- .../project/dropdowns/filters/members.tsx | 12 +-- .../core/components/project/form-loader.tsx | 50 ++++++------ .../project-settings-member-defaults.tsx | 14 ++-- .../project/publish-project/modal.tsx | 15 ++-- apps/web/core/components/readonly/state.tsx | 10 +-- .../rich-filters/filter-item/loader.tsx | 8 +- .../components/rich-filters/filters-row.tsx | 9 ++- .../stickies/layout/stickies-infinite.tsx | 9 ++- .../stickies/layout/stickies-loader.tsx | 38 ++++----- .../project-inbox/inbox-layout-loader.tsx | 16 ++-- .../workspace/sidebar/projects-list.tsx | 8 +- .../workspace/sidebar/workspace-menu-root.tsx | 10 +-- .../components/editors/document/loader.tsx | 36 ++++----- packages/ui/src/index.ts | 1 - packages/ui/src/loader.tsx | 38 --------- 82 files changed, 658 insertions(+), 684 deletions(-) delete mode 100644 packages/ui/src/loader.tsx diff --git a/apps/space/core/components/issues/filters/labels.tsx b/apps/space/core/components/issues/filters/labels.tsx index 8c8ed7d6..7e6751ca 100644 --- a/apps/space/core/components/issues/filters/labels.tsx +++ b/apps/space/core/components/issues/filters/labels.tsx @@ -6,7 +6,7 @@ import React, { useState } from "react"; // pace imports -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; // types import type { IIssueLabel } from "@/types/issue"; // local imports @@ -76,11 +76,11 @@ export function FilterLabels(props: Props) {

No matches found

) ) : ( - - - - - + + + + + )}
)} diff --git a/apps/space/core/components/issues/filters/state.tsx b/apps/space/core/components/issues/filters/state.tsx index edfbe282..35ad7879 100644 --- a/apps/space/core/components/issues/filters/state.tsx +++ b/apps/space/core/components/issues/filters/state.tsx @@ -9,7 +9,7 @@ import { observer } from "mobx-react"; // ui import { EIconSize } from "@pace/constants"; import { StateGroupIcon } from "@pace/propel/icons"; -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; // hooks import { useStates } from "@/hooks/store/use-state"; // local imports @@ -76,11 +76,11 @@ export const FilterState = observer(function FilterState(props: Props) {

No matches found

) ) : ( - - - - - + + + + + )} )} diff --git a/apps/space/core/components/issues/peek-overview/full-screen-peek-view.tsx b/apps/space/core/components/issues/peek-overview/full-screen-peek-view.tsx index 2538749c..22e1041e 100644 --- a/apps/space/core/components/issues/peek-overview/full-screen-peek-view.tsx +++ b/apps/space/core/components/issues/peek-overview/full-screen-peek-view.tsx @@ -6,7 +6,7 @@ import { observer } from "mobx-react"; // pace imports -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; // types import type { IIssue } from "@/types/issue"; // local imports @@ -44,14 +44,14 @@ export const FullScreenPeekView = observer(function FullScreenPeekView(props: Pr ) : ( - - + +
- - - + + +
-
+ )}
@@ -60,12 +60,12 @@ export const FullScreenPeekView = observer(function FullScreenPeekView(props: Pr {issueDetails ? ( ) : ( - - - - - - + + + + + + )}
diff --git a/apps/space/core/components/issues/peek-overview/side-peek-view.tsx b/apps/space/core/components/issues/peek-overview/side-peek-view.tsx index 1feb9a7c..4faf0895 100644 --- a/apps/space/core/components/issues/peek-overview/side-peek-view.tsx +++ b/apps/space/core/components/issues/peek-overview/side-peek-view.tsx @@ -6,7 +6,7 @@ import { observer } from "mobx-react"; // pace imports -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; // store hooks import { usePublish } from "@/hooks/store/publish"; // types @@ -53,14 +53,14 @@ export const SidePeekView = observer(function SidePeekView(props: Props) { )} ) : ( - - + +
- - - + + +
-
+ )} ); diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx index 04ad07cb..fd402336 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx @@ -12,7 +12,7 @@ import useSWR from "swr"; import { useTranslation } from "@pace/i18n"; import type { TIssue } from "@pace/types"; import { EIssueServiceType } from "@pace/types"; -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; // assets import emptyIssueDark from "@/app/assets/empty-state/search/issues-dark.webp?url"; import emptyIssueLight from "@/app/assets/empty-state/search/issues-light.webp?url"; @@ -107,20 +107,20 @@ export const IssueDetailsPage = observer(function IssueDetailsPage({ params }: R if (issueLoader) { return ( - +
- - - - + + + +
- - - - + + + +
-
+ ); } diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx index 81bc4c6c..abee02b0 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx @@ -12,7 +12,7 @@ import { Banner } from "@makeplane/propel/components/banner"; import { ArchiveOutline } from "@makeplane/propel/icons"; import { useTranslation } from "@pace/i18n"; import { Button } from "@pace/propel/button"; -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; // components import { PageHead } from "@/components/core/page-title"; import { IssueDetailRoot } from "@/components/issues/issue-detail"; @@ -53,20 +53,20 @@ function ArchivedIssueDetailsPage({ params }: Route.ComponentProps) { <> {issueLoader ? ( - +
- - - - + + + +
- - - - + + + +
-
+ ) : ( <> {count} ) : ( - + )} ); diff --git a/apps/web/core/components/analytics/insight-table/loader.tsx b/apps/web/core/components/analytics/insight-table/loader.tsx index 8a7f77db..1b787608 100644 --- a/apps/web/core/components/analytics/insight-table/loader.tsx +++ b/apps/web/core/components/analytics/insight-table/loader.tsx @@ -7,7 +7,7 @@ import * as React from "react"; import type { ColumnDef } from "@tanstack/react-table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@pace/propel/table"; -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; interface TableSkeletonProps { columns: ColumnDef[]; @@ -31,7 +31,7 @@ export function TableLoader({ columns, rows }: TableSkeletonProps) { {columns.map((_, colIndex) => ( - + ))} diff --git a/apps/web/core/components/analytics/loaders.tsx b/apps/web/core/components/analytics/loaders.tsx index a62787cf..c9f1fdf4 100644 --- a/apps/web/core/components/analytics/loaders.tsx +++ b/apps/web/core/components/analytics/loaders.tsx @@ -4,21 +4,21 @@ * See the LICENSE file for details. */ -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; export function ProjectInsightsLoader() { return (
- - - + + +
- - - - - - + + + + + +
); @@ -26,8 +26,8 @@ export function ProjectInsightsLoader() { export function ChartLoader() { return ( - - - + + + ); } diff --git a/apps/web/core/components/analytics/overview/active-projects.tsx b/apps/web/core/components/analytics/overview/active-projects.tsx index ca157ba1..f8abacbe 100644 --- a/apps/web/core/components/analytics/overview/active-projects.tsx +++ b/apps/web/core/components/analytics/overview/active-projects.tsx @@ -10,7 +10,7 @@ import { useParams } from "@/app/hooks/navigation"; import useSWR from "swr"; // pace package imports import { useTranslation } from "@pace/i18n"; -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; // pace web hooks import { useAnalytics } from "@/hooks/store/use-analytics"; import { useProject } from "@/hooks/store/use-project"; @@ -40,7 +40,7 @@ const ActiveProjects = observer(function ActiveProjects() { >
{isProjectAnalyticsCountLoading && - Array.from({ length: 5 }).map((_, index) => )} + Array.from({ length: 5 }).map((_, index) => )} {!isProjectAnalyticsCountLoading && projectAnalyticsCount?.map((project) => )}
diff --git a/apps/web/core/components/automation/auto-archive-automation.tsx b/apps/web/core/components/automation/auto-archive-automation.tsx index 079423bf..8df151a5 100644 --- a/apps/web/core/components/automation/auto-archive-automation.tsx +++ b/apps/web/core/components/automation/auto-archive-automation.tsx @@ -13,7 +13,8 @@ import { PROJECT_AUTOMATION_MONTHS, EUserPermissions, EUserPermissionsLevel } fr import { useTranslation } from "@pace/i18n"; import type { IProject } from "@pace/types"; import { Switch } from "@makeplane/propel/components/switch"; -import { CustomSelect, Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; +import { CustomSelect } from "@pace/ui"; // component import { SelectMonthModal } from "@/components/automation"; import { SettingsControlItem } from "@/components/settings/control-item"; @@ -125,9 +126,9 @@ export const AutoArchiveAutomation = observer(function AutoArchiveAutomation(pro ) ) : ( - - - + + + )} diff --git a/apps/web/core/components/automation/auto-close-automation.tsx b/apps/web/core/components/automation/auto-close-automation.tsx index a780560c..6024c8de 100644 --- a/apps/web/core/components/automation/auto-close-automation.tsx +++ b/apps/web/core/components/automation/auto-close-automation.tsx @@ -14,7 +14,8 @@ import { useTranslation } from "@pace/i18n"; import { StateGroupIcon } from "@pace/propel/icons"; import type { IProject } from "@pace/types"; import { Switch } from "@makeplane/propel/components/switch"; -import { CustomSelect, CustomSearchSelect, Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; +import { CustomSelect, CustomSearchSelect } from "@pace/ui"; import { SelectMonthModal } from "@/components/automation"; import { SettingsControlItem } from "@/components/settings/control-item"; // hooks @@ -188,9 +189,9 @@ export const AutoCloseAutomation = observer(function AutoCloseAutomation(props: ) ) : ( - - - + + + )} diff --git a/apps/web/core/components/base-layouts/gantt/sidebar.tsx b/apps/web/core/components/base-layouts/gantt/sidebar.tsx index 57e0c666..a5311520 100644 --- a/apps/web/core/components/base-layouts/gantt/sidebar.tsx +++ b/apps/web/core/components/base-layouts/gantt/sidebar.tsx @@ -8,7 +8,8 @@ import type { RefObject } from "react"; import { useState } from "react"; import { observer } from "mobx-react"; import type { IBaseLayoutsBaseItem, IBlockUpdateData } from "@pace/types"; -import { Loader, Row } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; +import { Row } from "@pace/ui"; import { cn } from "@pace/utils"; import RenderIfVisible from "@/components/core/render-if-visible-HOC"; import { BLOCK_HEIGHT } from "@/components/gantt-chart/constants"; @@ -143,12 +144,12 @@ export const BaseGanttSidebar = observer(function BaseGanttSidebar ) : ( - - - - - - + + + + + + )} ); diff --git a/apps/web/core/components/common/filters/created-by.tsx b/apps/web/core/components/common/filters/created-by.tsx index 9e654c5c..aa970c27 100644 --- a/apps/web/core/components/common/filters/created-by.tsx +++ b/apps/web/core/components/common/filters/created-by.tsx @@ -9,7 +9,7 @@ import { sortBy } from "lodash-es"; import { observer } from "mobx-react"; // ui import { Avatar } from "@makeplane/propel/components/avatar"; -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; // components import { getFileURL } from "@pace/utils"; import { FilterHeader, FilterOption } from "@/components/issues/issue-layouts/filters"; @@ -103,11 +103,11 @@ export const FilterCreatedBy = observer(function FilterCreatedBy(props: Props) {

No matches found

) ) : ( - - - - - + + + + + )} )} diff --git a/apps/web/core/components/core/description-versions/modal.tsx b/apps/web/core/components/core/description-versions/modal.tsx index 5d68d6e9..be897c90 100644 --- a/apps/web/core/components/core/description-versions/modal.tsx +++ b/apps/web/core/components/core/description-versions/modal.tsx @@ -15,7 +15,8 @@ import { ChevronLeftOutline, ChevronRightOutline, CopyOutline } from "@makeplane import { setToast, TOAST_TYPE } from "@pace/propel/toast"; import { Tooltip } from "@makeplane/propel/components/tooltip"; import type { TDescriptionVersion } from "@pace/types"; -import { EModalPosition, EModalWidth, Loader, ModalCore } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; +import { EModalPosition, EModalWidth, ModalCore } from "@pace/ui"; import { calculateTimeAgo, cn, getFileURL } from "@pace/utils"; // components import { RichTextEditor } from "@/components/editor/rich-text"; @@ -140,18 +141,18 @@ export const DescriptionVersionsModal = observer(function DescriptionVersionsMod /> ) : (
- - + +
- - + +
- - + +
- - + +
)} diff --git a/apps/web/core/components/core/image-picker-popover.tsx b/apps/web/core/components/core/image-picker-popover.tsx index ff43dbc4..c04d6121 100644 --- a/apps/web/core/components/core/image-picker-popover.tsx +++ b/apps/web/core/components/core/image-picker-popover.tsx @@ -20,7 +20,7 @@ import { useOutsideClickDetector } from "@pace/hooks"; import { Button, getButtonStyling } from "@pace/propel/button"; import { TOAST_TYPE, setToast } from "@pace/propel/toast"; import { EFileAssetType } from "@pace/types"; -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; // helpers import { STATIC_COVER_IMAGES, getCoverImageDisplayURL } from "@/helpers/cover-image.helper"; // hooks @@ -277,16 +277,16 @@ function ImagePickerPopoverComponentNo images found.

) ) : ( - - - - - - - - - - + + + + + + + + + + )} )} diff --git a/apps/web/core/components/core/modals/bulk-delete-issues-modal.tsx b/apps/web/core/components/core/modals/bulk-delete-issues-modal.tsx index 49fcecfe..7bdc0a4b 100644 --- a/apps/web/core/components/core/modals/bulk-delete-issues-modal.tsx +++ b/apps/web/core/components/core/modals/bulk-delete-issues-modal.tsx @@ -18,7 +18,8 @@ import { SearchOutline } from "@makeplane/propel/icons"; import { TOAST_TYPE, setToast } from "@pace/propel/toast"; import type { ISearchIssueResponse, IUser } from "@pace/types"; import { EIssuesStoreType } from "@pace/types"; -import { Loader, EModalPosition, EModalWidth, ModalCore } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; +import { EModalPosition, EModalWidth, ModalCore } from "@pace/ui"; // assets import darkIssuesAsset from "@/app/assets/empty-state/search/issues-dark.webp?url"; import lightIssuesAsset from "@/app/assets/empty-state/search/issues-light.webp?url"; @@ -185,12 +186,12 @@ export const BulkDeleteIssuesModal = observer(function BulkDeleteIssuesModal(pro {isSearching ? ( - - - - - - + + + + + + ) : ( <>{issueList} )} diff --git a/apps/web/core/components/core/modals/existing-issues-list-modal.tsx b/apps/web/core/components/core/modals/existing-issues-list-modal.tsx index 7a5bf364..f1a7124c 100644 --- a/apps/web/core/components/core/modals/existing-issues-list-modal.tsx +++ b/apps/web/core/components/core/modals/existing-issues-list-modal.tsx @@ -16,7 +16,8 @@ import { Tooltip } from "@makeplane/propel/components/tooltip"; import type { ISearchIssueResponse, TProjectIssuesSearchParams } from "@pace/types"; // ui import { Switch } from "@makeplane/propel/components/switch"; -import { Loader, EModalPosition, EModalWidth, ModalCore } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; +import { EModalPosition, EModalWidth, ModalCore } from "@pace/ui"; import { generateWorkItemLink, getTabIndex } from "@pace/utils"; // helpers // hooks @@ -235,12 +236,12 @@ export function ExistingIssuesListModal(props: Props) { )} {isSearching || isLoading ? ( - - - - - - + + + + + + ) : ( <> {filteredIssues.length === 0 ? ( diff --git a/apps/web/core/components/cycles/active-cycle/cycle-stats.tsx b/apps/web/core/components/cycles/active-cycle/cycle-stats.tsx index ecb851db..ad1f5dfc 100644 --- a/apps/web/core/components/cycles/active-cycle/cycle-stats.tsx +++ b/apps/web/core/components/cycles/active-cycle/cycle-stats.tsx @@ -20,7 +20,7 @@ import type { TWorkItemFilterCondition } from "@pace/shared-state"; import type { ICycle } from "@pace/types"; import { EIssuesStoreType } from "@pace/types"; // ui -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; import { cn, renderFormattedDate, renderFormattedDateWithoutYear, getFileURL } from "@pace/utils"; // assets import darkAssigneeAsset from "@/app/assets/empty-state/active-cycle/assignee-dark.webp?url"; @@ -97,11 +97,11 @@ export const ActiveCycleStats = observer(function ActiveCycleStats(props: Active useIntersectionObserver(issuesContainerRef, issuesLoaderElement, loadMoreIssues, `0% 0% 100% 0%`); const loaders = ( - - - - - + + + + + ); return cycleId ? ( @@ -370,8 +370,8 @@ export const ActiveCycleStats = observer(function ActiveCycleStats(props: Active ) : ( - - - + + + ); }); diff --git a/apps/web/core/components/cycles/active-cycle/productivity.tsx b/apps/web/core/components/cycles/active-cycle/productivity.tsx index 57e6a81f..cabb1619 100644 --- a/apps/web/core/components/cycles/active-cycle/productivity.tsx +++ b/apps/web/core/components/cycles/active-cycle/productivity.tsx @@ -11,7 +11,7 @@ import { useTheme } from "next-themes"; // pace imports import { useTranslation } from "@pace/i18n"; import type { ICycle, TCycleEstimateType } from "@pace/types"; -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; // assets import darkChartAsset from "@/app/assets/empty-state/active-cycle/chart-dark.webp?url"; import lightChartAsset from "@/app/assets/empty-state/active-cycle/chart-light.webp?url"; @@ -101,8 +101,8 @@ export const ActiveCycleProductivity = observer(function ActiveCycleProductivity ) : ( - - - + + + ); }); diff --git a/apps/web/core/components/cycles/active-cycle/progress.tsx b/apps/web/core/components/cycles/active-cycle/progress.tsx index f4d97b61..b54e5c2d 100644 --- a/apps/web/core/components/cycles/active-cycle/progress.tsx +++ b/apps/web/core/components/cycles/active-cycle/progress.tsx @@ -12,7 +12,7 @@ import { useTranslation } from "@pace/i18n"; import type { TWorkItemFilterCondition } from "@pace/shared-state"; import { LinearProgress } from "@makeplane/propel/components/linear-progress"; import type { ICycle } from "@pace/types"; -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; // assets import darkProgressAsset from "@/app/assets/empty-state/active-cycle/progress-dark.webp?url"; import lightProgressAsset from "@/app/assets/empty-state/active-cycle/progress-light.webp?url"; @@ -116,8 +116,8 @@ export const ActiveCycleProgress = observer(function ActiveCycleProgress(props: )} ) : ( - - - + + + ); }); diff --git a/apps/web/core/components/cycles/analytics-sidebar/root.tsx b/apps/web/core/components/cycles/analytics-sidebar/root.tsx index 30253e09..77712ccd 100644 --- a/apps/web/core/components/cycles/analytics-sidebar/root.tsx +++ b/apps/web/core/components/cycles/analytics-sidebar/root.tsx @@ -7,7 +7,7 @@ import React from "react"; import { observer } from "mobx-react"; // pace imports -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; // local imports import useCyclesDetails from "../active-cycle/use-cycles-details"; import { CycleAnalyticsProgress } from "./issue-progress"; @@ -34,17 +34,17 @@ export const CycleDetailsSidebar = observer(function CycleDetailsSidebar(props: if (!cycleDetails) return ( - +
- - + +
- - - + + +
-
+ ); return ( diff --git a/apps/web/core/components/cycles/analytics-sidebar/sidebar-chart.tsx b/apps/web/core/components/cycles/analytics-sidebar/sidebar-chart.tsx index b8d35190..cde79d59 100644 --- a/apps/web/core/components/cycles/analytics-sidebar/sidebar-chart.tsx +++ b/apps/web/core/components/cycles/analytics-sidebar/sidebar-chart.tsx @@ -9,7 +9,7 @@ import { observer } from "mobx-react"; // pace imports import { useTranslation } from "@pace/i18n"; import type { TCycleEstimateType } from "@pace/types"; -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; import { getDate } from "@pace/utils"; // components import ProgressChart from "@/components/core/sidebar/progress-chart"; @@ -79,9 +79,9 @@ export const SidebarChart = observer(function SidebarChart(props: ProgressChartP /> ) : ( - - - + + + )} diff --git a/apps/web/core/components/editor/rich-text/description-input/loader.tsx b/apps/web/core/components/editor/rich-text/description-input/loader.tsx index 7651dadf..5bddc4d0 100644 --- a/apps/web/core/components/editor/rich-text/description-input/loader.tsx +++ b/apps/web/core/components/editor/rich-text/description-input/loader.tsx @@ -5,7 +5,7 @@ */ // pace imports -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; import { cn } from "@pace/utils"; type Props = { @@ -16,24 +16,24 @@ export function DescriptionInputLoader(props: Props) { const { className } = props; return ( - - + +
- - + +
- - + +
- +
- +
- - + +
-
+ ); } diff --git a/apps/web/core/components/estimates/loader-screen.tsx b/apps/web/core/components/estimates/loader-screen.tsx index 15453c82..393d3236 100644 --- a/apps/web/core/components/estimates/loader-screen.tsx +++ b/apps/web/core/components/estimates/loader-screen.tsx @@ -4,15 +4,15 @@ * See the LICENSE file for details. */ -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; export function EstimateLoaderScreen() { return ( - - - - - - + + + + + + ); } diff --git a/apps/web/core/components/gantt-chart/sidebar/issues/sidebar.tsx b/apps/web/core/components/gantt-chart/sidebar/issues/sidebar.tsx index 639ee7d2..7e1a6850 100644 --- a/apps/web/core/components/gantt-chart/sidebar/issues/sidebar.tsx +++ b/apps/web/core/components/gantt-chart/sidebar/issues/sidebar.tsx @@ -10,7 +10,7 @@ import { observer } from "mobx-react"; // ui import { GANTT_TIMELINE_TYPE } from "@pace/types"; import type { IBlockUpdateData } from "@pace/types"; -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; // components import RenderIfVisible from "@/components/core/render-if-visible-HOC"; import { GanttLayoutListItemLoader } from "@/components/ui/loader/layouts/gantt-layout-loader"; @@ -122,12 +122,12 @@ export const IssueGanttSidebar = observer(function IssueGanttSidebar(props: Prop )} ) : ( - - - - - - + + + + + + )} ); diff --git a/apps/web/core/components/gantt-chart/sidebar/modules/sidebar.tsx b/apps/web/core/components/gantt-chart/sidebar/modules/sidebar.tsx index 2048b17a..fae2bc59 100644 --- a/apps/web/core/components/gantt-chart/sidebar/modules/sidebar.tsx +++ b/apps/web/core/components/gantt-chart/sidebar/modules/sidebar.tsx @@ -8,7 +8,7 @@ import { observer } from "mobx-react"; // ui import { GANTT_TIMELINE_TYPE } from "@pace/types"; import type { IBlockUpdateData } from "@pace/types"; -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; // components // hooks import { useTimeLineChart } from "@/hooks/use-timeline-chart"; @@ -52,12 +52,12 @@ export const ModuleGanttSidebar = observer(function ModuleGanttSidebar(props: Pr )) ) : ( - - - - - - + + + + + + )} ); diff --git a/apps/web/core/components/global/product-updates/changelog.tsx b/apps/web/core/components/global/product-updates/changelog.tsx index e9f565ad..d3dfa7c1 100644 --- a/apps/web/core/components/global/product-updates/changelog.tsx +++ b/apps/web/core/components/global/product-updates/changelog.tsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from "react"; import { observer } from "mobx-react"; // hooks -import { Loader } from "@pace/ui"; +import { Skeleton } from "@pace/propel/skeleton"; import { ProductUpdatesFallback } from "@/components/global/product-updates/fallback"; import { useInstance } from "@/hooks/store/use-instance"; @@ -74,9 +74,9 @@ export const ProductUpdatesChangelog = observer(function ProductUpdatesChangelog return (
{isLoading && ( - - - + + + )} {/* eslint-disable-next-line react/iframe-missing-sandbox oxlint-disable-next-line jsx_a11y/iframe-has-title */}