From 26def9682f897aae55303b465062d850cdb0ad13 Mon Sep 17 00:00:00 2001 From: Young Jun Joo Date: Tue, 15 Apr 2025 12:57:30 -0400 Subject: [PATCH] feat(docs): spotlight component for an article Ticket: DX-1310 Completely rewrote the Spotlight component implementation to work with CodeHike 1.0.5: - Created a custom React component that renders a side-by-side layout with steps and code - Implemented theme-aware styling that dynamically changes colors based on light/dark mode - Added specific code highlighting for important sections (blue for response, red for errors, green for solutions) - Fixed responsive layout issues with proper wrapping and minimum widths - Added transition effects for smoother theme switching This change specifically updates the intermediate-semantic-analysis.md documentation file while maintaining the same educational content and interactive experience from codehike/mdx version 0.9.0. chore(website): Remove unused variable --- .../intermediate-semantic-analysis.md | 95 +------ website/package-lock.json | 12 +- website/package.json | 3 +- website/src/components/CodeHike/Spotlight.jsx | 256 ++++++++++++++++++ website/src/components/CodeHike/index.js | 1 + website/src/css/custom.css | 14 + website/src/theme/MDXComponents.js | 8 + 7 files changed, 295 insertions(+), 94 deletions(-) create mode 100644 website/src/components/CodeHike/Spotlight.jsx create mode 100644 website/src/components/CodeHike/index.js create mode 100644 website/src/theme/MDXComponents.js diff --git a/website/docs/how-to-guides/intermediate-semantic-analysis.md b/website/docs/how-to-guides/intermediate-semantic-analysis.md index 1bbe30d0..f3d0d058 100644 --- a/website/docs/how-to-guides/intermediate-semantic-analysis.md +++ b/website/docs/how-to-guides/intermediate-semantic-analysis.md @@ -4,102 +4,13 @@ sidebar_position: 1 # How to Parse a number from a Query Parameter +import { Spotlight } from '@site/src/components/CodeHike'; + Query parameters are represented as the type `Record`, so using a codec that doesn't decode from a `string | string[] | undefined` will produce a type error. -Consider this `httpRoute` that compiles successfully. - -```typescript -import * as t from 'io-ts'; -import { httpRoute, httpRequest } from '@api-ts/io-ts-http'; - -const GetHello = httpRoute({ - path: '/hello/{name}', - method: 'GET', - request: httpRequest({ - params: { - name: t.string, - }, - }), - response: { - 200: t.string, - }, -}); -``` - -If you add an expected `number` value to the `httpRoute`'s query parameters, you'll see -the following compilation error: - -```typescript -import * as t from 'io-ts'; -import { httpRoute, httpRequest } from '@api-ts/io-ts-http'; - -const GetHello = httpRoute({ - path: '/hello/{name}', - method: 'GET', - request: httpRequest({ - params: { - name: t.string, - }, - query: { - repeat: t.number, // Compilation error! - }, - }), - response: { - 200: t.string, - }, -}); -``` - -If you add an expected `number` value to the `httpRoute`'s query parameters, you'll see -the following compilation error: - -``` -index.ts:16:7 - error TS2322: - Codec's output type is not assignable to - string | string[] | undefined. - Try using one like `NumberFromString` - -13 repeat: t.number -``` - -Recall that `t.number` decodes an `unknown` value into a `number` without any -manipulation of the starting value. If you started with a number, you'll decode a -number. - -We need a codec that decodes a `string` into a `number` and converts the -string-representation of a number into the `number` type. - -This is a fairly common requirement, so this codec already exists: [io-ts-types] offers -the [NumberFromString] codec that decodes a `string` value into a `number`. Use -`NumberFromString` to fix your compilation error. - -[io-ts-types]: https://github.com/gcanti/io-ts-types -[numberfromstring]: - https://gcanti.github.io/io-ts-types/modules/NumberFromString.ts.html - -```typescript -import * as t from 'io-ts'; -import { NumberFromString } from 'io-ts-types'; -import { httpRoute, httpRequest } from '@api-ts/io-ts-http'; - -const GetHello = httpRoute({ - path: '/hello/{name}', - method: 'GET', - request: httpRequest({ - params: { - name: t.string, - }, - query: { - repeat: NumberFromString, - }, - }), - response: { - 200: t.string, - }, -}); -``` + In general, the solution to decoding a query parameter into a non-string type is to use a codec that decodes and encodes from a `string` into your desired type. diff --git a/website/package-lock.json b/website/package-lock.json index f5cc616c..062ef6b1 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -16,7 +16,8 @@ "docusaurus-theme-mdx-v2": "^0.1.2", "prism-react-renderer": "^1.3.5", "react": "^18.0.0", - "react-dom": "^18.0.0" + "react-dom": "^18.0.0", + "zod": "^3.24.2" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.7.0" @@ -21081,6 +21082,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/website/package.json b/website/package.json index 8b97f5f2..27a261f5 100644 --- a/website/package.json +++ b/website/package.json @@ -22,7 +22,8 @@ "docusaurus-theme-mdx-v2": "^0.1.2", "prism-react-renderer": "^1.3.5", "react": "^18.0.0", - "react-dom": "^18.0.0" + "react-dom": "^18.0.0", + "zod": "^3.24.2" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.7.0" diff --git a/website/src/components/CodeHike/Spotlight.jsx b/website/src/components/CodeHike/Spotlight.jsx new file mode 100644 index 00000000..e12f6df3 --- /dev/null +++ b/website/src/components/CodeHike/Spotlight.jsx @@ -0,0 +1,256 @@ +import React from 'react'; +import { useColorMode } from '@docusaurus/theme-common'; + +// A simpler implementation of the Spotlight component +export default function Spotlight() { + // State to track the selected step + const [selectedIndex, setSelectedIndex] = React.useState(0); + // Get the current color mode (light or dark) + const { colorMode } = useColorMode(); + const isDarkTheme = colorMode === 'dark'; + + // Define theme-specific colors + const colors = { + background: isDarkTheme ? '#1e293b' : '#f8fafc', + border: isDarkTheme ? '#334155' : '#e2e8f0', + borderActive: isDarkTheme ? '#60a5fa' : '#3b82f6', + text: isDarkTheme ? '#f1f5f9' : '#0f172a', + codeBackground: isDarkTheme ? '#0f172a' : '#f1f5f9', + highlightBlue: isDarkTheme ? 'rgba(59, 130, 246, 0.3)' : 'rgba(59, 130, 246, 0.15)', + highlightRed: isDarkTheme ? 'rgba(239, 68, 68, 0.3)' : 'rgba(239, 68, 68, 0.15)', + highlightGreen: isDarkTheme + ? 'rgba(16, 185, 129, 0.3)' + : 'rgba(16, 185, 129, 0.15)', + }; + + // Define the steps manually based on the structure in the MDX file + const steps = [ + { + title: 'Working Route', + content: ( +
+

+ Consider this httpRoute that compiles successfully. +

+
+ ), + code: ( +
+          
+            {`import * as t from 'io-ts';
+import { httpRoute, httpRequest } from '@api-ts/io-ts-http';
+
+const GetHello = httpRoute({
+  path: '/hello/{name}',
+  method: 'GET',
+  request: httpRequest({
+    params: {
+      name: t.string,
+    },
+  }),`}
+            
+              {`  response: {
+    200: t.string,
+  },`}
+            
+            {`});
+`}
+          
+        
+ ), + }, + { + title: 'Compilation Error', + content: ( +
+

+ If you add an expected number value to the{' '} + httpRoute's query parameters, you'll see the following + compilation error: +

+

The error message looks like this:

+
+            
+              {`index.ts:16:7 - error TS2322:
+  Codec's output type is not assignable to
+  string | string[] | undefined.
+  Try using one like \`NumberFromString\`
+
+13       repeat: t.number`}
+            
+          
+

+ Recall that t.number decodes an unknown value into + a number without any manipulation of the starting value. If you + started with a number, you'll decode a number. +

+

+ We need a codec that decodes a string into a{' '} + number and converts the string-representation of a number into + the number type. +

+
+ ), + code: ( +
+          
+            {`import * as t from 'io-ts';
+import { httpRoute, httpRequest } from '@api-ts/io-ts-http';
+
+const GetHello = httpRoute({
+  path: '/hello/{name}',
+  method: 'GET',
+  request: httpRequest({
+    params: {
+      name: t.string,
+    },
+    query: {`}
+            
+              {`      repeat: t.number, // Compilation error!`}
+            
+            {`    },
+  }),
+  response: {
+    200: t.string,
+  },
+});`}
+          
+        
+ ), + }, + { + title: 'Solution', + content: ( +
+

+ This is a fairly common requirement, so this codec already exists:{' '} + io-ts-types offers the{' '} + + NumberFromString + {' '} + codec that decodes a string value into a number. + Use + NumberFromString to fix your compilation error. +

+
+ ), + code: ( +
+          
+            {`import * as t from 'io-ts';
+`}
+            
+              {`import { NumberFromString } from 'io-ts-types';`}
+            
+            {`import { httpRoute, httpRequest } from '@api-ts/io-ts-http';
+
+const GetHello = httpRoute({
+  path: '/hello/{name}',
+  method: 'GET',
+  request: httpRequest({
+    params: {
+      name: t.string,
+    },
+    query: {`}
+            
+              {`      repeat: NumberFromString,`}
+            
+            {`    },
+  }),
+  response: {
+    200: t.string,
+  },
+});`}
+          
+        
+ ), + }, + ]; + + return ( +
+
+
+
+ {steps.map((step, i) => ( +
setSelectedIndex(i)} + style={{ + borderLeft: `4px solid ${selectedIndex === i ? colors.borderActive : colors.border}`, + padding: '1rem', + marginBottom: '1rem', + borderRadius: '0.25rem', + backgroundColor: colors.background, + color: colors.text, + cursor: 'pointer', + transition: 'all 0.2s ease', + }} + > +

+ {step.title} +

+
{step.content}
+
+ ))} +
+
+
{steps[selectedIndex]?.code}
+
+
+
+
+ ); +} diff --git a/website/src/components/CodeHike/index.js b/website/src/components/CodeHike/index.js new file mode 100644 index 00000000..21bbef33 --- /dev/null +++ b/website/src/components/CodeHike/index.js @@ -0,0 +1 @@ +export { default as Spotlight } from './Spotlight'; diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 1fc775f9..e8fa94c3 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -119,3 +119,17 @@ border-color: white; color: white; } + +/* CodeHike Spotlight Component Styles */ +.spotlight-container { + margin: 2rem 0; +} + +.spotlight-container pre { + margin: 0; + border-radius: 0.5rem; +} + +.spotlight-container code { + font-size: 0.9rem; +} diff --git a/website/src/theme/MDXComponents.js b/website/src/theme/MDXComponents.js new file mode 100644 index 00000000..4339f637 --- /dev/null +++ b/website/src/theme/MDXComponents.js @@ -0,0 +1,8 @@ +import MDXComponents from '@theme-original/MDXComponents'; +import { Spotlight } from '../components/CodeHike'; + +export default { + ...MDXComponents, + // Add custom components here + Spotlight, +};