@flamingo-stack/openframe-frontend-core 0.0.838 → 0.0.840
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/build/export-map.json +1 -1
- package/dist/{chunk-JFR3OFE6.cjs → chunk-275AQ6LC.cjs} +6 -6
- package/dist/{chunk-5BM7X4GA.cjs → chunk-5GBLXY5F.cjs} +2 -2
- package/dist/{chunk-5L3UKDC6.cjs → chunk-6LDL3UZE.cjs} +9 -9
- package/dist/{chunk-SWMUW3RR.cjs → chunk-744JXTDP.cjs} +19 -19
- package/dist/{chunk-46LATWQR.cjs → chunk-7WKRMJI6.cjs} +5 -5
- package/dist/{chunk-2X5LYDNA.cjs → chunk-AT6D7BAZ.cjs} +5 -5
- package/dist/{chunk-C4OQIYGW.cjs → chunk-CCRXBFNL.cjs} +8 -8
- package/dist/{chunk-LC65AC6M.cjs → chunk-COWMXUHN.cjs} +2 -0
- package/dist/{chunk-5E4YEHHO.cjs → chunk-DGY5KEPY.cjs} +55 -55
- package/dist/{chunk-B4THEQOW.cjs → chunk-DYDQLS2R.cjs} +744 -561
- package/dist/{chunk-LWMWY3JO.cjs → chunk-E7M54JWW.cjs} +14 -14
- package/dist/{chunk-Z7A7IZWL.cjs → chunk-EV6BPF47.cjs} +236 -210
- package/dist/{chunk-7XRFAHJF.cjs → chunk-FHI4I3TQ.cjs} +9 -9
- package/dist/{chunk-ZABZIM2Y.cjs → chunk-FRL2ZN4O.cjs} +3 -3
- package/dist/{chunk-7Z536SPR.cjs → chunk-FW7RKWPY.cjs} +15 -1
- package/dist/{chunk-U4MJ2Z4V.cjs → chunk-GMK4JGC2.cjs} +46 -46
- package/dist/{chunk-NV5AHL4G.cjs → chunk-GUKFMMDZ.cjs} +3 -3
- package/dist/{chunk-JI34QKPJ.cjs → chunk-HXTZZE3R.cjs} +253 -131
- package/dist/{chunk-MMPMQUQM.cjs → chunk-IELPVUPR.cjs} +18 -18
- package/dist/{chunk-64PF3UC7.cjs → chunk-INBFKUZA.cjs} +17 -17
- package/dist/{chunk-APOMNC7H.cjs → chunk-IUDBG3HJ.cjs} +3 -3
- package/dist/{chunk-4JHO2NVI.cjs → chunk-J2IXYCEH.cjs} +26 -10
- package/dist/{chunk-V322TUBY.cjs → chunk-JM2XA7VY.cjs} +28 -28
- package/dist/{chunk-GPXKG34G.cjs → chunk-JUEAZCD6.cjs} +46 -46
- package/dist/{chunk-2QAUQPGC.cjs → chunk-MYAPG3FQ.cjs} +21 -21
- package/dist/{chunk-GRHF5SWL.cjs → chunk-NFNSPLGC.cjs} +3 -3
- package/dist/{chunk-4TUHHKLR.cjs → chunk-Q6WUHYOG.cjs} +72 -72
- package/dist/{chunk-H3WTYAVR.cjs → chunk-RSJYTQEA.cjs} +30 -30
- package/dist/{chunk-MG3HJIGG.cjs → chunk-UAI4VMEF.cjs} +4 -4
- package/dist/{chunk-S5RMRQKT.cjs → chunk-UZPYYHF5.cjs} +18 -18
- package/dist/{chunk-TMD55O5U.cjs → chunk-VBH2RCFM.cjs} +73 -73
- package/dist/{chunk-FNNDGOR3.cjs → chunk-WCK5T3UD.cjs} +43 -43
- package/dist/{chunk-TRZEFT2N.cjs → chunk-WI7EXAZT.cjs} +9 -5
- package/dist/{chunk-7QNGILIY.cjs → chunk-Z35PL3UR.cjs} +32 -32
- package/dist/{chunk-ENK75WN4.cjs → chunk-ZAO5SYBJ.cjs} +4 -4
- package/dist/{cloud-tenants-GLPDB2F5.cjs → cloud-tenants-PV63RZ73.cjs} +19 -19
- package/dist/components/case-studies/index.cjs +33 -33
- package/dist/components/chat/chat-nav-sidebar.d.ts.map +1 -1
- package/dist/components/chat/chat-nav-sidebar.js +17 -3
- package/dist/components/chat/index.cjs +13 -13
- package/dist/components/contact/index.cjs +20 -20
- package/dist/components/docs/index.cjs +23 -23
- package/dist/components/embeds/index.cjs +20 -20
- package/dist/components/faq/index.cjs +21 -21
- package/dist/components/features/command-box.d.ts.map +1 -1
- package/dist/components/features/command-box.js +4 -23
- package/dist/components/features/index.cjs +29 -19
- package/dist/components/features/software/index.d.ts +1 -0
- package/dist/components/features/software/index.d.ts.map +1 -1
- package/dist/components/features/software/index.js +12 -0
- package/dist/components/features/software/software-action-form.d.ts +9 -1
- package/dist/components/features/software/software-action-form.d.ts.map +1 -1
- package/dist/components/features/software/software-action-form.js +18 -1
- package/dist/components/features/software/update-settings-fields.d.ts +43 -0
- package/dist/components/features/software/update-settings-fields.d.ts.map +1 -0
- package/dist/components/features/software/update-settings-fields.js +202 -0
- package/dist/components/help-center-pages/download-apps-page.d.ts +1 -1
- package/dist/components/help-center-pages/download-apps-page.d.ts.map +1 -1
- package/dist/components/help-center-pages/download-apps-page.js +2 -3
- package/dist/components/help-center-pages/index.cjs +122 -123
- package/dist/components/index.cjs +86 -76
- package/dist/components/meeting-scheduler/index.cjs +82 -82
- package/dist/components/navigation/account-popover.d.ts.map +1 -1
- package/dist/components/navigation/account-popover.js +26 -2
- package/dist/components/navigation/app-layout-side-panel.d.ts.map +1 -1
- package/dist/components/navigation/app-layout-side-panel.js +2 -9
- package/dist/components/navigation/index.cjs +22 -22
- package/dist/components/navigation/navigation-sidebar-grouped.d.ts.map +1 -1
- package/dist/components/navigation/navigation-sidebar-grouped.js +30 -6
- package/dist/components/navigation/navigation-sidebar-item.d.ts.map +1 -1
- package/dist/components/navigation/navigation-sidebar-item.js +14 -1
- package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
- package/dist/components/navigation/navigation-sidebar.js +72 -3
- package/dist/components/onboarding-guides/index.cjs +22 -22
- package/dist/components/product-demo/fixtures/software-update.d.ts +2 -0
- package/dist/components/product-demo/fixtures/software-update.d.ts.map +1 -1
- package/dist/components/product-demo/fixtures/software-update.js +3 -2
- package/dist/components/product-demo/index.cjs +30 -30
- package/dist/components/related-content/index.cjs +8 -8
- package/dist/components/tickets/index.cjs +24 -24
- package/dist/components/ui/index.cjs +19 -19
- package/dist/components/ui/panel-resize-handle.d.ts +27 -6
- package/dist/components/ui/panel-resize-handle.d.ts.map +1 -1
- package/dist/components/ui/panel-resize-handle.js +27 -3
- package/dist/{devices-GHPW5WCO.cjs → devices-VECG5JNC.cjs} +20 -20
- package/dist/{embeddable-chat-RBC7F4TL.cjs → embeddable-chat-VRYAQQF6.cjs} +90 -90
- package/dist/hooks/index.cjs +11 -7
- package/dist/hooks/ui/index.d.ts +1 -0
- package/dist/hooks/ui/index.d.ts.map +1 -1
- package/dist/hooks/ui/index.js +1 -0
- package/dist/hooks/ui/use-clipped-label-tooltip.d.ts +14 -0
- package/dist/hooks/ui/use-clipped-label-tooltip.d.ts.map +1 -0
- package/dist/hooks/ui/use-clipped-label-tooltip.js +13 -0
- package/dist/hooks/ui/use-is-truncated.d.ts.map +1 -1
- package/dist/hooks/ui/use-is-truncated.js +1 -0
- package/dist/hooks/ui/use-media-query.d.ts.map +1 -1
- package/dist/hooks/ui/use-media-query.js +1 -0
- package/dist/hooks/ui/use-resizable-panel-size.d.ts +1 -0
- package/dist/hooks/ui/use-resizable-panel-size.d.ts.map +1 -1
- package/dist/hooks/ui/use-resizable-panel-size.js +5 -1
- package/dist/index.cjs +38 -24
- package/dist/{knowledge-4TOSY2S5.cjs → knowledge-YJSDJIXT.cjs} +20 -20
- package/dist/{logs-MUODFY6N.cjs → logs-42JG3NZ5.cjs} +26 -26
- package/dist/{policies-TJHO3VLE.cjs → policies-D4QNVJ26.cjs} +23 -23
- package/dist/{remote-session-CN3G7JNR.cjs → remote-session-EE6477O3.cjs} +10 -10
- package/dist/{scripts-X5CMYE2V.cjs → scripts-4VFYBXBE.cjs} +26 -26
- package/dist/{software-update-2UZKHTYM.cjs → software-update-OSDZQ5CP.cjs} +25 -25
- package/dist/{tickets-board-PLYD2NSY.cjs → tickets-board-44HO3LR4.cjs} +10 -10
- package/dist/types/navigation.d.ts +36 -0
- package/dist/types/navigation.d.ts.map +1 -1
- package/dist/utils/notifications.cjs +2 -0
- package/dist/utils/notifications.d.ts +1 -1
- package/dist/utils/notifications.d.ts.map +1 -1
- package/dist/utils/notifications.js +2 -0
- package/package.json +1 -1
- package/src/components/chat/chat-nav-sidebar.tsx +17 -4
- package/src/components/features/command-box.tsx +17 -19
- package/src/components/features/notifications/notification-category-icon.tsx +1 -1
- package/src/components/features/software/index.ts +11 -0
- package/src/components/features/software/software-action-form.tsx +28 -1
- package/src/components/features/software/update-settings-fields.tsx +265 -0
- package/src/components/help-center-pages/download-apps-page.tsx +4 -5
- package/src/components/navigation/account-popover.tsx +31 -2
- package/src/components/navigation/app-layout-side-panel.tsx +2 -7
- package/src/components/navigation/navigation-sidebar-grouped.tsx +37 -6
- package/src/components/navigation/navigation-sidebar-item.tsx +18 -1
- package/src/components/navigation/navigation-sidebar.tsx +116 -7
- package/src/components/product-demo/fixtures/software-update.ts +2 -2
- package/src/components/ui/panel-resize-handle.tsx +50 -8
- package/src/hooks/ui/index.ts +1 -0
- package/src/hooks/ui/use-clipped-label-tooltip.ts +20 -0
- package/src/hooks/ui/use-is-truncated.ts +2 -0
- package/src/hooks/ui/use-media-query.ts +2 -0
- package/src/hooks/ui/use-resizable-panel-size.ts +7 -1
- package/src/types/navigation.ts +37 -0
- package/src/utils/notifications.ts +2 -0
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
-
import type { CSSProperties, MouseEvent, ReactNode } from 'react';
|
|
4
|
+
import type { CSSProperties, MouseEvent, ReactNode, RefObject } from 'react';
|
|
5
5
|
import { useIsHydrated } from '../../hooks/ui/use-is-hydrated';
|
|
6
6
|
import { useIsomorphicLayoutEffect } from '../../hooks/ui/use-isomorphic-layout-effect';
|
|
7
7
|
import { useLocalStorage } from '../../hooks/ui/use-local-storage';
|
|
8
8
|
import { useLgUp, useMdUp } from '../../hooks/ui/use-media-query';
|
|
9
|
+
import { useResizablePanelSize } from '../../hooks/ui/use-resizable-panel-size';
|
|
9
10
|
import type { NavigationSidebarConfig, NavigationSidebarItem } from '../../types/navigation';
|
|
10
11
|
import { cn } from '../../utils';
|
|
12
|
+
import { PanelResizeGrip, PanelResizeHandle } from '../ui/panel-resize-handle';
|
|
11
13
|
import { NAVIGATION_SIDEBAR_ATTRIBUTE } from './app-layout-context';
|
|
12
14
|
import {
|
|
13
15
|
NavigationSidebarGroupedEntries,
|
|
@@ -24,6 +26,12 @@ const MINIMIZED_WIDTH = 56; // 3.5rem = 56px
|
|
|
24
26
|
const EXPANDED_WIDTH = 224; // 14rem = 224px — the default; a grouped config may set its own
|
|
25
27
|
/** The narrowest custom `expandedWidth`: the rows read 140px and up as expanded. */
|
|
26
28
|
const MIN_CUSTOM_EXPANDED_WIDTH = 141;
|
|
29
|
+
/**
|
|
30
|
+
* Px of its row a resizable sidebar leaves to the content beside it
|
|
31
|
+
* (`NavigationSidebarResizeConfig.minContentWidth`): the `content-md`
|
|
32
|
+
* breakpoint, under which page content lays out for a phone.
|
|
33
|
+
*/
|
|
34
|
+
const DEFAULT_MIN_CONTENT_WIDTH = 720;
|
|
27
35
|
const STORAGE_KEY = 'of.navigationSidebar.minimized';
|
|
28
36
|
|
|
29
37
|
/**
|
|
@@ -146,6 +154,27 @@ const SIDEBAR_TABLET_WIDTH = {
|
|
|
146
154
|
customOverlay: 'md:w-[var(--of-navigation-sidebar-expanded-width)]',
|
|
147
155
|
} as const;
|
|
148
156
|
|
|
157
|
+
/**
|
|
158
|
+
* The width of the sidebar's row — its parent, the room it shares with the
|
|
159
|
+
* content — for the resize clamp. 0 until measured, and when there is nothing to
|
|
160
|
+
* measure (not resizable, no parent, the server).
|
|
161
|
+
*/
|
|
162
|
+
function useRowWidth(asideRef: RefObject<HTMLElement | null>, enabled: boolean): number {
|
|
163
|
+
const [width, setWidth] = useState(0);
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
const row = enabled ? asideRef.current?.parentElement : null;
|
|
166
|
+
if (!row) return undefined;
|
|
167
|
+
const update = () => setWidth(row.clientWidth);
|
|
168
|
+
update();
|
|
169
|
+
// An environment without ResizeObserver (jsdom in a consumer's tests) measures once.
|
|
170
|
+
if (typeof ResizeObserver === 'undefined') return undefined;
|
|
171
|
+
const observer = new ResizeObserver(update);
|
|
172
|
+
observer.observe(row);
|
|
173
|
+
return () => observer.disconnect();
|
|
174
|
+
}, [asideRef, enabled]);
|
|
175
|
+
return width;
|
|
176
|
+
}
|
|
177
|
+
|
|
149
178
|
/** A click the browser will resolve itself — new tab, new window, middle button. */
|
|
150
179
|
export const isModifiedClick = (event?: MouseEvent): boolean =>
|
|
151
180
|
!!event && (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0);
|
|
@@ -255,18 +284,74 @@ export function NavigationSidebar({
|
|
|
255
284
|
|
|
256
285
|
// A grouped config's own expanded width (`expandedWidth`); `null` = the
|
|
257
286
|
// default 224px through the literal classes, exactly as before.
|
|
258
|
-
const
|
|
287
|
+
const configExpandedWidth =
|
|
259
288
|
config.variant === 'grouped' && config.expandedWidth !== undefined
|
|
260
289
|
? Math.max(config.expandedWidth, MIN_CUSTOM_EXPANDED_WIDTH)
|
|
261
290
|
: null;
|
|
262
|
-
|
|
291
|
+
|
|
292
|
+
// `config.resizable`: the expanded width is the user's — dragged or stepped on
|
|
293
|
+
// the handle below, kept under the config's storage key — and the config width
|
|
294
|
+
// is where it starts. Clamped to the config's bounds and to the row, so the
|
|
295
|
+
// content beside the rail keeps `minContentWidth`. Not resizable: the config
|
|
296
|
+
// width, and the hook reads and writes nothing.
|
|
297
|
+
const resize = config.resizable;
|
|
298
|
+
const asideRef = useRef<HTMLElement>(null);
|
|
299
|
+
const rowWidth = useRowWidth(asideRef, resize !== undefined);
|
|
300
|
+
const resizeMinWidth = Math.max(resize?.minWidth ?? MIN_CUSTOM_EXPANDED_WIDTH, MIN_CUSTOM_EXPANDED_WIDTH);
|
|
301
|
+
const {
|
|
302
|
+
size: chosenExpandedWidth,
|
|
303
|
+
setSize: setChosenExpandedWidth,
|
|
304
|
+
resetSize: resetChosenExpandedWidth,
|
|
305
|
+
clampSize: clampExpandedWidth,
|
|
306
|
+
} = useResizablePanelSize({
|
|
307
|
+
enabled: resize !== undefined,
|
|
308
|
+
minSize: resizeMinWidth,
|
|
309
|
+
maxSize: resize?.maxWidth ?? Number.POSITIVE_INFINITY,
|
|
310
|
+
defaultSize: configExpandedWidth ?? EXPANDED_WIDTH,
|
|
311
|
+
available: rowWidth,
|
|
312
|
+
reserve: resize?.minContentWidth ?? DEFAULT_MIN_CONTENT_WIDTH,
|
|
313
|
+
storageKey: resize?.storageKey,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// The desktop width — what the effect below publishes. The chosen width may
|
|
317
|
+
// be used here as soon as it is known: an effect never runs into the SSR HTML.
|
|
318
|
+
const expandedWidth = resize !== undefined ? chosenExpandedWidth : (configExpandedWidth ?? EXPANDED_WIDTH);
|
|
319
|
+
// The width in the MARKUP — the inline custom property on the rail and its
|
|
320
|
+
// tablet slot, which the tablet overlay opens to and the desktop rule falls
|
|
321
|
+
// back on, and the handle's `aria-valuenow`: the chosen width only once
|
|
322
|
+
// hydrated. The server could not read it, and a first client render that
|
|
323
|
+
// differs is a hydration mismatch, which React reports and leaves in the DOM.
|
|
324
|
+
// Until then the config width stands, as for a sidebar that is not resizable;
|
|
325
|
+
// on desktop the seeded width shows throughout, so nothing moves.
|
|
326
|
+
const markupExpandedWidth = hydrated ? chosenExpandedWidth : (configExpandedWidth ?? EXPANDED_WIDTH);
|
|
327
|
+
const customExpandedWidth = resize !== undefined ? markupExpandedWidth : configExpandedWidth;
|
|
263
328
|
const expandedWidthStyle =
|
|
264
329
|
customExpandedWidth === null ? undefined : ({ [EXPANDED_WIDTH_VAR]: `${customExpandedWidth}px` } as CSSProperties);
|
|
265
330
|
|
|
331
|
+
// A drag moves the rail's own width property on every pointer move and marks
|
|
332
|
+
// the rail (`data-resizing`, read by a class on it) so its width transition
|
|
333
|
+
// does not trail the pointer; the choice is committed once, on release, which
|
|
334
|
+
// also lifts the mark. Neither write concerns hydration: both run on the
|
|
335
|
+
// client only, once the user has the handle.
|
|
336
|
+
const dragExpandedWidth = useCallback((next: number) => {
|
|
337
|
+
const aside = asideRef.current;
|
|
338
|
+
if (!aside) return;
|
|
339
|
+
aside.setAttribute('data-resizing', '');
|
|
340
|
+
aside.style.setProperty(NAVIGATION_SIDEBAR_WIDTH_VAR, `${Math.round(next)}px`);
|
|
341
|
+
}, []);
|
|
342
|
+
const commitExpandedWidth = useCallback(
|
|
343
|
+
(next: number) => {
|
|
344
|
+
asideRef.current?.removeAttribute('data-resizing');
|
|
345
|
+
setChosenExpandedWidth(next);
|
|
346
|
+
},
|
|
347
|
+
[setChosenExpandedWidth],
|
|
348
|
+
);
|
|
349
|
+
|
|
266
350
|
// A new expanded width (the host swapped configs — e.g. a placeholder for
|
|
267
|
-
// the grouped tree
|
|
268
|
-
//
|
|
269
|
-
//
|
|
351
|
+
// the grouped tree; a resize committed, stepped or reset on the handle) is a
|
|
352
|
+
// different sidebar, not a toggle: it snaps instead of animating. The
|
|
353
|
+
// transition is dropped in the same commit that moves the width and comes
|
|
354
|
+
// back a frame later (the effect below).
|
|
270
355
|
const [widthFor, setWidthFor] = useState(expandedWidth);
|
|
271
356
|
if (widthFor !== expandedWidth) {
|
|
272
357
|
setWidthFor(expandedWidth);
|
|
@@ -447,7 +532,6 @@ export function NavigationSidebar({
|
|
|
447
532
|
// On the rail, not on `<html>` where the seed sits: the rail is its only reader,
|
|
448
533
|
// and an inherited custom property on the root made WebKit restyle the whole
|
|
449
534
|
// document on every toggle. Its own value overrides the inherited seed.
|
|
450
|
-
const asideRef = useRef<HTMLElement>(null);
|
|
451
535
|
useIsomorphicLayoutEffect(() => {
|
|
452
536
|
asideRef.current?.style.setProperty(NAVIGATION_SIDEBAR_WIDTH_VAR, sidebarWidth);
|
|
453
537
|
}, [sidebarWidth]);
|
|
@@ -555,6 +639,9 @@ export function NavigationSidebar({
|
|
|
555
639
|
? SIDEBAR_TABLET_WIDTH.overlay
|
|
556
640
|
: SIDEBAR_TABLET_WIDTH.customOverlay,
|
|
557
641
|
transitionsEnabled && widthTransition,
|
|
642
|
+
// A drag on the resize handle in progress: the width follows the
|
|
643
|
+
// pointer, with no transition trailing it (`dragExpandedWidth`).
|
|
644
|
+
'data-[resizing]:transition-none',
|
|
558
645
|
// Snaps where its width moves the content: the desktop rail, and the
|
|
559
646
|
// tablet overlay too when it pushes (its slot above snaps with it).
|
|
560
647
|
!animateWidth && (pushOnTablet ? 'transition-none' : 'lg:transition-none'),
|
|
@@ -632,6 +719,28 @@ export function NavigationSidebar({
|
|
|
632
719
|
onToggle={handleToggle}
|
|
633
720
|
compact={grouped}
|
|
634
721
|
/>
|
|
722
|
+
|
|
723
|
+
{/* The resize handle (`config.resizable`), astride the rail's right edge —
|
|
724
|
+
the grip the Mingo chat drawer had — on desktop while expanded: the
|
|
725
|
+
56px rail has nothing to resize, and below `lg` the width is the
|
|
726
|
+
tablet's. The 24px track straddles the border, half of it over the
|
|
727
|
+
content's edge; the grip marks the border as draggable. */}
|
|
728
|
+
{resize !== undefined && !minimized && (
|
|
729
|
+
<PanelResizeHandle
|
|
730
|
+
side="left"
|
|
731
|
+
variant="inLayout"
|
|
732
|
+
size={markupExpandedWidth}
|
|
733
|
+
minSize={resizeMinWidth}
|
|
734
|
+
maxSize={clampExpandedWidth(Number.POSITIVE_INFINITY)}
|
|
735
|
+
clampSize={clampExpandedWidth}
|
|
736
|
+
onSize={commitExpandedWidth}
|
|
737
|
+
onDrag={dragExpandedWidth}
|
|
738
|
+
onReset={resetChosenExpandedWidth}
|
|
739
|
+
ariaLabel="Resize navigation sidebar"
|
|
740
|
+
className="inset-y-0 hidden w-6 -translate-x-1/2 justify-center pl-0 lg:flex"
|
|
741
|
+
grip={<PanelResizeGrip />}
|
|
742
|
+
/>
|
|
743
|
+
)}
|
|
635
744
|
</aside>
|
|
636
745
|
</>
|
|
637
746
|
);
|
|
@@ -16,7 +16,7 @@ const chrome: SelectedPackage = {
|
|
|
16
16
|
const TONIGHT_11_PM = { value: '23:00', label: '11:00 PM' };
|
|
17
17
|
|
|
18
18
|
/** A fixed reading of the schedule fields: tonight's late slots, nothing in the past. */
|
|
19
|
-
const
|
|
19
|
+
export const SOFTWARE_DEMO_TIMING: SoftwareScheduleTiming = {
|
|
20
20
|
timeReferenceOptions: [
|
|
21
21
|
{ value: 'SERVER', label: 'Your account timezone' },
|
|
22
22
|
{ value: 'DEVICE_LOCAL', label: 'Device local timezone' },
|
|
@@ -49,7 +49,7 @@ export function buildSoftwareUpdateFixture(cast: DemoCast) {
|
|
|
49
49
|
return {
|
|
50
50
|
action: 'UPDATE',
|
|
51
51
|
package: chrome,
|
|
52
|
-
timing,
|
|
52
|
+
timing: SOFTWARE_DEMO_TIMING,
|
|
53
53
|
initialValues,
|
|
54
54
|
devices,
|
|
55
55
|
selectedIds: new Set<string>([DEMO_DEVICES.reception.id, DEMO_DEVICES.samT14.id, DEMO_DEVICES.frontDesk.id]),
|
|
@@ -3,13 +3,37 @@
|
|
|
3
3
|
import { type KeyboardEvent, type PointerEvent, type ReactNode, type RefObject, useEffect, useRef } from 'react';
|
|
4
4
|
|
|
5
5
|
import { cn } from '../../utils/cn';
|
|
6
|
+
import { Menu01Icon } from '../icons-v2-generated';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
* The drag-to-resize handle of a side or edge panel, shared by `Drawer
|
|
9
|
-
* `AppLayoutDrawer`. A drag writes the
|
|
10
|
-
*
|
|
9
|
+
* The drag-to-resize handle of a side or edge panel, shared by `Drawer`,
|
|
10
|
+
* `AppLayoutDrawer`, `ComposerSheet` and `NavigationSidebar`. A drag writes the
|
|
11
|
+
* panel's size to the DOM (or hands it to `onDrag`) and reports it once, on
|
|
12
|
+
* release; arrow keys, Home and End report at once; a double click asks for the
|
|
13
|
+
* default (`onReset`).
|
|
11
14
|
*/
|
|
12
15
|
|
|
16
|
+
/**
|
|
17
|
+
* The grip the design draws on a resizable edge (its `resize-handle`): a 16×32
|
|
18
|
+
* pill in the page's background astride the panel's border, with a three-line
|
|
19
|
+
* mark. `NavigationSidebar` hands it to the handle as its `grip`, and
|
|
20
|
+
* `AppLayoutSidePanel`'s own handle draws it too.
|
|
21
|
+
*/
|
|
22
|
+
export function PanelResizeGrip({ className }: { className?: string }) {
|
|
23
|
+
return (
|
|
24
|
+
<span
|
|
25
|
+
aria-hidden
|
|
26
|
+
className={cn(
|
|
27
|
+
'flex h-8 w-4 items-center justify-center rounded-sm border border-ods-border bg-ods-bg text-ods-text-secondary transition-colors',
|
|
28
|
+
'group-hover:border-ods-border-hover group-focus-visible:border-ods-accent group-focus-visible:text-ods-accent',
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
31
|
+
>
|
|
32
|
+
<Menu01Icon size={12} />
|
|
33
|
+
</span>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
13
37
|
type PanelSide = 'right' | 'left' | 'top' | 'bottom';
|
|
14
38
|
|
|
15
39
|
const HORIZONTAL_SIDES: ReadonlySet<PanelSide> = new Set(['left', 'right']);
|
|
@@ -37,8 +61,18 @@ export interface PanelResizeHandleProps {
|
|
|
37
61
|
onSize: (next: number) => void;
|
|
38
62
|
/** Clamp a candidate size to the min/max and the container. */
|
|
39
63
|
clampSize: (next: number) => number;
|
|
40
|
-
/** The panel the size applies to — written directly while dragging.
|
|
41
|
-
|
|
64
|
+
/** The panel the size applies to — written directly while dragging. Omit for a
|
|
65
|
+
* panel sized by other means (`onDrag`). */
|
|
66
|
+
panelRef?: RefObject<HTMLDivElement | null>;
|
|
67
|
+
/**
|
|
68
|
+
* Every size the drag reaches, in place of writing `panelRef`'s inline size:
|
|
69
|
+
* for a panel whose width is a custom property or a class rather than an
|
|
70
|
+
* inline style, or one that marks itself while dragged. `onSize` still
|
|
71
|
+
* reports once, on release.
|
|
72
|
+
*/
|
|
73
|
+
onDrag?: (next: number) => void;
|
|
74
|
+
/** A double click on the handle: back to the default size. */
|
|
75
|
+
onReset?: () => void;
|
|
42
76
|
/** `overlay` is the body-level `Drawer`'s handle, `inLayout` the wider one of
|
|
43
77
|
* `AppLayoutDrawer`. They differ in track width and grip colour only. */
|
|
44
78
|
variant: 'overlay' | 'inLayout';
|
|
@@ -57,6 +91,8 @@ export function PanelResizeHandle({
|
|
|
57
91
|
onSize,
|
|
58
92
|
clampSize,
|
|
59
93
|
panelRef,
|
|
94
|
+
onDrag,
|
|
95
|
+
onReset,
|
|
60
96
|
variant,
|
|
61
97
|
ariaLabel,
|
|
62
98
|
className,
|
|
@@ -95,12 +131,17 @@ export function PanelResizeHandle({
|
|
|
95
131
|
// the size is unchanged, so it leaves the style alone.
|
|
96
132
|
const handlePointerMove = (e: PointerEvent<HTMLDivElement>) => {
|
|
97
133
|
const start = startRef.current;
|
|
98
|
-
|
|
99
|
-
if (!
|
|
134
|
+
if (!start) return;
|
|
135
|
+
if (!onDrag && !panelRef?.current) return;
|
|
100
136
|
const delta = isHorizontal ? e.clientX - start.x : e.clientY - start.y;
|
|
101
137
|
const next = clampSize(start.size + delta * direction);
|
|
102
138
|
dragSizeRef.current = next;
|
|
103
|
-
|
|
139
|
+
if (onDrag) {
|
|
140
|
+
onDrag(next);
|
|
141
|
+
} else if (panelRef) {
|
|
142
|
+
const panel = panelRef.current;
|
|
143
|
+
if (panel) panel.style[isHorizontal ? 'width' : 'height'] = `${next}px`;
|
|
144
|
+
}
|
|
104
145
|
e.currentTarget.setAttribute('aria-valuenow', String(Math.round(next)));
|
|
105
146
|
};
|
|
106
147
|
|
|
@@ -174,6 +215,7 @@ export function PanelResizeHandle({
|
|
|
174
215
|
onPointerCancel={endDrag}
|
|
175
216
|
onLostPointerCapture={endDrag}
|
|
176
217
|
onKeyDown={handleKeyDown}
|
|
218
|
+
onDoubleClick={onReset}
|
|
177
219
|
className={cn(
|
|
178
220
|
'group absolute z-20 flex touch-none select-none',
|
|
179
221
|
'outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0',
|
package/src/hooks/ui/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from './use-header-height';
|
|
|
9
9
|
export * from './use-sticky-toolbar';
|
|
10
10
|
export * from './use-horizontal-scrollbar';
|
|
11
11
|
export * from './use-image-edge-color';
|
|
12
|
+
export * from './use-clipped-label-tooltip';
|
|
12
13
|
export * from './use-is-truncated';
|
|
13
14
|
export * from './use-isomorphic-layout-effect';
|
|
14
15
|
export * from './use-keyboard-collision-padding';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useIsTruncated } from './use-is-truncated';
|
|
4
|
+
import { useMediaQuery } from './use-media-query';
|
|
5
|
+
|
|
6
|
+
/** A pointer that can hover: where a tooltip on a clipped label is reachable. A touch screen has none. */
|
|
7
|
+
export const HOVER_MEDIA_QUERY = '(hover: hover) and (pointer: fine)';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A clipped label's "full text" tooltip: `ref` goes on the clipped element (see
|
|
11
|
+
* {@link useIsTruncated}), `enabled` says whether the tooltip may open — only
|
|
12
|
+
* while the text IS clipped, and only on a device whose pointer can hover. A
|
|
13
|
+
* phone or tablet has no hover to reach it with, so it offers none there; the
|
|
14
|
+
* rows keep their accessible names. Pass the rendered text as `value`.
|
|
15
|
+
*/
|
|
16
|
+
export function useClippedLabelTooltip<T extends HTMLElement>(value: unknown) {
|
|
17
|
+
const { ref, isTruncated } = useIsTruncated<T>(value);
|
|
18
|
+
const canHover = useMediaQuery(HOVER_MEDIA_QUERY) === true;
|
|
19
|
+
return { ref, enabled: isTruncated && canHover };
|
|
20
|
+
}
|
|
@@ -52,6 +52,8 @@ export function useIsTruncated<T extends HTMLElement>(value: unknown, options?:
|
|
|
52
52
|
);
|
|
53
53
|
};
|
|
54
54
|
check();
|
|
55
|
+
// An environment without ResizeObserver (jsdom in a consumer's tests) measures once.
|
|
56
|
+
if (typeof ResizeObserver === 'undefined') return undefined;
|
|
55
57
|
const observer = new ResizeObserver(check);
|
|
56
58
|
observer.observe(element);
|
|
57
59
|
return () => observer.disconnect();
|
|
@@ -13,6 +13,8 @@ export function useMediaQuery(query: string): boolean | undefined {
|
|
|
13
13
|
const [matches, setMatches] = useState<boolean | undefined>(undefined);
|
|
14
14
|
|
|
15
15
|
useIsomorphicLayoutEffect(() => {
|
|
16
|
+
// An environment without matchMedia (jsdom in a consumer's tests) never answers: `undefined` throughout.
|
|
17
|
+
if (typeof window.matchMedia !== 'function') return undefined;
|
|
16
18
|
const matchMedia = window.matchMedia(query);
|
|
17
19
|
|
|
18
20
|
const handleChange = () => {
|
|
@@ -110,5 +110,11 @@ export function useResizablePanelSize({
|
|
|
110
110
|
[clampSize, storage],
|
|
111
111
|
);
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
/** Back to `defaultSize`: the choice is forgotten and its stored entry removed. */
|
|
114
|
+
const resetSize = useCallback(() => {
|
|
115
|
+
setChosen({ storage, size: null });
|
|
116
|
+
storage?.clear();
|
|
117
|
+
}, [storage]);
|
|
118
|
+
|
|
119
|
+
return { size, setSize, resetSize, clampSize };
|
|
114
120
|
}
|
package/src/types/navigation.ts
CHANGED
|
@@ -115,6 +115,35 @@ export interface SlidingSidebarConfig {
|
|
|
115
115
|
className?: string;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* `NavigationSidebarConfig.resizable`: the user drags the expanded sidebar's
|
|
120
|
+
* right edge on desktop (`lg` and up). The 56px rail is not resizable, nor are
|
|
121
|
+
* the tablet overlay and the burger menu; the overlay opens to the chosen width.
|
|
122
|
+
*/
|
|
123
|
+
export interface NavigationSidebarResizeConfig {
|
|
124
|
+
/**
|
|
125
|
+
* Narrowest expanded width, in px. Raised to 141px when lower: the rows read
|
|
126
|
+
* 140px and up as expanded (`@[140px]/of-nav-sidebar`).
|
|
127
|
+
*/
|
|
128
|
+
minWidth: number;
|
|
129
|
+
/** Widest expanded width, in px. Also kept to the room there is (`minContentWidth`). */
|
|
130
|
+
maxWidth: number;
|
|
131
|
+
/**
|
|
132
|
+
* Px of the row the content beside the sidebar always keeps, so a wide sidebar
|
|
133
|
+
* cannot crowd the page out of a narrow desktop window. Default 720, the lib's
|
|
134
|
+
* `content-md` breakpoint.
|
|
135
|
+
*/
|
|
136
|
+
minContentWidth?: number;
|
|
137
|
+
/**
|
|
138
|
+
* `localStorage` key for the width the user chose: `{ size }` under
|
|
139
|
+
* `<storageKey>:chosen` (see `useResizablePanelSize`). Only a drag or a key
|
|
140
|
+
* press on the handle writes it, never the default or a clamped width; a double
|
|
141
|
+
* click removes it. Omit to keep the choice for the mount only. A host that seeds
|
|
142
|
+
* `NAVIGATION_SIDEBAR_WIDTH_VAR` before first paint reads the same entry.
|
|
143
|
+
*/
|
|
144
|
+
storageKey?: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
118
147
|
/**
|
|
119
148
|
* Configuration for the navigation sidebar component
|
|
120
149
|
*/
|
|
@@ -237,6 +266,14 @@ export interface NavigationSidebarConfig {
|
|
|
237
266
|
* chat search in `sectionSlot`.
|
|
238
267
|
*/
|
|
239
268
|
revealSignal?: number;
|
|
269
|
+
/**
|
|
270
|
+
* Let the user resize the expanded sidebar on desktop: a drag handle astride
|
|
271
|
+
* its right edge (the grip the Mingo chat drawer had), arrow keys, Home and
|
|
272
|
+
* End on it, a double click back to the default. The default stays
|
|
273
|
+
* `expandedWidth` (grouped) or 224px (flat); both variants. Omitted: the fixed
|
|
274
|
+
* widths, as before.
|
|
275
|
+
*/
|
|
276
|
+
resizable?: NavigationSidebarResizeConfig;
|
|
240
277
|
}
|
|
241
278
|
|
|
242
279
|
/** What a slot render function is told about the place it is drawn in. */
|
|
@@ -39,6 +39,7 @@ export const NOTIFICATION_CATEGORIES = [
|
|
|
39
39
|
'INSIGHTS',
|
|
40
40
|
'MINGO',
|
|
41
41
|
'INTEGRATIONS',
|
|
42
|
+
'BILLING',
|
|
42
43
|
'GENERIC',
|
|
43
44
|
] as const;
|
|
44
45
|
export type NotificationCategory = (typeof NOTIFICATION_CATEGORIES)[number];
|
|
@@ -56,6 +57,7 @@ export const NOTIFICATION_CATEGORY_LABELS: Readonly<Record<NotificationCategory,
|
|
|
56
57
|
INSIGHTS: 'Insights',
|
|
57
58
|
MINGO: 'Mingo',
|
|
58
59
|
INTEGRATIONS: 'Integrations',
|
|
60
|
+
BILLING: 'Billing',
|
|
59
61
|
GENERIC: 'General',
|
|
60
62
|
};
|
|
61
63
|
|