@flamingo-stack/openframe-frontend-core 0.0.770 → 0.0.772

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 (168) hide show
  1. package/dist/build/export-map.json +1 -1
  2. package/dist/{chunk-C66DWLCR.cjs → chunk-2MAENZL3.cjs} +37 -37
  3. package/dist/{chunk-2MND64QJ.cjs → chunk-5HBGBLJT.cjs} +50 -63
  4. package/dist/{chunk-4EJU462O.cjs → chunk-5V7BIUGA.cjs} +2 -2
  5. package/dist/{chunk-HK6UQVOI.cjs → chunk-7FYOVKQD.cjs} +12 -12
  6. package/dist/{chunk-MYA6NR2C.cjs → chunk-CTRJNTYU.cjs} +2 -2
  7. package/dist/{chunk-S5EUREOB.cjs → chunk-E25TJPWV.cjs} +3 -3
  8. package/dist/{chunk-HQF62LAU.cjs → chunk-FPCGKH5K.cjs} +28 -28
  9. package/dist/{chunk-VATCQOVC.cjs → chunk-FT5NPAZY.cjs} +17 -17
  10. package/dist/{chunk-I2TSCJT2.cjs → chunk-HFNWBNLE.cjs} +4 -4
  11. package/dist/{chunk-MFRIFVDV.cjs → chunk-IHCHU355.cjs} +41 -41
  12. package/dist/{chunk-NZELWPKO.cjs → chunk-ILZEXCJH.cjs} +98 -98
  13. package/dist/{chunk-DCBZMWLY.cjs → chunk-J5ZNEAGY.cjs} +24 -24
  14. package/dist/{chunk-BLTNKLAX.cjs → chunk-JJNOAYD4.cjs} +4 -4
  15. package/dist/{chunk-QS4G7CHU.cjs → chunk-KSCEAIBS.cjs} +2 -2
  16. package/dist/{chunk-Q575DR36.cjs → chunk-QOEOY7BJ.cjs} +33 -33
  17. package/dist/{chunk-SL7KFZWJ.cjs → chunk-QPK2433W.cjs} +24 -24
  18. package/dist/{chunk-VL44GB2I.cjs → chunk-REFL3YNE.cjs} +13 -13
  19. package/dist/{chunk-37XVY2RE.cjs → chunk-S3NEBAKD.cjs} +6 -6
  20. package/dist/{chunk-TBKXXMBU.cjs → chunk-UR3I3J5K.cjs} +90 -86
  21. package/dist/{chunk-MZLWB5AH.cjs → chunk-V3W56L2D.cjs} +18 -9
  22. package/dist/{chunk-4BRO3RZ6.cjs → chunk-VZAOLKKF.cjs} +72 -80
  23. package/dist/{chunk-QMUKTHWM.cjs → chunk-W5LX7DFB.cjs} +3 -3
  24. package/dist/{chunk-ZVNL5OK6.cjs → chunk-X5Z4HRH7.cjs} +2 -2
  25. package/dist/{chunk-WUYL4BMD.cjs → chunk-YI3CDGKM.cjs} +70 -70
  26. package/dist/{chunk-BZLHU4EK.cjs → chunk-ZVKJKWAB.cjs} +713 -695
  27. package/dist/{chunk-QSLKTDZ4.cjs → chunk-ZYGC2A7G.cjs} +4 -4
  28. package/dist/{cloud-tenants-IAZQL2JF.cjs → cloud-tenants-CUNC5KBD.cjs} +8 -8
  29. package/dist/components/case-studies/index.cjs +32 -30
  30. package/dist/components/case-studies/quote-cards-strip.d.ts +7 -2
  31. package/dist/components/case-studies/quote-cards-strip.d.ts.map +1 -1
  32. package/dist/components/case-studies/quote-cards-strip.js +7 -5
  33. package/dist/components/categories-cart.d.ts +9 -2
  34. package/dist/components/categories-cart.d.ts.map +1 -1
  35. package/dist/components/categories-cart.js +9 -4
  36. package/dist/components/chat/entity-cards/admin-content-card-grid.d.ts +5 -1
  37. package/dist/components/chat/entity-cards/admin-content-card-grid.d.ts.map +1 -1
  38. package/dist/components/chat/entity-cards/program-card.d.ts.map +1 -1
  39. package/dist/components/chat/entity-cards/program-card.js +2 -2
  40. package/dist/components/chat/index.cjs +5 -5
  41. package/dist/components/contact/index.cjs +9 -9
  42. package/dist/components/docs/index.cjs +12 -12
  43. package/dist/components/embeds/index.cjs +9 -9
  44. package/dist/components/faq/faq-section.d.ts +7 -4
  45. package/dist/components/faq/faq-section.d.ts.map +1 -1
  46. package/dist/components/faq/index.cjs +10 -10
  47. package/dist/components/faq-accordion.d.ts +2 -4
  48. package/dist/components/faq-accordion.d.ts.map +1 -1
  49. package/dist/components/features/capability-explorer.d.ts +8 -2
  50. package/dist/components/features/capability-explorer.d.ts.map +1 -1
  51. package/dist/components/features/capability-explorer.js +7 -3
  52. package/dist/components/features/cards-strip.d.ts +2 -2
  53. package/dist/components/features/index.cjs +8 -8
  54. package/dist/components/features/model-token-rates.js +2 -2
  55. package/dist/components/features/remote-session/remote-desktop-chat-panel.js +2 -2
  56. package/dist/components/help-center-pages/index.cjs +94 -103
  57. package/dist/components/help-center-pages/trust-center-page.d.ts +1 -1
  58. package/dist/components/help-center-pages/trust-center-sections.d.ts +7 -4
  59. package/dist/components/help-center-pages/trust-center-sections.d.ts.map +1 -1
  60. package/dist/components/help-center-pages/trust-center-sections.js +5 -14
  61. package/dist/components/index.cjs +66 -61
  62. package/dist/components/logs-list.d.ts.map +1 -1
  63. package/dist/components/logs-list.js +3 -2
  64. package/dist/components/meeting-scheduler/index.cjs +47 -47
  65. package/dist/components/navigation/index.cjs +11 -11
  66. package/dist/components/navigation/multi-level-navigation.d.ts +6 -7
  67. package/dist/components/navigation/multi-level-navigation.d.ts.map +1 -1
  68. package/dist/components/onboarding-guides/index.cjs +11 -11
  69. package/dist/components/onboarding-guides/onboarding-guides-catalog-skeleton.d.ts +5 -5
  70. package/dist/components/onboarding-guides/onboarding-guides-catalog-skeleton.d.ts.map +1 -1
  71. package/dist/components/onboarding-guides/onboarding-guides-catalog-skeleton.js +3 -3
  72. package/dist/components/product-demo/index.cjs +18 -18
  73. package/dist/components/related-content/index.cjs +5 -5
  74. package/dist/components/related-content/related-content-section.d.ts.map +1 -1
  75. package/dist/components/related-content/related-content-section.js +4 -17
  76. package/dist/components/shared/dev-section/dev-card-row.d.ts +3 -3
  77. package/dist/components/shared/dev-section/dev-card-row.d.ts.map +1 -1
  78. package/dist/components/shared/product-release/release-detail-page.js +2 -2
  79. package/dist/components/shared/roadmap/roadmap-grid-skeleton.d.ts +6 -1
  80. package/dist/components/shared/roadmap/roadmap-grid-skeleton.d.ts.map +1 -1
  81. package/dist/components/shared/roadmap/roadmap-grid-skeleton.js +3 -1
  82. package/dist/components/shared/roadmap/roadmap-grid.d.ts +10 -3
  83. package/dist/components/shared/roadmap/roadmap-grid.d.ts.map +1 -1
  84. package/dist/components/shared/roadmap/roadmap-grid.js +6 -3
  85. package/dist/components/tickets/help-center-list.js +4 -4
  86. package/dist/components/tickets/hooks/use-tickets-list.d.ts +2 -0
  87. package/dist/components/tickets/hooks/use-tickets-list.d.ts.map +1 -1
  88. package/dist/components/tickets/hooks/use-tickets-list.js +3 -2
  89. package/dist/components/tickets/index.cjs +14 -14
  90. package/dist/components/tickets/ticket-center.d.ts.map +1 -1
  91. package/dist/components/tickets/ticket-center.js +3 -9
  92. package/dist/components/ui/autocomplete.d.ts +5 -0
  93. package/dist/components/ui/autocomplete.d.ts.map +1 -1
  94. package/dist/components/ui/autocomplete.js +3 -2
  95. package/dist/components/ui/data-table/data-table-body.d.ts +5 -2
  96. package/dist/components/ui/data-table/data-table-body.d.ts.map +1 -1
  97. package/dist/components/ui/data-table/data-table-body.js +3 -1
  98. package/dist/components/ui/data-table/data-table-infinite-footer.d.ts +5 -2
  99. package/dist/components/ui/data-table/data-table-infinite-footer.d.ts.map +1 -1
  100. package/dist/components/ui/data-table/data-table-infinite-footer.js +5 -2
  101. package/dist/components/ui/data-table/utils.d.ts +21 -0
  102. package/dist/components/ui/data-table/utils.d.ts.map +1 -1
  103. package/dist/components/ui/data-table/utils.js +7 -0
  104. package/dist/components/ui/index.cjs +8 -8
  105. package/dist/components/ui/search-input.d.ts +3 -1
  106. package/dist/components/ui/search-input.d.ts.map +1 -1
  107. package/dist/components/ui/search-input.js +3 -2
  108. package/dist/components/ui/searchable-select.d.ts +2 -0
  109. package/dist/components/ui/searchable-select.d.ts.map +1 -1
  110. package/dist/components/ui/searchable-select.js +10 -6
  111. package/dist/components/ui/select.d.ts +11 -1
  112. package/dist/components/ui/select.d.ts.map +1 -1
  113. package/dist/components/ui/select.js +13 -5
  114. package/dist/components/ui/table/table-skeleton.d.ts +2 -2
  115. package/dist/components/ui/table/table.d.ts +1 -1
  116. package/dist/components/ui/table/table.d.ts.map +1 -1
  117. package/dist/components/ui/table/table.js +2 -1
  118. package/dist/components/ui/table/types.d.ts +1 -0
  119. package/dist/components/ui/table/types.d.ts.map +1 -1
  120. package/dist/{devices-7FDK2CGV.cjs → devices-TO4VUO7F.cjs} +9 -9
  121. package/dist/{embeddable-chat-3FR2RCHM.cjs → embeddable-chat-CEAP424I.cjs} +73 -73
  122. package/dist/index.cjs +15 -14
  123. package/dist/{knowledge-ZLHYBG4L.cjs → knowledge-CYJP4XYY.cjs} +9 -9
  124. package/dist/{logs-G6AFN7A6.cjs → logs-NL3ZBQKC.cjs} +16 -16
  125. package/dist/{policies-SEOK4IQO.cjs → policies-WTUV4EIT.cjs} +12 -12
  126. package/dist/{remote-session-WVORQV7V.cjs → remote-session-TC3BZDBZ.cjs} +4 -4
  127. package/dist/{scripts-HH4YVUBO.cjs → scripts-EE6MLBQP.cjs} +15 -15
  128. package/dist/{software-update-4A3XJZQB.cjs → software-update-HX6PN4SB.cjs} +12 -12
  129. package/dist/types/category.d.ts +5 -0
  130. package/dist/types/category.d.ts.map +1 -1
  131. package/dist/types/logs.types.d.ts +2 -0
  132. package/dist/types/logs.types.d.ts.map +1 -1
  133. package/package.json +1 -1
  134. package/src/components/case-studies/quote-cards-strip.tsx +16 -7
  135. package/src/components/categories-cart.tsx +16 -5
  136. package/src/components/chat/entity-cards/admin-content-card-grid.tsx +5 -1
  137. package/src/components/chat/entity-cards/program-card.tsx +6 -3
  138. package/src/components/faq/faq-section.tsx +7 -4
  139. package/src/components/faq-accordion.tsx +2 -4
  140. package/src/components/features/capability-explorer.tsx +16 -4
  141. package/src/components/features/cards-strip.tsx +3 -3
  142. package/src/components/features/model-token-rates.tsx +4 -4
  143. package/src/components/features/remote-session/remote-desktop-chat-panel.tsx +4 -4
  144. package/src/components/help-center-pages/trust-center-page.tsx +1 -1
  145. package/src/components/help-center-pages/trust-center-sections.tsx +15 -22
  146. package/src/components/logs-list.tsx +6 -5
  147. package/src/components/navigation/multi-level-navigation.tsx +6 -7
  148. package/src/components/onboarding-guides/onboarding-guides-catalog-skeleton.tsx +6 -6
  149. package/src/components/related-content/related-content-section.tsx +6 -27
  150. package/src/components/shared/dev-section/dev-card-row.tsx +3 -3
  151. package/src/components/shared/product-release/release-detail-page.tsx +2 -2
  152. package/src/components/shared/roadmap/roadmap-grid-skeleton.tsx +8 -2
  153. package/src/components/shared/roadmap/roadmap-grid.tsx +14 -4
  154. package/src/components/tickets/help-center-list.tsx +4 -4
  155. package/src/components/tickets/hooks/use-tickets-list.ts +3 -2
  156. package/src/components/tickets/ticket-center.tsx +4 -13
  157. package/src/components/ui/autocomplete.tsx +12 -3
  158. package/src/components/ui/data-table/data-table-body.tsx +8 -2
  159. package/src/components/ui/data-table/data-table-infinite-footer.tsx +10 -3
  160. package/src/components/ui/data-table/utils.ts +18 -0
  161. package/src/components/ui/search-input.tsx +9 -3
  162. package/src/components/ui/searchable-select.tsx +18 -9
  163. package/src/components/ui/select.tsx +28 -7
  164. package/src/components/ui/table/table-skeleton.tsx +2 -2
  165. package/src/components/ui/table/table.tsx +4 -1
  166. package/src/components/ui/table/types.ts +2 -1
  167. package/src/types/category.ts +5 -0
  168. package/src/types/logs.types.ts +2 -0
@@ -24,7 +24,6 @@ import { RefreshCw } from 'lucide-react';
24
24
  import { type ReactNode, useCallback, useState } from 'react';
25
25
  import { toast as defaultToast } from '../../hooks/use-toast';
26
26
  import type { DataSync } from '../../types/data-sync';
27
- import { cn } from '../../utils/cn';
28
27
  import { devSectionAnchorId } from '../../utils/dev-sections/dev-section-param-keys';
29
28
  import { ChatTicketItemSkeleton } from '../chat/entity-cards/chat-ticket-item';
30
29
  import { useChatIdentity } from '../chat/hooks/use-chat-identity';
@@ -36,7 +35,7 @@ import { Card } from '../ui/card';
36
35
  import { DataSyncLine } from '../ui/data-attribution';
37
36
  import { LoadingScope, useLoadingScope } from '../ui/loading-scope';
38
37
  import { useTicketActions } from './hooks/use-ticket-actions';
39
- import { useTicketsList } from './hooks/use-tickets-list';
38
+ import { TICKETS_PAGE_SIZE, useTicketsList } from './hooks/use-tickets-list';
40
39
  import { TicketOpenForm, type TicketOpenFormProps } from './ticket-open-form';
41
40
  import { TicketRow } from './ticket-row';
42
41
  import type { AnyTicket, OptimisticTicket, TicketData } from './types';
@@ -46,11 +45,6 @@ import { isOptimistic } from './types';
46
45
  const TICKET_CENTER_CLASS = 'flex flex-col gap-6';
47
46
  const TICKET_LIST_BLOCK_CLASS = 'flex flex-col gap-2';
48
47
  const TICKET_ROW_FRAME_CLASS = 'border-b border-ods-border last:border-b-0';
49
- /**
50
- * Ticket rows the list's skeleton draws: what one screen shows under the open-ticket form (an 80px row; the form
51
- * leaves room for four on a 1440x900 desktop and, stacked, for two on a 390x844 phone).
52
- */
53
- const TICKET_LIST_FOLD = { phone: 2, wide: 4 } as const;
54
48
 
55
49
  export interface TicketCenterProps {
56
50
  /** Optional toast override (test-friendly). Defaults to the lib's
@@ -312,18 +306,15 @@ function TicketListHeader({
312
306
  );
313
307
  }
314
308
 
309
+ /** The ticket list while it is read: ONE FULL PAGE of rows (the list reads `TICKETS_PAGE_SIZE` tickets), at every width. */
315
310
  function TicketListSkeleton() {
316
311
  return (
317
312
  <Card className="overflow-hidden">
318
- {Array.from({ length: Math.max(TICKET_LIST_FOLD.phone, TICKET_LIST_FOLD.wide) }, (_, i) => (
313
+ {Array.from({ length: TICKETS_PAGE_SIZE }, (_, i) => (
319
314
  <div
320
315
  // biome-ignore lint/suspicious/noArrayIndexKey: skeleton rows never reorder
321
316
  key={i}
322
- className={cn(
323
- TICKET_ROW_FRAME_CLASS,
324
- i >= TICKET_LIST_FOLD.phone && 'hidden md:block',
325
- i >= TICKET_LIST_FOLD.wide && 'md:hidden',
326
- )}
317
+ className={TICKET_ROW_FRAME_CLASS}
327
318
  >
328
319
  <ChatTicketItemSkeleton />
329
320
  </div>
@@ -102,6 +102,11 @@ interface AutocompleteBaseProps<T = string> {
102
102
  loading?: boolean;
103
103
  /** Loading text */
104
104
  loadingText?: string;
105
+ /**
106
+ * Option rows the list draws while `loading`: the options the host reads (its search's limit). Default a typical
107
+ * count.
108
+ */
109
+ loadingRows?: number;
105
110
  /**
106
111
  * The field itself is read and has not arrived (its value and its options): it draws its skeleton, as in a
107
112
  * loading scope. DISTINCT from `loading`, a search in flight under a field that stays up.
@@ -172,8 +177,11 @@ const AUTOCOMPLETE_ANCHOR_HEIGHT_CLASS = 'min-h-11 md:min-h-12';
172
177
  /** An option row's frame, read by the rows and by the list's loading state. */
173
178
  const AUTOCOMPLETE_OPTION_ROW_CLASS =
174
179
  'flex min-h-11 items-center border-b border-ods-border px-[var(--spacing-system-mf)] py-[var(--spacing-system-xsf)] last:border-b-0 md:min-h-12 text-h4';
175
- /** Option rows the list's loading state draws: the 240px scroller holds five 48px rows. */
176
- const AUTOCOMPLETE_OPTIONS_FOLD = 5;
180
+ /**
181
+ * Option rows the list's loading state draws when its host states no `loadingRows`: a typical count. A host that
182
+ * reads its options with a limit (a server search) passes that limit.
183
+ */
184
+ const AUTOCOMPLETE_LOADING_ROWS = 5;
177
185
  const AUTOCOMPLETE_ANCHOR_SHAPE_CLASS = 'rounded-[6px]';
178
186
 
179
187
  export interface AutocompleteSkeletonProps {
@@ -214,6 +222,7 @@ function AutocompleteInner<T = string>(props: AutocompleteProps<T>, ref: Forward
214
222
  onInputChange,
215
223
  loading = false,
216
224
  loadingText = 'Loading options',
225
+ loadingRows = AUTOCOMPLETE_LOADING_ROWS,
217
226
  fieldLoading = false,
218
227
  creatable = false,
219
228
  onCreateOption,
@@ -690,7 +699,7 @@ function AutocompleteInner<T = string>(props: AutocompleteProps<T>, ref: Forward
690
699
  // The options while they are read: the list's own rows, a label bar each (`loadingText` is what a
691
700
  // screen reader hears).
692
701
  <LoadingRegion label={loadingText}>
693
- {Array.from({ length: AUTOCOMPLETE_OPTIONS_FOLD }, (_, i) => (
702
+ {Array.from({ length: loadingRows }, (_, i) => (
694
703
  // biome-ignore lint/suspicious/noArrayIndexKey: skeleton rows never reorder
695
704
  <div key={i} className={AUTOCOMPLETE_OPTION_ROW_CLASS} aria-hidden>
696
705
  <TextLineSkeleton type="inherit" className="w-2/3" />
@@ -11,6 +11,7 @@ import { DataTableEmpty } from './data-table-empty';
11
11
  import { useDataTableLoadMoreStore } from './data-table-load-more';
12
12
  import { DataTableRow } from './data-table-row';
13
13
  import { DataTableSkeleton, PlaceholderRows, ReservedEmptyState, ROW_STACK_CLASSES } from './data-table-skeleton';
14
+ import { DATA_TABLE_SKELETON_ROWS, dataTablePageSize } from './utils';
14
15
 
15
16
  export interface DataTableBodyProps<T = unknown> {
16
17
  /** Show skeleton rows while `loading` is true and data is empty. */
@@ -19,7 +20,10 @@ export interface DataTableBodyProps<T = unknown> {
19
20
  emptyMessage?: string;
20
21
  /** Props for the empty state (NoData) shown when there are no rows. Overrides `emptyMessage`. */
21
22
  emptyState?: NoDataProps;
22
- /** Skeleton row count when loading. Default `10`. */
23
+ /**
24
+ * Skeleton rows while loading: ONE FULL PAGE. Default the table's page size (`state.pagination.pageSize`), else
25
+ * `minRows`, else `DATA_TABLE_SKELETON_ROWS` (a typical count for an unpaged table).
26
+ */
23
27
  skeletonRows?: number;
24
28
  /**
25
29
  * The loading state announces itself (`LoadingRegion`: aria-busy plus one "Loading results" status).
@@ -101,7 +105,7 @@ export function DataTableBody<T = unknown>({
101
105
  loading: loadingProp,
102
106
  emptyMessage,
103
107
  emptyState,
104
- skeletonRows = 10,
108
+ skeletonRows: skeletonRowsProp,
105
109
  loadingRegion = true,
106
110
  className,
107
111
  rowClassName,
@@ -113,6 +117,8 @@ export function DataTableBody<T = unknown>({
113
117
  renderSubRow,
114
118
  }: DataTableBodyProps<T>) {
115
119
  const table = useDataTableContext<T>();
120
+ const skeletonRows =
121
+ skeletonRowsProp ?? dataTablePageSize(table) ?? (minRows && minRows > 0 ? minRows : DATA_TABLE_SKELETON_ROWS);
116
122
  // In a page's loading state the body is loading: its rows are the row skeletons.
117
123
  const inScope = useLoadingScope();
118
124
  const loading = inScope || loadingProp;
@@ -7,12 +7,16 @@ import { Button } from '../button';
7
7
  import { useOptionalDataTableContext } from './data-table';
8
8
  import { useDataTableLoadMoreStore } from './data-table-load-more';
9
9
  import { DataTableSkeleton, ROW_STACK_CLASSES } from './data-table-skeleton';
10
+ import { dataTablePageSize } from './utils';
10
11
 
11
12
  export interface DataTableInfiniteFooterProps {
12
13
  hasNextPage: boolean;
13
14
  isFetchingNextPage: boolean;
14
15
  onLoadMore: () => void;
15
- /** Skeleton rows shown while fetching. Default `3`. */
16
+ /**
17
+ * Skeleton rows shown while the next page is fetched: that page, in full. Default the table's page size
18
+ * (`state.pagination.pageSize`), else a typical `3`.
19
+ */
16
20
  skeletonRows?: number;
17
21
  /**
18
22
  * `IntersectionObserver` rootMargin — how far ahead of the sentinel the next
@@ -93,7 +97,7 @@ export function DataTableInfiniteFooter({
93
97
  hasNextPage,
94
98
  isFetchingNextPage,
95
99
  onLoadMore,
96
- skeletonRows = 3,
100
+ skeletonRows: skeletonRowsProp,
97
101
  rootMargin = DEFAULT_ROOT_MARGIN,
98
102
  loadedCount,
99
103
  }: DataTableInfiniteFooterProps) {
@@ -106,6 +110,9 @@ export function DataTableInfiniteFooter({
106
110
  onLoadMoreRef.current = onLoadMore;
107
111
  });
108
112
 
113
+ const table = useOptionalDataTableContext();
114
+ const skeletonRows = skeletonRowsProp ?? (table ? dataTablePageSize(table) : undefined) ?? 3;
115
+
109
116
  const loadMore = useDataTableLoadMoreStore();
110
117
  const bodyDrawsRows = useSyncExternalStore(loadMore.subscribe, loadMore.getHasRowsBody, getNoBody);
111
118
  const requestedRows = isFetchingNextPage ? skeletonRows : 0;
@@ -122,7 +129,7 @@ export function DataTableInfiniteFooter({
122
129
  // TAIL (how many rows, and which one is last) rather than the count alone: a
123
130
  // search that replaces 40 rows with 20 while a page is in flight ends that
124
131
  // fetch with FEWER rows, and that is a new list, not a stalled one.
125
- const tableRows = useOptionalDataTableContext()?.getRowModel().rows;
132
+ const tableRows = table?.getRowModel().rows;
126
133
  const progressCount = loadedCount ?? tableRows?.length;
127
134
  const lastRowId = tableRows?.[tableRows.length - 1]?.id ?? '';
128
135
  const tail = progressCount === undefined ? null : `${progressCount}:${lastRowId}`;
@@ -107,3 +107,21 @@ export const multiSelectFilterFn: FilterFn<unknown> = (row, columnId, filterValu
107
107
  const cellValue = row.getValue(columnId);
108
108
  return values.some(v => v === cellValue || String(v) === String(cellValue));
109
109
  };
110
+
111
+ /** Skeleton rows of a table that knows no page size: a typical count for an UNPAGED table. */
112
+ export const DATA_TABLE_SKELETON_ROWS = 10;
113
+
114
+ /**
115
+ * The page size a host gave its table (`state.pagination.pageSize`, else `initialState.pagination.pageSize`), or
116
+ * undefined when it gave none. Read from the OPTIONS, never `getState()`: TanStack fills in a page size of 10 for a
117
+ * table that has no pagination at all, which is not a page this table loads.
118
+ */
119
+ export function dataTablePageSize(table: {
120
+ options: {
121
+ state?: { pagination?: { pageSize?: number } };
122
+ initialState?: { pagination?: { pageSize?: number } };
123
+ };
124
+ }): number | undefined {
125
+ const size = table.options.state?.pagination?.pageSize ?? table.options.initialState?.pagination?.pageSize;
126
+ return size && size > 0 ? size : undefined;
127
+ }
@@ -26,8 +26,11 @@ import { Tag } from './tag';
26
26
 
27
27
  /** A result row's frame, read by the rows and by their loading state. */
28
28
  const SEARCH_RESULT_ROW_CLASS = 'flex min-h-10 items-center border-b border-ods-border px-3 last:border-b-0';
29
- /** Rows the results' loading state draws: the dropdown's 320px scroller holds eight 40px rows. */
30
- const SEARCH_RESULTS_FOLD = 8;
29
+ /**
30
+ * Rows the results' loading state draws when its host states no `loadingRows`: a typical count. A host whose search
31
+ * answers a limited number of results passes that limit.
32
+ */
33
+ const SEARCH_RESULTS_LOADING_ROWS = 8;
31
34
 
32
35
  // ---------------------------------------------------------------------------
33
36
  // Types
@@ -68,6 +71,8 @@ export interface SearchInputProps {
68
71
  results?: SearchResult[];
69
72
  /** Whether results are loading */
70
73
  isLoading?: boolean;
74
+ /** Result rows drawn while `isLoading`: the results the host's search answers (its limit). Default a typical count. */
75
+ loadingRows?: number;
71
76
  /** Called when a result row is selected.
72
77
  *
73
78
  * `modifiers` carries the click event's modifier-key state when the
@@ -172,6 +177,7 @@ export function SearchInput({
172
177
  onSubmit,
173
178
  results = [],
174
179
  isLoading = false,
180
+ loadingRows = SEARCH_RESULTS_LOADING_ROWS,
175
181
  onResultSelect,
176
182
  debounceMs = 300,
177
183
  renderResult,
@@ -420,7 +426,7 @@ export function SearchInput({
420
426
  // The results while they are read: the dropdown's own rows, a title bar each.
421
427
  return (
422
428
  <LoadingRegion label="Loading results">
423
- {Array.from({ length: SEARCH_RESULTS_FOLD }, (_, i) => (
429
+ {Array.from({ length: loadingRows }, (_, i) => (
424
430
  // biome-ignore lint/suspicious/noArrayIndexKey: skeleton rows never reorder
425
431
  <div key={i} className={SEARCH_RESULT_ROW_CLASS} aria-hidden>
426
432
  <TextLineSkeleton type="h6" className="w-2/3" />
@@ -17,9 +17,12 @@ const SELECT_ROW_CLASS =
17
17
  'flex w-full items-center gap-[var(--spacing-system-xs)] px-[var(--spacing-system-sf)] py-[var(--spacing-system-xs)] text-left';
18
18
  const SELECT_TILE_CLASS =
19
19
  'flex min-w-0 flex-col items-center justify-center gap-[var(--spacing-system-xxs)] border-b border-r border-ods-border p-[var(--spacing-system-xs)]';
20
- /** Options the loading state draws: the `max-h-80` (320px) list holds eight 40px rows, or about twelve tiles. */
21
- const SELECT_ROWS_FOLD = 8;
22
- const SELECT_TILES_FOLD = 12;
20
+ /**
21
+ * Options the loading state draws (rows, or tiles in the grid layout) when its host states no `loadingRows`: a
22
+ * typical count. A host that reads its options with a limit passes that limit.
23
+ */
24
+ const SELECT_LOADING_ROWS = 8;
25
+ const SELECT_LOADING_TILES = 12;
23
26
 
24
27
  export interface SearchableSelectOption {
25
28
  value: string;
@@ -47,6 +50,8 @@ export interface SearchableSelectProps {
47
50
  emptyText?: string;
48
51
  loadingText?: string;
49
52
  isLoading?: boolean;
53
+ /** Options drawn while `isLoading`: the options the host reads (its limit). Default a typical count. */
54
+ loadingRows?: number;
50
55
  disabled?: boolean;
51
56
  /**
52
57
  * Custom trigger node (rendered via Popover.Trigger asChild). When omitted,
@@ -100,6 +105,7 @@ export function SearchableSelect(allProps: SearchableSelectProps) {
100
105
  emptyText = 'No results found',
101
106
  loadingText = 'Loading options',
102
107
  isLoading = false,
108
+ loadingRows,
103
109
  disabled = false,
104
110
  trigger,
105
111
  id,
@@ -248,12 +254,15 @@ export function SearchableSelect(allProps: SearchableSelectProps) {
248
254
  // The options while they are read: the list's own rows (or tiles), a label bar each; `loadingText` is
249
255
  // what a screen reader hears.
250
256
  <LoadingRegion label={loadingText} className="contents">
251
- {Array.from({ length: isGrid ? SELECT_TILES_FOLD : SELECT_ROWS_FOLD }, (_, i) => (
252
- // biome-ignore lint/suspicious/noArrayIndexKey: skeleton rows never reorder
253
- <div key={i} className={isGrid ? SELECT_TILE_CLASS : SELECT_ROW_CLASS} aria-hidden>
254
- <TextLineSkeleton type={isGrid ? 'h6' : 'h4'} className={isGrid ? 'w-full' : 'w-2/3'} />
255
- </div>
256
- ))}
257
+ {Array.from(
258
+ { length: loadingRows ?? (isGrid ? SELECT_LOADING_TILES : SELECT_LOADING_ROWS) },
259
+ (_, i) => (
260
+ // biome-ignore lint/suspicious/noArrayIndexKey: skeleton rows never reorder
261
+ <div key={i} className={isGrid ? SELECT_TILE_CLASS : SELECT_ROW_CLASS} aria-hidden>
262
+ <TextLineSkeleton type={isGrid ? 'h6' : 'h4'} className={isGrid ? 'w-full' : 'w-2/3'} />
263
+ </div>
264
+ ),
265
+ )}
257
266
  </LoadingRegion>
258
267
  ) : filtered.length === 0 ? (
259
268
  <div className={statusClass}>{emptyText}</div>
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as SelectPrimitive from '@radix-ui/react-select';
4
4
  import { ChevronDown, ChevronUp } from 'lucide-react';
5
- import { type ComponentPropsWithoutRef, type ComponentRef, type ElementRef, forwardRef } from 'react';
5
+ import { type ComponentPropsWithoutRef, type ComponentRef, type ElementRef, forwardRef, type ReactNode } from 'react';
6
6
  import { cn } from '../../utils/cn';
7
7
  import { Chevron02DownIcon } from '../icons-v2-generated/arrows/chevron-02-down-icon';
8
8
  import { CheckIcon } from '../icons-v2-generated/signs-and-symbols/check-icon';
@@ -154,14 +154,26 @@ SelectLabel.displayName = SelectPrimitive.Label.displayName;
154
154
 
155
155
  const SelectItem = forwardRef<
156
156
  ComponentRef<typeof SelectPrimitive.Item>,
157
- ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
158
- >(({ className, children, ...props }, ref) => (
157
+ ComponentPropsWithoutRef<typeof SelectPrimitive.Item> & {
158
+ /**
159
+ * A second, secondary-coloured line under the label. The row grows to
160
+ * fit it and both lines wrap, so a sentence longer than the list is read
161
+ * in full instead of being clipped at the edge. It sits outside
162
+ * `ItemText`, so the closed trigger mirrors the label alone and typeahead
163
+ * matches the label alone.
164
+ */
165
+ description?: ReactNode;
166
+ }
167
+ >(({ className, children, description, ...props }, ref) => (
159
168
  <SelectPrimitive.Item
160
169
  ref={ref}
161
170
  className={cn(
162
- 'flex h-11 w-full cursor-pointer select-none items-center justify-between border-b border-ods-border px-4 last:border-b-0 md:h-12',
171
+ 'flex w-full cursor-pointer select-none items-center justify-between gap-2 border-b border-ods-border px-4 last:border-b-0',
163
172
  // Typography - match trigger
164
- 'whitespace-nowrap text-h4',
173
+ 'text-h4',
174
+ description == null
175
+ ? 'h-11 whitespace-nowrap md:h-12'
176
+ : 'min-h-11 whitespace-normal py-[var(--spacing-system-xs)] md:min-h-12',
165
177
  // Hover state with visible background change
166
178
  'outline-none hover:bg-ods-bg-hover data-[highlighted]:bg-ods-bg-surface',
167
179
  'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
@@ -170,8 +182,17 @@ const SelectItem = forwardRef<
170
182
  )}
171
183
  {...props}
172
184
  >
173
- <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
174
- <SelectPrimitive.ItemIndicator>
185
+ {description == null ? (
186
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
187
+ ) : (
188
+ // min-w-0 lets the column shrink below its longest line, which is what
189
+ // makes the lines wrap instead of pushing past the list's edge.
190
+ <span className="flex min-w-0 flex-col text-left">
191
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
192
+ <span className="text-ods-text-secondary text-h6">{description}</span>
193
+ </span>
194
+ )}
195
+ <SelectPrimitive.ItemIndicator className="shrink-0">
175
196
  <CheckIcon className="text-ods-accent" size={20} />
176
197
  </SelectPrimitive.ItemIndicator>
177
198
  </SelectPrimitive.Item>
@@ -22,8 +22,8 @@ const ROW_HEIGHT_MOBILE = 'h-[66px]';
22
22
 
23
23
  /** @deprecated Use `DataTableSkeleton` from `data-table` instead. */
24
24
  /**
25
- * Rows a table's skeleton draws when its host states no count: one screen of rows under a page's header (a 68px row
26
- * card and its 8px gap; about ten fit under the header of a 1440x900 screen, nine of a phone's 66px rows).
25
+ * Rows a table's skeleton draws when it knows no page size: a typical count for an UNPAGED table. A table with page
26
+ * pagination draws its `pageSize` instead (one full page, at every width), and a host may state `skeletonRows`.
27
27
  */
28
28
  export const TABLE_SKELETON_ROWS = 10;
29
29
 
@@ -104,7 +104,7 @@ export function Table<T = TableRowData>({
104
104
  rowKey,
105
105
  loading: loadingProp = false,
106
106
  emptyMessage,
107
- skeletonRows = TABLE_SKELETON_ROWS,
107
+ skeletonRows: skeletonRowsProp,
108
108
  minRows,
109
109
  emptyState,
110
110
  keepHeightWhenEmpty,
@@ -137,6 +137,9 @@ export function Table<T = TableRowData>({
137
137
  animateRowReorder,
138
138
  actionsColumnWidth,
139
139
  }: TableProps<T>) {
140
+ // A loading table draws ONE FULL PAGE of rows: the host's count, else its page pagination's page size, else a
141
+ // typical count for an unpaged table.
142
+ const skeletonRows = skeletonRowsProp ?? pagePagination?.pageSize ?? TABLE_SKELETON_ROWS;
140
143
  // In a page's loading state the table is loading: its header real, its rows the row skeletons.
141
144
  const loading = useLoadingScope() || loadingProp;
142
145
  // Opt-in row-reorder animation: framer-motion is loaded lazily (its own chunk)
@@ -109,7 +109,8 @@ export interface TableProps<T = TableRowData> {
109
109
  emptyMessage?: string;
110
110
 
111
111
  // Skeleton configuration
112
- skeletonRows?: number; // Number of skeleton rows to show when loading (default: 10)
112
+ /** Skeleton rows while loading: one full page. Default `pagePagination.pageSize`, else `TABLE_SKELETON_ROWS`. */
113
+ skeletonRows?: number;
113
114
  /**
114
115
  * Keep the table height stable by padding with invisible rows when the page
115
116
  * is short, AND by reserving the same slots (empty state centred over them)
@@ -31,6 +31,11 @@ export interface RealCategoryCardProps {
31
31
  subcategoryCount?: number;
32
32
  vendors?: Vendor[];
33
33
  isLoading?: boolean;
34
+ /**
35
+ * The vendors the host reads for the card's strip (its fetch limit): the vendor icons the loading card draws.
36
+ * Default `CATEGORIES_CART_SKELETON_VENDORS`.
37
+ */
38
+ vendorLimit?: number;
34
39
  isLoadingVendorCount?: boolean;
35
40
  isLoadingSubcategoryCount?: boolean;
36
41
  className?: string;
@@ -21,6 +21,8 @@ export interface LogsListProps {
21
21
  showConnector?: boolean;
22
22
  onLogClick?: (log: LogEntry) => void;
23
23
  loading?: boolean;
24
+ /** Rows the loading state draws: the logs the host reads (its page size or limit). Default a typical count. */
25
+ skeletonRows?: number;
24
26
  emptyMessage?: string;
25
27
  className?: string;
26
28
  }