diff --git a/apps/docs/docs/components/layout/Accordion/_webStyles.mdx b/apps/docs/docs/components/layout/Accordion/_webStyles.mdx new file mode 100644 index 0000000000..6f2b198d41 --- /dev/null +++ b/apps/docs/docs/components/layout/Accordion/_webStyles.mdx @@ -0,0 +1,25 @@ +import { Accordion } from '@coinbase/cds-web/accordion/Accordion'; +import { AccordionItem } from '@coinbase/cds-web/accordion/AccordionItem'; +import { Text } from '@coinbase/cds-web/typography/Text'; +import { ComponentStylesTable } from '@site/src/components/page/ComponentStylesTable'; +import { StylesExplorer } from '@site/src/components/page/StylesExplorer'; + +import webStylesData from ':docgen/web/accordion/Accordion/styles-data'; + +## Explorer + + + {(classNames) => ( + + + + Accordion panel content + + + + )} + + +## Selectors + + diff --git a/apps/docs/docs/components/layout/Accordion/index.mdx b/apps/docs/docs/components/layout/Accordion/index.mdx index 2f862c5650..4fec2ae440 100644 --- a/apps/docs/docs/components/layout/Accordion/index.mdx +++ b/apps/docs/docs/components/layout/Accordion/index.mdx @@ -17,6 +17,7 @@ import WebPropsTable from './_webPropsTable.mdx'; import MobilePropsTable from './_mobilePropsTable.mdx'; import MobileExamples, { toc as mobileExamplesToc } from './_mobileExamples.mdx'; import WebExamples, { toc as webExamplesToc } from './_webExamples.mdx'; +import WebStyles, { toc as webStylesToc } from './_webStyles.mdx'; import webMetadata from './webMetadata.json'; import mobileMetadata from './mobileMetadata.json'; @@ -26,11 +27,13 @@ import mobileMetadata from './mobileMetadata.json'; } webExamples={} + webStyles={} mobilePropsTable={} mobileExamples={} webExamplesToc={webExamplesToc} mobileExamplesToc={mobileExamplesToc} webPropsToc={webPropsToc} mobilePropsToc={mobilePropsToc} + webStylesToc={webStylesToc} /> diff --git a/apps/docs/docs/components/layout/AccordionItem/_mobileStyles.mdx b/apps/docs/docs/components/layout/AccordionItem/_mobileStyles.mdx new file mode 100644 index 0000000000..f6ec48574f --- /dev/null +++ b/apps/docs/docs/components/layout/AccordionItem/_mobileStyles.mdx @@ -0,0 +1,7 @@ +import { ComponentStylesTable } from '@site/src/components/page/ComponentStylesTable'; + +import mobileStylesData from ':docgen/mobile/accordion/AccordionItem/styles-data'; + +## Selectors + + diff --git a/apps/docs/docs/components/layout/AccordionItem/_webStyles.mdx b/apps/docs/docs/components/layout/AccordionItem/_webStyles.mdx new file mode 100644 index 0000000000..3813de5376 --- /dev/null +++ b/apps/docs/docs/components/layout/AccordionItem/_webStyles.mdx @@ -0,0 +1,25 @@ +import { Accordion } from '@coinbase/cds-web/accordion/Accordion'; +import { AccordionItem } from '@coinbase/cds-web/accordion/AccordionItem'; +import { Text } from '@coinbase/cds-web/typography/Text'; +import { ComponentStylesTable } from '@site/src/components/page/ComponentStylesTable'; +import { StylesExplorer } from '@site/src/components/page/StylesExplorer'; + +import webStylesData from ':docgen/web/accordion/AccordionItem/styles-data'; + +## Explorer + + + {(classNames) => ( + + + + Accordion panel content + + + + )} + + +## Selectors + + diff --git a/apps/docs/docs/components/layout/AccordionItem/index.mdx b/apps/docs/docs/components/layout/AccordionItem/index.mdx index 3b2d68695f..efa39730e1 100644 --- a/apps/docs/docs/components/layout/AccordionItem/index.mdx +++ b/apps/docs/docs/components/layout/AccordionItem/index.mdx @@ -17,6 +17,8 @@ import WebPropsTable from './_webPropsTable.mdx'; import MobilePropsTable from './_mobilePropsTable.mdx'; import MobileExamples, { toc as mobileExamplesToc } from './_mobileExamples.mdx'; import WebExamples, { toc as webExamplesToc } from './_webExamples.mdx'; +import MobileStyles, { toc as mobileStylesToc } from './_mobileStyles.mdx'; +import WebStyles, { toc as webStylesToc } from './_webStyles.mdx'; import webMetadata from './webMetadata.json'; import mobileMetadata from './mobileMetadata.json'; @@ -30,11 +32,15 @@ import mobileMetadata from './mobileMetadata.json'; } webExamples={} + webStyles={} mobilePropsTable={} mobileExamples={} + mobileStyles={} webExamplesToc={webExamplesToc} mobileExamplesToc={mobileExamplesToc} webPropsToc={webPropsToc} mobilePropsToc={mobilePropsToc} + webStylesToc={webStylesToc} + mobileStylesToc={mobileStylesToc} /> diff --git a/apps/docs/docs/components/navigation/SegmentedTabs/_mobileExamples.mdx b/apps/docs/docs/components/navigation/SegmentedTabs/_mobileExamples.mdx index c800395624..fee69b62a2 100644 --- a/apps/docs/docs/components/navigation/SegmentedTabs/_mobileExamples.mdx +++ b/apps/docs/docs/components/navigation/SegmentedTabs/_mobileExamples.mdx @@ -150,6 +150,31 @@ function Example() { } ``` +### Equal Width + +Set `equalWidth` to make each tab stretch to an equal share of the available width, with the component filling its parent. This is the correct way to achieve a distributed tab layout — avoid using `justifyContent` for this, as it breaks the active indicator's position calculation. + +```jsx +function Example() { + const tabs = [ + { id: 'buy', label: 'Buy' }, + { id: 'sell', label: 'Sell' }, + { id: 'convert', label: 'Convert' }, + ]; + const [activeTab, updateActiveTab] = useState(tabs[0]); + const handleChange = useCallback((activeTab) => updateActiveTab(activeTab), []); + return ( + + ); +} +``` + ### Colors You can set `color` and `activeColor` to change the color of the labels. diff --git a/apps/docs/docs/components/navigation/SegmentedTabs/_webExamples.mdx b/apps/docs/docs/components/navigation/SegmentedTabs/_webExamples.mdx index d5be502dbb..db33e03d5b 100644 --- a/apps/docs/docs/components/navigation/SegmentedTabs/_webExamples.mdx +++ b/apps/docs/docs/components/navigation/SegmentedTabs/_webExamples.mdx @@ -149,6 +149,31 @@ function Example() { } ``` +### Equal Width + +Set `equalWidth` to make each tab stretch to an equal share of the available width. This also defaults `width` to `100%` so the component fills its parent — pass an explicit `width` to override this. This is the correct way to achieve a distributed tab layout — avoid using `justifyContent` for this, as it breaks the active indicator's position calculation. + +```jsx live +function Example() { + const tabs = [ + { id: 'buy', label: 'Buy' }, + { id: 'sell', label: 'Sell' }, + { id: 'convert', label: 'Convert' }, + ]; + const [activeTab, updateActiveTab] = useState(tabs[0]); + const handleChange = useCallback((activeTab) => updateActiveTab(activeTab), []); + return ( + + ); +} +``` + ### Colors You can set `color` and `activeColor` to change the color of the labels. diff --git a/apps/expo-app/src/playground/CustomerComponentConfigScreen.tsx b/apps/expo-app/src/playground/CustomerComponentConfigScreen.tsx new file mode 100644 index 0000000000..9f704e3c4c --- /dev/null +++ b/apps/expo-app/src/playground/CustomerComponentConfigScreen.tsx @@ -0,0 +1,224 @@ +import React, { memo, useCallback, useState } from 'react'; +import { ScrollView } from 'react-native'; +import type { TabValue } from '@coinbase/cds-common/tabs/useTabs'; +import { Accordion } from '@coinbase/cds-mobile/accordion/Accordion'; +import { AccordionItem } from '@coinbase/cds-mobile/accordion/AccordionItem'; +import { Select } from '@coinbase/cds-mobile/alpha/select/Select'; +import { Switch } from '@coinbase/cds-mobile/controls/Switch'; +import { TextInput } from '@coinbase/cds-mobile/controls/TextInput'; +import { DateInput } from '@coinbase/cds-mobile/dates/DateInput'; +import { useTheme } from '@coinbase/cds-mobile/hooks/useTheme'; +import { Divider } from '@coinbase/cds-mobile/layout'; +import { Box } from '@coinbase/cds-mobile/layout/Box'; +import { HStack } from '@coinbase/cds-mobile/layout/HStack'; +import { VStack } from '@coinbase/cds-mobile/layout/VStack'; +import { ComponentConfigProvider } from '@coinbase/cds-mobile/system/ComponentConfigProvider'; +import { ThemeProvider } from '@coinbase/cds-mobile/system/ThemeProvider'; +import { SegmentedTabs } from '@coinbase/cds-mobile/tabs/SegmentedTabs'; +import { Tabs } from '@coinbase/cds-mobile/tabs/Tabs'; +import { Text } from '@coinbase/cds-mobile/typography/Text'; + +import { ComponentConfigComparison } from './customerComponentConfig/ComponentConfigComparison'; +import { customerComponentConfig } from './customerComponentConfig/customerComponentConfig'; +import { retailCDSTheme } from './customerComponentConfig/retailCDSTheme'; + +const scrollContentContainerStyle = { flexGrow: 1 }; + +const tabsExampleData: TabValue[] = [ + { id: 'buy', label: 'Buy' }, + { id: 'sell', label: 'Sell' }, +]; + +/** Self-contained Tabs example so its active-tab state lives outside the render-prop callback. */ +const TabsExample = memo(() => { + const [activeTab, setActiveTab] = useState(tabsExampleData[0]); + return ( + + + + + ); +}); + +const segmentedTabsExampleData: TabValue[] = [ + { id: 'home', label: 'Home' }, + { id: 'trade', label: 'Trade' }, + { id: 'account', label: 'Account' }, +]; + +/** Self-contained SegmentedTabs example so its active-tab state lives outside the render-prop callback. */ +const SegmentedTabsExample = memo(() => { + const [activeTab, setActiveTab] = useState(segmentedTabsExampleData[0]); + return ( + + ); +}); + +const comparisonKeys = ['Accordion', 'Inputs', 'Tabs', 'SegmentedTabs'] as const; +type ComparisonKey = (typeof comparisonKeys)[number]; + +const initialConfiguredState: Record = { + Accordion: false, + Inputs: false, + Tabs: false, + SegmentedTabs: false, +}; + +/** + * Playground screen for iterating on a customer's CDS component config. + * Wrap examples in {@link ComponentConfigComparison} to toggle stock CDS vs configured. + */ +export const CustomerComponentConfigScreen = memo(() => { + const ambientTheme = useTheme(); + const [useRetailTheme, setUseRetailTheme] = useState(false); + const [configuredState, setConfiguredState] = useState(initialConfiguredState); + + const handleToggleRetailTheme = useCallback((_: string | undefined, checked?: boolean) => { + setUseRetailTheme(Boolean(checked)); + }, []); + + const setConfigured = useCallback((key: ComparisonKey, checked: boolean) => { + setConfiguredState((prev) => ({ ...prev, [key]: checked })); + }, []); + const handleAccordionChange = useCallback( + (checked: boolean) => setConfigured('Accordion', checked), + [setConfigured], + ); + const handleInputsChange = useCallback( + (checked: boolean) => setConfigured('Inputs', checked), + [setConfigured], + ); + const handleTabsChange = useCallback( + (checked: boolean) => setConfigured('Tabs', checked), + [setConfigured], + ); + const handleSegmentedTabsChange = useCallback( + (checked: boolean) => setConfigured('SegmentedTabs', checked), + [setConfigured], + ); + + const allConfigured = comparisonKeys.every((key) => configuredState[key]); + + const handleToggleAllConfigured = useCallback((_: string | undefined, checked?: boolean) => { + const nextChecked = Boolean(checked); + setConfiguredState( + comparisonKeys.reduce( + (next, key) => ({ ...next, [key]: nextChecked }), + {} as Record, + ), + ); + }, []); + + return ( + + + + {/* Always mount ThemeProvider (swapping only the `theme` value) so this subtree never + remounts on toggle, preserving the local state of every control beneath it. Nesting + the theme toggle itself inside the provider lets the retail theme restyle its own + switch too. */} + + + + + Apply Retail theme + + + + + Toggle all + + + + + + + {() => ( + + + Accordion panel content. + + + Accordion panel content 2. + + + )} + + + {() => ( + + +