Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 72 additions & 2 deletions components/ui/Accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Box, Stack } from "@telegraph/layout";
import { MenuItem } from "@telegraph/menu";
import { Icon } from "@telegraph/icon";
import { AnimatePresence, motion } from "framer-motion";
import { useState, useMemo } from "react";
import { useState, useMemo, useLayoutEffect, useRef } from "react";
import { Text, Code } from "@telegraph/typography";
import { ChevronRight } from "lucide-react";

Expand All @@ -15,11 +15,24 @@ const AccordionGroup = ({ children }) => (
</div>
);

function getHashFragment(): string {
if (typeof window === "undefined") return "";
const { hash } = window.location;
if (!hash || hash === "#") return "";
try {
return decodeURIComponent(hash.slice(1));
} catch {
return hash.slice(1);
}
}

type AccordionProps = {
children: React.ReactNode;
title: string;
description?: string;
defaultOpen?: boolean;
/** When set, this slug is used as the element `id` and the accordion opens if the URL hash matches (for deep links). Use a URL-safe hyphenated fragment, e.g. `my-section`. */
anchorSlug?: string;
};

// Helper function to parse title and split into text and code parts
Expand Down Expand Up @@ -68,12 +81,69 @@ const Accordion = ({
title,
description,
defaultOpen = false,
anchorSlug,
}: AccordionProps) => {
const [open, setOpen] = useState<boolean>(defaultOpen);
const titleParts = useMemo(() => parseTitleWithCode(title), [title]);
const elementRef = useRef<HTMLDivElement>(null);

useLayoutEffect(() => {
if (!anchorSlug) return;

let cancelled = false;
let resizeObserver: ResizeObserver | null = null;
let stopWatchingTimeoutId: number | null = null;

const performScroll = () => {
if (cancelled) return;
elementRef.current?.scrollIntoView({
block: "start",
behavior: "smooth",
});
};

const stopWatching = () => {
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
if (stopWatchingTimeoutId !== null) {
clearTimeout(stopWatchingTimeoutId);
stopWatchingTimeoutId = null;
}
};

const syncFromHash = () => {
if (getHashFragment() !== anchorSlug) return;
setOpen(true);
performScroll();

// Re-scroll whenever layout shifts (images loading, async content, etc.)
// so the accordion stays anchored to its intended position even as the
// document height changes.
stopWatching();
if (typeof ResizeObserver !== "undefined") {
resizeObserver = new ResizeObserver(() => {
performScroll();
});
resizeObserver.observe(document.body);
}
// Stop correcting after layout has had time to settle so we don't
// fight subsequent user-initiated scrolls.
stopWatchingTimeoutId = window.setTimeout(stopWatching, 1500);
};

syncFromHash();
window.addEventListener("hashchange", syncFromHash);
return () => {
cancelled = true;
stopWatching();
window.removeEventListener("hashchange", syncFromHash);
};
}, [anchorSlug]);

return (
<Box role="listitem">
<Box tgphRef={elementRef} role="listitem" id={anchorSlug}>
<MenuItem
as="button"
onClick={() => setOpen(!open)}
Expand Down
2 changes: 1 addition & 1 deletion content/integrations/chat/microsoft-teams/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ To use TeamsKit, you'll need to configure the API permissions and OAuth redirect

## How to set channel data for a Microsoft Teams integration in Knock

In Knock, the [`ChannelData`](/managing-recipients/setting-channel-data) concept provides you a way of storing recipient-specific connection data for a given integration. If you reference the [channel data requirements for Microsoft Teams](/managing-recipients/setting-channel-data#chat-app-channels), you'll see that there are two different schemas for an `MsTeamsConnection` stored on a [`User`](/concepts/users) or an [`Object`](/concepts/objects) in Knock.
In Knock, the [`ChannelData`](/managing-recipients/setting-channel-data) concept provides you a way of storing recipient-specific connection data for a given integration. If you reference the [channel data requirements for Microsoft Teams](/managing-recipients/setting-channel-data#microsoft-teams-channel-data), you'll see that there are two different schemas for an `MsTeamsConnection` stored on a [`User`](/concepts/users) or an [`Object`](/concepts/objects) in Knock.

Here's an example of setting channel data on an `Object` in Knock.

Expand Down
2 changes: 1 addition & 1 deletion content/managing-recipients/setting-channel-data.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ The `PushDevice` object is used to optionally set device-level metadata for a pu
| incoming_webhook.url | `string` | The Discord incoming webhook URL (to be used instead of the properties above) |

</Accordion>
<Accordion title ="Microsoft Teams">
<Accordion title ="Microsoft Teams" anchorSlug="microsoft-teams-channel-data">
| Property | Type | Description |
| ----------- | --------------------- | ---------------------------------- |
| connections | `MsTeamsConnection[]` | One or more connections to MsTeams |
Expand Down
4 changes: 2 additions & 2 deletions content/multi-tenancy/per-tenant-preferences.mdx

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@scotidodson FYI that I added the anchorSlug for the tenant-specific preference merge hierarhcy (since I mentioned it yesterday and for another example to view/test): https://docs-git-rt-kno-7916-linking-to-accordions-knocklabs.vercel.app/multi-tenancy/per-tenant-preferences#tenant-preference-evaluation-rules

Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,11 @@ The Knock workflow engine uses that `tenant` parameter to evaluate the user's `P

## Tenant preference evaluation rules

Here are a few things to keep in mind when using tenant preferences. You can learn more about how preferences are merged and evaluated [here](/preferences/overview#merging-preferences).
Here are a few things to keep in mind when using tenant preferences. You can learn more about how preferences are merged and evaluated [here](/preferences/overview#tenant-specific-preference-merge-hierarchy).

- When executing a workflow trigger, passing in a `tenant` will automatically load that tenant's default `PreferenceSet` (if one exists) for all recipients of the workflow. These tenant-level defaults will override a recipient's own `default` preferences.
- If the recipient has any per-tenant preferences set for that `tenant.id`, they will take precedence over the tenant-level default preferences. For more information on how to override a recipient's per-tenant preferences to respect the tenant-level default preferences, see the [frequently asked questions](#frequently-asked-questions) below.
- If there is no default `PreferenceSet` on the tenant AND the recipient has no per-tenant preferences set, the recipient's `default` preferences will be used. As always, the recipient's `default` preferences are [merged](/preferences/overview#merging-preferences) with the environment-level preference defaults.
- If there is no default `PreferenceSet` on the tenant AND the recipient has no per-tenant preferences set, the recipient's `default` preferences will be used. As always, the recipient's `default` preferences are [merged](/preferences/overview#tenant-specific-preference-merge-hierarchy) with the environment-level preference defaults.

## Frequently asked questions

Expand Down
2 changes: 1 addition & 1 deletion content/preferences/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ The following hierarchies are used when merging preferences.
The environment-level setting to opt all users into `collaboration` notifications will be respected because the recipient doesn't have an explicit preference for that category, but the recipient's preference to specifically opt in to SMS messages will override the environment-level setting to opt out.

</Accordion>
<Accordion title="Tenant-specific preference merge hierarchy">
<Accordion title="Tenant-specific preference merge hierarchy" anchorSlug="tenant-specific-preference-merge-hierarchy">
The following hierarchy is used to merge preferences when a `tenant` is applied to the workflow trigger. Each item in the list takes precedence over the ones that follow it:

1. A recipient's [tenant-specific preference](/multi-tenancy/per-tenant-preferences) set
Expand Down
2 changes: 1 addition & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
import "./.next/types/routes.d.ts";
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
4 changes: 2 additions & 2 deletions styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ textarea:not([rows]) {
min-height: 10em;
}

/* Anything that has been anchored to should have extra scroll margin */
/* Anything anchored to via #hash should clear the sticky page header */
:target {
scroll-margin-block: 5ex;
scroll-margin-top: calc(var(--header-height) + 1rem);
}

.dot-bg {
Expand Down
Loading