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
25 changes: 25 additions & 0 deletions apps/docs/docs/components/layout/Accordion/_webStyles.mdx
Original file line number Diff line number Diff line change
@@ -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

<StylesExplorer selectors={webStylesData.selectors}>
{(classNames) => (
<Accordion classNames={classNames} defaultActiveKey="1">
<AccordionItem itemKey="1" subtitle="Subtitle" title="Accordion item">
<Text as="p" font="body">
Accordion panel content
</Text>
</AccordionItem>
</Accordion>
)}
</StylesExplorer>

## Selectors

<ComponentStylesTable componentName="Accordion" styles={webStylesData} />
3 changes: 3 additions & 0 deletions apps/docs/docs/components/layout/Accordion/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -26,11 +27,13 @@ import mobileMetadata from './mobileMetadata.json';
<ComponentTabsContainer
webPropsTable={<WebPropsTable />}
webExamples={<WebExamples />}
webStyles={<WebStyles />}
mobilePropsTable={<MobilePropsTable />}
mobileExamples={<MobileExamples />}
webExamplesToc={webExamplesToc}
mobileExamplesToc={mobileExamplesToc}
webPropsToc={webPropsToc}
mobilePropsToc={mobilePropsToc}
webStylesToc={webStylesToc}
/>
</VStack>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ComponentStylesTable } from '@site/src/components/page/ComponentStylesTable';

import mobileStylesData from ':docgen/mobile/accordion/AccordionItem/styles-data';

## Selectors

<ComponentStylesTable componentName="AccordionItem" styles={mobileStylesData} />
25 changes: 25 additions & 0 deletions apps/docs/docs/components/layout/AccordionItem/_webStyles.mdx
Original file line number Diff line number Diff line change
@@ -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

<StylesExplorer selectors={webStylesData.selectors}>
{(classNames) => (
<Accordion defaultActiveKey="1">
<AccordionItem classNames={classNames} itemKey="1" subtitle="Subtitle" title="Accordion item">
<Text as="p" font="body">
Accordion panel content
</Text>
</AccordionItem>
</Accordion>
)}
</StylesExplorer>

## Selectors

<ComponentStylesTable componentName="AccordionItem" styles={webStylesData} />
6 changes: 6 additions & 0 deletions apps/docs/docs/components/layout/AccordionItem/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -30,11 +32,15 @@ import mobileMetadata from './mobileMetadata.json';
<ComponentTabsContainer
webPropsTable={<WebPropsTable />}
webExamples={<WebExamples />}
webStyles={<WebStyles />}
mobilePropsTable={<MobilePropsTable />}
mobileExamples={<MobileExamples />}
mobileStyles={<MobileStyles />}
webExamplesToc={webExamplesToc}
mobileExamplesToc={mobileExamplesToc}
webPropsToc={webPropsToc}
mobilePropsToc={mobilePropsToc}
webStylesToc={webStylesToc}
mobileStylesToc={mobileStylesToc}
/>
</VStack>
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<SegmentedTabs
accessibilityLabel="Switch token action views"
activeTab={activeTab}
equalWidth
onChange={handleChange}
tabs={tabs}
/>
);
}
```

### Colors

You can set `color` and `activeColor` to change the color of the labels.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<SegmentedTabs
accessibilityLabel="Switch token action views"
activeTab={activeTab}
equalWidth
onChange={handleChange}
tabs={tabs}
/>
);
}
```

### Colors

You can set `color` and `activeColor` to change the color of the labels.
Expand Down
224 changes: 224 additions & 0 deletions apps/expo-app/src/playground/CustomerComponentConfigScreen.tsx
Original file line number Diff line number Diff line change
@@ -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<TabValue | null>(tabsExampleData[0]);
return (
<VStack>
<Tabs activeTab={activeTab} onChange={setActiveTab} tabs={tabsExampleData} />
<Divider />
</VStack>
);
});

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<TabValue | null>(segmentedTabsExampleData[0]);
return (
<SegmentedTabs activeTab={activeTab} onChange={setActiveTab} tabs={segmentedTabsExampleData} />
);
});

const comparisonKeys = ['Accordion', 'Inputs', 'Tabs', 'SegmentedTabs'] as const;
type ComparisonKey = (typeof comparisonKeys)[number];

const initialConfiguredState: Record<ComparisonKey, boolean> = {
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<ComparisonKey, boolean>,
),
);
}, []);

return (
<ComponentConfigProvider value={customerComponentConfig}>
<Box background="bg" flexGrow={1}>
<ScrollView contentContainerStyle={scrollContentContainerStyle}>
{/* 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. */}
<ThemeProvider
activeColorScheme={ambientTheme.activeColorScheme}
theme={useRetailTheme ? retailCDSTheme : ambientTheme}
>
<VStack gap={2} paddingBottom={2} paddingTop={2} paddingX={2}>
<HStack alignItems="center" justifyContent="flex-end">
<Switch
accessibilityLabel={
useRetailTheme
? 'Retail theme applied to all examples'
: 'Default theme applied to all examples'
}
checked={useRetailTheme}
color={useRetailTheme ? 'fgPrimary' : undefined}
onChange={handleToggleRetailTheme}
>
Apply Retail theme
</Switch>
</HStack>
<HStack alignItems="center" justifyContent="flex-end">
<Switch
accessibilityLabel={
allConfigured
? 'Showing configured mode for all components'
: 'Showing default mode for all components'
}
checked={allConfigured}
color={allConfigured ? 'fgPrimary' : undefined}
onChange={handleToggleAllConfigured}
>
Toggle all
</Switch>
</HStack>
<Divider />
</VStack>
<VStack flexGrow={1} gap={4} paddingBottom={8} paddingTop={2} paddingX={2}>
<ComponentConfigComparison
checked={configuredState.Accordion}
componentName="Accordion"
onChange={handleAccordionChange}
>
{() => (
<Accordion>
<AccordionItem itemKey="1" subtitle="Subtitle" title="Title">
<Text font="body">Accordion panel content.</Text>
</AccordionItem>
<AccordionItem itemKey="2" subtitle="Subtitle 2" title="Title 2">
<Text font="body">Accordion panel content 2.</Text>
</AccordionItem>
</Accordion>
)}
</ComponentConfigComparison>
<ComponentConfigComparison
checked={configuredState.Inputs}
componentName="Inputs"
onChange={handleInputsChange}
>
{() => (
<VStack>
<TextInput
accessibilityLabel="testing"
label="Text"
placeholder="Hello World!"
/>
<Select
accessibilityLabel="testing"
label="Select"
onChange={() => {}}
options={[]}
placeholder="Select an option"
value={null}
/>
<DateInput
accessibilityLabel="testing"
date={new Date()}
error={null}
label="Date"
onChangeDate={() => {}}
onErrorDate={() => {}}
placeholder="Hello World!"
/>
</VStack>
)}
</ComponentConfigComparison>
<ComponentConfigComparison
checked={configuredState.Tabs}
componentName="Tabs"
onChange={handleTabsChange}
>
{() => <TabsExample />}
</ComponentConfigComparison>
<ComponentConfigComparison
checked={configuredState.SegmentedTabs}
componentName="SegmentedTabs"
onChange={handleSegmentedTabsChange}
>
{() => <SegmentedTabsExample />}
</ComponentConfigComparison>
</VStack>
</ThemeProvider>
</ScrollView>
</Box>
</ComponentConfigProvider>
);
});
Loading
Loading