devnonla-ui 0.3.0 → 0.4.1

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 (48) hide show
  1. package/README.md +3 -3
  2. package/package.json +2 -1
  3. package/src/app/App.tsx +4 -4
  4. package/src/button/Button.tsx +1 -1
  5. package/src/button/ButtonCopy.tsx +11 -21
  6. package/src/calendar/Calendar.tsx +1 -1
  7. package/src/chat/AgentPanel.tsx +3 -3
  8. package/src/chat/index.ts +35 -51
  9. package/src/chat/message-ui/ChatError.tsx +2 -4
  10. package/src/chat/message-ui/ChatInput.tsx +5 -9
  11. package/src/chat/message-ui/ChatThinking.tsx +2 -3
  12. package/src/chat/message-ui/ChatUserMessage.tsx +4 -5
  13. package/src/chat/message-ui/ChatWelcome.tsx +3 -8
  14. package/src/chat/message-ui/MermaidBlock.tsx +7 -47
  15. package/src/chat/tool-ui/BackgroundTaskToolUI.tsx +8 -11
  16. package/src/chat/tool-ui/BackgroundTasksBar.tsx +13 -14
  17. package/src/chat/tool-ui/CallAgentToolUI.tsx +10 -4
  18. package/src/chat/tool-ui/ChatToolCall.tsx +11 -17
  19. package/src/chat/tool-ui/GetCurrentTimeToolUI.tsx +2 -2
  20. package/src/chat/tool-ui/ReadSkillToolUI.tsx +3 -3
  21. package/src/chat/tool-ui/RunJsToolUI.tsx +2 -2
  22. package/src/chat/tool-ui/ToolUiTrailing.tsx +3 -7
  23. package/src/chat/tool-ui/WebFetchToolUI.tsx +22 -13
  24. package/src/checkbox/Checkbox.tsx +1 -1
  25. package/src/codeblock/CodeBlock.tsx +17 -19
  26. package/src/colorpicker/ColorPicker.tsx +7 -7
  27. package/src/datepicker/DatePicker.tsx +3 -3
  28. package/src/desktop/DesktopHeader.tsx +15 -20
  29. package/src/desktop/DesktopIcon.tsx +3 -9
  30. package/src/desktop/DesktopWindow.tsx +54 -30
  31. package/src/desktop/MeadowDesktop.tsx +1 -1
  32. package/src/form/Form.tsx +1 -1
  33. package/src/form/FormItem.tsx +2 -2
  34. package/src/form/index.ts +23 -24
  35. package/src/index.ts +167 -199
  36. package/src/input/Input.tsx +14 -12
  37. package/src/input/SearchInput.tsx +5 -2
  38. package/src/lib/sizes.ts +1 -1
  39. package/src/modal/Modal.tsx +3 -0
  40. package/src/pagination/Pagination.tsx +528 -51
  41. package/src/scroll/OverlayScroll.tsx +58 -37
  42. package/src/select/Select.tsx +2 -2
  43. package/src/spin/Spin.tsx +14 -17
  44. package/src/splitter/Splitter.tsx +3 -3
  45. package/src/styles.css +13 -11
  46. package/src/switch/Switch.tsx +1 -1
  47. package/src/table/Table.tsx +43 -18
  48. package/src/timepicker/TimePicker.tsx +2 -2
@@ -1,13 +1,16 @@
1
- import { type PointerEvent as ReactPointerEvent, type ReactNode, type Ref, type UIEventHandler, useCallback, useLayoutEffect, useRef, useState } from "react";
1
+ import { type ReactNode, type PointerEvent as ReactPointerEvent, type Ref, type UIEventHandler, useCallback, useLayoutEffect, useRef, useState } from "react";
2
2
  import { cn } from "../lib/cn";
3
3
 
4
4
  export type OverlayScrollVisibility = "hover" | "always";
5
5
 
6
6
  const HIDE_MS = 1000;
7
+ const MIN_THUMB = 32;
7
8
 
8
9
  export type OverlayScrollProps = {
9
10
  /** `hover` shows the thumb on hover / while scrolling. `always` keeps it visible. */
10
11
  visibility?: OverlayScrollVisibility;
12
+ /** Size to content (honors max-height on `className`) instead of filling the parent. */
13
+ autoHeight?: boolean;
11
14
  className?: string;
12
15
  innerClassName?: string;
13
16
  children?: ReactNode;
@@ -15,7 +18,10 @@ export type OverlayScrollProps = {
15
18
  scrollRef?: Ref<HTMLDivElement>;
16
19
  };
17
20
 
18
- type Thumb = { top: number; height: number; shown: boolean };
21
+ type Thumb = { offset: number; size: number; shown: boolean };
22
+ type Axis = "x" | "y";
23
+
24
+ const HIDDEN: Thumb = { offset: 0, size: 0, shown: false };
19
25
 
20
26
  function assignRef(ref: Ref<HTMLDivElement> | undefined, node: HTMLDivElement | null) {
21
27
  if (!ref) return;
@@ -23,27 +29,36 @@ function assignRef(ref: Ref<HTMLDivElement> | undefined, node: HTMLDivElement |
23
29
  else ref.current = node;
24
30
  }
25
31
 
26
- export function OverlayScroll({ visibility = "hover", className, innerClassName, children, onScroll, scrollRef }: OverlayScrollProps) {
32
+ function measure(scroll: number, scrollSize: number, client: number): Thumb {
33
+ const overflow = scrollSize - client;
34
+ if (overflow <= 1) return HIDDEN;
35
+ const size = Math.max(MIN_THUMB, (client / scrollSize) * client);
36
+ const offset = (scroll / overflow) * (client - size);
37
+ return { offset, size, shown: true };
38
+ }
39
+
40
+ function canScrollX(el: HTMLDivElement) {
41
+ const overflow = getComputedStyle(el).overflowX;
42
+ return overflow === "auto" || overflow === "scroll";
43
+ }
44
+
45
+ export function OverlayScroll({ visibility = "hover", autoHeight = false, className, innerClassName, children, onScroll, scrollRef }: OverlayScrollProps) {
27
46
  const elRef = useRef<HTMLDivElement | null>(null);
28
47
  const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
29
- const thumbRef = useRef<Thumb>({ top: 0, height: 0, shown: false });
48
+ const vThumbRef = useRef<Thumb>(HIDDEN);
49
+ const hThumbRef = useRef<Thumb>(HIDDEN);
30
50
  const [scrolling, setScrolling] = useState(false);
31
51
  const [dragging, setDragging] = useState(false);
32
- const [thumb, setThumb] = useState<Thumb>({ top: 0, height: 0, shown: false });
33
- thumbRef.current = thumb;
52
+ const [vThumb, setVThumb] = useState<Thumb>(HIDDEN);
53
+ const [hThumb, setHThumb] = useState<Thumb>(HIDDEN);
54
+ vThumbRef.current = vThumb;
55
+ hThumbRef.current = hThumb;
34
56
 
35
57
  const updateThumb = useCallback(() => {
36
58
  const el = elRef.current;
37
59
  if (!el) return;
38
- const { scrollTop, scrollHeight, clientHeight } = el;
39
- const overflow = scrollHeight - clientHeight;
40
- if (overflow <= 1) {
41
- setThumb((prev) => (prev.shown ? { top: 0, height: 0, shown: false } : prev));
42
- return;
43
- }
44
- const height = Math.max(32, (clientHeight / scrollHeight) * clientHeight);
45
- const top = (scrollTop / overflow) * (clientHeight - height);
46
- setThumb({ top, height, shown: true });
60
+ setVThumb(measure(el.scrollTop, el.scrollHeight, el.clientHeight));
61
+ setHThumb(canScrollX(el) ? measure(el.scrollLeft, el.scrollWidth, el.clientWidth) : HIDDEN);
47
62
  }, []);
48
63
 
49
64
  const setNode = useCallback(
@@ -64,7 +79,7 @@ export function OverlayScroll({ visibility = "hover", className, innerClassName,
64
79
  const child = el.firstElementChild;
65
80
  if (child) ro.observe(child);
66
81
  return () => ro.disconnect();
67
- }, [updateThumb]);
82
+ }, [updateThumb, innerClassName, autoHeight]);
68
83
 
69
84
  useLayoutEffect(() => {
70
85
  return () => {
@@ -82,27 +97,30 @@ export function OverlayScroll({ visibility = "hover", className, innerClassName,
82
97
  onScroll?.(e);
83
98
  };
84
99
 
85
- const onThumbPointerDown = (e: ReactPointerEvent<HTMLDivElement>) => {
100
+ const onThumbPointerDown = (axis: Axis) => (e: ReactPointerEvent<HTMLDivElement>) => {
86
101
  const el = elRef.current;
87
- const current = thumbRef.current;
102
+ const current = axis === "y" ? vThumbRef.current : hThumbRef.current;
88
103
  if (!el || !current.shown || e.button !== 0) return;
89
104
  e.preventDefault();
90
105
  e.stopPropagation();
91
106
 
92
- const overflow = el.scrollHeight - el.clientHeight;
93
- const track = el.clientHeight - current.height;
107
+ const overflow = axis === "y" ? el.scrollHeight - el.clientHeight : el.scrollWidth - el.clientWidth;
108
+ const track = (axis === "y" ? el.clientHeight : el.clientWidth) - current.size;
94
109
  if (overflow <= 0 || track <= 0) return;
95
110
 
96
- const startY = e.clientY;
97
- const startTop = current.top;
111
+ const start = axis === "y" ? e.clientY : e.clientX;
112
+ const startOffset = current.offset;
98
113
  e.currentTarget.setPointerCapture(e.pointerId);
99
114
  setDragging(true);
100
115
  setScrolling(true);
101
116
 
102
117
  const onMove = (ev: PointerEvent) => {
103
118
  if (ev.pointerId !== e.pointerId) return;
104
- const nextTop = Math.min(track, Math.max(0, startTop + (ev.clientY - startY)));
105
- el.scrollTop = (nextTop / track) * overflow;
119
+ const delta = (axis === "y" ? ev.clientY : ev.clientX) - start;
120
+ const next = Math.min(track, Math.max(0, startOffset + delta));
121
+ const pos = (next / track) * overflow;
122
+ if (axis === "y") el.scrollTop = pos;
123
+ else el.scrollLeft = pos;
106
124
  };
107
125
  const onUp = (ev: PointerEvent) => {
108
126
  if (ev.pointerId !== e.pointerId) return;
@@ -119,24 +137,27 @@ export function OverlayScroll({ visibility = "hover", className, innerClassName,
119
137
  };
120
138
 
121
139
  const thumbVisible = visibility === "always" || dragging || scrolling;
140
+ const thumbClass = cn(
141
+ "nonla-overlay-thumb absolute z-20 rounded-full transition-opacity duration-150",
142
+ visibility === "always" || dragging
143
+ ? "pointer-events-auto opacity-100"
144
+ : "pointer-events-none opacity-0 group-hover/scroll:pointer-events-auto group-hover/scroll:opacity-100 group-data-scrolling/scroll:pointer-events-auto group-data-scrolling/scroll:opacity-100",
145
+ );
122
146
 
123
147
  return (
124
148
  <div className={cn("group/scroll relative min-h-0 min-w-0 w-full overflow-hidden", className)} data-scrolling={thumbVisible ? "true" : undefined}>
125
- <div ref={setNode} onScroll={handleScroll} className={cn("nonla-scroll-hidden absolute inset-0 overflow-y-auto overflow-x-hidden [overflow-anchor:none]", innerClassName)}>
149
+ <div
150
+ ref={setNode}
151
+ onScroll={handleScroll}
152
+ className={cn("nonla-scroll-hidden overflow-y-auto overflow-x-hidden [overflow-anchor:none]", autoHeight ? "max-h-[inherit]" : "absolute inset-0", innerClassName)}
153
+ >
126
154
  {children}
127
155
  </div>
128
- {thumb.shown ? (
129
- <div
130
- aria-hidden
131
- onPointerDown={onThumbPointerDown}
132
- className={cn(
133
- "nonla-overlay-thumb absolute z-20 rounded-full transition-opacity duration-150",
134
- visibility === "always" || dragging
135
- ? "pointer-events-auto opacity-100"
136
- : "pointer-events-none opacity-0 group-hover/scroll:pointer-events-auto group-hover/scroll:opacity-100 group-data-scrolling/scroll:pointer-events-auto group-data-scrolling/scroll:opacity-100",
137
- )}
138
- style={{ right: 2, width: "var(--nonla-scrollbar-size)", height: thumb.height, transform: `translateY(${thumb.top}px)` }}
139
- />
156
+ {vThumb.shown ? (
157
+ <div aria-hidden onPointerDown={onThumbPointerDown("y")} className={thumbClass} style={{ right: 2, width: "var(--nonla-scrollbar-size)", height: vThumb.size, transform: `translateY(${vThumb.offset}px)` }} />
158
+ ) : null}
159
+ {hThumb.shown ? (
160
+ <div aria-hidden onPointerDown={onThumbPointerDown("x")} className={thumbClass} style={{ bottom: 2, height: "var(--nonla-scrollbar-size)", width: hThumb.size, transform: `translateX(${hThumb.offset}px)` }} />
140
161
  ) : null}
141
162
  </div>
142
163
  );
@@ -1,10 +1,10 @@
1
1
  import * as PopoverPrimitive from "@radix-ui/react-popover";
2
2
  import {
3
+ forwardRef,
4
+ isValidElement,
3
5
  type KeyboardEvent,
4
6
  type ReactElement,
5
7
  type ReactNode,
6
- forwardRef,
7
- isValidElement,
8
8
  useEffect,
9
9
  useLayoutEffect,
10
10
  useMemo,
package/src/spin/Spin.tsx CHANGED
@@ -1,14 +1,14 @@
1
1
  import { type ReactNode, useEffect, useState } from "react";
2
2
  import { cn } from "../lib/cn";
3
- import { type ControlSize, getSizeTokens, useControlSize } from "../lib/sizes";
3
+ import { type ControlSize, getSizeTokens, normalizeSize, useControlSize } from "../lib/sizes";
4
4
 
5
- export type SpinVariant = "default" | "agent" | "subAgent";
5
+ export type SpinVariant = "default" | "agent";
6
6
 
7
7
  export type SpinProps = {
8
8
  spinning?: boolean;
9
9
  tip?: ReactNode;
10
10
  size?: ControlSize;
11
- /** Visual style — agent / subAgent for AI run states. */
11
+ /** Visual style — `agent` is the 3×3 snake for AI run states. */
12
12
  variant?: SpinVariant;
13
13
  /** Replace the built-in indicator entirely. */
14
14
  indicator?: ReactNode;
@@ -75,11 +75,17 @@ function stepSnake(snake: Snake): Snake {
75
75
  return [next, head, mid];
76
76
  }
77
77
 
78
+ /** 3×3 metrics so `small` is visibly smaller than default (icon-aligned). */
79
+ function matrixMetrics(size: ControlSize | undefined): { cell: number; gap: number } {
80
+ const s = normalizeSize(size);
81
+ if (s === "small") return { cell: 3, gap: 1.5 };
82
+ if (s === "large") return { cell: 6, gap: 3 };
83
+ return { cell: 4, gap: 2 };
84
+ }
85
+
78
86
  /** 3×3 fixed dots — 3 adjacent lit cells crawl randomly (snake). */
79
- function MatrixSnake({ size, tickMs = 220 }: { size: ControlSize | undefined; tickMs?: number }) {
80
- const scale = getSizeTokens(size).icon / 16;
81
- const cell = Math.max(4, Math.round(5 * scale));
82
- const gap = Math.max(2, Math.round(2 * scale));
87
+ function MatrixSnake({ size, tickMs = 240 }: { size: ControlSize | undefined; tickMs?: number }) {
88
+ const { cell, gap } = matrixMetrics(size);
83
89
  const dim = cell * 3 + gap * 2;
84
90
  const [snake, setSnake] = useState<Snake>(() => randomSnake());
85
91
 
@@ -112,17 +118,8 @@ function MatrixSnake({ size, tickMs = 220 }: { size: ControlSize | undefined; ti
112
118
  );
113
119
  }
114
120
 
115
- function AgentSpinner({ size }: { size: ControlSize | undefined }) {
116
- return <MatrixSnake size={size} tickMs={240} />;
117
- }
118
-
119
- function SubAgentSpinner({ size }: { size: ControlSize | undefined }) {
120
- return <MatrixSnake size={size} tickMs={180} />;
121
- }
122
-
123
121
  function Spinner({ size, variant }: { size: ControlSize | undefined; variant: SpinVariant }) {
124
- if (variant === "agent") return <AgentSpinner size={size} />;
125
- if (variant === "subAgent") return <SubAgentSpinner size={size} />;
122
+ if (variant === "agent") return <MatrixSnake size={size} />;
126
123
  return <DefaultSpinner size={size} />;
127
124
  }
128
125
 
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  Children,
3
- Fragment,
4
3
  type CSSProperties,
4
+ Fragment,
5
+ isValidElement,
5
6
  type KeyboardEvent,
6
7
  type PointerEvent,
7
8
  type ReactElement,
8
9
  type ReactNode,
9
- isValidElement,
10
10
  useCallback,
11
11
  useEffect,
12
12
  useLayoutEffect,
@@ -15,7 +15,7 @@ import {
15
15
  useState,
16
16
  } from "react";
17
17
  import { cn } from "../lib/cn";
18
- import { applyDrag, distributeSizes, parseSplitterSize, scaleSizes, type SplitterSize } from "./sizes";
18
+ import { applyDrag, distributeSizes, parseSplitterSize, type SplitterSize, scaleSizes } from "./sizes";
19
19
 
20
20
  export type { SplitterSize };
21
21
  export type SplitterOrientation = "horizontal" | "vertical";
package/src/styles.css CHANGED
@@ -232,6 +232,8 @@
232
232
  --color-ink-active: var(--ink-active);
233
233
  --color-ink-line: var(--ink-line);
234
234
  --color-chat: var(--nonla-chat-bg);
235
+ --color-well: color-mix(in oklab, var(--foreground) 5.5%, var(--card));
236
+ --color-well-strong: color-mix(in oklab, var(--foreground) 10%, var(--card));
235
237
 
236
238
  --shadow-card: none;
237
239
  --shadow-whisper: none;
@@ -402,7 +404,7 @@
402
404
  }
403
405
  }
404
406
 
405
- /* ── Spin: agent / subAgent (3×3 random snake — color only) ─────────────── */
407
+ /* ── Spin: agent (3×3 random snake) ────────────────────────────────────── */
406
408
  .nonla-spin-matrix {
407
409
  align-content: center;
408
410
  justify-content: center;
@@ -413,12 +415,12 @@
413
415
  width: 100%;
414
416
  height: 100%;
415
417
  border-radius: 999px;
416
- background: var(--nonla-chip-hover);
418
+ background: color-mix(in oklab, var(--brand) 10%, transparent);
417
419
  transition: background 120ms linear;
418
420
  }
419
421
 
420
422
  .nonla-spin-matrix-cell.is-on {
421
- background: var(--nonla-ink);
423
+ background: var(--brand);
422
424
  }
423
425
 
424
426
  @media (prefers-reduced-motion: reduce) {
@@ -631,14 +633,6 @@
631
633
  backdrop-filter: blur(40px) saturate(108%);
632
634
  }
633
635
 
634
- /* Fluent Color desktop tile — same frost as the window, macOS squircle. */
635
- .nonla-desktop-icon-plate {
636
- overflow: hidden;
637
- border: 0;
638
- border-radius: 22%;
639
- box-shadow: none;
640
- }
641
-
642
636
  .nonla-btn-glass {
643
637
  border-color: var(--glass-border);
644
638
  background: var(--glass);
@@ -864,6 +858,14 @@
864
858
  animation: nonla-pop-out 100ms var(--nonla-ease-in);
865
859
  }
866
860
 
861
+ .nonla-chat-tool-result-ok {
862
+ background-image: linear-gradient(
863
+ 165deg,
864
+ color-mix(in oklab, var(--success) 14%, var(--card)) 0%,
865
+ color-mix(in oklab, var(--success) 5%, var(--card)) 100%
866
+ );
867
+ }
868
+
867
869
  .nonla-codeblock-pre code,
868
870
  .nonla-codeblock-pre .hljs {
869
871
  color: var(--foreground);
@@ -1,5 +1,5 @@
1
1
  import * as SwitchPrimitive from "@radix-ui/react-switch";
2
- import { type CSSProperties, type ComponentPropsWithoutRef, forwardRef } from "react";
2
+ import { type ComponentPropsWithoutRef, type CSSProperties, forwardRef } from "react";
3
3
  import { cn } from "../lib/cn";
4
4
  import { type ControlSize, controlRadiusVar, useControlSize } from "../lib/sizes";
5
5
 
@@ -3,7 +3,7 @@ import { Checkbox } from "../checkbox/Checkbox";
3
3
  import { Empty } from "../empty/Empty";
4
4
  import { cn } from "../lib/cn";
5
5
  import { type CanonicalSize, type ControlSize, useControlSize } from "../lib/sizes";
6
- import { Pagination } from "../pagination/Pagination";
6
+ import { Pagination, type PaginationProps } from "../pagination/Pagination";
7
7
  import { Spin } from "../spin/Spin";
8
8
 
9
9
  export type SortOrder = "ascend" | "descend" | null;
@@ -29,14 +29,29 @@ export type ColumnType<T> = {
29
29
 
30
30
  export type ColumnsType<T> = ColumnType<T>[];
31
31
 
32
- export type TablePaginationConfig = {
33
- current?: number;
34
- pageSize?: number;
35
- total?: number;
36
- onChange?: (page: number, pageSize: number) => void;
37
- hideOnSinglePage?: boolean;
38
- className?: string;
39
- };
32
+ export type TablePaginationConfig = Pick<
33
+ PaginationProps,
34
+ | "align"
35
+ | "current"
36
+ | "defaultCurrent"
37
+ | "pageSize"
38
+ | "defaultPageSize"
39
+ | "total"
40
+ | "onChange"
41
+ | "onShowSizeChange"
42
+ | "hideOnSinglePage"
43
+ | "className"
44
+ | "showSizeChanger"
45
+ | "showQuickJumper"
46
+ | "showTotal"
47
+ | "simple"
48
+ | "disabled"
49
+ | "size"
50
+ | "pageSizeOptions"
51
+ | "showLessItems"
52
+ | "showTitle"
53
+ | "itemRender"
54
+ >;
40
55
 
41
56
  export type TableRowSelection<T> = {
42
57
  selectedRowKeys?: Key[];
@@ -157,8 +172,8 @@ export function Table<T extends object = Record<string, unknown>>({ columns = []
157
172
  const [innerSelected, setInnerSelected] = useState<Key[]>(() => rowSelection?.defaultSelectedRowKeys ?? []);
158
173
  const selectedKeys = selectionControlled ? (rowSelection?.selectedRowKeys ?? []) : innerSelected;
159
174
 
160
- const [innerPage, setInnerPage] = useState(1);
161
- const [innerPageSize, setInnerPageSize] = useState(10);
175
+ const [innerPage, setInnerPage] = useState(() => (typeof pagination === "object" && pagination?.defaultCurrent) || 1);
176
+ const [innerPageSize, setInnerPageSize] = useState(() => (typeof pagination === "object" && pagination?.defaultPageSize) || 10);
162
177
 
163
178
  const initialSort = useMemo(() => {
164
179
  const col = columns.find((c) => c.defaultSortOrder);
@@ -179,8 +194,9 @@ export function Table<T extends object = Record<string, unknown>>({ columns = []
179
194
  const paginationOff = pagination === false;
180
195
  const pageConfig: TablePaginationConfig = paginationOff ? {} : (pagination ?? {});
181
196
  const pageControlled = pageConfig.current !== undefined;
197
+ const pageSizeControlled = pageConfig.pageSize !== undefined;
182
198
  const current = pageControlled ? (pageConfig.current ?? 1) : innerPage;
183
- const pageSize = pageConfig.pageSize ?? (pageControlled ? 10 : innerPageSize);
199
+ const pageSize = pageSizeControlled ? (pageConfig.pageSize ?? 10) : innerPageSize;
184
200
  const serverSide = pageConfig.total !== undefined;
185
201
 
186
202
  const sortedData = useMemo(() => {
@@ -227,10 +243,8 @@ export function Table<T extends object = Record<string, unknown>>({ columns = []
227
243
  };
228
244
 
229
245
  const handlePageChange = (page: number, nextSize: number) => {
230
- if (!pageControlled) {
231
- setInnerPage(page);
232
- setInnerPageSize(nextSize);
233
- }
246
+ if (!pageControlled) setInnerPage(page);
247
+ if (!pageSizeControlled) setInnerPageSize(nextSize);
234
248
  pageConfig.onChange?.(page, nextSize);
235
249
  onChange?.({ current: page, pageSize: nextSize, total }, {}, { columnKey: sortState.key ?? undefined, order: sortState.order });
236
250
  };
@@ -377,8 +391,19 @@ export function Table<T extends object = Record<string, unknown>>({ columns = []
377
391
  </Spin>
378
392
 
379
393
  {showPagination ? (
380
- <div className={cn("mt-4 flex justify-end", pageConfig.className)}>
381
- <Pagination current={current} pageSize={pageSize} total={total} size={size} onChange={handlePageChange} />
394
+ <div className={cn("mt-4", pageConfig.className)}>
395
+ <Pagination
396
+ {...pageConfig}
397
+ className={undefined}
398
+ hideOnSinglePage={false}
399
+ align={pageConfig.align ?? "end"}
400
+ current={current}
401
+ pageSize={pageSize}
402
+ total={total}
403
+ size={pageConfig.size ?? "small"}
404
+ showSizeChanger={pageConfig.showSizeChanger ?? false}
405
+ onChange={handlePageChange}
406
+ />
382
407
  </div>
383
408
  ) : null}
384
409
  </div>
@@ -2,11 +2,11 @@ import * as PopoverPrimitive from "@radix-ui/react-popover";
2
2
  import {
3
3
  type CSSProperties,
4
4
  type FocusEvent,
5
+ Fragment,
6
+ forwardRef,
5
7
  type KeyboardEvent,
6
8
  type MouseEvent,
7
9
  type PointerEvent,
8
- Fragment,
9
- forwardRef,
10
10
  useCallback,
11
11
  useEffect,
12
12
  useRef,