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
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 @@ -145,6 +145,7 @@ const preview: Preview = {
'Checkbox',
'HStack',
'Icons',
'Input',
'Pressable',
'Radio',
'Spinner',
Expand Down
131 changes: 131 additions & 0 deletions apps/native-ui-storybook/components/Input/Docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { Meta, Primary, Controls, Story, Canvas } from '@storybook/blocks';
import * as BadgeStories from './Input.stories';
import {
Input,
InputField,
InputIcon,
InputSlot,
Center,
NativeUIProvider,
} from '@utilitywarehouse/native-ui';
import { EmailMediumIcon } from '@utilitywarehouse/react-native-icons';

<Meta of={BadgeStories} />

# 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

<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 |
| ---- | ---- | ------- | ----------- |
| - | - | - | - |

## 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 };
87 changes: 87 additions & 0 deletions apps/native-ui-storybook/components/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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,
},
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,
isFocused: false,
_showIconLeft: false,
_showIconRight: false,
} as Meta<typeof Input>['args'];

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

const InputVariants: StoryFn = () => {
return (
<ScrollView>
<Box gap={8}>
<Text>Input</Text>
<Input>
<InputField />
</Input>
<Text>Input with icon</Text>
<Input>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField />
</Input>
<Text>Input with placeholder</Text>
<Input>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" />
</Input>
<Text>Input with value</Text>
<Input>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" value="filling the value" />
</Input>
<Text>Input focused</Text>
<Input isFocused>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" value="filling the value" />
</Input>
<Text>Input - Password</Text>
<Input>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" value="filling the value" type="password" />
</Input>
<Text>Input valid</Text>
<Input validationStatus="valid">
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" />
</Input>
<Text>Input valid focused</Text>
<Input validationStatus="valid" isFocused>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" />
</Input>
<Text>Input invalid</Text>
<Input validationStatus="invalid">
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" />
</Input>
<Text>Input invalid focused</Text>
<Input validationStatus="invalid" isFocused>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" />
</Input>
<Text>Input disabled</Text>
<Input isDisabled>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField placeholder="Input placeholder" />
</Input>
{/* <Text>Input readonly</Text> TODO: Show example when readonly is supported
See: https://github.com/gluestack/gluestack-ui/issues/2214
<Input
isReadOnly
sx={{
':readOnly': {
opacity: 0.5,
borderColor: 'transparent',
borderBottomColor: 'transparent',
},
}}
>
<InputSlot>
<InputIcon as={EmailMediumIcon} />
</InputSlot>
<InputField
placeholder="Input placeholder"
readOnly
/>
</Input> */}
</Box>
</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 @@ -216,6 +216,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
2 changes: 1 addition & 1 deletion apps/native-ui-storybook/docs/native-ui/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ yarn add @utilitywarehouse/native-ui
To install the peer dependencies, you can run the following command:

```console
yarn add @utilitywarehouse/colour-system @utilitywarehouse/react-native-icons react-native-svg@14.1.0 react-native-reanimated
yarn add @utilitywarehouse/colour-system @utilitywarehouse/react-native-icons react-native-svg react-native-reanimated
```

For more information on how to install and configure `react-native-svg` and `react-native-reanimated`, please refer to the [react-native-svg](https://github.com/software-mansion/react-native-svg?tab=readme-ov-file#installation)
Expand Down
Loading
Loading