diff --git a/app/javascript/mastodon/components/form_fields/toggle.module.css b/app/javascript/mastodon/components/form_fields/toggle.module.css new file mode 100644 index 0000000000..c2d3f57bcc --- /dev/null +++ b/app/javascript/mastodon/components/form_fields/toggle.module.css @@ -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)))); +} diff --git a/app/javascript/mastodon/components/form_fields/toggle_field.stories.tsx b/app/javascript/mastodon/components/form_fields/toggle_field.stories.tsx new file mode 100644 index 0000000000..260ba4131f --- /dev/null +++ b/app/javascript/mastodon/components/form_fields/toggle_field.stories.tsx @@ -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 ( +