mirror of https://github.com/portainer/portainer
feat(app): introduce button selector component [EE-2004] (#6112)
parent
17a20cb2c6
commit
8e83a95996
|
@ -82,6 +82,7 @@ overrides:
|
||||||
'@typescript-eslint/explicit-module-boundary-types': off
|
'@typescript-eslint/explicit-module-boundary-types': off
|
||||||
'@typescript-eslint/no-unused-vars': 'error'
|
'@typescript-eslint/no-unused-vars': 'error'
|
||||||
'@typescript-eslint/no-explicit-any': 'error'
|
'@typescript-eslint/no-explicit-any': 'error'
|
||||||
|
'jsx-a11y/label-has-associated-control': ['error', { 'assert': 'either' }]
|
||||||
- files:
|
- files:
|
||||||
- app/**/*.test.*
|
- app/**/*.test.*
|
||||||
extends:
|
extends:
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren } from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
type Size = 'xsmall' | 'small' | 'large';
|
export type Size = 'xsmall' | 'small' | 'large';
|
||||||
export interface Props {
|
export interface Props {
|
||||||
size?: Size;
|
size?: Size;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ButtonGroup({
|
export function ButtonGroup({
|
||||||
size = 'small',
|
size = 'small',
|
||||||
children,
|
children,
|
||||||
|
className,
|
||||||
}: PropsWithChildren<Props>) {
|
}: PropsWithChildren<Props>) {
|
||||||
return (
|
return (
|
||||||
<div className={clsx('btn-group', sizeClass(size))} role="group">
|
<div className={clsx('btn-group', sizeClass(size), className)} role="group">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
.group input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group input:checked + label {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #286090;
|
||||||
|
background-image: none;
|
||||||
|
border-color: #204d74;
|
||||||
|
}
|
|
@ -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<string>[] = [
|
||||||
|
{ value: 'sAMAccountName', label: 'username' },
|
||||||
|
{ value: 'userPrincipalName', label: 'user@domainname' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const [value, setValue] = useState('sAMAccountName');
|
||||||
|
return (
|
||||||
|
<ButtonSelector<string>
|
||||||
|
onChange={handleChange}
|
||||||
|
value={value}
|
||||||
|
options={options}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleChange(value: string) {
|
||||||
|
setValue(value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<T> {
|
||||||
|
value: T;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props<T> {
|
||||||
|
value: T;
|
||||||
|
onChange(value: T): void;
|
||||||
|
options: Option<T>[];
|
||||||
|
size?: Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ButtonSelector<T extends string | number>({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
size,
|
||||||
|
options,
|
||||||
|
}: Props<T>) {
|
||||||
|
return (
|
||||||
|
<ButtonGroup size={size} className={styles.group}>
|
||||||
|
{options.map((option) => (
|
||||||
|
<OptionItem
|
||||||
|
key={option.value}
|
||||||
|
selected={value === option.value}
|
||||||
|
onChange={() => onChange(option.value)}
|
||||||
|
>
|
||||||
|
{option.label || option.value.toString()}
|
||||||
|
</OptionItem>
|
||||||
|
))}
|
||||||
|
</ButtonGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OptionItemProps {
|
||||||
|
selected: boolean;
|
||||||
|
onChange(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function OptionItem({
|
||||||
|
selected,
|
||||||
|
children,
|
||||||
|
onChange,
|
||||||
|
}: PropsWithChildren<OptionItemProps>) {
|
||||||
|
return (
|
||||||
|
<label className={clsx('btn btn-primary', { active: selected })}>
|
||||||
|
{children}
|
||||||
|
<input type="radio" checked={selected} onChange={onChange} />
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue