Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions apps/docsite/src/components/ThemeInstallBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.

'use client';

import {VStack} from '@astryxdesign/core/Layout';
import {Heading, Text} from '@astryxdesign/core/Text';
import {Link} from '@astryxdesign/core/Link';
import {Section} from '@astryxdesign/core/Section';
import {CodeExampleBlock} from './CodeExampleBlock';
import {packages} from '../generated/packageRegistry';

// Strip "Theme: " prefix and " Theme" suffix from the registered displayName
// so headings read as the brand wordmark ("Y2K") rather than "Theme: Y2K Theme".
function brandLabel(displayName: string): string {
return displayName.replace(/^Theme:\s*/, '').replace(/\s*Theme$/, '');
}

interface ThemeInstallBlockProps {
/** Full npm package name, e.g. `@astryxdesign/theme-y2k`. */
packageName: string;
}

/**
* "Use this theme" snippet shown below the themed preview on /themes —
* `npm install` + the matching import so visitors can copy both without
* leaving the page.
*/
export function ThemeInstallBlock({packageName}: ThemeInstallBlockProps) {
const pkg = packages.find(p => p.name === packageName);
const themeLabel = pkg
? brandLabel(pkg.displayName) || pkg.displayName
: '';
const slug = packageName.replace(/^@astryxdesign\/theme-/, '');
const importName = `${slug}Theme`;
const installCode = `npm install ${packageName}`;
const importCode = `import {Theme} from '@astryxdesign/core';
import {${importName}} from '${packageName}';

<Theme theme={${importName}}>{/* your app */}</Theme>`;

return (
<Section>
<VStack gap={4}>
<VStack gap={1}>
<Heading level={2} type="display-3">
Use the {themeLabel} theme
</Heading>
<Text type="body" color="secondary">
Install the package, then wrap your app in {'<Theme>'}. See the{' '}
<Link
type="body"
color="secondary"
href="/docs/theme"
hasUnderline>
Theme System guide
</Link>{' '}
for SSR, light/dark mode, and customization.
</Text>
</VStack>
<CodeExampleBlock
code={installCode}
language="bash"
hasCopyButton
isWrapped
width="100%"
/>
<CodeExampleBlock
code={importCode}
language="tsx"
hasCopyButton
isWrapped
width="100%"
/>
</VStack>
</Section>
);
}
25 changes: 19 additions & 6 deletions apps/docsite/src/components/ThemePackagePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {useMediaQuery} from '@astryxdesign/core/hooks';
import {ThemeShowcaseStore} from '../../../../packages/cli/templates/pages/theme-showcase/page';
import {getThemeShowcaseContent} from './themeShowcaseContent';
import {buildPlaygroundHref} from './playgroundLink';
import {ThemeInstallBlock} from './ThemeInstallBlock';
import {packages} from '../generated/packageRegistry';
import {layout} from '../layout.stylex';
import {themeObjects} from '../generated/themeRegistry';
Expand Down Expand Up @@ -442,14 +443,17 @@ const styles = stylex.create({
showcaseCard: {
overflow: 'hidden',
},
// Caps the showcase at the site's wide-content width and centers it
// so it doesn't run edge-to-edge on very wide screens. overflow:hidden
// clips template content that exceeds the width on mobile (e.g.
// inventory filter rows, tables).
showcaseBlock: {
// Width cap + centering applied to the right column's main content
// blocks (showcase, install snippet, etc.) so they don't run
// edge-to-edge on very wide screens.
contentBlock: {
width: '100%',
maxWidth: layout.contentMaxWidth,
marginInline: 'auto',
},
// Showcase-only: clips template content that overflows on mobile
// (inventory rows, tables) to the card's rounded corners.
showcaseClip: {
overflow: 'hidden',
borderRadius: 'var(--radius-container)',
},
Expand Down Expand Up @@ -971,7 +975,7 @@ export function ThemePackagePage({packageName, theme}: ThemePackagePageProps) {
own backgrounds (top nav, sections) to the card's radius.
LinkProvider keeps demo href="#" clicks from scrolling the
page to the top (see PreviewAnchor). */}
<div {...stylex.props(styles.showcaseBlock)}>
<div {...stylex.props(styles.contentBlock, styles.showcaseClip)}>
<Card padding={0} xstyle={styles.showcaseCard}>
<Theme theme={selectedTheme} mode={mode}>
<LinkProvider component={PreviewAnchor}>
Expand All @@ -986,6 +990,15 @@ export function ThemePackagePage({packageName, theme}: ThemePackagePageProps) {
</Theme>
</Card>
</div>

{/* Install block — inline "Use this theme" affordance with the
two-step install + import snippet. Sits BELOW the showcase
so the themed preview leads (it's the reason the visitor came
here); the install snippet acts as the natural next step
after they've decided they like what they see. */}
<div {...stylex.props(styles.contentBlock)}>
<ThemeInstallBlock packageName={selectedPkgName} />
</div>
</div>
</div>
);
Expand Down
Loading