Skip to content

Commit

Permalink
feat(in-page-navigation): add new component package (#2827)
Browse files Browse the repository at this point in the history
* feat(in-page-nav): add new package

* chore: website description updates

* chore(in-page-nav): add styling

* chore(in-page-nav): add context

* chore(in-page-nav): add fullWidth story

* chore(in-page-nav): add changeset, update deps

* chore(in-page-nav): run install

* chore(in-page-nav): add tests

* docs: in-page-navigation

* docs: remove extra section

* chore: add customization story

* chore(in-page-nav): rename docs page

* chore(in-page-nav): take out comments

* docs: add in page nav vs tabs section

* chore(in-page-nav): add deps, refactor styling

* chore(in-page-nav): refactor, add global types

* chore(in-page-nav): add unique label to stories

* chore(in-page-nav): remove story comparisons

* chore(in-page-nav): fix underline bug

* chore: update overflow

* fix: overflow issue

* chore(in-page-nav): add padding

* fix: overflow issue

* chore: update site VRT

Co-authored-by: Nora Krantz <nkrantz@twilio.com>
Co-authored-by: TheSisb <shadiisber@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
4 people committed Nov 29, 2022
1 parent 9c85685 commit 8290a1e
Show file tree
Hide file tree
Showing 25 changed files with 748 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .changeset/three-weeks-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@twilio-paste/in-page-navigation': major
'@twilio-paste/core': minor
---

[In Page Navigation] add a package for the navigation component In Page Navigation
1 change: 1 addition & 0 deletions .codesandbox/ci.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"/packages/paste-core/layout/grid",
"/packages/paste-core/components/heading",
"/packages/paste-core/components/help-text",
"/packages/paste-core/components/in-page-navigation",
"/packages/paste-core/components/inline-code",
"/packages/paste-core/components/inline-control-group",
"/packages/paste-core/components/input",
Expand Down
24 changes: 24 additions & 0 deletions @types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,27 @@ declare module "*.svg" {
const content: any;
export default content;
}

export {};

declare global {
namespace jest {

interface AsymmetricMatcher {
$$typeof: Symbol;
sample?: string | RegExp | object | Array<any> | Function;
}

type Value = string | number | RegExp | AsymmetricMatcher | undefined;

interface Options {
media?: string;
modifier?: string;
supports?: string;
}

interface Matchers<R, T> {
toHaveStyleRule(property: string, value?: Value, options?: Options): R;
}
}
}
1 change: 1 addition & 0 deletions cypress/integration/sitemap-vrt/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const SITEMAP = [
'/components/icons/',
'/components/icons/usage-guidelines/',
'/components/',
'/components/in-page-navigation',
'/components/inline-code/',
'/components/input/',
'/components/label/',
Expand Down
2 changes: 2 additions & 0 deletions packages/paste-codemods/tools/.cache/mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@
"HeadingPropTypes": "@twilio-paste/core/heading",
"HelpText": "@twilio-paste/core/help-text",
"HelpTextVariants": "@twilio-paste/core/help-text",
"InPageNavigation": "@twilio-paste/core/in-page-navigation",
"InPageNavigationItem": "@twilio-paste/core/in-page-navigation",
"InlineCode": "@twilio-paste/core/inline-code",
"InlineControlGroup": "@twilio-paste/core/inline-control-group",
"InputBox": "@twilio-paste/core/input-box",
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import * as React from 'react';
import {render} from '@testing-library/react';
import {CustomizationProvider} from '@twilio-paste/customization';

import {InPageNavigation, InPageNavigationItem} from '../src';

describe('InPageNavigation', () => {
it('should render a nav with correct aria-label', () => {
const {getByRole} = render(
<InPageNavigation aria-label="my-nav">
<InPageNavigationItem href="#">page 1</InPageNavigationItem>
<InPageNavigationItem href="#">page 2</InPageNavigationItem>
</InPageNavigation>
);

expect(getByRole('navigation')).toHaveAttribute('aria-label', 'my-nav');
});

it('should render a list with list items and links', () => {
const {getAllByRole} = render(
<InPageNavigation aria-label="my-nav">
<InPageNavigationItem href="#">page 1</InPageNavigationItem>
<InPageNavigationItem href="#">page 2</InPageNavigationItem>
</InPageNavigation>
);

expect(getAllByRole('list')).toHaveLength(1);
expect(getAllByRole('listitem')).toHaveLength(2);
expect(getAllByRole('link')).toHaveLength(2);
});

it('should use the currentPage prop to apply aria-current', () => {
const {getByText} = render(
<InPageNavigation aria-label="my-nav">
<InPageNavigationItem href="#">page 1</InPageNavigationItem>
<InPageNavigationItem currentPage href="#">
page 2
</InPageNavigationItem>
</InPageNavigation>
);

expect(getByText('page 2')).toHaveAttribute('aria-current', 'page');
});

it('should pass props given to InPageNavigationItem onto its <a> child', () => {
const {getByText} = render(
<InPageNavigation aria-label="my-nav">
<InPageNavigationItem data-test-id="page-1" href="#">
page 1
</InPageNavigationItem>
<InPageNavigationItem currentPage href="#">
page 2
</InPageNavigationItem>
</InPageNavigation>
);

expect(getByText('page 1')).toHaveAttribute('data-test-id', 'page-1');
});
});

describe('Customization', () => {
it('should set a default element name', () => {
const {getByRole} = render(
<InPageNavigation aria-label="my-nav">
<InPageNavigationItem href="#">page 1</InPageNavigationItem>
</InPageNavigation>
);

expect(getByRole('navigation')).toHaveAttribute('data-paste-element', 'IN_PAGE_NAVIGATION');
expect(getByRole('list')).toHaveAttribute('data-paste-element', 'IN_PAGE_NAVIGATION_ITEMS');
expect(getByRole('listitem')).toHaveAttribute('data-paste-element', 'IN_PAGE_NAVIGATION_ITEM');
expect(getByRole('link')).toHaveAttribute('data-paste-element', 'IN_PAGE_NAVIGATION_ITEM_ANCHOR');
});

it('should set a custom element name when provided', () => {
const {getByRole} = render(
<InPageNavigation element="MY_IN_PAGE_NAVIGATION" aria-label="my-nav">
<InPageNavigationItem element="MY_IN_PAGE_NAVIGATION_ITEM" href="#">
page 1
</InPageNavigationItem>
</InPageNavigation>
);

expect(getByRole('navigation')).toHaveAttribute('data-paste-element', 'MY_IN_PAGE_NAVIGATION');
expect(getByRole('list')).toHaveAttribute('data-paste-element', 'MY_IN_PAGE_NAVIGATION_ITEMS');
expect(getByRole('listitem')).toHaveAttribute('data-paste-element', 'MY_IN_PAGE_NAVIGATION_ITEM');
expect(getByRole('link')).toHaveAttribute('data-paste-element', 'MY_IN_PAGE_NAVIGATION_ITEM_ANCHOR');
});

it('should add custom styles to default element names', () => {
const {getByRole} = render(
<CustomizationProvider
baseTheme="default"
theme={TestTheme}
elements={{
IN_PAGE_NAVIGATION: {fontWeight: 'fontWeightLight'},
IN_PAGE_NAVIGATION_ITEMS: {padding: 'space40'},
IN_PAGE_NAVIGATION_ITEM: {margin: 'space40'},
IN_PAGE_NAVIGATION_ITEM_ANCHOR: {fontSize: 'fontSize40'},
}}
>
<InPageNavigation aria-label="my-nav">
<InPageNavigationItem href="#">page 1</InPageNavigationItem>
</InPageNavigation>
</CustomizationProvider>
);

expect(getByRole('navigation')).toHaveStyleRule('font-weight', '400');
expect(getByRole('list')).toHaveStyleRule('padding', '0.75rem');
expect(getByRole('listitem')).toHaveStyleRule('margin', '0.75rem');
expect(getByRole('link')).toHaveStyleRule('font-size', '1rem');
});

it('should add custom styles to custom element names', () => {
const {getByRole} = render(
<CustomizationProvider
baseTheme="default"
theme={TestTheme}
elements={{
MY_IN_PAGE_NAVIGATION: {fontWeight: 'fontWeightLight'},
MY_IN_PAGE_NAVIGATION_ITEMS: {padding: 'space40'},
MY_IN_PAGE_NAVIGATION_ITEM: {margin: 'space40'},
MY_IN_PAGE_NAVIGATION_ITEM_ANCHOR: {fontSize: 'fontSize40'},
}}
>
<InPageNavigation element="MY_IN_PAGE_NAVIGATION" aria-label="my-nav">
<InPageNavigationItem element="MY_IN_PAGE_NAVIGATION_ITEM" href="#">
page 1
</InPageNavigationItem>
</InPageNavigation>
</CustomizationProvider>
);

expect(getByRole('navigation')).toHaveStyleRule('font-weight', '400');
expect(getByRole('list')).toHaveStyleRule('padding', '0.75rem');
expect(getByRole('listitem')).toHaveStyleRule('margin', '0.75rem');
expect(getByRole('link')).toHaveStyleRule('font-size', '1rem');
});
});
3 changes: 3 additions & 0 deletions packages/paste-core/components/in-page-navigation/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const {build} = require('../../../../tools/build/esbuild');

build(require('./package.json'));
60 changes: 60 additions & 0 deletions packages/paste-core/components/in-page-navigation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"name": "@twilio-paste/in-page-navigation",
"version": "0.0.0",
"category": "navigation",
"status": "production",
"description": "An In Page Navigation is a group of styled links that lets users navigate between related pages.",
"author": "Twilio Inc.",
"license": "MIT",
"main:dev": "src/index.tsx",
"main": "dist/index.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
"sideEffects": false,
"publishConfig": {
"access": "public"
},
"files": [
"dist"
],
"scripts": {
"build": "yarn clean && NODE_ENV=production node build.js && tsc",
"build:js": "NODE_ENV=development node build.js",
"build:props": "typedoc --tsconfig ./tsconfig.json --json ./dist/prop-types.json",
"clean": "rm -rf ./dist",
"tsc": "tsc"
},
"peerDependencies": {
"@twilio-paste/anchor": "^9.0.1",
"@twilio-paste/animation-library": "^0.3.2",
"@twilio-paste/box": "^7.0.0",
"@twilio-paste/customization": "^5.0.0",
"@twilio-paste/design-tokens": "^8.0.0",
"@twilio-paste/icons": "^9.4.0",
"@twilio-paste/style-props": "^6.0.0",
"@twilio-paste/styling-library": "^1.0.0",
"@twilio-paste/theme": "^8.0.0",
"@twilio-paste/types": "^3.1.1",
"@twilio-paste/uid-library": "^0.2.6",
"prop-types": "^15.7.2",
"react": "^16.8.6 || ^17.0.2",
"react-dom": "^16.8.6 || ^17.0.2"
},
"devDependencies": {
"@twilio-paste/anchor": "^9.0.1",
"@twilio-paste/animation-library": "^0.3.2",
"@twilio-paste/box": "^7.0.0",
"@twilio-paste/customization": "^5.0.0",
"@twilio-paste/design-tokens": "^8.0.0",
"@twilio-paste/icons": "^9.4.0",
"@twilio-paste/style-props": "^6.0.0",
"@twilio-paste/styling-library": "^1.0.0",
"@twilio-paste/theme": "^8.0.0",
"@twilio-paste/types": "^3.1.1",
"@twilio-paste/uid-library": "^0.2.6",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"typescript": "^4.6.4"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import {Box, safelySpreadBoxProps} from '@twilio-paste/box';
import type {BoxProps} from '@twilio-paste/box';

import type {Variants} from './types';
import {InPageNavigationContext} from './InPageNavigationContext';

export interface InPageNavigationProps extends Omit<React.ComponentPropsWithRef<'div'>, 'children'> {
children?: React.ReactNode;
element?: BoxProps['element'];
'aria-label': string;
variant?: Variants;
}

const InPageNavigation = React.forwardRef<HTMLDivElement, InPageNavigationProps>(
({element = 'IN_PAGE_NAVIGATION', variant = 'default', children, ...props}, ref) => {
return (
<InPageNavigationContext.Provider value={{variant}}>
<Box {...safelySpreadBoxProps(props)} as="nav" ref={ref} element={element}>
<Box
as="ul"
listStyleType="none"
element={`${element}_ITEMS`}
display="flex"
justifyContent={variant === 'fullWidth' ? 'space-evenly' : 'flex-start'}
borderBottomWidth="borderWidth10"
borderBottomColor="colorBorderWeak"
borderBottomStyle="solid"
margin="space0"
marginBottom="space60"
paddingLeft="space0"
paddingRight={variant === 'default' ? 'space70' : 'space0'}
columnGap={variant === 'default' ? 'space70' : 'space0'}
>
{children}
</Box>
</Box>
</InPageNavigationContext.Provider>
);
}
);

InPageNavigation.displayName = 'InPageNavigation';

InPageNavigation.propTypes = {
children: PropTypes.node,
element: PropTypes.string,
'aria-label': PropTypes.string.isRequired,
variant: PropTypes.oneOf(['fullWidth', 'default']),
};

export {InPageNavigation};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';

import type {Variants} from './types';

interface InPageNavigationContextValue {
variant?: Variants;
}

const InPageNavigationContext = React.createContext<InPageNavigationContextValue>({
variant: 'default',
});

export {InPageNavigationContext};
Loading

0 comments on commit 8290a1e

Please sign in to comment.