@gravity-ui/aikit 2.26.0 → 2.26.1-beta.36dd40dc8a74f42d68064c5a5c131f7344ffe925.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/build/cjs/components/atoms/InlineCitation/InlineCitation.css +76 -0
- package/build/cjs/components/atoms/InlineCitation/InlineCitation.d.ts +24 -0
- package/build/cjs/components/atoms/InlineCitation/InlineCitation.js +136 -0
- package/build/cjs/components/atoms/InlineCitation/InlineCitation.js.map +1 -0
- package/build/cjs/components/atoms/InlineCitation/i18n/en.json +7 -0
- package/build/cjs/components/atoms/InlineCitation/i18n/index.d.ts +13 -0
- package/build/cjs/components/atoms/InlineCitation/i18n/index.js +10 -0
- package/build/cjs/components/atoms/InlineCitation/i18n/index.js.map +1 -0
- package/build/cjs/components/atoms/InlineCitation/i18n/ru.json +7 -0
- package/build/cjs/components/atoms/InlineCitation/index.d.ts +2 -1
- package/build/cjs/components/atoms/InlineCitation/index.js +3 -0
- package/build/cjs/components/atoms/InlineCitation/index.js.map +1 -1
- package/build/cjs/components/atoms/MarkdownRenderer/MdxPortals.js +3 -1
- package/build/cjs/components/atoms/MarkdownRenderer/MdxPortals.js.map +1 -1
- package/build/cjs/package.json +1 -1
- package/build/docs/INDEX.md +1 -0
- package/build/docs/components/atoms/InlineCitation.md +148 -0
- package/build/docs/guides/COMPONENTS.md +1 -1
- package/build/esm/components/atoms/InlineCitation/InlineCitation.css +76 -0
- package/build/esm/components/atoms/InlineCitation/InlineCitation.d.ts +24 -0
- package/build/esm/components/atoms/InlineCitation/InlineCitation.js +133 -0
- package/build/esm/components/atoms/InlineCitation/InlineCitation.js.map +1 -0
- package/build/esm/components/atoms/InlineCitation/i18n/en.json +7 -0
- package/build/esm/components/atoms/InlineCitation/i18n/index.d.ts +13 -0
- package/build/esm/components/atoms/InlineCitation/i18n/index.js +6 -0
- package/build/esm/components/atoms/InlineCitation/i18n/index.js.map +1 -0
- package/build/esm/components/atoms/InlineCitation/i18n/ru.json +7 -0
- package/build/esm/components/atoms/InlineCitation/index.d.ts +2 -1
- package/build/esm/components/atoms/InlineCitation/index.js +1 -1
- package/build/esm/components/atoms/InlineCitation/index.js.map +1 -1
- package/build/esm/components/atoms/MarkdownRenderer/MdxPortals.js +3 -1
- package/build/esm/components/atoms/MarkdownRenderer/MdxPortals.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/docs/COMPONENTS.md +1 -1
- package/llms-full.txt +1 -1
- package/llms.txt +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
.g-aikit-inline-citation {
|
|
2
|
+
display: inline;
|
|
3
|
+
}
|
|
4
|
+
.g-aikit-inline-citation__trigger {
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
gap: var(--g-spacing-1);
|
|
8
|
+
max-width: 100%;
|
|
9
|
+
padding: 0 calc(var(--g-spacing-1) + var(--g-spacing-half));
|
|
10
|
+
border: 0;
|
|
11
|
+
border-radius: var(--g-aikit-inline-citation-border-radius, var(--g-border-radius-m));
|
|
12
|
+
background: var(--g-color-base-generic);
|
|
13
|
+
color: var(--g-color-text-secondary);
|
|
14
|
+
font: inherit;
|
|
15
|
+
font-size: var(--g-aikit-inline-citation-font-size, var(--g-text-caption-2-font-size));
|
|
16
|
+
line-height: inherit;
|
|
17
|
+
vertical-align: baseline;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
}
|
|
20
|
+
.g-aikit-inline-citation__trigger:hover, .g-aikit-inline-citation__trigger[aria-expanded=true] {
|
|
21
|
+
background: var(--g-color-base-generic-hover);
|
|
22
|
+
color: var(--g-color-text-primary);
|
|
23
|
+
}
|
|
24
|
+
.g-aikit-inline-citation__trigger:focus-visible {
|
|
25
|
+
outline: 2px solid var(--g-color-line-focus);
|
|
26
|
+
outline-offset: 2px;
|
|
27
|
+
}
|
|
28
|
+
.g-aikit-inline-citation__label {
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
text-overflow: ellipsis;
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
}
|
|
33
|
+
.g-aikit-inline-citation__count {
|
|
34
|
+
flex-shrink: 0;
|
|
35
|
+
}
|
|
36
|
+
.g-aikit-inline-citation__card {
|
|
37
|
+
box-sizing: border-box;
|
|
38
|
+
width: var(--g-aikit-inline-citation-card-width, 320px);
|
|
39
|
+
max-width: calc(100vw - var(--g-spacing-8));
|
|
40
|
+
max-height: min(480px, 100dvh - var(--g-spacing-8));
|
|
41
|
+
overflow: auto;
|
|
42
|
+
padding: var(--g-spacing-3);
|
|
43
|
+
color: var(--g-color-text-primary);
|
|
44
|
+
overflow-wrap: anywhere;
|
|
45
|
+
}
|
|
46
|
+
.g-aikit-inline-citation__header {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: var(--g-spacing-1);
|
|
50
|
+
margin-bottom: var(--g-spacing-2);
|
|
51
|
+
}
|
|
52
|
+
.g-aikit-inline-citation__index, .g-aikit-inline-citation__url {
|
|
53
|
+
color: var(--g-color-text-secondary);
|
|
54
|
+
font-size: var(--g-text-caption-2-font-size);
|
|
55
|
+
line-height: var(--g-text-caption-2-line-height);
|
|
56
|
+
}
|
|
57
|
+
.g-aikit-inline-citation__close {
|
|
58
|
+
margin-inline-start: auto;
|
|
59
|
+
}
|
|
60
|
+
.g-aikit-inline-citation__source {
|
|
61
|
+
display: flex;
|
|
62
|
+
flex-direction: column;
|
|
63
|
+
gap: var(--g-spacing-2);
|
|
64
|
+
font-size: var(--g-text-body-1-font-size);
|
|
65
|
+
line-height: var(--g-text-body-1-line-height);
|
|
66
|
+
}
|
|
67
|
+
.g-aikit-inline-citation__title {
|
|
68
|
+
font-weight: var(--g-text-subheader-font-weight);
|
|
69
|
+
}
|
|
70
|
+
.g-aikit-inline-citation__quote {
|
|
71
|
+
margin: 0;
|
|
72
|
+
padding-inline-start: var(--g-spacing-3);
|
|
73
|
+
border-inline-start: 2px solid var(--g-color-line-generic);
|
|
74
|
+
color: var(--g-color-text-secondary);
|
|
75
|
+
white-space: pre-wrap;
|
|
76
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import "./InlineCitation.css";
|
|
3
|
+
export interface InlineCitationSource {
|
|
4
|
+
/** Stable identifier, unique within this citation. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Absolute HTTP or HTTPS URL. Other URLs are displayed without a link. */
|
|
7
|
+
url: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
quote?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface InlineCitationProps {
|
|
13
|
+
sources: readonly InlineCitationSource[];
|
|
14
|
+
/** Overrides the first source's hostname in the trigger. */
|
|
15
|
+
label?: string;
|
|
16
|
+
open?: boolean;
|
|
17
|
+
defaultOpen?: boolean;
|
|
18
|
+
onOpenChange?: (open: boolean) => void;
|
|
19
|
+
/** Call event.preventDefault() to override navigation. */
|
|
20
|
+
onSourceClick?: (source: InlineCitationSource, event: React.MouseEvent<HTMLAnchorElement>) => void;
|
|
21
|
+
className?: string;
|
|
22
|
+
qa?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function InlineCitation({ sources, label, open, defaultOpen, onOpenChange, onSourceClick, className, qa, }: InlineCitationProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InlineCitation = InlineCitation;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const icons_1 = require("@gravity-ui/icons");
|
|
7
|
+
const uikit_1 = require("@gravity-ui/uikit");
|
|
8
|
+
const cn_1 = require("../../../utils/cn.js");
|
|
9
|
+
const i18n_1 = require("./i18n/index.js");
|
|
10
|
+
require("./InlineCitation.css");
|
|
11
|
+
const b = (0, cn_1.block)('inline-citation');
|
|
12
|
+
function getSourceUrl(url) {
|
|
13
|
+
try {
|
|
14
|
+
const parsed = new URL(url);
|
|
15
|
+
return ['http:', 'https:'].includes(parsed.protocol) ? parsed : undefined;
|
|
16
|
+
}
|
|
17
|
+
catch (_a) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function InlineCitation({ sources, label, open, defaultOpen = false, onOpenChange, onSourceClick, className, qa, }) {
|
|
22
|
+
var _a, _b, _c, _d;
|
|
23
|
+
const [internalOpen, setInternalOpen] = (0, react_1.useState)(defaultOpen);
|
|
24
|
+
const [interactive, setInteractive] = (0, react_1.useState)(defaultOpen);
|
|
25
|
+
const [triggerElement, setTriggerElement] = (0, react_1.useState)(null);
|
|
26
|
+
const [selectedId, setSelectedId] = (0, react_1.useState)();
|
|
27
|
+
const [cardLayout, setCardLayout] = (0, react_1.useState)({ above: false, availableHeight: 480 });
|
|
28
|
+
const triggerRef = (0, react_1.useRef)(null);
|
|
29
|
+
const cardRef = (0, react_1.useRef)(null);
|
|
30
|
+
const timerRef = (0, react_1.useRef)();
|
|
31
|
+
const pinnedRef = (0, react_1.useRef)(defaultOpen);
|
|
32
|
+
const suppressFocusRef = (0, react_1.useRef)(false);
|
|
33
|
+
const cardId = (0, react_1.useId)();
|
|
34
|
+
const titleId = (0, react_1.useId)();
|
|
35
|
+
const setTriggerRef = (0, react_1.useCallback)((element) => {
|
|
36
|
+
triggerRef.current = element;
|
|
37
|
+
setTriggerElement(element);
|
|
38
|
+
}, []);
|
|
39
|
+
const isOpen = (open !== null && open !== void 0 ? open : internalOpen) && sources.length > 0;
|
|
40
|
+
const selectedIndex = Math.max(0, sources.findIndex((source) => source.id === selectedId));
|
|
41
|
+
const source = sources[selectedIndex];
|
|
42
|
+
const clearTimer = (0, react_1.useCallback)(() => {
|
|
43
|
+
clearTimeout(timerRef.current);
|
|
44
|
+
timerRef.current = undefined;
|
|
45
|
+
}, []);
|
|
46
|
+
(0, react_1.useEffect)(() => clearTimer, [clearTimer]);
|
|
47
|
+
(0, react_1.useEffect)(() => {
|
|
48
|
+
if (!isOpen || !triggerElement) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const updateLayout = () => {
|
|
52
|
+
const rect = triggerElement.getBoundingClientRect();
|
|
53
|
+
const above = rect.top;
|
|
54
|
+
const below = window.innerHeight - rect.bottom;
|
|
55
|
+
setCardLayout({
|
|
56
|
+
above: above > below,
|
|
57
|
+
availableHeight: Math.max(0, Math.max(above, below) - 16),
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
updateLayout();
|
|
61
|
+
window.addEventListener('resize', updateLayout);
|
|
62
|
+
window.addEventListener('scroll', updateLayout, true);
|
|
63
|
+
return () => {
|
|
64
|
+
window.removeEventListener('resize', updateLayout);
|
|
65
|
+
window.removeEventListener('scroll', updateLayout, true);
|
|
66
|
+
};
|
|
67
|
+
}, [isOpen, triggerElement]);
|
|
68
|
+
(0, react_1.useEffect)(() => {
|
|
69
|
+
if (!isOpen) {
|
|
70
|
+
clearTimer();
|
|
71
|
+
pinnedRef.current = false;
|
|
72
|
+
setInteractive(false);
|
|
73
|
+
}
|
|
74
|
+
}, [isOpen, clearTimer]);
|
|
75
|
+
const changeOpen = (nextOpen) => {
|
|
76
|
+
var _a;
|
|
77
|
+
clearTimer();
|
|
78
|
+
if (!nextOpen) {
|
|
79
|
+
pinnedRef.current = false;
|
|
80
|
+
suppressFocusRef.current = Boolean((_a = cardRef.current) === null || _a === void 0 ? void 0 : _a.contains(document.activeElement));
|
|
81
|
+
}
|
|
82
|
+
if (open === undefined) {
|
|
83
|
+
setInternalOpen(nextOpen);
|
|
84
|
+
}
|
|
85
|
+
if (nextOpen !== isOpen) {
|
|
86
|
+
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(nextOpen);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const scheduleClose = () => {
|
|
90
|
+
clearTimer();
|
|
91
|
+
if (pinnedRef.current) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
timerRef.current = setTimeout(() => {
|
|
95
|
+
var _a;
|
|
96
|
+
const activeElement = document.activeElement;
|
|
97
|
+
if (activeElement !== triggerRef.current && !((_a = cardRef.current) === null || _a === void 0 ? void 0 : _a.contains(activeElement))) {
|
|
98
|
+
changeOpen(false);
|
|
99
|
+
}
|
|
100
|
+
}, 200);
|
|
101
|
+
};
|
|
102
|
+
const handlePointerEnter = (event) => {
|
|
103
|
+
clearTimer();
|
|
104
|
+
if (event.pointerType === 'mouse' && !isOpen) {
|
|
105
|
+
timerRef.current = setTimeout(() => changeOpen(true), 150);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
if (!source) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const hostname = (_a = getSourceUrl(sources[0].url)) === null || _a === void 0 ? void 0 : _a.hostname;
|
|
112
|
+
const triggerLabel = (_c = (_b = label !== null && label !== void 0 ? label : hostname) !== null && _b !== void 0 ? _b : sources[0].title) !== null && _c !== void 0 ? _c : (0, i18n_1.i18n)('source');
|
|
113
|
+
const sourceUrl = getSourceUrl(source.url);
|
|
114
|
+
const title = source.title || (sourceUrl === null || sourceUrl === void 0 ? void 0 : sourceUrl.hostname) || (0, i18n_1.i18n)('source');
|
|
115
|
+
return ((0, jsx_runtime_1.jsxs)("span", { className: b(null, className), "data-qa": qa, children: [(0, jsx_runtime_1.jsxs)("button", { ref: setTriggerRef, type: "button", className: b('trigger'), "aria-label": (0, i18n_1.i18n)('show-sources', { count: sources.length }), "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-controls": isOpen ? cardId : undefined, onPointerEnter: handlePointerEnter, onPointerLeave: scheduleClose, onFocus: () => {
|
|
116
|
+
if (suppressFocusRef.current) {
|
|
117
|
+
suppressFocusRef.current = false;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
changeOpen(true);
|
|
121
|
+
}
|
|
122
|
+
}, onBlur: scheduleClose, onClick: () => {
|
|
123
|
+
if (isOpen && pinnedRef.current) {
|
|
124
|
+
changeOpen(false);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
pinnedRef.current = true;
|
|
128
|
+
setInteractive(true);
|
|
129
|
+
changeOpen(true);
|
|
130
|
+
}
|
|
131
|
+
}, children: [(0, jsx_runtime_1.jsx)("span", { className: b('label'), children: triggerLabel }), sources.length > 1 && (0, jsx_runtime_1.jsxs)("span", { className: b('count'), children: ["+", sources.length - 1] })] }), (0, jsx_runtime_1.jsx)(uikit_1.Popup, { open: isOpen, anchorElement: triggerElement, placement: cardLayout.above ? 'top-start' : 'bottom-start', onOpenChange: changeOpen, className: b('popup', className), initialFocus: interactive ? 0 : -1, returnFocus: interactive, qa: qa ? `${qa}-popup` : undefined, children: (0, jsx_runtime_1.jsxs)("div", { ref: cardRef, role: "dialog", id: cardId, "aria-labelledby": titleId, className: b('card'), style: { maxHeight: `min(480px, ${cardLayout.availableHeight}px)` }, onPointerEnter: clearTimer, onPointerLeave: scheduleClose, onFocus: () => {
|
|
132
|
+
clearTimer();
|
|
133
|
+
setInteractive(true);
|
|
134
|
+
}, onBlur: scheduleClose, children: [(0, jsx_runtime_1.jsxs)("div", { className: b('header'), children: [sources.length > 1 && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(uikit_1.Button, { view: "flat-secondary", size: "s", disabled: selectedIndex === 0, "aria-label": (0, i18n_1.i18n)('previous'), onClick: () => setSelectedId(sources[selectedIndex - 1].id), children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.ChevronLeft, size: 16 }) }), (0, jsx_runtime_1.jsxs)("span", { className: b('index'), "aria-live": "polite", "aria-atomic": "true", children: [selectedIndex + 1, " / ", sources.length] }), (0, jsx_runtime_1.jsx)(uikit_1.Button, { view: "flat-secondary", size: "s", disabled: selectedIndex === sources.length - 1, "aria-label": (0, i18n_1.i18n)('next'), onClick: () => setSelectedId(sources[selectedIndex + 1].id), children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.ChevronRight, size: 16 }) })] })), (0, jsx_runtime_1.jsx)(uikit_1.Button, { view: "flat-secondary", size: "s", className: b('close'), "aria-label": (0, i18n_1.i18n)('close'), onClick: () => changeOpen(false), children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Xmark, size: 16 }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: b('source'), children: [(0, jsx_runtime_1.jsx)("div", { id: titleId, className: b('title'), children: sourceUrl ? ((0, jsx_runtime_1.jsx)(uikit_1.Link, { href: source.url, target: "_blank", rel: "noopener noreferrer", onClick: (event) => onSourceClick === null || onSourceClick === void 0 ? void 0 : onSourceClick(source, event), children: title })) : (title) }), (0, jsx_runtime_1.jsx)("div", { className: b('url'), children: (_d = sourceUrl === null || sourceUrl === void 0 ? void 0 : sourceUrl.hostname) !== null && _d !== void 0 ? _d : source.url }), source.description && ((0, jsx_runtime_1.jsx)("div", { className: b('description'), children: source.description })), source.quote && ((0, jsx_runtime_1.jsx)("blockquote", { className: b('quote'), children: source.quote }))] }, source.id)] }) })] }));
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=InlineCitation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InlineCitation.js","sourceRoot":"../../../../../src","sources":["components/atoms/InlineCitation/InlineCitation.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {useCallback, useEffect, useId, useRef, useState} from 'react';\n\nimport {ChevronLeft, ChevronRight, Xmark} from '@gravity-ui/icons';\nimport {Button, Icon, Link, Popup} from '@gravity-ui/uikit';\n\nimport {block} from '../../../utils/cn';\n\nimport {i18n} from './i18n';\n\nimport './InlineCitation.scss';\n\nconst b = block('inline-citation');\n\nexport interface InlineCitationSource {\n /** Stable identifier, unique within this citation. */\n id: string;\n /** Absolute HTTP or HTTPS URL. Other URLs are displayed without a link. */\n url: string;\n title?: string;\n description?: string;\n quote?: string;\n}\n\nexport interface InlineCitationProps {\n sources: readonly InlineCitationSource[];\n /** Overrides the first source's hostname in the trigger. */\n label?: string;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Call event.preventDefault() to override navigation. */\n onSourceClick?: (\n source: InlineCitationSource,\n event: React.MouseEvent<HTMLAnchorElement>,\n ) => void;\n className?: string;\n qa?: string;\n}\n\nfunction getSourceUrl(url: string) {\n try {\n const parsed = new URL(url);\n return ['http:', 'https:'].includes(parsed.protocol) ? parsed : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function InlineCitation({\n sources,\n label,\n open,\n defaultOpen = false,\n onOpenChange,\n onSourceClick,\n className,\n qa,\n}: InlineCitationProps) {\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const [interactive, setInteractive] = useState(defaultOpen);\n const [triggerElement, setTriggerElement] = useState<HTMLButtonElement | null>(null);\n const [selectedId, setSelectedId] = useState<string>();\n const [cardLayout, setCardLayout] = useState({above: false, availableHeight: 480});\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n const cardRef = useRef<HTMLDivElement>(null);\n const timerRef = useRef<ReturnType<typeof setTimeout>>();\n const pinnedRef = useRef(defaultOpen);\n const suppressFocusRef = useRef(false);\n const cardId = useId();\n const titleId = useId();\n const setTriggerRef = useCallback((element: HTMLButtonElement | null) => {\n triggerRef.current = element;\n setTriggerElement(element);\n }, []);\n const isOpen = (open ?? internalOpen) && sources.length > 0;\n const selectedIndex = Math.max(\n 0,\n sources.findIndex((source) => source.id === selectedId),\n );\n const source = sources[selectedIndex];\n\n const clearTimer = useCallback(() => {\n clearTimeout(timerRef.current);\n timerRef.current = undefined;\n }, []);\n\n useEffect(() => clearTimer, [clearTimer]);\n\n useEffect(() => {\n if (!isOpen || !triggerElement) {\n return undefined;\n }\n const updateLayout = () => {\n const rect = triggerElement.getBoundingClientRect();\n const above = rect.top;\n const below = window.innerHeight - rect.bottom;\n setCardLayout({\n above: above > below,\n availableHeight: Math.max(0, Math.max(above, below) - 16),\n });\n };\n updateLayout();\n window.addEventListener('resize', updateLayout);\n window.addEventListener('scroll', updateLayout, true);\n return () => {\n window.removeEventListener('resize', updateLayout);\n window.removeEventListener('scroll', updateLayout, true);\n };\n }, [isOpen, triggerElement]);\n\n useEffect(() => {\n if (!isOpen) {\n clearTimer();\n pinnedRef.current = false;\n setInteractive(false);\n }\n }, [isOpen, clearTimer]);\n\n const changeOpen = (nextOpen: boolean) => {\n clearTimer();\n if (!nextOpen) {\n pinnedRef.current = false;\n suppressFocusRef.current = Boolean(cardRef.current?.contains(document.activeElement));\n }\n if (open === undefined) {\n setInternalOpen(nextOpen);\n }\n if (nextOpen !== isOpen) {\n onOpenChange?.(nextOpen);\n }\n };\n\n const scheduleClose = () => {\n clearTimer();\n if (pinnedRef.current) {\n return;\n }\n timerRef.current = setTimeout(() => {\n const activeElement = document.activeElement;\n if (activeElement !== triggerRef.current && !cardRef.current?.contains(activeElement)) {\n changeOpen(false);\n }\n }, 200);\n };\n\n const handlePointerEnter = (event: React.PointerEvent) => {\n clearTimer();\n if (event.pointerType === 'mouse' && !isOpen) {\n timerRef.current = setTimeout(() => changeOpen(true), 150);\n }\n };\n\n if (!source) {\n return null;\n }\n\n const hostname = getSourceUrl(sources[0].url)?.hostname;\n const triggerLabel = label ?? hostname ?? sources[0].title ?? i18n('source');\n const sourceUrl = getSourceUrl(source.url);\n const title = source.title || sourceUrl?.hostname || i18n('source');\n\n return (\n <span className={b(null, className)} data-qa={qa}>\n <button\n ref={setTriggerRef}\n type=\"button\"\n className={b('trigger')}\n aria-label={i18n('show-sources', {count: sources.length})}\n aria-haspopup=\"dialog\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? cardId : undefined}\n onPointerEnter={handlePointerEnter}\n onPointerLeave={scheduleClose}\n onFocus={() => {\n if (suppressFocusRef.current) {\n suppressFocusRef.current = false;\n } else {\n changeOpen(true);\n }\n }}\n onBlur={scheduleClose}\n onClick={() => {\n if (isOpen && pinnedRef.current) {\n changeOpen(false);\n } else {\n pinnedRef.current = true;\n setInteractive(true);\n changeOpen(true);\n }\n }}\n >\n <span className={b('label')}>{triggerLabel}</span>\n {sources.length > 1 && <span className={b('count')}>+{sources.length - 1}</span>}\n </button>\n <Popup\n open={isOpen}\n anchorElement={triggerElement}\n placement={cardLayout.above ? 'top-start' : 'bottom-start'}\n onOpenChange={changeOpen}\n className={b('popup', className)}\n initialFocus={interactive ? 0 : -1}\n returnFocus={interactive}\n qa={qa ? `${qa}-popup` : undefined}\n >\n <div\n ref={cardRef}\n role=\"dialog\"\n id={cardId}\n aria-labelledby={titleId}\n className={b('card')}\n style={{maxHeight: `min(480px, ${cardLayout.availableHeight}px)`}}\n onPointerEnter={clearTimer}\n onPointerLeave={scheduleClose}\n onFocus={() => {\n clearTimer();\n setInteractive(true);\n }}\n onBlur={scheduleClose}\n >\n <div className={b('header')}>\n {sources.length > 1 && (\n <>\n <Button\n view=\"flat-secondary\"\n size=\"s\"\n disabled={selectedIndex === 0}\n aria-label={i18n('previous')}\n onClick={() => setSelectedId(sources[selectedIndex - 1].id)}\n >\n <Icon data={ChevronLeft} size={16} />\n </Button>\n <span className={b('index')} aria-live=\"polite\" aria-atomic=\"true\">\n {selectedIndex + 1} / {sources.length}\n </span>\n <Button\n view=\"flat-secondary\"\n size=\"s\"\n disabled={selectedIndex === sources.length - 1}\n aria-label={i18n('next')}\n onClick={() => setSelectedId(sources[selectedIndex + 1].id)}\n >\n <Icon data={ChevronRight} size={16} />\n </Button>\n </>\n )}\n <Button\n view=\"flat-secondary\"\n size=\"s\"\n className={b('close')}\n aria-label={i18n('close')}\n onClick={() => changeOpen(false)}\n >\n <Icon data={Xmark} size={16} />\n </Button>\n </div>\n <div className={b('source')} key={source.id}>\n <div id={titleId} className={b('title')}>\n {sourceUrl ? (\n <Link\n href={source.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(event) => onSourceClick?.(source, event)}\n >\n {title}\n </Link>\n ) : (\n title\n )}\n </div>\n <div className={b('url')}>{sourceUrl?.hostname ?? source.url}</div>\n {source.description && (\n <div className={b('description')}>{source.description}</div>\n )}\n {source.quote && (\n <blockquote className={b('quote')}>{source.quote}</blockquote>\n )}\n </div>\n </div>\n </Popup>\n </span>\n );\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const i18n: ((key: "source" | "next" | "previous" | "close" | "show-sources", params?: import("@gravity-ui/i18n").Params) => string) & {
|
|
2
|
+
Translation: import("react").ComponentType<{
|
|
3
|
+
children: (props: {
|
|
4
|
+
t: (key: "source" | "next" | "previous" | "close" | "show-sources", params?: import("@gravity-ui/i18n").Params) => string;
|
|
5
|
+
}) => React.ReactNode;
|
|
6
|
+
}>;
|
|
7
|
+
useTranslation: () => {
|
|
8
|
+
t: (key: "source" | "next" | "previous" | "close" | "show-sources", params?: import("@gravity-ui/i18n").Params) => string;
|
|
9
|
+
};
|
|
10
|
+
keysetData: {
|
|
11
|
+
"g-aikit-InlineCitation": Record<"source" | "next" | "previous" | "close" | "show-sources", import("@gravity-ui/i18n").KeyData>;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.i18n = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const i18n_1 = require("@gravity-ui/uikit/i18n");
|
|
6
|
+
const cn_1 = require("../../../../utils/cn.js");
|
|
7
|
+
const en_json_1 = tslib_1.__importDefault(require("./en.json"));
|
|
8
|
+
const ru_json_1 = tslib_1.__importDefault(require("./ru.json"));
|
|
9
|
+
exports.i18n = (0, i18n_1.addComponentKeysets)({ en: en_json_1.default, ru: ru_json_1.default }, `${cn_1.NAMESPACE}InlineCitation`);
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"../../../../../../src","sources":["components/atoms/InlineCitation/i18n/index.ts"],"names":[],"mappings":";;;;AAAA,iDAA2D;AAE3D,gDAA+C;AAE/C,gEAA2B;AAC3B,gEAA2B;AAEd,QAAA,IAAI,GAAG,IAAA,0BAAmB,EAAC,EAAC,EAAE,EAAF,iBAAE,EAAE,EAAE,EAAF,iBAAE,EAAC,EAAE,GAAG,cAAS,gBAAgB,CAAC,CAAC","sourcesContent":["import {addComponentKeysets} from '@gravity-ui/uikit/i18n';\n\nimport {NAMESPACE} from '../../../../utils/cn';\n\nimport en from './en.json';\nimport ru from './ru.json';\n\nexport const i18n = addComponentKeysets({en, ru}, `${NAMESPACE}InlineCitation`);\n"]}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export {};
|
|
1
|
+
export { InlineCitation } from "./InlineCitation.js";
|
|
2
|
+
export type { InlineCitationProps, InlineCitationSource } from "./InlineCitation.js";
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InlineCitation = void 0;
|
|
4
|
+
var InlineCitation_1 = require("./InlineCitation.js");
|
|
5
|
+
Object.defineProperty(exports, "InlineCitation", { enumerable: true, get: function () { return InlineCitation_1.InlineCitation; } });
|
|
3
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["components/atoms/InlineCitation/index.ts"],"names":[],"mappings":"","sourcesContent":["export {};\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["components/atoms/InlineCitation/index.ts"],"names":[],"mappings":";;;AAAA,sDAAgD;AAAxC,gHAAA,cAAc,OAAA","sourcesContent":["export {InlineCitation} from './InlineCitation';\nexport type {InlineCitationProps, InlineCitationSource} from './InlineCitation';\n"]}
|
|
@@ -8,6 +8,8 @@ const mdx_extension_1 = require("@diplodoc/mdx-extension");
|
|
|
8
8
|
const runtime = tslib_1.__importStar(require("react/jsx-runtime"));
|
|
9
9
|
const MdxContext_1 = require("./MdxContext.js");
|
|
10
10
|
const asyncExecuteCode_1 = require("./asyncExecuteCode.js");
|
|
11
|
+
// Keep the MDX mounting effect stable when a child creates a React portal.
|
|
12
|
+
const contextList = [MdxContext_1.MdxDataContext];
|
|
11
13
|
function useMdxComponentLoader(mdxArtifacts, nonce) {
|
|
12
14
|
const idMdx = mdxArtifacts === null || mdxArtifacts === void 0 ? void 0 : mdxArtifacts.idMdx;
|
|
13
15
|
const resolvedNonce = (0, asyncExecuteCode_1.resolveCspNonce)(nonce);
|
|
@@ -56,7 +58,7 @@ function MdxPortals({ refCtr, html, components, mdxArtifacts, nonce, mdxContext,
|
|
|
56
58
|
html,
|
|
57
59
|
components,
|
|
58
60
|
mdxArtifacts,
|
|
59
|
-
contextList
|
|
61
|
+
contextList,
|
|
60
62
|
idMdxComponentLoader,
|
|
61
63
|
});
|
|
62
64
|
return (0, jsx_runtime_1.jsx)(MdxContext_1.MdxDataContext.Provider, { value: mdxContext, children: portals });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MdxPortals.js","sourceRoot":"../../../../../src","sources":["components/atoms/MarkdownRenderer/MdxPortals.tsx"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"MdxPortals.js","sourceRoot":"../../../../../src","sources":["components/atoms/MarkdownRenderer/MdxPortals.tsx"],"names":[],"mappings":";;AAyFA,gCAmBC;;;AA5GD,iCAA0C;AAG1C,2DAA+C;AAG/C,mEAA6C;AAE7C,gDAA4C;AAC5C,4DAAqE;AAErE,2EAA2E;AAC3E,MAAM,WAAW,GAAG,CAAC,2BAAc,CAAC,CAAC;AAOrC,SAAS,qBAAqB,CAAC,YAAsC,EAAE,KAAc;IACjF,MAAM,KAAK,GAAG,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,KAAK,CAAC;IAClC,MAAM,aAAa,GAAG,IAAA,kCAAe,EAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,IAAA,gBAAQ,EAAiB,EAAC,SAAS,EAAE,KAAK,EAAC,CAAC,CAAC;IAEvE,IAAA,iBAAS,EAAC,GAAG,EAAE;QACX,IAAI,CAAC,aAAa,EAAE,CAAC;YACjB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC;QAC3B,CAAC;QAED,IAAI,QAAQ,GAAG,IAAI,CAAC;QAEpB,CAAC,KAAK,IAAI,EAAE;YACR,IAAI,CAAC;gBACD,MAAM,IAAI,GAAkD,EAAE,CAAC;gBAE/D,KAAK,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,EAAE,CAAC,EAAE,CAAC;oBAC3D,MAAM,EAAE,GAAG,MAAM,IAAA,mCAAgB,EAC7B,IAAI,EACJ,aAAa,CAChB,CAAC;oBAEF,IAAI,CAAC,QAAQ,EAAE,CAAC;wBACZ,OAAO;oBACX,CAAC;oBAED,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC;gBAC3C,CAAC;gBAED,IAAI,QAAQ,EAAE,CAAC;oBACX,QAAQ,CAAC,EAAC,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAC,CAAC,CAAC;gBACnE,CAAC;YACL,CAAC;YAAC,WAAM,CAAC;gBACL,IAAI,QAAQ,EAAE,CAAC;oBACX,QAAQ,CAAC,EAAC,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,EAAC,CAAC,CAAC;gBAC9D,CAAC;YACL,CAAC;QACL,CAAC,CAAC,EAAE,CAAC;QAEL,OAAO,GAAG,EAAE;YACR,QAAQ,GAAG,KAAK,CAAC;QACrB,CAAC,CAAC;IACN,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC;IAE3B,IAAI,CAAC,aAAa,EAAE,CAAC;QACjB,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,IAAI,KAAK,CAAC,KAAK,KAAK,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,aAAa,EAAE,CAAC;QACzD,OAAO,EAAC,SAAS,EAAE,KAAK,EAAC,CAAC;IAC9B,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC;AAiBD,SAAgB,UAAU,CAAC,EACvB,MAAM,EACN,IAAI,EACJ,UAAU,EACV,YAAY,EACZ,KAAK,EACL,UAAU,GACI;IACd,MAAM,oBAAoB,GAAG,qBAAqB,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,IAAA,sBAAM,EAAC;QACnB,MAAM;QACN,IAAI;QACJ,UAAU;QACV,YAAY;QACZ,WAAW;QACX,oBAAoB;KACvB,CAAC,CAAC;IAEH,OAAO,uBAAC,2BAAc,CAAC,QAAQ,IAAC,KAAK,EAAE,UAAU,YAAG,OAAO,GAA2B,CAAC;AAC3F,CAAC;AAED,UAAU,CAAC,WAAW,GAAG,YAAY,CAAC","sourcesContent":["import {useEffect, useState} from 'react';\nimport type {RefObject} from 'react';\n\nimport {useMdx} from '@diplodoc/mdx-extension';\nimport type {IdMdxComponentLoader, MdxArtifacts} from '@diplodoc/mdx-extension';\nimport type {MDXComponents, MDXModule, MDXProps} from 'mdx/types';\nimport * as runtime from 'react/jsx-runtime';\n\nimport {MdxDataContext} from './MdxContext';\nimport {asyncExecuteCode, resolveCspNonce} from './asyncExecuteCode';\n\n// Keep the MDX mounting effect stable when a child creates a React portal.\nconst contextList = [MdxDataContext];\n\ninterface MdxLoaderState extends IdMdxComponentLoader {\n idMdx?: Record<string, string>;\n nonce?: string;\n}\n\nfunction useMdxComponentLoader(mdxArtifacts: MdxArtifacts | undefined, nonce?: string) {\n const idMdx = mdxArtifacts?.idMdx;\n const resolvedNonce = resolveCspNonce(nonce);\n const [state, setState] = useState<MdxLoaderState>({isSuccess: false});\n\n useEffect(() => {\n if (!resolvedNonce) {\n return () => undefined;\n }\n\n let isActive = true;\n\n (async () => {\n try {\n const data: Record<string, React.ComponentType<MDXProps>> = {};\n\n for (const [artifactId, code] of Object.entries(idMdx ?? {})) {\n const fn = await asyncExecuteCode<(jsxRuntime: typeof runtime) => MDXModule>(\n code,\n resolvedNonce,\n );\n\n if (!isActive) {\n return;\n }\n\n data[artifactId] = fn(runtime).default;\n }\n\n if (isActive) {\n setState({idMdx, nonce: resolvedNonce, data, isSuccess: true});\n }\n } catch {\n if (isActive) {\n setState({idMdx, nonce: resolvedNonce, isSuccess: false});\n }\n }\n })();\n\n return () => {\n isActive = false;\n };\n }, [idMdx, resolvedNonce]);\n\n if (!resolvedNonce) {\n return undefined;\n }\n\n if (state.idMdx !== idMdx || state.nonce !== resolvedNonce) {\n return {isSuccess: false};\n }\n\n return state;\n}\n\nexport interface MdxPortalsProps {\n /** Ref to the container element into which the rendered HTML is injected. */\n refCtr: RefObject<HTMLDivElement | null>;\n /** HTML produced by the markdown transform for the current content. */\n html: string;\n /** Map of components rendered from embedded MDX/JSX in the markdown content. */\n components?: MDXComponents;\n /** Artifacts collected by the MDX transform, required to hydrate the components. */\n mdxArtifacts?: MdxArtifacts;\n /** CSP nonce applied to compiled MDX scripts. Defaults to a nonce found in the document. */\n nonce?: string;\n /** Arbitrary value exposed to the MDX components through {@link MdxDataContext}. */\n mdxContext?: Record<string, unknown>;\n}\n\nexport function MdxPortals({\n refCtr,\n html,\n components,\n mdxArtifacts,\n nonce,\n mdxContext,\n}: MdxPortalsProps) {\n const idMdxComponentLoader = useMdxComponentLoader(mdxArtifacts, nonce);\n const portals = useMdx({\n refCtr,\n html,\n components,\n mdxArtifacts,\n contextList,\n idMdxComponentLoader,\n });\n\n return <MdxDataContext.Provider value={mdxContext}>{portals}</MdxDataContext.Provider>;\n}\n\nMdxPortals.displayName = 'MdxPortals';\n"]}
|
package/build/cjs/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"2.26.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
|
|
1
|
+
{"version":"2.26.1-beta.36dd40dc8a74f42d68064c5a5c131f7344ffe925.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
|
package/build/docs/INDEX.md
CHANGED
|
@@ -99,6 +99,7 @@ function App() {
|
|
|
99
99
|
- [atoms/DiffStat](./components/atoms/DiffStat.md) — A compact component that displays diff statistics showing the number of added and deleted lines.
|
|
100
100
|
- [atoms/Disclaimer](./components/atoms/Disclaimer.md) — A Disclaimer component displays informational or warning messages
|
|
101
101
|
- [atoms/FileIcon](./components/atoms/FileIcon.md) — Displays an icon representing a file based on its MIME type or file name extension.
|
|
102
|
+
- [atoms/InlineCitation](./components/atoms/InlineCitation.md) — A compact inline source badge with an interactive source preview card.
|
|
102
103
|
- [atoms/IntersectionContainer](./components/atoms/IntersectionContainer.md) — Wrapper component for Intersection Observer API. Used for automatic loading of previous messages when scrolling up in MessageList.
|
|
103
104
|
- [atoms/Loader](./components/atoms/Loader.md) — A Loader visualizes loading state
|
|
104
105
|
- [atoms/MarkdownRenderer](./components/atoms/MarkdownRenderer.md) — A MarkdownRenderer component for rendering Yandex Flavored Markdown (YFM) content to HTML.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# InlineCitation
|
|
2
|
+
|
|
3
|
+
A compact inline source badge with an interactive source preview card.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Inline badge**: Displays the first source's hostname and the number of additional sources.
|
|
8
|
+
- **Source previews**: Shows a title, hostname, optional description, and quoted excerpt.
|
|
9
|
+
- **Multiple sources**: Previous/next buttons and a live position counter.
|
|
10
|
+
- **Accessible interactions**: Supports hover, keyboard focus, click, touch, and Escape.
|
|
11
|
+
- **Controlled visibility**: Supports both internal state and externally controlled opening.
|
|
12
|
+
- **Gravity themes**: Uses Gravity UI Popup, controls, links, and theme colors.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import {InlineCitation, type InlineCitationSource} from '@gravity-ui/aikit/InlineCitation';
|
|
18
|
+
|
|
19
|
+
const sources: InlineCitationSource[] = [
|
|
20
|
+
{
|
|
21
|
+
id: 'documentation',
|
|
22
|
+
url: 'https://gravity-ui.com',
|
|
23
|
+
title: 'Gravity UI documentation',
|
|
24
|
+
description: 'Components for building modern interfaces.',
|
|
25
|
+
quote: 'An open source design system.',
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
<p>
|
|
30
|
+
Read more in the documentation <InlineCitation sources={sources} />.
|
|
31
|
+
</p>;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Override the badge label or handle navigation yourself:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
<InlineCitation
|
|
38
|
+
sources={sources}
|
|
39
|
+
label="Documentation"
|
|
40
|
+
onSourceClick={(source, event) => {
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
openSourcePanel(source.id);
|
|
43
|
+
}}
|
|
44
|
+
/>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Control visibility from the host application:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
const [open, setOpen] = useState(false);
|
|
51
|
+
|
|
52
|
+
<InlineCitation sources={sources} open={open} onOpenChange={setOpen} />;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Markdown messages
|
|
56
|
+
|
|
57
|
+
The component consumes structured source data; it does not parse citation markers in Markdown.
|
|
58
|
+
Use the existing MDX integration to resolve source identifiers from each message's context:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import {withPortal} from '@diplodoc/mdx-extension';
|
|
62
|
+
import {InlineCitation, type InlineCitationSource} from '@gravity-ui/aikit/InlineCitation';
|
|
63
|
+
import {MarkdownRenderer, useMdxContext} from '@gravity-ui/aikit/MarkdownRenderer';
|
|
64
|
+
|
|
65
|
+
function Citation({sourceId}: {sourceId?: string}) {
|
|
66
|
+
const context = useMdxContext<{sources: InlineCitationSource[]}>();
|
|
67
|
+
return (
|
|
68
|
+
<InlineCitation sources={context?.sources.filter((source) => source.id === sourceId) ?? []} />
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
<MarkdownRenderer
|
|
73
|
+
content={'Read the documentation <Citation sourceId="documentation" />.'}
|
|
74
|
+
mdxOptions={{components: {Citation: withPortal(Citation)}}}
|
|
75
|
+
mdxContext={{sources}}
|
|
76
|
+
/>;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
See [MarkdownRenderer](./MarkdownRenderer.md) for MDX configuration and CSP requirements.
|
|
80
|
+
Use `withPortal` so the embedded citation inherits the application's Gravity `ThemeProvider`.
|
|
81
|
+
|
|
82
|
+
## Props
|
|
83
|
+
|
|
84
|
+
| Prop | Type | Required | Default | Description |
|
|
85
|
+
| --------------- | ------------------------------------------------------------------------------------ | -------- | -------------- | ------------------------------------------------------------------------------------------- |
|
|
86
|
+
| `sources` | `readonly InlineCitationSource[]` | ✓ | - | Sources with unique, stable identifiers. An empty array renders nothing. |
|
|
87
|
+
| `label` | `string` | - | First hostname | Overrides the badge text; the additional source count is still displayed. |
|
|
88
|
+
| `open` | `boolean` | - | - | Controls card visibility. Pair with `onOpenChange` to accept interaction requests. |
|
|
89
|
+
| `defaultOpen` | `boolean` | - | `false` | Initial visibility in uncontrolled mode. |
|
|
90
|
+
| `onOpenChange` | `(open: boolean) => void` | - | - | Called when an interaction requests a visibility change. |
|
|
91
|
+
| `onSourceClick` | `(source: InlineCitationSource, event: React.MouseEvent<HTMLAnchorElement>) => void` | - | - | Called when the source link is activated. Prevent the default event to override navigation. |
|
|
92
|
+
| `className` | `string` | - | - | Additional class applied to both the inline wrapper and portaled popup. |
|
|
93
|
+
| `qa` | `string` | - | - | QA identifier. The popup uses the identifier with a `-popup` suffix. |
|
|
94
|
+
|
|
95
|
+
### InlineCitationSource
|
|
96
|
+
|
|
97
|
+
| Field | Type | Required | Default | Description |
|
|
98
|
+
| ------------- | -------- | -------- | -------- | ----------------------------------------------------------------------------------------------- |
|
|
99
|
+
| `id` | `string` | ✓ | - | Unique identifier; preserves selection when sources are reordered. |
|
|
100
|
+
| `url` | `string` | ✓ | - | Absolute HTTP(S) URL. Invalid URLs and unsupported protocols render as text without navigation. |
|
|
101
|
+
| `title` | `string` | - | Hostname | Source link title. Falls back to the localized “Source” label if the URL is invalid. |
|
|
102
|
+
| `description` | `string` | - | - | Plain text source description. |
|
|
103
|
+
| `quote` | `string` | - | - | Plain text excerpt, preserving line breaks. |
|
|
104
|
+
|
|
105
|
+
Metadata is supplied by the host application. The component does not fetch pages, favicons, or previews.
|
|
106
|
+
Links open in a new tab with `noopener noreferrer`. If the selected source disappears, selection falls
|
|
107
|
+
back to the first available source. Navigation stops at the first and last sources.
|
|
108
|
+
|
|
109
|
+
## Keyboard and pointer support
|
|
110
|
+
|
|
111
|
+
- Hover with a mouse or focus the badge to preview sources without moving focus.
|
|
112
|
+
- Click the badge or press Enter/Space to keep the card open for interaction.
|
|
113
|
+
- Tab moves through the card's available controls and source link.
|
|
114
|
+
- Escape, the close button, or a click outside dismisses the card.
|
|
115
|
+
- Closing an interactively opened card returns focus to its badge when appropriate.
|
|
116
|
+
- Moving the pointer from the badge into the card keeps the preview open. A short close delay allows crossing the gap.
|
|
117
|
+
|
|
118
|
+
## Styling
|
|
119
|
+
|
|
120
|
+
| Variable | Default | Description |
|
|
121
|
+
| ----------------------------------------- | ------------------------------ | ---------------------------------------------- |
|
|
122
|
+
| `--g-aikit-inline-citation-card-width` | `320px` | Preferred card width, limited by the viewport. |
|
|
123
|
+
| `--g-aikit-inline-citation-border-radius` | `--g-border-radius-m` | Badge corner radius. |
|
|
124
|
+
| `--g-aikit-inline-citation-font-size` | `--g-text-caption-2-font-size` | Badge font size. |
|
|
125
|
+
| `--g-color-base-generic` | Gravity theme | Badge background. |
|
|
126
|
+
| `--g-color-base-generic-hover` | Gravity theme | Hovered and open badge background. |
|
|
127
|
+
| `--g-color-text-primary` | Gravity theme | Card text and active badge color. |
|
|
128
|
+
| `--g-color-text-secondary` | Gravity theme | Badge, hostname, counter, and quote color. |
|
|
129
|
+
| `--g-color-line-focus` | Gravity theme | Badge focus outline. |
|
|
130
|
+
| `--g-color-line-generic` | Gravity theme | Quote border color. |
|
|
131
|
+
|
|
132
|
+
Spacing follows Gravity tokens: `--g-spacing-half`, `--g-spacing-1`, `--g-spacing-2`,
|
|
133
|
+
`--g-spacing-3`, and `--g-spacing-8` control badge padding, gaps, card padding, and viewport
|
|
134
|
+
insets. Typography uses `--g-text-caption-2-font-size`, `--g-text-caption-2-line-height`,
|
|
135
|
+
`--g-text-body-1-font-size`, `--g-text-body-1-line-height`, and
|
|
136
|
+
`--g-text-subheader-font-weight`. The badge radius defaults to `--g-border-radius-m`.
|
|
137
|
+
|
|
138
|
+
Set custom properties on the supplied class so they also apply to the portaled card:
|
|
139
|
+
|
|
140
|
+
```css
|
|
141
|
+
.custom-citation {
|
|
142
|
+
--g-aikit-inline-citation-card-width: 360px;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
<InlineCitation sources={sources} className="custom-citation" />
|
|
148
|
+
```
|
|
@@ -16,7 +16,7 @@ Each component also has a dedicated subpath export for tree-shaking — `import
|
|
|
16
16
|
| [DiffStat](../components/atoms/DiffStat.md) | Diff statistics — added/deleted line counts. |
|
|
17
17
|
| [Disclaimer](../components/atoms/Disclaimer.md) | Informational or warning message block. |
|
|
18
18
|
| [FileIcon](../components/atoms/FileIcon.md) | Icon for a file based on MIME type or extension. |
|
|
19
|
-
| InlineCitation
|
|
19
|
+
| [InlineCitation](../components/atoms/InlineCitation.md) | Inline source badge with an interactive preview and source navigation. |
|
|
20
20
|
| [IntersectionContainer](../components/atoms/IntersectionContainer.md) | Intersection Observer wrapper (used by `MessageList` for infinite scroll). |
|
|
21
21
|
| [Loader](../components/atoms/Loader.md) | Loading-state indicator. |
|
|
22
22
|
| [MarkdownRenderer](../components/atoms/MarkdownRenderer.md) | Yandex Flavored Markdown (YFM) → HTML renderer. |
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
.g-aikit-inline-citation {
|
|
2
|
+
display: inline;
|
|
3
|
+
}
|
|
4
|
+
.g-aikit-inline-citation__trigger {
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
gap: var(--g-spacing-1);
|
|
8
|
+
max-width: 100%;
|
|
9
|
+
padding: 0 calc(var(--g-spacing-1) + var(--g-spacing-half));
|
|
10
|
+
border: 0;
|
|
11
|
+
border-radius: var(--g-aikit-inline-citation-border-radius, var(--g-border-radius-m));
|
|
12
|
+
background: var(--g-color-base-generic);
|
|
13
|
+
color: var(--g-color-text-secondary);
|
|
14
|
+
font: inherit;
|
|
15
|
+
font-size: var(--g-aikit-inline-citation-font-size, var(--g-text-caption-2-font-size));
|
|
16
|
+
line-height: inherit;
|
|
17
|
+
vertical-align: baseline;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
}
|
|
20
|
+
.g-aikit-inline-citation__trigger:hover, .g-aikit-inline-citation__trigger[aria-expanded=true] {
|
|
21
|
+
background: var(--g-color-base-generic-hover);
|
|
22
|
+
color: var(--g-color-text-primary);
|
|
23
|
+
}
|
|
24
|
+
.g-aikit-inline-citation__trigger:focus-visible {
|
|
25
|
+
outline: 2px solid var(--g-color-line-focus);
|
|
26
|
+
outline-offset: 2px;
|
|
27
|
+
}
|
|
28
|
+
.g-aikit-inline-citation__label {
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
text-overflow: ellipsis;
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
}
|
|
33
|
+
.g-aikit-inline-citation__count {
|
|
34
|
+
flex-shrink: 0;
|
|
35
|
+
}
|
|
36
|
+
.g-aikit-inline-citation__card {
|
|
37
|
+
box-sizing: border-box;
|
|
38
|
+
width: var(--g-aikit-inline-citation-card-width, 320px);
|
|
39
|
+
max-width: calc(100vw - var(--g-spacing-8));
|
|
40
|
+
max-height: min(480px, 100dvh - var(--g-spacing-8));
|
|
41
|
+
overflow: auto;
|
|
42
|
+
padding: var(--g-spacing-3);
|
|
43
|
+
color: var(--g-color-text-primary);
|
|
44
|
+
overflow-wrap: anywhere;
|
|
45
|
+
}
|
|
46
|
+
.g-aikit-inline-citation__header {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: var(--g-spacing-1);
|
|
50
|
+
margin-bottom: var(--g-spacing-2);
|
|
51
|
+
}
|
|
52
|
+
.g-aikit-inline-citation__index, .g-aikit-inline-citation__url {
|
|
53
|
+
color: var(--g-color-text-secondary);
|
|
54
|
+
font-size: var(--g-text-caption-2-font-size);
|
|
55
|
+
line-height: var(--g-text-caption-2-line-height);
|
|
56
|
+
}
|
|
57
|
+
.g-aikit-inline-citation__close {
|
|
58
|
+
margin-inline-start: auto;
|
|
59
|
+
}
|
|
60
|
+
.g-aikit-inline-citation__source {
|
|
61
|
+
display: flex;
|
|
62
|
+
flex-direction: column;
|
|
63
|
+
gap: var(--g-spacing-2);
|
|
64
|
+
font-size: var(--g-text-body-1-font-size);
|
|
65
|
+
line-height: var(--g-text-body-1-line-height);
|
|
66
|
+
}
|
|
67
|
+
.g-aikit-inline-citation__title {
|
|
68
|
+
font-weight: var(--g-text-subheader-font-weight);
|
|
69
|
+
}
|
|
70
|
+
.g-aikit-inline-citation__quote {
|
|
71
|
+
margin: 0;
|
|
72
|
+
padding-inline-start: var(--g-spacing-3);
|
|
73
|
+
border-inline-start: 2px solid var(--g-color-line-generic);
|
|
74
|
+
color: var(--g-color-text-secondary);
|
|
75
|
+
white-space: pre-wrap;
|
|
76
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import "./InlineCitation.css";
|
|
3
|
+
export interface InlineCitationSource {
|
|
4
|
+
/** Stable identifier, unique within this citation. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Absolute HTTP or HTTPS URL. Other URLs are displayed without a link. */
|
|
7
|
+
url: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
quote?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface InlineCitationProps {
|
|
13
|
+
sources: readonly InlineCitationSource[];
|
|
14
|
+
/** Overrides the first source's hostname in the trigger. */
|
|
15
|
+
label?: string;
|
|
16
|
+
open?: boolean;
|
|
17
|
+
defaultOpen?: boolean;
|
|
18
|
+
onOpenChange?: (open: boolean) => void;
|
|
19
|
+
/** Call event.preventDefault() to override navigation. */
|
|
20
|
+
onSourceClick?: (source: InlineCitationSource, event: React.MouseEvent<HTMLAnchorElement>) => void;
|
|
21
|
+
className?: string;
|
|
22
|
+
qa?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function InlineCitation({ sources, label, open, defaultOpen, onOpenChange, onSourceClick, className, qa, }: InlineCitationProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useId, useRef, useState } from 'react';
|
|
3
|
+
import { ChevronLeft, ChevronRight, Xmark } from '@gravity-ui/icons';
|
|
4
|
+
import { Button, Icon, Link, Popup } from '@gravity-ui/uikit';
|
|
5
|
+
import { block } from "../../../utils/cn.js";
|
|
6
|
+
import { i18n } from "./i18n/index.js";
|
|
7
|
+
import "./InlineCitation.css";
|
|
8
|
+
const b = block('inline-citation');
|
|
9
|
+
function getSourceUrl(url) {
|
|
10
|
+
try {
|
|
11
|
+
const parsed = new URL(url);
|
|
12
|
+
return ['http:', 'https:'].includes(parsed.protocol) ? parsed : undefined;
|
|
13
|
+
}
|
|
14
|
+
catch (_a) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function InlineCitation({ sources, label, open, defaultOpen = false, onOpenChange, onSourceClick, className, qa, }) {
|
|
19
|
+
var _a, _b, _c, _d;
|
|
20
|
+
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
21
|
+
const [interactive, setInteractive] = useState(defaultOpen);
|
|
22
|
+
const [triggerElement, setTriggerElement] = useState(null);
|
|
23
|
+
const [selectedId, setSelectedId] = useState();
|
|
24
|
+
const [cardLayout, setCardLayout] = useState({ above: false, availableHeight: 480 });
|
|
25
|
+
const triggerRef = useRef(null);
|
|
26
|
+
const cardRef = useRef(null);
|
|
27
|
+
const timerRef = useRef();
|
|
28
|
+
const pinnedRef = useRef(defaultOpen);
|
|
29
|
+
const suppressFocusRef = useRef(false);
|
|
30
|
+
const cardId = useId();
|
|
31
|
+
const titleId = useId();
|
|
32
|
+
const setTriggerRef = useCallback((element) => {
|
|
33
|
+
triggerRef.current = element;
|
|
34
|
+
setTriggerElement(element);
|
|
35
|
+
}, []);
|
|
36
|
+
const isOpen = (open !== null && open !== void 0 ? open : internalOpen) && sources.length > 0;
|
|
37
|
+
const selectedIndex = Math.max(0, sources.findIndex((source) => source.id === selectedId));
|
|
38
|
+
const source = sources[selectedIndex];
|
|
39
|
+
const clearTimer = useCallback(() => {
|
|
40
|
+
clearTimeout(timerRef.current);
|
|
41
|
+
timerRef.current = undefined;
|
|
42
|
+
}, []);
|
|
43
|
+
useEffect(() => clearTimer, [clearTimer]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!isOpen || !triggerElement) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const updateLayout = () => {
|
|
49
|
+
const rect = triggerElement.getBoundingClientRect();
|
|
50
|
+
const above = rect.top;
|
|
51
|
+
const below = window.innerHeight - rect.bottom;
|
|
52
|
+
setCardLayout({
|
|
53
|
+
above: above > below,
|
|
54
|
+
availableHeight: Math.max(0, Math.max(above, below) - 16),
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
updateLayout();
|
|
58
|
+
window.addEventListener('resize', updateLayout);
|
|
59
|
+
window.addEventListener('scroll', updateLayout, true);
|
|
60
|
+
return () => {
|
|
61
|
+
window.removeEventListener('resize', updateLayout);
|
|
62
|
+
window.removeEventListener('scroll', updateLayout, true);
|
|
63
|
+
};
|
|
64
|
+
}, [isOpen, triggerElement]);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!isOpen) {
|
|
67
|
+
clearTimer();
|
|
68
|
+
pinnedRef.current = false;
|
|
69
|
+
setInteractive(false);
|
|
70
|
+
}
|
|
71
|
+
}, [isOpen, clearTimer]);
|
|
72
|
+
const changeOpen = (nextOpen) => {
|
|
73
|
+
var _a;
|
|
74
|
+
clearTimer();
|
|
75
|
+
if (!nextOpen) {
|
|
76
|
+
pinnedRef.current = false;
|
|
77
|
+
suppressFocusRef.current = Boolean((_a = cardRef.current) === null || _a === void 0 ? void 0 : _a.contains(document.activeElement));
|
|
78
|
+
}
|
|
79
|
+
if (open === undefined) {
|
|
80
|
+
setInternalOpen(nextOpen);
|
|
81
|
+
}
|
|
82
|
+
if (nextOpen !== isOpen) {
|
|
83
|
+
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(nextOpen);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const scheduleClose = () => {
|
|
87
|
+
clearTimer();
|
|
88
|
+
if (pinnedRef.current) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
timerRef.current = setTimeout(() => {
|
|
92
|
+
var _a;
|
|
93
|
+
const activeElement = document.activeElement;
|
|
94
|
+
if (activeElement !== triggerRef.current && !((_a = cardRef.current) === null || _a === void 0 ? void 0 : _a.contains(activeElement))) {
|
|
95
|
+
changeOpen(false);
|
|
96
|
+
}
|
|
97
|
+
}, 200);
|
|
98
|
+
};
|
|
99
|
+
const handlePointerEnter = (event) => {
|
|
100
|
+
clearTimer();
|
|
101
|
+
if (event.pointerType === 'mouse' && !isOpen) {
|
|
102
|
+
timerRef.current = setTimeout(() => changeOpen(true), 150);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
if (!source) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const hostname = (_a = getSourceUrl(sources[0].url)) === null || _a === void 0 ? void 0 : _a.hostname;
|
|
109
|
+
const triggerLabel = (_c = (_b = label !== null && label !== void 0 ? label : hostname) !== null && _b !== void 0 ? _b : sources[0].title) !== null && _c !== void 0 ? _c : i18n('source');
|
|
110
|
+
const sourceUrl = getSourceUrl(source.url);
|
|
111
|
+
const title = source.title || (sourceUrl === null || sourceUrl === void 0 ? void 0 : sourceUrl.hostname) || i18n('source');
|
|
112
|
+
return (_jsxs("span", { className: b(null, className), "data-qa": qa, children: [_jsxs("button", { ref: setTriggerRef, type: "button", className: b('trigger'), "aria-label": i18n('show-sources', { count: sources.length }), "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-controls": isOpen ? cardId : undefined, onPointerEnter: handlePointerEnter, onPointerLeave: scheduleClose, onFocus: () => {
|
|
113
|
+
if (suppressFocusRef.current) {
|
|
114
|
+
suppressFocusRef.current = false;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
changeOpen(true);
|
|
118
|
+
}
|
|
119
|
+
}, onBlur: scheduleClose, onClick: () => {
|
|
120
|
+
if (isOpen && pinnedRef.current) {
|
|
121
|
+
changeOpen(false);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
pinnedRef.current = true;
|
|
125
|
+
setInteractive(true);
|
|
126
|
+
changeOpen(true);
|
|
127
|
+
}
|
|
128
|
+
}, children: [_jsx("span", { className: b('label'), children: triggerLabel }), sources.length > 1 && _jsxs("span", { className: b('count'), children: ["+", sources.length - 1] })] }), _jsx(Popup, { open: isOpen, anchorElement: triggerElement, placement: cardLayout.above ? 'top-start' : 'bottom-start', onOpenChange: changeOpen, className: b('popup', className), initialFocus: interactive ? 0 : -1, returnFocus: interactive, qa: qa ? `${qa}-popup` : undefined, children: _jsxs("div", { ref: cardRef, role: "dialog", id: cardId, "aria-labelledby": titleId, className: b('card'), style: { maxHeight: `min(480px, ${cardLayout.availableHeight}px)` }, onPointerEnter: clearTimer, onPointerLeave: scheduleClose, onFocus: () => {
|
|
129
|
+
clearTimer();
|
|
130
|
+
setInteractive(true);
|
|
131
|
+
}, onBlur: scheduleClose, children: [_jsxs("div", { className: b('header'), children: [sources.length > 1 && (_jsxs(_Fragment, { children: [_jsx(Button, { view: "flat-secondary", size: "s", disabled: selectedIndex === 0, "aria-label": i18n('previous'), onClick: () => setSelectedId(sources[selectedIndex - 1].id), children: _jsx(Icon, { data: ChevronLeft, size: 16 }) }), _jsxs("span", { className: b('index'), "aria-live": "polite", "aria-atomic": "true", children: [selectedIndex + 1, " / ", sources.length] }), _jsx(Button, { view: "flat-secondary", size: "s", disabled: selectedIndex === sources.length - 1, "aria-label": i18n('next'), onClick: () => setSelectedId(sources[selectedIndex + 1].id), children: _jsx(Icon, { data: ChevronRight, size: 16 }) })] })), _jsx(Button, { view: "flat-secondary", size: "s", className: b('close'), "aria-label": i18n('close'), onClick: () => changeOpen(false), children: _jsx(Icon, { data: Xmark, size: 16 }) })] }), _jsxs("div", { className: b('source'), children: [_jsx("div", { id: titleId, className: b('title'), children: sourceUrl ? (_jsx(Link, { href: source.url, target: "_blank", rel: "noopener noreferrer", onClick: (event) => onSourceClick === null || onSourceClick === void 0 ? void 0 : onSourceClick(source, event), children: title })) : (title) }), _jsx("div", { className: b('url'), children: (_d = sourceUrl === null || sourceUrl === void 0 ? void 0 : sourceUrl.hostname) !== null && _d !== void 0 ? _d : source.url }), source.description && (_jsx("div", { className: b('description'), children: source.description })), source.quote && (_jsx("blockquote", { className: b('quote'), children: source.quote }))] }, source.id)] }) })] }));
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=InlineCitation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InlineCitation.js","sourceRoot":"../../../../../src","sources":["components/atoms/InlineCitation/InlineCitation.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, {useCallback, useEffect, useId, useRef, useState} from 'react';\n\nimport {ChevronLeft, ChevronRight, Xmark} from '@gravity-ui/icons';\nimport {Button, Icon, Link, Popup} from '@gravity-ui/uikit';\n\nimport {block} from '../../../utils/cn';\n\nimport {i18n} from './i18n';\n\nimport './InlineCitation.scss';\n\nconst b = block('inline-citation');\n\nexport interface InlineCitationSource {\n /** Stable identifier, unique within this citation. */\n id: string;\n /** Absolute HTTP or HTTPS URL. Other URLs are displayed without a link. */\n url: string;\n title?: string;\n description?: string;\n quote?: string;\n}\n\nexport interface InlineCitationProps {\n sources: readonly InlineCitationSource[];\n /** Overrides the first source's hostname in the trigger. */\n label?: string;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Call event.preventDefault() to override navigation. */\n onSourceClick?: (\n source: InlineCitationSource,\n event: React.MouseEvent<HTMLAnchorElement>,\n ) => void;\n className?: string;\n qa?: string;\n}\n\nfunction getSourceUrl(url: string) {\n try {\n const parsed = new URL(url);\n return ['http:', 'https:'].includes(parsed.protocol) ? parsed : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function InlineCitation({\n sources,\n label,\n open,\n defaultOpen = false,\n onOpenChange,\n onSourceClick,\n className,\n qa,\n}: InlineCitationProps) {\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const [interactive, setInteractive] = useState(defaultOpen);\n const [triggerElement, setTriggerElement] = useState<HTMLButtonElement | null>(null);\n const [selectedId, setSelectedId] = useState<string>();\n const [cardLayout, setCardLayout] = useState({above: false, availableHeight: 480});\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n const cardRef = useRef<HTMLDivElement>(null);\n const timerRef = useRef<ReturnType<typeof setTimeout>>();\n const pinnedRef = useRef(defaultOpen);\n const suppressFocusRef = useRef(false);\n const cardId = useId();\n const titleId = useId();\n const setTriggerRef = useCallback((element: HTMLButtonElement | null) => {\n triggerRef.current = element;\n setTriggerElement(element);\n }, []);\n const isOpen = (open ?? internalOpen) && sources.length > 0;\n const selectedIndex = Math.max(\n 0,\n sources.findIndex((source) => source.id === selectedId),\n );\n const source = sources[selectedIndex];\n\n const clearTimer = useCallback(() => {\n clearTimeout(timerRef.current);\n timerRef.current = undefined;\n }, []);\n\n useEffect(() => clearTimer, [clearTimer]);\n\n useEffect(() => {\n if (!isOpen || !triggerElement) {\n return undefined;\n }\n const updateLayout = () => {\n const rect = triggerElement.getBoundingClientRect();\n const above = rect.top;\n const below = window.innerHeight - rect.bottom;\n setCardLayout({\n above: above > below,\n availableHeight: Math.max(0, Math.max(above, below) - 16),\n });\n };\n updateLayout();\n window.addEventListener('resize', updateLayout);\n window.addEventListener('scroll', updateLayout, true);\n return () => {\n window.removeEventListener('resize', updateLayout);\n window.removeEventListener('scroll', updateLayout, true);\n };\n }, [isOpen, triggerElement]);\n\n useEffect(() => {\n if (!isOpen) {\n clearTimer();\n pinnedRef.current = false;\n setInteractive(false);\n }\n }, [isOpen, clearTimer]);\n\n const changeOpen = (nextOpen: boolean) => {\n clearTimer();\n if (!nextOpen) {\n pinnedRef.current = false;\n suppressFocusRef.current = Boolean(cardRef.current?.contains(document.activeElement));\n }\n if (open === undefined) {\n setInternalOpen(nextOpen);\n }\n if (nextOpen !== isOpen) {\n onOpenChange?.(nextOpen);\n }\n };\n\n const scheduleClose = () => {\n clearTimer();\n if (pinnedRef.current) {\n return;\n }\n timerRef.current = setTimeout(() => {\n const activeElement = document.activeElement;\n if (activeElement !== triggerRef.current && !cardRef.current?.contains(activeElement)) {\n changeOpen(false);\n }\n }, 200);\n };\n\n const handlePointerEnter = (event: React.PointerEvent) => {\n clearTimer();\n if (event.pointerType === 'mouse' && !isOpen) {\n timerRef.current = setTimeout(() => changeOpen(true), 150);\n }\n };\n\n if (!source) {\n return null;\n }\n\n const hostname = getSourceUrl(sources[0].url)?.hostname;\n const triggerLabel = label ?? hostname ?? sources[0].title ?? i18n('source');\n const sourceUrl = getSourceUrl(source.url);\n const title = source.title || sourceUrl?.hostname || i18n('source');\n\n return (\n <span className={b(null, className)} data-qa={qa}>\n <button\n ref={setTriggerRef}\n type=\"button\"\n className={b('trigger')}\n aria-label={i18n('show-sources', {count: sources.length})}\n aria-haspopup=\"dialog\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? cardId : undefined}\n onPointerEnter={handlePointerEnter}\n onPointerLeave={scheduleClose}\n onFocus={() => {\n if (suppressFocusRef.current) {\n suppressFocusRef.current = false;\n } else {\n changeOpen(true);\n }\n }}\n onBlur={scheduleClose}\n onClick={() => {\n if (isOpen && pinnedRef.current) {\n changeOpen(false);\n } else {\n pinnedRef.current = true;\n setInteractive(true);\n changeOpen(true);\n }\n }}\n >\n <span className={b('label')}>{triggerLabel}</span>\n {sources.length > 1 && <span className={b('count')}>+{sources.length - 1}</span>}\n </button>\n <Popup\n open={isOpen}\n anchorElement={triggerElement}\n placement={cardLayout.above ? 'top-start' : 'bottom-start'}\n onOpenChange={changeOpen}\n className={b('popup', className)}\n initialFocus={interactive ? 0 : -1}\n returnFocus={interactive}\n qa={qa ? `${qa}-popup` : undefined}\n >\n <div\n ref={cardRef}\n role=\"dialog\"\n id={cardId}\n aria-labelledby={titleId}\n className={b('card')}\n style={{maxHeight: `min(480px, ${cardLayout.availableHeight}px)`}}\n onPointerEnter={clearTimer}\n onPointerLeave={scheduleClose}\n onFocus={() => {\n clearTimer();\n setInteractive(true);\n }}\n onBlur={scheduleClose}\n >\n <div className={b('header')}>\n {sources.length > 1 && (\n <>\n <Button\n view=\"flat-secondary\"\n size=\"s\"\n disabled={selectedIndex === 0}\n aria-label={i18n('previous')}\n onClick={() => setSelectedId(sources[selectedIndex - 1].id)}\n >\n <Icon data={ChevronLeft} size={16} />\n </Button>\n <span className={b('index')} aria-live=\"polite\" aria-atomic=\"true\">\n {selectedIndex + 1} / {sources.length}\n </span>\n <Button\n view=\"flat-secondary\"\n size=\"s\"\n disabled={selectedIndex === sources.length - 1}\n aria-label={i18n('next')}\n onClick={() => setSelectedId(sources[selectedIndex + 1].id)}\n >\n <Icon data={ChevronRight} size={16} />\n </Button>\n </>\n )}\n <Button\n view=\"flat-secondary\"\n size=\"s\"\n className={b('close')}\n aria-label={i18n('close')}\n onClick={() => changeOpen(false)}\n >\n <Icon data={Xmark} size={16} />\n </Button>\n </div>\n <div className={b('source')} key={source.id}>\n <div id={titleId} className={b('title')}>\n {sourceUrl ? (\n <Link\n href={source.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(event) => onSourceClick?.(source, event)}\n >\n {title}\n </Link>\n ) : (\n title\n )}\n </div>\n <div className={b('url')}>{sourceUrl?.hostname ?? source.url}</div>\n {source.description && (\n <div className={b('description')}>{source.description}</div>\n )}\n {source.quote && (\n <blockquote className={b('quote')}>{source.quote}</blockquote>\n )}\n </div>\n </div>\n </Popup>\n </span>\n );\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const i18n: ((key: "source" | "next" | "previous" | "close" | "show-sources", params?: import("@gravity-ui/i18n").Params) => string) & {
|
|
2
|
+
Translation: import("react").ComponentType<{
|
|
3
|
+
children: (props: {
|
|
4
|
+
t: (key: "source" | "next" | "previous" | "close" | "show-sources", params?: import("@gravity-ui/i18n").Params) => string;
|
|
5
|
+
}) => React.ReactNode;
|
|
6
|
+
}>;
|
|
7
|
+
useTranslation: () => {
|
|
8
|
+
t: (key: "source" | "next" | "previous" | "close" | "show-sources", params?: import("@gravity-ui/i18n").Params) => string;
|
|
9
|
+
};
|
|
10
|
+
keysetData: {
|
|
11
|
+
"g-aikit-InlineCitation": Record<"source" | "next" | "previous" | "close" | "show-sources", import("@gravity-ui/i18n").KeyData>;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { addComponentKeysets } from '@gravity-ui/uikit/i18n';
|
|
2
|
+
import { NAMESPACE } from "../../../../utils/cn.js";
|
|
3
|
+
import en from "./en.json";
|
|
4
|
+
import ru from "./ru.json";
|
|
5
|
+
export const i18n = addComponentKeysets({ en, ru }, `${NAMESPACE}InlineCitation`);
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"../../../../../../src","sources":["components/atoms/InlineCitation/i18n/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,mBAAmB,EAAC,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAC,SAAS,EAAC,gCAA6B;AAE/C,OAAO,EAAE,kBAAkB;AAC3B,OAAO,EAAE,kBAAkB;AAE3B,MAAM,CAAC,MAAM,IAAI,GAAG,mBAAmB,CAAC,EAAC,EAAE,EAAE,EAAE,EAAC,EAAE,GAAG,SAAS,gBAAgB,CAAC,CAAC","sourcesContent":["import {addComponentKeysets} from '@gravity-ui/uikit/i18n';\n\nimport {NAMESPACE} from '../../../../utils/cn';\n\nimport en from './en.json';\nimport ru from './ru.json';\n\nexport const i18n = addComponentKeysets({en, ru}, `${NAMESPACE}InlineCitation`);\n"]}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export {};
|
|
1
|
+
export { InlineCitation } from "./InlineCitation.js";
|
|
2
|
+
export type { InlineCitationProps, InlineCitationSource } from "./InlineCitation.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {};
|
|
1
|
+
export { InlineCitation } from "./InlineCitation.js";
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["components/atoms/InlineCitation/index.ts"],"names":[],"mappings":"","sourcesContent":["export {};\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["components/atoms/InlineCitation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,cAAc,EAAC,4BAAyB","sourcesContent":["export {InlineCitation} from './InlineCitation';\nexport type {InlineCitationProps, InlineCitationSource} from './InlineCitation';\n"]}
|
|
@@ -4,6 +4,8 @@ import { useMdx } from '@diplodoc/mdx-extension';
|
|
|
4
4
|
import * as runtime from 'react/jsx-runtime';
|
|
5
5
|
import { MdxDataContext } from "./MdxContext.js";
|
|
6
6
|
import { asyncExecuteCode, resolveCspNonce } from "./asyncExecuteCode.js";
|
|
7
|
+
// Keep the MDX mounting effect stable when a child creates a React portal.
|
|
8
|
+
const contextList = [MdxDataContext];
|
|
7
9
|
function useMdxComponentLoader(mdxArtifacts, nonce) {
|
|
8
10
|
const idMdx = mdxArtifacts === null || mdxArtifacts === void 0 ? void 0 : mdxArtifacts.idMdx;
|
|
9
11
|
const resolvedNonce = resolveCspNonce(nonce);
|
|
@@ -52,7 +54,7 @@ export function MdxPortals({ refCtr, html, components, mdxArtifacts, nonce, mdxC
|
|
|
52
54
|
html,
|
|
53
55
|
components,
|
|
54
56
|
mdxArtifacts,
|
|
55
|
-
contextList
|
|
57
|
+
contextList,
|
|
56
58
|
idMdxComponentLoader,
|
|
57
59
|
});
|
|
58
60
|
return _jsx(MdxDataContext.Provider, { value: mdxContext, children: portals });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MdxPortals.js","sourceRoot":"../../../../../src","sources":["components/atoms/MarkdownRenderer/MdxPortals.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAC,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAC;AAG1C,OAAO,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAG/C,OAAO,KAAK,OAAO,MAAM,mBAAmB,CAAC;AAE7C,OAAO,EAAC,cAAc,EAAC,wBAAqB;AAC5C,OAAO,EAAC,gBAAgB,EAAE,eAAe,EAAC,8BAA2B;
|
|
1
|
+
{"version":3,"file":"MdxPortals.js","sourceRoot":"../../../../../src","sources":["components/atoms/MarkdownRenderer/MdxPortals.tsx"],"names":[],"mappings":"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","sourcesContent":["import {useEffect, useState} from 'react';\nimport type {RefObject} from 'react';\n\nimport {useMdx} from '@diplodoc/mdx-extension';\nimport type {IdMdxComponentLoader, MdxArtifacts} from '@diplodoc/mdx-extension';\nimport type {MDXComponents, MDXModule, MDXProps} from 'mdx/types';\nimport * as runtime from 'react/jsx-runtime';\n\nimport {MdxDataContext} from './MdxContext';\nimport {asyncExecuteCode, resolveCspNonce} from './asyncExecuteCode';\n\n// Keep the MDX mounting effect stable when a child creates a React portal.\nconst contextList = [MdxDataContext];\n\ninterface MdxLoaderState extends IdMdxComponentLoader {\n idMdx?: Record<string, string>;\n nonce?: string;\n}\n\nfunction useMdxComponentLoader(mdxArtifacts: MdxArtifacts | undefined, nonce?: string) {\n const idMdx = mdxArtifacts?.idMdx;\n const resolvedNonce = resolveCspNonce(nonce);\n const [state, setState] = useState<MdxLoaderState>({isSuccess: false});\n\n useEffect(() => {\n if (!resolvedNonce) {\n return () => undefined;\n }\n\n let isActive = true;\n\n (async () => {\n try {\n const data: Record<string, React.ComponentType<MDXProps>> = {};\n\n for (const [artifactId, code] of Object.entries(idMdx ?? {})) {\n const fn = await asyncExecuteCode<(jsxRuntime: typeof runtime) => MDXModule>(\n code,\n resolvedNonce,\n );\n\n if (!isActive) {\n return;\n }\n\n data[artifactId] = fn(runtime).default;\n }\n\n if (isActive) {\n setState({idMdx, nonce: resolvedNonce, data, isSuccess: true});\n }\n } catch {\n if (isActive) {\n setState({idMdx, nonce: resolvedNonce, isSuccess: false});\n }\n }\n })();\n\n return () => {\n isActive = false;\n };\n }, [idMdx, resolvedNonce]);\n\n if (!resolvedNonce) {\n return undefined;\n }\n\n if (state.idMdx !== idMdx || state.nonce !== resolvedNonce) {\n return {isSuccess: false};\n }\n\n return state;\n}\n\nexport interface MdxPortalsProps {\n /** Ref to the container element into which the rendered HTML is injected. */\n refCtr: RefObject<HTMLDivElement | null>;\n /** HTML produced by the markdown transform for the current content. */\n html: string;\n /** Map of components rendered from embedded MDX/JSX in the markdown content. */\n components?: MDXComponents;\n /** Artifacts collected by the MDX transform, required to hydrate the components. */\n mdxArtifacts?: MdxArtifacts;\n /** CSP nonce applied to compiled MDX scripts. Defaults to a nonce found in the document. */\n nonce?: string;\n /** Arbitrary value exposed to the MDX components through {@link MdxDataContext}. */\n mdxContext?: Record<string, unknown>;\n}\n\nexport function MdxPortals({\n refCtr,\n html,\n components,\n mdxArtifacts,\n nonce,\n mdxContext,\n}: MdxPortalsProps) {\n const idMdxComponentLoader = useMdxComponentLoader(mdxArtifacts, nonce);\n const portals = useMdx({\n refCtr,\n html,\n components,\n mdxArtifacts,\n contextList,\n idMdxComponentLoader,\n });\n\n return <MdxDataContext.Provider value={mdxContext}>{portals}</MdxDataContext.Provider>;\n}\n\nMdxPortals.displayName = 'MdxPortals';\n"]}
|
package/build/esm/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"2.26.0","type":"module","sideEffects":["*.css","*.scss"]}
|
|
1
|
+
{"version":"2.26.1-beta.36dd40dc8a74f42d68064c5a5c131f7344ffe925.0","type":"module","sideEffects":["*.css","*.scss"]}
|
package/docs/COMPONENTS.md
CHANGED
|
@@ -16,7 +16,7 @@ Each component also has a dedicated subpath export for tree-shaking — `import
|
|
|
16
16
|
| [DiffStat](../src/components/atoms/DiffStat/README.md) | Diff statistics — added/deleted line counts. |
|
|
17
17
|
| [Disclaimer](../src/components/atoms/Disclaimer/README.md) | Informational or warning message block. |
|
|
18
18
|
| [FileIcon](../src/components/atoms/FileIcon/README.md) | Icon for a file based on MIME type or extension. |
|
|
19
|
-
| InlineCitation
|
|
19
|
+
| [InlineCitation](../src/components/atoms/InlineCitation/README.md) | Inline source badge with an interactive preview and source navigation. |
|
|
20
20
|
| [IntersectionContainer](../src/components/atoms/IntersectionContainer/README.md) | Intersection Observer wrapper (used by `MessageList` for infinite scroll). |
|
|
21
21
|
| [Loader](../src/components/atoms/Loader/README.md) | Loading-state indicator. |
|
|
22
22
|
| [MarkdownRenderer](../src/components/atoms/MarkdownRenderer/README.md) | Yandex Flavored Markdown (YFM) → HTML renderer. |
|
package/llms-full.txt
CHANGED
|
@@ -779,7 +779,7 @@ Each component also has a dedicated subpath export for tree-shaking — `import
|
|
|
779
779
|
| [DiffStat](../src/components/atoms/DiffStat/README.md) | Diff statistics — added/deleted line counts. |
|
|
780
780
|
| [Disclaimer](../src/components/atoms/Disclaimer/README.md) | Informational or warning message block. |
|
|
781
781
|
| [FileIcon](../src/components/atoms/FileIcon/README.md) | Icon for a file based on MIME type or extension. |
|
|
782
|
-
| InlineCitation
|
|
782
|
+
| [InlineCitation](../src/components/atoms/InlineCitation/README.md) | Inline source badge with an interactive preview and source navigation. |
|
|
783
783
|
| [IntersectionContainer](../src/components/atoms/IntersectionContainer/README.md) | Intersection Observer wrapper (used by `MessageList` for infinite scroll). |
|
|
784
784
|
| [Loader](../src/components/atoms/Loader/README.md) | Loading-state indicator. |
|
|
785
785
|
| [MarkdownRenderer](../src/components/atoms/MarkdownRenderer/README.md) | Yandex Flavored Markdown (YFM) → HTML renderer. |
|
package/llms.txt
CHANGED
|
@@ -29,6 +29,7 @@ Tree-shaken imports: `@gravity-ui/aikit/PromptInput`, `/Header`, `/MessageList`,
|
|
|
29
29
|
- [FileIcon](https://github.com/gravity-ui/aikit/blob/main/src/components/atoms/FileIcon/README.md): icon for a file based on MIME or extension
|
|
30
30
|
- [IntersectionContainer](https://github.com/gravity-ui/aikit/blob/main/src/components/atoms/IntersectionContainer/README.md): Intersection Observer wrapper for infinite scroll
|
|
31
31
|
- [Loader](https://github.com/gravity-ui/aikit/blob/main/src/components/atoms/Loader/README.md): loading-state indicator
|
|
32
|
+
- [InlineCitation](https://github.com/gravity-ui/aikit/blob/main/src/components/atoms/InlineCitation/README.md): Inline source badge with an interactive preview and source navigation.
|
|
32
33
|
- [MarkdownRenderer](https://github.com/gravity-ui/aikit/blob/main/src/components/atoms/MarkdownRenderer/README.md): Yandex Flavored Markdown → HTML renderer
|
|
33
34
|
- [MessageBalloon](https://github.com/gravity-ui/aikit/blob/main/src/components/atoms/MessageBalloon/README.md): visual wrapper for a user message
|
|
34
35
|
- [Shimmer](https://github.com/gravity-ui/aikit/blob/main/src/components/atoms/Shimmer/README.md): shimmer animation overlay
|
package/package.json
CHANGED