bubble-toolbar 1.0.0 → 1.2.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/README.md CHANGED
@@ -155,6 +155,146 @@ Both toolbar and group accept these props; group appearance overrides the toolba
155
155
 
156
156
  The effect follows circular and pill shapes without changing their dimensions. With `prefers-reduced-motion: reduce`, the border remains visible and static. Browsers without animated custom property support retain a static gradient; browsers without CSS masking retain a static colored border.
157
157
 
158
+ ## Live Activities
159
+
160
+ Like iOS Live Activities: while a task runs, the whole toolbar collapses into a single pill with a spinner; when it finishes, the pill becomes a card with the result and a close button; after its duration (or on close) the toolbar is restored. The groups stay mounted the whole time, so their state (an open search, a counter) is kept.
161
+
162
+ ```tsx
163
+ import { BubbleToolbar, useBubbleLiveActivity } from 'bubble-toolbar';
164
+
165
+ export function App() {
166
+ const live = useBubbleLiveActivity({ duration: 4000 });
167
+
168
+ const upload = async (files: File[]) => {
169
+ // Resolves or rejects exactly like the task itself.
170
+ const result = await live.run(() => api.upload(files), {
171
+ pending: { title: 'Uploading…' },
172
+ success: (r) => ({ title: 'Upload complete', description: `${r.count} files ready` }),
173
+ error: (e) => ({ title: 'Upload failed', description: String(e) }),
174
+ });
175
+ return result;
176
+ };
177
+
178
+ return (
179
+ <>
180
+ <UploadButton onFiles={upload} />
181
+ <BubbleToolbar position="top" liveActivity={live}>
182
+ {/* groups */}
183
+ </BubbleToolbar>
184
+ </>
185
+ );
186
+ }
187
+ ```
188
+
189
+ - `live.run(task, options)` shows the task and returns its own promise. `live.show(content, { duration })` shows a card without a task, and `live.dismiss()` closes the current one.
190
+ - Activities are queued (FIFO). The pill shows `+n` when more are waiting, and a task that already finished goes straight to its card.
191
+ - The pill stays at least `minPendingMs` (600ms by default) so fast tasks do not flicker. `duration: 0` keeps the card until it is closed.
192
+ - The timer pauses while the card is hovered or focused. Esc closes the card. Results are announced through a polite live region, and errors through an alert.
193
+ - If focus was inside the toolbar, it moves to the pill and comes back to the same element afterwards.
194
+ - Inside the toolbar, `useBubbleToolbarLiveActivity()` returns the same controller without passing it down.
195
+ - Content has a fixed shape: `{ title, description?, icon?, tone?: 'neutral' | 'success' | 'error' }`. Texts go in `labels` (`pending`, `success`, `error`, `close`, `group`) and the look in `tokens`, both as `useBubbleLiveActivity` options.
196
+ - With `prefers-reduced-motion`, the toolbar switches without the width morph.
197
+
198
+ | `tokens` key | CSS variable | Default | Guard |
199
+ | :--- | :--- | :--- | :--- |
200
+ | `compactWidth` | `--bubble-live-compact-width` | `200px` | Capped at `100vw - 32px` |
201
+ | `width` | `--bubble-live-width` | `360px` | Capped at `100vw - 32px` |
202
+ | `radius` | `--bubble-live-radius` | `24px` | |
203
+ | `progressColor` | `--bubble-live-progress-color` | `currentColor` at 40% | |
204
+ | `successColor` / `errorColor` | `--bubble-live-success-color` / `--bubble-live-error-color` | `#22c55e` / `#ef4444` | |
205
+ | `titleSize` | `--bubble-live-title-size` | `13px` | 11–18px |
206
+ | `metaSize` | `--bubble-live-meta-size` | `11px` | 9–14px |
207
+ | `duration` | `--bubble-live-duration` | `320ms` | 0–600ms |
208
+
209
+ ## Search and breadcrumb search
210
+
211
+ `BubbleSearch` is a search bubble: a button that expands into an accessible combobox with async results. `BubbleBreadcrumbSearch` shows the current location as a breadcrumb and turns into the same search when its button is pressed.
212
+
213
+ ```tsx
214
+ import { BubbleBreadcrumbSearch, BubbleToolbar } from 'bubble-toolbar';
215
+
216
+ const searchPages = async (query: string, signal: AbortSignal) => {
217
+ const response = await fetch(`/api/pages?q=${encodeURIComponent(query)}`, { signal });
218
+ return response.json(); // [{ id, title, description? }]
219
+ };
220
+
221
+ export function Header({ location, navigate }) {
222
+ return (
223
+ <BubbleToolbar position="top" style={{ alignItems: 'flex-start' }}>
224
+ <BubbleToolbar.BreadcrumbSearch
225
+ items={location} // [{ label: 'Projects', href: '/projects' }, { label: 'Overview' }]
226
+ onSearch={searchPages}
227
+ onSelect={(page) => navigate(page.id)}
228
+ suggestions={recentPages}
229
+ labels={{ location: 'Current page', placeholder: 'Search pages…' }}
230
+ />
231
+ </BubbleToolbar>
232
+ );
233
+ }
234
+ ```
235
+
236
+ - `onSearch` receives the trimmed, lower-cased query and an `AbortSignal`. The signal is aborted when the query changes, the search closes or the component unmounts, so stale results never replace newer ones. While a search is pending, the group shows the async activity border.
237
+ - Items have a fixed shape, `{ id, title, description? }`, so the listbox layout and keyboard navigation always work.
238
+ - The breadcrumb is controlled: pass the current `items` (usually from your router) and update them in `onSelect`. The last item is the current page and stays visible while parent levels truncate first.
239
+ - Keyboard: ↑ ↓ move through options, Enter selects, Esc closes and returns focus to the button. Clicking or tabbing outside closes the search.
240
+ - Without a breadcrumb, `BubbleSearch` is a circle while closed and expands when opened.
241
+ - In `top` and `static` toolbars the panel opens downward; give the toolbar `alignItems: 'flex-start'` so the other groups stay in place. In `bottom` toolbars it opens upward; use `alignItems: 'flex-end'`.
242
+ - The open/close collapse is applied inline, so the search works without `styles.css`; import it for the default look and the token-driven styles.
243
+
244
+ ## Theming with tokens
245
+
246
+ Customization goes through CSS custom properties. Structure, layout and behavior stay fixed, and tokens that could break the layout are clamped. Import `bubble-toolbar/styles.css` to get the defaults.
247
+
248
+ Toolbar tokens, set on `:root` or any ancestor:
249
+
250
+ | Token | Default |
251
+ | :--- | :--- |
252
+ | `--bubble-blur` | `16px` (ignored when `blurAmount` is set) |
253
+ | `--bubble-glass-bg` / `--bubble-glass-bg-solid` | `rgba(255, 255, 255, 0.65)` / `rgba(255, 255, 255, 0.92)` |
254
+ | `--bubble-glass-border` / `--bubble-glass-fg` | `rgba(255, 255, 255, 0.4)` / `#09090b` |
255
+ | `--bubble-glass-shadow` | Default floating shadow |
256
+ | `--bubble-light-bg` / `-bg-solid` / `-border` / `-fg` | `rgba(255, 255, 255, 0.75)` / `0.95` / `rgba(0, 0, 0, 0.08)` / `#18181b` |
257
+ | `--bubble-dark-bg` / `-bg-solid` / `-border` / `-fg` | `rgba(24, 24, 27, 0.72)` / `0.95` / `rgba(255, 255, 255, 0.12)` / `#f4f4f5` |
258
+ | `--bubble-button-hover-bg` / `--bubble-button-active-bg` | `rgba(0, 0, 0, 0.05)` / `rgba(0, 0, 0, 0.08)` |
259
+ | `--bubble-focus-ring` | `#3b82f6` |
260
+
261
+ The `blurred` prop picks the `-bg` (translucent) or `-bg-solid` variant. A string `shadow` prop still wins over `--bubble-glass-shadow`.
262
+
263
+ Search tokens can be passed through the typed `tokens` prop, where numbers become `px` (or `ms` for `duration`), or set as CSS variables:
264
+
265
+ ```tsx
266
+ <BubbleToolbar.Search tokens={{ width: 420, radius: 14, focusRing: '#f97316' }} … />
267
+ ```
268
+
269
+ ```css
270
+ .brand-search {
271
+ --bubble-search-option-active-bg: rgb(249 115 22 / 16%);
272
+ }
273
+ ```
274
+
275
+ | `tokens` key | CSS variable | Default | Guard |
276
+ | :--- | :--- | :--- | :--- |
277
+ | `width` | `--bubble-search-width` | `360px` | Capped at `100vw - 96px` |
278
+ | `radius` | `--bubble-search-radius` | `24px` | |
279
+ | `divider` | `--bubble-search-divider` | `rgb(128 128 128 / 18%)` | |
280
+ | `focusRing` | `--bubble-search-focus-ring` | `#818cf8` | |
281
+ | `optionActiveBg` | `--bubble-search-option-active-bg` | `rgb(129 140 248 / 18%)` | |
282
+ | `optionRadius` | `--bubble-search-option-radius` | `12px` | |
283
+ | `titleSize` | `--bubble-search-title-size` | `13px` | 10–20px |
284
+ | `titleWeight` | `--bubble-search-title-weight` | `600` | |
285
+ | `metaSize` | `--bubble-search-meta-size` | `11px` | 9–16px |
286
+ | `mutedOpacity` | `--bubble-search-muted-opacity` | `0.7` | 0.4–1 |
287
+ | `resultsMaxHeight` | `--bubble-search-results-max-height` | `220px` | Capped at `100dvh - 208px` |
288
+ | `panelPadding` | `--bubble-search-panel-padding` | `8px` | 0–16px |
289
+ | `duration` | `--bubble-search-duration` | `180ms` | 0–600ms |
290
+ | `easing` | `--bubble-search-easing` | `ease` | |
291
+ | `breadcrumbSize` | `--bubble-breadcrumb-size` | `12px` | 10–16px |
292
+ | `breadcrumbSeparatorColor` | `--bubble-breadcrumb-separator-color` | `currentColor` | |
293
+ | `breadcrumbCurrentWeight` | `--bubble-breadcrumb-current-weight` | `500` | |
294
+ | `breadcrumbMutedOpacity` | `--bubble-breadcrumb-muted-opacity` | `0.7` | 0.4–1 |
295
+
296
+ Unknown `tokens` keys are ignored. The `breadcrumb*` keys only apply to `BubbleBreadcrumbSearch`.
297
+
158
298
  ## Adaptive shapes
159
299
 
160
300
  | Group content | Shape | Behavior |
@@ -176,12 +316,13 @@ Use the `shape` prop on a group to override its automatic shape.
176
316
  | `theme` | `'glass' \| 'light' \| 'dark' \| 'auto'` | `'glass'` | Visual theme. |
177
317
  | `size` | `'sm' \| 'md' \| 'lg' \| number` | `'md'` | Group height and circle diameter: 40px, 48px, 56px, or a custom pixel value. |
178
318
  | `blurred` | `boolean` | `true` | Enables background blur. |
179
- | `blurAmount` | `number \| string` | `16` | Blur radius in pixels or a CSS length. |
319
+ | `blurAmount` | `number \| string` | `--bubble-blur` | Blur radius in pixels or a CSS length. Overrides the `--bubble-blur` token; negative numbers become `0`. |
180
320
  | `groupGap` | `number \| string` | `8` | Space between groups, in pixels or a CSS length. |
181
321
  | `itemGap` | `number \| string` | `8` | Space between children within a pill. |
182
322
  | `bordered` | `boolean` | `true` | Shows subtle borders. |
183
323
  | `shadow` | `boolean \| string` | `true` | Enables the default shadow or supplies a custom CSS shadow. |
184
324
  | `ariaLabel` | `string` | `'Toolbar'` | Accessible toolbar label. |
325
+ | `liveActivity` | `BubbleLiveActivityController` | `undefined` | Controller from `useBubbleLiveActivity`; see [Live Activities](#live-activities). |
185
326
 
186
327
  ### `<BubbleGroup />` / `<BubbleToolbar.Group />`
187
328
 
@@ -211,7 +352,34 @@ Use the `shape` prop on a group to override its automatic shape.
211
352
  | `minWidth` | `number \| string` | `120` | Minimum width in pixels or a CSS length. |
212
353
  | `maxWidth` | `number \| string` | `undefined` | Maximum width in pixels or a CSS length. |
213
354
 
214
- All components accept `className`, `style`, and standard attributes for their underlying element. Toolbar, group, and area refs point to a `div`; icon button refs point to a `button`.
355
+ ### `<BubbleSearch />` / `<BubbleToolbar.Search />`
356
+
357
+ | Prop | Type | Default | Description |
358
+ | :--- | :--- | :--- | :--- |
359
+ | `onSearch` | `(query, signal) => Promise<T[]>` | Required | Async search; `T extends { id, title, description? }`. |
360
+ | `onSelect` | `(item: T) => void` | Required | Called when an option is chosen. |
361
+ | `suggestions` | `T[]` | `[]` | Shown for an empty query and when nothing matches. |
362
+ | `onError` | `(error: unknown) => void` | `undefined` | Search errors other than cancellations. |
363
+ | `open` / `defaultOpen` / `onOpenChange` | `boolean` / `boolean` / `(open) => void` | Uncontrolled, `false` | Controlled or uncontrolled open state. |
364
+ | `debounceMs` | `number` | `0` | Delay before searching. |
365
+ | `labels` | `Partial<BubbleSearchLabels>` | English | Accessible names and visible texts (`group`, `button`, `input`, `listbox`, `placeholder`, `searching`, `noResults`, `suggestions`, `results(count)`, `hint`). |
366
+ | `tokens` | `BubbleSearchTokens` | `undefined` | See [Theming with tokens](#theming-with-tokens). |
367
+ | `className` | `string` | `undefined` | Class for the group, useful to scope tokens. |
368
+
369
+ ### `<BubbleBreadcrumbSearch />` / `<BubbleToolbar.BreadcrumbSearch />`
370
+
371
+ Accepts every `BubbleSearch` prop, plus:
372
+
373
+ | Prop | Type | Default | Description |
374
+ | :--- | :--- | :--- | :--- |
375
+ | `items` | `{ label: string; href?: string }[]` | Required | Current location; the last item is the current page. |
376
+ | `separator` | `string` | `'/'` | Text between levels. |
377
+ | `labels.location` | `string` | `'Breadcrumb'` | Accessible name of the breadcrumb navigation. |
378
+ | `tokens` | `BubbleBreadcrumbSearchTokens` | `undefined` | Search tokens plus `breadcrumb*` tokens. |
379
+
380
+ The search components intentionally accept no `style` and render a fixed structure; refs point to the group `div`.
381
+
382
+ The toolbar, group, area and icon button accept `className`, `style`, and standard attributes for their underlying element. Toolbar, group, and area refs point to a `div`; icon button refs point to a `button`.
215
383
 
216
384
  ## Development
217
385
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { HTMLAttributes, ReactNode, CSSProperties, ButtonHTMLAttributes, ForwardRefExoticComponent, RefAttributes } from 'react';
2
+ import { HTMLAttributes, ReactNode, CSSProperties, ButtonHTMLAttributes, RefAttributes, ReactElement, ForwardRefExoticComponent } from 'react';
3
3
 
4
4
  interface BubbleActivityValue {
5
5
  isBusy: boolean;
@@ -59,8 +59,8 @@ interface BubbleToolbarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
59
59
  */
60
60
  blurred?: boolean;
61
61
  /**
62
- * Amount of backdrop blur in pixels or CSS string.
63
- * @default 16
62
+ * Amount of backdrop blur in pixels or CSS string. Negative numbers are clamped to 0.
63
+ * When omitted, the `--bubble-blur` CSS token is used (16px by default).
64
64
  */
65
65
  blurAmount?: number | string;
66
66
  /**
@@ -96,6 +96,11 @@ interface BubbleToolbarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
96
96
  * @default 'Toolbar'
97
97
  */
98
98
  ariaLabel?: string;
99
+ /**
100
+ * Controller from useBubbleLiveActivity. While an activity is shown, the groups collapse
101
+ * into a single pill (and then a card) and are restored afterwards, keeping their state.
102
+ */
103
+ liveActivity?: BubbleLiveActivityController;
99
104
  }
100
105
  interface BubbleGroupProps extends HTMLAttributes<HTMLDivElement>, BubbleActivityProps {
101
106
  /**
@@ -166,11 +171,226 @@ interface BubbleAreaProps extends HTMLAttributes<HTMLDivElement> {
166
171
  */
167
172
  maxWidth?: number | string;
168
173
  }
174
+ /** A search result or suggestion. Its shape is fixed so the listbox layout stays intact. */
175
+ interface BubbleSearchItem {
176
+ /** Stable identifier, used as React key. */
177
+ id: string;
178
+ /** Main line of the option. */
179
+ title: string;
180
+ /** Optional secondary line, e.g. a path. */
181
+ description?: string;
182
+ }
183
+ /** Visible and accessible texts of BubbleSearch. */
184
+ interface BubbleSearchLabels {
185
+ /** Accessible name of the search group. @default 'Search' */
186
+ group: string;
187
+ /** Accessible name of the search button. @default 'Search' */
188
+ button: string;
189
+ /** Accessible name of the combobox input. @default 'Search' */
190
+ input: string;
191
+ /** Accessible name of the results listbox. @default 'Results' */
192
+ listbox: string;
193
+ /** @default 'Search…' */
194
+ placeholder: string;
195
+ /** @default 'Searching…' */
196
+ searching: string;
197
+ /** @default 'No results. Try another term or explore a suggestion.' */
198
+ noResults: string;
199
+ /** @default 'Suggestions' */
200
+ suggestions: string;
201
+ /** @default (count) => '1 result' | `${count} results` */
202
+ results: (count: number) => string;
203
+ /** @default '↑ ↓ to explore · Enter to select · Esc to close' */
204
+ hint: string;
205
+ }
206
+ /**
207
+ * Visual tokens of BubbleSearch, written as CSS custom properties `--bubble-search-*`.
208
+ * Numbers are px for lengths and ms for `duration`. Structure and layout are not customizable.
209
+ */
210
+ interface BubbleSearchTokens {
211
+ /** Expanded width, capped to the viewport. @default 360px */
212
+ width?: number | string;
213
+ /** Corner radius of the expanded search. @default 24px */
214
+ radius?: number | string;
215
+ /** Divider between the header and the results. @default rgb(128 128 128 / 18%) */
216
+ divider?: string;
217
+ /** Focus ring of the header. @default #818cf8 */
218
+ focusRing?: string;
219
+ /** Background of the hovered / active option. @default rgb(129 140 248 / 18%) */
220
+ optionActiveBg?: string;
221
+ /** @default 12px */
222
+ optionRadius?: number | string;
223
+ /** Option title size, clamped to 10–20px. @default 13px */
224
+ titleSize?: number | string;
225
+ /** @default 600 */
226
+ titleWeight?: number | string;
227
+ /** Size of descriptions, status and hint, clamped to 9–16px. @default 11px */
228
+ metaSize?: number | string;
229
+ /** Opacity of secondary texts, clamped to 0.4–1. @default 0.7 */
230
+ mutedOpacity?: number | string;
231
+ /** Results max height, capped to the viewport. @default 220px */
232
+ resultsMaxHeight?: number | string;
233
+ /** Panel padding, clamped to 0–16px. @default 8px */
234
+ panelPadding?: number | string;
235
+ /** Expand / collapse duration, clamped to 0–600ms. @default 180ms */
236
+ duration?: number | string;
237
+ /** @default ease */
238
+ easing?: string;
239
+ }
240
+ interface BubbleSearchProps<T extends BubbleSearchItem = BubbleSearchItem> {
241
+ /**
242
+ * Async search. Receives the trimmed, lower-cased query and an AbortSignal that is
243
+ * aborted when the query changes, the search closes or the component unmounts.
244
+ */
245
+ onSearch: (query: string, signal: AbortSignal) => Promise<T[]>;
246
+ /** Called when an option is chosen. The search closes and focus returns to the button. */
247
+ onSelect: (item: T) => void;
248
+ /** Shown while the query is empty and when nothing matches. */
249
+ suggestions?: T[];
250
+ /** Receives errors from onSearch, except cancellations. The status then shows no results. */
251
+ onError?: (error: unknown) => void;
252
+ /** Controlled open state. */
253
+ open?: boolean;
254
+ /** @default false */
255
+ defaultOpen?: boolean;
256
+ onOpenChange?: (open: boolean) => void;
257
+ /** Wait before searching. 0 searches immediately. @default 0 */
258
+ debounceMs?: number;
259
+ labels?: Partial<BubbleSearchLabels>;
260
+ tokens?: BubbleSearchTokens;
261
+ /** Class for the search group, useful to scope tokens in CSS. */
262
+ className?: string;
263
+ }
264
+ /** A breadcrumb level. The last item is the current page. */
265
+ interface BubbleBreadcrumbItem {
266
+ label: string;
267
+ href?: string;
268
+ }
269
+ interface BubbleBreadcrumbSearchLabels extends BubbleSearchLabels {
270
+ /** Accessible name of the breadcrumb navigation. @default 'Breadcrumb' */
271
+ location: string;
272
+ }
273
+ /** BubbleSearch tokens plus `--bubble-breadcrumb-*` tokens. */
274
+ interface BubbleBreadcrumbSearchTokens extends BubbleSearchTokens {
275
+ /** Breadcrumb text size, clamped to 10–16px. @default 12px */
276
+ breadcrumbSize?: number | string;
277
+ /** @default currentColor */
278
+ breadcrumbSeparatorColor?: string;
279
+ /** Weight of the current page. @default 500 */
280
+ breadcrumbCurrentWeight?: number | string;
281
+ /** Opacity of parent levels, clamped to 0.4–1. @default 0.7 */
282
+ breadcrumbMutedOpacity?: number | string;
283
+ }
284
+ interface BubbleBreadcrumbSearchProps<T extends BubbleSearchItem = BubbleSearchItem> extends Omit<BubbleSearchProps<T>, 'labels' | 'tokens'> {
285
+ /** Current location, controlled by the app (usually its router). */
286
+ items: BubbleBreadcrumbItem[];
287
+ /** Text between levels. @default '/' */
288
+ separator?: string;
289
+ labels?: Partial<BubbleBreadcrumbSearchLabels>;
290
+ tokens?: BubbleBreadcrumbSearchTokens;
291
+ }
292
+ /** Content of a Live Activity. Its shape is fixed so the pill and the card keep their layout. */
293
+ interface BubbleLiveContent {
294
+ title: string;
295
+ description?: string;
296
+ /** Replaces the default spinner / outcome icon. */
297
+ icon?: ReactNode;
298
+ /** @default 'success' after a resolved task, 'error' after a rejected one, otherwise 'neutral' */
299
+ tone?: 'neutral' | 'success' | 'error';
300
+ }
301
+ type BubbleLiveState = 'idle' | 'compact' | 'expanded';
302
+ interface BubbleLiveRunOptions<T> {
303
+ /** Shown in the compact pill while the task runs. Defaults to `labels.pending`. */
304
+ pending?: BubbleLiveContent;
305
+ /** Card shown when the task resolves. Defaults to `labels.success`. */
306
+ success?: (value: T) => BubbleLiveContent;
307
+ /** Card shown when the task rejects. Defaults to `labels.error`. */
308
+ error?: (error: unknown) => BubbleLiveContent;
309
+ /** Milliseconds the card stays visible; 0 keeps it until dismissed. Defaults to the controller's. */
310
+ duration?: number;
311
+ }
312
+ /** Texts of the Live Activity. */
313
+ interface BubbleLiveLabels {
314
+ /** @default 'Working…' */
315
+ pending: string;
316
+ /** @default 'Done' */
317
+ success: string;
318
+ /** @default 'Something went wrong' */
319
+ error: string;
320
+ /** Accessible name of the close button. @default 'Dismiss' */
321
+ close: string;
322
+ /** Accessible name of the live group. @default 'Live activity' */
323
+ group: string;
324
+ }
325
+ /**
326
+ * Visual tokens of the Live Activity, written as `--bubble-live-*` custom properties.
327
+ * Numbers are px for lengths and ms for `duration`.
328
+ */
329
+ interface BubbleLiveTokens {
330
+ /** Width of the compact pill, capped to the viewport. @default 200px */
331
+ compactWidth?: number | string;
332
+ /** Width of the card, capped to the viewport. @default 360px */
333
+ width?: number | string;
334
+ /** Corner radius of the card. @default 24px */
335
+ radius?: number | string;
336
+ /** Remaining-time bar. @default currentColor at 40% */
337
+ progressColor?: string;
338
+ /** @default #22c55e */
339
+ successColor?: string;
340
+ /** @default #ef4444 */
341
+ errorColor?: string;
342
+ /** Title size, clamped to 11–18px. @default 13px */
343
+ titleSize?: number | string;
344
+ /** Description size, clamped to 9–14px. @default 11px */
345
+ metaSize?: number | string;
346
+ /** Morph duration, clamped to 0–600ms. @default 320ms */
347
+ duration?: number | string;
348
+ }
349
+ interface BubbleLiveActivityOptions {
350
+ /** Default card duration in ms; 0 keeps cards until dismissed. @default 4000 */
351
+ duration?: number;
352
+ /** Minimum time the compact pill stays, so fast tasks do not flicker. @default 600 */
353
+ minPendingMs?: number;
354
+ labels?: Partial<BubbleLiveLabels>;
355
+ tokens?: BubbleLiveTokens;
356
+ }
357
+ /** Controller returned by useBubbleLiveActivity. Stable across renders; usable from anywhere. */
358
+ interface BubbleLiveActivityController {
359
+ /** Shows the task in the toolbar and returns its own promise (value or original error). */
360
+ run: <T>(task: () => Promise<T> | T, options?: BubbleLiveRunOptions<T>) => Promise<T>;
361
+ /** Queues a card without a task. */
362
+ show: (content: BubbleLiveContent, options?: {
363
+ duration?: number;
364
+ }) => void;
365
+ /** Closes the current activity and moves to the next queued one. */
366
+ dismiss: () => void;
367
+ /** Current state, for tests or custom UI. Not reactive; subscribe for updates. */
368
+ readonly state: BubbleLiveState;
369
+ subscribe: (listener: () => void) => () => void;
370
+ }
169
371
 
170
372
  declare const BubbleGroup: react.ForwardRefExoticComponent<BubbleGroupProps & react.RefAttributes<HTMLDivElement>>;
171
373
 
374
+ /**
375
+ * Shows the current location as a breadcrumb and turns into a search when its button is pressed.
376
+ * The last item is the current page; it stays visible while parents truncate first.
377
+ */
378
+ declare const BubbleBreadcrumbSearch: (<T extends BubbleSearchItem>(props: BubbleBreadcrumbSearchProps<T> & RefAttributes<HTMLDivElement>) => ReactElement | null) & {
379
+ displayName?: string;
380
+ };
381
+
382
+ /**
383
+ * Search bubble: a button that expands into a combobox with async results.
384
+ * Use it inside BubbleToolbar. Customize it through `labels` and `tokens`.
385
+ */
386
+ declare const BubbleSearch: (<T extends BubbleSearchItem>(props: BubbleSearchProps<T> & RefAttributes<HTMLDivElement>) => ReactElement | null) & {
387
+ displayName?: string;
388
+ };
389
+
172
390
  interface BubbleToolbarComponent extends ForwardRefExoticComponent<BubbleToolbarProps & RefAttributes<HTMLDivElement>> {
173
391
  Group: typeof BubbleGroup;
392
+ Search: typeof BubbleSearch;
393
+ BreadcrumbSearch: typeof BubbleBreadcrumbSearch;
174
394
  }
175
395
  declare const BubbleToolbar: BubbleToolbarComponent;
176
396
 
@@ -182,14 +402,27 @@ interface BubbleToolbarContextValue extends BubbleActivityProps {
182
402
  size: BubbleToolbarSize;
183
403
  theme: BubbleToolbarTheme;
184
404
  blurred: boolean;
185
- blurAmount: number | string;
405
+ /** Undefined falls back to the --bubble-blur token. */
406
+ blurAmount?: number | string;
186
407
  itemGap: number | string;
187
408
  bordered: boolean;
188
409
  shadow: boolean | string;
410
+ /** Toolbar placement; undefined outside a BubbleToolbar. */
411
+ position?: BubbleToolbarPosition;
412
+ /** Live Activity controller of the toolbar, if any. */
413
+ liveActivity?: BubbleLiveActivityController;
189
414
  }
190
415
  declare const BubbleToolbarContext: react.Context<BubbleToolbarContextValue>;
191
416
  declare const useBubbleToolbar: () => BubbleToolbarContextValue;
192
417
 
418
+ /**
419
+ * Creates a stable Live Activity controller. Pass it to `<BubbleToolbar liveActivity>` and call
420
+ * `run`, `show` or `dismiss` from anywhere. Options are read on every render.
421
+ */
422
+ declare const useBubbleLiveActivity: (options?: BubbleLiveActivityOptions) => BubbleLiveActivityController;
423
+ /** Reads the controller of the surrounding BubbleToolbar. */
424
+ declare const useBubbleToolbarLiveActivity: () => BubbleLiveActivityController;
425
+
193
426
  /**
194
427
  * Returns an array of non-null, non-undefined, non-boolean child elements.
195
428
  * Unpacks Fragment if encountered at the top level.
@@ -198,6 +431,6 @@ declare function getValidChildren(children: ReactNode): ReactNode[];
198
431
 
199
432
  declare function resolveSize(size: BubbleToolbarSize): number;
200
433
  declare function formatPx(val: number | string): string;
201
- declare function getGlassStyles(theme: BubbleToolbarTheme, blurred: boolean, blurAmount: number | string, bordered: boolean, shadow: boolean | string): CSSProperties;
434
+ declare function getGlassStyles(theme: BubbleToolbarTheme, blurred: boolean, blurAmount: number | string | undefined, bordered: boolean, shadow: boolean | string): CSSProperties;
202
435
 
203
- export { type BubbleActivityProps, type BubbleActivityValue, BubbleArea, type BubbleAreaProps, BubbleGroup, type BubbleGroupProps, BubbleIconButton, type BubbleIconButtonProps, type BubbleShape, BubbleToolbar, type BubbleToolbarComponent, BubbleToolbarContext, type BubbleToolbarPosition, type BubbleToolbarProps, type BubbleToolbarSize, type BubbleToolbarTheme, formatPx, getGlassStyles, getValidChildren, resolveSize, useBubbleActivity, useBubbleToolbar };
436
+ export { type BubbleActivityProps, type BubbleActivityValue, BubbleArea, type BubbleAreaProps, type BubbleBreadcrumbItem, BubbleBreadcrumbSearch, type BubbleBreadcrumbSearchLabels, type BubbleBreadcrumbSearchProps, type BubbleBreadcrumbSearchTokens, BubbleGroup, type BubbleGroupProps, BubbleIconButton, type BubbleIconButtonProps, type BubbleLiveActivityController, type BubbleLiveActivityOptions, type BubbleLiveContent, type BubbleLiveLabels, type BubbleLiveRunOptions, type BubbleLiveState, type BubbleLiveTokens, BubbleSearch, type BubbleSearchItem, type BubbleSearchLabels, type BubbleSearchProps, type BubbleSearchTokens, type BubbleShape, BubbleToolbar, type BubbleToolbarComponent, BubbleToolbarContext, type BubbleToolbarPosition, type BubbleToolbarProps, type BubbleToolbarSize, type BubbleToolbarTheme, formatPx, getGlassStyles, getValidChildren, resolveSize, useBubbleActivity, useBubbleLiveActivity, useBubbleToolbar, useBubbleToolbarLiveActivity };