jgthms / bulma

Modern CSS framework based on Flexbox
https://bulma.io
MIT License
48.85k stars 3.92k forks source link

Switch component #3764

Open stsrki opened 3 months ago

stsrki commented 3 months ago

Hello. Is there a plan to work on the switch component?

image

jgthms commented 3 months ago

There is not. It's fairly standard to design but it would require JavaScript to work.

stsrki commented 3 months ago

Not necessarily. It can be styled around the <input type="checkbox">. And by targeting the :checked, &:not(:checked) pseudo selectors.

jgthms commented 3 months ago

Yes but that's more of a hack.

Also, on mobile devices, you can drag that control. If you want to replicate that UX, you require Javascript.

Some other Bulma components require a bit of JavaScript so I'll think about it.

stsrki commented 3 months ago

Some other Bulma components require a bit of JavaScript so I'll think about it.

Appreciate it.

jbrazeau commented 3 months ago

I've created a patch for bulma-switch by basically :

The code is available here : https://gitlab.com/jf.brazeau/bulma-switch-migration

The changes are listed here : https://gitlab.com/jf.brazeau/bulma-switch-migration/-/commit/c3a2b3e2e61bfd1d420b73315aba78f963fd62fb

I think I will use the scss directly in my project until the extension is officially migrated.

@jgthms : congratulations for Bulma, really great framework.

digitigradeit commented 3 months ago

@jbrazeau haha you beat me to it!

I was working on it as well. it's remarkable how similar the code is.. Mine loosely based on the /Wikiki/bulma-switch version.

with additional mixin support.

@use "sass:math";
@use "sass:list";

@use "../node_modules/bulma/sass/utilities/initial-variables" as iv;
@use "../node_modules/bulma/sass/utilities/derived-variables" as dv;
@use "../node_modules/bulma/sass/utilities/css-variables" as cv;
@use "../node_modules/bulma/sass/utilities/extends" as ext;
@use "../node_modules/bulma/sass/utilities/controls" as ctrl;

$switch-background: iv.$grey-light !default;
$switch-background-active: dv.$primary !default;
$switch-colors: dv.$colors !default;
$switch-outline-width: ctrl.$control-border-width !default;
$switch-margin: 1rem !default;
$switch-paddle-background: iv.$white !default;
$switch-paddle-background-active: iv.$white !default;
$switch-paddle-offset: 0.25rem !default;
$switch-paddle-transition-duration: cv.getVar("duration") !default;
$switch-paddle-transition-property: background-color, border-color, opacity, left !default;
$switch-paddle-transition-timing-function: cv.getVar("easing") !default;
$switch-paddle-transition: $switch-paddle-transition-property $switch-paddle-transition-duration $switch-paddle-transition-timing-function !default;
$switch-radius: iv.$radius !default;

@mixin switch-color($color-active: $switch-background-active, $color-inactive: $switch-background) {
    &.is-outlined {
        label {
            background-color: transparent;
            outline-style: solid;
            outline-width: ctrl.$control-border-width;
            outline-color: $color-inactive;
        }

        label::before {
            background-color: $switch-paddle-background;
        }

        input:checked~label {
            outline-color: $color-active;
            background-color: transparent;
        }

        input:checked~label::before {
            background-color: $color-active;
        }
    }

    label {
        background-color: $color-inactive;
    }

    input:checked~label {
        background-color: $color-active;

        &::before {
            background-color: $switch-paddle-background-active;
        }
    }
}

// Mixin for switch size
@mixin switch-size($size: dv.$size-normal) {
    $switch-height: $size * 2;
    $switch-width: $switch-height * 2;
    $paddle-height: $switch-height - ($switch-paddle-offset * 2);
    $paddle-width: $switch-height - ($switch-paddle-offset * 2);
    $paddle-active-offset: $switch-width - $paddle-width - $switch-paddle-offset;

    input:checked~label::before {
        left: $paddle-active-offset;
    }

    label {
        position: relative;
        display: block;
        width: $switch-width;
        height: $switch-height;
        border-radius: $switch-radius;

        &::before {
            width: $paddle-width;
            height: $paddle-height;
            top: $switch-paddle-offset;
            left: $switch-paddle-offset;
            border-radius: $switch-radius;
            background-color: $switch-paddle-background;
            transition: $switch-paddle-transition;
        }

        &::after {
            font-size: $size;
            //height: $paddle-height;
            margin-left: $switch-width + 1rem;
        }
    }

    &.is-rounded {
        label {
            border-radius: math.div($switch-height, 2);

            &::before {
                border-radius: 50%;
            }
        }
    }
}

.switch {
    position: relative;
    display: inline-block;
    margin-bottom: $switch-margin;
    outline: none;
    user-select: none;

    input {
        display: none;
        opacity: 0;
    }

    label {
        opacity: 1;
        display: flex;
        align-items: center;
        text-wrap: nowrap;
        vertical-align: middle;
    }

    label::before,
    label:before {
        position: absolute;
        content: '';
        flex-grow: 0;
        flex-shrink: 0;
        display: flex;
    }

    label::after,
    label:after {
        position: absolute;
        //flex-grow: 1;
        //flex-shrink: 0;
        display: flex;
        align-items: center;
        content: attr(data-label-off);
        //width: 100%;
        height: 100%;
    }

    input:checked~label::after {
        content: attr(data-label-on);
    }

    @include switch-color;
    @include switch-size;
}

@each $name, $color in $switch-colors {
    $color-base: $color;

    @if type-of($color =="list") {
        $color-base: list.nth($color, 1);
    }

    .switch.is-#{$name} {
        @include switch-color($color-base);
    }
}

// Sizes
.switch {
    @include switch-size(dv.$size-normal);
}

.switch.is-small {
    @include switch-size(dv.$size-small);
}

.switch.is-normal {
    @include switch-size(dv.$size-normal);
}

.switch.is-medium {
    @include switch-size(dv.$size-medium);
}

.switch.is-large {
    @include switch-size(dv.$size-large);
}

example usage:

<div class="control is-expanded">
     <div class="switch is-outlined is-rounded">
          <input id="exampleSwitch1" type="checkbox" name="exampleSwitch1">
          <label for="exampleSwitch1" data-label-off="Normal switch off" data-label-on="Normal switch on"></label>
     </div>
</div>
<div class="control is-expanded">
     <div class="switch is-small is-info">
          <input id="exampleSwitch2" type="checkbox" name="exampleSwitch2">
          <label for="exampleSwitch2" data-label-off="Small switch off" data-label-on="Small switch on"></label>
     </div>
</div>

I will try to incorporate as much of the :focus (etc) selectors I possibly can into it in the coming days.. Nice job though @jbrazeau

jbrazeau commented 3 months ago

Hi @digitigradeit ! Thank for your reply ! In fact I didn't want to beat anybody 😉 I just need to migrate as soon as possible to bulma 1.0 so that I did need bulma-switch to work quickly. But I'm sure your contribution will be more mature than mine, so I look forward to hearing from you and as soon as possible I'll switch to your version or any officially supported version of bulma-switch (compliant with bulma 1.0).

Note : is using the colors directly like in the initial bulma-switch implementation compliant with the new theming feature of bulma ? I thought I needed to used CSS variables to fully support it. Am I wrong ? (the reason why I used cv.getVar($name))

digitigradeit commented 3 months ago

if you look at buttons, tags, etc anything with color support it usually copies dv.$colors to a local variable i.e. $button-colors and then has a loop referencing that. the color loop is slightly different now with 1.0 however... although with the concept of themes, im not sure which is better. but I would also venture that the entire color loop in general for this component is overkill as most people probably dont have switch skittles scattered on their site, rather a color for on and color for offoff... that why I recreated the mixin I did but also gave it some flexibility (if needed). by default it is grey (off) and primary (on)...

digitigradeit commented 3 months ago

slightly off-topic, i'm also working on a bulma-social (social buttons) for v1.0 too! that'd be a lot of fun and could use help with that as well :)

nioc commented 1 month ago

Hello @jbrazeau @digitigradeit, are you planning to release a npm package with an updated version of the switch?