@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.
- package/dist/{chunk-6ZCLKEXO.cjs → chunk-4USOR7KK.cjs} +25 -25
- package/dist/{chunk-6ZCLKEXO.cjs.map → chunk-4USOR7KK.cjs.map} +1 -1
- package/dist/{chunk-XVBNFRG7.cjs → chunk-5COLEZW6.cjs} +62 -62
- package/dist/{chunk-XVBNFRG7.cjs.map → chunk-5COLEZW6.cjs.map} +1 -1
- package/dist/{chunk-RTYIYX6C.js → chunk-5CWJGCOL.js} +4 -4
- package/dist/{chunk-OUN4V2CV.cjs → chunk-5DXWGCTW.cjs} +56 -56
- package/dist/{chunk-OUN4V2CV.cjs.map → chunk-5DXWGCTW.cjs.map} +1 -1
- package/dist/{chunk-OYRZCCVO.cjs → chunk-5GXMQPG7.cjs} +37 -37
- package/dist/{chunk-OYRZCCVO.cjs.map → chunk-5GXMQPG7.cjs.map} +1 -1
- package/dist/{chunk-ZEDY3IAB.cjs → chunk-5HRCKYFX.cjs} +9 -9
- package/dist/{chunk-ZEDY3IAB.cjs.map → chunk-5HRCKYFX.cjs.map} +1 -1
- package/dist/{chunk-XPDKUIGS.js → chunk-5YGKK636.js} +4 -4
- package/dist/{chunk-BUJZJN2K.js → chunk-6GSM6K5W.js} +2 -2
- package/dist/{chunk-GLKQDTCI.cjs → chunk-AYBSXRRC.cjs} +670 -532
- package/dist/chunk-AYBSXRRC.cjs.map +1 -0
- package/dist/{chunk-YLLX3O2D.js → chunk-EQSGC5WT.js} +2 -2
- package/dist/{chunk-S6QURVK4.js → chunk-GYWMN4AK.js} +4 -4
- package/dist/{chunk-AA2ZKWQV.js → chunk-HS2YU2NF.js} +2 -2
- package/dist/{chunk-4HII2ZLE.cjs → chunk-K2PS5MJH.cjs} +5 -5
- package/dist/{chunk-4HII2ZLE.cjs.map → chunk-K2PS5MJH.cjs.map} +1 -1
- package/dist/{chunk-SYRZBYKN.js → chunk-MIVP6B6P.js} +4 -4
- package/dist/{chunk-OI56RVPD.js → chunk-P3IIAUCL.js} +322 -184
- package/dist/chunk-P3IIAUCL.js.map +1 -0
- package/dist/{chunk-YNMGKOSL.cjs → chunk-QLSUOQMU.cjs} +7 -7
- package/dist/{chunk-YNMGKOSL.cjs.map → chunk-QLSUOQMU.cjs.map} +1 -1
- package/dist/{chunk-7CFMQIAC.cjs → chunk-U74OC6PM.cjs} +26 -26
- package/dist/{chunk-7CFMQIAC.cjs.map → chunk-U74OC6PM.cjs.map} +1 -1
- package/dist/{chunk-HDTW265W.js → chunk-UJIDDEXD.js} +2 -2
- package/dist/{chunk-MG72PRRZ.js → chunk-VSUBZBG7.js} +2 -2
- package/dist/{chunk-WEHLKMBD.js → chunk-VUE2FSVD.js} +2 -2
- package/dist/{chunk-5YWMPCTD.cjs → chunk-WAYJ7VBD.cjs} +12 -12
- package/dist/{chunk-5YWMPCTD.cjs.map → chunk-WAYJ7VBD.cjs.map} +1 -1
- package/dist/{chunk-4PAYSVBL.js → chunk-XFRQUV5M.js} +2 -2
- package/dist/{chunk-JJS5QJOX.cjs → chunk-XQBK7IVF.cjs} +15 -15
- package/dist/{chunk-JJS5QJOX.cjs.map → chunk-XQBK7IVF.cjs.map} +1 -1
- package/dist/{chunk-FCIMIGVA.cjs → chunk-XTV2EZHO.cjs} +31 -31
- package/dist/{chunk-FCIMIGVA.cjs.map → chunk-XTV2EZHO.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/chat-composer.d.ts.map +1 -1
- package/dist/components/chat/chat-input.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/help-center-pages/index.cjs +20 -20
- package/dist/components/help-center-pages/index.js +11 -11
- package/dist/components/index.cjs +122 -120
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +11 -9
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +5 -5
- package/dist/components/tickets/index.js +4 -4
- package/dist/components/ui/date-picker.d.ts +47 -0
- package/dist/components/ui/date-picker.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +4 -2
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +3 -1
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/src/components/chat/chat-composer.tsx +6 -4
- package/src/components/chat/chat-input.tsx +5 -1
- package/src/components/ui/date-picker.tsx +214 -9
- package/src/stories/DatePicker.stories.tsx +141 -0
- package/dist/chunk-GLKQDTCI.cjs.map +0 -1
- package/dist/chunk-OI56RVPD.js.map +0 -1
- /package/dist/{chunk-RTYIYX6C.js.map → chunk-5CWJGCOL.js.map} +0 -0
- /package/dist/{chunk-XPDKUIGS.js.map → chunk-5YGKK636.js.map} +0 -0
- /package/dist/{chunk-BUJZJN2K.js.map → chunk-6GSM6K5W.js.map} +0 -0
- /package/dist/{chunk-YLLX3O2D.js.map → chunk-EQSGC5WT.js.map} +0 -0
- /package/dist/{chunk-S6QURVK4.js.map → chunk-GYWMN4AK.js.map} +0 -0
- /package/dist/{chunk-AA2ZKWQV.js.map → chunk-HS2YU2NF.js.map} +0 -0
- /package/dist/{chunk-SYRZBYKN.js.map → chunk-MIVP6B6P.js.map} +0 -0
- /package/dist/{chunk-HDTW265W.js.map → chunk-UJIDDEXD.js.map} +0 -0
- /package/dist/{chunk-MG72PRRZ.js.map → chunk-VSUBZBG7.js.map} +0 -0
- /package/dist/{chunk-WEHLKMBD.js.map → chunk-VUE2FSVD.js.map} +0 -0
- /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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 & 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
|
+
};
|