@oxy.so/bloom 3.2.1 → 3.3.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/docs/adoption-matrix.mdx +2 -2
- package/docs/ai-chat.mdx +31 -3
- package/docs/composer-panel.mdx +23 -6
- package/lib/commonjs/ai-chat/AiChatContainer.js +122 -14
- package/lib/commonjs/ai-chat/AiChatContainer.js.map +1 -1
- package/lib/commonjs/ai-chat/AiChatShell.js +46 -6
- package/lib/commonjs/ai-chat/AiChatShell.js.map +1 -1
- package/lib/commonjs/ai-chat/context.js +5 -1
- package/lib/commonjs/ai-chat/context.js.map +1 -1
- package/lib/commonjs/ai-chat/index.js +7 -0
- package/lib/commonjs/ai-chat/index.js.map +1 -1
- package/lib/commonjs/ai-chat/index.web.js +7 -0
- package/lib/commonjs/ai-chat/index.web.js.map +1 -1
- package/lib/commonjs/composer-panel/ComposerControls.js +61 -0
- package/lib/commonjs/composer-panel/ComposerControls.js.map +1 -1
- package/lib/commonjs/composer-panel/ComposerPanelBase.js +12 -4
- package/lib/commonjs/composer-panel/ComposerPanelBase.js.map +1 -1
- package/lib/commonjs/composer-panel/ComposerPillBase.js +114 -79
- package/lib/commonjs/composer-panel/ComposerPillBase.js.map +1 -1
- package/lib/commonjs/composer-panel/EffortSlider.js +24 -15
- package/lib/commonjs/composer-panel/EffortSlider.js.map +1 -1
- package/lib/commonjs/composer-panel/ModelPickerBase.js +15 -4
- package/lib/commonjs/composer-panel/ModelPickerBase.js.map +1 -1
- package/lib/module/ai-chat/AiChatContainer.js +122 -14
- package/lib/module/ai-chat/AiChatContainer.js.map +1 -1
- package/lib/module/ai-chat/AiChatShell.js +46 -6
- package/lib/module/ai-chat/AiChatShell.js.map +1 -1
- package/lib/module/ai-chat/context.js +5 -1
- package/lib/module/ai-chat/context.js.map +1 -1
- package/lib/module/ai-chat/index.js +1 -0
- package/lib/module/ai-chat/index.js.map +1 -1
- package/lib/module/ai-chat/index.web.js +1 -0
- package/lib/module/ai-chat/index.web.js.map +1 -1
- package/lib/module/composer-panel/ComposerControls.js +60 -0
- package/lib/module/composer-panel/ComposerControls.js.map +1 -1
- package/lib/module/composer-panel/ComposerPanelBase.js +13 -5
- package/lib/module/composer-panel/ComposerPanelBase.js.map +1 -1
- package/lib/module/composer-panel/ComposerPillBase.js +115 -80
- package/lib/module/composer-panel/ComposerPillBase.js.map +1 -1
- package/lib/module/composer-panel/EffortSlider.js +24 -15
- package/lib/module/composer-panel/EffortSlider.js.map +1 -1
- package/lib/module/composer-panel/ModelPickerBase.js +15 -4
- package/lib/module/composer-panel/ModelPickerBase.js.map +1 -1
- package/lib/typescript/commonjs/ai-chat/AiChatContainer.d.ts +28 -5
- package/lib/typescript/commonjs/ai-chat/AiChatContainer.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/AiChatShell.d.ts +11 -2
- package/lib/typescript/commonjs/ai-chat/AiChatShell.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/context.d.ts +19 -1
- package/lib/typescript/commonjs/ai-chat/context.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/index.d.ts +3 -1
- package/lib/typescript/commonjs/ai-chat/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/index.web.d.ts +3 -1
- package/lib/typescript/commonjs/ai-chat/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/ai-chat/types.d.ts +101 -3
- package/lib/typescript/commonjs/ai-chat/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/composer-panel/ComposerControls.d.ts +14 -0
- package/lib/typescript/commonjs/composer-panel/ComposerControls.d.ts.map +1 -1
- package/lib/typescript/commonjs/composer-panel/ComposerPanelBase.d.ts +3 -2
- package/lib/typescript/commonjs/composer-panel/ComposerPanelBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/composer-panel/ComposerPillBase.d.ts +3 -2
- package/lib/typescript/commonjs/composer-panel/ComposerPillBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/composer-panel/EffortSlider.d.ts +10 -2
- package/lib/typescript/commonjs/composer-panel/EffortSlider.d.ts.map +1 -1
- package/lib/typescript/commonjs/composer-panel/ModelPickerBase.d.ts.map +1 -1
- package/lib/typescript/commonjs/composer-panel/types.d.ts +61 -20
- package/lib/typescript/commonjs/composer-panel/types.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/AiChatContainer.d.ts +28 -5
- package/lib/typescript/module/ai-chat/AiChatContainer.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/AiChatShell.d.ts +11 -2
- package/lib/typescript/module/ai-chat/AiChatShell.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/context.d.ts +19 -1
- package/lib/typescript/module/ai-chat/context.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/index.d.ts +3 -1
- package/lib/typescript/module/ai-chat/index.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/index.web.d.ts +3 -1
- package/lib/typescript/module/ai-chat/index.web.d.ts.map +1 -1
- package/lib/typescript/module/ai-chat/types.d.ts +101 -3
- package/lib/typescript/module/ai-chat/types.d.ts.map +1 -1
- package/lib/typescript/module/composer-panel/ComposerControls.d.ts +14 -0
- package/lib/typescript/module/composer-panel/ComposerControls.d.ts.map +1 -1
- package/lib/typescript/module/composer-panel/ComposerPanelBase.d.ts +3 -2
- package/lib/typescript/module/composer-panel/ComposerPanelBase.d.ts.map +1 -1
- package/lib/typescript/module/composer-panel/ComposerPillBase.d.ts +3 -2
- package/lib/typescript/module/composer-panel/ComposerPillBase.d.ts.map +1 -1
- package/lib/typescript/module/composer-panel/EffortSlider.d.ts +10 -2
- package/lib/typescript/module/composer-panel/EffortSlider.d.ts.map +1 -1
- package/lib/typescript/module/composer-panel/ModelPickerBase.d.ts.map +1 -1
- package/lib/typescript/module/composer-panel/types.d.ts +61 -20
- package/lib/typescript/module/composer-panel/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/support/adoption-matrix.ts +664 -0
- package/src/__tests__/support/collision-fixture-barrel.ts +20 -0
- package/src/__tests__/support/composite.ts +68 -0
- package/src/__tests__/support/constructed-style-sheets.ts +68 -0
- package/src/__tests__/support/press-host.ts +30 -0
- package/src/__tests__/support/rendered-style.ts +99 -0
- package/src/__tests__/support/unread-hook-fixture.ts +33 -0
- package/src/ai-chat/AiChatContainer.tsx +137 -16
- package/src/ai-chat/AiChatShell.tsx +59 -5
- package/src/ai-chat/context.ts +19 -1
- package/src/ai-chat/index.ts +3 -0
- package/src/ai-chat/index.web.ts +3 -0
- package/src/ai-chat/types.ts +104 -3
- package/src/composer-panel/ComposerControls.tsx +54 -0
- package/src/composer-panel/ComposerPanelBase.tsx +13 -5
- package/src/composer-panel/ComposerPillBase.tsx +101 -59
- package/src/composer-panel/EffortSlider.tsx +38 -18
- package/src/composer-panel/ModelPickerBase.tsx +23 -7
- package/src/composer-panel/types.ts +61 -20
- package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +7682 -0
- package/src/theme/__tests__/fixtures/color-engine-golden.json +1 -0
package/docs/adoption-matrix.mdx
CHANGED
|
@@ -111,7 +111,7 @@ The classification is an EQUALITY in `src/__tests__/adoption-matrix.test.ts`, no
|
|
|
111
111
|
| `agent-log` | `@oxy.so/bloom/agent-log` + barrel | universal | `agent-log.mdx` | yes | 1 | | | | |
|
|
112
112
|
| `agent-progress` | `@oxy.so/bloom/agent-progress` + barrel | universal | `agent-progress.mdx` | yes | 1 | | | | reads |
|
|
113
113
|
| `agent-thinking` | `@oxy.so/bloom/agent-thinking` + barrel | universal | `agent-thinking.mdx` | yes | 1 | | | | |
|
|
114
|
-
| `ai-chat` | `@oxy.so/bloom/ai-chat` + barrel | web fork | `ai-chat.mdx` | yes |
|
|
114
|
+
| `ai-chat` | `@oxy.so/bloom/ai-chat` + barrel | web fork | `ai-chat.mdx` | yes | 2 | | | | reads |
|
|
115
115
|
| `ai-profile-card` | `@oxy.so/bloom/ai-profile-card` + barrel | universal | `ai-profile-card.mdx` | yes | 1 | | | | reads |
|
|
116
116
|
| `alert-dialog` | `@oxy.so/bloom/alert-dialog` + barrel | web fork | `alert-dialog.mdx` | yes | 1 | | | | |
|
|
117
117
|
| `animated-check` | `@oxy.so/bloom/animated-check` + barrel | universal | `animated-check.mdx` | yes | 2 | | | | |
|
|
@@ -254,7 +254,7 @@ The classification is an EQUALITY in `src/__tests__/adoption-matrix.test.ts`, no
|
|
|
254
254
|
| `tenancy` | `@oxy.so/bloom/tenancy` + barrel | universal | `tenancy.mdx` | yes | 3 | | n/a¹ | | |
|
|
255
255
|
| `text-field` | `@oxy.so/bloom/text-field` + barrel | universal | `text-field.mdx` | yes | 10 | reads | reads | | |
|
|
256
256
|
| `textarea` | `@oxy.so/bloom/textarea` + barrel | universal | `textarea.mdx` | yes | 4 | reads | reads | | n/a¹ |
|
|
257
|
-
| `theme` | `@oxy.so/bloom/theme` + barrel | web fork | `theme.mdx` | yes |
|
|
257
|
+
| `theme` | `@oxy.so/bloom/theme` + barrel | web fork | `theme.mdx` | yes | 243 | | | n/a¹ | reads |
|
|
258
258
|
| `theme-toggle` | `@oxy.so/bloom/theme-toggle` + barrel | universal | `theme-toggle.mdx` | yes | 1 | n/a¹ | | | |
|
|
259
259
|
| `toast` | `@oxy.so/bloom/toast` + barrel | universal | `toast.mdx` | yes | 17 | | | | |
|
|
260
260
|
| `tooltip` | `@oxy.so/bloom/tooltip` + barrel | web fork | `tooltip.mdx` | yes | 1 | | | | |
|
package/docs/ai-chat.mdx
CHANGED
|
@@ -14,7 +14,7 @@ The pieces of the AI chat and AI image generation screens. Everything is data an
|
|
|
14
14
|
| `AiChatMobileHeader` | The in-container header below `xl` (menu button, title, panel button), wired to the shell |
|
|
15
15
|
| `AiChatResizeHandle` | The grip on the chat's right edge |
|
|
16
16
|
| `AiChatContainer` | The chat card: breadcrumb header, thread, composer slot, `AgentThinking` while working |
|
|
17
|
-
| `AiChatThread` | The bottom-anchored, self-following scroll of turns |
|
|
17
|
+
| `AiChatThread` | The bottom-anchored, self-following scroll of turns — with the follow, upward pagination and `ref` a host with history needs |
|
|
18
18
|
| `AiChatUserMessage` / `AiChatAssistantMessage` | The turns, with the staggered blur-in |
|
|
19
19
|
| `AiChatMessageLine`, `AiChatBulletList`, `AiChatBullet`, `AiChatStrong`, `AiChatLinkChip` | A reply's blocks and inline runs |
|
|
20
20
|
| `AiChatFeedbackRow` | Like / dislike / copy with tooltips |
|
|
@@ -29,6 +29,7 @@ import { AiChatShell, AiChatContainer, AiChatMobileHeader, AiChatThread, AiChatU
|
|
|
29
29
|
|
|
30
30
|
<AiChatShell
|
|
31
31
|
sidebar={<Sidebar {...rail} />}
|
|
32
|
+
sidebarCollapsed={railOnly}
|
|
32
33
|
mobileSidebar={<Sidebar {...rail} mobile flat />}
|
|
33
34
|
panelLabel="Code"
|
|
34
35
|
panel={(width) => <AiChatCodePanel width={width} code={file} language="tsx" />}>
|
|
@@ -37,7 +38,12 @@ import { AiChatShell, AiChatContainer, AiChatMobileHeader, AiChatThread, AiChatU
|
|
|
37
38
|
title="coding scenario"
|
|
38
39
|
header={<AiChatMobileHeader title="Agentic chat" />}
|
|
39
40
|
working={working}
|
|
40
|
-
|
|
41
|
+
background={<YourOwnField />}
|
|
42
|
+
composer={
|
|
43
|
+
<ComposerLoader active={working}>
|
|
44
|
+
<ComposerPill surface={false} glass={working} busy={working} disabled={working} onStop={stop} />
|
|
45
|
+
</ComposerLoader>
|
|
46
|
+
}>
|
|
41
47
|
<AiChatThread>
|
|
42
48
|
<AiChatUserMessage>update our colour tokens for dark mode</AiChatUserMessage>
|
|
43
49
|
<AiChatAssistantMessage>Done — the tokens are wired.</AiChatAssistantMessage>
|
|
@@ -55,12 +61,34 @@ Give the shell a sized parent — it fills it.
|
|
|
55
61
|
- **below xl** the panel is a right drawer — min(410, 100% − 12) wide, background-full, p 12, shadow-sidebar, a 40-tall header with the panel name and a close button — over a 40% black backdrop, sliding 300ms. `panel` receives `'100%'` there.
|
|
56
62
|
- **below lg** the `mobileSidebar` rests under the workspace (272 wide, py 12 / pl 6). Opening it slides the whole workspace 272 right and rounds it to 32 while the sidebar scales from 94% and fades in (325ms `cubic-bezier(0.42, 0, 0.58, 1)`); a 10% black / 5% white veil over the workspace closes it. Opening one drawer closes the other.
|
|
57
63
|
- `navOpen` / `panelOpen` are controllable. Reduced motion snaps.
|
|
64
|
+
- **A collapsible rail** — `sidebarWidth` fixes the in-flow column's width, `sidebarCollapsed` narrows it to `collapsedSidebarWidth` (56: one 36px control and its gutters). Without either, the sidebar sizes itself exactly as before. The column snaps between the two widths; whatever animates inside it is the sidebar's own business.
|
|
65
|
+
- **`background` and `surface`** — `background` is any node, drawn above the shell's own background-full and below the sidebar, workspace and drawers; it is never interactive. The workspace paints background-full too, so pass `surface={false}` with it (that drops both) or the layer is covered exactly where the chat sits. When the layer belongs to the chat rather than the page, put it on `AiChatContainer` instead, which is the nearer slot and needs no opt-out.
|
|
66
|
+
- **What the shell publishes** — `useAiChatShell()` returns the shell's state to any descendant: `compact`, `navCollapsed`, `sidebarCollapsed`, `hasNav` / `hasPanel`, `openNav` / `closeNav` / `openPanel`, and **`navPresented`**.
|
|
67
|
+
|
|
68
|
+
`navPresented` is the one to guard a nav's accessibility with. Below `lg` a closed drawer stays **mounted** — it is translated offscreen, not removed — so everything inside `mobileSidebar` is still focusable and still read aloud unless its own tree says otherwise. The shell marks its own wrapper `aria-hidden` and `pointerEvents="none"`, which handles the pointer but not the tab order of a host's controls; read `navPresented` and skip them while it is `false`.
|
|
58
69
|
|
|
59
70
|
## Container and thread
|
|
60
71
|
|
|
61
72
|
Radius 24 on background-secondary: the breadcrumb header (px 16 / pt 16, share and more glyphs 8 apart, deepening to black / white on hover), the thread, then the footer (px 10 / pt 12 / pb 10, gap 10) with `AgentThinking` (infinity) while `working` and the `composer`.
|
|
62
73
|
|
|
63
|
-
|
|
74
|
+
`project` is **optional**: a chat that belongs to nothing draws its own crumb alone, with no folder glyph and no empty crumb.
|
|
75
|
+
|
|
76
|
+
`background` is a layer filling the container, drawn **above its own surface and below every turn, header and composer**, clipped to the radius and never interactive — a host's wallpaper, gradient or animated field goes here rather than behind the container, and nothing has to reach into it. An opaque node needs nothing else; add `surface={false}` when the node is translucent and should show what the container is sitting on instead of background-secondary.
|
|
77
|
+
|
|
78
|
+
The thread scrolls with a thin scrollbar, px 16 / pt 16, turns 12 apart, and is **bottom-anchored**: a short exchange sits just above the composer. It follows the newest content with a smooth scroll whenever it grows — that is the default, and every prop below is off unless asked for.
|
|
79
|
+
|
|
80
|
+
| Prop | Type | Default | Description |
|
|
81
|
+
|---|---|---|---|
|
|
82
|
+
| `autoFollow` | `boolean` | `true` | Follow the newest turn as the content grows. |
|
|
83
|
+
| `followAnimated` | `boolean` | `true` | `false` for a follow that must not be seen travelling. |
|
|
84
|
+
| `followThreshold` | `number` | — | Follow only from within this many px of the bottom, measured **before** the growth. Omit and every growth follows. |
|
|
85
|
+
| `onStartReached` | `() => void` | — | The reader came near the **top**. A chat pages upward, so this — not `onEndReached` — is its pagination signal. Once per approach; it re-arms when the reader leaves the zone. |
|
|
86
|
+
| `onStartReachedThreshold` | `number` | `300` | How near the top, px. |
|
|
87
|
+
| `maintainStartPosition` | `boolean` | `false` | The growth that answers an `onStartReached` is added to the offset, so a page landing **above** the reader does not move the turn they are on. Inert without `onStartReached` — the thread only anchors growth it asked for. |
|
|
88
|
+
| `onScroll` / `scrollEventThrottle` | | `16` | The raw event. Nothing is attached at all unless something is listening. |
|
|
89
|
+
| `ref` | `AiChatThreadHandle` | — | `scrollToEnd({ animated })`, `scrollToOffset({ offset, animated })`, `getScrollView()`. |
|
|
90
|
+
|
|
91
|
+
**Not there:** virtualization and `scrollToIndex`. The thread is a `ScrollView`, so a long history is fully mounted, and a host jumping to a cursor measures that turn's row (`onLayout`) and calls `scrollToOffset`. `maintainStartPosition` is the web-safe half of React Native's `maintainVisibleContentPosition`, which react-native-web ignores.
|
|
64
92
|
|
|
65
93
|
**Hazard (web):** react-native-web gives every view `min-height: 0`, so the usual `flexGrow: 1` + `justifyContent: 'flex-end'` content container stays the viewport's height and its children overflow it instead of scrolling — and `onContentSizeChange` never sees the growth. The thread floors its container at `minHeight: '100%'` on web instead.
|
|
66
94
|
|
package/docs/composer-panel.mdx
CHANGED
|
@@ -34,6 +34,16 @@ import {
|
|
|
34
34
|
|
|
35
35
|
The panel is UI and interaction state only. Sending, streaming, uploading and model routing belong to the consumer: `onSubmit` hands you the draft, `disabled` greys send while a turn is in flight, and each attachment's `progress` is yours to drive.
|
|
36
36
|
|
|
37
|
+
## A turn in flight
|
|
38
|
+
|
|
39
|
+
`busy` puts the **stop** control where send was; `onStop` is what it calls. It is the one control `disabled` does not reach — `disabled` locks the composer for the whole turn, and cancelling that turn has to stay possible for the whole of it. Enter stops submitting while `busy` too.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
<ComposerPill busy={streaming} disabled={streaming} onStop={abort} onSubmit={send} />
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Both composers take the pair. `busy` **without** `onStop` draws no stop at all: a labelled control wired to nothing is worse than no control. It is the same shape `AgentChatComposer` already uses.
|
|
46
|
+
|
|
37
47
|
## Exports
|
|
38
48
|
|
|
39
49
|
| Export | What it is |
|
|
@@ -42,7 +52,7 @@ The panel is UI and interaction state only. Sending, streaming, uploading and mo
|
|
|
42
52
|
| `ComposerAttachments` | `ComposerPanel` with a **simulated** sequential upload queue: files arriving with `progress: 0` land one after another. For demos and prototypes — real uploads pass their own `progress` to `ComposerPanel`. |
|
|
43
53
|
| `ModelPicker` | The model picker on its own (provider rail, Quick Search, effort). |
|
|
44
54
|
| `ComposerPanelStatusTab` | The status tab — branch, project folder, context meter. Pass it as `status`. |
|
|
45
|
-
| `ComposerPill` | The AI chat's one-row pill composer: add menu, field, a simple Models + Effort menu, mic and send. |
|
|
55
|
+
| `ComposerPill` | The AI chat's one-row pill composer: add menu, field, a simple Models + Effort menu, mic and send / stop. |
|
|
46
56
|
| `ComposerStatusBar` | The line under the pill: branch, a Local Folders menu, agent mode and the context meter. |
|
|
47
57
|
| `COMPOSER_PANEL_PERMISSIONS`, `COMPOSER_PANEL_ADD_MENU`, `MODEL_PICKER_EFFORT_LEVELS` | The default modes, add-menu rows and effort stops, for extending rather than retyping. |
|
|
48
58
|
|
|
@@ -60,6 +70,8 @@ import { ComposerPill, ComposerStatusBar } from '@oxy.so/bloom/composer-panel';
|
|
|
60
70
|
|
|
61
71
|
- **Pill** — 52 tall, full radius, p 8, gap 10, background-primary + shadow-xs. The add disc is a step lighter than the panel's (neutral-100 / dark 700, hovered 200 / 600) and reuses the panel's `AddMenu`. The field is body-regular with an accent-500 caret; its placeholder shortens to `compactPlaceholder` ("Ask me") under 640 wide, and unfocused on web its last 32px fade into the pill.
|
|
62
72
|
- **Model menu** — a 32-tall trigger (radius 12, py 6 / pr 4 / pl 8) with a turning 18px chevron, opening a 266px panel upward: "Models" radio rows (p 8, radius 10, 14px radio, primary-hover while selected), a full-bleed divider (7 above, 12 below), "Effort *value*" (the value blurs in on change), Faster / Smarter and the six-stop slider. Choosing a model closes it; the slider leaves it open. Omit `models` to hide the button.
|
|
73
|
+
- **Model identity** — `models` takes `{ id, name }` entries, the same contract as `ModelPicker`: the menu keys, matches and reports the **id** and only ever draws the `name`. A bare string is shorthand for `{ id: name, name }`, which is why the old `models={['Fable 5']}` form still behaves exactly as it did — but a routing identifier that is matched by display name is a bug waiting for two models to share a label, so pass ids where you have them.
|
|
74
|
+
- **No effort axis** — `effortLevels={[]}` drops the divider and the whole effort half of the panel. A model with no stops gets the model rows alone, not an empty chip over a slider that cannot commit.
|
|
63
75
|
- **Mic and send** — the panel's own controls, 8 apart after a 6px lead.
|
|
64
76
|
- **`surface={false}`** inside a `ComposerLoader`, which paints the pill surface behind its light.
|
|
65
77
|
- **`glass`** — the add, model and mic controls drop their fill, border and shadow over 480ms onto frosted chips (web: 6px saturated backdrop blur, a 12% / dark 6% white tint, a 155° sheen, a 0.3px conic rim; native: the tint). Pair it with an active loader.
|
|
@@ -90,6 +102,7 @@ Every text run uses the type ramp (`body-medium`, `body-2-medium`, `body-regular
|
|
|
90
102
|
- **Add** — the plus turns 45° while open. Groups of rows (`addMenu`), each with a 20px icon or 24px artwork and an inline muted description. `onAddMenuSelect(rowId)` fires and the menu closes. `addMenu={[]}` hides the button.
|
|
91
103
|
- **Permissions** — a pill that paints only on hover, press or while open; the panel lists `permissions` as radios (`aria-checked`). `onLearnMore` shows "Learn more" in the header.
|
|
92
104
|
- **Model picker** — the rail browses providers (tabs, `aria-selected`) and always reopens on the chosen model's provider, keeping its mark in view. "Quick Search" (or `/` and ⌘K on web) filters every lineup; Escape clears the query, then leaves search. The selected row carries the effort chip, which opens the effort panel. Pass `providers` to `ComposerPanel` to show it.
|
|
105
|
+
- **Effort is a separate axis, and an optional one.** `effortLevels={[]}` drops the chip entirely — models whose deployments cannot agree on a level have no effort to offer, and a chip over an uncommittable slider is a control wired to nothing. `effort={null}` is the other half: **no stop chosen**, the parameter left out, the model deciding. The chip then reads `labels.effortAuto` ("Auto"), the fill is empty, every tick is faded and the thumb is drawn hollow and dashed, so an absent choice never looks like the first stop. The picker reports only stops a reader actually chose — it has no way back to `null`, which stays the caller's to hold.
|
|
93
106
|
|
|
94
107
|
All three panels open upward and hang from the trigger's left edge. On **web** they are `floating/FloatingPanel` on its menu surface — a 150ms fade + scale + blur, non-modal (except the model picker, which catches the outside press so a press inside its separately-portaled effort panel is not read as "outside"). On **native** each is a transparent `Modal` placed against the trigger's `measureInWindow` box (flipped when it does not fit, clamped 8px inside the window), not a bottom sheet: the effort panel opens from inside the picker, and nested `Modal`s stack where sheets do not.
|
|
95
108
|
|
|
@@ -126,7 +139,7 @@ interface ModelPickerProvider {
|
|
|
126
139
|
|
|
127
140
|
## State
|
|
128
141
|
|
|
129
|
-
Everything is controllable and works uncontrolled: `value`/`defaultValue`, `permission`/`defaultPermission`, `model`/`defaultModel`, `effort`/`defaultEffort`, `listening`/`defaultListening`. Enter submits on web (Shift+Enter breaks the line; IME composition is respected)
|
|
142
|
+
Everything is controllable and works uncontrolled: `value`/`defaultValue`, `permission`/`defaultPermission`, `model`/`defaultModel`, `effort`/`defaultEffort`, `listening`/`defaultListening`. Enter submits on web (Shift+Enter breaks the line; IME composition is respected) unless `busy`; on native the send button submits. An uncontrolled draft clears after submit.
|
|
130
143
|
|
|
131
144
|
## Colours
|
|
132
145
|
|
|
@@ -155,7 +168,7 @@ State colours 150ms; chevrons and the plus 200ms; mic ↔ equalizer crossfade 28
|
|
|
155
168
|
|
|
156
169
|
## Accessibility
|
|
157
170
|
|
|
158
|
-
Every control is named: the triggers announce their current value ("Permission: Auto", "Models: GPT-5.6 Mini", "Effort: Medium") with `aria-expanded`; permission modes and model rows are radios (`aria-checked` + `accessibilityState`); rail marks are tabs; the mic is a toggle (`aria-pressed`); send carries `aria-disabled`; the effort slider is `adjustable` with flat `aria-value*`, `aria-valuetext` (the level name), increment/decrement actions, and arrow/Home/End keys on web.
|
|
171
|
+
Every control is named: the triggers announce their current value ("Permission: Auto", "Models: GPT-5.6 Mini", "Effort: Medium", "Effort: Auto" with no stop chosen) with `aria-expanded`; stop is named "Stop generating" and carries no `aria-disabled`; permission modes and model rows are radios (`aria-checked` + `accessibilityState`); rail marks are tabs; the mic is a toggle (`aria-pressed`); send carries `aria-disabled`; the effort slider is `adjustable` with flat `aria-value*`, `aria-valuetext` (the level name, or the "Auto" label with no stop chosen — where `aria-valuenow` is omitted rather than guessed), increment/decrement actions, and arrow/Home/End keys on web.
|
|
159
172
|
|
|
160
173
|
## Props
|
|
161
174
|
|
|
@@ -163,7 +176,8 @@ Every control is named: the triggers announce their current value ("Permission:
|
|
|
163
176
|
|------|------|---------|-------------|
|
|
164
177
|
| `value` / `defaultValue` / `onValueChange` | `string` | `''` | The draft. |
|
|
165
178
|
| `onSubmit` | `(value) => void` | — | Send button and Enter (web). |
|
|
166
|
-
| `
|
|
179
|
+
| `onStop` / `busy` | `() => void` / `boolean` | — / `false` | Send becomes stop, outside `disabled`'s reach. No handler, no stop. |
|
|
180
|
+
| `disabled` | `boolean` | `false` | Greys send. Never the stop control. |
|
|
167
181
|
| `placeholder` | `string` | `'Hi, what do you need today?'` | |
|
|
168
182
|
| `permissions` | `ComposerPanelPermissionOption[]` | the default four | `[]` hides the picker. |
|
|
169
183
|
| `permission` / `defaultPermission` / `onPermissionChange` | `string` | first mode | |
|
|
@@ -171,8 +185,9 @@ Every control is named: the triggers announce their current value ("Permission:
|
|
|
171
185
|
| `addMenu` / `onAddMenuSelect` | `ComposerPanelAddMenuGroup[]` / `(id) => void` | Add + Plugins | `[]` hides the button. |
|
|
172
186
|
| `providers` | `ModelPickerProvider[]` | — | Shows the model picker. |
|
|
173
187
|
| `model` / `defaultModel` / `onModelChange` | `string` | first model | |
|
|
174
|
-
| `effort` / `defaultEffort`
|
|
175
|
-
| `
|
|
188
|
+
| `effort` / `defaultEffort` | `number \| null` | `1` | Index into `effortLevels`; `null` is "no stop chosen". |
|
|
189
|
+
| `onEffortChange` | `(effort: number) => void` | — | Only ever a stop a reader chose. |
|
|
190
|
+
| `effortLevels` | `string[]` | the default six | `[]` drops the effort axis. |
|
|
176
191
|
| `listening` / `defaultListening` / `onListeningChange` | `boolean` | `false` | Voice input toggle. |
|
|
177
192
|
| `attachments` / `onRemoveAttachment` | `ComposerPanelAttachment[]` / `(id) => void` | — | |
|
|
178
193
|
| `status` | `ReactNode` | — | Usually `<ComposerPanelStatusTab />`. |
|
|
@@ -180,4 +195,6 @@ Every control is named: the triggers announce their current value ("Permission:
|
|
|
180
195
|
| `labels`, `modelPickerLabels` | objects | English | Localisation. |
|
|
181
196
|
| `style`, `testID` | | | |
|
|
182
197
|
|
|
198
|
+
`ComposerPill` takes the same draft, add-menu, voice and `onStop` / `busy` props, plus `models` (`string | { id, name }` entries), `model` / `defaultModel` / `onModelChange` (by **id**), the effort trio, `effortLevels`, `compactPlaceholder`, `surface` and `glass`.
|
|
199
|
+
|
|
183
200
|
`ComposerAttachments` takes the same props plus `attachments` (required), `uploadDuration` (1100), `uploadGap` (240), `onAttachmentsChange`, `onUploadComplete`, `onAllUploaded`. `ModelPicker` takes `providers`, `value`/`defaultValue`/`onValueChange`, the effort trio, `effortLevels`, `labels`, `style`, `testID`.
|
|
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.AiChatContainer = AiChatContainer;
|
|
7
|
-
exports.AiChatThread =
|
|
7
|
+
exports.AiChatThread = void 0;
|
|
8
8
|
var _react = _interopRequireWildcard(require("react"));
|
|
9
9
|
var _reactNative = require("react-native");
|
|
10
10
|
var _index = require("../agent-thinking/index.js");
|
|
@@ -26,6 +26,9 @@ const DEFAULT_LABELS = {
|
|
|
26
26
|
* The AI chat's centre column: the centre column of the AI chat template.
|
|
27
27
|
*
|
|
28
28
|
* section fills its parent, radius 24, background-secondary, clipped
|
|
29
|
+
* layer the `background` slot, filling the section above its own paint
|
|
30
|
+
* and below everything else, clipped to the same radius;
|
|
31
|
+
* `surface={false}` drops that paint
|
|
29
32
|
* header px 16 / pt 16, gap 8: the project › chat breadcrumb (flex 1) and
|
|
30
33
|
* the 16px share / more glyphs, 8 apart
|
|
31
34
|
* thread the `children` — usually an `AiChatThread`
|
|
@@ -33,6 +36,9 @@ const DEFAULT_LABELS = {
|
|
|
33
36
|
* while `working`, then the `composer`
|
|
34
37
|
*
|
|
35
38
|
* `header` renders above all of it: the shell's `AiChatMobileHeader`.
|
|
39
|
+
*
|
|
40
|
+
* `project` is optional: without one the breadcrumb is the chat's own crumb
|
|
41
|
+
* alone, since a chat that belongs to nothing must not have to invent a folder.
|
|
36
42
|
*/
|
|
37
43
|
function AiChatContainer({
|
|
38
44
|
project,
|
|
@@ -46,6 +52,8 @@ function AiChatContainer({
|
|
|
46
52
|
composer,
|
|
47
53
|
working = false,
|
|
48
54
|
workingLabel,
|
|
55
|
+
background,
|
|
56
|
+
surface = true,
|
|
49
57
|
labels,
|
|
50
58
|
style,
|
|
51
59
|
testID
|
|
@@ -65,9 +73,19 @@ function AiChatContainer({
|
|
|
65
73
|
flexDirection: 'column',
|
|
66
74
|
overflow: 'hidden',
|
|
67
75
|
borderRadius: _shared.CONTAINER_RADIUS,
|
|
68
|
-
backgroundColor: palette.secondary
|
|
76
|
+
backgroundColor: surface ? palette.secondary : 'transparent'
|
|
69
77
|
}, style],
|
|
70
|
-
children: [
|
|
78
|
+
children: [background ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
79
|
+
pointerEvents: "none",
|
|
80
|
+
style: {
|
|
81
|
+
position: 'absolute',
|
|
82
|
+
top: 0,
|
|
83
|
+
left: 0,
|
|
84
|
+
right: 0,
|
|
85
|
+
bottom: 0
|
|
86
|
+
},
|
|
87
|
+
children: background
|
|
88
|
+
}) : null, header, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
71
89
|
style: {
|
|
72
90
|
width: '100%',
|
|
73
91
|
flexDirection: 'row',
|
|
@@ -85,7 +103,7 @@ function AiChatContainer({
|
|
|
85
103
|
},
|
|
86
104
|
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_index2.Breadcrumb, {
|
|
87
105
|
accessibilityLabel: l.breadcrumb,
|
|
88
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.BreadcrumbItem, {
|
|
106
|
+
children: [project === undefined ? null : /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.BreadcrumbItem, {
|
|
89
107
|
icon: projectIcon,
|
|
90
108
|
onPress: onProjectPress,
|
|
91
109
|
children: project
|
|
@@ -138,29 +156,119 @@ function AiChatContainer({
|
|
|
138
156
|
/**
|
|
139
157
|
* The thread: the scrolling conversation, anchored to the bottom
|
|
140
158
|
* so a short exchange sits just above the composer and grows upward — px 16 /
|
|
141
|
-
* pt 16, turns 12 apart, a thin scrollbar.
|
|
142
|
-
*
|
|
159
|
+
* pt 16, turns 12 apart, a thin scrollbar.
|
|
160
|
+
*
|
|
161
|
+
* It follows the newest turn with a smooth scroll whenever the content grows.
|
|
162
|
+
* That is the default and nothing else changes it; the timeline props are the
|
|
163
|
+
* ones a host with history needs and are each off unless asked for:
|
|
164
|
+
*
|
|
165
|
+
* autoFollow / followAnimated / followThreshold whether, how, and from how
|
|
166
|
+
* close to the bottom the follow happens
|
|
167
|
+
* onStartReached a chat pages UPWARD; this
|
|
168
|
+
* fires near the TOP, once per approach, re-arming when the reader leaves
|
|
169
|
+
* maintainStartPosition the growth that answers an
|
|
170
|
+
* `onStartReached` is added to the offset, so a page landing above the
|
|
171
|
+
* reader does not move the turn they are reading
|
|
172
|
+
* ref (`AiChatThreadHandle`) scrollToEnd /
|
|
173
|
+
* scrollToOffset / the ScrollView, for a cursor jump or a restore
|
|
174
|
+
*
|
|
175
|
+
* There is no virtualization and no `scrollToIndex`: a host jumping to a turn
|
|
176
|
+
* measures its row (`onLayout`) and calls `scrollToOffset`.
|
|
143
177
|
*/
|
|
144
|
-
function AiChatThread({
|
|
178
|
+
const AiChatThread = exports.AiChatThread = /*#__PURE__*/(0, _react.forwardRef)(function AiChatThread({
|
|
145
179
|
children,
|
|
180
|
+
autoFollow = true,
|
|
181
|
+
followAnimated = true,
|
|
182
|
+
followThreshold,
|
|
183
|
+
onStartReached,
|
|
184
|
+
onStartReachedThreshold = 300,
|
|
185
|
+
maintainStartPosition = false,
|
|
186
|
+
onScroll,
|
|
187
|
+
scrollEventThrottle = 16,
|
|
146
188
|
style,
|
|
147
189
|
testID
|
|
148
|
-
}) {
|
|
190
|
+
}, ref) {
|
|
149
191
|
const scrollRef = (0, _react.useRef)(null);
|
|
150
192
|
const lastHeight = (0, _react.useRef)(0);
|
|
193
|
+
const offset = (0, _react.useRef)(0);
|
|
194
|
+
const viewport = (0, _react.useRef)(0);
|
|
195
|
+
/** An `onStartReached` is out and its page has not landed yet. */
|
|
196
|
+
const pageOut = (0, _react.useRef)(false);
|
|
197
|
+
/** The reader has left the top zone, so the next approach may fire again. */
|
|
198
|
+
const startArmed = (0, _react.useRef)(true);
|
|
199
|
+
(0, _react.useImperativeHandle)(ref, () => ({
|
|
200
|
+
scrollToEnd: options => scrollRef.current?.scrollToEnd({
|
|
201
|
+
animated: options?.animated ?? true
|
|
202
|
+
}),
|
|
203
|
+
scrollToOffset: ({
|
|
204
|
+
offset: y,
|
|
205
|
+
animated = false
|
|
206
|
+
}) => scrollRef.current?.scrollTo({
|
|
207
|
+
y,
|
|
208
|
+
animated
|
|
209
|
+
}),
|
|
210
|
+
getScrollView: () => scrollRef.current
|
|
211
|
+
}), []);
|
|
212
|
+
const listening = !!onScroll || !!onStartReached || followThreshold !== undefined || maintainStartPosition;
|
|
213
|
+
const handleScroll = (0, _react.useCallback)(event => {
|
|
214
|
+
const {
|
|
215
|
+
contentOffset,
|
|
216
|
+
layoutMeasurement
|
|
217
|
+
} = event.nativeEvent;
|
|
218
|
+
offset.current = contentOffset.y;
|
|
219
|
+
viewport.current = layoutMeasurement.height;
|
|
220
|
+
if (onStartReached) {
|
|
221
|
+
if (contentOffset.y <= onStartReachedThreshold) {
|
|
222
|
+
if (startArmed.current) {
|
|
223
|
+
startArmed.current = false;
|
|
224
|
+
pageOut.current = true;
|
|
225
|
+
onStartReached();
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
startArmed.current = true;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
onScroll?.(event);
|
|
232
|
+
}, [onScroll, onStartReached, onStartReachedThreshold]);
|
|
233
|
+
const onLayout = (0, _react.useCallback)(event => {
|
|
234
|
+
viewport.current = event.nativeEvent.layout.height;
|
|
235
|
+
}, []);
|
|
151
236
|
const onContentSizeChange = (0, _react.useCallback)((_width, height) => {
|
|
152
|
-
const
|
|
153
|
-
const
|
|
237
|
+
const previous = lastHeight.current;
|
|
238
|
+
const grew = height > previous + 0.5;
|
|
239
|
+
const first = previous === 0;
|
|
154
240
|
lastHeight.current = height;
|
|
155
|
-
if (grew
|
|
156
|
-
|
|
241
|
+
if (!grew || first) return;
|
|
242
|
+
|
|
243
|
+
// A page landing above the reader: keep the turn they are on where it is.
|
|
244
|
+
if (maintainStartPosition && pageOut.current) {
|
|
245
|
+
pageOut.current = false;
|
|
246
|
+
scrollRef.current?.scrollTo({
|
|
247
|
+
y: offset.current + (height - previous),
|
|
248
|
+
animated: false
|
|
249
|
+
});
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
pageOut.current = false;
|
|
253
|
+
if (!autoFollow) return;
|
|
254
|
+
if (followThreshold !== undefined) {
|
|
255
|
+
// Measured against the height BEFORE the growth: was the reader near
|
|
256
|
+
// the bottom when this arrived?
|
|
257
|
+
const fromBottom = previous - offset.current - viewport.current;
|
|
258
|
+
if (fromBottom > followThreshold) return;
|
|
259
|
+
}
|
|
260
|
+
scrollRef.current?.scrollToEnd({
|
|
261
|
+
animated: followAnimated
|
|
157
262
|
});
|
|
158
|
-
}, []);
|
|
263
|
+
}, [autoFollow, followAnimated, followThreshold, maintainStartPosition]);
|
|
159
264
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
160
265
|
ref: scrollRef,
|
|
161
266
|
testID: testID,
|
|
162
267
|
...(0, _shared.dataHook)('bloomAiChatScroll', 'thin'),
|
|
163
268
|
onContentSizeChange: onContentSizeChange,
|
|
269
|
+
onLayout: onLayout,
|
|
270
|
+
onScroll: listening ? handleScroll : undefined,
|
|
271
|
+
scrollEventThrottle: listening ? scrollEventThrottle : undefined,
|
|
164
272
|
style: [{
|
|
165
273
|
minHeight: 0,
|
|
166
274
|
width: '100%',
|
|
@@ -185,5 +293,5 @@ function AiChatThread({
|
|
|
185
293
|
},
|
|
186
294
|
children: children
|
|
187
295
|
});
|
|
188
|
-
}
|
|
296
|
+
});
|
|
189
297
|
//# sourceMappingURL=AiChatContainer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_index","_index2","_RiFolderLine","_RiMoreFill","_RiShare2Line","_AiChatControls","_shared","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","DEFAULT_LABELS","breadcrumb","share","more","AiChatContainer","project","title","projectIcon","RiFolderLine","onProjectPress","onShare","onMore","header","children","composer","working","workingLabel","labels","style","testID","useAiChatWebCss","palette","useAiChatPalette","l","useMemo","jsxs","View","height","minWidth","flex","flexDirection","overflow","borderRadius","CONTAINER_RADIUS","backgroundColor","secondary","width","alignItems","justifyContent","gap","paddingLeft","paddingRight","paddingTop","
|
|
1
|
+
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_index","_index2","_RiFolderLine","_RiMoreFill","_RiShare2Line","_AiChatControls","_shared","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","DEFAULT_LABELS","breadcrumb","share","more","AiChatContainer","project","title","projectIcon","RiFolderLine","onProjectPress","onShare","onMore","header","children","composer","working","workingLabel","background","surface","labels","style","testID","useAiChatWebCss","palette","useAiChatPalette","l","useMemo","jsxs","View","height","minWidth","flex","flexDirection","overflow","borderRadius","CONTAINER_RADIUS","backgroundColor","secondary","jsx","pointerEvents","position","top","left","right","bottom","width","alignItems","justifyContent","gap","paddingLeft","paddingRight","paddingTop","Breadcrumb","accessibilityLabel","undefined","BreadcrumbItem","icon","onPress","current","flexShrink","GlyphAction","RiShare2Line","label","RiMoreFill","paddingBottom","AgentThinking","variant","AiChatThread","exports","forwardRef","autoFollow","followAnimated","followThreshold","onStartReached","onStartReachedThreshold","maintainStartPosition","onScroll","scrollEventThrottle","ref","scrollRef","useRef","lastHeight","offset","viewport","pageOut","startArmed","useImperativeHandle","scrollToEnd","options","animated","scrollToOffset","y","scrollTo","getScrollView","listening","handleScroll","useCallback","event","contentOffset","layoutMeasurement","nativeEvent","onLayout","layout","onContentSizeChange","_width","previous","grew","first","fromBottom","ScrollView","dataHook","minHeight","contentContainerStyle","IS_WEB","flexGrow"],"sourceRoot":"../../../src","sources":["ai-chat/AiChatContainer.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -24,6 +24,8 @@ const REVEAL_MS = 325;
|
|
|
24
24
|
const REVEAL_OFFSET = 272;
|
|
25
25
|
const DRAWER_EASE = _reactNativeReanimated.Easing.bezier(0.4, 0, 0.2, 1);
|
|
26
26
|
const DRAWER_MS = 300;
|
|
27
|
+
/** An icon-only rail: one 36px control plus its 10px gutters. */
|
|
28
|
+
const COLLAPSED_SIDEBAR_WIDTH = 56;
|
|
27
29
|
const DEFAULT_LABELS = {
|
|
28
30
|
openNavigation: 'Open navigation',
|
|
29
31
|
closeNavigation: 'Close navigation',
|
|
@@ -255,7 +257,9 @@ function AiChatMobileHeader({
|
|
|
255
257
|
* The AI chat shell: the full AI chat
|
|
256
258
|
* screen on background-full with a 12px frame.
|
|
257
259
|
*
|
|
258
|
-
* lg and up the floating `sidebar` in flow, 16 from the workspace
|
|
260
|
+
* lg and up the floating `sidebar` in flow, 16 from the workspace, at
|
|
261
|
+
* `sidebarWidth` (or its own width) — `sidebarCollapsed` narrows
|
|
262
|
+
* the column to `collapsedSidebarWidth` (56) for an icon rail
|
|
259
263
|
* workspace the chat container flexing, then (xl and up) the `panel` 12 to
|
|
260
264
|
* its right at `panelWidth`; the resize grip on the chat's right
|
|
261
265
|
* edge trades width between them, clamped 320–560
|
|
@@ -270,9 +274,19 @@ function AiChatMobileHeader({
|
|
|
270
274
|
* over the workspace closes it
|
|
271
275
|
*
|
|
272
276
|
* The chat container reads the drawer controls through `AiChatMobileHeader`.
|
|
277
|
+
* Everything the shell knows — including `navPresented`, which says whether the
|
|
278
|
+
* nav is on screen at all — is published to any descendant through
|
|
279
|
+
* `useAiChatShell()`.
|
|
280
|
+
*
|
|
281
|
+
* `background` is a layer drawn above the shell's own paint and below all of
|
|
282
|
+
* it; with `surface={false}` neither the root nor the workspace paints, so the
|
|
283
|
+
* layer reaches the chat.
|
|
273
284
|
*/
|
|
274
285
|
function AiChatShell({
|
|
275
286
|
sidebar,
|
|
287
|
+
sidebarWidth,
|
|
288
|
+
sidebarCollapsed = false,
|
|
289
|
+
collapsedSidebarWidth = COLLAPSED_SIDEBAR_WIDTH,
|
|
276
290
|
mobileSidebar,
|
|
277
291
|
children,
|
|
278
292
|
panel,
|
|
@@ -285,6 +299,8 @@ function AiChatShell({
|
|
|
285
299
|
onNavOpenChange,
|
|
286
300
|
panelOpen: panelOpenProp,
|
|
287
301
|
onPanelOpenChange,
|
|
302
|
+
background,
|
|
303
|
+
surface = true,
|
|
288
304
|
labels,
|
|
289
305
|
style,
|
|
290
306
|
testID
|
|
@@ -375,10 +391,14 @@ function AiChatShell({
|
|
|
375
391
|
navCollapsed: !navInFlow,
|
|
376
392
|
hasNav: !!mobileSidebar,
|
|
377
393
|
hasPanel: !!panel,
|
|
394
|
+
// In flow it is always on screen; as a drawer, only while it is open.
|
|
395
|
+
navPresented: navInFlow ? !!sidebar : navOpen,
|
|
396
|
+
sidebarCollapsed,
|
|
378
397
|
openNav: () => {
|
|
379
398
|
setPanelOpen(false);
|
|
380
399
|
setNavOpen(true);
|
|
381
400
|
},
|
|
401
|
+
closeNav: () => setNavOpen(false),
|
|
382
402
|
openPanel: () => {
|
|
383
403
|
setNavOpen(false);
|
|
384
404
|
setPanelOpen(true);
|
|
@@ -389,7 +409,7 @@ function AiChatShell({
|
|
|
389
409
|
openNavigation: l.openNavigation,
|
|
390
410
|
openPanel: l.openPanel
|
|
391
411
|
}
|
|
392
|
-
}), [wide, navInFlow, mobileSidebar, panel, setPanelOpen, setNavOpen, panelLabel, panelIcon, l]);
|
|
412
|
+
}), [wide, navInFlow, navOpen, sidebar, sidebarCollapsed, mobileSidebar, panel, setPanelOpen, setNavOpen, panelLabel, panelIcon, l]);
|
|
393
413
|
const rootStyle = {
|
|
394
414
|
position: 'relative',
|
|
395
415
|
width: '100%',
|
|
@@ -398,7 +418,7 @@ function AiChatShell({
|
|
|
398
418
|
gap: 16,
|
|
399
419
|
overflow: 'hidden',
|
|
400
420
|
padding: 12,
|
|
401
|
-
backgroundColor: palette.full
|
|
421
|
+
backgroundColor: surface ? palette.full : 'transparent'
|
|
402
422
|
};
|
|
403
423
|
const veil = palette.isDark ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.1)';
|
|
404
424
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_context.AiChatShellContext.Provider, {
|
|
@@ -408,7 +428,17 @@ function AiChatShell({
|
|
|
408
428
|
testID: testID,
|
|
409
429
|
onLayout: event => setShellWidth(event.nativeEvent.layout.width),
|
|
410
430
|
style: [rootStyle, style],
|
|
411
|
-
children: [
|
|
431
|
+
children: [background ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
432
|
+
pointerEvents: "none",
|
|
433
|
+
style: {
|
|
434
|
+
position: 'absolute',
|
|
435
|
+
top: 0,
|
|
436
|
+
left: 0,
|
|
437
|
+
right: 0,
|
|
438
|
+
bottom: 0
|
|
439
|
+
},
|
|
440
|
+
children: background
|
|
441
|
+
}) : null, !navInFlow && mobileSidebar ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
412
442
|
"aria-hidden": !navOpen,
|
|
413
443
|
pointerEvents: navOpen ? 'box-none' : 'none',
|
|
414
444
|
style: {
|
|
@@ -435,7 +465,17 @@ function AiChatShell({
|
|
|
435
465
|
style: {
|
|
436
466
|
position: 'relative',
|
|
437
467
|
zIndex: 10,
|
|
438
|
-
height: '100%'
|
|
468
|
+
height: '100%',
|
|
469
|
+
// Untouched unless the host asked for a width: the sidebar has
|
|
470
|
+
// always sized itself.
|
|
471
|
+
...(sidebarCollapsed ? {
|
|
472
|
+
width: collapsedSidebarWidth,
|
|
473
|
+
flexShrink: 0,
|
|
474
|
+
overflow: 'hidden'
|
|
475
|
+
} : sidebarWidth !== undefined ? {
|
|
476
|
+
width: sidebarWidth,
|
|
477
|
+
flexShrink: 0
|
|
478
|
+
} : null)
|
|
439
479
|
},
|
|
440
480
|
children: sidebar
|
|
441
481
|
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, {
|
|
@@ -447,7 +487,7 @@ function AiChatShell({
|
|
|
447
487
|
flexDirection: 'column',
|
|
448
488
|
gap: 8,
|
|
449
489
|
overflow: 'hidden',
|
|
450
|
-
backgroundColor: palette.full
|
|
490
|
+
backgroundColor: surface ? palette.full : 'transparent'
|
|
451
491
|
}, navInFlow ? null : workspaceStyle],
|
|
452
492
|
children: [!navInFlow ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
|
|
453
493
|
pointerEvents: navOpen ? 'auto' : 'none',
|