twico-ui 1.47.0 → 1.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/llms.txt CHANGED
@@ -219,7 +219,7 @@ Button is a primary action button with solid, soft, outline, and ghost variants
219
219
 
220
220
  Props:
221
221
  - `variant` — `"solid" | "soft" | "outline" | "ghost"` · default `"solid"` — Fill style: solid (primary CTA), soft (tinted), outline, or ghost. Cross with `tone` for color.
222
- - `tone` — `"primary" | "danger"` · default `"primary"` — Color/intent, orthogonal to variant — primary by default; tone="danger" makes a destructive button in any variant.
222
+ - `tone` — `"primary" | "success" | "warning" | "danger" | "info" | "neutral"` · default `"primary"` — Color/intent, orthogonal to variant. Widened in #504 from ActionTone (primary | danger) to the full Tone scale, so a standalone affirmative action can be green — a row-level confirm beside a danger sibling reads as "yes / no", and before this the yes came out the same blue as every neutral action. ActionTone is still exported as a convenience alias for the two intents most actions want. neutral follows Badge's mapping rather than an accent ramp of its own (--color-text on --color-surface for solid); variant="ghost" and variant="outline" already express a quiet action, so reach for those first.
223
223
  - `size` — `"xs" | "sm" | "md" | "lg"` · default `"md"` — Adds a new "xs" size (~26px tall, smaller than "sm") to the existing union; sm/md/lg are unchanged. xs height is composed locally from space tokens (no global control-h-xs token).
224
224
  - `leftIcon` — `React.ReactNode` — Renders an icon node before the label, commonly used to reinforce the action with a leading visual cue.
225
225
  - `rightIcon` — `React.ReactNode` — Renders an icon node after the label, often a chevron or arrow signaling navigation or progression.
@@ -258,7 +258,7 @@ IconButton is a square or circular button that renders a single icon and always
258
258
  Props:
259
259
  - `icon` — `React.ReactNode` — Supplies the single icon node to render, such as a Lucide SVG, falling back to children when omitted.
260
260
  - `variant` — `"solid" | "soft" | "outline" | "ghost"` · default `"ghost"` — Fill style (same axes as Button): solid, soft, outline, or ghost; ghost by default. Cross with `tone` for color.
261
- - `tone` — `"primary" | "danger"` · default `"primary"` — Color/intent, orthogonal to variant (mirrors Button) — primary by default; tone="danger" makes an icon-only destructive button in any variant.
261
+ - `tone` — `"primary" | "success" | "warning" | "danger" | "info" | "neutral"` · default `"primary"` — Color/intent, orthogonal to variant. Widened in #504 from ActionTone (primary | danger) to the full Tone scale, so a standalone affirmative action can be green — a row-level confirm beside a danger sibling reads as "yes / no", and before this the yes came out the same blue as every neutral action. ActionTone is still exported as a convenience alias for the two intents most actions want. neutral follows Badge's mapping rather than an accent ramp of its own (--color-text on --color-surface for solid); variant="ghost" and variant="outline" already express a quiet action, so reach for those first.
262
262
  - `size` — `"xs" | "sm" | "md" | "lg"` · default `"md"` — Adds a new "xs" size (~26px square, smaller than "sm") to the existing union; sm/md/lg are unchanged.
263
263
  - `round` — `boolean` · default `false` — Renders a fully rounded circular button when true instead of the default squared shape with rounded corners.
264
264
  - `as` — `"button" | "a"` · default `"button"` — Render as an "a" anchor for an icon link (a GitHub/repo icon, external nav) so navigation is a real link, not a button.
@@ -1715,6 +1715,8 @@ Props:
1715
1715
  - `rowHeight` — `number` — Estimated/fixed row height in px used to compute the virtualization window. Set if rows have a custom height so the scrollbar stays accurate.
1716
1716
  - `rowNumbers` — `boolean` · default `false` — Render a leading auto-numbered row column (1, 2, 3…). The number reflects each row's position in the current sorted/filtered order and continues across pages (page 2 starts where page 1 ended; in server mode it uses the current page × pageSize). Sits after the checkbox column, sticky-left. This prop is the initial state — when enabled, users can hide/show the column from the toolbar's **Columns** panel ("Row number").
1717
1717
  - `getRowStyle` — `(row: T, index: number) => React.CSSProperties | undefined` — Inline style for a WHOLE row from its own state — the row-level counterpart to a column's cellStyle, so marking a row no longer means repeating cellStyle on every column. A background (or backgroundColor) reaches the row's cells through the --twc-dt-row-bg custom property, since each cell paints its own opaque surface; every other declaration lands on the <tr>. Sticky positioning and measured row height are applied after yours, so it cannot break row pinning or row resizing. Layering is one rule: the tint is the bottom layer above the cell's plain surface and every built-in state covers it — hover, selection, activeRowId, a pinned or grabbed row, the diff washes, cell selection/editing, and an inline cellStyle over all of them — because a data-derived wash should never hide an interaction state. (So a row pinned from the ⋮ menu loses its background tint while pinned, like a selected one; use getRowClassName with a rule outranking (0,3,0) if you need a tint that beats hover — and note .twc-dt__row.my-row .twc-dt__td is itself (0,3,0), a tie, so add an element: tr.twc-dt__row.my-row .twc-dt__td.) Applies to every data row, pinned ones included — not to group-header or pivot aggregate rows, which have no row to pass. No stripe in that stack: Datatable does not zebra-stripe data rows at all, only the pivot grid does.
1718
+ - `verticalAlign` — `"top" | "middle" | "bottom"` — Default vertical alignment for every body cell. The transpose of a column's align: align decides where content sits across the column's width, this decides where it sits down the row's height. Only visible once rows vary in height — a wrapText column, a multi-line renderCell, or a drag-resized row. Precedence, lowest first: this → getRowVerticalAlign → the column's verticalAlign. Resolved in one place and emitted as a single data-valign on the cell rather than as three competing CSS rules, so the order cannot be changed by an unrelated stylesheet edit.
1719
+ - `getRowVerticalAlign` — `(row: T, index: number) => "top" | "middle" | "bottom" | undefined` — Vertical alignment for one row's cells, called (row, index) like getRowStyle; return undefined to leave a row on the table default. Overrides verticalAlign but NOT a column's own verticalAlign — an explicit column value is a property of that column's content type and is almost always a deliberate exemption (the actions column stays centred while the rest of the row moves). To move every cell in a row, leave the columns' values unset. index is page-relative, like getRowStyle.
1718
1720
  - `getRowClassName` — `(row: T, index: number) => string | undefined` — Class appended to the row's twc-dt__row (never replacing it), called (row, index) like getRowStyle. Which to reach for: getRowStyle is the default, because its background is routed through --twc-dt-row-bg and read by the (0,1,0) base rule, making it the LOWEST layer that every built-in state covers — the reverse of what "inline wins" would suggest. Use getRowClassName when you deliberately want the tint to outrank hover/selection, since your own rule picks its specificity (beat (0,3,0), e.g. tr.twc-dt__row.my-row .twc-dt__td). Same scope: every data row, pinned ones included; not group headers or pivot aggregates.
1719
1721
  - `searchFields` — `string[]` — Restrict the toolbar quick-search to these column `field`s (client mode); defaults to every visible column. Mirrors `runDatatableQuery`'s `options.searchFields`.
1720
1722
  - `searchable` — `boolean` · default `true` — Render the built-in toolbar quick-search box; set false to externalize search with your own input wired to quickFilter (CardGrid parity).
@@ -2051,6 +2053,7 @@ Props:
2051
2053
  - `renderCell (column)` — `(value: any, row: T, index: number) => React.ReactNode` — Alias for render (Datatable vocabulary); render wins when both are set.
2052
2054
  - `cellClassName (column)` — `(value: any, row: T) => string | undefined` — Class applied to the whole cell (.twc-dt__td, pinned included) — value/row-based tinting via your own stylesheet; a plain class may lose to the hover/selection background (specificity).
2053
2055
  - `cellStyle (column)` — `(value: any, row: T) => React.CSSProperties | undefined` — Inline style applied to the whole cell (.twc-dt__td, pinned included) — the simplest full-cell tint from value/row; wins over hover/selection/diff backgrounds (use cellClassName if those cues must stay visible), pin positioning preserved. On a pinned column keep the background opaque (composite over var(--color-surface)).
2056
+ - `verticalAlign (column)` — `"top" | "middle" | "bottom"` — Vertical alignment of this column's body cells. Wins over the row and table values: a per-column setting is a statement about the column's content type — an actions column or a status badge that should stay centred however tall the row gets — so it survives a row-level or table-wide sweep. To move a whole row including this column, leave this unset. Defaults to top for wrapText columns, otherwise to the table's verticalAlign.
2054
2057
 
2055
2058
  ```jsx
2056
2059
  import { Table } from "twico-ui";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twico-ui",
3
- "version": "1.47.0",
3
+ "version": "1.49.0",
4
4
  "description": "Twico UI — a free, modern, themeable React + Tailwind-friendly component library. 61 components with dark mode, motion, and accessibility built in.",
5
5
  "license": "MIT",
6
6
  "author": "Twico UI",
@@ -120,7 +120,7 @@
120
120
  "lucide-react": "^1.31.0",
121
121
  "semantic-release": "^25.0.9",
122
122
  "size-limit": "^14.0.0",
123
- "tsup": "^8.3.5",
123
+ "tsup": "^9.0.0",
124
124
  "typescript": "^5.9.3",
125
125
  "vitest": "^5.0.0",
126
126
  "wawoff2": "^2.0.1"
@@ -194,9 +194,9 @@
194
194
  --color-chart-on-fill-halo: rgb(15 23 42 / 0.55); /* slate-900 @ 55% */
195
195
 
196
196
  /* Primary / brand */
197
- --color-primary: var(--brand-500);
198
- --color-primary-hover: var(--brand-600);
199
- --color-primary-active: var(--brand-700);
197
+ --color-primary: var(--brand-600); /* was brand-500: white ink on it is 4.47:1, under 4.5 */
198
+ --color-primary-hover: var(--brand-700);
199
+ --color-primary-active: var(--brand-800);
200
200
  --color-primary-fg: #ffffff;
201
201
  --color-primary-subtle: var(--brand-50);
202
202
  --color-primary-subtle-fg: var(--brand-700);
@@ -207,26 +207,33 @@
207
207
  --color-ring: var(--brand-500);
208
208
 
209
209
  /* Success */
210
- --color-success: var(--emerald-600);
210
+ --color-success: var(--emerald-700); /* was emerald-600: white ink on it is 3.77:1 */
211
+ --color-success-hover: var(--emerald-800);
212
+ --color-success-active: var(--emerald-900);
211
213
  --color-success-fg: #ffffff;
212
214
  --color-success-subtle: var(--emerald-50);
213
215
  --color-success-subtle-fg: var(--emerald-700);
214
216
 
215
217
  /* Warning */
216
218
  --color-warning: var(--amber-500);
219
+ --color-warning-hover: var(--amber-400);
220
+ --color-warning-active: var(--amber-300);
217
221
  --color-warning-fg: var(--amber-950); /* dark ink on amber-500 = 6.97:1 (white was 2.15:1) */
218
222
  --color-warning-subtle: var(--amber-50);
219
223
  --color-warning-subtle-fg: var(--amber-700);
220
224
 
221
225
  /* Danger */
222
- --color-danger: var(--rose-500);
223
- --color-danger-hover: var(--rose-600);
226
+ --color-danger: var(--rose-600); /* was rose-500: white ink on it is 3.67:1 */
227
+ --color-danger-hover: var(--rose-700);
228
+ --color-danger-active: var(--rose-800);
224
229
  --color-danger-fg: #ffffff;
225
230
  --color-danger-subtle: var(--rose-50);
226
231
  --color-danger-subtle-fg: var(--rose-700);
227
232
 
228
233
  /* Info */
229
234
  --color-info: var(--sky-500);
235
+ --color-info-hover: var(--sky-400);
236
+ --color-info-active: var(--sky-300);
230
237
  --color-info-fg: var(--sky-950); /* dark ink on sky-500 (white was 2.77:1) */
231
238
  --color-info-subtle: var(--sky-50);
232
239
  --color-info-subtle-fg: var(--sky-700);
@@ -288,10 +295,14 @@
288
295
  --color-text-subtle: var(--slate-400);
289
296
  --color-text-inverted: var(--slate-900);
290
297
 
291
- --color-primary: var(--brand-500);
292
- --color-primary-hover: var(--brand-400);
293
- --color-primary-active: var(--brand-300);
294
- --color-primary-fg: #ffffff;
298
+ /* primary was the only tone in this theme still carrying WHITE ink, and the only one failing:
299
+ 4.47 at rest, 2.98 hovered, 1.99 active, because its steps go lighter INTO the ink. Darkening
300
+ it would clear the text bar but drop the fill to 2.84:1 against the slate-900 page. Every other
301
+ tone here already uses dark ink on a bright fill; primary now matches - 5.36 text, 5.98 fill. */
302
+ --color-primary: var(--brand-400);
303
+ --color-primary-hover: var(--brand-300);
304
+ --color-primary-active: var(--brand-200);
305
+ --color-primary-fg: #1e1b4b;
295
306
  --color-primary-subtle: rgb(99 102 241 / 0.16);
296
307
  --color-primary-subtle-fg: var(--brand-300);
297
308
  --color-primary-border: rgb(99 102 241 / 0.35);
@@ -299,22 +310,29 @@
299
310
  --color-ring: var(--brand-400); /* solid brand-400 ≈ 5.7:1 on slate-900 (translucent was ~2.7:1) */
300
311
 
301
312
  --color-success: var(--emerald-500);
313
+ --color-success-hover: var(--emerald-400);
314
+ --color-success-active: var(--emerald-300);
302
315
  --color-success-fg: #052e1d;
303
316
  --color-success-subtle: rgb(16 185 129 / 0.15);
304
317
  --color-success-subtle-fg: var(--emerald-400);
305
318
 
306
319
  --color-warning: var(--amber-500);
320
+ --color-warning-hover: var(--amber-400);
321
+ --color-warning-active: var(--amber-300);
307
322
  --color-warning-fg: #2c1701;
308
323
  --color-warning-subtle: rgb(245 158 11 / 0.15);
309
324
  --color-warning-subtle-fg: var(--amber-400);
310
325
 
311
326
  --color-danger: var(--rose-500);
312
327
  --color-danger-hover: var(--rose-400);
328
+ --color-danger-active: var(--rose-300);
313
329
  --color-danger-fg: #2c0610;
314
330
  --color-danger-subtle: rgb(244 63 94 / 0.15);
315
331
  --color-danger-subtle-fg: var(--rose-400);
316
332
 
317
333
  --color-info: var(--sky-500);
334
+ --color-info-hover: var(--sky-400);
335
+ --color-info-active: var(--sky-300);
318
336
  --color-info-fg: #04212f;
319
337
  --color-info-subtle: rgb(14 165 233 / 0.15);
320
338
  --color-info-subtle-fg: var(--sky-400);