@veluai/velu 0.2.43 → 0.2.44
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/package.json
CHANGED
|
@@ -16,13 +16,19 @@ import Switcher from '../primitives/Switcher.jsx';
|
|
|
16
16
|
* Variants (combinable):
|
|
17
17
|
* - basic: icon (accent, --f-h2) + title (--f-h4) + body
|
|
18
18
|
* - `horizontal`: icon vertically centered on the left, title+body right
|
|
19
|
-
* - `img`: image fills the top, clipped to the radius
|
|
19
|
+
* - `img`: image fills the top, clipped to the radius. Covers both themes,
|
|
20
|
+
* unless `imgLight`/`imgDark` override per theme.
|
|
21
|
+
* - `imgLight` / `imgDark`: per-theme image. When the effective light and
|
|
22
|
+
* dark sources differ, both render and CSS shows the right one per
|
|
23
|
+
* `[data-theme]` (same pattern as Logo's light/dark swap). When only one
|
|
24
|
+
* of the three is present, it covers both themes.
|
|
20
25
|
* - `cta`: { label, href } → the single clickable "Click Here ›" link
|
|
21
26
|
*
|
|
22
27
|
* `icon` is a lucide id string (shared resolveIcon).
|
|
23
28
|
*
|
|
24
29
|
* @param {{ title?: React.ReactNode, icon?: string, horizontal?: boolean,
|
|
25
|
-
* img?: string, imgAlt?: string,
|
|
30
|
+
* img?: string, imgAlt?: string, imgLight?: string, imgDark?: string,
|
|
31
|
+
* href?: string,
|
|
26
32
|
* cta?: {label: React.ReactNode, href: string},
|
|
27
33
|
* children?: React.ReactNode, className?: string }} props
|
|
28
34
|
*/
|
|
@@ -33,6 +39,8 @@ export function Card({
|
|
|
33
39
|
horizontal = false,
|
|
34
40
|
img,
|
|
35
41
|
imgAlt = '',
|
|
42
|
+
imgLight,
|
|
43
|
+
imgDark,
|
|
36
44
|
cta,
|
|
37
45
|
href,
|
|
38
46
|
children,
|
|
@@ -74,11 +82,40 @@ export function Card({
|
|
|
74
82
|
return (
|
|
75
83
|
<card className={cls} {...rest}>
|
|
76
84
|
{href && !cta && <a className="velu-card__link" href={href} aria-label={typeof title === 'string' ? title : 'Read more'} />}
|
|
77
|
-
{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
{(() => {
|
|
86
|
+
const lightSrc = imgLight ?? img;
|
|
87
|
+
const darkSrc = imgDark ?? img;
|
|
88
|
+
if (lightSrc && darkSrc && lightSrc !== darkSrc) {
|
|
89
|
+
return (
|
|
90
|
+
<div className="velu-card__media" data-component-part="card-image">
|
|
91
|
+
<img
|
|
92
|
+
className="velu-card__img velu-card__img--light"
|
|
93
|
+
src={lightSrc}
|
|
94
|
+
alt={imgAlt}
|
|
95
|
+
loading="lazy"
|
|
96
|
+
/>
|
|
97
|
+
<img
|
|
98
|
+
className="velu-card__img velu-card__img--dark"
|
|
99
|
+
src={darkSrc}
|
|
100
|
+
alt=""
|
|
101
|
+
aria-hidden="true"
|
|
102
|
+
loading="lazy"
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
const singleSrc = imgLight ?? imgDark ?? img;
|
|
108
|
+
return singleSrc ? (
|
|
109
|
+
<div className="velu-card__media" data-component-part="card-image">
|
|
110
|
+
<img
|
|
111
|
+
className="velu-card__img"
|
|
112
|
+
src={singleSrc}
|
|
113
|
+
alt={imgAlt}
|
|
114
|
+
loading="lazy"
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
117
|
+
) : null;
|
|
118
|
+
})()}
|
|
82
119
|
|
|
83
120
|
<div className="velu-card__body" data-component-part="card-content-container">
|
|
84
121
|
{horizontal ? (
|
|
@@ -4,6 +4,7 @@ import resolveIcon from '../lib/resolveIcon.jsx';
|
|
|
4
4
|
import Stack from '../primitives/Stack.jsx';
|
|
5
5
|
import Cluster from '../primitives/Cluster.jsx';
|
|
6
6
|
import Logo from './Logo.jsx';
|
|
7
|
+
import VeluMarkArt from '../lib/velu-mark.jsx';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* PageHeader — top of the docs site.
|
|
@@ -51,9 +52,13 @@ import Logo from './Logo.jsx';
|
|
|
51
52
|
/* Velu brand mark. Color: --accent-color via the wrapping wordmark
|
|
52
53
|
span; the path uses currentColor. Exported so consumers (e.g. the
|
|
53
54
|
mobile nav drawer) can render the mark alongside its own chrome.
|
|
54
|
-
Only the default theme shows it — the vepa / thulir / aalam / nila
|
|
55
|
-
presets hide it via CSS (text-only branding). */
|
|
56
|
-
export function VeluMark(
|
|
55
|
+
Only the default theme shows it — the vepa / thulir / aalam / nila /
|
|
56
|
+
kadhir presets hide it via CSS (text-only branding). */
|
|
57
|
+
export function VeluMark({ className = '', ...rest }) {
|
|
58
|
+
return (
|
|
59
|
+
<VeluMarkArt className={`velu-header__mark ${className}`.trim()} {...rest} />
|
|
60
|
+
);
|
|
61
|
+
}
|
|
57
62
|
|
|
58
63
|
function ActionButton({ action, Link, id }) {
|
|
59
64
|
const { label, icon, kind = 'outlined', href, onClick, external, component } = action;
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
flex-direction: column;
|
|
47
47
|
gap: var(--s-2);
|
|
48
48
|
padding: var(--s0) var(--s3);
|
|
49
|
+
flex: 1; /* fill the card so a CTA can pin to the bottom across a row */
|
|
49
50
|
}
|
|
50
51
|
.velu-card--horizontal .velu-card__body {
|
|
51
52
|
flex-direction: row;
|
|
@@ -81,7 +82,8 @@
|
|
|
81
82
|
display: inline-flex;
|
|
82
83
|
align-items: center;
|
|
83
84
|
gap: var(--s-3);
|
|
84
|
-
margin-block-start:
|
|
85
|
+
margin-block-start: auto; /* pin to the card bottom so CTAs line up across a row */
|
|
86
|
+
padding-block-start: var(--s-2); /* keep the text gap when there is no free space */
|
|
85
87
|
font-weight: 500;
|
|
86
88
|
color: var(--text-color);
|
|
87
89
|
text-decoration: none;
|
|
@@ -106,6 +108,19 @@
|
|
|
106
108
|
block-size: auto;
|
|
107
109
|
object-fit: cover;
|
|
108
110
|
}
|
|
111
|
+
/* Per-theme swap (same pattern as Logo): light image by default, `img` under
|
|
112
|
+
[data-theme=dark]. `display` (not opacity) so the inactive one leaves the
|
|
113
|
+
a11y tree too. A lone `img` (no `imgLight`) has neither modifier and always
|
|
114
|
+
shows. */
|
|
115
|
+
.velu-card__media .velu-card__img--dark {
|
|
116
|
+
display: none;
|
|
117
|
+
}
|
|
118
|
+
[data-theme='dark'] .velu-card__media .velu-card__img--light {
|
|
119
|
+
display: none;
|
|
120
|
+
}
|
|
121
|
+
[data-theme='dark'] .velu-card__media .velu-card__img--dark {
|
|
122
|
+
display: block;
|
|
123
|
+
}
|
|
109
124
|
|
|
110
125
|
/* CardGroup layout is the Switcher primitive (composed in Card.jsx). Its
|
|
111
126
|
threshold is computed count-aware in Card.jsx so the row flips to a full
|
|
@@ -7,8 +7,9 @@ import React from 'react';
|
|
|
7
7
|
* light/dark themes.
|
|
8
8
|
*
|
|
9
9
|
* Used by the "Powered by Velu" credits (PoweredBy badge + vepa/thulir
|
|
10
|
-
* family footer bottom row)
|
|
11
|
-
*
|
|
10
|
+
* family footer bottom row), the header brand, and the mobile nav drawer.
|
|
11
|
+
* Theme presets that want text-only branding hide `.velu-header__mark`
|
|
12
|
+
* via CSS.
|
|
12
13
|
*/
|
|
13
14
|
export default function VeluMark({ className = '', ...rest }) {
|
|
14
15
|
return (
|