@flamingo-stack/openframe-frontend-core 0.0.323 → 0.0.324-snapshot.20260625182754

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 (93) hide show
  1. package/dist/{chunk-6ZCLKEXO.cjs → chunk-4USOR7KK.cjs} +25 -25
  2. package/dist/{chunk-6ZCLKEXO.cjs.map → chunk-4USOR7KK.cjs.map} +1 -1
  3. package/dist/{chunk-XVBNFRG7.cjs → chunk-5COLEZW6.cjs} +62 -62
  4. package/dist/{chunk-XVBNFRG7.cjs.map → chunk-5COLEZW6.cjs.map} +1 -1
  5. package/dist/{chunk-RTYIYX6C.js → chunk-5CWJGCOL.js} +4 -4
  6. package/dist/{chunk-OUN4V2CV.cjs → chunk-5DXWGCTW.cjs} +56 -56
  7. package/dist/{chunk-OUN4V2CV.cjs.map → chunk-5DXWGCTW.cjs.map} +1 -1
  8. package/dist/{chunk-OYRZCCVO.cjs → chunk-5GXMQPG7.cjs} +37 -37
  9. package/dist/{chunk-OYRZCCVO.cjs.map → chunk-5GXMQPG7.cjs.map} +1 -1
  10. package/dist/{chunk-ZEDY3IAB.cjs → chunk-5HRCKYFX.cjs} +9 -9
  11. package/dist/{chunk-ZEDY3IAB.cjs.map → chunk-5HRCKYFX.cjs.map} +1 -1
  12. package/dist/{chunk-XPDKUIGS.js → chunk-5YGKK636.js} +4 -4
  13. package/dist/{chunk-BUJZJN2K.js → chunk-6GSM6K5W.js} +2 -2
  14. package/dist/{chunk-GLKQDTCI.cjs → chunk-AYBSXRRC.cjs} +670 -532
  15. package/dist/chunk-AYBSXRRC.cjs.map +1 -0
  16. package/dist/{chunk-YLLX3O2D.js → chunk-EQSGC5WT.js} +2 -2
  17. package/dist/{chunk-S6QURVK4.js → chunk-GYWMN4AK.js} +4 -4
  18. package/dist/{chunk-AA2ZKWQV.js → chunk-HS2YU2NF.js} +2 -2
  19. package/dist/{chunk-4HII2ZLE.cjs → chunk-K2PS5MJH.cjs} +5 -5
  20. package/dist/{chunk-4HII2ZLE.cjs.map → chunk-K2PS5MJH.cjs.map} +1 -1
  21. package/dist/{chunk-SYRZBYKN.js → chunk-MIVP6B6P.js} +4 -4
  22. package/dist/{chunk-OI56RVPD.js → chunk-P3IIAUCL.js} +322 -184
  23. package/dist/chunk-P3IIAUCL.js.map +1 -0
  24. package/dist/{chunk-YNMGKOSL.cjs → chunk-QLSUOQMU.cjs} +7 -7
  25. package/dist/{chunk-YNMGKOSL.cjs.map → chunk-QLSUOQMU.cjs.map} +1 -1
  26. package/dist/{chunk-7CFMQIAC.cjs → chunk-U74OC6PM.cjs} +26 -26
  27. package/dist/{chunk-7CFMQIAC.cjs.map → chunk-U74OC6PM.cjs.map} +1 -1
  28. package/dist/{chunk-HDTW265W.js → chunk-UJIDDEXD.js} +2 -2
  29. package/dist/{chunk-MG72PRRZ.js → chunk-VSUBZBG7.js} +2 -2
  30. package/dist/{chunk-WEHLKMBD.js → chunk-VUE2FSVD.js} +2 -2
  31. package/dist/{chunk-5YWMPCTD.cjs → chunk-WAYJ7VBD.cjs} +12 -12
  32. package/dist/{chunk-5YWMPCTD.cjs.map → chunk-WAYJ7VBD.cjs.map} +1 -1
  33. package/dist/{chunk-4PAYSVBL.js → chunk-XFRQUV5M.js} +2 -2
  34. package/dist/{chunk-JJS5QJOX.cjs → chunk-XQBK7IVF.cjs} +15 -15
  35. package/dist/{chunk-JJS5QJOX.cjs.map → chunk-XQBK7IVF.cjs.map} +1 -1
  36. package/dist/{chunk-FCIMIGVA.cjs → chunk-XTV2EZHO.cjs} +31 -31
  37. package/dist/{chunk-FCIMIGVA.cjs.map → chunk-XTV2EZHO.cjs.map} +1 -1
  38. package/dist/components/case-studies/index.cjs +8 -8
  39. package/dist/components/case-studies/index.js +2 -2
  40. package/dist/components/chat/chat-composer.d.ts.map +1 -1
  41. package/dist/components/chat/chat-input.d.ts.map +1 -1
  42. package/dist/components/chat/index.cjs +2 -2
  43. package/dist/components/chat/index.js +1 -1
  44. package/dist/components/contact/index.cjs +3 -3
  45. package/dist/components/contact/index.js +2 -2
  46. package/dist/components/docs/index.cjs +5 -5
  47. package/dist/components/docs/index.js +4 -4
  48. package/dist/components/embeds/index.cjs +3 -3
  49. package/dist/components/embeds/index.js +2 -2
  50. package/dist/components/faq/index.cjs +3 -3
  51. package/dist/components/faq/index.js +2 -2
  52. package/dist/components/features/index.cjs +2 -2
  53. package/dist/components/features/index.js +1 -1
  54. package/dist/components/help-center-pages/index.cjs +20 -20
  55. package/dist/components/help-center-pages/index.js +11 -11
  56. package/dist/components/index.cjs +122 -120
  57. package/dist/components/index.cjs.map +1 -1
  58. package/dist/components/index.js +11 -9
  59. package/dist/components/index.js.map +1 -1
  60. package/dist/components/navigation/index.cjs +2 -2
  61. package/dist/components/navigation/index.js +1 -1
  62. package/dist/components/onboarding-guides/index.cjs +5 -5
  63. package/dist/components/onboarding-guides/index.js +4 -4
  64. package/dist/components/related-content/index.cjs +3 -3
  65. package/dist/components/related-content/index.js +2 -2
  66. package/dist/components/tickets/index.cjs +5 -5
  67. package/dist/components/tickets/index.js +4 -4
  68. package/dist/components/ui/date-picker.d.ts +47 -0
  69. package/dist/components/ui/date-picker.d.ts.map +1 -1
  70. package/dist/components/ui/index.cjs +4 -2
  71. package/dist/components/ui/index.cjs.map +1 -1
  72. package/dist/components/ui/index.js +3 -1
  73. package/dist/index.cjs +4 -2
  74. package/dist/index.cjs.map +1 -1
  75. package/dist/index.js +3 -1
  76. package/package.json +1 -1
  77. package/src/components/chat/chat-composer.tsx +6 -4
  78. package/src/components/chat/chat-input.tsx +5 -1
  79. package/src/components/ui/date-picker.tsx +214 -9
  80. package/src/stories/DatePicker.stories.tsx +141 -0
  81. package/dist/chunk-GLKQDTCI.cjs.map +0 -1
  82. package/dist/chunk-OI56RVPD.js.map +0 -1
  83. /package/dist/{chunk-RTYIYX6C.js.map → chunk-5CWJGCOL.js.map} +0 -0
  84. /package/dist/{chunk-XPDKUIGS.js.map → chunk-5YGKK636.js.map} +0 -0
  85. /package/dist/{chunk-BUJZJN2K.js.map → chunk-6GSM6K5W.js.map} +0 -0
  86. /package/dist/{chunk-YLLX3O2D.js.map → chunk-EQSGC5WT.js.map} +0 -0
  87. /package/dist/{chunk-S6QURVK4.js.map → chunk-GYWMN4AK.js.map} +0 -0
  88. /package/dist/{chunk-AA2ZKWQV.js.map → chunk-HS2YU2NF.js.map} +0 -0
  89. /package/dist/{chunk-SYRZBYKN.js.map → chunk-MIVP6B6P.js.map} +0 -0
  90. /package/dist/{chunk-HDTW265W.js.map → chunk-UJIDDEXD.js.map} +0 -0
  91. /package/dist/{chunk-MG72PRRZ.js.map → chunk-VSUBZBG7.js.map} +0 -0
  92. /package/dist/{chunk-WEHLKMBD.js.map → chunk-VUE2FSVD.js.map} +0 -0
  93. /package/dist/{chunk-4PAYSVBL.js.map → chunk-XFRQUV5M.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import * as Popover from "@radix-ui/react-popover";
4
- import { Calendar, ChevronLeft, ChevronRight } from "lucide-react";
4
+ import { ArrowUpDown, Calendar, ChevronLeft, ChevronRight } from "lucide-react";
5
5
  import * as React from "react";
6
6
  import {
7
7
  DayPicker,
@@ -19,6 +19,7 @@ import {
19
19
  SelectTrigger,
20
20
  SelectValue,
21
21
  } from "./select";
22
+ import type { SortDirection } from "./sort-column-item";
22
23
 
23
24
  // ============================================================================
24
25
  // Types
@@ -121,6 +122,12 @@ interface DatePickerCalendarProps {
121
122
  fromDate?: Date;
122
123
  toDate?: Date;
123
124
  locale?: DayPickerProps["locale"];
125
+ /**
126
+ * When true the calendar fills its container width and day cells flex to
127
+ * fit (instead of the fixed 40px cells). Used by DateFilterMenu so the grid
128
+ * lines up with the surrounding controls per the Figma filter-menu.
129
+ */
130
+ fluid?: boolean;
124
131
  }
125
132
 
126
133
  function DatePickerCalendar({
@@ -131,6 +138,7 @@ function DatePickerCalendar({
131
138
  fromDate,
132
139
  toDate,
133
140
  locale,
141
+ fluid = false,
134
142
  }: DatePickerCalendarProps) {
135
143
  const today = new Date();
136
144
 
@@ -174,28 +182,35 @@ function DatePickerCalendar({
174
182
  onSelect(completed);
175
183
  };
176
184
 
185
+ // Fixed 40px cells by default; in fluid mode cells flex to fill the width.
186
+ const cellOuter = fluid ? "flex-1 aspect-square min-w-0" : "size-10";
187
+ const cellInner = fluid ? "size-full" : "size-10";
188
+
177
189
  const classNames: DayPickerProps["classNames"] = {
178
- root: "p-4 date-picker-calendar",
190
+ root: cn("p-4 date-picker-calendar", fluid && "w-full"),
179
191
  months: "flex gap-8",
180
- month: "flex flex-col gap-2",
192
+ month: cn("flex flex-col gap-2", fluid && "w-full"),
181
193
  month_caption: "hidden",
182
194
  nav: "hidden",
183
- month_grid: "border-collapse",
195
+ month_grid: cn("border-collapse", fluid && "w-full"),
184
196
  weekdays: "flex",
185
197
  weekday: cn(
186
- "size-10 flex items-center justify-center",
198
+ cellOuter,
199
+ "flex items-center justify-center",
187
200
  "text-[14px] font-medium leading-5 text-ods-text-secondary"
188
201
  ),
189
202
  week: "flex",
190
203
  day: cn(
191
- "size-10 flex items-center justify-center",
204
+ cellOuter,
205
+ "flex items-center justify-center",
192
206
  "text-h4 text-ods-text-primary",
193
207
  "cursor-pointer",
194
208
  "transition-colors duration-150",
195
209
  "hover:bg-ods-bg-surface hover:rounded-[6px]"
196
210
  ),
197
211
  day_button: cn(
198
- "size-10 flex items-center justify-center",
212
+ cellInner,
213
+ "flex items-center justify-center",
199
214
  "cursor-pointer bg-transparent border-none outline-none",
200
215
  "text-inherit font-inherit"
201
216
  ),
@@ -291,7 +306,7 @@ function DatePickerCalendar({
291
306
 
292
307
  if (mode === "single") {
293
308
  return (
294
- <div className="bg-ods-card border border-ods-border rounded-[6px] overflow-hidden">
309
+ <div className={cn("bg-ods-card border border-ods-border rounded-[6px] overflow-hidden", fluid && "w-full")}>
295
310
  <div className="flex items-center justify-between px-4 pt-4">
296
311
  <CalendarNavButton
297
312
  direction="left"
@@ -328,7 +343,7 @@ function DatePickerCalendar({
328
343
  // Range mode
329
344
  return (
330
345
  <div
331
- className="bg-ods-card border border-ods-border rounded-md overflow-hidden"
346
+ className={cn("bg-ods-card border border-ods-border rounded-md overflow-hidden", fluid && "w-full")}
332
347
  onMouseLeave={() => setHoveredDate(undefined)}
333
348
  >
334
349
  <style>{rangeStyles}</style>
@@ -966,6 +981,196 @@ export function DatePickerInputSimple({
966
981
  );
967
982
  }
968
983
 
984
+ // ============================================================================
985
+ // DateFilterMenu Component (sort + calendar filter popover from Figma)
986
+ // ============================================================================
987
+
988
+ export interface DateFilterResult {
989
+ /** Selected sort direction */
990
+ sort: SortDirection;
991
+ /** Selected single date (mode === "single") */
992
+ date?: Date;
993
+ /** Selected date range (mode === "range") */
994
+ range?: DateRange;
995
+ }
996
+
997
+ export interface DateFilterMenuProps {
998
+ /** Selection mode for the calendar. Defaults to "range". */
999
+ mode?: DatePickerMode;
1000
+ /** Current (applied) sort direction. Defaults to "desc". */
1001
+ sort?: SortDirection;
1002
+ /** Current (applied) single date — used when mode === "single". */
1003
+ date?: Date;
1004
+ /** Current (applied) range — used when mode === "range". */
1005
+ range?: DateRange;
1006
+ /** Fired when the user presses Apply with the drafted selection. */
1007
+ onApply?: (result: DateFilterResult) => void;
1008
+ /** Fired when the menu closes (Close button, outside click, Esc). */
1009
+ onClose?: () => void;
1010
+ /** Disable the trigger. */
1011
+ disabled?: boolean;
1012
+ /** Minimum selectable date. */
1013
+ fromDate?: Date;
1014
+ /** Maximum selectable date. */
1015
+ toDate?: Date;
1016
+ /** Locale for the calendar. */
1017
+ locale?: DayPickerProps["locale"];
1018
+ /** Popover alignment relative to the trigger. */
1019
+ align?: "start" | "center" | "end";
1020
+ /** Label for the ascending sort option. */
1021
+ ascLabel?: string;
1022
+ /** Label for the descending sort option. */
1023
+ descLabel?: string;
1024
+ /** Additional class name for the trigger button. */
1025
+ className?: string;
1026
+ /** Accessible label for the trigger. */
1027
+ "aria-label"?: string;
1028
+ }
1029
+
1030
+ /**
1031
+ * DateFilterMenu — a calendar-icon-triggered popover combining a sort-direction
1032
+ * selector and a date / date-range calendar, with Close and Apply actions.
1033
+ * Selections are drafted internally and only committed via `onApply`.
1034
+ */
1035
+ export function DateFilterMenu({
1036
+ mode = "range",
1037
+ sort = "desc",
1038
+ date,
1039
+ range,
1040
+ onApply,
1041
+ onClose,
1042
+ disabled = false,
1043
+ fromDate,
1044
+ toDate,
1045
+ locale,
1046
+ align = "start",
1047
+ ascLabel = "Sort by Ascending",
1048
+ descLabel = "Sort by Descending",
1049
+ className,
1050
+ "aria-label": ariaLabel = "Open date filter",
1051
+ }: DateFilterMenuProps) {
1052
+ const [open, setOpen] = React.useState(false);
1053
+
1054
+ // Drafted selection — initialized from props each time the menu opens.
1055
+ const [draftSort, setDraftSort] = React.useState<SortDirection>(sort);
1056
+ const [draftSelected, setDraftSelected] = React.useState<
1057
+ Date | DateRange | undefined
1058
+ >(mode === "single" ? date : range);
1059
+
1060
+ const resetDraft = React.useCallback(() => {
1061
+ setDraftSort(sort);
1062
+ setDraftSelected(mode === "single" ? date : range);
1063
+ }, [sort, date, range, mode]);
1064
+
1065
+ const handleOpenChange = (next: boolean) => {
1066
+ if (next) {
1067
+ resetDraft();
1068
+ } else {
1069
+ onClose?.();
1070
+ }
1071
+ setOpen(next);
1072
+ };
1073
+
1074
+ const handleApply = () => {
1075
+ const result: DateFilterResult =
1076
+ mode === "single"
1077
+ ? { sort: draftSort, date: draftSelected as Date | undefined }
1078
+ : { sort: draftSort, range: draftSelected as DateRange | undefined };
1079
+ onApply?.(result);
1080
+ setOpen(false);
1081
+ };
1082
+
1083
+ const handleClose = () => {
1084
+ onClose?.();
1085
+ setOpen(false);
1086
+ };
1087
+
1088
+ return (
1089
+ <Popover.Root open={open} onOpenChange={handleOpenChange}>
1090
+ <Popover.Trigger asChild>
1091
+ <Button
1092
+ type="button"
1093
+ variant="outline"
1094
+ size="icon"
1095
+ disabled={disabled}
1096
+ aria-label={ariaLabel}
1097
+ className={className}
1098
+ leftIcon={<Calendar className="size-6" />}
1099
+ />
1100
+ </Popover.Trigger>
1101
+
1102
+ <Popover.Portal>
1103
+ <Popover.Content
1104
+ className={cn(
1105
+ "z-[9999] w-80 max-w-[calc(100vw-2rem)]",
1106
+ "flex flex-col gap-4 rounded-[6px] border border-ods-border bg-ods-bg p-4",
1107
+ "animate-in fade-in-0 zoom-in-95",
1108
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
1109
+ "data-[side=bottom]:slide-in-from-top-2",
1110
+ "data-[side=top]:slide-in-from-bottom-2"
1111
+ )}
1112
+ sideOffset={8}
1113
+ align={align}
1114
+ >
1115
+ {/* Sort direction selector */}
1116
+ <Select
1117
+ value={draftSort}
1118
+ onValueChange={(value) => setDraftSort(value as SortDirection)}
1119
+ >
1120
+ <SelectTrigger className="gap-2" aria-label="Sort direction">
1121
+ {/* Wrapper is a <div> (not <span>) so SelectTrigger's
1122
+ `[&>span]:line-clamp-1` rule doesn't force it to a vertical
1123
+ -webkit-box and stack the icon/label into a column. */}
1124
+ <div className="flex min-w-0 flex-1 items-center gap-2">
1125
+ <ArrowUpDown className="size-6 shrink-0 text-ods-text-secondary" />
1126
+ <span className="truncate">
1127
+ <SelectValue />
1128
+ </span>
1129
+ </div>
1130
+ </SelectTrigger>
1131
+ <SelectContent>
1132
+ <SelectItem value="asc">{ascLabel}</SelectItem>
1133
+ <SelectItem value="desc">{descLabel}</SelectItem>
1134
+ </SelectContent>
1135
+ </Select>
1136
+
1137
+ {/* Calendar */}
1138
+ <DatePickerCalendar
1139
+ mode={mode}
1140
+ selected={draftSelected}
1141
+ onSelect={setDraftSelected}
1142
+ numberOfMonths={1}
1143
+ fromDate={fromDate}
1144
+ toDate={toDate}
1145
+ locale={locale}
1146
+ fluid
1147
+ />
1148
+
1149
+ {/* Actions */}
1150
+ <div className="flex w-full items-stretch gap-4">
1151
+ <Button
1152
+ type="button"
1153
+ variant="outline"
1154
+ fullWidth
1155
+ onClick={handleClose}
1156
+ >
1157
+ Close
1158
+ </Button>
1159
+ <Button
1160
+ type="button"
1161
+ variant="accent"
1162
+ fullWidth
1163
+ onClick={handleApply}
1164
+ >
1165
+ Apply
1166
+ </Button>
1167
+ </div>
1168
+ </Popover.Content>
1169
+ </Popover.Portal>
1170
+ </Popover.Root>
1171
+ );
1172
+ }
1173
+
969
1174
  // ============================================================================
970
1175
  // Exports
971
1176
  // ============================================================================
@@ -1,12 +1,15 @@
1
1
  import type { Meta, StoryObj } from "@storybook/nextjs-vite";
2
2
  import { useState } from "react";
3
3
  import {
4
+ DateFilterMenu,
5
+ type DateFilterResult,
4
6
  DatePicker,
5
7
  DatePickerInput,
6
8
  DatePickerInputSimple,
7
9
  type DatePickerBaseProps,
8
10
  type DateRange,
9
11
  } from "../components/ui/date-picker";
12
+ import type { SortDirection } from "../components/ui/sort-column-item";
10
13
 
11
14
  // Using a simplified type for Storybook since DatePicker uses discriminated union
12
15
  type DatePickerStoryProps = DatePickerBaseProps & {
@@ -759,3 +762,141 @@ export const SimpleInputDateOnly: Story = {
759
762
  ),
760
763
  ],
761
764
  };
765
+
766
+ // ============================================================================
767
+ // DateFilterMenu Stories (sort + calendar filter popover from Figma)
768
+ // ============================================================================
769
+
770
+ const fmtSort = (sort: SortDirection) =>
771
+ sort === "asc" ? "Ascending" : "Descending";
772
+
773
+ const fmtDay = (d: Date) =>
774
+ d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
775
+
776
+ /**
777
+ * DateFilterMenu — calendar-icon trigger opens a popover with a sort selector,
778
+ * a date-range calendar, and Close / Apply actions. Matches the Figma
779
+ * `filter-menu`. Selections are drafted internally and committed on Apply.
780
+ */
781
+ export const FilterMenuRange: Story = {
782
+ render: function Render() {
783
+ const [applied, setApplied] = useState<DateFilterResult | null>(null);
784
+
785
+ const summary = () => {
786
+ if (!applied) return "Nothing applied yet";
787
+ const { sort, range } = applied;
788
+ const r = range?.from
789
+ ? range.to
790
+ ? `${fmtDay(range.from)} - ${fmtDay(range.to)}`
791
+ : fmtDay(range.from)
792
+ : "all dates";
793
+ return `Sort ${fmtSort(sort)} · ${r}`;
794
+ };
795
+
796
+ return (
797
+ <div className="flex flex-col gap-4">
798
+ <div className="flex items-center gap-3">
799
+ <span className="text-h4 text-ods-text-primary">Last activity</span>
800
+ <DateFilterMenu mode="range" onApply={setApplied} />
801
+ </div>
802
+ <div className="text-[14px] text-ods-text-secondary p-3 bg-ods-bg rounded-lg border border-ods-border">
803
+ <span className="text-ods-text-primary">Applied:</span> {summary()}
804
+ </div>
805
+ </div>
806
+ );
807
+ },
808
+ };
809
+
810
+ /**
811
+ * DateFilterMenu in single-date mode.
812
+ */
813
+ export const FilterMenuSingle: Story = {
814
+ render: function Render() {
815
+ const [applied, setApplied] = useState<DateFilterResult | null>(null);
816
+
817
+ return (
818
+ <div className="flex flex-col gap-4">
819
+ <div className="flex items-center gap-3">
820
+ <span className="text-h4 text-ods-text-primary">Date &amp; time</span>
821
+ <DateFilterMenu mode="single" sort="asc" onApply={setApplied} />
822
+ </div>
823
+ <div className="text-[14px] text-ods-text-secondary p-3 bg-ods-bg rounded-lg border border-ods-border">
824
+ <span className="text-ods-text-primary">Applied:</span>{" "}
825
+ {applied
826
+ ? `Sort ${fmtSort(applied.sort)} · ${
827
+ applied.date ? fmtDay(applied.date) : "all dates"
828
+ }`
829
+ : "Nothing applied yet"}
830
+ </div>
831
+ </div>
832
+ );
833
+ },
834
+ };
835
+
836
+ /**
837
+ * DateFilterMenu wired to filter + sort a small table — mirrors the intended
838
+ * usage in table columns (organizations last-activity, scripts schedules,
839
+ * logs). Apply re-filters and re-sorts the rows by date.
840
+ */
841
+ export const FilterMenuWithTable: Story = {
842
+ render: function Render() {
843
+ type Row = { id: string; activity: Date };
844
+ const rows: Row[] = [
845
+ { id: "Acme Corp", activity: new Date(2026, 4, 2) },
846
+ { id: "Globex", activity: new Date(2026, 4, 6) },
847
+ { id: "Initech", activity: new Date(2026, 4, 11) },
848
+ { id: "Umbrella", activity: new Date(2026, 4, 18) },
849
+ { id: "Stark Ind.", activity: new Date(2026, 4, 24) },
850
+ ];
851
+
852
+ const [filter, setFilter] = useState<DateFilterResult>({
853
+ sort: "desc",
854
+ range: undefined,
855
+ });
856
+
857
+ const visible = rows
858
+ .filter((row) => {
859
+ const { from, to } = filter.range ?? {};
860
+ if (from && row.activity < from) return false;
861
+ if (to && row.activity > to) return false;
862
+ return true;
863
+ })
864
+ .sort((a, b) =>
865
+ filter.sort === "asc"
866
+ ? a.activity.getTime() - b.activity.getTime()
867
+ : b.activity.getTime() - a.activity.getTime()
868
+ );
869
+
870
+ return (
871
+ <div className="flex flex-col gap-3" style={{ width: 360 }}>
872
+ <div className="flex items-center justify-between">
873
+ <span className="text-h4 text-ods-text-primary">Organizations</span>
874
+ <DateFilterMenu
875
+ mode="range"
876
+ sort={filter.sort}
877
+ range={filter.range}
878
+ onApply={setFilter}
879
+ />
880
+ </div>
881
+ <div className="rounded-lg border border-ods-border overflow-hidden">
882
+ {visible.map((row) => (
883
+ <div
884
+ key={row.id}
885
+ className="flex items-center justify-between px-3 py-2 border-b border-ods-border last:border-b-0"
886
+ >
887
+ <span className="text-ods-text-primary text-[14px]">{row.id}</span>
888
+ <span className="text-ods-text-secondary text-[14px]">
889
+ {fmtDay(row.activity)}
890
+ </span>
891
+ </div>
892
+ ))}
893
+ {visible.length === 0 && (
894
+ <div className="px-3 py-4 text-center text-ods-text-secondary text-[14px]">
895
+ No results in range
896
+ </div>
897
+ )}
898
+ </div>
899
+ </div>
900
+ );
901
+ },
902
+ };