@api7/ui-components 0.7.0 → 0.8.0
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/README.md +169 -8
- package/dist/components/anchor/anchor.d.ts +28 -0
- package/dist/components/anchor/anchor.js +249 -0
- package/dist/components/breadcrumb/breadcrumb.d.ts +15 -0
- package/dist/components/breadcrumb/breadcrumb.js +60 -0
- package/dist/components/button/button.d.ts +1 -1
- package/dist/components/button/button.js +1 -1
- package/dist/components/calendar/calendar.js +24 -3
- package/dist/components/cascader/cascader-data.d.ts +26 -0
- package/dist/components/cascader/cascader-data.js +110 -0
- package/dist/components/cascader/cascader.d.ts +41 -0
- package/dist/components/cascader/cascader.js +513 -0
- package/dist/components/code/code.d.ts +9 -0
- package/dist/components/code/code.js +20 -0
- package/dist/components/code/copy-button.js +42 -0
- package/dist/components/code-block/code-block.d.ts +16 -0
- package/dist/components/code-block/code-block.js +45 -0
- package/dist/components/code-block/highlight.js +67 -0
- package/dist/components/collapsible/collapsible.d.ts +14 -0
- package/dist/components/collapsible/collapsible.js +36 -0
- package/dist/components/combobox/combobox-loading.d.ts +18 -0
- package/dist/components/combobox/combobox-loading.js +148 -0
- package/dist/components/combobox/combobox.d.ts +13 -6
- package/dist/components/combobox/combobox.js +58 -10
- package/dist/components/data-table/data-table.d.ts +27 -3
- package/dist/components/data-table/data-table.js +155 -40
- package/dist/components/data-table/expandable-row.js +40 -0
- package/dist/components/data-table/filter-header.d.ts +28 -5
- package/dist/components/data-table/filter-header.js +103 -40
- package/dist/components/data-table/index.d.ts +3 -3
- package/dist/components/data-table/use-table-layout.js +84 -0
- package/dist/components/date-picker/date-picker.js +11 -2
- package/dist/components/description-list/description-list.d.ts +1 -1
- package/dist/components/dialog/dialog.js +1 -1
- package/dist/components/drawer/drawer.js +1 -1
- package/dist/components/empty/empty.d.ts +1 -1
- package/dist/components/field/labeled-field.js +1 -1
- package/dist/components/file-upload/file-upload.d.ts +44 -0
- package/dist/components/file-upload/file-upload.js +195 -0
- package/dist/components/form/choice-fields.js +2 -2
- package/dist/components/form/date-fields.d.ts +1 -1
- package/dist/components/form/date-fields.js +1 -1
- package/dist/components/form/form-item.js +1 -1
- package/dist/components/form/form.js +1 -1
- package/dist/components/form/index.d.ts +2 -2
- package/dist/components/form/index.js +4 -4
- package/dist/components/form/text-field.js +2 -2
- package/dist/components/form/toggle-fields.d.ts +1 -1
- package/dist/components/form/toggle-fields.js +3 -3
- package/dist/components/pagination/pagination.d.ts +20 -4
- package/dist/components/pagination/pagination.js +83 -32
- package/dist/components/relative-date-range-picker/relative-date-range-picker.d.ts +14 -0
- package/dist/components/relative-date-range-picker/relative-date-range-picker.js +245 -0
- package/dist/components/relative-date-range-picker/relative-date-range.d.ts +26 -0
- package/dist/components/relative-date-range-picker/relative-date-range.js +109 -0
- package/dist/components/select/select.js +1 -1
- package/dist/components/sidebar/sidebar.js +2 -2
- package/dist/components/steps/steps.d.ts +23 -0
- package/dist/components/steps/steps.js +86 -0
- package/dist/components/table/table.d.ts +2 -1
- package/dist/components/table/table.js +2 -1
- package/dist/components/toast/toast.d.ts +1 -1
- package/dist/components/toast/toast.js +1 -1
- package/dist/config/config-provider.d.ts +2 -2
- package/dist/config/messages.d.ts +115 -1
- package/dist/config/messages.js +140 -2
- package/dist/hooks/use-clipboard.d.ts +11 -0
- package/dist/hooks/use-clipboard.js +75 -0
- package/dist/hooks/use-field-label-with-text.js +32 -0
- package/dist/index.d.ts +26 -13
- package/dist/index.js +15 -4
- package/dist/tokens/index.d.ts +1 -1
- package/dist/tokens/index.js +1 -1
- package/package.json +7 -6
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ComponentProps } from "react";
|
|
2
|
+
export interface CodeProps extends Omit<ComponentProps<'code'>, 'children'> {
|
|
3
|
+
children: string;
|
|
4
|
+
/** Show a copy button beside the inline code. Defaults to false. */
|
|
5
|
+
copyable?: boolean;
|
|
6
|
+
/** Text to copy instead of the displayed code, including an empty string. */
|
|
7
|
+
copyValue?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function Code({ children, copyable, copyValue, className, ...props }: CodeProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { CopyButton } from "./copy-button.js";
|
|
3
|
+
import { cn } from "../../lib/cn.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
// src/components/code/code.tsx
|
|
6
|
+
function Code({ children, copyable = false, copyValue, className, ...props }) {
|
|
7
|
+
return /* @__PURE__ */ jsxs("code", {
|
|
8
|
+
className: cn("inline-flex max-w-full items-center gap-1 rounded-sm bg-muted px-1 py-0.5 align-baseline font-mono text-xs text-foreground", className),
|
|
9
|
+
...props,
|
|
10
|
+
"data-slot": "code",
|
|
11
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
12
|
+
className: "min-w-0 whitespace-pre-wrap wrap-anywhere",
|
|
13
|
+
children
|
|
14
|
+
}), copyable && /* @__PURE__ */ jsx(CopyButton, {
|
|
15
|
+
value: copyValue ?? children,
|
|
16
|
+
className: "size-5"
|
|
17
|
+
})]
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export { Code };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Button } from "../button/button.js";
|
|
3
|
+
import { useMessages } from "../../config/config-provider.js";
|
|
4
|
+
import { useClipboard } from "../../hooks/use-clipboard.js";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { Check, Copy, TriangleAlert } from "lucide-react";
|
|
7
|
+
// src/components/code/copy-button.tsx
|
|
8
|
+
function CopyButtonContent({ value, className }) {
|
|
9
|
+
const { copy, status } = useClipboard();
|
|
10
|
+
const { code: messages } = useMessages();
|
|
11
|
+
const label = status === "copied" ? messages.copied : status === "error" ? messages.copyError : messages.copy;
|
|
12
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
13
|
+
className: "inline-flex shrink-0 align-middle",
|
|
14
|
+
"data-slot": "code-copy",
|
|
15
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
16
|
+
type: "button",
|
|
17
|
+
variant: "ghost",
|
|
18
|
+
size: "icon",
|
|
19
|
+
className,
|
|
20
|
+
"aria-label": label,
|
|
21
|
+
title: label,
|
|
22
|
+
"aria-busy": status === "copying" || void 0,
|
|
23
|
+
onClick: () => {
|
|
24
|
+
copy(value);
|
|
25
|
+
},
|
|
26
|
+
"data-status": status,
|
|
27
|
+
children: /* @__PURE__ */ jsx(status === "copied" ? Check : status === "error" ? TriangleAlert : Copy, {
|
|
28
|
+
"aria-hidden": "true",
|
|
29
|
+
className: status === "error" ? "text-destructive-text" : void 0
|
|
30
|
+
})
|
|
31
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
32
|
+
className: "sr-only",
|
|
33
|
+
role: "status",
|
|
34
|
+
"aria-live": "polite",
|
|
35
|
+
children: status === "copied" || status === "error" ? label : ""
|
|
36
|
+
})]
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function CopyButton(props) {
|
|
40
|
+
return /* @__PURE__ */ jsx(CopyButtonContent, { ...props }, props.value);
|
|
41
|
+
}
|
|
42
|
+
export { CopyButton };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ComponentProps } from "react";
|
|
2
|
+
export interface CodeBlockProps extends Omit<ComponentProps<'div'>, 'children'> {
|
|
3
|
+
code: string;
|
|
4
|
+
/** Syntax language or alias. Unknown languages render as plain text. */
|
|
5
|
+
language?: string;
|
|
6
|
+
/** Show a copy button. Defaults to true. */
|
|
7
|
+
copyable?: boolean;
|
|
8
|
+
/** Text to copy instead of the displayed code, including an empty string. */
|
|
9
|
+
copyValue?: string;
|
|
10
|
+
showLineNumbers?: boolean;
|
|
11
|
+
/** Wrap long lines instead of scrolling horizontally. */
|
|
12
|
+
wrapLines?: boolean;
|
|
13
|
+
/** Props for the scrollable pre element, including its accessible name and maximum height. */
|
|
14
|
+
preProps?: ComponentProps<'pre'>;
|
|
15
|
+
}
|
|
16
|
+
export declare function CodeBlock({ code, language, copyable, copyValue, showLineNumbers, wrapLines, preProps, className, ...props }: CodeBlockProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { highlightLines, tokenClassName } from "./highlight.js";
|
|
3
|
+
import { CopyButton } from "../code/copy-button.js";
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
|
+
import { useMemo } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
// src/components/code-block/code-block.tsx
|
|
8
|
+
function CodeBlock({ code, language = "text", copyable = true, copyValue, showLineNumbers = false, wrapLines = false, preProps, className, ...props }) {
|
|
9
|
+
const lines = useMemo(() => highlightLines(code, language), [code, language]);
|
|
10
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
11
|
+
className: cn("flex min-w-0 max-w-full items-start overflow-hidden rounded-lg bg-muted text-foreground", className),
|
|
12
|
+
...props,
|
|
13
|
+
"data-slot": "code-block",
|
|
14
|
+
children: [/* @__PURE__ */ jsx("pre", {
|
|
15
|
+
tabIndex: 0,
|
|
16
|
+
...preProps,
|
|
17
|
+
className: cn("m-0 min-w-0 flex-1 overflow-auto p-3 font-mono text-sm leading-6 outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-inset", preProps?.className),
|
|
18
|
+
"data-slot": "code-block-pre",
|
|
19
|
+
children: /* @__PURE__ */ jsx("code", {
|
|
20
|
+
className: cn("block", wrapLines ? "whitespace-pre-wrap wrap-anywhere" : "w-max min-w-full whitespace-pre"),
|
|
21
|
+
children: lines.map((line, index) => /* @__PURE__ */ jsxs("span", {
|
|
22
|
+
className: "flex min-h-6",
|
|
23
|
+
"data-slot": "code-block-line",
|
|
24
|
+
children: [showLineNumbers && /* @__PURE__ */ jsx("span", {
|
|
25
|
+
"aria-hidden": "true",
|
|
26
|
+
className: "me-4 shrink-0 text-end text-muted-foreground select-none",
|
|
27
|
+
style: { width: `${String(lines.length).length}ch` },
|
|
28
|
+
"data-slot": "code-block-line-number",
|
|
29
|
+
children: index + 1
|
|
30
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
31
|
+
className: "min-w-0 flex-1",
|
|
32
|
+
children: line.map((token, tokenIndex) => /* @__PURE__ */ jsx("span", {
|
|
33
|
+
className: tokenClassName(token.types),
|
|
34
|
+
children: token.content
|
|
35
|
+
}, tokenIndex))
|
|
36
|
+
})]
|
|
37
|
+
}, index))
|
|
38
|
+
})
|
|
39
|
+
}), copyable && /* @__PURE__ */ jsx(CopyButton, {
|
|
40
|
+
value: copyValue ?? code,
|
|
41
|
+
className: "m-1"
|
|
42
|
+
})]
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export { CodeBlock };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import bash from "refractor/bash";
|
|
2
|
+
import { refractor } from "refractor/core";
|
|
3
|
+
import css from "refractor/css";
|
|
4
|
+
import javascript from "refractor/javascript";
|
|
5
|
+
import json from "refractor/json";
|
|
6
|
+
import lua from "refractor/lua";
|
|
7
|
+
import markup from "refractor/markup";
|
|
8
|
+
import typescript from "refractor/typescript";
|
|
9
|
+
import yaml from "refractor/yaml";
|
|
10
|
+
// src/components/code-block/highlight.ts
|
|
11
|
+
for (const syntax of [
|
|
12
|
+
bash,
|
|
13
|
+
css,
|
|
14
|
+
javascript,
|
|
15
|
+
json,
|
|
16
|
+
lua,
|
|
17
|
+
markup,
|
|
18
|
+
typescript,
|
|
19
|
+
yaml
|
|
20
|
+
]) refractor.register(syntax);
|
|
21
|
+
function highlightLines(code, language) {
|
|
22
|
+
const grammar = language.toLowerCase();
|
|
23
|
+
const nodes = refractor.registered(grammar) ? refractor.highlight(code, grammar).children : [{
|
|
24
|
+
type: "text",
|
|
25
|
+
value: code
|
|
26
|
+
}];
|
|
27
|
+
const lines = [[]];
|
|
28
|
+
function visit(nodes, types = []) {
|
|
29
|
+
for (const node of nodes) if (node.type === "element") visit(node.children, [...types, ...node.properties.className ?? []]);
|
|
30
|
+
else if (node.type === "text") node.value.split(/(\r\n|\r|\n)/).forEach((content, index) => {
|
|
31
|
+
if (content) lines[lines.length - 1].push({
|
|
32
|
+
content,
|
|
33
|
+
types
|
|
34
|
+
});
|
|
35
|
+
if (index % 2 === 1) lines.push([]);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
visit(nodes);
|
|
39
|
+
return lines;
|
|
40
|
+
}
|
|
41
|
+
function tokenClassName(types) {
|
|
42
|
+
if (types.some((type) => [
|
|
43
|
+
"comment",
|
|
44
|
+
"prolog",
|
|
45
|
+
"doctype"
|
|
46
|
+
].includes(type))) return "text-muted-foreground";
|
|
47
|
+
if (types.some((type) => [
|
|
48
|
+
"string",
|
|
49
|
+
"char",
|
|
50
|
+
"attr-value"
|
|
51
|
+
].includes(type))) return "text-success-foreground";
|
|
52
|
+
if (types.some((type) => [
|
|
53
|
+
"keyword",
|
|
54
|
+
"boolean",
|
|
55
|
+
"number",
|
|
56
|
+
"constant",
|
|
57
|
+
"property",
|
|
58
|
+
"tag",
|
|
59
|
+
"attr-name"
|
|
60
|
+
].includes(type))) return "text-primary-text";
|
|
61
|
+
if (types.some((type) => [
|
|
62
|
+
"function",
|
|
63
|
+
"class-name",
|
|
64
|
+
"builtin"
|
|
65
|
+
].includes(type))) return "text-warning-foreground";
|
|
66
|
+
}
|
|
67
|
+
export { highlightLines, tokenClassName };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Collapsible } from "@base-ui/react/collapsible";
|
|
2
|
+
export type CollapsibleProps = Omit<Collapsible.Root.Props, 'className'> & {
|
|
3
|
+
className?: string;
|
|
4
|
+
};
|
|
5
|
+
export type CollapsibleTriggerProps = Omit<Collapsible.Trigger.Props, 'className'> & {
|
|
6
|
+
className?: string;
|
|
7
|
+
};
|
|
8
|
+
export type CollapsibleContentProps = Omit<Collapsible.Panel.Props, 'className'> & {
|
|
9
|
+
className?: string;
|
|
10
|
+
};
|
|
11
|
+
declare function Collapsible$1({ className, ...props }: CollapsibleProps): import("react").JSX.Element;
|
|
12
|
+
declare function CollapsibleTrigger({ className, children, ...props }: CollapsibleTriggerProps): import("react").JSX.Element;
|
|
13
|
+
declare function CollapsibleContent({ className, ...props }: CollapsibleContentProps): import("react").JSX.Element;
|
|
14
|
+
export { Collapsible$1 as Collapsible, CollapsibleContent, CollapsibleTrigger };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../../lib/cn.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { ChevronDown } from "lucide-react";
|
|
5
|
+
import { Collapsible } from "@base-ui/react/collapsible";
|
|
6
|
+
// src/components/collapsible/collapsible.tsx
|
|
7
|
+
function Collapsible$1({ className, ...props }) {
|
|
8
|
+
return /* @__PURE__ */ jsx(Collapsible.Root, {
|
|
9
|
+
className: cn("min-w-0", className),
|
|
10
|
+
...props,
|
|
11
|
+
"data-slot": "collapsible"
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function CollapsibleTrigger({ className, children, ...props }) {
|
|
15
|
+
return /* @__PURE__ */ jsxs(Collapsible.Trigger, {
|
|
16
|
+
className: cn("inline-flex max-w-full min-w-0 cursor-pointer items-center justify-between gap-2 rounded-md text-start text-sm font-medium text-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring/40 disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 data-disabled:cursor-not-allowed data-disabled:opacity-50 data-panel-open:[&>[data-slot=collapsible-icon]]:rotate-180", className),
|
|
17
|
+
...props,
|
|
18
|
+
"data-slot": "collapsible-trigger",
|
|
19
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
20
|
+
className: "min-w-0 [overflow-wrap:anywhere]",
|
|
21
|
+
children
|
|
22
|
+
}), /* @__PURE__ */ jsx(ChevronDown, {
|
|
23
|
+
"aria-hidden": "true",
|
|
24
|
+
"data-slot": "collapsible-icon",
|
|
25
|
+
className: "size-4 shrink-0 transition-transform duration-200 motion-reduce:transition-none"
|
|
26
|
+
})]
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function CollapsibleContent({ className, ...props }) {
|
|
30
|
+
return /* @__PURE__ */ jsx(Collapsible.Panel, {
|
|
31
|
+
className: cn("h-[var(--collapsible-panel-height)] overflow-hidden text-sm transition-[height,opacity] duration-200 ease-out data-ending-style:h-0 data-ending-style:opacity-0 data-starting-style:h-0 data-starting-style:opacity-0 motion-reduce:transition-none", className),
|
|
32
|
+
...props,
|
|
33
|
+
"data-slot": "collapsible-content"
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export { Collapsible$1 as Collapsible, CollapsibleContent, CollapsibleTrigger };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "react";
|
|
2
|
+
export interface ComboboxLoadRequest {
|
|
3
|
+
search: string;
|
|
4
|
+
/** Starts at 1 and advances after each successful page, including empty pages. */
|
|
5
|
+
page: number;
|
|
6
|
+
signal: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
export interface ComboboxPage<Item> {
|
|
9
|
+
items: Item[];
|
|
10
|
+
hasMore: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface ComboboxLoadingOptions<Item> {
|
|
13
|
+
loadItems: (request: ComboboxLoadRequest) => Promise<ComboboxPage<Item>>;
|
|
14
|
+
/** Delay before a nonempty search. An empty search loads immediately. */
|
|
15
|
+
debounceMs?: number;
|
|
16
|
+
/** Change this when the data source changes. Selection stays under the caller's control. */
|
|
17
|
+
resetKey?: unknown;
|
|
18
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Button } from "../button/button.js";
|
|
3
|
+
import { Spinner } from "../spinner/spinner.js";
|
|
4
|
+
import { useMessages } from "../../config/config-provider.js";
|
|
5
|
+
import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { Combobox } from "@base-ui/react/combobox";
|
|
8
|
+
// src/components/combobox/combobox-loading.tsx
|
|
9
|
+
const ComboboxLoadingContext = createContext(null);
|
|
10
|
+
function useComboboxLoadingContext() {
|
|
11
|
+
return useContext(ComboboxLoadingContext);
|
|
12
|
+
}
|
|
13
|
+
function attachInputRef(ref, node) {
|
|
14
|
+
if (typeof ref === "function") {
|
|
15
|
+
const cleanup = ref(node);
|
|
16
|
+
return typeof cleanup === "function" ? cleanup : () => {
|
|
17
|
+
ref(null);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (!ref) return () => {};
|
|
21
|
+
ref.current = node;
|
|
22
|
+
return () => {
|
|
23
|
+
ref.current = null;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function useComboboxInputRef(ref) {
|
|
27
|
+
const inputRef = useComboboxLoadingContext()?.inputRef;
|
|
28
|
+
return useMemo(() => {
|
|
29
|
+
if (!inputRef) return ref;
|
|
30
|
+
return (node) => {
|
|
31
|
+
const cleanupExternal = attachInputRef(ref, node);
|
|
32
|
+
const cleanupInternal = attachInputRef(inputRef, node);
|
|
33
|
+
return () => {
|
|
34
|
+
cleanupExternal();
|
|
35
|
+
cleanupInternal();
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
}, [ref, inputRef]);
|
|
39
|
+
}
|
|
40
|
+
function useComboboxLoading(loadItems, search, enabled, debounceMs, resetKey) {
|
|
41
|
+
const loader = useRef(loadItems);
|
|
42
|
+
useLayoutEffect(() => {
|
|
43
|
+
loader.current = loadItems;
|
|
44
|
+
}, [loadItems]);
|
|
45
|
+
const [state, setState] = useState({
|
|
46
|
+
items: [],
|
|
47
|
+
loading: true,
|
|
48
|
+
hasMore: false,
|
|
49
|
+
failed: false,
|
|
50
|
+
loadMore: () => {}
|
|
51
|
+
});
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!enabled) return;
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
let items = [];
|
|
56
|
+
let page = 1;
|
|
57
|
+
let hasMore = true;
|
|
58
|
+
let loading = false;
|
|
59
|
+
async function loadMore() {
|
|
60
|
+
if (!loader.current || controller.signal.aborted || loading || !hasMore) return;
|
|
61
|
+
loading = true;
|
|
62
|
+
setState({
|
|
63
|
+
items,
|
|
64
|
+
loading: true,
|
|
65
|
+
hasMore,
|
|
66
|
+
failed: false,
|
|
67
|
+
loadMore
|
|
68
|
+
});
|
|
69
|
+
try {
|
|
70
|
+
const result = await loader.current({
|
|
71
|
+
search,
|
|
72
|
+
page,
|
|
73
|
+
signal: controller.signal
|
|
74
|
+
});
|
|
75
|
+
if (controller.signal.aborted) return;
|
|
76
|
+
items = [...items, ...result.items];
|
|
77
|
+
hasMore = result.hasMore;
|
|
78
|
+
page += 1;
|
|
79
|
+
setState({
|
|
80
|
+
items,
|
|
81
|
+
loading: false,
|
|
82
|
+
hasMore,
|
|
83
|
+
failed: false,
|
|
84
|
+
loadMore
|
|
85
|
+
});
|
|
86
|
+
} catch {
|
|
87
|
+
if (controller.signal.aborted) return;
|
|
88
|
+
setState({
|
|
89
|
+
items,
|
|
90
|
+
loading: false,
|
|
91
|
+
hasMore,
|
|
92
|
+
failed: true,
|
|
93
|
+
loadMore
|
|
94
|
+
});
|
|
95
|
+
} finally {
|
|
96
|
+
loading = false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
setState({
|
|
100
|
+
items,
|
|
101
|
+
loading: true,
|
|
102
|
+
hasMore: false,
|
|
103
|
+
failed: false,
|
|
104
|
+
loadMore: () => {}
|
|
105
|
+
});
|
|
106
|
+
const timeout = setTimeout(() => void loadMore(), search ? debounceMs : 0);
|
|
107
|
+
return () => {
|
|
108
|
+
clearTimeout(timeout);
|
|
109
|
+
controller.abort();
|
|
110
|
+
};
|
|
111
|
+
}, [
|
|
112
|
+
search,
|
|
113
|
+
enabled,
|
|
114
|
+
debounceMs,
|
|
115
|
+
resetKey
|
|
116
|
+
]);
|
|
117
|
+
return {
|
|
118
|
+
...state,
|
|
119
|
+
enabled,
|
|
120
|
+
loading: enabled && state.loading
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function ComboboxLoadingStatus() {
|
|
124
|
+
const state = useComboboxLoadingContext();
|
|
125
|
+
const messages = useMessages();
|
|
126
|
+
if (!state) return null;
|
|
127
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Combobox.Status, {
|
|
128
|
+
className: "flex items-center gap-2 px-4 py-2.5 text-sm text-muted-foreground empty:p-0",
|
|
129
|
+
"data-slot": "combobox-status",
|
|
130
|
+
children: state.loading ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, {
|
|
131
|
+
size: "sm",
|
|
132
|
+
"aria-hidden": "true"
|
|
133
|
+
}), messages.combobox.loading] }) : state.failed ? messages.combobox.error : null
|
|
134
|
+
}), state.enabled && !state.loading && (state.failed || state.hasMore) && /* @__PURE__ */ jsx("div", {
|
|
135
|
+
className: "px-4 py-2",
|
|
136
|
+
"data-slot": "combobox-pagination",
|
|
137
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
138
|
+
type: "button",
|
|
139
|
+
variant: "link",
|
|
140
|
+
onClick: () => {
|
|
141
|
+
state.inputRef.current?.focus();
|
|
142
|
+
state.loadMore();
|
|
143
|
+
},
|
|
144
|
+
children: state.failed ? messages.combobox.retry : messages.combobox.loadMore
|
|
145
|
+
})
|
|
146
|
+
})] });
|
|
147
|
+
}
|
|
148
|
+
export { ComboboxLoadingContext, ComboboxLoadingStatus, useComboboxInputRef, useComboboxLoading, useComboboxLoadingContext };
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
+
import { ComboboxLoadRequest, ComboboxLoadingOptions, ComboboxPage } from "./combobox-loading.js";
|
|
1
2
|
import { ReactNode } from "react";
|
|
2
3
|
import { Combobox } from "@base-ui/react/combobox";
|
|
3
4
|
type WithClassName<P> = Omit<P, 'className'> & {
|
|
4
5
|
className?: string;
|
|
5
6
|
};
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
type LocalSource = {
|
|
8
|
+
loadItems?: never;
|
|
9
|
+
debounceMs?: never;
|
|
10
|
+
resetKey?: never;
|
|
11
|
+
};
|
|
12
|
+
type ManagedProps = 'items' | 'filteredItems' | 'filter' | 'inputValue' | 'defaultInputValue';
|
|
13
|
+
export type ComboboxProps<Item = unknown, Multiple extends boolean | undefined = boolean> = (Combobox.Root.Props<Item, Multiple> & LocalSource) | (Omit<Combobox.Root.Props<Item, Multiple>, ManagedProps> & ComboboxLoadingOptions<Item> & { [K in ManagedProps]?: never; });
|
|
14
|
+
declare function Combobox$1<Item, Multiple extends boolean | undefined = false>({ loadItems, debounceMs, resetKey, ...props }: ComboboxProps<Item, Multiple>): import("react").JSX.Element;
|
|
8
15
|
export type ComboboxInputProps = WithClassName<Combobox.Input.Props> & {
|
|
9
16
|
/** Accessible name of the clear button. Defaults to the `combobox.clear` message. */
|
|
10
17
|
clearLabel?: string;
|
|
11
18
|
/** Accessible name of the toggle button. Defaults to the `combobox.toggle` message. */
|
|
12
19
|
toggleLabel?: string;
|
|
13
20
|
};
|
|
14
|
-
declare function ComboboxInput({ className, clearLabel, toggleLabel, ...props }: ComboboxInputProps): import("react").JSX.Element;
|
|
21
|
+
declare function ComboboxInput({ className, clearLabel, toggleLabel, ref, ...props }: ComboboxInputProps): import("react").JSX.Element;
|
|
15
22
|
export type ComboboxContentProps = Omit<WithClassName<Combobox.Popup.Props>, 'children'> & {
|
|
16
23
|
side?: Combobox.Positioner.Props['side'];
|
|
17
24
|
sideOffset?: Combobox.Positioner.Props['sideOffset'];
|
|
@@ -20,7 +27,7 @@ export type ComboboxContentProps = Omit<WithClassName<Combobox.Popup.Props>, 'ch
|
|
|
20
27
|
/** Content shown when no item matches. Defaults to the localized 'No results' message. */
|
|
21
28
|
empty?: ReactNode;
|
|
22
29
|
};
|
|
23
|
-
declare function ComboboxContent({ className, side, sideOffset, align, children, empty, ...props }: ComboboxContentProps): import("react").JSX.Element;
|
|
30
|
+
declare function ComboboxContent({ className, side, sideOffset, align, children, empty, onScroll, ...props }: ComboboxContentProps): import("react").JSX.Element;
|
|
24
31
|
export type ComboboxItemProps = WithClassName<Combobox.Item.Props>;
|
|
25
32
|
declare function ComboboxItem({ className, children, ...props }: ComboboxItemProps): import("react").JSX.Element;
|
|
26
33
|
export type ComboboxEmptyProps = WithClassName<Combobox.Empty.Props>;
|
|
@@ -42,5 +49,5 @@ export type ComboboxChipProps = WithClassName<Combobox.Chip.Props> & {
|
|
|
42
49
|
};
|
|
43
50
|
declare function ComboboxChip({ className, children, removeLabel, ...props }: ComboboxChipProps): import("react").JSX.Element;
|
|
44
51
|
export type ComboboxChipsInputProps = WithClassName<Combobox.Input.Props>;
|
|
45
|
-
declare function ComboboxChipsInput({ className, ...props }: ComboboxChipsInputProps): import("react").JSX.Element;
|
|
46
|
-
export { Combobox$1 as Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxValue };
|
|
52
|
+
declare function ComboboxChipsInput({ className, ref, ...props }: ComboboxChipsInputProps): import("react").JSX.Element;
|
|
53
|
+
export { Combobox$1 as Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, type ComboboxLoadRequest, type ComboboxPage, ComboboxValue };
|
|
@@ -1,21 +1,55 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { ComboboxLoadingContext, ComboboxLoadingStatus, useComboboxInputRef, useComboboxLoading, useComboboxLoadingContext } from "./combobox-loading.js";
|
|
2
3
|
import { useMessages } from "../../config/config-provider.js";
|
|
3
4
|
import { cn } from "../../lib/cn.js";
|
|
4
|
-
import { listItemClassName, listboxClassName, popupLayerClassName } from "../../lib/popup.js";
|
|
5
5
|
import { controlFocus, controlFocusWithin, controlSurface } from "../../lib/control.js";
|
|
6
|
+
import { listItemClassName, listboxClassName, popupLayerClassName } from "../../lib/popup.js";
|
|
7
|
+
import { useRef, useState } from "react";
|
|
6
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
9
|
import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react";
|
|
8
10
|
import { Combobox } from "@base-ui/react/combobox";
|
|
9
11
|
// src/components/combobox/combobox.tsx
|
|
10
|
-
function Combobox$1(props) {
|
|
11
|
-
|
|
12
|
+
function Combobox$1({ loadItems, debounceMs = 300, resetKey, ...props }) {
|
|
13
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(props.defaultOpen ?? false);
|
|
14
|
+
const open = props.open ?? uncontrolledOpen;
|
|
15
|
+
const [search, setSearch] = useState("");
|
|
16
|
+
const inputRef = useRef(null);
|
|
17
|
+
const state = useComboboxLoading(loadItems, search, !!loadItems && open && !props.disabled, debounceMs, resetKey);
|
|
18
|
+
const asyncProps = loadItems ? {
|
|
19
|
+
items: state.items,
|
|
20
|
+
filter: null,
|
|
21
|
+
open,
|
|
22
|
+
onOpenChange: (nextOpen, details) => {
|
|
23
|
+
props.onOpenChange?.(nextOpen, details);
|
|
24
|
+
if (details.isCanceled) return;
|
|
25
|
+
setUncontrolledOpen(nextOpen);
|
|
26
|
+
},
|
|
27
|
+
onInputValueChange: (value, details) => {
|
|
28
|
+
props.onInputValueChange?.(value, details);
|
|
29
|
+
if (details.isCanceled) return;
|
|
30
|
+
if (details.reason === "input-change" || details.reason === "input-clear" || details.reason === "clear-press") setSearch(value);
|
|
31
|
+
else if (details.reason === "item-press" || details.reason === "none") setSearch("");
|
|
32
|
+
}
|
|
33
|
+
} : {};
|
|
34
|
+
return /* @__PURE__ */ jsx(ComboboxLoadingContext.Provider, {
|
|
35
|
+
value: loadItems ? {
|
|
36
|
+
...state,
|
|
37
|
+
inputRef
|
|
38
|
+
} : null,
|
|
39
|
+
children: /* @__PURE__ */ jsx(Combobox.Root, {
|
|
40
|
+
...props,
|
|
41
|
+
...asyncProps
|
|
42
|
+
})
|
|
43
|
+
});
|
|
12
44
|
}
|
|
13
|
-
function ComboboxInput({ className, clearLabel, toggleLabel, ...props }) {
|
|
45
|
+
function ComboboxInput({ className, clearLabel, toggleLabel, ref, ...props }) {
|
|
14
46
|
const messages = useMessages();
|
|
47
|
+
const inputRef = useComboboxInputRef(ref);
|
|
15
48
|
return /* @__PURE__ */ jsxs("div", {
|
|
16
49
|
className: "relative",
|
|
17
50
|
"data-slot": "combobox-input-group",
|
|
18
51
|
children: [/* @__PURE__ */ jsx(Combobox.Input, {
|
|
52
|
+
ref: inputRef,
|
|
19
53
|
className: cn(controlSurface, controlFocus, "h-8 w-full pr-16 pl-3", className),
|
|
20
54
|
...props,
|
|
21
55
|
"data-slot": "combobox-input"
|
|
@@ -38,7 +72,8 @@ function ComboboxInput({ className, clearLabel, toggleLabel, ...props }) {
|
|
|
38
72
|
})]
|
|
39
73
|
});
|
|
40
74
|
}
|
|
41
|
-
function ComboboxContent({ className, side = "bottom", sideOffset = 4, align = "start", children, empty = /* @__PURE__ */ jsx(ComboboxEmpty, {}), ...props }) {
|
|
75
|
+
function ComboboxContent({ className, side = "bottom", sideOffset = 4, align = "start", children, empty = /* @__PURE__ */ jsx(ComboboxEmpty, {}), onScroll, ...props }) {
|
|
76
|
+
const asyncState = useComboboxLoadingContext();
|
|
42
77
|
return /* @__PURE__ */ jsx(Combobox.Portal, { children: /* @__PURE__ */ jsx(Combobox.Positioner, {
|
|
43
78
|
className: popupLayerClassName,
|
|
44
79
|
side,
|
|
@@ -47,11 +82,22 @@ function ComboboxContent({ className, side = "bottom", sideOffset = 4, align = "
|
|
|
47
82
|
children: /* @__PURE__ */ jsxs(Combobox.Popup, {
|
|
48
83
|
className: cn(listboxClassName, "min-w-(--anchor-width) outline-none", className),
|
|
49
84
|
...props,
|
|
85
|
+
onScroll: (event) => {
|
|
86
|
+
onScroll?.(event);
|
|
87
|
+
if (event.defaultPrevented || !asyncState?.enabled || asyncState.loading || asyncState.failed) return;
|
|
88
|
+
const { scrollTop, scrollHeight, clientHeight } = event.currentTarget;
|
|
89
|
+
if (scrollHeight - scrollTop - clientHeight <= 32) asyncState.loadMore();
|
|
90
|
+
},
|
|
50
91
|
"data-slot": "combobox-content",
|
|
51
|
-
children: [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
92
|
+
children: [
|
|
93
|
+
/* @__PURE__ */ jsx(Combobox.List, {
|
|
94
|
+
"aria-busy": asyncState?.loading,
|
|
95
|
+
"data-slot": "combobox-list",
|
|
96
|
+
children
|
|
97
|
+
}),
|
|
98
|
+
!asyncState?.loading && !asyncState?.failed && !asyncState?.hasMore && empty,
|
|
99
|
+
/* @__PURE__ */ jsx(ComboboxLoadingStatus, {})
|
|
100
|
+
]
|
|
55
101
|
})
|
|
56
102
|
}) });
|
|
57
103
|
}
|
|
@@ -131,8 +177,10 @@ function ComboboxChip({ className, children, removeLabel, ...props }) {
|
|
|
131
177
|
})]
|
|
132
178
|
});
|
|
133
179
|
}
|
|
134
|
-
function ComboboxChipsInput({ className, ...props }) {
|
|
180
|
+
function ComboboxChipsInput({ className, ref, ...props }) {
|
|
181
|
+
const inputRef = useComboboxInputRef(ref);
|
|
135
182
|
return /* @__PURE__ */ jsx(Combobox.Input, {
|
|
183
|
+
ref: inputRef,
|
|
136
184
|
className: cn("h-6 min-w-16 flex-1 bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
137
185
|
...props,
|
|
138
186
|
"data-slot": "combobox-chips-input"
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { DataTableColumnFilter } from "./filter-header.js";
|
|
2
2
|
import { ComponentProps, ReactNode } from "react";
|
|
3
|
-
import { ColumnDef, ColumnFiltersState, PaginationState, RowData, SortingState, TableFeatures } from "@tanstack/react-table";
|
|
3
|
+
import { ColumnDef, ColumnFiltersState, ColumnPinningState, ExpandedState, PaginationState, RowData, SortingState, TableFeatures } from "@tanstack/react-table";
|
|
4
4
|
declare const dataTableFeatures: {
|
|
5
5
|
columnFilteringFeature: import("@tanstack/react-table").TableFeature;
|
|
6
|
+
columnPinningFeature: import("@tanstack/react-table").TableFeature;
|
|
7
|
+
columnSizingFeature: import("@tanstack/react-table").TableFeature;
|
|
6
8
|
columnVisibilityFeature: import("@tanstack/react-table").TableFeature;
|
|
7
9
|
rowSortingFeature: import("@tanstack/react-table").TableFeature;
|
|
10
|
+
rowExpandingFeature: import("@tanstack/react-table").TableFeature;
|
|
8
11
|
rowPaginationFeature: import("@tanstack/react-table").TableFeature;
|
|
9
12
|
filteredRowModel: (table: import("@tanstack/react-table").Table<any, any>) => () => import("@tanstack/react-table").RowModel<any, any>;
|
|
10
13
|
sortedRowModel: (table: import("@tanstack/react-table").Table<any, any>) => () => import("@tanstack/react-table").RowModel<any, any>;
|
|
@@ -23,8 +26,11 @@ type DataTableFeatures = typeof dataTableFeatures;
|
|
|
23
26
|
type DataTableColumnDef<TData extends RowData, TValue = unknown> = ColumnDef<DataTableFeatures, TData, TValue>;
|
|
24
27
|
declare function createDataTableColumnHelper<TData extends RowData>(): import("@tanstack/react-table").ColumnHelper<{
|
|
25
28
|
columnFilteringFeature: import("@tanstack/react-table").TableFeature;
|
|
29
|
+
columnPinningFeature: import("@tanstack/react-table").TableFeature;
|
|
30
|
+
columnSizingFeature: import("@tanstack/react-table").TableFeature;
|
|
26
31
|
columnVisibilityFeature: import("@tanstack/react-table").TableFeature;
|
|
27
32
|
rowSortingFeature: import("@tanstack/react-table").TableFeature;
|
|
33
|
+
rowExpandingFeature: import("@tanstack/react-table").TableFeature;
|
|
28
34
|
rowPaginationFeature: import("@tanstack/react-table").TableFeature;
|
|
29
35
|
filteredRowModel: (table: import("@tanstack/react-table").Table<any, any>) => () => import("@tanstack/react-table").RowModel<any, any>;
|
|
30
36
|
sortedRowModel: (table: import("@tanstack/react-table").Table<any, any>) => () => import("@tanstack/react-table").RowModel<any, any>;
|
|
@@ -43,8 +49,10 @@ declare module '@tanstack/react-table' {
|
|
|
43
49
|
interface ColumnMeta<TFeatures extends TableFeatures, TData extends RowData, TValue> {
|
|
44
50
|
/** Classes applied to this column's header and body cells. */
|
|
45
51
|
className?: string;
|
|
52
|
+
/** Plain column name for assistive technology when a custom header is visually hidden. */
|
|
53
|
+
accessibleLabel?: string;
|
|
46
54
|
/**
|
|
47
|
-
* Puts
|
|
55
|
+
* Puts an option menu or custom filter panel in the header. The filter value is the list of chosen values, so
|
|
48
56
|
* a column that the table filters itself needs `filterFn: 'oneOf'` (a row matches when its value is any of them),
|
|
49
57
|
* and with `manualFiltering` the owner of the data does the filtering.
|
|
50
58
|
*/
|
|
@@ -56,6 +64,20 @@ export interface DataTableProps<TData extends RowData> {
|
|
|
56
64
|
data: TData[];
|
|
57
65
|
/** Stable row id; defaults to the row index. */
|
|
58
66
|
getRowId?: (row: TData, index: number) => string;
|
|
67
|
+
/** Detail content rendered in a full-width row below its parent. Enables the leading expansion column. */
|
|
68
|
+
renderExpandedRow?: (row: TData) => ReactNode;
|
|
69
|
+
/** Defaults to true when renderExpandedRow is supplied. Ineligible rows have no expansion button. */
|
|
70
|
+
getRowCanExpand?: (row: TData) => boolean;
|
|
71
|
+
/** Controlled expansion by row ID, or true for all eligible rows. */
|
|
72
|
+
expanded?: ExpandedState;
|
|
73
|
+
/** Initial expansion when expanded is uncontrolled. Later eligibility changes do not reset it. */
|
|
74
|
+
defaultExpanded?: ExpandedState;
|
|
75
|
+
/** Receives the proposed expansion state when a row is toggled. */
|
|
76
|
+
onExpandedChange?: (expanded: ExpandedState) => void;
|
|
77
|
+
/** Logical edges: `end` pins Actions to the right in an LTR layout. */
|
|
78
|
+
columnPinning?: ColumnPinningState;
|
|
79
|
+
defaultColumnPinning?: ColumnPinningState;
|
|
80
|
+
onColumnPinningChange?: (pinning: ColumnPinningState) => void;
|
|
59
81
|
sorting?: SortingState;
|
|
60
82
|
defaultSorting?: SortingState;
|
|
61
83
|
onSortingChange?: (sorting: SortingState) => void;
|
|
@@ -75,12 +97,14 @@ export interface DataTableProps<TData extends RowData> {
|
|
|
75
97
|
/** Accessible name of the pagination region; set it when a page shows several tables. */
|
|
76
98
|
paginationLabel?: string;
|
|
77
99
|
loading?: boolean;
|
|
100
|
+
/** Hide repeated headers visually while preserving plain column names for assistive technology. */
|
|
101
|
+
showHeader?: boolean;
|
|
78
102
|
/** Replaces the default "No Data" message. */
|
|
79
103
|
empty?: ReactNode;
|
|
80
104
|
/** Rendered above the table, right-aligned (search inputs, buttons). */
|
|
81
105
|
toolbar?: ReactNode;
|
|
82
106
|
className?: string;
|
|
83
107
|
}
|
|
84
|
-
declare function DataTable<TData extends RowData>({ columns, data, getRowId, sorting, defaultSorting, onSortingChange, manualSorting, columnFilters, onColumnFiltersChange, manualFiltering, pagination, defaultPagination, onPaginationChange, manualPagination, rowCount, pageSizeOptions, hidePagination, paginationLabel, loading, empty, toolbar, className }: DataTableProps<TData>): import("react").JSX.Element;
|
|
108
|
+
declare function DataTable<TData extends RowData>({ columns, data, getRowId, renderExpandedRow, getRowCanExpand, expanded, defaultExpanded, onExpandedChange, columnPinning, defaultColumnPinning, onColumnPinningChange, sorting, defaultSorting, onSortingChange, manualSorting, columnFilters, onColumnFiltersChange, manualFiltering, pagination, defaultPagination, onPaginationChange, manualPagination, rowCount, pageSizeOptions, hidePagination, paginationLabel, loading, showHeader, empty, toolbar, className }: DataTableProps<TData>): import("react").JSX.Element;
|
|
85
109
|
declare function DataTableRowActions({ className, ...props }: ComponentProps<'div'>): import("react").JSX.Element;
|
|
86
110
|
export { DataTable, type DataTableColumnDef, type DataTableFeatures, DataTableRowActions, createDataTableColumnHelper, dataTableFeatures };
|