From 8e83a959965c0c265c3725758766bffb83d4ccc0 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Sun, 21 Nov 2021 11:39:26 +0200 Subject: [PATCH] feat(app): introduce button selector component [EE-2004] (#6112) --- .eslintrc.yml | 1 + .../components/Button/ButtonGroup.tsx | 6 +- .../ButtonSelector/ButtonSelector.module.css | 10 ++++ .../ButtonSelector/ButtonSelector.stories.tsx | 29 ++++++++++ .../ButtonSelector/ButtonSelector.tsx | 57 +++++++++++++++++++ 5 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 app/portainer/components/form-components/ButtonSelector/ButtonSelector.module.css create mode 100644 app/portainer/components/form-components/ButtonSelector/ButtonSelector.stories.tsx create mode 100644 app/portainer/components/form-components/ButtonSelector/ButtonSelector.tsx diff --git a/.eslintrc.yml b/.eslintrc.yml index ef7f9f088..5eb9c8d35 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -82,6 +82,7 @@ overrides: '@typescript-eslint/explicit-module-boundary-types': off '@typescript-eslint/no-unused-vars': 'error' '@typescript-eslint/no-explicit-any': 'error' + 'jsx-a11y/label-has-associated-control': ['error', { 'assert': 'either' }] - files: - app/**/*.test.* extends: diff --git a/app/portainer/components/Button/ButtonGroup.tsx b/app/portainer/components/Button/ButtonGroup.tsx index 89b966fb7..f807f28d8 100644 --- a/app/portainer/components/Button/ButtonGroup.tsx +++ b/app/portainer/components/Button/ButtonGroup.tsx @@ -1,17 +1,19 @@ import { PropsWithChildren } from 'react'; import clsx from 'clsx'; -type Size = 'xsmall' | 'small' | 'large'; +export type Size = 'xsmall' | 'small' | 'large'; export interface Props { size?: Size; + className?: string; } export function ButtonGroup({ size = 'small', children, + className, }: PropsWithChildren) { return ( -
+
{children}
); diff --git a/app/portainer/components/form-components/ButtonSelector/ButtonSelector.module.css b/app/portainer/components/form-components/ButtonSelector/ButtonSelector.module.css new file mode 100644 index 000000000..c5cf575b5 --- /dev/null +++ b/app/portainer/components/form-components/ButtonSelector/ButtonSelector.module.css @@ -0,0 +1,10 @@ +.group input { + display: none; +} + +.group input:checked + label { + color: #fff; + background-color: #286090; + background-image: none; + border-color: #204d74; +} diff --git a/app/portainer/components/form-components/ButtonSelector/ButtonSelector.stories.tsx b/app/portainer/components/form-components/ButtonSelector/ButtonSelector.stories.tsx new file mode 100644 index 000000000..74ba8fb3c --- /dev/null +++ b/app/portainer/components/form-components/ButtonSelector/ButtonSelector.stories.tsx @@ -0,0 +1,29 @@ +import { Meta } from '@storybook/react'; +import { useState } from 'react'; + +import { ButtonSelector, Option } from './ButtonSelector'; + +export default { + component: ButtonSelector, + title: 'Components/ButtonSelector', +} as Meta; + +export function TwoOptionsSelector() { + const options: Option[] = [ + { value: 'sAMAccountName', label: 'username' }, + { value: 'userPrincipalName', label: 'user@domainname' }, + ]; + + const [value, setValue] = useState('sAMAccountName'); + return ( + + onChange={handleChange} + value={value} + options={options} + /> + ); + + function handleChange(value: string) { + setValue(value); + } +} diff --git a/app/portainer/components/form-components/ButtonSelector/ButtonSelector.tsx b/app/portainer/components/form-components/ButtonSelector/ButtonSelector.tsx new file mode 100644 index 000000000..fc9457feb --- /dev/null +++ b/app/portainer/components/form-components/ButtonSelector/ButtonSelector.tsx @@ -0,0 +1,57 @@ +import clsx from 'clsx'; +import { PropsWithChildren } from 'react'; + +import { ButtonGroup, Size } from '../../Button/ButtonGroup'; + +import styles from './ButtonSelector.module.css'; + +export interface Option { + value: T; + label?: string; +} + +interface Props { + value: T; + onChange(value: T): void; + options: Option[]; + size?: Size; +} + +export function ButtonSelector({ + value, + onChange, + size, + options, +}: Props) { + return ( + + {options.map((option) => ( + onChange(option.value)} + > + {option.label || option.value.toString()} + + ))} + + ); +} + +interface OptionItemProps { + selected: boolean; + onChange(): void; +} + +function OptionItem({ + selected, + children, + onChange, +}: PropsWithChildren) { + return ( + + ); +}