@tightknitai/block-kitchen 0.10.4 → 0.10.6
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 +106 -1
- package/dist/helpers.d.cts +2 -2
- package/dist/helpers.d.ts +2 -2
- package/dist/index.cjs +197 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +198 -56
- package/dist/index.js.map +1 -1
- package/dist/{palette-Sz27861i.d.cts → palette-QbOx7kAa.d.cts} +41 -2
- package/dist/{palette-Sz27861i.d.ts → palette-QbOx7kAa.d.ts} +41 -2
- package/dist/palette.cjs +4 -1
- package/dist/palette.cjs.map +1 -1
- package/dist/palette.d.cts +1 -1
- package/dist/palette.d.ts +1 -1
- package/dist/palette.js +4 -1
- package/dist/palette.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -120,8 +120,9 @@ export function MyBuilderPage() {
|
|
|
120
120
|
| `customEmojis` | `CustomEmoji[]` | no | Workspace custom emoji (`{ name, url, alias }`) the preview resolves. Entries with a `url` render `:name:` as the workspace image; alias entries (`url: null`) fall back to their target emoji. Render-only — never serialized into the emitted Block Kit JSON. A caller-supplied `previewHooks.emoji` takes precedence. |
|
|
121
121
|
| `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). |
|
|
122
122
|
| `disabledBlockTypes` | `SupportedBlockType[]` | no | Block types to hide from the palette without rebuilding it. Filters at the variant level — a section keeps any variants whose block types aren't disabled; sections that end up empty are dropped. Convenient when you want the default palette minus a few types (e.g. `['image', 'table']` for a text-only builder). |
|
|
123
|
+
| `paletteMode` | `'advanced' \| 'simple'` | no | How much of the palette the user meets first. `'advanced'` (default) renders the full sectioned palette with its search input. `'simple'` opens on a flat list of the variants flagged `basic` — in the built-in palette, just **Rich Text Section** — with no search and an "Advanced" link at the top that swaps in the full palette. Nothing is removed; it's a smaller first screen. See [Simple vs advanced palette](#simple-vs-advanced-palette). |
|
|
123
124
|
| `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. |
|
|
124
|
-
| `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. |
|
|
125
|
+
| `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. Simple mode has no search either way — it appears with the rest of the palette behind "Advanced". |
|
|
125
126
|
| `paletteSearchPlaceholder` | `string` | no | Placeholder text for the palette search input. Defaults to `'Search blocks…'`. Useful for localization. |
|
|
126
127
|
| `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. |
|
|
127
128
|
| `showThemeControl` | `boolean` | no | Defaults to `true`. When `false`, the toolbar's light/dark toggle is hidden and the theme stays at `defaultPreviewTheme`. Ignored when `previewTheme` is set (a controlled theme always hides the toggle). |
|
|
@@ -452,6 +453,107 @@ const PALETTE: readonly PaletteSection[] = [
|
|
|
452
453
|
|
|
453
454
|
Variant `id`s must be unique across the array — the drag-drop lookup keys by id.
|
|
454
455
|
|
|
456
|
+
### Simple vs advanced palette
|
|
457
|
+
|
|
458
|
+
By default the palette opens on everything it has: every section, plus the search input. For consumers whose users are writing ordinary messages rather than building interactive apps, `paletteMode="simple"` opens on a one-block starter list instead — no sections, no search — behind an **Advanced** link that swaps in the full palette (and back).
|
|
459
|
+
|
|
460
|
+
```tsx
|
|
461
|
+
<BlockKitchen paletteMode="simple" {...rest} />
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Which variants the simple list holds is a property of the palette, not the flag: a variant opts in with `basic: true`. The built-in palette flags exactly one — **Rich Text Section** — so a custom palette that wants its own starter block says so:
|
|
465
|
+
|
|
466
|
+
```tsx
|
|
467
|
+
const PALETTE: readonly PaletteSection[] = [
|
|
468
|
+
{
|
|
469
|
+
name: "Company presets",
|
|
470
|
+
icon: AlignLeft,
|
|
471
|
+
variants: [
|
|
472
|
+
{ id: "help_footer", label: "Help footer", basic: true, factory: () => ({ ... }) },
|
|
473
|
+
{ id: "company_divider", label: "Company divider", factory: () => ({ ... }) },
|
|
474
|
+
],
|
|
475
|
+
},
|
|
476
|
+
];
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
A palette that flags none of its variants `basic` has no simple list to show, so it renders as `'advanced'` regardless — the link is withheld rather than leading to an empty rail.
|
|
480
|
+
|
|
481
|
+
## Templates (`TemplatePicker`)
|
|
482
|
+
|
|
483
|
+
`TemplatePicker` is a standalone, page-sized picker that renders `Template`s as a grid of cards with live block previews, grouped into category sections — modeled on Slack's own Block Kit Builder templates page. It's pure UI: it owns no dialog and no layout, so you decide whether it's a route, a modal, a slide-over, or a sidebar next to the builder.
|
|
484
|
+
|
|
485
|
+
```tsx
|
|
486
|
+
import {
|
|
487
|
+
BlockKitchen,
|
|
488
|
+
TemplatePicker,
|
|
489
|
+
type SupportedBlock,
|
|
490
|
+
type Template,
|
|
491
|
+
} from "@tightknitai/block-kitchen";
|
|
492
|
+
import { useState } from "react";
|
|
493
|
+
|
|
494
|
+
const TEMPLATES: Template[] = [
|
|
495
|
+
{
|
|
496
|
+
id: "standup",
|
|
497
|
+
name: "Daily standup",
|
|
498
|
+
description: "Yesterday / today / blockers",
|
|
499
|
+
surface: "message",
|
|
500
|
+
category: "Team", // optional — groups cards into sections
|
|
501
|
+
blocks: [
|
|
502
|
+
/* SupportedBlock[] */
|
|
503
|
+
],
|
|
504
|
+
},
|
|
505
|
+
];
|
|
506
|
+
|
|
507
|
+
function Builder() {
|
|
508
|
+
const [blocks, setBlocks] = useState<SupportedBlock[]>([]);
|
|
509
|
+
// `initialBlocks` is read once at mount, so bump a key to re-seed.
|
|
510
|
+
const [seedKey, setSeedKey] = useState(0);
|
|
511
|
+
|
|
512
|
+
return (
|
|
513
|
+
<div style={{ display: "flex", minHeight: 0 }}>
|
|
514
|
+
<div style={{ flex: 1, minWidth: 0 }}>
|
|
515
|
+
<BlockKitchen key={seedKey} initialBlocks={blocks} onChange={setBlocks} {...rest} />
|
|
516
|
+
</div>
|
|
517
|
+
{/* `.bk-root` is required — see below */}
|
|
518
|
+
<aside className="bk-root" style={{ width: 380, overflowY: "auto" }}>
|
|
519
|
+
<TemplatePicker
|
|
520
|
+
templates={TEMPLATES}
|
|
521
|
+
heading="Templates"
|
|
522
|
+
onSelect={(t) => {
|
|
523
|
+
setBlocks(t.blocks);
|
|
524
|
+
setSeedKey((n) => n + 1);
|
|
525
|
+
}}
|
|
526
|
+
/>
|
|
527
|
+
</aside>
|
|
528
|
+
</div>
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
| Prop | Type | Required | Description |
|
|
534
|
+
| --- | --- | --- | --- |
|
|
535
|
+
| `templates` | `Template[]` | yes | Templates to show. Order is preserved within each category section. |
|
|
536
|
+
| `onSelect` | `(template: Template) => void` | yes | Called with the whole template when a card is clicked. Nothing is applied to the builder for you — see [Applying a selection](#applying-a-selection). |
|
|
537
|
+
| `surface` | `'message' \| 'modal' \| 'app_home'` | no | Filter to templates whose `surface` matches. Omit to show all. |
|
|
538
|
+
| `theme` | `'light' \| 'dark'` | no | Preview theme used inside the card thumbnails. Defaults to `'light'`; pass the builder's `previewTheme` to keep them in step. |
|
|
539
|
+
| `heading` | `string` | no | Heading rendered above the grid. Omitted means no heading. |
|
|
540
|
+
| `emptyLabel` | `string` | no | Shown when the `surface` filter matches nothing. Defaults to `'No templates available.'`. |
|
|
541
|
+
| `className` | `string` | no | Merged onto the root element. |
|
|
542
|
+
|
|
543
|
+
A `Template` is `{ id, name, surface, blocks }` plus optional `description` and `category`. Templates without a `category` fall into a trailing **Other** section when at least one other template has one; with no categories at all, the grid renders flat without section headers.
|
|
544
|
+
|
|
545
|
+
### Bring your own templates
|
|
546
|
+
|
|
547
|
+
The package ships **no** templates on purpose — they're use-case examples ("Expense approval" belongs to an approvals product, "Daily standup" to a team product), so they belong in your app's config rather than the library bundle. The live demo defines its own set in [`demo/src/templates.ts`](demo/src/templates.ts) if you want a starting point to crib from.
|
|
548
|
+
|
|
549
|
+
### Applying a selection
|
|
550
|
+
|
|
551
|
+
`onSelect` hands you the template and stops there — the picker never reaches into the builder. Because [`initialBlocks`](#props) is read once at mount, replacing the current draft means re-seeding with a changed `key`, as above. Selecting a template this way discards the draft and its undo history, so gate it behind a confirmation if that's a surprise in your app.
|
|
552
|
+
|
|
553
|
+
### It needs a `.bk-root` ancestor
|
|
554
|
+
|
|
555
|
+
Unlike `BlockKitchen`, the picker does not add the `bk-root` class itself. The stylesheet ships its utilities inside `@scope (.bk-root, .bk-portal-content)`, so a picker mounted outside both roots renders unstyled. Wrap it (or any ancestor) in `className="bk-root"`.
|
|
556
|
+
|
|
455
557
|
## Boundary
|
|
456
558
|
|
|
457
559
|
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 — and the send flow itself is optional: see [compose-only mode](#compose-only-mode-bring-your-own-send-flow) when your app owns the moment of commitment.
|
|
@@ -465,6 +567,7 @@ import {
|
|
|
465
567
|
decodeBlocksFromString,
|
|
466
568
|
defaultPalette, // the built-in palette — spread to customize
|
|
467
569
|
SendDialog, // the built-in send dialog, standalone (bespoke send flows)
|
|
570
|
+
TemplatePicker, // the standalone templates grid — see Templates above
|
|
468
571
|
useSlackSignIn, // the OAuth sign-in state machine behind the identity picker
|
|
469
572
|
SlackSignInButton, // the "Sign in with Slack" button + polling spinner
|
|
470
573
|
} from "@tightknitai/block-kitchen";
|
|
@@ -480,6 +583,7 @@ import type {
|
|
|
480
583
|
BlockKitchenComposeOnlyProps, // the trio explicitly absent + `primaryAction`
|
|
481
584
|
PaletteSection,
|
|
482
585
|
PaletteVariant,
|
|
586
|
+
PaletteMode, // 'advanced' (default) | 'simple'
|
|
483
587
|
SendPayload,
|
|
484
588
|
SendResult,
|
|
485
589
|
SendDialogProps,
|
|
@@ -488,6 +592,7 @@ import type {
|
|
|
488
592
|
PrimaryActionContext,
|
|
489
593
|
ChannelOption,
|
|
490
594
|
SendAsUserStatus,
|
|
595
|
+
Template, // one entry in a TemplatePicker gallery
|
|
491
596
|
ValidationSummary,
|
|
492
597
|
PreviewHooks,
|
|
493
598
|
} from "@tightknitai/block-kitchen";
|
package/dist/helpers.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SupportedBlock } from './palette-
|
|
2
|
-
export {
|
|
1
|
+
import { S as SupportedBlock } from './palette-QbOx7kAa.cjs';
|
|
2
|
+
export { a1 as SupportedBlockType } from './palette-QbOx7kAa.cjs';
|
|
3
3
|
import 'react';
|
|
4
4
|
import 'slack-web-api-client';
|
|
5
5
|
|
package/dist/helpers.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SupportedBlock } from './palette-
|
|
2
|
-
export {
|
|
1
|
+
import { S as SupportedBlock } from './palette-QbOx7kAa.js';
|
|
2
|
+
export { a1 as SupportedBlockType } from './palette-QbOx7kAa.js';
|
|
3
3
|
import 'react';
|
|
4
4
|
import 'slack-web-api-client';
|
|
5
5
|
|
package/dist/index.cjs
CHANGED
|
@@ -848,7 +848,10 @@ var defaultPalette = [
|
|
|
848
848
|
variants: [
|
|
849
849
|
{
|
|
850
850
|
id: "rich_text_section",
|
|
851
|
-
label: "Section",
|
|
851
|
+
label: "Rich Text Section",
|
|
852
|
+
// The one variant simple mode offers, where it's listed without its
|
|
853
|
+
// section heading — hence the self-describing label.
|
|
854
|
+
basic: true,
|
|
852
855
|
factory: () => ({
|
|
853
856
|
type: "rich_text",
|
|
854
857
|
elements: [
|
|
@@ -3251,6 +3254,7 @@ function SlackSignInButton({ onClick, polling }) {
|
|
|
3251
3254
|
}
|
|
3252
3255
|
var LINK_DEBOUNCE_MS = 400;
|
|
3253
3256
|
var LINK_HELP_TEXT = 'Click the \u2807menu while hovering over the message in Slack (or right-click), and select "Copy Link".';
|
|
3257
|
+
var LINK_HELP_LABEL = "Where to find a message link";
|
|
3254
3258
|
var TABS = [
|
|
3255
3259
|
{ id: "recent", label: "Pick from Recent" },
|
|
3256
3260
|
{ id: "link", label: "Direct Link" }
|
|
@@ -3499,12 +3503,13 @@ function LoadMessageDialog({
|
|
|
3499
3503
|
}, POLL_INTERVAL_MS2);
|
|
3500
3504
|
};
|
|
3501
3505
|
const preview = activeTab === "recent" ? selectedRecent ? { kind: "ready", target: { ...selectedRecent, editableVia: selectedRecent.editableVia ?? "bot" } } : { kind: "empty" } : linkStatus.kind === "idle" ? { kind: "empty" } : linkStatus.kind === "ready" ? { kind: "ready", target: linkStatus.result } : linkStatus;
|
|
3502
|
-
const
|
|
3506
|
+
const verdictOwnsAction = linkStatus.kind === "not-editable" && (isSafeHref(linkStatus.oauthUrl) || (linkStatus.blocks?.length ?? 0) > 0);
|
|
3507
|
+
const canLoad = activeTab === "recent" ? !!selectedRecent : !!link.trim() && !verdictOwnsAction;
|
|
3503
3508
|
const panelProps = (id) => hasRecent ? { role: "tabpanel", id: panelDomId(id), "aria-labelledby": tabDomId(id) } : {};
|
|
3504
3509
|
return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3505
3510
|
DialogContent,
|
|
3506
3511
|
{
|
|
3507
|
-
className: "flex max-h-[85svh] w-[calc(100vw-1.5rem)] flex-col rounded-lg lg:max-w-
|
|
3512
|
+
className: "flex max-h-[85svh] w-[calc(100vw-1.5rem)] max-w-4xl flex-col rounded-lg max-lg:max-w-lg",
|
|
3508
3513
|
onOpenAutoFocus: (e) => {
|
|
3509
3514
|
if (hasRecent) {
|
|
3510
3515
|
return;
|
|
@@ -3517,7 +3522,7 @@ function LoadMessageDialog({
|
|
|
3517
3522
|
/* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: "Find an existing message" }),
|
|
3518
3523
|
/* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { children: hasRecent ? "Pick a recent message your app posted, or paste a Slack message link. Preview it before loading." : `Paste a Slack message link (Slack's "Copy link") to preview it and load its blocks for editing.` })
|
|
3519
3524
|
] }),
|
|
3520
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "-mx-1 flex min-h-0 min-w-0 flex-1 flex-col gap-4 overflow-y-auto
|
|
3525
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "-mx-1 flex min-h-0 min-w-0 flex-1 px-1 max-lg:flex-col max-lg:gap-4 max-lg:overflow-y-auto lg:flex-row lg:gap-5 lg:overflow-hidden", children: [
|
|
3521
3526
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 shrink-0 flex-col gap-3 lg:min-h-0 lg:w-[19rem]", children: [
|
|
3522
3527
|
hasRecent && /* @__PURE__ */ jsxRuntime.jsx(TabStrip, { activeTab, onSelect: setTab, tabDomId, panelDomId }),
|
|
3523
3528
|
activeTab === "recent" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -3552,7 +3557,7 @@ function LoadMessageDialog({
|
|
|
3552
3557
|
channelId && recent === null && !recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "shrink-0 text-xs text-muted-foreground", children: "Loading recent messages\u2026" }),
|
|
3553
3558
|
channelId && recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "shrink-0 text-xs text-destructive", children: recentError }),
|
|
3554
3559
|
channelId && recent && recent.length === 0 && !recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "shrink-0 text-xs text-muted-foreground", children: "No recent messages from this app in this channel." }),
|
|
3555
|
-
channelId && recent && recent.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex
|
|
3560
|
+
channelId && recent && recent.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-0 min-w-0 flex-col gap-1 overflow-y-auto max-lg:max-h-60 lg:flex-1", children: recent.map((m) => {
|
|
3556
3561
|
const asUser = (m.editableVia ?? "bot") === "user";
|
|
3557
3562
|
const authorName = m.username || (asUser ? "You" : "App bot");
|
|
3558
3563
|
const safeIcon = isSafeImageSrc(m.iconUrl) ? m.iconUrl : void 0;
|
|
@@ -3610,7 +3615,21 @@ function LoadMessageDialog({
|
|
|
3610
3615
|
className: "-mx-1 flex min-w-0 flex-col gap-2 px-1 lg:min-h-0 lg:flex-1 lg:overflow-y-auto",
|
|
3611
3616
|
children: [
|
|
3612
3617
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
3613
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3618
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
3619
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "message-link", children: "Message link" }),
|
|
3620
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
|
|
3621
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3622
|
+
"button",
|
|
3623
|
+
{
|
|
3624
|
+
type: "button",
|
|
3625
|
+
"aria-label": LINK_HELP_LABEL,
|
|
3626
|
+
className: "flex h-4 w-4 items-center justify-center rounded-full text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
3627
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Info, { className: "h-3.5 w-3.5" })
|
|
3628
|
+
}
|
|
3629
|
+
) }),
|
|
3630
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "right", children: LINK_HELP_TEXT })
|
|
3631
|
+
] })
|
|
3632
|
+
] }),
|
|
3614
3633
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3615
3634
|
Input,
|
|
3616
3635
|
{
|
|
@@ -3632,8 +3651,7 @@ function LoadMessageDialog({
|
|
|
3632
3651
|
autoComplete: "off",
|
|
3633
3652
|
spellCheck: false
|
|
3634
3653
|
}
|
|
3635
|
-
)
|
|
3636
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: LINK_HELP_TEXT })
|
|
3654
|
+
)
|
|
3637
3655
|
] }),
|
|
3638
3656
|
emptyLinkError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "rounded-md border border-destructive/30 bg-destructive/5 p-2 text-xs text-destructive", children: emptyLinkError })
|
|
3639
3657
|
]
|
|
@@ -3731,38 +3749,39 @@ function PreviewPane({
|
|
|
3731
3749
|
editableLabel(target.editableVia)
|
|
3732
3750
|
] }) : null
|
|
3733
3751
|
] }),
|
|
3752
|
+
state.kind === "not-editable" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-col gap-2 rounded-md border border-amber-200! bg-amber-50 p-3 text-xs text-amber-800", children: [
|
|
3753
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
|
|
3754
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
|
|
3755
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: state.reason })
|
|
3756
|
+
] }),
|
|
3757
|
+
signInUrl && /* @__PURE__ */ jsxRuntime.jsx(SlackSignInButton, { onClick: () => onSignIn(signInUrl), polling: signInPolling }),
|
|
3758
|
+
verdictBlocks && verdictBlocks.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3759
|
+
Button,
|
|
3760
|
+
{
|
|
3761
|
+
type: "button",
|
|
3762
|
+
variant: "outline",
|
|
3763
|
+
size: "sm",
|
|
3764
|
+
className: "self-start",
|
|
3765
|
+
onClick: () => onOpenAsNew(verdictBlocks),
|
|
3766
|
+
children: "Open as a new message instead"
|
|
3767
|
+
}
|
|
3768
|
+
)
|
|
3769
|
+
] }),
|
|
3734
3770
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3735
3771
|
"div",
|
|
3736
3772
|
{
|
|
3737
3773
|
role: "region",
|
|
3738
3774
|
"aria-label": "Message preview",
|
|
3739
3775
|
tabIndex: 0,
|
|
3740
|
-
className: "flex
|
|
3776
|
+
className: "flex flex-1 flex-col gap-3 overflow-y-auto rounded-md border bg-muted/40 p-3 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring max-lg:max-h-72 max-lg:min-h-32 lg:min-h-0",
|
|
3741
3777
|
children: [
|
|
3742
|
-
state.kind === "not-editable" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-col gap-2 rounded-md border border-amber-200! bg-amber-50 p-3 text-xs text-amber-800", children: [
|
|
3743
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
|
|
3744
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
|
|
3745
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: state.reason })
|
|
3746
|
-
] }),
|
|
3747
|
-
signInUrl && /* @__PURE__ */ jsxRuntime.jsx(SlackSignInButton, { onClick: () => onSignIn(signInUrl), polling: signInPolling }),
|
|
3748
|
-
verdictBlocks && verdictBlocks.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3749
|
-
Button,
|
|
3750
|
-
{
|
|
3751
|
-
type: "button",
|
|
3752
|
-
variant: "outline",
|
|
3753
|
-
size: "sm",
|
|
3754
|
-
className: "self-start",
|
|
3755
|
-
onClick: () => onOpenAsNew(verdictBlocks),
|
|
3756
|
-
children: "Open as a new message instead"
|
|
3757
|
-
}
|
|
3758
|
-
)
|
|
3759
|
-
] }),
|
|
3760
3778
|
state.kind === "error" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "shrink-0 rounded-md border border-destructive/30 bg-destructive/5 p-2 text-xs text-destructive", children: state.error }),
|
|
3761
3779
|
state.kind === "loading" && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "m-auto flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
3762
3780
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }),
|
|
3763
3781
|
"Looking up that message\u2026"
|
|
3764
3782
|
] }),
|
|
3765
3783
|
state.kind === "empty" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-auto px-4 text-center text-sm text-muted-foreground", children: emptyHint }),
|
|
3784
|
+
state.kind === "not-editable" && !(verdictBlocks && verdictBlocks.length > 0) && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-auto px-4 text-center text-sm text-muted-foreground", children: "Nothing to preview." }),
|
|
3766
3785
|
target && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3767
3786
|
SlackMessagePreview,
|
|
3768
3787
|
{
|
|
@@ -3792,6 +3811,20 @@ function isDefaultOpen(sectionName, config) {
|
|
|
3792
3811
|
if (typeof config === "boolean") return config;
|
|
3793
3812
|
return config.includes(sectionName);
|
|
3794
3813
|
}
|
|
3814
|
+
var PALETTE_TITLE = "Message Elements";
|
|
3815
|
+
var ADVANCED_HELP = "Browse every Slack block type \u2014 sections, images, buttons, inputs, tables and more \u2014 plus a search box.";
|
|
3816
|
+
var BASIC_HELP = "Go back to the short list: just the rich text block for writing a message.";
|
|
3817
|
+
function basicVariants(sections) {
|
|
3818
|
+
const out = [];
|
|
3819
|
+
for (const section of sections) {
|
|
3820
|
+
for (const variant of section.variants) {
|
|
3821
|
+
if (variant.basic) {
|
|
3822
|
+
out.push(variant);
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
return out;
|
|
3827
|
+
}
|
|
3795
3828
|
function filterSections(sections, query) {
|
|
3796
3829
|
const q = query.trim().toLowerCase();
|
|
3797
3830
|
if (q.length === 0) return sections;
|
|
@@ -3814,14 +3847,26 @@ function Palette({
|
|
|
3814
3847
|
defaultOpenSections = true,
|
|
3815
3848
|
showSearch = true,
|
|
3816
3849
|
searchPlaceholder = "Search blocks\u2026",
|
|
3850
|
+
mode = "advanced",
|
|
3817
3851
|
variant = "aside"
|
|
3818
3852
|
}) {
|
|
3819
3853
|
const [query, setQuery] = React.useState("");
|
|
3854
|
+
const [advancedOpen, setAdvancedOpen] = React.useState(false);
|
|
3855
|
+
const [prevMode, setPrevMode] = React.useState(mode);
|
|
3856
|
+
if (prevMode !== mode) {
|
|
3857
|
+
setPrevMode(mode);
|
|
3858
|
+
setAdvancedOpen(false);
|
|
3859
|
+
setQuery("");
|
|
3860
|
+
}
|
|
3861
|
+
const basics = React.useMemo(() => mode === "simple" ? basicVariants(sections) : [], [mode, sections]);
|
|
3862
|
+
const offersSimple = mode === "simple" && basics.length > 0;
|
|
3863
|
+
const advanced = !offersSimple || advancedOpen;
|
|
3864
|
+
const searchVisible = showSearch && advanced;
|
|
3820
3865
|
const visibleSections = React.useMemo(
|
|
3821
|
-
() =>
|
|
3822
|
-
[sections, query,
|
|
3866
|
+
() => searchVisible ? filterSections(sections, query) : sections,
|
|
3867
|
+
[sections, query, searchVisible]
|
|
3823
3868
|
);
|
|
3824
|
-
const queryActive =
|
|
3869
|
+
const queryActive = searchVisible && query.trim().length > 0;
|
|
3825
3870
|
const isSheet = variant === "sheet";
|
|
3826
3871
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3827
3872
|
"aside",
|
|
@@ -3831,38 +3876,72 @@ function Palette({
|
|
|
3831
3876
|
isSheet ? "w-full flex-1 bg-background" : "w-72 shrink-0 border-r bg-muted/20"
|
|
3832
3877
|
),
|
|
3833
3878
|
children: [
|
|
3834
|
-
|
|
3879
|
+
offersSimple || searchVisible ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3835
3880
|
"div",
|
|
3836
3881
|
{
|
|
3837
3882
|
className: cn(
|
|
3838
|
-
"sticky top-0 z-10 border-b px-3 pt-3 pb-2 backdrop-blur",
|
|
3883
|
+
"sticky top-0 z-10 flex flex-col gap-2 border-b px-3 pt-3 pb-2 backdrop-blur",
|
|
3839
3884
|
isSheet ? "bg-background" : "bg-muted/20"
|
|
3840
3885
|
),
|
|
3841
|
-
children:
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3886
|
+
children: [
|
|
3887
|
+
offersSimple ? (
|
|
3888
|
+
// Titled row: with the sections gone, simple mode's header would
|
|
3889
|
+
// otherwise be a lone right-aligned link over empty space, and
|
|
3890
|
+
// nothing would name what the rail below it holds.
|
|
3891
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
3892
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3893
|
+
"span",
|
|
3894
|
+
{
|
|
3895
|
+
className: cn(
|
|
3896
|
+
"min-w-0 truncate font-medium uppercase tracking-wide text-muted-foreground",
|
|
3897
|
+
isSheet ? "text-xs" : "text-[11px]"
|
|
3898
|
+
),
|
|
3899
|
+
children: PALETTE_TITLE
|
|
3900
|
+
}
|
|
3901
|
+
),
|
|
3902
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3903
|
+
ModeLink,
|
|
3904
|
+
{
|
|
3905
|
+
advanced,
|
|
3906
|
+
isSheet,
|
|
3907
|
+
onToggle: () => {
|
|
3908
|
+
setQuery("");
|
|
3909
|
+
setAdvancedOpen((v) => !v);
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3848
3912
|
)
|
|
3849
|
-
}
|
|
3850
|
-
),
|
|
3851
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3913
|
+
] })
|
|
3914
|
+
) : null,
|
|
3915
|
+
searchVisible ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
3916
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3917
|
+
lucideReact.Search,
|
|
3918
|
+
{
|
|
3919
|
+
className: cn(
|
|
3920
|
+
"pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground",
|
|
3921
|
+
isSheet ? "h-4 w-4" : "h-3.5 w-3.5"
|
|
3922
|
+
)
|
|
3923
|
+
}
|
|
3924
|
+
),
|
|
3925
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3926
|
+
Input,
|
|
3927
|
+
{
|
|
3928
|
+
type: "search",
|
|
3929
|
+
value: query,
|
|
3930
|
+
onChange: (e) => setQuery(e.target.value),
|
|
3931
|
+
placeholder: searchPlaceholder,
|
|
3932
|
+
"aria-label": searchPlaceholder,
|
|
3933
|
+
className: cn(isSheet ? "h-10 pl-8 text-base" : "h-8 pl-7 text-sm")
|
|
3934
|
+
}
|
|
3935
|
+
)
|
|
3936
|
+
] }) : null
|
|
3937
|
+
]
|
|
3863
3938
|
}
|
|
3864
3939
|
) : null,
|
|
3865
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex flex-col", isSheet ? "px-2 pb-6" : "p-3"), children:
|
|
3940
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex flex-col", isSheet ? "px-2 pb-6" : "p-3"), children: !advanced ? (
|
|
3941
|
+
// Simple mode: a flat list, no section headings — with this few
|
|
3942
|
+
// rows the headings would outnumber the blocks under them.
|
|
3943
|
+
basics.map((v) => /* @__PURE__ */ jsxRuntime.jsx(PaletteItem, { variant: v, onAdd: () => onAddBlock(v.factory()), mode: variant }, v.id))
|
|
3944
|
+
) : visibleSections.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-1 py-2 text-xs text-muted-foreground", children: "No blocks match." }) : visibleSections.map((section) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3866
3945
|
PaletteSection,
|
|
3867
3946
|
{
|
|
3868
3947
|
section,
|
|
@@ -3877,6 +3956,24 @@ function Palette({
|
|
|
3877
3956
|
}
|
|
3878
3957
|
);
|
|
3879
3958
|
}
|
|
3959
|
+
function ModeLink({ advanced, isSheet, onToggle }) {
|
|
3960
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
|
|
3961
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3962
|
+
"button",
|
|
3963
|
+
{
|
|
3964
|
+
type: "button",
|
|
3965
|
+
onClick: onToggle,
|
|
3966
|
+
"aria-label": advanced ? "Basic block palette" : "Advanced block palette",
|
|
3967
|
+
className: cn(
|
|
3968
|
+
"shrink-0 cursor-pointer appearance-none rounded border-0 bg-transparent p-0 font-medium text-primary underline underline-offset-2 transition-colors hover:text-primary/80 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
3969
|
+
isSheet ? "min-h-8 text-sm" : "text-xs"
|
|
3970
|
+
),
|
|
3971
|
+
children: advanced ? "Basic" : "Advanced"
|
|
3972
|
+
}
|
|
3973
|
+
) }),
|
|
3974
|
+
/* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", align: "end", children: advanced ? BASIC_HELP : ADVANCED_HELP })
|
|
3975
|
+
] }) });
|
|
3976
|
+
}
|
|
3880
3977
|
function PaletteSection({
|
|
3881
3978
|
section,
|
|
3882
3979
|
defaultOpen,
|
|
@@ -9818,6 +9915,48 @@ function EmptyState({
|
|
|
9818
9915
|
function DropIndicator2() {
|
|
9819
9916
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", className: "pointer-events-none relative my-1 h-0.5 w-full rounded-full bg-primary", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "-left-1 -top-[3px] absolute h-2 w-2 rounded-full bg-primary shadow-[0_0_0_2px_var(--color-background)]" }) });
|
|
9820
9917
|
}
|
|
9918
|
+
function SlackMark({ className }) {
|
|
9919
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9920
|
+
"svg",
|
|
9921
|
+
{
|
|
9922
|
+
className,
|
|
9923
|
+
viewBox: "0 0 122.8 122.8",
|
|
9924
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
9925
|
+
"aria-hidden": "true",
|
|
9926
|
+
focusable: "false",
|
|
9927
|
+
children: [
|
|
9928
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9929
|
+
"path",
|
|
9930
|
+
{
|
|
9931
|
+
fill: "#e01e5a",
|
|
9932
|
+
d: "M25.8 77.6c0 7.1-5.8 12.9-12.9 12.9S0 84.7 0 77.6s5.8-12.9 12.9-12.9h12.9v12.9zm6.5 0c0-7.1 5.8-12.9 12.9-12.9s12.9 5.8 12.9 12.9v32.3c0 7.1-5.8 12.9-12.9 12.9s-12.9-5.8-12.9-12.9V77.6z"
|
|
9933
|
+
}
|
|
9934
|
+
),
|
|
9935
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9936
|
+
"path",
|
|
9937
|
+
{
|
|
9938
|
+
fill: "#36c5f0",
|
|
9939
|
+
d: "M45.2 25.8c-7.1 0-12.9-5.8-12.9-12.9S38.1 0 45.2 0s12.9 5.8 12.9 12.9v12.9H45.2zm0 6.5c7.1 0 12.9 5.8 12.9 12.9s-5.8 12.9-12.9 12.9H12.9C5.8 58.1 0 52.3 0 45.2s5.8-12.9 12.9-12.9h32.3z"
|
|
9940
|
+
}
|
|
9941
|
+
),
|
|
9942
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9943
|
+
"path",
|
|
9944
|
+
{
|
|
9945
|
+
fill: "#2eb67d",
|
|
9946
|
+
d: "M97 45.2c0-7.1 5.8-12.9 12.9-12.9s12.9 5.8 12.9 12.9-5.8 12.9-12.9 12.9H97V45.2zm-6.5 0c0 7.1-5.8 12.9-12.9 12.9s-12.9-5.8-12.9-12.9V12.9C64.7 5.8 70.5 0 77.6 0s12.9 5.8 12.9 12.9v32.3z"
|
|
9947
|
+
}
|
|
9948
|
+
),
|
|
9949
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
9950
|
+
"path",
|
|
9951
|
+
{
|
|
9952
|
+
fill: "#ecb22e",
|
|
9953
|
+
d: "M77.6 97c7.1 0 12.9 5.8 12.9 12.9s-5.8 12.9-12.9 12.9-12.9-5.8-12.9-12.9V97h12.9zm0-6.5c-7.1 0-12.9-5.8-12.9-12.9s5.8-12.9 12.9-12.9h32.3c7.1 0 12.9 5.8 12.9 12.9s-5.8 12.9-12.9 12.9H77.6z"
|
|
9954
|
+
}
|
|
9955
|
+
)
|
|
9956
|
+
]
|
|
9957
|
+
}
|
|
9958
|
+
);
|
|
9959
|
+
}
|
|
9821
9960
|
var THEME_OPTIONS = [
|
|
9822
9961
|
{ value: "light", label: "Light", Icon: lucideReact.Sun },
|
|
9823
9962
|
{ value: "dark", label: "Dark", Icon: lucideReact.Moon }
|
|
@@ -9882,7 +10021,7 @@ function Toolbar2({
|
|
|
9882
10021
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-x-2 gap-y-1 border-b bg-background px-2 py-1.5 sm:px-3 sm:py-2", children: [
|
|
9883
10022
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1 sm:gap-2", children: [
|
|
9884
10023
|
loadEnabled ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", size: "sm", onClick: onOpenLoad, "aria-label": loadButtonLabel, children: [
|
|
9885
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
10024
|
+
/* @__PURE__ */ jsxRuntime.jsx(SlackMark, { className: "h-3.5 w-3.5" }),
|
|
9886
10025
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden md:inline", children: loadButtonLabel })
|
|
9887
10026
|
] }) : null,
|
|
9888
10027
|
onOpenPalette ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -10315,6 +10454,7 @@ function BlockKitchen(props) {
|
|
|
10315
10454
|
confirmUpdateLabel,
|
|
10316
10455
|
palette,
|
|
10317
10456
|
disabledBlockTypes,
|
|
10457
|
+
paletteMode,
|
|
10318
10458
|
showPaletteSearch,
|
|
10319
10459
|
paletteSearchPlaceholder,
|
|
10320
10460
|
defaultOpenSections,
|
|
@@ -10617,6 +10757,7 @@ function BlockKitchen(props) {
|
|
|
10617
10757
|
{
|
|
10618
10758
|
onAddBlock: (block) => addBlock(block),
|
|
10619
10759
|
sections: paletteSections,
|
|
10760
|
+
mode: paletteMode,
|
|
10620
10761
|
showSearch: showPaletteSearch,
|
|
10621
10762
|
searchPlaceholder: paletteSearchPlaceholder,
|
|
10622
10763
|
defaultOpenSections
|
|
@@ -10669,6 +10810,7 @@ function BlockKitchen(props) {
|
|
|
10669
10810
|
setPaletteOpen(false);
|
|
10670
10811
|
},
|
|
10671
10812
|
sections: paletteSections,
|
|
10813
|
+
mode: paletteMode,
|
|
10672
10814
|
showSearch: showPaletteSearch,
|
|
10673
10815
|
searchPlaceholder: paletteSearchPlaceholder,
|
|
10674
10816
|
defaultOpenSections,
|