Skip to content

Commit

Permalink
feat(tooltip): add tooltip package
Browse files Browse the repository at this point in the history
  • Loading branch information
richbachman committed Jul 2, 2020
1 parent fae82d7 commit a2b5f93
Show file tree
Hide file tree
Showing 9 changed files with 391 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/paste-core/components/tooltip/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
37 changes: 37 additions & 0 deletions packages/paste-core/components/tooltip/__test__/index.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as React from 'react';
import {axe} from 'jest-axe';
import {render, screen} from '@testing-library/react';
import {Button} from '@twilio-paste/button';
import {Theme} from '@twilio-paste/theme';
import {Tooltip} from '../src';

const TooltipMock: React.FC<{}> = () => {
return (
<Theme.Provider theme="console">
<Tooltip text="Welcome to Paste!" data-testid="tooltip-example">
<Button variant="primary">Open Tooltip</Button>
</Tooltip>
</Theme.Provider>
);
};

describe('Tooltip', () => {
describe('Render', () => {
render(<TooltipMock />);
it('should render a tooltip button with aria attributes', () => {
const renderedTooltipButton = screen.getByRole('button');
expect(renderedTooltipButton.getAttribute('aria-describedby')).toEqual('paste-tooltip-1');

const renderedTooltip = screen.getByTestId('tooltip-example');
expect(renderedTooltip.getAttribute('role')).toEqual('tooltip');
});
});

describe('Accessibility', () => {
it('Should have no accessibility violations', async () => {
const {container} = render(<TooltipMock />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
});
60 changes: 60 additions & 0 deletions packages/paste-core/components/tooltip/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"name": "@twilio-paste/tooltip",
"version": "0.0.0",
"category": "interaction",
"status": "alpha",
"description": "The Tooltip is a popup that displays clarifying text related to an element that has been focused or hovered on.",
"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 && yarn compile",
"build:dev": "yarn clean && yarn compile:dev",
"build:props": "typedoc --tsconfig ./tsconfig.json --json ./dist/prop-types.json",
"clean": "rm -rf ./dist && rm -rf tsconfig.build.tsbuildinfo && rm -rf .rpt2_cache",
"compile": "rollup -c --environment NODE_ENV:production",
"compile:dev": "rollup -cw --environment NODE_ENV:development",
"prepublishOnly": "yarn build",
"type-check": "tsc --noEmit"
},
"peerDependencies": {
"@twilio-paste/box": "^2.5.4",
"@twilio-paste/design-tokens": "^5.2.2",
"@twilio-paste/icons": "^2.2.9",
"@twilio-paste/reakit-library": "^0.6.0",
"@twilio-paste/spinner": "^1.2.18",
"@twilio-paste/style-props": "^1.2.5",
"@twilio-paste/styling-library": "^0.1.0",
"@twilio-paste/text": "^2.1.15",
"@twilio-paste/theme": "^3.2.5",
"@twilio-paste/tooltip-primitive": "^0.1.0",
"@twilio-paste/types": "^3.0.9",
"prop-types": "^15.7.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-uid": "^2.2.0"
},
"devDependencies": {
"@twilio-paste/box": "^2.5.4",
"@twilio-paste/design-tokens": "^5.2.2",
"@twilio-paste/icons": "^2.2.9",
"@twilio-paste/reakit-library": "^0.6.0",
"@twilio-paste/spinner": "^1.2.18",
"@twilio-paste/style-props": "^1.2.5",
"@twilio-paste/styling-library": "^0.1.0",
"@twilio-paste/text": "^2.1.15",
"@twilio-paste/theme": "^3.2.5",
"@twilio-paste/tooltip-primitive": "^0.1.0",
"@twilio-paste/types": "^3.0.9"
}
}
34 changes: 34 additions & 0 deletions packages/paste-core/components/tooltip/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import typescript from 'rollup-plugin-typescript2';
import babel from 'rollup-plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import {terser} from 'rollup-plugin-terser';
import pkg from './package.json';

export default {
input: pkg['main:dev'],
output: [
{
file: pkg.main,
format: 'cjs',
},
{
file: pkg.module,
format: 'esm',
},
],
external: [...Object.keys(pkg.peerDependencies || {})],
plugins: [
resolve(),
commonjs(),
typescript({
clean: true,
typescript: require('typescript'),
tsconfig: './tsconfig.build.json',
}),
babel({
exclude: 'node_modules/**',
}),
process.env.NODE_ENV === 'production' ? terser() : null,
],
};
21 changes: 21 additions & 0 deletions packages/paste-core/components/tooltip/src/TooltipArrow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from 'react';
import {TooltipPrimitiveArrow, TooltipPrimitiveArrowProps} from '@twilio-paste/tooltip-primitive';
import {useTheme} from '@twilio-paste/theme';

export type TooltipArrowProps = TooltipPrimitiveArrowProps;

const TooltipArrow: React.FC<TooltipArrowProps> = props => {
const theme = useTheme();

return (
<TooltipPrimitiveArrow
{...props}
size={theme.fontSizes.fontSize70}
stroke={theme.borderColors.colorBorderLight}
fill={theme.backgroundColors.colorBackgroundBody}
/>
);
};

TooltipArrow.displayName = 'TooltipArrow';
export {TooltipArrow};
70 changes: 70 additions & 0 deletions packages/paste-core/components/tooltip/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import {useUID} from 'react-uid';
import {Box, BoxProps, safelySpreadBoxProps} from '@twilio-paste/box';
import {StyledBase} from '@twilio-paste/theme';
import {Text} from '@twilio-paste/text';
import {
TooltipPrimitiveInitialState,
useTooltipPrimitiveState,
TooltipPrimitive,
TooltipPrimitiveReference,
} from '@twilio-paste/tooltip-primitive';
import {TooltipArrow} from './TooltipArrow';

const StyledTooltip = React.forwardRef<HTMLDivElement, BoxProps>(({style, ...props}, ref) => {
return (
<Box
{...safelySpreadBoxProps(props)}
backgroundColor="colorBackgroundBody"
borderColor="colorBorderLight"
borderRadius="borderRadius20"
borderStyle="solid"
borderWidth="borderWidth10"
boxShadow="shadowCard"
maxWidth="size30"
padding="space40"
paddingBottom="space30"
paddingTop="space30"
zIndex="zIndex90"
_focus={{outline: 'none'}}
style={style}
ref={ref}
/>
);
});

export interface TooltipProps extends TooltipPrimitiveInitialState {
children: NonNullable<React.ReactElement>;
text: string;
}

const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(({baseId, children, text, ...props}, ref) => {
const tooltip = useTooltipPrimitiveState({baseId: `paste-tooltip-${useUID()}`, ...props});
return (
<>
{React.Children.only(
<TooltipPrimitiveReference {...tooltip} ref={ref}>
{referenceProps => React.cloneElement(children, referenceProps)}
</TooltipPrimitiveReference>
)}
<TooltipPrimitive {...tooltip} {...props} as={StyledTooltip}>
<TooltipArrow {...tooltip} />
{/* import Paste Theme Based Styles due to portal positioning. */}
<StyledBase>
<Text as="span">{text}</Text>
</StyledBase>
</TooltipPrimitive>
</>
);
});

if (process.env.NODE_ENV === 'development') {
Tooltip.propTypes = {
children: PropTypes.element.isRequired,
text: PropTypes.string.isRequired,
};
}

Tooltip.displayName = 'Tooltip';
export {Tooltip};
116 changes: 116 additions & 0 deletions packages/paste-core/components/tooltip/stories/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import * as React from 'react';
import {storiesOf} from '@storybook/react';
import {withKnobs, text} from '@storybook/addon-knobs';
import {Absolute} from '@twilio-paste/absolute';
import {Anchor} from '@twilio-paste/anchor';
import {Box} from '@twilio-paste/box';
import {Button} from '@twilio-paste/button';
import {InformationIcon} from '@twilio-paste/icons/esm/InformationIcon';
import {Stack} from '@twilio-paste/stack';
import {Text} from '@twilio-paste/text';
import {Tooltip} from '../src';

storiesOf('Components|Tooltip', module)
.addDecorator(withKnobs)
.add('Default', () => {
return (
<Tooltip text={text('text', 'Welcome to Paste!')}>
<Button variant="primary">Open tooltip</Button>
</Tooltip>
);
})
.add('Tooltip Placements', () => {
return (
<Box marginTop="space120" paddingTop="space120" paddingLeft="space60">
<Stack orientation="horizontal" spacing="space40">
<Tooltip text="Welcome to Paste!" placement="top">
<Button variant="primary">Open tooltip to the top</Button>
</Tooltip>
<Tooltip text="Welcome to Paste!" placement="bottom">
<Button variant="primary">Open tooltip to the bottom</Button>
</Tooltip>
<Tooltip text="Welcome to Paste!" placement="right">
<Button variant="primary">Open tooltip to the right</Button>
</Tooltip>
<Tooltip text="Welcome to Paste!" placement="left">
<Button variant="primary">Open tooltip to the left</Button>
</Tooltip>
</Stack>
</Box>
);
})
.add('Automatic edge placement', () => {
return (
<>
<Absolute preset="top_left">
<Tooltip text="Welcome to Paste!">
<Button variant="primary">Open tooltip</Button>
</Tooltip>
</Absolute>
<Absolute preset="top_right">
<Tooltip text="Welcome to Paste!">
<Button variant="primary">Open tooltip</Button>
</Tooltip>
</Absolute>
<Absolute preset="bottom_left">
<Tooltip text="Welcome to Paste!">
<Button variant="primary">Open tooltip</Button>
</Tooltip>
</Absolute>
<Absolute preset="bottom_right">
<Tooltip text="Welcome to Paste!">
<Button variant="primary">Open tooltip</Button>
</Tooltip>
</Absolute>
</>
);
})
.add('Automatic adjusted placement', () => {
return (
<>
<Absolute preset="bottom_left">
<Tooltip text="Tooltip adjusted to the top because bottom would be off screen" placement="bottom">
<Button variant="primary">Open tooltip</Button>
</Tooltip>
</Absolute>
</>
);
})
.add('Icon Button Tooltip', () => {
return (
<Tooltip text="Welcome to Paste!">
<Button variant="secondary" size="icon">
<InformationIcon decorative={false} title="Open Tooltip" />
</Button>
</Tooltip>
);
})
.add('Custom Tooltip', () => {
return (
<Box display="flex" alignItems="center">
<Text as="span">Tooltip should appear from the icon.</Text>
<Tooltip text="Welcome to Paste!">
<Anchor href="https://paste.twilio.design">
<InformationIcon decorative={false} title="Open tooltip" display="block" />
</Anchor>
</Tooltip>
</Box>
);
})
.add('Multiple Tooltips', () => {
return (
<>
<Box display="flex" alignItems="center">
<Text as="span">Tooltip should appear from the icon.</Text>
<Tooltip text="Welcome to Paste!">
<Anchor href="https://paste.twilio.design">
<InformationIcon decorative={false} title="Open Tooltip" display="block" />
</Anchor>
</Tooltip>
</Box>
<Tooltip text="Welcome to Paste!">
<Button variant="primary">Open tooltip</Button>
</Tooltip>
</>
);
});
45 changes: 45 additions & 0 deletions packages/paste-core/components/tooltip/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"extends": "../../../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": [
"src/**/*"
],
"references": [
{
"path": "../../primitives/box"
},
{
"path": "../../../paste-design-tokens"
},
{
"path": "../../../paste-icons"
},
{
"path": "../../../paste-libraries/reakit"
},
{
"path": "../spinner"
},
{
"path": "../../../paste-style-props"
},
{
"path": "../../../paste-libraries/styling"
},
{
"path": "../../primitives/text"
},
{
"path": "../../../paste-theme"
},
{
"path": "../../primitives/tooltip"
},
{
"path": "../../../paste-types"
}
]
}
Loading

0 comments on commit a2b5f93

Please sign in to comment.