Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/breezy-swans-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@devup-ui/components": patch
---

Add Input component
3 changes: 1 addition & 2 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"ignore": [
"*-example",
"*-benchmark",
"landing",
"@devup-ui/components"
"landing"
]
}
5 changes: 5 additions & 0 deletions .changeset/stupid-brooms-see.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@devup-ui/components": patch
---

Fix Input comp test
1 change: 1 addition & 0 deletions packages/components/src/__tests__/index.browser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ describe('export', () => {
const index = await import('../index')
expect({ ...index }).toEqual({
Button: expect.any(Function),
Input: expect.any(Function),
})
})
})
9 changes: 9 additions & 0 deletions packages/components/src/components/Input/Controlled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useState } from 'react'

import { Input } from '.'

export function Controlled() {
const [value, setValue] = useState('')

return <Input onChange={(e) => setValue(e.target.value)} value={value} />
}
21 changes: 21 additions & 0 deletions packages/components/src/components/Input/GlassIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ComponentProps } from 'react'

export function GlassIcon(props: ComponentProps<'svg'>) {
return (
<svg
fill="none"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
clipRule="evenodd"
d="M14.5006 15.9949C13.445 16.6754 12.1959 17.069 10.8571 17.069C7.07005 17.069 4 13.9195 4 10.0345C4 6.14945 7.07005 3 10.8571 3C14.6442 3 17.7143 6.14945 17.7143 10.0345C17.7143 11.7044 17.1471 13.2384 16.1995 14.4448C16.2121 14.4567 16.2245 14.4688 16.2367 14.4813L19.6653 17.9986C20.1116 18.4564 20.1116 19.1988 19.6653 19.6566C19.2189 20.1145 18.4953 20.1145 18.049 19.6566L14.6204 16.1394C14.5761 16.0938 14.5361 16.0455 14.5006 15.9949ZM16.2143 10.0345C16.2143 13.1274 13.7799 15.569 10.8571 15.569C7.93435 15.569 5.5 13.1274 5.5 10.0345C5.5 6.94154 7.93435 4.5 10.8571 4.5C13.7799 4.5 16.2143 6.94154 16.2143 10.0345Z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
)
}
58 changes: 58 additions & 0 deletions packages/components/src/components/Input/Input.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Meta, StoryObj } from '@storybook/react-vite'

import { Controlled } from './Controlled'
import { GlassIcon } from './GlassIcon'
import { Input } from './index'

type Story = StoryObj<typeof meta>

// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta: Meta<typeof Input> = {
title: 'Devfive/Input',
component: Input,
decorators: [
(Story) => (
<div style={{ padding: '10px' }}>
<Story />
</div>
),
],
}

export const Default: Story = {
args: {
placeholder: 'Input text',
},
}

export const ControlledInput: Story = {
args: {
placeholder: 'Input text',
},
render: () => <Controlled />,
}

export const Error: Story = {
args: {
placeholder: 'Input text',
error: true,
errorMessage: 'Error message',
},
}

export const Disabled: Story = {
args: {
placeholder: 'Input text',
disabled: true,
},
}

export const WithIcon: Story = {
args: {
placeholder: 'Input text',
allowClear: true,
icon: <GlassIcon />,
},
}

export default meta

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { fireEvent, render } from '@testing-library/react'
import { DevupThemeTypography } from 'node_modules/@devup-ui/react/dist/types/typography'

import { ClearButton, Input } from '..'
import { Controlled } from '../Controlled'
import { GlassIcon } from '../GlassIcon'

describe('Input', () => {
it('should render with default props', () => {
const { container } = render(<Input />)
expect(container).toMatchSnapshot()
})

it('should render with disabled prop', () => {
const { container } = render(<Input disabled />)
expect(container).toMatchSnapshot()
})

it('should render with allowClear prop', () => {
const { container } = render(<Input allowClear />)
expect(container).toMatchSnapshot()
expect(container.querySelector('[aria-label="input"]')).toHaveClass(
'padding-right-0-36px--1',
)
})

it('should not have padding right when allowClear is false', () => {
const { container } = render(<Input allowClear={false} />)
expect(container).toMatchSnapshot()
expect(container.querySelector('[aria-label="input"]')).not.toHaveClass(
'padding-right-0-36px--1',
)
})

it('should show clear button when value is not empty', () => {
const { container } = render(<Input />)
expect(container).toMatchSnapshot()
fireEvent.change(container.querySelector('input')!, {
target: { value: 'test' },
})
expect(container.querySelector('button')).toBeInTheDocument()
})

it('should not show clear button when value is empty', () => {
const { container } = render(<Input />)
expect(container).toMatchSnapshot()
})

it('should be able to clear value by clicking clear button', () => {
const { container } = render(<Input allowClear />)
fireEvent.change(container.querySelector('input')!, {
target: { value: 'test' },
})
expect(container.querySelector('button')).toBeInTheDocument()
fireEvent.click(container.querySelector('button')!)
expect(container.querySelector('input')!.value).toBe('')
})

it('should be able to render with icon', () => {
const { container } = render(
<Input icon={<GlassIcon data-testid="icon" />} />,
)
expect(container.querySelector('[data-testid="icon"]')).toBeInTheDocument()
})

it('should render error style when error is true', () => {
const { container } = render(<Input error />)
expect(container).toMatchSnapshot()
expect(container.querySelector('[aria-label="input"]')).toHaveClass(
'border-color-0-var(--error,light-dark(#D52B2E,#FF5B5E))--1',
)
})

it('should be able to render with error message', () => {
const { container } = render(<Input errorMessage="Error message" />)
expect(
container.querySelector('[aria-label="error-message"]'),
).toBeInTheDocument()
})

it('should pass colors prop', () => {
const { container } = render(
<Input
colors={{
primary: 'red',
error: 'blue',
text: 'green',
}}
/>,
)
const input = container.querySelector('[aria-label="input"]')
expect(input).toHaveStyle({
'--primary': 'red',
'--error': 'blue',
'--text': 'green',
})
})

it('should have typography when typography is provided', () => {
const { container } = render(
<Input typography={'inlineLabelS' as keyof DevupThemeTypography} />,
)
expect(container).toMatchSnapshot()
expect(container.querySelector('input')).toHaveClass('typo-inlineLabelS')
})

it('should pass className prop to error message component', () => {
const { container } = render(
<Input
classNames={{
errorMessage: 'error-message',
}}
errorMessage="Error message"
/>,
)
expect(container).toMatchSnapshot()
expect(container.querySelector('[aria-label="error-message"]')).toHaveClass(
'error-message',
)
})

it('should pass className prop to icon component', () => {
const { container } = render(
<Input
classNames={{
icon: 'icon',
}}
icon={<GlassIcon />}
/>,
)
expect(container).toMatchSnapshot()
expect(container.querySelector('[aria-label="icon"]')).toHaveClass('icon')
})

it('should pass props to ClearButton component', async () => {
const { container } = render(<ClearButton />)
expect(container).toMatchSnapshot()
const clearButton = container.querySelector('[aria-label="clear-button"]')
expect(clearButton).toBeInTheDocument()
})

it('should render disabled icon style when disabled is true', () => {
const { container } = render(<Input disabled icon={<GlassIcon />} />)
expect(container).toMatchSnapshot()
expect(container.querySelector('[aria-label="icon"]')).toHaveClass(
'color-0-var(--inputDisabledText,light-dark(#D6D7DE,#373737))--1',
)
})

it('should call onChange prop when it is provided andvalue is changed', () => {
const onChange = vi.fn()
const { container } = render(<Input onChange={onChange} />)
fireEvent.change(container.querySelector('input')!, {
target: { value: 'test' },
})
expect(onChange).toHaveBeenCalledWith(expect.any(Object))
})
})

describe('Controlled Input', () => {
it('should render with value', () => {
const { container } = render(<Controlled />)
expect(container).toMatchSnapshot()
})

it('should update value when it is changed', () => {
const { container } = render(<Controlled />)
fireEvent.change(container.querySelector('input')!, {
target: { value: 'test' },
})
expect(container.querySelector('input')!.value).toBe('test')
})
})
Loading