feat: add searchable dropdown with Popover + Command pattern
Rewrite SelectField to use shadcn/ui Popover + Command (cmdk) pattern for searchable selects, following best practices. This eliminates console errors from the previous input-inside-SelectContent approach. Changes: - SelectField.js: Use Popover + Command for searchable fields - Add Command component with CommandInput for proper search - Update dialog.jsx to use Huge Icons instead of lucide-react - Simplify searchable logic to follow PHP config directly The Command component handles keyboard navigation and filtering properly without focus event conflicts.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
<?php return array('dependencies' => array('react', 'react-dom', 'wp-components', 'wp-element', 'wp-i18n', 'wp-icons/build/arrow-left', 'wp-icons/build/bell', 'wp-icons/build/message', 'wp-icons/build/trash'), 'version' => '79dab88e37717bf64790');
|
<?php return array('dependencies' => array('react', 'react-dom', 'wp-components', 'wp-element', 'wp-i18n', 'wp-icons/build/arrow-left', 'wp-icons/build/bell', 'wp-icons/build/message', 'wp-icons/build/trash'), 'version' => 'e564b3f018fca608f7b7');
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
216
src/admin/components/field-renderer/FieldTypes/SelectField.js
Normal file
216
src/admin/components/field-renderer/FieldTypes/SelectField.js
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
/**
|
||||||
|
* SelectField - Renders select dropdown fields
|
||||||
|
* - Non-searchable: uses shadcn/ui Select
|
||||||
|
* - Searchable: uses Popover + Command (Combobox pattern)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState, useMemo } from '@wordpress/element';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
|
import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from '@/components/ui/command';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { HugeiconsIcon } from '@hugeicons/react';
|
||||||
|
import { ArrowDown01Icon, Tick01Icon, Cancel01Icon } from '@hugeicons/core-free-icons';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
function HtmlContent({ html, className }) {
|
||||||
|
if (!html) return null;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={className}
|
||||||
|
dangerouslySetInnerHTML={{ __html: html }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SelectField({ field, value, onChange, error }) {
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
// Append asterisk to HTML if required
|
||||||
|
const labelHtml = field.required
|
||||||
|
? (field.label || '') + '<span class="text-destructive ml-0.5">*</span>'
|
||||||
|
: field.label;
|
||||||
|
|
||||||
|
// Determine if searchable - follows PHP config directly
|
||||||
|
const isSearchable = field.searchable ?? false;
|
||||||
|
|
||||||
|
// Get display label for current value
|
||||||
|
const displayLabel = useMemo(() => {
|
||||||
|
if (!value) return field.placeholder || `Select...`;
|
||||||
|
const label = field.options?.[value];
|
||||||
|
if (typeof label === 'string') {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
return String(label || value);
|
||||||
|
}, [value, field.options, field.placeholder]);
|
||||||
|
|
||||||
|
// Filter options based on search query
|
||||||
|
const filteredOptions = useMemo(() => {
|
||||||
|
if (!isSearchable || !searchQuery) {
|
||||||
|
return field.options || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = searchQuery.toLowerCase();
|
||||||
|
const filtered = {};
|
||||||
|
|
||||||
|
Object.entries(field.options || {}).forEach(([optValue, optLabel]) => {
|
||||||
|
const label = typeof optLabel === 'string' ? optLabel : String(optLabel);
|
||||||
|
if (label.toLowerCase().includes(query) || optValue.toLowerCase().includes(query)) {
|
||||||
|
filtered[optValue] = optLabel;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}, [field.options, searchQuery, isSearchable]);
|
||||||
|
|
||||||
|
const handleSelect = (newValue) => {
|
||||||
|
onChange(newValue);
|
||||||
|
setOpen(false);
|
||||||
|
setSearchQuery('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClear = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onChange('');
|
||||||
|
setSearchQuery('');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Non-searchable: use standard Select component
|
||||||
|
if (!isSearchable) {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-[30%_70%] gap-4 items-start py-2.5 px-1">
|
||||||
|
<div className="space-y-1 min-w-0">
|
||||||
|
<Label htmlFor={field.name} className="text-sm leading-tight">
|
||||||
|
<HtmlContent html={labelHtml} className="flex flex-wrap gap-1 items-center" />
|
||||||
|
</Label>
|
||||||
|
{field.description && (
|
||||||
|
<HtmlContent
|
||||||
|
html={field.description}
|
||||||
|
className="text-xs text-muted-foreground wrap-break-word"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 text-left">
|
||||||
|
<Select
|
||||||
|
value={value || ''}
|
||||||
|
onValueChange={onChange}
|
||||||
|
>
|
||||||
|
<SelectTrigger className={cn(error && "border-destructive")}>
|
||||||
|
<SelectValue placeholder={field.placeholder || `Select...`} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{Object.entries(field.options || {}).map(([optValue, optLabel]) => (
|
||||||
|
<SelectItem key={optValue} value={optValue}>
|
||||||
|
{optLabel}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{error && (
|
||||||
|
<p className="text-sm text-destructive mt-1">
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Searchable: use Popover + Command pattern (Combobox)
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-[30%_70%] gap-4 items-start py-2.5 px-1">
|
||||||
|
<div className="space-y-1 min-w-0">
|
||||||
|
<Label htmlFor={field.name} className="text-sm leading-tight">
|
||||||
|
<HtmlContent html={labelHtml} className="flex flex-wrap gap-1 items-center" />
|
||||||
|
</Label>
|
||||||
|
{field.description && (
|
||||||
|
<HtmlContent
|
||||||
|
html={field.description}
|
||||||
|
className="text-xs text-muted-foreground wrap-break-word"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 text-left">
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="combobox"
|
||||||
|
aria-expanded={open}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center justify-between w-full rounded-[30px]! border border-input bg-background shadow-sm px-3 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground h-9",
|
||||||
|
!value && "text-muted-foreground",
|
||||||
|
error && "border-destructive"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="truncate flex-1 text-left">{displayLabel}</span>
|
||||||
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
|
{value && (
|
||||||
|
<span
|
||||||
|
className="opacity-50 hover:opacity-100"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleClear(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HugeiconsIcon
|
||||||
|
icon={Cancel01Icon}
|
||||||
|
size={14}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<HugeiconsIcon icon={ArrowDown01Icon} size={16} className="opacity-50" />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="p-0 w-[--radix-popover-trigger-width]" align="start">
|
||||||
|
<Command shouldFilter={false}>
|
||||||
|
<CommandInput
|
||||||
|
placeholder={`Search ${field.label}...`}
|
||||||
|
value={searchQuery}
|
||||||
|
onValueChange={setSearchQuery}
|
||||||
|
className={"focus:shadow-none! border-none!"}
|
||||||
|
/>
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>
|
||||||
|
No results found
|
||||||
|
</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{Object.entries(filteredOptions).map(([optValue, optLabel]) => (
|
||||||
|
<CommandItem
|
||||||
|
key={optValue}
|
||||||
|
value={optValue}
|
||||||
|
onSelect={() => handleSelect(optValue)}
|
||||||
|
>
|
||||||
|
<HugeiconsIcon
|
||||||
|
icon={Tick01Icon}
|
||||||
|
size={14}
|
||||||
|
className={cn(
|
||||||
|
"mr-2",
|
||||||
|
value === optValue ? "opacity-100" : "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{optLabel}
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
{error && (
|
||||||
|
<p className="text-sm text-destructive mt-1">
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
160
src/admin/components/ui/command.jsx
Normal file
160
src/admin/components/ui/command.jsx
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { Command as CommandPrimitive } from "cmdk"
|
||||||
|
import { HugeiconsIcon } from '@hugeicons/react';
|
||||||
|
import { Search01Icon } from '@hugeicons/core-free-icons';
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
|
|
||||||
|
function Command({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive
|
||||||
|
data-slot="command"
|
||||||
|
className={cn(
|
||||||
|
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandDialog({
|
||||||
|
title = "Command Palette",
|
||||||
|
description = "Search for a command to run...",
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
showCloseButton = true,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog {...props}>
|
||||||
|
<DialogHeader className="sr-only">
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogDescription>{description}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogContent
|
||||||
|
className={cn("overflow-hidden p-0", className)}
|
||||||
|
showCloseButton={showCloseButton}>
|
||||||
|
<Command
|
||||||
|
className="**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||||
|
{children}
|
||||||
|
</Command>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandInput({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="command-input-wrapper"
|
||||||
|
className="flex h-9 items-center gap-2 border-b px-3">
|
||||||
|
<HugeiconsIcon icon={Search01Icon} className="size-4 shrink-0 opacity-50" />
|
||||||
|
<CommandPrimitive.Input
|
||||||
|
data-slot="command-input"
|
||||||
|
className={cn(
|
||||||
|
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandList({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.List
|
||||||
|
data-slot="command-list"
|
||||||
|
className={cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className)}
|
||||||
|
{...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandEmpty({
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (<CommandPrimitive.Empty data-slot="command-empty" className="py-6 text-center text-sm" {...props} />);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandGroup({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Group
|
||||||
|
data-slot="command-group"
|
||||||
|
className={cn(
|
||||||
|
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandSeparator({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Separator
|
||||||
|
data-slot="command-separator"
|
||||||
|
className={cn("-mx-1 h-px bg-border", className)}
|
||||||
|
{...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandItem({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Item
|
||||||
|
data-slot="command-item"
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandShortcut({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
data-slot="command-shortcut"
|
||||||
|
className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
|
||||||
|
{...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Command,
|
||||||
|
CommandDialog,
|
||||||
|
CommandInput,
|
||||||
|
CommandList,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandItem,
|
||||||
|
CommandShortcut,
|
||||||
|
CommandSeparator,
|
||||||
|
}
|
||||||
146
src/admin/components/ui/dialog.jsx
Normal file
146
src/admin/components/ui/dialog.jsx
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { HugeiconsIcon } from '@hugeicons/react';
|
||||||
|
import { Cancel01Icon } from '@hugeicons/core-free-icons';
|
||||||
|
import { Dialog as DialogPrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
|
||||||
|
function Dialog({
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTrigger({
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogPortal({
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogClose({
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogOverlay({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
data-slot="dialog-overlay"
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
showCloseButton = true,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DialogPortal data-slot="dialog-portal">
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
data-slot="dialog-content"
|
||||||
|
className={cn(
|
||||||
|
"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}>
|
||||||
|
{children}
|
||||||
|
{showCloseButton && (
|
||||||
|
<DialogPrimitive.Close
|
||||||
|
data-slot="dialog-close"
|
||||||
|
className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
|
||||||
|
<HugeiconsIcon icon={Cancel01Icon} />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
)}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogHeader({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="dialog-header"
|
||||||
|
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||||
|
{...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogFooter({
|
||||||
|
className,
|
||||||
|
showCloseButton = false,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="dialog-footer"
|
||||||
|
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
|
||||||
|
{...props}>
|
||||||
|
{children}
|
||||||
|
{showCloseButton && (
|
||||||
|
<DialogPrimitive.Close asChild>
|
||||||
|
<Button variant="outline">Close</Button>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTitle({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
data-slot="dialog-title"
|
||||||
|
className={cn("text-lg leading-none font-semibold", className)}
|
||||||
|
{...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
data-slot="dialog-description"
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogPortal,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ function PopoverTrigger({ ...props }) {
|
|||||||
function PopoverContent({ className, align = "center", sideOffset = 4, ...props }) {
|
function PopoverContent({ className, align = "center", sideOffset = 4, ...props }) {
|
||||||
return (
|
return (
|
||||||
<PopoverPrimitive.Portal>
|
<PopoverPrimitive.Portal>
|
||||||
|
<div className="formipay-design-system">
|
||||||
<PopoverPrimitive.Content
|
<PopoverPrimitive.Content
|
||||||
data-slot="popover-content"
|
data-slot="popover-content"
|
||||||
align={align}
|
align={align}
|
||||||
@@ -22,6 +23,7 @@ function PopoverContent({ className, align = "center", sideOffset = 4, ...props
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</PopoverPrimitive.Portal>
|
</PopoverPrimitive.Portal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user