Skip to content

Commit

Permalink
Add Toggle
Browse files Browse the repository at this point in the history
  • Loading branch information
Barry127 committed Nov 14, 2020
1 parent 67140c7 commit c5471bb
Show file tree
Hide file tree
Showing 15 changed files with 628 additions and 4 deletions.
17 changes: 14 additions & 3 deletions docsComponents/Nav.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import clsx from 'clsx';
import { Block, Col, Container, Link, Row, useDarkMode } from 'maeven';
import { moon, sun } from 'icon-packs/feather';
import { Block, Col, Container, Link, Toggle, Row, useDarkMode } from 'maeven';
import NextLink from 'next/link';
import { useRouter } from 'next/router';
import React, { FC } from 'react';
Expand All @@ -11,7 +12,7 @@ export const Nav: FC = () => {

return (
<Block element="nav" background="textBackground" className={classes.nav}>
<Container fluid>
<Container className={classes.container} fluid>
<Row align="center">
<Col>
<NextLink href="/">
Expand Down Expand Up @@ -42,7 +43,17 @@ export const Nav: FC = () => {
<Link href="https://github.com/Barry127/maeven" target="_blank">
GitHub
</Link>
<input type="checkbox" checked={isDark} onChange={toggleDark} />
<div className={classes['toggle-container']}>
&nbsp;
<Toggle
size="sm"
checked={!isDark}
onChange={toggleDark}
onIcon={sun}
offIcon={moon}
className={classes.toggle}
/>
</div>
</Col>
</Row>
</Container>
Expand Down
23 changes: 23 additions & 0 deletions docsComponents/nav.module.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import '../src/common/mixins';

.nav {
z-index: 10;
position: sticky;
Expand Down Expand Up @@ -55,3 +57,24 @@
padding-right: 0;
}
}

.toggle-container {
display: inline-block;
margin-left: 1rem;
position: relative;
width: 40px;
}

.toggle {
left: 0;
position: absolute;
top: 0;
transform: scale(0.75);
transform-origin: top left;
}

@media (max-width: $mvn-media-lg - 0.01) {
.container {
padding: 0;
}
}
2 changes: 2 additions & 0 deletions pages/docs/components/Form/Form.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const FullForm = () => {
return (
<Form onSubmit={(ev) => ev.preventDefault()}>
<TextInput label="Username" icon={user} />
<PasswordInput label="Password" icon={lock} />
<TextArea label="Comment" />
<FormField>
<Button type="submit" buttonType="primary">
Save
Expand Down
79 changes: 79 additions & 0 deletions pages/docs/components/Form/Toggle.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Description, DoDont, Meta, Props } from 'docsComponents';

<Meta title="Toggle" />

# Toggle

<Description of="Toggle" />

## Props

<Props of="Toggle" />

## Examples

### Basic

A basic usage of Toggle.

```js live
<Toggle>Children are placed next to the toggle.</Toggle>
```

### Disabled

Toggles can be disabled.

```js live
<>
<Toggle disabled checked />
</>
```

### Icons

Toggles can have icons to indicate on and off states.

```js live
// import { check, x } from 'icon-packs/feather';

<Toggle offIcon={x} onIcon={check} />
```

### Size

Toggles can have different sizes.

```js live
// import { check, x } from 'icon-packs/feather';

<Row gutter={1} align="center">
<Col span={8}>
<Toggle offIcon={x} onIcon={check} size="sm">
sm
</Toggle>
</Col>
<Col span={8}>
<Toggle offIcon={x} onIcon={check} size="md">
md
</Toggle>
</Col>
<Col span={8}>
<Toggle offIcon={x} onIcon={check} size="lg">
lg
</Toggle>
</Col>
</Row>
```

## Design Guidelines

<DoDont
do={[
'Use a toggle when the effect of state changes are immediate e.g. switch color theme.',
'Make use of a label when possible otherwise set aria-label.'
]}
dont={[
'Use a toggle to check multiple items in a list. Checkboxes are used in that case.'
]}
/>
1 change: 1 addition & 0 deletions pages/docs/nav.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"PasswordInput": "/docs/components/Form/PasswordInput",
"TextArea": "/docs/components/Form/TextArea",
"TextInput": "/docs/components/Form/TextInput",
"Toggle": "/docs/components/Form/Toggle",
"TypeAheadInput": "/docs/components/Form/TypeAheadInput"
}
},
Expand Down
1 change: 0 additions & 1 deletion src/components/Button/Button/button.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ $semanticTypes: ('primary', 'success', 'warning', 'danger');
&:focus {
outline: none;
z-index: 1;
border-color: var(--mvn-primary);
box-shadow: 0 0 0 var(--mvn-size-outline) var(--mvn-color-outline);
}

Expand Down
26 changes: 26 additions & 0 deletions src/components/Form/TextArea/TextArea.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,32 @@ describe('Textarea', () => {
});
});

describe('label', () => {
it('has no aria-describedby by default', () => {
render(<TextArea />);
const textarea = document.querySelector('textarea');
expect(textarea).not.toHaveAttribute('aria-describedby');
});

it('has no aria-describedby by default with autoSize off', () => {
render(<TextArea autoSize={false} />);
const textarea = document.querySelector('textarea');
expect(textarea).not.toHaveAttribute('aria-describedby');
});

it('sets aria-describedby by when textarea has a label', () => {
render(<TextArea label="Label" />);
const textarea = document.querySelector('textarea');
expect(textarea).toHaveAttribute('aria-describedby');
});

it('sets aria-describedby by when textarea has a label with autoSize off', () => {
render(<TextArea autoSize={false} label="Label" />);
const textarea = document.querySelector('textarea');
expect(textarea).toHaveAttribute('aria-describedby');
});
});

describe('size', () => {
it('is md by default', () => {
render(<TextArea />);
Expand Down
2 changes: 2 additions & 0 deletions src/components/Form/TextArea/TextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
maxRows={maxRows}
ref={ref}
rows={rows}
aria-describedby={label ? labelId : undefined}
/>
) : (
<textarea
Expand All @@ -66,6 +67,7 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
id={id}
ref={ref}
rows={rows}
aria-describedby={label ? labelId : undefined}
/>
)}
</label>
Expand Down
14 changes: 14 additions & 0 deletions src/components/Form/TextInput/TextInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,20 @@ describe('TextInput', () => {
});
});

describe('label', () => {
it('has no aria-describedby by default', () => {
render(<TextInput />);
const input = document.querySelector('input');
expect(input).not.toHaveAttribute('aria-describedby');
});

it('sets aria-describedby by when input has a label', () => {
render(<TextInput label="Label" />);
const input = document.querySelector('input');
expect(input).toHaveAttribute('aria-describedby');
});
});

describe('loading', () => {
it('is not loading by default', () => {
render(<TextInput iconRight={activity} />);
Expand Down
1 change: 1 addition & 0 deletions src/components/Form/TextInput/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
: undefined
}
type={type}
aria-describedby={label ? labelId : undefined}
/>
{icon && (
<Icon className={clsx(classes.icon, classes.left)} icon={icon} />
Expand Down
119 changes: 119 additions & 0 deletions src/components/Form/Toggle/Toggle.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { render } from '@testing-library/react';
import { activity } from 'icon-packs/cjs/feather';
import React, { createRef } from 'react';
import { Toggle } from './Toggle';

describe('Toggle', () => {
it('renders an input element and sets child text', () => {
render(<Toggle>Hello world!</Toggle>);
const container = document.querySelector('.mvn--toggle');
const input = document.querySelector('input');
expect(container).toBeInTheDocument();
expect(input).toBeInTheDocument();
expect(container).toHaveTextContent('Hello world!');
});

it('sets className', () => {
render(<Toggle className="toggle-class">Hello world!</Toggle>);
const element = document.querySelector('.mvn--toggle');
expect(element).toHaveClass('toggle-class');
});

it('passes props', () => {
render(
<Toggle id="ToggleId" data-test="toggle-data">
Hello world!
</Toggle>
);
const element = document.querySelector('input');
expect(element).toHaveAttribute('id', 'ToggleId');
expect(element).toHaveAttribute('data-test', 'toggle-data');
});

describe('disabled', () => {
it('is not disabled by default', () => {
render(<Toggle />);
const input = document.querySelector('input');
expect(input).not.toHaveAttribute('disabled');
});

it('sets disabled', () => {
render(<Toggle disabled />);
const input = document.querySelector('input');
expect(input).toHaveAttribute('disabled');
});
});

describe('icons', () => {
it('has no icon by default', () => {
render(<Toggle />);
const svg = document.querySelector('svg');
const img = document.querySelector('img');
expect(svg).not.toBeInTheDocument();
expect(img).not.toBeInTheDocument();
});

it('sets offIcon', () => {
render(<Toggle offIcon={activity} />);
const svg = document.querySelectorAll('svg');
expect(svg).toHaveLength(1);
});

it('sets onIcon', () => {
render(<Toggle onIcon={activity} />);
const svg = document.querySelectorAll('svg');
expect(svg).toHaveLength(1);
});

it('sets both icons', () => {
render(<Toggle offIcon={activity} onIcon={activity} />);
const svg = document.querySelectorAll('svg');
expect(svg).toHaveLength(2);
});
});

describe('label', () => {
it('has no aria-describedby by default', () => {
render(<Toggle />);
const input = document.querySelector('input');
expect(input).not.toHaveAttribute('aria-describedby');
});

it('sets aria-describedby by when input has a label', () => {
render(<Toggle label="Label" />);
const input = document.querySelector('input');
expect(input).toHaveAttribute('aria-describedby');
});
});

describe('size', () => {
it('is md by default', () => {
render(<Toggle />);
const container = document.querySelector('.mvn--toggle');
expect(container).toHaveClass('md');
expect(container).not.toHaveClass('sm');
expect(container).not.toHaveClass('lg');
});

it('sets sm', () => {
render(<Toggle size="sm" />);
const container = document.querySelector('.mvn--toggle');
expect(container).toHaveClass('sm');
});

it('sets lg', () => {
render(<Toggle size="lg" />);
const container = document.querySelector('.mvn--toggle');
expect(container).toHaveClass('lg');
});
});

describe('forwarding ref', () => {
it('sets ref', () => {
const ref = createRef<HTMLInputElement>();
render(<Toggle ref={ref} />);
const element = document.querySelector('input');
expect(ref.current).toBe(element);
});
});
});
Loading

0 comments on commit c5471bb

Please sign in to comment.