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: dropdown component #6241

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
39 changes: 39 additions & 0 deletions packages/main/src/plugin/color-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ export class ColorRegistry {
this.initInvertContent();
this.initCardContent();
this.initInputBox();
this.initDropdown();
}

protected initGlobalNav(): void {
Expand Down Expand Up @@ -484,4 +485,42 @@ export class ColorRegistry {
light: colorPalette.gray[500],
});
}

// dropdown boxes
protected initDropdown(): void {
const sNav = 'dropdown-';

this.registerColor(`${sNav}bg`, {
dark: colorPalette.transparent,
light: colorPalette.transparent,
});
this.registerColor(`${sNav}hover-bg`, {
dark: colorPalette.transparent,
light: colorPalette.transparent,
});
this.registerColor(`${sNav}focused-text`, {
dark: colorPalette.white,
light: colorPalette.gray[900],
});
this.registerColor(`${sNav}disabled-text`, {
dark: colorPalette.gray[900],
light: colorPalette.gray[900],
});
this.registerColor(`${sNav}stroke`, {
dark: colorPalette.charcoal[400],
light: colorPalette.charcoal[400],
});
this.registerColor(`${sNav}hover-stroke`, {
dark: colorPalette.purple[400],
light: colorPalette.purple[400],
});
this.registerColor(`${sNav}stroke-error`, {
dark: colorPalette.red[500],
light: colorPalette.red[500],
});
this.registerColor(`${sNav}stroke-readonly`, {
dark: colorPalette.charcoal[100],
light: colorPalette.charcoal[100],
});
}
}
17 changes: 5 additions & 12 deletions packages/renderer/src/lib/pod/DeployPodToKube.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Checkbox from '/@/lib/ui/Checkbox.svelte';
import type { V1Route } from '../../../../main/src/plugin/api/openshift-types';
import MonacoEditor from '../editor/MonacoEditor.svelte';
import Button from '../ui/Button.svelte';
import Dropdown from '../ui/Dropdown.svelte';
import ErrorMessage from '../ui/ErrorMessage.svelte';
import FormPage from '../ui/FormPage.svelte';
import Link from '../ui/Link.svelte';
Expand Down Expand Up @@ -452,17 +453,12 @@ function updateKubeResult() {
{#if createIngress && containerPortArray.length > 1}
<div class="pt-2 pb-4">
<label for="ingress" class="block mb-1 text-sm font-medium text-gray-300">Ingress Host Port:</label>
<select
bind:value="{ingressPort}"
name="serviceName"
id="serviceName"
class=" cursor-default w-full p-2 outline-none text-sm bg-charcoal-800 rounded-sm text-gray-400 placeholder-gray-400"
required>
<Dropdown bind:value="{ingressPort}" name="serviceName" id="serviceName" required>
<option value="" disabled selected>Select a port</option>
{#each containerPortArray as port}
<option value="{port}">{port}</option>
{/each}
</select>
</Dropdown>
<span class="text-gray-300 text-sm ml-1"
>There are multiple exposed ports available. Select the one you want to expose to '/' with the Ingress.
</span>
Expand Down Expand Up @@ -497,16 +493,13 @@ function updateKubeResult() {
{#if allNamespaces}
<div class="pt-2">
<label for="namespaceToUse" class="block mb-1 text-sm font-medium text-gray-400">Kubernetes Namespace:</label>
<select
class="w-full p-2 outline-none text-sm bg-charcoal-800 rounded-sm text-gray-700 placeholder-gray-700"
name="namespaceChoice"
bind:value="{currentNamespace}">
<Dropdown name="namespaceChoice" bind:value="{currentNamespace}">
{#each allNamespaces.items as namespace}
<option value="{namespace.metadata?.name}">
{namespace.metadata?.name}
</option>
{/each}
</select>
</Dropdown>
</div>
{/if}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ContainerIcon from '../images/ContainerIcon.svelte';
import SolidPodIcon from '../images/SolidPodIcon.svelte';
import StatusIcon from '../images/StatusIcon.svelte';
import Button from '../ui/Button.svelte';
import Dropdown from '../ui/Dropdown.svelte';
import ErrorMessage from '../ui/ErrorMessage.svelte';
import FormPage from '../ui/FormPage.svelte';

Expand Down Expand Up @@ -288,14 +289,11 @@ function updatePortExposure(port: number, checked: boolean) {
for="providerConnectionName"
class="p-2 block mb-2 text-sm font-medium rounded bg-zinc-700 text-gray-300"
>Container Engine
<select
class="w-full p-2 outline-none text-sm bg-charcoal-800 rounded-sm text-gray-400 placeholder-gray-400"
name="providerChoice"
bind:value="{selectedProvider}">
<Dropdown name="providerChoice" bind:value="{selectedProvider}">
{#each providerConnections as providerConnection}
<option value="{providerConnection}">{providerConnection.name}</option>
{/each}
</select>
</Dropdown>
</label>
{/if}
{#if providerConnections.length === 1 && selectedProviderConnection?.name}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import type { IConfigurationPropertyRecordedSchema } from '../../../../../main/src/plugin/configuration-registry';
import Dropdown from '../../ui/Dropdown.svelte';

export let record: IConfigurationPropertyRecordedSchema;
export let value: string | undefined;
Expand All @@ -15,8 +16,7 @@ function onInput(event: Event) {
}
</script>

<select
class="border-b block w-full p-1 bg-zinc-700 border-violet-500 text-white text-sm checked:bg-violet-50"
<Dropdown
name="{record.id}"
id="input-standard-{record.id}"
on:input="{onInput}"
Expand All @@ -28,4 +28,4 @@ function onInput(event: Event) {
<option value="{recordEnum}">{recordEnum}</option>
{/each}
{/if}
</select>
</Dropdown>
96 changes: 96 additions & 0 deletions packages/renderer/src/lib/ui/Dropdown.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**********************************************************************
* 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 Dropdown from './Dropdown.svelte';

function renderDropdown(value: string, readonly?: boolean, disabled?: boolean, error?: boolean): void {
render(Dropdown, {
value: value,
disabled: disabled,
readonly: readonly,
error: error,
});
}

test('Expect basic styling', async () => {
const value = 'test';
renderDropdown(value);

const element = screen.getByRole('combobox');
expect(element).toBeInTheDocument();
expect(element).toHaveClass('p-1');
expect(element).toHaveClass('outline-none');
expect(element).toHaveClass('bg-[var(--pd-dropdown-bg)]');
expect(element).toHaveClass('text-sm');
expect(element).toHaveClass('border-transparent');
expect(element).toHaveClass('text-[color:var(--pd-dropdown-focused-text)]');
});

test('Expect basic readonly styling', async () => {
const value = 'test';
renderDropdown(value, true);

const element = screen.getByRole('combobox');
expect(element).toBeInTheDocument();

expect(element).toHaveClass('p-1');
expect(element).toHaveClass('outline-none');
expect(element).toHaveClass('bg-[var(--pd-dropdown-bg)]');
expect(element).toHaveClass('text-sm');
expect(element).toHaveClass('border-transparent');
expect(element).toHaveClass('border-b-[var(--pd-dropdown-stroke-readonly)]');
expect(element).toHaveClass('text-[color:var(--pd-dropdown-disabled-text)]');
});

test('Expect basic disabled styling', async () => {
const value = 'test';
renderDropdown(value, false, true);

const element = screen.getByRole('combobox');
expect(element).toBeInTheDocument();
expect(element).toHaveClass('p-1');
expect(element).toHaveClass('outline-none');
expect(element).toHaveClass('bg-[var(--pd-dropdown-bg)]');
expect(element).toHaveClass('text-sm');
expect(element).toHaveClass('border-transparent');
expect(element).toHaveClass('border-b-[var(--pd-dropdown-stroke-readonly)]');
expect(element).toHaveClass('text-[color:var(--pd-dropdown-disabled-text)]');
});

test('Expect basic error styling', async () => {
const value = 'test';
renderDropdown(value, false, false, true);

const element = screen.getByRole('combobox');
expect(element).toBeInTheDocument();

expect(element).toHaveClass('p-1');
expect(element).toHaveClass('outline-none');
expect(element).toHaveClass('bg-[var(--pd-dropdown-bg)]');
expect(element).toHaveClass('text-sm');
expect(element).toHaveClass('border-transparent');
expect(element).toHaveClass('border-b-[var(--pd-dropdown-stroke-error)]');
expect(element).toHaveClass('hover:border-b-[var(--pd-dropdown-stroke-error)]');
});
31 changes: 31 additions & 0 deletions packages/renderer/src/lib/ui/Dropdown.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script lang="ts">
export let id: string | undefined = undefined;
export let name: string | undefined = undefined;
export let value: unknown;
export let readonly: boolean = false;
export let disabled: boolean = false;
export let required: boolean = false;
export let error: boolean = false;

let enabled: boolean = true;
$: enabled = !readonly && !disabled;
</script>

<select
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO We should not use <select> as it is a bad1 html element, with very hard customization, limiting strongly its usage :(

Footnotes

  1. This is opinionated, based on experience on many projects where select was always annoying, and the result was always using a custom component, (react-select, svelte-select, vue-select etc.) all having for the react 4M download/week.

class="w-full p-1 outline-none bg-[var(--pd-dropdown-bg)] border-[1px] border-transparent text-sm"
class:border-b-[var(--pd-dropdown-stroke)]="{enabled && !error}"
class:border-b-[var(--pd-dropdown-stroke-error)]="{enabled && error}"
class:border-b-[var(--pd-dropdown-stroke-readonly)]="{!enabled}"
class:text-[color:var(--pd-dropdown-focused-text)]="{enabled}"
class:text-[color:var(--pd-dropdown-disabled-text)]="{!enabled}"
class:hover:border-b-[var(--pd-dropdown-hover-stroke)]="{enabled && !error}"
class:hover:border-b-[var(--pd-dropdown-stroke-error)]="{enabled && error}"
class:hover:bg-[var(--pd-dropdown-hover-bg)]="{enabled}"
id="{id}"
name="{name}"
required="{required}"
bind:value="{value}"
aria-label="{$$props['aria-label']}"
aria-invalid="{$$props['aria-invalid']}">
<slot />
</select>