Skip to content

Commit 50bbd5c

Browse files
authored
docs(examples): add the UI Cookbook app for the React v15 docs (#3354)
1 parent c2cb10b commit 50bbd5c

480 files changed

Lines changed: 24479 additions & 3 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
---
2+
name: react-ui-cookbook
3+
description:
4+
Write, port or update a recipe for the React v15 docs' UI Cookbooks group, with its code in
5+
examples/cookbook and its screenshots taken from that app. Use whenever a task adds or changes a
6+
UI Cookbook recipe, its CSS, its screenshots, the cookbook base app or its demo data, so every
7+
recipe keeps the same look, cast and style rules.
8+
---
9+
10+
# React UI Cookbook
11+
12+
Every UI Cookbook recipe in the React v15 docs (getstream.io, `content/docs/chat-sdk/react/v15-rc/`,
13+
URLs under `/chat/docs/sdk/react/v15/ui-cookbooks/`) is built on one base app, follows one set of
14+
style rules, and is shot in one frame with one cast. The runnable code lives here, in
15+
`examples/cookbook`; the docs page shows that same code.
16+
17+
## The look
18+
19+
- **Theme:** the SDK's default theme, light, with its default font: the base app loads no theme
20+
of its own, and no recipe changes the theme's colors or font. A recipe's CSS reads the
21+
`--str-chat__*` tokens, so it follows whatever theme an app sets. Only the Theming recipe
22+
(`src/recipes/theming/brand-theme-and-dark-mode/`, shown on the UI › Customizing › Theming docs
23+
page) loads a theme, from its own variants: `theme.css`, a green brand theme in light and dark
24+
(an emerald accent, `#047857`, on green-tinted surfaces, solid green own bubbles, `#1a7f5a`,
25+
with white text, links and mentions, rounder bubbles and cards, Plus Jakarta Sans and soft
26+
green-tinted shadows), with every text at 4.5:1 and every border and icon at 3:1. Change the
27+
green theme only there. Recipes are shot in the light theme; only the Theming recipe adds dark
28+
shots.
29+
- **Frame:** desktop 1280×720 at DPR 1 (`ChatView`: channel list 320px on the left, the open
30+
channel in the middle, a thread on the right when the recipe needs one). Phone recipes use
31+
390×844. A shot with four columns (the channel list, the channel, a thread and the side panel)
32+
uses 1600×720, set with `viewport` in `shots.json`, so the messages don't wrap word by word.
33+
Crop each shot to the part the recipe changes, with a little surrounding UI.
34+
- **Side panel:** a recipe that shows content in a panel (details, a profile, info) opens it in the
35+
base app's one side panel (`src/base/SidePanel.tsx`), never in a column of its own: call
36+
`useSidePanel().openSidePanel({ id, title, body })`, or `openUserProfile(userId)` for a profile
37+
(the SDK's `userProfile` kind). The panel draws the title row and the close button; the recipe
38+
writes only the body, and its section headings use `.cookbook-side-panel__section-title`. It
39+
never closes the thread, and opening another panel replaces it: other content and closing
40+
release the whole slot, so an earlier profile never comes back. The panel moves focus to its
41+
title on open and gives it back to the opener on close; a recipe adds no focus code of its own.
42+
A recipe that only changes the message lists or the profile body sets `RecipeConfig.MessageList`
43+
and `RecipeConfig.sidePanelProps` instead of copying the channels view. The Overview shows its
44+
code. Likewise, a recipe that adds a view beside the channels view sets `RecipeConfig.views` and
45+
`RecipeConfig.layouts`, which the base app merges into its own.
46+
- **Cast:** the user is Cleo; the others are Bob, Cara and Dan, with initials avatars and no
47+
photos. Channels: Design review, Weekend trip, Book club, Garden plot, General, and a direct
48+
message with Bob. All of it, with fixed message ids, is in `src/base/demo-data.json`.
49+
- **Content:** neutral and friendly. The demo app is public: no real names, no personal data, no
50+
brand names.
51+
- **Read-only:** the base app blocks every request that would change the demo app (sending,
52+
editing, deleting, reacting, flagging, pinning, adding members, updating channels); a blocked
53+
request never settles, so the UI keeps its optimistic state. Reads, watching, typing and the
54+
user's read state still go through (`src/base/readOnly.ts`). Data changes go in the seed, never
55+
in a recipe or a screenshot helper. `?writes=on` turns the guard off, for an app of your own.
56+
57+
## Style rules
58+
59+
`yarn workspace @stream-io/stream-chat-react-cookbook check:css` checks them: literal colors and
60+
sizes, `var()`s of anything but `--str-chat__` tokens, CSS outside the layer, the prefix, and
61+
layout properties on SDK markup. It can't judge whether a token fits; review that by eye.
62+
63+
1. **Tokens only.** Colors, spacing, radii and type come from `--str-chat__*` semantic tokens:
64+
`--str-chat__text-secondary`, `--str-chat__spacing-sm`, `--str-chat__radius-md`,
65+
`--str-chat__font-caption-default`, … No hex, `rgb()` or named colors, and no `px`/`rem`/`em`
66+
for spacing, radius or type. Border widths and component sizes may use `px`.
67+
2. **One layer.** All recipe CSS sits inside `@layer stream-overrides { … }`. The base app declares
68+
the order (`modern-normalize, stream, cookbook-base, stream-overrides`), so a recipe never needs
69+
extra specificity or `!important`.
70+
3. **One prefix.** Classes a recipe adds start with `.cookbook-`, followed by a name for the part it
71+
styles, for example `.cookbook-pinned-by` or `.cookbook-channel-header__title`. SDK classes
72+
(`.str-chat…`) may be restyled.
73+
4. **Leave the SDK's layout alone.** Never set `display`, `position`, `flex`, `grid`, `float` or
74+
offsets on a rule whose target is an SDK class. Style your own markup, or change sizes through a
75+
component variable such as `--str-chat__channel-list-width`. Where the SDK has no variable, a
76+
comment starting `cookbook-allow-layout:` right above the rule says why; known case: the width of
77+
a thread beside a channel.
78+
79+
## Where things are
80+
81+
```
82+
examples/cookbook/
83+
src/base/ the base app (BaseApp.tsx), its CSS (base.css), client, demo data,
84+
the side panel (SidePanel.tsx), URL helpers (OpenFromUrl:
85+
?channel=&thread=; Simulate: ?simulate=typing&as=), and useClickInTurn
86+
for screenshot helpers that click through the UI
87+
src/recipes/<group>/<recipe>/
88+
*.tsx, *.css exactly the recipe code the docs page shows
89+
<variant>.recipe.tsx default-exports a RecipeConfig: what the recipe changes in the base app
90+
(ChannelHeader, Wrapper, MessageList, sidePanelProps, views and
91+
layouts merged into the base ones, chatViewProps, chatViewChildren,
92+
chatProps, openFromUrl, theme), and imports the recipe's CSS
93+
shots.json the screenshots the docs page uses (format in scripts/shots.mjs)
94+
demo-data.json optional: users and channels only this recipe needs, in the shape of
95+
src/base/demo-data.json; seeded and listed with the base data
96+
scripts/seed.mjs creates or brings back in line the cast, channels, members, messages,
97+
replies and pins; sets a message's attachments, mentions and custom
98+
fields after sending it; sets read state and receipts, invites, and
99+
the current user's pinned and archived channels, reminders, saved
100+
messages and drafts (fields below)
101+
scripts/shots.mjs takes every shot from the running app, writes them to --out
102+
scripts/check-css.mjs the style-rule check
103+
```
104+
105+
`<group>` and `<recipe>` match the docs URL: `/ui-cookbooks/headers/channel-header/` lives in
106+
`src/recipes/headers/channel-header/`. A page with two solutions has two variants, for example
107+
`css.recipe.tsx` and `component.recipe.tsx`. Open a recipe at
108+
`http://localhost:5176/?recipe=<group>/<recipe>&variant=<variant>`; `?recipe=base` is the base app
109+
alone, and no parameter lists every recipe.
110+
111+
## Adding or changing a recipe
112+
113+
1. Read the recipe's docs page and the pages it links to. Check every API it uses against
114+
`stream-chat-react` `master` and `stream-chat` `release-v10`.
115+
2. Write the code in `src/recipes/<group>/<recipe>/`, following the style rules, and add a
116+
`<variant>.recipe.tsx` for each solution the page shows.
117+
3. Put demo data only this recipe needs in its folder's `demo-data.json` (new ids, never reuse
118+
one); change `src/base/demo-data.json` only for data every recipe shares. A message may carry
119+
`attachments` (for example image URLs), which the seed sets after sending, since the demo app
120+
refuses uploads. A message may also carry `mentions` (user ids), which the seed sets after
121+
sending, since the demo app refuses a new message with mentions. A message may carry
122+
`reminderForCurrentUser`: `{}` saves it for later, `{ "remindAt": "<ISO time>" }` sets a fixed
123+
time, and `{ "remindAt": "+2m" }` a time that many minutes after the first seed, kept on later
124+
runs, which is how a reminder becomes overdue (the server refuses a past time). The demo app's
125+
`messaging` type has `user_message_reminders` off, so the seed reports that and skips them.
126+
A channel may carry `draftsForCurrentUser`, the current user's unsent drafts in it:
127+
`{ "text" }` for the channel's draft, with `"parent"` (a main-list message id) for a thread's
128+
draft and `"quotedMessage"` (a message id) for one that quotes a message. Each is saved only
129+
when the stored draft differs. A draft in a base channel would show in that channel's composer
130+
in every recipe, so drafts go in the recipe's own channels.
131+
132+
The other fields the seed reads, each set only when the stored state differs:
133+
- On a channel: `createdBy` (a cast id that creates it instead of the current user),
134+
`invites` (user ids left with a pending invite), `readBy` and `deliveredTo` (members who
135+
read or received its last message), `readUpTo` (`{ userId: messageId }`), `unreadFrom` (a
136+
message id the current user's unread state starts at), and `pinnedForCurrentUser` /
137+
`archivedForCurrentUser`.
138+
- On a message: `pinnedBy` (its author), `custom` (custom fields, such as
139+
`{ "ai_generated": true }`), and `unreadForCurrentUser` on a thread's parent (its latest
140+
reply stays unread).
141+
142+
Then run `seed`.
143+
4. List the shots in `shots.json`, reusing the file names the page already references.
144+
5. Run the checks and the shots (below), then look at every image.
145+
6. Copy the code into the docs page so it matches the folder exactly, formatted with the docs'
146+
prettier (double quotes, width 80), apart from the base app's setup, which the page either
147+
shows in full or links to on the UI Cookbooks Overview. Two rewrites are allowed: imports of
148+
the shared side panel and the New conversation dialog become `./SidePanel` and
149+
`./NewConversationDialog`, and the `cookbook-allow-layout:` prefix is dropped from comments.
150+
7. In getstream.io, copy the images into `content/docs/chat-sdk/react/v15-rc/_assets/` and run the
151+
docs checks.
152+
153+
## The docs page
154+
155+
Every recipe page has the same shape:
156+
157+
1. What it builds: a one- or two-sentence intro, then the features in a note: `<Admonition
158+
type="note">` that opens with the bold line **What you'll build**, then one short bullet per
159+
feature: what the user gets, never how it's done (the steps cover that). Then the main
160+
screenshot, captioned **Preview**.
161+
2. Which level it uses, from the Overview's order: restyle, replace one part, compose, or change
162+
behaviour in the state layer.
163+
3. "Start from the base app", linking to the Overview.
164+
4. The steps, each with its code and, where it helps, a screenshot next to it.
165+
5. How it works.
166+
6. Related pages.
167+
168+
Every screenshot has a caption: a short bold line right above the image, **Preview** for the main
169+
one and a few words saying what the image shows for the others (the site can't render image
170+
captions below an image). Each captioned screenshot sits between two horizontal rules (`---`, with a
171+
blank line on each side); consecutive screenshots share the rule between them.
172+
173+
A component and its stylesheet go in one `<Tabs>` block, the code tab labelled `React` and the
174+
stylesheet tab `CSS`, each with its `title` (see the Pin indicator page).
175+
176+
Docs prose follows the v15 docs conventions: name methods as `Class.method()`, name every field with
177+
its owner (`channel.state.typing`), say "paginator" for the object that loads items, and put example
178+
scenarios in an `<Admonition type="info">` titled **Example**.
179+
180+
## Commands
181+
182+
Run from the repository root unless noted.
183+
184+
```bash
185+
yarn install && yarn build # the SDK the cookbook imports
186+
yarn workspace @stream-io/stream-chat-react-cookbook seed # demo data (idempotent)
187+
yarn workspace @stream-io/stream-chat-react-cookbook dev # http://localhost:5176
188+
yarn workspace @stream-io/stream-chat-react-cookbook shots --out <getstream.io>/content/docs/chat-sdk/react/v15-rc/_assets [--only <group>/<recipe>]
189+
yarn workspace @stream-io/stream-chat-react-cookbook check:css
190+
yarn workspace @stream-io/stream-chat-react-cookbook build # type-check + build, as CI does
191+
```
192+
193+
`shots` needs the dev server running and Playwright's Chromium (`npx playwright install chromium`).
194+
A relative `--out` is resolved from the repository root; an absolute path is safest. Shots use the `en-US` locale
195+
and the UTC time zone, so dates look the same on every machine. File names must be unique across
196+
all recipes.
197+
198+
## Done when
199+
200+
- [ ] `check:css` passes.
201+
- [ ] `build` passes, and `npx eslint --max-warnings 0` and `npx prettier --check` pass for the
202+
changed files.
203+
- [ ] Every shot in `shots.json` is taken, looked at, and shows what the page says.
204+
- [ ] The docs page's code matches the recipe folder.
205+
- [ ] In getstream.io: the docs checks and the sample checker pass on the page, and the site builds.
206+
- [ ] Anything that only renders wrong because of the SDK is reported, not hidden with CSS.

‎.claude/skills/react-ui-cookbook‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../.agents/skills/react-ui-cookbook

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ jobs:
6666
run: git diff --exit-code -- src/i18n/keys.ts
6767

6868
- name: Build the example apps
69-
# Type-checks and bundles every `examples/*` workspace (vite, tutorial, ai-chatbot) against
69+
# Type-checks and bundles every `examples/*` workspace (vite, tutorial, ai-chatbot, cookbook) against
7070
# the `dist` built above, so an SDK change that breaks an example (or a stream-chat API one
7171
# relies on) fails here. None of the builds need Stream credentials.
7272
env:

‎.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ typings/
7878
/.env.local
7979
/.env.development
8080
/.env.production
81+
# Local env files in the example apps, such as the cookbook's Giphy key
82+
examples/*/.env*.local
8183

8284
# next.js build output
8385
.next

‎AGENTS.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Respect repo-specific rules. Do not suppress lint rules broadly; justify and sco
3838
- `src/` — library source: `components/`, `context/`, `hooks/`, `store/`, `i18n/`, `styling/`, `a11y/`, `plugins/`, `utils/`, `types/`, `constants/`, `mock-builders/`
3939
- `src/plugins/` — separately exported entry points: `AIComponents`, `ChannelDetail`, `Emojis`, `SlotGeometry`, `SlotLayout`, `encoders`
4040
- `scripts/` — build/validation scripts
41-
- `examples/` — private example workspaces: `examples/tutorial` (one folder per tutorial step), `examples/vite` (complete app), `examples/ai-chatbot` (Next.js AI chatbot)
41+
- `examples/` — private example workspaces: `examples/tutorial` (one folder per tutorial step), `examples/vite` (complete app), `examples/ai-chatbot` (Next.js AI chatbot), `examples/cookbook` (the base app and code of the docs' UI Cookbook recipes; see `.agents/skills/react-ui-cookbook/SKILL.md`)
4242
- `ai-docs/` — integrator-facing migration guides (`ai-migration-v14-v15.md`, `i18n-v15-migration.md`, `instance-configuration.md`, …) and the hand-reviewed `i18n-v15-key-map.json`
4343
- `developers/` — dev notes (`BRANCHES.md`, `COMMIT.md`, `DEPRECATIONS.md`, `DEVELOPMENT.md`, `DOCUMENTATION.md`, `PR.md`, `RELEASE.md`)
4444

@@ -84,6 +84,7 @@ yarn validate-entry-isolation # fails if the main entry's ESM graph reaches ai-c
8484
# Examples
8585
yarn start:tutorial # @stream-io/stream-chat-react-tutorial dev server
8686
yarn start:vite # @stream-io/stream-chat-react-vite dev server
87+
yarn start:cookbook # @stream-io/stream-chat-react-cookbook dev server (UI Cookbook recipes)
8788
yarn start:ai-chatbot # @stream-io/stream-chat-react-ai-chatbot dev server (run yarn build first)
8889
yarn examples:build # build all example workspaces
8990
```

‎examples/cookbook/index.html‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>UI Cookbook</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/main.tsx"></script>
11+
</body>
12+
</html>

‎examples/cookbook/package.json‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "@stream-io/stream-chat-react-cookbook",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite --port 5176 --strictPort",
8+
"build": "tsc -b && vite build",
9+
"preview": "vite preview --port 5176 --strictPort",
10+
"check:css": "node scripts/check-css.mjs",
11+
"seed": "node scripts/seed.mjs",
12+
"shots": "node scripts/shots.mjs"
13+
},
14+
"dependencies": {
15+
"@stream-io/state-store": "^1.1.6",
16+
"leaflet": "^1.9.4",
17+
"lucide-react": "^1.61.0",
18+
"modern-normalize": "^3.0.1",
19+
"react": "^19.2.6",
20+
"react-dom": "^19.2.6",
21+
"react-leaflet": "^5.0.0",
22+
"stream-chat": "10.0.0-rc.25",
23+
"stream-chat-react": "workspace:^"
24+
},
25+
"devDependencies": {
26+
"@playwright/test": "^1.60.0",
27+
"@types/leaflet": "^1.9.20",
28+
"@types/react": "^19.2.15",
29+
"@types/react-dom": "^19.2.3",
30+
"@vitejs/plugin-react": "^6.0.3",
31+
"typescript": "^6.0.3",
32+
"vite": "^8.1.3"
33+
}
34+
}

0 commit comments

Comments
 (0)