Skip to content

Commit

Permalink
Added reusable switch component according to design systems (#2892)
Browse files Browse the repository at this point in the history
* Added component, stories and tests

* Changed from tsx styles to scss

* changed import in Switch.spec.jsx

* fixed label output

* changed size props from enum to string

* fixed tests

* fixed curly braces for size in tests

* replace "" to ''
  • Loading branch information
amoutens authored Nov 28, 2024
1 parent 1451bae commit b768459
Show file tree
Hide file tree
Showing 4 changed files with 467 additions and 0 deletions.
135 changes: 135 additions & 0 deletions src/design-system/components/switch/Switch.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
@use '~scss/utilities' as *;

@mixin track-mixin($width, $border-width) {
position: absolute;
background-color: transparent !important;
border: #{$border-width} solid $blue-gray-200;
border-radius: calc(#{$width}px / 3);
opacity: 1 !important;
transition:
background-color 0.3s ease,
border-color 0.3s ease;
}

@mixin thumb-mixin($diameter) {
width: #{$diameter}px;
height: #{$diameter}px;
box-shadow: none;
transition:
transform 0.3s ease,
background-color 0.3s ease;
}

@mixin switch-base-mixin($r-width, $t-width) {
position: relative;
color: $blue-gray-400;
padding: 0;
left: calc(#{$r-width}px / 10);

&.Mui-checked {
color: $blue-gray-800;
left: calc(#{$r-width}px - #{$t-width}px - (#{$r-width}px / 10) - 20px);
}

&.Mui-disabled {
color: $blue-gray-100;
}
}

@mixin root-mixin($width, $height) {
display: flex;
align-items: center;
width: #{$width}px;
height: #{$height}px;
overflow: visible;
padding: 0;
margin: 5px;

&:hover {
.MuiSwitch-track {
border-color: $blue-gray-500;

&.Mui-disabled {
border-color: $blue-gray-100 !important;
}
}
}
}

.#{$prefix}switch {
&--sm {
@include root-mixin(45, 21);

.MuiSwitch-thumb {
@include thumb-mixin(15);
}

.MuiSwitch-track {
@include track-mixin(45, $border-width-sm);
}

.MuiSwitch-switchBase {
@include switch-base-mixin(47, 15);
}
}

&--md {
@include root-mixin(60, 28);

.MuiSwitch-thumb {
@include thumb-mixin(20);
}

.MuiSwitch-track {
@include track-mixin(60, $border-width-md);
}

.MuiSwitch-switchBase {
@include switch-base-mixin(64, 20);
}
}

&--lg {
@include root-mixin(75, 35);

.MuiSwitch-thumb {
@include thumb-mixin(25);
}

.MuiSwitch-track {
@include track-mixin(75, $border-width-md);
}

.MuiSwitch-switchBase {
@include switch-base-mixin(81, 25);
}
}
}

.#{$prefix}form-label-box {
display: flex;
align-items: center;
gap: 10px;
overflow: visible;

&--sm {
.MuiFormControlLabel-label {
color: $body-text-color;
font-size: $font-size-sm !important;
}
}

&--md {
.MuiFormControlLabel-label {
color: $body-text-color;
font-size: $font-size-md !important;
}
}

&--lg {
.MuiFormControlLabel-label {
color: $body-text-color;
font-size: $font-size-lg !important;
}
}
}
32 changes: 32 additions & 0 deletions src/design-system/components/switch/Switch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Switch, { SwitchProps } from '@mui/material/Switch'
import { FormControlLabel } from '@mui/material'
import './Switch.scss'
interface AppSwitchProps extends Omit<SwitchProps, 'size'> {
labelPosition?: 'start' | 'end' | 'top' | 'bottom'
size?: 'sm' | 'md' | 'lg'
label?: string
loading?: boolean
}
export const AppSwitch = ({
labelPosition = 'end',
size = 'md',
label = '',
loading,
disabled,
...props
}: AppSwitchProps) => {
return (
<FormControlLabel
className={`s2s-form-label-box s2s-form-label-box--${size}`}
control={
<Switch
className={`s2s-switch--${size}`}
disabled={loading || disabled}
{...props}
/>
}
label={label}
labelPlacement={labelPosition}
/>
)
}
207 changes: 207 additions & 0 deletions src/design-system/stories/Switch.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { Meta, StoryObj } from '@storybook/react/*'
import { AppSwitch } from '~/design-system/components/switch/Switch'

const meta: Meta<typeof AppSwitch> = {
title: 'Components/AppSwitch',
component: AppSwitch,
parameters: {
layout: 'centered',
docs: {
description: {
component: `
### AppSwitch
The **AppSwitch** component is a flexible switch element used for toggling between two states (on/off) in your application. It includes customizable sizes, states, and label positions to suit various use cases.
---
**Key Features:**
- 🏷️ **Label Customization**: Easily add labels and position them relative to the switch.
- 🖼️ **Sizes**: Supports small, medium, and large sizes.
- 🔄 **Loading State**: Indicate in-progress actions with a loading spinner.
- 🚫 **Disabled State**: Prevent user interaction when required.
This component is ideal for settings toggles, feature switches, and more.
`
}
}
},
tags: ['autodocs'],
argTypes: {
label: {
description: 'The label for the switch.',
control: 'text'
},
labelPosition: {
description: 'The position of the label relative to the switch.',
options: ['start', 'end', 'top', 'bottom'],
control: { type: 'radio' }
},
size: {
description: 'The size of the switch.',
options: ['sm', 'md', 'lg'],
control: { type: 'radio' }
},
loading: {
description: 'Displays a loading state when true.',
control: 'boolean'
},
disabled: {
description: 'Disables the switch when true.',
control: 'boolean'
}
},
args: {
label: 'Switch',
labelPosition: 'end',
size: 'md',
loading: false,
disabled: false
}
}

export default meta
type Story = StoryObj<typeof meta>

export const Default: Story = {
args: {
label: 'Default Switch',
size: 'md',
disabled: false,
loading: false
},
parameters: {
docs: {
description: {
story: 'A default switch with a label positioned on the right.'
}
}
}
}

export const Disabled: Story = {
args: {
label: 'Disabled Switch',
size: 'md',
disabled: true
},
parameters: {
docs: {
description: {
story: 'A switch that is disabled, preventing any interaction.'
}
}
}
}

export const Loading: Story = {
args: {
label: 'Loading Switch',
size: 'md',
loading: true
},
parameters: {
docs: {
description: {
story:
'A switch in a loading state, typically used when an action is in progress.'
}
}
}
}

export const SmallSize: Story = {
args: {
label: 'Small Switch',
size: 'sm',
disabled: false
},
parameters: {
docs: {
description: {
story:
'A small-sized switch, suitable for tight spaces or minimal designs.'
}
}
}
}

export const LargeSize: Story = {
args: {
label: 'Large Switch',
size: 'lg',
disabled: false
},
parameters: {
docs: {
description: {
story:
'A large-sized switch, making it more prominent and easier to interact with.'
}
}
}
}

export const TopPosition: Story = {
args: {
label: 'Top Label Position',
size: 'md',
labelPosition: 'top',
disabled: false
},
parameters: {
docs: {
description: {
story: 'Switch with the label positioned above it.'
}
}
}
}

export const BottomPosition: Story = {
args: {
label: 'Bottom Label Position',
size: 'md',
labelPosition: 'bottom',
disabled: false
},
parameters: {
docs: {
description: {
story: 'Switch with the label positioned below it.'
}
}
}
}

export const StartPosition: Story = {
args: {
label: 'Start Label Position',
size: 'md',
labelPosition: 'start',
disabled: false
},
parameters: {
docs: {
description: {
story: 'Switch with the label positioned to the left.'
}
}
}
}

export const EndPosition: Story = {
args: {
label: 'End Label Position',
size: 'md',
labelPosition: 'end',
disabled: false
},
parameters: {
docs: {
description: {
story: 'Switch with the label positioned to the right.'
}
}
}
}
Loading

0 comments on commit b768459

Please sign in to comment.