@nkootstra/block-kit 0.1.0 → 0.2.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/README.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  `@nkootstra/block-kit` renders Slack [Block Kit](https://docs.slack.dev/block-kit/) JSON in React, pixel-for-pixel the way Slack does. Pass it the `blocks` your app sends to `chat.postMessage` or `views.open` and it draws the message, modal or Home tab, with working buttons, menus, date pickers and inputs.
4
4
 
5
- Documentation: **<https://block-kit.kootstra.io>**
5
+ Documentation: **<https://docs.block-kit.dev>**
6
6
 
7
7
  ## Install
8
8
 
9
- The package needs React 19 or later.
9
+ The package needs React 18 or later.
10
10
 
11
11
  ```sh
12
12
  bun add @nkootstra/block-kit @fontsource/lato @fontsource/roboto-mono
@@ -33,7 +33,7 @@ export function App() {
33
33
  }
34
34
  ```
35
35
 
36
- The [installation guide](https://block-kit.kootstra.io/installation) lists the remaining font weights and the entry points, and the [quickstart](https://block-kit.kootstra.io/quickstart) wires up an interactive approval message.
36
+ The [installation guide](https://docs.block-kit.dev/installation) lists the remaining font weights and the entry points, and the [quickstart](https://docs.block-kit.dev/quickstart) wires up an interactive approval message.
37
37
 
38
38
  ## Entry points
39
39
 
@@ -582,6 +582,8 @@ interface PayloadContext {
582
582
  type SubmitResult = ViewResponseAction | undefined | void;
583
583
  interface BlockKitContextValue {
584
584
  surface: Surface;
585
+ /** The provider's `theme`. Popovers, tooltips and dialogs render outside its wrapper, so they set it themselves. */
586
+ theme?: "light" | "dark";
585
587
  onAction?: (action: BlockAction, context: ActionContext) => void;
586
588
  /** Same actions as `onAction`, wrapped in the full Slack `block_actions` payload (team/user/container/...). */
587
589
  onPayload?: (payload: ReturnType<typeof buildBlockActionsPayload>, context: PayloadContext) => void;
@@ -906,4 +908,4 @@ declare const VALIDATION_MESSAGES: {
906
908
  declare function validateView(blocks: readonly Json[] | readonly unknown[], state: StateValues): Record<string, string>;
907
909
  //#endregion
908
910
  export { BlockSuggestionPayload as $, BlockAction as A, StackedView as B, SlackAttachment as C, BlockProps as Ct, elementComponents as D, SlackReaction as E, EmojiOptions as F, SurfaceScopeProps as G, SubmitResult as H, MessageApi as I, ViewsApi as J, SurfaceScopeValue as K, MessageUpdate as L, BlockKitProvider as M, BlockKitProviderProps as N, Element as O, ElementState as P, BlockActionsPayload as Q, PayloadContext as R, MessageProps as S, Blocks as St, SlackMessageLike as T, Json as Tt, Surface as U, StateValues as V, SurfaceScope as W, useContainer as X, useBlockKit as Y, useLiveView as Z, HomeTabView as _, buildBlockSuggestionPayload as _t, Tooltip as a, MessageBlockActionsPayload as at, Message as b, blockComponents as bt, TextObject as c, PayloadIdentity as ct, ViewProps as d, SuggestionOption as dt, BuildBlockActionsPayloadOptions as et, Modal as f, ViewBlockActionsPayload as ft, HomeTabProps as g, buildBlockActionsPayload as gt, HomeTab as h, ViewResponseAction as ht, UserMentionProps as i, Container as it, BlockKitContextValue as j, ActionContext as k, AnyView as l, PayloadTeam as lt, ModalView as m, ViewLike as mt, validateView as n, BuildViewClosedPayloadOptions as nt, TooltipProps as o, MessageContainer as ot, ModalProps as p, ViewContainer as pt, UserProfile as q, UserMention as r, BuildViewSubmissionPayloadOptions as rt, Text as s, OptionsResponse as st, VALIDATION_MESSAGES as t, BuildBlockSuggestionPayloadOptions as tt, View as u, PayloadUser as ut, Mrkdwn as v, buildViewClosedPayload as vt, SlackAttachmentField as w, ElementProps as wt, MessageApp as x, Block as xt, MrkdwnProps as y, buildViewSubmissionPayload as yt, Resolvers as z };
909
- //# sourceMappingURL=index-BV_5_J_Q.d.ts.map
911
+ //# sourceMappingURL=index-FPjJcmn3.d.ts.map
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
 
2
- import { $ as BlockSuggestionPayload, A as BlockAction, B as StackedView, C as SlackAttachment, Ct as BlockProps, D as elementComponents, E as SlackReaction, F as EmojiOptions, G as SurfaceScopeProps, H as SubmitResult, I as MessageApi, J as ViewsApi, K as SurfaceScopeValue, L as MessageUpdate, M as BlockKitProvider, N as BlockKitProviderProps, O as Element, P as ElementState, Q as BlockActionsPayload, R as PayloadContext, S as MessageProps, St as Blocks, T as SlackMessageLike, Tt as Json, U as Surface, V as StateValues, W as SurfaceScope, X as useContainer, Y as useBlockKit, Z as useLiveView, _ as HomeTabView, _t as buildBlockSuggestionPayload, a as Tooltip, at as MessageBlockActionsPayload, b as Message, bt as blockComponents, c as TextObject, ct as PayloadIdentity, d as ViewProps, dt as SuggestionOption, et as BuildBlockActionsPayloadOptions, f as Modal, ft as ViewBlockActionsPayload, g as HomeTabProps, gt as buildBlockActionsPayload, h as HomeTab, ht as ViewResponseAction, i as UserMentionProps, it as Container, j as BlockKitContextValue, k as ActionContext, l as AnyView, lt as PayloadTeam, m as ModalView, mt as ViewLike, n as validateView, nt as BuildViewClosedPayloadOptions, o as TooltipProps, ot as MessageContainer, p as ModalProps, pt as ViewContainer, q as UserProfile, r as UserMention, rt as BuildViewSubmissionPayloadOptions, s as Text, st as OptionsResponse, t as VALIDATION_MESSAGES, tt as BuildBlockSuggestionPayloadOptions, u as View, ut as PayloadUser, v as Mrkdwn, vt as buildViewClosedPayload, w as SlackAttachmentField, wt as ElementProps, x as MessageApp, xt as Block, y as MrkdwnProps, yt as buildViewSubmissionPayload, z as Resolvers } from "./index-BV_5_J_Q.js";
2
+ import { $ as BlockSuggestionPayload, A as BlockAction, B as StackedView, C as SlackAttachment, Ct as BlockProps, D as elementComponents, E as SlackReaction, F as EmojiOptions, G as SurfaceScopeProps, H as SubmitResult, I as MessageApi, J as ViewsApi, K as SurfaceScopeValue, L as MessageUpdate, M as BlockKitProvider, N as BlockKitProviderProps, O as Element, P as ElementState, Q as BlockActionsPayload, R as PayloadContext, S as MessageProps, St as Blocks, T as SlackMessageLike, Tt as Json, U as Surface, V as StateValues, W as SurfaceScope, X as useContainer, Y as useBlockKit, Z as useLiveView, _ as HomeTabView, _t as buildBlockSuggestionPayload, a as Tooltip, at as MessageBlockActionsPayload, b as Message, bt as blockComponents, c as TextObject, ct as PayloadIdentity, d as ViewProps, dt as SuggestionOption, et as BuildBlockActionsPayloadOptions, f as Modal, ft as ViewBlockActionsPayload, g as HomeTabProps, gt as buildBlockActionsPayload, h as HomeTab, ht as ViewResponseAction, i as UserMentionProps, it as Container, j as BlockKitContextValue, k as ActionContext, l as AnyView, lt as PayloadTeam, m as ModalView, mt as ViewLike, n as validateView, nt as BuildViewClosedPayloadOptions, o as TooltipProps, ot as MessageContainer, p as ModalProps, pt as ViewContainer, q as UserProfile, r as UserMention, rt as BuildViewSubmissionPayloadOptions, s as Text, st as OptionsResponse, t as VALIDATION_MESSAGES, tt as BuildBlockSuggestionPayloadOptions, u as View, ut as PayloadUser, v as Mrkdwn, vt as buildViewClosedPayload, w as SlackAttachmentField, wt as ElementProps, x as MessageApp, xt as Block, y as MrkdwnProps, yt as buildViewSubmissionPayload, z as Resolvers } from "./index-FPjJcmn3.js";
3
3
  export { type ActionContext, type AnyView, Block, type BlockAction, type BlockActionsPayload, type BlockKitContextValue, BlockKitProvider, type BlockKitProviderProps, type BlockProps, type BlockSuggestionPayload, Blocks, type BuildBlockActionsPayloadOptions, type BuildBlockSuggestionPayloadOptions, type BuildViewClosedPayloadOptions, type BuildViewSubmissionPayloadOptions, type Container, Element, type ElementProps, type ElementState, type EmojiOptions, HomeTab, type HomeTabProps, type HomeTabView, type Json, Message, type MessageApi, type MessageApp, type MessageBlockActionsPayload, type MessageContainer, type MessageProps, type MessageUpdate, Modal, type ModalProps, type ModalView, Mrkdwn, type MrkdwnProps, type OptionsResponse, type PayloadContext, type PayloadIdentity, type PayloadTeam, type PayloadUser, type Resolvers, type SlackAttachment, type SlackAttachmentField, type SlackMessageLike, type SlackReaction, type StackedView, type StateValues, type SubmitResult, type SuggestionOption, type Surface, SurfaceScope, type SurfaceScopeProps, type SurfaceScopeValue, Text, type TextObject, Tooltip, type TooltipProps, UserMention, type UserMentionProps, type UserProfile, VALIDATION_MESSAGES, View, type ViewBlockActionsPayload, type ViewContainer, type ViewLike, type ViewProps, type ViewResponseAction, type ViewsApi, blockComponents, buildBlockActionsPayload, buildBlockSuggestionPayload, buildViewClosedPayload, buildViewSubmissionPayload, elementComponents, useBlockKit, useContainer, useLiveView, validateView };
package/dist/index.js CHANGED
@@ -1035,6 +1035,7 @@ function BlockKitProvider(props) {
1035
1035
  const stackedView = useContext(StackEntryContext);
1036
1036
  const value = useMemo(() => ({
1037
1037
  surface,
1038
+ theme,
1038
1039
  onAction,
1039
1040
  onPayload,
1040
1041
  onSubmit,
@@ -1054,6 +1055,7 @@ function BlockKitProvider(props) {
1054
1055
  respond
1055
1056
  }), [
1056
1057
  surface,
1058
+ theme,
1057
1059
  onAction,
1058
1060
  onPayload,
1059
1061
  onSubmit,
@@ -1226,6 +1228,13 @@ function actionTs(now = Date.now()) {
1226
1228
  return (now / 1e3).toFixed(6);
1227
1229
  }
1228
1230
  //#endregion
1231
+ //#region src/useClientLayoutEffect.ts
1232
+ /**
1233
+ * `useLayoutEffect` in the browser and `useEffect` on the server, where neither runs. React 18
1234
+ * warns about every `useLayoutEffect` it meets while server rendering; React 19 doesn't.
1235
+ */
1236
+ const useClientLayoutEffect = typeof document === "undefined" ? useEffect : useLayoutEffect;
1237
+ //#endregion
1229
1238
  //#region src/Tooltip.tsx
1230
1239
  /** How long the pointer rests on an anchor before its tooltip shows. */
1231
1240
  const SHOW_DELAY_MS = 300;
@@ -1235,6 +1244,7 @@ const SHOW_DELAY_MS = 300;
1235
1244
  * Slack. Portalled to `<body>` so scroll containers and `overflow: hidden` can't clip it.
1236
1245
  */
1237
1246
  function Tooltip({ label, children, placement = "top" }) {
1247
+ const { theme } = useBlockKit();
1238
1248
  const id = useId();
1239
1249
  const [open, setOpen] = useState(false);
1240
1250
  const [position, setPosition] = useState(null);
@@ -1242,7 +1252,7 @@ function Tooltip({ label, children, placement = "top" }) {
1242
1252
  const tipRef = useRef(null);
1243
1253
  const timer = useRef(void 0);
1244
1254
  useEffect(() => () => clearTimeout(timer.current), []);
1245
- useLayoutEffect(() => {
1255
+ useClientLayoutEffect(() => {
1246
1256
  if (!open) {
1247
1257
  setPosition(null);
1248
1258
  return;
@@ -1298,6 +1308,7 @@ function Tooltip({ label, children, placement = "top" }) {
1298
1308
  ref: tipRef,
1299
1309
  id,
1300
1310
  role: "tooltip",
1311
+ "data-theme": theme,
1301
1312
  className: `sbk-tooltip sbk-tooltip--${position?.side ?? placement}`,
1302
1313
  style: position ? {
1303
1314
  top: position.top,
@@ -5613,13 +5624,13 @@ function localTime(timeZone) {
5613
5624
  * `resolvers.userProfile` and falling back to the display name alone.
5614
5625
  */
5615
5626
  function UserMention({ id, name }) {
5616
- const { resolvers } = useBlockKit();
5627
+ const { resolvers, theme } = useBlockKit();
5617
5628
  const [open, setOpen] = useState(false);
5618
5629
  const [position, setPosition] = useState(null);
5619
5630
  const anchorRef = useRef(null);
5620
5631
  const cardRef = useRef(null);
5621
5632
  const cardId = useId();
5622
- useLayoutEffect(() => {
5633
+ useClientLayoutEffect(() => {
5623
5634
  if (!open) {
5624
5635
  setPosition(null);
5625
5636
  return;
@@ -5681,6 +5692,7 @@ function UserMention({ id, name }) {
5681
5692
  role: "dialog",
5682
5693
  "aria-label": profile.name,
5683
5694
  className: "sbk-root sbk-profile-card",
5695
+ "data-theme": theme,
5684
5696
  style: position ? {
5685
5697
  top: position.top,
5686
5698
  left: position.left
@@ -5865,10 +5877,12 @@ function Text({ text, emojiSize = 22 }) {
5865
5877
  //#region src/confirm/ConfirmDialog.tsx
5866
5878
  /** Slack's confirmation dialog: blocks the triggering action until the user picks confirm/deny. */
5867
5879
  function ConfirmDialog({ confirm, onConfirm, onDeny }) {
5880
+ const { theme } = useBlockKit();
5868
5881
  const confirmStyle = confirm.style ?? "primary";
5869
5882
  const node = /* @__PURE__ */ jsx("div", {
5870
5883
  className: "sbk-confirm__overlay",
5871
5884
  role: "presentation",
5885
+ "data-theme": theme,
5872
5886
  onClick: onDeny,
5873
5887
  children: /* @__PURE__ */ jsxs("div", {
5874
5888
  className: "sbk-confirm",
@@ -5972,7 +5986,9 @@ function Button({ element, blockId }) {
5972
5986
  * Slack's `focus_on_load`: the element takes focus when its modal or Home tab opens. Slack ignores
5973
5987
  * it in messages and allows it on only one element per view.
5974
5988
  *
5975
- * Pass the element's own ref when it already has one; otherwise attach the returned ref.
5989
+ * Pass the element's own ref when it already has one; otherwise attach the returned ref. The return
5990
+ * type is `useRef`'s own, which differs between @types/react 18 and 19, so it fits a `ref` prop in
5991
+ * both.
5976
5992
  */
5977
5993
  function useFocusOnLoad(element, existing) {
5978
5994
  const { surface } = useBlockKit();
@@ -6236,13 +6252,14 @@ const DEFAULT_GAP = 4;
6236
6252
  * from.
6237
6253
  */
6238
6254
  function Popover({ anchorRef, onDismiss, gap = DEFAULT_GAP, children }) {
6255
+ const { theme } = useBlockKit();
6239
6256
  const layerRef = useRef(null);
6240
6257
  const [position, setPosition] = useState(null);
6241
6258
  const dismiss = useRef(onDismiss);
6242
- useLayoutEffect(() => {
6259
+ useClientLayoutEffect(() => {
6243
6260
  dismiss.current = onDismiss;
6244
6261
  });
6245
- useLayoutEffect(() => {
6262
+ useClientLayoutEffect(() => {
6246
6263
  function place() {
6247
6264
  const anchor = anchorRef.current;
6248
6265
  const layer = layerRef.current;
@@ -6286,6 +6303,7 @@ function Popover({ anchorRef, onDismiss, gap = DEFAULT_GAP, children }) {
6286
6303
  return createPortal(/* @__PURE__ */ jsx("div", {
6287
6304
  ref: layerRef,
6288
6305
  className: "sbk-root sbk-popover",
6306
+ "data-theme": theme,
6289
6307
  style: position ?? {
6290
6308
  top: 0,
6291
6309
  left: 0,
@@ -7733,24 +7751,20 @@ function Alert({ block }) {
7733
7751
  }
7734
7752
  //#endregion
7735
7753
  //#region src/blocks/Callout.tsx
7736
- /**
7737
- * Measured from Builder references (`callout` background_color: "green" → #f4ffdb, "blue" →
7738
- * #e3f8ff). The remaining keys aren't covered by a fixture; they're extrapolated to the same
7739
- * pastel-tint style and may not match Slack's exact values.
7740
- */
7741
- const BACKGROUND_COLORS = {
7742
- green: "#f4ffdb",
7743
- blue: "#e3f8ff",
7744
- red: "#ffeceb",
7745
- yellow: "#fff8db",
7746
- purple: "#f3ecff",
7747
- gray: "#f4f4f4",
7748
- grey: "#f4f4f4"
7749
- };
7754
+ /** Callout colours; each has a token per theme (Callout.css, and Message.css for dark). */
7755
+ const BACKGROUND_COLORS = /* @__PURE__ */ new Set([
7756
+ "green",
7757
+ "blue",
7758
+ "red",
7759
+ "yellow",
7760
+ "purple",
7761
+ "gray"
7762
+ ]);
7750
7763
  function Callout({ block }) {
7751
7764
  const json = block;
7752
7765
  const color = typeof json.background_color === "string" ? json.background_color : void 0;
7753
- const background = (color && BACKGROUND_COLORS[color]) ?? BACKGROUND_COLORS.gray;
7766
+ const key = color === "grey" ? "gray" : color;
7767
+ const background = `var(--sbk-callout-${key && BACKGROUND_COLORS.has(key) ? key : "gray"}-bg)`;
7754
7768
  const children = json.child_blocks ?? [];
7755
7769
  return /* @__PURE__ */ jsx("div", {
7756
7770
  className: "sbk-callout",
@@ -8174,7 +8188,8 @@ function Collapse({ open, className, children }) {
8174
8188
  const [shown, setShown] = useState(open);
8175
8189
  if (open && !shown) setShown(true);
8176
8190
  const was = useRef(open);
8177
- useLayoutEffect(() => {
8191
+ useClientLayoutEffect(() => {
8192
+ ref.current?.toggleAttribute("inert", !open);
8178
8193
  if (was.current === open) return;
8179
8194
  was.current = open;
8180
8195
  const el = ref.current;
@@ -8192,7 +8207,6 @@ function Collapse({ open, className, children }) {
8192
8207
  return /* @__PURE__ */ jsx("div", {
8193
8208
  ref,
8194
8209
  className: className ? `sbk-collapse ${className}` : "sbk-collapse",
8195
- inert: !open,
8196
8210
  onTransitionEnd: (e) => {
8197
8211
  if (e.target !== e.currentTarget || e.propertyName !== "height") return;
8198
8212
  if (open) e.currentTarget.style.height = "";
@@ -8666,26 +8680,12 @@ const CHART_COLORS = [
8666
8680
  "#c474d3",
8667
8681
  "#0e9dd3"
8668
8682
  ];
8669
- /** Area-fill tints for the first two palette colours (measured); others get an approximation. */
8670
- const AREA_FILLS = {
8671
- "#e96825": "rgb(255,237,229)",
8672
- "#20a271": "rgb(227,255,243)"
8673
- };
8674
- function hexToRgb(hex) {
8675
- const n = Number.parseInt(hex.slice(1), 16);
8676
- return [
8677
- n >> 16 & 255,
8678
- n >> 8 & 255,
8679
- n & 255
8680
- ];
8681
- }
8682
- /** Fill colour for a series' area, tinted ~88% toward white when not one of the measured pairs. */
8683
- function areaFillFor(color) {
8684
- const known = AREA_FILLS[color.toLowerCase()];
8685
- if (known) return known;
8686
- const [r, g, b] = hexToRgb(color);
8687
- const mix = (c) => Math.round(c + (255 - c) * .88);
8688
- return `rgb(${mix(r)},${mix(g)},${mix(b)})`;
8683
+ /**
8684
+ * Fill of a series' area, as a token per theme (DataVisualization.css, and Message.css for dark):
8685
+ * a light tint of the series colour, or a dark one.
8686
+ */
8687
+ function areaFillForIndex(i) {
8688
+ return `var(--sbk-chart-area-${i % CHART_COLORS.length + 1})`;
8689
8689
  }
8690
8690
  function colorForIndex(i) {
8691
8691
  return CHART_COLORS[i % CHART_COLORS.length] ?? "#e96825";
@@ -8898,7 +8898,7 @@ function CartesianChart({ type, series, categories }) {
8898
8898
  const color = colorForIndex(si);
8899
8899
  return /* @__PURE__ */ jsxs("g", { children: [type === "area" ? /* @__PURE__ */ jsx("path", {
8900
8900
  d: `${smoothLinePath(points)}L${points[points.length - 1]?.x ?? 0} ${zeroY}L${points[0]?.x ?? 0} ${zeroY}Z`,
8901
- fill: areaFillFor(color),
8901
+ style: { fill: areaFillForIndex(si) },
8902
8902
  fillOpacity: .7
8903
8903
  }) : null, /* @__PURE__ */ jsx("path", {
8904
8904
  d: smoothLinePath(points),