mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-10 18:27:25 +03:00
More improvements to form controls with bootstrap 5
This commit is contained in:
parent
dee1932a64
commit
d8e4a4b891
11 changed files with 84 additions and 78 deletions
|
@ -1,7 +1,7 @@
|
|||
import { FC, useState } from 'react';
|
||||
import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
|
||||
import { ShlinkDomain, ShlinkDomainRedirects } from '../../api/types';
|
||||
import { FormGroupContainer, FormGroupContainerProps } from '../../utils/forms/FormGroupContainer';
|
||||
import { InputFormGroup, InputFormGroupProps } from '../../utils/forms/InputFormGroup';
|
||||
import { handleEventPreventingDefault, nonEmptyValueOrNull } from '../../utils/utils';
|
||||
import { InfoTooltip } from '../../utils/InfoTooltip';
|
||||
|
||||
|
@ -12,8 +12,8 @@ interface EditDomainRedirectsModalProps {
|
|||
editDomainRedirects: (domain: string, redirects: Partial<ShlinkDomainRedirects>) => Promise<void>;
|
||||
}
|
||||
|
||||
const FormGroup: FC<FormGroupContainerProps & { isLast?: boolean }> = ({ isLast, ...rest }) => (
|
||||
<FormGroupContainer
|
||||
const FormGroup: FC<InputFormGroupProps & { isLast?: boolean }> = ({ isLast, ...rest }) => (
|
||||
<InputFormGroup
|
||||
{...rest}
|
||||
required={false}
|
||||
type="url"
|
||||
|
|
|
@ -3,8 +3,3 @@
|
|||
.server-form .form-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.server-form__label {
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { FC, ReactNode, useEffect, useState } from 'react';
|
||||
import { FormGroupContainer, FormGroupContainerProps } from '../../utils/forms/FormGroupContainer';
|
||||
import { InputFormGroup } from '../../utils/forms/InputFormGroup';
|
||||
import { handleEventPreventingDefault } from '../../utils/utils';
|
||||
import { ServerData } from '../data';
|
||||
import { SimpleCard } from '../../utils/SimpleCard';
|
||||
|
@ -11,9 +11,6 @@ interface ServerFormProps {
|
|||
title?: ReactNode;
|
||||
}
|
||||
|
||||
const FormGroup: FC<FormGroupContainerProps> = (props) =>
|
||||
<FormGroupContainer {...props} labelClassName="server-form__label" />;
|
||||
|
||||
export const ServerForm: FC<ServerFormProps> = ({ onSubmit, initialValues, children, title }) => {
|
||||
const [ name, setName ] = useState('');
|
||||
const [ url, setUrl ] = useState('');
|
||||
|
@ -29,9 +26,9 @@ export const ServerForm: FC<ServerFormProps> = ({ onSubmit, initialValues, child
|
|||
return (
|
||||
<form className="server-form" onSubmit={handleSubmit}>
|
||||
<SimpleCard className="mb-3" title={title}>
|
||||
<FormGroup value={name} onChange={setName}>Name</FormGroup>
|
||||
<FormGroup type="url" value={url} onChange={setUrl}>URL</FormGroup>
|
||||
<FormGroup value={apiKey} onChange={setApiKey}>API key</FormGroup>
|
||||
<InputFormGroup value={name} onChange={setName}>Name</InputFormGroup>
|
||||
<InputFormGroup type="url" value={url} onChange={setUrl}>URL</InputFormGroup>
|
||||
<InputFormGroup value={apiKey} onChange={setApiKey}>API key</InputFormGroup>
|
||||
</SimpleCard>
|
||||
|
||||
<div className="text-end">{children}</div>
|
||||
|
|
|
@ -4,6 +4,7 @@ import ToggleSwitch from '../utils/ToggleSwitch';
|
|||
import { SimpleCard } from '../utils/SimpleCard';
|
||||
import { FormText } from '../utils/forms/FormText';
|
||||
import { Settings } from './reducers/settings';
|
||||
import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
|
||||
|
||||
interface RealTimeUpdatesProps {
|
||||
settings: Settings;
|
||||
|
@ -25,10 +26,11 @@ const RealTimeUpdatesSettings = (
|
|||
</FormText>
|
||||
</ToggleSwitch>
|
||||
</FormGroup>
|
||||
<div>
|
||||
<label className={classNames('form-label', { 'text-muted': !realTimeUpdates.enabled })}>
|
||||
Real-time updates frequency (in minutes):
|
||||
</label>
|
||||
<LabeledFormGroup
|
||||
noMargin
|
||||
label="Real-time updates frequency (in minutes):"
|
||||
labelClassName={classNames('form-label', { 'text-muted': !realTimeUpdates.enabled })}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
|
@ -47,7 +49,7 @@ const RealTimeUpdatesSettings = (
|
|||
{!realTimeUpdates.interval && 'Updates will be reflected in the UI as soon as they happen.'}
|
||||
</FormText>
|
||||
)}
|
||||
</div>
|
||||
</LabeledFormGroup>
|
||||
</SimpleCard>
|
||||
);
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import ToggleSwitch from '../utils/ToggleSwitch';
|
|||
import { DropdownBtn } from '../utils/DropdownBtn';
|
||||
import { FormText } from '../utils/forms/FormText';
|
||||
import { Settings, ShortUrlCreationSettings as ShortUrlsSettings, TagFilteringMode } from './reducers/settings';
|
||||
import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
|
||||
|
||||
interface ShortUrlCreationProps {
|
||||
settings: Settings;
|
||||
|
@ -50,8 +51,7 @@ export const ShortUrlCreationSettings: FC<ShortUrlCreationProps> = ({ settings,
|
|||
</FormText>
|
||||
</ToggleSwitch>
|
||||
</FormGroup>
|
||||
<div>
|
||||
<label className="form-label">Tag suggestions search mode:</label>
|
||||
<LabeledFormGroup noMargin label="Tag suggestions search mode:">
|
||||
<DropdownBtn text={tagFilteringModeText(shortUrlCreation.tagFilteringMode)}>
|
||||
<DropdownItem
|
||||
active={!shortUrlCreation.tagFilteringMode || shortUrlCreation.tagFilteringMode === 'startsWith'}
|
||||
|
@ -67,7 +67,7 @@ export const ShortUrlCreationSettings: FC<ShortUrlCreationProps> = ({ settings,
|
|||
</DropdownItem>
|
||||
</DropdownBtn>
|
||||
<FormText>{tagFilteringModeHint(shortUrlCreation.tagFilteringMode)}</FormText>
|
||||
</div>
|
||||
</LabeledFormGroup>
|
||||
</SimpleCard>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@ import { OrderingDropdown } from '../utils/OrderingDropdown';
|
|||
import { SHORT_URLS_ORDERABLE_FIELDS } from '../short-urls/data';
|
||||
import { SimpleCard } from '../utils/SimpleCard';
|
||||
import { DEFAULT_SHORT_URLS_ORDERING, Settings, ShortUrlsListSettings as ShortUrlsSettings } from './reducers/settings';
|
||||
import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
|
||||
|
||||
interface ShortUrlsListProps {
|
||||
settings: Settings;
|
||||
|
@ -13,11 +14,12 @@ export const ShortUrlsListSettings: FC<ShortUrlsListProps> = (
|
|||
{ settings: { shortUrlsList }, setShortUrlsListSettings },
|
||||
) => (
|
||||
<SimpleCard title="Short URLs list" className="h-100">
|
||||
<label className="form-label">Default ordering for short URLs list:</label>
|
||||
<OrderingDropdown
|
||||
items={SHORT_URLS_ORDERABLE_FIELDS}
|
||||
order={shortUrlsList?.defaultOrdering ?? DEFAULT_SHORT_URLS_ORDERING}
|
||||
onChange={(field, dir) => setShortUrlsListSettings({ defaultOrdering: { field, dir } })}
|
||||
/>
|
||||
<LabeledFormGroup noMargin label="Default ordering for short URLs list:">
|
||||
<OrderingDropdown
|
||||
items={SHORT_URLS_ORDERABLE_FIELDS}
|
||||
order={shortUrlsList?.defaultOrdering ?? DEFAULT_SHORT_URLS_ORDERING}
|
||||
onChange={(field, dir) => setShortUrlsListSettings({ defaultOrdering: { field, dir } })}
|
||||
/>
|
||||
</LabeledFormGroup>
|
||||
</SimpleCard>
|
||||
);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { FC } from 'react';
|
||||
import { FormGroup } from 'reactstrap';
|
||||
import { SimpleCard } from '../utils/SimpleCard';
|
||||
import { TagsModeDropdown } from '../tags/TagsModeDropdown';
|
||||
import { capitalize } from '../utils/utils';
|
||||
import { OrderingDropdown } from '../utils/OrderingDropdown';
|
||||
import { TAGS_ORDERABLE_FIELDS } from '../tags/data/TagsListChildrenProps';
|
||||
import { FormText } from '../utils/forms/FormText';
|
||||
import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
|
||||
import { Settings, TagsSettings as TagsSettingsOptions } from './reducers/settings';
|
||||
|
||||
interface TagsProps {
|
||||
|
@ -15,22 +15,20 @@ interface TagsProps {
|
|||
|
||||
export const TagsSettings: FC<TagsProps> = ({ settings: { tags }, setTagsSettings }) => (
|
||||
<SimpleCard title="Tags" className="h-100">
|
||||
<FormGroup>
|
||||
<label className="form-label">Default display mode when managing tags:</label>
|
||||
<LabeledFormGroup label="Default display mode when managing tags:">
|
||||
<TagsModeDropdown
|
||||
mode={tags?.defaultMode ?? 'cards'}
|
||||
renderTitle={(tagsMode) => capitalize(tagsMode)}
|
||||
onChange={(defaultMode) => setTagsSettings({ ...tags, defaultMode })}
|
||||
/>
|
||||
<FormText>Tags will be displayed as <b>{tags?.defaultMode ?? 'cards'}</b>.</FormText>
|
||||
</FormGroup>
|
||||
<div>
|
||||
<label className="form-label">Default ordering for tags list:</label>
|
||||
</LabeledFormGroup>
|
||||
<LabeledFormGroup noMargin label="Default ordering for tags list:">
|
||||
<OrderingDropdown
|
||||
items={TAGS_ORDERABLE_FIELDS}
|
||||
order={tags?.defaultOrdering ?? {}}
|
||||
onChange={(field, dir) => setTagsSettings({ ...tags, defaultOrdering: { field, dir } })}
|
||||
/>
|
||||
</div>
|
||||
</LabeledFormGroup>
|
||||
</SimpleCard>
|
||||
);
|
||||
|
|
|
@ -2,6 +2,7 @@ import { FC } from 'react';
|
|||
import { SimpleCard } from '../utils/SimpleCard';
|
||||
import { DateIntervalSelector } from '../utils/dates/DateIntervalSelector';
|
||||
import { Settings, VisitsSettings as VisitsSettingsConfig } from './reducers/settings';
|
||||
import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
|
||||
|
||||
interface VisitsProps {
|
||||
settings: Settings;
|
||||
|
@ -10,11 +11,12 @@ interface VisitsProps {
|
|||
|
||||
export const VisitsSettings: FC<VisitsProps> = ({ settings, setVisitsSettings }) => (
|
||||
<SimpleCard title="Visits" className="h-100">
|
||||
<label className="form-label">Default interval to load on visits sections:</label>
|
||||
<DateIntervalSelector
|
||||
allText="All visits"
|
||||
active={settings.visits?.defaultInterval ?? 'last30Days'}
|
||||
onChange={(defaultInterval) => setVisitsSettings({ defaultInterval })}
|
||||
/>
|
||||
<LabeledFormGroup noMargin label="Default interval to load on visits sections:">
|
||||
<DateIntervalSelector
|
||||
allText="All visits"
|
||||
active={settings.visits?.defaultInterval ?? 'last30Days'}
|
||||
onChange={(defaultInterval) => setVisitsSettings({ defaultInterval })}
|
||||
/>
|
||||
</LabeledFormGroup>
|
||||
</SimpleCard>
|
||||
);
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import { FC, useRef } from 'react';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { InputType } from 'reactstrap/types/lib/Input';
|
||||
import { FormGroup } from 'reactstrap';
|
||||
|
||||
export interface FormGroupContainerProps {
|
||||
value: string;
|
||||
onChange: (newValue: string) => void;
|
||||
id?: string;
|
||||
type?: InputType;
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
labelClassName?: string;
|
||||
}
|
||||
|
||||
export const FormGroupContainer: FC<FormGroupContainerProps> = (
|
||||
{ children, value, onChange, id, type, required, placeholder, className, labelClassName = '' },
|
||||
) => {
|
||||
const forId = useRef<string>(id ?? uuid());
|
||||
|
||||
return (
|
||||
<FormGroup className={className ?? ''}>
|
||||
<label htmlFor={forId.current} className={`form-label ${labelClassName}`}>{children}:</label>
|
||||
<input
|
||||
className="form-control"
|
||||
type={type ?? 'text'}
|
||||
id={forId.current}
|
||||
value={value}
|
||||
required={required ?? true}
|
||||
placeholder={placeholder}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
29
src/utils/forms/InputFormGroup.tsx
Normal file
29
src/utils/forms/InputFormGroup.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { FC } from 'react';
|
||||
import { InputType } from 'reactstrap/types/lib/Input';
|
||||
import { LabeledFormGroup } from './LabeledFormGroup';
|
||||
|
||||
export interface InputFormGroupProps {
|
||||
value: string;
|
||||
onChange: (newValue: string) => void;
|
||||
id?: string;
|
||||
type?: InputType;
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
labelClassName?: string;
|
||||
}
|
||||
|
||||
export const InputFormGroup: FC<InputFormGroupProps> = (
|
||||
{ children, value, onChange, type, required, placeholder, className, labelClassName },
|
||||
) => (
|
||||
<LabeledFormGroup label={<>{children}:</>} className={className ?? ''} labelClassName={labelClassName}>
|
||||
<input
|
||||
className="form-control"
|
||||
type={type ?? 'text'}
|
||||
value={value}
|
||||
required={required ?? true}
|
||||
placeholder={placeholder}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</LabeledFormGroup>
|
||||
);
|
17
src/utils/forms/LabeledFormGroup.tsx
Normal file
17
src/utils/forms/LabeledFormGroup.tsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { FC, ReactNode } from 'react';
|
||||
|
||||
interface LabeledFormGroupProps {
|
||||
label: ReactNode;
|
||||
noMargin?: boolean;
|
||||
className?: string;
|
||||
labelClassName?: string;
|
||||
}
|
||||
|
||||
export const LabeledFormGroup: FC<LabeledFormGroupProps> = (
|
||||
{ children, label, className = '', labelClassName = '', noMargin = false },
|
||||
) => (
|
||||
<div className={`${className} ${noMargin ? '' : 'mb-3'}`}>
|
||||
<label className={`form-label ${labelClassName}`}>{label}</label>
|
||||
{children}
|
||||
</div>
|
||||
);
|
Loading…
Reference in a new issue