@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,114 +1,114 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import resolveIcon from '../lib/resolveIcon.jsx';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Callout — admonition box (note/warning/info/tip/check/danger/callout).
|
|
6
|
-
*
|
|
7
|
-
* Fully tokenized: each `type` maps to its Figma bg + stroke token pair
|
|
8
|
-
* (base.css, light + dark), so theming is automatic via [data-theme]. Border
|
|
9
|
-
* + icon use the stroke; surface uses the bg; body uses --text-color.
|
|
10
|
-
* Spacing/radius from the modular scale.
|
|
11
|
-
*
|
|
12
|
-
* Layout: the icon is FLOATED into the text flow (not a fixed column) — it
|
|
13
|
-
* sits on the first line and the text wraps beside it, then subsequent/wrapped
|
|
14
|
-
* lines flow back UNDER the icon (no hanging indent). `display: flow-root`
|
|
15
|
-
* contains the float cleanly (no clipping, no clearfix hack).
|
|
16
|
-
*
|
|
17
|
-
* Content-driven & extensible: `icon` is a **lucide icon id string**
|
|
18
|
-
* (kebab-case), resolved by the shared resolveIcon util (also accepts a
|
|
19
|
-
* node). `stroke` / `bg` override the variant colors for custom callouts.
|
|
20
|
-
*
|
|
21
|
-
* SSR-safe & presentational (no effects/refs).
|
|
22
|
-
*
|
|
23
|
-
* @param {{ type?: 'note'|'warning'|'info'|'tip'|'check'|'danger'|'callout',
|
|
24
|
-
* icon?: string|React.ReactNode, stroke?: string, bg?: string,
|
|
25
|
-
* children: React.ReactNode, className?: string }} props
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
// Variant icon defaults are lucide IDS (not components) → one resolution path.
|
|
29
|
-
const VARIANTS = {
|
|
30
|
-
note: { bg: '--note-bg-color', stroke: '--note-stroke-color', icon: 'pencil' },
|
|
31
|
-
warning: {
|
|
32
|
-
bg: '--warning-bg-color',
|
|
33
|
-
stroke: '--warning-stroke-color',
|
|
34
|
-
icon: 'triangle-alert',
|
|
35
|
-
},
|
|
36
|
-
info: { bg: '--info-bg-color', stroke: '--info-stroke-color', icon: 'info' },
|
|
37
|
-
tip: {
|
|
38
|
-
bg: '--tip-bg-color',
|
|
39
|
-
stroke: '--tip-stroke-color',
|
|
40
|
-
icon: 'lightbulb',
|
|
41
|
-
},
|
|
42
|
-
check: {
|
|
43
|
-
bg: '--check-bg-color',
|
|
44
|
-
stroke: '--check-stroke-color',
|
|
45
|
-
icon: 'circle-check',
|
|
46
|
-
},
|
|
47
|
-
danger: {
|
|
48
|
-
bg: '--danger-bg-color',
|
|
49
|
-
stroke: '--danger-stroke-color',
|
|
50
|
-
icon: 'shield-alert',
|
|
51
|
-
},
|
|
52
|
-
callout: {
|
|
53
|
-
bg: '--callout-bg-color',
|
|
54
|
-
stroke: '--callout-stroke-color',
|
|
55
|
-
icon: 'message-circle',
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export default function Callout({
|
|
60
|
-
type = 'note',
|
|
61
|
-
icon,
|
|
62
|
-
stroke,
|
|
63
|
-
bg,
|
|
64
|
-
children,
|
|
65
|
-
className = '',
|
|
66
|
-
style,
|
|
67
|
-
...rest
|
|
68
|
-
}) {
|
|
69
|
-
const v = VARIANTS[type] || VARIANTS.note;
|
|
70
|
-
const strokeColor = stroke ?? `var(${v.stroke})`;
|
|
71
|
-
const bgColor = bg ?? `var(${v.bg})`;
|
|
72
|
-
// size only; stroke weight comes from the global --icon-stroke token
|
|
73
|
-
// (.lucide rule in base.css), not set per-icon.
|
|
74
|
-
const iconNode = resolveIcon(icon ?? v.icon, { size: '1em' });
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<div
|
|
78
|
-
className={className}
|
|
79
|
-
style={{
|
|
80
|
-
display: 'flow-root', // contains the floated icon, no clipping
|
|
81
|
-
padding: 'var(--s0) var(--s3)',
|
|
82
|
-
background: bgColor,
|
|
83
|
-
border: `var(--border-width) solid ${strokeColor}`,
|
|
84
|
-
borderRadius: 'var(--radius-sm)',
|
|
85
|
-
color: 'var(--text-color)',
|
|
86
|
-
fontSize: 'var(--f-body)',
|
|
87
|
-
lineHeight: 'var(--lh-body)',
|
|
88
|
-
...style,
|
|
89
|
-
}}
|
|
90
|
-
{...rest}
|
|
91
|
-
>
|
|
92
|
-
{iconNode && (
|
|
93
|
-
<span
|
|
94
|
-
aria-hidden="true"
|
|
95
|
-
style={{
|
|
96
|
-
float: 'left',
|
|
97
|
-
display: 'inline-flex',
|
|
98
|
-
alignItems: 'center',
|
|
99
|
-
// one line-box tall (body font-size × body line-height token) so
|
|
100
|
-
// the icon is centered on the FIRST line; text wraps beside it
|
|
101
|
-
// then flows back under it (no hanging indent).
|
|
102
|
-
blockSize: 'calc(var(--f-body) * var(--lh-body))',
|
|
103
|
-
marginInlineEnd: 'var(--s-1)',
|
|
104
|
-
color: strokeColor,
|
|
105
|
-
fontSize: 'var(--f-body)',
|
|
106
|
-
}}
|
|
107
|
-
>
|
|
108
|
-
{iconNode}
|
|
109
|
-
</span>
|
|
110
|
-
)}
|
|
111
|
-
{children}
|
|
112
|
-
</div>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import resolveIcon from '../lib/resolveIcon.jsx';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Callout — admonition box (note/warning/info/tip/check/danger/callout).
|
|
6
|
+
*
|
|
7
|
+
* Fully tokenized: each `type` maps to its Figma bg + stroke token pair
|
|
8
|
+
* (base.css, light + dark), so theming is automatic via [data-theme]. Border
|
|
9
|
+
* + icon use the stroke; surface uses the bg; body uses --text-color.
|
|
10
|
+
* Spacing/radius from the modular scale.
|
|
11
|
+
*
|
|
12
|
+
* Layout: the icon is FLOATED into the text flow (not a fixed column) — it
|
|
13
|
+
* sits on the first line and the text wraps beside it, then subsequent/wrapped
|
|
14
|
+
* lines flow back UNDER the icon (no hanging indent). `display: flow-root`
|
|
15
|
+
* contains the float cleanly (no clipping, no clearfix hack).
|
|
16
|
+
*
|
|
17
|
+
* Content-driven & extensible: `icon` is a **lucide icon id string**
|
|
18
|
+
* (kebab-case), resolved by the shared resolveIcon util (also accepts a
|
|
19
|
+
* node). `stroke` / `bg` override the variant colors for custom callouts.
|
|
20
|
+
*
|
|
21
|
+
* SSR-safe & presentational (no effects/refs).
|
|
22
|
+
*
|
|
23
|
+
* @param {{ type?: 'note'|'warning'|'info'|'tip'|'check'|'danger'|'callout',
|
|
24
|
+
* icon?: string|React.ReactNode, stroke?: string, bg?: string,
|
|
25
|
+
* children: React.ReactNode, className?: string }} props
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// Variant icon defaults are lucide IDS (not components) → one resolution path.
|
|
29
|
+
const VARIANTS = {
|
|
30
|
+
note: { bg: '--note-bg-color', stroke: '--note-stroke-color', icon: 'pencil' },
|
|
31
|
+
warning: {
|
|
32
|
+
bg: '--warning-bg-color',
|
|
33
|
+
stroke: '--warning-stroke-color',
|
|
34
|
+
icon: 'triangle-alert',
|
|
35
|
+
},
|
|
36
|
+
info: { bg: '--info-bg-color', stroke: '--info-stroke-color', icon: 'info' },
|
|
37
|
+
tip: {
|
|
38
|
+
bg: '--tip-bg-color',
|
|
39
|
+
stroke: '--tip-stroke-color',
|
|
40
|
+
icon: 'lightbulb',
|
|
41
|
+
},
|
|
42
|
+
check: {
|
|
43
|
+
bg: '--check-bg-color',
|
|
44
|
+
stroke: '--check-stroke-color',
|
|
45
|
+
icon: 'circle-check',
|
|
46
|
+
},
|
|
47
|
+
danger: {
|
|
48
|
+
bg: '--danger-bg-color',
|
|
49
|
+
stroke: '--danger-stroke-color',
|
|
50
|
+
icon: 'shield-alert',
|
|
51
|
+
},
|
|
52
|
+
callout: {
|
|
53
|
+
bg: '--callout-bg-color',
|
|
54
|
+
stroke: '--callout-stroke-color',
|
|
55
|
+
icon: 'message-circle',
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default function Callout({
|
|
60
|
+
type = 'note',
|
|
61
|
+
icon,
|
|
62
|
+
stroke,
|
|
63
|
+
bg,
|
|
64
|
+
children,
|
|
65
|
+
className = '',
|
|
66
|
+
style,
|
|
67
|
+
...rest
|
|
68
|
+
}) {
|
|
69
|
+
const v = VARIANTS[type] || VARIANTS.note;
|
|
70
|
+
const strokeColor = stroke ?? `var(${v.stroke})`;
|
|
71
|
+
const bgColor = bg ?? `var(${v.bg})`;
|
|
72
|
+
// size only; stroke weight comes from the global --icon-stroke token
|
|
73
|
+
// (.lucide rule in base.css), not set per-icon.
|
|
74
|
+
const iconNode = resolveIcon(icon ?? v.icon, { size: '1em' });
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div
|
|
78
|
+
className={className}
|
|
79
|
+
style={{
|
|
80
|
+
display: 'flow-root', // contains the floated icon, no clipping
|
|
81
|
+
padding: 'var(--s0) var(--s3)',
|
|
82
|
+
background: bgColor,
|
|
83
|
+
border: `var(--border-width) solid ${strokeColor}`,
|
|
84
|
+
borderRadius: 'var(--radius-sm)',
|
|
85
|
+
color: 'var(--text-color)',
|
|
86
|
+
fontSize: 'var(--f-body)',
|
|
87
|
+
lineHeight: 'var(--lh-body)',
|
|
88
|
+
...style,
|
|
89
|
+
}}
|
|
90
|
+
{...rest}
|
|
91
|
+
>
|
|
92
|
+
{iconNode && (
|
|
93
|
+
<span
|
|
94
|
+
aria-hidden="true"
|
|
95
|
+
style={{
|
|
96
|
+
float: 'left',
|
|
97
|
+
display: 'inline-flex',
|
|
98
|
+
alignItems: 'center',
|
|
99
|
+
// one line-box tall (body font-size × body line-height token) so
|
|
100
|
+
// the icon is centered on the FIRST line; text wraps beside it
|
|
101
|
+
// then flows back under it (no hanging indent).
|
|
102
|
+
blockSize: 'calc(var(--f-body) * var(--lh-body))',
|
|
103
|
+
marginInlineEnd: 'var(--s-1)',
|
|
104
|
+
color: strokeColor,
|
|
105
|
+
fontSize: 'var(--f-body)',
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
{iconNode}
|
|
109
|
+
</span>
|
|
110
|
+
)}
|
|
111
|
+
{children}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -1,131 +1,131 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import resolveIcon from '../lib/resolveIcon.jsx';
|
|
3
|
-
import Switcher from '../primitives/Switcher.jsx';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Card + CardGroup — content / horizontal / image / CTA cards.
|
|
7
|
-
*
|
|
8
|
-
* The card itself is NEVER a link and has NO hover state. The only
|
|
9
|
-
* interactive element is the CTA: pass `cta={{ label, href }}` to render a
|
|
10
|
-
* single "Click Here ›" link. Everything else is static presentation.
|
|
11
|
-
*
|
|
12
|
-
* Fully tokenized (border-width, border-color, radius scale, spacing, type,
|
|
13
|
-
* line-height, accent, icon-stroke) → light/dark automatic via [data-theme].
|
|
14
|
-
* SSR-safe & presentational.
|
|
15
|
-
*
|
|
16
|
-
* Variants (combinable):
|
|
17
|
-
* - basic: icon (accent, --f-h2) + title (--f-h4) + body
|
|
18
|
-
* - `horizontal`: icon vertically centered on the left, title+body right
|
|
19
|
-
* - `img`: image fills the top, clipped to the radius
|
|
20
|
-
* - `cta`: { label, href } → the single clickable "Click Here ›" link
|
|
21
|
-
*
|
|
22
|
-
* `icon` is a lucide id string (shared resolveIcon).
|
|
23
|
-
*
|
|
24
|
-
* @param {{ title?: React.ReactNode, icon?: string, horizontal?: boolean,
|
|
25
|
-
* img?: string, imgAlt?: string,
|
|
26
|
-
* cta?: {label: React.ReactNode, href: string},
|
|
27
|
-
* children?: React.ReactNode, className?: string }} props
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
export function Card({
|
|
31
|
-
title,
|
|
32
|
-
icon,
|
|
33
|
-
horizontal = false,
|
|
34
|
-
img,
|
|
35
|
-
imgAlt = '',
|
|
36
|
-
cta,
|
|
37
|
-
children,
|
|
38
|
-
className = '',
|
|
39
|
-
...rest
|
|
40
|
-
}) {
|
|
41
|
-
const cls = [
|
|
42
|
-
'velu-card',
|
|
43
|
-
horizontal && 'velu-card--horizontal',
|
|
44
|
-
img && 'velu-card--image',
|
|
45
|
-
cta && 'velu-card--cta',
|
|
46
|
-
className,
|
|
47
|
-
]
|
|
48
|
-
.filter(Boolean)
|
|
49
|
-
.join(' ');
|
|
50
|
-
|
|
51
|
-
const iconEl = icon ? (
|
|
52
|
-
<span className="velu-card__icon" aria-hidden="true">
|
|
53
|
-
{resolveIcon(icon, { size: '1em' })}
|
|
54
|
-
</span>
|
|
55
|
-
) : null;
|
|
56
|
-
|
|
57
|
-
const ctaEl = cta ? (
|
|
58
|
-
<a className="velu-card__cta" href={cta.href}>
|
|
59
|
-
{cta.label}
|
|
60
|
-
<span className="velu-card__cta-icon" aria-hidden="true">
|
|
61
|
-
{resolveIcon('chevron-right', { size: '1em' })}
|
|
62
|
-
</span>
|
|
63
|
-
</a>
|
|
64
|
-
) : null;
|
|
65
|
-
|
|
66
|
-
const titleEl = title ? (
|
|
67
|
-
<div className="velu-card__title">{title}</div>
|
|
68
|
-
) : null;
|
|
69
|
-
const textEl = children ? (
|
|
70
|
-
<div className="velu-card__text">{children}</div>
|
|
71
|
-
) : null;
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<div className={cls} {...rest}>
|
|
75
|
-
{img && (
|
|
76
|
-
<div className="velu-card__media">
|
|
77
|
-
<img src={img} alt={imgAlt} loading="lazy" />
|
|
78
|
-
</div>
|
|
79
|
-
)}
|
|
80
|
-
|
|
81
|
-
<div className="velu-card__body">
|
|
82
|
-
{horizontal ? (
|
|
83
|
-
<>
|
|
84
|
-
{iconEl}
|
|
85
|
-
<div className="velu-card__content">
|
|
86
|
-
{titleEl}
|
|
87
|
-
{textEl}
|
|
88
|
-
{ctaEl}
|
|
89
|
-
</div>
|
|
90
|
-
</>
|
|
91
|
-
) : (
|
|
92
|
-
<>
|
|
93
|
-
{!img ? iconEl : null}
|
|
94
|
-
{titleEl}
|
|
95
|
-
{textEl}
|
|
96
|
-
{ctaEl}
|
|
97
|
-
</>
|
|
98
|
-
)}
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* CardGroup — a row of Cards that switches to a FULL vertical stack the
|
|
106
|
-
* moment any one card would fall below the per-card minimum. Thin wrapper
|
|
107
|
-
* over the Switcher primitive: just supplies `min` (Switcher does the
|
|
108
|
-
* count-aware threshold math). `limit` hard-caps items per row.
|
|
109
|
-
*/
|
|
110
|
-
export function CardGroup({
|
|
111
|
-
space = 'var(--s0)',
|
|
112
|
-
min = 'var(--card-min, 18ch)',
|
|
113
|
-
limit = 3,
|
|
114
|
-
children,
|
|
115
|
-
className = '',
|
|
116
|
-
...rest
|
|
117
|
-
}) {
|
|
118
|
-
return (
|
|
119
|
-
<Switcher
|
|
120
|
-
className={`velu-card-group ${className}`.trim()}
|
|
121
|
-
space={space}
|
|
122
|
-
min={min}
|
|
123
|
-
limit={limit}
|
|
124
|
-
{...rest}
|
|
125
|
-
>
|
|
126
|
-
{children}
|
|
127
|
-
</Switcher>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export default Card;
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import resolveIcon from '../lib/resolveIcon.jsx';
|
|
3
|
+
import Switcher from '../primitives/Switcher.jsx';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Card + CardGroup — content / horizontal / image / CTA cards.
|
|
7
|
+
*
|
|
8
|
+
* The card itself is NEVER a link and has NO hover state. The only
|
|
9
|
+
* interactive element is the CTA: pass `cta={{ label, href }}` to render a
|
|
10
|
+
* single "Click Here ›" link. Everything else is static presentation.
|
|
11
|
+
*
|
|
12
|
+
* Fully tokenized (border-width, border-color, radius scale, spacing, type,
|
|
13
|
+
* line-height, accent, icon-stroke) → light/dark automatic via [data-theme].
|
|
14
|
+
* SSR-safe & presentational.
|
|
15
|
+
*
|
|
16
|
+
* Variants (combinable):
|
|
17
|
+
* - basic: icon (accent, --f-h2) + title (--f-h4) + body
|
|
18
|
+
* - `horizontal`: icon vertically centered on the left, title+body right
|
|
19
|
+
* - `img`: image fills the top, clipped to the radius
|
|
20
|
+
* - `cta`: { label, href } → the single clickable "Click Here ›" link
|
|
21
|
+
*
|
|
22
|
+
* `icon` is a lucide id string (shared resolveIcon).
|
|
23
|
+
*
|
|
24
|
+
* @param {{ title?: React.ReactNode, icon?: string, horizontal?: boolean,
|
|
25
|
+
* img?: string, imgAlt?: string,
|
|
26
|
+
* cta?: {label: React.ReactNode, href: string},
|
|
27
|
+
* children?: React.ReactNode, className?: string }} props
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export function Card({
|
|
31
|
+
title,
|
|
32
|
+
icon,
|
|
33
|
+
horizontal = false,
|
|
34
|
+
img,
|
|
35
|
+
imgAlt = '',
|
|
36
|
+
cta,
|
|
37
|
+
children,
|
|
38
|
+
className = '',
|
|
39
|
+
...rest
|
|
40
|
+
}) {
|
|
41
|
+
const cls = [
|
|
42
|
+
'velu-card',
|
|
43
|
+
horizontal && 'velu-card--horizontal',
|
|
44
|
+
img && 'velu-card--image',
|
|
45
|
+
cta && 'velu-card--cta',
|
|
46
|
+
className,
|
|
47
|
+
]
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.join(' ');
|
|
50
|
+
|
|
51
|
+
const iconEl = icon ? (
|
|
52
|
+
<span className="velu-card__icon" aria-hidden="true">
|
|
53
|
+
{resolveIcon(icon, { size: '1em' })}
|
|
54
|
+
</span>
|
|
55
|
+
) : null;
|
|
56
|
+
|
|
57
|
+
const ctaEl = cta ? (
|
|
58
|
+
<a className="velu-card__cta" href={cta.href}>
|
|
59
|
+
{cta.label}
|
|
60
|
+
<span className="velu-card__cta-icon" aria-hidden="true">
|
|
61
|
+
{resolveIcon('chevron-right', { size: '1em' })}
|
|
62
|
+
</span>
|
|
63
|
+
</a>
|
|
64
|
+
) : null;
|
|
65
|
+
|
|
66
|
+
const titleEl = title ? (
|
|
67
|
+
<div className="velu-card__title">{title}</div>
|
|
68
|
+
) : null;
|
|
69
|
+
const textEl = children ? (
|
|
70
|
+
<div className="velu-card__text">{children}</div>
|
|
71
|
+
) : null;
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className={cls} {...rest}>
|
|
75
|
+
{img && (
|
|
76
|
+
<div className="velu-card__media">
|
|
77
|
+
<img src={img} alt={imgAlt} loading="lazy" />
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
<div className="velu-card__body">
|
|
82
|
+
{horizontal ? (
|
|
83
|
+
<>
|
|
84
|
+
{iconEl}
|
|
85
|
+
<div className="velu-card__content">
|
|
86
|
+
{titleEl}
|
|
87
|
+
{textEl}
|
|
88
|
+
{ctaEl}
|
|
89
|
+
</div>
|
|
90
|
+
</>
|
|
91
|
+
) : (
|
|
92
|
+
<>
|
|
93
|
+
{!img ? iconEl : null}
|
|
94
|
+
{titleEl}
|
|
95
|
+
{textEl}
|
|
96
|
+
{ctaEl}
|
|
97
|
+
</>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* CardGroup — a row of Cards that switches to a FULL vertical stack the
|
|
106
|
+
* moment any one card would fall below the per-card minimum. Thin wrapper
|
|
107
|
+
* over the Switcher primitive: just supplies `min` (Switcher does the
|
|
108
|
+
* count-aware threshold math). `limit` hard-caps items per row.
|
|
109
|
+
*/
|
|
110
|
+
export function CardGroup({
|
|
111
|
+
space = 'var(--s0)',
|
|
112
|
+
min = 'var(--card-min, 18ch)',
|
|
113
|
+
limit = 3,
|
|
114
|
+
children,
|
|
115
|
+
className = '',
|
|
116
|
+
...rest
|
|
117
|
+
}) {
|
|
118
|
+
return (
|
|
119
|
+
<Switcher
|
|
120
|
+
className={`velu-card-group ${className}`.trim()}
|
|
121
|
+
space={space}
|
|
122
|
+
min={min}
|
|
123
|
+
limit={limit}
|
|
124
|
+
{...rest}
|
|
125
|
+
>
|
|
126
|
+
{children}
|
|
127
|
+
</Switcher>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export default Card;
|