@open-mercato/ui 0.6.6-develop.6542.1.fee3e68aa6 → 0.6.6-develop.6551.1.1511bfa890

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/detail/AttachmentMetadataDialog.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { TagsInput } from '@open-mercato/ui/backend/inputs/TagsInput'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Copy, Download, Trash2 } from 'lucide-react'\nimport { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'\nimport { AttachmentContentPreview } from '@open-mercato/core/modules/attachments/components/AttachmentContentPreview'\nimport { buildAttachmentFileUrl, buildAttachmentImageUrl, slugifyAttachmentFileName } from '@open-mercato/core/modules/attachments/lib/imageUrls'\nimport { E } from '@open-mercato/core/generated-shims/entities.ids.generated'\n\nexport type AttachmentAssignment = {\n type: string\n id: string\n href?: string | null\n label?: string | null\n}\n\nexport type AttachmentItem = {\n id: string\n fileName: string\n fileSize: number\n mimeType?: string | null\n partitionCode?: string | null\n partitionTitle?: string | null\n url?: string | null\n createdAt?: string | null\n tags?: string[]\n assignments?: AttachmentAssignment[]\n thumbnailUrl?: string\n content?: string | null\n}\n\nexport type AssignmentDraft = {\n type: string\n id: string\n href?: string\n label?: string\n}\n\nexport type AttachmentMetadataSavePayload = {\n tags: string[]\n assignments: AssignmentDraft[]\n customFields?: Record<string, unknown>\n}\n\ntype AttachmentMetadataResponse = {\n item: {\n id: string\n fileName?: string\n fileSize?: number\n mimeType?: string | null\n partitionCode?: string\n partitionTitle?: string | null\n tags?: string[]\n content?: string | null\n assignments?: AttachmentAssignment[]\n customFields?: Record<string, unknown>\n }\n error?: string\n}\n\ntype AttachmentMetadataFormValues = {\n id: string\n tags?: string[]\n assignments?: AssignmentDraft[]\n} & Record<string, unknown>\n\ntype AssignmentEditorLabels = {\n title: string\n description: string\n type: string\n id: string\n href: string\n label: string\n add: string\n remove: string\n}\n\ntype AttachmentMetadataDialogProps = {\n open: boolean\n onOpenChange: (next: boolean) => void\n item: AttachmentItem | null\n availableTags: string[]\n onSave: (id: string, payload: AttachmentMetadataSavePayload) => Promise<void>\n}\n\nfunction formatFileSize(value: number): string {\n if (!Number.isFinite(value)) return '\\u2014'\n if (value <= 0) return '0 B'\n const units = ['B', 'KB', 'MB', 'GB', 'TB']\n let idx = 0\n let current = value\n while (current >= 1024 && idx < units.length - 1) {\n current /= 1024\n idx += 1\n }\n return `${current.toFixed(idx === 0 ? 0 : 1)} ${units[idx]}`\n}\n\nconst ENV_APP_URL = (process.env.NEXT_PUBLIC_APP_URL || '').replace(/\\/$/, '')\n\nfunction resolveAbsoluteUrl(path: string): string {\n if (!path) return path\n if (/^https?:\\/\\//i.test(path)) return path\n const base =\n ENV_APP_URL ||\n (typeof window !== 'undefined' && window.location?.origin ? window.location.origin : '')\n if (!base) return path\n const normalizedBase = base.replace(/\\/$/, '')\n return `${normalizedBase}${path.startsWith('/') ? path : `/${path}`}`\n}\n\nfunction normalizeCustomFieldSubmitValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.filter((entry) => entry !== undefined)\n }\n if (value === undefined) return null\n return value\n}\n\nfunction prepareAssignmentsForForm(assignments?: AttachmentAssignment[] | null): AssignmentDraft[] {\n return (assignments ?? []).map((assignment) => ({\n type: assignment.type,\n id: assignment.id,\n href: assignment.href ?? '',\n label: assignment.label ?? '',\n }))\n}\n\nfunction prefixCustomFieldValues(values?: Record<string, unknown> | null): Record<string, unknown> {\n if (!values) return {}\n const prefixed: Record<string, unknown> = {}\n Object.entries(values).forEach(([key, value]) => {\n if (!key) return\n if (key.startsWith('cf_')) {\n prefixed[key] = value\n } else if (key.startsWith('cf:')) {\n const normalized = key.slice(3)\n if (normalized) prefixed[`cf_${normalized}`] = value\n } else {\n prefixed[`cf_${key}`] = value\n }\n })\n return prefixed\n}\n\nfunction AssignmentInputRow({\n value,\n onChange,\n labels,\n disabled,\n onRemove,\n}: {\n value: AssignmentDraft\n onChange: (next: AssignmentDraft) => void\n labels: AssignmentEditorLabels\n disabled?: boolean\n onRemove: () => void\n}) {\n const trimmedLabel = value.label?.trim() ?? ''\n const trimmedType = value.type?.trim() ?? ''\n const trimmedId = value.id?.trim() ?? ''\n const primaryLabel = trimmedLabel || trimmedType || trimmedId\n const secondaryLabel = [\n trimmedLabel ? trimmedType : '',\n trimmedId && trimmedId !== primaryLabel ? trimmedId : '',\n ]\n .filter(Boolean)\n .join(' - ')\n\n return (\n <div data-assignment-card className=\"min-w-0 rounded-md border border-border/70 bg-background p-3\">\n <div className=\"mb-3 flex min-w-0 items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n {primaryLabel ? (\n <p className=\"truncate text-sm font-medium text-foreground\">{primaryLabel}</p>\n ) : null}\n {secondaryLabel ? (\n <p className=\"truncate text-xs text-muted-foreground\">{secondaryLabel}</p>\n ) : null}\n </div>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"default\"\n aria-label={labels.remove}\n onClick={onRemove}\n disabled={disabled}\n className=\"shrink-0\"\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n </div>\n <div className=\"grid min-w-0 grid-cols-1 gap-3 md:grid-cols-2\">\n <div className=\"min-w-0 space-y-1\">\n <label className=\"text-xs font-medium\">{labels.type}</label>\n <Input\n className=\"w-full min-w-0\"\n value={value.type}\n onChange={(event) => onChange({ ...value, type: event.target.value })}\n placeholder=\"catalog.product\"\n disabled={disabled}\n />\n </div>\n <div className=\"min-w-0 space-y-1\">\n <label className=\"text-xs font-medium\">{labels.id}</label>\n <Input\n className=\"w-full min-w-0\"\n value={value.id}\n onChange={(event) => onChange({ ...value, id: event.target.value })}\n placeholder=\"Record ID\"\n disabled={disabled}\n />\n </div>\n <div className=\"min-w-0 space-y-1 md:col-span-2\">\n <label className=\"text-xs font-medium\">{labels.href}</label>\n <Input\n className=\"w-full min-w-0\"\n value={value.href ?? ''}\n onChange={(event) => onChange({ ...value, href: event.target.value })}\n placeholder=\"https://\"\n disabled={disabled}\n />\n </div>\n <div className=\"min-w-0 space-y-1 md:col-span-2\">\n <label className=\"text-xs font-medium\">{labels.label}</label>\n <Input\n className=\"w-full min-w-0\"\n value={value.label ?? ''}\n onChange={(event) => onChange({ ...value, label: event.target.value })}\n placeholder=\"Optional label\"\n disabled={disabled}\n />\n </div>\n </div>\n </div>\n )\n}\n\nfunction AttachmentAssignmentsEditor({\n value,\n onChange,\n labels,\n disabled,\n}: {\n value: AssignmentDraft[]\n onChange: (next: AssignmentDraft[]) => void\n labels: AssignmentEditorLabels\n disabled?: boolean\n}) {\n const handleAdd = React.useCallback(() => {\n onChange([...value, { type: '', id: '', href: '', label: '' }])\n }, [onChange, value])\n\n const handleChange = React.useCallback(\n (index: number, next: AssignmentDraft) => {\n const draft = [...value]\n draft[index] = next\n onChange(draft)\n },\n [onChange, value],\n )\n\n const handleRemove = React.useCallback(\n (index: number) => {\n const next = value.filter((_, idx) => idx !== index)\n onChange(next)\n },\n [onChange, value],\n )\n\n return (\n <div className=\"space-y-2\">\n <div className=\"space-y-1\">\n <div className=\"text-sm font-medium\">{labels.title}</div>\n <p className=\"text-xs text-muted-foreground\">{labels.description}</p>\n </div>\n <div className=\"space-y-2\">\n {value.length ? value.map((entry, idx) => (\n <AssignmentInputRow\n key={`${entry.type}-${entry.id}-${idx}`}\n value={entry}\n labels={labels}\n disabled={disabled}\n onChange={(next) => handleChange(idx, next)}\n onRemove={() => handleRemove(idx)}\n />\n )) : (\n <div className=\"rounded-md border border-dashed border-border/70 px-3 py-4 text-xs text-muted-foreground\">\n {labels.description}\n </div>\n )}\n </div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleAdd} disabled={disabled}>\n {labels.add}\n </Button>\n </div>\n )\n}\n\nexport function AttachmentMetadataDialog({ open, onOpenChange, item, availableTags, onSave }: AttachmentMetadataDialogProps) {\n const t = useT()\n const [sizeWidth, setSizeWidth] = React.useState<string>('')\n const [sizeHeight, setSizeHeight] = React.useState<string>('')\n const [imageTab, setImageTab] = React.useState<'preview' | 'resize'>('preview')\n const [initialValues, setInitialValues] = React.useState<Partial<AttachmentMetadataFormValues> | null>(null)\n const [loading, setLoading] = React.useState(false)\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [extractedContent, setExtractedContent] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (!open || !item) {\n setInitialValues(null)\n setLoadError(null)\n setLoading(false)\n setImageTab('preview')\n setExtractedContent(null)\n return\n }\n let cancelled = false\n setLoading(true)\n setLoadError(null)\n setSizeWidth('')\n setSizeHeight('')\n setImageTab('preview')\n setInitialValues({\n id: item.id,\n tags: item.tags ?? [],\n assignments: prepareAssignmentsForForm(item.assignments),\n })\n setExtractedContent(item.content && item.content.trim() ? item.content : null)\n const loadDetails = async () => {\n try {\n const call = await apiCall<AttachmentMetadataResponse>(`/api/attachments/library/${encodeURIComponent(item.id)}`)\n if (!call.ok || !call.result?.item) {\n const message = call.result?.error || t('attachments.library.metadata.error', 'Failed to update metadata.')\n throw new Error(message)\n }\n const payload = call.result.item\n const prefixedCustom = prefixCustomFieldValues(payload.customFields)\n if (!cancelled) {\n setInitialValues({\n id: payload.id,\n tags: Array.isArray(payload.tags) ? payload.tags : [],\n assignments: prepareAssignmentsForForm(payload.assignments ?? item.assignments),\n ...prefixedCustom,\n })\n const nextContent = typeof payload.content === 'string' && payload.content.trim() ? payload.content : null\n setExtractedContent(nextContent)\n }\n } catch (err: any) {\n if (!cancelled) {\n const message =\n err?.message || t('attachments.library.metadata.loadError', 'Failed to load attachment metadata.')\n setLoadError(message)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n void loadDetails()\n return () => {\n cancelled = true\n }\n }, [item, open, t])\n\n const isImage = React.useMemo(() => Boolean(item?.mimeType?.toLowerCase().startsWith('image/')), [item])\n const previewUrl = React.useMemo(() => {\n if (!item) return null\n return (\n item.thumbnailUrl ??\n buildAttachmentImageUrl(item.id, {\n width: 320,\n height: 320,\n slug: slugifyAttachmentFileName(item.fileName),\n })\n )\n }, [item])\n const downloadUrl = React.useMemo(() => {\n if (!item) return null\n const original = buildAttachmentFileUrl(item.id, { download: true })\n return resolveAbsoluteUrl(original)\n }, [item])\n\n const assignmentLabels = React.useMemo(\n () => ({\n title: t('attachments.library.metadata.assignments.title', 'Assignments'),\n description: t(\n 'attachments.library.metadata.assignments.description',\n 'Add the records this attachment belongs to with optional links.',\n ),\n type: t('attachments.library.metadata.assignments.type', 'Type'),\n id: t('attachments.library.metadata.assignments.id', 'Record ID'),\n href: t('attachments.library.metadata.assignments.href', 'Link'),\n label: t('attachments.library.metadata.assignments.label', 'Label'),\n add: t('attachments.library.metadata.assignments.add', 'Add assignment'),\n remove: t('attachments.library.metadata.assignments.remove', 'Remove'),\n }),\n [t],\n )\n\n const metadataFields = React.useMemo<CrudField[]>(() => {\n return [\n {\n id: 'tags',\n label: t('attachments.library.table.tags', 'Tags'),\n type: 'custom',\n component: ({ value, setValue, disabled }) => (\n <TagsInput\n value={Array.isArray(value) ? (value as string[]) : []}\n onChange={(next) => setValue(next)}\n suggestions={availableTags}\n placeholder={t('attachments.library.metadata.tagsPlaceholder', 'Add tags')}\n disabled={Boolean(disabled) || loading}\n />\n ),\n },\n {\n id: 'assignments',\n label: '',\n type: 'custom',\n component: ({ value, setValue, disabled }) => (\n <AttachmentAssignmentsEditor\n value={Array.isArray(value) ? (value as AssignmentDraft[]) : []}\n onChange={(next) => setValue(next)}\n labels={assignmentLabels}\n disabled={Boolean(disabled) || loading}\n />\n ),\n },\n ]\n }, [assignmentLabels, availableTags, loading, t])\n\n const metadataGroups = React.useMemo<CrudFormGroup[]>(() => {\n return [\n {\n id: 'details',\n title: t('attachments.library.metadata.details', 'Details'),\n column: 1,\n fields: ['tags', 'assignments'],\n },\n {\n id: 'customFields',\n title: t('entities.customFields.title', 'Custom attributes'),\n column: 2,\n kind: 'customFields',\n },\n ]\n }, [t])\n\n const metadataSchema = React.useMemo(\n () =>\n z\n .object({\n id: z.string().min(1),\n tags: z.array(z.string()).optional(),\n assignments: z\n .array(\n z.object({\n type: z.string().min(1),\n id: z.string().min(1),\n href: z.string().optional(),\n label: z.string().optional(),\n }),\n )\n .optional(),\n })\n .passthrough(),\n [],\n )\n\n const handleSubmit = React.useCallback(\n async (values: AttachmentMetadataFormValues) => {\n if (!item) return\n const tags = Array.isArray(values.tags)\n ? values.tags.map((tag) => (typeof tag === 'string' ? tag.trim() : '')).filter((tag) => tag.length > 0)\n : []\n const assignments = Array.isArray(values.assignments)\n ? values.assignments\n .map((assignment) => ({\n type: assignment.type?.trim() ?? '',\n id: assignment.id?.trim() ?? '',\n href: assignment.href?.trim() || undefined,\n label: assignment.label?.trim() || undefined,\n }))\n .filter((assignment) => assignment.type && assignment.id)\n : []\n const customFields = collectCustomFieldValues(values, {\n transform: (value) => normalizeCustomFieldSubmitValue(value),\n })\n const payload: AttachmentMetadataSavePayload = {\n tags,\n assignments,\n }\n if (Object.keys(customFields).length) {\n payload.customFields = customFields\n }\n await onSave(item.id, payload)\n },\n [item, onSave],\n )\n\n const handleKeyDown = useDialogKeyHandler({\n onCancel: () => onOpenChange(false),\n })\n\n const handleCopyResizedUrl = React.useCallback(async () => {\n if (!item) return\n const width = sizeWidth ? Number(sizeWidth) : undefined\n const height = sizeHeight ? Number(sizeHeight) : undefined\n if (!width && !height) {\n flash(\n t('attachments.library.metadata.resizeTool.missing', 'Enter width or height to generate the URL.'),\n 'error',\n )\n return\n }\n const url = buildAttachmentImageUrl(item.id, {\n width: width && width > 0 ? width : undefined,\n height: height && height > 0 ? height : undefined,\n slug: slugifyAttachmentFileName(item.fileName),\n })\n const absolute = resolveAbsoluteUrl(url)\n try {\n await navigator.clipboard.writeText(absolute)\n flash(\n t('attachments.library.metadata.resizeTool.copied', 'Image URL copied.'),\n 'success',\n )\n } catch {\n flash(\n t('attachments.library.metadata.resizeTool.copyError', 'Unable to copy URL.'),\n 'error',\n )\n }\n }, [item, sizeHeight, sizeWidth, t])\n\n const loadMessage = t('attachments.library.metadata.loading', 'Loading attachment details\\u2026')\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-2xl\" onKeyDown={handleKeyDown}>\n <DialogHeader>\n <DialogTitle>{t('attachments.library.metadata.title', 'Edit attachment metadata')}</DialogTitle>\n </DialogHeader>\n {item ? (\n <div className=\"space-y-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"space-y-1 min-w-0\">\n <div className=\"truncate text-sm font-medium\" title={item.fileName}>\n {item.fileName}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {formatFileSize(item.fileSize)} {'\\u2022'} {item.partitionTitle ?? item.partitionCode}\n </div>\n </div>\n {downloadUrl ? (\n <Button variant=\"outline\" size=\"sm\" asChild className=\"shrink-0\">\n <a href={downloadUrl} download>\n <Download className=\"mr-2 h-4 w-4\" />\n {t('attachments.library.metadata.download', 'Download')}\n </a>\n </Button>\n ) : null}\n </div>\n {isImage ? (\n <div className=\"rounded border\">\n <div className=\"flex flex-wrap gap-4 border-b px-3 py-2 text-sm font-medium\" role=\"tablist\">\n {(['preview', 'resize'] as const).map((tab) => (\n <Button\n key={tab}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"tab\"\n aria-selected={imageTab === tab}\n onClick={() => setImageTab(tab)}\n className={cn(\n 'h-auto -mb-px rounded-none border-b-2 border-transparent px-0 py-1',\n imageTab === tab\n ? 'border-accent-indigo text-foreground'\n : 'text-muted-foreground hover:text-foreground',\n )}\n >\n {tab === 'preview'\n ? t('attachments.library.metadata.preview', 'Preview')\n : t('attachments.library.metadata.resizeTool.title', 'Generate resized URL')}\n </Button>\n ))}\n </div>\n <div className=\"space-y-3 p-3\">\n {imageTab === 'preview' ? (\n previewUrl ? (\n <img\n src={previewUrl}\n alt={item.fileName}\n className=\"h-48 w-full rounded-md bg-muted object-contain\"\n />\n ) : (\n <div className=\"text-sm text-muted-foreground\">\n {t('attachments.library.metadata.previewUnavailable', 'Preview unavailable.')}\n </div>\n )\n ) : (\n <div className=\"space-y-2\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\" htmlFor=\"resize-width\">\n {t('attachments.library.metadata.resizeTool.width', 'Width (px)')}\n </label>\n <Input\n id=\"resize-width\"\n type=\"number\"\n min={0}\n value={sizeWidth}\n onChange={(event) => setSizeWidth(event.target.value)}\n disabled={loading}\n />\n </div>\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\" htmlFor=\"resize-height\">\n {t('attachments.library.metadata.resizeTool.height', 'Height (px)')}\n </label>\n <Input\n id=\"resize-height\"\n type=\"number\"\n min={0}\n value={sizeHeight}\n onChange={(event) => setSizeHeight(event.target.value)}\n disabled={loading}\n />\n </div>\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"inline-flex items-center gap-2\"\n onClick={() => void handleCopyResizedUrl()}\n disabled={loading}\n >\n <Copy className=\"h-4 w-4\" />\n {t('attachments.library.metadata.resizeTool.copy', 'Copy URL')}\n </Button>\n </div>\n )}\n </div>\n </div>\n ) : null}\n {loadError ? (\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive\">\n {loadError}\n </div>\n ) : null}\n <div className=\"rounded border border-border/70 bg-muted/30 px-3 py-2\">\n <div className=\"text-xs font-semibold text-muted-foreground\">\n {t('attachments.library.metadata.extractedTitle', 'Extracted text')}\n </div>\n <AttachmentContentPreview\n content={extractedContent}\n emptyLabel={t('attachments.library.metadata.noContent', 'No text extracted')}\n showMoreLabel={t('attachments.library.metadata.showMore', 'Show more')}\n showLessLabel={t('attachments.library.metadata.showLess', 'Show less')}\n />\n </div>\n <CrudForm<AttachmentMetadataFormValues>\n embedded\n schema={metadataSchema}\n entityId={E.attachments.attachment}\n fields={metadataFields}\n groups={metadataGroups}\n initialValues={initialValues ?? undefined}\n isLoading={!initialValues || loading}\n loadingMessage={loadMessage}\n submitLabel={t('attachments.library.metadata.save', 'Save')}\n extraActions={\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)}>\n {t('attachments.library.metadata.cancel', 'Cancel')}\n </Button>\n }\n onSubmit={handleSubmit}\n />\n </div>\n ) : (\n <div className=\"py-8 text-center text-sm text-muted-foreground\">\n {t('attachments.library.metadata.noSelection', 'Select an attachment to edit.')}\n </div>\n )}\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\nimport { TagsInput } from '@open-mercato/ui/backend/inputs/TagsInput'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Copy, Download, Trash2 } from 'lucide-react'\nimport { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'\nimport { AttachmentContentPreview } from '@open-mercato/core/modules/attachments/components/AttachmentContentPreview'\nimport { buildAttachmentFileUrl, buildAttachmentImageUrl, slugifyAttachmentFileName } from '@open-mercato/core/modules/attachments/lib/imageUrls'\nimport { E } from '@open-mercato/core/generated-shims/entities.ids.generated'\n\nexport type AttachmentAssignment = {\n type: string\n id: string\n href?: string | null\n label?: string | null\n}\n\nexport type AttachmentItem = {\n id: string\n fileName: string\n fileSize: number\n mimeType?: string | null\n partitionCode?: string | null\n partitionTitle?: string | null\n url?: string | null\n createdAt?: string | null\n tags?: string[]\n assignments?: AttachmentAssignment[]\n thumbnailUrl?: string\n content?: string | null\n}\n\nexport type AssignmentDraft = {\n type: string\n id: string\n href?: string\n label?: string\n}\n\nexport type AttachmentMetadataSavePayload = {\n tags: string[]\n assignments: AssignmentDraft[]\n customFields?: Record<string, unknown>\n}\n\ntype AttachmentMetadataResponse = {\n item: {\n id: string\n fileName?: string\n fileSize?: number\n mimeType?: string | null\n partitionCode?: string\n partitionTitle?: string | null\n tags?: string[]\n content?: string | null\n assignments?: AttachmentAssignment[]\n customFields?: Record<string, unknown>\n }\n error?: string\n}\n\ntype AttachmentMetadataFormValues = {\n id: string\n tags?: string[]\n assignments?: AssignmentDraft[]\n} & Record<string, unknown>\n\ntype AssignmentEditorLabels = {\n title: string\n description: string\n type: string\n id: string\n href: string\n label: string\n add: string\n remove: string\n}\n\ntype AttachmentMetadataDialogProps = {\n open: boolean\n onOpenChange: (next: boolean) => void\n item: AttachmentItem | null\n availableTags: string[]\n onSave: (id: string, payload: AttachmentMetadataSavePayload) => Promise<void>\n}\n\nfunction formatFileSize(value: number): string {\n if (!Number.isFinite(value)) return '\\u2014'\n if (value <= 0) return '0 B'\n const units = ['B', 'KB', 'MB', 'GB', 'TB']\n let idx = 0\n let current = value\n while (current >= 1024 && idx < units.length - 1) {\n current /= 1024\n idx += 1\n }\n return `${current.toFixed(idx === 0 ? 0 : 1)} ${units[idx]}`\n}\n\nconst ENV_APP_URL = (process.env.NEXT_PUBLIC_APP_URL || '').replace(/\\/$/, '')\n\nfunction resolveAbsoluteUrl(path: string): string {\n if (!path) return path\n if (/^https?:\\/\\//i.test(path)) return path\n const base =\n ENV_APP_URL ||\n (typeof window !== 'undefined' && window.location?.origin ? window.location.origin : '')\n if (!base) return path\n const normalizedBase = base.replace(/\\/$/, '')\n return `${normalizedBase}${path.startsWith('/') ? path : `/${path}`}`\n}\n\nfunction normalizeCustomFieldSubmitValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.filter((entry) => entry !== undefined)\n }\n if (value === undefined) return null\n return value\n}\n\nfunction prepareAssignmentsForForm(assignments?: AttachmentAssignment[] | null): AssignmentDraft[] {\n return (assignments ?? []).map((assignment) => ({\n type: assignment.type,\n id: assignment.id,\n href: assignment.href ?? '',\n label: assignment.label ?? '',\n }))\n}\n\nfunction prefixCustomFieldValues(values?: Record<string, unknown> | null): Record<string, unknown> {\n if (!values) return {}\n const prefixed: Record<string, unknown> = {}\n Object.entries(values).forEach(([key, value]) => {\n if (!key) return\n if (key.startsWith('cf_')) {\n prefixed[key] = value\n } else if (key.startsWith('cf:')) {\n const normalized = key.slice(3)\n if (normalized) prefixed[`cf_${normalized}`] = value\n } else {\n prefixed[`cf_${key}`] = value\n }\n })\n return prefixed\n}\n\nfunction AssignmentInputRow({\n value,\n onChange,\n labels,\n disabled,\n onRemove,\n}: {\n value: AssignmentDraft\n onChange: (next: AssignmentDraft) => void\n labels: AssignmentEditorLabels\n disabled?: boolean\n onRemove: () => void\n}) {\n const trimmedLabel = value.label?.trim() ?? ''\n const trimmedType = value.type?.trim() ?? ''\n const trimmedId = value.id?.trim() ?? ''\n const primaryLabel = trimmedLabel || trimmedType || trimmedId\n const secondaryLabel = [\n trimmedLabel ? trimmedType : '',\n trimmedId && trimmedId !== primaryLabel ? trimmedId : '',\n ]\n .filter(Boolean)\n .join(' - ')\n\n return (\n <div data-assignment-card className=\"min-w-0 rounded-md border border-border/70 bg-background p-3\">\n <div className=\"mb-3 flex min-w-0 items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n {primaryLabel ? (\n <p className=\"truncate text-sm font-medium text-foreground\">{primaryLabel}</p>\n ) : null}\n {secondaryLabel ? (\n <p className=\"truncate text-xs text-muted-foreground\">{secondaryLabel}</p>\n ) : null}\n </div>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"default\"\n aria-label={labels.remove}\n onClick={onRemove}\n disabled={disabled}\n className=\"shrink-0\"\n >\n <Trash2 className=\"size-4\" />\n </IconButton>\n </div>\n <div className=\"grid min-w-0 grid-cols-1 gap-3 md:grid-cols-2\">\n <div className=\"min-w-0 space-y-1\">\n <label className=\"text-xs font-medium\">{labels.type}</label>\n <Input\n className=\"w-full min-w-0\"\n value={value.type}\n onChange={(event) => onChange({ ...value, type: event.target.value })}\n placeholder=\"catalog.product\"\n disabled={disabled}\n />\n </div>\n <div className=\"min-w-0 space-y-1\">\n <label className=\"text-xs font-medium\">{labels.id}</label>\n <Input\n className=\"w-full min-w-0\"\n value={value.id}\n onChange={(event) => onChange({ ...value, id: event.target.value })}\n placeholder=\"Record ID\"\n disabled={disabled}\n />\n </div>\n <div className=\"min-w-0 space-y-1 md:col-span-2\">\n <label className=\"text-xs font-medium\">{labels.href}</label>\n <Input\n className=\"w-full min-w-0\"\n value={value.href ?? ''}\n onChange={(event) => onChange({ ...value, href: event.target.value })}\n placeholder=\"https://\"\n disabled={disabled}\n />\n </div>\n <div className=\"min-w-0 space-y-1 md:col-span-2\">\n <label className=\"text-xs font-medium\">{labels.label}</label>\n <Input\n className=\"w-full min-w-0\"\n value={value.label ?? ''}\n onChange={(event) => onChange({ ...value, label: event.target.value })}\n placeholder=\"Optional label\"\n disabled={disabled}\n />\n </div>\n </div>\n </div>\n )\n}\n\nfunction AttachmentAssignmentsEditor({\n value,\n onChange,\n labels,\n disabled,\n}: {\n value: AssignmentDraft[]\n onChange: (next: AssignmentDraft[]) => void\n labels: AssignmentEditorLabels\n disabled?: boolean\n}) {\n const handleAdd = React.useCallback(() => {\n onChange([...value, { type: '', id: '', href: '', label: '' }])\n }, [onChange, value])\n\n const handleChange = React.useCallback(\n (index: number, next: AssignmentDraft) => {\n const draft = [...value]\n draft[index] = next\n onChange(draft)\n },\n [onChange, value],\n )\n\n const handleRemove = React.useCallback(\n (index: number) => {\n const next = value.filter((_, idx) => idx !== index)\n onChange(next)\n },\n [onChange, value],\n )\n\n return (\n <div className=\"space-y-2\">\n <div className=\"space-y-1\">\n <div className=\"text-sm font-medium\">{labels.title}</div>\n <p className=\"text-xs text-muted-foreground\">{labels.description}</p>\n </div>\n <div className=\"space-y-2\">\n {value.length ? value.map((entry, idx) => (\n <AssignmentInputRow\n key={`${entry.type}-${entry.id}-${idx}`}\n value={entry}\n labels={labels}\n disabled={disabled}\n onChange={(next) => handleChange(idx, next)}\n onRemove={() => handleRemove(idx)}\n />\n )) : (\n <div className=\"rounded-md border border-dashed border-border/70 px-3 py-4 text-xs text-muted-foreground\">\n {labels.description}\n </div>\n )}\n </div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleAdd} disabled={disabled}>\n {labels.add}\n </Button>\n </div>\n )\n}\n\nexport function AttachmentMetadataDialog({ open, onOpenChange, item, availableTags, onSave }: AttachmentMetadataDialogProps) {\n const t = useT()\n const [sizeWidth, setSizeWidth] = React.useState<string>('')\n const [sizeHeight, setSizeHeight] = React.useState<string>('')\n const [imageTab, setImageTab] = React.useState<'preview' | 'resize'>('preview')\n const [initialValues, setInitialValues] = React.useState<Partial<AttachmentMetadataFormValues> | null>(null)\n const [loading, setLoading] = React.useState(false)\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [extractedContent, setExtractedContent] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n if (!open || !item) {\n setInitialValues(null)\n setLoadError(null)\n setLoading(false)\n setImageTab('preview')\n setExtractedContent(null)\n return\n }\n let cancelled = false\n setLoading(true)\n setLoadError(null)\n setSizeWidth('')\n setSizeHeight('')\n setImageTab('preview')\n setInitialValues({\n id: item.id,\n tags: item.tags ?? [],\n assignments: prepareAssignmentsForForm(item.assignments),\n })\n setExtractedContent(item.content && item.content.trim() ? item.content : null)\n const loadDetails = async () => {\n try {\n const call = await apiCall<AttachmentMetadataResponse>(`/api/attachments/library/${encodeURIComponent(item.id)}`)\n if (!call.ok || !call.result?.item) {\n const message = call.result?.error || t('attachments.library.metadata.error', 'Failed to update metadata.')\n throw new Error(message)\n }\n const payload = call.result.item\n const prefixedCustom = prefixCustomFieldValues(payload.customFields)\n if (!cancelled) {\n setInitialValues({\n id: payload.id,\n tags: Array.isArray(payload.tags) ? payload.tags : [],\n assignments: prepareAssignmentsForForm(payload.assignments ?? item.assignments),\n ...prefixedCustom,\n })\n const nextContent = typeof payload.content === 'string' && payload.content.trim() ? payload.content : null\n setExtractedContent(nextContent)\n }\n } catch (err: any) {\n if (!cancelled) {\n const message =\n err?.message || t('attachments.library.metadata.loadError', 'Failed to load attachment metadata.')\n setLoadError(message)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n void loadDetails()\n return () => {\n cancelled = true\n }\n }, [item, open, t])\n\n const isImage = React.useMemo(() => Boolean(item?.mimeType?.toLowerCase().startsWith('image/')), [item])\n const previewUrl = React.useMemo(() => {\n if (!item) return null\n return (\n item.thumbnailUrl ??\n buildAttachmentImageUrl(item.id, {\n width: 320,\n height: 320,\n slug: slugifyAttachmentFileName(item.fileName),\n })\n )\n }, [item])\n const downloadUrl = React.useMemo(() => {\n if (!item) return null\n const original = buildAttachmentFileUrl(item.id, { download: true })\n return resolveAbsoluteUrl(original)\n }, [item])\n\n const assignmentLabels = React.useMemo(\n () => ({\n title: t('attachments.library.metadata.assignments.title', 'Assignments'),\n description: t(\n 'attachments.library.metadata.assignments.description',\n 'Add the records this attachment belongs to with optional links.',\n ),\n type: t('attachments.library.metadata.assignments.type', 'Type'),\n id: t('attachments.library.metadata.assignments.id', 'Record ID'),\n href: t('attachments.library.metadata.assignments.href', 'Link'),\n label: t('attachments.library.metadata.assignments.label', 'Label'),\n add: t('attachments.library.metadata.assignments.add', 'Add assignment'),\n remove: t('attachments.library.metadata.assignments.remove', 'Remove'),\n }),\n [t],\n )\n\n const metadataFields = React.useMemo<CrudField[]>(() => {\n return [\n {\n id: 'tags',\n label: t('attachments.library.table.tags', 'Tags'),\n type: 'custom',\n component: ({ value, setValue, disabled }) => (\n <TagsInput\n value={Array.isArray(value) ? (value as string[]) : []}\n onChange={(next) => setValue(next)}\n suggestions={availableTags}\n placeholder={t('attachments.library.metadata.tagsPlaceholder', 'Add tags')}\n disabled={Boolean(disabled) || loading}\n />\n ),\n },\n {\n id: 'assignments',\n label: '',\n type: 'custom',\n component: ({ value, setValue, disabled }) => (\n <AttachmentAssignmentsEditor\n value={Array.isArray(value) ? (value as AssignmentDraft[]) : []}\n onChange={(next) => setValue(next)}\n labels={assignmentLabels}\n disabled={Boolean(disabled) || loading}\n />\n ),\n },\n ]\n }, [assignmentLabels, availableTags, loading, t])\n\n const metadataGroups = React.useMemo<CrudFormGroup[]>(() => {\n return [\n {\n id: 'details',\n title: t('attachments.library.metadata.details', 'Details'),\n column: 1,\n fields: ['tags', 'assignments'],\n },\n {\n id: 'customFields',\n title: t('entities.customFields.title', 'Custom attributes'),\n column: 2,\n kind: 'customFields',\n },\n ]\n }, [t])\n\n const metadataSchema = React.useMemo(\n () =>\n z\n .object({\n id: z.string().min(1),\n tags: z.array(z.string()).optional(),\n assignments: z\n .array(\n z.object({\n type: z.string().min(1),\n id: z.string().min(1),\n href: z.string().optional(),\n label: z.string().optional(),\n }),\n )\n .optional(),\n })\n .passthrough(),\n [],\n )\n\n const handleSubmit = React.useCallback(\n async (values: AttachmentMetadataFormValues) => {\n if (!item) return\n const tags = Array.isArray(values.tags)\n ? values.tags.map((tag) => (typeof tag === 'string' ? tag.trim() : '')).filter((tag) => tag.length > 0)\n : []\n const assignments = Array.isArray(values.assignments)\n ? values.assignments\n .map((assignment) => ({\n type: assignment.type?.trim() ?? '',\n id: assignment.id?.trim() ?? '',\n href: assignment.href?.trim() || undefined,\n label: assignment.label?.trim() || undefined,\n }))\n .filter((assignment) => assignment.type && assignment.id)\n : []\n const customFields = collectCustomFieldValues(values, {\n transform: (value) => normalizeCustomFieldSubmitValue(value),\n })\n const payload: AttachmentMetadataSavePayload = {\n tags,\n assignments,\n }\n if (Object.keys(customFields).length) {\n payload.customFields = customFields\n }\n await onSave(item.id, payload)\n },\n [item, onSave],\n )\n\n const handleKeyDown = useDialogKeyHandler({\n onCancel: () => onOpenChange(false),\n })\n\n const handleCopyResizedUrl = React.useCallback(async () => {\n if (!item) return\n const width = sizeWidth ? Number(sizeWidth) : undefined\n const height = sizeHeight ? Number(sizeHeight) : undefined\n if (!width && !height) {\n flash(\n t('attachments.library.metadata.resizeTool.missing', 'Enter width or height to generate the URL.'),\n 'error',\n )\n return\n }\n const url = buildAttachmentImageUrl(item.id, {\n width: width && width > 0 ? width : undefined,\n height: height && height > 0 ? height : undefined,\n slug: slugifyAttachmentFileName(item.fileName),\n })\n const absolute = resolveAbsoluteUrl(url)\n try {\n await navigator.clipboard.writeText(absolute)\n flash(\n t('attachments.library.metadata.resizeTool.copied', 'Image URL copied.'),\n 'success',\n )\n } catch {\n flash(\n t('attachments.library.metadata.resizeTool.copyError', 'Unable to copy URL.'),\n 'error',\n )\n }\n }, [item, sizeHeight, sizeWidth, t])\n\n const loadMessage = t('attachments.library.metadata.loading', 'Loading attachment details\\u2026')\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-2xl\" onKeyDown={handleKeyDown}>\n <DialogHeader>\n <DialogTitle>{t('attachments.library.metadata.title', 'Edit attachment metadata')}</DialogTitle>\n </DialogHeader>\n {item ? (\n <div className=\"space-y-4\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"space-y-1 min-w-0\">\n <div className=\"truncate text-sm font-medium\" title={item.fileName}>\n {item.fileName}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {formatFileSize(item.fileSize)} {'\\u2022'} {item.partitionTitle ?? item.partitionCode}\n </div>\n </div>\n {downloadUrl ? (\n <Button variant=\"outline\" size=\"sm\" asChild className=\"shrink-0\">\n <a href={downloadUrl} download>\n <Download className=\"mr-2 h-4 w-4\" />\n {t('attachments.library.metadata.download', 'Download')}\n </a>\n </Button>\n ) : null}\n </div>\n {isImage ? (\n <div className=\"rounded border\">\n <Tabs\n value={imageTab}\n onValueChange={(value) => setImageTab(value as 'preview' | 'resize')}\n variant=\"underline\"\n >\n <TabsList className=\"w-full flex-wrap px-3\">\n {(['preview', 'resize'] as const).map((tab) => (\n <TabsTrigger key={tab} value={tab}>\n {tab === 'preview'\n ? t('attachments.library.metadata.preview', 'Preview')\n : t('attachments.library.metadata.resizeTool.title', 'Generate resized URL')}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n <div className=\"space-y-3 p-3\">\n {imageTab === 'preview' ? (\n previewUrl ? (\n <img\n src={previewUrl}\n alt={item.fileName}\n className=\"h-48 w-full rounded-md bg-muted object-contain\"\n />\n ) : (\n <div className=\"text-sm text-muted-foreground\">\n {t('attachments.library.metadata.previewUnavailable', 'Preview unavailable.')}\n </div>\n )\n ) : (\n <div className=\"space-y-2\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\" htmlFor=\"resize-width\">\n {t('attachments.library.metadata.resizeTool.width', 'Width (px)')}\n </label>\n <Input\n id=\"resize-width\"\n type=\"number\"\n min={0}\n value={sizeWidth}\n onChange={(event) => setSizeWidth(event.target.value)}\n disabled={loading}\n />\n </div>\n <div className=\"space-y-1\">\n <label className=\"text-xs font-medium\" htmlFor=\"resize-height\">\n {t('attachments.library.metadata.resizeTool.height', 'Height (px)')}\n </label>\n <Input\n id=\"resize-height\"\n type=\"number\"\n min={0}\n value={sizeHeight}\n onChange={(event) => setSizeHeight(event.target.value)}\n disabled={loading}\n />\n </div>\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"inline-flex items-center gap-2\"\n onClick={() => void handleCopyResizedUrl()}\n disabled={loading}\n >\n <Copy className=\"h-4 w-4\" />\n {t('attachments.library.metadata.resizeTool.copy', 'Copy URL')}\n </Button>\n </div>\n )}\n </div>\n </div>\n ) : null}\n {loadError ? (\n <div className=\"rounded border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive\">\n {loadError}\n </div>\n ) : null}\n <div className=\"rounded border border-border/70 bg-muted/30 px-3 py-2\">\n <div className=\"text-xs font-semibold text-muted-foreground\">\n {t('attachments.library.metadata.extractedTitle', 'Extracted text')}\n </div>\n <AttachmentContentPreview\n content={extractedContent}\n emptyLabel={t('attachments.library.metadata.noContent', 'No text extracted')}\n showMoreLabel={t('attachments.library.metadata.showMore', 'Show more')}\n showLessLabel={t('attachments.library.metadata.showLess', 'Show less')}\n />\n </div>\n <CrudForm<AttachmentMetadataFormValues>\n embedded\n schema={metadataSchema}\n entityId={E.attachments.attachment}\n fields={metadataFields}\n groups={metadataGroups}\n initialValues={initialValues ?? undefined}\n isLoading={!initialValues || loading}\n loadingMessage={loadMessage}\n submitLabel={t('attachments.library.metadata.save', 'Save')}\n extraActions={\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)}>\n {t('attachments.library.metadata.cancel', 'Cancel')}\n </Button>\n }\n onSubmit={handleSubmit}\n />\n </div>\n ) : (\n <div className=\"py-8 text-center text-sm text-muted-foreground\">\n {t('attachments.library.metadata.noSelection', 'Select an attachment to edit.')}\n </div>\n )}\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -276,7 +276,7 @@ function AndOrToggle({
276
276
  const andLabel = t("ui.advancedFilter.combinator.and", "And");
277
277
  const orLabel = t("ui.advancedFilter.combinator.or", "Or");
278
278
  const baseBtn = "h-8 px-3 text-sm font-medium outline-none focus-visible:shadow-focus disabled:cursor-not-allowed";
279
- const selBtn = "bg-brand-violet text-white hover:bg-brand-violet/90";
279
+ const selBtn = "bg-brand-violet text-brand-violet-foreground hover:bg-brand-violet/90";
280
280
  const unselBtn = "bg-background text-foreground hover:bg-accent";
281
281
  return /* @__PURE__ */ jsxs(
282
282
  "div",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/filters/AdvancedFilterBuilder.tsx"],
4
- "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { Plus, Trash2, X, GripVertical, ChevronDown } from 'lucide-react'\nimport {\n DndContext,\n DragOverlay,\n PointerSensor,\n KeyboardSensor,\n useSensor,\n useSensors,\n closestCenter,\n type DragEndEvent,\n type DragStartEvent,\n} from '@dnd-kit/core'\nimport {\n SortableContext,\n sortableKeyboardCoordinates,\n useSortable,\n verticalListSortingStrategy,\n} from '@dnd-kit/sortable'\nimport { CSS } from '@dnd-kit/utilities'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Input } from '../../primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../../primitives/select'\nimport { Popover, PopoverContent, PopoverTrigger } from '../../primitives/popover'\nimport { Checkbox } from '../../primitives/checkbox'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type {\n FilterFieldDef,\n FilterFieldType,\n FilterOperator,\n} from '@open-mercato/shared/lib/query/advanced-filter'\nimport {\n OPERATORS_BY_FIELD_TYPE,\n getDefaultOperator,\n isValuelessOperator,\n} from '@open-mercato/shared/lib/query/advanced-filter'\nimport {\n type AdvancedFilterTree,\n type FilterRule,\n type FilterGroup,\n type FilterCombinator,\n TREE_LIMITS,\n} from '@open-mercato/shared/lib/query/advanced-filter-tree'\nimport { treeReducer, canAddRule, canAddGroup, type TreeAction } from './treeReducer'\nimport { FilterFieldPicker } from './FilterFieldPicker'\n\nexport type AdvancedFilterBuilderProps = {\n fields: FilterFieldDef[]\n value: AdvancedFilterTree\n onChange: (state: AdvancedFilterTree) => void\n onApply: () => void\n onClear: () => void\n pendingErrors?: Array<{ ruleId: string; messageKey: string; message: string }>\n}\n\nconst OPERATOR_LABELS: Record<FilterOperator, string> = {\n is: 'is', is_not: 'is not', contains: 'contains', does_not_contain: 'does not contain',\n starts_with: 'starts with', ends_with: 'ends with', is_empty: 'is empty', is_not_empty: 'is not empty',\n equals: 'equals', not_equals: 'not equals', greater_than: 'greater than', less_than: 'less than',\n greater_or_equal: 'greater or equal', less_or_equal: 'less or equal', between: 'between',\n is_before: 'is before', is_after: 'is after', is_any_of: 'is any of', is_none_of: 'is none of',\n is_true: 'is true', is_false: 'is false', has_any_of: 'has any of', has_all_of: 'has all of', has_none_of: 'has none of',\n}\n\nfunction getFieldType(fields: FilterFieldDef[], fieldKey: string): FilterFieldType {\n return fields.find((f) => f.key === fieldKey)?.type ?? 'text'\n}\n\n// Operator value shapes. Used to decide whether changing operator preserves the\n// current value: same shape preserves (`is` \u2192 `is not` keeps \"Active\"); different\n// shape resets (`is` \u2192 `between` clears, because value goes from scalar to pair).\ntype OperatorShape = 'valueless' | 'multi' | 'range' | 'single'\n\nconst MULTI_VALUE_OPS = new Set<FilterOperator>([\n 'is_any_of', 'is_none_of', 'has_any_of', 'has_all_of', 'has_none_of',\n])\n\nfunction getOperatorShape(op: FilterOperator): OperatorShape {\n if (isValuelessOperator(op)) return 'valueless'\n if (MULTI_VALUE_OPS.has(op)) return 'multi'\n if (op === 'between') return 'range'\n return 'single'\n}\n\ntype DragHandleProps = React.HTMLAttributes<HTMLButtonElement> & {\n ref?: React.Ref<HTMLButtonElement>\n}\n\ntype Translator = ReturnType<typeof useT>\n\nexport function AdvancedFilterBuilder({\n fields, value, onChange, onApply, onClear, pendingErrors,\n}: AdvancedFilterBuilderProps) {\n const t = useT()\n\n const dispatch = React.useCallback((action: TreeAction) => {\n onChange(treeReducer(value, action))\n }, [onChange, value])\n\n React.useEffect(() => {\n function onKey(e: KeyboardEvent) {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n onApply()\n }\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [onApply])\n\n const errorByRuleId = React.useMemo(() => {\n const m = new Map<string, string>()\n for (const e of pendingErrors ?? []) m.set(e.ruleId, t(e.messageKey, e.message))\n return m\n }, [pendingErrors, t])\n\n const empty = value.root.children.length === 0\n const defaultField = fields.length > 0 ? fields[0].key : undefined\n\n // Per-group SortableContexts scope drag-and-drop to within-group ordering.\n // The DragOverlay below renders a stable preview that\n // matches the original row's footprint \u2014 without it, dnd-kit applies\n // `transform` to the source row in place, which the wrapping flex layout\n // distorted into a \"tall\" / morphed ghost.\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),\n )\n const [activeDrag, setActiveDrag] = React.useState<{ node: FilterRule | FilterGroup; parent: FilterGroup } | null>(null)\n const findNode = React.useCallback((id: string) => locateInTree(value.root, id), [value])\n\n const handleDragStart = React.useCallback((e: DragStartEvent) => {\n const found = findNode(String(e.active.id))\n if (found) setActiveDrag(found)\n }, [findNode])\n\n const handleDragEnd = React.useCallback((e: DragEndEvent) => {\n setActiveDrag(null)\n const { active, over } = e\n if (!over || active.id === over.id) return\n const from = findNode(String(active.id))\n const to = findNode(String(over.id))\n if (!from || !to) return\n if (from.parent.id !== to.parent.id) return\n const fromIdx = from.parent.children.findIndex((c) => c.id === active.id)\n const toIdx = to.parent.children.findIndex((c) => c.id === over.id)\n if (fromIdx < 0 || toIdx < 0) return\n dispatch({ type: 'reorderChildren', groupId: from.parent.id, fromIdx, toIdx })\n }, [dispatch, findNode])\n\n const handleDragCancel = React.useCallback(() => setActiveDrag(null), [])\n\n // Wrapper has no `min-w-[640px]`: it forced horizontal scroll on a 375px viewport.\n // Rules wrap onto multiple lines on mobile; on desktop (`sm:max-w-[720px]`) they fit\n // on one line.\n return (\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n onDragStart={handleDragStart}\n onDragEnd={handleDragEnd}\n onDragCancel={handleDragCancel}\n >\n <div className=\"flex flex-col gap-3 p-3\">\n {!empty ? (\n <GroupView\n group={value.root}\n level={1}\n fields={fields}\n tree={value}\n dispatch={dispatch}\n defaultField={defaultField}\n errorByRuleId={errorByRuleId}\n t={t}\n />\n ) : null}\n {!empty ? (\n <div className=\"flex items-center gap-3 pt-2 border-t border-border\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 py-0 text-base text-muted-foreground hover:bg-transparent hover:text-foreground\"\n onClick={onClear}\n >\n <X className=\"size-4\" />\n {t('ui.advancedFilter.clearAll', 'Clear all')}\n </Button>\n </div>\n ) : null}\n </div>\n {/* Portal DragOverlay to document.body. Radix DialogContent uses CSS `transform`\n for centering, which becomes the containing block for `position: fixed`\n descendants (dnd-kit's overlay) and shifts the ghost off-screen. Portaling\n restores viewport-relative coordinates. */}\n {typeof document !== 'undefined'\n ? createPortal(\n // dnd-kit's DragOverlay sets its own inline z-index, so a Tailwind\n // utility class would be overridden \u2014 read the DS token via CSS var.\n <DragOverlay dropAnimation={null} style={{ zIndex: 'var(--z-index-modal-elevated, 55)' }}>\n {activeDrag ? (\n <DragGhost node={activeDrag.node} parent={activeDrag.parent} fields={fields} t={t} />\n ) : null}\n </DragOverlay>,\n document.body,\n )\n : null}\n </DndContext>\n )\n}\n\n/** Find a node in the tree along with its parent group. Used by drag handlers. */\nfunction locateInTree(root: FilterGroup, id: string): { node: FilterRule | FilterGroup; parent: FilterGroup } | null {\n for (const child of root.children) {\n if (child.id === id) return { node: child, parent: root }\n if (child.type === 'group') {\n const found = locateInTree(child, id)\n if (found) return found\n }\n }\n return null\n}\n\n// Compact drag preview rendered inside DragOverlay. Cannot reuse RuleRow/GroupView \u2014\n// their popovers and form inputs conflict with the overlay.\nfunction DragGhost({\n node, parent, fields, t,\n}: {\n node: FilterRule | FilterGroup\n parent: FilterGroup\n fields: FilterFieldDef[]\n t: Translator\n}) {\n const isGroup = node.type === 'group'\n const labelParts: string[] = []\n if (isGroup) {\n const ruleCount = countRulesIn(node)\n labelParts.push(t('ui.advancedFilter.dragGhost.group', 'Group'))\n labelParts.push(`(${ruleCount})`)\n } else {\n const def = fields.find((f) => f.key === node.field)\n labelParts.push(def?.label ?? (node.field || t('ui.advancedFilter.dragGhost.unsetField', 'Unset field')))\n labelParts.push(OPERATOR_LABELS[node.operator] ?? node.operator)\n if (typeof node.value === 'string' && node.value) labelParts.push(`\"${node.value}\"`)\n }\n return (\n <div className=\"inline-flex items-center gap-2 rounded-md border border-border bg-popover px-3 py-1.5 text-sm shadow-md cursor-grabbing\">\n <GripVertical className=\"size-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">{parent.combinator === 'and' ? t('ui.advancedFilter.connector.and', 'and') : t('ui.advancedFilter.connector.or', 'or')}</span>\n <span className=\"font-medium\">{labelParts.join(' ')}</span>\n </div>\n )\n}\n\nfunction countRulesIn(group: FilterGroup): number {\n let n = 0\n for (const c of group.children) {\n if (c.type === 'rule') n += 1\n else n += countRulesIn(c)\n }\n return n\n}\n\nfunction GroupView({\n group, level, fields, tree, dispatch, defaultField, errorByRuleId, t,\n}: {\n group: FilterGroup\n level: number\n fields: FilterFieldDef[]\n tree: AdvancedFilterTree\n dispatch: (a: TreeAction) => void\n defaultField?: string\n errorByRuleId: Map<string, string>\n t: Translator\n}) {\n const isRoot = level === 1\n // Sub-groups render as a card so the nesting boundary is visible; root group stays\n // flush with the popover so it doesn't double-up the dialog's own card chrome.\n const containerClass = isRoot\n ? 'space-y-3'\n : 'space-y-3 rounded-lg border border-border bg-muted/30 p-3'\n\n // Toolbar-first layout (Kendo React Filter pattern): one [And]/[Or] toggle per group\n // is the only combinator UI. Mixing AND with OR requires `Add subgroup`. Matches\n // `CompositeFilterDescriptor` semantics (one logic per group).\n return (\n <div className={containerClass}>\n <GroupToolbar\n group={group}\n level={level}\n tree={tree}\n dispatch={dispatch}\n defaultField={defaultField}\n fields={fields}\n isRoot={isRoot}\n t={t}\n />\n\n <SortableChildrenList\n group={group}\n level={level}\n fields={fields}\n tree={tree}\n dispatch={dispatch}\n defaultField={defaultField}\n errorByRuleId={errorByRuleId}\n t={t}\n />\n </div>\n )\n}\n\n// Kendo-style segmented `[And] [Or]` toggle. Clicking the unselected side flips\n// the group's combinator. Selected side is brand-violet filled so the affordance\n// is unambiguous.\nfunction AndOrToggle({\n combinator, onChange, t,\n}: {\n combinator: FilterCombinator\n onChange: (c: FilterCombinator) => void\n t: Translator\n}) {\n const andLabel = t('ui.advancedFilter.combinator.and', 'And')\n const orLabel = t('ui.advancedFilter.combinator.or', 'Or')\n const baseBtn = 'h-8 px-3 text-sm font-medium outline-none focus-visible:shadow-focus disabled:cursor-not-allowed'\n const selBtn = 'bg-brand-violet text-white hover:bg-brand-violet/90'\n const unselBtn = 'bg-background text-foreground hover:bg-accent'\n return (\n <div\n role=\"group\"\n aria-label={t('ui.advancedFilter.combinator.label', 'Group combinator')}\n className=\"inline-flex rounded-md border border-input overflow-hidden shrink-0\"\n data-testid=\"advanced-filter-combinator-toggle\"\n data-combinator={combinator}\n >\n <button\n type=\"button\"\n aria-pressed={combinator === 'and'}\n onClick={() => combinator !== 'and' && onChange('and')}\n className={`${baseBtn} ${combinator === 'and' ? selBtn : unselBtn}`}\n >\n {andLabel}\n </button>\n <button\n type=\"button\"\n aria-pressed={combinator === 'or'}\n onClick={() => combinator !== 'or' && onChange('or')}\n className={`${baseBtn} border-l border-input ${combinator === 'or' ? selBtn : unselBtn}`}\n >\n {orLabel}\n </button>\n </div>\n )\n}\n\nfunction SortableChildrenList({\n group, level, fields, tree, dispatch, defaultField, errorByRuleId, t,\n}: {\n group: FilterGroup\n level: number\n fields: FilterFieldDef[]\n tree: AdvancedFilterTree\n dispatch: (a: TreeAction) => void\n defaultField?: string\n errorByRuleId: Map<string, string>\n t: Translator\n}) {\n // DndContext + handlers live one level up so a drag can move children across\n // groups. Each group owns its own SortableContext to scope sort ordering.\n return (\n <SortableContext items={group.children.map((c) => c.id)} strategy={verticalListSortingStrategy}>\n {group.children.map((child, idx) => (\n <SortableRow\n key={child.id}\n child={child}\n idx={idx}\n parent={group}\n level={level}\n fields={fields}\n tree={tree}\n dispatch={dispatch}\n defaultField={defaultField}\n errorByRuleId={errorByRuleId}\n t={t}\n />\n ))}\n </SortableContext>\n )\n}\n\nfunction SortableRow({\n child, idx, parent, level, fields, tree, dispatch, defaultField, errorByRuleId, t,\n}: {\n child: FilterRule | FilterGroup\n idx: number\n parent: FilterGroup\n level: number\n fields: FilterFieldDef[]\n tree: AdvancedFilterTree\n dispatch: (a: TreeAction) => void\n defaultField?: string\n errorByRuleId: Map<string, string>\n t: Translator\n}) {\n const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({\n id: child.id,\n })\n // Hide the source while dragging (DragOverlay renders the snapshot); keep it in\n // layout via `visibility: hidden` so siblings don't jump. Without this, the flex\n // wrapper applies both `transform` and its own resizing to the source row.\n const style: React.CSSProperties = isDragging\n ? { visibility: 'hidden' as const }\n : { transform: CSS.Transform.toString(transform), transition }\n const dragHandleProps: DragHandleProps = { ...attributes, ...listeners }\n\n return (\n <div ref={setNodeRef} style={style} data-testid=\"filter-rule-row\" data-filter-node-id={child.id}>\n {child.type === 'rule' ? (\n <RuleRow\n rule={child}\n fields={fields}\n dispatch={dispatch}\n errorByRuleId={errorByRuleId}\n dragHandleProps={dragHandleProps}\n t={t}\n />\n ) : (\n <div className=\"flex items-start gap-2\">\n {/* Raw <button> required: dnd-kit spreads `attributes`+`listeners` onto the\n activator; <Button>'s own props/ref handling breaks the keyboard sensor. */}\n <button\n type=\"button\"\n {...dragHandleProps}\n data-testid=\"filter-drag-handle\"\n className=\"mt-1 text-muted-foreground/60 hover:text-muted-foreground cursor-grab outline-none focus-visible:shadow-focus rounded-sm\"\n aria-label={t('ui.advancedFilter.dragHandle', 'Drag to reorder')}\n >\n <GripVertical className=\"size-4\" />\n </button>\n <div className=\"flex-1\">\n <GroupView\n group={child}\n level={level + 1}\n fields={fields}\n tree={tree}\n dispatch={dispatch}\n defaultField={defaultField}\n errorByRuleId={errorByRuleId}\n t={t}\n />\n </div>\n </div>\n )}\n </div>\n )\n}\n\nfunction RuleRow({\n rule, fields, dispatch, errorByRuleId, dragHandleProps, t,\n}: {\n rule: FilterRule\n fields: FilterFieldDef[]\n dispatch: (a: TreeAction) => void\n errorByRuleId: Map<string, string>\n dragHandleProps: DragHandleProps\n t: Translator\n}) {\n const fieldType = getFieldType(fields, rule.field)\n const operators = OPERATORS_BY_FIELD_TYPE[fieldType] ?? OPERATORS_BY_FIELD_TYPE.text\n const valueless = isValuelessOperator(rule.operator)\n const errorMsg = errorByRuleId.get(rule.id)\n const fieldDef = fields.find((f) => f.key === rule.field)\n const [pickerOpen, setPickerOpen] = React.useState(false)\n const fieldBtnRef = React.useRef<HTMLButtonElement>(null)\n\n return (\n <div className=\"flex flex-col gap-1\" data-testid=\"filter-rule\" data-filter-rule-id={rule.id}>\n <div className=\"flex flex-wrap items-start gap-2\">\n {/* Raw <button> required for dnd-kit activator: see SortableRow drag-handle comment. */}\n <button\n type=\"button\"\n {...dragHandleProps}\n data-testid=\"filter-drag-handle\"\n className=\"mt-1 text-muted-foreground/60 hover:text-muted-foreground cursor-grab outline-none focus-visible:shadow-focus rounded-sm\"\n aria-label={t('ui.advancedFilter.dragHandle', 'Drag to reorder')}\n >\n <GripVertical className=\"size-4\" />\n </button>\n <Button\n ref={fieldBtnRef}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setPickerOpen(true)}\n className=\"min-w-[140px] justify-start\"\n >\n {fieldDef?.label ?? t('ui.advancedFilter.selectField', 'Select field')}\n </Button>\n <FilterFieldPicker\n fields={fields}\n open={pickerOpen}\n onOpenChange={setPickerOpen}\n onSelect={(f) => {\n const nextType = getFieldType(fields, f.key)\n dispatch({\n type: 'updateRule',\n ruleId: rule.id,\n updates: { field: f.key, operator: getDefaultOperator(nextType), value: '' },\n })\n }}\n triggerRef={fieldBtnRef as React.RefObject<HTMLElement | null>}\n />\n <Select\n value={rule.operator}\n onValueChange={(next) => {\n // Preserve the value when the operator shape doesn't change; reset\n // only when shape changes (e.g. `is` \u2192 `between` clears scalar to pair).\n const nextOp = next as FilterOperator\n const sameShape = getOperatorShape(rule.operator) === getOperatorShape(nextOp)\n const updates: Partial<Pick<FilterRule, 'operator' | 'value'>> = sameShape\n ? { operator: nextOp }\n : { operator: nextOp, value: getOperatorShape(nextOp) === 'multi' || getOperatorShape(nextOp) === 'range' ? [] : '' }\n dispatch({ type: 'updateRule', ruleId: rule.id, updates })\n }}\n >\n {/* `w-auto` overrides SelectTrigger's default `w-full` so it sits\n inline with the field/value inputs instead of breaking onto a new row. */}\n <SelectTrigger\n size=\"sm\"\n className=\"w-auto min-w-[140px]\"\n aria-label={t('ui.advancedFilter.selectOperator', 'Select operator')}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent data-advanced-filter-portal=\"\">\n {operators.map((op) => (\n <SelectItem key={op} value={op}>\n {t(`ui.advancedFilter.operator.${op}`, OPERATOR_LABELS[op])}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {!valueless ? (\n <ValueInput\n rule={rule}\n fields={fields}\n fieldType={fieldType}\n dispatch={dispatch}\n hasError={!!errorMsg}\n t={t}\n />\n ) : null}\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n type=\"button\"\n onClick={() => dispatch({ type: 'removeNode', nodeId: rule.id })}\n aria-label={t('ui.advancedFilter.removeCondition', 'Remove condition')}\n title={t('ui.advancedFilter.removeCondition', 'Remove condition')}\n >\n <Trash2 className=\"size-4 text-muted-foreground\" />\n </IconButton>\n </div>\n {errorMsg ? (\n <div className=\"ml-6 pl-12 text-xs text-status-error-text\">{errorMsg}</div>\n ) : null}\n </div>\n )\n}\n\nfunction ValueInput({\n rule, fields, fieldType, dispatch, hasError, t,\n}: {\n rule: FilterRule\n fields: FilterFieldDef[]\n fieldType: FilterFieldType\n dispatch: (a: TreeAction) => void\n hasError: boolean\n t: Translator\n}) {\n const fieldDef = fields.find((f) => f.key === rule.field)\n const value = rule.value\n const errorClass = hasError\n ? 'border-status-error-border ring-1 ring-status-error-border focus-visible:border-status-error-border focus-within:border-status-error-border'\n : ''\n\n if (fieldType === 'select' && fieldDef?.options) {\n // Multi-value operators (`is_any_of`, `is_none_of`, `has_any_of`, etc.)\n // need an array value with multiple selectable options. Radix `Select` is\n // single-select only, so the previous code silently overwrote each\n // selection (and validation kept failing because the value never became an\n // array). Render a checkbox-list popover when the operator is multi-shape.\n if (getOperatorShape(rule.operator) === 'multi') {\n return (\n <MultiValuePicker\n rule={rule}\n options={fieldDef.options}\n dispatch={dispatch}\n errorClass={errorClass}\n t={t}\n />\n )\n }\n return (\n <Select\n value={typeof value === 'string' && value.length ? value : undefined}\n onValueChange={(next) =>\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: next ?? '' } })\n }\n >\n <SelectTrigger\n size=\"sm\"\n className={`w-auto min-w-[160px] ${errorClass}`}\n aria-label={t('ui.advancedFilter.selectValue', 'Select value')}\n >\n <SelectValue placeholder={t('ui.advancedFilter.selectValuePlaceholder', 'Select...')} />\n </SelectTrigger>\n <SelectContent data-advanced-filter-portal=\"\">\n {fieldDef.options.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.tone ? (\n <span className=\"inline-flex items-center gap-2\">\n <span className={toneDotClass(opt.tone)} aria-hidden=\"true\" />\n {opt.label}\n </span>\n ) : (\n opt.label\n )}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n }\n\n // `Input` ships `w-full` by default \u2014 use `w-auto` so inputs don't stretch\n // inside the wrapping flex row. `between` needs a `[start, end]` pair, so it\n // gets its own component below.\n if (rule.operator === 'between' && (fieldType === 'number' || fieldType === 'date')) {\n return (\n <BetweenInput\n rule={rule}\n fieldType={fieldType}\n dispatch={dispatch}\n errorClass={errorClass}\n t={t}\n />\n )\n }\n\n if (fieldType === 'date') {\n return (\n <Input\n type=\"date\"\n size=\"sm\"\n className={`w-auto min-w-[160px] ${errorClass}`}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) =>\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: e.target.value } })\n }\n aria-label={t('ui.advancedFilter.dateValue', 'Date value')}\n />\n )\n }\n\n if (fieldType === 'number') {\n return (\n <Input\n type=\"number\"\n size=\"sm\"\n className={`w-[120px] ${errorClass}`}\n value={typeof value === 'number' ? value : typeof value === 'string' ? value : ''}\n onChange={(e) =>\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: e.target.value } })\n }\n placeholder={t('ui.advancedFilter.numberPlaceholder', 'Value')}\n aria-label={t('ui.advancedFilter.numberValue', 'Number value')}\n />\n )\n }\n\n return (\n <Input\n type=\"text\"\n size=\"sm\"\n className={`w-auto min-w-[160px] ${errorClass}`}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) =>\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: e.target.value } })\n }\n placeholder={t('ui.advancedFilter.textPlaceholder', 'Value...')}\n aria-label={t('ui.advancedFilter.textValue', 'Text value')}\n />\n )\n}\n\n/**\n * Two inputs (start / end) for the `between` operator on date and number\n * fields. Stores the value as `[start, end]`; either side can be empty while\n * the user types, but validation requires both to be set before apply.\n */\nfunction BetweenInput({\n rule, fieldType, dispatch, errorClass, t,\n}: {\n rule: FilterRule\n fieldType: FilterFieldType\n dispatch: (a: TreeAction) => void\n errorClass: string\n t: Translator\n}) {\n const pair = Array.isArray(rule.value) ? rule.value : []\n const start = typeof pair[0] === 'string' || typeof pair[0] === 'number' ? String(pair[0] ?? '') : ''\n const end = typeof pair[1] === 'string' || typeof pair[1] === 'number' ? String(pair[1] ?? '') : ''\n const update = (which: 'start' | 'end', next: string) => {\n const value = which === 'start' ? [next, end] : [start, next]\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value } })\n }\n const inputType = fieldType === 'date' ? 'date' : 'number'\n const inputWidth = fieldType === 'date' ? 'w-auto min-w-[140px]' : 'w-[110px]'\n return (\n <div className=\"inline-flex items-center gap-1\">\n <Input\n type={inputType}\n size=\"sm\"\n className={`${inputWidth} ${errorClass}`}\n value={start}\n onChange={(e) => update('start', e.target.value)}\n placeholder={fieldType === 'number' ? t('ui.advancedFilter.numberPlaceholder', 'Value') : undefined}\n aria-label={t('ui.advancedFilter.betweenStart', 'Start')}\n />\n <span className=\"text-xs text-muted-foreground\">{t('ui.advancedFilter.and', 'and')}</span>\n <Input\n type={inputType}\n size=\"sm\"\n className={`${inputWidth} ${errorClass}`}\n value={end}\n onChange={(e) => update('end', e.target.value)}\n placeholder={fieldType === 'number' ? t('ui.advancedFilter.numberPlaceholder', 'Value') : undefined}\n aria-label={t('ui.advancedFilter.betweenEnd', 'End')}\n />\n </div>\n )\n}\n\n/**\n * Trigger + popover with a checkbox list. Used when a select-typed rule has a\n * multi-shape operator (`is_any_of`, `has_all_of`, etc.). The trigger displays\n * a \"{first} +N\" summary; the popover lets the user toggle each option.\n */\nfunction MultiValuePicker({\n rule, options, dispatch, errorClass, t,\n}: {\n rule: FilterRule\n options: NonNullable<FilterFieldDef['options']>\n dispatch: (a: TreeAction) => void\n errorClass: string\n t: Translator\n}) {\n const selected: string[] = Array.isArray(rule.value)\n ? rule.value.map((v) => String(v)).filter((v) => v.length > 0)\n : []\n const selectedLabel = (() => {\n if (selected.length === 0) return t('ui.advancedFilter.selectValuePlaceholder', 'Select...')\n const firstOpt = options.find((o) => o.value === selected[0])\n const head = firstOpt?.label ?? selected[0]\n const more = selected.length - 1\n return more > 0 ? `${head} +${more}` : head\n })()\n const toggle = (val: string) => {\n const next = selected.includes(val) ? selected.filter((v) => v !== val) : [...selected, val]\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: next } })\n }\n return (\n <Popover>\n <PopoverTrigger asChild>\n {/* Visually mirrors `SelectTrigger size=\"sm\"` (h-8, rounded-md, border)\n so the operator + value pair stays visually consistent. */}\n <button\n type=\"button\"\n className={`inline-flex h-8 items-center justify-between gap-2 rounded-md border border-input bg-background px-2.5 text-xs shadow-xs transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:shadow-focus focus-visible:border-foreground w-auto min-w-[160px] ${errorClass}`}\n aria-label={t('ui.advancedFilter.selectValue', 'Select value')}\n data-testid=\"multi-value-trigger\"\n >\n <span className={selected.length === 0 ? 'text-muted-foreground' : ''}>{selectedLabel}</span>\n <ChevronDown className=\"size-3 text-muted-foreground\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-64 p-1\" align=\"start\" data-advanced-filter-portal=\"\">\n <div className=\"max-h-64 overflow-y-auto\" role=\"listbox\" aria-multiselectable=\"true\">\n {options.map((opt) => {\n const isOn = selected.includes(opt.value)\n return (\n // Raw <button>: needs `role=\"option\"` + `aria-selected`; <Button> would override role.\n <button\n key={opt.value}\n type=\"button\"\n role=\"option\"\n aria-selected={isOn}\n onClick={() => toggle(opt.value)}\n className=\"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm hover:bg-accent focus-visible:bg-accent focus-visible:outline-none\"\n >\n <Checkbox checked={isOn} aria-hidden=\"true\" tabIndex={-1} className=\"pointer-events-none\" />\n {opt.tone ? (\n <span className=\"inline-flex items-center gap-2\">\n <span className={toneDotClass(opt.tone)} aria-hidden=\"true\" />\n {opt.label}\n </span>\n ) : (\n <span>{opt.label}</span>\n )}\n </button>\n )\n })}\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nfunction toneDotClass(tone: NonNullable<FilterFieldDef['options']>[number]['tone']): string {\n // Solid filled dot using the saturated `*-icon` token (matches Tag's `dotColorMap`).\n const base = 'inline-block size-2 rounded-full shrink-0'\n switch (tone) {\n case 'success': return `${base} bg-status-success-icon`\n case 'error': return `${base} bg-status-error-icon`\n case 'warning': return `${base} bg-status-warning-icon`\n case 'info': return `${base} bg-status-info-icon`\n case 'neutral': return `${base} bg-status-neutral-icon`\n case 'pink': return `${base} bg-status-pink-icon`\n case 'brand': return `${base} bg-brand-violet`\n default: return `${base} bg-muted-foreground/40`\n }\n}\n\n// Single Kendo-style toolbar per group: combinator toggle, `Add condition`,\n// `Add subgroup` / `Add group`, plus a trailing delete for non-root groups.\nfunction GroupToolbar({\n group, level, tree, dispatch, defaultField, fields, isRoot, t,\n}: {\n group: FilterGroup\n level: number\n tree: AdvancedFilterTree\n dispatch: (a: TreeAction) => void\n defaultField?: string\n fields: FilterFieldDef[]\n isRoot: boolean\n t: Translator\n}) {\n const [pickerOpen, setPickerOpen] = React.useState(false)\n const addBtnRef = React.useRef<HTMLButtonElement>(null)\n const addRuleEnabled = canAddRule(tree, group.id)\n const addGroupEnabled = canAddGroup(tree, group.id)\n\n const widthCapHit = group.children.length >= TREE_LIMITS.maxChildrenPerGroup\n const depthCapHit = level >= TREE_LIMITS.maxGroupLevel\n\n const addRuleTitle = !addRuleEnabled\n ? widthCapHit\n ? t('ui.advancedFilter.limitWidthReached', 'Maximum {max} conditions per group', {\n max: TREE_LIMITS.maxChildrenPerGroup,\n })\n : t('ui.advancedFilter.limitTotalReached', 'Maximum {max} conditions reached', {\n max: TREE_LIMITS.maxTotalRules,\n })\n : undefined\n\n const addGroupTitle = !addGroupEnabled\n ? depthCapHit\n ? t('ui.advancedFilter.limitDepthReached', 'Maximum nesting depth reached')\n : widthCapHit\n ? t('ui.advancedFilter.limitWidthReached', 'Maximum {max} conditions per group', {\n max: TREE_LIMITS.maxChildrenPerGroup,\n })\n : t('ui.advancedFilter.limitTotalReached', 'Maximum {max} conditions reached', {\n max: TREE_LIMITS.maxTotalRules,\n })\n : undefined\n\n const addGroupLabel = level === 1\n ? t('ui.advancedFilter.addGroup', 'Add group')\n : t('ui.advancedFilter.addSubgroup', 'Add subgroup')\n\n // `text-brand-violet` overrides Button's default disabled token, so we have to\n // re-state muted disabled colors here \u2014 otherwise disabled CTAs still look clickable.\n const ctaClass = 'text-brand-violet hover:text-brand-violet/80 hover:bg-transparent px-0 disabled:text-muted-foreground/50 disabled:hover:text-muted-foreground/50'\n\n return (\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <AndOrToggle\n combinator={group.combinator}\n onChange={(c) => dispatch({ type: 'updateGroupCombinator', groupId: group.id, combinator: c })}\n t={t}\n />\n <Button\n ref={addBtnRef}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={!addRuleEnabled}\n title={addRuleTitle}\n onClick={() => setPickerOpen(true)}\n className={ctaClass}\n >\n <Plus className=\"size-4\" />\n {t('ui.advancedFilter.addCondition', 'Add condition')}\n </Button>\n <FilterFieldPicker\n fields={fields}\n open={pickerOpen}\n onOpenChange={setPickerOpen}\n onSelect={(f) =>\n dispatch({\n type: 'addRule',\n groupId: group.id,\n defaultField: f.key,\n defaultOperator: getDefaultOperator(f.type),\n })\n }\n triggerRef={addBtnRef as React.RefObject<HTMLElement | null>}\n />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={!addGroupEnabled}\n title={addGroupTitle}\n onClick={() =>\n dispatch({ type: 'addGroup', groupId: group.id, defaultField })\n }\n className={ctaClass}\n >\n <Plus className=\"size-4\" />\n {addGroupLabel}\n </Button>\n {!isRoot ? (\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n type=\"button\"\n onClick={() => dispatch({ type: 'removeNode', nodeId: group.id })}\n aria-label={t('ui.advancedFilter.deleteGroup', 'Delete group')}\n title={t('ui.advancedFilter.deleteGroup', 'Delete group')}\n className=\"ml-auto\"\n >\n <X className=\"size-4 text-muted-foreground\" />\n </IconButton>\n ) : null}\n </div>\n {!addGroupEnabled && depthCapHit ? (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"filter-depth-limit-hint\">\n {t('ui.advancedFilter.limitDepthHint', 'You\\'ve reached the maximum nesting depth ({max} levels). Switch this group\\'s combinator with the toggle above instead of nesting deeper.', {\n max: TREE_LIMITS.maxGroupLevel,\n })}\n </p>\n ) : null}\n </div>\n )\n}\n"],
4
+ "sourcesContent": ["'use client'\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { Plus, Trash2, X, GripVertical, ChevronDown } from 'lucide-react'\nimport {\n DndContext,\n DragOverlay,\n PointerSensor,\n KeyboardSensor,\n useSensor,\n useSensors,\n closestCenter,\n type DragEndEvent,\n type DragStartEvent,\n} from '@dnd-kit/core'\nimport {\n SortableContext,\n sortableKeyboardCoordinates,\n useSortable,\n verticalListSortingStrategy,\n} from '@dnd-kit/sortable'\nimport { CSS } from '@dnd-kit/utilities'\nimport { Button } from '../../primitives/button'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Input } from '../../primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../../primitives/select'\nimport { Popover, PopoverContent, PopoverTrigger } from '../../primitives/popover'\nimport { Checkbox } from '../../primitives/checkbox'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type {\n FilterFieldDef,\n FilterFieldType,\n FilterOperator,\n} from '@open-mercato/shared/lib/query/advanced-filter'\nimport {\n OPERATORS_BY_FIELD_TYPE,\n getDefaultOperator,\n isValuelessOperator,\n} from '@open-mercato/shared/lib/query/advanced-filter'\nimport {\n type AdvancedFilterTree,\n type FilterRule,\n type FilterGroup,\n type FilterCombinator,\n TREE_LIMITS,\n} from '@open-mercato/shared/lib/query/advanced-filter-tree'\nimport { treeReducer, canAddRule, canAddGroup, type TreeAction } from './treeReducer'\nimport { FilterFieldPicker } from './FilterFieldPicker'\n\nexport type AdvancedFilterBuilderProps = {\n fields: FilterFieldDef[]\n value: AdvancedFilterTree\n onChange: (state: AdvancedFilterTree) => void\n onApply: () => void\n onClear: () => void\n pendingErrors?: Array<{ ruleId: string; messageKey: string; message: string }>\n}\n\nconst OPERATOR_LABELS: Record<FilterOperator, string> = {\n is: 'is', is_not: 'is not', contains: 'contains', does_not_contain: 'does not contain',\n starts_with: 'starts with', ends_with: 'ends with', is_empty: 'is empty', is_not_empty: 'is not empty',\n equals: 'equals', not_equals: 'not equals', greater_than: 'greater than', less_than: 'less than',\n greater_or_equal: 'greater or equal', less_or_equal: 'less or equal', between: 'between',\n is_before: 'is before', is_after: 'is after', is_any_of: 'is any of', is_none_of: 'is none of',\n is_true: 'is true', is_false: 'is false', has_any_of: 'has any of', has_all_of: 'has all of', has_none_of: 'has none of',\n}\n\nfunction getFieldType(fields: FilterFieldDef[], fieldKey: string): FilterFieldType {\n return fields.find((f) => f.key === fieldKey)?.type ?? 'text'\n}\n\n// Operator value shapes. Used to decide whether changing operator preserves the\n// current value: same shape preserves (`is` \u2192 `is not` keeps \"Active\"); different\n// shape resets (`is` \u2192 `between` clears, because value goes from scalar to pair).\ntype OperatorShape = 'valueless' | 'multi' | 'range' | 'single'\n\nconst MULTI_VALUE_OPS = new Set<FilterOperator>([\n 'is_any_of', 'is_none_of', 'has_any_of', 'has_all_of', 'has_none_of',\n])\n\nfunction getOperatorShape(op: FilterOperator): OperatorShape {\n if (isValuelessOperator(op)) return 'valueless'\n if (MULTI_VALUE_OPS.has(op)) return 'multi'\n if (op === 'between') return 'range'\n return 'single'\n}\n\ntype DragHandleProps = React.HTMLAttributes<HTMLButtonElement> & {\n ref?: React.Ref<HTMLButtonElement>\n}\n\ntype Translator = ReturnType<typeof useT>\n\nexport function AdvancedFilterBuilder({\n fields, value, onChange, onApply, onClear, pendingErrors,\n}: AdvancedFilterBuilderProps) {\n const t = useT()\n\n const dispatch = React.useCallback((action: TreeAction) => {\n onChange(treeReducer(value, action))\n }, [onChange, value])\n\n React.useEffect(() => {\n function onKey(e: KeyboardEvent) {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n onApply()\n }\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [onApply])\n\n const errorByRuleId = React.useMemo(() => {\n const m = new Map<string, string>()\n for (const e of pendingErrors ?? []) m.set(e.ruleId, t(e.messageKey, e.message))\n return m\n }, [pendingErrors, t])\n\n const empty = value.root.children.length === 0\n const defaultField = fields.length > 0 ? fields[0].key : undefined\n\n // Per-group SortableContexts scope drag-and-drop to within-group ordering.\n // The DragOverlay below renders a stable preview that\n // matches the original row's footprint \u2014 without it, dnd-kit applies\n // `transform` to the source row in place, which the wrapping flex layout\n // distorted into a \"tall\" / morphed ghost.\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),\n )\n const [activeDrag, setActiveDrag] = React.useState<{ node: FilterRule | FilterGroup; parent: FilterGroup } | null>(null)\n const findNode = React.useCallback((id: string) => locateInTree(value.root, id), [value])\n\n const handleDragStart = React.useCallback((e: DragStartEvent) => {\n const found = findNode(String(e.active.id))\n if (found) setActiveDrag(found)\n }, [findNode])\n\n const handleDragEnd = React.useCallback((e: DragEndEvent) => {\n setActiveDrag(null)\n const { active, over } = e\n if (!over || active.id === over.id) return\n const from = findNode(String(active.id))\n const to = findNode(String(over.id))\n if (!from || !to) return\n if (from.parent.id !== to.parent.id) return\n const fromIdx = from.parent.children.findIndex((c) => c.id === active.id)\n const toIdx = to.parent.children.findIndex((c) => c.id === over.id)\n if (fromIdx < 0 || toIdx < 0) return\n dispatch({ type: 'reorderChildren', groupId: from.parent.id, fromIdx, toIdx })\n }, [dispatch, findNode])\n\n const handleDragCancel = React.useCallback(() => setActiveDrag(null), [])\n\n // Wrapper has no `min-w-[640px]`: it forced horizontal scroll on a 375px viewport.\n // Rules wrap onto multiple lines on mobile; on desktop (`sm:max-w-[720px]`) they fit\n // on one line.\n return (\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n onDragStart={handleDragStart}\n onDragEnd={handleDragEnd}\n onDragCancel={handleDragCancel}\n >\n <div className=\"flex flex-col gap-3 p-3\">\n {!empty ? (\n <GroupView\n group={value.root}\n level={1}\n fields={fields}\n tree={value}\n dispatch={dispatch}\n defaultField={defaultField}\n errorByRuleId={errorByRuleId}\n t={t}\n />\n ) : null}\n {!empty ? (\n <div className=\"flex items-center gap-3 pt-2 border-t border-border\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 py-0 text-base text-muted-foreground hover:bg-transparent hover:text-foreground\"\n onClick={onClear}\n >\n <X className=\"size-4\" />\n {t('ui.advancedFilter.clearAll', 'Clear all')}\n </Button>\n </div>\n ) : null}\n </div>\n {/* Portal DragOverlay to document.body. Radix DialogContent uses CSS `transform`\n for centering, which becomes the containing block for `position: fixed`\n descendants (dnd-kit's overlay) and shifts the ghost off-screen. Portaling\n restores viewport-relative coordinates. */}\n {typeof document !== 'undefined'\n ? createPortal(\n // dnd-kit's DragOverlay sets its own inline z-index, so a Tailwind\n // utility class would be overridden \u2014 read the DS token via CSS var.\n <DragOverlay dropAnimation={null} style={{ zIndex: 'var(--z-index-modal-elevated, 55)' }}>\n {activeDrag ? (\n <DragGhost node={activeDrag.node} parent={activeDrag.parent} fields={fields} t={t} />\n ) : null}\n </DragOverlay>,\n document.body,\n )\n : null}\n </DndContext>\n )\n}\n\n/** Find a node in the tree along with its parent group. Used by drag handlers. */\nfunction locateInTree(root: FilterGroup, id: string): { node: FilterRule | FilterGroup; parent: FilterGroup } | null {\n for (const child of root.children) {\n if (child.id === id) return { node: child, parent: root }\n if (child.type === 'group') {\n const found = locateInTree(child, id)\n if (found) return found\n }\n }\n return null\n}\n\n// Compact drag preview rendered inside DragOverlay. Cannot reuse RuleRow/GroupView \u2014\n// their popovers and form inputs conflict with the overlay.\nfunction DragGhost({\n node, parent, fields, t,\n}: {\n node: FilterRule | FilterGroup\n parent: FilterGroup\n fields: FilterFieldDef[]\n t: Translator\n}) {\n const isGroup = node.type === 'group'\n const labelParts: string[] = []\n if (isGroup) {\n const ruleCount = countRulesIn(node)\n labelParts.push(t('ui.advancedFilter.dragGhost.group', 'Group'))\n labelParts.push(`(${ruleCount})`)\n } else {\n const def = fields.find((f) => f.key === node.field)\n labelParts.push(def?.label ?? (node.field || t('ui.advancedFilter.dragGhost.unsetField', 'Unset field')))\n labelParts.push(OPERATOR_LABELS[node.operator] ?? node.operator)\n if (typeof node.value === 'string' && node.value) labelParts.push(`\"${node.value}\"`)\n }\n return (\n <div className=\"inline-flex items-center gap-2 rounded-md border border-border bg-popover px-3 py-1.5 text-sm shadow-md cursor-grabbing\">\n <GripVertical className=\"size-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">{parent.combinator === 'and' ? t('ui.advancedFilter.connector.and', 'and') : t('ui.advancedFilter.connector.or', 'or')}</span>\n <span className=\"font-medium\">{labelParts.join(' ')}</span>\n </div>\n )\n}\n\nfunction countRulesIn(group: FilterGroup): number {\n let n = 0\n for (const c of group.children) {\n if (c.type === 'rule') n += 1\n else n += countRulesIn(c)\n }\n return n\n}\n\nfunction GroupView({\n group, level, fields, tree, dispatch, defaultField, errorByRuleId, t,\n}: {\n group: FilterGroup\n level: number\n fields: FilterFieldDef[]\n tree: AdvancedFilterTree\n dispatch: (a: TreeAction) => void\n defaultField?: string\n errorByRuleId: Map<string, string>\n t: Translator\n}) {\n const isRoot = level === 1\n // Sub-groups render as a card so the nesting boundary is visible; root group stays\n // flush with the popover so it doesn't double-up the dialog's own card chrome.\n const containerClass = isRoot\n ? 'space-y-3'\n : 'space-y-3 rounded-lg border border-border bg-muted/30 p-3'\n\n // Toolbar-first layout (Kendo React Filter pattern): one [And]/[Or] toggle per group\n // is the only combinator UI. Mixing AND with OR requires `Add subgroup`. Matches\n // `CompositeFilterDescriptor` semantics (one logic per group).\n return (\n <div className={containerClass}>\n <GroupToolbar\n group={group}\n level={level}\n tree={tree}\n dispatch={dispatch}\n defaultField={defaultField}\n fields={fields}\n isRoot={isRoot}\n t={t}\n />\n\n <SortableChildrenList\n group={group}\n level={level}\n fields={fields}\n tree={tree}\n dispatch={dispatch}\n defaultField={defaultField}\n errorByRuleId={errorByRuleId}\n t={t}\n />\n </div>\n )\n}\n\n// Kendo-style segmented `[And] [Or]` toggle. Clicking the unselected side flips\n// the group's combinator. Selected side is brand-violet filled so the affordance\n// is unambiguous.\nfunction AndOrToggle({\n combinator, onChange, t,\n}: {\n combinator: FilterCombinator\n onChange: (c: FilterCombinator) => void\n t: Translator\n}) {\n const andLabel = t('ui.advancedFilter.combinator.and', 'And')\n const orLabel = t('ui.advancedFilter.combinator.or', 'Or')\n const baseBtn = 'h-8 px-3 text-sm font-medium outline-none focus-visible:shadow-focus disabled:cursor-not-allowed'\n const selBtn = 'bg-brand-violet text-brand-violet-foreground hover:bg-brand-violet/90'\n const unselBtn = 'bg-background text-foreground hover:bg-accent'\n return (\n <div\n role=\"group\"\n aria-label={t('ui.advancedFilter.combinator.label', 'Group combinator')}\n className=\"inline-flex rounded-md border border-input overflow-hidden shrink-0\"\n data-testid=\"advanced-filter-combinator-toggle\"\n data-combinator={combinator}\n >\n <button\n type=\"button\"\n aria-pressed={combinator === 'and'}\n onClick={() => combinator !== 'and' && onChange('and')}\n className={`${baseBtn} ${combinator === 'and' ? selBtn : unselBtn}`}\n >\n {andLabel}\n </button>\n <button\n type=\"button\"\n aria-pressed={combinator === 'or'}\n onClick={() => combinator !== 'or' && onChange('or')}\n className={`${baseBtn} border-l border-input ${combinator === 'or' ? selBtn : unselBtn}`}\n >\n {orLabel}\n </button>\n </div>\n )\n}\n\nfunction SortableChildrenList({\n group, level, fields, tree, dispatch, defaultField, errorByRuleId, t,\n}: {\n group: FilterGroup\n level: number\n fields: FilterFieldDef[]\n tree: AdvancedFilterTree\n dispatch: (a: TreeAction) => void\n defaultField?: string\n errorByRuleId: Map<string, string>\n t: Translator\n}) {\n // DndContext + handlers live one level up so a drag can move children across\n // groups. Each group owns its own SortableContext to scope sort ordering.\n return (\n <SortableContext items={group.children.map((c) => c.id)} strategy={verticalListSortingStrategy}>\n {group.children.map((child, idx) => (\n <SortableRow\n key={child.id}\n child={child}\n idx={idx}\n parent={group}\n level={level}\n fields={fields}\n tree={tree}\n dispatch={dispatch}\n defaultField={defaultField}\n errorByRuleId={errorByRuleId}\n t={t}\n />\n ))}\n </SortableContext>\n )\n}\n\nfunction SortableRow({\n child, idx, parent, level, fields, tree, dispatch, defaultField, errorByRuleId, t,\n}: {\n child: FilterRule | FilterGroup\n idx: number\n parent: FilterGroup\n level: number\n fields: FilterFieldDef[]\n tree: AdvancedFilterTree\n dispatch: (a: TreeAction) => void\n defaultField?: string\n errorByRuleId: Map<string, string>\n t: Translator\n}) {\n const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({\n id: child.id,\n })\n // Hide the source while dragging (DragOverlay renders the snapshot); keep it in\n // layout via `visibility: hidden` so siblings don't jump. Without this, the flex\n // wrapper applies both `transform` and its own resizing to the source row.\n const style: React.CSSProperties = isDragging\n ? { visibility: 'hidden' as const }\n : { transform: CSS.Transform.toString(transform), transition }\n const dragHandleProps: DragHandleProps = { ...attributes, ...listeners }\n\n return (\n <div ref={setNodeRef} style={style} data-testid=\"filter-rule-row\" data-filter-node-id={child.id}>\n {child.type === 'rule' ? (\n <RuleRow\n rule={child}\n fields={fields}\n dispatch={dispatch}\n errorByRuleId={errorByRuleId}\n dragHandleProps={dragHandleProps}\n t={t}\n />\n ) : (\n <div className=\"flex items-start gap-2\">\n {/* Raw <button> required: dnd-kit spreads `attributes`+`listeners` onto the\n activator; <Button>'s own props/ref handling breaks the keyboard sensor. */}\n <button\n type=\"button\"\n {...dragHandleProps}\n data-testid=\"filter-drag-handle\"\n className=\"mt-1 text-muted-foreground/60 hover:text-muted-foreground cursor-grab outline-none focus-visible:shadow-focus rounded-sm\"\n aria-label={t('ui.advancedFilter.dragHandle', 'Drag to reorder')}\n >\n <GripVertical className=\"size-4\" />\n </button>\n <div className=\"flex-1\">\n <GroupView\n group={child}\n level={level + 1}\n fields={fields}\n tree={tree}\n dispatch={dispatch}\n defaultField={defaultField}\n errorByRuleId={errorByRuleId}\n t={t}\n />\n </div>\n </div>\n )}\n </div>\n )\n}\n\nfunction RuleRow({\n rule, fields, dispatch, errorByRuleId, dragHandleProps, t,\n}: {\n rule: FilterRule\n fields: FilterFieldDef[]\n dispatch: (a: TreeAction) => void\n errorByRuleId: Map<string, string>\n dragHandleProps: DragHandleProps\n t: Translator\n}) {\n const fieldType = getFieldType(fields, rule.field)\n const operators = OPERATORS_BY_FIELD_TYPE[fieldType] ?? OPERATORS_BY_FIELD_TYPE.text\n const valueless = isValuelessOperator(rule.operator)\n const errorMsg = errorByRuleId.get(rule.id)\n const fieldDef = fields.find((f) => f.key === rule.field)\n const [pickerOpen, setPickerOpen] = React.useState(false)\n const fieldBtnRef = React.useRef<HTMLButtonElement>(null)\n\n return (\n <div className=\"flex flex-col gap-1\" data-testid=\"filter-rule\" data-filter-rule-id={rule.id}>\n <div className=\"flex flex-wrap items-start gap-2\">\n {/* Raw <button> required for dnd-kit activator: see SortableRow drag-handle comment. */}\n <button\n type=\"button\"\n {...dragHandleProps}\n data-testid=\"filter-drag-handle\"\n className=\"mt-1 text-muted-foreground/60 hover:text-muted-foreground cursor-grab outline-none focus-visible:shadow-focus rounded-sm\"\n aria-label={t('ui.advancedFilter.dragHandle', 'Drag to reorder')}\n >\n <GripVertical className=\"size-4\" />\n </button>\n <Button\n ref={fieldBtnRef}\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setPickerOpen(true)}\n className=\"min-w-[140px] justify-start\"\n >\n {fieldDef?.label ?? t('ui.advancedFilter.selectField', 'Select field')}\n </Button>\n <FilterFieldPicker\n fields={fields}\n open={pickerOpen}\n onOpenChange={setPickerOpen}\n onSelect={(f) => {\n const nextType = getFieldType(fields, f.key)\n dispatch({\n type: 'updateRule',\n ruleId: rule.id,\n updates: { field: f.key, operator: getDefaultOperator(nextType), value: '' },\n })\n }}\n triggerRef={fieldBtnRef as React.RefObject<HTMLElement | null>}\n />\n <Select\n value={rule.operator}\n onValueChange={(next) => {\n // Preserve the value when the operator shape doesn't change; reset\n // only when shape changes (e.g. `is` \u2192 `between` clears scalar to pair).\n const nextOp = next as FilterOperator\n const sameShape = getOperatorShape(rule.operator) === getOperatorShape(nextOp)\n const updates: Partial<Pick<FilterRule, 'operator' | 'value'>> = sameShape\n ? { operator: nextOp }\n : { operator: nextOp, value: getOperatorShape(nextOp) === 'multi' || getOperatorShape(nextOp) === 'range' ? [] : '' }\n dispatch({ type: 'updateRule', ruleId: rule.id, updates })\n }}\n >\n {/* `w-auto` overrides SelectTrigger's default `w-full` so it sits\n inline with the field/value inputs instead of breaking onto a new row. */}\n <SelectTrigger\n size=\"sm\"\n className=\"w-auto min-w-[140px]\"\n aria-label={t('ui.advancedFilter.selectOperator', 'Select operator')}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent data-advanced-filter-portal=\"\">\n {operators.map((op) => (\n <SelectItem key={op} value={op}>\n {t(`ui.advancedFilter.operator.${op}`, OPERATOR_LABELS[op])}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {!valueless ? (\n <ValueInput\n rule={rule}\n fields={fields}\n fieldType={fieldType}\n dispatch={dispatch}\n hasError={!!errorMsg}\n t={t}\n />\n ) : null}\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n type=\"button\"\n onClick={() => dispatch({ type: 'removeNode', nodeId: rule.id })}\n aria-label={t('ui.advancedFilter.removeCondition', 'Remove condition')}\n title={t('ui.advancedFilter.removeCondition', 'Remove condition')}\n >\n <Trash2 className=\"size-4 text-muted-foreground\" />\n </IconButton>\n </div>\n {errorMsg ? (\n <div className=\"ml-6 pl-12 text-xs text-status-error-text\">{errorMsg}</div>\n ) : null}\n </div>\n )\n}\n\nfunction ValueInput({\n rule, fields, fieldType, dispatch, hasError, t,\n}: {\n rule: FilterRule\n fields: FilterFieldDef[]\n fieldType: FilterFieldType\n dispatch: (a: TreeAction) => void\n hasError: boolean\n t: Translator\n}) {\n const fieldDef = fields.find((f) => f.key === rule.field)\n const value = rule.value\n const errorClass = hasError\n ? 'border-status-error-border ring-1 ring-status-error-border focus-visible:border-status-error-border focus-within:border-status-error-border'\n : ''\n\n if (fieldType === 'select' && fieldDef?.options) {\n // Multi-value operators (`is_any_of`, `is_none_of`, `has_any_of`, etc.)\n // need an array value with multiple selectable options. Radix `Select` is\n // single-select only, so the previous code silently overwrote each\n // selection (and validation kept failing because the value never became an\n // array). Render a checkbox-list popover when the operator is multi-shape.\n if (getOperatorShape(rule.operator) === 'multi') {\n return (\n <MultiValuePicker\n rule={rule}\n options={fieldDef.options}\n dispatch={dispatch}\n errorClass={errorClass}\n t={t}\n />\n )\n }\n return (\n <Select\n value={typeof value === 'string' && value.length ? value : undefined}\n onValueChange={(next) =>\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: next ?? '' } })\n }\n >\n <SelectTrigger\n size=\"sm\"\n className={`w-auto min-w-[160px] ${errorClass}`}\n aria-label={t('ui.advancedFilter.selectValue', 'Select value')}\n >\n <SelectValue placeholder={t('ui.advancedFilter.selectValuePlaceholder', 'Select...')} />\n </SelectTrigger>\n <SelectContent data-advanced-filter-portal=\"\">\n {fieldDef.options.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.tone ? (\n <span className=\"inline-flex items-center gap-2\">\n <span className={toneDotClass(opt.tone)} aria-hidden=\"true\" />\n {opt.label}\n </span>\n ) : (\n opt.label\n )}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n }\n\n // `Input` ships `w-full` by default \u2014 use `w-auto` so inputs don't stretch\n // inside the wrapping flex row. `between` needs a `[start, end]` pair, so it\n // gets its own component below.\n if (rule.operator === 'between' && (fieldType === 'number' || fieldType === 'date')) {\n return (\n <BetweenInput\n rule={rule}\n fieldType={fieldType}\n dispatch={dispatch}\n errorClass={errorClass}\n t={t}\n />\n )\n }\n\n if (fieldType === 'date') {\n return (\n <Input\n type=\"date\"\n size=\"sm\"\n className={`w-auto min-w-[160px] ${errorClass}`}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) =>\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: e.target.value } })\n }\n aria-label={t('ui.advancedFilter.dateValue', 'Date value')}\n />\n )\n }\n\n if (fieldType === 'number') {\n return (\n <Input\n type=\"number\"\n size=\"sm\"\n className={`w-[120px] ${errorClass}`}\n value={typeof value === 'number' ? value : typeof value === 'string' ? value : ''}\n onChange={(e) =>\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: e.target.value } })\n }\n placeholder={t('ui.advancedFilter.numberPlaceholder', 'Value')}\n aria-label={t('ui.advancedFilter.numberValue', 'Number value')}\n />\n )\n }\n\n return (\n <Input\n type=\"text\"\n size=\"sm\"\n className={`w-auto min-w-[160px] ${errorClass}`}\n value={typeof value === 'string' ? value : ''}\n onChange={(e) =>\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: e.target.value } })\n }\n placeholder={t('ui.advancedFilter.textPlaceholder', 'Value...')}\n aria-label={t('ui.advancedFilter.textValue', 'Text value')}\n />\n )\n}\n\n/**\n * Two inputs (start / end) for the `between` operator on date and number\n * fields. Stores the value as `[start, end]`; either side can be empty while\n * the user types, but validation requires both to be set before apply.\n */\nfunction BetweenInput({\n rule, fieldType, dispatch, errorClass, t,\n}: {\n rule: FilterRule\n fieldType: FilterFieldType\n dispatch: (a: TreeAction) => void\n errorClass: string\n t: Translator\n}) {\n const pair = Array.isArray(rule.value) ? rule.value : []\n const start = typeof pair[0] === 'string' || typeof pair[0] === 'number' ? String(pair[0] ?? '') : ''\n const end = typeof pair[1] === 'string' || typeof pair[1] === 'number' ? String(pair[1] ?? '') : ''\n const update = (which: 'start' | 'end', next: string) => {\n const value = which === 'start' ? [next, end] : [start, next]\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value } })\n }\n const inputType = fieldType === 'date' ? 'date' : 'number'\n const inputWidth = fieldType === 'date' ? 'w-auto min-w-[140px]' : 'w-[110px]'\n return (\n <div className=\"inline-flex items-center gap-1\">\n <Input\n type={inputType}\n size=\"sm\"\n className={`${inputWidth} ${errorClass}`}\n value={start}\n onChange={(e) => update('start', e.target.value)}\n placeholder={fieldType === 'number' ? t('ui.advancedFilter.numberPlaceholder', 'Value') : undefined}\n aria-label={t('ui.advancedFilter.betweenStart', 'Start')}\n />\n <span className=\"text-xs text-muted-foreground\">{t('ui.advancedFilter.and', 'and')}</span>\n <Input\n type={inputType}\n size=\"sm\"\n className={`${inputWidth} ${errorClass}`}\n value={end}\n onChange={(e) => update('end', e.target.value)}\n placeholder={fieldType === 'number' ? t('ui.advancedFilter.numberPlaceholder', 'Value') : undefined}\n aria-label={t('ui.advancedFilter.betweenEnd', 'End')}\n />\n </div>\n )\n}\n\n/**\n * Trigger + popover with a checkbox list. Used when a select-typed rule has a\n * multi-shape operator (`is_any_of`, `has_all_of`, etc.). The trigger displays\n * a \"{first} +N\" summary; the popover lets the user toggle each option.\n */\nfunction MultiValuePicker({\n rule, options, dispatch, errorClass, t,\n}: {\n rule: FilterRule\n options: NonNullable<FilterFieldDef['options']>\n dispatch: (a: TreeAction) => void\n errorClass: string\n t: Translator\n}) {\n const selected: string[] = Array.isArray(rule.value)\n ? rule.value.map((v) => String(v)).filter((v) => v.length > 0)\n : []\n const selectedLabel = (() => {\n if (selected.length === 0) return t('ui.advancedFilter.selectValuePlaceholder', 'Select...')\n const firstOpt = options.find((o) => o.value === selected[0])\n const head = firstOpt?.label ?? selected[0]\n const more = selected.length - 1\n return more > 0 ? `${head} +${more}` : head\n })()\n const toggle = (val: string) => {\n const next = selected.includes(val) ? selected.filter((v) => v !== val) : [...selected, val]\n dispatch({ type: 'updateRule', ruleId: rule.id, updates: { value: next } })\n }\n return (\n <Popover>\n <PopoverTrigger asChild>\n {/* Visually mirrors `SelectTrigger size=\"sm\"` (h-8, rounded-md, border)\n so the operator + value pair stays visually consistent. */}\n <button\n type=\"button\"\n className={`inline-flex h-8 items-center justify-between gap-2 rounded-md border border-input bg-background px-2.5 text-xs shadow-xs transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:shadow-focus focus-visible:border-foreground w-auto min-w-[160px] ${errorClass}`}\n aria-label={t('ui.advancedFilter.selectValue', 'Select value')}\n data-testid=\"multi-value-trigger\"\n >\n <span className={selected.length === 0 ? 'text-muted-foreground' : ''}>{selectedLabel}</span>\n <ChevronDown className=\"size-3 text-muted-foreground\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-64 p-1\" align=\"start\" data-advanced-filter-portal=\"\">\n <div className=\"max-h-64 overflow-y-auto\" role=\"listbox\" aria-multiselectable=\"true\">\n {options.map((opt) => {\n const isOn = selected.includes(opt.value)\n return (\n // Raw <button>: needs `role=\"option\"` + `aria-selected`; <Button> would override role.\n <button\n key={opt.value}\n type=\"button\"\n role=\"option\"\n aria-selected={isOn}\n onClick={() => toggle(opt.value)}\n className=\"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm hover:bg-accent focus-visible:bg-accent focus-visible:outline-none\"\n >\n <Checkbox checked={isOn} aria-hidden=\"true\" tabIndex={-1} className=\"pointer-events-none\" />\n {opt.tone ? (\n <span className=\"inline-flex items-center gap-2\">\n <span className={toneDotClass(opt.tone)} aria-hidden=\"true\" />\n {opt.label}\n </span>\n ) : (\n <span>{opt.label}</span>\n )}\n </button>\n )\n })}\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\nfunction toneDotClass(tone: NonNullable<FilterFieldDef['options']>[number]['tone']): string {\n // Solid filled dot using the saturated `*-icon` token (matches Tag's `dotColorMap`).\n const base = 'inline-block size-2 rounded-full shrink-0'\n switch (tone) {\n case 'success': return `${base} bg-status-success-icon`\n case 'error': return `${base} bg-status-error-icon`\n case 'warning': return `${base} bg-status-warning-icon`\n case 'info': return `${base} bg-status-info-icon`\n case 'neutral': return `${base} bg-status-neutral-icon`\n case 'pink': return `${base} bg-status-pink-icon`\n case 'brand': return `${base} bg-brand-violet`\n default: return `${base} bg-muted-foreground/40`\n }\n}\n\n// Single Kendo-style toolbar per group: combinator toggle, `Add condition`,\n// `Add subgroup` / `Add group`, plus a trailing delete for non-root groups.\nfunction GroupToolbar({\n group, level, tree, dispatch, defaultField, fields, isRoot, t,\n}: {\n group: FilterGroup\n level: number\n tree: AdvancedFilterTree\n dispatch: (a: TreeAction) => void\n defaultField?: string\n fields: FilterFieldDef[]\n isRoot: boolean\n t: Translator\n}) {\n const [pickerOpen, setPickerOpen] = React.useState(false)\n const addBtnRef = React.useRef<HTMLButtonElement>(null)\n const addRuleEnabled = canAddRule(tree, group.id)\n const addGroupEnabled = canAddGroup(tree, group.id)\n\n const widthCapHit = group.children.length >= TREE_LIMITS.maxChildrenPerGroup\n const depthCapHit = level >= TREE_LIMITS.maxGroupLevel\n\n const addRuleTitle = !addRuleEnabled\n ? widthCapHit\n ? t('ui.advancedFilter.limitWidthReached', 'Maximum {max} conditions per group', {\n max: TREE_LIMITS.maxChildrenPerGroup,\n })\n : t('ui.advancedFilter.limitTotalReached', 'Maximum {max} conditions reached', {\n max: TREE_LIMITS.maxTotalRules,\n })\n : undefined\n\n const addGroupTitle = !addGroupEnabled\n ? depthCapHit\n ? t('ui.advancedFilter.limitDepthReached', 'Maximum nesting depth reached')\n : widthCapHit\n ? t('ui.advancedFilter.limitWidthReached', 'Maximum {max} conditions per group', {\n max: TREE_LIMITS.maxChildrenPerGroup,\n })\n : t('ui.advancedFilter.limitTotalReached', 'Maximum {max} conditions reached', {\n max: TREE_LIMITS.maxTotalRules,\n })\n : undefined\n\n const addGroupLabel = level === 1\n ? t('ui.advancedFilter.addGroup', 'Add group')\n : t('ui.advancedFilter.addSubgroup', 'Add subgroup')\n\n // `text-brand-violet` overrides Button's default disabled token, so we have to\n // re-state muted disabled colors here \u2014 otherwise disabled CTAs still look clickable.\n const ctaClass = 'text-brand-violet hover:text-brand-violet/80 hover:bg-transparent px-0 disabled:text-muted-foreground/50 disabled:hover:text-muted-foreground/50'\n\n return (\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <AndOrToggle\n combinator={group.combinator}\n onChange={(c) => dispatch({ type: 'updateGroupCombinator', groupId: group.id, combinator: c })}\n t={t}\n />\n <Button\n ref={addBtnRef}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={!addRuleEnabled}\n title={addRuleTitle}\n onClick={() => setPickerOpen(true)}\n className={ctaClass}\n >\n <Plus className=\"size-4\" />\n {t('ui.advancedFilter.addCondition', 'Add condition')}\n </Button>\n <FilterFieldPicker\n fields={fields}\n open={pickerOpen}\n onOpenChange={setPickerOpen}\n onSelect={(f) =>\n dispatch({\n type: 'addRule',\n groupId: group.id,\n defaultField: f.key,\n defaultOperator: getDefaultOperator(f.type),\n })\n }\n triggerRef={addBtnRef as React.RefObject<HTMLElement | null>}\n />\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={!addGroupEnabled}\n title={addGroupTitle}\n onClick={() =>\n dispatch({ type: 'addGroup', groupId: group.id, defaultField })\n }\n className={ctaClass}\n >\n <Plus className=\"size-4\" />\n {addGroupLabel}\n </Button>\n {!isRoot ? (\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n type=\"button\"\n onClick={() => dispatch({ type: 'removeNode', nodeId: group.id })}\n aria-label={t('ui.advancedFilter.deleteGroup', 'Delete group')}\n title={t('ui.advancedFilter.deleteGroup', 'Delete group')}\n className=\"ml-auto\"\n >\n <X className=\"size-4 text-muted-foreground\" />\n </IconButton>\n ) : null}\n </div>\n {!addGroupEnabled && depthCapHit ? (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"filter-depth-limit-hint\">\n {t('ui.advancedFilter.limitDepthHint', 'You\\'ve reached the maximum nesting depth ({max} levels). Switch this group\\'s combinator with the toggle above instead of nesting deeper.', {\n max: TREE_LIMITS.maxGroupLevel,\n })}\n </p>\n ) : null}\n </div>\n )\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -33,8 +33,11 @@ const drawerContentVariants = cva(
33
33
  {
34
34
  variants: {
35
35
  side: {
36
- right: "inset-y-0 right-0 h-full w-full max-w-md rounded-l-xl data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right",
37
- left: "inset-y-0 left-0 h-full w-full max-w-md rounded-r-xl data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left",
36
+ right: (
37
+ // Figma Drawer [1.1] width: 400px (not the Tailwind max-w-md 448px)
38
+ "inset-y-0 right-0 h-full w-full max-w-[400px] rounded-l-xl data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right"
39
+ ),
40
+ left: "inset-y-0 left-0 h-full w-full max-w-[400px] rounded-r-xl data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left",
38
41
  top: "inset-x-0 top-0 max-h-[80vh] w-full rounded-b-xl data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top",
39
42
  bottom: "inset-x-0 bottom-0 max-h-[80vh] w-full rounded-t-xl data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom"
40
43
  }
@@ -187,7 +190,7 @@ const DrawerTitle = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
187
190
  {
188
191
  ref,
189
192
  "data-slot": "drawer-title",
190
- className: cn("text-base font-semibold leading-tight text-foreground", className),
193
+ className: cn("text-lg font-semibold leading-tight text-foreground", className),
191
194
  ...props
192
195
  }
193
196
  ));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/primitives/drawer.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { X } from 'lucide-react'\nimport { cva, type VariantProps } from 'class-variance-authority'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\n\n/**\n * Side-sheet primitive \u2014 slides in from `right` (default), `left`,\n * `top`, or `bottom`. Distinct from `Dialog`:\n *\n * `Drawer` \u2014 contextual / non-blocking-feeling. Used for detail\n * panes, secondary forms, mobile menus. The user can\n * glance at it while the surrounding page stays visible\n * at a glance behind the overlay.\n * `Dialog` \u2014 modal / focused. Used for confirmations, critical\n * forms, and full-content workflows that should fully\n * replace the page underneath.\n *\n * Built on `@radix-ui/react-dialog` (Radix Dialog under the hood \u2014\n * inherits role=\"dialog\", aria-modal, focus trap, ESC to close,\n * outside-click to dismiss).\n *\n * Figma source: DS Open Mercato `Drawer` page (`486:7366`) \u2014\n * `Drawer Header [1.1]` (`3187:2897`) covers 4 header variants\n * (title only / title + leading icon / title + description /\n * title + description + leading icon badge); `Drawer Footer [1.1]`\n * (`4096:21416`) covers 6 footer variants (50/50 stretched, right-\n * aligned compact, left checkbox + right buttons, left switch +\n * right buttons, left step dots + right buttons, left link button +\n * right buttons). Per the Figma examples the panel has rounded\n * corners on the inner (viewport-facing) edges only, no border on\n * the seam, and no chrome-level dividers between header / body /\n * footer (separators inside the body come from content, not the\n * Drawer).\n *\n * ```tsx\n * const [open, setOpen] = React.useState(false)\n * <Drawer open={open} onOpenChange={setOpen} side=\"right\">\n * <DrawerTrigger asChild>\n * <Button>Open drawer</Button>\n * </DrawerTrigger>\n * <DrawerContent>\n * <DrawerHeader leading={<Clock />}>\n * <DrawerTitle>Edit person</DrawerTitle>\n * <DrawerDescription>Update the person's contact info.</DrawerDescription>\n * </DrawerHeader>\n * <DrawerBody>{children}</DrawerBody>\n * <DrawerFooter layout=\"equal\">\n * <DrawerClose asChild>\n * <Button variant=\"outline\">Cancel</Button>\n * </DrawerClose>\n * <Button>Continue</Button>\n * </DrawerFooter>\n * </DrawerContent>\n * </Drawer>\n * ```\n */\n\nconst Drawer = DialogPrimitive.Root\nconst DrawerTrigger = DialogPrimitive.Trigger\nconst DrawerPortal = DialogPrimitive.Portal\nconst DrawerClose = DialogPrimitive.Close\n\nconst DrawerOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n data-slot=\"drawer-overlay\"\n className={cn(\n 'fixed inset-0 z-overlay bg-foreground/40 backdrop-blur-sm',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0',\n className,\n )}\n {...props}\n />\n))\nDrawerOverlay.displayName = DialogPrimitive.Overlay.displayName\n\nconst drawerContentVariants = cva(\n // Base layout \u2014 fixed position, flex column so DrawerHeader / Body\n // / Footer compose vertically with the body filling free space.\n // Per Figma the panel has rounded corners on the inner (viewport-\n // facing) edges only, no border on the seam, and a generous shadow.\n 'fixed z-popover flex flex-col gap-0 bg-background shadow-2xl outline-none ' +\n 'data-[state=open]:animate-in data-[state=closed]:animate-out ' +\n 'data-[state=open]:duration-200 data-[state=closed]:duration-150',\n {\n variants: {\n side: {\n right:\n 'inset-y-0 right-0 h-full w-full max-w-md rounded-l-xl ' +\n 'data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right',\n left:\n 'inset-y-0 left-0 h-full w-full max-w-md rounded-r-xl ' +\n 'data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left',\n top:\n 'inset-x-0 top-0 max-h-[80vh] w-full rounded-b-xl ' +\n 'data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top',\n bottom:\n 'inset-x-0 bottom-0 max-h-[80vh] w-full rounded-t-xl ' +\n 'data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom',\n },\n },\n defaultVariants: { side: 'right' },\n },\n)\n\nexport type DrawerContentProps = React.ComponentPropsWithoutRef<\n typeof DialogPrimitive.Content\n> &\n VariantProps<typeof drawerContentVariants> & {\n /** Hide the default top-right close button. */\n hideCloseButton?: boolean\n /** ARIA label for the auto-rendered close button. Default `\"Close\"`. */\n closeAriaLabel?: string\n }\n\nconst DrawerContent = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n DrawerContentProps\n>(\n (\n {\n className,\n side,\n children,\n hideCloseButton = false,\n closeAriaLabel = 'Close',\n ...props\n },\n ref,\n ) => (\n <DrawerPortal>\n <DrawerOverlay />\n <DialogPrimitive.Content\n ref={ref}\n data-slot=\"drawer-content\"\n data-side={side ?? 'right'}\n className={cn(drawerContentVariants({ side }), className)}\n {...props}\n >\n {children}\n {!hideCloseButton ? (\n <DrawerClose\n data-slot=\"drawer-close-button\"\n aria-label={closeAriaLabel}\n className={cn(\n 'absolute right-4 top-4 inline-flex size-8 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors',\n 'hover:bg-muted/40 hover:text-foreground',\n 'focus-visible:shadow-focus',\n )}\n >\n <X aria-hidden=\"true\" className=\"size-4\" />\n </DrawerClose>\n ) : null}\n </DialogPrimitive.Content>\n </DrawerPortal>\n ),\n)\nDrawerContent.displayName = 'DrawerContent'\n\nexport type DrawerHeaderProps = React.HTMLAttributes<HTMLDivElement> & {\n /**\n * Optional leading icon \u2014 rendered inside a size-10 bordered circle\n * badge to the left of the title block. Matches Figma `Drawer Header\n * [1.1]` variants 2 + 4 (icon-prefixed title). Pass a `lucide-react`\n * icon element (size-4 recommended) or any inline node.\n */\n leading?: React.ReactNode\n}\n\nconst DrawerHeader = React.forwardRef<HTMLDivElement, DrawerHeaderProps>(\n ({ className, leading, children, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"drawer-header\"\n className={cn(\n // Per Figma: no chrome border below the header. Padding-right\n // reserves room for the auto-close button at top-right.\n 'flex items-start gap-3 px-6 pt-5 pb-4 pr-14',\n className,\n )}\n {...props}\n >\n {leading ? (\n <span\n data-slot=\"drawer-header-leading\"\n aria-hidden=\"true\"\n className=\"inline-flex size-10 shrink-0 items-center justify-center rounded-full border border-input bg-background text-muted-foreground\"\n >\n {leading}\n </span>\n ) : null}\n <div data-slot=\"drawer-header-text\" className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {children}\n </div>\n </div>\n ),\n)\nDrawerHeader.displayName = 'DrawerHeader'\n\nconst DrawerBody = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"drawer-body\"\n className={cn('flex-1 overflow-y-auto px-6 py-2', className)}\n {...props}\n />\n ),\n)\nDrawerBody.displayName = 'DrawerBody'\n\nexport type DrawerFooterProps = React.HTMLAttributes<HTMLDivElement> & {\n /**\n * Footer button layout per Figma `Drawer Footer [1.1]`:\n * `default` \u2014 right-aligned compact buttons (Cancel + Continue).\n * `equal` \u2014 children stretched to share the row equally (50/50\n * for two children). Matches Figma variant 1.\n * @default 'default'\n */\n layout?: 'default' | 'equal'\n /**\n * Optional left-side slot \u2014 checkbox (\"Don't show again\"), switch\n * (\"Remember me\"), step-indicator dots, link button, or any leading\n * content per Figma footer variants 3\u20136. When provided, children\n * stay right-aligned and the leading slot anchors left.\n * Mutually exclusive with `layout=\"equal\"` (Figma never combines\n * the two \u2014 the 50/50 split is a confirmation-flow shape).\n */\n leading?: React.ReactNode\n}\n\nconst DrawerFooter = React.forwardRef<HTMLDivElement, DrawerFooterProps>(\n ({ className, layout = 'default', leading, children, ...props }, ref) => {\n const childArray = React.Children.toArray(children)\n if (layout === 'equal') {\n return (\n <div\n ref={ref}\n data-slot=\"drawer-footer\"\n data-layout=\"equal\"\n className={cn(\n // Per Figma: no chrome top-border separator; equal-stretch\n // children share the row (50/50 for two buttons).\n 'flex flex-row items-center gap-3 px-6 pt-4 pb-5 [&>*]:flex-1',\n className,\n )}\n {...props}\n >\n {childArray}\n </div>\n )\n }\n return (\n <div\n ref={ref}\n data-slot=\"drawer-footer\"\n data-layout=\"default\"\n className={cn(\n // Per Figma: no chrome top-border. Optional `leading` anchors\n // left (mr-auto sibling), buttons stay right-aligned.\n 'flex flex-row items-center gap-3 px-6 pt-4 pb-5',\n className,\n )}\n {...props}\n >\n {leading ? (\n <div\n data-slot=\"drawer-footer-leading\"\n className=\"mr-auto inline-flex items-center gap-2\"\n >\n {leading}\n </div>\n ) : null}\n {childArray.length > 0 ? (\n <div\n data-slot=\"drawer-footer-trailing\"\n className={cn(\n 'inline-flex items-center gap-3',\n leading ? '' : 'ml-auto',\n )}\n >\n {childArray}\n </div>\n ) : null}\n </div>\n )\n },\n)\nDrawerFooter.displayName = 'DrawerFooter'\n\nconst DrawerTitle = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n data-slot=\"drawer-title\"\n className={cn('text-base font-semibold leading-tight text-foreground', className)}\n {...props}\n />\n))\nDrawerTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DrawerDescription = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n data-slot=\"drawer-description\"\n className={cn('text-sm text-muted-foreground', className)}\n {...props}\n />\n))\nDrawerDescription.displayName = DialogPrimitive.Description.displayName\n\nexport {\n Drawer,\n DrawerTrigger,\n DrawerPortal,\n DrawerClose,\n DrawerOverlay,\n DrawerContent,\n DrawerHeader,\n DrawerBody,\n DrawerFooter,\n DrawerTitle,\n DrawerDescription,\n drawerContentVariants,\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { X } from 'lucide-react'\nimport { cva, type VariantProps } from 'class-variance-authority'\n\nimport { cn } from '@open-mercato/shared/lib/utils'\n\n/**\n * Side-sheet primitive \u2014 slides in from `right` (default), `left`,\n * `top`, or `bottom`. Distinct from `Dialog`:\n *\n * `Drawer` \u2014 contextual / non-blocking-feeling. Used for detail\n * panes, secondary forms, mobile menus. The user can\n * glance at it while the surrounding page stays visible\n * at a glance behind the overlay.\n * `Dialog` \u2014 modal / focused. Used for confirmations, critical\n * forms, and full-content workflows that should fully\n * replace the page underneath.\n *\n * Built on `@radix-ui/react-dialog` (Radix Dialog under the hood \u2014\n * inherits role=\"dialog\", aria-modal, focus trap, ESC to close,\n * outside-click to dismiss).\n *\n * Figma source: DS Open Mercato `Drawer` page (`486:7366`) \u2014\n * `Drawer Header [1.1]` (`3187:2897`) covers 4 header variants\n * (title only / title + leading icon / title + description /\n * title + description + leading icon badge); `Drawer Footer [1.1]`\n * (`4096:21416`) covers 6 footer variants (50/50 stretched, right-\n * aligned compact, left checkbox + right buttons, left switch +\n * right buttons, left step dots + right buttons, left link button +\n * right buttons). Per the Figma examples the panel has rounded\n * corners on the inner (viewport-facing) edges only, no border on\n * the seam, and no chrome-level dividers between header / body /\n * footer (separators inside the body come from content, not the\n * Drawer).\n *\n * ```tsx\n * const [open, setOpen] = React.useState(false)\n * <Drawer open={open} onOpenChange={setOpen} side=\"right\">\n * <DrawerTrigger asChild>\n * <Button>Open drawer</Button>\n * </DrawerTrigger>\n * <DrawerContent>\n * <DrawerHeader leading={<Clock />}>\n * <DrawerTitle>Edit person</DrawerTitle>\n * <DrawerDescription>Update the person's contact info.</DrawerDescription>\n * </DrawerHeader>\n * <DrawerBody>{children}</DrawerBody>\n * <DrawerFooter layout=\"equal\">\n * <DrawerClose asChild>\n * <Button variant=\"outline\">Cancel</Button>\n * </DrawerClose>\n * <Button>Continue</Button>\n * </DrawerFooter>\n * </DrawerContent>\n * </Drawer>\n * ```\n */\n\nconst Drawer = DialogPrimitive.Root\nconst DrawerTrigger = DialogPrimitive.Trigger\nconst DrawerPortal = DialogPrimitive.Portal\nconst DrawerClose = DialogPrimitive.Close\n\nconst DrawerOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n data-slot=\"drawer-overlay\"\n className={cn(\n 'fixed inset-0 z-overlay bg-foreground/40 backdrop-blur-sm',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=open]:fade-in-0 data-[state=closed]:fade-out-0',\n className,\n )}\n {...props}\n />\n))\nDrawerOverlay.displayName = DialogPrimitive.Overlay.displayName\n\nconst drawerContentVariants = cva(\n // Base layout \u2014 fixed position, flex column so DrawerHeader / Body\n // / Footer compose vertically with the body filling free space.\n // Per Figma the panel has rounded corners on the inner (viewport-\n // facing) edges only, no border on the seam, and a generous shadow.\n 'fixed z-popover flex flex-col gap-0 bg-background shadow-2xl outline-none ' +\n 'data-[state=open]:animate-in data-[state=closed]:animate-out ' +\n 'data-[state=open]:duration-200 data-[state=closed]:duration-150',\n {\n variants: {\n side: {\n right:\n // Figma Drawer [1.1] width: 400px (not the Tailwind max-w-md 448px)\n 'inset-y-0 right-0 h-full w-full max-w-[400px] rounded-l-xl ' +\n 'data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right',\n left:\n 'inset-y-0 left-0 h-full w-full max-w-[400px] rounded-r-xl ' +\n 'data-[state=open]:slide-in-from-left data-[state=closed]:slide-out-to-left',\n top:\n 'inset-x-0 top-0 max-h-[80vh] w-full rounded-b-xl ' +\n 'data-[state=open]:slide-in-from-top data-[state=closed]:slide-out-to-top',\n bottom:\n 'inset-x-0 bottom-0 max-h-[80vh] w-full rounded-t-xl ' +\n 'data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom',\n },\n },\n defaultVariants: { side: 'right' },\n },\n)\n\nexport type DrawerContentProps = React.ComponentPropsWithoutRef<\n typeof DialogPrimitive.Content\n> &\n VariantProps<typeof drawerContentVariants> & {\n /** Hide the default top-right close button. */\n hideCloseButton?: boolean\n /** ARIA label for the auto-rendered close button. Default `\"Close\"`. */\n closeAriaLabel?: string\n }\n\nconst DrawerContent = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n DrawerContentProps\n>(\n (\n {\n className,\n side,\n children,\n hideCloseButton = false,\n closeAriaLabel = 'Close',\n ...props\n },\n ref,\n ) => (\n <DrawerPortal>\n <DrawerOverlay />\n <DialogPrimitive.Content\n ref={ref}\n data-slot=\"drawer-content\"\n data-side={side ?? 'right'}\n className={cn(drawerContentVariants({ side }), className)}\n {...props}\n >\n {children}\n {!hideCloseButton ? (\n <DrawerClose\n data-slot=\"drawer-close-button\"\n aria-label={closeAriaLabel}\n className={cn(\n 'absolute right-4 top-4 inline-flex size-8 items-center justify-center rounded-md text-muted-foreground outline-none transition-colors',\n 'hover:bg-muted/40 hover:text-foreground',\n 'focus-visible:shadow-focus',\n )}\n >\n <X aria-hidden=\"true\" className=\"size-4\" />\n </DrawerClose>\n ) : null}\n </DialogPrimitive.Content>\n </DrawerPortal>\n ),\n)\nDrawerContent.displayName = 'DrawerContent'\n\nexport type DrawerHeaderProps = React.HTMLAttributes<HTMLDivElement> & {\n /**\n * Optional leading icon \u2014 rendered inside a size-10 bordered circle\n * badge to the left of the title block. Matches Figma `Drawer Header\n * [1.1]` variants 2 + 4 (icon-prefixed title). Pass a `lucide-react`\n * icon element (size-4 recommended) or any inline node.\n */\n leading?: React.ReactNode\n}\n\nconst DrawerHeader = React.forwardRef<HTMLDivElement, DrawerHeaderProps>(\n ({ className, leading, children, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"drawer-header\"\n className={cn(\n // Per Figma: no chrome border below the header. Padding-right\n // reserves room for the auto-close button at top-right.\n 'flex items-start gap-3 px-6 pt-5 pb-4 pr-14',\n className,\n )}\n {...props}\n >\n {leading ? (\n <span\n data-slot=\"drawer-header-leading\"\n aria-hidden=\"true\"\n className=\"inline-flex size-10 shrink-0 items-center justify-center rounded-full border border-input bg-background text-muted-foreground\"\n >\n {leading}\n </span>\n ) : null}\n <div data-slot=\"drawer-header-text\" className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {children}\n </div>\n </div>\n ),\n)\nDrawerHeader.displayName = 'DrawerHeader'\n\nconst DrawerBody = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"drawer-body\"\n className={cn('flex-1 overflow-y-auto px-6 py-2', className)}\n {...props}\n />\n ),\n)\nDrawerBody.displayName = 'DrawerBody'\n\nexport type DrawerFooterProps = React.HTMLAttributes<HTMLDivElement> & {\n /**\n * Footer button layout per Figma `Drawer Footer [1.1]`:\n * `default` \u2014 right-aligned compact buttons (Cancel + Continue).\n * `equal` \u2014 children stretched to share the row equally (50/50\n * for two children). Matches Figma variant 1.\n * @default 'default'\n */\n layout?: 'default' | 'equal'\n /**\n * Optional left-side slot \u2014 checkbox (\"Don't show again\"), switch\n * (\"Remember me\"), step-indicator dots, link button, or any leading\n * content per Figma footer variants 3\u20136. When provided, children\n * stay right-aligned and the leading slot anchors left.\n * Mutually exclusive with `layout=\"equal\"` (Figma never combines\n * the two \u2014 the 50/50 split is a confirmation-flow shape).\n */\n leading?: React.ReactNode\n}\n\nconst DrawerFooter = React.forwardRef<HTMLDivElement, DrawerFooterProps>(\n ({ className, layout = 'default', leading, children, ...props }, ref) => {\n const childArray = React.Children.toArray(children)\n if (layout === 'equal') {\n return (\n <div\n ref={ref}\n data-slot=\"drawer-footer\"\n data-layout=\"equal\"\n className={cn(\n // Per Figma: no chrome top-border separator; equal-stretch\n // children share the row (50/50 for two buttons).\n 'flex flex-row items-center gap-3 px-6 pt-4 pb-5 [&>*]:flex-1',\n className,\n )}\n {...props}\n >\n {childArray}\n </div>\n )\n }\n return (\n <div\n ref={ref}\n data-slot=\"drawer-footer\"\n data-layout=\"default\"\n className={cn(\n // Per Figma: no chrome top-border. Optional `leading` anchors\n // left (mr-auto sibling), buttons stay right-aligned.\n 'flex flex-row items-center gap-3 px-6 pt-4 pb-5',\n className,\n )}\n {...props}\n >\n {leading ? (\n <div\n data-slot=\"drawer-footer-leading\"\n className=\"mr-auto inline-flex items-center gap-2\"\n >\n {leading}\n </div>\n ) : null}\n {childArray.length > 0 ? (\n <div\n data-slot=\"drawer-footer-trailing\"\n className={cn(\n 'inline-flex items-center gap-3',\n leading ? '' : 'ml-auto',\n )}\n >\n {childArray}\n </div>\n ) : null}\n </div>\n )\n },\n)\nDrawerFooter.displayName = 'DrawerFooter'\n\nconst DrawerTitle = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n data-slot=\"drawer-title\"\n className={cn('text-lg font-semibold leading-tight text-foreground', className)}\n {...props}\n />\n))\nDrawerTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DrawerDescription = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n data-slot=\"drawer-description\"\n className={cn('text-sm text-muted-foreground', className)}\n {...props}\n />\n))\nDrawerDescription.displayName = DialogPrimitive.Description.displayName\n\nexport {\n Drawer,\n DrawerTrigger,\n DrawerPortal,\n DrawerClose,\n DrawerOverlay,\n DrawerContent,\n DrawerHeader,\n DrawerBody,\n DrawerFooter,\n DrawerTitle,\n DrawerDescription,\n drawerContentVariants,\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -48,7 +48,7 @@ function Tabs({
48
48
  }
49
49
  ) });
50
50
  }
51
- function TabsList({ children, className }) {
51
+ function TabsList({ children, className, "aria-label": ariaLabel }) {
52
52
  const { variant, orientation } = useTabsContext();
53
53
  const baseClasses = variant === "underline" ? (
54
54
  // Underline strip — flat, full-width border-bottom for the rail,
@@ -64,6 +64,7 @@ function TabsList({ children, className }) {
64
64
  "data-slot": "tabs-list",
65
65
  className: cn(baseClasses, className),
66
66
  role: "tablist",
67
+ "aria-label": ariaLabel,
67
68
  "aria-orientation": orientation,
68
69
  children
69
70
  }