@veluai/velu 0.2.34 → 0.2.36

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 (46) hide show
  1. package/README.md +39 -0
  2. package/dist/cli.js +57 -49
  3. package/package.json +1 -1
  4. package/runtime/velu-ui/components/Accordion.jsx +3 -3
  5. package/runtime/velu-ui/components/ApiClient.jsx +4 -2
  6. package/runtime/velu-ui/components/ApiReferencePage.jsx +12 -12
  7. package/runtime/velu-ui/components/ApiSamples.jsx +2 -2
  8. package/runtime/velu-ui/components/AskBar.jsx +3 -2
  9. package/runtime/velu-ui/components/Callout.jsx +11 -26
  10. package/runtime/velu-ui/components/Card.jsx +9 -8
  11. package/runtime/velu-ui/components/ChangelogFilters.jsx +2 -1
  12. package/runtime/velu-ui/components/Chatbot.jsx +11 -5
  13. package/runtime/velu-ui/components/CodeBlock.jsx +7 -6
  14. package/runtime/velu-ui/components/Columns.jsx +1 -0
  15. package/runtime/velu-ui/components/ContextMenu.jsx +3 -2
  16. package/runtime/velu-ui/components/Field.jsx +2 -2
  17. package/runtime/velu-ui/components/Icon.jsx +1 -0
  18. package/runtime/velu-ui/components/Image.jsx +2 -2
  19. package/runtime/velu-ui/components/MethodBadge.jsx +2 -2
  20. package/runtime/velu-ui/components/NavSelect.jsx +22 -4
  21. package/runtime/velu-ui/components/NotFound.jsx +7 -7
  22. package/runtime/velu-ui/components/PageFeedback.jsx +13 -3
  23. package/runtime/velu-ui/components/PageHeader.jsx +25 -10
  24. package/runtime/velu-ui/components/PageNav.jsx +2 -1
  25. package/runtime/velu-ui/components/Prompt.jsx +2 -2
  26. package/runtime/velu-ui/components/Search.jsx +1 -0
  27. package/runtime/velu-ui/components/Sidebar.jsx +18 -6
  28. package/runtime/velu-ui/components/Steps.jsx +4 -4
  29. package/runtime/velu-ui/components/ThemeToggle.jsx +1 -0
  30. package/runtime/velu-ui/components/Toc.jsx +6 -4
  31. package/runtime/velu-ui/components/Tree.jsx +6 -5
  32. package/runtime/velu-ui/components/TryItBar.jsx +1 -0
  33. package/runtime/velu-ui/components/Update.jsx +2 -2
  34. package/runtime/velu-ui/components/callout.css +28 -0
  35. package/runtime/velu-ui/components/docs-layout.css +5 -0
  36. package/runtime/velu-ui/components/page-header.css +5 -0
  37. package/runtime/velu-ui/components/sidebar.css +5 -5
  38. package/runtime/velu-ui/element-selectors.css +66 -0
  39. package/runtime/velu-ui/primitives/Cluster.jsx +12 -0
  40. package/runtime/velu-ui/primitives/Stack.jsx +12 -0
  41. package/runtime/velu-ui/primitives/Switcher.jsx +11 -1
  42. package/runtime/velu-ui/styles.css +51 -35
  43. package/src/runtime/App.jsx +43 -11
  44. package/src/runtime/client-entry.jsx +23 -0
  45. package/src/runtime/dev-warnings.js +16 -0
  46. package/src/runtime/server-entry.jsx +1 -0
@@ -110,10 +110,11 @@ export default function ApiClient({
110
110
  align="flex-start"
111
111
  className="velu-api-client__head"
112
112
  >
113
- <div className="velu-api-client__op" ref={opRef}>
113
+ <option-dropdown class="velu-api-client__op" ref={opRef}>
114
114
  {canSwitch ? (
115
115
  <button
116
116
  type="button"
117
+ id="endpoints-menu-trigger"
117
118
  className="velu-api-client__op-btn"
118
119
  onClick={() => setMenuOpen((v) => !v)}
119
120
  aria-haspopup="listbox"
@@ -166,7 +167,7 @@ export default function ApiClient({
166
167
  ))}
167
168
  </ul>
168
169
  )}
169
- </div>
170
+ </option-dropdown>
170
171
  <TryItBar
171
172
  method={method}
172
173
  path={path}
@@ -183,6 +184,7 @@ export default function ApiClient({
183
184
  visible without scrolling past the inputs; the form and code samples
184
185
  follow. See api.css. */}
185
186
  <div
187
+ id="api-playground-input"
186
188
  className="velu-api-client__body"
187
189
  data-has-response={response != null ? 'true' : undefined}
188
190
  >
@@ -80,14 +80,14 @@ export default function ApiReferencePage({ operation, samples = [], apiOperation
80
80
 
81
81
  {/* Authorizations */}
82
82
  {operation.auth?.length > 0 && (
83
- <section className="velu-api-page__section">
84
- <h2>Authorizations</h2>
83
+ <api-section class="velu-api-page__section">
84
+ <h2 data-component="api-section-heading">Authorizations</h2>
85
85
  {operation.auth.map((a, i) => (
86
86
  <Field key={i} name={a.name} pre={a.prefix} type={a.type} required={a.required} post={a.in}>
87
87
  {a.description}
88
88
  </Field>
89
89
  ))}
90
- </section>
90
+ </api-section>
91
91
  )}
92
92
 
93
93
  {/* Parameters, by location */}
@@ -95,37 +95,37 @@ export default function ApiReferencePage({ operation, samples = [], apiOperation
95
95
  const rows = operation.parameters?.[loc] || [];
96
96
  if (!rows.length) return null;
97
97
  return (
98
- <section key={loc} className="velu-api-page__section">
99
- <h2>{LOCATION_LABEL[loc]}</h2>
98
+ <api-section key={loc} class="velu-api-page__section">
99
+ <h2 data-component="api-section-heading">{LOCATION_LABEL[loc]}</h2>
100
100
  {rows.map((p, i) => (
101
101
  <Field key={i} name={p.name} type={p.type} required={p.required}>
102
102
  {p.description}
103
103
  {p.enum && <EnumHint values={p.enum} />}
104
104
  </Field>
105
105
  ))}
106
- </section>
106
+ </api-section>
107
107
  );
108
108
  })}
109
109
 
110
110
  {/* Request body */}
111
111
  {operation.body?.fields?.length > 0 && (
112
- <section className="velu-api-page__section">
113
- <h2>Body</h2>
112
+ <api-section class="velu-api-page__section">
113
+ <h2 data-component="api-section-heading">Body</h2>
114
114
  {operation.body.fields.map((f, i) => (
115
115
  <Field key={i} name={f.name} type={f.type} required={f.required}>
116
116
  {f.description}
117
117
  {f.enum && <EnumHint values={f.enum} />}
118
118
  </Field>
119
119
  ))}
120
- </section>
120
+ </api-section>
121
121
  )}
122
122
 
123
123
  {/* Responses */}
124
124
  {operation.responses?.length > 0 && (
125
- <section className="velu-api-page__section">
126
- <h2>Response</h2>
125
+ <api-section class="velu-api-page__section">
126
+ <h2 data-component="api-section-heading">Response</h2>
127
127
  <ResponseSections responses={operation.responses} />
128
- </section>
128
+ </api-section>
129
129
  )}
130
130
 
131
131
  {/* Try-It playground — rendered as an overlay above the page, with a
@@ -14,7 +14,7 @@ export default function ApiSamples({ samples = [], responses = [] }) {
14
14
  return (
15
15
  <div className="velu-api-samples">
16
16
  {samples.length > 0 && (
17
- <CodeGroup className="velu-api-samples__req">
17
+ <CodeGroup id="request-example" className="velu-api-samples__req">
18
18
  {samples.map((s) => (
19
19
  <CodeBlock key={s.key} title={s.label} language={s.language}>
20
20
  {s.code}
@@ -23,7 +23,7 @@ export default function ApiSamples({ samples = [], responses = [] }) {
23
23
  </CodeGroup>
24
24
  )}
25
25
  {withExample.length > 0 && (
26
- <CodeGroup className="velu-api-samples__res">
26
+ <CodeGroup id="response-example" className="velu-api-samples__res">
27
27
  {withExample.map((r) => (
28
28
  <CodeBlock key={r.status} title={r.status} language="json">
29
29
  {JSON.stringify(r.example, null, 2)}
@@ -48,7 +48,7 @@ export default function AskBar({
48
48
  const isEmpty = value.trim().length === 0;
49
49
 
50
50
  return (
51
- <div className={`velu-ask-bar ${className}`.trim()} {...rest}>
51
+ <chat-assistant-floating-input class={`velu-ask-bar ${className}`.trim()} {...rest}>
52
52
  <textarea
53
53
  className="velu-ask-bar__input"
54
54
  placeholder={placeholder}
@@ -59,6 +59,7 @@ export default function AskBar({
59
59
  />
60
60
  <button
61
61
  type="button"
62
+ data-component="chat-assistant-send-button"
62
63
  className="velu-ask-bar__send"
63
64
  onClick={submit}
64
65
  disabled={isEmpty}
@@ -66,6 +67,6 @@ export default function AskBar({
66
67
  >
67
68
  {resolveIcon('arrow-up', { size: '1em' })}
68
69
  </button>
69
- </div>
70
+ </chat-assistant-floating-input>
70
71
  );
71
72
  }
@@ -73,42 +73,27 @@ export default function Callout({
73
73
  // (.lucide rule in base.css), not set per-icon.
74
74
  const iconNode = resolveIcon(icon ?? v.icon, { size: '1em' });
75
75
 
76
+ // Static styles live in callout.css so a project's custom CSS can override
77
+ // them with a plain `callout { ... }` element selector (inline styles would
78
+ // beat any stylesheet). Only the per-instance colours are inline, as custom
79
+ // properties the stylesheet reads.
76
80
  return (
77
- <div
78
- className={className}
81
+ <callout
82
+ className={`velu-callout ${className}`.trim()}
83
+ data-type={type}
79
84
  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)',
85
+ '--velu-callout-bg': bgColor,
86
+ '--velu-callout-stroke': strokeColor,
88
87
  ...style,
89
88
  }}
90
89
  {...rest}
91
90
  >
92
91
  {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
- >
92
+ <span className="velu-callout__icon" aria-hidden="true">
108
93
  {iconNode}
109
94
  </span>
110
95
  )}
111
96
  {children}
112
- </div>
97
+ </callout>
113
98
  );
114
99
  }
@@ -49,13 +49,13 @@ export function Card({
49
49
  .join(' ');
50
50
 
51
51
  const iconEl = icon ? (
52
- <span className="velu-card__icon" aria-hidden="true">
52
+ <span className="velu-card__icon" data-component-part="card-icon" aria-hidden="true">
53
53
  {resolveIcon(icon, { size: '1em' })}
54
54
  </span>
55
55
  ) : null;
56
56
 
57
57
  const ctaEl = cta ? (
58
- <a className="velu-card__cta" href={cta.href}>
58
+ <a className="velu-card__cta" data-component-part="card-cta" href={cta.href}>
59
59
  {cta.label}
60
60
  <span className="velu-card__cta-icon" aria-hidden="true">
61
61
  {resolveIcon('chevron-right', { size: '1em' })}
@@ -64,21 +64,21 @@ export function Card({
64
64
  ) : null;
65
65
 
66
66
  const titleEl = title ? (
67
- <div className="velu-card__title">{title}</div>
67
+ <div className="velu-card__title" data-component-part="card-title">{title}</div>
68
68
  ) : null;
69
69
  const textEl = children ? (
70
- <div className="velu-card__text">{children}</div>
70
+ <div className="velu-card__text" data-component-part="card-content">{children}</div>
71
71
  ) : null;
72
72
 
73
73
  return (
74
- <div className={cls} {...rest}>
74
+ <card className={cls} {...rest}>
75
75
  {img && (
76
- <div className="velu-card__media">
76
+ <div className="velu-card__media" data-component-part="card-image">
77
77
  <img src={img} alt={imgAlt} loading="lazy" />
78
78
  </div>
79
79
  )}
80
80
 
81
- <div className="velu-card__body">
81
+ <div className="velu-card__body" data-component-part="card-content-container">
82
82
  {horizontal ? (
83
83
  <>
84
84
  {iconEl}
@@ -97,7 +97,7 @@ export function Card({
97
97
  </>
98
98
  )}
99
99
  </div>
100
- </div>
100
+ </card>
101
101
  );
102
102
  }
103
103
 
@@ -117,6 +117,7 @@ export function CardGroup({
117
117
  }) {
118
118
  return (
119
119
  <Switcher
120
+ as="card-group"
120
121
  className={`velu-card-group ${className}`.trim()}
121
122
  space={space}
122
123
  min={min}
@@ -45,9 +45,10 @@ export default function ChangelogFilters({ tags = [] }) {
45
45
  };
46
46
 
47
47
  return (
48
- <div className="velu-changelog-filters">
48
+ <div id="changelog-filters" className="velu-changelog-filters">
49
49
  <div className="velu-changelog-filters__heading">Filters</div>
50
50
  <div
51
+ id="changelog-filters-content"
51
52
  className="velu-changelog-filters__list"
52
53
  role="group"
53
54
  aria-label="Filter updates by tag"
@@ -339,6 +339,7 @@ function renderStream(tokens, sources, onNavigate) {
339
339
  function ChatHeader({ onClose, onNew, onHistory, historyOpen }) {
340
340
  return (
341
341
  <Cluster
342
+ as="chat-assistant-sheet-header"
342
343
  space="var(--s-2)"
343
344
  justify="space-between"
344
345
  align="center"
@@ -461,7 +462,7 @@ function Welcome({ onPick, suggestions }) {
461
462
  >
462
463
  <Cluster space="var(--s-2)" align="center">
463
464
  {resolveIcon(s.icon, { size: '1em' })}
464
- <span>{s.label}</span>
465
+ <starter-question-text>{s.label}</starter-question-text>
465
466
  </Cluster>
466
467
  {resolveIcon('arrow-right', { size: '1em' })}
467
468
  </button>
@@ -616,6 +617,8 @@ function Composer({ value, onChange, onSubmit, disabled }) {
616
617
  <div className="velu-chatbot__input-wrap">
617
618
  <textarea
618
619
  ref={taRef}
620
+ id="chat-assistant-textarea"
621
+ data-component="chat-assistant-input"
619
622
  className="velu-chatbot__input"
620
623
  placeholder="Ask anything about the docs…"
621
624
  rows={1}
@@ -625,6 +628,7 @@ function Composer({ value, onChange, onSubmit, disabled }) {
625
628
  />
626
629
  <button
627
630
  type="button"
631
+ data-component="chat-assistant-send-button"
628
632
  className={`velu-chatbot__send${active ? ' is-active' : ''}`}
629
633
  onClick={onSubmit}
630
634
  disabled={!active}
@@ -633,9 +637,9 @@ function Composer({ value, onChange, onSubmit, disabled }) {
633
637
  {resolveIcon('arrow-up', { size: '1em' })}
634
638
  </button>
635
639
  </div>
636
- <div className="velu-chatbot__foot">
640
+ <chat-assistant-disclaimer-text class="velu-chatbot__foot">
637
641
  <kbd>Enter</kbd> to send · <kbd>⇧ Enter</kbd> for newline
638
- </div>
642
+ </chat-assistant-disclaimer-text>
639
643
  </div>
640
644
  );
641
645
  }
@@ -910,6 +914,8 @@ export default function Chatbot({
910
914
 
911
915
  return (
912
916
  <aside
917
+ id="chat-assistant-sheet"
918
+ data-component="chat-assistant-sheet"
913
919
  className={cls}
914
920
  aria-hidden={!open}
915
921
  aria-label="Ask AI"
@@ -944,7 +950,7 @@ export default function Chatbot({
944
950
  />
945
951
  )}
946
952
 
947
- <div className="velu-chatbot__body" ref={bodyRef}>
953
+ <chat-assistant-sheet-content class="velu-chatbot__body" ref={bodyRef}>
948
954
  {messages.length === 0 ? (
949
955
  <Welcome onPick={(t) => send(t)} suggestions={suggestions} />
950
956
  ) : (
@@ -968,7 +974,7 @@ export default function Chatbot({
968
974
  )}
969
975
  </Stack>
970
976
  )}
971
- </div>
977
+ </chat-assistant-sheet-content>
972
978
 
973
979
  <Composer
974
980
  value={input}
@@ -90,9 +90,9 @@ function TabLabel({ icon, label }) {
90
90
  return (
91
91
  <>
92
92
  {icon != null && (
93
- <span className="velu-code-block__tab-icon" aria-hidden="true">
93
+ <code-block-icon class="velu-code-block__tab-icon" aria-hidden="true">
94
94
  {typeof icon === 'string' ? resolveIcon(icon, { size: '1em' }) : icon}
95
- </span>
95
+ </code-block-icon>
96
96
  )}
97
97
  <span className="velu-code-block__tab-label">{label}</span>
98
98
  </>
@@ -143,6 +143,7 @@ function CopyButton({ code }) {
143
143
  type="button"
144
144
  onClick={copy}
145
145
  className="velu-code-block__copy"
146
+ data-component="code-block-copy-button"
146
147
  aria-label={copied ? 'Copied' : 'Copy code'}
147
148
  title={copied ? 'Copied' : 'Copy'}
148
149
  >
@@ -242,7 +243,7 @@ export function CodeBlock({
242
243
  const label = filename || title;
243
244
  const tabIcon = pickIcon({ icon, withIcon, language });
244
245
  return (
245
- <div className={cls} {...rest}>
246
+ <code-block class={cls} {...rest}>
246
247
  {label && (
247
248
  <div className="velu-code-block__header">
248
249
  <Tab active icon={tabIcon} label={label} />
@@ -254,7 +255,7 @@ export function CodeBlock({
254
255
  lineNumbers={lineNumbers}
255
256
  highlight={highlight}
256
257
  />
257
- </div>
258
+ </code-block>
258
259
  );
259
260
  }
260
261
  CodeBlock.displayName = 'CodeBlock';
@@ -340,7 +341,7 @@ export function CodeGroup({
340
341
  }
341
342
 
342
343
  return (
343
- <div className={cls} {...rest}>
344
+ <code-group class={cls} {...rest}>
344
345
  <div className="velu-code-block__header">
345
346
  {canLeft && (
346
347
  <button
@@ -391,7 +392,7 @@ export function CodeGroup({
391
392
  lineNumbers={lineNumbers}
392
393
  highlight={highlight}
393
394
  />
394
- </div>
395
+ </code-group>
395
396
  );
396
397
  }
397
398
 
@@ -44,6 +44,7 @@ export default function Columns({
44
44
  }) {
45
45
  return (
46
46
  <Switcher
47
+ as="columns"
47
48
  className={`velu-columns ${className}`.trim()}
48
49
  space={space}
49
50
  min={min}
@@ -256,11 +256,11 @@ export default function ContextMenu({
256
256
  className="velu-context-bar"
257
257
  data-pagefind-ignore=""
258
258
  >
259
- {eyebrow ? <span className="velu-context-bar__eyebrow">{eyebrow}</span> : <span />}
259
+ {eyebrow ? <eyebrow className="velu-context-bar__eyebrow">{eyebrow}</eyebrow> : <span />}
260
260
 
261
261
  <div className="velu-context-bar__actions">
262
262
  {items.length > 0 && (
263
- <div ref={rootRef} className="velu-context-menu" data-open={open ? 'true' : 'false'}>
263
+ <div ref={rootRef} id="page-context-menu" className="velu-context-menu" data-open={open ? 'true' : 'false'}>
264
264
  <div className="velu-context-menu__split">
265
265
  <button
266
266
  type="button"
@@ -275,6 +275,7 @@ export default function ContextMenu({
275
275
  </button>
276
276
  <button
277
277
  type="button"
278
+ id="page-context-menu-button"
278
279
  className="velu-context-menu__toggle"
279
280
  aria-haspopup="menu"
280
281
  aria-expanded={open}
@@ -46,7 +46,7 @@ export default function Field({
46
46
  }) {
47
47
  const hasDefault = defaultValue !== undefined && defaultValue !== null;
48
48
  return (
49
- <div className={`velu-field ${className}`.trim()} {...rest}>
49
+ <field className={`velu-field ${className}`.trim()} {...rest}>
50
50
  {/* Cluster handles the wrap-friendly inline row layout — chips +
51
51
  name + type + required + default + post — with a 16px gap and
52
52
  baseline alignment so the bold name doesn't shift vertically
@@ -76,6 +76,6 @@ export default function Field({
76
76
  {children != null && children !== false && (
77
77
  <div className="velu-field__body">{children}</div>
78
78
  )}
79
- </div>
79
+ </field>
80
80
  );
81
81
  }
@@ -40,6 +40,7 @@ export default function Icon({
40
40
  src={icon}
41
41
  alt={ariaLabel || ''}
42
42
  className={classes}
43
+ data-component="icon"
43
44
  style={sharedStyle}
44
45
  aria-hidden={ariaLabel ? undefined : true}
45
46
  {...rest}
@@ -149,14 +149,14 @@ export default function Image({
149
149
 
150
150
  if (caption) {
151
151
  return (
152
- <figure className={cls} {...rest}>
152
+ <figure className={cls} data-component={chrome === 'frame' ? 'frame' : undefined} {...rest}>
153
153
  {body}
154
154
  <figcaption className="velu-image__caption">{caption}</figcaption>
155
155
  </figure>
156
156
  );
157
157
  }
158
158
  return (
159
- <div className={cls} {...rest}>
159
+ <div className={cls} data-component={chrome === 'frame' ? 'frame' : undefined} {...rest}>
160
160
  {body}
161
161
  </div>
162
162
  );
@@ -24,8 +24,8 @@ export default function MethodBadge({
24
24
  const safe = METHODS.includes(key) ? key : 'get';
25
25
  const cls = `velu-method-badge velu-method-badge--${safe} ${className}`.trim();
26
26
  return (
27
- <span className={cls} {...rest}>
27
+ <method-pill class={cls} {...rest}>
28
28
  {String(method).toUpperCase()}
29
- </span>
29
+ </method-pill>
30
30
  );
31
31
  }
@@ -30,9 +30,16 @@ export default function NavSelect({
30
30
  bare = false,
31
31
  linkComponent = 'a',
32
32
  className = '',
33
+ // Mintlify-compatible hooks: id prefix for the trigger/menu
34
+ // (e.g. 'localization-select' → #localization-select-trigger /
35
+ // #localization-select-content / #localization-select-item) and the
36
+ // data-component family ('nav-dropdown' → nav-dropdown-trigger, …).
37
+ idPrefix,
38
+ component = 'nav-dropdown',
33
39
  ...rest
34
40
  }) {
35
41
  const Link = linkComponent;
42
+ const hookId = (suffix) => (idPrefix ? `${idPrefix}-${suffix}` : undefined);
36
43
  const [open, setOpen] = React.useState(false);
37
44
  const rootRef = React.useRef(null);
38
45
 
@@ -61,6 +68,8 @@ export default function NavSelect({
61
68
  >
62
69
  <button
63
70
  type="button"
71
+ id={hookId('trigger')}
72
+ data-component={`${component}-trigger`}
64
73
  className="velu-nav-select__btn"
65
74
  aria-haspopup="menu"
66
75
  aria-expanded={open}
@@ -82,23 +91,32 @@ export default function NavSelect({
82
91
  focusable="false"
83
92
  />
84
93
  </button>
85
- <ul className="velu-nav-select__menu" role="menu" aria-hidden={!open}>
94
+ <ul
95
+ id={hookId('content')}
96
+ data-component={`${component}-content`}
97
+ className="velu-nav-select__menu"
98
+ role="menu"
99
+ aria-hidden={!open}
100
+ >
86
101
  {options.map((o, i) => (
87
102
  <li key={o.href ?? i} role="none">
88
103
  <Link
89
104
  role="menuitem"
105
+ id={i === 0 ? hookId('item') : undefined}
106
+ data-component={`${component}-item`}
107
+ data-active={o.active ? 'true' : undefined}
90
108
  className={`velu-nav-select__item${o.active ? ' velu-nav-select__item--active' : ''}`}
91
109
  href={o.href}
92
110
  tabIndex={open ? 0 : -1}
93
111
  onClick={() => setOpen(false)}
94
112
  >
95
113
  {o.icon && (
96
- <span className="velu-nav-select__item-icon" aria-hidden="true">
114
+ <nav-dropdown-item-icon class="velu-nav-select__item-icon" aria-hidden="true">
97
115
  {resolveIcon(o.icon, { size: '1em' })}
98
- </span>
116
+ </nav-dropdown-item-icon>
99
117
  )}
100
118
  {o.code && <span className="velu-nav-select__code">{o.code}</span>}
101
- <span>{o.label}</span>
119
+ <nav-dropdown-item-title>{o.label}</nav-dropdown-item-title>
102
120
  </Link>
103
121
  </li>
104
122
  ))}
@@ -27,15 +27,15 @@ export default function NotFound({
27
27
  }) {
28
28
  const Link = linkComponent;
29
29
  return (
30
- <section className="velu-404">
30
+ <not-found-container class="velu-404">
31
31
  <p className="velu-404__eyebrow">{eyebrow}</p>
32
- <div className="velu-404__num" aria-hidden="true">
32
+ <not-found-status-code class="velu-404__num" aria-hidden="true">
33
33
  404
34
- </div>
35
- <h1 className="velu-404__title">{title}</h1>
36
- <p className="velu-404__body">{body}</p>
34
+ </not-found-status-code>
35
+ <h1 className="velu-404__title" data-component="not-found-title">{title}</h1>
36
+ <p className="velu-404__body" data-component="not-found-description">{body}</p>
37
37
  <div className="velu-404__actions">
38
- <Link className="velu-404__btn velu-404__btn--primary" href={homeHref}>
38
+ <Link className="velu-404__btn velu-404__btn--primary" data-component="not-found-recommended-page-link" href={homeHref}>
39
39
  <span className="velu-404__btn-ic" aria-hidden="true">
40
40
  {resolveIcon('house', { size: '1em' })}
41
41
  </span>
@@ -58,6 +58,6 @@ export default function NotFound({
58
58
  </button>
59
59
  )}
60
60
  </div>
61
- </section>
61
+ </not-found-container>
62
62
  );
63
63
  }
@@ -110,16 +110,19 @@ export default function PageFeedback({
110
110
 
111
111
  return (
112
112
  <Stack
113
+ as="contextual-feedback-container"
113
114
  space="var(--s0)"
114
115
  className={`velu-feedback ${className}`.trim()}
115
116
  {...rest}
116
117
  >
117
- <Cluster space="var(--s0)" align="center" className="velu-feedback__bar">
118
+ <Cluster as="feedback-toolbar" space="var(--s0)" align="center" className="velu-feedback__bar">
118
119
  <span className="velu-feedback__question">{question}</span>
119
120
 
120
121
  <Cluster space="var(--s-2)" align="center">
121
122
  <button
122
123
  type="button"
124
+ id="feedback-thumbs-up"
125
+ data-component="contextual-feedback-button"
123
126
  className={`velu-feedback__btn${
124
127
  vote === 'yes' ? ' velu-feedback__btn--chosen' : ''
125
128
  }`}
@@ -132,6 +135,8 @@ export default function PageFeedback({
132
135
  </button>
133
136
  <button
134
137
  type="button"
138
+ id="feedback-thumbs-down"
139
+ data-component="contextual-feedback-button"
135
140
  className={`velu-feedback__btn${
136
141
  vote === 'no' ? ' velu-feedback__btn--chosen' : ''
137
142
  }`}
@@ -151,8 +156,8 @@ export default function PageFeedback({
151
156
  </Cluster>
152
157
 
153
158
  {showForm && (
154
- <Stack space="var(--s1)" className="velu-feedback__form">
155
- <h3 className="velu-feedback__form-title">
159
+ <Stack as="contextual-feedback-form" id="feedback-form" space="var(--s1)" className="velu-feedback__form">
160
+ <h3 className="velu-feedback__form-title" data-component="contextual-feedback-form-title">
156
161
  How can we improve our product?
157
162
  </h3>
158
163
 
@@ -176,6 +181,8 @@ export default function PageFeedback({
176
181
  </Stack>
177
182
 
178
183
  <textarea
184
+ id="feedback-form-input"
185
+ data-component="contextual-feedback-input"
179
186
  className="velu-feedback__detail"
180
187
  placeholder="(optional) Could you share more about your experience?"
181
188
  value={detail}
@@ -186,6 +193,7 @@ export default function PageFeedback({
186
193
  <Cluster space="var(--s-1)">
187
194
  <button
188
195
  type="button"
196
+ id="feedback-form-cancel"
189
197
  className="velu-feedback__action velu-feedback__action--cancel"
190
198
  onClick={cancel}
191
199
  >
@@ -193,6 +201,8 @@ export default function PageFeedback({
193
201
  </button>
194
202
  <button
195
203
  type="button"
204
+ id="feedback-form-submit"
205
+ data-component="contextual-feedback-form-submit-button"
196
206
  className="velu-feedback__action velu-feedback__action--submit"
197
207
  onClick={submit}
198
208
  >