@uxfront/layer-docs 0.4.0 → 0.5.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.
Files changed (99) hide show
  1. package/README.md +31 -220
  2. package/app/app.config.ts +8 -92
  3. package/app/components/content/FrameworkSwitcher.vue +66 -40
  4. package/app/components/docs/DocsAsideLeftTop.vue +9 -15
  5. package/app/components/docs/DocsFrameworkSelect.vue +4 -7
  6. package/app/composables/useFramework.ts +30 -38
  7. package/nuxt.config.ts +14 -170
  8. package/package.json +10 -77
  9. package/CHANGELOG.md +0 -180
  10. package/LICENSE +0 -21
  11. package/app/app.vue +0 -138
  12. package/app/assets/css/main.css +0 -15
  13. package/app/components/IconMenuToggle.vue +0 -92
  14. package/app/components/LanguageSelect.vue +0 -73
  15. package/app/components/MorphingGradientBackground.vue +0 -261
  16. package/app/components/OgImage/OgImageDocs.satori.vue +0 -40
  17. package/app/components/OgImage/OgImageLanding.satori.vue +0 -41
  18. package/app/components/app/AppFooter.vue +0 -13
  19. package/app/components/app/AppFooterCenter.vue +0 -17
  20. package/app/components/app/AppFooterLeft.vue +0 -21
  21. package/app/components/app/AppFooterRight.vue +0 -33
  22. package/app/components/app/AppHeader.vue +0 -123
  23. package/app/components/app/AppHeaderAttribution.vue +0 -45
  24. package/app/components/app/AppHeaderBody.vue +0 -14
  25. package/app/components/app/AppHeaderCTA.vue +0 -31
  26. package/app/components/app/AppHeaderCenter.vue +0 -10
  27. package/app/components/app/AppHeaderLogo.vue +0 -16
  28. package/app/components/app/AppOgDecoration.vue +0 -27
  29. package/app/components/app/AppOgLogo.vue +0 -19
  30. package/app/components/app/AppSearch.vue +0 -59
  31. package/app/components/app/AppSubHeader.vue +0 -21
  32. package/app/components/content/BrowserFrame.vue +0 -28
  33. package/app/components/content/GradientPageHero.vue +0 -35
  34. package/app/components/content/StorybookEmbed.vue +0 -160
  35. package/app/components/content/Video.vue +0 -103
  36. package/app/components/docs/DocsAsideLeftBody.vue +0 -20
  37. package/app/components/docs/DocsAsideRightBottom.vue +0 -15
  38. package/app/components/docs/DocsPageHeaderLinks.vue +0 -75
  39. package/app/composables/useDocsSections.ts +0 -57
  40. package/app/composables/useDocusI18n.ts +0 -49
  41. package/app/constants/sections.ts +0 -25
  42. package/app/error.vue +0 -140
  43. package/app/layouts/default.vue +0 -24
  44. package/app/pages/[[lang]]/[...slug].vue +0 -58
  45. package/app/pages/[[lang]]/docs/[section]/[...slug].vue +0 -180
  46. package/app/plugins/i18n.ts +0 -21
  47. package/app/plugins/posthog.client.ts +0 -56
  48. package/app/types/non-route-categories.ts +0 -12
  49. package/app/utils/flattenNavigation.ts +0 -22
  50. package/app/utils/foldNonRouteCategories.ts +0 -47
  51. package/app/utils/prerender.ts +0 -9
  52. package/app/utils/storybookEmbed.test.ts +0 -98
  53. package/app/utils/storybookEmbed.ts +0 -93
  54. package/i18n/locales/ar.json +0 -24
  55. package/i18n/locales/be.json +0 -24
  56. package/i18n/locales/bn.json +0 -24
  57. package/i18n/locales/ca.json +0 -24
  58. package/i18n/locales/ckb.json +0 -24
  59. package/i18n/locales/cs.json +0 -24
  60. package/i18n/locales/da.json +0 -24
  61. package/i18n/locales/de.json +0 -24
  62. package/i18n/locales/el.json +0 -24
  63. package/i18n/locales/en.json +0 -24
  64. package/i18n/locales/et.json +0 -24
  65. package/i18n/locales/fr.json +0 -24
  66. package/i18n/locales/he.json +0 -24
  67. package/i18n/locales/hi.json +0 -24
  68. package/i18n/locales/hy.json +0 -24
  69. package/i18n/locales/it.json +0 -24
  70. package/i18n/locales/ja.json +0 -24
  71. package/i18n/locales/kk.json +0 -24
  72. package/i18n/locales/km.json +0 -24
  73. package/i18n/locales/ko.json +0 -24
  74. package/i18n/locales/ky.json +0 -24
  75. package/i18n/locales/lb.json +0 -24
  76. package/i18n/locales/ms.json +0 -24
  77. package/i18n/locales/nb.json +0 -24
  78. package/i18n/locales/pl.json +0 -24
  79. package/i18n/locales/ru.json +0 -24
  80. package/i18n/locales/sl.json +0 -24
  81. package/i18n/locales/sv.json +0 -24
  82. package/i18n/locales/uk.json +0 -24
  83. package/i18n/locales/ur.json +0 -24
  84. package/i18n/locales/vi.json +0 -24
  85. package/modules/config.ts +0 -144
  86. package/modules/optimizeDeps.ts +0 -45
  87. package/modules/routing.ts +0 -20
  88. package/nuxt.schema.ts +0 -374
  89. package/server/plugins/llms-redirect.ts +0 -60
  90. package/server/routes/raw/[...slug].md.get.ts +0 -74
  91. package/storybook/index.test.ts +0 -110
  92. package/storybook/index.ts +0 -362
  93. package/test/brand-palette.ts +0 -235
  94. package/test/no-brand-leakage.test.ts +0 -124
  95. package/tsconfig.json +0 -17
  96. package/utils/accent.ts +0 -80
  97. package/utils/content.ts +0 -193
  98. package/utils/git.ts +0 -114
  99. package/utils/meta.ts +0 -28
@@ -1,180 +0,0 @@
1
- <script setup lang="ts">
2
- import { kebabCase } from "scule";
3
- import type { ContentNavigationItem, Collections, PageCollectionItemBase } from "@nuxt/content";
4
-
5
- type DocsPageItem = PageCollectionItemBase & {
6
- links?: Array<{
7
- label: string;
8
- icon: string;
9
- to: string;
10
- target?: string;
11
- }>;
12
- seo?: { title?: string; description?: string };
13
- };
14
- import { findPageHeadline } from "@nuxt/content/utils";
15
-
16
- definePageMeta({
17
- layout: "default",
18
- });
19
-
20
- const route = useRoute();
21
- const { locale, isEnabled, t } = useDocusI18n();
22
- const appConfig = useAppConfig();
23
- const navigationMap = inject<Ref<Record<string, ContentNavigationItem[]>>>("navigation");
24
-
25
- const sectionSlug = computed(() => route.params.section as string);
26
- const section = computed(() => findDocsSectionBySlug(sectionSlug.value));
27
-
28
- if (!section.value) {
29
- throw createError({
30
- statusCode: 404,
31
- statusMessage: "Page not found",
32
- fatal: true,
33
- });
34
- }
35
-
36
- const collectionName = computed(() =>
37
- isEnabled.value ? `docs_${section.value!.key}_${locale.value}` : `docs_${section.value!.key}`,
38
- );
39
-
40
- const navigation = computed(() => navigationMap?.value?.[section.value!.key] ?? []);
41
-
42
- const [{ data: page }, { data: surround }] = await Promise.all([
43
- useAsyncData(
44
- kebabCase(route.path),
45
- () =>
46
- queryCollection(collectionName.value as keyof Collections)
47
- .path(route.path)
48
- .first() as Promise<DocsPageItem>,
49
- ),
50
- useAsyncData(`${kebabCase(route.path)}-surround`, () => {
51
- return queryCollectionItemSurroundings(collectionName.value as keyof Collections, route.path, {
52
- fields: ["description"],
53
- });
54
- }),
55
- ]);
56
-
57
- if (!page.value) {
58
- throw createError({
59
- statusCode: 404,
60
- statusMessage: "Page not found",
61
- fatal: true,
62
- });
63
- }
64
-
65
- // Add the page path to the prerender list
66
- addPrerenderPath(`/raw${route.path}.md`);
67
-
68
- const title = page.value.seo?.title || page.value.title;
69
- const description = page.value.seo?.description || page.value.description;
70
-
71
- useSeoMeta({
72
- title,
73
- ogTitle: title,
74
- description,
75
- ogDescription: description,
76
- });
77
-
78
- const headline = ref(findPageHeadline(navigation.value, page.value?.path));
79
- watch(
80
- () => navigation.value,
81
- () => {
82
- headline.value = findPageHeadline(navigation.value, page.value?.path) || headline.value;
83
- },
84
- );
85
-
86
- defineOgImage("DocsSatori", {
87
- headline: headline.value,
88
- title,
89
- description,
90
- });
91
-
92
- const github = computed(() => (appConfig.github ? appConfig.github : null));
93
-
94
- const editLink = computed(() => {
95
- if (!github.value) {
96
- return;
97
- }
98
-
99
- return [
100
- github.value.url,
101
- "edit",
102
- github.value.branch,
103
- github.value.rootDir,
104
- "content",
105
- `${page.value?.stem}.${page.value?.extension}`,
106
- ]
107
- .filter(Boolean)
108
- .join("/");
109
- });
110
- </script>
111
-
112
- <template>
113
- <UPage v-if="page">
114
- <UPageHeader
115
- :title="page.title"
116
- :description="page.description"
117
- :headline="headline"
118
- :ui="{
119
- wrapper: 'flex-row items-center flex-wrap justify-between',
120
- }"
121
- >
122
- <template #links>
123
- <UButton
124
- v-for="(link, index) in (page as DocsPageItem).links"
125
- :key="index"
126
- size="sm"
127
- v-bind="link"
128
- />
129
-
130
- <DocsPageHeaderLinks />
131
- </template>
132
- </UPageHeader>
133
-
134
- <UPageBody>
135
- <ContentRenderer v-if="page" :value="page" />
136
-
137
- <!-- `decorative` drops the `separator` role: the rule is decoration around
138
- the edit/report actions, and a separator with focusable descendants is
139
- a `nested-interactive` violation. -->
140
- <USeparator decorative>
141
- <div v-if="github" class="flex items-center gap-2 text-sm text-muted">
142
- <UButton
143
- variant="link"
144
- color="neutral"
145
- :to="editLink"
146
- target="_blank"
147
- icon="i-lucide-pen"
148
- :ui="{ leadingIcon: 'size-4' }"
149
- >
150
- {{ t("docs.edit") }}
151
- </UButton>
152
- <span>{{ t("common.or") }}</span>
153
- <UButton
154
- variant="link"
155
- color="neutral"
156
- :to="`${github.url}/issues/new/choose`"
157
- target="_blank"
158
- icon="i-lucide-alert-circle"
159
- :ui="{ leadingIcon: 'size-4' }"
160
- >
161
- {{ t("docs.report") }}
162
- </UButton>
163
- </div>
164
- </USeparator>
165
- <UContentSurround :surround="surround" />
166
- </UPageBody>
167
-
168
- <template v-if="page?.body?.toc?.links?.length" #right>
169
- <UContentToc
170
- highlight
171
- :title="appConfig.toc?.title || t('docs.toc')"
172
- :links="page.body?.toc?.links"
173
- >
174
- <template #bottom>
175
- <DocsAsideRightBottom />
176
- </template>
177
- </UContentToc>
178
- </template>
179
- </UPage>
180
- </template>
@@ -1,21 +0,0 @@
1
- export default defineNuxtPlugin(() => {
2
- // Consumers opt out via `app.config` (`i18nRedirect.enabled: false`).
3
- if (!useAppConfig().i18nRedirect?.enabled) {
4
- return;
5
- }
6
-
7
- const nuxtApp = useNuxtApp();
8
-
9
- const i18nConfig = nuxtApp.$config.public.i18n as { defaultLocale?: string } | undefined;
10
- if (!i18nConfig) {
11
- return;
12
- }
13
-
14
- addRouteMiddleware((to) => {
15
- if (to.path === "/") {
16
- const cookieLocale = useCookie("i18n_redirected").value || i18nConfig.defaultLocale || "en";
17
-
18
- return navigateTo(`/${cookieLocale}`);
19
- }
20
- });
21
- });
@@ -1,56 +0,0 @@
1
- import type { ConfigDefaults } from "posthog-js";
2
- import { defineNuxtPlugin } from "#app";
3
-
4
- export default defineNuxtPlugin(async () => {
5
- if (import.meta.dev) {
6
- return {};
7
- }
8
-
9
- // Consumers opt out via `app.config` (`analytics.enabled: false`).
10
- if (!useAppConfig().analytics?.enabled) {
11
- return {};
12
- }
13
-
14
- const runtimeConfig = useRuntimeConfig();
15
-
16
- // No key provisioned (`NUXT_PUBLIC_POSTHOG_KEY` unset) — no-op instead of
17
- // initialising a client that would fire requests at nothing.
18
- if (!runtimeConfig.public.posthog.key) {
19
- return {};
20
- }
21
-
22
- // `posthog-js` is an OPTIONAL peer dependency. A static top-level import
23
- // compiles to a module-scope `throw` for every consumer that omits it, and
24
- // that throw runs before any of the guards above — killing the entire client
25
- // bundle, not just analytics, so the site serves as a dead static document.
26
- // Importing here keeps the unresolvable specifier behind the opt-in, and the
27
- // catch keeps a missing or broken module a no-analytics degradation rather
28
- // than a dead app.
29
- let posthog;
30
- try {
31
- ({ default: posthog } = await import("posthog-js"));
32
- } catch (error) {
33
- console.warn(
34
- "[layer-docs] analytics is enabled and a PostHog key is set, but " +
35
- "`posthog-js` could not be loaded — continuing without analytics. " +
36
- "Install `posthog-js` to enable it.",
37
- error,
38
- );
39
- return {};
40
- }
41
-
42
- const posthogClient = posthog.init(runtimeConfig.public.posthog.key, {
43
- api_host: runtimeConfig.public.posthog.host,
44
- defaults: runtimeConfig.public.posthog.defaults as ConfigDefaults,
45
- person_profiles: "identified_only", // or 'always' to create profiles for anonymous users as well
46
- loaded: (posthog) => {
47
- if (import.meta.env.MODE === "development") posthog.debug();
48
- },
49
- });
50
-
51
- return {
52
- provide: {
53
- posthog: () => posthogClient,
54
- },
55
- };
56
- });
@@ -1,12 +0,0 @@
1
- export interface NonRouteCategoryMeta {
2
- title: string;
3
- icon?: string | false;
4
- defaultOpen?: boolean;
5
- order: number;
6
- }
7
-
8
- declare module "nuxt/schema" {
9
- interface AppConfig {
10
- nonRouteCategories?: Record<string, NonRouteCategoryMeta>;
11
- }
12
- }
@@ -1,22 +0,0 @@
1
- import type { ContentNavigationItem } from "@nuxt/content";
2
-
3
- /**
4
- * Flatten navigation items that are false-positive groups.
5
- * Nuxt Content's index detection regex matches filenames containing "index"
6
- * as a substring (e.g. "z-index"), incorrectly nesting them as groups.
7
- * This flattens groups with a single child sharing the same path.
8
- */
9
- export function flattenNavigation(items: ContentNavigationItem[]): ContentNavigationItem[] {
10
- return items.map((item) => {
11
- if (item.children?.length === 1 && item.children[0]?.path === item.path) {
12
- const { children: _, ...parent } = item;
13
- return { ...parent, ...item.children[0], children: undefined };
14
- }
15
-
16
- if (item.children?.length) {
17
- return { ...item, children: flattenNavigation(item.children) };
18
- }
19
-
20
- return item;
21
- });
22
- }
@@ -1,47 +0,0 @@
1
- import type { ContentNavigationItem } from "@nuxt/content";
2
- import type { NonRouteCategoryMeta } from "~/types/non-route-categories";
3
-
4
- /**
5
- * Rebuilds the sidebar grouping for non-route categories.
6
- *
7
- * Pages under a `route: false` folder are flattened in the URL, so the native
8
- * navigation tree lists them as direct children with the category level gone.
9
- * This re-nests each such page under a synthetic group node (rendered as a
10
- * non-navigating accordion header), matching pages to categories by `stem`.
11
- */
12
- export function foldNonRouteCategories(
13
- items: ContentNavigationItem[],
14
- categories: Record<string, NonRouteCategoryMeta>,
15
- ): ContentNavigationItem[] {
16
- return items.map((node) => {
17
- if (!node.children?.length) {
18
- return node;
19
- }
20
- const passthrough: ContentNavigationItem[] = [];
21
- const groups = new Map<
22
- string,
23
- { meta: NonRouteCategoryMeta; children: ContentNavigationItem[] }
24
- >();
25
- for (const child of node.children) {
26
- const dir = child.stem ? child.stem.split("/").slice(0, -1).join("/") : "";
27
- const meta = categories[dir];
28
- if (meta && child.page !== false) {
29
- const group = groups.get(dir) ?? { meta, children: [] };
30
- group.children.push(child);
31
- groups.set(dir, group);
32
- } else {
33
- passthrough.push(foldNonRouteCategories([child], categories)[0]!);
34
- }
35
- }
36
- const groupNodes = [...groups.values()]
37
- .sort((a, b) => a.meta.order - b.meta.order || a.meta.title.localeCompare(b.meta.title))
38
- .map(({ meta, children }) => ({
39
- title: meta.title,
40
- ...(meta.icon ? { icon: meta.icon } : {}),
41
- ...(meta.defaultOpen !== undefined ? { defaultOpen: meta.defaultOpen } : {}),
42
- page: false as const,
43
- children,
44
- })) as ContentNavigationItem[];
45
- return { ...node, children: [...passthrough, ...groupNodes] };
46
- });
47
- }
@@ -1,9 +0,0 @@
1
- export const addPrerenderPath = (path: string) => {
2
- const event = useRequestEvent();
3
- if (event) {
4
- event.node.res.setHeader(
5
- "x-nitro-prerender",
6
- [event.node.res.getHeader("x-nitro-prerender"), path].filter(Boolean).join(","),
7
- );
8
- }
9
- };
@@ -1,98 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- buildStorybookEmbedUrl,
4
- resolveStorybookBaseUrl,
5
- STORYBOOK_EMBED_DEFAULT_HEIGHTS,
6
- storybookEmbedCssHeight,
7
- } from "./storybookEmbed";
8
-
9
- const TEMPLATE = "https://{framework}.storybook.example.com";
10
- const STORY = "components-actions-button--default";
11
-
12
- describe("resolveStorybookBaseUrl", () => {
13
- it("substitutes every occurrence of the framework placeholder", () => {
14
- expect(resolveStorybookBaseUrl("https://{framework}.example.com/{framework}", "vue")).toBe(
15
- "https://vue.example.com/vue",
16
- );
17
- });
18
-
19
- it("passes a template without a placeholder through, for a single Storybook", () => {
20
- expect(resolveStorybookBaseUrl("https://storybook.example.com", "vue")).toBe(
21
- "https://storybook.example.com",
22
- );
23
- });
24
-
25
- it("trims trailing slashes so the built URL never doubles them", () => {
26
- expect(resolveStorybookBaseUrl("https://storybook.example.com//")).toBe(
27
- "https://storybook.example.com",
28
- );
29
- });
30
- });
31
-
32
- describe("buildStorybookEmbedUrl", () => {
33
- it("addresses the preview surface by default and marks itself as a direct embed", () => {
34
- expect(buildStorybookEmbedUrl({ template: TEMPLATE, story: STORY, framework: "vue" })).toBe(
35
- `https://vue.storybook.example.com/iframe.html?id=${STORY}&viewMode=story&docsEmbed=1`,
36
- );
37
- });
38
-
39
- // `full` and `panel` reproduce the manager URLs the migrating docs site
40
- // already ships; a changed parameter here is a changed rendering there.
41
- it("addresses the manager in full mode", () => {
42
- expect(
43
- buildStorybookEmbedUrl({ template: TEMPLATE, story: STORY, framework: "vue", mode: "full" }),
44
- ).toBe(
45
- `https://vue.storybook.example.com/?path=/story/${STORY}&full=1&shortcuts=false&singleStory=true`,
46
- );
47
- });
48
-
49
- it("addresses the manager with its addon panel in panel mode", () => {
50
- expect(
51
- buildStorybookEmbedUrl({ template: TEMPLATE, story: STORY, framework: "vue", mode: "panel" }),
52
- ).toBe(
53
- `https://vue.storybook.example.com/?path=/story/${STORY}&shortcuts=false&singleStory=true`,
54
- );
55
- });
56
-
57
- it("only marks the preview surface — a manager embed is relayed, not direct", () => {
58
- for (const mode of ["full", "panel"] as const) {
59
- expect(buildStorybookEmbedUrl({ template: TEMPLATE, story: STORY, mode })).not.toContain(
60
- "docsEmbed",
61
- );
62
- }
63
- });
64
-
65
- it("encodes the story id rather than splicing it into the query raw", () => {
66
- expect(
67
- buildStorybookEmbedUrl({ template: "https://sb.example.com", story: "a&b=c" }),
68
- ).toContain("id=a%26b%3Dc&");
69
- });
70
-
71
- it("degrades to a same-origin URL when no consumer configured a host", () => {
72
- expect(buildStorybookEmbedUrl({ template: "", story: STORY })).toBe(
73
- `/iframe.html?id=${STORY}&viewMode=story&docsEmbed=1`,
74
- );
75
- });
76
- });
77
-
78
- describe("STORYBOOK_EMBED_DEFAULT_HEIGHTS", () => {
79
- // Reserved before the story reports anything — the space that keeps the embed
80
- // from shifting the page. Panel carries the addon panel on top of the story.
81
- it("reserves more room for the panel surface than the bare story", () => {
82
- expect(STORYBOOK_EMBED_DEFAULT_HEIGHTS).toEqual({ preview: 320, full: 320, panel: 600 });
83
- });
84
- });
85
-
86
- describe("storybookEmbedCssHeight", () => {
87
- it("reads a bare number as pixels, in either type markdown may deliver", () => {
88
- expect(storybookEmbedCssHeight(420)).toBe("420px");
89
- expect(storybookEmbedCssHeight("420")).toBe("420px");
90
- expect(storybookEmbedCssHeight(" 420 ")).toBe("420px");
91
- });
92
-
93
- it("passes a CSS length through untouched", () => {
94
- expect(storybookEmbedCssHeight("30rem")).toBe("30rem");
95
- expect(storybookEmbedCssHeight("420px")).toBe("420px");
96
- expect(storybookEmbedCssHeight("min(80vh, 600px)")).toBe("min(80vh, 600px)");
97
- });
98
- });
@@ -1,93 +0,0 @@
1
- import { DOCS_EMBED_PARAM } from "../../storybook";
2
-
3
- /**
4
- * Which Storybook surface a `StorybookEmbed` addresses.
5
- *
6
- * The three values are not styling choices — each is a different URL against a
7
- * different Storybook surface, and the height signal reaches the page by a
8
- * different route in each. See `@uxfront/layer-docs/storybook`.
9
- *
10
- * - `preview` — `iframe.html`, the story on its own with no Storybook UI.
11
- * - `full` — the manager in full-canvas mode: story plus toolbar, no panel.
12
- * - `panel` — the manager with its addon panel, for stories documented through
13
- * controls or actions.
14
- */
15
- export type StorybookEmbedMode = "preview" | "full" | "panel";
16
-
17
- /**
18
- * Height an embed occupies before Storybook reports its own, and the height it
19
- * keeps if no bridge is installed on the Storybook side.
20
- *
21
- * The panel default is taller because the addon panel is a fixed cost on top of
22
- * the story. Reserving the space up front is what keeps the embed from shifting
23
- * the page when the real height arrives.
24
- */
25
- export const STORYBOOK_EMBED_DEFAULT_HEIGHTS: Record<StorybookEmbedMode, number> = {
26
- preview: 320,
27
- full: 320,
28
- panel: 600,
29
- };
30
-
31
- /**
32
- * Resolve a Storybook base URL from a template.
33
- *
34
- * A `{framework}` placeholder addresses a per-framework Storybook deployment
35
- * (`https://{framework}.storybook.example.com`); a template without one is used
36
- * as-is, so a single-Storybook site needs no placeholder.
37
- */
38
- export function resolveStorybookBaseUrl(template: string, framework?: string): string {
39
- return template.replaceAll("{framework}", framework ?? "").replace(/\/+$/, "");
40
- }
41
-
42
- export interface StorybookEmbedUrlOptions {
43
- /** Base URL template, optionally containing `{framework}`. */
44
- template: string;
45
- /** Storybook story id, e.g. `components-actions-button--default`. */
46
- story: string;
47
- /** Framework substituted into the template's `{framework}` placeholder. */
48
- framework?: string;
49
- /** Storybook surface to address. Defaults to `preview`. */
50
- mode?: StorybookEmbedMode;
51
- }
52
-
53
- /**
54
- * Build the `src` for an embedded story.
55
- *
56
- * `preview` is marked with `docsEmbed=1`: embedded that way the docs page is
57
- * the preview's direct parent, so the preview bridge must post the final height
58
- * itself rather than the intermediate one it sends to a manager.
59
- *
60
- * An empty template yields a root-relative URL rather than a broken absolute
61
- * one, which is what an unconfigured consumer gets — a same-origin 404 it can
62
- * see, not a silent request to nowhere.
63
- */
64
- export function buildStorybookEmbedUrl(options: StorybookEmbedUrlOptions): string {
65
- const { template, story, framework, mode = "preview" } = options;
66
-
67
- const base = resolveStorybookBaseUrl(template, framework);
68
- const id = encodeURIComponent(story);
69
-
70
- if (mode === "preview") {
71
- return `${base}/iframe.html?id=${id}&viewMode=story&${DOCS_EMBED_PARAM}=1`;
72
- }
73
-
74
- // `singleStory` hides the sidebar, `shortcuts` stops the embed from
75
- // swallowing keystrokes meant for the page around it, and `full` drops the
76
- // addon panel.
77
- const full = mode === "full" ? "&full=1" : "";
78
-
79
- return `${base}/?path=/story/${id}${full}&shortcuts=false&singleStory=true`;
80
- }
81
-
82
- /**
83
- * Coerce an author-supplied height to a CSS length. A bare number — in either
84
- * type, since markdown attributes arrive as strings — means pixels; anything
85
- * else is already a CSS length and is passed through.
86
- */
87
- export function storybookEmbedCssHeight(height: number | string): string {
88
- if (typeof height === "number") return `${height}px`;
89
-
90
- const trimmed = height.trim();
91
-
92
- return /^\d+(\.\d+)?$/.test(trimmed) ? `${trimmed}px` : trimmed;
93
- }
@@ -1,24 +0,0 @@
1
- {
2
- "common": {
3
- "or": "أو",
4
- "error": {
5
- "title": "الصفحة غير موجودة",
6
- "description": "نأسف، لكن الصفحة التي تبحث عنها غير موجودة."
7
- }
8
- },
9
- "docs": {
10
- "copy": {
11
- "page": "نسخ الصفحة",
12
- "options": "خيارات نسخ إضافية",
13
- "link": "نسخ صفحة Markdown",
14
- "view": "عرض كـ Markdown",
15
- "gpt": "فتح في ChatGPT",
16
- "claude": "فتح في Claude"
17
- },
18
- "framework": "إطار العمل",
19
- "links": "المجتمع",
20
- "toc": "في هذه الصفحة",
21
- "report": "الإبلاغ عن مشكلة",
22
- "edit": "تحرير هذه الصفحة"
23
- }
24
- }
@@ -1,24 +0,0 @@
1
- {
2
- "common": {
3
- "or": "або",
4
- "error": {
5
- "title": "Старонка не знойдзена",
6
- "description": "Прабачце, але старонка, якую вы шукаеце, не знойдзена."
7
- }
8
- },
9
- "docs": {
10
- "copy": {
11
- "page": "Скапіраваць старонку",
12
- "options": "Дадатковыя параметры капіравання",
13
- "link": "Скапіраваць Markdown-старонку",
14
- "view": "Праглядзець як Markdown",
15
- "gpt": "Адкрыць у ChatGPT",
16
- "claude": "Адкрыць у Claude"
17
- },
18
- "framework": "Фреймворк",
19
- "links": "Супольнасць",
20
- "toc": "На гэтай старонцы",
21
- "report": "Паведаміць пра праблему",
22
- "edit": "Рэдагаваць гэтую старонку"
23
- }
24
- }
@@ -1,24 +0,0 @@
1
- {
2
- "common": {
3
- "or": "অথবা",
4
- "error": {
5
- "title": "পেজ খুঁজে পাওয়া যায়নি",
6
- "description": "দুঃখিত, আপনি যে পেজটি খুঁজছেন সেটি পাওয়া যায়নি।"
7
- }
8
- },
9
- "docs": {
10
- "copy": {
11
- "page": "পেজ কপি করুন",
12
- "options": "আরও কপি অপশন",
13
- "link": "Markdown পেজ কপি করুন",
14
- "view": "Markdown হিসেবে দেখুন",
15
- "gpt": "ChatGPT এ খুলুন",
16
- "claude": "Claude এ খুলুন"
17
- },
18
- "framework": "ফ্রেমওয়ার্ক",
19
- "links": "কমিউনিটি",
20
- "toc": "এই পেজে",
21
- "report": "সমস্যা রিপোর্ট করুন",
22
- "edit": "এই পেজ সম্পাদনা করুন"
23
- }
24
- }
@@ -1,24 +0,0 @@
1
- {
2
- "common": {
3
- "or": "o",
4
- "error": {
5
- "title": "Pàgina no trobada",
6
- "description": "Ho sentim, però la pàgina que cerques no existeix."
7
- }
8
- },
9
- "docs": {
10
- "copy": {
11
- "page": "Copiar pàgina",
12
- "options": "Més opcions de còpia",
13
- "link": "Copiar pàgina Markdown",
14
- "view": "Veure com a Markdown",
15
- "gpt": "Obrir a ChatGPT",
16
- "claude": "Obrir a Claude"
17
- },
18
- "framework": "Framework",
19
- "links": "Comunitat",
20
- "toc": "En aquesta pàgina",
21
- "report": "Informar d'un problema",
22
- "edit": "Editar aquesta pàgina"
23
- }
24
- }
@@ -1,24 +0,0 @@
1
- {
2
- "common": {
3
- "or": "یان",
4
- "error": {
5
- "title": "پەڕە نەدۆزرایەوە",
6
- "description": "بەداخەوە، ئەم پەڕەیە نەدۆزرایەوە."
7
- }
8
- },
9
- "docs": {
10
- "copy": {
11
- "page": "کۆپیکردنی پەڕە",
12
- "options": "بژاردەی زیاتری لەبەرگرتنەوە",
13
- "link": "کۆپیکردنی پەڕەی Markdown",
14
- "view": "بینین وەک Markdown",
15
- "gpt": "کردنەوە لە ChatGPT",
16
- "claude": "کردنەوە لە Claude"
17
- },
18
- "framework": "چوارچێوە",
19
- "links": "کۆمەڵگا",
20
- "toc": "لەم پەڕەدا",
21
- "report": "ڕاپۆرتکردنی کێشە",
22
- "edit": "دەستکاریکردنی ئەم پەڕەیە"
23
- }
24
- }