rift-ds 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/components/AppSidebar/AppSidebar.css +6 -0
- package/components/AppSidebar/AppSidebar.d.ts +12 -2
- package/components/AppSidebar/AppSidebar.js +11 -1
- package/components/ContextMenu/ContextMenu.css +25 -0
- package/components/ContextMenu/ContextMenu.js +14 -6
- package/components/DropdownMenu/DropdownMenu.css +29 -0
- package/components/DropdownMenu/DropdownMenu.d.ts +2 -0
- package/components/DropdownMenu/DropdownMenu.js +15 -7
- package/components/PixelAvatar/PixelAvatar.css +79 -0
- package/components/PixelAvatar/PixelAvatar.d.ts +47 -0
- package/components/PixelAvatar/PixelAvatar.js +90 -0
- package/components/PixelAvatar/pixelInks.d.ts +27 -0
- package/components/PixelAvatar/pixelInks.js +45 -0
- package/components/PromptSuggestions/PromptSuggestions.css +218 -0
- package/components/PromptSuggestions/PromptSuggestions.d.ts +10 -3
- package/components/PromptSuggestions/PromptSuggestions.js +26 -3
- package/components/Quote/Quote.css +2 -2
- package/components/ShaderField/useShaderField.d.ts +4 -4
- package/components/SourceChip/SourceChip.css +211 -11
- package/components/SourceChip/SourceChip.d.ts +33 -10
- package/components/SourceChip/SourceChip.js +174 -22
- package/components/Stat/Stat.css +2 -2
- package/components/ThreadPanel/ThreadPanel.css +6 -0
- package/components/ThreadPanel/ThreadPanel.d.ts +3 -1
- package/components/ThreadPanel/ThreadPanel.js +22 -4
- package/components/Widget/Widget.css +207 -0
- package/components/Widget/Widget.d.ts +83 -0
- package/components/Widget/Widget.js +112 -0
- package/components/registry.json +17 -1
- package/components/registry.json.d.ts +17 -1
- package/components/registry.json.js +1 -1
- package/fonts/presets/fredoka-300-700-latin-ext.woff2 +0 -0
- package/fonts/presets/fredoka-300-700-latin.woff2 +0 -0
- package/fonts/presets/manrope-300-800-latin-ext.woff2 +0 -0
- package/fonts/presets/manrope-300-800-latin.woff2 +0 -0
- package/fonts/presets/nunito-300-800-latin-ext.woff2 +0 -0
- package/fonts/presets/nunito-300-800-latin.woff2 +0 -0
- package/fonts/presets/playfair-display-400-800-latin-ext.woff2 +0 -0
- package/fonts/presets/playfair-display-400-800-latin.woff2 +0 -0
- package/fonts/presets/plus-jakarta-sans-300-800-latin-ext.woff2 +0 -0
- package/fonts/presets/plus-jakarta-sans-300-800-latin.woff2 +0 -0
- package/fonts/presets/source-sans-3-300-700-latin.woff2 +0 -0
- package/index.d.ts +2 -0
- package/index.js +10 -0
- package/package.json +1 -1
- package/tokens/presets/contrast.css +9 -9
- package/tokens/presets/coral.css +6 -6
- package/tokens/presets/forest.css +10 -10
- package/tokens/presets/gold.css +119 -11
- package/tokens/presets/mono.css +2 -2
- package/tokens/presets/pink.css +104 -24
- package/tokens/presets/terminal.css +13 -13
- package/tokens/presets/violet.css +114 -26
- package/tokens/presets/warm.css +10 -10
- package/tokens/presets/zest.css +10 -10
- package/tokens/registry.json +6 -0
- package/tokens/registry.json.d.ts +6 -0
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +9 -0
- package/tokens/tokens-light.css +14 -0
- package/tokens/tokens-typography.css +1 -1
- package/fonts/presets/fraunces-300-700-latin-ext.woff2 +0 -0
- package/fonts/presets/fraunces-300-700-latin.woff2 +0 -0
- package/fonts/presets/work-sans-300-700-latin-ext.woff2 +0 -0
- package/fonts/presets/work-sans-300-700-latin.woff2 +0 -0
|
@@ -3,16 +3,34 @@ import { default as React } from 'react';
|
|
|
3
3
|
type SourceChipOwnProps = {
|
|
4
4
|
/** The source name, e.g. "Design tokens quarterly". Truncates with an ellipsis when long. */
|
|
5
5
|
title: string;
|
|
6
|
-
/** Citation number, rendered as a leading numeral in its own small badge circle. */
|
|
6
|
+
/** Citation number, rendered as a leading numeral in its own small badge circle. Wins the leading slot over `logo` and `icon`. */
|
|
7
7
|
index?: number;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* The glyph for a source with neither a number nor a logo — a Material Symbol
|
|
10
|
+
* name (string) or custom element (ReactNode). Defaults to a globe, so the
|
|
11
|
+
* leading slot is never empty.
|
|
12
12
|
*/
|
|
13
13
|
icon?: string | React.ReactNode;
|
|
14
14
|
/** Optional href — renders as an `<a>` instead of a `<span>`. */
|
|
15
15
|
href?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The passage the answer drew on. Setting it gives the chip a preview panel
|
|
18
|
+
* that opens on hover, focus or press: the full title over this excerpt.
|
|
19
|
+
* Without an `href` the chip then renders as a `<button>`, so the preview
|
|
20
|
+
* is reachable from the keyboard.
|
|
21
|
+
*/
|
|
22
|
+
excerpt?: string;
|
|
23
|
+
/** Where the source lives, shown as the preview's top line, e.g. "docs.acme.com". Only rendered with `excerpt`. */
|
|
24
|
+
source?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The source's logo or favicon — an image URL (string) or a custom element
|
|
27
|
+
* (ReactNode), cropped to a circle. It fills the chip's leading slot when
|
|
28
|
+
* there is no `index`, and always leads the preview's top line. Decorative:
|
|
29
|
+
* the title and `source` text carry the name.
|
|
30
|
+
*/
|
|
31
|
+
logo?: string | React.ReactNode;
|
|
32
|
+
/** A short trailing fact for the preview's top line, e.g. "Updated 3 days ago". Only rendered with `excerpt`. */
|
|
33
|
+
meta?: string;
|
|
16
34
|
/** Additional CSS classes */
|
|
17
35
|
className?: string;
|
|
18
36
|
};
|
|
@@ -21,12 +39,17 @@ export interface SourceChipProps extends SourceChipOwnProps, Omit<React.Componen
|
|
|
21
39
|
/**
|
|
22
40
|
* SourceChip is the numbered citation pill linking a claim to its source.
|
|
23
41
|
* It renders inline after a sentence or in a sources row under an assistant
|
|
24
|
-
* chat message: a leading citation number
|
|
25
|
-
* source title.
|
|
42
|
+
* chat message: a leading slot — the citation number, the source's logo in
|
|
43
|
+
* a circle, or a globe — beside a truncating source title.
|
|
44
|
+
*
|
|
45
|
+
* Renders an `<a>` when `href` is supplied, a `<button>` when it has a preview
|
|
46
|
+
* but nowhere to link, and a plain `<span>` otherwise. Forwards a ref to
|
|
47
|
+
* whichever element it renders, and spreads unrecognised props onto it —
|
|
48
|
+
* `target` and `rel` pass through as native anchor attributes.
|
|
26
49
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
50
|
+
* With an `excerpt` the chip previews its source: a floating panel that opens
|
|
51
|
+
* above the chip on hover or focus, flips below when there is no room, and
|
|
52
|
+
* slides sideways to stay inside whatever clips it. Escape dismisses it.
|
|
30
53
|
*/
|
|
31
|
-
export declare const SourceChip: React.ForwardRefExoticComponent<SourceChipProps & React.RefAttributes<HTMLSpanElement | HTMLAnchorElement>>;
|
|
54
|
+
export declare const SourceChip: React.ForwardRefExoticComponent<SourceChipProps & React.RefAttributes<HTMLButtonElement | HTMLSpanElement | HTMLAnchorElement>>;
|
|
32
55
|
export {};
|
|
@@ -1,30 +1,182 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React from "react";
|
|
3
|
+
import React, { useState, useRef, useId, useEffect, useLayoutEffect } from "react";
|
|
3
4
|
import "./SourceChip.css";
|
|
4
5
|
import "../../fonts/material-symbols.css";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
import { MOTION_HOVER_SHOW_DELAY_MS, MOTION_HOVER_HIDE_DELAY_MS } from "../../tokens/motion.js";
|
|
7
|
+
const PREVIEW_EDGE_MARGIN = 8;
|
|
8
|
+
let closeOpenPreview = null;
|
|
9
|
+
function clipBounds(el) {
|
|
10
|
+
const bounds = { top: 0, left: 0, right: window.innerWidth, bottom: window.innerHeight };
|
|
11
|
+
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
12
|
+
const { overflowX, overflowY } = getComputedStyle(node);
|
|
13
|
+
const rect = node.getBoundingClientRect();
|
|
14
|
+
if (overflowX !== "visible") {
|
|
15
|
+
bounds.left = Math.max(bounds.left, rect.left);
|
|
16
|
+
bounds.right = Math.min(bounds.right, rect.right);
|
|
17
|
+
}
|
|
18
|
+
if (overflowY !== "visible") {
|
|
19
|
+
bounds.top = Math.max(bounds.top, rect.top);
|
|
20
|
+
bounds.bottom = Math.min(bounds.bottom, rect.bottom);
|
|
16
21
|
}
|
|
17
|
-
return /* @__PURE__ */ jsx(
|
|
18
|
-
"span",
|
|
19
|
-
{
|
|
20
|
-
...rest,
|
|
21
|
-
ref,
|
|
22
|
-
className: classes,
|
|
23
|
-
children: content
|
|
24
|
-
}
|
|
25
|
-
);
|
|
26
22
|
}
|
|
27
|
-
|
|
23
|
+
return bounds;
|
|
24
|
+
}
|
|
25
|
+
const SourceChip = React.forwardRef(({ title, index, icon = "language", href, excerpt, source, logo, meta, className = "", ...rest }, ref) => {
|
|
26
|
+
const baseClass = "ds-source-chip";
|
|
27
|
+
const hasPreview = Boolean(excerpt);
|
|
28
|
+
const [visible, setVisible] = useState(false);
|
|
29
|
+
const showTimeoutRef = useRef(void 0);
|
|
30
|
+
const hideTimeoutRef = useRef(void 0);
|
|
31
|
+
const hostRef = useRef(null);
|
|
32
|
+
const panelRef = useRef(null);
|
|
33
|
+
const panelId = useId();
|
|
34
|
+
const clearTimers = () => {
|
|
35
|
+
clearTimeout(showTimeoutRef.current);
|
|
36
|
+
clearTimeout(hideTimeoutRef.current);
|
|
37
|
+
};
|
|
38
|
+
const show = () => {
|
|
39
|
+
clearTimeout(hideTimeoutRef.current);
|
|
40
|
+
showTimeoutRef.current = setTimeout(() => setVisible(true), MOTION_HOVER_SHOW_DELAY_MS);
|
|
41
|
+
};
|
|
42
|
+
const hide = () => {
|
|
43
|
+
clearTimeout(showTimeoutRef.current);
|
|
44
|
+
hideTimeoutRef.current = setTimeout(() => setVisible(false), MOTION_HOVER_HIDE_DELAY_MS);
|
|
45
|
+
};
|
|
46
|
+
const showNow = () => {
|
|
47
|
+
clearTimers();
|
|
48
|
+
setVisible(true);
|
|
49
|
+
};
|
|
50
|
+
useEffect(() => clearTimers, []);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!visible) return;
|
|
53
|
+
const close = () => {
|
|
54
|
+
clearTimers();
|
|
55
|
+
setVisible(false);
|
|
56
|
+
};
|
|
57
|
+
closeOpenPreview?.();
|
|
58
|
+
closeOpenPreview = close;
|
|
59
|
+
const onKeyDown = (e) => {
|
|
60
|
+
if (e.key === "Escape") close();
|
|
61
|
+
};
|
|
62
|
+
const onPointerDown = (e) => {
|
|
63
|
+
if (!hostRef.current?.contains(e.target)) close();
|
|
64
|
+
};
|
|
65
|
+
document.addEventListener("keydown", onKeyDown);
|
|
66
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
67
|
+
return () => {
|
|
68
|
+
if (closeOpenPreview === close) closeOpenPreview = null;
|
|
69
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
70
|
+
document.removeEventListener("pointerdown", onPointerDown);
|
|
71
|
+
};
|
|
72
|
+
}, [visible]);
|
|
73
|
+
useLayoutEffect(() => {
|
|
74
|
+
const host = hostRef.current;
|
|
75
|
+
const panel = panelRef.current;
|
|
76
|
+
if (!visible || !host || !panel) return;
|
|
77
|
+
const bounds = clipBounds(host);
|
|
78
|
+
const anchor = host.getBoundingClientRect();
|
|
79
|
+
const width = panel.offsetWidth;
|
|
80
|
+
const height = panel.offsetHeight;
|
|
81
|
+
const roomAbove = anchor.top - bounds.top - PREVIEW_EDGE_MARGIN;
|
|
82
|
+
const roomBelow = bounds.bottom - anchor.bottom - PREVIEW_EDGE_MARGIN;
|
|
83
|
+
const side = roomAbove >= height || roomAbove >= roomBelow ? "top" : "bottom";
|
|
84
|
+
const minLeft = bounds.left + PREVIEW_EDGE_MARGIN;
|
|
85
|
+
const maxLeft = bounds.right - PREVIEW_EDGE_MARGIN - width;
|
|
86
|
+
const left = Math.max(minLeft, Math.min(anchor.left, maxLeft));
|
|
87
|
+
panel.dataset.side = side;
|
|
88
|
+
panel.style.setProperty("--ds-source-chip-shift", `${Math.round(left - anchor.left)}px`);
|
|
89
|
+
}, [visible]);
|
|
90
|
+
const element = href ? "a" : hasPreview ? "button" : "span";
|
|
91
|
+
const classes = [
|
|
92
|
+
baseClass,
|
|
93
|
+
href ? `${baseClass}--link` : "",
|
|
94
|
+
element !== "span" ? `${baseClass}--interactive` : "",
|
|
95
|
+
visible ? `${baseClass}--open` : "",
|
|
96
|
+
className
|
|
97
|
+
].filter(Boolean).join(" ");
|
|
98
|
+
const logoMark = logo ? typeof logo === "string" ? /* @__PURE__ */ jsx("img", { src: logo, alt: "" }) : logo : null;
|
|
99
|
+
const leading = index !== void 0 ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__index`, children: /* @__PURE__ */ jsx("span", { className: `${baseClass}__numeral`, children: index }) }) : logoMark ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__logo`, "aria-hidden": "true", children: logoMark }) : icon ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon`, "aria-hidden": "true", children: typeof icon === "string" ? /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", children: icon }) : icon }) : null;
|
|
100
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
101
|
+
leading,
|
|
102
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__title`, children: title })
|
|
103
|
+
] });
|
|
104
|
+
const describedBy = hasPreview ? panelId : void 0;
|
|
105
|
+
const chip = element === "a" ? /* @__PURE__ */ jsx(
|
|
106
|
+
"a",
|
|
107
|
+
{
|
|
108
|
+
"aria-describedby": describedBy,
|
|
109
|
+
...rest,
|
|
110
|
+
ref,
|
|
111
|
+
className: classes,
|
|
112
|
+
href,
|
|
113
|
+
children: content
|
|
114
|
+
}
|
|
115
|
+
) : element === "button" ? /* @__PURE__ */ jsx(
|
|
116
|
+
"button",
|
|
117
|
+
{
|
|
118
|
+
type: "button",
|
|
119
|
+
"aria-describedby": describedBy,
|
|
120
|
+
...rest,
|
|
121
|
+
ref,
|
|
122
|
+
className: classes,
|
|
123
|
+
children: content
|
|
124
|
+
}
|
|
125
|
+
) : /* @__PURE__ */ jsx(
|
|
126
|
+
"span",
|
|
127
|
+
{
|
|
128
|
+
...rest,
|
|
129
|
+
ref,
|
|
130
|
+
className: classes,
|
|
131
|
+
children: content
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
if (!hasPreview) return chip;
|
|
135
|
+
const handleBlur = (event) => {
|
|
136
|
+
if (!event.currentTarget.contains(event.relatedTarget)) hide();
|
|
137
|
+
};
|
|
138
|
+
return /* @__PURE__ */ jsxs(
|
|
139
|
+
"span",
|
|
140
|
+
{
|
|
141
|
+
ref: hostRef,
|
|
142
|
+
className: `${baseClass}-host`,
|
|
143
|
+
onMouseEnter: show,
|
|
144
|
+
onMouseLeave: hide,
|
|
145
|
+
onFocus: show,
|
|
146
|
+
onBlur: handleBlur,
|
|
147
|
+
onClick: showNow,
|
|
148
|
+
children: [
|
|
149
|
+
chip,
|
|
150
|
+
/* @__PURE__ */ jsxs(
|
|
151
|
+
"span",
|
|
152
|
+
{
|
|
153
|
+
ref: panelRef,
|
|
154
|
+
id: panelId,
|
|
155
|
+
role: "tooltip",
|
|
156
|
+
className: `${baseClass}__preview${visible ? ` ${baseClass}__preview--visible` : ""}`,
|
|
157
|
+
children: [
|
|
158
|
+
(logo || source || meta || href) && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__preview-top`, children: [
|
|
159
|
+
logoMark && /* @__PURE__ */ jsx("span", { className: `${baseClass}__logo`, "aria-hidden": "true", children: logoMark }),
|
|
160
|
+
source && /* @__PURE__ */ jsx("span", { className: `${baseClass}__preview-source`, children: source }),
|
|
161
|
+
meta && /* @__PURE__ */ jsx("span", { className: `${baseClass}__preview-meta`, children: meta }),
|
|
162
|
+
href && /* @__PURE__ */ jsx(
|
|
163
|
+
"span",
|
|
164
|
+
{
|
|
165
|
+
className: `${baseClass}__preview-open material-symbols-rounded`,
|
|
166
|
+
"aria-hidden": "true",
|
|
167
|
+
children: "arrow_outward"
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
] }),
|
|
171
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__preview-title`, children: title }),
|
|
172
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__preview-excerpt`, children: excerpt })
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
});
|
|
28
180
|
SourceChip.displayName = "SourceChip";
|
|
29
181
|
export {
|
|
30
182
|
SourceChip
|
package/components/Stat/Stat.css
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/* ============================================
|
|
16
|
-
VALUE — display weight (300
|
|
17
|
-
|
|
16
|
+
VALUE — the display tier's weight (300 in the
|
|
17
|
+
base theme), so numerals follow the theme
|
|
18
18
|
============================================ */
|
|
19
19
|
|
|
20
20
|
.ds-stat__value {
|
|
@@ -396,6 +396,12 @@
|
|
|
396
396
|
flex-shrink: 0;
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
/* A generated avatar in the icon seat: a block-level sprite, so the seat
|
|
400
|
+
lays it out as a box rather than on the text baseline. */
|
|
401
|
+
.ds-thread-panel__project-icon--avatar {
|
|
402
|
+
display: inline-flex;
|
|
403
|
+
}
|
|
404
|
+
|
|
399
405
|
.ds-thread-panel__project-label {
|
|
400
406
|
flex: 1;
|
|
401
407
|
min-width: 0;
|
|
@@ -52,8 +52,10 @@ export interface ThreadPanelProject {
|
|
|
52
52
|
id: string;
|
|
53
53
|
/** The project's name. */
|
|
54
54
|
label: string;
|
|
55
|
-
/** Leading icon on the row — Material Symbol name (string) or custom element (ReactNode).
|
|
55
|
+
/** Leading icon on the row — Material Symbol name (string) or custom element (ReactNode). Omit it and the row draws the project's generated PixelAvatar, which is the default mark. */
|
|
56
56
|
icon?: string | React.ReactNode;
|
|
57
|
+
/** Seed for the generated PixelAvatar a project wears when it has no `icon`. Defaults to `label`; pass a stable id so a renamed project keeps its character. The panel spreads the inks across its projects, so neighbours differ in colour as well as shape. */
|
|
58
|
+
pixelAvatar?: string;
|
|
57
59
|
/** Small trailing annotation in the caption face, e.g. how recently the project was touched. */
|
|
58
60
|
meta?: string;
|
|
59
61
|
/** Optional href — the row renders as an `<a>` instead of a `<button>`. */
|
|
@@ -3,8 +3,10 @@ import React from "react";
|
|
|
3
3
|
import { Avatar } from "../Avatar/Avatar.js";
|
|
4
4
|
import { DropdownMenu } from "../DropdownMenu/DropdownMenu.js";
|
|
5
5
|
import { Kbd } from "../Kbd/Kbd.js";
|
|
6
|
+
import { PixelAvatar } from "../PixelAvatar/PixelAvatar.js";
|
|
6
7
|
import "./ThreadPanel.css";
|
|
7
8
|
import "../../fonts/material-symbols.css";
|
|
9
|
+
import { distinctPixelInks } from "../PixelAvatar/pixelInks.js";
|
|
8
10
|
const baseClass = "ds-thread-panel";
|
|
9
11
|
function ActionRow({
|
|
10
12
|
className,
|
|
@@ -134,6 +136,9 @@ const ThreadPanel = React.forwardRef(
|
|
|
134
136
|
className
|
|
135
137
|
].filter(Boolean).join(" ");
|
|
136
138
|
const iconClass = "material-symbols-rounded";
|
|
139
|
+
const projectInks = distinctPixelInks(
|
|
140
|
+
(projects ?? []).map((project) => project.pixelAvatar ?? project.label)
|
|
141
|
+
);
|
|
137
142
|
const showHeader = Boolean(logo || logoText || onExpandedChange);
|
|
138
143
|
const showNew = Boolean(newThreadLabel || onNewThread || newThreadHref);
|
|
139
144
|
const showFooter = Boolean(footerSlot || profile);
|
|
@@ -252,7 +257,7 @@ const ThreadPanel = React.forwardRef(
|
|
|
252
257
|
}
|
|
253
258
|
)
|
|
254
259
|
] }),
|
|
255
|
-
/* @__PURE__ */ jsx("ul", { className: `${baseClass}__list`, "aria-label": projectsLabel, children: projects.map((project) => {
|
|
260
|
+
/* @__PURE__ */ jsx("ul", { className: `${baseClass}__list`, "aria-label": projectsLabel, children: projects.map((project, projectIndex) => {
|
|
256
261
|
const active = project.id === activeProjectId;
|
|
257
262
|
return /* @__PURE__ */ jsx("li", { className: `${baseClass}__row`, children: /* @__PURE__ */ jsxs(
|
|
258
263
|
ActionRow,
|
|
@@ -266,12 +271,25 @@ const ThreadPanel = React.forwardRef(
|
|
|
266
271
|
ariaCurrent: active ? project.href ? "page" : "true" : void 0,
|
|
267
272
|
title: project.label,
|
|
268
273
|
children: [
|
|
269
|
-
/* @__PURE__ */ jsx(
|
|
274
|
+
project.icon == null ? /* @__PURE__ */ jsx(
|
|
275
|
+
"span",
|
|
276
|
+
{
|
|
277
|
+
className: `${baseClass}__project-icon ${baseClass}__project-icon--avatar`,
|
|
278
|
+
"aria-hidden": "true",
|
|
279
|
+
children: /* @__PURE__ */ jsx(
|
|
280
|
+
PixelAvatar,
|
|
281
|
+
{
|
|
282
|
+
name: project.pixelAvatar ?? project.label,
|
|
283
|
+
ink: projectInks[projectIndex]
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
) : /* @__PURE__ */ jsx(
|
|
270
288
|
"span",
|
|
271
289
|
{
|
|
272
|
-
className: `${baseClass}__project-icon${typeof project.icon === "string"
|
|
290
|
+
className: `${baseClass}__project-icon${typeof project.icon === "string" ? ` ${iconClass}` : ""}`,
|
|
273
291
|
"aria-hidden": "true",
|
|
274
|
-
children: project.icon
|
|
292
|
+
children: project.icon
|
|
275
293
|
}
|
|
276
294
|
),
|
|
277
295
|
/* @__PURE__ */ jsx("span", { className: `${baseClass}__project-label`, children: project.label }),
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
WIDGET COMPONENT
|
|
3
|
+
A titled tile for a dashboard or home screen,
|
|
4
|
+
with its rows, its groups, and the board that
|
|
5
|
+
packs tiles into columns
|
|
6
|
+
============================================ */
|
|
7
|
+
|
|
8
|
+
/* Tile — the translucent container behind a hairline, blurred so the body
|
|
9
|
+
stays on a calm field when the tile sits over an ambient background. The
|
|
10
|
+
navigation-tile radius, per the radius scale. */
|
|
11
|
+
|
|
12
|
+
.ds-widget {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
gap: var(--gap-300);
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
min-width: 0;
|
|
18
|
+
padding: var(--padding-500);
|
|
19
|
+
border: var(--border-025) solid var(--color-bg-container-border);
|
|
20
|
+
border-radius: var(--radius-600);
|
|
21
|
+
background-color: var(--color-bg-container-primary-semi);
|
|
22
|
+
-webkit-backdrop-filter: blur(24px);
|
|
23
|
+
backdrop-filter: blur(24px);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.ds-widget__header {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: space-between;
|
|
30
|
+
gap: var(--gap-200);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.ds-widget__title {
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: baseline;
|
|
36
|
+
gap: var(--gap-200);
|
|
37
|
+
min-width: 0;
|
|
38
|
+
margin: 0;
|
|
39
|
+
font-family: var(--font-title-body-family);
|
|
40
|
+
font-size: var(--font-title-body-size);
|
|
41
|
+
font-weight: var(--font-title-body-weight);
|
|
42
|
+
line-height: var(--font-title-body-line-height);
|
|
43
|
+
letter-spacing: var(--font-title-body-letter-spacing);
|
|
44
|
+
color: var(--color-text-primary);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.ds-widget__count {
|
|
48
|
+
font-family: var(--font-paragraph-sm-family);
|
|
49
|
+
font-size: var(--font-paragraph-sm-size);
|
|
50
|
+
font-weight: var(--font-paragraph-sm-weight);
|
|
51
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
52
|
+
letter-spacing: var(--font-paragraph-sm-letter-spacing);
|
|
53
|
+
color: var(--color-text-tertiary);
|
|
54
|
+
font-variant-numeric: tabular-nums;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.ds-widget__action {
|
|
58
|
+
display: inline-flex;
|
|
59
|
+
flex-shrink: 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.ds-widget__body {
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
gap: var(--gap-100);
|
|
66
|
+
min-width: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Group — a labelled run of rows */
|
|
70
|
+
|
|
71
|
+
.ds-widget-group {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
gap: var(--gap-100);
|
|
75
|
+
min-width: 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.ds-widget-group + .ds-widget-group {
|
|
79
|
+
margin-top: var(--gap-300);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.ds-widget-group__label {
|
|
83
|
+
font-family: var(--font-caption-family);
|
|
84
|
+
font-size: var(--font-caption-size);
|
|
85
|
+
font-weight: var(--font-caption-weight);
|
|
86
|
+
line-height: var(--font-caption-line-height);
|
|
87
|
+
letter-spacing: var(--font-caption-letter-spacing);
|
|
88
|
+
color: var(--color-text-tertiary);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Row — a leading mark, a title over a description, a trailing fact */
|
|
92
|
+
|
|
93
|
+
.ds-widget-row {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
gap: var(--gap-300);
|
|
97
|
+
box-sizing: border-box;
|
|
98
|
+
width: 100%;
|
|
99
|
+
min-width: 0;
|
|
100
|
+
margin: 0;
|
|
101
|
+
padding: var(--padding-150) 0;
|
|
102
|
+
border: 0;
|
|
103
|
+
background: none;
|
|
104
|
+
color: inherit;
|
|
105
|
+
font: inherit;
|
|
106
|
+
text-align: left;
|
|
107
|
+
text-decoration: none;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.ds-widget-row__leading {
|
|
111
|
+
display: inline-flex;
|
|
112
|
+
flex-shrink: 0;
|
|
113
|
+
align-items: center;
|
|
114
|
+
--icon-size: var(--icon-size-600);
|
|
115
|
+
color: var(--color-icon-secondary);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.ds-widget-row__main {
|
|
119
|
+
display: flex;
|
|
120
|
+
flex: 1;
|
|
121
|
+
flex-direction: column;
|
|
122
|
+
min-width: 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.ds-widget-row__title,
|
|
126
|
+
.ds-widget-row__description {
|
|
127
|
+
overflow: hidden;
|
|
128
|
+
text-overflow: ellipsis;
|
|
129
|
+
white-space: nowrap;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.ds-widget-row__title {
|
|
133
|
+
font-family: var(--font-paragraph-sm-emphasis-family);
|
|
134
|
+
font-size: var(--font-paragraph-sm-emphasis-size);
|
|
135
|
+
font-weight: var(--font-paragraph-sm-emphasis-weight);
|
|
136
|
+
line-height: var(--font-paragraph-sm-emphasis-line-height);
|
|
137
|
+
letter-spacing: var(--font-paragraph-sm-emphasis-letter-spacing);
|
|
138
|
+
color: var(--color-text-primary);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.ds-widget-row__description {
|
|
142
|
+
font-family: var(--font-caption-family);
|
|
143
|
+
font-size: var(--font-caption-size);
|
|
144
|
+
font-weight: var(--font-caption-weight);
|
|
145
|
+
line-height: var(--font-caption-line-height);
|
|
146
|
+
letter-spacing: var(--font-caption-letter-spacing);
|
|
147
|
+
color: var(--color-text-tertiary);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.ds-widget-row__meta {
|
|
151
|
+
flex-shrink: 0;
|
|
152
|
+
font-family: var(--font-caption-family);
|
|
153
|
+
font-size: var(--font-caption-size);
|
|
154
|
+
font-weight: var(--font-caption-weight);
|
|
155
|
+
line-height: var(--font-caption-line-height);
|
|
156
|
+
letter-spacing: var(--font-caption-letter-spacing);
|
|
157
|
+
color: var(--color-text-secondary);
|
|
158
|
+
font-variant-numeric: tabular-nums;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* A row that goes somewhere: the hover fill bleeds past the text into the
|
|
162
|
+
tile's padding, so the whole line reads as one target. */
|
|
163
|
+
|
|
164
|
+
.ds-widget-row--interactive {
|
|
165
|
+
width: calc(100% + 2 * var(--padding-200));
|
|
166
|
+
margin-inline: calc(-1 * var(--padding-200));
|
|
167
|
+
padding-inline: var(--padding-200);
|
|
168
|
+
border-radius: var(--radius-300);
|
|
169
|
+
cursor: pointer;
|
|
170
|
+
transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.ds-widget-row--interactive:hover {
|
|
174
|
+
background-color: var(--color-action-passive-bg-hover);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.ds-widget-row--interactive:focus-visible {
|
|
178
|
+
outline: 2px solid var(--color-action-primary-bg);
|
|
179
|
+
outline-offset: 2px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Grid — CSS columns, so tiles of different heights pack with no holes and
|
|
183
|
+
the board folds as it narrows. The column floor is stated on the icon
|
|
184
|
+
scale (seven cover icons, the narrowest a row of title, description and
|
|
185
|
+
fact stays readable), not as a breakpoint. */
|
|
186
|
+
|
|
187
|
+
.ds-widget-grid {
|
|
188
|
+
column-gap: var(--gap-400);
|
|
189
|
+
column-width: calc(var(--icon-size-1200) * 7);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.ds-widget-grid--columns-1 {
|
|
193
|
+
column-count: 1;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.ds-widget-grid--columns-2 {
|
|
197
|
+
column-count: 2;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.ds-widget-grid--columns-3 {
|
|
201
|
+
column-count: 3;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.ds-widget-grid > * {
|
|
205
|
+
break-inside: avoid;
|
|
206
|
+
margin-bottom: var(--gap-400);
|
|
207
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** Props owned by Widget itself — everything else falls through to the root `<section>`. */
|
|
3
|
+
type WidgetOwnProps = {
|
|
4
|
+
/** The tile's heading. Also the section's accessible name, unless an `aria-label` is passed. Note: this shadows the native `title` tooltip attribute, which Widget does not expose. */
|
|
5
|
+
title: string;
|
|
6
|
+
/** A quiet figure after the title: how many rows the tile holds, how many are open. */
|
|
7
|
+
count?: number;
|
|
8
|
+
/** Trailing header slot, opposite the title — a compact button, a link, a period label. */
|
|
9
|
+
action?: React.ReactNode;
|
|
10
|
+
/** Heading element for the title, so the tile takes its place in the page's outline. */
|
|
11
|
+
titleAs?: 'h2' | 'h3' | 'h4';
|
|
12
|
+
/** The tile's body: WidgetRows, WidgetGroups, a chart, a figure, anything. */
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
/** Additional CSS classes */
|
|
15
|
+
className?: string;
|
|
16
|
+
};
|
|
17
|
+
export interface WidgetProps extends WidgetOwnProps, Omit<React.ComponentPropsWithoutRef<'section'>, keyof WidgetOwnProps> {
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Widget — a titled tile for a dashboard or a home screen: a heading with an
|
|
21
|
+
* optional count, then whatever the tile is about. The surface is the
|
|
22
|
+
* system's translucent container behind a hairline, so a board of them sits
|
|
23
|
+
* over an ambient background without going flat. Compose the body from
|
|
24
|
+
* WidgetRow and WidgetGroup for lists, or pass a chart or a figure.
|
|
25
|
+
*
|
|
26
|
+
* Purely presentational: safe to render from a Server Component.
|
|
27
|
+
*/
|
|
28
|
+
export declare const Widget: React.ForwardRefExoticComponent<WidgetProps & React.RefAttributes<HTMLElement>>;
|
|
29
|
+
/** Props owned by WidgetGroup itself — everything else falls through to the root `<div>`. */
|
|
30
|
+
type WidgetGroupOwnProps = {
|
|
31
|
+
/** The run's quiet label, e.g. "Waiting on you". */
|
|
32
|
+
label: string;
|
|
33
|
+
/** The rows in this run. */
|
|
34
|
+
children?: React.ReactNode;
|
|
35
|
+
/** Additional CSS classes */
|
|
36
|
+
className?: string;
|
|
37
|
+
};
|
|
38
|
+
export interface WidgetGroupProps extends WidgetGroupOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof WidgetGroupOwnProps> {
|
|
39
|
+
}
|
|
40
|
+
/** WidgetGroup — splits a Widget's rows into labelled runs. */
|
|
41
|
+
export declare const WidgetGroup: React.ForwardRefExoticComponent<WidgetGroupProps & React.RefAttributes<HTMLDivElement>>;
|
|
42
|
+
/** Props owned by WidgetRow itself — everything else falls through to the root element. */
|
|
43
|
+
type WidgetRowOwnProps = {
|
|
44
|
+
/** The row's main line. Note: this shadows the native `title` tooltip attribute, which WidgetRow does not expose. */
|
|
45
|
+
title: string;
|
|
46
|
+
/** A quiet second line under the title: who, where, or how much. */
|
|
47
|
+
description?: string;
|
|
48
|
+
/** Leading mark — a StatusDot, a PixelAvatar, an Avatar, a Spinner, an icon. Decorative unless the element names itself. */
|
|
49
|
+
leading?: React.ReactNode;
|
|
50
|
+
/** Trailing fact in the caption face: a date, an amount, a state. */
|
|
51
|
+
meta?: React.ReactNode;
|
|
52
|
+
/** Renders the row as a link to this URL. */
|
|
53
|
+
href?: string;
|
|
54
|
+
/** Additional CSS classes */
|
|
55
|
+
className?: string;
|
|
56
|
+
};
|
|
57
|
+
export interface WidgetRowProps extends WidgetRowOwnProps, Omit<React.HTMLAttributes<HTMLElement>, keyof WidgetRowOwnProps> {
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* WidgetRow — one line inside a Widget: a leading mark, a title over an
|
|
61
|
+
* optional description, and a trailing fact. It is a link when given an
|
|
62
|
+
* `href`, a button when given an `onClick`, and a plain row otherwise, so a
|
|
63
|
+
* list only offers a target where there is somewhere to go.
|
|
64
|
+
*/
|
|
65
|
+
export declare const WidgetRow: React.ForwardRefExoticComponent<WidgetRowProps & React.RefAttributes<HTMLElement>>;
|
|
66
|
+
/** Props owned by WidgetGrid itself — everything else falls through to the root `<div>`. */
|
|
67
|
+
type WidgetGridOwnProps = {
|
|
68
|
+
/** The most columns the board runs to. It folds to fewer as its own width narrows. */
|
|
69
|
+
columns?: 1 | 2 | 3;
|
|
70
|
+
/** The Widgets on the board, in reading order: they fill the first column, then the next. */
|
|
71
|
+
children?: React.ReactNode;
|
|
72
|
+
/** Additional CSS classes */
|
|
73
|
+
className?: string;
|
|
74
|
+
};
|
|
75
|
+
export interface WidgetGridProps extends WidgetGridOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof WidgetGridOwnProps> {
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* WidgetGrid — lays Widgets out as a board. Tiles of different heights pack
|
|
79
|
+
* into columns with no holes beside the tall ones, and the board folds to
|
|
80
|
+
* fewer columns as it narrows, with no breakpoint to configure.
|
|
81
|
+
*/
|
|
82
|
+
export declare const WidgetGrid: React.ForwardRefExoticComponent<WidgetGridProps & React.RefAttributes<HTMLDivElement>>;
|
|
83
|
+
export {};
|