@sntlr/registry-shell 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +200 -0
- package/dist/adapter/custom.d.ts +47 -0
- package/dist/adapter/custom.js +53 -0
- package/dist/adapter/custom.js.map +1 -0
- package/dist/adapter/default.d.ts +40 -0
- package/dist/adapter/default.js +202 -0
- package/dist/adapter/default.js.map +1 -0
- package/dist/cli/build.d.ts +1 -0
- package/dist/cli/build.js +31 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/dev.d.ts +1 -0
- package/dist/cli/dev.js +26 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/index.d.ts +12 -0
- package/dist/cli/index.js +49 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +1 -0
- package/dist/cli/init.js +70 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/shared.d.ts +33 -0
- package/dist/cli/shared.js +278 -0
- package/dist/cli/shared.js.map +1 -0
- package/dist/cli/start.d.ts +1 -0
- package/dist/cli/start.js +24 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/config-loader.d.ts +49 -0
- package/dist/config-loader.js +140 -0
- package/dist/define-config.d.ts +188 -0
- package/dist/define-config.js +21 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +9 -0
- package/package.json +124 -0
- package/src/adapter/custom.ts +90 -0
- package/src/adapter/default.ts +241 -0
- package/src/cli/build.ts +38 -0
- package/src/cli/dev.ts +38 -0
- package/src/cli/index.ts +52 -0
- package/src/cli/init.ts +76 -0
- package/src/cli/shared.ts +306 -0
- package/src/cli/start.ts +28 -0
- package/src/config-loader.ts +190 -0
- package/src/define-config.ts +206 -0
- package/src/index.ts +17 -0
- package/src/next-app/app/[...asset]/route.ts +81 -0
- package/src/next-app/app/_user-global.css +6 -0
- package/src/next-app/app/_user-sources.css +9 -0
- package/src/next-app/app/a11y/[name]/route.ts +19 -0
- package/src/next-app/app/api/search-index/route.ts +19 -0
- package/src/next-app/app/components/[name]/page.tsx +61 -0
- package/src/next-app/app/components/layout.tsx +18 -0
- package/src/next-app/app/docs/[slug]/page.tsx +53 -0
- package/src/next-app/app/docs/layout.tsx +18 -0
- package/src/next-app/app/globals.css +329 -0
- package/src/next-app/app/layout.tsx +102 -0
- package/src/next-app/app/page.tsx +9 -0
- package/src/next-app/app/preview-snapshot/[name]/page.tsx +20 -0
- package/src/next-app/app/preview-snapshot/layout.tsx +17 -0
- package/src/next-app/app/props/[name]/route.ts +19 -0
- package/src/next-app/app/r/[name]/route.ts +14 -0
- package/src/next-app/app/tests/[name]/route.ts +19 -0
- package/src/next-app/components/a11y-info.tsx +287 -0
- package/src/next-app/components/a11y-provider.tsx +39 -0
- package/src/next-app/components/component-breadcrumb.tsx +55 -0
- package/src/next-app/components/component-icon.tsx +140 -0
- package/src/next-app/components/component-preview.tsx +13 -0
- package/src/next-app/components/component-tabs.tsx +209 -0
- package/src/next-app/components/docs-toc.tsx +86 -0
- package/src/next-app/components/global-mobile-sidebar.tsx +35 -0
- package/src/next-app/components/header.tsx +188 -0
- package/src/next-app/components/heading-anchor.tsx +52 -0
- package/src/next-app/components/homepage-demo.tsx +180 -0
- package/src/next-app/components/locale-toggle.tsx +35 -0
- package/src/next-app/components/localized-mdx-client.tsx +14 -0
- package/src/next-app/components/localized-mdx.tsx +27 -0
- package/src/next-app/components/mobile-sidebar.tsx +22 -0
- package/src/next-app/components/nav-data-provider.tsx +37 -0
- package/src/next-app/components/navigation-progress.tsx +62 -0
- package/src/next-app/components/preview-canvas.tsx +368 -0
- package/src/next-app/components/preview-controls.tsx +94 -0
- package/src/next-app/components/preview-layout.tsx +218 -0
- package/src/next-app/components/props-table.tsx +134 -0
- package/src/next-app/components/resizable-preview.tsx +101 -0
- package/src/next-app/components/search.tsx +177 -0
- package/src/next-app/components/settings-modal.tsx +98 -0
- package/src/next-app/components/shell-ui/accordion.tsx +70 -0
- package/src/next-app/components/shell-ui/backdrop.tsx +29 -0
- package/src/next-app/components/shell-ui/badge.tsx +55 -0
- package/src/next-app/components/shell-ui/breadcrumb.tsx +120 -0
- package/src/next-app/components/shell-ui/button.tsx +64 -0
- package/src/next-app/components/shell-ui/card.tsx +127 -0
- package/src/next-app/components/shell-ui/checkbox.tsx +33 -0
- package/src/next-app/components/shell-ui/dialog.tsx +171 -0
- package/src/next-app/components/shell-ui/empty-state.tsx +66 -0
- package/src/next-app/components/shell-ui/input.tsx +27 -0
- package/src/next-app/components/shell-ui/kbd.tsx +30 -0
- package/src/next-app/components/shell-ui/label.tsx +25 -0
- package/src/next-app/components/shell-ui/select.tsx +204 -0
- package/src/next-app/components/shell-ui/separator.tsx +32 -0
- package/src/next-app/components/shell-ui/skeleton.tsx +18 -0
- package/src/next-app/components/shell-ui/table.tsx +124 -0
- package/src/next-app/components/shell-ui/tabs.tsx +102 -0
- package/src/next-app/components/shell-ui/toggle.tsx +56 -0
- package/src/next-app/components/sidebar-layout.tsx +37 -0
- package/src/next-app/components/sidebar-provider.tsx +75 -0
- package/src/next-app/components/sidebar.tsx +222 -0
- package/src/next-app/components/snapshot-preview.tsx +28 -0
- package/src/next-app/components/test-info.tsx +155 -0
- package/src/next-app/components/theme-provider.tsx +16 -0
- package/src/next-app/components/theme-toggle.tsx +21 -0
- package/src/next-app/components/translated-text.tsx +8 -0
- package/src/next-app/fallback/homepage.tsx +112 -0
- package/src/next-app/fallback/previews.ts +17 -0
- package/src/next-app/hooks/use-active-section.ts +23 -0
- package/src/next-app/hooks/use-controls.ts +72 -0
- package/src/next-app/hooks/use-mobile.ts +19 -0
- package/src/next-app/lib/branding.ts +52 -0
- package/src/next-app/lib/components-nav.ts +8 -0
- package/src/next-app/lib/docs.ts +16 -0
- package/src/next-app/lib/github.ts +38 -0
- package/src/next-app/lib/i18n.tsx +630 -0
- package/src/next-app/lib/locales.ts +17 -0
- package/src/next-app/lib/preview-loader.ts +7 -0
- package/src/next-app/lib/registry-adapter.ts +199 -0
- package/src/next-app/lib/utils.ts +6 -0
- package/src/next-app/next-env.d.ts +6 -0
- package/src/next-app/next.config.ts +101 -0
- package/src/next-app/postcss.config.mjs +7 -0
- package/src/next-app/public/favicon.ico +0 -0
- package/src/next-app/public/favicon_dark.svg +3 -0
- package/src/next-app/public/favicon_light.svg +3 -0
- package/src/next-app/registry.config.ts +50 -0
- package/src/next-app/tsconfig.json +29 -0
- package/src/next-app/user-aliases.d.ts +17 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `defineConfig` — the single entry point registry builders use.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* // registry-shell.config.ts
|
|
6
|
+
* import { defineConfig } from "@sntlr/registry-shell"
|
|
7
|
+
*
|
|
8
|
+
* export default defineConfig({
|
|
9
|
+
* branding: { siteName: "My UI", shortName: "UI", ... },
|
|
10
|
+
* // paths/homePage/adapter are optional
|
|
11
|
+
* })
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface GithubConfig {
|
|
16
|
+
/** GitHub org or user that owns the repo. */
|
|
17
|
+
owner: string
|
|
18
|
+
/** Repo name. */
|
|
19
|
+
repo: string
|
|
20
|
+
/** Button label in the header. Default: `"Github"`. */
|
|
21
|
+
label?: string
|
|
22
|
+
/**
|
|
23
|
+
* Show the public star count (fetched server-side, revalidated hourly).
|
|
24
|
+
* Default: `true`.
|
|
25
|
+
*/
|
|
26
|
+
showStars?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface BrandingConfig {
|
|
30
|
+
/** Full product name, e.g. "My UI". Used in HTML title. */
|
|
31
|
+
siteName: string
|
|
32
|
+
/** Short breadcrumb label, e.g. "UI". */
|
|
33
|
+
shortName: string
|
|
34
|
+
/** Canonical URL of the deployed registry, e.g. "https://ui.example.com". */
|
|
35
|
+
siteUrl?: string
|
|
36
|
+
/** SEO meta description. Shown in search results + social cards. */
|
|
37
|
+
description?: string
|
|
38
|
+
/** Path/URL to a 1200×630 Open Graph image. Relative to `siteUrl` if no scheme. */
|
|
39
|
+
ogImage?: string
|
|
40
|
+
/** Twitter handle (without `@`) for Twitter card attribution. */
|
|
41
|
+
twitterHandle?: string
|
|
42
|
+
/**
|
|
43
|
+
* Optional. Adds a GitHub link button to the header. Omit to hide the
|
|
44
|
+
* button entirely (default).
|
|
45
|
+
*/
|
|
46
|
+
github?: GithubConfig
|
|
47
|
+
/** Accessible alt text for the logo image. Default: siteName. */
|
|
48
|
+
logoAlt?: string
|
|
49
|
+
/** Public path to the dark-theme SVG favicon. */
|
|
50
|
+
faviconDark?: string
|
|
51
|
+
/** Public path to the light-theme SVG favicon. */
|
|
52
|
+
faviconLight?: string
|
|
53
|
+
/** Public path to a fallback `.ico` favicon. */
|
|
54
|
+
faviconIco?: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Filesystem locations the default adapter scans. All paths are relative to
|
|
59
|
+
* the config file's directory. Any path can be omitted to use the default.
|
|
60
|
+
*/
|
|
61
|
+
export interface ShellPaths {
|
|
62
|
+
/** Component source files. Default: "components/ui". */
|
|
63
|
+
components?: string
|
|
64
|
+
/** Block directories (each block is a folder). Default: "registry/new-york/blocks". */
|
|
65
|
+
blocks?: string
|
|
66
|
+
/** Preview index file. Default: "components/previews/index.ts" (or .tsx). */
|
|
67
|
+
previews?: string
|
|
68
|
+
/** Doc MDX files. Default: "content/docs". */
|
|
69
|
+
docs?: string
|
|
70
|
+
/** Built registry JSON files (served at /r/[name].json). Default: "public/r". */
|
|
71
|
+
registryJson?: string
|
|
72
|
+
/** A11y JSON files (served at /a11y/[name].json). Default: "public/a11y". */
|
|
73
|
+
a11y?: string
|
|
74
|
+
/** Test JSON files (served at /tests/[name].json). Default: "public/tests". */
|
|
75
|
+
tests?: string
|
|
76
|
+
/** Props JSON files (served at /props/[name].json). Default: "public/props". */
|
|
77
|
+
props?: string
|
|
78
|
+
/** Block names to omit from navigation (e.g. example blocks). */
|
|
79
|
+
skipBlocks?: string[]
|
|
80
|
+
/**
|
|
81
|
+
* Optional. Path to a `.css` file the shell imports AFTER its own
|
|
82
|
+
* globals. Use this for brand fonts (`@font-face`), token overrides
|
|
83
|
+
* (redefine `--primary` etc. on `:root` / `.dark`), extra `@source`
|
|
84
|
+
* directives, or any custom utilities.
|
|
85
|
+
*
|
|
86
|
+
* Imported at the very end of the shell's `globals.css` so your `:root`
|
|
87
|
+
* declarations win the cascade against the shell's defaults.
|
|
88
|
+
*
|
|
89
|
+
* Example: `globalCss: "./styles/theme.css"`.
|
|
90
|
+
*/
|
|
91
|
+
globalCss?: string
|
|
92
|
+
/**
|
|
93
|
+
* Optional. Directory (relative to the config file) where
|
|
94
|
+
* `registry-shell build` writes Next's build output, and where
|
|
95
|
+
* `registry-shell start` reads it back from. Default: `.next`.
|
|
96
|
+
*
|
|
97
|
+
* Override only if `.next` collides with something else in your
|
|
98
|
+
* project. Most Next.js hosts (Vercel, Netlify, self-hosted)
|
|
99
|
+
* auto-detect `.next` — if you change this, update your host's
|
|
100
|
+
* "Output Directory" setting to match.
|
|
101
|
+
*/
|
|
102
|
+
buildOutput?: string
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Advanced: point at a custom adapter module. The module must default-export
|
|
107
|
+
* a factory `(resolved: ResolvedShellConfig) => RegistryAdapter`. When unset,
|
|
108
|
+
* the shell uses its built-in convention-based adapter.
|
|
109
|
+
*/
|
|
110
|
+
export type CustomAdapterSpec = string
|
|
111
|
+
|
|
112
|
+
export interface ShellConfig {
|
|
113
|
+
/**
|
|
114
|
+
* Required. Displayed in shell chrome.
|
|
115
|
+
*/
|
|
116
|
+
branding: BrandingConfig
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* When `true`, docs are organized under per-locale subfolders
|
|
120
|
+
* (e.g. `content/docs/en/foo.mdx`, `content/docs/fr/foo.mdx`) and each
|
|
121
|
+
* subfolder name is treated as a locale code. Requires `defaultLocale`.
|
|
122
|
+
*
|
|
123
|
+
* When `false` (default), docs live directly under `paths.docs` and
|
|
124
|
+
* locale variants use the file-extension convention `{slug}.{locale}.mdx`
|
|
125
|
+
* alongside the canonical `{slug}.mdx`.
|
|
126
|
+
*/
|
|
127
|
+
multilocale?: boolean
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Required when `multilocale` is `true`. Locale code (e.g. `"en"`) of the
|
|
131
|
+
* subfolder containing the canonical doc set. Other locales are optional
|
|
132
|
+
* translations and fall back to this one when a slug is missing.
|
|
133
|
+
*/
|
|
134
|
+
defaultLocale?: string
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Optional in multilocale mode. Explicit list of locale codes the shell
|
|
138
|
+
* should offer in its locale toggle (e.g. `["en", "fr", "ja"]`). When
|
|
139
|
+
* unset, the shell auto-discovers locales by scanning subfolders under
|
|
140
|
+
* `paths.docs`.
|
|
141
|
+
*
|
|
142
|
+
* Ignored in single-locale mode (the toggle is hidden).
|
|
143
|
+
*/
|
|
144
|
+
locales?: string[]
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Optional. Override filesystem layout. Defaults match the sntlr-registry
|
|
148
|
+
* convention.
|
|
149
|
+
*/
|
|
150
|
+
paths?: ShellPaths
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Optional. Path to a custom homepage module, relative to config dir.
|
|
154
|
+
* The module must default-export a React component accepting
|
|
155
|
+
* `{ firstDocSlug?: string }` props.
|
|
156
|
+
*/
|
|
157
|
+
homePage?: string
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Optional. Locale → key → value dictionaries merged into the shell's
|
|
161
|
+
* built-in i18n table. Use for marketing copy referenced by a custom
|
|
162
|
+
* homepage.
|
|
163
|
+
*/
|
|
164
|
+
extraTranslations?: Record<string, Record<string, string>>
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Optional. Path to a custom adapter module. See `CustomAdapterSpec`.
|
|
168
|
+
*/
|
|
169
|
+
adapter?: CustomAdapterSpec
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Optional. Pin the dev/start server to a specific port. Falls through to
|
|
173
|
+
* Next.js's default (3000, auto-incrementing if in use) when unset.
|
|
174
|
+
*/
|
|
175
|
+
port?: number
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Optional. Extra npm package names the shell's Next.js build should
|
|
179
|
+
* transpile. Use this when your registry depends on another workspace
|
|
180
|
+
* package (e.g. a shared components library) whose TSX files should be
|
|
181
|
+
* compiled the same way as your own.
|
|
182
|
+
*
|
|
183
|
+
* Forwarded to Next's `transpilePackages`. The shell itself
|
|
184
|
+
* (`@sntlr/registry-shell`) is always transpiled regardless.
|
|
185
|
+
*/
|
|
186
|
+
transpilePackages?: string[]
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Optional. Template the shell uses to render the install command in the
|
|
190
|
+
* component "Install" tab. Supported placeholders:
|
|
191
|
+
* - `{name}` — the component/block slug (e.g. `"button"`)
|
|
192
|
+
* - `{siteUrl}` — `branding.siteUrl` (trailing slash stripped)
|
|
193
|
+
*
|
|
194
|
+
* Default: `"npx shadcn@latest add {siteUrl}/r/{name}.json"`. Set to an
|
|
195
|
+
* empty string to hide the install line entirely.
|
|
196
|
+
*/
|
|
197
|
+
installCommandTemplate?: string
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Identity function with type inference — users call this purely for editor
|
|
202
|
+
* support. No runtime validation here; the shell validates at boot time.
|
|
203
|
+
*/
|
|
204
|
+
export function defineConfig(config: ShellConfig): ShellConfig {
|
|
205
|
+
return config
|
|
206
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sntlr/registry-shell — public API.
|
|
3
|
+
*
|
|
4
|
+
* Registry builders import `defineConfig` from here into their
|
|
5
|
+
* `registry-shell.config.ts`. Custom-adapter authors also import
|
|
6
|
+
* `ResolvedShellConfig` to type their factory's argument.
|
|
7
|
+
*/
|
|
8
|
+
export { defineConfig } from "./define-config.js"
|
|
9
|
+
export type {
|
|
10
|
+
ShellConfig,
|
|
11
|
+
BrandingConfig,
|
|
12
|
+
GithubConfig,
|
|
13
|
+
ShellPaths,
|
|
14
|
+
CustomAdapterSpec,
|
|
15
|
+
} from "./define-config.js"
|
|
16
|
+
export type { ResolvedShellConfig } from "./config-loader.js"
|
|
17
|
+
export type { AdapterOverrides } from "./adapter/custom.js"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catch-all route that serves arbitrary files from the user registry's
|
|
3
|
+
* `public/` directory. Fires only when no other app route matches, so it
|
|
4
|
+
* doesn't interfere with static routes like `/`, `/docs/...`, `/r/...`,
|
|
5
|
+
* etc. The shell's own `public/` assets are served first by Next.js's
|
|
6
|
+
* built-in static handler; this route handles the user's project's assets
|
|
7
|
+
* since Next only serves the bundled package's `public/`.
|
|
8
|
+
*
|
|
9
|
+
* Examples of what this enables:
|
|
10
|
+
* - `` inside user MDX resolving to
|
|
11
|
+
* `{userRoot}/public/my-image.png`
|
|
12
|
+
* - Custom favicons referenced in `branding.faviconDark` that live under
|
|
13
|
+
* `{userRoot}/public/`
|
|
14
|
+
*/
|
|
15
|
+
import "server-only"
|
|
16
|
+
import fs from "node:fs"
|
|
17
|
+
import path from "node:path"
|
|
18
|
+
import { NextRequest } from "next/server"
|
|
19
|
+
|
|
20
|
+
const MIME: Record<string, string> = {
|
|
21
|
+
".png": "image/png",
|
|
22
|
+
".jpg": "image/jpeg",
|
|
23
|
+
".jpeg": "image/jpeg",
|
|
24
|
+
".gif": "image/gif",
|
|
25
|
+
".webp": "image/webp",
|
|
26
|
+
".avif": "image/avif",
|
|
27
|
+
".svg": "image/svg+xml",
|
|
28
|
+
".ico": "image/x-icon",
|
|
29
|
+
".woff": "font/woff",
|
|
30
|
+
".woff2": "font/woff2",
|
|
31
|
+
".ttf": "font/ttf",
|
|
32
|
+
".otf": "font/otf",
|
|
33
|
+
".json": "application/json",
|
|
34
|
+
".txt": "text/plain; charset=utf-8",
|
|
35
|
+
".xml": "application/xml",
|
|
36
|
+
".pdf": "application/pdf",
|
|
37
|
+
".mp4": "video/mp4",
|
|
38
|
+
".webm": "video/webm",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function GET(
|
|
42
|
+
_request: NextRequest,
|
|
43
|
+
{ params }: { params: Promise<{ asset: string[] }> },
|
|
44
|
+
) {
|
|
45
|
+
const { asset } = await params
|
|
46
|
+
const userRoot = process.env.USER_REGISTRY_ROOT
|
|
47
|
+
if (!userRoot || asset.length === 0) {
|
|
48
|
+
return new Response("Not found", { status: 404 })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Reject traversal.
|
|
52
|
+
if (asset.some((seg) => seg.includes("..") || seg.includes("\0"))) {
|
|
53
|
+
return new Response("Forbidden", { status: 403 })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const publicDir = path.join(userRoot, "public")
|
|
57
|
+
const filePath = path.join(publicDir, ...asset)
|
|
58
|
+
|
|
59
|
+
// Defense-in-depth: make sure the resolved path is still under public/.
|
|
60
|
+
const resolved = path.resolve(filePath)
|
|
61
|
+
if (!resolved.startsWith(path.resolve(publicDir) + path.sep)) {
|
|
62
|
+
return new Response("Forbidden", { status: 403 })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const stat = await fs.promises.stat(resolved)
|
|
67
|
+
if (!stat.isFile()) return new Response("Not found", { status: 404 })
|
|
68
|
+
const body = await fs.promises.readFile(resolved)
|
|
69
|
+
const type = MIME[path.extname(resolved).toLowerCase()] ?? "application/octet-stream"
|
|
70
|
+
return new Response(new Uint8Array(body), {
|
|
71
|
+
status: 200,
|
|
72
|
+
headers: {
|
|
73
|
+
"Content-Type": type,
|
|
74
|
+
"Content-Length": String(stat.size),
|
|
75
|
+
"Cache-Control": "public, max-age=0, must-revalidate",
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
} catch {
|
|
79
|
+
return new Response("Not found", { status: 404 })
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/* Auto-generated by @sntlr/registry-shell. Do not edit.
|
|
2
|
+
*
|
|
3
|
+
* This file is rewritten by the CLI on every `registry-shell dev/build` boot.
|
|
4
|
+
* When a registry sets `paths.globalCss` in its config, the generated file
|
|
5
|
+
* `@import`s that user CSS. Without a config (shell-only mode), it stays a
|
|
6
|
+
* no-op so the `@import "./_user-global.css"` in globals.css never 404s. */
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/* Auto-generated by @sntlr/registry-shell. Do not edit. */
|
|
2
|
+
@source "./";
|
|
3
|
+
@source "../components";
|
|
4
|
+
@source "../lib";
|
|
5
|
+
@source "../hooks";
|
|
6
|
+
@source "../fallback";
|
|
7
|
+
@source "../../../../sntlr-registry/components/ui";
|
|
8
|
+
@source "../../../../sntlr-registry/registry/new-york/blocks";
|
|
9
|
+
@source "../../../../sntlr-registry/components/previews";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server"
|
|
2
|
+
import { registry } from "@shell/registry.config"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Serves the user registry's `public/a11y/{name}.json` files. The shell's
|
|
6
|
+
* own `public/` doesn't hold these (they live in the user's project), so a
|
|
7
|
+
* route handler proxies the read through the adapter.
|
|
8
|
+
*/
|
|
9
|
+
export async function GET(
|
|
10
|
+
_request: NextRequest,
|
|
11
|
+
{ params }: { params: Promise<{ name: string }> },
|
|
12
|
+
) {
|
|
13
|
+
const { name } = await params
|
|
14
|
+
const data = registry?.getA11yData ? await registry.getA11yData(name) : null
|
|
15
|
+
if (!data) {
|
|
16
|
+
return NextResponse.json({ error: "A11y data not found" }, { status: 404 })
|
|
17
|
+
}
|
|
18
|
+
return NextResponse.json(data)
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NextResponse } from "next/server"
|
|
2
|
+
import { getAllDocs } from "@shell/lib/docs"
|
|
3
|
+
import { getAllComponents } from "@shell/lib/components-nav"
|
|
4
|
+
|
|
5
|
+
export function GET() {
|
|
6
|
+
const docs = getAllDocs().map((doc) => ({
|
|
7
|
+
label: doc.title,
|
|
8
|
+
href: `/docs/${doc.slug}`,
|
|
9
|
+
group: "Documentation",
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
const components = getAllComponents().map((comp) => ({
|
|
13
|
+
label: comp.label,
|
|
14
|
+
href: `/components/${comp.name}`,
|
|
15
|
+
group: "Components",
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
return NextResponse.json([...docs, ...components])
|
|
19
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { notFound } from "next/navigation"
|
|
2
|
+
import { getAllComponents } from "@shell/lib/components-nav"
|
|
3
|
+
import { registry } from "@shell/registry.config"
|
|
4
|
+
import { ComponentPreview } from "@shell/components/component-preview"
|
|
5
|
+
import { ComponentTabs } from "@shell/components/component-tabs"
|
|
6
|
+
import { TranslatedText } from "@shell/components/translated-text"
|
|
7
|
+
import { ResizablePreview } from "@shell/components/resizable-preview"
|
|
8
|
+
import { ComponentBreadcrumb } from "@shell/components/component-breadcrumb"
|
|
9
|
+
|
|
10
|
+
export function generateStaticParams() {
|
|
11
|
+
return getAllComponents().map((comp) => ({ name: comp.name }))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function generateMetadata({ params }: { params: Promise<{ name: string }> }) {
|
|
15
|
+
return params.then(({ name }) => {
|
|
16
|
+
const comp = getAllComponents().find((c) => c.name === name)
|
|
17
|
+
if (!comp) return {}
|
|
18
|
+
return {
|
|
19
|
+
title: `${comp.label} - UI Registry`,
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default async function ComponentPage({
|
|
25
|
+
params,
|
|
26
|
+
}: {
|
|
27
|
+
params: Promise<{ name: string }>
|
|
28
|
+
}) {
|
|
29
|
+
const { name } = await params
|
|
30
|
+
const comp = getAllComponents().find((c) => c.name === name)
|
|
31
|
+
|
|
32
|
+
if (!comp) notFound()
|
|
33
|
+
|
|
34
|
+
const source = registry?.getComponentSource(name) ?? null
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div>
|
|
38
|
+
{/* Component header — sticky on desktop, breadcrumb on mobile when scrolled */}
|
|
39
|
+
<ComponentBreadcrumb name={comp.label}>
|
|
40
|
+
<div className="sticky top-14 z-20 bg-background border-b border-border max-md:static max-md:border-b-0">
|
|
41
|
+
<div className="mx-auto px-4 md:px-8 py-4 max-md:py-3">
|
|
42
|
+
<h1 className="text-2xl max-md:text-xl font-bold">{comp.label}</h1>
|
|
43
|
+
<p className="text-sm text-muted-foreground max-md:hidden">
|
|
44
|
+
<TranslatedText k="component.subtitle" />
|
|
45
|
+
</p>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</ComponentBreadcrumb>
|
|
49
|
+
|
|
50
|
+
{/* Resizable preview area */}
|
|
51
|
+
<ResizablePreview>
|
|
52
|
+
<ComponentPreview name={name} />
|
|
53
|
+
</ResizablePreview>
|
|
54
|
+
|
|
55
|
+
{/* Tabs with reserved TOC column on the right. Content clamps to 900px on xl. */}
|
|
56
|
+
<div className="mx-auto px-4 md:px-8 pb-8 w-full">
|
|
57
|
+
<ComponentTabs name={name} source={source} />
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SidebarLayout } from "@shell/components/sidebar-layout"
|
|
2
|
+
import { getAllDocs } from "@shell/lib/docs"
|
|
3
|
+
import { getAllComponents } from "@shell/lib/components-nav"
|
|
4
|
+
|
|
5
|
+
export default function ComponentsLayout({
|
|
6
|
+
children,
|
|
7
|
+
}: {
|
|
8
|
+
children: React.ReactNode
|
|
9
|
+
}) {
|
|
10
|
+
const docs = getAllDocs()
|
|
11
|
+
const components = getAllComponents()
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<SidebarLayout docs={docs} components={components}>
|
|
15
|
+
{children}
|
|
16
|
+
</SidebarLayout>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { notFound } from "next/navigation"
|
|
2
|
+
import { getAllDocs, getDocBySlug, getDocAllLocales } from "@shell/lib/docs"
|
|
3
|
+
import { DocsToc } from "@shell/components/docs-toc"
|
|
4
|
+
import { LocalizedMdx } from "@shell/components/localized-mdx"
|
|
5
|
+
|
|
6
|
+
export function generateStaticParams() {
|
|
7
|
+
return getAllDocs().map((doc) => ({ slug: doc.slug }))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
|
|
11
|
+
return params.then(({ slug }) => {
|
|
12
|
+
const doc = getDocBySlug(slug)
|
|
13
|
+
if (!doc) return {}
|
|
14
|
+
return {
|
|
15
|
+
title: `${doc.meta.title} - UI Registry`,
|
|
16
|
+
description: doc.meta.description,
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default async function DocPage({
|
|
22
|
+
params,
|
|
23
|
+
}: {
|
|
24
|
+
params: Promise<{ slug: string }>
|
|
25
|
+
}) {
|
|
26
|
+
const { slug } = await params
|
|
27
|
+
const doc = getDocBySlug(slug)
|
|
28
|
+
|
|
29
|
+
if (!doc) notFound()
|
|
30
|
+
|
|
31
|
+
const locales = getDocAllLocales(slug)
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="mx-auto py-10 px-4 md:px-8 w-full max-w-300 flex justify-center gap-8">
|
|
35
|
+
{/* Left spacer mirrors the TOC width to keep the article column
|
|
36
|
+
horizontally centered under the topbar at xl+. */}
|
|
37
|
+
<div className="hidden xl:block w-44 shrink-0" aria-hidden="true" />
|
|
38
|
+
<article
|
|
39
|
+
data-docs-content
|
|
40
|
+
className="prose prose-zinc dark:prose-invert flex-1 min-w-0 xl:max-w-225"
|
|
41
|
+
>
|
|
42
|
+
<LocalizedMdx locales={locales} />
|
|
43
|
+
</article>
|
|
44
|
+
{/* TOC column — space always reserved at xl+ to avoid layout shift when
|
|
45
|
+
headings change; inner element hidden below xl since there's no room. */}
|
|
46
|
+
<div className="hidden xl:block w-44 shrink-0">
|
|
47
|
+
<div className="sticky top-20">
|
|
48
|
+
<DocsToc />
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SidebarLayout } from "@shell/components/sidebar-layout"
|
|
2
|
+
import { getAllDocs } from "@shell/lib/docs"
|
|
3
|
+
import { getAllComponents } from "@shell/lib/components-nav"
|
|
4
|
+
|
|
5
|
+
export default function DocsLayout({
|
|
6
|
+
children,
|
|
7
|
+
}: {
|
|
8
|
+
children: React.ReactNode
|
|
9
|
+
}) {
|
|
10
|
+
const docs = getAllDocs()
|
|
11
|
+
const components = getAllComponents()
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<SidebarLayout docs={docs} components={components}>
|
|
15
|
+
{children}
|
|
16
|
+
</SidebarLayout>
|
|
17
|
+
)
|
|
18
|
+
}
|