twico-ui 1.4.0 → 1.5.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";
@@ -1774,6 +1776,11 @@ Props:
1774
1776
  - `defaultCollapsed` — `boolean` · default `false` — Initial collapsed state for uncontrolled usage, defaulting to false so the sidebar starts fully expanded.
1775
1777
  - `collapsible` — `boolean` · default `true` — Shows the collapse toggle button when true (the default), allowing users to switch to the icon-only rail.
1776
1778
  - `onCollapsedChange` — `(collapsed: boolean) => void` — Fires with the new boolean state whenever the collapse toggle is activated, useful for persisting the preference.
1779
+ - `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.
1780
+ - `open` — `boolean` — Controlled open state of the overlay drawer (only meaningful when overlay is set); the parent owns visibility.
1781
+ - `defaultOpen` — `boolean` · default `false` — Initial open state of the overlay drawer for uncontrolled usage, defaulting to closed.
1782
+ - `onOpenChange` — `(open: boolean) => void` — Fires when the overlay drawer requests open or close (backdrop click or Escape); wire it to your open state.
1783
+ - `navLabel` — `string` · default `"Main"` — Accessible name for the inner nav landmark and the overlay dialog, so assistive tech can distinguish multiple navigations.
1777
1784
 
1778
1785
  ```jsx
1779
1786
  import { Sidebar } from "twico-ui";
@@ -2279,7 +2286,7 @@ function Page() {
2279
2286
  }
2280
2287
  ```
2281
2288
 
2282
- ## Hooks (23)
2289
+ ## Hooks (25)
2283
2290
 
2284
2291
  ### useIsomorphicLayoutEffect
2285
2292
 
@@ -2323,18 +2330,18 @@ export function useDisclosure(initial?: boolean): { open: boolean; onOpen: () =>
2323
2330
 
2324
2331
  ### useControllableState
2325
2332
 
2326
- Controlled/uncontrolled state — the pattern every form component needs.
2333
+ 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
2334
 
2328
2335
  ```ts
2329
- export function useControllableState<T>(options?: { value?: T; defaultValue?: T; onChange?: (value: T) => void; }): [T, (next: T | ((prev: T) => T)) => void];
2336
+ export function useControllableState<T>(options?: { value?: T; defaultValue?: T; onChange?: (value: T) => void; }): [T, (next: T | ((prev: T) => T)) => void, boolean];
2330
2337
  ```
2331
2338
 
2332
2339
  ### useMediaQuery
2333
2340
 
2334
- Reactively match a media query (SSR-safe).
2341
+ Reactively match a media query. Returns `defaultValue` on the server and the first client render, then syncs after mount — so hydration never mismatches.
2335
2342
 
2336
2343
  ```ts
2337
- export function useMediaQuery(query: string): boolean;
2344
+ export function useMediaQuery(query: string, options?: UseMediaQueryOptions): boolean;
2338
2345
  ```
2339
2346
 
2340
2347
  ### usePrefersReducedMotion
@@ -2358,7 +2365,7 @@ export function useColorScheme(options?: UseColorSchemeOptions): { theme: "light
2358
2365
  Attach an event listener (window by default, or a ref/element) with cleanup.
2359
2366
 
2360
2367
  ```ts
2361
- export function useEventListener( eventName: string, handler: (event: any) => void, element?: React.RefObject<any> | EventTarget | null, options?: boolean | AddEventListenerOptions ): void;
2368
+ export function useEventListener( eventName: string, handler: (event: any) => void, element?: React.RefObject<any | null> | EventTarget | null, options?: boolean | AddEventListenerOptions ): void;
2362
2369
  ```
2363
2370
 
2364
2371
  ### useClickOutside
@@ -2366,7 +2373,7 @@ export function useEventListener( eventName: string, handler: (event: any) => vo
2366
2373
  Call `handler` when a pointer/touch event lands outside `ref`.
2367
2374
 
2368
2375
  ```ts
2369
- export function useClickOutside<T extends HTMLElement = HTMLElement>( ref: React.RefObject<T>, handler: (event: Event) => void, events?: string[] ): void;
2376
+ export function useClickOutside<T extends HTMLElement = HTMLElement>( ref: React.RefObject<T | null>, handler: (event: Event) => void, events?: string[] ): void;
2370
2377
  ```
2371
2378
 
2372
2379
  ### useKeyPress
@@ -2438,7 +2445,7 @@ export function useWindowSize(): { width: number; height: number };
2438
2445
  `true` while the pointer is over the element referenced by `ref`.
2439
2446
 
2440
2447
  ```ts
2441
- export function useHover<T extends HTMLElement = HTMLElement>(ref: React.RefObject<T>): boolean;
2448
+ export function useHover<T extends HTMLElement = HTMLElement>(ref: React.RefObject<T | null>): boolean;
2442
2449
  ```
2443
2450
 
2444
2451
  ### useIntersectionObserver
@@ -2446,7 +2453,7 @@ export function useHover<T extends HTMLElement = HTMLElement>(ref: React.RefObje
2446
2453
  The latest IntersectionObserver entry for the element referenced by `ref`.
2447
2454
 
2448
2455
  ```ts
2449
- export function useIntersectionObserver<T extends HTMLElement = HTMLElement>( ref: React.RefObject<T>, options?: IntersectionObserverInit ): IntersectionObserverEntry | null;
2456
+ export function useIntersectionObserver<T extends HTMLElement = HTMLElement>( ref: React.RefObject<T | null>, options?: IntersectionObserverInit ): IntersectionObserverEntry | null;
2450
2457
  ```
2451
2458
 
2452
2459
  ### useScrollLock
@@ -2465,6 +2472,22 @@ A stable, SSR-safe unique id (optionally prefixed).
2465
2472
  export function useId(prefix?: string): string;
2466
2473
  ```
2467
2474
 
2475
+ ### useFocusTrap
2476
+
2477
+ 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.
2478
+
2479
+ ```ts
2480
+ export function useFocusTrap<T extends HTMLElement = HTMLElement>( ref: React.RefObject<T | null>, active?: boolean, options?: UseFocusTrapOptions ): void;
2481
+ ```
2482
+
2483
+ ### usePortal
2484
+
2485
+ Returns a stable `render(node)` that portals `node` to `document.body`, or `null` on the server (no document). Centralizes the overlay portal pattern.
2486
+
2487
+ ```ts
2488
+ export function usePortal(): (node: React.ReactNode) => React.ReactPortal | React.ReactNode | null;
2489
+ ```
2490
+
2468
2491
  ---
2469
2492
 
2470
2493
  _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.5.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);