@flamingo-stack/openframe-frontend-core 0.0.263 → 0.0.264

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 (68) hide show
  1. package/dist/{chunk-C3FAOQAH.cjs → chunk-3LWUHLPK.cjs} +28 -28
  2. package/dist/{chunk-C3FAOQAH.cjs.map → chunk-3LWUHLPK.cjs.map} +1 -1
  3. package/dist/{chunk-U7UDESAU.js → chunk-6ZEKKAAL.js} +2 -2
  4. package/dist/{chunk-YC357BPF.js → chunk-JOUI6TQY.js} +3 -3
  5. package/dist/{chunk-2XUWR7AH.cjs → chunk-P67D545X.cjs} +11 -11
  6. package/dist/{chunk-2XUWR7AH.cjs.map → chunk-P67D545X.cjs.map} +1 -1
  7. package/dist/{chunk-3DC6MHW6.cjs → chunk-QLWBBCI5.cjs} +491 -391
  8. package/dist/chunk-QLWBBCI5.cjs.map +1 -0
  9. package/dist/{chunk-36HLBLDK.cjs → chunk-SIYHXLFH.cjs} +462 -454
  10. package/dist/chunk-SIYHXLFH.cjs.map +1 -0
  11. package/dist/{chunk-ALEKX56R.js → chunk-SSUUMPAQ.js} +2 -2
  12. package/dist/{chunk-6AIXHYBZ.js → chunk-VYTU7JZR.js} +64 -56
  13. package/dist/chunk-VYTU7JZR.js.map +1 -0
  14. package/dist/{chunk-6GBA6YJN.js → chunk-ZMFT5XOB.js} +576 -476
  15. package/dist/chunk-ZMFT5XOB.js.map +1 -0
  16. package/dist/{chunk-XKA7UFED.cjs → chunk-ZOOTBYZO.cjs} +30 -30
  17. package/dist/{chunk-XKA7UFED.cjs.map → chunk-ZOOTBYZO.cjs.map} +1 -1
  18. package/dist/components/chat/index.cjs +3 -3
  19. package/dist/components/chat/index.js +2 -2
  20. package/dist/components/contact/index.cjs +4 -4
  21. package/dist/components/contact/index.js +3 -3
  22. package/dist/components/features/index.cjs +3 -3
  23. package/dist/components/features/index.js +2 -2
  24. package/dist/components/features/notifications/notification-drawer.d.ts.map +1 -1
  25. package/dist/components/features/notifications/notifications-context.d.ts +9 -1
  26. package/dist/components/features/notifications/notifications-context.d.ts.map +1 -1
  27. package/dist/components/index.cjs +50 -50
  28. package/dist/components/index.js +4 -4
  29. package/dist/components/navigation/index.cjs +3 -3
  30. package/dist/components/navigation/index.js +2 -2
  31. package/dist/components/navigation/sliding-sidebar.d.ts.map +1 -1
  32. package/dist/components/related-content/index.cjs +4 -4
  33. package/dist/components/related-content/index.js +3 -3
  34. package/dist/components/tickets/index.cjs +58 -58
  35. package/dist/components/tickets/index.js +4 -4
  36. package/dist/components/ui/drawer.d.ts +5 -0
  37. package/dist/components/ui/drawer.d.ts.map +1 -1
  38. package/dist/components/ui/index.cjs +3 -3
  39. package/dist/components/ui/index.js +2 -2
  40. package/dist/hooks/index.cjs +6 -2
  41. package/dist/hooks/index.cjs.map +1 -1
  42. package/dist/hooks/index.js +5 -1
  43. package/dist/hooks/ui/index.d.ts +6 -4
  44. package/dist/hooks/ui/index.d.ts.map +1 -1
  45. package/dist/hooks/ui/use-header-height.d.ts +8 -0
  46. package/dist/hooks/ui/use-header-height.d.ts.map +1 -0
  47. package/dist/hooks/ui/use-horizontal-scrollbar.d.ts.map +1 -1
  48. package/dist/hooks/ui/use-notification-permission.d.ts +14 -0
  49. package/dist/hooks/ui/use-notification-permission.d.ts.map +1 -0
  50. package/dist/index.cjs +7 -3
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.js +6 -2
  53. package/package.json +1 -1
  54. package/src/components/features/notifications/notification-drawer.tsx +51 -2
  55. package/src/components/features/notifications/notifications-context.tsx +26 -0
  56. package/src/components/navigation/sliding-sidebar.tsx +3 -82
  57. package/src/components/ui/drawer.tsx +17 -6
  58. package/src/hooks/ui/index.ts +6 -4
  59. package/src/hooks/ui/use-header-height.ts +55 -0
  60. package/src/hooks/ui/use-horizontal-scrollbar.ts +19 -2
  61. package/src/hooks/ui/use-notification-permission.ts +64 -0
  62. package/dist/chunk-36HLBLDK.cjs.map +0 -1
  63. package/dist/chunk-3DC6MHW6.cjs.map +0 -1
  64. package/dist/chunk-6AIXHYBZ.js.map +0 -1
  65. package/dist/chunk-6GBA6YJN.js.map +0 -1
  66. /package/dist/{chunk-U7UDESAU.js.map → chunk-6ZEKKAAL.js.map} +0 -0
  67. /package/dist/{chunk-YC357BPF.js.map → chunk-JOUI6TQY.js.map} +0 -0
  68. /package/dist/{chunk-ALEKX56R.js.map → chunk-SSUUMPAQ.js.map} +0 -0
@@ -3,6 +3,7 @@
3
3
  import React, { useState, useEffect } from 'react'
4
4
  import { motion, AnimatePresence } from 'framer-motion'
5
5
  import { cn } from '../../utils'
6
+ import { useHeaderHeight } from '../../hooks/ui'
6
7
  import { SlidingSidebarConfig, NavigationItem } from '../../types/navigation'
7
8
  import { Button } from '../ui/button'
8
9
 
@@ -13,89 +14,9 @@ export interface SlidingSidebarProps {
13
14
  export function SlidingSidebar({ config }: SlidingSidebarProps) {
14
15
  const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set())
15
16
  const [mounted, setMounted] = useState(false)
16
- const [headerHeight, setHeaderHeight] = useState(64) // Default to 64px
17
+ const headerHeight = useHeaderHeight()
17
18
 
18
- useEffect(() => {
19
- setMounted(true)
20
-
21
- // Function to calculate total header height
22
- const calculateHeaderHeight = () => {
23
- let totalHeight = 0
24
-
25
- // Find and measure the header
26
- const header = document.querySelector('header')
27
- if (header) {
28
- totalHeight += header.offsetHeight
29
- }
30
-
31
- // Find and measure the announcement bar (if exists)
32
- const announcementBar = document.querySelector('[data-announcement-bar]')
33
- if (announcementBar && announcementBar instanceof HTMLElement) {
34
- totalHeight += announcementBar.offsetHeight
35
- }
36
-
37
- // Update the header height if we found elements
38
- if (totalHeight > 0) {
39
- setHeaderHeight(totalHeight)
40
- } else {
41
- // If no header found, default to 64px
42
- setHeaderHeight(64)
43
- }
44
- }
45
-
46
- // Calculate initial height with a small delay to ensure elements are rendered
47
- setTimeout(calculateHeaderHeight, 100)
48
-
49
- // Also calculate immediately in case elements are already there
50
- calculateHeaderHeight()
51
-
52
- // Recalculate on window resize
53
- window.addEventListener('resize', calculateHeaderHeight)
54
-
55
- // Use ResizeObserver to detect header size changes
56
- const resizeObserver = new ResizeObserver(calculateHeaderHeight)
57
-
58
- // Observe header
59
- const header = document.querySelector('header')
60
- if (header) {
61
- resizeObserver.observe(header)
62
- }
63
-
64
- // Observe announcement bar
65
- const announcementBar = document.querySelector('[data-announcement-bar]')
66
- if (announcementBar) {
67
- resizeObserver.observe(announcementBar)
68
- }
69
-
70
- // Use MutationObserver to detect when announcement bar is added/removed
71
- const mutationObserver = new MutationObserver((mutations) => {
72
- // Check if announcement bar was added or removed
73
- for (const mutation of mutations) {
74
- if (mutation.type === 'childList') {
75
- // Recalculate height when DOM changes
76
- calculateHeaderHeight()
77
-
78
- // If announcement bar was added, start observing it
79
- const newAnnouncementBar = document.querySelector('[data-announcement-bar]')
80
- if (newAnnouncementBar) {
81
- resizeObserver.observe(newAnnouncementBar)
82
- }
83
- }
84
- }
85
- })
86
-
87
- // Observe the body for changes (announcement bar might be added/removed)
88
- mutationObserver.observe(document.body, {
89
- childList: true,
90
- subtree: true
91
- })
92
-
93
- return () => {
94
- window.removeEventListener('resize', calculateHeaderHeight)
95
- resizeObserver.disconnect()
96
- mutationObserver.disconnect()
97
- }
98
- }, [])
19
+ useEffect(() => { setMounted(true) }, [])
99
20
 
100
21
  const toggleExpanded = (itemId: string) => {
101
22
  const newExpanded = new Set(expandedItems)
@@ -6,6 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority"
6
6
  import { X } from "lucide-react"
7
7
 
8
8
  import { cn } from "../../utils/cn"
9
+ import { useHeaderHeight } from "../../hooks/ui/use-header-height"
9
10
 
10
11
  const Drawer = DialogPrimitive.Root
11
12
 
@@ -354,6 +355,9 @@ interface DrawerContentProps
354
355
  panelStyle?: React.CSSProperties
355
356
  /** Optional className applied to the inner panel. (Alias for `className`.) */
356
357
  panelClassName?: string
358
+ /** Offset the drawer's top edge below the app header + announcement bar.
359
+ * Measured live via ResizeObserver so it tracks height changes. */
360
+ offsetHeader?: boolean
357
361
  }
358
362
 
359
363
  const DrawerContent = React.forwardRef<
@@ -376,6 +380,7 @@ const DrawerContent = React.forwardRef<
376
380
  style,
377
381
  panelStyle,
378
382
  panelClassName,
383
+ offsetHeader = false,
379
384
  children,
380
385
  ...props
381
386
  },
@@ -385,6 +390,8 @@ const DrawerContent = React.forwardRef<
385
390
  const isHorizontal = HORIZONTAL_SIDES.has(resolvedSide)
386
391
  const initialSize = defaultSize ?? (isHorizontal ? 560 : 480)
387
392
 
393
+ const headerHeight = useHeaderHeight()
394
+
388
395
  const [isMobile, setIsMobile] = React.useState(false)
389
396
  React.useEffect(() => {
390
397
  if (typeof window === "undefined") return
@@ -417,7 +424,7 @@ const DrawerContent = React.forwardRef<
417
424
  <DialogPrimitive.Content
418
425
  ref={ref}
419
426
  className={cn(drawerVariants({ side, flush }))}
420
- style={style}
427
+ style={{ ...(offsetHeader ? { top: headerHeight } : {}), ...style }}
421
428
  {...props}
422
429
  >
423
430
  {/* Resize handle is a sibling of the panel — outside the panel's
@@ -468,12 +475,14 @@ interface DrawerTitleProps
468
475
  extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> {
469
476
  /** Optional header actions rendered between the title and the close button. */
470
477
  actions?: React.ReactNode
478
+ /** Hide the close (X) button. */
479
+ hideClose?: boolean
471
480
  }
472
481
 
473
482
  const DrawerTitle = React.forwardRef<
474
483
  React.ComponentRef<typeof DialogPrimitive.Title>,
475
484
  DrawerTitleProps
476
- >(({ className, children, actions, ...props }, ref) => (
485
+ >(({ className, children, actions, hideClose, ...props }, ref) => (
477
486
  <div className="flex items-start gap-4">
478
487
  <DialogPrimitive.Title
479
488
  ref={ref}
@@ -486,10 +495,12 @@ const DrawerTitle = React.forwardRef<
486
495
  {children}
487
496
  </DialogPrimitive.Title>
488
497
  {actions}
489
- <DialogPrimitive.Close className="shrink-0 rounded-sm text-ods-text-secondary transition-colors hover:text-ods-text-primary outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0">
490
- <X className="size-6" />
491
- <span className="sr-only">Close</span>
492
- </DialogPrimitive.Close>
498
+ {!hideClose && (
499
+ <DialogPrimitive.Close className="shrink-0 rounded-sm text-ods-text-secondary transition-colors hover:text-ods-text-primary outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0">
500
+ <X className="size-6" />
501
+ <span className="sr-only">Close</span>
502
+ </DialogPrimitive.Close>
503
+ )}
493
504
  </div>
494
505
  ))
495
506
  DrawerTitle.displayName = "DrawerTitle"
@@ -1,13 +1,15 @@
1
1
  // UI Hooks exports
2
+ export * from './use-auto-limit-tags'
2
3
  export * from './use-debounce'
4
+ export * from './use-header-height'
5
+ export * from './use-horizontal-scrollbar'
6
+ export * from './use-image-edge-color'
3
7
  export * from './use-local-storage'
4
8
  export * from './use-media-query'
5
9
  export * from './use-memoized-callback'
10
+ export * from './use-notification-permission'
6
11
  export * from './use-onboarding-state'
12
+ export * from './use-search'
7
13
  export * from './use-table-pagination'
8
14
  export * from './use-throttle'
9
15
  export * from './use-window-size'
10
- export * from './use-horizontal-scrollbar'
11
- export * from './use-image-edge-color'
12
- export * from './use-search'
13
- export * from './use-auto-limit-tags'
@@ -0,0 +1,55 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useState } from 'react'
4
+
5
+ /**
6
+ * Returns the combined height (px) of the sticky page header and announcement
7
+ * bar (if present), updated live via ResizeObserver / MutationObserver.
8
+ * Useful for offsetting fixed/absolute-positioned panels so they don't
9
+ * overlap the header.
10
+ */
11
+ export function useHeaderHeight(defaultHeight = 64): number {
12
+ const [height, setHeight] = useState(defaultHeight)
13
+
14
+ useEffect(() => {
15
+ const measure = () => {
16
+ let total = 0
17
+ const header = document.querySelector('header')
18
+ if (header) total += header.offsetHeight
19
+ const bar = document.querySelector('[data-announcement-bar]')
20
+ if (bar instanceof HTMLElement) total += bar.offsetHeight
21
+ setHeight(total > 0 ? total : defaultHeight)
22
+ }
23
+
24
+ measure()
25
+
26
+ const resizeObserver = new ResizeObserver(measure)
27
+ const header = document.querySelector('header')
28
+ if (header) resizeObserver.observe(header)
29
+ const bar = document.querySelector('[data-announcement-bar]')
30
+ if (bar) resizeObserver.observe(bar)
31
+
32
+ const mutationObserver = new MutationObserver((mutations) => {
33
+ for (const mutation of mutations) {
34
+ if (mutation.type === 'childList' || mutation.type === 'attributes') {
35
+ measure()
36
+ const newBar = document.querySelector('[data-announcement-bar]')
37
+ if (newBar) resizeObserver.observe(newBar)
38
+ }
39
+ }
40
+ })
41
+ mutationObserver.observe(document.body, {
42
+ childList: true,
43
+ subtree: true,
44
+ attributes: true,
45
+ attributeFilter: ['data-announcement-bar'],
46
+ })
47
+
48
+ return () => {
49
+ resizeObserver.disconnect()
50
+ mutationObserver.disconnect()
51
+ }
52
+ }, [defaultHeight])
53
+
54
+ return height
55
+ }
@@ -17,6 +17,7 @@ export function useHorizontalScrollbar() {
17
17
  const trackRef = useRef<HTMLDivElement>(null)
18
18
  const thumbRef = useRef<HTMLDivElement>(null)
19
19
  const roRef = useRef<ResizeObserver | null>(null)
20
+ const moRef = useRef<MutationObserver | null>(null)
20
21
  const [thumbRatio, setThumbRatio] = useState(0)
21
22
 
22
23
  // Edge-fade state
@@ -94,6 +95,10 @@ export function useHorizontalScrollbar() {
94
95
  roRef.current.disconnect()
95
96
  roRef.current = null
96
97
  }
98
+ if (moRef.current) {
99
+ moRef.current.disconnect()
100
+ moRef.current = null
101
+ }
97
102
  if (rafIdRef.current) {
98
103
  cancelAnimationFrame(rafIdRef.current)
99
104
  rafIdRef.current = 0
@@ -105,8 +110,20 @@ export function useHorizontalScrollbar() {
105
110
  const ratio = node.clientWidth / node.scrollWidth
106
111
  setThumbRatio(ratio >= 1 ? 0 : ratio)
107
112
 
108
- roRef.current = new ResizeObserver(measure)
109
- roRef.current.observe(node)
113
+ const ro = new ResizeObserver(measure)
114
+ roRef.current = ro
115
+ const observeAll = () => {
116
+ ro.disconnect()
117
+ ro.observe(node)
118
+ for (const child of Array.from(node.children)) ro.observe(child)
119
+ }
120
+ observeAll()
121
+
122
+ moRef.current = new MutationObserver(() => {
123
+ observeAll()
124
+ measure()
125
+ })
126
+ moRef.current.observe(node, { childList: true })
110
127
 
111
128
  requestAnimationFrame(() => {
112
129
  syncThumbToDOM()
@@ -0,0 +1,64 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useEffect, useState } from 'react'
4
+
5
+ export interface UseNotificationPermissionResult {
6
+ /** Whether the page-context Notification API exists in this browser (false during SSR and on iOS Safari). */
7
+ supported: boolean
8
+ permission: NotificationPermission
9
+ /** Prompt the user for permission. Must be called from a user gesture or browsers will auto-deny. */
10
+ request: () => Promise<NotificationPermission>
11
+ }
12
+
13
+ /**
14
+ * Tracks the browser's desktop-notification permission, staying in sync when
15
+ * the user changes the site setting externally (Permissions API change event
16
+ * where available, visibility/focus re-read as the Safari fallback).
17
+ */
18
+ export function useNotificationPermission(): UseNotificationPermissionResult {
19
+ const [supported, setSupported] = useState(false)
20
+ const [permission, setPermission] = useState<NotificationPermission>('default')
21
+
22
+ useEffect(() => {
23
+ if (typeof window === 'undefined' || !('Notification' in window)) return
24
+ setSupported(true)
25
+ const sync = () => setPermission(Notification.permission)
26
+ sync()
27
+
28
+ // The query resolves async; without the flag it would attach the listener after unmount.
29
+ let cancelled = false
30
+ let status: PermissionStatus | undefined
31
+ navigator.permissions
32
+ ?.query({ name: 'notifications' as PermissionName })
33
+ .then((s) => {
34
+ if (cancelled) return
35
+ status = s
36
+ s.addEventListener('change', sync)
37
+ })
38
+ .catch(() => undefined)
39
+
40
+ document.addEventListener('visibilitychange', sync)
41
+ window.addEventListener('focus', sync)
42
+ return () => {
43
+ cancelled = true
44
+ status?.removeEventListener('change', sync)
45
+ document.removeEventListener('visibilitychange', sync)
46
+ window.removeEventListener('focus', sync)
47
+ }
48
+ }, [])
49
+
50
+ const request = useCallback(async () => {
51
+ if (typeof window === 'undefined' || !('Notification' in window)) {
52
+ return 'denied' as NotificationPermission
53
+ }
54
+ // Older Safari only implements the callback form; resolving from both is harmless.
55
+ const result = await new Promise<NotificationPermission>((resolve) => {
56
+ const maybePromise = Notification.requestPermission(resolve)
57
+ if (maybePromise && typeof maybePromise.then === 'function') void maybePromise.then(resolve)
58
+ })
59
+ setPermission(Notification.permission)
60
+ return result
61
+ }, [])
62
+
63
+ return { supported, permission, request }
64
+ }