@proteos/ui 0.31.0
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.
- package/LICENSE +105 -0
- package/dist/chunk-IKAX76MB.js +113 -0
- package/dist/chunk-IKAX76MB.js.map +1 -0
- package/dist/chunk-MWAXK66H.cjs +119 -0
- package/dist/chunk-MWAXK66H.cjs.map +1 -0
- package/dist/index.cjs +6246 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2126 -0
- package/dist/index.d.ts +2126 -0
- package/dist/index.js +6069 -0
- package/dist/index.js.map +1 -0
- package/dist/pdf-view-3ZMMS6ML.cjs +95 -0
- package/dist/pdf-view-3ZMMS6ML.cjs.map +1 -0
- package/dist/pdf-view-BV5ZD6OC.js +93 -0
- package/dist/pdf-view-BV5ZD6OC.js.map +1 -0
- package/package.json +96 -0
- package/src/components/attachment-chip/attachment-chip.tsx +129 -0
- package/src/components/attachment-chip/index.ts +2 -0
- package/src/components/attribute-type-icon/attribute-type-icon.stories.tsx +70 -0
- package/src/components/attribute-type-icon/attribute-type-icon.tsx +125 -0
- package/src/components/attribute-type-icon/index.ts +8 -0
- package/src/components/avatar/avatar.stories.tsx +42 -0
- package/src/components/avatar/avatar.tsx +48 -0
- package/src/components/avatar/index.ts +2 -0
- package/src/components/avatar-group/avatar-group.test.tsx +38 -0
- package/src/components/avatar-group/avatar-group.tsx +60 -0
- package/src/components/avatar-group/index.ts +2 -0
- package/src/components/badge/badge.stories.tsx +62 -0
- package/src/components/badge/badge.test.tsx +36 -0
- package/src/components/badge/badge.tsx +54 -0
- package/src/components/badge/index.ts +2 -0
- package/src/components/badge-multi-select/badge-multi-select.stories.tsx +177 -0
- package/src/components/badge-multi-select/badge-multi-select.test.tsx +155 -0
- package/src/components/badge-multi-select/badge-multi-select.tsx +272 -0
- package/src/components/badge-multi-select/index.ts +2 -0
- package/src/components/breadcrumbs/breadcrumbs.stories.tsx +43 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +86 -0
- package/src/components/breadcrumbs/index.ts +2 -0
- package/src/components/button/button.stories.tsx +290 -0
- package/src/components/button/button.test.tsx +59 -0
- package/src/components/button/button.tsx +86 -0
- package/src/components/button/index.ts +2 -0
- package/src/components/card/card.stories.tsx +351 -0
- package/src/components/card/card.test.tsx +141 -0
- package/src/components/card/card.tsx +173 -0
- package/src/components/card/index.ts +13 -0
- package/src/components/checkbox/checkbox-group.tsx +52 -0
- package/src/components/checkbox/checkbox.stories.tsx +48 -0
- package/src/components/checkbox/checkbox.test.tsx +50 -0
- package/src/components/checkbox/checkbox.tsx +71 -0
- package/src/components/checkbox/index.ts +4 -0
- package/src/components/combobox/combobox.stories.tsx +97 -0
- package/src/components/combobox/combobox.test.tsx +123 -0
- package/src/components/combobox/combobox.tsx +239 -0
- package/src/components/combobox/index.ts +2 -0
- package/src/components/command/command-hint.tsx +46 -0
- package/src/components/command/command.stories.tsx +88 -0
- package/src/components/command/command.test.tsx +102 -0
- package/src/components/command/command.tsx +76 -0
- package/src/components/command/index.ts +16 -0
- package/src/components/currency-input/currency-input.tsx +192 -0
- package/src/components/currency-input/index.ts +6 -0
- package/src/components/data-table/cells.stories.tsx +120 -0
- package/src/components/data-table/cells.tsx +165 -0
- package/src/components/data-table/data-table.stories.tsx +371 -0
- package/src/components/data-table/data-table.tsx +556 -0
- package/src/components/data-table/index.ts +22 -0
- package/src/components/data-table/make-actions-column.ts +30 -0
- package/src/components/data-table/states/data-table-empty.tsx +40 -0
- package/src/components/data-table/states/data-table-error.tsx +30 -0
- package/src/components/data-table/states/data-table-skeleton.tsx +42 -0
- package/src/components/data-table/use-infinite-scroll-sentinel.ts +51 -0
- package/src/components/date-picker/calendar-internals.tsx +138 -0
- package/src/components/date-picker/date-picker.stories.tsx +78 -0
- package/src/components/date-picker/date-picker.test.tsx +71 -0
- package/src/components/date-picker/date-picker.tsx +140 -0
- package/src/components/date-picker/index.ts +2 -0
- package/src/components/date-time-picker/date-time-picker.stories.tsx +96 -0
- package/src/components/date-time-picker/date-time-picker.test.tsx +87 -0
- package/src/components/date-time-picker/date-time-picker.tsx +203 -0
- package/src/components/date-time-picker/index.ts +2 -0
- package/src/components/empty-state/empty-state.tsx +63 -0
- package/src/components/empty-state/index.ts +2 -0
- package/src/components/error-animation/error-animation.stories.tsx +50 -0
- package/src/components/error-animation/error-animation.test.tsx +22 -0
- package/src/components/error-animation/error-animation.tsx +26 -0
- package/src/components/error-animation/index.ts +2 -0
- package/src/components/field/field.tsx +142 -0
- package/src/components/field/index.ts +8 -0
- package/src/components/file-field/file-field.tsx +285 -0
- package/src/components/file-field/index.ts +2 -0
- package/src/components/file-viewer/fallback-card.tsx +68 -0
- package/src/components/file-viewer/file-viewer.stories.tsx +67 -0
- package/src/components/file-viewer/file-viewer.test.tsx +81 -0
- package/src/components/file-viewer/file-viewer.tsx +116 -0
- package/src/components/file-viewer/image-view.tsx +38 -0
- package/src/components/file-viewer/index.ts +7 -0
- package/src/components/file-viewer/pdf-view.tsx +122 -0
- package/src/components/file-viewer/video-view.tsx +16 -0
- package/src/components/image-attachment/image-attachment.stories.tsx +39 -0
- package/src/components/image-attachment/image-attachment.test.tsx +54 -0
- package/src/components/image-attachment/image-attachment.tsx +77 -0
- package/src/components/image-attachment/index.ts +3 -0
- package/src/components/image-attachment/is-previewable-image-name.ts +14 -0
- package/src/components/image-lightbox/image-lightbox.stories.tsx +38 -0
- package/src/components/image-lightbox/image-lightbox.test.tsx +72 -0
- package/src/components/image-lightbox/image-lightbox.tsx +92 -0
- package/src/components/image-lightbox/index.ts +2 -0
- package/src/components/input/index.ts +2 -0
- package/src/components/input/input.stories.tsx +71 -0
- package/src/components/input/input.test.tsx +36 -0
- package/src/components/input/input.tsx +76 -0
- package/src/components/kbd/index.ts +2 -0
- package/src/components/kbd/kbd.stories.tsx +34 -0
- package/src/components/kbd/kbd.tsx +27 -0
- package/src/components/menu/index.ts +2 -0
- package/src/components/menu/menu.tsx +121 -0
- package/src/components/modal/index.ts +10 -0
- package/src/components/modal/modal.stories.tsx +153 -0
- package/src/components/modal/modal.test.tsx +93 -0
- package/src/components/modal/modal.tsx +149 -0
- package/src/components/multi-select/index.ts +2 -0
- package/src/components/multi-select/multi-select.stories.tsx +211 -0
- package/src/components/multi-select/multi-select.test.tsx +151 -0
- package/src/components/multi-select/multi-select.tsx +339 -0
- package/src/components/nav/index.ts +7 -0
- package/src/components/nav/nav.stories.tsx +139 -0
- package/src/components/nav/nav.tsx +246 -0
- package/src/components/popover/index.ts +8 -0
- package/src/components/popover/popover.stories.tsx +92 -0
- package/src/components/popover/popover.tsx +135 -0
- package/src/components/progress-bar/index.ts +2 -0
- package/src/components/progress-bar/progress-bar.stories.tsx +75 -0
- package/src/components/progress-bar/progress-bar.test.tsx +29 -0
- package/src/components/progress-bar/progress-bar.tsx +57 -0
- package/src/components/radio-group/index.ts +2 -0
- package/src/components/radio-group/radio-group.stories.tsx +113 -0
- package/src/components/radio-group/radio-group.test.tsx +87 -0
- package/src/components/radio-group/radio-group.tsx +104 -0
- package/src/components/record-filter/attribute-picker.tsx +74 -0
- package/src/components/record-filter/chip-value.tsx +171 -0
- package/src/components/record-filter/filter-row.tsx +262 -0
- package/src/components/record-filter/filter-value-input.tsx +351 -0
- package/src/components/record-filter/group-add-controls.tsx +77 -0
- package/src/components/record-filter/group-body.tsx +173 -0
- package/src/components/record-filter/group-mutations.test.ts +104 -0
- package/src/components/record-filter/group-mutations.ts +116 -0
- package/src/components/record-filter/index.ts +58 -0
- package/src/components/record-filter/nested-group.tsx +138 -0
- package/src/components/record-filter/operator-picker.tsx +62 -0
- package/src/components/record-filter/operators.test.ts +105 -0
- package/src/components/record-filter/operators.ts +97 -0
- package/src/components/record-filter/record-filter-builder-panel.tsx +236 -0
- package/src/components/record-filter/record-filter-button.tsx +126 -0
- package/src/components/record-filter/record-filter-chips.tsx +527 -0
- package/src/components/record-filter/record-filter-editor.tsx +91 -0
- package/src/components/record-filter/record-filter.stories.tsx +196 -0
- package/src/components/record-filter/record-filter.test.tsx +437 -0
- package/src/components/record-filter/show-details-store.ts +72 -0
- package/src/components/record-filter/types.ts +150 -0
- package/src/components/segmented-control/index.ts +6 -0
- package/src/components/segmented-control/segmented-control.stories.tsx +67 -0
- package/src/components/segmented-control/segmented-control.test.tsx +47 -0
- package/src/components/segmented-control/segmented-control.tsx +105 -0
- package/src/components/select/index.ts +2 -0
- package/src/components/select/select.stories.tsx +76 -0
- package/src/components/select/select.test.tsx +56 -0
- package/src/components/select/select.tsx +159 -0
- package/src/components/separator/index.ts +2 -0
- package/src/components/separator/separator.stories.tsx +73 -0
- package/src/components/separator/separator.test.tsx +29 -0
- package/src/components/separator/separator.tsx +51 -0
- package/src/components/skeleton/index.ts +2 -0
- package/src/components/skeleton/skeleton.stories.tsx +68 -0
- package/src/components/skeleton/skeleton.test.tsx +38 -0
- package/src/components/skeleton/skeleton.tsx +43 -0
- package/src/components/success-animation/index.ts +2 -0
- package/src/components/success-animation/success-animation.stories.tsx +50 -0
- package/src/components/success-animation/success-animation.test.tsx +22 -0
- package/src/components/success-animation/success-animation.tsx +26 -0
- package/src/components/switch/index.ts +2 -0
- package/src/components/switch/switch.stories.tsx +60 -0
- package/src/components/switch/switch.test.tsx +50 -0
- package/src/components/switch/switch.tsx +49 -0
- package/src/components/table/index.ts +25 -0
- package/src/components/table/table.stories.tsx +306 -0
- package/src/components/table/table.test.tsx +115 -0
- package/src/components/table/table.tsx +543 -0
- package/src/components/tabs/index.ts +8 -0
- package/src/components/tabs/tabs.tsx +141 -0
- package/src/components/tag-input/index.ts +2 -0
- package/src/components/tag-input/tag-input.stories.tsx +73 -0
- package/src/components/tag-input/tag-input.test.tsx +80 -0
- package/src/components/tag-input/tag-input.tsx +193 -0
- package/src/components/textarea/index.ts +2 -0
- package/src/components/textarea/textarea.stories.tsx +75 -0
- package/src/components/textarea/textarea.test.tsx +41 -0
- package/src/components/textarea/textarea.tsx +86 -0
- package/src/components/time-field/index.ts +2 -0
- package/src/components/time-field/time-field.stories.tsx +76 -0
- package/src/components/time-field/time-field.test.tsx +51 -0
- package/src/components/time-field/time-field.tsx +98 -0
- package/src/components/tooltip/index.ts +2 -0
- package/src/components/tooltip/tooltip.stories.tsx +176 -0
- package/src/components/tooltip/tooltip.test.tsx +52 -0
- package/src/components/tooltip/tooltip.tsx +56 -0
- package/src/index.ts +47 -0
- package/src/lib/cn.ts +6 -0
- package/src/lib/commands/discovery-store.test.tsx +76 -0
- package/src/lib/commands/discovery-store.ts +110 -0
- package/src/lib/commands/index.ts +9 -0
- package/src/lib/commands/match-event.test.ts +99 -0
- package/src/lib/commands/match-event.ts +31 -0
- package/src/lib/commands/parse-combo.test.ts +65 -0
- package/src/lib/commands/parse-combo.ts +48 -0
- package/src/lib/commands/platform.ts +27 -0
- package/src/lib/commands/protected-combos.test.ts +18 -0
- package/src/lib/commands/protected-combos.ts +38 -0
- package/src/lib/commands/registry.test.ts +97 -0
- package/src/lib/commands/registry.ts +59 -0
- package/src/lib/commands/use-command.test.tsx +91 -0
- package/src/lib/commands/use-command.ts +32 -0
- package/src/lib/commands/use-escape-key.test.tsx +97 -0
- package/src/lib/commands/use-escape-key.ts +62 -0
- package/src/styles.css +321 -0
- package/src/test/setup.ts +9 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { cn } from '../../lib/cn'
|
|
2
|
+
import { SegmentedControl } from '../segmented-control/segmented-control'
|
|
3
|
+
import { type FilterValueInputRenderer, RenderValueInputProvider } from './filter-value-input'
|
|
4
|
+
import { RecordFilterEditor } from './record-filter-editor'
|
|
5
|
+
import type { FilterField, FilterGroup, LogicalOperator } from './types'
|
|
6
|
+
|
|
7
|
+
/* =========================================================================
|
|
8
|
+
RecordFilterBuilderPanel — the popover panel content for the Filter
|
|
9
|
+
button. Composition:
|
|
10
|
+
|
|
11
|
+
[Header joiner-segmented (≥2 rows) ··· Expand-inline · ×Close]
|
|
12
|
+
[Body one FilterRow per element, plus optional pending row]
|
|
13
|
+
[Add "+ Add filter" appends a new row in-place]
|
|
14
|
+
[Footer Clear all · Save view · Done]
|
|
15
|
+
|
|
16
|
+
"+ Add filter" appends a row with `field: ''`. That row's first cell
|
|
17
|
+
is an auto-focused inline AttributePicker (Combobox) with the dropdown
|
|
18
|
+
open — exactly one click to start picking. The other cells (operator,
|
|
19
|
+
value, remove) only appear once a field is chosen.
|
|
20
|
+
========================================================================= */
|
|
21
|
+
|
|
22
|
+
export type RecordFilterBuilderPanelProps = {
|
|
23
|
+
/** Display label shown in the header when fewer than 2 rows exist. */
|
|
24
|
+
entityLabel?: string
|
|
25
|
+
fields: ReadonlyArray<FilterField>
|
|
26
|
+
filter: FilterGroup
|
|
27
|
+
onChange: (next: FilterGroup) => void
|
|
28
|
+
onClose: () => void
|
|
29
|
+
/** Optional "Save as view" affordance. Hidden when not provided. */
|
|
30
|
+
onSaveView?: () => void
|
|
31
|
+
/** Optional "Expand inline" affordance — surface chips outside the popover. */
|
|
32
|
+
onExpand?: () => void
|
|
33
|
+
showFieldIcons?: boolean
|
|
34
|
+
showJoinerToggle?: boolean
|
|
35
|
+
/** Opt-in to nested-group affordances: "+ Add group" button, "Wrap in
|
|
36
|
+
* group" hover icon on leaves, and rendering of any sub-groups in the
|
|
37
|
+
* filter tree. Defaults to `false` for back-compat. */
|
|
38
|
+
isComplexEnabled?: boolean
|
|
39
|
+
/** Optional override for the value editor. Forwarded to RecordFilterEditor. */
|
|
40
|
+
renderValueInput?: FilterValueInputRenderer
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function RecordFilterBuilderPanel({
|
|
44
|
+
entityLabel,
|
|
45
|
+
fields,
|
|
46
|
+
filter,
|
|
47
|
+
onChange,
|
|
48
|
+
onClose,
|
|
49
|
+
onSaveView,
|
|
50
|
+
onExpand,
|
|
51
|
+
showFieldIcons = true,
|
|
52
|
+
showJoinerToggle = true,
|
|
53
|
+
isComplexEnabled = false,
|
|
54
|
+
renderValueInput,
|
|
55
|
+
}: RecordFilterBuilderPanelProps) {
|
|
56
|
+
const elements = filter.elements ?? []
|
|
57
|
+
const subgroups = filter.groups ?? []
|
|
58
|
+
|
|
59
|
+
const setJoiner = (next: LogicalOperator) => onChange({ ...filter, logicalOperator: next })
|
|
60
|
+
|
|
61
|
+
const clearAll = () => {
|
|
62
|
+
onChange({ ...filter, elements: [], groups: [] })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const totalChildren = elements.length + (isComplexEnabled ? subgroups.length : 0)
|
|
66
|
+
const showJoinerSegment = showJoinerToggle && totalChildren >= 2
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<RenderValueInputProvider renderer={renderValueInput}>
|
|
70
|
+
<div
|
|
71
|
+
className="w-[min(640px,92vw)] flex flex-col"
|
|
72
|
+
onKeyDown={(e) => {
|
|
73
|
+
// Cmd/Ctrl+Enter anywhere in the panel = "save & close" (Done
|
|
74
|
+
// equivalent). Plain Enter on a value Input is handled per-row in
|
|
75
|
+
// FilterRow's value-cell wrapper. Escape is handled by RAC's Dialog.
|
|
76
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
|
77
|
+
e.preventDefault()
|
|
78
|
+
onClose()
|
|
79
|
+
}
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
{/* Header */}
|
|
83
|
+
<div className="flex items-center justify-between px-3.5 py-2.5 border-b border-rule">
|
|
84
|
+
<div className="flex items-center gap-2.5">
|
|
85
|
+
{showJoinerSegment ? (
|
|
86
|
+
<SegmentedControl<LogicalOperator>
|
|
87
|
+
aria-label="Joiner"
|
|
88
|
+
options={[
|
|
89
|
+
{ id: 'and', label: 'AND' },
|
|
90
|
+
{ id: 'or', label: 'OR' },
|
|
91
|
+
]}
|
|
92
|
+
value={filter.logicalOperator}
|
|
93
|
+
onChange={setJoiner}
|
|
94
|
+
/>
|
|
95
|
+
) : (
|
|
96
|
+
<>
|
|
97
|
+
<span className="font-mono text-[10.5px] uppercase tracking-[0.08em] text-ink-4">
|
|
98
|
+
Filters
|
|
99
|
+
</span>
|
|
100
|
+
{entityLabel ? (
|
|
101
|
+
<>
|
|
102
|
+
<span className="font-mono text-[11px] text-ink-3">·</span>
|
|
103
|
+
<span className="font-mono text-[11px] text-ink-3">{entityLabel}</span>
|
|
104
|
+
</>
|
|
105
|
+
) : null}
|
|
106
|
+
</>
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
<div className="flex items-center gap-1.5">
|
|
110
|
+
{onExpand ? (
|
|
111
|
+
<button
|
|
112
|
+
type="button"
|
|
113
|
+
onClick={onExpand}
|
|
114
|
+
title="Show filter chips inline above the table"
|
|
115
|
+
className={cn(
|
|
116
|
+
'inline-flex items-center gap-1 px-2 py-1 rounded-sm',
|
|
117
|
+
'font-mono text-[11.5px] text-ink-3 hover:text-ink hover:bg-surface-2',
|
|
118
|
+
'cursor-pointer outline-none',
|
|
119
|
+
)}
|
|
120
|
+
>
|
|
121
|
+
<svg
|
|
122
|
+
aria-hidden
|
|
123
|
+
viewBox="0 0 12 12"
|
|
124
|
+
className="size-3"
|
|
125
|
+
fill="none"
|
|
126
|
+
stroke="currentColor"
|
|
127
|
+
strokeWidth="1.5"
|
|
128
|
+
strokeLinecap="round"
|
|
129
|
+
strokeLinejoin="round"
|
|
130
|
+
>
|
|
131
|
+
<path d="m3 3 3 3-3 3M7 3l3 3-3 3" />
|
|
132
|
+
</svg>
|
|
133
|
+
Expand inline
|
|
134
|
+
</button>
|
|
135
|
+
) : null}
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
onClick={onClose}
|
|
139
|
+
aria-label="Close"
|
|
140
|
+
className={cn(
|
|
141
|
+
'size-6 inline-flex items-center justify-center rounded-sm',
|
|
142
|
+
'text-ink-3 hover:text-ink hover:bg-surface-2 cursor-pointer outline-none',
|
|
143
|
+
)}
|
|
144
|
+
>
|
|
145
|
+
<svg
|
|
146
|
+
aria-hidden
|
|
147
|
+
viewBox="0 0 12 12"
|
|
148
|
+
className="size-3"
|
|
149
|
+
fill="none"
|
|
150
|
+
stroke="currentColor"
|
|
151
|
+
strokeWidth="1.4"
|
|
152
|
+
strokeLinecap="round"
|
|
153
|
+
>
|
|
154
|
+
<path d="M3 3l6 6M9 3l-6 6" />
|
|
155
|
+
</svg>
|
|
156
|
+
</button>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
{/* Body */}
|
|
161
|
+
<div className="px-2 py-2 max-h-[420px] overflow-auto">
|
|
162
|
+
<RecordFilterEditor
|
|
163
|
+
fields={fields}
|
|
164
|
+
filter={filter}
|
|
165
|
+
onChange={onChange}
|
|
166
|
+
isComplexEnabled={isComplexEnabled}
|
|
167
|
+
showFieldIcons={showFieldIcons}
|
|
168
|
+
showJoinerToggle={showJoinerToggle}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
{/* Footer */}
|
|
173
|
+
<div
|
|
174
|
+
className={cn(
|
|
175
|
+
'flex items-center justify-between px-3.5 py-2.5 border-t border-rule rounded-b-md',
|
|
176
|
+
'bg-[color-mix(in_oklab,var(--color-surface-2)_60%,var(--color-surface))]',
|
|
177
|
+
)}
|
|
178
|
+
>
|
|
179
|
+
<button
|
|
180
|
+
type="button"
|
|
181
|
+
onClick={clearAll}
|
|
182
|
+
disabled={totalChildren === 0}
|
|
183
|
+
className={cn(
|
|
184
|
+
'font-mono text-[11.5px] text-ink-3 hover:text-ink',
|
|
185
|
+
'disabled:opacity-40 disabled:pointer-events-none',
|
|
186
|
+
'outline-none cursor-pointer',
|
|
187
|
+
)}
|
|
188
|
+
>
|
|
189
|
+
Clear all
|
|
190
|
+
</button>
|
|
191
|
+
<div className="flex items-center gap-1.5">
|
|
192
|
+
{onSaveView ? (
|
|
193
|
+
<button
|
|
194
|
+
type="button"
|
|
195
|
+
onClick={onSaveView}
|
|
196
|
+
disabled={totalChildren === 0}
|
|
197
|
+
className={cn(
|
|
198
|
+
'inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-sm',
|
|
199
|
+
'font-sans text-[13px] text-ink-2 hover:bg-surface-2 hover:text-ink',
|
|
200
|
+
'disabled:opacity-40 disabled:pointer-events-none',
|
|
201
|
+
'outline-none cursor-pointer',
|
|
202
|
+
)}
|
|
203
|
+
>
|
|
204
|
+
<svg
|
|
205
|
+
aria-hidden
|
|
206
|
+
viewBox="0 0 14 14"
|
|
207
|
+
className="size-3"
|
|
208
|
+
fill="none"
|
|
209
|
+
stroke="currentColor"
|
|
210
|
+
strokeWidth="1.4"
|
|
211
|
+
strokeLinecap="round"
|
|
212
|
+
strokeLinejoin="round"
|
|
213
|
+
>
|
|
214
|
+
<path d="M3 2h7l2 2v8a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1z" />
|
|
215
|
+
<path d="M5 2v3h4V2" />
|
|
216
|
+
</svg>
|
|
217
|
+
Save as view
|
|
218
|
+
</button>
|
|
219
|
+
) : null}
|
|
220
|
+
<button
|
|
221
|
+
type="button"
|
|
222
|
+
onClick={onClose}
|
|
223
|
+
className={cn(
|
|
224
|
+
'inline-flex items-center gap-1.5 px-3 py-1.5 rounded-sm',
|
|
225
|
+
'bg-ink text-surface font-mono text-[12px]',
|
|
226
|
+
'hover:bg-accent-ink outline-none cursor-pointer',
|
|
227
|
+
)}
|
|
228
|
+
>
|
|
229
|
+
Done
|
|
230
|
+
</button>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
</RenderValueInputProvider>
|
|
235
|
+
)
|
|
236
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { useRef, useState } from 'react'
|
|
2
|
+
import { cn } from '../../lib/cn'
|
|
3
|
+
import { PopoverContent } from '../popover/popover'
|
|
4
|
+
import { type FilterValueInputRenderer, RenderValueInputProvider } from './filter-value-input'
|
|
5
|
+
import { countLeaves } from './group-mutations'
|
|
6
|
+
import { RecordFilterBuilderPanel } from './record-filter-builder-panel'
|
|
7
|
+
import { useShowDetails } from './show-details-store'
|
|
8
|
+
import type { FilterField, FilterGroup } from './types'
|
|
9
|
+
|
|
10
|
+
/* =========================================================================
|
|
11
|
+
RecordFilterButton — the inline trigger that opens the builder popover.
|
|
12
|
+
Sized to sit on the same row as other table actions (Columns / Sort /
|
|
13
|
+
New customer). When the chips row is expanded AND has at least one
|
|
14
|
+
active filter, this button hides itself — the chip row is the single
|
|
15
|
+
source of UI in that mode.
|
|
16
|
+
========================================================================= */
|
|
17
|
+
|
|
18
|
+
export type RecordFilterButtonProps = {
|
|
19
|
+
/** Display label shown in the builder header. */
|
|
20
|
+
entityLabel?: string
|
|
21
|
+
fields: ReadonlyArray<FilterField>
|
|
22
|
+
filter: FilterGroup
|
|
23
|
+
onChange: (next: FilterGroup) => void
|
|
24
|
+
showFieldIcons?: boolean
|
|
25
|
+
showJoinerToggle?: boolean
|
|
26
|
+
/** Optional save-as-view affordance forwarded to the builder. */
|
|
27
|
+
onSaveView?: () => void
|
|
28
|
+
/** localStorage key shared with `RecordFilterChips`. */
|
|
29
|
+
storageKey?: string
|
|
30
|
+
/** Opt-in to nested-group affordances inside the builder popover. Defaults
|
|
31
|
+
* to `false` for back-compat. Forwarded to `RecordFilterBuilderPanel`. */
|
|
32
|
+
isComplexEnabled?: boolean
|
|
33
|
+
/** Optional override for the value editor. Forwarded to the builder panel. */
|
|
34
|
+
renderValueInput?: FilterValueInputRenderer
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function RecordFilterButton({
|
|
38
|
+
entityLabel,
|
|
39
|
+
fields,
|
|
40
|
+
filter,
|
|
41
|
+
onChange,
|
|
42
|
+
showFieldIcons = true,
|
|
43
|
+
showJoinerToggle = true,
|
|
44
|
+
onSaveView,
|
|
45
|
+
storageKey,
|
|
46
|
+
isComplexEnabled = false,
|
|
47
|
+
renderValueInput,
|
|
48
|
+
}: RecordFilterButtonProps) {
|
|
49
|
+
const ref = useRef<HTMLSpanElement>(null)
|
|
50
|
+
const [open, setOpen] = useState(false)
|
|
51
|
+
const [showDetails, toggleDetails] = useShowDetails(storageKey)
|
|
52
|
+
|
|
53
|
+
const filterCount = countLeaves(filter)
|
|
54
|
+
const hasFilters = filterCount > 0
|
|
55
|
+
|
|
56
|
+
// Expanded mode hides the Filter button entirely — the chip row is the
|
|
57
|
+
// single source of UI in that mode (+ Add pill is the entry point).
|
|
58
|
+
if (showDetails) return null
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<RenderValueInputProvider renderer={renderValueInput}>
|
|
62
|
+
<span ref={ref} className="inline-flex">
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
data-filter-trigger="collapsed"
|
|
66
|
+
onClick={() => setOpen((v) => !v)}
|
|
67
|
+
className={cn(
|
|
68
|
+
'inline-flex items-center gap-1.5 px-3 py-1.5 rounded-md border',
|
|
69
|
+
'font-mono text-[12px] cursor-pointer outline-none',
|
|
70
|
+
'focus-visible:shadow-[var(--shadow-focus)]',
|
|
71
|
+
hasFilters
|
|
72
|
+
? cn(
|
|
73
|
+
'border-[color-mix(in_oklab,var(--color-accent)_35%,var(--color-rule))]',
|
|
74
|
+
'bg-[color-mix(in_oklab,var(--color-accent)_8%,var(--color-surface))]',
|
|
75
|
+
'text-accent-ink',
|
|
76
|
+
)
|
|
77
|
+
: 'border-rule bg-surface text-ink-2 hover:border-ink-4 hover:text-ink',
|
|
78
|
+
)}
|
|
79
|
+
>
|
|
80
|
+
<svg
|
|
81
|
+
aria-hidden
|
|
82
|
+
viewBox="0 0 14 14"
|
|
83
|
+
className="size-3"
|
|
84
|
+
fill="none"
|
|
85
|
+
stroke="currentColor"
|
|
86
|
+
strokeWidth="1.4"
|
|
87
|
+
strokeLinecap="round"
|
|
88
|
+
strokeLinejoin="round"
|
|
89
|
+
>
|
|
90
|
+
<path d="M2 3h10M4 7h6M6 11h2" />
|
|
91
|
+
</svg>
|
|
92
|
+
Filter
|
|
93
|
+
{hasFilters ? (
|
|
94
|
+
<span className="font-mono text-[10.5px] px-1.5 py-px rounded-pill bg-accent text-surface">
|
|
95
|
+
{filterCount}
|
|
96
|
+
</span>
|
|
97
|
+
) : null}
|
|
98
|
+
</button>
|
|
99
|
+
</span>
|
|
100
|
+
|
|
101
|
+
<PopoverContent
|
|
102
|
+
triggerRef={ref}
|
|
103
|
+
isOpen={open}
|
|
104
|
+
onOpenChange={setOpen}
|
|
105
|
+
placement="bottom start"
|
|
106
|
+
panelClassName="p-0 bg-bg"
|
|
107
|
+
>
|
|
108
|
+
<RecordFilterBuilderPanel
|
|
109
|
+
{...(entityLabel ? { entityLabel } : {})}
|
|
110
|
+
fields={fields}
|
|
111
|
+
filter={filter}
|
|
112
|
+
onChange={onChange}
|
|
113
|
+
onClose={() => setOpen(false)}
|
|
114
|
+
{...(onSaveView ? { onSaveView } : {})}
|
|
115
|
+
onExpand={() => {
|
|
116
|
+
if (!showDetails) toggleDetails()
|
|
117
|
+
setOpen(false)
|
|
118
|
+
}}
|
|
119
|
+
showFieldIcons={showFieldIcons}
|
|
120
|
+
showJoinerToggle={showJoinerToggle}
|
|
121
|
+
isComplexEnabled={isComplexEnabled}
|
|
122
|
+
/>
|
|
123
|
+
</PopoverContent>
|
|
124
|
+
</RenderValueInputProvider>
|
|
125
|
+
)
|
|
126
|
+
}
|