@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ComponentProps } from "react";
|
|
2
2
|
import { VariantProps } from "class-variance-authority";
|
|
3
3
|
declare const descriptionListVariants: (props?: ({
|
|
4
|
-
variant?: "
|
|
4
|
+
variant?: "outline" | "default" | null | undefined;
|
|
5
5
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
6
6
|
export type DescriptionListProps = Omit<ComponentProps<'dl'>, 'className'> & VariantProps<typeof descriptionListVariants> & {
|
|
7
7
|
className?: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { Button } from "../button/button.js";
|
|
2
3
|
import { useMessages } from "../../config/config-provider.js";
|
|
3
4
|
import { cn } from "../../lib/cn.js";
|
|
4
|
-
import { Button } from "../button/button.js";
|
|
5
5
|
import { createContext, useContext } from "react";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { XIcon } from "lucide-react";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { Button } from "../button/button.js";
|
|
2
3
|
import { useMessages } from "../../config/config-provider.js";
|
|
3
4
|
import { cn } from "../../lib/cn.js";
|
|
4
|
-
import { Button } from "../button/button.js";
|
|
5
5
|
import { createContext, useContext } from "react";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { XIcon } from "lucide-react";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ComponentProps } from "react";
|
|
2
2
|
import { VariantProps } from "class-variance-authority";
|
|
3
3
|
declare const emptyMediaVariants: (props?: ({
|
|
4
|
-
variant?: "
|
|
4
|
+
variant?: "icon" | "default" | null | undefined;
|
|
5
5
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
6
6
|
export type EmptyProps = ComponentProps<'div'>;
|
|
7
7
|
export type EmptyHeaderProps = ComponentProps<'div'>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useMessages } from "../../config/config-provider.js";
|
|
3
2
|
import { Field, FieldDescription, FieldError, FieldLabel, Fieldset, FieldsetLegend } from "./field.js";
|
|
4
3
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip/tooltip.js";
|
|
4
|
+
import { useMessages } from "../../config/config-provider.js";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { InfoIcon } from "lucide-react";
|
|
7
7
|
// src/components/field/labeled-field.tsx
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ButtonProps } from "../button/button.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
export type FileUploadRejectionCode = 'file-invalid-type' | 'file-too-large' | 'file-too-small' | 'too-many-files' | 'custom';
|
|
4
|
+
export interface FileUploadRejection {
|
|
5
|
+
file: File;
|
|
6
|
+
code: FileUploadRejectionCode;
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
9
|
+
export interface FileUploadProps {
|
|
10
|
+
/** Controlled selection. Pass an empty array to clear it. */
|
|
11
|
+
value?: readonly File[];
|
|
12
|
+
defaultValue?: readonly File[];
|
|
13
|
+
onValueChange?: (files: File[]) => void;
|
|
14
|
+
onFileReject?: (rejections: FileUploadRejection[]) => void;
|
|
15
|
+
/** Native accept syntax, also checked after selection: extensions, MIME types or MIME wildcards. */
|
|
16
|
+
accept?: string;
|
|
17
|
+
/** Appends files to the selection. The default single-file mode replaces the selected file. */
|
|
18
|
+
multiple?: boolean;
|
|
19
|
+
/** Maximum total number of files in multiple mode. */
|
|
20
|
+
maxFiles?: number;
|
|
21
|
+
/** Inclusive per-file size limits, in bytes. */
|
|
22
|
+
minSize?: number;
|
|
23
|
+
maxSize?: number;
|
|
24
|
+
/** Return an error message to reject a file. Async content checks belong to the caller. */
|
|
25
|
+
validateFile?: (file: File) => string | null | undefined;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
/** Prevents selection and removal while the caller reads, validates or uploads files. */
|
|
28
|
+
loading?: boolean;
|
|
29
|
+
/** Upload percentage while loading. Omit it or pass null for indeterminate progress. */
|
|
30
|
+
progress?: number | null;
|
|
31
|
+
/** Trigger content. Defaults to the translated "Select file" or "Select files". */
|
|
32
|
+
children?: ReactNode;
|
|
33
|
+
/** Set false for a standalone upload button beside an editor or a custom preview. */
|
|
34
|
+
showFileList?: boolean;
|
|
35
|
+
/** Replaces the file icon and filename, while keeping the built-in remove button. */
|
|
36
|
+
renderFile?: (file: File) => ReactNode;
|
|
37
|
+
/** An external error, for example from reading or uploading a file. */
|
|
38
|
+
error?: ReactNode;
|
|
39
|
+
/** Props for the visible trigger, including id, ref, aria-label and onBlur. */
|
|
40
|
+
triggerProps?: Omit<ButtonProps, 'children' | 'disabled' | 'loading' | 'render' | 'nativeButton' | 'type' | 'onClick' | 'value' | 'defaultValue' | 'name'>;
|
|
41
|
+
className?: string;
|
|
42
|
+
}
|
|
43
|
+
declare function FileUpload(props: FileUploadProps): import("react").JSX.Element;
|
|
44
|
+
export { FileUpload };
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Button } from "../button/button.js";
|
|
3
|
+
import { Progress } from "../progress/progress.js";
|
|
4
|
+
import { useMessages } from "../../config/config-provider.js";
|
|
5
|
+
import { useFieldLabelWithText } from "../../hooks/use-field-label-with-text.js";
|
|
6
|
+
import { cn } from "../../lib/cn.js";
|
|
7
|
+
import { useId, useRef, useState } from "react";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { FileIcon, UploadIcon, XIcon } from "lucide-react";
|
|
10
|
+
import { Field } from "@base-ui/react/field";
|
|
11
|
+
// src/components/file-upload/file-upload.tsx
|
|
12
|
+
function acceptsFile(file, accept) {
|
|
13
|
+
const rules = accept?.split(",").map((rule) => rule.trim().toLowerCase()).filter(Boolean);
|
|
14
|
+
if (!rules?.length) return true;
|
|
15
|
+
const name = file.name.toLowerCase();
|
|
16
|
+
const type = file.type.toLowerCase();
|
|
17
|
+
return rules.some((rule) => {
|
|
18
|
+
if (rule.startsWith(".")) return name.endsWith(rule);
|
|
19
|
+
if (rule.endsWith("/*")) return type.startsWith(rule.slice(0, -1));
|
|
20
|
+
return type === rule;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function isSameFile(a, b) {
|
|
24
|
+
return a.name === b.name && a.size === b.size && a.type === b.type && a.lastModified === b.lastModified;
|
|
25
|
+
}
|
|
26
|
+
function FileUpload(props) {
|
|
27
|
+
const { value, defaultValue = [], onValueChange, onFileReject, accept, multiple = false, maxFiles = Infinity, minSize, maxSize, validateFile, disabled = false, loading = false, progress = null, children, showFileList = true, renderFile, error, triggerProps, className } = props;
|
|
28
|
+
const { id: triggerId, ref: forwardedRef, "aria-describedby": ariaDescribedby, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledby, ...buttonProps } = triggerProps ?? {};
|
|
29
|
+
const messages = useMessages().fileUpload;
|
|
30
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
|
|
31
|
+
const [rejections, setRejections] = useState([]);
|
|
32
|
+
const inputRef = useRef(null);
|
|
33
|
+
const { ref: triggerRef, textId, labelledBy } = useFieldLabelWithText(ariaLabelledby);
|
|
34
|
+
const errorId = useId();
|
|
35
|
+
const listId = useId();
|
|
36
|
+
const controlled = "value" in props;
|
|
37
|
+
const files = (controlled ? value : uncontrolledValue) ?? [];
|
|
38
|
+
const unavailable = disabled || loading;
|
|
39
|
+
const hasError = Boolean(error) || rejections.length > 0;
|
|
40
|
+
function updateValue(next) {
|
|
41
|
+
if (!controlled) setUncontrolledValue(next);
|
|
42
|
+
onValueChange?.(next);
|
|
43
|
+
}
|
|
44
|
+
function selectFiles(selected) {
|
|
45
|
+
if (unavailable || selected.length === 0) return;
|
|
46
|
+
const next = multiple ? [...files] : [];
|
|
47
|
+
const rejected = [];
|
|
48
|
+
const limit = multiple ? maxFiles : 1;
|
|
49
|
+
let added = false;
|
|
50
|
+
for (const file of selected) {
|
|
51
|
+
let rejection;
|
|
52
|
+
if (!acceptsFile(file, accept)) rejection = {
|
|
53
|
+
code: "file-invalid-type",
|
|
54
|
+
message: messages.invalidType({
|
|
55
|
+
name: file.name,
|
|
56
|
+
accept
|
|
57
|
+
})
|
|
58
|
+
};
|
|
59
|
+
else if (maxSize !== void 0 && file.size > maxSize) rejection = {
|
|
60
|
+
code: "file-too-large",
|
|
61
|
+
message: messages.tooLarge({
|
|
62
|
+
name: file.name,
|
|
63
|
+
maxSize
|
|
64
|
+
})
|
|
65
|
+
};
|
|
66
|
+
else if (minSize !== void 0 && file.size < minSize) rejection = {
|
|
67
|
+
code: "file-too-small",
|
|
68
|
+
message: messages.tooSmall({
|
|
69
|
+
name: file.name,
|
|
70
|
+
minSize
|
|
71
|
+
})
|
|
72
|
+
};
|
|
73
|
+
else {
|
|
74
|
+
const message = validateFile?.(file);
|
|
75
|
+
if (message) rejection = {
|
|
76
|
+
code: "custom",
|
|
77
|
+
message
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (!rejection && multiple && next.some((existing) => isSameFile(existing, file))) continue;
|
|
81
|
+
if (!rejection && next.length >= limit) rejection = {
|
|
82
|
+
code: "too-many-files",
|
|
83
|
+
message: messages.tooMany({ maxFiles: limit })
|
|
84
|
+
};
|
|
85
|
+
if (rejection) rejected.push({
|
|
86
|
+
file,
|
|
87
|
+
...rejection
|
|
88
|
+
});
|
|
89
|
+
else {
|
|
90
|
+
next.push(file);
|
|
91
|
+
added = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
setRejections(rejected);
|
|
95
|
+
if (added) updateValue(next);
|
|
96
|
+
if (rejected.length) onFileReject?.(rejected);
|
|
97
|
+
}
|
|
98
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
99
|
+
className: cn("flex min-w-0 flex-col gap-2 text-sm text-foreground", className),
|
|
100
|
+
"data-slot": "file-upload",
|
|
101
|
+
"aria-busy": loading || void 0,
|
|
102
|
+
children: [
|
|
103
|
+
/* @__PURE__ */ jsx("input", {
|
|
104
|
+
ref: inputRef,
|
|
105
|
+
type: "file",
|
|
106
|
+
hidden: true,
|
|
107
|
+
accept,
|
|
108
|
+
multiple,
|
|
109
|
+
disabled: unavailable,
|
|
110
|
+
"data-slot": "file-upload-input",
|
|
111
|
+
onChange: (event) => {
|
|
112
|
+
const selected = Array.from(event.currentTarget.files ?? []);
|
|
113
|
+
event.currentTarget.value = "";
|
|
114
|
+
selectFiles(selected);
|
|
115
|
+
}
|
|
116
|
+
}),
|
|
117
|
+
/* @__PURE__ */ jsx(Field.Control, {
|
|
118
|
+
id: triggerId,
|
|
119
|
+
ref: forwardedRef,
|
|
120
|
+
value: files.map((file) => file.name).join(", "),
|
|
121
|
+
disabled: unavailable,
|
|
122
|
+
"aria-invalid": hasError || ariaInvalid || void 0,
|
|
123
|
+
"aria-describedby": [
|
|
124
|
+
ariaDescribedby,
|
|
125
|
+
showFileList && files.length ? listId : void 0,
|
|
126
|
+
hasError ? errorId : void 0
|
|
127
|
+
].filter(Boolean).join(" ") || void 0,
|
|
128
|
+
render: /* @__PURE__ */ jsxs(Button, {
|
|
129
|
+
variant: "outline",
|
|
130
|
+
...buttonProps,
|
|
131
|
+
"aria-labelledby": labelledBy,
|
|
132
|
+
ref: triggerRef,
|
|
133
|
+
type: "button",
|
|
134
|
+
className: cn("self-start aria-invalid:border-destructive aria-invalid:ring-destructive/40", triggerProps?.className),
|
|
135
|
+
disabled: unavailable,
|
|
136
|
+
loading,
|
|
137
|
+
onClick: () => inputRef.current?.click(),
|
|
138
|
+
"data-slot": "file-upload-trigger",
|
|
139
|
+
children: [!loading && /* @__PURE__ */ jsx(UploadIcon, { "aria-hidden": "true" }), /* @__PURE__ */ jsx("span", {
|
|
140
|
+
id: textId,
|
|
141
|
+
children: children ?? (multiple ? messages.selectFiles : messages.selectFile)
|
|
142
|
+
})]
|
|
143
|
+
})
|
|
144
|
+
}),
|
|
145
|
+
showFileList && files.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
146
|
+
id: listId,
|
|
147
|
+
"aria-label": messages.selectedFiles,
|
|
148
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
149
|
+
"data-slot": "file-upload-list",
|
|
150
|
+
children: files.map((file, index) => /* @__PURE__ */ jsxs("li", {
|
|
151
|
+
className: "flex min-w-0 items-center gap-2 rounded-sm border border-input px-3 py-1",
|
|
152
|
+
"data-slot": "file-upload-item",
|
|
153
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
154
|
+
className: "flex min-w-0 flex-1 items-center gap-2",
|
|
155
|
+
children: renderFile ? renderFile(file) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FileIcon, {
|
|
156
|
+
"aria-hidden": "true",
|
|
157
|
+
className: "size-3.5 shrink-0 text-muted-foreground"
|
|
158
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
159
|
+
className: "min-w-0 break-all",
|
|
160
|
+
children: file.name
|
|
161
|
+
})] })
|
|
162
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
163
|
+
type: "button",
|
|
164
|
+
variant: "ghost",
|
|
165
|
+
size: "icon",
|
|
166
|
+
className: "size-6",
|
|
167
|
+
"aria-label": messages.remove({ name: file.name }),
|
|
168
|
+
disabled: unavailable,
|
|
169
|
+
"data-slot": "file-upload-remove",
|
|
170
|
+
onClick: () => {
|
|
171
|
+
if (unavailable) return;
|
|
172
|
+
setRejections([]);
|
|
173
|
+
updateValue(files.filter((_, fileIndex) => fileIndex !== index));
|
|
174
|
+
triggerRef.current?.focus();
|
|
175
|
+
},
|
|
176
|
+
children: /* @__PURE__ */ jsx(XIcon, { "aria-hidden": "true" })
|
|
177
|
+
})]
|
|
178
|
+
}, `${file.name}-${file.size}-${file.lastModified}-${index}`))
|
|
179
|
+
}),
|
|
180
|
+
loading && /* @__PURE__ */ jsx(Progress, {
|
|
181
|
+
value: progress,
|
|
182
|
+
size: "xs",
|
|
183
|
+
"aria-label": messages.progress
|
|
184
|
+
}),
|
|
185
|
+
hasError && /* @__PURE__ */ jsx("div", {
|
|
186
|
+
id: errorId,
|
|
187
|
+
role: "alert",
|
|
188
|
+
className: "text-sm break-all text-destructive-text",
|
|
189
|
+
"data-slot": "file-upload-error",
|
|
190
|
+
children: error || rejections.map((rejection, index) => /* @__PURE__ */ jsx("p", { children: rejection.message }, index))
|
|
191
|
+
})
|
|
192
|
+
]
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
export { FileUpload };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxContent, ComboboxInput, ComboboxItem, ComboboxValue } from "../combobox/combobox.js";
|
|
3
|
-
import { NumberField } from "../number-field/number-field.js";
|
|
4
|
-
import { Select, SelectContent, SelectTrigger, SelectValue } from "../select/select.js";
|
|
5
3
|
import { useFieldContext } from "./form-context.js";
|
|
6
4
|
import { FormItem } from "./form-item.js";
|
|
5
|
+
import { NumberField } from "../number-field/number-field.js";
|
|
6
|
+
import { Select, SelectContent, SelectTrigger, SelectValue } from "../select/select.js";
|
|
7
7
|
import { useRef } from "react";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
// src/components/form/choice-fields.tsx
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FormItemProps } from "./form-item.js";
|
|
2
1
|
import { DatePickerProps, DateRangePickerProps } from "../date-picker/date-picker.js";
|
|
2
|
+
import { FormItemProps } from "./form-item.js";
|
|
3
3
|
type BoundPickerProps = 'value' | 'defaultValue' | 'onValueChange' | 'onOpenChange' | 'onBlur' | 'className';
|
|
4
4
|
export type DatePickerFieldProps = Omit<FormItemProps, 'layout'> & Omit<DatePickerProps, BoundPickerProps>;
|
|
5
5
|
export type DateRangePickerFieldProps = Omit<FormItemProps, 'layout'> & Omit<DateRangePickerProps, BoundPickerProps>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { Skeleton } from "../skeleton/skeleton.js";
|
|
3
2
|
import { useFieldContext } from "./form-context.js";
|
|
4
3
|
import { FormItem } from "./form-item.js";
|
|
4
|
+
import { Skeleton } from "../skeleton/skeleton.js";
|
|
5
5
|
import { Suspense, lazy, useRef } from "react";
|
|
6
6
|
import { jsx } from "react/jsx-runtime";
|
|
7
7
|
// src/components/form/date-fields.tsx
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { LabeledField } from "../field/labeled-field.js";
|
|
3
|
-
import { useFieldContext } from "./form-context.js";
|
|
4
3
|
import { formatFieldErrors } from "./errors.js";
|
|
4
|
+
import { useFieldContext } from "./form-context.js";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
// src/components/form/form-item.tsx
|
|
7
7
|
/** Reads the bound field and returns what FormItem and controls need. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { fieldContext, formContext } from "./form-context.js";
|
|
3
2
|
import { DatePickerField, DateRangePickerField } from "./date-fields.js";
|
|
3
|
+
import { fieldContext, formContext } from "./form-context.js";
|
|
4
4
|
import { LazyComboboxField, LazyFormNumberField, LazySelectField } from "./lazy-field-components.js";
|
|
5
5
|
import { SubmitButton } from "./submit-button.js";
|
|
6
6
|
import { TextField, TextareaField } from "./text-field.js";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { FormItem, FormItemLayout, FormItemProps, useFieldState } from "./form-item.js";
|
|
2
1
|
import { ComboboxField, ComboboxFieldProps, FormNumberField, NumberFieldFieldProps, SelectField, SelectFieldProps } from "./choice-fields.js";
|
|
3
2
|
import { DatePickerField, DatePickerFieldProps, DateRangePickerField, DateRangePickerFieldProps } from "./date-fields.js";
|
|
4
3
|
import { formatFieldErrors } from "./errors.js";
|
|
5
4
|
import { fieldContext, formContext, useFieldContext, useFormContext } from "./form-context.js";
|
|
5
|
+
import { FormItem, FormItemLayout, FormItemProps, useFieldState } from "./form-item.js";
|
|
6
|
+
import { fieldComponents, formComponents, useAppForm, withFieldGroup, withForm } from "./form.js";
|
|
6
7
|
import { SubmitButton, SubmitButtonProps } from "./submit-button.js";
|
|
7
8
|
import { TextField, TextFieldProps, TextareaField, TextareaFieldProps } from "./text-field.js";
|
|
8
9
|
import { CheckboxField, CheckboxFieldProps, RadioGroupField, RadioGroupFieldProps, SwitchField, SwitchFieldProps } from "./toggle-fields.js";
|
|
9
|
-
import { fieldComponents, formComponents, useAppForm, withFieldGroup, withForm } from "./form.js";
|
|
10
10
|
export { CheckboxField, type CheckboxFieldProps, ComboboxField, type ComboboxFieldProps, DatePickerField, type DatePickerFieldProps, DateRangePickerField, type DateRangePickerFieldProps, FormItem, type FormItemLayout, type FormItemProps, FormNumberField, type NumberFieldFieldProps, RadioGroupField, type RadioGroupFieldProps, SelectField, type SelectFieldProps, SubmitButton, type SubmitButtonProps, SwitchField, type SwitchFieldProps, TextField, type TextFieldProps, TextareaField, type TextareaFieldProps, fieldComponents, fieldContext, formComponents, formContext, formatFieldErrors, useAppForm, useFieldContext, useFieldState, useFormContext, withFieldGroup, withForm };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { fieldContext, formContext, useFieldContext, useFormContext } from "./form-context.js";
|
|
2
|
-
import { formatFieldErrors } from "./errors.js";
|
|
3
|
-
import { FormItem, useFieldState } from "./form-item.js";
|
|
4
1
|
import { ComboboxField, FormNumberField, SelectField } from "./choice-fields.js";
|
|
5
2
|
import { DatePickerField, DateRangePickerField } from "./date-fields.js";
|
|
3
|
+
import { formatFieldErrors } from "./errors.js";
|
|
4
|
+
import { fieldContext, formContext, useFieldContext, useFormContext } from "./form-context.js";
|
|
5
|
+
import { FormItem, useFieldState } from "./form-item.js";
|
|
6
|
+
import { fieldComponents, formComponents, useAppForm, withFieldGroup, withForm } from "./form.js";
|
|
6
7
|
import { SubmitButton } from "./submit-button.js";
|
|
7
8
|
import { TextField, TextareaField } from "./text-field.js";
|
|
8
9
|
import { CheckboxField, RadioGroupField, SwitchField } from "./toggle-fields.js";
|
|
9
|
-
import { fieldComponents, formComponents, useAppForm, withFieldGroup, withForm } from "./form.js";
|
|
10
10
|
export { CheckboxField, ComboboxField, DatePickerField, DateRangePickerField, FormItem, FormNumberField, RadioGroupField, SelectField, SubmitButton, SwitchField, TextField, TextareaField, fieldComponents, fieldContext, formComponents, formContext, formatFieldErrors, useAppForm, useFieldContext, useFieldState, useFormContext, withFieldGroup, withForm };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { Input } from "../input/input.js";
|
|
3
|
-
import { Textarea } from "../textarea/textarea.js";
|
|
4
2
|
import { useFieldContext } from "./form-context.js";
|
|
5
3
|
import { FormItem } from "./form-item.js";
|
|
4
|
+
import { Input } from "../input/input.js";
|
|
5
|
+
import { Textarea } from "../textarea/textarea.js";
|
|
6
6
|
import { jsx } from "react/jsx-runtime";
|
|
7
7
|
// src/components/form/text-field.tsx
|
|
8
8
|
function TextField({ label, description, labelTooltip, optional, optionalLabel, moreInfoLabel, className, ...inputProps }) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FormItemProps } from "./form-item.js";
|
|
2
1
|
import { CheckboxProps } from "../checkbox/checkbox.js";
|
|
2
|
+
import { FormItemProps } from "./form-item.js";
|
|
3
3
|
import { SwitchProps } from "../switch/switch.js";
|
|
4
4
|
import { ReactNode } from "react";
|
|
5
5
|
export type CheckboxFieldProps = Omit<FormItemProps, 'layout'> & Omit<CheckboxProps, 'name' | 'checked' | 'defaultChecked' | 'onCheckedChange' | 'onBlur' | 'className'>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { cn } from "../../lib/cn.js";
|
|
3
2
|
import { Checkbox } from "../checkbox/checkbox.js";
|
|
4
3
|
import { FieldLabel } from "../field/field.js";
|
|
5
|
-
import { RadioGroup, RadioGroupItem } from "../radio-group/radio-group.js";
|
|
6
|
-
import { Switch } from "../switch/switch.js";
|
|
7
4
|
import { useFieldContext } from "./form-context.js";
|
|
8
5
|
import { FormItem } from "./form-item.js";
|
|
6
|
+
import { RadioGroup, RadioGroupItem } from "../radio-group/radio-group.js";
|
|
7
|
+
import { Switch } from "../switch/switch.js";
|
|
8
|
+
import { cn } from "../../lib/cn.js";
|
|
9
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
import { Field } from "@base-ui/react/field";
|
|
11
11
|
// src/components/form/toggle-fields.tsx
|
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
* directly instead of collapsing it behind an ellipsis.
|
|
7
7
|
*/
|
|
8
8
|
export declare function getPageItems(pageIndex: number, pageCount: number): Array<number | 'ellipsis'>;
|
|
9
|
-
|
|
9
|
+
interface PaginationBaseProps {
|
|
10
10
|
pageIndex: number;
|
|
11
11
|
pageSize: number;
|
|
12
|
-
rowCount: number;
|
|
13
12
|
/** @default [10, 20, 50, 100] */
|
|
14
13
|
pageSizeOptions?: number[];
|
|
15
14
|
onPageIndexChange: (pageIndex: number) => void;
|
|
@@ -18,5 +17,22 @@ export interface PaginationProps {
|
|
|
18
17
|
label?: string;
|
|
19
18
|
className?: string;
|
|
20
19
|
}
|
|
21
|
-
/**
|
|
22
|
-
|
|
20
|
+
/**
|
|
21
|
+
* A list with a known total: the range, a window of page numbers, Previous and Next, and the page size.
|
|
22
|
+
* Renders nothing when `rowCount` is 0, matching the legacy footer's `total > 0` guard.
|
|
23
|
+
*/
|
|
24
|
+
export interface PaginationProps extends PaginationBaseProps {
|
|
25
|
+
rowCount: number;
|
|
26
|
+
hasNextPage?: never;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A list read by position, which has no total and cannot jump to a page. Shows the current page, Previous and Next, and the page
|
|
30
|
+
* size, and neither the range nor page numbers. `hasNextPage` says whether a page follows the current one. It renders for an empty
|
|
31
|
+
* page too, so the person can still step back.
|
|
32
|
+
*/
|
|
33
|
+
export interface PaginationByPositionProps extends PaginationBaseProps {
|
|
34
|
+
hasNextPage: boolean;
|
|
35
|
+
rowCount?: never;
|
|
36
|
+
}
|
|
37
|
+
export declare function Pagination(props: PaginationProps | PaginationByPositionProps): import("react").JSX.Element;
|
|
38
|
+
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../select/select.js";
|
|
2
3
|
import { useLocale, useMessages } from "../../config/config-provider.js";
|
|
3
4
|
import { cn } from "../../lib/cn.js";
|
|
4
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../select/select.js";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
7
7
|
// src/components/pagination/pagination.tsx
|
|
@@ -43,8 +43,44 @@ function getPageItems(pageIndex, pageCount) {
|
|
|
43
43
|
}
|
|
44
44
|
return items;
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
function Pagination(props) {
|
|
47
|
+
return props.hasNextPage === void 0 ? /* @__PURE__ */ jsx(PaginationWithTotal, { ...props }) : /* @__PURE__ */ jsx(PaginationByPosition, { ...props });
|
|
48
|
+
}
|
|
49
|
+
function PaginationByPosition({ pageIndex, pageSize, hasNextPage, pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS, onPageIndexChange, onPageSizeChange, label, className }) {
|
|
50
|
+
const messages = useMessages();
|
|
51
|
+
const locale = useLocale();
|
|
52
|
+
const formatter = new Intl.NumberFormat(locale);
|
|
53
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
54
|
+
className: cn("flex h-11 items-center justify-between px-[22px] py-3", className),
|
|
55
|
+
"data-slot": "pagination",
|
|
56
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
57
|
+
className: "text-sm text-foreground",
|
|
58
|
+
children: messages.pagination.page({ page: formatter.format(pageIndex + 1) })
|
|
59
|
+
}), /* @__PURE__ */ jsxs("nav", {
|
|
60
|
+
"aria-label": label ?? messages.pagination.label,
|
|
61
|
+
className: "flex items-center gap-1.5",
|
|
62
|
+
"data-slot": "pagination-nav",
|
|
63
|
+
children: [
|
|
64
|
+
/* @__PURE__ */ jsx(PageStepButton, {
|
|
65
|
+
direction: "previous",
|
|
66
|
+
disabled: pageIndex <= 0,
|
|
67
|
+
onClick: () => onPageIndexChange(pageIndex - 1)
|
|
68
|
+
}),
|
|
69
|
+
/* @__PURE__ */ jsx(PageStepButton, {
|
|
70
|
+
direction: "next",
|
|
71
|
+
disabled: !hasNextPage,
|
|
72
|
+
onClick: () => onPageIndexChange(pageIndex + 1)
|
|
73
|
+
}),
|
|
74
|
+
/* @__PURE__ */ jsx(PageSizeSelect, {
|
|
75
|
+
pageSize,
|
|
76
|
+
pageSizeOptions,
|
|
77
|
+
onPageSizeChange
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
})]
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function PaginationWithTotal({ pageIndex, pageSize, rowCount, pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS, onPageIndexChange, onPageSizeChange, label, className }) {
|
|
48
84
|
const messages = useMessages();
|
|
49
85
|
const locale = useLocale();
|
|
50
86
|
if (rowCount === 0) return null;
|
|
@@ -54,7 +90,6 @@ function Pagination({ pageIndex, pageSize, rowCount, pageSizeOptions = DEFAULT_P
|
|
|
54
90
|
const from = currentPage * pageSize + 1;
|
|
55
91
|
const to = Math.min(rowCount, (currentPage + 1) * pageSize);
|
|
56
92
|
const pageItems = getPageItems(currentPage, pageCount);
|
|
57
|
-
const effectivePageSizeOptions = pageSizeOptions.includes(pageSize) ? pageSizeOptions : [...pageSizeOptions, pageSize].sort((a, b) => a - b);
|
|
58
93
|
return /* @__PURE__ */ jsxs("div", {
|
|
59
94
|
className: cn("flex h-11 items-center justify-between px-[22px] py-3", className),
|
|
60
95
|
"data-slot": "pagination",
|
|
@@ -73,14 +108,10 @@ function Pagination({ pageIndex, pageSize, rowCount, pageSizeOptions = DEFAULT_P
|
|
|
73
108
|
className: "flex items-center gap-1.5",
|
|
74
109
|
"data-slot": "pagination-nav",
|
|
75
110
|
children: [
|
|
76
|
-
/* @__PURE__ */ jsx(
|
|
77
|
-
|
|
78
|
-
"aria-label": messages.pagination.previous,
|
|
111
|
+
/* @__PURE__ */ jsx(PageStepButton, {
|
|
112
|
+
direction: "previous",
|
|
79
113
|
disabled: currentPage <= 0,
|
|
80
|
-
onClick: () => onPageIndexChange(currentPage - 1)
|
|
81
|
-
className: cn("flex size-6 items-center justify-center rounded-sm border border-input outline-none focus-visible:ring-2 focus-visible:ring-ring/40", currentPage <= 0 && "cursor-not-allowed opacity-50"),
|
|
82
|
-
"data-slot": "pagination-previous",
|
|
83
|
-
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-3.5" })
|
|
114
|
+
onClick: () => onPageIndexChange(currentPage - 1)
|
|
84
115
|
}),
|
|
85
116
|
pageItems.map((item, index) => item === "ellipsis" ? /* @__PURE__ */ jsx("span", {
|
|
86
117
|
"aria-hidden": "true",
|
|
@@ -89,40 +120,60 @@ function Pagination({ pageIndex, pageSize, rowCount, pageSizeOptions = DEFAULT_P
|
|
|
89
120
|
children: "…"
|
|
90
121
|
}, `ellipsis-${index}`) : /* @__PURE__ */ jsx("button", {
|
|
91
122
|
type: "button",
|
|
92
|
-
"aria-label": messages.pagination.page({ page:
|
|
123
|
+
"aria-label": messages.pagination.page({ page: String(item + 1) }),
|
|
93
124
|
"aria-current": item === currentPage ? "page" : void 0,
|
|
94
125
|
onClick: () => onPageIndexChange(item),
|
|
95
126
|
className: cn("size-6 rounded-sm text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring/40", item === currentPage ? "bg-primary text-primary-foreground" : "hover:bg-accent-hover"),
|
|
96
127
|
"data-slot": "pagination-page",
|
|
97
128
|
children: item + 1
|
|
98
129
|
}, item)),
|
|
99
|
-
/* @__PURE__ */ jsx(
|
|
100
|
-
|
|
101
|
-
"aria-label": messages.pagination.next,
|
|
130
|
+
/* @__PURE__ */ jsx(PageStepButton, {
|
|
131
|
+
direction: "next",
|
|
102
132
|
disabled: currentPage >= pageCount - 1,
|
|
103
|
-
onClick: () => onPageIndexChange(currentPage + 1)
|
|
104
|
-
className: cn("flex size-6 items-center justify-center rounded-sm border border-input outline-none focus-visible:ring-2 focus-visible:ring-ring/40", currentPage >= pageCount - 1 && "cursor-not-allowed opacity-50"),
|
|
105
|
-
"data-slot": "pagination-next",
|
|
106
|
-
children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-3.5" })
|
|
133
|
+
onClick: () => onPageIndexChange(currentPage + 1)
|
|
107
134
|
}),
|
|
108
|
-
/* @__PURE__ */
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
113
|
-
size: "sm",
|
|
114
|
-
className: "w-[110px]",
|
|
115
|
-
"aria-label": messages.pagination.pageSizeLabel,
|
|
116
|
-
children: /* @__PURE__ */ jsx(SelectValue, { className: "flex-1 text-center" })
|
|
117
|
-
}), /* @__PURE__ */ jsx(SelectContent, { children: effectivePageSizeOptions.map((size) => /* @__PURE__ */ jsx(SelectItem, {
|
|
118
|
-
value: String(size),
|
|
119
|
-
children: messages.pagination.pageSize({ size: formatter.format(size) })
|
|
120
|
-
}, size)) })]
|
|
135
|
+
/* @__PURE__ */ jsx(PageSizeSelect, {
|
|
136
|
+
pageSize,
|
|
137
|
+
pageSizeOptions,
|
|
138
|
+
onPageSizeChange
|
|
121
139
|
})
|
|
122
140
|
]
|
|
123
141
|
})]
|
|
124
142
|
});
|
|
125
143
|
}
|
|
144
|
+
function PageStepButton({ direction, disabled, onClick }) {
|
|
145
|
+
const messages = useMessages();
|
|
146
|
+
const Icon = direction === "previous" ? ChevronLeft : ChevronRight;
|
|
147
|
+
return /* @__PURE__ */ jsx("button", {
|
|
148
|
+
type: "button",
|
|
149
|
+
"aria-label": messages.pagination[direction],
|
|
150
|
+
disabled,
|
|
151
|
+
onClick,
|
|
152
|
+
className: cn("flex size-6 items-center justify-center rounded-sm border border-input outline-none focus-visible:ring-2 focus-visible:ring-ring/40", disabled && "cursor-not-allowed opacity-50"),
|
|
153
|
+
"data-slot": `pagination-${direction}`,
|
|
154
|
+
children: /* @__PURE__ */ jsx(Icon, { className: "size-3.5" })
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function PageSizeSelect({ pageSize, pageSizeOptions, onPageSizeChange }) {
|
|
158
|
+
const messages = useMessages();
|
|
159
|
+
const locale = useLocale();
|
|
160
|
+
const formatter = new Intl.NumberFormat(locale);
|
|
161
|
+
const options = pageSizeOptions.includes(pageSize) ? pageSizeOptions : [...pageSizeOptions, pageSize].sort((a, b) => a - b);
|
|
162
|
+
return /* @__PURE__ */ jsxs(Select, {
|
|
163
|
+
items: pageSizeItems(options, messages, formatter),
|
|
164
|
+
value: String(pageSize),
|
|
165
|
+
onValueChange: (value) => onPageSizeChange(Number(value)),
|
|
166
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
167
|
+
size: "sm",
|
|
168
|
+
className: "w-[110px]",
|
|
169
|
+
"aria-label": messages.pagination.pageSizeLabel,
|
|
170
|
+
children: /* @__PURE__ */ jsx(SelectValue, { className: "flex-1 text-center" })
|
|
171
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: options.map((size) => /* @__PURE__ */ jsx(SelectItem, {
|
|
172
|
+
value: String(size),
|
|
173
|
+
children: messages.pagination.pageSize({ size: formatter.format(size) })
|
|
174
|
+
}, size)) })]
|
|
175
|
+
});
|
|
176
|
+
}
|
|
126
177
|
function pageSizeItems(pageSizeOptions, messages, formatter) {
|
|
127
178
|
return Object.fromEntries(pageSizeOptions.map((size) => [String(size), messages.pagination.pageSize({ size: formatter.format(size) })]));
|
|
128
179
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RelativeDateRangeValue } from "./relative-date-range.js";
|
|
2
|
+
/** A quick range: its label and the two ends it fills in. */
|
|
3
|
+
export type RelativeDateRangePreset = RelativeDateRangeValue & {
|
|
4
|
+
label: string;
|
|
5
|
+
};
|
|
6
|
+
export type RelativeDateRangePickerProps = {
|
|
7
|
+
value: RelativeDateRangeValue;
|
|
8
|
+
onChange: (value: RelativeDateRangeValue) => void;
|
|
9
|
+
presets: readonly RelativeDateRangePreset[];
|
|
10
|
+
maxSpanSeconds: number;
|
|
11
|
+
now: () => number;
|
|
12
|
+
className?: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function RelativeDateRangePicker({ value, onChange, presets, maxSpanSeconds, now, className }: RelativeDateRangePickerProps): import("react").JSX.Element;
|