@nori-ui/core 1.0.2 → 1.0.4
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/dist/stories/index.cjs
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
4
|
|
|
5
|
-
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
6
5
|
var __defProp = Object.defineProperty;
|
|
7
6
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
7
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
@@ -26,12 +25,9 @@ __name(humanise, "humanise");
|
|
|
26
25
|
// src/stories/csf-loader-bundler.ts
|
|
27
26
|
function discoverCsfModules() {
|
|
28
27
|
try {
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
if (modules && Object.keys(modules).length > 0) {
|
|
33
|
-
return modules;
|
|
34
|
-
}
|
|
28
|
+
const modules = undefined("../components/**/*.stories.tsx", { eager: true });
|
|
29
|
+
if (modules && Object.keys(modules).length > 0) {
|
|
30
|
+
return modules;
|
|
35
31
|
}
|
|
36
32
|
} catch {
|
|
37
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/stories/csf-helpers.ts","../../src/stories/csf-loader-bundler.ts","../../src/stories/csf-loader.tsx"],"names":["createElement"],"mappings":"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","file":"index.cjs","sourcesContent":["// Pure string helpers shared between the runtime CSF loader and the\n// Node-only `csf-slugs` helper. Kept dependency-free so the Node helper\n// never pulls in `csf-loader.tsx` (which depends on `csf-loader-bundler`,\n// which uses `import.meta`).\n\n/**\n * Convert PascalCase → kebab-case using only lowercase→uppercase\n * boundaries so leading acronyms stay intact: `'HStack'` → `'hstack'`,\n * `'AlertDialog'` → `'alert-dialog'`. Matches the docs MDX filename\n * convention (`hstack.mdx`, not `h-stack.mdx`).\n */\nexport function pascalToKebab(s: string): string {\n return s.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();\n}\n\n/**\n * Convert PascalCase → \"With Gap\" for display. Same boundary rule as\n * `pascalToKebab` (only lowercase→uppercase splits).\n */\nexport function humanise(s: string): string {\n const spaced = s.replace(/([a-z0-9])([A-Z])/g, '$1 $2');\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n","// Bundler-specific CSF discovery — kept in its own file because\n// `import.meta` cannot be compiled to CommonJS, which ts-jest's default\n// transform produces. The Jest config redirects this module to a stub\n// during tests so the parser never sees `import.meta`.\n//\n// Production callers come from Metro (require.context) or Vite\n// (import.meta.glob). Both static-string call sites must remain verbatim\n// — each bundler's static analyser matches the AST shape literally and\n// rewrites it at build time.\n\nimport type { CsfModule, RequireContext } from './csf-loader';\n\ndeclare const require: {\n (id: string): unknown;\n context?: (directory: string, useSubdirectories: boolean, regExp: RegExp) => RequireContext;\n};\n\nexport function discoverCsfModules(): Record<string, CsfModule> {\n // Vite / Rollup — `import.meta.glob` statically transformed at build time.\n try {\n // biome-ignore lint/suspicious/noExplicitAny: `glob` is a Vite extension to ImportMeta\n const meta = import.meta as any;\n if (meta && typeof meta.glob === 'function') {\n // biome-ignore lint/suspicious/noExplicitAny: Vite types live in `vite/client` which we don't pull in\n const modules = (import.meta as any).glob('../components/**/*.stories.tsx', { eager: true }) as Record<\n string,\n CsfModule\n >;\n if (modules && Object.keys(modules).length > 0) {\n return modules;\n }\n }\n } catch {\n // import.meta access can throw in non-ESM contexts — fall through.\n }\n\n // Metro / webpack — `require.context` static call rewritten at bundle time.\n try {\n if (typeof require === 'function' && typeof require.context === 'function') {\n const ctx = require.context('../components', true, /\\.stories\\.tsx$/);\n const out: Record<string, CsfModule> = {};\n for (const key of ctx.keys()) {\n out[key] = ctx(key);\n }\n return out;\n }\n } catch {\n // require may be undefined under pure ESM — fall through.\n }\n\n return {};\n}\n","// CSF loader — discovers every `*.stories.tsx` file under `../components`\n// at bundle time via Metro's `require.context`, normalises the CSF default\n// export + named story exports into the `ComponentEntry` shape consumed by\n// the native playground.\n//\n// This is the runtime side of the \"CSF as the single source of truth\"\n// architecture (see `docs/superpowers/specs/2026-04-28-playground-showcase-design.md`).\n// It deliberately handles only the CSF surface the existing stories use:\n// - default.title (string)\n// - default.component (component)\n// - default.args (object, optional)\n// - default.render (function, optional)\n// - named exports: each with optional `args`, optional `render`, and\n// optional `parameters.platforms` (filters the story out when the\n// current platform is not in the list — used for demos that only\n// make sense on a resizable web canvas, etc.)\n// `decorators`, `loaders`, and `play` are ignored.\n\nimport { type ComponentType, createElement, type ReactNode } from 'react';\nimport { humanise, pascalToKebab } from './csf-helpers';\nimport { discoverCsfModules } from './csf-loader-bundler';\n\ntype StoryPlatform = 'web' | 'native';\n// We can't `import { Platform } from 'react-native'` here — Jest's CJS\n// transform chokes on RN's flow-typed entry. The DOM probe is a reliable\n// proxy: only RN's JSC/Hermes runtime lacks `document`. Tests (jsdom)\n// and Storybook see `document` and resolve to `'web'`, which matches\n// where they actually run.\nconst CURRENT_PLATFORM: StoryPlatform = typeof document !== 'undefined' ? 'web' : 'native';\n\nexport type Story = {\n /** kebab-case story id (export name, kebab-cased) */\n id: string;\n /** Humanised story title (export name, spaced) */\n title: string;\n /** Component that renders this story with its merged args */\n render: ComponentType<Record<string, never>>;\n};\n\nexport type ComponentEntry = {\n /** kebab-case slug (last segment of CSF title, kebab-cased) */\n slug: string;\n /** Display name (last segment of CSF title) */\n name: string;\n /** Stories in CSF declaration order */\n stories: Story[];\n};\n\nexport { humanise, pascalToKebab } from './csf-helpers';\n\nexport type CsfModule = {\n default: {\n title: string;\n component?: ComponentType<unknown>;\n args?: Record<string, unknown>;\n render?: (args: Record<string, unknown>) => unknown;\n };\n [key: string]: unknown;\n};\n\nexport type RequireContext = {\n keys(): string[];\n (path: string): CsfModule;\n};\n\n// Tests stub the discovery via `__setCsfModules` below.\nlet testModules: Record<string, CsfModule> | null = null;\n\n/** Test-only: inject CSF modules so Jest can exercise the loader logic. */\nexport function __setCsfModules(modules: Record<string, CsfModule> | null): void {\n testModules = modules;\n}\n\nfunction readModules(): Record<string, CsfModule> {\n if (testModules) {\n return testModules;\n }\n // Bundler-specific discovery (Metro require.context / Vite import.meta.glob)\n // is isolated in csf-loader-bundler.ts because import.meta is a syntax\n // error under ts-jest's CJS target. Jest stubs that module to a no-op.\n return discoverCsfModules();\n}\n\nexport function buildComponents(modules: Record<string, CsfModule>): ComponentEntry[] {\n const entries: ComponentEntry[] = [];\n for (const path of Object.keys(modules)) {\n const mod = modules[path];\n const meta = mod?.default;\n if (!meta || typeof meta.title !== 'string') {\n continue;\n }\n\n const titleLast = meta.title.split('/').pop() ?? meta.title;\n const slug = pascalToKebab(titleLast);\n\n const stories: Story[] = [];\n // Object.keys preserves declaration order for own string keys\n // (per ECMAScript spec). We rely on this for \"stories in CSF\n // declaration order\".\n for (const key of Object.keys(mod)) {\n if (key === 'default') {\n continue;\n }\n const story = mod[key] as\n | {\n args?: Record<string, unknown>;\n render?: (a: Record<string, unknown>) => unknown;\n parameters?: { platforms?: ReadonlyArray<StoryPlatform> };\n }\n | undefined;\n if (!story || typeof story !== 'object') {\n continue;\n }\n\n const platforms = story.parameters?.platforms;\n if (platforms && !platforms.includes(CURRENT_PLATFORM)) {\n continue;\n }\n\n const mergedArgs: Record<string, unknown> = { ...(meta.args ?? {}), ...(story.args ?? {}) };\n const renderFn = story.render ?? meta.render;\n const Component = meta.component;\n\n const Render: ComponentType<Record<string, never>> = renderFn\n ? () => renderFn(mergedArgs) as ReactNode\n : Component\n ? () => createElement(Component as ComponentType<Record<string, unknown>>, mergedArgs)\n : () => null;\n\n stories.push({\n id: pascalToKebab(key),\n title: humanise(key),\n render: Render,\n });\n }\n\n if (stories.length === 0) {\n continue;\n }\n\n entries.push({ slug, name: titleLast, stories });\n }\n\n entries.sort((a, b) => a.slug.localeCompare(b.slug));\n return entries;\n}\n\n/** Native playground's source of truth for components + stories. */\nexport const components: ComponentEntry[] = buildComponents(readModules());\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/stories/csf-helpers.ts","../../src/stories/csf-loader-bundler.ts","../../src/stories/csf-loader.tsx"],"names":["createElement"],"mappings":"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","file":"index.cjs","sourcesContent":["// Pure string helpers shared between the runtime CSF loader and the\n// Node-only `csf-slugs` helper. Kept dependency-free so the Node helper\n// never pulls in `csf-loader.tsx` (which depends on `csf-loader-bundler`,\n// which uses `import.meta`).\n\n/**\n * Convert PascalCase → kebab-case using only lowercase→uppercase\n * boundaries so leading acronyms stay intact: `'HStack'` → `'hstack'`,\n * `'AlertDialog'` → `'alert-dialog'`. Matches the docs MDX filename\n * convention (`hstack.mdx`, not `h-stack.mdx`).\n */\nexport function pascalToKebab(s: string): string {\n return s.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();\n}\n\n/**\n * Convert PascalCase → \"With Gap\" for display. Same boundary rule as\n * `pascalToKebab` (only lowercase→uppercase splits).\n */\nexport function humanise(s: string): string {\n const spaced = s.replace(/([a-z0-9])([A-Z])/g, '$1 $2');\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n","// Bundler-specific CSF discovery — kept in its own file because\n// `import.meta` cannot be compiled to CommonJS, which ts-jest's default\n// transform produces. The Jest config redirects this module to a stub\n// during tests so the parser never sees `import.meta`.\n//\n// Production callers come from Metro (require.context) or Vite\n// (import.meta.glob). Both static-string call sites must remain verbatim\n// — each bundler's static analyser matches the AST shape literally and\n// rewrites it at build time.\n\nimport type { CsfModule, RequireContext } from './csf-loader';\n\ndeclare const require: {\n (id: string): unknown;\n context?: (directory: string, useSubdirectories: boolean, regExp: RegExp) => RequireContext;\n};\n\nexport function discoverCsfModules(): Record<string, CsfModule> {\n // Vite / Rollup — `import.meta.glob` is rewritten statically at build\n // time into a literal modules map. We don't gate on a runtime check\n // for `meta.glob` being a function (it isn't — only the static call\n // site is transformed). Vite returns an empty object when the call\n // sees no matches; bundlers that don't recognise the call (Metro)\n // either tree-shake it out or throw, and the catch falls through to\n // the require.context branch below.\n try {\n // biome-ignore lint/suspicious/noExplicitAny: Vite types live in `vite/client` which we don't pull in\n const modules = (import.meta as any).glob('../components/**/*.stories.tsx', { eager: true }) as Record<\n string,\n CsfModule\n >;\n if (modules && Object.keys(modules).length > 0) {\n return modules;\n }\n } catch {\n // import.meta access can throw in non-ESM contexts — fall through.\n }\n\n // Metro / webpack — `require.context` static call rewritten at bundle time.\n try {\n if (typeof require === 'function' && typeof require.context === 'function') {\n const ctx = require.context('../components', true, /\\.stories\\.tsx$/);\n const out: Record<string, CsfModule> = {};\n for (const key of ctx.keys()) {\n out[key] = ctx(key);\n }\n return out;\n }\n } catch {\n // require may be undefined under pure ESM — fall through.\n }\n\n return {};\n}\n","// CSF loader — discovers every `*.stories.tsx` file under `../components`\n// at bundle time via Metro's `require.context`, normalises the CSF default\n// export + named story exports into the `ComponentEntry` shape consumed by\n// the native playground.\n//\n// This is the runtime side of the \"CSF as the single source of truth\"\n// architecture (see `docs/superpowers/specs/2026-04-28-playground-showcase-design.md`).\n// It deliberately handles only the CSF surface the existing stories use:\n// - default.title (string)\n// - default.component (component)\n// - default.args (object, optional)\n// - default.render (function, optional)\n// - named exports: each with optional `args`, optional `render`, and\n// optional `parameters.platforms` (filters the story out when the\n// current platform is not in the list — used for demos that only\n// make sense on a resizable web canvas, etc.)\n// `decorators`, `loaders`, and `play` are ignored.\n\nimport { type ComponentType, createElement, type ReactNode } from 'react';\nimport { humanise, pascalToKebab } from './csf-helpers';\nimport { discoverCsfModules } from './csf-loader-bundler';\n\ntype StoryPlatform = 'web' | 'native';\n// We can't `import { Platform } from 'react-native'` here — Jest's CJS\n// transform chokes on RN's flow-typed entry. The DOM probe is a reliable\n// proxy: only RN's JSC/Hermes runtime lacks `document`. Tests (jsdom)\n// and Storybook see `document` and resolve to `'web'`, which matches\n// where they actually run.\nconst CURRENT_PLATFORM: StoryPlatform = typeof document !== 'undefined' ? 'web' : 'native';\n\nexport type Story = {\n /** kebab-case story id (export name, kebab-cased) */\n id: string;\n /** Humanised story title (export name, spaced) */\n title: string;\n /** Component that renders this story with its merged args */\n render: ComponentType<Record<string, never>>;\n};\n\nexport type ComponentEntry = {\n /** kebab-case slug (last segment of CSF title, kebab-cased) */\n slug: string;\n /** Display name (last segment of CSF title) */\n name: string;\n /** Stories in CSF declaration order */\n stories: Story[];\n};\n\nexport { humanise, pascalToKebab } from './csf-helpers';\n\nexport type CsfModule = {\n default: {\n title: string;\n component?: ComponentType<unknown>;\n args?: Record<string, unknown>;\n render?: (args: Record<string, unknown>) => unknown;\n };\n [key: string]: unknown;\n};\n\nexport type RequireContext = {\n keys(): string[];\n (path: string): CsfModule;\n};\n\n// Tests stub the discovery via `__setCsfModules` below.\nlet testModules: Record<string, CsfModule> | null = null;\n\n/** Test-only: inject CSF modules so Jest can exercise the loader logic. */\nexport function __setCsfModules(modules: Record<string, CsfModule> | null): void {\n testModules = modules;\n}\n\nfunction readModules(): Record<string, CsfModule> {\n if (testModules) {\n return testModules;\n }\n // Bundler-specific discovery (Metro require.context / Vite import.meta.glob)\n // is isolated in csf-loader-bundler.ts because import.meta is a syntax\n // error under ts-jest's CJS target. Jest stubs that module to a no-op.\n return discoverCsfModules();\n}\n\nexport function buildComponents(modules: Record<string, CsfModule>): ComponentEntry[] {\n const entries: ComponentEntry[] = [];\n for (const path of Object.keys(modules)) {\n const mod = modules[path];\n const meta = mod?.default;\n if (!meta || typeof meta.title !== 'string') {\n continue;\n }\n\n const titleLast = meta.title.split('/').pop() ?? meta.title;\n const slug = pascalToKebab(titleLast);\n\n const stories: Story[] = [];\n // Object.keys preserves declaration order for own string keys\n // (per ECMAScript spec). We rely on this for \"stories in CSF\n // declaration order\".\n for (const key of Object.keys(mod)) {\n if (key === 'default') {\n continue;\n }\n const story = mod[key] as\n | {\n args?: Record<string, unknown>;\n render?: (a: Record<string, unknown>) => unknown;\n parameters?: { platforms?: ReadonlyArray<StoryPlatform> };\n }\n | undefined;\n if (!story || typeof story !== 'object') {\n continue;\n }\n\n const platforms = story.parameters?.platforms;\n if (platforms && !platforms.includes(CURRENT_PLATFORM)) {\n continue;\n }\n\n const mergedArgs: Record<string, unknown> = { ...(meta.args ?? {}), ...(story.args ?? {}) };\n const renderFn = story.render ?? meta.render;\n const Component = meta.component;\n\n const Render: ComponentType<Record<string, never>> = renderFn\n ? () => renderFn(mergedArgs) as ReactNode\n : Component\n ? () => createElement(Component as ComponentType<Record<string, unknown>>, mergedArgs)\n : () => null;\n\n stories.push({\n id: pascalToKebab(key),\n title: humanise(key),\n render: Render,\n });\n }\n\n if (stories.length === 0) {\n continue;\n }\n\n entries.push({ slug, name: titleLast, stories });\n }\n\n entries.sort((a, b) => a.slug.localeCompare(b.slug));\n return entries;\n}\n\n/** Native playground's source of truth for components + stories. */\nexport const components: ComponentEntry[] = buildComponents(readModules());\n"]}
|
package/dist/stories/index.js
CHANGED
|
@@ -15,12 +15,9 @@ __name(humanise, "humanise");
|
|
|
15
15
|
// src/stories/csf-loader-bundler.ts
|
|
16
16
|
function discoverCsfModules() {
|
|
17
17
|
try {
|
|
18
|
-
const
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
if (modules && Object.keys(modules).length > 0) {
|
|
22
|
-
return modules;
|
|
23
|
-
}
|
|
18
|
+
const modules = import.meta.glob("../components/**/*.stories.tsx", { eager: true });
|
|
19
|
+
if (modules && Object.keys(modules).length > 0) {
|
|
20
|
+
return modules;
|
|
24
21
|
}
|
|
25
22
|
} catch {
|
|
26
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/stories/csf-helpers.ts","../../src/stories/csf-loader-bundler.ts","../../src/stories/csf-loader.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Pure string helpers shared between the runtime CSF loader and the\n// Node-only `csf-slugs` helper. Kept dependency-free so the Node helper\n// never pulls in `csf-loader.tsx` (which depends on `csf-loader-bundler`,\n// which uses `import.meta`).\n\n/**\n * Convert PascalCase → kebab-case using only lowercase→uppercase\n * boundaries so leading acronyms stay intact: `'HStack'` → `'hstack'`,\n * `'AlertDialog'` → `'alert-dialog'`. Matches the docs MDX filename\n * convention (`hstack.mdx`, not `h-stack.mdx`).\n */\nexport function pascalToKebab(s: string): string {\n return s.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();\n}\n\n/**\n * Convert PascalCase → \"With Gap\" for display. Same boundary rule as\n * `pascalToKebab` (only lowercase→uppercase splits).\n */\nexport function humanise(s: string): string {\n const spaced = s.replace(/([a-z0-9])([A-Z])/g, '$1 $2');\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n","// Bundler-specific CSF discovery — kept in its own file because\n// `import.meta` cannot be compiled to CommonJS, which ts-jest's default\n// transform produces. The Jest config redirects this module to a stub\n// during tests so the parser never sees `import.meta`.\n//\n// Production callers come from Metro (require.context) or Vite\n// (import.meta.glob). Both static-string call sites must remain verbatim\n// — each bundler's static analyser matches the AST shape literally and\n// rewrites it at build time.\n\nimport type { CsfModule, RequireContext } from './csf-loader';\n\ndeclare const require: {\n (id: string): unknown;\n context?: (directory: string, useSubdirectories: boolean, regExp: RegExp) => RequireContext;\n};\n\nexport function discoverCsfModules(): Record<string, CsfModule> {\n // Vite / Rollup — `import.meta.glob` statically transformed at build time.\n try {\n // biome-ignore lint/suspicious/noExplicitAny: `glob` is a Vite extension to ImportMeta\n const meta = import.meta as any;\n if (meta && typeof meta.glob === 'function') {\n // biome-ignore lint/suspicious/noExplicitAny: Vite types live in `vite/client` which we don't pull in\n const modules = (import.meta as any).glob('../components/**/*.stories.tsx', { eager: true }) as Record<\n string,\n CsfModule\n >;\n if (modules && Object.keys(modules).length > 0) {\n return modules;\n }\n }\n } catch {\n // import.meta access can throw in non-ESM contexts — fall through.\n }\n\n // Metro / webpack — `require.context` static call rewritten at bundle time.\n try {\n if (typeof require === 'function' && typeof require.context === 'function') {\n const ctx = require.context('../components', true, /\\.stories\\.tsx$/);\n const out: Record<string, CsfModule> = {};\n for (const key of ctx.keys()) {\n out[key] = ctx(key);\n }\n return out;\n }\n } catch {\n // require may be undefined under pure ESM — fall through.\n }\n\n return {};\n}\n","// CSF loader — discovers every `*.stories.tsx` file under `../components`\n// at bundle time via Metro's `require.context`, normalises the CSF default\n// export + named story exports into the `ComponentEntry` shape consumed by\n// the native playground.\n//\n// This is the runtime side of the \"CSF as the single source of truth\"\n// architecture (see `docs/superpowers/specs/2026-04-28-playground-showcase-design.md`).\n// It deliberately handles only the CSF surface the existing stories use:\n// - default.title (string)\n// - default.component (component)\n// - default.args (object, optional)\n// - default.render (function, optional)\n// - named exports: each with optional `args`, optional `render`, and\n// optional `parameters.platforms` (filters the story out when the\n// current platform is not in the list — used for demos that only\n// make sense on a resizable web canvas, etc.)\n// `decorators`, `loaders`, and `play` are ignored.\n\nimport { type ComponentType, createElement, type ReactNode } from 'react';\nimport { humanise, pascalToKebab } from './csf-helpers';\nimport { discoverCsfModules } from './csf-loader-bundler';\n\ntype StoryPlatform = 'web' | 'native';\n// We can't `import { Platform } from 'react-native'` here — Jest's CJS\n// transform chokes on RN's flow-typed entry. The DOM probe is a reliable\n// proxy: only RN's JSC/Hermes runtime lacks `document`. Tests (jsdom)\n// and Storybook see `document` and resolve to `'web'`, which matches\n// where they actually run.\nconst CURRENT_PLATFORM: StoryPlatform = typeof document !== 'undefined' ? 'web' : 'native';\n\nexport type Story = {\n /** kebab-case story id (export name, kebab-cased) */\n id: string;\n /** Humanised story title (export name, spaced) */\n title: string;\n /** Component that renders this story with its merged args */\n render: ComponentType<Record<string, never>>;\n};\n\nexport type ComponentEntry = {\n /** kebab-case slug (last segment of CSF title, kebab-cased) */\n slug: string;\n /** Display name (last segment of CSF title) */\n name: string;\n /** Stories in CSF declaration order */\n stories: Story[];\n};\n\nexport { humanise, pascalToKebab } from './csf-helpers';\n\nexport type CsfModule = {\n default: {\n title: string;\n component?: ComponentType<unknown>;\n args?: Record<string, unknown>;\n render?: (args: Record<string, unknown>) => unknown;\n };\n [key: string]: unknown;\n};\n\nexport type RequireContext = {\n keys(): string[];\n (path: string): CsfModule;\n};\n\n// Tests stub the discovery via `__setCsfModules` below.\nlet testModules: Record<string, CsfModule> | null = null;\n\n/** Test-only: inject CSF modules so Jest can exercise the loader logic. */\nexport function __setCsfModules(modules: Record<string, CsfModule> | null): void {\n testModules = modules;\n}\n\nfunction readModules(): Record<string, CsfModule> {\n if (testModules) {\n return testModules;\n }\n // Bundler-specific discovery (Metro require.context / Vite import.meta.glob)\n // is isolated in csf-loader-bundler.ts because import.meta is a syntax\n // error under ts-jest's CJS target. Jest stubs that module to a no-op.\n return discoverCsfModules();\n}\n\nexport function buildComponents(modules: Record<string, CsfModule>): ComponentEntry[] {\n const entries: ComponentEntry[] = [];\n for (const path of Object.keys(modules)) {\n const mod = modules[path];\n const meta = mod?.default;\n if (!meta || typeof meta.title !== 'string') {\n continue;\n }\n\n const titleLast = meta.title.split('/').pop() ?? meta.title;\n const slug = pascalToKebab(titleLast);\n\n const stories: Story[] = [];\n // Object.keys preserves declaration order for own string keys\n // (per ECMAScript spec). We rely on this for \"stories in CSF\n // declaration order\".\n for (const key of Object.keys(mod)) {\n if (key === 'default') {\n continue;\n }\n const story = mod[key] as\n | {\n args?: Record<string, unknown>;\n render?: (a: Record<string, unknown>) => unknown;\n parameters?: { platforms?: ReadonlyArray<StoryPlatform> };\n }\n | undefined;\n if (!story || typeof story !== 'object') {\n continue;\n }\n\n const platforms = story.parameters?.platforms;\n if (platforms && !platforms.includes(CURRENT_PLATFORM)) {\n continue;\n }\n\n const mergedArgs: Record<string, unknown> = { ...(meta.args ?? {}), ...(story.args ?? {}) };\n const renderFn = story.render ?? meta.render;\n const Component = meta.component;\n\n const Render: ComponentType<Record<string, never>> = renderFn\n ? () => renderFn(mergedArgs) as ReactNode\n : Component\n ? () => createElement(Component as ComponentType<Record<string, unknown>>, mergedArgs)\n : () => null;\n\n stories.push({\n id: pascalToKebab(key),\n title: humanise(key),\n render: Render,\n });\n }\n\n if (stories.length === 0) {\n continue;\n }\n\n entries.push({ slug, name: titleLast, stories });\n }\n\n entries.sort((a, b) => a.slug.localeCompare(b.slug));\n return entries;\n}\n\n/** Native playground's source of truth for components + stories. */\nexport const components: ComponentEntry[] = buildComponents(readModules());\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/stories/csf-helpers.ts","../../src/stories/csf-loader-bundler.ts","../../src/stories/csf-loader.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Pure string helpers shared between the runtime CSF loader and the\n// Node-only `csf-slugs` helper. Kept dependency-free so the Node helper\n// never pulls in `csf-loader.tsx` (which depends on `csf-loader-bundler`,\n// which uses `import.meta`).\n\n/**\n * Convert PascalCase → kebab-case using only lowercase→uppercase\n * boundaries so leading acronyms stay intact: `'HStack'` → `'hstack'`,\n * `'AlertDialog'` → `'alert-dialog'`. Matches the docs MDX filename\n * convention (`hstack.mdx`, not `h-stack.mdx`).\n */\nexport function pascalToKebab(s: string): string {\n return s.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();\n}\n\n/**\n * Convert PascalCase → \"With Gap\" for display. Same boundary rule as\n * `pascalToKebab` (only lowercase→uppercase splits).\n */\nexport function humanise(s: string): string {\n const spaced = s.replace(/([a-z0-9])([A-Z])/g, '$1 $2');\n return spaced.charAt(0).toUpperCase() + spaced.slice(1);\n}\n","// Bundler-specific CSF discovery — kept in its own file because\n// `import.meta` cannot be compiled to CommonJS, which ts-jest's default\n// transform produces. The Jest config redirects this module to a stub\n// during tests so the parser never sees `import.meta`.\n//\n// Production callers come from Metro (require.context) or Vite\n// (import.meta.glob). Both static-string call sites must remain verbatim\n// — each bundler's static analyser matches the AST shape literally and\n// rewrites it at build time.\n\nimport type { CsfModule, RequireContext } from './csf-loader';\n\ndeclare const require: {\n (id: string): unknown;\n context?: (directory: string, useSubdirectories: boolean, regExp: RegExp) => RequireContext;\n};\n\nexport function discoverCsfModules(): Record<string, CsfModule> {\n // Vite / Rollup — `import.meta.glob` is rewritten statically at build\n // time into a literal modules map. We don't gate on a runtime check\n // for `meta.glob` being a function (it isn't — only the static call\n // site is transformed). Vite returns an empty object when the call\n // sees no matches; bundlers that don't recognise the call (Metro)\n // either tree-shake it out or throw, and the catch falls through to\n // the require.context branch below.\n try {\n // biome-ignore lint/suspicious/noExplicitAny: Vite types live in `vite/client` which we don't pull in\n const modules = (import.meta as any).glob('../components/**/*.stories.tsx', { eager: true }) as Record<\n string,\n CsfModule\n >;\n if (modules && Object.keys(modules).length > 0) {\n return modules;\n }\n } catch {\n // import.meta access can throw in non-ESM contexts — fall through.\n }\n\n // Metro / webpack — `require.context` static call rewritten at bundle time.\n try {\n if (typeof require === 'function' && typeof require.context === 'function') {\n const ctx = require.context('../components', true, /\\.stories\\.tsx$/);\n const out: Record<string, CsfModule> = {};\n for (const key of ctx.keys()) {\n out[key] = ctx(key);\n }\n return out;\n }\n } catch {\n // require may be undefined under pure ESM — fall through.\n }\n\n return {};\n}\n","// CSF loader — discovers every `*.stories.tsx` file under `../components`\n// at bundle time via Metro's `require.context`, normalises the CSF default\n// export + named story exports into the `ComponentEntry` shape consumed by\n// the native playground.\n//\n// This is the runtime side of the \"CSF as the single source of truth\"\n// architecture (see `docs/superpowers/specs/2026-04-28-playground-showcase-design.md`).\n// It deliberately handles only the CSF surface the existing stories use:\n// - default.title (string)\n// - default.component (component)\n// - default.args (object, optional)\n// - default.render (function, optional)\n// - named exports: each with optional `args`, optional `render`, and\n// optional `parameters.platforms` (filters the story out when the\n// current platform is not in the list — used for demos that only\n// make sense on a resizable web canvas, etc.)\n// `decorators`, `loaders`, and `play` are ignored.\n\nimport { type ComponentType, createElement, type ReactNode } from 'react';\nimport { humanise, pascalToKebab } from './csf-helpers';\nimport { discoverCsfModules } from './csf-loader-bundler';\n\ntype StoryPlatform = 'web' | 'native';\n// We can't `import { Platform } from 'react-native'` here — Jest's CJS\n// transform chokes on RN's flow-typed entry. The DOM probe is a reliable\n// proxy: only RN's JSC/Hermes runtime lacks `document`. Tests (jsdom)\n// and Storybook see `document` and resolve to `'web'`, which matches\n// where they actually run.\nconst CURRENT_PLATFORM: StoryPlatform = typeof document !== 'undefined' ? 'web' : 'native';\n\nexport type Story = {\n /** kebab-case story id (export name, kebab-cased) */\n id: string;\n /** Humanised story title (export name, spaced) */\n title: string;\n /** Component that renders this story with its merged args */\n render: ComponentType<Record<string, never>>;\n};\n\nexport type ComponentEntry = {\n /** kebab-case slug (last segment of CSF title, kebab-cased) */\n slug: string;\n /** Display name (last segment of CSF title) */\n name: string;\n /** Stories in CSF declaration order */\n stories: Story[];\n};\n\nexport { humanise, pascalToKebab } from './csf-helpers';\n\nexport type CsfModule = {\n default: {\n title: string;\n component?: ComponentType<unknown>;\n args?: Record<string, unknown>;\n render?: (args: Record<string, unknown>) => unknown;\n };\n [key: string]: unknown;\n};\n\nexport type RequireContext = {\n keys(): string[];\n (path: string): CsfModule;\n};\n\n// Tests stub the discovery via `__setCsfModules` below.\nlet testModules: Record<string, CsfModule> | null = null;\n\n/** Test-only: inject CSF modules so Jest can exercise the loader logic. */\nexport function __setCsfModules(modules: Record<string, CsfModule> | null): void {\n testModules = modules;\n}\n\nfunction readModules(): Record<string, CsfModule> {\n if (testModules) {\n return testModules;\n }\n // Bundler-specific discovery (Metro require.context / Vite import.meta.glob)\n // is isolated in csf-loader-bundler.ts because import.meta is a syntax\n // error under ts-jest's CJS target. Jest stubs that module to a no-op.\n return discoverCsfModules();\n}\n\nexport function buildComponents(modules: Record<string, CsfModule>): ComponentEntry[] {\n const entries: ComponentEntry[] = [];\n for (const path of Object.keys(modules)) {\n const mod = modules[path];\n const meta = mod?.default;\n if (!meta || typeof meta.title !== 'string') {\n continue;\n }\n\n const titleLast = meta.title.split('/').pop() ?? meta.title;\n const slug = pascalToKebab(titleLast);\n\n const stories: Story[] = [];\n // Object.keys preserves declaration order for own string keys\n // (per ECMAScript spec). We rely on this for \"stories in CSF\n // declaration order\".\n for (const key of Object.keys(mod)) {\n if (key === 'default') {\n continue;\n }\n const story = mod[key] as\n | {\n args?: Record<string, unknown>;\n render?: (a: Record<string, unknown>) => unknown;\n parameters?: { platforms?: ReadonlyArray<StoryPlatform> };\n }\n | undefined;\n if (!story || typeof story !== 'object') {\n continue;\n }\n\n const platforms = story.parameters?.platforms;\n if (platforms && !platforms.includes(CURRENT_PLATFORM)) {\n continue;\n }\n\n const mergedArgs: Record<string, unknown> = { ...(meta.args ?? {}), ...(story.args ?? {}) };\n const renderFn = story.render ?? meta.render;\n const Component = meta.component;\n\n const Render: ComponentType<Record<string, never>> = renderFn\n ? () => renderFn(mergedArgs) as ReactNode\n : Component\n ? () => createElement(Component as ComponentType<Record<string, unknown>>, mergedArgs)\n : () => null;\n\n stories.push({\n id: pascalToKebab(key),\n title: humanise(key),\n render: Render,\n });\n }\n\n if (stories.length === 0) {\n continue;\n }\n\n entries.push({ slug, name: titleLast, stories });\n }\n\n entries.sort((a, b) => a.slug.localeCompare(b.slug));\n return entries;\n}\n\n/** Native playground's source of truth for components + stories. */\nexport const components: ComponentEntry[] = buildComponents(readModules());\n"]}
|
package/package.json
CHANGED