rift-ds 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/behaviors/LinkProvider.d.ts +36 -0
  2. package/behaviors/LinkProvider.js +15 -0
  3. package/chat/ChatProvider.d.ts +5 -1
  4. package/chat/ChatProvider.js +11 -1
  5. package/components/AiButton/AiButton.d.ts +7 -0
  6. package/components/AiButton/AiButton.js +4 -1
  7. package/components/AppLayout/AppLayout.d.ts +7 -0
  8. package/components/AppLayout/AppLayout.js +3 -1
  9. package/components/AppSidebar/AppSidebar.d.ts +7 -0
  10. package/components/AppSidebar/AppSidebar.js +8 -3
  11. package/components/BottomNav/BottomNav.d.ts +7 -0
  12. package/components/BottomNav/BottomNav.js +5 -2
  13. package/components/Breadcrumb/Breadcrumb.d.ts +8 -0
  14. package/components/Breadcrumb/Breadcrumb.js +3 -2
  15. package/components/Button/Button.d.ts +7 -0
  16. package/components/Button/Button.js +4 -1
  17. package/components/Card/Card.d.ts +7 -0
  18. package/components/Card/Card.js +4 -1
  19. package/components/CircularButton/CircularButton.d.ts +8 -0
  20. package/components/CircularButton/CircularButton.js +3 -1
  21. package/components/ContactCard/ContactCard.d.ts +7 -0
  22. package/components/ContactCard/ContactCard.js +4 -1
  23. package/components/LinkList/LinkList.d.ts +8 -0
  24. package/components/LinkList/LinkList.js +3 -2
  25. package/components/NavList/NavList.d.ts +7 -0
  26. package/components/NavList/NavList.js +2 -0
  27. package/components/SidebarPanel/SidebarPanel.d.ts +7 -0
  28. package/components/SidebarPanel/SidebarPanel.js +4 -1
  29. package/components/SourceChip/SourceChip.d.ts +7 -0
  30. package/components/SourceChip/SourceChip.js +5 -3
  31. package/components/SourceTrail/SourceTrail.d.ts +7 -0
  32. package/components/SourceTrail/SourceTrail.js +2 -0
  33. package/components/ThreadPanel/ThreadPanel.d.ts +8 -0
  34. package/components/ThreadPanel/ThreadPanel.js +9 -2
  35. package/components/TopAppBar/TopAppBar.d.ts +7 -0
  36. package/components/TopAppBar/TopAppBar.js +4 -1
  37. package/components/Widget/Widget.d.ts +8 -0
  38. package/components/Widget/Widget.js +3 -2
  39. package/package.json +5 -1
  40. package/tokens/presets/mono.css +168 -168
@@ -0,0 +1,36 @@
1
+ import { default as React } from 'react';
2
+ /**
3
+ * The props a link component receives: every anchor attribute, the ref, and
4
+ * an `href` that is always a string. Next.js's `Link` takes these as they
5
+ * are; a router whose link takes `to` instead needs a one-line adapter:
6
+ * `({ href, ...props }) => <RouterLink to={href} {...props} />`.
7
+ */
8
+ export type LinkComponentProps = React.ComponentPropsWithRef<'a'> & {
9
+ href: string;
10
+ };
11
+ /** A component that renders a link in place of a plain `<a>`, such as a router's. */
12
+ export type LinkComponent = React.ElementType<LinkComponentProps>;
13
+ export interface LinkProviderProps {
14
+ /**
15
+ * The link component every rift-ds link under this provider renders, such
16
+ * as Next.js's `Link`. Named like the instance prop it stands in for.
17
+ */
18
+ linkComponent: LinkComponent;
19
+ /** The part of the app whose links it applies to, usually the whole app. */
20
+ children?: React.ReactNode;
21
+ }
22
+ /**
23
+ * Sets the link component for every client-rendered rift-ds component below
24
+ * it that takes an `href`, so their links navigate through the app's router
25
+ * instead of reloading the page. A component's own `linkComponent` prop
26
+ * wins over it. The link components that can render on the server cannot
27
+ * read a provider and take the prop alone (design.md's Components intro
28
+ * names them, and scripts/validate-component-contract.mjs keeps them off
29
+ * the provider).
30
+ */
31
+ export declare function LinkProvider({ linkComponent, children }: LinkProviderProps): React.JSX.Element;
32
+ /**
33
+ * The link component to render: the component's own `linkComponent` prop,
34
+ * else the nearest LinkProvider's, else a plain `<a>`.
35
+ */
36
+ export declare function useLinkComponent(override?: LinkComponent): LinkComponent;
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext } from "react";
4
+ const LinkContext = createContext(null);
5
+ function LinkProvider({ linkComponent, children }) {
6
+ return /* @__PURE__ */ jsx(LinkContext.Provider, { value: linkComponent, children });
7
+ }
8
+ function useLinkComponent(override) {
9
+ const provided = useContext(LinkContext);
10
+ return override ?? provided ?? "a";
11
+ }
12
+ export {
13
+ LinkProvider,
14
+ useLinkComponent
15
+ };
@@ -36,7 +36,11 @@ export interface ChatProviderProps {
36
36
  renderers?: ChatCardRenderers;
37
37
  /** Draws an artifact's preview by kind, for artifacts that are live React content rather than a document or a URL. */
38
38
  artifactRenderers?: ChatArtifactRenderers;
39
- /** Renders a same-site link in an answer. The default is a plain anchor; a Next.js host passes its Link. */
39
+ /**
40
+ * Renders a same-site link in an answer. The default renders through the
41
+ * nearest LinkProvider's link component, else a plain anchor; passing
42
+ * this wins over both.
43
+ */
40
44
  renderLink?: ChatLinkRenderer;
41
45
  /** Which hrefs in an answer become links. Everything else renders as text. */
42
46
  isInternalHref?: (href: string) => boolean;
@@ -7,7 +7,17 @@ import { createFollowupsFetcher, followupTarget, DEFAULT_FALLBACK_FOLLOWUPS, pic
7
7
  import { useThreads } from "./threads.js";
8
8
  import { createFetchTransport } from "./transport.js";
9
9
  import { DEFAULT_PLACEHOLDER_COPY, useChat } from "./useChat.js";
10
- const defaultRenderLink = ({ href, children, onNavigate }) => /* @__PURE__ */ jsx("a", { href, onClick: onNavigate, children });
10
+ import { useLinkComponent } from "../behaviors/LinkProvider.js";
11
+ function DefaultChatLink({ href, children, onNavigate }) {
12
+ const Link = useLinkComponent();
13
+ return (
14
+ // The hook returns a stable reference from context (or 'a'), never a
15
+ // component created here, which is what the rule guards against.
16
+ // eslint-disable-next-line react-hooks/static-components
17
+ /* @__PURE__ */ jsx(Link, { href, onClick: onNavigate, children })
18
+ );
19
+ }
20
+ const defaultRenderLink = (props) => /* @__PURE__ */ jsx(DefaultChatLink, { ...props });
11
21
  function ChatProvider({
12
22
  transport,
13
23
  endpoint = "/api/chat",
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
2
3
  import { PromptSuggestion } from '../PromptSuggestions/PromptSuggestions';
3
4
  /** The content of AiButton's summary panel. */
4
5
  export interface AiButtonSummary {
@@ -59,6 +60,12 @@ type AiButtonOwnProps = {
59
60
  suggestionsLabel?: string;
60
61
  /** Additional CSS classes */
61
62
  className?: string;
63
+ /**
64
+ * The component the link renders with, such as Next.js's `Link`, so it
65
+ * navigates through the app's router. Wins over a LinkProvider's; a plain
66
+ * `<a>` when neither is set.
67
+ */
68
+ linkComponent?: LinkComponent;
62
69
  };
63
70
  export interface AiButtonProps extends AiButtonOwnProps, Omit<React.ComponentPropsWithoutRef<'button'>, keyof AiButtonOwnProps | 'type'> {
64
71
  }
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
3
  import React, { useState, useRef, useEffect } from "react";
4
+ import { useLinkComponent } from "../../behaviors/LinkProvider.js";
4
5
  import { PromptSuggestions } from "../PromptSuggestions/PromptSuggestions.js";
5
6
  import { Skeleton } from "../Skeleton/Skeleton.js";
6
7
  import { StreamingText } from "../StreamingText/StreamingText.js";
@@ -25,9 +26,11 @@ const AiButton = React.forwardRef(
25
26
  dismissLabel = "Dismiss summary",
26
27
  suggestionsLabel = "Suggested prompts",
27
28
  className = "",
29
+ linkComponent,
28
30
  ...rest
29
31
  }, ref) => {
30
32
  const baseClass = "ds-ai-button";
33
+ const Link = useLinkComponent(linkComponent);
31
34
  const [hoverOpen, setHoverOpen] = useState(false);
32
35
  const [dismissed, setDismissed] = useState(false);
33
36
  const [phase, setPhase] = useState("skeleton");
@@ -91,7 +94,7 @@ const AiButton = React.forwardRef(
91
94
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__text`, children: label })
92
95
  ] });
93
96
  const control = href && !disabled ? /* @__PURE__ */ jsx(
94
- "a",
97
+ Link,
95
98
  {
96
99
  ...rest,
97
100
  ref,
@@ -1,5 +1,6 @@
1
1
  import { default as React } from 'react';
2
2
  import { AppSidebarSection, AppSidebarProfile } from '../AppSidebar/AppSidebar';
3
+ import { LinkComponent } from '../../behaviors/LinkProvider';
3
4
  /** Props owned by AppLayout itself — everything else falls through to the outer wrapper `<div>`. */
4
5
  type AppLayoutOwnProps = {
5
6
  /** Sidebar navigation sections */
@@ -28,6 +29,12 @@ type AppLayoutOwnProps = {
28
29
  * follows the surrounding data-theme like any other component.
29
30
  */
30
31
  theme?: 'dark' | 'inherit';
32
+ /**
33
+ * The component its links render with, such as Next.js's `Link`, so they
34
+ * navigate through the app's router; passed on to the AppSidebar rail. Wins over a
35
+ * LinkProvider's; a plain `<a>` when neither is set.
36
+ */
37
+ linkComponent?: LinkComponent;
31
38
  /** Additional CSS classes on outer wrapper */
32
39
  className?: string;
33
40
  };
@@ -16,6 +16,7 @@ const AppLayout = React.forwardRef(
16
16
  logo,
17
17
  children,
18
18
  theme = "dark",
19
+ linkComponent,
19
20
  className = "",
20
21
  ...rest
21
22
  }, ref) => {
@@ -49,7 +50,8 @@ const AppLayout = React.forwardRef(
49
50
  expanded,
50
51
  onExpandedChange: handleExpandedChange,
51
52
  logoText,
52
- logo
53
+ logo,
54
+ linkComponent
53
55
  }
54
56
  ),
55
57
  /* @__PURE__ */ jsx("main", { className: `${baseClass}__main`, children: /* @__PURE__ */ jsx("div", { className: `${baseClass}__content`, children }) })
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
2
3
  export interface AppSidebarSubItem {
3
4
  /** Unique key for this sub-item */
4
5
  key: string;
@@ -108,6 +109,12 @@ type AppSidebarOwnProps = {
108
109
  footerSlot?: React.ReactNode;
109
110
  /** Additional CSS classes */
110
111
  className?: string;
112
+ /**
113
+ * The component its links render with, such as Next.js's `Link`, so they
114
+ * navigate through the app's router. Wins over a LinkProvider's; a plain
115
+ * `<a>` when neither is set.
116
+ */
117
+ linkComponent?: LinkComponent;
111
118
  /** Logo element — defaults to the built-in brand mark */
112
119
  logo?: React.ReactNode;
113
120
  /** Text shown next to logo when expanded */
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsxs, Fragment, jsx } from "react/jsx-runtime";
3
3
  import React, { useState, useRef, useEffect, useCallback, useId } from "react";
4
+ import { useLinkComponent } from "../../behaviors/LinkProvider.js";
4
5
  import { useLayer } from "../../behaviors/useLayer.js";
5
6
  import { useFocusScope } from "../../behaviors/useFocusScope.js";
6
7
  import { useScrollLock } from "../../behaviors/useScrollLock.js";
@@ -56,9 +57,11 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
56
57
  collapseLabel = "Collapse sidebar",
57
58
  profileMoreLabel = "Profile options",
58
59
  menuLabel = "Menu",
60
+ linkComponent,
59
61
  ...rest
60
62
  }, ref) {
61
63
  const baseClass = "ds-app-sidebar";
64
+ const Link = useLinkComponent(linkComponent);
62
65
  const avatarSeed = (item) => typeof item.pixelAvatar === "string" ? item.pixelAvatar : item.label;
63
66
  const avatarItems = sections.flatMap(
64
67
  (section) => section.items.filter((item) => item.pixelAvatar)
@@ -279,6 +282,7 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
279
282
  mobileOpen && drilled && panelSource ? /* @__PURE__ */ jsx(
280
283
  SidebarPanel,
281
284
  {
285
+ linkComponent: Link,
282
286
  className: `${baseClass}__drill`,
283
287
  items: panelSource.items,
284
288
  title: panelSource.label,
@@ -327,8 +331,8 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
327
331
  )
328
332
  ] });
329
333
  return /* @__PURE__ */ jsxs(React.Fragment, { children: [
330
- isLink ? /* @__PURE__ */ jsx(
331
- "a",
334
+ isLink && item.href ? /* @__PURE__ */ jsx(
335
+ Link,
332
336
  {
333
337
  className: itemClasses,
334
338
  href: item.href,
@@ -408,7 +412,7 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
408
412
  children: subLabel
409
413
  }
410
414
  ) : sub.href ? /* @__PURE__ */ jsx(
411
- "a",
415
+ Link,
412
416
  {
413
417
  className: subClasses,
414
418
  href: sub.href,
@@ -467,6 +471,7 @@ const AppSidebar = React.forwardRef(function AppSidebar2({
467
471
  panelSource && !mobileOpen && /* @__PURE__ */ jsx(
468
472
  SidebarPanel,
469
473
  {
474
+ linkComponent: Link,
470
475
  id: panelId,
471
476
  className: [
472
477
  `${baseClass}__panel`,
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
2
3
  export interface BottomNavItem {
3
4
  /** Unique key for this destination */
4
5
  key: string;
@@ -67,6 +68,12 @@ type BottomNavOwnProps = {
67
68
  badgeLabel?: (badge: number | string | true) => string;
68
69
  /** Additional CSS classes */
69
70
  className?: string;
71
+ /**
72
+ * The component its links render with, such as Next.js's `Link`, so they
73
+ * navigate through the app's router. Wins over a LinkProvider's; a plain
74
+ * `<a>` when neither is set.
75
+ */
76
+ linkComponent?: LinkComponent;
70
77
  };
71
78
  export interface BottomNavProps extends BottomNavOwnProps, Omit<React.ComponentPropsWithoutRef<'nav'>, keyof BottomNavOwnProps> {
72
79
  }
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
3
3
  import React from "react";
4
+ import { useLinkComponent } from "../../behaviors/LinkProvider.js";
4
5
  import "./BottomNav.css";
5
6
  import "../../fonts/material-symbols.css";
6
7
  const defaultBadgeLabel = (badge) => badge === true ? ", new" : `, ${badge} new`;
@@ -17,9 +18,11 @@ const BottomNav = React.forwardRef(
17
18
  badgeLabel = defaultBadgeLabel,
18
19
  className = "",
19
20
  style,
21
+ linkComponent,
20
22
  ...rest
21
23
  }, ref) => {
22
24
  const baseClass = "ds-bottom-nav";
25
+ const Link = useLinkComponent(linkComponent);
23
26
  const isIos = platform === "ios";
24
27
  const destinations = search && !isIos ? [...items, { key: "__search", label: search.label, icon: search.icon ?? "search", href: search.href, onClick: search.onClick }] : items;
25
28
  const activeIndex = destinations.findIndex((item) => item.key === activeKey);
@@ -69,7 +72,7 @@ const BottomNav = React.forwardRef(
69
72
  className: `${baseClass}__slot`,
70
73
  inert: isMinimized && !isActive,
71
74
  children: item.href ? /* @__PURE__ */ jsx(
72
- "a",
75
+ Link,
73
76
  {
74
77
  className: itemClasses,
75
78
  href: item.href,
@@ -92,7 +95,7 @@ const BottomNav = React.forwardRef(
92
95
  );
93
96
  }) })
94
97
  ] }),
95
- isIos && search && (search.href ? /* @__PURE__ */ jsx("a", { className: `${baseClass}__search`, href: search.href, onClick: search.onClick, "aria-label": search.label, children: renderIcon(search.icon ?? "search") }) : /* @__PURE__ */ jsx("button", { type: "button", className: `${baseClass}__search`, onClick: search.onClick, "aria-label": search.label, children: renderIcon(search.icon ?? "search") }))
98
+ isIos && search && (search.href ? /* @__PURE__ */ jsx(Link, { className: `${baseClass}__search`, href: search.href, onClick: search.onClick, "aria-label": search.label, children: renderIcon(search.icon ?? "search") }) : /* @__PURE__ */ jsx("button", { type: "button", className: `${baseClass}__search`, onClick: search.onClick, "aria-label": search.label, children: renderIcon(search.icon ?? "search") }))
96
99
  ] });
97
100
  }
98
101
  );
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
2
3
  export interface BreadcrumbItem {
3
4
  /** Display label */
4
5
  label: string;
@@ -19,6 +20,13 @@ type BreadcrumbOwnProps = {
19
20
  * @deprecated Pass the native `aria-label` attribute instead.
20
21
  */
21
22
  ariaLabel?: string;
23
+ /**
24
+ * The component its links render with, such as Next.js's `Link`, so they
25
+ * navigate through the app's router; a plain `<a>` when unset. This
26
+ * component can render on the server, so it never reads a LinkProvider:
27
+ * pass the prop even under one.
28
+ */
29
+ linkComponent?: LinkComponent;
22
30
  };
23
31
  export interface BreadcrumbProps extends BreadcrumbOwnProps, Omit<React.ComponentPropsWithoutRef<'nav'>, keyof BreadcrumbOwnProps | 'children'> {
24
32
  }
@@ -3,8 +3,9 @@ import React from "react";
3
3
  import "./Breadcrumb.css";
4
4
  import "../../fonts/material-symbols.css";
5
5
  const Breadcrumb = React.forwardRef(
6
- ({ items, maxItems, className = "", ariaLabel, ...rest }, ref) => {
6
+ ({ items, maxItems, className = "", ariaLabel, linkComponent, ...rest }, ref) => {
7
7
  const baseClass = "ds-breadcrumb";
8
+ const Link = linkComponent ?? "a";
8
9
  const classes = [baseClass, className].filter(Boolean).join(" ");
9
10
  const shouldCollapse = maxItems !== void 0 && maxItems >= 2 && items.length > maxItems;
10
11
  let visibleItems;
@@ -28,7 +29,7 @@ const Breadcrumb = React.forwardRef(
28
29
  }
29
30
  return /* @__PURE__ */ jsxs("li", { className: `${baseClass}__item`, children: [
30
31
  index > 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__separator material-symbols-rounded`, "aria-hidden": "true", children: "chevron_right" }),
31
- isLast || !item.href ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__current`, "aria-current": "page", children: item.label }) : /* @__PURE__ */ jsx("a", { href: item.href, className: `${baseClass}__link`, children: item.label })
32
+ isLast || !item.href ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__current`, "aria-current": "page", children: item.label }) : /* @__PURE__ */ jsx(Link, { href: item.href, className: `${baseClass}__link`, children: item.label })
32
33
  ] }, index);
33
34
  }) }) });
34
35
  }
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
2
3
  /** Props owned by Button itself — everything else falls through to the DOM node. */
3
4
  type ButtonOwnProps = {
4
5
  /** Button text content */
@@ -39,6 +40,12 @@ type ButtonOwnProps = {
39
40
  target?: string;
40
41
  /** Optional rel attribute for links */
41
42
  rel?: string;
43
+ /**
44
+ * The component the link renders with, such as Next.js's `Link`, so it
45
+ * navigates through the app's router. Wins over a LinkProvider's; a plain
46
+ * `<a>` when neither is set.
47
+ */
48
+ linkComponent?: LinkComponent;
42
49
  /** Marks this link as the current page (adds aria-current="page") */
43
50
  ariaCurrent?: boolean;
44
51
  /** Additional CSS classes */
@@ -2,6 +2,7 @@
2
2
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
3
  import React from "react";
4
4
  import { Spinner } from "../Spinner/Spinner.js";
5
+ import { useLinkComponent } from "../../behaviors/LinkProvider.js";
5
6
  import "./Button.css";
6
7
  import "../../fonts/material-symbols.css";
7
8
  const LEGACY_VARIANTS = {
@@ -33,6 +34,7 @@ const Button = React.forwardRef(
33
34
  href,
34
35
  target,
35
36
  rel,
37
+ linkComponent,
36
38
  ariaCurrent,
37
39
  className = "",
38
40
  priority,
@@ -42,6 +44,7 @@ const Button = React.forwardRef(
42
44
  ...rest
43
45
  }, ref) => {
44
46
  const baseClass = "ds-button";
47
+ const Link = useLinkComponent(linkComponent);
45
48
  const resolvedVariant = resolveButtonCell(variant ?? priority ?? "primary", tone);
46
49
  const isDisabled = disabled ?? state === "disabled";
47
50
  const resolvedState = state ?? (isDisabled ? "disabled" : "default");
@@ -73,7 +76,7 @@ const Button = React.forwardRef(
73
76
  ] });
74
77
  if (href && !isDisabled && !isLoading) {
75
78
  return /* @__PURE__ */ jsx(
76
- "a",
79
+ Link,
77
80
  {
78
81
  ...rest,
79
82
  ref,
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
2
3
  /** Props owned by Card itself — everything else falls through to the DOM node. */
3
4
  type CardOwnProps = {
4
5
  /** Card variant */
@@ -34,6 +35,12 @@ type CardOwnProps = {
34
35
  dek?: string;
35
36
  /** Render as a disabled placeholder (no href, dimmed, not interactive) */
36
37
  placeholder?: boolean;
38
+ /**
39
+ * The component the link renders with, such as Next.js's `Link`, so it
40
+ * navigates through the app's router. Wins over a LinkProvider's; a plain
41
+ * `<a>` when neither is set.
42
+ */
43
+ linkComponent?: LinkComponent;
37
44
  };
38
45
  export interface CardProps extends CardOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof CardOwnProps> {
39
46
  }
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
3
  import React from "react";
4
+ import { useLinkComponent } from "../../behaviors/LinkProvider.js";
4
5
  import "./Card.css";
5
6
  import "../../fonts/material-symbols.css";
6
7
  const Card = React.forwardRef(
@@ -20,8 +21,10 @@ const Card = React.forwardRef(
20
21
  companyName,
21
22
  dek,
22
23
  placeholder = false,
24
+ linkComponent,
23
25
  ...rest
24
26
  }, ref) => {
27
+ const Link = useLinkComponent(linkComponent);
25
28
  if (variant === "case-study") {
26
29
  const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
27
30
  /* @__PURE__ */ jsx("div", { className: "ds-card--case-study__cover-wrap", children: cover ? /* @__PURE__ */ jsx("div", { className: "ds-card--case-study__cover-slot", children: cover }) : coverSrc ? /* @__PURE__ */ jsx(
@@ -67,7 +70,7 @@ const Card = React.forwardRef(
67
70
  );
68
71
  }
69
72
  return /* @__PURE__ */ jsx(
70
- "a",
73
+ Link,
71
74
  {
72
75
  ...rest,
73
76
  ref,
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
2
3
  /** Props owned by CircularButton itself — everything else falls through to the DOM node. */
3
4
  type CircularButtonOwnProps = {
4
5
  /** The icon — Material Symbol name (string) or custom element (ReactNode) */
@@ -65,6 +66,13 @@ type CircularButtonOwnProps = {
65
66
  rel?: string;
66
67
  /** Additional CSS classes */
67
68
  className?: string;
69
+ /**
70
+ * The component the link renders with, such as Next.js's `Link`, so it
71
+ * navigates through the app's router; a plain `<a>` when unset. This
72
+ * component can render on the server, so it never reads a LinkProvider:
73
+ * pass the prop even under one.
74
+ */
75
+ linkComponent?: LinkComponent;
68
76
  };
69
77
  export interface CircularButtonProps extends CircularButtonOwnProps, Omit<React.ComponentPropsWithoutRef<'button'>, keyof CircularButtonOwnProps | 'type'> {
70
78
  }
@@ -32,9 +32,11 @@ const CircularButton = React.forwardRef(
32
32
  target,
33
33
  rel,
34
34
  className = "",
35
+ linkComponent,
35
36
  ...rest
36
37
  }, ref) => {
37
38
  const baseClass = "ds-circular-button";
39
+ const Link = linkComponent ?? "a";
38
40
  const resolvedVariant = resolveCircularButtonCell(variant ?? priority ?? "primary", tone);
39
41
  const isDisabled = disabled ?? state === "disabled";
40
42
  const resolvedState = state ?? (isDisabled ? "disabled" : "default");
@@ -49,7 +51,7 @@ const CircularButton = React.forwardRef(
49
51
  ].filter(Boolean).join(" ");
50
52
  const children = isLoading ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon`, "aria-hidden": "true", children: /* @__PURE__ */ jsx(Spinner, { size: size === "compact" ? "sm" : "md", tone: "inherit" }) }) : /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon${typeof icon === "string" ? " material-symbols-rounded" : ""}`, "aria-hidden": "true", children: icon });
51
53
  const control = href && !isDisabled && !isLoading ? /* @__PURE__ */ jsx(
52
- "a",
54
+ Link,
53
55
  {
54
56
  ...rest,
55
57
  ref,
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
2
3
  /** Props owned by ContactCard itself — everything else falls through to the root node. */
3
4
  type ContactCardOwnProps = {
4
5
  /** Primary label — e.g. "Email" */
@@ -40,6 +41,12 @@ type ContactCardOwnProps = {
40
41
  copyLabel?: string;
41
42
  /** Additional CSS classes */
42
43
  className?: string;
44
+ /**
45
+ * The component its links render with, such as Next.js's `Link`, so they
46
+ * navigate through the app's router. Wins over a LinkProvider's; a plain
47
+ * `<a>` when neither is set.
48
+ */
49
+ linkComponent?: LinkComponent;
43
50
  };
44
51
  export interface ContactCardProps extends ContactCardOwnProps, Omit<React.ComponentPropsWithoutRef<'a'>, keyof ContactCardOwnProps | 'children'> {
45
52
  }
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
3
  import React from "react";
4
+ import { useLinkComponent } from "../../behaviors/LinkProvider.js";
4
5
  import "./ContactCard.css";
5
6
  import "../../fonts/material-symbols.css";
6
7
  const ContactCard = React.forwardRef(
@@ -16,9 +17,11 @@ const ContactCard = React.forwardRef(
16
17
  copyOnClick = false,
17
18
  copyLabel,
18
19
  className = "",
20
+ linkComponent,
19
21
  ...rest
20
22
  }, ref) => {
21
23
  const classes = ["ds-contact-card", className].filter(Boolean).join(" ");
24
+ const Link = useLinkComponent(linkComponent);
22
25
  const isProtocolLink = /^(mailto:|tel:)/i.test(href);
23
26
  const openInNewTab = external && !isProtocolLink;
24
27
  const trailingIcon = copyOnClick ? "content_copy" : external ? "open_in_new" : "arrow_forward";
@@ -74,7 +77,7 @@ const ContactCard = React.forwardRef(
74
77
  );
75
78
  }
76
79
  return /* @__PURE__ */ jsx(
77
- "a",
80
+ Link,
78
81
  {
79
82
  ...rest,
80
83
  ref,
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
2
3
  export interface LinkListItem {
3
4
  /** Link destination */
4
5
  href: string;
@@ -21,6 +22,13 @@ type LinkListOwnProps = {
21
22
  items: LinkListItem[];
22
23
  /** Additional CSS classes */
23
24
  className?: string;
25
+ /**
26
+ * The component its links render with, such as Next.js's `Link`, so they
27
+ * navigate through the app's router; a plain `<a>` when unset. This
28
+ * component can render on the server, so it never reads a LinkProvider:
29
+ * pass the prop even under one.
30
+ */
31
+ linkComponent?: LinkComponent;
24
32
  };
25
33
  export interface LinkListProps extends LinkListOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof LinkListOwnProps | 'children'> {
26
34
  }
@@ -3,13 +3,14 @@ import React from "react";
3
3
  import "./LinkList.css";
4
4
  import "../../fonts/material-symbols.css";
5
5
  const LinkList = React.forwardRef(
6
- ({ items, className = "", ...rest }, ref) => {
6
+ ({ items, className = "", linkComponent, ...rest }, ref) => {
7
7
  const classes = ["ds-link-list", className].filter(Boolean).join(" ");
8
+ const Link = linkComponent ?? "a";
8
9
  return /* @__PURE__ */ jsx("div", { ...rest, ref, className: classes, children: items.map((item) => {
9
10
  const subs = item.sub ? Array.isArray(item.sub) ? item.sub : [item.sub] : [];
10
11
  const newTab = item.newTab ?? true;
11
12
  return /* @__PURE__ */ jsxs(
12
- "a",
13
+ Link,
13
14
  {
14
15
  href: item.href,
15
16
  target: newTab ? "_blank" : void 0,
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
2
3
  export interface NavListItem {
3
4
  /** Row text. */
4
5
  label: string;
@@ -40,6 +41,12 @@ type NavListOwnProps = {
40
41
  expandLabel?: (label: string) => string;
41
42
  /** Accessible name of an expanded section's chevron toggle, given the row's label. Defaults to "Collapse <label>". */
42
43
  collapseLabel?: (label: string) => string;
44
+ /**
45
+ * The component its links render with, such as Next.js's `Link`, so they
46
+ * navigate through the app's router; passed on to each row's Button. Wins over a
47
+ * LinkProvider's; a plain `<a>` when neither is set.
48
+ */
49
+ linkComponent?: LinkComponent;
43
50
  /** Additional CSS classes */
44
51
  className?: string;
45
52
  };
@@ -16,6 +16,7 @@ const NavList = React.forwardRef(
16
16
  onNavigate,
17
17
  expandLabel = (label) => `Expand ${label}`,
18
18
  collapseLabel = (label) => `Collapse ${label}`,
19
+ linkComponent,
19
20
  className = "",
20
21
  ...rest
21
22
  }, ref) => {
@@ -45,6 +46,7 @@ const NavList = React.forwardRef(
45
46
  size: level === 1 ? "default" : "compact",
46
47
  label: item.label,
47
48
  href: item.href,
49
+ linkComponent,
48
50
  ariaCurrent: isCurrent,
49
51
  className: isCurrent ? "ds-button--active" : void 0,
50
52
  onClick: (event) => onNavigate?.(item, event)
@@ -1,4 +1,5 @@
1
1
  import { default as React } from 'react';
2
+ import { LinkComponent } from '../../behaviors/LinkProvider';
2
3
  export interface SidebarPanelItem {
3
4
  /** Unique key for this item */
4
5
  key: string;
@@ -37,6 +38,12 @@ type SidebarPanelOwnProps = {
37
38
  backLabel?: string;
38
39
  /** Additional CSS classes */
39
40
  className?: string;
41
+ /**
42
+ * The component its links render with, such as Next.js's `Link`, so they
43
+ * navigate through the app's router. Wins over a LinkProvider's; a plain
44
+ * `<a>` when neither is set.
45
+ */
46
+ linkComponent?: LinkComponent;
40
47
  };
41
48
  export interface SidebarPanelProps extends SidebarPanelOwnProps, Omit<React.ComponentPropsWithoutRef<'nav'>, keyof SidebarPanelOwnProps> {
42
49
  }