From 4159756902cd93b85859ce1040ef67c228654faa Mon Sep 17 00:00:00 2001 From: Kyle McDonald Date: Fri, 4 Sep 2026 12:10:34 -0500 Subject: [PATCH 1/9] fix(react): pin and upgrade @telegraph/combobox to 0.6.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @knocklabs/react declared "@telegraph/combobox": ">=0.5.0", so every consumer resolved to whatever was latest on npm — including 0.6.0, the Base UI rewrite, against code only ever tested on 0.5.0. Pin the range to ^0.6.0 across the workspace, bump the lockfile, and take the upgrade deliberately. Two things the upgrade needed: - SlackChannelCombobox renders an icon beside each channel name, so its options have element children. 0.6.0 resolves an option's label as `label || children || value` and falls back to `value` whenever the label is not a string, so the trigger announced the raw Slack channel id. Each option now passes an explicit `label`. - 0.6.0 tightened Combobox.Root's `value` type, which no longer accepts `(string | undefined)[]`. Both multi-select call sites now narrow. Add coverage for all three comboboxes, which had none, and polyfill ResizeObserver and scrollIntoView so Base UI can mount under jsdom. --- .changeset/great-pugs-shake.md | 9 + examples/nextjs-app-dir-example/package.json | 2 +- examples/nextjs-example/package.json | 2 +- packages/react/package.json | 2 +- packages/react/setupTest.ts | 14 + .../MsTeamsChannelInTeamCombobox.tsx | 11 +- .../SlackChannelCombobox.tsx | 13 +- .../ms-teams/MsTeamsChannelCombobox.test.tsx | 135 +++++++ .../test/slack/SlackChannelCombobox.test.tsx | 101 ++++++ yarn.lock | 342 +++++++++++++++++- 10 files changed, 614 insertions(+), 17 deletions(-) create mode 100644 .changeset/great-pugs-shake.md create mode 100644 packages/react/test/ms-teams/MsTeamsChannelCombobox.test.tsx create mode 100644 packages/react/test/slack/SlackChannelCombobox.test.tsx diff --git a/.changeset/great-pugs-shake.md b/.changeset/great-pugs-shake.md new file mode 100644 index 000000000..cec77c301 --- /dev/null +++ b/.changeset/great-pugs-shake.md @@ -0,0 +1,9 @@ +--- +"@knocklabs/react": patch +--- + +Pin `@telegraph/combobox` to `^0.6.0` and fix the Slack channel combobox's accessible name + +`@knocklabs/react` declared `@telegraph/combobox: ">=0.5.0"`, so consumers resolved to whatever the latest release was. The range is now a caret range, and the package is upgraded to 0.6.0. + +`SlackChannelCombobox` renders an icon beside each channel name, so its options have element children. In 0.6.0 an option's accessible name falls back to its `value` when the label is not a string, which made the trigger announce the raw Slack channel id instead of the channel name. Each option now passes an explicit `label`. diff --git a/examples/nextjs-app-dir-example/package.json b/examples/nextjs-app-dir-example/package.json index e6abfbc27..affacc5ca 100644 --- a/examples/nextjs-app-dir-example/package.json +++ b/examples/nextjs-app-dir-example/package.json @@ -15,7 +15,7 @@ "@knocklabs/node": "^1.30.0", "@knocklabs/react": "workspace:*", "@telegraph/button": ">=0.8.0", - "@telegraph/combobox": ">=0.5.0", + "@telegraph/combobox": "^0.6.0", "@telegraph/icon": ">=0.6.0", "@telegraph/input": ">=0.5.0", "@telegraph/layout": ">=0.6.1", diff --git a/examples/nextjs-example/package.json b/examples/nextjs-example/package.json index 3efd44dac..4c94aa56b 100644 --- a/examples/nextjs-example/package.json +++ b/examples/nextjs-example/package.json @@ -17,7 +17,7 @@ "@knocklabs/node": "^1.30.0", "@knocklabs/react": "workspace:^", "@telegraph/button": ">=0.8.0", - "@telegraph/combobox": ">=0.5.0", + "@telegraph/combobox": "^0.6.0", "@telegraph/icon": ">=0.6.0", "@telegraph/input": ">=0.5.0", "@telegraph/layout": ">=0.6.1", diff --git a/packages/react/package.json b/packages/react/package.json index 37c7d4fb9..ca84668db 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -79,7 +79,7 @@ "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-hover-card": "^1.1.15", "@telegraph/button": ">=0.8.0", - "@telegraph/combobox": ">=0.5.0", + "@telegraph/combobox": "^0.6.0", "@telegraph/icon": ">=0.6.0", "@telegraph/layout": ">=0.6.1", "@telegraph/segmented-control": ">=0.4.0", diff --git a/packages/react/setupTest.ts b/packages/react/setupTest.ts index e26219327..060621dfd 100644 --- a/packages/react/setupTest.ts +++ b/packages/react/setupTest.ts @@ -5,3 +5,17 @@ import { afterEach } from "vitest"; afterEach(() => { cleanup(); }); + +// jsdom implements neither of these, and Base UI (via @telegraph/combobox) +// calls both while positioning its popup. +if (!("ResizeObserver" in globalThis)) { + globalThis.ResizeObserver = class ResizeObserver { + observe() {} + unobserve() {} + disconnect() {} + }; +} + +if (!Element.prototype.scrollIntoView) { + Element.prototype.scrollIntoView = () => {}; +} diff --git a/packages/react/src/modules/ms-teams/components/MsTeamsChannelCombobox/MsTeamsChannelInTeamCombobox.tsx b/packages/react/src/modules/ms-teams/components/MsTeamsChannelCombobox/MsTeamsChannelInTeamCombobox.tsx index bd1dcfae4..113187685 100644 --- a/packages/react/src/modules/ms-teams/components/MsTeamsChannelCombobox/MsTeamsChannelInTeamCombobox.tsx +++ b/packages/react/src/modules/ms-teams/components/MsTeamsChannelCombobox/MsTeamsChannelInTeamCombobox.tsx @@ -64,12 +64,11 @@ export const MsTeamsChannelInTeamCombobox: FunctionComponent< const comboboxValue = useMemo( () => currentConnections - ?.filter( - (connection) => - connection.ms_teams_channel_id && - isChannelInThisTeam(connection.ms_teams_channel_id), - ) - .map((connection) => connection.ms_teams_channel_id), + ?.map((connection) => connection.ms_teams_channel_id) + .filter( + (channelId): channelId is string => + !!channelId && isChannelInThisTeam(channelId), + ), [currentConnections, isChannelInThisTeam], ); diff --git a/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx b/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx index 203833ac5..4fa139c61 100644 --- a/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx +++ b/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx @@ -142,7 +142,10 @@ export const SlackChannelCombobox: FunctionComponent< ]); const comboboxValue = useMemo( - () => currentConnectedChannels.map((connection) => connection.channel_id), + () => + currentConnectedChannels + .map((connection) => connection.channel_id) + .filter((channelId): channelId is string => !!channelId), [currentConnectedChannels], ); @@ -198,7 +201,13 @@ export const SlackChannelCombobox: FunctionComponent< /> {slackChannels.map((channel) => ( - + [ + { id: "team_1", displayName: "Engineering" }, + { id: "team_2", displayName: "Design" }, +]; + +const buildChannels = () => [ + { id: "channel_1", displayName: "General" }, + { id: "channel_2", displayName: "Releases" }, +]; + +let teams = buildTeams(); +let channels = buildChannels(); +let connectedChannels: Array<{ + ms_teams_team_id?: string; + ms_teams_channel_id?: string; +}> = []; + +vi.mock("@knocklabs/react-core", async () => { + const actual = await vi.importActual("@knocklabs/react-core"); + return { + ...actual, + useKnockMsTeamsClient: () => ({ connectionStatus: "connected" }), + useMsTeamsTeams: () => ({ data: teams, isLoading: false }), + useMsTeamsChannels: () => ({ data: channels, isLoading: false }), + useConnectedMsTeamsChannels: () => ({ + data: connectedChannels, + updateConnectedChannels: vi.fn().mockResolvedValue(undefined), + error: null, + }), + }; +}); + +const recipientObject = { objectId: "object_123", collection: "projects" }; + +beforeEach(() => { + teams = buildTeams(); + channels = buildChannels(); + connectedChannels = []; +}); + +describe("MsTeamsTeamCombobox", () => { + // These options pass string children, so @telegraph/combobox resolves the + // trigger's accessible name from them without an explicit `label` prop. + test("trigger announces the selected team by display name", () => { + render( + team.id === "team_1")!} + onTeamChange={vi.fn()} + getChannelCount={() => 0} + />, + ); + + expect(screen.getByRole("combobox")).toHaveAccessibleName("Engineering"); + }); + + test("trigger includes the connected channel count", () => { + render( + team.id === "team_1")!} + onTeamChange={vi.fn()} + getChannelCount={() => 2} + />, + ); + + expect(screen.getByRole("combobox")).toHaveAccessibleName( + "Engineering (2)", + ); + }); + + test("shows the placeholder when no team is selected", () => { + render( + 0} + />, + ); + + expect(screen.getByRole("combobox")).toHaveAccessibleName("Select team"); + }); +}); + +describe("MsTeamsChannelInTeamCombobox", () => { + test("trigger announces connected channels by display name", () => { + connectedChannels = [ + { ms_teams_team_id: "team_1", ms_teams_channel_id: "channel_1" }, + ]; + + render( + , + ); + + expect(screen.getByRole("combobox")).toHaveAccessibleName("General"); + }); + + test("ignores connections that belong to another team", () => { + connectedChannels = [ + { ms_teams_team_id: "team_1", ms_teams_channel_id: "channel_1" }, + { ms_teams_team_id: "team_2", ms_teams_channel_id: "channel_from_team_2" }, + ]; + + render( + , + ); + + expect(screen.getByRole("combobox")).toHaveAccessibleName("General"); + }); + + test("shows the placeholder when no channels are connected", () => { + render( + , + ); + + expect(screen.getByRole("combobox")).toHaveAccessibleName( + "Select channels", + ); + }); +}); diff --git a/packages/react/test/slack/SlackChannelCombobox.test.tsx b/packages/react/test/slack/SlackChannelCombobox.test.tsx new file mode 100644 index 000000000..a9929f510 --- /dev/null +++ b/packages/react/test/slack/SlackChannelCombobox.test.tsx @@ -0,0 +1,101 @@ +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { beforeEach, describe, expect, test, vi } from "vitest"; + +import { SlackChannelCombobox } from "../../src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox"; + +const slackChannels = [ + { id: "C0123ABC", name: "general", is_private: false }, + { id: "C0456DEF", name: "engineering", is_private: true }, +]; + +let connectedChannels: Array<{ channel_id?: string }> = []; +const updateConnectedChannels = vi.fn().mockResolvedValue(undefined); + +vi.mock("@knocklabs/react-core", async () => { + const actual = await vi.importActual("@knocklabs/react-core"); + return { + ...actual, + useTranslations: () => ({ t: (k: string) => k }), + useKnockSlackClient: () => ({ + connectionStatus: "connected", + errorLabel: null, + }), + useSlackChannels: () => ({ data: slackChannels, isLoading: false }), + useConnectedSlackChannels: () => ({ + data: connectedChannels, + updateConnectedChannels, + error: null, + updating: false, + }), + }; +}); + +const recipientObject = { objectId: "object_123", collection: "projects" }; + +const renderCombobox = () => + render(); + +describe("SlackChannelCombobox", () => { + beforeEach(() => { + connectedChannels = []; + updateConnectedChannels.mockClear(); + }); + + test("trigger announces connected channels by name, not by id", () => { + connectedChannels = [{ channel_id: "C0123ABC" }]; + + renderCombobox(); + + // Each option renders an icon beside the channel name, so its children are + // elements rather than a string. @telegraph/combobox resolves an option's + // accessible label from `label || children || value` and falls back to + // `value` for non-string labels, so without an explicit `label` prop the + // trigger announces the raw Slack channel id. + expect(screen.getByRole("combobox")).toHaveAccessibleName("general"); + }); + + test("trigger announces every connected channel by name", () => { + connectedChannels = [{ channel_id: "C0123ABC" }, { channel_id: "C0456DEF" }]; + + renderCombobox(); + + expect(screen.getByRole("combobox")).toHaveAccessibleName( + "general, engineering", + ); + }); + + test("shows the placeholder when no channels are connected", () => { + connectedChannels = []; + + renderCombobox(); + + expect( + screen.getByText("slackSearchbarNoChannelsConnected"), + ).toBeInTheDocument(); + }); + + test("ignores connections for channels that are no longer available", () => { + connectedChannels = [{ channel_id: "C0123ABC" }, { channel_id: "C0NOPE" }]; + + renderCombobox(); + + expect(screen.getByRole("combobox")).toHaveAccessibleName("general"); + }); + + test("selecting an option connects that channel", async () => { + connectedChannels = []; + + renderCombobox(); + + fireEvent.click(screen.getByRole("combobox")); + + const option = await screen.findByRole("option", { name: "general" }); + fireEvent.click(option); + + await waitFor(() => + expect(updateConnectedChannels).toHaveBeenCalledWith([ + { channel_id: "C0123ABC" }, + ]), + ); + }); +}); diff --git a/yarn.lock b/yarn.lock index 8125bbdb9..dca300759 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2488,7 +2488,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.29.2": +"@babel/runtime@npm:^7.29.2, @babel/runtime@npm:^7.29.7": version: 7.29.7 resolution: "@babel/runtime@npm:7.29.7" checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e @@ -2664,6 +2664,32 @@ __metadata: languageName: node linkType: hard +"@base-ui/react@npm:^1.6.0": + version: 1.8.0 + resolution: "@base-ui/react@npm:1.8.0" + dependencies: + "@babel/runtime": "npm:^7.29.7" + "@base-ui/utils": "npm:0.4.0" + "@floating-ui/react-dom": "npm:^2.1.9" + "@floating-ui/utils": "npm:^0.2.12" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + "@date-fns/tz": ^1.2.0 + "@types/react": ^17 || ^18 || ^19 + date-fns: ^4.0.0 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + "@date-fns/tz": + optional: true + "@types/react": + optional: true + date-fns: + optional: true + checksum: 10c0/bf3faa8891fe6c9695a07b30caa3349f9ffc067f0efd381bb6ed12d68400a2d273ab0f764dd07c5cc6b25f5bc6df6a5688e0164d4e2500ad07efaec8bdd88896 + languageName: node + linkType: hard + "@base-ui/utils@npm:0.3.1": version: 0.3.1 resolution: "@base-ui/utils@npm:0.3.1" @@ -2683,6 +2709,25 @@ __metadata: languageName: node linkType: hard +"@base-ui/utils@npm:0.4.0": + version: 0.4.0 + resolution: "@base-ui/utils@npm:0.4.0" + dependencies: + "@babel/runtime": "npm:^7.29.7" + "@floating-ui/utils": "npm:^0.2.12" + reselect: "npm:^5.2.0" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + "@types/react": ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/dd32220d13d42ed2cf0d0ae18e02bf9ece5c54d6a26e49c9449514d66d38b1fee51c875f7d9d21484a326b7a8a2c33c95be620e8a5976b7b6f3bfa27572b6baf + languageName: node + linkType: hard + "@bcoe/v8-coverage@npm:^1.0.2": version: 1.0.2 resolution: "@bcoe/v8-coverage@npm:1.0.2" @@ -4078,7 +4123,7 @@ __metadata: languageName: node linkType: hard -"@floating-ui/react-dom@npm:^2.1.8": +"@floating-ui/react-dom@npm:^2.1.8, @floating-ui/react-dom@npm:^2.1.9": version: 2.1.9 resolution: "@floating-ui/react-dom@npm:2.1.9" dependencies: @@ -4875,7 +4920,7 @@ __metadata: "@radix-ui/react-hover-card": "npm:^1.1.15" "@tanstack/react-router": "npm:1.168.21" "@telegraph/button": "npm:>=0.8.0" - "@telegraph/combobox": "npm:>=0.5.0" + "@telegraph/combobox": "npm:^0.6.0" "@telegraph/icon": "npm:>=0.6.0" "@telegraph/layout": "npm:>=0.6.1" "@telegraph/segmented-control": "npm:>=0.4.0" @@ -7077,6 +7122,18 @@ __metadata: languageName: node linkType: hard +"@telegraph/appearance@npm:^0.0.16": + version: 0.0.16 + resolution: "@telegraph/appearance@npm:0.0.16" + dependencies: + "@telegraph/helpers": "npm:^0.3.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10c0/b65b2e4e36530f279bb171348c653ab96052cf8b455e752704ec9a010ab510be173dc8add8002c97f1dbd2afa0487f784630cddcdded9977b7b86c1f0add6991 + languageName: node + linkType: hard + "@telegraph/button@npm:>=0.8.0, @telegraph/button@npm:^0.8.0": version: 0.8.0 resolution: "@telegraph/button@npm:0.8.0" @@ -7095,7 +7152,25 @@ __metadata: languageName: node linkType: hard -"@telegraph/combobox@npm:0.5.0, @telegraph/combobox@npm:>=0.5.0": +"@telegraph/button@npm:^0.9.1": + version: 0.9.1 + resolution: "@telegraph/button@npm:0.9.1" + dependencies: + "@telegraph/helpers": "npm:^0.3.1" + "@telegraph/icon": "npm:^0.6.2" + "@telegraph/layout": "npm:^0.6.3" + "@telegraph/style-engine": "npm:^0.4.2" + "@telegraph/typography": "npm:^0.5.2" + clsx: "npm:^2.1.1" + lucide-react: "npm:^1.23.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10c0/5525ee36a366dc1dfed5ee74a07ef90c76c27d1aa0ef2cb56b2f42f1d22d5a2a516fe8c54a9dd03cc9cb58a408e3b9f0d6e6a43ec47579505c1a611b5ba90a1d + languageName: node + linkType: hard + +"@telegraph/combobox@npm:0.5.0": version: 0.5.0 resolution: "@telegraph/combobox@npm:0.5.0" dependencies: @@ -7119,6 +7194,31 @@ __metadata: languageName: node linkType: hard +"@telegraph/combobox@npm:^0.6.0": + version: 0.6.0 + resolution: "@telegraph/combobox@npm:0.6.0" + dependencies: + "@base-ui/react": "npm:^1.6.0" + "@telegraph/button": "npm:^0.9.1" + "@telegraph/compose-refs": "npm:^0.0.9" + "@telegraph/helpers": "npm:^0.3.2" + "@telegraph/icon": "npm:^0.6.2" + "@telegraph/input": "npm:^0.5.2" + "@telegraph/layout": "npm:^0.6.3" + "@telegraph/segmented-control": "npm:^0.4.3" + "@telegraph/tag": "npm:^0.3.3" + "@telegraph/tooltip": "npm:^0.6.3" + "@telegraph/truncate": "npm:^0.3.1" + "@telegraph/typography": "npm:^0.5.2" + lucide-react: "npm:^1.23.0" + motion: "npm:^13.1.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10c0/612dec91cdf57e56efeb1623fe0477aadd995d5b86ba340f9c2ad9721a3ca91dcc9a4b26c937aef5c8c6d51d0b82b3a28fbd1ad0a1376078c5f731cc56be5905 + languageName: node + linkType: hard + "@telegraph/compose-refs@npm:^0.0.9": version: 0.0.9 resolution: "@telegraph/compose-refs@npm:0.0.9" @@ -7141,6 +7241,18 @@ __metadata: languageName: node linkType: hard +"@telegraph/helpers@npm:^0.3.0, @telegraph/helpers@npm:^0.3.1, @telegraph/helpers@npm:^0.3.2": + version: 0.3.2 + resolution: "@telegraph/helpers@npm:0.3.2" + dependencies: + "@base-ui/react": "npm:^1.6.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10c0/9658c95ed1cb1410a2c1e1ce7f5aa914e5a3f82fb7ab3c61a76d361c35916bda16a90d2ad1c4f89217006f8812ee14b04d152ff6b7bfd8d7fac550598fc5cfa7 + languageName: node + linkType: hard + "@telegraph/icon@npm:>=0.6.0, @telegraph/icon@npm:^0.6.0": version: 0.6.0 resolution: "@telegraph/icon@npm:0.6.0" @@ -7156,6 +7268,21 @@ __metadata: languageName: node linkType: hard +"@telegraph/icon@npm:^0.6.2": + version: 0.6.2 + resolution: "@telegraph/icon@npm:0.6.2" + dependencies: + "@telegraph/helpers": "npm:^0.3.1" + "@telegraph/typography": "npm:^0.5.2" + clsx: "npm:^2.1.1" + lucide-react: "npm:^1.23.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10c0/e2ff6942d41f00e7de38df252f8f3c712bff1407652d75e141fdde3db879cf718cffdf6c509ef1f44a5316dfe87e0584107534a0b18b0a596bedd271cffb5d58 + languageName: node + linkType: hard + "@telegraph/input@npm:>=0.5.0, @telegraph/input@npm:^0.5.0": version: 0.5.0 resolution: "@telegraph/input@npm:0.5.0" @@ -7172,6 +7299,22 @@ __metadata: languageName: node linkType: hard +"@telegraph/input@npm:^0.5.2": + version: 0.5.2 + resolution: "@telegraph/input@npm:0.5.2" + dependencies: + "@telegraph/compose-refs": "npm:^0.0.9" + "@telegraph/helpers": "npm:^0.3.1" + "@telegraph/layout": "npm:^0.6.3" + "@telegraph/typography": "npm:^0.5.2" + clsx: "npm:^2.1.1" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10c0/35a9eacaac4f3f32a6c157f914055af8875a4940780527e6f5634b048e62c5e52d216f616c11f8b124529d22cdbd8f373dad33fc88f7058b904aea5192639edf + languageName: node + linkType: hard + "@telegraph/layout@npm:>=0.6.1": version: 0.6.1 resolution: "@telegraph/layout@npm:0.6.1" @@ -7202,6 +7345,21 @@ __metadata: languageName: node linkType: hard +"@telegraph/layout@npm:^0.6.3": + version: 0.6.3 + resolution: "@telegraph/layout@npm:0.6.3" + dependencies: + "@telegraph/compose-refs": "npm:^0.0.9" + "@telegraph/helpers": "npm:^0.3.1" + "@telegraph/style-engine": "npm:^0.4.2" + clsx: "npm:^2.1.1" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10c0/a09dfa58cc73687e4fafe5cf6023cd8e2240c3674e72c8678a876c7482f6befeac57d3abd2e1a063c4a9ba93401ab517e3feeedb3363a5f9f9e8470fbb577568 + languageName: node + linkType: hard + "@telegraph/menu@npm:^0.6.0": version: 0.6.0 resolution: "@telegraph/menu@npm:0.6.0" @@ -7240,6 +7398,25 @@ __metadata: languageName: node linkType: hard +"@telegraph/segmented-control@npm:^0.4.3": + version: 0.4.3 + resolution: "@telegraph/segmented-control@npm:0.4.3" + dependencies: + "@base-ui/react": "npm:^1.6.0" + "@telegraph/button": "npm:^0.9.1" + "@telegraph/compose-refs": "npm:^0.0.9" + "@telegraph/helpers": "npm:^0.3.2" + "@telegraph/layout": "npm:^0.6.3" + "@telegraph/truncate": "npm:^0.3.1" + lucide-react: "npm:^1.23.0" + motion: "npm:^13.1.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10c0/53c41475cac7475b89f2382730700125a9216324305bcae685ebbeb7257be90a90e2f78f731bda34003dc048df71093473550a0b79a16d10b0505cd12e5a7fb4 + languageName: node + linkType: hard + "@telegraph/select@npm:>=0.1.0": version: 0.1.0 resolution: "@telegraph/select@npm:0.1.0" @@ -7266,6 +7443,19 @@ __metadata: languageName: node linkType: hard +"@telegraph/style-engine@npm:^0.4.2": + version: 0.4.2 + resolution: "@telegraph/style-engine@npm:0.4.2" + dependencies: + "@telegraph/tokens": "npm:^0.2.2" + postcss: "npm:^8.5.26" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10c0/c9b07ecabd483c9f89561a7d69583006c7031e5f7c2ba6cf805d3bbdc87d8492b004b0121fc1904ea39a449acfacbcbdc0ca81f0e370f6d776dc11c2ca114998 + languageName: node + linkType: hard + "@telegraph/tag@npm:>=0.3.0, @telegraph/tag@npm:^0.3.0": version: 0.3.0 resolution: "@telegraph/tag@npm:0.3.0" @@ -7287,6 +7477,27 @@ __metadata: languageName: node linkType: hard +"@telegraph/tag@npm:^0.3.3": + version: 0.3.3 + resolution: "@telegraph/tag@npm:0.3.3" + dependencies: + "@telegraph/button": "npm:^0.9.1" + "@telegraph/compose-refs": "npm:^0.0.9" + "@telegraph/helpers": "npm:^0.3.2" + "@telegraph/icon": "npm:^0.6.2" + "@telegraph/layout": "npm:^0.6.3" + "@telegraph/tooltip": "npm:^0.6.3" + "@telegraph/typography": "npm:^0.5.2" + clsx: "npm:^2.1.1" + lucide-react: "npm:^1.23.0" + motion: "npm:^13.1.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10c0/33c9222d6961155b518018c5d336a8fd2e8403e509400ce5fabd2df910a9446fdb639f866e66b2f803f16c6f56bdfed814133fc80c41dae7fdcbee79f27fb76c + languageName: node + linkType: hard + "@telegraph/textarea@npm:>=0.4.0": version: 0.4.0 resolution: "@telegraph/textarea@npm:0.4.0" @@ -7344,6 +7555,23 @@ __metadata: languageName: node linkType: hard +"@telegraph/tooltip@npm:^0.6.1, @telegraph/tooltip@npm:^0.6.3": + version: 0.6.3 + resolution: "@telegraph/tooltip@npm:0.6.3" + dependencies: + "@base-ui/react": "npm:^1.6.0" + "@telegraph/appearance": "npm:^0.0.16" + "@telegraph/helpers": "npm:^0.3.2" + "@telegraph/layout": "npm:^0.6.3" + "@telegraph/typography": "npm:^0.5.2" + motion: "npm:^13.1.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10c0/52eeb7f1e8893850a0f2b0db96d9413ca90e26d7220dd65a48c59fe08ed34365d3b82ca6969abac3dfd619f580afd4190013ed8df617afc43cc9d7862c388193 + languageName: node + linkType: hard + "@telegraph/truncate@npm:^0.3.0": version: 0.3.0 resolution: "@telegraph/truncate@npm:0.3.0" @@ -7358,6 +7586,20 @@ __metadata: languageName: node linkType: hard +"@telegraph/truncate@npm:^0.3.1": + version: 0.3.1 + resolution: "@telegraph/truncate@npm:0.3.1" + dependencies: + "@telegraph/helpers": "npm:^0.3.0" + "@telegraph/tooltip": "npm:^0.6.1" + "@telegraph/typography": "npm:^0.5.1" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10c0/c3ef73bae2cc22861cde17f6ac524caad4c38a7d39ebc1b0fddce9e40d9c707a2a55d0dc3b4e37284cb1a6e572495ef03ad3616b82e0c7e634e21f5e09fb65ed + languageName: node + linkType: hard + "@telegraph/typography@npm:>=0.5.0, @telegraph/typography@npm:^0.5.0": version: 0.5.0 resolution: "@telegraph/typography@npm:0.5.0" @@ -7373,6 +7615,21 @@ __metadata: languageName: node linkType: hard +"@telegraph/typography@npm:^0.5.1, @telegraph/typography@npm:^0.5.2": + version: 0.5.2 + resolution: "@telegraph/typography@npm:0.5.2" + dependencies: + "@telegraph/helpers": "npm:^0.3.1" + "@telegraph/layout": "npm:^0.6.3" + "@telegraph/style-engine": "npm:^0.4.2" + clsx: "npm:^2.1.1" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 10c0/59de08612163934b6d09657cd0e68bc1959252c0c668f07bab1e289cb07d09a5a08d77bdcbe05d8bb44175ca774b3f1d4e941ba291b4024bf7c0122cf98e8bb8 + languageName: node + linkType: hard + "@testing-library/dom@npm:^10.4.1": version: 10.4.1 resolution: "@testing-library/dom@npm:10.4.1" @@ -13197,6 +13454,25 @@ __metadata: languageName: node linkType: hard +"framer-motion@npm:^13.2.0": + version: 13.2.0 + resolution: "framer-motion@npm:13.2.0" + dependencies: + motion-dom: "npm:^13.2.0" + motion-utils: "npm:^13.0.0" + tslib: "npm:^2.4.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + checksum: 10c0/b381cf2c6f9def6ae169c096f9f873b0dafc8343fbdc78cc3d27e3ede2e8f682b22e1160a1f8cde07f263993aecab2c94239a43702fe4f06eccaeba109fcff13 + languageName: node + linkType: hard + "fresh@npm:0.5.2": version: 0.5.2 resolution: "fresh@npm:0.5.2" @@ -16194,6 +16470,15 @@ __metadata: languageName: node linkType: hard +"motion-dom@npm:^13.2.0": + version: 13.2.0 + resolution: "motion-dom@npm:13.2.0" + dependencies: + motion-utils: "npm:^13.0.0" + checksum: 10c0/009725cf822940fcd10e20492cfd0be50f11db9b5f1b374dd2fddbe2bc53db80c7f44d02c8bf68e2b9baaa8335aba45bd2a860176b3c23685ca8b59ca69db6b7 + languageName: node + linkType: hard + "motion-utils@npm:^12.36.0": version: 12.36.0 resolution: "motion-utils@npm:12.36.0" @@ -16201,6 +16486,13 @@ __metadata: languageName: node linkType: hard +"motion-utils@npm:^13.0.0": + version: 13.0.0 + resolution: "motion-utils@npm:13.0.0" + checksum: 10c0/4b279ce85f71926d668ff5966feb898b174541d16952a61c7150982388553266602ecd2a76e057530076ff8f38d06672e585639ddc73c41dd1a67d04aaa3662d + languageName: node + linkType: hard + "motion@npm:^12.38.0": version: 12.38.0 resolution: "motion@npm:12.38.0" @@ -16222,6 +16514,24 @@ __metadata: languageName: node linkType: hard +"motion@npm:^13.1.0": + version: 13.2.0 + resolution: "motion@npm:13.2.0" + dependencies: + framer-motion: "npm:^13.2.0" + tslib: "npm:^2.4.0" + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + checksum: 10c0/3943e36a0848f39a3633fb822bc2230966cf8e215fd243d0cd39a62b1799c89ca75fdc07f964b9633b962a3ace845525f4c4b2f63860a7268e9d13cc1667ad4a + languageName: node + linkType: hard + "mri@npm:^1.2.0": version: 1.2.0 resolution: "mri@npm:1.2.0" @@ -16337,6 +16647,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.18": + version: 3.3.18 + resolution: "nanoid@npm:3.3.18" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/b994b4e396730f8be2520923284e2040d61eaee55cc6d4935ef6d38d34bafdc46133eda4d3faea5073bda545aa6079d82b886caeac5c731cf9ac18bcc1301425 + languageName: node + linkType: hard + "nanoid@npm:^3.3.6, nanoid@npm:^3.3.8": version: 3.3.11 resolution: "nanoid@npm:3.3.11" @@ -16476,7 +16795,7 @@ __metadata: "@knocklabs/typescript-config": "workspace:^" "@next/eslint-plugin-next": "npm:^15.3.1" "@telegraph/button": "npm:>=0.8.0" - "@telegraph/combobox": "npm:>=0.5.0" + "@telegraph/combobox": "npm:^0.6.0" "@telegraph/icon": "npm:>=0.6.0" "@telegraph/input": "npm:>=0.5.0" "@telegraph/layout": "npm:>=0.6.1" @@ -16517,7 +16836,7 @@ __metadata: "@knocklabs/typescript-config": "workspace:^" "@next/eslint-plugin-next": "npm:^15.3.1" "@telegraph/button": "npm:>=0.8.0" - "@telegraph/combobox": "npm:>=0.5.0" + "@telegraph/combobox": "npm:^0.6.0" "@telegraph/icon": "npm:>=0.6.0" "@telegraph/input": "npm:>=0.5.0" "@telegraph/layout": "npm:>=0.6.1" @@ -17318,6 +17637,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.5.26": + version: 8.5.28 + resolution: "postcss@npm:8.5.28" + dependencies: + nanoid: "npm:^3.3.18" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/9fe44215a6628d89c8a75184b92f5b2f77e16f9e5b13b39b9009bb7e8e6eccddf82c8854bae922db1f17ace6ef3445073fe38abff513caeb03e5285b1b55620a + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" From 22c4fa270c146631db5975ace9f3f489d5a51d24 Mon Sep 17 00:00:00 2001 From: Kyle McDonald Date: Fri, 4 Sep 2026 12:48:14 -0500 Subject: [PATCH 2/9] fix(react): stop sortByDisplayName reordering its input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useMsTeamsChannels returns a reference straight into SWR's cache, so sorting in place reordered the cached array for every other reader of that key — including apps that call the hook themselves, since both MS Teams hooks are public exports. Copy first, matching the Slack helper, and take a readonly parameter so a bare .sort() no longer compiles. --- .changeset/olive-donkeys-repeat.md | 7 +++++++ packages/react/src/modules/ms-teams/utils.ts | 4 ++-- .../ms-teams/MsTeamsChannelCombobox.test.tsx | 14 ++++---------- packages/react/test/ms-teams/utils.test.ts | 19 +++++++++++++++++++ 4 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 .changeset/olive-donkeys-repeat.md diff --git a/.changeset/olive-donkeys-repeat.md b/.changeset/olive-donkeys-repeat.md new file mode 100644 index 000000000..22cc7ff95 --- /dev/null +++ b/.changeset/olive-donkeys-repeat.md @@ -0,0 +1,7 @@ +--- +"@knocklabs/react": patch +--- + +Stop `sortByDisplayName` from reordering the array it is given + +The MS Teams comboboxes sorted their teams and channels in place. `useMsTeamsChannels` returns a reference straight into SWR's cache, so rendering `MsTeamsChannelInTeamCombobox` reordered the cached array for every other reader of that key, including apps calling the hook themselves. The helper now copies before sorting, matching its Slack counterpart. diff --git a/packages/react/src/modules/ms-teams/utils.ts b/packages/react/src/modules/ms-teams/utils.ts index 7b4b996b1..8be5b55b0 100644 --- a/packages/react/src/modules/ms-teams/utils.ts +++ b/packages/react/src/modules/ms-teams/utils.ts @@ -1,6 +1,6 @@ export const sortByDisplayName = ( - items: T[], + items: readonly T[], ) => - items.sort((a, b) => + [...items].sort((a, b) => a.displayName.toLowerCase().localeCompare(b.displayName.toLowerCase()), ); diff --git a/packages/react/test/ms-teams/MsTeamsChannelCombobox.test.tsx b/packages/react/test/ms-teams/MsTeamsChannelCombobox.test.tsx index 6de2bfd25..09358235c 100644 --- a/packages/react/test/ms-teams/MsTeamsChannelCombobox.test.tsx +++ b/packages/react/test/ms-teams/MsTeamsChannelCombobox.test.tsx @@ -4,20 +4,16 @@ import { beforeEach, describe, expect, test, vi } from "vitest"; import { MsTeamsChannelInTeamCombobox } from "../../src/modules/ms-teams/components/MsTeamsChannelCombobox/MsTeamsChannelInTeamCombobox"; import { MsTeamsTeamCombobox } from "../../src/modules/ms-teams/components/MsTeamsChannelCombobox/MsTeamsTeamCombobox"; -// `sortByDisplayName` sorts in place, so hand each render its own array rather -// than sharing one across tests. -const buildTeams = () => [ +const teams = [ { id: "team_1", displayName: "Engineering" }, { id: "team_2", displayName: "Design" }, ]; -const buildChannels = () => [ +const channels = [ { id: "channel_1", displayName: "General" }, { id: "channel_2", displayName: "Releases" }, ]; -let teams = buildTeams(); -let channels = buildChannels(); let connectedChannels: Array<{ ms_teams_team_id?: string; ms_teams_channel_id?: string; @@ -41,8 +37,6 @@ vi.mock("@knocklabs/react-core", async () => { const recipientObject = { objectId: "object_123", collection: "projects" }; beforeEach(() => { - teams = buildTeams(); - channels = buildChannels(); connectedChannels = []; }); @@ -52,7 +46,7 @@ describe("MsTeamsTeamCombobox", () => { test("trigger announces the selected team by display name", () => { render( team.id === "team_1")!} + team={teams[0]!} onTeamChange={vi.fn()} getChannelCount={() => 0} />, @@ -64,7 +58,7 @@ describe("MsTeamsTeamCombobox", () => { test("trigger includes the connected channel count", () => { render( team.id === "team_1")!} + team={teams[0]!} onTeamChange={vi.fn()} getChannelCount={() => 2} />, diff --git a/packages/react/test/ms-teams/utils.test.ts b/packages/react/test/ms-teams/utils.test.ts index 225fb85b5..81b698810 100644 --- a/packages/react/test/ms-teams/utils.test.ts +++ b/packages/react/test/ms-teams/utils.test.ts @@ -17,4 +17,23 @@ describe("sortByDisplayName", () => { "Charlie", ]); }); + + test("leaves the array it was given alone", () => { + // Callers hand this the array returned by useMsTeamsTeams and + // useMsTeamsChannels, and the latter points straight into SWR's cache. + const items = [ + { id: 1, displayName: "Charlie" }, + { id: 2, displayName: "alpha" }, + { id: 3, displayName: "Bravo" }, + ]; + + const sorted = sortByDisplayName(items); + + expect(items.map((i) => i.displayName)).toEqual([ + "Charlie", + "alpha", + "Bravo", + ]); + expect(sorted).not.toBe(items); + }); }); From 652e8d07e40e370a7dac0fc00b0a1fd5a71b26ed Mon Sep 17 00:00:00 2001 From: Kyle McDonald Date: Fri, 4 Sep 2026 13:01:51 -0500 Subject: [PATCH 3/9] fix(react): keep the channel-type icon on Slack combobox options Combobox.Option renders `label || children || value`, so the string label added for the accessible name stopped the option's children rendering. That silently dropped the hash/lock icon from both the dropdown row and the selected tags, and it is the only place a channel's private/public state is shown. Move the icon into the option's trailing icon slot. The leading slot is not available: OptionItemLeading returns the selection check early for any selectable combobox, so `leadingIcon` and `leadingComponent` are both ignored there. The icon now sits at the right edge of the row rather than beside the name, and the trigger's tags still show the name alone, because TriggerTagText renders `label || value` and never sees the icon. --- .changeset/shaggy-moons-sleep.md | 7 ++++++ .../SlackChannelCombobox.tsx | 24 +++++++++---------- .../test/slack/SlackChannelCombobox.test.tsx | 15 ++++++++++++ 3 files changed, 33 insertions(+), 13 deletions(-) create mode 100644 .changeset/shaggy-moons-sleep.md diff --git a/.changeset/shaggy-moons-sleep.md b/.changeset/shaggy-moons-sleep.md new file mode 100644 index 000000000..2c73045ed --- /dev/null +++ b/.changeset/shaggy-moons-sleep.md @@ -0,0 +1,7 @@ +--- +"@knocklabs/react": patch +--- + +Keep the channel-type icon on Slack combobox options + +`@telegraph/combobox` renders an option as `label || children || value`, so giving an option a string `label` for its accessible name stops its children rendering. The hash/lock icon now comes through the option's trailing icon slot, since the leading slot belongs to the selection check. diff --git a/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx b/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx index 4fa139c61..3aeaea9bf 100644 --- a/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx +++ b/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx @@ -8,7 +8,6 @@ import { useTranslations, } from "@knocklabs/react-core"; import { Combobox } from "@telegraph/combobox"; -import { Icon } from "@telegraph/icon"; import { Stack } from "@telegraph/layout"; import { Text } from "@telegraph/typography"; import { Hash, Lock } from "lucide-react"; @@ -201,22 +200,21 @@ export const SlackChannelCombobox: FunctionComponent< /> {slackChannels.map((channel) => ( + // `label` has to be a string: the combobox derives both the + // option's and the trigger's accessible name from it, and falls + // back to `value` (the raw channel id) for anything else. That + // rules out passing the icon as children, since children lose to + // `label`. The leading slot is taken by the selection check, so + // the channel type sits in the trailing slot instead. - - - {channel.name} - - + trailingIcon={{ + icon: channel.is_private ? Lock : Hash, + "aria-hidden": true, + }} + /> ))} diff --git a/packages/react/test/slack/SlackChannelCombobox.test.tsx b/packages/react/test/slack/SlackChannelCombobox.test.tsx index a9929f510..9d4144abb 100644 --- a/packages/react/test/slack/SlackChannelCombobox.test.tsx +++ b/packages/react/test/slack/SlackChannelCombobox.test.tsx @@ -82,6 +82,21 @@ describe("SlackChannelCombobox", () => { expect(screen.getByRole("combobox")).toHaveAccessibleName("general"); }); + test("each option still shows its channel-type icon", async () => { + connectedChannels = []; + + renderCombobox(); + + fireEvent.click(screen.getByRole("combobox")); + + const option = await screen.findByRole("option", { name: "general" }); + + // An option's children lose to its `label`, so the icon has to come + // through an icon slot. Each option renders two: the selection check and + // the hash/lock that says whether the channel is private. + expect(option.querySelectorAll("[data-tgph-icon-svg]")).toHaveLength(2); + }); + test("selecting an option connects that channel", async () => { connectedChannels = []; From 47e829be2380050a2d449a099a22a551b28d977f Mon Sep 17 00:00:00 2001 From: Kyle McDonald Date: Fri, 4 Sep 2026 13:19:37 -0500 Subject: [PATCH 4/9] chore(react): pin and update the remaining telegraph packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every other @telegraph/* dependency was still an open >= range, so an install resolved to whatever npm had newest rather than something this package was built against — the same problem the combobox pin fixed. The one with a real consequence was @telegraph/select, stuck on 0.1.0, which hard-pins @telegraph/combobox at 0.5.0. Consumers were getting two copies of the combobox. 0.2.0 depends on 0.6.0, so the lockfile now carries a single entry. --- .changeset/olive-eels-smile.md | 7 + examples/nextjs-app-dir-example/package.json | 16 +- examples/nextjs-example/package.json | 16 +- packages/react/package.json | 22 +- yarn.lock | 436 +++---------------- 5 files changed, 91 insertions(+), 406 deletions(-) create mode 100644 .changeset/olive-eels-smile.md diff --git a/.changeset/olive-eels-smile.md b/.changeset/olive-eels-smile.md new file mode 100644 index 000000000..9dac5a496 --- /dev/null +++ b/.changeset/olive-eels-smile.md @@ -0,0 +1,7 @@ +--- +"@knocklabs/react": patch +--- + +Pin every `@telegraph/*` dependency to a caret range at its latest release + +These were open `>=` ranges, so an install resolved to whatever was newest on npm rather than a version this package had been built against. `@telegraph/select` was still on 0.1.0, which pins `@telegraph/combobox` at 0.5.0, so consumers received two copies of the combobox. Moving it to 0.2.0 leaves one. diff --git a/examples/nextjs-app-dir-example/package.json b/examples/nextjs-app-dir-example/package.json index affacc5ca..f344e50f5 100644 --- a/examples/nextjs-app-dir-example/package.json +++ b/examples/nextjs-app-dir-example/package.json @@ -14,15 +14,15 @@ "@faker-js/faker": "^9.9.0", "@knocklabs/node": "^1.30.0", "@knocklabs/react": "workspace:*", - "@telegraph/button": ">=0.8.0", + "@telegraph/button": "^0.9.1", "@telegraph/combobox": "^0.6.0", - "@telegraph/icon": ">=0.6.0", - "@telegraph/input": ">=0.5.0", - "@telegraph/layout": ">=0.6.1", - "@telegraph/select": ">=0.1.0", - "@telegraph/textarea": ">=0.4.0", - "@telegraph/tokens": ">=0.2.2", - "@telegraph/typography": ">=0.5.0", + "@telegraph/icon": "^0.6.2", + "@telegraph/input": "^0.5.2", + "@telegraph/layout": "^0.6.3", + "@telegraph/select": "^0.2.0", + "@telegraph/textarea": "^0.4.1", + "@telegraph/tokens": "^0.2.2", + "@telegraph/typography": "^0.5.2", "framer-motion": "^12.38.0", "next": "npm:next@15.3.6", "next-seo": "^6.8.0", diff --git a/examples/nextjs-example/package.json b/examples/nextjs-example/package.json index 4c94aa56b..9eac2eecc 100644 --- a/examples/nextjs-example/package.json +++ b/examples/nextjs-example/package.json @@ -16,15 +16,15 @@ "@faker-js/faker": "^9.9.0", "@knocklabs/node": "^1.30.0", "@knocklabs/react": "workspace:^", - "@telegraph/button": ">=0.8.0", + "@telegraph/button": "^0.9.1", "@telegraph/combobox": "^0.6.0", - "@telegraph/icon": ">=0.6.0", - "@telegraph/input": ">=0.5.0", - "@telegraph/layout": ">=0.6.1", - "@telegraph/select": ">=0.1.0", - "@telegraph/textarea": ">=0.4.0", - "@telegraph/tokens": ">=0.2.2", - "@telegraph/typography": ">=0.5.0", + "@telegraph/icon": "^0.6.2", + "@telegraph/input": "^0.5.2", + "@telegraph/layout": "^0.6.3", + "@telegraph/select": "^0.2.0", + "@telegraph/textarea": "^0.4.1", + "@telegraph/tokens": "^0.2.2", + "@telegraph/typography": "^0.5.2", "framer-motion": "^12.38.0", "next": "15.3.6", "next-seo": "^6.8.0", diff --git a/packages/react/package.json b/packages/react/package.json index ca84668db..ee226b6cb 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -78,23 +78,23 @@ "@popperjs/core": "^2.11.8", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-hover-card": "^1.1.15", - "@telegraph/button": ">=0.8.0", + "@telegraph/button": "^0.9.1", "@telegraph/combobox": "^0.6.0", - "@telegraph/icon": ">=0.6.0", - "@telegraph/layout": ">=0.6.1", - "@telegraph/segmented-control": ">=0.4.0", - "@telegraph/select": ">=0.1.0", - "@telegraph/tag": ">=0.3.0", - "@telegraph/toggle": ">=0.3.0", - "@telegraph/tokens": ">=0.2.2", - "@telegraph/tooltip": ">=0.6.0", - "@telegraph/typography": ">=0.5.0", + "@telegraph/icon": "^0.6.2", + "@telegraph/layout": "^0.6.3", + "@telegraph/segmented-control": "^0.4.3", + "@telegraph/select": "^0.2.0", + "@telegraph/tag": "^0.3.3", + "@telegraph/toggle": "^0.3.2", + "@telegraph/tokens": "^0.2.2", + "@telegraph/tooltip": "^0.6.3", + "@telegraph/typography": "^0.5.2", "lucide-react": "^0.544.0" }, "devDependencies": { "@codecov/vite-plugin": "^2.0.1", "@tanstack/react-router": "1.168.21", - "@telegraph/style-engine": "^0.4.0", + "@telegraph/style-engine": "^0.4.2", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.2", "@types/eslint-plugin-jsx-a11y": "^6", diff --git a/yarn.lock b/yarn.lock index dca300759..2502b33cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2488,7 +2488,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.29.2, @babel/runtime@npm:^7.29.7": +"@babel/runtime@npm:^7.29.7": version: 7.29.7 resolution: "@babel/runtime@npm:7.29.7" checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e @@ -2638,32 +2638,6 @@ __metadata: languageName: node linkType: hard -"@base-ui/react@npm:^1.5.0": - version: 1.6.0 - resolution: "@base-ui/react@npm:1.6.0" - dependencies: - "@babel/runtime": "npm:^7.29.2" - "@base-ui/utils": "npm:0.3.1" - "@floating-ui/react-dom": "npm:^2.1.8" - "@floating-ui/utils": "npm:^0.2.11" - use-sync-external-store: "npm:^1.6.0" - peerDependencies: - "@date-fns/tz": ^1.2.0 - "@types/react": ^17 || ^18 || ^19 - date-fns: ^4.0.0 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - "@date-fns/tz": - optional: true - "@types/react": - optional: true - date-fns: - optional: true - checksum: 10c0/5e5994dc782710eac900d68cc0691aea70a47fabc6ef8d2ea943103d9aa96c4b44f9fd76ba65d29fa2667a5fbd7ea7a1cc02fe111c4bf639d4e3e65a1c281118 - languageName: node - linkType: hard - "@base-ui/react@npm:^1.6.0": version: 1.8.0 resolution: "@base-ui/react@npm:1.8.0" @@ -2690,25 +2664,6 @@ __metadata: languageName: node linkType: hard -"@base-ui/utils@npm:0.3.1": - version: 0.3.1 - resolution: "@base-ui/utils@npm:0.3.1" - dependencies: - "@babel/runtime": "npm:^7.29.2" - "@floating-ui/utils": "npm:^0.2.11" - reselect: "npm:^5.2.0" - use-sync-external-store: "npm:^1.6.0" - peerDependencies: - "@types/react": ^17 || ^18 || ^19 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/4de858c3e2c8a4486a549d51c5e887bba6921e5a7f8db54439ba558feaf1656c3592a873928aee8a1711ee9210be3a65ca43b26713e97f649695ed2fa8c3af2c - languageName: node - linkType: hard - "@base-ui/utils@npm:0.4.0": version: 0.4.0 resolution: "@base-ui/utils@npm:0.4.0" @@ -4123,7 +4078,7 @@ __metadata: languageName: node linkType: hard -"@floating-ui/react-dom@npm:^2.1.8, @floating-ui/react-dom@npm:^2.1.9": +"@floating-ui/react-dom@npm:^2.1.9": version: 2.1.9 resolution: "@floating-ui/react-dom@npm:2.1.9" dependencies: @@ -4135,7 +4090,7 @@ __metadata: languageName: node linkType: hard -"@floating-ui/utils@npm:^0.2.11, @floating-ui/utils@npm:^0.2.12": +"@floating-ui/utils@npm:^0.2.12": version: 0.2.12 resolution: "@floating-ui/utils@npm:0.2.12" checksum: 10c0/bb910b90d56991a62011afaf5f8e0c4b7fb6dab69403f4dd17fbd6eb25560b28d533dff9791f270b4f459852e9006a1077b5d73cbedb5e34d9424afc6a828314 @@ -4919,18 +4874,18 @@ __metadata: "@radix-ui/react-dialog": "npm:^1.1.15" "@radix-ui/react-hover-card": "npm:^1.1.15" "@tanstack/react-router": "npm:1.168.21" - "@telegraph/button": "npm:>=0.8.0" + "@telegraph/button": "npm:^0.9.1" "@telegraph/combobox": "npm:^0.6.0" - "@telegraph/icon": "npm:>=0.6.0" - "@telegraph/layout": "npm:>=0.6.1" - "@telegraph/segmented-control": "npm:>=0.4.0" - "@telegraph/select": "npm:>=0.1.0" - "@telegraph/style-engine": "npm:^0.4.0" - "@telegraph/tag": "npm:>=0.3.0" - "@telegraph/toggle": "npm:>=0.3.0" - "@telegraph/tokens": "npm:>=0.2.2" - "@telegraph/tooltip": "npm:>=0.6.0" - "@telegraph/typography": "npm:>=0.5.0" + "@telegraph/icon": "npm:^0.6.2" + "@telegraph/layout": "npm:^0.6.3" + "@telegraph/segmented-control": "npm:^0.4.3" + "@telegraph/select": "npm:^0.2.0" + "@telegraph/style-engine": "npm:^0.4.2" + "@telegraph/tag": "npm:^0.3.3" + "@telegraph/toggle": "npm:^0.3.2" + "@telegraph/tokens": "npm:^0.2.2" + "@telegraph/tooltip": "npm:^0.6.3" + "@telegraph/typography": "npm:^0.5.2" "@testing-library/dom": "npm:^10.4.1" "@testing-library/react": "npm:^16.3.2" "@types/eslint-plugin-jsx-a11y": "npm:^6" @@ -7110,18 +7065,6 @@ __metadata: languageName: node linkType: hard -"@telegraph/appearance@npm:^0.0.15": - version: 0.0.15 - resolution: "@telegraph/appearance@npm:0.0.15" - dependencies: - "@telegraph/helpers": "npm:^0.2.0" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/3b5ac31cf58a1ceae37e64cfe1aa4007156c380a3e247d545d24ec8dd36154cd1e3b7c00e4bb6d75224120523521d3c8e3f0107555085cc17e1aa40cef0b052c - languageName: node - linkType: hard - "@telegraph/appearance@npm:^0.0.16": version: 0.0.16 resolution: "@telegraph/appearance@npm:0.0.16" @@ -7134,24 +7077,6 @@ __metadata: languageName: node linkType: hard -"@telegraph/button@npm:>=0.8.0, @telegraph/button@npm:^0.8.0": - version: 0.8.0 - resolution: "@telegraph/button@npm:0.8.0" - dependencies: - "@telegraph/helpers": "npm:^0.2.0" - "@telegraph/icon": "npm:^0.6.0" - "@telegraph/layout": "npm:^0.6.0" - "@telegraph/style-engine": "npm:^0.4.0" - "@telegraph/typography": "npm:^0.5.0" - clsx: "npm:^2.1.1" - lucide-react: "npm:^1.23.0" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/a4a38c76a0d57870f1b83ae22f9bb188d3ac9e5c5bd86b9d57eae5d1eb0511af6addb9e0a67f52d521bb522b9b33c84168b5a7456de1315d50c38f6dce416cd4 - languageName: node - linkType: hard - "@telegraph/button@npm:^0.9.1": version: 0.9.1 resolution: "@telegraph/button@npm:0.9.1" @@ -7170,31 +7095,7 @@ __metadata: languageName: node linkType: hard -"@telegraph/combobox@npm:0.5.0": - version: 0.5.0 - resolution: "@telegraph/combobox@npm:0.5.0" - dependencies: - "@telegraph/button": "npm:^0.8.0" - "@telegraph/compose-refs": "npm:^0.0.9" - "@telegraph/helpers": "npm:^0.2.0" - "@telegraph/icon": "npm:^0.6.0" - "@telegraph/input": "npm:^0.5.0" - "@telegraph/layout": "npm:^0.6.0" - "@telegraph/menu": "npm:^0.6.0" - "@telegraph/tag": "npm:^0.3.0" - "@telegraph/tooltip": "npm:^0.6.0" - "@telegraph/truncate": "npm:^0.3.0" - "@telegraph/typography": "npm:^0.5.0" - lucide-react: "npm:^1.23.0" - motion: "npm:^12.38.0" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/41d05da3dfa2fa6cb6aea2faeb1f42a68217c40175f6506503daf53b6482cd945e27c8b34bb5bc501a59e4a0e4c33155b2dbd9d43cbc73be2f0d878ec84c9453 - languageName: node - linkType: hard - -"@telegraph/combobox@npm:^0.6.0": +"@telegraph/combobox@npm:0.6.0, @telegraph/combobox@npm:^0.6.0": version: 0.6.0 resolution: "@telegraph/combobox@npm:0.6.0" dependencies: @@ -7229,19 +7130,7 @@ __metadata: languageName: node linkType: hard -"@telegraph/helpers@npm:0.2.0, @telegraph/helpers@npm:^0.2.0": - version: 0.2.0 - resolution: "@telegraph/helpers@npm:0.2.0" - dependencies: - "@base-ui/react": "npm:^1.5.0" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/4f724feb11ba4a3cd253e2de92aab8aa63a9a658a7ca9a2801d5319a87165de8b06ad47a182d04be328686bd5ed598833211024232f1fe4f7e5025aee635dc5d - languageName: node - linkType: hard - -"@telegraph/helpers@npm:^0.3.0, @telegraph/helpers@npm:^0.3.1, @telegraph/helpers@npm:^0.3.2": +"@telegraph/helpers@npm:0.3.2, @telegraph/helpers@npm:^0.3.0, @telegraph/helpers@npm:^0.3.1, @telegraph/helpers@npm:^0.3.2": version: 0.3.2 resolution: "@telegraph/helpers@npm:0.3.2" dependencies: @@ -7253,21 +7142,6 @@ __metadata: languageName: node linkType: hard -"@telegraph/icon@npm:>=0.6.0, @telegraph/icon@npm:^0.6.0": - version: 0.6.0 - resolution: "@telegraph/icon@npm:0.6.0" - dependencies: - "@telegraph/helpers": "npm:^0.2.0" - "@telegraph/typography": "npm:^0.5.0" - clsx: "npm:^2.1.1" - lucide-react: "npm:^1.23.0" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/38e063f15723d56786768cce1e665303a42854f9d5c6312a9db4d8e2e751481be8349ad58604751753250c3587e4cef92d597d21eb925923494e0df3b99911f3 - languageName: node - linkType: hard - "@telegraph/icon@npm:^0.6.2": version: 0.6.2 resolution: "@telegraph/icon@npm:0.6.2" @@ -7283,22 +7157,6 @@ __metadata: languageName: node linkType: hard -"@telegraph/input@npm:>=0.5.0, @telegraph/input@npm:^0.5.0": - version: 0.5.0 - resolution: "@telegraph/input@npm:0.5.0" - dependencies: - "@telegraph/compose-refs": "npm:^0.0.9" - "@telegraph/helpers": "npm:^0.2.0" - "@telegraph/layout": "npm:^0.6.0" - "@telegraph/typography": "npm:^0.5.0" - clsx: "npm:^2.1.1" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/daf1905c86503497e1ccdb71bc35124037e6ac8431fb750e8d494d61277546877f802484f85dc204ce4935239e89ddac391400e1c915e36e5afef2a18210a05d - languageName: node - linkType: hard - "@telegraph/input@npm:^0.5.2": version: 0.5.2 resolution: "@telegraph/input@npm:0.5.2" @@ -7315,36 +7173,6 @@ __metadata: languageName: node linkType: hard -"@telegraph/layout@npm:>=0.6.1": - version: 0.6.1 - resolution: "@telegraph/layout@npm:0.6.1" - dependencies: - "@telegraph/compose-refs": "npm:^0.0.9" - "@telegraph/helpers": "npm:^0.2.0" - "@telegraph/style-engine": "npm:^0.4.0" - clsx: "npm:^2.1.1" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/a82fdf4ad38c067b3aa23df7e027545bca71ab3f540cd754a28164ac6c144815bd6e668d54916f7b335add2a074486a88bbec4f74c124de336ecd7c205552af8 - languageName: node - linkType: hard - -"@telegraph/layout@npm:^0.6.0": - version: 0.6.0 - resolution: "@telegraph/layout@npm:0.6.0" - dependencies: - "@telegraph/compose-refs": "npm:^0.0.9" - "@telegraph/helpers": "npm:^0.2.0" - "@telegraph/style-engine": "npm:^0.4.0" - clsx: "npm:^2.1.1" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/9d4b1c8761e3f5c05536f71be0301dd286f41240ef5f445a03a8b895e10676682f2d5cca76dd9e22ddbcd1d668b1cfea183eebc96d9ff447ab156f96fcae3257 - languageName: node - linkType: hard - "@telegraph/layout@npm:^0.6.3": version: 0.6.3 resolution: "@telegraph/layout@npm:0.6.3" @@ -7360,44 +7188,6 @@ __metadata: languageName: node linkType: hard -"@telegraph/menu@npm:^0.6.0": - version: 0.6.0 - resolution: "@telegraph/menu@npm:0.6.0" - dependencies: - "@base-ui/react": "npm:^1.5.0" - "@telegraph/button": "npm:^0.8.0" - "@telegraph/compose-refs": "npm:^0.0.9" - "@telegraph/helpers": "npm:^0.2.0" - "@telegraph/icon": "npm:^0.6.0" - "@telegraph/layout": "npm:^0.6.0" - lucide-react: "npm:^1.23.0" - motion: "npm:^12.38.0" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/e1b65ab3a64476843037661719ac1f852f9d05bb8a3d7519f54aa02cf2e346115d0189304c44773b48f076697b9242645d3e1c16a0cef2f340c3be4b273a0ae9 - languageName: node - linkType: hard - -"@telegraph/segmented-control@npm:>=0.4.0": - version: 0.4.0 - resolution: "@telegraph/segmented-control@npm:0.4.0" - dependencies: - "@base-ui/react": "npm:^1.5.0" - "@telegraph/button": "npm:^0.8.0" - "@telegraph/compose-refs": "npm:^0.0.9" - "@telegraph/helpers": "npm:^0.2.0" - "@telegraph/layout": "npm:^0.6.0" - "@telegraph/truncate": "npm:^0.3.0" - lucide-react: "npm:^1.23.0" - motion: "npm:^12.38.0" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/fa39845aefbdabaea9660bc84b7b2f1b07bdde97d0625f73c8d9c0ac165cac594ad70894dfabd3b8380957674efac952c7ff4fe9c20e3a68d0a629777a8e0ca2 - languageName: node - linkType: hard - "@telegraph/segmented-control@npm:^0.4.3": version: 0.4.3 resolution: "@telegraph/segmented-control@npm:0.4.3" @@ -7417,29 +7207,16 @@ __metadata: languageName: node linkType: hard -"@telegraph/select@npm:>=0.1.0": - version: 0.1.0 - resolution: "@telegraph/select@npm:0.1.0" - dependencies: - "@telegraph/combobox": "npm:0.5.0" - "@telegraph/helpers": "npm:0.2.0" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/47d3abcebaab5353d5c12b2026572ab14dc63ace0fc9f0246426a73ddd65ca387cce555566a3560dd1682e4d4e390a5dc16991b7ab8d6499c773665f05967ae9 - languageName: node - linkType: hard - -"@telegraph/style-engine@npm:^0.4.0": - version: 0.4.0 - resolution: "@telegraph/style-engine@npm:0.4.0" +"@telegraph/select@npm:^0.2.0": + version: 0.2.0 + resolution: "@telegraph/select@npm:0.2.0" dependencies: - "@telegraph/tokens": "npm:^0.2.2" - postcss: "npm:^8.5.23" + "@telegraph/combobox": "npm:0.6.0" + "@telegraph/helpers": "npm:0.3.2" peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/78c424b2ccf1cbc892f09408fd6bf08f41fadc40842aea59f3832f245b014ed3b427c9cfb0ada637869bb2f95806aed6960488ab9df4d3ce7b00106fb5f0a716 + checksum: 10c0/cdb3ff806f2933cbf3eb4baee4dc6e947706a17628c64cd7bf2d2c734899f1c0603f52370870f304373d85f56e35d4a4e058e757251a3e975cfc00bc7865ace0 languageName: node linkType: hard @@ -7456,28 +7233,7 @@ __metadata: languageName: node linkType: hard -"@telegraph/tag@npm:>=0.3.0, @telegraph/tag@npm:^0.3.0": - version: 0.3.0 - resolution: "@telegraph/tag@npm:0.3.0" - dependencies: - "@telegraph/button": "npm:^0.8.0" - "@telegraph/compose-refs": "npm:^0.0.9" - "@telegraph/helpers": "npm:^0.2.0" - "@telegraph/icon": "npm:^0.6.0" - "@telegraph/layout": "npm:^0.6.0" - "@telegraph/tooltip": "npm:^0.6.0" - "@telegraph/typography": "npm:^0.5.0" - clsx: "npm:^2.1.1" - lucide-react: "npm:^1.23.0" - motion: "npm:^12.38.0" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/9165fc42da48ed3bd80c3a68682a03d3ac2c6d09faae5972489a9a1e83bbfe010d639dc7b8f9cf00d938b3ff09fb76d3c2cc2c063ff1e396e31c413c03eb6ff6 - languageName: node - linkType: hard - -"@telegraph/tag@npm:^0.3.3": +"@telegraph/tag@npm:^0.3.2, @telegraph/tag@npm:^0.3.3": version: 0.3.3 resolution: "@telegraph/tag@npm:0.3.3" dependencies: @@ -7498,38 +7254,38 @@ __metadata: languageName: node linkType: hard -"@telegraph/textarea@npm:>=0.4.0": - version: 0.4.0 - resolution: "@telegraph/textarea@npm:0.4.0" +"@telegraph/textarea@npm:^0.4.1": + version: 0.4.1 + resolution: "@telegraph/textarea@npm:0.4.1" dependencies: - "@telegraph/helpers": "npm:^0.2.0" - "@telegraph/typography": "npm:^0.5.0" + "@telegraph/helpers": "npm:^0.3.0" + "@telegraph/typography": "npm:^0.5.1" peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/889699a5ffec077f00912c4821b4b16cb80a80cad0f2bc5b8ddbc3ffff60391f4b255509f22f536793a2c21cfa6f226756e236bb24fb4588e451d2b57903c240 + checksum: 10c0/e9e72383c85546b6b4d916c22a6890517f5422cfdc23540bf9d25ebbb0115e65334413f8cf28cb4389dbe5be83b43e3bd5ed75f98a85ae90cb972f8371d5ac34 languageName: node linkType: hard -"@telegraph/toggle@npm:>=0.3.0": - version: 0.3.0 - resolution: "@telegraph/toggle@npm:0.3.0" - dependencies: - "@telegraph/button": "npm:^0.8.0" - "@telegraph/helpers": "npm:^0.2.0" - "@telegraph/icon": "npm:^0.6.0" - "@telegraph/layout": "npm:^0.6.0" - "@telegraph/tag": "npm:^0.3.0" - "@telegraph/typography": "npm:^0.5.0" +"@telegraph/toggle@npm:^0.3.2": + version: 0.3.2 + resolution: "@telegraph/toggle@npm:0.3.2" + dependencies: + "@telegraph/button": "npm:^0.9.1" + "@telegraph/helpers": "npm:^0.3.1" + "@telegraph/icon": "npm:^0.6.2" + "@telegraph/layout": "npm:^0.6.3" + "@telegraph/tag": "npm:^0.3.2" + "@telegraph/typography": "npm:^0.5.2" lucide-react: "npm:^1.23.0" peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/28cae3866821ed04cb121f9e1d11a4c14f9e5439d1ef6f042f79892044c94dffb7fcd560f9dc7e10ccb62272b258fbcc99e27ccb43e8dd5a6825b406e1a3e0ca + checksum: 10c0/2230e9b5982b74d01b1515d39bfe5ed3e79b4c3339850e0b5f789d90321fac4c383062ddab3467c119c3c22166596f5af9f9c9948fd332119fae38ca9b35a4fc languageName: node linkType: hard -"@telegraph/tokens@npm:>=0.2.2, @telegraph/tokens@npm:^0.2.2": +"@telegraph/tokens@npm:^0.2.2": version: 0.2.2 resolution: "@telegraph/tokens@npm:0.2.2" peerDependencies: @@ -7538,23 +7294,6 @@ __metadata: languageName: node linkType: hard -"@telegraph/tooltip@npm:>=0.6.0, @telegraph/tooltip@npm:^0.6.0": - version: 0.6.0 - resolution: "@telegraph/tooltip@npm:0.6.0" - dependencies: - "@base-ui/react": "npm:^1.5.0" - "@telegraph/appearance": "npm:^0.0.15" - "@telegraph/helpers": "npm:^0.2.0" - "@telegraph/layout": "npm:^0.6.0" - "@telegraph/typography": "npm:^0.5.0" - motion: "npm:^12.38.0" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/6a53652fe63fd239f634969e3d6b7c334edcf156b002c5c7151a5e28730e347ac38921186bb89683b1dfe1eaaf2ba441319d8e3764f32577893a4202c5ca3b4d - languageName: node - linkType: hard - "@telegraph/tooltip@npm:^0.6.1, @telegraph/tooltip@npm:^0.6.3": version: 0.6.3 resolution: "@telegraph/tooltip@npm:0.6.3" @@ -7572,20 +7311,6 @@ __metadata: languageName: node linkType: hard -"@telegraph/truncate@npm:^0.3.0": - version: 0.3.0 - resolution: "@telegraph/truncate@npm:0.3.0" - dependencies: - "@telegraph/helpers": "npm:^0.2.0" - "@telegraph/tooltip": "npm:^0.6.0" - "@telegraph/typography": "npm:^0.5.0" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/927cb45afbd9e1a04c6568581e680c410f46d63d814d1d0a34211e6b647517009599296d3c4d19bc772ee115cc1e3c1a1e4a8adeabc5f26c2c13bc039ae8fa54 - languageName: node - linkType: hard - "@telegraph/truncate@npm:^0.3.1": version: 0.3.1 resolution: "@telegraph/truncate@npm:0.3.1" @@ -7600,21 +7325,6 @@ __metadata: languageName: node linkType: hard -"@telegraph/typography@npm:>=0.5.0, @telegraph/typography@npm:^0.5.0": - version: 0.5.0 - resolution: "@telegraph/typography@npm:0.5.0" - dependencies: - "@telegraph/helpers": "npm:^0.2.0" - "@telegraph/layout": "npm:^0.6.0" - "@telegraph/style-engine": "npm:^0.4.0" - clsx: "npm:^2.1.1" - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - checksum: 10c0/49647b1d9835ba0c5a0d8f1305893fc3a571a3b92c55e807d1eeef78420b9f6d06e6cffcfe60cad30e57442e83db8c5d87aff3b7b1ba39375dd16bf4d1b9d492 - languageName: node - linkType: hard - "@telegraph/typography@npm:^0.5.1, @telegraph/typography@npm:^0.5.2": version: 0.5.2 resolution: "@telegraph/typography@npm:0.5.2" @@ -16493,27 +16203,6 @@ __metadata: languageName: node linkType: hard -"motion@npm:^12.38.0": - version: 12.38.0 - resolution: "motion@npm:12.38.0" - dependencies: - framer-motion: "npm:^12.38.0" - tslib: "npm:^2.4.0" - peerDependencies: - "@emotion/is-prop-valid": "*" - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@emotion/is-prop-valid": - optional: true - react: - optional: true - react-dom: - optional: true - checksum: 10c0/661a08b17592295b267df6a4a4887e9da78b29c310aaf60860fcfa68dac6cf2c39ce03701d6182159ad6f3000f28b8c62c3c6d139e620435d56b3bb5f28c5c1b - languageName: node - linkType: hard - "motion@npm:^13.1.0": version: 13.2.0 resolution: "motion@npm:13.2.0" @@ -16794,15 +16483,15 @@ __metadata: "@knocklabs/react": "workspace:*" "@knocklabs/typescript-config": "workspace:^" "@next/eslint-plugin-next": "npm:^15.3.1" - "@telegraph/button": "npm:>=0.8.0" + "@telegraph/button": "npm:^0.9.1" "@telegraph/combobox": "npm:^0.6.0" - "@telegraph/icon": "npm:>=0.6.0" - "@telegraph/input": "npm:>=0.5.0" - "@telegraph/layout": "npm:>=0.6.1" - "@telegraph/select": "npm:>=0.1.0" - "@telegraph/textarea": "npm:>=0.4.0" - "@telegraph/tokens": "npm:>=0.2.2" - "@telegraph/typography": "npm:>=0.5.0" + "@telegraph/icon": "npm:^0.6.2" + "@telegraph/input": "npm:^0.5.2" + "@telegraph/layout": "npm:^0.6.3" + "@telegraph/select": "npm:^0.2.0" + "@telegraph/textarea": "npm:^0.4.1" + "@telegraph/tokens": "npm:^0.2.2" + "@telegraph/typography": "npm:^0.5.2" "@types/eslint": "npm:^8.44.7" "@types/node": "npm:^24" "@types/react": "npm:^19.2.17" @@ -16835,15 +16524,15 @@ __metadata: "@knocklabs/react": "workspace:^" "@knocklabs/typescript-config": "workspace:^" "@next/eslint-plugin-next": "npm:^15.3.1" - "@telegraph/button": "npm:>=0.8.0" + "@telegraph/button": "npm:^0.9.1" "@telegraph/combobox": "npm:^0.6.0" - "@telegraph/icon": "npm:>=0.6.0" - "@telegraph/input": "npm:>=0.5.0" - "@telegraph/layout": "npm:>=0.6.1" - "@telegraph/select": "npm:>=0.1.0" - "@telegraph/textarea": "npm:>=0.4.0" - "@telegraph/tokens": "npm:>=0.2.2" - "@telegraph/typography": "npm:>=0.5.0" + "@telegraph/icon": "npm:^0.6.2" + "@telegraph/input": "npm:^0.5.2" + "@telegraph/layout": "npm:^0.6.3" + "@telegraph/select": "npm:^0.2.0" + "@telegraph/textarea": "npm:^0.4.1" + "@telegraph/tokens": "npm:^0.2.2" + "@telegraph/typography": "npm:^0.5.2" "@types/eslint": "npm:^8.44.7" "@types/node": "npm:^24" "@types/react": "npm:^19.2.17" @@ -17626,17 +17315,6 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.5.23": - version: 8.5.25 - resolution: "postcss@npm:8.5.25" - dependencies: - nanoid: "npm:^3.3.16" - picocolors: "npm:^1.1.1" - source-map-js: "npm:^1.2.1" - checksum: 10c0/0a12c1e74b456c57122e81f684e02fd98ff4d57526f794d10c996df1147158808f5ae373ac82b988c8de6cbbaa82dbd7b13803b14f5dd3cf7cc6a42ccad5c9f2 - languageName: node - linkType: hard - "postcss@npm:^8.5.26": version: 8.5.28 resolution: "postcss@npm:8.5.28" From 97d8b75f4469e704e0690a80159667ec63f34665 Mon Sep 17 00:00:00 2001 From: Kyle McDonald Date: Fri, 4 Sep 2026 14:11:43 -0500 Subject: [PATCH 5/9] fix(react): name the Slack combobox trigger without changing its markup Earlier commits fixed the trigger's accessible name by giving each option a string `label`. An option renders `label || children || value`, so that also stopped the children rendering and cost the hash/lock icon; routing it through `trailingIcon` got it back but moved it to the right edge, and the trigger's tags lost it entirely. Combobox.Trigger spreads consumer props over its own `aria-label`, so the name can be set directly and the markup left alone. The options go back to exactly what main renders, and the icon returns to its old position in both the dropdown and the tags. The option's own name in the listbox needs nothing: its icon is aria-hidden, so the name already resolves from its text. --- .changeset/great-pugs-shake.md | 2 +- .changeset/quiet-hounds-wave.md | 7 +++ .changeset/shaggy-moons-sleep.md | 7 --- .../SlackChannelCombobox.tsx | 47 ++++++++++++------- .../test/slack/SlackChannelCombobox.test.tsx | 14 +++--- 5 files changed, 45 insertions(+), 32 deletions(-) create mode 100644 .changeset/quiet-hounds-wave.md delete mode 100644 .changeset/shaggy-moons-sleep.md diff --git a/.changeset/great-pugs-shake.md b/.changeset/great-pugs-shake.md index cec77c301..0611d47f9 100644 --- a/.changeset/great-pugs-shake.md +++ b/.changeset/great-pugs-shake.md @@ -6,4 +6,4 @@ Pin `@telegraph/combobox` to `^0.6.0` and fix the Slack channel combobox's acces `@knocklabs/react` declared `@telegraph/combobox: ">=0.5.0"`, so consumers resolved to whatever the latest release was. The range is now a caret range, and the package is upgraded to 0.6.0. -`SlackChannelCombobox` renders an icon beside each channel name, so its options have element children. In 0.6.0 an option's accessible name falls back to its `value` when the label is not a string, which made the trigger announce the raw Slack channel id instead of the channel name. Each option now passes an explicit `label`. +`SlackChannelCombobox` renders an icon beside each channel name, so its options have element children. In 0.6.0 an option's accessible name falls back to its `value` when the label is not a string, which made the trigger announce the raw Slack channel id instead of the channel name. diff --git a/.changeset/quiet-hounds-wave.md b/.changeset/quiet-hounds-wave.md new file mode 100644 index 000000000..c89e7176c --- /dev/null +++ b/.changeset/quiet-hounds-wave.md @@ -0,0 +1,7 @@ +--- +"@knocklabs/react": patch +--- + +Name the Slack channel combobox trigger without changing what it renders + +The trigger announced the raw Slack channel id, because an option's accessible name is derived from its children and these render an icon beside the name. The component now passes its own `aria-label`, which the trigger spreads over the one it derives, so the channel icon and the layout stay exactly as they were. diff --git a/.changeset/shaggy-moons-sleep.md b/.changeset/shaggy-moons-sleep.md deleted file mode 100644 index 2c73045ed..000000000 --- a/.changeset/shaggy-moons-sleep.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@knocklabs/react": patch ---- - -Keep the channel-type icon on Slack combobox options - -`@telegraph/combobox` renders an option as `label || children || value`, so giving an option a string `label` for its accessible name stops its children rendering. The hash/lock icon now comes through the option's trailing icon slot, since the leading slot belongs to the selection check. diff --git a/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx b/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx index 3aeaea9bf..5f429a115 100644 --- a/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx +++ b/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx @@ -8,6 +8,7 @@ import { useTranslations, } from "@knocklabs/react-core"; import { Combobox } from "@telegraph/combobox"; +import { Icon } from "@telegraph/icon"; import { Stack } from "@telegraph/layout"; import { Text } from "@telegraph/typography"; import { Hash, Lock } from "lucide-react"; @@ -148,6 +149,25 @@ export const SlackChannelCombobox: FunctionComponent< [currentConnectedChannels], ); + // An option's accessible name is derived from its children, and ours are + // elements (an icon beside the name), so the combobox falls back to the raw + // channel id. Name the trigger ourselves rather than flattening the option + // to a bare string, which would cost the icon. + const triggerLabel = useMemo(() => { + const namesById = new Map( + slackChannels.map((channel) => [channel.id, channel.name]), + ); + const connectedNames = comboboxValue + .map((channelId) => namesById.get(channelId)) + .filter((name): name is string => !!name); + + // Falling back to the placeholder matches what the combobox would have + // named the trigger on its own. It has to stay a string: the trigger + // spreads our props over its own `aria-label`, so undefined would leave + // the trigger unnamed rather than deferring. + return connectedNames.join(", ") || searchPlaceholder || ""; + }, [slackChannels, comboboxValue, searchPlaceholder]); + if (slackChannels.length > MAX_ALLOWED_CHANNELS) { return ( - + {slackChannels.map((channel) => ( - // `label` has to be a string: the combobox derives both the - // option's and the trigger's accessible name from it, and falls - // back to `value` (the raw channel id) for anything else. That - // rules out passing the icon as children, since children lose to - // `label`. The leading slot is taken by the selection check, so - // the channel type sits in the trailing slot instead. - + + + + {channel.name} + + ))} diff --git a/packages/react/test/slack/SlackChannelCombobox.test.tsx b/packages/react/test/slack/SlackChannelCombobox.test.tsx index 9d4144abb..134a5a377 100644 --- a/packages/react/test/slack/SlackChannelCombobox.test.tsx +++ b/packages/react/test/slack/SlackChannelCombobox.test.tsx @@ -46,11 +46,9 @@ describe("SlackChannelCombobox", () => { renderCombobox(); - // Each option renders an icon beside the channel name, so its children are - // elements rather than a string. @telegraph/combobox resolves an option's - // accessible label from `label || children || value` and falls back to - // `value` for non-string labels, so without an explicit `label` prop the - // trigger announces the raw Slack channel id. + // Options render an icon beside the channel name, so their children are + // elements and the combobox falls back to naming the trigger after the raw + // channel id. The component passes its own `aria-label` instead. expect(screen.getByRole("combobox")).toHaveAccessibleName("general"); }); @@ -91,9 +89,9 @@ describe("SlackChannelCombobox", () => { const option = await screen.findByRole("option", { name: "general" }); - // An option's children lose to its `label`, so the icon has to come - // through an icon slot. Each option renders two: the selection check and - // the hash/lock that says whether the channel is private. + // Two icons per option: the selection check, and the hash/lock that says + // whether the channel is private. The second one is the reason the option + // keeps element children instead of a bare string label. expect(option.querySelectorAll("[data-tgph-icon-svg]")).toHaveLength(2); }); From 4343725d8f24807cdc7ce1dbb1228a2de9a1414f Mon Sep 17 00:00:00 2001 From: Kyle McDonald Date: Tue, 8 Sep 2026 12:57:45 -0500 Subject: [PATCH 6/9] refactor(react): build the Slack channel lookup once currentConnectedChannels and triggerLabel each built their own Map over slackChannels, so every render paid for two passes over a list that can hold a thousand channels. Lift one memoized map and read both from it. --- .../SlackChannelCombobox.tsx | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx b/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx index 5f429a115..5c84636c6 100644 --- a/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx +++ b/packages/react/src/modules/slack/components/SlackChannelCombobox/SlackChannelCombobox.tsx @@ -62,21 +62,22 @@ export const SlackChannelCombobox: FunctionComponent< updating: connectedChannelsUpdating, } = useConnectedSlackChannels({ slackChannelsRecipientObject }); - const currentConnectedChannels = useMemo(() => { - // Used to make sure we're only showing currently available channels to select from. - // There are cases where a channel is "connected" in Knock, but it wouldn't be - // posting to it if the channel is private and the Slackbot doesn't belong to it, - // so the channel won't show up here and it won't be posted to. - const slackChannelsMap = new Map( - slackChannels.map((channel) => [channel.id, channel]), - ); + const slackChannelsById = useMemo( + () => new Map(slackChannels.map((channel) => [channel.id, channel])), + [slackChannels], + ); - return ( - connectedChannels?.filter((connectedChannel) => { - return slackChannelsMap.has(connectedChannel.channel_id || ""); - }) || [] - ); - }, [connectedChannels, slackChannels]); + const currentConnectedChannels = useMemo( + () => + // Used to make sure we're only showing currently available channels to select from. + // There are cases where a channel is "connected" in Knock, but it wouldn't be + // posting to it if the channel is private and the Slackbot doesn't belong to it, + // so the channel won't show up here and it won't be posted to. + connectedChannels?.filter((connectedChannel) => + slackChannelsById.has(connectedChannel.channel_id || ""), + ) || [], + [connectedChannels, slackChannelsById], + ); const inErrorState = useMemo( () => @@ -154,11 +155,8 @@ export const SlackChannelCombobox: FunctionComponent< // channel id. Name the trigger ourselves rather than flattening the option // to a bare string, which would cost the icon. const triggerLabel = useMemo(() => { - const namesById = new Map( - slackChannels.map((channel) => [channel.id, channel.name]), - ); const connectedNames = comboboxValue - .map((channelId) => namesById.get(channelId)) + .map((channelId) => slackChannelsById.get(channelId)?.name) .filter((name): name is string => !!name); // Falling back to the placeholder matches what the combobox would have @@ -166,7 +164,7 @@ export const SlackChannelCombobox: FunctionComponent< // spreads our props over its own `aria-label`, so undefined would leave // the trigger unnamed rather than deferring. return connectedNames.join(", ") || searchPlaceholder || ""; - }, [slackChannels, comboboxValue, searchPlaceholder]); + }, [slackChannelsById, comboboxValue, searchPlaceholder]); if (slackChannels.length > MAX_ALLOWED_CHANNELS) { return ( From 4059f16266ab2ef78e5a6162989ebc350524c9f2 Mon Sep 17 00:00:00 2001 From: Kyle McDonald Date: Tue, 8 Sep 2026 13:22:20 -0500 Subject: [PATCH 7/9] chore(react): fold the combobox upgrade into the dependency changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The combobox entry described the accessible-name bug but, once the fix moved to an aria-label on the trigger, no longer described the fix — and its other half repeated the dependency pin that the telegraph entry already covers. Consumers would have read both twice, one of them as an unresolved defect. --- .changeset/great-pugs-shake.md | 9 --------- .changeset/olive-eels-smile.md | 4 +++- 2 files changed, 3 insertions(+), 10 deletions(-) delete mode 100644 .changeset/great-pugs-shake.md diff --git a/.changeset/great-pugs-shake.md b/.changeset/great-pugs-shake.md deleted file mode 100644 index 0611d47f9..000000000 --- a/.changeset/great-pugs-shake.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@knocklabs/react": patch ---- - -Pin `@telegraph/combobox` to `^0.6.0` and fix the Slack channel combobox's accessible name - -`@knocklabs/react` declared `@telegraph/combobox: ">=0.5.0"`, so consumers resolved to whatever the latest release was. The range is now a caret range, and the package is upgraded to 0.6.0. - -`SlackChannelCombobox` renders an icon beside each channel name, so its options have element children. In 0.6.0 an option's accessible name falls back to its `value` when the label is not a string, which made the trigger announce the raw Slack channel id instead of the channel name. diff --git a/.changeset/olive-eels-smile.md b/.changeset/olive-eels-smile.md index 9dac5a496..a40f8b9d7 100644 --- a/.changeset/olive-eels-smile.md +++ b/.changeset/olive-eels-smile.md @@ -4,4 +4,6 @@ Pin every `@telegraph/*` dependency to a caret range at its latest release -These were open `>=` ranges, so an install resolved to whatever was newest on npm rather than a version this package had been built against. `@telegraph/select` was still on 0.1.0, which pins `@telegraph/combobox` at 0.5.0, so consumers received two copies of the combobox. Moving it to 0.2.0 leaves one. +These were open `>=` ranges, so an install resolved to whatever was newest on npm rather than a version this package had been built against. In practice that already meant `@telegraph/combobox` 0.6.0, a rewrite onto Base UI, so the three comboboxes are now tested and pinned against it rather than receiving it by accident. + +`@telegraph/select` was a second route to the same problem: it sat on 0.1.0, which pins `@telegraph/combobox` at 0.5.0, so consumers received two copies of the combobox. Moving it to 0.2.0 leaves one. From f633637a19fc8a1850f115d1e4c9eb2618abbc1f Mon Sep 17 00:00:00 2001 From: Kyle McDonald Date: Tue, 8 Sep 2026 16:41:42 -0500 Subject: [PATCH 8/9] test(react): make the stale-connection tests able to fail Both "ignores connections that are no longer available / belong to another team" cases asserted only on the trigger's accessible name. A selected id with no matching option resolves to undefined and is filtered out of that name, so removing the guard each test exists to cover left the name unchanged and both passed regardless. The stray id does render a tag, since the trigger falls back to the raw value when no option matches, so assert the tag is absent instead. --- packages/react/test/ms-teams/MsTeamsChannelCombobox.test.tsx | 4 ++++ packages/react/test/slack/SlackChannelCombobox.test.tsx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/react/test/ms-teams/MsTeamsChannelCombobox.test.tsx b/packages/react/test/ms-teams/MsTeamsChannelCombobox.test.tsx index 09358235c..24bc35589 100644 --- a/packages/react/test/ms-teams/MsTeamsChannelCombobox.test.tsx +++ b/packages/react/test/ms-teams/MsTeamsChannelCombobox.test.tsx @@ -111,7 +111,11 @@ describe("MsTeamsChannelInTeamCombobox", () => { />, ); + // The accessible name alone cannot catch this: an id with no matching + // option resolves to undefined and drops out of the name. It does render a + // tag though, so assert on that. expect(screen.getByRole("combobox")).toHaveAccessibleName("General"); + expect(screen.queryByText("channel_from_team_2")).not.toBeInTheDocument(); }); test("shows the placeholder when no channels are connected", () => { diff --git a/packages/react/test/slack/SlackChannelCombobox.test.tsx b/packages/react/test/slack/SlackChannelCombobox.test.tsx index 134a5a377..d6d57b11e 100644 --- a/packages/react/test/slack/SlackChannelCombobox.test.tsx +++ b/packages/react/test/slack/SlackChannelCombobox.test.tsx @@ -77,7 +77,11 @@ describe("SlackChannelCombobox", () => { renderCombobox(); + // The accessible name alone cannot catch this: an id with no matching + // option contributes nothing to the name. It does render a tag though, so + // assert on that. expect(screen.getByRole("combobox")).toHaveAccessibleName("general"); + expect(screen.queryByText("C0NOPE")).not.toBeInTheDocument(); }); test("each option still shows its channel-type icon", async () => { From 668e8399842c80df58e3294c4de484708f105d57 Mon Sep 17 00:00:00 2001 From: Kyle McDonald Date: Tue, 8 Sep 2026 17:01:23 -0500 Subject: [PATCH 9/9] test(react): cover the Slack combobox's fallback paths Three branches added by this PR had no test behind them: connected channels arriving undefined before the first load, a connection with no channel id at all, and the trigger label falling through to "" when nothing is connected and the error state carries no label to show. That last one is the reason the label is built as a string rather than left undefined, so it is worth pinning down. --- .../test/slack/SlackChannelCombobox.test.tsx | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/packages/react/test/slack/SlackChannelCombobox.test.tsx b/packages/react/test/slack/SlackChannelCombobox.test.tsx index d6d57b11e..d14860f01 100644 --- a/packages/react/test/slack/SlackChannelCombobox.test.tsx +++ b/packages/react/test/slack/SlackChannelCombobox.test.tsx @@ -8,7 +8,8 @@ const slackChannels = [ { id: "C0456DEF", name: "engineering", is_private: true }, ]; -let connectedChannels: Array<{ channel_id?: string }> = []; +let connectedChannels: Array<{ channel_id?: string }> | undefined = []; +let connectionStatus = "connected"; const updateConnectedChannels = vi.fn().mockResolvedValue(undefined); vi.mock("@knocklabs/react-core", async () => { @@ -16,10 +17,7 @@ vi.mock("@knocklabs/react-core", async () => { return { ...actual, useTranslations: () => ({ t: (k: string) => k }), - useKnockSlackClient: () => ({ - connectionStatus: "connected", - errorLabel: null, - }), + useKnockSlackClient: () => ({ connectionStatus, errorLabel: null }), useSlackChannels: () => ({ data: slackChannels, isLoading: false }), useConnectedSlackChannels: () => ({ data: connectedChannels, @@ -38,6 +36,7 @@ const renderCombobox = () => describe("SlackChannelCombobox", () => { beforeEach(() => { connectedChannels = []; + connectionStatus = "connected"; updateConnectedChannels.mockClear(); }); @@ -115,4 +114,37 @@ describe("SlackChannelCombobox", () => { ]), ); }); + + test("renders before the connected channels have loaded", () => { + connectedChannels = undefined; + + renderCombobox(); + + expect(screen.getByRole("combobox")).toHaveAccessibleName( + "slackSearchbarNoChannelsConnected", + ); + }); + + test("ignores a connection that carries no channel id", () => { + connectedChannels = [{ channel_id: "C0123ABC" }, {}]; + + renderCombobox(); + + expect(screen.getByRole("combobox")).toHaveAccessibleName("general"); + }); + + test("stays disabled when the connection errored with no label to show", () => { + // Nothing is connected and `errorLabel` is null, so both the names and the + // placeholder the trigger falls back to are empty. It still has to be + // handed a string, since undefined would blank the label the trigger + // derives for itself. + connectionStatus = "error"; + connectedChannels = []; + + renderCombobox(); + + const trigger = screen.getByRole("combobox"); + expect(trigger).toBeDisabled(); + expect(trigger).toHaveAttribute("aria-label", ""); + }); });