@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.
Files changed (74) hide show
  1. package/README.md +169 -8
  2. package/dist/components/anchor/anchor.d.ts +28 -0
  3. package/dist/components/anchor/anchor.js +249 -0
  4. package/dist/components/breadcrumb/breadcrumb.d.ts +15 -0
  5. package/dist/components/breadcrumb/breadcrumb.js +60 -0
  6. package/dist/components/button/button.d.ts +1 -1
  7. package/dist/components/button/button.js +1 -1
  8. package/dist/components/calendar/calendar.js +24 -3
  9. package/dist/components/cascader/cascader-data.d.ts +26 -0
  10. package/dist/components/cascader/cascader-data.js +110 -0
  11. package/dist/components/cascader/cascader.d.ts +41 -0
  12. package/dist/components/cascader/cascader.js +513 -0
  13. package/dist/components/code/code.d.ts +9 -0
  14. package/dist/components/code/code.js +20 -0
  15. package/dist/components/code/copy-button.js +42 -0
  16. package/dist/components/code-block/code-block.d.ts +16 -0
  17. package/dist/components/code-block/code-block.js +45 -0
  18. package/dist/components/code-block/highlight.js +67 -0
  19. package/dist/components/collapsible/collapsible.d.ts +14 -0
  20. package/dist/components/collapsible/collapsible.js +36 -0
  21. package/dist/components/combobox/combobox-loading.d.ts +18 -0
  22. package/dist/components/combobox/combobox-loading.js +148 -0
  23. package/dist/components/combobox/combobox.d.ts +13 -6
  24. package/dist/components/combobox/combobox.js +58 -10
  25. package/dist/components/data-table/data-table.d.ts +27 -3
  26. package/dist/components/data-table/data-table.js +155 -40
  27. package/dist/components/data-table/expandable-row.js +40 -0
  28. package/dist/components/data-table/filter-header.d.ts +28 -5
  29. package/dist/components/data-table/filter-header.js +103 -40
  30. package/dist/components/data-table/index.d.ts +3 -3
  31. package/dist/components/data-table/use-table-layout.js +84 -0
  32. package/dist/components/date-picker/date-picker.js +11 -2
  33. package/dist/components/description-list/description-list.d.ts +1 -1
  34. package/dist/components/dialog/dialog.js +1 -1
  35. package/dist/components/drawer/drawer.js +1 -1
  36. package/dist/components/empty/empty.d.ts +1 -1
  37. package/dist/components/field/labeled-field.js +1 -1
  38. package/dist/components/file-upload/file-upload.d.ts +44 -0
  39. package/dist/components/file-upload/file-upload.js +195 -0
  40. package/dist/components/form/choice-fields.js +2 -2
  41. package/dist/components/form/date-fields.d.ts +1 -1
  42. package/dist/components/form/date-fields.js +1 -1
  43. package/dist/components/form/form-item.js +1 -1
  44. package/dist/components/form/form.js +1 -1
  45. package/dist/components/form/index.d.ts +2 -2
  46. package/dist/components/form/index.js +4 -4
  47. package/dist/components/form/text-field.js +2 -2
  48. package/dist/components/form/toggle-fields.d.ts +1 -1
  49. package/dist/components/form/toggle-fields.js +3 -3
  50. package/dist/components/pagination/pagination.d.ts +20 -4
  51. package/dist/components/pagination/pagination.js +83 -32
  52. package/dist/components/relative-date-range-picker/relative-date-range-picker.d.ts +14 -0
  53. package/dist/components/relative-date-range-picker/relative-date-range-picker.js +245 -0
  54. package/dist/components/relative-date-range-picker/relative-date-range.d.ts +26 -0
  55. package/dist/components/relative-date-range-picker/relative-date-range.js +109 -0
  56. package/dist/components/select/select.js +1 -1
  57. package/dist/components/sidebar/sidebar.js +2 -2
  58. package/dist/components/steps/steps.d.ts +23 -0
  59. package/dist/components/steps/steps.js +86 -0
  60. package/dist/components/table/table.d.ts +2 -1
  61. package/dist/components/table/table.js +2 -1
  62. package/dist/components/toast/toast.d.ts +1 -1
  63. package/dist/components/toast/toast.js +1 -1
  64. package/dist/config/config-provider.d.ts +2 -2
  65. package/dist/config/messages.d.ts +115 -1
  66. package/dist/config/messages.js +140 -2
  67. package/dist/hooks/use-clipboard.d.ts +11 -0
  68. package/dist/hooks/use-clipboard.js +75 -0
  69. package/dist/hooks/use-field-label-with-text.js +32 -0
  70. package/dist/index.d.ts +26 -13
  71. package/dist/index.js +15 -4
  72. package/dist/tokens/index.d.ts +1 -1
  73. package/dist/tokens/index.js +1 -1
  74. 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?: "default" | "outline" | null | undefined;
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?: "default" | "icon" | null | undefined;
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
- export interface PaginationProps {
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
- /** Renders nothing when `rowCount` is 0, matching the legacy footer's `total > 0` guard. */
22
- export declare function Pagination({ pageIndex, pageSize, rowCount, pageSizeOptions, onPageIndexChange, onPageSizeChange, label, className }: PaginationProps): import("react").JSX.Element | null;
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
- /** Renders nothing when `rowCount` is 0, matching the legacy footer's `total > 0` guard. */
47
- function Pagination({ pageIndex, pageSize, rowCount, pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS, onPageIndexChange, onPageSizeChange, label, className }) {
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("button", {
77
- type: "button",
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: formatter.format(item + 1) }),
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("button", {
100
- type: "button",
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__ */ jsxs(Select, {
109
- items: pageSizeItems(effectivePageSizeOptions, messages, formatter),
110
- value: String(pageSize),
111
- onValueChange: (value) => onPageSizeChange(Number(value)),
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;