@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veluai/velu",
3
- "version": "0.2.43",
3
+ "version": "0.2.44",
4
4
  "type": "module",
5
5
  "bin": "./dist/cli.js",
6
6
  "publishConfig": {
@@ -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, href?: 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
- {img && (
78
- <div className="velu-card__media" data-component-part="card-image">
79
- <img src={img} alt={imgAlt} loading="lazy" />
80
- </div>
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() { return null; }
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: var(--s-2);
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). NOT used in headers — those render the
11
- * wordmark alone (PageHeader's VeluMark stub stays null).
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 (