@veluai/velu 0.2.13 → 0.2.15
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 +80 -80
- package/dist/cli.js +37 -37
- package/package.json +64 -64
- package/runtime/velu-ui/base.css +320 -320
- package/runtime/velu-ui/components/Accordion.jsx +64 -64
- package/runtime/velu-ui/components/ApiClient.jsx +207 -207
- package/runtime/velu-ui/components/ApiField.jsx +87 -87
- package/runtime/velu-ui/components/ApiPath.jsx +63 -63
- package/runtime/velu-ui/components/ApiReferencePage.jsx +384 -384
- package/runtime/velu-ui/components/ApiSamples.jsx +36 -36
- package/runtime/velu-ui/components/ApiSidebar.jsx +122 -122
- package/runtime/velu-ui/components/AskBar.jsx +71 -71
- package/runtime/velu-ui/components/Callout.jsx +114 -114
- package/runtime/velu-ui/components/Card.jsx +131 -131
- package/runtime/velu-ui/components/Chatbot.jsx +885 -885
- package/runtime/velu-ui/components/CodeBlock.jsx +375 -375
- package/runtime/velu-ui/components/Columns.jsx +56 -56
- package/runtime/velu-ui/components/ContextMenu.jsx +298 -273
- package/runtime/velu-ui/components/ErrorCard.jsx +138 -138
- package/runtime/velu-ui/components/Field.jsx +81 -81
- package/runtime/velu-ui/components/Image.jsx +163 -163
- package/runtime/velu-ui/components/Logo.jsx +31 -31
- package/runtime/velu-ui/components/MethodBadge.jsx +31 -31
- package/runtime/velu-ui/components/NavSelect.jsx +108 -108
- package/runtime/velu-ui/components/NotFound.jsx +63 -63
- package/runtime/velu-ui/components/PageFeedback.jsx +219 -219
- package/runtime/velu-ui/components/PageFooter.jsx +145 -145
- package/runtime/velu-ui/components/PageHeader.jsx +422 -422
- package/runtime/velu-ui/components/PageNav.jsx +77 -77
- package/runtime/velu-ui/components/PoweredBy.jsx +51 -51
- package/runtime/velu-ui/components/Prompt.jsx +115 -115
- package/runtime/velu-ui/components/Search.jsx +460 -460
- package/runtime/velu-ui/components/Sidebar.jsx +254 -254
- package/runtime/velu-ui/components/SocialLinks.jsx +90 -90
- package/runtime/velu-ui/components/Steps.jsx +65 -65
- package/runtime/velu-ui/components/ThemeToggle.jsx +48 -48
- package/runtime/velu-ui/components/Toc.jsx +537 -537
- package/runtime/velu-ui/components/TocBar.jsx +195 -195
- package/runtime/velu-ui/components/Tree.jsx +87 -87
- package/runtime/velu-ui/components/TryItBar.jsx +102 -102
- package/runtime/velu-ui/components/accordion.css +92 -92
- package/runtime/velu-ui/components/api-page.css +208 -208
- package/runtime/velu-ui/components/api.css +635 -635
- package/runtime/velu-ui/components/ask-bar.css +94 -94
- package/runtime/velu-ui/components/card.css +105 -105
- package/runtime/velu-ui/components/chatbot.css +622 -622
- package/runtime/velu-ui/components/code-block.css +263 -263
- package/runtime/velu-ui/components/context-menu.css +173 -173
- package/runtime/velu-ui/components/docs-layout.css +822 -822
- package/runtime/velu-ui/components/field.css +82 -82
- package/runtime/velu-ui/components/image.css +237 -237
- package/runtime/velu-ui/components/nav-select.css +157 -157
- package/runtime/velu-ui/components/not-found.css +94 -94
- package/runtime/velu-ui/components/page-feedback.css +241 -241
- package/runtime/velu-ui/components/page-footer.css +130 -130
- package/runtime/velu-ui/components/page-header.css +558 -558
- package/runtime/velu-ui/components/page-nav.css +50 -50
- package/runtime/velu-ui/components/powered-by.css +92 -92
- package/runtime/velu-ui/components/prompt.css +99 -99
- package/runtime/velu-ui/components/search.css +307 -307
- package/runtime/velu-ui/components/sidebar.css +205 -205
- package/runtime/velu-ui/components/steps.css +77 -77
- package/runtime/velu-ui/components/theme-toggle.css +102 -102
- package/runtime/velu-ui/components/toc-bar.css +234 -234
- package/runtime/velu-ui/components/tree.css +49 -49
- package/runtime/velu-ui/index.js +54 -54
- package/runtime/velu-ui/lib/api-send.js +92 -92
- package/runtime/velu-ui/lib/brand-icons.jsx +103 -103
- package/runtime/velu-ui/lib/component-schemas.js +100 -100
- package/runtime/velu-ui/lib/copyText.js +64 -64
- package/runtime/velu-ui/lib/docs-assistant.js +250 -250
- package/runtime/velu-ui/lib/lang-icons.jsx +147 -147
- package/runtime/velu-ui/lib/pagefind.js +113 -113
- package/runtime/velu-ui/lib/prism-langs.js +957 -957
- package/runtime/velu-ui/lib/prism-loader.js +74 -74
- package/runtime/velu-ui/lib/resolveIcon.jsx +29 -29
- package/runtime/velu-ui/lib/scrollIntoNearestView.js +66 -66
- package/runtime/velu-ui/mdx-components.jsx +105 -105
- package/runtime/velu-ui/primitives/Cluster.jsx +49 -49
- package/runtime/velu-ui/primitives/Stack.jsx +63 -63
- package/runtime/velu-ui/primitives/Switcher.jsx +57 -57
- package/runtime/velu-ui/primitives/stack.css +3 -3
- package/runtime/velu-ui/primitives/switcher.css +25 -25
- package/runtime/velu-ui/styles.css +46 -46
- package/runtime/velu-ui/tokens.css +4 -4
- package/schema/velu.schema.json +423 -423
- package/src/lib/extract-mdx-error.js +170 -170
- package/src/lib/issues.js +159 -159
- package/src/lib/known-components.js +34 -34
- package/src/navigation.js +443 -443
- package/src/runtime/App.jsx +1669 -1668
- package/src/runtime/ErrorBoundary.jsx +54 -54
- package/src/runtime/client-entry.jsx +22 -22
- package/src/runtime/server-entry.jsx +16 -16
- package/src/template.html +48 -48
- package/templates/starter/ai-tools/claude-code.mdx +26 -26
- package/templates/starter/ai-tools/cursor.mdx +17 -17
- package/templates/starter/api-reference/introduction.mdx +43 -43
- package/templates/starter/development.mdx +19 -19
- package/templates/starter/essentials/code.mdx +29 -29
- package/templates/starter/essentials/images.mdx +29 -29
- package/templates/starter/essentials/markdown.mdx +25 -25
- package/templates/starter/essentials/navigation.mdx +39 -39
- package/templates/starter/essentials/settings.mdx +30 -30
- package/templates/starter/favicon.svg +6 -6
- package/templates/starter/index.mdx +31 -31
- package/templates/starter/openapi.json +160 -160
- package/templates/starter/quickstart.mdx +31 -31
- package/templates/starter/velu.json +41 -41
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import CodeBlock, { CodeGroup } from './CodeBlock.jsx';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* ApiSamples — the right-rail of an API reference page: the request code
|
|
6
|
-
* snippets (curl / JS / Python / Ruby tabs) and the example responses
|
|
7
|
-
* (one tab per status). The runtime renders this in the page's right aside
|
|
8
|
-
* (where the TOC sits for normal pages).
|
|
9
|
-
*
|
|
10
|
-
* @param {{ samples: {key,label,language,code}[], responses: object[] }} props
|
|
11
|
-
*/
|
|
12
|
-
export default function ApiSamples({ samples = [], responses = [] }) {
|
|
13
|
-
const withExample = responses.filter((r) => r.example != null);
|
|
14
|
-
return (
|
|
15
|
-
<div className="velu-api-samples">
|
|
16
|
-
{samples.length > 0 && (
|
|
17
|
-
<CodeGroup className="velu-api-samples__req">
|
|
18
|
-
{samples.map((s) => (
|
|
19
|
-
<CodeBlock key={s.key} title={s.label} language={s.language}>
|
|
20
|
-
{s.code}
|
|
21
|
-
</CodeBlock>
|
|
22
|
-
))}
|
|
23
|
-
</CodeGroup>
|
|
24
|
-
)}
|
|
25
|
-
{withExample.length > 0 && (
|
|
26
|
-
<CodeGroup className="velu-api-samples__res">
|
|
27
|
-
{withExample.map((r) => (
|
|
28
|
-
<CodeBlock key={r.status} title={r.status} language="json">
|
|
29
|
-
{JSON.stringify(r.example, null, 2)}
|
|
30
|
-
</CodeBlock>
|
|
31
|
-
))}
|
|
32
|
-
</CodeGroup>
|
|
33
|
-
)}
|
|
34
|
-
</div>
|
|
35
|
-
);
|
|
36
|
-
}
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import CodeBlock, { CodeGroup } from './CodeBlock.jsx';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ApiSamples — the right-rail of an API reference page: the request code
|
|
6
|
+
* snippets (curl / JS / Python / Ruby tabs) and the example responses
|
|
7
|
+
* (one tab per status). The runtime renders this in the page's right aside
|
|
8
|
+
* (where the TOC sits for normal pages).
|
|
9
|
+
*
|
|
10
|
+
* @param {{ samples: {key,label,language,code}[], responses: object[] }} props
|
|
11
|
+
*/
|
|
12
|
+
export default function ApiSamples({ samples = [], responses = [] }) {
|
|
13
|
+
const withExample = responses.filter((r) => r.example != null);
|
|
14
|
+
return (
|
|
15
|
+
<div className="velu-api-samples">
|
|
16
|
+
{samples.length > 0 && (
|
|
17
|
+
<CodeGroup className="velu-api-samples__req">
|
|
18
|
+
{samples.map((s) => (
|
|
19
|
+
<CodeBlock key={s.key} title={s.label} language={s.language}>
|
|
20
|
+
{s.code}
|
|
21
|
+
</CodeBlock>
|
|
22
|
+
))}
|
|
23
|
+
</CodeGroup>
|
|
24
|
+
)}
|
|
25
|
+
{withExample.length > 0 && (
|
|
26
|
+
<CodeGroup className="velu-api-samples__res">
|
|
27
|
+
{withExample.map((r) => (
|
|
28
|
+
<CodeBlock key={r.status} title={r.status} language="json">
|
|
29
|
+
{JSON.stringify(r.example, null, 2)}
|
|
30
|
+
</CodeBlock>
|
|
31
|
+
))}
|
|
32
|
+
</CodeGroup>
|
|
33
|
+
)}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import Stack from '../primitives/Stack.jsx';
|
|
3
|
-
import resolveIcon from '../lib/resolveIcon.jsx';
|
|
4
|
-
import MethodBadge from './MethodBadge.jsx';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* ApiSidebar — navigation for API reference pages. Like <Sidebar> but
|
|
8
|
-
* flat (no nested folders): every entry is a single endpoint shown as
|
|
9
|
-
* a method pill + name. Entries are organized into groups, each with
|
|
10
|
-
* an optional icon.
|
|
11
|
-
*
|
|
12
|
-
* <ApiSidebar
|
|
13
|
-
* activeHref="/api/trigger"
|
|
14
|
-
* linkComponent={RouterLink}
|
|
15
|
-
* sections={[
|
|
16
|
-
* {
|
|
17
|
-
* title: 'Admin',
|
|
18
|
-
* icon: 'rocket',
|
|
19
|
-
* endpoints: [
|
|
20
|
-
* { method: 'POST', label: 'Trigger', href: '/api/trigger' },
|
|
21
|
-
* { method: 'GET', label: 'Get deployment-status',
|
|
22
|
-
* href: '/api/status' },
|
|
23
|
-
* { method: 'DELETE', label: 'Trigger Preview deployment',
|
|
24
|
-
* href: '/api/preview' },
|
|
25
|
-
* ],
|
|
26
|
-
* },
|
|
27
|
-
* ]}
|
|
28
|
-
* />
|
|
29
|
-
*
|
|
30
|
-
* Vertical rhythm = the Stack primitive (DRY, same as <Sidebar>);
|
|
31
|
-
* api.css holds only the rail / active state / row layout / icon size.
|
|
32
|
-
* Selection is route-driven (`endpoint.href === activeHref`, or
|
|
33
|
-
* `endpoint.active: true`). Router-agnostic via `linkComponent`.
|
|
34
|
-
*
|
|
35
|
-
* @typedef {Object} ApiEndpoint
|
|
36
|
-
* @property {string} method HTTP method — drives the MethodBadge color
|
|
37
|
-
* @property {string} label endpoint display name
|
|
38
|
-
* @property {string} [href]
|
|
39
|
-
* @property {boolean} [active]
|
|
40
|
-
*
|
|
41
|
-
* @param {{ sections: { title?: string, icon?: string|React.ReactNode,
|
|
42
|
-
* endpoints: ApiEndpoint[] }[], activeHref?: string,
|
|
43
|
-
* linkComponent?: React.ElementType, className?: string }} props
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
const Ctx = React.createContext({ activeHref: undefined, Link: 'a' });
|
|
47
|
-
|
|
48
|
-
function Icon({ icon }) {
|
|
49
|
-
const node = resolveIcon(icon);
|
|
50
|
-
return node ? (
|
|
51
|
-
<span className="velu-api-sidebar__icon" aria-hidden="true">
|
|
52
|
-
{node}
|
|
53
|
-
</span>
|
|
54
|
-
) : null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function Endpoint({ endpoint }) {
|
|
58
|
-
const { activeHref, Link } = React.useContext(Ctx);
|
|
59
|
-
const { method = 'GET', label, href = '#', active } = endpoint;
|
|
60
|
-
const isActive = Boolean(
|
|
61
|
-
active || (href != null && href === activeHref),
|
|
62
|
-
);
|
|
63
|
-
const cls = `velu-api-sidebar__item${
|
|
64
|
-
isActive ? ' velu-api-sidebar__item--active' : ''
|
|
65
|
-
}`;
|
|
66
|
-
return (
|
|
67
|
-
<li>
|
|
68
|
-
<Link
|
|
69
|
-
className={cls}
|
|
70
|
-
href={href}
|
|
71
|
-
aria-current={isActive ? 'page' : undefined}
|
|
72
|
-
>
|
|
73
|
-
<MethodBadge method={method} />
|
|
74
|
-
<span className="velu-api-sidebar__label">{label}</span>
|
|
75
|
-
</Link>
|
|
76
|
-
</li>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export default function ApiSidebar({
|
|
81
|
-
sections = [],
|
|
82
|
-
activeHref,
|
|
83
|
-
linkComponent = 'a',
|
|
84
|
-
className = '',
|
|
85
|
-
...rest
|
|
86
|
-
}) {
|
|
87
|
-
const ctx = React.useMemo(
|
|
88
|
-
() => ({ activeHref, Link: linkComponent }),
|
|
89
|
-
[activeHref, linkComponent],
|
|
90
|
-
);
|
|
91
|
-
return (
|
|
92
|
-
<Ctx.Provider value={ctx}>
|
|
93
|
-
<Stack
|
|
94
|
-
as="nav"
|
|
95
|
-
space="var(--s1)"
|
|
96
|
-
className={`velu-api-sidebar ${className}`.trim()}
|
|
97
|
-
aria-label="API reference"
|
|
98
|
-
{...rest}
|
|
99
|
-
>
|
|
100
|
-
{sections.map((section, i) => (
|
|
101
|
-
<React.Fragment key={i}>
|
|
102
|
-
{section.title != null && (
|
|
103
|
-
<h5 className="velu-api-sidebar__section">
|
|
104
|
-
<Icon icon={section.icon} />
|
|
105
|
-
{section.title}
|
|
106
|
-
</h5>
|
|
107
|
-
)}
|
|
108
|
-
<Stack
|
|
109
|
-
as="ul"
|
|
110
|
-
space="var(--s0)"
|
|
111
|
-
className="velu-api-sidebar__list"
|
|
112
|
-
>
|
|
113
|
-
{(section.endpoints ?? []).map((ep, j) => (
|
|
114
|
-
<Endpoint key={j} endpoint={ep} />
|
|
115
|
-
))}
|
|
116
|
-
</Stack>
|
|
117
|
-
</React.Fragment>
|
|
118
|
-
))}
|
|
119
|
-
</Stack>
|
|
120
|
-
</Ctx.Provider>
|
|
121
|
-
);
|
|
122
|
-
}
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Stack from '../primitives/Stack.jsx';
|
|
3
|
+
import resolveIcon from '../lib/resolveIcon.jsx';
|
|
4
|
+
import MethodBadge from './MethodBadge.jsx';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ApiSidebar — navigation for API reference pages. Like <Sidebar> but
|
|
8
|
+
* flat (no nested folders): every entry is a single endpoint shown as
|
|
9
|
+
* a method pill + name. Entries are organized into groups, each with
|
|
10
|
+
* an optional icon.
|
|
11
|
+
*
|
|
12
|
+
* <ApiSidebar
|
|
13
|
+
* activeHref="/api/trigger"
|
|
14
|
+
* linkComponent={RouterLink}
|
|
15
|
+
* sections={[
|
|
16
|
+
* {
|
|
17
|
+
* title: 'Admin',
|
|
18
|
+
* icon: 'rocket',
|
|
19
|
+
* endpoints: [
|
|
20
|
+
* { method: 'POST', label: 'Trigger', href: '/api/trigger' },
|
|
21
|
+
* { method: 'GET', label: 'Get deployment-status',
|
|
22
|
+
* href: '/api/status' },
|
|
23
|
+
* { method: 'DELETE', label: 'Trigger Preview deployment',
|
|
24
|
+
* href: '/api/preview' },
|
|
25
|
+
* ],
|
|
26
|
+
* },
|
|
27
|
+
* ]}
|
|
28
|
+
* />
|
|
29
|
+
*
|
|
30
|
+
* Vertical rhythm = the Stack primitive (DRY, same as <Sidebar>);
|
|
31
|
+
* api.css holds only the rail / active state / row layout / icon size.
|
|
32
|
+
* Selection is route-driven (`endpoint.href === activeHref`, or
|
|
33
|
+
* `endpoint.active: true`). Router-agnostic via `linkComponent`.
|
|
34
|
+
*
|
|
35
|
+
* @typedef {Object} ApiEndpoint
|
|
36
|
+
* @property {string} method HTTP method — drives the MethodBadge color
|
|
37
|
+
* @property {string} label endpoint display name
|
|
38
|
+
* @property {string} [href]
|
|
39
|
+
* @property {boolean} [active]
|
|
40
|
+
*
|
|
41
|
+
* @param {{ sections: { title?: string, icon?: string|React.ReactNode,
|
|
42
|
+
* endpoints: ApiEndpoint[] }[], activeHref?: string,
|
|
43
|
+
* linkComponent?: React.ElementType, className?: string }} props
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
const Ctx = React.createContext({ activeHref: undefined, Link: 'a' });
|
|
47
|
+
|
|
48
|
+
function Icon({ icon }) {
|
|
49
|
+
const node = resolveIcon(icon);
|
|
50
|
+
return node ? (
|
|
51
|
+
<span className="velu-api-sidebar__icon" aria-hidden="true">
|
|
52
|
+
{node}
|
|
53
|
+
</span>
|
|
54
|
+
) : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function Endpoint({ endpoint }) {
|
|
58
|
+
const { activeHref, Link } = React.useContext(Ctx);
|
|
59
|
+
const { method = 'GET', label, href = '#', active } = endpoint;
|
|
60
|
+
const isActive = Boolean(
|
|
61
|
+
active || (href != null && href === activeHref),
|
|
62
|
+
);
|
|
63
|
+
const cls = `velu-api-sidebar__item${
|
|
64
|
+
isActive ? ' velu-api-sidebar__item--active' : ''
|
|
65
|
+
}`;
|
|
66
|
+
return (
|
|
67
|
+
<li>
|
|
68
|
+
<Link
|
|
69
|
+
className={cls}
|
|
70
|
+
href={href}
|
|
71
|
+
aria-current={isActive ? 'page' : undefined}
|
|
72
|
+
>
|
|
73
|
+
<MethodBadge method={method} />
|
|
74
|
+
<span className="velu-api-sidebar__label">{label}</span>
|
|
75
|
+
</Link>
|
|
76
|
+
</li>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export default function ApiSidebar({
|
|
81
|
+
sections = [],
|
|
82
|
+
activeHref,
|
|
83
|
+
linkComponent = 'a',
|
|
84
|
+
className = '',
|
|
85
|
+
...rest
|
|
86
|
+
}) {
|
|
87
|
+
const ctx = React.useMemo(
|
|
88
|
+
() => ({ activeHref, Link: linkComponent }),
|
|
89
|
+
[activeHref, linkComponent],
|
|
90
|
+
);
|
|
91
|
+
return (
|
|
92
|
+
<Ctx.Provider value={ctx}>
|
|
93
|
+
<Stack
|
|
94
|
+
as="nav"
|
|
95
|
+
space="var(--s1)"
|
|
96
|
+
className={`velu-api-sidebar ${className}`.trim()}
|
|
97
|
+
aria-label="API reference"
|
|
98
|
+
{...rest}
|
|
99
|
+
>
|
|
100
|
+
{sections.map((section, i) => (
|
|
101
|
+
<React.Fragment key={i}>
|
|
102
|
+
{section.title != null && (
|
|
103
|
+
<h5 className="velu-api-sidebar__section">
|
|
104
|
+
<Icon icon={section.icon} />
|
|
105
|
+
{section.title}
|
|
106
|
+
</h5>
|
|
107
|
+
)}
|
|
108
|
+
<Stack
|
|
109
|
+
as="ul"
|
|
110
|
+
space="var(--s0)"
|
|
111
|
+
className="velu-api-sidebar__list"
|
|
112
|
+
>
|
|
113
|
+
{(section.endpoints ?? []).map((ep, j) => (
|
|
114
|
+
<Endpoint key={j} endpoint={ep} />
|
|
115
|
+
))}
|
|
116
|
+
</Stack>
|
|
117
|
+
</React.Fragment>
|
|
118
|
+
))}
|
|
119
|
+
</Stack>
|
|
120
|
+
</Ctx.Provider>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import resolveIcon from '../lib/resolveIcon.jsx';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* AskBar — an "ask a question" input card.
|
|
6
|
-
*
|
|
7
|
-
* <AskBar onSubmit={(q) => …} />
|
|
8
|
-
*
|
|
9
|
-
* The component owns only the card visuals (border, glow-on-focus,
|
|
10
|
-
* multi-line input, circular send button). PLACEMENT is the consumer's
|
|
11
|
-
* job — the docs page pins it `position: absolute` to the bottom of
|
|
12
|
-
* the article column so content scrolls behind its opaque surface.
|
|
13
|
-
*
|
|
14
|
-
* - `placeholder` input placeholder (default "Ask a question…")
|
|
15
|
-
* - `onSubmit(value)` fired on the send button or Enter (Shift+Enter
|
|
16
|
-
* inserts a newline). Dummy-friendly — omit it and the bar is inert.
|
|
17
|
-
*
|
|
18
|
-
* The send affordance is a circular accent button with an up-arrow;
|
|
19
|
-
* the card border glows in the accent color while the input is focused.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export default function AskBar({
|
|
23
|
-
placeholder = 'Ask a question…',
|
|
24
|
-
onSubmit,
|
|
25
|
-
className = '',
|
|
26
|
-
...rest
|
|
27
|
-
}) {
|
|
28
|
-
const [value, setValue] = useState('');
|
|
29
|
-
|
|
30
|
-
function submit() {
|
|
31
|
-
const q = value.trim();
|
|
32
|
-
if (!q) return;
|
|
33
|
-
onSubmit?.(q);
|
|
34
|
-
setValue('');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function onKeyDown(e) {
|
|
38
|
-
// Enter sends; Shift+Enter inserts a newline.
|
|
39
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
40
|
-
e.preventDefault();
|
|
41
|
-
submit();
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Disable the send button until there's non-whitespace input. The
|
|
46
|
-
// CSS reads :disabled to render it muted + un-hoverable; React reads
|
|
47
|
-
// the attribute to short-circuit clicks too.
|
|
48
|
-
const isEmpty = value.trim().length === 0;
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<div className={`velu-ask-bar ${className}`.trim()} {...rest}>
|
|
52
|
-
<textarea
|
|
53
|
-
className="velu-ask-bar__input"
|
|
54
|
-
placeholder={placeholder}
|
|
55
|
-
value={value}
|
|
56
|
-
onChange={(e) => setValue(e.target.value)}
|
|
57
|
-
onKeyDown={onKeyDown}
|
|
58
|
-
rows={2}
|
|
59
|
-
/>
|
|
60
|
-
<button
|
|
61
|
-
type="button"
|
|
62
|
-
className="velu-ask-bar__send"
|
|
63
|
-
onClick={submit}
|
|
64
|
-
disabled={isEmpty}
|
|
65
|
-
aria-label="Ask"
|
|
66
|
-
>
|
|
67
|
-
{resolveIcon('arrow-up', { size: '1em' })}
|
|
68
|
-
</button>
|
|
69
|
-
</div>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import resolveIcon from '../lib/resolveIcon.jsx';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AskBar — an "ask a question" input card.
|
|
6
|
+
*
|
|
7
|
+
* <AskBar onSubmit={(q) => …} />
|
|
8
|
+
*
|
|
9
|
+
* The component owns only the card visuals (border, glow-on-focus,
|
|
10
|
+
* multi-line input, circular send button). PLACEMENT is the consumer's
|
|
11
|
+
* job — the docs page pins it `position: absolute` to the bottom of
|
|
12
|
+
* the article column so content scrolls behind its opaque surface.
|
|
13
|
+
*
|
|
14
|
+
* - `placeholder` input placeholder (default "Ask a question…")
|
|
15
|
+
* - `onSubmit(value)` fired on the send button or Enter (Shift+Enter
|
|
16
|
+
* inserts a newline). Dummy-friendly — omit it and the bar is inert.
|
|
17
|
+
*
|
|
18
|
+
* The send affordance is a circular accent button with an up-arrow;
|
|
19
|
+
* the card border glows in the accent color while the input is focused.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export default function AskBar({
|
|
23
|
+
placeholder = 'Ask a question…',
|
|
24
|
+
onSubmit,
|
|
25
|
+
className = '',
|
|
26
|
+
...rest
|
|
27
|
+
}) {
|
|
28
|
+
const [value, setValue] = useState('');
|
|
29
|
+
|
|
30
|
+
function submit() {
|
|
31
|
+
const q = value.trim();
|
|
32
|
+
if (!q) return;
|
|
33
|
+
onSubmit?.(q);
|
|
34
|
+
setValue('');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function onKeyDown(e) {
|
|
38
|
+
// Enter sends; Shift+Enter inserts a newline.
|
|
39
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
submit();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Disable the send button until there's non-whitespace input. The
|
|
46
|
+
// CSS reads :disabled to render it muted + un-hoverable; React reads
|
|
47
|
+
// the attribute to short-circuit clicks too.
|
|
48
|
+
const isEmpty = value.trim().length === 0;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className={`velu-ask-bar ${className}`.trim()} {...rest}>
|
|
52
|
+
<textarea
|
|
53
|
+
className="velu-ask-bar__input"
|
|
54
|
+
placeholder={placeholder}
|
|
55
|
+
value={value}
|
|
56
|
+
onChange={(e) => setValue(e.target.value)}
|
|
57
|
+
onKeyDown={onKeyDown}
|
|
58
|
+
rows={2}
|
|
59
|
+
/>
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
className="velu-ask-bar__send"
|
|
63
|
+
onClick={submit}
|
|
64
|
+
disabled={isEmpty}
|
|
65
|
+
aria-label="Ask"
|
|
66
|
+
>
|
|
67
|
+
{resolveIcon('arrow-up', { size: '1em' })}
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|