@tightknitai/block-kitchen 0.6.1-alpha.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/LICENSE +21 -0
- package/README.md +192 -0
- package/dist/index.cjs +6943 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +659 -0
- package/dist/index.d.ts +659 -0
- package/dist/index.js +6906 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +118 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tightknit and block-kitchen contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./assets/logo.png" alt="block-kitchen logo" width="160" height="160" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# block-kitchen
|
|
6
|
+
|
|
7
|
+
[](https://github.com/TightknitAI/block-kitchen/actions/workflows/ci.yml)
|
|
8
|
+
[](https://www.npmjs.com/package/@tightknitai/block-kitchen)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://block-kitchen.tightknit.dev)
|
|
11
|
+
|
|
12
|
+
A drag-and-drop, no-code-friendly visual builder for Slack Block Kit messages, packaged as an **integration-agnostic React component**.
|
|
13
|
+
|
|
14
|
+
Inspired by [Slack's Block Kit Builder](https://app.slack.com/block-kit-builder), reimagined as an embeddable React component you can drop into your own app.
|
|
15
|
+
|
|
16
|
+
**[Try the live demo →](https://block-kitchen.tightknit.dev)** (mocked Slack — drag, drop, edit, preview)
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img src="./assets/screenshot.png" alt="block-kitchen demo screenshot" width="900" />
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
The package owns the entire builder UX — palette, sortable preview surface, per-block popover editors, send dialog. It knows nothing about how channels are listed, who the user is, or how messages are sent. The consumer wires those concerns through callback props. A working end-to-end app is shown in [block-kit-builder-template](https://github.com/TightknitAI/block-kit-builder-template).
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm add @tightknitai/block-kitchen
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Peer deps: `react`, `react-dom`.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { BlockKitchen } from "@tightknitai/block-kitchen";
|
|
36
|
+
import "@tightknitai/block-kitchen/styles.css";
|
|
37
|
+
|
|
38
|
+
export function MyBuilderPage() {
|
|
39
|
+
return (
|
|
40
|
+
<BlockKitchen
|
|
41
|
+
workspaceName="Acme Inc."
|
|
42
|
+
loadChannels={async () => {
|
|
43
|
+
const res = await fetch("/api/slack/channels");
|
|
44
|
+
return res.json();
|
|
45
|
+
}}
|
|
46
|
+
loadSendAsUserStatus={async () => {
|
|
47
|
+
const res = await fetch("/api/slack/me/can-send-as-user");
|
|
48
|
+
return res.json();
|
|
49
|
+
}}
|
|
50
|
+
onSend={async ({ channelId, blocks, sendAsUser }) => {
|
|
51
|
+
const res = await fetch("/api/slack/messages/send", {
|
|
52
|
+
method: "POST",
|
|
53
|
+
body: JSON.stringify({ channelId, blocks, sendAsUser }),
|
|
54
|
+
});
|
|
55
|
+
return res.json();
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Props
|
|
63
|
+
|
|
64
|
+
| Prop | Type | Required | Description |
|
|
65
|
+
|---|---|---|---|
|
|
66
|
+
| `workspaceName` | `string` | no | Shown in the preview chrome to mimic a real Slack message header. |
|
|
67
|
+
| `initialBlocks` | `SupportedBlock[]` | no | Starting draft. If omitted, the builder starts empty. |
|
|
68
|
+
| `onChange` | `(blocks: SupportedBlock[]) => void` | no | Fires on every state change. Use this to persist the draft (URL, localStorage, etc). |
|
|
69
|
+
| `loadChannels` | `() => Promise<{ id: string; name: string }[]>` | yes | Returns channels available to send to. The package never makes Slack API calls itself. |
|
|
70
|
+
| `loadSendAsUserStatus` | `() => Promise<{ canSendAsUser: boolean; oauthUrl?: string }>` | yes | Whether the current user has a Slack user-token and can post as themselves. If `canSendAsUser` is false, `oauthUrl` is shown as a "Sign in with Slack" link. |
|
|
71
|
+
| `onSend` | `(payload) => Promise<{ ok: boolean; error?: string }>` | yes | Called when the user submits the send dialog. Payload is `{ channelId, blocks, sendAsUser }`. |
|
|
72
|
+
| `previewHooks` | `PreviewHooks` | no | Hooks forwarded to `slack-blocks-to-jsx`'s `<Message>` for resolving user / channel / emoji directives. |
|
|
73
|
+
| `palette` | `PaletteSection[]` | no | The left-hand palette of draggable variants. Defaults to `defaultPalette`. Spread it to filter, reorder, or add your own pre-configured variants — see [Customizing the palette](#customizing-the-palette). |
|
|
74
|
+
| `defaultOpenSections` | `boolean \| string[]` | no | Which palette section headers are expanded on first paint. `true` (default) opens all sections; `false` collapses all (Slack-style); an array opens only sections whose `name` is in the list (e.g. `['Section', 'Actions']`). The palette also has a built-in search input that expands matching sections on demand. |
|
|
75
|
+
| `showPaletteSearch` | `boolean` | no | Whether the palette renders the quick-search input above the section list. Defaults to `true`. Set `false` for compact palettes (e.g. when you've passed a small custom `palette`) where scanning by eye is faster than typing. |
|
|
76
|
+
| `paletteSearchPlaceholder` | `string` | no | Placeholder text for the palette search input. Defaults to `'Search blocks…'`. Useful for localization. |
|
|
77
|
+
| `allowedSurfaces` | `PreviewSurface[]` | no | Allowlist of preview surfaces (`'message'`, `'modal'`, `'app_home'`). Defaults to `['message']` — surface dropdown is hidden when only one surface is allowed. The first entry is the initial selection. |
|
|
78
|
+
| `showThemeControl` | `boolean` | no | Defaults to `true`. When `false`, the theme is locked to `'light'`. |
|
|
79
|
+
| `defaultPreviewTheme` | `'light' \| 'dark'` | no | Pass the host app's current theme so the preview opens matched to the consuming app's appearance. |
|
|
80
|
+
| `theme` | `BrandTheme \| BrandPreset` | no | Branding tokens applied to the builder chrome (toolbar, palette, popovers, dialogs). Accepts a `Partial<BrandTokens>` map and optional `light`/`dark` overrides. See [Styling](#styling) below. |
|
|
81
|
+
|
|
82
|
+
## Customizing the palette
|
|
83
|
+
|
|
84
|
+
The default palette ships with curated presets for every supported block type. To narrow what's available, or add your own pre-configured variants (e.g. a "Help footer" section), pass a `palette` array. Define it at module scope (or wrap in `useMemo`) so it stays referentially stable across renders.
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import {
|
|
88
|
+
BlockKitchen,
|
|
89
|
+
defaultPalette,
|
|
90
|
+
type PaletteSection,
|
|
91
|
+
} from "@tightknitai/block-kitchen";
|
|
92
|
+
|
|
93
|
+
const PALETTE: readonly PaletteSection[] = [
|
|
94
|
+
...defaultPalette.filter((s) => s.blockType !== "input"),
|
|
95
|
+
{
|
|
96
|
+
name: "Company presets",
|
|
97
|
+
blockType: "section",
|
|
98
|
+
variants: [
|
|
99
|
+
{
|
|
100
|
+
id: "help_footer",
|
|
101
|
+
label: "help footer",
|
|
102
|
+
factory: () => ({
|
|
103
|
+
type: "section",
|
|
104
|
+
text: { type: "mrkdwn", text: "Need help? Reach out in <#C0HELP>." },
|
|
105
|
+
}),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
<BlockKitchen palette={PALETTE} {...rest} />;
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Variant `id`s must be unique across the array — the drag-drop lookup keys by id.
|
|
115
|
+
|
|
116
|
+
## Boundary
|
|
117
|
+
|
|
118
|
+
The package is deliberately decoupled from any Slack SDK or backend. It does not import HTTP clients, OAuth libraries, or workspace-state systems. Everything I/O-shaped is brokered through props.
|
|
119
|
+
|
|
120
|
+
Helpers also exported:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import {
|
|
124
|
+
toSlackBlocks, // strips builder-only fields (e.g. header `level`) before sending
|
|
125
|
+
encodeBlocksToString, // base64url-encode a blocks array (for URL state)
|
|
126
|
+
decodeBlocksFromString,
|
|
127
|
+
defaultPalette, // the built-in palette — spread to customize
|
|
128
|
+
} from "@tightknitai/block-kitchen";
|
|
129
|
+
|
|
130
|
+
import type {
|
|
131
|
+
SupportedBlock,
|
|
132
|
+
SupportedBlockType,
|
|
133
|
+
BlockKitchenProps,
|
|
134
|
+
PaletteSection,
|
|
135
|
+
PaletteVariant,
|
|
136
|
+
SendPayload,
|
|
137
|
+
SendResult,
|
|
138
|
+
ChannelOption,
|
|
139
|
+
SendAsUserStatus,
|
|
140
|
+
PreviewHooks,
|
|
141
|
+
} from "@tightknitai/block-kitchen";
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Backend
|
|
145
|
+
|
|
146
|
+
The builder is frontend-only. For a full app that handles OAuth, channel listing, and `chat.postMessage`, see [block-kit-builder-template](https://github.com/TightknitAI/block-kit-builder-template) — a Vite + React SPA on Cloudflare Workers that wires this package to [slack-hono](https://github.com/TightknitAI/slack-hono) on the backend.
|
|
147
|
+
|
|
148
|
+
## Validation
|
|
149
|
+
|
|
150
|
+
Defense-in-depth: blocks are validated against [slack-block-kit-validator](https://github.com/TightknitAI/slack-block-kit-validator) before send. Issues are surfaced in the issues sheet with line numbers — users can fix them inline before posting.
|
|
151
|
+
|
|
152
|
+
## Styling
|
|
153
|
+
|
|
154
|
+
Ships a compiled stylesheet at `@tightknitai/block-kitchen/styles.css`. The styles use CSS custom properties (`--background`, `--primary`, `--border`, etc.) for theming. Consumers must provide values for these vars — the standard shadcn/ui token set works as-is.
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
import "@tightknitai/block-kitchen/styles.css";
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Branding (typed `theme` prop)
|
|
161
|
+
|
|
162
|
+
For consumers who don't already have a shadcn token set on `:root`, the `theme` prop is a typed shortcut that writes a subset of tokens directly:
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
import type { BrandTheme } from "@tightknitai/block-kitchen";
|
|
166
|
+
|
|
167
|
+
const brand: BrandTheme = {
|
|
168
|
+
tokens: { primary: "262 83% 58%", radius: "0.75rem" },
|
|
169
|
+
dark: { primary: "263 70% 75%" }
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
<BlockKitchen theme={brand} {...rest} />
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
- `tokens` applies in both light and dark contexts.
|
|
176
|
+
- `light` and `dark` override per mode; the dark variant kicks in under a standard `.dark` ancestor class (next-themes default).
|
|
177
|
+
- Color tokens take HSL component strings (`"262 83% 58%"`), matching the underlying CSS variable contract; `radius` takes a CSS length.
|
|
178
|
+
- Scope is the builder chrome only. The embedded Slack preview keeps its native Slack styling regardless of `theme`; use `defaultPreviewTheme` for the preview's light/dark toggle.
|
|
179
|
+
|
|
180
|
+
The lower-level CSS-variable contract above keeps working; the `theme` prop simply layers on top of it.
|
|
181
|
+
|
|
182
|
+
### Typography
|
|
183
|
+
|
|
184
|
+
Fonts are deliberately not part of `BrandTheme`. The builder sets no `font-family` of its own (aside from `font-mono` on the JSON viewer, which is intentional), so it inherits whatever the host page declares on `<html>` or `<body>`. Set your brand typography globally and the builder will pick it up automatically — no additional configuration needed. The Slack preview surface continues to render with Slack's own typography via `slack-blocks-to-jsx`.
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
MIT. See [LICENSE](./LICENSE).
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
Maintained by the [Tightknit](https://tightknit.ai) team.
|