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.
- package/behaviors/LinkProvider.d.ts +36 -0
- package/behaviors/LinkProvider.js +15 -0
- package/chat/ChatProvider.d.ts +5 -1
- package/chat/ChatProvider.js +11 -1
- package/components/AiButton/AiButton.d.ts +7 -0
- package/components/AiButton/AiButton.js +4 -1
- package/components/AppLayout/AppLayout.d.ts +7 -0
- package/components/AppLayout/AppLayout.js +3 -1
- package/components/AppSidebar/AppSidebar.d.ts +7 -0
- package/components/AppSidebar/AppSidebar.js +8 -3
- package/components/BottomNav/BottomNav.d.ts +7 -0
- package/components/BottomNav/BottomNav.js +5 -2
- package/components/Breadcrumb/Breadcrumb.d.ts +8 -0
- package/components/Breadcrumb/Breadcrumb.js +3 -2
- package/components/Button/Button.d.ts +7 -0
- package/components/Button/Button.js +4 -1
- package/components/Card/Card.d.ts +7 -0
- package/components/Card/Card.js +4 -1
- package/components/CircularButton/CircularButton.d.ts +8 -0
- package/components/CircularButton/CircularButton.js +3 -1
- package/components/ContactCard/ContactCard.d.ts +7 -0
- package/components/ContactCard/ContactCard.js +4 -1
- package/components/LinkList/LinkList.d.ts +8 -0
- package/components/LinkList/LinkList.js +3 -2
- package/components/NavList/NavList.d.ts +7 -0
- package/components/NavList/NavList.js +2 -0
- package/components/SidebarPanel/SidebarPanel.d.ts +7 -0
- package/components/SidebarPanel/SidebarPanel.js +4 -1
- package/components/SourceChip/SourceChip.d.ts +7 -0
- package/components/SourceChip/SourceChip.js +5 -3
- package/components/SourceTrail/SourceTrail.d.ts +7 -0
- package/components/SourceTrail/SourceTrail.js +2 -0
- package/components/ThreadPanel/ThreadPanel.d.ts +8 -0
- package/components/ThreadPanel/ThreadPanel.js +9 -2
- package/components/TopAppBar/TopAppBar.d.ts +7 -0
- package/components/TopAppBar/TopAppBar.js +4 -1
- package/components/Widget/Widget.d.ts +8 -0
- package/components/Widget/Widget.js +3 -2
- package/package.json +5 -1
- 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
|
+
};
|
package/chat/ChatProvider.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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;
|
package/chat/ChatProvider.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
}
|
package/components/Card/Card.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|