From c90b23db512e6f7186394a6ea967b83f4874985e Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Wed, 29 Apr 2026 16:45:31 -0400 Subject: [PATCH] feat(types): add Card, Carousel, and Alert block types --- packages/types/src/block-kit/blocks.ts | 77 ++++++++++++++++++++++++++ packages/types/test/blocks.test-d.ts | 64 +++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 packages/types/test/blocks.test-d.ts diff --git a/packages/types/src/block-kit/blocks.ts b/packages/types/src/block-kit/blocks.ts index d8262b0a8..4b5c81091 100644 --- a/packages/types/src/block-kit/blocks.ts +++ b/packages/types/src/block-kit/blocks.ts @@ -26,6 +26,7 @@ import type { WorkflowButton, } from './block-elements'; import type { + MrkdwnElement, PlainTextElement, RawTextElement, SlackFileImageObject, @@ -54,6 +55,9 @@ export interface Block { */ export type KnownBlock = | ActionsBlock + | AlertBlock + | CardBlock + | CarouselBlock | ContextBlock | ContextActionsBlock | DividerBlock @@ -467,6 +471,79 @@ export interface PlanBlock extends Block { tasks?: (TaskCardBlock | Record)[]; } +/** + * @description A rich display block for presenting structured content such as recommendations, results, or work items. + * At least one of `hero_image`, `title`, `actions`, or `body` must be provided. + * @see {@link https://docs.slack.dev/reference/block-kit/blocks/card-block Card block reference}. + */ +export interface CardBlock extends Block { + /** + * @description The type of block. For a card block, `type` is always `card`. + */ + type: 'card'; + /** + * @description A top banner image for the card in the form of an {@link ImageElement}. + */ + hero_image?: ImageElement; + /** + * @description A small icon displayed next to the title and subtitle in the form of an {@link ImageElement}. + */ + icon?: ImageElement; + /** + * @description The title of the card in the form of a {@link MrkdwnElement}. + * Maximum length for the text in this field is 150 characters. + */ + title?: MrkdwnElement; + /** + * @description The subtitle of the card in the form of a {@link MrkdwnElement}. + * Maximum length for the text in this field is 150 characters. + */ + subtitle?: MrkdwnElement; + /** + * @description The body text of the card in the form of a {@link MrkdwnElement}. + * Maximum length for the text in this field is 200 characters. + */ + body?: MrkdwnElement; + /** + * @description An array of {@link Button} elements displayed at the bottom of the card. + */ + actions?: Button[]; +} + +/** + * @description A prominent notice block for displaying warnings, status updates, or other important information. + * @see {@link https://docs.slack.dev/reference/block-kit/blocks/alert-block Alert block reference}. + */ +export interface AlertBlock extends Block { + /** + * @description The type of block. For an alert block, `type` is always `alert`. + */ + type: 'alert'; + /** + * @description The alert message content in the form of a {@link TextObject}. + */ + text: TextObject; + /** + * @description The severity level of the alert. Defaults to `"default"` if omitted. + */ + level?: 'default' | 'info' | 'warning' | 'error' | 'success'; +} + +/** + * @description A horizontally scrollable collection of {@link CardBlock} elements. + * @see {@link https://docs.slack.dev/reference/block-kit/blocks/carousel-block Carousel block reference}. + */ +export interface CarouselBlock extends Block { + /** + * @description The type of block. For a carousel block, `type` is always `carousel`. + */ + type: 'carousel'; + /** + * @description An array of {@link CardBlock} elements. Minimum 1, maximum 10 cards. + */ + elements: CardBlock[]; +} + /** * @description Displays an embedded video player. A video block is designed to embed videos in all app surfaces (e.g. * link unfurls, messages, modals, App Home) — anywhere you can put blocks! To use the video block within your app, you diff --git a/packages/types/test/blocks.test-d.ts b/packages/types/test/blocks.test-d.ts new file mode 100644 index 000000000..e154b6c1b --- /dev/null +++ b/packages/types/test/blocks.test-d.ts @@ -0,0 +1,64 @@ +import { expectAssignable, expectError } from 'tsd'; +import type { AlertBlock, CardBlock, CarouselBlock, KnownBlock } from '../src/index'; + +// CardBlock +// -- sad path +expectError({}); // missing type +expectError({ type: 'card', title: { type: 'plain_text', text: 'wrong type' } }); // title must be mrkdwn +// -- happy path +expectAssignable({ type: 'card' }); +expectAssignable({ + type: 'card', + title: { type: 'mrkdwn', text: 'Title' }, +}); +expectAssignable({ + type: 'card', + icon: { type: 'image', image_url: 'https://example.com/icon.png', alt_text: 'icon' }, + title: { type: 'mrkdwn', text: 'Lumon Industries' }, + subtitle: { type: 'mrkdwn', text: 'Committed to work-life balance' }, + hero_image: { type: 'image', image_url: 'https://example.com/hero.png', alt_text: 'hero' }, + body: { type: 'mrkdwn', text: 'Please enjoy each card equally.' }, + actions: [{ type: 'button', text: { type: 'plain_text', text: 'Click' }, action_id: 'btn' }], +}); +expectAssignable({ type: 'card', body: { type: 'mrkdwn', text: 'hi' } }); + +// AlertBlock +// -- sad path +expectError({}); // missing type and text +expectError({ type: 'alert' }); // missing required text +// -- happy path +expectAssignable({ + type: 'alert', + text: { type: 'mrkdwn', text: 'Something happened' }, +}); +expectAssignable({ + type: 'alert', + text: { type: 'plain_text', text: 'Simple alert' }, + level: 'warning', +}); +expectAssignable({ + type: 'alert', + text: { type: 'mrkdwn', text: 'Notice' }, + level: 'error', +}); + +// CarouselBlock +// -- sad path +expectError({}); // missing type and elements +expectError({ type: 'carousel' }); // missing required elements +// -- happy path +expectAssignable({ + type: 'carousel', + elements: [{ type: 'card', title: { type: 'mrkdwn', text: 'Card 1' } }], +}); +expectAssignable({ + type: 'carousel', + elements: [ + { type: 'card', title: { type: 'mrkdwn', text: 'Card 1' } }, + { type: 'card', body: { type: 'mrkdwn', text: 'Card 2 body' } }, + ], +}); +expectAssignable({ + type: 'carousel', + elements: [{ type: 'card' }], +});