Skip to content

Commit

Permalink
feat: simple Button api (#408)
Browse files Browse the repository at this point in the history
  • Loading branch information
jordmccord authored Jul 11, 2024
1 parent 4961af9 commit a3a7459
Show file tree
Hide file tree
Showing 14 changed files with 309 additions and 157 deletions.
5 changes: 5 additions & 0 deletions .changeset/violet-falcons-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@utilitywarehouse/native-ui': patch
---

Add simple `Button` API with loading, icon and disabled props
138 changes: 106 additions & 32 deletions apps/native-ui-storybook/components/Button/Button.docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,9 @@ The `Button` component is used to trigger an action or event, such as submitting
- [Playground](#playground)
- [Usage](#usage)
- [Props](#props)
- [Components](#components)
- [ButtonGroup](#buttongroup)
- [ButtonIcon](#buttonicon)
- [ButtonSpinner](#buttonspinner)
- [Variants](#variants)
- [Advanced Usage](#advanced-usage)
- [Components](#components)

## Playground

Expand All @@ -50,39 +48,40 @@ The `Button` component is used to trigger an action or event, such as submitting
<Canvas>
<NativeUIProvider>
<Center>
<Button colorScheme="green">
<ButtonText>Button</ButtonText>
</Button>
<Button colorScheme="green">Button</Button>
</Center>
</NativeUIProvider>
</Canvas>

```tsx
import { Button, ButtonText } from '@utilitywarehouse/native-ui';
import { Button } from '@utilitywarehouse/native-ui';

const MyComponent = () => {
const handlePress = () => {
console.log('Button pressed');
};
return (
<Button onPress={handlePress} colorScheme="green">
<ButtonText>Button</ButtonText>
Button
</Button>
);
};
```

## Props

| Property | Type | Description | Default |
| ------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------- | -------- |
| `onPress` | `() => void` | Callback function to be called when the button is pressed. | - |
| `variant` | `'solid' \| 'outline' \| 'ghost'` | The variant of the button. | 'solid' |
| `size` | `'small' \| 'medium' \| 'large'` | The size of the button. | 'medium' |
| `colorScheme` | `'cyan' \| 'greeb' \| 'red' \| 'grey' \| 'gold'` | The colour scheme of the button. | 'cyan' |
| `inverted` | `boolean` | Changes the button to an inverted state. (To only be used on `midnight` or `purple` backgrounds). | `false` |
| `isDisabled` | `boolean` | Disables the button. | `false` |
| `isPressed` | `boolean` | Changes the button to a pressed state. | `false` |
| Property | Type | Description | Default |
| -------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------- | -------- |
| `onPress` | `() => void` | Callback function to be called when the button is pressed. | - |
| `variant` | `'solid' \| 'outline' \| 'ghost'` | The variant of the button. | 'solid' |
| `size` | `'small' \| 'medium' \| 'large'` | The size of the button. | 'medium' |
| `colorScheme` | `'cyan' \| 'green' \| 'red' \| 'grey' \| 'gold'` | The colour scheme of the button. | 'cyan' |
| `icon` | `ReactNode` | The icon to be displayed in the button. _Note: Can't use with advanced usage._ | - |
| `iconPosition` | `'left' \| 'right'` | The position of the icon in the button. _Note: Can't use with advanced usage._ | 'left' |
| `inverted` | `boolean` | Changes the button to an inverted state. (To only be used on `midnight` or `purple` backgrounds). | `false` |
| `loading` | `boolean` | Shows a loading spinner in the button. _Note: Can't use with advanced usage._ | `false` |
| `disabled` | `boolean` | Disables the button. | `false` |
| `isPressed` | `boolean` | Changes the button to a pressed state. | `false` |

#### Descendants Styling Props

Expand All @@ -94,11 +93,52 @@ Props to style the child components.
| `_icon` | Prop to style ButtonIcon Component |
| `_spinner` | Prop to style ButtonSpinner Component |

## Variants

<Canvas of={Stories.PlaygroundVariants} />

## Advanced Usage

For more advanced usage, you can use the `ButtonText`, `ButtonIcon` and `ButtonSpinner` components to customise the button.

<Canvas>
<NativeUIProvider>
<Center>
<Button colorScheme="cyan">
<ButtonIcon as={AddSmallIcon} />
<ButtonText>Button</ButtonText>
</Button>
</Center>
</NativeUIProvider>
</Canvas>

```tsx
import { Button, ButtonIcon, ButtonText, ButtonSpinner } from '@utilitywarehouse/native-ui';
import { AddSmallIcon } from '@utilitywarehouse/react-native-icons';

const MyComponent = ({ doSomething }) => {
const [loading, setLoading] = useState(false);
const handleClick = () => {
setLoading(true);
doSomething();
};
return (
<Button colorScheme="cyan" disabled={loading}>
{loading ? <ButtonSpinner /> : <ButtonIcon as={AddSmallIcon} />}
<ButtonText onPress={handleClick}>Button</ButtonText>
</Button>
);
};
```

## Components

The `Button` component is composed of the following components under the hood and can be used to customise the button further:

- [ButtonGroup](#buttongroup)
- [ButtonIcon](#buttonicon)
- [ButtonSpinner](#buttonspinner)
- [ButtonText](#buttontext) (Advanced usage only)
- [ButtonIcon](#buttonicon) (Advanced usage only)
- [ButtonSpinner](#buttonspinner) (Advanced usage only)

### `ButtonGroup`

Expand All @@ -123,19 +163,17 @@ The `ButtonGroup` component is used to group multiple buttons together.
</Canvas>

```tsx
import { ButtonGroup, Button, ButtonText } from '@utilitywarehouse/native-ui';
import { ButtonGroup, Button } from '@utilitywarehouse/native-ui';

const MyComponent = () => {
return (
<ButtonGroup flexDirection="column">
<Button colorScheme="cyan">
<ButtonText>Button 1</ButtonText>
</Button>
<Button colorScheme="cyan">Button 1</Button>
<Button colorScheme="grey" variant="outline">
<ButtonText>Button 2</ButtonText>
Button 2
</Button>
<Button colorScheme="red" variant="ghost">
<ButtonText>Button 3</ButtonText>
Button 3
</Button>
</ButtonGroup>
);
Expand All @@ -156,6 +194,9 @@ const MyComponent = () => {

The `ButtonIcon` component is used to add an icon to the button.

**Note:** The `ButtonIcon` component should be used only when utilising [advanced usage](#advanced-usage) with the `Button` component.
We recommend using the `icon` prop on the `Button` component for simpler usage.

<Canvas>
<NativeUIProvider>
<Center>
Expand All @@ -181,32 +222,65 @@ const MyComponent = () => {
};
```

### `ButtonSpinner`
### `ButtonText`

The `ButtonSpinner` component is used to add a spinner to the button and show loading state.
The `ButtonText` component is used to add text to the button.

**Note:** The `ButtonText` component should be used only when utilising [advanced usage](#advanced-usage) with the `Button` component.
We recommend using the children prop on the `Button` component for simpler usage.

<Canvas>
<NativeUIProvider>
<Center>
<Button colorScheme="cyan">
<ButtonSpinner />
<ButtonText>Button</ButtonText>
</Button>
</Center>
</NativeUIProvider>
</Canvas>

```tsx
import { Button, ButtonSpinner, ButtonText } from '@utilitywarehouse/native-ui';
import { Button, ButtonText } from '@utilitywarehouse/native-ui';

const MyComponent = () => {
return (
<Button colorScheme="cyan">
<ButtonSpinner />
<ButtonText>Button</ButtonText>
</Button>
);
};
```

<Canvas of={Stories.PlaygroundVariants} />
### `ButtonSpinner`

The `ButtonSpinner` component is used to add a spinner to the button and show loading state.

> This component is used for buttons that trigger long-running actions, like form submissions or data loading.
> The Button's appearance resembles a disabled state, as user interaction is prevented during the loading process.
**Note:** The `ButtonSpinner` component should be used only when utilising [advanced usage](#advanced-usage) with the `Button` component and
should inlude the `disabled` prop. We recommend using the `loading` prop on the `Button` component for simpler usage.

<Canvas>
<NativeUIProvider>
<Center>
<Button colorScheme="cyan" disabled>
<ButtonSpinner />
<ButtonText>Button</ButtonText>
</Button>
</Center>
</NativeUIProvider>
</Canvas>

```tsx
import { Button, ButtonSpinner, ButtonText } from '@utilitywarehouse/native-ui';

const MyComponent = ({ loading }) => {
return (
<Button colorScheme="cyan" disabled>
{loading ? <ButtonSpinner /> : null}
<ButtonText>Button</ButtonText>
</Button>
);
};
```
80 changes: 35 additions & 45 deletions apps/native-ui-storybook/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
import { Box, Button, ButtonIcon, ButtonSpinner, ButtonText } from '@utilitywarehouse/native-ui';
import { Box, Button } from '@utilitywarehouse/native-ui';
import React from 'react';
import { AddSmallIcon } from '@utilitywarehouse/react-native-icons';
import * as Icons from '@utilitywarehouse/react-native-icons';
import { StoryFn } from '@storybook/react';
import ScrollWrap from '../../docs/components/ScrollWrap';

const ButtonBasic: StoryFn = ({
size,
variant,
size,
colorScheme,
isDisabled,
disabled,
loading,
iconPosition,
icon: _icon,
inverted,
_ButtonText,
_showIcon,
_showLoading,
_loadingPosition,
_iconPosition,
_backgroundColor,
}: any) => {
// @ts-expect-error - This is a playground
const icon = _icon === 'none' ? undefined : Icons[_icon];
return (
<Box height={68} width="100%">
<ScrollWrap backgroundColor={_backgroundColor}>
<Button
size={size}
variant={variant}
colorScheme={colorScheme}
isDisabled={isDisabled}
disabled={disabled}
inverted={inverted}
iconPosition={iconPosition}
icon={icon}
loading={loading}
>
{_showIcon && _iconPosition === 'left' && <ButtonIcon as={AddSmallIcon} />}
{_showLoading && _loadingPosition === 'left' && <ButtonSpinner />}
<ButtonText>{_ButtonText}</ButtonText>
{_showLoading && _loadingPosition === 'right' && <ButtonSpinner />}
{_showIcon && _iconPosition === 'right' && <ButtonIcon as={AddSmallIcon} />}
{_ButtonText}
</Button>
</ScrollWrap>
</Box>
Expand All @@ -54,46 +54,37 @@ ButtonBasic.argTypes = {
control: 'select',
description: 'The color scheme of the button.',
},
inverted: {
type: 'boolean',
control: 'boolean',
description:
'To set the button to be inverted. (To only be used on `midnight` or `purple` backgrounds)',
},
isDisabled: {
disabled: {
type: 'boolean',
control: 'boolean',
description: 'To manually set disable to the button.',
},
_ButtonText: {
type: 'string',
control: 'text',
description:
'The text component for the button.\n _Note: this is not a prop of the `Button` component, just a representation of the `ButtonText` component for the Storybook playground._',
},
_showIcon: {
inverted: {
type: 'boolean',
control: 'boolean',
description:
'To show or hide the icon component for the button.\n _Note: this is not a prop of the `Button` component, just a representation of the `ButtonIcon` component for the Storybook playground._',
'To set the button to be inverted. (To only be used on `midnight` or `purple` backgrounds)',
},
_iconPosition: {
icon: {
options: ['none', ...Object.keys(Icons).filter(icon => icon.includes('Small'))],
control: 'select',
description: 'The icon component for the button.',
},
iconPosition: {
options: ['left', 'right'],
control: 'select',
description:
'The position of the icon component in the button.\n _Note: this is not a prop of the `Button` component, just a representation of the `ButtonIcon` component for the Storybook playground._',
description: 'The position of the icon component in the button.',
},
_showLoading: {
loading: {
type: 'boolean',
control: 'boolean',
description:
'To show or hide the loading spinner component for the button.\n _Note: this is not a prop of the `Button` component, just a representation of the `ButtonSpinner` component for the Storybook playground._',
description: 'To show or hide the loading spinner component for the button.',
},
_loadingPosition: {
options: ['left', 'right'],
control: 'select',
_ButtonText: {
type: 'string',
control: 'text',
description:
'The position of the loading spinner component in the button.\n _Note: this is not a prop of the `Button` component, just a representation of the `ButtonSpinner` component for the Storybook playground._',
'The text component for the button.\n _Note: this is not a prop of the `Button` component, just a representation of the `ButtonText` component for the Storybook playground._',
},
_backgroundColor: {
options: ['default', 'midnight', 'purple'],
Expand All @@ -107,16 +98,15 @@ ButtonBasic.args = {
size: 'medium',
variant: 'solid',
colorScheme: 'cyan',
isDisabled: false,
disabled: false,
inverted: false,
loading: false,
icon: 'none',
iconPosition: 'left',
_ButtonText: 'Example',
_showIcon: true,
_iconPosition: 'left',
_showLoading: false,
_loadingPosition: 'left',
_backgroundColor: 'default',
};

export default ButtonBasic;

export { ButtonText, Button };
export { Button };
Loading

0 comments on commit a3a7459

Please sign in to comment.