@veevarts/design-system 2.0.0-alpha.62 → 2.0.0-alpha.63
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 +10 -1
- package/dist/assistant.cjs +2 -1
- package/dist/assistant.d.ts +10 -0
- package/dist/assistant.js +1046 -389
- package/dist/patterns/ArtifactCard/ArtifactCard.d.ts +19 -0
- package/dist/patterns/ArtifactCard/index.d.ts +2 -0
- package/dist/patterns/ArtifactCard/styles.d.ts +31 -0
- package/dist/patterns/ArtifactCard/types.d.ts +36 -0
- package/dist/patterns/AssistantMarkdown/AssistantMarkdown.d.ts +17 -0
- package/dist/patterns/AssistantMarkdown/index.d.ts +3 -0
- package/dist/patterns/AssistantMarkdown/links.d.ts +43 -0
- package/dist/patterns/AssistantMarkdown/parser.d.ts +5 -0
- package/dist/patterns/AssistantMarkdown/styles.d.ts +39 -0
- package/dist/patterns/AssistantMarkdown/types.d.ts +48 -0
- package/dist/patterns/CitationMarker/CitationMarker.d.ts +16 -0
- package/dist/patterns/CitationMarker/index.d.ts +2 -0
- package/dist/patterns/CitationMarker/styles.d.ts +19 -0
- package/dist/patterns/CitationMarker/types.d.ts +13 -0
- package/dist/patterns/SourceList/SourceList.d.ts +14 -0
- package/dist/patterns/SourceList/index.d.ts +2 -0
- package/dist/patterns/SourceList/styles.d.ts +20 -0
- package/dist/patterns/SourceList/types.d.ts +32 -0
- package/dist/patterns/SourcePill/SourcePill.d.ts +13 -0
- package/dist/patterns/SourcePill/index.d.ts +2 -0
- package/dist/patterns/SourcePill/styles.d.ts +19 -0
- package/dist/patterns/SourcePill/types.d.ts +20 -0
- package/package.json +3 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { JSX } from 'react';
|
|
2
|
+
import { ArtifactCardProps } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* A saved artifact (a table, a chart, a report) shown in a conversation or a
|
|
5
|
+
* list: kind, title, a preview, its save status in words, and the caller's
|
|
6
|
+
* actions. A HeroUI `Card`.
|
|
7
|
+
*
|
|
8
|
+
* The status line is one polite live region whose role never changes, so
|
|
9
|
+
* "Saving…" turning into "Saved" or "Couldn't save" is announced once, and a
|
|
10
|
+
* card that mounts already failed (a conversation reopened) interrupts no one.
|
|
11
|
+
* An assertive error belongs to `AssistantNotice`, as in the rest of the
|
|
12
|
+
* family. A failed save keeps its preview, so the work stays in view; a
|
|
13
|
+
* deleted one drops it.
|
|
14
|
+
*/
|
|
15
|
+
export declare function ArtifactCard({ title, kindLabel, icon, status, statusLabel, meta, badge, children, actions, titleAs: TitleElement, className, 'data-testid': dataTestId, }: ArtifactCardProps): JSX.Element;
|
|
16
|
+
export declare namespace ArtifactCard {
|
|
17
|
+
var displayName: string;
|
|
18
|
+
}
|
|
19
|
+
export default ArtifactCard;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ArtifactCardStatus } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Every slot class of `ArtifactCard`. HeroUI semantic colours only, so the
|
|
4
|
+
* card works under `.dark`: `content1` card, `divider` edges, `foreground`
|
|
5
|
+
* text, `default-600` secondary text (it also sits on `default-100`, where
|
|
6
|
+
* `default-500` falls under 4.5:1). Status tones use the 700 step, which
|
|
7
|
+
* stays readable in both themes because HeroUI's dark scales are reversed.
|
|
8
|
+
*/
|
|
9
|
+
export declare const artifactCardClass: {
|
|
10
|
+
readonly base: "w-full border border-default-200 bg-content1 shadow-none";
|
|
11
|
+
readonly deleted: "border-dashed";
|
|
12
|
+
readonly header: "flex flex-col items-start gap-1 px-4 pb-0 pt-3";
|
|
13
|
+
readonly kindRow: "flex min-w-0 flex-wrap items-center gap-2 text-xs text-default-600";
|
|
14
|
+
readonly kind: "inline-flex items-center gap-1.5 font-medium";
|
|
15
|
+
readonly badge: "inline-flex items-center gap-1 rounded-md border border-default-300 bg-default-100 px-1.5 py-0.5 text-[0.6875rem] font-semibold uppercase tracking-wide text-default-700";
|
|
16
|
+
readonly title: "m-0 min-w-0 text-sm font-semibold text-foreground [overflow-wrap:anywhere]";
|
|
17
|
+
readonly titleDeleted: "text-default-600";
|
|
18
|
+
readonly meta: "text-xs tabular-nums text-default-600 [overflow-wrap:anywhere]";
|
|
19
|
+
readonly body: "min-w-0 px-4 py-3";
|
|
20
|
+
readonly skeleton: "flex flex-col gap-2";
|
|
21
|
+
readonly skeletonLine: "h-3 rounded-md";
|
|
22
|
+
/** Three lines of a preview that has not arrived yet. */
|
|
23
|
+
readonly skeletonWidths: readonly ["w-3/4", "w-full", "w-2/3"];
|
|
24
|
+
readonly footer: "flex flex-wrap items-center justify-between gap-2 border-t border-divider px-4 py-2.5";
|
|
25
|
+
readonly status: "flex min-w-0 items-center gap-1.5 text-xs font-medium [overflow-wrap:anywhere]";
|
|
26
|
+
/** HeroUI's small spinner is 20px; sized to the 14px status icons so the footer keeps its height when the save ends. */
|
|
27
|
+
readonly spinner: "size-3.5";
|
|
28
|
+
readonly actions: "flex flex-wrap items-center gap-1.5";
|
|
29
|
+
};
|
|
30
|
+
export declare const artifactCardStatusClass: Record<ArtifactCardStatus, string>;
|
|
31
|
+
export declare const ARTIFACT_CARD_STATUS_ICON: Record<Exclude<ArtifactCardStatus, 'saving'>, string>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Where a saved artifact stands. `saving` while it is written, `saved` once
|
|
4
|
+
* it is kept, `failed` when it could not be saved (too large, an error), and
|
|
5
|
+
* `deleted` once its owner removed it.
|
|
6
|
+
*/
|
|
7
|
+
export type ArtifactCardStatus = 'saving' | 'saved' | 'failed' | 'deleted';
|
|
8
|
+
export interface ArtifactCardProps {
|
|
9
|
+
/** The artifact's title, e.g. "Renewals in Q1 with red health". */
|
|
10
|
+
title: string;
|
|
11
|
+
/** What kind of artifact it is, e.g. "Table", "Chart", "Report". */
|
|
12
|
+
kindLabel: string;
|
|
13
|
+
/** Iconify name for the kind. */
|
|
14
|
+
icon?: string;
|
|
15
|
+
/** Defaults to `saved`. */
|
|
16
|
+
status?: ArtifactCardStatus;
|
|
17
|
+
/**
|
|
18
|
+
* The status in words, always shown: "Saved · private to you", "Saving…",
|
|
19
|
+
* "Couldn't save: the report is over the size limit", "Deleted".
|
|
20
|
+
*/
|
|
21
|
+
statusLabel: ReactNode;
|
|
22
|
+
/** A short line under the title, e.g. "12 rows · 4 columns". */
|
|
23
|
+
meta?: ReactNode;
|
|
24
|
+
/** A small marker next to the kind, e.g. "Internal". Styled by the card so it reads the same everywhere. */
|
|
25
|
+
badge?: ReactNode;
|
|
26
|
+
/** A preview: the first rows, a small chart, the first lines. Hidden once `deleted`; a skeleton while `saving` without one. */
|
|
27
|
+
children?: ReactNode;
|
|
28
|
+
/** Controls (Open, Download, Delete, a narrower request). Rendered as given. */
|
|
29
|
+
actions?: ReactNode;
|
|
30
|
+
/** The title's element. Defaults to `p`; pick a heading that fits the page's outline. */
|
|
31
|
+
titleAs?: 'h2' | 'h3' | 'h4' | 'p';
|
|
32
|
+
/** Classes on the card. */
|
|
33
|
+
className?: string;
|
|
34
|
+
/** Test id of the root; its parts add suffixes (see Test ids in the docs). Defaults to `artifact-card`. */
|
|
35
|
+
'data-testid'?: string;
|
|
36
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { JSX } from 'react';
|
|
2
|
+
import { AssistantMarkdownProps } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* An assistant's answer, rendered from a safe Markdown subset: headings,
|
|
5
|
+
* paragraphs, lists, bold and italic, inline code, code blocks, tables,
|
|
6
|
+
* blockquotes and links. No raw HTML, no images, no auto-linked URLs; links
|
|
7
|
+
* open in a new tab, show their domain, and pass `linkPolicy` first.
|
|
8
|
+
*
|
|
9
|
+
* markdown-it parses (a peer dependency, shared with `@tiptap/pm`); every
|
|
10
|
+
* token is turned into a React element from an allowlist, and nothing reaches
|
|
11
|
+
* the DOM as HTML.
|
|
12
|
+
*/
|
|
13
|
+
export declare function AssistantMarkdown({ source, linkPolicy, headingOffset, renderCitation, newTabLabel, tableLabel, codeLabel, className, 'data-testid': dataTestId, }: AssistantMarkdownProps): JSX.Element;
|
|
14
|
+
export declare namespace AssistantMarkdown {
|
|
15
|
+
var displayName: string;
|
|
16
|
+
}
|
|
17
|
+
export default AssistantMarkdown;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { AssistantMarkdownLinkPolicy } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The href a rendered link may carry, so the domain shown next to it is where
|
|
4
|
+
* it goes. `AssistantMarkdown` applies it to whatever the link policy returns,
|
|
5
|
+
* the default or the caller's. Only three schemes get through, plus hrefs with
|
|
6
|
+
* no scheme:
|
|
7
|
+
* - `http:` and `https:` must name their host (`https://…`), and come back in
|
|
8
|
+
* canonical form, so the domain shown and the target are read from one
|
|
9
|
+
* string. `https:evil.example` and `https:/evil.example` read as
|
|
10
|
+
* evil.example on their own, but a browser resolves them against the page,
|
|
11
|
+
* as paths on the page's own origin: refused (`null`).
|
|
12
|
+
* - `mailto:` is refused when its address is empty, can't be decoded, or
|
|
13
|
+
* holds a control, a space or an invisible format character (a right-to-left
|
|
14
|
+
* override would make `moc.doog@live.example` read as an address at
|
|
15
|
+
* good.com). Otherwise it comes back rebuilt from that address plus its
|
|
16
|
+
* `subject` and `body` fields only: `to=`, `cc=`, `bcc=`, every other field,
|
|
17
|
+
* a `subject` or `body` holding an encoded `&`, and the `#` fragment are
|
|
18
|
+
* dropped, so the address shown is everyone the email goes to.
|
|
19
|
+
* - Every other scheme is refused: `javascript:`, `vbscript:`, `data:`,
|
|
20
|
+
* `file:`, `tel:` and the rest, however the policy came by it (say, by
|
|
21
|
+
* decoding a redirect's `url=` parameter).
|
|
22
|
+
* - An href with no scheme that names a host (`//evil.example`) leaves the
|
|
23
|
+
* page, but no domain can be shown for it: refused. Any other href with no
|
|
24
|
+
* scheme (a path on the app) is returned as is.
|
|
25
|
+
*/
|
|
26
|
+
export declare function checkedLinkHref(href: string): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* The link policy used when the caller passes none: absolute `http://` and
|
|
29
|
+
* `https://` URLs, and `mailto:`, only (each through `checkedLinkHref`).
|
|
30
|
+
* markdown-it has already refused `javascript:`, `vbscript:`, `file:` and
|
|
31
|
+
* non-image `data:` links by then (it lets `data:image/*` through, which this
|
|
32
|
+
* policy refuses); this also refuses relative links, because only the app
|
|
33
|
+
* knows where they would go.
|
|
34
|
+
*
|
|
35
|
+
* A `mailto:` keeps its address only: `?subject=`, `body=`, `cc=`, `bcc=` and
|
|
36
|
+
* a `#` fragment are dropped, so a link can't open an email already filled in.
|
|
37
|
+
*/
|
|
38
|
+
export declare const defaultAssistantLinkPolicy: AssistantMarkdownLinkPolicy;
|
|
39
|
+
/**
|
|
40
|
+
* Where a link goes, shown next to it: the host for `http(s):` (without
|
|
41
|
+
* `www.`), the address for `mailto:`. Empty when it can't be read.
|
|
42
|
+
*/
|
|
43
|
+
export declare function linkDestination(href: string): string;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { default as MarkdownIt } from 'markdown-it';
|
|
2
|
+
/** markdown-it's token, without importing a deep path that the bundle would also have to keep external. */
|
|
3
|
+
export type MarkdownToken = ReturnType<MarkdownIt['parse']>[number];
|
|
4
|
+
/** One shared parser: building one is the costly part, and its settings never change. */
|
|
5
|
+
export declare function assistantMarkdownParser(): MarkdownIt;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every slot class of `AssistantMarkdown`. HeroUI semantic colours plus
|
|
3
|
+
* `ai-accent` for links, so the rendered answer works under `.dark`. Text
|
|
4
|
+
* tones: `foreground` for body, `default-600` for quotes, `default-500` only
|
|
5
|
+
* for the link domain on a plain card (4.83:1 light, 6.9:1 dark).
|
|
6
|
+
*/
|
|
7
|
+
export declare const assistantMarkdownClass: {
|
|
8
|
+
readonly root: "min-w-0 space-y-3 text-sm leading-relaxed text-foreground [overflow-wrap:anywhere]";
|
|
9
|
+
readonly paragraph: "m-0";
|
|
10
|
+
readonly heading: Record<number, string>;
|
|
11
|
+
readonly bulletList: "m-0 list-disc space-y-1 ps-5";
|
|
12
|
+
readonly orderedList: "m-0 list-decimal space-y-1 ps-5";
|
|
13
|
+
readonly listItem: "ps-1 [&>ol]:mt-1 [&>ul]:mt-1";
|
|
14
|
+
/** Quoted third-party text (an email, a call note): set apart, never styled as the assistant's own words. */
|
|
15
|
+
readonly blockquote: "m-0 space-y-2 border-s-2 border-default-300 ps-3 text-default-600";
|
|
16
|
+
readonly code: "rounded bg-default-100 px-1 py-0.5 font-mono text-[0.8125em]";
|
|
17
|
+
/** The code block's scroll box: named and focusable like the table's, so a long line can be scrolled by keyboard in every browser. */
|
|
18
|
+
readonly codeWrap: "max-w-full overflow-x-auto rounded-md bg-default-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ai-focus";
|
|
19
|
+
readonly pre: "m-0 whitespace-pre p-3 font-mono text-xs [overflow-wrap:normal]";
|
|
20
|
+
readonly rule: "my-3 border-0 border-t border-divider";
|
|
21
|
+
readonly tableWrap: "max-w-full overflow-x-auto rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ai-focus";
|
|
22
|
+
readonly table: "w-full border-collapse text-sm";
|
|
23
|
+
/**
|
|
24
|
+
* Cells break between words only (the root's `overflow-wrap: anywhere`
|
|
25
|
+
* would split them mid-word and squeeze a wide table to fit), and headers
|
|
26
|
+
* stay on one line, so a wide table scrolls inside its box instead.
|
|
27
|
+
*/
|
|
28
|
+
readonly headerCell: "whitespace-nowrap border-b border-divider px-2 py-1.5 font-semibold";
|
|
29
|
+
readonly cell: "border-b border-divider px-2 py-1.5 align-top [overflow-wrap:normal]";
|
|
30
|
+
readonly align: Record<string, string>;
|
|
31
|
+
readonly link: "text-ai-accent underline underline-offset-2 hover:no-underline";
|
|
32
|
+
/**
|
|
33
|
+
* `inline-block` keeps the link's underline off the domain. `isolate`
|
|
34
|
+
* keeps a bidi control in the link's text from changing how the domain
|
|
35
|
+
* reads, even if the box stops being an inline-block.
|
|
36
|
+
*/
|
|
37
|
+
readonly linkDomain: "inline-block text-default-500 [unicode-bidi:isolate]";
|
|
38
|
+
readonly srOnly: "sr-only";
|
|
39
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Decides what a link may do: return the href to use (as is, or rewritten),
|
|
4
|
+
* or `null` to show the link's text without a link.
|
|
5
|
+
*/
|
|
6
|
+
export type AssistantMarkdownLinkPolicy = (href: string) => string | null;
|
|
7
|
+
export interface AssistantMarkdownProps {
|
|
8
|
+
/** The Markdown to render. Untrusted: raw HTML, images and bare or angle-bracket URLs render as text. */
|
|
9
|
+
source: string;
|
|
10
|
+
/**
|
|
11
|
+
* Defaults to `defaultAssistantLinkPolicy`: absolute `http://` and `https://`
|
|
12
|
+
* URLs, and `mailto:` (address only), only. Whatever a policy returns is
|
|
13
|
+
* checked too, so the domain shown is where the link goes: only `http(s):`,
|
|
14
|
+
* `mailto:` and hrefs with no scheme can render (`javascript:`, `data:`,
|
|
15
|
+
* `tel:` and every other scheme are refused); an `http(s)` href must name
|
|
16
|
+
* its host (`https://…`) and is rendered in canonical form; a `mailto:`
|
|
17
|
+
* address with a control, a space or an invisible format character (a
|
|
18
|
+
* bidi override) is refused, and a `mailto:` keeps only its address,
|
|
19
|
+
* `subject` and `body` (no `to`, `cc`, `bcc`, `#` fragment, or `&` hidden
|
|
20
|
+
* in a kept value); and an href with no scheme that names a host (`//…`)
|
|
21
|
+
* is refused.
|
|
22
|
+
*/
|
|
23
|
+
linkPolicy?: AssistantMarkdownLinkPolicy;
|
|
24
|
+
/**
|
|
25
|
+
* Shifts heading levels so an answer fits under the page's own headings:
|
|
26
|
+
* `#` renders as `h(1 + headingOffset)`, capped at `h6`. Defaults to 2.
|
|
27
|
+
*/
|
|
28
|
+
headingOffset?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Renders each `[n]` in the text (n from 1 to 999), for example with
|
|
31
|
+
* `CitationMarker`. Not inside link text, where a control would nest in a
|
|
32
|
+
* link, and not for an escaped `\\[n\\]`. Without it, `[n]` stays text.
|
|
33
|
+
* A `[n]` inside a quote (`>`) is handed over too, so an answer can cite
|
|
34
|
+
* the email it quotes; quoted text can also hold a `[n]` the assistant
|
|
35
|
+
* never wrote, so check `n` against the sources you have.
|
|
36
|
+
*/
|
|
37
|
+
renderCitation?: (index: number) => ReactNode;
|
|
38
|
+
/** Read by screen readers after each link. Defaults to "opens in a new tab". */
|
|
39
|
+
newTabLabel?: string;
|
|
40
|
+
/** Accessible name of each table's scroll box (a `group`, not a landmark). Defaults to "Table". */
|
|
41
|
+
tableLabel?: string;
|
|
42
|
+
/** Accessible name of each code block's scroll box (a `group`, not a landmark). Defaults to "Code". */
|
|
43
|
+
codeLabel?: string;
|
|
44
|
+
/** Classes on the root. */
|
|
45
|
+
className?: string;
|
|
46
|
+
/** Test id of the root; its parts add suffixes (see Test ids in the docs). Defaults to `assistant-markdown`. */
|
|
47
|
+
'data-testid'?: string;
|
|
48
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { JSX } from 'react';
|
|
2
|
+
import { CitationMarkerProps } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* A `[n]` marker inside an assistant's answer, pointing at one of its sources.
|
|
5
|
+
* Pressed, it calls `onSelect` (reveal the source, open it); without
|
|
6
|
+
* `onSelect` it is plain text with the same look.
|
|
7
|
+
*
|
|
8
|
+
* A native `<button>`, not HeroUI's: it sits inside a sentence and must align
|
|
9
|
+
* to the text baseline at text size, which HeroUI's Button (a fixed-height
|
|
10
|
+
* control with a minimum width) is not built for.
|
|
11
|
+
*/
|
|
12
|
+
export declare function CitationMarker({ index, label, onSelect, 'data-testid': dataTestId, }: CitationMarkerProps): JSX.Element;
|
|
13
|
+
export declare namespace CitationMarker {
|
|
14
|
+
var displayName: string;
|
|
15
|
+
}
|
|
16
|
+
export default CitationMarker;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every slot class of `CitationMarker`. The `ai-*` palette marks it as the
|
|
3
|
+
* assistant's: `ai-accent` on `ai-fill` measures 5.49:1 light and 8.15:1 dark
|
|
4
|
+
* (`src/tokens/__test__/assistant-surface.test.ts`), enough for 11px bold.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The `[n]` badge, shared with `SourcePill`'s marker: the two exist to be
|
|
8
|
+
* matched by eye, so they must never drift apart.
|
|
9
|
+
*/
|
|
10
|
+
export declare const citationBadgeClass = "inline-flex items-center rounded border border-ai-border bg-ai-fill px-1 text-[0.6875rem] font-semibold leading-4 tabular-nums text-ai-accent";
|
|
11
|
+
export declare const citationMarkerClass: {
|
|
12
|
+
/**
|
|
13
|
+
* Inline in a sentence: baseline-aligned, a left margin only (a right one
|
|
14
|
+
* would strand the punctuation after it), no fixed height.
|
|
15
|
+
*/
|
|
16
|
+
readonly base: "ms-0.5 align-baseline inline-flex items-center rounded border border-ai-border bg-ai-fill px-1 text-[0.6875rem] font-semibold leading-4 tabular-nums text-ai-accent";
|
|
17
|
+
readonly interactive: "cursor-pointer transition-colors hover:border-ai-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ai-focus motion-reduce:transition-none";
|
|
18
|
+
readonly srOnly: "sr-only";
|
|
19
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface CitationMarkerProps {
|
|
2
|
+
/** What the marker shows: `1` renders as `[1]`; `E1` as `[E1]`. */
|
|
3
|
+
index: number | string;
|
|
4
|
+
/**
|
|
5
|
+
* The accessible name, e.g. "Show source 1: email, Renewal pricing". The
|
|
6
|
+
* visible `[1]` alone says nothing to a screen reader.
|
|
7
|
+
*/
|
|
8
|
+
label: string;
|
|
9
|
+
/** Called when the marker is pressed, typically to reveal its source. Omitted, the marker is plain text. */
|
|
10
|
+
onSelect?: () => void;
|
|
11
|
+
/** Test id of the marker. Defaults to `citation-marker-<index>`; pass one per occurrence when a source is cited twice. */
|
|
12
|
+
'data-testid'?: string;
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SourceListHandle, SourceListProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The sources behind an assistant's answer, "Sources (n)", grouped by kind
|
|
4
|
+
* and closed by default. A HeroUI `Accordion` with one item; each source is a
|
|
5
|
+
* `SourcePill`.
|
|
6
|
+
*
|
|
7
|
+
* `reveal(id)` (through a ref) opens the list, moves focus to one source and
|
|
8
|
+
* scrolls it into view, so a `CitationMarker` can lead to the source it
|
|
9
|
+
* points at.
|
|
10
|
+
*/
|
|
11
|
+
export declare const SourceList: import('react').ForwardRefExoticComponent<SourceListProps & import('react').RefAttributes<SourceListHandle>> & {
|
|
12
|
+
displayName: string;
|
|
13
|
+
};
|
|
14
|
+
export default SourceList;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Every slot class of `SourceList`. HeroUI semantic colours only. */
|
|
2
|
+
export declare const sourceListClass: {
|
|
3
|
+
readonly base: "w-full px-0";
|
|
4
|
+
readonly item: "shadow-none";
|
|
5
|
+
readonly trigger: "gap-2 py-1.5 motion-reduce:transition-none [html[data-pos-reduce-motion=true]_&]:transition-none";
|
|
6
|
+
readonly title: "text-sm font-medium text-default-700";
|
|
7
|
+
readonly count: "ms-1 tabular-nums text-default-600";
|
|
8
|
+
readonly indicator: "text-default-500";
|
|
9
|
+
readonly content: "pb-2 pt-0";
|
|
10
|
+
readonly groups: "flex flex-col gap-3";
|
|
11
|
+
readonly group: "flex flex-col gap-1.5";
|
|
12
|
+
readonly groupLabel: "m-0 text-xs font-semibold uppercase tracking-wide text-default-600";
|
|
13
|
+
readonly list: "m-0 flex list-none flex-wrap gap-1.5 p-0";
|
|
14
|
+
/**
|
|
15
|
+
* The item takes focus only from `reveal()`, a script focus that follows a
|
|
16
|
+
* mouse click as often as a key, and `:focus-visible` would not match the
|
|
17
|
+
* click. So the ring is on `:focus`: the revealed source is marked either way.
|
|
18
|
+
*/
|
|
19
|
+
readonly listItem: "max-w-full rounded-full outline-none focus:ring-2 focus:ring-ai-focus";
|
|
20
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { SourcePillProps } from '../SourcePill';
|
|
3
|
+
export interface SourceListItem extends Omit<SourcePillProps, 'data-testid'> {
|
|
4
|
+
/** Stable key; also what `reveal(id)` takes. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Sources with the same `groupKey` are listed together, groups in order of first appearance. */
|
|
7
|
+
groupKey: string;
|
|
8
|
+
/** The group's heading, e.g. "Emails". */
|
|
9
|
+
groupLabel: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SourceListHandle {
|
|
12
|
+
/** Opens the list and moves focus to the source with this id, e.g. when its `[n]` is pressed. */
|
|
13
|
+
reveal: (id: string) => void;
|
|
14
|
+
}
|
|
15
|
+
export interface SourceListProps {
|
|
16
|
+
/** The sources: each a `SourcePill`'s props plus its `id` and group. */
|
|
17
|
+
items: SourceListItem[];
|
|
18
|
+
/** The toggle's text, e.g. "Sources". The count is added after it. */
|
|
19
|
+
title: ReactNode;
|
|
20
|
+
/** Plain-text `title`, needed when `title` is not a string (React Aria's type-to-select). */
|
|
21
|
+
titleText?: string;
|
|
22
|
+
/** Controlled open state. */
|
|
23
|
+
isExpanded?: boolean;
|
|
24
|
+
/** Uncontrolled initial state. Defaults to closed. */
|
|
25
|
+
defaultExpanded?: boolean;
|
|
26
|
+
/** Called when the list opens or closes, including when `reveal` opens it. */
|
|
27
|
+
onExpandedChange?: (isExpanded: boolean) => void;
|
|
28
|
+
/** Classes on the accordion. */
|
|
29
|
+
className?: string;
|
|
30
|
+
/** Test id of the root; its parts add suffixes (see Test ids in the docs). Defaults to `source-list`. */
|
|
31
|
+
'data-testid'?: string;
|
|
32
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { JSX } from 'react';
|
|
2
|
+
import { SourcePillProps } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* One source an assistant used: its kind, title and date, with the `[n]` it
|
|
5
|
+
* answers to in the text. With `onOpen` it is a button that opens the source;
|
|
6
|
+
* without it, plain text. A source that can no longer be opened says why, in
|
|
7
|
+
* words, instead of showing a dead button.
|
|
8
|
+
*/
|
|
9
|
+
export declare function SourcePill({ kindLabel, title, date, icon, marker, onOpen, isDisabled, disabledReason, 'data-testid': dataTestId, }: SourcePillProps): JSX.Element;
|
|
10
|
+
export declare namespace SourcePill {
|
|
11
|
+
var displayName: string;
|
|
12
|
+
}
|
|
13
|
+
export default SourcePill;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every slot class of `SourcePill`. HeroUI semantic colours plus the `ai-*`
|
|
3
|
+
* marker badge. Secondary text is `default-600`, because the pill's hover fill
|
|
4
|
+
* is `default-100` and `default-500` falls under 4.5:1 there.
|
|
5
|
+
*/
|
|
6
|
+
export declare const sourcePillClass: {
|
|
7
|
+
readonly base: "inline-flex h-auto min-h-8 min-w-0 max-w-full items-center justify-start gap-1.5 rounded-full border border-default-200 bg-content1 px-2.5 py-1 text-xs text-foreground";
|
|
8
|
+
readonly interactive: "data-[hover=true]:bg-default-100 data-[focus-visible=true]:outline-none data-[focus-visible=true]:ring-2 data-[focus-visible=true]:ring-ai-focus";
|
|
9
|
+
readonly disabled: "border-dashed";
|
|
10
|
+
/** The same badge as `CitationMarker`, so `[n]` in the text and on the pill match. */
|
|
11
|
+
readonly marker: "flex-none inline-flex items-center rounded border border-ai-border bg-ai-fill px-1 text-[0.6875rem] font-semibold leading-4 tabular-nums text-ai-accent";
|
|
12
|
+
readonly srOnly: "sr-only";
|
|
13
|
+
readonly icon: "flex-none text-default-600";
|
|
14
|
+
readonly kind: "flex-none font-semibold";
|
|
15
|
+
readonly title: "min-w-0 truncate";
|
|
16
|
+
readonly date: "flex-none tabular-nums text-default-600";
|
|
17
|
+
readonly reason: "flex flex-none items-center gap-1 text-default-600";
|
|
18
|
+
readonly separator: "flex-none text-default-600";
|
|
19
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface SourcePillProps {
|
|
2
|
+
/** What kind of source it is, e.g. "Email", "Call", "Case". */
|
|
3
|
+
kindLabel: string;
|
|
4
|
+
/** The source's short title. Long titles are cut with an ellipsis; the full text stays in the accessible name. */
|
|
5
|
+
title: string;
|
|
6
|
+
/** A preformatted date, e.g. "12 Oct". */
|
|
7
|
+
date?: string;
|
|
8
|
+
/** Iconify name for the kind. */
|
|
9
|
+
icon?: string;
|
|
10
|
+
/** The `[n]` this source answers to in the text, shown first so the two can be matched by eye. */
|
|
11
|
+
marker?: number | string;
|
|
12
|
+
/** Opens the source. Omitted, the pill is plain text. */
|
|
13
|
+
onOpen?: () => void;
|
|
14
|
+
/** The source can't be opened (deleted, no longer linked). Shown with `disabledReason`, never as a dead button. */
|
|
15
|
+
isDisabled?: boolean;
|
|
16
|
+
/** Why it can't be opened, shown next to it, e.g. "No longer available". */
|
|
17
|
+
disabledReason?: string;
|
|
18
|
+
/** Test id of the root; its parts add suffixes (see Test ids in the docs). Defaults to `source-pill`. */
|
|
19
|
+
'data-testid'?: string;
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veevarts/design-system",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "2.0.0-alpha.
|
|
4
|
+
"version": "2.0.0-alpha.63",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.js",
|
|
@@ -112,6 +112,7 @@
|
|
|
112
112
|
"@tiptap/pm": "^2.27.0",
|
|
113
113
|
"@tiptap/react": "^2.27.0",
|
|
114
114
|
"@tiptap/starter-kit": "^2.27.0",
|
|
115
|
+
"@types/markdown-it": "^14.1.2",
|
|
115
116
|
"@types/node": "^24.10.9",
|
|
116
117
|
"@types/react": "^19.2.5",
|
|
117
118
|
"@types/react-dom": "^19.2.3",
|
|
@@ -127,6 +128,7 @@
|
|
|
127
128
|
"globals": "^16.5.0",
|
|
128
129
|
"jsdom": "^27.4.0",
|
|
129
130
|
"lucide-react": "^0.562.0",
|
|
131
|
+
"markdown-it": "^14.1.0",
|
|
130
132
|
"playwright": "^1.56.1",
|
|
131
133
|
"postcss": "^8.5.6",
|
|
132
134
|
"react": "19.2.3",
|