@neasg/design-system 0.4.21 → 0.4.23

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.
@@ -21,13 +21,13 @@ function PopoverMenuSection({ className, children, ...props }) {
21
21
  return (_jsx("div", { className: cn("space-y-0", className), ...props, children: children }));
22
22
  }
23
23
  function PopoverMenuItem({ className, children, active, type = "button", ...props }) {
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
+ 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 aria-[selected=true]:bg-highlight aria-[selected=true]:text-highlight-foreground aria-[selected=true]:[--muted-foreground:var(--instructional-foreground)] aria-[selected=true]:hover:bg-highlight/80 aria-[selected=true]:hover:text-highlight-foreground aria-[selected=true]:focus-visible:bg-highlight/80 aria-[selected=true]:focus-visible:text-highlight-foreground", popoverMenuItemFocusClassName, active &&
25
+ "bg-highlight text-highlight-foreground [--muted-foreground:var(--instructional-foreground)] hover:bg-highlight/80 hover:text-highlight-foreground focus-visible:bg-highlight/80 focus-visible:text-highlight-foreground", className), ...props, children: [children, active ? _jsx(Check, { "aria-hidden": "true", className: "ml-auto h-4 w-4 shrink-0" }) : null] }));
26
26
  }
27
27
  function PopoverMenuCheckboxItem({ className, children, checked = false, description, disabled, onChange, readOnly, ...props }) {
28
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 &&
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] })] }));
29
+ "bg-highlight text-highlight-foreground hover:bg-highlight/80 hover:text-highlight-foreground focus-within:bg-highlight/80 focus-within:text-highlight-foreground", disabled &&
30
+ "cursor-not-allowed opacity-50 hover:bg-transparent hover:text-inherit", className), children: [_jsx(Checkbox, { ...props, "data-slot": "popover-menu-item", 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] })] }));
31
31
  }
32
32
  function PopoverMenuDivider({ className, ...props }) {
33
33
  return _jsx("div", { className: cn("-mx-1 my-1 h-px bg-border", className), ...props });
@@ -49,7 +49,7 @@ function PopoverMenuGroupLabel({ className, children, ...props }) {
49
49
  }
50
50
  const PopoverMenuSearch = React.forwardRef(({ "aria-label": ariaLabel, className, containerClassName, label, placeholder, ...props }, ref) => {
51
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 }) }));
52
+ return (_jsx("div", { className: cn("relative -mx-1 -mt-1 mb-1 px-1 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
53
  });
54
54
  PopoverMenuSearch.displayName = "PopoverMenuSearch";
55
55
  function PopoverMenuScrollArea({ className, children, maxHeight = "max-h-64", ...props }) {
@@ -62,6 +62,8 @@ function PopoverMenuView({ children, className, direction = "forward", viewKey =
62
62
  key: viewKey,
63
63
  children,
64
64
  });
65
+ const activeViewElementRef = React.useRef(null);
66
+ const previousViewKeyRef = React.useRef(viewKey);
65
67
  const exitTimeoutRef = React.useRef(null);
66
68
  if (Object.is(viewKey, activeViewKey)) {
67
69
  activeViewRef.current = { key: viewKey, children };
@@ -99,12 +101,32 @@ function PopoverMenuView({ children, className, direction = "forward", viewKey =
99
101
  }
100
102
  };
101
103
  }, [direction, viewKey]);
104
+ useIsomorphicLayoutEffect(() => {
105
+ var _a;
106
+ if (Object.is(previousViewKeyRef.current, viewKey)) {
107
+ return;
108
+ }
109
+ previousViewKeyRef.current = viewKey;
110
+ const firstControl = (_a = activeViewElementRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('button:not(:disabled), input:not(:disabled):not([type="hidden"]), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"])');
111
+ firstControl === null || firstControl === void 0 ? void 0 : firstControl.focus({ preventScroll: true });
112
+ }, [viewKey]);
102
113
  const activeDirection = exitingView === null || exitingView === void 0 ? void 0 : exitingView.direction;
103
114
  const shouldAnimateActive = Boolean(activeDirection);
104
- return (_jsxs("div", { className: cn("grid min-w-0 overflow-hidden", className), ...props, children: [exitingView ? (_jsx("div", { "aria-hidden": "true", className: cn("pointer-events-none col-start-1 row-start-1 min-w-0", exitingView.direction !== "none" &&
115
+ const setExitingViewElement = React.useCallback((element) => {
116
+ if (!element) {
117
+ return;
118
+ }
119
+ element.inert = true;
120
+ element
121
+ .querySelectorAll('a[href], button, input, select, textarea, [contenteditable="true"], [tabindex]')
122
+ .forEach((control) => {
123
+ control.tabIndex = -1;
124
+ });
125
+ }, []);
126
+ return (_jsxs("div", { className: cn("grid min-w-0 overflow-hidden", className), ...props, children: [exitingView ? (_jsx("div", { ref: setExitingViewElement, "aria-hidden": "true", inert: "", "data-popover-menu-view": "exiting", className: cn("pointer-events-none col-start-1 row-start-1 min-w-0", exitingView.direction !== "none" &&
105
127
  "motion-safe:animate-out motion-safe:fade-out-0 motion-safe:duration-300 motion-reduce:animate-none", exitingView.direction === "forward" &&
106
128
  "motion-safe:slide-out-to-left-full", exitingView.direction === "backward" &&
107
- "motion-safe:slide-out-to-right-full"), children: exitingView.children }, `leaving-${exitingView.key}`)) : null, _jsx("div", { className: cn("col-start-1 row-start-1 min-w-0", shouldAnimateActive &&
129
+ "motion-safe:slide-out-to-right-full"), children: exitingView.children }, `leaving-${exitingView.key}`)) : null, _jsx("div", { ref: activeViewElementRef, "data-popover-menu-view": "active", className: cn("col-start-1 row-start-1 min-w-0", shouldAnimateActive &&
108
130
  "motion-safe:animate-in motion-safe:fade-in-0 motion-safe:duration-300 motion-reduce:animate-none", activeDirection === "forward" &&
109
131
  "motion-safe:slide-in-from-right-full", activeDirection === "backward" &&
110
132
  "motion-safe:slide-in-from-left-full"), children: children }, viewKey)] }));
package/dist/popover.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import * as PopoverPrimitive from "@radix-ui/react-popover";
3
+ import { type PopoverTabBehavior } from "./popover-focus";
3
4
  import { type TooltipProps } from "./tooltip";
4
5
  import { Typography } from "./typography";
5
6
  type PopoverRootProps = React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Root>;
@@ -10,13 +11,15 @@ export interface PopoverProps extends Pick<PopoverRootProps, "defaultOpen" | "mo
10
11
  /** Visible popover heading. Also labels the popover for assistive technology. */
11
12
  title?: React.ReactNode;
12
13
  surface?: "panel" | "menu";
14
+ /** Controls whether Tab stays within the popover or closes it and continues in the page Tab order. */
15
+ tabBehavior?: PopoverTabBehavior;
13
16
  contentClassName?: string;
14
17
  triggerTooltip?: React.ReactNode;
15
18
  triggerTooltipSide?: TooltipProps["side"];
16
19
  triggerTooltipSideOffset?: TooltipProps["sideOffset"];
17
20
  triggerTooltipClassName?: TooltipProps["contentClassName"];
18
21
  }
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;
22
+ declare function Popover({ defaultOpen, modal, onOpenChange, open, trigger, children, title, surface, tabBehavior, contentClassName, triggerTooltip, triggerTooltipSide, triggerTooltipSideOffset, triggerTooltipClassName, align, sideOffset, ...contentProps }: PopoverProps): import("react/jsx-runtime").JSX.Element;
20
23
  export interface PopoverTitleProps extends Omit<React.ComponentPropsWithoutRef<typeof Typography>, "as" | "asChild" | "variant" | "color"> {
21
24
  }
22
25
  declare function PopoverTitle({ className, children, ...props }: PopoverTitleProps): import("react/jsx-runtime").JSX.Element;
package/dist/popover.js CHANGED
@@ -3,13 +3,33 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { cn } from "./lib/utils";
5
5
  import { PopoverContent, PopoverRoot, PopoverTrigger, } from "./popover-primitive";
6
+ import { usePopoverTabExit, } from "./popover-focus";
6
7
  import { Tooltip } from "./tooltip";
7
8
  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
+ function Popover({ defaultOpen, modal, onOpenChange, open, trigger, children, title, surface = "panel", tabBehavior = "contain", contentClassName, triggerTooltip, triggerTooltipSide, triggerTooltipSideOffset, triggerTooltipClassName, align = "center", sideOffset = 4, ...contentProps }) {
9
10
  const generatedTitleId = React.useId();
10
11
  const titleId = title ? `popover-title-${generatedTitleId}` : undefined;
11
- const triggerElement = _jsx(PopoverTrigger, { asChild: true, children: trigger });
12
+ const [internalOpen, setInternalOpen] = React.useState(defaultOpen !== null && defaultOpen !== void 0 ? defaultOpen : false);
13
+ const isControlled = open !== undefined;
14
+ const currentOpen = isControlled ? open : internalOpen;
15
+ const triggerRef = React.useRef(null);
16
+ const contentRef = React.useRef(null);
17
+ const setOpen = React.useCallback((nextOpen) => {
18
+ if (!isControlled) {
19
+ setInternalOpen(nextOpen);
20
+ }
21
+ onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(nextOpen);
22
+ }, [isControlled, onOpenChange]);
23
+ const { handleCloseAutoFocus, handleTabKeyDown } = usePopoverTabExit({
24
+ behavior: tabBehavior,
25
+ triggerRef,
26
+ contentRef,
27
+ onExit: () => setOpen(false),
28
+ });
29
+ const triggerElement = (_jsx(PopoverTrigger, { ref: triggerRef, asChild: true, children: trigger }));
12
30
  const contentOnKeyDown = contentProps.onKeyDown;
31
+ const contentOnKeyDownCapture = contentProps.onKeyDownCapture;
32
+ const contentOnCloseAutoFocus = contentProps.onCloseAutoFocus;
13
33
  const handleContentKeyDown = React.useCallback((event) => {
14
34
  contentOnKeyDown === null || contentOnKeyDown === void 0 ? void 0 : contentOnKeyDown(event);
15
35
  if (event.defaultPrevented ||
@@ -51,7 +71,15 @@ function Popover({ defaultOpen, modal, onOpenChange, open, trigger, children, ti
51
71
  event.preventDefault();
52
72
  focusTarget.focus({ preventScroll: true });
53
73
  }, [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"
74
+ const handleContentKeyDownCapture = React.useCallback((event) => {
75
+ contentOnKeyDownCapture === null || contentOnKeyDownCapture === void 0 ? void 0 : contentOnKeyDownCapture(event);
76
+ handleTabKeyDown(event);
77
+ }, [contentOnKeyDownCapture, handleTabKeyDown]);
78
+ const handleContentCloseAutoFocus = React.useCallback((event) => {
79
+ contentOnCloseAutoFocus === null || contentOnCloseAutoFocus === void 0 ? void 0 : contentOnCloseAutoFocus(event);
80
+ handleCloseAutoFocus(event);
81
+ }, [contentOnCloseAutoFocus, handleCloseAutoFocus]);
82
+ return (_jsxs(PopoverRoot, { modal: modal, onOpenChange: setOpen, open: currentOpen, children: [triggerTooltip ? (_jsx(Tooltip, { content: triggerTooltip, side: triggerTooltipSide, sideOffset: triggerTooltipSideOffset, contentClassName: triggerTooltipClassName, children: triggerElement })) : (triggerElement), _jsxs(PopoverContent, { ref: contentRef, 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, onCloseAutoFocus: handleContentCloseAutoFocus, onKeyDown: handleContentKeyDown, onKeyDownCapture: handleContentKeyDownCapture, children: [title ? (_jsx(PopoverTitle, { id: titleId, className: cn(surface === "menu"
55
83
  ? "px-2 py-1.5"
56
84
  : "-mx-4 -mt-4 mb-4 border-b px-4 py-3"), children: title })) : null, children] })] }));
57
85
  }
@@ -390,5 +390,5 @@ export function RichTextEditor({ placeholder = "Type message here...", editorAri
390
390
  }, children: color.value === "" && (_jsx("span", { className: "text-xs text-muted-foreground", children: "A" })) }, `bg-${color.name}`))) }) }), _jsx(ToolbarIconButton, { label: "Bullet list", active: editor === null || editor === void 0 ? void 0 : editor.isActive("bulletList"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleBulletList().run(), children: _jsx(ListIcon, { size: 14 }) }), _jsx(ToolbarIconButton, { label: "Ordered list", active: editor === null || editor === void 0 ? void 0 : editor.isActive("orderedList"), onClick: () => editor === null || editor === void 0 ? void 0 : editor.chain().focus().toggleOrderedList().run(), children: _jsx(ListOrderedIcon, { size: 14 }) }), _jsx(Tooltip, { content: `Attach files (max ${maxFileSizeLabel}) - ${acceptedFileTypesLabel}`, side: "top", contentClassName: "max-w-[240px] text-center", children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "h-8 w-8", onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, "aria-label": "Attach files", children: _jsx(PaperclipIcon, { size: 14 }) }) }), _jsx(ToolbarIconButton, { label: "Add link", active: editor === null || editor === void 0 ? void 0 : editor.isActive("link"), onClick: openLinkMenu, children: _jsx(LinkIcon, { size: 14 }) }), showTemplates && templates.length > 0 && (_jsx(_Fragment, { children: _jsxs(Popover, { open: templatePopoverOpen, onOpenChange: setTemplatePopoverOpen, align: "end", contentClassName: "w-96 p-0", trigger: _jsxs(Button, { type: "button", variant: "ghost", className: "h-8 gap-1 text-muted-foreground", children: ["Template", _jsx(ChevronDownIcon, { size: 14 })] }), children: [_jsx("div", { className: "max-h-80 overflow-y-auto", children: templatesByCategory.map(({ category, templates }) => {
391
391
  var _a;
392
392
  return (_jsxs("div", { children: [_jsx("div", { className: "sticky top-0 bg-muted px-3 py-2 text-sm font-semibold text-foreground", children: (_a = templateCategoryLabels[category]) !== null && _a !== void 0 ? _a : category }), templates.map((template) => (_jsxs("button", { type: "button", className: "w-full border-b border-border/50 px-3 py-2 text-left text-sm transition-colors last:border-0 hover:bg-muted", onClick: () => applyTemplate(template), children: [_jsx("div", { className: "truncate font-medium", children: template.name }), template.description && (_jsx("div", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: template.description }))] }, template.id)))] }, category));
393
- }) }), templateCreateHref && (_jsx("div", { className: "border-t p-2", children: _jsxs("a", { href: templateCreateHref, target: "_blank", rel: "noopener noreferrer", className: "flex w-full items-center justify-center gap-1.5 rounded-md px-3 py-2 text-sm text-primary transition-colors hover:bg-muted", children: [_jsx(PlusIcon, { size: 16 }), templateCreateLabel] }) }))] }) }))] }) }), _jsx("div", { className: "bg-muted px-3 pb-2 pt-1 dark:bg-background", children: sendAction })] }));
393
+ }) }), templateCreateHref && (_jsx("div", { className: "border-t p-2", children: _jsxs("a", { href: templateCreateHref, target: "_blank", rel: "noopener noreferrer", className: "flex w-full items-center justify-center gap-1.5 rounded-md px-3 py-2 text-sm text-interactive transition-colors hover:bg-muted", children: [_jsx(PlusIcon, { size: 16 }), templateCreateLabel] }) }))] }) }))] }) }), _jsx("div", { className: "bg-muted px-3 pb-2 pt-1 dark:bg-background", children: sendAction })] }));
394
394
  }
@@ -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 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] })] })));
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-highlight data-[state=checked]:text-highlight-foreground data-[state=checked]:hover:bg-highlight/80 data-[state=checked]:hover:text-highlight-foreground data-[state=checked]:focus:bg-highlight/80 data-[state=checked]:focus:text-highlight-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] })] })));
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
@@ -4,6 +4,7 @@ import * as React from "react";
4
4
  import { Field, FieldDescription, FieldError, FieldLabel } from "./field";
5
5
  import { InputControl } from "./input-control";
6
6
  import { cn } from "./lib/utils";
7
+ import { usePopoverTabExit } from "./popover-focus";
7
8
  import { SearchHighlight } from "./use-search-highlight";
8
9
  import { SelectContent, SelectGroup, SelectItem, SelectLabel, SelectRoot, SelectSeparator, SelectTrigger, SelectValue, } from "./select-primitive";
9
10
  function groupOptions(options) {
@@ -42,6 +43,7 @@ function renderHighlightedLabel(label, query) {
42
43
  function Select({ label, options, placeholder = "Select an option", description, error, invalid = false, hideLabel = false, searchable = false, searchPlaceholder = "Search options...", searchEmptyMessage = "No options found.", searchInputAriaLabel = "Search options", className, triggerClassName, contentClassName, labelClassName, descriptionClassName, errorClassName, id, disabled, open, onOpenChange, ...props }) {
43
44
  const generatedId = React.useId();
44
45
  const selectId = id !== null && id !== void 0 ? id : `select-${generatedId}`;
46
+ const triggerRef = React.useRef(null);
45
47
  const searchInputRef = React.useRef(null);
46
48
  const contentRef = React.useRef(null);
47
49
  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);
@@ -63,6 +65,22 @@ function Select({ label, options, placeholder = "Select an option", description,
63
65
  : options;
64
66
  const optionGroups = groupOptions(filteredOptions);
65
67
  const hasVisibleOptions = filteredOptions.length > 0;
68
+ const handleOpenChange = React.useCallback((nextOpen) => {
69
+ if (open === undefined) {
70
+ setUncontrolledOpen(nextOpen);
71
+ }
72
+ if (!nextOpen) {
73
+ setSearchQuery("");
74
+ }
75
+ onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(nextOpen);
76
+ }, [onOpenChange, open]);
77
+ const closeForTab = React.useCallback(() => handleOpenChange(false), [handleOpenChange]);
78
+ const { handleCloseAutoFocus, handleTabKeyDown } = usePopoverTabExit({
79
+ behavior: "close",
80
+ triggerRef,
81
+ contentRef,
82
+ onExit: closeForTab,
83
+ });
66
84
  const focusSearchInput = React.useCallback(() => {
67
85
  const input = searchInputRef.current;
68
86
  if (!input || document.activeElement === input) {
@@ -82,6 +100,9 @@ function Select({ label, options, placeholder = "Select an option", description,
82
100
  option === null || option === void 0 ? void 0 : option.focus({ preventScroll: true });
83
101
  }, [getEnabledOptionElements]);
84
102
  const handleContentKeyDownCapture = React.useCallback((event) => {
103
+ if (handleTabKeyDown(event)) {
104
+ return;
105
+ }
85
106
  if (event.key !== "ArrowUp") {
86
107
  return;
87
108
  }
@@ -94,7 +115,12 @@ function Select({ label, options, placeholder = "Select an option", description,
94
115
  if (searchable) {
95
116
  focusSearchInput();
96
117
  }
97
- }, [focusSearchInput, getEnabledOptionElements, searchable]);
118
+ }, [
119
+ focusSearchInput,
120
+ getEnabledOptionElements,
121
+ handleTabKeyDown,
122
+ searchable,
123
+ ]);
98
124
  React.useEffect(() => {
99
125
  if (!isOpen) {
100
126
  setSearchQuery("");
@@ -127,16 +153,7 @@ function Select({ label, options, placeholder = "Select an option", description,
127
153
  searchable,
128
154
  searchQuery,
129
155
  ]);
130
- const handleOpenChange = React.useCallback((nextOpen) => {
131
- if (open === undefined) {
132
- setUncontrolledOpen(nextOpen);
133
- }
134
- if (!nextOpen) {
135
- setSearchQuery("");
136
- }
137
- onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(nextOpen);
138
- }, [onOpenChange, open]);
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) => {
156
+ 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: isOpen, onOpenChange: handleOpenChange, ...props, children: [_jsx(SelectTrigger, { ref: triggerRef, 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, onCloseAutoFocus: handleCloseAutoFocus, 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
157
  if (event.key === "ArrowDown" ||
141
158
  event.key === "ArrowUp") {
142
159
  event.preventDefault();
package/dist/spinner.js CHANGED
@@ -8,7 +8,7 @@ const SIZE_CLASS = {
8
8
  lg: "h-10 w-10 border-[3px]",
9
9
  };
10
10
  function Spinner({ size = "md", label = "Loading", className, ...props }) {
11
- return (_jsxs("div", { role: "status", "aria-live": "polite", className: cn("inline-flex items-center justify-center", className), ...props, children: [_jsx("span", { "aria-hidden": "true", className: cn("shrink-0 animate-spin rounded-full border-current border-t-transparent text-primary", SIZE_CLASS[size]) }), _jsx("span", { className: "sr-only", children: label })] }));
11
+ return (_jsxs("div", { role: "status", "aria-live": "polite", className: cn("inline-flex items-center justify-center", className), ...props, children: [_jsx("span", { "aria-hidden": "true", className: cn("shrink-0 animate-spin rounded-full border-current border-t-transparent text-interactive", SIZE_CLASS[size]) }), _jsx("span", { className: "sr-only", children: label })] }));
12
12
  }
13
13
  function LoaderStack({ size, label, message, description, }) {
14
14
  return (_jsxs(_Fragment, { children: [_jsx("div", { className: "mb-4", children: _jsx(Spinner, { size: size, label: label }) }), message || description ? (_jsxs("div", { className: "space-y-1.5", children: [message ? (_jsx(Typography, { as: "p", variant: "bodySm", className: "font-semibold", children: message })) : null, description ? (_jsx(Typography, { as: "p", variant: "caption", className: "max-w-md", children: description })) : null] })) : null] }));
package/dist/styles.css CHANGED
@@ -171,6 +171,15 @@
171
171
 
172
172
  --primary: var(--nea-green-700);
173
173
  --primary-foreground: var(--nea-neutral-0);
174
+ --interactive-foreground: var(--nea-green-800);
175
+ --highlight-background: var(--nea-green-100);
176
+ --highlight-foreground: var(--nea-green-900);
177
+ --button-primary-background: var(--nea-green-800);
178
+ --button-primary-hover-background: var(--nea-green-900);
179
+ --button-primary-foreground: var(--nea-neutral-0);
180
+ --count-badge-background: var(--nea-green-800);
181
+ --count-badge-foreground: var(--nea-neutral-0);
182
+ --instructional-foreground: 215 16% 40%;
174
183
 
175
184
  --secondary: var(--nea-neutral-100);
176
185
  --secondary-foreground: var(--nea-neutral-900);
@@ -296,6 +305,15 @@
296
305
 
297
306
  --primary: 148 30% 38%;
298
307
  --primary-foreground: var(--nea-dark-neutral-50);
308
+ --interactive-foreground: 143 44% 67%;
309
+ --highlight-background: 148 20% 18%;
310
+ --highlight-foreground: 143 44% 67%;
311
+ --button-primary-background: 148 30% 38%;
312
+ --button-primary-hover-background: 148 30% 39%;
313
+ --button-primary-foreground: 0 0% 100%;
314
+ --count-badge-background: 148 24% 24%;
315
+ --count-badge-foreground: 143 44% 67%;
316
+ --instructional-foreground: var(--nea-dark-neutral-200);
299
317
 
300
318
  --secondary: var(--nea-dark-neutral-500);
301
319
  --secondary-foreground: var(--nea-dark-neutral-100);
package/dist/table.d.ts CHANGED
@@ -30,6 +30,14 @@ declare const TableCell: React.ForwardRefExoticComponent<React.TdHTMLAttributes<
30
30
  treatWidthAsMinWidth?: boolean;
31
31
  containOverflow?: boolean;
32
32
  } & React.RefAttributes<HTMLTableCellElement>>;
33
+ declare const TableRowHeader: React.ForwardRefExoticComponent<React.ThHTMLAttributes<HTMLTableCellElement> & {
34
+ width?: string;
35
+ minWidth?: string;
36
+ maxWidth?: string;
37
+ wrap?: boolean;
38
+ treatWidthAsMinWidth?: boolean;
39
+ containOverflow?: boolean;
40
+ } & React.RefAttributes<HTMLTableCellElement>>;
33
41
  declare const TableCaption: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableCaptionElement> & React.RefAttributes<HTMLTableCaptionElement>>;
34
42
  type TableAlign = "left" | "center" | "right";
35
43
  export type TableRowTone = "default" | "muted" | "warning";
@@ -85,13 +93,15 @@ export interface TableColumn<Row> {
85
93
  skeleton?: TableSkeletonConfig;
86
94
  headerClassName?: string;
87
95
  cellClassName?: string;
96
+ /** Renders this column's body cells as semantic row headers. */
97
+ rowHeader?: boolean;
88
98
  /** Pins the column to the left or right edge while the table scrolls horizontally. */
89
99
  sticky?: "left" | "right";
90
100
  /** Enables a resize handle for this column. Use `resizableColumns` on Table to opt every column in by default. */
91
101
  resizable?: boolean;
92
102
  /** Minimum pixel width while resizing. Defaults to `minWidth`, then 64. */
93
103
  resizeMinWidth?: number;
94
- /** Maximum pixel width while resizing. Omit to allow the column to grow. */
104
+ /** Maximum pixel width while resizing. Defaults to 1200. */
95
105
  resizeMaxWidth?: number;
96
106
  }
97
107
  export interface TableSortingProps {
@@ -137,6 +147,8 @@ export interface TableColumnResizeHandleRenderProps<Row> {
137
147
  handle: React.ReactElement;
138
148
  }
139
149
  export interface TableProps<Row> extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
150
+ /** Accessible name used as a visually hidden caption when `caption` is omitted. */
151
+ accessibleLabel: string;
140
152
  columns: TableColumn<Row>[];
141
153
  rows: Row[];
142
154
  getRowKey: (row: Row, index: number) => string;
@@ -155,6 +167,8 @@ export interface TableProps<Row> extends Omit<React.HTMLAttributes<HTMLDivElemen
155
167
  sorting?: TableSortingProps;
156
168
  pagination?: Omit<PaginationProps, "className">;
157
169
  loading?: boolean;
170
+ /** Status announced while loading. */
171
+ loadingLabel?: string;
158
172
  loadingRows?: number;
159
173
  /** Optional controlled ordered list of visible column keys. Omit to render every column in config order. */
160
174
  visibleColumnKeys?: readonly string[];
@@ -180,8 +194,6 @@ export interface TableProps<Row> extends Omit<React.HTMLAttributes<HTMLDivElemen
180
194
  striped?: boolean;
181
195
  /** Adds a disclosure column and full-width expandable content for nested tables or row details. */
182
196
  rowExpansion?: TableRowExpansionProps<Row>;
183
- /** When set, rows become interactive: cursor-pointer, hover highlight, and click handling. */
184
- onRowClick?: (row: Row, index: number) => void;
185
197
  }
186
- declare function Table<Row>({ columns, rows, getRowKey, emptyState, toolbar, caption, className, containerClassName, tableClassName, rowClassName, rowTone, maxHeight, minHeight, sorting, pagination, loading, loadingRows, visibleColumnKeys, resizableColumns, columnResizeMode, columnWidths, defaultColumnWidths, onColumnWidthsChange, renderColumnResizeHandle, tableLayout, centerEmptyState, emptyStateMinHeight, striped, rowExpansion, onRowClick, onScroll, style, ...props }: TableProps<Row>): import("react/jsx-runtime").JSX.Element;
187
- export { Table, TableRoot, TableContainer, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, SortableTableHead, TableRowSkeleton, };
198
+ declare function Table<Row>({ accessibleLabel, columns, rows, getRowKey, emptyState, toolbar, caption, className, containerClassName, tableClassName, rowClassName, rowTone, maxHeight, minHeight, sorting, pagination, loading, loadingLabel, loadingRows, visibleColumnKeys, resizableColumns, columnResizeMode, columnWidths, defaultColumnWidths, onColumnWidthsChange, renderColumnResizeHandle, tableLayout, centerEmptyState, emptyStateMinHeight, striped, rowExpansion, onScroll, style, ...props }: TableProps<Row>): import("react/jsx-runtime").JSX.Element;
199
+ export { Table, TableRoot, TableContainer, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableRowHeader, TableCaption, SortableTableHead, TableRowSkeleton, };