Skip to content

Commit 8c756ef

Browse files
Merge pull request #1036 from BitGo/codehike
feat(docs): spotlight component for an article
2 parents 0e0f858 + 26def96 commit 8c756ef

File tree

7 files changed

+295
-94
lines changed

7 files changed

+295
-94
lines changed

website/docs/how-to-guides/intermediate-semantic-analysis.md

Lines changed: 3 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -4,102 +4,13 @@ sidebar_position: 1
44

55
# How to Parse a number from a Query Parameter
66

7+
import { Spotlight } from '@site/src/components/CodeHike';
8+
79
Query parameters are represented as the type
810
`Record<string, string | string[] | undefined>`, so using a codec that doesn't decode
911
from a `string | string[] | undefined` will produce a type error.
1012

11-
Consider this `httpRoute` that compiles successfully.
12-
13-
```typescript
14-
import * as t from 'io-ts';
15-
import { httpRoute, httpRequest } from '@api-ts/io-ts-http';
16-
17-
const GetHello = httpRoute({
18-
path: '/hello/{name}',
19-
method: 'GET',
20-
request: httpRequest({
21-
params: {
22-
name: t.string,
23-
},
24-
}),
25-
response: {
26-
200: t.string,
27-
},
28-
});
29-
```
30-
31-
If you add an expected `number` value to the `httpRoute`'s query parameters, you'll see
32-
the following compilation error:
33-
34-
```typescript
35-
import * as t from 'io-ts';
36-
import { httpRoute, httpRequest } from '@api-ts/io-ts-http';
37-
38-
const GetHello = httpRoute({
39-
path: '/hello/{name}',
40-
method: 'GET',
41-
request: httpRequest({
42-
params: {
43-
name: t.string,
44-
},
45-
query: {
46-
repeat: t.number, // Compilation error!
47-
},
48-
}),
49-
response: {
50-
200: t.string,
51-
},
52-
});
53-
```
54-
55-
If you add an expected `number` value to the `httpRoute`'s query parameters, you'll see
56-
the following compilation error:
57-
58-
```
59-
index.ts:16:7 - error TS2322:
60-
Codec's output type is not assignable to
61-
string | string[] | undefined.
62-
Try using one like `NumberFromString`
63-
64-
13 repeat: t.number
65-
```
66-
67-
Recall that `t.number` decodes an `unknown` value into a `number` without any
68-
manipulation of the starting value. If you started with a number, you'll decode a
69-
number.
70-
71-
We need a codec that decodes a `string` into a `number` and converts the
72-
string-representation of a number into the `number` type.
73-
74-
This is a fairly common requirement, so this codec already exists: [io-ts-types] offers
75-
the [NumberFromString] codec that decodes a `string` value into a `number`. Use
76-
`NumberFromString` to fix your compilation error.
77-
78-
[io-ts-types]: https://github.com/gcanti/io-ts-types
79-
[numberfromstring]:
80-
https://gcanti.github.io/io-ts-types/modules/NumberFromString.ts.html
81-
82-
```typescript
83-
import * as t from 'io-ts';
84-
import { NumberFromString } from 'io-ts-types';
85-
import { httpRoute, httpRequest } from '@api-ts/io-ts-http';
86-
87-
const GetHello = httpRoute({
88-
path: '/hello/{name}',
89-
method: 'GET',
90-
request: httpRequest({
91-
params: {
92-
name: t.string,
93-
},
94-
query: {
95-
repeat: NumberFromString,
96-
},
97-
}),
98-
response: {
99-
200: t.string,
100-
},
101-
});
102-
```
13+
<Spotlight />
10314

10415
In general, the solution to decoding a query parameter into a non-string type is to use
10516
a codec that decodes and encodes from a `string` into your desired type.

website/package-lock.json

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

website/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"docusaurus-theme-mdx-v2": "^0.1.2",
2323
"prism-react-renderer": "^1.3.5",
2424
"react": "^18.0.0",
25-
"react-dom": "^18.0.0"
25+
"react-dom": "^18.0.0",
26+
"zod": "^3.24.2"
2627
},
2728
"devDependencies": {
2829
"@docusaurus/module-type-aliases": "3.7.0"
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import React from 'react';
2+
import { useColorMode } from '@docusaurus/theme-common';
3+
4+
// A simpler implementation of the Spotlight component
5+
export default function Spotlight() {
6+
// State to track the selected step
7+
const [selectedIndex, setSelectedIndex] = React.useState(0);
8+
// Get the current color mode (light or dark)
9+
const { colorMode } = useColorMode();
10+
const isDarkTheme = colorMode === 'dark';
11+
12+
// Define theme-specific colors
13+
const colors = {
14+
background: isDarkTheme ? '#1e293b' : '#f8fafc',
15+
border: isDarkTheme ? '#334155' : '#e2e8f0',
16+
borderActive: isDarkTheme ? '#60a5fa' : '#3b82f6',
17+
text: isDarkTheme ? '#f1f5f9' : '#0f172a',
18+
codeBackground: isDarkTheme ? '#0f172a' : '#f1f5f9',
19+
highlightBlue: isDarkTheme ? 'rgba(59, 130, 246, 0.3)' : 'rgba(59, 130, 246, 0.15)',
20+
highlightRed: isDarkTheme ? 'rgba(239, 68, 68, 0.3)' : 'rgba(239, 68, 68, 0.15)',
21+
highlightGreen: isDarkTheme
22+
? 'rgba(16, 185, 129, 0.3)'
23+
: 'rgba(16, 185, 129, 0.15)',
24+
};
25+
26+
// Define the steps manually based on the structure in the MDX file
27+
const steps = [
28+
{
29+
title: 'Working Route',
30+
content: (
31+
<div>
32+
<p>
33+
Consider this <code>httpRoute</code> that compiles successfully.
34+
</p>
35+
</div>
36+
),
37+
code: (
38+
<pre className="language-typescript">
39+
<code className="language-typescript">
40+
{`import * as t from 'io-ts';
41+
import { httpRoute, httpRequest } from '@api-ts/io-ts-http';
42+
43+
const GetHello = httpRoute({
44+
path: '/hello/{name}',
45+
method: 'GET',
46+
request: httpRequest({
47+
params: {
48+
name: t.string,
49+
},
50+
}),`}
51+
<span
52+
style={{
53+
backgroundColor: colors.highlightBlue,
54+
display: 'block',
55+
margin: '0 -1rem',
56+
padding: '0 1rem',
57+
}}
58+
>
59+
{` response: {
60+
200: t.string,
61+
},`}
62+
</span>
63+
{`});
64+
`}
65+
</code>
66+
</pre>
67+
),
68+
},
69+
{
70+
title: 'Compilation Error',
71+
content: (
72+
<div>
73+
<p>
74+
If you add an expected <code>number</code> value to the{' '}
75+
<code>httpRoute</code>'s query parameters, you'll see the following
76+
compilation error:
77+
</p>
78+
<p>The error message looks like this:</p>
79+
<pre>
80+
<code>
81+
{`index.ts:16:7 - error TS2322:
82+
Codec's output type is not assignable to
83+
string | string[] | undefined.
84+
Try using one like \`NumberFromString\`
85+
86+
13 repeat: t.number`}
87+
</code>
88+
</pre>
89+
<p>
90+
Recall that <code>t.number</code> decodes an <code>unknown</code> value into
91+
a <code>number</code> without any manipulation of the starting value. If you
92+
started with a number, you'll decode a number.
93+
</p>
94+
<p>
95+
We need a codec that decodes a <code>string</code> into a{' '}
96+
<code>number</code> and converts the string-representation of a number into
97+
the <code>number</code> type.
98+
</p>
99+
</div>
100+
),
101+
code: (
102+
<pre className="language-typescript">
103+
<code className="language-typescript">
104+
{`import * as t from 'io-ts';
105+
import { httpRoute, httpRequest } from '@api-ts/io-ts-http';
106+
107+
const GetHello = httpRoute({
108+
path: '/hello/{name}',
109+
method: 'GET',
110+
request: httpRequest({
111+
params: {
112+
name: t.string,
113+
},
114+
query: {`}
115+
<span
116+
style={{
117+
backgroundColor: colors.highlightRed,
118+
display: 'block',
119+
margin: '0 -1rem',
120+
padding: '0 1rem',
121+
}}
122+
>
123+
{` repeat: t.number, // Compilation error!`}
124+
</span>
125+
{` },
126+
}),
127+
response: {
128+
200: t.string,
129+
},
130+
});`}
131+
</code>
132+
</pre>
133+
),
134+
},
135+
{
136+
title: 'Solution',
137+
content: (
138+
<div>
139+
<p>
140+
This is a fairly common requirement, so this codec already exists:{' '}
141+
<a href="https://github.com/gcanti/io-ts-types">io-ts-types</a> offers the{' '}
142+
<a href="https://gcanti.github.io/io-ts-types/modules/NumberFromString.ts.html">
143+
NumberFromString
144+
</a>{' '}
145+
codec that decodes a <code>string</code> value into a <code>number</code>.
146+
Use
147+
<code>NumberFromString</code> to fix your compilation error.
148+
</p>
149+
</div>
150+
),
151+
code: (
152+
<pre className="language-typescript">
153+
<code className="language-typescript">
154+
{`import * as t from 'io-ts';
155+
`}
156+
<span
157+
style={{
158+
backgroundColor: colors.highlightGreen,
159+
display: 'block',
160+
margin: '0 -1rem',
161+
padding: '0 1rem',
162+
}}
163+
>
164+
{`import { NumberFromString } from 'io-ts-types';`}
165+
</span>
166+
{`import { httpRoute, httpRequest } from '@api-ts/io-ts-http';
167+
168+
const GetHello = httpRoute({
169+
path: '/hello/{name}',
170+
method: 'GET',
171+
request: httpRequest({
172+
params: {
173+
name: t.string,
174+
},
175+
query: {`}
176+
<span
177+
style={{
178+
backgroundColor: colors.highlightGreen,
179+
display: 'block',
180+
margin: '0 -1rem',
181+
padding: '0 1rem',
182+
}}
183+
>
184+
{` repeat: NumberFromString,`}
185+
</span>
186+
{` },
187+
}),
188+
response: {
189+
200: t.string,
190+
},
191+
});`}
192+
</code>
193+
</pre>
194+
),
195+
},
196+
];
197+
198+
return (
199+
<div className="spotlight-container">
200+
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
201+
<div
202+
style={{
203+
display: 'flex',
204+
flexDirection: 'row',
205+
gap: '1rem',
206+
flexWrap: 'wrap',
207+
}}
208+
>
209+
<div style={{ flex: 1, minWidth: '300px' }}>
210+
{steps.map((step, i) => (
211+
<div
212+
key={i}
213+
onClick={() => setSelectedIndex(i)}
214+
style={{
215+
borderLeft: `4px solid ${selectedIndex === i ? colors.borderActive : colors.border}`,
216+
padding: '1rem',
217+
marginBottom: '1rem',
218+
borderRadius: '0.25rem',
219+
backgroundColor: colors.background,
220+
color: colors.text,
221+
cursor: 'pointer',
222+
transition: 'all 0.2s ease',
223+
}}
224+
>
225+
<h3
226+
style={{
227+
marginTop: '0.5rem',
228+
fontSize: '1.25rem',
229+
fontWeight: 'bold',
230+
}}
231+
>
232+
{step.title}
233+
</h3>
234+
<div>{step.content}</div>
235+
</div>
236+
))}
237+
</div>
238+
<div
239+
style={{
240+
flex: 1,
241+
minWidth: '300px',
242+
position: 'sticky',
243+
top: '5rem',
244+
backgroundColor: colors.codeBackground,
245+
borderRadius: '0.25rem',
246+
padding: '0.5rem',
247+
transition: 'background-color 0.2s ease',
248+
}}
249+
>
250+
<div style={{ overflow: 'auto' }}>{steps[selectedIndex]?.code}</div>
251+
</div>
252+
</div>
253+
</div>
254+
</div>
255+
);
256+
}

0 commit comments

Comments
 (0)