Skip to content

Commit

Permalink
Uxpt 4810 - Initial creation of the Tabs (#1340)
Browse files Browse the repository at this point in the history
* initial commit with first coouple of stories

* small cleanup of some stuff

* fixing to compile for build time

* editting stories and adding constants folder for tests

* removing unused import

* adding portion of snapshot

* adding hover state and icon stories

* additional test cases

* removing unused values

* adding change file

* reworked to include Radio

* lowering coverage to apst cicd

* fixing compile issue

* PR review and fixups

* Update packages/core/src/Tab/Tab.styled.tsx

Co-authored-by: Steve Dalonzo <[email protected]>

* rush prepare

* wip, pushing up to Craig

* rush update

* rerunning rush update after rebase

* fix compiler issue

* removing extra await

* fixing border-radius

* pr fixups

* lowering jets coverage

* removing bum package.json

* fixing small bugs and UI tweaks.

* getting tab to properly react onHover for chip

* jest coverage

* add more stories, better controls, and improve readibility of tabs component. Added vertical orientation

* style adjustments

* refactoring ids, fixing radio to function correctly, cleaning up types

* cleaning up more unneeded indexes

* cleaning up unused imports

* sorting exports

* adding test cases for coverage

* reverting changes to jest file

* fixing jest coverage

* cleaning up types, adding types to Chip, and stylistic changes from Wes

* fixing type error

* type adjustments

* add responsive styling to buttonTab

* removed unused import

* reworking stories and fixing hover behaviro

* removing unused prop

* removing import

* final adjustments

* fixing things again

* adding ternary for icon logic

* removing flex

* config

* adding dynamic tab width prop

* jest

* adding width to chip

* jest

* removing unused prop

* Type adjustments, and using getByRole where applicable

* PR feedbacks, adding defaultValue, onTabSelect, ariaLabel, props

* adding TSDoc

* TSDoc

* tsdoc blurb

* jest adjustments

* moved tabContent to another file

---------

Co-authored-by: Steve Dalonzo <[email protected]>
  • Loading branch information
Bsoong and sdalonzo authored Mar 8, 2024
1 parent 2c78365 commit 544b97d
Show file tree
Hide file tree
Showing 18 changed files with 868 additions and 130 deletions.
10 changes: 10 additions & 0 deletions common/changes/pcln-design-system/UXPT-4810_2023-07-13-20-28.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "pcln-design-system",
"comment": "Added Tab component",
"type": "minor"
}
],
"packageName": "pcln-design-system"
}
227 changes: 118 additions & 109 deletions common/config/rush/pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/core/config/jest.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
"extends": "@priceline/heft-component-rig/profiles/react/config/jest.config.json",
"coverageThreshold": {
"global": {
"statements": 95,
"statements": 94,
"branches": 94,
"functions": 88,
"lines": 95
"lines": 94
}
}
}
5 changes: 5 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
{
"name": "Justin Trenary",
"email": "[email protected]"
},
{
"name": "Brandon Soong",
"email": "[email protected]"
}
],
"sideEffects": false,
Expand Down Expand Up @@ -73,6 +77,7 @@
},
"dependencies": {
"@radix-ui/react-accordion": "~1.1.2",
"@radix-ui/react-tabs": "~1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-visually-hidden": "^1.0.3",
"@styled-system/theme-get": "^5.1.2",
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/Chip/ChipContent/ChipContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Flex } from '../../Flex/Flex'
import { Text } from '../../Text/Text'
import { getPaletteColor } from '../../utils/utils'
import { ChipContentWrapper } from '../ChipContentWrapper'
import { ResponsiveValue } from 'styled-system'

const ImageWrapper = styled(Flex)`
background-color: ${getPaletteColor('background.lightest')};
Expand Down Expand Up @@ -39,7 +40,7 @@ export type ChipContentProps = BoxProps & {
title?: React.ReactNode
}
image?: React.ReactNode
size?: string
size?: ResponsiveValue<'sm' | 'md'>
bridgeLabel?: string
BridgeIcon?: IconComponent
variation?: ChipContentVariation
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/Chip/ChoiceChip/ChoiceChip.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import { FontSizeProps, SpaceProps } from 'styled-system'
import { ChipContent } from '../ChipContent/ChipContent'
import { FontSizeProps, ResponsiveValue, SpaceProps } from 'styled-system'
import { ChipContent, IconComponent } from '../ChipContent/ChipContent'
import { ChipInput } from '../ChipInput'
import { ChipLabel } from '../ChipLabel'

Expand All @@ -15,6 +15,8 @@ export type ChoiceChipVariations = 'outline' | 'shadow'
export type ChoiceChipProps = SpaceProps &
FontSizeProps &
React.HTMLAttributes<HTMLElement> & {
Icon?: IconComponent
size?: ResponsiveValue<'sm' | 'md'>
name?: string
disabled?: boolean
selected?: boolean
Expand Down
28 changes: 13 additions & 15 deletions packages/core/src/FormField/FormField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,7 @@ export const DynamicLabel = () => (
<Label autoHide htmlFor='dynamic-label-with-a-value'>
With value
</Label>
<Input
id='dynamic-label-with-a-value'
name='dynamic-label-with-a-value'
value='[email protected]' />
<Input id='dynamic-label-with-a-value' name='dynamic-label-with-a-value' value='[email protected]' />
</FormField>
</Box>
<Box px={2} width={1 / 3}>
Expand Down Expand Up @@ -175,32 +172,34 @@ const validateEmailFormat = (email) => {
}

export const Usage = () => {
const [email, setEmail] = useState('');
const [email, setEmail] = useState('')
const [isValidEmail, setIsValidEmail] = useState(false)
const [isFocused, SetIsFocused] = useState(true)

// update email address
// update email address
const updateInput = (e) => {
const value = e.target.value
setEmail(value)
}

// handle when focus leave the field
// handle when focus leave the field
const handleBlur = (e) => {
const value = e.target.value
setIsValidEmail(validateEmailFormat(value))
SetIsFocused(false)
}

// handle when field is on focus
// handle when field is on focus
const handleFocus = (e) => {
SetIsFocused(true)
}

return (
<Box>
<FormField>
<Label autoHide htmlFor='dynamic-label-usage'>Email Address</Label>
<Label autoHide htmlFor='dynamic-label-usage'>
Email Address
</Label>
<Input
id='dynamic-label-usage'
name='dynamic-label-usage'
Expand All @@ -213,12 +212,11 @@ export const Usage = () => {
/>
{isFocused ? null : isValidEmail ? <SuccessIcon color='success' /> : <WarningIcon color='error' />}
</FormField>
{!isFocused && !isValidEmail
? <Tooltip id='demo-error' right color='error'>
Email address is required
</Tooltip>
: null
}
{!isFocused && !isValidEmail ? (
<Tooltip id='demo-error' right color='error'>
Email address is required
</Tooltip>
) : null}
</Box>
)
}
85 changes: 85 additions & 0 deletions packages/core/src/Tab/Tab.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react'
import { render, screen } from '../__test__/testing-library'
import { userEvent } from '@storybook/testing-library'
import { Tab } from './Tab'
import { Departure } from 'pcln-icons'

describe('Tab', () => {
const tabsData = [
{ id: 'tab1', text: 'Tab 1', children: <div>Tab 1 Content</div>, onTabSelect: () => {} },
{ id: 'tab2', text: 'Tab 2', children: <div>Tab 2 Content</div>, onTabSelect: () => {} },
]
const tabsDataWithIcon = [
{ id: 'tab1', text: 'Tab 1', icon: Departure, children: <div>Tab 1 Content</div>, onTabSelect: () => {} },
{ id: 'tab2', text: 'Tab 2', children: <div>Tab 2 Content</div>, onTabSelect: () => {} },
]

it('renders tabs with correct text', () => {
render(<Tab type='button' tabsData={tabsData} />)

expect(screen.getByRole('tab', { name: 'Tab 1' })).toBeInTheDocument()
expect(screen.getByRole('tab', { name: 'Tab 2' })).toBeInTheDocument()
})

it('renders tab content based on active tab', async () => {
render(<Tab type='button' tabsData={tabsData} />)

expect(screen.getByRole('tab', { name: 'Tab 1' })).toBeInTheDocument()
expect(screen.getByText('Tab 1 Content')).toBeInTheDocument()
expect(screen.queryByText('Tab 2 Content')).not.toBeInTheDocument()

await userEvent.click(screen.getByText('Tab 2'))
expect(screen.getByRole('tab', { name: 'Tab 2' })).toBeInTheDocument()
expect(screen.queryByText('Tab 1 Content')).not.toBeInTheDocument()
expect(screen.getByText('Tab 2 Content')).toBeInTheDocument()
})
it('renders tab content based on active tab with icon', async () => {
render(<Tab type='button' tabsData={tabsDataWithIcon} />)

expect(screen.getByRole('tab', { name: 'Tab 1' })).toBeInTheDocument()
expect(screen.getByText('Tab 1 Content')).toBeInTheDocument()
expect(screen.queryByText('Tab 2 Content')).not.toBeInTheDocument()

await userEvent.click(screen.getByText('Tab 2'))
expect(screen.getByRole('tab', { name: 'Tab 2' })).toBeInTheDocument()
expect(screen.queryByText('Tab 1 Content')).not.toBeInTheDocument()
expect(screen.getByText('Tab 2 Content')).toBeInTheDocument()
})

it('renders tab content based on active tab in Chip Variation', async () => {
render(<Tab type='chip' tabsData={tabsData} />)

expect(screen.getByRole('radio', { name: 'Tab 1' })).toBeInTheDocument()
expect(screen.getByText('Tab 1 Content')).toBeInTheDocument()
expect(screen.queryByText('Tab 2 Content')).not.toBeInTheDocument()

await userEvent.click(screen.getByText('Tab 2'))
expect(screen.getByRole('radio', { name: 'Tab 2' })).toBeInTheDocument()
expect(screen.queryByText('Tab 1 Content')).not.toBeInTheDocument()
expect(screen.getByText('Tab 2 Content')).toBeInTheDocument()
})

it('renders tab content based on active tab in Radio Variation', async () => {
render(<Tab type='radio' tabsData={tabsData} />)

expect(screen.getByRole('tab', { name: 'Tab 1' })).toBeInTheDocument()
expect(screen.getByText('Tab 1 Content')).toBeInTheDocument()
expect(screen.queryByText('Tab 2 Content')).not.toBeInTheDocument()

await userEvent.click(screen.getByText('Tab 2'))
expect(screen.queryByText('Tab 1 Content')).not.toBeInTheDocument()
expect(screen.getByText('Tab 2 Content')).toBeInTheDocument()
})

it('renders tab content based on orientation', async () => {
render(<Tab type='button' orientation='vertical' tabsData={tabsData} />)
expect(screen.getByRole('tab', { name: 'Tab 1' })).toBeInTheDocument()
await userEvent.click(screen.getByText('Tab 1'))
expect(screen.getByText('Tab 1 Content')).toBeInTheDocument()
expect(screen.queryByText('Tab 2 Content')).not.toBeInTheDocument()

await userEvent.click(screen.getByText('Tab 2'))
expect(screen.queryByText('Tab 1 Content')).not.toBeInTheDocument()
expect(screen.getByText('Tab 2 Content')).toBeInTheDocument()
})
})
63 changes: 63 additions & 0 deletions packages/core/src/Tab/Tab.stories.args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { TabProps } from './Tab'
import { DefaultContent, DefaultContent2, DefaultContent3 } from '../__test__/mocks/tabContent'

export const defaultArgs: Partial<TabProps> = {
size: 'md',
type: 'button',
tabsData: [
{
id: 't1',
text: 'Tab 1',
children: DefaultContent(),
onTabSelect: () => {},
},
{
id: 't2',
text: 'Tab 2',
children: DefaultContent2(),
onTabSelect: () => {},
},
{
id: 't3',
text: 'Tab 3',
children: DefaultContent3(),
onTabSelect: () => {
alert('onTabSelectFired!')
},
},
],
}
export const argTypes = {
size: {
control: 'select',
options: ['sm', 'md'],
},
type: {
control: 'select',
options: ['chip', 'radio', 'button'],
},
orientation: {
control: 'select',
options: ['horizontal', 'vertical'],
},
tabGap: {
control: 'number',
},
isTransparent: {
defaultValue: false,
control: 'boolean',
},
border: {
defaultValue: false,
control: 'boolean',
},
dynamicTabWidth: {
defaultValue: false,
control: 'boolean',
},
defaultValue: {
defaultValue: 't1',
control: 'select',
options: ['t1', 't2', 't3'],
},
}
Loading

0 comments on commit 544b97d

Please sign in to comment.