@eifi1/ui-kit 0.7.1 → 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 (222) hide show
  1. package/README.md +32 -17
  2. package/dist/chart.d.ts +3 -2
  3. package/dist/components/alert-banner.d.ts +59 -10
  4. package/dist/components/alert-banner.js +114 -8
  5. package/dist/components/alert-banner.js.map +1 -1
  6. package/dist/components/amount-input.d.ts +3 -0
  7. package/dist/components/button-group.d.ts +26 -0
  8. package/dist/components/button-group.js +49 -0
  9. package/dist/components/button-group.js.map +1 -0
  10. package/dist/components/calculator.d.ts +3 -0
  11. package/dist/components/chart-zoom.d.ts +63 -14
  12. package/dist/components/chart-zoom.js +82 -16
  13. package/dist/components/chart-zoom.js.map +1 -1
  14. package/dist/components/chip.d.ts +67 -2
  15. package/dist/components/chip.js +73 -8
  16. package/dist/components/chip.js.map +1 -1
  17. package/dist/components/confirm-dialog.d.ts +120 -0
  18. package/dist/components/confirm-dialog.js +96 -0
  19. package/dist/components/confirm-dialog.js.map +1 -0
  20. package/dist/components/copy-button.d.ts +62 -0
  21. package/dist/components/copy-button.js +95 -0
  22. package/dist/components/copy-button.js.map +1 -0
  23. package/dist/components/data-table-filter-popover.d.ts +1 -1
  24. package/dist/components/data-table-filters.d.ts +1 -1
  25. package/dist/components/data-table-pagination.d.ts +5 -1
  26. package/dist/components/data-table-pagination.js +71 -58
  27. package/dist/components/data-table-pagination.js.map +1 -1
  28. package/dist/components/data-table-sort.d.ts +28 -5
  29. package/dist/components/data-table-sort.js +11 -7
  30. package/dist/components/data-table-sort.js.map +1 -1
  31. package/dist/components/data-table.d.ts +1 -1
  32. package/dist/components/data-table.js +544 -423
  33. package/dist/components/data-table.js.map +1 -1
  34. package/dist/components/date-picker.d.ts +51 -5
  35. package/dist/components/date-picker.js +152 -40
  36. package/dist/components/date-picker.js.map +1 -1
  37. package/dist/components/description-list.d.ts +60 -0
  38. package/dist/components/description-list.js +112 -0
  39. package/dist/components/description-list.js.map +1 -0
  40. package/dist/components/disclosure.d.ts +37 -2
  41. package/dist/components/disclosure.js +14 -5
  42. package/dist/components/disclosure.js.map +1 -1
  43. package/dist/components/facing-pair.d.ts +2 -0
  44. package/dist/components/file-button.d.ts +17 -1
  45. package/dist/components/file-button.js +8 -1
  46. package/dist/components/file-button.js.map +1 -1
  47. package/dist/components/file-dropzone.d.ts +54 -9
  48. package/dist/components/file-dropzone.js +100 -70
  49. package/dist/components/file-dropzone.js.map +1 -1
  50. package/dist/components/floating-panel.d.ts +107 -0
  51. package/dist/components/floating-panel.js +206 -0
  52. package/dist/components/floating-panel.js.map +1 -0
  53. package/dist/components/full-bleed-dialog.d.ts +12 -1
  54. package/dist/components/full-bleed-dialog.js +26 -5
  55. package/dist/components/full-bleed-dialog.js.map +1 -1
  56. package/dist/components/modal.d.ts +6 -2
  57. package/dist/components/modal.js +2 -1
  58. package/dist/components/modal.js.map +1 -1
  59. package/dist/components/number-field.d.ts +3 -0
  60. package/dist/components/number-input.d.ts +3 -0
  61. package/dist/components/numpad-sheet.d.ts +3 -0
  62. package/dist/components/progress-bar.d.ts +54 -0
  63. package/dist/components/progress-bar.js +94 -0
  64. package/dist/components/progress-bar.js.map +1 -0
  65. package/dist/components/scroll-area.d.ts +53 -0
  66. package/dist/components/scroll-area.js +86 -0
  67. package/dist/components/scroll-area.js.map +1 -0
  68. package/dist/components/separator.d.ts +23 -0
  69. package/dist/components/separator.js +24 -0
  70. package/dist/components/separator.js.map +1 -0
  71. package/dist/components/series-chart-ticks.d.ts +34 -1
  72. package/dist/components/series-chart-ticks.js +83 -1
  73. package/dist/components/series-chart-ticks.js.map +1 -1
  74. package/dist/components/series-chart.d.ts +314 -19
  75. package/dist/components/series-chart.js +510 -120
  76. package/dist/components/series-chart.js.map +1 -1
  77. package/dist/components/skeleton.d.ts +36 -0
  78. package/dist/components/skeleton.js +35 -0
  79. package/dist/components/skeleton.js.map +1 -0
  80. package/dist/components/stat-tile.d.ts +60 -6
  81. package/dist/components/stat-tile.js +60 -26
  82. package/dist/components/stat-tile.js.map +1 -1
  83. package/dist/components/table.d.ts +83 -0
  84. package/dist/components/table.js +166 -0
  85. package/dist/components/table.js.map +1 -0
  86. package/dist/components/toggle-legend.d.ts +3 -2
  87. package/dist/components/toggle-legend.js +1 -1
  88. package/dist/components/toggle-legend.js.map +1 -1
  89. package/dist/components/tree-view.d.ts +129 -0
  90. package/dist/components/tree-view.js +376 -0
  91. package/dist/components/tree-view.js.map +1 -0
  92. package/dist/components/treemap.d.ts +9 -2
  93. package/dist/components/treemap.js +2 -1
  94. package/dist/components/treemap.js.map +1 -1
  95. package/dist/components/ui.d.ts +87 -13
  96. package/dist/components/ui.js +111 -29
  97. package/dist/components/ui.js.map +1 -1
  98. package/dist/components/use-table-state.d.ts +1 -1
  99. package/dist/{data-table-filters-noy0Abvi.d.ts → data-table-filters-CF1PXqjQ.d.ts} +73 -3
  100. package/dist/data-table.d.ts +2 -2
  101. package/dist/data-table.js.map +1 -1
  102. package/dist/hooks/use-copy-to-clipboard.d.ts +28 -0
  103. package/dist/hooks/use-copy-to-clipboard.js +81 -0
  104. package/dist/hooks/use-copy-to-clipboard.js.map +1 -0
  105. package/dist/hooks/use-debounce.d.ts +43 -0
  106. package/dist/hooks/use-debounce.js +56 -0
  107. package/dist/hooks/use-debounce.js.map +1 -0
  108. package/dist/hooks/use-file-drop.d.ts +72 -0
  109. package/dist/hooks/use-file-drop.js +57 -0
  110. package/dist/hooks/use-file-drop.js.map +1 -0
  111. package/dist/i18n/defaults.d.ts +3 -0
  112. package/dist/i18n/defaults.js +7 -1
  113. package/dist/i18n/defaults.js.map +1 -1
  114. package/dist/i18n/kit-labels.d.ts +15 -0
  115. package/dist/i18n/kit-labels.js +6 -2
  116. package/dist/i18n/kit-labels.js.map +1 -1
  117. package/dist/i18n/locales/de-CH-informal.d.ts +43 -0
  118. package/dist/i18n/locales/de-CH-informal.js +8 -0
  119. package/dist/i18n/locales/de-CH-informal.js.map +1 -0
  120. package/dist/i18n/locales/de-CH.d.ts +3 -0
  121. package/dist/i18n/locales/de-CH.js +1 -14
  122. package/dist/i18n/locales/de-CH.js.map +1 -1
  123. package/dist/i18n/locales/de-informal.d.ts +64 -0
  124. package/dist/i18n/locales/de-informal.js +37 -0
  125. package/dist/i18n/locales/de-informal.js.map +1 -0
  126. package/dist/i18n/locales/de.d.ts +3 -0
  127. package/dist/i18n/locales/de.js +25 -0
  128. package/dist/i18n/locales/de.js.map +1 -1
  129. package/dist/i18n/locales/es.d.ts +3 -0
  130. package/dist/i18n/locales/es.js +25 -0
  131. package/dist/i18n/locales/es.js.map +1 -1
  132. package/dist/i18n/locales/fr.d.ts +3 -0
  133. package/dist/i18n/locales/fr.js +25 -0
  134. package/dist/i18n/locales/fr.js.map +1 -1
  135. package/dist/i18n/locales/hu.d.ts +3 -0
  136. package/dist/i18n/locales/hu.js +25 -0
  137. package/dist/i18n/locales/hu.js.map +1 -1
  138. package/dist/i18n/locales/it.d.ts +3 -0
  139. package/dist/i18n/locales/it.js +25 -0
  140. package/dist/i18n/locales/it.js.map +1 -1
  141. package/dist/i18n/locales/zh.d.ts +3 -0
  142. package/dist/i18n/locales/zh.js +25 -0
  143. package/dist/i18n/locales/zh.js.map +1 -1
  144. package/dist/i18n/swiss.d.ts +13 -0
  145. package/dist/i18n/swiss.js +19 -0
  146. package/dist/i18n/swiss.js.map +1 -0
  147. package/dist/index.d.ts +25 -10
  148. package/dist/index.js +22 -0
  149. package/dist/index.js.map +1 -1
  150. package/dist/lib/dates.d.ts +37 -1
  151. package/dist/lib/dates.js +19 -0
  152. package/dist/lib/dates.js.map +1 -1
  153. package/dist/search/command-palette.d.ts +15 -1
  154. package/dist/search/command-palette.js +10 -2
  155. package/dist/search/command-palette.js.map +1 -1
  156. package/dist/wizard/stepper-nav.d.ts +35 -2
  157. package/dist/wizard/stepper-nav.js +54 -22
  158. package/dist/wizard/stepper-nav.js.map +1 -1
  159. package/dist/wizard/types.d.ts +107 -5
  160. package/dist/wizard/types.js +2 -1
  161. package/dist/wizard/types.js.map +1 -1
  162. package/dist/wizard/use-wizard.js +141 -21
  163. package/dist/wizard/use-wizard.js.map +1 -1
  164. package/dist/wizard/wizard-summary.d.ts +10 -2
  165. package/dist/wizard/wizard-summary.js +3 -1
  166. package/dist/wizard/wizard-summary.js.map +1 -1
  167. package/dist/wizard.d.ts +4 -1
  168. package/dist/wizard.js.map +1 -1
  169. package/package.json +1 -1
  170. package/src/components/alert-banner.tsx +211 -20
  171. package/src/components/button-group.tsx +75 -0
  172. package/src/components/chart-zoom.tsx +158 -20
  173. package/src/components/chip.tsx +160 -9
  174. package/src/components/confirm-dialog.tsx +242 -0
  175. package/src/components/copy-button.tsx +158 -0
  176. package/src/components/data-table-pagination.tsx +19 -6
  177. package/src/components/data-table-sort.ts +49 -10
  178. package/src/components/data-table.tsx +293 -24
  179. package/src/components/date-picker.tsx +262 -57
  180. package/src/components/description-list.tsx +174 -0
  181. package/src/components/disclosure.tsx +58 -5
  182. package/src/components/file-button.tsx +22 -2
  183. package/src/components/file-dropzone.tsx +198 -100
  184. package/src/components/floating-panel.tsx +341 -0
  185. package/src/components/full-bleed-dialog.tsx +48 -5
  186. package/src/components/modal.tsx +9 -2
  187. package/src/components/progress-bar.tsx +163 -0
  188. package/src/components/scroll-area.tsx +129 -0
  189. package/src/components/separator.tsx +37 -0
  190. package/src/components/series-chart-ticks.ts +135 -0
  191. package/src/components/series-chart.tsx +934 -67
  192. package/src/components/skeleton.tsx +65 -0
  193. package/src/components/stat-tile.tsx +160 -27
  194. package/src/components/table.tsx +263 -0
  195. package/src/components/toggle-legend.tsx +4 -3
  196. package/src/components/tree-view.tsx +589 -0
  197. package/src/components/treemap.tsx +10 -2
  198. package/src/components/ui.tsx +263 -39
  199. package/src/data-table.ts +2 -0
  200. package/src/hooks/use-copy-to-clipboard.ts +122 -0
  201. package/src/hooks/use-debounce.ts +105 -0
  202. package/src/hooks/use-file-drop.ts +123 -0
  203. package/src/i18n/defaults.ts +6 -0
  204. package/src/i18n/kit-labels.tsx +19 -0
  205. package/src/i18n/locales/de-CH-informal.ts +10 -0
  206. package/src/i18n/locales/de-CH.ts +1 -16
  207. package/src/i18n/locales/de-informal.ts +61 -0
  208. package/src/i18n/locales/de.ts +25 -0
  209. package/src/i18n/locales/es.ts +25 -0
  210. package/src/i18n/locales/fr.ts +25 -0
  211. package/src/i18n/locales/hu.ts +25 -0
  212. package/src/i18n/locales/it.ts +25 -0
  213. package/src/i18n/locales/zh.ts +25 -0
  214. package/src/i18n/swiss.ts +25 -0
  215. package/src/index.ts +29 -0
  216. package/src/lib/dates.ts +48 -0
  217. package/src/search/command-palette.tsx +29 -3
  218. package/src/wizard/stepper-nav.tsx +102 -28
  219. package/src/wizard/types.ts +106 -4
  220. package/src/wizard/use-wizard.ts +211 -31
  221. package/src/wizard/wizard-summary.tsx +24 -12
  222. package/src/wizard.ts +3 -2
@@ -0,0 +1,62 @@
1
+ import * as react from 'react';
2
+ import { ButtonHTMLAttributes } from 'react';
3
+ import { ButtonVariant, IconButtonSize } from './ui.js';
4
+ import { TooltipSide } from './tooltip.js';
5
+ import '../hooks/use-anchored-rect.js';
6
+
7
+ /** The words a copy button says. The visible three are short on purpose (they
8
+ * replace the button's own text in the `label` variant); the two announcements
9
+ * are the sentences a screen reader hears, since the button's focus never moves. */
10
+ interface CopyButtonLabels {
11
+ copy: string;
12
+ copied: string;
13
+ failed: string;
14
+ copiedAnnouncement: string;
15
+ failedAnnouncement: string;
16
+ }
17
+ declare const DEFAULT_COPY_BUTTON_LABELS: CopyButtonLabels;
18
+ interface CopyButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onClick"> {
19
+ /** What to copy. A function is read at click time — for a value that is expensive
20
+ * to build (a CSV of the table) or that changes under the button. */
21
+ text: string | (() => string);
22
+ /** `icon` (default): a square icon button whose tooltip states the result.
23
+ * `label`: a text button whose words change to the result. */
24
+ variant?: "icon" | "label";
25
+ /** The idle accessible name — and, in the `label` variant, the visible text:
26
+ * "Copy IBAN" says WHAT is copied, which a row of three copy icons needs. Default
27
+ * `copyButton.copy`. */
28
+ label?: string;
29
+ /** The button's own look. Default: `ghost` for the icon, `secondary` for the label. */
30
+ buttonVariant?: ButtonVariant;
31
+ /** Icon variant only. */
32
+ size?: IconButtonSize;
33
+ /** Icon variant only: where the result tooltip opens. */
34
+ tooltipSide?: TooltipSide;
35
+ /** Pass through to the tooltip — needed inside a scroll container (see Tooltip). */
36
+ tooltipPortal?: boolean;
37
+ /** ms until the button returns to idle. Default 2000. */
38
+ resetAfter?: number;
39
+ /** Called with the real outcome once the copy settles. */
40
+ onCopied?: (ok: boolean) => void;
41
+ labels?: Partial<CopyButtonLabels>;
42
+ }
43
+ /**
44
+ * A button that copies a value and tells the truth about whether it did.
45
+ *
46
+ * keksdose asked for it after two of its own copy buttons said "Copied" on a build
47
+ * where the copy had failed (no Clipboard API over plain http). This one takes its
48
+ * state from {@link useCopyToClipboard}, which reads the result, and has a `failed`
49
+ * state that looks and sounds different from success.
50
+ *
51
+ * The result is spoken through {@link useAnnounce}: activating the button moves no
52
+ * focus, so without a live region a screen-reader user hears nothing at all. A
53
+ * failure goes through an ASSERTIVE region and a success through a polite one — the
54
+ * failure is the news that changes what they do next.
55
+ *
56
+ * The icon variant keeps the SAME accessible name in every state. A button renaming
57
+ * itself under the focus is announced inconsistently across readers; the live region
58
+ * is the channel that is actually heard.
59
+ */
60
+ declare function CopyButton({ text, variant, label, buttonVariant, size, tooltipSide, tooltipPortal, resetAfter, onCopied, labels, className, disabled, ...rest }: CopyButtonProps): react.JSX.Element;
61
+
62
+ export { CopyButton, type CopyButtonLabels, type CopyButtonProps, DEFAULT_COPY_BUTTON_LABELS };
@@ -0,0 +1,95 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { Check, Copy, X } from "lucide-react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { useKitLabels } from "../i18n/kit-labels.js";
6
+ import { useAnnounce } from "../hooks/use-announce.js";
7
+ import { useCopyToClipboard } from "../hooks/use-copy-to-clipboard.js";
8
+ import { Button, IconButton } from "./ui.js";
9
+ import { Tooltip } from "./tooltip.js";
10
+ const DEFAULT_COPY_BUTTON_LABELS = {
11
+ copy: "Copy",
12
+ copied: "Copied",
13
+ failed: "Couldn\u2019t copy",
14
+ copiedAnnouncement: "Copied to clipboard",
15
+ failedAnnouncement: "Couldn\u2019t copy to the clipboard"
16
+ };
17
+ const ICONS = { idle: Copy, copied: Check, failed: X };
18
+ function CopyButton({
19
+ text,
20
+ variant = "icon",
21
+ label,
22
+ buttonVariant,
23
+ size = "sm",
24
+ tooltipSide = "top",
25
+ tooltipPortal = false,
26
+ resetAfter = 2e3,
27
+ onCopied,
28
+ labels,
29
+ className,
30
+ disabled,
31
+ ...rest
32
+ }) {
33
+ const words = useKitLabels("copyButton", DEFAULT_COPY_BUTTON_LABELS, labels);
34
+ const { state, copy } = useCopyToClipboard({ resetAfter });
35
+ const polite = useAnnounce();
36
+ const assertive = useAnnounce({ politeness: "assertive" });
37
+ const idleText = label ?? words.copy;
38
+ const stateText = state === "copied" ? words.copied : state === "failed" ? words.failed : idleText;
39
+ const Icon = ICONS[state];
40
+ const iconTone = state === "copied" ? "text-[var(--success)]" : state === "failed" ? "text-[var(--danger)]" : void 0;
41
+ const onClick = async () => {
42
+ const ok = await copy(typeof text === "function" ? text() : text);
43
+ if (ok) polite.announce(words.copiedAnnouncement);
44
+ else assertive.announce(words.failedAnnouncement);
45
+ onCopied?.(ok);
46
+ };
47
+ const regions = /* @__PURE__ */ jsxs(Fragment, { children: [
48
+ /* @__PURE__ */ jsx("span", { ...polite.regionProps }),
49
+ /* @__PURE__ */ jsx("span", { ...assertive.regionProps })
50
+ ] });
51
+ if (variant === "label") {
52
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
53
+ /* @__PURE__ */ jsxs(
54
+ Button,
55
+ {
56
+ type: "button",
57
+ ...rest,
58
+ disabled,
59
+ variant: buttonVariant ?? "secondary",
60
+ "data-state": state,
61
+ onClick,
62
+ className,
63
+ children: [
64
+ /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: cn("size-4 shrink-0", iconTone) }),
65
+ stateText
66
+ ]
67
+ }
68
+ ),
69
+ regions
70
+ ] });
71
+ }
72
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
73
+ /* @__PURE__ */ jsx(Tooltip, { label: stateText, side: tooltipSide, portal: tooltipPortal, children: /* @__PURE__ */ jsx(
74
+ IconButton,
75
+ {
76
+ type: "button",
77
+ ...rest,
78
+ disabled,
79
+ "aria-label": idleText,
80
+ variant: buttonVariant ?? "ghost",
81
+ size,
82
+ "data-state": state,
83
+ onClick,
84
+ className: cn(iconTone, className),
85
+ children: /* @__PURE__ */ jsx(Icon, { "aria-hidden": true })
86
+ }
87
+ ) }),
88
+ regions
89
+ ] });
90
+ }
91
+ export {
92
+ CopyButton,
93
+ DEFAULT_COPY_BUTTON_LABELS
94
+ };
95
+ //# sourceMappingURL=copy-button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/copy-button.tsx"],"sourcesContent":["import type { ButtonHTMLAttributes } from \"react\";\nimport { Check, Copy, X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { useAnnounce } from \"../hooks/use-announce\";\nimport { useCopyToClipboard } from \"../hooks/use-copy-to-clipboard\";\nimport type { CopyState } from \"../hooks/use-copy-to-clipboard\";\nimport { Button, IconButton } from \"./ui\";\nimport type { ButtonVariant, IconButtonSize } from \"./ui\";\nimport { Tooltip } from \"./tooltip\";\nimport type { TooltipSide } from \"./tooltip\";\n\n/** The words a copy button says. The visible three are short on purpose (they\n * replace the button's own text in the `label` variant); the two announcements\n * are the sentences a screen reader hears, since the button's focus never moves. */\nexport interface CopyButtonLabels {\n copy: string;\n copied: string;\n failed: string;\n copiedAnnouncement: string;\n failedAnnouncement: string;\n}\n\nexport const DEFAULT_COPY_BUTTON_LABELS: CopyButtonLabels = {\n copy: \"Copy\",\n copied: \"Copied\",\n failed: \"Couldn’t copy\",\n copiedAnnouncement: \"Copied to clipboard\",\n failedAnnouncement: \"Couldn’t copy to the clipboard\",\n};\n\nexport interface CopyButtonProps\n extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"children\" | \"onClick\"> {\n /** What to copy. A function is read at click time — for a value that is expensive\n * to build (a CSV of the table) or that changes under the button. */\n text: string | (() => string);\n /** `icon` (default): a square icon button whose tooltip states the result.\n * `label`: a text button whose words change to the result. */\n variant?: \"icon\" | \"label\";\n /** The idle accessible name — and, in the `label` variant, the visible text:\n * \"Copy IBAN\" says WHAT is copied, which a row of three copy icons needs. Default\n * `copyButton.copy`. */\n label?: string;\n /** The button's own look. Default: `ghost` for the icon, `secondary` for the label. */\n buttonVariant?: ButtonVariant;\n /** Icon variant only. */\n size?: IconButtonSize;\n /** Icon variant only: where the result tooltip opens. */\n tooltipSide?: TooltipSide;\n /** Pass through to the tooltip — needed inside a scroll container (see Tooltip). */\n tooltipPortal?: boolean;\n /** ms until the button returns to idle. Default 2000. */\n resetAfter?: number;\n /** Called with the real outcome once the copy settles. */\n onCopied?: (ok: boolean) => void;\n labels?: Partial<CopyButtonLabels>;\n}\n\nconst ICONS: Record<CopyState, typeof Copy> = { idle: Copy, copied: Check, failed: X };\n\n/**\n * A button that copies a value and tells the truth about whether it did.\n *\n * keksdose asked for it after two of its own copy buttons said \"Copied\" on a build\n * where the copy had failed (no Clipboard API over plain http). This one takes its\n * state from {@link useCopyToClipboard}, which reads the result, and has a `failed`\n * state that looks and sounds different from success.\n *\n * The result is spoken through {@link useAnnounce}: activating the button moves no\n * focus, so without a live region a screen-reader user hears nothing at all. A\n * failure goes through an ASSERTIVE region and a success through a polite one — the\n * failure is the news that changes what they do next.\n *\n * The icon variant keeps the SAME accessible name in every state. A button renaming\n * itself under the focus is announced inconsistently across readers; the live region\n * is the channel that is actually heard.\n */\nexport function CopyButton({\n text,\n variant = \"icon\",\n label,\n buttonVariant,\n size = \"sm\",\n tooltipSide = \"top\",\n tooltipPortal = false,\n resetAfter = 2000,\n onCopied,\n labels,\n className,\n disabled,\n ...rest\n}: CopyButtonProps) {\n const words = useKitLabels(\"copyButton\", DEFAULT_COPY_BUTTON_LABELS, labels);\n const { state, copy } = useCopyToClipboard({ resetAfter });\n const polite = useAnnounce();\n const assertive = useAnnounce({ politeness: \"assertive\" });\n\n const idleText = label ?? words.copy;\n const stateText = state === \"copied\" ? words.copied : state === \"failed\" ? words.failed : idleText;\n const Icon = ICONS[state];\n const iconTone =\n state === \"copied\" ? \"text-[var(--success)]\" : state === \"failed\" ? \"text-[var(--danger)]\" : undefined;\n\n const onClick = async () => {\n const ok = await copy(typeof text === \"function\" ? text() : text);\n if (ok) polite.announce(words.copiedAnnouncement);\n else assertive.announce(words.failedAnnouncement);\n onCopied?.(ok);\n };\n\n const regions = (\n <>\n <span {...polite.regionProps} />\n <span {...assertive.regionProps} />\n </>\n );\n\n if (variant === \"label\") {\n return (\n <>\n <Button\n type=\"button\"\n {...rest}\n disabled={disabled}\n variant={buttonVariant ?? \"secondary\"}\n data-state={state}\n onClick={onClick}\n className={className}\n >\n <Icon aria-hidden className={cn(\"size-4 shrink-0\", iconTone)} />\n {stateText}\n </Button>\n {regions}\n </>\n );\n }\n\n return (\n <>\n <Tooltip label={stateText} side={tooltipSide} portal={tooltipPortal}>\n <IconButton\n type=\"button\"\n {...rest}\n disabled={disabled}\n aria-label={idleText}\n variant={buttonVariant ?? \"ghost\"}\n size={size}\n data-state={state}\n onClick={onClick}\n className={cn(iconTone, className)}\n >\n <Icon aria-hidden />\n </IconButton>\n </Tooltip>\n {regions}\n </>\n );\n}\n"],"mappings":";AA+GI,mBACE,KADF;AA9GJ,SAAS,OAAO,MAAM,SAAS;AAC/B,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,0BAA0B;AAEnC,SAAS,QAAQ,kBAAkB;AAEnC,SAAS,eAAe;AAcjB,MAAM,6BAA+C;AAAA,EAC1D,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,oBAAoB;AAAA,EACpB,oBAAoB;AACtB;AA6BA,MAAM,QAAwC,EAAE,MAAM,MAAM,QAAQ,OAAO,QAAQ,EAAE;AAmB9E,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,QAAM,QAAQ,aAAa,cAAc,4BAA4B,MAAM;AAC3E,QAAM,EAAE,OAAO,KAAK,IAAI,mBAAmB,EAAE,WAAW,CAAC;AACzD,QAAM,SAAS,YAAY;AAC3B,QAAM,YAAY,YAAY,EAAE,YAAY,YAAY,CAAC;AAEzD,QAAM,WAAW,SAAS,MAAM;AAChC,QAAM,YAAY,UAAU,WAAW,MAAM,SAAS,UAAU,WAAW,MAAM,SAAS;AAC1F,QAAM,OAAO,MAAM,KAAK;AACxB,QAAM,WACJ,UAAU,WAAW,0BAA0B,UAAU,WAAW,yBAAyB;AAE/F,QAAM,UAAU,YAAY;AAC1B,UAAM,KAAK,MAAM,KAAK,OAAO,SAAS,aAAa,KAAK,IAAI,IAAI;AAChE,QAAI,GAAI,QAAO,SAAS,MAAM,kBAAkB;AAAA,QAC3C,WAAU,SAAS,MAAM,kBAAkB;AAChD,eAAW,EAAE;AAAA,EACf;AAEA,QAAM,UACJ,iCACE;AAAA,wBAAC,UAAM,GAAG,OAAO,aAAa;AAAA,IAC9B,oBAAC,UAAM,GAAG,UAAU,aAAa;AAAA,KACnC;AAGF,MAAI,YAAY,SAAS;AACvB,WACE,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACJ,GAAG;AAAA,UACJ;AAAA,UACA,SAAS,iBAAiB;AAAA,UAC1B,cAAY;AAAA,UACZ;AAAA,UACA;AAAA,UAEA;AAAA,gCAAC,QAAK,eAAW,MAAC,WAAW,GAAG,mBAAmB,QAAQ,GAAG;AAAA,YAC7D;AAAA;AAAA;AAAA,MACH;AAAA,MACC;AAAA,OACH;AAAA,EAEJ;AAEA,SACE,iCACE;AAAA,wBAAC,WAAQ,OAAO,WAAW,MAAM,aAAa,QAAQ,eACpD;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACJ,GAAG;AAAA,QACJ;AAAA,QACA,cAAY;AAAA,QACZ,SAAS,iBAAiB;AAAA,QAC1B;AAAA,QACA,cAAY;AAAA,QACZ;AAAA,QACA,WAAW,GAAG,UAAU,SAAS;AAAA,QAEjC,8BAAC,QAAK,eAAW,MAAC;AAAA;AAAA,IACpB,GACF;AAAA,IACC;AAAA,KACH;AAEJ;","names":[]}
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { a as DataTableColumn, c as FilterValue } from '../data-table-filters-noy0Abvi.js';
2
+ import { b as DataTableColumn, e as FilterValue } from '../data-table-filters-CF1PXqjQ.js';
3
3
  import { DataTableLabels } from './data-table-labels.js';
4
4
  import './data-table-sort.js';
5
5
  import './swipeable-row.js';
@@ -1,4 +1,4 @@
1
- export { C as ColumnFilter, c as FilterValue, d as decodeFilterValue, e as decodeFilterValueOfType, f as defaultFilterState, g as encodeFilterValue, i as isFilterActive, r as resolveFilter, h as rowMatches } from '../data-table-filters-noy0Abvi.js';
1
+ export { C as ColumnFilter, e as FilterValue, f as decodeFilterValue, g as decodeFilterValueOfType, h as defaultFilterState, i as encodeFilterValue, j as isFilterActive, r as resolveFilter, k as rowMatches } from '../data-table-filters-CF1PXqjQ.js';
2
2
  import 'react';
3
3
  import './data-table-sort.js';
4
4
  import './swipeable-row.js';
@@ -15,9 +15,13 @@ interface PaginationProps {
15
15
  labels?: Partial<DataTableLabels>;
16
16
  /** For the page numbers and page-size options; falls back to the provider's. */
17
17
  locale?: string;
18
+ /** `"compact"` for a pager under a compact {@link DataTable} (lenkbank's in-card
19
+ * tables): a slimmer footer and page buttons, so the pager is not the tallest row
20
+ * of a table that was made compact on purpose. */
21
+ density?: "comfortable" | "compact";
18
22
  }
19
23
  /** Footer for {@link DataTable}: range summary, page-size select, and a
20
24
  * windowed page-number strip (first/last + ±2 around the current page). */
21
- declare function Pagination({ page, totalPages, pageSize, total, onPage, onPageSize, labels: labelsProp, locale: localeProp, }: PaginationProps): react.JSX.Element;
25
+ declare function Pagination({ page, totalPages, pageSize, total, onPage, onPageSize, labels: labelsProp, locale: localeProp, density, }: PaginationProps): react.JSX.Element;
22
26
 
23
27
  export { PAGE_SIZE_OPTIONS, Pagination };
@@ -14,8 +14,10 @@ function Pagination({
14
14
  onPage,
15
15
  onPageSize,
16
16
  labels: labelsProp,
17
- locale: localeProp
17
+ locale: localeProp,
18
+ density = "comfortable"
18
19
  }) {
20
+ const compact = density === "compact";
19
21
  const labels = useKitLabels("dataTable", DEFAULT_DATA_TABLE_LABELS, labelsProp);
20
22
  const locale = useKitLocale(localeProp);
21
23
  const num = useMemo(() => new Intl.NumberFormat(locale), [locale]);
@@ -33,64 +35,75 @@ function Pagination({
33
35
  if (idx > 0 && p - visible[idx - 1] > 1) items.push("ellipsis");
34
36
  items.push(p);
35
37
  });
36
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 border-t border-[var(--border)] px-3 py-2 text-xs text-[var(--text-secondary)]", children: [
37
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
38
- /* @__PURE__ */ jsx("span", { children: pageSize === Infinity ? labels.rowCount(total) : labels.pageRange(page * pageSize + 1, Math.min(total, (page + 1) * pageSize), total) }),
39
- onPageSize && /* @__PURE__ */ jsxs(
40
- "select",
41
- {
42
- value: pageSize === Infinity ? "all" : pageSize,
43
- onChange: (e) => onPageSize(e.target.value === "all" ? Infinity : Number(e.target.value)),
44
- className: "rounded border border-[var(--border)] bg-[var(--bg-surface)] px-1 py-0.5 text-xs",
45
- "aria-label": labels.pageSize,
46
- children: [
47
- PAGE_SIZE_OPTIONS.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: num.format(n) }, n)),
48
- /* @__PURE__ */ jsx("option", { value: "all", children: labels.pageSizeAll })
49
- ]
50
- }
51
- )
52
- ] }),
53
- pageSize !== Infinity && totalPages > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
54
- /* @__PURE__ */ jsx(
55
- "button",
56
- {
57
- type: "button",
58
- onClick: () => onPage(Math.max(0, page - 1)),
59
- disabled: page === 0,
60
- className: "rounded p-1 hover:bg-[var(--bg-hover)] disabled:opacity-40",
61
- "aria-label": labels.prevPage,
62
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4 rtl:-scale-x-100" })
63
- }
38
+ return /* @__PURE__ */ jsxs(
39
+ "div",
40
+ {
41
+ "data-density": density,
42
+ className: cn(
43
+ "flex flex-wrap items-center justify-between gap-2 border-t border-[var(--border)] text-xs text-[var(--text-secondary)]",
44
+ compact ? "px-2 py-1" : "px-3 py-2"
64
45
  ),
65
- items.map(
66
- (it, i) => it === "ellipsis" ? /* @__PURE__ */ jsx("span", { className: "px-1 text-[var(--text-placeholder)]", children: "\u2026" }, `e${i}`) : /* @__PURE__ */ jsx(
67
- "button",
68
- {
69
- type: "button",
70
- onClick: () => onPage(it),
71
- "aria-current": it === page ? "page" : void 0,
72
- className: cn(
73
- "min-w-7 rounded px-2 py-0.5",
74
- it === page ? "bg-[var(--bg-inverse)] text-[var(--text-inverse)]" : "hover:bg-[var(--bg-hover)]"
75
- ),
76
- children: num.format(it + 1)
77
- },
78
- it
79
- )
80
- ),
81
- /* @__PURE__ */ jsx(
82
- "button",
83
- {
84
- type: "button",
85
- onClick: () => onPage(Math.min(totalPages - 1, page + 1)),
86
- disabled: page >= totalPages - 1,
87
- className: "rounded p-1 hover:bg-[var(--bg-hover)] disabled:opacity-40",
88
- "aria-label": labels.nextPage,
89
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4 rtl:-scale-x-100" })
90
- }
91
- )
92
- ] })
93
- ] });
46
+ children: [
47
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
48
+ /* @__PURE__ */ jsx("span", { children: pageSize === Infinity ? labels.rowCount(total) : labels.pageRange(page * pageSize + 1, Math.min(total, (page + 1) * pageSize), total) }),
49
+ onPageSize && /* @__PURE__ */ jsxs(
50
+ "select",
51
+ {
52
+ value: pageSize === Infinity ? "all" : pageSize,
53
+ onChange: (e) => onPageSize(e.target.value === "all" ? Infinity : Number(e.target.value)),
54
+ className: "rounded border border-[var(--border)] bg-[var(--bg-surface)] px-1 py-0.5 text-xs",
55
+ "aria-label": labels.pageSize,
56
+ children: [
57
+ PAGE_SIZE_OPTIONS.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: num.format(n) }, n)),
58
+ /* @__PURE__ */ jsx("option", { value: "all", children: labels.pageSizeAll })
59
+ ]
60
+ }
61
+ )
62
+ ] }),
63
+ pageSize !== Infinity && totalPages > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
64
+ /* @__PURE__ */ jsx(
65
+ "button",
66
+ {
67
+ type: "button",
68
+ onClick: () => onPage(Math.max(0, page - 1)),
69
+ disabled: page === 0,
70
+ className: cn("rounded hover:bg-[var(--bg-hover)] disabled:opacity-40", compact ? "p-0.5" : "p-1"),
71
+ "aria-label": labels.prevPage,
72
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: cn("rtl:-scale-x-100", compact ? "size-3.5" : "size-4") })
73
+ }
74
+ ),
75
+ items.map(
76
+ (it, i) => it === "ellipsis" ? /* @__PURE__ */ jsx("span", { className: "px-1 text-[var(--text-placeholder)]", children: "\u2026" }, `e${i}`) : /* @__PURE__ */ jsx(
77
+ "button",
78
+ {
79
+ type: "button",
80
+ onClick: () => onPage(it),
81
+ "aria-current": it === page ? "page" : void 0,
82
+ className: cn(
83
+ "rounded",
84
+ compact ? "min-w-6 px-1.5 py-0" : "min-w-7 px-2 py-0.5",
85
+ it === page ? "bg-[var(--bg-inverse)] text-[var(--text-inverse)]" : "hover:bg-[var(--bg-hover)]"
86
+ ),
87
+ children: num.format(it + 1)
88
+ },
89
+ it
90
+ )
91
+ ),
92
+ /* @__PURE__ */ jsx(
93
+ "button",
94
+ {
95
+ type: "button",
96
+ onClick: () => onPage(Math.min(totalPages - 1, page + 1)),
97
+ disabled: page >= totalPages - 1,
98
+ className: cn("rounded hover:bg-[var(--bg-hover)] disabled:opacity-40", compact ? "p-0.5" : "p-1"),
99
+ "aria-label": labels.nextPage,
100
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: cn("rtl:-scale-x-100", compact ? "size-3.5" : "size-4") })
101
+ }
102
+ )
103
+ ] })
104
+ ]
105
+ }
106
+ );
94
107
  }
95
108
  export {
96
109
  PAGE_SIZE_OPTIONS,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/data-table-pagination.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels } from \"./data-table-labels\";\nimport { useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\n\nexport const PAGE_SIZE_OPTIONS = [10, 25, 50, 100, 200] as const;\n\ninterface PaginationProps {\n page: number;\n totalPages: number;\n pageSize: number;\n total: number;\n onPage: (p: number) => void;\n /** Omit when the page size is not the user's to change (a server that fixes it):\n * the select is then not rendered at all, rather than rendered and inert. */\n onPageSize?: (n: number) => void;\n /** Over `dataTable` from `<UiKitProvider labels>`, over the English defaults. */\n labels?: Partial<DataTableLabels>;\n /** For the page numbers and page-size options; falls back to the provider's. */\n locale?: string;\n}\n\n/** Footer for {@link DataTable}: range summary, page-size select, and a\n * windowed page-number strip (first/last + ±2 around the current page). */\nexport function Pagination({\n page,\n totalPages,\n pageSize,\n total,\n onPage,\n onPageSize,\n labels: labelsProp,\n locale: localeProp,\n}: PaginationProps) {\n // prop > provider > English, like every other kit component. This defaulted straight\n // to the English object, so a standalone pager under a translated provider still\n // said \"Rows per page\" — only the pager INSIDE a DataTable was translated, because\n // the table resolved the provider's labels and handed them down.\n const labels = useKitLabels(\"dataTable\", DEFAULT_DATA_TABLE_LABELS, labelsProp);\n const locale = useKitLocale(localeProp);\n // The bare numbers on the strip and in the select are text too: `String(n)` is\n // always ASCII digits, which is wrong for a locale that writes its own. The range\n // summary is not formatted here — `pageRange` / `rowCount` take NUMBERS so the\n // translation can format them itself, alongside the words around them.\n const num = useMemo(() => new Intl.NumberFormat(locale), [locale]);\n const visible = useMemo(() => {\n if (totalPages <= 7) return Array.from({ length: totalPages }, (_, i) => i);\n const set = new Set<number>([0, totalPages - 1, page]);\n for (let d = 1; d <= 2; d++) {\n if (page - d >= 0) set.add(page - d);\n if (page + d <= totalPages - 1) set.add(page + d);\n }\n return Array.from(set).sort((a, b) => a - b);\n }, [page, totalPages]);\n\n const items: (number | \"ellipsis\")[] = [];\n visible.forEach((p, idx) => {\n if (idx > 0 && p - visible[idx - 1] > 1) items.push(\"ellipsis\");\n items.push(p);\n });\n\n return (\n <div className=\"flex flex-wrap items-center justify-between gap-2 border-t border-[var(--border)] px-3 py-2 text-xs text-[var(--text-secondary)]\">\n <div className=\"flex items-center gap-2\">\n <span>\n {pageSize === Infinity\n ? labels.rowCount(total)\n : labels.pageRange(page * pageSize + 1, Math.min(total, (page + 1) * pageSize), total)}\n </span>\n {onPageSize && (\n <select\n value={pageSize === Infinity ? \"all\" : pageSize}\n onChange={(e) =>\n onPageSize(e.target.value === \"all\" ? Infinity : Number(e.target.value))\n }\n className=\"rounded border border-[var(--border)] bg-[var(--bg-surface)] px-1 py-0.5 text-xs\"\n aria-label={labels.pageSize}\n >\n {PAGE_SIZE_OPTIONS.map((n) => (\n <option key={n} value={n}>\n {num.format(n)}\n </option>\n ))}\n <option value=\"all\">{labels.pageSizeAll}</option>\n </select>\n )}\n </div>\n {pageSize !== Infinity && totalPages > 1 && (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => onPage(Math.max(0, page - 1))}\n disabled={page === 0}\n className=\"rounded p-1 hover:bg-[var(--bg-hover)] disabled:opacity-40\"\n aria-label={labels.prevPage}\n >\n {/* \"Previous\" points back along the line — left in LTR, right in RTL. */}\n <ChevronLeft className=\"size-4 rtl:-scale-x-100\" />\n </button>\n {items.map((it, i) =>\n it === \"ellipsis\" ? (\n <span key={`e${i}`} className=\"px-1 text-[var(--text-placeholder)]\">\n …\n </span>\n ) : (\n <button\n key={it}\n type=\"button\"\n onClick={() => onPage(it)}\n // Which page you are on is carried only by a background colour\n // otherwise, and the strip is a row of bare numbers with nothing\n // else to tell them apart.\n aria-current={it === page ? \"page\" : undefined}\n className={cn(\n \"min-w-7 rounded px-2 py-0.5\",\n it === page\n ? \"bg-[var(--bg-inverse)] text-[var(--text-inverse)]\"\n : \"hover:bg-[var(--bg-hover)]\",\n )}\n >\n {num.format(it + 1)}\n </button>\n ),\n )}\n <button\n type=\"button\"\n onClick={() => onPage(Math.min(totalPages - 1, page + 1))}\n disabled={page >= totalPages - 1}\n className=\"rounded p-1 hover:bg-[var(--bg-hover)] disabled:opacity-40\"\n aria-label={labels.nextPage}\n >\n <ChevronRight className=\"size-4 rtl:-scale-x-100\" />\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/data-table-pagination.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels } from \"./data-table-labels\";\nimport { useKitLabels, useKitLocale } from \"../i18n/kit-labels\";\n\nexport const PAGE_SIZE_OPTIONS = [10, 25, 50, 100, 200] as const;\n\ninterface PaginationProps {\n page: number;\n totalPages: number;\n pageSize: number;\n total: number;\n onPage: (p: number) => void;\n /** Omit when the page size is not the user's to change (a server that fixes it):\n * the select is then not rendered at all, rather than rendered and inert. */\n onPageSize?: (n: number) => void;\n /** Over `dataTable` from `<UiKitProvider labels>`, over the English defaults. */\n labels?: Partial<DataTableLabels>;\n /** For the page numbers and page-size options; falls back to the provider's. */\n locale?: string;\n /** `\"compact\"` for a pager under a compact {@link DataTable} (lenkbank's in-card\n * tables): a slimmer footer and page buttons, so the pager is not the tallest row\n * of a table that was made compact on purpose. */\n density?: \"comfortable\" | \"compact\";\n}\n\n/** Footer for {@link DataTable}: range summary, page-size select, and a\n * windowed page-number strip (first/last + ±2 around the current page). */\nexport function Pagination({\n page,\n totalPages,\n pageSize,\n total,\n onPage,\n onPageSize,\n labels: labelsProp,\n locale: localeProp,\n density = \"comfortable\",\n}: PaginationProps) {\n const compact = density === \"compact\";\n // prop > provider > English, like every other kit component. This defaulted straight\n // to the English object, so a standalone pager under a translated provider still\n // said \"Rows per page\" — only the pager INSIDE a DataTable was translated, because\n // the table resolved the provider's labels and handed them down.\n const labels = useKitLabels(\"dataTable\", DEFAULT_DATA_TABLE_LABELS, labelsProp);\n const locale = useKitLocale(localeProp);\n // The bare numbers on the strip and in the select are text too: `String(n)` is\n // always ASCII digits, which is wrong for a locale that writes its own. The range\n // summary is not formatted here — `pageRange` / `rowCount` take NUMBERS so the\n // translation can format them itself, alongside the words around them.\n const num = useMemo(() => new Intl.NumberFormat(locale), [locale]);\n const visible = useMemo(() => {\n if (totalPages <= 7) return Array.from({ length: totalPages }, (_, i) => i);\n const set = new Set<number>([0, totalPages - 1, page]);\n for (let d = 1; d <= 2; d++) {\n if (page - d >= 0) set.add(page - d);\n if (page + d <= totalPages - 1) set.add(page + d);\n }\n return Array.from(set).sort((a, b) => a - b);\n }, [page, totalPages]);\n\n const items: (number | \"ellipsis\")[] = [];\n visible.forEach((p, idx) => {\n if (idx > 0 && p - visible[idx - 1] > 1) items.push(\"ellipsis\");\n items.push(p);\n });\n\n return (\n <div\n data-density={density}\n className={cn(\n \"flex flex-wrap items-center justify-between gap-2 border-t border-[var(--border)] text-xs text-[var(--text-secondary)]\",\n compact ? \"px-2 py-1\" : \"px-3 py-2\",\n )}\n >\n <div className=\"flex items-center gap-2\">\n <span>\n {pageSize === Infinity\n ? labels.rowCount(total)\n : labels.pageRange(page * pageSize + 1, Math.min(total, (page + 1) * pageSize), total)}\n </span>\n {onPageSize && (\n <select\n value={pageSize === Infinity ? \"all\" : pageSize}\n onChange={(e) =>\n onPageSize(e.target.value === \"all\" ? Infinity : Number(e.target.value))\n }\n className=\"rounded border border-[var(--border)] bg-[var(--bg-surface)] px-1 py-0.5 text-xs\"\n aria-label={labels.pageSize}\n >\n {PAGE_SIZE_OPTIONS.map((n) => (\n <option key={n} value={n}>\n {num.format(n)}\n </option>\n ))}\n <option value=\"all\">{labels.pageSizeAll}</option>\n </select>\n )}\n </div>\n {pageSize !== Infinity && totalPages > 1 && (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => onPage(Math.max(0, page - 1))}\n disabled={page === 0}\n className={cn(\"rounded hover:bg-[var(--bg-hover)] disabled:opacity-40\", compact ? \"p-0.5\" : \"p-1\")}\n aria-label={labels.prevPage}\n >\n {/* \"Previous\" points back along the line — left in LTR, right in RTL. */}\n <ChevronLeft className={cn(\"rtl:-scale-x-100\", compact ? \"size-3.5\" : \"size-4\")} />\n </button>\n {items.map((it, i) =>\n it === \"ellipsis\" ? (\n <span key={`e${i}`} className=\"px-1 text-[var(--text-placeholder)]\">\n …\n </span>\n ) : (\n <button\n key={it}\n type=\"button\"\n onClick={() => onPage(it)}\n // Which page you are on is carried only by a background colour\n // otherwise, and the strip is a row of bare numbers with nothing\n // else to tell them apart.\n aria-current={it === page ? \"page\" : undefined}\n className={cn(\n \"rounded\",\n compact ? \"min-w-6 px-1.5 py-0\" : \"min-w-7 px-2 py-0.5\",\n it === page\n ? \"bg-[var(--bg-inverse)] text-[var(--text-inverse)]\"\n : \"hover:bg-[var(--bg-hover)]\",\n )}\n >\n {num.format(it + 1)}\n </button>\n ),\n )}\n <button\n type=\"button\"\n onClick={() => onPage(Math.min(totalPages - 1, page + 1))}\n disabled={page >= totalPages - 1}\n className={cn(\"rounded hover:bg-[var(--bg-hover)] disabled:opacity-40\", compact ? \"p-0.5\" : \"p-1\")}\n aria-label={labels.nextPage}\n >\n <ChevronRight className={cn(\"rtl:-scale-x-100\", compact ? \"size-3.5\" : \"size-4\")} />\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -14,10 +14,33 @@ declare function normalizeSorts(raw: unknown): SortState[];
14
14
  declare function encodeSorts(sorts: SortState[]): string | null;
15
15
  /** Decode a `sort` URL param, dropping unknown and duplicated keys. */
16
16
  declare function decodeSorts(raw: string | null, validKeys: ReadonlySet<string>): SortState[];
17
+ /**
18
+ * How a header click steps through the directions (see {@link nextSorts}).
19
+ *
20
+ * * `"tri"` — first direction → the other → unsorted. The default, and the only cycle
21
+ * before 0.8.0.
22
+ * * `"toggle"` — first direction ⇄ the other, never unsorted: a report table that is
23
+ * ALWAYS ranked by something (keksdose's aggregated tables) must not have a third
24
+ * click that silently drops the ranking. Clicking another column still replaces
25
+ * the sort, so the table can always be re-ranked; a shift-click criterion flips in
26
+ * place and stays for the same reason.
27
+ */
28
+ type SortCycle = "tri" | "toggle";
29
+ /** Per-click options for {@link nextSorts}; every field is optional and the
30
+ * defaults reproduce the pre-0.8.0 cycle exactly. */
31
+ interface SortStepOptions {
32
+ /** The direction a column starts in on its first click. `"desc"` for money and
33
+ * count columns, where the interesting rows are the large ones. Default `"asc"`. */
34
+ firstDir?: SortDir;
35
+ /** Default `"tri"`. */
36
+ cycle?: SortCycle;
37
+ }
17
38
  /** Header-click semantics. Plain click: make `key` the only sort — cycling
18
- * asc → desc → none when it already is the sole criterion. Shift-click
19
- * (`additive`): append `key` as the next-priority sort, or cycle an existing
20
- * entry's direction in place (asc → desc → removed). */
21
- declare function nextSorts(prev: SortState[], key: string, additive: boolean): SortState[];
39
+ * first → other → none (`"tri"`) or first ⇄ other (`"toggle"`) when it already is
40
+ * the sole criterion. Shift-click (`additive`): append `key` as the next-priority
41
+ * sort, or step an existing entry in place (first → other → removed under `"tri"`,
42
+ * a plain flip under `"toggle"`). `firstDir` defaults to `"asc"`, `cycle` to
43
+ * `"tri"` — the cycle every table had before the options existed. */
44
+ declare function nextSorts(prev: SortState[], key: string, additive: boolean, options?: SortStepOptions): SortState[];
22
45
 
23
- export { type SortDir, type SortState, decodeSorts, encodeSorts, nextSorts, normalizeSorts };
46
+ export { type SortCycle, type SortDir, type SortState, type SortStepOptions, decodeSorts, encodeSorts, nextSorts, normalizeSorts };
@@ -33,18 +33,22 @@ function decodeSorts(raw, validKeys) {
33
33
  }
34
34
  return out;
35
35
  }
36
- function nextSorts(prev, key, additive) {
36
+ const flip = (d) => d === "asc" ? "desc" : "asc";
37
+ function nextSorts(prev, key, additive, options = {}) {
38
+ const first = options.firstDir ?? "asc";
39
+ const toggle = options.cycle === "toggle";
37
40
  const idx = prev.findIndex((s) => s.key === key);
41
+ const step = (dir2) => dir2 === first ? flip(first) : toggle ? first : null;
38
42
  if (!additive) {
39
43
  if (prev.length === 1 && idx === 0) {
40
- return prev[0].dir === "asc" ? [{ key, dir: "desc" }] : [];
44
+ const dir2 = step(prev[0].dir);
45
+ return dir2 ? [{ key, dir: dir2 }] : [];
41
46
  }
42
- return [{ key, dir: "asc" }];
43
- }
44
- if (idx === -1) return [...prev, { key, dir: "asc" }];
45
- if (prev[idx].dir === "asc") {
46
- return prev.map((s, i) => i === idx ? { key, dir: "desc" } : s);
47
+ return [{ key, dir: first }];
47
48
  }
49
+ if (idx === -1) return [...prev, { key, dir: first }];
50
+ const dir = step(prev[idx].dir);
51
+ if (dir) return prev.map((s, i) => i === idx ? { key, dir } : s);
48
52
  return prev.filter((_, i) => i !== idx);
49
53
  }
50
54
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/data-table-sort.ts"],"sourcesContent":["/** Multi-column sort state for DataTable: ordered by priority (index 0 is the\n * primary sort). Encoded as `sort=date.desc,amount` in URLs and the server's\n * transactions `sort` param — the pre-multi-sort single-value format\n * (`sort=date.desc`) decodes as a one-element list. */\n\nexport type SortDir = \"asc\" | \"desc\";\nexport interface SortState {\n key: string;\n dir: SortDir;\n}\n\n/** Coerce any persisted/URL-derived shape into a `SortState[]`. The\n * pre-multi-sort localStorage format stored one `{key, dir}` object (or\n * null); arrays pass through with junk entries dropped. */\nexport function normalizeSorts(raw: unknown): SortState[] {\n const one = (v: unknown): SortState | null => {\n if (!v || typeof v !== \"object\") return null;\n const { key, dir } = v as { key?: unknown; dir?: unknown };\n if (typeof key !== \"string\" || !key) return null;\n return { key, dir: dir === \"desc\" ? \"desc\" : \"asc\" };\n };\n if (Array.isArray(raw)) {\n const out: SortState[] = [];\n for (const entry of raw) {\n const s = one(entry);\n if (s && !out.some((e) => e.key === s.key)) out.push(s);\n }\n return out;\n }\n const single = one(raw);\n return single ? [single] : [];\n}\n\nexport function encodeSorts(sorts: SortState[]): string | null {\n if (!sorts.length) return null;\n return sorts.map((s) => (s.dir === \"desc\" ? `${s.key}.desc` : s.key)).join(\",\");\n}\n\n/** Decode a `sort` URL param, dropping unknown and duplicated keys. */\nexport function decodeSorts(raw: string | null, validKeys: ReadonlySet<string>): SortState[] {\n if (!raw) return [];\n const out: SortState[] = [];\n for (const part of raw.split(\",\")) {\n const desc = part.endsWith(\".desc\");\n const asc = part.endsWith(\".asc\");\n const key = desc ? part.slice(0, -\".desc\".length) : asc ? part.slice(0, -\".asc\".length) : part;\n if (!validKeys.has(key) || out.some((s) => s.key === key)) continue;\n out.push({ key, dir: desc ? \"desc\" : \"asc\" });\n }\n return out;\n}\n\n/** Header-click semantics. Plain click: make `key` the only sort — cycling\n * asc → desc → none when it already is the sole criterion. Shift-click\n * (`additive`): append `key` as the next-priority sort, or cycle an existing\n * entry's direction in place (asc → desc → removed). */\nexport function nextSorts(prev: SortState[], key: string, additive: boolean): SortState[] {\n const idx = prev.findIndex((s) => s.key === key);\n if (!additive) {\n if (prev.length === 1 && idx === 0) {\n return prev[0].dir === \"asc\" ? [{ key, dir: \"desc\" }] : [];\n }\n return [{ key, dir: \"asc\" }];\n }\n if (idx === -1) return [...prev, { key, dir: \"asc\" }];\n if (prev[idx].dir === \"asc\") {\n return prev.map((s, i) => (i === idx ? { key, dir: \"desc\" as SortDir } : s));\n }\n return prev.filter((_, i) => i !== idx);\n}\n"],"mappings":";AAcO,SAAS,eAAe,KAA2B;AACxD,QAAM,MAAM,CAAC,MAAiC;AAC5C,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,UAAM,EAAE,KAAK,IAAI,IAAI;AACrB,QAAI,OAAO,QAAQ,YAAY,CAAC,IAAK,QAAO;AAC5C,WAAO,EAAE,KAAK,KAAK,QAAQ,SAAS,SAAS,MAAM;AAAA,EACrD;AACA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,UAAM,MAAmB,CAAC;AAC1B,eAAW,SAAS,KAAK;AACvB,YAAM,IAAI,IAAI,KAAK;AACnB,UAAI,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAG,KAAI,KAAK,CAAC;AAAA,IACxD;AACA,WAAO;AAAA,EACT;AACA,QAAM,SAAS,IAAI,GAAG;AACtB,SAAO,SAAS,CAAC,MAAM,IAAI,CAAC;AAC9B;AAEO,SAAS,YAAY,OAAmC;AAC7D,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,SAAO,MAAM,IAAI,CAAC,MAAO,EAAE,QAAQ,SAAS,GAAG,EAAE,GAAG,UAAU,EAAE,GAAI,EAAE,KAAK,GAAG;AAChF;AAGO,SAAS,YAAY,KAAoB,WAA6C;AAC3F,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,QAAM,MAAmB,CAAC;AAC1B,aAAW,QAAQ,IAAI,MAAM,GAAG,GAAG;AACjC,UAAM,OAAO,KAAK,SAAS,OAAO;AAClC,UAAM,MAAM,KAAK,SAAS,MAAM;AAChC,UAAM,MAAM,OAAO,KAAK,MAAM,GAAG,CAAC,QAAQ,MAAM,IAAI,MAAM,KAAK,MAAM,GAAG,CAAC,OAAO,MAAM,IAAI;AAC1F,QAAI,CAAC,UAAU,IAAI,GAAG,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG,EAAG;AAC3D,QAAI,KAAK,EAAE,KAAK,KAAK,OAAO,SAAS,MAAM,CAAC;AAAA,EAC9C;AACA,SAAO;AACT;AAMO,SAAS,UAAU,MAAmB,KAAa,UAAgC;AACxF,QAAM,MAAM,KAAK,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG;AAC/C,MAAI,CAAC,UAAU;AACb,QAAI,KAAK,WAAW,KAAK,QAAQ,GAAG;AAClC,aAAO,KAAK,CAAC,EAAE,QAAQ,QAAQ,CAAC,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC;AAAA,IAC3D;AACA,WAAO,CAAC,EAAE,KAAK,KAAK,MAAM,CAAC;AAAA,EAC7B;AACA,MAAI,QAAQ,GAAI,QAAO,CAAC,GAAG,MAAM,EAAE,KAAK,KAAK,MAAM,CAAC;AACpD,MAAI,KAAK,GAAG,EAAE,QAAQ,OAAO;AAC3B,WAAO,KAAK,IAAI,CAAC,GAAG,MAAO,MAAM,MAAM,EAAE,KAAK,KAAK,OAAkB,IAAI,CAAE;AAAA,EAC7E;AACA,SAAO,KAAK,OAAO,CAAC,GAAG,MAAM,MAAM,GAAG;AACxC;","names":[]}
1
+ {"version":3,"sources":["../../src/components/data-table-sort.ts"],"sourcesContent":["/** Multi-column sort state for DataTable: ordered by priority (index 0 is the\n * primary sort). Encoded as `sort=date.desc,amount` in URLs and the server's\n * transactions `sort` param — the pre-multi-sort single-value format\n * (`sort=date.desc`) decodes as a one-element list. */\n\nexport type SortDir = \"asc\" | \"desc\";\nexport interface SortState {\n key: string;\n dir: SortDir;\n}\n\n/** Coerce any persisted/URL-derived shape into a `SortState[]`. The\n * pre-multi-sort localStorage format stored one `{key, dir}` object (or\n * null); arrays pass through with junk entries dropped. */\nexport function normalizeSorts(raw: unknown): SortState[] {\n const one = (v: unknown): SortState | null => {\n if (!v || typeof v !== \"object\") return null;\n const { key, dir } = v as { key?: unknown; dir?: unknown };\n if (typeof key !== \"string\" || !key) return null;\n return { key, dir: dir === \"desc\" ? \"desc\" : \"asc\" };\n };\n if (Array.isArray(raw)) {\n const out: SortState[] = [];\n for (const entry of raw) {\n const s = one(entry);\n if (s && !out.some((e) => e.key === s.key)) out.push(s);\n }\n return out;\n }\n const single = one(raw);\n return single ? [single] : [];\n}\n\nexport function encodeSorts(sorts: SortState[]): string | null {\n if (!sorts.length) return null;\n return sorts.map((s) => (s.dir === \"desc\" ? `${s.key}.desc` : s.key)).join(\",\");\n}\n\n/** Decode a `sort` URL param, dropping unknown and duplicated keys. */\nexport function decodeSorts(raw: string | null, validKeys: ReadonlySet<string>): SortState[] {\n if (!raw) return [];\n const out: SortState[] = [];\n for (const part of raw.split(\",\")) {\n const desc = part.endsWith(\".desc\");\n const asc = part.endsWith(\".asc\");\n const key = desc ? part.slice(0, -\".desc\".length) : asc ? part.slice(0, -\".asc\".length) : part;\n if (!validKeys.has(key) || out.some((s) => s.key === key)) continue;\n out.push({ key, dir: desc ? \"desc\" : \"asc\" });\n }\n return out;\n}\n\n/**\n * How a header click steps through the directions (see {@link nextSorts}).\n *\n * * `\"tri\"` — first direction → the other → unsorted. The default, and the only cycle\n * before 0.8.0.\n * * `\"toggle\"` — first direction ⇄ the other, never unsorted: a report table that is\n * ALWAYS ranked by something (keksdose's aggregated tables) must not have a third\n * click that silently drops the ranking. Clicking another column still replaces\n * the sort, so the table can always be re-ranked; a shift-click criterion flips in\n * place and stays for the same reason.\n */\nexport type SortCycle = \"tri\" | \"toggle\";\n\n/** Per-click options for {@link nextSorts}; every field is optional and the\n * defaults reproduce the pre-0.8.0 cycle exactly. */\nexport interface SortStepOptions {\n /** The direction a column starts in on its first click. `\"desc\"` for money and\n * count columns, where the interesting rows are the large ones. Default `\"asc\"`. */\n firstDir?: SortDir;\n /** Default `\"tri\"`. */\n cycle?: SortCycle;\n}\n\nconst flip = (d: SortDir): SortDir => (d === \"asc\" ? \"desc\" : \"asc\");\n\n/** Header-click semantics. Plain click: make `key` the only sort — cycling\n * first → other → none (`\"tri\"`) or first ⇄ other (`\"toggle\"`) when it already is\n * the sole criterion. Shift-click (`additive`): append `key` as the next-priority\n * sort, or step an existing entry in place (first → other → removed under `\"tri\"`,\n * a plain flip under `\"toggle\"`). `firstDir` defaults to `\"asc\"`, `cycle` to\n * `\"tri\"` — the cycle every table had before the options existed. */\nexport function nextSorts(\n prev: SortState[],\n key: string,\n additive: boolean,\n options: SortStepOptions = {},\n): SortState[] {\n const first = options.firstDir ?? \"asc\";\n const toggle = options.cycle === \"toggle\";\n const idx = prev.findIndex((s) => s.key === key);\n // One step from `dir`: the second direction after the first, then either back to\n // the first (toggle) or off (null). Judged against THIS column's first direction,\n // so a desc-first column goes desc → asc → none rather than skipping a step.\n const step = (dir: SortDir): SortDir | null =>\n dir === first ? flip(first) : toggle ? first : null;\n if (!additive) {\n if (prev.length === 1 && idx === 0) {\n const dir = step(prev[0].dir);\n return dir ? [{ key, dir }] : [];\n }\n return [{ key, dir: first }];\n }\n if (idx === -1) return [...prev, { key, dir: first }];\n const dir = step(prev[idx].dir);\n if (dir) return prev.map((s, i) => (i === idx ? { key, dir } : s));\n return prev.filter((_, i) => i !== idx);\n}\n"],"mappings":";AAcO,SAAS,eAAe,KAA2B;AACxD,QAAM,MAAM,CAAC,MAAiC;AAC5C,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,UAAM,EAAE,KAAK,IAAI,IAAI;AACrB,QAAI,OAAO,QAAQ,YAAY,CAAC,IAAK,QAAO;AAC5C,WAAO,EAAE,KAAK,KAAK,QAAQ,SAAS,SAAS,MAAM;AAAA,EACrD;AACA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,UAAM,MAAmB,CAAC;AAC1B,eAAW,SAAS,KAAK;AACvB,YAAM,IAAI,IAAI,KAAK;AACnB,UAAI,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAG,KAAI,KAAK,CAAC;AAAA,IACxD;AACA,WAAO;AAAA,EACT;AACA,QAAM,SAAS,IAAI,GAAG;AACtB,SAAO,SAAS,CAAC,MAAM,IAAI,CAAC;AAC9B;AAEO,SAAS,YAAY,OAAmC;AAC7D,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,SAAO,MAAM,IAAI,CAAC,MAAO,EAAE,QAAQ,SAAS,GAAG,EAAE,GAAG,UAAU,EAAE,GAAI,EAAE,KAAK,GAAG;AAChF;AAGO,SAAS,YAAY,KAAoB,WAA6C;AAC3F,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,QAAM,MAAmB,CAAC;AAC1B,aAAW,QAAQ,IAAI,MAAM,GAAG,GAAG;AACjC,UAAM,OAAO,KAAK,SAAS,OAAO;AAClC,UAAM,MAAM,KAAK,SAAS,MAAM;AAChC,UAAM,MAAM,OAAO,KAAK,MAAM,GAAG,CAAC,QAAQ,MAAM,IAAI,MAAM,KAAK,MAAM,GAAG,CAAC,OAAO,MAAM,IAAI;AAC1F,QAAI,CAAC,UAAU,IAAI,GAAG,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG,EAAG;AAC3D,QAAI,KAAK,EAAE,KAAK,KAAK,OAAO,SAAS,MAAM,CAAC;AAAA,EAC9C;AACA,SAAO;AACT;AAyBA,MAAM,OAAO,CAAC,MAAyB,MAAM,QAAQ,SAAS;AAQvD,SAAS,UACd,MACA,KACA,UACA,UAA2B,CAAC,GACf;AACb,QAAM,QAAQ,QAAQ,YAAY;AAClC,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,MAAM,KAAK,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG;AAI/C,QAAM,OAAO,CAACA,SACZA,SAAQ,QAAQ,KAAK,KAAK,IAAI,SAAS,QAAQ;AACjD,MAAI,CAAC,UAAU;AACb,QAAI,KAAK,WAAW,KAAK,QAAQ,GAAG;AAClC,YAAMA,OAAM,KAAK,KAAK,CAAC,EAAE,GAAG;AAC5B,aAAOA,OAAM,CAAC,EAAE,KAAK,KAAAA,KAAI,CAAC,IAAI,CAAC;AAAA,IACjC;AACA,WAAO,CAAC,EAAE,KAAK,KAAK,MAAM,CAAC;AAAA,EAC7B;AACA,MAAI,QAAQ,GAAI,QAAO,CAAC,GAAG,MAAM,EAAE,KAAK,KAAK,MAAM,CAAC;AACpD,QAAM,MAAM,KAAK,KAAK,GAAG,EAAE,GAAG;AAC9B,MAAI,IAAK,QAAO,KAAK,IAAI,CAAC,GAAG,MAAO,MAAM,MAAM,EAAE,KAAK,IAAI,IAAI,CAAE;AACjE,SAAO,KAAK,OAAO,CAAC,GAAG,MAAM,MAAM,GAAG;AACxC;","names":["dir"]}
@@ -1,5 +1,5 @@
1
1
  import 'react';
2
- export { D as DataTable, a as DataTableColumn, b as DataTableProps, F as FilterState, M as MobileSwipeActions, S as ServerPagination } from '../data-table-filters-noy0Abvi.js';
2
+ export { D as DataTable, a as DataTableChrome, b as DataTableColumn, c as DataTableDensity, d as DataTableProps, F as FilterState, M as MobileSwipeActions, S as ServerPagination } from '../data-table-filters-CF1PXqjQ.js';
3
3
  export { SortState } from './data-table-sort.js';
4
4
  import './swipeable-row.js';
5
5
  import './data-table-labels.js';