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

chore: more use of slide toggle #7169

Merged
merged 1 commit into from
May 14, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
53 changes: 52 additions & 1 deletion packages/main/src/plugin/color-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export class ColorRegistry {
this.initCardContent();
this.initInputBox();
this.initCheckbox();
this.initToggle();
}

protected initGlobalNav(): void {
Expand Down Expand Up @@ -545,7 +546,7 @@ export class ColorRegistry {
});
}

// chexkboxes
// checkboxes
protected initCheckbox(): void {
const sNav = 'input-checkbox-';

Expand Down Expand Up @@ -578,4 +579,54 @@ export class ColorRegistry {
light: colorPalette.purple[700],
});
}

// toggles
protected initToggle(): void {
const sNav = 'input-toggle-';

this.registerColor(`${sNav}off-bg`, {
dark: colorPalette.gray[900],
light: colorPalette.gray[900],
});
this.registerColor(`${sNav}off-focused-bg`, {
dark: colorPalette.purple[700],
light: colorPalette.purple[700],
});
this.registerColor(`${sNav}on-bg`, {
dark: colorPalette.purple[500],
light: colorPalette.purple[500],
});
this.registerColor(`${sNav}on-focused-bg`, {
dark: colorPalette.purple[400],
light: colorPalette.purple[600],
});
this.registerColor(`${sNav}switch`, {
dark: colorPalette.white,
light: colorPalette.black,
});
this.registerColor(`${sNav}focused-switch`, {
dark: colorPalette.white,
light: colorPalette.black,
});
this.registerColor(`${sNav}on-text`, {
dark: colorPalette.white,
light: colorPalette.black,
});
this.registerColor(`${sNav}off-text`, {
dark: colorPalette.gray[700],
light: colorPalette.gray[900],
});
this.registerColor(`${sNav}off-disabled-bg`, {
dark: colorPalette.charcoal[900],
light: colorPalette.gray[900],
});
this.registerColor(`${sNav}on-disabled-bg`, {
dark: colorPalette.charcoal[900],
light: colorPalette.gray[900],
});
this.registerColor(`${sNav}disabled-switch`, {
dark: colorPalette.gray[900],
light: colorPalette.charcoal[900],
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { ProxySettings } from '@podman-desktop/api';
import { Button, ErrorMessage, Input } from '@podman-desktop/ui-svelte';
import { onMount } from 'svelte';

import SlideToggle from '../ui/SlideToggle.svelte';
import SettingsPage from './SettingsPage.svelte';
import { validateProxyAddress } from './Util';

Expand Down Expand Up @@ -42,8 +43,8 @@ async function updateProxySettings() {
});
}

async function updateProxyState() {
await window.setProxyState(proxyState);
async function updateProxyState(state: boolean) {
await window.setProxyState(state);
}

function validate(event: any) {
Expand All @@ -62,19 +63,8 @@ function validate(event: any) {
<div class="flex flex-col bg-[var(--pd-invert-content-card-bg)] rounded-md p-3 space-y-2">
<!-- if proxy is not enabled, display a toggle -->

<label for="toggle-proxy" class="inline-flex relative items-center mt-1 mb-4 cursor-pointer">
<input
type="checkbox"
bind:checked="{proxyState}"
on:change="{() => updateProxyState()}"
id="toggle-proxy"
class="sr-only peer" />
<div
class="w-9 h-5 rounded-full peer bg-zinc-400 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-400 after:border after:rounded-full after:h-4 after:w-4 after:transition-all border-gray-900 peer-checked:bg-violet-600">
</div>
<span class="ml-3 text-sm font-medium text-[var(--pd-invert-content-card-header-text)]"
>Proxy configuration {proxyState ? 'enabled' : 'disabled'}</span>
</label>
<SlideToggle id="toggle-proxy" bind:checked="{proxyState}" on:checked="{event => updateProxyState(event.detail)}"
>Proxy configuration {proxyState ? 'enabled' : 'disabled'}</SlideToggle>

{#if proxySettings}
<div class="space-y-2">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
<script lang="ts">
import type { IConfigurationPropertyRecordedSchema } from '../../../../../main/src/plugin/configuration-registry';
import SlideToggle from '../../ui/SlideToggle.svelte';

export let record: IConfigurationPropertyRecordedSchema;
export let checked = false;
export let onChange = async (_id: string, _value: boolean) => {};
let invalidEntry = false;

function onInput(event: Event) {
function onChecked(state: boolean) {
invalidEntry = false;
const target = event.target as HTMLInputElement;
if (record.id && target.checked !== checked) {
onChange(record.id, target.checked).catch((_: unknown) => (invalidEntry = true));
if (record.id && state !== checked) {
onChange(record.id, state).catch((_: unknown) => (invalidEntry = true));
}
}
</script>

<label class="relative inline-flex items-center cursor-pointer">
<span class="text-xs {checked ? 'text-white' : 'text-gray-700'} mr-3">{checked ? 'Enabled' : 'Disabled'}</span>
<input
on:input="{onInput}"
class="sr-only peer"
bind:checked="{checked}"
name="{record.id}"
type="checkbox"
readonly="{!!record.readonly}"
disabled="{!!record.readonly}"
id="input-standard-{record.id}"
aria-invalid="{invalidEntry}"
aria-label="{record.description || record.markdownDescription}" />
<div
class="w-8 h-[20px] bg-gray-900 rounded-full peer peer-checked:after:translate-x-full after:bg-charcoal-600 after:content-[''] after:absolute after:top-[4px] after:left-[61px] after:rounded-full after:h-3 after:w-3 after:transition-all peer-checked:bg-violet-600">
</div>
</label>
<SlideToggle
id="input-standard-{record.id}"
name="{record.id}"
left
bind:checked="{checked}"
on:checked="{event => onChecked(event.detail)}"
readonly="{!!record.readonly}"
disabled="{!!record.readonly}"
aria-invalid="{invalidEntry}"
aria-label="{record.description || record.markdownDescription}">
<span class="text-xs">{checked ? 'Enabled' : 'Disabled'}</span>
</SlideToggle>
1 change: 1 addition & 0 deletions packages/renderer/src/lib/ui/ProviderResultPage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ function onSeverityClicked(severity: 'critical' | 'high' | 'medium' | 'low' | 's
{/if}
{#if provider.state === 'success'}
<SlideToggle
id="{provider.info.id}"
on:checked="{event => onProviderChecked(provider.info.id, event.detail)}"
checked="{selectedProviders.get(provider.info.id) ?? true}" />
{/if}
Expand Down
74 changes: 74 additions & 0 deletions packages/renderer/src/lib/ui/SlideToggle.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**********************************************************************
* Copyright (C) 2024 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

/* eslint-disable @typescript-eslint/no-explicit-any */

import '@testing-library/jest-dom/vitest';

import { render, screen } from '@testing-library/svelte';
import { expect, test } from 'vitest';

import SlideToggle from './SlideToggle.svelte';

function renderToggle(id: string, name: string, checked: boolean, disabled?: boolean): void {
render(SlideToggle, {
id: id,
name: name,
checked: checked,
disabled: disabled,
});
}

test('Expect basic styling', async () => {
renderToggle('id', 'test', false);

const input = screen.getByRole('checkbox');
expect(input).toBeInTheDocument();

const toggle = input.parentElement?.children[1];
expect(toggle).not.toBeUndefined();
expect(toggle).toBeInTheDocument();
expect(toggle).toHaveClass('w-9');
expect(toggle).toHaveClass('h-5');
expect(toggle).toHaveClass('rounded-full');
expect(toggle).toHaveClass('after:top-0.5');
expect(toggle).toHaveClass('after:left-0.5');
expect(toggle).toHaveClass('after:rounded-full');
expect(toggle).toHaveClass('after:h-4');
expect(toggle).toHaveClass('after:w-4');
expect(toggle).toHaveClass('bg-[var(--pd-input-toggle-off-bg)]');
expect(toggle).toHaveClass('hover:bg-[var(--pd-input-toggle-off-focused-bg)]');
expect(toggle).toHaveClass('after:bg-[var(--pd-input-toggle-switch)]');
expect(toggle).toHaveClass('hover:after:bg-[var(--pd-input-toggle-focused-switch)]');
expect(toggle).toHaveClass('peer-checked:bg-[var(--pd-input-toggle-on-bg)]');
expect(toggle).toHaveClass('hover:peer-checked:bg-[var(--pd-input-toggle-on-focused-bg)]');
});

test('Expect disabled styling', async () => {
renderToggle('id', 'test', false, true);

const input = screen.getByRole('checkbox');
expect(input).toBeInTheDocument();

const toggle = input.parentElement?.children[1];
expect(toggle).not.toBeUndefined();
expect(toggle).toBeInTheDocument();
expect(toggle).toHaveClass('bg-[var(--pd-input-toggle-off-disabled-bg)]');
expect(toggle).toHaveClass('peer-checked:bg-[var(--pd-input-toggle-on-disabled-bg)]');
expect(toggle).toHaveClass('after:bg-[var(--pd-input-toggle-disabled-switch)]');
});
52 changes: 46 additions & 6 deletions packages/renderer/src/lib/ui/SlideToggle.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';

export let checked = false;
export let id: string;
export let name: string | undefined = undefined;
export let checked: boolean = false;
export let readonly: boolean = false;
export let disabled: boolean = false;
export let left: boolean = false;

let enabled: boolean;
$: enabled = !readonly && !disabled;

const dispatch = createEventDispatcher();

Expand All @@ -10,10 +18,42 @@ function onInput() {
}
</script>

<label class="h-[20px] relative inline-flex cursor-pointer">
<span class="text-xs {checked ? 'text-white' : 'text-gray-700'} mr-3"></span>
<input on:input="{onInput}" class="sr-only peer" bind:checked="{checked}" type="checkbox" />
<div
class="w-8 h-[20px] bg-gray-900 rounded-full peer peer-checked:after:translate-x-full after:bg-charcoal-600 after:content-[''] after:absolute after:top-[4px] after:left-[16px] after:rounded-full after:h-3 after:w-3 after:transition-all peer-checked:bg-violet-600">
<label class="inline-flex items-center cursor-pointer" for="{id}">
{#if left && $$slots}
<span
class="mr-3 text-sm"
class:text-[var(--pd-input-toggle-on-text)]="{checked}"
class:text-[var(--pd-input-toggle-off-text)]="{!checked}"><slot /></span>
{/if}
<div class="relative inline-flex items-center cursor-pointer">
<input
id="{id}"
name="{name}"
type="checkbox"
class="sr-only peer"
on:input="{onInput}"
bind:checked="{checked}"
readonly="{readonly}"
disabled="{disabled}"
aria-invalid="{$$props['aria-invalid']}"
aria-label="{$$props['aria-label']}" />
<div
class="w-9 h-5 rounded-full peer peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-0.5 after:left-0.5 after:rounded-full after:h-4 after:w-4 after:transition-all"
class:bg-[var(--pd-input-toggle-off-bg)]="{enabled}"
class:hover:bg-[var(--pd-input-toggle-off-focused-bg)]="{enabled}"
class:after:bg-[var(--pd-input-toggle-switch)]="{enabled}"
class:hover:after:bg-[var(--pd-input-toggle-focused-switch)]="{enabled}"
class:peer-checked:bg-[var(--pd-input-toggle-on-bg)]="{enabled}"
class:hover:peer-checked:bg-[var(--pd-input-toggle-on-focused-bg)]="{enabled}"
class:bg-[var(--pd-input-toggle-off-disabled-bg)]="{!enabled}"
class:peer-checked:bg-[var(--pd-input-toggle-on-disabled-bg)]="{!enabled}"
class:after:bg-[var(--pd-input-toggle-disabled-switch)]="{!enabled}">
</div>
</div>
{#if !left && $$slots}
<span
class="ml-3 text-sm"
class:text-[var(--pd-input-toggle-on-text)]="{checked}"
class:text-[var(--pd-input-toggle-off-text)]="{!checked}"><slot /></span>
{/if}
</label>