-
Notifications
You must be signed in to change notification settings - Fork 4k
style: update work item details properties UI #8357
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
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 |
|---|---|---|
|
|
@@ -6,7 +6,6 @@ import { Combobox } from "@headlessui/react"; | |
| // plane imports | ||
| import { EUserPermissionsLevel, getRandomLabelColor } from "@plane/constants"; | ||
| import { useTranslation } from "@plane/i18n"; | ||
| import { LabelFilledIcon, LabelPropertyIcon } from "@plane/propel/icons"; | ||
| import type { IIssueLabel } from "@plane/types"; | ||
| import { EUserProjectRoles } from "@plane/types"; | ||
| // helpers | ||
|
|
@@ -86,15 +85,9 @@ export const IssueLabelSelect = observer(function IssueLabelSelect(props: IIssue | |
| const issueLabels = values ?? []; | ||
|
|
||
| const label = ( | ||
| <button | ||
| type="button" | ||
| className="h-full w-full flex items-center gap-1.5 rounded-lg px-2 py-0.5 bg-layer-transparent-active hover:bg-layer-transparent-hover text-body-xs-regular text-tertiary" | ||
| > | ||
| <div className="flex-shrink-0"> | ||
| <LabelFilledIcon className="size-3.5" /> | ||
| </div> | ||
| <div className="flex-shrink-0">{t("label.select")}</div> | ||
| </button> | ||
| <span className="size-full flex items-center rounded-sm px-2 py-0.5 bg-layer-transparent hover:bg-layer-transparent-hover text-body-xs-regular text-tertiary"> | ||
| {t("label.select")} | ||
| </span> | ||
| ); | ||
|
|
||
| const searchInputKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => { | ||
|
|
@@ -121,101 +114,149 @@ export const IssueLabelSelect = observer(function IssueLabelSelect(props: IIssue | |
| if (!issueId || !values) return <></>; | ||
|
|
||
| return ( | ||
| <> | ||
| <Combobox | ||
| as="div" | ||
| className={`w-auto max-w-full flex-shrink-0 text-left`} | ||
| value={issueLabels} | ||
| onChange={(value) => onSelect(value)} | ||
| multiple | ||
| > | ||
| <Combobox.Button as={Fragment}> | ||
| <button | ||
| ref={setReferenceElement} | ||
| type="button" | ||
| className="cursor-pointer" | ||
| onClick={() => !projectLabels && fetchLabels()} | ||
| > | ||
| {label} | ||
| </button> | ||
| </Combobox.Button> | ||
|
|
||
| <Combobox.Options className="fixed z-10"> | ||
| <div | ||
| className={`z-10 my-1 w-48 whitespace-nowrap rounded-sm border border-strong bg-surface-1 py-2.5 text-11 shadow-raised-200 focus:outline-none`} | ||
| ref={setPopperElement} | ||
| style={styles.popper} | ||
| {...attributes.popper} | ||
| > | ||
| <div className="px-2"> | ||
| <div className="flex w-full items-center justify-start rounded-sm border border-subtle bg-surface-2 px-2"> | ||
| <Search className="h-3.5 w-3.5 text-tertiary" /> | ||
| <Combobox.Input | ||
| className="w-full bg-transparent px-2 py-1 text-11 text-secondary placeholder:text-placeholder focus:outline-none" | ||
| value={query} | ||
| onChange={(e) => setQuery(e.target.value)} | ||
| placeholder={t("common.search.label")} | ||
| displayValue={(assigned: any) => assigned?.name} | ||
| onKeyDown={searchInputKeyDown} | ||
| tabIndex={baseTabIndex} | ||
| /> | ||
| </div> | ||
| <Combobox | ||
| as="div" | ||
| className="size-full flex-shrink-0 text-left" | ||
| value={issueLabels} | ||
| onChange={(value) => onSelect(value)} | ||
| multiple | ||
| > | ||
| <Combobox.Button as={Fragment}> | ||
| <button | ||
| ref={setReferenceElement} | ||
| type="button" | ||
| className="cursor-pointer size-full" | ||
| onClick={() => !projectLabels && fetchLabels()} | ||
| > | ||
| {label} | ||
| </button> | ||
| </Combobox.Button> | ||
| <Combobox.Options className="fixed z-10"> | ||
| <div | ||
| className={`z-10 my-1 w-48 whitespace-nowrap rounded-sm border border-strong bg-surface-1 py-2.5 text-11 shadow-raised-200 focus:outline-none`} | ||
| ref={setPopperElement} | ||
| style={styles.popper} | ||
| {...attributes.popper} | ||
| > | ||
| <div className="px-2"> | ||
| <div className="flex w-full items-center justify-start rounded-sm border border-subtle bg-surface-2 px-2"> | ||
| <Search className="h-3.5 w-3.5 text-tertiary" /> | ||
| <Combobox.Input | ||
| className="w-full bg-transparent px-2 py-1 text-11 text-secondary placeholder:text-placeholder focus:outline-none" | ||
| value={query} | ||
| onChange={(e) => setQuery(e.target.value)} | ||
| placeholder={t("common.search.label")} | ||
| displayValue={(assigned: any) => assigned?.name} | ||
| onKeyDown={searchInputKeyDown} | ||
| tabIndex={baseTabIndex} | ||
| /> | ||
| </div> | ||
| <div className={`vertical-scrollbar scrollbar-sm mt-2 max-h-48 space-y-1 overflow-y-scroll px-2 pr-0`}> | ||
| {isLoading ? ( | ||
| <p className="text-center text-secondary">{t("common.loading")}</p> | ||
| ) : filteredOptions.length > 0 ? ( | ||
| filteredOptions.map((option) => ( | ||
| <Combobox.Option | ||
| key={option.value} | ||
| value={option.value} | ||
| className={({ selected }) => | ||
| `flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 hover:bg-layer-1 ${ | ||
| selected ? "text-primary" : "text-secondary" | ||
| }` | ||
| } | ||
| > | ||
| {({ selected }) => ( | ||
| <> | ||
| {option.content} | ||
| {selected && ( | ||
| <div className="flex-shrink-0"> | ||
| <Check className={`h-3.5 w-3.5`} /> | ||
| </div> | ||
| )} | ||
| </> | ||
| )} | ||
| </Combobox.Option> | ||
| )) | ||
| ) : submitting ? ( | ||
| <Loader className="spin h-3.5 w-3.5" /> | ||
| ) : canCreateLabel ? ( | ||
| </div> | ||
| <div className={`vertical-scrollbar scrollbar-sm mt-2 max-h-48 space-y-1 overflow-y-scroll px-2 pr-0`}> | ||
| {isLoading ? ( | ||
| <p className="text-center text-secondary">{t("common.loading")}</p> | ||
| ) : filteredOptions.length > 0 ? ( | ||
| filteredOptions.map((option) => ( | ||
| <Combobox.Option | ||
| value={query} | ||
| onClick={(e) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| if (!query.length) return; | ||
| handleAddLabel(query); | ||
| }} | ||
| className={`text-left text-secondary ${query.length ? "cursor-pointer" : "cursor-default"}`} | ||
| key={option.value} | ||
| value={option.value} | ||
| className={({ selected }) => | ||
| `flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 hover:bg-layer-1 ${ | ||
| selected ? "text-primary" : "text-secondary" | ||
| }` | ||
| } | ||
| > | ||
| {query.length ? ( | ||
| {({ selected }) => ( | ||
| <> | ||
| {/* TODO: Translate here */}+ Add <span className="text-primary">"{query}"</span> to | ||
| labels | ||
| {option.content} | ||
| {selected && ( | ||
| <div className="flex-shrink-0"> | ||
| <Check className={`h-3.5 w-3.5`} /> | ||
| </div> | ||
| )} | ||
| </> | ||
| ) : ( | ||
| t("label.create.type") | ||
| )} | ||
| </Combobox.Option> | ||
| )) | ||
| ) : submitting ? ( | ||
| <Loader className="spin h-3.5 w-3.5" /> | ||
| ) : canCreateLabel ? ( | ||
| <Combobox.Option | ||
| value={query} | ||
| onClick={(e) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| if (!query.length) return; | ||
| handleAddLabel(query); | ||
| }} | ||
| className={`text-left text-secondary ${query.length ? "cursor-pointer" : "cursor-default"}`} | ||
| > | ||
| {query.length ? ( | ||
| <> | ||
| {/* TODO: Translate here */}+ Add <span className="text-primary">"{query}"</span> to | ||
| labels | ||
| </> | ||
| ) : ( | ||
| t("label.create.type") | ||
| )} | ||
| </Combobox.Option> | ||
| ) : ( | ||
| <p className="text-left text-secondary ">{t("common.search.no_matching_results")}</p> | ||
| )} | ||
| </div> | ||
| </div> | ||
| <div className={`vertical-scrollbar scrollbar-sm mt-2 max-h-48 space-y-1 overflow-y-scroll px-2 pr-0`}> | ||
| {isLoading ? ( | ||
| <p className="text-center text-secondary">{t("common.loading")}</p> | ||
| ) : filteredOptions.length > 0 ? ( | ||
| filteredOptions.map((option) => ( | ||
| <Combobox.Option | ||
| key={option.value} | ||
| value={option.value} | ||
| className={({ selected }) => | ||
| `flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 hover:bg-layer-1 ${ | ||
| selected ? "text-primary" : "text-secondary" | ||
| }` | ||
| } | ||
| > | ||
| {({ selected }) => ( | ||
| <> | ||
| {option.content} | ||
| {selected && ( | ||
| <div className="flex-shrink-0"> | ||
| <Check className={`h-3.5 w-3.5`} /> | ||
| </div> | ||
| )} | ||
| </> | ||
| )} | ||
| </Combobox.Option> | ||
| )) | ||
| ) : submitting ? ( | ||
| <Loader className="spin h-3.5 w-3.5" /> | ||
| ) : canCreateLabel ? ( | ||
| <Combobox.Option | ||
| value={query} | ||
| onClick={(e) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| if (!query.length) return; | ||
| handleAddLabel(query); | ||
| }} | ||
| className={`text-left text-secondary ${query.length ? "cursor-pointer" : "cursor-default"}`} | ||
| > | ||
| {query.length ? ( | ||
| <> | ||
| {/* TODO: Translate here */}+ Add <span className="text-primary">"{query}"</span> to labels | ||
| </> | ||
| ) : ( | ||
| <p className="text-left text-secondary ">{t("common.search.no_matching_results")}</p> | ||
| t("label.create.type") | ||
| )} | ||
| </div> | ||
| </div> | ||
| </Combobox.Options> | ||
| </Combobox> | ||
| </> | ||
| </Combobox.Option> | ||
| ) : ( | ||
| <p className="text-left text-secondary ">{t("common.search.no_matching_results")}</p> | ||
| )} | ||
| </div> | ||
|
Comment on lines
+208
to
+258
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. Duplicate code block renders dropdown content twice. Lines 208-258 duplicate the exact same content from lines 155-206. This renders the options list twice inside </div>
</div>
- <div className={`vertical-scrollbar scrollbar-sm mt-2 max-h-48 space-y-1 overflow-y-scroll px-2 pr-0`}>
- {isLoading ? (
- <p className="text-center text-secondary">{t("common.loading")}</p>
- ) : filteredOptions.length > 0 ? (
- filteredOptions.map((option) => (
- <Combobox.Option
- key={option.value}
- value={option.value}
- className={({ selected }) =>
- `flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 hover:bg-layer-1 ${
- selected ? "text-primary" : "text-secondary"
- }`
- }
- >
- {({ selected }) => (
- <>
- {option.content}
- {selected && (
- <div className="flex-shrink-0">
- <Check className={`h-3.5 w-3.5`} />
- </div>
- )}
- </>
- )}
- </Combobox.Option>
- ))
- ) : submitting ? (
- <Loader className="spin h-3.5 w-3.5" />
- ) : canCreateLabel ? (
- <Combobox.Option
- value={query}
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- if (!query.length) return;
- handleAddLabel(query);
- }}
- className={`text-left text-secondary ${query.length ? "cursor-pointer" : "cursor-default"}`}
- >
- {query.length ? (
- <>
- {/* TODO: Translate here */}+ Add <span className="text-primary">"{query}"</span> to labels
- </>
- ) : (
- t("label.create.type")
- )}
- </Combobox.Option>
- ) : (
- <p className="text-left text-secondary ">{t("common.search.no_matching_results")}</p>
- )}
- </div>
</Combobox.Options>
</Combobox>
🤖 Prompt for AI Agents |
||
| </Combobox.Options> | ||
| </Combobox> | ||
| ); | ||
| }); | ||
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.
The entire dropdown options list (lines 208-258) is duplicated outside the dropdown container. This duplicate block should be removed as it will render the same list of options twice, once inside the proper dropdown container (lines 155-206) and once outside of it. Only the first instance (lines 155-206) inside the dropdown container should remain.