forked from github/plane
style: issue modal select attributes
This commit is contained in:
parent
9ef30b4fbf
commit
5dc9e00c3d
@ -5,6 +5,7 @@ import { ICustomAttribute } from "types";
|
||||
|
||||
type Props = {
|
||||
attributeDetails: ICustomAttribute;
|
||||
className?: string;
|
||||
issueId: string;
|
||||
projectId: string;
|
||||
value: Date | undefined;
|
||||
@ -22,12 +23,15 @@ const TIME_FORMATS: { [key: string]: string } = {
|
||||
"24": "HH:mm",
|
||||
};
|
||||
|
||||
export const CustomDateTimeAttribute: React.FC<Props> = ({ attributeDetails, onChange, value }) => (
|
||||
export const CustomDateTimeAttribute: React.FC<Props> = (props) => {
|
||||
const { attributeDetails, className = "", onChange, value } = props;
|
||||
|
||||
return (
|
||||
<div className="flex-shrink-0">
|
||||
<ReactDatePicker
|
||||
selected={value}
|
||||
onChange={onChange}
|
||||
className="bg-custom-background-80 rounded text-xs px-2.5 py-0.5 outline-none truncate"
|
||||
className={`bg-custom-background-80 rounded text-xs px-2.5 py-0.5 outline-none truncate ${className}`}
|
||||
calendarClassName="!bg-custom-background-80"
|
||||
dateFormat={`${
|
||||
attributeDetails.extra_settings.hide_date
|
||||
@ -44,3 +48,4 @@ export const CustomDateTimeAttribute: React.FC<Props> = ({ attributeDetails, onC
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -13,12 +13,13 @@ import useWorkspaceDetails from "hooks/use-workspace-details";
|
||||
import { getFileIcon } from "components/icons";
|
||||
import { X } from "lucide-react";
|
||||
// helpers
|
||||
import { getFileExtension, getFileName } from "helpers/attachment.helper";
|
||||
import { getFileExtension } from "helpers/attachment.helper";
|
||||
// types
|
||||
import { ICustomAttribute } from "types";
|
||||
|
||||
type Props = {
|
||||
attributeDetails: ICustomAttribute;
|
||||
className?: string;
|
||||
issueId: string;
|
||||
projectId: string;
|
||||
value: string | undefined;
|
||||
@ -28,7 +29,7 @@ type Props = {
|
||||
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB
|
||||
|
||||
export const CustomFileAttribute: React.FC<Props> = (props) => {
|
||||
const { attributeDetails, onChange, value } = props;
|
||||
const { attributeDetails, className = "", onChange, value } = props;
|
||||
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
@ -136,7 +137,7 @@ export const CustomFileAttribute: React.FC<Props> = (props) => {
|
||||
{...getRootProps()}
|
||||
className={`flex items-center bg-custom-background-80 text-xs rounded px-2.5 py-0.5 cursor-pointer truncate w-min max-w-full whitespace-nowrap ${
|
||||
isDragActive ? "bg-custom-primary-100/10" : ""
|
||||
} ${isDragReject ? "bg-red-500/10" : ""}`}
|
||||
} ${isDragReject ? "bg-red-500/10" : ""} ${className}`}
|
||||
>
|
||||
<input className="flex-shrink-0" {...getInputProps()} />
|
||||
<span className={`flex-grow truncate text-left ${fileError ? "text-red-500" : ""}`}>
|
||||
|
@ -15,9 +15,12 @@ import { Search } from "lucide-react";
|
||||
import { ICustomAttribute } from "types";
|
||||
// fetch-keys
|
||||
import { CYCLES_LIST, MODULE_LIST } from "constants/fetch-keys";
|
||||
import useProjectMembers from "hooks/use-project-members";
|
||||
import { Avatar } from "components/ui";
|
||||
|
||||
type Props = {
|
||||
attributeDetails: ICustomAttribute;
|
||||
className?: string;
|
||||
issueId: string;
|
||||
projectId: string;
|
||||
value: string | undefined;
|
||||
@ -26,6 +29,7 @@ type Props = {
|
||||
|
||||
export const CustomRelationAttribute: React.FC<Props> = ({
|
||||
attributeDetails,
|
||||
className = "",
|
||||
onChange,
|
||||
projectId,
|
||||
value,
|
||||
@ -54,17 +58,30 @@ export const CustomRelationAttribute: React.FC<Props> = ({
|
||||
: null
|
||||
);
|
||||
|
||||
const { members } = useProjectMembers(workspaceSlug?.toString(), projectId);
|
||||
|
||||
const optionsList =
|
||||
attributeDetails.unit === "cycle"
|
||||
? cycles?.map((c) => ({ id: c.id, name: c.name }))
|
||||
? cycles?.map((c) => ({ id: c.id, query: c.name, label: c.name }))
|
||||
: attributeDetails.unit === "module"
|
||||
? modules?.map((m) => ({ id: m.id, name: m.name }))
|
||||
? modules?.map((m) => ({ id: m.id, query: m.name, label: m.name }))
|
||||
: attributeDetails.unit === "user"
|
||||
? members?.map((m) => ({
|
||||
id: m.member.id,
|
||||
query: m.member.display_name,
|
||||
label: (
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar user={m.member} />
|
||||
{m.member.is_bot ? m.member.first_name : m.member.display_name}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
: [];
|
||||
|
||||
const selectedOption = (optionsList ?? []).find((option) => option.id === value);
|
||||
|
||||
const options = (optionsList ?? []).filter((option) =>
|
||||
option.name.toLowerCase().includes(query.toLowerCase())
|
||||
option.query.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
@ -76,8 +93,10 @@ export const CustomRelationAttribute: React.FC<Props> = ({
|
||||
>
|
||||
{({ open }: { open: boolean }) => (
|
||||
<>
|
||||
<Combobox.Button className="flex items-center text-xs rounded px-2.5 py-0.5 truncate w-min max-w-full text-left bg-custom-background-80">
|
||||
{selectedOption?.name ?? `Select ${attributeDetails.unit}`}
|
||||
<Combobox.Button
|
||||
className={`lex items-center text-xs rounded px-2.5 py-0.5 truncate w-min max-w-full text-left bg-custom-background-80 ${className}`}
|
||||
>
|
||||
{selectedOption?.label ?? `Select ${attributeDetails.unit}`}
|
||||
</Combobox.Button>
|
||||
<Transition
|
||||
show={open}
|
||||
@ -109,7 +128,7 @@ export const CustomRelationAttribute: React.FC<Props> = ({
|
||||
value={option.id}
|
||||
className="flex items-center gap-1 cursor-pointer select-none truncate rounded px-1 py-1.5 hover:bg-custom-background-80 w-full"
|
||||
>
|
||||
<span className="px-1 rounded-sm truncate">{option.name}</span>
|
||||
{option.label}
|
||||
</Combobox.Option>
|
||||
))
|
||||
) : (
|
||||
|
@ -11,6 +11,7 @@ import { ICustomAttribute } from "types";
|
||||
|
||||
type Props = {
|
||||
attributeDetails: ICustomAttribute;
|
||||
className?: string;
|
||||
issueId: string;
|
||||
onChange: (val: string | string[] | undefined) => void;
|
||||
projectId: string;
|
||||
@ -23,7 +24,7 @@ type Props = {
|
||||
);
|
||||
|
||||
export const CustomSelectAttribute: React.FC<Props> = (props) => {
|
||||
const { attributeDetails, multiple = false, onChange, value } = props;
|
||||
const { attributeDetails, className = "", multiple = false, onChange, value } = props;
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [query, setQuery] = useState("");
|
||||
@ -31,10 +32,6 @@ export const CustomSelectAttribute: React.FC<Props> = (props) => {
|
||||
const dropdownButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const dropdownOptionsRef = useRef<HTMLUListElement>(null);
|
||||
|
||||
const selectedOption =
|
||||
attributeDetails.children.find((option) => option.id === value) ??
|
||||
attributeDetails.children.find((option) => option.is_default);
|
||||
|
||||
const options = attributeDetails.children.filter((option) =>
|
||||
option.display_name.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
@ -118,7 +115,9 @@ export const CustomSelectAttribute: React.FC<Props> = (props) => {
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs px-2.5 py-0.5 rounded bg-custom-background-80">
|
||||
<div
|
||||
className={`text-xs px-2.5 py-0.5 rounded bg-custom-background-80 ${className}`}
|
||||
>
|
||||
Select {attributeDetails.display_name}
|
||||
</div>
|
||||
)
|
||||
@ -148,7 +147,9 @@ export const CustomSelectAttribute: React.FC<Props> = (props) => {
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="cursor-pointer text-xs truncate bg-custom-background-80 px-2.5 py-0.5 rounded">
|
||||
<div
|
||||
className={`cursor-pointer text-xs truncate bg-custom-background-80 px-2.5 py-0.5 rounded ${className}`}
|
||||
>
|
||||
Select {attributeDetails.display_name}
|
||||
</div>
|
||||
)}
|
||||
|
@ -28,15 +28,11 @@ export const RelationAttributeForm: React.FC<FormComponentProps> = ({ control })
|
||||
optionsClassName="w-full"
|
||||
input
|
||||
>
|
||||
{CUSTOM_ATTRIBUTE_UNITS.map((unit) => {
|
||||
if (unit.value === "user") return null;
|
||||
|
||||
return (
|
||||
{CUSTOM_ATTRIBUTE_UNITS.map((unit) => (
|
||||
<CustomSelect.Option key={unit.value} value={unit.value}>
|
||||
{unit.label}
|
||||
</CustomSelect.Option>
|
||||
);
|
||||
})}
|
||||
))}
|
||||
</CustomSelect>
|
||||
)}
|
||||
/>
|
||||
|
@ -77,7 +77,7 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer((props)
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Disclosure.Button className="font-medium flex items-center gap-2">
|
||||
<ChevronDown
|
||||
className={`transition-all ${open ? "-rotate-90" : ""}`}
|
||||
className={`transition-all ${open ? "" : "-rotate-90"}`}
|
||||
size={14}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
@ -119,7 +119,7 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer((props)
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
<div className="flex items-center gap-1 flex-wrap mt-3.5">
|
||||
<div className="flex items-center gap-2 flex-wrap mt-3.5">
|
||||
{Object.entries(nonDescriptionFields).map(([attributeId, attribute]) => (
|
||||
<div key={attributeId}>
|
||||
{attribute.type === "checkbox" && (
|
||||
@ -134,6 +134,7 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer((props)
|
||||
{attribute.type === "datetime" && (
|
||||
<CustomDateTimeAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1"
|
||||
issueId={issueId}
|
||||
onChange={(val) =>
|
||||
onChange(attribute.id, val ? [val.toISOString()] : undefined)
|
||||
@ -147,15 +148,17 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer((props)
|
||||
{attribute.type === "file" && (
|
||||
<CustomFileAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1"
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, val)}
|
||||
projectId={projectId}
|
||||
value={undefined}
|
||||
value={values[attribute.id]?.[0]}
|
||||
/>
|
||||
)}
|
||||
{attribute.type === "multi_select" && (
|
||||
<CustomSelectAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1"
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, val)}
|
||||
projectId={projectId}
|
||||
@ -166,6 +169,7 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer((props)
|
||||
{attribute.type === "relation" && (
|
||||
<CustomRelationAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1"
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, val)}
|
||||
projectId={projectId}
|
||||
@ -175,6 +179,7 @@ export const IssueModalCustomAttributesList: React.FC<Props> = observer((props)
|
||||
{attribute.type === "select" && (
|
||||
<CustomSelectAttribute
|
||||
attributeDetails={attribute}
|
||||
className="bg-transparent border border-custom-border-200 py-1"
|
||||
issueId={issueId}
|
||||
onChange={(val) => onChange(attribute.id, val)}
|
||||
projectId={projectId}
|
||||
|
@ -42,17 +42,14 @@ export const ObjectsSelect: React.FC<Props> = observer(({ onChange, projectId, v
|
||||
|
||||
return (
|
||||
<CustomSearchSelect
|
||||
customButton={
|
||||
<button type="button" className="bg-custom-background-80 rounded text-xs px-2.5 py-0.5">
|
||||
{entities?.find((e) => e.id === value)?.display_name ?? "Default"}
|
||||
</button>
|
||||
}
|
||||
label={entities?.find((e) => e.id === value)?.display_name ?? "Default"}
|
||||
value={value}
|
||||
maxHeight="md"
|
||||
optionsClassName="!min-w-[10rem]"
|
||||
onChange={onChange}
|
||||
options={options}
|
||||
position="right"
|
||||
noChevron
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -49,7 +49,7 @@ export const IssueAssigneeSelect: React.FC<Props> = ({ projectId, value = [], on
|
||||
<AssigneesList userIds={value} length={3} showLength={true} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center gap-2 px-1.5 py-1 rounded shadow-sm border border-custom-border-300 hover:bg-custom-background-80">
|
||||
<div className="flex items-center justify-center gap-2 px-1.5 py-1 rounded shadow-sm border border-custom-border-200 hover:bg-custom-background-80">
|
||||
<Icon iconName="person" className="!text-base !leading-4" />
|
||||
<span className="text-custom-text-200">Assignee</span>
|
||||
</div>
|
||||
|
@ -19,7 +19,7 @@ export const IssueDateSelect: React.FC<Props> = ({ label, maxDate, minDate, onCh
|
||||
<Popover className="relative flex items-center justify-center rounded-lg">
|
||||
{({ close }) => (
|
||||
<>
|
||||
<Popover.Button className="flex cursor-pointer items-center rounded-md border border-custom-border-200 text-xs shadow-sm duration-200">
|
||||
<Popover.Button className="flex cursor-pointer items-center rounded border border-custom-border-200 text-xs shadow-sm duration-200">
|
||||
<span className="flex items-center justify-center gap-2 px-2 py-1 text-xs text-custom-text-200 hover:bg-custom-background-80">
|
||||
{value ? (
|
||||
<>
|
||||
|
@ -69,7 +69,7 @@ export const IssueLabelSelect: React.FC<Props> = ({ setIsOpen, value, onChange,
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center justify-center gap-2 px-2 py-1 text-xs rounded shadow-sm border border-custom-border-300 hover:bg-custom-background-80">
|
||||
<span className="flex items-center justify-center gap-2 px-2 py-1 text-xs rounded shadow-sm border border-custom-border-200 hover:bg-custom-background-80">
|
||||
<TagIcon className="h-3.5 w-3.5 text-custom-text-200" />
|
||||
<span className=" text-custom-text-200">Label</span>
|
||||
</span>
|
||||
|
@ -78,7 +78,7 @@ export const CustomSearchSelect = ({
|
||||
) : (
|
||||
<Combobox.Button
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full rounded-md shadow-sm border border-custom-border-300 duration-300 focus:outline-none ${
|
||||
className={`flex items-center justify-between gap-1 w-full rounded shadow-sm border border-custom-border-200 duration-300 focus:outline-none ${
|
||||
input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
|
||||
} ${
|
||||
disabled
|
||||
|
@ -45,7 +45,7 @@ const CustomSelect = ({
|
||||
) : (
|
||||
<Listbox.Button
|
||||
type="button"
|
||||
className={`flex items-center justify-between gap-1 w-full rounded-md border border-custom-border-300 shadow-sm duration-300 focus:outline-none ${
|
||||
className={`flex items-center justify-between gap-1 w-full rounded border border-custom-border-200 shadow-sm duration-300 focus:outline-none ${
|
||||
input ? "px-3 py-2 text-sm" : "px-2.5 py-1 text-xs"
|
||||
} ${
|
||||
disabled
|
||||
|
Loading…
Reference in New Issue
Block a user