feat: Add support for product export in UI (#8281)

* feat: Add support for product export in UI

* fix:Return the backend URL for private files of local file provider
This commit is contained in:
Stevche Radevski
2024-07-29 22:50:22 +03:00
committed by GitHub
parent 1d773c536f
commit b539c6d5bb
18 changed files with 564 additions and 343 deletions

View File

@@ -41,10 +41,15 @@ export type Filter = {
type DataTableFilterProps = {
filters: Filter[]
readonly?: boolean
prefix?: string
}
export const DataTableFilter = ({ filters, prefix }: DataTableFilterProps) => {
export const DataTableFilter = ({
filters,
readonly,
prefix,
}: DataTableFilterProps) => {
const { t } = useTranslation()
const [searchParams] = useSearchParams()
const [open, setOpen] = useState(false)
@@ -127,6 +132,7 @@ export const DataTableFilter = ({ filters, prefix }: DataTableFilterProps) => {
key={filter.key}
filter={filter}
prefix={prefix}
readonly={readonly}
options={filter.options}
multiple={filter.multiple}
searchable={filter.searchable}
@@ -139,6 +145,7 @@ export const DataTableFilter = ({ filters, prefix }: DataTableFilterProps) => {
key={filter.key}
filter={filter}
prefix={prefix}
readonly={readonly}
openOnMount={filter.openOnMount}
/>
)
@@ -148,6 +155,7 @@ export const DataTableFilter = ({ filters, prefix }: DataTableFilterProps) => {
key={filter.key}
filter={filter}
prefix={prefix}
readonly={readonly}
openOnMount={filter.openOnMount}
/>
)
@@ -157,6 +165,7 @@ export const DataTableFilter = ({ filters, prefix }: DataTableFilterProps) => {
key={filter.key}
filter={filter}
prefix={prefix}
readonly={readonly}
openOnMount={filter.openOnMount}
/>
)
@@ -164,7 +173,7 @@ export const DataTableFilter = ({ filters, prefix }: DataTableFilterProps) => {
break
}
})}
{availableFilters.length > 0 && (
{!readonly && availableFilters.length > 0 && (
<Popover.Root modal open={open} onOpenChange={setOpen}>
<Popover.Trigger asChild id="filters_menu_trigger">
<Button size="small" variant="secondary">
@@ -208,7 +217,7 @@ export const DataTableFilter = ({ filters, prefix }: DataTableFilterProps) => {
</Popover.Portal>
</Popover.Root>
)}
{activeFilters.length > 0 && (
{!readonly && activeFilters.length > 0 && (
<ClearAllFilters filters={filters} prefix={prefix} />
)}
</div>

View File

@@ -35,6 +35,7 @@ type DateComparisonOperator = {
export const DateFilter = ({
filter,
prefix,
readonly,
openOnMount,
}: DateFilterProps) => {
const [open, setOpen] = useState(openOnMount)
@@ -118,112 +119,119 @@ export const DateFilter = ({
return (
<Popover.Root modal open={open} onOpenChange={handleOpenChange}>
<DateDisplay label={label} value={displayValue} onRemove={handleRemove} />
<Popover.Portal>
<Popover.Content
data-name="date_filter_content"
align="start"
sideOffset={8}
collisionPadding={24}
className={clx(
"bg-ui-bg-base text-ui-fg-base shadow-elevation-flyout h-full max-h-[var(--radix-popper-available-height)] w-[300px] overflow-auto rounded-lg"
)}
onInteractOutside={(e) => {
if (e.target instanceof HTMLElement) {
if (
e.target.attributes.getNamedItem("data-name")?.value ===
"filters_menu_content"
) {
e.preventDefault()
<DateDisplay
label={label}
value={displayValue}
onRemove={handleRemove}
readonly={readonly}
/>
{!readonly && (
<Popover.Portal>
<Popover.Content
data-name="date_filter_content"
align="start"
sideOffset={8}
collisionPadding={24}
className={clx(
"bg-ui-bg-base text-ui-fg-base shadow-elevation-flyout h-full max-h-[var(--radix-popper-available-height)] w-[300px] overflow-auto rounded-lg"
)}
onInteractOutside={(e) => {
if (e.target instanceof HTMLElement) {
if (
e.target.attributes.getNamedItem("data-name")?.value ===
"filters_menu_content"
) {
e.preventDefault()
}
}
}
}}
>
<ul className="w-full p-1">
{presets.map((preset) => {
const isSelected = selectedParams
.get()
.includes(JSON.stringify(preset.value))
return (
<li key={preset.label}>
<button
className="bg-ui-bg-base hover:bg-ui-bg-base-hover focus-visible:bg-ui-bg-base-pressed text-ui-fg-base data-[disabled]:text-ui-fg-disabled txt-compact-small relative flex w-full cursor-pointer select-none items-center rounded-md px-2 py-1.5 outline-none transition-colors data-[disabled]:pointer-events-none"
type="button"
onClick={() => {
handleSelectPreset(preset.value)
}}
>
<div
className={clx(
"transition-fg flex h-5 w-5 items-center justify-center",
{
"[&_svg]:invisible": !isSelected,
}
)}
}}
>
<ul className="w-full p-1">
{presets.map((preset) => {
const isSelected = selectedParams
.get()
.includes(JSON.stringify(preset.value))
return (
<li key={preset.label}>
<button
className="bg-ui-bg-base hover:bg-ui-bg-base-hover focus-visible:bg-ui-bg-base-pressed text-ui-fg-base data-[disabled]:text-ui-fg-disabled txt-compact-small relative flex w-full cursor-pointer select-none items-center rounded-md px-2 py-1.5 outline-none transition-colors data-[disabled]:pointer-events-none"
type="button"
onClick={() => {
handleSelectPreset(preset.value)
}}
>
<EllipseMiniSolid />
</div>
{preset.label}
</button>
</li>
)
})}
<li>
<button
className="bg-ui-bg-base hover:bg-ui-bg-base-hover focus-visible:bg-ui-bg-base-pressed text-ui-fg-base data-[disabled]:text-ui-fg-disabled txt-compact-small relative flex w-full cursor-pointer select-none items-center rounded-md px-2 py-1.5 outline-none transition-colors data-[disabled]:pointer-events-none"
type="button"
onClick={handleSelectCustom}
>
<div
className={clx(
"transition-fg flex h-5 w-5 items-center justify-center",
{
"[&_svg]:invisible": !showCustom,
}
)}
<div
className={clx(
"transition-fg flex h-5 w-5 items-center justify-center",
{
"[&_svg]:invisible": !isSelected,
}
)}
>
<EllipseMiniSolid />
</div>
{preset.label}
</button>
</li>
)
})}
<li>
<button
className="bg-ui-bg-base hover:bg-ui-bg-base-hover focus-visible:bg-ui-bg-base-pressed text-ui-fg-base data-[disabled]:text-ui-fg-disabled txt-compact-small relative flex w-full cursor-pointer select-none items-center rounded-md px-2 py-1.5 outline-none transition-colors data-[disabled]:pointer-events-none"
type="button"
onClick={handleSelectCustom}
>
<EllipseMiniSolid />
<div
className={clx(
"transition-fg flex h-5 w-5 items-center justify-center",
{
"[&_svg]:invisible": !showCustom,
}
)}
>
<EllipseMiniSolid />
</div>
{t("filters.date.custom")}
</button>
</li>
</ul>
{showCustom && (
<div className="border-t px-1 pb-3 pt-1">
<div>
<div className="px-2 py-1">
<Text size="xsmall" leading="compact" weight="plus">
{t("filters.date.from")}
</Text>
</div>
<div className="px-2 py-1">
<DatePicker
maxValue={customEndValue}
value={customStartValue}
onChange={(d) => handleCustomDateChange(d, "start")}
/>
</div>
</div>
{t("filters.date.custom")}
</button>
</li>
</ul>
{showCustom && (
<div className="border-t px-1 pb-3 pt-1">
<div>
<div className="px-2 py-1">
<Text size="xsmall" leading="compact" weight="plus">
{t("filters.date.from")}
</Text>
</div>
<div className="px-2 py-1">
<DatePicker
maxValue={customEndValue}
value={customStartValue}
onChange={(d) => handleCustomDateChange(d, "start")}
/>
<div>
<div className="px-2 py-1">
<Text size="xsmall" leading="compact" weight="plus">
{t("filters.date.to")}
</Text>
</div>
<div className="px-2 py-1">
<DatePicker
minValue={customStartValue}
value={customEndValue || undefined}
onChange={(d) => {
handleCustomDateChange(d, "end")
}}
/>
</div>
</div>
</div>
<div>
<div className="px-2 py-1">
<Text size="xsmall" leading="compact" weight="plus">
{t("filters.date.to")}
</Text>
</div>
<div className="px-2 py-1">
<DatePicker
minValue={customStartValue}
value={customEndValue || undefined}
onChange={(d) => {
handleCustomDateChange(d, "end")
}}
/>
</div>
</div>
</div>
)}
</Popover.Content>
</Popover.Portal>
)}
</Popover.Content>
</Popover.Portal>
)}
</Popover.Root>
)
}
@@ -231,10 +239,16 @@ export const DateFilter = ({
type DateDisplayProps = {
label: string
value?: string
readonly?: boolean
onRemove: () => void
}
const DateDisplay = ({ label, value, onRemove }: DateDisplayProps) => {
const DateDisplay = ({
label,
value,
readonly,
onRemove,
}: DateDisplayProps) => {
const handleRemove = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
onRemove()
@@ -245,8 +259,10 @@ const DateDisplay = ({ label, value, onRemove }: DateDisplayProps) => {
asChild
className={clx(
"bg-ui-bg-field transition-fg shadow-borders-base text-ui-fg-subtle flex cursor-pointer select-none items-center rounded-md",
"hover:bg-ui-bg-field-hover",
"data-[state=open]:bg-ui-bg-field-hover"
{
"hover:bg-ui-bg-field-hover": !readonly,
"data-[state=open]:bg-ui-bg-field-hover": !readonly,
}
)}
>
<div>
@@ -268,7 +284,7 @@ const DateDisplay = ({ label, value, onRemove }: DateDisplayProps) => {
</div>
</div>
)}
{value && (
{!readonly && value && (
<div>
<button
onClick={handleRemove}

View File

@@ -24,6 +24,7 @@ type Operator = "lt" | "gt" | "eq"
export const NumberFilter = ({
filter,
prefix,
readonly,
openOnMount,
}: NumberFilterProps) => {
const { t } = useTranslation()
@@ -136,104 +137,107 @@ export const NumberFilter = ({
label={label}
value={currentValue}
onRemove={handleRemove}
readonly={readonly}
/>
<Popover.Portal>
<Popover.Content
data-name="number_filter_content"
align="start"
sideOffset={8}
collisionPadding={24}
className={clx(
"bg-ui-bg-base text-ui-fg-base shadow-elevation-flyout max-h-[var(--radix-popper-available-height)] w-[300px] divide-y overflow-y-auto rounded-lg outline-none"
)}
onInteractOutside={(e) => {
if (e.target instanceof HTMLElement) {
if (
e.target.attributes.getNamedItem("data-name")?.value ===
"filters_menu_content"
) {
e.preventDefault()
}
}
}}
>
<div className="p-1">
<RadioGroup.Root
value={operator}
onValueChange={(val) => setOperator(val as Comparison)}
className="flex flex-col items-start"
orientation="vertical"
autoFocus
>
{operators.map((o) => (
<RadioGroup.Item
key={o.operator}
value={o.operator}
className="txt-compact-small hover:bg-ui-bg-base-hover focus-visible:bg-ui-bg-base-hover active:bg-ui-bg-base-pressed transition-fg grid w-full grid-cols-[20px_1fr] gap-2 rounded-[4px] px-2 py-1.5 text-left outline-none"
>
<div className="size-5">
<RadioGroup.Indicator>
<EllipseMiniSolid />
</RadioGroup.Indicator>
</div>
<span className="w-full">{o.label}</span>
</RadioGroup.Item>
))}
</RadioGroup.Root>
</div>
<div>
{operator === "range" ? (
<div className="px-1 pb-3 pt-1" key="range">
<div className="px-2 py-1.5">
<Label size="xsmall" weight="plus" htmlFor={GT_KEY}>
{t("filters.compare.greaterThan")}
</Label>
</div>
<div className="px-2 py-0.5">
<Input
name={GT_KEY}
size="small"
type="number"
defaultValue={getValue(currentValue, "gt")}
onChange={(e) => debouncedOnChange(e, "gt")}
/>
</div>
<div className="px-2 py-1.5">
<Label size="xsmall" weight="plus" htmlFor={LT_KEY}>
{t("filters.compare.lessThan")}
</Label>
</div>
<div className="px-2 py-0.5">
<Input
name={LT_KEY}
size="small"
type="number"
defaultValue={getValue(currentValue, "lt")}
onChange={(e) => debouncedOnChange(e, "lt")}
/>
</div>
</div>
) : (
<div className="px-1 pb-3 pt-1" key="exact">
<div className="px-2 py-1.5">
<Label size="xsmall" weight="plus" htmlFor={EQ_KEY}>
{label}
</Label>
</div>
<div className="px-2 py-0.5">
<Input
name={EQ_KEY}
size="small"
type="number"
defaultValue={getValue(currentValue, "eq")}
onChange={(e) => debouncedOnChange(e, "eq")}
/>
</div>
</div>
{!readonly && (
<Popover.Portal>
<Popover.Content
data-name="number_filter_content"
align="start"
sideOffset={8}
collisionPadding={24}
className={clx(
"bg-ui-bg-base text-ui-fg-base shadow-elevation-flyout max-h-[var(--radix-popper-available-height)] w-[300px] divide-y overflow-y-auto rounded-lg outline-none"
)}
</div>
</Popover.Content>
</Popover.Portal>
onInteractOutside={(e) => {
if (e.target instanceof HTMLElement) {
if (
e.target.attributes.getNamedItem("data-name")?.value ===
"filters_menu_content"
) {
e.preventDefault()
}
}
}}
>
<div className="p-1">
<RadioGroup.Root
value={operator}
onValueChange={(val) => setOperator(val as Comparison)}
className="flex flex-col items-start"
orientation="vertical"
autoFocus
>
{operators.map((o) => (
<RadioGroup.Item
key={o.operator}
value={o.operator}
className="txt-compact-small hover:bg-ui-bg-base-hover focus-visible:bg-ui-bg-base-hover active:bg-ui-bg-base-pressed transition-fg grid w-full grid-cols-[20px_1fr] gap-2 rounded-[4px] px-2 py-1.5 text-left outline-none"
>
<div className="size-5">
<RadioGroup.Indicator>
<EllipseMiniSolid />
</RadioGroup.Indicator>
</div>
<span className="w-full">{o.label}</span>
</RadioGroup.Item>
))}
</RadioGroup.Root>
</div>
<div>
{operator === "range" ? (
<div className="px-1 pb-3 pt-1" key="range">
<div className="px-2 py-1.5">
<Label size="xsmall" weight="plus" htmlFor={GT_KEY}>
{t("filters.compare.greaterThan")}
</Label>
</div>
<div className="px-2 py-0.5">
<Input
name={GT_KEY}
size="small"
type="number"
defaultValue={getValue(currentValue, "gt")}
onChange={(e) => debouncedOnChange(e, "gt")}
/>
</div>
<div className="px-2 py-1.5">
<Label size="xsmall" weight="plus" htmlFor={LT_KEY}>
{t("filters.compare.lessThan")}
</Label>
</div>
<div className="px-2 py-0.5">
<Input
name={LT_KEY}
size="small"
type="number"
defaultValue={getValue(currentValue, "lt")}
onChange={(e) => debouncedOnChange(e, "lt")}
/>
</div>
</div>
) : (
<div className="px-1 pb-3 pt-1" key="exact">
<div className="px-2 py-1.5">
<Label size="xsmall" weight="plus" htmlFor={EQ_KEY}>
{label}
</Label>
</div>
<div className="px-2 py-0.5">
<Input
name={EQ_KEY}
size="small"
type="number"
defaultValue={getValue(currentValue, "eq")}
onChange={(e) => debouncedOnChange(e, "eq")}
/>
</div>
</div>
)}
</div>
</Popover.Content>
</Popover.Portal>
)}
</Popover.Root>
)
}
@@ -241,10 +245,12 @@ export const NumberFilter = ({
const NumberDisplay = ({
label,
value,
readonly,
onRemove,
}: {
label: string
value?: string[]
readonly?: boolean
onRemove: () => void
}) => {
const { t } = useTranslation()
@@ -282,8 +288,10 @@ const NumberDisplay = ({
asChild
className={clx(
"bg-ui-bg-field transition-fg shadow-borders-base text-ui-fg-subtle flex cursor-pointer select-none items-center rounded-md",
"hover:bg-ui-bg-field-hover",
"data-[state=open]:bg-ui-bg-field-hover"
{
"hover:bg-ui-bg-field-hover": !readonly,
"data-[state=open]:bg-ui-bg-field-hover": !readonly,
}
)}
>
<div>
@@ -317,7 +325,7 @@ const NumberDisplay = ({
</div>
</div>
)}
{value && (
{!readonly && value && (
<div>
<button
onClick={handleRemove}

View File

@@ -11,6 +11,7 @@ import { IFilter } from "./types"
interface SelectFilterProps extends IFilter {
options: { label: string; value: unknown }[]
readonly?: boolean
multiple?: boolean
searchable?: boolean
}
@@ -18,6 +19,7 @@ interface SelectFilterProps extends IFilter {
export const SelectFilter = ({
filter,
prefix,
readonly,
multiple,
searchable,
options,
@@ -80,103 +82,107 @@ export const SelectFilter = ({
return (
<Popover.Root modal open={open} onOpenChange={handleOpenChange}>
<SelectDisplay
readonly={readonly}
label={label}
value={labelValues}
onRemove={handleRemove}
/>
<Popover.Portal>
<Popover.Content
hideWhenDetached
align="start"
sideOffset={8}
collisionPadding={8}
className={clx(
"bg-ui-bg-base text-ui-fg-base shadow-elevation-flyout z-[1] h-full max-h-[200px] w-[300px] overflow-hidden rounded-lg outline-none"
)}
onInteractOutside={(e) => {
if (e.target instanceof HTMLElement) {
if (
e.target.attributes.getNamedItem("data-name")?.value ===
"filters_menu_content"
) {
e.preventDefault()
e.stopPropagation()
{!readonly && (
<Popover.Portal>
<Popover.Content
hideWhenDetached
align="start"
sideOffset={8}
collisionPadding={8}
className={clx(
"bg-ui-bg-base text-ui-fg-base shadow-elevation-flyout z-[1] h-full max-h-[200px] w-[300px] overflow-hidden rounded-lg outline-none"
)}
onInteractOutside={(e) => {
if (e.target instanceof HTMLElement) {
if (
e.target.attributes.getNamedItem("data-name")?.value ===
"filters_menu_content"
) {
e.preventDefault()
e.stopPropagation()
}
}
}
}}
>
<Command className="h-full">
{searchable && (
<div className="border-b p-1">
<div className="grid grid-cols-[1fr_20px] gap-x-2 rounded-md px-2 py-1">
<Command.Input
ref={setSearchRef}
value={search}
onValueChange={setSearch}
className="txt-compact-small placeholder:text-ui-fg-muted bg-transparent outline-none"
placeholder="Search"
/>
<div className="flex h-5 w-5 items-center justify-center">
<button
disabled={!search}
onClick={handleClearSearch}
className={clx(
"transition-fg text-ui-fg-muted focus-visible:bg-ui-bg-base-pressed rounded-md outline-none",
{
invisible: !search,
}
)}
>
<XMarkMini />
</button>
}}
>
<Command className="h-full">
{searchable && (
<div className="border-b p-1">
<div className="grid grid-cols-[1fr_20px] gap-x-2 rounded-md px-2 py-1">
<Command.Input
ref={setSearchRef}
value={search}
onValueChange={setSearch}
className="txt-compact-small placeholder:text-ui-fg-muted bg-transparent outline-none"
placeholder="Search"
/>
<div className="flex h-5 w-5 items-center justify-center">
<button
disabled={!search}
onClick={handleClearSearch}
className={clx(
"transition-fg text-ui-fg-muted focus-visible:bg-ui-bg-base-pressed rounded-md outline-none",
{
invisible: !search,
}
)}
>
<XMarkMini />
</button>
</div>
</div>
</div>
</div>
)}
<Command.Empty className="txt-compact-small flex items-center justify-center p-1">
<span className="w-full px-2 py-1 text-center">
{t("general.noResultsTitle")}
</span>
</Command.Empty>
<Command.List className="h-full max-h-[163px] min-h-[0] overflow-auto p-1 outline-none">
{options.map((option) => {
const isSelected = selectedParams
.get()
.includes(String(option.value))
)}
<Command.Empty className="txt-compact-small flex items-center justify-center p-1">
<span className="w-full px-2 py-1 text-center">
{t("general.noResultsTitle")}
</span>
</Command.Empty>
<Command.List className="h-full max-h-[163px] min-h-[0] overflow-auto p-1 outline-none">
{options.map((option) => {
const isSelected = selectedParams
.get()
.includes(String(option.value))
return (
<Command.Item
key={String(option.value)}
className="bg-ui-bg-base hover:bg-ui-bg-base-hover aria-selected:bg-ui-bg-base-pressed focus-visible:bg-ui-bg-base-pressed text-ui-fg-base data-[disabled]:text-ui-fg-disabled txt-compact-small relative flex cursor-pointer select-none items-center gap-x-2 rounded-md px-2 py-1.5 outline-none transition-colors data-[disabled]:pointer-events-none"
value={option.label}
onSelect={() => {
handleSelect(option.value)
}}
>
<div
className={clx(
"transition-fg flex h-5 w-5 items-center justify-center",
{
"[&_svg]:invisible": !isSelected,
}
)}
return (
<Command.Item
key={String(option.value)}
className="bg-ui-bg-base hover:bg-ui-bg-base-hover aria-selected:bg-ui-bg-base-pressed focus-visible:bg-ui-bg-base-pressed text-ui-fg-base data-[disabled]:text-ui-fg-disabled txt-compact-small relative flex cursor-pointer select-none items-center gap-x-2 rounded-md px-2 py-1.5 outline-none transition-colors data-[disabled]:pointer-events-none"
value={option.label}
onSelect={() => {
handleSelect(option.value)
}}
>
{multiple ? <CheckMini /> : <EllipseMiniSolid />}
</div>
{option.label}
</Command.Item>
)
})}
</Command.List>
</Command>
</Popover.Content>
</Popover.Portal>
<div
className={clx(
"transition-fg flex h-5 w-5 items-center justify-center",
{
"[&_svg]:invisible": !isSelected,
}
)}
>
{multiple ? <CheckMini /> : <EllipseMiniSolid />}
</div>
{option.label}
</Command.Item>
)
})}
</Command.List>
</Command>
</Popover.Content>
</Popover.Portal>
)}
</Popover.Root>
)
}
type SelectDisplayProps = {
label: string
readonly?: boolean
value?: string | string[]
onRemove: () => void
}
@@ -185,6 +191,7 @@ export const SelectDisplay = ({
label,
value,
onRemove,
readonly,
}: SelectDisplayProps) => {
const { t } = useTranslation()
const v = value ? (Array.isArray(value) ? value : [value]) : null
@@ -200,8 +207,10 @@ export const SelectDisplay = ({
<div
className={clx(
"bg-ui-bg-field transition-fg shadow-borders-base text-ui-fg-subtle flex cursor-pointer select-none items-center overflow-hidden rounded-md",
"hover:bg-ui-bg-field-hover",
"data-[state=open]:bg-ui-bg-field-hover"
{
"hover:bg-ui-bg-field-hover": !readonly,
"data-[state=open]:bg-ui-bg-field-hover": !readonly,
}
)}
>
<div
@@ -242,7 +251,7 @@ export const SelectDisplay = ({
</div>
)}
</div>
{v && v.length > 0 && (
{!readonly && v && v.length > 0 && (
<div>
<button
onClick={handleRemove}

View File

@@ -13,6 +13,7 @@ type StringFilterProps = IFilter
export const StringFilter = ({
filter,
prefix,
readonly,
openOnMount,
}: StringFilterProps) => {
const [open, setOpen] = useState(openOnMount)
@@ -67,45 +68,52 @@ export const StringFilter = ({
return (
<Popover.Root modal open={open} onOpenChange={handleOpenChange}>
<StringDisplay label={label} value={query?.[0]} onRemove={handleRemove} />
<Popover.Portal>
<Popover.Content
hideWhenDetached
align="start"
sideOffset={8}
collisionPadding={8}
className={clx(
"bg-ui-bg-base text-ui-fg-base shadow-elevation-flyout z-[1] h-full max-h-[200px] w-[300px] overflow-hidden rounded-lg outline-none"
)}
onInteractOutside={(e) => {
if (e.target instanceof HTMLElement) {
if (
e.target.attributes.getNamedItem("data-name")?.value ===
"filters_menu_content"
) {
e.preventDefault()
e.stopPropagation()
<StringDisplay
label={label}
value={query?.[0]}
onRemove={handleRemove}
readonly={readonly}
/>
{!readonly && (
<Popover.Portal>
<Popover.Content
hideWhenDetached
align="start"
sideOffset={8}
collisionPadding={8}
className={clx(
"bg-ui-bg-base text-ui-fg-base shadow-elevation-flyout z-[1] h-full max-h-[200px] w-[300px] overflow-hidden rounded-lg outline-none"
)}
onInteractOutside={(e) => {
if (e.target instanceof HTMLElement) {
if (
e.target.attributes.getNamedItem("data-name")?.value ===
"filters_menu_content"
) {
e.preventDefault()
e.stopPropagation()
}
}
}
}}
>
<div className="px-1 pb-3 pt-1">
<div className="px-2 py-1.5">
<Label size="xsmall" weight="plus" htmlFor={key}>
{label}
</Label>
}}
>
<div className="px-1 pb-3 pt-1">
<div className="px-2 py-1.5">
<Label size="xsmall" weight="plus" htmlFor={key}>
{label}
</Label>
</div>
<div className="px-2 py-0.5">
<Input
name={key}
size="small"
defaultValue={query?.[0] || undefined}
onChange={debouncedOnChange}
/>
</div>
</div>
<div className="px-2 py-0.5">
<Input
name={key}
size="small"
defaultValue={query?.[0] || undefined}
onChange={debouncedOnChange}
/>
</div>
</div>
</Popover.Content>
</Popover.Portal>
</Popover.Content>
</Popover.Portal>
)}
</Popover.Root>
)
}
@@ -113,10 +121,12 @@ export const StringFilter = ({
const StringDisplay = ({
label,
value,
readonly,
onRemove,
}: {
label: string
value?: string
readonly?: boolean
onRemove: () => void
}) => {
const { t } = useTranslation()
@@ -126,8 +136,10 @@ const StringDisplay = ({
<div
className={clx(
"bg-ui-bg-field transition-fg shadow-borders-base text-ui-fg-subtle flex cursor-pointer select-none items-center overflow-hidden rounded-md",
"hover:bg-ui-bg-field-hover",
"data-[state=open]:bg-ui-bg-field-hover"
{
"hover:bg-ui-bg-field-hover": !readonly,
"data-[state=open]:bg-ui-bg-field-hover": !readonly,
}
)}
>
<div
@@ -168,7 +180,7 @@ const StringDisplay = ({
</div>
)}
</div>
{!!value && (
{!readonly && !!value && (
<div>
<button
onClick={onRemove}

View File

@@ -3,6 +3,7 @@ export interface IFilter {
key: string
label: string
}
readonly?: boolean
openOnMount?: boolean
prefix?: string
}