Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(blade): add Breadcrumb component #2028

Merged
merged 18 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
5 changes: 5 additions & 0 deletions .changeset/five-cows-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@razorpay/blade": minor
---

feat(blade): breadcrumb web implementation
Copy link
Collaborator

@chaitanyadeorukhkar chaitanyadeorukhkar Feb 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
feat(blade): breadcrumb web implementation
feat(blade): add `Breadcrumb` component

13 changes: 13 additions & 0 deletions packages/blade/src/components/Breadcrumb/Breadcrumb.native.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { BreadcrumbProps } from './types';
import { throwBladeError } from '~utils/logger';

const Breadcrumb = (_: BreadcrumbProps): React.ReactElement => {
throwBladeError({
message: 'Breadcrumb is not yet implemented for native',
moduleName: 'Breadcrumb',
});

return <></>;
};

export { Breadcrumb };
329 changes: 329 additions & 0 deletions packages/blade/src/components/Breadcrumb/Breadcrumb.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
/* eslint-disable jsx-a11y/label-has-associated-control */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { Title } from '@storybook/addon-docs';
import type { StoryFn, Meta } from '@storybook/react';
import React from 'react';
import type { LinkProps } from 'react-router-dom';
import { useLocation, Link as RouterLink, matchPath, Route } from 'react-router-dom';
import StoryRouter from 'storybook-react-router';
import type { BreadcrumbItemProps, BreadcrumbProps } from './types';
import { Breadcrumb } from './Breadcrumb';
import { BreadcrumbItem } from './BreadcrumbItem';
import StoryPageWrapper from '~utils/storybook/StoryPageWrapper';
import { Sandbox } from '~utils/storybook/Sandbox';
import { Box } from '~components/Box';
import { HomeIcon } from '~components/Icons';
import { Code, Text } from '~components/Typography';
import { Card, CardBody, CardHeader, CardHeaderLeading } from '~components/Card';
import { Link } from '~components/Link';
import { getStyledPropsArgTypes } from '~components/Box/BaseBox/storybookArgTypes';

const Page = (): React.ReactElement => {
return (
<StoryPageWrapper
componentName="Breadcrumb"
componentDescription="Breadcrumbs are used for navigating through or to show user’s location in an application"
figmaURL="https://www.figma.com/file/jubmQL9Z8V7881ayUD95ps/Blade-DSL?node-id=81010%3A24682"
>
<Title>Usage</Title>
<Sandbox>
{`
import { Box, Breadcrumb, BreadcrumbItem, HomeIcon } from '@razorpay/blade/components';

function App(): React.ReactElement {
return (
<Box padding="spacing.4">
<Breadcrumb>
<BreadcrumbItem accessibilityLabel="Home" icon={HomeIcon} href="/home" />
<BreadcrumbItem href="/dashboard">Dashboard</BreadcrumbItem>
<BreadcrumbItem isCurrentPage href="/settlements">
Settlements
</BreadcrumbItem>
</Breadcrumb>
</Box>
);
}

export default App;
`}
</Sandbox>
</StoryPageWrapper>
);
};

export default {
title: 'Components/Breadcrumb',
component: Breadcrumb,
tags: ['autodocs'],
argTypes: {
...getStyledPropsArgTypes(),
},
args: {
size: 'medium',
color: 'primary',
showLastSeparator: false,
},
// eslint-disable-next-line babel/new-cap
decorators: [StoryRouter(undefined, { initialEntries: ['/home'] })] as unknown,
parameters: {
docs: {
page: Page,
},
},
} as Meta<BreadcrumbProps>;

const BasicToastTemplate: StoryFn<BreadcrumbProps> = (props) => {
return (
<Box
padding="spacing.4"
backgroundColor={props.color === 'white' ? 'surface.background.cloud.intense' : undefined}
>
<Breadcrumb {...props}>
<BreadcrumbItem accessibilityLabel="Home" icon={HomeIcon} href="/home" />
<BreadcrumbItem href="/dashboard">Dashboard</BreadcrumbItem>
<BreadcrumbItem isCurrentPage href="/settlements">
Settlements
</BreadcrumbItem>
</Breadcrumb>
</Box>
);
};

BasicToastTemplate.storyName = 'Basic';
export const Basic = BasicToastTemplate.bind({});

const BreadcrumbSizes: StoryFn<BreadcrumbProps> = () => {
return (
<Box display="flex" gap="spacing.5" flexDirection="column">
<Breadcrumb size="small">
<BreadcrumbItem accessibilityLabel="Home" icon={HomeIcon} href="/home" />
<BreadcrumbItem href="/dashboard">Dashboard</BreadcrumbItem>
<BreadcrumbItem isCurrentPage href="/settlements">
Settlements
</BreadcrumbItem>
</Breadcrumb>
<Breadcrumb size="medium">
<BreadcrumbItem icon={HomeIcon} href="/home" />
<BreadcrumbItem href="/dashboard">Dashboard</BreadcrumbItem>
<BreadcrumbItem isCurrentPage href="/settlements">
Settlements
</BreadcrumbItem>
</Breadcrumb>
<Breadcrumb size="large">
<BreadcrumbItem icon={HomeIcon} href="/home" />
<BreadcrumbItem href="/dashboard">Dashboard</BreadcrumbItem>
<BreadcrumbItem isCurrentPage href="/settlements">
Settlements
</BreadcrumbItem>
</Breadcrumb>
</Box>
);
};

BreadcrumbSizes.storyName = 'Sizes';
export const Sizes = BreadcrumbSizes.bind({});

const BreadcrumbColors: StoryFn<BreadcrumbProps> = () => {
return (
<Box display="flex" gap="spacing.5" flexDirection="column">
<Box padding="spacing.4">
<Breadcrumb size="medium" color="primary">
<BreadcrumbItem accessibilityLabel="Home" icon={HomeIcon} href="/home" />
<BreadcrumbItem href="/dashboard">Dashboard</BreadcrumbItem>
<BreadcrumbItem isCurrentPage href="/settlements">
Settlements
</BreadcrumbItem>
</Breadcrumb>
</Box>
<Box padding="spacing.4">
<Breadcrumb size="medium" color="neutral">
<BreadcrumbItem icon={HomeIcon} href="/home" />
<BreadcrumbItem href="/dashboard">Dashboard</BreadcrumbItem>
<BreadcrumbItem isCurrentPage href="/settlements">
Settlements
</BreadcrumbItem>
</Breadcrumb>
</Box>
<Box padding="spacing.4" backgroundColor="surface.background.cloud.intense">
<Breadcrumb size="medium" color="white">
<BreadcrumbItem icon={HomeIcon} href="/home" />
<BreadcrumbItem href="/dashboard">Dashboard</BreadcrumbItem>
<BreadcrumbItem isCurrentPage href="/settlements">
Settlements
</BreadcrumbItem>
</Breadcrumb>
</Box>
</Box>
);
};

BreadcrumbColors.storyName = 'Colors';
export const Colors = BreadcrumbColors.bind({});

const BreadcrumbWrapMultilineTemplate: StoryFn<BreadcrumbProps> = () => {
return (
<Box display="flex" gap="spacing.5" flexDirection="column">
<Box padding="spacing.6" width="350px" backgroundColor="surface.background.gray.intense">
<Breadcrumb size="medium" color="primary">
<BreadcrumbItem accessibilityLabel="Home" icon={HomeIcon} href="/home" />
<BreadcrumbItem href="/item1">Item 1</BreadcrumbItem>
<BreadcrumbItem href="/item2">Item 2</BreadcrumbItem>
<BreadcrumbItem href="/item3">Item 3</BreadcrumbItem>
<BreadcrumbItem href="/item4">Item 4</BreadcrumbItem>
<BreadcrumbItem href="/item5">Item 5</BreadcrumbItem>
<BreadcrumbItem href="/item6">Item 6</BreadcrumbItem>
<BreadcrumbItem href="/item7">Item 7</BreadcrumbItem>
</Breadcrumb>
</Box>
</Box>
);
};

BreadcrumbWrapMultilineTemplate.storyName = 'BreadcrumbWrapMultiline';
export const BreadcrumbWrapMultiline = BreadcrumbWrapMultilineTemplate.bind({});

const urls = {
home: '/home',
dashboard: '/dashboard',
settlements: '/settlements',
payment: '/payment-gateway',
};

type BreadcrumbLinkProps = Omit<LinkProps, 'children'> &
BreadcrumbItemProps & { navigate: () => void };

const BreadcrumbLink = ({ onClick, ...props }: BreadcrumbLinkProps): React.ReactElement => {
const location = useLocation();

const isCurrentPage =
matchPath(location.pathname, {
path: props.href,
exact: true,
}) !== null;

return (
<BreadcrumbItem
isCurrentPage={isCurrentPage}
onClick={(e) => {
onClick?.(e as never);
e.preventDefault();
props.navigate();
}}
{...props}
/>
);
};

const BreadcrumbNavLink = (
props: Omit<BreadcrumbItemProps, 'href'> & LinkProps,
): React.ReactElement => {
return <RouterLink component={BreadcrumbLink} {...props} />;
};

const Page1 = (): React.ReactElement => {
return (
<Breadcrumb size="medium" color="primary">
<BreadcrumbNavLink icon={HomeIcon} to={urls.home}>
Home
</BreadcrumbNavLink>
</Breadcrumb>
);
};

const Page2 = (): React.ReactElement => {
return (
<Breadcrumb size="medium" color="primary">
<BreadcrumbNavLink icon={HomeIcon} to={urls.home}>
Home
</BreadcrumbNavLink>
<BreadcrumbNavLink icon={HomeIcon} to={urls.dashboard}>
Dashboard
</BreadcrumbNavLink>
</Breadcrumb>
);
};

const Page3 = (): React.ReactElement => {
return (
<Breadcrumb size="medium" color="primary">
<BreadcrumbNavLink icon={HomeIcon} to={urls.home}>
Home
</BreadcrumbNavLink>
<BreadcrumbNavLink icon={HomeIcon} to={urls.dashboard}>
Dashboard
</BreadcrumbNavLink>
<BreadcrumbNavLink icon={HomeIcon} to={urls.settlements}>
Settlements
</BreadcrumbNavLink>
</Breadcrumb>
);
};

const Page4 = (): React.ReactElement => {
return (
<Breadcrumb size="medium" color="primary">
<BreadcrumbNavLink icon={HomeIcon} to={urls.home}>
Home
</BreadcrumbNavLink>
<BreadcrumbNavLink icon={HomeIcon} to={urls.dashboard}>
Dashboard
</BreadcrumbNavLink>
<BreadcrumbNavLink icon={HomeIcon} to={urls.settlements}>
Settlements
</BreadcrumbNavLink>
<BreadcrumbNavLink icon={HomeIcon} to={urls.payment}>
Payment Gateway
</BreadcrumbNavLink>
</Breadcrumb>
);
};

const CommonPage = (): React.ReactElement => {
const location = useLocation();
return (
<Card marginTop="spacing.5">
<CardHeader>
<CardHeaderLeading title={`Welcome to ${location.pathname}`} />
</CardHeader>
<CardBody>
<Box marginBottom="spacing.8">
<Text>
You can use <Code size="medium">Breadcrumbs</Code> with{' '}
<Code size="medium">react-router</Code> to create a breadcrumb trail for your app.
</Text>
<Text weight="semibold" as="span" marginTop="spacing.4">
Open this <Link href="#">Codesandbox link</Link> to see the source code.
</Text>
</Box>

<Text marginBottom="spacing.3">Trigger URL Change:</Text>
<Box display="flex" flexDirection="column" gap="spacing.2">
<RouterLink to={urls.home}>Home</RouterLink>
<RouterLink to={urls.dashboard}>Dashboard</RouterLink>
<RouterLink to={urls.settlements}>Settlements</RouterLink>
<RouterLink to={urls.payment}>Payment Gateway</RouterLink>
</Box>
</CardBody>
</Card>
);
};

const RouterExample = (): React.ReactElement => {
return (
<Box>
<Route path={urls.home} component={Page1} />
<Route path={urls.dashboard} component={Page2} />
<Route path={urls.settlements} component={Page3} />
<Route path={urls.payment} component={Page4} />
<Route path="/" component={CommonPage} />
</Box>
);
};

const ReactRouterUsageTemplate: StoryFn<BreadcrumbProps> = () => {
return <RouterExample />;
};

ReactRouterUsageTemplate.storyName = 'ReactRouterUsage';
export const ReactRouterUsage = ReactRouterUsageTemplate.bind({});
Loading
Loading