Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Input component #361

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
5 changes: 5 additions & 0 deletions .changeset/seven-donuts-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@utilitywarehouse/native-ui': minor
---

feat: add `Input` component
1 change: 1 addition & 0 deletions apps/native-ui-storybook/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ const preview: Preview = {
'Heading',
'HStack',
'Icons',
'Input',
'IconButton',
'Pressable',
'Radio',
Expand Down
152 changes: 152 additions & 0 deletions apps/native-ui-storybook/components/Input/Input.docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { Meta, Primary, Controls, Story, Canvas } from '@storybook/blocks';
import * as Stories from './Input.stories';
import {
Input,
InputField,
InputIcon,
InputSlot,
Center,
NativeUIProvider,
} from '@utilitywarehouse/native-ui';
import { EmailMediumIcon } from '@utilitywarehouse/react-native-icons';
import { ViewFigmaButton, BackToTopButton } from '../../docs/components';

<Meta of={Stories} />

<BackToTopButton />

<ViewFigmaButton url="https://www.figma.com/design/3RY3OvLA88yZksRjOfjQJm/UW-App-UI?node-id=6988-2603&m=dev" />

# Input

The input component is a text field that allows users to enter text, numbers, or other data. It is commonly used in forms and search fields.

- [Playground](#playground)
- [Usage](#usage)
- [Props](#props)
- [`Input`](#input)
- [`InputField`](#inputfield)
- [`InputIcon`](#inputicon)
- [`InputSlot`](#inputslot)
- [Variants](#variants)
- [Accessibility](#accessibility)

## Playground

<Primary />

<Controls />

## Usage

> This component should be wrapped in a `NativeUIProvider` component to ensure that the correct theme is applied.

<Canvas>
<NativeUIProvider>
<Center>
<Input onChange={() => console.log('###')}>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Placeholder" />
</Input>
</Center>
</NativeUIProvider>
</Canvas>

```tsx
import { Input, InputField, InputIcon, InputSlot } from '@utilitywarehouse/native-ui';
import { EmailMediumIcon } from '@utilitywarehouse/react-native-icons';

const MyComponent = () => {
const [value, setValue] = useState('');
const handleChange = e => {
setValue(e.target.value);
};
return (
<Input onChange={handleChange} value={value}>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Placeholder" />
</Input>
);
};
```

## Props

### `Input`

| Prop | Type | Default | Description |
| ------------------ | ---------------------------------- | ----------- | ------------------------------------ |
| validationStatus | `'initial' \| 'valid' \| 'invalid` | `'initial'` | The validation status of the input. |
| showValidationIcon | boolean | false | Whether to show the validation icon. |
| isDisabled | boolean | false | Disables the input. |
| isReadOnly | boolean | false | Makes the input read-only. |
| isFocused | boolean | false | Sets focus on the input. |

#### Descendants Styling Props

Props to style child components.

| Sx Prop | Description |
| ---------------- | --------------------------------- |
| \_input | Prop to style Input |
| \_icon | Prop to style InputIcon |
| \_validationIcon | Prop to style InputValidationIcon |

### `InputField`

| Prop | Type | Default | Description |
| ----------- | ---------------------- | -------- | ----------------------------------------------------------------- |
| value | string | | The value of the input. |
| onChange | function | | Callback function that is triggered when the input value changes. |
| onBlur | function | | Callback function that is triggered when the input loses focus. |
| onFocus | function | | Callback function that is triggered when the input gains focus. |
| placeholder | string | | The placeholder text for the input. |
| type | `'text' \| 'password'` | `'text'` | The type of the input. |

### `InputIcon`

| Prop | Type | Default | Description |
| ---- | ------ | ------- | ------------------- |
| as | string | | The icon component. |

### `InputSlot`

| Prop | Type | Default | Description |
| ---- | ---- | ------- | ----------- |
| - | - | - | - |

## Variants

The `Input` component has the following variants:

<Canvas of={Stories.Variants} />

## Accessibility

We have outlined the various features that ensure the Input component is accessible to all users, including those with disabilities. These features help ensure that your application is inclusive and meets accessibility standards.Adheres to the [WAI-ARIA design pattern](https://www.w3.org/TR/wai-aria-1.2/#textbox).

### Keyboard

- Setting the aria-label and aria-hint to help users understand the purpose and function of the Input

### Screen Reader

- Compatible with screen readers such as VoiceOver and Talk-back.
- The accessible and aria-label props to provide descriptive information about the Input
- Setting aria-traits and aria-hint to provide contextual information about the various states of the Input, such as "double tap to edit".

### Focus Management

- The onFocus and onBlur props to manage focus states and provide visual cues to users. This is especially important for users who rely on keyboard navigation.

### States

- In error state, aria-invalid will be passed to indicate that the Input has an error, and providing support for an aria-errormessage to describe the error in more detail.
- In disabled state, aria-hidden will be passed to make input not focusable.
- In required state, aria-required will be passed to indicate that the Input is required.

For more information, see the [Gluestack Input component documentation](https://gluestack.io/ui/docs/components/forms/input).
12 changes: 12 additions & 0 deletions apps/native-ui-storybook/components/Input/Input.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Meta } from '@storybook/react';
import Input from './Input';
import Variants from './Variants';

const InputMeta: Meta<typeof Input> = {
title: 'Native UI / Components / Input',
component: Input,
};

export default InputMeta;

export { Input as Playground, Variants };
93 changes: 93 additions & 0 deletions apps/native-ui-storybook/components/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from 'react';
import { Input, InputField, InputIcon, InputSlot } from '@utilitywarehouse/native-ui';
import { EmailMediumIcon, EyeMediumIcon } from '@utilitywarehouse/react-native-icons';
import { Meta, StoryFn } from '@storybook/react';

const InputBasic: StoryFn = ({
placeholder,
validationStatus,
type,
_showIconLeft,
_showIconRight,
...props
}: any) => {
return (
<Input {...props} validationStatus={validationStatus}>
{_showIconLeft && (
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
)}
<InputField placeholder={placeholder} type={type} />
{_showIconRight && (
<InputSlot>
<InputIcon as={EyeMediumIcon} />
</InputSlot>
)}
</Input>
);
};

InputBasic.argTypes = {
placeholder: {
control: 'text',
description: 'The Input field placeholder',
defaultValue: '',
},
type: {
control: 'select',
options: ['text', 'password'],
description: 'The Input field type',
defaultValue: 'text',
},
validationStatus: {
control: 'select',
options: ['initial', 'valid', 'invalid'],
description: 'The validation status of the Input component',
defaultValue: 'initial',
},
showValidationIcon: {
control: 'boolean',
description: 'Show the validation icon',
defaultValue: true,
},
isDisabled: {
control: 'boolean',
description: 'Disable the Input component',
defaultValue: false,
},
isReadOnly: {
control: 'boolean',
description: 'Read only the Input component',
defaultValue: false,
},
isFocused: {
control: 'boolean',
description: 'Focus the Input component',
defaultValue: false,
},
_showIconLeft: {
control: 'boolean',
description:
'Show icon left. \n _Note: this is not a prop of the `Input` component, just a representation of the `InputSlot` and `InputIcon` component for the Storybook playground._',
},
_showIconRight: {
control: 'boolean',
description:
'Show icon right. \n _Note: this is not a prop of the `Input` component, just a representation of the `InputSlot` and `InputIcon` component for the Storybook playground._',
},
} as Meta<typeof Input>['argTypes'];

InputBasic.args = {
placeholder: 'Input placeholder',
validationStatus: 'initial',
showValidationIcon: true,
type: 'text',
isDisabled: false,
isReadOnly: false,
isFocused: false,
_showIconLeft: false,
_showIconRight: false,
} as Meta<typeof Input>['args'];

export default InputBasic;
116 changes: 116 additions & 0 deletions apps/native-ui-storybook/components/Input/Variants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React from 'react';
import {
Input,
InputField,
InputIcon,
InputSlot,
ScrollView,
VStack,
} from '@utilitywarehouse/native-ui';
import { StoryFn } from '@storybook/react';
import { EmailMediumIcon } from '@utilitywarehouse/react-native-icons';
import { VariantTitle } from '../../docs/components';

const InputVariants: StoryFn = () => {
return (
<ScrollView>
<VStack gap="$4">
<VariantTitle title="Default">
<Input>
<InputField />
</Input>
</VariantTitle>
<VariantTitle title="With icon">
<Input>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField />
</Input>
</VariantTitle>
<VariantTitle title="With placeholder">
<Input>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" />
</Input>
</VariantTitle>
<VariantTitle title="With value">
<Input>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" value="filling the value" />
</Input>
</VariantTitle>
<VariantTitle title="Focused">
<Input isFocused>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" value="filling the value" />
</Input>
</VariantTitle>
<VariantTitle title="Type password">
<Input>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" value="filling the value" type="password" />
</Input>
</VariantTitle>
<VariantTitle title="Valid">
<Input validationStatus="valid">
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" />
</Input>
</VariantTitle>
<VariantTitle title="Valid focused">
<Input validationStatus="valid" isFocused>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" />
</Input>
</VariantTitle>
<VariantTitle title="Invalid">
<Input validationStatus="invalid">
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" />
</Input>
</VariantTitle>
<VariantTitle title="Invalid focused">
<Input validationStatus="invalid" isFocused>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" />
</Input>
</VariantTitle>
<VariantTitle title="Disabled">
<Input isDisabled>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" />
</Input>
</VariantTitle>
<VariantTitle title="Readonly">
<Input isReadOnly>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" readOnly />
</Input>
</VariantTitle>
</VStack>
</ScrollView>
);
};

export default InputVariants;
1 change: 1 addition & 0 deletions apps/native-ui-storybook/docs/native-ui/guides/tokens.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ By default these spacing value can be referenced by the padding, margin, and top
| `$10` | 40 |
| `$11` | 44 |
| `$12` | 48 |
| `$14` | 56 |
| `$16` | 64 |
| `$20` | 80 |
| `$24` | 96 |
Expand Down
Loading
Loading