@flamingo-stack/openframe-frontend-core 0.0.853 → 0.0.854

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 (146) hide show
  1. package/dist/build/export-map.json +1 -1
  2. package/dist/{chunk-G7GF4PWA.cjs → chunk-2IJMHBOK.cjs} +3 -3
  3. package/dist/{chunk-IC4IJJB2.cjs → chunk-3WXHPD6N.cjs} +55 -55
  4. package/dist/{chunk-UV45EPXN.cjs → chunk-4LTLEUY5.cjs} +5 -5
  5. package/dist/{chunk-BNHTBLAG.cjs → chunk-4O5N5CGK.cjs} +17 -17
  6. package/dist/{chunk-COWMXUHN.cjs → chunk-4SSJ3T3N.cjs} +3 -1
  7. package/dist/{chunk-IUDBG3HJ.cjs → chunk-5HMENXSR.cjs} +3 -3
  8. package/dist/{chunk-COJG2ATY.cjs → chunk-5OENPMRU.cjs} +43 -43
  9. package/dist/{chunk-IO6NN3SL.cjs → chunk-5TXE7BXT.cjs} +46 -46
  10. package/dist/{chunk-EIACEY2L.cjs → chunk-63ZV3UGG.cjs} +10 -10
  11. package/dist/{chunk-HTYJUMFB.cjs → chunk-6DCUGIGU.cjs} +2 -2
  12. package/dist/{chunk-BNG6JOGZ.cjs → chunk-6XU3RCK3.cjs} +18 -18
  13. package/dist/{chunk-KEV6F3QN.cjs → chunk-7J5NSWJG.cjs} +87 -87
  14. package/dist/{chunk-53QPG7SJ.cjs → chunk-7MOBD44H.cjs} +28 -28
  15. package/dist/{chunk-U767P4JU.cjs → chunk-ACPOJE32.cjs} +9 -9
  16. package/dist/{chunk-E4MFQKPC.cjs → chunk-BAA3YCLT.cjs} +72 -72
  17. package/dist/{chunk-7RZHSBNX.cjs → chunk-D2XGRWPW.cjs} +73 -73
  18. package/dist/{chunk-5RMBLOJ5.cjs → chunk-EIHMPQRQ.cjs} +9 -9
  19. package/dist/{chunk-TBGQMHCG.cjs → chunk-ETQNMR2K.cjs} +5 -5
  20. package/dist/{chunk-2W22LY3W.cjs → chunk-FBANHIAG.cjs} +9 -9
  21. package/dist/{chunk-P63CRPWI.cjs → chunk-FJXSKMQJ.cjs} +20 -20
  22. package/dist/{chunk-QOW2GP3V.cjs → chunk-GVHCQRUE.cjs} +21 -21
  23. package/dist/{chunk-KNN2GCMW.cjs → chunk-H3GBPYPS.cjs} +18 -18
  24. package/dist/{chunk-FW7RKWPY.cjs → chunk-JKBNK5U6.cjs} +3 -3
  25. package/dist/{chunk-W7OFAROY.cjs → chunk-KA3L6D5N.cjs} +3 -3
  26. package/dist/{chunk-USPUZO53.cjs → chunk-LQDAB5V5.cjs} +4 -4
  27. package/dist/{chunk-KHOB3TES.cjs → chunk-ONYANAMA.cjs} +4 -4
  28. package/dist/{chunk-GI6ECCWB.cjs → chunk-PRJZMZ3X.cjs} +1155 -739
  29. package/dist/chunk-QD727QFI.cjs +62 -0
  30. package/dist/{chunk-67CZ2Q5K.cjs → chunk-RMI6O3AE.cjs} +32 -32
  31. package/dist/{chunk-EF2FZ7GT.cjs → chunk-S5P6CF5R.cjs} +47 -47
  32. package/dist/{chunk-6V2M2X3M.cjs → chunk-TMPLHUUU.cjs} +6 -6
  33. package/dist/{chunk-UO2EJGRJ.cjs → chunk-UPQ4MAR7.cjs} +14 -14
  34. package/dist/{chunk-LFEURIZN.cjs → chunk-VMAOZA2V.cjs} +215 -215
  35. package/dist/{chunk-NMWLXHFH.cjs → chunk-YY3WP2YQ.cjs} +30 -30
  36. package/dist/{chunk-HF3V6WBJ.cjs → chunk-ZDPPNHA2.cjs} +3 -3
  37. package/dist/{cloud-tenants-QUL2XPAS.cjs → cloud-tenants-5RNK4ZAT.cjs} +19 -19
  38. package/dist/components/case-studies/index.cjs +33 -33
  39. package/dist/components/chat/index.cjs +13 -13
  40. package/dist/components/contact/index.cjs +20 -20
  41. package/dist/components/docs/index.cjs +23 -23
  42. package/dist/components/embeds/index.cjs +20 -20
  43. package/dist/components/faq/index.cjs +21 -21
  44. package/dist/components/features/cloud-tenants/index.d.ts +7 -5
  45. package/dist/components/features/cloud-tenants/index.d.ts.map +1 -1
  46. package/dist/components/features/cloud-tenants/index.js +8 -1
  47. package/dist/components/features/cloud-tenants/single-customer-tenants-table-view.d.ts +26 -0
  48. package/dist/components/features/cloud-tenants/single-customer-tenants-table-view.d.ts.map +1 -0
  49. package/dist/components/features/cloud-tenants/single-customer-tenants-table-view.js +176 -0
  50. package/dist/components/features/cloud-tenants/tenant-access-cell.d.ts +4 -2
  51. package/dist/components/features/cloud-tenants/tenant-access-cell.d.ts.map +1 -1
  52. package/dist/components/features/cloud-tenants/tenant-access-cell.js +3 -2
  53. package/dist/components/features/cloud-tenants/tenant-customer-cell.d.ts +12 -5
  54. package/dist/components/features/cloud-tenants/tenant-customer-cell.d.ts.map +1 -1
  55. package/dist/components/features/cloud-tenants/tenant-customer-cell.js +38 -7
  56. package/dist/components/features/cloud-tenants/tenant-licenses-cell.d.ts +7 -0
  57. package/dist/components/features/cloud-tenants/tenant-licenses-cell.d.ts.map +1 -0
  58. package/dist/components/features/cloud-tenants/tenant-licenses-cell.js +17 -0
  59. package/dist/components/features/cloud-tenants/tenant-presentation.d.ts +19 -0
  60. package/dist/components/features/cloud-tenants/tenant-presentation.d.ts.map +1 -1
  61. package/dist/components/features/cloud-tenants/tenant-presentation.js +24 -3
  62. package/dist/components/features/cloud-tenants/tenant-row.d.ts +35 -8
  63. package/dist/components/features/cloud-tenants/tenant-row.d.ts.map +1 -1
  64. package/dist/components/features/cloud-tenants/tenants-table-columns.d.ts +9 -3
  65. package/dist/components/features/cloud-tenants/tenants-table-columns.d.ts.map +1 -1
  66. package/dist/components/features/cloud-tenants/tenants-table-columns.js +5 -3
  67. package/dist/components/features/cloud-tenants/tenants-table-view.d.ts +27 -6
  68. package/dist/components/features/cloud-tenants/tenants-table-view.d.ts.map +1 -1
  69. package/dist/components/features/cloud-tenants/tenants-table-view.js +75 -17
  70. package/dist/components/features/devices/device-directory.d.ts +18 -0
  71. package/dist/components/features/devices/device-directory.d.ts.map +1 -0
  72. package/dist/components/features/devices/device-directory.js +35 -0
  73. package/dist/components/features/devices/devices-filter-toolbar.d.ts +3 -1
  74. package/dist/components/features/devices/devices-filter-toolbar.d.ts.map +1 -1
  75. package/dist/components/features/devices/devices-filter-toolbar.js +2 -1
  76. package/dist/components/features/devices/devices-panel-view.d.ts +3 -1
  77. package/dist/components/features/devices/devices-panel-view.d.ts.map +1 -1
  78. package/dist/components/features/devices/devices-panel-view.js +11 -2
  79. package/dist/components/features/devices/devices-table-columns.d.ts +17 -3
  80. package/dist/components/features/devices/devices-table-columns.d.ts.map +1 -1
  81. package/dist/components/features/devices/devices-table-columns.js +80 -10
  82. package/dist/components/features/devices/index.d.ts +1 -0
  83. package/dist/components/features/devices/index.d.ts.map +1 -1
  84. package/dist/components/features/devices/index.js +8 -0
  85. package/dist/components/features/devices/types.d.ts +14 -0
  86. package/dist/components/features/devices/types.d.ts.map +1 -1
  87. package/dist/components/features/devices/use-tag-filter-modal.d.ts +1 -0
  88. package/dist/components/features/devices/use-tag-filter-modal.d.ts.map +1 -1
  89. package/dist/components/features/devices/use-tag-filter-modal.js +13 -8
  90. package/dist/components/features/index.cjs +33 -19
  91. package/dist/components/help-center-pages/index.cjs +120 -120
  92. package/dist/components/index.cjs +90 -76
  93. package/dist/components/meeting-scheduler/index.cjs +82 -82
  94. package/dist/components/navigation/app-layout.js +2 -2
  95. package/dist/components/navigation/index.cjs +22 -22
  96. package/dist/components/onboarding-guides/index.cjs +22 -22
  97. package/dist/components/product-demo/index.cjs +30 -30
  98. package/dist/components/related-content/index.cjs +8 -8
  99. package/dist/components/tickets/index.cjs +24 -24
  100. package/dist/components/ui/date-picker.d.ts +2 -0
  101. package/dist/components/ui/date-picker.d.ts.map +1 -1
  102. package/dist/components/ui/date-picker.js +19 -6
  103. package/dist/components/ui/index.cjs +19 -19
  104. package/dist/{devices-LNB4CYPP.cjs → devices-44O5PZOW.cjs} +20 -20
  105. package/dist/{embeddable-chat-SJEEJI4N.cjs → embeddable-chat-W3CLMGC3.cjs} +90 -90
  106. package/dist/hooks/index.cjs +11 -7
  107. package/dist/hooks/ui/use-content-breakpoint.d.ts +8 -0
  108. package/dist/hooks/ui/use-content-breakpoint.d.ts.map +1 -1
  109. package/dist/hooks/ui/use-content-breakpoint.js +17 -3
  110. package/dist/hooks/ui/use-media-query.d.ts +1 -0
  111. package/dist/hooks/ui/use-media-query.d.ts.map +1 -1
  112. package/dist/hooks/ui/use-media-query.js +3 -1
  113. package/dist/index.cjs +42 -24
  114. package/dist/{knowledge-UTWKB6XG.cjs → knowledge-UUU3WVO3.cjs} +20 -20
  115. package/dist/{logs-PMXIWMBQ.cjs → logs-DFHNSODA.cjs} +26 -26
  116. package/dist/{policies-6DCMFDL7.cjs → policies-RGFUGDBP.cjs} +23 -23
  117. package/dist/{remote-session-SG44CNOK.cjs → remote-session-U3H77HKR.cjs} +10 -10
  118. package/dist/{scripts-2G7LXW7Z.cjs → scripts-KOVO5DHW.cjs} +26 -26
  119. package/dist/{software-update-MYLKHDHC.cjs → software-update-4EKHVV5W.cjs} +23 -23
  120. package/dist/{tickets-board-SQY2Y6QV.cjs → tickets-board-YIFOVMDX.cjs} +10 -10
  121. package/dist/utils/presentation-for.d.ts +6 -0
  122. package/dist/utils/presentation-for.d.ts.map +1 -0
  123. package/dist/utils/presentation-for.js +7 -0
  124. package/package.json +1 -1
  125. package/src/components/features/cloud-tenants/index.ts +8 -3
  126. package/src/components/features/cloud-tenants/single-customer-tenants-table-view.tsx +234 -0
  127. package/src/components/features/cloud-tenants/tenant-access-cell.tsx +12 -5
  128. package/src/components/features/cloud-tenants/tenant-customer-cell.tsx +48 -12
  129. package/src/components/features/cloud-tenants/tenant-licenses-cell.tsx +28 -0
  130. package/src/components/features/cloud-tenants/tenant-presentation.ts +46 -4
  131. package/src/components/features/cloud-tenants/tenant-row.ts +38 -8
  132. package/src/components/features/cloud-tenants/tenants-table-columns.ts +8 -5
  133. package/src/components/features/cloud-tenants/tenants-table-view.tsx +123 -28
  134. package/src/components/features/devices/device-directory.ts +55 -0
  135. package/src/components/features/devices/devices-filter-toolbar.tsx +4 -1
  136. package/src/components/features/devices/devices-panel-view.tsx +12 -2
  137. package/src/components/features/devices/devices-table-columns.tsx +123 -8
  138. package/src/components/features/devices/index.ts +7 -0
  139. package/src/components/features/devices/types.ts +14 -0
  140. package/src/components/features/devices/use-tag-filter-modal.ts +32 -14
  141. package/src/components/navigation/app-layout.tsx +4 -4
  142. package/src/components/ui/date-picker.tsx +21 -3
  143. package/src/hooks/ui/use-content-breakpoint.ts +21 -2
  144. package/src/hooks/ui/use-media-query.ts +1 -0
  145. package/src/utils/presentation-for.ts +10 -0
  146. package/dist/chunk-UAI4VMEF.cjs +0 -46
@@ -34,6 +34,14 @@ export interface DeviceRow extends DeviceNameSource {
34
34
  organizationImageUrl?: string | null;
35
35
  organizationImageHash?: string | null;
36
36
  tags?: DeviceTag[];
37
+ /** Who the device is tied to in a directory: PRIMARY, SHARED or NONE. The USER column reads it. */
38
+ directoryUserLink?: string;
39
+ /** The primary user's name; only PRIMARY has one. */
40
+ directoryUserName?: string | null;
41
+ /** How many users are tied to it: "N recent users" when SHARED. */
42
+ directoryUserCount?: number;
43
+ /** The directories that know the device besides the agent. The SOURCES column reads it. */
44
+ directorySources?: readonly string[];
37
45
  /** The viewer's unread notifications about this device. Absent or 0: no indicator. */
38
46
  unreadNotificationCount?: number;
39
47
  }
@@ -63,6 +71,10 @@ export interface DeviceFilters {
63
71
  organizationIds: DeviceFilterTag[];
64
72
  tagKeys: TagFilterOption[];
65
73
  filteredCount: number;
74
+ /** Behind the USER funnel; only hosts that draw the directory columns read it. */
75
+ userLinks?: DeviceFilterValue[];
76
+ /** Behind the SOURCES funnel; only hosts that draw the directory columns read it. */
77
+ directorySources?: DeviceFilterValue[];
66
78
  }
67
79
 
68
80
  /** What narrows a device list, in the backend's field names. */
@@ -73,4 +85,6 @@ export interface DeviceFilterInput {
73
85
  organizationIds?: string[];
74
86
  tagKeys?: string[];
75
87
  tagValues?: string[];
88
+ userLinks?: string[];
89
+ directorySources?: string[];
76
90
  }
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useCallback, useMemo, useRef, useState } from 'react';
4
- import { useMdUp } from '../../../hooks/ui/use-media-query';
4
+ import { useContentLgUp, useContentMdUp, useContentXlUp } from '../../../hooks/ui/use-content-breakpoint';
5
5
  import type { DeviceFilters } from './types';
6
6
 
7
7
  function isCompleteTag(tag: string): boolean {
@@ -19,7 +19,13 @@ interface FilterOption {
19
19
  interface UseTagFilterModalParams {
20
20
  tags: string[];
21
21
  deviceFilters: DeviceFilters | null;
22
- columns: Array<{ key: string; label: string; filterable?: boolean; filterOptions?: FilterOption[] }>;
22
+ columns: Array<{
23
+ key: string;
24
+ label: string;
25
+ filterable?: boolean;
26
+ filterOptions?: FilterOption[];
27
+ inWindowLgToXl?: boolean;
28
+ }>;
23
29
  /** `useApiParams`' setter, each device list param is a list of ids. */
24
30
  setParams: (params: Record<string, string[]>) => void;
25
31
  /**
@@ -39,7 +45,11 @@ export function useTagFilterModal({
39
45
  setParams,
40
46
  onApplied = scrollMainToTop,
41
47
  }: UseTagFilterModalParams) {
42
- const isMdUp = useMdUp();
48
+ // The content area's step, not the window's: the table header follows it, and a docked side panel narrows it while
49
+ // the window stays wide.
50
+ const isMdUp = useContentMdUp();
51
+ const lgUp = useContentLgUp();
52
+ const xlUp = useContentXlUp();
43
53
  const [isOpen, setIsOpen] = useState(false);
44
54
 
45
55
  // FilterModal calls onFilterChange first, then onTagsChange.
@@ -49,16 +59,19 @@ export function useTagFilterModal({
49
59
  const open = useCallback(() => setIsOpen(true), []);
50
60
  const close = useCallback(() => setIsOpen(false), []);
51
61
 
52
- const filterGroups = useMemo(() => {
53
- if (isMdUp) return [];
54
- return columns
55
- .filter(column => column.filterable)
56
- .map(column => ({
57
- id: column.key,
58
- title: column.label,
59
- options: column.filterOptions || [],
60
- }));
61
- }, [columns, isMdUp]);
62
+ // Below md the table draws no header, so every column filters here; from lg until xl only the columns whose header
63
+ // has no funnel there do.
64
+ const filterGroups = useMemo(
65
+ () =>
66
+ columns
67
+ .filter(column => column.filterable && (!isMdUp || (lgUp && xlUp === false && column.inWindowLgToXl)))
68
+ .map(column => ({
69
+ id: column.key,
70
+ title: column.label,
71
+ options: column.filterOptions || [],
72
+ })),
73
+ [columns, isMdUp, lgUp, xlUp],
74
+ );
62
75
 
63
76
  const tagFilterKeys = useMemo(() => {
64
77
  if (!deviceFilters?.tagKeys) return [];
@@ -79,6 +92,9 @@ export function useTagFilterModal({
79
92
  pendingFiltersRef.current = columnFilters;
80
93
  }, []);
81
94
 
95
+ // Only a host that draws the directory columns has the USER and SOURCES groups: another's list params stay untouched
96
+ const hasDirectoryGroups = useMemo(() => columns.some(column => column.key === 'user'), [columns]);
97
+
82
98
  // Step 2: combine with buffered filters and flush (called second by FilterModal)
83
99
  const handleTagsChange = useCallback(
84
100
  (newTags: string[]) => {
@@ -87,12 +103,14 @@ export function useTagFilterModal({
87
103
  statuses: cf.status || [],
88
104
  osTypes: cf.os || [],
89
105
  organizationIds: cf.organization || [],
106
+ // FilterModal drops a group with nothing ticked, so a missing group clears its param
107
+ ...(hasDirectoryGroups && { userLinks: cf.user || [], directorySources: cf.sources || [] }),
90
108
  tags: newTags.filter(isCompleteTag),
91
109
  });
92
110
  pendingFiltersRef.current = {};
93
111
  onApplied();
94
112
  },
95
- [setParams, onApplied],
113
+ [setParams, onApplied, hasDirectoryGroups],
96
114
  );
97
115
 
98
116
  return {
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import type { CSSProperties, ReactNode } from 'react';
5
- import { ContentAreaWidthContext, contentBreakpointFor } from '../../hooks/ui/use-content-breakpoint';
5
+ import { ContentAreaWidthContext, contentStepFor } from '../../hooks/ui/use-content-breakpoint';
6
6
  import { useIsomorphicLayoutEffect } from '../../hooks/ui/use-isomorphic-layout-effect';
7
7
  import type { NavigationSidebarConfig } from '../../types/navigation';
8
8
  import { cn } from '../../utils';
@@ -203,14 +203,14 @@ export function AppLayout({
203
203
 
204
204
  useIsomorphicLayoutEffect(() => {
205
205
  if (!adaptive || !mainElement) return undefined;
206
- // Every reader asks for the breakpoint (`useContentBreakpoint`), so only a
207
- // crossing re-renders the layout: kept at the old width, the state bails out.
206
+ // Every reader asks for a step (`useContentBreakpoint`, `useContentXlUp`), so
207
+ // only a crossing re-renders the layout: kept at the old width, the state bails out.
208
208
  // Per pixel, the uncompiled layout and every breakpoint consumer re-rendered on
209
209
  // each frame of a sidebar toggle, a window drag or a side-panel resize.
210
210
  const update = () => {
211
211
  const width = mainElement.clientWidth;
212
212
  setContentWidth(previous =>
213
- previous !== null && contentBreakpointFor(previous) === contentBreakpointFor(width) ? previous : width,
213
+ previous !== null && contentStepFor(previous) === contentStepFor(width) ? previous : width,
214
214
  );
215
215
  };
216
216
  update();
@@ -42,6 +42,8 @@ export interface DatePickerBaseProps {
42
42
  error?: string;
43
43
  /** When true, renders red error border */
44
44
  invalid?: boolean;
45
+ /** The trigger button's id: a label drawn outside the picker names it with `htmlFor`. */
46
+ id?: string;
45
47
  }
46
48
 
47
49
  export interface SingleDatePickerProps extends DatePickerBaseProps {
@@ -69,6 +71,10 @@ export type DatePickerProps = SingleDatePickerProps | RangeDatePickerProps;
69
71
  const defaultFormatDate = (date: Date): string =>
70
72
  formatDateWithTimezone(date, VIEWER_TIMEZONE, 'numeric', { viewerLocale: true });
71
73
 
74
+ // A label tied to the trigger (`id`) becomes the button's whole accessible name, so the date it shows would go unread:
75
+ // the trigger is described by its value instead.
76
+ const triggerValueId = (id: string | undefined) => (id ? `${id}-value` : undefined);
77
+
72
78
  const formatDateRange = (range: DateRange | undefined, formatFn: (date: Date) => string): string => {
73
79
  if (!range?.from) return '';
74
80
  if (!range.to) return formatFn(range.from);
@@ -667,6 +673,7 @@ export function DatePicker(props: DatePickerProps) {
667
673
  label,
668
674
  error,
669
675
  invalid = false,
676
+ id,
670
677
  } = props;
671
678
 
672
679
  const [open, setOpen] = useState(false);
@@ -701,6 +708,8 @@ export function DatePicker(props: DatePickerProps) {
701
708
  <Button
702
709
  variant="field"
703
710
  type="button"
711
+ id={id}
712
+ aria-describedby={triggerValueId(id)}
704
713
  disabled={disabled}
705
714
  // Same marker Input/Textarea/Select expose — it is how a form finds
706
715
  // (and scrolls to) the first field that failed validation.
@@ -709,6 +718,7 @@ export function DatePicker(props: DatePickerProps) {
709
718
  >
710
719
  <Calendar className="size-6 shrink-0 text-ods-text-secondary" />
711
720
  <span
721
+ id={triggerValueId(id)}
712
722
  className={cn(
713
723
  'flex-1 truncate text-left',
714
724
  disabled ? 'text-ods-text-disabled' : displayValue ? 'text-ods-text-primary' : 'text-ods-text-secondary',
@@ -749,7 +759,7 @@ export function DatePicker(props: DatePickerProps) {
749
759
  );
750
760
 
751
761
  return (
752
- <FieldWrapper label={label} error={error} errorSlot={'error' in props}>
762
+ <FieldWrapper label={label} htmlFor={id} error={error} errorSlot={'error' in props}>
753
763
  {picker}
754
764
  </FieldWrapper>
755
765
  );
@@ -799,6 +809,7 @@ export function DatePickerInput(props: DatePickerInputProps) {
799
809
  label,
800
810
  error,
801
811
  invalid = false,
812
+ id,
802
813
  } = props;
803
814
  const [open, setOpen] = useState(false);
804
815
  const portalContainer = usePortalContainer();
@@ -883,6 +894,8 @@ export function DatePickerInput(props: DatePickerInputProps) {
883
894
  <Button
884
895
  variant="field"
885
896
  type="button"
897
+ id={id}
898
+ aria-describedby={triggerValueId(id)}
886
899
  disabled={disabled}
887
900
  // Same marker Input/Textarea/Select expose — it is how a form finds
888
901
  // (and scrolls to) the first field that failed validation.
@@ -891,6 +904,7 @@ export function DatePickerInput(props: DatePickerInputProps) {
891
904
  >
892
905
  <Calendar className="size-6 shrink-0 text-ods-text-secondary" />
893
906
  <span
907
+ id={triggerValueId(id)}
894
908
  className={cn(
895
909
  'flex-1 truncate text-left',
896
910
  disabled
@@ -984,7 +998,7 @@ export function DatePickerInput(props: DatePickerInputProps) {
984
998
  );
985
999
 
986
1000
  return (
987
- <FieldWrapper label={label} error={error} errorSlot={'error' in props} className={className}>
1001
+ <FieldWrapper label={label} htmlFor={id} error={error} errorSlot={'error' in props} className={className}>
988
1002
  {content}
989
1003
  </FieldWrapper>
990
1004
  );
@@ -1050,6 +1064,7 @@ export function DatePickerInputSimple(props: DatePickerInputSimpleProps) {
1050
1064
  label,
1051
1065
  error,
1052
1066
  invalid = false,
1067
+ id,
1053
1068
  } = props;
1054
1069
  const [open, setOpen] = useState(false);
1055
1070
  const portalContainer = usePortalContainer();
@@ -1112,6 +1127,8 @@ export function DatePickerInputSimple(props: DatePickerInputSimpleProps) {
1112
1127
  <Button
1113
1128
  variant="field"
1114
1129
  type="button"
1130
+ id={id}
1131
+ aria-describedby={triggerValueId(id)}
1115
1132
  disabled={disabled}
1116
1133
  // Same marker Input/Textarea/Select expose — it is how a form finds
1117
1134
  // (and scrolls to) the first field that failed validation.
@@ -1120,6 +1137,7 @@ export function DatePickerInputSimple(props: DatePickerInputSimpleProps) {
1120
1137
  >
1121
1138
  <Calendar className="size-6 shrink-0 text-ods-text-secondary" />
1122
1139
  <span
1140
+ id={triggerValueId(id)}
1123
1141
  className={cn(
1124
1142
  'flex-1 truncate text-left',
1125
1143
  disabled
@@ -1181,7 +1199,7 @@ export function DatePickerInputSimple(props: DatePickerInputSimpleProps) {
1181
1199
  );
1182
1200
 
1183
1201
  return (
1184
- <FieldWrapper label={label} error={error} errorSlot={'error' in props} className={className}>
1202
+ <FieldWrapper label={label} htmlFor={id} error={error} errorSlot={'error' in props} className={className}>
1185
1203
  {content}
1186
1204
  </FieldWrapper>
1187
1205
  );
@@ -2,12 +2,24 @@
2
2
 
3
3
  import { createContext, createElement, type ReactNode, useContext } from 'react';
4
4
  import { LAYOUT_STEPS } from '../../styles/layout-steps';
5
- import { useLgUp, useMdUp } from './use-media-query';
5
+ import { breakpoints, useLgUp, useMdUp, useMediaQuery } from './use-media-query';
6
6
 
7
7
  export type ContentBreakpoint = 'mobile' | 'tablet' | 'desktop';
8
8
 
9
9
  /** Content-area steps, in px of the area's own width. */
10
- export const CONTENT_BREAKPOINTS = { md: LAYOUT_STEPS.md.content, lg: LAYOUT_STEPS.lg.content } as const;
10
+ export const CONTENT_BREAKPOINTS = {
11
+ md: LAYOUT_STEPS.md.content,
12
+ lg: LAYOUT_STEPS.lg.content,
13
+ xl: LAYOUT_STEPS.xl.content,
14
+ } as const;
15
+
16
+ /**
17
+ * How many content steps a width has passed, `xl` included: `AppLayout` publishes a new width only when this changes,
18
+ * so every reader below sees each crossing it asks about (`useContentBreakpoint`, `useContentXlUp`).
19
+ */
20
+ export function contentStepFor(width: number): number {
21
+ return [CONTENT_BREAKPOINTS.md, CONTENT_BREAKPOINTS.lg, CONTENT_BREAKPOINTS.xl].filter(step => width >= step).length;
22
+ }
11
23
 
12
24
  /**
13
25
  * Width of the enclosing content area (`AppLayout` with a side panel or a composer
@@ -58,3 +70,10 @@ export function useContentLgUp(): boolean | undefined {
58
70
  const breakpoint = useContentBreakpoint();
59
71
  return breakpoint === undefined ? undefined : breakpoint === 'desktop';
60
72
  }
73
+
74
+ /** The `content-xl:` step in JS: the content area's width from `xl`, the viewport's outside one. */
75
+ export function useContentXlUp(): boolean | undefined {
76
+ const width = useContext(ContentAreaWidthContext);
77
+ const viewportXlUp = useMediaQuery(breakpoints.xl);
78
+ return width === null ? viewportXlUp : width >= CONTENT_BREAKPOINTS.xl;
79
+ }
@@ -40,6 +40,7 @@ export function useMediaQuery(query: string): boolean | undefined {
40
40
  export const breakpoints = {
41
41
  md: `(min-width: ${LAYOUT_STEPS.md.viewport}px)`, // Tablet
42
42
  lg: `(min-width: ${LAYOUT_STEPS.lg.viewport}px)`, // Desktop
43
+ xl: `(min-width: ${LAYOUT_STEPS.xl.viewport}px)`, // Wide desktop
43
44
  };
44
45
 
45
46
  /** @deprecated Use useMdUp instead */
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The entry of a value → presentation table for a value off the wire, `undefined` for one the table does not know.
3
+ * Own keys only: `in` or a bare index would answer `toString` or `constructor` with the prototype's function.
4
+ */
5
+ export function presentationFor<K extends string, V>(
6
+ map: Readonly<Record<K, V>>,
7
+ key: string | null | undefined,
8
+ ): V | undefined {
9
+ return key != null && Object.prototype.hasOwnProperty.call(map, key) ? map[key as K] : undefined;
10
+ }
@@ -1,46 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});"use client";
2
-
3
-
4
-
5
-
6
- var _chunkCOWMXUHNcjs = require('./chunk-COWMXUHN.cjs');
7
-
8
- // src/hooks/ui/use-content-breakpoint.ts
9
- var _react = require('react');
10
- var CONTENT_BREAKPOINTS = { md: _chunkCOWMXUHNcjs.LAYOUT_STEPS.md.content, lg: _chunkCOWMXUHNcjs.LAYOUT_STEPS.lg.content };
11
- var ContentAreaWidthContext = _react.createContext.call(void 0, null);
12
- function ViewportBreakpoints({ children }) {
13
- return _react.createElement.call(void 0, ContentAreaWidthContext.Provider, { value: null }, children);
14
- }
15
- function contentBreakpointFor(width) {
16
- if (width >= CONTENT_BREAKPOINTS.lg) return "desktop";
17
- if (width >= CONTENT_BREAKPOINTS.md) return "tablet";
18
- return "mobile";
19
- }
20
- function useContentBreakpoint() {
21
- const width = _react.useContext.call(void 0, ContentAreaWidthContext);
22
- const mdUp = _chunkCOWMXUHNcjs.useMdUp.call(void 0, );
23
- const lgUp = _chunkCOWMXUHNcjs.useLgUp.call(void 0, );
24
- if (width !== null) return contentBreakpointFor(width);
25
- if (mdUp === void 0) return void 0;
26
- if (lgUp) return "desktop";
27
- return mdUp ? "tablet" : "mobile";
28
- }
29
- function useContentMdUp() {
30
- const breakpoint = useContentBreakpoint();
31
- return breakpoint === void 0 ? void 0 : breakpoint !== "mobile";
32
- }
33
- function useContentLgUp() {
34
- const breakpoint = useContentBreakpoint();
35
- return breakpoint === void 0 ? void 0 : breakpoint === "desktop";
36
- }
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
- exports.CONTENT_BREAKPOINTS = CONTENT_BREAKPOINTS; exports.ContentAreaWidthContext = ContentAreaWidthContext; exports.ViewportBreakpoints = ViewportBreakpoints; exports.contentBreakpointFor = contentBreakpointFor; exports.useContentBreakpoint = useContentBreakpoint; exports.useContentMdUp = useContentMdUp; exports.useContentLgUp = useContentLgUp;