myoperator-mcp 0.2.372 → 0.2.374

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 (2) hide show
  1. package/dist/index.js +1148 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2562,6 +2562,777 @@ const DateDivider = React.forwardRef(
2562
2562
  DateDivider.displayName = "DateDivider";
2563
2563
 
2564
2564
  export { DateDivider };
2565
+ `,
2566
+ "date-range-picker": `import * as React from "react";
2567
+ import { createPortal } from "react-dom";
2568
+ import {
2569
+ autoUpdate,
2570
+ flip,
2571
+ offset,
2572
+ shift,
2573
+ size as floatingSize,
2574
+ useFloating,
2575
+ type Placement,
2576
+ type Strategy,
2577
+ } from "@floating-ui/react-dom";
2578
+ import { cva, type VariantProps } from "class-variance-authority";
2579
+ import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
2580
+
2581
+ import { cn } from "@/lib/utils";
2582
+ import {
2583
+ DropdownMenu,
2584
+ DropdownMenuContent,
2585
+ DropdownMenuItem,
2586
+ DropdownMenuTrigger,
2587
+ } from "./dropdown-menu";
2588
+
2589
+ const DEFAULT_PLACEHOLDER = "Date Range";
2590
+ const POPOVER_MARGIN = 8;
2591
+ const POPOVER_GAP = 4;
2592
+ const MAX_POPOVER_HEIGHT = 420;
2593
+ const POPOVER_SCROLL_HEIGHT_VAR = "--date-range-picker-scroll-height";
2594
+ const CALENDAR_PLACEMENT: Placement = "bottom-start";
2595
+ // Above the calendar popover's own z-index (10050) so the month/year
2596
+ // dropdowns render on top of it rather than behind.
2597
+ const CALENDAR_DROPDOWN_Z_INDEX = "z-[10060]";
2598
+ const YEAR_OPTIONS_SPAN = 10;
2599
+
2600
+ const weekDays = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"];
2601
+ const MONTH_SHORT_NAMES = [
2602
+ "Jan",
2603
+ "Feb",
2604
+ "Mar",
2605
+ "Apr",
2606
+ "May",
2607
+ "Jun",
2608
+ "Jul",
2609
+ "Aug",
2610
+ "Sep",
2611
+ "Oct",
2612
+ "Nov",
2613
+ "Dec",
2614
+ ];
2615
+ const MONTH_LONG_NAMES = [
2616
+ "January",
2617
+ "February",
2618
+ "March",
2619
+ "April",
2620
+ "May",
2621
+ "June",
2622
+ "July",
2623
+ "August",
2624
+ "September",
2625
+ "October",
2626
+ "November",
2627
+ "December",
2628
+ ];
2629
+
2630
+ const dateRangePickerTriggerVariants = cva(
2631
+ "flex h-10 w-full items-center gap-2 rounded-lg border border-solid border-semantic-border-input bg-semantic-bg-primary px-4 py-2.5 text-left text-sm text-semantic-text-primary outline-none transition-colors hover:border-semantic-border-input-focus/50 disabled:cursor-not-allowed disabled:opacity-50",
2632
+ {
2633
+ variants: {
2634
+ state: {
2635
+ default: "",
2636
+ error:
2637
+ "border-semantic-error-primary hover:border-semantic-error-primary",
2638
+ },
2639
+ },
2640
+ defaultVariants: {
2641
+ state: "default",
2642
+ },
2643
+ }
2644
+ );
2645
+
2646
+ export interface DateRangeValue {
2647
+ start?: Date;
2648
+ end?: Date;
2649
+ }
2650
+
2651
+ export interface DateRangePreset {
2652
+ label: string;
2653
+ getRange: () => DateRangeValue;
2654
+ }
2655
+
2656
+ export interface DateRangePickerProps
2657
+ extends
2658
+ Omit<React.HTMLAttributes<HTMLDivElement>, "defaultValue" | "onChange">,
2659
+ Pick<VariantProps<typeof dateRangePickerTriggerVariants>, "state"> {
2660
+ value?: DateRangeValue;
2661
+ defaultValue?: DateRangeValue;
2662
+ onValueChange?: (value: DateRangeValue) => void;
2663
+ /** Trigger placeholder text when no range is selected. Defaults to "Date Range". */
2664
+ placeholder?: string;
2665
+ /**
2666
+ * Presets shown in the left column. Defaults to
2667
+ * Today/Yesterday/Last 7 days/Last 30 days/This month/Last month.
2668
+ * Pass an empty array to hide the presets column entirely.
2669
+ */
2670
+ presets?: DateRangePreset[];
2671
+ minDate?: Date;
2672
+ maxDate?: Date;
2673
+ disablePastDates?: boolean;
2674
+ disabled?: boolean;
2675
+ open?: boolean;
2676
+ defaultOpen?: boolean;
2677
+ onOpenChange?: (open: boolean) => void;
2678
+ portalContainer?: HTMLElement | null;
2679
+ /** Custom display formatter for the trigger's filled-state text. Defaults to "D MMM YYYY - D MMM YYYY". */
2680
+ formatRange?: (value: DateRangeValue) => string;
2681
+ /** Additional className merged onto the trigger button (overrides base trigger styling) */
2682
+ triggerClassName?: string;
2683
+ /** Additional className merged onto the trigger's label span \u2014 e.g. \`"hidden sm:inline"\` to collapse to an icon-only button below a breakpoint */
2684
+ triggerLabelClassName?: string;
2685
+ }
2686
+
2687
+ function normalizeValue(value?: DateRangeValue): DateRangeValue {
2688
+ return { start: value?.start, end: value?.end };
2689
+ }
2690
+
2691
+ function isSameDay(a?: Date, b?: Date) {
2692
+ if (!a || !b) return false;
2693
+
2694
+ return (
2695
+ a.getFullYear() === b.getFullYear() &&
2696
+ a.getMonth() === b.getMonth() &&
2697
+ a.getDate() === b.getDate()
2698
+ );
2699
+ }
2700
+
2701
+ function startOfDay(date: Date) {
2702
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
2703
+ }
2704
+
2705
+ function isBeforeDay(date: Date, minDate: Date) {
2706
+ return startOfDay(date).getTime() < startOfDay(minDate).getTime();
2707
+ }
2708
+
2709
+ function isAfterDay(date: Date, maxDate: Date) {
2710
+ return startOfDay(date).getTime() > startOfDay(maxDate).getTime();
2711
+ }
2712
+
2713
+ function startOfMonth(date: Date) {
2714
+ return new Date(date.getFullYear(), date.getMonth(), 1);
2715
+ }
2716
+
2717
+ function addMonths(date: Date, months: number) {
2718
+ return new Date(date.getFullYear(), date.getMonth() + months, 1);
2719
+ }
2720
+
2721
+ function addDays(date: Date, days: number) {
2722
+ const result = new Date(date);
2723
+ result.setDate(result.getDate() + days);
2724
+ return result;
2725
+ }
2726
+
2727
+ function getCalendarDays(month: Date) {
2728
+ const firstDay = startOfMonth(month);
2729
+ const gridStart = new Date(firstDay);
2730
+ gridStart.setDate(firstDay.getDate() - firstDay.getDay());
2731
+
2732
+ return Array.from({ length: 42 }, (_, index) => {
2733
+ const day = new Date(gridStart);
2734
+ day.setDate(gridStart.getDate() + index);
2735
+ return day;
2736
+ });
2737
+ }
2738
+
2739
+ function isPointerInsideElement(
2740
+ event: MouseEvent,
2741
+ element: HTMLElement | null
2742
+ ) {
2743
+ if (!element) return false;
2744
+
2745
+ const target = event.target;
2746
+ if (target instanceof Node && element.contains(target)) return true;
2747
+
2748
+ if (typeof event.composedPath === "function") {
2749
+ return event
2750
+ .composedPath()
2751
+ .some((node) => node instanceof Node && element.contains(node));
2752
+ }
2753
+
2754
+ return false;
2755
+ }
2756
+
2757
+ function formatDateShort(date: Date) {
2758
+ return \`\${date.getDate()} \${MONTH_SHORT_NAMES[date.getMonth()]} \${date.getFullYear()}\`;
2759
+ }
2760
+
2761
+ function defaultFormatRange(value: DateRangeValue) {
2762
+ if (value.start && value.end) {
2763
+ return \`\${formatDateShort(value.start)} - \${formatDateShort(value.end)}\`;
2764
+ }
2765
+ if (value.start) return formatDateShort(value.start);
2766
+ if (value.end) return formatDateShort(value.end);
2767
+
2768
+ return "";
2769
+ }
2770
+
2771
+ /**
2772
+ * Default presets shown in the DateRangePicker's left column. Exported so
2773
+ * consumers can reference, filter, or extend the list.
2774
+ */
2775
+ export const DEFAULT_DATE_RANGE_PRESETS: DateRangePreset[] = [
2776
+ {
2777
+ label: "Today",
2778
+ getRange: () => {
2779
+ const today = startOfDay(new Date());
2780
+ return { start: today, end: today };
2781
+ },
2782
+ },
2783
+ {
2784
+ label: "Yesterday",
2785
+ getRange: () => {
2786
+ const yesterday = startOfDay(addDays(new Date(), -1));
2787
+ return { start: yesterday, end: yesterday };
2788
+ },
2789
+ },
2790
+ {
2791
+ label: "Last 7 days",
2792
+ getRange: () => {
2793
+ const today = startOfDay(new Date());
2794
+ return { start: addDays(today, -6), end: today };
2795
+ },
2796
+ },
2797
+ {
2798
+ label: "Last 30 days",
2799
+ getRange: () => {
2800
+ const today = startOfDay(new Date());
2801
+ return { start: addDays(today, -29), end: today };
2802
+ },
2803
+ },
2804
+ {
2805
+ label: "This month",
2806
+ getRange: () => {
2807
+ const today = startOfDay(new Date());
2808
+ return { start: startOfMonth(today), end: today };
2809
+ },
2810
+ },
2811
+ {
2812
+ label: "Last month",
2813
+ getRange: () => {
2814
+ const start = addMonths(startOfMonth(new Date()), -1);
2815
+ const end = new Date(start.getFullYear(), start.getMonth() + 1, 0);
2816
+ return { start, end };
2817
+ },
2818
+ },
2819
+ ];
2820
+
2821
+ function FigmaCalendarIcon({ className }: { className?: string }) {
2822
+ return (
2823
+ <svg
2824
+ viewBox="0 0 18 18"
2825
+ fill="none"
2826
+ xmlns="http://www.w3.org/2000/svg"
2827
+ className={className}
2828
+ aria-hidden="true"
2829
+ >
2830
+ <path
2831
+ d="M6 1.5V4.5M12 1.5V4.5M2.25 7.5H15.75M3.75 3H14.25C15.0784 3 15.75 3.67157 15.75 4.5V15C15.75 15.8284 15.0784 16.5 14.25 16.5H3.75C2.92157 16.5 2.25 15.8284 2.25 15V4.5C2.25 3.67157 2.92157 3 3.75 3Z"
2832
+ stroke="currentColor"
2833
+ strokeWidth="0.8"
2834
+ strokeLinecap="round"
2835
+ strokeLinejoin="round"
2836
+ />
2837
+ </svg>
2838
+ );
2839
+ }
2840
+
2841
+ const DateRangePicker = React.forwardRef<HTMLDivElement, DateRangePickerProps>(
2842
+ (
2843
+ {
2844
+ className,
2845
+ state,
2846
+ value,
2847
+ defaultValue,
2848
+ onValueChange,
2849
+ placeholder = DEFAULT_PLACEHOLDER,
2850
+ presets = DEFAULT_DATE_RANGE_PRESETS,
2851
+ minDate,
2852
+ maxDate,
2853
+ disablePastDates = false,
2854
+ disabled = false,
2855
+ open: controlledOpen,
2856
+ defaultOpen = false,
2857
+ onOpenChange,
2858
+ portalContainer,
2859
+ formatRange,
2860
+ triggerClassName,
2861
+ triggerLabelClassName,
2862
+ id,
2863
+ ...props
2864
+ },
2865
+ ref
2866
+ ) => {
2867
+ const generatedId = React.useId();
2868
+ const triggerId = id ?? generatedId;
2869
+ const isValueControlled = value !== undefined;
2870
+ const isOpenControlled = controlledOpen !== undefined;
2871
+ const [internalValue, setInternalValue] = React.useState(() =>
2872
+ normalizeValue(defaultValue)
2873
+ );
2874
+ const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
2875
+ const currentValue = normalizeValue(
2876
+ isValueControlled ? value : internalValue
2877
+ );
2878
+ const open = isOpenControlled ? controlledOpen : internalOpen;
2879
+
2880
+ // Draft state \u2014 the calendar operates on a working copy of the range so
2881
+ // that Cancel can discard in-progress selections and Apply commits them.
2882
+ const [draftValue, setDraftValue] = React.useState<DateRangeValue>(
2883
+ () => currentValue
2884
+ );
2885
+ // Tracks whether the next day click should complete the range (true) or
2886
+ // start a new one (false). Drives the "click 1 = start, click 2 = end,
2887
+ // click 3 = restart" cycle.
2888
+ const [pendingEnd, setPendingEnd] = React.useState(false);
2889
+ const [visibleMonth, setVisibleMonth] = React.useState(() =>
2890
+ startOfMonth(currentValue.start ?? currentValue.end ?? new Date())
2891
+ );
2892
+
2893
+ const rootRef = React.useRef<HTMLDivElement | null>(null);
2894
+ const triggerRef = React.useRef<HTMLButtonElement | null>(null);
2895
+ const popoverRef = React.useRef<HTMLDivElement | null>(null);
2896
+ const usesContainerPortal = portalContainer !== undefined;
2897
+ const floatingStrategy: Strategy = usesContainerPortal
2898
+ ? "absolute"
2899
+ : "fixed";
2900
+ const floatingMiddleware = React.useMemo(
2901
+ () => [
2902
+ offset(POPOVER_GAP),
2903
+ flip({ padding: POPOVER_MARGIN }),
2904
+ shift({ padding: POPOVER_MARGIN }),
2905
+ floatingSize({
2906
+ padding: POPOVER_MARGIN,
2907
+ apply({ availableHeight, elements }) {
2908
+ const maxHeight = Math.max(
2909
+ 1,
2910
+ Math.min(MAX_POPOVER_HEIGHT, availableHeight)
2911
+ );
2912
+ elements.floating.style.setProperty(
2913
+ POPOVER_SCROLL_HEIGHT_VAR,
2914
+ \`\${maxHeight}px\`
2915
+ );
2916
+ },
2917
+ }),
2918
+ ],
2919
+ []
2920
+ );
2921
+ const { refs, floatingStyles, isPositioned } =
2922
+ useFloating<HTMLButtonElement>({
2923
+ open,
2924
+ placement: CALENDAR_PLACEMENT,
2925
+ strategy: floatingStrategy,
2926
+ transform: false,
2927
+ middleware: floatingMiddleware,
2928
+ whileElementsMounted: (reference, floating, update) =>
2929
+ autoUpdate(reference, floating, update, { animationFrame: true }),
2930
+ });
2931
+ const calendarDays = React.useMemo(
2932
+ () => getCalendarDays(visibleMonth),
2933
+ [visibleMonth]
2934
+ );
2935
+ const displayValue = (formatRange ?? defaultFormatRange)(currentValue);
2936
+ const effectiveMinDate = React.useMemo(() => {
2937
+ if (!disablePastDates) return minDate;
2938
+
2939
+ const today = startOfDay(new Date());
2940
+ if (!minDate) return today;
2941
+
2942
+ return isBeforeDay(minDate, today) ? today : minDate;
2943
+ }, [disablePastDates, minDate]);
2944
+ const portalMount =
2945
+ typeof document !== "undefined"
2946
+ ? usesContainerPortal
2947
+ ? portalContainer
2948
+ : document.body
2949
+ : null;
2950
+
2951
+ const setOpen = React.useCallback(
2952
+ (nextOpen: boolean) => {
2953
+ if (!isOpenControlled) {
2954
+ setInternalOpen(nextOpen);
2955
+ }
2956
+
2957
+ onOpenChange?.(nextOpen);
2958
+ },
2959
+ [isOpenControlled, onOpenChange]
2960
+ );
2961
+
2962
+ const setTriggerRef = React.useCallback(
2963
+ (node: HTMLButtonElement | null) => {
2964
+ triggerRef.current = node;
2965
+ refs.setReference(node);
2966
+ },
2967
+ [refs]
2968
+ );
2969
+
2970
+ const setPopoverRef = React.useCallback(
2971
+ (node: HTMLDivElement | null) => {
2972
+ popoverRef.current = node;
2973
+ refs.setFloating(node);
2974
+ },
2975
+ [refs]
2976
+ );
2977
+
2978
+ // Reset the draft to the last committed value (and jump the calendar to
2979
+ // that month) each time the popover opens, so a Cancel from a previous
2980
+ // session never leaks into the next one.
2981
+ const currentValueRef = React.useRef(currentValue);
2982
+ React.useEffect(() => {
2983
+ currentValueRef.current = currentValue;
2984
+ });
2985
+
2986
+ React.useEffect(() => {
2987
+ if (!open) return;
2988
+
2989
+ const latestValue = currentValueRef.current;
2990
+ setDraftValue(latestValue);
2991
+ setPendingEnd(false);
2992
+ setVisibleMonth(
2993
+ startOfMonth(latestValue.start ?? latestValue.end ?? new Date())
2994
+ );
2995
+ }, [open]);
2996
+
2997
+ React.useEffect(() => {
2998
+ if (!open) return;
2999
+
3000
+ const handlePointerDown = (event: MouseEvent) => {
3001
+ if (
3002
+ !isPointerInsideElement(event, rootRef.current) &&
3003
+ !isPointerInsideElement(event, popoverRef.current)
3004
+ ) {
3005
+ setOpen(false);
3006
+ }
3007
+ };
3008
+
3009
+ const handleKeyDown = (event: KeyboardEvent) => {
3010
+ if (event.key === "Escape") {
3011
+ setOpen(false);
3012
+ }
3013
+ };
3014
+
3015
+ document.addEventListener("mousedown", handlePointerDown);
3016
+ document.addEventListener("keydown", handleKeyDown);
3017
+
3018
+ return () => {
3019
+ document.removeEventListener("mousedown", handlePointerDown);
3020
+ document.removeEventListener("keydown", handleKeyDown);
3021
+ };
3022
+ }, [open, setOpen]);
3023
+
3024
+ const commitValue = React.useCallback(
3025
+ (nextValue: DateRangeValue) => {
3026
+ if (!isValueControlled) {
3027
+ setInternalValue(nextValue);
3028
+ }
3029
+
3030
+ onValueChange?.(nextValue);
3031
+ },
3032
+ [isValueControlled, onValueChange]
3033
+ );
3034
+
3035
+ const handlePresetClick = (preset: DateRangePreset) => {
3036
+ const range = preset.getRange();
3037
+ commitValue(range);
3038
+ setOpen(false);
3039
+ };
3040
+
3041
+ const handleDayClick = (day: Date) => {
3042
+ if (!pendingEnd) {
3043
+ setDraftValue({ start: day, end: day });
3044
+ setPendingEnd(true);
3045
+ return;
3046
+ }
3047
+
3048
+ // Second click completes the range \u2014 commit and close immediately,
3049
+ // the same way a preset does, since there's no Apply step anymore.
3050
+ const start = draftValue.start ?? day;
3051
+ const nextValue = isBeforeDay(day, start)
3052
+ ? { start: day, end: start }
3053
+ : { start, end: day };
3054
+
3055
+ setDraftValue(nextValue);
3056
+ setPendingEnd(false);
3057
+ commitValue(nextValue);
3058
+ setOpen(false);
3059
+ };
3060
+
3061
+ const popover =
3062
+ open &&
3063
+ !disabled &&
3064
+ portalMount &&
3065
+ createPortal(
3066
+ <div
3067
+ ref={setPopoverRef}
3068
+ role="dialog"
3069
+ aria-modal="false"
3070
+ aria-labelledby={\`\${triggerId}-calendar-heading\`}
3071
+ className={cn(
3072
+ "flex flex-col rounded-lg border border-solid border-semantic-border-layout bg-semantic-bg-primary shadow-lg overflow-y-auto overflow-x-hidden overscroll-contain pointer-events-auto",
3073
+ "[scrollbar-gutter:stable] [scrollbar-width:thin] [scrollbar-color:var(--semantic-border-secondary)_transparent]",
3074
+ "[&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-semantic-border-secondary"
3075
+ )}
3076
+ style={{
3077
+ ...floatingStyles,
3078
+ maxWidth: \`calc(100vw - \${POPOVER_MARGIN * 2}px)\`,
3079
+ maxHeight: \`var(\${POPOVER_SCROLL_HEIGHT_VAR}, min(\${MAX_POPOVER_HEIGHT}px, calc(100dvh - \${
3080
+ POPOVER_MARGIN * 2
3081
+ }px)))\`,
3082
+ zIndex: 10050,
3083
+ visibility: isPositioned ? undefined : "hidden",
3084
+ }}
3085
+ onPointerDown={(event) => event.stopPropagation()}
3086
+ onMouseDown={(event) => event.stopPropagation()}
3087
+ onWheel={(event) => event.stopPropagation()}
3088
+ onTouchMove={(event) => event.stopPropagation()}
3089
+ >
3090
+ <div className="flex flex-row">
3091
+ {presets.length > 0 && (
3092
+ <div className="flex w-36 shrink-0 flex-col gap-0.5 border-r border-solid border-semantic-border-layout p-3">
3093
+ {presets.map((preset) => (
3094
+ <button
3095
+ key={preset.label}
3096
+ type="button"
3097
+ className="w-full rounded px-2 py-1.5 text-left text-sm text-semantic-text-primary transition-colors hover:bg-semantic-bg-hover"
3098
+ onClick={() => handlePresetClick(preset)}
3099
+ >
3100
+ {preset.label}
3101
+ </button>
3102
+ ))}
3103
+ </div>
3104
+ )}
3105
+
3106
+ <div className="min-w-[272px] flex-1 p-3 touch-pan-y">
3107
+ <div className="mb-3 flex items-center justify-between gap-2">
3108
+ <button
3109
+ type="button"
3110
+ aria-label="Previous month"
3111
+ className="p-1 rounded hover:bg-semantic-bg-hover text-semantic-text-secondary transition-colors"
3112
+ onClick={() =>
3113
+ setVisibleMonth((month) => addMonths(month, -1))
3114
+ }
3115
+ >
3116
+ <ChevronLeft className="size-4" aria-hidden="true" />
3117
+ </button>
3118
+
3119
+ <div
3120
+ id={\`\${triggerId}-calendar-heading\`}
3121
+ className="flex items-center gap-2"
3122
+ >
3123
+ <DropdownMenu>
3124
+ <DropdownMenuTrigger asChild>
3125
+ <button
3126
+ type="button"
3127
+ className="rounded border border-solid border-semantic-border-layout px-3 py-1.5 text-sm font-semibold text-semantic-text-primary transition-colors hover:bg-semantic-bg-hover"
3128
+ >
3129
+ {MONTH_LONG_NAMES[visibleMonth.getMonth()]}
3130
+ </button>
3131
+ </DropdownMenuTrigger>
3132
+ <DropdownMenuContent
3133
+ align="start"
3134
+ className={cn(CALENDAR_DROPDOWN_Z_INDEX, "max-h-[240px] overflow-y-auto")}
3135
+ >
3136
+ {MONTH_LONG_NAMES.map((label, monthIndex) => (
3137
+ <DropdownMenuItem
3138
+ key={label}
3139
+ onSelect={() =>
3140
+ setVisibleMonth(
3141
+ startOfMonth(
3142
+ new Date(visibleMonth.getFullYear(), monthIndex, 1)
3143
+ )
3144
+ )
3145
+ }
3146
+ >
3147
+ {label}
3148
+ </DropdownMenuItem>
3149
+ ))}
3150
+ </DropdownMenuContent>
3151
+ </DropdownMenu>
3152
+
3153
+ <DropdownMenu>
3154
+ <DropdownMenuTrigger asChild>
3155
+ <button
3156
+ type="button"
3157
+ className="flex items-center gap-1 rounded border border-solid border-semantic-border-layout px-3 py-1.5 text-sm font-semibold text-semantic-text-primary transition-colors hover:bg-semantic-bg-hover"
3158
+ >
3159
+ {visibleMonth.getFullYear()}
3160
+ <ChevronDown
3161
+ className="size-3.5 text-semantic-text-muted"
3162
+ aria-hidden="true"
3163
+ />
3164
+ </button>
3165
+ </DropdownMenuTrigger>
3166
+ <DropdownMenuContent
3167
+ align="start"
3168
+ className={cn(CALENDAR_DROPDOWN_Z_INDEX, "max-h-[240px] overflow-y-auto")}
3169
+ >
3170
+ {Array.from(
3171
+ { length: YEAR_OPTIONS_SPAN * 2 + 1 },
3172
+ (_, index) => visibleMonth.getFullYear() - YEAR_OPTIONS_SPAN + index
3173
+ ).map((year) => (
3174
+ <DropdownMenuItem
3175
+ key={year}
3176
+ onSelect={() =>
3177
+ setVisibleMonth(
3178
+ startOfMonth(new Date(year, visibleMonth.getMonth(), 1))
3179
+ )
3180
+ }
3181
+ >
3182
+ {year}
3183
+ </DropdownMenuItem>
3184
+ ))}
3185
+ </DropdownMenuContent>
3186
+ </DropdownMenu>
3187
+ </div>
3188
+
3189
+ <button
3190
+ type="button"
3191
+ aria-label="Next month"
3192
+ className="p-1 rounded hover:bg-semantic-bg-hover text-semantic-text-secondary transition-colors"
3193
+ onClick={() =>
3194
+ setVisibleMonth((month) => addMonths(month, 1))
3195
+ }
3196
+ >
3197
+ <ChevronRight className="size-4" aria-hidden="true" />
3198
+ </button>
3199
+ </div>
3200
+
3201
+ <div className="grid grid-cols-7">
3202
+ {weekDays.map((day) => (
3203
+ <div
3204
+ key={day}
3205
+ className="flex h-8 items-center justify-center text-xs font-medium text-semantic-text-muted"
3206
+ >
3207
+ {day}
3208
+ </div>
3209
+ ))}
3210
+ {calendarDays.map((day) => {
3211
+ const isCurrentMonth =
3212
+ day.getMonth() === visibleMonth.getMonth();
3213
+ const isToday = isSameDay(day, new Date());
3214
+ const { start, end } = draftValue;
3215
+ const hasRange = !!start && !!end && !isSameDay(start, end);
3216
+ const isRangeStart = isSameDay(day, start);
3217
+ const isRangeEnd = isSameDay(day, end);
3218
+ const isBetween =
3219
+ hasRange &&
3220
+ !!start &&
3221
+ !!end &&
3222
+ isAfterDay(day, start) &&
3223
+ isBeforeDay(day, end);
3224
+ const isInBand =
3225
+ hasRange && (isRangeStart || isRangeEnd || isBetween);
3226
+ const isSelectedEdge = isRangeStart || isRangeEnd;
3227
+ const isDisabled =
3228
+ (effectiveMinDate &&
3229
+ isBeforeDay(day, effectiveMinDate)) ||
3230
+ (maxDate && isAfterDay(day, maxDate));
3231
+ const dayLabel = day.toLocaleDateString("en-US", {
3232
+ month: "long",
3233
+ day: "numeric",
3234
+ year: "numeric",
3235
+ });
3236
+
3237
+ return (
3238
+ <div
3239
+ key={day.toISOString()}
3240
+ className={cn(
3241
+ "relative flex h-8 items-center justify-center",
3242
+ isInBand && "bg-semantic-info-surface",
3243
+ isRangeStart && hasRange && "rounded-l-full",
3244
+ isRangeEnd && hasRange && "rounded-r-full"
3245
+ )}
3246
+ >
3247
+ <button
3248
+ type="button"
3249
+ aria-label={dayLabel}
3250
+ aria-pressed={isSelectedEdge}
3251
+ aria-current={isToday ? "date" : undefined}
3252
+ disabled={!!isDisabled}
3253
+ className={cn(
3254
+ "relative flex size-8 items-center justify-center rounded-full text-xs transition-colors",
3255
+ isSelectedEdge
3256
+ ? "bg-semantic-primary text-semantic-text-inverted font-semibold"
3257
+ : isCurrentMonth
3258
+ ? "text-semantic-text-primary hover:bg-semantic-bg-hover"
3259
+ : "text-semantic-text-muted hover:bg-semantic-bg-hover",
3260
+ isDisabled &&
3261
+ "opacity-40 cursor-not-allowed pointer-events-none"
3262
+ )}
3263
+ onClick={() => {
3264
+ if (isDisabled) return;
3265
+
3266
+ handleDayClick(day);
3267
+ }}
3268
+ >
3269
+ {day.getDate()}
3270
+ {isToday && !isSelectedEdge && (
3271
+ <span className="absolute bottom-0.5 left-1/2 -translate-x-1/2 size-1 rounded-full bg-semantic-primary" />
3272
+ )}
3273
+ </button>
3274
+ </div>
3275
+ );
3276
+ })}
3277
+ </div>
3278
+ </div>
3279
+ </div>
3280
+ </div>,
3281
+ portalMount
3282
+ );
3283
+
3284
+ return (
3285
+ <div
3286
+ ref={(node) => {
3287
+ rootRef.current = node;
3288
+
3289
+ if (typeof ref === "function") {
3290
+ ref(node);
3291
+ } else if (ref) {
3292
+ (ref as React.MutableRefObject<HTMLDivElement | null>).current =
3293
+ node;
3294
+ }
3295
+ }}
3296
+ className={cn("relative inline-block w-full max-w-full", className)}
3297
+ {...props}
3298
+ >
3299
+ <button
3300
+ ref={setTriggerRef}
3301
+ id={triggerId}
3302
+ type="button"
3303
+ disabled={disabled}
3304
+ aria-haspopup="dialog"
3305
+ aria-expanded={open}
3306
+ className={cn(
3307
+ dateRangePickerTriggerVariants({ state }),
3308
+ open &&
3309
+ state !== "error" &&
3310
+ "border-semantic-border-input-focus/50 shadow-[0_0_0_1px_rgba(43,188,202,0.15)]",
3311
+ !displayValue && "text-semantic-text-placeholder",
3312
+ triggerClassName
3313
+ )}
3314
+ onClick={() => setOpen(!open)}
3315
+ >
3316
+ <FigmaCalendarIcon className="size-[18px] shrink-0 text-semantic-text-secondary" />
3317
+ <span
3318
+ className={cn(
3319
+ "min-w-0 flex-1 truncate",
3320
+ !displayValue && "font-normal",
3321
+ triggerLabelClassName
3322
+ )}
3323
+ >
3324
+ {displayValue || placeholder}
3325
+ </span>
3326
+ </button>
3327
+
3328
+ {popover}
3329
+ </div>
3330
+ );
3331
+ }
3332
+ );
3333
+ DateRangePicker.displayName = "DateRangePicker";
3334
+
3335
+ export { DateRangePicker, dateRangePickerTriggerVariants };
2565
3336
  `,
2566
3337
  "date-time-picker": `import * as React from "react";
2567
3338
  import { createPortal } from "react-dom";
@@ -5029,7 +5800,7 @@ const DialogContent = React.forwardRef(({ className, children, size, hideCloseBu
5029
5800
  {children}
5030
5801
  {/* Accessibility: Add hidden description if none provided */}
5031
5802
  {!hasDescription && (
5032
- <DialogPrimitive.Description className="sr-only">
5803
+ <DialogPrimitive.Description className="sr-only m-0">
5033
5804
  Dialog content
5034
5805
  </DialogPrimitive.Description>
5035
5806
  )}
@@ -5088,7 +5859,7 @@ DialogTitle.displayName = DialogPrimitive.Title.displayName;
5088
5859
  const DialogDescription = React.forwardRef(({ className, ...props }: React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>, ref: React.Ref<React.ElementRef<typeof DialogPrimitive.Description>>) => (
5089
5860
  <DialogPrimitive.Description
5090
5861
  ref={ref}
5091
- className={cn("text-sm text-muted-foreground", className)}
5862
+ className={cn("m-0 text-sm text-muted-foreground", className)}
5092
5863
  {...props}
5093
5864
  />
5094
5865
  ));
@@ -5816,11 +6587,11 @@ const Input = React.forwardRef(
5816
6587
  input.value.slice(selectionEnd);
5817
6588
 
5818
6589
  if (nextValue.includes(" ")) {
6590
+ // Blocking the insertion is enough \u2014 nothing moved, so the caret is
6591
+ // already where it belongs. Re-setting it (especially from a later
6592
+ // animation frame, with a selection offset captured before this
6593
+ // keystroke) drags the caret backwards and reorders what follows.
5819
6594
  e.preventDefault();
5820
- input.setSelectionRange(selectionStart, selectionStart);
5821
- window.requestAnimationFrame(() => {
5822
- input.setSelectionRange(selectionStart, selectionStart);
5823
- });
5824
6595
  }
5825
6596
  }}
5826
6597
  onChange={(e) => {
@@ -5841,9 +6612,19 @@ const Input = React.forwardRef(
5841
6612
  );
5842
6613
 
5843
6614
  input.value = collapsedValue;
6615
+ // Restore the caret synchronously: assigning \`value\` above parks it
6616
+ // at 0, and the next keystroke can land before any animation frame
6617
+ // runs, which reorders what the user typed. The deferred pass is
6618
+ // kept for browsers that reset the caret again after React
6619
+ // re-renders, but it must no-op once the value has moved on \u2014 a
6620
+ // frame that fires mid-typing would otherwise drag the caret back
6621
+ // to a position captured several keystrokes ago.
6622
+ input.setSelectionRange(nextCursor, nextCursor);
5844
6623
  window.requestAnimationFrame(() => {
6624
+ if (input.value !== collapsedValue) return;
5845
6625
  input.setSelectionRange(nextCursor, nextCursor);
5846
6626
  });
6627
+
5847
6628
  }
5848
6629
  onChange?.(e);
5849
6630
  }}
@@ -5987,6 +6768,18 @@ export function flattenMultiSelectOptions(
5987
6768
  return (input as MultiSelectOption[]).map(normalizeMultiSelectOption);
5988
6769
  }
5989
6770
 
6771
+ /** Menu never renders narrower than this, even off a tiny trigger. */
6772
+ const MENU_MIN_WIDTH = 180;
6773
+ /** Floor for the flip/shrink height so the list is never collapsed to nothing. */
6774
+ const MENU_MIN_HEIGHT = 120;
6775
+
6776
+ /**
6777
+ * Distance from the bottom of the option list, in px, at which \`onScrollEnd\`
6778
+ * fires. Roughly one option row, so the next page starts loading just before
6779
+ * the user actually hits the end.
6780
+ */
6781
+ const SCROLL_END_THRESHOLD_PX = 48;
6782
+
5990
6783
  /**
5991
6784
  * MultiSelect trigger variants matching TextField styling
5992
6785
  */
@@ -6020,6 +6813,27 @@ export interface MultiSelectProps extends VariantProps<
6020
6813
  disabled?: boolean;
6021
6814
  /** Loading state with spinner */
6022
6815
  loading?: boolean;
6816
+ /**
6817
+ * Called once when the option list is scrolled to within
6818
+ * \`SCROLL_END_THRESHOLD_PX\` of its bottom. Use it to fetch the next page of
6819
+ * server-side options. Fires at most once per scroll gesture \u2014 it re-arms
6820
+ * only after the user scrolls back away from the bottom, so a single
6821
+ * trackpad flick (which keeps emitting scroll events while it decelerates)
6822
+ * cannot fan out into several requests.
6823
+ */
6824
+ onScrollEnd?: () => void;
6825
+ /**
6826
+ * Whether the server has more pages. When false, \`onScrollEnd\` is never
6827
+ * called.
6828
+ * @default false
6829
+ */
6830
+ hasMore?: boolean;
6831
+ /**
6832
+ * Whether a page fetch is in flight. Renders a loading row at the bottom of
6833
+ * the list and suppresses further \`onScrollEnd\` calls.
6834
+ * @default false
6835
+ */
6836
+ loadingMore?: boolean;
6023
6837
  /** Placeholder text when no value selected */
6024
6838
  placeholder?: string;
6025
6839
  /** Currently selected values (controlled) */
@@ -6039,6 +6853,18 @@ export interface MultiSelectProps extends VariantProps<
6039
6853
  searchable?: boolean;
6040
6854
  /** Search placeholder text */
6041
6855
  searchPlaceholder?: string;
6856
+ /**
6857
+ * When set, the trigger shows a single compact summary (e.g. "3 lines
6858
+ * selected") instead of one chip per selection; hovering it reveals the full
6859
+ * list of selected labels in a tooltip. Receives the selected count.
6860
+ */
6861
+ summaryLabel?: (count: number) => string;
6862
+ /**
6863
+ * When set, pins a select-all row at the top of the list with this label
6864
+ * (e.g. "All lines"). Toggles every non-disabled option; respects
6865
+ * \`maxSelections\`. Omit to hide the row entirely.
6866
+ */
6867
+ selectAllLabel?: string;
6042
6868
  /** Maximum selections allowed */
6043
6869
  maxSelections?: number;
6044
6870
  /**
@@ -6068,6 +6894,19 @@ export interface MultiSelectProps extends VariantProps<
6068
6894
  showClearAll?: boolean;
6069
6895
  /** Vertical rule before the chevron (Figma-style trigger) */
6070
6896
  showSeparatorBeforeChevron?: boolean;
6897
+ /**
6898
+ * Element the dropdown is portaled into. Defaults to \`document.body\` so the
6899
+ * menu escapes \`overflow\` and \`transform\` ancestors (a Radix DialogContent is
6900
+ * both, and a transformed ancestor would clip a \`position: fixed\` menu).
6901
+ */
6902
+ menuContainer?: HTMLElement | null;
6903
+ /**
6904
+ * Truncate long labels in the dropdown list to a single line with an ellipsis
6905
+ * instead of wrapping them. Selected chips in the trigger always truncate \u2014
6906
+ * this only controls the option rows.
6907
+ * @default false for \`simple\`, true for \`detailed\` (single-line row design)
6908
+ */
6909
+ truncateOptionText?: boolean;
6071
6910
  }
6072
6911
 
6073
6912
  /**
@@ -6096,6 +6935,9 @@ const MultiSelect = React.forwardRef(
6096
6935
  error,
6097
6936
  disabled,
6098
6937
  loading,
6938
+ onScrollEnd,
6939
+ hasMore = false,
6940
+ loadingMore = false,
6099
6941
  placeholder = "Select options",
6100
6942
  value,
6101
6943
  defaultValue = [],
@@ -6103,6 +6945,8 @@ const MultiSelect = React.forwardRef(
6103
6945
  options,
6104
6946
  searchable,
6105
6947
  searchPlaceholder = "Search...",
6948
+ selectAllLabel,
6949
+ summaryLabel,
6106
6950
  maxSelections,
6107
6951
  showSelectionFooter = true,
6108
6952
  wrapperClassName,
@@ -6114,6 +6958,8 @@ const MultiSelect = React.forwardRef(
6114
6958
  optionVariant = "simple",
6115
6959
  separateSelectedWithDivider = false,
6116
6960
  showClearAll = true,
6961
+ menuContainer,
6962
+ truncateOptionText,
6117
6963
  showSeparatorBeforeChevron = false,
6118
6964
  closeOnEscape = false,
6119
6965
  }: MultiSelectProps,
@@ -6127,9 +6973,25 @@ const MultiSelect = React.forwardRef(
6127
6973
  // Search query
6128
6974
  const [searchQuery, setSearchQuery] = React.useState("");
6129
6975
 
6976
+ // \`detailed\` rows are a single-line design, so they truncate unless the
6977
+ // caller opts out; \`simple\` rows wrap the full label unless asked not to.
6978
+ const truncateOptions =
6979
+ truncateOptionText ?? optionVariant === "detailed";
6980
+
6130
6981
  // Container ref for click outside detection
6131
6982
  const containerRef = React.useRef<HTMLDivElement | null>(null);
6132
6983
 
6984
+ /** Where the dropdown gets portaled; body unless the caller overrides. */
6985
+ const [portalTarget, setPortalTarget] = React.useState<HTMLElement | null>(
6986
+ null
6987
+ );
6988
+
6989
+ React.useEffect(() => {
6990
+ if (!isOpen || typeof document === "undefined") return;
6991
+ setPortalTarget(menuContainer ?? document.body);
6992
+ }, [isOpen, menuContainer]);
6993
+
6994
+
6133
6995
  const { refs, floatingStyles, isPositioned } = useFloating({
6134
6996
  open: isOpen,
6135
6997
  placement: "bottom-start",
@@ -6140,8 +7002,19 @@ const MultiSelect = React.forwardRef(
6140
7002
  shift({ padding: 8 }),
6141
7003
  size({
6142
7004
  padding: 8,
6143
- apply({ rects, elements }) {
6144
- elements.floating.style.width = \`\${rects.reference.width}px\`;
7005
+ apply({ rects, elements, availableHeight, availableWidth }) {
7006
+ // Match the trigger, but never render an unreadably narrow menu on a
7007
+ // small trigger, and never spill past the viewport on a small screen.
7008
+ const width = Math.min(
7009
+ Math.max(rects.reference.width, MENU_MIN_WIDTH),
7010
+ availableWidth
7011
+ );
7012
+ elements.floating.style.width = \`\${width}px\`;
7013
+ // Let the option list shrink instead of overflowing a short viewport.
7014
+ elements.floating.style.setProperty(
7015
+ "--multi-select-available-height",
7016
+ \`\${Math.max(availableHeight, MENU_MIN_HEIGHT)}px\`
7017
+ );
6145
7018
  },
6146
7019
  }),
6147
7020
  ],
@@ -6163,6 +7036,112 @@ const MultiSelect = React.forwardRef(
6163
7036
  [refs]
6164
7037
  );
6165
7038
 
7039
+ /**
7040
+ * Radix Dialog / Drawer wrap their content in \`react-remove-scroll\`, which
7041
+ * listens for \`wheel\` / \`touchmove\` on \`document\` (bubble phase) and calls
7042
+ * \`preventDefault()\` for anything outside the locked subtree \u2014 our
7043
+ * body-portaled menu counts as outside, so its list would not scroll.
7044
+ * Stopping propagation at the menu keeps that document listener from ever
7045
+ * seeing the event, leaving the browser's native scrolling intact.
7046
+ * Clicks are handled separately: the dialog sets \`pointer-events: none\` on
7047
+ * \`<body>\`, which the menu overrides with \`pointer-events: auto\`.
7048
+ */
7049
+ React.useEffect(() => {
7050
+ const node = refs.floating.current;
7051
+ if (!isOpen || !node) return;
7052
+
7053
+ const stop = (event: Event) => event.stopPropagation();
7054
+ node.addEventListener("wheel", stop, { passive: false });
7055
+ node.addEventListener("touchmove", stop, { passive: false });
7056
+
7057
+ /**
7058
+ * The dialog also traps focus. Radix \`FocusScope\` registers TWO bubble
7059
+ * listeners on \`document\` and either one is enough to make the search
7060
+ * input untypable:
7061
+ * - \`focusin\` \u2014 target outside the dialog, so focus is pulled back.
7062
+ * - \`focusout\` \u2014 fired on the element focus is LEAVING (inside the
7063
+ * dialog) with \`relatedTarget\` = our input; since that is outside, it
7064
+ * restores the previously focused element.
7065
+ * The \`focusout\` one never travels through the menu, so a listener on the
7066
+ * menu node cannot see it. Intercepting in the CAPTURE phase on
7067
+ * \`document\` does: capture at \`document\` runs before the bubble listeners
7068
+ * on the same node, so stopping propagation there means FocusScope never
7069
+ * runs \u2014 but only for focus events that involve the menu. Every other
7070
+ * focus event in the app is untouched.
7071
+ */
7072
+ const stopMenuFocusEvent = (event: Event) => {
7073
+ const { target, relatedTarget } = event as FocusEvent;
7074
+ const touchesMenu =
7075
+ (target instanceof Node && node.contains(target)) ||
7076
+ (relatedTarget instanceof Node && node.contains(relatedTarget));
7077
+ if (touchesMenu) event.stopPropagation();
7078
+ };
7079
+ document.addEventListener("focusin", stopMenuFocusEvent, true);
7080
+ document.addEventListener("focusout", stopMenuFocusEvent, true);
7081
+
7082
+ return () => {
7083
+ node.removeEventListener("wheel", stop);
7084
+ node.removeEventListener("touchmove", stop);
7085
+ document.removeEventListener("focusin", stopMenuFocusEvent, true);
7086
+ document.removeEventListener("focusout", stopMenuFocusEvent, true);
7087
+ };
7088
+ }, [isOpen, portalTarget, refs.floating]);
7089
+
7090
+ /** The scrollable option list; also read by the no-scrollbar fallback. */
7091
+ const listRef = React.useRef<HTMLDivElement | null>(null);
7092
+ /**
7093
+ * True once \`onScrollEnd\` has fired for the current visit to the bottom.
7094
+ * Cleared only when the user scrolls back out of the threshold zone, so
7095
+ * trackpad inertia \u2014 which keeps firing \`scroll\` for hundreds of ms after
7096
+ * the finger lifts \u2014 cannot re-trigger the callback.
7097
+ */
7098
+ const isLatchedRef = React.useRef(false);
7099
+
7100
+ /**
7101
+ * Deliberately a plain function, not a \`useCallback\` \u2014 it must read the
7102
+ * current \`hasMore\` / \`loadingMore\` on every scroll event, and a memoised
7103
+ * handler would need a props ref (writing refs during render is banned).
7104
+ */
7105
+ const maybeLoadMore = () => {
7106
+ const node = listRef.current;
7107
+ if (!node) return;
7108
+
7109
+ const isNearBottom =
7110
+ node.scrollHeight - node.scrollTop - node.clientHeight <
7111
+ SCROLL_END_THRESHOLD_PX;
7112
+
7113
+ if (!isNearBottom) {
7114
+ // Only an explicit scroll away from the boundary re-arms the latch.
7115
+ isLatchedRef.current = false;
7116
+ return;
7117
+ }
7118
+ if (isLatchedRef.current || !hasMore || loadingMore || !onScrollEnd)
7119
+ return;
7120
+
7121
+ isLatchedRef.current = true;
7122
+ onScrollEnd();
7123
+ };
7124
+
7125
+ // A closed menu or a new search starts from a clean latch.
7126
+ React.useEffect(() => {
7127
+ isLatchedRef.current = false;
7128
+ }, [isOpen, searchQuery]);
7129
+
7130
+ /**
7131
+ * A page can arrive without making the list taller than its max-height
7132
+ * (few results, or a short viewport). No further \`scroll\` event would ever
7133
+ * fire, so pagination would stall silently \u2014 re-check whenever the rendered
7134
+ * options or the fetch state change.
7135
+ */
7136
+ React.useEffect(() => {
7137
+ if (!isOpen || !hasMore || loadingMore) return;
7138
+ const node = listRef.current;
7139
+ if (!node || node.scrollHeight > node.clientHeight) return;
7140
+ if (isLatchedRef.current) return;
7141
+ isLatchedRef.current = true;
7142
+ onScrollEnd?.();
7143
+ }, [isOpen, portalTarget, hasMore, loadingMore, options, onScrollEnd]);
7144
+
6166
7145
  const flatOptions = React.useMemo(
6167
7146
  () => flattenMultiSelectOptions(options),
6168
7147
  [options]
@@ -6172,6 +7151,28 @@ const MultiSelect = React.forwardRef(
6172
7151
  const isControlled = value !== undefined;
6173
7152
  const selectedValues = isControlled ? value : internalValue;
6174
7153
 
7154
+ // Select-all: values eligible for select-all exclude disabled options
7155
+ const selectableValues = React.useMemo(
7156
+ () => flatOptions.filter((o) => !o.disabled).map((o) => o.value),
7157
+ [flatOptions]
7158
+ );
7159
+ const allSelected =
7160
+ selectableValues.length > 0 &&
7161
+ selectableValues.every((v) => selectedValues.includes(v));
7162
+ const someSelected = selectedValues.length > 0 && !allSelected;
7163
+
7164
+ const toggleSelectAll = () => {
7165
+ const newValues = allSelected
7166
+ ? []
7167
+ : maxSelections
7168
+ ? selectableValues.slice(0, maxSelections)
7169
+ : selectableValues;
7170
+ if (!isControlled) {
7171
+ setInternalValue(newValues);
7172
+ }
7173
+ onValueChange?.(newValues);
7174
+ };
7175
+
6175
7176
  // Derive state from props
6176
7177
  const derivedState = error ? "error" : (state ?? "default");
6177
7178
 
@@ -6344,14 +7345,14 @@ const MultiSelect = React.forwardRef(
6344
7345
  return (
6345
7346
  <div
6346
7347
  ref={containerRef}
6347
- className={cn("flex flex-col gap-1", wrapperClassName)}
7348
+ className={cn("flex min-w-0 flex-col gap-1", wrapperClassName)}
6348
7349
  >
6349
7350
  {/* Label */}
6350
7351
  {label && (
6351
7352
  <label
6352
7353
  htmlFor={selectId}
6353
7354
  className={cn(
6354
- "text-sm font-semibold text-semantic-text-secondary",
7355
+ "break-words text-sm font-semibold text-semantic-text-secondary",
6355
7356
  labelClassName
6356
7357
  )}
6357
7358
  >
@@ -6387,18 +7388,40 @@ const MultiSelect = React.forwardRef(
6387
7388
  triggerClassName
6388
7389
  )}
6389
7390
  >
6390
- <div className="flex-1 flex flex-wrap gap-1">
7391
+ <div className="min-w-0 flex-1 flex flex-wrap gap-1">
6391
7392
  {selectedValues.length === 0 ? (
6392
7393
  <span className="text-base text-semantic-text-placeholder">
6393
7394
  {placeholder}
6394
7395
  </span>
7396
+ ) : summaryLabel ? (
7397
+ <TooltipProvider delayDuration={200}>
7398
+ <Tooltip>
7399
+ <TooltipTrigger asChild>
7400
+ <span className="min-w-0 truncate text-sm text-semantic-text-primary">
7401
+ {summaryLabel(selectedValues.length)}
7402
+ </span>
7403
+ </TooltipTrigger>
7404
+ <TooltipContent>
7405
+ <div className="flex flex-col gap-0.5">
7406
+ {selectedLabels.map((label, index) => (
7407
+ <span key={selectedValues[index]}>{label}</span>
7408
+ ))}
7409
+ </div>
7410
+ </TooltipContent>
7411
+ </Tooltip>
7412
+ </TooltipProvider>
6395
7413
  ) : (
6396
7414
  selectedLabels.map((label, index) => (
6397
7415
  <span
6398
7416
  key={selectedValues[index]}
6399
- className="inline-flex items-center gap-1 bg-semantic-bg-ui text-semantic-text-primary text-sm px-2 py-0.5 rounded"
7417
+ className="inline-flex min-w-0 max-w-full items-center gap-1 bg-semantic-bg-ui text-semantic-text-primary text-sm px-2 py-0.5 rounded"
6400
7418
  >
6401
- {label}
7419
+ <span
7420
+ className="min-w-0 truncate"
7421
+ title={typeof label === "string" ? label : undefined}
7422
+ >
7423
+ {label}
7424
+ </span>
6402
7425
  <span
6403
7426
  role="button"
6404
7427
  tabIndex={0}
@@ -6412,7 +7435,7 @@ const MultiSelect = React.forwardRef(
6412
7435
  );
6413
7436
  }
6414
7437
  }}
6415
- className="cursor-pointer hover:text-semantic-error-primary focus:outline-none"
7438
+ className="shrink-0 cursor-pointer hover:text-semantic-error-primary focus:outline-none"
6416
7439
  aria-label={\`Remove \${label}\`}
6417
7440
  >
6418
7441
  <X className="size-3" />
@@ -6471,14 +7494,14 @@ const MultiSelect = React.forwardRef(
6471
7494
  className="size-3.5 shrink-0 text-semantic-error-primary"
6472
7495
  aria-hidden
6473
7496
  />
6474
- <span className="text-sm text-semantic-error-primary">
7497
+ <span className="min-w-0 break-words text-sm text-semantic-error-primary">
6475
7498
  {error}
6476
7499
  </span>
6477
7500
  </div>
6478
7501
  ) : helperText ? (
6479
7502
  <span
6480
7503
  id={helperId}
6481
- className="text-sm text-semantic-text-muted"
7504
+ className="min-w-0 break-words text-sm text-semantic-text-muted"
6482
7505
  >
6483
7506
  {helperText}
6484
7507
  </span>
@@ -6487,7 +7510,7 @@ const MultiSelect = React.forwardRef(
6487
7510
  )}
6488
7511
 
6489
7512
  {isOpen &&
6490
- typeof document !== "undefined" &&
7513
+ portalTarget &&
6491
7514
  createPortal(
6492
7515
  <TooltipProvider delayDuration={200}>
6493
7516
  <div
@@ -6499,6 +7522,9 @@ const MultiSelect = React.forwardRef(
6499
7522
  style={{
6500
7523
  ...floatingStyles,
6501
7524
  zIndex: 10050,
7525
+ // Radix Dialog sets \`pointer-events: none\` on <body> while
7526
+ // open; without this the menu swallows nothing and clicks die.
7527
+ pointerEvents: "auto",
6502
7528
  visibility: isPositioned ? undefined : "hidden",
6503
7529
  }}
6504
7530
  onMouseDown={(e) => e.stopPropagation()}
@@ -6517,9 +7543,43 @@ const MultiSelect = React.forwardRef(
6517
7543
  </div>
6518
7544
  )}
6519
7545
 
7546
+ {/* Select all */}
7547
+ {selectAllLabel && (
7548
+ <div
7549
+ role="option"
7550
+ aria-selected={allSelected}
7551
+ tabIndex={0}
7552
+ onClick={toggleSelectAll}
7553
+ onKeyDown={(e) => {
7554
+ if (e.key === "Enter" || e.key === " ") {
7555
+ e.preventDefault();
7556
+ toggleSelectAll();
7557
+ }
7558
+ }}
7559
+ className="flex w-full cursor-pointer select-none items-center gap-2 border-b border-solid border-semantic-border-layout px-3 py-2 text-sm text-semantic-text-primary outline-none hover:bg-semantic-bg-ui"
7560
+ >
7561
+ <Checkbox
7562
+ checked={allSelected ? true : someSelected ? "indeterminate" : false}
7563
+ size="sm"
7564
+ className="pointer-events-none shrink-0"
7565
+ aria-hidden
7566
+ tabIndex={-1}
7567
+ />
7568
+ <span className="min-w-0 flex-1 truncate text-left">{selectAllLabel}</span>
7569
+ </div>
7570
+ )}
7571
+
6520
7572
  {/* Options */}
6521
- <div className="max-h-60 overflow-auto p-1">
6522
- {filteredOptions.length === 0 ? (
7573
+ <div
7574
+ ref={listRef}
7575
+ onScroll={maybeLoadMore}
7576
+ className="overflow-auto overscroll-contain p-1"
7577
+ style={{
7578
+ maxHeight:
7579
+ "min(15rem, var(--multi-select-available-height, 15rem))",
7580
+ }}
7581
+ >
7582
+ {filteredOptions.length === 0 && !loadingMore ? (
6523
7583
  <div className="py-6 text-center text-sm text-semantic-text-muted">
6524
7584
  No results found
6525
7585
  </div>
@@ -6583,7 +7643,19 @@ const MultiSelect = React.forwardRef(
6583
7643
  <Check className="size-4 text-semantic-primary" />
6584
7644
  )}
6585
7645
  </span>
6586
- <span className="min-w-0 flex-1 whitespace-normal break-words text-left">
7646
+ <span
7647
+ title={
7648
+ truncateOptions && typeof option.label === "string"
7649
+ ? option.label
7650
+ : undefined
7651
+ }
7652
+ className={cn(
7653
+ "min-w-0 flex-1 text-left",
7654
+ truncateOptions
7655
+ ? "truncate"
7656
+ : "whitespace-normal break-words"
7657
+ )}
7658
+ >
6587
7659
  {option.label}
6588
7660
  </span>
6589
7661
  </button>
@@ -6615,7 +7687,19 @@ const MultiSelect = React.forwardRef(
6615
7687
  aria-hidden
6616
7688
  tabIndex={-1}
6617
7689
  />
6618
- <span className="min-w-0 flex-1 truncate text-left">
7690
+ <span
7691
+ title={
7692
+ typeof option.label === "string"
7693
+ ? option.label
7694
+ : undefined
7695
+ }
7696
+ className={cn(
7697
+ "min-w-0 flex-1 text-left",
7698
+ truncateOptions
7699
+ ? "truncate"
7700
+ : "whitespace-normal break-words"
7701
+ )}
7702
+ >
6619
7703
  {option.label}
6620
7704
  </span>
6621
7705
  {secondaryLine ? (
@@ -6657,6 +7741,17 @@ const MultiSelect = React.forwardRef(
6657
7741
  return withDisabledTooltip(simpleRow);
6658
7742
  })
6659
7743
  )}
7744
+
7745
+ {loadingMore ? (
7746
+ <div
7747
+ role="status"
7748
+ aria-live="polite"
7749
+ className="flex items-center justify-center gap-2 py-3 text-sm text-semantic-text-muted"
7750
+ >
7751
+ <Loader2 className="size-4 animate-spin" />
7752
+ <span>Loading more...</span>
7753
+ </div>
7754
+ ) : null}
6660
7755
  </div>
6661
7756
 
6662
7757
  {/* Footer with count */}
@@ -6667,7 +7762,7 @@ const MultiSelect = React.forwardRef(
6667
7762
  ) : null}
6668
7763
  </div>
6669
7764
  </TooltipProvider>,
6670
- document.body
7765
+ portalTarget
6671
7766
  )}
6672
7767
  </div>
6673
7768
 
@@ -10680,7 +11775,15 @@ const TextField = React.forwardRef(
10680
11775
  );
10681
11776
 
10682
11777
  input.value = collapsedValue;
11778
+ // Restore the caret synchronously: assigning \`value\` above parks it at
11779
+ // 0, and the next keystroke can land before any animation frame runs,
11780
+ // which reorders what the user typed. The deferred pass is kept for
11781
+ // browsers that reset the caret again after React re-renders, but it
11782
+ // must no-op once the value has moved on \u2014 a frame that fires mid-typing
11783
+ // would otherwise drag the caret back to a stale position.
11784
+ input.setSelectionRange(nextCursor, nextCursor);
10683
11785
  window.requestAnimationFrame(() => {
11786
+ if (input.value !== collapsedValue) return;
10684
11787
  input.setSelectionRange(nextCursor, nextCursor);
10685
11788
  });
10686
11789
  }
@@ -10764,11 +11867,11 @@ const TextField = React.forwardRef(
10764
11867
  input.value.slice(selectionEnd);
10765
11868
 
10766
11869
  if (nextValue.includes(" ")) {
11870
+ // Blocking the insertion is enough \u2014 nothing moved, so the caret is
11871
+ // already where it belongs. Re-setting it from a later animation
11872
+ // frame, with an offset captured before this keystroke, drags the
11873
+ // caret backwards and reorders what follows.
10767
11874
  e.preventDefault();
10768
- input.setSelectionRange(selectionStart, selectionStart);
10769
- window.requestAnimationFrame(() => {
10770
- input.setSelectionRange(selectionStart, selectionStart);
10771
- });
10772
11875
  }
10773
11876
  }}
10774
11877
  onChange={handleChange}
@@ -12632,6 +13735,25 @@ var componentMetadata = {
12632
13735
  }
12633
13736
  ]
12634
13737
  },
13738
+ "date-range-picker": {
13739
+ "name": "DateRangePicker",
13740
+ "description": "A date range picker component.",
13741
+ "dependencies": [
13742
+ "class-variance-authority",
13743
+ "clsx",
13744
+ "tailwind-merge",
13745
+ "lucide-react"
13746
+ ],
13747
+ "props": [],
13748
+ "variants": [],
13749
+ "examples": [
13750
+ {
13751
+ "title": "Basic DateRangePicker",
13752
+ "code": "<DateRangePicker>Content</DateRangePicker>",
13753
+ "description": "Simple date range picker usage"
13754
+ }
13755
+ ]
13756
+ },
12635
13757
  "date-time-picker": {
12636
13758
  "name": "DateTimePicker",
12637
13759
  "description": "A date time picker component.",