mirror of https://github.com/portainer/portainer
feat(app): introduce input-group component [EE-2062] (#6135)
parent
9ad626b36e
commit
830286c332
|
@ -836,6 +836,10 @@ json-tree .branch-preview {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.space-y-8 > * + * {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.my-8 {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
|
|
|
@ -16,6 +16,7 @@ export function NumberInput({
|
|||
value,
|
||||
className,
|
||||
readonly,
|
||||
placeholder,
|
||||
onChange,
|
||||
}: Props) {
|
||||
return (
|
||||
|
@ -27,6 +28,7 @@ export function NumberInput({
|
|||
disabled={disabled}
|
||||
readonly={readonly}
|
||||
required={required}
|
||||
placeholder={placeholder}
|
||||
onChange={(value) => onChange(parseFloat(value))}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -20,6 +20,7 @@ export function Select<T extends number | string>({
|
|||
disabled,
|
||||
id,
|
||||
required,
|
||||
placeholder,
|
||||
}: Props<T>) {
|
||||
return (
|
||||
<select
|
||||
|
@ -28,6 +29,7 @@ export function Select<T extends number | string>({
|
|||
id={id}
|
||||
required={required}
|
||||
className={clsx(className, 'form-control')}
|
||||
placeholder={placeholder}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{options.map((item) => (
|
||||
|
|
|
@ -17,6 +17,7 @@ export function TextInput({
|
|||
disabled,
|
||||
readonly,
|
||||
required,
|
||||
placeholder,
|
||||
}: TextInputProps) {
|
||||
return (
|
||||
<BaseInput
|
||||
|
@ -28,6 +29,7 @@ export function TextInput({
|
|||
disabled={disabled}
|
||||
readonly={readonly}
|
||||
required={required}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,9 @@ export function Textarea({
|
|||
onChange,
|
||||
value,
|
||||
id,
|
||||
placeholder,
|
||||
disabled,
|
||||
required,
|
||||
}: Props & InputProps) {
|
||||
return (
|
||||
<BaseInput
|
||||
|
@ -20,6 +23,9 @@ export function Textarea({
|
|||
className={className}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ export interface InputProps {
|
|||
className?: string;
|
||||
required?: boolean;
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export interface ChangeProps<T> {
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
import { Meta } from '@storybook/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { InputGroup } from '.';
|
||||
|
||||
export default {
|
||||
component: InputGroup,
|
||||
title: 'Components/Form/InputGroup',
|
||||
} as Meta;
|
||||
|
||||
export function BasicExample() {
|
||||
const [value1, setValue1] = useState('');
|
||||
const [valueNumber, setValueNumber] = useState(0);
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<InputGroup>
|
||||
<InputGroup.Addon>@</InputGroup.Addon>
|
||||
<InputGroup.Input
|
||||
value={value1}
|
||||
onChange={setValue1}
|
||||
placeholder="Username"
|
||||
aria-describedby="basic-addon1"
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<InputGroup.Input
|
||||
value={value1}
|
||||
onChange={setValue1}
|
||||
placeholder="Recipient's username"
|
||||
aria-describedby="basic-addon2"
|
||||
/>
|
||||
<InputGroup.Addon>@example.com</InputGroup.Addon>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<InputGroup.Addon>$</InputGroup.Addon>
|
||||
<InputGroup.NumberInput
|
||||
value={valueNumber}
|
||||
onChange={setValueNumber}
|
||||
aria-label="Amount (to the nearest dollar)"
|
||||
/>
|
||||
<InputGroup.Addon>.00</InputGroup.Addon>
|
||||
</InputGroup>
|
||||
|
||||
<label htmlFor="basic-url">Your vanity URL</label>
|
||||
<InputGroup>
|
||||
<InputGroup.Addon>https://example.com/users/</InputGroup.Addon>
|
||||
<InputGroup.Input
|
||||
value={value1}
|
||||
onChange={setValue1}
|
||||
id="basic-url"
|
||||
aria-describedby="basic-addon3"
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Addons() {
|
||||
const [value1, setValue1] = useState('');
|
||||
const [value2, setValue2] = useState('');
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<InputGroup>
|
||||
<InputGroup.ButtonWrapper>
|
||||
<button className="btn btn-default" type="button">
|
||||
Go!
|
||||
</button>
|
||||
</InputGroup.ButtonWrapper>
|
||||
<InputGroup.Input value={value1} onChange={setValue1} />
|
||||
</InputGroup>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<InputGroup>
|
||||
<InputGroup.Input value={value2} onChange={setValue2} />
|
||||
<InputGroup.Addon>
|
||||
<input type="checkbox" />
|
||||
</InputGroup.Addon>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Sizing() {
|
||||
const [value, setValue] = useState('');
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<InputGroup size="small">
|
||||
<InputGroup.Addon>Small</InputGroup.Addon>
|
||||
<InputGroup.Input value={value} onChange={setValue} />
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<InputGroup.Addon>Default</InputGroup.Addon>
|
||||
<InputGroup.Input value={value} onChange={setValue} />
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup size="large">
|
||||
<InputGroup.Addon>Large</InputGroup.Addon>
|
||||
<InputGroup.Input value={value} onChange={setValue} />
|
||||
</InputGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import clsx from 'clsx';
|
||||
import { createContext, PropsWithChildren, useContext } from 'react';
|
||||
|
||||
const Context = createContext<null | boolean>(null);
|
||||
|
||||
type Size = 'small' | 'large';
|
||||
|
||||
export function useInputGroupContext() {
|
||||
const context = useContext(Context);
|
||||
|
||||
if (context == null) {
|
||||
throw new Error('Should be inside a InputGroup component');
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
size?: Size;
|
||||
}
|
||||
|
||||
export function InputGroup({ children, size }: PropsWithChildren<Props>) {
|
||||
return (
|
||||
<Context.Provider value>
|
||||
<div className={clsx('input-group', sizeClass(size))}>{children}</div>
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function sizeClass(size?: Size) {
|
||||
switch (size) {
|
||||
case 'large':
|
||||
return 'input-group-lg';
|
||||
case 'small':
|
||||
return 'input-group-sm';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import { useInputGroupContext } from './InputGroup';
|
||||
|
||||
export function InputGroupAddon({ children }: PropsWithChildren<unknown>) {
|
||||
useInputGroupContext();
|
||||
|
||||
return <span className="input-group-addon">{children}</span>;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import { useInputGroupContext } from './InputGroup';
|
||||
|
||||
export function InputGroupButtonWrapper({
|
||||
children,
|
||||
}: PropsWithChildren<unknown>) {
|
||||
useInputGroupContext();
|
||||
|
||||
return <span className="input-group-btn">{children}</span>;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { NumberInput, TextInput } from '../Input';
|
||||
|
||||
import { InputGroup as MainComponent } from './InputGroup';
|
||||
import { InputGroupAddon } from './InputGroupAddon';
|
||||
import { InputGroupButtonWrapper } from './InputGroupButtonWrapper';
|
||||
|
||||
interface InputGroupSubComponents {
|
||||
Addon: typeof InputGroupAddon;
|
||||
ButtonWrapper: typeof InputGroupButtonWrapper;
|
||||
Input: typeof TextInput;
|
||||
NumberInput: typeof NumberInput;
|
||||
}
|
||||
|
||||
const InputGroup: typeof MainComponent &
|
||||
InputGroupSubComponents = MainComponent as typeof MainComponent &
|
||||
InputGroupSubComponents;
|
||||
|
||||
InputGroup.Addon = InputGroupAddon;
|
||||
InputGroup.ButtonWrapper = InputGroupButtonWrapper;
|
||||
InputGroup.Input = TextInput;
|
||||
InputGroup.NumberInput = NumberInput;
|
||||
|
||||
export { InputGroup };
|
Loading…
Reference in New Issue