handoff-app 1.1.2 → 1.1.3-1

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 (31) hide show
  1. package/Changelog.md +81 -0
  2. package/dist/app-builder/build.js +2 -0
  3. package/dist/app-builder/watchers.d.ts +9 -1
  4. package/dist/app-builder/watchers.js +59 -5
  5. package/dist/cli.js +1 -1
  6. package/package.json +2 -1
  7. package/src/app/components/ComponentGuidelines.tsx +3 -1
  8. package/src/app/components/Markdown/MarkdownComponents.tsx +183 -50
  9. package/src/app/components/Navigation/AnchorNav.tsx +28 -28
  10. package/src/app/components/util/index.ts +99 -79
  11. package/src/app/css/index.css +4 -0
  12. package/src/app/pages/{[level1] → [...slug]}/index.tsx +24 -31
  13. package/src/app/pages/assets/fonts.tsx +3 -2
  14. package/src/app/pages/assets/index.tsx +3 -2
  15. package/src/app/pages/assets/logos.tsx +3 -2
  16. package/src/app/pages/foundations/colors.tsx +3 -2
  17. package/src/app/pages/foundations/effects.tsx +3 -2
  18. package/src/app/pages/foundations/icons/index.tsx +3 -2
  19. package/src/app/pages/foundations/index.tsx +3 -2
  20. package/src/app/pages/foundations/logo.tsx +3 -2
  21. package/src/app/pages/foundations/typography.tsx +3 -2
  22. package/src/app/pages/index.tsx +3 -2
  23. package/src/app/pages/system/component/[component]/index.tsx +3 -2
  24. package/src/app/pages/system/index.tsx +3 -2
  25. package/src/app/pages/system/tokens/foundations/colors.tsx +3 -2
  26. package/src/app/pages/system/tokens/foundations/effects.tsx +3 -2
  27. package/src/app/pages/system/tokens/foundations/typography.tsx +3 -2
  28. package/src/app-builder/build.ts +3 -1
  29. package/src/app-builder/watchers.ts +66 -4
  30. package/src/cli.ts +1 -1
  31. package/src/app/pages/[level1]/[level2]/index.tsx +0 -104
@@ -3,8 +3,9 @@ import { groupBy, upperFirst } from 'lodash';
3
3
  import type { GetStaticProps } from 'next';
4
4
  import ReactMarkdown from 'react-markdown';
5
5
  import rehypeRaw from 'rehype-raw';
6
+ import remarkGfm from 'remark-gfm';
6
7
  import Layout from '../../../../components/Layout/Main';
7
- import { MarkdownComponents } from '../../../../components/Markdown/MarkdownComponents';
8
+ import { MarkdownComponents, remarkCodeMeta } from '../../../../components/Markdown/MarkdownComponents';
8
9
  import AnchorNav from '../../../../components/Navigation/AnchorNav';
9
10
  import HeadersType from '../../../../components/Typography/Headers';
10
11
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../../../../components/ui/table';
@@ -61,7 +62,7 @@ const ComponentsPage = ({ content, menu, metadata, current, config, design }: Fo
61
62
  <div className="mt-10 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_280px]">
62
63
  <div>
63
64
  <div className="prose">
64
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
65
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
65
66
  {content}
66
67
  </ReactMarkdown>
67
68
  </div>
@@ -4,8 +4,9 @@ import type { GetStaticProps } from 'next';
4
4
  import React, { ReactElement } from 'react';
5
5
  import ReactMarkdown from 'react-markdown';
6
6
  import rehypeRaw from 'rehype-raw';
7
+ import remarkGfm from 'remark-gfm';
7
8
  import Layout from '../../../../components/Layout/Main';
8
- import { MarkdownComponents } from '../../../../components/Markdown/MarkdownComponents';
9
+ import { MarkdownComponents, remarkCodeMeta } from '../../../../components/Markdown/MarkdownComponents';
9
10
  import AnchorNav from '../../../../components/Navigation/AnchorNav';
10
11
  import HeadersType from '../../../../components/Typography/Headers';
11
12
  import { Table, TableBody, TableCell, TableRow } from '../../../../components/ui/table';
@@ -53,7 +54,7 @@ const ComponentsPage = ({ content, menu, metadata, current, config, design }: Fo
53
54
  <div className="mt-10 lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_280px]">
54
55
  <div>
55
56
  <div className="prose">
56
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
57
+ <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
57
58
  {content}
58
59
  </ReactMarkdown>
59
60
  </div>
@@ -9,7 +9,7 @@ import processComponents from '../transformers/preview/component/builder';
9
9
  import { Logger } from '../utils/logger';
10
10
  import { generateTokensApi, persistClientConfig } from './client-config';
11
11
  import { getAppPath, syncPublicFiles } from './paths';
12
- import { WatcherState, getRuntimeComponentsPathsToWatch, watchAppSource, watchGlobalEntries, watchPages, watchPublicDirectory, watchRuntimeComponents, watchRuntimeConfiguration } from './watchers';
12
+ import { WatcherState, getRuntimeComponentsPathsToWatch, watchAppSource, watchComponentDirectories, watchGlobalEntries, watchPages, watchPublicDirectory, watchRuntimeComponents, watchRuntimeConfiguration } from './watchers';
13
13
  import { createWebSocketServer } from './websocket';
14
14
 
15
15
  const escapeForSingleQuotedJsString = (value: string): string => value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
@@ -193,11 +193,13 @@ export const watchApp = async (handoff: Handoff): Promise<void> => {
193
193
  debounce: false,
194
194
  runtimeComponentsWatcher: null,
195
195
  runtimeConfigurationWatcher: null,
196
+ componentDirectoriesWatcher: null,
196
197
  };
197
198
 
198
199
  watchPublicDirectory(handoff, wss, state, chokidarConfig);
199
200
  watchRuntimeComponents(handoff, state, getRuntimeComponentsPathsToWatch(handoff));
200
201
  watchRuntimeConfiguration(handoff, state);
202
+ watchComponentDirectories(handoff, state, chokidarConfig);
201
203
  watchGlobalEntries(handoff, state, chokidarConfig);
202
204
  watchPages(handoff, chokidarConfig);
203
205
  };
@@ -15,6 +15,7 @@ export interface WatcherState {
15
15
  debounce: boolean;
16
16
  runtimeComponentsWatcher: chokidar.FSWatcher | null;
17
17
  runtimeConfigurationWatcher: chokidar.FSWatcher | null;
18
+ componentDirectoriesWatcher: chokidar.FSWatcher | null;
18
19
  }
19
20
 
20
21
  /**
@@ -258,13 +259,9 @@ export const watchRuntimeConfiguration = (handoff: Handoff, state: WatcherState)
258
259
  state.debounce = true;
259
260
  try {
260
261
  file = path.dirname(file);
261
- // Reload the Handoff instance to pick up configuration changes
262
262
  handoff.reload();
263
- // After reloading, persist the updated client configuration
264
263
  await persistClientConfig(handoff);
265
- // Restart the runtime components watcher to track potentially updated/added/removed components
266
264
  watchRuntimeComponents(handoff, state, getRuntimeComponentsPathsToWatch(handoff));
267
- // Process components based on the updated configuration and file path
268
265
  await processComponents(handoff, path.basename(file));
269
266
  } catch (e) {
270
267
  Logger.error('Error reloading runtime configuration:', e);
@@ -277,3 +274,68 @@ export const watchRuntimeConfiguration = (handoff: Handoff, state: WatcherState)
277
274
  });
278
275
  }
279
276
  };
277
+
278
+ /**
279
+ * Watches the parent component directories from config.entries.components
280
+ * for new components being added. When a new config file (e.g. button.json)
281
+ * appears in a new subdirectory, reloads the runtime config and restarts
282
+ * the component/configuration watchers so the new component is picked up.
283
+ */
284
+ export const watchComponentDirectories = (handoff: Handoff, state: WatcherState, chokidarConfig: chokidar.WatchOptions) => {
285
+ if (state.componentDirectoriesWatcher) {
286
+ state.componentDirectoriesWatcher.close();
287
+ }
288
+
289
+ const componentPaths = handoff.config.entries?.components ?? [];
290
+ if (componentPaths.length === 0) return;
291
+
292
+ const dirsToWatch: string[] = [];
293
+ for (const componentPath of componentPaths) {
294
+ const resolved = path.resolve(handoff.workingPath, componentPath);
295
+ if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
296
+ dirsToWatch.push(resolved);
297
+ }
298
+ }
299
+
300
+ if (dirsToWatch.length === 0) return;
301
+
302
+ const knownComponents = new Set(Object.keys(handoff.runtimeConfig?.entries?.components ?? {}));
303
+
304
+ state.componentDirectoriesWatcher = chokidar.watch(dirsToWatch, {
305
+ ...chokidarConfig,
306
+ depth: 1,
307
+ });
308
+
309
+ state.componentDirectoriesWatcher.on('add', async (file) => {
310
+ const basename = path.basename(file);
311
+ const dirName = path.basename(path.dirname(file));
312
+
313
+ const isConfigFile = basename.endsWith('.json') || basename.endsWith('.js') || basename.endsWith('.cjs');
314
+ const isNewComponent = isConfigFile && basename.startsWith(dirName) && !knownComponents.has(dirName);
315
+
316
+ if (!isNewComponent) return;
317
+
318
+ if (!state.debounce) {
319
+ state.debounce = true;
320
+ try {
321
+ Logger.warn(`New component detected: ${dirName}. Reloading configuration...`);
322
+ handoff.reload();
323
+ knownComponents.add(dirName);
324
+
325
+ // Update known set with all current components
326
+ for (const id of Object.keys(handoff.runtimeConfig?.entries?.components ?? {})) {
327
+ knownComponents.add(id);
328
+ }
329
+
330
+ await persistClientConfig(handoff);
331
+ watchRuntimeComponents(handoff, state, getRuntimeComponentsPathsToWatch(handoff));
332
+ watchRuntimeConfiguration(handoff, state);
333
+ await processComponents(handoff, dirName);
334
+ } catch (e) {
335
+ Logger.error('Error processing new component:', e);
336
+ } finally {
337
+ state.debounce = false;
338
+ }
339
+ }
340
+ });
341
+ };
package/src/cli.ts CHANGED
@@ -19,7 +19,7 @@ class HandoffCliError extends Error {
19
19
  * Show the help message
20
20
  */
21
21
  const showVersion = () => {
22
- return 'Handoff App - 1.1.2';
22
+ return 'Handoff App - 1.1.3';
23
23
  };
24
24
 
25
25
  /**
@@ -1,104 +0,0 @@
1
- import { GetStaticProps } from 'next';
2
- import Head from 'next/head';
3
- import Link from 'next/link';
4
- import ReactMarkdown from 'react-markdown';
5
- import rehypeRaw from 'rehype-raw';
6
- import Layout from '../../../components/Layout/Main';
7
- import { MarkdownComponents } from '../../../components/Markdown/MarkdownComponents';
8
- import HeadersType from '../../../components/Typography/Headers';
9
- import {
10
- buildL2StaticPaths,
11
- DocumentationProps,
12
- fetchDocPageMarkdown,
13
- getClientRuntimeConfig,
14
- IParams,
15
- reduceSlugToString,
16
- } from '../../../components/util';
17
-
18
- export interface SubPageType {
19
- params: {
20
- level1: string;
21
- level2: string;
22
- };
23
- }
24
- /**
25
- * Render all index pages
26
- * @returns
27
- */
28
- export async function getStaticPaths() {
29
- return {
30
- paths: buildL2StaticPaths(),
31
- fallback: false,
32
- };
33
- }
34
- /**
35
- * Get all the markdown data, build a menu tree, and then fetch the contents
36
- * of the current page for rendering
37
- *
38
- * This is all done statically at build time
39
- * @param context GetStaticProps
40
- * @returns
41
- */
42
- export const getStaticProps: GetStaticProps = async (context) => {
43
- // Read current slug
44
- const { level1 } = context.params as IParams;
45
- let { level2 } = context.params as IParams;
46
- if (!level2) {
47
- level2 = '404';
48
- }
49
- return {
50
- props: {
51
- ...fetchDocPageMarkdown(`docs/${level1}/`, reduceSlugToString(level2), `/${level1}`).props,
52
- config: getClientRuntimeConfig(),
53
- },
54
- };
55
- };
56
-
57
- /**
58
- * Render Docs page
59
- * @param param0
60
- * @returns
61
- */
62
- export default function DocSubPage({ content, menu, metadata, current, config }: DocumentationProps) {
63
- if (content) {
64
- return (
65
- <Layout config={config} menu={menu} current={current} metadata={metadata}>
66
- <div className="flex flex-col gap-2 pb-7">
67
- <HeadersType.H1>{metadata.title}</HeadersType.H1>
68
- <p className="text-lg leading-relaxed text-gray-600 dark:text-gray-300">{metadata.description}</p>
69
- </div>
70
- <div>
71
- <div className="prose mb-10">
72
- <ReactMarkdown components={MarkdownComponents} rehypePlugins={[rehypeRaw]}>
73
- {content}
74
- </ReactMarkdown>
75
- </div>
76
- </div>
77
- </Layout>
78
- );
79
- } else {
80
- return (
81
- <div className="flex min-h-screen flex-col items-center justify-center bg-white dark:bg-gray-900">
82
- <Head>
83
- <title>404 - Page Not Found</title>
84
- <meta name="description" content="Page Not Found" />
85
- </Head>
86
- <div className="flex flex-col items-center">
87
- <div className="mb-2 text-7xl font-bold text-gray-800 dark:text-white">404</div>
88
- <h1 className="mb-4 text-2xl font-semibold text-gray-700 dark:text-gray-300">Oops! Page not found.</h1>
89
- <p className="mb-6 max-w-md text-center text-gray-500 dark:text-gray-400">
90
- Sorry, the page you are looking for does not exist or has been moved.
91
- <br />
92
- Please check the URL or return to the homepage.
93
- </p>
94
- <Link
95
- href="/"
96
- className="rounded-md bg-blue-600 px-6 py-2 font-medium text-white shadow-md transition-colors duration-200 hover:bg-blue-700"
97
- >
98
- Go Home
99
- </Link>
100
- </div>
101
- </div>
102
- );
103
- }
104
- }