@neasg/design-system 0.4.18 → 0.4.20
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/animated-icons/filter.js +1 -1
- package/dist/avatar-profile-popover.js +2 -1
- package/dist/command-search.js +3 -2
- package/dist/date-input.d.ts +2 -0
- package/dist/date-input.js +6 -6
- package/dist/filter-popover.d.ts +5 -1
- package/dist/filter-popover.js +16 -7
- package/dist/filter-toggle.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/multi-select.js +106 -21
- package/dist/popover-menu.d.ts +8 -4
- package/dist/popover-menu.js +13 -7
- package/dist/popover.d.ts +9 -3
- package/dist/popover.js +54 -3
- package/dist/section-nav.js +4 -3
- package/dist/select-primitive.js +1 -1
- package/dist/select.js +37 -1
- package/dist/table-column-filter.d.ts +42 -0
- package/dist/table-column-filter.js +80 -0
- package/dist/table.d.ts +5 -0
- package/dist/table.js +25 -16
- package/dist/tabs.js +2 -2
- package/dist/use-search-highlight.js +1 -1
- package/package.json +1 -1
|
@@ -72,7 +72,7 @@ const FilterIcon = forwardRef(({ onMouseEnter, onMouseLeave, className, size = 2
|
|
|
72
72
|
controls.start("normal");
|
|
73
73
|
}
|
|
74
74
|
}, [controls, onMouseLeave, isParentHovered]);
|
|
75
|
-
return (_jsx("div", { ref: divRef, className: cn(className), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, ...props, children: _jsx(motion.svg, { animate: controls, initial: "normal", fill: "none", height: size, stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", transition: DEFAULT_TRANSITION, variants: SVG_VARIANTS, viewBox: "0 0 24 24", width: size, xmlns: "http://www.w3.org/2000/svg", children: _jsx("polygon", { points: "22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" }) }) }));
|
|
75
|
+
return (_jsx("div", { ref: divRef, "data-slot": "filter-icon", className: cn(className), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, ...props, children: _jsx(motion.svg, { animate: controls, initial: "normal", fill: "none", height: size, stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", transition: DEFAULT_TRANSITION, variants: SVG_VARIANTS, viewBox: "0 0 24 24", width: size, xmlns: "http://www.w3.org/2000/svg", children: _jsx("polygon", { points: "22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" }) }) }));
|
|
76
76
|
});
|
|
77
77
|
FilterIcon.displayName = "FilterIcon";
|
|
78
78
|
export { FilterIcon };
|
|
@@ -4,6 +4,7 @@ import { AvatarProfile } from "./avatar";
|
|
|
4
4
|
import { Button } from "./button";
|
|
5
5
|
import { cn } from "./lib/utils";
|
|
6
6
|
import { Popover } from "./popover";
|
|
7
|
+
import { popoverMenuItemFocusClassName } from "./popover-menu";
|
|
7
8
|
import { Typography } from "./typography";
|
|
8
9
|
function AvatarProfilePopover({ trigger, children, contentClassName, align = "start", sideOffset = 4, ...props }) {
|
|
9
10
|
return (_jsx(Popover, { ...props, trigger: trigger, surface: "menu", align: align, sideOffset: sideOffset, contentClassName: cn("max-h-[calc(100vh-1rem)] w-80 max-w-[calc(100vw-1rem)] overflow-visible", contentClassName), children: children }));
|
|
@@ -21,7 +22,7 @@ function AvatarProfilePopoverFooter({ className, ...props }) {
|
|
|
21
22
|
return (_jsx("div", { "data-slot": "avatar-profile-popover-footer", className: cn("-mx-1 -mb-1 border-t p-1.5", className), ...props }));
|
|
22
23
|
}
|
|
23
24
|
function AvatarProfilePopoverFooterAction({ className, tone = "default", type = "button", ...props }) {
|
|
24
|
-
return (_jsx(Button, { type: type, variant: "ghost", size: "sm", className: cn("h-auto min-h-8 w-full justify-start px-2 py-1.5 text-left whitespace-normal
|
|
25
|
+
return (_jsx(Button, { type: type, variant: "ghost", size: "sm", className: cn("h-auto min-h-8 w-full justify-start px-2 py-1.5 text-left whitespace-normal", popoverMenuItemFocusClassName, tone === "destructive" &&
|
|
25
26
|
"text-destructive hover:bg-destructive/10 hover:text-destructive", className), ...props }));
|
|
26
27
|
}
|
|
27
28
|
export { AvatarProfilePopover, AvatarProfilePopoverContent, AvatarProfilePopoverHeader, AvatarProfilePopoverSection, AvatarProfilePopoverFooter, AvatarProfilePopoverFooterAction, };
|
package/dist/command-search.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { ArrowRight, ArrowUpRight
|
|
4
|
+
import { ArrowRight, ArrowUpRight } from "lucide-react";
|
|
5
|
+
import { FilterIcon } from "./animated-icons";
|
|
5
6
|
import { Badge } from "./badge";
|
|
6
7
|
import { Button } from "./button";
|
|
7
8
|
import { Command, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from "./command";
|
|
@@ -211,7 +212,7 @@ function CommandSearch({ value, onValueChange, sections, children, className, is
|
|
|
211
212
|
? "h-control w-full min-w-[14rem] justify-start gap-[var(--space-2)] rounded-control border border-[hsl(var(--layout-sidebar-border))] bg-card px-[var(--space-2)] !font-normal text-muted-foreground shadow-none hover:bg-card hover:text-foreground focus-visible:border-ring focus-visible:bg-card focus-visible:ring-ring/30"
|
|
212
213
|
: isCollapsed
|
|
213
214
|
? "h-control min-h-control w-control min-w-control max-w-control shrink-0 rounded-control p-[var(--space-0)] !font-normal text-muted-foreground hover:bg-card hover:text-foreground"
|
|
214
|
-
: "h-control w-full min-w-[14rem] justify-start gap-[var(--space-2)] rounded-control border border-border bg-card px-[var(--space-2)] !font-normal text-muted-foreground hover:bg-card hover:text-foreground", triggerClassName), children: [_jsx(SearchIcon, { className: "shrink-0", size: 16 }), !isCollapsed ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "flex-1 text-left text-sm", children: triggerPlaceholder }), _jsx("kbd", { className: "hidden h-[var(--space-5)] items-center gap-[var(--space-1)] rounded border border-border bg-muted px-[var(--space-1)] font-mono text-[11px] text-muted-foreground sm:inline-flex", children: resolvedLabel })] })) : null] }), _jsx(DialogRoot, { open: isOpen, onOpenChange: setIsOpen, children: _jsxs(DialogSurface, { className: cn("w-[calc(100vw-var(--space-8))] max-w-[60rem] gap-[var(--space-0)] overflow-hidden p-[var(--space-0)] sm:w-[min(72vw,60rem)]", dialogClassName), "aria-describedby": undefined, hideCloseButton: true, children: [_jsx(DialogTitle, { className: "sr-only", children: inputPlaceholder }), _jsxs(Command, { shouldFilter: false, className: "rounded-none border-0", children: [_jsx(CommandInput, { ref: inputRef, value: value, onValueChange: onValueChange, placeholder: inputPlaceholder, wrapperClassName: "border-b px-[var(--space-3)] py-[var(--space-2)]", iconClassName: "hidden", className: "h-control border-0 bg-transparent px-[var(--space-3)] py-[var(--space-0)] pr-[calc(var(--space-12)+var(--space-6))] text-sm shadow-none focus-visible:border-transparent focus-visible:ring-0", endAdornment: _jsxs(_Fragment, { children: [value ? (_jsx("button", { type: "button", "aria-label": "Clear search", onMouseDown: preserveInputFocus, onClick: clearSearch, className: "flex size-[var(--space-6)] items-center justify-center rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: _jsx(XIcon, { size: 14, "aria-hidden": "true" }) })) : null, _jsx("kbd", { className: "inline-flex h-[var(--space-5)] items-center rounded border border-border bg-muted px-[var(--space-1)] font-mono text-[11px] leading-none text-muted-foreground", "aria-hidden": "true", children: "Esc" })] }) }), allFilter || filters.length ? (_jsxs("div", { role: "group", "aria-label": "Search filters", "data-multiselectable": allowMultiSelect ? "true" : "false", className: "flex flex-wrap items-center gap-[var(--space-1)] border-b px-[var(--space-3)] py-[var(--space-2)]", children: [allFilter ? (_jsxs(FilterToggle, { selected: allFilter.selected, "aria-pressed": Boolean(allFilter.selected), onMouseDown: preserveInputFocus, onClick: () => selectFilter(allFilter), children: [allFilter.label, typeof allFilter.count === "number" ? (_jsx(CountBadge, { count: allFilter.count, hideWhenZero: true, size: "sm", variant: allFilter.selected ? "default" : "muted" })) : null] })) : null, filters.map((filter) => (_jsxs(FilterToggle, { selected: filter.selected, "aria-pressed": Boolean(filter.selected), onMouseDown: preserveInputFocus, onClick: () => selectFilter(filter), children: [filter.label, typeof filter.count === "number" ? (_jsx(CountBadge, { count: filter.count, hideWhenZero: true, size: "sm", variant: filter.selected ? "default" : "muted" })) : null] }, filter.value)))] })) : null, subFilters.length ? (_jsxs("div", { role: "group", "aria-label": "Search sub-filters", "data-multiselectable": allowMultiSelect ? "true" : "false", className: "flex flex-wrap items-center gap-[var(--space-1)] border-b bg-muted/30 px-[var(--space-3)] py-[var(--space-2)]", children: [_jsx(
|
|
215
|
+
: "h-control w-full min-w-[14rem] justify-start gap-[var(--space-2)] rounded-control border border-border bg-card px-[var(--space-2)] !font-normal text-muted-foreground hover:bg-card hover:text-foreground", triggerClassName), children: [_jsx(SearchIcon, { className: "shrink-0", size: 16 }), !isCollapsed ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "flex-1 text-left text-sm", children: triggerPlaceholder }), _jsx("kbd", { className: "hidden h-[var(--space-5)] items-center gap-[var(--space-1)] rounded border border-border bg-muted px-[var(--space-1)] font-mono text-[11px] text-muted-foreground sm:inline-flex", children: resolvedLabel })] })) : null] }), _jsx(DialogRoot, { open: isOpen, onOpenChange: setIsOpen, children: _jsxs(DialogSurface, { className: cn("w-[calc(100vw-var(--space-8))] max-w-[60rem] gap-[var(--space-0)] overflow-hidden p-[var(--space-0)] sm:w-[min(72vw,60rem)]", dialogClassName), "aria-describedby": undefined, hideCloseButton: true, children: [_jsx(DialogTitle, { className: "sr-only", children: inputPlaceholder }), _jsxs(Command, { shouldFilter: false, className: "rounded-none border-0", children: [_jsx(CommandInput, { ref: inputRef, value: value, onValueChange: onValueChange, placeholder: inputPlaceholder, wrapperClassName: "border-b px-[var(--space-3)] py-[var(--space-2)]", iconClassName: "hidden", className: "h-control border-0 bg-transparent px-[var(--space-3)] py-[var(--space-0)] pr-[calc(var(--space-12)+var(--space-6))] text-sm shadow-none focus-visible:border-transparent focus-visible:ring-0", endAdornment: _jsxs(_Fragment, { children: [value ? (_jsx("button", { type: "button", "aria-label": "Clear search", onMouseDown: preserveInputFocus, onClick: clearSearch, className: "flex size-[var(--space-6)] items-center justify-center rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: _jsx(XIcon, { size: 14, "aria-hidden": "true" }) })) : null, _jsx("kbd", { className: "inline-flex h-[var(--space-5)] items-center rounded border border-border bg-muted px-[var(--space-1)] font-mono text-[11px] leading-none text-muted-foreground", "aria-hidden": "true", children: "Esc" })] }) }), allFilter || filters.length ? (_jsxs("div", { role: "group", "aria-label": "Search filters", "data-multiselectable": allowMultiSelect ? "true" : "false", className: "flex flex-wrap items-center gap-[var(--space-1)] border-b px-[var(--space-3)] py-[var(--space-2)]", children: [allFilter ? (_jsxs(FilterToggle, { selected: allFilter.selected, "aria-pressed": Boolean(allFilter.selected), onMouseDown: preserveInputFocus, onClick: () => selectFilter(allFilter), children: [allFilter.label, typeof allFilter.count === "number" ? (_jsx(CountBadge, { count: allFilter.count, hideWhenZero: true, size: "sm", variant: allFilter.selected ? "default" : "muted" })) : null] })) : null, filters.map((filter) => (_jsxs(FilterToggle, { selected: filter.selected, "aria-pressed": Boolean(filter.selected), onMouseDown: preserveInputFocus, onClick: () => selectFilter(filter), children: [filter.label, typeof filter.count === "number" ? (_jsx(CountBadge, { count: filter.count, hideWhenZero: true, size: "sm", variant: filter.selected ? "default" : "muted" })) : null] }, filter.value)))] })) : null, subFilters.length ? (_jsxs("div", { role: "group", "aria-label": "Search sub-filters", "data-multiselectable": allowMultiSelect ? "true" : "false", className: "flex flex-wrap items-center gap-[var(--space-1)] border-b bg-muted/30 px-[var(--space-3)] py-[var(--space-2)]", children: [_jsx(FilterIcon, { "data-slot": "command-search-sub-filter-icon", className: "mr-[var(--space-1)] shrink-0 text-muted-foreground", size: 14, "aria-hidden": "true" }), subFilters.map((filter) => (_jsxs(FilterToggle, { selected: filter.selected, "aria-pressed": Boolean(filter.selected), onMouseDown: preserveInputFocus, onClick: () => selectFilter(filter), children: [filter.label, typeof filter.count === "number" ? (_jsx(CountBadge, { count: filter.count, hideWhenZero: true, size: "sm", variant: filter.selected ? "default" : "muted" })) : null] }, filter.value)))] })) : null, _jsx(CommandList, { "aria-busy": loading || undefined, className: cn("p-[var(--space-1)]", resultsClassName), style: {
|
|
215
216
|
minHeight: resultsMinHeight,
|
|
216
217
|
maxHeight: resultsMaxHeight,
|
|
217
218
|
}, children: loading ? (_jsx(CommandSearchSkeleton, { rows: loadingRows })) : children !== undefined ? children : sections ? (visibleSections.length ? (visibleSections.map((section, index) => {
|
package/dist/date-input.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export interface DateRangeInputValue {
|
|
|
5
5
|
}
|
|
6
6
|
interface DateInputSharedProps {
|
|
7
7
|
label: React.ReactNode;
|
|
8
|
+
/** Visible heading for the calendar popover. */
|
|
9
|
+
popoverTitle?: React.ReactNode;
|
|
8
10
|
appearance?: "field" | "inline";
|
|
9
11
|
description?: React.ReactNode;
|
|
10
12
|
error?: React.ReactNode;
|
package/dist/date-input.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { format, parse } from "date-fns";
|
|
5
5
|
import { CalendarIcon } from "lucide-react";
|
|
@@ -7,8 +7,8 @@ import { XIcon } from "./animated-icons/x";
|
|
|
7
7
|
import { Calendar } from "./calendar";
|
|
8
8
|
import { inputControlClassName } from "./input-control";
|
|
9
9
|
import { Field, FieldDescription, FieldError, FieldLabel } from "./field";
|
|
10
|
-
import { PopoverContent, PopoverRoot, PopoverTrigger } from "./popover-primitive";
|
|
11
10
|
import { cn } from "./lib/utils";
|
|
11
|
+
import { Popover } from "./popover";
|
|
12
12
|
function createLocalDate(year, monthIndex, day) {
|
|
13
13
|
const date = new Date(year, monthIndex, day);
|
|
14
14
|
if (date.getFullYear() !== year ||
|
|
@@ -93,7 +93,7 @@ function formatDisplayRange(range) {
|
|
|
93
93
|
}
|
|
94
94
|
const DateInput = React.forwardRef((props, ref) => {
|
|
95
95
|
var _a, _b, _c;
|
|
96
|
-
const { label, appearance = "field", description, error, invalid = false, hideLabel = false, disabled = false, clearable = false, clearLabel, minDate, maxDate, className, triggerClassName, labelClassName, descriptionClassName, errorClassName, id, } = props;
|
|
96
|
+
const { label, popoverTitle, appearance = "field", description, error, invalid = false, hideLabel = false, disabled = false, clearable = false, clearLabel, minDate, maxDate, className, triggerClassName, labelClassName, descriptionClassName, errorClassName, id, } = props;
|
|
97
97
|
const [open, setOpen] = React.useState(false);
|
|
98
98
|
const [isSelectingRangeEnd, setIsSelectingRangeEnd] = React.useState(false);
|
|
99
99
|
const generatedId = React.useId();
|
|
@@ -164,11 +164,11 @@ const DateInput = React.forwardRef((props, ref) => {
|
|
|
164
164
|
}
|
|
165
165
|
};
|
|
166
166
|
const disabledDays = (date) => isDateOutsideBounds(date, minDate, maxDate);
|
|
167
|
-
const calendarContent = (_jsx(
|
|
167
|
+
const calendarContent = (_jsx(_Fragment, { children: props.mode === "range" ? (_jsx(Calendar, { mode: "range", selected: selectedRange, onSelect: handleRangeSelect, disabled: disabledDays, defaultMonth: (_b = selectedRange === null || selectedRange === void 0 ? void 0 : selectedRange.from) !== null && _b !== void 0 ? _b : selectedRange === null || selectedRange === void 0 ? void 0 : selectedRange.to, numberOfMonths: (_c = props.numberOfMonths) !== null && _c !== void 0 ? _c : 2, showOutsideDays: false, autoFocus: true })) : (_jsx(Calendar, { mode: "single", selected: selectedDate, onSelect: handleSelect, disabled: disabledDays, defaultMonth: selectedDate, autoFocus: true })) }));
|
|
168
168
|
if (appearance === "inline") {
|
|
169
|
-
return (_jsxs("span", { className: cn("inline-flex", className), "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [
|
|
169
|
+
return (_jsxs("span", { className: cn("inline-flex", className), "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(Popover, { open: open, onOpenChange: handleOpenChange, align: "start", title: popoverTitle, surface: popoverTitle ? "menu" : "panel", contentClassName: "w-auto p-0", trigger: _jsxs("button", { ref: ref, type: "button", id: inputId, disabled: disabled, "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || undefined, className: cn("inline-flex cursor-pointer items-baseline rounded-sm border-0 bg-transparent p-0 font-medium text-primary underline decoration-primary/40 underline-offset-4 transition-colors hover:decoration-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50", !displayValue && "text-muted-foreground decoration-border", isInvalid && "text-destructive decoration-destructive", triggerClassName), children: [_jsxs("span", { className: "sr-only", children: [label, ": "] }), _jsx("span", { children: displayValue !== null && displayValue !== void 0 ? displayValue : placeholder })] }), children: calendarContent }), showDescription ? (_jsx("span", { id: descriptionId, className: "sr-only", children: description })) : null, error ? (_jsx("span", { id: errorId, role: "alert", className: "sr-only", children: error })) : null] }));
|
|
170
170
|
}
|
|
171
|
-
return (_jsxs(Field, { className: className, "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: inputId, className: cn(hideLabel && "sr-only", labelClassName), children: label }), _jsxs(
|
|
171
|
+
return (_jsxs(Field, { className: className, "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: inputId, className: cn(hideLabel && "sr-only", labelClassName), children: label }), _jsxs("div", { className: "relative", children: [_jsx(Popover, { open: open, onOpenChange: handleOpenChange, align: "start", title: popoverTitle, surface: popoverTitle ? "menu" : "panel", contentClassName: "w-auto p-0", trigger: _jsx("button", { ref: ref, type: "button", id: inputId, disabled: disabled, "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || undefined, className: cn(inputControlClassName, "relative cursor-pointer pr-9 text-left", showClearButton && "pr-16", !displayValue && "text-muted-foreground", isInvalid && "border-destructive focus-visible:ring-destructive", triggerClassName), children: _jsx("span", { className: "block truncate", children: displayValue !== null && displayValue !== void 0 ? displayValue : placeholder }) }), children: calendarContent }), _jsxs("span", { "aria-hidden": showClearButton ? undefined : true, className: "pointer-events-none absolute inset-y-0 right-3 flex items-center gap-2 text-muted-foreground", children: [showClearButton ? (_jsx("button", { type: "button", "aria-label": resolvedClearLabel, onClick: handleClear, className: "pointer-events-auto inline-flex h-4 w-4 cursor-pointer items-center justify-center rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:cursor-not-allowed", children: _jsx(XIcon, { size: 14, className: "flex items-center justify-center" }) })) : null, _jsx(CalendarIcon, { "aria-hidden": "true", className: "h-4 w-4 shrink-0", strokeWidth: 2 })] })] }), showDescription ? (_jsx(FieldDescription, { id: descriptionId, className: descriptionClassName, children: description })) : null, _jsx(FieldError, { id: errorId, className: errorClassName, children: error })] }));
|
|
172
172
|
});
|
|
173
173
|
DateInput.displayName = "DateInput";
|
|
174
174
|
export { DateInput };
|
package/dist/filter-popover.d.ts
CHANGED
|
@@ -25,14 +25,18 @@ export interface FilterPopoverProps extends Pick<PopoverProps, "align" | "defaul
|
|
|
25
25
|
selectedCount?: number;
|
|
26
26
|
trigger?: React.ReactElement;
|
|
27
27
|
triggerLabel?: React.ReactNode;
|
|
28
|
+
title?: React.ReactNode;
|
|
28
29
|
groupListLabel?: React.ReactNode;
|
|
29
30
|
resetLabel?: React.ReactNode;
|
|
30
31
|
emptyLabel?: React.ReactNode;
|
|
32
|
+
ariaLabel?: string;
|
|
31
33
|
onReset?: () => void;
|
|
32
34
|
contentClassName?: string;
|
|
33
35
|
viewClassName?: string;
|
|
34
36
|
maxHeight?: string;
|
|
37
|
+
/** Opens the only group directly, without the grouped-filter navigation screen. */
|
|
38
|
+
directSingleGroup?: boolean;
|
|
35
39
|
}
|
|
36
40
|
declare function getFilterSelectionLabel(count: number): string;
|
|
37
|
-
declare function FilterPopover({ align, contentClassName, defaultOpen, emptyLabel, groupListLabel, groups, maxHeight, modal, onOpenChange, onReset, open, resetLabel, selectedCount, side, trigger, triggerLabel, viewClassName, }: FilterPopoverProps): import("react/jsx-runtime").JSX.Element;
|
|
41
|
+
declare function FilterPopover({ align, ariaLabel, contentClassName, defaultOpen, directSingleGroup, emptyLabel, groupListLabel, groups, maxHeight, modal, onOpenChange, onReset, open, resetLabel, selectedCount, side, trigger, triggerLabel, title, viewClassName, }: FilterPopoverProps): import("react/jsx-runtime").JSX.Element;
|
|
38
42
|
export { FilterPopover, getFilterSelectionLabel };
|
package/dist/filter-popover.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { ChevronLeft, ChevronRight
|
|
4
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
5
|
+
import { FilterIcon } from "./animated-icons";
|
|
5
6
|
import { Button } from "./button";
|
|
6
7
|
import { CountBadge } from "./count-badge";
|
|
7
8
|
import { cn } from "./lib/utils";
|
|
8
9
|
import { Popover } from "./popover";
|
|
9
|
-
import { PopoverMenuFooter, PopoverMenuFooterAction, PopoverMenuGroup, PopoverMenuGroupLabel, PopoverMenuItem, PopoverMenuScrollArea, PopoverMenuSearch, PopoverMenuSection, PopoverMenuView, } from "./popover-menu";
|
|
10
|
+
import { PopoverMenuFooter, PopoverMenuFooterAction, PopoverMenuGroup, PopoverMenuGroupLabel, PopoverMenuItem, PopoverMenuScrollArea, PopoverMenuSearch, PopoverMenuSection, PopoverMenuView, popoverMenuItemFocusClassName, } from "./popover-menu";
|
|
10
11
|
import { Typography } from "./typography";
|
|
11
12
|
function getText(value) {
|
|
12
13
|
if (typeof value === "string" || typeof value === "number") {
|
|
@@ -32,12 +33,13 @@ function getFilterSelectionLabel(count) {
|
|
|
32
33
|
}
|
|
33
34
|
return count === 1 ? "1 selected" : `${count} selected`;
|
|
34
35
|
}
|
|
35
|
-
function FilterPopover({ align = "start", contentClassName, defaultOpen, emptyLabel = "No options found.", groupListLabel = "Filter by", groups, maxHeight = "max-h-80", modal, onOpenChange, onReset, open, resetLabel = "Reset filters", selectedCount, side, trigger, triggerLabel = "Filters", viewClassName, }) {
|
|
36
|
-
var _a, _b, _c, _d, _e;
|
|
36
|
+
function FilterPopover({ align = "start", ariaLabel, contentClassName, defaultOpen, directSingleGroup = false, emptyLabel = "No options found.", groupListLabel = "Filter by", groups, maxHeight = "max-h-80", modal, onOpenChange, onReset, open, resetLabel = "Reset filters", selectedCount, side, trigger, triggerLabel = "Filters", title, viewClassName, }) {
|
|
37
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
37
38
|
const [activeGroupId, setActiveGroupId] = React.useState(null);
|
|
38
39
|
const [transitionDirection, setTransitionDirection] = React.useState("forward");
|
|
39
40
|
const [query, setQuery] = React.useState("");
|
|
40
|
-
const
|
|
41
|
+
const directGroup = directSingleGroup && groups.length === 1 ? groups[0] : null;
|
|
42
|
+
const activeGroup = (_a = directGroup !== null && directGroup !== void 0 ? directGroup : groups.find((group) => group.id === activeGroupId)) !== null && _a !== void 0 ? _a : null;
|
|
41
43
|
const resolvedSelectedCount = selectedCount !== null && selectedCount !== void 0 ? selectedCount : groups.reduce((total, group) => total + getGroupSelectedCount(group), 0);
|
|
42
44
|
const normalizedQuery = query.trim().toLowerCase();
|
|
43
45
|
const filteredOptions = activeGroup && normalizedQuery
|
|
@@ -62,8 +64,15 @@ function FilterPopover({ align = "start", contentClassName, defaultOpen, emptyLa
|
|
|
62
64
|
setTransitionDirection("backward");
|
|
63
65
|
setActiveGroupId(null);
|
|
64
66
|
};
|
|
65
|
-
const defaultTrigger = (_jsxs(Button, { type: "button", variant: "outline", children: [_jsx(
|
|
66
|
-
|
|
67
|
+
const defaultTrigger = (_jsxs(Button, { type: "button", variant: "outline", children: [_jsx(FilterIcon, { size: 16 }), triggerLabel, resolvedSelectedCount > 0 ? (_jsx(CountBadge, { count: resolvedSelectedCount, size: "sm" })) : null] }));
|
|
68
|
+
const handleOpenChange = (nextOpen) => {
|
|
69
|
+
if (!nextOpen && directGroup) {
|
|
70
|
+
setQuery("");
|
|
71
|
+
}
|
|
72
|
+
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(nextOpen);
|
|
73
|
+
};
|
|
74
|
+
const resolvedTitle = (_c = title !== null && title !== void 0 ? title : directGroup === null || directGroup === void 0 ? void 0 : directGroup.label) !== null && _c !== void 0 ? _c : (!activeGroup ? triggerLabel : undefined);
|
|
75
|
+
return (_jsxs(Popover, { align: align, contentClassName: cn("w-[360px]", contentClassName), defaultOpen: defaultOpen, modal: modal, onOpenChange: handleOpenChange, open: open, side: side, surface: "menu", trigger: trigger !== null && trigger !== void 0 ? trigger : defaultTrigger, title: resolvedTitle, "aria-label": (_e = (_d = ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : getText(resolvedTitle)) !== null && _d !== void 0 ? _d : getText(triggerLabel)) !== null && _e !== void 0 ? _e : "Filters", children: [_jsx(PopoverMenuView, { className: cn("-mx-1 -mt-1 px-1 pt-1", viewClassName), direction: transitionDirection, viewKey: activeGroup ? `group-${activeGroup.id}` : "filter-groups", children: activeGroup ? (_jsxs(_Fragment, { children: [!directGroup ? (_jsx("div", { className: "-mx-1 -mt-1 border-b p-1", children: _jsxs(Button, { type: "button", variant: "ghost", size: "sm", className: cn("w-full justify-start", popoverMenuItemFocusClassName), onClick: showFilterGroups, children: [_jsx(ChevronLeft, { className: "h-4 w-4" }), activeGroup.label] }) })) : null, activeGroup.searchable ? (_jsx(PopoverMenuSearch, { value: query, onChange: (event) => setQuery(event.target.value), placeholder: (_f = activeGroup.searchPlaceholder) !== null && _f !== void 0 ? _f : `Search ${getGroupLabel(activeGroup).toLowerCase()}...`, "aria-label": (_g = activeGroup.searchPlaceholder) !== null && _g !== void 0 ? _g : `Search ${getGroupLabel(activeGroup).toLowerCase()}` })) : null, _jsx(PopoverMenuScrollArea, { maxHeight: maxHeight, children: _jsx(PopoverMenuSection, { children: filteredOptions.length ? (filteredOptions.map((option) => (_jsx(PopoverMenuItem, { active: option.selected, "aria-label": option.ariaLabel, "aria-pressed": option.selected, disabled: option.disabled, onClick: option.onSelect, children: option.label }, option.id)))) : (_jsx(Typography, { as: "p", variant: "caption", className: "px-2 py-2", children: (_h = activeGroup.emptyLabel) !== null && _h !== void 0 ? _h : emptyLabel })) }) })] })) : (_jsx(PopoverMenuScrollArea, { maxHeight: maxHeight, children: _jsxs(PopoverMenuGroup, { children: [!resolvedTitle ? (_jsx(PopoverMenuGroupLabel, { children: groupListLabel })) : null, _jsx(PopoverMenuSection, { children: groups.map((group) => {
|
|
67
76
|
var _a;
|
|
68
77
|
const groupSelectedCount = getGroupSelectedCount(group);
|
|
69
78
|
const selectionLabel = (_a = group.selectionLabel) !== null && _a !== void 0 ? _a : getFilterSelectionLabel(groupSelectedCount);
|
package/dist/filter-toggle.js
CHANGED
|
@@ -9,7 +9,7 @@ const filterToggleVariants = cva("inline-flex shrink-0 cursor-pointer select-non
|
|
|
9
9
|
muted: "",
|
|
10
10
|
},
|
|
11
11
|
selected: {
|
|
12
|
-
true: "border-transparent bg-primary/10 text-primary",
|
|
12
|
+
true: "border-transparent bg-primary/10 text-primary hover:bg-primary/20 focus-visible:bg-primary/20",
|
|
13
13
|
false: "border-transparent bg-muted text-muted-foreground hover:bg-muted/80",
|
|
14
14
|
},
|
|
15
15
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -82,8 +82,8 @@ export { TaskModeShell, TASK_MODE_SHELL_ANIMATION_DURATION_MS, } from "./task-mo
|
|
|
82
82
|
export type { TaskModeShellProps } from "./task-mode-shell";
|
|
83
83
|
export { useFileUpload } from "./use-file-upload";
|
|
84
84
|
export type { FileUploadProgress, FileUploadResult, FileUploadStatus, UseFileUploadOptions, UseFileUploadReturn, } from "./use-file-upload";
|
|
85
|
-
export { Popover } from "./popover";
|
|
86
|
-
export type { PopoverProps } from "./popover";
|
|
85
|
+
export { Popover, PopoverTitle } from "./popover";
|
|
86
|
+
export type { PopoverProps, PopoverTitleProps } from "./popover";
|
|
87
87
|
export { PopoverMenuContent, PopoverMenuCheckboxItem, PopoverMenuHeader, PopoverMenuSection, PopoverMenuItem, PopoverMenuDivider, PopoverMenuFooter, PopoverMenuFooterAction, PopoverMenuFooterMeta, PopoverMenuGroup, PopoverMenuGroupLabel, PopoverMenuSearch, PopoverMenuScrollArea, PopoverMenuView, } from "./popover-menu";
|
|
88
88
|
export type { PopoverMenuContentProps, PopoverMenuCheckboxItemProps, PopoverMenuHeaderProps, PopoverMenuSectionProps, PopoverMenuItemProps, PopoverMenuDividerProps, PopoverMenuFooterActionProps, PopoverMenuFooterMetaProps, PopoverMenuFooterProps, PopoverMenuGroupProps, PopoverMenuGroupLabelProps, PopoverMenuSearchProps, PopoverMenuScrollAreaProps, PopoverMenuViewProps, } from "./popover-menu";
|
|
89
89
|
export { SearchInput } from "./search-input";
|
|
@@ -133,6 +133,7 @@ export { useTableSort } from "./use-table-sort";
|
|
|
133
133
|
export type { UseTableSortOptions, UseTableSortReturn, } from "./use-table-sort";
|
|
134
134
|
export { Table, TableRoot, TableContainer, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, TableRowSkeleton, TABLE_SKELETON_PRESETS, } from "./table";
|
|
135
135
|
export type { TableColumn, TableColumnWidths, TableProps, TableResizeMode, TableRowExpansionProps, TableRowTone, TableSortDirection, TableSortingProps, TableSkeletonConfig, TableRowSkeletonProps, } from "./table";
|
|
136
|
+
export type { TableColumnFilter, TableColumnFilterOption, TableDateColumnFilter, TableOptionsColumnFilter, TableTextColumnFilter, } from "./table-column-filter";
|
|
136
137
|
export { Tabs, TabsRoot, TabWithDropdown, type TabsItem, type TabsItemLabel, type TabsItemLabelProps, type TabsProps, TabsList, TabsTrigger, TabsContent, type TabWithDropdownProps, useTabsContext, type TabDropdownSection, } from "./tabs";
|
|
137
138
|
export { Tooltip, TooltipRoot, TooltipTrigger, TooltipContent, TooltipProvider, type TooltipProps, } from "./tooltip";
|
|
138
139
|
export { Toaster, toast } from "./toaster";
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ export { RadioGroup } from "./radio-group";
|
|
|
42
42
|
export { ScrollHint } from "./scroll-hint";
|
|
43
43
|
export { TaskModeShell, TASK_MODE_SHELL_ANIMATION_DURATION_MS, } from "./task-mode-shell";
|
|
44
44
|
export { useFileUpload } from "./use-file-upload";
|
|
45
|
-
export { Popover } from "./popover";
|
|
45
|
+
export { Popover, PopoverTitle } from "./popover";
|
|
46
46
|
export { PopoverMenuContent, PopoverMenuCheckboxItem, PopoverMenuHeader, PopoverMenuSection, PopoverMenuItem, PopoverMenuDivider, PopoverMenuFooter, PopoverMenuFooterAction, PopoverMenuFooterMeta, PopoverMenuGroup, PopoverMenuGroupLabel, PopoverMenuSearch, PopoverMenuScrollArea, PopoverMenuView, } from "./popover-menu";
|
|
47
47
|
export { SearchInput } from "./search-input";
|
|
48
48
|
export { SectionNav } from "./section-nav";
|
package/dist/multi-select.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { ChevronDownIcon } from "./animated-icons/chevron-down";
|
|
4
5
|
import { XIcon } from "./animated-icons/x";
|
|
5
6
|
import { Badge } from "./badge";
|
|
6
7
|
import { Field, FieldDescription, FieldError, FieldLabel } from "./field";
|
|
@@ -22,6 +23,8 @@ function MultiSelect({ label, options, value, onValueChange, placeholder = "Sele
|
|
|
22
23
|
const containerRef = React.useRef(null);
|
|
23
24
|
const triggerRef = React.useRef(null);
|
|
24
25
|
const searchInputRef = React.useRef(null);
|
|
26
|
+
const listboxRef = React.useRef(null);
|
|
27
|
+
const openFocusTargetRef = React.useRef(searchable ? "search" : "first");
|
|
25
28
|
const normalizedQuery = searchQuery.trim().toLowerCase();
|
|
26
29
|
const filteredOptions = searchable && normalizedQuery
|
|
27
30
|
? options.filter((opt) => {
|
|
@@ -33,6 +36,28 @@ function MultiSelect({ label, options, value, onValueChange, placeholder = "Sele
|
|
|
33
36
|
const selectedLabels = value
|
|
34
37
|
.map((v) => options.find((opt) => opt.value === v))
|
|
35
38
|
.filter(Boolean);
|
|
39
|
+
const getEnabledOptionElements = React.useCallback(() => {
|
|
40
|
+
var _a, _b;
|
|
41
|
+
return Array.from((_b = (_a = listboxRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll('[role="option"]:not(:disabled)')) !== null && _b !== void 0 ? _b : []);
|
|
42
|
+
}, []);
|
|
43
|
+
const focusBoundaryOption = React.useCallback((boundary) => {
|
|
44
|
+
const enabledOptions = getEnabledOptionElements();
|
|
45
|
+
const option = boundary === "first"
|
|
46
|
+
? enabledOptions[0]
|
|
47
|
+
: enabledOptions[enabledOptions.length - 1];
|
|
48
|
+
option === null || option === void 0 ? void 0 : option.focus({ preventScroll: true });
|
|
49
|
+
}, [getEnabledOptionElements]);
|
|
50
|
+
const openWithFocus = React.useCallback((target) => {
|
|
51
|
+
openFocusTargetRef.current = target;
|
|
52
|
+
setOpen(true);
|
|
53
|
+
}, []);
|
|
54
|
+
const closeAndFocusTrigger = React.useCallback(() => {
|
|
55
|
+
setOpen(false);
|
|
56
|
+
window.requestAnimationFrame(() => {
|
|
57
|
+
var _a;
|
|
58
|
+
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.focus({ preventScroll: true });
|
|
59
|
+
});
|
|
60
|
+
}, []);
|
|
36
61
|
const toggleOption = React.useCallback((optionValue) => {
|
|
37
62
|
const next = value.includes(optionValue)
|
|
38
63
|
? value.filter((v) => v !== optionValue)
|
|
@@ -47,14 +72,16 @@ function MultiSelect({ label, options, value, onValueChange, placeholder = "Sele
|
|
|
47
72
|
setSearchQuery("");
|
|
48
73
|
return;
|
|
49
74
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
75
|
+
const frame = window.requestAnimationFrame(() => {
|
|
76
|
+
var _a;
|
|
77
|
+
if (openFocusTargetRef.current === "search" && searchable) {
|
|
53
78
|
(_a = searchInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
focusBoundaryOption(openFocusTargetRef.current === "last" ? "last" : "first");
|
|
82
|
+
});
|
|
83
|
+
return () => window.cancelAnimationFrame(frame);
|
|
84
|
+
}, [focusBoundaryOption, open, searchable]);
|
|
58
85
|
React.useEffect(() => {
|
|
59
86
|
const handleClickOutside = (event) => {
|
|
60
87
|
if (containerRef.current &&
|
|
@@ -72,33 +99,91 @@ function MultiSelect({ label, options, value, onValueChange, placeholder = "Sele
|
|
|
72
99
|
return;
|
|
73
100
|
if (event.key === "Enter" || event.key === " ") {
|
|
74
101
|
event.preventDefault();
|
|
75
|
-
|
|
102
|
+
if (open) {
|
|
103
|
+
setOpen(false);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
openWithFocus(searchable ? "search" : "first");
|
|
107
|
+
}
|
|
76
108
|
}
|
|
77
109
|
else if (event.key === "ArrowDown") {
|
|
78
110
|
event.preventDefault();
|
|
79
|
-
|
|
111
|
+
openWithFocus("first");
|
|
112
|
+
}
|
|
113
|
+
else if (event.key === "ArrowUp") {
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
openWithFocus("last");
|
|
80
116
|
}
|
|
81
117
|
else if (event.key === "Escape") {
|
|
82
|
-
|
|
118
|
+
event.preventDefault();
|
|
119
|
+
closeAndFocusTrigger();
|
|
120
|
+
}
|
|
121
|
+
}, [closeAndFocusTrigger, disabled, open, openWithFocus, searchable]);
|
|
122
|
+
const handleSearchKeyDown = React.useCallback((event) => {
|
|
123
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
focusBoundaryOption(event.key === "ArrowDown" ? "first" : "last");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (event.key === "Escape") {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
closeAndFocusTrigger();
|
|
131
|
+
}
|
|
132
|
+
}, [closeAndFocusTrigger, focusBoundaryOption]);
|
|
133
|
+
const handleListboxKeyDown = React.useCallback((event) => {
|
|
134
|
+
var _a, _b;
|
|
135
|
+
const enabledOptions = getEnabledOptionElements();
|
|
136
|
+
const currentIndex = enabledOptions.findIndex((option) => option === document.activeElement);
|
|
137
|
+
if (event.key === "Escape") {
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
closeAndFocusTrigger();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (event.key === "Home" || event.key === "End") {
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
focusBoundaryOption(event.key === "Home" ? "first" : "last");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (event.key !== "ArrowDown" && event.key !== "ArrowUp") {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
if (event.key === "ArrowUp" && currentIndex <= 0 && searchable) {
|
|
152
|
+
(_a = searchInputRef.current) === null || _a === void 0 ? void 0 : _a.focus({ preventScroll: true });
|
|
153
|
+
return;
|
|
83
154
|
}
|
|
84
|
-
|
|
155
|
+
const offset = event.key === "ArrowDown" ? 1 : -1;
|
|
156
|
+
const nextIndex = Math.min(Math.max(currentIndex + offset, 0), enabledOptions.length - 1);
|
|
157
|
+
(_b = enabledOptions[nextIndex]) === null || _b === void 0 ? void 0 : _b.focus({ preventScroll: true });
|
|
158
|
+
}, [
|
|
159
|
+
closeAndFocusTrigger,
|
|
160
|
+
focusBoundaryOption,
|
|
161
|
+
getEnabledOptionElements,
|
|
162
|
+
searchable,
|
|
163
|
+
]);
|
|
85
164
|
return (_jsxs(Field, { className: className, "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { id: labelId, htmlFor: selectId, className: cn(hideLabel && "sr-only", labelClassName), onClick: (event) => {
|
|
86
165
|
var _a;
|
|
87
166
|
event.preventDefault();
|
|
88
167
|
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
89
|
-
}, children: label }), _jsxs("div", { ref: containerRef, className: "relative", children: [
|
|
168
|
+
}, children: label }), _jsxs("div", { ref: containerRef, className: "relative", children: [_jsxs("div", { ref: triggerRef, role: "combobox", id: selectId, tabIndex: disabled ? -1 : 0, onClick: () => {
|
|
90
169
|
if (!disabled) {
|
|
91
|
-
|
|
170
|
+
if (open) {
|
|
171
|
+
setOpen(false);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
openWithFocus(searchable ? "search" : "first");
|
|
175
|
+
}
|
|
92
176
|
}
|
|
93
|
-
}, onKeyDown: handleTriggerKeyDown, "aria-expanded": open, "aria-controls": listboxId, "aria-haspopup": "listbox", "aria-invalid": isInvalid ? "true" : undefined, "aria-disabled": disabled ? "true" : undefined, "aria-labelledby": labelId, "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || undefined, className: cn("flex h-control w-full cursor-pointer items-center gap-1.5 overflow-hidden rounded-control border border-input bg-background px-2.5 py-1.5 text-sm ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", isInvalid ? "border-destructive" : "border-input", disabled && "cursor-not-allowed opacity-50", triggerClassName), children: selectedLabels.length > 0 ? (_jsxs(_Fragment, { children: [selectedLabels.slice(0, maxVisibleTags).map((opt) => (_jsxs(Badge, { variant: "secondary", className: "max-w-[10rem] shrink-0 justify-start gap-1 overflow-visible text-sm font-medium", children: [_jsx("span", { className: "min-w-0 truncate", children: opt.label }), _jsx("button", { type: "button", className: "inline-flex cursor-pointer items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", onClick: (e) => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
177
|
+
}, onKeyDown: handleTriggerKeyDown, "aria-expanded": open, "aria-controls": listboxId, "aria-haspopup": "listbox", "aria-invalid": isInvalid ? "true" : undefined, "aria-disabled": disabled ? "true" : undefined, "aria-labelledby": labelId, "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || undefined, className: cn("relative flex h-control w-full cursor-pointer items-center gap-1.5 overflow-hidden rounded-control border border-input bg-background px-2.5 py-1.5 pr-8 text-sm ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", isInvalid ? "border-destructive" : "border-input", disabled && "cursor-not-allowed opacity-50", triggerClassName), children: [selectedLabels.length > 0 ? (_jsxs(_Fragment, { children: [selectedLabels.slice(0, maxVisibleTags).map((opt) => (_jsxs(Badge, { variant: "secondary", className: "max-w-[10rem] shrink-0 justify-start gap-1 overflow-visible text-sm font-medium", children: [_jsx("span", { className: "min-w-0 truncate", children: opt.label }), _jsx("button", { type: "button", className: "inline-flex cursor-pointer items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", onClick: (e) => {
|
|
178
|
+
e.stopPropagation();
|
|
179
|
+
removeOption(opt.value);
|
|
180
|
+
}, "aria-label": `Remove ${opt.label}`, children: _jsx(XIcon, { size: 12 }) })] }, opt.value))), selectedLabels.length > maxVisibleTags ? (_jsxs(Badge, { variant: "secondary", className: "shrink-0 text-sm font-medium", title: selectedLabels
|
|
181
|
+
.slice(maxVisibleTags)
|
|
182
|
+
.map((o) => o.label)
|
|
183
|
+
.join(", "), children: ["+", selectedLabels.length - maxVisibleTags] })) : null] })) : (_jsx("span", { className: "text-muted-foreground", children: placeholder })), _jsx(ChevronDownIcon, { "aria-hidden": "true", size: 16, className: cn("pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 shrink-0 text-muted-foreground transition-transform duration-150", open && "rotate-180") })] }), open ? (_jsxs("div", { className: "absolute z-50 mt-1 w-full rounded-overlay border bg-popover shadow-md", children: [searchable ? (_jsx("div", { className: "border-b p-1", children: _jsx(InputControl, { ref: searchInputRef, value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: searchPlaceholder, className: "h-control border-0 bg-transparent px-2 py-0 text-sm shadow-none", onKeyDown: handleSearchKeyDown }) })) : null, _jsx("div", { ref: listboxRef, id: listboxId, role: "listbox", "aria-label": typeof label === "string" ? label : "Options", "aria-multiselectable": "true", className: "max-h-64 overflow-y-auto p-1", onKeyDown: handleListboxKeyDown, children: filteredOptions.length > 0 ? (filteredOptions.map((option) => {
|
|
100
184
|
const isSelected = value.includes(option.value);
|
|
101
|
-
return (_jsxs("button", { type: "button", role: "option", "aria-selected": isSelected, disabled: option.disabled, onClick: () => toggleOption(option.value), className: cn("flex min-h-8 w-full cursor-pointer items-center gap-1.5 rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50", isSelected &&
|
|
185
|
+
return (_jsxs("button", { type: "button", role: "option", "aria-selected": isSelected, disabled: option.disabled, tabIndex: -1, onClick: () => toggleOption(option.value), className: cn("flex min-h-8 w-full cursor-pointer items-center gap-1.5 rounded-sm px-2 py-1.5 text-left text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50", isSelected &&
|
|
186
|
+
"bg-primary/10 text-primary hover:bg-primary/20 hover:text-primary focus:bg-primary/20 focus:text-primary"), children: [_jsx(Checkbox, { checked: isSelected, disabled: option.disabled, tabIndex: -1, readOnly: true, "aria-hidden": "true", className: "mt-0.5" }), _jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { children: option.label }), option.description ? (_jsx(Typography, { as: "span", variant: "caption", children: option.description })) : null] })] }, option.value));
|
|
102
187
|
})) : (_jsx("div", { className: "px-2.5 py-1.5 text-center text-sm text-muted-foreground", children: "No options found." })) })] })) : null] }), showDescription ? (_jsx(FieldDescription, { id: descriptionId, className: descriptionClassName, children: description })) : null, _jsx(FieldError, { id: errorId, className: errorClassName, children: error })] }));
|
|
103
188
|
}
|
|
104
189
|
export { MultiSelect };
|
package/dist/popover-menu.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type ButtonProps } from "./button";
|
|
3
3
|
import { type CheckboxProps } from "./checkbox";
|
|
4
|
-
import {
|
|
4
|
+
import { type InputProps } from "./input";
|
|
5
5
|
import { PopoverContent } from "./popover-primitive";
|
|
6
6
|
import { Typography } from "./typography";
|
|
7
|
+
declare const popoverMenuItemFocusClassName = "rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-0";
|
|
8
|
+
declare const popoverMenuItemFocusWithinClassName = "rounded-sm focus-within:ring-2 focus-within:ring-inset focus-within:ring-ring";
|
|
7
9
|
export interface PopoverMenuHeaderProps extends Omit<React.ComponentPropsWithoutRef<typeof Typography>, "as" | "asChild" | "variant" | "color"> {
|
|
8
10
|
}
|
|
9
11
|
export interface PopoverMenuContentProps extends React.ComponentPropsWithoutRef<typeof PopoverContent> {
|
|
@@ -41,10 +43,12 @@ declare function PopoverMenuGroup({ className, children, ...props }: PopoverMenu
|
|
|
41
43
|
export interface PopoverMenuGroupLabelProps extends Omit<React.ComponentPropsWithoutRef<typeof Typography>, "as" | "asChild" | "variant" | "color"> {
|
|
42
44
|
}
|
|
43
45
|
declare function PopoverMenuGroupLabel({ className, children, ...props }: PopoverMenuGroupLabelProps): import("react/jsx-runtime").JSX.Element;
|
|
44
|
-
export interface PopoverMenuSearchProps extends
|
|
46
|
+
export interface PopoverMenuSearchProps extends Omit<InputProps, "className" | "hideLabel" | "inputClassName" | "label"> {
|
|
47
|
+
label?: React.ReactNode;
|
|
48
|
+
className?: string;
|
|
45
49
|
containerClassName?: string;
|
|
46
50
|
}
|
|
47
|
-
declare const PopoverMenuSearch: React.ForwardRefExoticComponent<PopoverMenuSearchProps & React.RefAttributes<HTMLInputElement>>;
|
|
51
|
+
declare const PopoverMenuSearch: React.ForwardRefExoticComponent<Omit<PopoverMenuSearchProps, "ref"> & React.RefAttributes<HTMLInputElement>>;
|
|
48
52
|
export interface PopoverMenuScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
49
53
|
maxHeight?: string;
|
|
50
54
|
}
|
|
@@ -54,4 +58,4 @@ export interface PopoverMenuViewProps extends React.HTMLAttributes<HTMLDivElemen
|
|
|
54
58
|
viewKey?: React.Key;
|
|
55
59
|
}
|
|
56
60
|
declare function PopoverMenuView({ children, className, direction, viewKey, ...props }: PopoverMenuViewProps): import("react/jsx-runtime").JSX.Element;
|
|
57
|
-
export { PopoverMenuContent, PopoverMenuHeader, PopoverMenuSection, PopoverMenuItem, PopoverMenuCheckboxItem, PopoverMenuDivider, PopoverMenuFooter, PopoverMenuFooterAction, PopoverMenuFooterMeta, PopoverMenuGroup, PopoverMenuGroupLabel, PopoverMenuSearch, PopoverMenuScrollArea, PopoverMenuView, };
|
|
61
|
+
export { PopoverMenuContent, PopoverMenuHeader, PopoverMenuSection, PopoverMenuItem, PopoverMenuCheckboxItem, PopoverMenuDivider, PopoverMenuFooter, PopoverMenuFooterAction, PopoverMenuFooterMeta, PopoverMenuGroup, PopoverMenuGroupLabel, PopoverMenuSearch, PopoverMenuScrollArea, PopoverMenuView, popoverMenuItemFocusClassName, popoverMenuItemFocusWithinClassName, };
|
package/dist/popover-menu.js
CHANGED
|
@@ -4,12 +4,14 @@ import * as React from "react";
|
|
|
4
4
|
import { Check } from "lucide-react";
|
|
5
5
|
import { Button } from "./button";
|
|
6
6
|
import { Checkbox } from "./checkbox";
|
|
7
|
+
import { Input } from "./input";
|
|
7
8
|
import { cn } from "./lib/utils";
|
|
8
|
-
import { InputControl } from "./input-control";
|
|
9
9
|
import { PopoverContent } from "./popover-primitive";
|
|
10
10
|
import { Typography } from "./typography";
|
|
11
11
|
const POPOVER_MENU_VIEW_TRANSITION_MS = 300;
|
|
12
12
|
const useIsomorphicLayoutEffect = typeof window === "undefined" ? React.useEffect : React.useLayoutEffect;
|
|
13
|
+
const popoverMenuItemFocusClassName = "rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring focus-visible:ring-offset-0";
|
|
14
|
+
const popoverMenuItemFocusWithinClassName = "rounded-sm focus-within:ring-2 focus-within:ring-inset focus-within:ring-ring";
|
|
13
15
|
const PopoverMenuContent = React.forwardRef(({ className, ...props }, ref) => (_jsx(PopoverContent, { ref: ref, className: cn("overflow-hidden p-1", className), ...props })));
|
|
14
16
|
PopoverMenuContent.displayName = "PopoverMenuContent";
|
|
15
17
|
function PopoverMenuHeader({ className, children, ...props }) {
|
|
@@ -19,11 +21,12 @@ function PopoverMenuSection({ className, children, ...props }) {
|
|
|
19
21
|
return (_jsx("div", { className: cn("space-y-0", className), ...props, children: children }));
|
|
20
22
|
}
|
|
21
23
|
function PopoverMenuItem({ className, children, active, type = "button", ...props }) {
|
|
22
|
-
return (_jsxs(Button, { type: type, variant: "ghost", className: cn("h-auto min-h-8 w-full justify-start gap-2
|
|
23
|
-
"bg-
|
|
24
|
+
return (_jsxs(Button, { type: type, variant: "ghost", "data-slot": "popover-menu-item", className: cn("h-auto min-h-8 w-full justify-start gap-2 px-2 py-2 text-left font-normal whitespace-normal", popoverMenuItemFocusClassName, active &&
|
|
25
|
+
"bg-primary/10 text-primary hover:bg-primary/20 hover:text-primary focus-visible:bg-primary/20 focus-visible:text-primary", className), ...props, children: [children, active ? _jsx(Check, { "aria-hidden": "true", className: "ml-auto h-4 w-4 shrink-0" }) : null] }));
|
|
24
26
|
}
|
|
25
27
|
function PopoverMenuCheckboxItem({ className, children, checked = false, description, disabled, onChange, readOnly, ...props }) {
|
|
26
|
-
return (_jsxs("label", { className: cn("flex h-auto min-h-8 w-full cursor-pointer items-start gap-1.5 rounded-sm px-2 py-2 text-left text-popover-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-within:bg-accent focus-within:text-accent-foreground",
|
|
28
|
+
return (_jsxs("label", { className: cn("flex h-auto min-h-8 w-full cursor-pointer items-start gap-1.5 rounded-sm px-2 py-2 text-left text-popover-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-within:bg-accent focus-within:text-accent-foreground", popoverMenuItemFocusWithinClassName, checked &&
|
|
29
|
+
"bg-primary/10 text-primary hover:bg-primary/20 hover:text-primary focus-within:bg-primary/20 focus-within:text-primary", disabled &&
|
|
27
30
|
"cursor-not-allowed opacity-50 hover:bg-transparent hover:text-inherit", className), children: [_jsx(Checkbox, { ...props, checked: checked, disabled: disabled, readOnly: readOnly !== null && readOnly !== void 0 ? readOnly : !onChange, onChange: onChange, className: "mt-0.5" }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx(Typography, { as: "span", variant: "bodySm", className: "block", children: children }), description ? (_jsx(Typography, { as: "span", variant: "caption", className: "mt-0.5 block", children: description })) : null] })] }));
|
|
28
31
|
}
|
|
29
32
|
function PopoverMenuDivider({ className, ...props }) {
|
|
@@ -33,7 +36,7 @@ function PopoverMenuFooter({ className, children, inset = "menu", ...props }) {
|
|
|
33
36
|
return (_jsx("div", { className: cn("flex items-center gap-2 border-t p-1.5", inset === "menu" && "-mx-1 -mb-1", inset === "flush" && "m-0", className), ...props, children: children }));
|
|
34
37
|
}
|
|
35
38
|
function PopoverMenuFooterAction({ className, type = "button", ...props }) {
|
|
36
|
-
return (_jsx(Button, { type: type, variant: "ghost", size: "sm", className: cn("h-auto min-h-8 w-full justify-start px-2 py-1.5 text-left whitespace-normal
|
|
39
|
+
return (_jsx(Button, { type: type, variant: "ghost", size: "sm", className: cn("h-auto min-h-8 w-full justify-start px-2 py-1.5 text-left whitespace-normal", popoverMenuItemFocusClassName, className), ...props }));
|
|
37
40
|
}
|
|
38
41
|
function PopoverMenuFooterMeta({ className, children, ...props }) {
|
|
39
42
|
return (_jsx(Typography, { as: "span", variant: "bodySm", color: "muted", className: cn("shrink-0 px-2 font-medium", className), ...props, children: children }));
|
|
@@ -44,7 +47,10 @@ function PopoverMenuGroup({ className, children, ...props }) {
|
|
|
44
47
|
function PopoverMenuGroupLabel({ className, children, ...props }) {
|
|
45
48
|
return (_jsx(Typography, { as: "p", variant: "caption", color: "muted", className: cn("px-2 py-1.5", className), ...props, children: children }));
|
|
46
49
|
}
|
|
47
|
-
const PopoverMenuSearch = React.forwardRef(({
|
|
50
|
+
const PopoverMenuSearch = React.forwardRef(({ "aria-label": ariaLabel, className, containerClassName, label, placeholder, ...props }, ref) => {
|
|
51
|
+
var _a, _b;
|
|
52
|
+
return (_jsx("div", { className: cn("relative -mx-1 -mt-1 mb-1 px-2 py-1 after:absolute after:inset-x-0 after:bottom-0 after:h-px after:bg-border after:content-['']", containerClassName), children: _jsx(Input, { ref: ref, "data-popover-menu-search": "true", label: (_b = (_a = label !== null && label !== void 0 ? label : ariaLabel) !== null && _a !== void 0 ? _a : placeholder) !== null && _b !== void 0 ? _b : "Search", hideLabel: true, className: "gap-0", inputClassName: className, "aria-label": ariaLabel, placeholder: placeholder, ...props }) }));
|
|
53
|
+
});
|
|
48
54
|
PopoverMenuSearch.displayName = "PopoverMenuSearch";
|
|
49
55
|
function PopoverMenuScrollArea({ className, children, maxHeight = "max-h-64", ...props }) {
|
|
50
56
|
return (_jsx("div", { className: cn(maxHeight, "space-y-1 overflow-y-auto overflow-x-hidden", className), ...props, children: children }));
|
|
@@ -103,4 +109,4 @@ function PopoverMenuView({ children, className, direction = "forward", viewKey =
|
|
|
103
109
|
"motion-safe:slide-in-from-right-full", activeDirection === "backward" &&
|
|
104
110
|
"motion-safe:slide-in-from-left-full"), children: children }, viewKey)] }));
|
|
105
111
|
}
|
|
106
|
-
export { PopoverMenuContent, PopoverMenuHeader, PopoverMenuSection, PopoverMenuItem, PopoverMenuCheckboxItem, PopoverMenuDivider, PopoverMenuFooter, PopoverMenuFooterAction, PopoverMenuFooterMeta, PopoverMenuGroup, PopoverMenuGroupLabel, PopoverMenuSearch, PopoverMenuScrollArea, PopoverMenuView, };
|
|
112
|
+
export { PopoverMenuContent, PopoverMenuHeader, PopoverMenuSection, PopoverMenuItem, PopoverMenuCheckboxItem, PopoverMenuDivider, PopoverMenuFooter, PopoverMenuFooterAction, PopoverMenuFooterMeta, PopoverMenuGroup, PopoverMenuGroupLabel, PopoverMenuSearch, PopoverMenuScrollArea, PopoverMenuView, popoverMenuItemFocusClassName, popoverMenuItemFocusWithinClassName, };
|
package/dist/popover.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
3
3
|
import { type TooltipProps } from "./tooltip";
|
|
4
|
+
import { Typography } from "./typography";
|
|
4
5
|
type PopoverRootProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Root>;
|
|
5
6
|
type PopoverContentProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>;
|
|
6
|
-
export interface PopoverProps extends Pick<PopoverRootProps, "defaultOpen" | "modal" | "onOpenChange" | "open">, Omit<PopoverContentProps, "children" | "className"> {
|
|
7
|
+
export interface PopoverProps extends Pick<PopoverRootProps, "defaultOpen" | "modal" | "onOpenChange" | "open">, Omit<PopoverContentProps, "children" | "className" | "title"> {
|
|
7
8
|
trigger: React.ReactElement;
|
|
8
9
|
children: React.ReactNode;
|
|
10
|
+
/** Visible popover heading. Also labels the popover for assistive technology. */
|
|
11
|
+
title?: React.ReactNode;
|
|
9
12
|
surface?: "panel" | "menu";
|
|
10
13
|
contentClassName?: string;
|
|
11
14
|
triggerTooltip?: React.ReactNode;
|
|
@@ -13,5 +16,8 @@ export interface PopoverProps extends Pick<PopoverRootProps, "defaultOpen" | "mo
|
|
|
13
16
|
triggerTooltipSideOffset?: TooltipProps["sideOffset"];
|
|
14
17
|
triggerTooltipClassName?: TooltipProps["contentClassName"];
|
|
15
18
|
}
|
|
16
|
-
declare function Popover({ defaultOpen, modal, onOpenChange, open, trigger, children, surface, contentClassName, triggerTooltip, triggerTooltipSide, triggerTooltipSideOffset, triggerTooltipClassName, align, sideOffset, ...contentProps }: PopoverProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
-
export
|
|
19
|
+
declare function Popover({ defaultOpen, modal, onOpenChange, open, trigger, children, title, surface, contentClassName, triggerTooltip, triggerTooltipSide, triggerTooltipSideOffset, triggerTooltipClassName, align, sideOffset, ...contentProps }: PopoverProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export interface PopoverTitleProps extends Omit<React.ComponentPropsWithoutRef<typeof Typography>, "as" | "asChild" | "variant" | "color"> {
|
|
21
|
+
}
|
|
22
|
+
declare function PopoverTitle({ className, children, ...props }: PopoverTitleProps): import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
export { Popover, PopoverTitle };
|
package/dist/popover.js
CHANGED
|
@@ -1,10 +1,61 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
3
4
|
import { cn } from "./lib/utils";
|
|
4
5
|
import { PopoverContent, PopoverRoot, PopoverTrigger, } from "./popover-primitive";
|
|
5
6
|
import { Tooltip } from "./tooltip";
|
|
6
|
-
|
|
7
|
+
import { Typography } from "./typography";
|
|
8
|
+
function Popover({ defaultOpen, modal, onOpenChange, open, trigger, children, title, surface = "panel", contentClassName, triggerTooltip, triggerTooltipSide, triggerTooltipSideOffset, triggerTooltipClassName, align = "center", sideOffset = 4, ...contentProps }) {
|
|
9
|
+
const generatedTitleId = React.useId();
|
|
10
|
+
const titleId = title ? `popover-title-${generatedTitleId}` : undefined;
|
|
7
11
|
const triggerElement = _jsx(PopoverTrigger, { asChild: true, children: trigger });
|
|
8
|
-
|
|
12
|
+
const contentOnKeyDown = contentProps.onKeyDown;
|
|
13
|
+
const handleContentKeyDown = React.useCallback((event) => {
|
|
14
|
+
contentOnKeyDown === null || contentOnKeyDown === void 0 ? void 0 : contentOnKeyDown(event);
|
|
15
|
+
if (event.defaultPrevented ||
|
|
16
|
+
!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const menuItems = Array.from(event.currentTarget.querySelectorAll('[data-slot="popover-menu-item"]:not(:disabled)')).filter((item) => !item.closest('[aria-hidden="true"]'));
|
|
20
|
+
if (menuItems.length === 0) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const currentIndex = menuItems.findIndex((item) => item === document.activeElement);
|
|
24
|
+
const searchInput = event.currentTarget.querySelector('[data-popover-menu-search="true"]');
|
|
25
|
+
let focusTarget;
|
|
26
|
+
if (event.key === "Home") {
|
|
27
|
+
focusTarget = menuItems[0];
|
|
28
|
+
}
|
|
29
|
+
else if (event.key === "End") {
|
|
30
|
+
focusTarget = menuItems[menuItems.length - 1];
|
|
31
|
+
}
|
|
32
|
+
else if (document.activeElement === searchInput) {
|
|
33
|
+
focusTarget =
|
|
34
|
+
event.key === "ArrowDown"
|
|
35
|
+
? menuItems[0]
|
|
36
|
+
: menuItems[menuItems.length - 1];
|
|
37
|
+
}
|
|
38
|
+
else if (currentIndex >= 0) {
|
|
39
|
+
if (event.key === "ArrowUp" && currentIndex === 0 && searchInput) {
|
|
40
|
+
focusTarget = searchInput;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const offset = event.key === "ArrowDown" ? 1 : -1;
|
|
44
|
+
const nextIndex = Math.min(Math.max(currentIndex + offset, 0), menuItems.length - 1);
|
|
45
|
+
focusTarget = menuItems[nextIndex];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!focusTarget) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
focusTarget.focus({ preventScroll: true });
|
|
53
|
+
}, [contentOnKeyDown]);
|
|
54
|
+
return (_jsxs(PopoverRoot, { defaultOpen: defaultOpen, modal: modal, onOpenChange: onOpenChange, open: open, children: [triggerTooltip ? (_jsx(Tooltip, { content: triggerTooltip, side: triggerTooltipSide, sideOffset: triggerTooltipSideOffset, contentClassName: triggerTooltipClassName, children: triggerElement })) : (triggerElement), _jsxs(PopoverContent, { align: align, sideOffset: sideOffset, "aria-labelledby": titleId !== null && titleId !== void 0 ? titleId : contentProps["aria-labelledby"], className: cn(surface === "menu" && "overflow-hidden p-1", contentClassName), ...contentProps, onKeyDown: handleContentKeyDown, children: [title ? (_jsx(PopoverTitle, { id: titleId, className: cn(surface === "menu"
|
|
55
|
+
? "px-2 py-1.5"
|
|
56
|
+
: "-mx-4 -mt-4 mb-4 border-b px-4 py-3"), children: title })) : null, children] })] }));
|
|
9
57
|
}
|
|
10
|
-
|
|
58
|
+
function PopoverTitle({ className, children, ...props }) {
|
|
59
|
+
return (_jsx(Typography, { as: "p", variant: "label", "data-slot": "popover-title", className: className, ...props, children: children }));
|
|
60
|
+
}
|
|
61
|
+
export { Popover, PopoverTitle };
|
package/dist/section-nav.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Button } from "./button";
|
|
|
7
7
|
import { cn } from "./lib/utils";
|
|
8
8
|
import { Link } from "./link";
|
|
9
9
|
import { Popover } from "./popover";
|
|
10
|
+
import { popoverMenuItemFocusClassName } from "./popover-menu";
|
|
10
11
|
import { Typography } from "./typography";
|
|
11
12
|
function hasSectionNavChildren(item) {
|
|
12
13
|
return Array.isArray(item.items) && item.items.length > 0;
|
|
@@ -28,7 +29,7 @@ const SectionNavRow = React.forwardRef(({ item, active, surface, hasChildren = f
|
|
|
28
29
|
const isLink = Boolean(item.href) && !item.disabled && !hasChildren;
|
|
29
30
|
const className = cn(surface === "list"
|
|
30
31
|
? "w-full justify-start gap-2 rounded-control px-2.5 text-sm font-medium shadow-none"
|
|
31
|
-
: "h-auto min-h-8 w-full justify-start gap-2
|
|
32
|
+
: cn("h-auto min-h-8 w-full justify-start gap-2 px-2 py-1.5 text-left text-sm font-normal shadow-none whitespace-normal", popoverMenuItemFocusClassName), active && !item.disabled
|
|
32
33
|
? surface === "list"
|
|
33
34
|
? "bg-background hover:bg-background"
|
|
34
35
|
: "bg-accent text-accent-foreground hover:bg-accent hover:text-accent-foreground"
|
|
@@ -42,14 +43,14 @@ const SectionNavRow = React.forwardRef(({ item, active, surface, hasChildren = f
|
|
|
42
43
|
hasChildren,
|
|
43
44
|
});
|
|
44
45
|
if (isLink) {
|
|
45
|
-
return (_jsx(Button, { ref: ref, asChild: true, variant: "ghost", disabled: item.disabled, className: cn(className, triggerClassName), "aria-current": active ? "page" : undefined, ...buttonProps, children: _jsx(Link, { variant: "inherit", href: item.href, target: item.target, rel: item.rel, onClick: (event) => {
|
|
46
|
+
return (_jsx(Button, { ref: ref, asChild: true, variant: "ghost", disabled: item.disabled, "data-slot": surface === "menu" ? "popover-menu-item" : undefined, className: cn(className, triggerClassName), "aria-current": active ? "page" : undefined, ...buttonProps, children: _jsx(Link, { variant: "inherit", href: item.href, target: item.target, rel: item.rel, onClick: (event) => {
|
|
46
47
|
var _a;
|
|
47
48
|
externalOnClick === null || externalOnClick === void 0 ? void 0 : externalOnClick(event);
|
|
48
49
|
onSelect === null || onSelect === void 0 ? void 0 : onSelect();
|
|
49
50
|
(_a = item.onClick) === null || _a === void 0 ? void 0 : _a.call(item);
|
|
50
51
|
}, children: content }) }));
|
|
51
52
|
}
|
|
52
|
-
return (_jsx(Button, { ref: ref, type: "button", variant: "ghost", disabled: item.disabled, className: cn(className, triggerClassName), "aria-current": active && !hasChildren ? "page" : undefined, "aria-expanded": hasChildren ? ariaExpanded : undefined, "aria-haspopup": hasChildren ? "menu" : undefined, ...buttonProps, onClick: hasChildren
|
|
53
|
+
return (_jsx(Button, { ref: ref, type: "button", variant: "ghost", disabled: item.disabled, "data-slot": surface === "menu" ? "popover-menu-item" : undefined, className: cn(className, triggerClassName), "aria-current": active && !hasChildren ? "page" : undefined, "aria-expanded": hasChildren ? ariaExpanded : undefined, "aria-haspopup": hasChildren ? "menu" : undefined, ...buttonProps, onClick: hasChildren
|
|
53
54
|
? externalOnClick
|
|
54
55
|
: (event) => {
|
|
55
56
|
var _a;
|
package/dist/select-primitive.js
CHANGED
|
@@ -27,7 +27,7 @@ const SelectContent = React.forwardRef(({ className, children, header, position
|
|
|
27
27
|
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
|
28
28
|
const SelectLabel = React.forwardRef(({ className, ...props }, ref) => (_jsx(SelectPrimitive.Label, { ref: ref, "data-slot": "select-label", className: cn("px-2 py-1.5 text-xs text-muted-foreground", className), ...props })));
|
|
29
29
|
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
|
30
|
-
const SelectItem = React.forwardRef(({ className, children, description, ...props }, ref) => (_jsxs(SelectPrimitive.Item, { ref: ref, "data-slot": "select-item", className: cn("relative flex min-h-8 w-full cursor-pointer select-none items-center gap-1.5 rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className), ...props, children: [_jsx("span", { "data-slot": "select-item-indicator", className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(SelectPrimitive.ItemIndicator, { children: _jsx(Check, { className: "h-4 w-4" }) }) }), _jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx(SelectPrimitive.ItemText, { className: "break-words font-normal", children: children }), description ? (_jsx(Typography, { as: "span", variant: "caption", children: description })) : null] })] })));
|
|
30
|
+
const SelectItem = React.forwardRef(({ className, children, description, ...props }, ref) => (_jsxs(SelectPrimitive.Item, { ref: ref, "data-slot": "select-item", className: cn("relative flex min-h-8 w-full cursor-pointer select-none items-center gap-1.5 rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[state=checked]:bg-primary/10 data-[state=checked]:text-primary data-[state=checked]:hover:bg-primary/20 data-[state=checked]:hover:text-primary data-[state=checked]:focus:bg-primary/20 data-[state=checked]:focus:text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className), ...props, children: [_jsx("span", { "data-slot": "select-item-indicator", className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: _jsx(SelectPrimitive.ItemIndicator, { children: _jsx(Check, { className: "h-4 w-4" }) }) }), _jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx(SelectPrimitive.ItemText, { className: "break-words font-normal", children: children }), description ? (_jsx(Typography, { as: "span", variant: "caption", children: description })) : null] })] })));
|
|
31
31
|
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
|
32
32
|
const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => (_jsx(SelectPrimitive.Separator, { ref: ref, "data-slot": "select-separator", className: cn("-mx-1 my-1 h-px bg-border", className), ...props })));
|
|
33
33
|
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
package/dist/select.js
CHANGED
|
@@ -43,6 +43,7 @@ function Select({ label, options, placeholder = "Select an option", description,
|
|
|
43
43
|
const generatedId = React.useId();
|
|
44
44
|
const selectId = id !== null && id !== void 0 ? id : `select-${generatedId}`;
|
|
45
45
|
const searchInputRef = React.useRef(null);
|
|
46
|
+
const contentRef = React.useRef(null);
|
|
46
47
|
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
|
|
47
48
|
const [searchQuery, setSearchQuery] = React.useState("");
|
|
48
49
|
const errorId = error ? `${selectId}-error` : undefined;
|
|
@@ -69,6 +70,31 @@ function Select({ label, options, placeholder = "Select an option", description,
|
|
|
69
70
|
}
|
|
70
71
|
input.focus({ preventScroll: true });
|
|
71
72
|
}, []);
|
|
73
|
+
const getEnabledOptionElements = React.useCallback(() => {
|
|
74
|
+
var _a, _b;
|
|
75
|
+
return Array.from((_b = (_a = contentRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll('[data-slot="select-item"]:not([data-disabled])')) !== null && _b !== void 0 ? _b : []);
|
|
76
|
+
}, []);
|
|
77
|
+
const focusBoundaryOption = React.useCallback((boundary) => {
|
|
78
|
+
const enabledOptions = getEnabledOptionElements();
|
|
79
|
+
const option = boundary === "first"
|
|
80
|
+
? enabledOptions[0]
|
|
81
|
+
: enabledOptions[enabledOptions.length - 1];
|
|
82
|
+
option === null || option === void 0 ? void 0 : option.focus({ preventScroll: true });
|
|
83
|
+
}, [getEnabledOptionElements]);
|
|
84
|
+
const handleContentKeyDownCapture = React.useCallback((event) => {
|
|
85
|
+
if (event.key !== "ArrowUp") {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const firstOption = getEnabledOptionElements()[0];
|
|
89
|
+
if (document.activeElement !== firstOption) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
event.stopPropagation();
|
|
94
|
+
if (searchable) {
|
|
95
|
+
focusSearchInput();
|
|
96
|
+
}
|
|
97
|
+
}, [focusSearchInput, getEnabledOptionElements, searchable]);
|
|
72
98
|
React.useEffect(() => {
|
|
73
99
|
if (!isOpen) {
|
|
74
100
|
setSearchQuery("");
|
|
@@ -110,7 +136,17 @@ function Select({ label, options, placeholder = "Select an option", description,
|
|
|
110
136
|
}
|
|
111
137
|
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(nextOpen);
|
|
112
138
|
}, [onOpenChange, open]);
|
|
113
|
-
return (_jsxs(Field, { className: className, "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: selectId, className: cn(hideLabel && "sr-only", labelClassName), children: label }), _jsxs(SelectRoot, { disabled: disabled, open: open, onOpenChange: handleOpenChange, ...props, children: [_jsx(SelectTrigger, { id: selectId, className: triggerClassName, "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || undefined, children: _jsx(SelectValue, { placeholder: placeholder }) }), _jsx(SelectContent, { className: contentClassName, header: searchable ? (_jsx(InputControl, { ref: searchInputRef, value: searchQuery, onChange: (event) => setSearchQuery(event.target.value), placeholder: searchPlaceholder, "aria-label": searchInputAriaLabel, autoComplete: "off", className: "h-control border-0 bg-transparent px-2 py-0 text-sm shadow-none
|
|
139
|
+
return (_jsxs(Field, { className: className, "data-disabled": disabled ? "true" : undefined, "data-invalid": isInvalid ? "true" : undefined, children: [_jsx(FieldLabel, { htmlFor: selectId, className: cn(hideLabel && "sr-only", labelClassName), children: label }), _jsxs(SelectRoot, { disabled: disabled, open: open, onOpenChange: handleOpenChange, ...props, children: [_jsx(SelectTrigger, { id: selectId, className: triggerClassName, "aria-invalid": isInvalid ? "true" : undefined, "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || undefined, children: _jsx(SelectValue, { placeholder: placeholder }) }), _jsx(SelectContent, { ref: contentRef, className: contentClassName, onKeyDownCapture: handleContentKeyDownCapture, header: searchable ? (_jsx(InputControl, { ref: searchInputRef, value: searchQuery, onChange: (event) => setSearchQuery(event.target.value), placeholder: searchPlaceholder, "aria-label": searchInputAriaLabel, autoComplete: "off", className: "h-control border-0 bg-transparent px-2 py-0 text-sm shadow-none", onKeyDown: (event) => {
|
|
140
|
+
if (event.key === "ArrowDown" ||
|
|
141
|
+
event.key === "ArrowUp") {
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
event.stopPropagation();
|
|
144
|
+
focusBoundaryOption(event.key === "ArrowDown" ? "first" : "last");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (event.key === "Escape" || event.key === "Tab") {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
114
150
|
event.stopPropagation();
|
|
115
151
|
} })) : undefined, viewportClassName: searchable ? "max-h-64" : undefined, children: hasVisibleOptions ? (optionGroups.map((group, groupIndex) => {
|
|
116
152
|
var _a;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
interface TableColumnFilterBase {
|
|
3
|
+
/** Overrides the column header text in accessible filter labels. */
|
|
4
|
+
label?: string;
|
|
5
|
+
/** Short text shown in the filter row before a value is selected. */
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface TableTextColumnFilter extends TableColumnFilterBase {
|
|
10
|
+
type: "text";
|
|
11
|
+
value: string;
|
|
12
|
+
onValueChange: (value: string) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface TableColumnFilterOption {
|
|
15
|
+
label: React.ReactNode;
|
|
16
|
+
value: string;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface TableOptionsColumnFilter extends TableColumnFilterBase {
|
|
20
|
+
type: "options";
|
|
21
|
+
values: readonly string[];
|
|
22
|
+
onValuesChange: (values: string[]) => void;
|
|
23
|
+
options: readonly TableColumnFilterOption[];
|
|
24
|
+
/** Adds a compact option search field. Defaults to true for more than seven options. */
|
|
25
|
+
searchable?: boolean;
|
|
26
|
+
searchPlaceholder?: string;
|
|
27
|
+
emptyMessage?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface TableDateColumnFilter extends TableColumnFilterBase {
|
|
30
|
+
type: "date";
|
|
31
|
+
value: string;
|
|
32
|
+
onValueChange: (value: string) => void;
|
|
33
|
+
minDate?: Date;
|
|
34
|
+
maxDate?: Date;
|
|
35
|
+
}
|
|
36
|
+
export type TableColumnFilter = TableTextColumnFilter | TableOptionsColumnFilter | TableDateColumnFilter;
|
|
37
|
+
interface TableColumnFilterControlProps {
|
|
38
|
+
filter: TableColumnFilter;
|
|
39
|
+
label: string;
|
|
40
|
+
}
|
|
41
|
+
declare function TableColumnFilterControl({ filter, label, }: TableColumnFilterControlProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
export { TableColumnFilterControl };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { FilterIcon } from "./animated-icons";
|
|
5
|
+
import { DateInput } from "./date-input";
|
|
6
|
+
import { FilterPopover } from "./filter-popover";
|
|
7
|
+
import { FilterToggle, filterToggleVariants, } from "./filter-toggle";
|
|
8
|
+
import { cn } from "./lib/utils";
|
|
9
|
+
import { Popover } from "./popover";
|
|
10
|
+
import { PopoverMenuFooter, PopoverMenuFooterAction, PopoverMenuSearch, } from "./popover-menu";
|
|
11
|
+
function isTableColumnFilterActive(filter) {
|
|
12
|
+
return filter.type === "options"
|
|
13
|
+
? filter.values.length > 0
|
|
14
|
+
: filter.value.trim().length > 0;
|
|
15
|
+
}
|
|
16
|
+
function getOptionsFilterSummary(filter) {
|
|
17
|
+
var _a, _b, _c;
|
|
18
|
+
if (filter.values.length === 0) {
|
|
19
|
+
return (_a = filter.placeholder) !== null && _a !== void 0 ? _a : "Filter";
|
|
20
|
+
}
|
|
21
|
+
if (filter.values.length > 1) {
|
|
22
|
+
return `${filter.values.length} selected`;
|
|
23
|
+
}
|
|
24
|
+
return ((_c = (_b = filter.options.find((option) => option.value === filter.values[0])) === null || _b === void 0 ? void 0 : _b.label) !== null && _c !== void 0 ? _c : filter.values[0]);
|
|
25
|
+
}
|
|
26
|
+
const TableFilterTrigger = React.forwardRef(({ active, disabled, label, children, ...props }, ref) => (_jsx(FilterToggle, { ref: ref, type: "button", disabled: disabled, selected: active, "aria-label": `Filter by ${label}${active ? ", active" : ""}`, className: cn("h-9 w-full min-w-0 justify-start overflow-hidden px-2 text-sm font-normal", !active &&
|
|
27
|
+
"bg-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground"), icon: _jsx(FilterIcon, { "aria-hidden": "true", className: "shrink-0", size: 16 }), ...props, children: _jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: children }) })));
|
|
28
|
+
TableFilterTrigger.displayName = "TableFilterTrigger";
|
|
29
|
+
function TextColumnFilter({ filter, label, }) {
|
|
30
|
+
var _a, _b, _c;
|
|
31
|
+
const active = isTableColumnFilterActive(filter);
|
|
32
|
+
const accessibleLabel = (_a = filter.label) !== null && _a !== void 0 ? _a : label;
|
|
33
|
+
return (_jsxs(Popover, { align: "start", surface: "menu", "aria-label": `${accessibleLabel} filter`, title: `Filter by ${accessibleLabel}`, trigger: _jsx(TableFilterTrigger, { active: active, disabled: filter.disabled, label: accessibleLabel, children: active ? filter.value : (_b = filter.placeholder) !== null && _b !== void 0 ? _b : "Filter" }), children: [_jsx(PopoverMenuSearch, { autoFocus: true, value: filter.value, onChange: (event) => filter.onValueChange(event.target.value), placeholder: (_c = filter.placeholder) !== null && _c !== void 0 ? _c : `Search ${accessibleLabel}`, "aria-label": `Filter ${accessibleLabel}`, disabled: filter.disabled, containerClassName: "after:hidden" }), active ? (_jsx(PopoverMenuFooter, { children: _jsx(PopoverMenuFooterAction, { onClick: () => filter.onValueChange(""), children: "Clear filter" }) })) : null] }));
|
|
34
|
+
}
|
|
35
|
+
function OptionsColumnFilter({ filter, label, }) {
|
|
36
|
+
var _a, _b, _c, _d;
|
|
37
|
+
const active = isTableColumnFilterActive(filter);
|
|
38
|
+
const accessibleLabel = (_a = filter.label) !== null && _a !== void 0 ? _a : label;
|
|
39
|
+
const searchable = (_b = filter.searchable) !== null && _b !== void 0 ? _b : filter.options.length > 7;
|
|
40
|
+
const toggleOption = (optionValue) => {
|
|
41
|
+
const nextValues = filter.values.includes(optionValue)
|
|
42
|
+
? filter.values.filter((value) => value !== optionValue)
|
|
43
|
+
: [...filter.values, optionValue];
|
|
44
|
+
filter.onValuesChange(nextValues);
|
|
45
|
+
};
|
|
46
|
+
return (_jsx(FilterPopover, { directSingleGroup: true, ariaLabel: `${accessibleLabel} filter`, title: `Filter by ${accessibleLabel}`, trigger: _jsx(TableFilterTrigger, { active: active, disabled: filter.disabled, label: accessibleLabel, children: getOptionsFilterSummary(filter) }), groups: [
|
|
47
|
+
{
|
|
48
|
+
id: `table-column-${accessibleLabel}`,
|
|
49
|
+
label: accessibleLabel,
|
|
50
|
+
searchable,
|
|
51
|
+
searchPlaceholder: (_c = filter.searchPlaceholder) !== null && _c !== void 0 ? _c : "Search options",
|
|
52
|
+
emptyLabel: (_d = filter.emptyMessage) !== null && _d !== void 0 ? _d : "No options found",
|
|
53
|
+
selectedCount: filter.values.length,
|
|
54
|
+
options: filter.options.map((option) => ({
|
|
55
|
+
id: option.value,
|
|
56
|
+
label: option.label,
|
|
57
|
+
selected: filter.values.includes(option.value),
|
|
58
|
+
disabled: filter.disabled || option.disabled,
|
|
59
|
+
onSelect: () => toggleOption(option.value),
|
|
60
|
+
})),
|
|
61
|
+
},
|
|
62
|
+
], selectedCount: filter.values.length, onReset: active ? () => filter.onValuesChange([]) : undefined, resetLabel: "Clear filter" }));
|
|
63
|
+
}
|
|
64
|
+
function DateColumnFilter({ filter, label, }) {
|
|
65
|
+
var _a, _b;
|
|
66
|
+
const active = isTableColumnFilterActive(filter);
|
|
67
|
+
const accessibleLabel = (_a = filter.label) !== null && _a !== void 0 ? _a : label;
|
|
68
|
+
return (_jsx(DateInput, { label: `Filter by ${accessibleLabel}${active ? ", active" : ""}`, hideLabel: true, value: filter.value, onValueChange: filter.onValueChange, placeholder: (_b = filter.placeholder) !== null && _b !== void 0 ? _b : "Pick a date", disabled: filter.disabled, minDate: filter.minDate, maxDate: filter.maxDate, clearable: true, clearLabel: `Clear ${accessibleLabel} filter`, className: "gap-0", triggerClassName: cn(filterToggleVariants({ selected: active }), "h-9 w-full min-w-0 justify-start text-sm font-normal shadow-none", active ? "pl-2 pr-16" : "pl-2 pr-9", !active &&
|
|
69
|
+
"border-0 bg-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground") }));
|
|
70
|
+
}
|
|
71
|
+
function TableColumnFilterControl({ filter, label, }) {
|
|
72
|
+
if (filter.type === "text") {
|
|
73
|
+
return _jsx(TextColumnFilter, { filter: filter, label: label });
|
|
74
|
+
}
|
|
75
|
+
if (filter.type === "options") {
|
|
76
|
+
return _jsx(OptionsColumnFilter, { filter: filter, label: label });
|
|
77
|
+
}
|
|
78
|
+
return _jsx(DateColumnFilter, { filter: filter, label: label });
|
|
79
|
+
}
|
|
80
|
+
export { TableColumnFilterControl };
|
package/dist/table.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type PaginationProps } from "./pagination";
|
|
3
|
+
import { type TableColumnFilter } from "./table-column-filter";
|
|
3
4
|
interface TableRootProps extends React.HTMLAttributes<HTMLTableElement> {
|
|
4
5
|
wrapperClassName?: string;
|
|
5
6
|
}
|
|
@@ -73,6 +74,10 @@ export interface TableColumn<Row> {
|
|
|
73
74
|
sortable?: boolean;
|
|
74
75
|
sortKey?: string;
|
|
75
76
|
sortLabel?: string;
|
|
77
|
+
/** Controlled filter rendered in a second header row. Filter the supplied `rows` in the screen or data hook. */
|
|
78
|
+
filter?: TableColumnFilter;
|
|
79
|
+
/** Optional class name for this column's filter cell. */
|
|
80
|
+
filterClassName?: string;
|
|
76
81
|
/** When false, restore-defaults helpers can omit the column from the visible set. */
|
|
77
82
|
defaultVisible?: boolean;
|
|
78
83
|
/** When false, the column visibility menu keeps the column out of its checklist. */
|
package/dist/table.js
CHANGED
|
@@ -6,6 +6,7 @@ import { ArrowUpIcon } from "./animated-icons/arrow-up";
|
|
|
6
6
|
import { EmptyState } from "./empty-state";
|
|
7
7
|
import { Pagination } from "./pagination";
|
|
8
8
|
import { Skeleton } from "./skeleton";
|
|
9
|
+
import { TableColumnFilterControl, } from "./table-column-filter";
|
|
9
10
|
import { cn } from "./lib/utils";
|
|
10
11
|
const TableRoot = React.forwardRef(({ className, wrapperClassName: _wrapperClassName, ...props }, ref) => (_jsx("table", { ref: ref, className: cn("w-full caption-bottom text-sm", className), ...props })));
|
|
11
12
|
TableRoot.displayName = "TableRoot";
|
|
@@ -110,7 +111,7 @@ function hasColumnWidthOverrides(columnWidths) {
|
|
|
110
111
|
}
|
|
111
112
|
function getMeasuredColumnWidths(resizeHandle, columns, fallbackColumnWidths) {
|
|
112
113
|
const table = resizeHandle.closest("table");
|
|
113
|
-
const headerCells = table === null || table === void 0 ? void 0 : table.querySelectorAll('thead th
|
|
114
|
+
const headerCells = table === null || table === void 0 ? void 0 : table.querySelectorAll('thead th[data-slot="table-column-head"]');
|
|
114
115
|
if (!(headerCells === null || headerCells === void 0 ? void 0 : headerCells.length)) {
|
|
115
116
|
return fallbackColumnWidths;
|
|
116
117
|
}
|
|
@@ -177,8 +178,8 @@ function stickyClassName(sticky, kind, showShadow = false) {
|
|
|
177
178
|
: "shadow-[inset_1px_0_0_0_hsl(var(--table-border)),_-8px_0_12px_-10px_hsl(var(--foreground)_/_0.35)]"
|
|
178
179
|
: undefined;
|
|
179
180
|
const background = kind === "head" ? "bg-[hsl(var(--table-header-background))]" : "bg-card";
|
|
180
|
-
//
|
|
181
|
-
const z = kind === "
|
|
181
|
+
// Header and filter cells layer above sticky body cells on the other axis.
|
|
182
|
+
const z = kind === "cell" ? "z-10" : "z-20";
|
|
182
183
|
return cn("sticky", background, side, z, shadow);
|
|
183
184
|
}
|
|
184
185
|
function getStickyShadowVisibility(sticky, scrollState) {
|
|
@@ -245,7 +246,7 @@ export const TABLE_SKELETON_PRESETS = {
|
|
|
245
246
|
};
|
|
246
247
|
function SortableTableHead({ field, children, currentSortField, currentSortDirection, onSort, className, allowWrap = false, width, minWidth, maxWidth, align, sortLabel, resizeHandle, treatWidthAsMinWidth, containOverflow, }) {
|
|
247
248
|
const isSorted = currentSortField === field;
|
|
248
|
-
return (_jsxs(TableHead, { className: cn(resizeHandle && "relative", alignClassName(align), className), wrap: allowWrap, width: width, minWidth: minWidth, maxWidth: maxWidth, treatWidthAsMinWidth: treatWidthAsMinWidth, containOverflow: containOverflow, children: [_jsxs("button", { type: "button", onClick: () => onSort(field), className: cn("-ml-1.5 flex h-7 cursor-pointer items-center gap-1 rounded px-1.5 py-0 font-medium transition-colors hover:text-foreground", containOverflow && "max-w-full overflow-hidden", allowWrap
|
|
249
|
+
return (_jsxs(TableHead, { "data-slot": "table-column-head", className: cn(resizeHandle && "relative", alignClassName(align), className), wrap: allowWrap, width: width, minWidth: minWidth, maxWidth: maxWidth, treatWidthAsMinWidth: treatWidthAsMinWidth, containOverflow: containOverflow, children: [_jsxs("button", { type: "button", onClick: () => onSort(field), className: cn("-ml-1.5 flex h-7 cursor-pointer items-center gap-1 rounded px-1.5 py-0 font-medium transition-colors hover:text-foreground", containOverflow && "max-w-full overflow-hidden", allowWrap
|
|
249
250
|
? "max-w-full whitespace-normal text-left"
|
|
250
251
|
: "whitespace-nowrap", align === "right" && "ml-auto justify-end", align === "center" && "mx-auto justify-center"), style: { font: "inherit", color: "inherit", fontWeight: 500 }, "aria-label": `Sort by ${sortLabel !== null && sortLabel !== void 0 ? sortLabel : field}`, children: [_jsx("span", { className: cn("min-w-0", containOverflow && !allowWrap && "truncate", containOverflow && allowWrap && "break-words [overflow-wrap:anywhere]"), children: children }), _jsx("span", { className: "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center", children: isSorted ? (currentSortDirection === "asc" ? (_jsx(ArrowUpIcon, { className: "shrink-0", size: 14 })) : (_jsx(ArrowDownIcon, { className: "shrink-0", size: 14 }))) : (_jsx(ArrowUpDown, { className: "h-3.5 w-3.5 shrink-0 opacity-50" })) })] }), resizeHandle] }));
|
|
251
252
|
}
|
|
@@ -331,6 +332,7 @@ function isStickyResizeBoundary(column, nextColumn) {
|
|
|
331
332
|
function Table({ columns, rows, getRowKey, emptyState = (_jsx(EmptyState, { message: "No data available", description: "There are no rows to display in this table yet.", className: "border-0 bg-transparent px-0 py-6" })), toolbar, caption, className, containerClassName, tableClassName, rowClassName, rowTone, maxHeight, minHeight, sorting, pagination, loading = false, loadingRows = 5, visibleColumnKeys, resizableColumns = false, columnResizeMode = "pair", columnWidths, defaultColumnWidths, onColumnWidthsChange, renderColumnResizeHandle, tableLayout, centerEmptyState = false, emptyStateMinHeight = DEFAULT_CENTERED_EMPTY_STATE_MIN_HEIGHT, striped = false, rowExpansion, onRowClick, onScroll, style, ...props }) {
|
|
332
333
|
const visibleColumns = getVisibleColumns(columns, visibleColumnKeys);
|
|
333
334
|
const hasExpansionColumn = Boolean(rowExpansion);
|
|
335
|
+
const hasColumnFilters = visibleColumns.some((column) => column.filter);
|
|
334
336
|
const isGrowResizeMode = columnResizeMode === "grow";
|
|
335
337
|
const isColumnResizingEnabled = resizableColumns || visibleColumns.some((column) => column.resizable);
|
|
336
338
|
// Use auto layout by default so the browser can grow a column to contain its
|
|
@@ -561,18 +563,25 @@ function Table({ columns, rows, getRowKey, emptyState = (_jsx(EmptyState, { mess
|
|
|
561
563
|
minWidth: getRenderedColumnMinWidth(column, isColumnResizingEnabled),
|
|
562
564
|
maxWidth: getRenderedColumnMaxWidth(column, isColumnResizingEnabled),
|
|
563
565
|
treatWidthAsMinWidth: !isColumnResizingEnabled,
|
|
564
|
-
}) }, column.key)))] }),
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
566
|
+
}) }, column.key)))] }), _jsxs(TableHeader, { children: [_jsxs(TableRow, { children: [hasExpansionColumn ? (_jsx(TableHead, { "data-slot": "table-expansion-head", "aria-label": "Expand rows", className: "w-12 px-2" })) : null, visibleColumns.map((column, columnIndex) => {
|
|
567
|
+
var _a;
|
|
568
|
+
const sortField = (_a = column.sortKey) !== null && _a !== void 0 ? _a : column.key;
|
|
569
|
+
const isSortable = column.sortable && sorting;
|
|
570
|
+
const columnWidth = getRenderedColumnWidth({
|
|
571
|
+
column,
|
|
572
|
+
columnWidths: resolvedColumnWidths,
|
|
573
|
+
resizing: isColumnResizingEnabled,
|
|
574
|
+
});
|
|
575
|
+
const resizeHandle = renderResizeHandle(column, columnIndex);
|
|
576
|
+
return (isSortable ? (_jsx(SortableTableHead, { field: sortField, currentSortField: sorting.field, currentSortDirection: sorting.direction, onSort: sorting.onSort, className: cn(stickyClassName(column.sticky, "head", getStickyShadowVisibility(column.sticky, stickyScrollState)), column.headerClassName), allowWrap: column.wrap, width: columnWidth, minWidth: getRenderedColumnMinWidth(column, isColumnResizingEnabled), maxWidth: getRenderedColumnMaxWidth(column, isColumnResizingEnabled), align: column.align, sortLabel: column.sortLabel, resizeHandle: resizeHandle, treatWidthAsMinWidth: !isColumnResizingEnabled, containOverflow: isColumnResizingEnabled, children: column.header }, column.key)) : (_jsxs(TableHead, { "data-slot": "table-column-head", width: columnWidth, minWidth: getRenderedColumnMinWidth(column, isColumnResizingEnabled), maxWidth: getRenderedColumnMaxWidth(column, isColumnResizingEnabled), wrap: column.wrap, treatWidthAsMinWidth: !isColumnResizingEnabled, containOverflow: isColumnResizingEnabled, className: cn(resizeHandle && "relative", alignClassName(column.align), stickyClassName(column.sticky, "head", getStickyShadowVisibility(column.sticky, stickyScrollState)), column.headerClassName), children: [column.header, resizeHandle] }, column.key)));
|
|
577
|
+
})] }), hasColumnFilters ? (_jsxs(TableRow, { "data-slot": "table-filter-row", className: "!bg-card", children: [hasExpansionColumn ? (_jsx(TableHead, { "data-slot": "table-expansion-filter-cell", "aria-hidden": "true", className: "w-12 px-2" })) : null, visibleColumns.map((column) => {
|
|
578
|
+
const columnWidth = getRenderedColumnWidth({
|
|
579
|
+
column,
|
|
580
|
+
columnWidths: resolvedColumnWidths,
|
|
581
|
+
resizing: isColumnResizingEnabled,
|
|
582
|
+
});
|
|
583
|
+
return (_jsx(TableHead, { "data-slot": "table-filter-cell", width: columnWidth, minWidth: getRenderedColumnMinWidth(column, isColumnResizingEnabled), maxWidth: getRenderedColumnMaxWidth(column, isColumnResizingEnabled), treatWidthAsMinWidth: !isColumnResizingEnabled, containOverflow: isColumnResizingEnabled, className: cn("relative h-11 px-2 py-1 font-normal", stickyClassName(column.sticky, "filter", getStickyShadowVisibility(column.sticky, stickyScrollState)), column.filterClassName), children: column.filter ? (_jsx("div", { "data-slot": "table-filter-control", className: "absolute inset-x-2 inset-y-1 min-w-0", children: _jsx(TableColumnFilterControl, { filter: column.filter, label: getColumnResizeLabel(column) }) })) : null }, column.key));
|
|
584
|
+
})] })) : null] }), _jsx(TableBody, { children: loading ? (_jsx(TableRowSkeleton, { rows: loadingRows, columns: [
|
|
576
585
|
...(hasExpansionColumn
|
|
577
586
|
? [{ width: "1rem", height: "1rem" }]
|
|
578
587
|
: []),
|
package/dist/tabs.js
CHANGED
|
@@ -7,7 +7,7 @@ import { ChevronDownIcon } from "./animated-icons/chevron-down";
|
|
|
7
7
|
import { XIcon } from "./animated-icons/x";
|
|
8
8
|
import { cn } from "./lib/utils";
|
|
9
9
|
import { Popover } from "./popover";
|
|
10
|
-
import { PopoverMenuSection } from "./popover-menu";
|
|
10
|
+
import { PopoverMenuSection, popoverMenuItemFocusClassName, } from "./popover-menu";
|
|
11
11
|
const TabsContext = React.createContext(null);
|
|
12
12
|
const getTabValueSelector = (value) => {
|
|
13
13
|
const escapedValue = typeof CSS !== "undefined" && typeof CSS.escape === "function"
|
|
@@ -82,7 +82,7 @@ function TabTriggerWithDropdown({ item, triggerClassName, contentClassName, }) {
|
|
|
82
82
|
setInternalActiveSection((_b = (_a = sections[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null);
|
|
83
83
|
}
|
|
84
84
|
}, [isTabSelected, item.activeSection, sections]);
|
|
85
|
-
return (_jsx(Popover, { open: open, onOpenChange: setOpen, surface: "menu", align: "start", contentClassName: cn("min-w-[12rem]", contentClassName), trigger: _jsxs(TabsTrigger, { value: item.value, disabled: item.disabled, "data-state": isTabSelected ? "active" : "inactive", "data-tab-value": item.value, className: cn("flex items-center gap-1", triggerClassName), children: [renderTabsItemLabel(item.label, isTabSelected), _jsx(ChevronDownIcon, { size: 12, className: cn("transition-transform duration-200", open && "rotate-180") })] }), children: _jsx(PopoverMenuSection, { className: "space-y-1", children: sections.map((section) => (_jsxs("button", { type: "button", className: cn("flex min-h-fit w-full cursor-pointer items-start gap-2
|
|
85
|
+
return (_jsx(Popover, { open: open, onOpenChange: setOpen, surface: "menu", align: "start", contentClassName: cn("min-w-[12rem]", contentClassName), trigger: _jsxs(TabsTrigger, { value: item.value, disabled: item.disabled, "data-state": isTabSelected ? "active" : "inactive", "data-tab-value": item.value, className: cn("flex items-center gap-1", triggerClassName), children: [renderTabsItemLabel(item.label, isTabSelected), _jsx(ChevronDownIcon, { size: 12, className: cn("transition-transform duration-200", open && "rotate-180") })] }), children: _jsx(PopoverMenuSection, { className: "space-y-1", children: sections.map((section) => (_jsxs("button", { type: "button", "data-slot": "popover-menu-item", className: cn("flex min-h-fit w-full cursor-pointer items-start gap-2 px-3 py-2 text-left text-sm font-normal leading-5 text-popover-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground", popoverMenuItemFocusClassName, isTabSelected &&
|
|
86
86
|
activeSection === section.id &&
|
|
87
87
|
"bg-accent text-accent-foreground hover:bg-accent hover:text-accent-foreground"), onClick: () => handleSectionClick(section.id), children: [_jsx("span", { className: "block min-w-0 flex-1 whitespace-normal break-words leading-5", children: section.label }), isTabSelected && activeSection === section.id ? (_jsx(Check, { "aria-hidden": "true", className: "mt-0.5 h-4 w-4 shrink-0" })) : null] }, section.id))) }) }));
|
|
88
88
|
}
|
|
@@ -41,6 +41,6 @@ function useSearchHighlight(text, query) {
|
|
|
41
41
|
}
|
|
42
42
|
function SearchHighlight({ text, query, className, markClassName, ...props }) {
|
|
43
43
|
const segments = useSearchHighlight(text, query);
|
|
44
|
-
return (_jsx("span", { className: className, ...props, children: segments.map((segment, index) => segment.matched ? (_jsx("mark", { className: cn("rounded-[2px] bg-
|
|
44
|
+
return (_jsx("span", { className: className, ...props, children: segments.map((segment, index) => segment.matched ? (_jsx("mark", { className: cn("rounded-[2px] bg-primary/10 px-0.5 font-medium text-primary dark:bg-primary/50 dark:text-foreground dark:ring-1 dark:ring-inset dark:ring-[hsl(var(--status-success-foreground)/0.55)]", markClassName), children: segment.text }, `${segment.text}-${index}`)) : (_jsx(React.Fragment, { children: segment.text }, `${segment.text}-${index}`))) }));
|
|
45
45
|
}
|
|
46
46
|
export { SearchHighlight, useSearchHighlight };
|