Skip to content

Commit

Permalink
feat: updated settings - new form wrapper (#3725)
Browse files Browse the repository at this point in the history
* feat: add desktop version of settings form wrapper
* chore: move components into forms display section
* feat: update wrapper for mobile
  • Loading branch information
benfurber committed Jul 5, 2024
1 parent 6e93440 commit 46972f7
Show file tree
Hide file tree
Showing 15 changed files with 374 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FieldDatepicker } from './FieldDatepicker'
import type { Meta, StoryFn } from '@storybook/react'

export default {
title: 'Components/FieldDatepicker',
title: 'Forms/FieldDatepicker',
component: FieldDatepicker,
} as Meta<typeof FieldDatepicker>

Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/FieldInput/FieldInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FieldInput } from './FieldInput'
import type { Meta, StoryFn } from '@storybook/react'

export default {
title: 'Components/FieldInput',
title: 'Forms/FieldInput',
component: FieldInput,
} as Meta<typeof FieldInput>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FieldTextarea } from './FieldTextarea'
import type { Meta, StoryFn } from '@storybook/react'

export default {
title: 'Components/FieldTextarea',
title: 'Forms/FieldTextarea',
component: FieldTextarea,
} as Meta<typeof FieldTextarea>

Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/Guidelines/Guidelines.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Guidelines } from './Guidelines'
import type { Meta, StoryFn } from '@storybook/react'

export default {
title: 'Components/Guidelines',
title: 'Forms/Guidelines',
component: Guidelines,
} as Meta<typeof Guidelines>

Expand Down
5 changes: 4 additions & 1 deletion packages/components/src/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { ExternalUrl } from './ExternalUrl'
import { iconMap } from './svgs'

import type { SpaceProps, VerticalAlignProps } from 'styled-system'
import type { ThemeUIStyleObject } from 'theme-ui'
import type { IGlyphs } from './types'

interface IGlyphProps {
Expand All @@ -53,6 +54,7 @@ export interface IProps {
marginRight?: string
opacity?: string
onClick?: () => void
sx?: ThemeUIStyleObject | undefined
}

export const glyphs: IGlyphs = {
Expand Down Expand Up @@ -150,7 +152,7 @@ const Glyph = ({ glyph }: IGlyphProps) => {
}

export const Icon = (props: Props) => {
const { glyph, size, marginRight } = props
const { glyph, size, marginRight, sx } = props

const isSizeNumeric = !isNaN(size as any)

Expand All @@ -172,6 +174,7 @@ export const Icon = (props: Props) => {
'& svg': {
fontSize: definedSize,
},
...sx,
}}
size={definedSize}
style={{ marginRight }}
Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/ImageCrop/ImageCrop.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ImageCrop } from './ImageCrop'
import type { Meta, StoryFn } from '@storybook/react'

export default {
title: 'Components/ImageCrop',
title: 'Forms/ImageCrop',
component: ImageCrop,
} as Meta<typeof ImageCrop>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import '@testing-library/jest-dom/vitest'

import { Tabs } from '@mui/base/Tabs'
import { describe, expect, it } from 'vitest'

import { render } from '../test/utils'
import { SettingsFormTab } from './SettingsFormTab'

describe('SettingsFormTab', () => {
it('renders all expected props', () => {
const bodyText = 'Profile settings area'
const headerText = 'Header area'
const notificationText = 'Success message'

const { getByText } = render(
<Tabs defaultValue={0}>
<SettingsFormTab
tab={{
body: <>{bodyText}</>,
header: <>{headerText}</>,
notifications: <>{notificationText}</>,
title: 'Profile',
glyph: 'comment',
}}
value={0}
/>
</Tabs>,
)

expect(getByText(bodyText)).toBeInTheDocument()
expect(getByText(headerText)).toBeInTheDocument()
expect(getByText(notificationText)).toBeInTheDocument()
})
})
40 changes: 40 additions & 0 deletions packages/components/src/SettingsFormWrapper/SettingsFormTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { TabPanel } from '@mui/base/TabPanel'
import { Card } from '@theme-ui/components'
import { Box } from 'theme-ui'

import type { availableGlyphs } from '../Icon/types'

export interface ITab {
header?: React.ReactNode
notifications?: React.ReactNode
body: React.ReactNode
glyph: availableGlyphs
title: string
}

interface IProps {
tab: ITab
value: number
}

export const SettingsFormTab = (props: IProps) => {
const { tab, value } = props
const { body, header, notifications } = tab

const sx = {
borderRadius: 3,
marginBottom: 3,
padding: 3,
overflow: 'hidden',
}

return (
<TabPanel value={value}>
{header && (
<Card sx={{ ...sx, backgroundColor: 'softblue' }}>{header}</Card>
)}
{notifications && <Box sx={{ ...sx, padding: 0 }}>{notifications}</Box>}
<Card sx={sx}>{body}</Card>
</TabPanel>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import '@testing-library/jest-dom/vitest'

import { Tabs } from '@mui/base/Tabs'
import { describe, expect, it } from 'vitest'

import { render } from '../test/utils'
import { SettingsFormTabList } from './SettingsFormTabList'

import type { availableGlyphs } from '../Icon/types'

describe('SettingsFormTab', () => {
const title = 'Tab Title'
const tab = {
body: <></>,
title,
glyph: 'comment' as availableGlyphs,
}

it('renders when more than one tab provided', () => {
const { getAllByText } = render(
<Tabs defaultValue={0}>
<SettingsFormTabList
value={0}
setValue={() => null}
tabs={[tab, tab]}
/>
</Tabs>,
)

expect(getAllByText('Tab Title')[0]).toBeInTheDocument()
})

it('renders nothing when only one tab provided', () => {
const { queryByText } = render(
<Tabs defaultValue={0}>
<SettingsFormTabList value={0} setValue={() => null} tabs={[tab]} />
</Tabs>,
)

expect(queryByText(title)).not.toBeInTheDocument()
})
})
105 changes: 105 additions & 0 deletions packages/components/src/SettingsFormWrapper/SettingsFormTabList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import styled from '@emotion/styled'
import { Tab as BaseTab, tabClasses } from '@mui/base/Tab'
import { TabsList as BaseTabsList } from '@mui/base/TabsList'
import { Box, Select } from 'theme-ui'

import { Icon } from '../Icon/Icon'

import type { ITab } from './SettingsFormTab'

interface IProps {
tabs: ITab[]
value: number
setValue: (value: number) => void
}

export const SettingsFormTabList = (props: IProps) => {
const { tabs, value, setValue } = props

if (tabs.length === 1) return

const Tab = styled(BaseTab)`
color: grey;
cursor: pointer;
background-color: transparent;
padding: 12px 18px;
border: none;
border-radius: 12px;
display: flex;
gap: 8px;
justify-content: flex-start;
&:hover {
background-color: white;
}
&:focus {
outline: 2px solid #666;
}
&.${tabClasses.disabled} {
opacity: 0.5;
cursor: not-allowed;
}
&.${tabClasses.selected} {
color: #1b1b1b;
outline: 2px solid #1b1b1b;
background-color: #e2edf7;
}
`

const TabsList = styled(BaseTabsList)`
border-radius: inherit;
display: flex;
flex: 2;
gap: 12px;
flex-direction: column;
justify-content: flex-start;
align-content: flex-start;
`

const defaultValue = tabs.find((_, index) => index === value)?.title || ''

return (
<>
<Box sx={{ display: ['none', 'inherit'] }}>
<TabsList>
{tabs.map(({ glyph, title }, index) => {
return (
<Tab key={index}>
<Icon glyph={glyph} /> {title}
</Tab>
)
})}
</TabsList>
</Box>

<Box sx={{ display: ['inherit', 'none'] }}>
<TabsList>
<Select
arrow={
<Icon
glyph="arrow-full-down"
sx={{
ml: -7,
alignSelf: 'center',
pointerEvents: 'none',
}}
/>
}
defaultValue={defaultValue}
>
{tabs.map(({ title }, index) => {
return (
<option key={index} onClick={() => setValue(index)}>
{title}
</option>
)
})}
</Select>
</TabsList>
</Box>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Alert, Flex, Text } from 'theme-ui'

import { SettingsFormWrapper } from './SettingsFormWrapper'

import type { Meta, StoryFn } from '@storybook/react'

export default {
title: 'Forms/SettingsFormWrapper',
component: SettingsFormWrapper,
} as Meta<typeof SettingsFormWrapper>

export const Default: StoryFn<typeof SettingsFormWrapper> = () => (
<div style={{ maxWidth: '900px' }}>
<SettingsFormWrapper
tabs={[
{
title: 'Profile',
header: (
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
<Text as="h3">✏️ Complete your profile</Text>
<Text>
In order to post comments or create content, we'd like you to
share something about yourself.
</Text>
</Flex>
),
body: <>Form Body 1</>,
glyph: 'thunderbolt',
},
{
title: 'Notifications',
body: <>Form Body 2</>,
notifications: (
<Alert variant="success">Nice, all submitted fine.</Alert>
),
glyph: 'account-circle',
},
{
title: 'Bad',
body: <>Form Body 3</>,
header: <>Bad thing header</>,
notifications: <Alert variant="failure">Problem! Sort it out!</Alert>,
glyph: 'bazar',
},
]}
/>
</div>
)

export const SingleTab: StoryFn<typeof SettingsFormWrapper> = () => (
<div style={{ maxWidth: '900px' }}>
<SettingsFormWrapper
tabs={[
{
title: 'Profile',
body: <>Anything for the moment</>,
header: <>header</>,
glyph: 'thunderbolt',
},
]}
/>
</div>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import '@testing-library/jest-dom/vitest'

import { fireEvent } from '@testing-library/react'
import { describe, expect, it } from 'vitest'

import { render } from '../test/utils'
import { Default } from './SettingsFormWrapper.stories'

import type { IProps } from './SettingsFormWrapper'

describe('SettingsFormWrapper', () => {
it('changes the tab display as expected', async () => {
const screen = render(<Default {...(Default.args as IProps)} />)
expect(screen.getByText('Form Body 1')).toBeInTheDocument()

const tabThree = screen.getAllByText('Bad', {
exact: false,
})[0]

await fireEvent.click(tabThree)

expect(screen.getByText('Form Body 3')).toBeInTheDocument()
})
})
Loading

0 comments on commit 46972f7

Please sign in to comment.