From 150beac66d4178b87d5dd6714d54b6d6d566c293 Mon Sep 17 00:00:00 2001 From: Hristo Totov Date: Mon, 24 Aug 2026 14:57:22 +0300 Subject: [PATCH] docs: replace FAB and Switch screenshots with live interactive examples --- docs/6.x/docs/components/FAB/FAB.mdx | 6 +- docs/6.x/docs/components/FAB/FABExtended.mdx | 3 + docs/6.x/docs/components/Switch/Switch.mdx | 7 +- docs/component-docs-plugin/generatePageMDX.ts | 31 ++++++- docs/component-docs.config.ts | 3 + docs/src/components/FABExample.tsx | 72 +++++++++++++++ docs/src/components/InteractiveExample.tsx | 92 +++++++++++++++++++ docs/src/components/SwitchExample.tsx | 66 +++++++++++++ docs/src/css/custom.css | 23 +++++ docs/src/data/liveExamples.ts | 35 +++++++ docs/src/data/screenshots.ts | 12 --- src/components/Switch/Switch.tsx | 2 + .../__snapshots__/Switch.test.tsx.snap | 4 + 13 files changed, 337 insertions(+), 19 deletions(-) create mode 100644 docs/src/components/FABExample.tsx create mode 100644 docs/src/components/InteractiveExample.tsx create mode 100644 docs/src/components/SwitchExample.tsx create mode 100644 docs/src/data/liveExamples.ts 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 6f12dafdd8..0ee8e7be06 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,