@flamingo-stack/openframe-frontend-core 0.0.400-snapshot.20260710144905 → 0.0.400
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/dist/{chunk-RWKVTABY.js → chunk-463X3AW2.js} +2 -2
- package/dist/{chunk-2B5FIJE5.js → chunk-4HAVKWTJ.js} +2 -2
- package/dist/{chunk-7YKHVAUU.cjs → chunk-556BG7SX.cjs} +31 -31
- package/dist/{chunk-7YKHVAUU.cjs.map → chunk-556BG7SX.cjs.map} +1 -1
- package/dist/{chunk-YON2QYWK.js → chunk-56XCRZ7I.js} +2 -2
- package/dist/{chunk-5P5WMTVW.js → chunk-6VR7BJF6.js} +4 -4
- package/dist/{chunk-Q4HI3DGY.cjs → chunk-6XQJSN7G.cjs} +15 -15
- package/dist/{chunk-Q4HI3DGY.cjs.map → chunk-6XQJSN7G.cjs.map} +1 -1
- package/dist/{chunk-FRLLKYMO.cjs → chunk-ABRDOCVN.cjs} +7 -7
- package/dist/{chunk-FRLLKYMO.cjs.map → chunk-ABRDOCVN.cjs.map} +1 -1
- package/dist/{chunk-QOOWYEPL.cjs → chunk-EFTKJFEF.cjs} +5 -5
- package/dist/{chunk-QOOWYEPL.cjs.map → chunk-EFTKJFEF.cjs.map} +1 -1
- package/dist/{chunk-PMR74LEE.js → chunk-FHEIG7QO.js} +2 -2
- package/dist/{chunk-FLDYBM25.js → chunk-I4XG5PJA.js} +2 -2
- package/dist/{chunk-QDERWRT4.cjs → chunk-ILWSJCWD.cjs} +26 -26
- package/dist/{chunk-QDERWRT4.cjs.map → chunk-ILWSJCWD.cjs.map} +1 -1
- package/dist/{chunk-F2LZ67KM.cjs → chunk-J6IQQJJQ.cjs} +1172 -1165
- package/dist/chunk-J6IQQJJQ.cjs.map +1 -0
- package/dist/{chunk-TGWHUEPD.cjs → chunk-KIC4ZEZ7.cjs} +62 -62
- package/dist/{chunk-TGWHUEPD.cjs.map → chunk-KIC4ZEZ7.cjs.map} +1 -1
- package/dist/{chunk-MZPEN27A.js → chunk-LAL63W5J.js} +4 -4
- package/dist/{chunk-3GXZPZHR.cjs → chunk-MFRP2UDK.cjs} +26 -26
- package/dist/{chunk-3GXZPZHR.cjs.map → chunk-MFRP2UDK.cjs.map} +1 -1
- package/dist/{chunk-V4YFWSB2.js → chunk-MQRTT3DO.js} +4 -4
- package/dist/{chunk-PLB6Z3E4.cjs → chunk-OPD5V57R.cjs} +12 -12
- package/dist/{chunk-PLB6Z3E4.cjs.map → chunk-OPD5V57R.cjs.map} +1 -1
- package/dist/{chunk-B3IDZ6JJ.js → chunk-PD5MXNTJ.js} +4 -4
- package/dist/{chunk-VIEWUAFE.cjs → chunk-QGQ76SSR.cjs} +9 -9
- package/dist/{chunk-VIEWUAFE.cjs.map → chunk-QGQ76SSR.cjs.map} +1 -1
- package/dist/{chunk-BI23LOMC.cjs → chunk-RSFHAFZN.cjs} +37 -37
- package/dist/{chunk-BI23LOMC.cjs.map → chunk-RSFHAFZN.cjs.map} +1 -1
- package/dist/{chunk-V3NY65ET.js → chunk-RYPCNUXA.js} +2 -2
- package/dist/{chunk-TZDROKLI.js → chunk-UFP45JLE.js} +2 -2
- package/dist/{chunk-EDR55RQJ.js → chunk-UYBNOHTC.js} +4714 -4707
- package/dist/chunk-UYBNOHTC.js.map +1 -0
- package/dist/{chunk-CG7LIPSA.cjs → chunk-VTSEL7CJ.cjs} +59 -59
- package/dist/{chunk-CG7LIPSA.cjs.map → chunk-VTSEL7CJ.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/chat-ticket-list.d.ts.map +1 -1
- package/dist/components/chat/guide-welcome.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/chat/mingo-chat-history.d.ts.map +1 -1
- package/dist/components/chat/mingo-welcome.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/help-center-pages/index.cjs +20 -20
- package/dist/components/help-center-pages/index.js +11 -11
- package/dist/components/index.cjs +120 -126
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +9 -15
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +5 -5
- package/dist/components/tickets/index.js +4 -4
- package/dist/components/ui/data-table/data-table-header.d.ts.map +1 -1
- package/dist/components/ui/data-table/types.d.ts +0 -6
- package/dist/components/ui/data-table/types.d.ts.map +1 -1
- package/dist/components/ui/date-picker.d.ts +4 -41
- package/dist/components/ui/date-picker.d.ts.map +1 -1
- package/dist/components/ui/filter-modal.d.ts +1 -16
- package/dist/components/ui/filter-modal.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +2 -8
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +0 -1
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +1 -7
- package/dist/index.cjs +2 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -7
- package/package.json +1 -1
- package/src/components/chat/chat-ticket-list.tsx +36 -8
- package/src/components/chat/guide-welcome.tsx +43 -5
- package/src/components/chat/mingo-chat-history.tsx +40 -6
- package/src/components/chat/mingo-welcome.tsx +47 -7
- package/src/components/ui/data-table/data-table-header.tsx +3 -6
- package/src/components/ui/data-table/types.ts +0 -6
- package/src/components/ui/date-picker.tsx +41 -149
- package/src/components/ui/filter-modal.tsx +2 -57
- package/src/components/ui/index.ts +0 -1
- package/src/stories/DatePicker.stories.tsx +2 -4
- package/src/stories/FilterModal.stories.tsx +0 -33
- package/dist/chunk-EDR55RQJ.js.map +0 -1
- package/dist/chunk-F2LZ67KM.cjs.map +0 -1
- package/dist/components/ui/scroll-fade.d.ts +0 -33
- package/dist/components/ui/scroll-fade.d.ts.map +0 -1
- package/src/components/ui/scroll-fade.tsx +0 -90
- /package/dist/{chunk-RWKVTABY.js.map → chunk-463X3AW2.js.map} +0 -0
- /package/dist/{chunk-2B5FIJE5.js.map → chunk-4HAVKWTJ.js.map} +0 -0
- /package/dist/{chunk-YON2QYWK.js.map → chunk-56XCRZ7I.js.map} +0 -0
- /package/dist/{chunk-5P5WMTVW.js.map → chunk-6VR7BJF6.js.map} +0 -0
- /package/dist/{chunk-PMR74LEE.js.map → chunk-FHEIG7QO.js.map} +0 -0
- /package/dist/{chunk-FLDYBM25.js.map → chunk-I4XG5PJA.js.map} +0 -0
- /package/dist/{chunk-MZPEN27A.js.map → chunk-LAL63W5J.js.map} +0 -0
- /package/dist/{chunk-V4YFWSB2.js.map → chunk-MQRTT3DO.js.map} +0 -0
- /package/dist/{chunk-B3IDZ6JJ.js.map → chunk-PD5MXNTJ.js.map} +0 -0
- /package/dist/{chunk-V3NY65ET.js.map → chunk-RYPCNUXA.js.map} +0 -0
- /package/dist/{chunk-TZDROKLI.js.map → chunk-UFP45JLE.js.map} +0 -0
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { cn } from '../../utils/cn'
|
|
5
5
|
import { MingoIcon } from '../icons'
|
|
6
|
-
import { ScrollFadeOverlay, useScrollFade } from '../ui/scroll-fade'
|
|
7
6
|
import { Skeleton } from '../ui/skeleton'
|
|
8
7
|
import { ChatQuickActionRow } from './chat-quick-action-row'
|
|
9
8
|
import { EntityIcon } from '../icon-display'
|
|
@@ -101,7 +100,25 @@ export function GuideWelcome({
|
|
|
101
100
|
// Scroll-fade affordances: a 48px gradient at the top/bottom edge of the
|
|
102
101
|
// scroll region, shown only while content is actually hidden in that
|
|
103
102
|
// direction. (Same behaviour as MingoWelcome.)
|
|
104
|
-
const
|
|
103
|
+
const scrollRef = React.useRef<HTMLDivElement>(null)
|
|
104
|
+
const [scrollFade, setScrollFade] = React.useState({ top: false, bottom: false })
|
|
105
|
+
const updateScrollFade = React.useCallback(() => {
|
|
106
|
+
const el = scrollRef.current
|
|
107
|
+
if (!el) return
|
|
108
|
+
const top = el.scrollTop > 1
|
|
109
|
+
const bottom = el.scrollTop + el.clientHeight < el.scrollHeight - 1
|
|
110
|
+
setScrollFade((prev) =>
|
|
111
|
+
prev.top === top && prev.bottom === bottom ? prev : { top, bottom },
|
|
112
|
+
)
|
|
113
|
+
}, [])
|
|
114
|
+
React.useEffect(() => {
|
|
115
|
+
updateScrollFade()
|
|
116
|
+
const el = scrollRef.current
|
|
117
|
+
if (!el || typeof ResizeObserver === 'undefined') return
|
|
118
|
+
const ro = new ResizeObserver(updateScrollFade)
|
|
119
|
+
ro.observe(el)
|
|
120
|
+
return () => ro.disconnect()
|
|
121
|
+
}, [updateScrollFade])
|
|
105
122
|
|
|
106
123
|
// Map to the shared `ChatQuickActionRow` chip shape. In `wrap` mode every chip
|
|
107
124
|
// renders (no overflow), and `onHoverStart`/`onHoverEnd` drive the composer
|
|
@@ -187,9 +204,30 @@ export function GuideWelcome({
|
|
|
187
204
|
{children}
|
|
188
205
|
</div>
|
|
189
206
|
|
|
190
|
-
{/*
|
|
191
|
-
<
|
|
192
|
-
|
|
207
|
+
{/* Top scroll-fade — visible only when content is hidden above. */}
|
|
208
|
+
<div
|
|
209
|
+
aria-hidden
|
|
210
|
+
className={cn(
|
|
211
|
+
'pointer-events-none absolute inset-x-0 top-0 h-12 transition-opacity duration-150',
|
|
212
|
+
scrollFade.top ? 'opacity-100' : 'opacity-0',
|
|
213
|
+
)}
|
|
214
|
+
style={{
|
|
215
|
+
background:
|
|
216
|
+
'linear-gradient(0deg, transparent 0%, var(--color-bg) 100%)',
|
|
217
|
+
}}
|
|
218
|
+
/>
|
|
219
|
+
{/* Bottom scroll-fade — visible only when content is hidden below. */}
|
|
220
|
+
<div
|
|
221
|
+
aria-hidden
|
|
222
|
+
className={cn(
|
|
223
|
+
'pointer-events-none absolute inset-x-0 bottom-0 h-12 transition-opacity duration-150',
|
|
224
|
+
scrollFade.bottom ? 'opacity-100' : 'opacity-0',
|
|
225
|
+
)}
|
|
226
|
+
style={{
|
|
227
|
+
background:
|
|
228
|
+
'linear-gradient(180deg, transparent 0%, var(--color-bg) 100%)',
|
|
229
|
+
}}
|
|
230
|
+
/>
|
|
193
231
|
</div>
|
|
194
232
|
|
|
195
233
|
{/* Pinned quick-action chips above the composer — the shared
|
|
@@ -6,7 +6,6 @@ import { useDebounce } from '../../hooks/ui/use-debounce'
|
|
|
6
6
|
import { ActionsMenuDropdown, type ActionsMenuItem } from '../ui/actions-menu'
|
|
7
7
|
import { Button } from '../ui/button'
|
|
8
8
|
import { Input } from '../ui/input'
|
|
9
|
-
import { ScrollFadeOverlay, useScrollFade } from '../ui/scroll-fade'
|
|
10
9
|
import { Ellipsis01Icon, SearchIcon } from '../icons-v2-generated'
|
|
11
10
|
import type { DialogItem } from './types/component.types'
|
|
12
11
|
|
|
@@ -358,9 +357,24 @@ export function MingoChatHistory({
|
|
|
358
357
|
const groups = React.useMemo(() => groupDialogs(dialogs), [dialogs])
|
|
359
358
|
const noSearchResults = groups.length === 0 && !!searchQuery?.trim()
|
|
360
359
|
|
|
361
|
-
// Scroll-fade affordances
|
|
362
|
-
|
|
363
|
-
const {
|
|
360
|
+
// Scroll-fade affordances (same pattern as MingoWelcome's greeting region).
|
|
361
|
+
const scrollRef = React.useRef<HTMLDivElement>(null)
|
|
362
|
+
const [fade, setFade] = React.useState({ top: false, bottom: false })
|
|
363
|
+
const updateFade = React.useCallback(() => {
|
|
364
|
+
const el = scrollRef.current
|
|
365
|
+
if (!el) return
|
|
366
|
+
const top = el.scrollTop > 1
|
|
367
|
+
const bottom = el.scrollTop + el.clientHeight < el.scrollHeight - 1
|
|
368
|
+
setFade((p) => (p.top === top && p.bottom === bottom ? p : { top, bottom }))
|
|
369
|
+
}, [])
|
|
370
|
+
React.useEffect(() => {
|
|
371
|
+
updateFade()
|
|
372
|
+
const el = scrollRef.current
|
|
373
|
+
if (!el || typeof ResizeObserver === 'undefined') return
|
|
374
|
+
const ro = new ResizeObserver(updateFade)
|
|
375
|
+
ro.observe(el)
|
|
376
|
+
return () => ro.disconnect()
|
|
377
|
+
}, [updateFade, dialogs])
|
|
364
378
|
|
|
365
379
|
// Infinite scroll — load the next page when the sentinel enters the
|
|
366
380
|
// scroll viewport.
|
|
@@ -431,8 +445,28 @@ export function MingoChatHistory({
|
|
|
431
445
|
</div>
|
|
432
446
|
|
|
433
447
|
{/* Scroll-fade — only while content is hidden in that direction. */}
|
|
434
|
-
<
|
|
435
|
-
|
|
448
|
+
<div
|
|
449
|
+
aria-hidden
|
|
450
|
+
className={cn(
|
|
451
|
+
'pointer-events-none absolute inset-x-0 top-0 h-12 transition-opacity duration-150',
|
|
452
|
+
fade.top ? 'opacity-100' : 'opacity-0',
|
|
453
|
+
)}
|
|
454
|
+
style={{
|
|
455
|
+
background:
|
|
456
|
+
'linear-gradient(0deg, transparent 0%, var(--color-bg-card) 100%)',
|
|
457
|
+
}}
|
|
458
|
+
/>
|
|
459
|
+
<div
|
|
460
|
+
aria-hidden
|
|
461
|
+
className={cn(
|
|
462
|
+
'pointer-events-none absolute inset-x-0 bottom-0 h-12 transition-opacity duration-150',
|
|
463
|
+
fade.bottom ? 'opacity-100' : 'opacity-0',
|
|
464
|
+
)}
|
|
465
|
+
style={{
|
|
466
|
+
background:
|
|
467
|
+
'linear-gradient(180deg, transparent 0%, var(--color-bg-card) 100%)',
|
|
468
|
+
}}
|
|
469
|
+
/>
|
|
436
470
|
</div>
|
|
437
471
|
</div>
|
|
438
472
|
)
|
|
@@ -7,7 +7,6 @@ import { MingoChatHistorySkeleton } from './mingo-chat-history'
|
|
|
7
7
|
import { ChatQuickActionRow } from './chat-quick-action-row'
|
|
8
8
|
import { QuickActionChipButton } from './quick-action-chip'
|
|
9
9
|
import { Button } from '../ui/button'
|
|
10
|
-
import { ScrollFadeOverlay, useScrollFade } from '../ui/scroll-fade'
|
|
11
10
|
import { XmarkIcon } from '../icons-v2-generated/signs-and-symbols/xmark-icon'
|
|
12
11
|
import {
|
|
13
12
|
Rocket01Icon,
|
|
@@ -232,9 +231,29 @@ export function MingoWelcome({
|
|
|
232
231
|
}
|
|
233
232
|
}, [promoStorage, promoStorageKey])
|
|
234
233
|
|
|
235
|
-
// Scroll-fade affordances
|
|
236
|
-
// only while content is hidden in that direction
|
|
237
|
-
const
|
|
234
|
+
// Scroll-fade affordances: show a 48px gradient at the top/bottom edge of the
|
|
235
|
+
// scroll region only while content is actually hidden in that direction.
|
|
236
|
+
const scrollRef = React.useRef<HTMLDivElement>(null)
|
|
237
|
+
const [scrollFade, setScrollFade] = React.useState({ top: false, bottom: false })
|
|
238
|
+
const updateScrollFade = React.useCallback(() => {
|
|
239
|
+
const el = scrollRef.current
|
|
240
|
+
if (!el) return
|
|
241
|
+
const top = el.scrollTop > 1
|
|
242
|
+
const bottom = el.scrollTop + el.clientHeight < el.scrollHeight - 1
|
|
243
|
+
setScrollFade((prev) =>
|
|
244
|
+
prev.top === top && prev.bottom === bottom ? prev : { top, bottom },
|
|
245
|
+
)
|
|
246
|
+
}, [])
|
|
247
|
+
React.useEffect(() => {
|
|
248
|
+
updateScrollFade()
|
|
249
|
+
const el = scrollRef.current
|
|
250
|
+
if (!el || typeof ResizeObserver === 'undefined') return
|
|
251
|
+
// Recompute when the panel resizes — a taller panel may remove the need
|
|
252
|
+
// to scroll entirely, a shorter one introduces it.
|
|
253
|
+
const ro = new ResizeObserver(updateScrollFade)
|
|
254
|
+
ro.observe(el)
|
|
255
|
+
return () => ro.disconnect()
|
|
256
|
+
}, [updateScrollFade])
|
|
238
257
|
|
|
239
258
|
const cellCount = featureCards.length
|
|
240
259
|
// Last row's first index — used to drop the bottom divider on the final row.
|
|
@@ -346,9 +365,30 @@ export function MingoWelcome({
|
|
|
346
365
|
)}
|
|
347
366
|
</div>
|
|
348
367
|
|
|
349
|
-
{/*
|
|
350
|
-
<
|
|
351
|
-
|
|
368
|
+
{/* Top scroll-fade — visible only when content is hidden above. */}
|
|
369
|
+
<div
|
|
370
|
+
aria-hidden
|
|
371
|
+
className={cn(
|
|
372
|
+
'pointer-events-none absolute inset-x-0 top-0 h-12 transition-opacity duration-150',
|
|
373
|
+
scrollFade.top ? 'opacity-100' : 'opacity-0',
|
|
374
|
+
)}
|
|
375
|
+
style={{
|
|
376
|
+
background:
|
|
377
|
+
'linear-gradient(0deg, transparent 0%, var(--color-bg-card) 100%)',
|
|
378
|
+
}}
|
|
379
|
+
/>
|
|
380
|
+
{/* Bottom scroll-fade — visible only when content is hidden below. */}
|
|
381
|
+
<div
|
|
382
|
+
aria-hidden
|
|
383
|
+
className={cn(
|
|
384
|
+
'pointer-events-none absolute inset-x-0 bottom-0 h-12 transition-opacity duration-150',
|
|
385
|
+
scrollFade.bottom ? 'opacity-100' : 'opacity-0',
|
|
386
|
+
)}
|
|
387
|
+
style={{
|
|
388
|
+
background:
|
|
389
|
+
'linear-gradient(180deg, transparent 0%, var(--color-bg-card) 100%)',
|
|
390
|
+
}}
|
|
391
|
+
/>
|
|
352
392
|
</div>
|
|
353
393
|
</>
|
|
354
394
|
))}
|
|
@@ -69,8 +69,7 @@ export function DataTableHeader({
|
|
|
69
69
|
const hasVisibleHeaderCell = headerGroup.headers.some(header => {
|
|
70
70
|
if (header.isPlaceholder) return false
|
|
71
71
|
if (isLgUp) return true
|
|
72
|
-
|
|
73
|
-
return Boolean(meta?.filter) || meta?.alwaysShowHeader === true
|
|
72
|
+
return Boolean(header.column.columnDef.meta?.filter)
|
|
74
73
|
})
|
|
75
74
|
|
|
76
75
|
return (
|
|
@@ -123,8 +122,6 @@ function HeaderCell({ header, isLgUp, sort, onSortChange }: HeaderCellProps) {
|
|
|
123
122
|
const column = header.column
|
|
124
123
|
const meta = column.columnDef.meta
|
|
125
124
|
const hasFilter = Boolean(meta?.filter)
|
|
126
|
-
// Tablet visibility: filterable columns and explicit opt-ins stay rendered.
|
|
127
|
-
const keepOnTablet = hasFilter || meta?.alwaysShowHeader === true
|
|
128
125
|
const align = meta?.align ?? 'left'
|
|
129
126
|
// Sort is opt-in via `meta.sortable`. Direction is fully consumer-driven via
|
|
130
127
|
// the `sort` prop; we do not consult TanStack's sort APIs here.
|
|
@@ -133,7 +130,7 @@ function HeaderCell({ header, isLgUp, sort, onSortChange }: HeaderCellProps) {
|
|
|
133
130
|
sort?.id === column.id ? (sort.desc ? 'desc' : 'asc') : false
|
|
134
131
|
|
|
135
132
|
// Mobile (md, below lg): hide non-filter columns so only filters are accessible.
|
|
136
|
-
if (!isLgUp && !
|
|
133
|
+
if (!isLgUp && !hasFilter) return null
|
|
137
134
|
|
|
138
135
|
return (
|
|
139
136
|
<div
|
|
@@ -142,7 +139,7 @@ function HeaderCell({ header, isLgUp, sort, onSortChange }: HeaderCellProps) {
|
|
|
142
139
|
isLgUp && (meta?.width || 'flex-1 min-w-0'),
|
|
143
140
|
meta?.headerClassName,
|
|
144
141
|
// Don't apply hide classes if column is filterable on tablet (keep filter accessible)
|
|
145
|
-
!(
|
|
142
|
+
!(hasFilter && !isLgUp) && getHideClasses(meta?.hideAt),
|
|
146
143
|
)}
|
|
147
144
|
>
|
|
148
145
|
{hasFilter ? (
|
|
@@ -16,12 +16,6 @@ declare module '@tanstack/react-table' {
|
|
|
16
16
|
align?: 'left' | 'center' | 'right'
|
|
17
17
|
/** Hide column at/below these Tailwind breakpoints. */
|
|
18
18
|
hideAt?: TailwindBreakpoint | TailwindBreakpoint[]
|
|
19
|
-
/**
|
|
20
|
-
* Keep this header visible on tablet (md, below lg) even without
|
|
21
|
-
* `meta.filter` — for custom headers hosting their own filter UI
|
|
22
|
-
* (e.g. the date filter calendar trigger).
|
|
23
|
-
*/
|
|
24
|
-
alwaysShowHeader?: boolean
|
|
25
19
|
/** If present, header renders a filter dropdown with these options. */
|
|
26
20
|
filter?: {
|
|
27
21
|
options: DataTableFilterOption[]
|
|
@@ -150,14 +150,6 @@ function DatePickerCalendar({
|
|
|
150
150
|
);
|
|
151
151
|
const [hoveredDate, setHoveredDate] = React.useState<Date | undefined>(undefined);
|
|
152
152
|
|
|
153
|
-
// Keep the internal range in sync when the consumer changes `selected`
|
|
154
|
-
// externally (e.g. DateFilterMenu Reset while the popover stays open).
|
|
155
|
-
React.useEffect(() => {
|
|
156
|
-
if (mode === "range") {
|
|
157
|
-
setDraftRange(selected as DateRange | undefined);
|
|
158
|
-
}
|
|
159
|
-
}, [mode, selected]);
|
|
160
|
-
|
|
161
153
|
const rangeSelected = draftRange;
|
|
162
154
|
const hasCompleteRange =
|
|
163
155
|
mode === "range" &&
|
|
@@ -176,11 +168,7 @@ function DatePickerCalendar({
|
|
|
176
168
|
if (!triggerDate) return;
|
|
177
169
|
|
|
178
170
|
if (!draftRange?.from || draftRange.to) {
|
|
179
|
-
|
|
180
|
-
// act on a single-day selection (e.g. DateFilterMenu Apply/Reset).
|
|
181
|
-
const started: DateRange = { from: triggerDate, to: undefined };
|
|
182
|
-
setDraftRange(started);
|
|
183
|
-
onSelect(started);
|
|
171
|
+
setDraftRange({ from: triggerDate, to: undefined });
|
|
184
172
|
return;
|
|
185
173
|
}
|
|
186
174
|
// Second click closes the range, ordering the two ends.
|
|
@@ -993,89 +981,6 @@ export function DatePickerInputSimple({
|
|
|
993
981
|
);
|
|
994
982
|
}
|
|
995
983
|
|
|
996
|
-
// ============================================================================
|
|
997
|
-
// DateFilterPanel Component (sort select + fluid calendar)
|
|
998
|
-
// ============================================================================
|
|
999
|
-
|
|
1000
|
-
export interface DateFilterPanelProps {
|
|
1001
|
-
/** Selection mode for the calendar. Defaults to "range". */
|
|
1002
|
-
mode?: DatePickerMode;
|
|
1003
|
-
/** Current sort direction shown in the select. */
|
|
1004
|
-
sort: SortDirection;
|
|
1005
|
-
onSortChange: (sort: SortDirection) => void;
|
|
1006
|
-
/** Current calendar selection. */
|
|
1007
|
-
selected: Date | DateRange | undefined;
|
|
1008
|
-
onSelect: (value: Date | DateRange | undefined) => void;
|
|
1009
|
-
/** Minimum selectable date. */
|
|
1010
|
-
fromDate?: Date;
|
|
1011
|
-
/** Maximum selectable date. */
|
|
1012
|
-
toDate?: Date;
|
|
1013
|
-
/** Locale for the calendar. */
|
|
1014
|
-
locale?: DayPickerProps["locale"];
|
|
1015
|
-
/** Label for the ascending sort option. */
|
|
1016
|
-
ascLabel?: string;
|
|
1017
|
-
/** Label for the descending sort option. */
|
|
1018
|
-
descLabel?: string;
|
|
1019
|
-
className?: string;
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
/**
|
|
1023
|
-
* DateFilterPanel — the controlled sort-direction select + fluid calendar
|
|
1024
|
-
* block shared by DateFilterMenu (popover) and FilterModal (mobile "Sort and
|
|
1025
|
-
* Filter"). Owns no state: the consumer drafts and commits the values.
|
|
1026
|
-
*/
|
|
1027
|
-
export function DateFilterPanel({
|
|
1028
|
-
mode = "range",
|
|
1029
|
-
sort,
|
|
1030
|
-
onSortChange,
|
|
1031
|
-
selected,
|
|
1032
|
-
onSelect,
|
|
1033
|
-
fromDate,
|
|
1034
|
-
toDate,
|
|
1035
|
-
locale,
|
|
1036
|
-
ascLabel = "Sort by Ascending",
|
|
1037
|
-
descLabel = "Sort by Descending",
|
|
1038
|
-
className,
|
|
1039
|
-
}: DateFilterPanelProps) {
|
|
1040
|
-
return (
|
|
1041
|
-
<div className={cn("flex flex-col gap-4", className)}>
|
|
1042
|
-
{/* Sort direction selector */}
|
|
1043
|
-
<Select
|
|
1044
|
-
value={sort}
|
|
1045
|
-
onValueChange={(value) => onSortChange(value as SortDirection)}
|
|
1046
|
-
>
|
|
1047
|
-
<SelectTrigger className="gap-2" aria-label="Sort direction">
|
|
1048
|
-
{/* Wrapper is a <div> (not <span>) so SelectTrigger's
|
|
1049
|
-
`[&>span]:line-clamp-1` rule doesn't force it to a vertical
|
|
1050
|
-
-webkit-box and stack the icon/label into a column. */}
|
|
1051
|
-
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
1052
|
-
<ArrowUpDown className="size-6 shrink-0 text-ods-text-secondary" />
|
|
1053
|
-
<span className="truncate">
|
|
1054
|
-
<SelectValue />
|
|
1055
|
-
</span>
|
|
1056
|
-
</div>
|
|
1057
|
-
</SelectTrigger>
|
|
1058
|
-
<SelectContent>
|
|
1059
|
-
<SelectItem value="asc">{ascLabel}</SelectItem>
|
|
1060
|
-
<SelectItem value="desc">{descLabel}</SelectItem>
|
|
1061
|
-
</SelectContent>
|
|
1062
|
-
</Select>
|
|
1063
|
-
|
|
1064
|
-
{/* Calendar */}
|
|
1065
|
-
<DatePickerCalendar
|
|
1066
|
-
mode={mode}
|
|
1067
|
-
selected={selected}
|
|
1068
|
-
onSelect={onSelect}
|
|
1069
|
-
numberOfMonths={1}
|
|
1070
|
-
fromDate={fromDate}
|
|
1071
|
-
toDate={toDate}
|
|
1072
|
-
locale={locale}
|
|
1073
|
-
fluid
|
|
1074
|
-
/>
|
|
1075
|
-
</div>
|
|
1076
|
-
);
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
984
|
// ============================================================================
|
|
1080
985
|
// DateFilterMenu Component (sort + calendar filter popover from Figma)
|
|
1081
986
|
// ============================================================================
|
|
@@ -1094,21 +999,14 @@ export interface DateFilterMenuProps {
|
|
|
1094
999
|
mode?: DatePickerMode;
|
|
1095
1000
|
/** Current (applied) sort direction. Defaults to "desc". */
|
|
1096
1001
|
sort?: SortDirection;
|
|
1097
|
-
/** Baseline sort direction — Reset restores it, and a draft that differs
|
|
1098
|
-
* from it counts as an active change (shows Reset). Defaults to "desc". */
|
|
1099
|
-
defaultSort?: SortDirection;
|
|
1100
1002
|
/** Current (applied) single date — used when mode === "single". */
|
|
1101
1003
|
date?: Date;
|
|
1102
1004
|
/** Current (applied) range — used when mode === "range". */
|
|
1103
1005
|
range?: DateRange;
|
|
1104
|
-
/** Fired when the user presses Apply with the drafted selection.
|
|
1105
|
-
* by Reset with a cleared selection so the consumer refetches unfiltered data. */
|
|
1006
|
+
/** Fired when the user presses Apply with the drafted selection. */
|
|
1106
1007
|
onApply?: (result: DateFilterResult) => void;
|
|
1107
1008
|
/** Fired when the menu closes (Close button, outside click, Esc). */
|
|
1108
1009
|
onClose?: () => void;
|
|
1109
|
-
/** Custom trigger element (rendered via Radix `asChild` — must accept a ref,
|
|
1110
|
-
* e.g. a native button). Defaults to the outline calendar icon Button. */
|
|
1111
|
-
trigger?: React.ReactNode;
|
|
1112
1010
|
/** Disable the trigger. */
|
|
1113
1011
|
disabled?: boolean;
|
|
1114
1012
|
/** Minimum selectable date. */
|
|
@@ -1131,21 +1029,16 @@ export interface DateFilterMenuProps {
|
|
|
1131
1029
|
|
|
1132
1030
|
/**
|
|
1133
1031
|
* DateFilterMenu — a calendar-icon-triggered popover combining a sort-direction
|
|
1134
|
-
* selector and a date / date-range calendar, with Close
|
|
1135
|
-
*
|
|
1136
|
-
* `onApply`. While a date is selected, Close is replaced by Reset, which
|
|
1137
|
-
* clears and commits the empty selection (fires `onApply`) so the consumer
|
|
1138
|
-
* drops the filter.
|
|
1032
|
+
* selector and a date / date-range calendar, with Close and Apply actions.
|
|
1033
|
+
* Selections are drafted internally and only committed via `onApply`.
|
|
1139
1034
|
*/
|
|
1140
1035
|
export function DateFilterMenu({
|
|
1141
1036
|
mode = "range",
|
|
1142
1037
|
sort = "desc",
|
|
1143
|
-
defaultSort = "desc",
|
|
1144
1038
|
date,
|
|
1145
1039
|
range,
|
|
1146
1040
|
onApply,
|
|
1147
1041
|
onClose,
|
|
1148
|
-
trigger,
|
|
1149
1042
|
disabled = false,
|
|
1150
1043
|
fromDate,
|
|
1151
1044
|
toDate,
|
|
@@ -1192,39 +1085,18 @@ export function DateFilterMenu({
|
|
|
1192
1085
|
setOpen(false);
|
|
1193
1086
|
};
|
|
1194
1087
|
|
|
1195
|
-
// Anything to reset? A calendar selection or a non-default sort — drives Close vs Reset.
|
|
1196
|
-
const hasSelection =
|
|
1197
|
-
mode === "single"
|
|
1198
|
-
? Boolean(draftSelected)
|
|
1199
|
-
: Boolean((draftSelected as DateRange | undefined)?.from);
|
|
1200
|
-
const hasChanges = hasSelection || draftSort !== defaultSort;
|
|
1201
|
-
|
|
1202
|
-
// Reset restores the defaults and commits them so the consumer drops the
|
|
1203
|
-
// filter and refetches; the menu stays open with the button back to Close.
|
|
1204
|
-
const handleReset = () => {
|
|
1205
|
-
setDraftSort(defaultSort);
|
|
1206
|
-
setDraftSelected(undefined);
|
|
1207
|
-
onApply?.(
|
|
1208
|
-
mode === "single"
|
|
1209
|
-
? { sort: defaultSort, date: undefined }
|
|
1210
|
-
: { sort: defaultSort, range: undefined }
|
|
1211
|
-
);
|
|
1212
|
-
};
|
|
1213
|
-
|
|
1214
1088
|
return (
|
|
1215
1089
|
<Popover.Root open={open} onOpenChange={handleOpenChange}>
|
|
1216
1090
|
<Popover.Trigger asChild>
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
/>
|
|
1227
|
-
)}
|
|
1091
|
+
<Button
|
|
1092
|
+
type="button"
|
|
1093
|
+
variant="outline"
|
|
1094
|
+
size="icon"
|
|
1095
|
+
disabled={disabled}
|
|
1096
|
+
aria-label={ariaLabel}
|
|
1097
|
+
className={className}
|
|
1098
|
+
leftIcon={<Calendar className="size-6" />}
|
|
1099
|
+
/>
|
|
1228
1100
|
</Popover.Trigger>
|
|
1229
1101
|
|
|
1230
1102
|
<Popover.Portal>
|
|
@@ -1240,18 +1112,38 @@ export function DateFilterMenu({
|
|
|
1240
1112
|
sideOffset={8}
|
|
1241
1113
|
align={align}
|
|
1242
1114
|
>
|
|
1243
|
-
{/* Sort
|
|
1244
|
-
<
|
|
1115
|
+
{/* Sort direction selector */}
|
|
1116
|
+
<Select
|
|
1117
|
+
value={draftSort}
|
|
1118
|
+
onValueChange={(value) => setDraftSort(value as SortDirection)}
|
|
1119
|
+
>
|
|
1120
|
+
<SelectTrigger className="gap-2" aria-label="Sort direction">
|
|
1121
|
+
{/* Wrapper is a <div> (not <span>) so SelectTrigger's
|
|
1122
|
+
`[&>span]:line-clamp-1` rule doesn't force it to a vertical
|
|
1123
|
+
-webkit-box and stack the icon/label into a column. */}
|
|
1124
|
+
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
1125
|
+
<ArrowUpDown className="size-6 shrink-0 text-ods-text-secondary" />
|
|
1126
|
+
<span className="truncate">
|
|
1127
|
+
<SelectValue />
|
|
1128
|
+
</span>
|
|
1129
|
+
</div>
|
|
1130
|
+
</SelectTrigger>
|
|
1131
|
+
<SelectContent>
|
|
1132
|
+
<SelectItem value="asc">{ascLabel}</SelectItem>
|
|
1133
|
+
<SelectItem value="desc">{descLabel}</SelectItem>
|
|
1134
|
+
</SelectContent>
|
|
1135
|
+
</Select>
|
|
1136
|
+
|
|
1137
|
+
{/* Calendar */}
|
|
1138
|
+
<DatePickerCalendar
|
|
1245
1139
|
mode={mode}
|
|
1246
|
-
sort={draftSort}
|
|
1247
|
-
onSortChange={setDraftSort}
|
|
1248
1140
|
selected={draftSelected}
|
|
1249
1141
|
onSelect={setDraftSelected}
|
|
1142
|
+
numberOfMonths={1}
|
|
1250
1143
|
fromDate={fromDate}
|
|
1251
1144
|
toDate={toDate}
|
|
1252
1145
|
locale={locale}
|
|
1253
|
-
|
|
1254
|
-
descLabel={descLabel}
|
|
1146
|
+
fluid
|
|
1255
1147
|
/>
|
|
1256
1148
|
|
|
1257
1149
|
{/* Actions */}
|
|
@@ -1260,9 +1152,9 @@ export function DateFilterMenu({
|
|
|
1260
1152
|
type="button"
|
|
1261
1153
|
variant="outline"
|
|
1262
1154
|
fullWidth
|
|
1263
|
-
onClick={
|
|
1155
|
+
onClick={handleClose}
|
|
1264
1156
|
>
|
|
1265
|
-
|
|
1157
|
+
Close
|
|
1266
1158
|
</Button>
|
|
1267
1159
|
<Button
|
|
1268
1160
|
type="button"
|
|
@@ -6,9 +6,7 @@ import { cn } from '../../utils/cn'
|
|
|
6
6
|
import { Filter02Icon } from '../icons-v2-generated/sort-and-filter/filter-02-icon'
|
|
7
7
|
import { Button } from './button'
|
|
8
8
|
import { FilterCheckboxItem } from './filter-checkbox-item'
|
|
9
|
-
import { DateFilterPanel, type DateFilterResult, type DateRange } from './date-picker'
|
|
10
9
|
import { ModalV2, ModalV2Content, ModalV2Footer, ModalV2Header, ModalV2Title } from './modal-v2'
|
|
11
|
-
import { ScrollFadeOverlay, useScrollFade } from './scroll-fade'
|
|
12
10
|
import { Skeleton } from './skeleton'
|
|
13
11
|
import { SortColumnItem, type SortConfig, type SortDirection } from './sort-column-item'
|
|
14
12
|
import { TagKeyValueFilter, type TagKeyConfig } from './tag-key-value-filter'
|
|
@@ -53,20 +51,6 @@ export interface FilterModalProps {
|
|
|
53
51
|
tagFilterTitle?: string
|
|
54
52
|
/** Show skeleton loading state */
|
|
55
53
|
isLoading?: boolean
|
|
56
|
-
/** Date sort + range filter rendered as the last section. Drafted with the
|
|
57
|
-
* other filters and committed via `onChange` on Apply (defaults on Reset). */
|
|
58
|
-
dateFilter?: {
|
|
59
|
-
/** Section title. Defaults to 'Date'. */
|
|
60
|
-
title?: string
|
|
61
|
-
/** Applied sort direction. Defaults to `defaultSort`. */
|
|
62
|
-
sort?: SortDirection
|
|
63
|
-
/** Baseline sort direction restored by Reset. Defaults to 'desc'. */
|
|
64
|
-
defaultSort?: SortDirection
|
|
65
|
-
/** Applied date range. */
|
|
66
|
-
range?: DateRange
|
|
67
|
-
/** Fired on Apply with the drafted values, and on Reset with the defaults. */
|
|
68
|
-
onChange: (result: DateFilterResult) => void
|
|
69
|
-
}
|
|
70
54
|
/** Text shown in empty state title */
|
|
71
55
|
emptyStateTitle?: string
|
|
72
56
|
/** Text shown in empty state description */
|
|
@@ -90,7 +74,6 @@ export function FilterModal({
|
|
|
90
74
|
selectedTags,
|
|
91
75
|
onTagsChange,
|
|
92
76
|
tagFilterTitle,
|
|
93
|
-
dateFilter,
|
|
94
77
|
isLoading = false,
|
|
95
78
|
emptyStateTitle = 'No filters available',
|
|
96
79
|
emptyStateDescription = 'There are no filter options to display at the moment',
|
|
@@ -100,20 +83,10 @@ export function FilterModal({
|
|
|
100
83
|
})
|
|
101
84
|
const [pendingTags, setPendingTags] = useState<string[]>(selectedTags ?? [])
|
|
102
85
|
|
|
103
|
-
const dateDefaultSort: SortDirection = dateFilter?.defaultSort ?? 'desc'
|
|
104
|
-
const [draftDateSort, setDraftDateSort] = useState<SortDirection>(dateFilter?.sort ?? dateDefaultSort)
|
|
105
|
-
const [draftDateRange, setDraftDateRange] = useState<DateRange | undefined>(dateFilter?.range)
|
|
106
|
-
|
|
107
|
-
// Scroll shadows: bottom fade by default while content continues below,
|
|
108
|
-
// top fade once scrolled (same pattern as the chat lists).
|
|
109
|
-
const { scrollRef, fadeTop, fadeBottom, update: updateFade } = useScrollFade<HTMLDivElement>()
|
|
110
|
-
|
|
111
86
|
useEffect(() => {
|
|
112
87
|
if (isOpen) {
|
|
113
88
|
setSelectedFilters({ ...currentFilters })
|
|
114
89
|
setPendingTags(selectedTags ?? [])
|
|
115
|
-
setDraftDateSort(dateFilter?.sort ?? dateDefaultSort)
|
|
116
|
-
setDraftDateRange(dateFilter?.range)
|
|
117
90
|
}
|
|
118
91
|
}, [isOpen, JSON.stringify(currentFilters), JSON.stringify(selectedTags)])
|
|
119
92
|
|
|
@@ -136,14 +109,12 @@ export function FilterModal({
|
|
|
136
109
|
const handleReset = () => {
|
|
137
110
|
onFilterChange({})
|
|
138
111
|
onTagsChange?.([])
|
|
139
|
-
dateFilter?.onChange({ sort: dateDefaultSort, range: undefined })
|
|
140
112
|
onClose()
|
|
141
113
|
}
|
|
142
114
|
|
|
143
115
|
const handleApply = () => {
|
|
144
116
|
onFilterChange(selectedFilters)
|
|
145
117
|
onTagsChange?.(pendingTags)
|
|
146
|
-
dateFilter?.onChange({ sort: draftDateSort, range: draftDateRange })
|
|
147
118
|
onClose()
|
|
148
119
|
}
|
|
149
120
|
|
|
@@ -154,7 +125,7 @@ export function FilterModal({
|
|
|
154
125
|
const hasSort = !!sortConfig && sortConfig.columns.length > 0
|
|
155
126
|
const hasFilterGroups = filterGroups.length > 0
|
|
156
127
|
const hasTagFilter = !!tagFilterKeys && tagFilterKeys.length > 0 && !!onTagsChange
|
|
157
|
-
const isEmpty = !isLoading && !hasSort && !hasFilterGroups && !hasTagFilter
|
|
128
|
+
const isEmpty = !isLoading && !hasSort && !hasFilterGroups && !hasTagFilter
|
|
158
129
|
|
|
159
130
|
return (
|
|
160
131
|
<ModalV2
|
|
@@ -172,12 +143,7 @@ export function FilterModal({
|
|
|
172
143
|
!isEmpty && 'md:bg-ods-bg md:border md:border-ods-border md:rounded-md md:px-3',
|
|
173
144
|
)}
|
|
174
145
|
>
|
|
175
|
-
<div className="
|
|
176
|
-
<div
|
|
177
|
-
ref={scrollRef}
|
|
178
|
-
onScroll={updateFade}
|
|
179
|
-
className="flex-1 min-h-0 overflow-y-auto flex flex-col gap-4 md:py-3"
|
|
180
|
-
>
|
|
146
|
+
<div className="flex-1 min-h-0 overflow-y-auto flex flex-col gap-4 md:py-3">
|
|
181
147
|
{isLoading ? (
|
|
182
148
|
<>
|
|
183
149
|
{[0, 1].map((group) => (
|
|
@@ -263,31 +229,10 @@ export function FilterModal({
|
|
|
263
229
|
onTagsChange={setPendingTags}
|
|
264
230
|
keysTitle={tagFilterTitle}
|
|
265
231
|
/>
|
|
266
|
-
)}
|
|
267
|
-
{/* Date sort + range filter */}
|
|
268
|
-
{dateFilter && (
|
|
269
|
-
<div className="flex flex-col gap-1">
|
|
270
|
-
<span className="text-h5 text-ods-text-secondary">
|
|
271
|
-
{dateFilter.title ?? 'Date'}
|
|
272
|
-
</span>
|
|
273
|
-
<DateFilterPanel
|
|
274
|
-
mode="range"
|
|
275
|
-
sort={draftDateSort}
|
|
276
|
-
onSortChange={setDraftDateSort}
|
|
277
|
-
selected={draftDateRange}
|
|
278
|
-
onSelect={(value) => setDraftDateRange(value as DateRange | undefined)}
|
|
279
|
-
className="gap-[var(--spacing-system-xxs)]"
|
|
280
|
-
/>
|
|
281
|
-
</div>
|
|
282
232
|
)}
|
|
283
233
|
</>
|
|
284
234
|
)}
|
|
285
235
|
</div>
|
|
286
|
-
|
|
287
|
-
{/* Scroll shadows over the filter list edges */}
|
|
288
|
-
<ScrollFadeOverlay edge="top" visible={fadeTop} />
|
|
289
|
-
<ScrollFadeOverlay edge="bottom" visible={fadeBottom} />
|
|
290
|
-
</div>
|
|
291
236
|
</ModalV2Content>
|
|
292
237
|
|
|
293
238
|
<ModalV2Footer>
|