@eifi1/ui-kit 0.9.0 → 0.11.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 (243) hide show
  1. package/README.md +37 -23
  2. package/dist/components/alert-banner.d.ts +57 -5
  3. package/dist/components/alert-banner.js +63 -17
  4. package/dist/components/alert-banner.js.map +1 -1
  5. package/dist/components/amount-input.d.ts +6 -0
  6. package/dist/components/breadcrumbs.d.ts +60 -0
  7. package/dist/components/breadcrumbs.js +88 -0
  8. package/dist/components/breadcrumbs.js.map +1 -0
  9. package/dist/components/bulk-action-bar.d.ts +112 -0
  10. package/dist/components/bulk-action-bar.js +196 -0
  11. package/dist/components/bulk-action-bar.js.map +1 -0
  12. package/dist/components/button-group.d.ts +58 -3
  13. package/dist/components/button-group.js +56 -5
  14. package/dist/components/button-group.js.map +1 -1
  15. package/dist/components/calculator.d.ts +6 -0
  16. package/dist/components/calendar-heatmap.d.ts +124 -0
  17. package/dist/components/calendar-heatmap.js +295 -0
  18. package/dist/components/calendar-heatmap.js.map +1 -0
  19. package/dist/components/chip.d.ts +31 -5
  20. package/dist/components/chip.js +85 -5
  21. package/dist/components/chip.js.map +1 -1
  22. package/dist/components/choice-card.d.ts +34 -2
  23. package/dist/components/choice-card.js +49 -0
  24. package/dist/components/choice-card.js.map +1 -1
  25. package/dist/components/copy-button.d.ts +14 -4
  26. package/dist/components/copy-button.js +17 -4
  27. package/dist/components/copy-button.js.map +1 -1
  28. package/dist/components/data-table.js +2 -0
  29. package/dist/components/data-table.js.map +1 -1
  30. package/dist/components/date-picker.d.ts +22 -2
  31. package/dist/components/date-picker.js +11 -3
  32. package/dist/components/date-picker.js.map +1 -1
  33. package/dist/components/description-list.d.ts +43 -6
  34. package/dist/components/description-list.js +91 -11
  35. package/dist/components/description-list.js.map +1 -1
  36. package/dist/components/dialog-frame.d.ts +7 -0
  37. package/dist/components/dialog-frame.js.map +1 -1
  38. package/dist/components/disclosure.d.ts +57 -2
  39. package/dist/components/disclosure.js +24 -12
  40. package/dist/components/disclosure.js.map +1 -1
  41. package/dist/components/field.d.ts +43 -0
  42. package/dist/components/field.js +52 -0
  43. package/dist/components/field.js.map +1 -0
  44. package/dist/components/file-button.d.ts +1 -0
  45. package/dist/components/file-dropzone.d.ts +4 -4
  46. package/dist/components/file-dropzone.js +2 -1
  47. package/dist/components/file-dropzone.js.map +1 -1
  48. package/dist/components/floating-panel.d.ts +147 -4
  49. package/dist/components/floating-panel.js +188 -28
  50. package/dist/components/floating-panel.js.map +1 -1
  51. package/dist/components/full-bleed-dialog.d.ts +23 -4
  52. package/dist/components/full-bleed-dialog.js +11 -2
  53. package/dist/components/full-bleed-dialog.js.map +1 -1
  54. package/dist/components/list.d.ts +187 -0
  55. package/dist/components/list.js +221 -0
  56. package/dist/components/list.js.map +1 -0
  57. package/dist/components/menu-item.d.ts +109 -0
  58. package/dist/components/menu-item.js +88 -0
  59. package/dist/components/menu-item.js.map +1 -0
  60. package/dist/components/mini-calendar.d.ts +70 -5
  61. package/dist/components/mini-calendar.js +166 -62
  62. package/dist/components/mini-calendar.js.map +1 -1
  63. package/dist/components/modal.d.ts +23 -1
  64. package/dist/components/modal.js +36 -10
  65. package/dist/components/modal.js.map +1 -1
  66. package/dist/components/nav-pills.d.ts +66 -0
  67. package/dist/components/nav-pills.js +65 -0
  68. package/dist/components/nav-pills.js.map +1 -0
  69. package/dist/components/number-field.d.ts +6 -0
  70. package/dist/components/number-input.d.ts +6 -0
  71. package/dist/components/numpad-sheet.d.ts +6 -0
  72. package/dist/components/page-contents.js +1 -2
  73. package/dist/components/page-contents.js.map +1 -1
  74. package/dist/components/page-header.d.ts +37 -0
  75. package/dist/components/page-header.js +36 -0
  76. package/dist/components/page-header.js.map +1 -0
  77. package/dist/components/progress-bar.d.ts +31 -2
  78. package/dist/components/progress-bar.js +73 -7
  79. package/dist/components/progress-bar.js.map +1 -1
  80. package/dist/components/series-chart-labels.d.ts +3 -0
  81. package/dist/components/series-chart-labels.js +2 -1
  82. package/dist/components/series-chart-labels.js.map +1 -1
  83. package/dist/components/series-chart.d.ts +32 -0
  84. package/dist/components/series-chart.js +141 -4
  85. package/dist/components/series-chart.js.map +1 -1
  86. package/dist/components/settings-fields.d.ts +1 -0
  87. package/dist/components/status-dot.d.ts +53 -0
  88. package/dist/components/status-dot.js +57 -0
  89. package/dist/components/status-dot.js.map +1 -0
  90. package/dist/components/table.d.ts +78 -13
  91. package/dist/components/table.js +84 -11
  92. package/dist/components/table.js.map +1 -1
  93. package/dist/components/text.d.ts +70 -0
  94. package/dist/components/text.js +24 -0
  95. package/dist/components/text.js.map +1 -0
  96. package/dist/components/time-input.d.ts +1 -0
  97. package/dist/components/toast.d.ts +167 -0
  98. package/dist/components/toast.js +226 -0
  99. package/dist/components/toast.js.map +1 -0
  100. package/dist/components/toggle-group.d.ts +44 -1
  101. package/dist/components/toggle-group.js +73 -4
  102. package/dist/components/toggle-group.js.map +1 -1
  103. package/dist/components/tooltip.d.ts +46 -15
  104. package/dist/components/tooltip.js +87 -47
  105. package/dist/components/tooltip.js.map +1 -1
  106. package/dist/components/ui.d.ts +158 -7
  107. package/dist/components/ui.js +191 -10
  108. package/dist/components/ui.js.map +1 -1
  109. package/dist/components/user-avatar.d.ts +25 -3
  110. package/dist/components/user-avatar.js +32 -3
  111. package/dist/components/user-avatar.js.map +1 -1
  112. package/dist/hooks/use-close-transition.d.ts +7 -1
  113. package/dist/hooks/use-close-transition.js +5 -2
  114. package/dist/hooks/use-close-transition.js.map +1 -1
  115. package/dist/hooks/use-copy-to-clipboard.js +1 -1
  116. package/dist/hooks/use-copy-to-clipboard.js.map +1 -1
  117. package/dist/hooks/use-file-drop.d.ts +1 -0
  118. package/dist/i18n/defaults.d.ts +6 -0
  119. package/dist/i18n/defaults.js +11 -1
  120. package/dist/i18n/defaults.js.map +1 -1
  121. package/dist/i18n/kit-labels.d.ts +11 -0
  122. package/dist/i18n/kit-labels.js.map +1 -1
  123. package/dist/i18n/locales/de-CH-informal.d.ts +6 -0
  124. package/dist/i18n/locales/de-CH.d.ts +6 -0
  125. package/dist/i18n/locales/de-informal.d.ts +6 -0
  126. package/dist/i18n/locales/de.d.ts +6 -0
  127. package/dist/i18n/locales/de.js +30 -2
  128. package/dist/i18n/locales/de.js.map +1 -1
  129. package/dist/i18n/locales/es.d.ts +6 -0
  130. package/dist/i18n/locales/es.js +30 -2
  131. package/dist/i18n/locales/es.js.map +1 -1
  132. package/dist/i18n/locales/fr.d.ts +6 -0
  133. package/dist/i18n/locales/fr.js +30 -2
  134. package/dist/i18n/locales/fr.js.map +1 -1
  135. package/dist/i18n/locales/hu.d.ts +6 -0
  136. package/dist/i18n/locales/hu.js +30 -2
  137. package/dist/i18n/locales/hu.js.map +1 -1
  138. package/dist/i18n/locales/it.d.ts +6 -0
  139. package/dist/i18n/locales/it.js +30 -2
  140. package/dist/i18n/locales/it.js.map +1 -1
  141. package/dist/i18n/locales/zh.d.ts +6 -0
  142. package/dist/i18n/locales/zh.js +30 -2
  143. package/dist/i18n/locales/zh.js.map +1 -1
  144. package/dist/index.d.ts +27 -15
  145. package/dist/index.js +15 -0
  146. package/dist/index.js.map +1 -1
  147. package/dist/lib/clipping.d.ts +25 -0
  148. package/dist/lib/clipping.js +17 -0
  149. package/dist/lib/clipping.js.map +1 -0
  150. package/dist/lib/dates.d.ts +17 -1
  151. package/dist/lib/dates.js +19 -0
  152. package/dist/lib/dates.js.map +1 -1
  153. package/dist/rhf/form.d.ts +1 -0
  154. package/dist/rhf/use-rhf-wizard-step.d.ts +22 -0
  155. package/dist/rhf/use-rhf-wizard-step.js +38 -0
  156. package/dist/rhf/use-rhf-wizard-step.js.map +1 -0
  157. package/dist/rhf.d.ts +2 -0
  158. package/dist/rhf.js +1 -0
  159. package/dist/rhf.js.map +1 -1
  160. package/dist/search/command-palette.d.ts +19 -2
  161. package/dist/search/command-palette.js +16 -5
  162. package/dist/search/command-palette.js.map +1 -1
  163. package/dist/search/global-search.d.ts +26 -1
  164. package/dist/search/global-search.js +29 -13
  165. package/dist/search/global-search.js.map +1 -1
  166. package/dist/search.d.ts +1 -1
  167. package/dist/shell/topbar-action-menu.d.ts +73 -3
  168. package/dist/shell/topbar-action-menu.js +100 -27
  169. package/dist/shell/topbar-action-menu.js.map +1 -1
  170. package/dist/shell.d.ts +2 -1
  171. package/dist/wizard/stepper-nav.d.ts +1 -0
  172. package/dist/wizard/stepper-nav.js +1 -1
  173. package/dist/wizard/stepper-nav.js.map +1 -1
  174. package/dist/wizard/types.d.ts +2 -2
  175. package/dist/wizard/types.js.map +1 -1
  176. package/dist/wizard/use-wizard.js +1 -1
  177. package/dist/wizard/use-wizard.js.map +1 -1
  178. package/dist/wizard/wizard-context.d.ts +7 -1
  179. package/dist/wizard/wizard-context.js +4 -0
  180. package/dist/wizard/wizard-context.js.map +1 -1
  181. package/dist/wizard/wizard-summary.js +3 -3
  182. package/dist/wizard/wizard-summary.js.map +1 -1
  183. package/dist/wizard.d.ts +2 -1
  184. package/package.json +16 -12
  185. package/src/components/alert-banner.tsx +149 -21
  186. package/src/components/breadcrumbs.tsx +168 -0
  187. package/src/components/bulk-action-bar.tsx +362 -0
  188. package/src/components/button-group.tsx +124 -4
  189. package/src/components/calendar-heatmap.tsx +504 -0
  190. package/src/components/chip.tsx +128 -7
  191. package/src/components/choice-card.tsx +97 -1
  192. package/src/components/copy-button.tsx +29 -7
  193. package/src/components/data-table.tsx +7 -0
  194. package/src/components/date-picker.tsx +49 -1
  195. package/src/components/description-list.tsx +168 -15
  196. package/src/components/dialog-frame.tsx +7 -0
  197. package/src/components/disclosure.tsx +93 -21
  198. package/src/components/field.tsx +137 -0
  199. package/src/components/file-dropzone.tsx +9 -10
  200. package/src/components/floating-panel.tsx +366 -15
  201. package/src/components/full-bleed-dialog.tsx +42 -5
  202. package/src/components/list.tsx +443 -0
  203. package/src/components/menu-item.tsx +235 -0
  204. package/src/components/mini-calendar.tsx +245 -54
  205. package/src/components/modal.tsx +80 -17
  206. package/src/components/nav-pills.tsx +145 -0
  207. package/src/components/page-contents.tsx +4 -4
  208. package/src/components/page-header.tsx +68 -0
  209. package/src/components/progress-bar.tsx +129 -11
  210. package/src/components/series-chart-labels.ts +4 -0
  211. package/src/components/series-chart.tsx +274 -3
  212. package/src/components/status-dot.tsx +107 -0
  213. package/src/components/table.tsx +176 -17
  214. package/src/components/text.tsx +97 -0
  215. package/src/components/toast.tsx +441 -0
  216. package/src/components/toggle-group.tsx +128 -4
  217. package/src/components/tooltip.tsx +200 -100
  218. package/src/components/ui.tsx +381 -14
  219. package/src/components/user-avatar.tsx +57 -3
  220. package/src/hooks/use-close-transition.ts +14 -5
  221. package/src/hooks/use-copy-to-clipboard.ts +1 -1
  222. package/src/i18n/defaults.ts +10 -0
  223. package/src/i18n/kit-labels.tsx +10 -0
  224. package/src/i18n/locales/de.ts +29 -0
  225. package/src/i18n/locales/es.ts +29 -0
  226. package/src/i18n/locales/fr.ts +29 -0
  227. package/src/i18n/locales/hu.ts +29 -0
  228. package/src/i18n/locales/it.ts +29 -0
  229. package/src/i18n/locales/zh.ts +29 -0
  230. package/src/index.ts +31 -0
  231. package/src/lib/clipping.ts +34 -0
  232. package/src/lib/dates.ts +37 -0
  233. package/src/rhf/use-rhf-wizard-step.ts +113 -0
  234. package/src/rhf.ts +2 -0
  235. package/src/search/command-palette.tsx +42 -5
  236. package/src/search/global-search.tsx +59 -18
  237. package/src/shell/topbar-action-menu.tsx +239 -49
  238. package/src/wizard/stepper-nav.tsx +2 -1
  239. package/src/wizard/types.ts +2 -2
  240. package/src/wizard/use-wizard.ts +3 -3
  241. package/src/wizard/wizard-context.tsx +9 -0
  242. package/src/wizard/wizard-summary.tsx +14 -10
  243. package/tokens.css +97 -0
@@ -1,8 +1,31 @@
1
1
  import * as react from 'react';
2
- import { ComponentPropsWithoutRef } from 'react';
2
+ import { ComponentPropsWithoutRef, ReactNode } from 'react';
3
3
 
4
- type TableDensity = "comfortable" | "compact";
4
+ /** `none` (0.10.0): no cell padding at all — keksdose's VAT summary, a table set
5
+ * inside a card's own padding whose columns are spaced by hand (`pl-2` on exactly the
6
+ * columns that need it). Type stays at the body size, as `comfortable`. */
7
+ type TableDensity = "comfortable" | "compact" | "none";
8
+ /** CSS `table-layout`. `auto` (the browser default) sizes columns to their content;
9
+ * `fixed` takes widths from the first row and ignores the rest — keksdose's VAT
10
+ * summary switches to it while editing so two inputs stop bidding against the
11
+ * figures for room. */
12
+ type TableLayout = "auto" | "fixed";
5
13
  type TableAlign = "start" | "center" | "end";
14
+ /**
15
+ * Vertical alignment of a cell's content (0.11.0). The defaults stay as they were — a
16
+ * head cell sits on the `bottom` (a wrapped heading ends on the line above the
17
+ * figures), a body cell at the `top` (a wrapped note starts level with its row). A
18
+ * table whose cells are INPUTS wants `middle`: keksdose's VAT summary
19
+ * (invoices/vat-summary.tsx) sets fields beside derived read-out figures in one row,
20
+ * and a figure at the top of its cell sat above the middle of the field next to it —
21
+ * so it writes `<tr className="align-middle">` over raw `<th>`/`<td>`, because the
22
+ * kit's cells each stated `align-top` and a class on the row could not reach them.
23
+ */
24
+ type TableVAlign = "top" | "middle" | "bottom";
25
+ /** The head cell's type size: `xs` (the head's default) or `sm` (the body's). */
26
+ type TableHeaderCellSize = "xs" | "sm";
27
+ /** The header cell's weight. Default `medium`. */
28
+ type TableHeaderCellWeight = "normal" | "medium" | "semibold";
6
29
  /**
7
30
  * The alignment a figure column wants: end-aligned (so the units line up under each
8
31
  * other in either reading direction) with tabular digits (so they line up at all).
@@ -20,6 +43,8 @@ interface TableProps extends ComponentPropsWithoutRef<"table"> {
20
43
  hover?: boolean;
21
44
  /** Classes for the overflow wrapper around the `<table>` (a max-height, a border). */
22
45
  wrapperClassName?: string;
46
+ /** See {@link TableLayout}. Unset leaves the browser's `auto` and adds no class. */
47
+ layout?: TableLayout;
23
48
  }
24
49
  /**
25
50
  * A plain, static HTML table in the kit's tokens — for the detail views that need
@@ -38,35 +63,75 @@ interface TableProps extends ComponentPropsWithoutRef<"table"> {
38
63
  * Alignment is logical throughout (`text-start` / `text-end`), so a right-to-left
39
64
  * page gets its first column on the right without a class changing.
40
65
  */
41
- declare function Table({ density, zebra, hover, wrapperClassName, className, "aria-label": ariaLabel, ...rest }: TableProps): react.JSX.Element;
66
+ declare function Table({ density, zebra, hover, wrapperClassName, layout, className, "aria-label": ariaLabel, ...rest }: TableProps): react.JSX.Element;
42
67
  type TableHeadProps = ComponentPropsWithoutRef<"thead">;
43
68
  declare function TableHead({ className, ...rest }: TableHeadProps): react.JSX.Element;
44
- type TableBodyProps = ComponentPropsWithoutRef<"tbody">;
45
- declare function TableBody({ className, ...rest }: TableBodyProps): react.JSX.Element;
69
+ interface TableBodyProps extends ComponentPropsWithoutRef<"tbody"> {
70
+ /**
71
+ * Shown as ONE full-width row (a {@link TableEmpty}) when the body has no rows —
72
+ * `children` that render nothing: an empty `.map`, a `false`, `null`. kastlan hand-
73
+ * writes that row with a counted `colSpan` in unit-values-editor, the meeting agenda
74
+ * and invitations tabs, the journal-entry and invoice detail pages, and a column
75
+ * added later leaves every one of those counts one short.
76
+ */
77
+ empty?: ReactNode;
78
+ }
79
+ declare function TableBody({ className, empty, children, ...rest }: TableBodyProps): react.JSX.Element;
80
+ interface TableEmptyProps extends Omit<ComponentPropsWithoutRef<"tr">, "children"> {
81
+ children: ReactNode;
82
+ /** Columns to span. Default: MEASURED — the widest row of the table it renders in,
83
+ * counted after mount, so the row stays full width when a column is added. */
84
+ colSpan?: number;
85
+ /** Classes for the `<td>`. */
86
+ cellClassName?: string;
87
+ }
88
+ /**
89
+ * The "nothing here" row: one cell across every column, centred, muted. What
90
+ * {@link TableBodyProps.empty} renders, and usable on its own for a body whose rows
91
+ * are not its direct children.
92
+ */
93
+ declare function TableEmpty({ children, colSpan, cellClassName, className, ...rest }: TableEmptyProps): react.JSX.Element;
46
94
  type TableFootProps = ComponentPropsWithoutRef<"tfoot">;
47
95
  /** The totals row. Not in the brief, but every ledger view has one and a `<tfoot>`
48
96
  * is what tells a reader it is a total rather than one more row. */
49
97
  declare function TableFoot({ className, ...rest }: TableFootProps): react.JSX.Element;
50
- type TableRowProps = ComponentPropsWithoutRef<"tr">;
51
- declare function TableRow({ className, ...rest }: TableRowProps): react.JSX.Element;
98
+ /** The deprecated HTML `valign` attribute is replaced by a class-backed one. */
99
+ interface TableRowProps extends ComponentPropsWithoutRef<"tr"> {
100
+ /** Vertical alignment for every cell of the row that does not set its own. See
101
+ * {@link TableVAlign}. Left out, each cell keeps its default. */
102
+ valign?: TableVAlign;
103
+ }
104
+ declare function TableRow({ valign, className, ...rest }: TableRowProps): react.JSX.Element;
52
105
  interface CellAlignProps {
53
106
  /** Shorthand for a figure column: end-aligned, tabular digits. */
54
107
  numeric?: boolean;
55
108
  /** Logical alignment. Default `start` (`end` when `numeric`). */
56
109
  align?: TableAlign;
110
+ /** Vertical alignment. Default: the row's `valign`, else `bottom` for a head cell and
111
+ * `top` for a body cell. See {@link TableVAlign}. */
112
+ valign?: TableVAlign;
57
113
  }
58
- /** The deprecated HTML `align` attribute is replaced by a logical one. */
59
- interface TableHeaderCellProps extends Omit<ComponentPropsWithoutRef<"th">, "align">, CellAlignProps {
114
+ /** The deprecated HTML `align` and `valign` attributes are replaced by class-backed ones. */
115
+ interface TableHeaderCellProps extends Omit<ComponentPropsWithoutRef<"th">, "align" | "valign">, CellAlignProps {
116
+ /**
117
+ * Type size. Left out: `xs` in the head, the table's own size in the body — as
118
+ * before. keksdose's VAT summary heads its columns at the body size and normal weight
119
+ * (vat-summary.tsx:158–168, `font-normal` on each `<th>`), a quiet header over a
120
+ * small table of figures, and could not say so without overriding classes.
121
+ */
122
+ size?: TableHeaderCellSize;
123
+ /** Default `medium`. `normal` for the quiet header above; see {@link size}. */
124
+ weight?: TableHeaderCellWeight;
60
125
  }
61
126
  /**
62
127
  * A `<th>`. `scope` defaults to `col` in the head and `row` in the body — a header
63
128
  * cell in a body row is the row's label ("Net rent" in a key-value table), and a
64
129
  * reader needs the scope to read it with each cell beside it.
65
130
  */
66
- declare function TableHeaderCell({ numeric, align, scope, className, ...rest }: TableHeaderCellProps): react.JSX.Element;
67
- interface TableCellProps extends Omit<ComponentPropsWithoutRef<"td">, "align">, CellAlignProps {
131
+ declare function TableHeaderCell({ numeric, align, valign, size, weight, scope, className, ...rest }: TableHeaderCellProps): react.JSX.Element;
132
+ interface TableCellProps extends Omit<ComponentPropsWithoutRef<"td">, "align" | "valign">, CellAlignProps {
68
133
  }
69
- declare function TableCell({ numeric, align, className, ...rest }: TableCellProps): react.JSX.Element;
134
+ declare function TableCell({ numeric, align, valign, className, ...rest }: TableCellProps): react.JSX.Element;
70
135
  type TableCaptionProps = ComponentPropsWithoutRef<"caption">;
71
136
  /**
72
137
  * The table's title. Beneath the table by default (`caption-bottom`, the shadcn
@@ -80,4 +145,4 @@ type TableCaptionProps = ComponentPropsWithoutRef<"caption">;
80
145
  */
81
146
  declare function TableCaption({ className, id, ...rest }: TableCaptionProps): react.JSX.Element;
82
147
 
83
- export { NUMERIC_CELL_CLASS, Table, type TableAlign, TableBody, type TableBodyProps, TableCaption, type TableCaptionProps, TableCell, type TableCellProps, type TableDensity, TableFoot, type TableFootProps, TableHead, type TableHeadProps, TableHeaderCell, type TableHeaderCellProps, type TableProps, TableRow, type TableRowProps };
148
+ export { NUMERIC_CELL_CLASS, Table, type TableAlign, TableBody, type TableBodyProps, TableCaption, type TableCaptionProps, TableCell, type TableCellProps, type TableDensity, TableEmpty, type TableEmptyProps, TableFoot, type TableFootProps, TableHead, type TableHeadProps, TableHeaderCell, type TableHeaderCellProps, type TableHeaderCellSize, type TableHeaderCellWeight, type TableLayout, type TableProps, TableRow, type TableRowProps, type TableVAlign };
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
- import { createContext, useContext, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
3
+ import { Children, createContext, useContext, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  import { cn } from "../lib/cn.js";
5
5
  import { THIN_SCROLLBAR_CLASS, useScrollOverflow } from "./scroll-area.js";
6
6
  const TableContext = createContext({
@@ -12,9 +12,25 @@ const TableContext = createContext({
12
12
  }
13
13
  });
14
14
  const SectionContext = createContext("body");
15
+ const RowVAlignContext = createContext(void 0);
16
+ const VALIGN = {
17
+ top: "align-top",
18
+ middle: "align-middle",
19
+ bottom: "align-bottom"
20
+ };
21
+ const HEADER_SIZE = {
22
+ xs: "text-xs",
23
+ sm: "text-sm"
24
+ };
25
+ const HEADER_WEIGHT = {
26
+ normal: "font-normal",
27
+ medium: "font-medium",
28
+ semibold: "font-semibold"
29
+ };
15
30
  const CELL_PAD = {
16
31
  comfortable: "px-3 py-2",
17
- compact: "px-2 py-1"
32
+ compact: "px-2 py-1",
33
+ none: "p-0"
18
34
  };
19
35
  const ALIGN = {
20
36
  start: "text-start",
@@ -27,6 +43,7 @@ function Table({
27
43
  zebra = false,
28
44
  hover = false,
29
45
  wrapperClassName,
46
+ layout,
30
47
  className,
31
48
  "aria-label": ariaLabel,
32
49
  ...rest
@@ -57,6 +74,7 @@ function Table({
57
74
  ...overflowing ? regionName : null,
58
75
  tabIndex: overflowing ? 0 : void 0,
59
76
  "data-overflowing": overflowing || void 0,
77
+ "data-clips": "",
60
78
  className: cn(
61
79
  // `relative`: the containing block for an `sr-only` caption.
62
80
  "relative w-full overflow-x-auto",
@@ -72,6 +90,8 @@ function Table({
72
90
  className: cn(
73
91
  "w-full caption-bottom border-collapse text-sm text-[var(--text-primary)]",
74
92
  density === "compact" && "text-xs",
93
+ layout === "fixed" && "table-fixed",
94
+ layout === "auto" && "table-auto",
75
95
  className
76
96
  )
77
97
  }
@@ -82,17 +102,51 @@ function Table({
82
102
  function TableHead({ className, ...rest }) {
83
103
  return /* @__PURE__ */ jsx(SectionContext.Provider, { value: "head", children: /* @__PURE__ */ jsx("thead", { ...rest, className: cn("border-b border-[var(--border)]", className) }) });
84
104
  }
85
- function TableBody({ className, ...rest }) {
86
- return /* @__PURE__ */ jsx(SectionContext.Provider, { value: "body", children: /* @__PURE__ */ jsx("tbody", { ...rest, className: cn("[&>tr:last-child]:border-b-0", className) }) });
105
+ function TableBody({ className, empty, children, ...rest }) {
106
+ const hasRows = Children.toArray(children).length > 0;
107
+ return /* @__PURE__ */ jsx(SectionContext.Provider, { value: "body", children: /* @__PURE__ */ jsx("tbody", { ...rest, className: cn("[&>tr:last-child]:border-b-0", className), children: hasRows || empty === void 0 || empty === null || empty === false ? children : /* @__PURE__ */ jsx(TableEmpty, { children: empty }) }) });
108
+ }
109
+ function TableEmpty({ children, colSpan, cellClassName, className, ...rest }) {
110
+ const { density } = useContext(TableContext);
111
+ const cell = useRef(null);
112
+ const [measured, setMeasured] = useState(1);
113
+ useLayoutEffect(() => {
114
+ if (colSpan !== void 0) return;
115
+ const row = cell.current?.parentElement;
116
+ const table = cell.current?.closest("table");
117
+ if (!row || !table) return;
118
+ let widest = 1;
119
+ for (const r of Array.from(table.rows)) {
120
+ if (r === row) continue;
121
+ let n = 0;
122
+ for (const c of Array.from(r.cells)) n += c.colSpan || 1;
123
+ widest = Math.max(widest, n);
124
+ }
125
+ setMeasured(widest);
126
+ });
127
+ return /* @__PURE__ */ jsx("tr", { ...rest, "data-table-empty": "", className, children: /* @__PURE__ */ jsx(
128
+ "td",
129
+ {
130
+ ref: cell,
131
+ colSpan: colSpan ?? measured,
132
+ className: cn(
133
+ // `none` keeps the message off the edges regardless: it is text, not a figure.
134
+ density === "none" ? "py-2" : CELL_PAD[density],
135
+ "text-center text-[var(--text-muted)]",
136
+ cellClassName
137
+ ),
138
+ children
139
+ }
140
+ ) });
87
141
  }
88
142
  function TableFoot({ className, ...rest }) {
89
143
  return /* @__PURE__ */ jsx(SectionContext.Provider, { value: "foot", children: /* @__PURE__ */ jsx("tfoot", { ...rest, className: cn("border-t border-[var(--border-strong)] font-medium", className) }) });
90
144
  }
91
- function TableRow({ className, ...rest }) {
145
+ function TableRow({ valign, className, ...rest }) {
92
146
  const { zebra, hover } = useContext(TableContext);
93
147
  const section = useContext(SectionContext);
94
148
  const body = section === "body";
95
- return /* @__PURE__ */ jsx(
149
+ const row = /* @__PURE__ */ jsx(
96
150
  "tr",
97
151
  {
98
152
  ...rest,
@@ -100,14 +154,29 @@ function TableRow({ className, ...rest }) {
100
154
  body && "border-b border-[var(--border)]",
101
155
  body && zebra && "even:bg-[var(--bg-surface-2)]",
102
156
  body && hover && "transition-colors hover:bg-[var(--bg-hover)]",
157
+ // On the row as well, for a raw `<td>` of the caller's, which inherits it.
158
+ valign && VALIGN[valign],
103
159
  className
104
160
  )
105
161
  }
106
162
  );
163
+ return valign ? /* @__PURE__ */ jsx(RowVAlignContext.Provider, { value: valign, children: row }) : row;
107
164
  }
108
- function TableHeaderCell({ numeric = false, align, scope, className, ...rest }) {
165
+ function TableHeaderCell({
166
+ numeric = false,
167
+ align,
168
+ valign,
169
+ size,
170
+ weight = "medium",
171
+ scope,
172
+ className,
173
+ ...rest
174
+ }) {
109
175
  const { density } = useContext(TableContext);
110
176
  const section = useContext(SectionContext);
177
+ const rowVAlign = useContext(RowVAlignContext);
178
+ const head = section === "head";
179
+ const textSize = size ?? (head ? "xs" : void 0);
111
180
  return /* @__PURE__ */ jsx(
112
181
  "th",
113
182
  {
@@ -117,22 +186,25 @@ function TableHeaderCell({ numeric = false, align, scope, className, ...rest })
117
186
  CELL_PAD[density],
118
187
  ALIGN[align ?? (numeric ? "end" : "start")],
119
188
  numeric && "tabular-nums",
120
- "align-bottom font-medium",
121
- section === "head" ? "text-xs text-[var(--text-muted)]" : "align-top text-[var(--text-secondary)]",
189
+ VALIGN[valign ?? rowVAlign ?? (head ? "bottom" : "top")],
190
+ HEADER_WEIGHT[weight],
191
+ head ? "text-[var(--text-muted)]" : "text-[var(--text-secondary)]",
192
+ textSize && HEADER_SIZE[textSize],
122
193
  className
123
194
  )
124
195
  }
125
196
  );
126
197
  }
127
- function TableCell({ numeric = false, align, className, ...rest }) {
198
+ function TableCell({ numeric = false, align, valign, className, ...rest }) {
128
199
  const { density } = useContext(TableContext);
200
+ const rowVAlign = useContext(RowVAlignContext);
129
201
  return /* @__PURE__ */ jsx(
130
202
  "td",
131
203
  {
132
204
  ...rest,
133
205
  className: cn(
134
206
  CELL_PAD[density],
135
- "align-top",
207
+ VALIGN[valign ?? rowVAlign ?? "top"],
136
208
  ALIGN[align ?? (numeric ? "end" : "start")],
137
209
  numeric && "tabular-nums whitespace-nowrap",
138
210
  className
@@ -158,6 +230,7 @@ export {
158
230
  TableBody,
159
231
  TableCaption,
160
232
  TableCell,
233
+ TableEmpty,
161
234
  TableFoot,
162
235
  TableHead,
163
236
  TableHeaderCell,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/table.tsx"],"sourcesContent":["import { createContext, useContext, useId, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { THIN_SCROLLBAR_CLASS, useScrollOverflow } from \"./scroll-area\";\n\nexport type TableDensity = \"comfortable\" | \"compact\";\nexport type TableAlign = \"start\" | \"center\" | \"end\";\n\ninterface TableContextValue {\n density: TableDensity;\n zebra: boolean;\n hover: boolean;\n captionId: string;\n registerCaption: () => () => void;\n}\n\nconst TableContext = createContext<TableContextValue>({\n density: \"comfortable\",\n zebra: false,\n hover: false,\n captionId: \"\",\n registerCaption: () => () => {},\n});\n\n/** Which section a row sits in — zebra and hover apply to body rows only. */\nconst SectionContext = createContext<\"head\" | \"body\" | \"foot\">(\"body\");\n\nconst CELL_PAD: Record<TableDensity, string> = {\n comfortable: \"px-3 py-2\",\n compact: \"px-2 py-1\",\n};\n\nconst ALIGN: Record<TableAlign, string> = {\n start: \"text-start\",\n center: \"text-center\",\n end: \"text-end\",\n};\n\n/**\n * The alignment a figure column wants: end-aligned (so the units line up under each\n * other in either reading direction) with tabular digits (so they line up at all).\n * Exported for a cell the kit did not render — a DataTable column, a `<td>` of the\n * app's own.\n */\nexport const NUMERIC_CELL_CLASS = \"text-end tabular-nums\";\n\nexport interface TableProps extends ComponentPropsWithoutRef<\"table\"> {\n /** `compact` for a dense detail view (a ledger, a spec sheet). */\n density?: TableDensity;\n /** Tint every other body row. */\n zebra?: boolean;\n /** Tint the body row under the pointer. Off by default: a static table whose rows\n * light up promises a click that does nothing. */\n hover?: boolean;\n /** Classes for the overflow wrapper around the `<table>` (a max-height, a border). */\n wrapperClassName?: string;\n}\n\n/**\n * A plain, static HTML table in the kit's tokens — for the detail views that need\n * rows and columns and none of {@link DataTable}'s sorting, filtering or paging.\n *\n * kastlan renders about twenty of these on shadcn's Table parts; the part names here\n * are the same idea (`TableHead` is the `<thead>`, `TableHeaderCell` the `<th>`), so\n * the move is a rename rather than a rewrite.\n *\n * The table sits in an overflow-x wrapper, because a table cannot shrink below its\n * content and on a phone it would otherwise widen the page. While it overflows that\n * wrapper becomes a tab stop and a `role=\"region\"` named by the `TableCaption` (or the\n * table's own `aria-label`), so a keyboard user can scroll it and a reader can say\n * what it is — the same contract as {@link ScrollArea}.\n *\n * Alignment is logical throughout (`text-start` / `text-end`), so a right-to-left\n * page gets its first column on the right without a class changing.\n */\nexport function Table({\n density = \"comfortable\",\n zebra = false,\n hover = false,\n wrapperClassName,\n className,\n \"aria-label\": ariaLabel,\n ...rest\n}: TableProps) {\n const captionId = useId();\n const [captions, setCaptions] = useState(0);\n const wrapper = useRef<HTMLDivElement | null>(null);\n const overflowing = useScrollOverflow(wrapper);\n\n const ctx = useMemo<TableContextValue>(\n () => ({\n density,\n zebra,\n hover,\n captionId,\n registerCaption: () => {\n setCaptions((n) => n + 1);\n return () => setCaptions((n) => n - 1);\n },\n }),\n [density, zebra, hover, captionId],\n );\n\n const regionName = captions > 0 ? { \"aria-labelledby\": captionId } : ariaLabel ? { \"aria-label\": ariaLabel } : null;\n\n return (\n <TableContext.Provider value={ctx}>\n <div\n ref={wrapper}\n // Only a region when it scrolls AND has a name: a landmark for every short\n // table on a page would bury the real ones.\n role={overflowing && regionName ? \"region\" : undefined}\n {...(overflowing ? regionName : null)}\n tabIndex={overflowing ? 0 : undefined}\n data-overflowing={overflowing || undefined}\n className={cn(\n // `relative`: the containing block for an `sr-only` caption.\n \"relative w-full overflow-x-auto\",\n THIN_SCROLLBAR_CLASS,\n \"focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--brand)]\",\n wrapperClassName,\n )}\n >\n <table\n {...rest}\n aria-label={ariaLabel}\n className={cn(\n \"w-full caption-bottom border-collapse text-sm text-[var(--text-primary)]\",\n density === \"compact\" && \"text-xs\",\n className,\n )}\n />\n </div>\n </TableContext.Provider>\n );\n}\n\nexport type TableHeadProps = ComponentPropsWithoutRef<\"thead\">;\n\nexport function TableHead({ className, ...rest }: TableHeadProps) {\n return (\n <SectionContext.Provider value=\"head\">\n <thead {...rest} className={cn(\"border-b border-[var(--border)]\", className)} />\n </SectionContext.Provider>\n );\n}\n\nexport type TableBodyProps = ComponentPropsWithoutRef<\"tbody\">;\n\nexport function TableBody({ className, ...rest }: TableBodyProps) {\n return (\n <SectionContext.Provider value=\"body\">\n <tbody {...rest} className={cn(\"[&>tr:last-child]:border-b-0\", className)} />\n </SectionContext.Provider>\n );\n}\n\nexport type TableFootProps = ComponentPropsWithoutRef<\"tfoot\">;\n\n/** The totals row. Not in the brief, but every ledger view has one and a `<tfoot>`\n * is what tells a reader it is a total rather than one more row. */\nexport function TableFoot({ className, ...rest }: TableFootProps) {\n return (\n <SectionContext.Provider value=\"foot\">\n <tfoot {...rest} className={cn(\"border-t border-[var(--border-strong)] font-medium\", className)} />\n </SectionContext.Provider>\n );\n}\n\nexport type TableRowProps = ComponentPropsWithoutRef<\"tr\">;\n\nexport function TableRow({ className, ...rest }: TableRowProps) {\n const { zebra, hover } = useContext(TableContext);\n const section = useContext(SectionContext);\n const body = section === \"body\";\n return (\n <tr\n {...rest}\n className={cn(\n body && \"border-b border-[var(--border)]\",\n body && zebra && \"even:bg-[var(--bg-surface-2)]\",\n body && hover && \"transition-colors hover:bg-[var(--bg-hover)]\",\n className,\n )}\n />\n );\n}\n\ninterface CellAlignProps {\n /** Shorthand for a figure column: end-aligned, tabular digits. */\n numeric?: boolean;\n /** Logical alignment. Default `start` (`end` when `numeric`). */\n align?: TableAlign;\n}\n\n/** The deprecated HTML `align` attribute is replaced by a logical one. */\nexport interface TableHeaderCellProps extends Omit<ComponentPropsWithoutRef<\"th\">, \"align\">, CellAlignProps {}\n\n/**\n * A `<th>`. `scope` defaults to `col` in the head and `row` in the body — a header\n * cell in a body row is the row's label (\"Net rent\" in a key-value table), and a\n * reader needs the scope to read it with each cell beside it.\n */\nexport function TableHeaderCell({ numeric = false, align, scope, className, ...rest }: TableHeaderCellProps) {\n const { density } = useContext(TableContext);\n const section = useContext(SectionContext);\n return (\n <th\n {...rest}\n scope={scope ?? (section === \"head\" ? \"col\" : \"row\")}\n className={cn(\n CELL_PAD[density],\n ALIGN[align ?? (numeric ? \"end\" : \"start\")],\n numeric && \"tabular-nums\",\n \"align-bottom font-medium\",\n section === \"head\" ? \"text-xs text-[var(--text-muted)]\" : \"align-top text-[var(--text-secondary)]\",\n className,\n )}\n />\n );\n}\n\nexport interface TableCellProps extends Omit<ComponentPropsWithoutRef<\"td\">, \"align\">, CellAlignProps {}\n\nexport function TableCell({ numeric = false, align, className, ...rest }: TableCellProps) {\n const { density } = useContext(TableContext);\n return (\n <td\n {...rest}\n className={cn(\n CELL_PAD[density],\n \"align-top\",\n ALIGN[align ?? (numeric ? \"end\" : \"start\")],\n numeric && \"tabular-nums whitespace-nowrap\",\n className,\n )}\n />\n );\n}\n\nexport type TableCaptionProps = ComponentPropsWithoutRef<\"caption\">;\n\n/**\n * The table's title. Beneath the table by default (`caption-bottom`, the shadcn\n * convention kastlan's views already follow); `className=\"caption-top\"` moves it.\n * `className=\"sr-only\"` keeps it for readers only — the wrapper is `relative`, so\n * the hidden caption stays inside it.\n *\n * It also names the scroll region when the table overflows: it registers itself\n * with the {@link Table} so the wrapper points at an id that exists, and never at\n * one that does not.\n */\nexport function TableCaption({ className, id, ...rest }: TableCaptionProps) {\n const { captionId, registerCaption } = useContext(TableContext);\n useLayoutEffect(() => (id ? undefined : registerCaption()), [id, registerCaption]);\n return (\n <caption\n {...rest}\n id={id ?? captionId}\n className={cn(\"mt-2 text-start text-sm text-[var(--text-muted)]\", className)}\n />\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/table.tsx"],"sourcesContent":["import { Children, createContext, useContext, useId, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { THIN_SCROLLBAR_CLASS, useScrollOverflow } from \"./scroll-area\";\n\n/** `none` (0.10.0): no cell padding at all — keksdose's VAT summary, a table set\n * inside a card's own padding whose columns are spaced by hand (`pl-2` on exactly the\n * columns that need it). Type stays at the body size, as `comfortable`. */\nexport type TableDensity = \"comfortable\" | \"compact\" | \"none\";\n/** CSS `table-layout`. `auto` (the browser default) sizes columns to their content;\n * `fixed` takes widths from the first row and ignores the rest — keksdose's VAT\n * summary switches to it while editing so two inputs stop bidding against the\n * figures for room. */\nexport type TableLayout = \"auto\" | \"fixed\";\nexport type TableAlign = \"start\" | \"center\" | \"end\";\n/**\n * Vertical alignment of a cell's content (0.11.0). The defaults stay as they were — a\n * head cell sits on the `bottom` (a wrapped heading ends on the line above the\n * figures), a body cell at the `top` (a wrapped note starts level with its row). A\n * table whose cells are INPUTS wants `middle`: keksdose's VAT summary\n * (invoices/vat-summary.tsx) sets fields beside derived read-out figures in one row,\n * and a figure at the top of its cell sat above the middle of the field next to it —\n * so it writes `<tr className=\"align-middle\">` over raw `<th>`/`<td>`, because the\n * kit's cells each stated `align-top` and a class on the row could not reach them.\n */\nexport type TableVAlign = \"top\" | \"middle\" | \"bottom\";\n/** The head cell's type size: `xs` (the head's default) or `sm` (the body's). */\nexport type TableHeaderCellSize = \"xs\" | \"sm\";\n/** The header cell's weight. Default `medium`. */\nexport type TableHeaderCellWeight = \"normal\" | \"medium\" | \"semibold\";\n\ninterface TableContextValue {\n density: TableDensity;\n zebra: boolean;\n hover: boolean;\n captionId: string;\n registerCaption: () => () => void;\n}\n\nconst TableContext = createContext<TableContextValue>({\n density: \"comfortable\",\n zebra: false,\n hover: false,\n captionId: \"\",\n registerCaption: () => () => {},\n});\n\n/** Which section a row sits in — zebra and hover apply to body rows only. */\nconst SectionContext = createContext<\"head\" | \"body\" | \"foot\">(\"body\");\n\n/** A {@link TableRow}'s `valign`, which its cells take unless they set their own. It\n * cannot be left to CSS inheritance: every cell states its own default alignment,\n * and a class on the cell beats one on the row. */\nconst RowVAlignContext = createContext<TableVAlign | undefined>(undefined);\n\nconst VALIGN: Record<TableVAlign, string> = {\n top: \"align-top\",\n middle: \"align-middle\",\n bottom: \"align-bottom\",\n};\n\nconst HEADER_SIZE: Record<TableHeaderCellSize, string> = {\n xs: \"text-xs\",\n sm: \"text-sm\",\n};\n\nconst HEADER_WEIGHT: Record<TableHeaderCellWeight, string> = {\n normal: \"font-normal\",\n medium: \"font-medium\",\n semibold: \"font-semibold\",\n};\n\nconst CELL_PAD: Record<TableDensity, string> = {\n comfortable: \"px-3 py-2\",\n compact: \"px-2 py-1\",\n none: \"p-0\",\n};\n\nconst ALIGN: Record<TableAlign, string> = {\n start: \"text-start\",\n center: \"text-center\",\n end: \"text-end\",\n};\n\n/**\n * The alignment a figure column wants: end-aligned (so the units line up under each\n * other in either reading direction) with tabular digits (so they line up at all).\n * Exported for a cell the kit did not render — a DataTable column, a `<td>` of the\n * app's own.\n */\nexport const NUMERIC_CELL_CLASS = \"text-end tabular-nums\";\n\nexport interface TableProps extends ComponentPropsWithoutRef<\"table\"> {\n /** `compact` for a dense detail view (a ledger, a spec sheet). */\n density?: TableDensity;\n /** Tint every other body row. */\n zebra?: boolean;\n /** Tint the body row under the pointer. Off by default: a static table whose rows\n * light up promises a click that does nothing. */\n hover?: boolean;\n /** Classes for the overflow wrapper around the `<table>` (a max-height, a border). */\n wrapperClassName?: string;\n /** See {@link TableLayout}. Unset leaves the browser's `auto` and adds no class. */\n layout?: TableLayout;\n}\n\n/**\n * A plain, static HTML table in the kit's tokens — for the detail views that need\n * rows and columns and none of {@link DataTable}'s sorting, filtering or paging.\n *\n * kastlan renders about twenty of these on shadcn's Table parts; the part names here\n * are the same idea (`TableHead` is the `<thead>`, `TableHeaderCell` the `<th>`), so\n * the move is a rename rather than a rewrite.\n *\n * The table sits in an overflow-x wrapper, because a table cannot shrink below its\n * content and on a phone it would otherwise widen the page. While it overflows that\n * wrapper becomes a tab stop and a `role=\"region\"` named by the `TableCaption` (or the\n * table's own `aria-label`), so a keyboard user can scroll it and a reader can say\n * what it is — the same contract as {@link ScrollArea}.\n *\n * Alignment is logical throughout (`text-start` / `text-end`), so a right-to-left\n * page gets its first column on the right without a class changing.\n */\nexport function Table({\n density = \"comfortable\",\n zebra = false,\n hover = false,\n wrapperClassName,\n layout,\n className,\n \"aria-label\": ariaLabel,\n ...rest\n}: TableProps) {\n const captionId = useId();\n const [captions, setCaptions] = useState(0);\n const wrapper = useRef<HTMLDivElement | null>(null);\n const overflowing = useScrollOverflow(wrapper);\n\n const ctx = useMemo<TableContextValue>(\n () => ({\n density,\n zebra,\n hover,\n captionId,\n registerCaption: () => {\n setCaptions((n) => n + 1);\n return () => setCaptions((n) => n - 1);\n },\n }),\n [density, zebra, hover, captionId],\n );\n\n const regionName = captions > 0 ? { \"aria-labelledby\": captionId } : ariaLabel ? { \"aria-label\": ariaLabel } : null;\n\n return (\n <TableContext.Provider value={ctx}>\n <div\n ref={wrapper}\n // Only a region when it scrolls AND has a name: a landmark for every short\n // table on a page would bury the real ones.\n role={overflowing && regionName ? \"region\" : undefined}\n {...(overflowing ? regionName : null)}\n tabIndex={overflowing ? 0 : undefined}\n data-overflowing={overflowing || undefined}\n data-clips=\"\"\n className={cn(\n // `relative`: the containing block for an `sr-only` caption.\n \"relative w-full overflow-x-auto\",\n THIN_SCROLLBAR_CLASS,\n \"focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--brand)]\",\n wrapperClassName,\n )}\n >\n <table\n {...rest}\n aria-label={ariaLabel}\n className={cn(\n \"w-full caption-bottom border-collapse text-sm text-[var(--text-primary)]\",\n density === \"compact\" && \"text-xs\",\n layout === \"fixed\" && \"table-fixed\",\n layout === \"auto\" && \"table-auto\",\n className,\n )}\n />\n </div>\n </TableContext.Provider>\n );\n}\n\nexport type TableHeadProps = ComponentPropsWithoutRef<\"thead\">;\n\nexport function TableHead({ className, ...rest }: TableHeadProps) {\n return (\n <SectionContext.Provider value=\"head\">\n <thead {...rest} className={cn(\"border-b border-[var(--border)]\", className)} />\n </SectionContext.Provider>\n );\n}\n\nexport interface TableBodyProps extends ComponentPropsWithoutRef<\"tbody\"> {\n /**\n * Shown as ONE full-width row (a {@link TableEmpty}) when the body has no rows —\n * `children` that render nothing: an empty `.map`, a `false`, `null`. kastlan hand-\n * writes that row with a counted `colSpan` in unit-values-editor, the meeting agenda\n * and invitations tabs, the journal-entry and invoice detail pages, and a column\n * added later leaves every one of those counts one short.\n */\n empty?: ReactNode;\n}\n\nexport function TableBody({ className, empty, children, ...rest }: TableBodyProps) {\n // `toArray` drops null/undefined/booleans and flattens arrays, so `[[], false]` —\n // an empty map beside a conditional row that is off — is no rows.\n const hasRows = Children.toArray(children).length > 0;\n return (\n <SectionContext.Provider value=\"body\">\n <tbody {...rest} className={cn(\"[&>tr:last-child]:border-b-0\", className)}>\n {hasRows || empty === undefined || empty === null || empty === false ? (\n children\n ) : (\n <TableEmpty>{empty}</TableEmpty>\n )}\n </tbody>\n </SectionContext.Provider>\n );\n}\n\nexport interface TableEmptyProps extends Omit<ComponentPropsWithoutRef<\"tr\">, \"children\"> {\n children: ReactNode;\n /** Columns to span. Default: MEASURED — the widest row of the table it renders in,\n * counted after mount, so the row stays full width when a column is added. */\n colSpan?: number;\n /** Classes for the `<td>`. */\n cellClassName?: string;\n}\n\n/**\n * The \"nothing here\" row: one cell across every column, centred, muted. What\n * {@link TableBodyProps.empty} renders, and usable on its own for a body whose rows\n * are not its direct children.\n */\nexport function TableEmpty({ children, colSpan, cellClassName, className, ...rest }: TableEmptyProps) {\n const { density } = useContext(TableContext);\n const cell = useRef<HTMLTableCellElement | null>(null);\n const [measured, setMeasured] = useState(1);\n // Every render, deliberately: a column can appear in the head without anything this\n // row depends on changing. It cannot loop — setting the same count is a bail-out.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(() => {\n if (colSpan !== undefined) return;\n const row = cell.current?.parentElement;\n const table = cell.current?.closest(\"table\");\n if (!row || !table) return;\n let widest = 1;\n for (const r of Array.from(table.rows)) {\n if (r === row) continue;\n let n = 0;\n for (const c of Array.from(r.cells)) n += c.colSpan || 1;\n widest = Math.max(widest, n);\n }\n setMeasured(widest);\n });\n return (\n <tr {...rest} data-table-empty=\"\" className={className}>\n <td\n ref={cell}\n colSpan={colSpan ?? measured}\n className={cn(\n // `none` keeps the message off the edges regardless: it is text, not a figure.\n density === \"none\" ? \"py-2\" : CELL_PAD[density],\n \"text-center text-[var(--text-muted)]\",\n cellClassName,\n )}\n >\n {children}\n </td>\n </tr>\n );\n}\n\nexport type TableFootProps = ComponentPropsWithoutRef<\"tfoot\">;\n\n/** The totals row. Not in the brief, but every ledger view has one and a `<tfoot>`\n * is what tells a reader it is a total rather than one more row. */\nexport function TableFoot({ className, ...rest }: TableFootProps) {\n return (\n <SectionContext.Provider value=\"foot\">\n <tfoot {...rest} className={cn(\"border-t border-[var(--border-strong)] font-medium\", className)} />\n </SectionContext.Provider>\n );\n}\n\n/** The deprecated HTML `valign` attribute is replaced by a class-backed one. */\nexport interface TableRowProps extends ComponentPropsWithoutRef<\"tr\"> {\n /** Vertical alignment for every cell of the row that does not set its own. See\n * {@link TableVAlign}. Left out, each cell keeps its default. */\n valign?: TableVAlign;\n}\n\nexport function TableRow({ valign, className, ...rest }: TableRowProps) {\n const { zebra, hover } = useContext(TableContext);\n const section = useContext(SectionContext);\n const body = section === \"body\";\n const row = (\n <tr\n {...rest}\n className={cn(\n body && \"border-b border-[var(--border)]\",\n body && zebra && \"even:bg-[var(--bg-surface-2)]\",\n body && hover && \"transition-colors hover:bg-[var(--bg-hover)]\",\n // On the row as well, for a raw `<td>` of the caller's, which inherits it.\n valign && VALIGN[valign],\n className,\n )}\n />\n );\n return valign ? <RowVAlignContext.Provider value={valign}>{row}</RowVAlignContext.Provider> : row;\n}\n\ninterface CellAlignProps {\n /** Shorthand for a figure column: end-aligned, tabular digits. */\n numeric?: boolean;\n /** Logical alignment. Default `start` (`end` when `numeric`). */\n align?: TableAlign;\n /** Vertical alignment. Default: the row's `valign`, else `bottom` for a head cell and\n * `top` for a body cell. See {@link TableVAlign}. */\n valign?: TableVAlign;\n}\n\n/** The deprecated HTML `align` and `valign` attributes are replaced by class-backed ones. */\nexport interface TableHeaderCellProps extends Omit<ComponentPropsWithoutRef<\"th\">, \"align\" | \"valign\">, CellAlignProps {\n /**\n * Type size. Left out: `xs` in the head, the table's own size in the body — as\n * before. keksdose's VAT summary heads its columns at the body size and normal weight\n * (vat-summary.tsx:158–168, `font-normal` on each `<th>`), a quiet header over a\n * small table of figures, and could not say so without overriding classes.\n */\n size?: TableHeaderCellSize;\n /** Default `medium`. `normal` for the quiet header above; see {@link size}. */\n weight?: TableHeaderCellWeight;\n}\n\n/**\n * A `<th>`. `scope` defaults to `col` in the head and `row` in the body — a header\n * cell in a body row is the row's label (\"Net rent\" in a key-value table), and a\n * reader needs the scope to read it with each cell beside it.\n */\nexport function TableHeaderCell({\n numeric = false,\n align,\n valign,\n size,\n weight = \"medium\",\n scope,\n className,\n ...rest\n}: TableHeaderCellProps) {\n const { density } = useContext(TableContext);\n const section = useContext(SectionContext);\n const rowVAlign = useContext(RowVAlignContext);\n const head = section === \"head\";\n const textSize = size ?? (head ? \"xs\" : undefined);\n return (\n <th\n {...rest}\n scope={scope ?? (section === \"head\" ? \"col\" : \"row\")}\n className={cn(\n CELL_PAD[density],\n ALIGN[align ?? (numeric ? \"end\" : \"start\")],\n numeric && \"tabular-nums\",\n VALIGN[valign ?? rowVAlign ?? (head ? \"bottom\" : \"top\")],\n HEADER_WEIGHT[weight],\n head ? \"text-[var(--text-muted)]\" : \"text-[var(--text-secondary)]\",\n textSize && HEADER_SIZE[textSize],\n className,\n )}\n />\n );\n}\n\nexport interface TableCellProps extends Omit<ComponentPropsWithoutRef<\"td\">, \"align\" | \"valign\">, CellAlignProps {}\n\nexport function TableCell({ numeric = false, align, valign, className, ...rest }: TableCellProps) {\n const { density } = useContext(TableContext);\n const rowVAlign = useContext(RowVAlignContext);\n return (\n <td\n {...rest}\n className={cn(\n CELL_PAD[density],\n VALIGN[valign ?? rowVAlign ?? \"top\"],\n ALIGN[align ?? (numeric ? \"end\" : \"start\")],\n numeric && \"tabular-nums whitespace-nowrap\",\n className,\n )}\n />\n );\n}\n\nexport type TableCaptionProps = ComponentPropsWithoutRef<\"caption\">;\n\n/**\n * The table's title. Beneath the table by default (`caption-bottom`, the shadcn\n * convention kastlan's views already follow); `className=\"caption-top\"` moves it.\n * `className=\"sr-only\"` keeps it for readers only — the wrapper is `relative`, so\n * the hidden caption stays inside it.\n *\n * It also names the scroll region when the table overflows: it registers itself\n * with the {@link Table} so the wrapper points at an id that exists, and never at\n * one that does not.\n */\nexport function TableCaption({ className, id, ...rest }: TableCaptionProps) {\n const { captionId, registerCaption } = useContext(TableContext);\n useLayoutEffect(() => (id ? undefined : registerCaption()), [id, registerCaption]);\n return (\n <caption\n {...rest}\n id={id ?? captionId}\n className={cn(\"mt-2 text-start text-sm text-[var(--text-muted)]\", className)}\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,70 @@
1
+ import * as react from 'react';
2
+ import { ComponentPropsWithoutRef, ReactNode } from 'react';
3
+
4
+ /**
5
+ * `xs` is 10px — the size `LegendGroup`'s title and the top-bar menus' heading row draw;
6
+ * `sm` (default) is 12px — the size `StatTile`'s label and lenkbank's section headings
7
+ * (control-page.tsx:119/123/127) draw. Two sizes because the label sits either inside a
8
+ * dense panel (a legend, a menu) or above a block of page content, and one size looked
9
+ * wrong in the other place.
10
+ *
11
+ * `md` (0.11.0) is 11px, the rung between them: keksdose's budget-summary card labels
12
+ * its figures ("Assigned", "Activity", "Upcoming", "Available" —
13
+ * budget-summary-card:258/264/286/297) and its support thread its day divider and a message's meta line
14
+ * (support-thread:153/223) at `text-[11px]` by hand — 10px read as a footnote under a
15
+ * figure that size, 12px competed with it. Named `md` rather than slotted in order
16
+ * because `sm` was already the default and renaming it would move every caller.
17
+ */
18
+ type SectionLabelSize = "xs" | "md" | "sm";
19
+ /**
20
+ * The small, uppercase, tracked, muted label — as a class string, per size.
21
+ *
22
+ * Exported because `LegendGroup` and `StatTile` already draw it inline, and a class a
23
+ * caller has to position itself (a `<th>`, a `<legend>`, a menu `<li>`) is better
24
+ * reached as a string than wrapped in a component that only takes a `className`. The
25
+ * uppercase is CSS, so a screen reader reads the text as written: write it in normal
26
+ * case.
27
+ */
28
+ declare const SECTION_LABEL_CLASS: Record<SectionLabelSize, string>;
29
+ type SectionLabelElement = "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span" | "div" | "legend";
30
+ interface SectionLabelProps extends ComponentPropsWithoutRef<"h3"> {
31
+ /**
32
+ * The element — which is the heading LEVEL, and so is the caller's to pick: the kit
33
+ * cannot know where in the outline the label sits. Default `h3`, which is what
34
+ * lenkbank's control page writes under its `h2` card title. Pass `span` or `p` for a
35
+ * label that is not a heading at all (a legend group's title).
36
+ */
37
+ as?: SectionLabelElement;
38
+ size?: SectionLabelSize;
39
+ children: ReactNode;
40
+ }
41
+ /**
42
+ * A section's small uppercase label. lenkbank spells
43
+ * `text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400`
44
+ * on every `<h3>` of its control page and more; keksdose's budget switcher writes the
45
+ * 10px version on a menu row. One component, the token colour, and the level chosen
46
+ * where the outline is known.
47
+ */
48
+ declare function SectionLabel({ as, size, className, children, ...rest }: SectionLabelProps): react.JSX.Element;
49
+ /**
50
+ * The caption type as a class string: 11px, snug, muted. lenkbank's `CAPTION`
51
+ * (shared/ui/caption.ts:23) — written out 31 times before it was a constant — as a
52
+ * token colour rather than `slate-500 dark:slate-400`, so it follows the palette.
53
+ * Exported for the same reason lenkbank's is a string: every caller positions it
54
+ * differently (`mt-1`, `self-end pb-2`, `border-t`), and what must be one thing is the
55
+ * type, not the box.
56
+ */
57
+ declare const CAPTION_CLASS = "text-[11px] leading-snug text-[var(--text-muted)]";
58
+ interface CaptionProps extends ComponentPropsWithoutRef<"p"> {
59
+ /** Default `p`; `span` for a caption inside a line of other content. */
60
+ as?: "p" | "span" | "div";
61
+ children: ReactNode;
62
+ }
63
+ /**
64
+ * The small grey sentence under a field, a table or a button — prose ABOUT the thing
65
+ * above it. Not `FieldHint`, which hides behind a field label's "?" so a paragraph does
66
+ * not make one field taller than its neighbour (lenkbank feedback #27).
67
+ */
68
+ declare function Caption({ as, className, children, ...rest }: CaptionProps): react.JSX.Element;
69
+
70
+ export { CAPTION_CLASS, Caption, type CaptionProps, SECTION_LABEL_CLASS, SectionLabel, type SectionLabelProps, type SectionLabelSize };
@@ -0,0 +1,24 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { cn } from "../lib/cn.js";
4
+ const SECTION_LABEL_CLASS = {
5
+ xs: "text-[10px] font-semibold uppercase tracking-wide text-[var(--text-muted)]",
6
+ md: "text-[11px] font-semibold uppercase tracking-wide text-[var(--text-muted)]",
7
+ sm: "text-xs font-semibold uppercase tracking-wide text-[var(--text-muted)]"
8
+ };
9
+ function SectionLabel({ as = "h3", size = "sm", className, children, ...rest }) {
10
+ const Tag = as;
11
+ return /* @__PURE__ */ jsx(Tag, { ...rest, className: cn(SECTION_LABEL_CLASS[size], className), children });
12
+ }
13
+ const CAPTION_CLASS = "text-[11px] leading-snug text-[var(--text-muted)]";
14
+ function Caption({ as = "p", className, children, ...rest }) {
15
+ const Tag = as;
16
+ return /* @__PURE__ */ jsx(Tag, { ...rest, className: cn(CAPTION_CLASS, className), children });
17
+ }
18
+ export {
19
+ CAPTION_CLASS,
20
+ Caption,
21
+ SECTION_LABEL_CLASS,
22
+ SectionLabel
23
+ };
24
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/text.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, ElementType, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\n\n/* ── SectionLabel ─────────────────────────────────────────────────────────── */\n\n/**\n * `xs` is 10px — the size `LegendGroup`'s title and the top-bar menus' heading row draw;\n * `sm` (default) is 12px — the size `StatTile`'s label and lenkbank's section headings\n * (control-page.tsx:119/123/127) draw. Two sizes because the label sits either inside a\n * dense panel (a legend, a menu) or above a block of page content, and one size looked\n * wrong in the other place.\n *\n * `md` (0.11.0) is 11px, the rung between them: keksdose's budget-summary card labels\n * its figures (\"Assigned\", \"Activity\", \"Upcoming\", \"Available\" —\n * budget-summary-card:258/264/286/297) and its support thread its day divider and a message's meta line\n * (support-thread:153/223) at `text-[11px]` by hand — 10px read as a footnote under a\n * figure that size, 12px competed with it. Named `md` rather than slotted in order\n * because `sm` was already the default and renaming it would move every caller.\n */\nexport type SectionLabelSize = \"xs\" | \"md\" | \"sm\";\n\n/**\n * The small, uppercase, tracked, muted label — as a class string, per size.\n *\n * Exported because `LegendGroup` and `StatTile` already draw it inline, and a class a\n * caller has to position itself (a `<th>`, a `<legend>`, a menu `<li>`) is better\n * reached as a string than wrapped in a component that only takes a `className`. The\n * uppercase is CSS, so a screen reader reads the text as written: write it in normal\n * case.\n */\nexport const SECTION_LABEL_CLASS: Record<SectionLabelSize, string> = {\n xs: \"text-[10px] font-semibold uppercase tracking-wide text-[var(--text-muted)]\",\n md: \"text-[11px] font-semibold uppercase tracking-wide text-[var(--text-muted)]\",\n sm: \"text-xs font-semibold uppercase tracking-wide text-[var(--text-muted)]\",\n};\n\ntype SectionLabelElement = \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"span\" | \"div\" | \"legend\";\n\nexport interface SectionLabelProps extends ComponentPropsWithoutRef<\"h3\"> {\n /**\n * The element — which is the heading LEVEL, and so is the caller's to pick: the kit\n * cannot know where in the outline the label sits. Default `h3`, which is what\n * lenkbank's control page writes under its `h2` card title. Pass `span` or `p` for a\n * label that is not a heading at all (a legend group's title).\n */\n as?: SectionLabelElement;\n size?: SectionLabelSize;\n children: ReactNode;\n}\n\n/**\n * A section's small uppercase label. lenkbank spells\n * `text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400`\n * on every `<h3>` of its control page and more; keksdose's budget switcher writes the\n * 10px version on a menu row. One component, the token colour, and the level chosen\n * where the outline is known.\n */\nexport function SectionLabel({ as = \"h3\", size = \"sm\", className, children, ...rest }: SectionLabelProps) {\n const Tag = as as ElementType;\n return (\n <Tag {...rest} className={cn(SECTION_LABEL_CLASS[size], className)}>\n {children}\n </Tag>\n );\n}\n\n/* ── Caption ──────────────────────────────────────────────────────────────── */\n\n/**\n * The caption type as a class string: 11px, snug, muted. lenkbank's `CAPTION`\n * (shared/ui/caption.ts:23) — written out 31 times before it was a constant — as a\n * token colour rather than `slate-500 dark:slate-400`, so it follows the palette.\n * Exported for the same reason lenkbank's is a string: every caller positions it\n * differently (`mt-1`, `self-end pb-2`, `border-t`), and what must be one thing is the\n * type, not the box.\n */\nexport const CAPTION_CLASS = \"text-[11px] leading-snug text-[var(--text-muted)]\";\n\nexport interface CaptionProps extends ComponentPropsWithoutRef<\"p\"> {\n /** Default `p`; `span` for a caption inside a line of other content. */\n as?: \"p\" | \"span\" | \"div\";\n children: ReactNode;\n}\n\n/**\n * The small grey sentence under a field, a table or a button — prose ABOUT the thing\n * above it. Not `FieldHint`, which hides behind a field label's \"?\" so a paragraph does\n * not make one field taller than its neighbour (lenkbank feedback #27).\n */\nexport function Caption({ as = \"p\", className, children, ...rest }: CaptionProps) {\n const Tag = as as ElementType;\n return (\n <Tag {...rest} className={cn(CAPTION_CLASS, className)}>\n {children}\n </Tag>\n );\n}\n"],"mappings":";AA4DI;AA3DJ,SAAS,UAAU;AA6BZ,MAAM,sBAAwD;AAAA,EACnE,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAuBO,SAAS,aAAa,EAAE,KAAK,MAAM,OAAO,MAAM,WAAW,UAAU,GAAG,KAAK,GAAsB;AACxG,QAAM,MAAM;AACZ,SACE,oBAAC,OAAK,GAAG,MAAM,WAAW,GAAG,oBAAoB,IAAI,GAAG,SAAS,GAC9D,UACH;AAEJ;AAYO,MAAM,gBAAgB;AAatB,SAAS,QAAQ,EAAE,KAAK,KAAK,WAAW,UAAU,GAAG,KAAK,GAAiB;AAChF,QAAM,MAAM;AACZ,SACE,oBAAC,OAAK,GAAG,MAAM,WAAW,GAAG,eAAe,SAAS,GAClD,UACH;AAEJ;","names":[]}
@@ -2,6 +2,7 @@ import * as react from 'react';
2
2
  import { InputProps } from './ui.js';
3
3
  import './tooltip.js';
4
4
  import '../hooks/use-anchored-rect.js';
5
+ import '../lib/clipping.js';
5
6
 
6
7
  /**
7
8
  * A time of day, as a `"HH:mm"` string (`"HH:mm:ss"` once `step` asks for seconds).