diff --git a/.changeset/five-cows-cheer.md b/.changeset/five-cows-cheer.md new file mode 100644 index 00000000000..d4461a474e6 --- /dev/null +++ b/.changeset/five-cows-cheer.md @@ -0,0 +1,5 @@ +--- +"@razorpay/blade": minor +--- + +feat(blade): breadcrumb web implementation diff --git a/packages/blade/src/components/Breadcrumb/Breadcrumb.native.tsx b/packages/blade/src/components/Breadcrumb/Breadcrumb.native.tsx new file mode 100644 index 00000000000..cd0aba1d8ce --- /dev/null +++ b/packages/blade/src/components/Breadcrumb/Breadcrumb.native.tsx @@ -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 }; diff --git a/packages/blade/src/components/Breadcrumb/Breadcrumb.stories.tsx b/packages/blade/src/components/Breadcrumb/Breadcrumb.stories.tsx new file mode 100644 index 00000000000..6fabac8d455 --- /dev/null +++ b/packages/blade/src/components/Breadcrumb/Breadcrumb.stories.tsx @@ -0,0 +1,330 @@ +/* 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 ( + + Usage + + {` + import { Box, Breadcrumb, BreadcrumbItem, HomeIcon } from '@razorpay/blade/components'; + + function App(): React.ReactElement { + return ( + + + + Dashboard + + Settlements + + + + ); + } + + export default App; + `} + + + ); +}; + +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; + +const BasicToastTemplate: StoryFn = (props) => { + return ( + + + + Dashboard + + Settlements + + + + ); +}; + +BasicToastTemplate.storyName = 'Basic'; +export const Basic = BasicToastTemplate.bind({}); + +const BreadcrumbSizes: StoryFn = () => { + return ( + + + + Dashboard + + Settlements + + + + + Dashboard + + Settlements + + + + + Dashboard + + Settlements + + + + ); +}; + +BreadcrumbSizes.storyName = 'Sizes'; +export const Sizes = BreadcrumbSizes.bind({}); + +const BreadcrumbColors: StoryFn = () => { + return ( + + + + + Dashboard + + Settlements + + + + + + + Dashboard + + Settlements + + + + + + + Dashboard + + Settlements + + + + + ); +}; + +BreadcrumbColors.storyName = 'Colors'; +export const Colors = BreadcrumbColors.bind({}); + +const BreadcrumbWrapMultilineTemplate: StoryFn = () => { + return ( + + + + + Item 1 + Item 2 + Item 3 + Item 4 + Item 5 + Item 6 + Item 7 + + + + ); +}; + +BreadcrumbWrapMultilineTemplate.storyName = 'BreadcrumbWrapMultiline'; +export const BreadcrumbWrapMultiline = BreadcrumbWrapMultilineTemplate.bind({}); + +const urls = { + home: '/home', + products: '/products', + payments: '/payments', + intPayments: '/international-payments', + acceptIntPayments: '/accepts-international-payments', +}; + +type BreadcrumbLinkProps = Omit & + 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 ( + { + onClick?.(e as never); + e.preventDefault(); + props.navigate(); + }} + {...props} + /> + ); +}; + +const BreadcrumbNavLink = ( + props: Omit & LinkProps, +): React.ReactElement => { + return ; +}; + +const Page1 = (): React.ReactElement => { + return ( + + + + ); +}; + +const Page2 = (): React.ReactElement => { + return ( + + + Products + + ); +}; + +const Page3 = (): React.ReactElement => { + return ( + + + Products + Payments + + ); +}; + +const Page4 = (): React.ReactElement => { + return ( + + + Products + Payments + International Payments + + ); +}; + +const Page5 = (): React.ReactElement => { + return ( + + + Products + Payments + International Payments + + Accept International Payments + + + ); +}; + +const CommonPage = (): React.ReactElement => { + const location = useLocation(); + return ( + + + + + + + + You can use Breadcrumbs with{' '} + react-router to create a breadcrumb trail for your app. + + + Open this{' '} + Stackblitz link to + see the source code. + + + + Trigger URL Change: + + Home + Products + Payments + International Payments + + Accept International Payments + + + + + ); +}; + +const RouterExample = (): React.ReactElement => { + return ( + + + + + + + + + ); +}; + +const ReactRouterUsageTemplate: StoryFn = () => { + return ; +}; + +ReactRouterUsageTemplate.storyName = 'ReactRouterUsage'; +export const ReactRouterUsage = ReactRouterUsageTemplate.bind({}); diff --git a/packages/blade/src/components/Breadcrumb/Breadcrumb.web.tsx b/packages/blade/src/components/Breadcrumb/Breadcrumb.web.tsx new file mode 100644 index 00000000000..4e3fd89c65f --- /dev/null +++ b/packages/blade/src/components/Breadcrumb/Breadcrumb.web.tsx @@ -0,0 +1,88 @@ +/* eslint-disable react-native-a11y/has-valid-accessibility-role */ +import React from 'react'; +import type { BreadcrumbProps } from './types'; +import { BreadcrumbContext } from './BreadcrumbContext'; +import BaseBox from '~components/Box/BaseBox'; +import { Text } from '~components/Typography'; +import { makeAccessible } from '~utils/makeAccessible'; +import { getStyledProps } from '~components/Box/styledProps'; +import { metaAttribute, MetaConstants } from '~utils/metaAttribute'; + +const Separator = ({ + size, + color, +}: Pick): React.ReactElement => { + return ( + + / + + ); +}; + +const listStyleNone = { listStyle: 'none' }; + +const Breadcrumb = ({ + size = 'medium', + color = 'primary', + showLastSeparator = false, + accessibilityLabel = 'Breadcrumb', + children, + ...styledProps +}: BreadcrumbProps): React.ReactElement => { + const contextValue = React.useMemo(() => ({ size, color }), [size, color]); + + return ( + + + + {React.Children.map(children, (child, index) => { + const ariaCurrent = (child as React.ReactElement)?.props?.isCurrentPage + ? 'page' + : undefined; + + return ( + + {child} + + {index !== React.Children.count(children) - 1 && } + {index === React.Children.count(children) - 1 && showLastSeparator && ( + + )} + + + ); + })} + + + + ); +}; + +export { Breadcrumb }; diff --git a/packages/blade/src/components/Breadcrumb/BreadcrumbContext.tsx b/packages/blade/src/components/Breadcrumb/BreadcrumbContext.tsx new file mode 100644 index 00000000000..ade158efe70 --- /dev/null +++ b/packages/blade/src/components/Breadcrumb/BreadcrumbContext.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import type { BreadcrumbProps } from './types'; + +type BreadcrumbContextValue = Pick; +const BreadcrumbContext = React.createContext({ + size: 'medium', + color: 'primary', +}); + +export { BreadcrumbContext }; diff --git a/packages/blade/src/components/Breadcrumb/BreadcrumbItem.native.tsx b/packages/blade/src/components/Breadcrumb/BreadcrumbItem.native.tsx new file mode 100644 index 00000000000..14ea0fae26f --- /dev/null +++ b/packages/blade/src/components/Breadcrumb/BreadcrumbItem.native.tsx @@ -0,0 +1,13 @@ +import type { BreadcrumbItemProps } from './types'; +import { throwBladeError } from '~utils/logger'; + +const BreadcrumbItem = (_: BreadcrumbItemProps): React.ReactElement => { + throwBladeError({ + message: 'BreadcrumbItem is not yet implemented for native', + moduleName: 'BreadcrumbItem', + }); + + return <>; +}; + +export { BreadcrumbItem }; diff --git a/packages/blade/src/components/Breadcrumb/BreadcrumbItem.web.tsx b/packages/blade/src/components/Breadcrumb/BreadcrumbItem.web.tsx new file mode 100644 index 00000000000..5df63cbf85b --- /dev/null +++ b/packages/blade/src/components/Breadcrumb/BreadcrumbItem.web.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import type { BreadcrumbItemProps } from './types'; +import { BreadcrumbContext } from './BreadcrumbContext'; +import { BaseLink } from '~components/Link/BaseLink'; +import { Text } from '~components/Typography'; +import BaseBox from '~components/Box/BaseBox'; +import { opacity } from '~tokens/global'; + +const BreadcrumbItem = ({ + children, + href, + icon: Icon, + isCurrentPage, + onClick, + accessibilityLabel, +}: BreadcrumbItemProps): React.ReactElement => { + const { color, size } = React.useContext(BreadcrumbContext); + + if (isCurrentPage) { + const isWhite = color === 'white'; + return ( + + {Icon && ( + + )} + + {children} + + + ); + } + + return ( + + {children ?? ''} + + ); +}; + +export { BreadcrumbItem }; diff --git a/packages/blade/src/components/Breadcrumb/_KitchenSink.Breadcrumb.stories.tsx b/packages/blade/src/components/Breadcrumb/_KitchenSink.Breadcrumb.stories.tsx new file mode 100644 index 00000000000..9d4a4bbddc5 --- /dev/null +++ b/packages/blade/src/components/Breadcrumb/_KitchenSink.Breadcrumb.stories.tsx @@ -0,0 +1,31 @@ +import { composeStories } from '@storybook/react'; +import * as BreadcrumbStories from './Breadcrumb.stories'; +import { Box } from '~components/Box'; +import { Heading } from '~components/Typography'; + +const allStories = Object.values(composeStories(BreadcrumbStories)); + +export const Breadcrumb = (): JSX.Element => { + return ( + + {allStories.map((Story) => { + return ( + <> + {Story.storyName} + + + ); + })} + + ); +}; + +export default { + title: 'Components/KitchenSink/Breadcrumb', + component: Breadcrumb, + parameters: { + // enable Chromatic's snapshotting only for kitchensink + chromatic: { disableSnapshot: false }, + options: { showPanel: false }, + }, +}; diff --git a/packages/blade/src/components/Breadcrumb/__tests__/Breadcrumb.ssr.test.tsx b/packages/blade/src/components/Breadcrumb/__tests__/Breadcrumb.ssr.test.tsx new file mode 100644 index 00000000000..5712f9871c7 --- /dev/null +++ b/packages/blade/src/components/Breadcrumb/__tests__/Breadcrumb.ssr.test.tsx @@ -0,0 +1,20 @@ +import { Breadcrumb, BreadcrumbItem } from '../'; +import { HomeIcon } from '~components/Icons'; +import renderWithSSR from '~utils/testing/renderWithSSR.web'; + +describe('', () => { + it('should render breadcrumb', () => { + const { container } = renderWithSSR( + + + Home + + About + + Contact + + , + ); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/packages/blade/src/components/Breadcrumb/__tests__/Breadcrumb.web.test.tsx b/packages/blade/src/components/Breadcrumb/__tests__/Breadcrumb.web.test.tsx new file mode 100644 index 00000000000..6c1e270c08e --- /dev/null +++ b/packages/blade/src/components/Breadcrumb/__tests__/Breadcrumb.web.test.tsx @@ -0,0 +1,71 @@ +import userEvent from '@testing-library/user-event'; +import { Breadcrumb, BreadcrumbItem } from '../'; +import renderWithTheme from '~utils/testing/renderWithTheme.web'; +import assertAccessible from '~utils/testing/assertAccessible.web'; + +describe('', () => { + it('should render', () => { + const { container } = renderWithTheme( + + Home + About + Contact + , + ); + + expect(container).toMatchSnapshot(); + }); + + test('current item should have aria-current', () => { + const { container, getByText } = renderWithTheme( + + Home + About + + Contact + + , + ); + + expect(container).toMatchSnapshot(); + expect(getByText('Contact').closest('li')).toHaveAttribute('aria-current', 'page'); + }); + + test('should work with showLastSeparator', () => { + const { container } = renderWithTheme( + + Home + About + Contact + , + ); + + expect(container).toMatchSnapshot(); + }); + + test('should work with onClick', async () => { + const onClick = jest.fn(); + const { getByText } = renderWithTheme( + + + Home + + , + ); + const link = getByText('Home'); + await userEvent.click(link); + expect(onClick).toHaveBeenCalledTimes(1); + }); + + it('should pass a11y render', async () => { + const { container } = renderWithTheme( + + Home + About + Contact + , + ); + + await assertAccessible(container); + }); +}); diff --git a/packages/blade/src/components/Breadcrumb/__tests__/__snapshots__/Breadcrumb.ssr.test.tsx.snap b/packages/blade/src/components/Breadcrumb/__tests__/__snapshots__/Breadcrumb.ssr.test.tsx.snap new file mode 100644 index 00000000000..b9896bdeaa2 --- /dev/null +++ b/packages/blade/src/components/Breadcrumb/__tests__/__snapshots__/Breadcrumb.ssr.test.tsx.snap @@ -0,0 +1,293 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render breadcrumb 1`] = `"
"`; + +exports[` should render breadcrumb 2`] = ` +.c0.c0.c0.c0.c0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 0px; + margin: 0px; + gap: 4px; +} + +.c1.c1.c1.c1.c1 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + gap: 4px; +} + +.c3.c3.c3.c3.c3 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + opacity: 1; +} + +.c4.c4.c4.c4.c4 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding-right: 4px; +} + +.c5.c5.c5.c5.c5 { + color: hsla(227,71%,51%,1); + font-family: "Inter",-apple-system,BlinkMacSystemFont,San Francisco,Segoe UI,Roboto,Helvetica Neue,sans-serif; + font-size: 1rem; + font-weight: 500; + font-style: normal; + -webkit-text-decoration-line: none; + text-decoration-line: none; + line-height: 1.5rem; + -webkit-letter-spacing: 0px; + -moz-letter-spacing: 0px; + -ms-letter-spacing: 0px; + letter-spacing: 0px; + text-align: center; + margin: 0; + padding: 0; +} + +.c6.c6.c6.c6.c6 { + color: hsla(211,22%,56%,1); + font-family: "Inter",-apple-system,BlinkMacSystemFont,San Francisco,Segoe UI,Roboto,Helvetica Neue,sans-serif; + font-size: 1rem; + font-weight: 500; + font-style: normal; + -webkit-text-decoration-line: none; + text-decoration-line: none; + line-height: 1.5rem; + -webkit-letter-spacing: 0px; + -moz-letter-spacing: 0px; + -ms-letter-spacing: 0px; + letter-spacing: 0px; + margin: 0; + padding: 0; +} + +.c7.c7.c7.c7.c7 { + color: hsla(212,39%,16%,1); + font-family: "Inter",-apple-system,BlinkMacSystemFont,San Francisco,Segoe UI,Roboto,Helvetica Neue,sans-serif; + font-size: 1rem; + font-weight: 500; + font-style: normal; + -webkit-text-decoration-line: none; + text-decoration-line: none; + line-height: 1.5rem; + -webkit-letter-spacing: 0px; + -moz-letter-spacing: 0px; + -ms-letter-spacing: 0px; + letter-spacing: 0px; + margin: 0; + padding: 0; +} + +.c2.c2.c2.c2.c2 { + padding: 0; + background-color: transparent; + outline: none; + -webkit-text-decoration: none; + text-decoration: none; + border: none; + cursor: pointer; + display: inline-block; + border-radius: 2px; + -webkit-transition-property: box-shadow; + transition-property: box-shadow; + -webkit-transition-timing-function: cubic-bezier(0.3,0,0.2,1); + transition-timing-function: cubic-bezier(0.3,0,0.2,1); + -webkit-transition-duration: 70ms; + transition-duration: 70ms; +} + +.c2.c2.c2.c2.c2 .content-container { + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + border-radius: 2px; +} + +.c2.c2.c2.c2.c2:focus-visible .content-container { + box-shadow: 0px 0px 0px 4px hsla(227,100%,59%,0.09); +} + +.c2.c2.c2.c2.c2 * { + -webkit-transition-property: color,fill; + transition-property: color,fill; + -webkit-transition-timing-function: cubic-bezier(0.3,0,0.2,1); + transition-timing-function: cubic-bezier(0.3,0,0.2,1); + -webkit-transition-duration: 70ms; + transition-duration: 70ms; +} + +
+ +
+`; diff --git a/packages/blade/src/components/Breadcrumb/__tests__/__snapshots__/Breadcrumb.web.test.tsx.snap b/packages/blade/src/components/Breadcrumb/__tests__/__snapshots__/Breadcrumb.web.test.tsx.snap new file mode 100644 index 00000000000..982404e49b6 --- /dev/null +++ b/packages/blade/src/components/Breadcrumb/__tests__/__snapshots__/Breadcrumb.web.test.tsx.snap @@ -0,0 +1,731 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` current item should have aria-current 1`] = ` +.c0.c0.c0.c0.c0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 0px; + margin: 0px; + gap: 4px; +} + +.c1.c1.c1.c1.c1 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + gap: 4px; +} + +.c3.c3.c3.c3.c3 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + opacity: 1; +} + +.c4.c4.c4.c4.c4 { + color: hsla(227,71%,51%,1); + font-family: "Inter",-apple-system,BlinkMacSystemFont,San Francisco,Segoe UI,Roboto,Helvetica Neue,sans-serif; + font-size: 0.875rem; + font-weight: 500; + font-style: normal; + -webkit-text-decoration-line: none; + text-decoration-line: none; + line-height: 1.25rem; + -webkit-letter-spacing: 0px; + -moz-letter-spacing: 0px; + -ms-letter-spacing: 0px; + letter-spacing: 0px; + text-align: center; + margin: 0; + padding: 0; +} + +.c5.c5.c5.c5.c5 { + color: hsla(211,22%,56%,1); + font-family: "Inter",-apple-system,BlinkMacSystemFont,San Francisco,Segoe UI,Roboto,Helvetica Neue,sans-serif; + font-size: 0.875rem; + font-weight: 500; + font-style: normal; + -webkit-text-decoration-line: none; + text-decoration-line: none; + line-height: 1.25rem; + -webkit-letter-spacing: 0px; + -moz-letter-spacing: 0px; + -ms-letter-spacing: 0px; + letter-spacing: 0px; + margin: 0; + padding: 0; +} + +.c6.c6.c6.c6.c6 { + color: hsla(212,39%,16%,1); + font-family: "Inter",-apple-system,BlinkMacSystemFont,San Francisco,Segoe UI,Roboto,Helvetica Neue,sans-serif; + font-size: 0.875rem; + font-weight: 500; + font-style: normal; + -webkit-text-decoration-line: none; + text-decoration-line: none; + line-height: 1.25rem; + -webkit-letter-spacing: 0px; + -moz-letter-spacing: 0px; + -ms-letter-spacing: 0px; + letter-spacing: 0px; + margin: 0; + padding: 0; +} + +.c2.c2.c2.c2.c2 { + padding: 0; + background-color: transparent; + outline: none; + -webkit-text-decoration: none; + text-decoration: none; + border: none; + cursor: pointer; + display: inline-block; + border-radius: 2px; + -webkit-transition-property: box-shadow; + transition-property: box-shadow; + -webkit-transition-timing-function: cubic-bezier(0.3,0,0.2,1); + transition-timing-function: cubic-bezier(0.3,0,0.2,1); + -webkit-transition-duration: 70ms; + transition-duration: 70ms; +} + +.c2.c2.c2.c2.c2 .content-container { + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + border-radius: 2px; +} + +.c2.c2.c2.c2.c2:focus-visible .content-container { + box-shadow: 0px 0px 0px 4px hsla(227,100%,59%,0.09); +} + +.c2.c2.c2.c2.c2 * { + -webkit-transition-property: color,fill; + transition-property: color,fill; + -webkit-transition-timing-function: cubic-bezier(0.3,0,0.2,1); + transition-timing-function: cubic-bezier(0.3,0,0.2,1); + -webkit-transition-duration: 70ms; + transition-duration: 70ms; +} + +
+ +
+`; + +exports[` should render 1`] = ` +.c0.c0.c0.c0.c0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 0px; + margin: 0px; + gap: 4px; +} + +.c1.c1.c1.c1.c1 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + gap: 4px; +} + +.c3.c3.c3.c3.c3 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + opacity: 1; +} + +.c4.c4.c4.c4.c4 { + color: hsla(227,71%,51%,1); + font-family: "Inter",-apple-system,BlinkMacSystemFont,San Francisco,Segoe UI,Roboto,Helvetica Neue,sans-serif; + font-size: 0.875rem; + font-weight: 500; + font-style: normal; + -webkit-text-decoration-line: none; + text-decoration-line: none; + line-height: 1.25rem; + -webkit-letter-spacing: 0px; + -moz-letter-spacing: 0px; + -ms-letter-spacing: 0px; + letter-spacing: 0px; + text-align: center; + margin: 0; + padding: 0; +} + +.c5.c5.c5.c5.c5 { + color: hsla(211,22%,56%,1); + font-family: "Inter",-apple-system,BlinkMacSystemFont,San Francisco,Segoe UI,Roboto,Helvetica Neue,sans-serif; + font-size: 0.875rem; + font-weight: 500; + font-style: normal; + -webkit-text-decoration-line: none; + text-decoration-line: none; + line-height: 1.25rem; + -webkit-letter-spacing: 0px; + -moz-letter-spacing: 0px; + -ms-letter-spacing: 0px; + letter-spacing: 0px; + margin: 0; + padding: 0; +} + +.c2.c2.c2.c2.c2 { + padding: 0; + background-color: transparent; + outline: none; + -webkit-text-decoration: none; + text-decoration: none; + border: none; + cursor: pointer; + display: inline-block; + border-radius: 2px; + -webkit-transition-property: box-shadow; + transition-property: box-shadow; + -webkit-transition-timing-function: cubic-bezier(0.3,0,0.2,1); + transition-timing-function: cubic-bezier(0.3,0,0.2,1); + -webkit-transition-duration: 70ms; + transition-duration: 70ms; +} + +.c2.c2.c2.c2.c2 .content-container { + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + border-radius: 2px; +} + +.c2.c2.c2.c2.c2:focus-visible .content-container { + box-shadow: 0px 0px 0px 4px hsla(227,100%,59%,0.09); +} + +.c2.c2.c2.c2.c2 * { + -webkit-transition-property: color,fill; + transition-property: color,fill; + -webkit-transition-timing-function: cubic-bezier(0.3,0,0.2,1); + transition-timing-function: cubic-bezier(0.3,0,0.2,1); + -webkit-transition-duration: 70ms; + transition-duration: 70ms; +} + +
+ +
+`; + +exports[` should work with showLastSeparator 1`] = ` +.c0.c0.c0.c0.c0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 0px; + margin: 0px; + gap: 4px; +} + +.c1.c1.c1.c1.c1 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + gap: 4px; +} + +.c3.c3.c3.c3.c3 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + opacity: 1; +} + +.c4.c4.c4.c4.c4 { + color: hsla(227,71%,51%,1); + font-family: "Inter",-apple-system,BlinkMacSystemFont,San Francisco,Segoe UI,Roboto,Helvetica Neue,sans-serif; + font-size: 0.875rem; + font-weight: 500; + font-style: normal; + -webkit-text-decoration-line: none; + text-decoration-line: none; + line-height: 1.25rem; + -webkit-letter-spacing: 0px; + -moz-letter-spacing: 0px; + -ms-letter-spacing: 0px; + letter-spacing: 0px; + text-align: center; + margin: 0; + padding: 0; +} + +.c5.c5.c5.c5.c5 { + color: hsla(211,22%,56%,1); + font-family: "Inter",-apple-system,BlinkMacSystemFont,San Francisco,Segoe UI,Roboto,Helvetica Neue,sans-serif; + font-size: 0.875rem; + font-weight: 500; + font-style: normal; + -webkit-text-decoration-line: none; + text-decoration-line: none; + line-height: 1.25rem; + -webkit-letter-spacing: 0px; + -moz-letter-spacing: 0px; + -ms-letter-spacing: 0px; + letter-spacing: 0px; + margin: 0; + padding: 0; +} + +.c2.c2.c2.c2.c2 { + padding: 0; + background-color: transparent; + outline: none; + -webkit-text-decoration: none; + text-decoration: none; + border: none; + cursor: pointer; + display: inline-block; + border-radius: 2px; + -webkit-transition-property: box-shadow; + transition-property: box-shadow; + -webkit-transition-timing-function: cubic-bezier(0.3,0,0.2,1); + transition-timing-function: cubic-bezier(0.3,0,0.2,1); + -webkit-transition-duration: 70ms; + transition-duration: 70ms; +} + +.c2.c2.c2.c2.c2 .content-container { + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + border-radius: 2px; +} + +.c2.c2.c2.c2.c2:focus-visible .content-container { + box-shadow: 0px 0px 0px 4px hsla(227,100%,59%,0.09); +} + +.c2.c2.c2.c2.c2 * { + -webkit-transition-property: color,fill; + transition-property: color,fill; + -webkit-transition-timing-function: cubic-bezier(0.3,0,0.2,1); + transition-timing-function: cubic-bezier(0.3,0,0.2,1); + -webkit-transition-duration: 70ms; + transition-duration: 70ms; +} + +
+ +
+`; diff --git a/packages/blade/src/components/Breadcrumb/index.ts b/packages/blade/src/components/Breadcrumb/index.ts new file mode 100644 index 00000000000..c8f5f169de6 --- /dev/null +++ b/packages/blade/src/components/Breadcrumb/index.ts @@ -0,0 +1,3 @@ +export * from './Breadcrumb'; +export * from './BreadcrumbItem'; +export * from './types'; diff --git a/packages/blade/src/components/Breadcrumb/types.ts b/packages/blade/src/components/Breadcrumb/types.ts new file mode 100644 index 00000000000..7bcb57a9f0f --- /dev/null +++ b/packages/blade/src/components/Breadcrumb/types.ts @@ -0,0 +1,65 @@ +import type { StyledPropsBlade } from '~components/Box/styledProps'; +import type { IconComponent } from '~components/Icons'; +import type { LinkProps } from '~components/Link'; +import type { StringChildrenType } from '~utils/types'; + +type BreadcrumbProps = StyledPropsBlade & { + /** + * Size of the Breadcrumb + * + * @default medium + */ + size?: 'small' | 'medium' | 'large'; + /** + * Color of the Breadcrumb + * + * @default neutral + */ + color?: 'neutral' | 'primary' | 'white'; + /** + * Content of the Breadcrumb, accepts BreadcrumbItem + */ + children: React.ReactNode; + /** + * Whether to show the last separator + */ + showLastSeparator?: boolean; + /** + * aria-label for breadcrumb + */ + accessibilityLabel?: string; +}; + +type BreadcrumbItemProps = { + /** + * Href of the BreadcrumbItem + */ + href: string; + /** + * Function to be called on click of the BreadcrumbItem, + * + * This can be used to integrate with routing libraries like `react-router-dom` + */ + onClick?: LinkProps['onClick']; + /** + * Whether the BreadcrumbItem is the current page, + * Sets the aria-current attribute to `page` + * + * @default false + */ + isCurrentPage?: boolean; + /** + * Content of the BreadcrumbItem + */ + children?: StringChildrenType; + /** + * Icon to be shown before the BreadcrumbItem + */ + icon?: IconComponent; + /** + * Accessibility label for the BreadcrumbItem, can be used in icon only variant + */ + accessibilityLabel?: string; +}; + +export type { BreadcrumbProps, BreadcrumbItemProps }; diff --git a/packages/blade/src/components/Link/BaseLink/BaseLink.tsx b/packages/blade/src/components/Link/BaseLink/BaseLink.tsx index 9c67be12828..b9a0a6547eb 100644 --- a/packages/blade/src/components/Link/BaseLink/BaseLink.tsx +++ b/packages/blade/src/components/Link/BaseLink/BaseLink.tsx @@ -78,6 +78,7 @@ type BaseLinkCommonProps = { * The title of the link which is displayed as a tooltip. This is a web only prop and has no effect on react-native. */ htmlTitle?: string; + opacity?: number; } & TestID & StyledPropsBlade & Omit; @@ -269,6 +270,7 @@ const _BaseLink: React.ForwardRefRenderFunction target, rel, color = 'primary', + opacity, accessibilityProps, // @ts-expect-error avoiding exposing to public className, @@ -394,6 +396,7 @@ const _BaseLink: React.ForwardRefRenderFunction flexDirection="row" className="content-container" alignItems="center" + opacity={opacity} > {Icon && iconPosition == 'left' ? ( diff --git a/packages/blade/src/components/index.ts b/packages/blade/src/components/index.ts index 6add468277f..a84600bf861 100644 --- a/packages/blade/src/components/index.ts +++ b/packages/blade/src/components/index.ts @@ -5,6 +5,7 @@ export * from './Amount'; export * from './Badge'; export * from './BladeProvider'; export * from './BottomSheet'; +export * from './Breadcrumb'; export * from './Box'; export * from './Button'; export * from './Button/IconButton'; diff --git a/packages/blade/src/utils/metaAttribute/metaConstants.ts b/packages/blade/src/utils/metaAttribute/metaConstants.ts index a923bc74cca..e12f2ba7625 100644 --- a/packages/blade/src/utils/metaAttribute/metaConstants.ts +++ b/packages/blade/src/utils/metaAttribute/metaConstants.ts @@ -13,6 +13,8 @@ export const MetaConstants = { BaseBox: 'base-box', BaseText: 'base-text', Button: 'button', + Breadcrumb: 'breadcrumb', + BreadcrumbItem: 'breadcrumb-item', Carousel: 'carousel', Checkbox: 'checkbox', CheckboxGroup: 'checkbox-group',