diff --git a/docs/6.x/docs/components/FAB/FAB.mdx b/docs/6.x/docs/components/FAB/FAB.mdx
index d672df9781..7bd4d116d8 100644
--- a/docs/6.x/docs/components/FAB/FAB.mdx
+++ b/docs/6.x/docs/components/FAB/FAB.mdx
@@ -7,14 +7,16 @@ import ExtendsLink from '@docs/components/ExtendsLink.tsx';
import ThemeColorsTable from '@docs/components/ThemeColorsTable.tsx';
import ScreenshotTabs from '@docs/components/ScreenshotTabs.tsx';
import ExtendedExample from '@docs/components/ExtendedExample.tsx';
+import { FABVariantsExample, FABSizesExample } from '@docs/components/FABExample.tsx';
A floating action button represents the primary action on a screen.
+
-
-
+
+
## Usage
```js
diff --git a/docs/6.x/docs/components/FAB/FABExtended.mdx b/docs/6.x/docs/components/FAB/FABExtended.mdx
index 98969dc964..5ebf7298dc 100644
--- a/docs/6.x/docs/components/FAB/FABExtended.mdx
+++ b/docs/6.x/docs/components/FAB/FABExtended.mdx
@@ -7,6 +7,7 @@ import ExtendsLink from '@docs/components/ExtendsLink.tsx';
import ThemeColorsTable from '@docs/components/ThemeColorsTable.tsx';
import ScreenshotTabs from '@docs/components/ScreenshotTabs.tsx';
import ExtendedExample from '@docs/components/ExtendedExample.tsx';
+import { FABExtendedExample } from '@docs/components/FABExample.tsx';
An extended floating action button represents the primary action on a screen
and shows a label next to the icon. Animates between expanded (icon + label)
@@ -16,6 +17,8 @@ and collapsed (icon only) states.
+
+
## Usage
```js
import * as React from 'react';
diff --git a/docs/6.x/docs/components/Switch/Switch.mdx b/docs/6.x/docs/components/Switch/Switch.mdx
index 41df316693..63dc75eb45 100644
--- a/docs/6.x/docs/components/Switch/Switch.mdx
+++ b/docs/6.x/docs/components/Switch/Switch.mdx
@@ -7,14 +7,17 @@ import ExtendsLink from '@docs/components/ExtendsLink.tsx';
import ThemeColorsTable from '@docs/components/ThemeColorsTable.tsx';
import ScreenshotTabs from '@docs/components/ScreenshotTabs.tsx';
import ExtendedExample from '@docs/components/ExtendedExample.tsx';
+import { SwitchStatesExample, SwitchDisabledExample, SwitchIconsExample } from '@docs/components/SwitchExample.tsx';
Material 3 toggle between two mutually exclusive states (on / off).
+
-
-
+
+
+
## Usage
```js
diff --git a/docs/component-docs-plugin/generatePageMDX.ts b/docs/component-docs-plugin/generatePageMDX.ts
index 274f3622d8..9b7eb96e0d 100644
--- a/docs/component-docs-plugin/generatePageMDX.ts
+++ b/docs/component-docs-plugin/generatePageMDX.ts
@@ -96,6 +96,27 @@ function generateThemeColors(
`;
}
+type LiveExampleData = {
+ module: string;
+ exports: string[];
+};
+
+function generateLiveExamplesImport(liveExample: LiveExampleData | undefined) {
+ if (!liveExample) {
+ return '';
+ }
+
+ return `\nimport { ${liveExample.exports.join(', ')} } from '${liveExample.module}';`;
+}
+
+function generateLiveExamples(liveExample: LiveExampleData | undefined) {
+ if (!liveExample) {
+ return '';
+ }
+
+ return `\n\n${liveExample.exports.map((name) => `<${name} />`).join('\n')}`;
+}
+
function generateScreenshots(screenshotData: string | undefined) {
if (!screenshotData) {
return ``;
@@ -224,8 +245,12 @@ export default function generatePageMDX(doc: ComponentDoc, link: string) {
const summary = summaryMatch ? summaryMatch[1] : '';
const usage = description.replace(summary, '');
+ const liveExample = customFields.liveExamples[doc.title];
const themeColorsData = JSON.stringify(customFields.themeColors[doc.title]);
- const screenshotData = JSON.stringify(customFields.screenshots[doc.title]);
+ // Live examples supersede screenshots, so a component never shows both.
+ const screenshotData = liveExample
+ ? undefined
+ : JSON.stringify(customFields.screenshots[doc.title]);
const extendedExamplesData = JSON.stringify(
customFields.extendedExamples[doc.title]
);
@@ -241,11 +266,11 @@ import PropTable from '@docs/components/PropTable.tsx';
import ExtendsLink from '@docs/components/ExtendsLink.tsx';
import ThemeColorsTable from '@docs/components/ThemeColorsTable.tsx';
import ScreenshotTabs from '@docs/components/ScreenshotTabs.tsx';
-import ExtendedExample from '@docs/components/ExtendedExample.tsx';
+import ExtendedExample from '@docs/components/ExtendedExample.tsx';${generateLiveExamplesImport(liveExample)}
${summary}
-${generateScreenshots(screenshotData)}
+${generateScreenshots(screenshotData)}${generateLiveExamples(liveExample)}
${generateExtendedExamples(usage, extendedExamplesData)}
diff --git a/docs/component-docs.config.ts b/docs/component-docs.config.ts
index bad25d4eec..80a1d5614b 100644
--- a/docs/component-docs.config.ts
+++ b/docs/component-docs.config.ts
@@ -2,6 +2,7 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { extendedExamples } from './src/data/extendedExamples.ts';
+import { liveExamples, type LiveExample } from './src/data/liveExamples.ts';
import { screenshots } from './src/data/screenshots.ts';
import { themeColors } from './src/data/themeColors.ts';
@@ -19,6 +20,7 @@ type ComponentDocsConfig = {
themeColors: Record;
screenshots: Record;
extendedExamples: Record;
+ liveExamples: Record;
};
};
@@ -163,6 +165,7 @@ const componentDocsConfig: ComponentDocsConfig = {
themeColors,
screenshots,
extendedExamples,
+ liveExamples,
},
};
diff --git a/docs/src/components/FABExample.tsx b/docs/src/components/FABExample.tsx
new file mode 100644
index 0000000000..ee53cc9544
--- /dev/null
+++ b/docs/src/components/FABExample.tsx
@@ -0,0 +1,72 @@
+import * as React from 'react';
+
+import { FAB } from 'react-native-paper';
+
+import InteractiveExample, { ExampleRow, Labelled } from './InteractiveExample';
+
+/**
+ * Every role-color preset accepted by the `variant` prop, kept in the same
+ * order as the `Variant` union in `src/components/FAB/tokens.ts`.
+ */
+const VARIANTS = [
+ 'primary',
+ 'secondary',
+ 'tertiary',
+ 'tonalPrimary',
+ 'tonalSecondary',
+ 'tonalTertiary',
+] as const;
+
+/**
+ * Every spec size accepted by the `size` prop.
+ */
+const SIZES = ['default', 'medium', 'large'] as const;
+
+export const FABVariantsExample = () => (
+
+
+ {VARIANTS.map((variant) => (
+
+ {}}
+ aria-label={`${variant} floating action button`}
+ />
+
+ ))}
+
+
+);
+
+export const FABSizesExample = () => (
+
+
+ {SIZES.map((size) => (
+
+ {}}
+ aria-label={`${size} floating action button`}
+ />
+
+ ))}
+
+
+);
+
+export const FABExtendedExample = () => {
+ const [expanded, setExpanded] = React.useState(true);
+
+ return (
+
+ setExpanded((value) => !value)}
+ />
+
+ );
+};
diff --git a/docs/src/components/InteractiveExample.tsx b/docs/src/components/InteractiveExample.tsx
new file mode 100644
index 0000000000..9a1b1fa80b
--- /dev/null
+++ b/docs/src/components/InteractiveExample.tsx
@@ -0,0 +1,92 @@
+import * as React from 'react';
+import { StyleSheet, View } from 'react-native';
+
+import { BrowserOnly } from '@rspress/core/runtime';
+import { DarkTheme, LightTheme, Provider, Text } from 'react-native-paper';
+
+import { useColorMode } from './theme-common';
+
+const styles = StyleSheet.create({
+ content: {
+ alignItems: 'flex-start',
+ },
+ row: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ alignItems: 'center',
+ gap: 24,
+ },
+ item: {
+ alignItems: 'center',
+ gap: 8,
+ },
+ itemLabel: {
+ fontSize: 12,
+ opacity: 0.7,
+ },
+});
+
+/**
+ * Row of demo variations, wrapping on narrow viewports.
+ */
+export const ExampleRow = ({ children }: React.PropsWithChildren) => (
+ {children}
+);
+
+/**
+ * A single demo variation captioned with the prop value it illustrates.
+ */
+export const Labelled = ({
+ label,
+ children,
+}: React.PropsWithChildren<{ label: string }>) => (
+
+ {children}
+ {label}
+
+);
+
+type InteractiveExampleProps = React.PropsWithChildren<{
+ /**
+ * Short caption rendered above the live preview, describing what the
+ * example demonstrates.
+ */
+ title?: string;
+}>;
+
+/**
+ * Shared shell for live component demos embedded in the docs.
+ *
+ * Paper components depend on browser APIs through `react-native-web`, so the
+ * tree is only mounted on the client and a same-sized placeholder is rendered
+ * during SSR to avoid layout shift. The Paper theme is kept in sync with the
+ * active docs color mode so demos match the surrounding page.
+ */
+const Frame = ({ title, children }: InteractiveExampleProps) => {
+ const isDarkTheme = useColorMode().colorMode === 'dark';
+
+ return (
+
+
+ {title ? (
+
+ {title}
+
+ ) : null}
+ {children}
+
+
+ );
+};
+
+const Placeholder = () => (
+
+);
+
+const InteractiveExample = (props: InteractiveExampleProps) => (
+ }>
+ {() => }
+
+);
+
+export default InteractiveExample;
diff --git a/docs/src/components/SwitchExample.tsx b/docs/src/components/SwitchExample.tsx
new file mode 100644
index 0000000000..181329af07
--- /dev/null
+++ b/docs/src/components/SwitchExample.tsx
@@ -0,0 +1,66 @@
+import * as React from 'react';
+
+import { Switch } from 'react-native-paper';
+
+import InteractiveExample, { ExampleRow, Labelled } from './InteractiveExample';
+
+export const SwitchStatesExample = () => {
+ const [on, setOn] = React.useState(true);
+ const [off, setOff] = React.useState(false);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const SwitchDisabledExample = () => (
+
+
+
+
+
+
+
+
+
+
+);
+
+export const SwitchIconsExample = () => {
+ const [on, setOn] = React.useState(true);
+ const [off, setOff] = React.useState(false);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css
index 03288b6bcd..e0fbac05f6 100644
--- a/docs/src/css/custom.css
+++ b/docs/src/css/custom.css
@@ -1286,3 +1286,26 @@ html:not(.dark) .paper-version-selector-menu {
padding: 18px;
}
}
+
+.paper-interactive-example {
+ margin: 16px 0 24px;
+ padding: 24px;
+ border: 1px solid var(--rp-c-divider, rgba(148, 163, 184, 0.24));
+ border-radius: 16px;
+ background: var(--rp-c-bg-soft, rgba(148, 163, 184, 0.06));
+ overflow-x: auto;
+}
+
+.paper-interactive-example__title {
+ margin-bottom: 16px;
+ color: var(--rp-c-text-2);
+ font-size: 13px;
+ font-weight: 600;
+}
+
+/* Nominal reserve so the page does not jump to zero height during SSR. Demos
+ vary in height, so this deliberately approximates a single row rather than
+ claiming to match any particular example. */
+.paper-interactive-example--loading {
+ min-height: 96px;
+}
diff --git a/docs/src/data/liveExamples.ts b/docs/src/data/liveExamples.ts
new file mode 100644
index 0000000000..7e3ca72804
--- /dev/null
+++ b/docs/src/data/liveExamples.ts
@@ -0,0 +1,35 @@
+/**
+ * Components whose docs page renders live, interactive examples instead of
+ * static screenshots.
+ *
+ * Each entry maps a component title (as it appears in the generated docs) to
+ * the named exports of a module under `@docs/components` that should be
+ * rendered right below the page summary. Adding an entry here automatically
+ * removes the component's screenshot tabs, so a component is documented either
+ * with screenshots or with live examples — never both.
+ */
+export type LiveExample = {
+ /** Module specifier the examples are imported from. */
+ module: string;
+ /** Named exports rendered, in order, under the page summary. */
+ exports: string[];
+};
+
+export const liveExamples: Record = {
+ FAB: {
+ module: '@docs/components/FABExample.tsx',
+ exports: ['FABVariantsExample', 'FABSizesExample'],
+ },
+ Extended: {
+ module: '@docs/components/FABExample.tsx',
+ exports: ['FABExtendedExample'],
+ },
+ Switch: {
+ module: '@docs/components/SwitchExample.tsx',
+ exports: [
+ 'SwitchStatesExample',
+ 'SwitchDisabledExample',
+ 'SwitchIconsExample',
+ ],
+ },
+};
diff --git a/docs/src/data/screenshots.ts b/docs/src/data/screenshots.ts
index 92bf8f2788..2be980043c 100644
--- a/docs/src/data/screenshots.ts
+++ b/docs/src/data/screenshots.ts
@@ -71,12 +71,6 @@ export const screenshots = {
'Drawer.CollapsedItem': 'screenshots/drawer-collapsed.png',
'Drawer.Item': 'screenshots/drawer-item.png',
'Drawer.Section': 'screenshots/drawer-section.png',
- FAB: {
- 'all variants': 'screenshots/fab-1.png',
- 'all sizes': 'screenshots/fab-2.png',
- 'all modes': 'screenshots/fab-4.png',
- 'with label': 'screenshots/fab-3.png',
- },
AnimatedFAB: 'screenshots/animated-fab.gif',
'FAB.Group': 'screenshots/fab-group.gif',
Icon: 'screenshots/icon.png',
@@ -138,12 +132,6 @@ export const screenshots = {
elevated: 'screenshots/surface-elevated-full-width.png',
flat: 'screenshots/surface-flat-full-width.png',
},
- Switch: {
- 'Android (enabled)': 'screenshots/switch-enabled.android.png',
- 'Android (disabled)': 'screenshots/switch-disabled.android.png',
- 'iOS (enabled)': 'screenshots/switch-enabled.ios.png',
- 'iOS (disabled)': 'screenshots/switch-disabled.ios.png',
- },
Text: 'screenshots/typography.png',
TextInput: {
filled: 'screenshots/text-input-filled.png',
diff --git a/src/components/Switch/Switch.tsx b/src/components/Switch/Switch.tsx
index b0ca4b4bd7..24cefae80b 100644
--- a/src/components/Switch/Switch.tsx
+++ b/src/components/Switch/Switch.tsx
@@ -535,6 +535,8 @@ const styles = StyleSheet.create({
left: 0,
right: 0,
bottom: 0,
+ alignItems: 'center',
+ justifyContent: 'center',
},
});
diff --git a/src/components/__tests__/__snapshots__/Switch.test.tsx.snap b/src/components/__tests__/__snapshots__/Switch.test.tsx.snap
index 3589a26ee9..3acd1e2505 100644
--- a/src/components/__tests__/__snapshots__/Switch.test.tsx.snap
+++ b/src/components/__tests__/__snapshots__/Switch.test.tsx.snap
@@ -946,7 +946,9 @@ exports[`Switch render renders with checked icon 1`] = `
style={
[
{
+ "alignItems": "center",
"bottom": 0,
+ "justifyContent": "center",
"left": 0,
"position": "absolute",
"right": 0,
@@ -1190,7 +1192,9 @@ exports[`Switch render renders with per-state icons 1`] = `
style={
[
{
+ "alignItems": "center",
"bottom": 0,
+ "justifyContent": "center",
"left": 0,
"position": "absolute",
"right": 0,