mirror of
https://github.com/glitch-soc/mastodon.git
synced 2026-03-29 03:00:33 +02:00
Toggle component (#37582)
This commit is contained in:
@@ -0,0 +1,70 @@
|
|||||||
|
.input {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
--diameter: 20px;
|
||||||
|
--padding: 2px;
|
||||||
|
--transition: 0.2s ease-in-out;
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 9999px;
|
||||||
|
width: calc(var(--diameter) * 2);
|
||||||
|
background: var(--color-bg-tertiary);
|
||||||
|
padding: var(--padding);
|
||||||
|
transition: background var(--transition);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle::before {
|
||||||
|
content: '';
|
||||||
|
height: var(--diameter);
|
||||||
|
width: var(--diameter);
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: var(--color-text-on-brand-base);
|
||||||
|
box-shadow: 0 2px 4px 0 color-mix(var(--color-black), transparent 75%);
|
||||||
|
transition: transform var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.toggle,
|
||||||
|
.toggle::before {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:checked + .toggle {
|
||||||
|
background: var(--color-bg-brand-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:checked:is(:hover, :focus) + .toggle {
|
||||||
|
background: var(--color-bg-brand-base-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:focus-visible + .toggle {
|
||||||
|
outline: var(--outline-focus-default);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:checked + .toggle::before {
|
||||||
|
transform: translateX(calc(var(--diameter) - (var(--padding) * 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:disabled + .toggle {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global([dir='rtl']) .input:checked + .toggle::before {
|
||||||
|
transform: translateX(calc(-1 * (var(--diameter) - (var(--padding) * 2))));
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
import { PlainToggleField, ToggleField } from './toggle_field';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Form Fields/ToggleField',
|
||||||
|
component: ToggleField,
|
||||||
|
args: {
|
||||||
|
label: 'Label',
|
||||||
|
hint: 'This is a description of this form field',
|
||||||
|
disabled: false,
|
||||||
|
size: 20,
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
size: {
|
||||||
|
control: { type: 'range', min: 10, max: 40, step: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render(args) {
|
||||||
|
// Component styles require a wrapper class at the moment
|
||||||
|
return (
|
||||||
|
<div className='simple_form'>
|
||||||
|
<ToggleField {...args} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof ToggleField>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Simple: Story = {};
|
||||||
|
|
||||||
|
export const Required: Story = {
|
||||||
|
args: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Optional: Story = {
|
||||||
|
args: {
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithError: Story = {
|
||||||
|
args: {
|
||||||
|
required: false,
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Disabled: Story = {
|
||||||
|
args: {
|
||||||
|
disabled: true,
|
||||||
|
checked: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Plain: Story = {
|
||||||
|
render(props) {
|
||||||
|
return <PlainToggleField {...props} />;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Small: Story = {
|
||||||
|
args: {
|
||||||
|
size: 12,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Large: Story = {
|
||||||
|
args: {
|
||||||
|
size: 36,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import type { ComponentPropsWithoutRef, CSSProperties } from 'react';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import classes from './toggle.module.css';
|
||||||
|
import type { CommonFieldWrapperProps } from './wrapper';
|
||||||
|
import { FormFieldWrapper } from './wrapper';
|
||||||
|
|
||||||
|
type Props = Omit<ComponentPropsWithoutRef<'input'>, 'type'> & {
|
||||||
|
size?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ToggleField = forwardRef<
|
||||||
|
HTMLInputElement,
|
||||||
|
Props & CommonFieldWrapperProps
|
||||||
|
>(({ id, label, hint, hasError, required, ...otherProps }, ref) => (
|
||||||
|
<FormFieldWrapper
|
||||||
|
label={label}
|
||||||
|
hint={hint}
|
||||||
|
required={required}
|
||||||
|
hasError={hasError}
|
||||||
|
inputId={id}
|
||||||
|
>
|
||||||
|
{(inputProps) => (
|
||||||
|
<PlainToggleField {...otherProps} {...inputProps} ref={ref} />
|
||||||
|
)}
|
||||||
|
</FormFieldWrapper>
|
||||||
|
));
|
||||||
|
|
||||||
|
ToggleField.displayName = 'ToggleField';
|
||||||
|
|
||||||
|
export const PlainToggleField = forwardRef<HTMLInputElement, Props>(
|
||||||
|
({ className, size, ...otherProps }, ref) => (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
{...otherProps}
|
||||||
|
type='checkbox'
|
||||||
|
className={classes.input}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={classNames(classes.toggle, className)}
|
||||||
|
style={
|
||||||
|
{ '--diameter': size ? `${size}px` : undefined } as CSSProperties
|
||||||
|
}
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
PlainToggleField.displayName = 'PlainToggleField';
|
||||||
@@ -43,7 +43,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['app/javascript/**/*.module.scss'],
|
files: ['app/javascript/**/*.module.scss', 'app/javascript/**/*.module.css'],
|
||||||
rules: {
|
rules: {
|
||||||
'selector-pseudo-class-no-unknown': [
|
'selector-pseudo-class-no-unknown': [
|
||||||
true,
|
true,
|
||||||
|
|||||||
Reference in New Issue
Block a user