-
Notifications
You must be signed in to change notification settings - Fork 3.1k
[WEB-4921] refactor: implement propel tabs everywhere #8235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: preview
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,11 +5,12 @@ import { useDropzone } from "react-dropzone"; | |
| import type { Control } from "react-hook-form"; | ||
| import { Controller } from "react-hook-form"; | ||
| import useSWR from "swr"; | ||
| import { Tab, Popover } from "@headlessui/react"; | ||
| import { Popover } from "@headlessui/react"; | ||
| // plane imports | ||
| import { ACCEPTED_COVER_IMAGE_MIME_TYPES_FOR_REACT_DROPZONE, MAX_FILE_SIZE } from "@plane/constants"; | ||
| import { useOutsideClickDetector } from "@plane/hooks"; | ||
| import { Button } from "@plane/propel/button"; | ||
| import { Tabs } from "@plane/propel/tabs"; | ||
| import { TOAST_TYPE, setToast } from "@plane/propel/toast"; | ||
| import { EFileAssetType } from "@plane/types"; | ||
| import { Input, Loader } from "@plane/ui"; | ||
|
|
@@ -197,91 +198,88 @@ export const ImagePickerPopover = observer(function ImagePickerPopover(props: Pr | |
| ref={imagePickerRef} | ||
| className="flex h-96 w-80 flex-col overflow-auto rounded border border-custom-border-300 bg-custom-background-100 p-3 shadow-2xl md:h-[28rem] md:w-[36rem]" | ||
| > | ||
| <Tab.Group> | ||
| <Tab.List as="span" className="inline-block rounded bg-custom-background-80 p-1"> | ||
| <Tabs defaultValue={tabOptions[0].key} className={"h-full overflow-hidden"}> | ||
| <Tabs.List className="p-1 w-fit"> | ||
| {tabOptions.map((tab) => ( | ||
| <Tab | ||
| <Tabs.Trigger | ||
| key={tab.key} | ||
| className={({ selected }) => | ||
| `rounded px-4 py-1 text-center text-sm outline-none transition-colors ${ | ||
| selected ? "bg-custom-primary text-white" : "text-custom-text-100" | ||
| }` | ||
| } | ||
| value={tab.key} | ||
| className="rounded px-4 py-1 text-center text-sm outline-none transition-colors data-[selected]:bg-custom-primary data-[selected]:text-white text-custom-text-100" | ||
| > | ||
| {tab.title} | ||
| </Tab> | ||
| </Tabs.Trigger> | ||
| ))} | ||
| </Tab.List> | ||
| <Tab.Panels className="vertical-scrollbar scrollbar-md h-full w-full flex-1 overflow-y-auto overflow-x-hidden"> | ||
| <Tab.Panel className="mt-4 h-full w-full space-y-4"> | ||
| {(unsplashImages || !unsplashError) && ( | ||
| <> | ||
| <div className="flex gap-x-2"> | ||
| <Controller | ||
| control={control} | ||
| name="search" | ||
| render={({ field: { value, ref } }) => ( | ||
| <Input | ||
| id="search" | ||
| name="search" | ||
| type="text" | ||
| onKeyDown={(e) => { | ||
| if (e.key === "Enter") { | ||
| e.preventDefault(); | ||
| setSearchParams(formData.search); | ||
| } | ||
| </Tabs.List> | ||
|
Comment on lines
+201
to
+212
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n apps/web/core/components/core/image-picker-popover.tsx | head -100Repository: makeplane/plane Length of output: 3873 🏁 Script executed: sed -n '190,220p' apps/web/core/components/core/image-picker-popover.tsxRepository: makeplane/plane Length of output: 1486 🏁 Script executed: sed -n '220,280p' apps/web/core/components/core/image-picker-popover.tsxRepository: makeplane/plane Length of output: 3067 🏁 Script executed: sed -n '275,350p' apps/web/core/components/core/image-picker-popover.tsxRepository: makeplane/plane Length of output: 3911 🏁 Script executed: sed -n '360,380p' apps/web/core/components/core/image-picker-popover.tsxRepository: makeplane/plane Length of output: 485 Filter disabled tabs and set default to first enabled tab. The If Filter tabs by - <Tabs defaultValue={tabOptions[0].key} className={"h-full overflow-hidden"}>
+ <Tabs defaultValue={tabOptions.find(tab => tab.isEnabled)?.key ?? "images"} className={"h-full overflow-hidden"}>
<Tabs.List className="p-1 w-fit">
- {tabOptions.map((tab) => (
+ {tabOptions.filter(tab => tab.isEnabled).map((tab) => (🤖 Prompt for AI Agents |
||
|
|
||
| <div className="mt-4 flex-1 overflow-auto"> | ||
| {(unsplashImages || !unsplashError) && ( | ||
| <Tabs.Content className="h-full w-full space-y-4" value="unsplash"> | ||
| <div className="flex gap-x-2"> | ||
| <Controller | ||
| control={control} | ||
| name="search" | ||
| render={({ field: { value, ref } }) => ( | ||
| <Input | ||
| id="search" | ||
| name="search" | ||
| type="text" | ||
| onKeyDown={(e) => { | ||
| if (e.key === "Enter") { | ||
| e.preventDefault(); | ||
| setSearchParams(formData.search); | ||
| } | ||
| }} | ||
| value={value} | ||
| onChange={(e) => setFormData({ ...formData, search: e.target.value })} | ||
| ref={ref} | ||
| placeholder="Search for images" | ||
| className="w-full text-sm" | ||
| /> | ||
| )} | ||
| /> | ||
| <Button variant="primary" onClick={() => setSearchParams(formData.search)} size="sm"> | ||
| Search | ||
| </Button> | ||
| </div> | ||
| {unsplashImages ? ( | ||
| unsplashImages.length > 0 ? ( | ||
| <div className="grid grid-cols-4 gap-4"> | ||
| {unsplashImages.map((image) => ( | ||
| <div | ||
| key={image.id} | ||
| className="relative col-span-2 aspect-video md:col-span-1" | ||
| onClick={() => { | ||
| setIsOpen(false); | ||
| onChange(image.urls.regular); | ||
| }} | ||
| value={value} | ||
| onChange={(e) => setFormData({ ...formData, search: e.target.value })} | ||
| ref={ref} | ||
| placeholder="Search for images" | ||
| className="w-full text-sm" | ||
| /> | ||
| )} | ||
| /> | ||
| <Button variant="primary" onClick={() => setSearchParams(formData.search)} size="sm"> | ||
| Search | ||
| </Button> | ||
| </div> | ||
| {unsplashImages ? ( | ||
| unsplashImages.length > 0 ? ( | ||
| <div className="grid grid-cols-4 gap-4"> | ||
| {unsplashImages.map((image) => ( | ||
| <div | ||
| key={image.id} | ||
| className="relative col-span-2 aspect-video md:col-span-1" | ||
| onClick={() => { | ||
| setIsOpen(false); | ||
| onChange(image.urls.regular); | ||
| }} | ||
| > | ||
| <img | ||
| src={image.urls.small} | ||
| alt={image.alt_description} | ||
| className="absolute left-0 top-0 h-full w-full cursor-pointer rounded object-cover" | ||
| /> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| ) : ( | ||
| <p className="pt-7 text-center text-xs text-custom-text-300">No images found.</p> | ||
| ) | ||
| > | ||
| <img | ||
| src={image.urls.small} | ||
| alt={image.alt_description} | ||
| className="absolute left-0 top-0 h-full w-full cursor-pointer rounded object-cover" | ||
| /> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| ) : ( | ||
| <Loader className="grid grid-cols-4 gap-4"> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| </Loader> | ||
| )} | ||
| </> | ||
| )} | ||
| </Tab.Panel> | ||
| <Tab.Panel className="mt-4 h-full w-full space-y-4"> | ||
| <p className="pt-7 text-center text-xs text-custom-text-300">No images found.</p> | ||
| ) | ||
| ) : ( | ||
| <Loader className="grid grid-cols-4 gap-4"> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| <Loader.Item height="80px" width="100%" /> | ||
| </Loader> | ||
| )} | ||
| </Tabs.Content> | ||
| )} | ||
|
|
||
| <Tabs.Content className="h-full w-full space-y-4" value="images"> | ||
| <div className="grid grid-cols-4 gap-4"> | ||
| {Object.values(STATIC_COVER_IMAGES).map((imageUrl, index) => ( | ||
| <div | ||
|
|
@@ -297,8 +295,9 @@ export const ImagePickerPopover = observer(function ImagePickerPopover(props: Pr | |
| </div> | ||
| ))} | ||
| </div> | ||
| </Tab.Panel> | ||
| <Tab.Panel className="mt-4 h-full w-full"> | ||
| </Tabs.Content> | ||
|
|
||
| <Tabs.Content className="h-full w-full" value="upload"> | ||
| <div className="flex h-full w-full flex-col gap-y-2"> | ||
| <div className="flex w-full flex-1 items-center gap-3"> | ||
| <div | ||
|
|
@@ -365,9 +364,9 @@ export const ImagePickerPopover = observer(function ImagePickerPopover(props: Pr | |
| </Button> | ||
| </div> | ||
| </div> | ||
| </Tab.Panel> | ||
| </Tab.Panels> | ||
| </Tab.Group> | ||
| </Tabs.Content> | ||
| </div> | ||
| </Tabs> | ||
| </div> | ||
| </Popover.Panel> | ||
| )} | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,8 +2,8 @@ import type { FC } from "react"; | |||||
| import { useState } from "react"; | ||||||
| import { observer } from "mobx-react"; | ||||||
| import { CheckCircle } from "lucide-react"; | ||||||
| import { Tab } from "@headlessui/react"; | ||||||
| // plane imports | ||||||
| import { Tabs } from "@plane/propel/tabs"; | ||||||
| // helpers | ||||||
| import type { EProductSubscriptionEnum, TBillingFrequency, TSubscriptionPrice } from "@plane/types"; | ||||||
| import { getSubscriptionBackgroundColor, getUpgradeCardVariantStyle } from "@plane/ui"; | ||||||
|
|
@@ -39,32 +39,27 @@ export const BasePaidPlanCard = observer(function BasePaidPlanCard(props: TBaseP | |||||
|
|
||||||
| return ( | ||||||
| <div className={cn("flex flex-col py-6 px-3", upgradeCardVariantStyle)}> | ||||||
| <Tab.Group selectedIndex={selectedPlan === "month" ? 0 : 1}> | ||||||
| <div className="flex w-full justify-center h-9"> | ||||||
| <Tab.List | ||||||
| className={cn("flex space-x-1 rounded-md p-0.5 w-60", getSubscriptionBackgroundColor(planVariant, "50"))} | ||||||
| > | ||||||
| <Tabs value={selectedPlan} onValueChange={(value) => setSelectedPlan(value as TBillingFrequency)}> | ||||||
| <div className="flex w-full justify-center"> | ||||||
| <Tabs.List className={cn("flex rounded-md w-60", getSubscriptionBackgroundColor(planVariant, "50"))}> | ||||||
| {prices.map((price: TSubscriptionPrice) => ( | ||||||
| <Tab | ||||||
| <Tabs.Trigger | ||||||
| key={price.key} | ||||||
| className={({ selected }) => | ||||||
| cn( | ||||||
| "w-full rounded py-1 text-sm font-medium leading-5", | ||||||
| selected | ||||||
| ? "bg-custom-background-100 text-custom-text-100 shadow" | ||||||
| : "text-custom-text-300 hover:text-custom-text-200" | ||||||
| ) | ||||||
| } | ||||||
| onClick={() => setSelectedPlan(price.recurring)} | ||||||
| value={price.recurring} | ||||||
| className={cn( | ||||||
| "w-full rounded text-sm font-medium leading-5 py-2", | ||||||
|
||||||
| "w-full rounded text-sm font-medium leading-5 py-2", | |
| "w-full rounded text-sm font-medium leading-5 py-2", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary curly braces: The className value is a static string and doesn't need to be wrapped in curly braces. Use
className="h-full overflow-hidden"instead ofclassName={"h-full overflow-hidden"}.