Skip to content

Commit 5b738b0

Browse files
committed
chore: more use of slide toggle
We have toggle switches in several places, but none of them were the same code. This updates the existing SlideToggle to support a few more cases (properties, aria, label on left or right) and adds colors for light mode. The component is then reused for Settings > Preferences and Settings > Proxies. I had to make a couple judgement calls to make things consistent: - The switch (circle) is bigger than before in Preferences. - The proxy text gets dimmer when disabled/off, to match what was happening in preferences. - I added missing hover states, making each one step brighter to match Buttons, except I made the off-hover state purple to match other controls to let you know you can click on it. Signed-off-by: Tim deBoer <[email protected]>
1 parent e31d31e commit 5b738b0

File tree

6 files changed

+194
-43
lines changed

6 files changed

+194
-43
lines changed

packages/main/src/plugin/color-registry.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ export class ColorRegistry {
232232
this.initCardContent();
233233
this.initInputBox();
234234
this.initCheckbox();
235+
this.initToggle();
235236
}
236237

237238
protected initGlobalNav(): void {
@@ -545,7 +546,7 @@ export class ColorRegistry {
545546
});
546547
}
547548

548-
// chexkboxes
549+
// checkboxes
549550
protected initCheckbox(): void {
550551
const sNav = 'input-checkbox-';
551552

@@ -578,4 +579,54 @@ export class ColorRegistry {
578579
light: colorPalette.purple[700],
579580
});
580581
}
582+
583+
// toggles
584+
protected initToggle(): void {
585+
const sNav = 'input-toggle-';
586+
587+
this.registerColor(`${sNav}off-bg`, {
588+
dark: colorPalette.gray[900],
589+
light: colorPalette.gray[900],
590+
});
591+
this.registerColor(`${sNav}off-focused-bg`, {
592+
dark: colorPalette.purple[700],
593+
light: colorPalette.purple[700],
594+
});
595+
this.registerColor(`${sNav}on-bg`, {
596+
dark: colorPalette.purple[500],
597+
light: colorPalette.purple[500],
598+
});
599+
this.registerColor(`${sNav}on-focused-bg`, {
600+
dark: colorPalette.purple[400],
601+
light: colorPalette.purple[600],
602+
});
603+
this.registerColor(`${sNav}switch`, {
604+
dark: colorPalette.white,
605+
light: colorPalette.black,
606+
});
607+
this.registerColor(`${sNav}focused-switch`, {
608+
dark: colorPalette.white,
609+
light: colorPalette.black,
610+
});
611+
this.registerColor(`${sNav}on-text`, {
612+
dark: colorPalette.white,
613+
light: colorPalette.black,
614+
});
615+
this.registerColor(`${sNav}off-text`, {
616+
dark: colorPalette.gray[700],
617+
light: colorPalette.gray[900],
618+
});
619+
this.registerColor(`${sNav}off-disabled-bg`, {
620+
dark: colorPalette.charcoal[900],
621+
light: colorPalette.gray[900],
622+
});
623+
this.registerColor(`${sNav}on-disabled-bg`, {
624+
dark: colorPalette.charcoal[900],
625+
light: colorPalette.gray[900],
626+
});
627+
this.registerColor(`${sNav}disabled-switch`, {
628+
dark: colorPalette.gray[900],
629+
light: colorPalette.charcoal[900],
630+
});
631+
}
581632
}

packages/renderer/src/lib/preferences/PreferencesProxiesRendering.svelte

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { ProxySettings } from '@podman-desktop/api';
44
import { Button, ErrorMessage, Input } from '@podman-desktop/ui-svelte';
55
import { onMount } from 'svelte';
66
7+
import SlideToggle from '../ui/SlideToggle.svelte';
78
import SettingsPage from './SettingsPage.svelte';
89
import { validateProxyAddress } from './Util';
910
@@ -42,8 +43,8 @@ async function updateProxySettings() {
4243
});
4344
}
4445
45-
async function updateProxyState() {
46-
await window.setProxyState(proxyState);
46+
async function updateProxyState(state: boolean) {
47+
await window.setProxyState(state);
4748
}
4849
4950
function validate(event: any) {
@@ -62,19 +63,8 @@ function validate(event: any) {
6263
<div class="flex flex-col bg-[var(--pd-invert-content-card-bg)] rounded-md p-3 space-y-2">
6364
<!-- if proxy is not enabled, display a toggle -->
6465

65-
<label for="toggle-proxy" class="inline-flex relative items-center mt-1 mb-4 cursor-pointer">
66-
<input
67-
type="checkbox"
68-
bind:checked="{proxyState}"
69-
on:change="{() => updateProxyState()}"
70-
id="toggle-proxy"
71-
class="sr-only peer" />
72-
<div
73-
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">
74-
</div>
75-
<span class="ml-3 text-sm font-medium text-[var(--pd-invert-content-card-header-text)]"
76-
>Proxy configuration {proxyState ? 'enabled' : 'disabled'}</span>
77-
</label>
66+
<SlideToggle id="toggle-proxy" bind:checked="{proxyState}" on:checked="{event => updateProxyState(event.detail)}"
67+
>Proxy configuration {proxyState ? 'enabled' : 'disabled'}</SlideToggle>
7868

7969
{#if proxySettings}
8070
<div class="space-y-2">
Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,29 @@
11
<script lang="ts">
22
import type { IConfigurationPropertyRecordedSchema } from '../../../../../main/src/plugin/configuration-registry';
3+
import SlideToggle from '../../ui/SlideToggle.svelte';
34
45
export let record: IConfigurationPropertyRecordedSchema;
56
export let checked = false;
67
export let onChange = async (_id: string, _value: boolean) => {};
78
let invalidEntry = false;
89
9-
function onInput(event: Event) {
10+
function onChecked(state: boolean) {
1011
invalidEntry = false;
11-
const target = event.target as HTMLInputElement;
12-
if (record.id && target.checked !== checked) {
13-
onChange(record.id, target.checked).catch((_: unknown) => (invalidEntry = true));
12+
if (record.id && state !== checked) {
13+
onChange(record.id, state).catch((_: unknown) => (invalidEntry = true));
1414
}
1515
}
1616
</script>
1717

18-
<label class="relative inline-flex items-center cursor-pointer">
19-
<span class="text-xs {checked ? 'text-white' : 'text-gray-700'} mr-3">{checked ? 'Enabled' : 'Disabled'}</span>
20-
<input
21-
on:input="{onInput}"
22-
class="sr-only peer"
23-
bind:checked="{checked}"
24-
name="{record.id}"
25-
type="checkbox"
26-
readonly="{!!record.readonly}"
27-
disabled="{!!record.readonly}"
28-
id="input-standard-{record.id}"
29-
aria-invalid="{invalidEntry}"
30-
aria-label="{record.description || record.markdownDescription}" />
31-
<div
32-
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">
33-
</div>
34-
</label>
18+
<SlideToggle
19+
id="input-standard-{record.id}"
20+
name="{record.id}"
21+
left
22+
bind:checked="{checked}"
23+
on:checked="{event => onChecked(event.detail)}"
24+
readonly="{!!record.readonly}"
25+
disabled="{!!record.readonly}"
26+
aria-invalid="{invalidEntry}"
27+
aria-label="{record.description || record.markdownDescription}">
28+
<span class="text-xs">{checked ? 'Enabled' : 'Disabled'}</span>
29+
</SlideToggle>

packages/renderer/src/lib/ui/ProviderResultPage.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ function onSeverityClicked(severity: 'critical' | 'high' | 'medium' | 'low' | 's
175175
{/if}
176176
{#if provider.state === 'success'}
177177
<SlideToggle
178+
id="{provider.info.id}"
178179
on:checked="{event => onProviderChecked(provider.info.id, event.detail)}"
179180
checked="{selectedProviders.get(provider.info.id) ?? true}" />
180181
{/if}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**********************************************************************
2+
* Copyright (C) 2024 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
***********************************************************************/
18+
19+
/* eslint-disable @typescript-eslint/no-explicit-any */
20+
21+
import '@testing-library/jest-dom/vitest';
22+
23+
import { render, screen } from '@testing-library/svelte';
24+
import { expect, test } from 'vitest';
25+
26+
import SlideToggle from './SlideToggle.svelte';
27+
28+
function renderToggle(id: string, name: string, checked: boolean, disabled?: boolean): void {
29+
render(SlideToggle, {
30+
id: id,
31+
name: name,
32+
checked: checked,
33+
disabled: disabled,
34+
});
35+
}
36+
37+
test('Expect basic styling', async () => {
38+
renderToggle('id', 'test', false);
39+
40+
const input = screen.getByRole('checkbox');
41+
expect(input).toBeInTheDocument();
42+
43+
const toggle = input.parentElement?.children[1];
44+
expect(toggle).not.toBeUndefined();
45+
expect(toggle).toBeInTheDocument();
46+
expect(toggle).toHaveClass('w-9');
47+
expect(toggle).toHaveClass('h-5');
48+
expect(toggle).toHaveClass('rounded-full');
49+
expect(toggle).toHaveClass('after:top-0.5');
50+
expect(toggle).toHaveClass('after:left-0.5');
51+
expect(toggle).toHaveClass('after:rounded-full');
52+
expect(toggle).toHaveClass('after:h-4');
53+
expect(toggle).toHaveClass('after:w-4');
54+
expect(toggle).toHaveClass('bg-[var(--pd-input-toggle-off-bg)]');
55+
expect(toggle).toHaveClass('hover:bg-[var(--pd-input-toggle-off-focused-bg)]');
56+
expect(toggle).toHaveClass('after:bg-[var(--pd-input-toggle-switch)]');
57+
expect(toggle).toHaveClass('hover:after:bg-[var(--pd-input-toggle-focused-switch)]');
58+
expect(toggle).toHaveClass('peer-checked:bg-[var(--pd-input-toggle-on-bg)]');
59+
expect(toggle).toHaveClass('hover:peer-checked:bg-[var(--pd-input-toggle-on-focused-bg)]');
60+
});
61+
62+
test('Expect disabled styling', async () => {
63+
renderToggle('id', 'test', false, true);
64+
65+
const input = screen.getByRole('checkbox');
66+
expect(input).toBeInTheDocument();
67+
68+
const toggle = input.parentElement?.children[1];
69+
expect(toggle).not.toBeUndefined();
70+
expect(toggle).toBeInTheDocument();
71+
expect(toggle).toHaveClass('bg-[var(--pd-input-toggle-off-disabled-bg)]');
72+
expect(toggle).toHaveClass('peer-checked:bg-[var(--pd-input-toggle-on-disabled-bg)]');
73+
expect(toggle).toHaveClass('after:bg-[var(--pd-input-toggle-disabled-switch)]');
74+
});
Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
<script lang="ts">
22
import { createEventDispatcher } from 'svelte';
33
4-
export let checked = false;
4+
export let id: string;
5+
export let name: string | undefined = undefined;
6+
export let checked: boolean = false;
7+
export let readonly: boolean = false;
8+
export let disabled: boolean = false;
9+
export let left: boolean = false;
10+
11+
let enabled: boolean;
12+
$: enabled = !readonly && !disabled;
513
614
const dispatch = createEventDispatcher();
715
@@ -10,10 +18,42 @@ function onInput() {
1018
}
1119
</script>
1220

13-
<label class="h-[20px] relative inline-flex cursor-pointer">
14-
<span class="text-xs {checked ? 'text-white' : 'text-gray-700'} mr-3"></span>
15-
<input on:input="{onInput}" class="sr-only peer" bind:checked="{checked}" type="checkbox" />
16-
<div
17-
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">
21+
<label class="inline-flex items-center cursor-pointer" for="{id}">
22+
{#if left && $$slots}
23+
<span
24+
class="mr-3 text-sm"
25+
class:text-[var(--pd-input-toggle-on-text)]="{checked}"
26+
class:text-[var(--pd-input-toggle-off-text)]="{!checked}"><slot /></span>
27+
{/if}
28+
<div class="relative inline-flex items-center cursor-pointer">
29+
<input
30+
id="{id}"
31+
name="{name}"
32+
type="checkbox"
33+
class="sr-only peer"
34+
on:input="{onInput}"
35+
bind:checked="{checked}"
36+
readonly="{readonly}"
37+
disabled="{disabled}"
38+
aria-invalid="{$$props['aria-invalid']}"
39+
aria-label="{$$props['aria-label']}" />
40+
<div
41+
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"
42+
class:bg-[var(--pd-input-toggle-off-bg)]="{enabled}"
43+
class:hover:bg-[var(--pd-input-toggle-off-focused-bg)]="{enabled}"
44+
class:after:bg-[var(--pd-input-toggle-switch)]="{enabled}"
45+
class:hover:after:bg-[var(--pd-input-toggle-focused-switch)]="{enabled}"
46+
class:peer-checked:bg-[var(--pd-input-toggle-on-bg)]="{enabled}"
47+
class:hover:peer-checked:bg-[var(--pd-input-toggle-on-focused-bg)]="{enabled}"
48+
class:bg-[var(--pd-input-toggle-off-disabled-bg)]="{!enabled}"
49+
class:peer-checked:bg-[var(--pd-input-toggle-on-disabled-bg)]="{!enabled}"
50+
class:after:bg-[var(--pd-input-toggle-disabled-switch)]="{!enabled}">
51+
</div>
1852
</div>
53+
{#if !left && $$slots}
54+
<span
55+
class="ml-3 text-sm"
56+
class:text-[var(--pd-input-toggle-on-text)]="{checked}"
57+
class:text-[var(--pd-input-toggle-off-text)]="{!checked}"><slot /></span>
58+
{/if}
1959
</label>

0 commit comments

Comments
 (0)