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: TET-895 toggle #136

Merged
merged 5 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
MartaKozina010 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading