@veluai/velu 0.2.25 → 0.2.28

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.
Files changed (114) hide show
  1. package/README.md +80 -80
  2. package/dist/cli.js +44 -44
  3. package/package.json +64 -64
  4. package/runtime/velu-ui/base.css +320 -320
  5. package/runtime/velu-ui/components/Accordion.jsx +64 -64
  6. package/runtime/velu-ui/components/ApiClient.jsx +207 -207
  7. package/runtime/velu-ui/components/ApiField.jsx +87 -87
  8. package/runtime/velu-ui/components/ApiPath.jsx +63 -63
  9. package/runtime/velu-ui/components/ApiReferencePage.jsx +384 -384
  10. package/runtime/velu-ui/components/ApiSamples.jsx +36 -36
  11. package/runtime/velu-ui/components/ApiSidebar.jsx +125 -125
  12. package/runtime/velu-ui/components/AskBar.jsx +71 -71
  13. package/runtime/velu-ui/components/Callout.jsx +114 -114
  14. package/runtime/velu-ui/components/Card.jsx +131 -131
  15. package/runtime/velu-ui/components/ChangelogFilters.jsx +75 -75
  16. package/runtime/velu-ui/components/Chatbot.jsx +972 -972
  17. package/runtime/velu-ui/components/CodeBlock.jsx +375 -375
  18. package/runtime/velu-ui/components/Columns.jsx +56 -56
  19. package/runtime/velu-ui/components/ContextMenu.jsx +327 -327
  20. package/runtime/velu-ui/components/ErrorCard.jsx +138 -138
  21. package/runtime/velu-ui/components/Field.jsx +81 -81
  22. package/runtime/velu-ui/components/Image.jsx +163 -163
  23. package/runtime/velu-ui/components/Logo.jsx +31 -31
  24. package/runtime/velu-ui/components/MethodBadge.jsx +31 -31
  25. package/runtime/velu-ui/components/NavSelect.jsx +108 -108
  26. package/runtime/velu-ui/components/NotFound.jsx +63 -63
  27. package/runtime/velu-ui/components/PageFeedback.jsx +219 -219
  28. package/runtime/velu-ui/components/PageFooter.jsx +145 -145
  29. package/runtime/velu-ui/components/PageHeader.jsx +422 -422
  30. package/runtime/velu-ui/components/PageNav.jsx +77 -77
  31. package/runtime/velu-ui/components/PoweredBy.jsx +51 -51
  32. package/runtime/velu-ui/components/Prompt.jsx +115 -115
  33. package/runtime/velu-ui/components/Search.jsx +460 -460
  34. package/runtime/velu-ui/components/Sidebar.jsx +246 -246
  35. package/runtime/velu-ui/components/SocialLinks.jsx +90 -90
  36. package/runtime/velu-ui/components/Steps.jsx +65 -65
  37. package/runtime/velu-ui/components/ThemeToggle.jsx +48 -48
  38. package/runtime/velu-ui/components/Toc.jsx +537 -537
  39. package/runtime/velu-ui/components/TocBar.jsx +195 -195
  40. package/runtime/velu-ui/components/Tree.jsx +87 -87
  41. package/runtime/velu-ui/components/TryItBar.jsx +102 -102
  42. package/runtime/velu-ui/components/Update.jsx +92 -92
  43. package/runtime/velu-ui/components/accordion.css +92 -92
  44. package/runtime/velu-ui/components/api-page.css +208 -208
  45. package/runtime/velu-ui/components/api.css +635 -635
  46. package/runtime/velu-ui/components/ask-bar.css +103 -103
  47. package/runtime/velu-ui/components/card.css +105 -105
  48. package/runtime/velu-ui/components/changelog-filters.css +43 -43
  49. package/runtime/velu-ui/components/chatbot.css +687 -687
  50. package/runtime/velu-ui/components/code-block.css +263 -263
  51. package/runtime/velu-ui/components/context-menu.css +199 -199
  52. package/runtime/velu-ui/components/docs-layout.css +822 -822
  53. package/runtime/velu-ui/components/field.css +82 -82
  54. package/runtime/velu-ui/components/image.css +237 -237
  55. package/runtime/velu-ui/components/nav-select.css +157 -157
  56. package/runtime/velu-ui/components/not-found.css +94 -94
  57. package/runtime/velu-ui/components/page-feedback.css +261 -261
  58. package/runtime/velu-ui/components/page-footer.css +130 -130
  59. package/runtime/velu-ui/components/page-header.css +558 -558
  60. package/runtime/velu-ui/components/page-nav.css +50 -50
  61. package/runtime/velu-ui/components/powered-by.css +92 -92
  62. package/runtime/velu-ui/components/prompt.css +99 -99
  63. package/runtime/velu-ui/components/search.css +307 -307
  64. package/runtime/velu-ui/components/sidebar.css +205 -205
  65. package/runtime/velu-ui/components/steps.css +77 -77
  66. package/runtime/velu-ui/components/theme-toggle.css +102 -102
  67. package/runtime/velu-ui/components/toc-bar.css +234 -234
  68. package/runtime/velu-ui/components/tree.css +49 -49
  69. package/runtime/velu-ui/components/update.css +111 -111
  70. package/runtime/velu-ui/index.js +58 -58
  71. package/runtime/velu-ui/lib/api-send.js +118 -118
  72. package/runtime/velu-ui/lib/brand-icons.jsx +103 -103
  73. package/runtime/velu-ui/lib/component-schemas.js +100 -100
  74. package/runtime/velu-ui/lib/copyText.js +64 -64
  75. package/runtime/velu-ui/lib/docs-assistant.js +273 -273
  76. package/runtime/velu-ui/lib/lang-icons.jsx +147 -147
  77. package/runtime/velu-ui/lib/page-feedback.js +72 -72
  78. package/runtime/velu-ui/lib/pagefind.js +116 -116
  79. package/runtime/velu-ui/lib/prism-langs.js +957 -957
  80. package/runtime/velu-ui/lib/prism-loader.js +74 -74
  81. package/runtime/velu-ui/lib/resolveIcon.jsx +29 -29
  82. package/runtime/velu-ui/lib/scrollIntoNearestView.js +66 -66
  83. package/runtime/velu-ui/mdx-components.jsx +107 -107
  84. package/runtime/velu-ui/primitives/Cluster.jsx +49 -49
  85. package/runtime/velu-ui/primitives/Stack.jsx +63 -63
  86. package/runtime/velu-ui/primitives/Switcher.jsx +57 -57
  87. package/runtime/velu-ui/primitives/stack.css +3 -3
  88. package/runtime/velu-ui/primitives/switcher.css +25 -25
  89. package/runtime/velu-ui/styles.css +48 -48
  90. package/runtime/velu-ui/tokens.css +4 -4
  91. package/schema/velu.schema.json +419 -419
  92. package/src/lib/extract-mdx-error.js +170 -170
  93. package/src/lib/issues.js +159 -159
  94. package/src/lib/known-components.js +35 -35
  95. package/src/navigation.js +459 -459
  96. package/src/runtime/App.jsx +1754 -1754
  97. package/src/runtime/ErrorBoundary.jsx +54 -54
  98. package/src/runtime/client-entry.jsx +27 -27
  99. package/src/runtime/server-entry.jsx +24 -24
  100. package/src/template.html +48 -48
  101. package/templates/starter/ai-tools/claude-code.mdx +26 -26
  102. package/templates/starter/ai-tools/cursor.mdx +17 -17
  103. package/templates/starter/api-reference/introduction.mdx +43 -43
  104. package/templates/starter/development.mdx +19 -19
  105. package/templates/starter/essentials/code.mdx +29 -29
  106. package/templates/starter/essentials/images.mdx +29 -29
  107. package/templates/starter/essentials/markdown.mdx +25 -25
  108. package/templates/starter/essentials/navigation.mdx +39 -39
  109. package/templates/starter/essentials/settings.mdx +30 -30
  110. package/templates/starter/favicon.svg +6 -6
  111. package/templates/starter/index.mdx +31 -31
  112. package/templates/starter/openapi.json +160 -160
  113. package/templates/starter/quickstart.mdx +31 -31
  114. package/templates/starter/velu.json +40 -40
@@ -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;
@@ -1,75 +1,75 @@
1
- import React, { useEffect, useState } from 'react';
2
-
3
- /**
4
- * ChangelogFilters — replaces the table of contents on a changelog page with a
5
- * tag filter. The tag set is passed in (collected at build time from each
6
- * <Update tags=...>); toggling a tag shows/hides the matching entries purely
7
- * from the DOM via the `data-update-tags` attribute each <Update> emits.
8
- *
9
- * An entry is shown when no tag is selected, or when it carries any selected
10
- * tag (OR semantics).
11
- *
12
- * @param {{ tags?: string[] }} props
13
- */
14
- export default function ChangelogFilters({ tags = [] }) {
15
- const [selected, setSelected] = useState([]);
16
-
17
- useEffect(() => {
18
- if (typeof document === 'undefined') return undefined;
19
- const sel = selected.map((s) => s.toLowerCase());
20
- const nodes = document.querySelectorAll('.velu-update');
21
- nodes.forEach((el) => {
22
- const own = (el.getAttribute('data-update-tags') || '')
23
- .split('|')
24
- .map((t) => t.trim().toLowerCase())
25
- .filter(Boolean);
26
- const visible = sel.length === 0 || sel.some((s) => own.includes(s));
27
- el.hidden = !visible;
28
- });
29
- return () => {
30
- document.querySelectorAll('.velu-update').forEach((el) => {
31
- el.hidden = false;
32
- });
33
- };
34
- }, [selected]);
35
-
36
- if (!tags.length) return null;
37
-
38
- const toggle = (tag) => {
39
- const lower = tag.toLowerCase();
40
- setSelected((prev) =>
41
- prev.some((s) => s.toLowerCase() === lower)
42
- ? prev.filter((s) => s.toLowerCase() !== lower)
43
- : [...prev, tag],
44
- );
45
- };
46
-
47
- return (
48
- <div className="velu-changelog-filters">
49
- <div className="velu-changelog-filters__heading">Filters</div>
50
- <div
51
- className="velu-changelog-filters__list"
52
- role="group"
53
- aria-label="Filter updates by tag"
54
- >
55
- {tags.map((tag) => {
56
- const active = selected.some((s) => s.toLowerCase() === tag.toLowerCase());
57
- return (
58
- <button
59
- key={tag}
60
- type="button"
61
- className={
62
- 'velu-changelog-filter' +
63
- (active ? ' velu-changelog-filter--active' : '')
64
- }
65
- aria-pressed={active}
66
- onClick={() => toggle(tag)}
67
- >
68
- {tag}
69
- </button>
70
- );
71
- })}
72
- </div>
73
- </div>
74
- );
75
- }
1
+ import React, { useEffect, useState } from 'react';
2
+
3
+ /**
4
+ * ChangelogFilters — replaces the table of contents on a changelog page with a
5
+ * tag filter. The tag set is passed in (collected at build time from each
6
+ * <Update tags=...>); toggling a tag shows/hides the matching entries purely
7
+ * from the DOM via the `data-update-tags` attribute each <Update> emits.
8
+ *
9
+ * An entry is shown when no tag is selected, or when it carries any selected
10
+ * tag (OR semantics).
11
+ *
12
+ * @param {{ tags?: string[] }} props
13
+ */
14
+ export default function ChangelogFilters({ tags = [] }) {
15
+ const [selected, setSelected] = useState([]);
16
+
17
+ useEffect(() => {
18
+ if (typeof document === 'undefined') return undefined;
19
+ const sel = selected.map((s) => s.toLowerCase());
20
+ const nodes = document.querySelectorAll('.velu-update');
21
+ nodes.forEach((el) => {
22
+ const own = (el.getAttribute('data-update-tags') || '')
23
+ .split('|')
24
+ .map((t) => t.trim().toLowerCase())
25
+ .filter(Boolean);
26
+ const visible = sel.length === 0 || sel.some((s) => own.includes(s));
27
+ el.hidden = !visible;
28
+ });
29
+ return () => {
30
+ document.querySelectorAll('.velu-update').forEach((el) => {
31
+ el.hidden = false;
32
+ });
33
+ };
34
+ }, [selected]);
35
+
36
+ if (!tags.length) return null;
37
+
38
+ const toggle = (tag) => {
39
+ const lower = tag.toLowerCase();
40
+ setSelected((prev) =>
41
+ prev.some((s) => s.toLowerCase() === lower)
42
+ ? prev.filter((s) => s.toLowerCase() !== lower)
43
+ : [...prev, tag],
44
+ );
45
+ };
46
+
47
+ return (
48
+ <div className="velu-changelog-filters">
49
+ <div className="velu-changelog-filters__heading">Filters</div>
50
+ <div
51
+ className="velu-changelog-filters__list"
52
+ role="group"
53
+ aria-label="Filter updates by tag"
54
+ >
55
+ {tags.map((tag) => {
56
+ const active = selected.some((s) => s.toLowerCase() === tag.toLowerCase());
57
+ return (
58
+ <button
59
+ key={tag}
60
+ type="button"
61
+ className={
62
+ 'velu-changelog-filter' +
63
+ (active ? ' velu-changelog-filter--active' : '')
64
+ }
65
+ aria-pressed={active}
66
+ onClick={() => toggle(tag)}
67
+ >
68
+ {tag}
69
+ </button>
70
+ );
71
+ })}
72
+ </div>
73
+ </div>
74
+ );
75
+ }