Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(disclosure): create component package
- Loading branch information
Showing
11 changed files
with
511 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
60 changes: 60 additions & 0 deletions
60
packages/paste-core/components/disclosure/__tests__/disclosure.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import * as React from 'react'; | ||
import {axe} from 'jest-axe'; | ||
import {render, screen, fireEvent} from '@testing-library/react'; | ||
import {Disclosure, DisclosureContent, DisclosureHeading, DisclosureHeadingProps, DisclosureProps} from '../src'; | ||
|
||
export const MockDisclosure: React.FC<{ | ||
visible?: DisclosureProps['visible']; | ||
disabled?: DisclosureHeadingProps['disabled']; | ||
focusable?: DisclosureHeadingProps['focusable']; | ||
}> = ({visible, disabled, focusable}) => { | ||
return ( | ||
<Disclosure baseId="disclosure" visible={visible}> | ||
<DisclosureHeading as="h1" variant="heading10" disabled={disabled} focusable={focusable}> | ||
Clickable heading | ||
</DisclosureHeading> | ||
<DisclosureContent data-testid="disclosure">Disclosure content</DisclosureContent> | ||
</Disclosure> | ||
); | ||
}; | ||
|
||
describe('Disclosure', () => { | ||
it('should render a disclosure button with aria attributes', () => { | ||
render(<MockDisclosure />); | ||
const renderedDisclosureButton = screen.getByRole('button'); | ||
expect(renderedDisclosureButton.getAttribute('aria-expanded')).toEqual('false'); | ||
expect(renderedDisclosureButton.getAttribute('aria-controls')).toEqual('disclosure'); | ||
expect(renderedDisclosureButton.getAttribute('tabindex')).toEqual('0'); | ||
expect(screen.getByTestId('disclosure').id).toEqual('disclosure'); | ||
}); | ||
it('should render a disclosure open', () => { | ||
render(<MockDisclosure visible />); | ||
const renderedDisclosureButton = screen.getByRole('button'); | ||
expect(renderedDisclosureButton.getAttribute('aria-expanded')).toEqual('true'); | ||
}); | ||
it('should update attributes when clicked', () => { | ||
render(<MockDisclosure />); | ||
const renderedDisclosureButton = screen.getByRole('button'); | ||
fireEvent.click(renderedDisclosureButton); | ||
expect(renderedDisclosureButton.getAttribute('aria-expanded')).toEqual('true'); | ||
}); | ||
it('should render a disabled disclosure', () => { | ||
render(<MockDisclosure disabled />); | ||
const renderedDisclosureButton = screen.getByRole('button'); | ||
expect(renderedDisclosureButton.getAttribute('aria-disabled')).toEqual('true'); | ||
expect(renderedDisclosureButton.getAttribute('tabindex')).toBeNull(); | ||
}); | ||
it('should render a disabled but focusable disclosure', () => { | ||
render(<MockDisclosure disabled focusable />); | ||
const renderedDisclosureButton = screen.getByRole('button'); | ||
expect(renderedDisclosureButton.getAttribute('aria-disabled')).toEqual('true'); | ||
expect(renderedDisclosureButton.getAttribute('tabindex')).toEqual('0'); | ||
}); | ||
describe('accessibility', () => { | ||
it('should have no accessibility violations', async () => { | ||
const {container} = render(<MockDisclosure />); | ||
const results = await axe(container); | ||
expect(results).toHaveNoViolations(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
{ | ||
"name": "@twilio-paste/disclosure", | ||
"version": "0.0.0", | ||
"category": "interaction", | ||
"status": "alpha", | ||
"description": "The Disclosure is used to create accessible, hierarchical and collapsible structure to your 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 && 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 -c --environment NODE_ENV:development", | ||
"prepublishOnly": "yarn build", | ||
"type-check": "tsc --noEmit" | ||
}, | ||
"peerDependencies": { | ||
"@twilio-paste/box": "^2.5.4", | ||
"@twilio-paste/card": "^1.3.41", | ||
"@twilio-paste/design-tokens": "^5.2.2", | ||
"@twilio-paste/disclosure-primitive": "^0.1.6", | ||
"@twilio-paste/heading": "^2.0.14", | ||
"@twilio-paste/icons": "^2.2.9", | ||
"@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/types": "^3.0.9", | ||
"prop-types": "^15.7.2", | ||
"react": "^16.8.6", | ||
"react-dom": "^16.8.6" | ||
}, | ||
"devDependencies": { | ||
"@twilio-paste/box": "^2.5.4", | ||
"@twilio-paste/card": "^1.3.41", | ||
"@twilio-paste/design-tokens": "^5.2.2", | ||
"@twilio-paste/disclosure-primitive": "^0.1.6", | ||
"@twilio-paste/heading": "^2.0.14", | ||
"@twilio-paste/icons": "^2.2.9", | ||
"@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/types": "^3.0.9" | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
packages/paste-core/components/disclosure/rollup.config.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
], | ||
}; |
47 changes: 47 additions & 0 deletions
47
packages/paste-core/components/disclosure/src/Disclosure.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import * as React from 'react'; | ||
import * as PropTypes from 'prop-types'; | ||
import { | ||
useDisclosurePrimitiveState, | ||
DisclosurePrimitiveInitialState, | ||
DisclosurePrimitveStateReturn, | ||
} from '@twilio-paste/disclosure-primitive'; | ||
import {Card} from '@twilio-paste/card'; | ||
|
||
export type Variants = 'contained' | 'default'; | ||
|
||
export interface DisclosureContextProps { | ||
disclosure: DisclosurePrimitveStateReturn; | ||
variant: Variants; | ||
} | ||
|
||
export const DisclosureContext = React.createContext<DisclosureContextProps>({} as any); | ||
|
||
export interface DisclosureProps extends DisclosurePrimitiveInitialState { | ||
children: NonNullable<React.ReactNode>; | ||
variant?: Variants; | ||
} | ||
const Disclosure: React.FC<DisclosureProps> = ({children, variant = 'default', ...props}) => { | ||
const disclosure = useDisclosurePrimitiveState({...props}); | ||
const disclosureContext = { | ||
disclosure, | ||
variant, | ||
}; | ||
|
||
if (variant === 'contained') { | ||
return ( | ||
<DisclosureContext.Provider value={disclosureContext}> | ||
<Card padding="space0">{children}</Card> | ||
</DisclosureContext.Provider> | ||
); | ||
} | ||
return <DisclosureContext.Provider value={disclosureContext}>{children}</DisclosureContext.Provider>; | ||
}; | ||
Disclosure.displayName = 'Disclosure'; | ||
|
||
if (process.env.NODE_ENV === 'development') { | ||
Disclosure.propTypes = { | ||
children: PropTypes.node.isRequired, | ||
variant: PropTypes.oneOf(['default', 'contained']), | ||
}; | ||
} | ||
export {Disclosure}; |
39 changes: 39 additions & 0 deletions
39
packages/paste-core/components/disclosure/src/DisclosureContent.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import * as React from 'react'; | ||
import * as PropTypes from 'prop-types'; | ||
import {DisclosurePrimitiveContent, DisclosurePrimitiveContentProps} from '@twilio-paste/disclosure-primitive'; | ||
import {Box} from '@twilio-paste/box'; | ||
import {DisclosureContext, Variants} from './Disclosure'; | ||
|
||
const getVariantStyles = (variant: Variants): {} => { | ||
switch (variant) { | ||
case 'contained': | ||
return { | ||
borderTopStyle: 'solid', | ||
borderTopWidth: 'borderWidth20', | ||
borderTopColor: 'colorBorderLight', | ||
padding: 'space50', | ||
}; | ||
default: | ||
return {}; | ||
} | ||
}; | ||
|
||
export interface DisclosureContentProps extends DisclosurePrimitiveContentProps { | ||
children: NonNullable<React.ReactNode>; | ||
} | ||
const DisclosureContent: React.FC<DisclosureContentProps> = ({children, ...props}) => { | ||
const {disclosure, variant} = React.useContext(DisclosureContext); | ||
return ( | ||
<DisclosurePrimitiveContent {...disclosure} {...props} as={Box} {...getVariantStyles(variant)}> | ||
{children} | ||
</DisclosurePrimitiveContent> | ||
); | ||
}; | ||
DisclosureContent.displayName = 'DisclosureContent'; | ||
|
||
if (process.env.NODE_ENV === 'development') { | ||
DisclosureContent.propTypes = { | ||
children: PropTypes.node.isRequired, | ||
}; | ||
} | ||
export {DisclosureContent}; |
123 changes: 123 additions & 0 deletions
123
packages/paste-core/components/disclosure/src/DisclosureHeading.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import * as React from 'react'; | ||
import {Heading, HeadingProps, HeadingPropTypes} from '@twilio-paste/heading'; | ||
import {Box} from '@twilio-paste/box'; | ||
import {ChevronDownIcon, ChevronDownIconProps} from '@twilio-paste/icons/esm/ChevronDownIcon'; | ||
import {DisclosurePrimitive, DisclosurePrimitiveProps} from '@twilio-paste/disclosure-primitive'; | ||
import {DisclosureContext, Variants} from './Disclosure'; | ||
|
||
export interface DisclosureHeadingProps extends Omit<DisclosurePrimitiveProps, 'baseId' | 'toggle'> { | ||
children: NonNullable<React.ReactNode>; | ||
as: HeadingProps['as']; | ||
marginBottom?: HeadingProps['marginBottom']; | ||
variant: HeadingProps['variant']; | ||
} | ||
|
||
interface StyledDisclosureHeadingProps extends Omit<DisclosureHeadingProps, 'as'> { | ||
renderAs: HeadingProps['as']; | ||
customDisabled?: boolean; | ||
customFocusable?: boolean; | ||
disclosureVariant: Variants; | ||
} | ||
|
||
const getIconSize = (variant: HeadingProps['variant']): ChevronDownIconProps['size'] => { | ||
switch (variant) { | ||
case 'heading10': | ||
return 'sizeIcon90'; | ||
case 'heading20': | ||
return 'sizeIcon70'; | ||
case 'heading30': | ||
return 'sizeIcon60'; | ||
case 'heading40': | ||
return 'sizeIcon40'; | ||
case 'heading50': | ||
return 'sizeIcon30'; | ||
case 'heading60': | ||
default: | ||
return 'sizeIcon20'; | ||
} | ||
}; | ||
|
||
const getVariantStyles = (variant: Variants): {} => { | ||
switch (variant) { | ||
case 'contained': | ||
return { | ||
paddingBottom: 'space50', | ||
paddingLeft: 'space40', | ||
paddingRight: 'space40', | ||
paddingTop: 'space50', | ||
}; | ||
default: | ||
return {}; | ||
} | ||
}; | ||
|
||
const StyledDisclosureHeading = React.forwardRef<HTMLDivElement, StyledDisclosureHeadingProps>( | ||
({children, marginBottom, renderAs, disclosureVariant, customDisabled, customFocusable, variant, ...props}, ref) => { | ||
return ( | ||
<Heading | ||
as={renderAs} | ||
marginBottom={disclosureVariant === 'contained' ? 'space0' : marginBottom} | ||
variant={variant} | ||
> | ||
<Box | ||
as="div" | ||
alignItems="center" | ||
borderRadius="borderRadius20" | ||
cursor="pointer" | ||
display="flex" | ||
outline="none" | ||
position="relative" | ||
ref={ref} | ||
role="button" | ||
_hover={{ | ||
textDecoration: 'underline', | ||
}} | ||
_focus={{ | ||
boxShadow: 'shadowFocus', | ||
}} | ||
_disabled={{ | ||
color: 'colorTextWeak', | ||
cursor: 'not-allowed', | ||
}} | ||
{...getVariantStyles(disclosureVariant)} | ||
{...props} | ||
> | ||
<Box | ||
as="span" | ||
display="flex" | ||
transform={props['aria-expanded'] ? 'rotate(0deg)' : 'rotate(-90deg)'} | ||
transition="all 200ms linear" | ||
> | ||
<ChevronDownIcon decorative size={getIconSize(variant)} /> | ||
</Box> | ||
<Box flexGrow={1}>{children}</Box> | ||
</Box> | ||
</Heading> | ||
); | ||
} | ||
); | ||
|
||
const DisclosureHeading: React.FC<DisclosureHeadingProps> = ({children, as, disabled, focusable, ...props}) => { | ||
const {disclosure, variant} = React.useContext(DisclosureContext); | ||
return ( | ||
<DisclosurePrimitive | ||
{...disclosure} | ||
{...props} | ||
renderAs={as} | ||
as={StyledDisclosureHeading} | ||
disclosureVariant={variant} | ||
disabled={disabled} | ||
customDisabled={disabled} | ||
focusable={focusable} | ||
customFocusable={focusable} | ||
> | ||
{children} | ||
</DisclosurePrimitive> | ||
); | ||
}; | ||
DisclosureHeading.displayName = 'DisclosureHeading'; | ||
|
||
if (process.env.NODE_ENV === 'development') { | ||
DisclosureHeading.propTypes = HeadingPropTypes; | ||
} | ||
export {DisclosureHeading}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './Disclosure'; | ||
export * from './DisclosureHeading'; | ||
export * from './DisclosureContent'; |
Oops, something went wrong.