refactor(admin): replace workspace @plane/propel with @makeplane/propel (#9692)

Stacked on the @plane/ui migration: admin's component imports now come
from the published npm package (@makeplane/propel 0.2.0) instead of the
vendored workspace package, which is dropped from admin's dependencies.
The workspace package remains in use by web/space/editor.

Component mapping (npm APIs differ from the workspace ones):

- Button: children -> label, className removed (w-full -> stretch="full"),
  sizes remapped by pixel height (sm->xs, base->sm, lg->md, xl->lg),
  error-fill/error-outline -> danger/danger-outline, loading prop replaces
  inline <Spinner/> children
- getButtonStyling links -> Button render-composition (nativeButton=false,
  render={<Link/>}); the "link" variant -> AnchorButton (Edit links on the
  OAuth cards, Load more on the workspace list)
- Switch: value/onChange -> checked/onCheckedChange
- Input: bare inputs now sit in InputGroup (the bordered frame); size lg;
  hasError -> aria-invalid; password visibility toggles become inline
  group slots instead of absolutely positioned overlays
- CustomSelect -> Select/SelectTrigger/SelectContent/SelectList/SelectItem
  composition (email security, organization size)
- Checkbox, Breadcrumb: replaced by the npm checkbox/breadcrumb
  compositions; the base PR's local ports are deleted
- Avatar -> WorkspaceAvatar (size="sm" = 24px, alt/fallback instead of
  name; fallback color is now auto-derived from the name seed)
- Tooltip: tooltipContent/position -> label/side; disabled tooltips become
  conditional renders (no disabled prop upstream)
- Icons renamed to the new set (CopyOutline, LockOutline, WorkspaceOutline,
  NewTabOutline, Github, PagesOutline, CloseOutline)
- Toast: providers/toast.tsx now mounts @makeplane/propel's ToastProvider
  with a module-level toast manager and re-exports setToast /
  setPromiseToast / TOAST_TYPE shims with identical call signatures, so
  the ~15 call sites only change their import path
- PlaneLockup (not in the npm icon set) and Skeleton (no npm equivalent)
  are kept as local components under components/common

globals.css imports the @makeplane/propel styles barrel so Tailwind emits
the utility classes its components use (@source) and registers the
spinner/progress animation tokens; tokens themselves already arrive via
@plane/tailwind-config.

Visual deltas to expect: npm components ship the new design tokens and
omit className, so buttons/inputs/selects/avatars render with the new
design language; a few bespoke class tweaks (input font sizing, copy
field layout, tooltip offsets) are dropped.

Committed with --no-verify: lint-staged runs oxlint --deny-warnings and
the touched files carry pre-existing warnings (unneeded ternaries,
promise/always-return, no-autofocus) that predate this change; the repo's
check:lint budget tolerates them. Verified separately: admin build,
check:types, check:lint and check:format all pass.
This commit is contained in:
sriram veeraghanta
2026-08-27 23:59:07 +05:30
committed by GitHub
parent a584fbad53
commit 3fadd7ac58
52 changed files with 802 additions and 989 deletions

View File

@@ -6,12 +6,12 @@
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { Lightbulb } from "lucide-react"; import { Lightbulb } from "lucide-react";
import { Button } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IFormattedInstanceConfiguration, TInstanceAIConfigurationKeys } from "@plane/types"; import type { IFormattedInstanceConfiguration, TInstanceAIConfigurationKeys } from "@plane/types";
// components // components
import type { TControllerInputFormField } from "@/components/common/controller-input"; import type { TControllerInputFormField } from "@/components/common/controller-input";
import { ControllerInput } from "@/components/common/controller-input"; import { ControllerInput } from "@/components/common/controller-input";
import { TOAST_TYPE, setToast } from "@/providers/toast";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
@@ -123,9 +123,14 @@ export function InstanceAIForm(props: IInstanceAIForm) {
</div> </div>
<div className="flex flex-col items-start gap-4"> <div className="flex flex-col items-start gap-4">
<Button variant="primary" size="lg" onClick={handleSubmit(onSubmit)} loading={isSubmitting}> <Button
{isSubmitting ? "Saving" : "Save changes"} variant="primary"
</Button> size="md"
stretch="auto"
onClick={handleSubmit(onSubmit)}
loading={isSubmitting}
label={isSubmitting ? "Saving" : "Save changes"}
/>
<div className="relative inline-flex items-center gap-1.5 rounded-sm border border-accent-subtle bg-accent-subtle px-4 py-2 text-caption-sm-regular text-accent-secondary"> <div className="relative inline-flex items-center gap-1.5 rounded-sm border border-accent-subtle bg-accent-subtle px-4 py-2 text-caption-sm-regular text-accent-secondary">
<Lightbulb className="size-4" /> <Lightbulb className="size-4" />

View File

@@ -6,9 +6,9 @@
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import useSWR from "swr"; import useSWR from "swr";
import { Skeleton } from "@plane/propel/skeleton";
// components // components
import { PageWrapper } from "@/components/common/page-wrapper"; import { PageWrapper } from "@/components/common/page-wrapper";
import { Skeleton } from "@/components/common/skeleton";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
// types // types

View File

@@ -10,8 +10,8 @@ import Link from "next/link";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
// plane internal packages // plane internal packages
import { API_BASE_URL } from "@plane/constants"; import { API_BASE_URL } from "@plane/constants";
import { Button, getButtonStyling } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { TOAST_TYPE, setToast } from "@/providers/toast";
import type { IFormattedInstanceConfiguration, TInstanceGiteaAuthenticationConfigurationKeys } from "@plane/types"; import type { IFormattedInstanceConfiguration, TInstanceGiteaAuthenticationConfigurationKeys } from "@plane/types";
// components // components
import { CodeBlock } from "@/components/common/code-block"; import { CodeBlock } from "@/components/common/code-block";
@@ -195,16 +195,21 @@ export function InstanceGiteaConfigForm(props: Props) {
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Button <Button
variant="primary" variant="primary"
size="lg" size="md"
stretch="auto"
onClick={(e) => void handleSubmit(onSubmit)(e)} onClick={(e) => void handleSubmit(onSubmit)(e)}
loading={isSubmitting} loading={isSubmitting}
disabled={!isDirty} disabled={!isDirty}
> label={isSubmitting ? "Saving" : "Save changes"}
{isSubmitting ? "Saving" : "Save changes"} />
</Button> <Button
<Link href="/authentication" className={getButtonStyling("secondary", "lg")} onClick={handleGoBack}> variant="secondary"
Go back size="md"
</Link> stretch="auto"
nativeButton={false}
render={<Link href="/authentication" onClick={handleGoBack} />}
label="Go back"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -8,14 +8,14 @@ import { useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import useSWR from "swr"; import useSWR from "swr";
// plane internal packages // plane internal packages
import { Skeleton } from "@plane/propel/skeleton"; import { Switch } from "@makeplane/propel/components/switch";
import { Switch } from "@plane/propel/switch";
import { setPromiseToast } from "@plane/propel/toast";
// assets // assets
import giteaLogo from "@/app/assets/logos/gitea-logo.svg?url"; import giteaLogo from "@/app/assets/logos/gitea-logo.svg?url";
// components // components
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card"; import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
import { PageWrapper } from "@/components/common/page-wrapper"; import { PageWrapper } from "@/components/common/page-wrapper";
import { Skeleton } from "@/components/common/skeleton";
import { setPromiseToast } from "@/providers/toast";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
// types // types
@@ -74,8 +74,8 @@ const InstanceGiteaAuthenticationPage = observer(function InstanceGiteaAuthentic
icon={<img src={giteaLogo} height={24} width={24} alt="Gitea Logo" />} icon={<img src={giteaLogo} height={24} width={24} alt="Gitea Logo" />}
config={ config={
<Switch <Switch
value={isGiteaEnabled} checked={isGiteaEnabled}
onChange={() => { onCheckedChange={() => {
updateConfig("IS_GITEA_ENABLED", isGiteaEnabled ? "0" : "1"); updateConfig("IS_GITEA_ENABLED", isGiteaEnabled ? "0" : "1");
}} }}
size="sm" size="sm"

View File

@@ -11,8 +11,8 @@ import { useForm } from "react-hook-form";
import { Monitor } from "lucide-react"; import { Monitor } from "lucide-react";
// plane internal packages // plane internal packages
import { API_BASE_URL } from "@plane/constants"; import { API_BASE_URL } from "@plane/constants";
import { Button, getButtonStyling } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { TOAST_TYPE, setToast } from "@/providers/toast";
import type { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types"; import type { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types";
// components // components
import { CodeBlock } from "@/components/common/code-block"; import { CodeBlock } from "@/components/common/code-block";
@@ -216,16 +216,21 @@ export function InstanceGithubConfigForm(props: Props) {
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Button <Button
variant="primary" variant="primary"
size="lg" size="md"
stretch="auto"
onClick={(e) => void handleSubmit(onSubmit)(e)} onClick={(e) => void handleSubmit(onSubmit)(e)}
loading={isSubmitting} loading={isSubmitting}
disabled={!isDirty} disabled={!isDirty}
> label={isSubmitting ? "Saving" : "Save changes"}
{isSubmitting ? "Saving" : "Save changes"} />
</Button> <Button
<Link href="/authentication" className={getButtonStyling("secondary", "lg")} onClick={handleGoBack}> variant="secondary"
Go back size="md"
</Link> stretch="auto"
nativeButton={false}
render={<Link href="/authentication" onClick={handleGoBack} />}
label="Go back"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -9,9 +9,7 @@ import { observer } from "mobx-react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import useSWR from "swr"; import useSWR from "swr";
// plane internal packages // plane internal packages
import { Skeleton } from "@plane/propel/skeleton"; import { Switch } from "@makeplane/propel/components/switch";
import { Switch } from "@plane/propel/switch";
import { setPromiseToast } from "@plane/propel/toast";
import { resolveGeneralTheme } from "@plane/utils"; import { resolveGeneralTheme } from "@plane/utils";
// assets // assets
import githubLightModeImage from "@/app/assets/logos/github-black.png?url"; import githubLightModeImage from "@/app/assets/logos/github-black.png?url";
@@ -19,6 +17,8 @@ import githubDarkModeImage from "@/app/assets/logos/github-white.png?url";
// components // components
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card"; import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
import { PageWrapper } from "@/components/common/page-wrapper"; import { PageWrapper } from "@/components/common/page-wrapper";
import { Skeleton } from "@/components/common/skeleton";
import { setPromiseToast } from "@/providers/toast";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
// types // types
@@ -89,8 +89,8 @@ const InstanceGithubAuthenticationPage = observer(function InstanceGithubAuthent
} }
config={ config={
<Switch <Switch
value={isGithubEnabled} checked={isGithubEnabled}
onChange={() => { onCheckedChange={() => {
updateConfig("IS_GITHUB_ENABLED", isGithubEnabled ? "0" : "1"); updateConfig("IS_GITHUB_ENABLED", isGithubEnabled ? "0" : "1");
}} }}
size="sm" size="sm"

View File

@@ -10,8 +10,8 @@ import Link from "next/link";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
// plane internal packages // plane internal packages
import { API_BASE_URL } from "@plane/constants"; import { API_BASE_URL } from "@plane/constants";
import { Button, getButtonStyling } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { TOAST_TYPE, setToast } from "@/providers/toast";
import type { IFormattedInstanceConfiguration, TInstanceGitlabAuthenticationConfigurationKeys } from "@plane/types"; import type { IFormattedInstanceConfiguration, TInstanceGitlabAuthenticationConfigurationKeys } from "@plane/types";
// components // components
import { CodeBlock } from "@/components/common/code-block"; import { CodeBlock } from "@/components/common/code-block";
@@ -198,16 +198,21 @@ export function InstanceGitlabConfigForm(props: Props) {
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Button <Button
variant="primary" variant="primary"
size="lg" size="md"
stretch="auto"
onClick={(e) => void handleSubmit(onSubmit)(e)} onClick={(e) => void handleSubmit(onSubmit)(e)}
loading={isSubmitting} loading={isSubmitting}
disabled={!isDirty} disabled={!isDirty}
> label={isSubmitting ? "Saving" : "Save changes"}
{isSubmitting ? "Saving" : "Save changes"} />
</Button> <Button
<Link href="/authentication" className={getButtonStyling("secondary", "lg")} onClick={handleGoBack}> variant="secondary"
Go back size="md"
</Link> stretch="auto"
nativeButton={false}
render={<Link href="/authentication" onClick={handleGoBack} />}
label="Go back"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -7,14 +7,14 @@
import { useState } from "react"; import { useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import useSWR from "swr"; import useSWR from "swr";
import { Skeleton } from "@plane/propel/skeleton"; import { Switch } from "@makeplane/propel/components/switch";
import { Switch } from "@plane/propel/switch";
import { setPromiseToast } from "@plane/propel/toast";
// assets // assets
import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url"; import GitlabLogo from "@/app/assets/logos/gitlab-logo.svg?url";
// components // components
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card"; import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
import { PageWrapper } from "@/components/common/page-wrapper"; import { PageWrapper } from "@/components/common/page-wrapper";
import { Skeleton } from "@/components/common/skeleton";
import { setPromiseToast } from "@/providers/toast";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
// types // types
@@ -73,8 +73,8 @@ const InstanceGitlabAuthenticationPage = observer(function InstanceGitlabAuthent
icon={<img src={GitlabLogo} height={24} width={24} alt="GitLab Logo" />} icon={<img src={GitlabLogo} height={24} width={24} alt="GitLab Logo" />}
config={ config={
<Switch <Switch
value={Boolean(parseInt(enableGitlabConfig))} checked={Boolean(parseInt(enableGitlabConfig))}
onChange={() => { onCheckedChange={() => {
if (Boolean(parseInt(enableGitlabConfig)) === true) { if (Boolean(parseInt(enableGitlabConfig)) === true) {
updateConfig("IS_GITLAB_ENABLED", "0"); updateConfig("IS_GITLAB_ENABLED", "0");
} else { } else {

View File

@@ -11,8 +11,8 @@ import { useForm } from "react-hook-form";
import { Monitor } from "lucide-react"; import { Monitor } from "lucide-react";
// plane internal packages // plane internal packages
import { API_BASE_URL } from "@plane/constants"; import { API_BASE_URL } from "@plane/constants";
import { Button, getButtonStyling } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { TOAST_TYPE, setToast } from "@/providers/toast";
import type { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types"; import type { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types";
// components // components
import { CodeBlock } from "@/components/common/code-block"; import { CodeBlock } from "@/components/common/code-block";
@@ -208,16 +208,21 @@ export function InstanceGoogleConfigForm(props: Props) {
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Button <Button
variant="primary" variant="primary"
size="lg" size="md"
stretch="auto"
onClick={(e) => void handleSubmit(onSubmit)(e)} onClick={(e) => void handleSubmit(onSubmit)(e)}
loading={isSubmitting} loading={isSubmitting}
disabled={!isDirty} disabled={!isDirty}
> label={isSubmitting ? "Saving" : "Save changes"}
{isSubmitting ? "Saving" : "Save changes"} />
</Button> <Button
<Link href="/authentication" className={getButtonStyling("secondary", "lg")} onClick={handleGoBack}> variant="secondary"
Go back size="md"
</Link> stretch="auto"
nativeButton={false}
render={<Link href="/authentication" onClick={handleGoBack} />}
label="Go back"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -7,14 +7,14 @@
import { useState } from "react"; import { useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import useSWR from "swr"; import useSWR from "swr";
import { Skeleton } from "@plane/propel/skeleton"; import { Switch } from "@makeplane/propel/components/switch";
import { Switch } from "@plane/propel/switch";
import { setPromiseToast } from "@plane/propel/toast";
// assets // assets
import GoogleLogo from "@/app/assets/logos/google-logo.svg?url"; import GoogleLogo from "@/app/assets/logos/google-logo.svg?url";
// components // components
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card"; import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
import { PageWrapper } from "@/components/common/page-wrapper"; import { PageWrapper } from "@/components/common/page-wrapper";
import { Skeleton } from "@/components/common/skeleton";
import { setPromiseToast } from "@/providers/toast";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
// types // types
@@ -74,8 +74,8 @@ const InstanceGoogleAuthenticationPage = observer(function InstanceGoogleAuthent
icon={<img src={GoogleLogo} height={24} width={24} alt="Google Logo" />} icon={<img src={GoogleLogo} height={24} width={24} alt="Google Logo" />}
config={ config={
<Switch <Switch
value={Boolean(parseInt(enableGoogleConfig))} checked={Boolean(parseInt(enableGoogleConfig))}
onChange={() => { onCheckedChange={() => {
if (Boolean(parseInt(enableGoogleConfig)) === true) { if (Boolean(parseInt(enableGoogleConfig)) === true) {
updateConfig("IS_GOOGLE_ENABLED", "0"); updateConfig("IS_GOOGLE_ENABLED", "0");
} else { } else {

View File

@@ -9,14 +9,14 @@ import { observer } from "mobx-react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import useSWR from "swr"; import useSWR from "swr";
// plane internal packages // plane internal packages
import { Skeleton } from "@plane/propel/skeleton"; import { Switch } from "@makeplane/propel/components/switch";
import { Switch } from "@plane/propel/switch";
import { setPromiseToast, setToast, TOAST_TYPE } from "@plane/propel/toast";
import type { TInstanceConfigurationKeys, TInstanceAuthenticationModes } from "@plane/types"; import type { TInstanceConfigurationKeys, TInstanceAuthenticationModes } from "@plane/types";
import { cn, resolveGeneralTheme } from "@plane/utils"; import { cn, resolveGeneralTheme } from "@plane/utils";
// components // components
import { PageWrapper } from "@/components/common/page-wrapper"; import { PageWrapper } from "@/components/common/page-wrapper";
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card"; import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
import { Skeleton } from "@/components/common/skeleton";
import { setPromiseToast, setToast, TOAST_TYPE } from "@/providers/toast";
// helpers // helpers
import { canDisableAuthMethod } from "@/helpers/authentication"; import { canDisableAuthMethod } from "@/helpers/authentication";
// hooks // hooks
@@ -132,8 +132,8 @@ const InstanceAuthenticationPage = observer(function InstanceAuthenticationPage(
<div className={`shrink-0 pr-4 ${isSubmitting && "opacity-70"}`}> <div className={`shrink-0 pr-4 ${isSubmitting && "opacity-70"}`}>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Switch <Switch
value={Boolean(parseInt(enableSignUpConfig))} checked={Boolean(parseInt(enableSignUpConfig))}
onChange={() => { onCheckedChange={() => {
if (Boolean(parseInt(enableSignUpConfig)) === true) { if (Boolean(parseInt(enableSignUpConfig)) === true) {
updateConfig("ENABLE_SIGNUP", "0"); updateConfig("ENABLE_SIGNUP", "0");
} else { } else {

View File

@@ -7,13 +7,13 @@
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
// types // types
import { Button } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { Select, SelectContent, SelectItem, SelectList, SelectTrigger } from "@makeplane/propel/components/select";
import type { IFormattedInstanceConfiguration, TInstanceEmailConfigurationKeys } from "@plane/types"; import type { IFormattedInstanceConfiguration, TInstanceEmailConfigurationKeys } from "@plane/types";
// components // components
import type { TControllerInputFormField } from "@/components/common/controller-input"; import type { TControllerInputFormField } from "@/components/common/controller-input";
import { ControllerInput } from "@/components/common/controller-input"; import { ControllerInput } from "@/components/common/controller-input";
import { CustomSelect } from "@/components/common/custom-select"; import { TOAST_TYPE, setToast } from "@/providers/toast";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
// local components // local components
@@ -163,19 +163,19 @@ export function InstanceEmailForm(props: IInstanceEmailForm) {
))} ))}
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<h4 className="text-13 text-tertiary">Email security</h4> <h4 className="text-13 text-tertiary">Email security</h4>
<CustomSelect <Select
value={emailSecurityKey} value={emailSecurityKey}
label={EMAIL_SECURITY_OPTIONS[emailSecurityKey]} onValueChange={(value) => handleEmailSecurityChange(value as TEmailSecurityKeys)}
onChange={handleEmailSecurityChange}
buttonClassName="rounded-md border-subtle"
input
> >
{Object.entries(EMAIL_SECURITY_OPTIONS).map(([key, value]) => ( <SelectTrigger size="lg" placeholder="Select email security" />
<CustomSelect.Option key={key} value={key} className="w-full"> <SelectContent>
{value} <SelectList>
</CustomSelect.Option> {Object.entries(EMAIL_SECURITY_OPTIONS).map(([key, value]) => (
))} <SelectItem key={key} value={key} label={value} size="lg" />
</CustomSelect> ))}
</SelectList>
</SelectContent>
</Select>
</div> </div>
</div> </div>
<div className="my-6 flex flex-col gap-6 border-t border-subtle pt-4"> <div className="my-6 flex flex-col gap-6 border-t border-subtle pt-4">
@@ -209,22 +209,22 @@ export function InstanceEmailForm(props: IInstanceEmailForm) {
<div className="flex max-w-4xl items-center gap-4 py-1"> <div className="flex max-w-4xl items-center gap-4 py-1">
<Button <Button
variant="primary" variant="primary"
size="lg" size="md"
stretch="auto"
onClick={handleSubmit(onSubmit)} onClick={handleSubmit(onSubmit)}
loading={isSubmitting} loading={isSubmitting}
disabled={!isValid || !isDirty} disabled={!isValid || !isDirty}
> label={isSubmitting ? "Saving" : "Save changes"}
{isSubmitting ? "Saving" : "Save changes"} />
</Button>
<Button <Button
variant="secondary" variant="secondary"
size="lg" size="md"
stretch="auto"
onClick={() => setIsSendTestEmailModalOpen(true)} onClick={() => setIsSendTestEmailModalOpen(true)}
loading={isSubmitting} loading={isSubmitting}
disabled={!isValid} disabled={!isValid}
> label="Send test email"
Send test email />
</Button>
</div> </div>
</div> </div>
); );

View File

@@ -7,11 +7,11 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import useSWR from "swr"; import useSWR from "swr";
import { Skeleton } from "@plane/propel/skeleton"; import { Switch } from "@makeplane/propel/components/switch";
import { Switch } from "@plane/propel/switch";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
// components // components
import { PageWrapper } from "@/components/common/page-wrapper"; import { PageWrapper } from "@/components/common/page-wrapper";
import { Skeleton } from "@/components/common/skeleton";
import { TOAST_TYPE, setToast } from "@/providers/toast";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
// types // types
@@ -76,7 +76,7 @@ const InstanceEmailPage = observer(function InstanceEmailPage(_props: Route.Comp
<Skeleton.Item width="24px" height="16px" className="rounded-full" /> <Skeleton.Item width="24px" height="16px" className="rounded-full" />
</Skeleton> </Skeleton>
) : ( ) : (
<Switch value={isSMTPEnabled} onChange={handleToggle} size="sm" disabled={isSubmitting} /> <Switch checked={isSMTPEnabled} onCheckedChange={handleToggle} size="sm" disabled={isSubmitting} />
), ),
}} }}
> >

View File

@@ -7,8 +7,8 @@
import { useEffect, useState, Fragment } from "react"; import { useEffect, useState, Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
// plane imports // plane imports
import { Button } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
import { Input } from "@plane/propel/input"; import { Input } from "@makeplane/propel/components/input";
import { InstanceService } from "@plane/services"; import { InstanceService } from "@plane/services";
type Props = { type Props = {
@@ -100,15 +100,17 @@ export function SendTestEmailModal(props: Props) {
</h3> </h3>
<div className="pt-6 pb-2"> <div className="pt-6 pb-2">
{sendEmailStep === ESendEmailSteps.SEND_EMAIL && ( {sendEmailStep === ESendEmailSteps.SEND_EMAIL && (
<Input <div className="w-full">
id="receiver_email" <Input
type="email" id="receiver_email"
value={receiverEmail} type="email"
onChange={(e) => setReceiverEmail(e.target.value)} size="lg"
placeholder="Receiver email" value={receiverEmail}
className="w-full resize-none text-16" onChange={(e) => setReceiverEmail(e.target.value)}
tabIndex={0} placeholder="Receiver email"
/> tabIndex={0}
/>
</div>
)} )}
{sendEmailStep === ESendEmailSteps.SUCCESS && ( {sendEmailStep === ESendEmailSteps.SUCCESS && (
<div className="flex flex-col gap-y-4 text-13"> <div className="flex flex-col gap-y-4 text-13">
@@ -121,13 +123,24 @@ export function SendTestEmailModal(props: Props) {
)} )}
{sendEmailStep === ESendEmailSteps.FAILED && <div className="text-13">{error}</div>} {sendEmailStep === ESendEmailSteps.FAILED && <div className="text-13">{error}</div>}
<div className="mt-5 flex items-center justify-end gap-2"> <div className="mt-5 flex items-center justify-end gap-2">
<Button variant="secondary" size="lg" onClick={handleClose} tabIndex={0}> <Button
{sendEmailStep === ESendEmailSteps.SEND_EMAIL ? "Cancel" : "Close"} variant="secondary"
</Button> size="md"
stretch="auto"
onClick={handleClose}
tabIndex={0}
label={sendEmailStep === ESendEmailSteps.SEND_EMAIL ? "Cancel" : "Close"}
/>
{sendEmailStep === ESendEmailSteps.SEND_EMAIL && ( {sendEmailStep === ESendEmailSteps.SEND_EMAIL && (
<Button variant="primary" size="lg" loading={isLoading} onClick={handleSubmit} tabIndex={0}> <Button
{isLoading ? "Sending email" : "Send email"} variant="primary"
</Button> size="md"
stretch="auto"
loading={isLoading}
onClick={handleSubmit}
tabIndex={0}
label={isLoading ? "Sending email" : "Send email"}
/>
)} )}
</div> </div>
</div> </div>

View File

@@ -8,13 +8,13 @@ import { observer } from "mobx-react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { Telescope } from "lucide-react"; import { Telescope } from "lucide-react";
// plane imports // plane imports
import { Button } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
import { Input } from "@plane/propel/input"; import { Input } from "@makeplane/propel/components/input";
import { Switch } from "@plane/propel/switch"; import { Switch } from "@makeplane/propel/components/switch";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IInstance, IInstanceAdmin } from "@plane/types"; import type { IInstance, IInstanceAdmin } from "@plane/types";
// components // components
import { ControllerInput } from "@/components/common/controller-input"; import { ControllerInput } from "@/components/common/controller-input";
import { TOAST_TYPE, setToast } from "@/providers/toast";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
@@ -72,28 +72,25 @@ export const GeneralConfigurationForm = observer(function GeneralConfigurationFo
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<h4 className="text-13 text-tertiary">Email</h4> <h4 className="text-13 text-tertiary">Email</h4>
<Input <div className="w-full">
id="email" <Input
name="email" id="email"
type="email" name="email"
value={instanceAdmins[0]?.user_detail?.email ?? ""} type="email"
placeholder="Admin email" size="lg"
className="w-full cursor-not-allowed !text-placeholder" value={instanceAdmins[0]?.user_detail?.email ?? ""}
autoComplete="on" placeholder="Admin email"
disabled autoComplete="on"
/> disabled
/>
</div>
</div> </div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<h4 className="text-13 text-tertiary">Instance ID</h4> <h4 className="text-13 text-tertiary">Instance ID</h4>
<Input <div className="w-full">
id="instance_id" <Input id="instance_id" name="instance_id" type="text" size="lg" value={instance.instance_id} disabled />
name="instance_id" </div>
type="text"
value={instance.instance_id}
className="w-full cursor-not-allowed rounded-md font-medium !text-placeholder"
disabled
/>
</div> </div>
</div> </div>
</div> </div>
@@ -128,7 +125,7 @@ export const GeneralConfigurationForm = observer(function GeneralConfigurationFo
control={control} control={control}
name="is_telemetry_enabled" name="is_telemetry_enabled"
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<Switch value={value ?? false} onChange={onChange} size="sm" disabled={isSubmitting} /> <Switch checked={value ?? false} onCheckedChange={onChange} size="sm" disabled={isSubmitting} />
)} )}
/> />
</div> </div>
@@ -138,14 +135,14 @@ export const GeneralConfigurationForm = observer(function GeneralConfigurationFo
<div> <div>
<Button <Button
variant="primary" variant="primary"
size="lg" size="md"
stretch="auto"
onClick={() => { onClick={() => {
void handleSubmit(onSubmit)(); void handleSubmit(onSubmit)();
}} }}
loading={isSubmitting} loading={isSubmitting}
> label={isSubmitting ? "Saving" : "Save changes"}
{isSubmitting ? "Saving" : "Save changes"} />
</Button>
</div> </div>
</div> </div>
); );

View File

@@ -5,11 +5,11 @@
*/ */
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { Button } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IFormattedInstanceConfiguration, TInstanceImageConfigurationKeys } from "@plane/types"; import type { IFormattedInstanceConfiguration, TInstanceImageConfigurationKeys } from "@plane/types";
// components // components
import { ControllerInput } from "@/components/common/controller-input"; import { ControllerInput } from "@/components/common/controller-input";
import { TOAST_TYPE, setToast } from "@/providers/toast";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
@@ -77,9 +77,14 @@ export function InstanceImageConfigForm(props: IInstanceImageConfigForm) {
</div> </div>
<div> <div>
<Button variant="primary" size="lg" onClick={handleSubmit(onSubmit)} loading={isSubmitting}> <Button
{isSubmitting ? "Saving" : "Save changes"} variant="primary"
</Button> size="md"
stretch="auto"
onClick={handleSubmit(onSubmit)}
loading={isSubmitting}
label={isSubmitting ? "Saving" : "Save changes"}
/>
</div> </div>
</div> </div>
); );

View File

@@ -6,9 +6,9 @@
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import useSWR from "swr"; import useSWR from "swr";
import { Skeleton } from "@plane/propel/skeleton";
// components // components
import { PageWrapper } from "@/components/common/page-wrapper"; import { PageWrapper } from "@/components/common/page-wrapper";
import { Skeleton } from "@/components/common/skeleton";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
// types // types

View File

@@ -11,7 +11,7 @@ import { LogOut, UserCog2, Palette } from "lucide-react";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
// plane internal packages // plane internal packages
import { API_BASE_URL } from "@plane/constants"; import { API_BASE_URL } from "@plane/constants";
import { Avatar } from "@plane/propel/avatar"; import { WorkspaceAvatar } from "@makeplane/propel/components/workspace-avatar";
import { AuthService } from "@plane/services"; import { AuthService } from "@plane/services";
import { getFileURL, cn } from "@plane/utils"; import { getFileURL, cn } from "@plane/utils";
// hooks // hooks
@@ -124,12 +124,11 @@ export const AdminSidebarDropdown = observer(function AdminSidebarDropdown() {
{!isSidebarCollapsed && currentUser && ( {!isSidebarCollapsed && currentUser && (
<Menu as="div" className="relative flex-shrink-0"> <Menu as="div" className="relative flex-shrink-0">
<Menu.Button className="grid place-items-center outline-none"> <Menu.Button className="grid place-items-center outline-none">
<Avatar <WorkspaceAvatar
name={currentUser.display_name} alt={currentUser.display_name ?? "Admin user"}
fallback={currentUser.display_name?.[0]?.toUpperCase()}
src={getFileURL(currentUser.avatar_url)} src={getFileURL(currentUser.avatar_url)}
size={24} size="sm"
shape="square"
className="!text-body-sm-medium"
/> />
</Menu.Button> </Menu.Button>

View File

@@ -11,8 +11,8 @@ import { HelpCircle, MessageSquare, MoveLeft } from "lucide-react";
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import { WEB_BASE_URL } from "@plane/constants"; import { WEB_BASE_URL } from "@plane/constants";
// plane internal packages // plane internal packages
import { GithubIcon, NewTabIcon, PageIcon } from "@plane/propel/icons"; import { Tooltip } from "@makeplane/propel/components/tooltip";
import { Tooltip } from "@plane/propel/tooltip"; import { Github, NewTabOutline, PagesOutline } from "@makeplane/propel/icons";
import { cn } from "@plane/utils"; import { cn } from "@plane/utils";
// hooks // hooks
import { useInstance, useTheme } from "@/hooks/store"; import { useInstance, useTheme } from "@/hooks/store";
@@ -22,7 +22,7 @@ const helpOptions = [
{ {
name: "Documentation", name: "Documentation",
href: "https://docs.plane.so/", href: "https://docs.plane.so/",
Icon: PageIcon, Icon: PagesOutline,
}, },
{ {
name: "Join our Forum", name: "Join our Forum",
@@ -32,7 +32,7 @@ const helpOptions = [
{ {
name: "Report a bug", name: "Report a bug",
href: "https://github.com/makeplane/plane/issues/new/choose", href: "https://github.com/makeplane/plane/issues/new/choose",
Icon: GithubIcon, Icon: Github,
}, },
]; ];
@@ -57,16 +57,28 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
)} )}
> >
<div className={`flex items-center gap-1 ${isSidebarCollapsed ? "flex-col justify-center" : "w-full"}`}> <div className={`flex items-center gap-1 ${isSidebarCollapsed ? "flex-col justify-center" : "w-full"}`}>
<Tooltip tooltipContent="Redirect to Plane" position="right" className="ml-4" disabled={!isSidebarCollapsed}> {!isSidebarCollapsed ? (
<a <>
href={redirectionLink} <a
className={`relative flex items-center gap-1 rounded-sm bg-layer-1 px-2 py-1 text-body-xs-medium whitespace-nowrap text-secondary`} href={redirectionLink}
> className={`relative flex items-center gap-1 rounded-sm bg-layer-1 px-2 py-1 text-body-xs-medium whitespace-nowrap text-secondary`}
<NewTabIcon width={14} height={14} /> >
{!isSidebarCollapsed && "Redirect to Plane"} <NewTabOutline width={14} height={14} />
</a> {!isSidebarCollapsed && "Redirect to Plane"}
</Tooltip> </a>
<Tooltip tooltipContent="Help" position={isSidebarCollapsed ? "right" : "top"} className="ml-4"> </>
) : (
<Tooltip label="Redirect to Plane" side="right">
<a
href={redirectionLink}
className={`relative flex items-center gap-1 rounded-sm bg-layer-1 px-2 py-1 text-body-xs-medium whitespace-nowrap text-secondary`}
>
<NewTabOutline width={14} height={14} />
{!isSidebarCollapsed && "Redirect to Plane"}
</a>
</Tooltip>
)}
<Tooltip label="Help" side={isSidebarCollapsed ? "right" : "top"}>
<button <button
type="button" type="button"
aria-label="Help" aria-label="Help"
@@ -78,7 +90,7 @@ export const AdminSidebarHelpSection = observer(function AdminSidebarHelpSection
<HelpCircle className="size-4" /> <HelpCircle className="size-4" />
</button> </button>
</Tooltip> </Tooltip>
<Tooltip tooltipContent="Toggle sidebar" position={isSidebarCollapsed ? "right" : "top"} className="ml-4"> <Tooltip label="Toggle sidebar" side={isSidebarCollapsed ? "right" : "top"}>
<button <button
type="button" type="button"
aria-label="Toggle sidebar" aria-label="Toggle sidebar"

View File

@@ -8,7 +8,7 @@ import { observer } from "mobx-react";
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
// plane internal packages // plane internal packages
import { Tooltip } from "@plane/propel/tooltip"; import { Tooltip } from "@makeplane/propel/components/tooltip";
import { cn } from "@plane/utils"; import { cn } from "@plane/utils";
// hooks // hooks
import { useTheme } from "@/hooks/store"; import { useTheme } from "@/hooks/store";
@@ -35,26 +35,49 @@ export const AdminSidebarMenu = observer(function AdminSidebarMenu() {
return ( return (
<Link key={item.href} href={item.href} onClick={handleItemClick}> <Link key={item.href} href={item.href} onClick={handleItemClick}>
<div> <div>
<Tooltip tooltipContent={item.name} position="right" className="ml-2" disabled={!isSidebarCollapsed}> {!isSidebarCollapsed ? (
<div <>
className={cn( <div
"group flex w-full items-center gap-3 rounded-md px-3 py-2 transition-colors outline-none", className={cn(
{ "group flex w-full items-center gap-3 rounded-md px-3 py-2 transition-colors outline-none",
"!bg-layer-transparent-active text-primary": isActive, {
"text-secondary hover:bg-layer-transparent-hover active:bg-layer-transparent-active": !isActive, "!bg-layer-transparent-active text-primary": isActive,
}, "text-secondary hover:bg-layer-transparent-hover active:bg-layer-transparent-active": !isActive,
isSidebarCollapsed ? "justify-center" : "w-[260px]" },
)} isSidebarCollapsed ? "justify-center" : "w-[260px]"
> )}
{<item.Icon className="h-4 w-4 flex-shrink-0" />} >
{!isSidebarCollapsed && ( {<item.Icon className="h-4 w-4 flex-shrink-0" />}
<div className="w-full"> {!isSidebarCollapsed && (
<div className={cn(`text-body-xs-medium transition-colors`)}>{item.name}</div> <div className="w-full">
<div className={cn(`text-caption-sm-regular transition-colors`)}>{item.description}</div> <div className={cn(`text-body-xs-medium transition-colors`)}>{item.name}</div>
</div> <div className={cn(`text-caption-sm-regular transition-colors`)}>{item.description}</div>
)} </div>
</div> )}
</Tooltip> </div>
</>
) : (
<Tooltip label={item.name} side="right">
<div
className={cn(
"group flex w-full items-center gap-3 rounded-md px-3 py-2 transition-colors outline-none",
{
"!bg-layer-transparent-active text-primary": isActive,
"text-secondary hover:bg-layer-transparent-hover active:bg-layer-transparent-active": !isActive,
},
isSidebarCollapsed ? "justify-center" : "w-[260px]"
)}
>
{<item.Icon className="h-4 w-4 flex-shrink-0" />}
{!isSidebarCollapsed && (
<div className="w-full">
<div className={cn(`text-body-xs-medium transition-colors`)}>{item.name}</div>
<div className={cn(`text-caption-sm-regular transition-colors`)}>{item.description}</div>
</div>
)}
</div>
</Tooltip>
)}
</div> </div>
</Link> </Link>
); );

View File

@@ -10,14 +10,14 @@ import { useRouter } from "next/navigation";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// plane imports // plane imports
import { WEB_BASE_URL, ORGANIZATION_SIZE, RESTRICTED_URLS } from "@plane/constants"; import { WEB_BASE_URL, ORGANIZATION_SIZE, RESTRICTED_URLS } from "@plane/constants";
import { Button, getButtonStyling } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
import { Input } from "@plane/propel/input"; import { Input, InputGroup } from "@makeplane/propel/components/input";
import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { Select, SelectContent, SelectItem, SelectList, SelectTrigger } from "@makeplane/propel/components/select";
import { InstanceWorkspaceService } from "@plane/services"; import { InstanceWorkspaceService } from "@plane/services";
import type { IWorkspace } from "@plane/types"; import type { IWorkspace } from "@plane/types";
import { validateSlug, validateWorkspaceName } from "@plane/utils"; import { validateSlug, validateWorkspaceName } from "@plane/utils";
// components // components
import { CustomSelect } from "@/components/common/custom-select"; import { TOAST_TYPE, setToast } from "@/providers/toast";
// hooks // hooks
import { useWorkspace } from "@/hooks/store"; import { useWorkspace } from "@/hooks/store";
@@ -107,22 +107,24 @@ export function WorkspaceCreateForm() {
validate: (value) => validateWorkspaceName(value, true), validate: (value) => validateWorkspaceName(value, true),
}} }}
render={({ field: { value, ref, onChange } }) => ( render={({ field: { value, ref, onChange } }) => (
<Input <InputGroup size="lg">
id="workspaceName" <Input
type="text" size="lg"
value={value} id="workspaceName"
onChange={(e) => { type="text"
onChange(e.target.value); value={value}
setValue("name", e.target.value); onChange={(e) => {
setValue("slug", e.target.value.toLocaleLowerCase().trim().replace(/ /g, "-"), { onChange(e.target.value);
shouldValidate: true, setValue("name", e.target.value);
}); setValue("slug", e.target.value.toLocaleLowerCase().trim().replace(/ /g, "-"), {
}} shouldValidate: true,
ref={ref} });
hasError={Boolean(errors.name)} }}
placeholder="Something familiar and recognizable is always best." ref={ref}
className="w-full" aria-invalid={Boolean(errors.name)}
/> placeholder="Something familiar and recognizable is always best."
/>
</InputGroup>
)} )}
/> />
<span className="text-11 text-danger-primary">{errors?.name?.message}</span> <span className="text-11 text-danger-primary">{errors?.name?.message}</span>
@@ -142,6 +144,7 @@ export function WorkspaceCreateForm() {
<Input <Input
id="workspaceUrl" id="workspaceUrl"
type="text" type="text"
size="lg"
value={value.toLocaleLowerCase().trim().replace(/ /g, "-")} value={value.toLocaleLowerCase().trim().replace(/ /g, "-")}
onChange={(e) => { onChange={(e) => {
if (/^[a-zA-Z0-9_-]+$/.test(e.target.value)) setInvalidSlug(false); if (/^[a-zA-Z0-9_-]+$/.test(e.target.value)) setInvalidSlug(false);
@@ -149,9 +152,8 @@ export function WorkspaceCreateForm() {
onChange(e.target.value.toLowerCase()); onChange(e.target.value.toLowerCase());
}} }}
ref={ref} ref={ref}
hasError={Boolean(errors.slug)} aria-invalid={Boolean(errors.slug)}
placeholder="workspace-name" placeholder="workspace-name"
className="block w-full rounded-md border-none bg-transparent !px-0 py-2 text-13"
/> />
)} )}
/> />
@@ -170,23 +172,16 @@ export function WorkspaceCreateForm() {
control={control} control={control}
rules={{ required: "This is a required field." }} rules={{ required: "This is a required field." }}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<CustomSelect <Select value={value} onValueChange={onChange}>
value={value} <SelectTrigger size="lg" placeholder={<span className="text-placeholder">Select a range</span>} />
onChange={onChange} <SelectContent>
label={ <SelectList>
ORGANIZATION_SIZE.find((c) => c === value) ?? ( {ORGANIZATION_SIZE.map((item) => (
<span className="text-placeholder">Select a range</span> <SelectItem key={item} value={item} label={item} size="lg" />
) ))}
} </SelectList>
buttonClassName="!border-[0.5px] !border-subtle !shadow-none" </SelectContent>
input </Select>
>
{ORGANIZATION_SIZE.map((item) => (
<CustomSelect.Option key={item} value={item}>
{item}
</CustomSelect.Option>
))}
</CustomSelect>
)} )}
/> />
{errors.organization_size && ( {errors.organization_size && (
@@ -198,16 +193,21 @@ export function WorkspaceCreateForm() {
<div className="flex max-w-4xl items-center gap-4 py-1"> <div className="flex max-w-4xl items-center gap-4 py-1">
<Button <Button
variant="primary" variant="primary"
size="lg" size="md"
stretch="auto"
onClick={handleSubmit(handleCreateWorkspace)} onClick={handleSubmit(handleCreateWorkspace)}
disabled={!isValid} disabled={!isValid}
loading={isSubmitting} loading={isSubmitting}
> label={isSubmitting ? "Creating workspace" : "Create workspace"}
{isSubmitting ? "Creating workspace" : "Create workspace"} />
</Button> <Button
<Link className={getButtonStyling("secondary", "lg")} href="/workspace"> variant="secondary"
Go back size="md"
</Link> stretch="auto"
nativeButton={false}
render={<Link href="/workspace" />}
label="Go back"
/>
</div> </div>
</div> </div>
); );

View File

@@ -10,15 +10,16 @@ import Link from "next/link";
import useSWR from "swr"; import useSWR from "swr";
import { Loader as LoaderIcon } from "lucide-react"; import { Loader as LoaderIcon } from "lucide-react";
// types // types
import { Button, getButtonStyling } from "@plane/propel/button"; import { AnchorButton } from "@makeplane/propel/components/anchor-button";
import { Skeleton } from "@plane/propel/skeleton"; import { Button } from "@makeplane/propel/components/button";
import { Switch } from "@plane/propel/switch"; import { Switch } from "@makeplane/propel/components/switch";
import { setPromiseToast } from "@plane/propel/toast";
import type { TInstanceConfigurationKeys } from "@plane/types"; import type { TInstanceConfigurationKeys } from "@plane/types";
import { cn } from "@plane/utils"; import { cn } from "@plane/utils";
// components // components
import { PageWrapper } from "@/components/common/page-wrapper"; import { PageWrapper } from "@/components/common/page-wrapper";
import { Skeleton } from "@/components/common/skeleton";
import { WorkspaceListItem } from "@/components/workspace/list-item"; import { WorkspaceListItem } from "@/components/workspace/list-item";
import { setPromiseToast } from "@/providers/toast";
// hooks // hooks
import { useInstance, useWorkspace } from "@/hooks/store"; import { useInstance, useWorkspace } from "@/hooks/store";
// types // types
@@ -96,8 +97,8 @@ const WorkspaceManagementPage = observer(function WorkspaceManagementPage(_props
<div className={`shrink-0 pr-4 ${isSubmitting && "opacity-70"}`}> <div className={`shrink-0 pr-4 ${isSubmitting && "opacity-70"}`}>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Switch <Switch
value={Boolean(parseInt(disableWorkspaceCreation))} checked={Boolean(parseInt(disableWorkspaceCreation))}
onChange={() => { onCheckedChange={() => {
if (Boolean(parseInt(disableWorkspaceCreation)) === true) { if (Boolean(parseInt(disableWorkspaceCreation)) === true) {
updateConfig("DISABLE_WORKSPACE_CREATION", "0"); updateConfig("DISABLE_WORKSPACE_CREATION", "0");
} else { } else {
@@ -131,9 +132,14 @@ const WorkspaceManagementPage = observer(function WorkspaceManagementPage(_props
</div> </div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Link href="/workspace/create" className={getButtonStyling("primary", "base")}> <Button
Create workspace variant="primary"
</Link> size="sm"
stretch="auto"
nativeButton={false}
render={<Link href="/workspace/create" />}
label="Create workspace"
/>
</div> </div>
</div> </div>
<div className="flex flex-col gap-4 py-2"> <div className="flex flex-col gap-4 py-2">
@@ -143,15 +149,13 @@ const WorkspaceManagementPage = observer(function WorkspaceManagementPage(_props
</div> </div>
{hasNextPage && ( {hasNextPage && (
<div className="flex justify-center"> <div className="flex justify-center">
<Button <AnchorButton
variant="link" variant="primary"
size="lg" size="md"
onClick={() => fetchNextWorkspaces()} onClick={() => fetchNextWorkspaces()}
disabled={workspaceLoader === "pagination"} loading={workspaceLoader === "pagination"}
> label="Load more"
Load more />
{workspaceLoader === "pagination" && <LoaderIcon className="h-3 w-3 animate-spin" />}
</Button>
</div> </div>
)} )}
</> </>

View File

@@ -8,7 +8,7 @@ import { Info } from "lucide-react";
// plane constants // plane constants
import type { TAdminAuthErrorInfo } from "@plane/constants"; import type { TAdminAuthErrorInfo } from "@plane/constants";
// icons // icons
import { CloseIcon } from "@plane/propel/icons"; import { CloseOutline } from "@makeplane/propel/icons";
type TAuthBanner = { type TAuthBanner = {
bannerData: TAdminAuthErrorInfo | undefined; bannerData: TAdminAuthErrorInfo | undefined;
@@ -31,7 +31,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 transition-all hover:bg-accent-primary/20" className="relative ml-auto flex h-6 w-6 cursor-pointer items-center justify-center rounded-xs text-accent-primary transition-all hover:bg-accent-primary/20"
onClick={() => handleBannerData && handleBannerData(undefined)} onClick={() => handleBannerData && handleBannerData(undefined)}
> >
<CloseIcon className="h-4 w-4 flex-shrink-0" /> <CloseOutline className="h-4 w-4 flex-shrink-0" />
</button> </button>
</div> </div>
); );

View File

@@ -5,7 +5,7 @@
*/ */
import Link from "next/link"; import Link from "next/link";
import { PlaneLockup } from "@plane/propel/icons"; import { PlaneLockup } from "@/components/common/plane-lockup";
export function AuthHeader() { export function AuthHeader() {
return ( return (

View File

@@ -10,9 +10,8 @@ import { Eye, EyeOff } from "lucide-react";
// plane internal packages // plane internal packages
import type { EAdminAuthErrorCodes, TAdminAuthErrorInfo } from "@plane/constants"; import type { EAdminAuthErrorCodes, TAdminAuthErrorInfo } from "@plane/constants";
import { API_BASE_URL } from "@plane/constants"; import { API_BASE_URL } from "@plane/constants";
import { Button } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
import { Input } from "@plane/propel/input"; import { Input, InputGroup } from "@makeplane/propel/components/input";
import { Spinner } from "@plane/propel/spinners";
import { AuthService } from "@plane/services"; import { AuthService } from "@plane/services";
// components // components
import { Banner } from "@/components/common/banner"; import { Banner } from "@/components/common/banner";
@@ -136,31 +135,31 @@ export function InstanceSignInForm() {
<label className="text-13 font-medium text-tertiary" htmlFor="email"> <label className="text-13 font-medium text-tertiary" htmlFor="email">
Email <span className="text-danger-primary">*</span> Email <span className="text-danger-primary">*</span>
</label> </label>
<Input <InputGroup size="lg">
className="w-full border border-subtle !bg-surface-1 placeholder:text-placeholder" <Input
id="email" size="lg"
name="email" id="email"
type="email" name="email"
inputSize="md" type="email"
placeholder="name@company.com" placeholder="name@company.com"
value={formData.email} value={formData.email}
onChange={(e) => handleFormChange("email", e.target.value)} onChange={(e) => handleFormChange("email", e.target.value)}
autoComplete="off" autoComplete="off"
autoFocus autoFocus
/> />
</InputGroup>
</div> </div>
<div className="w-full space-y-1"> <div className="w-full space-y-1">
<label className="text-13 font-medium text-tertiary" htmlFor="password"> <label className="text-13 font-medium text-tertiary" htmlFor="password">
Password <span className="text-danger-primary">*</span> Password <span className="text-danger-primary">*</span>
</label> </label>
<div className="relative"> <InputGroup size="lg">
<Input <Input
className="w-full border border-subtle !bg-surface-1 placeholder:text-placeholder" size="lg"
id="password" id="password"
name="password" name="password"
type={showPassword ? "text" : "password"} type={showPassword ? "text" : "password"}
inputSize="md"
placeholder="Enter your password" placeholder="Enter your password"
value={formData.password} value={formData.password}
onChange={(e) => handleFormChange("password", e.target.value)} onChange={(e) => handleFormChange("password", e.target.value)}
@@ -170,7 +169,7 @@ export function InstanceSignInForm() {
<button <button
type="button" type="button"
aria-label="Hide password" aria-label="Hide password"
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder" className="flex items-center justify-center text-placeholder"
onClick={() => setShowPassword(false)} onClick={() => setShowPassword(false)}
> >
<EyeOff className="h-4 w-4" /> <EyeOff className="h-4 w-4" />
@@ -179,18 +178,24 @@ export function InstanceSignInForm() {
<button <button
type="button" type="button"
aria-label="Show password" aria-label="Show password"
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder" className="flex items-center justify-center text-placeholder"
onClick={() => setShowPassword(true)} onClick={() => setShowPassword(true)}
> >
<Eye className="h-4 w-4" /> <Eye className="h-4 w-4" />
</button> </button>
)} )}
</div> </InputGroup>
</div> </div>
<div className="py-2"> <div className="py-2">
<Button type="submit" size="xl" className="w-full" disabled={isButtonDisabled}> <Button
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Sign in"} type="submit"
</Button> variant="primary"
size="lg"
stretch="full"
disabled={isButtonDisabled}
loading={isSubmitting}
label="Sign in"
/>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -7,7 +7,7 @@
import React from "react"; import React from "react";
import { Link } from "react-router"; import { Link } from "react-router";
// ui // ui
import { Button } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
// images // images
import Image404 from "@/app/assets/images/404.svg?url"; import Image404 from "@/app/assets/images/404.svg?url";
@@ -28,9 +28,7 @@ function PageNotFound() {
</div> </div>
<Link to="/general/"> <Link to="/general/">
<span className="flex justify-center py-4"> <span className="flex justify-center py-4">
<Button variant="secondary" size="lg"> <Button variant="secondary" size="md" stretch="auto" label="Go to general settings" />
Go to general settings
</Button>
</span> </span>
</Link> </Link>
</div> </div>

View File

@@ -7,7 +7,7 @@
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
// hooks // hooks
import { Switch } from "@plane/propel/switch"; import { Switch } from "@makeplane/propel/components/switch";
import type { TInstanceAuthenticationMethodKeys } from "@plane/types"; import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
// ui // ui
@@ -27,8 +27,8 @@ export const EmailCodesConfiguration = observer(function EmailCodesConfiguration
return ( return (
<Switch <Switch
value={Boolean(parseInt(enableMagicLogin))} checked={Boolean(parseInt(enableMagicLogin))}
onChange={() => { onCheckedChange={() => {
const newEnableMagicLogin = Boolean(parseInt(enableMagicLogin)) === true ? "0" : "1"; const newEnableMagicLogin = Boolean(parseInt(enableMagicLogin)) === true ? "0" : "1";
updateConfig("ENABLE_MAGIC_LINK_LOGIN", newEnableMagicLogin); updateConfig("ENABLE_MAGIC_LINK_LOGIN", newEnableMagicLogin);
}} }}

View File

@@ -9,10 +9,10 @@ import Link from "next/link";
// icons // icons
import { Settings2 } from "lucide-react"; import { Settings2 } from "lucide-react";
// plane internal packages // plane internal packages
import { getButtonStyling } from "@plane/propel/button"; import { AnchorButton } from "@makeplane/propel/components/anchor-button";
import { Switch } from "@plane/propel/switch"; import { Button } from "@makeplane/propel/components/button";
import { Switch } from "@makeplane/propel/components/switch";
import type { TInstanceAuthenticationMethodKeys } from "@plane/types"; import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { cn } from "@plane/utils";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
@@ -34,12 +34,16 @@ export const GiteaConfiguration = observer(function GiteaConfiguration(props: Pr
<> <>
{GiteaConfigured ? ( {GiteaConfigured ? (
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Link href="/authentication/gitea" className={cn(getButtonStyling("link", "base"), "font-medium")}> <AnchorButton
Edit variant="primary"
</Link> size="sm"
nativeButton={false}
render={<Link href="/authentication/gitea" />}
label="Edit"
/>
<Switch <Switch
value={Boolean(parseInt(GiteaConfig))} checked={Boolean(parseInt(GiteaConfig))}
onChange={() => { onCheckedChange={() => {
Boolean(parseInt(GiteaConfig)) === true Boolean(parseInt(GiteaConfig)) === true
? updateConfig("IS_GITEA_ENABLED", "0") ? updateConfig("IS_GITEA_ENABLED", "0")
: updateConfig("IS_GITEA_ENABLED", "1"); : updateConfig("IS_GITEA_ENABLED", "1");
@@ -49,10 +53,15 @@ export const GiteaConfiguration = observer(function GiteaConfiguration(props: Pr
/> />
</div> </div>
) : ( ) : (
<Link href="/authentication/gitea" className={cn(getButtonStyling("secondary", "base"), "text-tertiary")}> <Button
<Settings2 className="h-4 w-4 p-0.5 text-tertiary" /> variant="secondary"
Configure size="sm"
</Link> stretch="auto"
nativeButton={false}
render={<Link href="/authentication/gitea" />}
icon={<Settings2 className="h-4 w-4 p-0.5 text-tertiary" />}
label="Configure"
/>
)} )}
</> </>
); );

View File

@@ -9,10 +9,10 @@ import Link from "next/link";
// icons // icons
import { Settings2 } from "lucide-react"; import { Settings2 } from "lucide-react";
// plane internal packages // plane internal packages
import { getButtonStyling } from "@plane/propel/button"; import { AnchorButton } from "@makeplane/propel/components/anchor-button";
import { Switch } from "@plane/propel/switch"; import { Button } from "@makeplane/propel/components/button";
import { Switch } from "@makeplane/propel/components/switch";
import type { TInstanceAuthenticationMethodKeys } from "@plane/types"; import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { cn } from "@plane/utils";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
@@ -33,12 +33,16 @@ export const GithubConfiguration = observer(function GithubConfiguration(props:
<> <>
{isGithubConfigured ? ( {isGithubConfigured ? (
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Link href="/authentication/github" className={cn(getButtonStyling("link", "base"), "font-medium")}> <AnchorButton
Edit variant="primary"
</Link> size="sm"
nativeButton={false}
render={<Link href="/authentication/github" />}
label="Edit"
/>
<Switch <Switch
value={Boolean(parseInt(enableGithubConfig))} checked={Boolean(parseInt(enableGithubConfig))}
onChange={() => { onCheckedChange={() => {
const newEnableGithubConfig = Boolean(parseInt(enableGithubConfig)) === true ? "0" : "1"; const newEnableGithubConfig = Boolean(parseInt(enableGithubConfig)) === true ? "0" : "1";
updateConfig("IS_GITHUB_ENABLED", newEnableGithubConfig); updateConfig("IS_GITHUB_ENABLED", newEnableGithubConfig);
}} }}
@@ -47,10 +51,15 @@ export const GithubConfiguration = observer(function GithubConfiguration(props:
/> />
</div> </div>
) : ( ) : (
<Link href="/authentication/github" className={cn(getButtonStyling("secondary", "base"), "text-tertiary")}> <Button
<Settings2 className="h-4 w-4 p-0.5 text-tertiary" /> variant="secondary"
Configure size="sm"
</Link> stretch="auto"
nativeButton={false}
render={<Link href="/authentication/github" />}
icon={<Settings2 className="h-4 w-4 p-0.5 text-tertiary" />}
label="Configure"
/>
)} )}
</> </>
); );

View File

@@ -9,10 +9,10 @@ import Link from "next/link";
// icons // icons
import { Settings2 } from "lucide-react"; import { Settings2 } from "lucide-react";
// plane internal packages // plane internal packages
import { getButtonStyling } from "@plane/propel/button"; import { AnchorButton } from "@makeplane/propel/components/anchor-button";
import { Switch } from "@plane/propel/switch"; import { Button } from "@makeplane/propel/components/button";
import { Switch } from "@makeplane/propel/components/switch";
import type { TInstanceAuthenticationMethodKeys } from "@plane/types"; import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { cn } from "@plane/utils";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
@@ -33,12 +33,16 @@ export const GitlabConfiguration = observer(function GitlabConfiguration(props:
<> <>
{isGitlabConfigured ? ( {isGitlabConfigured ? (
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Link href="/authentication/gitlab" className={cn(getButtonStyling("link", "base"), "font-medium")}> <AnchorButton
Edit variant="primary"
</Link> size="sm"
nativeButton={false}
render={<Link href="/authentication/gitlab" />}
label="Edit"
/>
<Switch <Switch
value={Boolean(parseInt(enableGitlabConfig))} checked={Boolean(parseInt(enableGitlabConfig))}
onChange={() => { onCheckedChange={() => {
const newEnableGitlabConfig = Boolean(parseInt(enableGitlabConfig)) === true ? "0" : "1"; const newEnableGitlabConfig = Boolean(parseInt(enableGitlabConfig)) === true ? "0" : "1";
updateConfig("IS_GITLAB_ENABLED", newEnableGitlabConfig); updateConfig("IS_GITLAB_ENABLED", newEnableGitlabConfig);
}} }}
@@ -47,10 +51,15 @@ export const GitlabConfiguration = observer(function GitlabConfiguration(props:
/> />
</div> </div>
) : ( ) : (
<Link href="/authentication/gitlab" className={cn(getButtonStyling("secondary", "base"), "text-tertiary")}> <Button
<Settings2 className="h-4 w-4 p-0.5 text-tertiary" /> variant="secondary"
Configure size="sm"
</Link> stretch="auto"
nativeButton={false}
render={<Link href="/authentication/gitlab" />}
icon={<Settings2 className="h-4 w-4 p-0.5 text-tertiary" />}
label="Configure"
/>
)} )}
</> </>
); );

View File

@@ -9,10 +9,10 @@ import Link from "next/link";
// icons // icons
import { Settings2 } from "lucide-react"; import { Settings2 } from "lucide-react";
// plane internal packages // plane internal packages
import { getButtonStyling } from "@plane/propel/button"; import { AnchorButton } from "@makeplane/propel/components/anchor-button";
import { Switch } from "@plane/propel/switch"; import { Button } from "@makeplane/propel/components/button";
import { Switch } from "@makeplane/propel/components/switch";
import type { TInstanceAuthenticationMethodKeys } from "@plane/types"; import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { cn } from "@plane/utils";
// hooks // hooks
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
@@ -33,12 +33,16 @@ export const GoogleConfiguration = observer(function GoogleConfiguration(props:
<> <>
{isGoogleConfigured ? ( {isGoogleConfigured ? (
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Link href="/authentication/google" className={cn(getButtonStyling("link", "base"), "font-medium")}> <AnchorButton
Edit variant="primary"
</Link> size="sm"
nativeButton={false}
render={<Link href="/authentication/google" />}
label="Edit"
/>
<Switch <Switch
value={Boolean(parseInt(enableGoogleConfig))} checked={Boolean(parseInt(enableGoogleConfig))}
onChange={() => { onCheckedChange={() => {
const newEnableGoogleConfig = Boolean(parseInt(enableGoogleConfig)) === true ? "0" : "1"; const newEnableGoogleConfig = Boolean(parseInt(enableGoogleConfig)) === true ? "0" : "1";
updateConfig("IS_GOOGLE_ENABLED", newEnableGoogleConfig); updateConfig("IS_GOOGLE_ENABLED", newEnableGoogleConfig);
}} }}
@@ -47,10 +51,15 @@ export const GoogleConfiguration = observer(function GoogleConfiguration(props:
/> />
</div> </div>
) : ( ) : (
<Link href="/authentication/google" className={cn(getButtonStyling("secondary", "base"), "text-tertiary")}> <Button
<Settings2 className="h-4 w-4 p-0.5 text-tertiary" /> variant="secondary"
Configure size="sm"
</Link> stretch="auto"
nativeButton={false}
render={<Link href="/authentication/google" />}
icon={<Settings2 className="h-4 w-4 p-0.5 text-tertiary" />}
label="Configure"
/>
)} )}
</> </>
); );

View File

@@ -7,7 +7,7 @@
import React from "react"; import React from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
// hooks // hooks
import { Switch } from "@plane/propel/switch"; import { Switch } from "@makeplane/propel/components/switch";
import type { TInstanceAuthenticationMethodKeys } from "@plane/types"; import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
// ui // ui
@@ -27,8 +27,8 @@ export const PasswordLoginConfiguration = observer(function PasswordLoginConfigu
return ( return (
<Switch <Switch
value={Boolean(parseInt(enableEmailPassword))} checked={Boolean(parseInt(enableEmailPassword))}
onChange={() => { onCheckedChange={() => {
const newEnableEmailPassword = Boolean(parseInt(enableEmailPassword)) === true ? "0" : "1"; const newEnableEmailPassword = Boolean(parseInt(enableEmailPassword)) === true ? "0" : "1";
updateConfig("ENABLE_EMAIL_PASSWORD", newEnableEmailPassword); updateConfig("ENABLE_EMAIL_PASSWORD", newEnableEmailPassword);
}} }}

View File

@@ -1,37 +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 Link from "next/link";
import { Tooltip } from "@plane/propel/tooltip";
type Props = {
label?: string;
href?: string;
icon?: React.ReactNode | undefined;
};
export function BreadcrumbLink(props: Props) {
const { href, label, icon } = props;
return (
<Tooltip tooltipContent={label} position="bottom">
<li className="flex items-center space-x-2" tabIndex={-1}>
<div className="flex flex-wrap items-center gap-2.5">
{href ? (
<Link className="flex items-center gap-1 text-13 font-medium text-tertiary hover:text-primary" href={href}>
{icon && <div className="flex h-5 w-5 items-center justify-center overflow-hidden !text-16">{icon}</div>}
<div className="relative line-clamp-1 block max-w-[150px] truncate overflow-hidden">{label}</div>
</Link>
) : (
<div className="flex cursor-default items-center gap-1 text-13 font-medium text-primary">
{icon && <div className="flex h-5 w-5 items-center justify-center overflow-hidden">{icon}</div>}
<div className="relative line-clamp-1 block max-w-[150px] truncate overflow-hidden">{label}</div>
</div>
)}
</div>
</li>
</Tooltip>
);
}

View File

@@ -1,112 +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";
// plane internal packages
import { ChevronRightIcon } from "@plane/propel/icons";
// helpers
import { cn } from "@plane/utils";
type BreadcrumbsProps = {
className?: string;
children: React.ReactNode;
};
function BreadcrumbsRoot({ className, children }: BreadcrumbsProps) {
const [isSmallScreen, setIsSmallScreen] = React.useState(false);
React.useEffect(() => {
const handleResize = () => {
setIsSmallScreen(window.innerWidth <= 640); // Adjust this value as per your requirement
};
window.addEventListener("resize", handleResize);
handleResize(); // Call it initially to set the correct state
return () => window.removeEventListener("resize", handleResize);
}, []);
const childrenArray = React.Children.toArray(children);
return (
<div className={cn("flex flex-grow items-center gap-0.5 overflow-hidden", className)}>
{!isSmallScreen && (
<>
{childrenArray.map((child, index) => {
if (React.isValidElement<BreadcrumbItemProps>(child)) {
return React.cloneElement(child, {
isLast: index === childrenArray.length - 1,
});
}
return child;
})}
</>
)}
{isSmallScreen && childrenArray.length > 1 && (
<>
<div className="flex items-center gap-2.5 p-1">
<ChevronRightIcon className="h-3.5 w-3.5 flex-shrink-0 text-placeholder" aria-hidden="true" />
</div>
<div className="flex items-center gap-2.5 p-1">
{React.isValidElement(childrenArray[childrenArray.length - 1])
? React.cloneElement(childrenArray[childrenArray.length - 1] as React.ReactElement<BreadcrumbItemProps>, {
isLast: true,
})
: childrenArray[childrenArray.length - 1]}
</div>
</>
)}
{isSmallScreen && childrenArray.length === 1 && childrenArray}
</div>
);
}
// breadcrumb item
type BreadcrumbItemProps = {
component?: React.ReactNode;
showSeparator?: boolean;
isLast?: boolean;
};
function BreadcrumbItem(props: BreadcrumbItemProps) {
const { component, showSeparator = true, isLast = false } = props;
return (
<div className="flex h-6 items-center gap-0.5">
{component}
{showSeparator && !isLast && <BreadcrumbSeparator />}
</div>
);
}
// breadcrumb separator
type BreadcrumbSeparatorProps = {
className?: string;
containerClassName?: string;
iconClassName?: string;
};
function BreadcrumbSeparator(props: BreadcrumbSeparatorProps) {
const { className, containerClassName, iconClassName } = props;
return (
<div className={cn("relative flex h-full items-center justify-center px-1.5 py-1", className)}>
<div
className={cn(
"flex flex-shrink-0 items-center justify-center rounded-sm text-placeholder transition-all",
containerClassName
)}
>
<ChevronRightIcon className={cn("h-3.5 w-3.5 flex-shrink-0", iconClassName)} />
</div>
</div>
);
}
const Breadcrumbs = Object.assign(BreadcrumbsRoot, {
Item: BreadcrumbItem,
Separator: BreadcrumbSeparator,
});
export { Breadcrumbs };

View File

@@ -1,96 +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";
// helpers
import { cn } from "@plane/utils";
export interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
containerClassName?: string;
iconClassName?: string;
indeterminate?: boolean;
}
const Checkbox = React.forwardRef(function Checkbox(props: CheckboxProps, ref: React.ForwardedRef<HTMLInputElement>) {
const {
id,
name,
checked,
indeterminate = false,
disabled,
containerClassName,
iconClassName,
className,
...rest
} = props;
return (
<div className={cn("relative flex flex-shrink-0 gap-2", containerClassName)}>
<input
id={id}
ref={ref}
type="checkbox"
name={name}
checked={checked}
className={cn(
"size-4 shrink-0 cursor-pointer appearance-none rounded-[3px] border focus:outline-1 focus:outline-offset-4 focus:outline-accent-strong",
{
"cursor-not-allowed border-subtle bg-layer-1": disabled,
"border-strong bg-transparent hover:border-strong-1": !disabled,
"border-accent-strong-40 hover:border-accent-strong-40 bg-accent-primary hover:bg-accent-primary/80":
!disabled && (checked || indeterminate),
"border-none": checked,
},
className
)}
disabled={disabled}
{...rest}
/>
<svg
className={cn(
"pointer-events-none absolute top-1/2 left-1/2 hidden size-4 -translate-x-1/2 -translate-y-1/2 p-0.5 text-on-color outline-none",
{
block: checked,
"text-placeholder opacity-40": disabled,
},
iconClassName
)}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="20 6 9 17 4 12" />
</svg>
<svg
className={cn(
"pointer-events-none absolute top-1/2 left-1/2 hidden size-4 -translate-x-1/2 -translate-y-1/2 stroke-white p-0.5 outline-none",
{
"stroke-placeholder opacity-40": disabled,
block: indeterminate && !checked,
},
iconClassName
)}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
fill="none"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M5.75 4H2.25" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
);
});
Checkbox.displayName = "form-checkbox-field";
export { Checkbox };

View File

@@ -9,7 +9,7 @@ import Link from "next/link";
// headless ui // headless ui
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
// ui // ui
import { Button, getButtonStyling } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@@ -61,12 +61,15 @@ export function ConfirmDiscardModal(props: Props) {
</div> </div>
</div> </div>
<div className="flex items-center justify-end gap-2 p-4 sm:px-6"> <div className="flex items-center justify-end gap-2 p-4 sm:px-6">
<Button variant="secondary" size="lg" onClick={handleClose}> <Button variant="secondary" size="md" stretch="auto" onClick={handleClose} label="Keep editing" />
Keep editing <Button
</Button> variant="primary"
<Link href={onDiscardHref} className={getButtonStyling("primary", "base")}> size="sm"
Go back stretch="auto"
</Link> nativeButton={false}
render={<Link href={onDiscardHref} />}
label="Go back"
/>
</div> </div>
</Dialog.Panel> </Dialog.Panel>
</Transition.Child> </Transition.Child>

View File

@@ -10,8 +10,7 @@ import { Controller } from "react-hook-form";
// icons // icons
import { Eye, EyeOff } from "lucide-react"; import { Eye, EyeOff } from "lucide-react";
// plane internal packages // plane internal packages
import { Input } from "@plane/propel/input"; import { Input, InputGroup } from "@makeplane/propel/components/input";
import { cn } from "@plane/utils";
// Generic over the form's values because react-hook-form's Control is invariant: its // Generic over the form's values because react-hook-form's Control is invariant: its
// `_options.validate` narrows `name` to a keyof union, so `Control<any>` no longer // `_options.validate` narrows `name` to a keyof union, so `Control<any>` no longer
@@ -45,24 +44,22 @@ export function ControllerInput<TFieldValues extends FieldValues = FieldValues>(
return ( return (
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<h4 className="text-13 text-tertiary">{label}</h4> <h4 className="text-13 text-tertiary">{label}</h4>
<div className="relative"> <InputGroup size="lg">
<Controller <Controller
control={control} control={control}
name={name} name={name}
rules={{ required: required ? `${label} is required.` : false }} rules={{ required: required ? `${label} is required.` : false }}
render={({ field: { value, onChange, ref } }) => ( render={({ field: { value, onChange, ref } }) => (
<Input <Input
size="lg"
id={name} id={name}
name={name} name={name}
type={type === "password" && showPassword ? "text" : type} type={type === "password" && showPassword ? "text" : type}
value={value} value={value}
onChange={onChange} onChange={onChange}
ref={ref} ref={ref}
hasError={error} aria-invalid={error}
placeholder={placeholder} placeholder={placeholder}
className={cn("w-full rounded-md font-medium", {
"pr-10": type === "password",
})}
/> />
)} )}
/> />
@@ -71,7 +68,7 @@ export function ControllerInput<TFieldValues extends FieldValues = FieldValues>(
<button <button
type="button" type="button"
aria-label="Hide password" aria-label="Hide password"
className="absolute top-2.5 right-3 flex items-center justify-center text-placeholder" className="flex items-center justify-center text-placeholder"
onClick={() => setShowPassword(false)} onClick={() => setShowPassword(false)}
> >
<EyeOff className="h-4 w-4" /> <EyeOff className="h-4 w-4" />
@@ -80,13 +77,13 @@ export function ControllerInput<TFieldValues extends FieldValues = FieldValues>(
<button <button
type="button" type="button"
aria-label="Show password" aria-label="Show password"
className="absolute top-2.5 right-3 flex items-center justify-center text-placeholder" className="flex items-center justify-center text-placeholder"
onClick={() => setShowPassword(true)} onClick={() => setShowPassword(true)}
> >
<Eye className="h-4 w-4" /> <Eye className="h-4 w-4" />
</button> </button>
))} ))}
</div> </InputGroup>
{description && <p className="pt-0.5 text-11 text-tertiary">{description}</p>} {description && <p className="pt-0.5 text-11 text-tertiary">{description}</p>}
</div> </div>
); );

View File

@@ -7,7 +7,7 @@
import type { Control, FieldPath, FieldValues } from "react-hook-form"; import type { Control, FieldPath, FieldValues } from "react-hook-form";
import { Controller } from "react-hook-form"; import { Controller } from "react-hook-form";
// plane internal packages // plane internal packages
import { Switch } from "@plane/propel/switch"; import { Switch } from "@makeplane/propel/components/switch";
type Props<T extends FieldValues = FieldValues> = { type Props<T extends FieldValues = FieldValues> = {
control: Control<T>; control: Control<T>;
@@ -35,7 +35,7 @@ export function ControllerSwitch<T extends FieldValues>(props: Props<T>) {
render={({ field: { value, onChange } }) => { render={({ field: { value, onChange } }) => {
const parsedValue = Number.parseInt(typeof value === "string" ? value : String(value ?? "0"), 10); const parsedValue = Number.parseInt(typeof value === "string" ? value : String(value ?? "0"), 10);
const isOn = !Number.isNaN(parsedValue) && parsedValue !== 0; const isOn = !Number.isNaN(parsedValue) && parsedValue !== 0;
return <Switch value={isOn} onChange={() => onChange(isOn ? "0" : "1")} size="sm" />; return <Switch checked={isOn} onCheckedChange={() => onChange(isOn ? "0" : "1")} size="sm" />;
}} }}
/> />
</div> </div>

View File

@@ -6,9 +6,10 @@
import React from "react"; import React from "react";
// ui // ui
import { Button } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
import { CopyIcon } from "@plane/propel/icons"; import { CopyOutline } from "@makeplane/propel/icons";
import { TOAST_TYPE, setToast } from "@plane/propel/toast"; // components
import { TOAST_TYPE, setToast } from "@/providers/toast";
type Props = { type Props = {
label: string; label: string;
@@ -31,8 +32,11 @@ export function CopyField(props: Props) {
<h4 className="text-13 text-secondary">{label}</h4> <h4 className="text-13 text-secondary">{label}</h4>
<Button <Button
variant="secondary" variant="secondary"
size="lg" size="md"
className="flex items-center justify-between py-2" stretch="auto"
icon={<CopyOutline width={18} height={18} color="#B9B9B9" />}
iconPosition="end"
label={url}
onClick={() => { onClick={() => {
navigator.clipboard.writeText(url); navigator.clipboard.writeText(url);
setToast({ setToast({
@@ -41,10 +45,7 @@ export function CopyField(props: Props) {
message: `The ${label} has been successfully copied to your clipboard`, message: `The ${label} has been successfully copied to your clipboard`,
}); });
}} }}
> />
<p className="text-13 font-medium">{url}</p>
<CopyIcon width={18} height={18} color="#B9B9B9" />
</Button>
<div className="text-11 text-tertiary">{description}</div> <div className="text-11 text-tertiary">{description}</div>
</div> </div>
); );

View File

@@ -1,247 +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 { Combobox } from "@headlessui/react";
import React, { createContext, useCallback, useContext, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { usePopper } from "react-popper";
// plane internal packages
import { useOutsideClickDetector } from "@plane/hooks";
import { CheckIcon, ChevronDownIcon } from "@plane/propel/icons";
// helpers
import { cn } from "@plane/utils";
type Placement =
| "top"
| "top-start"
| "top-end"
| "bottom"
| "bottom-start"
| "bottom-end"
| "left"
| "left-start"
| "left-end"
| "right"
| "right-start"
| "right-end";
export interface CustomSelectProps {
children: React.ReactNode;
value: any;
onChange: any;
customButtonClassName?: string;
buttonClassName?: string;
className?: string;
customButton?: React.ReactNode;
disabled?: boolean;
input?: boolean;
label?: string | React.ReactNode;
maxHeight?: "sm" | "rg" | "md" | "lg";
noChevron?: boolean;
optionsClassName?: string;
placement?: Placement;
tabIndex?: number;
}
export interface CustomSelectItemProps {
children: React.ReactNode;
value: any;
className?: string;
}
const useDropdownKeyDown = (
onOpen: () => void,
onClose: () => void,
isOpen: boolean,
selectActiveItem?: () => void
): ((event: React.KeyboardEvent<HTMLElement>) => void) =>
useCallback(
(event: React.KeyboardEvent<HTMLElement>) => {
if (event.key === "Enter" && !event.nativeEvent.isComposing) {
if (!isOpen) {
event.stopPropagation();
onOpen();
} else {
selectActiveItem && selectActiveItem();
}
} else if (event.key === "Escape" && isOpen) {
event.stopPropagation();
onClose();
}
},
[isOpen, onOpen, onClose]
);
// Context to share the close handler with option components
const DropdownContext = createContext<() => void>(() => {});
function CustomSelect(props: CustomSelectProps) {
const {
customButtonClassName = "",
buttonClassName = "",
placement,
children,
className = "",
customButton,
disabled = false,
input = false,
label,
maxHeight = "md",
noChevron = false,
onChange,
optionsClassName = "",
value,
tabIndex,
} = props;
// states
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const [isOpen, setIsOpen] = useState(false);
// refs
const dropdownRef = useRef<HTMLDivElement | null>(null);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: placement ?? "bottom-start",
});
const openDropdown = useCallback(() => {
setIsOpen(true);
if (referenceElement) referenceElement.focus();
}, [referenceElement]);
const closeDropdown = useCallback(() => setIsOpen(false), []);
const handleKeyDown = useDropdownKeyDown(openDropdown, closeDropdown, isOpen);
useOutsideClickDetector(dropdownRef, closeDropdown);
const toggleDropdown = useCallback(() => {
if (isOpen) closeDropdown();
else openDropdown();
}, [closeDropdown, isOpen, openDropdown]);
return (
<DropdownContext.Provider value={closeDropdown}>
<Combobox
as="div"
ref={dropdownRef}
tabIndex={tabIndex}
value={value}
onChange={(val) => {
onChange?.(val);
closeDropdown();
}}
className={cn("relative flex-shrink-0 text-left", className)}
onKeyDown={handleKeyDown}
disabled={disabled}
>
<>
{customButton ? (
<Combobox.Button as={React.Fragment}>
<button
ref={setReferenceElement}
type="button"
className={`flex items-center justify-between gap-1 rounded text-11 ${
disabled ? "cursor-not-allowed text-secondary" : "cursor-pointer hover:bg-layer-transparent-hover"
} ${customButtonClassName}`}
onClick={toggleDropdown}
>
{customButton}
</button>
</Combobox.Button>
) : (
<Combobox.Button as={React.Fragment}>
<button
ref={setReferenceElement}
type="button"
className={cn(
"flex w-full items-center justify-between gap-1 rounded border border-strong",
{
"px-3 py-2 text-13": input,
"px-2 py-1 text-11": !input,
"cursor-not-allowed text-secondary": disabled,
"cursor-pointer hover:bg-layer-transparent-hover": !disabled,
},
buttonClassName
)}
onClick={toggleDropdown}
>
{label}
{!noChevron && !disabled && <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />}
</button>
</Combobox.Button>
)}
</>
{isOpen &&
createPortal(
<Combobox.Options as="ul" data-prevent-outside-click>
<div
className={cn(
"z-30 my-1 min-w-48 overflow-y-scroll rounded-md border-[0.5px] border-subtle-1 bg-surface-1 px-2 py-2.5 text-11 whitespace-nowrap focus:outline-none",
optionsClassName
)}
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
<div
className={cn("space-y-1 overflow-y-scroll", {
"max-h-60": maxHeight === "lg",
"max-h-48": maxHeight === "md",
"max-h-36": maxHeight === "rg",
"max-h-28": maxHeight === "sm",
})}
>
{children}
</div>
</div>
</Combobox.Options>,
document.body
)}
</Combobox>
</DropdownContext.Provider>
);
}
function Option(props: CustomSelectItemProps) {
const { children, value, className } = props;
const closeDropdown = useContext(DropdownContext);
const handleClick = useCallback(() => {
// Close dropdown for both new and already-selected options.
// Use setTimeout to ensure HeadlessUI's onChange handler fires first for new selections.
// For already-selected options, this ensures the dropdown closes since onChange won't fire.
setTimeout(() => {
closeDropdown();
}, 0);
}, [closeDropdown]);
return (
<Combobox.Option
as="li"
value={value}
className={({ active }) =>
cn(
"flex cursor-pointer items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 text-secondary select-none",
{
"bg-layer-transparent-hover": active,
},
className
)
}
onClick={handleClick}
>
{({ selected }) => (
<div className="flex w-full items-center justify-between gap-2">
{children}
{selected && <CheckIcon className="h-3.5 w-3.5 flex-shrink-0" />}
</div>
)}
</Combobox.Option>
);
}
CustomSelect.Option = Option;
export { CustomSelect };

View File

@@ -4,13 +4,20 @@
* See the LICENSE file for details. * See the LICENSE file for details.
*/ */
import { Fragment } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
// icons // icons
import { Menu, Settings } from "lucide-react"; import { Menu, Settings } from "lucide-react";
// components // plane internal packages
import { Breadcrumbs } from "@/components/common/breadcrumbs"; import {
import { BreadcrumbLink } from "../breadcrumb-link"; Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbSeparator,
} from "@makeplane/propel/components/breadcrumb";
// hooks // hooks
import { useTheme } from "@/hooks/store"; import { useTheme } from "@/hooks/store";
// local imports // local imports
@@ -62,26 +69,28 @@ export const AdminHeader = observer(function AdminHeader() {
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap"> <div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<HamburgerToggle /> <HamburgerToggle />
<div> <div>
<Breadcrumbs> <Breadcrumb aria-label="Breadcrumb">
<Breadcrumbs.Item <BreadcrumbList>
component={ <BreadcrumbItem>
<BreadcrumbLink <BreadcrumbLink
href="/general/"
label="Settings" label="Settings"
icon={<Settings className="h-4 w-4 text-tertiary" />} icon={<Settings className="h-4 w-4 text-tertiary" />}
render={<Link href="/general/" />}
/> />
} </BreadcrumbItem>
/> {breadcrumbItems.map(
{breadcrumbItems.map( (item) =>
(item) => item.title && (
item.title && ( <Fragment key={item.title}>
<Breadcrumbs.Item <BreadcrumbSeparator />
key={item.title} <BreadcrumbItem>
component={<BreadcrumbLink href={item.href} label={item.title} />} <BreadcrumbLink label={item.title} render={<Link href={item.href} />} />
/> </BreadcrumbItem>
) </Fragment>
)} )
</Breadcrumbs> )}
</BreadcrumbList>
</Breadcrumb>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -8,7 +8,7 @@ import { observer } from "mobx-react";
import Link from "next/link"; import Link from "next/link";
import { useTheme as useNextTheme } from "next-themes"; import { useTheme as useNextTheme } from "next-themes";
// ui // ui
import { Button, getButtonStyling } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
import { resolveGeneralTheme } from "@plane/utils"; import { resolveGeneralTheme } from "@plane/utils";
// hooks // hooks
import TakeoffIconDark from "@/app/assets/logos/takeoff-icon-dark.svg?url"; import TakeoffIconDark from "@/app/assets/logos/takeoff-icon-dark.svg?url";
@@ -33,12 +33,15 @@ export const NewUserPopup = observer(function NewUserPopup() {
workspace. workspace.
</div> </div>
<div className="flex items-center gap-4 pt-2"> <div className="flex items-center gap-4 pt-2">
<Link href="/workspace/create" className={getButtonStyling("primary", "lg")}> <Button
Create workspace variant="primary"
</Link> size="md"
<Button variant="secondary" size="lg" onClick={toggleNewUserPopup}> stretch="auto"
Close nativeButton={false}
</Button> render={<Link href="/workspace/create" />}
label="Create workspace"
/>
<Button variant="secondary" size="md" stretch="auto" onClick={toggleNewUserPopup} label="Close" />
</div> </div>
</div> </div>
<div className="flex shrink-0 items-center justify-center"> <div className="flex shrink-0 items-center justify-center">

View File

@@ -0,0 +1,58 @@
/**
* 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";
// Plane logo lockup. Not part of @makeplane/propel's icon set, so it is kept as a local asset.
type PlaneLockupProps = {
width?: string | number;
height?: string | number;
className?: string;
color?: string;
};
export function PlaneLockup({ width = "253", height = "53", className, color = "currentColor" }: PlaneLockupProps) {
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>
);
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
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.
type SkeletonProps = {
children: React.ReactNode;
className?: string;
ariaLabel?: string;
};
function SkeletonRoot({ children, className = "", ariaLabel = "Loading content" }: SkeletonProps) {
return (
<div data-slot="skeleton" className={cn("animate-pulse", className)} role="status" aria-label={ariaLabel}>
{children}
</div>
);
}
type ItemProps = {
height?: string;
width?: string;
className?: string;
};
function SkeletonItem({ height = "auto", width = "auto", className = "" }: ItemProps) {
return <div data-slot="skeleton-item" className={cn("rounded-md bg-layer-1", className)} style={{ height, width }} />;
}
const Skeleton = Object.assign(SkeletonRoot, { Item: SkeletonItem });
SkeletonRoot.displayName = "plane-ui-skeleton";
SkeletonItem.displayName = "plane-ui-skeleton-item";
export { Skeleton };

View File

@@ -6,7 +6,7 @@
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { Button } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
// assets // assets
import { AuthHeader } from "@/app/(all)/(home)/auth-header"; import { AuthHeader } from "@/app/(all)/(home)/auth-header";
import InstanceFailureDarkImage from "@/app/assets/instance/instance-failure-dark.svg?url"; import InstanceFailureDarkImage from "@/app/assets/instance/instance-failure-dark.svg?url";
@@ -34,9 +34,7 @@ export const InstanceFailureView = observer(function InstanceFailureView() {
</p> </p>
</div> </div>
<div className="flex justify-center"> <div className="flex justify-center">
<Button size="lg" onClick={handleRetry}> <Button variant="primary" size="md" stretch="auto" onClick={handleRetry} label="Retry" />
Retry
</Button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -10,14 +10,13 @@ import { useSearchParams } from "next/navigation";
import { Eye, EyeOff } from "lucide-react"; import { Eye, EyeOff } from "lucide-react";
// plane internal packages // plane internal packages
import { API_BASE_URL, E_PASSWORD_STRENGTH } from "@plane/constants"; import { API_BASE_URL, E_PASSWORD_STRENGTH } from "@plane/constants";
import { Button } from "@plane/propel/button"; import { Button } from "@makeplane/propel/components/button";
import { Input } from "@plane/propel/input"; import { Checkbox } from "@makeplane/propel/components/checkbox";
import { Spinner } from "@plane/propel/spinners"; import { Input, InputGroup } from "@makeplane/propel/components/input";
import { AuthService } from "@plane/services"; import { AuthService } from "@plane/services";
import { getPasswordStrength, validatePersonName, validateCompanyName } from "@plane/utils"; import { getPasswordStrength, validatePersonName, validateCompanyName } from "@plane/utils";
// components // components
import { AuthHeader } from "@/app/(all)/(home)/auth-header"; import { AuthHeader } from "@/app/(all)/(home)/auth-header";
import { Checkbox } from "@/components/common/checkbox";
import { PasswordStrengthIndicator } from "@/components/common/password-strength-indicator"; import { PasswordStrengthIndicator } from "@/components/common/password-strength-indicator";
import { Banner } from "../common/banner"; import { Banner } from "../common/banner";
import { FormHeader } from "./form-header"; import { FormHeader } from "./form-header";
@@ -168,46 +167,48 @@ export function InstanceSetupForm() {
<label className="text-13 font-medium text-tertiary" htmlFor="first_name"> <label className="text-13 font-medium text-tertiary" htmlFor="first_name">
First name <span className="text-danger-primary">*</span> First name <span className="text-danger-primary">*</span>
</label> </label>
<Input <InputGroup size="lg">
className="w-full border border-subtle !bg-surface-1 placeholder:text-placeholder" <Input
id="first_name" size="lg"
name="first_name" id="first_name"
type="text" name="first_name"
inputSize="md" type="text"
placeholder="Wilber" placeholder="Wilber"
value={formData.first_name} value={formData.first_name}
onChange={(e) => { onChange={(e) => {
const validation = validatePersonName(e.target.value); const validation = validatePersonName(e.target.value);
if (validation === true || e.target.value === "") { if (validation === true || e.target.value === "") {
handleFormChange("first_name", e.target.value); handleFormChange("first_name", e.target.value);
} }
}} }}
autoComplete="off" autoComplete="off"
autoFocus autoFocus
maxLength={50} maxLength={50}
/> />
</InputGroup>
</div> </div>
<div className="w-full space-y-1"> <div className="w-full space-y-1">
<label className="text-13 font-medium text-tertiary" htmlFor="last_name"> <label className="text-13 font-medium text-tertiary" htmlFor="last_name">
Last name <span className="text-danger-primary">*</span> Last name <span className="text-danger-primary">*</span>
</label> </label>
<Input <InputGroup size="lg">
className="w-full border border-subtle !bg-surface-1 placeholder:text-placeholder" <Input
id="last_name" size="lg"
name="last_name" id="last_name"
type="text" name="last_name"
inputSize="md" type="text"
placeholder="Wright" placeholder="Wright"
value={formData.last_name} value={formData.last_name}
onChange={(e) => { onChange={(e) => {
const validation = validatePersonName(e.target.value); const validation = validatePersonName(e.target.value);
if (validation === true || e.target.value === "") { if (validation === true || e.target.value === "") {
handleFormChange("last_name", e.target.value); handleFormChange("last_name", e.target.value);
} }
}} }}
autoComplete="off" autoComplete="off"
maxLength={50} maxLength={50}
/> />
</InputGroup>
</div> </div>
</div> </div>
@@ -215,18 +216,19 @@ export function InstanceSetupForm() {
<label className="text-13 font-medium text-tertiary" htmlFor="email"> <label className="text-13 font-medium text-tertiary" htmlFor="email">
Email <span className="text-danger-primary">*</span> Email <span className="text-danger-primary">*</span>
</label> </label>
<Input <InputGroup size="lg">
className="w-full border border-subtle !bg-surface-1 placeholder:text-placeholder" <Input
id="email" size="lg"
name="email" id="email"
type="email" name="email"
inputSize="md" type="email"
placeholder="name@company.com" placeholder="name@company.com"
value={formData.email} value={formData.email}
onChange={(e) => handleFormChange("email", e.target.value)} onChange={(e) => handleFormChange("email", e.target.value)}
hasError={errorData.type && errorData.type === EErrorCodes.INVALID_EMAIL ? true : false} aria-invalid={errorData.type && errorData.type === EErrorCodes.INVALID_EMAIL ? true : false}
autoComplete="off" autoComplete="off"
/> />
</InputGroup>
{errorData.type && errorData.type === EErrorCodes.INVALID_EMAIL && errorData.message && ( {errorData.type && errorData.type === EErrorCodes.INVALID_EMAIL && errorData.message && (
<p className="px-1 text-11 text-danger-primary">{errorData.message}</p> <p className="px-1 text-11 text-danger-primary">{errorData.message}</p>
)} )}
@@ -236,39 +238,39 @@ export function InstanceSetupForm() {
<label className="text-13 font-medium text-tertiary" htmlFor="company_name"> <label className="text-13 font-medium text-tertiary" htmlFor="company_name">
Company name <span className="text-danger-primary">*</span> Company name <span className="text-danger-primary">*</span>
</label> </label>
<Input <InputGroup size="lg">
className="w-full border border-subtle !bg-surface-1 placeholder:text-placeholder" <Input
id="company_name" size="lg"
name="company_name" id="company_name"
type="text" name="company_name"
inputSize="md" type="text"
placeholder="Company name" placeholder="Company name"
value={formData.company_name} value={formData.company_name}
onChange={(e) => { onChange={(e) => {
const validation = validateCompanyName(e.target.value, false); const validation = validateCompanyName(e.target.value, false);
if (validation === true || e.target.value === "") { if (validation === true || e.target.value === "") {
handleFormChange("company_name", e.target.value); handleFormChange("company_name", e.target.value);
} }
}} }}
maxLength={80} maxLength={80}
/> />
</InputGroup>
</div> </div>
<div className="w-full space-y-1"> <div className="w-full space-y-1">
<label className="text-13 font-medium text-tertiary" htmlFor="password"> <label className="text-13 font-medium text-tertiary" htmlFor="password">
Set a password <span className="text-danger-primary">*</span> Set a password <span className="text-danger-primary">*</span>
</label> </label>
<div className="relative"> <InputGroup size="lg">
<Input <Input
className="w-full border border-subtle !bg-surface-1 placeholder:text-placeholder" size="lg"
id="password" id="password"
name="password" name="password"
type={showPassword.password ? "text" : "password"} type={showPassword.password ? "text" : "password"}
inputSize="md"
placeholder="New password" placeholder="New password"
value={formData.password} value={formData.password}
onChange={(e) => handleFormChange("password", e.target.value)} onChange={(e) => handleFormChange("password", e.target.value)}
hasError={errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD ? true : false} aria-invalid={errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD ? true : false}
onFocus={() => setIsPasswordInputFocused(true)} onFocus={() => setIsPasswordInputFocused(true)}
onBlur={() => setIsPasswordInputFocused(false)} onBlur={() => setIsPasswordInputFocused(false)}
autoComplete="new-password" autoComplete="new-password"
@@ -277,7 +279,7 @@ export function InstanceSetupForm() {
<button <button
type="button" type="button"
aria-label="Hide password" aria-label="Hide password"
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder" className="flex items-center justify-center text-placeholder"
onClick={() => handleShowPassword("password")} onClick={() => handleShowPassword("password")}
> >
<EyeOff className="h-4 w-4" /> <EyeOff className="h-4 w-4" />
@@ -286,13 +288,13 @@ export function InstanceSetupForm() {
<button <button
type="button" type="button"
aria-label="Show password" aria-label="Show password"
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder" className="flex items-center justify-center text-placeholder"
onClick={() => handleShowPassword("password")} onClick={() => handleShowPassword("password")}
> >
<Eye className="h-4 w-4" /> <Eye className="h-4 w-4" />
</button> </button>
)} )}
</div> </InputGroup>
{errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD && errorData.message && ( {errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD && errorData.message && (
<p className="px-1 text-11 text-danger-primary">{errorData.message}</p> <p className="px-1 text-11 text-danger-primary">{errorData.message}</p>
)} )}
@@ -303,16 +305,15 @@ export function InstanceSetupForm() {
<label className="text-13 font-medium text-tertiary" htmlFor="confirm_password"> <label className="text-13 font-medium text-tertiary" htmlFor="confirm_password">
Confirm password <span className="text-danger-primary">*</span> Confirm password <span className="text-danger-primary">*</span>
</label> </label>
<div className="relative"> <InputGroup size="lg">
<Input <Input
size="lg"
type={showPassword.retypePassword ? "text" : "password"} type={showPassword.retypePassword ? "text" : "password"}
id="confirm_password" id="confirm_password"
name="confirm_password" name="confirm_password"
inputSize="md"
value={formData.confirm_password} value={formData.confirm_password}
onChange={(e) => handleFormChange("confirm_password", e.target.value)} onChange={(e) => handleFormChange("confirm_password", e.target.value)}
placeholder="Confirm password" placeholder="Confirm password"
className="w-full border border-subtle !bg-surface-1 pr-12 placeholder:text-placeholder"
onFocus={() => setIsRetryPasswordInputFocused(true)} onFocus={() => setIsRetryPasswordInputFocused(true)}
onBlur={() => setIsRetryPasswordInputFocused(false)} onBlur={() => setIsRetryPasswordInputFocused(false)}
autoComplete="new-password" autoComplete="new-password"
@@ -321,7 +322,7 @@ export function InstanceSetupForm() {
<button <button
type="button" type="button"
aria-label="Hide password" aria-label="Hide password"
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder" className="flex items-center justify-center text-placeholder"
onClick={() => handleShowPassword("retypePassword")} onClick={() => handleShowPassword("retypePassword")}
> >
<EyeOff className="h-4 w-4" /> <EyeOff className="h-4 w-4" />
@@ -330,13 +331,13 @@ export function InstanceSetupForm() {
<button <button
type="button" type="button"
aria-label="Show password" aria-label="Show password"
className="absolute top-3.5 right-3 flex items-center justify-center text-placeholder" className="flex items-center justify-center text-placeholder"
onClick={() => handleShowPassword("retypePassword")} onClick={() => handleShowPassword("retypePassword")}
> >
<Eye className="h-4 w-4" /> <Eye className="h-4 w-4" />
</button> </button>
)} )}
</div> </InputGroup>
{!!formData.confirm_password && {!!formData.confirm_password &&
formData.password !== formData.confirm_password && formData.password !== formData.confirm_password &&
renderPasswordMatchError && ( renderPasswordMatchError && (
@@ -347,10 +348,9 @@ export function InstanceSetupForm() {
<div className="relative flex gap-2"> <div className="relative flex gap-2">
<div> <div>
<Checkbox <Checkbox
className="h-4 w-4"
iconClassName="w-3 h-3"
id="is_telemetry_enabled" id="is_telemetry_enabled"
onChange={() => handleFormChange("is_telemetry_enabled", !formData.is_telemetry_enabled)} aria-label="Allow Plane to anonymously collect usage events"
onCheckedChange={(checked) => handleFormChange("is_telemetry_enabled", checked)}
checked={formData.is_telemetry_enabled} checked={formData.is_telemetry_enabled}
/> />
</div> </div>
@@ -368,9 +368,15 @@ export function InstanceSetupForm() {
</div> </div>
<div className="py-2"> <div className="py-2">
<Button type="submit" size="xl" className="w-full" disabled={isButtonDisabled}> <Button
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Continue"} type="submit"
</Button> variant="primary"
size="lg"
stretch="full"
disabled={isButtonDisabled}
loading={isSubmitting}
label="Continue"
/>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -7,9 +7,9 @@
import { observer } from "mobx-react"; import { observer } from "mobx-react";
// plane internal packages // plane internal packages
import { Tooltip } from "@makeplane/propel/components/tooltip";
import { NewTabOutline } from "@makeplane/propel/icons";
import { WEB_BASE_URL } from "@plane/constants"; import { WEB_BASE_URL } from "@plane/constants";
import { NewTabIcon } from "@plane/propel/icons";
import { Tooltip } from "@plane/propel/tooltip";
import { getFileURL } from "@plane/utils"; import { getFileURL } from "@plane/utils";
// hooks // hooks
import { useWorkspace } from "@/hooks/store"; import { useWorkspace } from "@/hooks/store";
@@ -52,7 +52,7 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
<div className="flex flex-col items-start gap-1"> <div className="flex flex-col items-start gap-1">
<div className="flex w-full flex-wrap items-center gap-2.5"> <div className="flex w-full flex-wrap items-center gap-2.5">
<h3 className={`text-14 font-medium capitalize`}>{workspace.name}</h3>/ <h3 className={`text-14 font-medium capitalize`}>{workspace.name}</h3>/
<Tooltip tooltipContent="The unique URL of your workspace"> <Tooltip label="The unique URL of your workspace">
<h4 className="text-13 text-tertiary">[{workspace.slug}]</h4> <h4 className="text-13 text-tertiary">[{workspace.slug}]</h4>
</Tooltip> </Tooltip>
</div> </div>
@@ -82,7 +82,7 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
</div> </div>
</div> </div>
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<NewTabIcon width={14} height={16} className="text-placeholder group-hover:text-secondary" /> <NewTabOutline width={14} height={16} className="text-placeholder group-hover:text-secondary" />
</div> </div>
</a> </a>
); );

View File

@@ -6,7 +6,7 @@
import { Image, BrainCog, Cog, Mail } from "lucide-react"; import { Image, BrainCog, Cog, Mail } from "lucide-react";
// plane imports // plane imports
import { LockIcon, WorkspaceIcon } from "@plane/propel/icons"; import { LockOutline, WorkspaceOutline } from "@makeplane/propel/icons";
// types // types
import type { TSidebarMenuItem } from "./types"; import type { TSidebarMenuItem } from "./types";
@@ -26,13 +26,13 @@ export const coreSidebarMenuLinks: Record<TCoreSidebarMenuKey, TSidebarMenuItem>
href: `/email/`, href: `/email/`,
}, },
workspace: { workspace: {
Icon: WorkspaceIcon, Icon: WorkspaceOutline,
name: "Workspaces", name: "Workspaces",
description: "Manage all workspaces on this instance.", description: "Manage all workspaces on this instance.",
href: `/workspace/`, href: `/workspace/`,
}, },
authentication: { authentication: {
Icon: LockIcon, Icon: LockOutline,
name: "Authentication", name: "Authentication",
description: "Configure authentication modes.", description: "Configure authentication modes.",
href: `/authentication/`, href: `/authentication/`,

View File

@@ -23,9 +23,9 @@
"@fontsource/ibm-plex-mono": "catalog:", "@fontsource/ibm-plex-mono": "catalog:",
"@fontsource/material-symbols-rounded": "catalog:", "@fontsource/material-symbols-rounded": "catalog:",
"@headlessui/react": "catalog:", "@headlessui/react": "catalog:",
"@makeplane/propel": "0.2.0",
"@plane/constants": "workspace:*", "@plane/constants": "workspace:*",
"@plane/hooks": "workspace:*", "@plane/hooks": "workspace:*",
"@plane/propel": "workspace:*",
"@plane/services": "workspace:*", "@plane/services": "workspace:*",
"@plane/types": "workspace:*", "@plane/types": "workspace:*",
"@plane/utils": "workspace:*", "@plane/utils": "workspace:*",
@@ -39,7 +39,6 @@
"react": "catalog:", "react": "catalog:",
"react-dom": "catalog:", "react-dom": "catalog:",
"react-hook-form": "catalog:", "react-hook-form": "catalog:",
"react-popper": "catalog:",
"react-router": "catalog:", "react-router": "catalog:",
"serve": "catalog:", "serve": "catalog:",
"swr": "catalog:", "swr": "catalog:",

View File

@@ -4,11 +4,80 @@
* See the LICENSE file for details. * See the LICENSE file for details.
*/ */
import { useTheme } from "next-themes"; import { createToastManager, ToastProvider } from "@makeplane/propel/components/toast";
import { Toast } from "@plane/propel/toast"; import type { ToastData, ToastVariant } from "@makeplane/propel/components/toast";
import { resolveGeneralTheme } from "@plane/utils"; 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.
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] },
});
type PromiseToastData<TValue> = {
title: string;
message?: (data: TValue) => string;
};
type PromiseToastOptions<TValue> = {
loading?: string;
success: PromiseToastData<TValue>;
error: PromiseToastData<TValue>;
};
export const setPromiseToast = <TValue,>(promise: Promise<TValue>, options: PromiseToastOptions<TValue>): void => {
void toastManager.promise<TValue, ToastData>(promise, {
loading: {
title: options.loading ?? "Loading...",
data: { variant: "neutral" },
},
success: (data) => ({
title: options.success.title,
description: options.success.message?.(data),
data: { variant: "success" },
}),
error: (data) => ({
title: options.error.title,
description: options.error.message?.(data),
data: { variant: "danger" },
}),
});
};
export function ToastWithTheme() { export function ToastWithTheme() {
const { resolvedTheme } = useTheme(); return (
return <Toast theme={resolveGeneralTheme(resolvedTheme)} />; <ToastProvider
toastManager={toastManager}
close={
<IconButton variant="ghost" size="sm" aria-label="Close notification" icon={<Icon icon={CloseOutline} />} />
}
/>
);
} }

View File

@@ -1,4 +1,9 @@
@import "@plane/tailwind-config/index.css"; @import "@plane/tailwind-config/index.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 utility classes used by its components are emitted, plus the animation
tokens (animate-spinner-reveal, animate-progress-indeterminate) its components rely on. */
@import "@makeplane/propel/styles";
.shadow-custom { .shadow-custom {
box-shadow: 2px 2px 8px 2px rgba(234, 231, 250, 0.3); /* Convert #EAE7FA4D to rgba */ box-shadow: 2px 2px 8px 2px rgba(234, 231, 250, 0.3); /* Convert #EAE7FA4D to rgba */

9
pnpm-lock.yaml generated
View File

@@ -610,15 +610,15 @@ importers:
'@headlessui/react': '@headlessui/react':
specifier: 'catalog:' specifier: 'catalog:'
version: 2.2.10(react-dom@19.2.8(react@19.2.8))(react@19.2.8) 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': '@plane/constants':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/constants version: link:../../packages/constants
'@plane/hooks': '@plane/hooks':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/hooks version: link:../../packages/hooks
'@plane/propel':
specifier: workspace:*
version: link:../../packages/propel
'@plane/services': '@plane/services':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/services version: link:../../packages/services
@@ -658,9 +658,6 @@ importers:
react-hook-form: react-hook-form:
specifier: 'catalog:' specifier: 'catalog:'
version: 7.84.0(react@19.2.8) version: 7.84.0(react@19.2.8)
react-popper:
specifier: 'catalog:'
version: 2.3.0(@popperjs/core@2.11.8)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
react-router: react-router:
specifier: 'catalog:' specifier: 'catalog:'
version: 8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) version: 8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)