@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.
Files changed (226) hide show
  1. package/LICENSE +105 -0
  2. package/dist/chunk-IKAX76MB.js +113 -0
  3. package/dist/chunk-IKAX76MB.js.map +1 -0
  4. package/dist/chunk-MWAXK66H.cjs +119 -0
  5. package/dist/chunk-MWAXK66H.cjs.map +1 -0
  6. package/dist/index.cjs +6246 -0
  7. package/dist/index.cjs.map +1 -0
  8. package/dist/index.d.cts +2126 -0
  9. package/dist/index.d.ts +2126 -0
  10. package/dist/index.js +6069 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/pdf-view-3ZMMS6ML.cjs +95 -0
  13. package/dist/pdf-view-3ZMMS6ML.cjs.map +1 -0
  14. package/dist/pdf-view-BV5ZD6OC.js +93 -0
  15. package/dist/pdf-view-BV5ZD6OC.js.map +1 -0
  16. package/package.json +96 -0
  17. package/src/components/attachment-chip/attachment-chip.tsx +129 -0
  18. package/src/components/attachment-chip/index.ts +2 -0
  19. package/src/components/attribute-type-icon/attribute-type-icon.stories.tsx +70 -0
  20. package/src/components/attribute-type-icon/attribute-type-icon.tsx +125 -0
  21. package/src/components/attribute-type-icon/index.ts +8 -0
  22. package/src/components/avatar/avatar.stories.tsx +42 -0
  23. package/src/components/avatar/avatar.tsx +48 -0
  24. package/src/components/avatar/index.ts +2 -0
  25. package/src/components/avatar-group/avatar-group.test.tsx +38 -0
  26. package/src/components/avatar-group/avatar-group.tsx +60 -0
  27. package/src/components/avatar-group/index.ts +2 -0
  28. package/src/components/badge/badge.stories.tsx +62 -0
  29. package/src/components/badge/badge.test.tsx +36 -0
  30. package/src/components/badge/badge.tsx +54 -0
  31. package/src/components/badge/index.ts +2 -0
  32. package/src/components/badge-multi-select/badge-multi-select.stories.tsx +177 -0
  33. package/src/components/badge-multi-select/badge-multi-select.test.tsx +155 -0
  34. package/src/components/badge-multi-select/badge-multi-select.tsx +272 -0
  35. package/src/components/badge-multi-select/index.ts +2 -0
  36. package/src/components/breadcrumbs/breadcrumbs.stories.tsx +43 -0
  37. package/src/components/breadcrumbs/breadcrumbs.tsx +86 -0
  38. package/src/components/breadcrumbs/index.ts +2 -0
  39. package/src/components/button/button.stories.tsx +290 -0
  40. package/src/components/button/button.test.tsx +59 -0
  41. package/src/components/button/button.tsx +86 -0
  42. package/src/components/button/index.ts +2 -0
  43. package/src/components/card/card.stories.tsx +351 -0
  44. package/src/components/card/card.test.tsx +141 -0
  45. package/src/components/card/card.tsx +173 -0
  46. package/src/components/card/index.ts +13 -0
  47. package/src/components/checkbox/checkbox-group.tsx +52 -0
  48. package/src/components/checkbox/checkbox.stories.tsx +48 -0
  49. package/src/components/checkbox/checkbox.test.tsx +50 -0
  50. package/src/components/checkbox/checkbox.tsx +71 -0
  51. package/src/components/checkbox/index.ts +4 -0
  52. package/src/components/combobox/combobox.stories.tsx +97 -0
  53. package/src/components/combobox/combobox.test.tsx +123 -0
  54. package/src/components/combobox/combobox.tsx +239 -0
  55. package/src/components/combobox/index.ts +2 -0
  56. package/src/components/command/command-hint.tsx +46 -0
  57. package/src/components/command/command.stories.tsx +88 -0
  58. package/src/components/command/command.test.tsx +102 -0
  59. package/src/components/command/command.tsx +76 -0
  60. package/src/components/command/index.ts +16 -0
  61. package/src/components/currency-input/currency-input.tsx +192 -0
  62. package/src/components/currency-input/index.ts +6 -0
  63. package/src/components/data-table/cells.stories.tsx +120 -0
  64. package/src/components/data-table/cells.tsx +165 -0
  65. package/src/components/data-table/data-table.stories.tsx +371 -0
  66. package/src/components/data-table/data-table.tsx +556 -0
  67. package/src/components/data-table/index.ts +22 -0
  68. package/src/components/data-table/make-actions-column.ts +30 -0
  69. package/src/components/data-table/states/data-table-empty.tsx +40 -0
  70. package/src/components/data-table/states/data-table-error.tsx +30 -0
  71. package/src/components/data-table/states/data-table-skeleton.tsx +42 -0
  72. package/src/components/data-table/use-infinite-scroll-sentinel.ts +51 -0
  73. package/src/components/date-picker/calendar-internals.tsx +138 -0
  74. package/src/components/date-picker/date-picker.stories.tsx +78 -0
  75. package/src/components/date-picker/date-picker.test.tsx +71 -0
  76. package/src/components/date-picker/date-picker.tsx +140 -0
  77. package/src/components/date-picker/index.ts +2 -0
  78. package/src/components/date-time-picker/date-time-picker.stories.tsx +96 -0
  79. package/src/components/date-time-picker/date-time-picker.test.tsx +87 -0
  80. package/src/components/date-time-picker/date-time-picker.tsx +203 -0
  81. package/src/components/date-time-picker/index.ts +2 -0
  82. package/src/components/empty-state/empty-state.tsx +63 -0
  83. package/src/components/empty-state/index.ts +2 -0
  84. package/src/components/error-animation/error-animation.stories.tsx +50 -0
  85. package/src/components/error-animation/error-animation.test.tsx +22 -0
  86. package/src/components/error-animation/error-animation.tsx +26 -0
  87. package/src/components/error-animation/index.ts +2 -0
  88. package/src/components/field/field.tsx +142 -0
  89. package/src/components/field/index.ts +8 -0
  90. package/src/components/file-field/file-field.tsx +285 -0
  91. package/src/components/file-field/index.ts +2 -0
  92. package/src/components/file-viewer/fallback-card.tsx +68 -0
  93. package/src/components/file-viewer/file-viewer.stories.tsx +67 -0
  94. package/src/components/file-viewer/file-viewer.test.tsx +81 -0
  95. package/src/components/file-viewer/file-viewer.tsx +116 -0
  96. package/src/components/file-viewer/image-view.tsx +38 -0
  97. package/src/components/file-viewer/index.ts +7 -0
  98. package/src/components/file-viewer/pdf-view.tsx +122 -0
  99. package/src/components/file-viewer/video-view.tsx +16 -0
  100. package/src/components/image-attachment/image-attachment.stories.tsx +39 -0
  101. package/src/components/image-attachment/image-attachment.test.tsx +54 -0
  102. package/src/components/image-attachment/image-attachment.tsx +77 -0
  103. package/src/components/image-attachment/index.ts +3 -0
  104. package/src/components/image-attachment/is-previewable-image-name.ts +14 -0
  105. package/src/components/image-lightbox/image-lightbox.stories.tsx +38 -0
  106. package/src/components/image-lightbox/image-lightbox.test.tsx +72 -0
  107. package/src/components/image-lightbox/image-lightbox.tsx +92 -0
  108. package/src/components/image-lightbox/index.ts +2 -0
  109. package/src/components/input/index.ts +2 -0
  110. package/src/components/input/input.stories.tsx +71 -0
  111. package/src/components/input/input.test.tsx +36 -0
  112. package/src/components/input/input.tsx +76 -0
  113. package/src/components/kbd/index.ts +2 -0
  114. package/src/components/kbd/kbd.stories.tsx +34 -0
  115. package/src/components/kbd/kbd.tsx +27 -0
  116. package/src/components/menu/index.ts +2 -0
  117. package/src/components/menu/menu.tsx +121 -0
  118. package/src/components/modal/index.ts +10 -0
  119. package/src/components/modal/modal.stories.tsx +153 -0
  120. package/src/components/modal/modal.test.tsx +93 -0
  121. package/src/components/modal/modal.tsx +149 -0
  122. package/src/components/multi-select/index.ts +2 -0
  123. package/src/components/multi-select/multi-select.stories.tsx +211 -0
  124. package/src/components/multi-select/multi-select.test.tsx +151 -0
  125. package/src/components/multi-select/multi-select.tsx +339 -0
  126. package/src/components/nav/index.ts +7 -0
  127. package/src/components/nav/nav.stories.tsx +139 -0
  128. package/src/components/nav/nav.tsx +246 -0
  129. package/src/components/popover/index.ts +8 -0
  130. package/src/components/popover/popover.stories.tsx +92 -0
  131. package/src/components/popover/popover.tsx +135 -0
  132. package/src/components/progress-bar/index.ts +2 -0
  133. package/src/components/progress-bar/progress-bar.stories.tsx +75 -0
  134. package/src/components/progress-bar/progress-bar.test.tsx +29 -0
  135. package/src/components/progress-bar/progress-bar.tsx +57 -0
  136. package/src/components/radio-group/index.ts +2 -0
  137. package/src/components/radio-group/radio-group.stories.tsx +113 -0
  138. package/src/components/radio-group/radio-group.test.tsx +87 -0
  139. package/src/components/radio-group/radio-group.tsx +104 -0
  140. package/src/components/record-filter/attribute-picker.tsx +74 -0
  141. package/src/components/record-filter/chip-value.tsx +171 -0
  142. package/src/components/record-filter/filter-row.tsx +262 -0
  143. package/src/components/record-filter/filter-value-input.tsx +351 -0
  144. package/src/components/record-filter/group-add-controls.tsx +77 -0
  145. package/src/components/record-filter/group-body.tsx +173 -0
  146. package/src/components/record-filter/group-mutations.test.ts +104 -0
  147. package/src/components/record-filter/group-mutations.ts +116 -0
  148. package/src/components/record-filter/index.ts +58 -0
  149. package/src/components/record-filter/nested-group.tsx +138 -0
  150. package/src/components/record-filter/operator-picker.tsx +62 -0
  151. package/src/components/record-filter/operators.test.ts +105 -0
  152. package/src/components/record-filter/operators.ts +97 -0
  153. package/src/components/record-filter/record-filter-builder-panel.tsx +236 -0
  154. package/src/components/record-filter/record-filter-button.tsx +126 -0
  155. package/src/components/record-filter/record-filter-chips.tsx +527 -0
  156. package/src/components/record-filter/record-filter-editor.tsx +91 -0
  157. package/src/components/record-filter/record-filter.stories.tsx +196 -0
  158. package/src/components/record-filter/record-filter.test.tsx +437 -0
  159. package/src/components/record-filter/show-details-store.ts +72 -0
  160. package/src/components/record-filter/types.ts +150 -0
  161. package/src/components/segmented-control/index.ts +6 -0
  162. package/src/components/segmented-control/segmented-control.stories.tsx +67 -0
  163. package/src/components/segmented-control/segmented-control.test.tsx +47 -0
  164. package/src/components/segmented-control/segmented-control.tsx +105 -0
  165. package/src/components/select/index.ts +2 -0
  166. package/src/components/select/select.stories.tsx +76 -0
  167. package/src/components/select/select.test.tsx +56 -0
  168. package/src/components/select/select.tsx +159 -0
  169. package/src/components/separator/index.ts +2 -0
  170. package/src/components/separator/separator.stories.tsx +73 -0
  171. package/src/components/separator/separator.test.tsx +29 -0
  172. package/src/components/separator/separator.tsx +51 -0
  173. package/src/components/skeleton/index.ts +2 -0
  174. package/src/components/skeleton/skeleton.stories.tsx +68 -0
  175. package/src/components/skeleton/skeleton.test.tsx +38 -0
  176. package/src/components/skeleton/skeleton.tsx +43 -0
  177. package/src/components/success-animation/index.ts +2 -0
  178. package/src/components/success-animation/success-animation.stories.tsx +50 -0
  179. package/src/components/success-animation/success-animation.test.tsx +22 -0
  180. package/src/components/success-animation/success-animation.tsx +26 -0
  181. package/src/components/switch/index.ts +2 -0
  182. package/src/components/switch/switch.stories.tsx +60 -0
  183. package/src/components/switch/switch.test.tsx +50 -0
  184. package/src/components/switch/switch.tsx +49 -0
  185. package/src/components/table/index.ts +25 -0
  186. package/src/components/table/table.stories.tsx +306 -0
  187. package/src/components/table/table.test.tsx +115 -0
  188. package/src/components/table/table.tsx +543 -0
  189. package/src/components/tabs/index.ts +8 -0
  190. package/src/components/tabs/tabs.tsx +141 -0
  191. package/src/components/tag-input/index.ts +2 -0
  192. package/src/components/tag-input/tag-input.stories.tsx +73 -0
  193. package/src/components/tag-input/tag-input.test.tsx +80 -0
  194. package/src/components/tag-input/tag-input.tsx +193 -0
  195. package/src/components/textarea/index.ts +2 -0
  196. package/src/components/textarea/textarea.stories.tsx +75 -0
  197. package/src/components/textarea/textarea.test.tsx +41 -0
  198. package/src/components/textarea/textarea.tsx +86 -0
  199. package/src/components/time-field/index.ts +2 -0
  200. package/src/components/time-field/time-field.stories.tsx +76 -0
  201. package/src/components/time-field/time-field.test.tsx +51 -0
  202. package/src/components/time-field/time-field.tsx +98 -0
  203. package/src/components/tooltip/index.ts +2 -0
  204. package/src/components/tooltip/tooltip.stories.tsx +176 -0
  205. package/src/components/tooltip/tooltip.test.tsx +52 -0
  206. package/src/components/tooltip/tooltip.tsx +56 -0
  207. package/src/index.ts +47 -0
  208. package/src/lib/cn.ts +6 -0
  209. package/src/lib/commands/discovery-store.test.tsx +76 -0
  210. package/src/lib/commands/discovery-store.ts +110 -0
  211. package/src/lib/commands/index.ts +9 -0
  212. package/src/lib/commands/match-event.test.ts +99 -0
  213. package/src/lib/commands/match-event.ts +31 -0
  214. package/src/lib/commands/parse-combo.test.ts +65 -0
  215. package/src/lib/commands/parse-combo.ts +48 -0
  216. package/src/lib/commands/platform.ts +27 -0
  217. package/src/lib/commands/protected-combos.test.ts +18 -0
  218. package/src/lib/commands/protected-combos.ts +38 -0
  219. package/src/lib/commands/registry.test.ts +97 -0
  220. package/src/lib/commands/registry.ts +59 -0
  221. package/src/lib/commands/use-command.test.tsx +91 -0
  222. package/src/lib/commands/use-command.ts +32 -0
  223. package/src/lib/commands/use-escape-key.test.tsx +97 -0
  224. package/src/lib/commands/use-escape-key.ts +62 -0
  225. package/src/styles.css +321 -0
  226. 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
+ }