@veluai/velu 0.1.13 → 0.1.14

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 (96) hide show
  1. package/README.md +80 -80
  2. package/dist/cli.js +18 -18
  3. package/package.json +3 -2
  4. package/runtime/velu-ui/components/Accordion.jsx +64 -64
  5. package/runtime/velu-ui/components/ApiClient.jsx +121 -121
  6. package/runtime/velu-ui/components/ApiField.jsx +87 -87
  7. package/runtime/velu-ui/components/ApiPath.jsx +63 -63
  8. package/runtime/velu-ui/components/ApiSidebar.jsx +122 -122
  9. package/runtime/velu-ui/components/AskBar.jsx +71 -71
  10. package/runtime/velu-ui/components/Callout.jsx +114 -114
  11. package/runtime/velu-ui/components/Card.jsx +131 -131
  12. package/runtime/velu-ui/components/Chatbot.jsx +596 -596
  13. package/runtime/velu-ui/components/CodeBlock.jsx +375 -375
  14. package/runtime/velu-ui/components/Columns.jsx +56 -56
  15. package/runtime/velu-ui/components/ErrorCard.jsx +138 -138
  16. package/runtime/velu-ui/components/Field.jsx +81 -81
  17. package/runtime/velu-ui/components/Image.jsx +163 -163
  18. package/runtime/velu-ui/components/MethodBadge.jsx +31 -31
  19. package/runtime/velu-ui/components/NavSelect.jsx +108 -108
  20. package/runtime/velu-ui/components/PageFeedback.jsx +219 -219
  21. package/runtime/velu-ui/components/PageFooter.jsx +213 -213
  22. package/runtime/velu-ui/components/PageHeader.jsx +414 -414
  23. package/runtime/velu-ui/components/PageNav.jsx +77 -77
  24. package/runtime/velu-ui/components/PoweredBy.jsx +51 -51
  25. package/runtime/velu-ui/components/Prompt.jsx +115 -115
  26. package/runtime/velu-ui/components/Search.jsx +411 -366
  27. package/runtime/velu-ui/components/Sidebar.jsx +191 -191
  28. package/runtime/velu-ui/components/Steps.jsx +65 -65
  29. package/runtime/velu-ui/components/ThemeToggle.jsx +48 -48
  30. package/runtime/velu-ui/components/Toc.jsx +537 -537
  31. package/runtime/velu-ui/components/TocBar.jsx +195 -195
  32. package/runtime/velu-ui/components/Tree.jsx +87 -87
  33. package/runtime/velu-ui/components/TryItBar.jsx +90 -90
  34. package/runtime/velu-ui/components/accordion.css +92 -92
  35. package/runtime/velu-ui/components/api.css +479 -479
  36. package/runtime/velu-ui/components/ask-bar.css +94 -94
  37. package/runtime/velu-ui/components/card.css +105 -105
  38. package/runtime/velu-ui/components/chatbot.css +617 -617
  39. package/runtime/velu-ui/components/code-block.css +263 -263
  40. package/runtime/velu-ui/components/docs-layout.css +784 -775
  41. package/runtime/velu-ui/components/field.css +82 -82
  42. package/runtime/velu-ui/components/image.css +237 -237
  43. package/runtime/velu-ui/components/nav-select.css +157 -157
  44. package/runtime/velu-ui/components/page-feedback.css +241 -241
  45. package/runtime/velu-ui/components/page-footer.css +130 -130
  46. package/runtime/velu-ui/components/page-header.css +520 -520
  47. package/runtime/velu-ui/components/page-nav.css +50 -50
  48. package/runtime/velu-ui/components/powered-by.css +66 -66
  49. package/runtime/velu-ui/components/prompt.css +99 -99
  50. package/runtime/velu-ui/components/search.css +307 -307
  51. package/runtime/velu-ui/components/sidebar.css +144 -144
  52. package/runtime/velu-ui/components/steps.css +77 -77
  53. package/runtime/velu-ui/components/theme-toggle.css +101 -101
  54. package/runtime/velu-ui/components/toc-bar.css +234 -234
  55. package/runtime/velu-ui/components/tree.css +49 -49
  56. package/runtime/velu-ui/index.js +46 -46
  57. package/runtime/velu-ui/lib/component-schemas.js +100 -100
  58. package/runtime/velu-ui/lib/copyText.js +64 -64
  59. package/runtime/velu-ui/lib/lang-icons.jsx +147 -147
  60. package/runtime/velu-ui/lib/prism-langs.js +957 -957
  61. package/runtime/velu-ui/lib/prism-loader.js +74 -74
  62. package/runtime/velu-ui/lib/resolveIcon.jsx +29 -29
  63. package/runtime/velu-ui/lib/scrollIntoNearestView.js +66 -66
  64. package/runtime/velu-ui/mdx-components.jsx +105 -105
  65. package/runtime/velu-ui/primitives/Cluster.jsx +49 -49
  66. package/runtime/velu-ui/primitives/Stack.jsx +63 -63
  67. package/runtime/velu-ui/primitives/Switcher.jsx +57 -57
  68. package/runtime/velu-ui/primitives/stack.css +3 -3
  69. package/runtime/velu-ui/primitives/switcher.css +25 -25
  70. package/runtime/velu-ui/styles.css +43 -43
  71. package/runtime/velu-ui/tokens.css +4 -4
  72. package/schema/velu.schema.json +167 -167
  73. package/src/lib/extract-mdx-error.js +170 -170
  74. package/src/lib/issues.js +159 -159
  75. package/src/lib/known-components.js +34 -34
  76. package/src/navigation.js +434 -434
  77. package/src/runtime/App.jsx +1490 -1476
  78. package/src/runtime/ErrorBoundary.jsx +54 -54
  79. package/src/runtime/client-entry.jsx +22 -22
  80. package/src/runtime/server-entry.jsx +16 -16
  81. package/src/template.html +48 -48
  82. package/templates/starter/ai-tools/claude-code.mdx +26 -26
  83. package/templates/starter/ai-tools/cursor.mdx +17 -17
  84. package/templates/starter/api-reference/endpoint/create.mdx +24 -24
  85. package/templates/starter/api-reference/endpoint/get.mdx +27 -27
  86. package/templates/starter/api-reference/introduction.mdx +28 -28
  87. package/templates/starter/development.mdx +19 -19
  88. package/templates/starter/essentials/code.mdx +29 -29
  89. package/templates/starter/essentials/images.mdx +29 -29
  90. package/templates/starter/essentials/markdown.mdx +25 -25
  91. package/templates/starter/essentials/navigation.mdx +39 -39
  92. package/templates/starter/essentials/settings.mdx +30 -30
  93. package/templates/starter/favicon.svg +6 -6
  94. package/templates/starter/index.mdx +31 -31
  95. package/templates/starter/quickstart.mdx +31 -31
  96. package/templates/starter/velu.json +33 -33
@@ -1,191 +1,191 @@
1
- import React from 'react';
2
- import { ChevronRight, ExternalLink } from 'lucide-react';
3
- import Stack from '../primitives/Stack.jsx';
4
- import resolveIcon from '../lib/resolveIcon.jsx';
5
- import scrollIntoNearestView from '../lib/scrollIntoNearestView.js';
6
-
7
- /**
8
- * Sidebar — docs navigation. Data-driven, recursive (arbitrary depth),
9
- * tokenized, SSR-safe.
10
- *
11
- * Composition: ALL vertical rhythm is the Stack primitive (DRY) — faithful to
12
- * the source design's flat structure:
13
- * <Stack as="nav" space=--s1> ← headings + lists alternate here
14
- * <h5 section/> (uniform --s1 between every child)
15
- * <Stack as="ul" space=--s0> items </Stack>
16
- * nested groups: <Stack as="ul" space="0"> (spacing via item padding)
17
- * sidebar.css holds only what Stack cannot: rails, active state, item rows,
18
- * chevron, summary, icon sizing.
19
- *
20
- * Icons (`section.icon` / `item.icon`) are **lucide icon id strings**
21
- * (kebab-case), resolved by the shared resolveIcon util (a React node is
22
- * also accepted). Size + stroke come from .velu-sidebar__icon CSS.
23
- *
24
- * Selection is ROUTE-DRIVEN (SSR-correct, reload-safe, URL-shareable): an
25
- * item is active when its href === activeHref (or active:true). Ancestor
26
- * groups of the active item auto-open. Router-agnostic via `linkComponent`
27
- * (defaults to <a>); external items always render a real <a target=_blank>.
28
- *
29
- * @typedef {Object} SidebarItem
30
- * @property {string} label
31
- * @property {string} [href]
32
- * @property {string|React.ReactNode} [icon] // lucide id or node
33
- * @property {boolean} [active]
34
- * @property {boolean} [external]
35
- * @property {SidebarItem[]} [items]
36
- *
37
- * @param {{ sections: { title: string, icon?: string|React.ReactNode,
38
- * items: SidebarItem[] }[], activeHref?: string,
39
- * linkComponent?: React.ElementType, className?: string }} props
40
- */
41
-
42
- const SidebarCtx = React.createContext({ activeHref: undefined, Link: 'a' });
43
-
44
- function isActive(item, activeHref) {
45
- return Boolean(
46
- item.active || (item.href != null && item.href === activeHref)
47
- );
48
- }
49
-
50
- function hasActiveDescendant(item, activeHref) {
51
- if (isActive(item, activeHref)) return true;
52
- return (
53
- Array.isArray(item.items) &&
54
- item.items.some((c) => hasActiveDescendant(c, activeHref))
55
- );
56
- }
57
-
58
- function Icon({ icon }) {
59
- const node = resolveIcon(icon);
60
- return node ? (
61
- <span className="velu-sidebar__icon" aria-hidden="true">
62
- {node}
63
- </span>
64
- ) : null;
65
- }
66
-
67
- function Chevron() {
68
- return (
69
- <ChevronRight
70
- className="velu-sidebar__chevron"
71
- aria-hidden="true"
72
- focusable="false"
73
- />
74
- );
75
- }
76
-
77
- function ExternalIcon() {
78
- return (
79
- <ExternalLink
80
- className="velu-sidebar__icon"
81
- aria-hidden="true"
82
- focusable="false"
83
- />
84
- );
85
- }
86
-
87
- /** Recursive node. depth 0 = top-level; depth >= 1 = nested (subitem style). */
88
- function Node({ item, depth }) {
89
- const { activeHref, Link } = React.useContext(SidebarCtx);
90
- const { label, href = '#', icon, external, items } = item;
91
- const base = depth === 0 ? 'velu-sidebar__item' : 'velu-sidebar__subitem';
92
-
93
- if (items && items.length) {
94
- return (
95
- <li>
96
- <details open={hasActiveDescendant(item, activeHref) || undefined}>
97
- <summary className={`${base} velu-sidebar__summary`}>
98
- <Icon icon={icon} />
99
- <span className="velu-sidebar__label">{label}</span>
100
- <Chevron />
101
- </summary>
102
- {/* nested list: Stack with no gap (spacing = item padding-block),
103
- matching the source's stack-s */}
104
- <Stack as="ul" space="0" className="velu-sidebar__sublist">
105
- {items.map((child, i) => (
106
- <Node key={i} item={child} depth={depth + 1} />
107
- ))}
108
- </Stack>
109
- </details>
110
- </li>
111
- );
112
- }
113
-
114
- const active = isActive(item, activeHref);
115
- const cls = base + (active ? ' velu-sidebar__item--active' : '');
116
- const LinkTag = external ? 'a' : Link;
117
- const linkProps = external
118
- ? { href, target: '_blank', rel: 'noreferrer' }
119
- : { href };
120
-
121
- return (
122
- <li>
123
- <LinkTag
124
- className={cls}
125
- aria-current={active ? 'page' : undefined}
126
- {...linkProps}
127
- >
128
- <Icon icon={icon} />
129
- <span className="velu-sidebar__label">{label}</span>
130
- {external ? <ExternalIcon /> : null}
131
- </LinkTag>
132
- </li>
133
- );
134
- }
135
-
136
- export default function Sidebar({
137
- sections = [],
138
- activeHref,
139
- linkComponent = 'a',
140
- className = '',
141
- ...rest
142
- }) {
143
- const ctx = React.useMemo(
144
- () => ({ activeHref, Link: linkComponent }),
145
- [activeHref, linkComponent]
146
- );
147
-
148
- // Bring the active item into view inside the parent scroll container
149
- // whenever the active route changes — e.g. after navigation, the
150
- // selected page is already visible without the user scrolling the
151
- // sidebar manually. Uses the shared util (instead of native
152
- // scrollIntoView) so `scroll-padding-*` on the parent — which the
153
- // docs layout uses to mark the footer-eclipsed band as off-limits —
154
- // is reliably honoured.
155
- const rootRef = React.useRef(null);
156
- React.useEffect(() => {
157
- const el = rootRef.current?.querySelector('[aria-current="page"]');
158
- scrollIntoNearestView(el);
159
- }, [activeHref]);
160
-
161
- return (
162
- <SidebarCtx.Provider value={ctx}>
163
- <Stack
164
- ref={rootRef}
165
- as="nav"
166
- space="var(--s0)"
167
- className={`velu-sidebar ${className}`.trim()}
168
- aria-label="Documentation"
169
- {...rest}
170
- >
171
- {/* Each section is its own Stack so the heading sits TIGHT to
172
- its list (small inner gap), while the nav's larger gap
173
- separates one section from the next — compact but still
174
- visibly grouped. */}
175
- {sections.map((section, i) => (
176
- <Stack key={i} space="var(--s-4)">
177
- <h5 className="velu-sidebar__section">
178
- <Icon icon={section.icon} />
179
- {section.title}
180
- </h5>
181
- <Stack as="ul" space="var(--s-4)" className="velu-sidebar__list">
182
- {section.items.map((it, j) => (
183
- <Node key={j} item={it} depth={0} />
184
- ))}
185
- </Stack>
186
- </Stack>
187
- ))}
188
- </Stack>
189
- </SidebarCtx.Provider>
190
- );
191
- }
1
+ import React from 'react';
2
+ import { ChevronRight, ExternalLink } from 'lucide-react';
3
+ import Stack from '../primitives/Stack.jsx';
4
+ import resolveIcon from '../lib/resolveIcon.jsx';
5
+ import scrollIntoNearestView from '../lib/scrollIntoNearestView.js';
6
+
7
+ /**
8
+ * Sidebar — docs navigation. Data-driven, recursive (arbitrary depth),
9
+ * tokenized, SSR-safe.
10
+ *
11
+ * Composition: ALL vertical rhythm is the Stack primitive (DRY) — faithful to
12
+ * the source design's flat structure:
13
+ * <Stack as="nav" space=--s1> ← headings + lists alternate here
14
+ * <h5 section/> (uniform --s1 between every child)
15
+ * <Stack as="ul" space=--s0> items </Stack>
16
+ * nested groups: <Stack as="ul" space="0"> (spacing via item padding)
17
+ * sidebar.css holds only what Stack cannot: rails, active state, item rows,
18
+ * chevron, summary, icon sizing.
19
+ *
20
+ * Icons (`section.icon` / `item.icon`) are **lucide icon id strings**
21
+ * (kebab-case), resolved by the shared resolveIcon util (a React node is
22
+ * also accepted). Size + stroke come from .velu-sidebar__icon CSS.
23
+ *
24
+ * Selection is ROUTE-DRIVEN (SSR-correct, reload-safe, URL-shareable): an
25
+ * item is active when its href === activeHref (or active:true). Ancestor
26
+ * groups of the active item auto-open. Router-agnostic via `linkComponent`
27
+ * (defaults to <a>); external items always render a real <a target=_blank>.
28
+ *
29
+ * @typedef {Object} SidebarItem
30
+ * @property {string} label
31
+ * @property {string} [href]
32
+ * @property {string|React.ReactNode} [icon] // lucide id or node
33
+ * @property {boolean} [active]
34
+ * @property {boolean} [external]
35
+ * @property {SidebarItem[]} [items]
36
+ *
37
+ * @param {{ sections: { title: string, icon?: string|React.ReactNode,
38
+ * items: SidebarItem[] }[], activeHref?: string,
39
+ * linkComponent?: React.ElementType, className?: string }} props
40
+ */
41
+
42
+ const SidebarCtx = React.createContext({ activeHref: undefined, Link: 'a' });
43
+
44
+ function isActive(item, activeHref) {
45
+ return Boolean(
46
+ item.active || (item.href != null && item.href === activeHref)
47
+ );
48
+ }
49
+
50
+ function hasActiveDescendant(item, activeHref) {
51
+ if (isActive(item, activeHref)) return true;
52
+ return (
53
+ Array.isArray(item.items) &&
54
+ item.items.some((c) => hasActiveDescendant(c, activeHref))
55
+ );
56
+ }
57
+
58
+ function Icon({ icon }) {
59
+ const node = resolveIcon(icon);
60
+ return node ? (
61
+ <span className="velu-sidebar__icon" aria-hidden="true">
62
+ {node}
63
+ </span>
64
+ ) : null;
65
+ }
66
+
67
+ function Chevron() {
68
+ return (
69
+ <ChevronRight
70
+ className="velu-sidebar__chevron"
71
+ aria-hidden="true"
72
+ focusable="false"
73
+ />
74
+ );
75
+ }
76
+
77
+ function ExternalIcon() {
78
+ return (
79
+ <ExternalLink
80
+ className="velu-sidebar__icon"
81
+ aria-hidden="true"
82
+ focusable="false"
83
+ />
84
+ );
85
+ }
86
+
87
+ /** Recursive node. depth 0 = top-level; depth >= 1 = nested (subitem style). */
88
+ function Node({ item, depth }) {
89
+ const { activeHref, Link } = React.useContext(SidebarCtx);
90
+ const { label, href = '#', icon, external, items } = item;
91
+ const base = depth === 0 ? 'velu-sidebar__item' : 'velu-sidebar__subitem';
92
+
93
+ if (items && items.length) {
94
+ return (
95
+ <li>
96
+ <details open={hasActiveDescendant(item, activeHref) || undefined}>
97
+ <summary className={`${base} velu-sidebar__summary`}>
98
+ <Icon icon={icon} />
99
+ <span className="velu-sidebar__label">{label}</span>
100
+ <Chevron />
101
+ </summary>
102
+ {/* nested list: Stack with no gap (spacing = item padding-block),
103
+ matching the source's stack-s */}
104
+ <Stack as="ul" space="0" className="velu-sidebar__sublist">
105
+ {items.map((child, i) => (
106
+ <Node key={i} item={child} depth={depth + 1} />
107
+ ))}
108
+ </Stack>
109
+ </details>
110
+ </li>
111
+ );
112
+ }
113
+
114
+ const active = isActive(item, activeHref);
115
+ const cls = base + (active ? ' velu-sidebar__item--active' : '');
116
+ const LinkTag = external ? 'a' : Link;
117
+ const linkProps = external
118
+ ? { href, target: '_blank', rel: 'noreferrer' }
119
+ : { href };
120
+
121
+ return (
122
+ <li>
123
+ <LinkTag
124
+ className={cls}
125
+ aria-current={active ? 'page' : undefined}
126
+ {...linkProps}
127
+ >
128
+ <Icon icon={icon} />
129
+ <span className="velu-sidebar__label">{label}</span>
130
+ {external ? <ExternalIcon /> : null}
131
+ </LinkTag>
132
+ </li>
133
+ );
134
+ }
135
+
136
+ export default function Sidebar({
137
+ sections = [],
138
+ activeHref,
139
+ linkComponent = 'a',
140
+ className = '',
141
+ ...rest
142
+ }) {
143
+ const ctx = React.useMemo(
144
+ () => ({ activeHref, Link: linkComponent }),
145
+ [activeHref, linkComponent]
146
+ );
147
+
148
+ // Bring the active item into view inside the parent scroll container
149
+ // whenever the active route changes — e.g. after navigation, the
150
+ // selected page is already visible without the user scrolling the
151
+ // sidebar manually. Uses the shared util (instead of native
152
+ // scrollIntoView) so `scroll-padding-*` on the parent — which the
153
+ // docs layout uses to mark the footer-eclipsed band as off-limits —
154
+ // is reliably honoured.
155
+ const rootRef = React.useRef(null);
156
+ React.useEffect(() => {
157
+ const el = rootRef.current?.querySelector('[aria-current="page"]');
158
+ scrollIntoNearestView(el);
159
+ }, [activeHref]);
160
+
161
+ return (
162
+ <SidebarCtx.Provider value={ctx}>
163
+ <Stack
164
+ ref={rootRef}
165
+ as="nav"
166
+ space="var(--s0)"
167
+ className={`velu-sidebar ${className}`.trim()}
168
+ aria-label="Documentation"
169
+ {...rest}
170
+ >
171
+ {/* Each section is its own Stack so the heading sits TIGHT to
172
+ its list (small inner gap), while the nav's larger gap
173
+ separates one section from the next — compact but still
174
+ visibly grouped. */}
175
+ {sections.map((section, i) => (
176
+ <Stack key={i} space="var(--s-4)">
177
+ <h5 className="velu-sidebar__section">
178
+ <Icon icon={section.icon} />
179
+ {section.title}
180
+ </h5>
181
+ <Stack as="ul" space="var(--s-4)" className="velu-sidebar__list">
182
+ {section.items.map((it, j) => (
183
+ <Node key={j} item={it} depth={0} />
184
+ ))}
185
+ </Stack>
186
+ </Stack>
187
+ ))}
188
+ </Stack>
189
+ </SidebarCtx.Provider>
190
+ );
191
+ }
@@ -1,65 +1,65 @@
1
- import React, { Children, cloneElement, isValidElement } from 'react';
2
-
3
- /**
4
- * Steps + Step — numbered procedural steps with a continuous accent rail
5
- * down the left edge.
6
- *
7
- * <Steps>
8
- * <Step title="First Step">These are instructions…</Step>
9
- * <Step title="Second Step">…</Step>
10
- * <Step title="Third Step">…</Step>
11
- * </Steps>
12
- *
13
- * Steps assigns 1-based `index` to each <Step> child via cloneElement —
14
- * callers don't have to number them by hand. A <Step> rendered outside
15
- * <Steps> falls back to whatever `index` it was given (or 1).
16
- *
17
- * Each Step is a 2-column grid:
18
- *
19
- * [circle + line] title
20
- * body
21
- *
22
- * The marker column spans both rows. The vertical rail (line below the
23
- * circle) is `flex: 1`, so it stretches to fill the Step's full height —
24
- * meaning adjacent steps' rails meet end-to-end with no manual height
25
- * math. The last step keeps its rail too (matches the design).
26
- */
27
-
28
- export function Step({
29
- title,
30
- index = 1,
31
- children,
32
- className = '',
33
- ...rest
34
- }) {
35
- return (
36
- <div className={`velu-step ${className}`.trim()} {...rest}>
37
- <div className="velu-step__circle" aria-hidden="true">
38
- {index}
39
- </div>
40
- <div className="velu-step__line" aria-hidden="true" />
41
- {title != null && (
42
- <div className="velu-step__title">{title}</div>
43
- )}
44
- {children != null && children !== false && (
45
- <div className="velu-step__body">{children}</div>
46
- )}
47
- </div>
48
- );
49
- }
50
- Step.displayName = 'Step';
51
-
52
- export default function Steps({ children, className = '', ...rest }) {
53
- let i = 0;
54
- const numbered = Children.map(children, (child) => {
55
- if (!isValidElement(child)) return child;
56
- if (child.type !== Step && child.type?.displayName !== 'Step') return child;
57
- i += 1;
58
- return cloneElement(child, { index: child.props.index ?? i });
59
- });
60
- return (
61
- <div className={`velu-steps ${className}`.trim()} {...rest}>
62
- {numbered}
63
- </div>
64
- );
65
- }
1
+ import React, { Children, cloneElement, isValidElement } from 'react';
2
+
3
+ /**
4
+ * Steps + Step — numbered procedural steps with a continuous accent rail
5
+ * down the left edge.
6
+ *
7
+ * <Steps>
8
+ * <Step title="First Step">These are instructions…</Step>
9
+ * <Step title="Second Step">…</Step>
10
+ * <Step title="Third Step">…</Step>
11
+ * </Steps>
12
+ *
13
+ * Steps assigns 1-based `index` to each <Step> child via cloneElement —
14
+ * callers don't have to number them by hand. A <Step> rendered outside
15
+ * <Steps> falls back to whatever `index` it was given (or 1).
16
+ *
17
+ * Each Step is a 2-column grid:
18
+ *
19
+ * [circle + line] title
20
+ * body
21
+ *
22
+ * The marker column spans both rows. The vertical rail (line below the
23
+ * circle) is `flex: 1`, so it stretches to fill the Step's full height —
24
+ * meaning adjacent steps' rails meet end-to-end with no manual height
25
+ * math. The last step keeps its rail too (matches the design).
26
+ */
27
+
28
+ export function Step({
29
+ title,
30
+ index = 1,
31
+ children,
32
+ className = '',
33
+ ...rest
34
+ }) {
35
+ return (
36
+ <div className={`velu-step ${className}`.trim()} {...rest}>
37
+ <div className="velu-step__circle" aria-hidden="true">
38
+ {index}
39
+ </div>
40
+ <div className="velu-step__line" aria-hidden="true" />
41
+ {title != null && (
42
+ <div className="velu-step__title">{title}</div>
43
+ )}
44
+ {children != null && children !== false && (
45
+ <div className="velu-step__body">{children}</div>
46
+ )}
47
+ </div>
48
+ );
49
+ }
50
+ Step.displayName = 'Step';
51
+
52
+ export default function Steps({ children, className = '', ...rest }) {
53
+ let i = 0;
54
+ const numbered = Children.map(children, (child) => {
55
+ if (!isValidElement(child)) return child;
56
+ if (child.type !== Step && child.type?.displayName !== 'Step') return child;
57
+ i += 1;
58
+ return cloneElement(child, { index: child.props.index ?? i });
59
+ });
60
+ return (
61
+ <div className={`velu-steps ${className}`.trim()} {...rest}>
62
+ {numbered}
63
+ </div>
64
+ );
65
+ }
@@ -1,48 +1,48 @@
1
- import React from 'react';
2
- import { Sun, Moon } from 'lucide-react';
3
-
4
- /**
5
- * ThemeToggle — pill-shaped two-state light/dark switch. The thumb
6
- * sits at the inline-start in light mode and slides to the inline-end
7
- * in dark mode. A sun glyph rides the thumb in light, a moon in dark.
8
- *
9
- * SSR-safe by design: markup is identical regardless of theme (the
10
- * server doesn't know it), and the thumb position + which glyph shows
11
- * are decided purely by CSS via the `[data-theme]` attribute — NOT by
12
- * React state. So there is no hydration mismatch and no icon flash. JS
13
- * only handles the click: it flips `data-theme` and persists the
14
- * explicit choice (which then overrides the OS preference on future
15
- * visits, per the anti-flash script in the template).
16
- */
17
- export default function ThemeToggle({ className = '', ...rest }) {
18
- function toggle() {
19
- const root = document.documentElement;
20
- const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
21
- root.dataset.theme = next;
22
- try {
23
- localStorage.setItem('velu-theme', next);
24
- } catch {
25
- /* private mode / storage disabled — toggle still works for the session */
26
- }
27
- }
28
-
29
- return (
30
- <button
31
- type="button"
32
- onClick={toggle}
33
- aria-label="Toggle color theme"
34
- title="Toggle color theme"
35
- className={`velu-theme-toggle ${className}`.trim()}
36
- {...rest}
37
- >
38
- <Sun
39
- className="velu-theme-toggle__icon velu-theme-toggle__icon--sun"
40
- aria-hidden="true"
41
- />
42
- <Moon
43
- className="velu-theme-toggle__icon velu-theme-toggle__icon--moon"
44
- aria-hidden="true"
45
- />
46
- </button>
47
- );
48
- }
1
+ import React from 'react';
2
+ import { Sun, Moon } from 'lucide-react';
3
+
4
+ /**
5
+ * ThemeToggle — pill-shaped two-state light/dark switch. The thumb
6
+ * sits at the inline-start in light mode and slides to the inline-end
7
+ * in dark mode. A sun glyph rides the thumb in light, a moon in dark.
8
+ *
9
+ * SSR-safe by design: markup is identical regardless of theme (the
10
+ * server doesn't know it), and the thumb position + which glyph shows
11
+ * are decided purely by CSS via the `[data-theme]` attribute — NOT by
12
+ * React state. So there is no hydration mismatch and no icon flash. JS
13
+ * only handles the click: it flips `data-theme` and persists the
14
+ * explicit choice (which then overrides the OS preference on future
15
+ * visits, per the anti-flash script in the template).
16
+ */
17
+ export default function ThemeToggle({ className = '', ...rest }) {
18
+ function toggle() {
19
+ const root = document.documentElement;
20
+ const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
21
+ root.dataset.theme = next;
22
+ try {
23
+ localStorage.setItem('velu-theme', next);
24
+ } catch {
25
+ /* private mode / storage disabled — toggle still works for the session */
26
+ }
27
+ }
28
+
29
+ return (
30
+ <button
31
+ type="button"
32
+ onClick={toggle}
33
+ aria-label="Toggle color theme"
34
+ title="Toggle color theme"
35
+ className={`velu-theme-toggle ${className}`.trim()}
36
+ {...rest}
37
+ >
38
+ <Sun
39
+ className="velu-theme-toggle__icon velu-theme-toggle__icon--sun"
40
+ aria-hidden="true"
41
+ />
42
+ <Moon
43
+ className="velu-theme-toggle__icon velu-theme-toggle__icon--moon"
44
+ aria-hidden="true"
45
+ />
46
+ </button>
47
+ );
48
+ }