Skip to content

Commit

Permalink
feat: TET-895 toggle (#136)
Browse files Browse the repository at this point in the history
* feat: TET-895 toggle

* feat: TET-895 toggle

* feat: TET-895 toggle

---------

Co-authored-by: Marta Kozina <marta.kozina.external@jetbrains.com>
Co-authored-by: Tomasz P. <tpiechaczek@tuta.io>
  • Loading branch information
3 people committed Sep 9, 2024
1 parent c0ca247 commit 99f8a15
Show file tree
Hide file tree
Showing 15 changed files with 692 additions and 14 deletions.
2 changes: 1 addition & 1 deletion src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import styled from '@xstyled/styled-components';
import { forwardRef, useCallback, useId, useMemo } from 'react';

import type { CheckboxProps } from './Checkbox.props';
import { useIndeterminate } from './hooks';
import { stylesBuilder } from './stylesBuilder';
import { HelperText } from '../HelperText';

import { useIndeterminate } from '@/hooks';
import { extractInputProps } from '@/services';
import { tet } from '@/tetrisly';
import { MarginProps } from '@/types/MarginProps';
Expand Down
1 change: 0 additions & 1 deletion src/components/Checkbox/hooks/index.ts

This file was deleted.

11 changes: 0 additions & 11 deletions src/components/Checkbox/hooks/useIconChecked.ts

This file was deleted.

22 changes: 22 additions & 0 deletions src/components/Toggle/Toggle.props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { InputHTMLAttributes } from 'react';

import type { ToggleConfig } from './Toggle.styles';
import { HelperTextProps } from '../HelperText';

export type ToggleProps = {
isIndeterminate?: boolean;
isChecked?: boolean;
size?: 'small' | 'large';
state?: 'disabled';
custom?: ToggleConfig;
} & Omit<
InputHTMLAttributes<HTMLInputElement>,
'checked' | 'disabled' | 'color' | 'type' | 'size'
> &
(
| { label?: string; helperText?: never }
| {
label: string;
helperText?: Pick<HelperTextProps, 'text'>;
}
);
104 changes: 104 additions & 0 deletions src/components/Toggle/Toggle.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import type { Meta, StoryObj } from '@storybook/react';
import { useLayoutEffect, useRef, useState } from 'react';

import { Toggle } from './Toggle';

import { TetDocs } from '@/docs-components/TetDocs';
import { ToggleDocs } from '@/docs-components/ToggleDocs.tsx';
import { tet } from '@/tetrisly';

const meta = {
title: 'Toggle',
component: Toggle,
tags: ['autodocs'],
argTypes: {
state: {
control: {
type: 'select',
options: [undefined, 'disabled'],
},
},
},
parameters: {
docs: {
description: {
component:
'A visual representation of the switch that allows the user to choose between two states, such as on and off or enable and disable. Toggles are often used in forms or settings to represent binary options and provide clear visual feedback of the active state.',
},
page: () => (
<TetDocs docs="https://docs.tetrisly.com/components/list/toggle">
<ToggleDocs />
</TetDocs>
),
},
},
} satisfies Meta<typeof Toggle>;

export default meta;
type Story = StoryObj<typeof meta>;
export const Checked: Story = {
args: {
isChecked: true,
},
};

export const Disabled: Story = {
args: {
state: 'disabled',
},
};

export const Indeterminate = () => {
const [mainChecked, setMainChecked] = useState(false);
const [toggle1Value, setToggle1Value] = useState(true);
const [toggle2Value, setToggle2Value] = useState(false);
const isInitialRender = useRef(true);

useLayoutEffect(() => {
if (isInitialRender.current) {
isInitialRender.current = false;
return;
}
setToggle1Value(mainChecked);
setToggle2Value(mainChecked);
}, [mainChecked]);

return (
<tet.div
display="flex"
flexDirection="column"
gap="$space-component-gap-small"
>
<Toggle
size="large"
isIndeterminate={toggle1Value || toggle2Value}
isChecked={mainChecked || (toggle1Value && toggle2Value)}
onChange={() => setMainChecked((prevValue) => !prevValue)}
label="Main label"
/>
<Toggle
isChecked={toggle1Value}
onChange={() => setToggle1Value((prevValue) => !prevValue)}
label="Label 1"
/>
<Toggle
isChecked={toggle2Value}
onChange={() => setToggle2Value((prevValue) => !prevValue)}
label="Label 2"
/>
</tet.div>
);
};

export const Label: Story = {
args: {
label: 'Label',
},
};

export const HelperText: Story = {
args: {
label: 'Label',
helperText: { text: 'Helper text' },
},
};
158 changes: 158 additions & 0 deletions src/components/Toggle/Toggle.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { SystemProps } from '@xstyled/styled-components';

import { HelperTextConfig } from '../HelperText/HelperText.styles';

import { BaseProps } from '@/types/BaseProps';

type ToggleSize = { size?: Record<'small' | 'large', BaseProps> };
export type ToggleConfig = {
innerElements?: {
toggle?: {
input?: SystemProps;
slider?: BaseProps & ToggleSize;
toggleOval?: BaseProps & ToggleSize;
};
labelContainer?: BaseProps;
label?: BaseProps;
helperText?: HelperTextConfig;
};
} & BaseProps;

export const defaultConfig = {
display: 'inline-flex',
flexDirection: 'column',
alignItems: 'flex-start',
gap: '$space-component-gap-xSmall',
opacity: {
_: 1,
disabled: 0.5,
},
innerElements: {
toggle: {
toggleOval: {
size: {
large: {
w: '36px',
h: '20px',
},
small: {
w: '28px',
h: '16px',
},
},
p: '$space-component-padding-2xSmall',
backgroundColor: {
_: '$color-interaction-disabled-normal',
hover: '$color-interaction-disabled-hover',
focus: '$color-interaction-disabled-focus',
active: '$color-interaction-disabled-active',
disabled: '$color-interaction-disabled-normal',
selected: {
_: '$color-interaction-default-normal',
hover: '$color-interaction-default-hover',
focus: '$color-interaction-default-focus',
active: '$color-interaction-default-active',
disabled: '$color-interaction-default-normal',
},
indeterminate: {
_: '$color-interaction-default-normal',
hover: '$color-interaction-default-hover',
focus: '$color-interaction-default-focus',
active: '$color-interaction-default-active',
disabled: '$color-interaction-default-normal',
},
},
transition: '0.2s',
borderRadius: '100px',
display: 'flex',
position: 'relative',
alignItems: 'center',
outlineColor: {
focusWithin: '$color-interaction-focus-default',
},
outlineWidth: {
focusWithin: '$border-width-focus',
},
outlineStyle: {
focusWithin: 'solid',
},
outlineOffset: {
focusWithin: '$border-width-small',
},
},
slider: {
size: {
large: {
w: {
_: '16px',
indeterminate: '15px',
},
h: {
_: '16px',
indeterminate: '1.5px',
},
transform: {
selected: 'translateX(16px)',
indeterminate: 'translateX(8px)',
},
},
small: {
w: {
_: '12px',
indeterminate: '10px',
},
h: {
_: '12px',
indeterminate: '1.5px',
},
transform: {
selected: 'translateX(12px)',
indeterminate: 'translateX(7px)',
},
},
},
transition: 'transform 0.2s ease-in-out',
backgroundColor: '$color-whiteA-0',
borderRadius: '$border-radius-full',
borderWidth: '$border-width-small',
borderStyle: '$border-style-solid',
borderColor: '$color-border-defaultA',
boxShadow: '$elevation-bottom-100',
position: 'absolute',
},
input: {
borderRadius: '100px',
w: '100%',
h: '100%',
appearance: 'none',
zIndex: 1,
cursor: {
_: 'pointer',
disabled: 'default',
},
},
},
labelContainer: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
gap: '$space-component-gap-medium',
text: '$typo-body-medium',
color: '$color-content-primary',
},
label: {
cursor: {
_: 'pointer',
disabled: 'default',
},
},
helperText: {
paddingLeft: '$space-component-padding-2xLarge',
cursor: 'default',
},
},
} satisfies ToggleConfig;

export const toggleStyles = {
defaultConfig,
};
Loading

0 comments on commit 99f8a15

Please sign in to comment.