From d4ba48e5ca85226f1068a949890d79d80da358ed Mon Sep 17 00:00:00 2001 From: Powerplex Date: Mon, 12 Aug 2024 15:48:32 +0200 Subject: [PATCH] feat(breadcrumb): new breadcrumb component boilerplate --- package-lock.json | 17 +++++ packages/components/breadcrumb/.npmignore | 2 + packages/components/breadcrumb/LICENSE.md | 21 ++++++ packages/components/breadcrumb/README.md | 13 ++++ packages/components/breadcrumb/package.json | 46 ++++++++++++ .../breadcrumb/src/Breadcrumb.doc.mdx | 54 ++++++++++++++ .../breadcrumb/src/Breadcrumb.stories.tsx | 30 ++++++++ .../breadcrumb/src/Breadcrumb.styles.ts | 0 .../breadcrumb/src/Breadcrumb.test.tsx | 70 +++++++++++++++++++ .../components/breadcrumb/src/Breadcrumb.tsx | 22 ++++++ .../breadcrumb/src/BreadcrumbCurrentPage.tsx | 27 +++++++ .../breadcrumb/src/BreadcrumbItem.tsx | 19 +++++ .../breadcrumb/src/BreadcrumbLink.tsx | 26 +++++++ .../breadcrumb/src/BreadcrumbSeparator.tsx | 29 ++++++++ packages/components/breadcrumb/src/index.ts | 25 +++++++ packages/components/breadcrumb/tsconfig.json | 4 ++ packages/components/breadcrumb/vite.config.ts | 6 ++ 17 files changed, 411 insertions(+) create mode 100644 packages/components/breadcrumb/.npmignore create mode 100644 packages/components/breadcrumb/LICENSE.md create mode 100644 packages/components/breadcrumb/README.md create mode 100644 packages/components/breadcrumb/package.json create mode 100644 packages/components/breadcrumb/src/Breadcrumb.doc.mdx create mode 100644 packages/components/breadcrumb/src/Breadcrumb.stories.tsx create mode 100644 packages/components/breadcrumb/src/Breadcrumb.styles.ts create mode 100644 packages/components/breadcrumb/src/Breadcrumb.test.tsx create mode 100644 packages/components/breadcrumb/src/Breadcrumb.tsx create mode 100644 packages/components/breadcrumb/src/BreadcrumbCurrentPage.tsx create mode 100644 packages/components/breadcrumb/src/BreadcrumbItem.tsx create mode 100644 packages/components/breadcrumb/src/BreadcrumbLink.tsx create mode 100644 packages/components/breadcrumb/src/BreadcrumbSeparator.tsx create mode 100644 packages/components/breadcrumb/src/index.ts create mode 100644 packages/components/breadcrumb/tsconfig.json create mode 100644 packages/components/breadcrumb/vite.config.ts diff --git a/package-lock.json b/package-lock.json index 285bd441a..752123eff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2330,6 +2330,7 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -9211,6 +9212,10 @@ "resolved": "packages/components/badge", "link": true }, + "node_modules/@spark-ui/breadcrumb": { + "resolved": "packages/components/breadcrumb", + "link": true + }, "node_modules/@spark-ui/button": { "resolved": "packages/components/button", "link": true @@ -36018,6 +36023,18 @@ "tailwindcss": "^3.0.0" } }, + "packages/components/breadcrumb": { + "name": "@spark-ui/breadcrumb", + "version": "0.0.0", + "license": "MIT", + "peerDependencies": { + "@spark-ui/tailwind-plugins": "latest", + "@spark-ui/theme-utils": "latest", + "react": "^18.0 || ^19.0", + "react-dom": "^18.0 || ^19.0", + "tailwindcss": "^3.0.0" + } + }, "packages/components/button": { "name": "@spark-ui/button", "version": "5.1.2", diff --git a/packages/components/breadcrumb/.npmignore b/packages/components/breadcrumb/.npmignore new file mode 100644 index 000000000..14144f5f7 --- /dev/null +++ b/packages/components/breadcrumb/.npmignore @@ -0,0 +1,2 @@ +src +**/*.stories.* diff --git a/packages/components/breadcrumb/LICENSE.md b/packages/components/breadcrumb/LICENSE.md new file mode 100644 index 000000000..62d374781 --- /dev/null +++ b/packages/components/breadcrumb/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Adevinta ASA. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/components/breadcrumb/README.md b/packages/components/breadcrumb/README.md new file mode 100644 index 000000000..51b796192 --- /dev/null +++ b/packages/components/breadcrumb/README.md @@ -0,0 +1,13 @@ +# Breadcrumb +> @spark-ui/breadcrumb + +[![storybook](https://img.shields.io/badge/storybook-black?logo=storybook)](https://sparkui.vercel.app/?path=/docs/components-breadcrumb--docs) +[![documentation](https://img.shields.io/badge/documentation-black?logo=googledocs)](https://sparkui-adv.vercel.app/docs/components/breadcrumb) +[![issue](https://img.shields.io/badge/report%20a%20bug-black?logo=openbugbounty&logoColor=red)](https://github.com/adevinta/spark/issues/new?&projects=4&template=bug-report.yml&assignees=&labels=Component,Component%3A%20breadcrumb) +[![npm](https://img.shields.io/npm/dt/%40spark-ui/breadcrumb?logo=npm&labelColor=black)](https://www.npmjs.com/package/@spark-ui/breadcrumb) + + +This package is part of the [`@spark-ui`](https://github.com/adevinta/spark) react-js user interface component library project. + +[![Issues open](https://img.shields.io/github/issues-search/adevinta/spark?query=is%3Aopen%20label%3A%22Component%3A%20breadcrumb%22&logo=openbugbounty&logoColor=red&label=issues%20open&color=red)](https://github.com/adevinta/spark/issues?q=is%3Aopen+label%3AComponent%3A%20breadcrumb) +[![NPM](https://img.shields.io/npm/l/%40spark-ui%2Fbreadcrumb)](https://github.com/adevinta/spark/blob/main/packages/components/breadcrumb/LICENSE.md) diff --git a/packages/components/breadcrumb/package.json b/packages/components/breadcrumb/package.json new file mode 100644 index 000000000..a8a68f6c0 --- /dev/null +++ b/packages/components/breadcrumb/package.json @@ -0,0 +1,46 @@ +{ + "name": "@spark-ui/breadcrumb", + "version": "0.0.0", + "description": "navigation pattern that helps users understand the hierarchy of a website", + "publishConfig": { + "access": "public" + }, + "keywords": [ + "@spark-ui", + "react", + "component", + "accessible", + "accessibility", + "wai-aria", + "aria", + "a11y", + "breadcrumb" + ], + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "scripts": { + "build": "vite build" + }, + "peerDependencies": { + "@spark-ui/tailwind-plugins": "latest", + "@spark-ui/theme-utils": "latest", + "react": "^18.0 || ^19.0", + "react-dom": "^18.0 || ^19.0", + "tailwindcss": "^3.0.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/adevinta/spark.git", + "directory": "packages/components/breadcrumb" + }, + "config": { + "title": "breadcrumb", + "category": "components" + }, + "bugs": { + "url": "https://github.com/adevinta/spark/issues?q=is%3Aopen+label%3A%22Component%3A+breadcrumb%22" + }, + "homepage": "https://sparkui.vercel.app", + "license": "MIT" +} diff --git a/packages/components/breadcrumb/src/Breadcrumb.doc.mdx b/packages/components/breadcrumb/src/Breadcrumb.doc.mdx new file mode 100644 index 000000000..b71113282 --- /dev/null +++ b/packages/components/breadcrumb/src/Breadcrumb.doc.mdx @@ -0,0 +1,54 @@ +import { Meta, Canvas } from '@storybook/addon-docs' +import { ArgTypes } from '@docs/helpers/ArgTypes' + +import { Breadcrumb } from '.' + +import * as stories from './Breadcrumb.stories' + + + +# Breadcrumb + +Navigation pattern that helps users understand the hierarchy of a website + +## Install + +```sh +npm install @spark-ui/breadcrumb +``` + +## Import + +```tsx +import { Breadcrumb } from '@spark-ui/breadcrumb' +``` + +## Props + + + +## Variants + + diff --git a/packages/components/breadcrumb/src/Breadcrumb.stories.tsx b/packages/components/breadcrumb/src/Breadcrumb.stories.tsx new file mode 100644 index 000000000..b48d4b392 --- /dev/null +++ b/packages/components/breadcrumb/src/Breadcrumb.stories.tsx @@ -0,0 +1,30 @@ +import { Meta, StoryFn } from '@storybook/react' + +import { Breadcrumb } from '.' + +const meta: Meta = { + title: 'Experimental/Breadcrumb', + component: Breadcrumb, +} + +export default meta + +export const Default: StoryFn = _args => ( + + + Home + + + + + + Components + + + + + + Breadcrumb + + +) diff --git a/packages/components/breadcrumb/src/Breadcrumb.styles.ts b/packages/components/breadcrumb/src/Breadcrumb.styles.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/components/breadcrumb/src/Breadcrumb.test.tsx b/packages/components/breadcrumb/src/Breadcrumb.test.tsx new file mode 100644 index 000000000..19b180b97 --- /dev/null +++ b/packages/components/breadcrumb/src/Breadcrumb.test.tsx @@ -0,0 +1,70 @@ +import { render, screen } from '@testing-library/react' +import { describe, expect, it } from 'vitest' + +import { Breadcrumb } from '.' + +describe('Breadcrumb', () => { + it('should render all parts', () => { + // Given a breadcrumb with links, separator and current page item. + render( + + + Home + + + + + + Components + + + + + + Breadcrumb + + + ) + + expect(screen.getByRole('navigation', { name: 'Breadcrumb' })).toBeInTheDocument() + + // Then Breadcrumb links should be rendered + expect(screen.getByRole('link', { name: 'Home' })).toHaveAttribute('href', '/') + expect(screen.getByRole('link', { name: 'Components' })).toHaveAttribute( + 'href', + '/docs/components' + ) + + // Then breadcrumb current page should be rendered + expect(screen.getByRole('link', { name: 'Breadcrumb' })).not.toHaveAttribute('href') + + // Then separators should be rendered (hidden) + expect(screen.getAllByRole('presentation', { hidden: true })).toHaveLength(2) + }) + + it('should render custom separators', () => { + // Given a breadcrumb with links, separator and current page item. + render( + + + Home + + + __ + + + Components + + + __ + + + Breadcrumb + + + ) + + // Then separators should be rendered with their custom content + expect(screen.getAllByText('__')).toHaveLength(2) + }) +}) diff --git a/packages/components/breadcrumb/src/Breadcrumb.tsx b/packages/components/breadcrumb/src/Breadcrumb.tsx new file mode 100644 index 000000000..287b27df5 --- /dev/null +++ b/packages/components/breadcrumb/src/Breadcrumb.tsx @@ -0,0 +1,22 @@ +import { cx } from 'class-variance-authority' +import { ComponentPropsWithoutRef, forwardRef } from 'react' + +export interface BreadcrumbProps extends ComponentPropsWithoutRef<'nav'> { + className?: string + ['aria-label']: string +} + +export const Breadcrumb = forwardRef( + ({ className, 'aria-label': ariaLabel, ...rest }, ref) => { + return ( + + ) + } +) + +Breadcrumb.displayName = 'Breadcrumb.Breadcrumb' diff --git a/packages/components/breadcrumb/src/BreadcrumbCurrentPage.tsx b/packages/components/breadcrumb/src/BreadcrumbCurrentPage.tsx new file mode 100644 index 000000000..019f04f07 --- /dev/null +++ b/packages/components/breadcrumb/src/BreadcrumbCurrentPage.tsx @@ -0,0 +1,27 @@ +import { Slot } from '@spark-ui/slot' +import { ComponentPropsWithoutRef, forwardRef } from 'react' + +export interface CurrentPageProps extends ComponentPropsWithoutRef<'span'> { + asChild?: boolean + className?: string +} + +export const CurrentPage = forwardRef( + ({ asChild = false, className, ...rest }, ref) => { + const Component = asChild ? Slot : 'span' + + return ( + + ) + } +) + +CurrentPage.displayName = 'Breadcrumb.CurrentPage' diff --git a/packages/components/breadcrumb/src/BreadcrumbItem.tsx b/packages/components/breadcrumb/src/BreadcrumbItem.tsx new file mode 100644 index 000000000..7aa794e16 --- /dev/null +++ b/packages/components/breadcrumb/src/BreadcrumbItem.tsx @@ -0,0 +1,19 @@ +import { cx } from 'class-variance-authority' +import { ComponentPropsWithoutRef, forwardRef } from 'react' + +export interface ItemProps extends ComponentPropsWithoutRef<'li'> { + className?: string +} + +export const Item = forwardRef(({ className, ...rest }, ref) => { + return ( +
  • + ) +}) + +Item.displayName = 'Breadcrumb.Item' diff --git a/packages/components/breadcrumb/src/BreadcrumbLink.tsx b/packages/components/breadcrumb/src/BreadcrumbLink.tsx new file mode 100644 index 000000000..dfc8d8b9c --- /dev/null +++ b/packages/components/breadcrumb/src/BreadcrumbLink.tsx @@ -0,0 +1,26 @@ +import { Slot } from '@spark-ui/slot' +import { ComponentPropsWithoutRef, forwardRef } from 'react' + +export interface LinkProps extends ComponentPropsWithoutRef<'a'> { + asChild?: boolean + className?: string + href?: string +} + +export const Link = forwardRef( + ({ asChild = false, className, href, ...rest }, ref) => { + const Component = asChild ? Slot : 'a' + + return ( + + ) + } +) + +Link.displayName = 'Breadcrumb.Link' diff --git a/packages/components/breadcrumb/src/BreadcrumbSeparator.tsx b/packages/components/breadcrumb/src/BreadcrumbSeparator.tsx new file mode 100644 index 000000000..21393bc7d --- /dev/null +++ b/packages/components/breadcrumb/src/BreadcrumbSeparator.tsx @@ -0,0 +1,29 @@ +import { Slot } from '@spark-ui/slot' +import { cx } from 'class-variance-authority' +import { ComponentPropsWithoutRef, forwardRef } from 'react' + +export interface SeparatorProps extends ComponentPropsWithoutRef<'li'> { + asChild?: boolean + className?: string +} + +export const Separator = forwardRef( + ({ asChild = false, className, children, ...rest }, ref) => { + const Component = asChild ? Slot : 'li' + + return ( + + {children ?? '/'} + + ) + } +) + +Separator.displayName = 'Breadcrumb.Separator' diff --git a/packages/components/breadcrumb/src/index.ts b/packages/components/breadcrumb/src/index.ts new file mode 100644 index 000000000..75eec786c --- /dev/null +++ b/packages/components/breadcrumb/src/index.ts @@ -0,0 +1,25 @@ +import type { FC } from 'react' + +import { Breadcrumb as Root, type BreadcrumbProps } from './Breadcrumb' +import { CurrentPage } from './BreadcrumbCurrentPage' +import { Item } from './BreadcrumbItem' +import { Link } from './BreadcrumbLink' +import { Separator } from './BreadcrumbSeparator' + +export const Breadcrumb: FC & { + Item: typeof Item + Link: typeof Link + CurrentPage: typeof CurrentPage + Separator: typeof Separator +} = Object.assign(Root, { + Item, + Link, + CurrentPage, + Separator, +}) + +Breadcrumb.displayName = 'Breadcrumb' +Item.displayName = 'Breadcrumb.Item' +Link.displayName = 'Breadcrumb.Link' +CurrentPage.displayName = 'Breadcrumb.CurrentPage' +Separator.displayName = 'Breadcrumb.Separator' diff --git a/packages/components/breadcrumb/tsconfig.json b/packages/components/breadcrumb/tsconfig.json new file mode 100644 index 000000000..18f4c0e16 --- /dev/null +++ b/packages/components/breadcrumb/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../tsconfig.json", + "include": ["src/**/*", "../../../global.d.ts"] +} diff --git a/packages/components/breadcrumb/vite.config.ts b/packages/components/breadcrumb/vite.config.ts new file mode 100644 index 000000000..64973e5ac --- /dev/null +++ b/packages/components/breadcrumb/vite.config.ts @@ -0,0 +1,6 @@ +import path from 'path' +import { getComponentConfiguration } from '../../../config/index' + +const { name } = require(path.resolve(__dirname, 'package.json')) + +export default getComponentConfiguration(process.cwd(), name)