UNPKG

@trail-ui/react

Version:
673 lines (660 loc) 27.4 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/multiselect/index.ts var multiselect_exports = {}; __export(multiselect_exports, { MultiSelect: () => MultiSelect, MultiSelectField: () => MultiSelectField, MultiSelectItem: () => MultiSelectItem }); module.exports = __toCommonJS(multiselect_exports); // src/multiselect/multiselect.tsx var import_react6 = require("react"); var import_react_aria_components7 = require("react-aria-components"); var import_react_stately = require("react-stately"); var import_react_aria = require("react-aria"); // src/multiselect/tw-field.tsx var import_react3 = __toESM(require("react")); var import_react_aria_components3 = require("react-aria-components"); var import_tailwind_merge4 = require("tailwind-merge"); // src/multiselect/tw-utils.ts var import_react_aria_components = require("react-aria-components"); var import_tailwind_merge = require("tailwind-merge"); function composeTailwindRenderProps(className, tw) { return (0, import_react_aria_components.composeRenderProps)(className, (className2) => (0, import_tailwind_merge.twMerge)(tw, className2)); } var focusOutlineStyle = ["outline outline-2 outline-blue-500 outline-offset-2"]; var inputFieldStyle = [ "group flex flex-col", "[&_[data-slot=label]:not([class*=mb-])]:mb-1", "[&_[data-slot=description]:not([class*=mb-]):has(+:is(input,textarea,[data-slot=control]))]:mb-2", "[&_:is(input,textarea,[data-slot=control])+[data-slot=description]:not([class*=mt-])]:mt-1" ]; // src/multiselect/tw-slot.tsx var import_react = __toESM(require("react")); var import_tailwind_merge2 = require("tailwind-merge"); function Slot({ children, ...props }) { if ("asChild" in props) { delete props.asChild; } if (import_react.default.isValidElement(children)) { return import_react.default.cloneElement(children, { ...props, ...children.props, style: { ...props.style, ...children.props.style }, className: (0, import_tailwind_merge2.twMerge)(props.className, children.props.className) }); } if (import_react.default.Children.count(children) > 1) { import_react.default.Children.only(null); } return null; } // src/multiselect/tw-button.tsx var import_react2 = __toESM(require("react")); var import_react_aria_components2 = require("react-aria-components"); var import_tailwind_merge3 = require("tailwind-merge"); var import_jsx_runtime = require("react/jsx-runtime"); function buttonStyle({ size, color, iconOnly, ...props }) { if (props.unstyle) { return "relative outline-none rounded-lg"; } return [ // Base "group relative inline-flex justify-center items-center whitespace-nowrap rounded-lg outline-none", // Disabled "disabled:opacity-50", // Size iconOnly ? [ // default size "p-1.5 size-9", size === "sm" && "p-1 size-7", size === "lg" && "size-10" ] : [ // default size "h-9 px-3 gap-x-2 text-base/6 sm:text-sm/6 font-semibold", size === "sm" && "h-7 px-2 gap-x-1 text-sm/6 sm:text-xs/6", size === "lg" && "h-10 px-4 gap-x-2" ], // Color color === "accent" && "text-accent", color === "success" && "text-success", color === "destructive" && "text-destructive", // Variant props.outline && [ "border border-foreground/10 hover:border-foreground/15 dark:border-foreground/15 hover:dark:border-foreground/10 bg-transparent shadow-sm hover:bg-hover/95 pressed:bg-hover" ], props.plain && ["bg-transparent hover:bg-hover/95 pressed:bg-hover"], props.outline === void 0 && props.plain === void 0 && [ "border dark:border-0", "shadow-[inset_0_1px_0_0_rgba(255,255,255,0.1)]", "border-accent bg-accent text-white hover:bg-accent/90", color === "success" && "border-success bg-success hover:bg-success/90", color === "destructive" && "border-destructive bg-destructive hover:bg-destructive/90" ], // Set svg size when the icon does not have a size set "[&.h-7_svg:not([class*=size-])]:size-3 [&.size-7_svg:not([class*=size-])]:size-4", "[&.h-9_svg:not([class*=size-])]:size-4 [&.size-9_svg:not([class*=size-])]:size-5", "[&.h-10_svg:not([class*=size-])]:size-5 [&.size-10_svg::not([class*=size-])]:size-6", // Set svg color when the icon does not have color set // outline "[&.border.bg-transparent:not(:hover)_svg:not([class*=text-])]:text-muted", // not outline && plain iconOnly ? "[&:not(.bg-transparent):not(:hover)_svg:not([class*=text-])]:text-white/90" : "[&:not(.bg-transparent):not(:hover)_svg:not([class*=text-])]:text-white/75" ]; } var Button = import_react2.default.forwardRef(function Button2(props, ref) { if (props.asChild) { const { size: size2, color: color2, plain: plain2, unstyle: unstyle2, outline: outline2, iconOnly: iconOnly2, ...slotProps } = props; return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( Slot, { ...slotProps, className: (0, import_tailwind_merge3.twMerge)( buttonStyle({ size: size2, color: color2, iconOnly: iconOnly2, ...{ unstyle: unstyle2, outline: outline2, plain: plain2 } }) ) } ); } const { children, isLoading, loadingLabel, size, color, plain, unstyle, outline, iconOnly, ...buttonProps } = props; return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_react_aria_components2.Button, { ...buttonProps, ref, className: (0, import_react_aria_components2.composeRenderProps)(props.className, (className, renderProps) => { return (0, import_tailwind_merge3.twMerge)( buttonStyle({ size, color, iconOnly, ...{ unstyle, outline, plain } }), renderProps.isFocusVisible && focusOutlineStyle, isLoading && "text-transparent", className ); }), children: (renderProps) => { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute flex h-full items-center justify-center" }) : null, typeof children === "function" ? children(renderProps) : children ] }); } } ), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-live": "polite", className: "sr-only", children: isLoading ? loadingLabel : "" }) ] }); }); // src/multiselect/tw-field.tsx var import_jsx_runtime2 = require("react/jsx-runtime"); var LabeledGroup = import_react3.default.forwardRef(function(props, ref) { const labelId = import_react3.default.useId(); return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components3.LabelContext.Provider, { value: { id: labelId, elementType: "span" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_aria_components3.GroupContext.Provider, { value: { "aria-labelledby": labelId }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( import_react_aria_components3.Group, { ...props, ref, className: (0, import_react_aria_components3.composeRenderProps)(props.className, (className) => { return (0, import_tailwind_merge4.twMerge)("relative flex flex-col", className); }) } ) }) }); }); function WithLabelContext({ children }) { var _a; const context = (_a = import_react3.default.useContext(import_react_aria_components3.LabelContext)) != null ? _a : {}; return children({ "aria-labelledBy": context == null ? void 0 : context.id }); } var DescriptionContext = import_react3.default.createContext(null); function WithDescriptionContext({ children }) { const context = import_react3.default.useContext(DescriptionContext); return children(context); } function DescriptionProvider({ children }) { const descriptionId = import_react3.default.useId(); const [descriptionRendered, setDescriptionRendered] = import_react3.default.useState(true); import_react3.default.useLayoutEffect(() => { if (!document.getElementById(descriptionId)) { setDescriptionRendered(false); } }, [descriptionId]); return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( DescriptionContext.Provider, { value: { "aria-describedby": descriptionRendered ? descriptionId : void 0 }, children } ); } var InputFieldGroup = import_react3.default.forwardRef( function InputFieldGroup2(props, ref) { return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( import_react_aria_components3.Group, { ...props, "data-slot": "control", ref, className: (0, import_react_aria_components3.composeRenderProps)(props.className, (className) => { return (0, import_tailwind_merge4.twMerge)( "group relative flex w-full items-center overflow-hidden rounded-md border bg-inherit shadow-sm", "[&_svg]:text-muted", "group-invalid:border-destructive", // Disabled and readonly style "[&:has(_[data-disabled=true])]:opacity-50", "[&:has([readonly])]:opacity-50", // Prevent double opacity "[&:has(_[data-disabled=true])_[class*=opacity-]]:opacity-100", "[&:has([readonly])_[class*=opacity-]]:opacity-100", // Remove inside input/data-input border style "[&_:is(input,[data-slot=control])]:border-none", "[&_:is(input,[data-slot=control])]:shadow-none", "[&_:is(input,[data-slot=control])]:ring-0", className ); }) } ); } ); var Input = import_react3.default.forwardRef(function Input2(props, ref) { return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( import_react_aria_components3.Input, { ...props, ref, className: (0, import_react_aria_components3.composeRenderProps)(props.className, (className, { isDisabled, isInvalid }) => { return (0, import_tailwind_merge4.twMerge)( "flex w-full rounded-md border bg-inherit px-2 py-[5px] shadow-sm outline-none", "text-sm opacity-60 placeholder:text-neutral-900", "[&[readonly]]:opacity-50", isInvalid && "border-destructive", isDisabled && "opacity-50", className ); }) } ); }); // src/multiselect/tw-popover.tsx var import_react_aria_components4 = require("react-aria-components"); var import_tailwind_merge5 = require("tailwind-merge"); var import_jsx_runtime3 = require("react/jsx-runtime"); function Popover({ children, className, ...props }) { const popoverContext = (0, import_react_aria_components4.useSlottedContext)(import_react_aria_components4.PopoverContext); const isSubmenu = (popoverContext == null ? void 0 : popoverContext.trigger) === "SubmenuTrigger"; let offset = 8; offset = props.offset !== void 0 ? props.offset : isSubmenu ? offset - 14 : offset; return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( import_react_aria_components4.Popover, { ...props, offset, className: (0, import_react_aria_components4.composeRenderProps)(className, (className2, renderProps) => { return (0, import_tailwind_merge5.twMerge)( "ring-foreground/10 dark:ring-foreground/15 rounded text-[12px] shadow-md ring-1 dark:ring-inset", renderProps.isEntering && "duration-50 animate-in fade-in placement-left:slide-in-from-right-1 placement-right:slide-in-from-left-1 placement-top:slide-in-from-bottom-1 placement-bottom:slide-in-from-top-1 ease-out", renderProps.isExiting && "duration-50 animate-out fade-out placement-left:slide-out-to-right-1 placement-right:slide-out-to-left-1 placement-top:slide-out-to-bottom-1 placement-bottom:slide-out-to-top-1 ease-in", className2 ); }), children } ); } // src/multiselect/tw-listbox.tsx var import_react4 = __toESM(require("react")); var import_react_aria_components5 = require("react-aria-components"); var import_tailwind_merge6 = require("tailwind-merge"); var import_jsx_runtime4 = require("react/jsx-runtime"); function ListBox({ children, ...props }) { const ref = import_react4.default.useRef(null); import_react4.default.useEffect(() => { if (ref.current) { const selectedItem = ref.current.querySelector("[aria-selected=true]"); if (selectedItem) { const timer = setTimeout(() => { selectedItem.scrollIntoView({ block: "nearest", behavior: "smooth" }); }, 50); return () => { clearTimeout(timer); }; } } }, []); return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( import_react_aria_components5.ListBox, { ...props, className: composeTailwindRenderProps(props.className, "outline-none"), ref, children } ); } function ListBoxItem(props) { const textValue = props.textValue || (typeof props.children === "string" ? props.children : void 0); return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( import_react_aria_components5.ListBoxItem, { ...props, textValue, className: (0, import_react_aria_components5.composeRenderProps)( props.className, (className, { isDisabled, isFocusVisible }) => { return (0, import_tailwind_merge6.twMerge)( "group relative flex cursor-pointer border-l-[3px] border-transparent px-2 py-1.5 hover:border-purple-600 hover:bg-purple-50", isDisabled && "opacity-50", isFocusVisible && "border-l-[3px] border-purple-600 bg-purple-50", className ); } ), children: props.children } ); } // src/multiselect/multiselect.tsx var import_tailwind_merge8 = require("tailwind-merge"); // src/multiselect/tw-tag-group.tsx var import_react5 = __toESM(require("react")); var import_react_aria_components6 = require("react-aria-components"); var import_tailwind_merge7 = require("tailwind-merge"); var import_icons = require("@trail-ui/icons"); var import_jsx_runtime5 = require("react/jsx-runtime"); var ColorContext = import_react5.default.createContext("default"); function TagGroup({ children, ...props }) { return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_aria_components6.TagGroup, { ...props, className: (0, import_tailwind_merge7.twMerge)("flex flex-col gap-1", props.className), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ColorContext.Provider, { value: props.color || "default", children }) }); } function TagList(props) { return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( import_react_aria_components6.TagList, { ...props, className: composeTailwindRenderProps(props.className, "flex flex-wrap gap-1") } ); } function Tag({ children, ...props }) { const textValue = typeof children === "string" ? children : void 0; return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( import_react_aria_components6.Tag, { ...props, textValue, className: (0, import_react_aria_components6.composeRenderProps)(props.className, (className, renderProps) => { return (0, import_tailwind_merge7.twMerge)( "flex h-6 max-w-fit cursor-default items-center gap-0.5 rounded bg-neutral-200 px-2 text-xs text-neutral-800 transition", renderProps.allowsRemoving && "pe-1", renderProps.isFocusVisible && ["outline-offset-1 outline-purple-600"], className ); }), children: ({ allowsRemoving }) => { return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [ children, allowsRemoving && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( import_react_aria_components6.Button, { slot: "remove", className: "flex cursor-pointer items-center justify-center rounded-full outline-0", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_icons.CloseIcon, { height: 16, width: 16 }) } ) ] }); } } ); } // src/multiselect/multiselect.tsx var import_icons2 = require("@trail-ui/icons"); var import_jsx_runtime6 = require("react/jsx-runtime"); function MultiSelectField({ children, className, ...props }) { return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( LabeledGroup, { role: "presentation", ...props, className: composeTailwindRenderProps(className, inputFieldStyle), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DescriptionProvider, { children }) } ); } function MultiSelect({ children, items, selectedList, label, placeholder, onItemRemove, onItemAdd, className, ...props }) { const { contains } = (0, import_react_aria.useFilter)({ sensitivity: "base" }); const selectedKeys = selectedList.items.map((i) => i.id); const filter = (0, import_react6.useCallback)( (item, filterText) => { return !selectedKeys.includes(item.id) && contains(item.textValue, filterText); }, [contains, selectedKeys] ); const availableList = (0, import_react_stately.useListData)({ initialItems: items, filter }); const [fieldState, setFieldState] = (0, import_react6.useState)({ selectedKey: null, inputValue: "" }); const onRemove = (0, import_react6.useCallback)( (keys) => { const key = keys.values().next().value; selectedList.remove(key); setFieldState({ inputValue: "", selectedKey: null }); onItemRemove == null ? void 0 : onItemRemove(key); }, [selectedList, onItemRemove] ); const onSelectionChange = (id) => { if (!id) { return; } const item = availableList.getItem(id); if (!item) { return; } if (!selectedKeys.includes(id)) { selectedList.append(item); setFieldState({ inputValue: "", selectedKey: id }); onItemAdd == null ? void 0 : onItemAdd(id); } availableList.setFilterText(""); }; const onInputChange = (value) => { setFieldState((prevState) => ({ inputValue: value, selectedKey: value === "" ? null : prevState.selectedKey })); availableList.setFilterText(value); }; const deleteAllTags = (0, import_react6.useCallback)(() => { selectedList.items.forEach((key) => { selectedList.remove(key.id); onItemRemove == null ? void 0 : onItemRemove(key.id); }); }, [selectedList, onItemRemove]); const tagGroupId = (0, import_react6.useId)(); const triggerRef = (0, import_react6.useRef)(null); const triggerButtonRef = (0, import_react6.useRef)(null); const [width, setWidth] = (0, import_react6.useState)(0); (0, import_react6.useEffect)(() => { const trigger = triggerRef.current; if (!trigger) { return; } const observer = new ResizeObserver((entries) => { for (const entry of entries) { setWidth(entry.target.clientWidth); } }); observer.observe(trigger); return () => { observer.unobserve(trigger); }; }, [triggerRef]); return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(WithLabelContext, { children: (labelContext) => { return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(WithDescriptionContext, { children: (descriptionContext) => { var _a; return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_aria_components7.Label, { className: "pb-1 text-sm font-medium", children: label }), /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)( "div", { "data-slot": "control", ref: triggerRef, className: "relative flex min-h-10 w-full flex-row flex-wrap items-center rounded border border-neutral-300 bg-neutral-50 py-1 pl-1 pr-[60px] has-[input[data-focused=true]]:border-2 has-[input[data-hovered=true]]:border-2 has-[input[data-focused=true]]:border-purple-600 data-[pressed=true]:border-purple-600 data-[hovered=true]:bg-neutral-100", children: [ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( TagGroup, { id: tagGroupId, "aria-labelledby": labelContext == null ? void 0 : labelContext["aria-labelledBy"], className: "contents", onRemove, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TagList, { items: selectedList.items, children: (item) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Tag, { textValue: item.textValue, children: item.textValue }) }) } ), /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)( import_react_aria_components7.ComboBox, { ...props, allowsEmptyCollection: true, className: (0, import_tailwind_merge8.twMerge)("group flex flex-1", className), items: availableList.items, selectedKey: fieldState.selectedKey, inputValue: fieldState.inputValue, onSelectionChange, onInputChange, "aria-labelledby": labelContext == null ? void 0 : labelContext["aria-labelledBy"], children: [ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "inline-flex flex-1 flex-wrap items-center gap-1 py-1", children: [ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( Input, { placeholder: selectedList.items.length <= 0 ? placeholder : "", className: "flex-1 border-0 py-0 pl-1 text-neutral-900 shadow-none ring-0", onBlur: () => { setFieldState({ inputValue: "", selectedKey: null }); availableList.setFilterText(""); }, "aria-describedby": [ tagGroupId, (_a = descriptionContext == null ? void 0 : descriptionContext["aria-describedby"]) != null ? _a : "" ].join(" ") } ), /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "sr-only", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Button, { plain: true, ref: triggerButtonRef, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons2.ChevronDownIcon, { height: 16, width: 16 }) }) }) ] }), /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( Popover, { style: { width: `${width}px` }, triggerRef, className: "max-w-none bg-neutral-50 duration-0", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( ListBox, { selectionMode: "multiple", className: "flex max-h-[inherit] flex-col overflow-auto py-2 outline-none", renderEmptyState: () => { return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "py-2 text-center text-sm text-neutral-400", children: fieldState.inputValue ? "No results found" : "No more options" }); }, children } ) } ) ] } ), /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "absolute right-0 mr-2 flex rounded", children: [ selectedList.items.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( "button", { className: "flex h-6 w-6 items-center justify-center focus-visible:outline-purple-600", onClick: deleteAllTags, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons2.CloseFilledIcon, { height: 20, width: 20, className: "text-neutral-600" }) } ), /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( "button", { className: "flex h-6 w-6 items-center justify-center focus-visible:outline-purple-600", onClick: (e) => { var _a2; e.preventDefault(); (_a2 = triggerButtonRef.current) == null ? void 0 : _a2.click(); }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons2.ChevronDownIcon, { height: 24, width: 24, className: "text-neutral-600" }) } ) ] }) ] } ) ] }); } }); } }); } function MultiSelectItem(props) { return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)( ListBoxItem, { ...props, className: (0, import_react_aria_components7.composeRenderProps)(props.className, (className) => { return (0, import_tailwind_merge8.twMerge)(["px-2 py-1.5 text-sm", className]); }), children: props.children } ); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { MultiSelect, MultiSelectField, MultiSelectItem });