diff --git a/apps/storybook-react-native/.storybook/storybook.requires.js b/apps/storybook-react-native/.storybook/storybook.requires.js index 599589f3e..78e61508b 100644 --- a/apps/storybook-react-native/.storybook/storybook.requires.js +++ b/apps/storybook-react-native/.storybook/storybook.requires.js @@ -115,6 +115,7 @@ const getStories = () => { "./../../packages/design-system-react-native/src/components/TextButton/TextButton.stories.tsx": require("../../../packages/design-system-react-native/src/components/TextButton/TextButton.stories.tsx"), "./../../packages/design-system-react-native/src/components/TextField/TextField.stories.tsx": require("../../../packages/design-system-react-native/src/components/TextField/TextField.stories.tsx"), "./../../packages/design-system-react-native/src/components/TextFieldSearch/TextFieldSearch.stories.tsx": require("../../../packages/design-system-react-native/src/components/TextFieldSearch/TextFieldSearch.stories.tsx"), + "./../../packages/design-system-react-native/src/components/TitleHub/TitleHub.stories.tsx": require("../../../packages/design-system-react-native/src/components/TitleHub/TitleHub.stories.tsx"), "./../../packages/design-system-react-native/src/components/Toast/Toast.stories.tsx": require("../../../packages/design-system-react-native/src/components/Toast/Toast.stories.tsx"), "./stories/Backgrounds.stories.tsx": require("../stories/Backgrounds.stories.tsx"), "./stories/WalletHome.stories.tsx": require("../stories/WalletHome.stories.tsx"), diff --git a/packages/design-system-react-native/src/components/TitleHub/README.md b/packages/design-system-react-native/src/components/TitleHub/README.md new file mode 100644 index 000000000..61ccd0504 --- /dev/null +++ b/packages/design-system-react-native/src/components/TitleHub/README.md @@ -0,0 +1,283 @@ +# TitleHub + +TitleHub is used to display a **required** title row with an optional amount line below it, optional rows beneath that, optional inline accessories next to each row, and optional bottom label or custom bottom content. + +```tsx +import { TitleHub } from '@metamask/design-system-react-native'; + +; +``` + +## Props + +### `title` + +Title row (required). When `title` is a string, it uses `TextVariant.HeadingMd` and `TextColor.TextDefault` (merged with `titleProps`). For custom layout, pass a `ReactNode`. The row also renders when only `titleEndAccessory` is renderable (for example `title={false}` with an end accessory). + +Legacy **`TitleStandard`** **`topLabel`** maps to **`title`** on `TitleHub`. The old main-line value (large amount) maps to **`amount`**, not `title`. + +| TYPE | REQUIRED | DEFAULT | +| ----------- | -------- | ------- | +| `ReactNode` | Yes | — | + +```tsx +import { TitleHub } from '@metamask/design-system-react-native'; + +; +``` + +### `titleEndAccessory` + +Optional node to the right of `title` in the title row (same pattern as `amountEndAccessory`). + +| TYPE | REQUIRED | DEFAULT | +| ----------- | -------- | ----------- | +| `ReactNode` | No | `undefined` | + +```tsx +import { + TitleHub, + Box, + Icon, + IconName, + IconSize, +} from '@metamask/design-system-react-native'; + + + + + } + amount="$4.42" +/>; +``` + +### `amount` + +Optional primary amount line below the title. The amount row renders when `amount` or `amountEndAccessory` is renderable. When `amount` is a string, it is wrapped with display typography (`TextVariant.DisplayLg` and `amountProps`); other `ReactNode` values render as provided. + +| TYPE | REQUIRED | DEFAULT | +| ----------- | -------- | ----------- | +| `ReactNode` | No | `undefined` | + +```tsx +import { TitleHub } from '@metamask/design-system-react-native'; + +; +``` + +### `amountEndAccessory` + +Optional node rendered to the right of the amount (for example an info icon). + +| TYPE | REQUIRED | DEFAULT | +| ----------- | -------- | ----------- | +| `ReactNode` | No | `undefined` | + +```tsx +import { + TitleHub, + Box, + Icon, + IconName, + IconSize, +} from '@metamask/design-system-react-native'; + + + + + } +/>; +``` + +### `bottomLabel` + +Optional bottom label row with secondary typography when the value is a string (`BodySm`, medium, `TextColor.TextAlternative`). If `bottomLabel` or `bottomLabelEndAccessory` is renderable, that row is shown and `bottomAccessory` is not used. + +| TYPE | REQUIRED | DEFAULT | +| ----------- | -------- | ----------- | +| `ReactNode` | No | `undefined` | + +```tsx +import { TitleHub } from '@metamask/design-system-react-native'; + +; +``` + +### `bottomLabelEndAccessory` + +Optional node to the right of `bottomLabel` in the bottom label row (same pattern as `amountEndAccessory`). + +| TYPE | REQUIRED | DEFAULT | +| ----------- | -------- | ----------- | +| `ReactNode` | No | `undefined` | + +```tsx +import { + TitleHub, + Box, + Icon, + IconName, + IconSize, +} from '@metamask/design-system-react-native'; + + + + + } +/>; +``` + +### `bottomAccessory` + +Optional custom bottom row when neither `bottomLabel` nor `bottomLabelEndAccessory` is renderable. Renders without default label typography; compose layout inside the node. + +| TYPE | REQUIRED | DEFAULT | +| ----------- | -------- | ----------- | +| `ReactNode` | No | `undefined` | + +```tsx +import { + TitleHub, + Box, + BoxFlexDirection, + BoxAlignItems, + Icon, + IconName, + IconSize, + Text, + TextVariant, +} from '@metamask/design-system-react-native'; + + + + ~$0.50 fee + + } +/>; +``` + +### `amountProps` + +Optional props merged into the amount `Text` when `amount` is a string. Use for `testID` or typography overrides. + +| TYPE | REQUIRED | DEFAULT | +| -------------------- | -------- | ----------- | +| `Partial` | No | `undefined` | + +```tsx +import { TitleHub } from '@metamask/design-system-react-native'; + +; +``` + +### `titleProps` + +Optional props merged into the title row `Text` when `title` is a string. + +| TYPE | REQUIRED | DEFAULT | +| -------------------- | -------- | ----------- | +| `Partial` | No | `undefined` | + +```tsx +import { TitleHub } from '@metamask/design-system-react-native'; + +; +``` + +### `bottomLabelProps` + +Optional props merged into the bottom label `Text` when `bottomLabel` is a string. + +| TYPE | REQUIRED | DEFAULT | +| -------------------- | -------- | ----------- | +| `Partial` | No | `undefined` | + +```tsx +import { TitleHub } from '@metamask/design-system-react-native'; + +; +``` + +### `twClassName` + +Use the `twClassName` prop to add Tailwind CSS classes to the component. These classes will be merged with the component's default classes using `tw.style()`, allowing you to: + +- Add new styles that don't exist in the default component +- Override the component's default styles when needed + +| TYPE | REQUIRED | DEFAULT | +| -------- | -------- | ----------- | +| `string` | No | `undefined` | + +```tsx +import { TitleHub } from '@metamask/design-system-react-native'; + +// Add additional styles + + +// Override default styles + +``` + +### `style` + +Use the `style` prop to customize the component's appearance with React Native styles. For consistent styling, prefer using `twClassName` with Tailwind classes when possible. Use `style` with `tw.style()` for conditionals or dynamic values. Other `View` props (for example `testID` and accessibility fields) are also accepted on the root container. + +| TYPE | REQUIRED | DEFAULT | +| ---------------------- | -------- | ----------- | +| `StyleProp` | No | `undefined` | + +```tsx +import { useTailwind } from '@metamask/design-system-twrnc-preset'; + +import { TitleHub } from '@metamask/design-system-react-native'; + +export const ConditionalExample = ({ isActive }: { isActive: boolean }) => { + const tw = useTailwind(); + + return ( + + ); +}; +``` + +## References + +[MetaMask Design System Guides](https://www.notion.so/MetaMask-Design-System-Guides-Design-f86ecc914d6b4eb6873a122b83c12940) diff --git a/packages/design-system-react-native/src/components/TitleHub/TitleHub.stories.tsx b/packages/design-system-react-native/src/components/TitleHub/TitleHub.stories.tsx new file mode 100644 index 000000000..dcc7ac290 --- /dev/null +++ b/packages/design-system-react-native/src/components/TitleHub/TitleHub.stories.tsx @@ -0,0 +1,147 @@ +import type { Meta, StoryObj } from '@storybook/react-native'; +import React from 'react'; + +import { Box, BoxAlignItems, BoxFlexDirection } from '../Box'; +import { Icon, IconName, IconSize, IconColor } from '../Icon'; +import { Text, TextColor, FontWeight, TextVariant } from '../Text'; + +import { TitleHub } from './TitleHub'; +import type { TitleHubProps } from './TitleHub.types'; + +/** + * Pill badge: dot + label (e.g. network), for `titleEndAccessory`. + * TODO: Temporary until a Tag component exists. + * + * @returns Story-only testnet badge UI. + */ +const TestnetBadge = () => ( + + + + Testnet + + +); + +const meta: Meta = { + title: 'Components/TitleHub', + component: TitleHub, + argTypes: { + title: { + control: 'text', + }, + amount: { + control: 'text', + }, + bottomLabel: { + control: 'text', + }, + twClassName: { control: 'text' }, + }, + decorators: [ + (Story) => ( + + + + ), + ], +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + title: 'Perps', + amount: '$336.21', + bottomLabel: '$336.21 available', + twClassName: '', + }, + render: (args) => } />, +}; + +export const Amount: Story = { + render: () => , +}; + +export const AmountAccessory: Story = { + render: () => ( + + + + } + /> + ), +}; + +export const Title: Story = { + render: () => ( + + + + ), +}; + +export const TitleAccessory: Story = { + render: () => } />, +}; + +export const BottomLabel: Story = { + args: { + title: 'Perps', + amount: '$336.21', + bottomLabel: '$336.21 available', + twClassName: '', + }, +}; + +export const BottomLabelAccessory: Story = { + render: () => ( + } + /> + ), +}; + +export const BottomAccessory: Story = { + render: () => ( + + + + Perps use isolated margin. Liquidation can occur if collateral falls + below maintenance. + + + } + /> + ), +}; diff --git a/packages/design-system-react-native/src/components/TitleHub/TitleHub.test.tsx b/packages/design-system-react-native/src/components/TitleHub/TitleHub.test.tsx new file mode 100644 index 000000000..78fc97dd2 --- /dev/null +++ b/packages/design-system-react-native/src/components/TitleHub/TitleHub.test.tsx @@ -0,0 +1,320 @@ +// Third party dependencies. +import { useTailwind } from '@metamask/design-system-twrnc-preset'; +import { renderHook } from '@testing-library/react-hooks'; +import { render } from '@testing-library/react-native'; +import React from 'react'; +import { Text } from 'react-native'; + +// Internal dependencies. +import { TitleHub } from './TitleHub'; + +const CONTAINER_TEST_ID = 'title-hub-container'; +const AMOUNT_TEST_ID = 'title-hub-amount'; +const TITLE_ROW_TEST_ID = 'title-hub-title'; +const BOTTOM_LABEL_TEST_ID = 'title-hub-bottom-label'; + +describe('TitleHub', () => { + let tw: ReturnType; + + beforeAll(() => { + tw = renderHook(() => useTailwind()).result.current; + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('rendering', () => { + it('renders string title', () => { + const { getByText } = render(); + + expect(getByText('Section')).toBeOnTheScreen(); + }); + + it('renders string amount when provided', () => { + const { getByText } = render(); + + expect(getByText('Send')).toBeOnTheScreen(); + expect(getByText('$4.42')).toBeOnTheScreen(); + }); + + it('renders React node amount', () => { + const { getByTestId } = render( + Custom amount} + />, + ); + + expect(getByTestId('title-hub-amount-node')).toBeOnTheScreen(); + }); + + it('renders container with testID when provided', () => { + const { getByTestId } = render( + , + ); + + expect(getByTestId(CONTAINER_TEST_ID)).toBeOnTheScreen(); + }); + + it('forwards amountProps testID to amount Text when amount is a string', () => { + const { getByTestId } = render( + , + ); + + expect(getByTestId(AMOUNT_TEST_ID)).toBeOnTheScreen(); + }); + }); + + describe('when title is provided', () => { + it('renders title and amount', () => { + const { getByText } = render( + Custom Top} amount="$4.42" />, + ); + + expect(getByText('Custom Top')).toBeOnTheScreen(); + expect(getByText('$4.42')).toBeOnTheScreen(); + }); + + it('renders title and titleEndAccessory', () => { + const { getByText } = render( + Title extra} + />, + ); + + expect(getByText('Step 1')).toBeOnTheScreen(); + expect(getByText('Title extra')).toBeOnTheScreen(); + }); + + it('forwards titleProps testID to title row Text when title is a string', () => { + const { getByTestId } = render( + , + ); + + expect(getByTestId(TITLE_ROW_TEST_ID)).toBeOnTheScreen(); + }); + }); + + describe('when title is false', () => { + it('does not render title node', () => { + const showTitle = false; + const { getByText, queryByTestId } = render( + Top : false + } + amount="$4.42" + />, + ); + + expect(getByText('$4.42')).toBeOnTheScreen(); + expect(queryByTestId('title-hub-title-slot')).not.toBeOnTheScreen(); + }); + }); + + describe('when titleEndAccessory is false', () => { + it('renders title only', () => { + const { getByText } = render( + , + ); + + expect(getByText('Hi')).toBeOnTheScreen(); + expect(getByText('$4.42')).toBeOnTheScreen(); + }); + }); + + describe('when amount is false', () => { + it('does not render amount node', () => { + const showAmount = false; + const { getByText, queryByTestId } = render( + $1 : false + } + />, + ); + + expect(getByText('Send')).toBeOnTheScreen(); + expect(queryByTestId('title-hub-amount-slot')).not.toBeOnTheScreen(); + }); + }); + + describe('when bottomLabel is provided', () => { + it('renders bottomLabel text', () => { + const { getByText } = render( + , + ); + + expect(getByText('0.002 ETH')).toBeOnTheScreen(); + }); + + it('renders bottomLabel and bottomLabelEndAccessory', () => { + const { getByText } = render( + Fee info} + />, + ); + + expect(getByText('0.002 ETH')).toBeOnTheScreen(); + expect(getByText('Fee info')).toBeOnTheScreen(); + }); + + it('forwards bottomLabelProps testID to bottom label Text', () => { + const { getByTestId } = render( + , + ); + + expect(getByTestId(BOTTOM_LABEL_TEST_ID)).toBeOnTheScreen(); + }); + }); + + describe('when bottomAccessory is provided', () => { + it('renders bottomAccessory when bottomLabel is omitted', () => { + const { getByText } = render( + Custom Bottom} + />, + ); + + expect(getByText('Custom Bottom')).toBeOnTheScreen(); + }); + }); + + describe('when bottomLabel and bottomAccessory are both provided', () => { + it('renders only bottomLabel', () => { + const { getByText, queryByText } = render( + Accessory} + />, + ); + + expect(getByText('Label Priority')).toBeOnTheScreen(); + expect(queryByText('Accessory')).not.toBeOnTheScreen(); + }); + }); + + describe('when only bottomLabelEndAccessory is provided', () => { + it('renders bottom label row with accessory and not bottomAccessory', () => { + const { getByText, queryByText } = render( + Only accessory} + bottomAccessory={Full row} + />, + ); + + expect(getByText('Only accessory')).toBeOnTheScreen(); + expect(queryByText('Full row')).not.toBeOnTheScreen(); + }); + }); + + describe('when amountEndAccessory is provided', () => { + it('renders amount and amountEndAccessory', () => { + const { getByText } = render( + Info} + />, + ); + + expect(getByText('$4.42')).toBeOnTheScreen(); + expect(getByText('Info')).toBeOnTheScreen(); + }); + + it('renders amountEndAccessory when amount is an empty string', () => { + const { getByText } = render( + Accessory only} + />, + ); + + expect(getByText('Accessory only')).toBeOnTheScreen(); + }); + }); + + describe('when amountEndAccessory is false', () => { + it('renders amount only', () => { + const { getByText } = render( + , + ); + + expect(getByText('$4.42')).toBeOnTheScreen(); + }); + }); + + describe('when title, amountEndAccessory, and bottomLabel are provided', () => { + it('renders all slots', () => { + const { getByText } = render( + Send} + amount="$4.42" + amountEndAccessory={i} + bottomLabel="0.002 ETH" + />, + ); + + expect(getByText('Send')).toBeOnTheScreen(); + expect(getByText('$4.42')).toBeOnTheScreen(); + expect(getByText('i')).toBeOnTheScreen(); + expect(getByText('0.002 ETH')).toBeOnTheScreen(); + }); + }); + + describe('style and twClassName', () => { + it('applies custom style to root container', () => { + const customStyle = { opacity: 0.5 }; + const { getByTestId } = render( + , + ); + + expect(getByTestId(CONTAINER_TEST_ID)).toHaveStyle(customStyle); + }); + + it('merges twClassName with base styles', () => { + const { getByTestId } = render( + , + ); + + const container = getByTestId(CONTAINER_TEST_ID); + + expect(container).toHaveStyle(tw`bg-default`); + }); + }); +}); diff --git a/packages/design-system-react-native/src/components/TitleHub/TitleHub.tsx b/packages/design-system-react-native/src/components/TitleHub/TitleHub.tsx new file mode 100644 index 000000000..6d12393bc --- /dev/null +++ b/packages/design-system-react-native/src/components/TitleHub/TitleHub.tsx @@ -0,0 +1,118 @@ +// Third party dependencies. +import { isReactNodeRenderable } from '@metamask/design-system-shared'; +import React from 'react'; + +// Internal dependencies. +import { Box } from '../Box'; +import { BoxRow } from '../BoxRow'; +import { TextVariant, TextColor, FontWeight } from '../Text'; + +import type { TitleHubProps } from './TitleHub.types'; + +/** + * Displays a required title row with optional amount, inline accessories, and bottom rows in a left-aligned layout. + * Remaining `View` props are forwarded to the root `Box`. + * + * @param props - Component props + * @param props.title - Title row content (required) + * @param props.titleEndAccessory - Optional inline accessory to the right of `title` + * @param props.amount - Optional primary amount below the title + * @param props.amountEndAccessory - Optional inline accessory to the right of the amount + * @param props.bottomAccessory - Optional custom bottom row when the bottom label row is not shown + * @param props.bottomLabel - Optional secondary label below the amount row + * @param props.bottomLabelEndAccessory - Optional inline accessory to the right of `bottomLabel` + * @param props.titleProps - Optional props merged into title row `Text` when `title` is a string + * @param props.amountProps - Optional props merged into amount `Text` when `amount` is a string + * @param props.bottomLabelProps - Optional props merged into bottom label `Text` when `bottomLabel` is a string + * @param props.twClassName - Optional Tailwind classes on the root container + * + * @returns The rendered TitleHub layout. + */ +export const TitleHub: React.FC = ({ + amount, + amountEndAccessory, + title, + titleEndAccessory, + bottomAccessory, + bottomLabel, + bottomLabelEndAccessory, + amountProps, + titleProps, + bottomLabelProps, + twClassName = '', + ...props +}) => { + const amountEndAccessoryNode = isReactNodeRenderable(amountEndAccessory) + ? amountEndAccessory + : undefined; + + const titleEndAccessoryNode = isReactNodeRenderable(titleEndAccessory) + ? titleEndAccessory + : undefined; + + const bottomLabelEndAccessoryNode = isReactNodeRenderable( + bottomLabelEndAccessory, + ) + ? bottomLabelEndAccessory + : undefined; + + const renderTitleRow = + isReactNodeRenderable(title) || isReactNodeRenderable(titleEndAccessory); + const renderAmountRow = + isReactNodeRenderable(amount) || isReactNodeRenderable(amountEndAccessory); + const renderBottomLabelRow = + isReactNodeRenderable(bottomLabel) || + isReactNodeRenderable(bottomLabelEndAccessory); + const renderBottomAccessory = + !renderBottomLabelRow && isReactNodeRenderable(bottomAccessory); + + const titleRow = ( + + {title} + + ); + + const amountRow = ( + + {amount} + + ); + + const bottomLabelRow = ( + + {bottomLabel} + + ); + + return ( + + {renderTitleRow ? titleRow : null} + {renderAmountRow ? amountRow : null} + {renderBottomLabelRow ? bottomLabelRow : null} + {renderBottomAccessory ? bottomAccessory : null} + + ); +}; + +TitleHub.displayName = 'TitleHub'; diff --git a/packages/design-system-react-native/src/components/TitleHub/TitleHub.types.ts b/packages/design-system-react-native/src/components/TitleHub/TitleHub.types.ts new file mode 100644 index 000000000..d6c98126c --- /dev/null +++ b/packages/design-system-react-native/src/components/TitleHub/TitleHub.types.ts @@ -0,0 +1,29 @@ +// Third party dependencies. +import type { TitleHubPropsShared } from '@metamask/design-system-shared'; +import type { ViewProps } from 'react-native'; + +// Internal dependencies. +import type { TextProps } from '../Text/Text.types'; + +/** + * TitleHub component props (React Native). + * Extends {@link TitleHubPropsShared} (requires `title`) with platform `Text` passthroughs, `twClassName`, and `View` props. + */ +export type TitleHubProps = TitleHubPropsShared & { + /** + * Optional props merged into {@link BoxHorizontal} `textProps` when `amount` is a string. + */ + amountProps?: Partial; + /** + * Optional props merged into {@link BoxHorizontal} `textProps` when `title` is a string. + */ + titleProps?: Partial; + /** + * Optional props merged into {@link BoxHorizontal} `textProps` when `bottomLabel` is a string. + */ + bottomLabelProps?: Partial; + /** + * Optional Tailwind class name to apply to the container. + */ + twClassName?: string; +} & Omit; diff --git a/packages/design-system-react-native/src/components/TitleHub/index.ts b/packages/design-system-react-native/src/components/TitleHub/index.ts new file mode 100644 index 000000000..67231dc5a --- /dev/null +++ b/packages/design-system-react-native/src/components/TitleHub/index.ts @@ -0,0 +1,3 @@ +export type { TitleHubPropsShared } from '@metamask/design-system-shared'; +export { TitleHub } from './TitleHub'; +export type { TitleHubProps } from './TitleHub.types'; diff --git a/packages/design-system-react-native/src/components/index.ts b/packages/design-system-react-native/src/components/index.ts index e057985d2..3afafcfdc 100644 --- a/packages/design-system-react-native/src/components/index.ts +++ b/packages/design-system-react-native/src/components/index.ts @@ -204,6 +204,9 @@ export type { TextFieldSearchProps } from './TextFieldSearch'; export { TextOrChildren } from './temp-components/TextOrChildren'; export type { TextOrChildrenProps } from './temp-components/TextOrChildren'; +export { TitleHub } from './TitleHub'; +export type { TitleHubProps, TitleHubPropsShared } from './TitleHub'; + export { Toast, ToastVariant, diff --git a/packages/design-system-shared/src/index.ts b/packages/design-system-shared/src/index.ts index c16cb2e07..4fd113a43 100644 --- a/packages/design-system-shared/src/index.ts +++ b/packages/design-system-shared/src/index.ts @@ -37,12 +37,15 @@ export { type BannerBasePropsShared } from './types/BannerBase'; // TextOrChildren types (ADR-0004) export { type TextOrChildrenPropsShared } from './types/TextOrChildren'; -// BoxRow types (ADR-0004) -export { type BoxRowPropsShared } from './types/BoxRow'; +// TitleHub types (ADR-0004) +export { type TitleHubPropsShared } from './types/TitleHub'; // BoxColumn types (ADR-0004) export { type BoxColumnPropsShared } from './types/BoxColumn'; +// BoxRow types (ADR-0004) +export { type BoxRowPropsShared } from './types/BoxRow'; + // HeaderSearch types (ADR-0003 + ADR-0004) export { HeaderSearchVariant, diff --git a/packages/design-system-shared/src/types/TitleHub/TitleHub.types.ts b/packages/design-system-shared/src/types/TitleHub/TitleHub.types.ts new file mode 100644 index 000000000..1b5afe233 --- /dev/null +++ b/packages/design-system-shared/src/types/TitleHub/TitleHub.types.ts @@ -0,0 +1,41 @@ +import type { ReactNode } from 'react'; + +/** + * TitleHub component shared props (ADR-0004). + * Platform-independent properties; platform packages extend with `ViewProps` / `className`, + * `twClassName`, and platform `Text` prop passthroughs. + */ +export type TitleHubPropsShared = { + /** + * Optional primary amount line below the title (for example a fiat or token value). + * When a string, platforms typically wrap with large display styles via `textProps`. + * The amount row renders when `amount` or `amountEndAccessory` is renderable. + */ + amount?: ReactNode; + /** + * Optional accessory rendered inline to the right of the amount. + */ + amountEndAccessory?: ReactNode; + /** + * Title row above the optional amount (via platform `textProps` when a string). Required. + */ + title: ReactNode; + /** + * Optional accessory rendered inline to the right of `title` in the title row. + */ + titleEndAccessory?: ReactNode; + /** + * Optional custom bottom row when neither `bottomLabel` nor `bottomLabelEndAccessory` is renderable. + * Mutually exclusive with the bottom label row: only one bottom row is shown. + */ + bottomAccessory?: ReactNode; + /** + * Optional bottom row with secondary label styling when a string (via platform `textProps`). + * If `bottomLabel` or `bottomLabelEndAccessory` is renderable, that row is shown instead of `bottomAccessory`. + */ + bottomLabel?: ReactNode; + /** + * Optional accessory rendered inline to the right of `bottomLabel` in the bottom label row. + */ + bottomLabelEndAccessory?: ReactNode; +}; diff --git a/packages/design-system-shared/src/types/TitleHub/index.ts b/packages/design-system-shared/src/types/TitleHub/index.ts new file mode 100644 index 000000000..4eb38613e --- /dev/null +++ b/packages/design-system-shared/src/types/TitleHub/index.ts @@ -0,0 +1 @@ +export type { TitleHubPropsShared } from './TitleHub.types';