twico-ui 1.4.0 → 1.6.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
@@ -291,6 +291,8 @@ Props:
291
291
  - `children` — `React.ReactNode` — Main content; fills the remaining space and scrolls independently of the fixed sidebar and header.
292
292
  - `height` — `string | number` · default `"100dvh"` — Overall shell height; pass a fixed value to embed the shell inside a smaller region (e.g. a demo).
293
293
  - `padded` — `boolean` · default `true` — Pads the scrollable content region with the standard page padding; set false for edge-to-edge content.
294
+ - `sidebarOpen` — `boolean` — Open state of the sidebar's off-canvas drawer on mobile; providing this (or onSidebarOpenChange) makes the shell forward overlay/open/onOpenChange to the sidebar element.
295
+ - `onSidebarOpenChange` — `(open: boolean) => void` — Called when the sidebar drawer requests open or close; wire a Navbar's onMenuClick to () => onSidebarOpenChange(true) for the mobile menu button.
294
296
 
295
297
  ```jsx
296
298
  import { AppShell, Sidebar, Button } from "twico-ui";
@@ -852,6 +854,9 @@ Props:
852
854
  - `tone` — `"primary" | "success" | "warning" | "danger" | "info" | "neutral"` · default `"primary"` — Recolors the focus/open accent (border + ring) using one of six semantic intents; defaults to primary.
853
855
  - `leftIcon` — `React.ReactNode` — Renders an icon node inside the field on the leading edge, useful as a prefix like a search or mail glyph.
854
856
  - `rightIcon` — `React.ReactNode` — Renders a trailing icon node inside the field, overriding the built-in password reveal toggle when supplied.
857
+ - `leftAddon` — `React.ReactNode` — Bordered text addon flush against the leading edge (e.g. https://, @), distinct from the inline leftIcon.
858
+ - `rightAddon` — `React.ReactNode` — Bordered text addon flush against the trailing edge (e.g. .00, kg, %).
859
+ - `showCount` — `boolean` · default `false` — With maxLength, renders a live current / max character counter on the hint/error line, danger-toned near the limit.
855
860
  - `onChange` — `(e: React.ChangeEvent) => void` — Native change handler on the underlying form control.
856
861
  - `name` — `string` — Form field name submitted with the value.
857
862
  - `placeholder` — `string` — Placeholder text shown while the field is empty.
@@ -965,7 +970,10 @@ Props:
965
970
  - `tone` — `"primary" | "success" | "warning" | "danger" | "info" | "neutral"` · default `"warning"` — Sets the filled-star color from six semantic intents; defaults to warning (gold). The explicit color prop overrides it.
966
971
  - `color` — `string` — Overrides the filled-star color with any CSS color, falling back to the warning/amber token when omitted.
967
972
  - `readOnly` — `boolean` · default `false` — Renders the stars as a non-interactive display only, useful for showing an existing score without accepting input.
968
- - `showValue` — `boolean` · default `false` — Displays the numeric score beside the stars when enabled, giving an exact figure alongside the visual rating.
973
+ - `showValue` — `boolean` · default `false` — Displays the numeric score beside the stars (clean integer, or one decimal for fractional values).
974
+ - `clearable` — `boolean` · default `true` — Allow clearing back to 0 — clicking the selected star, or pressing Delete/Backspace, resets to no rating.
975
+ - `format` — `(value: number) => React.ReactNode` — Custom formatter for the showValue badge and the read-only accessible label (e.g. custom precision, i18n, a /5 suffix).
976
+ - `name` — `string` — Name for a hidden form field so the rating value participates in native form submission.
969
977
  - `onChange` — `(value: number) => void` — Fires with the new rating when the user clicks a star, or 0 when they clear the current selection.
970
978
  - `disabled` — `boolean` · default `false` — Disable all interaction and dim the control.
971
979
 
@@ -1049,6 +1057,11 @@ Props:
1049
1057
  - `showValue` — `boolean` · default `true` — Displays the current value at the top-right above the track when true (the default), for an always-visible readout.
1050
1058
  - `showTicks` — `boolean` · default `false` — Renders step ticks along the rail when enabled, helping users see and snap to discrete increments.
1051
1059
  - `formatValue` — `(value: number) => React.ReactNode` — Formats the displayed value in the label and drag bubble, useful for adding units, currency, or custom text.
1060
+ - `range` — `boolean` · default `false` — Enable dual-thumb range mode (also auto-enabled by a [number, number] value/defaultValue); onChange then emits a [min, max] tuple.
1061
+ - `pageStep` — `number` · default `Math.max(step, (max-min)/10)` — Large-step increment for PageUp/PageDown keys; defaults to ~10% of the range.
1062
+ - `precision` — `number` — Decimal places for the default display and aria value; derived from step when unset (so a 0.1 step shows one decimal).
1063
+ - `getAriaValueText` — `(value: number) => string` — Human-readable value for aria-valuetext; use when formatValue returns a node so assistive tech still announces a meaningful value.
1064
+ - `name` — `string` — Name for a hidden form field so the slider value participates in native form submission.
1052
1065
  - `onChange` — `(value: number) => void` — Fires with the new number whenever the value changes via drag or keyboard, keeping state in sync.
1053
1066
  - `hint` — `React.ReactNode` — Helper text shown below the track when there is no error.
1054
1067
  - `error` — `React.ReactNode` — Error message shown below the track; turns it red (sets aria-invalid) and replaces the hint.
@@ -1117,6 +1130,10 @@ Props:
1117
1130
  - `required` — `boolean` · default `false` — Adds a required asterisk beside the label when true, signaling that the field must be filled in.
1118
1131
  - `size` — `"sm" | "md" | "lg"` · default `"md"` — Control size — same padding steps as Input.
1119
1132
  - `tone` — `"primary" | "success" | "warning" | "danger" | "info" | "neutral"` · default `"primary"` — Recolors the focus/open accent (border + ring) using one of six semantic intents; defaults to primary.
1133
+ - `autosize` — `boolean` · default `false` — Grows the textarea to fit its content between minRows and maxRows, hiding the manual resize handle.
1134
+ - `minRows` — `number` — Minimum rows when autosize is on (falls back to rows).
1135
+ - `maxRows` — `number` — Maximum rows when autosize is on; taller content scrolls internally.
1136
+ - `showCount` — `boolean` · default `false` — With maxLength, renders a live current / max character counter, danger-toned near the limit.
1120
1137
  - `onChange` — `(e: React.ChangeEvent) => void` — Native change handler on the underlying form control.
1121
1138
  - `name` — `string` — Form field name submitted with the value.
1122
1139
  - `placeholder` — `string` — Placeholder text shown while the field is empty.
@@ -1225,6 +1242,7 @@ Card is a surface container for grouping related content, with an optional heade
1225
1242
  Props:
1226
1243
  - `title` — `React.ReactNode` — Renders a bold title in the card header, giving the grouped content a clear heading at the top.
1227
1244
  - `subtitle` — `React.ReactNode` — Renders a muted subtitle beneath the title to add supporting context or a brief secondary description.
1245
+ - `actions` — `React.ReactNode` — Header actions rendered top-right, opposite the title/subtitle (e.g. a button or overflow menu).
1228
1246
  - `footer` — `React.ReactNode` — Renders pinned footer content such as action buttons below the card body, separated from the main content.
1229
1247
  - `variant` — `"elevated" | "outline" | "soft"` · default `"elevated"` — Selects the surface treatment from elevated, outline, or soft to control how the card stands out from the page.
1230
1248
  - `padding` — `"none" | "md" | "lg"` · default `"md"` — Sets the card's inner padding to none, md, or lg, with none useful for edge-to-edge media or custom layouts.
@@ -1343,6 +1361,8 @@ Props:
1343
1361
  - `exportFilename` — `string` · default `export` — Sets the base filename used for exported files, defaulting to "export".
1344
1362
  - `aggregationValues` — `object|null` · default `null` — Supplies precomputed footer aggregation values for serverMode, where totals cannot be calculated client-side; null by default.
1345
1363
  - `disableColumnReorder` — `boolean` · default `false` — Prevents users from dragging columns into a new order, locking the column arrangement; off by default.
1364
+ - `emptyMessage` — `React.ReactNode` — Message shown when there are no rows; the default is filter-aware ("No rows match your filters" vs "No rows").
1365
+ - `renderEmpty` — `() => React.ReactNode` — Render a custom empty state inside the table body (e.g. the shipped EmptyState); overrides emptyMessage.
1346
1366
  - `disableColumnResize` — `boolean` · default `false` — Prevents resizing columns, locking their widths; off by default. When enabled, drag a header's right edge to resize, or double-click it to auto-fit the column to its content (Excel-style).
1347
1367
  - `editMode` — `boolean` · default `false` — Makes all columns editable inline so users can change cell values directly; off by default.
1348
1368
  - `onRowUpdate` — `(updated,original,field)=>void` · default `-` — Fires when an inline cell edit is committed, receiving the updated row, the original row, and the edited field.
@@ -1504,6 +1524,7 @@ List renders a vertical stack of rows with optional leading/trailing slots, a ti
1504
1524
  Props:
1505
1525
  - `items` — `ListItemData[]` · **required** — Provides the rows to render, each with a title plus optional description, leading, trailing, onClick, href, and active fields.
1506
1526
  - `plain` — `boolean` · default `false` — Drops the card chrome to render borderless, transparent rows when set to true; defaults to false for the boxed style.
1527
+ - `emptyMessage` — `React.ReactNode` · default `"Nothing here yet"` — Shown as a centered zero-state row when items is empty, so an empty list reads as intentional rather than broken.
1507
1528
 
1508
1529
  ```jsx
1509
1530
  import { List } from "twico-ui";
@@ -1531,6 +1552,7 @@ Props:
1531
1552
  - `size` — `"sm" | "md"` · default `"md"` — Sets the control density, sm or md, scaling button height and spacing to match the surrounding layout.
1532
1553
  - `showJumper` — `boolean` · default `false` — Adds a "Go to" number input so users can jump directly to a specific page instead of clicking through.
1533
1554
  - `jumperLabel` — `React.ReactNode` · default `"Go to"` — Text or node rendered before the jumper input, defaulting to "Go to", to clarify what the field does.
1555
+ - `getPageLabel` — `(page: number) => string` · default `(n) => `Page ${n}`` — Accessible label for each numbered page button, so screen readers announce "Page 3" rather than a bare number; override for i18n.
1534
1556
  - `defaultPage` — `number` · default `1` — Initial 1-based page when uncontrolled (no `page` prop).
1535
1557
  - `showPageJumper` — `boolean` · default `false` — Show a "Go to" page input for jumping directly to a page; preferred alias for showJumper.
1536
1558
 
@@ -1599,6 +1621,11 @@ Props:
1599
1621
  - `onSortChange` — `(sort: TableSort) => void` — Fired when a sortable header is clicked with the next sort state.
1600
1622
  - `stickyHeader` — `boolean` · default `false` — Pin the header to the top while the body scrolls (use with maxHeight or an enclosing scroll container).
1601
1623
  - `maxHeight` — `number | string` — Cap the table height and give it its own vertical scroll area (CSS length or number of px); enables stickyHeader to shine.
1624
+ - `loading` — `boolean` · default `false` — Renders shimmering skeleton rows instead of data and sets aria-busy, avoiding a flash-of-empty during fetches.
1625
+ - `loadingRows` — `number` · default `8` — Number of skeleton rows to show while loading is true.
1626
+ - `emptyMessage` — `React.ReactNode` · default `"No data"` — Rendered as a single full-width row when rows is empty instead of a bare header over a blank body.
1627
+ - `ariaLabel` — `string` — Accessible name applied to the <table> itself (not the wrapper div); a raw aria-label prop takes precedence over it.
1628
+ - `caption` — `React.ReactNode` — Renders a visually-hidden <caption> naming the table for assistive technology.
1602
1629
  - `rows` — `T[]` — Row objects.
1603
1630
  - `field (column)` — `string` — Alias for key (Datatable vocabulary); key wins when both are set.
1604
1631
  - `headerName (column)` — `React.ReactNode` — Alias for header (Datatable vocabulary); header wins when both are set.
@@ -1774,6 +1801,11 @@ Props:
1774
1801
  - `defaultCollapsed` — `boolean` · default `false` — Initial collapsed state for uncontrolled usage, defaulting to false so the sidebar starts fully expanded.
1775
1802
  - `collapsible` — `boolean` · default `true` — Shows the collapse toggle button when true (the default), allowing users to switch to the icon-only rail.
1776
1803
  - `onCollapsedChange` — `(collapsed: boolean) => void` — Fires with the new boolean state whenever the collapse toggle is activated, useful for persisting the preference.
1804
+ - `overlay` — `boolean` · default `false` — Renders the sidebar as an off-canvas drawer instead of an in-flow rail: a fixed slide-over panel behind a dismissable backdrop, portaled to the body, with a focus trap, Escape-to-close, and scroll lock.
1805
+ - `open` — `boolean` — Controlled open state of the overlay drawer (only meaningful when overlay is set); the parent owns visibility.
1806
+ - `defaultOpen` — `boolean` · default `false` — Initial open state of the overlay drawer for uncontrolled usage, defaulting to closed.
1807
+ - `onOpenChange` — `(open: boolean) => void` — Fires when the overlay drawer requests open or close (backdrop click or Escape); wire it to your open state.
1808
+ - `navLabel` — `string` · default `"Main"` — Accessible name for the inner nav landmark and the overlay dialog, so assistive tech can distinguish multiple navigations.
1777
1809
 
1778
1810
  ```jsx
1779
1811
  import { Sidebar } from "twico-ui";
@@ -2279,7 +2311,7 @@ function Page() {
2279
2311
  }
2280
2312
  ```
2281
2313
 
2282
- ## Hooks (23)
2314
+ ## Hooks (25)
2283
2315
 
2284
2316
  ### useIsomorphicLayoutEffect
2285
2317
 
@@ -2323,18 +2355,18 @@ export function useDisclosure(initial?: boolean): { open: boolean; onOpen: () =>
2323
2355
 
2324
2356
  ### useControllableState
2325
2357
 
2326
- Controlled/uncontrolled state — the pattern every form component needs.
2358
+ Controlled/uncontrolled state — the pattern every form component needs. Returns `[value, setValue, isControlled]`; the third element reports whether a `value` prop is currently driving the state. In development it warns once if a component switches between controlled and uncontrolled.
2327
2359
 
2328
2360
  ```ts
2329
- export function useControllableState<T>(options?: { value?: T; defaultValue?: T; onChange?: (value: T) => void; }): [T, (next: T | ((prev: T) => T)) => void];
2361
+ export function useControllableState<T>(options?: { value?: T; defaultValue?: T; onChange?: (value: T) => void; }): [T, (next: T | ((prev: T) => T)) => void, boolean];
2330
2362
  ```
2331
2363
 
2332
2364
  ### useMediaQuery
2333
2365
 
2334
- Reactively match a media query (SSR-safe).
2366
+ Reactively match a media query. Returns `defaultValue` on the server and the first client render, then syncs after mount — so hydration never mismatches.
2335
2367
 
2336
2368
  ```ts
2337
- export function useMediaQuery(query: string): boolean;
2369
+ export function useMediaQuery(query: string, options?: UseMediaQueryOptions): boolean;
2338
2370
  ```
2339
2371
 
2340
2372
  ### usePrefersReducedMotion
@@ -2358,7 +2390,7 @@ export function useColorScheme(options?: UseColorSchemeOptions): { theme: "light
2358
2390
  Attach an event listener (window by default, or a ref/element) with cleanup.
2359
2391
 
2360
2392
  ```ts
2361
- export function useEventListener( eventName: string, handler: (event: any) => void, element?: React.RefObject<any> | EventTarget | null, options?: boolean | AddEventListenerOptions ): void;
2393
+ export function useEventListener( eventName: string, handler: (event: any) => void, element?: React.RefObject<any | null> | EventTarget | null, options?: boolean | AddEventListenerOptions ): void;
2362
2394
  ```
2363
2395
 
2364
2396
  ### useClickOutside
@@ -2366,7 +2398,7 @@ export function useEventListener( eventName: string, handler: (event: any) => vo
2366
2398
  Call `handler` when a pointer/touch event lands outside `ref`.
2367
2399
 
2368
2400
  ```ts
2369
- export function useClickOutside<T extends HTMLElement = HTMLElement>( ref: React.RefObject<T>, handler: (event: Event) => void, events?: string[] ): void;
2401
+ export function useClickOutside<T extends HTMLElement = HTMLElement>( ref: React.RefObject<T | null>, handler: (event: Event) => void, events?: string[] ): void;
2370
2402
  ```
2371
2403
 
2372
2404
  ### useKeyPress
@@ -2438,7 +2470,7 @@ export function useWindowSize(): { width: number; height: number };
2438
2470
  `true` while the pointer is over the element referenced by `ref`.
2439
2471
 
2440
2472
  ```ts
2441
- export function useHover<T extends HTMLElement = HTMLElement>(ref: React.RefObject<T>): boolean;
2473
+ export function useHover<T extends HTMLElement = HTMLElement>(ref: React.RefObject<T | null>): boolean;
2442
2474
  ```
2443
2475
 
2444
2476
  ### useIntersectionObserver
@@ -2446,7 +2478,7 @@ export function useHover<T extends HTMLElement = HTMLElement>(ref: React.RefObje
2446
2478
  The latest IntersectionObserver entry for the element referenced by `ref`.
2447
2479
 
2448
2480
  ```ts
2449
- export function useIntersectionObserver<T extends HTMLElement = HTMLElement>( ref: React.RefObject<T>, options?: IntersectionObserverInit ): IntersectionObserverEntry | null;
2481
+ export function useIntersectionObserver<T extends HTMLElement = HTMLElement>( ref: React.RefObject<T | null>, options?: IntersectionObserverInit ): IntersectionObserverEntry | null;
2450
2482
  ```
2451
2483
 
2452
2484
  ### useScrollLock
@@ -2465,6 +2497,22 @@ A stable, SSR-safe unique id (optionally prefixed).
2465
2497
  export function useId(prefix?: string): string;
2466
2498
  ```
2467
2499
 
2500
+ ### useFocusTrap
2501
+
2502
+ Trap focus inside the modal region referenced by `ref`. While `active`, focus moves inside on activate, Tab/Shift+Tab cycle within it, and (by default) focus is restored to the previously-focused element on deactivate. Escape is NOT handled — that stays with the component. SSR-safe.
2503
+
2504
+ ```ts
2505
+ export function useFocusTrap<T extends HTMLElement = HTMLElement>( ref: React.RefObject<T | null>, active?: boolean, options?: UseFocusTrapOptions ): void;
2506
+ ```
2507
+
2508
+ ### usePortal
2509
+
2510
+ Returns a stable `render(node)` that portals `node` to `document.body`, or `null` on the server (no document). Centralizes the overlay portal pattern.
2511
+
2512
+ ```ts
2513
+ export function usePortal(): (node: React.ReactNode) => React.ReactPortal | React.ReactNode | null;
2514
+ ```
2515
+
2468
2516
  ---
2469
2517
 
2470
2518
  _Generated by `scripts/gen-llms.mjs`. Do not edit by hand — run `npm run gen:llms`. Source of truth: the component `.d.ts` files and `hooks/index.d.ts`._
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twico-ui",
3
- "version": "1.4.0",
3
+ "version": "1.6.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",
@@ -82,6 +82,8 @@
82
82
  "gen:llms:check": "node scripts/gen-llms.mjs --check",
83
83
  "typecheck": "tsc --noEmit",
84
84
  "check:exports": "attw --pack . --profile node16 --exclude-entrypoints styles.css",
85
+ "gen:ds-bundle": "node scripts/gen-ds-bundle.mjs",
86
+ "check:ds-bundle": "node scripts/check-ds-bundle.mjs",
85
87
  "test": "vitest",
86
88
  "test:run": "vitest run",
87
89
  "size": "size-limit",
@@ -169,8 +169,9 @@
169
169
  --color-primary-subtle-fg: var(--brand-700);
170
170
  --color-primary-border: var(--brand-200);
171
171
 
172
- /* Focus ring */
173
- --color-ring: rgb(99 102 241 / 0.45);
172
+ /* Focus ring — solid brand so the 3px ring clears WCAG 2.4.11 / 1.4.11 (3:1)
173
+ on light surfaces (a translucent indigo alpha-composited to only ~1.8:1). */
174
+ --color-ring: var(--brand-500);
174
175
 
175
176
  /* Success */
176
177
  --color-success: var(--emerald-600);
@@ -180,7 +181,7 @@
180
181
 
181
182
  /* Warning */
182
183
  --color-warning: var(--amber-500);
183
- --color-warning-fg: #ffffff;
184
+ --color-warning-fg: var(--amber-950); /* dark ink on amber-500 = 6.97:1 (white was 2.15:1) */
184
185
  --color-warning-subtle: var(--amber-50);
185
186
  --color-warning-subtle-fg: var(--amber-600);
186
187
 
@@ -193,7 +194,7 @@
193
194
 
194
195
  /* Info */
195
196
  --color-info: var(--sky-500);
196
- --color-info-fg: #ffffff;
197
+ --color-info-fg: var(--sky-950); /* dark ink on sky-500 (white was 2.77:1) */
197
198
  --color-info-subtle: var(--sky-50);
198
199
  --color-info-subtle-fg: var(--sky-600);
199
200
  }
@@ -229,7 +230,7 @@
229
230
  --color-primary-subtle-fg: var(--brand-300);
230
231
  --color-primary-border: rgb(99 102 241 / 0.35);
231
232
 
232
- --color-ring: rgb(129 140 248 / 0.55);
233
+ --color-ring: var(--brand-400); /* solid brand-400 ≈ 5.7:1 on slate-900 (translucent was ~2.7:1) */
233
234
 
234
235
  --color-success: var(--emerald-500);
235
236
  --color-success-fg: #052e1d;
@@ -564,6 +565,15 @@ textarea:focus-visible,
564
565
  select:focus-visible {
565
566
  box-shadow: none;
566
567
  }
568
+ /* Windows High Contrast / forced-colors strips author box-shadows, which would
569
+ erase the box-shadow focus ring entirely. Repaint a real outline in the user's
570
+ system palette (CanvasText) — the designed ring is untouched in normal render. */
571
+ @media (forced-colors: active) {
572
+ :focus-visible {
573
+ outline: 2px solid CanvasText;
574
+ outline-offset: 2px;
575
+ }
576
+ }
567
577
 
568
578
  ::selection {
569
579
  background: var(--color-primary-subtle);